diff --git a/docs/cordis-catalog/events-and-services.md b/docs/cordis-catalog/events-and-services.md index 5553592ba0..f4e30fe651 100644 --- a/docs/cordis-catalog/events-and-services.md +++ b/docs/cordis-catalog/events-and-services.md @@ -547,7 +547,7 @@ async execute(exec: ToolExecution): Promise Types: [ToolDefinition](../core-data-structures/tools.md) · [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) -Source: [`packages/core/tools/src/index.ts:365`](../../packages/core/tools/src/index.ts) +Source: [`packages/core/tools/src/index.ts:366`](../../packages/core/tools/src/index.ts) ## Inherited tier (cordis core + loader/hmr/timer) diff --git a/docs/rfc/implemented/architecture/2026-07-02-result-time-applied-hunk-diffs.md b/docs/rfc/implemented/architecture/2026-07-02-result-time-applied-hunk-diffs.md index caa10f0de3..8a4c49ff2c 100644 --- a/docs/rfc/implemented/architecture/2026-07-02-result-time-applied-hunk-diffs.md +++ b/docs/rfc/implemented/architecture/2026-07-02-result-time-applied-hunk-diffs.md @@ -35,7 +35,7 @@ Per the [capability-seam split](2026-06-13-capability-seams.md), the storage bac ### 3. The bridge renders a `diff` result card -`ToolResultView` gains a `DiffResultView { card:'diff'; title?; diffs: FileDiff[] }`; the bridge's result-side `switch (view.card)` gets a `diff` arm emitting the `{type:'diff'}` `ToolCallContent` blocks (mirroring the call-side arm). An ACP `tool_call_update.content` REPLACES the call's content in an editor, so the result-time contextual hunk **supersedes** the call-time snippet — the two-update sequence (call snippet, then result hunk) matches `claude-agent-acp` exactly. +`ToolResultView` gains a `DiffResultView { card:'diff'; title?; diffs: FileDiff[] }`; the bridge's result-side `switch (view.card)` gets a `diff` arm emitting the `{type:'diff'}` `ToolCallContent` blocks (mirroring the call-side arm). An ACP `tool_call_update.content` REPLACES the call's content in an editor, so the result diff **supersedes** the call-time snippet (and keeps the model-facing result text from clobbering it) — the two-update sequence (call snippet, then result diff) matches `claude-agent-acp` exactly. ### The diff algorithm — a third-party runtime dependency over vendoring @@ -44,7 +44,7 @@ Computing hunks-with-context is a solved problem with sharp edge cases (grouping ## Non-goals - **Live incremental diff streaming.** The hunk is computed once, after the mutation completes; there is no per-keystroke diff. -- **Diffing a binary/non-UTF-8 overwrite.** `before` is `null` for such a file (it has no text diff basis); the write still succeeds and renders the call-time card only. +- **Diffing a binary/non-UTF-8 overwrite.** `before` is `null` for such a file (it has no text diff basis); the write still succeeds and the result renders a whole-file diff (`oldText: null`) rather than a contextual hunk. - **Rename/move diffs.** Only content diffs of a single resolved path. ## Related diff --git a/docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md b/docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md index 66bea7a820..d574f50bba 100644 --- a/docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md +++ b/docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md @@ -65,6 +65,6 @@ interface TerminalResultView { card: 'terminal'; title?: string; output?: string ## Related - Supersedes the deferral in [Collapse tool-owned UI presentation](../../rejected/simplification/2026-06-20-generic-tool-rendering.md) (rejected — "wait for two real tools and two real consumers, then a tagged render-intent union"). That bar is now met; this is that union. -- Extended by [Result-time applied-hunk diffs](2026-07-02-result-time-applied-hunk-diffs.md), which adds a persisted `meta` channel so write/edit emit a result-time contextual-hunk `DiffResultView` (context lines + one hunk per `replace_all` site) on top of this union's call-time diff card. +- Extended by [Result-time applied-hunk diffs](2026-07-02-result-time-applied-hunk-diffs.md), which adds a persisted `meta` channel so write/edit emit a result-time `DiffResultView` — the applied change (a contextual hunk with context lines / one per `replace_all` site, or a whole-file diff for a create) — on top of this union's call-time diff card. - Folds `ToolTerminal` into the `terminal` views described by [ACP terminal and tool-call rendering](../feature/2026-06-18-acp-terminal-and-tool-rendering.md) (the `_meta` terminal-card convention and capability gate are unchanged; only the harness-side presentation type changes). - The ACP SDK's `Diff` / `ToolCallContent` types back the new `diff` card. diff --git a/packages/core/tools/src/index.ts b/packages/core/tools/src/index.ts index 43cd860734..76f67291ed 100644 --- a/packages/core/tools/src/index.ts +++ b/packages/core/tools/src/index.ts @@ -159,8 +159,9 @@ export interface TerminalCallView { * A call that creates or modifies files, rendered as an inline diff card by a * capable UI. Set by a tool whose call writes/edits a file (e.g. `write`, * `edit`). The diffs are derived from the call ARGUMENTS (a create's `oldText` is - * `null`); the result-time applied-hunk diff (with context) is a separate - * {@link DiffResultView} the tool emits after `execute`. + * `null`); the tool emits a separate {@link DiffResultView} after `execute` — the + * applied change (an edit/overwrite hunk with context, or a whole-file diff for a + * create). */ export interface DiffCallView { card: 'diff' diff --git a/packages/fs/fs-local/src/fsio.ts b/packages/fs/fs-local/src/fsio.ts index e2aead2bfc..ae4830336b 100644 --- a/packages/fs/fs-local/src/fsio.ts +++ b/packages/fs/fs-local/src/fsio.ts @@ -386,8 +386,9 @@ export async function readForEdit( * Best-effort read of a file's current text for a before/after diff basis, used * by an overwrite. Returns the LF-normalized decoded content, or `null` when the * file is binary or not valid UTF-8 — a write must succeed regardless of the - * prior bytes, so an undiffable prior file simply yields no contextual diff - * (the caller treats `null` the same as an absent file: call-time card only). + * prior bytes, so an undiffable prior file simply yields no contextual-hunk basis + * (the caller treats `null` the same as an absent file: the result renders a + * whole-file diff rather than an applied hunk). */ export async function readTextForDiff(absolutePath: string, signal?: AbortSignal): Promise { const buffer = await readFileAbortable(absolutePath, 'read', signal) diff --git a/packages/fs/fs/src/types.ts b/packages/fs/fs/src/types.ts index 1b768724cf..424d581771 100644 --- a/packages/fs/fs/src/types.ts +++ b/packages/fs/fs/src/types.ts @@ -103,8 +103,10 @@ export interface FsWriteOutcome { version: FsVersion /** * The file's content BEFORE the write, or `null` when the file did not exist - * (a create). LF-normalized storage text (the diff basis), never a diff — a - * consumer computes the result-time contextual diff from `before`/`after`. + * (a create) or was undiffable (binary/non-UTF-8). LF-normalized storage text + * (the diff basis), never a diff — a consumer computes the result-time + * contextual diff from `before`/`after` when `before` is present, else falls + * back to a whole-file diff. */ before: string | null /** The file's content AFTER the write, LF-normalized to share `before`'s diff basis. */ diff --git a/packages/ui/acp/src/index.ts b/packages/ui/acp/src/index.ts index cfa133ae8f..1c1f0f770a 100644 --- a/packages/ui/acp/src/index.ts +++ b/packages/ui/acp/src/index.ts @@ -1170,10 +1170,12 @@ function toolResultUpdate(callId: CallId, view: ToolResultView, isError: boolean ...view.title !== undefined ? { title: view.title } : {}, } case 'diff': { - // A result-time applied-hunk diff: emit one `{ type: 'diff' }` content block - // per hunk (mirroring the call-side diff arm). `tool_call_update.content` - // REPLACES the call's content in an editor, so these hunks supersede the - // call-time whole-file snippet the pending card installed. + // A result-time diff: emit one `{ type: 'diff' }` content block per entry + // (an applied hunk for an edit/overwrite, or a whole-file diff for a + // create), mirroring the call-side diff arm. `tool_call_update.content` + // REPLACES the call's content in an editor, so this result diff supersedes + // the diff the pending card installed (and keeps the model-facing result + // text from clobbering it). const content: AcpToolCallContent[] = view.diffs.map(d => ({ type: 'diff', path: d.path, oldText: d.oldText, newText: d.newText })) return { sessionUpdate: 'tool_call_update',