paseo-acp-agy 1.2.11 → 1.2.13
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-server.d.ts +1 -0
- package/dist/acp-server.js +70 -15
- package/dist/antigravity-process.js +29 -3
- package/dist/mcp-bridge.d.ts +27 -0
- package/dist/mcp-bridge.js +62 -0
- package/dist/protocol.d.ts +1 -1
- package/dist/protocol.js +11 -6
- package/dist/tool-formatter.d.ts +37 -0
- package/dist/tool-formatter.js +219 -0
- package/dist/transcript.js +13 -14
- package/package.json +1 -1
package/dist/acp-server.d.ts
CHANGED
package/dist/acp-server.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import readline from "node:readline";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
3
|
import { logger } from "./logger.js";
|
|
4
|
-
import { ACP_METHODS, AVAILABLE_MODES, AVAILABLE_PERMISSIONS, fetchAvailableModels, buildConfigOptionsForSession, extractPromptText,
|
|
4
|
+
import { ACP_METHODS, AVAILABLE_MODES, AVAILABLE_PERMISSIONS, fetchAvailableModels, buildConfigOptionsForSession, extractPromptText, extractImageMarkdownLink, isNarrationText, calculateUsageCostUsd, roundUsageCostUsd, getModelContextWindow, fetchAntigravityUsage, } from "./protocol.js";
|
|
5
5
|
import { SessionManager } from "./session.js";
|
|
6
6
|
import { executeSlashCommand, AVAILABLE_SLASH_COMMANDS } from "./slash-commands.js";
|
|
7
7
|
import { getShortVersion } from "./version.js";
|
|
8
8
|
import { resolveDefaultAgyBinary } from "./antigravity-process.js";
|
|
9
9
|
import { loadConversationTranscript } from "./transcript.js";
|
|
10
|
+
import { normalizeToolCall, normalizeToolResult, extractPlanEntries } from "./tool-formatter.js";
|
|
11
|
+
import { McpBridge } from "./mcp-bridge.js";
|
|
10
12
|
function splitModelAndEffort(modelInput) {
|
|
11
13
|
if (!modelInput)
|
|
12
14
|
return {};
|
|
@@ -20,6 +22,7 @@ function splitModelAndEffort(modelInput) {
|
|
|
20
22
|
}
|
|
21
23
|
export class ACPServer extends EventEmitter {
|
|
22
24
|
sessionManager;
|
|
25
|
+
mcpBridge;
|
|
23
26
|
input;
|
|
24
27
|
output;
|
|
25
28
|
binaryPath;
|
|
@@ -31,6 +34,7 @@ export class ACPServer extends EventEmitter {
|
|
|
31
34
|
this.input = options.input || process.stdin;
|
|
32
35
|
this.output = options.output || process.stdout;
|
|
33
36
|
this.binaryPath = options.binaryPath || resolveDefaultAgyBinary();
|
|
37
|
+
this.mcpBridge = new McpBridge(this.binaryPath);
|
|
34
38
|
this.skipNarration =
|
|
35
39
|
options.skipNarration ?? (process.env.AGY_ACP_SKIP_NARRATION === "true");
|
|
36
40
|
this.sessionManager =
|
|
@@ -223,6 +227,10 @@ export class ACPServer extends EventEmitter {
|
|
|
223
227
|
const requestedModel = typeof params.model === "string" ? params.model : undefined;
|
|
224
228
|
const parsedModel = splitModelAndEffort(requestedModel);
|
|
225
229
|
const mode = typeof params.mode === "string" ? params.mode : undefined;
|
|
230
|
+
const mcpServers = params.mcpServers;
|
|
231
|
+
if (mcpServers) {
|
|
232
|
+
void this.mcpBridge.syncServers(mcpServers);
|
|
233
|
+
}
|
|
226
234
|
const session = this.sessionManager.createSession({
|
|
227
235
|
cwd,
|
|
228
236
|
model: parsedModel.model,
|
|
@@ -257,6 +265,10 @@ export class ACPServer extends EventEmitter {
|
|
|
257
265
|
this.sendError(id, -32602, "sessionId is required");
|
|
258
266
|
break;
|
|
259
267
|
}
|
|
268
|
+
const mcpServers = params.mcpServers;
|
|
269
|
+
if (mcpServers) {
|
|
270
|
+
void this.mcpBridge.syncServers(mcpServers);
|
|
271
|
+
}
|
|
260
272
|
let session = this.sessionManager.getSession(sessionId);
|
|
261
273
|
try {
|
|
262
274
|
if (!session) {
|
|
@@ -509,6 +521,17 @@ export class ACPServer extends EventEmitter {
|
|
|
509
521
|
content: { type: "text", text: step.text_delta },
|
|
510
522
|
},
|
|
511
523
|
});
|
|
524
|
+
// Live Plan / Todo checklist tracker
|
|
525
|
+
const planEntries = extractPlanEntries(step.text_delta);
|
|
526
|
+
if (planEntries) {
|
|
527
|
+
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
528
|
+
sessionId: session.id,
|
|
529
|
+
update: {
|
|
530
|
+
sessionUpdate: "plan",
|
|
531
|
+
entries: planEntries,
|
|
532
|
+
},
|
|
533
|
+
});
|
|
534
|
+
}
|
|
512
535
|
}
|
|
513
536
|
else if (step.step_type === "thought" && step.text_delta) {
|
|
514
537
|
hasSentNonNarration = true;
|
|
@@ -522,29 +545,40 @@ export class ACPServer extends EventEmitter {
|
|
|
522
545
|
}
|
|
523
546
|
else if (step.step_type === "tool" && step.tool_info) {
|
|
524
547
|
hasSentNonNarration = true;
|
|
525
|
-
const toolCallId = `tool_${step.step_index}`;
|
|
526
548
|
const toolName = step.tool_name || step.tool_info.name || "tool";
|
|
527
|
-
const
|
|
549
|
+
const toolCall = normalizeToolCall(toolName, step.tool_info.parameters || {}, step.step_index);
|
|
528
550
|
if (step.state === "ACTIVE") {
|
|
529
551
|
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
530
552
|
sessionId: session.id,
|
|
531
553
|
update: {
|
|
532
554
|
sessionUpdate: "tool_call",
|
|
533
|
-
toolCallId,
|
|
534
|
-
title:
|
|
535
|
-
kind:
|
|
536
|
-
|
|
555
|
+
toolCallId: toolCall.toolCallId,
|
|
556
|
+
title: toolCall.title,
|
|
557
|
+
kind: toolCall.kind,
|
|
558
|
+
...(toolCall.locations ? { locations: toolCall.locations } : {}),
|
|
559
|
+
rawInput: toolCall.rawInput,
|
|
537
560
|
},
|
|
538
561
|
});
|
|
562
|
+
if (toolCall.questionCard) {
|
|
563
|
+
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
564
|
+
sessionId: session.id,
|
|
565
|
+
update: {
|
|
566
|
+
sessionUpdate: "agent_message_chunk",
|
|
567
|
+
content: { type: "text", text: `\n\n${toolCall.questionCard}\n\n` },
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
}
|
|
539
571
|
}
|
|
540
572
|
else if (step.state === "DONE") {
|
|
573
|
+
const toolRes = normalizeToolResult(toolName, step.tool_info.parameters || {}, step.tool_info.output);
|
|
541
574
|
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
542
575
|
sessionId: session.id,
|
|
543
576
|
update: {
|
|
544
577
|
sessionUpdate: "tool_call_update",
|
|
545
|
-
toolCallId,
|
|
546
|
-
status:
|
|
547
|
-
rawOutput:
|
|
578
|
+
toolCallId: toolCall.toolCallId,
|
|
579
|
+
status: toolRes.status,
|
|
580
|
+
rawOutput: toolRes.rawOutput,
|
|
581
|
+
...(toolRes.content ? { content: toolRes.content } : {}),
|
|
548
582
|
},
|
|
549
583
|
});
|
|
550
584
|
const imageMarkdown = extractImageMarkdownLink(toolName, step.tool_info.output, step.tool_info.parameters);
|
|
@@ -559,13 +593,14 @@ export class ACPServer extends EventEmitter {
|
|
|
559
593
|
}
|
|
560
594
|
}
|
|
561
595
|
else if (step.state === "ERROR") {
|
|
596
|
+
const toolRes = normalizeToolResult(toolName, step.tool_info.parameters || {}, null, step.tool_info.error || { message: "Tool execution error" });
|
|
562
597
|
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
563
598
|
sessionId: session.id,
|
|
564
599
|
update: {
|
|
565
600
|
sessionUpdate: "tool_call_update",
|
|
566
|
-
toolCallId,
|
|
601
|
+
toolCallId: toolCall.toolCallId,
|
|
567
602
|
status: "failed",
|
|
568
|
-
rawOutput:
|
|
603
|
+
rawOutput: toolRes.rawOutput,
|
|
569
604
|
},
|
|
570
605
|
});
|
|
571
606
|
}
|
|
@@ -610,7 +645,25 @@ export class ACPServer extends EventEmitter {
|
|
|
610
645
|
}
|
|
611
646
|
};
|
|
612
647
|
try {
|
|
613
|
-
|
|
648
|
+
let resultEvent;
|
|
649
|
+
try {
|
|
650
|
+
resultEvent = await session.process.sendPrompt(promptText, onStepUpdate);
|
|
651
|
+
}
|
|
652
|
+
catch (firstErr) {
|
|
653
|
+
const firstMsg = firstErr.message || "";
|
|
654
|
+
if (stepCount === 0 &&
|
|
655
|
+
!session.isCancelled &&
|
|
656
|
+
firstMsg.includes("agy process exited prematurely")) {
|
|
657
|
+
logger.warn("agy process exited before streaming any steps; retrying prompt with fresh process", {
|
|
658
|
+
sessionId,
|
|
659
|
+
error: firstMsg,
|
|
660
|
+
});
|
|
661
|
+
resultEvent = await session.process.sendPrompt(promptText, onStepUpdate);
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
throw firstErr;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
614
667
|
const turnUsage = resultEvent.result?.usage;
|
|
615
668
|
const numTurns = resultEvent.result?.num_turns || stepCount || 1;
|
|
616
669
|
// Usage is billable even when the turn terminates with ERROR
|
|
@@ -642,12 +695,14 @@ export class ACPServer extends EventEmitter {
|
|
|
642
695
|
}
|
|
643
696
|
}
|
|
644
697
|
catch (promptErr) {
|
|
645
|
-
|
|
698
|
+
const errorMsg = promptErr.message || "Turn execution failed";
|
|
699
|
+
if (session.isCancelled ||
|
|
700
|
+
errorMsg.includes("cancelled") ||
|
|
701
|
+
errorMsg.includes("Turn was cancelled")) {
|
|
646
702
|
if (!isNotification)
|
|
647
703
|
this.sendSuccess(id, { stopReason: "cancelled" });
|
|
648
704
|
}
|
|
649
705
|
else {
|
|
650
|
-
const errorMsg = promptErr.message || "Turn execution failed";
|
|
651
706
|
logger.error("Error executing prompt turn", { error: errorMsg, sessionId });
|
|
652
707
|
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
653
708
|
sessionId: session.id,
|
|
@@ -399,6 +399,7 @@ export class AntigravityProcess extends EventEmitter {
|
|
|
399
399
|
child.stderr.setEncoding("utf-8");
|
|
400
400
|
let stdoutBuffer = "";
|
|
401
401
|
let stderrBuffer = "";
|
|
402
|
+
const recentStderrLines = [];
|
|
402
403
|
child.stdout.on("data", (chunk) => {
|
|
403
404
|
this.emit("activity");
|
|
404
405
|
stdoutBuffer += chunk;
|
|
@@ -424,8 +425,13 @@ export class AntigravityProcess extends EventEmitter {
|
|
|
424
425
|
const lines = stderrBuffer.split("\n");
|
|
425
426
|
stderrBuffer = lines.pop() || "";
|
|
426
427
|
for (const line of lines) {
|
|
427
|
-
|
|
428
|
-
|
|
428
|
+
const trimmed = line.trim();
|
|
429
|
+
if (trimmed) {
|
|
430
|
+
logger.warn("agy stderr: " + trimmed);
|
|
431
|
+
recentStderrLines.push(trimmed);
|
|
432
|
+
if (recentStderrLines.length > 20)
|
|
433
|
+
recentStderrLines.shift();
|
|
434
|
+
}
|
|
429
435
|
}
|
|
430
436
|
});
|
|
431
437
|
child.on("error", (err) => {
|
|
@@ -438,9 +444,24 @@ export class AntigravityProcess extends EventEmitter {
|
|
|
438
444
|
});
|
|
439
445
|
child.on("close", (code, signal) => {
|
|
440
446
|
logger.info("agy process exited", { code, signal, pid: child.pid });
|
|
447
|
+
if (stderrBuffer.trim()) {
|
|
448
|
+
recentStderrLines.push(stderrBuffer.trim());
|
|
449
|
+
if (recentStderrLines.length > 20)
|
|
450
|
+
recentStderrLines.shift();
|
|
451
|
+
}
|
|
452
|
+
const stderrDiagnostic = recentStderrLines.join(" ").trim();
|
|
453
|
+
const exitDiagnostic = stderrDiagnostic
|
|
454
|
+
? `agy process exited prematurely with code ${code} signal ${signal}: ${stderrDiagnostic}`
|
|
455
|
+
: `agy process exited prematurely with code ${code} signal ${signal}`;
|
|
441
456
|
if (this.currentTurnPromise?.child === child) {
|
|
442
|
-
this.currentTurnPromise
|
|
457
|
+
const turn = this.currentTurnPromise;
|
|
443
458
|
this.currentTurnPromise = null;
|
|
459
|
+
if (turn.cancelled) {
|
|
460
|
+
turn.reject(new Error("Turn was cancelled"));
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
turn.reject(new Error(exitDiagnostic));
|
|
464
|
+
}
|
|
444
465
|
}
|
|
445
466
|
// The leader can die while a tool subprocess remains in its process
|
|
446
467
|
// group. Keep the group identity long enough to reap those descendants.
|
|
@@ -742,6 +763,11 @@ export class AntigravityProcess extends EventEmitter {
|
|
|
742
763
|
logger.info("Cancelling active agy turn", { hasChild: Boolean(active.child) });
|
|
743
764
|
if (active.child) {
|
|
744
765
|
const child = active.child;
|
|
766
|
+
// Immediately detach this.child so subsequent turns will NOT attach to this dying process!
|
|
767
|
+
if (this.child === child) {
|
|
768
|
+
this.child = null;
|
|
769
|
+
this.initialInfo = null;
|
|
770
|
+
}
|
|
745
771
|
this.signalProcessTree(child, "SIGINT");
|
|
746
772
|
setTimeout(() => {
|
|
747
773
|
if (this.isProcessTreeAlive(child)) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface McpServerStdioConfig {
|
|
2
|
+
type: "stdio";
|
|
3
|
+
command: string;
|
|
4
|
+
args?: string[];
|
|
5
|
+
env?: Record<string, string>;
|
|
6
|
+
alwaysLoad?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface McpServerHttpConfig {
|
|
9
|
+
type: "http" | "sse";
|
|
10
|
+
url: string;
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
alwaysLoad?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export type McpServerConfig = McpServerStdioConfig | McpServerHttpConfig;
|
|
15
|
+
/**
|
|
16
|
+
* Bridges Paseo session MCP servers into Google Antigravity CLI's MCP system.
|
|
17
|
+
*/
|
|
18
|
+
export declare class McpBridge {
|
|
19
|
+
private readonly binaryPath;
|
|
20
|
+
private registered;
|
|
21
|
+
constructor(binaryPath?: string);
|
|
22
|
+
/**
|
|
23
|
+
* Syncs MCP servers configured by Paseo into Antigravity.
|
|
24
|
+
*/
|
|
25
|
+
syncServers(servers?: Record<string, McpServerConfig>): Promise<void>;
|
|
26
|
+
private addServer;
|
|
27
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { logger } from "./logger.js";
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
/**
|
|
6
|
+
* Bridges Paseo session MCP servers into Google Antigravity CLI's MCP system.
|
|
7
|
+
*/
|
|
8
|
+
export class McpBridge {
|
|
9
|
+
binaryPath;
|
|
10
|
+
registered = new Set();
|
|
11
|
+
constructor(binaryPath = "agy") {
|
|
12
|
+
this.binaryPath = binaryPath;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Syncs MCP servers configured by Paseo into Antigravity.
|
|
16
|
+
*/
|
|
17
|
+
async syncServers(servers) {
|
|
18
|
+
if (!servers || typeof servers !== "object")
|
|
19
|
+
return;
|
|
20
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
21
|
+
if (!name || !config || typeof config !== "object")
|
|
22
|
+
continue;
|
|
23
|
+
const key = `${name}:${JSON.stringify(config)}`;
|
|
24
|
+
if (this.registered.has(key))
|
|
25
|
+
continue;
|
|
26
|
+
try {
|
|
27
|
+
await this.addServer(name, config);
|
|
28
|
+
this.registered.add(key);
|
|
29
|
+
logger.info("Successfully synced MCP server from Paseo to Antigravity", { name, type: config.type });
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
logger.warn("Could not sync MCP server to Antigravity (continuing without it)", {
|
|
33
|
+
name,
|
|
34
|
+
error: err.message,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async addServer(name, config) {
|
|
40
|
+
const args = ["mcp", "add"];
|
|
41
|
+
if (config.type === "stdio") {
|
|
42
|
+
if (config.env) {
|
|
43
|
+
for (const [k, v] of Object.entries(config.env)) {
|
|
44
|
+
args.push("--env", `${k}=${v}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
args.push(name, config.command);
|
|
48
|
+
if (config.args && config.args.length > 0) {
|
|
49
|
+
args.push(...config.args);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (config.type === "http" || config.type === "sse") {
|
|
53
|
+
if (config.headers) {
|
|
54
|
+
for (const [k, v] of Object.entries(config.headers)) {
|
|
55
|
+
args.push("--header", `${k}: ${v}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
args.push(name, config.url);
|
|
59
|
+
}
|
|
60
|
+
await execFileAsync(this.binaryPath, args, { timeout: 10_000 });
|
|
61
|
+
}
|
|
62
|
+
}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -135,7 +135,7 @@ export interface SessionInfo {
|
|
|
135
135
|
updatedAt?: string | null;
|
|
136
136
|
_meta?: Record<string, unknown> | null;
|
|
137
137
|
}
|
|
138
|
-
export type ToolKind = "read" | "edit" | "execute" | "fetch" | "other";
|
|
138
|
+
export type ToolKind = "read" | "edit" | "execute" | "fetch" | "search" | "other";
|
|
139
139
|
export declare function mapToolNameToKind(toolName: string): ToolKind;
|
|
140
140
|
export declare function extractPromptText(promptInput: unknown): string;
|
|
141
141
|
export interface ModelDefinition {
|
package/dist/protocol.js
CHANGED
|
@@ -40,12 +40,18 @@ export const ACP_METHODS = {
|
|
|
40
40
|
};
|
|
41
41
|
export function mapToolNameToKind(toolName) {
|
|
42
42
|
const name = toolName.toLowerCase();
|
|
43
|
+
if (name.includes("search") ||
|
|
44
|
+
name.includes("grep") ||
|
|
45
|
+
name.includes("find") ||
|
|
46
|
+
name === "search_web" ||
|
|
47
|
+
name === "grep_search" ||
|
|
48
|
+
name === "find_by_name")
|
|
49
|
+
return "search";
|
|
43
50
|
if (name.includes("read") ||
|
|
44
51
|
name.includes("view") ||
|
|
45
52
|
name.includes("list") ||
|
|
46
|
-
name.includes("grep") ||
|
|
47
|
-
name.includes("find") ||
|
|
48
53
|
name === "view_file" ||
|
|
54
|
+
name === "read_file" ||
|
|
49
55
|
name === "list_dir")
|
|
50
56
|
return "read";
|
|
51
57
|
if (name.includes("write") ||
|
|
@@ -59,12 +65,11 @@ export function mapToolNameToKind(toolName) {
|
|
|
59
65
|
if (name.includes("command") ||
|
|
60
66
|
name.includes("terminal") ||
|
|
61
67
|
name.includes("execute") ||
|
|
68
|
+
name.includes("subagent") ||
|
|
62
69
|
name === "run_command")
|
|
63
70
|
return "execute";
|
|
64
|
-
if (name.includes("
|
|
65
|
-
name.includes("
|
|
66
|
-
name.includes("browser") ||
|
|
67
|
-
name === "search_web")
|
|
71
|
+
if (name.includes("url") ||
|
|
72
|
+
name.includes("browser"))
|
|
68
73
|
return "fetch";
|
|
69
74
|
return "other";
|
|
70
75
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ToolKind } from "./protocol.js";
|
|
2
|
+
export interface NormalizedToolCall {
|
|
3
|
+
toolCallId: string;
|
|
4
|
+
title: string;
|
|
5
|
+
kind: ToolKind;
|
|
6
|
+
locations?: Array<{
|
|
7
|
+
path: string;
|
|
8
|
+
line?: number;
|
|
9
|
+
}>;
|
|
10
|
+
rawInput: Record<string, unknown>;
|
|
11
|
+
questionCard?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface NormalizedToolResult {
|
|
14
|
+
status: "completed" | "failed";
|
|
15
|
+
rawOutput: string;
|
|
16
|
+
content?: Array<Record<string, unknown>>;
|
|
17
|
+
imageMarkdown?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface PlanEntry {
|
|
20
|
+
content: string;
|
|
21
|
+
status: "pending" | "in_progress" | "completed";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Normalizes Antigravity tool calls into standard ACP format with descriptive titles,
|
|
25
|
+
* proper kinds, clickable file locations, and normalized parameter names.
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeToolCall(toolName: string, rawParams: Record<string, unknown> | undefined, stepIndex: number): NormalizedToolCall;
|
|
28
|
+
/**
|
|
29
|
+
* Normalizes tool execution results into rich timeline content (diffs, terminals, text, images).
|
|
30
|
+
*/
|
|
31
|
+
export declare function normalizeToolResult(toolName: string, rawParams: Record<string, unknown> | undefined, output: unknown, error?: {
|
|
32
|
+
message?: string;
|
|
33
|
+
}): NormalizedToolResult;
|
|
34
|
+
/**
|
|
35
|
+
* Extracts markdown checklist / todo plan updates from assistant text or thoughts.
|
|
36
|
+
*/
|
|
37
|
+
export declare function extractPlanEntries(text: string): PlanEntry[] | null;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Normalizes Antigravity tool calls into standard ACP format with descriptive titles,
|
|
4
|
+
* proper kinds, clickable file locations, and normalized parameter names.
|
|
5
|
+
*/
|
|
6
|
+
export function normalizeToolCall(toolName, rawParams = {}, stepIndex) {
|
|
7
|
+
const toolCallId = `tool_${stepIndex}`;
|
|
8
|
+
const normalizedParams = { ...rawParams };
|
|
9
|
+
const name = toolName.toLowerCase();
|
|
10
|
+
// 1. Shell commands: run_command
|
|
11
|
+
if (name === "run_command" ||
|
|
12
|
+
name.includes("command") ||
|
|
13
|
+
name.includes("terminal") ||
|
|
14
|
+
Boolean(rawParams.CommandLine) ||
|
|
15
|
+
Boolean(rawParams.command)) {
|
|
16
|
+
const cmd = String(rawParams.CommandLine || rawParams.command || rawParams.cmd || "").trim();
|
|
17
|
+
const summary = String(rawParams.toolSummary || rawParams.summary || "").trim();
|
|
18
|
+
const title = summary || (cmd ? truncate(cmd, 60) : "run_command");
|
|
19
|
+
normalizedParams.command = cmd;
|
|
20
|
+
normalizedParams.CommandLine = cmd;
|
|
21
|
+
return {
|
|
22
|
+
toolCallId,
|
|
23
|
+
title,
|
|
24
|
+
kind: "execute",
|
|
25
|
+
rawInput: normalizedParams,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// 2. File reads: read_file, view_file, list_dir
|
|
29
|
+
if (name === "read_file" || name === "view_file" || name === "list_dir" || name.includes("read")) {
|
|
30
|
+
const rawPath = String(rawParams.Path || rawParams.path || rawParams.AbsolutePath || rawParams.filePath || rawParams.file || "").trim();
|
|
31
|
+
const startLine = typeof rawParams.StartLine === "number" ? rawParams.StartLine : undefined;
|
|
32
|
+
const base = rawPath ? path.basename(rawPath) : "read_file";
|
|
33
|
+
const title = rawPath ? (startLine ? `${base}:${startLine}` : base) : "read_file";
|
|
34
|
+
normalizedParams.path = rawPath;
|
|
35
|
+
normalizedParams.filePath = rawPath;
|
|
36
|
+
normalizedParams.Path = rawPath;
|
|
37
|
+
return {
|
|
38
|
+
toolCallId,
|
|
39
|
+
title,
|
|
40
|
+
kind: "read",
|
|
41
|
+
locations: rawPath ? [{ path: rawPath, ...(startLine ? { line: startLine } : {}) }] : undefined,
|
|
42
|
+
rawInput: normalizedParams,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// 3. File edits / writes: edit_file, write_file, replace_file_content, multi_replace_file_content
|
|
46
|
+
if (name === "edit_file" ||
|
|
47
|
+
name === "write_file" ||
|
|
48
|
+
name === "replace_file_content" ||
|
|
49
|
+
name === "multi_replace_file_content" ||
|
|
50
|
+
name === "sed_file" ||
|
|
51
|
+
name.includes("edit") ||
|
|
52
|
+
name.includes("write")) {
|
|
53
|
+
const rawPath = String(rawParams.Path || rawParams.path || rawParams.AbsolutePath || rawParams.filePath || rawParams.TargetFile || "").trim();
|
|
54
|
+
const base = rawPath ? path.basename(rawPath) : "edit_file";
|
|
55
|
+
const target = String(rawParams.Target || rawParams.oldText || rawParams.oldString || "");
|
|
56
|
+
const replacement = String(rawParams.Replacement || rawParams.newText || rawParams.newString || rawParams.Content || "");
|
|
57
|
+
normalizedParams.path = rawPath;
|
|
58
|
+
normalizedParams.filePath = rawPath;
|
|
59
|
+
normalizedParams.Path = rawPath;
|
|
60
|
+
normalizedParams.oldText = target;
|
|
61
|
+
normalizedParams.oldString = target;
|
|
62
|
+
normalizedParams.newText = replacement;
|
|
63
|
+
normalizedParams.newString = replacement;
|
|
64
|
+
return {
|
|
65
|
+
toolCallId,
|
|
66
|
+
title: base,
|
|
67
|
+
kind: "edit",
|
|
68
|
+
locations: rawPath ? [{ path: rawPath }] : undefined,
|
|
69
|
+
rawInput: normalizedParams,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// 4. Search tools: search_web, grep_search, find_by_name
|
|
73
|
+
if (name === "search_web" || name === "grep_search" || name === "find_by_name" || name.includes("search")) {
|
|
74
|
+
const query = String(rawParams.query || rawParams.pattern || rawParams.CommandLine || "").trim();
|
|
75
|
+
const title = query ? `Busca: ${truncate(query, 50)}` : "search";
|
|
76
|
+
normalizedParams.query = query;
|
|
77
|
+
return {
|
|
78
|
+
toolCallId,
|
|
79
|
+
title,
|
|
80
|
+
kind: "search",
|
|
81
|
+
rawInput: normalizedParams,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// 5. Subagents: invoke_subagent, browser_subagent, manage_subagents
|
|
85
|
+
if (name.includes("subagent")) {
|
|
86
|
+
const role = String(rawParams.Role || rawParams.TypeName || rawParams.type || "Subagente").trim();
|
|
87
|
+
const prompt = String(rawParams.Prompt || rawParams.prompt || "").trim();
|
|
88
|
+
const title = `🤖 Subagente: ${role}`;
|
|
89
|
+
normalizedParams.role = role;
|
|
90
|
+
normalizedParams.prompt = prompt;
|
|
91
|
+
return {
|
|
92
|
+
toolCallId,
|
|
93
|
+
title,
|
|
94
|
+
kind: "execute",
|
|
95
|
+
rawInput: normalizedParams,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// 6. Interactive Questions: ask_question
|
|
99
|
+
if (name === "ask_question") {
|
|
100
|
+
const questions = Array.isArray(rawParams.questions) ? rawParams.questions : [];
|
|
101
|
+
const questionCard = formatQuestionCard(questions);
|
|
102
|
+
return {
|
|
103
|
+
toolCallId,
|
|
104
|
+
title: "❓ Pergunta do Assistente",
|
|
105
|
+
kind: "other",
|
|
106
|
+
rawInput: normalizedParams,
|
|
107
|
+
questionCard: questionCard || undefined,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// 7. General fallback
|
|
111
|
+
const summary = String(rawParams.toolSummary || rawParams.summary || "").trim();
|
|
112
|
+
return {
|
|
113
|
+
toolCallId,
|
|
114
|
+
title: summary || toolName,
|
|
115
|
+
kind: "other",
|
|
116
|
+
rawInput: normalizedParams,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Normalizes tool execution results into rich timeline content (diffs, terminals, text, images).
|
|
121
|
+
*/
|
|
122
|
+
export function normalizeToolResult(toolName, rawParams = {}, output, error) {
|
|
123
|
+
if (error) {
|
|
124
|
+
return {
|
|
125
|
+
status: "failed",
|
|
126
|
+
rawOutput: error.message || "Tool execution error",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const outputStr = typeof output === "string" ? output : JSON.stringify(output ?? "");
|
|
130
|
+
const name = toolName.toLowerCase();
|
|
131
|
+
const content = [];
|
|
132
|
+
// Edit tools: emit diff block so Paseo displays visual green/red syntax diffs
|
|
133
|
+
if (name === "edit_file" ||
|
|
134
|
+
name === "replace_file_content" ||
|
|
135
|
+
name === "multi_replace_file_content" ||
|
|
136
|
+
name === "write_file") {
|
|
137
|
+
const filePath = String(rawParams.Path || rawParams.path || rawParams.AbsolutePath || "");
|
|
138
|
+
const oldText = String(rawParams.Target || rawParams.oldText || rawParams.oldString || "");
|
|
139
|
+
const newText = String(rawParams.Replacement || rawParams.newText || rawParams.newString || rawParams.Content || "");
|
|
140
|
+
if (oldText || newText) {
|
|
141
|
+
content.push({
|
|
142
|
+
type: "diff",
|
|
143
|
+
path: filePath,
|
|
144
|
+
oldText,
|
|
145
|
+
newText,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else if (name === "read_file" || name === "view_file") {
|
|
150
|
+
if (outputStr.trim()) {
|
|
151
|
+
content.push({
|
|
152
|
+
type: "text",
|
|
153
|
+
text: outputStr,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
status: "completed",
|
|
159
|
+
rawOutput: outputStr,
|
|
160
|
+
content: content.length > 0 ? content : undefined,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Extracts markdown checklist / todo plan updates from assistant text or thoughts.
|
|
165
|
+
*/
|
|
166
|
+
export function extractPlanEntries(text) {
|
|
167
|
+
if (!text || typeof text !== "string")
|
|
168
|
+
return null;
|
|
169
|
+
const lines = text.split(/\r?\n/);
|
|
170
|
+
const entries = [];
|
|
171
|
+
for (const line of lines) {
|
|
172
|
+
const match = line.match(/^[-*]\s+\[([ xX/\\-])\]\s+(.+)$/);
|
|
173
|
+
if (match) {
|
|
174
|
+
const mark = match[1].trim();
|
|
175
|
+
const content = match[2].trim();
|
|
176
|
+
let status = "pending";
|
|
177
|
+
if (mark === "x" || mark === "X") {
|
|
178
|
+
status = "completed";
|
|
179
|
+
}
|
|
180
|
+
else if (mark === "/" || mark === "-") {
|
|
181
|
+
status = "in_progress";
|
|
182
|
+
}
|
|
183
|
+
entries.push({ content, status });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return entries.length >= 2 ? entries : null;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Formats Antigravity's ask_question structure into a clear, responsive markdown card.
|
|
190
|
+
*/
|
|
191
|
+
function formatQuestionCard(questions) {
|
|
192
|
+
if (!questions || questions.length === 0)
|
|
193
|
+
return "";
|
|
194
|
+
const cards = [];
|
|
195
|
+
for (const q of questions) {
|
|
196
|
+
if (!q || typeof q !== "object")
|
|
197
|
+
continue;
|
|
198
|
+
const prompt = q.question || "Escolha uma opção:";
|
|
199
|
+
const options = Array.isArray(q.options) ? q.options : [];
|
|
200
|
+
let card = `### ❓ Pergunta do Assistente\n> **${prompt}**\n\n`;
|
|
201
|
+
if (options.length > 0) {
|
|
202
|
+
card += `*Responda indicando o número da opção desejada:*\n`;
|
|
203
|
+
card += options
|
|
204
|
+
.map((opt, idx) => {
|
|
205
|
+
const isRec = opt.includes("(Recommended)") || opt.includes("(Recomendado)");
|
|
206
|
+
const clean = opt.replace(/^\(Recommended\)\s*/i, "").replace(/^\(Recomendado\)\s*/i, "");
|
|
207
|
+
return `- **[${idx + 1}]** ${clean}${isRec ? " *(Recomendado)*" : ""}`;
|
|
208
|
+
})
|
|
209
|
+
.join("\n");
|
|
210
|
+
}
|
|
211
|
+
cards.push(card);
|
|
212
|
+
}
|
|
213
|
+
return cards.join("\n\n---\n\n");
|
|
214
|
+
}
|
|
215
|
+
function truncate(text, maxLen) {
|
|
216
|
+
if (text.length <= maxLen)
|
|
217
|
+
return text;
|
|
218
|
+
return text.slice(0, maxLen - 3) + "...";
|
|
219
|
+
}
|
package/dist/transcript.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import { logger } from "./logger.js";
|
|
5
|
-
import {
|
|
5
|
+
import { normalizeToolCall } from "./tool-formatter.js";
|
|
6
6
|
/**
|
|
7
7
|
* Strips XML-like tags injected by harness or system prompts (e.g., <USER_REQUEST>, <ADDITIONAL_METADATA>)
|
|
8
8
|
* to recover the clean user prompt for history display in clients like Paseo.
|
|
@@ -106,21 +106,20 @@ export function loadConversationTranscript(conversationId) {
|
|
|
106
106
|
for (let i = 0; i < step.tool_calls.length; i++) {
|
|
107
107
|
const tc = step.tool_calls[i];
|
|
108
108
|
const rawArgs = (tc.args || tc.parameters || {});
|
|
109
|
-
const toolName = String(tc.name || tc.tool_name || rawArgs.toolAction || "tool");
|
|
110
|
-
const
|
|
111
|
-
rawArgs
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
lastToolCallId = toolCallId;
|
|
109
|
+
const toolName = String(tc.name || tc.tool_name || tc.toolAction || rawArgs.toolAction || "tool");
|
|
110
|
+
const mergedParams = {
|
|
111
|
+
...rawArgs,
|
|
112
|
+
toolSummary: rawArgs.toolSummary || tc.toolSummary,
|
|
113
|
+
toolAction: rawArgs.toolAction || tc.toolAction,
|
|
114
|
+
};
|
|
115
|
+
const toolCall = normalizeToolCall(toolName, mergedParams, stepIndex * 100 + i);
|
|
116
|
+
lastToolCallId = toolCall.toolCallId;
|
|
118
117
|
replayItems.push({
|
|
119
118
|
type: "tool_call",
|
|
120
|
-
toolCallId,
|
|
121
|
-
title,
|
|
122
|
-
kind:
|
|
123
|
-
rawInput:
|
|
119
|
+
toolCallId: toolCall.toolCallId,
|
|
120
|
+
title: toolCall.title,
|
|
121
|
+
kind: toolCall.kind,
|
|
122
|
+
rawInput: toolCall.rawInput,
|
|
124
123
|
});
|
|
125
124
|
}
|
|
126
125
|
}
|