paseo-acp-agy 1.2.6 → 1.2.7

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.
@@ -716,35 +716,70 @@ export async function fetchAntigravityUsage() {
716
716
 
717
717
  for (const rawLine of lines) {
718
718
  const line = rawLine.trim();
719
- if (!line) continue;
719
+ if (!line || line.toLowerCase().startsWith("quota:")) continue;
720
720
 
721
- if (line.endsWith(":") && !line.includes("%")) {
722
- currentModel = line.slice(0, -1).trim();
723
- continue;
724
- }
725
-
726
- const isGemini = /gemini/i.test(currentModel) || !/claude/i.test(currentModel);
727
- const match = line.match(/^([A-Za-z0-9\\s_-]+):\\s*(\\d+)%\\s*(?:used)?(?:\\s*\\((?:resets?\\s+in\\s+)?([^)]+)\\))?/i);
728
- if (match) {
729
- const limitType = match[1].trim();
730
- const usedPct = parseInt(match[2], 10);
731
- const resetStr = match[3] ? match[3].trim() : null;
721
+ let scope = currentModel;
722
+ let limitType = "";
723
+ let remainingPct = null;
724
+ let resetsAt = null;
732
725
 
733
- let resetsAt = null;
734
- if (resetStr) {
735
- const durMatch = resetStr.match(/(?:(\\d+)h)?\\s*(?:(\\d+)m)?/i);
736
- if (durMatch && (durMatch[1] || durMatch[2])) {
737
- const h = parseInt(durMatch[1] || "0", 10);
738
- const m = parseInt(durMatch[2] || "0", 10);
739
- resetsAt = new Date(Date.now() + (h * 3600 + m * 60) * 1000).toISOString();
740
- } else {
741
- const parsed = Date.parse(resetStr);
742
- if (!isNaN(parsed)) resetsAt = new Date(parsed).toISOString();
726
+ // Format 1: Table format e.g. "Gemini Models Weekly Limit Remaining 56% 2026-10-14 04:37 UTC"
727
+ const m = line.match(/^(.*?)\\s{2,}(.*?Remaining)\\s+(\\d+)%(?:\\s+(.*))?$/i);
728
+ if (m) {
729
+ scope = m[1].trim();
730
+ limitType = m[2].trim();
731
+ remainingPct = parseInt(m[3], 10);
732
+ if (m[4]) {
733
+ try {
734
+ const d = new Date(m[4].trim());
735
+ resetsAt = isNaN(d.getTime()) ? null : d.toISOString();
736
+ } catch {}
737
+ }
738
+ } else {
739
+ // Format 2: Tab / multi-space separated
740
+ const parts = line.split(/\\t+|\\s{2,}/).map(p => p.trim());
741
+ if (parts.length >= 3 && parts[1]?.toLowerCase().includes("remaining")) {
742
+ scope = parts[0];
743
+ limitType = parts[1];
744
+ const remMatch = parts[2].match(/(\\d+)%/);
745
+ if (remMatch) remainingPct = parseInt(remMatch[1], 10);
746
+ if (parts[3]) {
747
+ try {
748
+ const d = new Date(parts[3]);
749
+ resetsAt = isNaN(d.getTime()) ? null : d.toISOString();
750
+ } catch {}
751
+ }
752
+ } else if (line.endsWith(":") && !line.includes("%")) {
753
+ currentModel = line.slice(0, -1).trim();
754
+ continue;
755
+ } else {
756
+ // Format 3: Colon format e.g. "Session: 20% used (resets in 2h)"
757
+ const colonMatch = line.match(/^([A-Za-z0-9\\s_-]+):\\s*(\\d+)%\\s*(?:used)?(?:\\s*\\((?:resets?\\s+in\\s+)?([^)]+)\\))?/i);
758
+ if (colonMatch) {
759
+ limitType = colonMatch[1].trim();
760
+ const usedVal = parseInt(colonMatch[2], 10);
761
+ remainingPct = Math.max(0, 100 - usedVal);
762
+ const resetStr = colonMatch[3] ? colonMatch[3].trim() : null;
763
+ if (resetStr) {
764
+ const durMatch = resetStr.match(/(?:(\\d+)h)?\\s*(?:(\\d+)m)?/i);
765
+ if (durMatch && (durMatch[1] || durMatch[2])) {
766
+ const h = parseInt(durMatch[1] || "0", 10);
767
+ const m = parseInt(durMatch[2] || "0", 10);
768
+ resetsAt = new Date(Date.now() + (h * 3600 + m * 60) * 1000).toISOString();
769
+ } else {
770
+ const parsed = Date.parse(resetStr);
771
+ if (!isNaN(parsed)) resetsAt = new Date(parsed).toISOString();
772
+ }
773
+ }
743
774
  }
744
775
  }
776
+ }
745
777
 
746
- const isFiveHour = /session|5-?hour|5h/i.test(limitType);
778
+ if (remainingPct !== null && !isNaN(remainingPct)) {
779
+ const usedPct = Math.max(0, Math.min(100, 100 - remainingPct));
780
+ const isFiveHour = /session|five\\s*hour|5-?hour|5h/i.test(limitType);
747
781
  const isWeekly = /week|7-?day/i.test(limitType);
782
+ const isGemini = /gemini/i.test(scope) || !/claude/i.test(scope);
748
783
 
749
784
  let id = isFiveHour ? "session" : isWeekly ? "weekly" : "quota";
750
785
  let label = isFiveHour ? "Session" : isWeekly ? "Weekly" : limitType.replace(/\\s+Remaining$/i, "");
@@ -759,7 +794,7 @@ export async function fetchAntigravityUsage() {
759
794
  shortLabel: isFiveHour ? "5h" : isWeekly ? "wk" : "",
760
795
  summary: true,
761
796
  usedPct,
762
- remainingPct: Math.max(0, 100 - usedPct),
797
+ remainingPct,
763
798
  resetsAt,
764
799
  tone: toneFromUsedPct(usedPct),
765
800
  isFiveHour,
@@ -826,7 +861,7 @@ export function registerAntigravityUsageSource(server) {
826
861
  icon: "icon.svg",
827
862
  input: z.object({}).passthrough(),
828
863
  discover: async (scope) => {
829
- if (scope && scope.kind === "session" && scope.provider && scope.provider !== "antigravity") {
864
+ if (scope && scope.kind === "session" && scope.provider && scope.provider !== "antigravity" && scope.provider !== "acp") {
830
865
  return [];
831
866
  }
832
867
  return [{ key: "default", label: "Antigravity", input: {} }];
@@ -903,19 +938,18 @@ export function patchPaseoServer(serverDir) {
903
938
  indexModified = true;
904
939
  }
905
940
  // Inject registration call into contribute function
906
- const contributeMatch = indexCode.match(/(export\s+default\s+function\s+contribute\s*\([^)]*\)\s*\{)([\s\S]*?)(\})/);
907
- if (contributeMatch) {
908
- const before = contributeMatch[1];
909
- const body = contributeMatch[2];
910
- const after = contributeMatch[3];
911
- if (!body.includes("registerAntigravityUsageSource")) {
912
- const patchedBody = body.replace(/return\s+\(\)\s*=>\s*\{[^}]*\};?/, (m) => `server.registerUsageSource ? registerAntigravityUsageSource(server) : null;\n ${m}`);
913
- if (patchedBody !== body) {
914
- indexCode = indexCode.replace(contributeMatch[0], `${before}${patchedBody}${after}`);
915
- indexModified = true;
916
- }
917
- else {
918
- indexCode = indexCode.replace(contributeMatch[0], `${before}${body}\n server.registerUsageSource ? registerAntigravityUsageSource(server) : null;\n${after}`);
941
+ if (!indexCode.includes("registerAntigravityUsageSource(server)")) {
942
+ if (/return\s*\(\)\s*=>/.test(indexCode)) {
943
+ indexCode = indexCode.replace(/return\s*\(\)\s*=>/, "server.registerUsageSource ? registerAntigravityUsageSource(server) : null;\n return () =>");
944
+ indexModified = true;
945
+ }
946
+ else {
947
+ const lastBraceIndex = indexCode.lastIndexOf("}");
948
+ if (lastBraceIndex !== -1) {
949
+ indexCode =
950
+ indexCode.slice(0, lastBraceIndex) +
951
+ " server.registerUsageSource ? registerAntigravityUsageSource(server) : null;\n" +
952
+ indexCode.slice(lastBraceIndex);
919
953
  indexModified = true;
920
954
  }
921
955
  }
@@ -1101,11 +1135,112 @@ ${indent}}`;
1101
1135
  }
1102
1136
  }
1103
1137
  }
1138
+ // Patch usageSession() in ACPAgentSession for Paseo 0.11+ usage reporting
1139
+ if (!acpCode.includes("usageSession()")) {
1140
+ const constructorEndMatch = acpCode.match(/(?:^|\n)([ \t]*)this\.extensionCommandsParser\s*=[^;\n]*;[ \t]*\r?\n([ \t]*)\}/);
1141
+ if (constructorEndMatch && constructorEndMatch.index !== undefined) {
1142
+ const indent = constructorEndMatch[1] || " ";
1143
+ const braceIndent = constructorEndMatch[2] || " ";
1144
+ const usageSnippet = `this.usageSessionKey = typeof randomUUID === "function" ? randomUUID() : Math.random().toString(36).slice(2);
1145
+ ${braceIndent}}
1146
+ ${braceIndent}usageSession() {
1147
+ ${braceIndent} if (this.closed)
1148
+ ${braceIndent} return null;
1149
+ ${braceIndent} if (!this.usageSessionKey) {
1150
+ ${braceIndent} this.usageSessionKey = typeof randomUUID === "function" ? randomUUID() : Math.random().toString(36).slice(2);
1151
+ ${braceIndent} }
1152
+ ${braceIndent} return {
1153
+ ${braceIndent} provider: this.provider,
1154
+ ${braceIndent} model: this.currentModel ?? this.config?.model,
1155
+ ${braceIndent} env: (this.launchEnv && typeof this.launchEnv === "object") ? this.launchEnv : {},
1156
+ ${braceIndent} sessionKey: this.usageSessionKey,
1157
+ ${braceIndent} };
1158
+ ${braceIndent}}`;
1159
+ acpCode = acpCode.replace(/(?:^|\n)([ \t]*)this\.extensionCommandsParser\s*=[^;\n]*;[ \t]*\r?\n([ \t]*)\}/, (match, ind1) => `\n${ind1}this.extensionCommandsParser = options.extensionCommandsParser;\n${ind1}${usageSnippet}`);
1160
+ acpModified = true;
1161
+ }
1162
+ else {
1163
+ const sessionClassMatch = acpCode.match(/(?:export\s+)?class\s+ACPAgentSession\b[^{]*\{/);
1164
+ if (sessionClassMatch && sessionClassMatch.index !== undefined) {
1165
+ const insertPos = sessionClassMatch.index + sessionClassMatch[0].length;
1166
+ const usageSnippet = `\n usageSession() {
1167
+ if (this.closed)
1168
+ return null;
1169
+ if (!this.usageSessionKey) {
1170
+ this.usageSessionKey = typeof randomUUID === "function" ? randomUUID() : Math.random().toString(36).slice(2);
1171
+ }
1172
+ return {
1173
+ provider: this.provider,
1174
+ model: this.currentModel ?? this.config?.model,
1175
+ env: (this.launchEnv && typeof this.launchEnv === "object") ? this.launchEnv : {},
1176
+ sessionKey: this.usageSessionKey,
1177
+ };
1178
+ }`;
1179
+ acpCode = acpCode.slice(0, insertPos) + usageSnippet + acpCode.slice(insertPos);
1180
+ acpModified = true;
1181
+ }
1182
+ }
1183
+ }
1104
1184
  if (acpModified) {
1105
1185
  fs.writeFileSync(acpFile, acpCode, "utf-8");
1106
1186
  changes.push(`Patched ${acpFile} for context-window token telemetry and usage updates`);
1107
1187
  }
1108
1188
  }
1189
+ // 4. Locate and patch plugin-provider.js (if present) for Paseo 0.11+
1190
+ const pluginProviderCandidates = [
1191
+ path.join(serverDir, "dist", "server", "server", "agent", "plugin-provider.js"),
1192
+ path.join(serverDir, "dist", "server", "agent", "plugin-provider.js"),
1193
+ path.join(serverDir, "dist", "agent", "plugin-provider.js"),
1194
+ ];
1195
+ let pluginProviderFile = pluginProviderCandidates.find((f) => fs.existsSync(f));
1196
+ if (!pluginProviderFile && fs.existsSync(path.join(serverDir, "dist"))) {
1197
+ const findPluginProvider = (dir, depth = 0) => {
1198
+ if (depth > 5)
1199
+ return null;
1200
+ try {
1201
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
1202
+ for (const e of entries) {
1203
+ const full = path.join(dir, e.name);
1204
+ if (e.isDirectory() && e.name !== "node_modules") {
1205
+ const found = findPluginProvider(full, depth + 1);
1206
+ if (found)
1207
+ return found;
1208
+ }
1209
+ else if (e.isFile() && e.name === "plugin-provider.js") {
1210
+ return full;
1211
+ }
1212
+ }
1213
+ }
1214
+ catch { }
1215
+ return null;
1216
+ };
1217
+ pluginProviderFile = findPluginProvider(path.join(serverDir, "dist")) || undefined;
1218
+ }
1219
+ if (pluginProviderFile) {
1220
+ let ppCode = fs.readFileSync(pluginProviderFile, "utf-8");
1221
+ if (!ppCode.includes("usageSession()")) {
1222
+ const ppClassMatch = ppCode.match(/(?:export\s+)?class\s+PluginAgentSession\b[^{]*\{/);
1223
+ if (ppClassMatch && ppClassMatch.index !== undefined) {
1224
+ const insertPos = ppClassMatch.index + ppClassMatch[0].length;
1225
+ const ppUsage = `\n usageSession() {
1226
+ if (this.closed)
1227
+ return null;
1228
+ if (!this.usageSessionKey) {
1229
+ this.usageSessionKey = typeof randomUUID === "function" ? randomUUID() : Math.random().toString(36).slice(2);
1230
+ }
1231
+ return {
1232
+ provider: this.provider,
1233
+ model: this.bridge?.config?.model,
1234
+ env: (this.bridge?.launch?.env && typeof this.bridge?.launch?.env === "object") ? this.bridge.launch.env : {},
1235
+ sessionKey: this.usageSessionKey,
1236
+ };
1237
+ }`;
1238
+ ppCode = ppCode.slice(0, insertPos) + ppUsage + ppCode.slice(insertPos);
1239
+ fs.writeFileSync(pluginProviderFile, ppCode, "utf-8");
1240
+ changes.push(`Patched ${pluginProviderFile} with usageSession for plugin provider sessions`);
1241
+ }
1242
+ }
1243
+ }
1109
1244
  return { success: true, changes };
1110
1245
  }
1111
1246
  catch (err) {
@@ -1540,6 +1675,9 @@ export function isPaseoServerPatched(serverDir) {
1540
1675
  if (!content.includes("this.currentTurnUsage =") || !content.includes("contextWindowMaxTokens")) {
1541
1676
  return false;
1542
1677
  }
1678
+ if (pluginDir && !content.includes("usageSession()")) {
1679
+ return false;
1680
+ }
1543
1681
  }
1544
1682
  return true;
1545
1683
  }
@@ -106,6 +106,11 @@ export declare const ACP_METHODS: {
106
106
  readonly SESSION_LOAD_ALIAS: "loadSession";
107
107
  readonly SESSION_RESUME: "session/resume";
108
108
  readonly SESSION_RESUME_ALIAS: "unstable_resumeSession";
109
+ readonly SESSION_LIST: "session/list";
110
+ readonly SESSION_LIST_CAMEL: "session/listSessions";
111
+ readonly SESSION_LIST_ALIAS: "sessionList";
112
+ readonly SESSION_FORK: "session/fork";
113
+ readonly SESSION_FORK_ALIAS: "forkSession";
109
114
  readonly SESSION_PROMPT: "session/prompt";
110
115
  readonly SESSION_PROMPT_ALIAS: "prompt";
111
116
  readonly SESSION_CANCEL: "session/cancel";
@@ -123,6 +128,13 @@ export declare const ACP_METHODS: {
123
128
  readonly SESSION_SET_CONFIG_OPTION_ALIAS: "setSessionConfigOption";
124
129
  readonly SESSION_UPDATE: "session/update";
125
130
  };
131
+ export interface SessionInfo {
132
+ sessionId: string;
133
+ cwd: string;
134
+ title?: string | null;
135
+ updatedAt?: string | null;
136
+ _meta?: Record<string, unknown> | null;
137
+ }
126
138
  export type ToolKind = "read" | "edit" | "execute" | "fetch" | "other";
127
139
  export declare function mapToolNameToKind(toolName: string): ToolKind;
128
140
  export declare function extractPromptText(promptInput: unknown): string;
package/dist/protocol.js CHANGED
@@ -16,6 +16,11 @@ export const ACP_METHODS = {
16
16
  SESSION_LOAD_ALIAS: "loadSession",
17
17
  SESSION_RESUME: "session/resume",
18
18
  SESSION_RESUME_ALIAS: "unstable_resumeSession",
19
+ SESSION_LIST: "session/list",
20
+ SESSION_LIST_CAMEL: "session/listSessions",
21
+ SESSION_LIST_ALIAS: "sessionList",
22
+ SESSION_FORK: "session/fork",
23
+ SESSION_FORK_ALIAS: "forkSession",
19
24
  SESSION_PROMPT: "session/prompt",
20
25
  SESSION_PROMPT_ALIAS: "prompt",
21
26
  SESSION_CANCEL: "session/cancel",
@@ -136,6 +141,8 @@ export const MODEL_PRICING = {
136
141
  "gemini-3.7-flash": { inputCostPerM: 0.10, outputCostPerM: 0.40, cachedInputCostPerM: 0.025 },
137
142
  "gemini-3.6-flash": { inputCostPerM: 0.10, outputCostPerM: 0.40, cachedInputCostPerM: 0.025 },
138
143
  "gemini-3.1-pro": { inputCostPerM: 1.25, outputCostPerM: 5.00, cachedInputCostPerM: 0.3125 },
144
+ "claude-opus-5-5": { inputCostPerM: 15.00, outputCostPerM: 75.00, cachedInputCostPerM: 1.50 },
145
+ "claude-sonnet-5-5": { inputCostPerM: 3.00, outputCostPerM: 15.00, cachedInputCostPerM: 0.30 },
139
146
  "claude-sonnet-4-6": { inputCostPerM: 3.00, outputCostPerM: 15.00, cachedInputCostPerM: 0.30 },
140
147
  "claude-opus-4-6-thinking": { inputCostPerM: 15.00, outputCostPerM: 75.00, cachedInputCostPerM: 1.50 },
141
148
  "gpt-oss-120b": { inputCostPerM: 0.15, outputCostPerM: 0.60, cachedInputCostPerM: 0.0375 },
@@ -145,6 +152,8 @@ export const MODEL_CONTEXT_WINDOWS = {
145
152
  "gemini-3.7-flash": 1_048_576,
146
153
  "gemini-3.6-flash": 1_048_576,
147
154
  "gemini-3.1-pro": 2_097_152,
155
+ "claude-opus-5-5": 200_000,
156
+ "claude-sonnet-5-5": 200_000,
148
157
  "claude-sonnet-4-6": 200_000,
149
158
  "claude-opus-4-6-thinking": 200_000,
150
159
  "gpt-oss-120b": 128_000,
@@ -178,14 +187,14 @@ export function getModelPricing(modelId) {
178
187
  if (lower.includes("pro"))
179
188
  return MODEL_PRICING["gemini-3.1-pro"];
180
189
  if (lower.includes("flash"))
181
- return MODEL_PRICING["gemini-3.7-flash"];
190
+ return MODEL_PRICING["gemini-3.8-flash"];
182
191
  if (lower.includes("gpt-oss"))
183
192
  return MODEL_PRICING["gpt-oss-120b"];
184
193
  return null;
185
194
  }
186
195
  // Keep costs at full JavaScript floating-point precision. Rounding happens
187
196
  // only when values are serialized for the UI so micro-costs accumulate.
188
- export function calculateUsageCostUsd(modelId = "gemini-3.7-flash", inputTokens = 0, outputTokens = 0, cachedInputTokens = 0) {
197
+ export function calculateUsageCostUsd(modelId = "gemini-3.8-flash", inputTokens = 0, outputTokens = 0, cachedInputTokens = 0) {
189
198
  const pricing = getModelPricing(modelId);
190
199
  if (!pricing)
191
200
  return 0;
@@ -389,6 +398,8 @@ export const FALLBACK_MODELS = [
389
398
  { modelId: "gemini-3.7-flash", name: "Gemini 3.7 Flash", description: "Google Gemini 3.7 Flash", supportedEfforts: ["high", "medium", "low"], contextWindowMaxTokens: 1_048_576 },
390
399
  { modelId: "gemini-3.6-flash", name: "Gemini 3.6 Flash", description: "Google Gemini 3.6 Flash", supportedEfforts: ["high", "medium", "low"], contextWindowMaxTokens: 1_048_576 },
391
400
  { modelId: "gemini-3.1-pro", name: "Gemini 3.1 Pro", description: "Google Gemini 3.1 Pro", supportedEfforts: ["high", "low"], contextWindowMaxTokens: 2_097_152 },
401
+ { modelId: "claude-opus-5-5", name: "Claude Opus 5.5 (Thinking)", description: "Anthropic Claude Opus 5.5 (Thinking)", supportedEfforts: ["high", "medium", "low"], contextWindowMaxTokens: 200_000 },
402
+ { modelId: "claude-sonnet-5-5", name: "Claude Sonnet 5.5 (Thinking)", description: "Anthropic Claude Sonnet 5.5 (Thinking)", supportedEfforts: ["high", "medium", "low"], contextWindowMaxTokens: 200_000 },
392
403
  { modelId: "claude-sonnet-4-6", name: "Claude Sonnet 4.6 (Thinking)", description: "Anthropic Claude Sonnet 4.6 (Thinking)", supportedEfforts: [], contextWindowMaxTokens: 200_000 },
393
404
  { modelId: "claude-opus-4-6-thinking", name: "Claude Opus 4.6 (Thinking)", description: "Anthropic Claude Opus 4.6 (Thinking)", supportedEfforts: [], contextWindowMaxTokens: 200_000 },
394
405
  { modelId: "gpt-oss-120b", name: "GPT-OSS 120B", description: "GPT-OSS 120B (Medium)", supportedEfforts: ["medium"], contextWindowMaxTokens: 128_000 },
@@ -401,8 +412,12 @@ const MODEL_CACHE_TTL_MS = 60_000;
401
412
  export function parseAgyModelsOutput(rawOutput) {
402
413
  const modelsMap = new Map();
403
414
  for (const line of rawOutput.split(/[\r\n]+/)) {
404
- const trimmed = line.trim();
405
- if (!trimmed || trimmed.includes("Fetching available models"))
415
+ const trimmed = line
416
+ .replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "")
417
+ .replace(/[\u2800-\u28ff]+/g, "")
418
+ .replace(/Fetching available models\.\.\./gi, "")
419
+ .trim();
420
+ if (!trimmed)
406
421
  continue;
407
422
  const parts = trimmed.split(/\t+|\s{2,}/).map((p) => p.trim()).filter(Boolean);
408
423
  if (parts.length < 2)
@@ -456,10 +471,20 @@ export async function fetchAvailableModels(binaryPath = "agy", force = false) {
456
471
  windowsHide: true,
457
472
  });
458
473
  const parsed = parseAgyModelsOutput(stdout);
459
- cachedModels = parsed;
474
+ const mergedMap = new Map();
475
+ for (const m of parsed) {
476
+ mergedMap.set(m.modelId, m);
477
+ }
478
+ for (const fallback of FALLBACK_MODELS) {
479
+ if (!mergedMap.has(fallback.modelId)) {
480
+ mergedMap.set(fallback.modelId, fallback);
481
+ }
482
+ }
483
+ const merged = Array.from(mergedMap.values());
484
+ cachedModels = merged;
460
485
  cachedModelsBinaryPath = binaryPath;
461
486
  lastModelFetch = Date.now();
462
- return parsed;
487
+ return merged;
463
488
  }
464
489
  catch (err) {
465
490
  logger.warn("Failed to fetch models from agy CLI, using fallback models", {
@@ -11,6 +11,7 @@ export interface PersistedSessionState {
11
11
  sessionId: string;
12
12
  conversationId?: string;
13
13
  cwd: string;
14
+ title?: string;
14
15
  model: string;
15
16
  effort: string;
16
17
  mode: string;
@@ -21,8 +22,11 @@ export interface PersistedSessionState {
21
22
  export declare class SessionStore {
22
23
  private dir;
23
24
  constructor(dir?: string);
25
+ get storageDir(): string;
24
26
  private pathFor;
25
27
  load(sessionId: string): PersistedSessionState | null;
26
28
  save(state: PersistedSessionState): void;
29
+ delete(sessionId: string): boolean;
30
+ list(cwd?: string): PersistedSessionState[];
27
31
  }
28
32
  export declare const sessionStore: SessionStore;
@@ -28,6 +28,9 @@ export class SessionStore {
28
28
  }
29
29
  catch { }
30
30
  }
31
+ get storageDir() {
32
+ return this.dir;
33
+ }
31
34
  pathFor(sessionId) {
32
35
  return path.join(this.dir, fileNameForSession(sessionId));
33
36
  }
@@ -80,5 +83,61 @@ export class SessionStore {
80
83
  });
81
84
  }
82
85
  }
86
+ delete(sessionId) {
87
+ const filePath = this.pathFor(sessionId);
88
+ try {
89
+ if (fs.existsSync(filePath)) {
90
+ fs.unlinkSync(filePath);
91
+ return true;
92
+ }
93
+ return false;
94
+ }
95
+ catch (err) {
96
+ logger.warn("Failed to delete persisted ACP session", {
97
+ sessionId,
98
+ error: err.message,
99
+ });
100
+ return false;
101
+ }
102
+ }
103
+ list(cwd) {
104
+ if (!fs.existsSync(this.dir))
105
+ return [];
106
+ const results = [];
107
+ const normalizedTargetCwd = cwd ? path.resolve(cwd).toLowerCase() : undefined;
108
+ try {
109
+ const files = fs.readdirSync(this.dir);
110
+ for (const file of files) {
111
+ if (!file.endsWith(".json"))
112
+ continue;
113
+ const fullPath = path.join(this.dir, file);
114
+ try {
115
+ const content = fs.readFileSync(fullPath, "utf8");
116
+ const parsed = JSON.parse(content);
117
+ if (!parsed.sessionId)
118
+ continue;
119
+ if (normalizedTargetCwd && parsed.cwd) {
120
+ const sessionCwd = path.resolve(parsed.cwd).toLowerCase();
121
+ if (sessionCwd !== normalizedTargetCwd)
122
+ continue;
123
+ }
124
+ results.push(parsed);
125
+ }
126
+ catch {
127
+ // ignore corrupted individual files
128
+ }
129
+ }
130
+ // Sort by updatedAt descending
131
+ results.sort((a, b) => {
132
+ const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
133
+ const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
134
+ return timeB - timeA;
135
+ });
136
+ }
137
+ catch (err) {
138
+ logger.warn("Failed to list persisted ACP sessions", { error: err.message });
139
+ }
140
+ return results;
141
+ }
83
142
  }
84
143
  export const sessionStore = new SessionStore();
package/dist/session.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { AntigravityProcess } from "./antigravity-process.js";
2
+ import { SessionInfo } from "./protocol.js";
2
3
  import { SessionStore, SessionUsageState } from "./session-store.js";
3
4
  export interface SessionOptions {
4
5
  id?: string;
5
6
  cwd?: string;
7
+ title?: string;
6
8
  model?: string;
7
9
  effort?: string;
8
10
  mode?: string;
@@ -18,6 +20,7 @@ export interface SessionOptions {
18
20
  export declare class Session {
19
21
  readonly id: string;
20
22
  cwd: string;
23
+ title?: string;
21
24
  model: string;
22
25
  effort: string;
23
26
  mode: string;
@@ -31,6 +34,7 @@ export declare class Session {
31
34
  private promptOperationActive;
32
35
  constructor(options?: SessionOptions);
33
36
  private persistedState;
37
+ setTitle(title: string): void;
34
38
  persist(): void;
35
39
  get isPromptOperationActive(): boolean;
36
40
  tryBeginPromptOperation(): boolean;
@@ -65,5 +69,6 @@ export declare class SessionManager {
65
69
  hasPersistedSession(id: string): boolean;
66
70
  closeSession(id: string): Promise<boolean>;
67
71
  closeAll(): Promise<void>;
68
- listSessions(): Session[];
72
+ listActiveSessions(): Session[];
73
+ listSessions(cwd?: string): SessionInfo[];
69
74
  }
package/dist/session.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import crypto from "node:crypto";
2
+ import path from "node:path";
2
3
  import { AntigravityProcess } from "./antigravity-process.js";
3
4
  import { logger } from "./logger.js";
4
5
  import { resolvePermissionSettings } from "./permissions.js";
@@ -7,6 +8,7 @@ import { sessionStore, } from "./session-store.js";
7
8
  export class Session {
8
9
  id;
9
10
  cwd;
11
+ title;
10
12
  model;
11
13
  effort;
12
14
  mode;
@@ -21,7 +23,8 @@ export class Session {
21
23
  constructor(options = {}) {
22
24
  this.id = options.id || crypto.randomUUID();
23
25
  this.cwd = options.cwd || process.cwd();
24
- this.model = options.model || "gemini-3.7-flash";
26
+ this.title = options.title;
27
+ this.model = options.model || "gemini-3.8-flash";
25
28
  this.effort = options.effort || "high";
26
29
  this.mode = options.mode || "default";
27
30
  this.permission =
@@ -65,6 +68,7 @@ export class Session {
65
68
  });
66
69
  this.process.on("init", () => this.persist());
67
70
  this.process.on("result", () => this.persist());
71
+ this.process.on("conversation_healed", () => this.persist());
68
72
  this.persist();
69
73
  }
70
74
  persistedState() {
@@ -72,6 +76,7 @@ export class Session {
72
76
  sessionId: this.id,
73
77
  conversationId: this.process.currentConversationId,
74
78
  cwd: this.cwd,
79
+ title: this.title,
75
80
  model: this.model,
76
81
  effort: this.effort,
77
82
  mode: this.mode,
@@ -80,6 +85,12 @@ export class Session {
80
85
  updatedAt: new Date().toISOString(),
81
86
  };
82
87
  }
88
+ setTitle(title) {
89
+ if (this.title !== title) {
90
+ this.title = title;
91
+ this.persist();
92
+ }
93
+ }
83
94
  persist() {
84
95
  this.store.save(this.persistedState());
85
96
  }
@@ -224,6 +235,7 @@ export class SessionManager {
224
235
  id: options.id,
225
236
  binaryPath: options.binaryPath || this.defaultBinaryPath,
226
237
  cwd: options.cwd || persisted?.cwd,
238
+ title: options.title || persisted?.title,
227
239
  model: options.model || persisted?.model,
228
240
  effort: options.effort || persisted?.effort,
229
241
  mode: options.mode || persisted?.mode,
@@ -264,7 +276,40 @@ export class SessionManager {
264
276
  await Promise.allSettled(Array.from(this.sessions.values(), (session) => session.close()));
265
277
  this.sessions.clear();
266
278
  }
267
- listSessions() {
279
+ listActiveSessions() {
268
280
  return Array.from(this.sessions.values());
269
281
  }
282
+ listSessions(cwd) {
283
+ const persisted = this.store.list(cwd);
284
+ const sessionMap = new Map();
285
+ for (const p of persisted) {
286
+ sessionMap.set(p.sessionId, {
287
+ sessionId: p.sessionId,
288
+ cwd: p.cwd,
289
+ title: p.title || (p.cwd ? path.basename(p.cwd) : "Antigravity Session"),
290
+ updatedAt: p.updatedAt,
291
+ });
292
+ }
293
+ for (const active of this.sessions.values()) {
294
+ if (cwd) {
295
+ const targetCwd = path.resolve(cwd).toLowerCase();
296
+ const activeCwd = path.resolve(active.cwd).toLowerCase();
297
+ if (targetCwd !== activeCwd)
298
+ continue;
299
+ }
300
+ sessionMap.set(active.id, {
301
+ sessionId: active.id,
302
+ cwd: active.cwd,
303
+ title: active.title || (active.cwd ? path.basename(active.cwd) : "Antigravity Session"),
304
+ updatedAt: active.lastActivity.toISOString(),
305
+ });
306
+ }
307
+ const list = Array.from(sessionMap.values());
308
+ list.sort((a, b) => {
309
+ const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
310
+ const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
311
+ return timeB - timeA;
312
+ });
313
+ return list;
314
+ }
270
315
  }
@@ -0,0 +1,25 @@
1
+ import { ToolKind } from "./protocol.js";
2
+ export interface ReplayItem {
3
+ type: "user_message" | "agent_message" | "agent_thought" | "tool_call" | "tool_call_update";
4
+ text?: string;
5
+ toolCallId?: string;
6
+ title?: string;
7
+ kind?: ToolKind;
8
+ rawInput?: Record<string, unknown>;
9
+ rawOutput?: string;
10
+ status?: "completed" | "failed";
11
+ }
12
+ /**
13
+ * Strips XML-like tags injected by harness or system prompts (e.g., <USER_REQUEST>, <ADDITIONAL_METADATA>)
14
+ * to recover the clean user prompt for history display in clients like Paseo.
15
+ */
16
+ export declare function extractCleanUserPrompt(rawText: string): string;
17
+ /**
18
+ * Resolves the path to the Antigravity conversation directory in ~/.gemini/antigravity-cli/brain/<conversationId>
19
+ */
20
+ export declare function resolveConversationBrainDir(conversationId: string): string | null;
21
+ /**
22
+ * Loads and parses transcript.jsonl (or transcript_full.jsonl) for a conversation,
23
+ * transforming each step into ordered ACP replay events.
24
+ */
25
+ export declare function loadConversationTranscript(conversationId: string): ReplayItem[];