paseo-acp-agy 1.2.15 → 1.3.0
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/README.md +14 -9
- package/dist/acp-server.d.ts +12 -0
- package/dist/acp-server.js +193 -29
- package/dist/antigravity-process.js +14 -3
- package/dist/asar.d.ts +5 -0
- package/dist/asar.js +34 -0
- package/dist/index.js +36 -9
- package/dist/paseo-patcher.d.ts +11 -1
- package/dist/paseo-patcher.js +223 -16
- package/dist/protocol.d.ts +1 -1
- package/dist/session.d.ts +1 -0
- package/dist/session.js +13 -1
- package/dist/tool-formatter.js +1 -1
- package/dist/transcript.d.ts +5 -0
- package/dist/transcript.js +30 -0
- package/dist/version.js +8 -1
- package/package.json +2 -1
- package/paseo-plugin/client/data.ts +111 -0
- package/paseo-plugin/client/format.ts +61 -0
- package/paseo-plugin/client/pill.tsx +208 -0
- package/paseo-plugin/client/ui-helpers.ts +8 -0
- package/paseo-plugin/client/ui.tsx +379 -0
- package/paseo-plugin/icon.svg +5 -0
- package/paseo-plugin/index.client.tsx +44 -0
- package/paseo-plugin/index.server.ts +47 -0
- package/paseo-plugin/paseo-plugin.json +7 -0
- package/paseo-plugin/server/agy-usage.ts +621 -0
- package/paseo-plugin/shared/telemetry.ts +77 -0
- package/paseo-plugin/tsconfig.json +20 -0
package/README.md
CHANGED
|
@@ -92,24 +92,29 @@ To include `paseo-acp-agy` in Paseo's built-in provider store (`ACP_PROVIDER_CAT
|
|
|
92
92
|
|
|
93
93
|
## Plan Usage & Context Window Meter Setup
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
### Recommended (Paseo >= 0.11.0): Official Update-Proof Paseo Plugin
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
npx -y paseo-acp-agy setup
|
|
99
|
-
```
|
|
97
|
+
Install the bundled **`antigravity-telemetry`** Paseo plugin once. It registers in `~/.paseo/config.json` and survives all future `Paseo.app`, Web, iPhone, and CLI updates without modifying signed `.app` bundles:
|
|
100
98
|
|
|
101
|
-
Or if installed globally:
|
|
102
99
|
```bash
|
|
103
|
-
paseo-acp-agy
|
|
100
|
+
paseo plugin install "$(paseo-acp-agy --plugin-path)"
|
|
104
101
|
```
|
|
105
102
|
|
|
106
|
-
|
|
103
|
+
What this provides across Desktop, Web, and Mobile (iPhone):
|
|
104
|
+
- Native **Google Antigravity** quota gauges in Paseo's Sidebar and `Settings -> Usage` (`5h` window, weekly pool, credits)
|
|
105
|
+
- Live per-agent **Composer Pill** (`$cost · ctx X% · 5h Y%`) with interactive Popover / Mobile Bottom Sheet
|
|
106
|
+
- Dedicated **Antigravity Telemetry** workspace panel, Command Center action, and `/agy-telemetry` slash command
|
|
107
|
+
- Combined with native ACP `usage_update` notifications so Paseo's built-in `ContextWindowMeter` ("bolinha") stays deterministic on every session open, resume, model switch, and turn completion
|
|
108
|
+
|
|
109
|
+
### Standalone Server Patch (Optional, Linux / Paseo < 0.11.0)
|
|
110
|
+
|
|
111
|
+
For standalone `@getpaseo/cli` servers where you prefer direct server-level patching:
|
|
112
|
+
|
|
107
113
|
```bash
|
|
114
|
+
paseo-acp-agy setup
|
|
108
115
|
paseo daemon restart
|
|
109
116
|
```
|
|
110
117
|
|
|
111
|
-
> **Note**: `paseo-acp-agy` also checks and auto-configures your local Paseo server automatically whenever it launches via `--acp`. Running `setup` manually ensures your existing daemon instance picks up the telemetry hooks immediately upon restart.
|
|
112
|
-
|
|
113
118
|
---
|
|
114
119
|
|
|
115
120
|
## Requirements
|
package/dist/acp-server.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export declare class ACPServer extends EventEmitter {
|
|
|
10
10
|
private skipNarration;
|
|
11
11
|
private rl;
|
|
12
12
|
private isRunning;
|
|
13
|
+
private pendingRequests;
|
|
14
|
+
private usagePublishTimers;
|
|
15
|
+
private nextRequestId;
|
|
13
16
|
constructor(options?: {
|
|
14
17
|
input?: Readable;
|
|
15
18
|
output?: Writable;
|
|
@@ -22,8 +25,17 @@ export declare class ACPServer extends EventEmitter {
|
|
|
22
25
|
private sendNotification;
|
|
23
26
|
private sendSuccess;
|
|
24
27
|
private sendError;
|
|
28
|
+
sendRequest<T = any>(method: string, params: unknown): Promise<T>;
|
|
25
29
|
private publishCommands;
|
|
26
30
|
private publishUsageUpdate;
|
|
31
|
+
private clearUsagePublishTimers;
|
|
32
|
+
/**
|
|
33
|
+
* Schedules coalesced, unref'd usage_update notifications so ACP hosts (like Paseo)
|
|
34
|
+
* deterministically receive context window & session telemetry:
|
|
35
|
+
* - after awaiting newSession/loadSession + applyConfiguredOverrides() + session.subscribe()
|
|
36
|
+
* - after turn_completed overwrites or initializes currentTurnUsage in Paseo 0.10..0.13+
|
|
37
|
+
*/
|
|
38
|
+
private scheduleUsagePublishBurst;
|
|
27
39
|
private sessionState;
|
|
28
40
|
private requireSession;
|
|
29
41
|
private validateModel;
|
package/dist/acp-server.js
CHANGED
|
@@ -6,7 +6,7 @@ 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
|
-
import { loadConversationTranscript } from "./transcript.js";
|
|
9
|
+
import { loadConversationTranscript, estimateReplayContextTokens } from "./transcript.js";
|
|
10
10
|
import { normalizeToolCall, normalizeToolResult, extractPlanEntries } from "./tool-formatter.js";
|
|
11
11
|
import { McpBridge } from "./mcp-bridge.js";
|
|
12
12
|
function splitModelAndEffort(modelInput) {
|
|
@@ -29,6 +29,9 @@ export class ACPServer extends EventEmitter {
|
|
|
29
29
|
skipNarration;
|
|
30
30
|
rl = null;
|
|
31
31
|
isRunning = false;
|
|
32
|
+
pendingRequests = new Map();
|
|
33
|
+
usagePublishTimers = new Map();
|
|
34
|
+
nextRequestId = 1;
|
|
32
35
|
constructor(options = {}) {
|
|
33
36
|
super();
|
|
34
37
|
this.input = options.input || process.stdin;
|
|
@@ -98,6 +101,13 @@ export class ACPServer extends EventEmitter {
|
|
|
98
101
|
};
|
|
99
102
|
this.send(response);
|
|
100
103
|
}
|
|
104
|
+
sendRequest(method, params) {
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
const id = this.nextRequestId++;
|
|
107
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
108
|
+
this.send({ jsonrpc: "2.0", id, method, params });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
101
111
|
publishCommands(sessionId) {
|
|
102
112
|
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
103
113
|
sessionId,
|
|
@@ -129,6 +139,53 @@ export class ACPServer extends EventEmitter {
|
|
|
129
139
|
},
|
|
130
140
|
});
|
|
131
141
|
}
|
|
142
|
+
clearUsagePublishTimers(sessionId) {
|
|
143
|
+
if (sessionId !== undefined) {
|
|
144
|
+
const timers = this.usagePublishTimers.get(sessionId);
|
|
145
|
+
if (timers) {
|
|
146
|
+
for (const t of timers)
|
|
147
|
+
clearTimeout(t);
|
|
148
|
+
this.usagePublishTimers.delete(sessionId);
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
for (const timers of this.usagePublishTimers.values()) {
|
|
153
|
+
for (const t of timers)
|
|
154
|
+
clearTimeout(t);
|
|
155
|
+
}
|
|
156
|
+
this.usagePublishTimers.clear();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Schedules coalesced, unref'd usage_update notifications so ACP hosts (like Paseo)
|
|
160
|
+
* deterministically receive context window & session telemetry:
|
|
161
|
+
* - after awaiting newSession/loadSession + applyConfiguredOverrides() + session.subscribe()
|
|
162
|
+
* - after turn_completed overwrites or initializes currentTurnUsage in Paseo 0.10..0.13+
|
|
163
|
+
*/
|
|
164
|
+
scheduleUsagePublishBurst(sessionId, delaysMs) {
|
|
165
|
+
this.clearUsagePublishTimers(sessionId);
|
|
166
|
+
if (!this.isRunning || delaysMs.length === 0)
|
|
167
|
+
return;
|
|
168
|
+
const timers = new Set();
|
|
169
|
+
this.usagePublishTimers.set(sessionId, timers);
|
|
170
|
+
for (const delayMs of delaysMs) {
|
|
171
|
+
const timer = setTimeout(() => {
|
|
172
|
+
timers.delete(timer);
|
|
173
|
+
if (timers.size === 0) {
|
|
174
|
+
this.usagePublishTimers.delete(sessionId);
|
|
175
|
+
}
|
|
176
|
+
if (!this.isRunning)
|
|
177
|
+
return;
|
|
178
|
+
const current = this.sessionManager.getSession(sessionId);
|
|
179
|
+
if (current) {
|
|
180
|
+
this.publishUsageUpdate(current);
|
|
181
|
+
}
|
|
182
|
+
}, delayMs);
|
|
183
|
+
if (typeof timer.unref === "function") {
|
|
184
|
+
timer.unref();
|
|
185
|
+
}
|
|
186
|
+
timers.add(timer);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
132
189
|
async sessionState(session, forceModels = false) {
|
|
133
190
|
const availableModels = await fetchAvailableModels(this.binaryPath, forceModels);
|
|
134
191
|
return {
|
|
@@ -198,6 +255,20 @@ export class ACPServer extends EventEmitter {
|
|
|
198
255
|
const isNotification = id === undefined || id === null;
|
|
199
256
|
const params = (parsed.params || {});
|
|
200
257
|
logger.debug("Received ACP message", { method, isNotification, id });
|
|
258
|
+
// Handle responses to requests initiated by the server (e.g. session/request_permission)
|
|
259
|
+
if (id !== undefined && !method) {
|
|
260
|
+
const pending = this.pendingRequests.get(id);
|
|
261
|
+
if (pending) {
|
|
262
|
+
this.pendingRequests.delete(id);
|
|
263
|
+
if (parsed.error) {
|
|
264
|
+
pending.reject(parsed.error);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
pending.resolve(parsed.result);
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
201
272
|
try {
|
|
202
273
|
switch (method) {
|
|
203
274
|
case ACP_METHODS.INITIALIZE: {
|
|
@@ -242,17 +313,9 @@ export class ACPServer extends EventEmitter {
|
|
|
242
313
|
this.sendSuccess(id, await this.sessionState(session, true));
|
|
243
314
|
this.publishCommands(session.id);
|
|
244
315
|
this.publishUsageUpdate(session);
|
|
245
|
-
// Re-publish usage
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
this.publishUsageUpdate(session);
|
|
249
|
-
}
|
|
250
|
-
}, 150);
|
|
251
|
-
setTimeout(() => {
|
|
252
|
-
if (this.sessionManager.getSession(session.id)) {
|
|
253
|
-
this.publishUsageUpdate(session);
|
|
254
|
-
}
|
|
255
|
-
}, 500);
|
|
316
|
+
// Re-publish usage across startup window so ACPAgent receives it after
|
|
317
|
+
// awaiting newSession + applyConfiguredOverrides() + session.subscribe()
|
|
318
|
+
this.scheduleUsagePublishBurst(session.id, [120, 450, 1200, 2800]);
|
|
256
319
|
}
|
|
257
320
|
break;
|
|
258
321
|
}
|
|
@@ -288,6 +351,9 @@ export class ACPServer extends EventEmitter {
|
|
|
288
351
|
session.setTitle(firstUser.text.slice(0, 80).replace(/\r?\n/g, " ").trim());
|
|
289
352
|
}
|
|
290
353
|
}
|
|
354
|
+
if (replayItems.length > 0) {
|
|
355
|
+
session.seedEstimatedContextUsage(estimateReplayContextTokens(replayItems));
|
|
356
|
+
}
|
|
291
357
|
for (const item of replayItems) {
|
|
292
358
|
switch (item.type) {
|
|
293
359
|
case "user_message":
|
|
@@ -355,12 +421,7 @@ export class ACPServer extends EventEmitter {
|
|
|
355
421
|
this.sendSuccess(id, await this.sessionState(session));
|
|
356
422
|
this.publishCommands(session.id);
|
|
357
423
|
this.publishUsageUpdate(session);
|
|
358
|
-
|
|
359
|
-
setTimeout(() => {
|
|
360
|
-
if (this.sessionManager.getSession(activeSession.id)) {
|
|
361
|
-
this.publishUsageUpdate(activeSession);
|
|
362
|
-
}
|
|
363
|
-
}, 150);
|
|
424
|
+
this.scheduleUsagePublishBurst(session.id, [120, 450, 1200, 2800]);
|
|
364
425
|
}
|
|
365
426
|
}
|
|
366
427
|
catch (err) {
|
|
@@ -391,10 +452,23 @@ export class ACPServer extends EventEmitter {
|
|
|
391
452
|
created = true;
|
|
392
453
|
}
|
|
393
454
|
await session.ensureReadyForResume();
|
|
455
|
+
const resumeConversationId = session.process.currentConversationId;
|
|
456
|
+
if (resumeConversationId &&
|
|
457
|
+
session.usage.contextWindowUsedTokens === 0 &&
|
|
458
|
+
session.usage.totalTokens === 0) {
|
|
459
|
+
try {
|
|
460
|
+
const replayItems = await loadConversationTranscript(resumeConversationId);
|
|
461
|
+
if (replayItems.length > 0) {
|
|
462
|
+
session.seedEstimatedContextUsage(estimateReplayContextTokens(replayItems));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch { }
|
|
466
|
+
}
|
|
394
467
|
if (!isNotification) {
|
|
395
468
|
this.sendSuccess(id, await this.sessionState(session));
|
|
396
469
|
this.publishCommands(session.id);
|
|
397
470
|
this.publishUsageUpdate(session);
|
|
471
|
+
this.scheduleUsagePublishBurst(session.id, [120, 450, 1200, 2800]);
|
|
398
472
|
}
|
|
399
473
|
}
|
|
400
474
|
catch (err) {
|
|
@@ -435,6 +509,17 @@ export class ACPServer extends EventEmitter {
|
|
|
435
509
|
mode: sourceSession?.mode,
|
|
436
510
|
permission: sourceSession?.permission,
|
|
437
511
|
conversationId: sourceSession?.process.currentConversationId,
|
|
512
|
+
usage: sourceSession
|
|
513
|
+
? {
|
|
514
|
+
inputTokens: 0,
|
|
515
|
+
outputTokens: 0,
|
|
516
|
+
cachedInputTokens: 0,
|
|
517
|
+
totalTokens: 0,
|
|
518
|
+
totalCostUsd: 0,
|
|
519
|
+
contextWindowUsedTokens: sourceSession.usage.contextWindowUsedTokens,
|
|
520
|
+
contextWindowMaxTokens: sourceSession.usage.contextWindowMaxTokens,
|
|
521
|
+
}
|
|
522
|
+
: undefined,
|
|
438
523
|
binaryPath: this.binaryPath,
|
|
439
524
|
});
|
|
440
525
|
if (sourceSession?.title) {
|
|
@@ -444,6 +529,7 @@ export class ACPServer extends EventEmitter {
|
|
|
444
529
|
this.sendSuccess(id, await this.sessionState(newSession));
|
|
445
530
|
this.publishCommands(newSession.id);
|
|
446
531
|
this.publishUsageUpdate(newSession);
|
|
532
|
+
this.scheduleUsagePublishBurst(newSession.id, [120, 450, 1200]);
|
|
447
533
|
}
|
|
448
534
|
break;
|
|
449
535
|
}
|
|
@@ -494,8 +580,11 @@ export class ACPServer extends EventEmitter {
|
|
|
494
580
|
},
|
|
495
581
|
});
|
|
496
582
|
}
|
|
497
|
-
|
|
583
|
+
this.publishUsageUpdate(session);
|
|
584
|
+
if (!isNotification) {
|
|
498
585
|
this.sendSuccess(id, { stopReason: "end_turn" });
|
|
586
|
+
this.scheduleUsagePublishBurst(session.id, [25, 150]);
|
|
587
|
+
}
|
|
499
588
|
break;
|
|
500
589
|
}
|
|
501
590
|
let hasSentNonNarration = false;
|
|
@@ -559,13 +648,70 @@ export class ACPServer extends EventEmitter {
|
|
|
559
648
|
rawInput: toolCall.rawInput,
|
|
560
649
|
},
|
|
561
650
|
});
|
|
562
|
-
if (
|
|
563
|
-
|
|
651
|
+
if (toolName === "ask_question") {
|
|
652
|
+
const rawParams = step.tool_info.parameters || {};
|
|
653
|
+
const rawQuestions = Array.isArray(rawParams.questions)
|
|
654
|
+
? rawParams.questions
|
|
655
|
+
: [];
|
|
656
|
+
const options = [];
|
|
657
|
+
for (let qIdx = 0; qIdx < rawQuestions.length; qIdx++) {
|
|
658
|
+
const q = rawQuestions[qIdx];
|
|
659
|
+
const opts = Array.isArray(q.options) ? q.options : [];
|
|
660
|
+
for (let optIdx = 0; optIdx < opts.length; optIdx++) {
|
|
661
|
+
const opt = opts[optIdx];
|
|
662
|
+
const clean = typeof opt === "string" ? opt.replace(/^\(Recommended\)\s*/i, "").trim() : String(opt);
|
|
663
|
+
const isRec = typeof opt === "string" && (opt.includes("(Recommended)") || opt.includes("(Recomendado)"));
|
|
664
|
+
options.push({
|
|
665
|
+
optionId: `q${qIdx + 1}_opt${optIdx + 1}`,
|
|
666
|
+
name: `${clean}${isRec ? " (Recomendado)" : ""}`,
|
|
667
|
+
kind: "allow_once",
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
options.push({
|
|
672
|
+
optionId: "cancel",
|
|
673
|
+
name: "Cancelar",
|
|
674
|
+
kind: "reject_once",
|
|
675
|
+
});
|
|
676
|
+
const promptText = rawQuestions.map((q) => q.question).filter(Boolean).join(" / ") || "Pergunta do Assistente";
|
|
677
|
+
this.sendRequest("session/request_permission", {
|
|
564
678
|
sessionId: session.id,
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
679
|
+
toolCall: {
|
|
680
|
+
toolCallId: toolCall.toolCallId,
|
|
681
|
+
title: toolCall.title,
|
|
682
|
+
kind: "question",
|
|
683
|
+
content: [{ type: "text", text: promptText }],
|
|
684
|
+
rawInput: toolCall.rawInput,
|
|
568
685
|
},
|
|
686
|
+
options,
|
|
687
|
+
questions: rawQuestions,
|
|
688
|
+
}).then((permRes) => {
|
|
689
|
+
logger.info("Interactive question answered in Paseo", { permRes });
|
|
690
|
+
const outcome = permRes?.outcome;
|
|
691
|
+
let answerText = "";
|
|
692
|
+
if (outcome?._meta?.answers && typeof outcome._meta.answers === "object") {
|
|
693
|
+
answerText = Object.entries(outcome._meta.answers)
|
|
694
|
+
.map(([k, v]) => `• **${k}**: ${v}`)
|
|
695
|
+
.join("\n");
|
|
696
|
+
}
|
|
697
|
+
else if (outcome?.optionId && outcome.optionId !== "cancel") {
|
|
698
|
+
const matched = options.find((o) => o.optionId === outcome.optionId);
|
|
699
|
+
answerText = matched ? matched.name : outcome.optionId;
|
|
700
|
+
}
|
|
701
|
+
if (answerText) {
|
|
702
|
+
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
703
|
+
sessionId: session.id,
|
|
704
|
+
update: {
|
|
705
|
+
sessionUpdate: "agent_message_chunk",
|
|
706
|
+
content: {
|
|
707
|
+
type: "text",
|
|
708
|
+
text: `\n\n> 💡 **Opção Selecionada:**\n${answerText}\n\n`,
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}).catch((err) => {
|
|
714
|
+
logger.debug("Interactive question cancelled or dismissed", { error: err.message });
|
|
569
715
|
});
|
|
570
716
|
}
|
|
571
717
|
}
|
|
@@ -674,6 +820,7 @@ export class ACPServer extends EventEmitter {
|
|
|
674
820
|
if (session.isCancelled) {
|
|
675
821
|
if (!isNotification) {
|
|
676
822
|
this.sendSuccess(id, { stopReason: "cancelled", usage: usagePayload });
|
|
823
|
+
this.scheduleUsagePublishBurst(session.id, [25, 150]);
|
|
677
824
|
}
|
|
678
825
|
}
|
|
679
826
|
else if (resultEvent.result?.status === "ERROR") {
|
|
@@ -688,10 +835,12 @@ export class ACPServer extends EventEmitter {
|
|
|
688
835
|
});
|
|
689
836
|
if (!isNotification) {
|
|
690
837
|
this.sendSuccess(id, { stopReason: "refusal", usage: usagePayload });
|
|
838
|
+
this.scheduleUsagePublishBurst(session.id, [25, 150]);
|
|
691
839
|
}
|
|
692
840
|
}
|
|
693
841
|
else if (!isNotification) {
|
|
694
842
|
this.sendSuccess(id, { stopReason: "end_turn", usage: usagePayload });
|
|
843
|
+
this.scheduleUsagePublishBurst(session.id, [25, 150]);
|
|
695
844
|
}
|
|
696
845
|
}
|
|
697
846
|
catch (promptErr) {
|
|
@@ -699,8 +848,10 @@ export class ACPServer extends EventEmitter {
|
|
|
699
848
|
if (session.isCancelled ||
|
|
700
849
|
errorMsg.includes("cancelled") ||
|
|
701
850
|
errorMsg.includes("Turn was cancelled")) {
|
|
702
|
-
if (!isNotification)
|
|
851
|
+
if (!isNotification) {
|
|
703
852
|
this.sendSuccess(id, { stopReason: "cancelled" });
|
|
853
|
+
this.scheduleUsagePublishBurst(session.id, [25, 150]);
|
|
854
|
+
}
|
|
704
855
|
}
|
|
705
856
|
else {
|
|
706
857
|
logger.error("Error executing prompt turn", { error: errorMsg, sessionId });
|
|
@@ -711,8 +862,10 @@ export class ACPServer extends EventEmitter {
|
|
|
711
862
|
content: { type: "text", text: `\n\n**Error:** ${errorMsg}\n` },
|
|
712
863
|
},
|
|
713
864
|
});
|
|
714
|
-
if (!isNotification)
|
|
865
|
+
if (!isNotification) {
|
|
715
866
|
this.sendSuccess(id, { stopReason: "refusal" });
|
|
867
|
+
this.scheduleUsagePublishBurst(session.id, [25, 150]);
|
|
868
|
+
}
|
|
716
869
|
}
|
|
717
870
|
}
|
|
718
871
|
}
|
|
@@ -736,6 +889,7 @@ export class ACPServer extends EventEmitter {
|
|
|
736
889
|
case ACP_METHODS.SESSION_CLOSE:
|
|
737
890
|
case ACP_METHODS.SESSION_CLOSE_ALIAS: {
|
|
738
891
|
const sessionId = String(params.sessionId || "");
|
|
892
|
+
this.clearUsagePublishTimers(sessionId);
|
|
739
893
|
await this.sessionManager.closeSession(sessionId);
|
|
740
894
|
if (!isNotification)
|
|
741
895
|
this.sendSuccess(id, {});
|
|
@@ -758,8 +912,11 @@ export class ACPServer extends EventEmitter {
|
|
|
758
912
|
break;
|
|
759
913
|
}
|
|
760
914
|
session.setMode(modeId);
|
|
761
|
-
if (!isNotification)
|
|
915
|
+
if (!isNotification) {
|
|
762
916
|
this.sendSuccess(id, {});
|
|
917
|
+
this.publishUsageUpdate(session);
|
|
918
|
+
this.scheduleUsagePublishBurst(session.id, [80, 350, 1000]);
|
|
919
|
+
}
|
|
763
920
|
break;
|
|
764
921
|
}
|
|
765
922
|
case ACP_METHODS.SESSION_SET_MODEL:
|
|
@@ -801,8 +958,11 @@ export class ACPServer extends EventEmitter {
|
|
|
801
958
|
currentPermission: session.permission,
|
|
802
959
|
availableModels: models,
|
|
803
960
|
});
|
|
804
|
-
if (!isNotification)
|
|
961
|
+
if (!isNotification) {
|
|
805
962
|
this.sendSuccess(id, { configOptions });
|
|
963
|
+
this.publishUsageUpdate(session);
|
|
964
|
+
this.scheduleUsagePublishBurst(session.id, [80, 350, 1000]);
|
|
965
|
+
}
|
|
806
966
|
break;
|
|
807
967
|
}
|
|
808
968
|
case ACP_METHODS.SESSION_SET_CONFIG_OPTION:
|
|
@@ -869,8 +1029,11 @@ export class ACPServer extends EventEmitter {
|
|
|
869
1029
|
currentPermission: session.permission,
|
|
870
1030
|
availableModels: models,
|
|
871
1031
|
});
|
|
872
|
-
if (!isNotification)
|
|
1032
|
+
if (!isNotification) {
|
|
873
1033
|
this.sendSuccess(id, { configOptions });
|
|
1034
|
+
this.publishUsageUpdate(session);
|
|
1035
|
+
this.scheduleUsagePublishBurst(session.id, [80, 350, 1000]);
|
|
1036
|
+
}
|
|
874
1037
|
break;
|
|
875
1038
|
}
|
|
876
1039
|
case "provider/usage":
|
|
@@ -897,6 +1060,7 @@ export class ACPServer extends EventEmitter {
|
|
|
897
1060
|
if (!this.isRunning)
|
|
898
1061
|
return;
|
|
899
1062
|
this.isRunning = false;
|
|
1063
|
+
this.clearUsagePublishTimers();
|
|
900
1064
|
if (this.rl) {
|
|
901
1065
|
this.rl.close();
|
|
902
1066
|
this.rl = null;
|
|
@@ -135,9 +135,20 @@ export function resolveDefaultAgyBinary(force = false) {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
else {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
const posixCandidates = [
|
|
139
|
+
path.join(home, ".local", "bin", "agy"),
|
|
140
|
+
"/opt/homebrew/bin/agy",
|
|
141
|
+
"/usr/local/bin/agy",
|
|
142
|
+
"/usr/bin/agy",
|
|
143
|
+
path.join(home, ".antigravity", "bin", "agy"),
|
|
144
|
+
path.join(home, ".gemini", "antigravity-cli", "bin", "agy"),
|
|
145
|
+
];
|
|
146
|
+
for (const cand of posixCandidates) {
|
|
147
|
+
if (fs.existsSync(cand)) {
|
|
148
|
+
resolved = cand;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
141
152
|
}
|
|
142
153
|
}
|
|
143
154
|
}
|
package/dist/asar.d.ts
CHANGED
|
@@ -21,6 +21,11 @@ export declare function readArchiveHeader(archivePath: string): {
|
|
|
21
21
|
* Lists all file paths inside an ASAR archive.
|
|
22
22
|
*/
|
|
23
23
|
export declare function listPackage(archivePath: string): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Reads a single UTF-8 text file directly from an ASAR archive without extracting
|
|
26
|
+
* the entire archive to disk. Returns null if the archive or entry cannot be read.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readFileFromPackage(archivePath: string, relPath: string): string | null;
|
|
24
29
|
/**
|
|
25
30
|
* Pure TypeScript, zero-dependency ASAR archive extractor.
|
|
26
31
|
* Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
|
package/dist/asar.js
CHANGED
|
@@ -54,6 +54,40 @@ export function listPackage(archivePath) {
|
|
|
54
54
|
walk(header, "");
|
|
55
55
|
return result;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Reads a single UTF-8 text file directly from an ASAR archive without extracting
|
|
59
|
+
* the entire archive to disk. Returns null if the archive or entry cannot be read.
|
|
60
|
+
*/
|
|
61
|
+
export function readFileFromPackage(archivePath, relPath) {
|
|
62
|
+
try {
|
|
63
|
+
const { header, dataBaseOffset } = readArchiveHeader(archivePath);
|
|
64
|
+
const parts = relPath.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
65
|
+
let node = header;
|
|
66
|
+
for (const part of parts) {
|
|
67
|
+
node = node?.files?.[part];
|
|
68
|
+
if (!node)
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (node.unpacked || node.size === undefined || node.offset === undefined) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (node.size === 0)
|
|
75
|
+
return "";
|
|
76
|
+
const fileOffset = dataBaseOffset + parseInt(node.offset, 10);
|
|
77
|
+
const fd = fs.openSync(archivePath, "r");
|
|
78
|
+
try {
|
|
79
|
+
const buf = Buffer.alloc(node.size);
|
|
80
|
+
const bytesRead = fs.readSync(fd, buf, 0, node.size, fileOffset);
|
|
81
|
+
return buf.subarray(0, bytesRead).toString("utf8");
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
fs.closeSync(fd);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
57
91
|
/**
|
|
58
92
|
* Pure TypeScript, zero-dependency ASAR archive extractor.
|
|
59
93
|
* Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
3
5
|
import { execFileSync } from "node:child_process";
|
|
4
6
|
import { ACPServer } from "./acp-server.js";
|
|
5
7
|
import { logger } from "./logger.js";
|
|
@@ -8,6 +10,11 @@ import { ensurePaseoIntegration, restorePaseoIntegration, findPaseoServerInstall
|
|
|
8
10
|
import { resolveDefaultAgyBinary, isWindowsBatchScript } from "./antigravity-process.js";
|
|
9
11
|
import { parseAgyQuotaOutput } from "./protocol.js";
|
|
10
12
|
const args = process.argv.slice(2);
|
|
13
|
+
const bundledPluginDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "paseo-plugin");
|
|
14
|
+
if (args.includes("plugin-path") || args.includes("--plugin-path")) {
|
|
15
|
+
process.stdout.write(`${bundledPluginDir}\n`);
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
11
18
|
if (args.includes("--version") || args.includes("-v")) {
|
|
12
19
|
if (args.includes("--json")) {
|
|
13
20
|
process.stdout.write(JSON.stringify(resolveBuildMetadata(), null, 2) + "\n");
|
|
@@ -31,12 +38,14 @@ Commands:
|
|
|
31
38
|
setup, patch Configure and integrate Antigravity telemetry with Paseo
|
|
32
39
|
doctor Diagnose Antigravity binary, quota provider, and Paseo status
|
|
33
40
|
restore Restore original Paseo files from backup (.bak)
|
|
41
|
+
plugin-path Print path to the update-proof Paseo telemetry plugin directory
|
|
34
42
|
|
|
35
43
|
Options:
|
|
36
44
|
--acp Start ACP server over stdio (default)
|
|
37
45
|
--setup Integrate Antigravity with local Paseo server installation
|
|
38
46
|
--doctor Run environment, binary, and telemetry diagnostics
|
|
39
47
|
--restore Restore original Paseo files from backup
|
|
48
|
+
--plugin-path Print bundled Paseo plugin directory for 'paseo plugin install'
|
|
40
49
|
--force-macos-app Force patching inside macOS .app bundle (re-signs with ad-hoc)
|
|
41
50
|
--skip-naration Drop leading narration-only assistant chunks (e.g. 'I will...')
|
|
42
51
|
--skip-narration Alias for --skip-naration
|
|
@@ -139,6 +148,22 @@ if (args.includes("doctor") || args.includes("--doctor")) {
|
|
|
139
148
|
process.stdout.write("\n3. Paseo Installation Targets:\n");
|
|
140
149
|
const serverPaths = findPaseoServerInstallations();
|
|
141
150
|
const asarPaths = findPaseoAsarPaths();
|
|
151
|
+
let telemetryPluginEnabled = false;
|
|
152
|
+
try {
|
|
153
|
+
const paseoHome = process.env.PASEO_HOME || path.join(process.env.HOME || process.env.USERPROFILE || "", ".paseo");
|
|
154
|
+
const cfgPath = path.join(paseoHome, "config.json");
|
|
155
|
+
if (fs.existsSync(cfgPath)) {
|
|
156
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
157
|
+
if (cfg?.pluginsEnabled === true && cfg?.plugins) {
|
|
158
|
+
const p = cfg.plugins["antigravity-telemetry"];
|
|
159
|
+
if (p && p.enabled !== false) {
|
|
160
|
+
telemetryPluginEnabled = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch { }
|
|
166
|
+
process.stdout.write(` - [Plugin] antigravity-telemetry -> ${telemetryPluginEnabled ? "[ENABLED]" : "[NOT INSTALLED]"}\n`);
|
|
142
167
|
const totalFound = serverPaths.length + asarPaths.length;
|
|
143
168
|
if (totalFound === 0) {
|
|
144
169
|
process.stdout.write(" [!] No Paseo server or desktop installations discovered in standard paths.\n");
|
|
@@ -185,21 +210,22 @@ if (args.includes("doctor") || args.includes("--doctor")) {
|
|
|
185
210
|
process.stdout.write(" - Antigravity CLI ('agy') executable was not found.\n");
|
|
186
211
|
process.stdout.write(" Action: Install Antigravity CLI or set AGY_BIN_PATH environment variable.\n");
|
|
187
212
|
}
|
|
188
|
-
if (totalFound === 0) {
|
|
213
|
+
if (totalFound === 0 && !telemetryPluginEnabled) {
|
|
189
214
|
hasIssue = true;
|
|
190
215
|
process.stdout.write(" - No Paseo installations found.\n");
|
|
191
216
|
process.stdout.write(" Action: Set PASEO_SERVER_PATH or PASEO_ASAR_PATH to your Paseo install directory if located elsewhere.\n");
|
|
192
217
|
}
|
|
193
|
-
else if (unpatchedCount > 0) {
|
|
218
|
+
else if (unpatchedCount > 0 && !telemetryPluginEnabled) {
|
|
194
219
|
hasIssue = true;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
220
|
+
process.stdout.write(` - Found ${unpatchedCount} unpatched Paseo target(s) and 'antigravity-telemetry' plugin is not yet installed.\n`);
|
|
221
|
+
process.stdout.write(" Recommended update-proof action (survives Paseo updates without touching .app signatures):\n");
|
|
222
|
+
process.stdout.write(` paseo plugin install "${bundledPluginDir}"\n`);
|
|
223
|
+
if (isWin) {
|
|
224
|
+
process.stdout.write(" Or to patch standalone Paseo after closing it (Stop-Process -Name \"Paseo\" -Force):\n");
|
|
198
225
|
process.stdout.write(" npx -y paseo-acp-agy setup\n");
|
|
199
226
|
}
|
|
200
227
|
else {
|
|
201
|
-
process.stdout.write(
|
|
202
|
-
process.stdout.write(" Action: Run setup to patch Paseo for Antigravity telemetry:\n");
|
|
228
|
+
process.stdout.write(" Or for standalone @getpaseo/cli servers:\n");
|
|
203
229
|
process.stdout.write(" npx -y paseo-acp-agy setup\n");
|
|
204
230
|
}
|
|
205
231
|
}
|
|
@@ -272,8 +298,9 @@ if (args.includes("setup") ||
|
|
|
272
298
|
process.stdout.write(`\n🛡️ Preserved macOS Code Signature:\n` +
|
|
273
299
|
` Skipped ${res.skippedPaths.length} target(s) inside macOS .app bundle to protect official Apple Developer ID signature\n` +
|
|
274
300
|
` and prevent Gatekeeper "damaged app" warnings.\n` +
|
|
275
|
-
`
|
|
276
|
-
`
|
|
301
|
+
` Recommended update-proof telemetry setup (survives every Paseo update):\n` +
|
|
302
|
+
` paseo plugin install "${bundledPluginDir}"\n` +
|
|
303
|
+
` (Or if you specifically wish to patch the .app bundle: paseo-acp-agy patch --force-macos-app)\n\n`);
|
|
277
304
|
}
|
|
278
305
|
if (isPaseoRunning()) {
|
|
279
306
|
const isWin = process.platform === "win32";
|
package/dist/paseo-patcher.d.ts
CHANGED
|
@@ -58,7 +58,17 @@ export declare function ensurePaseoIntegration(options?: {
|
|
|
58
58
|
forceMacosApp?: boolean;
|
|
59
59
|
}): Promise<PatchResult>;
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* Reads the Paseo version embedded inside an ASAR archive's package.json.
|
|
62
|
+
*/
|
|
63
|
+
export declare function resolveAsarArchiveVersion(archivePath: string): string | null;
|
|
64
|
+
/**
|
|
65
|
+
* Resolves the authoritative Paseo version of a target app.asar installation,
|
|
66
|
+
* preferring macOS bundle Info.plist (CFBundleShortVersionString) when inside an .app bundle.
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveTargetPaseoVersion(asarPath: string): string | null;
|
|
69
|
+
/**
|
|
70
|
+
* Restores original unpatched Paseo files from backup (.bak) archives,
|
|
71
|
+
* enforcing strict version matching so an older backup can never downgrade a newer Paseo app.
|
|
62
72
|
*/
|
|
63
73
|
export declare function restorePaseoIntegration(): Promise<{
|
|
64
74
|
restored: string[];
|