Merge remote-tracking branch 'origin/master' into feat/search-presenter
# Conflicts: # docs/config-catalog.md # docs/cookbook/adding-a-tool.i18n.yaml # docs/cookbook/adding-a-tool.md # docs/cookbook/adding-a-tool.zh.md # docs/cordis-catalog/events.md # docs/cordis-catalog/services.md # docs/core-data-structures/tools.i18n.yaml # docs/core-data-structures/tools.md # docs/core-data-structures/tools.zh.md # docs/event-producer-consumer.md # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/cordis/tool-cordis/src/api-catalog.ts # packages/core/tools/README.i18n.yaml # packages/core/tools/README.md # packages/core/tools/README.zh.md # packages/core/tools/src/index.ts # packages/core/tools/src/presentation.ts # packages/fs/tool-fs-search/src/glob.ts # packages/fs/tool-fs-search/src/index.ts # packages/ui/tui/src/components/transcript.ts # packages/ui/tui/tests/tui.spec.ts
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: a8afab7839983d300c2c17627e34dafaa4648d8b
|
||||
README.zh.md: 8beb63e8376f397ac859a6a04ab2f35316ef6d27
|
||||
README.md: b1be8c012287a686afd3524b0bcefa419839c6a2
|
||||
README.zh.md: 6c757f9b7b727700a9ee635a4b3a4c848ce45150
|
||||
|
||||
@@ -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 }`, or `{ 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).
|
||||
- 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), 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: 'search', shape, title?, truncated, total, … }`(已完成的发现型搜索——`shape: 'matches'`(grep)为按文件分组的匹配,`shape: 'paths'`(glob)为扁平路径列表,配 `truncated`/`total` 使 UI 永不把被截断的结果当作完整结果呈现;该视图不携带结果文本,且搜索没有 `card: 'search'` 的调用时对应视图)。
|
||||
- 结果视图为 `{ 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: '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) 规定卡片词汇。
|
||||
|
||||
|
||||
@@ -87,6 +87,10 @@ export type {
|
||||
SearchPathsResultView,
|
||||
SearchFileMatches,
|
||||
SearchLineMatch,
|
||||
WebResultView,
|
||||
WebSearchResultView,
|
||||
WebFetchResultView,
|
||||
WebSource,
|
||||
} from './presentation.ts'
|
||||
|
||||
declare module 'cordis' {
|
||||
|
||||
@@ -125,7 +125,7 @@ export interface DiffCallView {
|
||||
* `ToolDefinition.presentResult`; omitting the method keeps the pending
|
||||
* title and renders the raw result content.
|
||||
*/
|
||||
export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView
|
||||
export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView | WebResultView
|
||||
|
||||
/**
|
||||
* The default completed card: an optional replacement title and reformatted
|
||||
@@ -253,3 +253,84 @@ export interface SearchPathsResultView {
|
||||
* the structured shape exists only after `execute`.
|
||||
*/
|
||||
export type SearchResultView = SearchMatchesResultView | SearchPathsResultView
|
||||
|
||||
/**
|
||||
* One citeable source in a completed {@link WebSearchResultView}, the faithful
|
||||
* projection of one web-search source. The presentation projection of `dsh-web`'s
|
||||
* `WebSearchSource`: that seam type is the authoritative shape (core cannot depend
|
||||
* on the web seam, so the two are declared separately and MUST evolve together).
|
||||
* A web tool projects this shape through `output.presentationMeta` because the
|
||||
* render text cannot losslessly carry it (see the web-result-card Agent Note); its
|
||||
* `presentResult` reads it back.
|
||||
*/
|
||||
export interface WebSource {
|
||||
/** The source URL. */
|
||||
url: string
|
||||
/** The source title, when the provider returned one. */
|
||||
title?: string
|
||||
/** A short excerpt or summary, when the provider returned one. */
|
||||
snippet?: string
|
||||
/** Publication/crawl timestamp as a provider-supplied ISO-8601 string, when present. */
|
||||
publishedAt?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A completed web retrieval rendered as a structured card by a capable UI. Set
|
||||
* by a web tool whose call retrieves from the web (`web_search`, `web_fetch`).
|
||||
* One `kind`-tagged union carries both shapes because both are web retrieval and
|
||||
* a UI renders them with one component family; a UI switches on `kind`. An
|
||||
* incapable UI falls back to the raw `tool/result` content (this view carries no
|
||||
* `content` copy — see the web-result-card Agent Note). This is the result-time
|
||||
* analogue of the `web_search`/`web_fetch` calls' generic call views
|
||||
* (`kind: 'search'`/`'fetch'`); those tools keep their generic pending card and
|
||||
* add only this completed card.
|
||||
*
|
||||
* The `kind` field here is this union's own discriminant, NOT a
|
||||
* {@link ToolCallKind}: the two values deliberately match the tools' pending
|
||||
* `ToolCallKind` (`'search'`/`'fetch'`) so a call and its result read as one
|
||||
* category, but a new arm is a union edit plus a consumer branch, not any
|
||||
* arbitrary `ToolCallKind` value.
|
||||
*/
|
||||
export type WebResultView = WebSearchResultView | WebFetchResultView
|
||||
|
||||
/**
|
||||
* The completed state of a `web_search` call: the structured sources the model
|
||||
* cited, an optional provider answer, and whether the source list was cut to the
|
||||
* result cap. A capable UI renders the sources as a citation list; a UI without
|
||||
* the `web` capability falls back to the raw `tool/result` content.
|
||||
*/
|
||||
export interface WebSearchResultView {
|
||||
card: 'web'
|
||||
kind: 'search'
|
||||
/** Replacement title for the completed call. Omit to keep the pending-state title. */
|
||||
title?: string
|
||||
/** The faithful, structured sources — the field render text cannot losslessly carry. */
|
||||
sources: WebSource[]
|
||||
/** The provider-generated answer text, when any. */
|
||||
answer?: string
|
||||
/** True when the seam cut the source list to honor the result cap. */
|
||||
truncated: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* The completed state of a `web_fetch` call: the fetched URL, its HTTP status,
|
||||
* and whether the content was cut. The body itself is already markdown in the
|
||||
* raw `tool/result` content, so this card carries only the retrieval summary and
|
||||
* a UI without the `web` capability falls back to that content.
|
||||
*/
|
||||
export interface WebFetchResultView {
|
||||
card: 'web'
|
||||
kind: 'fetch'
|
||||
/** Replacement title for the completed call. Omit to keep the pending-state title. */
|
||||
title?: string
|
||||
/** The final URL after allowed redirects. */
|
||||
url: string
|
||||
/** HTTP status code of the fetched response. */
|
||||
statusCode: number
|
||||
/**
|
||||
* True when the provider capped the decoded body, or the output cap or a
|
||||
* pre-conversion source cut trimmed the rendered text (the effective
|
||||
* truncation the model-facing text also reflects).
|
||||
*/
|
||||
truncated: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user