paseo-acp-agy 1.2.7 → 1.2.9

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.
@@ -0,0 +1,4 @@
1
+ import { SessionInfo } from "./protocol.js";
2
+ export declare function getAgyAppDataDir(): string;
3
+ export declare function loadNativeAntigravitySessions(cwd?: string): SessionInfo[];
4
+ export declare function getNativeAntigravitySession(conversationId: string): SessionInfo | null;
@@ -0,0 +1,176 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { createRequire } from "node:module";
5
+ import { logger } from "./logger.js";
6
+ const require = createRequire(import.meta.url);
7
+ export function getAgyAppDataDir() {
8
+ if (process.env.AGY_APP_DATA_DIR) {
9
+ return process.env.AGY_APP_DATA_DIR;
10
+ }
11
+ if (process.env.AGY_BRAIN_DIR) {
12
+ return path.dirname(process.env.AGY_BRAIN_DIR);
13
+ }
14
+ return path.join(os.homedir(), ".gemini", "antigravity-cli");
15
+ }
16
+ export function loadNativeAntigravitySessions(cwd) {
17
+ const results = [];
18
+ const appDataDir = getAgyAppDataDir();
19
+ const dbPath = path.join(appDataDir, "conversation_summaries.db");
20
+ const normalizedTargetCwd = cwd ? path.resolve(cwd).toLowerCase() : undefined;
21
+ if (fs.existsSync(dbPath)) {
22
+ let DatabaseSync = null;
23
+ try {
24
+ DatabaseSync = require("node:sqlite")?.DatabaseSync;
25
+ }
26
+ catch { }
27
+ if (DatabaseSync) {
28
+ try {
29
+ const db = new DatabaseSync(dbPath, { readOnly: true });
30
+ const rows = db
31
+ .prepare(`SELECT conversation_id, title, preview, last_modified_time, workspace_uris
32
+ FROM conversation_summaries
33
+ ORDER BY last_modified_time DESC
34
+ LIMIT 100`)
35
+ .all();
36
+ for (const row of rows) {
37
+ if (!row.conversation_id)
38
+ continue;
39
+ let primaryCwd = process.cwd();
40
+ let matchesCwd = false;
41
+ if (row.workspace_uris) {
42
+ try {
43
+ const uris = JSON.parse(row.workspace_uris);
44
+ if (Array.isArray(uris) && uris.length > 0) {
45
+ const filePaths = uris
46
+ .map((u) => u.replace(/^file:\/\//i, ""))
47
+ .filter(Boolean);
48
+ if (filePaths.length > 0) {
49
+ primaryCwd = filePaths[0];
50
+ }
51
+ if (normalizedTargetCwd) {
52
+ matchesCwd = filePaths.some((p) => path.resolve(p).toLowerCase() === normalizedTargetCwd);
53
+ }
54
+ }
55
+ }
56
+ catch { }
57
+ }
58
+ if (normalizedTargetCwd && !matchesCwd) {
59
+ continue;
60
+ }
61
+ const rawDate = row.last_modified_time;
62
+ const dateIso = rawDate ? new Date(rawDate).toISOString() : undefined;
63
+ results.push({
64
+ sessionId: row.conversation_id,
65
+ cwd: primaryCwd,
66
+ title: (row.title && row.title.trim()) ||
67
+ (row.preview && row.preview.trim()) ||
68
+ "Antigravity Session",
69
+ updatedAt: dateIso,
70
+ });
71
+ }
72
+ }
73
+ catch (err) {
74
+ logger.debug("Failed to read native conversation summaries from sqlite", {
75
+ error: err.message,
76
+ });
77
+ }
78
+ }
79
+ }
80
+ // Fallback: Only scan brainDir if no conversations were found in DB
81
+ if (results.length === 0) {
82
+ try {
83
+ const brainDir = path.join(appDataDir, "brain");
84
+ if (fs.existsSync(brainDir)) {
85
+ const entries = fs.readdirSync(brainDir, { withFileTypes: true });
86
+ for (const entry of entries) {
87
+ if (!entry.isDirectory())
88
+ continue;
89
+ const convId = entry.name;
90
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(convId)) {
91
+ continue;
92
+ }
93
+ if (results.some((r) => r.sessionId === convId))
94
+ continue;
95
+ const transcriptPath = path.join(brainDir, convId, ".system_generated", "logs", "transcript.jsonl");
96
+ if (fs.existsSync(transcriptPath)) {
97
+ const stat = fs.statSync(transcriptPath);
98
+ if (stat.size > 0) {
99
+ results.push({
100
+ sessionId: convId,
101
+ cwd: process.cwd(),
102
+ title: "Antigravity Session",
103
+ updatedAt: stat.mtime.toISOString(),
104
+ });
105
+ if (results.length >= 50)
106
+ break;
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ catch { }
113
+ }
114
+ return results;
115
+ }
116
+ export function getNativeAntigravitySession(conversationId) {
117
+ if (!conversationId)
118
+ return null;
119
+ const appDataDir = getAgyAppDataDir();
120
+ const dbPath = path.join(appDataDir, "conversation_summaries.db");
121
+ if (fs.existsSync(dbPath)) {
122
+ let DatabaseSync = null;
123
+ try {
124
+ DatabaseSync = require("node:sqlite")?.DatabaseSync;
125
+ }
126
+ catch { }
127
+ if (DatabaseSync) {
128
+ try {
129
+ const db = new DatabaseSync(dbPath, { readOnly: true });
130
+ const row = db
131
+ .prepare(`SELECT conversation_id, title, preview, last_modified_time, workspace_uris
132
+ FROM conversation_summaries
133
+ WHERE conversation_id = ?`)
134
+ .get(conversationId);
135
+ if (row) {
136
+ let primaryCwd = process.cwd();
137
+ if (row.workspace_uris) {
138
+ try {
139
+ const uris = JSON.parse(row.workspace_uris);
140
+ if (Array.isArray(uris) && uris.length > 0) {
141
+ const p = uris[0].replace(/^file:\/\//i, "");
142
+ if (p)
143
+ primaryCwd = p;
144
+ }
145
+ }
146
+ catch { }
147
+ }
148
+ return {
149
+ sessionId: row.conversation_id,
150
+ cwd: primaryCwd,
151
+ title: (row.title && row.title.trim()) ||
152
+ (row.preview && row.preview.trim()) ||
153
+ "Antigravity Session",
154
+ updatedAt: row.last_modified_time
155
+ ? new Date(row.last_modified_time).toISOString()
156
+ : undefined,
157
+ };
158
+ }
159
+ }
160
+ catch { }
161
+ }
162
+ }
163
+ // Fallback check transcript file
164
+ const brainDir = path.join(appDataDir, "brain", conversationId);
165
+ const transcriptPath = path.join(brainDir, ".system_generated", "logs", "transcript.jsonl");
166
+ if (fs.existsSync(transcriptPath)) {
167
+ const stat = fs.statSync(transcriptPath);
168
+ return {
169
+ sessionId: conversationId,
170
+ cwd: process.cwd(),
171
+ title: "Antigravity Session",
172
+ updatedAt: stat.mtime.toISOString(),
173
+ };
174
+ }
175
+ return null;
176
+ }
@@ -116,6 +116,13 @@ export class SessionStore {
116
116
  const parsed = JSON.parse(content);
117
117
  if (!parsed.sessionId)
118
118
  continue;
119
+ // Skip untitled placeholder sessions that have neither conversationId nor any recorded usage
120
+ const hasTitle = Boolean(parsed.title && parsed.title.trim());
121
+ const hasConv = Boolean(parsed.conversationId);
122
+ const hasUsage = Boolean(parsed.usage && (parsed.usage.totalTokens > 0 || parsed.usage.inputTokens > 0));
123
+ if (!hasTitle && !hasConv && !hasUsage) {
124
+ continue;
125
+ }
119
126
  if (normalizedTargetCwd && parsed.cwd) {
120
127
  const sessionCwd = path.resolve(parsed.cwd).toLowerCase();
121
128
  if (sessionCwd !== normalizedTargetCwd)
package/dist/session.d.ts CHANGED
@@ -11,6 +11,7 @@ export interface SessionOptions {
11
11
  permission?: string;
12
12
  conversationId?: string;
13
13
  usage?: SessionUsageState;
14
+ updatedAt?: string;
14
15
  sandbox?: boolean;
15
16
  dangerouslySkipPermissions?: boolean;
16
17
  env?: Record<string, string>;
@@ -27,6 +28,7 @@ export declare class Session {
27
28
  permission: string;
28
29
  readonly createdAt: Date;
29
30
  lastActivity: Date;
31
+ updatedAt: string;
30
32
  isCancelled: boolean;
31
33
  process: AntigravityProcess;
32
34
  usage: SessionUsageState;
package/dist/session.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import crypto from "node:crypto";
2
+ import fs from "node:fs";
2
3
  import path from "node:path";
3
4
  import { AntigravityProcess } from "./antigravity-process.js";
4
5
  import { logger } from "./logger.js";
5
6
  import { resolvePermissionSettings } from "./permissions.js";
6
7
  import { calculateUsageCostUsd, getModelContextWindow } from "./protocol.js";
7
8
  import { sessionStore, } from "./session-store.js";
9
+ import { resolveConversationBrainDir } from "./transcript.js";
10
+ import { loadNativeAntigravitySessions, getNativeAntigravitySession, } from "./native-sessions.js";
8
11
  export class Session {
9
12
  id;
10
13
  cwd;
@@ -15,6 +18,7 @@ export class Session {
15
18
  permission;
16
19
  createdAt;
17
20
  lastActivity;
21
+ updatedAt;
18
22
  isCancelled = false;
19
23
  process;
20
24
  usage;
@@ -32,6 +36,7 @@ export class Session {
32
36
  (options.sandbox ? "sandbox" : options.dangerouslySkipPermissions ? "bypass" : "default");
33
37
  this.createdAt = new Date();
34
38
  this.lastActivity = new Date();
39
+ this.updatedAt = options.updatedAt || new Date().toISOString();
35
40
  this.store = options.store || sessionStore;
36
41
  const maxTokens = getModelContextWindow(this.model);
37
42
  this.usage = options.usage
@@ -82,7 +87,7 @@ export class Session {
82
87
  mode: this.mode,
83
88
  permission: this.permission,
84
89
  usage: this.usage,
85
- updatedAt: new Date().toISOString(),
90
+ updatedAt: this.updatedAt,
86
91
  };
87
92
  }
88
93
  setTitle(title) {
@@ -199,10 +204,13 @@ export class Session {
199
204
  }
200
205
  }
201
206
  this.usage.contextWindowUsedTokens = Math.min(activeContextTokens, executingMaxTokens);
207
+ this.lastActivity = new Date();
208
+ this.updatedAt = this.lastActivity.toISOString();
202
209
  this.persist();
203
210
  }
204
211
  touch() {
205
212
  this.lastActivity = new Date();
213
+ this.updatedAt = this.lastActivity.toISOString();
206
214
  }
207
215
  cancelTurn() {
208
216
  const processCancelled = this.process.cancelCurrentTurn();
@@ -228,20 +236,26 @@ export class SessionManager {
228
236
  }
229
237
  createSession(options = {}) {
230
238
  const persisted = options.id ? this.store.load(options.id) : null;
231
- if (options.id && !persisted) {
239
+ const native = options.id ? getNativeAntigravitySession(options.id) : null;
240
+ const brainDir = options.id ? resolveConversationBrainDir(options.id) : null;
241
+ const hasBrainDir = Boolean(brainDir && fs.existsSync(brainDir));
242
+ if (options.id && !persisted && !native && !hasBrainDir) {
232
243
  throw new Error(`Cannot restore unknown ACP session: ${options.id}`);
233
244
  }
234
245
  const session = new Session({
235
246
  id: options.id,
236
247
  binaryPath: options.binaryPath || this.defaultBinaryPath,
237
- cwd: options.cwd || persisted?.cwd,
238
- title: options.title || persisted?.title,
248
+ cwd: options.cwd || persisted?.cwd || (native?.cwd ?? undefined),
249
+ title: options.title || persisted?.title || (native?.title ?? undefined),
239
250
  model: options.model || persisted?.model,
240
251
  effort: options.effort || persisted?.effort,
241
252
  mode: options.mode || persisted?.mode,
242
253
  permission: options.permission || persisted?.permission,
243
- conversationId: options.conversationId || persisted?.conversationId,
254
+ conversationId: options.conversationId ||
255
+ persisted?.conversationId ||
256
+ (options.id && (hasBrainDir || native) ? options.id : undefined),
244
257
  usage: options.usage || persisted?.usage,
258
+ updatedAt: options.updatedAt || persisted?.updatedAt || (native?.updatedAt ?? undefined),
245
259
  sandbox: options.sandbox,
246
260
  dangerouslySkipPermissions: options.dangerouslySkipPermissions,
247
261
  env: options.env,
@@ -280,16 +294,32 @@ export class SessionManager {
280
294
  return Array.from(this.sessions.values());
281
295
  }
282
296
  listSessions(cwd) {
283
- const persisted = this.store.list(cwd);
284
297
  const sessionMap = new Map();
298
+ // 1. Add native Antigravity conversations
299
+ const nativeSessions = loadNativeAntigravitySessions(cwd);
300
+ const nativeByConvId = new Map();
301
+ for (const n of nativeSessions) {
302
+ nativeByConvId.set(n.sessionId, n);
303
+ sessionMap.set(n.sessionId, n);
304
+ }
305
+ // 2. Add persisted ACP sessions
306
+ const persisted = this.store.list(cwd);
285
307
  for (const p of persisted) {
308
+ const nativeMatch = p.conversationId ? nativeByConvId.get(p.conversationId) : undefined;
309
+ const realTitle = p.title || nativeMatch?.title || (p.cwd ? path.basename(p.cwd) : "Antigravity Session");
310
+ const realUpdatedAt = nativeMatch?.updatedAt || p.updatedAt;
311
+ // If an ACP session wraps a native conversation, remove the raw native entry to prevent duplicates
312
+ if (p.conversationId && sessionMap.has(p.conversationId)) {
313
+ sessionMap.delete(p.conversationId);
314
+ }
286
315
  sessionMap.set(p.sessionId, {
287
316
  sessionId: p.sessionId,
288
317
  cwd: p.cwd,
289
- title: p.title || (p.cwd ? path.basename(p.cwd) : "Antigravity Session"),
290
- updatedAt: p.updatedAt,
318
+ title: realTitle,
319
+ updatedAt: realUpdatedAt,
291
320
  });
292
321
  }
322
+ // 3. Active in-memory sessions
293
323
  for (const active of this.sessions.values()) {
294
324
  if (cwd) {
295
325
  const targetCwd = path.resolve(cwd).toLowerCase();
@@ -297,10 +327,16 @@ export class SessionManager {
297
327
  if (targetCwd !== activeCwd)
298
328
  continue;
299
329
  }
330
+ const hasTitle = Boolean(active.title && active.title.trim());
331
+ const hasConv = Boolean(active.process.currentConversationId);
332
+ const hasUsage = Boolean(active.usage && active.usage.totalTokens > 0);
333
+ if (!hasTitle && !hasConv && !hasUsage)
334
+ continue;
335
+ const existing = sessionMap.get(active.id);
300
336
  sessionMap.set(active.id, {
301
337
  sessionId: active.id,
302
338
  cwd: active.cwd,
303
- title: active.title || (active.cwd ? path.basename(active.cwd) : "Antigravity Session"),
339
+ title: active.title || existing?.title || (active.cwd ? path.basename(active.cwd) : "Antigravity Session"),
304
340
  updatedAt: active.lastActivity.toISOString(),
305
341
  });
306
342
  }
@@ -310,6 +346,6 @@ export class SessionManager {
310
346
  const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
311
347
  return timeB - timeA;
312
348
  });
313
- return list;
349
+ return list.slice(0, 100);
314
350
  }
315
351
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paseo-acp-agy",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
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",