Commit Graph

6270 Commits

Author SHA1 Message Date
deopa0402 37d9d613b6 fix(auto-update): clean stale OMO cache roots
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-17 18:35:29 +09:00
YeonGyu-Kim babee921b2 Merge pull request #4109 from code-yeongyu/code-yeongyu/unify-prompt-async-routes
refactor(prompt-gate): unify internal prompt dispatch routes
2026-05-17 17:30:02 +09:00
YeonGyu-Kim 7a3a0a031c test(tmux): ignore unrelated pane runner mock calls
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-17 17:21:35 +09:00
YeonGyu-Kim 0f92d2c98d test(prompt-gate): narrow audit binding detection
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-17 17:17:20 +09:00
YeonGyu-Kim 98df0a43e3 docs(prompt-gate): document unified dispatch invariant
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-17 17:16:18 +09:00
YeonGyu-Kim 6768decddb fix(session-recovery): fallback when stored unavailable-tool parts are absent
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-17 17:16:10 +09:00
YeonGyu-Kim 12bd658079 refactor(prompt-async-gate): remove deprecated dispatch wrappers
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-17 17:15:54 +09:00
YeonGyu-Kim 1bbe065c60 refactor(prompt-callers): migrate shared and cli dispatch
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-17 17:09:04 +09:00
YeonGyu-Kim 989ab7171d refactor(hooks): 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 17:07:35 +09:00
YeonGyu-Kim dd3fecaf40 refactor(plugin): 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 17:01:50 +09:00
YeonGyu-Kim fee515c5ac refactor(prompt-callers): migrate team and call_omo_agent dispatch
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-17 16:48:06 +09: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 a42f894f88 refactor(prompt-async-gate): collapse dispatch into mode-based entrypoint
Use one dispatchInternalPrompt surface with mode: async | sync so source, settle, hold, timeout, status checks, reservations, and release semantics stay in one runner. Keep the old helper names temporarily so caller migration can land atomically in follow-up commits.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-17 16:37:59 +09:00
YeonGyu-Kim b5d24619c8 test(prompt-async-gate): pin unified internal prompt dispatch contract
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-17 16:34:05 +09:00
YeonGyu-Kim f1a0ba2060 Merge pull request #4108 from code-yeongyu/code-yeongyu/fix-idle-recovery-fanout
fix: prevent idle recovery fanout and slash duplication
2026-05-17 16:22:41 +09:00
YeonGyu-Kim 8bc4977563 fix(slash-command): skip already tagged command output 2026-05-17 16:17:42 +09:00
YeonGyu-Kim 55312cc4b6 fix(session-recovery): preflight idle recovery fanout 2026-05-17 16:17:36 +09:00
YeonGyu-Kim 1fea761cf2 Merge pull request #4106 from code-yeongyu/code-yeongyu/fix-stale-tool-hang
fix(session-recovery): recover interrupted idle tool turns
2026-05-17 15:51:15 +09:00
YeonGyu-Kim a7b7ace7ed fix(prompt-gate): block prompts into pending tool turns 2026-05-17 15:42:58 +09:00
YeonGyu-Kim 6eb88a0545 fix(session-recovery): prefer valid tool use ids 2026-05-17 15:15:13 +09:00
YeonGyu-Kim 4d417a33b6 fix(process-cleanup): stop force-exiting opencode on transient unhandled errors
Root cause of the user-visible `/init-deep ulw` hang (session
`ses_1cb9c3013ffesUOy5H3QOIya4K`): the plugin's `unhandledRejection` and
`uncaughtException` listeners were calling
`scheduleForcedExit(handler(error), 1, true)`, which both ran the entire
`cleanupAll()` chain (BackgroundManager shutdown, tmux pane closure,
team-mode teardown) and then `process.exit(1)`'d the host. Under heavy
slash commands like `/init-deep ultrafucking deep`, a single mid-stream
error (e.g. opencode's own `session.processor` Aborted-process condition,
or a transient socket reset) would:

  1. trigger the listener,
  2. abort the in-flight background tasks (`session.error
     MessageAbortedError` for both child sessions in the log),
  3. close the tmux panes the user was watching,
  4. immediately kill the host via `process.exit(1)`.

From the user's seat that looked like a frozen TUI, which is what they
reported as "ulw 여전히 멈추는데". The error blob also logged as `{}`
because `JSON.stringify(new Error(...))` strips non-enumerable Error
fields, so the previous log line carried no diagnostic value.

This change makes the global `uncaughtException` / `unhandledRejection`
listeners log-only:

* New `describeProcessCleanupError()` extracts `{name, message, stack}`
  from Error instances, falls back to a structured `{raw: ...}` payload
  for plain objects / primitives, so the log now actually says what
  failed.
* `registerErrorEvent()` no longer runs cleanup and no longer calls
  `scheduleForcedExit`. It detaches itself, logs a single explanatory
  line, and returns. Bun's default crash behaviour is already suppressed
  for these events when a listener is present, so the host now genuinely
  survives transient streaming errors instead of being killed by our
  own helper.
* Signal handlers (`SIGINT` / `SIGTERM` / `SIGBREAK` / `beforeExit` /
  `exit`) keep their existing behaviour and still run `cleanupAll()`
  before the host terminates — that is now the only path that tears
  down background tasks and tmux panes.

Tests are updated to lock in the new contract:

* New regression `#given scheduleForcedExit enabled AND unhandledRejection
  fires #when the listener runs #then process.exit is NOT called AND
  process.exitCode stays 0 AND no cleanup runs` (and the
  uncaughtException twin) re-enables `scheduleForcedExit`, spies on
  `process.exit` plus `globalThis.setTimeout`, and asserts none of them
  are touched. Without the fix this test failed exactly like the
  observed hang (exit called once, exitCode set to 1).
* The existing "manager shuts down before process exits" tests are
  rewritten to assert the opposite: cleanup is NOT invoked from the
  error path.
* A complementary `'exit'` listener test pins the real shutdown
  contract (`exit` event still triggers `cleanupAll`).
* A new `#given describeProcessCleanupError` block covers the four
  shapes (Error, plain object with own fields, empty object, primitive).
* The unregister assertion is tightened to check the listener count
  drops back to baseline (previously it relied on a side effect of the
  old cleanup-on-error path).

Manual QA: `bun /tmp/process-cleanup-smoke-test.ts` (out-of-tree smoke
driver) emits five back-to-back unhandledRejection/uncaughtException
events with various payload shapes and prints
`SMOKE_TEST_OK survived 5 emissions; exitCode=0; shutdownInvocations=0`,
confirming the host survives and no spurious cleanup runs.

`bun test` runs green for the affected modules:
  - src/features/background-agent (528 tests)
  - src/create-managers + src/plugin (218 tests)
  - src/hooks/{unstable-agent-babysitter,ralph-loop,keyword-detector}
    (225 tests)
2026-05-17 15:11:44 +09:00
YeonGyu-Kim f43effb842 fix(session-recovery): recover interrupted idle tool turns 2026-05-17 15:08:39 +09:00
YeonGyu-Kim fbec112bc2 fix(background-output): bound session.messages fetch to stop forever-hang during /init-deep
Root cause: `formatTaskResult` and `formatFullSession` both call
`client.session.messages({ path: { id: task.sessionId } })` with no
timeout. The OpenCode SDK explicitly disables fetch timeout
(`req.timeout = false` in `packages/sdk/js/src/client.ts:37`), so when
`session.processor` enters an "Aborted process" loop (same condition
patched in #4086 for the Read hot path) every subsequent
`session.messages` call hangs forever.

User-visible impact: the parent agent running a heavy slash command
such as `/init-deep` fires many background `task` agents in parallel
and then calls `background_output(task_id, block=true)` for each. The
existing `timeoutMs` (max 10min) caps only the polling loop that waits
for the child task to transition out of `running`. Once the child is
`completed`, the loop exits and the code falls through to
`formatTaskResult` / `formatFullSession`. The 10min cap does not apply
to that post-completion fetch, so a wedged `session.messages` leaves
the tool call hanging indefinitely. The parent session appears stuck
to the user (the symptom they report on `/init-deep ultrafucking
deep`).

Fix: race the underlying `session.messages` call against a 5s timeout
through a new `withSdkCallTimeout` helper local to
`src/tools/background-task/` (mirrors `withFetchTimeout` in
`src/shared/dynamic-truncator.ts` and `withDispatchTimeout` in
`src/shared/prompt-async-gate.ts`). On timeout the caller returns a
clearly-marked `"Error fetching messages: ... timed out after 5000ms"`
fallback so the agent can recognise the failure and continue instead
of waiting forever.

Tests:
- New `sdk-call-timeout.test.ts` pins the fix with three BDD cases
  using a never-settling mock and the new
  `_setBackgroundOutputFetchTimeoutMsForTesting(50)` override:
  (1) `formatTaskResult` resolves to the timeout fallback under the
  fetch budget, (2) same for `formatFullSession`, (3) two parallel
  callers against the wedged client both resolve cleanly.
- All three failed (timed out) before the fix and pass in 51ms each
  after.

Verification:
- `bun test src/tools/background-task/` -- 33 pass.
- `bun test` (full suite) -- 7014 pass, 1 skip, 0 fail across 723
  files.
- `bun run typecheck` -- clean (tsgo --noEmit).
- LSP diagnostics on the four changed files -- 0 errors.
- Manual QA harness `.local-ignore/init-deep-hang-qa.ts` (gitignored)
  drove production default 5s timeout against a wedged client:
  serial `formatTaskResult` resolved in 5001ms, serial
  `formatFullSession` in 5002ms, 10 parallel `formatTaskResult` calls
  all resolved within 5002ms with the timeout fallback. Without the
  fix every call hangs indefinitely.

Refs: builds on #4086 (dynamic-truncator) which patched the Read hot
path of the same SDK hang.
2026-05-17 14:23:16 +09:00
YeonGyu-Kim 24261da824 Merge pull request #4103 from code-yeongyu/code-yeongyu/fix-prompt-hang-race
fix(call-omo-agent): fail fast on lost prompts
2026-05-17 14:02:40 +09:00
YeonGyu-Kim f4f1efcb6f fix(call-omo-agent): fail fast on lost prompts
Detect OpenCode promptAsync calls that return before a child session has any durable message, and surface a prompt acceptance error before the generic five-minute sync poll timeout.

Add a failing-first regression for the idle zero-message case and keep the existing durable-message completion path covered.

Debugging-Journal: .debugging
2026-05-17 13:57:12 +09:00
YeonGyu-Kim 75223149dd Merge pull request #4096 from code-yeongyu/kimi-k2.6
fix: add merge-conflict guard test to prevent source file corruption
2026-05-17 04:32:40 +09:00
YeonGyu-Kim 412ac04551 docs: add debugging journal for prompt hang investigation 2026-05-17 04:26:53 +09:00
YeonGyu-Kim d8f365bfd4 test(guard): add merge-conflict guard to prevent unresolved git conflicts in source files
Unresolved git merge conflict markers (<<<<<<<, =======, >>>>>>>) in
TypeScript source files break parsing and can cause the plugin to fail
at runtime or tests to hang with cryptic errors. This guard scans all
.ts/.tsx/.json files under src/ and fails the test suite if any
conflict markers are found.

Closes #debugging-hang-investigation
2026-05-17 04:17:45 +09:00
YeonGyu-Kim 38702f6e85 Merge pull request #4094 from code-yeongyu/fix/opus-4.7
fix(dynamic-truncator): bound session.messages fetch to stop forever-hang on Read (#4086)
2026-05-17 03:57:28 +09:00
YeonGyu-Kim c142066fd2 Merge pull request #4093 from code-yeongyu/k2p6-turbo
fix(prompt-async-gate): timeout isSessionActive to prevent infinite hang on stale SDK status
2026-05-17 03:54:21 +09:00
YeonGyu-Kim 67ead7bf6d fix(dynamic-truncator): bound session.messages fetch to stop forever-hang on Read (#4086)
Root cause: `getContextWindowUsage` caches the *promise* of
`fetchContextWindowUsage` in a per-session WeakMap keyed by client. When
`ctx.client.session.messages({ path: { id: sessionID } })` never settles
(observed once `service=session.processor ... error=Aborted process`
takes hold), the cached pending promise wedges every concurrent and
later caller in the same session. The five hooks that share one
`createDynamicTruncator(ctx)` -- directory-agents-injector,
directory-readme-injector, rules-injector, tool-output-truncator, plus
indirect callers -- all await that same poisoned promise on every Read,
so the user-facing tool chain hangs forever and ESC cannot break it.
Reporters in #4086 land on this path consistently when reading AGENTS.md
files (which trigger directory-agents-injector via the directory walk).

Fix: race the underlying `session.messages` call against a 5s timeout
through a new `withFetchTimeout` helper. On timeout the catch block logs
the failure and returns `null`, which `dynamicTruncate` already treats
as the "context usage unavailable" signal and falls back to the static
truncation budget. Successful responses still cache as before. The
`message.updated finish=true` invalidation hook still clears poisoned
caches on the next completed turn so retries are clean.

Tests:
- Add a never-settling `session.messages` mock with a 50 ms override via
  the new `_setContextWindowUsageFetchTimeoutMsForTesting` hook (matches
  the established `_setXxxForTesting` pattern in `opencode-http-api.ts`
  and `prompt-async-gate.ts`).
- Three new BDD cases pin the fix: (1) single caller returns null fast,
  (2) parallel concurrent callers all unblock on the same cached promise
  instead of hanging, (3) invalidate + retry rehydrates cleanly.
- All 8 pre-existing tests in the file still pass (happy paths, cache
  reuse, invalidation, env/model fallback).

Verification:
- `bun test src/shared/dynamic-truncator.test.ts` -- 11 pass.
- `bun test src/shared/prompt-async-route-audit.test.ts` -- 6 pass
  (added log import, no raw prompt route added).
- `bun test` (full suite) -- 7009 pass, 1 skip, 1 pre-existing flake in
  `closeTmuxPane` mock.module test (reproduces on dev without this
  change; isolated run passes).
- `bun run typecheck` -- clean.
- `bun run build` -- clean (esm bundle + tsc + schema).
- Manual harness `.debugging/manual-qa.ts` (uncommitted) drives the same
  shape as the real hook chain and resolves the hang scenario in 51 ms.
2026-05-17 03:51:56 +09:00
YeonGyu-Kim fcd0011a6b test(atlas): track active timers instead of scheduled delays in setTimeout mock
- Replace the simple scheduledDelays array with an activeTimers Map so
  that clearTimeout removes timers from the tracked set.
- This prevents false positives when internal withDispatchTimeout calls
  setTimeout for safety timeouts that are immediately cancelled.
- Keeps the test intent unchanged: only genuinely scheduled retries are
  counted as delayed duplicate retries.
2026-05-17 03:50:02 +09:00
YeonGyu-Kim 2613de522f fix(prompt-async-gate): timeout isSessionActive to prevent infinite hang on stale SDK status
- Wrap isSessionActive in withDispatchTimeout (capped at 5s) so a
  stuck OpenCode SDK status() call cannot block internal prompts forever.
- Catch the timeout and treat session as inactive so the prompt can
  proceed rather than hanging indefinitely.
- Add regression test: session.status that never resolves now times out
  and allows dispatch instead of hanging the test (and production).

Refs: AGENTS.md internal-message-injection safety note
2026-05-17 03:41:08 +09:00
YeonGyu-Kim 169e61f775 test(audit): allowlist build-team-idle-wake-hint-client.ts in prompt route audit
Red: a43215f24 introduced plugin/build-team-idle-wake-hint-client.ts which
    accesses session.promptAsync for method binding. The audit test flagged it
    as a raw prompt route offender, breaking CI on dev.

Green: Add the narrow client facade to RAW_PROMPT_ALLOWLIST with the same
       justification pattern used for event.ts and recover-unavailable-tool.ts.
       The facade binds SDK methods back to the Session instance and performs
       no direct dispatch itself; all downstream calls flow through the shared
       prompt-async gate.

Verification: bun test src/shared/prompt-async-route-audit.test.ts passes
(6 pass, 0 fail, offenders list empty).
2026-05-17 03:10:02 +09:00
YeonGyu-Kim a43215f24e fix(plugin/event): bind team-idle-wake-hint client methods to SDK Session
The team-mode wiring at `createEventHandler` extracted
`pluginContext.client.session.promptAsync` and `.status` into a fresh
wrapper object. The methods were copied by reference, so the prompt-async
gate's `session.promptAsync.bind(session)` was binding to that plain
wrapper rather than the underlying SDK `Session` instance. The opencode
SDK's `promptAsync` reads `this._client.post(...)`, so production calls
threw `TypeError: undefined is not an object (evaluating 'this._client')`
on the very first dispatch — fingerprinted in /tmp/oh-my-opencode.log as
688 `background-agent-parent-wake`, 47 `model-suggestion-retry`, and 4
`team-idle-wake-hint` failures over the past three days.

Move the wrapper construction into `buildTeamIdleWakeHintClient`, which
preserves the narrow factory contract while binding both methods back to
the SDK `Session` so `_client` survives the dispatch. Cover the contract
with four BDD-style tests including the historical destructure-only
failure mode so any future regression is caught at unit-test time.
2026-05-17 02:06:52 +09:00
YeonGyu-Kim 271878bcea perf(rules-injector): cache full candidates and memoize ancestor scans
The rule scan cache stored a path[] keyed by (projectRoot|startDir|
skipClaudeUserRules), so two issues stacked up on every tracked tool
call:

- Cache hits still ran safeRealpathSync(realpathSync) and re-derived
  isGlobal / distance / isSingleFile for every cached path. That is a
  per-candidate sync syscall plus repeated string-prefix walks.
- Sibling files in the same project landed under different startDir
  keys, so the entire walk-and-recursive-scan chain repeated even
  though every ancestor rule directory was identical.

Store the full RuleFileCandidate[] in the per-call cache so a cache hit
returns immediately with no realpath syscall. Add a separate per-
directory scan cache (getDirScan/setDirScan) keyed by absolute rule
directory path, so two sibling files reuse the same readdir + realpath
work for every shared ancestor.

Microbench (200 files / 20 modules / cached session):
- single sweep: 41.8ms -> 2.5ms (16x)
- 3-pass replay: 88.6ms -> 3.2ms (28x)

Pin the new invariants with two new tests:
- 'does not re-resolve symlinked rule path on cache hit' via a
  retargeted directory symlink.
- 'reuses ancestor directory scan for sibling files in the same
  project' by deleting the source rule file between the two calls.
2026-05-17 02:06:52 +09:00
YeonGyu-Kim c25f75294e perf(rules-injector): cache project root for visited ancestors
findProjectRoot was keyed by exact startPath, so sibling files in the
same project repeated the entire upward marker walk. The walk does one
existsSync per marker per ancestor directory, which adds up on every
read/write/edit/multiedit tool call.

Track every directory visited during the walk and seed the cache with
the resolved root for each of them. Subsequent lookups for any
descendant short-circuit to the cached ancestor without re-running
marker probes. Cache invalidation still happens on session.deleted /
session.compacted, so production semantics are unchanged.

Pin the new contract via a sibling-startpath test, and make the
existing finder.test.ts beforeEach explicit about cache state so the
more aggressive cache does not leak between tests.
2026-05-17 02:06:52 +09:00
YeonGyu-Kim f843f57cf0 Merge pull request #4088 from code-yeongyu/fix/session-agent-map-cleanup
fix(claude-code-session-state): clear session-agent map on delete and sync cleanup
2026-05-17 01:42:03 +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 25d8054192 Merge pull request #4074 from code-yeongyu/fix/delegate-task-spawn
fix(delegate-task): start child prompts reliably
2026-05-17 01:00:03 +09:00
YeonGyu-Kim c9ec11dd51 bump comment-checker to 0.7.1 2026-05-17 00:51:50 +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 cc97a023cc test(agents): drop unsafe AgentFactory cast and add typed empty skills
Tests that mock an AgentFactory were using an `as AgentFactory` cast
and a separate mutation of `mockFactory.mode` to satisfy the type.
Replace with Object.assign so the factory type is constructed correctly
without casts. Also type the empty discoveredSkills fixture so its
element type is inferred from the function signature instead of
collapsing to never[].
2026-05-17 00:09:21 +09:00
YeonGyu-Kim 791fbf3e55 refactor(delegate-task): share buildSyncPromptTools between bootstrap and prompt dispatch
Two call sites built the sync delegate tool gate independently:
sync-prompt-sender's prompt body construction and sync-task's bootstrap
registration. Drift between them would let bootstrap claim one tool set
while the actual prompt sent a different one. Extract buildSyncPromptTools
and route both call sites through it so the registered bootstrap and the
dispatched prompt always agree.
2026-05-17 00:09:11 +09:00
YeonGyu-Kim ea5f6ddfd7 fix(call-omo-agent): register bootstrap and session agent before sync prompt dispatch
call_omo_agent sync path created the child OpenCode session and went
straight into promptAsync without registering child session agent,
session tools, or bootstrap state. If first dispatch failed before any
durable user message persisted, runtime fallback could not reconstruct
the original prompt or the agent identity for that child session.

Bind setSessionAgent and setSessionTools to the child session id with
the same tool restrictions that the prompt body sends, register a
delegated child session bootstrap with the prompt text, fallback chain,
and prompt tools, then clean bootstrap + session tools in finally for
sessions this call created.
2026-05-17 00:08:56 +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 ba648685d4 fix(runtime-fallback): carry delegated system and tools through bootstrap retry
When the first prompt fails before any durable user message persists,
runtime fallback retry was rebuilding the request from parts alone and
losing the delegated agent system prompt and tool gates. Now it threads
bootstrap.system and bootstrap.tools into the retry body alongside the
captured retry parts, so the retried prompt keeps the same scope as the
initial delegate launch.
2026-05-17 00:08:30 +09:00
YeonGyu-Kim 761f682add refactor(delegated-bootstrap): accept optional system and tools
Add optional system and tools fields to DelegatedChildSessionBootstrap
so callers can stash the original delegated context alongside retry
parts. Backward compatible - existing callers stay unchanged.
2026-05-17 00:08:19 +09:00
YeonGyu-Kim 4f9813848a feat: add ci test runner, session routing, bash parser, and test fixtures
- script/run-ci-tests.ts: CI test sharding and isolation logic
- script/run-ci-tests.test.ts: tests for CI test target selection
- src/features/background-agent/session-route.ts: session prompt routing for background agents
- src/hooks/interactive-bash-session/parser.ts: interactive bash output parser
- src/hooks/ralph-loop/completion-promise-detector-test-input.ts: test fixture for completion promise detection
2026-05-16 23:51:43 +09:00
github-actions[bot] 166e5de06c @pizzav-xyz has signed the CLA in code-yeongyu/oh-my-openagent#4084 2026-05-16 11:43:47 +00:00