@letta-ai/dreams 0.0.3 → 0.0.4

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.
package/dist/cli.js CHANGED
@@ -2,6 +2,8 @@
2
2
  /** @letta-ai/dreams command-line entry point. */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.main = main;
5
+ // Must be first: filters node:sqlite ExperimentalWarning before other imports load it.
6
+ require("./suppress-sqlite-warning");
5
7
  const index_1 = require("./auth/index");
6
8
  const commands_1 = require("./auth/commands");
7
9
  const development_reset_1 = require("./development-reset");
@@ -15,6 +17,7 @@ const snapshots_1 = require("./snapshots");
15
17
  const skill_1 = require("./skill");
16
18
  const store_1 = require("./store");
17
19
  const backfill_1 = require("./local/backfill");
20
+ const hooks_install_1 = require("./local/hooks-install");
18
21
  const sync_1 = require("./sync");
19
22
  const version_1 = require("./version");
20
23
  const USAGE = `Usage: dreams <command> [options]
@@ -28,12 +31,15 @@ Commands:
28
31
  auth logout Revoke Dreams' refresh token and clear stored credentials
29
32
  dev reset local Preview/reset local SQLite and blob state
30
33
  dev reset identity Preview/reset local state, credentials, and device identity
31
- source detect Detect Claude Code and register a detected Cloud source
32
- source scan Discover Claude Code sessions and build the local upload queue
33
- source approve-root <path> Approve a work root so its transcripts become eligible
34
- source list Show local sources, approved roots, and the pending queue
34
+ source detect Detect a harness and register a detected Cloud source
35
+ source scan Discover sessions and build the local upload queue
36
+ source approve-root <path> Approve a work directory so its transcripts become eligible
37
+ source approve-dir <path> Alias for approve-root (preferred user-facing name)
38
+ source list Show local sources, approved directories, and the pending queue
35
39
  snapshots create Submit a skill-authored snapshot JSON file to Cloud
36
- sync Schedule a hook-safe sync worker (live then active backfill)
40
+ hooks install Install or preview harness live-sync hooks (Claude/Codex)
41
+ wake SessionStart housekeeping + kick pending sync/backfill
42
+ sync Schedule a live-turn sync worker (Stop hook; then active backfill)
37
43
  sync pause|resume|status Pause/resume hooks or show sync diagnostics
38
44
  backfill start Queue a bounded historical backfill window (local only)
39
45
  upload Upload pending queued objects to Letta Cloud
@@ -41,8 +47,9 @@ Commands:
41
47
  Options:
42
48
  --json Machine-readable JSON output
43
49
  --confirm Apply a development reset preview
50
+ --dry-run Preview hooks install without writing files
44
51
  --force Bypass sync cadence (never bypasses pause/privacy)
45
- --source <type> Sync source adapter (claude-code default; codex)
52
+ --source <type> Source adapter (claude-code default; codex) for sync/wake, source detect/scan, upload, backfill, hooks install
46
53
  --since <RFC3339> Backfill window start (with backfill start)
47
54
  --type <type> Snapshot type (with snapshots create)
48
55
  --file <path> Snapshot JSON file (with snapshots create)
@@ -51,12 +58,13 @@ Options:
51
58
  --version Print the CLI version
52
59
  --help Show this help
53
60
  `;
54
- const COMMANDS_WITH_SUBCOMMANDS = new Set(["auth", "dev", "source", "snapshots", "sync", "backfill"]);
61
+ const COMMANDS_WITH_SUBCOMMANDS = new Set(["auth", "dev", "source", "snapshots", "sync", "backfill", "hooks"]);
55
62
  function parseArgs(argv) {
56
63
  const parsed = {
57
64
  positionals: [],
58
65
  json: false,
59
66
  confirm: false,
67
+ dryRun: false,
60
68
  help: false,
61
69
  version: false,
62
70
  force: false,
@@ -75,6 +83,8 @@ function parseArgs(argv) {
75
83
  parsed.json = true;
76
84
  else if (arg === "--confirm")
77
85
  parsed.confirm = true;
86
+ else if (arg === "--dry-run")
87
+ parsed.dryRun = true;
78
88
  else if (arg === "--force")
79
89
  parsed.force = true;
80
90
  else if (arg === "--worker")
@@ -257,8 +267,18 @@ async function runCli() {
257
267
  process.stderr.write(`--force/--worker are only valid with \`dreams sync\`\n\n${USAGE}`);
258
268
  return 1;
259
269
  }
260
- if (args.source && command !== "sync") {
261
- process.stderr.write(`--source is only valid with \`dreams sync\`\n\n${USAGE}`);
270
+ if (args.dryRun && command !== "hooks") {
271
+ process.stderr.write(`--dry-run is only valid with \`dreams hooks install\`\n\n${USAGE}`);
272
+ return 1;
273
+ }
274
+ if (args.source &&
275
+ command !== "sync" &&
276
+ command !== "wake" &&
277
+ command !== "upload" &&
278
+ command !== "hooks" &&
279
+ !(command === "source" && (subcommand === "detect" || subcommand === "scan")) &&
280
+ !(command === "backfill" && subcommand === "start")) {
281
+ process.stderr.write(`--source is only valid with \`dreams sync\`, \`dreams wake\`, \`dreams hooks install\`, \`dreams source detect\`, \`dreams source scan\`, \`dreams upload\`, or \`dreams backfill start\`\n\n${USAGE}`);
262
282
  return 1;
263
283
  }
264
284
  if (args.since && command !== "backfill") {
@@ -286,6 +306,14 @@ async function runCli() {
286
306
  process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(2).join(", ")}\n\n${USAGE}`);
287
307
  return 1;
288
308
  }
309
+ if (command === "hooks" && args.positionals.length > 2) {
310
+ process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(2).join(", ")}\n\n${USAGE}`);
311
+ return 1;
312
+ }
313
+ if (command === "wake" && args.positionals.length > 1) {
314
+ process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(1).join(", ")}\n\n${USAGE}`);
315
+ return 1;
316
+ }
289
317
  switch (command) {
290
318
  case "onboard":
291
319
  return (0, onboard_1.commandOnboard)(args.json);
@@ -298,7 +326,7 @@ async function runCli() {
298
326
  case "dev":
299
327
  return commandDevelopment(args.positionals, args.confirm, args.json);
300
328
  case "source":
301
- return (0, commands_2.commandSource)(subcommand, args.positionals[2], args.json);
329
+ return (0, commands_2.commandSource)(subcommand, args.positionals[2], args.json, args.source);
302
330
  case "snapshots":
303
331
  return (0, snapshots_1.commandSnapshots)(subcommand, {
304
332
  type: args.type,
@@ -307,6 +335,14 @@ async function runCli() {
307
335
  workspaceId: args.workspaceId,
308
336
  json: args.json,
309
337
  });
338
+ case "hooks":
339
+ if (subcommand !== "install") {
340
+ process.stderr.write(`Unknown hooks subcommand: ${subcommand ?? "(none)"} — expected install\n\n${USAGE}`);
341
+ return 1;
342
+ }
343
+ return (0, hooks_install_1.commandHooksInstall)({ json: args.json, dryRun: args.dryRun, sourceType: args.source });
344
+ case "wake":
345
+ return (0, sync_1.commandWake)({ json: args.json, sourceType: args.source });
310
346
  case "sync":
311
347
  return (0, sync_1.commandSync)(subcommand, {
312
348
  json: args.json,
@@ -319,9 +355,9 @@ async function runCli() {
319
355
  process.stderr.write(`Unknown backfill subcommand: ${subcommand ?? "(none)"} — expected start\n\n${USAGE}`);
320
356
  return 1;
321
357
  }
322
- return (0, backfill_1.commandBackfillStart)({ since: args.since, json: args.json });
358
+ return (0, backfill_1.commandBackfillStart)({ since: args.since, json: args.json, sourceType: args.source });
323
359
  case "upload":
324
- return (0, commands_2.commandUpload)(args.json);
360
+ return (0, commands_2.commandUpload)(args.json, args.source);
325
361
  default:
326
362
  process.stderr.write(`Unknown command: ${command}\n\n${USAGE}`);
327
363
  return 1;
@@ -517,6 +517,21 @@ function commandBackfillStart(options) {
517
517
  process.stderr.write("dreams backfill start requires --since <RFC3339>\n");
518
518
  return 1;
519
519
  }
520
+ // Resolve before any installation/DB/policy mutation (unknown/empty fail closed).
521
+ let adapter;
522
+ try {
523
+ adapter = (0, source_adapters_1.resolveSourceAdapter)(options.sourceType);
524
+ }
525
+ catch (error) {
526
+ const message = error instanceof Error ? error.message : String(error);
527
+ if (options.json) {
528
+ console.log(JSON.stringify({ status: "error", schema_version: 1, code: "invalid_argument", message }));
529
+ }
530
+ else {
531
+ process.stderr.write(`${message}\n`);
532
+ }
533
+ return 1;
534
+ }
520
535
  const lock = (0, state_lock_1.acquireLocalStateLock)();
521
536
  if (lock === null) {
522
537
  if (options.json) {
@@ -548,7 +563,7 @@ function commandBackfillStart(options) {
548
563
  installationId: installation.installation_id,
549
564
  leaseOwner: owner,
550
565
  since: options.since,
551
- adapter: (0, source_adapters_1.defaultSourceAdapter)(),
566
+ adapter,
552
567
  });
553
568
  }
554
569
  finally {
@@ -557,6 +572,8 @@ function commandBackfillStart(options) {
557
572
  const payload = {
558
573
  schema_version: 1,
559
574
  status: result.status,
575
+ source: adapter.sourceType,
576
+ source_id: adapter.sourceId,
560
577
  policy: result.policy,
561
578
  sessions_in_window: result.sessions_in_window,
562
579
  sessions_selected: result.sessions_selected,
@@ -568,7 +585,7 @@ function commandBackfillStart(options) {
568
585
  console.log(JSON.stringify(payload, null, 2));
569
586
  else {
570
587
  console.log([
571
- `Backfill ${result.status} for ${result.policy.since_at} → ${result.policy.until_at}`,
588
+ `Backfill ${result.status} for ${adapter.sourceType} (${result.policy.since_at} → ${result.policy.until_at})`,
572
589
  "",
573
590
  ` Sessions in window: ${result.sessions_in_window}`,
574
591
  ` Sessions scanned: ${result.sessions_selected}`,
@@ -576,7 +593,7 @@ function commandBackfillStart(options) {
576
593
  ` Frontier complete: ${result.frontier_complete}`,
577
594
  ` Segments enqueued: ${result.scan.segments_enqueued}`,
578
595
  "",
579
- "Local queue only — wake sync to upload: dreams sync --force",
596
+ `Local queue only — wake sync to upload: dreams sync --source ${adapter.sourceType} --force`,
580
597
  ].join("\n"));
581
598
  }
582
599
  return 0;
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ /**
3
+ * Claude Code live-sync hook helpers — merge Dreams SessionStart/Stop into
4
+ * ~/.claude/settings.json without clobbering unrelated handlers.
5
+ *
6
+ * SessionStart → `dreams wake` (housekeeping + pending drain)
7
+ * Stop → `dreams sync` (live turn, stdin session hint)
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.CLAUDE_HOOK_MARKER = void 0;
44
+ exports.claudeSettingsPath = claudeSettingsPath;
45
+ exports.buildClaudeHookCommandPosix = buildClaudeHookCommandPosix;
46
+ exports.buildClaudeHookCommandPowerShell = buildClaudeHookCommandPowerShell;
47
+ exports.buildClaudeHookCommand = buildClaudeHookCommand;
48
+ exports.mergeClaudeSettingsJson = mergeClaudeSettingsJson;
49
+ exports.planClaudeHooksInstall = planClaudeHooksInstall;
50
+ exports.installClaudeHooks = installClaudeHooks;
51
+ const fs = __importStar(require("node:fs"));
52
+ const os = __importStar(require("node:os"));
53
+ const path = __importStar(require("node:path"));
54
+ exports.CLAUDE_HOOK_MARKER = "dreams-sync-hook";
55
+ function claudeSettingsPath(home = os.homedir()) {
56
+ return path.join(home, ".claude", "settings.json");
57
+ }
58
+ /** POSIX bash command for Claude Code command hooks. */
59
+ function buildClaudeHookCommandPosix(cliPath, mode) {
60
+ return `"${cliPath}" ${mode} >/dev/null 2>&1 || true # ${exports.CLAUDE_HOOK_MARKER}`;
61
+ }
62
+ /** PowerShell command for Claude Code command hooks on Windows. */
63
+ function buildClaudeHookCommandPowerShell(cliPath, mode) {
64
+ const quoted = cliPath.replace(/'/g, "''");
65
+ return `& '${quoted}' ${mode} *> $null; exit 0 # ${exports.CLAUDE_HOOK_MARKER}`;
66
+ }
67
+ function buildClaudeHookCommand(cliPath, mode, platform = process.platform) {
68
+ return platform === "win32"
69
+ ? buildClaudeHookCommandPowerShell(cliPath, mode)
70
+ : buildClaudeHookCommandPosix(cliPath, mode);
71
+ }
72
+ function isDreamsClaudeHandler(command) {
73
+ if (typeof command !== "string")
74
+ return false;
75
+ // Prefer the marker; also recognize older Dreams sync-only handlers by cli+sync.
76
+ return (command.includes(exports.CLAUDE_HOOK_MARKER) ||
77
+ (/\bsync\b/.test(command) && (command.includes("dreams") || command.includes(">/dev/null") || command.includes("*> $null"))));
78
+ }
79
+ function upsertEventHandler(matchers, command, platform) {
80
+ const next = matchers.map((matcher) => ({ ...matcher, hooks: [...(matcher.hooks ?? [])] }));
81
+ let updated = false;
82
+ for (const matcher of next) {
83
+ const hooks = matcher.hooks ?? [];
84
+ for (let i = 0; i < hooks.length; i++) {
85
+ if (isDreamsClaudeHandler(hooks[i]?.command)) {
86
+ const handler = { ...hooks[i], type: "command", command };
87
+ if (platform === "win32")
88
+ handler.shell = "powershell";
89
+ else
90
+ delete handler.shell;
91
+ hooks[i] = handler;
92
+ updated = true;
93
+ }
94
+ }
95
+ matcher.hooks = hooks;
96
+ }
97
+ if (updated)
98
+ return next;
99
+ const handler = { type: "command", command };
100
+ if (platform === "win32")
101
+ handler.shell = "powershell";
102
+ return [...next, { hooks: [handler] }];
103
+ }
104
+ /**
105
+ * Merge Dreams Claude handlers into settings.json.
106
+ * SessionStart uses wake; Stop uses sync.
107
+ */
108
+ function mergeClaudeSettingsJson(existingRaw, cliPath, platform = process.platform) {
109
+ const wakeCmd = buildClaudeHookCommand(cliPath, "wake", platform);
110
+ const syncCmd = buildClaudeHookCommand(cliPath, "sync", platform);
111
+ let doc = {};
112
+ if (existingRaw !== null && existingRaw.trim() !== "") {
113
+ const parsed = JSON.parse(existingRaw);
114
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
115
+ throw new Error("claude settings.json must be a JSON object");
116
+ }
117
+ doc = { ...parsed };
118
+ }
119
+ const hooksValue = doc.hooks;
120
+ const hooks = hooksValue && typeof hooksValue === "object" && !Array.isArray(hooksValue)
121
+ ? { ...hooksValue }
122
+ : {};
123
+ const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
124
+ const stop = Array.isArray(hooks.Stop) ? hooks.Stop : [];
125
+ hooks.SessionStart = upsertEventHandler(sessionStart, wakeCmd, platform);
126
+ hooks.Stop = upsertEventHandler(stop, syncCmd, platform);
127
+ doc.hooks = hooks;
128
+ return `${JSON.stringify(doc, null, 2)}\n`;
129
+ }
130
+ function planClaudeHooksInstall(options) {
131
+ const home = options.home ?? os.homedir();
132
+ const platform = options.platform ?? process.platform;
133
+ const settingsPath = claudeSettingsPath(home);
134
+ let existing = null;
135
+ try {
136
+ existing = fs.readFileSync(settingsPath, "utf8");
137
+ }
138
+ catch {
139
+ existing = null;
140
+ }
141
+ const next = mergeClaudeSettingsJson(existing, options.cliPath, platform);
142
+ return {
143
+ settings_path: settingsPath,
144
+ changed: existing !== next,
145
+ session_start_command: buildClaudeHookCommand(options.cliPath, "wake", platform),
146
+ stop_command: buildClaudeHookCommand(options.cliPath, "sync", platform),
147
+ next_contents: next,
148
+ };
149
+ }
150
+ function installClaudeHooks(options) {
151
+ const plan = planClaudeHooksInstall(options);
152
+ if (plan.changed) {
153
+ fs.mkdirSync(path.dirname(plan.settings_path), { recursive: true });
154
+ const temporary = `${plan.settings_path}.tmp-${process.pid}-${Math.random().toString(16).slice(2)}`;
155
+ try {
156
+ fs.writeFileSync(temporary, plan.next_contents, { encoding: "utf8" });
157
+ fs.renameSync(temporary, plan.settings_path);
158
+ }
159
+ finally {
160
+ try {
161
+ fs.rmSync(temporary, { force: true });
162
+ }
163
+ catch {
164
+ // ignore
165
+ }
166
+ }
167
+ }
168
+ return plan;
169
+ }
@@ -5,9 +5,12 @@
5
5
  * Discovery rule (filesystem shape is authoritative for the pilot):
6
6
  * - only DIRECT children of ~/.claude/projects/<encoded-project>/
7
7
  * - filename must be a UUID-like `<sessionId>.jsonl`
8
- * - the transcript's own `sessionId` must agree with the filename
8
+ * - a complete head record's `sessionId` must agree with the filename
9
+ * - `cwd` is taken from the first head record that carries a string `cwd`
10
+ * for that session (modern Claude transcripts often open with mode /
11
+ * ai-title / etc. before the first cwd-bearing record)
9
12
  * - never recurse; the `subagents/` directory is excluded outright
10
- * - `isSidechain: true` records are a secondary signal to exclude sidechains
13
+ * - `isSidechain: true` on any matching head record excludes the file
11
14
  * `parentUuid` is intentionally NOT used — ordinary main-session records also
12
15
  * carry parent relationships.
13
16
  *
@@ -107,28 +110,84 @@ function detectClaudeCodeHarness(home = os.homedir()) {
107
110
  }
108
111
  return { present: true, evidence };
109
112
  }
110
- /** Read the file head and return the first COMPLETE JSONL record, or null. */
111
- function readFirstRecord(filePath) {
113
+ /**
114
+ * Read up to HEAD_READ_BYTES and derive session identity + cwd from complete
115
+ * JSONL records in that window. Modern Claude Code transcripts often open with
116
+ * metadata lines (mode, ai-title, …) that omit `cwd`; the first cwd-bearing
117
+ * record may appear several lines later (still well within the head window).
118
+ *
119
+ * Returns null when the head has no complete line, no matching sessionId, a
120
+ * mismatched sessionId, or is unreadable / malformed beyond recovery.
121
+ */
122
+ function readHeadSessionFacts(filePath, expectedSessionId) {
112
123
  let fd;
113
124
  try {
114
125
  fd = fs.openSync(filePath, "r");
115
126
  const buf = Buffer.alloc(HEAD_READ_BYTES);
116
127
  const read = fs.readSync(fd, buf, 0, HEAD_READ_BYTES, 0);
128
+ if (read === 0)
129
+ return null;
117
130
  const head = buf.subarray(0, read);
118
- const nl = head.indexOf(0x0a);
119
- if (nl === -1)
120
- return null; // no complete first line yet
121
- const line = head.subarray(0, nl).toString("utf8").trim();
122
- if (line === "")
131
+ // Only complete lines — a trailing partial record in the head is ignored.
132
+ const text = head.toString("utf8");
133
+ const lines = text.split("\n");
134
+ // If the buffer did not end on a newline, the final split piece is partial.
135
+ const completeLines = head[head.length - 1] === 0x0a ? lines : lines.slice(0, -1);
136
+ if (completeLines.length === 0)
123
137
  return null;
124
- return JSON.parse(line);
138
+ let sawMatchingSession = false;
139
+ let isSidechain = false;
140
+ let cwd = null;
141
+ for (const raw of completeLines) {
142
+ const line = raw.trim();
143
+ if (line === "")
144
+ continue;
145
+ let record;
146
+ try {
147
+ const parsed = JSON.parse(line);
148
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
149
+ continue;
150
+ record = parsed;
151
+ }
152
+ catch {
153
+ continue;
154
+ }
155
+ const sessionId = typeof record.sessionId === "string" ? record.sessionId : null;
156
+ if (sessionId !== null) {
157
+ if (sessionId !== expectedSessionId) {
158
+ return null; // filename must agree with every sessionId in the head
159
+ }
160
+ sawMatchingSession = true;
161
+ if (record.isSidechain === true)
162
+ isSidechain = true;
163
+ }
164
+ // First cwd-bearing record wins. Modern transcripts put cwd on a later
165
+ // user/assistant line; some preamble lines omit sessionId entirely.
166
+ if (cwd === null && typeof record.cwd === "string" && record.cwd.length > 0) {
167
+ try {
168
+ cwd = (0, config_1.canonicalize)(record.cwd);
169
+ }
170
+ catch {
171
+ cwd = null;
172
+ }
173
+ }
174
+ }
175
+ if (!sawMatchingSession)
176
+ return null;
177
+ return { sessionId: expectedSessionId, cwd, isSidechain };
125
178
  }
126
179
  catch {
127
180
  return null;
128
181
  }
129
182
  finally {
130
- if (fd !== undefined)
131
- fs.closeSync(fd);
183
+ if (fd !== undefined) {
184
+ try {
185
+ fs.closeSync(fd);
186
+ }
187
+ catch {
188
+ // ignore
189
+ }
190
+ }
132
191
  }
133
192
  }
134
193
  function statSignals(filePath) {
@@ -176,20 +235,17 @@ function discoverSessions(projectsDir = claudeProjectsDir()) {
176
235
  if (!UUID_RE.test(base))
177
236
  continue;
178
237
  const filePath = path.join(projectDir, file.name);
179
- const record = readFirstRecord(filePath);
180
- if (!record)
238
+ const facts = readHeadSessionFacts(filePath, base);
239
+ if (!facts)
181
240
  continue;
182
- if (record.isSidechain === true)
241
+ if (facts.isSidechain)
183
242
  continue;
184
- const sessionId = typeof record.sessionId === "string" ? record.sessionId : null;
185
- if (sessionId !== base)
186
- continue; // filename must agree with the transcript
187
243
  const signals = statSignals(filePath);
188
244
  sessions.push({
189
245
  path: filePath,
190
246
  real_path: (0, config_1.canonicalize)(filePath),
191
- session_id: sessionId,
192
- cwd: typeof record.cwd === "string" ? (0, config_1.canonicalize)(record.cwd) : null,
247
+ session_id: facts.sessionId,
248
+ cwd: facts.cwd,
193
249
  size: signals.size,
194
250
  inode: signals.inode,
195
251
  birthtime_ms: signals.birthtime_ms,