throughline 0.3.23 → 0.3.25

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 (111) hide show
  1. package/.claude/commands/tl-trim.md +42 -0
  2. package/.codex-sidecar.yml +62 -0
  3. package/CHANGELOG.md +583 -0
  4. package/README.ja.md +42 -5
  5. package/README.md +400 -23
  6. package/bin/throughline.mjs +168 -4
  7. package/codex/skills/throughline/SKILL.md +157 -0
  8. package/codex/skills/throughline/agents/openai.yaml +7 -0
  9. package/docs/INHERITANCE_ON_CLEAR_ONLY.md +146 -0
  10. package/docs/L1_L2_L3_REDESIGN.md +415 -0
  11. package/docs/PUBLIC_RELEASE_PLAN.md +184 -0
  12. package/docs/THROUGHLINE_CODEX_DUAL_SUPPORT.md +249 -0
  13. package/docs/THROUGHLINE_CODEX_FIRST_ROADMAP.md +555 -0
  14. package/docs/THROUGHLINE_CODEX_MONITOR_IMPLEMENTATION_PLAN.md +220 -0
  15. package/docs/THROUGHLINE_CODEX_TRIM_IMPLEMENTATION_PLAN.md +528 -0
  16. package/docs/THROUGHLINE_CODEX_TRIM_ROLLBACK_FIX_PLAN.md +672 -0
  17. package/docs/archive/CONCEPT.md +476 -0
  18. package/docs/archive/EXPERIMENT.md +371 -0
  19. package/docs/archive/README.md +22 -0
  20. package/docs/archive/SESSION_LINKING_DESIGN.md +231 -0
  21. package/docs/archive/THROUGHLINE_NEXT_STEPS.md +134 -0
  22. package/docs/throughline-codex-trim-rollback-incident-report.md +306 -0
  23. package/docs/throughline-handoff-context.example.json +57 -0
  24. package/docs/throughline-rollback-context-trim-insight.md +455 -0
  25. package/package.json +6 -2
  26. package/src/cli/codex-capture.mjs +95 -0
  27. package/src/cli/codex-handoff-model-smoke.mjs +292 -0
  28. package/src/cli/codex-handoff-model-smoke.test.mjs +262 -0
  29. package/src/cli/codex-handoff-smoke.mjs +163 -0
  30. package/src/cli/codex-handoff-smoke.test.mjs +149 -0
  31. package/src/cli/codex-handoff-start.mjs +291 -0
  32. package/src/cli/codex-handoff-start.test.mjs +194 -0
  33. package/src/cli/codex-hook.mjs +276 -0
  34. package/src/cli/codex-hook.test.mjs +293 -0
  35. package/src/cli/codex-host-primitive-audit.mjs +110 -0
  36. package/src/cli/codex-host-primitive-audit.test.mjs +75 -0
  37. package/src/cli/codex-restore-smoke.mjs +357 -0
  38. package/src/cli/codex-restore-source-audit.mjs +304 -0
  39. package/src/cli/codex-resume.mjs +138 -0
  40. package/src/cli/codex-rollback-model-visible-smoke.mjs +373 -0
  41. package/src/cli/codex-rollback-model-visible-smoke.test.mjs +255 -0
  42. package/src/cli/codex-sidecar-diagnostics.mjs +48 -0
  43. package/src/cli/codex-sidecar-dry-run.mjs +85 -0
  44. package/src/cli/codex-summarize.mjs +224 -0
  45. package/src/cli/codex-threads.mjs +89 -0
  46. package/src/cli/codex-visibility-smoke.mjs +196 -0
  47. package/src/cli/codex-vscode-restore-smoke.mjs +226 -0
  48. package/src/cli/codex-vscode-rollback-smoke.mjs +114 -0
  49. package/src/cli/doctor.mjs +503 -1
  50. package/src/cli/doctor.test.mjs +542 -3
  51. package/src/cli/handoff-preview.mjs +78 -0
  52. package/src/cli/help.test.mjs +64 -0
  53. package/src/cli/install.mjs +227 -4
  54. package/src/cli/install.test.mjs +207 -4
  55. package/src/cli/trim.mjs +564 -0
  56. package/src/codex-app-server.mjs +1816 -0
  57. package/src/codex-app-server.test.mjs +512 -0
  58. package/src/codex-auto-refresh.mjs +194 -0
  59. package/src/codex-auto-refresh.test.mjs +182 -0
  60. package/src/codex-capture.mjs +235 -0
  61. package/src/codex-capture.test.mjs +393 -0
  62. package/src/codex-handoff-model-smoke.mjs +114 -0
  63. package/src/codex-handoff-model-smoke.test.mjs +89 -0
  64. package/src/codex-handoff-smoke.mjs +124 -0
  65. package/src/codex-handoff-smoke.test.mjs +103 -0
  66. package/src/codex-handoff.mjs +331 -0
  67. package/src/codex-handoff.test.mjs +220 -0
  68. package/src/codex-host-primitive-audit.mjs +374 -0
  69. package/src/codex-host-primitive-audit.test.mjs +208 -0
  70. package/src/codex-restore-smoke.test.mjs +639 -0
  71. package/src/codex-restore-source-audit.mjs +1348 -0
  72. package/src/codex-restore-source-audit.test.mjs +623 -0
  73. package/src/codex-resume.test.mjs +242 -0
  74. package/src/codex-rollout-memory.mjs +711 -0
  75. package/src/codex-rollout-memory.test.mjs +610 -0
  76. package/src/codex-sidecar-cli.test.mjs +75 -0
  77. package/src/codex-sidecar.mjs +246 -0
  78. package/src/codex-sidecar.test.mjs +172 -0
  79. package/src/codex-summarize.test.mjs +143 -0
  80. package/src/codex-thread-identity.mjs +23 -0
  81. package/src/codex-thread-index.mjs +173 -0
  82. package/src/codex-thread-index.test.mjs +164 -0
  83. package/src/codex-usage.mjs +110 -0
  84. package/src/codex-usage.test.mjs +140 -0
  85. package/src/codex-visibility-smoke.test.mjs +222 -0
  86. package/src/codex-vscode-restore-smoke.mjs +206 -0
  87. package/src/codex-vscode-restore-smoke.test.mjs +325 -0
  88. package/src/codex-vscode-rollback-smoke.mjs +90 -0
  89. package/src/codex-vscode-rollback-smoke.test.mjs +290 -0
  90. package/src/db-schema.test.mjs +97 -0
  91. package/src/haiku-summarizer.mjs +267 -26
  92. package/src/haiku-summarizer.test.mjs +282 -0
  93. package/src/handoff-preview.test.mjs +108 -0
  94. package/src/handoff-record.mjs +294 -0
  95. package/src/handoff-record.test.mjs +226 -0
  96. package/src/hook-entrypoints.test.mjs +326 -0
  97. package/src/package-files.test.mjs +19 -0
  98. package/src/prompt-submit.mjs +9 -6
  99. package/src/resume-context.mjs +44 -140
  100. package/src/resume-context.test.mjs +172 -0
  101. package/src/session-start.mjs +8 -5
  102. package/src/state-file.mjs +50 -6
  103. package/src/state-file.test.mjs +50 -0
  104. package/src/token-monitor.mjs +14 -10
  105. package/src/token-monitor.test.mjs +27 -0
  106. package/src/trim-cli.test.mjs +1584 -0
  107. package/src/trim-model.mjs +584 -0
  108. package/src/trim-model.test.mjs +568 -0
  109. package/src/turn-processor.mjs +17 -10
  110. package/src/vscode-task.mjs +94 -6
  111. package/src/vscode-task.test.mjs +186 -6
@@ -0,0 +1,196 @@
1
+ import { randomUUID } from 'node:crypto';
2
+
3
+ import { getDb } from '../db.mjs';
4
+ import { buildHandoffRecord } from '../handoff-record.mjs';
5
+ import { renderCodexActiveWorkContext } from '../codex-handoff.mjs';
6
+ import { runCodexModelVisibilitySmoke } from '../codex-app-server.mjs';
7
+
8
+ async function readStdin() {
9
+ let raw = '';
10
+ await new Promise((resolve) => {
11
+ process.stdin.setEncoding('utf8');
12
+ process.stdin.on('data', (chunk) => {
13
+ raw += chunk;
14
+ });
15
+ process.stdin.on('end', resolve);
16
+ });
17
+ return raw;
18
+ }
19
+
20
+ function parseArgs(args) {
21
+ const out = {
22
+ sessionId: null,
23
+ codexThreadId: null,
24
+ marker: `TL_CODEX_VISIBLE_${randomUUID().slice(0, 8)}`,
25
+ json: false,
26
+ codexAppServerBin: null,
27
+ timeoutMs: 180_000,
28
+ requestTimeoutMs: 150_000,
29
+ memoStdin: false,
30
+ resumeAfterInject: false,
31
+ };
32
+
33
+ for (let i = 0; i < args.length; i++) {
34
+ const arg = args[i];
35
+ if (arg === '--session') {
36
+ const value = args[++i];
37
+ if (!value || value.startsWith('-')) {
38
+ throw new Error('--session requires a session id');
39
+ }
40
+ out.sessionId = value;
41
+ } else if (arg === '--codex-thread-id') {
42
+ const value = args[++i];
43
+ if (!value || value.startsWith('-')) {
44
+ throw new Error('--codex-thread-id requires a thread id');
45
+ }
46
+ out.codexThreadId = value;
47
+ } else if (arg === '--marker') {
48
+ const value = args[++i];
49
+ if (!value || value.startsWith('-')) {
50
+ throw new Error('--marker requires a marker string');
51
+ }
52
+ out.marker = value;
53
+ } else if (arg === '--codex-app-server-bin') {
54
+ const value = args[++i];
55
+ if (!value || value.startsWith('-')) {
56
+ throw new Error('--codex-app-server-bin requires a command path');
57
+ }
58
+ out.codexAppServerBin = value;
59
+ } else if (arg === '--timeout-ms') {
60
+ const value = Number(args[++i]);
61
+ if (!Number.isInteger(value) || value < 1) {
62
+ throw new Error('--timeout-ms must be a positive integer');
63
+ }
64
+ out.timeoutMs = value;
65
+ } else if (arg === '--request-timeout-ms') {
66
+ const value = Number(args[++i]);
67
+ if (!Number.isInteger(value) || value < 1) {
68
+ throw new Error('--request-timeout-ms must be a positive integer');
69
+ }
70
+ out.requestTimeoutMs = value;
71
+ } else if (arg === '--memo-stdin') {
72
+ out.memoStdin = true;
73
+ } else if (arg === '--resume-after-inject') {
74
+ out.resumeAfterInject = true;
75
+ } else if (arg === '--json') {
76
+ out.json = true;
77
+ } else if (!arg.startsWith('-') && !out.sessionId) {
78
+ out.sessionId = arg;
79
+ } else {
80
+ throw new Error(`unknown argument: ${arg}`);
81
+ }
82
+ }
83
+
84
+ if (!out.sessionId && out.codexThreadId) {
85
+ out.sessionId = `codex:${out.codexThreadId}`;
86
+ }
87
+ if (!out.codexThreadId && out.sessionId?.startsWith('codex:')) {
88
+ out.codexThreadId = out.sessionId.slice('codex:'.length);
89
+ }
90
+
91
+ return out;
92
+ }
93
+
94
+ function findLatestCodexSessionId(db, projectPath) {
95
+ const row = db
96
+ .prepare(
97
+ `SELECT session_id
98
+ FROM sessions
99
+ WHERE lower(project_path) = lower(?)
100
+ AND session_id LIKE 'codex:%'
101
+ ORDER BY updated_at DESC
102
+ LIMIT 1`,
103
+ )
104
+ .get(projectPath);
105
+ return row?.session_id ?? null;
106
+ }
107
+
108
+ function renderTextResult(result) {
109
+ const lines = [];
110
+ lines.push('throughline codex visibility smoke');
111
+ lines.push('');
112
+ lines.push(` status: ${result.status}`);
113
+ lines.push(` reason: ${result.reason}`);
114
+ lines.push(` thread: ${result.threadId}`);
115
+ lines.push(` marker: ${result.marker}`);
116
+ lines.push(` readTurns: ${result.readTurns ?? 'unknown'}`);
117
+ lines.push(` resumedTurns: ${result.resumedTurns ?? 'unknown'}`);
118
+ if (result.resumeAfterInject) {
119
+ lines.push(` postInjectResumeTurns: ${result.postInjectResumedTurns ?? 'unknown'}`);
120
+ }
121
+ lines.push(` injectSent: ${result.injectSent ? 'yes' : 'no'}`);
122
+ lines.push(` resumeAfterInject: ${result.resumeAfterInject ? 'yes' : 'no'}`);
123
+ lines.push(` turnStartSent: ${result.turnStartSent ? 'yes' : 'no'}`);
124
+ if (result.agentText) {
125
+ lines.push('');
126
+ lines.push('agent text:');
127
+ lines.push(result.agentText);
128
+ }
129
+ return lines.join('\n');
130
+ }
131
+
132
+ export async function run(args) {
133
+ let parsed;
134
+ try {
135
+ parsed = parseArgs(args);
136
+ } catch (err) {
137
+ const msg = err instanceof Error ? err.message : 'unknown';
138
+ process.stderr.write(`[codex-visibility-smoke] ${msg}\n`);
139
+ process.exit(1);
140
+ }
141
+
142
+ if (process.env.THROUGHLINE_EXPERIMENTAL_CODEX_MODEL_VISIBLE_SMOKE !== '1') {
143
+ const result = {
144
+ status: 'refused',
145
+ reason: 'experimental_env_required',
146
+ requiredEnv: 'THROUGHLINE_EXPERIMENTAL_CODEX_MODEL_VISIBLE_SMOKE=1',
147
+ };
148
+ if (parsed.json) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
149
+ else process.stderr.write(`[codex-visibility-smoke] ${result.reason}\n`);
150
+ process.exit(1);
151
+ }
152
+
153
+ const inflightMemo = parsed.memoStdin ? await readStdin() : null;
154
+ const db = getDb();
155
+ const sessionId = parsed.sessionId ?? findLatestCodexSessionId(db, process.cwd());
156
+ const codexThreadId =
157
+ parsed.codexThreadId ?? (sessionId?.startsWith('codex:') ? sessionId.slice('codex:'.length) : null);
158
+ if (!sessionId || !codexThreadId) {
159
+ process.stderr.write(
160
+ '[codex-visibility-smoke] pass --session codex:<thread-id> or --codex-thread-id <id>.\n',
161
+ );
162
+ process.exit(1);
163
+ }
164
+
165
+ const record = buildHandoffRecord(db, { sessionId, isInheritance: false, inflightMemo });
166
+ if (!record) {
167
+ process.stderr.write(`[codex-visibility-smoke] no handoff memory found for session ${sessionId}\n`);
168
+ process.exit(1);
169
+ }
170
+
171
+ const memoryText = [
172
+ renderCodexActiveWorkContext(record),
173
+ '',
174
+ '### Model Visibility Smoke',
175
+ `When asked for the Throughline model-visible smoke marker, reply exactly: ${parsed.marker}`,
176
+ ].join('\n');
177
+ const command = parsed.codexAppServerBin ?? process.env.THROUGHLINE_CODEX_APP_SERVER_BIN ?? 'codex';
178
+ const result = await runCodexModelVisibilitySmoke({
179
+ threadId: codexThreadId,
180
+ cwd: process.cwd(),
181
+ memoryText,
182
+ marker: parsed.marker,
183
+ command,
184
+ timeoutMs: parsed.timeoutMs,
185
+ requestTimeoutMs: parsed.requestTimeoutMs,
186
+ resumeAfterInject: parsed.resumeAfterInject,
187
+ });
188
+
189
+ if (parsed.json) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
190
+ else process.stdout.write(renderTextResult(result) + '\n');
191
+ process.exit(result.status === 'visible' ? 0 : 1);
192
+ }
193
+
194
+ export const _internal = {
195
+ parseArgs,
196
+ };
@@ -0,0 +1,226 @@
1
+ import {
2
+ buildCodexVsCodeRestoreSmokeMemory,
3
+ buildCodexVsCodeRestoreSmokePrompt,
4
+ inspectCodexVsCodeRestoreSmokeRollout,
5
+ makeCodexVsCodeRestoreSmokeMarker,
6
+ } from '../codex-vscode-restore-smoke.mjs';
7
+ import { runCodexDeveloperMemoryInject } from '../codex-app-server.mjs';
8
+ import { defaultCodexHome } from '../codex-thread-index.mjs';
9
+ import { resolveCodexThreadIdentity } from '../codex-thread-identity.mjs';
10
+
11
+ const CODEX_VSCODE_RESTORE_SMOKE_ENV = 'THROUGHLINE_EXPERIMENTAL_CODEX_VSCODE_RESTORE_SMOKE';
12
+
13
+ function parseArgs(args) {
14
+ const out = {
15
+ mode: null,
16
+ codexThreadId: null,
17
+ marker: null,
18
+ preparedAt: null,
19
+ afterVsCodeRestart: false,
20
+ json: false,
21
+ codexHome: defaultCodexHome(),
22
+ codexAppServerBin: null,
23
+ timeoutMs: 60_000,
24
+ requestTimeoutMs: 20_000,
25
+ };
26
+
27
+ for (let i = 0; i < args.length; i++) {
28
+ const arg = args[i];
29
+ if (arg === '--prepare') {
30
+ out.mode = 'prepare';
31
+ } else if (arg === '--verify') {
32
+ out.mode = 'verify';
33
+ } else if (arg === '--codex-thread-id') {
34
+ const value = args[++i];
35
+ if (!value || value.startsWith('-')) {
36
+ throw new Error('--codex-thread-id requires a thread id');
37
+ }
38
+ out.codexThreadId = value;
39
+ } else if (arg === '--marker') {
40
+ const value = args[++i];
41
+ if (!value || value.startsWith('-')) {
42
+ throw new Error('--marker requires a marker string');
43
+ }
44
+ out.marker = value;
45
+ } else if (arg === '--prepared-at') {
46
+ const value = args[++i];
47
+ if (!value || value.startsWith('-') || Number.isNaN(Date.parse(value))) {
48
+ throw new Error('--prepared-at requires an ISO timestamp');
49
+ }
50
+ out.preparedAt = value;
51
+ } else if (arg === '--after-vscode-restart') {
52
+ out.afterVsCodeRestart = true;
53
+ } else if (arg === '--codex-home') {
54
+ const value = args[++i];
55
+ if (!value || value.startsWith('-')) {
56
+ throw new Error('--codex-home requires a path');
57
+ }
58
+ out.codexHome = value;
59
+ } else if (arg === '--codex-app-server-bin') {
60
+ const value = args[++i];
61
+ if (!value || value.startsWith('-')) {
62
+ throw new Error('--codex-app-server-bin requires a command path');
63
+ }
64
+ out.codexAppServerBin = value;
65
+ } else if (arg === '--timeout-ms') {
66
+ const value = Number(args[++i]);
67
+ if (!Number.isInteger(value) || value < 1) {
68
+ throw new Error('--timeout-ms must be a positive integer');
69
+ }
70
+ out.timeoutMs = value;
71
+ } else if (arg === '--request-timeout-ms') {
72
+ const value = Number(args[++i]);
73
+ if (!Number.isInteger(value) || value < 1) {
74
+ throw new Error('--request-timeout-ms must be a positive integer');
75
+ }
76
+ out.requestTimeoutMs = value;
77
+ } else if (arg === '--json') {
78
+ out.json = true;
79
+ } else {
80
+ throw new Error(`unknown argument: ${arg}`);
81
+ }
82
+ }
83
+
84
+ if (!out.mode) out.mode = 'verify';
85
+ return out;
86
+ }
87
+
88
+ function renderTextResult(result) {
89
+ const lines = [];
90
+ lines.push('throughline codex vscode restore smoke');
91
+ lines.push('');
92
+ lines.push(` status: ${result.status}`);
93
+ lines.push(` reason: ${result.reason}`);
94
+ lines.push(` thread: ${result.threadId ?? 'unknown'}`);
95
+ lines.push(` marker: ${result.marker ?? 'unknown'}`);
96
+ lines.push(` proof scope: ${result.proofScope ?? 'none'}`);
97
+ lines.push(` restart safe: ${result.restartSafe ? 'yes' : 'no'}`);
98
+ if (result.rolloutPath) lines.push(` rollout: ${result.rolloutPath}`);
99
+ if (result.preparedAt) lines.push(` prepared at: ${result.preparedAt}`);
100
+ if (result.prompt) {
101
+ lines.push('');
102
+ lines.push('VS Code prompt after reload/reconnect:');
103
+ lines.push(result.prompt);
104
+ }
105
+ if (Array.isArray(result.assistantMarkerMatches)) {
106
+ lines.push(` assistant marker matches: ${result.assistantMarkerMatches.length}`);
107
+ lines.push(` user marker leaks: ${result.userMarkerMatches.length}`);
108
+ }
109
+ return lines.join('\n');
110
+ }
111
+
112
+ export async function run(args) {
113
+ let parsed;
114
+ try {
115
+ parsed = parseArgs(args);
116
+ } catch (err) {
117
+ const msg = err instanceof Error ? err.message : 'unknown';
118
+ process.stderr.write(`[codex-vscode-restore-smoke] ${msg}\n`);
119
+ process.exit(1);
120
+ }
121
+
122
+ parsed = {
123
+ ...parsed,
124
+ ...resolveCodexThreadIdentity({ codexThreadId: parsed.codexThreadId }, process.env),
125
+ };
126
+
127
+ if (!parsed.codexThreadId) {
128
+ const result = {
129
+ status: 'refused',
130
+ reason: 'codex_thread_id_required',
131
+ proofScope: 'none',
132
+ restartSafe: false,
133
+ };
134
+ if (parsed.json) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
135
+ else process.stderr.write(`[codex-vscode-restore-smoke] ${result.reason}\n`);
136
+ process.exit(1);
137
+ }
138
+
139
+ if (parsed.mode === 'prepare') {
140
+ const result = await prepareSmoke(parsed);
141
+ if (parsed.json) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
142
+ else process.stdout.write(renderTextResult(result) + '\n');
143
+ process.exit(result.status === 'prepared' ? 0 : 1);
144
+ }
145
+
146
+ const result = verifySmoke(parsed);
147
+ if (parsed.json) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
148
+ else process.stdout.write(renderTextResult(result) + '\n');
149
+ process.exit(result.status === 'vscode-restart-visible' ? 0 : 1);
150
+ }
151
+
152
+ async function prepareSmoke(parsed) {
153
+ if (process.env[CODEX_VSCODE_RESTORE_SMOKE_ENV] !== '1') {
154
+ return {
155
+ status: 'refused',
156
+ reason: 'experimental_env_required',
157
+ requiredEnv: `${CODEX_VSCODE_RESTORE_SMOKE_ENV}=1`,
158
+ proofScope: 'none',
159
+ restartSafe: false,
160
+ threadId: parsed.codexThreadId,
161
+ };
162
+ }
163
+
164
+ const marker = parsed.marker ?? makeCodexVsCodeRestoreSmokeMarker();
165
+ const preparedAt = new Date().toISOString();
166
+ const memoryText = buildCodexVsCodeRestoreSmokeMemory({ marker });
167
+ const command = parsed.codexAppServerBin ?? process.env.THROUGHLINE_CODEX_APP_SERVER_BIN ?? 'codex';
168
+ const inject = await runCodexDeveloperMemoryInject({
169
+ threadId: parsed.codexThreadId,
170
+ cwd: process.cwd(),
171
+ memoryText,
172
+ command,
173
+ timeoutMs: parsed.timeoutMs,
174
+ requestTimeoutMs: parsed.requestTimeoutMs,
175
+ });
176
+
177
+ return {
178
+ status: 'prepared',
179
+ reason: 'developer_memory_marker_injected',
180
+ proofScope: 'pending_manual_vscode_reload',
181
+ restartSafe: false,
182
+ threadId: parsed.codexThreadId,
183
+ marker,
184
+ preparedAt,
185
+ prompt: buildCodexVsCodeRestoreSmokePrompt(),
186
+ verifyArgs: [
187
+ 'throughline',
188
+ 'codex-vscode-restore-smoke',
189
+ '--verify',
190
+ '--codex-thread-id',
191
+ parsed.codexThreadId,
192
+ '--marker',
193
+ marker,
194
+ '--prepared-at',
195
+ preparedAt,
196
+ '--after-vscode-restart',
197
+ ],
198
+ inject,
199
+ };
200
+ }
201
+
202
+ function verifySmoke(parsed) {
203
+ if (!parsed.marker) {
204
+ return {
205
+ status: 'refused',
206
+ reason: 'marker_required',
207
+ proofScope: 'none',
208
+ restartSafe: false,
209
+ threadId: parsed.codexThreadId,
210
+ };
211
+ }
212
+
213
+ return inspectCodexVsCodeRestoreSmokeRollout({
214
+ threadId: parsed.codexThreadId,
215
+ marker: parsed.marker,
216
+ codexHome: parsed.codexHome,
217
+ projectPath: process.cwd(),
218
+ preparedAt: parsed.preparedAt,
219
+ afterVsCodeRestart: parsed.afterVsCodeRestart,
220
+ });
221
+ }
222
+
223
+ export const _internal = {
224
+ parseArgs,
225
+ renderTextResult,
226
+ };
@@ -0,0 +1,114 @@
1
+ import { inspectCodexVsCodeRollbackSmoke } from '../codex-vscode-rollback-smoke.mjs';
2
+ import { defaultCodexHome } from '../codex-thread-index.mjs';
3
+ import { resolveCodexThreadIdentity } from '../codex-thread-identity.mjs';
4
+
5
+ function parseArgs(args) {
6
+ const out = {
7
+ codexThreadId: null,
8
+ codexHome: defaultCodexHome(),
9
+ afterVsCodeRestart: false,
10
+ json: false,
11
+ };
12
+
13
+ for (let i = 0; i < args.length; i++) {
14
+ const arg = args[i];
15
+ if (arg === '--verify') {
16
+ continue;
17
+ } else if (arg === '--codex-thread-id') {
18
+ const value = args[++i];
19
+ if (!value || value.startsWith('-')) {
20
+ throw new Error('--codex-thread-id requires a thread id');
21
+ }
22
+ out.codexThreadId = value;
23
+ } else if (arg === '--codex-home') {
24
+ const value = args[++i];
25
+ if (!value || value.startsWith('-')) {
26
+ throw new Error('--codex-home requires a path');
27
+ }
28
+ out.codexHome = value;
29
+ } else if (arg === '--after-vscode-restart') {
30
+ out.afterVsCodeRestart = true;
31
+ } else if (arg === '--json') {
32
+ out.json = true;
33
+ } else {
34
+ throw new Error(`unknown argument: ${arg}`);
35
+ }
36
+ }
37
+
38
+ return out;
39
+ }
40
+
41
+ function renderTextResult(result) {
42
+ const lines = [];
43
+ lines.push('throughline codex vscode rollback smoke');
44
+ lines.push('');
45
+ lines.push(` status: ${result.status}`);
46
+ lines.push(` reason: ${result.reason}`);
47
+ lines.push(` thread: ${result.threadId ?? 'unknown'}`);
48
+ lines.push(` proof scope: ${result.proofScope ?? 'none'}`);
49
+ lines.push(` restart safe: ${result.restartSafe ? 'yes' : 'no'}`);
50
+ if (result.rolloutPath) lines.push(` rollout: ${result.rolloutPath}`);
51
+ if (result.restoreSafety) {
52
+ lines.push(` restore safety: ${result.restoreSafety.status}`);
53
+ lines.push(` rollback events: ${result.stats?.rollbackEvents ?? 0}`);
54
+ lines.push(` rolled-back user messages: ${result.stats?.rolledBackUserMessages ?? 0}`);
55
+ lines.push(` user messages after rollback: ${result.stats?.userMessagesAfterRollback ?? 0}`);
56
+ lines.push(
57
+ ` rollback text retained in compacted: ${
58
+ result.restoreSafety.rollbackTextRetainedInCompacted ?? 0
59
+ }`,
60
+ );
61
+ lines.push(` resurrected user messages: ${result.restoreSafety.resurrectedUserMessages ?? 0}`);
62
+ if (Array.isArray(result.restoreSafety.risks) && result.restoreSafety.risks.length > 0) {
63
+ lines.push(
64
+ ` risks: ${result.restoreSafety.risks
65
+ .map((risk) => `${risk.type}:${risk.count ?? 'unknown'}`)
66
+ .join(', ')}`,
67
+ );
68
+ }
69
+ }
70
+ return lines.join('\n');
71
+ }
72
+
73
+ export async function run(args) {
74
+ let parsed;
75
+ try {
76
+ parsed = parseArgs(args);
77
+ } catch (err) {
78
+ const msg = err instanceof Error ? err.message : 'unknown';
79
+ process.stderr.write(`[codex-vscode-rollback-smoke] ${msg}\n`);
80
+ process.exit(1);
81
+ }
82
+
83
+ parsed = {
84
+ ...parsed,
85
+ ...resolveCodexThreadIdentity({ codexThreadId: parsed.codexThreadId }, process.env),
86
+ };
87
+
88
+ if (!parsed.codexThreadId) {
89
+ const result = {
90
+ status: 'refused',
91
+ reason: 'codex_thread_id_required',
92
+ proofScope: 'none',
93
+ restartSafe: false,
94
+ };
95
+ if (parsed.json) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
96
+ else process.stderr.write(`[codex-vscode-rollback-smoke] ${result.reason}\n`);
97
+ process.exit(1);
98
+ }
99
+
100
+ const result = inspectCodexVsCodeRollbackSmoke({
101
+ threadId: parsed.codexThreadId,
102
+ codexHome: parsed.codexHome,
103
+ projectPath: process.cwd(),
104
+ afterVsCodeRestart: parsed.afterVsCodeRestart,
105
+ });
106
+ if (parsed.json) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
107
+ else process.stdout.write(renderTextResult(result) + '\n');
108
+ process.exit(result.status === 'vscode-restart-rollback-nonresurrection-visible' ? 0 : 1);
109
+ }
110
+
111
+ export const _internal = {
112
+ parseArgs,
113
+ renderTextResult,
114
+ };