kiro-memory 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,19 +24,28 @@ __export(Observations_exports, {
24
24
  deleteObservation: () => deleteObservation,
25
25
  getObservationsByProject: () => getObservationsByProject,
26
26
  getObservationsBySession: () => getObservationsBySession,
27
+ isDuplicateObservation: () => isDuplicateObservation,
27
28
  searchObservations: () => searchObservations,
28
29
  updateLastAccessed: () => updateLastAccessed
29
30
  });
30
31
  function escapeLikePattern(input) {
31
32
  return input.replace(/[%_\\]/g, "\\$&");
32
33
  }
33
- function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber) {
34
+ function isDuplicateObservation(db, contentHash, windowMs = 3e4) {
35
+ if (!contentHash) return false;
36
+ const threshold = Date.now() - windowMs;
37
+ const result = db.query(
38
+ "SELECT id FROM observations WHERE content_hash = ? AND created_at_epoch > ? LIMIT 1"
39
+ ).get(contentHash, threshold);
40
+ return !!result;
41
+ }
42
+ function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, contentHash = null, discoveryTokens = 0) {
34
43
  const now = /* @__PURE__ */ new Date();
35
44
  const result = db.run(
36
- `INSERT INTO observations
37
- (memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch)
38
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
39
- [memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime()]
45
+ `INSERT INTO observations
46
+ (memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch, content_hash, discovery_tokens)
47
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
48
+ [memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime(), contentHash, discoveryTokens]
40
49
  );
41
50
  return Number(result.lastInsertRowid);
42
51
  }
@@ -92,39 +101,42 @@ function consolidateObservations(db, project, options = {}) {
92
101
  if (groups.length === 0) return { merged: 0, removed: 0 };
93
102
  let totalMerged = 0;
94
103
  let totalRemoved = 0;
95
- for (const group of groups) {
96
- const obsIds = group.ids.split(",").map(Number);
97
- const placeholders = obsIds.map(() => "?").join(",");
98
- const observations = db.query(
99
- `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
100
- ).all(...obsIds);
101
- if (observations.length < minGroupSize) continue;
102
- if (options.dryRun) {
103
- totalMerged += 1;
104
- totalRemoved += observations.length - 1;
105
- continue;
106
- }
107
- const keeper = observations[0];
108
- const others = observations.slice(1);
109
- const uniqueTexts = /* @__PURE__ */ new Set();
110
- if (keeper.text) uniqueTexts.add(keeper.text);
111
- for (const obs of others) {
112
- if (obs.text && !uniqueTexts.has(obs.text)) {
113
- uniqueTexts.add(obs.text);
104
+ const runConsolidation = db.transaction(() => {
105
+ for (const group of groups) {
106
+ const obsIds = group.ids.split(",").map(Number);
107
+ const placeholders = obsIds.map(() => "?").join(",");
108
+ const observations = db.query(
109
+ `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
110
+ ).all(...obsIds);
111
+ if (observations.length < minGroupSize) continue;
112
+ if (options.dryRun) {
113
+ totalMerged += 1;
114
+ totalRemoved += observations.length - 1;
115
+ continue;
114
116
  }
117
+ const keeper = observations[0];
118
+ const others = observations.slice(1);
119
+ const uniqueTexts = /* @__PURE__ */ new Set();
120
+ if (keeper.text) uniqueTexts.add(keeper.text);
121
+ for (const obs of others) {
122
+ if (obs.text && !uniqueTexts.has(obs.text)) {
123
+ uniqueTexts.add(obs.text);
124
+ }
125
+ }
126
+ const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
127
+ db.run(
128
+ "UPDATE observations SET text = ?, title = ? WHERE id = ?",
129
+ [consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
130
+ );
131
+ const removeIds = others.map((o) => o.id);
132
+ const removePlaceholders = removeIds.map(() => "?").join(",");
133
+ db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
134
+ db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
135
+ totalMerged += 1;
136
+ totalRemoved += removeIds.length;
115
137
  }
116
- const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
117
- db.run(
118
- "UPDATE observations SET text = ?, title = ? WHERE id = ?",
119
- [consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
120
- );
121
- const removeIds = others.map((o) => o.id);
122
- const removePlaceholders = removeIds.map(() => "?").join(",");
123
- db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
124
- db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
125
- totalMerged += 1;
126
- totalRemoved += removeIds.length;
127
- }
138
+ });
139
+ runConsolidation();
128
140
  return { merged: totalMerged, removed: totalRemoved };
129
141
  }
130
142
  var init_Observations = __esm({
@@ -182,7 +194,7 @@ function searchObservationsFTS(db, query, filters = {}) {
182
194
  sql += " AND o.created_at_epoch <= ?";
183
195
  params.push(filters.dateEnd);
184
196
  }
185
- sql += " ORDER BY rank LIMIT ?";
197
+ sql += ` ORDER BY bm25(observations_fts, ${BM25_WEIGHTS}) LIMIT ?`;
186
198
  params.push(limit);
187
199
  const stmt = db.query(sql);
188
200
  return stmt.all(...params);
@@ -196,7 +208,7 @@ function searchObservationsFTSWithRank(db, query, filters = {}) {
196
208
  const safeQuery = sanitizeFTS5Query(query);
197
209
  if (!safeQuery) return [];
198
210
  let sql = `
199
- SELECT o.*, rank as fts5_rank FROM observations o
211
+ SELECT o.*, bm25(observations_fts, ${BM25_WEIGHTS}) as fts5_rank FROM observations o
200
212
  JOIN observations_fts fts ON o.id = fts.rowid
201
213
  WHERE observations_fts MATCH ?
202
214
  `;
@@ -217,7 +229,7 @@ function searchObservationsFTSWithRank(db, query, filters = {}) {
217
229
  sql += " AND o.created_at_epoch <= ?";
218
230
  params.push(filters.dateEnd);
219
231
  }
220
- sql += " ORDER BY rank LIMIT ?";
232
+ sql += ` ORDER BY bm25(observations_fts, ${BM25_WEIGHTS}) LIMIT ?`;
221
233
  params.push(limit);
222
234
  const stmt = db.query(sql);
223
235
  return stmt.all(...params);
@@ -321,11 +333,23 @@ function getProjectStats(db, project) {
321
333
  const sumStmt = db.query("SELECT COUNT(*) as count FROM summaries WHERE project = ?");
322
334
  const sesStmt = db.query("SELECT COUNT(*) as count FROM sessions WHERE project = ?");
323
335
  const prmStmt = db.query("SELECT COUNT(*) as count FROM prompts WHERE project = ?");
336
+ const discoveryStmt = db.query(
337
+ "SELECT COALESCE(SUM(discovery_tokens), 0) as total FROM observations WHERE project = ?"
338
+ );
339
+ const discoveryTokens = discoveryStmt.get(project)?.total || 0;
340
+ const readStmt = db.query(
341
+ `SELECT COALESCE(SUM(
342
+ CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
343
+ ), 0) as total FROM observations WHERE project = ?`
344
+ );
345
+ const readTokens = readStmt.get(project)?.total || 0;
346
+ const savings = Math.max(0, discoveryTokens - readTokens);
324
347
  return {
325
348
  observations: obsStmt.get(project)?.count || 0,
326
349
  summaries: sumStmt.get(project)?.count || 0,
327
350
  sessions: sesStmt.get(project)?.count || 0,
328
- prompts: prmStmt.get(project)?.count || 0
351
+ prompts: prmStmt.get(project)?.count || 0,
352
+ tokenEconomics: { discoveryTokens, readTokens, savings }
329
353
  };
330
354
  }
331
355
  function getStaleObservations(db, project) {
@@ -367,9 +391,11 @@ function markObservationsStale(db, ids, stale) {
367
391
  [stale ? 1 : 0, ...validIds]
368
392
  );
369
393
  }
394
+ var BM25_WEIGHTS;
370
395
  var init_Search = __esm({
371
396
  "src/services/sqlite/Search.ts"() {
372
397
  "use strict";
398
+ BM25_WEIGHTS = "10.0, 1.0, 5.0, 3.0";
373
399
  }
374
400
  });
375
401
 
@@ -1111,6 +1137,29 @@ var MigrationRunner = class {
1111
1137
  db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_project ON checkpoints(project)");
1112
1138
  db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_epoch ON checkpoints(created_at_epoch)");
1113
1139
  }
1140
+ },
1141
+ {
1142
+ version: 7,
1143
+ up: (db) => {
1144
+ db.run("ALTER TABLE observations ADD COLUMN content_hash TEXT");
1145
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_hash ON observations(content_hash)");
1146
+ }
1147
+ },
1148
+ {
1149
+ version: 8,
1150
+ up: (db) => {
1151
+ db.run("ALTER TABLE observations ADD COLUMN discovery_tokens INTEGER DEFAULT 0");
1152
+ db.run("ALTER TABLE summaries ADD COLUMN discovery_tokens INTEGER DEFAULT 0");
1153
+ }
1154
+ },
1155
+ {
1156
+ version: 9,
1157
+ up: (db) => {
1158
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_project_epoch ON observations(project, created_at_epoch DESC)");
1159
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_project_type ON observations(project, type)");
1160
+ db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_epoch ON summaries(project, created_at_epoch DESC)");
1161
+ db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_epoch ON prompts(project, created_at_epoch DESC)");
1162
+ }
1114
1163
  }
1115
1164
  ];
1116
1165
  }
@@ -1355,6 +1404,7 @@ init_Search();
1355
1404
 
1356
1405
  // src/sdk/index.ts
1357
1406
  init_Observations();
1407
+ import { createHash } from "crypto";
1358
1408
  init_Search();
1359
1409
 
1360
1410
  // src/services/search/EmbeddingService.ts
@@ -1867,31 +1917,71 @@ var KiroMemorySDK = class {
1867
1917
  logger.debug("SDK", `Embedding generation fallita per obs ${observationId}: ${error}`);
1868
1918
  }
1869
1919
  }
1920
+ /**
1921
+ * Genera content hash SHA256 per deduplicazione basata su contenuto.
1922
+ * Usa (project + type + title + narrative) come tupla di identità semantica.
1923
+ * NON include sessionId perché è unico ad ogni invocazione.
1924
+ */
1925
+ generateContentHash(type, title, narrative) {
1926
+ const payload = `${this.project}|${type}|${title}|${narrative || ""}`;
1927
+ return createHash("sha256").update(payload).digest("hex");
1928
+ }
1929
+ /**
1930
+ * Finestre di deduplicazione per tipo (ms).
1931
+ * Tipi con molte ripetizioni hanno finestre più ampie.
1932
+ */
1933
+ getDeduplicationWindow(type) {
1934
+ switch (type) {
1935
+ case "file-read":
1936
+ return 6e4;
1937
+ // 60s — letture frequenti sugli stessi file
1938
+ case "file-write":
1939
+ return 1e4;
1940
+ // 10s — scritture rapide consecutive
1941
+ case "command":
1942
+ return 3e4;
1943
+ // 30s — standard
1944
+ case "research":
1945
+ return 12e4;
1946
+ // 120s — web search e fetch ripetuti
1947
+ case "delegation":
1948
+ return 6e4;
1949
+ // 60s — delegazioni rapide
1950
+ default:
1951
+ return 3e4;
1952
+ }
1953
+ }
1870
1954
  /**
1871
1955
  * Store a new observation
1872
1956
  */
1873
1957
  async storeObservation(data) {
1874
1958
  this.validateObservationInput(data);
1959
+ const sessionId = "sdk-" + Date.now();
1960
+ const contentHash = this.generateContentHash(data.type, data.title, data.narrative);
1961
+ const dedupWindow = this.getDeduplicationWindow(data.type);
1962
+ if (isDuplicateObservation(this.db.db, contentHash, dedupWindow)) {
1963
+ logger.debug("SDK", `Osservazione duplicata scartata (${data.type}, ${dedupWindow}ms): ${data.title}`);
1964
+ return -1;
1965
+ }
1966
+ const filesRead = data.filesRead || (data.type === "file-read" ? data.files : void 0);
1967
+ const filesModified = data.filesModified || (data.type === "file-write" ? data.files : void 0);
1968
+ const discoveryTokens = Math.ceil(data.content.length / 4);
1875
1969
  const observationId = createObservation(
1876
1970
  this.db.db,
1877
- "sdk-" + Date.now(),
1971
+ sessionId,
1878
1972
  this.project,
1879
1973
  data.type,
1880
1974
  data.title,
1881
- null,
1882
- // subtitle
1975
+ data.subtitle || null,
1883
1976
  data.content,
1884
- null,
1885
- // narrative
1886
- null,
1887
- // facts
1977
+ data.narrative || null,
1978
+ data.facts || null,
1888
1979
  data.concepts?.join(", ") || null,
1889
- data.files?.join(", ") || null,
1890
- // files_read
1891
- data.files?.join(", ") || null,
1892
- // files_modified
1893
- 0
1894
- // prompt_number
1980
+ filesRead?.join(", ") || null,
1981
+ filesModified?.join(", ") || null,
1982
+ 0,
1983
+ contentHash,
1984
+ discoveryTokens
1895
1985
  );
1896
1986
  this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
1897
1987
  });
@@ -1934,9 +2024,16 @@ var KiroMemorySDK = class {
1934
2024
  };
1935
2025
  }
1936
2026
  })();
2027
+ const sessionId = "sdk-" + Date.now();
2028
+ const contentHash = this.generateContentHash(data.type, data.title);
2029
+ if (isDuplicateObservation(this.db.db, contentHash)) {
2030
+ logger.debug("SDK", `Knowledge duplicata scartata: ${data.title}`);
2031
+ return -1;
2032
+ }
2033
+ const discoveryTokens = Math.ceil(data.content.length / 4);
1937
2034
  const observationId = createObservation(
1938
2035
  this.db.db,
1939
- "sdk-" + Date.now(),
2036
+ sessionId,
1940
2037
  data.project || this.project,
1941
2038
  data.knowledgeType,
1942
2039
  // type = knowledgeType
@@ -1950,9 +2047,12 @@ var KiroMemorySDK = class {
1950
2047
  // facts = metadati JSON
1951
2048
  data.concepts?.join(", ") || null,
1952
2049
  data.files?.join(", ") || null,
1953
- data.files?.join(", ") || null,
1954
- 0
2050
+ null,
2051
+ // filesModified: knowledge non modifica file
2052
+ 0,
1955
2053
  // prompt_number
2054
+ contentHash,
2055
+ discoveryTokens
1956
2056
  );
1957
2057
  this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
1958
2058
  });
@@ -1968,11 +2068,11 @@ var KiroMemorySDK = class {
1968
2068
  "sdk-" + Date.now(),
1969
2069
  this.project,
1970
2070
  data.request || null,
1971
- null,
2071
+ data.investigated || null,
1972
2072
  data.learned || null,
1973
2073
  data.completed || null,
1974
2074
  data.nextSteps || null,
1975
- null
2075
+ data.notes || null
1976
2076
  );
1977
2077
  }
1978
2078
  /**
@@ -2156,7 +2256,14 @@ var KiroMemorySDK = class {
2156
2256
  }));
2157
2257
  } else {
2158
2258
  const observations = getObservationsByProject(this.db.db, this.project, 30);
2159
- items = observations.map((obs) => {
2259
+ const knowledgeTypes = new Set(KNOWLEDGE_TYPES);
2260
+ const knowledgeObs = [];
2261
+ const normalObs = [];
2262
+ for (const obs of observations) {
2263
+ if (knowledgeTypes.has(obs.type)) knowledgeObs.push(obs);
2264
+ else normalObs.push(obs);
2265
+ }
2266
+ const scoreObs = (obs) => {
2160
2267
  const signals = {
2161
2268
  semantic: 0,
2162
2269
  fts5: 0,
@@ -2164,7 +2271,6 @@ var KiroMemorySDK = class {
2164
2271
  projectMatch: projectMatchScore(obs.project, this.project)
2165
2272
  };
2166
2273
  const baseScore = computeCompositeScore(signals, CONTEXT_WEIGHTS);
2167
- const boostedScore = Math.min(1, baseScore * knowledgeTypeBoost(obs.type));
2168
2274
  return {
2169
2275
  id: obs.id,
2170
2276
  title: obs.title,
@@ -2173,17 +2279,23 @@ var KiroMemorySDK = class {
2173
2279
  project: obs.project,
2174
2280
  created_at: obs.created_at,
2175
2281
  created_at_epoch: obs.created_at_epoch,
2176
- score: boostedScore,
2282
+ score: Math.min(1, baseScore * knowledgeTypeBoost(obs.type)),
2177
2283
  signals
2178
2284
  };
2179
- });
2180
- items.sort((a, b) => b.score - a.score);
2285
+ };
2286
+ const scoredKnowledge = knowledgeObs.map(scoreObs).sort((a, b) => b.score - a.score);
2287
+ const scoredNormal = normalObs.map(scoreObs).sort((a, b) => b.score - a.score);
2288
+ items = [...scoredKnowledge, ...scoredNormal];
2181
2289
  }
2182
2290
  let tokensUsed = 0;
2291
+ const budgetItems = [];
2183
2292
  for (const item of items) {
2184
- tokensUsed += Math.ceil((item.title.length + item.content.length) / 4);
2185
- if (tokensUsed > tokenBudget) break;
2293
+ const itemTokens = Math.ceil((item.title.length + item.content.length) / 4);
2294
+ if (tokensUsed + itemTokens > tokenBudget) break;
2295
+ tokensUsed += itemTokens;
2296
+ budgetItems.push(item);
2186
2297
  }
2298
+ items = budgetItems;
2187
2299
  return {
2188
2300
  project: this.project,
2189
2301
  items,
@@ -2306,28 +2418,55 @@ runHook("stop", async (input) => {
2306
2418
  const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1e3;
2307
2419
  const sessionObs = sessionId ? recentObs.filter((o) => o.memory_session_id === sessionId) : recentObs.filter((o) => o.created_at_epoch > fourHoursAgo);
2308
2420
  if (sessionObs.length === 0) return;
2309
- const completed = sessionObs.map((o) => o.title).slice(0, 10).join("; ");
2421
+ const byType = /* @__PURE__ */ new Map();
2422
+ for (const obs of sessionObs) {
2423
+ const group = byType.get(obs.type) || [];
2424
+ group.push(obs);
2425
+ byType.set(obs.type, group);
2426
+ }
2427
+ const readFiles = byType.get("file-read") || [];
2428
+ const researched = byType.get("research") || [];
2429
+ const investigated = [
2430
+ ...readFiles.slice(0, 5).map((o) => o.narrative || o.title),
2431
+ ...researched.slice(0, 3).map((o) => o.narrative || o.title)
2432
+ ].filter(Boolean).join("; ") || void 0;
2433
+ const writes = byType.get("file-write") || [];
2434
+ const commands = byType.get("command") || [];
2435
+ const completed = [
2436
+ ...writes.slice(0, 8).map((o) => o.narrative || o.title),
2437
+ ...commands.slice(0, 3).map((o) => o.narrative || o.title)
2438
+ ].filter(Boolean).join("; ") || void 0;
2439
+ const learned = researched.map((o) => o.text?.substring(0, 150)).filter(Boolean).slice(0, 5).join("; ") || void 0;
2310
2440
  const filesModified = [...new Set(
2311
2441
  sessionObs.filter((o) => o.files_modified).map((o) => o.files_modified).flatMap((f) => f.split(",").map((s) => s.trim()))
2312
2442
  )];
2313
- const learned = sessionObs.filter((o) => o.type === "research" || o.type === "code-intelligence").map((o) => o.text?.substring(0, 100)).filter(Boolean).slice(0, 5).join("; ");
2443
+ const sessionConcepts = [...new Set(
2444
+ sessionObs.filter((o) => o.concepts).flatMap((o) => o.concepts.split(",").map((c) => c.trim()))
2445
+ )].slice(0, 10);
2446
+ const nextSteps = [
2447
+ filesModified.length > 0 ? `Files modified: ${filesModified.slice(0, 10).join(", ")}` : "",
2448
+ sessionConcepts.length > 0 ? `Concepts: ${sessionConcepts.join(", ")}` : ""
2449
+ ].filter(Boolean).join(". ") || void 0;
2450
+ const mainAction = writes.length > 0 ? `${writes.length} file modific${writes.length === 1 ? "ato" : "ati"}` : commands.length > 0 ? `${commands.length} comand${commands.length === 1 ? "o" : "i"}` : `${sessionObs.length} osservazion${sessionObs.length === 1 ? "e" : "i"}`;
2314
2451
  await sdk.storeSummary({
2315
- request: `Sessione ${project} - ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
2316
- completed: completed || void 0,
2317
- learned: learned || void 0,
2318
- nextSteps: filesModified.length > 0 ? `File modificati: ${filesModified.join(", ")}` : void 0
2452
+ request: `${project} \u2014 ${mainAction} \u2014 ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
2453
+ investigated,
2454
+ completed,
2455
+ learned,
2456
+ nextSteps
2319
2457
  });
2320
2458
  await notifyWorker("summary-created", { project });
2321
2459
  const session = await sdk.getOrCreateSession(input.session_id || `stop-${Date.now()}`);
2322
- const task = sessionObs[0]?.title || `Sessione ${project}`;
2323
- const progress = completed || "Nessun progresso registrato";
2324
- const nextStepsCheckpoint = filesModified.length > 0 ? `Continuare lavoro su: ${filesModified.slice(0, 5).join(", ")}` : void 0;
2460
+ const task = sessionObs[0]?.title || `${project} session`;
2461
+ const progress = completed || "No progress recorded";
2462
+ const nextStepsCheckpoint = filesModified.length > 0 ? `Continue work on: ${filesModified.slice(0, 5).join(", ")}` : void 0;
2325
2463
  await sdk.createCheckpoint(session.id, {
2326
2464
  task,
2327
2465
  progress,
2328
2466
  nextSteps: nextStepsCheckpoint,
2329
2467
  relevantFiles: filesModified.slice(0, 20)
2330
2468
  });
2469
+ await sdk.completeSession(session.id);
2331
2470
  await notifyWorker("checkpoint-created", { project });
2332
2471
  } finally {
2333
2472
  sdk.close();