@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
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.captureOpencodeExport = captureOpencodeExport;
|
|
7
|
+
const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
11
|
/**
|
|
9
12
|
* Capture the complete `opencode export <sessionId>` JSON document.
|
|
10
13
|
*
|
|
@@ -35,125 +38,121 @@ const path = require('path');
|
|
|
35
38
|
* @returns {Promise<string|null>} Full JSON string, or null on failure
|
|
36
39
|
*/
|
|
37
40
|
function captureOpencodeExport(sessionId, opts = {}) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
finish(content);
|
|
41
|
+
const { worktree, env, timeoutMs = 30000, maxBytes = 32 * 1024 * 1024, spawn = node_child_process_1.default.spawn, } = opts;
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
if (!sessionId) {
|
|
44
|
+
resolve(null);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
let settled = false;
|
|
48
|
+
let timer = null;
|
|
49
|
+
let size = 0;
|
|
50
|
+
let tmpFd = null;
|
|
51
|
+
let tmpPath = null;
|
|
52
|
+
const finish = (value) => {
|
|
53
|
+
if (settled) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
settled = true;
|
|
57
|
+
if (timer) {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
}
|
|
60
|
+
// Clean up temp file
|
|
61
|
+
if (tmpFd !== null) {
|
|
62
|
+
try {
|
|
63
|
+
node_fs_1.default.closeSync(tmpFd);
|
|
64
|
+
}
|
|
65
|
+
catch (_) { /* already closed */ }
|
|
66
|
+
}
|
|
67
|
+
if (tmpPath) {
|
|
68
|
+
try {
|
|
69
|
+
node_fs_1.default.unlinkSync(tmpPath);
|
|
70
|
+
}
|
|
71
|
+
catch (_) { /* already gone */ }
|
|
72
|
+
}
|
|
73
|
+
resolve(value);
|
|
74
|
+
};
|
|
75
|
+
const killChild = (child) => {
|
|
76
|
+
try {
|
|
77
|
+
if (child) {
|
|
78
|
+
child.kill('SIGKILL');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (_) { /* already gone */ }
|
|
82
|
+
};
|
|
83
|
+
// Create a temp file for stdout to avoid pipe-buffer truncation
|
|
84
|
+
let tmpFileError = null;
|
|
85
|
+
try {
|
|
86
|
+
tmpPath = node_path_1.default.join(node_os_1.default.tmpdir(), `opencode-export-${process.pid}-${Date.now()}.json`);
|
|
87
|
+
tmpFd = node_fs_1.default.openSync(tmpPath, 'w');
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
tmpFileError = e;
|
|
91
|
+
}
|
|
92
|
+
let child;
|
|
93
|
+
try {
|
|
94
|
+
child = spawn('opencode', ['export', sessionId], {
|
|
95
|
+
cwd: worktree,
|
|
96
|
+
env: { ...process.env, ...(env || {}) },
|
|
97
|
+
stdio: ['ignore', tmpFd !== null ? tmpFd : 'pipe', 'pipe'],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (_) {
|
|
101
|
+
finish(null);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (tmpFileError) {
|
|
105
|
+
killChild(child);
|
|
106
|
+
finish(null);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
timer = setTimeout(() => {
|
|
110
|
+
// Hung or slow export: kill it and degrade to null. Do not block.
|
|
111
|
+
killChild(child);
|
|
112
|
+
finish(null);
|
|
113
|
+
}, timeoutMs);
|
|
114
|
+
// Intentionally NOT unref'd: the caller always awaits this Promise, and the
|
|
115
|
+
// timer is cleared via finish() the moment the export resolves. Leaving it
|
|
116
|
+
// ref'd keeps the timeout deterministic under Node's test runner (an unref'd
|
|
117
|
+
// timer can be skipped when the loop is otherwise idle, cancelling the test).
|
|
118
|
+
// Consume stderr to prevent pipe-buffer issues with the opencode binary
|
|
119
|
+
if (child.stderr) {
|
|
120
|
+
child.stderr.on('data', () => { });
|
|
121
|
+
}
|
|
122
|
+
child.on('error', () => finish(null));
|
|
123
|
+
child.on('close', () => {
|
|
124
|
+
if (settled) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (tmpFd === null) {
|
|
128
|
+
finish(null);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
node_fs_1.default.closeSync(tmpFd);
|
|
133
|
+
}
|
|
134
|
+
catch (_) { /* already closed */ }
|
|
135
|
+
tmpFd = null;
|
|
136
|
+
let content;
|
|
137
|
+
if (!tmpPath) {
|
|
138
|
+
finish(null);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
content = node_fs_1.default.readFileSync(tmpPath, 'utf8');
|
|
143
|
+
}
|
|
144
|
+
catch (_) {
|
|
145
|
+
finish(null);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
size = Buffer.byteLength(content);
|
|
149
|
+
if (size > maxBytes) {
|
|
150
|
+
// Explicit failure rather than silent truncation: a partial JSON
|
|
151
|
+
// document would parse to null/garbage and fabricate zero telemetry.
|
|
152
|
+
finish(null);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
finish(content);
|
|
156
|
+
});
|
|
155
157
|
});
|
|
156
|
-
});
|
|
157
158
|
}
|
|
158
|
-
|
|
159
|
-
module.exports = { captureOpencodeExport };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import childProcess from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
interface CaptureOpencodeExportOptions {
|
|
7
|
+
worktree?: string;
|
|
8
|
+
env?: Record<string, string>;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
maxBytes?: number;
|
|
11
|
+
spawn?: typeof childProcess.spawn;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Capture the complete `opencode export <sessionId>` JSON document.
|
|
16
|
+
*
|
|
17
|
+
* Unlike the limit-hit tail buffer in spawn-tee.js, telemetry needs the WHOLE
|
|
18
|
+
* export document (it is a single JSON object beginning with `{`), so a tail
|
|
19
|
+
* buffer would discard the prefix and yield invalid JSON. This collector keeps
|
|
20
|
+
* the full stdout in memory up to an explicit cap and, critically:
|
|
21
|
+
*
|
|
22
|
+
* - Enforces a timeout that kills a hung/non-exiting export child so the
|
|
23
|
+
* launcher can never block on it (mission risk + stop rule).
|
|
24
|
+
* - Fails explicitly (resolves null) when output exceeds `maxBytes` rather
|
|
25
|
+
* than silently truncating into unparseable JSON.
|
|
26
|
+
*
|
|
27
|
+
* Uses a temp-file fd for stdout to avoid pipe-buffer data loss that occurs
|
|
28
|
+
* with large (>100 KB) child-process output. The child writes directly to
|
|
29
|
+
* the fd; after exit we read the file back as a string.
|
|
30
|
+
*
|
|
31
|
+
* Never rejects: any error/timeout/oversize resolves to null so telemetry
|
|
32
|
+
* capture stays best-effort and cannot break the launch.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} sessionId - opencode session id (e.g. ses_...)
|
|
35
|
+
* @param {object} opts
|
|
36
|
+
* @param {string} [opts.worktree] - cwd for the export process
|
|
37
|
+
* @param {object} [opts.env] - extra env vars
|
|
38
|
+
* @param {number} [opts.timeoutMs] - hard timeout before the child is killed
|
|
39
|
+
* @param {number} [opts.maxBytes] - explicit byte cap; exceeding it fails
|
|
40
|
+
* @param {Function} [opts.spawn] - injectable spawn (defaults to child_process.spawn)
|
|
41
|
+
* @returns {Promise<string|null>} Full JSON string, or null on failure
|
|
42
|
+
*/
|
|
43
|
+
function captureOpencodeExport(sessionId: string, opts: CaptureOpencodeExportOptions = {}) {
|
|
44
|
+
const {
|
|
45
|
+
worktree,
|
|
46
|
+
env,
|
|
47
|
+
timeoutMs = 30000,
|
|
48
|
+
maxBytes = 32 * 1024 * 1024,
|
|
49
|
+
spawn = childProcess.spawn,
|
|
50
|
+
} = opts;
|
|
51
|
+
|
|
52
|
+
return new Promise<string | null>((resolve) => {
|
|
53
|
+
if (!sessionId) {
|
|
54
|
+
resolve(null);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let settled = false;
|
|
59
|
+
let timer: NodeJS.Timeout | null = null;
|
|
60
|
+
let size = 0;
|
|
61
|
+
let tmpFd: number | null = null;
|
|
62
|
+
let tmpPath: string | null = null;
|
|
63
|
+
|
|
64
|
+
const finish = (value: string | null) => {
|
|
65
|
+
if (settled) {return;}
|
|
66
|
+
settled = true;
|
|
67
|
+
if (timer) {clearTimeout(timer);}
|
|
68
|
+
// Clean up temp file
|
|
69
|
+
if (tmpFd !== null) {
|
|
70
|
+
try { fs.closeSync(tmpFd); } catch (_) { /* already closed */ }
|
|
71
|
+
}
|
|
72
|
+
if (tmpPath) {
|
|
73
|
+
try { fs.unlinkSync(tmpPath); } catch (_) { /* already gone */ }
|
|
74
|
+
}
|
|
75
|
+
resolve(value);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const killChild = (child: childProcess.ChildProcess | undefined) => {
|
|
79
|
+
try { if (child) { child.kill('SIGKILL'); } } catch (_) { /* already gone */ }
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Create a temp file for stdout to avoid pipe-buffer truncation
|
|
83
|
+
let tmpFileError = null;
|
|
84
|
+
try {
|
|
85
|
+
tmpPath = path.join(os.tmpdir(), `opencode-export-${process.pid}-${Date.now()}.json`);
|
|
86
|
+
tmpFd = fs.openSync(tmpPath, 'w');
|
|
87
|
+
} catch (e) {
|
|
88
|
+
tmpFileError = e;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let child;
|
|
92
|
+
try {
|
|
93
|
+
child = spawn('opencode', ['export', sessionId], {
|
|
94
|
+
cwd: worktree,
|
|
95
|
+
env: { ...process.env, ...(env || {}) },
|
|
96
|
+
stdio: ['ignore', tmpFd !== null ? tmpFd : 'pipe', 'pipe'],
|
|
97
|
+
});
|
|
98
|
+
} catch (_) {
|
|
99
|
+
finish(null);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (tmpFileError) {
|
|
104
|
+
killChild(child);
|
|
105
|
+
finish(null);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
timer = setTimeout(() => {
|
|
110
|
+
// Hung or slow export: kill it and degrade to null. Do not block.
|
|
111
|
+
killChild(child);
|
|
112
|
+
finish(null);
|
|
113
|
+
}, timeoutMs);
|
|
114
|
+
// Intentionally NOT unref'd: the caller always awaits this Promise, and the
|
|
115
|
+
// timer is cleared via finish() the moment the export resolves. Leaving it
|
|
116
|
+
// ref'd keeps the timeout deterministic under Node's test runner (an unref'd
|
|
117
|
+
// timer can be skipped when the loop is otherwise idle, cancelling the test).
|
|
118
|
+
|
|
119
|
+
// Consume stderr to prevent pipe-buffer issues with the opencode binary
|
|
120
|
+
if (child.stderr) {
|
|
121
|
+
child.stderr.on('data', () => { /* drain stderr */ });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
child.on('error', () => finish(null));
|
|
125
|
+
|
|
126
|
+
child.on('close', () => {
|
|
127
|
+
if (settled) {return;}
|
|
128
|
+
if (tmpFd === null) {
|
|
129
|
+
finish(null);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
fs.closeSync(tmpFd);
|
|
135
|
+
} catch (_) { /* already closed */ }
|
|
136
|
+
tmpFd = null;
|
|
137
|
+
|
|
138
|
+
let content;
|
|
139
|
+
if (!tmpPath) { finish(null); return; }
|
|
140
|
+
try {
|
|
141
|
+
content = fs.readFileSync(tmpPath, 'utf8');
|
|
142
|
+
} catch (_) {
|
|
143
|
+
finish(null);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
size = Buffer.byteLength(content);
|
|
148
|
+
if (size > maxBytes) {
|
|
149
|
+
// Explicit failure rather than silent truncation: a partial JSON
|
|
150
|
+
// document would parse to null/garbage and fabricate zero telemetry.
|
|
151
|
+
finish(null);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
finish(content);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export { captureOpencodeExport };
|