diff --git a/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.i18n.yaml
index 7068ba34df..21599ff5f7 100644
--- a/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.md
-2026-08-23-client-derived-tool-presentation.md: 7f62e99fe152875bfd04bc11c5a1d5dea9014caa
-2026-08-23-client-derived-tool-presentation.zh.md: 433d1efb4cded651b39452716399be1ca02c102d
+2026-08-23-client-derived-tool-presentation.md: 58a8f23d717580355b852703f448c723d9c3a7ea
+2026-08-23-client-derived-tool-presentation.zh.md: 8e1a02eff57cce7967985a22c5bdcec7c18e6059
diff --git a/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.md b/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.md
index 7f62e99fe1..58a8f23d71 100644
--- a/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.md
+++ b/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.md
@@ -51,7 +51,7 @@ The Host `ToolDefinition.presentCall`, `ToolDefinition.presentResult`, `ToolCall
| Retained | the Session log format, Remote journal lifecycle, and Conversation identity/topology |
| Retained | the existing keyed slot, Generic fallback, and Chat, Details, and Trajectory structure |
| Forbidden | a new Client presenter service, parallel registry, or wire renderer id |
-| Forbidden | new cards, visual redesign, interaction redesign, or Code Dispatch rich-card enhancements except the separately owned terminal exception below |
+| Forbidden | new cards, visual redesign, interaction redesign, or Code Dispatch rich-card enhancements except the [nested terminal-card exception](../bug-fix/2026-09-05-nested-terminal-cards.md) |
| Forbidden | compatibility dual-writing, version negotiation, or retention of the old `view` field |
## Terminology
@@ -310,7 +310,7 @@ The Client terminal model derives existing `TerminalBlock` props from the tool n
| background/error `terminal_send` | Generic result |
| Code Dispatch child | same terminal eligibility and fallback rules as a root call |
-Standard shell results continue parsing trailing `[exit code: N]` and `[killed by signal: X]` markers. A parsed marker is removed from the body; timeout, sandbox denial, and markers without a pill remain in the body.
+Standard shell results parse trailing `[exit code: N]` and `[killed by signal: X]` markers. A final recognized spill-policy notice selects Generic output instead: expandable in shell rows and raw in Details, because the exit marker may be displaced or omitted. A parsed marker is removed from the terminal body; timeout, sandbox denial, and markers without a pill remain in the body.
Call `description` remains above the card and overrides the collapsed summary. Workdir continues handling absolute, relative, and missing values. Relative paths resolve against the Session cwd while preserving normalization for `.`, `..`, drive letters, and UNC roots.
@@ -634,7 +634,7 @@ An on-demand RPC would turn one page read into N network calls and would still r
### Allow presentation enhancements
-Bundling richer Code Dispatch cards, missing-call-head inference, or other historical presentation enhancements with the ownership change would prevent snapshots from proving equivalence. This decision rejects that coupling; the separately owned terminal exception below does not relax nonterminal child restrictions.
+Bundling richer Code Dispatch cards, missing-call-head inference, or other historical presentation enhancements with the ownership change would prevent snapshots from proving equivalence. This decision rejects that coupling; the [nested terminal-card exception](../bug-fix/2026-09-05-nested-terminal-cards.md) does not relax nonterminal child restrictions.
### Accept temporary Generic degradation
diff --git a/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.zh.md b/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.zh.md
index 433d1efb4c..8e1a02eff5 100644
--- a/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.zh.md
+++ b/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.zh.md
@@ -51,7 +51,7 @@ Host 的 `ToolDefinition.presentCall`、`ToolDefinition.presentResult`、`ToolCa
| 保留 | Session 日志格式、Remote journal 生命周期与 Conversation identity/topology |
| 保留 | 现有 keyed slot、Generic fallback、Chat、Details 与 Trajectory 结构 |
| 禁止 | 新 Client presenter service、平行 registry 或 wire renderer id |
-| 禁止 | 新卡片、视觉改版、交互改版或 Code Dispatch rich-card 增强,下述独立决策的 terminal 例外除外 |
+| 禁止 | 新卡片、视觉改版、交互改版或 Code Dispatch rich-card 增强,[嵌套 terminal 卡片例外](../bug-fix/2026-09-05-nested-terminal-cards.zh.md)除外 |
| 禁止 | 为兼容保留双写、版本协商或旧 `view` 字段 |
## 术语
@@ -310,7 +310,7 @@ Client terminal model 从工具名称、调用参数、结果 content、error
| `terminal_send` background/error | Generic 结果 |
| Code Dispatch child | 与根调用相同的 terminal 适用规则与 fallback 规则 |
-标准 shell 结果继续解析末尾 `[exit code: N]` 与 `[killed by signal: X]`。已解析的 marker 从正文移除;timeout、sandbox denial 与没有 pill 的 marker 留在正文。
+标准 shell 结果解析末尾 `[exit code: N]` 与 `[killed by signal: X]`。末尾已识别的 spill 策略提示会改用 Generic 输出:在 shell 行中可展开,在 Details 中显示原文,因为退出标记可能被移位或省略。已解析的 marker 从 terminal 正文移除;timeout、sandbox denial 与没有 pill 的 marker 留在正文。
调用 `description` 继续显示在 card 上方并覆盖折叠摘要。workdir 继续按绝对、相对和缺失三种情况处理;相对路径基于 Session cwd,且保留 `.`、`..`、盘符与 UNC root 的归一化。
@@ -634,7 +634,7 @@ read 行结构、applied diff、search 分组、web sources 和有效 truncation
### 允许展示增强
-将更丰富的 Code Dispatch 卡片、缺失 call head 的推断或其他历史展示增强与所有权变更捆绑,会使快照无法证明对等。本决定拒绝这种捆绑;下述独立决策的 terminal 例外不放宽非 terminal 子调用限制。
+将更丰富的 Code Dispatch 卡片、缺失 call head 的推断或其他历史展示增强与所有权变更捆绑,会使快照无法证明对等。本决定拒绝这种捆绑;[嵌套 terminal 卡片例外](../bug-fix/2026-09-05-nested-terminal-cards.zh.md)不放宽非 terminal 子调用限制。
### 接受临时 Generic 退化
diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml
index 98a3742c9f..d7fb9e1418 100644
--- a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml
+++ b/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md
-2026-09-05-nested-terminal-cards.md: 10e0c082650fe5a937aa8fba9f6080896fe8538f
-2026-09-05-nested-terminal-cards.zh.md: 2ad94f62657b78b5eaf6fde0033e73abee07997e
+2026-09-05-nested-terminal-cards.md: 0e38414e9450be060b0b66c42c4cac8111dd5476
+2026-09-05-nested-terminal-cards.zh.md: 5f167a926771195d0f6b8596d5335aebe57af3e2
diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md b/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md
index 10e0c08265..0e38414e94 100644
--- a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md
+++ b/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md
@@ -14,12 +14,16 @@ A shell command dispatched through `run_code` carries the arguments and rendered
This partially supersedes only the terminal child-card prohibition in [Client-derived tool presentation](../architecture/2026-08-23-client-derived-tool-presentation.md). That note remains active for Client presentation ownership and the diff/read/search/web child restrictions. No Host presenter, event, schema, metadata, call-tree, or model-context change is required. The metadata and execution-local value decisions in [canonical tool output](../architecture/2026-07-20-canonical-tool-output-contract.md) and [PTC typed returns](../feature/2026-07-20-ptc-typed-tool-returns.md) remain intact; metadata omission does not prohibit Client-derived terminal cards.
+Shell output ending in a recognized spill-policy notice uses generic output: expandable in `BashRow`, raw fallback in Details. The notice can follow or replace the exit marker, so its absence at the end does not justify a successful terminal status. The Client recognizes the final notice without changing Host output or schemas.
+
## Alternatives considered
**Keep the blanket nested-call rejection.** Rejected because nesting does not remove the raw facts the terminal model already consumes. It hides usable shell output while the same call renders as a terminal at the root.
**Enable every nested structured card.** Rejected because other card models have independent metadata requirements and child restrictions. This fix changes only terminal eligibility.
+**Parse exit markers around spill suffixes.** Rejected because truncation can remove the real status; conservative generic output avoids guessing success from an incomplete result.
+
## Consequences
Rows and Details share terminal derivation for nested calls without a second renderer or presentation hint. Generic fallback and settled-persistent behavior remain separate from terminal-card eligibility. The parent-child relationship still controls tree placement, not terminal rendering.
diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.zh.md b/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.zh.md
index 2ad94f6265..5f167a9267 100644
--- a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.zh.md
+++ b/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.zh.md
@@ -14,12 +14,16 @@ Status: implemented
本文仅部分取代 [Client 派生工具展示](../architecture/2026-08-23-client-derived-tool-presentation.zh.md)中的 terminal 子调用卡片禁令。该文继续负责 Client 展示所有权及 diff/read/search/web 子调用限制。无需更改 Host 展示转换器、事件、schema、元数据、调用树或模型上下文。[规范工具输出](../architecture/2026-07-20-canonical-tool-output-contract.zh.md)与 [PTC 类型化返回值](../feature/2026-07-20-ptc-typed-tool-returns.zh.md)中的元数据和执行期值决策保持不变;省略元数据不禁止 Client 派生 terminal 卡片。
+以已识别的 spill 策略提示结尾的 shell 输出使用通用展示:在 `BashRow` 中可展开,在 Details 中使用原始回退。提示可能位于退出标记之后或取代它,因此末尾缺少退出标记不能作为 terminal 成功状态的依据。Client 识别最终提示,不更改 Host 输出或 schema。
+
## 考虑过的替代方案
**保留对嵌套调用的一律拒绝。** 不予采用,因为嵌套不会移除 terminal model 已消费的原始事实。这会隐藏可用的 shell 输出,而同一调用位于根时却可渲染为 terminal。
**启用所有嵌套结构化卡片。** 不予采用,因为其他 card model 有独立的元数据要求与子调用限制。本修复只改变 terminal 适用性。
+**解析 spill 后缀附近的退出标记。** 不予采用,因为截断可能移除真实状态;保守的通用输出避免从不完整结果猜测成功。
+
## 后果
行与 Details 对嵌套调用共享 terminal 派生,不增加第二个渲染器或展示提示字段。通用回退与已完成持久 shell 的行为仍独立于 terminal 卡片适用性。父子关系仍控制树中的位置,而非 terminal 渲染。
diff --git a/apps/web/tests/ptc-round.e2e.ts b/apps/web/tests/ptc-round.e2e.ts
index 8d169dbfdf..66ff93de30 100644
--- a/apps/web/tests/ptc-round.e2e.ts
+++ b/apps/web/tests/ptc-round.e2e.ts
@@ -135,6 +135,9 @@ describe('web e2e: PTC mode round renders nested sub-calls', () => {
it.skipIf(MODE === 'record')('matches the expanded conversation aria golden with stable anchors', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-ptc-aria'))
+ const row = page.locator('[data-subcalls] [data-sample="bash"]').first()
+ await expandOwningTurnProcess(page, row)
+ if (await row.getAttribute('aria-expanded') !== 'true') await row.click()
const snapshot = await captureExpandedTurnProcessAria(
page,
'[class*="centerCol"]',
diff --git a/packages/client/ui-tool/README.i18n.yaml b/packages/client/ui-tool/README.i18n.yaml
index 652aebb5a4..87ea8a67bf 100644
--- a/packages/client/ui-tool/README.i18n.yaml
+++ b/packages/client/ui-tool/README.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/ui-tool/README.md
-README.md: 02c403133f7586217ed3397d9f496baedfcfb0a8
-README.zh.md: 0278e7fceca34d0e2b9334cfb33ae1a85fbdc27c
+README.md: e9c975fb40f1584149eb9200f43e25ec70379d8f
+README.zh.md: f64bc19752b97295ac80b839f56dd39f47ad2dfb
diff --git a/packages/client/ui-tool/README.md b/packages/client/ui-tool/README.md
index 02c403133f..e9c975fb40 100644
--- a/packages/client/ui-tool/README.md
+++ b/packages/client/ui-tool/README.md
@@ -43,7 +43,7 @@ The owner payload is `ToolCallOwnerProps`: `callId`, `toolName`, the frozen `blo
### Built-in views
-This package owns the generic fallback and the built-in shell/pwsh, read, read_image, write/edit, running `str_replace_editor` `create`/`str_replace`, grep/glob, web, todo, question, and Code Dispatch presentations. Structured cards derive directly from first-party raw event fields; Host `presentCall` and `presentResult` values never enter the Client. Running and settled foreground `bash`, `pwsh`, and `terminal_send` calls use terminal cards at the root and in Code Dispatch children, subject to the same argument, result, and error checks. Settled persistent-shell results stay generic because reset and partial-output diagnostics do not always describe one process exit status; root persistent results are expandable, while background acknowledgements remain collapsed. A successful question row pairs call questions with result answers by their stable ids and shows readable question/answer lines when expanded. A cancelled or interrupted row shows its verdict and original questions without inventing answers. Unsupported, malformed, or ambiguous inputs fall back to flattened Tool input/result text. `ui-skill` demonstrates a business-owned registration for `skill`.
+This package owns the generic fallback and the built-in shell/pwsh, read, read_image, write/edit, running `str_replace_editor` `create`/`str_replace`, grep/glob, web, todo, question, and Code Dispatch presentations. Structured cards derive directly from first-party raw event fields; Host `presentCall` and `presentResult` values never enter the Client. Running and settled foreground standard `bash`/`pwsh` and `terminal_send` calls use terminal cards at the root and in Code Dispatch children, subject to the same argument, result, and error checks. Persistent `bash`/`pwsh` calls use terminal cards only while running. Shell output ending in a recognized spill-policy notice uses expandable generic output in shell rows and generic output in Details; a displaced or omitted exit marker cannot establish success. Settled persistent-shell results stay generic because reset and partial-output diagnostics do not always describe one process exit status; root persistent results are expandable, while background acknowledgements remain collapsed. A successful question row pairs call questions with result answers by their stable ids and shows readable question/answer lines when expanded. A cancelled or interrupted row shows its verdict and original questions without inventing answers. Unsupported, malformed, or ambiguous inputs fall back to flattened Tool input/result text. `ui-skill` demonstrates a business-owned registration for `skill`.
-----
diff --git a/packages/client/ui-tool/README.zh.md b/packages/client/ui-tool/README.zh.md
index 0278e7fcec..f64bc19752 100644
--- a/packages/client/ui-tool/README.zh.md
+++ b/packages/client/ui-tool/README.zh.md
@@ -43,7 +43,7 @@ owner 载荷为 `ToolCallOwnerProps`:`callId`、`toolName`、冻结的 `block`
### 内置视图
-本包拥有 generic fallback,以及 shell/pwsh、read、read_image、write/edit、running `str_replace_editor` `create`/`str_replace`、grep/glob、web、todo、question 与 Code Dispatch 的内置展示。结构化卡片直接从第一方原始 event 字段派生;Host `presentCall` 与 `presentResult` 值不会进入 Client。运行中与已完成的前台 `bash`、`pwsh` 和 `terminal_send` 调用,无论位于根还是 Code Dispatch 子调用中,都在通过相同的参数、结果和错误检查后使用 terminal 卡片。已完成的持久 shell 结果保持 generic 展示,因为 reset 与部分输出诊断不一定描述单个进程的退出状态;根调用的持久 shell 结果可展开,后台启动回执则保持折叠。成功的问题行按稳定 id 配对调用中的问题与结果中的回答,展开后显示可读的问答行。已取消或已中断的问题行显示其裁决与原始问题,不虚构回答。不受支持、格式错误或含糊的输入回退为压平的工具输入/结果文本。`ui-skill` 展示了业务包自行拥有的 `skill` 注册项。
+本包拥有 generic fallback,以及 shell/pwsh、read、read_image、write/edit、running `str_replace_editor` `create`/`str_replace`、grep/glob、web、todo、question 与 Code Dispatch 的内置展示。结构化卡片直接从第一方原始 event 字段派生;Host `presentCall` 与 `presentResult` 值不会进入 Client。运行中与已完成的前台标准 `bash`/`pwsh` 和 `terminal_send` 调用,无论位于根还是 Code Dispatch 子调用中,都在通过相同的参数、结果和错误检查后使用 terminal 卡片。持久 `bash`/`pwsh` 调用仅在运行中使用 terminal 卡片。以已识别的 spill 策略提示结尾的 shell 输出,在 shell 行中使用可展开的 generic 输出,在 Details 中使用 generic 输出;位置被改变或被省略的退出标记无法证明成功。已完成的持久 shell 结果保持 generic 展示,因为 reset 与部分输出诊断不一定描述单个进程的退出状态;根调用的持久 shell 结果可展开,后台启动回执则保持折叠。成功的问题行按稳定 id 配对调用中的问题与结果中的回答,展开后显示可读的问答行。已取消或已中断的问题行显示其裁决与原始问题,不虚构回答。不受支持、格式错误或含糊的输入回退为压平的工具输入/结果文本。`ui-skill` 展示了业务包自行拥有的 `skill` 注册项。
-----
diff --git a/packages/client/ui-tool/src/client/tool/models/terminal-card-model.ts b/packages/client/ui-tool/src/client/tool/models/terminal-card-model.ts
index 618afa6610..c8f68968fc 100644
--- a/packages/client/ui-tool/src/client/tool/models/terminal-card-model.ts
+++ b/packages/client/ui-tool/src/client/tool/models/terminal-card-model.ts
@@ -219,6 +219,24 @@ export function isSettledPersistentShellCall(block: ToolCallBlock): boolean {
return shellCall(parsed.name, parsed.args)?.persistent === true
}
+/**
+ * Identify a settled foreground shell preview whose spill footer can hide the exit marker.
+ * @param block - running or settled Tool block.
+ * @returns whether the shell output must remain generic without an inferred exit status.
+ */
+export function isSpilledShellCall(block: ToolCallBlock): boolean {
+ if (!('kind' in block)) return false
+ const parsed = parsedToolCall(block)
+ if (parsed === null) return false
+ const call = shellCall(parsed.name, parsed.args)
+ if (call === null || call.background) return false
+ const output = singleResultText(block)
+ // spill-policy appends this footer after a bounded preview; the process
+ // status can be displaced or omitted, so absence cannot imply exit zero.
+ return output !== undefined
+ && /(?:^|\n\n)\(Omitted \d+ bytes\. Full formatted result stored at: [\s\S]+\)$/.test(output)
+}
+
interface TerminalSendCall {
kind: 'terminal-send'
text: string
@@ -257,8 +275,8 @@ function parseExitStatus(text: string): { output: string; exitCode?: number; sig
/**
* Derive terminal props for supported shell and terminal-send calls, including
* nested Code Dispatch calls. Standard shell results parse their final status
- * marker; persistent shell results, background calls, errors, and malformed
- * input use the generic path. {@link isSettledPersistentShellCall} lets that generic
+ * marker; persistent shell results, spill previews, background calls, errors,
+ * and malformed input use the generic path. {@link isSettledPersistentShellCall} lets that generic
* persistent result remain expandable without inventing one process status.
* @param block - running or settled Tool block.
* @param sessionCwd - session workspace root used to resolve workdir.
@@ -289,7 +307,7 @@ export function terminalCardModel(
},
}
}
- if (block.isError || (call.kind === 'shell' && call.persistent)) return null
+ if (block.isError || (call.kind === 'shell' && call.persistent) || isSpilledShellCall(block)) return null
const output = singleResultText(block)
if (output === undefined) return null
const status = call.kind === 'terminal-send' ? { output } : parseExitStatus(output)
diff --git a/packages/client/ui-tool/src/client/tool/toolviews/bash-sample.tsx b/packages/client/ui-tool/src/client/tool/toolviews/bash-sample.tsx
index c291a733a1..e5380979b9 100644
--- a/packages/client/ui-tool/src/client/tool/toolviews/bash-sample.tsx
+++ b/packages/client/ui-tool/src/client/tool/toolviews/bash-sample.tsx
@@ -8,6 +8,7 @@ import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'
import type { ToolCallViewProps } from '../../contract/slots.ts'
import {
isSettledPersistentShellCall,
+ isSpilledShellCall,
localizeTerminalCardModel,
terminalBlockLabels,
terminalCardModel,
@@ -53,11 +54,10 @@ export function BashRow({ toolName, block, sessionId, useSessions, inspect, t }:
: model.state
const status = stateStatus(state, t)
const [expanded, setExpanded] = useState(false)
- // Execution failures and persistent-shell results have no terminal card.
- // Keep their recorded args and complete output reachable through the generic
- // body; background acknowledgements and malformed calls remain collapsed.
+ // Failures, persistent-shell results, and spill previews use a generic body;
+ // background acknowledgements and malformed calls remain collapsed.
const genericBody = terminal === null
- && (model.state === 'error' || isSettledPersistentShellCall(block))
+ && (model.state === 'error' || isSettledPersistentShellCall(block) || isSpilledShellCall(block))
&& (model.bodyRaw !== null || model.output !== null)
const expandable = terminal !== null || genericBody
const open = expanded && expandable
diff --git a/packages/client/ui-tool/tests/terminal-card.client.spec.tsx b/packages/client/ui-tool/tests/terminal-card.client.spec.tsx
index be03edabd4..9476de0ce5 100644
--- a/packages/client/ui-tool/tests/terminal-card.client.spec.tsx
+++ b/packages/client/ui-tool/tests/terminal-card.client.spec.tsx
@@ -225,6 +225,30 @@ describe('terminalCardModel', () => {
expect(terminalCardModel(running({ parentCallId: 'parent' }))).toEqual(terminalCardModel(running()))
})
+ it.each(['bash', 'pwsh'])('keeps nested persistent %s running cards and settled generic results', (name) => {
+ const argsRaw = JSON.stringify({ command: 'pwd' })
+ expect(terminalCardModel(running({ name, argsRaw, parentCallId: 'parent' })))
+ .toEqual(terminalCardModel(running({ name, argsRaw })))
+ expect(terminalCardModel(settled({ call: { name, argsRaw }, parentCallId: 'parent' }))).toBeNull()
+ })
+
+ it.each(['bash', 'pwsh'])('does not infer %s exit status from a spilled preview', (name) => {
+ const notice = '(Omitted 50000 bytes. Full formatted result stored at: /spill/output.txt. Read the file.)'
+ for (const parentCallId of [undefined, 'parent']) {
+ for (const preview of ['failed\n[exit code: 7]', 'killed\n[killed by signal: SIGTERM]', 'partial', '']) {
+ expect(terminalCardModel(settled({
+ ...parentCallId === undefined ? {} : { parentCallId },
+ call: { name, argsRaw: ARGS },
+ content: [{ type: 'text', text: preview === '' ? notice : `${preview}\n\n${notice}` }],
+ }))).toBeNull()
+ }
+ }
+ expect(terminalCardModel(settled({
+ call: { name, argsRaw: ARGS },
+ content: [{ type: 'text', text: `${notice}\nordinary output` }],
+ }))).not.toBeNull()
+ })
+
it('returns null for background, errors, malformed args, unsupported tools, and non-text results', () => {
expect(terminalCardModel(running({ argsRaw: shellArgs({ run_in_background: true }) }))).toBeNull()
expect(terminalCardModel(settled({ isError: true }))).toBeNull()
@@ -466,6 +490,18 @@ describe('BashRow terminal card', () => {
.toBe('a.ts b.ts\nc.ts d.ts\n')
})
+ it('expands a spilled child result without a successful terminal indicator', () => {
+ const output = 'failed\n[exit code: 7]\n\n(Omitted 50000 bytes. Full formatted result stored at: /spill/output.txt. Read the file.)'
+ const view = render()
+ const row = view.container.querySelector('[data-sample="bash"]')!
+ expect(row.getAttribute('role')).toBe('button')
+ fireEvent.click(row)
+ expect(view.container.querySelector('[data-terminal]')).toBeNull()
+ expect(view.container.querySelectorAll('[class*="_ioText_"]')[1]?.textContent).toBe(output)
+ })
+
it('a non-terminal bash call (background start) renders the summary row alone', () => {
const view = render(