@oh-my-pi/pi-coding-agent 17.1.6 → 17.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/dist/{CHANGELOG-x9zt79k8.md → CHANGELOG-vt8ene9g.md} +58 -0
- package/dist/cli.js +4309 -7287
- package/dist/template-dys3vk5b.js +1671 -0
- package/dist/template-f8wx9vfn.css +1355 -0
- package/dist/template-qat058wr.html +55 -0
- package/dist/tool-views.generated-jdfmzwmn.js +35 -0
- package/dist/types/advisor/advise-tool.d.ts +0 -7
- package/dist/types/advisor/runtime.d.ts +0 -9
- package/dist/types/async/job-manager.d.ts +11 -0
- package/dist/types/cleanse/agent.d.ts +19 -0
- package/dist/types/cleanse/balance.d.ts +7 -0
- package/dist/types/cleanse/checkers.d.ts +13 -0
- package/dist/types/cleanse/index.d.ts +16 -0
- package/dist/types/cleanse/loop.d.ts +16 -0
- package/dist/types/cleanse/parsers.d.ts +13 -0
- package/dist/types/cleanse/progress.d.ts +14 -0
- package/dist/types/cleanse/types.d.ts +64 -0
- package/dist/types/commands/cleanse.d.ts +23 -0
- package/dist/types/config/settings-schema.d.ts +16 -1
- package/dist/types/cursor.d.ts +2 -1
- package/dist/types/export/html/index.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +4 -0
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +27 -1
- package/dist/types/extensibility/shared-events.d.ts +18 -1
- package/dist/types/modes/components/custom-editor.d.ts +5 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -3
- package/dist/types/session/agent-session-types.d.ts +5 -5
- package/dist/types/session/agent-session.d.ts +26 -1
- package/dist/types/session/async-job-delivery.d.ts +8 -0
- package/dist/types/session/model-controls.d.ts +4 -4
- package/dist/types/session/session-tools.d.ts +44 -6
- package/dist/types/session/streaming-output.d.ts +7 -2
- package/dist/types/session/turn-recovery.d.ts +1 -1
- package/dist/types/task/isolation-ownership.d.ts +34 -0
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +1 -2
- package/dist/types/tools/browser/tab-worker.d.ts +1 -4
- package/dist/types/tools/index.d.ts +8 -3
- package/dist/types/tools/output-meta.d.ts +5 -0
- package/dist/types/tools/read.d.ts +9 -1
- package/dist/types/tools/xdev.d.ts +53 -67
- package/dist/types/tui/output-block.d.ts +5 -5
- package/dist/types/utils/cpuprofile.d.ts +51 -0
- package/dist/types/utils/inspect-image-mode.d.ts +29 -0
- package/dist/types/utils/profile-tree.d.ts +47 -0
- package/dist/types/utils/sample-profile.d.ts +67 -0
- package/package.json +16 -12
- package/scripts/bundle-dist.ts +3 -1
- package/src/advisor/advise-tool.ts +0 -11
- package/src/advisor/runtime.ts +0 -12
- package/src/async/job-manager.ts +30 -7
- package/src/cleanse/agent.ts +226 -0
- package/src/cleanse/balance.ts +79 -0
- package/src/cleanse/checkers.ts +996 -0
- package/src/cleanse/index.ts +190 -0
- package/src/cleanse/loop.ts +51 -0
- package/src/cleanse/parsers.ts +726 -0
- package/src/cleanse/progress.ts +50 -0
- package/src/cleanse/prompts/assignment.md +47 -0
- package/src/cleanse/types.ts +72 -0
- package/src/cli/update-cli.ts +3 -0
- package/src/cli/usage-cli.ts +29 -4
- package/src/cli/worktree-cli.ts +28 -11
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +2 -1
- package/src/commands/cleanse.ts +45 -0
- package/src/config/settings-schema.ts +15 -1
- package/src/config/settings.ts +35 -0
- package/src/cursor.ts +4 -3
- package/src/discovery/builtin-rules/index.ts +2 -0
- package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
- package/src/discovery/claude-plugins.ts +144 -34
- package/src/export/html/index.ts +17 -10
- package/src/extensibility/extensions/runner.ts +26 -0
- package/src/extensibility/extensions/wrapper.ts +74 -42
- package/src/extensibility/hooks/tool-wrapper.ts +11 -4
- package/src/extensibility/legacy-pi-ai-shim.ts +46 -1
- package/src/extensibility/shared-events.ts +18 -1
- package/src/launch/broker.ts +14 -4
- package/src/modes/acp/acp-agent.ts +12 -9
- package/src/modes/acp/acp-event-mapper.ts +38 -4
- package/src/modes/components/custom-editor.ts +39 -16
- package/src/modes/components/settings-selector.ts +9 -1
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/tool-execution.ts +8 -7
- package/src/modes/controllers/extension-ui-controller.ts +4 -4
- package/src/modes/controllers/selector-controller.ts +7 -2
- package/src/modes/interactive-mode.ts +36 -47
- package/src/modes/prompt-action-autocomplete.ts +5 -3
- package/src/prompts/goals/guided-goal-interview.md +41 -6
- package/src/prompts/system/vibe-mode-active.md +4 -1
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/goal.md +1 -1
- package/src/sdk.ts +47 -50
- package/src/session/agent-session-types.ts +5 -5
- package/src/session/agent-session.ts +151 -11
- package/src/session/async-job-delivery.ts +8 -0
- package/src/session/model-controls.ts +9 -9
- package/src/session/session-advisors.ts +2 -7
- package/src/session/session-history-format.ts +31 -6
- package/src/session/session-listing.ts +66 -4
- package/src/session/session-tools.ts +158 -52
- package/src/session/streaming-output.ts +18 -6
- package/src/session/turn-recovery.ts +4 -4
- package/src/slash-commands/builtin-registry.ts +74 -2
- package/src/task/isolation-ownership.ts +106 -0
- package/src/task/worktree.ts +8 -0
- package/src/tools/ask.ts +3 -3
- package/src/tools/ast-edit.ts +9 -2
- package/src/tools/bash.ts +16 -9
- package/src/tools/browser/cmux/cmux-tab.ts +9 -14
- package/src/tools/browser/tab-worker.ts +12 -35
- package/src/tools/browser.ts +2 -2
- package/src/tools/gh-renderer.ts +3 -3
- package/src/tools/index.ts +46 -17
- package/src/tools/output-meta.ts +20 -0
- package/src/tools/read.ts +87 -13
- package/src/tools/write.ts +51 -7
- package/src/tools/xdev.ts +198 -210
- package/src/tui/code-cell.ts +4 -4
- package/src/tui/output-block.ts +25 -8
- package/src/utils/cpuprofile.ts +235 -0
- package/src/utils/inspect-image-mode.ts +39 -0
- package/src/utils/profile-tree.ts +111 -0
- package/src/utils/sample-profile.ts +437 -0
- package/src/utils/shell-snapshot-fn-env.sh +5 -2
- package/src/web/search/render.ts +2 -2
- package/dist/types/goals/guided-setup.d.ts +0 -30
- package/src/goals/guided-setup.ts +0 -171
- package/src/prompts/goals/guided-goal-system.md +0 -33
package/src/tools/xdev.ts
CHANGED
|
@@ -9,22 +9,27 @@
|
|
|
9
9
|
* read xd://<tool> → tool docs + JSON parameter schema
|
|
10
10
|
* write xd://<tool> → execute: `content` is the JSON args object
|
|
11
11
|
*
|
|
12
|
+
* Direct and device dispatch share one canonical tool map. The mounted-name
|
|
13
|
+
* set controls presentation only; dispatch accepts the enabled union of
|
|
14
|
+
* top-level active and mounted names. Listing and prompt docs stay
|
|
15
|
+
* mounted-only because top-level tools already ship their schemas.
|
|
16
|
+
*
|
|
12
17
|
* Args go through the same machinery as native tool calls: validated with
|
|
13
18
|
* pi-ai's `validateToolArguments` (the schema is returned on mismatch, so a
|
|
14
19
|
* malformed call self-corrects without a round trip) and streamed through
|
|
15
20
|
* the write tool's existing incremental `content` decoding for live render
|
|
16
21
|
* previews. Compared to a dispatcher def this still costs zero *schema
|
|
17
22
|
* duplication* — one wire schema per tool instead of one per dispatcher
|
|
18
|
-
* branch — but full docs + schema for every mounted device
|
|
19
|
-
* the system prompt
|
|
20
|
-
*
|
|
23
|
+
* branch — but full docs + schema for every mounted device can be inlined
|
|
24
|
+
* into the system prompt, so no discovery read is needed before first use;
|
|
25
|
+
* `read xd://<tool>` remains for on-demand re-fetch.
|
|
21
26
|
*
|
|
22
27
|
* Rendering: the write renderer draws NOTHING until the streamed `path` is
|
|
23
28
|
* known and provably does not target `xd://`; device writes then delegate to
|
|
24
29
|
* the wrapped tool's own renderer with the decoded inner args.
|
|
25
30
|
*/
|
|
26
31
|
import type { AgentToolContext, AgentToolResult, AgentToolUpdateCallback, ToolLoadMode } from "@oh-my-pi/pi-agent-core";
|
|
27
|
-
import { type Tool as AiTool, toolWireSchema, validateToolArguments } from "@oh-my-pi/pi-ai";
|
|
32
|
+
import { type Tool as AiTool, jsonSchemaToTypeScript, toolWireSchema, validateToolArguments } from "@oh-my-pi/pi-ai";
|
|
28
33
|
import { type Component, Container, Text } from "@oh-my-pi/pi-tui";
|
|
29
34
|
import { parseStreamingJson } from "@oh-my-pi/pi-utils";
|
|
30
35
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
@@ -69,8 +74,7 @@ export type XdevDocsMode = "inline" | "builtins" | "catalog";
|
|
|
69
74
|
* while the `xd://` transport is active. Discoverable tools mount unless they
|
|
70
75
|
* are pinned top-level by {@link XDEV_KEEP_TOP_LEVEL} or carry the transport
|
|
71
76
|
* itself ({@link XDEV_TRANSPORT_TOOLS}); essential tools never do. The caller
|
|
72
|
-
* gates this on the transport being active
|
|
73
|
-
* {@link XdevRegistry} existing).
|
|
77
|
+
* gates this on the transport being active.
|
|
74
78
|
*/
|
|
75
79
|
export function isMountableUnderXdev(tool: { name: string; loadMode?: ToolLoadMode }): boolean {
|
|
76
80
|
if (tool.name in XDEV_TRANSPORT_TOOLS || tool.name in XDEV_KEEP_TOP_LEVEL) return false;
|
|
@@ -107,7 +111,7 @@ function schemaDeclaresIntentField(schema: unknown): boolean {
|
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
function renderDocs(inst: Tool, heading = "#", descriptionCap?: number): string {
|
|
110
|
-
const schema =
|
|
114
|
+
const schema = jsonSchemaToTypeScript(toolWireSchema(inst as AiTool));
|
|
111
115
|
let description = inst.description ?? "";
|
|
112
116
|
if (descriptionCap !== undefined && description.length > descriptionCap) {
|
|
113
117
|
description = `${description.slice(0, descriptionCap).trimEnd()}… (full docs: read ${XD_URL_PREFIX}${inst.name})`;
|
|
@@ -118,8 +122,8 @@ function renderDocs(inst: Tool, heading = "#", descriptionCap?: number): string
|
|
|
118
122
|
description,
|
|
119
123
|
"",
|
|
120
124
|
`${heading}# Schema`,
|
|
121
|
-
"```
|
|
122
|
-
schema
|
|
125
|
+
"```ts",
|
|
126
|
+
`type Args = ${schema};`,
|
|
123
127
|
"```",
|
|
124
128
|
`Execute by writing JSON to ${XD_URL_PREFIX}${inst.name}.`,
|
|
125
129
|
].join("\n");
|
|
@@ -208,227 +212,211 @@ function decodeInnerArgs(raw: unknown): Record<string, unknown> {
|
|
|
208
212
|
/** Device-write content that requests docs instead of executing: empty, `?`, or `help`. */
|
|
209
213
|
const HELP_CONTENT_RE = /^\s*(\?|help)?\s*$/i;
|
|
210
214
|
|
|
211
|
-
/**
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
*/
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
#dynamic = new Map<string, Tool>();
|
|
225
|
-
|
|
226
|
-
constructor(builtins: Iterable<Tool>) {
|
|
227
|
-
for (const tool of builtins) this.#builtins.set(tool.name, tool);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Replace the dynamic mount set while preserving the built-in devices. Order
|
|
232
|
-
* follows `tools`; names absent from it are dropped. A built-in device is
|
|
233
|
-
* never shadowed by a same-named dynamic entry.
|
|
234
|
-
*/
|
|
235
|
-
reconcile(tools: Iterable<Tool>): void {
|
|
236
|
-
const next = new Map<string, Tool>();
|
|
237
|
-
for (const tool of tools) {
|
|
238
|
-
if (this.#builtins.has(tool.name)) continue;
|
|
239
|
-
next.set(tool.name, tool);
|
|
240
|
-
}
|
|
241
|
-
this.#dynamic = next;
|
|
242
|
-
}
|
|
215
|
+
/** Shared tool state consumed by the `xd://` presentation layer. */
|
|
216
|
+
export interface XdevState {
|
|
217
|
+
/** Canonical session tool map; direct and device dispatch read the same instances. */
|
|
218
|
+
readonly tools: Map<string, Tool>;
|
|
219
|
+
/** Ordered names currently presented as mounted devices. */
|
|
220
|
+
readonly mountedNames: Set<string>;
|
|
221
|
+
/** Names originating from built-in factories, used only for prompt presentation. */
|
|
222
|
+
readonly builtInNames: Set<string>;
|
|
223
|
+
/** Whether a name is active at the top level. */
|
|
224
|
+
readonly isActive: (name: string) => boolean;
|
|
225
|
+
/** Optional execution-only decorator, such as the ACP permission gate. */
|
|
226
|
+
decorateExecution?(tool: Tool): Tool;
|
|
227
|
+
}
|
|
243
228
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
229
|
+
/** Full-doc character budget for system-prompt mounted-device sections. */
|
|
230
|
+
export const XDEV_DOCS_TOTAL_BUDGET = 48_000;
|
|
231
|
+
/** Per-device cap preventing one pathological description from starving later devices. */
|
|
232
|
+
export const XDEV_DOCS_PER_DEVICE_CAP = 10_000;
|
|
233
|
+
/** Description cap for external mounted tools; their full docs remain readable on demand. */
|
|
234
|
+
export const XDEV_EXTERNAL_DESCRIPTION_CAP = 200;
|
|
235
|
+
|
|
236
|
+
/** Resolve any enabled tool through the canonical session map. */
|
|
237
|
+
export function resolveXdevTool(state: XdevState, name: string): Tool | undefined {
|
|
238
|
+
if (!state.mountedNames.has(name) && !state.isActive(name)) return undefined;
|
|
239
|
+
return state.tools.get(name);
|
|
240
|
+
}
|
|
247
241
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
242
|
+
/** Resolve a mounted tool for top-level fallback execution. */
|
|
243
|
+
export function resolveMountedXdevTool(state: XdevState, name: string): Tool | undefined {
|
|
244
|
+
return state.mountedNames.has(name) ? state.tools.get(name) : undefined;
|
|
245
|
+
}
|
|
252
246
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
247
|
+
/** Resolve a mounted tool with its execution-only permission decorator. */
|
|
248
|
+
export function resolveMountedXdevExecutable(state: XdevState, name: string): Tool | undefined {
|
|
249
|
+
const tool = resolveMountedXdevTool(state, name);
|
|
250
|
+
return tool && state.decorateExecution ? state.decorateExecution(tool) : tool;
|
|
251
|
+
}
|
|
252
|
+
/** Mounted tools in presentation order, resolved from the canonical map. */
|
|
253
|
+
export function listXdevTools(state: XdevState): Tool[] {
|
|
254
|
+
return [...state.mountedNames].flatMap(name => {
|
|
255
|
+
const tool = state.tools.get(name);
|
|
256
|
+
return tool ? [tool] : [];
|
|
257
|
+
});
|
|
258
|
+
}
|
|
256
259
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
260
|
+
/** `{name, summary}` pairs for prompt templates and `/tools` display. */
|
|
261
|
+
export function xdevEntries(state: XdevState): Array<{ name: string; summary: string }> {
|
|
262
|
+
return listXdevTools(state).map(tool => ({
|
|
263
|
+
name: tool.name,
|
|
264
|
+
summary: promptCatalogSummary(
|
|
265
|
+
tool,
|
|
266
|
+
state.builtInNames.has(tool.name) ? undefined : XDEV_EXTERNAL_DESCRIPTION_CAP,
|
|
267
|
+
),
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
267
270
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
271
|
+
/** `read xd://` listing with one device per line. */
|
|
272
|
+
export function xdevListing(state: XdevState): string {
|
|
273
|
+
const rows = xdevEntries(state).map(({ name, summary }) => `${XD_URL_PREFIX}${name.padEnd(14)} ${summary}`);
|
|
274
|
+
return [
|
|
275
|
+
`${XD_URL_PREFIX} ${state.mountedNames.size} mounted tool devices.`,
|
|
276
|
+
...rows,
|
|
277
|
+
"",
|
|
278
|
+
`Read ${XD_URL_PREFIX}<tool> for docs + JSON schema; write the JSON args object to ${XD_URL_PREFIX}<tool> to execute. Active top-level tools accept the same dispatch.`,
|
|
279
|
+
].join("\n");
|
|
280
|
+
}
|
|
278
281
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
282
|
+
/** Docs + schema for any enabled tool. */
|
|
283
|
+
export function xdevDocs(state: XdevState, name: string): string {
|
|
284
|
+
return renderDocs(resolveRequiredXdevTool(state, name));
|
|
285
|
+
}
|
|
283
286
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
static readonly EXTERNAL_DESCRIPTION_CAP = 200;
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Docs + schema for mounted devices, nested under `##` headings for
|
|
302
|
-
* system-prompt embedding. Inlines full docs in catalog order (built-ins
|
|
303
|
-
* first) until {@link DOCS_TOTAL_BUDGET} is spent; the rest are listed by
|
|
304
|
-
* name + summary with a pointer to on-demand `read xd://<tool>` docs.
|
|
305
|
-
* Dynamic mounts embed at most {@link EXTERNAL_DESCRIPTION_CAP} description
|
|
306
|
-
* chars (schema always intact); `read xd://<tool>` returns the full text.
|
|
307
|
-
*/
|
|
308
|
-
docsAll(mode: XdevDocsMode = "inline", inlinePatterns: readonly string[] = []): string {
|
|
309
|
-
const sections: string[] = [];
|
|
310
|
-
const overflow: Tool[] = [];
|
|
311
|
-
const inlineGlobs = compileInlineGlobs(inlinePatterns);
|
|
312
|
-
let used = 0;
|
|
313
|
-
for (const tool of this.list()) {
|
|
314
|
-
if (!this.#shouldInline(tool, mode, inlineGlobs)) {
|
|
315
|
-
overflow.push(tool);
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
const descriptionCap = this.#dynamic.has(tool.name) ? XdevRegistry.EXTERNAL_DESCRIPTION_CAP : undefined;
|
|
319
|
-
const docs = renderDocs(tool, "##", descriptionCap);
|
|
320
|
-
if (docs.length > XdevRegistry.DOCS_PER_DEVICE_CAP || used + docs.length > XdevRegistry.DOCS_TOTAL_BUDGET) {
|
|
321
|
-
overflow.push(tool);
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
used += docs.length;
|
|
325
|
-
sections.push(docs);
|
|
287
|
+
/** Docs + schema for mounted devices under the configured prompt-doc policy. */
|
|
288
|
+
export function xdevDocsAll(
|
|
289
|
+
state: XdevState,
|
|
290
|
+
mode: XdevDocsMode = "inline",
|
|
291
|
+
inlinePatterns: readonly string[] = [],
|
|
292
|
+
): string {
|
|
293
|
+
const sections: string[] = [];
|
|
294
|
+
const overflow: Tool[] = [];
|
|
295
|
+
const inlineGlobs = compileInlineGlobs(inlinePatterns);
|
|
296
|
+
let used = 0;
|
|
297
|
+
for (const tool of listXdevTools(state)) {
|
|
298
|
+
if (!shouldInlineXdevTool(state, tool, mode, inlineGlobs)) {
|
|
299
|
+
overflow.push(tool);
|
|
300
|
+
continue;
|
|
326
301
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const maxLength = this.#dynamic.has(tool.name) ? XdevRegistry.EXTERNAL_DESCRIPTION_CAP : undefined;
|
|
333
|
-
return `- ${XD_URL_PREFIX}${tool.name} — ${promptCatalogSummary(tool, maxLength)}`;
|
|
334
|
-
}),
|
|
335
|
-
"",
|
|
336
|
-
`Read ${XD_URL_PREFIX}<tool> for full docs + JSON schema before first use.`,
|
|
337
|
-
].join("\n"),
|
|
338
|
-
);
|
|
302
|
+
const descriptionCap = state.builtInNames.has(tool.name) ? undefined : XDEV_EXTERNAL_DESCRIPTION_CAP;
|
|
303
|
+
const docs = renderDocs(tool, "##", descriptionCap);
|
|
304
|
+
if (docs.length > XDEV_DOCS_PER_DEVICE_CAP || used + docs.length > XDEV_DOCS_TOTAL_BUDGET) {
|
|
305
|
+
overflow.push(tool);
|
|
306
|
+
continue;
|
|
339
307
|
}
|
|
340
|
-
|
|
308
|
+
used += docs.length;
|
|
309
|
+
sections.push(docs);
|
|
310
|
+
}
|
|
311
|
+
if (overflow.length > 0) {
|
|
312
|
+
sections.push(
|
|
313
|
+
[
|
|
314
|
+
"## Additional devices (docs on demand)",
|
|
315
|
+
...overflow.map(tool => {
|
|
316
|
+
const maxLength = state.builtInNames.has(tool.name) ? undefined : XDEV_EXTERNAL_DESCRIPTION_CAP;
|
|
317
|
+
return `- ${XD_URL_PREFIX}${tool.name} — ${promptCatalogSummary(tool, maxLength)}`;
|
|
318
|
+
}),
|
|
319
|
+
"",
|
|
320
|
+
`Read ${XD_URL_PREFIX}<tool> for full docs + JSON schema before first use.`,
|
|
321
|
+
].join("\n"),
|
|
322
|
+
);
|
|
341
323
|
}
|
|
324
|
+
return sections.join("\n\n");
|
|
325
|
+
}
|
|
342
326
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
327
|
+
/** Docs for selected mounted devices under the configured prompt-doc policy. */
|
|
328
|
+
export function xdevDocsFor(
|
|
329
|
+
state: XdevState,
|
|
330
|
+
names: Iterable<string>,
|
|
331
|
+
mode: XdevDocsMode,
|
|
332
|
+
inlinePatterns: readonly string[] = [],
|
|
333
|
+
): string {
|
|
334
|
+
const sections: string[] = [];
|
|
335
|
+
const inlineGlobs = compileInlineGlobs(inlinePatterns);
|
|
336
|
+
let used = 0;
|
|
337
|
+
for (const name of names) {
|
|
338
|
+
const tool = resolveMountedXdevTool(state, name);
|
|
339
|
+
if (!tool || !shouldInlineXdevTool(state, tool, mode, inlineGlobs)) continue;
|
|
340
|
+
const descriptionCap = state.builtInNames.has(tool.name) ? undefined : XDEV_EXTERNAL_DESCRIPTION_CAP;
|
|
341
|
+
const docs = renderDocs(tool, "##", descriptionCap);
|
|
342
|
+
if (docs.length > XDEV_DOCS_PER_DEVICE_CAP || used + docs.length > XDEV_DOCS_TOTAL_BUDGET) continue;
|
|
343
|
+
used += docs.length;
|
|
344
|
+
sections.push(docs);
|
|
359
345
|
}
|
|
346
|
+
return sections.join("\n\n");
|
|
347
|
+
}
|
|
360
348
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
349
|
+
function shouldInlineXdevTool(
|
|
350
|
+
state: XdevState,
|
|
351
|
+
tool: Tool,
|
|
352
|
+
mode: XdevDocsMode,
|
|
353
|
+
inlineGlobs: readonly Bun.Glob[],
|
|
354
|
+
): boolean {
|
|
355
|
+
return (
|
|
356
|
+
mode !== "catalog" &&
|
|
357
|
+
(mode === "inline" || state.builtInNames.has(tool.name) || inlineGlobs.some(glob => glob.match(tool.name)))
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function resolveRequiredXdevTool(state: XdevState, name: string): Tool {
|
|
362
|
+
const inst = resolveXdevTool(state, name);
|
|
363
|
+
if (!inst) {
|
|
364
|
+
throw new ToolError(
|
|
365
|
+
`No such tool: ${XD_URL_PREFIX}${name}. Mounted devices: ${[...state.mountedNames].join(", ")}. Active top-level tools are also dispatchable via ${XD_URL_PREFIX}<tool>.`,
|
|
365
366
|
);
|
|
366
367
|
}
|
|
368
|
+
return inst;
|
|
369
|
+
}
|
|
367
370
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
371
|
+
/** Execute an enabled canonical tool through `write xd://<tool>`. */
|
|
372
|
+
export async function dispatchXdevTool(
|
|
373
|
+
state: XdevState,
|
|
374
|
+
name: string,
|
|
375
|
+
content: string,
|
|
376
|
+
toolCallId: string,
|
|
377
|
+
signal?: AbortSignal,
|
|
378
|
+
onUpdate?: AgentToolUpdateCallback,
|
|
379
|
+
context?: AgentToolContext,
|
|
380
|
+
): Promise<{ result: AgentToolResult<unknown>; xdev: XdevDispatch }> {
|
|
381
|
+
let xdev: XdevDispatch = { tool: name, mode: "execute" };
|
|
382
|
+
try {
|
|
383
|
+
const canonical = resolveRequiredXdevTool(state, name);
|
|
379
384
|
|
|
380
|
-
|
|
381
|
-
* Execute a device write: `content` is the JSON args object (empty, `?`, or
|
|
382
|
-
* `help` returns docs). Args validate against the wrapped tool's schema —
|
|
383
|
-
* the schema comes back in the error on mismatch.
|
|
384
|
-
*/
|
|
385
|
-
async dispatch(
|
|
386
|
-
name: string,
|
|
387
|
-
content: string,
|
|
388
|
-
toolCallId: string,
|
|
389
|
-
signal?: AbortSignal,
|
|
390
|
-
onUpdate?: AgentToolUpdateCallback,
|
|
391
|
-
context?: AgentToolContext,
|
|
392
|
-
): Promise<{ result: AgentToolResult<unknown>; xdev: XdevDispatch }> {
|
|
393
|
-
let xdev: XdevDispatch = { tool: name, mode: "execute" };
|
|
394
|
-
try {
|
|
395
|
-
const inst = this.#resolve(name);
|
|
396
|
-
|
|
397
|
-
if (HELP_CONTENT_RE.test(content)) {
|
|
398
|
-
return {
|
|
399
|
-
result: { content: [{ type: "text", text: renderDocs(inst) }] },
|
|
400
|
-
xdev: { tool: name, mode: "help" },
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const validated = parseDeviceArgs(inst as AiTool, content, toolCallId, () => renderDocs(inst));
|
|
405
|
-
xdev = { ...xdev, args: validated };
|
|
406
|
-
const innerOnUpdate: AgentToolUpdateCallback | undefined = onUpdate
|
|
407
|
-
? partial =>
|
|
408
|
-
onUpdate({
|
|
409
|
-
content: partial.content,
|
|
410
|
-
details: { xdev: { ...xdev, inner: partial.details } },
|
|
411
|
-
isError: partial.isError,
|
|
412
|
-
})
|
|
413
|
-
: undefined;
|
|
414
|
-
const result = await inst.execute(toolCallId, validated as never, signal, innerOnUpdate, context);
|
|
415
|
-
return { result, xdev: { ...xdev, inner: result.details } };
|
|
416
|
-
} catch (error) {
|
|
417
|
-
if (
|
|
418
|
-
error instanceof ToolAbortError ||
|
|
419
|
-
signal?.aborted ||
|
|
420
|
-
(error instanceof Error && error.name === "AbortError")
|
|
421
|
-
) {
|
|
422
|
-
throw error;
|
|
423
|
-
}
|
|
385
|
+
if (HELP_CONTENT_RE.test(content)) {
|
|
424
386
|
return {
|
|
425
|
-
result: {
|
|
426
|
-
|
|
427
|
-
isError: true,
|
|
428
|
-
},
|
|
429
|
-
xdev,
|
|
387
|
+
result: { content: [{ type: "text", text: renderDocs(canonical) }] },
|
|
388
|
+
xdev: { tool: name, mode: "help" },
|
|
430
389
|
};
|
|
431
390
|
}
|
|
391
|
+
|
|
392
|
+
const validated = parseDeviceArgs(canonical as AiTool, content, toolCallId, () => renderDocs(canonical));
|
|
393
|
+
xdev = { ...xdev, args: validated };
|
|
394
|
+
const innerOnUpdate: AgentToolUpdateCallback | undefined = onUpdate
|
|
395
|
+
? partial =>
|
|
396
|
+
onUpdate({
|
|
397
|
+
content: partial.content,
|
|
398
|
+
details: { xdev: { ...xdev, inner: partial.details } },
|
|
399
|
+
isError: partial.isError,
|
|
400
|
+
})
|
|
401
|
+
: undefined;
|
|
402
|
+
const executable = state.decorateExecution?.(canonical) ?? canonical;
|
|
403
|
+
const result = await executable.execute(toolCallId, validated as never, signal, innerOnUpdate, context);
|
|
404
|
+
return { result, xdev: { ...xdev, inner: result.details } };
|
|
405
|
+
} catch (error) {
|
|
406
|
+
if (
|
|
407
|
+
error instanceof ToolAbortError ||
|
|
408
|
+
signal?.aborted ||
|
|
409
|
+
(error instanceof Error && error.name === "AbortError")
|
|
410
|
+
) {
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
result: {
|
|
415
|
+
content: [{ type: "text", text: renderError(error) }],
|
|
416
|
+
isError: true,
|
|
417
|
+
},
|
|
418
|
+
xdev,
|
|
419
|
+
};
|
|
432
420
|
}
|
|
433
421
|
}
|
|
434
422
|
|
package/src/tui/code-cell.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
formatStatusIcon,
|
|
11
11
|
replaceTabs,
|
|
12
12
|
} from "../tools/render-utils";
|
|
13
|
-
import { renderOutputBlock } from "./output-block";
|
|
13
|
+
import { outputBlockContentWidth, renderOutputBlock } from "./output-block";
|
|
14
14
|
import type { State } from "./types";
|
|
15
15
|
|
|
16
16
|
export interface CodeCellOptions {
|
|
@@ -230,9 +230,9 @@ export function renderMarkdownCell(options: MarkdownCellOptions, theme: Theme):
|
|
|
230
230
|
const { title, meta } = formatHeader(codeOptions, theme);
|
|
231
231
|
const state = getState(options.status);
|
|
232
232
|
|
|
233
|
-
// Markdown component manages its own wrapping at the inner
|
|
234
|
-
// `renderOutputBlock
|
|
235
|
-
const innerWidth = Math.max(20, width
|
|
233
|
+
// Markdown component manages its own wrapping at the same inner width as
|
|
234
|
+
// `renderOutputBlock`, so collapsed row caps are applied after final wrapping.
|
|
235
|
+
const innerWidth = Math.max(20, outputBlockContentWidth(width));
|
|
236
236
|
const allLines = content.trim() ? new Markdown(content, 0, 0, getMarkdownTheme()).render(innerWidth) : [];
|
|
237
237
|
const maxContentLines = expanded ? allLines.length : Math.min(allLines.length, contentMaxLines);
|
|
238
238
|
const contentLines = allLines.slice(0, maxContentLines);
|
package/src/tui/output-block.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface OutputBlockOptions {
|
|
|
17
17
|
width: number;
|
|
18
18
|
applyBg?: boolean;
|
|
19
19
|
contentPaddingLeft?: number;
|
|
20
|
+
contentPaddingRight?: number;
|
|
20
21
|
/** Override the state-derived border color. Used for muted "legacy" tool
|
|
21
22
|
* frames that should not visually compete with framed-output tools. */
|
|
22
23
|
borderColor?: ThemeColor;
|
|
@@ -48,13 +49,18 @@ function normalizeContentPaddingLeft(value: number | undefined): number {
|
|
|
48
49
|
|
|
49
50
|
/**
|
|
50
51
|
* Inner content width that {@link renderOutputBlock} wraps its body to, for a
|
|
51
|
-
* given outer `width`: both vertical borders
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* into more rows than they counted and the box overflows its intended height.
|
|
52
|
+
* given outer `width`: both vertical borders plus symmetric content padding.
|
|
53
|
+
* An explicit left padding of zero keeps legacy flush blocks flush on both
|
|
54
|
+
* sides unless a right padding is provided separately.
|
|
55
55
|
*/
|
|
56
|
-
export function outputBlockContentWidth(
|
|
57
|
-
|
|
56
|
+
export function outputBlockContentWidth(
|
|
57
|
+
width: number,
|
|
58
|
+
contentPaddingLeft?: number,
|
|
59
|
+
contentPaddingRight?: number,
|
|
60
|
+
): number {
|
|
61
|
+
const left = normalizeContentPaddingLeft(contentPaddingLeft);
|
|
62
|
+
const right = normalizeContentPaddingLeft(contentPaddingRight ?? left);
|
|
63
|
+
return Math.max(1, width - 2 - left - right);
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): string[] {
|
|
@@ -88,8 +94,13 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
|
|
|
88
94
|
})();
|
|
89
95
|
|
|
90
96
|
const contentPaddingLeft = normalizeContentPaddingLeft(options.contentPaddingLeft);
|
|
91
|
-
const
|
|
97
|
+
const contentPaddingRight = normalizeContentPaddingLeft(options.contentPaddingRight ?? contentPaddingLeft);
|
|
98
|
+
const contentWidth = Math.max(
|
|
99
|
+
0,
|
|
100
|
+
lineWidth - visibleWidth(v) - contentPaddingLeft - contentPaddingRight - visibleWidth(v),
|
|
101
|
+
);
|
|
92
102
|
const contentLeftPadding = contentPaddingLeft > 0 ? padding(contentPaddingLeft) : "";
|
|
103
|
+
const contentRightPadding = contentPaddingRight > 0 ? padding(contentPaddingRight) : "";
|
|
93
104
|
|
|
94
105
|
// ── Layout pass: collect row descriptors before emitting the bordered lines. ──
|
|
95
106
|
const rows: BlockRow[] = [];
|
|
@@ -170,7 +181,8 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
|
|
|
170
181
|
return `${border(leftGlyphs)}${border(fillGlyphs)}${border(rightGlyph)}`;
|
|
171
182
|
};
|
|
172
183
|
|
|
173
|
-
const renderContent = (inner: string): string =>
|
|
184
|
+
const renderContent = (inner: string): string =>
|
|
185
|
+
`${border(v)}${contentLeftPadding}${inner}${contentRightPadding}${border(v)}`;
|
|
174
186
|
|
|
175
187
|
const lines: string[] = [];
|
|
176
188
|
for (let r = 0; r < H; r++) {
|
|
@@ -215,6 +227,11 @@ export class CachedOutputBlock {
|
|
|
215
227
|
const h = new Hasher();
|
|
216
228
|
h.u32(options.width);
|
|
217
229
|
h.u32(normalizeContentPaddingLeft(options.contentPaddingLeft));
|
|
230
|
+
h.u32(
|
|
231
|
+
normalizeContentPaddingLeft(
|
|
232
|
+
options.contentPaddingRight ?? normalizeContentPaddingLeft(options.contentPaddingLeft),
|
|
233
|
+
),
|
|
234
|
+
);
|
|
218
235
|
h.optional(options.header);
|
|
219
236
|
h.optional(options.headerMeta);
|
|
220
237
|
h.optional(options.state);
|