agent-sh 0.15.1 → 0.15.3
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/dist/agent/agent-loop.js +12 -9
- package/dist/agent/events.d.ts +5 -0
- package/dist/agent/index.js +10 -5
- package/dist/agent/subagent.js +1 -1
- package/examples/extensions/ashi/package.json +1 -1
- package/examples/extensions/ashi/src/chat/tool-group.ts +3 -2
- package/examples/extensions/ashi/src/cli.ts +6 -6
- package/examples/extensions/ashi/src/dialogs.ts +16 -1
- package/examples/extensions/ashi/src/events.ts +1 -0
- package/examples/extensions/ashi/src/frontend.ts +43 -10
- package/examples/extensions/ashi/src/renderer.ts +2 -2
- package/examples/extensions/ashi/src/renderers/pi-tui/schema-mount.ts +4 -3
- package/examples/extensions/ashi/src/schema.ts +3 -2
- package/examples/extensions/ashi/src/ui.ts +11 -0
- package/package.json +1 -1
- package/src/agent/agent-loop.ts +12 -9
- package/src/agent/events.ts +3 -1
- package/src/agent/index.ts +9 -4
- package/src/agent/subagent.ts +1 -1
package/dist/agent/agent-loop.js
CHANGED
|
@@ -528,14 +528,15 @@ export class AgentLoop {
|
|
|
528
528
|
// Advisable so extensions can inject fallback parsers without
|
|
529
529
|
// subclassing the protocol.
|
|
530
530
|
h.define("tool-protocol:extract-calls", (args) => this.toolProtocol.extractToolCalls(args.text, args.streamedCalls));
|
|
531
|
-
// System prompt: static identity + behavioral instructions.
|
|
532
|
-
//
|
|
533
|
-
// advise system-prompt:frontend to describe their
|
|
534
|
-
// prompt, or advise
|
|
531
|
+
// System prompt: static identity + behavioral instructions. Extensions can
|
|
532
|
+
// use registerInstruction() for a managed section, advise system-prompt:identity
|
|
533
|
+
// to replace the kernel identity, advise system-prompt:frontend to describe their
|
|
534
|
+
// surface high in the prompt, or advise system-prompt:build directly for full control.
|
|
535
|
+
h.define("system-prompt:identity", () => STATIC_IDENTITY);
|
|
535
536
|
h.define("system-prompt:build", () => {
|
|
536
537
|
// The active frontend's surface goes right after the identity; omitted if none.
|
|
537
538
|
const frontend = (this.handlers.call("system-prompt:frontend") ?? "").trim();
|
|
538
|
-
const parts = [
|
|
539
|
+
const parts = [this.handlers.call("system-prompt:identity")];
|
|
539
540
|
if (frontend)
|
|
540
541
|
parts.push(frontend);
|
|
541
542
|
parts.push(STATIC_GUIDE);
|
|
@@ -966,7 +967,7 @@ export class AgentLoop {
|
|
|
966
967
|
// Execute via handler — extensions can advise to add safe-mode,
|
|
967
968
|
// logging, metrics, custom permission policies, etc.
|
|
968
969
|
const defaultOnChunk = (chunk) => {
|
|
969
|
-
this.bus.emit("agent:tool-output-chunk", { chunk });
|
|
970
|
+
this.bus.emit("agent:tool-output-chunk", { chunk, toolCallId: tc.id });
|
|
970
971
|
};
|
|
971
972
|
const result = await this.handlers.call("tool:execute", { name: tc.name, id: tc.id, args, tool, onChunk: defaultOnChunk,
|
|
972
973
|
batchIndex, batchTotal: batchTotal > 1 ? batchTotal : undefined,
|
|
@@ -1229,8 +1230,10 @@ export class AgentLoop {
|
|
|
1229
1230
|
let reasoning = "";
|
|
1230
1231
|
const reasoningDetailsByIndex = new Map();
|
|
1231
1232
|
const pendingToolCalls = [];
|
|
1232
|
-
// Tool protocol controls what goes in the API tools param vs dynamic context
|
|
1233
|
-
|
|
1233
|
+
// Tool protocol controls what goes in the API tools param vs dynamic context.
|
|
1234
|
+
// agent:tools:visible is a filter point on the assembled list — distinct from
|
|
1235
|
+
// getTools(), which other code (e.g. tool bridges) needs unfiltered.
|
|
1236
|
+
const toolView = this.bus.emitPipe("agent:tools:visible", { tools: this.getTools() }).tools;
|
|
1234
1237
|
const apiTools = this.toolProtocol.getApiTools(toolView);
|
|
1235
1238
|
const toolPrompt = this.toolProtocol.getToolPrompt(toolView);
|
|
1236
1239
|
// Dynamic context rides on the trailing message — see
|
|
@@ -1242,7 +1245,7 @@ export class AgentLoop {
|
|
|
1242
1245
|
// Let extensions transform the message array (compact, summarize, filter, etc.)
|
|
1243
1246
|
const messages = this.handlers.call("conversation:prepare", rawMessages);
|
|
1244
1247
|
// Stream filter strips tool tags from display (inline mode only)
|
|
1245
|
-
const streamFilter = this.toolProtocol.createStreamFilter(
|
|
1248
|
+
const streamFilter = this.toolProtocol.createStreamFilter(toolView.map((t) => t.name));
|
|
1246
1249
|
const requestParams = {
|
|
1247
1250
|
messages,
|
|
1248
1251
|
tools: apiTools,
|
package/dist/agent/events.d.ts
CHANGED
|
@@ -30,6 +30,10 @@ declare module "../core/event-bus.js" {
|
|
|
30
30
|
"agent:tools": {
|
|
31
31
|
tools: ToolDefinition[];
|
|
32
32
|
};
|
|
33
|
+
/** Filter point: the assembled tool list as the model will see it, after getTools(). */
|
|
34
|
+
"agent:tools:visible": {
|
|
35
|
+
tools: ToolDefinition[];
|
|
36
|
+
};
|
|
33
37
|
"agent:instructions": {
|
|
34
38
|
instructions: Array<{
|
|
35
39
|
name: string;
|
|
@@ -131,6 +135,7 @@ declare module "../core/event-bus.js" {
|
|
|
131
135
|
};
|
|
132
136
|
"agent:tool-output-chunk": {
|
|
133
137
|
chunk: string;
|
|
138
|
+
toolCallId?: string;
|
|
134
139
|
};
|
|
135
140
|
"tool:interactive-start": Record<string, never>;
|
|
136
141
|
"tool:interactive-end": Record<string, never>;
|
package/dist/agent/index.js
CHANGED
|
@@ -89,11 +89,15 @@ export default function agentBackend(ctx) {
|
|
|
89
89
|
const providerContribs = new Map();
|
|
90
90
|
// Settings overlay — fields here win over contributing extensions' payloads.
|
|
91
91
|
const settingsProviders = new Map();
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
const refreshSettingsProviders = () => {
|
|
93
|
+
settingsProviders.clear();
|
|
94
|
+
for (const name of getProviderNames()) {
|
|
95
|
+
const p = resolveProvider(name);
|
|
96
|
+
if (p)
|
|
97
|
+
settingsProviders.set(name, p);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
refreshSettingsProviders();
|
|
97
101
|
const providerHooks = new Map();
|
|
98
102
|
// Bakes model id so ModelEndpoint.buildReasoningParams keeps its (level) signature.
|
|
99
103
|
const bindReasoning = (shapeId, model) => {
|
|
@@ -341,6 +345,7 @@ export default function agentBackend(ctx) {
|
|
|
341
345
|
let agentLoop = null;
|
|
342
346
|
let loadedExtensionNames = [];
|
|
343
347
|
bus.on("agent:providers:changed", () => {
|
|
348
|
+
refreshSettingsProviders();
|
|
344
349
|
resolvedProviders = computeResolvedProviders();
|
|
345
350
|
if (!resolved)
|
|
346
351
|
return;
|
package/dist/agent/subagent.js
CHANGED
|
@@ -72,7 +72,7 @@ export async function runSubagent(opts) {
|
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
74
|
const onChunk = bus && tool.showOutput !== false
|
|
75
|
-
? (chunk) => { bus.emit("agent:tool-output-chunk", { chunk }); }
|
|
75
|
+
? (chunk) => { bus.emit("agent:tool-output-chunk", { chunk, toolCallId: tc.id }); }
|
|
76
76
|
: undefined;
|
|
77
77
|
const result = await tool.execute(args, onChunk);
|
|
78
78
|
if (bus) {
|
|
@@ -44,8 +44,9 @@ export class ToolGroup {
|
|
|
44
44
|
this.repaint();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
this.expanded
|
|
47
|
+
setExpanded(expanded: boolean): void {
|
|
48
|
+
if (this.expanded === expanded) return;
|
|
49
|
+
this.expanded = expanded;
|
|
49
50
|
this.repaint();
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -6,7 +6,7 @@ import { createCore } from "agent-sh/core";
|
|
|
6
6
|
import { loadBuiltinExtensions } from "agent-sh/extensions";
|
|
7
7
|
import { loadExtensions } from "agent-sh/extension-loader";
|
|
8
8
|
import { activateAgent } from "agent-sh/agent";
|
|
9
|
-
import { getSettings } from "agent-sh/settings";
|
|
9
|
+
import { getSettings, CONFIG_DIR } from "agent-sh/settings";
|
|
10
10
|
import { Shell } from "agent-sh/shell";
|
|
11
11
|
import { TerminalBuffer } from "agent-sh/utils/terminal-buffer";
|
|
12
12
|
import type { Terminal } from "agent-sh/shell/terminal";
|
|
@@ -37,7 +37,6 @@ import { createPiTuiRenderer } from "./renderers/pi-tui/index.js";
|
|
|
37
37
|
import type { Renderer } from "./renderer.js";
|
|
38
38
|
import { loadRendererPreference } from "./display-config.js";
|
|
39
39
|
import { applyOutputMode } from "./terminal-mode.js";
|
|
40
|
-
import * as os from "node:os";
|
|
41
40
|
import * as path from "node:path";
|
|
42
41
|
import { fileURLToPath } from "node:url";
|
|
43
42
|
|
|
@@ -150,7 +149,7 @@ async function main(): Promise<void> {
|
|
|
150
149
|
|
|
151
150
|
const cwd = process.cwd();
|
|
152
151
|
const cwdSlug = cwd.replace(/\//g, "-").replace(/^-/, "");
|
|
153
|
-
const sessionsDir = path.join(
|
|
152
|
+
const sessionsDir = path.join(CONFIG_DIR, "ashi", "history", cwdSlug, "sessions");
|
|
154
153
|
const resumeId = config.continueLast
|
|
155
154
|
? MultiSessionStore.readLastSessionId(sessionsDir, { fallbackToLatest: true })
|
|
156
155
|
: undefined;
|
|
@@ -265,9 +264,10 @@ async function main(): Promise<void> {
|
|
|
265
264
|
} else {
|
|
266
265
|
// New-session only: skip on resume so a restored transcript isn't prefixed
|
|
267
266
|
// with this. List user/installed extensions only — built-ins are always present.
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
267
|
+
const all = [...new Set(loaded)];
|
|
268
|
+
const shown = (core.bus.emitPipe("ashi:startup-extensions", { names: all }).names ?? []) as string[];
|
|
269
|
+
if (shown.length > 0) {
|
|
270
|
+
ctx.bus.emit("ui:info", { message: `extensions: ${shown.join(" · ")}` });
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { App, Renderer } from "./renderer.js";
|
|
1
|
+
import type { App, Renderer, RenderNode } from "./renderer.js";
|
|
2
2
|
import { InfoLine } from "./chat/lines.js";
|
|
3
3
|
|
|
4
4
|
export interface SelectChoice {
|
|
@@ -9,9 +9,11 @@ export interface SelectChoice {
|
|
|
9
9
|
export interface SelectOpts {
|
|
10
10
|
title?: string;
|
|
11
11
|
items: SelectChoice[];
|
|
12
|
+
body?: string[] | ((width: number) => string[]);
|
|
12
13
|
}
|
|
13
14
|
export interface ConfirmOpts {
|
|
14
15
|
title: string;
|
|
16
|
+
body?: string[] | ((width: number) => string[]);
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export interface DialogGuard {
|
|
@@ -28,6 +30,16 @@ export function createDialogs(app: App, renderer: Renderer, guard: DialogGuard):
|
|
|
28
30
|
const select = (opts: SelectOpts): Promise<string | undefined> => {
|
|
29
31
|
if (guard.isOpen() || opts.items.length === 0) return Promise.resolve(undefined);
|
|
30
32
|
return new Promise((resolve) => {
|
|
33
|
+
let bodyNode: RenderNode | null = null;
|
|
34
|
+
if (typeof opts.body === "function") {
|
|
35
|
+
const t = renderer.text();
|
|
36
|
+
t.setRenderFn(opts.body);
|
|
37
|
+
bodyNode = t.node;
|
|
38
|
+
} else if (opts.body && opts.body.length) {
|
|
39
|
+
const t = renderer.text();
|
|
40
|
+
t.setLines(opts.body);
|
|
41
|
+
bodyNode = t.node;
|
|
42
|
+
}
|
|
31
43
|
const hint = new InfoLine(renderer, opts.title ?? "↑↓ move · enter: select · esc: cancel");
|
|
32
44
|
const picker = app.createSelectList(
|
|
33
45
|
opts.items.map((c) => ({ value: c.value, label: c.label, description: c.description })),
|
|
@@ -40,6 +52,7 @@ export function createDialogs(app: App, renderer: Renderer, guard: DialogGuard):
|
|
|
40
52
|
guard.setOpen(false);
|
|
41
53
|
app.footerSlot.removeChild(picker.node);
|
|
42
54
|
app.footerSlot.removeChild(hint.node);
|
|
55
|
+
if (bodyNode) app.footerSlot.removeChild(bodyNode);
|
|
43
56
|
app.focusInput();
|
|
44
57
|
app.requestRender();
|
|
45
58
|
resolve(result);
|
|
@@ -47,6 +60,7 @@ export function createDialogs(app: App, renderer: Renderer, guard: DialogGuard):
|
|
|
47
60
|
picker.onSelect((item) => close(item.value));
|
|
48
61
|
picker.onCancel(() => close());
|
|
49
62
|
guard.setOpen(true);
|
|
63
|
+
if (bodyNode) app.footerSlot.addChild(bodyNode);
|
|
50
64
|
app.footerSlot.addChild(hint.node);
|
|
51
65
|
app.footerSlot.addChild(picker.node);
|
|
52
66
|
app.setFocus(picker.node);
|
|
@@ -57,6 +71,7 @@ export function createDialogs(app: App, renderer: Renderer, guard: DialogGuard):
|
|
|
57
71
|
const confirm = (opts: ConfirmOpts): Promise<boolean> =>
|
|
58
72
|
select({
|
|
59
73
|
title: opts.title,
|
|
74
|
+
body: opts.body,
|
|
60
75
|
items: [
|
|
61
76
|
{ value: "yes", label: "Yes" },
|
|
62
77
|
{ value: "no", label: "No" },
|
|
@@ -36,6 +36,7 @@ import type { Capture, NestedDiff } from "./capture.js";
|
|
|
36
36
|
import { execSync } from "node:child_process";
|
|
37
37
|
import { readClipboardImage } from "./clipboard-image.js";
|
|
38
38
|
import { renderDiff, detectLanguage, highlightLine } from "agent-sh/utils/diff-renderer.js";
|
|
39
|
+
import { computeDiff } from "agent-sh/utils/diff.js";
|
|
39
40
|
import { renderBoxFrame } from "agent-sh/utils/box-frame.js";
|
|
40
41
|
|
|
41
42
|
const GROUPABLE_KINDS = new Set(["read", "search"]);
|
|
@@ -217,6 +218,17 @@ export function mountAshi(
|
|
|
217
218
|
const dialogs = createDialogs(app, renderer, modalGuard);
|
|
218
219
|
ctx.define("ui:select", (opts: SelectOpts) => dialogs.select(opts));
|
|
219
220
|
ctx.define("ui:confirm", (opts: ConfirmOpts) => dialogs.confirm(opts));
|
|
221
|
+
ctx.define(
|
|
222
|
+
"ui:diff",
|
|
223
|
+
(opts: { before?: string | null; after?: string; filePath?: string; boxed?: boolean }) => {
|
|
224
|
+
const diff = computeDiff(opts.before ?? null, opts.after ?? "");
|
|
225
|
+
return buildDiffRenderer(
|
|
226
|
+
diff as Parameters<typeof buildDiffRenderer>[0],
|
|
227
|
+
opts.filePath ?? "",
|
|
228
|
+
opts.boxed !== false,
|
|
229
|
+
);
|
|
230
|
+
},
|
|
231
|
+
);
|
|
220
232
|
ctx.define("ui:input", (opts: InputOpts) => inputPrompt.prompt(opts));
|
|
221
233
|
ctx.define("ui:editor:get-text", () => input.getText());
|
|
222
234
|
ctx.define("ui:editor:set-text", (text: string) => { input.setText(text); });
|
|
@@ -306,6 +318,7 @@ export function mountAshi(
|
|
|
306
318
|
| { t: "thinking"; ctrl: ThinkingBlock }
|
|
307
319
|
| { t: "assistant"; ctrl: AssistantMessage }
|
|
308
320
|
| { t: "pair"; result: ToolResultView }
|
|
321
|
+
| { t: "user" }
|
|
309
322
|
| { t: "plain" };
|
|
310
323
|
const chatEntries: ChatEntry[] = [];
|
|
311
324
|
const appendEntry = (node: RenderNode, entry: ChatEntry): void => {
|
|
@@ -325,6 +338,14 @@ export function mountAshi(
|
|
|
325
338
|
const activeTools = new Map<string, LiveToolEntry>();
|
|
326
339
|
const groupMaxVisible = loadGroupMaxVisible();
|
|
327
340
|
|
|
341
|
+
let allExpanded = false;
|
|
342
|
+
const makeGroup = (kind: string): ToolGroup => {
|
|
343
|
+
const g = new ToolGroup(renderer, kind, groupMaxVisible);
|
|
344
|
+
g.setExpanded(allExpanded);
|
|
345
|
+
appendEntry(g.node, { t: "group", group: g });
|
|
346
|
+
return g;
|
|
347
|
+
};
|
|
348
|
+
|
|
328
349
|
let openGroup: ToolGroup | null = null;
|
|
329
350
|
const sealOpenGroup = (): void => {
|
|
330
351
|
if (openGroup) { openGroup.seal(); openGroup = null; }
|
|
@@ -464,6 +485,7 @@ export function mountAshi(
|
|
|
464
485
|
kind: args.kind,
|
|
465
486
|
rawInput: args.rawInput,
|
|
466
487
|
});
|
|
488
|
+
result.setExpanded(allExpanded);
|
|
467
489
|
return { call, result, startedAt: Date.now() };
|
|
468
490
|
};
|
|
469
491
|
|
|
@@ -559,7 +581,7 @@ export function mountAshi(
|
|
|
559
581
|
.join("")
|
|
560
582
|
: "";
|
|
561
583
|
if (raw.startsWith("[Compacted conversation summary]")) return;
|
|
562
|
-
appendEntry(renderUserMessage(stripContextWrappers(raw)), { t: "
|
|
584
|
+
appendEntry(renderUserMessage(stripContextWrappers(raw)), { t: "user" });
|
|
563
585
|
} else if (m.role === "assistant") {
|
|
564
586
|
const reasoning = readReasoning(m);
|
|
565
587
|
if (reasoning) {
|
|
@@ -579,8 +601,7 @@ export function mountAshi(
|
|
|
579
601
|
const kind = TOOL_KIND[name];
|
|
580
602
|
if (kind && GROUPABLE_KINDS.has(kind) && renderer.mountToolGroup) {
|
|
581
603
|
const mergeable = findMergeableGroup(kind);
|
|
582
|
-
const group = mergeable
|
|
583
|
-
?? (() => { const g = new ToolGroup(renderer, kind, groupMaxVisible); appendEntry(g.node, { t: "group", group: g }); return g; })();
|
|
604
|
+
const group = mergeable ?? makeGroup(kind);
|
|
584
605
|
group.addCall(id, name, detailFromArgs(tc.function.arguments));
|
|
585
606
|
if (id) toolMap.set(id, { kind: "group", group, name });
|
|
586
607
|
continue;
|
|
@@ -641,7 +662,7 @@ export function mountAshi(
|
|
|
641
662
|
bus.on("agent:query", ({ query }) => {
|
|
642
663
|
app.commitScrollback?.();
|
|
643
664
|
sealOpenGroup();
|
|
644
|
-
appendEntry(renderUserMessage(query), { t: "
|
|
665
|
+
appendEntry(renderUserMessage(query), { t: "user" });
|
|
645
666
|
activeAssistant = null;
|
|
646
667
|
app.requestRender();
|
|
647
668
|
});
|
|
@@ -724,8 +745,7 @@ export function mountAshi(
|
|
|
724
745
|
if (GROUPABLE_KINDS.has(kind) && renderer.mountToolGroup) {
|
|
725
746
|
const mergeable = findMergeableGroup(kind);
|
|
726
747
|
if (!mergeable) sealOpenGroup();
|
|
727
|
-
const group = mergeable
|
|
728
|
-
?? (() => { const g = new ToolGroup(renderer, kind, groupMaxVisible); appendEntry(g.node, { t: "group", group: g }); return g; })();
|
|
748
|
+
const group = mergeable ?? makeGroup(kind);
|
|
729
749
|
group.addCall(id, lookupName, detail);
|
|
730
750
|
openGroup = group;
|
|
731
751
|
activeTools.set(id, { kind: "group", group });
|
|
@@ -744,7 +764,13 @@ export function mountAshi(
|
|
|
744
764
|
app.requestRender();
|
|
745
765
|
});
|
|
746
766
|
|
|
747
|
-
bus.on("agent:tool-output-chunk", ({ chunk }) => {
|
|
767
|
+
bus.on("agent:tool-output-chunk", ({ chunk, toolCallId }) => {
|
|
768
|
+
const owner = toolCallId ? activeTools.get(toolCallId) : undefined;
|
|
769
|
+
if (owner?.kind === "pair") {
|
|
770
|
+
owner.pair.result.appendChunk(chunk);
|
|
771
|
+
app.requestRender();
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
748
774
|
for (const entry of [...activeTools.values()].reverse()) {
|
|
749
775
|
if (entry.kind === "pair") {
|
|
750
776
|
entry.pair.result.appendChunk(chunk);
|
|
@@ -1242,9 +1268,16 @@ export function mountAshi(
|
|
|
1242
1268
|
return { consume: true };
|
|
1243
1269
|
}
|
|
1244
1270
|
if (key.matches("ctrl+o")) {
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1271
|
+
allExpanded = !allExpanded;
|
|
1272
|
+
// Toggle only the latest turn; re-rendering the whole transcript is O(history).
|
|
1273
|
+
let start = 0;
|
|
1274
|
+
for (let i = chatEntries.length - 1; i >= 0; i--) {
|
|
1275
|
+
if (chatEntries[i]!.t === "user") { start = i; break; }
|
|
1276
|
+
}
|
|
1277
|
+
for (let i = start; i < chatEntries.length; i++) {
|
|
1278
|
+
const e = chatEntries[i]!;
|
|
1279
|
+
if (e.t === "group") e.group.setExpanded(allExpanded);
|
|
1280
|
+
else if (e.t === "pair") e.result.setExpanded(allExpanded);
|
|
1248
1281
|
}
|
|
1249
1282
|
app.requestRender();
|
|
1250
1283
|
return { consume: true };
|
|
@@ -133,7 +133,7 @@ export interface App {
|
|
|
133
133
|
export interface ToolCallView {
|
|
134
134
|
node: RenderNode;
|
|
135
135
|
setStatus(opts: { exitCode: number | null; elapsedMs: number; summary?: string }): void;
|
|
136
|
-
|
|
136
|
+
setExpanded?(expanded: boolean): void;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
export interface ToolResultView {
|
|
@@ -142,7 +142,7 @@ export interface ToolResultView {
|
|
|
142
142
|
/** Width-aware diff closure produced by the edit/write tool at finalize. */
|
|
143
143
|
setDiffRenderer(fn: (width: number) => string[]): void;
|
|
144
144
|
finalize(opts: { exitCode: number | null; summary?: string }): void;
|
|
145
|
-
|
|
145
|
+
setExpanded(expanded: boolean): void;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
export interface ToolGroupChild {
|
|
@@ -136,8 +136,9 @@ class SchemaResultComponent extends Container {
|
|
|
136
136
|
this.handle.dispatch("status", { ...opts, elapsedMs: 0 });
|
|
137
137
|
HANDLES.delete(this.handle.toolCallId);
|
|
138
138
|
}
|
|
139
|
-
|
|
140
|
-
this.handle.cell.env
|
|
139
|
+
setExpanded(expanded: boolean): void {
|
|
140
|
+
if (this.handle.cell.env.expanded === expanded) return;
|
|
141
|
+
this.handle.cell.env = { ...this.handle.cell.env, expanded };
|
|
141
142
|
this.repaint();
|
|
142
143
|
this.handle.cell.callView?.repaint();
|
|
143
144
|
}
|
|
@@ -198,6 +199,6 @@ export function mountResult<S>(model: RenderModel<S>, args: MountArgs, env: Moun
|
|
|
198
199
|
appendChunk: (chunk) => comp.appendChunk(chunk),
|
|
199
200
|
setDiffRenderer: (fn) => comp.setDiffRenderer(fn),
|
|
200
201
|
finalize: (opts) => comp.finalize(opts),
|
|
201
|
-
|
|
202
|
+
setExpanded: (expanded) => comp.setExpanded(expanded),
|
|
202
203
|
};
|
|
203
204
|
}
|
|
@@ -214,10 +214,11 @@ function renderStream(buffer: string, env: Env): string {
|
|
|
214
214
|
const lines = display.split("\n");
|
|
215
215
|
const trimmed = lines.slice(-env.previewLines).join("\n");
|
|
216
216
|
const remaining = Math.max(0, lines.length - env.previewLines);
|
|
217
|
+
// The preview is the tail, so the hidden lines come before it — note goes above.
|
|
217
218
|
const overflow = remaining > 0
|
|
218
|
-
?
|
|
219
|
+
? `${theme.fg("muted", `... (${remaining} earlier ${remaining === 1 ? "line" : "lines"})`)}\n`
|
|
219
220
|
: "";
|
|
220
|
-
return `${theme.fg("toolOutput", trimmed)}
|
|
221
|
+
return `${overflow}${theme.fg("toolOutput", trimmed)}`;
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
function lineCountHint(buffer: string): string {
|
|
@@ -10,6 +10,13 @@ export type { StatusSegment } from "./status-footer.js";
|
|
|
10
10
|
|
|
11
11
|
export type NoticeLevel = "info" | "warn" | "error" | "success";
|
|
12
12
|
|
|
13
|
+
export interface DiffOpts {
|
|
14
|
+
before?: string | null;
|
|
15
|
+
after?: string;
|
|
16
|
+
filePath?: string;
|
|
17
|
+
boxed?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
export interface Contribution {
|
|
14
21
|
/** Re-pull this surface (call after the content it depends on changes). */
|
|
15
22
|
refresh(): void;
|
|
@@ -26,6 +33,7 @@ export interface Ui {
|
|
|
26
33
|
notify(message: string, level?: NoticeLevel): void;
|
|
27
34
|
select(opts: SelectOpts): Promise<string | undefined>;
|
|
28
35
|
confirm(opts: ConfirmOpts): Promise<boolean>;
|
|
36
|
+
diff(opts: DiffOpts): (width: number) => string[];
|
|
29
37
|
input(opts?: InputOpts): Promise<string | undefined>;
|
|
30
38
|
getEditorText(): string;
|
|
31
39
|
setEditorText(text: string): void;
|
|
@@ -50,6 +58,9 @@ export function createUi(ctx: ExtensionContext): Ui {
|
|
|
50
58
|
if (!has("ui:confirm")) return Promise.resolve(false);
|
|
51
59
|
return ctx.call("ui:confirm", opts) as Promise<boolean>;
|
|
52
60
|
},
|
|
61
|
+
diff(opts) {
|
|
62
|
+
return has("ui:diff") ? (ctx.call("ui:diff", opts) as (width: number) => string[]) : (() => []);
|
|
63
|
+
},
|
|
53
64
|
input(opts = {}) {
|
|
54
65
|
if (!has("ui:input")) return Promise.resolve(undefined);
|
|
55
66
|
return ctx.call("ui:input", opts) as Promise<string | undefined>;
|
package/package.json
CHANGED
package/src/agent/agent-loop.ts
CHANGED
|
@@ -623,14 +623,15 @@ export class AgentLoop implements AgentBackend {
|
|
|
623
623
|
streamedCalls: ProtocolPendingToolCall[];
|
|
624
624
|
}) => this.toolProtocol.extractToolCalls(args.text, args.streamedCalls));
|
|
625
625
|
|
|
626
|
-
// System prompt: static identity + behavioral instructions.
|
|
627
|
-
//
|
|
628
|
-
// advise system-prompt:frontend to describe their
|
|
629
|
-
// prompt, or advise
|
|
626
|
+
// System prompt: static identity + behavioral instructions. Extensions can
|
|
627
|
+
// use registerInstruction() for a managed section, advise system-prompt:identity
|
|
628
|
+
// to replace the kernel identity, advise system-prompt:frontend to describe their
|
|
629
|
+
// surface high in the prompt, or advise system-prompt:build directly for full control.
|
|
630
|
+
h.define("system-prompt:identity", () => STATIC_IDENTITY);
|
|
630
631
|
h.define("system-prompt:build", () => {
|
|
631
632
|
// The active frontend's surface goes right after the identity; omitted if none.
|
|
632
633
|
const frontend = ((this.handlers.call("system-prompt:frontend") as string) ?? "").trim();
|
|
633
|
-
const parts: string[] = [
|
|
634
|
+
const parts: string[] = [this.handlers.call("system-prompt:identity") as string];
|
|
634
635
|
if (frontend) parts.push(frontend);
|
|
635
636
|
parts.push(STATIC_GUIDE);
|
|
636
637
|
|
|
@@ -1123,7 +1124,7 @@ export class AgentLoop implements AgentBackend {
|
|
|
1123
1124
|
// Execute via handler — extensions can advise to add safe-mode,
|
|
1124
1125
|
// logging, metrics, custom permission policies, etc.
|
|
1125
1126
|
const defaultOnChunk = (chunk: string) => {
|
|
1126
|
-
this.bus.emit("agent:tool-output-chunk", { chunk });
|
|
1127
|
+
this.bus.emit("agent:tool-output-chunk", { chunk, toolCallId: tc.id });
|
|
1127
1128
|
};
|
|
1128
1129
|
const result = await this.handlers.call(
|
|
1129
1130
|
"tool:execute",
|
|
@@ -1410,8 +1411,10 @@ export class AgentLoop implements AgentBackend {
|
|
|
1410
1411
|
const reasoningDetailsByIndex = new Map<number, Record<string, unknown>>();
|
|
1411
1412
|
const pendingToolCalls: PendingToolCall[] = [];
|
|
1412
1413
|
|
|
1413
|
-
// Tool protocol controls what goes in the API tools param vs dynamic context
|
|
1414
|
-
|
|
1414
|
+
// Tool protocol controls what goes in the API tools param vs dynamic context.
|
|
1415
|
+
// agent:tools:visible is a filter point on the assembled list — distinct from
|
|
1416
|
+
// getTools(), which other code (e.g. tool bridges) needs unfiltered.
|
|
1417
|
+
const toolView = this.bus.emitPipe("agent:tools:visible", { tools: this.getTools() }).tools;
|
|
1415
1418
|
const apiTools = this.toolProtocol.getApiTools(toolView);
|
|
1416
1419
|
const toolPrompt = this.toolProtocol.getToolPrompt(toolView);
|
|
1417
1420
|
|
|
@@ -1427,7 +1430,7 @@ export class AgentLoop implements AgentBackend {
|
|
|
1427
1430
|
|
|
1428
1431
|
// Stream filter strips tool tags from display (inline mode only)
|
|
1429
1432
|
const streamFilter = this.toolProtocol.createStreamFilter(
|
|
1430
|
-
|
|
1433
|
+
toolView.map((t) => t.name),
|
|
1431
1434
|
);
|
|
1432
1435
|
|
|
1433
1436
|
const requestParams = {
|
package/src/agent/events.ts
CHANGED
|
@@ -29,6 +29,8 @@ declare module "../core/event-bus.js" {
|
|
|
29
29
|
|
|
30
30
|
"agent:info": AgentIdentity;
|
|
31
31
|
"agent:tools": { tools: ToolDefinition[] };
|
|
32
|
+
/** Filter point: the assembled tool list as the model will see it, after getTools(). */
|
|
33
|
+
"agent:tools:visible": { tools: ToolDefinition[] };
|
|
32
34
|
"agent:instructions": { instructions: Array<{ name: string; text: string }> };
|
|
33
35
|
"agent:skills": { skills: Array<{ name: string; description: string; filePath: string }> };
|
|
34
36
|
|
|
@@ -86,7 +88,7 @@ declare module "../core/event-bus.js" {
|
|
|
86
88
|
kind?: string;
|
|
87
89
|
resultDisplay?: ToolResultDisplay;
|
|
88
90
|
};
|
|
89
|
-
"agent:tool-output-chunk": { chunk: string };
|
|
91
|
+
"agent:tool-output-chunk": { chunk: string; toolCallId?: string };
|
|
90
92
|
|
|
91
93
|
"tool:interactive-start": Record<string, never>;
|
|
92
94
|
"tool:interactive-end": Record<string, never>;
|
package/src/agent/index.ts
CHANGED
|
@@ -116,10 +116,14 @@ export default function agentBackend(ctx: ExtensionContext): void {
|
|
|
116
116
|
|
|
117
117
|
// Settings overlay — fields here win over contributing extensions' payloads.
|
|
118
118
|
const settingsProviders = new Map<string, ResolvedProvider>();
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
const refreshSettingsProviders = () => {
|
|
120
|
+
settingsProviders.clear();
|
|
121
|
+
for (const name of getProviderNames()) {
|
|
122
|
+
const p = resolveProvider(name);
|
|
123
|
+
if (p) settingsProviders.set(name, p);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
refreshSettingsProviders();
|
|
123
127
|
|
|
124
128
|
const providerHooks = new Map<string, {
|
|
125
129
|
reasoningParams?: (level: string, model?: string) => Record<string, unknown>;
|
|
@@ -368,6 +372,7 @@ export default function agentBackend(ctx: ExtensionContext): void {
|
|
|
368
372
|
let loadedExtensionNames: string[] = [];
|
|
369
373
|
|
|
370
374
|
bus.on("agent:providers:changed", () => {
|
|
375
|
+
refreshSettingsProviders();
|
|
371
376
|
resolvedProviders = computeResolvedProviders();
|
|
372
377
|
if (!resolved) return;
|
|
373
378
|
bus.emit("agent:models-changed", {});
|
package/src/agent/subagent.ts
CHANGED
|
@@ -157,7 +157,7 @@ export async function runSubagent(opts: SubagentOptions): Promise<string> {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
const onChunk = bus && tool.showOutput !== false
|
|
160
|
-
? (chunk: string) => { bus.emit("agent:tool-output-chunk", { chunk }); }
|
|
160
|
+
? (chunk: string) => { bus.emit("agent:tool-output-chunk", { chunk, toolCallId: tc.id }); }
|
|
161
161
|
: undefined;
|
|
162
162
|
|
|
163
163
|
const result = await tool.execute(args, onChunk);
|