Merge origin/master into codex/sandbox-policy-context
This commit is contained in:
@@ -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/core/tools/README.md
|
||||
README.md: dcce455f9551318f3871e3df84c29789078fef7c
|
||||
README.zh.md: 63eaa2e0b66797c74a1d4845c29ca970b63f5f99
|
||||
README.md: 15fc5839a3b0e3fa2d20c5a9cc50577e9807ffda
|
||||
README.zh.md: 8547ee4a796dcd93945dfa40373c14c10d7d0c8a
|
||||
|
||||
@@ -108,7 +108,7 @@ Optional `isConcurrencySafe(args)` receives typed, softly validated arguments. E
|
||||
Tools optionally own pure `presentCall()` and `presentResult()` render intents, so UIs do not special-case tool names:
|
||||
|
||||
- Call views are `{ card: 'generic', title, kind?, rawInput?, content?, locations? }`, `{ card: 'terminal', title, description?, cwd? }`, or `{ card: 'diff', title, diffs, locations? }`.
|
||||
- Result views are `{ card: 'generic', title?, content? }`, `{ card: 'terminal', title?, output?, exitCode?, signal? }`, `{ card: 'diff', title?, diffs }`, `{ card: 'read', title?, path, offset, lines, totalLines, lang?, content? }` (a completed file read → a line-numbered, optionally syntax-highlighted code view; `offset` is the 1-based first line the window requested, kept even when `lines` is empty; `lines` is `{ number, text }[]` keeping each file line number, and `content` is the envelope-stripped text a UI without read support falls back to), or `{ card: 'web', kind: 'search' | 'fetch', title?, … }` (a completed web retrieval; the `kind` arms carry the structured search sources or the fetch summary, and a UI without the `web` capability falls back to the raw result content).
|
||||
- Result views are `{ card: 'generic', title?, content? }`, `{ card: 'terminal', title?, output?, exitCode?, signal? }`, `{ card: 'diff', title?, diffs }`, `{ card: 'search', shape, title?, truncated, total, … }` (a completed discovery search — grouped-by-file matches for `shape: 'matches'` (grep) or a flat path list for `shape: 'paths'` (glob), with `truncated`/`total` so a UI never presents a capped result as complete; the view carries no result text and a search has no `card: 'search'` call-time analogue), `{ card: 'read', title?, path, offset, lines, totalLines, lang?, content? }` (a completed file read → a line-numbered, optionally syntax-highlighted code view; `offset` is the 1-based first line the window requested, kept even when `lines` is empty; `lines` is `{ number, text }[]` keeping each file line number, and `content` is the envelope-stripped text a UI without read support falls back to), or `{ card: 'web', kind: 'search' | 'fetch', title?, … }` (a completed web retrieval; the `kind` arms carry the structured search sources or the fetch summary, and a UI without the `web` capability falls back to the raw result content).
|
||||
|
||||
Returning `undefined` selects generic fallback. Presenters depend only on their arguments and the durable result because UIs call them during live streaming and log replay. `output.presentationMeta(args, value)` derives JSON metadata for direct surface calls; that metadata persists with `tool/result` and returns to `presentResult`, while the canonical value itself remains execution-local and is never replayed. Nested Code dispatches do not compute metadata. `defineTool` soft-validates older logged arguments and falls back instead of crashing replay. `dsh-tool-bash` and `dsh-tool-fs` are the reference implementations; the [canonical-output Agent Note](../../../.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md) owns the value/presentation split and the [render-intent Agent Note](../../../.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md) owns card vocabulary.
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ ctx.tools.register(defineTool({
|
||||
工具可以选择拥有纯 `presentCall()` 和 `presentResult()` 呈现意图,使 UI 无需特殊处理工具名称:
|
||||
|
||||
- 调用视图为 `{ card: 'generic', title, kind?, rawInput?, content?, locations? }`、`{ card: 'terminal', title, description?, cwd? }` 或 `{ card: 'diff', title, diffs, locations? }`。
|
||||
- 结果视图为 `{ card: 'generic', title?, content? }`、`{ card: 'terminal', title?, output?, exitCode?, signal? }`、`{ card: 'diff', title?, diffs }`、`{ card: 'read', title?, path, offset, lines, totalLines, lang?, content? }`(已完成的文件读取→带行号、可选语法高亮的代码视图;`offset` 是窗口请求的 1-based 起始行,即使 `lines` 为空也保留;`lines` 是 `{ number, text }[]`,保留每一行的文件行号,`content` 是无读取能力的 UI 回退时使用的去信封文本)或 `{ card: 'web', kind: 'search' | 'fetch', title?, … }`(已完成的 web 检索;`kind` 各分支携带结构化的搜索来源或抓取摘要,不具备 `web` 能力的 UI 回退到原始结果内容)。
|
||||
- 结果视图为 `{ card: 'generic', title?, content? }`、`{ card: 'terminal', title?, output?, exitCode?, signal? }`、`{ card: 'diff', title?, diffs }`、`{ card: 'search', shape, title?, truncated, total, … }`(已完成的发现型搜索——`shape: 'matches'`(grep)为按文件分组的匹配,`shape: 'paths'`(glob)为扁平路径列表,配 `truncated`/`total` 使 UI 永不把被截断的结果当作完整结果呈现;该视图不携带结果文本,且搜索没有 `card: 'search'` 的调用时对应视图)、`{ card: 'read', title?, path, offset, lines, totalLines, lang?, content? }`(已完成的文件读取→带行号、可选语法高亮的代码视图;`offset` 是窗口请求的 1-based 起始行,即使 `lines` 为空也保留;`lines` 是 `{ number, text }[]`,保留每一行的文件行号,`content` 是无读取能力的 UI 回退时使用的去信封文本)或 `{ card: 'web', kind: 'search' | 'fetch', title?, … }`(已完成的 web 检索;`kind` 各分支携带结构化的搜索来源或抓取摘要,不具备 `web` 能力的 UI 回退到原始结果内容)。
|
||||
|
||||
返回 `undefined` 会选择通用回退。呈现器只依赖其参数和持久结果,因为 UI 会在实时流式输出和日志回放期间调用它们。`output.presentationMeta(args, value)` 为直接接口调用派生 JSON 元数据;该元数据随 `tool/result` 持久化并传回 `presentResult`,而规范值本身仍只存在于执行局部,绝不会回放。嵌套 Code 分发不会计算元数据。`defineTool` 会软验证较旧的日志参数并回退,而不会使回放崩溃。`dsh-tool-bash` 与 `dsh-tool-fs` 是参考实现;[规范输出 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md) 规定值/呈现拆分,[呈现意图 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md) 规定卡片词汇。
|
||||
|
||||
|
||||
@@ -83,6 +83,11 @@ export type {
|
||||
GenericResultView,
|
||||
TerminalResultView,
|
||||
DiffResultView,
|
||||
SearchResultView,
|
||||
SearchMatchesResultView,
|
||||
SearchPathsResultView,
|
||||
SearchFileMatches,
|
||||
SearchLineMatch,
|
||||
ReadResultView,
|
||||
WebResultView,
|
||||
WebSearchResultView,
|
||||
|
||||
@@ -137,7 +137,7 @@ export interface ReadFileLine {
|
||||
* `ToolDefinition.presentResult`; omitting the method keeps the pending
|
||||
* title and renders the raw result content.
|
||||
*/
|
||||
export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | ReadResultView | WebResultView
|
||||
export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView | ReadResultView | WebResultView
|
||||
|
||||
/**
|
||||
* The default completed card: an optional replacement title and reformatted
|
||||
@@ -189,6 +189,83 @@ export interface DiffResultView {
|
||||
diffs: FileDiff[]
|
||||
}
|
||||
|
||||
/** One matched line inside a {@link SearchFileMatches} group: its 1-based line number and text. */
|
||||
export interface SearchLineMatch {
|
||||
/** 1-based line number of the match within its file. */
|
||||
lineNumber: number
|
||||
/** The matched line text, as the tool surfaced it (the per-line preview budget already applied). */
|
||||
line: string
|
||||
}
|
||||
|
||||
/** One file's grouped content matches for a {@link SearchMatchesResultView}, in first-seen file order. */
|
||||
export interface SearchFileMatches {
|
||||
/** The file the matches belong to (the model-facing display path). */
|
||||
path: string
|
||||
/** The file's matched lines, in output order. */
|
||||
matches: SearchLineMatch[]
|
||||
}
|
||||
|
||||
/**
|
||||
* A completed content search (`grep`) rendered as a search card whose matches are
|
||||
* grouped by file, so a capable UI can list each file as an expandable group of
|
||||
* its matched lines. `shape: 'matches'` discriminates this variant from the path
|
||||
* variant ({@link SearchPathsResultView}) within {@link SearchResultView}. The
|
||||
* discriminant is `shape`, not `kind`, so it never collides with the
|
||||
* {@link ToolCallKind} `kind` an icon-picking bridge reads off a call view.
|
||||
*/
|
||||
export interface SearchMatchesResultView {
|
||||
card: 'search'
|
||||
shape: 'matches'
|
||||
/** Replacement title for the completed call. Omit to keep the pending-state title. */
|
||||
title?: string
|
||||
/** Matched lines grouped by file, in first-seen file order. */
|
||||
files: SearchFileMatches[]
|
||||
/**
|
||||
* Whether the tool capped the inline result: `files` carries only the retained
|
||||
* matches, not every match the search found. A UI shows a capped indicator so it
|
||||
* never presents a partial group as complete.
|
||||
*/
|
||||
truncated: boolean
|
||||
/** Total matches the search found before capping (equals the retained count when not `truncated`). */
|
||||
total: number
|
||||
}
|
||||
|
||||
/**
|
||||
* A completed path search (`glob`) rendered as a search card whose result is a flat
|
||||
* path list. `shape: 'paths'` discriminates this variant from the grouped-matches
|
||||
* variant ({@link SearchMatchesResultView}) within {@link SearchResultView}.
|
||||
*/
|
||||
export interface SearchPathsResultView {
|
||||
card: 'search'
|
||||
shape: 'paths'
|
||||
/** Replacement title for the completed call. Omit to keep the pending-state title. */
|
||||
title?: string
|
||||
/** The discovered paths, in the tool's result order (the retained page when `truncated`). */
|
||||
paths: string[]
|
||||
/**
|
||||
* Whether the tool capped the inline result: `paths` carries only the retained
|
||||
* page, not every path the search found. A UI shows a capped indicator so it
|
||||
* never presents a partial list as complete.
|
||||
*/
|
||||
truncated: boolean
|
||||
/** Total paths the search found before capping (equals `paths.length` when not `truncated`). */
|
||||
total: number
|
||||
}
|
||||
|
||||
/**
|
||||
* A completed search rendered as a search card, the result-time view a discovery
|
||||
* tool (`grep`, `glob`) returns from `presentResult`. One `card: 'search'` view
|
||||
* with two `shape`-discriminated variants: grouped-by-file content matches
|
||||
* ({@link SearchMatchesResultView}) and a flat path list
|
||||
* ({@link SearchPathsResultView}). Both carry a `truncated`/`total` signal so a UI
|
||||
* never presents a capped result as complete. The view carries no result text: a
|
||||
* UI without a search card falls back to the raw `tool/result` content. There is
|
||||
* no call-time analogue: a search call stays a {@link GenericCallView}
|
||||
* (`kind: 'search'`) because the pending state has no matches or paths to show —
|
||||
* the structured shape exists only after `execute`.
|
||||
*/
|
||||
export type SearchResultView = SearchMatchesResultView | SearchPathsResultView
|
||||
|
||||
/**
|
||||
* A completed file read rendered as a line-numbered, optionally syntax-highlighted
|
||||
* code view by a capable UI. Set by a tool whose call reads file text (e.g.
|
||||
|
||||
Reference in New Issue
Block a user