codehere 0.6.1 → 0.7.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.
Files changed (81) hide show
  1. package/README.md +5 -3
  2. package/dist/application/use-cases/domain-trust.js +110 -0
  3. package/dist/application/use-cases/generate-final-report.js +84 -5
  4. package/dist/application/use-cases/generate-narration.js +20 -3
  5. package/dist/application/use-cases/generate-thread-title.js +4 -0
  6. package/dist/application/use-cases/thread-export.js +155 -0
  7. package/dist/application/use-cases/verify-claim.js +274 -0
  8. package/dist/application/use-cases/write-pass.js +277 -0
  9. package/dist/index.js +218 -19
  10. package/dist/infrastructure/completion/bash-completion.js +2 -2
  11. package/dist/infrastructure/config/config-manager.js +1 -1
  12. package/dist/infrastructure/context/adapters/drawio-source.js +157 -0
  13. package/dist/infrastructure/context/adapters/figma-source.js +163 -0
  14. package/dist/infrastructure/context/adapters/github-issue-source.js +159 -0
  15. package/dist/infrastructure/context/adapters/linear-source.js +190 -0
  16. package/dist/infrastructure/context/context-resolver.js +144 -0
  17. package/dist/infrastructure/context/context-source.js +114 -0
  18. package/dist/infrastructure/context/oauth-flow.js +207 -0
  19. package/dist/infrastructure/context/oauth-store.js +119 -0
  20. package/dist/infrastructure/context/registry-bootstrap.js +99 -0
  21. package/dist/infrastructure/context/write-pass-resolver.js +106 -0
  22. package/dist/infrastructure/cost/budget-config.js +1 -1
  23. package/dist/infrastructure/cost/spend-aggregator.js +5 -5
  24. package/dist/infrastructure/hooks/claude-sdk-hook.js +79 -59
  25. package/dist/infrastructure/hooks/session-analyzer.js +65 -8
  26. package/dist/infrastructure/observability/execution-tracer.js +0 -5
  27. package/dist/infrastructure/orchestration/aider-adapter.js +45 -17
  28. package/dist/infrastructure/orchestration/artifacts-extractor.js +39 -5
  29. package/dist/infrastructure/orchestration/cassette-recorder.js +120 -0
  30. package/dist/infrastructure/orchestration/claude-code-adapter.js +33 -4
  31. package/dist/infrastructure/orchestration/codex-adapter.js +157 -19
  32. package/dist/infrastructure/orchestration/context-handoff.js +4 -3
  33. package/dist/infrastructure/orchestration/control/codex-control-surface.js +99 -0
  34. package/dist/infrastructure/orchestration/control/control-surface.js +15 -0
  35. package/dist/infrastructure/orchestration/gemini-adapter.js +15 -17
  36. package/dist/infrastructure/orchestration/graphify-context.js +6 -13
  37. package/dist/infrastructure/orchestration/memory-injector.js +11 -16
  38. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +233 -0
  39. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +73 -0
  40. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +83 -0
  41. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +37 -0
  42. package/dist/infrastructure/orchestration/opencode-adapter.js +23 -3
  43. package/dist/infrastructure/orchestration/progress-aggregator.js +178 -5
  44. package/dist/infrastructure/orchestration/session-manager.js +142 -29
  45. package/dist/infrastructure/orchestration/session-persistence.js +139 -30
  46. package/dist/infrastructure/orchestration/thread-key-store.js +127 -0
  47. package/dist/infrastructure/orchestration/write-pass-store.js +46 -0
  48. package/dist/infrastructure/protocols/cloud-sync.js +10 -11
  49. package/dist/infrastructure/protocols/mcp-server-impl.js +1295 -17
  50. package/dist/infrastructure/protocols/thread-phase.js +105 -0
  51. package/dist/infrastructure/protocols/ui-page-v2.js +3768 -75
  52. package/dist/infrastructure/protocols/ui-page.js +12 -3299
  53. package/dist/infrastructure/protocols/verify-page.js +941 -0
  54. package/dist/infrastructure/security/trust-scorer.js +76 -1
  55. package/dist/infrastructure/storage/cross-tool-agents.js +20 -3
  56. package/dist/infrastructure/storage/project-thread-migration.js +14 -9
  57. package/dist/infrastructure/storage/project-thread-reader.js +11 -1
  58. package/dist/infrastructure/storage/project-thread-title-backfill.js +7 -3
  59. package/dist/monitoring.js +1 -1
  60. package/dist/presentation/cli/commands/audit-command.js +126 -0
  61. package/dist/presentation/cli/commands/budget-command.js +2 -2
  62. package/dist/presentation/cli/commands/config-command.js +2 -2
  63. package/dist/presentation/cli/commands/context-source-command.js +198 -0
  64. package/dist/presentation/cli/commands/help-command.js +51 -1
  65. package/dist/presentation/cli/commands/init-command.js +5 -4
  66. package/dist/presentation/cli/commands/memory-command.js +29 -15
  67. package/dist/presentation/cli/commands/protocols-builder.js +2 -2
  68. package/dist/presentation/cli/commands/repo-map-builder.js +4 -4
  69. package/dist/presentation/cli/commands/report-command.js +6 -4
  70. package/dist/presentation/cli/commands/setup-command.js +71 -18
  71. package/dist/presentation/cli/commands/status-command.js +0 -4
  72. package/dist/presentation/cli/commands/thread-command.js +288 -0
  73. package/dist/presentation/cli/commands/verify-command.js +233 -0
  74. package/dist/presentation/cli/error-display.js +1 -1
  75. package/dist/presentation/cli/repl.js +52 -2
  76. package/dist/presentation/cli/task-deliverable.js +127 -0
  77. package/dist/testing/cassette.js +125 -0
  78. package/dist/testing/replay-adapter.js +166 -0
  79. package/dist/ui.js +54 -29
  80. package/dist/utils/typo-guard.js +1 -1
  81. package/package.json +4 -2
@@ -22,16 +22,20 @@
22
22
  import { existsSync, mkdirSync, appendFileSync, readFileSync } from 'fs';
23
23
  import { homedir } from 'os';
24
24
  import { join, dirname } from 'path';
25
- const SESSIONS_FILE = join(homedir(), '.codehere', 'sessions.jsonl');
25
+ function sessionsFile() {
26
+ // Honor CODEHERE_HOME like the rest of the codebase (config, audit, threads)
27
+ // so a scratch home isolates sessions for dogfooding / the mock harness.
28
+ return join(process.env.CODEHERE_HOME || homedir(), '.codehere', 'sessions.jsonl');
29
+ }
26
30
  function ensureDir() {
27
- const dir = dirname(SESSIONS_FILE);
31
+ const dir = dirname(sessionsFile());
28
32
  if (!existsSync(dir))
29
33
  mkdirSync(dir, { recursive: true });
30
34
  }
31
35
  export function appendSpawned(rec) {
32
36
  try {
33
37
  ensureDir();
34
- appendFileSync(SESSIONS_FILE, JSON.stringify({ t: 'spawned', ...rec }) + '\n', 'utf-8');
38
+ appendFileSync(sessionsFile(), JSON.stringify({ t: 'spawned', ...rec }) + '\n', 'utf-8');
35
39
  }
36
40
  catch {
37
41
  /* persistence is best-effort; never break the spawn path */
@@ -40,7 +44,7 @@ export function appendSpawned(rec) {
40
44
  export function appendCompleted(rec) {
41
45
  try {
42
46
  ensureDir();
43
- appendFileSync(SESSIONS_FILE, JSON.stringify({ t: 'completed', ...rec }) + '\n', 'utf-8');
47
+ appendFileSync(sessionsFile(), JSON.stringify({ t: 'completed', ...rec }) + '\n', 'utf-8');
44
48
  }
45
49
  catch {
46
50
  /* best-effort */
@@ -61,7 +65,7 @@ export function appendCompleted(rec) {
61
65
  export function appendFinalReport(rec) {
62
66
  try {
63
67
  ensureDir();
64
- appendFileSync(SESSIONS_FILE, JSON.stringify({ t: 'finalReport', ...rec }) + '\n', 'utf-8');
68
+ appendFileSync(sessionsFile(), JSON.stringify({ t: 'finalReport', ...rec }) + '\n', 'utf-8');
65
69
  }
66
70
  catch {
67
71
  /* best-effort */
@@ -76,20 +80,34 @@ export function appendFinalReport(rec) {
76
80
  * died, no artifacts), or (b) a concurrent daemon is mid-flight (not ours to
77
81
  * show). Either way, not useful in the UI.
78
82
  */
79
- export function loadRecent(limit = 20) {
80
- if (!existsSync(SESSIONS_FILE))
81
- return [];
83
+ /**
84
+ * v0.7 — load a single completed session by id. Returns undefined if no
85
+ * matching session exists or if the session is orphaned (spawned but never
86
+ * completed). Used by `codehere verify <session-id>` (F-03 S2) and the
87
+ * upcoming `codehere audit verify` CLI (F-05).
88
+ *
89
+ * Walks the whole file once. For 1k+ sessions this is fine; if it ever
90
+ * becomes hot we can keep an in-memory index.
91
+ *
92
+ * Accepts a short-id prefix too (first 7+ chars), mirroring git's UX.
93
+ * If the prefix is ambiguous we return the most recent match (newest-first
94
+ * traversal). Returning undefined on ambiguity would surprise users at
95
+ * the CLI · git's `--abbrev-commit` collisions are very rare in practice
96
+ * for our id space.
97
+ */
98
+ export function loadSessionById(idOrPrefix) {
99
+ const file = sessionsFile();
100
+ if (!existsSync(file))
101
+ return undefined;
82
102
  let content;
83
103
  try {
84
- content = readFileSync(SESSIONS_FILE, 'utf-8');
104
+ content = readFileSync(file, 'utf-8');
85
105
  }
86
106
  catch {
87
- return [];
107
+ return undefined;
88
108
  }
89
109
  const spawned = new Map();
90
110
  const completed = [];
91
- /** v0.6.x — Final Reports keyed by sessionId. Last write wins
92
- * (rare in practice; reports fire once per session). */
93
111
  const finalReports = new Map();
94
112
  for (const line of content.split('\n')) {
95
113
  const trimmed = line.trim();
@@ -109,40 +127,131 @@ export function loadRecent(limit = 20) {
109
127
  else if (rec.t === 'finalReport')
110
128
  finalReports.set(rec.id, rec);
111
129
  }
112
- const merged = [];
113
- // Walk completed in reverse so we return newest-first
114
- for (let i = completed.length - 1; i >= 0 && merged.length < limit; i--) {
130
+ // Newest-first to break prefix-collision ties toward the most recent
131
+ for (let i = completed.length - 1; i >= 0; i--) {
115
132
  const c = completed[i];
133
+ if (c.id !== idOrPrefix && !c.id.startsWith(idOrPrefix))
134
+ continue;
116
135
  const s = spawned.get(c.id);
117
136
  if (!s)
118
- continue; // orphan completed (shouldn't happen but harmless)
137
+ continue;
119
138
  const fr = finalReports.get(c.id);
120
- merged.push({
139
+ const failoverFrom = c.failoverFrom ?? s.failoverFrom;
140
+ const failoverTo = c.failoverTo ?? s.failoverTo;
141
+ const failoverReason = c.failoverReason ?? s.failoverReason;
142
+ return {
121
143
  sessionId: s.id,
122
144
  agent: s.agent,
123
145
  prompt: s.prompt,
124
146
  cwd: s.cwd,
125
147
  startedAt: s.startedAt,
126
- // BUG 2 (May 1 2026) — defensive normalization for zombie records.
127
- // Pre-fix sessions persisted before commit <THIS> were written with
128
- // status='running' even when their completed-record carried real
129
- // durationMs/completedAt — the conductor adapter mirrored its status
130
- // AFTER emitting the exit event. The fix at the source (conductor-
131
- // adapter.ts) prevents new zombies; this normalization repairs old
132
- // records on read so the UI doesn't show a "running" badge on a
133
- // session that obviously finished. We have no ground truth on what
134
- // the actual exit code WAS for these old records, so 'completed' is
135
- // the safest default — they'd been displayed as completed in the UI
136
- // had this been a session-manager.ts bug. Mark explicitly via a
137
- // synthetic note so debug viewers can tell normalization happened.
138
148
  status: c.status === 'running' && c.completedAt > 0 ? 'completed' : c.status,
139
149
  durationMs: c.durationMs,
140
150
  completedAt: c.completedAt,
141
151
  steps: c.steps,
142
152
  artifacts: c.artifacts,
153
+ ...(c.turns ? { turns: c.turns } : {}),
143
154
  ...(fr ? { finalReport: { summary: fr.summary, generatedAt: fr.generatedAt, durationMs: fr.durationMs } } : {}),
155
+ ...(failoverFrom ? { failoverFrom } : {}),
156
+ ...(failoverTo ? { failoverTo } : {}),
157
+ ...(failoverReason ? { failoverReason } : {}),
158
+ ...(s.failoverAt ? { failoverAt: s.failoverAt } : {}),
159
+ ...(c.stalledReason ? { stalledReason: c.stalledReason } : {}),
144
160
  provenance: 'persisted',
145
- });
161
+ };
162
+ }
163
+ return undefined;
164
+ }
165
+ export function loadRecent(limit = 20) {
166
+ const file = sessionsFile();
167
+ if (!existsSync(file))
168
+ return [];
169
+ let content;
170
+ try {
171
+ content = readFileSync(file, 'utf-8');
172
+ }
173
+ catch {
174
+ return [];
175
+ }
176
+ const spawned = new Map();
177
+ const completed = [];
178
+ /** v0.6.x — Final Reports keyed by sessionId. Last write wins
179
+ * (rare in practice; reports fire once per session). */
180
+ const finalReports = new Map();
181
+ for (const line of content.split('\n')) {
182
+ const trimmed = line.trim();
183
+ if (!trimmed)
184
+ continue;
185
+ let rec;
186
+ try {
187
+ rec = JSON.parse(trimmed);
188
+ }
189
+ catch {
190
+ continue;
191
+ }
192
+ if (rec.t === 'spawned')
193
+ spawned.set(rec.id, rec);
194
+ else if (rec.t === 'completed')
195
+ completed.push(rec);
196
+ else if (rec.t === 'finalReport')
197
+ finalReports.set(rec.id, rec);
198
+ }
199
+ const merged = [];
200
+ const includedIds = new Set();
201
+ const build = (c, s, fr) => ({
202
+ sessionId: s.id,
203
+ agent: s.agent,
204
+ prompt: s.prompt,
205
+ cwd: s.cwd,
206
+ startedAt: s.startedAt,
207
+ // BUG 2 (May 1 2026) — defensive normalization for zombie records.
208
+ // Pre-fix sessions persisted before commit <THIS> were written with
209
+ // status='running' even when their completed-record carried real
210
+ // durationMs/completedAt — the conductor adapter mirrored its status
211
+ // AFTER emitting the exit event. The fix at the source (conductor-
212
+ // adapter.ts) prevents new zombies; this normalization repairs old
213
+ // records on read so the UI doesn't show a "running" badge on a
214
+ // session that obviously finished. We have no ground truth on what
215
+ // the actual exit code WAS for these old records, so 'completed' is
216
+ // the safest default — they'd been displayed as completed in the UI
217
+ // had this been a session-manager.ts bug. Mark explicitly via a
218
+ // synthetic note so debug viewers can tell normalization happened.
219
+ status: c.status === 'running' && c.completedAt > 0 ? 'completed' : c.status,
220
+ durationMs: c.durationMs,
221
+ completedAt: c.completedAt,
222
+ steps: c.steps,
223
+ artifacts: c.artifacts,
224
+ ...(c.turns ? { turns: c.turns } : {}),
225
+ ...(fr ? { finalReport: { summary: fr.summary, generatedAt: fr.generatedAt, durationMs: fr.durationMs } } : {}),
226
+ ...((c.failoverFrom ?? s.failoverFrom) ? { failoverFrom: c.failoverFrom ?? s.failoverFrom } : {}),
227
+ ...((c.failoverTo ?? s.failoverTo) ? { failoverTo: c.failoverTo ?? s.failoverTo } : {}),
228
+ ...((c.failoverReason ?? s.failoverReason) ? { failoverReason: c.failoverReason ?? s.failoverReason } : {}),
229
+ ...(s.failoverAt ? { failoverAt: s.failoverAt } : {}),
230
+ ...(c.stalledReason ? { stalledReason: c.stalledReason } : {}),
231
+ provenance: 'persisted',
232
+ });
233
+ // Walk completed in reverse so we return newest-first, up to `limit`.
234
+ for (let i = completed.length - 1; i >= 0 && merged.length < limit; i--) {
235
+ const c = completed[i];
236
+ const s = spawned.get(c.id);
237
+ if (!s || includedIds.has(c.id))
238
+ continue; // orphan / dup (harmless)
239
+ merged.push(build(c, s, finalReports.get(c.id)));
240
+ includedIds.add(c.id);
241
+ }
242
+ // Always include seeded demo-* sessions even past the recent-N cap. They're
243
+ // pinned to FEATURED in the dashboard and are the fixtures /verify observes —
244
+ // dropping them off the list once you have >limit real runs is a bug (it's
245
+ // what made /verify report seeded fixtures as "not rendered"). Newest-first.
246
+ for (let i = completed.length - 1; i >= 0; i--) {
247
+ const c = completed[i];
248
+ if (!c.id.startsWith('demo-') || includedIds.has(c.id))
249
+ continue;
250
+ const s = spawned.get(c.id);
251
+ if (!s)
252
+ continue;
253
+ merged.push(build(c, s, finalReports.get(c.id)));
254
+ includedIds.add(c.id);
146
255
  }
147
256
  return merged;
148
257
  }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Thread-export key storage · v0.8 S4.
3
+ *
4
+ * Two responsibilities:
5
+ * 1. Per-machine Ed25519 keypair · generated on first export · persisted
6
+ * at ~/.codehere/keys/thread-signing.{pem,pub.pem}. Used to SIGN
7
+ * bundles. 0600 on private file.
8
+ * 2. Trusted-keys registry · ~/.codehere/keys/trusted.json maps base64
9
+ * public keys to { label, addedAt }. When importing a bundle whose
10
+ * key is in the trust store, replay proceeds silently. Otherwise the
11
+ * caller (CLI or web UI) prompts the user to trust + continue.
12
+ *
13
+ * Both files live ENTIRELY on the user's machine. Codehere never sends
14
+ * keys anywhere. The trust model is "users trust their friends' keys" ·
15
+ * same shape as SSH known_hosts.
16
+ */
17
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
18
+ import { homedir } from 'os';
19
+ import { join } from 'path';
20
+ import { generateKeypair, exportPrivatePem, exportPublicPem, importPrivatePem, importPublicPem, publicKeyToBase64, } from '../../application/use-cases/thread-export.js';
21
+ function keysDir() {
22
+ const home = process.env.CODEHERE_HOME || homedir();
23
+ return join(home, '.codehere', 'keys');
24
+ }
25
+ function privPath() {
26
+ return join(keysDir(), 'thread-signing.pem');
27
+ }
28
+ function pubPath() {
29
+ return join(keysDir(), 'thread-signing.pub.pem');
30
+ }
31
+ function trustedPath() {
32
+ return join(keysDir(), 'trusted.json');
33
+ }
34
+ /**
35
+ * Load (or generate) the local signing keypair. First call creates a new
36
+ * one and persists it; subsequent calls re-read from disk.
37
+ */
38
+ export function loadOrCreateKeypair() {
39
+ mkdirSync(keysDir(), { recursive: true });
40
+ if (existsSync(privPath()) && existsSync(pubPath())) {
41
+ const priv = readFileSync(privPath(), 'utf-8');
42
+ const pub = readFileSync(pubPath(), 'utf-8');
43
+ return {
44
+ privateKey: importPrivatePem(priv),
45
+ publicKey: importPublicPem(pub),
46
+ };
47
+ }
48
+ const fresh = generateKeypair();
49
+ writeFileSync(privPath(), exportPrivatePem(fresh.privateKey), 'utf-8');
50
+ writeFileSync(pubPath(), exportPublicPem(fresh.publicKey), 'utf-8');
51
+ try {
52
+ chmodSync(privPath(), 0o600);
53
+ }
54
+ catch { }
55
+ return fresh;
56
+ }
57
+ /**
58
+ * Return the local key's base64 string (used in bundle equality + UI display).
59
+ * Doesn't generate · only reads. Returns null if no key exists yet.
60
+ */
61
+ export function localPublicKeyBase64() {
62
+ if (!existsSync(pubPath()))
63
+ return null;
64
+ try {
65
+ const pub = readFileSync(pubPath(), 'utf-8');
66
+ return publicKeyToBase64(importPublicPem(pub));
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ export function loadTrustedKeys() {
73
+ const path = trustedPath();
74
+ const local = localPublicKeyBase64();
75
+ const seed = { schema: 1, keys: {} };
76
+ if (local)
77
+ seed.keys[local] = { label: 'this machine', addedAt: Date.now() };
78
+ if (!existsSync(path))
79
+ return seed;
80
+ try {
81
+ const raw = JSON.parse(readFileSync(path, 'utf-8'));
82
+ if (raw && typeof raw === 'object' && raw.schema === 1 && typeof raw.keys === 'object') {
83
+ // Always include the local key implicitly even if the trusted file
84
+ // doesn't list it (avoids "can't import my own export" if file is
85
+ // wiped or corrupted).
86
+ if (local && !raw.keys[local]) {
87
+ raw.keys[local] = { label: 'this machine', addedAt: Date.now() };
88
+ }
89
+ return raw;
90
+ }
91
+ }
92
+ catch {
93
+ /* fall through to seed */
94
+ }
95
+ return seed;
96
+ }
97
+ export function saveTrustedKeys(file) {
98
+ mkdirSync(keysDir(), { recursive: true });
99
+ writeFileSync(trustedPath(), JSON.stringify(file, null, 2) + '\n', 'utf-8');
100
+ try {
101
+ chmodSync(trustedPath(), 0o600);
102
+ }
103
+ catch { }
104
+ }
105
+ export function isKeyTrusted(publicKeyB64) {
106
+ return !!loadTrustedKeys().keys[publicKeyB64];
107
+ }
108
+ export function trustKey(publicKeyB64, label) {
109
+ const file = loadTrustedKeys();
110
+ file.keys[publicKeyB64] = { label, addedAt: Date.now() };
111
+ saveTrustedKeys(file);
112
+ }
113
+ export function untrustKey(publicKeyB64) {
114
+ const file = loadTrustedKeys();
115
+ const local = localPublicKeyBase64();
116
+ if (local && publicKeyB64 === local)
117
+ return false; // can't untrust own key
118
+ if (!file.keys[publicKeyB64])
119
+ return false;
120
+ delete file.keys[publicKeyB64];
121
+ saveTrustedKeys(file);
122
+ return true;
123
+ }
124
+ export function trustedKeySet() {
125
+ return new Set(Object.keys(loadTrustedKeys().keys));
126
+ }
127
+ //# sourceMappingURL=thread-key-store.js.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Write-pass store · v0.9 S6 part B · in-memory holding tank for pending
3
+ * write-passes. Lives only in the daemon process · cleared on restart by
4
+ * design. The audit chain is the durable record · this store just holds
5
+ * the intent between emission and decision so the UI can poll it.
6
+ *
7
+ * Scope decisions:
8
+ * - In-memory only. A pending write that survives a daemon restart is
9
+ * a half-thought · the agent should re-emit. Persistence would invite
10
+ * replay attacks (an old approval against a payload the user no longer
11
+ * remembers).
12
+ * - Per-id Map. O(1) get/set. No iteration ordering guarantees · the
13
+ * UI lists them by intent.emittedAt for display.
14
+ * - Status transitions go through applyDecision / applyResponse in
15
+ * write-pass.ts so the store stays dumb · it's just a typed Map.
16
+ */
17
+ const store = new Map();
18
+ /** Place a freshly-validated intent in the store as 'pending'. */
19
+ export function putPending(intent) {
20
+ const status = { kind: 'pending', intent };
21
+ store.set(intent.id, status);
22
+ return status;
23
+ }
24
+ export function getById(passId) {
25
+ return store.get(passId);
26
+ }
27
+ export function setStatus(status) {
28
+ store.set(status.intent.id, status);
29
+ }
30
+ export function listPending() {
31
+ return [...store.values()].filter((s) => s.kind === 'pending');
32
+ }
33
+ export function listForSession(sessionId) {
34
+ return [...store.values()]
35
+ .filter((s) => s.intent.sessionId === sessionId)
36
+ .sort((a, b) => a.intent.emittedAt - b.intent.emittedAt);
37
+ }
38
+ /** Drop a pass · for tests + the "discard" admin action. */
39
+ export function discard(passId) {
40
+ return store.delete(passId);
41
+ }
42
+ /** Test-only · clear the store between cases. */
43
+ export function _resetForTests() {
44
+ store.clear();
45
+ }
46
+ //# sourceMappingURL=write-pass-store.js.map
@@ -62,11 +62,10 @@ export function collectSessionMetadata() {
62
62
  }
63
63
  try {
64
64
  const content = readFileSync(SESSIONS_FILE, 'utf-8');
65
- const lines = content.split('\n').filter(l => l.trim().length > 0);
65
+ const lines = content.split('\n').filter((l) => l.trim().length > 0);
66
66
  // Parse records and pair spawned + completed
67
67
  const spawnedMap = new Map();
68
68
  const completedMap = new Map();
69
- const finalReportMap = new Map();
70
69
  for (const line of lines) {
71
70
  try {
72
71
  const record = JSON.parse(line);
@@ -76,9 +75,6 @@ export function collectSessionMetadata() {
76
75
  else if (record.t === 'completed') {
77
76
  completedMap.set(record.id, record);
78
77
  }
79
- else if (record.t === 'finalReport') {
80
- finalReportMap.set(record.id, record);
81
- }
82
78
  }
83
79
  catch {
84
80
  // Skip malformed lines
@@ -87,11 +83,14 @@ export function collectSessionMetadata() {
87
83
  const sessions = [];
88
84
  for (const [id, spawned] of spawnedMap.entries()) {
89
85
  const completed = completedMap.get(id);
90
- const finalReport = finalReportMap.get(id);
91
86
  const status = completed
92
- ? (completed.status === 'completed' ? 'done' : completed.status === 'failed' ? 'failed' : 'done')
87
+ ? completed.status === 'completed'
88
+ ? 'done'
89
+ : completed.status === 'failed'
90
+ ? 'failed'
91
+ : 'done'
93
92
  : 'running';
94
- const durationMs = completed?.durationMs ?? (Date.now() - spawned.startedAt);
93
+ const durationMs = completed?.durationMs ?? Date.now() - spawned.startedAt;
95
94
  const tools = completed?.steps?.length ?? 0;
96
95
  const files = completed?.artifacts?.filesChanged?.length ?? 0;
97
96
  const trustScore = extractTrustScore(completed?.artifacts);
@@ -151,7 +150,7 @@ export function startCloudSync(machineName) {
151
150
  console.error(` Cloud sync failed: ${res.status} ${body}`);
152
151
  }
153
152
  else {
154
- const data = await res.json();
153
+ const data = (await res.json());
155
154
  if (data.sessionsPushed && data.sessionsPushed > 0) {
156
155
  console.log(` Cloud sync: ${data.sessionsPushed} session(s) pushed`);
157
156
  }
@@ -166,7 +165,7 @@ export function startCloudSync(machineName) {
166
165
  const res = await fetch(`${CLOUD_URL}?action=pull-tasks&machineId=${machineId}`);
167
166
  if (!res.ok)
168
167
  return;
169
- const data = await res.json();
168
+ const data = (await res.json());
170
169
  const tasks = data.tasks || [];
171
170
  for (const task of tasks) {
172
171
  if (task.prompt) {
@@ -187,7 +186,7 @@ export function startCloudSync(machineName) {
187
186
  }
188
187
  }
189
188
  }
190
- catch (err) {
189
+ catch {
191
190
  // Silent fail for task pull — not critical
192
191
  }
193
192
  };