paseo-acp-agy 1.1.9 → 1.2.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-server.d.ts +2 -0
- package/dist/acp-server.js +83 -4
- package/dist/antigravity-process.d.ts +2 -0
- package/dist/antigravity-process.js +17 -1
- package/dist/asar.d.ts +28 -0
- package/dist/asar.js +171 -0
- package/dist/attachments.js +3 -0
- package/dist/index.js +34 -11
- package/dist/paseo-patcher.js +81 -15
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.js +12 -0
- package/dist/protocol.d.ts +45 -0
- package/dist/protocol.js +126 -2
- package/dist/session-store.d.ts +1 -0
- package/dist/session-store.js +3 -0
- package/dist/session.d.ts +3 -0
- package/dist/session.js +20 -2
- package/dist/slash-commands.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -4
package/dist/acp-server.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare class ACPServer {
|
|
|
5
5
|
private input;
|
|
6
6
|
private output;
|
|
7
7
|
private binaryPath;
|
|
8
|
+
private skipNarration;
|
|
8
9
|
private rl;
|
|
9
10
|
private isRunning;
|
|
10
11
|
constructor(options?: {
|
|
@@ -12,6 +13,7 @@ export declare class ACPServer {
|
|
|
12
13
|
output?: Writable;
|
|
13
14
|
sessionManager?: SessionManager;
|
|
14
15
|
binaryPath?: string;
|
|
16
|
+
skipNarration?: boolean;
|
|
15
17
|
});
|
|
16
18
|
start(): void;
|
|
17
19
|
private send;
|
package/dist/acp-server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import readline from "node:readline";
|
|
2
2
|
import { logger } from "./logger.js";
|
|
3
|
-
import { ACP_METHODS, AVAILABLE_MODES, fetchAvailableModels,
|
|
3
|
+
import { ACP_METHODS, AVAILABLE_MODES, AVAILABLE_PERMISSIONS, fetchAvailableModels, buildConfigOptionsForSession, extractPromptText, mapToolNameToKind, extractImageMarkdownLink, isNarrationText, calculateUsageCostUsd, roundUsageCostUsd, getModelContextWindow, fetchAntigravityUsage, } from "./protocol.js";
|
|
4
4
|
import { SessionManager } from "./session.js";
|
|
5
5
|
import { executeSlashCommand, AVAILABLE_SLASH_COMMANDS } from "./slash-commands.js";
|
|
6
6
|
import { getShortVersion } from "./version.js";
|
|
@@ -21,12 +21,15 @@ export class ACPServer {
|
|
|
21
21
|
input;
|
|
22
22
|
output;
|
|
23
23
|
binaryPath;
|
|
24
|
+
skipNarration;
|
|
24
25
|
rl = null;
|
|
25
26
|
isRunning = false;
|
|
26
27
|
constructor(options = {}) {
|
|
27
28
|
this.input = options.input || process.stdin;
|
|
28
29
|
this.output = options.output || process.stdout;
|
|
29
30
|
this.binaryPath = options.binaryPath || resolveDefaultAgyBinary();
|
|
31
|
+
this.skipNarration =
|
|
32
|
+
options.skipNarration ?? (process.env.AGY_ACP_SKIP_NARRATION === "true");
|
|
30
33
|
this.sessionManager =
|
|
31
34
|
options.sessionManager || new SessionManager({ defaultBinaryPath: this.binaryPath });
|
|
32
35
|
}
|
|
@@ -112,7 +115,13 @@ export class ACPServer {
|
|
|
112
115
|
availableModels,
|
|
113
116
|
currentModelId: session.model,
|
|
114
117
|
},
|
|
115
|
-
configOptions:
|
|
118
|
+
configOptions: buildConfigOptionsForSession({
|
|
119
|
+
modelId: session.model,
|
|
120
|
+
currentEffort: session.effort,
|
|
121
|
+
currentMode: session.mode,
|
|
122
|
+
currentPermission: session.permission,
|
|
123
|
+
availableModels,
|
|
124
|
+
}),
|
|
116
125
|
};
|
|
117
126
|
}
|
|
118
127
|
requireSession(sessionId) {
|
|
@@ -199,6 +208,17 @@ export class ACPServer {
|
|
|
199
208
|
this.sendSuccess(id, await this.sessionState(session, true));
|
|
200
209
|
this.publishCommands(session.id);
|
|
201
210
|
this.publishUsageUpdate(session);
|
|
211
|
+
// Re-publish usage after ACPAgent completes awaiting newSession and stores this.sessionId
|
|
212
|
+
setTimeout(() => {
|
|
213
|
+
if (this.sessionManager.getSession(session.id)) {
|
|
214
|
+
this.publishUsageUpdate(session);
|
|
215
|
+
}
|
|
216
|
+
}, 150);
|
|
217
|
+
setTimeout(() => {
|
|
218
|
+
if (this.sessionManager.getSession(session.id)) {
|
|
219
|
+
this.publishUsageUpdate(session);
|
|
220
|
+
}
|
|
221
|
+
}, 500);
|
|
202
222
|
}
|
|
203
223
|
break;
|
|
204
224
|
}
|
|
@@ -229,6 +249,12 @@ export class ACPServer {
|
|
|
229
249
|
this.sendSuccess(id, await this.sessionState(session));
|
|
230
250
|
this.publishCommands(session.id);
|
|
231
251
|
this.publishUsageUpdate(session);
|
|
252
|
+
const activeSession = session;
|
|
253
|
+
setTimeout(() => {
|
|
254
|
+
if (this.sessionManager.getSession(activeSession.id)) {
|
|
255
|
+
this.publishUsageUpdate(activeSession);
|
|
256
|
+
}
|
|
257
|
+
}, 150);
|
|
232
258
|
}
|
|
233
259
|
}
|
|
234
260
|
catch (err) {
|
|
@@ -329,11 +355,19 @@ export class ACPServer {
|
|
|
329
355
|
this.sendSuccess(id, { stopReason: "end_turn" });
|
|
330
356
|
break;
|
|
331
357
|
}
|
|
358
|
+
let hasSentNonNarration = false;
|
|
332
359
|
const onStepUpdate = (event) => {
|
|
333
360
|
const step = event.step_update;
|
|
334
361
|
if (!step)
|
|
335
362
|
return;
|
|
336
363
|
if (step.step_type === "agent_response" && step.text_delta) {
|
|
364
|
+
if (this.skipNarration && !hasSentNonNarration) {
|
|
365
|
+
if (isNarrationText(step.text_delta)) {
|
|
366
|
+
logger.debug("Skipping pure narration text chunk", { delta: step.text_delta });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
hasSentNonNarration = true;
|
|
370
|
+
}
|
|
337
371
|
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
338
372
|
sessionId: session.id,
|
|
339
373
|
update: {
|
|
@@ -343,6 +377,7 @@ export class ACPServer {
|
|
|
343
377
|
});
|
|
344
378
|
}
|
|
345
379
|
else if (step.step_type === "thought" && step.text_delta) {
|
|
380
|
+
hasSentNonNarration = true;
|
|
346
381
|
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
347
382
|
sessionId: session.id,
|
|
348
383
|
update: {
|
|
@@ -352,6 +387,7 @@ export class ACPServer {
|
|
|
352
387
|
});
|
|
353
388
|
}
|
|
354
389
|
else if (step.step_type === "tool" && step.tool_info) {
|
|
390
|
+
hasSentNonNarration = true;
|
|
355
391
|
const toolCallId = `tool_${step.step_index}`;
|
|
356
392
|
const toolName = step.tool_name || step.tool_info.name || "tool";
|
|
357
393
|
const toolKind = mapToolNameToKind(toolName);
|
|
@@ -377,6 +413,16 @@ export class ACPServer {
|
|
|
377
413
|
rawOutput: step.tool_info.output ?? "",
|
|
378
414
|
},
|
|
379
415
|
});
|
|
416
|
+
const imageMarkdown = extractImageMarkdownLink(toolName, step.tool_info.output, step.tool_info.parameters);
|
|
417
|
+
if (imageMarkdown) {
|
|
418
|
+
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
419
|
+
sessionId: session.id,
|
|
420
|
+
update: {
|
|
421
|
+
sessionUpdate: "agent_message_chunk",
|
|
422
|
+
content: { type: "text", text: `\n\n${imageMarkdown}\n\n` },
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
}
|
|
380
426
|
}
|
|
381
427
|
else if (step.state === "ERROR") {
|
|
382
428
|
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
@@ -496,6 +542,7 @@ export class ACPServer {
|
|
|
496
542
|
break;
|
|
497
543
|
}
|
|
498
544
|
case ACP_METHODS.SESSION_SET_MODE:
|
|
545
|
+
case ACP_METHODS.SESSION_SET_MODE_CAMEL:
|
|
499
546
|
case ACP_METHODS.SESSION_SET_MODE_ALIAS: {
|
|
500
547
|
const sessionId = String(params.sessionId || "");
|
|
501
548
|
const modeId = String(params.modeId || params.mode || "default");
|
|
@@ -516,6 +563,7 @@ export class ACPServer {
|
|
|
516
563
|
break;
|
|
517
564
|
}
|
|
518
565
|
case ACP_METHODS.SESSION_SET_MODEL:
|
|
566
|
+
case ACP_METHODS.SESSION_SET_MODEL_CAMEL:
|
|
519
567
|
case ACP_METHODS.SESSION_SET_MODEL_ALIAS: {
|
|
520
568
|
const sessionId = String(params.sessionId || "");
|
|
521
569
|
const session = this.requireSession(sessionId);
|
|
@@ -546,12 +594,19 @@ export class ACPServer {
|
|
|
546
594
|
if (parsedModel.effort)
|
|
547
595
|
session.setEffort(parsedModel.effort);
|
|
548
596
|
session.setModel(model.modelId);
|
|
549
|
-
const configOptions =
|
|
597
|
+
const configOptions = buildConfigOptionsForSession({
|
|
598
|
+
modelId: session.model,
|
|
599
|
+
currentEffort: session.effort,
|
|
600
|
+
currentMode: session.mode,
|
|
601
|
+
currentPermission: session.permission,
|
|
602
|
+
availableModels: models,
|
|
603
|
+
});
|
|
550
604
|
if (!isNotification)
|
|
551
605
|
this.sendSuccess(id, { configOptions });
|
|
552
606
|
break;
|
|
553
607
|
}
|
|
554
608
|
case ACP_METHODS.SESSION_SET_CONFIG_OPTION:
|
|
609
|
+
case ACP_METHODS.SESSION_SET_CONFIG_OPTION_CAMEL:
|
|
555
610
|
case ACP_METHODS.SESSION_SET_CONFIG_OPTION_ALIAS: {
|
|
556
611
|
const sessionId = String(params.sessionId || "");
|
|
557
612
|
const configId = String(params.configId || "");
|
|
@@ -584,12 +639,36 @@ export class ACPServer {
|
|
|
584
639
|
session.setModel(value);
|
|
585
640
|
logger.info("Updated model for session via config option", { sessionId, model: value });
|
|
586
641
|
}
|
|
642
|
+
else if (configId === "mode") {
|
|
643
|
+
if (!AVAILABLE_MODES.some((candidate) => candidate.id === value)) {
|
|
644
|
+
if (!isNotification)
|
|
645
|
+
this.sendError(id, -32602, `Unsupported mode: ${value}`);
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
session.setMode(value);
|
|
649
|
+
logger.info("Updated mode for session via config option", { sessionId, mode: value });
|
|
650
|
+
}
|
|
651
|
+
else if (configId === "permission") {
|
|
652
|
+
if (!AVAILABLE_PERMISSIONS.some((candidate) => candidate.id === value)) {
|
|
653
|
+
if (!isNotification)
|
|
654
|
+
this.sendError(id, -32602, `Unsupported permission: ${value}`);
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
session.setPermission(value);
|
|
658
|
+
logger.info("Updated permission for session via config option", { sessionId, permission: value });
|
|
659
|
+
}
|
|
587
660
|
else {
|
|
588
661
|
if (!isNotification)
|
|
589
662
|
this.sendError(id, -32602, `Unsupported config option: ${configId}`);
|
|
590
663
|
break;
|
|
591
664
|
}
|
|
592
|
-
const configOptions =
|
|
665
|
+
const configOptions = buildConfigOptionsForSession({
|
|
666
|
+
modelId: session.model,
|
|
667
|
+
currentEffort: session.effort,
|
|
668
|
+
currentMode: session.mode,
|
|
669
|
+
currentPermission: session.permission,
|
|
670
|
+
availableModels: models,
|
|
671
|
+
});
|
|
593
672
|
if (!isNotification)
|
|
594
673
|
this.sendSuccess(id, { configOptions });
|
|
595
674
|
break;
|
|
@@ -40,6 +40,8 @@ export declare class AntigravityProcess extends EventEmitter {
|
|
|
40
40
|
setModel(model: string): void;
|
|
41
41
|
setEffort(effort: string): void;
|
|
42
42
|
setMode(mode: string): void;
|
|
43
|
+
get currentPermissions(): PermissionSettings;
|
|
44
|
+
setPermissions(permissions: Partial<PermissionSettings>): void;
|
|
43
45
|
setConversationId(conversationId?: string): void;
|
|
44
46
|
private scheduleRestart;
|
|
45
47
|
private isProcessTreeAlive;
|
|
@@ -42,6 +42,11 @@ export function resolveDefaultAgyBinary(force = false) {
|
|
|
42
42
|
path.join(localAppData, "Programs", "Antigravity", "agy.exe"),
|
|
43
43
|
path.join(programFiles, "Antigravity", "bin", "agy.exe"),
|
|
44
44
|
path.join(programFilesX86, "Antigravity", "bin", "agy.exe"),
|
|
45
|
+
path.join(localAppData, "Google", "Antigravity", "agy.exe"),
|
|
46
|
+
path.join(localAppData, "Google", "Antigravity", "bin", "agy.exe"),
|
|
47
|
+
path.join(home, ".antigravity", "bin", "agy.exe"),
|
|
48
|
+
path.join(home, ".antigravity", "agy.exe"),
|
|
49
|
+
path.join(home, ".gemini", "antigravity-cli", "bin", "agy.exe"),
|
|
45
50
|
path.join(localAppData, "Microsoft", "WindowsApps", "agy.exe"),
|
|
46
51
|
path.join(home, ".local", "bin", "agy.exe"),
|
|
47
52
|
path.join(appData, "npm", "agy.exe"),
|
|
@@ -79,7 +84,11 @@ export function resolveDefaultAgyBinary(force = false) {
|
|
|
79
84
|
path.join(appData, "npm", "agy.cmd"),
|
|
80
85
|
path.join(localAppData, "npm", "agy.cmd"),
|
|
81
86
|
path.join(home, ".local", "bin", "agy.cmd"),
|
|
87
|
+
path.join(home, ".antigravity", "bin", "agy.cmd"),
|
|
88
|
+
path.join(home, ".gemini", "antigravity-cli", "bin", "agy.cmd"),
|
|
82
89
|
path.join(appData, "npm", "agy.bat"),
|
|
90
|
+
path.join(localAppData, "npm", "agy.bat"),
|
|
91
|
+
path.join(home, ".local", "bin", "agy.bat"),
|
|
83
92
|
];
|
|
84
93
|
for (const cand of batchCandidates) {
|
|
85
94
|
if (fs.existsSync(cand)) {
|
|
@@ -189,6 +198,13 @@ export class AntigravityProcess extends EventEmitter {
|
|
|
189
198
|
this.scheduleRestart();
|
|
190
199
|
}
|
|
191
200
|
}
|
|
201
|
+
get currentPermissions() {
|
|
202
|
+
return this.permissions;
|
|
203
|
+
}
|
|
204
|
+
setPermissions(permissions) {
|
|
205
|
+
this.permissions = { ...this.permissions, ...permissions };
|
|
206
|
+
this.scheduleRestart();
|
|
207
|
+
}
|
|
192
208
|
setConversationId(conversationId) {
|
|
193
209
|
if (this.conversationId !== conversationId) {
|
|
194
210
|
this.conversationId = conversationId;
|
|
@@ -348,7 +364,7 @@ export class AntigravityProcess extends EventEmitter {
|
|
|
348
364
|
effectiveEffort,
|
|
349
365
|
});
|
|
350
366
|
const isWin = process.platform === "win32";
|
|
351
|
-
const isBatch = isWin && isWindowsBatchScript(this.binaryPath);
|
|
367
|
+
const isBatch = isWin && (isWindowsBatchScript(this.binaryPath) || this.binaryPath === "agy");
|
|
352
368
|
const child = spawn(this.binaryPath, args, {
|
|
353
369
|
cwd: this.cwd,
|
|
354
370
|
env: this.env,
|
package/dist/asar.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface AsarNode {
|
|
2
|
+
files?: Record<string, AsarNode>;
|
|
3
|
+
size?: number;
|
|
4
|
+
offset?: string;
|
|
5
|
+
executable?: boolean;
|
|
6
|
+
unpacked?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Reads and parses the ASAR archive header and returns the root node and base offset for data.
|
|
10
|
+
*/
|
|
11
|
+
export declare function readArchiveHeader(archivePath: string): {
|
|
12
|
+
header: AsarNode;
|
|
13
|
+
dataBaseOffset: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Lists all file paths inside an ASAR archive.
|
|
17
|
+
*/
|
|
18
|
+
export declare function listPackage(archivePath: string): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Pure TypeScript, zero-dependency ASAR archive extractor.
|
|
21
|
+
* Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractAll(archivePath: string, destDir: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Pure TypeScript, zero-dependency ASAR archive builder.
|
|
26
|
+
* Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createPackage(srcDir: string, destFile: string): Promise<void>;
|
package/dist/asar.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Reads and parses the ASAR archive header and returns the root node and base offset for data.
|
|
5
|
+
*/
|
|
6
|
+
export function readArchiveHeader(archivePath) {
|
|
7
|
+
if (!fs.existsSync(archivePath)) {
|
|
8
|
+
throw new Error(`ASAR archive not found: ${archivePath}`);
|
|
9
|
+
}
|
|
10
|
+
const fd = fs.openSync(archivePath, "r");
|
|
11
|
+
try {
|
|
12
|
+
const sizeBuf = Buffer.alloc(8);
|
|
13
|
+
const bytesRead = fs.readSync(fd, sizeBuf, 0, 8, 0);
|
|
14
|
+
if (bytesRead < 8) {
|
|
15
|
+
throw new Error(`Invalid ASAR archive: file too small (${bytesRead} bytes)`);
|
|
16
|
+
}
|
|
17
|
+
const payloadSize = sizeBuf.readUInt32LE(0);
|
|
18
|
+
const headerSize = sizeBuf.readUInt32LE(4);
|
|
19
|
+
if (payloadSize !== 4 || headerSize <= 0) {
|
|
20
|
+
throw new Error(`Invalid ASAR archive header size: payloadSize=${payloadSize}, headerSize=${headerSize}`);
|
|
21
|
+
}
|
|
22
|
+
const headerPickleBuf = Buffer.alloc(headerSize);
|
|
23
|
+
const headerRead = fs.readSync(fd, headerPickleBuf, 0, headerSize, 8);
|
|
24
|
+
if (headerRead < headerSize) {
|
|
25
|
+
throw new Error(`Truncated ASAR header: expected ${headerSize} bytes, got ${headerRead}`);
|
|
26
|
+
}
|
|
27
|
+
const strLen = headerPickleBuf.readUInt32LE(4);
|
|
28
|
+
const jsonStr = headerPickleBuf.subarray(8, 8 + strLen).toString("utf8");
|
|
29
|
+
const header = JSON.parse(jsonStr);
|
|
30
|
+
const dataBaseOffset = 8 + headerSize;
|
|
31
|
+
return { header, dataBaseOffset };
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
fs.closeSync(fd);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Lists all file paths inside an ASAR archive.
|
|
39
|
+
*/
|
|
40
|
+
export function listPackage(archivePath) {
|
|
41
|
+
const { header } = readArchiveHeader(archivePath);
|
|
42
|
+
const result = [];
|
|
43
|
+
function walk(node, currentPrefix) {
|
|
44
|
+
if (node.files) {
|
|
45
|
+
for (const [name, child] of Object.entries(node.files)) {
|
|
46
|
+
const next = currentPrefix ? `${currentPrefix}/${name}` : `/${name}`;
|
|
47
|
+
walk(child, next);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
result.push(currentPrefix);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
walk(header, "");
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Pure TypeScript, zero-dependency ASAR archive extractor.
|
|
59
|
+
* Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
|
|
60
|
+
*/
|
|
61
|
+
export function extractAll(archivePath, destDir) {
|
|
62
|
+
const { header, dataBaseOffset } = readArchiveHeader(archivePath);
|
|
63
|
+
const fd = fs.openSync(archivePath, "r");
|
|
64
|
+
try {
|
|
65
|
+
function extractNode(node, currentPath, relParts = []) {
|
|
66
|
+
if (node.files) {
|
|
67
|
+
fs.mkdirSync(currentPath, { recursive: true });
|
|
68
|
+
for (const [name, child] of Object.entries(node.files)) {
|
|
69
|
+
extractNode(child, path.join(currentPath, name), [...relParts, name]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (node.size !== undefined && node.offset !== undefined) {
|
|
73
|
+
const fileOffset = dataBaseOffset + parseInt(node.offset, 10);
|
|
74
|
+
const fileBuf = Buffer.alloc(node.size);
|
|
75
|
+
if (node.size > 0) {
|
|
76
|
+
fs.readSync(fd, fileBuf, 0, node.size, fileOffset);
|
|
77
|
+
}
|
|
78
|
+
fs.mkdirSync(path.dirname(currentPath), { recursive: true });
|
|
79
|
+
fs.writeFileSync(currentPath, fileBuf, {
|
|
80
|
+
mode: node.executable ? 0o755 : 0o644,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else if (node.unpacked) {
|
|
84
|
+
const unpackedSrc = path.join(archivePath + ".unpacked", ...relParts);
|
|
85
|
+
if (fs.existsSync(unpackedSrc)) {
|
|
86
|
+
fs.mkdirSync(path.dirname(currentPath), { recursive: true });
|
|
87
|
+
fs.copyFileSync(unpackedSrc, currentPath);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
extractNode(header, destDir);
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
fs.closeSync(fd);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Pure TypeScript, zero-dependency ASAR archive builder.
|
|
99
|
+
* Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
|
|
100
|
+
*/
|
|
101
|
+
export async function createPackage(srcDir, destFile) {
|
|
102
|
+
const files = [];
|
|
103
|
+
function walk(dir, rel = "") {
|
|
104
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
105
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
106
|
+
for (const e of entries) {
|
|
107
|
+
const entryRel = rel ? `${rel}/${e.name}` : e.name;
|
|
108
|
+
const full = path.join(dir, e.name);
|
|
109
|
+
if (e.isDirectory()) {
|
|
110
|
+
walk(full, entryRel);
|
|
111
|
+
}
|
|
112
|
+
else if (e.isFile()) {
|
|
113
|
+
const stat = fs.statSync(full);
|
|
114
|
+
const executable = Boolean(stat.mode & 0o111);
|
|
115
|
+
files.push({
|
|
116
|
+
relPath: entryRel,
|
|
117
|
+
fullPath: full,
|
|
118
|
+
size: stat.size,
|
|
119
|
+
executable: executable || undefined,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
walk(srcDir);
|
|
125
|
+
const header = { files: {} };
|
|
126
|
+
let currentOffset = 0;
|
|
127
|
+
for (const f of files) {
|
|
128
|
+
const parts = f.relPath.split("/");
|
|
129
|
+
let curr = header.files;
|
|
130
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
131
|
+
if (!curr[parts[i]]) {
|
|
132
|
+
curr[parts[i]] = { files: {} };
|
|
133
|
+
}
|
|
134
|
+
curr = curr[parts[i]].files;
|
|
135
|
+
}
|
|
136
|
+
const leaf = parts[parts.length - 1];
|
|
137
|
+
curr[leaf] = {
|
|
138
|
+
size: f.size,
|
|
139
|
+
offset: String(currentOffset),
|
|
140
|
+
executable: f.executable,
|
|
141
|
+
};
|
|
142
|
+
currentOffset += f.size;
|
|
143
|
+
}
|
|
144
|
+
const jsonBuf = Buffer.from(JSON.stringify(header), "utf8");
|
|
145
|
+
const alignedJsonLen = (jsonBuf.length + 3) & ~3;
|
|
146
|
+
const headerPayloadSize = 4 + alignedJsonLen;
|
|
147
|
+
// Header pickle: payloadSize (uint32LE) + stringLength (uint32LE) + json bytes (padded to multiple of 4)
|
|
148
|
+
const headerPickle = Buffer.alloc(4 + headerPayloadSize, 0);
|
|
149
|
+
headerPickle.writeUInt32LE(headerPayloadSize, 0);
|
|
150
|
+
headerPickle.writeUInt32LE(jsonBuf.length, 4);
|
|
151
|
+
jsonBuf.copy(headerPickle, 8);
|
|
152
|
+
// Size pickle: 4 (uint32LE payload size) + headerPickle.length (uint32LE)
|
|
153
|
+
const sizePickle = Buffer.alloc(8, 0);
|
|
154
|
+
sizePickle.writeUInt32LE(4, 0);
|
|
155
|
+
sizePickle.writeUInt32LE(headerPickle.length, 4);
|
|
156
|
+
fs.mkdirSync(path.dirname(destFile), { recursive: true });
|
|
157
|
+
const fd = fs.openSync(destFile, "w");
|
|
158
|
+
try {
|
|
159
|
+
fs.writeSync(fd, sizePickle);
|
|
160
|
+
fs.writeSync(fd, headerPickle);
|
|
161
|
+
for (const f of files) {
|
|
162
|
+
if (f.size > 0) {
|
|
163
|
+
const content = fs.readFileSync(f.fullPath);
|
|
164
|
+
fs.writeSync(fd, content);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
fs.closeSync(fd);
|
|
170
|
+
}
|
|
171
|
+
}
|
package/dist/attachments.js
CHANGED
|
@@ -7,6 +7,9 @@ const DEFAULT_MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
|
|
|
7
7
|
function getStateRoot() {
|
|
8
8
|
if (process.env.AGY_ACP_STATE_DIR)
|
|
9
9
|
return process.env.AGY_ACP_STATE_DIR;
|
|
10
|
+
if (process.platform === "win32" && process.env.LOCALAPPDATA) {
|
|
11
|
+
return path.join(process.env.LOCALAPPDATA, "agy-acp");
|
|
12
|
+
}
|
|
10
13
|
const stateHome = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
|
|
11
14
|
return path.join(stateHome, "agy-acp");
|
|
12
15
|
}
|
package/dist/index.js
CHANGED
|
@@ -32,18 +32,22 @@ Commands:
|
|
|
32
32
|
doctor Diagnose Antigravity binary, quota provider, and Paseo status
|
|
33
33
|
|
|
34
34
|
Options:
|
|
35
|
-
--acp
|
|
36
|
-
--setup
|
|
37
|
-
--doctor
|
|
38
|
-
-
|
|
39
|
-
--
|
|
40
|
-
-
|
|
35
|
+
--acp Start ACP server over stdio (default)
|
|
36
|
+
--setup Integrate Antigravity with local Paseo server installation
|
|
37
|
+
--doctor Run environment, binary, and telemetry diagnostics
|
|
38
|
+
--skip-naration Drop leading narration-only assistant chunks (e.g. 'I will...')
|
|
39
|
+
--skip-narration Alias for --skip-naration
|
|
40
|
+
-v, --version Show version
|
|
41
|
+
--json Show version in JSON format (with --version)
|
|
42
|
+
-h, --help Show help
|
|
41
43
|
|
|
42
44
|
Environment Variables:
|
|
43
45
|
AGY_ACP_LOG_LEVEL debug | info | warn | error (default: info)
|
|
44
46
|
AGY_ACP_LOG_DIR Directory for log files
|
|
45
47
|
AGY_ACP_SANDBOX Set to 'true' to run agy in sandbox mode
|
|
46
48
|
AGY_ACP_DANGEROUSLY_SKIP_PERMISSIONS Set to 'true' to auto-approve tool permissions
|
|
49
|
+
AGY_ACP_SKIP_NARRATION Set to 'true' to drop leading narration chunks
|
|
50
|
+
AGY_EXTRA_ARGS Extra space-separated CLI arguments passed to agy
|
|
47
51
|
AGY_BIN_PATH Path to agy binary (default: agy in PATH or ~/.local/bin/agy)
|
|
48
52
|
PASEO_SERVER_PATH Path to local @getpaseo/server directory
|
|
49
53
|
PASEO_ASAR_PATH Path to local Paseo app.asar package
|
|
@@ -150,7 +154,14 @@ if (args.includes("doctor") || args.includes("--doctor")) {
|
|
|
150
154
|
const paseoRunning = isPaseoRunning();
|
|
151
155
|
if (paseoRunning) {
|
|
152
156
|
process.stdout.write(" [!] Paseo process is currently running.\n");
|
|
153
|
-
|
|
157
|
+
if (isWin) {
|
|
158
|
+
process.stdout.write(" Tip: To restart Paseo completely on Windows, run in PowerShell:\n");
|
|
159
|
+
process.stdout.write(" Stop-Process -Name \"Paseo\" -Force -ErrorAction SilentlyContinue\n");
|
|
160
|
+
process.stdout.write(" Then launch Paseo again.\n");
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
process.stdout.write(" Tip: Run 'paseo daemon restart' or close the Paseo app to reload.\n");
|
|
164
|
+
}
|
|
154
165
|
}
|
|
155
166
|
else {
|
|
156
167
|
process.stdout.write(" [OK] Paseo is not currently running (safe to patch/update).\n");
|
|
@@ -180,7 +191,7 @@ if (args.includes("doctor") || args.includes("--doctor")) {
|
|
|
180
191
|
hasIssue = true;
|
|
181
192
|
if (paseoRunning) {
|
|
182
193
|
process.stdout.write(` - Found ${unpatchedCount} unpatched Paseo target(s), but Paseo is currently running.\n`);
|
|
183
|
-
process.stdout.write(" Action: Close Paseo
|
|
194
|
+
process.stdout.write(" Action: Close Paseo completely (Stop-Process -Name \"Paseo\" -Force), then run:\n");
|
|
184
195
|
process.stdout.write(" npx -y paseo-acp-agy setup\n");
|
|
185
196
|
}
|
|
186
197
|
else {
|
|
@@ -221,12 +232,21 @@ if (args.includes("setup") ||
|
|
|
221
232
|
const allPatched = [...res.patchedPaths, ...(res.patchedAsarPaths || [])];
|
|
222
233
|
if (allPatched.length > 0) {
|
|
223
234
|
process.stdout.write(`Successfully integrated with: \n${allPatched.map((p) => ` - ${p}`).join("\n")}\n\n` +
|
|
224
|
-
`Antigravity quota provider and context-window telemetry are now enabled!\n`
|
|
225
|
-
`Please restart Paseo (or run 'paseo daemon restart') to apply changes.\n`);
|
|
235
|
+
`Antigravity quota provider and context-window telemetry are now enabled!\n`);
|
|
226
236
|
}
|
|
227
237
|
else {
|
|
228
238
|
process.stdout.write("Paseo is already up-to-date and configured for Antigravity telemetry.\n");
|
|
229
239
|
}
|
|
240
|
+
if (isPaseoRunning()) {
|
|
241
|
+
process.stdout.write(`\n⚠️ IMPORTANT: Paseo is currently running in the background!\n` +
|
|
242
|
+
` Windows caches running executables in memory, so changes will only take effect after restarting Paseo.\n` +
|
|
243
|
+
` In PowerShell, run:\n` +
|
|
244
|
+
` Stop-Process -Name "Paseo" -Force -ErrorAction SilentlyContinue\n` +
|
|
245
|
+
` Then launch Paseo again.\n\n`);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
process.stdout.write(`Please start Paseo to apply changes.\n\n`);
|
|
249
|
+
}
|
|
230
250
|
if (res.errors.length > 0) {
|
|
231
251
|
process.stderr.write(`Notice: Some paths could not be modified (may require admin/close Paseo):\n${res.errors.map(e => ` - ${e}`).join("\n")}\n`);
|
|
232
252
|
}
|
|
@@ -239,7 +259,10 @@ if (args.includes("setup") ||
|
|
|
239
259
|
}
|
|
240
260
|
// Auto-run integration in background when starting ACP server
|
|
241
261
|
void ensurePaseoIntegration().catch(() => { });
|
|
242
|
-
const
|
|
262
|
+
const skipNarration = args.includes("--skip-naration") ||
|
|
263
|
+
args.includes("--skip-narration") ||
|
|
264
|
+
process.env.AGY_ACP_SKIP_NARRATION === "true";
|
|
265
|
+
const server = new ACPServer({ skipNarration });
|
|
243
266
|
const cleanup = async () => {
|
|
244
267
|
try {
|
|
245
268
|
await server.stop();
|
package/dist/paseo-patcher.js
CHANGED
|
@@ -3,6 +3,7 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
5
|
import { logger } from "./logger.js";
|
|
6
|
+
import { extractAll, createPackage, listPackage } from "./asar.js";
|
|
6
7
|
/**
|
|
7
8
|
* Searches the host machine for @getpaseo/server installations across
|
|
8
9
|
* Windows, macOS, and Linux.
|
|
@@ -160,7 +161,12 @@ export function findPaseoServerInstallations() {
|
|
|
160
161
|
}
|
|
161
162
|
// Filter out any paths that do not actually have a dist directory or package.json
|
|
162
163
|
const verified = [];
|
|
164
|
+
const seen = new Set();
|
|
163
165
|
for (const dir of candidates) {
|
|
166
|
+
const key = process.platform === "win32" ? dir.toLowerCase() : dir;
|
|
167
|
+
if (seen.has(key))
|
|
168
|
+
continue;
|
|
169
|
+
seen.add(key);
|
|
164
170
|
if (fs.existsSync(path.join(dir, "dist")) || fs.existsSync(path.join(dir, "package.json"))) {
|
|
165
171
|
verified.push(dir);
|
|
166
172
|
}
|
|
@@ -208,6 +214,11 @@ function resolveAgyBinary() {
|
|
|
208
214
|
path.join(localAppData, "Programs", "Antigravity", "agy.exe"),
|
|
209
215
|
path.join(programFiles, "Antigravity", "bin", "agy.exe"),
|
|
210
216
|
path.join(programFilesX86, "Antigravity", "bin", "agy.exe"),
|
|
217
|
+
path.join(localAppData, "Google", "Antigravity", "agy.exe"),
|
|
218
|
+
path.join(localAppData, "Google", "Antigravity", "bin", "agy.exe"),
|
|
219
|
+
path.join(home, ".antigravity", "bin", "agy.exe"),
|
|
220
|
+
path.join(home, ".antigravity", "agy.exe"),
|
|
221
|
+
path.join(home, ".gemini", "antigravity-cli", "bin", "agy.exe"),
|
|
211
222
|
path.join(localAppData, "Microsoft", "WindowsApps", "agy.exe"),
|
|
212
223
|
path.join(home, ".local", "bin", "agy.exe"),
|
|
213
224
|
path.join(appData, "npm", "agy.exe"),
|
|
@@ -241,6 +252,8 @@ function resolveAgyBinary() {
|
|
|
241
252
|
path.join(appData, "npm", "agy.cmd"),
|
|
242
253
|
path.join(localAppData, "npm", "agy.cmd"),
|
|
243
254
|
path.join(home, ".local", "bin", "agy.cmd"),
|
|
255
|
+
path.join(home, ".antigravity", "bin", "agy.cmd"),
|
|
256
|
+
path.join(home, ".gemini", "antigravity-cli", "bin", "agy.cmd"),
|
|
244
257
|
path.join(appData, "npm", "agy.bat"),
|
|
245
258
|
path.join(localAppData, "npm", "agy.bat"),
|
|
246
259
|
path.join(home, ".local", "bin", "agy.bat"),
|
|
@@ -290,13 +303,48 @@ export class AntigravityQuotaProvider {
|
|
|
290
303
|
const isWin = process.platform === "win32";
|
|
291
304
|
const bin = resolveAgyBinary();
|
|
292
305
|
this.binaryPath = bin;
|
|
293
|
-
const isBatch = isWin &&
|
|
306
|
+
const isBatch = isWin && (!bin.toLowerCase().endsWith(".exe"));
|
|
307
|
+
const home = os.homedir();
|
|
308
|
+
const appData = process.env.APPDATA || (home ? path.join(home, "AppData", "Roaming") : "");
|
|
309
|
+
const localAppData = process.env.LOCALAPPDATA || (home ? path.join(home, "AppData", "Local") : "");
|
|
310
|
+
const programFiles = process.env.ProgramFiles || "C:\\\\Program Files";
|
|
311
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\\\Program Files (x86)";
|
|
312
|
+
|
|
313
|
+
const extraPaths = isWin ? [
|
|
314
|
+
path.join(appData, "npm"),
|
|
315
|
+
path.join(localAppData, "npm"),
|
|
316
|
+
path.join(programFiles, "nodejs"),
|
|
317
|
+
path.join(programFilesX86, "nodejs"),
|
|
318
|
+
path.join(localAppData, "Programs", "Antigravity", "bin"),
|
|
319
|
+
path.join(localAppData, "Programs", "antigravity"),
|
|
320
|
+
path.join(home, ".antigravity", "bin"),
|
|
321
|
+
path.join(home, ".local", "bin"),
|
|
322
|
+
].filter(p => fs.existsSync(p)) : [];
|
|
323
|
+
|
|
324
|
+
const env = { ...process.env };
|
|
325
|
+
if (extraPaths.length > 0) {
|
|
326
|
+
const currentPath = env.PATH || env.Path || "";
|
|
327
|
+
const newPath = (currentPath ? currentPath + ";" : "") + extraPaths.join(";");
|
|
328
|
+
env.PATH = newPath;
|
|
329
|
+
env.Path = newPath;
|
|
330
|
+
}
|
|
331
|
+
|
|
294
332
|
const [usageRes, creditsRes] = await Promise.allSettled([
|
|
295
|
-
execFileAsync(bin, ["--print-timeout", "24h", "--print", "/usage"], { timeout: 15000, env
|
|
296
|
-
execFileAsync(bin, ["--print-timeout", "24h", "--print", "/credits"], { timeout: 15000, env
|
|
333
|
+
execFileAsync(bin, ["--print-timeout", "24h", "--print", "/usage"], { timeout: 15000, env, shell: isBatch, windowsHide: true }),
|
|
334
|
+
execFileAsync(bin, ["--print-timeout", "24h", "--print", "/credits"], { timeout: 15000, env, shell: isBatch, windowsHide: true }),
|
|
297
335
|
]);
|
|
298
336
|
|
|
299
|
-
|
|
337
|
+
if (usageRes.status === "rejected") {
|
|
338
|
+
const reason = usageRes.reason;
|
|
339
|
+
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
340
|
+
return unavailableUsage({
|
|
341
|
+
providerId: this.providerId,
|
|
342
|
+
displayName: "Antigravity",
|
|
343
|
+
error: \`Quota fetch failed: \${msg}\`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const rawUsageOut = usageRes.value.stdout || usageRes.value.stderr || "";
|
|
300
348
|
const rawCreditsOut = creditsRes.status === "fulfilled" ? creditsRes.value.stdout || creditsRes.value.stderr : "";
|
|
301
349
|
|
|
302
350
|
const usageOut = (rawUsageOut || "").replace(/\\r\\n/g, "\\n");
|
|
@@ -317,7 +365,14 @@ export class AntigravityQuotaProvider {
|
|
|
317
365
|
scope = m[1].trim();
|
|
318
366
|
limitType = m[2].trim();
|
|
319
367
|
remainingPct = parseInt(m[3], 10);
|
|
320
|
-
|
|
368
|
+
if (m[4]) {
|
|
369
|
+
try {
|
|
370
|
+
const d = new Date(m[4].trim());
|
|
371
|
+
resetsAt = isNaN(d.getTime()) ? null : d.toISOString();
|
|
372
|
+
} catch {
|
|
373
|
+
resetsAt = null;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
321
376
|
} else {
|
|
322
377
|
const parts = trimmed.split(/\\t+|\\s{2,}/).map(p => p.trim());
|
|
323
378
|
if (parts.length >= 3) {
|
|
@@ -325,7 +380,14 @@ export class AntigravityQuotaProvider {
|
|
|
325
380
|
limitType = parts[1];
|
|
326
381
|
const remMatch = parts[2].match(/(\\d+)%/);
|
|
327
382
|
if (remMatch) remainingPct = parseInt(remMatch[1], 10);
|
|
328
|
-
|
|
383
|
+
if (parts[3]) {
|
|
384
|
+
try {
|
|
385
|
+
const d = new Date(parts[3]);
|
|
386
|
+
resetsAt = isNaN(d.getTime()) ? null : d.toISOString();
|
|
387
|
+
} catch {
|
|
388
|
+
resetsAt = null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
329
391
|
}
|
|
330
392
|
}
|
|
331
393
|
|
|
@@ -649,7 +711,16 @@ export function findPaseoAsarPaths() {
|
|
|
649
711
|
candidates.add(path.resolve(loc));
|
|
650
712
|
}
|
|
651
713
|
}
|
|
652
|
-
|
|
714
|
+
const result = [];
|
|
715
|
+
const seen = new Set();
|
|
716
|
+
for (const loc of candidates) {
|
|
717
|
+
const key = process.platform === "win32" ? loc.toLowerCase() : loc;
|
|
718
|
+
if (!seen.has(key)) {
|
|
719
|
+
seen.add(key);
|
|
720
|
+
result.push(loc);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return result;
|
|
653
724
|
}
|
|
654
725
|
/**
|
|
655
726
|
* Extracts, patches, and repacks a Paseo app.asar archive to integrate Antigravity
|
|
@@ -663,11 +734,8 @@ export async function patchPaseoAsar(asarPath) {
|
|
|
663
734
|
if (!fs.existsSync(asarPath)) {
|
|
664
735
|
return { success: false, changes: [], error: `Asar archive not found: ${asarPath}` };
|
|
665
736
|
}
|
|
666
|
-
// Dynamic import of @electron/asar
|
|
667
|
-
const asarModule = await import("@electron/asar");
|
|
668
|
-
const asar = asarModule.default || asarModule;
|
|
669
737
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "paseo-asar-extract-"));
|
|
670
|
-
|
|
738
|
+
extractAll(asarPath, tempDir);
|
|
671
739
|
// Look for server directory in extracted files
|
|
672
740
|
const serverCandidates = [
|
|
673
741
|
path.join(tempDir, "node_modules", "@getpaseo", "server"),
|
|
@@ -722,7 +790,7 @@ export async function patchPaseoAsar(asarPath) {
|
|
|
722
790
|
}
|
|
723
791
|
}
|
|
724
792
|
tempAsar = path.join(os.tmpdir(), `app-${Date.now()}.asar`);
|
|
725
|
-
await
|
|
793
|
+
await createPackage(tempDir, tempAsar);
|
|
726
794
|
// Replace original archive with locked file handling for Windows
|
|
727
795
|
try {
|
|
728
796
|
fs.copyFileSync(tempAsar, asarPath);
|
|
@@ -871,9 +939,7 @@ export async function isPaseoAsarPatched(asarPath) {
|
|
|
871
939
|
try {
|
|
872
940
|
if (!fs.existsSync(asarPath))
|
|
873
941
|
return false;
|
|
874
|
-
const
|
|
875
|
-
const asar = asarModule.default || asarModule;
|
|
876
|
-
const files = asar.listPackage(asarPath);
|
|
942
|
+
const files = listPackage(asarPath);
|
|
877
943
|
return files.some((f) => f.includes("antigravity.js"));
|
|
878
944
|
}
|
|
879
945
|
catch {
|
package/dist/permissions.d.ts
CHANGED
|
@@ -12,4 +12,5 @@ export declare function resolvePermissionSettings(options?: {
|
|
|
12
12
|
addDirs?: string[];
|
|
13
13
|
printTimeout?: string;
|
|
14
14
|
}): PermissionSettings;
|
|
15
|
+
export declare function parseExtraArgs(raw?: string): string[];
|
|
15
16
|
export declare function buildAgyArgs(settings: PermissionSettings, extraArgs?: string[]): string[];
|
package/dist/permissions.js
CHANGED
|
@@ -20,6 +20,14 @@ export function resolvePermissionSettings(options) {
|
|
|
20
20
|
printTimeout,
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
+
export function parseExtraArgs(raw) {
|
|
24
|
+
if (!raw || !raw.trim())
|
|
25
|
+
return [];
|
|
26
|
+
const matches = raw.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
|
|
27
|
+
if (!matches)
|
|
28
|
+
return [];
|
|
29
|
+
return matches.map((m) => m.replace(/^["']|["']$/g, ""));
|
|
30
|
+
}
|
|
23
31
|
export function buildAgyArgs(settings, extraArgs) {
|
|
24
32
|
const args = ["--input-format", "stream-json", "--output-format", "stream-json", "--print="];
|
|
25
33
|
if (settings.printTimeout) {
|
|
@@ -41,6 +49,10 @@ export function buildAgyArgs(settings, extraArgs) {
|
|
|
41
49
|
}
|
|
42
50
|
}
|
|
43
51
|
}
|
|
52
|
+
const envExtra = parseExtraArgs(process.env.AGY_EXTRA_ARGS);
|
|
53
|
+
if (envExtra.length > 0) {
|
|
54
|
+
args.push(...envExtra);
|
|
55
|
+
}
|
|
44
56
|
if (extraArgs && extraArgs.length > 0) {
|
|
45
57
|
args.push(...extraArgs);
|
|
46
58
|
}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -113,10 +113,13 @@ export declare const ACP_METHODS: {
|
|
|
113
113
|
readonly SESSION_CLOSE: "session/close";
|
|
114
114
|
readonly SESSION_CLOSE_ALIAS: "unstable_closeSession";
|
|
115
115
|
readonly SESSION_SET_MODE: "session/set_mode";
|
|
116
|
+
readonly SESSION_SET_MODE_CAMEL: "session/setMode";
|
|
116
117
|
readonly SESSION_SET_MODE_ALIAS: "setSessionMode";
|
|
117
118
|
readonly SESSION_SET_MODEL: "session/set_model";
|
|
119
|
+
readonly SESSION_SET_MODEL_CAMEL: "session/setModel";
|
|
118
120
|
readonly SESSION_SET_MODEL_ALIAS: "unstable_setSessionModel";
|
|
119
121
|
readonly SESSION_SET_CONFIG_OPTION: "session/set_config_option";
|
|
122
|
+
readonly SESSION_SET_CONFIG_OPTION_CAMEL: "session/setConfigOption";
|
|
120
123
|
readonly SESSION_SET_CONFIG_OPTION_ALIAS: "setSessionConfigOption";
|
|
121
124
|
readonly SESSION_UPDATE: "session/update";
|
|
122
125
|
};
|
|
@@ -193,6 +196,11 @@ export declare const AVAILABLE_MODES: {
|
|
|
193
196
|
name: string;
|
|
194
197
|
description: string;
|
|
195
198
|
}[];
|
|
199
|
+
export declare const AVAILABLE_PERMISSIONS: {
|
|
200
|
+
id: string;
|
|
201
|
+
name: string;
|
|
202
|
+
description: string;
|
|
203
|
+
}[];
|
|
196
204
|
export declare function buildConfigOptionsForModel(modelId: string, currentEffort?: string, availableModels?: ModelDefinition[]): {
|
|
197
205
|
id: string;
|
|
198
206
|
name: string;
|
|
@@ -205,3 +213,40 @@ export declare function buildConfigOptionsForModel(modelId: string, currentEffor
|
|
|
205
213
|
description: string;
|
|
206
214
|
}[];
|
|
207
215
|
}[];
|
|
216
|
+
export declare function buildConfigOptionsForSession(options: {
|
|
217
|
+
modelId: string;
|
|
218
|
+
currentEffort?: string;
|
|
219
|
+
currentMode?: string;
|
|
220
|
+
currentPermission?: string;
|
|
221
|
+
availableModels?: ModelDefinition[];
|
|
222
|
+
}): {
|
|
223
|
+
id: string;
|
|
224
|
+
name: string;
|
|
225
|
+
category: string;
|
|
226
|
+
type: "select";
|
|
227
|
+
currentValue: string;
|
|
228
|
+
options: {
|
|
229
|
+
value: string;
|
|
230
|
+
name: string;
|
|
231
|
+
description: string;
|
|
232
|
+
}[];
|
|
233
|
+
}[];
|
|
234
|
+
/**
|
|
235
|
+
* Escapes characters in URLs/paths for Markdown image/link syntax.
|
|
236
|
+
* Escapes backslashes `\` and parentheses `)` to prevent premature link termination,
|
|
237
|
+
* especially crucial for Windows paths (e.g. C:\path\img (1).png).
|
|
238
|
+
*/
|
|
239
|
+
export declare function escapeMarkdownUrl(urlOrPath: string): string;
|
|
240
|
+
/**
|
|
241
|
+
* Normalizes an image path or file URL and formats it as an inline Markdown image link.
|
|
242
|
+
*/
|
|
243
|
+
export declare function formatImageMarkdown(filePathOrUrl: string): string;
|
|
244
|
+
/**
|
|
245
|
+
* Extracts an image reference from a tool execution output or parameters,
|
|
246
|
+
* returning a formatted Markdown image string if detected.
|
|
247
|
+
*/
|
|
248
|
+
export declare function extractImageMarkdownLink(toolName: string, output: unknown, parameters?: Record<string, unknown>): string | null;
|
|
249
|
+
/**
|
|
250
|
+
* Detects whether text consists entirely of conversational narration (e.g. "I will...", "I'll...").
|
|
251
|
+
*/
|
|
252
|
+
export declare function isNarrationText(text: string): boolean;
|
package/dist/protocol.js
CHANGED
|
@@ -23,10 +23,13 @@ export const ACP_METHODS = {
|
|
|
23
23
|
SESSION_CLOSE: "session/close",
|
|
24
24
|
SESSION_CLOSE_ALIAS: "unstable_closeSession",
|
|
25
25
|
SESSION_SET_MODE: "session/set_mode",
|
|
26
|
+
SESSION_SET_MODE_CAMEL: "session/setMode",
|
|
26
27
|
SESSION_SET_MODE_ALIAS: "setSessionMode",
|
|
27
28
|
SESSION_SET_MODEL: "session/set_model",
|
|
29
|
+
SESSION_SET_MODEL_CAMEL: "session/setModel",
|
|
28
30
|
SESSION_SET_MODEL_ALIAS: "unstable_setSessionModel",
|
|
29
31
|
SESSION_SET_CONFIG_OPTION: "session/set_config_option",
|
|
32
|
+
SESSION_SET_CONFIG_OPTION_CAMEL: "session/setConfigOption",
|
|
30
33
|
SESSION_SET_CONFIG_OPTION_ALIAS: "setSessionConfigOption",
|
|
31
34
|
SESSION_UPDATE: "session/update",
|
|
32
35
|
};
|
|
@@ -300,7 +303,7 @@ export async function fetchAntigravityUsage(binaryPath = "agy", force = false) {
|
|
|
300
303
|
now - lastProviderUsageFetch < PROVIDER_USAGE_CACHE_TTL_MS) {
|
|
301
304
|
return cachedProviderUsage;
|
|
302
305
|
}
|
|
303
|
-
const isBatch = process.platform === "win32" && isWindowsBatchScript(binaryPath);
|
|
306
|
+
const isBatch = process.platform === "win32" && (isWindowsBatchScript(binaryPath) || binaryPath === "agy");
|
|
304
307
|
const cmd = isBatch && binaryPath.includes(" ") && !binaryPath.startsWith('"') ? `"${binaryPath}"` : binaryPath;
|
|
305
308
|
try {
|
|
306
309
|
const [usageResult, creditsResult] = await Promise.allSettled([
|
|
@@ -438,7 +441,7 @@ export async function fetchAvailableModels(binaryPath = "agy", force = false) {
|
|
|
438
441
|
return inFlightModelFetch;
|
|
439
442
|
}
|
|
440
443
|
inFlightModelFetch = (async () => {
|
|
441
|
-
const isBatch = process.platform === "win32" && isWindowsBatchScript(binaryPath);
|
|
444
|
+
const isBatch = process.platform === "win32" && (isWindowsBatchScript(binaryPath) || binaryPath === "agy");
|
|
442
445
|
const cmd = isBatch && binaryPath.includes(" ") && !binaryPath.startsWith('"') ? `"${binaryPath}"` : binaryPath;
|
|
443
446
|
try {
|
|
444
447
|
const { stdout } = await execFileAsync(cmd, ["models"], {
|
|
@@ -485,6 +488,12 @@ export function getEffectiveEffortForModel(modelId, requestedEffort, models) {
|
|
|
485
488
|
export const AVAILABLE_MODES = [
|
|
486
489
|
{ id: "default", name: "Default (Accept Edits)", description: "Standard autonomous execution and coding mode" },
|
|
487
490
|
{ id: "plan", name: "Plan Mode", description: "Planning and read-only analysis without file modifications" },
|
|
491
|
+
{ id: "accept-edits", name: "Accept Edits", description: "Run agy with --mode accept-edits" },
|
|
492
|
+
];
|
|
493
|
+
export const AVAILABLE_PERMISSIONS = [
|
|
494
|
+
{ id: "default", name: "Default", description: "Standard permission flow" },
|
|
495
|
+
{ id: "sandbox", name: "Sandbox", description: "Run agy with --sandbox" },
|
|
496
|
+
{ id: "bypass", name: "Bypass", description: "Run agy with --dangerously-skip-permissions" },
|
|
488
497
|
];
|
|
489
498
|
export function buildConfigOptionsForModel(modelId, currentEffort = "high", availableModels) {
|
|
490
499
|
const modelList = availableModels || cachedModels || FALLBACK_MODELS;
|
|
@@ -513,3 +522,118 @@ export function buildConfigOptionsForModel(modelId, currentEffort = "high", avai
|
|
|
513
522
|
},
|
|
514
523
|
];
|
|
515
524
|
}
|
|
525
|
+
export function buildConfigOptionsForSession(options) {
|
|
526
|
+
const modelOptions = buildConfigOptionsForModel(options.modelId, options.currentEffort, options.availableModels);
|
|
527
|
+
const modeOption = {
|
|
528
|
+
id: "mode",
|
|
529
|
+
name: "Execution Mode",
|
|
530
|
+
category: "mode",
|
|
531
|
+
type: "select",
|
|
532
|
+
currentValue: options.currentMode || "default",
|
|
533
|
+
options: AVAILABLE_MODES.map((m) => ({
|
|
534
|
+
value: m.id,
|
|
535
|
+
name: m.name,
|
|
536
|
+
description: m.description,
|
|
537
|
+
})),
|
|
538
|
+
};
|
|
539
|
+
const permissionOption = {
|
|
540
|
+
id: "permission",
|
|
541
|
+
name: "Permission Policy",
|
|
542
|
+
category: "permission",
|
|
543
|
+
type: "select",
|
|
544
|
+
currentValue: options.currentPermission || "default",
|
|
545
|
+
options: AVAILABLE_PERMISSIONS.map((p) => ({
|
|
546
|
+
value: p.id,
|
|
547
|
+
name: p.name,
|
|
548
|
+
description: p.description,
|
|
549
|
+
})),
|
|
550
|
+
};
|
|
551
|
+
return [...modelOptions, modeOption, permissionOption];
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Escapes characters in URLs/paths for Markdown image/link syntax.
|
|
555
|
+
* Escapes backslashes `\` and parentheses `)` to prevent premature link termination,
|
|
556
|
+
* especially crucial for Windows paths (e.g. C:\path\img (1).png).
|
|
557
|
+
*/
|
|
558
|
+
export function escapeMarkdownUrl(urlOrPath) {
|
|
559
|
+
return urlOrPath.replace(/\\/g, "\\\\").replace(/\)/g, "\\)");
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Normalizes an image path or file URL and formats it as an inline Markdown image link.
|
|
563
|
+
*/
|
|
564
|
+
export function formatImageMarkdown(filePathOrUrl) {
|
|
565
|
+
let clean = filePathOrUrl.trim();
|
|
566
|
+
if (clean.startsWith("file://")) {
|
|
567
|
+
try {
|
|
568
|
+
clean = fileURLToPath(clean);
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
clean = clean.replace(/^file:\/\//, "");
|
|
572
|
+
if (process.platform === "win32" && /^\/[a-zA-Z]:/.test(clean)) {
|
|
573
|
+
clean = clean.slice(1);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return `})`;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Extracts an image reference from a tool execution output or parameters,
|
|
581
|
+
* returning a formatted Markdown image string if detected.
|
|
582
|
+
*/
|
|
583
|
+
export function extractImageMarkdownLink(toolName, output, parameters) {
|
|
584
|
+
const name = toolName.toLowerCase();
|
|
585
|
+
const isImageTool = name.includes("image") || name.includes("generate_image");
|
|
586
|
+
let rawPath = null;
|
|
587
|
+
if (typeof output === "string") {
|
|
588
|
+
const fileUriMatch = output.match(/file:\/\/[^\s)"]+\.(png|jpe?g|webp|gif|svg|bmp)/i);
|
|
589
|
+
if (fileUriMatch) {
|
|
590
|
+
rawPath = fileUriMatch[0];
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
const winPathMatch = output.match(/[a-zA-Z]:\\[^\s)"]+\.(png|jpe?g|webp|gif|svg|bmp)/i);
|
|
594
|
+
if (winPathMatch) {
|
|
595
|
+
rawPath = winPathMatch[0];
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
const posixPathMatch = output.match(/\/[^\s)"]+\.(png|jpe?g|webp|gif|svg|bmp)/i);
|
|
599
|
+
if (posixPathMatch) {
|
|
600
|
+
rawPath = posixPathMatch[0];
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
else if (output && typeof output === "object") {
|
|
606
|
+
const obj = output;
|
|
607
|
+
if (typeof obj.path === "string")
|
|
608
|
+
rawPath = obj.path;
|
|
609
|
+
else if (typeof obj.filePath === "string")
|
|
610
|
+
rawPath = obj.filePath;
|
|
611
|
+
else if (typeof obj.uri === "string")
|
|
612
|
+
rawPath = obj.uri;
|
|
613
|
+
}
|
|
614
|
+
if (!rawPath && isImageTool && parameters) {
|
|
615
|
+
if (typeof parameters.TargetFile === "string" &&
|
|
616
|
+
/\.(png|jpe?g|webp|gif|svg|bmp)$/i.test(parameters.TargetFile)) {
|
|
617
|
+
rawPath = parameters.TargetFile;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (!rawPath)
|
|
621
|
+
return null;
|
|
622
|
+
return formatImageMarkdown(rawPath);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Detects whether text consists entirely of conversational narration (e.g. "I will...", "I'll...").
|
|
626
|
+
*/
|
|
627
|
+
export function isNarrationText(text) {
|
|
628
|
+
const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
629
|
+
if (lines.length === 0)
|
|
630
|
+
return false;
|
|
631
|
+
return lines.every((l) => {
|
|
632
|
+
const lower = l.toLowerCase();
|
|
633
|
+
return (lower.startsWith("i will") ||
|
|
634
|
+
lower.startsWith("i'll") ||
|
|
635
|
+
lower.startsWith("i’ll") ||
|
|
636
|
+
lower.startsWith("let me") ||
|
|
637
|
+
lower.startsWith("i am going to"));
|
|
638
|
+
});
|
|
639
|
+
}
|
package/dist/session-store.d.ts
CHANGED
package/dist/session-store.js
CHANGED
|
@@ -7,6 +7,9 @@ function getStateRoot() {
|
|
|
7
7
|
if (process.env.AGY_ACP_STATE_DIR) {
|
|
8
8
|
return process.env.AGY_ACP_STATE_DIR;
|
|
9
9
|
}
|
|
10
|
+
if (process.platform === "win32" && process.env.LOCALAPPDATA) {
|
|
11
|
+
return path.join(process.env.LOCALAPPDATA, "agy-acp");
|
|
12
|
+
}
|
|
10
13
|
const stateHome = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
|
|
11
14
|
return path.join(stateHome, "agy-acp");
|
|
12
15
|
}
|
package/dist/session.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface SessionOptions {
|
|
|
6
6
|
model?: string;
|
|
7
7
|
effort?: string;
|
|
8
8
|
mode?: string;
|
|
9
|
+
permission?: string;
|
|
9
10
|
conversationId?: string;
|
|
10
11
|
usage?: SessionUsageState;
|
|
11
12
|
sandbox?: boolean;
|
|
@@ -20,6 +21,7 @@ export declare class Session {
|
|
|
20
21
|
model: string;
|
|
21
22
|
effort: string;
|
|
22
23
|
mode: string;
|
|
24
|
+
permission: string;
|
|
23
25
|
readonly createdAt: Date;
|
|
24
26
|
lastActivity: Date;
|
|
25
27
|
isCancelled: boolean;
|
|
@@ -36,6 +38,7 @@ export declare class Session {
|
|
|
36
38
|
setModel(model: string): void;
|
|
37
39
|
setEffort(effort: string): void;
|
|
38
40
|
setMode(mode: string): void;
|
|
41
|
+
setPermission(permission: string): void;
|
|
39
42
|
setConversationId(conversationId?: string): void;
|
|
40
43
|
ensureReadyForResume(): Promise<void>;
|
|
41
44
|
resumeConversation(conversationId: string): Promise<void>;
|
package/dist/session.js
CHANGED
|
@@ -10,6 +10,7 @@ export class Session {
|
|
|
10
10
|
model;
|
|
11
11
|
effort;
|
|
12
12
|
mode;
|
|
13
|
+
permission;
|
|
13
14
|
createdAt;
|
|
14
15
|
lastActivity;
|
|
15
16
|
isCancelled = false;
|
|
@@ -23,6 +24,9 @@ export class Session {
|
|
|
23
24
|
this.model = options.model || "gemini-3.7-flash";
|
|
24
25
|
this.effort = options.effort || "high";
|
|
25
26
|
this.mode = options.mode || "default";
|
|
27
|
+
this.permission =
|
|
28
|
+
options.permission ||
|
|
29
|
+
(options.sandbox ? "sandbox" : options.dangerouslySkipPermissions ? "bypass" : "default");
|
|
26
30
|
this.createdAt = new Date();
|
|
27
31
|
this.lastActivity = new Date();
|
|
28
32
|
this.store = options.store || sessionStore;
|
|
@@ -35,9 +39,11 @@ export class Session {
|
|
|
35
39
|
contextWindowUsedTokens: 0,
|
|
36
40
|
contextWindowMaxTokens: getModelContextWindow(this.model),
|
|
37
41
|
};
|
|
42
|
+
const isSandbox = this.permission === "sandbox" || options.sandbox;
|
|
43
|
+
const isBypass = this.permission === "bypass" || options.dangerouslySkipPermissions;
|
|
38
44
|
const permissions = resolvePermissionSettings({
|
|
39
|
-
sandbox:
|
|
40
|
-
dangerouslySkipPermissions:
|
|
45
|
+
sandbox: isSandbox,
|
|
46
|
+
dangerouslySkipPermissions: isBypass,
|
|
41
47
|
mode: this.mode,
|
|
42
48
|
});
|
|
43
49
|
this.process = new AntigravityProcess({
|
|
@@ -62,6 +68,7 @@ export class Session {
|
|
|
62
68
|
model: this.model,
|
|
63
69
|
effort: this.effort,
|
|
64
70
|
mode: this.mode,
|
|
71
|
+
permission: this.permission,
|
|
65
72
|
usage: this.usage,
|
|
66
73
|
updatedAt: new Date().toISOString(),
|
|
67
74
|
};
|
|
@@ -98,6 +105,16 @@ export class Session {
|
|
|
98
105
|
this.process.setMode(mode);
|
|
99
106
|
this.persist();
|
|
100
107
|
}
|
|
108
|
+
setPermission(permission) {
|
|
109
|
+
this.permission = permission;
|
|
110
|
+
const isSandbox = permission === "sandbox";
|
|
111
|
+
const isBypass = permission === "bypass";
|
|
112
|
+
this.process.setPermissions({
|
|
113
|
+
sandbox: isSandbox,
|
|
114
|
+
dangerouslySkipPermissions: isBypass || (!isSandbox && this.process.currentPermissions.dangerouslySkipPermissions),
|
|
115
|
+
});
|
|
116
|
+
this.persist();
|
|
117
|
+
}
|
|
101
118
|
setConversationId(conversationId) {
|
|
102
119
|
this.process.setConversationId(conversationId);
|
|
103
120
|
this.persist();
|
|
@@ -185,6 +202,7 @@ export class SessionManager {
|
|
|
185
202
|
model: options.model || persisted?.model,
|
|
186
203
|
effort: options.effort || persisted?.effort,
|
|
187
204
|
mode: options.mode || persisted?.mode,
|
|
205
|
+
permission: options.permission || persisted?.permission,
|
|
188
206
|
conversationId: options.conversationId || persisted?.conversationId,
|
|
189
207
|
usage: options.usage || persisted?.usage,
|
|
190
208
|
sandbox: options.sandbox,
|
package/dist/slash-commands.js
CHANGED
|
@@ -337,7 +337,7 @@ export function formatUsageOutput(rawText) {
|
|
|
337
337
|
return out;
|
|
338
338
|
}
|
|
339
339
|
async function runAgySlash(binaryPath, cwd, slashCommand) {
|
|
340
|
-
const isBatch = process.platform === "win32" && isWindowsBatchScript(binaryPath);
|
|
340
|
+
const isBatch = process.platform === "win32" && (isWindowsBatchScript(binaryPath) || binaryPath === "agy");
|
|
341
341
|
const cmd = isBatch && binaryPath.includes(" ") && !binaryPath.startsWith('"') ? `"${binaryPath}"` : binaryPath;
|
|
342
342
|
const { stdout, stderr } = await execFileAsync(cmd, ["--print-timeout", "24h", "--print", slashCommand], {
|
|
343
343
|
cwd,
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paseo-acp-agy",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "ACP (Agent Client Protocol) provider for Google Antigravity in Paseo and Zed",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -43,10 +43,11 @@
|
|
|
43
43
|
"bugs": {
|
|
44
44
|
"url": "https://github.com/tucomel/paseo-acp-agy/issues"
|
|
45
45
|
},
|
|
46
|
-
"
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.0.0"
|
|
48
|
+
},
|
|
47
49
|
"dependencies": {
|
|
48
|
-
"@agentclientprotocol/sdk": "^0.17.1"
|
|
49
|
-
"@electron/asar": "^4.3.0"
|
|
50
|
+
"@agentclientprotocol/sdk": "^0.17.1"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@types/node": "^22.0.0",
|