Commit Graph

826 Commits

Author SHA1 Message Date
YeonGyu-Kim 8c4c4ff644 feat(team-mode): add caller team-lead resolution with tests 2026-05-06 14:19:38 +09:00
YeonGyu-Kim 41d6e12f0d feat(team-mode): add member session resolution and routing logic 2026-05-06 14:19:38 +09:00
YeonGyu-Kim 8a42d7fd69 feat(team-mode): add member guidance and parser utilities 2026-05-06 14:19:38 +09:00
YeonGyu-Kim 783d70558b feat(team-mode): add core types, dependencies and type-level tests 2026-05-06 14:19:38 +09:00
YeonGyu-Kim 5e4ae8815e test(tmux-subagent): expand zombie-pane test coverage 2026-05-06 14:19:38 +09:00
YeonGyu-Kim 49bb5dd81a test(tmux-subagent): add pane-state-querier runner tests 2026-05-06 14:19:38 +09:00
YeonGyu-Kim 8d3d882578 test(tmux-subagent): add manager project-directory resolution tests 2026-05-06 14:19:38 +09:00
YeonGyu-Kim 77655a7c30 test(tmux-subagent): expand manager test coverage 2026-05-06 14:19:38 +09:00
YeonGyu-Kim 680927d0fd test(tmux-subagent): expand polling-manager test coverage 2026-05-06 14:19:38 +09:00
YeonGyu-Kim b6dcf05d45 test(tmux-subagent): expand action-executor test coverage 2026-05-06 14:19:38 +09:00
YeonGyu-Kim c8b225099c refactor(tmux-subagent): stabilize polling, execution and session lifecycle with runner 2026-05-06 14:19:38 +09:00
YeonGyu-Kim 3ddc757b15 fix(bun-spawn-shim): eliminate globalThis.Bun top-level destructures for Electron/Node compat
Root cause: bun build --target bun inlines top-level
  var { spawn } = globalThis.Bun;
for every file that contains 'import { spawn } from "bun"'. On Node/Electron
where globalThis.Bun is undefined, this crashes with
  Cannot destructure property 'spawn' of 'globalThis.Bun' as it is undefined.
26 source files had this import; the bundled output had 25 top-level destructures.

Fix:
- Add src/shared/bun-spawn-shim.ts: a thin wrapper that
  - delegates to Bun.spawn/spawnSync when globalThis.Bun is present (real Bun)
  - falls back to static ESM imports of node:child_process otherwise
  - uses static 'import { spawn } from "node:child_process"' so Bun bundler
    does NOT emit any globalThis.Bun destructures for this module
- Replace all 26 'from "bun"' spawn/spawnSync imports with relative paths to shim
- Replace 4 direct Bun.spawn() call sites with shim's spawn()
- Remove src/electron-compat.ts and script/prepend-electron-shim.ts (no longer needed)
- Update src/electron-compat.test.ts to assert 0 top-level globalThis.Bun destructures

Verification: grep -c '} = globalThis.Bun;' dist/index.js → 0 (was 25)
All 5921 tests pass (1 pre-existing timeout failure unrelated to this change).

Fixes #3797
2026-05-05 22:41:45 +09:00
YeonGyu-Kim 32fab88d68 fix(process-cleanup): add test seam to prevent process.exitCode contaminating bun runner
scheduleForcedExit() sets process.exitCode which taints the bun test runner's
own exit code for the entire suite. This caused CI to fail even though all
tests passed individually.

Fix:
- Add __disableScheduledForcedExitForTesting / __enableScheduledForcedExitForTesting
  seams to skip scheduleForcedExit() during tests
- beforeEach disables forced exit; afterEach re-enables
- The 'fallback exit timer' test explicitly re-enables to verify setTimeout/clearTimeout
- Remove process.exitCode and exitSpy assertions that required forced exit to be active
  (shutdown call counts are sufficient to verify behavior)
2026-05-05 19:04:40 +09:00
YeonGyu-Kim ad535cd29d fix(process-cleanup): isolate test and avoid checking process.exitCode directly
The test file modifies process.exitCode and emits process signals which can
leak into bun test's exit code. Add:
1. mock.module() sentinel to route to isolated batch (following abort-with-timeout.test.ts pattern)
2. Global afterAll() hook that resets process.exitCode = 0 before test runner checks it
3. Remove direct checks of process.exitCode in assertions - only check that process.exit() was called with the right code via spy

This ensures bun test exits with code 0 even after tests verify process.exit behavior.

Fixes #3792
2026-05-05 05:15:59 +09:00
YeonGyu-Kim 45452a039c Merge pull request #3731 from yizhifengye/fix/process-cleanup-infinite-loop-epipe
fix(background-agent): detach error listener before running body to s…
2026-05-05 04:24:01 +09:00
YeonGyu-Kim fd62f5b072 Merge pull request #3639 from auyua9/fix/fatal-cleanup-exit
fix(background): exit after fatal cleanup
2026-05-05 04:15:50 +09:00
YeonGyu-Kim 6323fa8ef9 fix(background-agent): fix parentSessionID -> parentSessionId typo causing TS build failure
Property 'parentSessionID' does not exist on type 'LaunchInput' / 'BackgroundTask'.
The correct casing is 'parentSessionId' (camelCase with lowercase 'd').
Fixes CI build failure on dev branch.
2026-05-05 03:53:44 +09:00
YeonGyu-Kim e699f3388d Merge pull request #3455 from CHLK/fix/cli-run-premature-exit-with-background-tasks
fix(cli-run): prevent premature exit when background tasks are active
2026-05-04 23:58:34 +09:00
YeonGyu-Kim 8c8522ee72 Merge pull request #3415 from lightrabbit/fix/skill-mcp-stdio-cwd
fix(skill-mcp): pass workspace directory as cwd to stdio MCP processes
2026-05-04 23:58:22 +09:00
YeonGyu-Kim 4e763fb0cb test(team-mode/team-worktree): use named tmpdir import to avoid mock leak
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-04 16:30:23 +09:00
YeonGyu-Kim 65c1b50946 test(background-agent): align manager test fixtures with normalized field names
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-04 16:30:06 +09:00
YeonGyu-Kim 3ae9f5f104 fix(background-agent): prevent false task completion on status API outage
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-04 16:30:06 +09:00
YeonGyu-Kim 6a8bdcaa25 feat(team-mode): add resolveCallerTeamLead helper
🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
2026-05-04 01:43:55 +09:00
YeonGyu-Kim da251c9b30 refactor(background-agent): normalize task ID field naming
Rename BackgroundTask and attempt ID fields to camelCase across background-agent consumers while moving BackgroundManager construction to a single config object.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-02 03:01:03 +09:00
auyua9 875fffb7d1 fix(background): exit after fatal cleanup 2026-05-01 16:04:03 +08:00
yizhifengye 49c2a40251 fix(background-agent): detach error listener before running body to stop re-entrant log loop
When shutdown() itself emitted uncaughtException (e.g. EPIPE while closing
a broken pipe), the error listener re-entered itself, re-logged, re-ran
cleanup, and threw EPIPE again. The 6 s forced-exit timer could not fire
because every re-entry stalled the event loop with fresh synchronous work.

Users hit this after v3.17.5 and observed 100+ GB of log lines written to
disk within minutes, with one confirmed report of a 157 GB log file filling
the filesystem.

Detaching the listener with process.off() before running log() + handler()
breaks the loop at the first re-emit: the second event has no listener to
invoke, and the first invocation's scheduleForcedExit() proceeds normally.
2026-04-30 14:47:09 +08:00
Choi Kijin / 최 기진 / チョイ キジン 7adb725960 Merge remote-tracking branch 'origin/dev' into fix/retry-recovery-and-observability 2026-04-29 12:25:02 +09:00
Choi Kijin / 최 기진 / チョイ キジン 2237c1accd fix(background-agent): guard stale launch errors and retry links
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-28 21:43:09 +09:00
YeonGyu-Kim 24201ea951 perf(background-agent): index tasks by parent session
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-28 18:00:04 +09:00
Choi Kijin / 최 기진 / チョイ キジン 3fc9e6bafd Merge remote-tracking branch 'origin/dev' into opencode/mighty-wolf 2026-04-28 15:47:58 +09:00
Choi Kijin / 최 기진 / チョイ キジン 79054ea3e5 feat(background-task): render retry timelines and links
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-28 15:29:49 +09:00
Choi Kijin / 최 기진 / チョイ キジン 25548f2561 fix(model-fallback): retry forbidden provider errors
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-28 15:29:33 +09:00
Choi Kijin / 최 기진 / チョイ キジン a4968a3d1d feat(background-task): track retry attempts across sessions
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-28 15:27:35 +09:00
YeonGyu-Kim bda7fdbbda test(background-agent): add spawner test coverage
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-27 21:15:40 +09:00
YeonGyu-Kim 333ad3aadd refactor(agents): drop ZWSP prefixes from agent display names
The sort shim from the previous commit enforces canonical core ordering at runtime, so ZWSP prefixes are no longer needed. Removing them eliminates the Bun.stringWidth vs terminal-width drift that broke the TUI status bar (#3259).

Drop AGENT_LIST_SORT_PREFIXES and getAgentRuntimeName from agent-display-names; switch all call sites to getAgentDisplayName. getAgentListDisplayName stays as a thin alias for external importers.

Keep stripInvisibleAgentCharacters and the ZWSP regex paths so legacy session state and configs from v3.14.0-v3.16.0 still resolve.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-27 18:59:49 +09:00
YeonGyu-Kim 0d3eed17a5 fix(builtin-skills): gate dev-browser by provider selection
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-27 17:31:07 +09:00
YeonGyu-Kim 1da7df1aee Revert "Merge pull request #3657 from code-yeongyu/refactor/replace-zwsp-with-real-spaces"
This reverts commit f1a11f2c92, reversing
changes made to 62c19ce0ef.
2026-04-27 15:54:05 +09:00
YeonGyu-Kim f1a11f2c92 Merge pull request #3657 from code-yeongyu/refactor/replace-zwsp-with-real-spaces
refactor(agents): replace broken ZWSP sort prefixes with leading ASCII spaces
2026-04-27 15:36:51 +09:00
YeonGyu-Kim acf293de96 fix(tmux): validate fallback port
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-27 14:24:42 +09:00
YeonGyu-Kim c65f90ee09 fix(tmux): preserve configured fallback port
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-27 13:51:16 +09:00
YeonGyu-Kim 0e9bb5969d refactor(agents): replace broken ZWSP sort prefixes with leading ASCII spaces
The ZWSP-based core agent sort prefixes silently failed to produce the
canonical sisyphus -> hephaestus -> prometheus -> atlas order.

Empirical testing of OpenCode's Agent.list() sort behavior shows that
Unicode collation treats zero-width characters as ignorable at the primary
level, so ZWSP-prefixed names sorted alphabetically with non-core agents
interleaved (e.g. Sisyphus, athena, Atlas, explore, Hephaestus, ...).

This commit replaces the ZWSP prefixes with leading ASCII spaces in
descending lengths (sisyphus=4, hephaestus=3, prometheus=2, atlas=1).
ASCII spaces sort reliably before alphabetic characters in localeCompare
under all locales and render correctly in every terminal.

Changes:
- AGENT_LIST_SORT_PREFIXES: ZWSP -> leading spaces (4-3-2-1 descending)
- stripAgentListSortPrefix: now strips both legacy ZWSP and new leading
  whitespace, preserving backward compatibility with existing sessions
- normalizeStoredAgentName / normalizeRegisteredAgentName: extract a
  shared stripSortPrefix helper that handles both prefix formats
- agent-config-handler: resolve user-provided default_agent display
  names through getAgentConfigKey before applying the runtime prefix,
  so configs like default_agent="Hephaestus - Deep Agent" are normalized
- agent-runtime-name-sort.test.ts: new regression test simulating
  OpenCode's exact sortBy logic (default_agent desc + name asc localeCompare)
  to verify canonical core agent order under randomised input permutations
- AGENTS.md: document the empirical finding that ZWSP was broken, why
  ASCII spaces work, and the descending prefix-length contract

Existing strip functions retain ZWSP support so legacy session state and
configs continue to resolve correctly without migration.
2026-04-27 13:48:09 +09:00
YeonGyu-Kim f1353cd9a8 fix(tmux): use stable fallback server URL
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-27 13:24:50 +09:00
柯杨 eaa3837d0b Merge remote-tracking branch 'origin/dev' into fix/cli-run-premature-exit-with-background-tasks 2026-04-22 09:45:56 +08:00
YeonGyu-Kim 913fac05f5 fix(tmux-subagent): retry stale sweep if first attempt throws
Oracle flagged: staleSweepCompleted was set to true BEFORE
sweepStaleOmoAgentSessions() ran, so any throw from the first
invocation would permanently disable stale cleanup for the rest
of the process lifetime.

Fix:
- Move staleSweepCompleted=true into the try-block success branch.
- Add staleSweepInProgress guard so concurrent onSessionCreated calls
  do not invoke sweep twice in parallel (sweep is idempotent, but the
  guard prevents doubled log noise).
- finally{} clears the inProgress flag regardless of outcome.
- cleanup() resets both flags.

Two new tests cover: retry after a thrown first attempt, and single
invocation when subsequent spawns follow a successful first sweep.
2026-04-18 20:45:21 +09:00
YeonGyu-Kim 859d67f41e fix(tmux-subagent): revert session.error cleanup (recoverable-error regression)
Oracle flagged a regression introduced in PR #3507 commit 21554be8:
event.ts routed session.error through tmux pane cleanup BEFORE the
existing session-recovery / model-fallback logic ran.

Problem: when session.error was recoverable (context window limit,
quota rate limit, provider fallback), the recovery/fallback code would
successfully continue the SAME session - but by then its tmux pane had
already been destroyed. User-visible symptom is exactly the original
complaint - 'screen appears but streaming stops working' after an
auto-retry.

Fix is the minimal revert: remove the onSessionError funnel from
event.ts and drop onSessionError from the manager. Fatal errors that
actually end a session still fire session.deleted, which continues to
trigger cleanup correctly. Non-fatal error streams stay attached to
the surviving pane.
2026-04-18 20:37:49 +09:00
YeonGyu-Kim 3dce19d173 fix(tmux-subagent): move 'cleanup complete' log back to cleanup() method
The log line was misplaced at the end of sweepStaleIsolatedSessionsOnce
where it said 'cleanup complete' after the stale sweep, which was
misleading. Per Oracle review.
2026-04-18 20:27:48 +09:00
YeonGyu-Kim 104523051d feat(tmux): sweep stale omo-agents-<pid> sessions on first spawn
Follow-up to PR #3507 addressing the Oracle-noted operational limitation:
per-PID isolated session names (getIsolatedSessionName(process.pid)) mean
that when an opencode process is SIGKILL'd (or the machine hard-reboots),
the old omo-agents-<old-pid> tmux session survives forever because nothing
is around to kill it.

Added sweepStaleOmoAgentSessions() that:
1. Lists tmux sessions matching /^omo-agents-(\d+)$/
2. For each, checks process.kill(pid, 0) to detect a dead PID
3. Skips our own PID
4. Calls killTmuxSessionIfExists for every session whose owner process is gone

Wired into TmuxSessionManager.onSessionCreated() as a one-shot (guarded by
staleSweepCompleted flag) so it runs lazily on the first subagent spawn when
isolation="session". The flag is reset in cleanup() so subsequent process
restarts re-run the sweep.

6 new tests cover: outside-tmux no-op, no matching sessions, multiple dead
PIDs, current PID skip, live PID skip, list-sessions failure.

Manual E2E verified on real tmux:
- Created omo-agents-99999, sweep killed it
- Spawned our own omo-agents-<pid>, closeTmuxPane returned true even after
  pane auto-destroy from Ctrl+C
- Final tmux list-sessions shows zero omo-agents-* orphans
2026-04-18 20:22:00 +09:00
YeonGyu-Kim aa79284dc5 fix(background-agent): reset process.exitCode to 0 between cleanup tests
CI test suite exited 1 despite 0 failing tests because process-cleanup.test.ts
assertions left process.exitCode=1 in place. The afterEach hook only reset to
originalExitCode (which starts undefined), not 0, so Bun picked up exitCode=1
on shutdown and reported the shared batch as failing.

Explicitly set process.exitCode = 0 in beforeEach and afterEach so each test
starts and ends with a clean exit state.
2026-04-18 19:57:26 +09:00
YeonGyu-Kim 257b6cf951 fix(tmux): scope isolated session name per plugin instance (Oracle review)
Oracle flagged the previous commit: "omo-agents" was a shared constant,
so when two plugin instances ran in the same tmux server they wrote into
the same session. One instance's cleanup would then kill-session on the
shared name and tear down the other instance's live attached panes.

Replace the const ISOLATED_SESSION_NAME with getIsolatedSessionName(pid)
which defaults to process.pid, so every opencode process owns its own
"omo-agents-<pid>" session. spawnTmuxSession and cleanup both resolve
the name through this helper. Discovery is straightforward from the
host tmux via 'tmux list-sessions | grep omo-agents-'.

Manager test covers two concurrent managers and asserts each kills a
per-pid session name, proving they no longer collide on a global name.
2026-04-18 19:47:36 +09:00
YeonGyu-Kim f8a1a11bb7 fix(team-mode): refactor layout to use testable spawn-process helper
The existing layout.test.ts relied on mock.module("bun", ...) registered
at the top level, but test-setup.ts calls mock.restore() + restoreModuleMocks()
in afterEach, so every test except the first one lost its mocks. CI has
been red on this file since e303feef.

Two changes:

1. layout.ts now imports spawn from the existing spawn-process helper
   instead of "bun" directly, matching the pattern established for
   closeTmuxPane and killTmuxSessionIfExists. This does not change
   runtime behavior - spawn-process just re-exports Bun's spawn.

2. layout.test.ts registers module mocks inside beforeEach and uses the
   ?test=UUID cache-busting dynamic-import pattern so the mocks apply
   on every test run, not just the first.

All 4 layout.test.ts cases now pass.
2026-04-18 19:42:19 +09:00