llm-cli-gateway 1.17.4 → 1.17.5

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +1 -1
  3. package/dist/approval-manager.js +0 -8
  4. package/dist/async-job-manager.d.ts +0 -113
  5. package/dist/async-job-manager.js +6 -124
  6. package/dist/cache-stats.d.ts +0 -89
  7. package/dist/cache-stats.js +0 -62
  8. package/dist/claude-mcp-config.js +0 -1
  9. package/dist/cli-updater.d.ts +0 -8
  10. package/dist/cli-updater.js +0 -12
  11. package/dist/codex-json-parser.d.ts +0 -20
  12. package/dist/codex-json-parser.js +0 -21
  13. package/dist/config.d.ts +0 -31
  14. package/dist/config.js +2 -72
  15. package/dist/db.d.ts +0 -18
  16. package/dist/db.js +0 -22
  17. package/dist/doctor.d.ts +0 -49
  18. package/dist/doctor.js +0 -47
  19. package/dist/endpoint-exposure.js +0 -1
  20. package/dist/executor.d.ts +0 -19
  21. package/dist/executor.js +3 -38
  22. package/dist/flight-recorder.d.ts +0 -26
  23. package/dist/flight-recorder.js +1 -70
  24. package/dist/gemini-json-parser.d.ts +0 -25
  25. package/dist/gemini-json-parser.js +0 -28
  26. package/dist/health.d.ts +0 -3
  27. package/dist/health.js +0 -3
  28. package/dist/index.d.ts +1 -221
  29. package/dist/index.js +14 -563
  30. package/dist/job-store.d.ts +0 -74
  31. package/dist/job-store.js +1 -73
  32. package/dist/logger.d.ts +0 -7
  33. package/dist/logger.js +0 -6
  34. package/dist/migrate-sessions.d.ts +0 -3
  35. package/dist/migrate-sessions.js +0 -16
  36. package/dist/migrate.js +1 -18
  37. package/dist/mistral-meta-json-parser.js +0 -67
  38. package/dist/model-registry.js +0 -13
  39. package/dist/pricing.d.ts +0 -46
  40. package/dist/pricing.js +0 -47
  41. package/dist/process-monitor.d.ts +0 -15
  42. package/dist/process-monitor.js +2 -31
  43. package/dist/prompt-parts.d.ts +0 -25
  44. package/dist/prompt-parts.js +0 -11
  45. package/dist/provider-status.d.ts +0 -8
  46. package/dist/provider-status.js +0 -11
  47. package/dist/request-helpers.d.ts +0 -334
  48. package/dist/request-helpers.js +1 -229
  49. package/dist/resources.d.ts +0 -20
  50. package/dist/resources.js +1 -34
  51. package/dist/retry.d.ts +0 -45
  52. package/dist/retry.js +3 -40
  53. package/dist/session-manager-pg.d.ts +0 -32
  54. package/dist/session-manager-pg.js +0 -32
  55. package/dist/session-manager.d.ts +0 -21
  56. package/dist/session-manager.js +1 -15
  57. package/dist/stream-json-parser.d.ts +0 -18
  58. package/dist/stream-json-parser.js +0 -22
  59. package/dist/upstream-contracts.d.ts +0 -55
  60. package/dist/upstream-contracts.js +0 -77
  61. package/dist/validation-orchestrator.js +0 -3
  62. package/dist/worktree-manager.d.ts +0 -9
  63. package/dist/worktree-manager.js +0 -21
  64. package/package.json +1 -1
@@ -23,10 +23,6 @@ export declare function resolveJobStoreDbPath(): string | null;
23
23
  export declare function resolveJobRetentionMs(): number;
24
24
  export declare function resolveDedupWindowMs(): number;
25
25
  export declare function computeRequestKey(cli: string, args: string[], extra?: string): string;
26
- /**
27
- * Public surface every backend (sqlite/postgres/memory) must implement. The
28
- * AsyncJobManager talks to this interface only.
29
- */
30
26
  export interface JobStore {
31
27
  recordStart(input: {
32
28
  id: string;
@@ -51,15 +47,6 @@ export interface JobStore {
51
47
  }): void;
52
48
  getById(id: string): JobRecord | null;
53
49
  findByRequestKey(requestKey: string): JobRecord | null;
54
- /**
55
- * Flip every `status='running'` row to `'orphaned'` at gateway boot.
56
- *
57
- * Returns the row count AND a snapshot of every row that was flipped, so
58
- * AsyncJobManager can write a flight-recorder logComplete with the full
59
- * sync-helper-equivalent payload (response from stderr||stdout,
60
- * durationMs from startedAt). Pre-slice-1.5 rows that never wrote a
61
- * logStart degrade silently to a no-op UPDATE inside the FR.
62
- */
63
50
  markOrphanedOnStartup(): {
64
51
  count: number;
65
52
  orphaned: Array<OrphanedJobSnapshot>;
@@ -67,11 +54,6 @@ export interface JobStore {
67
54
  evictExpired(): number;
68
55
  close(): void;
69
56
  }
70
- /**
71
- * Per-orphan snapshot returned by `markOrphanedOnStartup` so the
72
- * AsyncJobManager constructor can build a faithful FlightLogResult for
73
- * each row it flipped.
74
- */
75
57
  export interface OrphanedJobSnapshot {
76
58
  id: string;
77
59
  correlationId: string;
@@ -80,10 +62,6 @@ export interface OrphanedJobSnapshot {
80
62
  stderr: string;
81
63
  exitCode: number | null;
82
64
  }
83
- /**
84
- * SQLite-backed job store. Default backend for production. Durable across
85
- * gateway restarts; safe for single-instance deployments.
86
- */
87
65
  export declare class SqliteJobStore implements JobStore {
88
66
  private logger;
89
67
  private db;
@@ -101,9 +79,6 @@ export declare class SqliteJobStore implements JobStore {
101
79
  retentionMs?: number;
102
80
  dedupWindowMs?: number;
103
81
  });
104
- /**
105
- * Insert a new running job row. Caller has already computed requestKey.
106
- */
107
82
  recordStart(input: {
108
83
  id: string;
109
84
  correlationId: string;
@@ -114,13 +89,7 @@ export declare class SqliteJobStore implements JobStore {
114
89
  startedAt: string;
115
90
  pid: number | null;
116
91
  }): void;
117
- /**
118
- * Batched output flush. Cheap to call repeatedly; better-sqlite3 is sync.
119
- */
120
92
  recordOutput(id: string, stdout: string, stderr: string, outputTruncated: boolean): void;
121
- /**
122
- * Mark a job as completed/failed/canceled. Sets expires_at = now + retention.
123
- */
124
93
  recordComplete(input: {
125
94
  id: string;
126
95
  status: Exclude<JobStoreStatus, "running">;
@@ -132,43 +101,15 @@ export declare class SqliteJobStore implements JobStore {
132
101
  finishedAt: string;
133
102
  }): void;
134
103
  getById(id: string): JobRecord | null;
135
- /**
136
- * Returns the most recent matching job within the dedup window, if any.
137
- * Caller pre-filters out forceRefresh requests.
138
- */
139
104
  findByRequestKey(requestKey: string): JobRecord | null;
140
- /**
141
- * On gateway boot, flip any jobs that were 'running' to 'orphaned'.
142
- * The child processes were detached but can't be reattached to in this process.
143
- *
144
- * Returns the row count + a per-orphan snapshot so AsyncJobManager can
145
- * write a flight-recorder logComplete with proper audit data
146
- * (durationMs from startedAt, response from stderr||stdout).
147
- */
148
105
  markOrphanedOnStartup(): {
149
106
  count: number;
150
107
  orphaned: Array<OrphanedJobSnapshot>;
151
108
  };
152
- /**
153
- * Delete rows whose expires_at has passed. Returns number of rows deleted.
154
- */
155
109
  evictExpired(): number;
156
110
  close(): void;
157
111
  }
158
- /**
159
- * Backwards-compatibility alias. Older code and tests construct `new JobStore(path)`
160
- * directly; that surface now resolves to the SQLite implementation. Prefer
161
- * `createJobStore(config)` in new code.
162
- *
163
- * @deprecated Use `SqliteJobStore` directly, or `createJobStore(persistenceConfig)`.
164
- */
165
112
  export declare const JobStoreClass: typeof SqliteJobStore;
166
- /**
167
- * In-process job store. Same semantics as SqliteJobStore but state lives in a
168
- * Map and is lost on process exit. Use for tests and ephemeral/CI gateways
169
- * that have explicitly acknowledged the trade-off via
170
- * `[persistence].acknowledgeEphemeral = true`.
171
- */
172
113
  export declare class MemoryJobStore implements JobStore {
173
114
  private rows;
174
115
  private retentionMs;
@@ -200,10 +141,6 @@ export declare class MemoryJobStore implements JobStore {
200
141
  }): void;
201
142
  getById(id: string): JobRecord | null;
202
143
  findByRequestKey(requestKey: string): JobRecord | null;
203
- /**
204
- * In-memory stores have no cross-process state, so any "running" rows here
205
- * came from this very process and aren't actually orphaned. No-op.
206
- */
207
144
  markOrphanedOnStartup(): {
208
145
  count: number;
209
146
  orphaned: Array<OrphanedJobSnapshot>;
@@ -211,12 +148,6 @@ export declare class MemoryJobStore implements JobStore {
211
148
  evictExpired(): number;
212
149
  close(): void;
213
150
  }
214
- /**
215
- * Stub for the planned Postgres backend. The interface and config surface ship
216
- * now so multi-instance deployments can plan around them, but the
217
- * implementation is intentionally not yet provided — calling code must select
218
- * `sqlite` or `memory` until a real impl lands.
219
- */
220
151
  export declare class PostgresJobStore implements JobStore {
221
152
  constructor(_dsn: string, _logger?: Logger);
222
153
  recordStart(): void;
@@ -231,9 +162,4 @@ export declare class PostgresJobStore implements JobStore {
231
162
  evictExpired(): number;
232
163
  close(): void;
233
164
  }
234
- /**
235
- * Construct the JobStore appropriate to the resolved PersistenceConfig.
236
- * Returns `null` when `backend = "none"` — callers must not register
237
- * `*_request_async` tools in that case (use `config.asyncJobsEnabled`).
238
- */
239
165
  export declare function createJobStore(config: PersistenceConfig, logger?: Logger): JobStore | null;
package/dist/job-store.js CHANGED
@@ -25,7 +25,7 @@ export function resolveJobRetentionMs() {
25
25
  }
26
26
  return days * 24 * 60 * 60 * 1000;
27
27
  }
28
- const DEFAULT_DEDUP_WINDOW_MS = 60 * 60 * 1000; // 1 hour
28
+ const DEFAULT_DEDUP_WINDOW_MS = 60 * 60 * 1000;
29
29
  export function resolveDedupWindowMs() {
30
30
  const raw = process.env.LLM_GATEWAY_DEDUP_WINDOW_MS;
31
31
  if (raw === undefined)
@@ -59,10 +59,6 @@ function rowToRecord(row) {
59
59
  expiresAt: row.expires_at,
60
60
  };
61
61
  }
62
- /**
63
- * SQLite-backed job store. Default backend for production. Durable across
64
- * gateway restarts; safe for single-instance deployments.
65
- */
66
62
  export class SqliteJobStore {
67
63
  logger;
68
64
  db;
@@ -116,7 +112,6 @@ export class SqliteJobStore {
116
112
  chmodSync(dbPath, 0o600);
117
113
  }
118
114
  catch {
119
- // Best effort permissions hardening.
120
115
  }
121
116
  }
122
117
  this.retentionMs = options.retentionMs ?? resolveJobRetentionMs();
@@ -140,8 +135,6 @@ export class SqliteJobStore {
140
135
  WHERE id = @id
141
136
  `);
142
137
  this.getByIdStmt = this.db.prepare(`SELECT * FROM jobs WHERE id = ?`);
143
- // Dedup query: most recent non-orphaned job with matching request_key, started within window.
144
- // Exclude orphaned/canceled/failed-with-error from dedup so a broken run isn't reused.
145
138
  this.findByRequestKeyStmt = this.db.prepare(`
146
139
  SELECT * FROM jobs
147
140
  WHERE request_key = ?
@@ -150,12 +143,6 @@ export class SqliteJobStore {
150
143
  ORDER BY started_at DESC
151
144
  LIMIT 1
152
145
  `);
153
- // Snapshot every in-flight row's audit data BEFORE the orphan-flip
154
- // UPDATE so AsyncJobManager can construct a full FlightLogResult per
155
- // orphan. No transaction wrapper required: gateway boot is
156
- // single-threaded before any new jobs can arrive, so no
157
- // status='running' row can be inserted between this SELECT and the
158
- // UPDATE below.
159
146
  this.selectRunningOrphansStmt = this.db.prepare(`
160
147
  SELECT id, correlation_id, started_at, stdout, stderr, exit_code
161
148
  FROM jobs WHERE status = 'running'
@@ -170,9 +157,6 @@ export class SqliteJobStore {
170
157
  `);
171
158
  this.deleteExpiredStmt = this.db.prepare(`DELETE FROM jobs WHERE expires_at < ?`);
172
159
  }
173
- /**
174
- * Insert a new running job row. Caller has already computed requestKey.
175
- */
176
160
  recordStart(input) {
177
161
  this.insertStmt.run({
178
162
  id: input.id,
@@ -190,13 +174,9 @@ export class SqliteJobStore {
190
174
  started_at: input.startedAt,
191
175
  finished_at: null,
192
176
  pid: input.pid,
193
- // Running jobs never expire — only completed/failed/canceled do.
194
177
  expires_at: FAR_FUTURE_ISO,
195
178
  });
196
179
  }
197
- /**
198
- * Batched output flush. Cheap to call repeatedly; better-sqlite3 is sync.
199
- */
200
180
  recordOutput(id, stdout, stderr, outputTruncated) {
201
181
  this.updateOutputStmt.run({
202
182
  id,
@@ -205,9 +185,6 @@ export class SqliteJobStore {
205
185
  output_truncated: outputTruncated ? 1 : 0,
206
186
  });
207
187
  }
208
- /**
209
- * Mark a job as completed/failed/canceled. Sets expires_at = now + retention.
210
- */
211
188
  recordComplete(input) {
212
189
  const expiresAt = new Date(Date.parse(input.finishedAt) + this.retentionMs).toISOString();
213
190
  this.updateCompleteStmt.run({
@@ -226,30 +203,14 @@ export class SqliteJobStore {
226
203
  const row = this.getByIdStmt.get(id);
227
204
  return row ? rowToRecord(row) : null;
228
205
  }
229
- /**
230
- * Returns the most recent matching job within the dedup window, if any.
231
- * Caller pre-filters out forceRefresh requests.
232
- */
233
206
  findByRequestKey(requestKey) {
234
207
  const cutoff = new Date(Date.now() - this.dedupWindowMs).toISOString();
235
208
  const row = this.findByRequestKeyStmt.get(requestKey, cutoff);
236
209
  return row ? rowToRecord(row) : null;
237
210
  }
238
- /**
239
- * On gateway boot, flip any jobs that were 'running' to 'orphaned'.
240
- * The child processes were detached but can't be reattached to in this process.
241
- *
242
- * Returns the row count + a per-orphan snapshot so AsyncJobManager can
243
- * write a flight-recorder logComplete with proper audit data
244
- * (durationMs from startedAt, response from stderr||stdout).
245
- */
246
211
  markOrphanedOnStartup() {
247
212
  const now = new Date().toISOString();
248
- // Orphaned jobs retain a short window so callers can collect the partial output,
249
- // then evict. Reuse the standard retention.
250
213
  const expiresAt = new Date(Date.now() + this.retentionMs).toISOString();
251
- // SELECT before UPDATE — gateway boot is single-threaded so no row can
252
- // appear in 'running' between the two statements.
253
214
  const rows = (this.selectRunningOrphansStmt.all?.() ?? []);
254
215
  const orphaned = rows.map(row => ({
255
216
  id: row.id,
@@ -262,9 +223,6 @@ export class SqliteJobStore {
262
223
  const result = this.markOrphanedStmt.run(now, expiresAt);
263
224
  return { count: result?.changes ?? 0, orphaned };
264
225
  }
265
- /**
266
- * Delete rows whose expires_at has passed. Returns number of rows deleted.
267
- */
268
226
  evictExpired() {
269
227
  const now = new Date().toISOString();
270
228
  const result = this.deleteExpiredStmt.run(now);
@@ -279,20 +237,7 @@ export class SqliteJobStore {
279
237
  }
280
238
  }
281
239
  }
282
- /**
283
- * Backwards-compatibility alias. Older code and tests construct `new JobStore(path)`
284
- * directly; that surface now resolves to the SQLite implementation. Prefer
285
- * `createJobStore(config)` in new code.
286
- *
287
- * @deprecated Use `SqliteJobStore` directly, or `createJobStore(persistenceConfig)`.
288
- */
289
240
  export const JobStoreClass = SqliteJobStore;
290
- /**
291
- * In-process job store. Same semantics as SqliteJobStore but state lives in a
292
- * Map and is lost on process exit. Use for tests and ephemeral/CI gateways
293
- * that have explicitly acknowledged the trade-off via
294
- * `[persistence].acknowledgeEphemeral = true`.
295
- */
296
241
  export class MemoryJobStore {
297
242
  rows = new Map();
298
243
  retentionMs;
@@ -362,10 +307,6 @@ export class MemoryJobStore {
362
307
  }
363
308
  return best ? { ...best } : null;
364
309
  }
365
- /**
366
- * In-memory stores have no cross-process state, so any "running" rows here
367
- * came from this very process and aren't actually orphaned. No-op.
368
- */
369
310
  markOrphanedOnStartup() {
370
311
  return { count: 0, orphaned: [] };
371
312
  }
@@ -384,12 +325,6 @@ export class MemoryJobStore {
384
325
  this.rows.clear();
385
326
  }
386
327
  }
387
- /**
388
- * Stub for the planned Postgres backend. The interface and config surface ship
389
- * now so multi-instance deployments can plan around them, but the
390
- * implementation is intentionally not yet provided — calling code must select
391
- * `sqlite` or `memory` until a real impl lands.
392
- */
393
328
  export class PostgresJobStore {
394
329
  constructor(_dsn, _logger = noopLogger) {
395
330
  throw new Error("PostgresJobStore is not yet implemented. Use backend = 'sqlite' (single-instance) or " +
@@ -417,14 +352,8 @@ export class PostgresJobStore {
417
352
  throw new Error("not implemented");
418
353
  }
419
354
  close() {
420
- /* no-op */
421
355
  }
422
356
  }
423
- /**
424
- * Construct the JobStore appropriate to the resolved PersistenceConfig.
425
- * Returns `null` when `backend = "none"` — callers must not register
426
- * `*_request_async` tools in that case (use `config.asyncJobsEnabled`).
427
- */
428
357
  export function createJobStore(config, logger = noopLogger) {
429
358
  const opts = {
430
359
  retentionMs: config.retentionDays * 24 * 60 * 60 * 1000,
@@ -436,7 +365,6 @@ export function createJobStore(config, logger = noopLogger) {
436
365
  case "memory":
437
366
  return new MemoryJobStore(opts);
438
367
  case "postgres":
439
- // Throws today; design surface is honest so callers can react.
440
368
  return new PostgresJobStore(config.dsn ?? "", logger);
441
369
  case "sqlite":
442
370
  default:
package/dist/logger.d.ts CHANGED
@@ -2,14 +2,7 @@ export interface Logger {
2
2
  info(message: string, meta?: unknown): void;
3
3
  error(message: string, meta?: unknown): void;
4
4
  debug(message: string, meta?: unknown): void;
5
- /** Optional: callers that want explicit WARN routing can implement this. */
6
5
  warn?(message: string, meta?: unknown): void;
7
6
  }
8
7
  export declare const noopLogger: Logger;
9
- /**
10
- * Emit a warning through whichever logger surface is available. Some Logger
11
- * implementations (legacy) only provide `info`/`error`/`debug`; in that case
12
- * the message is prefixed with `[WARN]` and routed through `info` so it still
13
- * reaches stderr.
14
- */
15
8
  export declare function logWarn(logger: Logger, message: string, meta?: unknown): void;
package/dist/logger.js CHANGED
@@ -4,12 +4,6 @@ export const noopLogger = {
4
4
  debug: () => { },
5
5
  warn: () => { },
6
6
  };
7
- /**
8
- * Emit a warning through whichever logger surface is available. Some Logger
9
- * implementations (legacy) only provide `info`/`error`/`debug`; in that case
10
- * the message is prefixed with `[WARN]` and routed through `info` so it still
11
- * reaches stderr.
12
- */
13
7
  export function logWarn(logger, message, meta) {
14
8
  if (typeof logger.warn === "function") {
15
9
  logger.warn(message, meta);
@@ -5,8 +5,5 @@ interface MigrationResult {
5
5
  failed: number;
6
6
  errors: string[];
7
7
  }
8
- /**
9
- * Migrate sessions from file-based storage to PostgreSQL
10
- */
11
8
  export declare function migrateFromFile(filePath: string, pgManager: PostgreSQLSessionManager): Promise<MigrationResult>;
12
9
  export {};
@@ -5,22 +5,17 @@ import { join } from "path";
5
5
  import { PostgreSQLSessionManager } from "./session-manager-pg.js";
6
6
  import { loadConfig } from "./config.js";
7
7
  import { createDatabaseConnection } from "./db.js";
8
- // Simple console logger for migration script
9
8
  const logger = {
10
9
  info: (message, meta) => console.error(`[INFO] ${message}`, meta || ""),
11
10
  error: (message, meta) => console.error(`[ERROR] ${message}`, meta || ""),
12
11
  debug: (message, meta) => console.error(`[DEBUG] ${message}`, meta || ""),
13
12
  };
14
- /**
15
- * Migrate sessions from file-based storage to PostgreSQL
16
- */
17
13
  export async function migrateFromFile(filePath, pgManager) {
18
14
  const result = {
19
15
  migrated: 0,
20
16
  failed: 0,
21
17
  errors: [],
22
18
  };
23
- // Read file-based sessions
24
19
  let fileData;
25
20
  try {
26
21
  const fileContent = readFileSync(filePath, "utf-8");
@@ -30,11 +25,9 @@ export async function migrateFromFile(filePath, pgManager) {
30
25
  throw new Error(`Failed to read sessions file: ${error instanceof Error ? error.message : String(error)}`);
31
26
  }
32
27
  console.error(`Found ${Object.keys(fileData.sessions).length} sessions to migrate`);
33
- // Migrate sessions
34
28
  for (const [id, session] of Object.entries(fileData.sessions)) {
35
29
  try {
36
30
  await pgManager.createSession(session.cli, session.description, session.id);
37
- // Migrate metadata if present
38
31
  if (session.metadata) {
39
32
  await pgManager.updateSessionMetadata(id, session.metadata);
40
33
  }
@@ -48,7 +41,6 @@ export async function migrateFromFile(filePath, pgManager) {
48
41
  console.error(`✗ ${errorMsg}`);
49
42
  }
50
43
  }
51
- // Restore active sessions
52
44
  console.error("\nRestoring active sessions...");
53
45
  for (const [cli, sessionId] of Object.entries(fileData.activeSession)) {
54
46
  if (sessionId) {
@@ -65,9 +57,6 @@ export async function migrateFromFile(filePath, pgManager) {
65
57
  }
66
58
  return result;
67
59
  }
68
- /**
69
- * Main CLI entry point
70
- */
71
60
  async function main() {
72
61
  const args = process.argv.slice(2);
73
62
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
@@ -85,7 +74,6 @@ Environment Variables:
85
74
  `);
86
75
  process.exit(args[0] === "--help" || args[0] === "-h" ? 0 : 1);
87
76
  }
88
- // Parse arguments
89
77
  let filePath = join(homedir(), ".llm-cli-gateway", "sessions.json");
90
78
  for (let i = 0; i < args.length; i++) {
91
79
  if (args[i] === "--from" && args[i + 1]) {
@@ -97,19 +85,16 @@ Environment Variables:
97
85
  console.error(` Source: ${filePath}`);
98
86
  console.error(` DATABASE_URL: ${process.env.DATABASE_URL ? "[set]" : "[not set]"}`);
99
87
  console.error("");
100
- // Load config
101
88
  const config = loadConfig();
102
89
  if (!config.database) {
103
90
  console.error("ERROR: DATABASE_URL must be set");
104
91
  process.exit(1);
105
92
  }
106
- // Connect to database
107
93
  console.error("Connecting to database...");
108
94
  const db = await createDatabaseConnection(config, logger);
109
95
  const pgManager = new PostgreSQLSessionManager(db.getPool());
110
96
  console.error("✓ Connected to database\n");
111
97
  try {
112
- // Run migration
113
98
  console.error("Starting migration...\n");
114
99
  const result = await migrateFromFile(filePath, pgManager);
115
100
  console.error("\n" + "=".repeat(50));
@@ -137,7 +122,6 @@ Environment Variables:
137
122
  await db.disconnect();
138
123
  }
139
124
  }
140
- // Run if executed directly
141
125
  if (import.meta.url === `file://${process.argv[1]}`) {
142
126
  main();
143
127
  }
package/dist/migrate.js CHANGED
@@ -4,14 +4,11 @@ import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = dirname(__filename);
7
- /**
8
- * Load all migration files from migrations directory
9
- */
10
7
  function loadMigrations() {
11
8
  const migrationsDir = join(__dirname, "..", "migrations");
12
9
  const files = readdirSync(migrationsDir)
13
10
  .filter(f => f.endsWith(".sql"))
14
- .sort(); // Ensures migrations run in order
11
+ .sort();
15
12
  return files.map(file => {
16
13
  const match = file.match(/^(\d+)_(.+)\.sql$/);
17
14
  if (!match) {
@@ -23,11 +20,7 @@ function loadMigrations() {
23
20
  return { version, name, sql };
24
21
  });
25
22
  }
26
- /**
27
- * Get list of applied migrations
28
- */
29
23
  async function getAppliedMigrations(pool) {
30
- // First, ensure schema_migrations table exists
31
24
  await pool.query(`
32
25
  CREATE TABLE IF NOT EXISTS schema_migrations (
33
26
  version INTEGER PRIMARY KEY,
@@ -38,9 +31,6 @@ async function getAppliedMigrations(pool) {
38
31
  const result = await pool.query("SELECT version FROM schema_migrations ORDER BY version");
39
32
  return result.rows.map(row => row.version);
40
33
  }
41
- /**
42
- * Run a single migration
43
- */
44
34
  async function runMigration(pool, migration) {
45
35
  console.error(`Running migration ${migration.version}: ${migration.name}...`);
46
36
  const client = await pool.connect();
@@ -58,9 +48,6 @@ async function runMigration(pool, migration) {
58
48
  client.release();
59
49
  }
60
50
  }
61
- /**
62
- * Main migration runner
63
- */
64
51
  async function main() {
65
52
  const databaseUrl = process.env.DATABASE_URL;
66
53
  if (!databaseUrl) {
@@ -70,20 +57,16 @@ async function main() {
70
57
  const { Pool } = await importOptionalPg();
71
58
  const pool = new Pool({ connectionString: databaseUrl });
72
59
  try {
73
- // Load migrations
74
60
  const migrations = loadMigrations();
75
61
  console.error(`Found ${migrations.length} migration(s)`);
76
- // Get applied migrations
77
62
  const applied = await getAppliedMigrations(pool);
78
63
  console.error(`${applied.length} migration(s) already applied`);
79
- // Filter pending migrations
80
64
  const pending = migrations.filter(m => !applied.includes(m.version));
81
65
  if (pending.length === 0) {
82
66
  console.error("✓ All migrations up to date");
83
67
  return;
84
68
  }
85
69
  console.error(`Running ${pending.length} pending migration(s)...`);
86
- // Run pending migrations
87
70
  for (const migration of pending) {
88
71
  await runMigration(pool, migration);
89
72
  }
@@ -1,37 +1,3 @@
1
- /**
2
- * Phase 4 slice β — Mistral Vibe `meta.json` parser.
3
- *
4
- * Vibe writes per-session telemetry to
5
- *
6
- * ~/.vibe/logs/session/session_<YYYYMMDD>_<HHMMSS>_<first8hex>/meta.json
7
- *
8
- * where `<first8hex>` is the first 8 lowercase hex characters of the full
9
- * session UUID. Inside the file:
10
- *
11
- * {
12
- * "session_id": "<full-uuid>",
13
- * "stats": {
14
- * "session_prompt_tokens": <number> → inputTokens
15
- * "session_completion_tokens": <number> → outputTokens
16
- * "session_cost": <number> → costUsd
17
- * }
18
- * }
19
- *
20
- * The gateway's mistral session-id surface accepts the full UUID (so does
21
- * `vibe --resume <uuid>`). To find the right directory we glob for
22
- * `session_*_<first8>` and disambiguate by reading each candidate's
23
- * `session_id` field. If callers happen to pass the directory basename
24
- * itself we still honour that — useful for tests and for forward-compat if
25
- * Vibe ever changes its dir naming scheme.
26
- *
27
- * Cache-token surfaces are not exposed by Vibe today, so `cacheReadTokens`
28
- * and `cacheCreationTokens` are intentionally absent.
29
- *
30
- * Best-effort by design: any failure (missing file, bad JSON, missing
31
- * fields, gateway-generated `gw-*` sessionId, unresolvable UUID, path
32
- * outside the session log root) returns `{}` so the flight-recorder row
33
- * simply lacks usage data.
34
- */
35
1
  import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "fs";
36
2
  import { join, resolve, sep } from "path";
37
3
  import { GATEWAY_SESSION_PREFIX } from "./request-helpers.js";
@@ -41,12 +7,6 @@ function asPositiveNumber(value) {
41
7
  }
42
8
  return value;
43
9
  }
44
- /**
45
- * Read a file only if its realpath lives under `realBase`. Returns undefined
46
- * on any error, missing file, or out-of-tree symlink target. This is the one
47
- * place that calls `readFileSync` for meta.json content — the rest of the
48
- * module routes through it so the security boundary is uniform.
49
- */
50
10
  function readInBase(realBase, candidate) {
51
11
  if (!existsSync(candidate))
52
12
  return undefined;
@@ -67,30 +27,12 @@ function readInBase(realBase, candidate) {
67
27
  return undefined;
68
28
  }
69
29
  }
70
- // UUID v4-ish (Vibe's own session UUIDs are not strictly v4, so we
71
- // validate against the broader 8-4-4-4-12 lowercase-hex shape) OR
72
- // Vibe's session_<digits>_<digits>_<first8> directory basename.
73
30
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
74
31
  const DIRNAME_RE = /^session_\d{8}_\d{6}_[0-9a-f]{8}$/;
75
- /**
76
- * Resolve the session-log directory basename for a given gateway sessionId.
77
- * Returns undefined when no candidate can be found or the input is
78
- * unsuitable. Pure with respect to side-effects on the caller — only reads
79
- * the filesystem.
80
- *
81
- * Security invariants enforced here:
82
- * - Inputs are charset-gated (UUID or DIRNAME) before any filesystem read.
83
- * - For UUID input, the chosen candidate's meta.json MUST advertise the
84
- * same `session_id` — single-candidate is NOT trusted, because two
85
- * UUIDs sharing the first 8 hex chars would otherwise cross-attribute
86
- * usage (and leak telemetry to the caller of the other session).
87
- */
88
32
  function resolveVibeSessionDirname(baseDir, realBase, sessionId) {
89
- // 1. Caller already supplied the directory name verbatim.
90
33
  if (DIRNAME_RE.test(sessionId) && existsSync(join(baseDir, sessionId, "meta.json"))) {
91
34
  return sessionId;
92
35
  }
93
- // 2. Treat the input as a full session UUID.
94
36
  if (!UUID_RE.test(sessionId))
95
37
  return undefined;
96
38
  const short = sessionId.slice(0, 8).toLowerCase();
@@ -101,8 +43,6 @@ function resolveVibeSessionDirname(baseDir, realBase, sessionId) {
101
43
  catch {
102
44
  return undefined;
103
45
  }
104
- // Filter to candidates matching `session_*_<short>`. Sort newest-first
105
- // by mtime; we still require an exact session_id match below.
106
46
  const candidates = entries
107
47
  .filter(name => DIRNAME_RE.test(name) && name.endsWith(`_${short}`))
108
48
  .map(name => {
@@ -111,7 +51,6 @@ function resolveVibeSessionDirname(baseDir, realBase, sessionId) {
111
51
  mtimeMs = statSync(join(baseDir, name)).mtimeMs;
112
52
  }
113
53
  catch {
114
- /* ignore */
115
54
  }
116
55
  return { name, mtimeMs };
117
56
  })
@@ -127,7 +66,6 @@ function resolveVibeSessionDirname(baseDir, realBase, sessionId) {
127
66
  }
128
67
  }
129
68
  catch {
130
- /* ignore and continue */
131
69
  }
132
70
  }
133
71
  return undefined;
@@ -136,7 +74,6 @@ export function parseVibeMetaJson(home, sessionId) {
136
74
  if (!sessionId)
137
75
  return {};
138
76
  if (sessionId.startsWith(GATEWAY_SESSION_PREFIX)) {
139
- // gw-* IDs are gateway internal — Vibe never wrote a meta.json under that name.
140
77
  return {};
141
78
  }
142
79
  const baseDir = resolve(join(home, ".vibe", "logs", "session"));
@@ -150,10 +87,6 @@ export function parseVibeMetaJson(home, sessionId) {
150
87
  const dirname = resolveVibeSessionDirname(baseDir, realBase, sessionId);
151
88
  if (!dirname)
152
89
  return {};
153
- // `readInBase` is the security boundary: it realpath-resolves the file
154
- // and rejects anything whose target lives outside `realBase`. Re-routing
155
- // the final read through it (instead of a bespoke readFileSync) keeps
156
- // the in-tree-only invariant in one place.
157
90
  const text = readInBase(realBase, join(baseDir, dirname, "meta.json"));
158
91
  if (text === undefined)
159
92
  return {};