@the-open-engine/zeroshot 6.9.1 → 6.10.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/cli/commands/inspect.js +1 -0
- package/cli/index.js +17 -1
- package/cluster-hooks/block-ask-user-question.py +2 -3
- package/cluster-hooks/block-dangerous-git.py +2 -3
- package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/claude.js +57 -3
- package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +32 -2
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/adapters/common.js +1 -1
- package/lib/agent-cli-provider/adapters/common.js.map +1 -1
- package/lib/agent-cli-provider/adapters/index.d.ts +1 -0
- package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/index.js +8 -0
- package/lib/agent-cli-provider/adapters/index.js.map +1 -1
- package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-options.js +3 -0
- package/lib/agent-cli-provider/contract-options.js.map +1 -1
- package/lib/agent-cli-provider/index.d.ts +1 -1
- package/lib/agent-cli-provider/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/index.js +2 -1
- package/lib/agent-cli-provider/index.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +9 -0
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +3 -0
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +10 -2
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/package.json +1 -1
- package/src/agent/agent-context-builder.js +90 -4
- package/src/agent/agent-context-sources.js +20 -2
- package/src/agent/agent-lifecycle.js +43 -8
- package/src/agent/agent-task-executor.js +557 -314
- package/src/agent/guidance-queue.js +29 -7
- package/src/agent/provider-session.js +277 -0
- package/src/agent-cli-provider/adapters/claude.ts +64 -4
- package/src/agent-cli-provider/adapters/codex.ts +47 -3
- package/src/agent-cli-provider/adapters/common.ts +1 -1
- package/src/agent-cli-provider/adapters/index.ts +10 -0
- package/src/agent-cli-provider/contract-options.ts +7 -0
- package/src/agent-cli-provider/index.ts +1 -0
- package/src/agent-cli-provider/provider-registry.ts +13 -1
- package/src/agent-cli-provider/types.ts +13 -6
- package/src/agent-wrapper.js +44 -8
- package/src/claude-task-runner.js +153 -44
- package/src/ledger-sequence.js +63 -0
- package/src/ledger.js +114 -29
- package/src/orchestrator.js +40 -2
- package/src/task-spawn-cleanup-ownership.js +82 -0
- package/src/worktree-claude-config.js +193 -85
- package/task-lib/attachable-watcher.js +101 -253
- package/task-lib/command-spec-cleanup.js +219 -0
- package/task-lib/commands/clean.js +35 -5
- package/task-lib/commands/get-task-id-by-spawn-token.js +10 -0
- package/task-lib/commands/kill.js +117 -4
- package/task-lib/commands/resume.js +29 -6
- package/task-lib/commands/status.js +4 -0
- package/task-lib/provider-helper-runtime.js +1 -0
- package/task-lib/provider-session-capture.js +138 -0
- package/task-lib/runner.js +70 -5
- package/task-lib/store.js +126 -12
- package/task-lib/watcher-output-runtime.js +284 -0
- package/task-lib/watcher.js +131 -242
|
@@ -101,6 +101,9 @@ export interface ClaudeCliFeatures extends BaseCliFeatures {
|
|
|
101
101
|
readonly supportsVerbose: boolean;
|
|
102
102
|
readonly supportsModel: boolean;
|
|
103
103
|
readonly supportsEffort: boolean;
|
|
104
|
+
readonly supportsSettings: boolean;
|
|
105
|
+
readonly supportsMcpConfig: boolean;
|
|
106
|
+
readonly supportsResume: boolean;
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
export interface CodexCliFeatures extends BaseCliFeatures {
|
|
@@ -112,6 +115,7 @@ export interface CodexCliFeatures extends BaseCliFeatures {
|
|
|
112
115
|
readonly supportsConfigOverride: boolean;
|
|
113
116
|
readonly supportsModel: boolean;
|
|
114
117
|
readonly supportsSkipGitRepoCheck: boolean;
|
|
118
|
+
readonly supportsResume: boolean;
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
export interface GeminiCliFeatures extends BaseCliFeatures {
|
|
@@ -193,6 +197,7 @@ export interface CliFeatureOverrides {
|
|
|
193
197
|
readonly supportsVerbose?: boolean;
|
|
194
198
|
readonly supportsModel?: boolean;
|
|
195
199
|
readonly supportsEffort?: boolean;
|
|
200
|
+
readonly supportsSettings?: boolean;
|
|
196
201
|
readonly supportsJson?: boolean;
|
|
197
202
|
readonly supportsOutputSchema?: boolean;
|
|
198
203
|
readonly supportsDir?: boolean;
|
|
@@ -212,6 +217,7 @@ export interface CliFeatureOverrides {
|
|
|
212
217
|
readonly supportsNoAskUser?: boolean;
|
|
213
218
|
readonly supportsAddDir?: boolean;
|
|
214
219
|
readonly supportsMcpConfig?: boolean;
|
|
220
|
+
readonly supportsResume?: boolean;
|
|
215
221
|
readonly supportsBundledRunner?: boolean;
|
|
216
222
|
readonly supportsAcpStdio?: boolean;
|
|
217
223
|
readonly supportsPromptImages?: boolean;
|
|
@@ -228,10 +234,10 @@ export interface CliFeatureOverrides {
|
|
|
228
234
|
}
|
|
229
235
|
|
|
230
236
|
export interface CleanupMetadata {
|
|
231
|
-
readonly kind: 'temp-file';
|
|
237
|
+
readonly kind: 'temp-file' | 'temp-directory';
|
|
232
238
|
readonly provider: ProviderId;
|
|
233
239
|
readonly path: string;
|
|
234
|
-
readonly reason: 'output-schema';
|
|
240
|
+
readonly reason: 'output-schema' | 'settings-overlay';
|
|
235
241
|
}
|
|
236
242
|
|
|
237
243
|
export interface WarningMetadata {
|
|
@@ -265,14 +271,14 @@ export interface BuildProviderCommandOptions {
|
|
|
265
271
|
readonly autoApprove?: boolean;
|
|
266
272
|
readonly resumeSessionId?: string;
|
|
267
273
|
readonly continueSession?: boolean;
|
|
274
|
+
readonly claudeSettingsFile?: string;
|
|
268
275
|
readonly cliFeatures?: CliFeatureOverrides;
|
|
269
276
|
readonly authEnv?: Readonly<Record<string, string>>;
|
|
270
277
|
readonly strictSchema?: boolean;
|
|
271
278
|
readonly gateway?: GatewayBuildOptions;
|
|
272
|
-
// MCP server configs forwarded to providers that accept an MCP config CLI flag
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
// entry. Providers whose adapter models no MCP flag ignore this field.
|
|
279
|
+
// MCP server configs forwarded to providers that accept an MCP config CLI flag. Claude accepts
|
|
280
|
+
// one variadic `--mcp-config` followed by file paths; adapters such as Copilot accept repeated
|
|
281
|
+
// flags with inline JSON so configs survive container path translation.
|
|
276
282
|
readonly mcpConfig?: readonly string[];
|
|
277
283
|
}
|
|
278
284
|
|
|
@@ -368,6 +374,7 @@ export interface ProviderAdapter {
|
|
|
368
374
|
readonly defaultMinLevel: ModelLevel;
|
|
369
375
|
detectCliFeatures(helpText?: string | null): ProviderCliFeatures;
|
|
370
376
|
buildCommand(context: string, options?: BuildProviderCommandOptions): CommandSpec;
|
|
377
|
+
extractSessionId?(line: string): string | null;
|
|
371
378
|
parseEvent(line: string, state: ProviderParserState): ProviderParseResult;
|
|
372
379
|
createParserState(): ProviderParserState;
|
|
373
380
|
resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec;
|
package/src/agent-wrapper.js
CHANGED
|
@@ -17,6 +17,11 @@ const { normalizeProviderName } = require('../lib/provider-names');
|
|
|
17
17
|
const { getProvider } = require('./providers');
|
|
18
18
|
const { buildContext } = require('./agent/agent-context-builder');
|
|
19
19
|
const { collectQueuedGuidance } = require('./agent/guidance-queue');
|
|
20
|
+
const {
|
|
21
|
+
promptIdentity,
|
|
22
|
+
resolveAgentProviderSession,
|
|
23
|
+
updateAgentProviderSession,
|
|
24
|
+
} = require('./agent/provider-session');
|
|
20
25
|
const { findMatchingTrigger, evaluateTrigger } = require('./agent/agent-trigger-evaluator');
|
|
21
26
|
const { executeHook } = require('./agent/agent-hook-executor');
|
|
22
27
|
const { injectInput: injectAgentInput } = require('./agent/agent-input-injector');
|
|
@@ -66,6 +71,11 @@ class AgentWrapper {
|
|
|
66
71
|
this.currentTask = null;
|
|
67
72
|
/** @type {string | null} */
|
|
68
73
|
this.currentTaskId = null; // Track spawned task ID for resume capability
|
|
74
|
+
/** @type {{provider: string, sessionId: string, agentId: string, taskId: string, generation: number, cwd: string, worktreePath: string|null, contextSequence: string, guidanceSequence: string|null, promptIdentity: string|null} | null} */
|
|
75
|
+
this.providerSession = null; // Provider continuation state, owned by this logical agent only
|
|
76
|
+
this.currentContextSequence = '0';
|
|
77
|
+
this.currentGuidanceSequence = null;
|
|
78
|
+
this.currentPromptIdentity = null;
|
|
69
79
|
/** @type {number | null} */
|
|
70
80
|
this.processPid = null; // Track process PID for resource monitoring
|
|
71
81
|
this.running = false;
|
|
@@ -76,7 +86,7 @@ class AgentWrapper {
|
|
|
76
86
|
/** @type {number | null} */
|
|
77
87
|
this.lastAgentStartTime = null; // Track when agent last began executing (for context filtering)
|
|
78
88
|
/** @type {number | null} */
|
|
79
|
-
this.
|
|
89
|
+
this.lastGuidanceAppliedId = null; // Track last queued guidance sequence applied to prompt
|
|
80
90
|
|
|
81
91
|
// LIVENESS DETECTION - Track output freshness to detect stuck agents
|
|
82
92
|
/** @type {number | null} */
|
|
@@ -119,6 +129,7 @@ class AgentWrapper {
|
|
|
119
129
|
|
|
120
130
|
this.testMode = options.testMode || false;
|
|
121
131
|
this.quiet = options.quiet || false;
|
|
132
|
+
this.providerCliFeatures = options.providerCliFeatures || null;
|
|
122
133
|
|
|
123
134
|
// ISOLATION SUPPORT - Run tasks inside Docker container
|
|
124
135
|
this.isolation = options.isolation || null;
|
|
@@ -435,12 +446,29 @@ class AgentWrapper {
|
|
|
435
446
|
*/
|
|
436
447
|
_buildContext(triggeringMessage) {
|
|
437
448
|
const previousAgentStart = this.lastAgentStartTime;
|
|
449
|
+
const providerName = this._resolveProvider();
|
|
450
|
+
let providerSession = resolveAgentProviderSession(this, providerName);
|
|
451
|
+
if (providerSession && !this._providerSupportsSessionResume(providerName)) {
|
|
452
|
+
updateAgentProviderSession(this, null);
|
|
453
|
+
providerSession = null;
|
|
454
|
+
}
|
|
455
|
+
const contextMode = providerSession ? 'continuation' : 'full';
|
|
456
|
+
const selectedPrompt = this._selectPrompt();
|
|
457
|
+
const latestMessage = this.messageBus.findLast({
|
|
458
|
+
cluster_id: this.cluster.id,
|
|
459
|
+
orderBySequence: true,
|
|
460
|
+
});
|
|
461
|
+
this.currentContextSequence = latestMessage?.sequence || '0';
|
|
438
462
|
const queuedGuidance = collectQueuedGuidance({
|
|
439
463
|
messageBus: this.messageBus,
|
|
440
464
|
clusterId: this.cluster.id,
|
|
441
465
|
agentId: this.id,
|
|
442
|
-
|
|
466
|
+
afterId: this.lastGuidanceAppliedId,
|
|
467
|
+
throughId: this.currentContextSequence,
|
|
443
468
|
});
|
|
469
|
+
this.currentGuidanceSequence =
|
|
470
|
+
queuedGuidance.latestSequence ?? this.lastGuidanceAppliedId ?? null;
|
|
471
|
+
this.currentPromptIdentity = promptIdentity(selectedPrompt);
|
|
444
472
|
const context = buildContext({
|
|
445
473
|
id: this.id,
|
|
446
474
|
role: this.role,
|
|
@@ -451,27 +479,35 @@ class AgentWrapper {
|
|
|
451
479
|
lastTaskEndTime: this.lastTaskEndTime,
|
|
452
480
|
lastAgentStartTime: previousAgentStart,
|
|
453
481
|
triggeringMessage,
|
|
454
|
-
selectedPrompt
|
|
482
|
+
selectedPrompt,
|
|
455
483
|
queuedGuidance: queuedGuidance.guidanceBlock,
|
|
484
|
+
mode: contextMode,
|
|
485
|
+
continuationSequence: providerSession?.contextSequence,
|
|
486
|
+
contextThroughId: this.currentContextSequence,
|
|
487
|
+
previousPromptIdentity: providerSession?.promptIdentity,
|
|
488
|
+
currentPromptIdentity: this.currentPromptIdentity,
|
|
456
489
|
// Pass isolation state for conditional git restriction
|
|
457
490
|
worktree: this.worktree,
|
|
458
491
|
isolation: this.isolation,
|
|
459
492
|
});
|
|
460
493
|
|
|
461
494
|
// Record when this iteration started so future "since: last_agent_start" filters work.
|
|
462
|
-
const latestMessage = this.messageBus.findLast({ cluster_id: this.cluster.id });
|
|
463
495
|
const latestTimestamp = latestMessage?.timestamp;
|
|
464
496
|
const now = Date.now();
|
|
465
497
|
this.lastAgentStartTime =
|
|
466
498
|
typeof latestTimestamp === 'number' ? Math.max(now, latestTimestamp + 1) : now;
|
|
467
499
|
|
|
468
|
-
if (queuedGuidance.latestTimestamp !== null) {
|
|
469
|
-
this.lastGuidanceAppliedAt = queuedGuidance.latestTimestamp;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
500
|
return context;
|
|
473
501
|
}
|
|
474
502
|
|
|
503
|
+
_providerSupportsSessionResume(providerName) {
|
|
504
|
+
const override = this.providerCliFeatures?.[providerName];
|
|
505
|
+
if (override && Object.hasOwn(override, 'supportsResume')) {
|
|
506
|
+
return override.supportsResume === true;
|
|
507
|
+
}
|
|
508
|
+
return getProvider(providerName).getCliFeatures().supportsResume === true;
|
|
509
|
+
}
|
|
510
|
+
|
|
475
511
|
/**
|
|
476
512
|
* Spawn claude-zeroshots process and stream output via message bus
|
|
477
513
|
* @private
|
|
@@ -12,7 +12,23 @@ const { loadSettings } = require('../lib/settings');
|
|
|
12
12
|
const { normalizeProviderName } = require('../lib/provider-names');
|
|
13
13
|
const { getProvider } = require('./providers');
|
|
14
14
|
const { prependWorktreeToolBinToEnv } = require('./worktree-tooling-env');
|
|
15
|
-
const {
|
|
15
|
+
const { getTask, getTaskBySpawnOwnershipToken } = require('../task-lib/store.js');
|
|
16
|
+
const {
|
|
17
|
+
TASK_SPAWN_OWNERSHIP_TOKEN_ENV,
|
|
18
|
+
cleanupCallerOwnedCommand,
|
|
19
|
+
callerOwnsCommandCleanup,
|
|
20
|
+
createTaskSpawnOwnershipToken,
|
|
21
|
+
requireTaskIdFromWrapperResult,
|
|
22
|
+
trackTaskWrapperCleanupOwnership,
|
|
23
|
+
} = require('./task-spawn-cleanup-ownership');
|
|
24
|
+
const {
|
|
25
|
+
CLAUDE_MCP_CONFIG_ENV,
|
|
26
|
+
CLAUDE_SETTINGS_ENV,
|
|
27
|
+
cleanupClaudeSettingsOverlay,
|
|
28
|
+
prepareClaudeSettingsOverlay,
|
|
29
|
+
resolveContainerMcpConfigPath,
|
|
30
|
+
resolveRepoMcpConfigPath,
|
|
31
|
+
} = require('./worktree-claude-config');
|
|
16
32
|
const {
|
|
17
33
|
appendTaskRunModelArgs,
|
|
18
34
|
wrapTaskRunWithIsolatedSettings,
|
|
@@ -80,6 +96,28 @@ function runCommand(command, args, options = {}, callback = null) {
|
|
|
80
96
|
});
|
|
81
97
|
}
|
|
82
98
|
|
|
99
|
+
const TASK_TERMINAL_STATUSES = new Set(['completed', 'failed', 'killed', 'stale', 'cancelled']);
|
|
100
|
+
|
|
101
|
+
async function cleanupPersistedTaskAfterLaunchFailure(ctPath, taskId) {
|
|
102
|
+
let lastError = null;
|
|
103
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
104
|
+
let commandError = null;
|
|
105
|
+
try {
|
|
106
|
+
await runCommand(ctPath, ['kill', taskId], { timeout: 10000 });
|
|
107
|
+
} catch (error) {
|
|
108
|
+
commandError = error;
|
|
109
|
+
}
|
|
110
|
+
const task = getTask(taskId);
|
|
111
|
+
if (!task || (TASK_TERMINAL_STATUSES.has(task.status) && !task.commandCleanup)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
lastError =
|
|
115
|
+
commandError ||
|
|
116
|
+
new Error(`Task ${taskId} termination and command cleanup were not confirmed`);
|
|
117
|
+
}
|
|
118
|
+
throw lastError || new Error(`Task ${taskId} cleanup failed`);
|
|
119
|
+
}
|
|
120
|
+
|
|
83
121
|
function runCommandSync(command, args, options = {}) {
|
|
84
122
|
const timeout = options.timeout ?? 30000;
|
|
85
123
|
const result = spawnSync(command, args, { ...options, timeout });
|
|
@@ -95,6 +133,17 @@ function runCommandSync(command, args, options = {}) {
|
|
|
95
133
|
return result.stdout?.toString() || '';
|
|
96
134
|
}
|
|
97
135
|
|
|
136
|
+
function appendIsolatedMcpConfigArgs(command, provider, options) {
|
|
137
|
+
if (provider !== 'claude') return;
|
|
138
|
+
const mcpConfigPath = resolveContainerMcpConfigPath({
|
|
139
|
+
cwd: options.cwd || process.cwd(),
|
|
140
|
+
worktreePath: options.worktreePath || null,
|
|
141
|
+
});
|
|
142
|
+
if (mcpConfigPath) {
|
|
143
|
+
command.push('--mcp-config', mcpConfigPath);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
98
147
|
class ClaudeTaskRunner extends TaskRunner {
|
|
99
148
|
/**
|
|
100
149
|
* @param {Object} options
|
|
@@ -191,14 +240,20 @@ class ClaudeTaskRunner extends TaskRunner {
|
|
|
191
240
|
worktreePath,
|
|
192
241
|
});
|
|
193
242
|
|
|
194
|
-
|
|
243
|
+
let taskId;
|
|
244
|
+
try {
|
|
245
|
+
taskId = await this._spawnAndGetTaskId(ctPath, args, cwd, spawnEnv, agentId);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
cleanupCallerOwnedCommand(error, () =>
|
|
248
|
+
cleanupClaudeSettingsOverlay(spawnEnv[CLAUDE_SETTINGS_ENV])
|
|
249
|
+
);
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
195
252
|
|
|
253
|
+
// Once a task ID is returned, the detached watcher owns provider
|
|
254
|
+
// termination and command cleanup.
|
|
196
255
|
this._log(`📋 [${agentId}]: Following zeroshot logs for ${taskId}`);
|
|
197
|
-
|
|
198
|
-
// Wait for task registration
|
|
199
256
|
await this._waitForTaskReady(ctPath, taskId);
|
|
200
|
-
|
|
201
|
-
// Follow logs until completion
|
|
202
257
|
return this._followLogs(ctPath, taskId, agentId);
|
|
203
258
|
}
|
|
204
259
|
|
|
@@ -325,9 +380,12 @@ class ClaudeTaskRunner extends TaskRunner {
|
|
|
325
380
|
spawnEnv.ANTHROPIC_MODEL = resolvedModelSpec.model;
|
|
326
381
|
}
|
|
327
382
|
if (providerName === 'claude') {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
383
|
+
spawnEnv[CLAUDE_SETTINGS_ENV] = prepareClaudeSettingsOverlay({
|
|
384
|
+
includeDangerousGit: Boolean(worktreePath),
|
|
385
|
+
});
|
|
386
|
+
const mcpConfigPath = resolveRepoMcpConfigPath({ cwd, worktreePath });
|
|
387
|
+
if (mcpConfigPath) {
|
|
388
|
+
spawnEnv[CLAUDE_MCP_CONFIG_ENV] = mcpConfigPath;
|
|
331
389
|
}
|
|
332
390
|
}
|
|
333
391
|
|
|
@@ -345,16 +403,49 @@ class ClaudeTaskRunner extends TaskRunner {
|
|
|
345
403
|
* @returns {Promise<string>}
|
|
346
404
|
*/
|
|
347
405
|
_spawnAndGetTaskId(ctPath, args, cwd, spawnEnv, _agentId) {
|
|
406
|
+
const ownershipToken = createTaskSpawnOwnershipToken();
|
|
407
|
+
const findPersistedTaskId = () => getTaskBySpawnOwnershipToken(ownershipToken)?.id || null;
|
|
348
408
|
return new Promise((resolve, reject) => {
|
|
349
409
|
const proc = spawn(ctPath, args, {
|
|
350
410
|
cwd,
|
|
351
411
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
352
|
-
env: spawnEnv,
|
|
412
|
+
env: { ...spawnEnv, [TASK_SPAWN_OWNERSHIP_TOKEN_ENV]: ownershipToken },
|
|
353
413
|
windowsHide: true,
|
|
354
414
|
});
|
|
355
415
|
|
|
356
416
|
let stdout = '';
|
|
357
417
|
let stderr = '';
|
|
418
|
+
let settled = false;
|
|
419
|
+
const classifyCleanupOwnership = trackTaskWrapperCleanupOwnership(findPersistedTaskId);
|
|
420
|
+
const rejectWithOwnership = async (error) => {
|
|
421
|
+
if (settled) return;
|
|
422
|
+
settled = true;
|
|
423
|
+
const classifiedError = classifyCleanupOwnership(error);
|
|
424
|
+
if (!callerOwnsCommandCleanup(classifiedError)) {
|
|
425
|
+
classifiedError.spawnOwnershipToken = ownershipToken;
|
|
426
|
+
let persistedTaskId = null;
|
|
427
|
+
let lookupError = null;
|
|
428
|
+
try {
|
|
429
|
+
persistedTaskId = findPersistedTaskId();
|
|
430
|
+
} catch (lookupFailure) {
|
|
431
|
+
lookupError = lookupFailure;
|
|
432
|
+
}
|
|
433
|
+
classifiedError.taskId = persistedTaskId;
|
|
434
|
+
try {
|
|
435
|
+
if (lookupError) throw lookupError;
|
|
436
|
+
if (persistedTaskId) {
|
|
437
|
+
await cleanupPersistedTaskAfterLaunchFailure(ctPath, persistedTaskId);
|
|
438
|
+
}
|
|
439
|
+
} catch (cleanupError) {
|
|
440
|
+
classifiedError.message += ` Task cleanup was not confirmed: ${cleanupError.message}`;
|
|
441
|
+
classifiedError.permanent = true;
|
|
442
|
+
classifiedError.restartExhausted = true;
|
|
443
|
+
classifiedError.terminationExhausted = true;
|
|
444
|
+
classifiedError.terminationAttempts = persistedTaskId ? 3 : 1;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
reject(classifiedError);
|
|
448
|
+
};
|
|
358
449
|
|
|
359
450
|
proc.stdout.on('data', (data) => {
|
|
360
451
|
stdout += data.toString();
|
|
@@ -364,21 +455,26 @@ class ClaudeTaskRunner extends TaskRunner {
|
|
|
364
455
|
stderr += data.toString();
|
|
365
456
|
});
|
|
366
457
|
|
|
367
|
-
proc.on('close', (code) => {
|
|
368
|
-
if (
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
458
|
+
proc.on('close', async (code) => {
|
|
459
|
+
if (settled) return;
|
|
460
|
+
try {
|
|
461
|
+
const taskId = requireTaskIdFromWrapperResult({
|
|
462
|
+
code,
|
|
463
|
+
stdout,
|
|
464
|
+
stderr,
|
|
465
|
+
parseTaskId: (output) =>
|
|
466
|
+
output.match(/Task spawned: ((?:task-)?[a-z]+-[a-z]+-[a-z0-9]+)/)?.[1],
|
|
467
|
+
persistedTaskId: findPersistedTaskId(),
|
|
468
|
+
});
|
|
469
|
+
settled = true;
|
|
470
|
+
resolve(taskId);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
await rejectWithOwnership(error);
|
|
377
473
|
}
|
|
378
474
|
});
|
|
379
475
|
|
|
380
|
-
proc.on('error', (error) => {
|
|
381
|
-
|
|
476
|
+
proc.on('error', async (error) => {
|
|
477
|
+
await rejectWithOwnership(error);
|
|
382
478
|
});
|
|
383
479
|
});
|
|
384
480
|
}
|
|
@@ -424,6 +520,7 @@ class ClaudeTaskRunner extends TaskRunner {
|
|
|
424
520
|
let statusCheckInterval = null;
|
|
425
521
|
let resolved = false;
|
|
426
522
|
let lineBuffer = '';
|
|
523
|
+
let cleanupRecoveryPending = false;
|
|
427
524
|
|
|
428
525
|
// Get log file path
|
|
429
526
|
try {
|
|
@@ -558,33 +655,43 @@ class ClaudeTaskRunner extends TaskRunner {
|
|
|
558
655
|
|
|
559
656
|
statusCheckInterval = setInterval(() => {
|
|
560
657
|
runCommand(ctPath, ['status', taskId], {}, (error, stdout) => {
|
|
561
|
-
if (resolved) return;
|
|
562
|
-
|
|
563
|
-
if (
|
|
564
|
-
|
|
565
|
-
(
|
|
566
|
-
|
|
567
|
-
|
|
658
|
+
if (resolved || error) return;
|
|
659
|
+
const terminalMatch = stdout.match(/Status:\s+(completed|failed)/i);
|
|
660
|
+
if (!terminalMatch) return;
|
|
661
|
+
if (/Cleanup:\s+pending/i.test(stdout)) {
|
|
662
|
+
if (!cleanupRecoveryPending) {
|
|
663
|
+
cleanupRecoveryPending = true;
|
|
664
|
+
runCommand(ctPath, ['kill', taskId], { timeout: 10000 }, (cleanupError) => {
|
|
665
|
+
cleanupRecoveryPending = false;
|
|
666
|
+
if (cleanupError) {
|
|
667
|
+
this._log(
|
|
668
|
+
`⚠️ [${agentId}]: Terminal cleanup recovery will retry: ${cleanupError.message}`
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
568
675
|
|
|
569
|
-
|
|
676
|
+
const success = terminalMatch[1].toLowerCase() === 'completed';
|
|
677
|
+
pollLogFile();
|
|
570
678
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
679
|
+
setTimeout(() => {
|
|
680
|
+
if (resolved) return;
|
|
681
|
+
resolved = true;
|
|
574
682
|
|
|
575
|
-
|
|
576
|
-
|
|
683
|
+
clearInterval(pollInterval);
|
|
684
|
+
clearInterval(statusCheckInterval);
|
|
577
685
|
|
|
578
|
-
|
|
686
|
+
const errorContext = extractErrorContext(success, stdout);
|
|
579
687
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
688
|
+
resolve({
|
|
689
|
+
success,
|
|
690
|
+
output,
|
|
691
|
+
error: errorContext,
|
|
692
|
+
taskId,
|
|
693
|
+
});
|
|
694
|
+
}, 500);
|
|
588
695
|
});
|
|
589
696
|
}, 1000);
|
|
590
697
|
|
|
@@ -664,6 +771,8 @@ class ClaudeTaskRunner extends TaskRunner {
|
|
|
664
771
|
command.push('--json-schema', JSON.stringify(jsonSchema));
|
|
665
772
|
}
|
|
666
773
|
|
|
774
|
+
appendIsolatedMcpConfigArgs(command, provider, options);
|
|
775
|
+
|
|
667
776
|
let finalContext = context;
|
|
668
777
|
if (jsonSchema && desiredOutputFormat === 'json' && runOutputFormat === 'stream-json') {
|
|
669
778
|
finalContext += `\n\n## Output Format (REQUIRED)\n\nReturn a JSON object that matches this schema exactly.\n\nSchema:\n\`\`\`json\n${JSON.stringify(
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const MAX_SQLITE_ROWID = 9223372036854775807n;
|
|
2
|
+
const CANONICAL_SEQUENCE = /^(0|[1-9][0-9]*)$/;
|
|
3
|
+
|
|
4
|
+
function canonicalMessageSequence(value, name = 'message sequence') {
|
|
5
|
+
let sequence;
|
|
6
|
+
if (typeof value === 'string' && CANONICAL_SEQUENCE.test(value)) {
|
|
7
|
+
sequence = value;
|
|
8
|
+
} else if (Number.isSafeInteger(value) && value >= 0) {
|
|
9
|
+
// Accept legacy JSON state while normalizing every new boundary to a
|
|
10
|
+
// JSON-safe decimal string.
|
|
11
|
+
sequence = String(value);
|
|
12
|
+
} else {
|
|
13
|
+
throw new TypeError(`${name} must be a canonical non-negative decimal string`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (BigInt(sequence) > MAX_SQLITE_ROWID) {
|
|
17
|
+
throw new RangeError(`${name} exceeds the SQLite rowid range`);
|
|
18
|
+
}
|
|
19
|
+
return sequence;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function messageSequenceFromSql(value, name = 'message sequence') {
|
|
23
|
+
if (typeof value === 'bigint') {
|
|
24
|
+
if (value < 0n || value > MAX_SQLITE_ROWID) {
|
|
25
|
+
throw new RangeError(`${name} is outside the SQLite rowid range`);
|
|
26
|
+
}
|
|
27
|
+
return value.toString();
|
|
28
|
+
}
|
|
29
|
+
return canonicalMessageSequence(value, name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function messageSequenceToSql(value, name = 'message sequence') {
|
|
33
|
+
return BigInt(canonicalMessageSequence(value, name));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function compareMessageSequences(left, right) {
|
|
37
|
+
const leftValue = messageSequenceToSql(left, 'left message sequence');
|
|
38
|
+
const rightValue = messageSequenceToSql(right, 'right message sequence');
|
|
39
|
+
if (leftValue < rightValue) {
|
|
40
|
+
return -1;
|
|
41
|
+
}
|
|
42
|
+
if (leftValue > rightValue) {
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function tryCanonicalMessageSequence(value) {
|
|
49
|
+
try {
|
|
50
|
+
return canonicalMessageSequence(value);
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
MAX_SQLITE_ROWID,
|
|
58
|
+
canonicalMessageSequence,
|
|
59
|
+
compareMessageSequences,
|
|
60
|
+
messageSequenceFromSql,
|
|
61
|
+
messageSequenceToSql,
|
|
62
|
+
tryCanonicalMessageSequence,
|
|
63
|
+
};
|