Commit Graph

254 Commits

Author SHA1 Message Date
YeonGyu-Kim d3e218f912 fix(prompt): treat post-dispatch failures as accepted 2026-05-20 11:42:32 +09:00
YeonGyu-Kim 1492bffd20 fix(prompt-gate): harden internal prompt dispatch 2026-05-19 19:18:13 +09:00
YeonGyu-Kim d44cd1c1a0 fix(background-agent): preserve parent agent on retry wakes
Background fallback retry notifications were queued as bare internal user messages, so OpenCode could treat the notification as a new default-agent turn. Reuse the same parent prompt context resolver used by completion notifications for retrying and retry-ready wakes, and pin regression coverage for Hephaestus parent sessions plus missing-context fallbacks.
2026-05-18 14:17:33 +09:00
Claude Agent 3294128271 fix(background-agent): defer parent-wake when a user message just arrived (fixes #4120)
When a background subagent emits [ALL BACKGROUND TASKS COMPLETE], the
plugin queues a parent-wake that ultimately calls
dispatchInternalPrompt against the parent session. If the user submits
a new prompt inside the ~250 ms post-dispatch hold window, both writes
land on the same OpenCode session-storage file at the same instant.
OpenCode's @parcel/watcher (which the plugin itself does not depend on,
but does indirectly trigger) batches those events into a TSFN callback
and dispatches them into a JS env that the renderer has just torn down
because the session view re-mounted around the user's new message ->
napi_fatal_error / SIGABRT on macOS arm64. Removing the plugin removes
the parent-wake, which is why removing OmO eliminates the crash.

Mitigation:
- Before flushPendingParentWake calls dispatchInternalPrompt, inspect
  the parent session's message tail. If the most recent message is a
  user message added inside PARENT_WAKE_USER_MESSAGE_IN_PROGRESS_WINDOW_MS
  (default 2_000 ms), reschedule instead of dispatching. The user's own
  prompt will drive the model; queued notifications will be re-flushed
  on the next idle.
- Best-effort unref() of the long-lived pending-retry and dispatched-
  wake bookkeeping setTimeouts. They previously pinned the host event
  loop and prolonged the teardown window during which the watcher race
  can fire.

The new option userMessageInProgressWindowMs is wired through
BackgroundManager via a module-level constant and is independently
testable.

Regression test parent-wake-user-message-race.test.ts covers:
- fresh user message -> dispatch deferred
- latest message is assistant -> dispatch proceeds
- user message older than window -> dispatch proceeds
- window=0 disables the guard

This is a surface-level mitigation of the most-likely root cause from
the audit; a deeper fix (singleton guard against plugin
double-instantiation under @opencode-ai/plugin@local reload, dispose
lifecycle for OpenCode plugin reload) is out of scope here.
2026-05-17 21:27:11 +02:00
YeonGyu-Kim df198d8b2d refactor(background-agent): use unified internal prompt dispatch
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-17 16:44:56 +09:00
YeonGyu-Kim 2b8782de85 fix(claude-code-session-state): clear session-agent map on delete and sync cleanup
External review on PR #4074 noted that adding setSessionAgent for child
sessions left sessionAgentMap holding entries after the session was
deleted or after the sync call_omo_agent executor cleaned up other
owned state. The map only grows; entries never get reused but they do
accumulate across long-running plugin instances.

Close both gaps:
- BackgroundManager.handleEvent for session.deleted now calls
  clearSessionAgent for the deleted session id on both the early-return
  no-task branch and the cascade tail. This pairs with the existing
  clearDelegatedChildSessionBootstrap and SessionCategoryRegistry.remove
  so all owned session state is dropped together.
- sync-executor finally for createdSessionForExecution now calls
  clearSessionAgent alongside the existing subagentSessions,
  syncSubagentSessions, and deleteSessionTools cleanup so sessions this
  executor created cannot leak their agent mapping.

Adds focused tests:
- BackgroundManager.handleEvent - session.deleted cascade > should
  clear session agent state for deleted sessions to prevent map leak
- executeSync > registers child-session bootstrap and tracked prompt
  state before sync prompt dispatch (extended assertion for cleanup)
2026-05-17 01:28:12 +09:00
YeonGyu-Kim d3318617d0 fix(background-agent): clean child session-agent state on pre-start abort and normalize stored agent
Two adjacent gaps cubic flagged on the previous diff:

1. spawner.startTask stored input.agent (potentially prefixed with sort
   marker and ZWSP) in setSessionAgent, but the prompt body used the
   stripped/normalized form. The session-agent registry therefore did
   not match what promptAsync actually dispatched. Capture the
   normalized agent once at the top of startTask and use it for
   setSessionAgent plus the launch log lines.
2. manager.startTask wrote setSessionAgent(sessionID, input.agent)
   before the cancelled and stale-attempt cleanup branches, but those
   branches only cleared subagentSessions and the delegated bootstrap.
   The session->agent mapping survived as orphan state after an aborted
   launch. Call clearSessionAgent inside both early-return paths so
   nothing remains tied to a session we just aborted.

Adds focused tests for both: spawner persistence parity with promptAsync
and manager cancellation cleanup leaving getSessionAgent undefined.
2026-05-17 00:30:50 +09:00
YeonGyu-Kim 097d7dc547 fix(background-agent): keep delegated skill, permission, and child agent across retries
Three coupled gaps surfaced after the initial spawn fix:

1. fallback-retry-handler dropped task.skillContent and
   task.sessionPermission when rebuilding LaunchInput, so the retried
   background task lost the delegated system prompt and question-deny
   permission rule.
2. manager.startTask never bound the child sessionID to the resolved
   agent via setSessionAgent, leaving runtime fallback and other hooks
   with no idea which agent owned the new child session.
3. The fallback-to-general path in spawner.ts rebuilt the prompt body
   without going through buildFallbackBody, so bootstrap state, session
   tools, and session agent updates drifted apart.

Persist skillContent and sessionPermission on BackgroundTask, bind
setSessionAgent/updateSessionAgent at session creation and on fallback,
and route the FALLBACK_AGENT retry through buildFallbackBody so the
prompt body, bootstrap tools, and session registries all agree.
2026-05-17 00:08:44 +09:00
YeonGyu-Kim 982fa81367 fix(delegate-task): start child prompts reliably
Preserve delegated child prompt/bootstrap metadata for early runtime fallback before OpenCode has persisted the first user turn. Bind prompt gate calls to the SDK session receiver and keep completed background task lookup visible across plugin manager instances.
2026-05-16 16:27:56 +09:00
YeonGyu-Kim a20540579e Merge pull request #4068 from code-yeongyu/feat/pre-publish-fix-v420
v4.2.0: pre-publish review fixes (BLOCKER-1..3, HIGH-5..10, MID-11/12)
2026-05-16 14:50:54 +09:00
YeonGyu-Kim 7dbb34cd4f refactor(background-agent): wire ParentWakeNotifier into BackgroundManager
Replace the inlined parent-wake coalescing logic in manager.ts with delegation to the ParentWakeNotifier extracted in c1ccf8d09. The four timer Maps and the related methods now live in their own module with a narrow public API, while BackgroundManager retains the wiring point and the enqueue-callback bridge.

Closes HIGH-9 (step 2: integration)

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-16 01:43:16 +09:00
Momentum96 5238dd484e fix(background-agent): start promptAsync before blocking tmux callback
- Fixes BackgroundManager to launch promptAsync before invoking the blocking tmux callback.
- Adds regression test to ensure promptAsync is called before tmux callback.
2026-05-15 23:11:07 +09:00
YeonGyu-Kim 39fef2042b fix(background-agent): resolve parent wake agent aliases 2026-05-15 21:52:40 +09:00
YeonGyu-Kim e66d60f4c5 Merge pull request #3773 from cailgarrisk-collab/fix/glm-rate-limit-fallback-statuscode
fix(model-fallback): add HTTP statusCode check for GLM rate limit fallback
2026-05-15 20:04:22 +09:00
YeonGyu-Kim c6054af9fa Merge pull request #3872 from x-x-gpu/dev
fix: pass resolved model to session.create so sub-agent sessions use the correct model
2026-05-15 19:56:09 +09:00
YeonGyu-Kim 83ab000963 Merge pull request #3972 from MoerAI/fix/circuit-breaker-tool-input-fallback
fix(background-agent): fall back to partInfo.input when state.input is unavailable for circuit breaker (fixes #3962)
2026-05-15 19:20:36 +09:00
YeonGyu-Kim 3c7d1299a7 Revert "Merge pull request #3825 from tw-yshuang/fix/delegated-child-session-early-failure-fallback"
This reverts commit cd33f3a397, reversing
changes made to 521c99cf0b.
2026-05-15 19:13:48 +09:00
YeonGyu-Kim cd33f3a397 Merge pull request #3825 from tw-yshuang/fix/delegated-child-session-early-failure-fallback
fix(delegate-task): harden child-session first-prompt fallback recovery
2026-05-15 19:06:43 +09:00
YeonGyu-Kim 7a94cc72be fix(background-agent): stabilize parent wakes 2026-05-15 17:54:32 +09:00
YeonGyu-Kim c0544a703a fix(background-agent): defer retry notifications 2026-05-15 17:29:37 +09:00
YeonGyu-Kim cd1c1a5921 fix(background-agent): avoid branched parent wakes 2026-05-15 14:39:55 +09:00
YeonGyu-Kim c2aa180e7e fix(prompt-gate): pin duplicate prompt dispatches
Keep prompt reservations briefly after successful dispatch so rapid idle/message/error transitions cannot inject the same follow-up twice.

Route all production session prompt calls through the shared gate, restore skipped background resume state, release holds after abort/recovery paths, and preserve Ralph/ULW loop state when a dispatch is deferred.

Add regression coverage for session routing, static prompt route auditing, team-mode live messaging, model suggestion retries, call-omo-agent reuse, background parent wakes, runtime fallback, compaction recovery, Atlas, and Ralph/ULW loops.
2026-05-15 13:19:10 +09:00
YeonGyu-Kim 174cbd0fbd fix(background-agent): gate parent wake prompts 2026-05-15 12:55:56 +09:00
YeonGyu-Kim 268c89c945 fix(background-agent): coalesce rapid-fire idle parent notifications
When many background tasks complete in rapid succession while the parent
session is idle, each completion fired its own promptAsync call, stacking
N consecutive `<system-reminder>` user messages with no assistant turn
between. Hyperplan + many parallel explore subagents made this very
visible to the user.

Route the idle-path through the existing pendingParentWakes queue with a
100ms debounce window. Notifications arriving during the debounce join
the same batch, the 150ms settle window also coalesces newcomers, and a
single batched prompt fires to the parent. Busy-path semantics are
unchanged (still 1s retry).

Prior attempts (1c05c60dc, ea55c385b, a337635e3) all coalesced only the
busy-defer path, leaving the idle-immediate-send path uncoalesced.
2026-05-14 18:52:28 +09:00
YeonGyu-Kim ea55c385bb fix(background-agent): defer busy parent notifications 2026-05-14 00:45:36 +09:00
YeonGyu-Kim a337635e3b fix(background-agent): defer active parent wakes 2026-05-13 17:02:17 +09:00
tw-yshuang 12f5233877 merge(dev): resolve latest manager and runtime-fallback conflicts
Sync the PR branch with the newest dev branch and resolve the new import-level conflicts in background-agent manager and runtime-fallback tests. Preserve both the delegated bootstrap coverage from this branch and the newer upstream test utilities and runtime wiring changes, then re-verify the affected delegated fallback suites and typecheck.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-12 22:49:57 +08:00
MoerAI 5cd95cf57e fix(background-agent): fall back to partInfo.input when state.input is unavailable for circuit breaker (fixes #3962)
The circuit breaker in manager.ts uses recordToolCall to detect when a subagent gets stuck repeating identical tool_use blocks. It passed partInfo.state?.input as the tool-input signature. When a model (Kimi K2.6 in the reporter's case) emits duplicate tool_use parts faster than the tool actually starts running, state.input is still null, so loop-detector falls back to the bare 'tool::__unknown-input__' signature. As soon as one part has state.input populated (next event), the signature flips to 'tool::{actual-args}' and the consecutive counter resets to 1, repeatedly. The breaker never reaches its 20-call threshold.

Add a top-level input?: Record<string, unknown> field to the local MessagePartInfo interface and prefer state.input when present, falling back to the part's own input when state is still pre-running. The OpenCode part payload carries the tool input as soon as the tool_use block is generated, so this fallback restores signature stability across the model's repeated emissions.

Verification: added 2 regression tests in manager-circuit-breaker.test.ts. Test 1 (reproduce) emits 20 part.updated events with only top-level input and asserts the task is cancelled by the breaker — fails before the fix, passes after. Test 2 confirms that when state.input IS present, it still wins over the top-level input (precedence preserved). All 10 manager-circuit-breaker tests pass, all 20 loop-detector tests pass, typecheck clean.
2026-05-12 19:02:20 +09:00
YeonGyu-Kim 6035a551ad fix(background-agent): route session prompts by directory 2026-05-12 18:13:39 +09:00
YeonGyu-Kim 07797e1975 fix(background-agent): preserve direct all-complete replies 2026-05-12 15:28:35 +09:00
YeonGyu-Kim d82f6a6024 fix(background-agent): refresh stale todo state 2026-05-12 13:28:40 +09:00
YeonGyu-Kim 4da48555ee fix(plugin): normalize event session ids
Handle OpenCode session events that carry the session ID under properties.info.id or properties.info.sessionID so background tasks and continuation hooks do not miss idle/error/delete events.

Add regression coverage for nested session.idle events completing background tasks and waking continuation hooks.
2026-05-12 12:32:26 +09:00
tw-yshuang 1e1f574b95 merge(dev): resolve latest sync-task conflict for delegated fallback PR
Sync the PR branch with the latest dev branch and resolve the remaining conflict in sync-task.test.ts while preserving both the new upstream poll-recovery coverage and this branch's delegated bootstrap cleanup and isolation coverage. Re-verified the affected delegated fallback suites and typecheck after the merge resolution.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-12 00:37:01 +08:00
YeonGyu-Kim 1c05c60dcc fix(background-agent): replace system-reminder wake with queued notifications 2026-05-11 18:55:36 +09:00
YeonGyu-Kim 90f0971f4f fix(background-agent): handle idle status events 2026-05-11 18:06:22 +09:00
YeonGyu-Kim a5cc498433 fix(background-agent): retry deferred parent wake 2026-05-11 12:38:04 +09:00
YeonGyu-Kim eb6605a87f fix(background-agent): preserve non-sensitive timing in archived tasks
Carry queuedAt and startedAt in sanitized archive snapshots so post-cleanup task output duration remains accurate without retaining prompt content.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-11 08:53:44 +09:00
YeonGyu-Kim a4ce0b63b6 fix(background-agent): redact archived prompts and cap fallback archive
Store only sanitized completed-task snapshots in archive to avoid retaining sensitive prompts after cleanup.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-11 08:47:39 +09:00
tw-yshuang 74171b9156 merge(dev): resolve background-agent delegated fallback conflicts
Reconcile the latest dev branch changes with the delegated child-session fallback work. Preserve the upstream background-agent updates while keeping the delegated bootstrap cleanup and compatibility wiring fixes intact, then re-verify the affected regression suites and typecheck.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-11 03:02:14 +08:00
YeonGyu-Kim dbaea82b73 fix(background-agent): retain completed tasks via archive fallback after cleanup
MessageAbortedError/worker shutdown could race with scheduled removeTask, leaving background_output's manager.getTask returning 'Task not found' even though the task had completed cleanly.

Fix: add completedTaskArchive (max 500, FIFO eviction). On removeTask, archive non-running/pending tasks with sessionId. getTask falls back to archive on active-map miss. addTask clears stale archive entries on re-registration.

Fixes #3895
2026-05-10 14:56:28 +09:00
YeonGyu-Kim 862a2df0db fix(delegate-task): pause timeouts for active sessions 2026-05-10 13:52:22 +09:00
YeonGyu-Kim 5a4127cc8a fix(background-agent): defer busy parent wake 2026-05-10 13:40:41 +09:00
YeonGyu-Kim d801c88888 fix(team-mode): preserve member context on retry 2026-05-10 13:11:52 +09:00
YeonGyu-Kim b36389ef2c fix(team-mode): keep member communication tools visible 2026-05-10 12:54:16 +09:00
x-x-gpu 0b9cc80ecf fix: pass resolved model to session.create so sub-agent sessions use the correct model
Sub-agent sessions created via the task tool or call_omo_agent tool were
being created without a model. The resolved model was only passed as a
promptAsync body override, which opencode core ignores, causing fallback
to the client's system default model (often a reasoning model that hangs).

OpenCode's session.create API supports model: { id, providerID, variant }
at creation time. This fix ensures the resolved category/agent model is
passed during session creation across all paths:

- createSyncSession() (delegate task / category-based)
- createOrGetSession() (call_omo_agent / direct agent calls)
- BackgroundManager.startTask() (background tasks)

This guarantees each sub-agent session is created with the correct model
regardless of whether promptAsync honors its model override.
2026-05-08 20:52:30 +08:00
YeonGyu-Kim ee938aa097 fix full-suite isolation regressions 2026-05-07 18:36:03 +09:00
tw-yshuang fac90d69f8 fix(delegate-task): harden child-session fallback bootstrap and cleanup
Capture delegated child-session retry context before the first prompt so fallback recovery still works when session history is empty. Align background and sync launch paths around the same bootstrap contract, clear session-scoped fallback state on every terminal path, and lock the behavior with regression coverage for first-prompt retries, exhaustion, isolation, and cleanup.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-07 08:34:52 +08:00
YeonGyu-Kim 25d183fbe8 fix(agent): sanitize backslash/quote from agent name in launch() and background-executor
Agent names like \hephaestus\ cause 'Agent not found' errors because
sanitizeSubagentType() was only called in subagent-resolver.ts but not
in the direct manager.launch() path or background-executor.ts.

- manager.ts: strip leading/trailing backslash/quote chars from input.agent
  before validation so \hephaestus\ → hephaestus
- background-executor.ts: call sanitizeSubagentType(args.subagent_type)
  instead of passing raw value to manager.launch()
- agent-display-names.ts: reuse sanitizeSubagentType in stripAgentListSortPrefix
- Add unit tests for all three fix points

Fixes: sessions dying with 'Agent not found: \hephaestus\'
2026-05-06 17:41:41 +09:00
YeonGyu-Kim 503df29195 fix(team-mode): register spawned sessions early 2026-05-06 14:20:50 +09:00
YeonGyu-Kim a0d122b1e6 feat(background-agent): integrate team-mode into background manager 2026-05-06 14:19:39 +09:00