@magnusekdahl/parallix 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/agents/agents.js +737 -838
- package/lib/agents/agents.ts +930 -0
- package/lib/agents/claude-telemetry.js +149 -154
- package/lib/agents/claude-telemetry.ts +233 -0
- package/lib/agents/claude.js +121 -131
- package/lib/agents/claude.ts +156 -0
- package/lib/agents/codex-telemetry.js +176 -164
- package/lib/agents/codex-telemetry.ts +205 -0
- package/lib/agents/codex.js +168 -201
- package/lib/agents/codex.ts +236 -0
- package/lib/agents/limit-hit.js +216 -226
- package/lib/agents/limit-hit.ts +262 -0
- package/lib/agents/mistral-telemetry.js +7 -10
- package/lib/agents/mistral-telemetry.ts +44 -0
- package/lib/agents/mistral.js +40 -58
- package/lib/agents/mistral.ts +84 -0
- package/lib/agents/opencode-export.js +126 -127
- package/lib/agents/opencode-export.ts +160 -0
- package/lib/agents/opencode-telemetry.js +282 -261
- package/lib/agents/opencode-telemetry.ts +350 -0
- package/lib/agents/opencode.js +263 -305
- package/lib/agents/opencode.ts +370 -0
- package/lib/agents/stage-telemetry.js +23 -24
- package/lib/agents/stage-telemetry.ts +47 -0
- package/lib/commands/integrate.js +9 -3
- package/lib/core/fmt.js +140 -211
- package/lib/core/fmt.ts +189 -0
- package/lib/core/git.js +121 -102
- package/lib/core/git.ts +146 -0
- package/lib/core/gitignore.js +59 -106
- package/lib/core/gitignore.ts +103 -0
- package/lib/core/mission-utils.js +833 -875
- package/lib/core/mission-utils.ts +998 -0
- package/lib/core/persistent-data-migration.js +247 -205
- package/lib/core/persistent-data-migration.ts +255 -0
- package/lib/core/product-config.js +400 -476
- package/lib/core/product-config.ts +518 -0
- package/lib/core/runtime-matrix.js +41 -87
- package/lib/core/runtime-matrix.ts +84 -0
- package/lib/core/spawn-tee.js +152 -192
- package/lib/core/spawn-tee.ts +202 -0
- package/lib/core/state-map.js +92 -92
- package/lib/core/state-map.ts +115 -0
- package/lib/core/storage.js +101 -129
- package/lib/core/storage.ts +182 -0
- package/lib/core/verification.js +148 -138
- package/lib/core/verification.ts +200 -0
- package/package.json +4 -2
- package/prompts/draft.md +1 -0
- package/prompts/portfolio.md +1 -1
- package/px.js +0 -2
package/lib/agents/claude.js
CHANGED
|
@@ -1,160 +1,150 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractClaudeTelemetryFromStdout = void 0;
|
|
4
|
+
exports.buildClaudeInvocation = buildClaudeInvocation;
|
|
5
|
+
exports.extractClaudeSessionId = extractClaudeSessionId;
|
|
6
|
+
exports.resolveClaudeCommand = resolveClaudeCommand;
|
|
7
|
+
exports.startClaudeAgent = startClaudeAgent;
|
|
8
|
+
exports.__setSpawnAndTeeForTest = __setSpawnAndTeeForTest;
|
|
9
|
+
exports.__setSessionsForTest = __setSessionsForTest;
|
|
10
|
+
const spawn_tee_js_1 = require("../core/spawn-tee.js");
|
|
11
|
+
const claude_telemetry_js_1 = require("./claude-telemetry.js");
|
|
12
|
+
Object.defineProperty(exports, "extractClaudeTelemetryFromStdout", { enumerable: true, get: function () { return claude_telemetry_js_1.extractClaudeTelemetryFromStdout; } });
|
|
13
|
+
// tools/sessions is still CJS (not converted in this wave); require keeps it
|
|
14
|
+
// untyped (any) without pulling a non-included .js into the typecheck program.
|
|
3
15
|
const sessions = require('../tools/sessions');
|
|
4
|
-
|
|
5
16
|
// Injectable I/O for tests. Production uses the real spawn-tee / export capture.
|
|
6
|
-
|
|
7
|
-
let _spawnAndTee = spawnAndTee;
|
|
8
|
-
/** @type {object} */
|
|
17
|
+
let _spawnAndTee = spawn_tee_js_1.spawnAndTee;
|
|
9
18
|
let _sessions = sessions;
|
|
10
|
-
|
|
11
19
|
// Test hooks: override the launcher's I/O without touching the public signature.
|
|
12
|
-
|
|
13
|
-
function __setSpawnAndTeeForTest(fn) { _spawnAndTee = fn || spawnAndTee; }
|
|
14
|
-
/** @param {object} mod */
|
|
20
|
+
function __setSpawnAndTeeForTest(fn) { _spawnAndTee = fn || spawn_tee_js_1.spawnAndTee; }
|
|
15
21
|
function __setSessionsForTest(mod) { _sessions = mod || sessions; }
|
|
16
|
-
|
|
17
22
|
// Claude usage telemetry is parsed from the `stream-json` stdout stream, which
|
|
18
23
|
// carries `message_start` (input) and `message_delta` (output) usage events. The
|
|
19
24
|
// leading `message_start` (with input_tokens) sits at the very start of the
|
|
20
25
|
// stream, so the spawn-tee tail buffer must be large enough to retain it for a
|
|
21
26
|
// full mission-scale invocation rather than the 64 KiB limit-hit default.
|
|
22
27
|
const CLAUDE_TELEMETRY_TAIL_BYTES = 8 * 1024 * 1024;
|
|
23
|
-
|
|
24
28
|
// Claude outputs "Resume this session with: claude --resume <id>" at the end.
|
|
25
29
|
const CLAUDE_SESSION_ID_RE = /claude\s+--resume\s+([0-9a-f-]+)/i;
|
|
26
|
-
|
|
27
|
-
/** @param {string} stdout */
|
|
28
30
|
function extractClaudeSessionIdFromStreamJson(stdout) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
for (const line of lines) {
|
|
32
|
-
const trimmed = line.trim();
|
|
33
|
-
if (!trimmed.startsWith('{')) {continue;}
|
|
34
|
-
try {
|
|
35
|
-
const evt = JSON.parse(trimmed);
|
|
36
|
-
// stream-json 'result' events carry session_id at the top level.
|
|
37
|
-
if (evt.type === 'result' && evt.session_id) {return evt.session_id;}
|
|
38
|
-
} catch {
|
|
39
|
-
/* not JSON — skip */
|
|
31
|
+
if (!stdout) {
|
|
32
|
+
return null;
|
|
40
33
|
}
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
const lines = stdout.split('\n');
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
const trimmed = line.trim();
|
|
37
|
+
if (!trimmed.startsWith('{')) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const evt = JSON.parse(trimmed);
|
|
42
|
+
// stream-json 'result' events carry session_id at the top level.
|
|
43
|
+
if (evt.type === 'result' && evt.session_id) {
|
|
44
|
+
return evt.session_id;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/* not JSON — skip */
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
43
52
|
}
|
|
44
|
-
|
|
45
|
-
/** @param {string} stdout */
|
|
46
53
|
function extractClaudeSessionId(stdout) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
if (!stdout) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
// Try stream-json result event first (new default format).
|
|
58
|
+
const fromStream = extractClaudeSessionIdFromStreamJson(stdout);
|
|
59
|
+
if (fromStream) {
|
|
60
|
+
return fromStream;
|
|
61
|
+
}
|
|
62
|
+
// Fall back to legacy plain-text regex.
|
|
63
|
+
const m = CLAUDE_SESSION_ID_RE.exec(stdout);
|
|
64
|
+
return m ? m[1] : null;
|
|
54
65
|
}
|
|
55
|
-
|
|
56
66
|
function resolveClaudeCommand() {
|
|
57
|
-
|
|
67
|
+
return 'claude';
|
|
58
68
|
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* @param {{ prompt: string, worktree: string, env?: object, resume?: boolean, sessionId?: string | null, model?: string | null }} opts
|
|
62
|
-
*/
|
|
63
69
|
function buildClaudeInvocation({ prompt, worktree, env, resume = false, sessionId = null, model = null }) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// sessionId from a stale cross-family marker must be ignored when resume=false.
|
|
68
|
-
if (resume && sessionId) {
|
|
69
|
-
args.push('--resume', sessionId);
|
|
70
|
-
} else if (resume) {
|
|
71
|
-
args.push('--continue');
|
|
72
|
-
}
|
|
73
|
-
// Stream human-readable intermediate progress via stream-json events.
|
|
74
|
-
// The spawn-tee tee mechanism forwards these JSONL events to the terminal
|
|
75
|
-
// in real time so the operator sees tool calls and assistant text as they
|
|
76
|
-
// happen, rather than waiting for the final result.
|
|
77
|
-
// --include-partial-messages is required: without it, the assistant event
|
|
78
|
-
// contains the full response at once and no intermediate progress is emitted.
|
|
79
|
-
args.push('--output-format', 'stream-json', '--verbose', '--include-partial-messages');
|
|
80
|
-
args.push('-p', prompt);
|
|
81
|
-
return {
|
|
82
|
-
command: resolveClaudeCommand(),
|
|
83
|
-
args,
|
|
84
|
-
options: {
|
|
85
|
-
stdio: 'inherit',
|
|
86
|
-
cwd: worktree,
|
|
87
|
-
env: { ...process.env, ...env }
|
|
70
|
+
const args = ['--dangerously-skip-permissions'];
|
|
71
|
+
if (model) {
|
|
72
|
+
args.push('--model', model);
|
|
88
73
|
}
|
|
89
|
-
|
|
74
|
+
// Only resume when the marker explicitly says so (resume=true).
|
|
75
|
+
// sessionId from a stale cross-family marker must be ignored when resume=false.
|
|
76
|
+
if (resume && sessionId) {
|
|
77
|
+
args.push('--resume', sessionId);
|
|
78
|
+
}
|
|
79
|
+
else if (resume) {
|
|
80
|
+
args.push('--continue');
|
|
81
|
+
}
|
|
82
|
+
// Stream human-readable intermediate progress via stream-json events.
|
|
83
|
+
// The spawn-tee tee mechanism forwards these JSONL events to the terminal
|
|
84
|
+
// in real time so the operator sees tool calls and assistant text as they
|
|
85
|
+
// happen, rather than waiting for the final result.
|
|
86
|
+
// --include-partial-messages is required: without it, the assistant event
|
|
87
|
+
// contains the full response at once and no intermediate progress is emitted.
|
|
88
|
+
args.push('--output-format', 'stream-json', '--verbose', '--include-partial-messages');
|
|
89
|
+
args.push('-p', prompt);
|
|
90
|
+
return {
|
|
91
|
+
command: resolveClaudeCommand(),
|
|
92
|
+
args,
|
|
93
|
+
options: {
|
|
94
|
+
stdio: 'inherit',
|
|
95
|
+
cwd: worktree,
|
|
96
|
+
env: { ...process.env, ...env }
|
|
97
|
+
}
|
|
98
|
+
};
|
|
90
99
|
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* @param {{ prompt: string, worktree: string, env?: object, resume?: boolean, sessionId?: string | null, model?: string | null, teeOptions?: object, slug?: string | null, role?: string | null }} opts
|
|
94
|
-
*/
|
|
95
100
|
function startClaudeAgent({ prompt, worktree, env, resume = false, sessionId = null, model = null, teeOptions = {}, slug = null, role = null }) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
function isStaleSessionResult(result) {
|
|
100
|
-
if (!result) {return false;}
|
|
101
|
-
const stderr = result.stderr || '';
|
|
102
|
-
const stdout = result.stdout || '';
|
|
103
|
-
return (stderr.includes('Session not found') || stdout.includes('Session not found'));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* @param {{stdout?: string, sessionId?: string, telemetry?: object, model?: string, provider?: string}} result
|
|
108
|
-
*/
|
|
109
|
-
function processResult(result) {
|
|
110
|
-
if (result && result.stdout) {
|
|
111
|
-
result.sessionId = extractClaudeSessionId(result.stdout);
|
|
112
|
-
try {
|
|
113
|
-
const telemetry = extractClaudeTelemetryFromStdout(result.stdout);
|
|
114
|
-
if (telemetry) {
|
|
115
|
-
result.telemetry = telemetry;
|
|
116
|
-
if (telemetry.model) {result.model = telemetry.model;}
|
|
117
|
-
if (telemetry.provider) {result.provider = telemetry.provider;}
|
|
101
|
+
function isStaleSessionResult(result) {
|
|
102
|
+
if (!result) {
|
|
103
|
+
return false;
|
|
118
104
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
105
|
+
const stderr = result.stderr || '';
|
|
106
|
+
const stdout = result.stdout || '';
|
|
107
|
+
return (stderr.includes('Session not found') || stdout.includes('Session not found'));
|
|
122
108
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
109
|
+
function processResult(result) {
|
|
110
|
+
if (result && result.stdout) {
|
|
111
|
+
result.sessionId = extractClaudeSessionId(result.stdout);
|
|
112
|
+
try {
|
|
113
|
+
const telemetry = (0, claude_telemetry_js_1.extractClaudeTelemetryFromStdout)(result.stdout);
|
|
114
|
+
if (telemetry) {
|
|
115
|
+
result.telemetry = telemetry;
|
|
116
|
+
if (telemetry.model) {
|
|
117
|
+
result.model = telemetry.model;
|
|
118
|
+
}
|
|
119
|
+
if (telemetry.provider) {
|
|
120
|
+
result.provider = telemetry.provider;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (_) {
|
|
125
|
+
// Telemetry is best-effort; never let it break the launch result.
|
|
126
|
+
}
|
|
140
127
|
}
|
|
141
128
|
return result;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
129
|
+
}
|
|
130
|
+
function staleSessionHandler(invocation) {
|
|
131
|
+
const teeWithTail = { maxTailBytes: CLAUDE_TELEMETRY_TAIL_BYTES, ...invocation.options, ...teeOptions };
|
|
132
|
+
return _spawnAndTee(invocation.command, invocation.args, teeWithTail)
|
|
133
|
+
.then((result) => {
|
|
134
|
+
if (isStaleSessionResult(result) && worktree && resume) {
|
|
135
|
+
try {
|
|
136
|
+
_sessions.clearSession(worktree, slug, role);
|
|
137
|
+
}
|
|
138
|
+
catch (_) { /* best-effort */ }
|
|
139
|
+
const freshInv = buildClaudeInvocation({ prompt, worktree, env, resume: false, sessionId: null, model });
|
|
140
|
+
const freshTee = { maxTailBytes: CLAUDE_TELEMETRY_TAIL_BYTES, ...freshInv.options, ...teeOptions };
|
|
141
|
+
return _spawnAndTee(freshInv.command, freshInv.args, freshTee);
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
})
|
|
145
|
+
.then(processResult);
|
|
146
|
+
}
|
|
147
|
+
const invocation = buildClaudeInvocation({ prompt, worktree, env, resume, sessionId, model });
|
|
148
|
+
const resultPromise = staleSessionHandler(invocation);
|
|
149
|
+
return { invocation, resultPromise };
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
module.exports = {
|
|
153
|
-
buildClaudeInvocation,
|
|
154
|
-
extractClaudeSessionId,
|
|
155
|
-
extractClaudeTelemetryFromStdout,
|
|
156
|
-
resolveClaudeCommand,
|
|
157
|
-
startClaudeAgent,
|
|
158
|
-
__setSpawnAndTeeForTest,
|
|
159
|
-
__setSessionsForTest
|
|
160
|
-
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { spawnAndTee } from '../core/spawn-tee.js';
|
|
2
|
+
import { extractClaudeTelemetryFromStdout } from './claude-telemetry.js';
|
|
3
|
+
// tools/sessions is still CJS (not converted in this wave); require keeps it
|
|
4
|
+
// untyped (any) without pulling a non-included .js into the typecheck program.
|
|
5
|
+
const sessions = require('../tools/sessions');
|
|
6
|
+
|
|
7
|
+
interface ClaudeInvocationOptions {
|
|
8
|
+
prompt: string;
|
|
9
|
+
worktree: string;
|
|
10
|
+
env?: object;
|
|
11
|
+
resume?: boolean;
|
|
12
|
+
sessionId?: string | null;
|
|
13
|
+
model?: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface StartClaudeAgentOptions extends ClaudeInvocationOptions {
|
|
17
|
+
teeOptions?: object;
|
|
18
|
+
slug?: string | null;
|
|
19
|
+
role?: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Injectable I/O for tests. Production uses the real spawn-tee / export capture.
|
|
23
|
+
let _spawnAndTee: any = spawnAndTee;
|
|
24
|
+
let _sessions: any = sessions;
|
|
25
|
+
|
|
26
|
+
// Test hooks: override the launcher's I/O without touching the public signature.
|
|
27
|
+
function __setSpawnAndTeeForTest(fn: any) { _spawnAndTee = fn || spawnAndTee; }
|
|
28
|
+
function __setSessionsForTest(mod: any) { _sessions = mod || sessions; }
|
|
29
|
+
|
|
30
|
+
// Claude usage telemetry is parsed from the `stream-json` stdout stream, which
|
|
31
|
+
// carries `message_start` (input) and `message_delta` (output) usage events. The
|
|
32
|
+
// leading `message_start` (with input_tokens) sits at the very start of the
|
|
33
|
+
// stream, so the spawn-tee tail buffer must be large enough to retain it for a
|
|
34
|
+
// full mission-scale invocation rather than the 64 KiB limit-hit default.
|
|
35
|
+
const CLAUDE_TELEMETRY_TAIL_BYTES = 8 * 1024 * 1024;
|
|
36
|
+
|
|
37
|
+
// Claude outputs "Resume this session with: claude --resume <id>" at the end.
|
|
38
|
+
const CLAUDE_SESSION_ID_RE = /claude\s+--resume\s+([0-9a-f-]+)/i;
|
|
39
|
+
|
|
40
|
+
function extractClaudeSessionIdFromStreamJson(stdout: string) {
|
|
41
|
+
if (!stdout) {return null;}
|
|
42
|
+
const lines = stdout.split('\n');
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
const trimmed = line.trim();
|
|
45
|
+
if (!trimmed.startsWith('{')) {continue;}
|
|
46
|
+
try {
|
|
47
|
+
const evt = JSON.parse(trimmed);
|
|
48
|
+
// stream-json 'result' events carry session_id at the top level.
|
|
49
|
+
if (evt.type === 'result' && evt.session_id) {return evt.session_id;}
|
|
50
|
+
} catch {
|
|
51
|
+
/* not JSON — skip */
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractClaudeSessionId(stdout: string) {
|
|
58
|
+
if (!stdout) {return null;}
|
|
59
|
+
// Try stream-json result event first (new default format).
|
|
60
|
+
const fromStream = extractClaudeSessionIdFromStreamJson(stdout);
|
|
61
|
+
if (fromStream) {return fromStream;}
|
|
62
|
+
// Fall back to legacy plain-text regex.
|
|
63
|
+
const m = CLAUDE_SESSION_ID_RE.exec(stdout);
|
|
64
|
+
return m ? m[1] : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveClaudeCommand() {
|
|
68
|
+
return 'claude';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildClaudeInvocation({ prompt, worktree, env, resume = false, sessionId = null, model = null }: ClaudeInvocationOptions) {
|
|
72
|
+
const args = ['--dangerously-skip-permissions'];
|
|
73
|
+
if (model) {args.push('--model', model);}
|
|
74
|
+
// Only resume when the marker explicitly says so (resume=true).
|
|
75
|
+
// sessionId from a stale cross-family marker must be ignored when resume=false.
|
|
76
|
+
if (resume && sessionId) {
|
|
77
|
+
args.push('--resume', sessionId);
|
|
78
|
+
} else if (resume) {
|
|
79
|
+
args.push('--continue');
|
|
80
|
+
}
|
|
81
|
+
// Stream human-readable intermediate progress via stream-json events.
|
|
82
|
+
// The spawn-tee tee mechanism forwards these JSONL events to the terminal
|
|
83
|
+
// in real time so the operator sees tool calls and assistant text as they
|
|
84
|
+
// happen, rather than waiting for the final result.
|
|
85
|
+
// --include-partial-messages is required: without it, the assistant event
|
|
86
|
+
// contains the full response at once and no intermediate progress is emitted.
|
|
87
|
+
args.push('--output-format', 'stream-json', '--verbose', '--include-partial-messages');
|
|
88
|
+
args.push('-p', prompt);
|
|
89
|
+
return {
|
|
90
|
+
command: resolveClaudeCommand(),
|
|
91
|
+
args,
|
|
92
|
+
options: {
|
|
93
|
+
stdio: 'inherit',
|
|
94
|
+
cwd: worktree,
|
|
95
|
+
env: { ...process.env, ...env }
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function startClaudeAgent({ prompt, worktree, env, resume = false, sessionId = null, model = null, teeOptions = {}, slug = null, role = null }: StartClaudeAgentOptions) {
|
|
101
|
+
function isStaleSessionResult(result: any) {
|
|
102
|
+
if (!result) {return false;}
|
|
103
|
+
const stderr = result.stderr || '';
|
|
104
|
+
const stdout = result.stdout || '';
|
|
105
|
+
return (stderr.includes('Session not found') || stdout.includes('Session not found'));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function processResult(result: any) {
|
|
109
|
+
if (result && result.stdout) {
|
|
110
|
+
result.sessionId = extractClaudeSessionId(result.stdout);
|
|
111
|
+
try {
|
|
112
|
+
const telemetry = extractClaudeTelemetryFromStdout(result.stdout);
|
|
113
|
+
if (telemetry) {
|
|
114
|
+
result.telemetry = telemetry;
|
|
115
|
+
if (telemetry.model) {result.model = telemetry.model;}
|
|
116
|
+
if (telemetry.provider) {result.provider = telemetry.provider;}
|
|
117
|
+
}
|
|
118
|
+
} catch (_) {
|
|
119
|
+
// Telemetry is best-effort; never let it break the launch result.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function staleSessionHandler(invocation: any) {
|
|
126
|
+
const teeWithTail = { maxTailBytes: CLAUDE_TELEMETRY_TAIL_BYTES, ...invocation.options, ...teeOptions };
|
|
127
|
+
return _spawnAndTee(invocation.command, invocation.args, teeWithTail)
|
|
128
|
+
.then((result: any) => {
|
|
129
|
+
if (isStaleSessionResult(result) && worktree && resume) {
|
|
130
|
+
try {
|
|
131
|
+
_sessions.clearSession(worktree, slug, role);
|
|
132
|
+
} catch (_) { /* best-effort */ }
|
|
133
|
+
const freshInv = buildClaudeInvocation({ prompt, worktree, env, resume: false, sessionId: null, model });
|
|
134
|
+
const freshTee = { maxTailBytes: CLAUDE_TELEMETRY_TAIL_BYTES, ...freshInv.options, ...teeOptions };
|
|
135
|
+
return _spawnAndTee(freshInv.command, freshInv.args, freshTee);
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
})
|
|
139
|
+
.then(processResult);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const invocation = buildClaudeInvocation({ prompt, worktree, env, resume, sessionId, model });
|
|
143
|
+
const resultPromise = staleSessionHandler(invocation);
|
|
144
|
+
|
|
145
|
+
return { invocation, resultPromise };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export {
|
|
149
|
+
buildClaudeInvocation,
|
|
150
|
+
extractClaudeSessionId,
|
|
151
|
+
extractClaudeTelemetryFromStdout,
|
|
152
|
+
resolveClaudeCommand,
|
|
153
|
+
startClaudeAgent,
|
|
154
|
+
__setSpawnAndTeeForTest,
|
|
155
|
+
__setSessionsForTest
|
|
156
|
+
};
|