paseo-acp-agy 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -418,6 +418,7 @@ export class ACPServer extends EventEmitter {
418
418
  }
419
419
  }
420
420
  if (!isNotification) {
421
+ this.publishUsageUpdate(session);
421
422
  this.sendSuccess(id, await this.sessionState(session));
422
423
  this.publishCommands(session.id);
423
424
  this.publishUsageUpdate(session);
@@ -465,6 +466,7 @@ export class ACPServer extends EventEmitter {
465
466
  catch { }
466
467
  }
467
468
  if (!isNotification) {
469
+ this.publishUsageUpdate(session);
468
470
  this.sendSuccess(id, await this.sessionState(session));
469
471
  this.publishCommands(session.id);
470
472
  this.publishUsageUpdate(session);
@@ -679,8 +681,8 @@ export class ACPServer extends EventEmitter {
679
681
  toolCall: {
680
682
  toolCallId: toolCall.toolCallId,
681
683
  title: toolCall.title,
682
- kind: "question",
683
- content: [{ type: "text", text: promptText }],
684
+ kind: "other",
685
+ content: [{ type: "content", content: { type: "text", text: promptText } }],
684
686
  rawInput: toolCall.rawInput,
685
687
  },
686
688
  options,
@@ -119,14 +119,12 @@ export function findPaseoServerInstallations() {
119
119
  }
120
120
  }
121
121
  else {
122
- // POSIX locations (Linux / macOS)
122
+ // POSIX locations (Linux / standalone CLI — never inside signed macOS .app bundles)
123
123
  const posixLocations = [
124
124
  "/usr/lib/node_modules/@getpaseo/cli/node_modules/@getpaseo/server",
125
125
  "/usr/lib/node_modules/@getpaseo/server",
126
126
  "/usr/local/lib/node_modules/@getpaseo/cli/node_modules/@getpaseo/server",
127
127
  "/usr/local/lib/node_modules/@getpaseo/server",
128
- "/Applications/Paseo.app/Contents/Resources/app.asar.unpacked/node_modules/@getpaseo/server",
129
- "/Applications/Paseo.app/Contents/Resources/app/node_modules/@getpaseo/server",
130
128
  ];
131
129
  if (home) {
132
130
  posixLocations.push(path.join(home, ".local", "lib", "node_modules", "@getpaseo", "cli", "node_modules", "@getpaseo", "server"), path.join(home, ".local", "lib", "node_modules", "@getpaseo", "server"), path.join(home, ".local", "share", "pnpm", "global", "5", "node_modules", "@getpaseo", "cli", "node_modules", "@getpaseo", "server"), path.join(home, ".bun", "install", "global", "node_modules", "@getpaseo", "cli", "node_modules", "@getpaseo", "server"));
@@ -199,6 +197,11 @@ export function findPaseoServerInstallations() {
199
197
  if (seen.has(key))
200
198
  continue;
201
199
  seen.add(key);
200
+ if (process.platform === "darwin" &&
201
+ dir.includes(".app/Contents/") &&
202
+ process.env.PASEO_SERVER_PATH !== dir) {
203
+ continue;
204
+ }
202
205
  if (fs.existsSync(path.join(dir, "dist")) || fs.existsSync(path.join(dir, "package.json"))) {
203
206
  verified.push(dir);
204
207
  }
@@ -711,10 +714,18 @@ function toneFromUsedPct(usedPct) {
711
714
 
712
715
  function isStandaloneTelemetryPluginEnabled() {
713
716
  try {
714
- const paseoHome = process.env.PASEO_HOME || path.join(os.homedir(), ".paseo");
717
+ let paseoHome = process.env.PASEO_HOME;
718
+ if (!paseoHome && Array.isArray(process.argv)) {
719
+ const homeIdx = process.argv.indexOf("--home");
720
+ if (homeIdx !== -1 && process.argv[homeIdx + 1]) {
721
+ paseoHome = process.argv[homeIdx + 1];
722
+ }
723
+ }
724
+ paseoHome = paseoHome || path.join(os.homedir(), ".paseo");
715
725
  const cfgPath = path.join(paseoHome, "config.json");
716
726
  if (!fs.existsSync(cfgPath)) return false;
717
727
  const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
728
+ if (cfg && cfg.pluginsEnabled === false) return false;
718
729
  const entry = cfg && cfg.plugins && cfg.plugins["antigravity-telemetry"];
719
730
  return Boolean(entry && entry.enabled !== false);
720
731
  } catch {
@@ -1262,6 +1273,28 @@ ${braceIndent}}`;
1262
1273
  acpModified = true;
1263
1274
  }
1264
1275
  }
1276
+ // Patch handlePromptResponse so response.usage merges defined fields instead of wiping
1277
+ // totalCostUsd / contextWindowMaxTokens / contextWindowUsedTokens from this.currentTurnUsage
1278
+ const promptRespAssignRegex = /([ \t]*)this\.currentTurnUsage\s*=\s*mapACPUsage\(response\.usage\)\s*\?\?\s*this\.currentTurnUsage;/;
1279
+ if (promptRespAssignRegex.test(acpCode) && !acpCode.includes("_mappedRespUsage")) {
1280
+ acpCode = acpCode.replace(promptRespAssignRegex, (_m, ind) => `${ind}const _mappedRespUsage = mapACPUsage(response.usage);\n` +
1281
+ `${ind}if (_mappedRespUsage) {\n` +
1282
+ `${ind} this.currentTurnUsage = {\n` +
1283
+ `${ind} ...this.currentTurnUsage,\n` +
1284
+ `${ind} ...Object.fromEntries(Object.entries(_mappedRespUsage).filter(([, v]) => v !== undefined)),\n` +
1285
+ `${ind} };\n` +
1286
+ `${ind}}`);
1287
+ acpModified = true;
1288
+ }
1289
+ // Patch subscribe(callback) so any usage_update received during newSession/loadSession
1290
+ // (before subscribers attach, including history resumes) is replayed immediately
1291
+ const subscribeMethodMatch = acpCode.match(/subscribe\s*\([^)]*\)\s*\{[\s\S]*?\n\s*\}/);
1292
+ if (subscribeMethodMatch &&
1293
+ acpCode.includes('type: "thread_started"') &&
1294
+ !subscribeMethodMatch[0].includes("usage_updated")) {
1295
+ acpCode = acpCode.replace(/(\bcallback\(\{\s*type:\s*"thread_started"[\s\S]*?\}\);\s*\})/, `$1\n if (this.currentTurnUsage) {\n callback({\n type: "usage_updated",\n provider: this.provider,\n usage: this.currentTurnUsage,\n });\n }`);
1296
+ acpModified = true;
1297
+ }
1265
1298
  // Patch mapPermissionRequest for native QuestionFormCard support in Paseo UI
1266
1299
  if (!acpCode.includes("snapshot?.kind === \"question\" || params?.kind === \"question\"")) {
1267
1300
  const oldMapPermRegex = /function\s+mapPermissionRequest\s*\([^)]*\)\s*\{[\s\S]*?const\s+kind\s*=\s*snapshot\.kind\s*===\s*"switch_mode"\s*\?\s*"mode"\s*:\s*"tool";/m;
@@ -1968,6 +2001,10 @@ export function isPaseoServerPatched(serverDir) {
1968
2001
  if (pluginDir && (!content.includes("usageSession()") || content.includes("!this.usageProvider"))) {
1969
2002
  return false;
1970
2003
  }
2004
+ if (content.includes("mapACPUsage(response.usage) ?? this.currentTurnUsage") &&
2005
+ !content.includes("_mappedRespUsage")) {
2006
+ return false;
2007
+ }
1971
2008
  }
1972
2009
  return true;
1973
2010
  }
@@ -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" | "search" | "question" | "other";
138
+ export type ToolKind = "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "switch_mode" | "other";
139
139
  export declare function mapToolNameToKind(toolName: string): ToolKind;
140
140
  export declare function extractPromptText(promptInput: unknown): string;
141
141
  export interface ModelDefinition {
@@ -18,6 +18,7 @@ export interface NormalizedToolResult {
18
18
  }
19
19
  export interface PlanEntry {
20
20
  content: string;
21
+ priority: "high" | "medium" | "low";
21
22
  status: "pending" | "in_progress" | "completed";
22
23
  }
23
24
  /**
@@ -102,7 +102,7 @@ export function normalizeToolCall(toolName, rawParams = {}, stepIndex) {
102
102
  return {
103
103
  toolCallId,
104
104
  title: "❓ Pergunta do Assistente",
105
- kind: "question",
105
+ kind: "other",
106
106
  rawInput: normalizedParams,
107
107
  questionCard: questionCard || undefined,
108
108
  };
@@ -149,8 +149,11 @@ export function normalizeToolResult(toolName, rawParams = {}, output, error) {
149
149
  else if (name === "read_file" || name === "view_file") {
150
150
  if (outputStr.trim()) {
151
151
  content.push({
152
- type: "text",
153
- text: outputStr,
152
+ type: "content",
153
+ content: {
154
+ type: "text",
155
+ text: outputStr,
156
+ },
154
157
  });
155
158
  }
156
159
  }
@@ -180,7 +183,7 @@ export function extractPlanEntries(text) {
180
183
  else if (mark === "/" || mark === "-") {
181
184
  status = "in_progress";
182
185
  }
183
- entries.push({ content, status });
186
+ entries.push({ content, priority: "medium", status });
184
187
  }
185
188
  }
186
189
  return entries.length >= 2 ? entries : null;
@@ -8,6 +8,7 @@ export interface ReplayItem {
8
8
  rawInput?: Record<string, unknown>;
9
9
  rawOutput?: string;
10
10
  status?: "completed" | "failed";
11
+ compacted?: boolean;
11
12
  }
12
13
  /**
13
14
  * Strips XML-like tags injected by harness or system prompts (e.g., <USER_REQUEST>, <ADDITIONAL_METADATA>)
@@ -85,11 +85,13 @@ export function loadConversationTranscript(conversationId) {
85
85
  const stepType = String(step.type || "");
86
86
  if (stepType === "USER_INPUT") {
87
87
  const rawContent = typeof step.content === "string" ? step.content : "";
88
+ const isCompacted = rawContent.includes("<CONTEXT_SUMMARY>");
88
89
  const cleanPrompt = extractCleanUserPrompt(rawContent);
89
90
  if (cleanPrompt) {
90
91
  replayItems.push({
91
92
  type: "user_message",
92
93
  text: cleanPrompt,
94
+ ...(isCompacted ? { compacted: true } : {}),
93
95
  });
94
96
  }
95
97
  }
@@ -163,6 +165,9 @@ export function estimateReplayContextTokens(items) {
163
165
  return 0;
164
166
  let totalChars = 0;
165
167
  for (const item of items) {
168
+ if (item.compacted) {
169
+ totalChars = 0;
170
+ }
166
171
  if (item.text) {
167
172
  totalChars += item.text.length;
168
173
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paseo-acp-agy",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
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",
@@ -41,6 +41,8 @@ export function toneColor(tone: UsageTone, theme: PluginTheme): string {
41
41
  export function buildPillLabel(session: SessionTelemetry, quota: QuotaReport): string {
42
42
  const parts: string[] = [];
43
43
 
44
+ parts.push(`${Math.round(session.contextPct)}% ctx`);
45
+
44
46
  if (session.totalCostUsd > 0) {
45
47
  parts.push(fmtCost(session.totalCostUsd));
46
48
  } else if (session.totalTokens > 0) {
@@ -49,8 +51,6 @@ export function buildPillLabel(session: SessionTelemetry, quota: QuotaReport): s
49
51
  parts.push("$0.00");
50
52
  }
51
53
 
52
- parts.push(`ctx ${Math.round(session.contextPct)}%`);
53
-
54
54
  const primaryFiveHour =
55
55
  quota.windows.find((w) => w.id === "session") ?? quota.windows[0] ?? null;
56
56
  if (primaryFiveHour) {
@@ -5,13 +5,22 @@ import type {
5
5
  PluginClientContext,
6
6
  } from "@getpaseo/plugin/client";
7
7
  import { Icon, ScrollView } from "@getpaseo/plugin/client/react-native";
8
- import { useEffect } from "react";
8
+ import { useEffect, useRef } from "react";
9
9
  import { Text, View } from "react-native";
10
10
  import { isAntigravityProvider } from "../shared/telemetry.ts";
11
11
  import { useAntigravityTelemetry } from "./data.ts";
12
12
  import { TelemetryBody } from "./ui.tsx";
13
13
  import { buildPillLabel } from "./ui-helpers.ts";
14
14
 
15
+ function sanitizeButtonId(agentId: string): string {
16
+ const cleaned = agentId
17
+ .toLowerCase()
18
+ .replace(/[^a-z0-9-]+/g, "-")
19
+ .replace(/-+/g, "-")
20
+ .replace(/^-|-$/g, "");
21
+ return `agy-telemetry-${cleaned || "agent"}`;
22
+ }
23
+
15
24
  interface PopoverProps {
16
25
  theme: PluginTheme;
17
26
  layout: { compact: boolean };
@@ -62,21 +71,35 @@ export function contributeAntigravityPills(client: PluginClientContext): () => P
62
71
 
63
72
  function TelemetryPillIcon(props: PluginButtonIconProps) {
64
73
  const { data, error } = useAntigravityTelemetry(agentId);
74
+ const lastLabelRef = useRef<string>("AGY …");
75
+
65
76
  useEffect(() => {
66
77
  if (!handle) return;
67
- const nextLabel = error
78
+ const computed = error
68
79
  ? "AGY"
69
80
  : data
70
81
  ? buildPillLabel(data.session, data.quota)
71
82
  : "AGY …";
72
- handle.update({ label: nextLabel });
83
+ const nextLabel = computed.trim() || "AGY";
84
+ if (lastLabelRef.current !== nextLabel) {
85
+ lastLabelRef.current = nextLabel;
86
+ handle.update({ label: nextLabel });
87
+ }
73
88
  }, [data, error]);
74
89
 
90
+ const pct = data?.session.contextPct ?? 0;
91
+ const iconColor =
92
+ pct >= 90
93
+ ? "#ef4444"
94
+ : pct >= 75
95
+ ? "#f59e0b"
96
+ : props.color;
97
+
75
98
  return (
76
99
  <Icon
77
100
  name={data?.session.activeTurn ? "Activity" : "Gauge"}
78
101
  size={props.size}
79
- color={props.color}
102
+ color={iconColor}
80
103
  />
81
104
  );
82
105
  }
@@ -123,7 +146,7 @@ export function contributeAntigravityPills(client: PluginClientContext): () => P
123
146
  }
124
147
 
125
148
  handle = client.addComposerPill({
126
- id: `agy-telemetry-${agentId}`,
149
+ id: sanitizeButtonId(agentId),
127
150
  workspaceId,
128
151
  agentId,
129
152
  button: {
@@ -30,14 +30,19 @@ export default function contribute(client: PluginClientContext) {
30
30
  ).addSlashCommand;
31
31
 
32
32
  if (typeof maybeAddSlashCommand === "function") {
33
- maybeAddSlashCommand.call(client, {
34
- name: "agy-telemetry",
35
- description: "Open Antigravity session usage, context wheel & quota panel",
36
- context: "agent",
37
- async onSubmit({ openPanel }) {
38
- openPanel("telemetry");
39
- },
40
- });
33
+ try {
34
+ maybeAddSlashCommand.call(client, {
35
+ name: "agy-telemetry",
36
+ description: "Open Antigravity session usage, context wheel & quota panel",
37
+ argumentHint: "[panel]",
38
+ context: "agent",
39
+ async onSubmit({ openPanel }) {
40
+ openPanel("telemetry");
41
+ },
42
+ });
43
+ } catch (err) {
44
+ console.warn("[antigravity-telemetry] Slash command registration skipped:", err);
45
+ }
41
46
  }
42
47
 
43
48
  return contributeAntigravityPills(client);
@@ -475,6 +475,43 @@ export interface AgentSnapshotInfo {
475
475
  } | null;
476
476
  }
477
477
 
478
+ function estimateConversationContextTokens(conversationId: string | null | undefined, maxTokens: number): number | null {
479
+ if (!conversationId) return null;
480
+ const home = os.homedir();
481
+ const candidates = [
482
+ process.env.AGY_BRAIN_DIR ? path.join(process.env.AGY_BRAIN_DIR, conversationId) : "",
483
+ home ? path.join(home, ".gemini", "antigravity-cli", "brain", conversationId) : "",
484
+ home ? path.join(home, ".antigravity", "brain", conversationId) : "",
485
+ ].filter(Boolean);
486
+
487
+ for (const brainDir of candidates) {
488
+ const transcriptPath = path.join(brainDir, ".system_generated", "logs", "transcript.jsonl");
489
+ if (!fs.existsSync(transcriptPath)) continue;
490
+ try {
491
+ const content = fs.readFileSync(transcriptPath, "utf-8");
492
+ const lines = content.split(/\r?\n/).filter(Boolean);
493
+ let activeChars = 0;
494
+ for (const line of lines) {
495
+ try {
496
+ const step = JSON.parse(line) as Record<string, unknown>;
497
+ const rawContent = typeof step.content === "string" ? step.content : "";
498
+ if (step.type === "USER_INPUT" && rawContent.includes("<CONTEXT_SUMMARY>")) {
499
+ activeChars = 0;
500
+ }
501
+ activeChars += rawContent.length;
502
+ if (typeof step.thinking === "string") activeChars += step.thinking.length;
503
+ if (Array.isArray(step.tool_calls)) activeChars += JSON.stringify(step.tool_calls).length;
504
+ } catch {}
505
+ }
506
+ if (activeChars <= 0) return null;
507
+ return Math.min(maxTokens, 4_000 + Math.ceil(activeChars / 4));
508
+ } catch {
509
+ return null;
510
+ }
511
+ }
512
+ return null;
513
+ }
514
+
478
515
  export function resolveSessionTelemetry(
479
516
  agentId: string,
480
517
  snapshot: AgentSnapshotInfo | null | undefined
@@ -496,10 +533,22 @@ export function resolveSessionTelemetry(
496
533
  : defaultMax;
497
534
  // Prefer live streaming contextWindowUsedTokens from snapshot if higher/fresher mid-turn
498
535
  const liveUsed = snapshot?.lastUsage?.contextWindowUsedTokens;
499
- const rawUsed =
536
+ let rawUsed =
500
537
  snapshot?.activeTurn && typeof liveUsed === "number" && liveUsed > 0
501
538
  ? liveUsed
502
539
  : (u.contextWindowUsedTokens ?? liveUsed ?? 0);
540
+ // Repair legacy pre-v1.3.0 sessions where cumulative inputTokens was clamped to maxTokens
541
+ if (
542
+ rawUsed >= maxTokens &&
543
+ inputTokens > maxTokens &&
544
+ (!liveUsed || liveUsed >= maxTokens) &&
545
+ persisted.conversationId
546
+ ) {
547
+ const estimated = estimateConversationContextTokens(persisted.conversationId, maxTokens);
548
+ if (estimated !== null && estimated > 0 && estimated < maxTokens) {
549
+ rawUsed = estimated;
550
+ }
551
+ }
503
552
  const usedTokens = Math.min(Math.max(0, rawUsed), maxTokens);
504
553
  const totalCostUsd =
505
554
  typeof u.totalCostUsd === "number"
@@ -545,9 +594,9 @@ export function resolveSessionTelemetry(
545
594
  return {
546
595
  agentId,
547
596
  sessionId,
548
- conversationId: null,
597
+ conversationId: persisted?.conversationId ?? null,
549
598
  model,
550
- effort: null,
599
+ effort: persisted?.effort ?? null,
551
600
  activeTurn: Boolean(snapshot.activeTurn),
552
601
  inputTokens,
553
602
  outputTokens,
@@ -562,6 +611,28 @@ export function resolveSessionTelemetry(
562
611
  };
563
612
  }
564
613
 
614
+ if (persisted?.conversationId) {
615
+ const estimated = estimateConversationContextTokens(persisted.conversationId, defaultMax) ?? 0;
616
+ return {
617
+ agentId,
618
+ sessionId,
619
+ conversationId: persisted.conversationId,
620
+ model,
621
+ effort: persisted.effort ?? null,
622
+ activeTurn: Boolean(snapshot?.activeTurn),
623
+ inputTokens: 0,
624
+ outputTokens: 0,
625
+ cachedInputTokens: 0,
626
+ totalTokens: 0,
627
+ totalCostUsd: 0,
628
+ contextWindowUsedTokens: estimated,
629
+ contextWindowMaxTokens: defaultMax,
630
+ contextPct: defaultMax > 0 ? Math.min(100, Math.round((estimated / defaultMax) * 1000) / 10) : 0,
631
+ updatedAt: persisted.updatedAt ?? snapshot?.updatedAt ?? null,
632
+ source: "acp_store",
633
+ };
634
+ }
635
+
565
636
  return {
566
637
  agentId,
567
638
  sessionId,