@probelabs/probe 0.6.0-rc331 → 0.6.0-rc334

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 (45) hide show
  1. package/bin/binaries/{probe-v0.6.0-rc331-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc334-aarch64-apple-darwin.tar.gz} +0 -0
  2. package/bin/binaries/{probe-v0.6.0-rc331-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc334-aarch64-unknown-linux-musl.tar.gz} +0 -0
  3. package/bin/binaries/{probe-v0.6.0-rc331-x86_64-apple-darwin.tar.gz → probe-v0.6.0-rc334-x86_64-apple-darwin.tar.gz} +0 -0
  4. package/bin/binaries/{probe-v0.6.0-rc331-x86_64-pc-windows-msvc.zip → probe-v0.6.0-rc334-x86_64-pc-windows-msvc.zip} +0 -0
  5. package/bin/binaries/{probe-v0.6.0-rc331-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc334-x86_64-unknown-linux-musl.tar.gz} +0 -0
  6. package/build/agent/ProbeAgent.d.ts +105 -4
  7. package/build/agent/ProbeAgent.js +209 -12
  8. package/build/agent/bashExecutor.js +36 -101
  9. package/build/agent/engines/codex.js +367 -88
  10. package/build/agent/engines/governed-answer-failure.js +152 -0
  11. package/build/agent/engines/governed-codex-profile.js +198 -0
  12. package/build/agent/governance/acknowledgedJsonlChannel.js +328 -0
  13. package/build/agent/governance/atomicTerminalReceipt.js +188 -0
  14. package/build/agent/governance/index.d.ts +130 -0
  15. package/build/agent/governance/index.js +8 -0
  16. package/build/agent/mcp/built-in-server.js +152 -53
  17. package/build/agent/mcp/index.d.ts +65 -0
  18. package/build/agent/mcp/index.js +6 -1
  19. package/build/agent/probeTool.js +1 -1
  20. package/build/agent/processSupervisor.js +351 -0
  21. package/build/agent/tools.js +14 -8
  22. package/build/index.js +2 -0
  23. package/build/utils/provider.js +9 -3
  24. package/cjs/agent/ProbeAgent.cjs +13463 -12187
  25. package/cjs/index.cjs +75974 -74139
  26. package/index.d.ts +149 -4
  27. package/package.json +6 -2
  28. package/src/agent/ProbeAgent.d.ts +105 -4
  29. package/src/agent/ProbeAgent.js +209 -12
  30. package/src/agent/bashExecutor.js +36 -101
  31. package/src/agent/engines/codex.js +367 -88
  32. package/src/agent/engines/governed-answer-failure.js +152 -0
  33. package/src/agent/engines/governed-codex-profile.js +198 -0
  34. package/src/agent/governance/acknowledgedJsonlChannel.js +328 -0
  35. package/src/agent/governance/atomicTerminalReceipt.js +188 -0
  36. package/src/agent/governance/index.d.ts +130 -0
  37. package/src/agent/governance/index.js +8 -0
  38. package/src/agent/mcp/built-in-server.js +152 -53
  39. package/src/agent/mcp/index.d.ts +65 -0
  40. package/src/agent/mcp/index.js +6 -1
  41. package/src/agent/probeTool.js +1 -1
  42. package/src/agent/processSupervisor.js +351 -0
  43. package/src/agent/tools.js +14 -8
  44. package/src/index.js +2 -0
  45. package/src/utils/provider.js +9 -3
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Governed child-process execution with bounded output and explicit lifecycle facts.
3
+ * The ChildProcess and its PID intentionally never cross this module boundary.
4
+ * @module agent/processSupervisor
5
+ */
6
+
7
+ import { spawn } from 'child_process';
8
+ import { randomBytes } from 'crypto';
9
+
10
+ const DEFAULT_TERMINATION_GRACE_MS = 5000;
11
+ const DEFAULT_CLEANUP_TIMEOUT_MS = 10000;
12
+ const DEFAULT_STREAM_BYTE_CAP = 10 * 1024 * 1024;
13
+
14
+ let nextProcessSequence = 0;
15
+
16
+ function makeId() {
17
+ nextProcessSequence += 1;
18
+ return `process-${nextProcessSequence.toString(36)}-${randomBytes(6).toString('hex')}`;
19
+ }
20
+
21
+ function nonNegativeNumber(value, fallback, name) {
22
+ if (value === undefined) return fallback;
23
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
24
+ throw new TypeError(`${name} must be a non-negative finite number`);
25
+ }
26
+ return value;
27
+ }
28
+
29
+ function byteCap(value, fallback, name) {
30
+ const cap = nonNegativeNumber(value, fallback, name);
31
+ if (!Number.isSafeInteger(cap)) {
32
+ throw new TypeError(`${name} must be a safe integer`);
33
+ }
34
+ return cap;
35
+ }
36
+
37
+ function makeSettledHandle(id, error) {
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ const receipt = Object.freeze({
40
+ id,
41
+ classification: 'spawn_error',
42
+ reason: 'spawn_error',
43
+ error: message,
44
+ stdout: '',
45
+ stderr: '',
46
+ stdoutBytes: 0,
47
+ stderrBytes: 0,
48
+ exitCode: null,
49
+ signal: null,
50
+ barriers: Object.freeze({ close: false, stdoutEOF: false, stderrEOF: false }),
51
+ observed: Object.freeze([Object.freeze({ sequence: 1, fact: 'spawn-error', error: message })])
52
+ });
53
+ const result = Promise.resolve(receipt);
54
+ return Object.freeze({ id, terminate: () => result, result });
55
+ }
56
+
57
+ /**
58
+ * Spawn a process whose resources and termination are governed by fixed deadlines.
59
+ *
60
+ * @param {Object} spec
61
+ * @param {string} spec.command
62
+ * @param {string[]} [spec.args]
63
+ * @param {string} [spec.cwd]
64
+ * @param {NodeJS.ProcessEnv} [spec.env]
65
+ * @param {AbortSignal} [spec.signal]
66
+ * @param {number} [spec.executionTimeoutMs=0]
67
+ * @param {number} [spec.terminationGraceMs=5000]
68
+ * @param {number} [spec.cleanupTimeoutMs=10000] Must be at least terminationGraceMs.
69
+ * @param {number} [spec.stdoutByteCap=10485760]
70
+ * @param {number} [spec.stderrByteCap=10485760]
71
+ * @param {'child'|'process-group'} [spec.signalScope='child']
72
+ * @returns {{id: string, terminate: (reason?: string) => Promise<Object>, result: Promise<Object>}}
73
+ */
74
+ function governProcess(spec, attachedChild = null) {
75
+ if (!spec || typeof spec !== 'object') {
76
+ throw new TypeError('spec must be an object');
77
+ }
78
+ if (typeof spec.command !== 'string' || spec.command.length === 0) {
79
+ throw new TypeError('command must be a non-empty string');
80
+ }
81
+ if (spec.args !== undefined && (!Array.isArray(spec.args) || spec.args.some(arg => typeof arg !== 'string'))) {
82
+ throw new TypeError('args must be an array of strings');
83
+ }
84
+ if (spec.signal !== undefined && (
85
+ !spec.signal ||
86
+ typeof spec.signal !== 'object' ||
87
+ typeof spec.signal.aborted !== 'boolean' ||
88
+ typeof spec.signal.addEventListener !== 'function' ||
89
+ typeof spec.signal.removeEventListener !== 'function'
90
+ )) {
91
+ throw new TypeError('signal must be an AbortSignal');
92
+ }
93
+
94
+ const executionTimeoutMs = nonNegativeNumber(spec.executionTimeoutMs, 0, 'executionTimeoutMs');
95
+ const terminationGraceMs = nonNegativeNumber(spec.terminationGraceMs, DEFAULT_TERMINATION_GRACE_MS, 'terminationGraceMs');
96
+ const cleanupTimeoutMs = nonNegativeNumber(spec.cleanupTimeoutMs, DEFAULT_CLEANUP_TIMEOUT_MS, 'cleanupTimeoutMs');
97
+ if (cleanupTimeoutMs < terminationGraceMs) {
98
+ throw new TypeError('cleanupTimeoutMs must be greater than or equal to terminationGraceMs');
99
+ }
100
+ const stdoutByteCap = byteCap(spec.stdoutByteCap, DEFAULT_STREAM_BYTE_CAP, 'stdoutByteCap');
101
+ const stderrByteCap = byteCap(spec.stderrByteCap, DEFAULT_STREAM_BYTE_CAP, 'stderrByteCap');
102
+ const captureStdout = !attachedChild || spec.captureStdout !== false;
103
+ const signalScope = spec.signalScope ?? 'child';
104
+ if (signalScope !== 'child' && signalScope !== 'process-group') {
105
+ throw new TypeError("signalScope must be 'child' or 'process-group'");
106
+ }
107
+
108
+ const id = makeId();
109
+ let child;
110
+ if (attachedChild) {
111
+ child = attachedChild;
112
+ } else {
113
+ try {
114
+ child = spawn(spec.command, spec.args ?? [], {
115
+ cwd: spec.cwd,
116
+ env: spec.env,
117
+ stdio: ['ignore', 'pipe', 'pipe'],
118
+ shell: false,
119
+ detached: signalScope === 'process-group',
120
+ windowsHide: true
121
+ });
122
+ } catch (error) {
123
+ return makeSettledHandle(id, error);
124
+ }
125
+ }
126
+
127
+ let resolveResult;
128
+ const result = new Promise(resolve => { resolveResult = resolve; });
129
+ const observed = [];
130
+ const barriers = { close: false, stdoutEOF: false, stderrEOF: false };
131
+ const stdoutChunks = [];
132
+ const stderrChunks = [];
133
+ let stdoutBytes = 0;
134
+ let stderrBytes = 0;
135
+ let exitCode = null;
136
+ let exitSignal = null;
137
+ let exitObserved = false;
138
+ let settled = false;
139
+ let terminalReason = null;
140
+ let terminalClassification = null;
141
+ let terminalError = null;
142
+ let lastSignalDelivery = null;
143
+ let executionTimer = null;
144
+ let escalationTimer = null;
145
+ let cleanupTimer = null;
146
+
147
+ const observe = (fact, details = {}) => {
148
+ if (settled) return;
149
+ observed.push(Object.freeze({ sequence: observed.length + 1, fact, ...details }));
150
+ };
151
+
152
+ const startTimer = (callback, delay) => {
153
+ const timer = setTimeout(callback, delay);
154
+ timer.unref?.();
155
+ return timer;
156
+ };
157
+
158
+ const clearTimer = (timer) => {
159
+ if (timer) clearTimeout(timer);
160
+ };
161
+
162
+ const settle = (classification, reason = terminalReason) => {
163
+ if (settled) return;
164
+ settled = true;
165
+ clearTimer(executionTimer);
166
+ clearTimer(cleanupTimer);
167
+ clearTimer(escalationTimer);
168
+ if (spec.signal) spec.signal.removeEventListener('abort', onAbort);
169
+
170
+ resolveResult(Object.freeze({
171
+ id,
172
+ classification,
173
+ reason: reason ?? null,
174
+ ...(terminalError ? { error: terminalError } : {}),
175
+ stdout: Buffer.concat(stdoutChunks, stdoutBytes).toString(),
176
+ stderr: Buffer.concat(stderrChunks, stderrBytes).toString(),
177
+ stdoutBytes,
178
+ stderrBytes,
179
+ exitCode,
180
+ signal: exitSignal,
181
+ barriers: Object.freeze({ ...barriers }),
182
+ observed: Object.freeze([...observed])
183
+ }));
184
+ };
185
+
186
+ const fullBarrierObserved = () => barriers.close && barriers.stdoutEOF && barriers.stderrEOF;
187
+
188
+ const maybeSettle = () => {
189
+ if (settled || !exitObserved || !fullBarrierObserved()) return;
190
+ settle(terminalClassification ?? 'exited', terminalReason);
191
+ };
192
+
193
+ const startCleanupDeadline = () => {
194
+ if (cleanupTimer || settled) return;
195
+ cleanupTimer = startTimer(() => {
196
+ observe('barrier', { barrier: 'cleanup_deadline' });
197
+ child.stdout.destroy();
198
+ child.stderr.destroy();
199
+ settle('cleanup_timeout', terminalReason ?? 'cleanup_timeout');
200
+ }, cleanupTimeoutMs);
201
+ };
202
+
203
+ const attemptSignal = (signal) => {
204
+ let accepted = false;
205
+ if (settled || exitObserved) return;
206
+ if (!child.pid) {
207
+ observe('signal-attempt', {
208
+ signal,
209
+ requestedScope: signalScope,
210
+ actualScope: null,
211
+ accepted: false
212
+ });
213
+ return;
214
+ }
215
+ let actualScope = null;
216
+ try {
217
+ if (signalScope === 'process-group') {
218
+ process.kill(-child.pid, signal);
219
+ accepted = true;
220
+ actualScope = 'process-group';
221
+ } else {
222
+ accepted = child.kill(signal);
223
+ if (accepted) actualScope = 'child';
224
+ }
225
+ } catch {
226
+ try {
227
+ accepted = child.kill(signal);
228
+ if (accepted) actualScope = 'child';
229
+ } catch {
230
+ accepted = false;
231
+ }
232
+ }
233
+ observe('signal-attempt', {
234
+ signal,
235
+ requestedScope: signalScope,
236
+ actualScope,
237
+ accepted: Boolean(accepted)
238
+ });
239
+ if (accepted) lastSignalDelivery = { signal, actualScope };
240
+ };
241
+
242
+ const beginTermination = (reason, classification = 'terminated') => {
243
+ if (settled || exitObserved || terminalClassification) return result;
244
+ terminalReason = typeof reason === 'string' && reason.length > 0 ? reason : 'terminated';
245
+ terminalClassification = classification;
246
+ clearTimer(executionTimer);
247
+ attemptSignal('SIGTERM');
248
+ escalationTimer = startTimer(() => attemptSignal('SIGKILL'), terminationGraceMs);
249
+ startCleanupDeadline();
250
+ return result;
251
+ };
252
+
253
+ const appendChunk = (stream, data) => {
254
+ if (settled) return;
255
+ const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data);
256
+ const isStdout = stream === 'stdout';
257
+ const used = isStdout ? stdoutBytes : stderrBytes;
258
+ const cap = isStdout ? stdoutByteCap : stderrByteCap;
259
+ const remaining = Math.max(0, cap - used);
260
+ const kept = remaining >= chunk.length ? chunk : chunk.subarray(0, remaining);
261
+ if (kept.length > 0) {
262
+ (isStdout ? stdoutChunks : stderrChunks).push(kept);
263
+ if (isStdout) stdoutBytes += kept.length;
264
+ else stderrBytes += kept.length;
265
+ }
266
+ if (chunk.length > remaining) {
267
+ beginTermination(`${stream}_overflow`, 'output_overflow');
268
+ }
269
+ };
270
+
271
+ const observeBarrier = (barrier) => {
272
+ if (settled || barriers[barrier]) return;
273
+ barriers[barrier] = true;
274
+ observe('barrier', { barrier });
275
+ maybeSettle();
276
+ };
277
+
278
+ const onAbort = () => beginTermination('aborted', 'aborted');
279
+
280
+ if (captureStdout) child.stdout.on('data', data => appendChunk('stdout', data));
281
+ child.stderr.on('data', data => appendChunk('stderr', data));
282
+ child.stdout.once('end', () => observeBarrier('stdoutEOF'));
283
+ child.stderr.once('end', () => observeBarrier('stderrEOF'));
284
+
285
+ child.once('exit', (code, signal) => {
286
+ if (settled) return;
287
+ exitObserved = true;
288
+ clearTimer(executionTimer);
289
+ clearTimer(escalationTimer);
290
+ exitCode = code;
291
+ exitSignal = signal;
292
+ observe('exit', { code, signal });
293
+ if (signal) {
294
+ observe('signal', {
295
+ signal,
296
+ requestedScope: lastSignalDelivery?.signal === signal ? signalScope : null,
297
+ actualScope: lastSignalDelivery?.signal === signal ? lastSignalDelivery.actualScope : null
298
+ });
299
+ }
300
+ startCleanupDeadline();
301
+ maybeSettle();
302
+ });
303
+
304
+ child.once('close', () => observeBarrier('close'));
305
+
306
+ child.once('error', error => {
307
+ if (settled) return;
308
+ terminalError = error.message;
309
+ observe('spawn-error', { error: error.message });
310
+ settle('spawn_error', 'spawn_error');
311
+ });
312
+
313
+ if (executionTimeoutMs > 0) {
314
+ executionTimer = startTimer(
315
+ () => beginTermination('execution_timeout', 'execution_timeout'),
316
+ executionTimeoutMs
317
+ );
318
+ }
319
+
320
+ if (spec.signal) {
321
+ spec.signal.addEventListener('abort', onAbort, { once: true });
322
+ if (spec.signal.aborted) onAbort();
323
+ }
324
+
325
+ return Object.freeze({
326
+ id,
327
+ terminate: reason => beginTermination(reason),
328
+ result
329
+ });
330
+ }
331
+
332
+ export function spawnGovernedProcess(spec) {
333
+ return governProcess(spec);
334
+ }
335
+
336
+ /**
337
+ * Internal duplex adapter for engines that must retain protocol access to a child.
338
+ * The returned handle keeps the same bounded termination and close/EOF barriers as
339
+ * spawnGovernedProcess without exposing the child through the public governance API.
340
+ *
341
+ * @param {import('child_process').ChildProcess} child
342
+ * @param {Object} [spec]
343
+ * @returns {{id: string, terminate: (reason?: string) => Promise<Object>, result: Promise<Object>}}
344
+ */
345
+ export function governSpawnedProcess(child, spec = {}) {
346
+ if (!child || typeof child !== 'object' || typeof child.kill !== 'function' ||
347
+ !child.stdout || !child.stderr) {
348
+ throw new TypeError('child must be a spawned process with stdout and stderr pipes');
349
+ }
350
+ return governProcess({ ...spec, command: 'attached-child' }, child);
351
+ }
@@ -5,14 +5,23 @@ import {
5
5
  extractTool,
6
6
  delegateTool,
7
7
  analyzeAllTool,
8
- symbolsTool,
8
+ symbolsTool
9
+ } from '../tools/vercel.js';
10
+ import {
9
11
  createExecutePlanTool,
10
- createCleanupExecutePlanTool,
11
- bashTool,
12
+ createCleanupExecutePlanTool
13
+ } from '../tools/executePlan.js';
14
+ import { bashTool } from '../tools/bash.js';
15
+ import {
12
16
  editTool,
13
17
  createTool,
14
18
  multiEditTool,
15
- DEFAULT_SYSTEM_MESSAGE,
19
+ editSchema,
20
+ createSchema,
21
+ multiEditSchema
22
+ } from '../tools/edit.js';
23
+ import { DEFAULT_SYSTEM_MESSAGE } from '../tools/system-message.js';
24
+ import {
16
25
  searchSchema,
17
26
  querySchema,
18
27
  extractSchema,
@@ -21,9 +30,6 @@ import {
21
30
  executePlanSchema,
22
31
  cleanupExecutePlanSchema,
23
32
  bashSchema,
24
- editSchema,
25
- createSchema,
26
- multiEditSchema,
27
33
  listFilesSchema,
28
34
  searchFilesSchema,
29
35
  readImageSchema,
@@ -31,7 +37,7 @@ import {
31
37
  symbolsSchema,
32
38
  listSkillsSchema,
33
39
  useSkillSchema
34
- } from '../index.js';
40
+ } from '../tools/common.js';
35
41
 
36
42
  // Create configured tool instances
37
43
  export function createTools(configOptions) {
package/build/index.js CHANGED
@@ -50,6 +50,7 @@ import { editTool, createTool, multiEditTool } from './tools/edit.js';
50
50
  import { FileTracker } from './tools/fileTracker.js';
51
51
  import { ProbeAgent, ENGINE_ACTIVITY_TIMEOUT_DEFAULT, ENGINE_ACTIVITY_TIMEOUT_MIN, ENGINE_ACTIVITY_TIMEOUT_MAX } from './agent/ProbeAgent.js';
52
52
  import { SimpleTelemetry, SimpleAppTracer, initializeSimpleTelemetryFromOptions } from './agent/simpleTelemetry.js';
53
+ import { spawnGovernedProcess } from './agent/processSupervisor.js';
53
54
  import { listFilesToolInstance, searchFilesToolInstance } from './agent/probeTool.js';
54
55
  import { StorageAdapter, InMemoryStorageAdapter } from './agent/storage/index.js';
55
56
  import { HookManager, HOOK_TYPES } from './agent/hooks/index.js';
@@ -88,6 +89,7 @@ export {
88
89
  SimpleTelemetry,
89
90
  SimpleAppTracer,
90
91
  initializeSimpleTelemetryFromOptions,
92
+ spawnGovernedProcess,
91
93
  // Export tool generators directly
92
94
  searchTool,
93
95
  queryTool,
@@ -30,12 +30,18 @@ export function createProviderInstance(config) {
30
30
  ...(config.baseURL && { baseURL: config.baseURL })
31
31
  });
32
32
 
33
- case 'openai':
34
- return createOpenAI({
33
+ case 'openai': {
34
+ const openai = createOpenAI({
35
35
  compatibility: 'strict',
36
36
  apiKey: config.apiKey,
37
37
  ...(config.baseURL && { baseURL: config.baseURL })
38
38
  });
39
+ const chatProvider = (modelId, settings) => openai.chat(modelId, settings);
40
+ chatProvider.chat = openai.chat.bind(openai);
41
+ chatProvider.responses = openai.responses.bind(openai);
42
+ chatProvider.languageModel = openai.languageModel.bind(openai);
43
+ return chatProvider;
44
+ }
39
45
 
40
46
  case 'google':
41
47
  return createGoogleGenerativeAI({
@@ -97,7 +103,7 @@ export function resolveBaseUrl(providerName) {
97
103
  case 'anthropic':
98
104
  return process.env.ANTHROPIC_API_URL || process.env.ANTHROPIC_BASE_URL || llmBaseUrl;
99
105
  case 'openai':
100
- return process.env.OPENAI_API_URL || llmBaseUrl;
106
+ return process.env.OPENAI_API_URL || process.env.OPENAI_BASE_URL || llmBaseUrl;
101
107
  case 'google':
102
108
  return process.env.GOOGLE_API_URL || llmBaseUrl;
103
109
  case 'bedrock':