From 08d57291ad8c004a4ca7fba0d5b3a90dcbb9da11 Mon Sep 17 00:00:00 2001 From: ZiyaZhang <199893125+ZiyaZhang@users.noreply.github.com> Date: Wed, 14 Oct 2026 23:10:35 -0700 Subject: [PATCH 1/2] fix(subagent-codex): accept the partial_answer agent message phase Codex app-server 0.162 adds `partial_answer` to the agentMessage phases (stable answer text that later assistant output or tool calls may follow). The wire treated every phase other than final_answer, commentary, and null as fatal, so one such message failed the whole delegation. Record the latest partial answer and select final, then partial, then unphased text; unknown phases still fail the run. The pinned @openai/codex 0.153.4 payload never emits the phase, so behavior with the shipped binary is unchanged. --- .../subagent/subagent-codex/README.i18n.yaml | 8 ++-- packages/subagent/subagent-codex/README.md | 4 +- packages/subagent/subagent-codex/README.zh.md | 4 +- packages/subagent/subagent-codex/src/wire.ts | 13 +++-- .../tests/subagent-codex.spec.ts | 47 +++++++++++++++++++ 5 files changed, 65 insertions(+), 11 deletions(-) diff --git a/packages/subagent/subagent-codex/README.i18n.yaml b/packages/subagent/subagent-codex/README.i18n.yaml index 2534f90e70..308cbc6b47 100644 --- a/packages/subagent/subagent-codex/README.i18n.yaml +++ b/packages/subagent/subagent-codex/README.i18n.yaml @@ -42,8 +42,8 @@ en: 36647129f061610f zh: 42947c700d1d9202 /deepseek-ai-dsh-subagent-codex/understand-the-implementation/run-flow: - en: 19b10ff145644564 - zh: c015d530f1bd227e + en: b8244d26ba25f468 + zh: 6cb77dfece19cce7 /deepseek-ai-dsh-subagent-codex/further-exploration: en: e2e35a40060c1ba8 zh: c1676579ebac7c7f @@ -66,8 +66,8 @@ en: 4c391bb16e3cd380 zh: ab3847efe35fa6dc /deepseek-ai-dsh-subagent-codex/model-experience/parent-scheduling-and-results-indirectly/what-the-model-sees: - en: 18dcb74d6a52e96a - zh: 068c57f9fd959ecb + en: 9ca324fb0b034a6c + zh: 83ff78167bd31a14 /deepseek-ai-dsh-subagent-codex/model-experience/parent-scheduling-and-results-indirectly/token-effect: en: 0c436fb0897a6eca zh: 7ec0a953d0232af2 diff --git a/packages/subagent/subagent-codex/README.md b/packages/subagent/subagent-codex/README.md index ee5930344d..ba3b2429fd 100644 --- a/packages/subagent/subagent-codex/README.md +++ b/packages/subagent/subagent-codex/README.md @@ -107,7 +107,7 @@ This section explains how the provider drives a real Codex app-server and where ### Run flow -A start accepts only a non-empty sequence of text blocks and uses the child cwd selected by the subagent service. It spawns the fixed command through the subprocess seam, performs the `initialize` → `initialized` handshake, maps the Profile-selected mode and optional model into official `thread/start` fields beside `{ cwd, ephemeral: true }`, and publishes the run only after Codex returns a valid ephemeral thread. The published result starts exactly one turn, accepts only notifications for that run's thread and turn, and waits for the authoritative `turn/completed` terminal. The latest `agentMessage` with `phase: "final_answer"` wins; when Codex emits no explicit final phase, the latest message with `phase: null` is the compatibility fallback. A successful turn with no nonblank answer settles as an error. Failed turns use the coarse categories `limit`, `access-policy`, `service`, `transport`, `product-error`, `invalid-result`, or `unknown`; an early app-server exit uses `process`, and applicable connection and stream failures retain a numeric `httpStatusCode`. +A start accepts only a non-empty sequence of text blocks and uses the child cwd selected by the subagent service. It spawns the fixed command through the subprocess seam, performs the `initialize` → `initialized` handshake, maps the Profile-selected mode and optional model into official `thread/start` fields beside `{ cwd, ephemeral: true }`, and publishes the run only after Codex returns a valid ephemeral thread. The published result starts exactly one turn, accepts only notifications for that run's thread and turn, and waits for the authoritative `turn/completed` terminal. The latest `agentMessage` with `phase: "final_answer"` wins; without one, the latest `phase: "partial_answer"` message (stable answer text that later assistant output or tool calls may follow) is selected; when Codex emits neither explicit answer phase, the latest message with `phase: null` is the compatibility fallback. `commentary` messages are never selected, and any other phase fails the run. A successful turn with no nonblank answer settles as an error. Failed turns use the coarse categories `limit`, `access-policy`, `service`, `transport`, `product-error`, `invalid-result`, or `unknown`; an early app-server exit uses `process`, and applicable connection and stream failures retain a numeric `httpStatusCode`. @@ -147,7 +147,7 @@ Independent of the parent request cache. Reuse depends only on Codex's own provi #### What the model sees -Through `dsh-tool-subagent`, the parent model first receives a child id, then the final Codex answer or a failure notice with its stop reason and safe diagnostic. Diagnostics contain only fixed stage, category, and observed protocol or process facts. Product reasoning, intermediate messages, tool activity, stderr, usage, product identifiers, commands, paths, and raw protocol payloads are not copied into the parent Session. +Through `dsh-tool-subagent`, the parent model first receives a child id, then the selected Codex answer (final, partial, or unphased, in that order of precedence) or a failure notice with its stop reason and safe diagnostic. Diagnostics contain only fixed stage, category, and observed protocol or process facts. Product reasoning, intermediate messages, tool activity, stderr, usage, product identifiers, commands, paths, and raw protocol payloads are not copied into the parent Session. #### Token effect diff --git a/packages/subagent/subagent-codex/README.zh.md b/packages/subagent/subagent-codex/README.zh.md index 91803ef3cb..f4b388cddc 100644 --- a/packages/subagent/subagent-codex/README.zh.md +++ b/packages/subagent/subagent-codex/README.zh.md @@ -107,7 +107,7 @@ Bundle 将 `tool-subagent-codex` 作为 Host 条目插入,因此 `subagent_cod ### 运行流程 -一次启动只接受非空的文本块序列,并使用子代理服务选择的子级 cwd。它经子进程 seam spawn 固定命令,完成 `initialize` → `initialized` 握手,把 Profile 选择的模式与可选模型映射为官方 `thread/start` 字段并与 `{ cwd, ephemeral: true }` 一起发送,且仅在 Codex 返回有效的临时线程后发布运行。已发布的结果恰好启动一个轮次,只接受与此次运行的线程和轮次匹配的通知,并等待权威的 `turn/completed` 终态。以最后一条 `phase: "final_answer"` 的 `agentMessage` 为准;若 Codex 没有发出明确的最终阶段,则以最后一条 `phase: null` 的消息作为兼容性回退。成功完成的轮次若没有非空白答案,结果也会判为错误。失败轮次使用粗粒度类别 `limit`、`access-policy`、`service`、`transport`、`product-error`、`invalid-result` 或 `unknown`;app-server 提前退出使用 `process`,适用的连接与 stream 失败保留数值 `httpStatusCode`。 +一次启动只接受非空的文本块序列,并使用子代理服务选择的子级 cwd。它经子进程 seam spawn 固定命令,完成 `initialize` → `initialized` 握手,把 Profile 选择的模式与可选模型映射为官方 `thread/start` 字段并与 `{ cwd, ephemeral: true }` 一起发送,且仅在 Codex 返回有效的临时线程后发布运行。已发布的结果恰好启动一个轮次,只接受与此次运行的线程和轮次匹配的通知,并等待权威的 `turn/completed` 终态。以最后一条 `phase: "final_answer"` 的 `agentMessage` 为准;若没有,则选最后一条 `phase: "partial_answer"` 的消息(稳定的回答文本,其后仍可能有助手输出或工具调用);若 Codex 两种明确的回答阶段都没有发出,则以最后一条 `phase: null` 的消息作为兼容性回退。`commentary` 消息绝不会被选中,其它任何阶段都会使运行失败。成功完成的轮次若没有非空白答案,结果也会判为错误。失败轮次使用粗粒度类别 `limit`、`access-policy`、`service`、`transport`、`product-error`、`invalid-result` 或 `unknown`;app-server 提前退出使用 `process`,适用的连接与 stream 失败保留数值 `httpStatusCode`。 @@ -147,7 +147,7 @@ Codex 子级会在一个全新的临时线程中,以单个轮次接收这些 #### 模型看到什么 -通过 `dsh-tool-subagent`,父模型先收到 child id,随后收到 Codex 最终答案或带停止原因与安全诊断的失败通知。诊断只包含固定的阶段、类别和已观测的协议或进程事实。产品推理、中间消息、工具活动、stderr、用量、产品标识符、命令、路径与原始协议载荷不会复制到父 Session。 +通过 `dsh-tool-subagent`,父模型先收到 child id,随后收到所选的 Codex 答案(按 final、partial、未标阶段的优先次序)或带停止原因与安全诊断的失败通知。诊断只包含固定的阶段、类别和已观测的协议或进程事实。产品推理、中间消息、工具活动、stderr、用量、产品标识符、命令、路径与原始协议载荷不会复制到父 Session。 #### Token 影响 diff --git a/packages/subagent/subagent-codex/src/wire.ts b/packages/subagent/subagent-codex/src/wire.ts index 9a9f284eed..551a077e7f 100644 --- a/packages/subagent/subagent-codex/src/wire.ts +++ b/packages/subagent/subagent-codex/src/wire.ts @@ -205,6 +205,7 @@ export class CodexAppServerWire { readonly order: number }> = [] private lastFinalAnswer: string | undefined + private lastPartialAnswer: string | undefined private lastUnphasedAnswer: string | undefined private diagnostic: string | undefined private failure: CodexWireFailureFacts | undefined @@ -386,11 +387,13 @@ export class CodexAppServerWire { } /** - * The best non-commentary answer observed so far, preserving exact bytes. - * @returns the selected final or nullable-phase text block, if any. + * The best non-commentary answer observed so far, preserving exact bytes: + * the latest `final_answer`, else the latest `partial_answer`, else the + * latest nullable-phase message. + * @returns the selected text block, if any. */ collectOutput(): ContentBlock[] { - const selected = this.lastFinalAnswer ?? this.lastUnphasedAnswer + const selected = this.lastFinalAnswer ?? this.lastPartialAnswer ?? this.lastUnphasedAnswer return selected !== undefined && selected.trim().length > 0 ? [{ type: 'text', text: selected }] : [] @@ -667,6 +670,10 @@ export class CodexAppServerWire { : (() => { throw new Error('subagent-codex: app-server returned an invalid agent message') })() if (item.phase === 'final_answer') { this.lastFinalAnswer = text + } else if (item.phase === 'partial_answer') { + // Stable answer text that later assistant output or tool calls may + // follow; it outranks only unphased text. + this.lastPartialAnswer = text } else if (item.phase === null) { this.lastUnphasedAnswer = text } else if (item.phase !== 'commentary') { diff --git a/packages/subagent/subagent-codex/tests/subagent-codex.spec.ts b/packages/subagent/subagent-codex/tests/subagent-codex.spec.ts index 529ef96199..d882f3de67 100644 --- a/packages/subagent/subagent-codex/tests/subagent-codex.spec.ts +++ b/packages/subagent/subagent-codex/tests/subagent-codex.spec.ts @@ -821,6 +821,53 @@ describe('CodexAppServerWire', () => { wire.close() }) + it('ranks final_answer above partial_answer and partial_answer above nullable-phase answers', async () => { + const scenarios: Array<{ + readonly frames: JsonObject[] + readonly text: string + }> = [ + { + frames: [ + agentMessage('partial', 'partial_answer'), + agentMessage('final', 'final_answer'), + ], + text: 'final', + }, + { + frames: [ + agentMessage('final', 'final_answer'), + agentMessage('partial after final', 'partial_answer'), + ], + text: 'final', + }, + { + frames: [agentMessage('only partial', 'partial_answer')], + text: 'only partial', + }, + { + frames: [ + agentMessage('unphased', null), + agentMessage('first partial', 'partial_answer'), + agentMessage('last partial', 'partial_answer'), + agentMessage('unphased after partial', null), + ], + text: 'last partial', + }, + ] + for (const scenario of scenarios) { + const { child, wire } = await initializeWire() + const result = wire.runTurn(['task'], new AbortController().signal) + const turnStart = await child.peer.nextMethod('turn/start') + child.peer.respond(turnStart, { turn: { id: 'turn-1' } }) + child.peer.send(...scenario.frames, turnCompleted('completed')) + await expect(result).resolves.toEqual({ + output: [{ type: 'text', text: scenario.text }], + stopReason: 'completed', + }) + wire.close() + } + }) + it('groups representative string errors without changing stop reasons', async () => { const scenarios = [ ['contextWindowExceeded', 'limit', 'max-tokens'], From e480bda8d2474b85efb6b2688feacfa50b897f1e Mon Sep 17 00:00:00 2001 From: ZiyaZhang <199893125+ZiyaZhang@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:44:26 -0700 Subject: [PATCH 2/2] fix(subagent-codex): call the payload the selected answer, not the final one Address the ds-review-bot round on #6272: the README summary, completion notice, token effect, and Known Limitations lines no longer call the payload the final answer; the empty-result error reads "without an answer"; the ordering spec also interleaves commentary; the partial_answer branch cites its upstream source. --- .../subagent/subagent-codex/README.i18n.yaml | 16 ++++++++-------- packages/subagent/subagent-codex/README.md | 8 ++++---- packages/subagent/subagent-codex/README.zh.md | 8 ++++---- packages/subagent/subagent-codex/src/wire.ts | 7 ++++--- .../subagent-codex/tests/subagent-codex.spec.ts | 8 +++++--- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/subagent/subagent-codex/README.i18n.yaml b/packages/subagent/subagent-codex/README.i18n.yaml index 308cbc6b47..1b6d354c28 100644 --- a/packages/subagent/subagent-codex/README.i18n.yaml +++ b/packages/subagent/subagent-codex/README.i18n.yaml @@ -9,8 +9,8 @@ en: c3f2c833cb91228d zh: 3267d24936c5973f /deepseek-ai-dsh-subagent-codex/summary: - en: 6eade95bb21eae52 - zh: 72de7d6fcdc0c31a + en: 895eb3910428c51e + zh: c5eb0d32c789520d /deepseek-ai-dsh-subagent-codex/table-of-contents: en: d152484eb41ac6b4 zh: 09388d293f9be9cb @@ -27,8 +27,8 @@ en: a36399c7ac98724d zh: d43815e08689da6b /deepseek-ai-dsh-subagent-codex/use-this-package/what-you-get: - en: 4b271f17f16fb162 - zh: 154c909023f1366b + en: abd75016d4c7f61e + zh: 904158c896bd21ce /deepseek-ai-dsh-subagent-codex/use-this-package/failure-and-recovery: en: 4ffe0312c5a8f722 zh: 532a1997e9ae825b @@ -69,14 +69,14 @@ en: 9ca324fb0b034a6c zh: 83ff78167bd31a14 /deepseek-ai-dsh-subagent-codex/model-experience/parent-scheduling-and-results-indirectly/token-effect: - en: 0c436fb0897a6eca - zh: 7ec0a953d0232af2 + en: f618e05fde7c8b57 + zh: 7306c4535b6fa4c0 /deepseek-ai-dsh-subagent-codex/model-experience/parent-scheduling-and-results-indirectly/kv-cache-effect: en: 19f1316ea4bb1bb9 zh: 181231ddffdc8494 /deepseek-ai-dsh-subagent-codex/known-limitations-and-deferred-work: - en: 46cbb0a3f4f81a5a - zh: a27faaaefe61158f + en: e55a466c946cbce4 + zh: 96dfb5c8f8210927 /deepseek-ai-dsh-subagent-codex/known-limitations-and-deferred-work/dev-note: en: cacc98025ed8913e zh: e7673a3087ec0c62 diff --git a/packages/subagent/subagent-codex/README.md b/packages/subagent/subagent-codex/README.md index ba3b2429fd..376e6e9fe4 100644 --- a/packages/subagent/subagent-codex/README.md +++ b/packages/subagent/subagent-codex/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -Install `@deepseek-ai/dsh-subagent-codex` into a Profile when delegated work should run in a genuine, unattended Codex session in the parent Session's workspace. Each delegation uses a fresh isolated Codex thread for one self-contained text task and returns only its final answer or a safe failure diagnostic. Native Codex configuration and authentication remain authoritative, while `permissionMode` selects the non-interactive approval and sandbox behavior. The Bundle supplies a compatible native Codex payload and adds its delegation tool as a global tool. +Install `@deepseek-ai/dsh-subagent-codex` into a Profile when delegated work should run in a genuine, unattended Codex session in the parent Session's workspace. Each delegation uses a fresh isolated Codex thread for one self-contained text task and returns only the selected answer or a safe failure diagnostic. Native Codex configuration and authentication remain authoritative, while `permissionMode` selects the non-interactive approval and sandbox behavior. The Bundle supplies a compatible native Codex payload and adds its delegation tool as a global tool. ## Table of Contents @@ -74,7 +74,7 @@ The tool returns a child id after accepting the task and sends its result to the ### What you get -The completion notice contains the final Codex answer, or the stop reason and optional safe diagnostic. The parent Session also retains the external task identity and complete terminal result independently of the notice; no local child Session is created. Product reasoning, tool activity, raw stderr, and workspace diffs do not enter the parent Session. +The completion notice contains the selected Codex answer, or the stop reason and optional safe diagnostic. The parent Session also retains the external task identity and complete terminal result independently of the notice; no local child Session is created. Product reasoning, tool activity, raw stderr, and workspace diffs do not enter the parent Session. ### Failure and recovery @@ -151,7 +151,7 @@ Through `dsh-tool-subagent`, the parent model first receives a child id, then th #### Token effect -Parent input grows by the start acknowledgement and completion notice, including the final answer or failure detail. Child tokens do not enter the parent context. This provider adds no parent tool schema by itself. +Parent input grows by the start acknowledgement and completion notice, including the selected answer or failure detail. Child tokens do not enter the parent context. This provider adds no parent tool schema by itself. #### KV Cache effect @@ -170,7 +170,7 @@ These limits define when this provider is a poor fit or needs special operationa - **The native platform payload is required at delegation time** — installs that omit optional dependencies, unsupported platforms, and missing or damaged payloads fail at the first run; there is no host-CLI fallback. - **Compatibility is pinned by development evidence** — upgrading from the verified 0.153.4 protocol baseline requires regenerating upstream schema evidence and rerunning handshake, answer-selection, approval, cancellation, keyless real-product, and credentialed DeepSeek nonce tests. - **No human approval path** — known unattended approval requests are denied and unknown server requests fail closed; the three Profile modes never create a DSH interaction channel or per-call allow policy. -- **Assistant payload is final text only** — failed runs can also expose a separate safe diagnostic; reasoning, intermediate messages, tool traffic, usage, stderr, and workspace diffs remain outside the parent Session. Task identity and terminal results are retained in the parent log. +- **Assistant payload is the selected answer text only** — failed runs can also expose a separate safe diagnostic; reasoning, intermediate messages, tool traffic, usage, stderr, and workspace diffs remain outside the parent Session. Task identity and terminal results are retained in the parent log. - **No optional shared capabilities** — `agentPreset`, `agentOptions`, output schemas, and harness depth enforcement are rejected by the shared service for this provider. - **No wall-clock timeout or side-effect rollback** — the caller cancels long work, and files or external systems changed before cancellation are not restored. diff --git a/packages/subagent/subagent-codex/README.zh.md b/packages/subagent/subagent-codex/README.zh.md index f4b388cddc..5e488daaf8 100644 --- a/packages/subagent/subagent-codex/README.zh.md +++ b/packages/subagent/subagent-codex/README.zh.md @@ -9,7 +9,7 @@ kind: "package-bundle" ## 概述 -当委派工作需要在父会话工作区中的真实无人值守 Codex 会话内运行时,把 `@deepseek-ai/dsh-subagent-codex` 安装进 Profile。每次委派都会为一个自包含文本任务使用全新且隔离的 Codex 线程,并且只返回其最终答案或安全失败诊断。原生 Codex 配置和身份验证继续作为权威来源,而 `permissionMode` 选择非交互式审批和沙箱行为。Bundle 提供兼容的原生 Codex 载荷,并以全局工具形式添加委派工具。 +当委派工作需要在父会话工作区中的真实无人值守 Codex 会话内运行时,把 `@deepseek-ai/dsh-subagent-codex` 安装进 Profile。每次委派都会为一个自包含文本任务使用全新且隔离的 Codex 线程,并且只返回所选答案或安全失败诊断。原生 Codex 配置和身份验证继续作为权威来源,而 `permissionMode` 选择非交互式审批和沙箱行为。Bundle 提供兼容的原生 Codex 载荷,并以全局工具形式添加委派工具。 ## 目录 @@ -74,7 +74,7 @@ Bundle 将 `tool-subagent-codex` 作为 Host 条目插入,因此 `subagent_cod ### 你会得到什么 -完成通知包含 Codex 的最终答案,或停止原因与可选的安全诊断。父会话还独立保存外部任务的身份和完整终态结果;无需创建本地子 Session。产品推理、工具活动、原始 stderr 与工作区差异不会进入父会话。 +完成通知包含所选的 Codex 答案,或停止原因与可选的安全诊断。父会话还独立保存外部任务的身份和完整终态结果;无需创建本地子 Session。产品推理、工具活动、原始 stderr 与工作区差异不会进入父会话。 ### 失败与恢复 @@ -151,7 +151,7 @@ Codex 子级会在一个全新的临时线程中,以单个轮次接收这些 #### Token 影响 -父级输入会增加启动确认与完成通知,包括最终答案或失败信息。子任务 token 不进入父级上下文。本提供方自身不添加父级工具 schema。 +父级输入会增加启动确认与完成通知,包括所选答案或失败信息。子任务 token 不进入父级上下文。本提供方自身不添加父级工具 schema。 #### KV Cache 影响 @@ -170,7 +170,7 @@ Codex 子级会在一个全新的临时线程中,以单个轮次接收这些 - **委派时必须存在原生平台载荷**——省略 optional dependencies 的安装、不受支持的平台以及缺失或损坏的载荷都会在第一次运行时失败;不会回退到宿主 CLI。 - **兼容性由开发证据锁定**——若要从已验证的 0.153.4 协议基线升级,必须重新生成上游 schema 证据,并重新运行握手、答案选择、审批、取消、无密钥真实产品以及带密钥的 DeepSeek 随机数测试。 - **没有人工审批路径**——已知的无人值守审批请求会被拒绝,未知服务器请求会以默认拒绝方式使运行失败;三种 Profile 模式都不会创建 DSH 交互通道或逐次调用 allow 策略。 -- **assistant 载荷仅包含最终文本**——失败运行还可公开独立的安全诊断;推理、中间消息、工具通信、用量、stderr 与工作区差异不会进入父 Session。任务身份与终态结果保存在父日志中。 +- **assistant 载荷仅包含所选回答文本**——失败运行还可公开独立的安全诊断;推理、中间消息、工具通信、用量、stderr 与工作区差异不会进入父 Session。任务身份与终态结果保存在父日志中。 - **没有可选的共享能力**——对于本提供方,共享服务会拒绝 `agentPreset`、`agentOptions`、输出 schema 和 harness 深度强制约束。 - **没有按实际经过时间触发的超时或副作用回滚**——长时间运行的工作由调用方取消,且取消前已更改的文件或外部系统不会恢复原状。 diff --git a/packages/subagent/subagent-codex/src/wire.ts b/packages/subagent/subagent-codex/src/wire.ts index 551a077e7f..b4e0f662b1 100644 --- a/packages/subagent/subagent-codex/src/wire.ts +++ b/packages/subagent/subagent-codex/src/wire.ts @@ -369,7 +369,7 @@ export class CodexAppServerWire { const output = this.collectOutput() if (output.length === 0) { this.recordFailure({ stage: 'turn', category: 'invalid-result' }) - throw new Error('subagent-codex: Codex completed without a final answer') + throw new Error('subagent-codex: Codex completed without an answer') } return { output, stopReason: 'completed' } } @@ -671,8 +671,9 @@ export class CodexAppServerWire { if (item.phase === 'final_answer') { this.lastFinalAnswer = text } else if (item.phase === 'partial_answer') { - // Stable answer text that later assistant output or tool calls may - // follow; it outranks only unphased text. + // `MessagePhase::PartialAnswer` (openai/codex codex-rs/protocol/src/models.rs, + // app-server 0.162+): stable answer text that later assistant output + // or tool calls may follow; it outranks only unphased text. this.lastPartialAnswer = text } else if (item.phase === null) { this.lastUnphasedAnswer = text diff --git a/packages/subagent/subagent-codex/tests/subagent-codex.spec.ts b/packages/subagent/subagent-codex/tests/subagent-codex.spec.ts index d882f3de67..80607bca6c 100644 --- a/packages/subagent/subagent-codex/tests/subagent-codex.spec.ts +++ b/packages/subagent/subagent-codex/tests/subagent-codex.spec.ts @@ -821,7 +821,7 @@ describe('CodexAppServerWire', () => { wire.close() }) - it('ranks final_answer above partial_answer and partial_answer above nullable-phase answers', async () => { + it('ranks final_answer above partial_answer above nullable-phase answers and never selects commentary', async () => { const scenarios: Array<{ readonly frames: JsonObject[] readonly text: string @@ -847,8 +847,10 @@ describe('CodexAppServerWire', () => { { frames: [ agentMessage('unphased', null), + agentMessage('commentary before partial', 'commentary'), agentMessage('first partial', 'partial_answer'), agentMessage('last partial', 'partial_answer'), + agentMessage('commentary after partial', 'commentary'), agentMessage('unphased after partial', null), ], text: 'last partial', @@ -1005,7 +1007,7 @@ describe('CodexAppServerWire', () => { }> = [ { frames: [turnCompleted('completed')], - message: 'without a final answer', + message: 'without an answer', category: 'invalid-result', }, { @@ -1014,7 +1016,7 @@ describe('CodexAppServerWire', () => { agentMessage(' \n ', 'final_answer'), turnCompleted('completed'), ], - message: 'without a final answer', + message: 'without an answer', category: 'invalid-result', }, {