@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,10 +1,13 @@
1
- 'use strict';
2
-
3
- const childProcess = require('child_process');
4
- const fs = require('fs');
5
- const os = require('os');
6
- const path = require('path');
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
- const {
39
- worktree,
40
- env,
41
- timeoutMs = 30000,
42
- maxBytes = 32 * 1024 * 1024,
43
- spawn = childProcess.spawn,
44
- } = opts;
45
-
46
- return new Promise((resolve) => {
47
- if (!sessionId) {
48
- resolve(null);
49
- return;
50
- }
51
-
52
- let settled = false;
53
- /** @type {NodeJS.Timeout | null} */
54
- let timer = null;
55
- let size = 0;
56
- /** @type {number | null} */
57
- let tmpFd = null;
58
- /** @type {string | null} */
59
- let tmpPath = null;
60
-
61
- /** @param {string | null} value */
62
- const finish = (value) => {
63
- if (settled) {return;}
64
- settled = true;
65
- if (timer) {clearTimeout(timer);}
66
- // Clean up temp file
67
- if (tmpFd !== null) {
68
- try { fs.closeSync(tmpFd); } catch (_) { /* already closed */ }
69
- }
70
- if (tmpPath) {
71
- try { fs.unlinkSync(tmpPath); } catch (_) { /* already gone */ }
72
- }
73
- resolve(value);
74
- };
75
-
76
- /** @param {import('child_process').ChildProcess | undefined} child */
77
- const killChild = (child) => {
78
- try { if (child) { child.kill('SIGKILL'); } } catch (_) { /* already gone */ }
79
- };
80
-
81
- // Create a temp file for stdout to avoid pipe-buffer truncation
82
- let tmpFileError = null;
83
- try {
84
- tmpPath = path.join(os.tmpdir(), `opencode-export-${process.pid}-${Date.now()}.json`);
85
- tmpFd = fs.openSync(tmpPath, 'w');
86
- } catch (e) {
87
- tmpFileError = e;
88
- }
89
-
90
- let child;
91
- try {
92
- child = spawn('opencode', ['export', sessionId], {
93
- cwd: worktree,
94
- env: { ...process.env, ...(env || {}) },
95
- stdio: ['ignore', tmpFd !== null ? tmpFd : 'pipe', 'pipe'],
96
- });
97
- } catch (_) {
98
- finish(null);
99
- return;
100
- }
101
-
102
- if (tmpFileError) {
103
- killChild(child);
104
- finish(null);
105
- return;
106
- }
107
-
108
- timer = setTimeout(() => {
109
- // Hung or slow export: kill it and degrade to null. Do not block.
110
- killChild(child);
111
- finish(null);
112
- }, timeoutMs);
113
- // Intentionally NOT unref'd: the caller always awaits this Promise, and the
114
- // timer is cleared via finish() the moment the export resolves. Leaving it
115
- // ref'd keeps the timeout deterministic under Node's test runner (an unref'd
116
- // timer can be skipped when the loop is otherwise idle, cancelling the test).
117
-
118
- // Consume stderr to prevent pipe-buffer issues with the opencode binary
119
- if (child.stderr) {
120
- child.stderr.on('data', () => { /* drain stderr */ });
121
- }
122
-
123
- child.on('error', () => finish(null));
124
-
125
- child.on('close', () => {
126
- if (settled) {return;}
127
- if (tmpFd === null) {
128
- finish(null);
129
- return;
130
- }
131
-
132
- try {
133
- fs.closeSync(tmpFd);
134
- } catch (_) { /* already closed */ }
135
- tmpFd = null;
136
-
137
- let content;
138
- if (!tmpPath) { finish(null); return; }
139
- try {
140
- content = fs.readFileSync(tmpPath, 'utf8');
141
- } catch (_) {
142
- finish(null);
143
- return;
144
- }
145
-
146
- size = Buffer.byteLength(content);
147
- if (size > maxBytes) {
148
- // Explicit failure rather than silent truncation: a partial JSON
149
- // document would parse to null/garbage and fabricate zero telemetry.
150
- finish(null);
151
- return;
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 };