agent-sh 0.15.2 → 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.
@@ -967,7 +967,7 @@ export class AgentLoop {
967
967
  // Execute via handler — extensions can advise to add safe-mode,
968
968
  // logging, metrics, custom permission policies, etc.
969
969
  const defaultOnChunk = (chunk) => {
970
- this.bus.emit("agent:tool-output-chunk", { chunk });
970
+ this.bus.emit("agent:tool-output-chunk", { chunk, toolCallId: tc.id });
971
971
  };
972
972
  const result = await this.handlers.call("tool:execute", { name: tc.name, id: tc.id, args, tool, onChunk: defaultOnChunk,
973
973
  batchIndex, batchTotal: batchTotal > 1 ? batchTotal : undefined,
@@ -135,6 +135,7 @@ declare module "../core/event-bus.js" {
135
135
  };
136
136
  "agent:tool-output-chunk": {
137
137
  chunk: string;
138
+ toolCallId?: string;
138
139
  };
139
140
  "tool:interactive-start": Record<string, never>;
140
141
  "tool:interactive-end": Record<string, never>;
@@ -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
- for (const name of getProviderNames()) {
93
- const p = resolveProvider(name);
94
- if (p)
95
- settingsProviders.set(name, p);
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;
@@ -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) {
@@ -318,6 +318,7 @@ export function mountAshi(
318
318
  | { t: "thinking"; ctrl: ThinkingBlock }
319
319
  | { t: "assistant"; ctrl: AssistantMessage }
320
320
  | { t: "pair"; result: ToolResultView }
321
+ | { t: "user" }
321
322
  | { t: "plain" };
322
323
  const chatEntries: ChatEntry[] = [];
323
324
  const appendEntry = (node: RenderNode, entry: ChatEntry): void => {
@@ -580,7 +581,7 @@ export function mountAshi(
580
581
  .join("")
581
582
  : "";
582
583
  if (raw.startsWith("[Compacted conversation summary]")) return;
583
- appendEntry(renderUserMessage(stripContextWrappers(raw)), { t: "plain" });
584
+ appendEntry(renderUserMessage(stripContextWrappers(raw)), { t: "user" });
584
585
  } else if (m.role === "assistant") {
585
586
  const reasoning = readReasoning(m);
586
587
  if (reasoning) {
@@ -661,7 +662,7 @@ export function mountAshi(
661
662
  bus.on("agent:query", ({ query }) => {
662
663
  app.commitScrollback?.();
663
664
  sealOpenGroup();
664
- appendEntry(renderUserMessage(query), { t: "plain" });
665
+ appendEntry(renderUserMessage(query), { t: "user" });
665
666
  activeAssistant = null;
666
667
  app.requestRender();
667
668
  });
@@ -763,7 +764,13 @@ export function mountAshi(
763
764
  app.requestRender();
764
765
  });
765
766
 
766
- 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
+ }
767
774
  for (const entry of [...activeTools.values()].reverse()) {
768
775
  if (entry.kind === "pair") {
769
776
  entry.pair.result.appendChunk(chunk);
@@ -1262,7 +1269,13 @@ export function mountAshi(
1262
1269
  }
1263
1270
  if (key.matches("ctrl+o")) {
1264
1271
  allExpanded = !allExpanded;
1265
- for (const e of chatEntries) {
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]!;
1266
1279
  if (e.t === "group") e.group.setExpanded(allExpanded);
1267
1280
  else if (e.t === "pair") e.result.setExpanded(allExpanded);
1268
1281
  }
@@ -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
- ? `\n${theme.fg("muted", `... (${remaining} more ${remaining === 1 ? "line" : "lines"})`)}`
219
+ ? `${theme.fg("muted", `... (${remaining} earlier ${remaining === 1 ? "line" : "lines"})`)}\n`
219
220
  : "";
220
- return `${theme.fg("toolOutput", trimmed)}${overflow}`;
221
+ return `${overflow}${theme.fg("toolOutput", trimmed)}`;
221
222
  }
222
223
 
223
224
  function lineCountHint(buffer: string): string {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sh",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "description": "A composable agent runtime — pair any frontend with any agent backend over one shared extension layer",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -1124,7 +1124,7 @@ export class AgentLoop implements AgentBackend {
1124
1124
  // Execute via handler — extensions can advise to add safe-mode,
1125
1125
  // logging, metrics, custom permission policies, etc.
1126
1126
  const defaultOnChunk = (chunk: string) => {
1127
- this.bus.emit("agent:tool-output-chunk", { chunk });
1127
+ this.bus.emit("agent:tool-output-chunk", { chunk, toolCallId: tc.id });
1128
1128
  };
1129
1129
  const result = await this.handlers.call(
1130
1130
  "tool:execute",
@@ -88,7 +88,7 @@ declare module "../core/event-bus.js" {
88
88
  kind?: string;
89
89
  resultDisplay?: ToolResultDisplay;
90
90
  };
91
- "agent:tool-output-chunk": { chunk: string };
91
+ "agent:tool-output-chunk": { chunk: string; toolCallId?: string };
92
92
 
93
93
  "tool:interactive-start": Record<string, never>;
94
94
  "tool:interactive-end": Record<string, never>;
@@ -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
- for (const name of getProviderNames()) {
120
- const p = resolveProvider(name);
121
- if (p) settingsProviders.set(name, p);
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", {});
@@ -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);