@spool-lab/core 0.5.2 → 0.6.1

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 (148) hide show
  1. package/README.md +22 -14
  2. package/dist/db/db.d.ts +2 -2
  3. package/dist/db/db.d.ts.map +1 -1
  4. package/dist/db/db.js +106 -15
  5. package/dist/db/db.js.map +1 -1
  6. package/dist/db/native-binding.d.ts +21 -0
  7. package/dist/db/native-binding.d.ts.map +1 -0
  8. package/dist/db/native-binding.js +32 -0
  9. package/dist/db/native-binding.js.map +1 -0
  10. package/dist/db/published-shares-cache.d.ts +39 -0
  11. package/dist/db/published-shares-cache.d.ts.map +1 -0
  12. package/dist/db/published-shares-cache.js +60 -0
  13. package/dist/db/published-shares-cache.js.map +1 -0
  14. package/dist/db/queries.d.ts +5 -0
  15. package/dist/db/queries.d.ts.map +1 -1
  16. package/dist/db/queries.js +219 -80
  17. package/dist/db/queries.js.map +1 -1
  18. package/dist/db/search-query.d.ts +7 -0
  19. package/dist/db/search-query.d.ts.map +1 -1
  20. package/dist/db/search-query.js +33 -7
  21. package/dist/db/search-query.js.map +1 -1
  22. package/dist/db/share-drafts.d.ts.map +1 -1
  23. package/dist/db/share-drafts.js +1 -3
  24. package/dist/db/share-drafts.js.map +1 -1
  25. package/dist/doctor/checks/config.d.ts.map +1 -1
  26. package/dist/doctor/checks/config.js +3 -3
  27. package/dist/doctor/checks/config.js.map +1 -1
  28. package/dist/doctor/checks/db.d.ts.map +1 -1
  29. package/dist/doctor/checks/db.js +36 -18
  30. package/dist/doctor/checks/db.js.map +1 -1
  31. package/dist/doctor/checks/env.d.ts.map +1 -1
  32. package/dist/doctor/checks/env.js +5 -1
  33. package/dist/doctor/checks/env.js.map +1 -1
  34. package/dist/doctor/checks/index.js +2 -2
  35. package/dist/doctor/checks/index.js.map +1 -1
  36. package/dist/doctor/checks/native.d.ts.map +1 -1
  37. package/dist/doctor/checks/native.js.map +1 -1
  38. package/dist/doctor/checks/versions.d.ts.map +1 -1
  39. package/dist/doctor/checks/versions.js +2 -2
  40. package/dist/doctor/checks/versions.js.map +1 -1
  41. package/dist/doctor/preflight.js +2 -2
  42. package/dist/doctor/preflight.js.map +1 -1
  43. package/dist/doctor/runner.d.ts.map +1 -1
  44. package/dist/doctor/runner.js +1 -1
  45. package/dist/doctor/runner.js.map +1 -1
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +2 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/migrations/agent-search-cleanup.d.ts.map +1 -1
  51. package/dist/migrations/agent-search-cleanup.js +10 -6
  52. package/dist/migrations/agent-search-cleanup.js.map +1 -1
  53. package/dist/migrations/worktree-identity-upgrade.d.ts +5 -3
  54. package/dist/migrations/worktree-identity-upgrade.d.ts.map +1 -1
  55. package/dist/migrations/worktree-identity-upgrade.js +26 -3
  56. package/dist/migrations/worktree-identity-upgrade.js.map +1 -1
  57. package/dist/observability/exporter-file.d.ts.map +1 -1
  58. package/dist/observability/exporter-file.js +14 -6
  59. package/dist/observability/exporter-file.js.map +1 -1
  60. package/dist/observability/exporter-pretty.d.ts.map +1 -1
  61. package/dist/observability/exporter-pretty.js +13 -7
  62. package/dist/observability/exporter-pretty.js.map +1 -1
  63. package/dist/observability/layer.d.ts.map +1 -1
  64. package/dist/observability/layer.js +1 -1
  65. package/dist/observability/layer.js.map +1 -1
  66. package/dist/parsers/claude.d.ts.map +1 -1
  67. package/dist/parsers/claude.js +4 -144
  68. package/dist/parsers/claude.js.map +1 -1
  69. package/dist/parsers/codex.d.ts +7 -1
  70. package/dist/parsers/codex.d.ts.map +1 -1
  71. package/dist/parsers/codex.js +43 -186
  72. package/dist/parsers/codex.js.map +1 -1
  73. package/dist/parsers/gemini.d.ts.map +1 -1
  74. package/dist/parsers/gemini.js +113 -13
  75. package/dist/parsers/gemini.js.map +1 -1
  76. package/dist/parsers/opencode.d.ts.map +1 -1
  77. package/dist/parsers/opencode.js +67 -33
  78. package/dist/parsers/opencode.js.map +1 -1
  79. package/dist/parsers/pi.d.ts +18 -0
  80. package/dist/parsers/pi.d.ts.map +1 -0
  81. package/dist/parsers/pi.js +155 -0
  82. package/dist/parsers/pi.js.map +1 -0
  83. package/dist/parsers/spool-prelude.d.ts +1 -15
  84. package/dist/parsers/spool-prelude.d.ts.map +1 -1
  85. package/dist/parsers/spool-prelude.js +3 -20
  86. package/dist/parsers/spool-prelude.js.map +1 -1
  87. package/dist/projects/fs.d.ts.map +1 -1
  88. package/dist/projects/fs.js +1 -1
  89. package/dist/projects/fs.js.map +1 -1
  90. package/dist/projects/groups.d.ts +4 -1
  91. package/dist/projects/groups.d.ts.map +1 -1
  92. package/dist/projects/groups.js +32 -11
  93. package/dist/projects/groups.js.map +1 -1
  94. package/dist/projects/identity-synthesizers.d.ts.map +1 -1
  95. package/dist/projects/identity-synthesizers.js.map +1 -1
  96. package/dist/projects/identity.d.ts +2 -2
  97. package/dist/projects/identity.d.ts.map +1 -1
  98. package/dist/projects/identity.js +24 -13
  99. package/dist/projects/identity.js.map +1 -1
  100. package/dist/projects/sessions.d.ts.map +1 -1
  101. package/dist/projects/sessions.js +11 -10
  102. package/dist/projects/sessions.js.map +1 -1
  103. package/dist/projects/worktree-resolvers.d.ts +3 -0
  104. package/dist/projects/worktree-resolvers.d.ts.map +1 -1
  105. package/dist/projects/worktree-resolvers.js +144 -8
  106. package/dist/projects/worktree-resolvers.js.map +1 -1
  107. package/dist/security/index.d.ts +2 -2
  108. package/dist/security/index.d.ts.map +1 -1
  109. package/dist/security/index.js +1 -1
  110. package/dist/security/index.js.map +1 -1
  111. package/dist/security/maintenance.d.ts.map +1 -1
  112. package/dist/security/maintenance.js +1 -1
  113. package/dist/security/maintenance.js.map +1 -1
  114. package/dist/security/profile.d.ts.map +1 -1
  115. package/dist/security/profile.js +6 -4
  116. package/dist/security/profile.js.map +1 -1
  117. package/dist/security/purge.d.ts +2 -2
  118. package/dist/security/purge.d.ts.map +1 -1
  119. package/dist/security/purge.js +31 -15
  120. package/dist/security/purge.js.map +1 -1
  121. package/dist/security/repo.d.ts +1 -1
  122. package/dist/security/repo.d.ts.map +1 -1
  123. package/dist/security/repo.js +64 -37
  124. package/dist/security/repo.js.map +1 -1
  125. package/dist/security/scan.d.ts +2 -2
  126. package/dist/security/scan.d.ts.map +1 -1
  127. package/dist/security/scan.js +6 -4
  128. package/dist/security/scan.js.map +1 -1
  129. package/dist/security/worker.d.ts +2 -2
  130. package/dist/security/worker.d.ts.map +1 -1
  131. package/dist/security/worker.js +5 -11
  132. package/dist/security/worker.js.map +1 -1
  133. package/dist/sync/source-paths.d.ts.map +1 -1
  134. package/dist/sync/source-paths.js +46 -16
  135. package/dist/sync/source-paths.js.map +1 -1
  136. package/dist/sync/syncer.d.ts.map +1 -1
  137. package/dist/sync/syncer.js +112 -80
  138. package/dist/sync/syncer.js.map +1 -1
  139. package/dist/sync/watcher.d.ts +1 -1
  140. package/dist/sync/watcher.d.ts.map +1 -1
  141. package/dist/sync/watcher.js +17 -4
  142. package/dist/sync/watcher.js.map +1 -1
  143. package/dist/types.d.ts +8 -1
  144. package/dist/types.d.ts.map +1 -1
  145. package/dist/util/resolve-bin.d.ts.map +1 -1
  146. package/dist/util/resolve-bin.js +40 -25
  147. package/dist/util/resolve-bin.js.map +1 -1
  148. package/package.json +35 -17
@@ -1,111 +1,9 @@
1
1
  import { readFileSync } from 'node:fs';
2
+ import { parseClaudeSessionText } from '@spool-lab/session-kit';
3
+ // The parsing brain lives in @spool-lab/session-kit (browser-safe, shared
4
+ // with the web reader); this wrapper owns only the file I/O.
2
5
  export function loadClaudeSession(filePath) {
3
- const raw = readFileSync(filePath, 'utf8');
4
- const lines = raw.split('\n').filter(l => l.trim().length > 0);
5
- const messages = [];
6
- let sessionUuid = '';
7
- let cwd = '';
8
- let model = '';
9
- let customTitle = '';
10
- const SKIP_TYPES = new Set([
11
- 'file-history-snapshot',
12
- 'progress',
13
- 'queue-operation',
14
- 'last-prompt',
15
- ]);
16
- for (const line of lines) {
17
- let record;
18
- try {
19
- record = JSON.parse(line);
20
- }
21
- catch {
22
- continue;
23
- }
24
- const type = record['type'];
25
- if (!type || SKIP_TYPES.has(type))
26
- continue;
27
- if (!sessionUuid && record['sessionId'])
28
- sessionUuid = record['sessionId'];
29
- if (!cwd && record['cwd'])
30
- cwd = record['cwd'];
31
- if (type === 'custom-title') {
32
- const ct = record['customTitle'];
33
- if (ct)
34
- customTitle = ct;
35
- continue;
36
- }
37
- if (type === 'assistant') {
38
- const msg = record['message'];
39
- if (msg?.['model'])
40
- model = msg['model'];
41
- }
42
- if (type === 'summary') {
43
- const summaryText = record['summary'];
44
- if (summaryText) {
45
- messages.push({
46
- uuid: record['uuid'] ?? `summary-${messages.length}`,
47
- parentUuid: record['parentUuid'] ?? null,
48
- role: 'system',
49
- contentText: summaryText.trim(),
50
- timestamp: record['timestamp'],
51
- isSidechain: Boolean(record['isSidechain']),
52
- toolNames: [],
53
- seq: messages.length,
54
- });
55
- }
56
- continue;
57
- }
58
- const msgObj = record['message'];
59
- if (!msgObj)
60
- continue;
61
- const role = msgObj['role'];
62
- if (role !== 'user' && role !== 'assistant')
63
- continue;
64
- const contentRaw = msgObj['content'];
65
- const contentText = extractText(contentRaw);
66
- const toolNames = extractToolNames(contentRaw);
67
- // Skip empty messages (e.g. tool result placeholders with no text)
68
- if (!contentText && toolNames.length === 0)
69
- continue;
70
- messages.push({
71
- uuid: record['uuid'] ?? `msg-${messages.length}`,
72
- parentUuid: record['parentUuid'] ?? null,
73
- role: role,
74
- contentText,
75
- timestamp: record['timestamp'],
76
- isSidechain: Boolean(record['isSidechain']),
77
- toolNames,
78
- seq: messages.length,
79
- });
80
- }
81
- if (messages.length === 0)
82
- return { kind: 'skipped' };
83
- // Use cwd from messages if not in top-level fields
84
- if (!cwd) {
85
- for (const m of messages) {
86
- // cwd is on the record level, not message level — already captured above
87
- }
88
- }
89
- const firstUserMsg = messages.find(m => m.role === 'user' && m.contentText.length > 0 && !m.isSidechain);
90
- const title = customTitle
91
- || (firstUserMsg
92
- ? firstUserMsg.contentText.replace(/<[^>]+>/g, '').trim().slice(0, 120)
93
- : '(no title)');
94
- const timestamps = messages.map(m => m.timestamp).filter(Boolean).sort();
95
- return {
96
- kind: 'parsed',
97
- session: {
98
- source: 'claude',
99
- sessionUuid: sessionUuid || filePath,
100
- filePath,
101
- title,
102
- cwd,
103
- model,
104
- startedAt: timestamps[0] ?? new Date().toISOString(),
105
- endedAt: timestamps[timestamps.length - 1] ?? new Date().toISOString(),
106
- messages,
107
- },
108
- };
6
+ return parseClaudeSessionText(readFileSync(filePath, 'utf8'), filePath);
109
7
  }
110
8
  export function parseClaudeSession(filePath) {
111
9
  try {
@@ -116,44 +14,6 @@ export function parseClaudeSession(filePath) {
116
14
  return null;
117
15
  }
118
16
  }
119
- // Slash-command records in Claude Code JSONL come as a triplet:
120
- // <command-name>/X</command-name>
121
- // <command-message>X</command-message>
122
- // <command-args>Y</command-args>
123
- // Strip the whole record as one unit so bare <command-args> appearing in
124
- // legitimate user content (e.g. a user pasting log output that contains
125
- // these tags) is preserved.
126
- const SLASH_COMMAND_RECORD = /<command-name>[\s\S]*?<\/command-name>(?:\s*<command-message>[\s\S]*?<\/command-message>)?(?:\s*<command-args>[\s\S]*?<\/command-args>)?/g;
127
- function extractText(content) {
128
- let raw;
129
- if (typeof content === 'string') {
130
- raw = content;
131
- }
132
- else if (Array.isArray(content)) {
133
- raw = content
134
- .filter(item => item.type === 'text')
135
- .map(item => item.text ?? '')
136
- .join('\n');
137
- }
138
- else {
139
- return '';
140
- }
141
- return raw
142
- .replace(/<spool-system-prelude>[\s\S]*?<\/spool-system-prelude>/g, '')
143
- .replace(SLASH_COMMAND_RECORD, '')
144
- .replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g, '')
145
- .replace(/<local-command-caveat>[\s\S]*?<\/local-command-caveat>/g, '')
146
- .replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '')
147
- .replace(/<[^>]+>/g, '')
148
- .trim();
149
- }
150
- function extractToolNames(content) {
151
- if (!Array.isArray(content))
152
- return [];
153
- return content
154
- .filter(item => item.type === 'tool_use' && item.name)
155
- .map(item => item.name);
156
- }
157
17
  /** Decode a Claude project slug to a display path.
158
18
  * e.g. '-Users-claw-code-spool' → '/Users/claw/code/spool'
159
19
  * Note: lossy for paths containing hyphens — prefer cwd from session records.
@@ -1 +1 @@
1
- {"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/parsers/claude.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/parsers/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAEtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAI/D,0EAA0E;AAC1E,6DAA6D;AAE7D,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,OAAO,sBAAsB,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAA;AACzE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAA;QAC1C,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACtC,OAAO,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;AAC/C,CAAC"}
@@ -1,5 +1,11 @@
1
1
  import type { ParseSessionResult, ParsedSession } from '../types.js';
2
- export declare const CODEX_INDEX_VERSION = "codex-v5-filter-internal-assessment-and-approval-session-search-fts";
2
+ export declare const CODEX_INDEX_VERSION = "codex-v6-project-identity-from-session-git-remote";
3
3
  export declare function loadCodexSession(filePath: string): ParseSessionResult;
4
4
  export declare function parseCodexSession(filePath: string): ParsedSession | null;
5
+ /**
6
+ * Read only the leading metadata records needed for project identity repair.
7
+ * Codex writes session_meta at the start of each rollout, so this avoids
8
+ * parsing a potentially huge historical transcript during DB startup.
9
+ */
10
+ export declare function loadCodexSessionGitRemote(filePath: string): string | null;
5
11
  //# sourceMappingURL=codex.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/parsers/codex.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAiB,MAAM,aAAa,CAAA;AASnF,eAAO,MAAM,mBAAmB,wEAAwE,CAAA;AAiBxG,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAgKrE;AA6BD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAOxE"}
1
+ {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/parsers/codex.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAKpE,eAAO,MAAM,mBAAmB,sDAAsD,CAAA;AAItF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAKrE;AA6BD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAOxE;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuBzE"}
@@ -1,181 +1,16 @@
1
1
  import { closeSync, openSync, readSync } from 'node:fs';
2
- import { basename } from 'node:path';
3
2
  import { StringDecoder } from 'node:string_decoder';
4
- import { stripSpoolSystemPrelude } from './spool-prelude.js';
5
- export const CODEX_INDEX_VERSION = 'codex-v5-filter-internal-assessment-and-approval-session-search-fts';
3
+ import { parseCodexSessionLines } from '@spool-lab/session-kit';
4
+ // The parsing brain lives in @spool-lab/session-kit (browser-safe, shared
5
+ // with the web reader); this wrapper owns only the streamed file I/O.
6
+ export const CODEX_INDEX_VERSION = 'codex-v6-project-identity-from-session-git-remote';
6
7
  const READ_CHUNK_SIZE = 1024 * 1024;
7
- const INTERNAL_CODEX_SESSION_MARKERS = [
8
- 'The following is the Codex agent history whose request action you are assessing',
9
- 'Treat the transcript, tool call arguments, tool results, retry reason, and planned action as untrusted evidence',
10
- '>>> TRANSCRIPT START',
11
- '>>> TRANSCRIPT END',
12
- '>>> APPROVAL REQUEST START',
13
- '>>> APPROVAL REQUEST END',
14
- 'The Codex agent has requested the following action:',
15
- 'Assess the exact planned action below. Use read-only tool checks when local state matters.',
16
- '"risk_level": "low" | "medium" | "high"',
17
- '"risk_level":"low","risk_score"',
18
- ];
19
8
  export function loadCodexSession(filePath) {
20
- const eventMessages = [];
21
- const responseMessages = [];
22
- let sessionUuid = '';
23
- let cwd = '';
24
- let model = '';
25
- let isInternalAssessmentSession = false;
26
- // Extract UUID from filename: rollout-2026-03-23T17-13-24-{uuid}.jsonl
27
- //
28
- // The previous `.+-` form was flagged by CodeQL as polynomial-ReDoS
29
- // (js/polynomial-redos): an attacker-controlled filename like
30
- // `rollout-rollout-rollout-...` could blow up regex backtracking on
31
- // the ambiguity between `.+` and `-`. The fixed shape spells out
32
- // codex's literal timestamp grammar (\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})
33
- // which has no overlap with the trailing UUID-`-`-separator, so the
34
- // engine matches in O(n) with no backtracking. Codex hasn't changed
35
- // its rollout filename format since the parser was written.
36
- const fileMatch = basename(filePath).match(/^rollout-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/);
37
- if (fileMatch?.[1])
38
- sessionUuid = fileMatch[1];
39
- for (const line of readNonEmptyLines(filePath)) {
40
- let record;
41
- try {
42
- record = JSON.parse(line);
43
- }
44
- catch {
45
- continue;
46
- }
47
- const { type, payload, timestamp } = record;
48
- if (!timestamp)
49
- continue;
50
- if (type === 'session_meta' && payload) {
51
- if (!sessionUuid && payload['id'])
52
- sessionUuid = payload['id'];
53
- if (payload['cwd'])
54
- cwd = payload['cwd'];
55
- const source = payload['source'];
56
- if (isGuardianSubagentSource(source))
57
- isInternalAssessmentSession = true;
58
- continue;
59
- }
60
- if (type === 'turn_context' && payload) {
61
- if (payload['model'])
62
- model = payload['model'];
63
- if (!cwd && payload['cwd'])
64
- cwd = payload['cwd'];
65
- continue;
66
- }
67
- if (type === 'event_msg' && payload) {
68
- const msgType = payload['type'];
69
- if (msgType === 'user_message' && payload['message']) {
70
- const text = stripSpoolSystemPrelude(String(payload['message']));
71
- if (looksLikeInternalCodexAssessment(text)) {
72
- isInternalAssessmentSession = true;
73
- continue;
74
- }
75
- if (text) {
76
- eventMessages.push({
77
- uuid: `codex-${sessionUuid}-u-${eventMessages.length}`,
78
- parentUuid: null,
79
- role: 'user',
80
- contentText: text,
81
- timestamp,
82
- isSidechain: false,
83
- toolNames: [],
84
- seq: eventMessages.length,
85
- });
86
- }
87
- }
88
- else if (msgType === 'agent_message' && payload['message']) {
89
- const text = String(payload['message']).trim();
90
- if (looksLikeInternalCodexAssessment(text)) {
91
- isInternalAssessmentSession = true;
92
- continue;
93
- }
94
- if (text) {
95
- eventMessages.push({
96
- uuid: `codex-${sessionUuid}-a-${eventMessages.length}`,
97
- parentUuid: null,
98
- role: 'assistant',
99
- contentText: text,
100
- timestamp,
101
- isSidechain: false,
102
- toolNames: [],
103
- seq: eventMessages.length,
104
- });
105
- }
106
- }
107
- continue;
108
- }
109
- if (type === 'response_item' && payload) {
110
- const role = payload['role'];
111
- if (role === 'assistant') {
112
- const content = payload['content'];
113
- if (Array.isArray(content)) {
114
- const text = content
115
- .filter(c => c.type === 'output_text' || c.type === 'text')
116
- .map(c => c.text ?? '')
117
- .join('\n')
118
- .trim();
119
- if (looksLikeInternalCodexAssessment(text)) {
120
- isInternalAssessmentSession = true;
121
- continue;
122
- }
123
- if (text) {
124
- responseMessages.push({
125
- uuid: `codex-${sessionUuid}-ri-${responseMessages.length}`,
126
- parentUuid: null,
127
- role: 'assistant',
128
- contentText: text,
129
- timestamp,
130
- isSidechain: false,
131
- toolNames: [],
132
- seq: responseMessages.length,
133
- });
134
- }
135
- }
136
- }
137
- continue;
138
- }
139
- }
140
- // Strategy: use event_msg for UI (concise); supplement with response_items for
141
- // FTS richness when event_msgs are sparse. We index both but deduplicate.
142
- //
143
- // If we have event_msgs, use them as the primary message list.
144
- // response_items are added as system-level messages for FTS indexing only.
145
- let messages;
146
- if (eventMessages.length > 0) {
147
- messages = [...eventMessages];
148
- // Add response_items as sidechain messages for FTS richness
149
- for (const rm of responseMessages) {
150
- messages.push({ ...rm, isSidechain: true, seq: messages.length });
151
- }
152
- }
153
- else {
154
- messages = responseMessages;
155
- }
156
- if (isInternalAssessmentSession)
157
- return { kind: 'filtered' };
158
- if (messages.length === 0)
159
- return { kind: 'skipped' };
160
- // Re-number seq
161
- messages = messages.map((m, i) => ({ ...m, seq: i }));
162
- const firstUserMsg = messages.find(m => m.role === 'user' && !m.isSidechain);
163
- const title = firstUserMsg?.contentText.slice(0, 120) ?? '(no title)';
164
- const timestamps = messages.filter(m => !m.isSidechain).map(m => m.timestamp).sort();
165
- return {
166
- kind: 'parsed',
167
- session: {
168
- source: 'codex',
169
- sessionUuid: sessionUuid || filePath,
170
- filePath,
171
- title,
172
- cwd,
173
- model,
174
- startedAt: timestamps[0] ?? new Date().toISOString(),
175
- endedAt: timestamps[timestamps.length - 1] ?? new Date().toISOString(),
176
- messages,
177
- },
178
- };
9
+ const result = parseCodexSessionLines(readNonEmptyLines(filePath), filePath);
10
+ if (result.kind !== 'parsed')
11
+ return result;
12
+ const gitRemote = loadCodexSessionGitRemote(filePath);
13
+ return gitRemote ? { kind: 'parsed', session: { ...result.session, gitRemote } } : result;
179
14
  }
180
15
  function* readNonEmptyLines(filePath) {
181
16
  const fd = openSync(filePath, 'r');
@@ -212,17 +47,39 @@ export function parseCodexSession(filePath) {
212
47
  return null;
213
48
  }
214
49
  }
215
- function isGuardianSubagentSource(source) {
216
- if (!source || typeof source !== 'object')
217
- return false;
218
- const subagent = source['subagent'];
219
- if (!subagent || typeof subagent !== 'object')
220
- return false;
221
- return subagent['other'] === 'guardian';
222
- }
223
- function looksLikeInternalCodexAssessment(text) {
224
- if (!text)
225
- return false;
226
- return INTERNAL_CODEX_SESSION_MARKERS.some(marker => text.includes(marker));
50
+ /**
51
+ * Read only the leading metadata records needed for project identity repair.
52
+ * Codex writes session_meta at the start of each rollout, so this avoids
53
+ * parsing a potentially huge historical transcript during DB startup.
54
+ */
55
+ export function loadCodexSessionGitRemote(filePath) {
56
+ try {
57
+ let scanned = 0;
58
+ for (const line of readNonEmptyLines(filePath)) {
59
+ if (scanned++ >= 100)
60
+ break;
61
+ let record;
62
+ try {
63
+ record = JSON.parse(line);
64
+ }
65
+ catch {
66
+ continue;
67
+ }
68
+ if (record['type'] !== 'session_meta')
69
+ continue;
70
+ const payload = record['payload'];
71
+ if (!payload || typeof payload !== 'object')
72
+ return null;
73
+ const git = payload['git'];
74
+ if (!git || typeof git !== 'object')
75
+ return null;
76
+ const repositoryUrl = git['repository_url'];
77
+ return typeof repositoryUrl === 'string' && repositoryUrl.trim() ? repositoryUrl : null;
78
+ }
79
+ return null;
80
+ }
81
+ catch {
82
+ return null;
83
+ }
227
84
  }
228
85
  //# sourceMappingURL=codex.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/parsers/codex.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/parsers/codex.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/parsers/gemini.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAiB,aAAa,EAAE,MAAM,aAAa,CAAA;AA4BnF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CA0DtE;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAOzE"}
1
+ {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/parsers/gemini.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAiB,aAAa,EAAE,MAAM,aAAa,CAAA;AAyGnF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAoEtE;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAOzE"}
@@ -1,11 +1,96 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
3
2
  import { homedir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
4
  import { stripSpoolSystemPrelude } from './spool-prelude.js';
5
5
  const GEMINI_INDEXABLE_TYPES = new Set(['user', 'gemini', 'info', 'warning', 'error']);
6
+ /** Replays a Gemini CLI JSONL session log into a single session record.
7
+ *
8
+ * Mirrors the upstream loader (gemini-cli chatRecordingService.ts
9
+ * loadConversationRecord): a line is exactly one of — a `$rewindTo`
10
+ * marker (drop that message and everything after it), a message
11
+ * snapshot keyed by `id` (later snapshots replace earlier ones, e.g.
12
+ * tool-call status updates), a `$set` metadata update whose
13
+ * `messages` array is a checkpoint that REPLACES all prior messages,
14
+ * or the initial metadata line. */
15
+ function loadJsonlRecord(raw) {
16
+ const record = {};
17
+ const messages = new Map();
18
+ const setMessage = (msg) => {
19
+ if (!msg || typeof msg !== 'object')
20
+ return;
21
+ const message = msg;
22
+ if (typeof message.id !== 'string')
23
+ return;
24
+ messages.set(message.id, message);
25
+ };
26
+ const applyMetadata = (src) => {
27
+ if (typeof src['sessionId'] === 'string')
28
+ record.sessionId = src['sessionId'];
29
+ if (typeof src['startTime'] === 'string')
30
+ record.startTime = src['startTime'];
31
+ if (typeof src['lastUpdated'] === 'string')
32
+ record.lastUpdated = src['lastUpdated'];
33
+ if (typeof src['kind'] === 'string')
34
+ record.kind = src['kind'];
35
+ if (typeof src['summary'] === 'string')
36
+ record.summary = src['summary'];
37
+ };
38
+ for (const line of raw.split('\n')) {
39
+ if (!line.trim())
40
+ continue;
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(line);
44
+ }
45
+ catch {
46
+ continue;
47
+ }
48
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
49
+ continue;
50
+ const obj = parsed;
51
+ if (typeof obj['$rewindTo'] === 'string') {
52
+ let found = false;
53
+ for (const id of messages.keys()) {
54
+ if (id === obj['$rewindTo'])
55
+ found = true;
56
+ if (found)
57
+ messages.delete(id);
58
+ }
59
+ if (!found)
60
+ messages.clear();
61
+ continue;
62
+ }
63
+ if (typeof obj['id'] === 'string') {
64
+ setMessage(obj);
65
+ continue;
66
+ }
67
+ if (obj['$set'] && typeof obj['$set'] === 'object') {
68
+ const set = obj['$set'];
69
+ if (Array.isArray(set['messages'])) {
70
+ messages.clear();
71
+ for (const msg of set['messages'])
72
+ setMessage(msg);
73
+ }
74
+ applyMetadata(set);
75
+ continue;
76
+ }
77
+ if (typeof obj['sessionId'] === 'string') {
78
+ applyMetadata(obj);
79
+ // A legacy whole-file record can appear as a single JSONL line.
80
+ if (Array.isArray(obj['messages'])) {
81
+ for (const msg of obj['messages'])
82
+ setMessage(msg);
83
+ }
84
+ }
85
+ }
86
+ record.messages = Array.from(messages.values());
87
+ return record;
88
+ }
6
89
  export function loadGeminiSession(filePath) {
7
90
  const raw = readFileSync(filePath, 'utf8');
8
- const record = JSON.parse(raw);
91
+ const record = filePath.endsWith('.jsonl')
92
+ ? loadJsonlRecord(raw)
93
+ : JSON.parse(raw);
9
94
  if (record.kind === 'subagent')
10
95
  return { kind: 'filtered' };
11
96
  if (!Array.isArray(record.messages) || record.messages.length === 0)
@@ -16,7 +101,10 @@ export function loadGeminiSession(filePath) {
16
101
  const type = message.type;
17
102
  if (!type || !GEMINI_INDEXABLE_TYPES.has(type))
18
103
  continue;
19
- const contentText = extractText(message.content);
104
+ // <session_context> is environment metadata gemini-cli injects into the
105
+ // first user turn — strip it so it never pollutes FTS or derived titles.
106
+ const rawText = extractText(message.content);
107
+ const contentText = type === 'user' ? stripSessionContext(rawText) : rawText;
20
108
  const toolNames = extractToolNames(message.toolCalls);
21
109
  if (!contentText && toolNames.length === 0)
22
110
  continue;
@@ -33,14 +121,15 @@ export function loadGeminiSession(filePath) {
33
121
  seq: messages.length,
34
122
  });
35
123
  }
36
- if (!messages.some(message => message.role === 'user' || message.role === 'assistant')) {
124
+ if (!messages.some((message) => message.role === 'user' || message.role === 'assistant')) {
37
125
  return { kind: 'skipped' };
38
126
  }
39
- const firstUserMessage = messages.find(message => message.role === 'user' && message.contentText.trim().length > 0);
40
- const title = record.summary?.trim()
41
- || firstUserMessage?.contentText.slice(0, 120)
42
- || '(no title)';
43
- const timestamps = messages.map(message => message.timestamp).filter(Boolean).sort();
127
+ const firstUserMessage = messages.find((message) => message.role === 'user' && message.contentText.trim().length > 0);
128
+ const title = record.summary?.trim() || firstUserMessage?.contentText.slice(0, 120) || '(no title)';
129
+ const timestamps = messages
130
+ .map((message) => message.timestamp)
131
+ .filter(Boolean)
132
+ .sort();
44
133
  const cwd = resolveGeminiProjectRoot(filePath);
45
134
  return {
46
135
  kind: 'parsed',
@@ -83,7 +172,7 @@ function extractText(content) {
83
172
  if (!Array.isArray(content))
84
173
  return '';
85
174
  return stripSpoolSystemPrelude(content
86
- .map(item => {
175
+ .map((item) => {
87
176
  if (typeof item === 'string')
88
177
  return item;
89
178
  if (!item || typeof item !== 'object')
@@ -94,11 +183,23 @@ function extractText(content) {
94
183
  .filter(Boolean)
95
184
  .join('\n'));
96
185
  }
186
+ function stripSessionContext(text) {
187
+ let result = text;
188
+ let open = result.indexOf('<session_context>');
189
+ while (open !== -1) {
190
+ const close = result.indexOf('</session_context>', open + '<session_context>'.length);
191
+ if (close === -1)
192
+ break;
193
+ result = result.slice(0, open) + result.slice(close + '</session_context>'.length);
194
+ open = result.indexOf('<session_context>');
195
+ }
196
+ return result.trim();
197
+ }
97
198
  function extractToolNames(toolCalls) {
98
199
  if (!Array.isArray(toolCalls))
99
200
  return [];
100
201
  return toolCalls
101
- .map(toolCall => {
202
+ .map((toolCall) => {
102
203
  if (!toolCall || typeof toolCall !== 'object')
103
204
  return undefined;
104
205
  const record = toolCall;
@@ -125,8 +226,7 @@ function resolveGeminiProjectRoot(filePath) {
125
226
  return '';
126
227
  try {
127
228
  const rawProjects = JSON.parse(readFileSync(projectsPath, 'utf8'));
128
- const entry = Object.entries(rawProjects.projects ?? {})
129
- .find(([, shortId]) => shortId === identifier);
229
+ const entry = Object.entries(rawProjects.projects ?? {}).find(([, shortId]) => shortId === identifier);
130
230
  return entry?.[0] ?? '';
131
231
  }
132
232
  catch {