agent-sh 0.3.0 → 0.3.1
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/acp-client.js +38 -22
- package/dist/core.js +10 -0
- package/dist/event-bus.d.ts +22 -0
- package/dist/event-bus.js +10 -0
- package/dist/extensions/tui-renderer.d.ts +1 -1
- package/dist/extensions/tui-renderer.js +304 -160
- package/dist/settings.d.ts +11 -0
- package/dist/settings.js +19 -1
- package/dist/types.d.ts +15 -0
- package/dist/utils/box-frame.js +2 -1
- package/dist/utils/diff-renderer.js +1 -1
- package/dist/utils/frame-renderer.d.ts +26 -0
- package/dist/utils/frame-renderer.js +76 -0
- package/dist/utils/handler-registry.d.ts +41 -0
- package/dist/utils/handler-registry.js +52 -0
- package/dist/utils/markdown.d.ts +15 -6
- package/dist/utils/markdown.js +106 -67
- package/dist/utils/output-writer.d.ts +22 -0
- package/dist/utils/output-writer.js +29 -0
- package/dist/utils/stream-transform.d.ts +70 -0
- package/dist/utils/stream-transform.js +229 -0
- package/dist/utils/tool-display.d.ts +9 -8
- package/dist/utils/tool-display.js +26 -31
- package/examples/extensions/latex-images.ts +142 -0
- package/package.json +10 -2
package/dist/acp-client.js
CHANGED
|
@@ -21,7 +21,7 @@ export class AcpClient {
|
|
|
21
21
|
terminalDonePromises = new Map();
|
|
22
22
|
terminalCounter = 0;
|
|
23
23
|
fileWatcher;
|
|
24
|
-
pendingToolCalls = new Map();
|
|
24
|
+
pendingToolCalls = new Map();
|
|
25
25
|
autoCancelled = false;
|
|
26
26
|
pendingToolCounter = 0;
|
|
27
27
|
agentInfo = null;
|
|
@@ -146,19 +146,15 @@ export class AcpClient {
|
|
|
146
146
|
const contextBlock = this.contextManager.getContext();
|
|
147
147
|
try {
|
|
148
148
|
this.log("sending prompt...");
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
],
|
|
159
|
-
}),
|
|
160
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error(`Prompt timeout after ${promptTimeoutMs}ms`)), promptTimeoutMs)),
|
|
161
|
-
]);
|
|
149
|
+
const response = await this.connection.prompt({
|
|
150
|
+
sessionId: this.sessionId,
|
|
151
|
+
prompt: [
|
|
152
|
+
{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: contextBlock + "\n" + query,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
});
|
|
162
158
|
this.log(`prompt resolved: stopReason=${response.stopReason}`);
|
|
163
159
|
if (response.stopReason === "cancelled") {
|
|
164
160
|
cancelled = true;
|
|
@@ -176,7 +172,7 @@ export class AcpClient {
|
|
|
176
172
|
finally {
|
|
177
173
|
this.log("restoring shell mode");
|
|
178
174
|
if (!cancelled) {
|
|
179
|
-
this.bus.
|
|
175
|
+
this.bus.emitTransform("agent:response-done", {
|
|
180
176
|
response: this.currentResponseText,
|
|
181
177
|
});
|
|
182
178
|
}
|
|
@@ -327,8 +323,15 @@ export class AcpClient {
|
|
|
327
323
|
createClientHandler() {
|
|
328
324
|
return {
|
|
329
325
|
// Required: handle session update notifications (streaming)
|
|
326
|
+
// Errors must not propagate — the ACP SDK returns them as error
|
|
327
|
+
// responses to the agent, which can stall the stream.
|
|
330
328
|
sessionUpdate: async (params) => {
|
|
331
|
-
|
|
329
|
+
try {
|
|
330
|
+
this.handleSessionUpdate(params);
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
this.log(`Error in sessionUpdate handler: ${err instanceof Error ? err.stack : err}`);
|
|
334
|
+
}
|
|
332
335
|
},
|
|
333
336
|
// Required: handle permission requests
|
|
334
337
|
requestPermission: async (params) => {
|
|
@@ -370,40 +373,53 @@ export class AcpClient {
|
|
|
370
373
|
const content = update.content;
|
|
371
374
|
if (content.type === "text") {
|
|
372
375
|
this.currentResponseText += content.text;
|
|
373
|
-
this.bus.
|
|
376
|
+
this.bus.emitTransform("agent:response-chunk", { text: content.text });
|
|
374
377
|
}
|
|
375
378
|
break;
|
|
376
379
|
}
|
|
377
380
|
case "agent_thought_chunk": {
|
|
378
381
|
const thought = update.content;
|
|
379
382
|
if (thought.type === "text" && thought.text) {
|
|
380
|
-
this.bus.
|
|
383
|
+
this.bus.emitTransform("agent:thinking-chunk", { text: thought.text });
|
|
381
384
|
}
|
|
382
385
|
break;
|
|
383
386
|
}
|
|
384
387
|
case "tool_call": {
|
|
385
388
|
const toolId = update.toolCallId || `tool-${this.pendingToolCounter++}`;
|
|
386
|
-
|
|
387
|
-
this.bus.emit("agent:tool-started", {
|
|
389
|
+
const payload = {
|
|
388
390
|
title: update.title,
|
|
389
391
|
toolCallId: toolId,
|
|
390
392
|
kind: update.kind ?? undefined,
|
|
391
393
|
locations: update.locations?.map((l) => ({ path: l.path, line: l.line })),
|
|
392
394
|
rawInput: update.rawInput,
|
|
395
|
+
};
|
|
396
|
+
const defer = this.pendingToolCalls.size > 0;
|
|
397
|
+
this.pendingToolCalls.set(toolId, {
|
|
398
|
+
title: update.title ?? "",
|
|
399
|
+
deferredPayload: defer ? payload : undefined,
|
|
393
400
|
});
|
|
401
|
+
if (!defer) {
|
|
402
|
+
this.bus.emit("agent:tool-started", payload);
|
|
403
|
+
}
|
|
394
404
|
break;
|
|
395
405
|
}
|
|
396
406
|
case "tool_call_update": {
|
|
397
407
|
const toolId = update.toolCallId;
|
|
398
|
-
const
|
|
408
|
+
const toolInfo = toolId ? this.pendingToolCalls.get(toolId) : undefined;
|
|
409
|
+
const toolTitle = toolInfo?.title;
|
|
399
410
|
if (update.status === "completed" || update.status === "failed") {
|
|
411
|
+
// Emit deferred tool-started before output (parallel tools)
|
|
412
|
+
if (toolInfo?.deferredPayload) {
|
|
413
|
+
this.bus.emit("agent:tool-started", toolInfo.deferredPayload);
|
|
414
|
+
toolInfo.deferredPayload = undefined;
|
|
415
|
+
}
|
|
400
416
|
// Show content only on final status. Skip tools whose output the
|
|
401
417
|
// user already sees (user_shell → PTY) or is agent-only (shell_recall).
|
|
402
418
|
const skipOutput = toolTitle === "user_shell" || toolTitle === "shell_recall";
|
|
403
419
|
if (!skipOutput && update.content && Array.isArray(update.content)) {
|
|
404
420
|
for (const block of update.content) {
|
|
405
421
|
if (block.type === "content" && block.content?.type === "text" && block.content.text) {
|
|
406
|
-
this.bus.
|
|
422
|
+
this.bus.emitTransform("agent:tool-output-chunk", { chunk: block.content.text });
|
|
407
423
|
}
|
|
408
424
|
}
|
|
409
425
|
}
|
package/dist/core.js
CHANGED
|
@@ -20,11 +20,15 @@ import { EventBus } from "./event-bus.js";
|
|
|
20
20
|
import { ContextManager } from "./context-manager.js";
|
|
21
21
|
import { AcpClient } from "./acp-client.js";
|
|
22
22
|
import { setPalette } from "./utils/palette.js";
|
|
23
|
+
import * as streamTransform from "./utils/stream-transform.js";
|
|
24
|
+
import * as settingsMod from "./settings.js";
|
|
25
|
+
import { HandlerRegistry } from "./utils/handler-registry.js";
|
|
23
26
|
// Re-export types that library consumers need
|
|
24
27
|
export { EventBus } from "./event-bus.js";
|
|
25
28
|
export { palette, setPalette, resetPalette } from "./utils/palette.js";
|
|
26
29
|
export function createCore(config) {
|
|
27
30
|
const bus = new EventBus();
|
|
31
|
+
const handlers = new HandlerRegistry();
|
|
28
32
|
const contextManager = new ContextManager(bus);
|
|
29
33
|
const client = new AcpClient({ bus, contextManager, config });
|
|
30
34
|
let connected = false;
|
|
@@ -67,6 +71,12 @@ export function createCore(config) {
|
|
|
67
71
|
getAcpClient: () => client,
|
|
68
72
|
quit: opts.quit,
|
|
69
73
|
setPalette,
|
|
74
|
+
createBlockTransform: (o) => streamTransform.createBlockTransform(bus, o),
|
|
75
|
+
createFencedBlockTransform: (o) => streamTransform.createFencedBlockTransform(bus, o),
|
|
76
|
+
getExtensionSettings: settingsMod.getExtensionSettings,
|
|
77
|
+
define: (name, fn) => handlers.define(name, fn),
|
|
78
|
+
advise: (name, wrapper) => handlers.advise(name, wrapper),
|
|
79
|
+
call: (name, ...args) => handlers.call(name, ...args),
|
|
70
80
|
};
|
|
71
81
|
},
|
|
72
82
|
kill() {
|
package/dist/event-bus.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface ShellEvents {
|
|
|
32
32
|
};
|
|
33
33
|
"agent:response-chunk": {
|
|
34
34
|
text: string;
|
|
35
|
+
blocks?: ContentBlock[];
|
|
35
36
|
};
|
|
36
37
|
"agent:response-done": {
|
|
37
38
|
response: string;
|
|
@@ -126,6 +127,20 @@ export interface ShellEvents {
|
|
|
126
127
|
}[];
|
|
127
128
|
};
|
|
128
129
|
}
|
|
130
|
+
export type ContentBlock = {
|
|
131
|
+
type: "text";
|
|
132
|
+
text: string;
|
|
133
|
+
} | {
|
|
134
|
+
type: "code-block";
|
|
135
|
+
language: string;
|
|
136
|
+
code: string;
|
|
137
|
+
} | {
|
|
138
|
+
type: "image";
|
|
139
|
+
data: Buffer;
|
|
140
|
+
} | {
|
|
141
|
+
type: "raw";
|
|
142
|
+
escape: string;
|
|
143
|
+
};
|
|
129
144
|
type Listener<T> = (payload: T) => void;
|
|
130
145
|
type PipeListener<T> = (payload: T) => T;
|
|
131
146
|
type AsyncPipeListener<T> = (payload: T) => T | Promise<T>;
|
|
@@ -145,6 +160,13 @@ export declare class EventBus {
|
|
|
145
160
|
off<K extends keyof ShellEvents>(event: K, fn: Listener<ShellEvents[K]>): void;
|
|
146
161
|
/** Emit a fire-and-forget event. */
|
|
147
162
|
emit<K extends keyof ShellEvents>(event: K, payload: ShellEvents[K]): void;
|
|
163
|
+
/**
|
|
164
|
+
* Transform-then-notify: run the payload through any registered pipe
|
|
165
|
+
* listeners (transforms), then emit the final result to regular `on`
|
|
166
|
+
* listeners (renderers). This enables content pipelines where extensions
|
|
167
|
+
* modify data (e.g. render LaTeX → terminal image) before renderers see it.
|
|
168
|
+
*/
|
|
169
|
+
emitTransform<K extends keyof ShellEvents>(event: K, payload: ShellEvents[K]): void;
|
|
148
170
|
/** Register a transform listener for a pipeline event. */
|
|
149
171
|
onPipe<K extends keyof ShellEvents>(event: K, fn: PipeListener<ShellEvents[K]>): void;
|
|
150
172
|
/**
|
package/dist/event-bus.js
CHANGED
|
@@ -21,6 +21,16 @@ export class EventBus {
|
|
|
21
21
|
emit(event, payload) {
|
|
22
22
|
this.emitter.emit(event, payload);
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Transform-then-notify: run the payload through any registered pipe
|
|
26
|
+
* listeners (transforms), then emit the final result to regular `on`
|
|
27
|
+
* listeners (renderers). This enables content pipelines where extensions
|
|
28
|
+
* modify data (e.g. render LaTeX → terminal image) before renderers see it.
|
|
29
|
+
*/
|
|
30
|
+
emitTransform(event, payload) {
|
|
31
|
+
const transformed = this.emitPipe(event, payload);
|
|
32
|
+
this.emitter.emit(event, transformed);
|
|
33
|
+
}
|
|
24
34
|
/** Register a transform listener for a pipeline event. */
|
|
25
35
|
onPipe(event, fn) {
|
|
26
36
|
let listeners = this.pipeListeners.get(event);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ExtensionContext } from "../types.js";
|
|
2
|
-
export default function activate(
|
|
2
|
+
export default function activate(ctx: ExtensionContext): void;
|