Commit Graph

760 Commits

Author SHA1 Message Date
YeonGyu-Kim 043e84be56 fix: add adaptHostSkillConfig utility for host config.skills.paths
Converts the host OpenCode config.skills object (with paths/urls arrays set by other plugins like superpowers) into the SkillsConfig format used by discoverConfigSourceSkills. Filters blank/whitespace entries and non-string values.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 13:43:08 +09:00
YeonGyu-Kim b20e2c9c55 fix: refactor 5 additional aliased output.args mutations + strengthen audit test
Address Oracle review feedback: refactor 4 aliased mutations via argsObject
in plugin/tool-execute-before.ts and 1 via toolOutput in atlas/tool-execute-before.ts.
Strengthen audit test regex to catch Output.args mutations regardless of the
variable name prefix (toolOutput, argsObject aliases).

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 13:04:10 +09:00
YeonGyu-Kim 3a63a8b205 test(shared): add audit test forbidding direct output.args mutation + fix 9th site
Add replace-tool-args.audit.test.ts that scans src/**/*.ts for direct
output.args property assignments and Object.assign(output.args, ...) outside
the helper. Also fix the 9th mutation site discovered by the audit in
compaction-todo-preserver/hook.ts.

Add replace-tool-args.test.ts with 12 regression tests covering both mutable
and Object.freeze'd output.args scenarios for all hook patterns.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 12:46:52 +09:00
YeonGyu-Kim 6bffb2c40f feat(shared): add replaceToolArgs helper for safe tool-args mutation
opencode >=1.14 freezes output.args via Immer before plugin hooks run.
Direct property assignment or Object.assign on a frozen object throws
TypeError. This helper replaces output.args with a shallow clone
containing the patch, avoiding mutation of the frozen original.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 12:32:10 +09:00
Chau Luu 4713a90816 fix(shared): cap log file growth via size-based rotation
Refs #3772 (the rotation half — EPIPE shutdown-noise suppression
remains a separate follow-up).

`src/shared/logger.ts` appends every entry to `os.tmpdir()/oh-my-opencode.log`
via `fs.appendFileSync` with no size cap. On long-running or busy projects
the file grows into the multi-GB range — a real-world reproduction on one
machine showed a 4.5 GB `oh-my-opencode.log.1` accumulated from per-shutdown
noise across many sessions. Eats `%TEMP%` on Windows and `/tmp` on Unix.

Add size-based rotation inside the existing batched `flush()` path:

  oh-my-opencode.log    → oh-my-opencode.log.1
  oh-my-opencode.log.1  → oh-my-opencode.log.2 (oldest dropped)

Cap is 50 MB per file; worst-case on-disk footprint is therefore ~150 MB.
The check runs only inside `flush()`, so the cost is amortized over
`BUFFER_SIZE_LIMIT` (50 entries) or the 500 ms flush timer. All filesystem
ops stay wrapped in try/catch — logging must never throw — and a failed
rotation leaves existing on-disk state intact rather than crashing the
agent. Pattern mirrors `src/openclaw/reply-listener-log.ts`, but with two
backup slots instead of one to keep a usable history window for debugging.

No config knobs in this iteration. The issue proposes `logs.max_size_mb`
/ `logs.max_files`, but the defaults are reasonable and adding schema is
more surface area than the bug warrants. Easy to promote later (the
existing test seams already let callers override the cap).

Tests:
- `src/shared/logger.test.ts` (new): under-threshold no-rotate, over-
  threshold rotates to `.1`, repeated rotation evicts oldest, rotation-
  failure-doesn't-throw, default path lives under `os.tmpdir()`. Uses a
  `mock.module(...)` substring marker so `script/run-ci-tests.ts` routes
  the file to its own bun process — the logger module's singleton state
  otherwise gets contaminated by sibling tests that mock `./shared`.

Out of scope: suppressing specific shutdown-noise messages (EPIPE,
`unhandledRejection received during shutdown cleanup`). The rotation
cap bounds the disk impact regardless of which noise pattern is
generating volume; per-message suppression can stand on its own
merits in a follow-up.
2026-05-17 19:46:29 +00: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 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 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 a7b7ace7ed fix(prompt-gate): block prompts into pending tool turns 2026-05-17 15:42:58 +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 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 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 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 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 b5992b13ec test(shared): stabilize port utility interface check 2026-05-16 18:28:33 +09:00
YeonGyu-Kim cdac0d69bb fix(workspace): report only the active notepad change 2026-05-16 18:12:50 +09:00
YeonGyu-Kim 240a4a17ad fix(workspace): harden omo migration review issues 2026-05-16 18:02:54 +09:00
YeonGyu-Kim f10f796318 fix(workspace): keep omo and legacy rules compatible 2026-05-16 17:41:49 +09:00
YeonGyu-Kim 36e373cdbb feat(workspace): point planning guardrails at omo 2026-05-16 17:41:35 +09:00
YeonGyu-Kim 5dca1a5742 feat(workspace): migrate legacy sisyphus state to omo 2026-05-16 17:39:54 +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 eba17441cf test(mock-module-audit): require lifecycle cleanup for mock.module
New AST-based audit walks all *.test.ts files under src/ and asserts every mock.module(...) call is paired with cleanup. Existing offenders are documented in MOCK_MODULE_LIFECYCLE_ALLOWLIST with TODO references.

Closes HIGH-10
2026-05-16 02:13:11 +09:00
YeonGyu-Kim 102d067022 fix(model-suggestion-retry): release reservation on async error path
The promptWithModelSuggestionRetry async variant did not release the
post-dispatch reservation when the wrapped promptAsync threw. Callers
that immediately retry (such as sendSyncPrompt error toast paths) hit
the gate as reserved and surfaced 'promptAsync skipped by gate: reserved'
instead of the underlying error.

Mirrors the existing sync variant fix from ff1b15d53.

Closes regression introduced by BLOCKER-2 hardening
2026-05-16 01:56:04 +09:00
YeonGyu-Kim 4848017219 test(mock-module-audit): require lifecycle cleanup for mock.module
Walk all test files, parse with TypeScript Compiler API, assert every
mock.module(path, factory) invocation has a paired afterEach/afterAll
cleanup. Existing offenders are allowlisted with TODOs for v4.2.1 work.

Closes H10
2026-05-16 01:37:28 +09:00
YeonGyu-Kim c096a596eb test(mock-module-audit): require lifecycle cleanup for mock.module
Closes H10

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-16 01:35:10 +09:00
YeonGyu-Kim ff1b15d533 fix(model-suggestion-retry): release reservation before retry attempt
After BLOCKER-2's post-dispatch hold landed (the gate now keeps the
reservation through the hold window regardless of whether the dispatch
threw), the synchronous retry path inside promptSyncWithModelSuggestionRetry
hit 'reserved' on its own second attempt because the first attempt's
post-dispatch hold was still active.

The first attempt's failure is ProviderModelNotFoundError, which is a
synchronous SDK rejection - the prompt never reached the server, so
there is no durable session state worth protecting from a duplicate
injection. Release the post-dispatch reservation hold explicitly before
the suggested-model retry so the second attempt can dispatch immediately.

Fixes test regression introduced by the gate hardening (BLOCKER-2 fix).
2026-05-16 00:53:15 +09:00
YeonGyu-Kim 8c4cc09de7 test(prompt-async-route-audit): migrate to TypeScript AST walker
Replaces the previous regex-based audit (6 line-prefix patterns) with a
TypeScript Compiler API AST walker that detects raw client.session.prompt
and client.session.promptAsync access in any access shape:
- direct call (existing): client.session.promptAsync(...)
- property access reference: const x = client.session.promptAsync
- bracket access: client['session']['promptAsync']
- optional chaining: client.session?.promptAsync
- type cast aliasing: (client.session as { promptAsync }).promptAsync
- destructuring: const { promptAsync } = client.session

RAW_PROMPT_ALLOWLIST captures two legitimate callers that route through
the gate but reference promptAsync as a property value:
- src/plugin/event.ts wires a client facade for team-idle-wake-hint
- src/hooks/session-recovery/recover-unavailable-tool.ts guards capability
before dispatching through promptAsyncAfterSessionIdle.

Each allowlist entry carries a justification string so future contributors
understand why the exception exists.

Closes HIGH-5

Co-authored-by: audit-ast (deep / gpt-5.3-codex high)
2026-05-16 00:49:50 +09:00
YeonGyu-Kim b333a52800 fix(prompt-async-gate): add dispatch timeout, shared runner, harden prefix release
BLOCKER-1 (dispatch deadlock): wrap session.promptAsync / session.prompt in
withDispatchTimeout() that uses Promise.race with a default 30s timeout.
Stalled upstream responses no longer hold the reservation forever.

BLOCKER-2 (post-dispatch failure released too early): collapse the
holdReservationAfterDispatch flag into a dispatchAttempted state so the
post-dispatch hold runs in the finally block regardless of whether
promptAsync resolved or threw. AGENTS.md's documented race window where
promptAsync 'returns before durably accepted, later failures arrive as
session.error' is now covered.

HIGH-6 (sync/async protocol duplicated): extract dispatchAfterSessionIdle
internal runner. promptAsyncAfterSessionIdle and promptAfterSessionIdle
become thin wrappers passing client.session.promptAsync vs prompt as
the dispatch callback. Future reservation semantics fixes apply once.

HIGH-7 (releasePromptAsyncReservation prefix foot-gun, partial): tighten
reservationSourceMatches to require prefix strings to end in ':' so
release cannot accidentally free reservations whose source merely starts
with the same identifier characters. Symbol token verification is still
internal-only as the audit invariant prevents external callers from
bypassing the gate.

Closes BLOCKER-1, BLOCKER-2, HIGH-6
Refs HIGH-7 (prefix hardened; token-required release deferred to follow-up)

Co-authored-by: gate-correctness (deep / gpt-5.3-codex high)
2026-05-16 00:49:28 +09:00
YeonGyu-Kim d02cca4422 test(tmux): align pane replace placeholder expectations 2026-05-16 00:18:33 +09:00
YeonGyu-Kim 54a7256a71 test(tmux): align placeholder command expectations 2026-05-16 00:14:06 +09:00
Disaster-Terminator 91f1cf5fbc fix(tmux): pin pane commands to /bin/sh 2026-05-16 00:08:51 +09:00
Disaster-Terminator 8c5ca73634 fix(tmux): sweep suffixed stale isolated sessions 2026-05-16 00:07:57 +09:00
Disaster-Terminator 0c8e546c57 fix(tmux): support manager-scoped isolated session names 2026-05-16 00:06:58 +09:00
Disaster-Terminator c63108d55b fix(tmux): track placeholder panes before attach readiness 2026-05-16 00:02:02 +09:00
Disaster-Terminator 688bb551b2 fix(tmux): defer subagent attach until pane focus 2026-05-15 23:57:07 +09:00
YeonGyu-Kim ad10450b5c Merge pull request #3841 from Momentum96/fix/background-manager-tmux-ordering 2026-05-15 23:35:29 +09:00
YeonGyu-Kim c580b8f2ce fix(session): ignore internal synthetic turns 2026-05-15 23:16:05 +09:00
Momentum96 efb862ce9f fix(tmux): prefer real tmux when session env exists 2026-05-15 23:12:56 +09:00
YeonGyu-Kim 3e9b125fd5 test(runtime-fallback): cover localized provider errors 2026-05-15 22:25:20 +09:00
wjiuxing c206b16865 feat: add Chinese error patterns to model-error-classifier 2026-05-15 22:23:06 +09:00
YeonGyu-Kim 27788b4ab0 fix(session-recovery): audit raw prompt aliases 2026-05-15 21:53:05 +09:00
YeonGyu-Kim 2bd4944bad fix(prompt-gate): scope reservation releases 2026-05-15 21:52:29 +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 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