codehere 1.0.1 → 1.0.3
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/README.md +35 -193
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -105
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -147
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session persistence — survives daemon restart.
|
|
3
|
-
*
|
|
4
|
-
* Beta.3 dogfood surfaced a reliability gap: after `ch start` exited and was
|
|
5
|
-
* relaunched, the task UI was empty even when completed sessions had produced
|
|
6
|
-
* valuable final reports. The audit log (hook-events.jsonl) persisted every
|
|
7
|
-
* tool call but not the session-level PM state (prompt, artifacts, steps).
|
|
8
|
-
*
|
|
9
|
-
* This module adds a minimal two-record schema:
|
|
10
|
-
* { t: 'spawned', id, agent, prompt, cwd, startedAt }
|
|
11
|
-
* { t: 'completed', id, status, durationMs, steps, artifacts, completedAt }
|
|
12
|
-
*
|
|
13
|
-
* On spawn, we write the first line. On exit, we write the second. On daemon
|
|
14
|
-
* start, we read the last N pairs and surface them as read-only "persisted"
|
|
15
|
-
* sessions (no kill button, no live stream; the subprocess died with the
|
|
16
|
-
* previous daemon).
|
|
17
|
-
*
|
|
18
|
-
* Write path stays append-only — never rewrites history. Lock-free because
|
|
19
|
-
* single writer (the daemon). If two daemons ran concurrently the worst case
|
|
20
|
-
* is interleaved lines, which the reader tolerates.
|
|
21
|
-
*/
|
|
22
|
-
import { existsSync, mkdirSync, appendFileSync, readFileSync } from 'fs';
|
|
23
|
-
import { homedir } from 'os';
|
|
24
|
-
import { join, dirname } from 'path';
|
|
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
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* A session's records must all land in the SAME file: the one resolved when it
|
|
32
|
-
* was spawned. sessionsFile() reads env per-call, so a write that fires after
|
|
33
|
-
* the environment moved on (async exit handlers; test suites that swap
|
|
34
|
-
* CODEHERE_HOME per test) would otherwise scatter a session across homes —
|
|
35
|
-
* the failover-skip flake forensics caught exactly this shape (a slow
|
|
36
|
-
* real-claude exit record from another test's home, spawned-record absent —
|
|
37
|
-
* full record in commit message). Spawn binds the
|
|
38
|
-
* path; completed/finalReport reuse it. FIFO-capped — eviction only means a
|
|
39
|
-
* later write falls back to the env-resolved path (prod-identical: the
|
|
40
|
-
* daemon's home never changes mid-run).
|
|
41
|
-
*/
|
|
42
|
-
const sessionFileById = new Map();
|
|
43
|
-
const SESSION_FILE_CAP = 500;
|
|
44
|
-
function bindSessionFile(id) {
|
|
45
|
-
const file = sessionsFile();
|
|
46
|
-
if (sessionFileById.size >= SESSION_FILE_CAP) {
|
|
47
|
-
const oldest = sessionFileById.keys().next().value;
|
|
48
|
-
if (oldest !== undefined)
|
|
49
|
-
sessionFileById.delete(oldest);
|
|
50
|
-
}
|
|
51
|
-
sessionFileById.set(id, file);
|
|
52
|
-
return file;
|
|
53
|
-
}
|
|
54
|
-
function fileForSession(id) {
|
|
55
|
-
return sessionFileById.get(id) ?? sessionsFile();
|
|
56
|
-
}
|
|
57
|
-
function ensureDirFor(file) {
|
|
58
|
-
const dir = dirname(file);
|
|
59
|
-
if (!existsSync(dir))
|
|
60
|
-
mkdirSync(dir, { recursive: true });
|
|
61
|
-
}
|
|
62
|
-
export function appendSpawned(rec) {
|
|
63
|
-
try {
|
|
64
|
-
const file = bindSessionFile(rec.id);
|
|
65
|
-
ensureDirFor(file);
|
|
66
|
-
// S18 · practice is a property of the RUN, stamped once on the durable
|
|
67
|
-
// record at spawn — never re-derived from a later daemon's env. Seeded
|
|
68
|
-
// demo-* showcases keep their FEATURED meaning even if seeded under mock.
|
|
69
|
-
const mockDaemon = process.env.CODEHERE_MOCK === '1' || process.env.CODEHERE_MOCK === 'true';
|
|
70
|
-
const mockRun = mockDaemon && !rec.id.startsWith('demo-');
|
|
71
|
-
appendFileSync(file, JSON.stringify({ t: 'spawned', ...rec, mockRun }) + '\n', 'utf-8');
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
/* persistence is best-effort; never break the spawn path */
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
export function appendCompleted(rec) {
|
|
78
|
-
try {
|
|
79
|
-
const file = fileForSession(rec.id);
|
|
80
|
-
ensureDirFor(file);
|
|
81
|
-
appendFileSync(file, JSON.stringify({ t: 'completed', ...rec }) + '\n', 'utf-8');
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
/* best-effort */
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* v0.6.x — persist a Final Report so it survives daemon restart.
|
|
89
|
-
*
|
|
90
|
-
* Pre-fix: Final Reports lived only in SessionManager.finalReportBySession
|
|
91
|
-
* (in-memory Map) and were lost on every daemon bounce. Founder hit this
|
|
92
|
-
* 2026-05-03 after a Mac restart: completed sessions showed
|
|
93
|
-
* "Generating report…" forever because there was no on-disk record + no
|
|
94
|
-
* "report-unavailable" state in the UI.
|
|
95
|
-
*
|
|
96
|
-
* Append-only: Final Report fires AFTER the CompletedRecord (generateFinalReport
|
|
97
|
-
* is async post-exit), so it gets its own record type. Reader merges by id.
|
|
98
|
-
*/
|
|
99
|
-
/** S3 · persist the human's "mark resolved" ack so it sticks across reload.
|
|
100
|
-
* Append-only like every other record; reader merges by id (last wins). */
|
|
101
|
-
export function appendResolved(rec) {
|
|
102
|
-
try {
|
|
103
|
-
const file = fileForSession(rec.id);
|
|
104
|
-
ensureDirFor(file);
|
|
105
|
-
appendFileSync(file, JSON.stringify({ t: 'resolved', ...rec }) + '\n', 'utf-8');
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
/* best-effort */
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
export function appendFinalReport(rec) {
|
|
112
|
-
try {
|
|
113
|
-
const file = fileForSession(rec.id);
|
|
114
|
-
ensureDirFor(file);
|
|
115
|
-
appendFileSync(file, JSON.stringify({ t: 'finalReport', ...rec }) + '\n', 'utf-8');
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
/* best-effort */
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/** Tier 2.2 · persist the audit↔disk reconciliation so the OBSERVED signal
|
|
122
|
-
* survives a daemon restart (mirrors appendFinalReport's async-post-exit shape). */
|
|
123
|
-
export function appendAuditRecon(id, recon) {
|
|
124
|
-
try {
|
|
125
|
-
const file = fileForSession(id);
|
|
126
|
-
ensureDirFor(file);
|
|
127
|
-
appendFileSync(file, JSON.stringify({ t: 'audit-recon', id, recon }) + '\n', 'utf-8');
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
/* best-effort */
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Walk the session log backward, pair spawned+completed records by id, return
|
|
135
|
-
* the most recent `limit` completed pairs sorted newest-first.
|
|
136
|
-
*
|
|
137
|
-
* We only surface fully-completed sessions because an orphaned `spawned` with
|
|
138
|
-
* no `completed` means either: (a) the daemon died mid-task (subprocess also
|
|
139
|
-
* died, no artifacts), or (b) a concurrent daemon is mid-flight (not ours to
|
|
140
|
-
* show). Either way, not useful in the UI.
|
|
141
|
-
*/
|
|
142
|
-
/**
|
|
143
|
-
* v0.7 — load a single completed session by id. Returns undefined if no
|
|
144
|
-
* matching session exists or if the session is orphaned (spawned but never
|
|
145
|
-
* completed). Used by `codehere verify <session-id>` (F-03 S2) and the
|
|
146
|
-
* upcoming `codehere audit verify` CLI (F-05).
|
|
147
|
-
*
|
|
148
|
-
* Walks the whole file once. For 1k+ sessions this is fine; if it ever
|
|
149
|
-
* becomes hot we can keep an in-memory index.
|
|
150
|
-
*
|
|
151
|
-
* Accepts a short-id prefix too (first 7+ chars), mirroring git's UX.
|
|
152
|
-
* If the prefix is ambiguous we return the most recent match (newest-first
|
|
153
|
-
* traversal). Returning undefined on ambiguity would surprise users at
|
|
154
|
-
* the CLI · git's `--abbrev-commit` collisions are very rare in practice
|
|
155
|
-
* for our id space.
|
|
156
|
-
*/
|
|
157
|
-
/** S18 · classification for the joined record. The run-time stamp wins. For
|
|
158
|
-
* LEGACY records written before the stamp existed, the mock adapter leaves
|
|
159
|
-
* two durable on-record signals, and either proves the run was practice:
|
|
160
|
-
* (1) it mints its own resume tokens "mock-…" (mock-agent-repl.ts) — a real
|
|
161
|
-
* vendor CLI never does; (2) its ONLY edit artifact is the hardcoded,
|
|
162
|
-
* gitignored `codehere-mock-change.md` (MOCK_EDIT_FILE) — conductor practice
|
|
163
|
-
* runs carry no resume token but their steps touched it. Classified on read
|
|
164
|
-
* so pre-stamp practice never renders as the founder's real work. demo-*
|
|
165
|
-
* showcases keep their FEATURED meaning. Records with neither stamp nor
|
|
166
|
-
* signal stay undefined (the reader's env fallback — genuinely no signal). */
|
|
167
|
-
function recordedMockRun(s, c) {
|
|
168
|
-
if (s.mockRun !== undefined)
|
|
169
|
-
return s.mockRun;
|
|
170
|
-
if (s.id.startsWith('demo-'))
|
|
171
|
-
return undefined;
|
|
172
|
-
if (c.nativeSessionId?.startsWith('mock-'))
|
|
173
|
-
return true;
|
|
174
|
-
if (c.steps?.some((st) => st.label?.includes('codehere-mock-change.md')))
|
|
175
|
-
return true;
|
|
176
|
-
return undefined;
|
|
177
|
-
}
|
|
178
|
-
export function loadSessionById(idOrPrefix) {
|
|
179
|
-
const file = sessionsFile();
|
|
180
|
-
if (!existsSync(file))
|
|
181
|
-
return undefined;
|
|
182
|
-
let content;
|
|
183
|
-
try {
|
|
184
|
-
content = readFileSync(file, 'utf-8');
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
return undefined;
|
|
188
|
-
}
|
|
189
|
-
const spawned = new Map();
|
|
190
|
-
const completed = [];
|
|
191
|
-
const finalReports = new Map();
|
|
192
|
-
const resolved = new Map();
|
|
193
|
-
const auditRecons = new Map();
|
|
194
|
-
for (const line of content.split('\n')) {
|
|
195
|
-
const trimmed = line.trim();
|
|
196
|
-
if (!trimmed)
|
|
197
|
-
continue;
|
|
198
|
-
let rec;
|
|
199
|
-
try {
|
|
200
|
-
rec = JSON.parse(trimmed);
|
|
201
|
-
}
|
|
202
|
-
catch {
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
if (rec.t === 'spawned')
|
|
206
|
-
spawned.set(rec.id, rec);
|
|
207
|
-
else if (rec.t === 'completed')
|
|
208
|
-
completed.push(rec);
|
|
209
|
-
else if (rec.t === 'finalReport')
|
|
210
|
-
finalReports.set(rec.id, rec);
|
|
211
|
-
else if (rec.t === 'resolved')
|
|
212
|
-
resolved.set(rec.id, rec.resolvedAt);
|
|
213
|
-
else if (rec.t === 'audit-recon')
|
|
214
|
-
auditRecons.set(rec.id, rec.recon);
|
|
215
|
-
}
|
|
216
|
-
// Newest-first to break prefix-collision ties toward the most recent
|
|
217
|
-
for (let i = completed.length - 1; i >= 0; i--) {
|
|
218
|
-
const c = completed[i];
|
|
219
|
-
if (c.id !== idOrPrefix && !c.id.startsWith(idOrPrefix))
|
|
220
|
-
continue;
|
|
221
|
-
const s = spawned.get(c.id);
|
|
222
|
-
if (!s)
|
|
223
|
-
continue;
|
|
224
|
-
const fr = finalReports.get(c.id);
|
|
225
|
-
const mockRun = recordedMockRun(s, c);
|
|
226
|
-
const failoverFrom = c.failoverFrom ?? s.failoverFrom;
|
|
227
|
-
const failoverTo = c.failoverTo ?? s.failoverTo;
|
|
228
|
-
const failoverReason = c.failoverReason ?? s.failoverReason;
|
|
229
|
-
return {
|
|
230
|
-
sessionId: s.id,
|
|
231
|
-
agent: s.agent,
|
|
232
|
-
prompt: s.prompt,
|
|
233
|
-
cwd: s.cwd,
|
|
234
|
-
startedAt: s.startedAt,
|
|
235
|
-
status: c.status === 'running' && c.completedAt > 0 ? 'completed' : c.status,
|
|
236
|
-
durationMs: c.durationMs,
|
|
237
|
-
completedAt: c.completedAt,
|
|
238
|
-
steps: c.steps,
|
|
239
|
-
artifacts: c.artifacts,
|
|
240
|
-
...(c.turns ? { turns: c.turns } : {}),
|
|
241
|
-
...(fr
|
|
242
|
-
? {
|
|
243
|
-
finalReport: {
|
|
244
|
-
summary: fr.summary,
|
|
245
|
-
generatedAt: fr.generatedAt,
|
|
246
|
-
durationMs: fr.durationMs,
|
|
247
|
-
source: fr.source,
|
|
248
|
-
},
|
|
249
|
-
}
|
|
250
|
-
: {}),
|
|
251
|
-
...(failoverFrom ? { failoverFrom } : {}),
|
|
252
|
-
...(failoverTo ? { failoverTo } : {}),
|
|
253
|
-
...(failoverReason ? { failoverReason } : {}),
|
|
254
|
-
...(s.failoverAt ? { failoverAt: s.failoverAt } : {}),
|
|
255
|
-
...(c.stalledReason ? { stalledReason: c.stalledReason } : {}),
|
|
256
|
-
...(s.batchId ? { batchId: s.batchId } : {}),
|
|
257
|
-
...(c.delegatedTo ? { delegatedTo: c.delegatedTo } : {}),
|
|
258
|
-
...(c.nativeSessionId ? { nativeSessionId: c.nativeSessionId } : {}),
|
|
259
|
-
...(c.delegateSessionId ? { delegateSessionId: c.delegateSessionId } : {}),
|
|
260
|
-
...(auditRecons.get(c.id) ? { auditRecon: auditRecons.get(c.id) } : {}),
|
|
261
|
-
...(s.importedFrom ? { importedFrom: s.importedFrom } : {}),
|
|
262
|
-
...(s.importAudit ? { importAudit: s.importAudit } : {}),
|
|
263
|
-
...(s.parentSessionId ? { parentSessionId: s.parentSessionId } : {}),
|
|
264
|
-
...(s.isReview ? { isReview: true } : {}),
|
|
265
|
-
...(resolved.has(c.id) ? { resolvedAt: resolved.get(c.id) } : {}),
|
|
266
|
-
...(mockRun !== undefined ? { mockRun } : {}),
|
|
267
|
-
provenance: 'persisted',
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
return undefined;
|
|
271
|
-
}
|
|
272
|
-
export function loadRecent(limit = 20) {
|
|
273
|
-
const file = sessionsFile();
|
|
274
|
-
if (!existsSync(file))
|
|
275
|
-
return [];
|
|
276
|
-
let content;
|
|
277
|
-
try {
|
|
278
|
-
content = readFileSync(file, 'utf-8');
|
|
279
|
-
}
|
|
280
|
-
catch {
|
|
281
|
-
return [];
|
|
282
|
-
}
|
|
283
|
-
const spawned = new Map();
|
|
284
|
-
const completed = [];
|
|
285
|
-
/** v0.6.x — Final Reports keyed by sessionId. Last write wins
|
|
286
|
-
* (rare in practice; reports fire once per session). */
|
|
287
|
-
const finalReports = new Map();
|
|
288
|
-
const resolved = new Map();
|
|
289
|
-
const auditRecons = new Map();
|
|
290
|
-
for (const line of content.split('\n')) {
|
|
291
|
-
const trimmed = line.trim();
|
|
292
|
-
if (!trimmed)
|
|
293
|
-
continue;
|
|
294
|
-
let rec;
|
|
295
|
-
try {
|
|
296
|
-
rec = JSON.parse(trimmed);
|
|
297
|
-
}
|
|
298
|
-
catch {
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
if (rec.t === 'spawned')
|
|
302
|
-
spawned.set(rec.id, rec);
|
|
303
|
-
else if (rec.t === 'completed')
|
|
304
|
-
completed.push(rec);
|
|
305
|
-
else if (rec.t === 'finalReport')
|
|
306
|
-
finalReports.set(rec.id, rec);
|
|
307
|
-
else if (rec.t === 'resolved')
|
|
308
|
-
resolved.set(rec.id, rec.resolvedAt);
|
|
309
|
-
else if (rec.t === 'audit-recon')
|
|
310
|
-
auditRecons.set(rec.id, rec.recon);
|
|
311
|
-
}
|
|
312
|
-
const merged = [];
|
|
313
|
-
const includedIds = new Set();
|
|
314
|
-
const build = (c, s, fr) => {
|
|
315
|
-
const mockRun = recordedMockRun(s, c);
|
|
316
|
-
return {
|
|
317
|
-
sessionId: s.id,
|
|
318
|
-
agent: s.agent,
|
|
319
|
-
prompt: s.prompt,
|
|
320
|
-
cwd: s.cwd,
|
|
321
|
-
startedAt: s.startedAt,
|
|
322
|
-
// BUG 2 (May 1 2026) — defensive normalization for zombie records.
|
|
323
|
-
// Pre-fix sessions persisted before commit <THIS> were written with
|
|
324
|
-
// status='running' even when their completed-record carried real
|
|
325
|
-
// durationMs/completedAt — the conductor adapter mirrored its status
|
|
326
|
-
// AFTER emitting the exit event. The fix at the source (conductor-
|
|
327
|
-
// adapter.ts) prevents new zombies; this normalization repairs old
|
|
328
|
-
// records on read so the UI doesn't show a "running" badge on a
|
|
329
|
-
// session that obviously finished. We have no ground truth on what
|
|
330
|
-
// the actual exit code WAS for these old records, so 'completed' is
|
|
331
|
-
// the safest default — they'd been displayed as completed in the UI
|
|
332
|
-
// had this been a session-manager.ts bug. Mark explicitly via a
|
|
333
|
-
// synthetic note so debug viewers can tell normalization happened.
|
|
334
|
-
status: c.status === 'running' && c.completedAt > 0 ? 'completed' : c.status,
|
|
335
|
-
durationMs: c.durationMs,
|
|
336
|
-
completedAt: c.completedAt,
|
|
337
|
-
steps: c.steps,
|
|
338
|
-
artifacts: c.artifacts,
|
|
339
|
-
...(c.turns ? { turns: c.turns } : {}),
|
|
340
|
-
...(fr
|
|
341
|
-
? {
|
|
342
|
-
finalReport: {
|
|
343
|
-
summary: fr.summary,
|
|
344
|
-
generatedAt: fr.generatedAt,
|
|
345
|
-
durationMs: fr.durationMs,
|
|
346
|
-
source: fr.source,
|
|
347
|
-
},
|
|
348
|
-
}
|
|
349
|
-
: {}),
|
|
350
|
-
...((c.failoverFrom ?? s.failoverFrom) ? { failoverFrom: c.failoverFrom ?? s.failoverFrom } : {}),
|
|
351
|
-
...((c.failoverTo ?? s.failoverTo) ? { failoverTo: c.failoverTo ?? s.failoverTo } : {}),
|
|
352
|
-
...((c.failoverReason ?? s.failoverReason) ? { failoverReason: c.failoverReason ?? s.failoverReason } : {}),
|
|
353
|
-
...(s.failoverAt ? { failoverAt: s.failoverAt } : {}),
|
|
354
|
-
...(c.stalledReason ? { stalledReason: c.stalledReason } : {}),
|
|
355
|
-
...(s.batchId ? { batchId: s.batchId } : {}),
|
|
356
|
-
...(c.delegatedTo ? { delegatedTo: c.delegatedTo } : {}),
|
|
357
|
-
...(c.nativeSessionId ? { nativeSessionId: c.nativeSessionId } : {}),
|
|
358
|
-
...(c.delegateSessionId ? { delegateSessionId: c.delegateSessionId } : {}),
|
|
359
|
-
...(auditRecons.get(c.id) ? { auditRecon: auditRecons.get(c.id) } : {}),
|
|
360
|
-
...(s.importedFrom ? { importedFrom: s.importedFrom } : {}),
|
|
361
|
-
...(s.importAudit ? { importAudit: s.importAudit } : {}),
|
|
362
|
-
...(s.parentSessionId ? { parentSessionId: s.parentSessionId } : {}),
|
|
363
|
-
...(s.isReview ? { isReview: true } : {}),
|
|
364
|
-
...(resolved.has(c.id) ? { resolvedAt: resolved.get(c.id) } : {}),
|
|
365
|
-
...(mockRun !== undefined ? { mockRun } : {}),
|
|
366
|
-
provenance: 'persisted',
|
|
367
|
-
};
|
|
368
|
-
};
|
|
369
|
-
// Walk completed in reverse so we return newest-first, up to `limit`.
|
|
370
|
-
for (let i = completed.length - 1; i >= 0 && merged.length < limit; i--) {
|
|
371
|
-
const c = completed[i];
|
|
372
|
-
const s = spawned.get(c.id);
|
|
373
|
-
if (!s || includedIds.has(c.id))
|
|
374
|
-
continue; // orphan / dup (harmless)
|
|
375
|
-
merged.push(build(c, s, finalReports.get(c.id)));
|
|
376
|
-
includedIds.add(c.id);
|
|
377
|
-
}
|
|
378
|
-
// Always include seeded demo-* sessions even past the recent-N cap. They're
|
|
379
|
-
// pinned to FEATURED in the dashboard and are the fixtures /verify observes —
|
|
380
|
-
// dropping them off the list once you have >limit real runs is a bug (it's
|
|
381
|
-
// what made /verify report seeded fixtures as "not rendered"). Newest-first.
|
|
382
|
-
for (let i = completed.length - 1; i >= 0; i--) {
|
|
383
|
-
const c = completed[i];
|
|
384
|
-
if (!c.id.startsWith('demo-') || includedIds.has(c.id))
|
|
385
|
-
continue;
|
|
386
|
-
const s = spawned.get(c.id);
|
|
387
|
-
if (!s)
|
|
388
|
-
continue;
|
|
389
|
-
merged.push(build(c, s, finalReports.get(c.id)));
|
|
390
|
-
includedIds.add(c.id);
|
|
391
|
-
}
|
|
392
|
-
return merged;
|
|
393
|
-
}
|
|
394
|
-
//# sourceMappingURL=session-persistence.js.map
|
|
@@ -1,127 +0,0 @@
|
|
|
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
|
-
export 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
|
|
@@ -1,38 +0,0 @@
|
|
|
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
|
-
//# sourceMappingURL=write-pass-store.js.map
|