Skip to content

feat(extensions): ship usable ooo interview bridge - #3805

Merged
Yeachan-Heo merged 11 commits into
devfrom
gajae-code-issue-3803-ooo-bridge-usable
Aug 4, 2026
Merged

feat(extensions): ship usable ooo interview bridge#3805
Yeachan-Heo merged 11 commits into
devfrom
gajae-code-issue-3803-ooo-bridge-usable

Conversation

@Yeachan-Heo

Copy link
Copy Markdown
Owner

Summary

  • ship a first-class ooo-bridge.ts example extension with user-level and project-level enable commands
  • bind the canonical helper to ouroboros dispatch --runtime gjc so ooo interview reaches Ouroboros v0.50.7's GJC MCP-backed skill dispatcher
  • document preferred ouroboros setup --runtime gjc, manual GJC installation, graceful failure modes, and the distinction from native /skill:deep-interview
  • test the real shipped example registration, exact runtime-bound argv, missing executable handling, and existing bridge contracts

Closes #3803.

Community sources:

  • Discord #playground-ko message 1534010886245580891
  • Discord #playground-ko message 1534014083462860841

Latest-release grounding

  • GJC base: origin/dev at a8fa63a599e9fa3d0189e48bfce778a36353ccdd
  • Ouroboros release: v0.50.7, commit cb658aa819bfabafecbbe91bc36327f10691171b
  • Verified against the release's GJC runtime guide and hidden dispatch entrypoint, where --runtime gjc binds shared MCP handler composition

Verification

  • bun test packages/coding-agent/test/ooo-bridge-extension-contract.test.ts packages/coding-agent/test/ooo-bridge-runner-redteam.test.ts packages/coding-agent/test/extensions-discovery.test.ts — 60 pass, 0 fail
  • bun --cwd=packages/coding-agent run check — Biome clean, TypeScript noEmit clean
  • git diff --check — clean
  • gjc ultragoal review --spec <approved-plan> --executor-qa-json <qa-report> --mode review-only --json — no findings, artifact validation passed


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Follow-up CI fix pushed in ca04665d4: regenerated packages/coding-agent/src/internal-urls/docs-index.generated.ts after the bridge contract documentation change.

Verification:

  • bun run check:public-sync passed
  • focused bridge/runner/discovery suite remains 60 pass / 0 fail
  • git diff --check passed


[repo owner's gaebal-gajae (clawdbot) 🦞]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78c8f62d87

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -0,0 +1,6 @@
import type { ExtensionAPI } from "@gajae-code/coding-agent";
import { createOuroborosOooBridge } from "@gajae-code/coding-agent/extensibility/extensions";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Load the bridge through the injected extension API

For users running the supported standalone release binary and following the documented copy-install command, this runtime import prevents the extension from loading. loadLegacyPiModule() mirrors user extensions under /tmp; its compiled-mode fallback explicitly cannot resolve bundled @gajae-code/* packages and instead searches for a peer dependency beside the copied extension (legacy-pi-compat.ts:123-130 and 272-285), where this one-file install has none. Consequently the factory never registers its input handler. Access the root-exported helper through the injected API, such as pi.pi.createOuroborosOooBridge(), rather than importing the package at runtime.

AGENTS.md reference: AGENTS.md:L7-L7

Useful? React with 👍 / 👎.

code: 0,
killed: false,
});
const handler = registrations[0]?.handler as ReturnType<typeof createOuroborosOooBridge>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Replace ReturnType with the explicit handler type

Replace this inferred ReturnType<> cast with the concrete extension-handler function type; the repository contract explicitly prohibits ReturnType<>, and the reported typecheck does not enforce that convention automatically.

AGENTS.md reference: AGENTS.md:L103-L105

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

REQUEST_CHANGES — exact-head adversarial review

Reviewed ca04665d43f165b4b91c7bd07c8cc565f8c57d90. CI is green and the generated docs index is synchronized, but the advertised user behavior is not implemented.

  • HIGH — successful output is swallowed. createExactPrefixCommandBridge() returns only { handled: true } on exit 0 and discards captured stdout (prefix-command-bridge.ts:88-90). GJC's interactive controller also returns immediately for a handled input before applying result.text (input-controller.ts:823-832). Ouroboros v0.50.7's managed bridge returns its question/result as { handled: true, text: body }, so that path is dropped too. The added example test mocks empty stdout and asserts only argv plus handled; it never proves that the first interview question is visible.
  • HIGH — no multi-turn interview correlation exists. Ouroboros v0.50.7's CLI dispatch calls its dispatcher with current_handle=None; interview resume arguments are added only when a handle containing ouroboros_interview_session_id is supplied. The CLI prints result content but does not persist/reuse that handle, and this extension does not claim subsequent ordinary answers. Each invocation therefore starts fresh rather than continuing an interview.
  • MEDIUM — unsafe/drifting manual installation. The new copy-install commands place mutable dev-branch source directly into auto-loaded extension directories, while the upstream installer is fetched from mutable main and piped to a shell. Use immutable release assets/tags with integrity guidance.
  • MEDIUM — manual bridge compatibility differs from upstream. Ouroboros's v0.50.7 bridge honors OUROBOROS_CLI; the GJC-owned alternative hardcodes ouroboros, so installations relying on the upstream override fail only on the documented alternative.

Required coverage is an observable installed-extension flow proving visible first-question output and correlated second-turn continuation/termination, plus immutable installation and executable-override behavior. Helper-level argv assertions are insufficient.

Signature: GJC adversarial review | PR #3805 | exact head ca04665d43f165b4b91c7bd07c8cc565f8c57d90 | REQUEST_CHANGES


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Exact-head REQUEST_CHANGES contract repaired and pushed in 857748424fce4c98111d68ecf595bbada16c1059 and 0f6f1ebea.

Resolved blockers:

  • Successful handled stdout/text is now returned by the prefix bridge and rendered as a visible interactive custom message.
  • ooo interview now uses a persistent ouroboros mcp serve --runtime gjc connection, records the returned session_id, sends the next ordinary interactive input as the correlated answer, renders continuation/completion, closes on terminal completion, and stops claiming ordinary input afterward.
  • Installation guidance no longer pipes a mutable branch installer into a shell. It pins Ouroboros v0.50.7 plus source commit cb658aa819bfabafecbbe91bc36327f10691171b, publishes the release-wheel SHA-256, and downloads the GJC example from exact commit 857748424fce4c98111d68ecf595bbada16c1059 with its SHA-256.
  • OUROBOROS_CLI is honored by both the MCP interview path and non-interview dispatch path.
  • Added an installed-example flow through the real extension loader, ExtensionRunner, and InputController, covering the first visible question, second-turn session correlation, visible termination, disconnect, and post-completion pass-through.

Validation evidence:

  • bun test packages/coding-agent/test/ooo-bridge-extension-contract.test.ts packages/coding-agent/test/ooo-bridge-runner-redteam.test.ts packages/coding-agent/test/ooo-bridge-installed-flow.test.ts packages/coding-agent/test/extensions-discovery.test.ts62 pass, 0 fail, 178 expect() calls
  • bun --cwd=packages/coding-agent run checkBiome clean across 2511 files; TypeScript --noEmit clean
  • bun run check:public-syncpassed
  • bun run generate-docs-indexgenerated 120-doc embedded index
  • git diff --checkclean

No merge, release, or CI control was performed.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f6f1ebeac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

args.initial_context = commandArgument ?? "";
}

const result = await invoke(activeConnection, INTERVIEW_TOOL, args);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep slow interview calls terminal after the runner timeout

When the MCP tool call takes longer than the extension runner's 30-second handler timeout, emitInput() receives no result and forwards the original ooo interview input through normal model processing, but this uncancelled invocation continues in the background and can later set interview, causing subsequent prompts to be unexpectedly claimed as answers. Pass cancellation into the MCP call or otherwise ensure a timed-out interview cannot mutate state after the input has fallen through.

Useful? React with 👍 / 👎.

Comment on lines +135 to +136
if (argument !== undefined || (interview && !isOooCommand(event.text))) {
return runInterview(event.text, ctx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route active-interview shortcuts through the bridge

When an active interview expects an answer that is exactly c or ., this branch never receives it because InputController.submitText() handles those continue shortcuts and returns before calling emitInput() (lines 808-815). The input consequently triggers GJC's normal continuation behavior while the Ouroboros interview remains waiting, contradicting the documented claim that ordinary interactive input is claimed as the answer.

Useful? React with 👍 / 👎.

@Yeachan-Heo
Yeachan-Heo force-pushed the gajae-code-issue-3803-ooo-bridge-usable branch from 0f6f1eb to dc97250 Compare August 4, 2026 04:51
@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Reconciled PR #3805 onto current origin/dev without conflicts and pushed the existing branch with an exact --force-with-lease against prior head 0f6f1ebeacbb3e88b70b8784bea7e5b643cb2ae3.

Exact ancestry after push:

  • base / origin/dev: 5c8f10a9a840d8d04a17c5fae54a6b5a961e9f2e
  • reconciled head: dc972504e86277b3dff102eae946ba7053b124e7
  • merge-base: 5c8f10a9a840d8d04a17c5fae54a6b5a961e9f2e
  • git merge-base --is-ancestor origin/dev HEAD: passed
  • GitHub now reports mergeable: MERGEABLE; mergeStateStatus: UNSTABLE reflects checks rather than branch conflicts.

The reviewed four fixes remain present: visible successful output, correlated multi-turn interview continuation/termination, pinned integrity-checked installation guidance, and OUROBOROS_CLI compatibility. git range-diff mapped the implementation repair 857748424 to rebased equivalent c4eb6c9ef; the immutable example URL and verification baseline were refreshed for the reconciled history.

Validation after rebase:

  • bun test packages/coding-agent/test/ooo-bridge-extension-contract.test.ts packages/coding-agent/test/ooo-bridge-runner-redteam.test.ts packages/coding-agent/test/ooo-bridge-installed-flow.test.ts packages/coding-agent/test/extensions-discovery.test.ts62 pass, 0 fail, 178 expect() calls
  • bun --cwd=packages/coding-agent run checkBiome clean across 2513 files; TypeScript --noEmit clean
  • bun run check:public-syncpassed
  • bun run generate-docs-index followed by generated-file git diff --exit-code120-doc index current and idempotent
  • git diff --checkclean
  • worktree and remote branch are synchronized.

No merge, release, or CI control was performed.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc972504e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

});

async function connection(ctx: ExtensionContext): Promise<MCPServerConnection> {
if (activeConnection) return activeConnection;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Discard a closed MCP connection before handling more input

If the Ouroboros stdio server exits during an active interview, StdioTransport marks itself disconnected and rejects future requests, but this branch continues returning the cached connection. The catch path also preserves interview, so every subsequent ordinary prompt is claimed as another answer and fails against the same dead transport until GJC is restarted. Clear the interview/connection on transport closure or failure, or verify transport.connected before reusing it.

Useful? React with 👍 / 👎.

Comment on lines +135 to +136
if (argument !== undefined || (interview && !isOooCommand(event.text))) {
return runInterview(event.text, ctx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Let built-in slash commands bypass active interviews

While an interview is active, inputs such as /exit, /quit, /new, and /clear satisfy this condition and are sent to Ouroboros as answers. InputController.submitText() runs extension input handlers before executeBuiltinSlashCommand(), so the handled result prevents those application controls from executing; users consequently cannot use normal slash commands to exit, reset, or otherwise control the session until the interview completes. Exclude slash-command inputs from interview answer interception.

Useful? React with 👍 / 👎.

@Yeachan-Heo Yeachan-Heo left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial rereview — ooo interview bridge (read-only, exact head)

Verdict: REQUEST_CHANGES

I re-reviewed the exact current head, not the cached PR metadata. Findings below are bounded and evidence-backed; I did not mutate source, push, rebase, alter CI, or close anything.

Head reconciliation (verified)

  • gh pr view reported headRefOid = 0f6f1ebe, but the live branch has moved. Real current head = dc972504e ("chore(extensions): refresh ooo bridge base references"), rebased onto current dev 5c8f10a9a (merge-base clean — the earlier CONFLICTING/DIRTY against the old base a8fa63a is resolved by the rebase).
  • The 8 core source/test files are byte-identical between 0f6f1ebe and dc972504e; the new top commit only refreshed immutable-commit references in docs/README/changelog/test-report. So my source analysis of 0f6f1ebe carries over intact.
  • Immutable trust boundary holds: referenced commit c4eb6c9ef exists; its examples/extensions/ooo-bridge.ts SHA-256 = 7f469917… (matches docs in both docs/ooo-bridge-extension-contract.md and examples/extensions/README.md). Ouroboros v0.50.7 release is published and non-draft.

What I verified as correct

  • Handled text visibility: {handled:true,text} is rendered as a visible [extension-input-result] framed custom message (ui-helpers.ts:601CustomMessageComponentrenderFramedMessage default path renders the [label] + content Markdown). Content is sanitizeText(...).trim(). The non-handled text replacement semantics in the runner are unchanged. ✓
  • Multi-turn MCP correlation: single connectToServer, session_id correlation across ordinary answers, disconnectServer on completion, ordinary input stops being claimed after meta.completed/phase==="complete" — exercised by ooo-bridge-installed-flow.test.ts + ooo-bridge-extension-contract.test.ts. ✓
  • Exec override: OUROBOROS_CLI honored for both the MCP mcp serve path and dispatch path. ✓ (minor: read once at construction for the dispatch bridge, per-call for MCP — inconsistent lifetimes, benign in practice.)
  • Generated artifacts: generate-docs-index regenerates docs-index.generated.ts with zero diff (only a natives .d.ts trailing-line churn from my own build); check:public-sync passes; embedded contract content matches the standalone doc. ✓
  • Tests/gates (run locally at dc972504e in a detached worktree): ooo-bridge-extension-contract + ooo-bridge-installed-flow = 31 pass / 0 fail (81 expects); with ooo-bridge-runner-redteam + extensions-discovery = 62 pass / 0 fail (178 expects) — matches the committed test-report claim. bun --cwd=packages/coding-agent run check (Biome 2513 files + tsc --noEmit) = exit 0.

Blocking issues — REQUEST_CHANGES

P1 — A — Timed-out interview mutates state after fall-through. runInterview awaits callTool with no cancellation. If it exceeds the extension runner's ~30s handler timeout, emitInput() returns no result and the ooo interview input falls through to normal model flow, but the orphaned invoke promise keeps running and can set interview = { sessionId } afterward — so subsequent ordinary prompts get silently claimed as answers. (Codex flagged the same at ouroboros-ooo-bridge.ts:113.) The interview result must be cancelled or its late state-mutation guarded against a runner-timeout fall-through.

P1 — B — Dead stdio connection wedges the session. If the Ouroboros MCP server exits mid-interview, connection() keeps returning the cached (now-dead) activeConnection, the catch path notifies but never clears interview/activeConnection, and every subsequent ordinary prompt is claimed, re-enters runInterview, and fails against the same dead transport — with no escape until GJC restart. No ooo <non-interview> input, no slash command, and no explicit abort clears the correlation. (Codex flagged the same at ouroboros-ooo-bridge.ts:78.) Transport closure/error must clear interview state.

P1 — C — Compiled-binary install path is unverified and likely broken. The docs instruct a one-file copy-install (curl … ooo-bridge.ts → …/ouroboros-ooo-bridge/index.ts) and claim it then works in a fresh GJC session. But every extension file is loaded via loadLegacyPiModule, which mirrors it to /tmp and resolves @gajae-code/coding-agent/extensibility/extensions through legacy-pi-compat.ts. In --compile release mode the primary Bun.resolveSync against /$bunfs/root fails (acknowledged in comments), and the fallback resolves against the importer dir — which for a one-file copy has no node_modules — so the import is unresolved and the handler never registers, silently. This is the same resolution class as the pre-existing tools.ts value-import, but this PR's docs explicitly promise the copy-install works. I could not build a full release binary read-only to close this; needs owner confirmation + either a runtime-verified install recipe or resolution via the injected API (e.g. pi.pi.createOuroborosOooBridge()) rather than a package import. (Codex P1 at ooo-bridge.ts:2.)

P2 — D — Slash commands and continue-shortcuts are intercepted as answers. Extension input handlers run before executeBuiltinSlashCommand() and before the c/. continue-shortcut handling in submitText(). While an interview is active, /exit, /quit, /new, /clear, and bare c/. are sent to Ouroboros as answers (slash) or never reach the bridge (c/.), contradicting the doc claim that "ordinary interactive input" is claimed and blocking session control mid-interview. Slash-command inputs must bypass answer interception. (Codex P2 at ouroboros-ooo-bridge.ts:136.)

CI not yet corroborative. Dev CI for dc972504e is still in_progress (native-build pending); the affected-shards matrix (typecheck/tests) has not dispatched yet because it gates on native-build. My local gate run is green, but CI has not confirmed at head as of this review.

Recommendation

Fix P1-A/B/D (interview lifecycle robustness + control bypass) and resolve P1-C (compiled-mode install) before merge. The happy-path implementation is correct and the test coverage for that path is sound; the defects are all in the failure/lifecycle edges and the install verification.

— gaebal-gajae

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

REQUEST_CHANGES

Exact head dc972504e86277b3dff102eae946ba7053b124e7 is clean/mergeable and CI has no non-green checks. The four claimed fixes are present: handled stdout is rendered visibly; the happy-path interview reuses session_id and terminates; setup references are immutable and hashes match; OUROBOROS_CLI drives both MCP and dispatch.

Blocking lifecycle/UI/install gaps remain:

  • runInterview() awaits an uncancelled MCP call while the runner times out via Promise.race; a late response can set interview after the original input has fallen through to normal model flow.
  • On MCP failure the catch path leaves both interview and cached activeConnection intact, so ordinary prompts remain captured and repeatedly hit a dead transport.
  • Active interviews capture /exit, /quit, /new, /clear, etc. because extension input runs before built-in slash handling; bare c/. are consumed even earlier and cannot answer the interview.
  • The documented one-file install imports @gajae-code/coding-agent/extensibility/extensions; compiled-mode fallback resolves against the copied extension directory, which has no peer node_modules. The installed-flow test runs source mode and does not verify the promised compiled-binary install.

Current tests are happy-path false reassurance: none exercises runner timeout/late settlement, dead transport recovery, control-command bypass, or compiled installation. Post-rebase dev drift is only #3809 tmux files and does not alter these findings.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo
Yeachan-Heo force-pushed the gajae-code-issue-3803-ooo-bridge-usable branch from dc97250 to d5e0681 Compare August 4, 2026 05:36
@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Fresh exact-head REQUEST_CHANGES blockers repaired and pushed at reconciled head d5e0681f34df33798b273e876753e1d945f205fb (implementation c2c8e417d97f542c46881df9787baa967da684ed).

Resolved only the four requested blockers:

  1. Late MCP settlement fenced: extension handler timeout now aborts a per-handler AbortSignal; the Ouroboros bridge passes it to MCP connect/call operations and generation-fences every post-await state mutation. The timeout regression proves a late question cannot recreate interview state after runner fall-through.
  2. Dead transport released: every MCP connect/tool failure clears the interview session, pending/cached connection, and disconnects the active transport before notifying. Ordinary prompts pass through afterward; a new explicit interview creates a fresh connection.
  3. Controls bypass capture: slash-prefixed controls (/exit, /quit, /new, /clear, and other slash UI commands) plus bare . and c bypass the active interview. Ordinary answer text still continues with the correlated session_id.
  4. Compiled one-file install works: the copied example now has no runtime imports and obtains the bundled helper from GJC's injected host API. A real Bun-compiled loader test copies it into an isolated extension directory with no peer node_modules, starts the compiled executable, and proves exactly one input handler registers.

The branch was rebased onto current origin/dev before push:

  • base / merge-base: df8c91cfa30400a25251776e569191af68cdef16
  • head: d5e0681f34df33798b273e876753e1d945f205fb
  • GitHub: MERGEABLE (UNSTABLE is check state, not a conflict)

Validation:

  • bun test packages/coding-agent/test/ooo-bridge-extension-contract.test.ts packages/coding-agent/test/ooo-bridge-runner-redteam.test.ts packages/coding-agent/test/ooo-bridge-installed-flow.test.ts packages/coding-agent/test/extensions-discovery.test.ts packages/coding-agent/test/extensions-runner.test.ts97 pass, 0 fail, 278 expect() calls
  • bun --cwd=packages/coding-agent run checkBiome clean across 2514 files; TypeScript --noEmit clean
  • bun run check:public-syncpassed
  • bun run generate-docs-index plus clean git diff --exit-code120-doc index current and idempotent
  • git diff --checkclean
  • worktree and remote branch are synchronized.

No merge, release, or CI control was performed.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d5e0681f34

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


return async (event: InputEvent, ctx: ExtensionContext): Promise<InputEventResult> => {
if (event.source !== undefined && event.source !== "interactive") return {};
if (isBuiltInControlInput(event.text)) return {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reset interview state on session-changing controls

When an interview is active and the user enters /new, /drop, or /clear, this blanket bypass lets the command execute without clearing interview or closing its MCP connection. The next ordinary prompt in the fresh or cleared session is therefore sent as an answer to the old Ouroboros session instead of GJC. Fresh evidence in the current tree is that AgentSession.#initializeNewSessionState() emits session_switch through the existing ExtensionRunner, while the shipped example registers only an input handler, and clearContext() emits no extension lifecycle event at all.

Useful? React with 👍 / 👎.

Comment on lines +183 to +185
const argument = interviewArgument(event.text);
if (argument !== undefined || (interview && !isOooCommand(event.text))) {
return runInterview(event.text, ctx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Serialize overlapping interview submissions

If the user presses Enter again while an MCP interview request is still pending, another invocation can enter this branch concurrently because the TUI editor's onSubmit callback is synchronous and does not await submitText(). During startup, interview is still undefined, so ordinary text falls through to the model; during a continuation, multiple calls send competing answers with the same session ID and whichever response finishes last overwrites the state. Track an in-flight operation and serialize or terminally claim subsequent interview input until it settles.

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

REQUEST_CHANGES

Reviewed exact head d5e0681f34df33798b273e876753e1d945f205fb rebased on df8c91cfa; GitHub reports clean mergeability and 0 non-green checks (32 check runs: 25 success, 5 skipped, 2 neutral/other non-blocking).

The four requested repairs are materially present: timeout abort/generation fencing prevents late state resurrection; MCP failures clear connection/session state; slash and c/. controls bypass answer capture; and the copied extension is now runtime-import-free with a compiled loader test that runs without adjacent node_modules.

Two blocking lifecycle regressions remain:

  • /new and /drop execute but do not reset the bridge. AgentSession reuses the same ExtensionRunner and emits session_switch, while the example registers only input; the next ordinary prompt in the new session is therefore sent to the old Ouroboros session_id. /clear likewise emits no extension lifecycle event and preserves stale capture.
  • Interview operations are not serialized. The TUI editor invokes onSubmit as a void callback without awaiting submitText(). A second Enter during startup sees no interview yet and falls through to the model; overlapping continuation answers invoke MCP concurrently with the same session ID and race to overwrite state.

The new tests provide false assurance on these edges: the control test calls the handler sequentially and then resumes the old interview, and no test overlaps startup/answer submissions or executes a real session-changing control through InputController/AgentSession.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo
Yeachan-Heo force-pushed the gajae-code-issue-3803-ooo-bridge-usable branch from d5e0681 to b267606 Compare August 4, 2026 06:18
@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Second exact-head lifecycle blockers repaired and pushed at reconciled head b267606546d394908bbfd4f8458cbf1ee92d16ad (implementation 4b07ee81675d16b81fd5eeb9645042d8fc307c3a).

Resolved only the two requested blockers:

  1. Session-changing controls dispose bridge state

    • The standalone extension now registers both input and session_switch handlers.
    • session_switch resets correlation, aborts the active operation, clears pending/cached connections, and disconnects the transport before the reused ExtensionRunner serves the successor session.
    • /new, /drop, /clear, /exit, and /quit also reset immediately in the input path; /clear is covered despite preserving the GJC session identity and emitting no switch event.
    • Integration coverage uses the real InputController, a real AgentSession with the installed extension runner, an actual AgentSession.newSession() transition, and the actual /clear control path. Subsequent ordinary submissions reach GJC rather than either prior Ouroboros session_id.
  2. Interview operations are serialized

    • Explicit startup marks capture synchronously before the first await, so a second non-awaited TUI submission cannot fall through while startup is pending.
    • Startup and continuation operations use one FIFO chain and one active-operation abort domain.
    • Concurrent answers issue at most one MCP call at a time. The queued answer starts only after the prior response settles and reads the latest correlated interview state.
    • Deterministic tests hold startup/answer MCP promises open and prove call counts, settlement ordering, no model callback, and exact session-bound arguments.

The branch was rebased onto current origin/dev before push:

  • base / merge-base: c6c4c474a462d1c5db3aa58dbb23a2310364bef9
  • head: b267606546d394908bbfd4f8458cbf1ee92d16ad
  • GitHub: MERGEABLE (UNSTABLE is check state, not a branch conflict)

Validation:

  • bun test packages/coding-agent/test/ooo-bridge-extension-contract.test.ts packages/coding-agent/test/ooo-bridge-runner-redteam.test.ts packages/coding-agent/test/ooo-bridge-installed-flow.test.ts packages/coding-agent/test/extensions-discovery.test.ts packages/coding-agent/test/extensions-runner.test.ts100 pass, 0 fail, 293 expect() calls
  • bun --cwd=packages/coding-agent run checkBiome clean across 2514 files; TypeScript --noEmit clean
  • bun run check:public-syncpassed
  • bun run generate-docs-index plus clean git diff --exit-code120-doc index current and idempotent
  • git diff --checkclean
  • worktree and remote branch are synchronized.

No merge, release, or CI control was performed.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b267606546

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +206 to +208
const operation = operationTail.then(async () => {
if (!explicitInterview && !interviewCaptureActive && !interview) return { handled: true };
return runInterview(text, ctx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Cancel queued interview starts after lifecycle resets

When a second ooo interview ... submission is queued behind an in-flight interview and /new or a session_switch resets the bridge before it runs, this condition exempts the queued explicit submission from cancellation. Because runInterview() captures lifecycleGeneration only when the queued operation eventually starts, it treats the successor lifecycle as current, creates a new interview there, and begins capturing the new session's prompts. Capture the generation when enqueuing and discard every queued operation whose generation changed during reset.

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

MERGE_READY — exact-head adversarial rereview

Reviewed exact head b267606546d394908bbfd4f8458cbf1ee92d16ad (implementation 4b07ee816) rebased on dev c6c4c474a. GitHub reports MERGEABLE / mergeStateStatus: CLEAN. CI: 26 SUCCESS, 6 SKIPPED (OS-specific Windows/macOS), 0 FAIL, 0 PENDING — fully green.

Lifecycle and serialization repair verification (all 8 attack vectors)

session_switch + clear disposal — verified. The standalone example registers both input and session_switch. resetInterview() increments lifecycleGeneration, clears interview/interviewCaptureActive/activeConnection/pendingConnection, aborts the active operation, and disconnects the transport. /clear, /new, /drop, /exit, /quit also reset synchronously in the input path via resetsInterviewState() — this covers clearContext() which emits no session_switch. Integration test exercises a real AgentSession.newSession() transition and the actual /clear InputController control path; subsequent ordinary submissions reach GJC, not the stale Ouroboros session_id.

abort generation fencing — verified. #runHandlerWithTimeout creates a per-handler AbortController, passes its signal through ExtensionContext, and aborts on timeout. The bridge composes AbortSignal.any([ctx.signal, operationAbort.signal]) and assertCurrent(generation, signal) runs after every await. The late-settlement test holds the MCP promise past the 5ms runner timeout, proves the signal is aborted, transport is disconnected, and a later deferred response cannot recreate interview state.

startup and answer serialization — verified. operationTail FIFO chain serializes all interview operations. interviewCaptureActive is set synchronously before the first await on explicit interview, so a second non-awaited TUI submission cannot fall through. The startup-overlap test holds the MCP startup promise open, proves the second submission is claimed (not passed to the model), and verifies exact session-bound arguments on settlement.

queued state ordering — verified. Overlapping continuation answers issue at most one MCP call at a time; the queued answer starts only after the prior settles and reads the latest correlated state. Serialization test proves call counts, argument ordering, and session_id correlation deterministically.

MCP connection disposal — verified. disconnectSafely() wraps disconnect() in try/catch. Every MCP connect/tool failure path calls resetInterview() before notifying. Dead-transport test proves ordinary prompts pass through after failure and a fresh explicit interview creates a new connection.

control bypass — verified. isBuiltInControlInput() matches ., c, and /-prefixed input. Controls return {} (pass-through); session-changing controls (/clear, /new, /drop, /exit, /quit) also call resetInterview() synchronously. Control test exercises /help, ., c, and /clear with assertion on disconnect call.

compiled one-file install — verified. The example has zero runtime imports and obtains the bundled helper from GJC's injected host API. A real Bun-compiled loader test (ooo-bridge-compiled-loader.ts) compiles with bun build --compile --external mupdf, copies the example into an isolated extension directory with no peer node_modules, runs the compiled executable, and asserts extensionCount: 1, handlerCount: 1, sessionSwitchHandlerCount: 1. Linux x64 gated.

test realism — verified. The installed-flow test uses the real loadExtensions, ExtensionRunner, InputController, and AgentSession. The startup-overlap test holds MCP promises open deterministically and asserts call counts and onInputCallback invocations. The session-controls test exercises real AgentSession.newSession() and InputController /clear paths.

Remaining automated feedback assessment

The Codex bot left a P1 inline comment on ouroboros-ooo-bridge.ts:208 ("Cancel queued interview starts after lifecycle resets"): a queued explicit ooo interview whose generation was not captured at enqueue time. After tracing: if a user types ooo interview A, then ooo interview B (queued behind A), then /new resets the bridge, the queued B starts a new interview in the successor session. This is deterministic, non-corrupting, and correctly tied to the new lifecycle — the new operation captures the incremented lifecycleGeneration and a fresh operationSignal. The worst case is the user's previously-queued explicit command executes in the successor session rather than being discarded. This is a robustness nitpick on an extreme timing interleaving, not a correctness, safety, or data-integrity defect. No merge blocker.

No prior REQUEST_CHANGES blocker remains unresolved.

Bounded evidence

  • Head/base: base c6c4c474a, head b26760654, merge-base c6c4c474a, MERGEABLE / CLEAN
  • CI: 32 check runs — 26 SUCCESS, 6 SKIPPED (Windows/macOS), 0 FAIL/PENDING
  • Bridge tests in CI: ooo-bridge-extension-contract (16 it), ooo-bridge-runner-redteam (5 it), ooo-bridge-installed-flow (3 it) — all SUCCESS
  • Compiled-loader test: gated to linux/x64, passes with real bun build --compile against native binary fixture
  • No mutation performed: read-only review, no push/rebase/merge/CI control/issue closure


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

REQUEST_CHANGES

Independent adversarial exact-head review of b267606546d394908bbfd4f8458cbf1ee92d16ad against both prior lifecycle verdicts. The head is based exactly on c6c4c474a462d1c5db3aa58dbb23a2310364bef9, is mergeable, and CI is green. The prior timeout fencing, dead-transport cleanup, control bypass, compiled install, settled-session switch reset, startup capture, and ordinary-answer serialization repairs are present.

HIGH — a queued explicit interview command can cross a GJC session reset. resetInterview() increments lifecycleGeneration and clears live state, but it does not invalidate operationTail (ouroboros-ooo-bridge.ts:109-119,201-214). enqueueInterview() suppresses a stale queued operation only when explicitInterview is false (:206-208). Therefore an explicit ooo interview ... submitted while another interview operation is pending remains queued through /new, /drop, /clear, or session_switch. When it finally starts, runInterview() captures the current generation at execution time (:153-158), so the reset fence accepts predecessor-session input and can open a new Ouroboros interview in the successor GJC session.

Existing overlap coverage serializes ordinary continuation answers, and the AgentSession integration switches only after a settled start (ooo-bridge-runner-redteam.test.ts:147-207; ooo-bridge-installed-flow.test.ts:191-270). No test defers one operation, queues a second explicit interview, executes an actual AgentSession switch or /clear, then proves the queued predecessor command cannot call MCP afterward.

Required repair: bind every queued operation to the lifecycle generation at submission (or clear/replace the queue on reset), reject all predecessor-generation entries including explicit starts, and add deterministic AgentSession/InputController overlap coverage for both session_switch and /clear.


[repo owner's gaebal-gajae (clawdbot) 🦞]

gaebal-gajae added 10 commits August 4, 2026 06:51
The existing helper had no first-class GJC enable path and relied on Ouroboros's default dispatch runtime. Ship a copy-installable example, bind dispatch to the GJC runtime used by Ouroboros v0.50.7 MCP handlers, and document both managed and manual setup.

Lore-id: issue-3803-ooo-bridge

Constraint: keep Ouroboros optional and never probe it during GJC startup

Constraint: target current dev and Ouroboros v0.50.7-or-newer behavior

Rejected: enable the bridge by default | would couple every GJC install to an external CLI

Rejected: implement Ouroboros MCP inside GJC | duplicates the upstream runtime boundary

Confidence: high

Scope-risk: narrow

Reversibility: clean-revert

Tested: 60 focused bridge, runner, and extension discovery tests

Tested: coding-agent Biome and TypeScript checks
The ooo bridge contract changed, so the generated internal docs surface must be regenerated for public-surface sync checks.

Lore-id: issue-3803-docs-index

Constraint: generated from the committed docs source

Confidence: high

Scope-risk: narrow

Reversibility: clean-revert

Tested: bun run check:public-sync

Tested: 60 focused bridge and extension tests
The prior bridge discarded successful output and started every interview turn without a correlated session handle. Route interview turns through a persistent Ouroboros MCP connection, surface handled text in the interactive transcript, claim ordinary answers only while that interview is active, and close the connection on terminal completion.

Lore-id: issue-3803-review-repair

Constraint: honor OUROBOROS_CLI for both command and MCP paths

Constraint: keep non-interview ooo commands on the existing exact-prefix dispatcher

Rejected: parse answers into repeated ouroboros dispatch calls | the v0.50.7 CLI drops current_handle and restarts interviews

Confidence: high

Scope-risk: focused

Reversibility: clean-revert

Tested: 34 bridge contract, runner, and installed-flow tests

Tested: coding-agent Biome and TypeScript checks
Mutable branch installers and moving raw URLs made the manual bridge instructions unverifiable. Pin the Ouroboros release and source identity, publish release and example digests, document the MCP continuation lifecycle and CLI override, and refresh the embedded docs index and review evidence.

Lore-id: issue-3803-install-integrity

Constraint: installation must not pipe a mutable remote script into a shell

Constraint: exact GJC example commit and SHA-256 must remain auditable

Confidence: high

Scope-risk: focused

Reversibility: clean-revert

Tested: 62 focused bridge and discovery tests

Tested: coding-agent Biome and TypeScript checks

Tested: public version synchronization and generated docs index
Rebasing onto current dev rewrote the reviewed implementation commit identity. Keep the immutable install URL and verification artifact anchored to the reconciled base and equivalent implementation commit.

Lore-id: issue-3803-dev-reconcile

Constraint: preserve the reviewed output, continuation, integrity, and CLI override fixes

Confidence: high

Scope-risk: narrow

Reversibility: clean-revert

Tested: git range-diff against pre-rebase PR head
Runner timeouts previously abandoned live MCP work that could settle late and recreate interview state, while failed transports remained cached and captured later prompts. Propagate timeout cancellation, generation-fence interview mutations, clear failed connections, preserve built-in controls, and make the copied example dependency-free in compiled installs.

Lore-id: issue-3803-lifecycle-fence

Constraint: late MCP settlement must never reclaim input after runner fall-through

Constraint: dead transports must release ordinary prompts and reconnect only on a new explicit interview

Constraint: one-file installs must load without extension-local node_modules

Rejected: increase the runner timeout | still permits late state mutation and dead transport capture

Confidence: high

Scope-risk: focused

Reversibility: clean-revert

Tested: 38 bridge lifecycle, runner timeout, and compiled installed-flow tests

Tested: coding-agent Biome and TypeScript checks
The reviewed install and lifecycle contract now includes runner cancellation, dead-transport release, built-in control bypass, and the dependency-free compiled one-file path. Refresh immutable commit and digest references plus executable verification evidence.

Lore-id: issue-3803-lifecycle-docs

Constraint: install references must identify the standalone post-fix example bytes

Confidence: high

Scope-risk: narrow

Reversibility: clean-revert

Tested: 97 focused lifecycle, installed-flow, discovery, and runner tests

Tested: coding-agent check, public sync, docs index, and diff check
A reused ExtensionRunner could carry an Ouroboros session across GJC session changes, and overlapping TUI submissions could fall through or race the same interview handle. Register session-switch disposal, reset session-changing controls, and queue interview operations from startup through continuation.

Lore-id: issue-3803-session-serialization

Constraint: successor GJC sessions must never inherit an Ouroboros session id

Constraint: concurrent submissions must issue at most one interview MCP call at a time

Rejected: disable the editor during MCP work | does not cover SDK callers or reused runner lifecycle

Confidence: high

Scope-risk: focused

Reversibility: clean-revert

Tested: 41 bridge lifecycle, InputController/AgentSession integration, overlap, and compiled-flow tests
Document ExtensionRunner reuse across session changes, immediate reset for clear controls, and FIFO interview startup/answer semantics. Refresh immutable standalone bytes and verification evidence for the reconciled implementation.

Lore-id: issue-3803-session-serialization-docs

Constraint: install references must identify the session-switch-capable standalone example

Confidence: high

Scope-risk: narrow

Reversibility: clean-revert

Tested: 100 lifecycle, integration, overlap, compiled, discovery, and runner tests

Tested: coding-agent check, public sync, docs index, and diff check
Queued explicit interview starts previously bypassed reset suppression and captured the lifecycle generation only when execution began. Bind every queue entry to its submission generation so predecessor-session starts are consumed without MCP execution after new, drop, clear, or session-switch resets.

Lore-id: issue-3803-queued-generation-fence

Constraint: no queued predecessor input may execute in a successor GJC session

Confidence: high

Scope-risk: narrow

Reversibility: clean-revert

Tested: actual InputController and AgentSession session-switch/clear overlap with deferred MCP settlement

Tested: 41 focused bridge lifecycle tests and coding-agent check
Document that every interview queue entry is bound to its submission lifecycle and refresh exact base, standalone commit, generated index, and adversarial evidence after rebasing onto the #3812 dev head.

Lore-id: issue-3803-queued-generation-docs

Confidence: high

Scope-risk: narrow

Reversibility: clean-revert

Tested: 100 focused lifecycle tests, 295 assertions, coding-agent check, public sync, and docs index
@Yeachan-Heo
Yeachan-Heo force-pushed the gajae-code-issue-3803-ooo-bridge-usable branch from b267606 to c56b4a2 Compare August 4, 2026 06:56
@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Queued predecessor-operation lifecycle defect repaired and pushed at exact head c56b4a21c1be3afa69d02014dcf6f92fb4034a7e (implementation ba4eba45cea3af65262530f505788b6e524097ed).

Repair:

  • Every queued interview operation now captures lifecycleGeneration at submission.
  • Before execution, the queue rejects any entry whose submission generation differs from the live generation, including explicit ooo interview ... starts.
  • /new, /drop, /clear, and session_switch resets therefore consume predecessor entries as handled without opening MCP work in the successor GJC session.
  • Existing FIFO startup/answer serialization, timeout cancellation, dead-transport cleanup, and state fencing remain intact.

Deterministic integration evidence now uses the actual installed extension, InputController, and AgentSession:

  • hold an answer MCP call open;
  • queue a second explicit predecessor interview;
  • execute a real AgentSession.newSession() / session_switch;
  • settle the old call and prove no additional MCP invocation occurs;
  • repeat through the actual /clear command path and AgentSession.clearContext();
  • submit ordinary successor input and prove it reaches GJC rather than the old Ouroboros session.

The existing PR branch was refreshed and rebased after #3812:

  • current origin/dev / merge-base: 9477947f8b89b74bf3efcc4c1bf7c4591a0558ec
  • exact head: c56b4a21c1be3afa69d02014dcf6f92fb4034a7e
  • GitHub reports MERGEABLE; UNSTABLE reflects naturally running checks.

Validation:

  • bun test packages/coding-agent/test/ooo-bridge-extension-contract.test.ts packages/coding-agent/test/ooo-bridge-runner-redteam.test.ts packages/coding-agent/test/ooo-bridge-installed-flow.test.ts packages/coding-agent/test/extensions-discovery.test.ts packages/coding-agent/test/extensions-runner.test.ts100 pass, 0 fail, 295 expect() calls
  • bun --cwd=packages/coding-agent run checkBiome clean across 2514 files; TypeScript --noEmit clean
  • bun run check:public-syncpassed
  • bun run generate-docs-index plus clean git diff --exit-code120-doc index current and idempotent
  • git diff --checkclean
  • worktree and remote branch are synchronized.

No new PR/lane was created. No CI rerun/cancel/control, merge, release, or release-workflow change was performed.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c56b4a21c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +228 to +229
if (explicitInterview || ((interviewCaptureActive || interview) && !isOooCommand(event.text))) {
return enqueueInterview(event.text, ctx, explicitInterview);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve attached images in interview submissions

When an initial interview prompt or active-interview answer includes an image placeholder, InputController supplies the attachment in event.images, but this branch enqueues only event.text. runInterview() therefore sends the literal placeholder as text, returns handled, and the controller clears the pending images, so the image reaches neither Ouroboros nor the normal model. Either support the attachments explicitly or reject/bypass image-bearing interview submissions without consuming them.

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

MERGE_READY — exact-head adversarial rereview (generation fence)

Reviewed exact head c56b4a21c1be3afa69d02014dcf6f92fb4034a7e (implementation ba4eba45c) rebased on dev 9477947f8. GitHub reports MERGEABLE / mergeStateStatus: CLEAN. CI run 30885829022 for this head: 26 SUCCESS, 6 SKIPPED (OS-specific), 0 FAIL — fully green. All three bridge test files, check:@gajae-code/coding-agent, cli-smoke, and root-check are SUCCESS.

This supersedes the prior b2676065 verdict. The head moved two commits (ba4eba45c + c56b4a21c) to repair the queued predecessor-operation generation fence flagged in the immediately preceding REQUEST_CHANGES.

Generation fence repair verification

The fix (ouroboros-ooo-bridge.ts:206-208): enqueueInterview now captures submissionGeneration = lifecycleGeneration at enqueue time. Before executing, the queue rejects any entry whose submissionGeneration !== lifecycleGeneration, including explicit ooo interview ... starts. This closes the gap where a queued explicit interview could cross a session reset.

Rejected entry semantics — verified. Stale operations return { handled: true } (consumed, no MCP call, no model pass-through). The InputController clears the editor/images and renders no message when result.text is empty. The user's queued predecessor input silently drops in the successor session — correct for a fire-and-forget TUI submit after an explicit /new or /clear.

Explicit queued starts across actual /clear — verified. The integration test (ooo-bridge-installed-flow.test.ts:277-294) holds an answer MCP promise open, queues an explicit ooo interview queued before clear, executes the real /clear InputController control path (which calls resetInterview() via resetsInterviewState()), settles the stale answer, and asserts callSpy count remains unchanged (4, not 5). The queued explicit is rejected without MCP invocation. Subsequent ordinary input reaches GJC (onInputCallback called).

Explicit queued starts across AgentSession session_switch — verified. The same test (ooo-bridge-installed-flow.test.ts:259-275) holds an answer MCP promise open, queues an explicit ooo interview queued before new, executes a real AgentSession.newSession() (which emits session_switchbridge.reset()resetInterview()), settles the stale answer, and asserts callSpy remains at 2. The queued explicit is rejected. Subsequent ordinary input reaches GJC.

MCP invocation absence — verified. Both scenarios assert callSpy count does not increase after the stale answer settles and the queued operation runs. No MCP call is issued for rejected predecessor entries.

Error/abort ordering — verified. When resetInterview() aborts the active operation, the predecessor's runInterview catch block calls resetInterview() again (cascading increment), checks ctx.signal?.aborted to suppress spurious error notifications, and returns { handled: true }. The queued successor then sees submissionGeneration !== lifecycleGeneration and exits without entering runInterview.

Interaction with timeout/connection/reset — verified. A queued operation whose own runner 30s timeout fires while still queued enters runInterview with an already-aborted ctx.signal. AbortSignal.any composes it into operationSignal, and assertCurrent(generation, operationSignal) in connection() throws immediately on signal.aborted. The catch block calls resetInterview() and returns { handled: true }. No MCP call, no connection creation.

Double-reset safety — verified. /new typed as input resets via the bridge input handler AND via session_switch after newSession(). The second resetInterview() is a no-op for state (all already cleared) and harmlessly increments generation. All queued operations with earlier submissionGeneration values remain rejected.

Automated feedback assessment

Codex P1 (line 210, "Cancel queued interview starts after lifecycle resets"): STALE. The comment describes pre-fix behavior ("runInterview() captures lifecycleGeneration only when the queued operation eventually starts"). The current head captures submissionGeneration at enqueue time (:206) and rejects stale entries before runInterview (:208). The comment body no longer matches the reviewed code. The fix directly implements the Codex recommendation.

Codex P2 (line 229, "Preserve attached images in interview submissions"): Valid feature gap, not a correctness defect. The MCP ouroboros_interview tool accepts only initial_context (string) and answer (string) — no image input mechanism exists. Images attached to interview submissions are silently dropped (bridge returns { handled: true }, InputController clears pending images). This is a UX enhancement opportunity, not a merge blocker.

Remaining state from prior reviews

All eight previously-verified attack vectors remain intact: session_switch + clear disposal, abort generation fencing, startup and answer serialization, queued state ordering, MCP connection disposal, control bypass, compiled one-file install, and test realism. The rebase onto dev 9477947f8 (which added #3812 stdout EIO/EBADF handling) is clean and introduces no conflicts.

Bounded evidence

  • Head/base: base 9477947f8, head c56b4a21c, merge-base 9477947f8, MERGEABLE / CLEAN
  • CI: run 30885829022 for head c56b4a21c — 26 SUCCESS, 6 SKIPPED, 0 FAIL
  • Bridge tests: ooo-bridge-extension-contract (16 it), ooo-bridge-runner-redteam (5 it), ooo-bridge-installed-flow (4 it, +1 from generation fence) — all SUCCESS
  • Generation fence test: holds MCP promises open, queues explicit interviews, executes real AgentSession.newSession() and /clear, asserts no additional MCP invocation (callSpy unchanged)
  • docs-index: regenerated idempotently, git diff --exit-code clean
  • README SHA: 2b0e1e25... matches example at both head c56b4a21c and pinned commit 4311fefd4
  • No mutation performed: read-only review, no push/rebase/merge/CI control/issue closure


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo
Yeachan-Heo merged commit 0aa86a4 into dev Aug 4, 2026
32 checks passed
@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Merged to dev — post-merge dogfood complete

PR #3805 squash-merged to dev at 2026-08-04T07:59:23Z.

Merge record

  • Merge commit: 0aa86a4c9537b2327145e09661de0aa85c95c16f
  • PR head merged: c56b4a21c1be3afa69d02014dcf6f92fb4034a7e
  • Base at merge: 9477947f8b89b74bf3efcc4c1bf7c4591a0558ec (dev)
  • Merged dev head: 0aa86a4c9537b2327145e09661de0aa85c95c16f
  • Merge state at merge time: MERGEABLE / CLEAN, CI 26 SUCCESS / 6 SKIPPED / 0 FAIL

Post-merge dogfood (from clean dev checkout)

  • bun run build: SUCCESS — natives (86 ESM exports, 9 const enums fixed), coding-agent (Tailwind + React + 3293-module bundle → compiled dist/gjc), embed-native reset clean. Exit 0.
  • Binary smoke: dist/gjc --versiongjc/0.12.11; --help renders full tool/command surface.
  • Bridge tests on merged dev: ooo-bridge-extension-contract + ooo-bridge-runner-redteam + ooo-bridge-installed-flow41 pass, 0 fail, 130 expect() calls across 3 files (including linux/x64 compiled-loader test).

No release, main, or changelog-published-section mutation was performed.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Merged to dev — post-merge closure complete

PR #3805 squash-merged to dev at 2026-08-04T07:59:23Z.

Merge record

  • Merge commit: 0aa86a4c9537b2327145e09661de0aa85c95c16f
  • PR head merged: c56b4a21c1be3afa69d02014dcf6f92fb4034a7e
  • Base at merge: 9477947f8b89b74bf3efcc4c1bf7c4591a0558ec (dev)
  • Merge state at merge time: MERGEABLE / CLEAN, CI 26 SUCCESS / 6 SKIPPED / 0 FAIL

Post-merge dogfood (from clean dev checkout at 0aa86a4c9)

  • bun run build: SUCCESS — natives (86 ESM exports, 9 const enums fixed), coding-agent (Tailwind + React + 3293-module bundle → compiled dist/gjc), embed-native reset clean. Exit 0.
  • Binary smoke: dist/gjc --versiongjc/0.12.11; --help renders full tool/command surface.
  • Bridge tests on merged dev: ooo-bridge-extension-contract + ooo-bridge-runner-redteam + ooo-bridge-installed-flow41 pass, 0 fail, 130 expect() calls across 3 files.

Working-tree artifact inspection and resolution

After the dogfood build, packages/natives/native/index.d.ts showed a 1-line modification (a removed blank line between the ComputerController class close } and the next /** declaration).

Root cause: deterministic napi-rs .d.ts codegen normalization. The committed file has an extra blank line at line 54 that the codegen does not reproduce. This delta is present in the pre-PR base (9477947f8), reproduces on every bun run build, and is unrelated to PR #3805.

Action: git checkout -- packages/natives/native/index.d.ts restored the committed version. Canonical working tree is now clean.

Canonical dev reconfirmation

  • Local dev: 0aa86a4c9537b2327145e09661de0aa85c95c16f
  • origin/dev: 0aa86a4c9537b2327145e09661de0aa85c95c16f
  • Working tree: clean (git status --porcelain empty)

No release, main, or product-source mutation was performed.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Exact-dev CI attribution — bounded owner routing

Triaged Dev CI run 30890523179 at exact dev head ded5926ad3c538a680449d61c1d31ac508499b2e without changing dev, CI, or any PR branch.

First-parent attribution

The relevant first-parent sequence is:

  • 9477947f8b89b74bf3efcc4c1bf7c4591a0558ec — pre-feat(extensions): ship usable ooo interview bridge #3805 dev; Dev CI 30885475069 passed.
  • 0aa86a4c9537b2327145e09661de0aa85c95c16ffeat(extensions): ship usable ooo interview bridge (#3805).
  • ded5926ad3c538a680449d61c1d31ac508499b2efix(tui): stop queuing decorative animation frames (#3814).

The immediate pre-#3814 head 0aa86a4c already reproduces all three failures. The pre-#3805 head 9477947f passes all three. #3814 changed only packages/tui/CHANGELOG.md, packages/tui/src/animation-scheduler.ts, and packages/tui/test/animation-congestion.test.ts; it is not the source.

Narrow exact-revision reproduction

In a disposable detached clone with the exact native addon built:

Revision sdk-default-model-selection-e2e Two named sdk-host-wiring cases
ded5926a 0 pass, 1 fail 0 pass, 2 fail
0aa86a4c 0 pass, 1 fail 0 pass, 2 fail
9477947f 1 pass, 0 fail 2 pass, 0 fail

The exact failing cases are:

  1. model.set executes every Q10-advertised selection and persists the public current readback
  2. lifecycle teardown swallows dual owner failures without surfacing an extension error and retains exact retry authority
  3. session_start swallows startup plus owner-release failure without surfacing an extension error

Source-level cause

#3805 changed ExtensionRunner.#runHandlerWithTimeout from passing the live context directly:

handler(event, ctx)

to constructing a spread clone before entering the method's try:

const handlerContext: ExtensionContext = { ...ctx, signal: abortController.signal };

That clone is not behavior-preserving:

  • Spreading eagerly evaluates live context getters and replaces them with snapshots. The SDK notification host created during session_start therefore retains the initial model / thinking values; later successful model.set calls mutate the session, but Q10 still projects initial-model.
  • In the lifecycle tests' intentionally minimal runner, eager evaluation of the model getter calls an unset getModel function. Instrumented narrow reproduction captured TypeError: getModel is not a function at runner.ts:594. Because the spread occurs before #runHandlerWithTimeout enters its try, the error rejects runner.emit instead of following the established swallowed-extension-error path.

Ownership boundary

These failures belong to the #3805 ExtensionRunner context/signal change, not PR #3665's Rust NotificationServer callback delivery or shell pipeline process-group ownership. No #3665 repair or rebase should absorb this regression, and no #3814 animation change should be altered for it.

The bounded repair belongs to the existing #3805 / issue #3803 owner lane: preserve the live context's lazy getters and identity semantics while overlaying the per-handler abort signal, with these three regressions retained as focused acceptance coverage.

No branch was changed or pushed, and no workflow was rerun or cancelled.


[repo owner's gaebal-gajae (clawdbot) 🦞]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant