@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.
Files changed (51) hide show
  1. package/lib/agents/agents.js +737 -838
  2. package/lib/agents/agents.ts +930 -0
  3. package/lib/agents/claude-telemetry.js +149 -154
  4. package/lib/agents/claude-telemetry.ts +233 -0
  5. package/lib/agents/claude.js +121 -131
  6. package/lib/agents/claude.ts +156 -0
  7. package/lib/agents/codex-telemetry.js +176 -164
  8. package/lib/agents/codex-telemetry.ts +205 -0
  9. package/lib/agents/codex.js +168 -201
  10. package/lib/agents/codex.ts +236 -0
  11. package/lib/agents/limit-hit.js +216 -226
  12. package/lib/agents/limit-hit.ts +262 -0
  13. package/lib/agents/mistral-telemetry.js +7 -10
  14. package/lib/agents/mistral-telemetry.ts +44 -0
  15. package/lib/agents/mistral.js +40 -58
  16. package/lib/agents/mistral.ts +84 -0
  17. package/lib/agents/opencode-export.js +126 -127
  18. package/lib/agents/opencode-export.ts +160 -0
  19. package/lib/agents/opencode-telemetry.js +282 -261
  20. package/lib/agents/opencode-telemetry.ts +350 -0
  21. package/lib/agents/opencode.js +263 -305
  22. package/lib/agents/opencode.ts +370 -0
  23. package/lib/agents/stage-telemetry.js +23 -24
  24. package/lib/agents/stage-telemetry.ts +47 -0
  25. package/lib/commands/integrate.js +9 -3
  26. package/lib/core/fmt.js +140 -211
  27. package/lib/core/fmt.ts +189 -0
  28. package/lib/core/git.js +121 -102
  29. package/lib/core/git.ts +146 -0
  30. package/lib/core/gitignore.js +59 -106
  31. package/lib/core/gitignore.ts +103 -0
  32. package/lib/core/mission-utils.js +833 -875
  33. package/lib/core/mission-utils.ts +998 -0
  34. package/lib/core/persistent-data-migration.js +247 -205
  35. package/lib/core/persistent-data-migration.ts +255 -0
  36. package/lib/core/product-config.js +400 -476
  37. package/lib/core/product-config.ts +518 -0
  38. package/lib/core/runtime-matrix.js +41 -87
  39. package/lib/core/runtime-matrix.ts +84 -0
  40. package/lib/core/spawn-tee.js +152 -192
  41. package/lib/core/spawn-tee.ts +202 -0
  42. package/lib/core/state-map.js +92 -92
  43. package/lib/core/state-map.ts +115 -0
  44. package/lib/core/storage.js +101 -129
  45. package/lib/core/storage.ts +182 -0
  46. package/lib/core/verification.js +148 -138
  47. package/lib/core/verification.ts +200 -0
  48. package/package.json +4 -2
  49. package/prompts/draft.md +1 -0
  50. package/prompts/portfolio.md +1 -1
  51. package/px.js +0 -2
@@ -1,160 +1,150 @@
1
- const { spawnAndTee } = require('../core/spawn-tee');
2
- const { extractClaudeTelemetryFromStdout } = require('./claude-telemetry');
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
- /** @type {Function} */
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
- /** @param {Function} fn */
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
- if (!stdout) {return null;}
30
- const lines = stdout.split('\n');
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
- return null;
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
- if (!stdout) {return null;}
48
- // Try stream-json result event first (new default format).
49
- const fromStream = extractClaudeSessionIdFromStreamJson(stdout);
50
- if (fromStream) {return fromStream;}
51
- // Fall back to legacy plain-text regex.
52
- const m = CLAUDE_SESSION_ID_RE.exec(stdout);
53
- return m ? m[1] : null;
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
- return 'claude';
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
- const args = ['--dangerously-skip-permissions'];
65
- if (model) {args.push('--model', model);}
66
- // Only resume when the marker explicitly says so (resume=true).
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
- * @param {{stdout?: string, stderr?: string}} result
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
- } catch (_) {
120
- // Telemetry is best-effort; never let it break the launch result.
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
- return result;
124
- }
125
-
126
- /**
127
- * @param {{command: string, args: string[], options?: object}} invocation
128
- */
129
- function staleSessionHandler(invocation) {
130
- const teeWithTail = { maxTailBytes: CLAUDE_TELEMETRY_TAIL_BYTES, ...invocation.options, ...teeOptions };
131
- return _spawnAndTee(invocation.command, invocation.args, teeWithTail)
132
- .then(/** @param {object} result */ (result) => {
133
- if (isStaleSessionResult(result) && worktree && resume) {
134
- try {
135
- /** @type {any} */(_sessions).clearSession(worktree, slug, role);
136
- } catch (_) { /* best-effort */ }
137
- const freshInv = buildClaudeInvocation({ prompt, worktree, env, resume: false, sessionId: null, model });
138
- const freshTee = { maxTailBytes: CLAUDE_TELEMETRY_TAIL_BYTES, ...freshInv.options, ...teeOptions };
139
- return _spawnAndTee(freshInv.command, freshInv.args, freshTee);
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
- .then(processResult);
144
- }
145
-
146
- const invocation = buildClaudeInvocation({ prompt, worktree, env, resume, sessionId, model });
147
- const resultPromise = staleSessionHandler(invocation);
148
-
149
- return { invocation, resultPromise };
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
+ };