Commit Graph

3533 Commits

Author SHA1 Message Date
YeonGyu-Kim 0d5b087440 Revert "Merge pull request #3260 from code-yeongyu/fix/remove-zwsp-sort-prefixes"
This reverts commit c3be4c2793, reversing
changes made to d2bb5d57d1.
2026-04-09 12:21:02 +09:00
YeonGyu-Kim 99a6d50db5 Merge pull request #3197 from code-yeongyu/fix/delegate-task-depth-guard
fix(delegate-task): tighten subagent depth guard + add regression smoke tests
2026-04-09 11:29:29 +09:00
YeonGyu-Kim 524e0f6cbd Merge pull request #3170 from code-yeongyu/fix/gpt-apply-patch
fix(agents): deny apply_patch for GPT models to prevent verification hangs (#2935)
2026-04-09 11:14:21 +09:00
YeonGyu-Kim 21127b0308 Merge remote-tracking branch 'origin/dev' into fix/gpt-apply-patch-rebased 2026-04-09 11:06:36 +09:00
YeonGyu-Kim 545c444a85 Merge remote-tracking branch 'origin/dev' into fix/delegate-task-depth-guard-rebased 2026-04-09 11:06:31 +09:00
YeonGyu-Kim defe4e511c Merge remote-tracking branch 'origin/dev' into fix/doctor-custom-providers-rebased 2026-04-09 11:03:12 +09:00
YeonGyu-Kim 57ebb11e68 Merge pull request #3262 from code-yeongyu/fix/migration-sidecar
fix(migration): track applied migrations in sidecar so user reverts stick
2026-04-09 11:01:02 +09:00
YeonGyu-Kim 00a4f318ef fix(migration): track applied migrations in sidecar so user reverts stick
Users who auto-migrated from `openai/gpt-5.3-codex` to `openai/gpt-5.4`
and then reverted their config back to `gpt-5.3-codex` by hand had the
migration re-apply on every startup in an infinite loop. Discord bug
report pointed at the exact symptom: "i deleted the migrations and they
kept coming back".

The old migration tracking lived on the config body itself as a
`_migrations` string array. The skip-already-applied check relied on
the user not touching that field. But users hit by the unwanted
migration naturally reached for the JSON file to roll their model back,
and the natural human reaction to an incomprehensible internal field
next to their config is to delete it. That wiped the migration memory
and let the same migration re-apply at the next startup.

This PR introduces a sidecar state file that lives next to the config
as `<configPath>.migrations.json` and tracks applied migrations
outside the user's hand-editable config body. The migration pipeline:

  1. Reads applied migrations from BOTH the sidecar AND the legacy
     in-config `_migrations` field, unioning them. This keeps old
     configs that still carry `_migrations` working without forcing
     a reset.

  2. Writes the updated migration set to the sidecar, never to the
     config body.

  3. Strips the legacy `_migrations` field out of the config body on
     the first write after the sidecar takes over. Users stop seeing
     the mystery internal field in their own config from that point
     forward.

If the user also deletes the sidecar (explicit fresh-start gesture)
the migrations run again - that is intentional.

Tests (TDD, all new tests written before implementation):

  - src/shared/migration/migrations-sidecar.test.ts - 11 unit tests
    covering read/write/round-trip, malformed-payload resilience,
    parent-directory creation, sorted output for stable diffs, and
    non-string entry filtering.

  - src/shared/migration.test.ts - 6 new integration tests under the
    "migrateConfigFile with migration tracking via sidecar" block
    covering: no-op path, sidecar-only write, sidecar skip after user
    revert, legacy _migrations mirroring + strip, sidecar + legacy
    union with dedupe, and partial-history append. Existing
    "preserves existing _migrations and appends new ones" test was
    rewritten to assert the new sidecar-based contract.

  - Also fixes a latent test-hygiene bug: the shared
    /tmp/nonexistent-path-for-test.json config path used by many
    migrateConfigFile tests did not clean up its companion sidecar
    between tests, letting state from one test bleed into the next.
    Added afterEach that unlinks the sidecar.

Verified:

  - bun test src/shared/migration/ -> 11 new sidecar tests pass
  - bun test src/shared/migration.test.ts -> 82 pass, 0 fail
  - bun run typecheck -> clean
  - bun run script/run-ci-tests.ts -> 4458 pass, 0 fail (full suite)
2026-04-09 10:54:25 +09:00
YeonGyu-Kim 8b418ea38a fix(agents): remove ZWSP sort prefixes from display name helper (#3259)
AGENT_LIST_SORT_PREFIXES prepended U+200B Zero Width Space characters
to the four core agent display names so they would sort ahead of user
agents in the Tab cycle. Two problems with that approach surfaced:

  1. Some terminal emulators (Ghostty, certain Windows Terminal
     builds) render ZWSP as a visible box or extra space, producing a
     visible black gap in the status bar before "Sisyphus" and
     misaligning the layout (#3259).

  2. The prefixes leaked into the plugin API surface via config.agent
     keys, breaking prompt_async consumers that received
     ZWSP-contaminated agent names (#3238).

#3242 already removed every call site of getAgentListDisplayName() in
production code. That made the sort prefixes dead code: the constant
table was still defined but nothing read it. This PR finishes the
cleanup by:

  - Deleting the AGENT_LIST_SORT_PREFIXES constant entirely
  - Turning getAgentListDisplayName() into a thin alias over
    getAgentDisplayName() for BC with external importers
  - Keeping stripAgentListSortPrefix() as a legacy data migration for
    users upgrading from v3.14.0-v3.16.0 whose config.agent keys may
    still have ZWSP baked in from the old code path
  - Documenting the history on stripAgentListSortPrefix() so future
    maintainers understand why the stripper has to stay even after
    the injector is gone

Sort ordering is preserved via JS object insertion order in
reorderAgentsByPriority() plus the `order` field it injects on the
four core agents. Both mechanisms are already in place and both
pre-date this PR; the ZWSP prefix was an older third layer that was
only meant to work around alphabetical sorting in legacy OpenCode
before the `order` field landed upstream.

Tests: 4445 pass, 0 fail. Added 3 new assertions to
agent-display-names.test.ts verifying that getAgentListDisplayName
returns plain names containing no zero-width characters. Updated
chat-message.test.ts to use a literal ZWSP string instead of the
helper so the defensive-strip path still has coverage.

Closes #3259
2026-04-09 10:10:12 +09:00
YeonGyu-Kim 4344a41eae fix(auto-update): resolve cached version when installed as oh-my-openagent (#3257)
The auto-update-checker's version resolver hardcoded the canonical
oh-my-opencode package name in three read paths:

  1. INSTALLED_PACKAGE_JSON pointed only at
     cache/node_modules/oh-my-opencode/package.json
  2. findPackageJsonUp() rejected any walked-up package.json whose name
     did not equal PACKAGE_NAME
  3. getLocalDevPath() only matched file:// plugin entries whose path
     contained the canonical name

The publish pipeline ships the same code under two npm package names
(oh-my-opencode canonical, oh-my-openagent alias). Users who add
"oh-my-openagent" to their opencode config end up with
node_modules/oh-my-openagent/package.json, so every read path above
silently missed the installed version and the startup toast fell back
to "unknown".

Introduce ACCEPTED_PACKAGE_NAMES + INSTALLED_PACKAGE_JSON_CANDIDATES in
constants.ts and teach the three readers to accept both names. Writes
are untouched (sync-package-json, pinned-version-updater, cache
invalidation) because the auto-update-checker still owns its own cache
workspace and writes to the canonical name there.

Tests: 54 auto-update-checker tests pass (4 new), full 4444-test suite
passes, tsc clean. New tests cover both install paths, the walk-up
resolver, and the priority order when both candidates exist.

Closes #3257
2026-04-09 07:29:07 +09:00
YeonGyu-Kim 6943b6d4d8 fix(ci): isolate model-resolver and prometheus-config tests from mock.module contamination
model-resolver.test.ts and prometheus-agent-config-builder.test.ts use
spyOn(shared, 'log') but do not own the logger module. When other test
files in the same bun test process call mock.module('../shared/logger'),
the import cache is poisoned and the spyOn targets a stale binding.

Add a lightweight mock.module call at the top of each file so the
auto-detection in run-ci-tests.ts picks them up as isolated targets.
This ensures each file gets its own module instance and the spy
captures all calls correctly.

Fixes the flaky CI failure pattern where resolveModelWithFallback and
buildPrometheusAgentConfig tests pass locally (separate bun process)
but fail in the shared CI batch.
2026-04-08 23:39:43 +09:00
YeonGyu-Kim 47283f9238 fix(agents): remove ZWSP prefixes from config.agent keys (#3238)
Agent names in the config.agent object (which becomes the /agent API
response) contained invisible Zero-Width Space (U+200B) characters
baked in by getAgentListDisplayName(). These ZWSP prefixes were used
for TUI sort ordering, but they leaked into the public API surface.

Impact: any prompt_async consumer that discovered agent names via the
/agent endpoint and passed them back to prompt_async without manual
ZWSP stripping got silent message drops — the agent name didn't match.
hy-pony's feishu-bridge integration went dark after upgrading to 3.16.0
with no error, no warning, and no indication that invisible Unicode
characters in agent names were the cause.

Fix: switch all four callsites from getAgentListDisplayName() (which
prepends \u200B×N) to getAgentDisplayName() (clean names):

- agent-key-remapper.ts: config keys → display names (was the primary
  injection point)
- agent-priority-order.ts: CORE_AGENT_ORDER lookup (must agree with
  the keys emitted by the remapper)
- command-config-handler.ts: command agent field normalization
- tool-config-handler.ts: agent config lookup (simplified fallback
  chain since the primary lookup is now clean)

Sort ordering is preserved by:
1. JS object insertion order from reorderAgentsByPriority()
2. The injected `order` field (1-4) added by injectOrderField()

getAgentListDisplayName() is marked @deprecated with a link to #3238.
AGENT_LIST_SORT_PREFIXES and stripAgentListSortPrefix() are kept for
any internal callers that strip prefixes from legacy data.

Closes #3238
2026-04-08 22:51:59 +09:00
YeonGyu-Kim 1ea0ee4319 fix(preemptive-compaction): notify user on failure and reduce timeout
Reports from david_66 on Discord: sessions get perceived as 'stuck' when
context usage crosses the 78% threshold. Investigation confirmed two
issues in the preemptive compaction hook:

1. PREEMPTIVE_COMPACTION_TIMEOUT_MS was 120s. While the summarize
   request is in flight, tool.execute.after short-circuits via the
   compactionInProgress guard. A hung summarize blocked the session
   for two full minutes before giving up, which users reasonably
   experience as a hang.

2. On failure (timeout or exception) only a log line was emitted. The
   user had no visibility into why their session was unresponsive or
   why auto-compaction never ran, so a transient upstream error could
   silently leave them well above the threshold with no signal.

Fix:

- Reduce timeout 120s -> 60s. Still gives the upstream a generous
  window, but caps the worst-case perceived hang at one minute.
- Show a warning toast via ctx.client.tui.showToast whenever the
  catch block fires, including the underlying error string so users
  can act (retry, manual /compact, or adjust provider).
- Include providerID/modelID in the Compaction failed log entry so
  wild failures are easier to correlate to a specific target model.

Two existing failure-path assertions were updated to match the new
log shape and a new test covers the toast notification contract.

Discord report: https://discord.com/channels/1452487457085063218/1490536332961906829/1491345441399505037
2026-04-08 20:10:25 +09:00
YeonGyu-Kim c5b6d59c94 Merge pull request #3227 from code-yeongyu/fix/installer-and-auto-update
fix(installer): enforce min version + upgrade pinned entries
2026-04-08 17:40:54 +09:00
YeonGyu-Kim d781b7537f Merge pull request #3226 from code-yeongyu/fix/plan-progress-and-trust
fix(plan): skill MCP trust + non-ASCII names + simple-mode progress + ralph-loop cap
2026-04-08 17:40:51 +09:00
YeonGyu-Kim bdf6e4195a Merge pull request #3225 from code-yeongyu/fix/agent-name-safety
fix(agent): narrow ULW auto-start + strip ZWSP on background path + fix auto-update identity
2026-04-08 17:40:48 +09:00
YeonGyu-Kim c37e440ba8 Merge pull request #3224 from code-yeongyu/fix/oauth-wiring
fix(oauth): wire refresh mutex + post-request auth handler
2026-04-08 17:40:46 +09:00
YeonGyu-Kim 5abab08eef test(tmux): add missing tmux exports to zombie-pane mock module 2026-04-08 17:36:28 +09:00
YeonGyu-Kim 7f8ed7b056 test(tmux): add missing tmux exports to zombie-pane mock module 2026-04-08 17:36:27 +09:00
YeonGyu-Kim 600d68da04 test(tmux): add missing tmux exports to zombie-pane mock module 2026-04-08 17:36:26 +09:00
YeonGyu-Kim d53be83634 test(tmux): add missing tmux exports to zombie-pane mock module 2026-04-08 17:36:24 +09:00
YeonGyu-Kim 4c0225a23f test(tmux): add missing tmux exports to zombie-pane mock module 2026-04-08 17:36:23 +09:00
YeonGyu-Kim 85fa939051 test(skill-mcp): fix connection env var tests after oauth-handler import changes
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 17:26:00 +09:00
YeonGyu-Kim 119c23342a test(background): fix variant propagation test to match parent-context resolution 2026-04-08 17:25:54 +09:00
YeonGyu-Kim 6d8d82d760 fix(installer): enforce minimum OpenCode version check during install
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 17:20:00 +09:00
YeonGyu-Kim 389b194fbf fix(installer): actually upgrade pinned plugin version instead of preserving old entry
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 17:19:52 +09:00
YeonGyu-Kim bbbbf68382 fix(ralph-loop): update template to reflect 500 iteration cap
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 17:18:37 +09:00
YeonGyu-Kim 0cb938e3ac fix(boulder): count only top-level checkboxes in simple-mode plan progress
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 17:18:29 +09:00
YeonGyu-Kim aa0de17f03 fix(start-work): preserve non-ASCII characters in plan name normalization
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 17:18:23 +09:00
YeonGyu-Kim 001d29ea8e fix(skill-mcp): treat opencode-project and local scopes as untrusted for env var access
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 17:18:17 +09:00
YeonGyu-Kim 15e3b14cca fix(auto-update): match both canonical and legacy plugin names in entry finder
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 17:17:41 +09:00
YeonGyu-Kim 359f74132a fix(delegate-task): strip ZWSP from agent names on background launch path
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 17:17:26 +09:00
YeonGyu-Kim 917ae4dfc3 fix(keyword-detector): narrow ULW auto-start to leading keyword position only
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 17:17:14 +09:00
YeonGyu-Kim 0479693ca3 fix(oauth): wire post-request 401/403 handler into skill-mcp withOperationRetry
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 17:16:36 +09:00
YeonGyu-Kim 63ba16bcce fix(oauth): wire refresh mutex into provider.refresh() for concurrent deduplication
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 17:16:28 +09:00
YeonGyu-Kim 80c74c8849 fix(background): prevent double-decrement of descendant quota in processKey error cleanup 2026-04-08 17:15:18 +09:00
YeonGyu-Kim 1cf4119dd4 fix(background): use parent session variant in notifyParentSession instead of child task variant 2026-04-08 17:15:02 +09:00
YeonGyu-Kim ed16dc0608 fix(chat-params): complete maxOutputTokens migration in session prompt params 2026-04-08 17:14:17 +09:00
YeonGyu-Kim 06b825dd74 fix(start-work): reuse registered opencode agent names 2026-04-08 16:18:26 +09:00
YeonGyu-Kim 24629643f0 fix(start-work): use canonical display name for command routing 2026-04-08 16:09:09 +09:00
YeonGyu-Kim 8925ec3a16 fix(start-work): align command routing with exported agent keys 2026-04-08 16:00:47 +09:00
YeonGyu-Kim 4394129667 fix(compaction): harden continuation directive markers 2026-04-08 15:58:02 +09:00
YeonGyu-Kim cd95172e42 fix(start-work): keep native command agents on config keys 2026-04-08 15:58:02 +09:00
YeonGyu-Kim b28567251d fix(plugin-loader): filter project-scoped plugins by cwd
discoverInstalledPlugins read scope from installed_plugins.json but
never filtered by it, so project/local scoped Claude Code plugins
leaked into every session regardless of process.cwd().

Add projectPath to PluginInstallation and InstalledPluginEntryV3,
propagate it through v3EntryToInstallation, and introduce
shouldLoadPluginForCwd which reuses shared/contains-path for safe
symlink- and ancestor-aware matching and expands a leading tilde.
user and managed scopes still always load; project and local without
a projectPath are skipped as a safe default.

Covered by 13 new shouldLoadPluginForCwd unit tests (including tilde
expansion against a mocked homedir) and 13 new discoverInstalledPlugins
integration tests spanning v1, v2, and v3 database formats plus the
existing enabledPluginsOverride path.

Fixes #3216
2026-04-08 15:28:17 +09:00
YeonGyu-Kim d22c3f23db test(plugin-interface): fix Atlas display name expectation
- Update expected value to match actual output after ZWSP prefix stripping

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 13:40:43 +09:00
YeonGyu-Kim 2c6a161441 test(runtime-fallback): fix OpenAI auto-retry test expectations
- Add timeout_seconds to mock config for auto-retry signal detection

- Add 'usage limit' pattern to quota_exceeded error classification

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 13:40:40 +09:00
YeonGyu-Kim 4d9652c028 test(start-work): update display name expectations for ZWSP fix
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 13:33:19 +09:00
YeonGyu-Kim 94449e0a24 test(delegate-task): update isPlanAgent test for exact match fix
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 13:33:18 +09:00
YeonGyu-Kim 1a04a6effb test(ralph-loop): update iteration cap expectation to 500
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 13:33:09 +09:00
YeonGyu-Kim ab0e1e3be0 fix(oauth): refresh-once handler for 401/403 responses
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-08 13:28:17 +09:00