throughline 0.6.0 → 0.6.2
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/CHANGELOG.md +33 -0
- package/README.md +46 -5
- package/bin/throughline.mjs +41 -0
- package/docs/00_overview.md +2 -0
- package/docs/04_public_release_plan.md +2 -0
- package/docs/12_desktop_clear_handoff_plan.md +3 -3
- package/docs/13_native_factory_diagnostics_plan.md +46 -0
- package/docs/BUGHUB_RUNTIME_ERROR_STORE_PLAN.md +52 -0
- package/package.json +2 -2
- package/src/auditor-context.mjs +330 -0
- package/src/auditor-context.test.mjs +303 -0
- package/src/cli/auditor-context.mjs +141 -0
- package/src/cli/auditor-context.test.mjs +148 -0
- package/src/cli/codex-hook.mjs +27 -4
- package/src/cli/codex-hook.test.mjs +4 -0
- package/src/cli/codex-restore-smoke.mjs +2 -1
- package/src/cli/codex-restore-source-audit.mjs +1 -1
- package/src/cli/doctor.mjs +5 -1
- package/src/cli/factory-diagnostics.mjs +246 -0
- package/src/cli/factory-diagnostics.test.mjs +201 -0
- package/src/cli/runtime-errors.mjs +85 -0
- package/src/cli/runtime-errors.test.mjs +75 -0
- package/src/cli/trim.mjs +4 -4
- package/src/codex-handoff-model-smoke.mjs +2 -3
- package/src/codex-sidecar-cli.test.mjs +19 -8
- package/src/codex-sidecar.mjs +2 -5
- package/src/codex-sidecar.test.mjs +17 -9
- package/src/codex-thread-index.mjs +17 -2
- package/src/db.mjs +1 -1
- package/src/factory-diagnostics.mjs +118 -0
- package/src/factory-diagnostics.test.mjs +97 -0
- package/src/haiku-summarizer.mjs +3 -5
- package/src/haiku-summarizer.test.mjs +52 -47
- package/src/hook-entrypoints.test.mjs +3 -1
- package/src/phase0-spotter-contract.test.mjs +279 -0
- package/src/portable-spawn-sync.mjs +58 -0
- package/src/portable-spawn-sync.test.mjs +40 -0
- package/src/prompt-submit.mjs +2 -0
- package/src/runtime-error-hook.test.mjs +106 -0
- package/src/runtime-error-observer.mjs +8 -0
- package/src/runtime-error-store.mjs +595 -0
- package/src/runtime-error-store.test.mjs +307 -0
- package/src/session-start.mjs +2 -0
- package/src/test-env.mjs +59 -2
- package/src/turn-backfill.test.mjs +2 -2
- package/src/turn-processor.mjs +2 -0
- package/src/windows-acl-test-helper.mjs +29 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export const FACTORY_DIAGNOSTICS_SCHEMA = 'throughline.native_factory_diagnostics.v1';
|
|
2
|
+
export const DATABASE_SCHEMA = 'throughline.database.v8';
|
|
3
|
+
|
|
4
|
+
const STATUSES = new Set(['ready', 'not_ready', 'not_applicable', 'unverified']);
|
|
5
|
+
|
|
6
|
+
function statusOf(value, fallback = 'unverified') {
|
|
7
|
+
return STATUSES.has(value) ? value : fallback;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function publicReason(status) {
|
|
11
|
+
if (status === 'ready') return 'ready';
|
|
12
|
+
if (status === 'not_ready') return 'not_ready';
|
|
13
|
+
if (status === 'not_applicable') return 'not_applicable';
|
|
14
|
+
return 'diagnostic_unverified';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function aggregate(statuses) {
|
|
18
|
+
const normalized = statuses.map((status) => statusOf(status));
|
|
19
|
+
if (normalized.includes('not_ready')) return 'not_ready';
|
|
20
|
+
if (normalized.includes('unverified')) return 'unverified';
|
|
21
|
+
if (normalized.includes('ready')) return 'ready';
|
|
22
|
+
return 'not_applicable';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Native factory 用の公開可能な read-only readiness projection を組み立てる。
|
|
27
|
+
* 入力に path、本文、例外を含めても、それらは出力へ移送しない。
|
|
28
|
+
*/
|
|
29
|
+
export function buildFactoryDiagnostics({ version, database = {}, hooks = {}, thread = {} } = {}) {
|
|
30
|
+
const databaseStatus = statusOf(database.status);
|
|
31
|
+
const hooksStatus = statusOf(hooks.status);
|
|
32
|
+
const claudeConnectorStatus = statusOf(hooks.claudeStatus);
|
|
33
|
+
const threadStatus = statusOf(thread.status);
|
|
34
|
+
const rolloutAvailable = thread.rolloutAvailable === true;
|
|
35
|
+
const handoffMemory = database.handoffMemory === true;
|
|
36
|
+
|
|
37
|
+
const captureStatus =
|
|
38
|
+
threadStatus === 'not_applicable'
|
|
39
|
+
? 'not_applicable'
|
|
40
|
+
: threadStatus === 'ready' && rolloutAvailable
|
|
41
|
+
? 'ready'
|
|
42
|
+
: threadStatus === 'not_ready'
|
|
43
|
+
? 'not_ready'
|
|
44
|
+
: 'unverified';
|
|
45
|
+
const restoreStatus =
|
|
46
|
+
databaseStatus === 'not_applicable'
|
|
47
|
+
? 'not_applicable'
|
|
48
|
+
: databaseStatus === 'not_ready'
|
|
49
|
+
? 'not_ready'
|
|
50
|
+
: databaseStatus === 'ready'
|
|
51
|
+
? 'ready'
|
|
52
|
+
: 'unverified';
|
|
53
|
+
const restoreSmokeStatus = threadStatus === 'not_applicable' ? 'not_applicable' : 'unverified';
|
|
54
|
+
const handoffStatus =
|
|
55
|
+
threadStatus === 'not_applicable'
|
|
56
|
+
? 'not_applicable'
|
|
57
|
+
: threadStatus === 'ready' && rolloutAvailable && databaseStatus === 'ready' && handoffMemory
|
|
58
|
+
? 'ready'
|
|
59
|
+
: threadStatus === 'not_ready' || databaseStatus === 'not_ready' ||
|
|
60
|
+
(databaseStatus === 'ready' && !handoffMemory)
|
|
61
|
+
? 'not_ready'
|
|
62
|
+
: 'unverified';
|
|
63
|
+
|
|
64
|
+
const stateSchemaStatus =
|
|
65
|
+
databaseStatus === 'ready'
|
|
66
|
+
? 'ready'
|
|
67
|
+
: databaseStatus === 'not_applicable'
|
|
68
|
+
? 'not_applicable'
|
|
69
|
+
: databaseStatus === 'not_ready'
|
|
70
|
+
? 'not_ready'
|
|
71
|
+
: 'unverified';
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
schema: FACTORY_DIAGNOSTICS_SCHEMA,
|
|
75
|
+
version: typeof version === 'string' && version.length > 0 ? version : 'unknown',
|
|
76
|
+
overall: {
|
|
77
|
+
status: aggregate([
|
|
78
|
+
stateSchemaStatus,
|
|
79
|
+
hooksStatus,
|
|
80
|
+
claudeConnectorStatus,
|
|
81
|
+
captureStatus,
|
|
82
|
+
restoreStatus,
|
|
83
|
+
handoffStatus,
|
|
84
|
+
]),
|
|
85
|
+
},
|
|
86
|
+
databaseSchema: {
|
|
87
|
+
schema: DATABASE_SCHEMA,
|
|
88
|
+
status: stateSchemaStatus,
|
|
89
|
+
databaseSchemaVersion: Number.isInteger(database.schemaVersion) ? database.schemaVersion : null,
|
|
90
|
+
supportedDatabaseSchemaVersion: Number.isInteger(database.supportedSchemaVersion)
|
|
91
|
+
? database.supportedSchemaVersion
|
|
92
|
+
: null,
|
|
93
|
+
reason: publicReason(stateSchemaStatus),
|
|
94
|
+
},
|
|
95
|
+
hooks: {
|
|
96
|
+
scope: 'codex',
|
|
97
|
+
status: hooksStatus,
|
|
98
|
+
reason: publicReason(hooksStatus),
|
|
99
|
+
events: {
|
|
100
|
+
userPromptSubmit: statusOf(hooks.events?.userPromptSubmit, hooksStatus),
|
|
101
|
+
postToolUse: statusOf(hooks.events?.postToolUse, hooksStatus),
|
|
102
|
+
stop: statusOf(hooks.events?.stop, hooksStatus),
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
readiness: {
|
|
106
|
+
capture: { status: captureStatus, reason: publicReason(captureStatus) },
|
|
107
|
+
restore: { status: restoreStatus, reason: publicReason(restoreStatus) },
|
|
108
|
+
handoff: { status: handoffStatus, reason: publicReason(handoffStatus) },
|
|
109
|
+
},
|
|
110
|
+
evidence: {
|
|
111
|
+
restoreSmoke: { status: restoreSmokeStatus, reason: publicReason(restoreSmokeStatus) },
|
|
112
|
+
},
|
|
113
|
+
connectors: {
|
|
114
|
+
claude: { status: claudeConnectorStatus, reason: publicReason(claudeConnectorStatus) },
|
|
115
|
+
codex: { status: hooksStatus, reason: publicReason(hooksStatus) },
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
FACTORY_DIAGNOSTICS_SCHEMA,
|
|
6
|
+
buildFactoryDiagnostics,
|
|
7
|
+
} from './factory-diagnostics.mjs';
|
|
8
|
+
|
|
9
|
+
test('factory diagnostics: 未設定の native factory は not_applicable を成功へ丸めない', () => {
|
|
10
|
+
const result = buildFactoryDiagnostics({
|
|
11
|
+
version: '0.6.1',
|
|
12
|
+
database: { status: 'not_applicable', reason: 'db_not_found', schemaVersion: null, handoffMemory: false },
|
|
13
|
+
hooks: { status: 'not_applicable', claudeStatus: 'not_applicable', reason: 'hooks_not_registered', events: {} },
|
|
14
|
+
thread: { status: 'not_applicable', reason: 'codex_thread_not_detected', rolloutAvailable: false },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
assert.equal(result.schema, FACTORY_DIAGNOSTICS_SCHEMA);
|
|
18
|
+
assert.equal(result.overall.status, 'not_applicable');
|
|
19
|
+
assert.equal(result.readiness.capture.status, 'not_applicable');
|
|
20
|
+
assert.equal(result.readiness.restore.status, 'not_applicable');
|
|
21
|
+
assert.equal(result.readiness.handoff.status, 'not_applicable');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('factory diagnostics: restore は実行していない smoke を ready にしない', () => {
|
|
25
|
+
const result = buildFactoryDiagnostics({
|
|
26
|
+
version: '0.6.1',
|
|
27
|
+
database: { status: 'ready', reason: 'db_schema_supported', schemaVersion: 8, handoffMemory: true },
|
|
28
|
+
hooks: {
|
|
29
|
+
status: 'ready',
|
|
30
|
+
claudeStatus: 'ready',
|
|
31
|
+
reason: 'managed_hooks_ready',
|
|
32
|
+
events: {
|
|
33
|
+
userPromptSubmit: 'ready',
|
|
34
|
+
postToolUse: 'ready',
|
|
35
|
+
stop: 'ready',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
thread: { status: 'ready', reason: 'thread_and_rollout_detected', rolloutAvailable: true },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
assert.equal(result.readiness.capture.status, 'ready');
|
|
42
|
+
assert.equal(result.readiness.handoff.status, 'ready');
|
|
43
|
+
assert.equal(result.readiness.restore.status, 'ready');
|
|
44
|
+
assert.equal(result.evidence.restoreSmoke.status, 'unverified');
|
|
45
|
+
assert.equal(result.overall.status, 'ready');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('factory diagnostics: JSON に本文、秘密、絶対 path、生 state を含めない', () => {
|
|
49
|
+
const secret = 'sk-test-very-secret';
|
|
50
|
+
const body = 'ユーザーの prompt 本文';
|
|
51
|
+
const absolutePath = '/Users/example/.throughline/state/session.json';
|
|
52
|
+
const result = buildFactoryDiagnostics({
|
|
53
|
+
version: '0.6.1',
|
|
54
|
+
database: {
|
|
55
|
+
status: 'unverified',
|
|
56
|
+
reason: `db_open_failed:${secret}:${absolutePath}`,
|
|
57
|
+
schemaVersion: 8,
|
|
58
|
+
handoffMemory: false,
|
|
59
|
+
rawState: { body, secret, absolutePath },
|
|
60
|
+
},
|
|
61
|
+
hooks: { status: 'unverified', claudeStatus: 'unverified', reason: `config_unreadable:${absolutePath}`, events: {} },
|
|
62
|
+
thread: { status: 'unverified', reason: `rollout_unreadable:${body}`, rolloutAvailable: false },
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const json = JSON.stringify(result);
|
|
66
|
+
assert.doesNotMatch(json, new RegExp(secret));
|
|
67
|
+
assert.doesNotMatch(json, new RegExp(body));
|
|
68
|
+
assert.doesNotMatch(json, new RegExp(absolutePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
69
|
+
assert.equal(result.databaseSchema.reason, 'diagnostic_unverified');
|
|
70
|
+
assert.equal(result.hooks.reason, 'diagnostic_unverified');
|
|
71
|
+
assert.equal(result.readiness.capture.reason, 'diagnostic_unverified');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('factory diagnostics: known not_readyをunverifiedで隠さずDB schemaをoverallへ含める', () => {
|
|
75
|
+
const result = buildFactoryDiagnostics({
|
|
76
|
+
version: '0.6.1',
|
|
77
|
+
database: { status: 'not_ready', schemaVersion: 7, handoffMemory: false },
|
|
78
|
+
hooks: { status: 'ready', claudeStatus: 'ready', events: {} },
|
|
79
|
+
thread: { status: 'ready', rolloutAvailable: true },
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
assert.equal(result.databaseSchema.status, 'not_ready');
|
|
83
|
+
assert.equal(result.readiness.restore.status, 'not_ready');
|
|
84
|
+
assert.equal(result.overall.status, 'not_ready');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('factory diagnostics: project不一致threadのmemoryをhandoff readyにしない', () => {
|
|
88
|
+
const result = buildFactoryDiagnostics({
|
|
89
|
+
version: '0.6.1',
|
|
90
|
+
database: { status: 'ready', schemaVersion: 8, supportedSchemaVersion: 8, handoffMemory: true },
|
|
91
|
+
hooks: { status: 'ready', claudeStatus: 'ready', events: {} },
|
|
92
|
+
thread: { status: 'not_ready', rolloutAvailable: false },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
assert.equal(result.readiness.handoff.status, 'not_ready');
|
|
96
|
+
assert.equal(result.overall.status, 'not_ready');
|
|
97
|
+
});
|
package/src/haiku-summarizer.mjs
CHANGED
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
* 2. それでも失敗したら L2 全文を L1 に入れる(情報欠損ゼロ)
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
|
-
import { spawnSync } from 'child_process';
|
|
40
39
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'fs';
|
|
41
40
|
import { join } from 'path';
|
|
42
41
|
import { homedir, tmpdir } from 'os';
|
|
@@ -45,6 +44,7 @@ import {
|
|
|
45
44
|
CODEX_SIDECAR_STATUS,
|
|
46
45
|
runCodexSidecarCommand,
|
|
47
46
|
} from './codex-sidecar.mjs';
|
|
47
|
+
import { spawnPortableSync } from './portable-spawn-sync.mjs';
|
|
48
48
|
|
|
49
49
|
const MODEL = 'claude-haiku-4-5-20251001';
|
|
50
50
|
const MAX_RETRIES = 2;
|
|
@@ -206,11 +206,10 @@ function summarizeWithHaiku(l2Text, prompt, env) {
|
|
|
206
206
|
|
|
207
207
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
208
208
|
try {
|
|
209
|
-
const result =
|
|
209
|
+
const result = spawnPortableSync('claude', ['-p', '--model', MODEL, prompt], {
|
|
210
210
|
input: l2Text,
|
|
211
211
|
encoding: 'utf8',
|
|
212
212
|
timeout: TIMEOUT_MS,
|
|
213
|
-
shell: process.platform === 'win32', // Windows は claude.cmd ラッパー
|
|
214
213
|
env: childEnv,
|
|
215
214
|
cwd: HAIKU_WORKDIR, // ← これが再帰防止の本丸
|
|
216
215
|
});
|
|
@@ -247,7 +246,7 @@ function summarizeWithCodexCli(l2Text, { projectPath, env }) {
|
|
|
247
246
|
const command = env.THROUGHLINE_CODEX_CLI_BIN ?? 'codex';
|
|
248
247
|
const prompt = buildCodexPrompt(l2Text);
|
|
249
248
|
const childEnv = { ...env, [CODEX_SUMMARIZER_GUARD_ENV]: '1' };
|
|
250
|
-
const result =
|
|
249
|
+
const result = spawnPortableSync(
|
|
251
250
|
command,
|
|
252
251
|
[
|
|
253
252
|
'exec',
|
|
@@ -265,7 +264,6 @@ function summarizeWithCodexCli(l2Text, { projectPath, env }) {
|
|
|
265
264
|
input: l2Text,
|
|
266
265
|
encoding: 'utf8',
|
|
267
266
|
timeout: CODEX_CLI_TIMEOUT_MS,
|
|
268
|
-
shell: process.platform === 'win32',
|
|
269
267
|
env: childEnv,
|
|
270
268
|
cwd: projectPath,
|
|
271
269
|
},
|
|
@@ -2,14 +2,29 @@ import { test } from 'node:test';
|
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
5
|
+
import { delimiter, join } from 'node:path';
|
|
6
6
|
import { summarizeToL1 } from './haiku-summarizer.mjs';
|
|
7
7
|
|
|
8
8
|
function makeBin(dir, name, body) {
|
|
9
|
-
const
|
|
10
|
-
writeFileSync(
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const script = join(dir, `${name}.mjs`);
|
|
10
|
+
writeFileSync(script, body);
|
|
11
|
+
if (process.platform === 'win32') {
|
|
12
|
+
const command = join(dir, `${name}.cmd`);
|
|
13
|
+
writeFileSync(command, `@echo off\r\n${JSON.stringify(process.execPath)} ${JSON.stringify(script)} %*\r\n`);
|
|
14
|
+
writeFileSync(join(dir, `${name}.ps1`), `& ${JSON.stringify(process.execPath)} ${JSON.stringify(script)} @args\nexit $LASTEXITCODE\n`);
|
|
15
|
+
return command;
|
|
16
|
+
}
|
|
17
|
+
const command = join(dir, name);
|
|
18
|
+
writeFileSync(command, `#!/bin/sh\nexec ${JSON.stringify(process.execPath)} ${JSON.stringify(script)} "$@"\n`);
|
|
19
|
+
chmodSync(command, 0o755);
|
|
20
|
+
return command;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function envWithPrependedPath(dir) {
|
|
24
|
+
const env = { ...process.env };
|
|
25
|
+
const pathKey = Object.keys(env).find((key) => key.toLowerCase() === 'path') ?? 'PATH';
|
|
26
|
+
env[pathKey] = `${dir}${delimiter}${env[pathKey] ?? ''}`;
|
|
27
|
+
return env;
|
|
13
28
|
}
|
|
14
29
|
|
|
15
30
|
test('summarizeToL1: returns empty fallback for blank input', () => {
|
|
@@ -44,13 +59,11 @@ test('summarizeToL1: uses codex-sidecar when diagnostics and run both succeed',
|
|
|
44
59
|
const sidecar = makeBin(
|
|
45
60
|
dir,
|
|
46
61
|
'codex-sidecar',
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
printf '{"status":"ok","summary":"sidecar summary"}\\n'
|
|
53
|
-
exit 0
|
|
62
|
+
`if (process.argv[2] === 'diagnostics') {
|
|
63
|
+
process.stdout.write('{"status":"ok"}\\n');
|
|
64
|
+
} else {
|
|
65
|
+
process.stdout.write('{"status":"ok","summary":"sidecar summary"}\\n');
|
|
66
|
+
}
|
|
54
67
|
`,
|
|
55
68
|
);
|
|
56
69
|
|
|
@@ -78,13 +91,11 @@ test('summarizeToL1: accepts stable SidecarResult summary without status field',
|
|
|
78
91
|
const sidecar = makeBin(
|
|
79
92
|
dir,
|
|
80
93
|
'codex-sidecar',
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
printf '{"summary":"stable sidecar summary","confidence":{"level":"high"},"recommendedNextAction":"continue"}\\n'
|
|
87
|
-
exit 0
|
|
94
|
+
`if (process.argv[2] === 'diagnostics') {
|
|
95
|
+
process.stdout.write('{"status":"ok"}\\n');
|
|
96
|
+
} else {
|
|
97
|
+
process.stdout.write('{"summary":"stable sidecar summary","confidence":{"level":"high"},"recommendedNextAction":"continue"}\\n');
|
|
98
|
+
}
|
|
88
99
|
`,
|
|
89
100
|
);
|
|
90
101
|
|
|
@@ -112,9 +123,8 @@ test('summarizeToL1: when sidecar is disabled, keeps current Haiku-compatible pa
|
|
|
112
123
|
makeBin(
|
|
113
124
|
dir,
|
|
114
125
|
'claude',
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
printf 'haiku summary\\n'
|
|
126
|
+
`for await (const _chunk of process.stdin) {}
|
|
127
|
+
process.stdout.write('haiku summary\\n');
|
|
118
128
|
`,
|
|
119
129
|
);
|
|
120
130
|
|
|
@@ -122,8 +132,7 @@ printf 'haiku summary\\n'
|
|
|
122
132
|
hostMode: 'claude-primary',
|
|
123
133
|
projectPath: '/repo',
|
|
124
134
|
env: {
|
|
125
|
-
...
|
|
126
|
-
PATH: `${dir}:${process.env.PATH ?? ''}`,
|
|
135
|
+
...envWithPrependedPath(dir),
|
|
127
136
|
THROUGHLINE_CODEX_SIDECAR_DISABLED: '1',
|
|
128
137
|
},
|
|
129
138
|
});
|
|
@@ -143,21 +152,19 @@ test('summarizeToL1: sidecar run failure keeps current Haiku-compatible path', (
|
|
|
143
152
|
const sidecar = makeBin(
|
|
144
153
|
dir,
|
|
145
154
|
'codex-sidecar',
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
exit 42
|
|
155
|
+
`if (process.argv[2] === 'diagnostics') {
|
|
156
|
+
process.stdout.write('{"status":"ok"}\\n');
|
|
157
|
+
} else {
|
|
158
|
+
process.stderr.write('sidecar failed\\n');
|
|
159
|
+
process.exit(42);
|
|
160
|
+
}
|
|
153
161
|
`,
|
|
154
162
|
);
|
|
155
163
|
makeBin(
|
|
156
164
|
dir,
|
|
157
165
|
'claude',
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
printf 'haiku after sidecar failure\\n'
|
|
166
|
+
`for await (const _chunk of process.stdin) {}
|
|
167
|
+
process.stdout.write('haiku after sidecar failure\\n');
|
|
161
168
|
`,
|
|
162
169
|
);
|
|
163
170
|
|
|
@@ -165,8 +172,7 @@ printf 'haiku after sidecar failure\\n'
|
|
|
165
172
|
hostMode: 'claude-primary',
|
|
166
173
|
projectPath: '/repo',
|
|
167
174
|
env: {
|
|
168
|
-
...
|
|
169
|
-
PATH: `${dir}:${process.env.PATH ?? ''}`,
|
|
175
|
+
...envWithPrependedPath(dir),
|
|
170
176
|
THROUGHLINE_CODEX_SIDECAR_BIN: sidecar,
|
|
171
177
|
},
|
|
172
178
|
});
|
|
@@ -199,10 +205,12 @@ test('summarizeToL1: codex-primary uses Codex CLI backend', () => {
|
|
|
199
205
|
const codex = makeBin(
|
|
200
206
|
dir,
|
|
201
207
|
'codex',
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
`import { writeFileSync } from 'node:fs';
|
|
209
|
+
writeFileSync(${JSON.stringify(argsFile)}, process.argv.slice(2).join('\\n') + '\\n');
|
|
210
|
+
let input = '';
|
|
211
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
212
|
+
writeFileSync(${JSON.stringify(stdinFile)}, input);
|
|
213
|
+
process.stdout.write('codex summary\\n');
|
|
206
214
|
`,
|
|
207
215
|
);
|
|
208
216
|
|
|
@@ -244,16 +252,14 @@ test('summarizeToL1: codex-primary failure is not hidden by fallback', () => {
|
|
|
244
252
|
const codex = makeBin(
|
|
245
253
|
dir,
|
|
246
254
|
'codex',
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
exit 42
|
|
255
|
+
`process.stderr.write('codex failed\\n');
|
|
256
|
+
process.exit(42);
|
|
250
257
|
`,
|
|
251
258
|
);
|
|
252
259
|
makeBin(
|
|
253
260
|
dir,
|
|
254
261
|
'claude',
|
|
255
|
-
|
|
256
|
-
printf 'should not run\\n'
|
|
262
|
+
`process.stdout.write('should not run\\n');
|
|
257
263
|
`,
|
|
258
264
|
);
|
|
259
265
|
|
|
@@ -263,8 +269,7 @@ printf 'should not run\\n'
|
|
|
263
269
|
hostMode: 'codex-primary',
|
|
264
270
|
projectPath: dir,
|
|
265
271
|
env: {
|
|
266
|
-
...
|
|
267
|
-
PATH: `${dir}:${process.env.PATH ?? ''}`,
|
|
272
|
+
...envWithPrependedPath(dir),
|
|
268
273
|
THROUGHLINE_CODEX_CLI_BIN: codex,
|
|
269
274
|
},
|
|
270
275
|
}),
|
|
@@ -408,7 +408,9 @@ test('process-turn subprocess backfills all completed logical turns from a multi
|
|
|
408
408
|
}
|
|
409
409
|
});
|
|
410
410
|
|
|
411
|
-
test('session-start backfills a derived predecessor transcript without a state file',
|
|
411
|
+
test('session-start backfills a derived predecessor transcript without a state file', {
|
|
412
|
+
skip: process.platform === 'win32' ? 'Windowsはstate file transcriptPath fallback契約' : undefined,
|
|
413
|
+
}, () => {
|
|
412
414
|
const home = makeTempHome();
|
|
413
415
|
const project = makeTempProject();
|
|
414
416
|
const predecessorId = 'missing-stop-predecessor';
|