@nowcrew/daemon 0.5.18 → 0.5.20
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/README.md +23 -0
- package/dist/attachments.js +196 -0
- package/dist/computer-cli.js +72 -12
- package/dist/computer-profile-lock.js +395 -0
- package/dist/computer-profile.js +189 -20
- package/dist/config.js +2 -1
- package/dist/console.js +175 -9
- package/dist/execution-event-limit.js +1 -1
- package/dist/execution-journal-lock.js +199 -40
- package/dist/execution-journal.js +42 -4
- package/dist/execution-protocol.js +21 -1
- package/dist/execution-recovery.js +71 -0
- package/dist/execution-runner.js +68 -77
- package/dist/execution-supervisor.js +79 -31
- package/dist/external-output.js +114 -0
- package/dist/i18n.js +5 -5
- package/dist/list-models.js +41 -5
- package/dist/local-executor.js +103 -14
- package/dist/machine-info.js +6 -1
- package/dist/main.js +23 -8
- package/dist/origin-decision.js +3 -1
- package/dist/prompt.js +4 -1
- package/dist/runner.js +14 -9
- package/dist/runtime-cancellation.js +74 -0
- package/dist/runtime-capabilities.js +38 -0
- package/dist/runtime-path.js +60 -0
- package/dist/runtimes/claude.js +9 -4
- package/dist/runtimes/codex-app-server-runner.js +340 -0
- package/dist/runtimes/codex.js +10 -4
- package/dist/runtimes/kimi-acp-runner.js +117 -17
- package/dist/runtimes/kimi.js +2 -0
- package/dist/runtimes/progress-watchdog.js +26 -0
- package/dist/serve-lifecycle.js +82 -0
- package/dist/serve.js +212 -212
- package/dist/session.js +1 -1
- package/dist/shared-execution-slots.js +68 -0
- package/dist/shutdown-deadline.js +32 -0
- package/dist/slog.js +34 -20
- package/dist/supervised-runtime.js +104 -0
- package/dist/websocket-shutdown.js +53 -0
- package/package.json +3 -3
|
@@ -558,7 +558,7 @@ export function createExecutionJournal(agentsRoot, options = {}) {
|
|
|
558
558
|
return { kind: "created", ...entry };
|
|
559
559
|
});
|
|
560
560
|
},
|
|
561
|
-
startGuarded: async (executionId, processStartedAt, startDormant) => {
|
|
561
|
+
startGuarded: async (executionId, processStartedAt, startDormant, hooks) => {
|
|
562
562
|
validateExecutionId(executionId);
|
|
563
563
|
TimestampSchema.parse(processStartedAt);
|
|
564
564
|
return serialized(async () => {
|
|
@@ -567,9 +567,21 @@ export function createExecutionJournal(agentsRoot, options = {}) {
|
|
|
567
567
|
return { kind: "existing", entry: await confirmDurable(entry) };
|
|
568
568
|
}
|
|
569
569
|
const dormant = await startDormant();
|
|
570
|
+
let abortPromise = null;
|
|
571
|
+
const abort = () => {
|
|
572
|
+
if (abortPromise === null) {
|
|
573
|
+
try {
|
|
574
|
+
abortPromise = Promise.resolve(dormant.abort());
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
abortPromise = Promise.reject(error);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return abortPromise;
|
|
581
|
+
};
|
|
570
582
|
const abortWith = async (error) => {
|
|
571
583
|
try {
|
|
572
|
-
await
|
|
584
|
+
await abort();
|
|
573
585
|
}
|
|
574
586
|
catch (abortError) {
|
|
575
587
|
throw new AggregateError([error, abortError], "Dormant runtime abort failed");
|
|
@@ -591,6 +603,9 @@ export function createExecutionJournal(agentsRoot, options = {}) {
|
|
|
591
603
|
});
|
|
592
604
|
await writeRecord(updated);
|
|
593
605
|
try {
|
|
606
|
+
const gate = hooks?.beforeRelease?.({ entry: updated, handle: dormant, abort });
|
|
607
|
+
if (gate !== undefined)
|
|
608
|
+
await gate;
|
|
594
609
|
await dormant.release();
|
|
595
610
|
}
|
|
596
611
|
catch (error) {
|
|
@@ -671,8 +686,31 @@ export function createExecutionJournal(agentsRoot, options = {}) {
|
|
|
671
686
|
}
|
|
672
687
|
}),
|
|
673
688
|
prune: async () => serialized(pruneInternal),
|
|
674
|
-
close: async () => {
|
|
675
|
-
|
|
689
|
+
close: async (closeOptions = {}) => {
|
|
690
|
+
const signal = closeOptions.signal;
|
|
691
|
+
let started = false;
|
|
692
|
+
const closing = runSerialized(directory, async () => {
|
|
693
|
+
started = true;
|
|
694
|
+
if (signal?.aborted)
|
|
695
|
+
throw signal.reason ?? new Error("Journal close aborted");
|
|
696
|
+
await lease.close(closeOptions);
|
|
697
|
+
});
|
|
698
|
+
if (signal === undefined)
|
|
699
|
+
return closing;
|
|
700
|
+
await new Promise((resolveClose, rejectClose) => {
|
|
701
|
+
const settle = (settler) => {
|
|
702
|
+
signal.removeEventListener("abort", onAbort);
|
|
703
|
+
settler();
|
|
704
|
+
};
|
|
705
|
+
const onAbort = () => {
|
|
706
|
+
if (!started)
|
|
707
|
+
settle(() => rejectClose(signal.reason ?? new Error("Journal close aborted")));
|
|
708
|
+
};
|
|
709
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
710
|
+
if (signal.aborted)
|
|
711
|
+
onAbort();
|
|
712
|
+
closing.then(() => settle(resolveClose), (error) => settle(() => rejectClose(error)));
|
|
713
|
+
});
|
|
676
714
|
},
|
|
677
715
|
};
|
|
678
716
|
}
|
|
@@ -48,7 +48,7 @@ export const LegacyAgentStartSchema = z.object({
|
|
|
48
48
|
content: z.string().optional(),
|
|
49
49
|
senderHandle: z.string().optional(),
|
|
50
50
|
threadId: z.string().optional(),
|
|
51
|
-
origin: z.enum(["wecom"]).optional(),
|
|
51
|
+
origin: z.enum(["wecom", "feishu"]).optional(),
|
|
52
52
|
}).passthrough().optional(),
|
|
53
53
|
scheduledRun: z.object({
|
|
54
54
|
jobId: z.string().min(1),
|
|
@@ -85,6 +85,12 @@ export const ConsoleStreamSchema = z.enum([
|
|
|
85
85
|
"result",
|
|
86
86
|
"error",
|
|
87
87
|
]);
|
|
88
|
+
export const ExecutionAttachmentSchema = z.object({
|
|
89
|
+
id: z.string().min(1).max(200),
|
|
90
|
+
filename: z.string().min(1).max(255),
|
|
91
|
+
mime: z.string().min(1).max(200),
|
|
92
|
+
sizeBytes: z.number().int().nonnegative().max(25 * 1024 * 1024),
|
|
93
|
+
}).strict();
|
|
88
94
|
export const ExecutionStartSchema = z.object({
|
|
89
95
|
type: z.literal("execution:start"),
|
|
90
96
|
protocolVersion: ProtocolVersionSchema,
|
|
@@ -115,6 +121,9 @@ export const ExecutionStartSchema = z.object({
|
|
|
115
121
|
channelId: z.string().min(1),
|
|
116
122
|
threadId: z.string().min(1).optional(),
|
|
117
123
|
wakeMessageId: z.string().min(1).optional(),
|
|
124
|
+
externalResponseSessionId: ExecutionIdSchema.optional(),
|
|
125
|
+
answerStream: z.boolean().optional(),
|
|
126
|
+
attachments: z.array(ExecutionAttachmentSchema).max(20).optional(),
|
|
118
127
|
}).strict(),
|
|
119
128
|
reporting: z.object({
|
|
120
129
|
captureFinal: z.boolean(),
|
|
@@ -185,6 +194,15 @@ export const ExecutionConsoleSchema = z.object({
|
|
|
185
194
|
seq: SequenceSchema,
|
|
186
195
|
at: TimestampSchema,
|
|
187
196
|
}).strict();
|
|
197
|
+
export const ExecutionOutputSchema = z.object({
|
|
198
|
+
type: z.literal("execution:output"),
|
|
199
|
+
protocolVersion: ProtocolVersionSchema,
|
|
200
|
+
executionId: ExecutionIdSchema,
|
|
201
|
+
channel: z.literal("external_answer"),
|
|
202
|
+
text: z.string().min(1),
|
|
203
|
+
seq: SequenceSchema,
|
|
204
|
+
at: TimestampSchema,
|
|
205
|
+
}).strict();
|
|
188
206
|
export const ExecutionUsageSchema = z.object({
|
|
189
207
|
inputTokens: TokenCountSchema,
|
|
190
208
|
outputTokens: TokenCountSchema,
|
|
@@ -205,6 +223,7 @@ const RawExecutionCompletedSchema = z.object({
|
|
|
205
223
|
model: z.string().optional(),
|
|
206
224
|
resumed: z.boolean(),
|
|
207
225
|
finalText: z.string().optional(),
|
|
226
|
+
externalAnswer: z.string().min(1).optional(),
|
|
208
227
|
boundImDecision: z.enum(["notify", "silent"]).optional(),
|
|
209
228
|
usage: ExecutionUsageSchema.optional(),
|
|
210
229
|
startedAt: TimestampSchema,
|
|
@@ -312,6 +331,7 @@ const RawDaemonToServerExecutionFrameSchema = z.discriminatedUnion("type", [
|
|
|
312
331
|
ExecutionStartedSchema,
|
|
313
332
|
ExecutionActivitySchema,
|
|
314
333
|
ExecutionConsoleSchema,
|
|
334
|
+
ExecutionOutputSchema,
|
|
315
335
|
RawExecutionCompletedSchema,
|
|
316
336
|
ExecutionSnapshotSchema,
|
|
317
337
|
]);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
export async function reconcileExecutionJournal(journal, dependencies) {
|
|
3
|
+
try {
|
|
4
|
+
await journal.reconcileAfterRestart();
|
|
5
|
+
}
|
|
6
|
+
catch (error) {
|
|
7
|
+
const agentsRoot = resolve(dependencies.agentsRoot);
|
|
8
|
+
const errorRecord = typeof error === "object" && error !== null
|
|
9
|
+
? error
|
|
10
|
+
: {};
|
|
11
|
+
const journalPath = typeof errorRecord.journalPath === "string"
|
|
12
|
+
? resolve(errorRecord.journalPath)
|
|
13
|
+
: join(agentsRoot, ".crew", "executions");
|
|
14
|
+
const ownerPid = typeof errorRecord.ownerPid === "number" ? errorRecord.ownerPid : undefined;
|
|
15
|
+
const errorType = error instanceof Error ? error.name : typeof error;
|
|
16
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
17
|
+
const diagnostics = {
|
|
18
|
+
server_url: dependencies.serverUrl,
|
|
19
|
+
agents_root: agentsRoot,
|
|
20
|
+
journal_path: journalPath,
|
|
21
|
+
owner_pid: ownerPid,
|
|
22
|
+
error_type: errorType,
|
|
23
|
+
error_message: errorMessage,
|
|
24
|
+
};
|
|
25
|
+
dependencies.log("execution.recovery_failed", "execution journal 恢复失败", {
|
|
26
|
+
level: "ERROR",
|
|
27
|
+
...diagnostics,
|
|
28
|
+
});
|
|
29
|
+
dependencies.writeStderr(`${JSON.stringify({
|
|
30
|
+
level: "ERROR",
|
|
31
|
+
event_type: "execution.recovery_failed",
|
|
32
|
+
message: "execution journal 恢复失败",
|
|
33
|
+
...diagnostics,
|
|
34
|
+
})}\n`);
|
|
35
|
+
const reportCleanupFailure = (stage, cleanupError) => {
|
|
36
|
+
const cleanupErrorType = cleanupError instanceof Error ? cleanupError.name : typeof cleanupError;
|
|
37
|
+
const cleanupErrorMessage = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
38
|
+
const cleanupDiagnostics = {
|
|
39
|
+
server_url: dependencies.serverUrl,
|
|
40
|
+
agents_root: agentsRoot,
|
|
41
|
+
journal_path: journalPath,
|
|
42
|
+
owner_pid: ownerPid,
|
|
43
|
+
cleanup_stage: stage,
|
|
44
|
+
error_type: cleanupErrorType,
|
|
45
|
+
error_message: cleanupErrorMessage,
|
|
46
|
+
recovery_error_type: errorType,
|
|
47
|
+
recovery_error_message: errorMessage,
|
|
48
|
+
};
|
|
49
|
+
dependencies.log("execution.recovery_cleanup_failed", "execution journal 恢复失败后的清理失败", { level: "ERROR", ...cleanupDiagnostics });
|
|
50
|
+
dependencies.writeStderr(`${JSON.stringify({
|
|
51
|
+
level: "ERROR",
|
|
52
|
+
event_type: "execution.recovery_cleanup_failed",
|
|
53
|
+
message: "execution journal 恢复失败后的清理失败",
|
|
54
|
+
...cleanupDiagnostics,
|
|
55
|
+
})}\n`);
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
await dependencies.flush();
|
|
59
|
+
}
|
|
60
|
+
catch (cleanupError) {
|
|
61
|
+
reportCleanupFailure("slog_flush", cleanupError);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
await journal.close();
|
|
65
|
+
}
|
|
66
|
+
catch (cleanupError) {
|
|
67
|
+
reportCleanupFailure("journal_close", cleanupError);
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
package/dist/execution-runner.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
3
|
import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, ExecutionRejectedSchema, ExecutionStartSchema, } from "./execution-protocol.js";
|
|
5
4
|
import { JournalConflictError } from "./execution-journal.js";
|
|
6
5
|
import { boundExecutionFrame } from "./execution-event-limit.js";
|
|
7
6
|
import { mintAgentToken } from "./token.js";
|
|
8
7
|
import { executeLocal, withLocalExecutionFacts, } from "./local-executor.js";
|
|
9
8
|
import { startDormantSupervisor, } from "./execution-supervisor.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { CLAUDE_EFFORT_LEVELS } from "./runtimes/claude.js";
|
|
10
|
+
import { CODEX_EFFORT_LEVELS } from "./runtimes/codex.js";
|
|
12
11
|
import { KIMI_EFFORT_LEVELS } from "./runtimes/kimi.js";
|
|
13
12
|
import { executionBackendCapability } from "./execution-backend.js";
|
|
14
13
|
import { readBoundImDecisionFile, resetBoundImDecisionFile } from "./bound-im-decision.js";
|
|
14
|
+
import { RuntimeCancelledError } from "./runtime-cancellation.js";
|
|
15
|
+
import { supervisorLaunch } from "./supervised-runtime.js";
|
|
16
|
+
export { supervisorLaunch } from "./supervised-runtime.js";
|
|
15
17
|
const ACTIVITY_KIND = {
|
|
16
18
|
init: "working",
|
|
17
19
|
text: "thinking",
|
|
@@ -183,56 +185,6 @@ function launchProviderConfig(config) {
|
|
|
183
185
|
...(config.description === undefined ? {} : { description: config.description }),
|
|
184
186
|
};
|
|
185
187
|
}
|
|
186
|
-
function supervisorLaunch(request) {
|
|
187
|
-
const common = {
|
|
188
|
-
wakePrompt: request.wakePrompt,
|
|
189
|
-
dangerous: request.effectivePermission === "full_access",
|
|
190
|
-
effectivePermission: request.effectivePermission,
|
|
191
|
-
...(request.model === undefined ? {} : { model: request.model }),
|
|
192
|
-
...(request.reasoning === undefined ? {} : { reasoning: request.reasoning }),
|
|
193
|
-
};
|
|
194
|
-
if (request.runtime === "claude") {
|
|
195
|
-
return {
|
|
196
|
-
command: request.bin,
|
|
197
|
-
args: buildClaudeArgs({
|
|
198
|
-
...common,
|
|
199
|
-
bin: request.bin,
|
|
200
|
-
cwd: request.cwd,
|
|
201
|
-
env: request.env,
|
|
202
|
-
systemPromptPath: request.systemPromptPath,
|
|
203
|
-
...(request.sessionId === undefined ? {} : {
|
|
204
|
-
sessionId: request.sessionId,
|
|
205
|
-
resume: request.resume,
|
|
206
|
-
}),
|
|
207
|
-
}),
|
|
208
|
-
cwd: request.cwd,
|
|
209
|
-
env: request.env,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
if (request.runtime === "codex") {
|
|
213
|
-
return {
|
|
214
|
-
command: request.bin,
|
|
215
|
-
args: buildCodexArgs(common),
|
|
216
|
-
cwd: request.cwd,
|
|
217
|
-
env: request.env,
|
|
218
|
-
stdinText: `${request.systemPrompt}\n\n${request.wakePrompt}`,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
if (request.effectivePermission !== "full_access") {
|
|
222
|
-
throw new Error(`Kimi ACP cannot enforce ${request.effectivePermission} permission`);
|
|
223
|
-
}
|
|
224
|
-
return {
|
|
225
|
-
command: process.execPath,
|
|
226
|
-
args: [
|
|
227
|
-
fileURLToPath(new URL("./runtimes/kimi-acp-runner.js", import.meta.url)),
|
|
228
|
-
"--bin", request.bin,
|
|
229
|
-
...(request.model === undefined ? [] : ["--model", request.model]),
|
|
230
|
-
],
|
|
231
|
-
cwd: request.cwd,
|
|
232
|
-
env: request.env,
|
|
233
|
-
stdinText: `${request.systemPrompt}\n\n${request.wakePrompt}`,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
188
|
function rejection(executionId, reason, message, at) {
|
|
237
189
|
return ExecutionRejectedSchema.parse({
|
|
238
190
|
type: "execution:rejected",
|
|
@@ -442,6 +394,7 @@ export async function runExecution(config, input, dependencies) {
|
|
|
442
394
|
const providerConfig = launchProviderConfig(credential.config);
|
|
443
395
|
let activitySequence = 0;
|
|
444
396
|
let consoleSequence = 0;
|
|
397
|
+
let externalOutputSequence = 0;
|
|
445
398
|
const callbacks = {
|
|
446
399
|
...(spec.reporting.streamActivity ? {
|
|
447
400
|
onActivity: (activity) => {
|
|
@@ -479,8 +432,26 @@ export async function runExecution(config, input, dependencies) {
|
|
|
479
432
|
catch { /* best-effort console omitted when its envelope cannot fit */ }
|
|
480
433
|
},
|
|
481
434
|
} : {}),
|
|
435
|
+
...(spec.context.externalResponseSessionId || spec.context.answerStream ? {
|
|
436
|
+
onExternalOutput: (text) => {
|
|
437
|
+
const frame = DaemonToServerExecutionFrameSchema.parse({
|
|
438
|
+
type: "execution:output",
|
|
439
|
+
protocolVersion: 1,
|
|
440
|
+
executionId: spec.executionId,
|
|
441
|
+
channel: "external_answer",
|
|
442
|
+
text,
|
|
443
|
+
seq: externalOutputSequence++,
|
|
444
|
+
at: now().toISOString(),
|
|
445
|
+
});
|
|
446
|
+
try {
|
|
447
|
+
telemetry.enqueue(boundExecutionFrame(frame, config.executionLimits.maxEventBytes));
|
|
448
|
+
}
|
|
449
|
+
catch { /* final completion remains the authoritative repair */ }
|
|
450
|
+
},
|
|
451
|
+
} : {}),
|
|
482
452
|
};
|
|
483
453
|
const localDependencies = {
|
|
454
|
+
...(dependencies.cancellation === undefined ? {} : { cancellation: dependencies.cancellation }),
|
|
484
455
|
launchRuntime: async (request) => {
|
|
485
456
|
if (launchClosed || dependencies.cancellation?.isRequested())
|
|
486
457
|
throw new ExecutionCancelledError();
|
|
@@ -489,27 +460,42 @@ export async function runExecution(config, input, dependencies) {
|
|
|
489
460
|
launchAttempts.add(launchSettled);
|
|
490
461
|
try {
|
|
491
462
|
const processStartedAt = now().toISOString();
|
|
492
|
-
const
|
|
463
|
+
const launchControl = { cancel: null };
|
|
464
|
+
const guarded = await dependencies.journal.startGuarded(spec.executionId, processStartedAt, () => startSupervisor(supervisorLaunch(request)), {
|
|
465
|
+
beforeRelease: ({ entry, handle, abort }) => {
|
|
466
|
+
supervisorState.active = handle;
|
|
467
|
+
let stopPromise = null;
|
|
468
|
+
let releaseStarted = false;
|
|
469
|
+
const stopOnce = (operation) => {
|
|
470
|
+
if (stopPromise === null) {
|
|
471
|
+
try {
|
|
472
|
+
stopPromise = Promise.resolve(operation());
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
stopPromise = Promise.reject(error);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return stopPromise;
|
|
479
|
+
};
|
|
480
|
+
launchControl.cancel = () => stopOnce(releaseStarted ? handle.cancel : abort);
|
|
481
|
+
supervisorState.abortOnce = () => stopOnce(abort);
|
|
482
|
+
dependencies.cancellation?.register(launchControl.cancel);
|
|
483
|
+
startedAt = entry.processStartedAt ?? processStartedAt;
|
|
484
|
+
if (dependencies.cancellation?.isRequested()) {
|
|
485
|
+
return dependencies.cancellation.waitForStop().then(() => {
|
|
486
|
+
throw new ExecutionCancelledError();
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
releaseStarted = true;
|
|
490
|
+
},
|
|
491
|
+
});
|
|
493
492
|
if (guarded.kind !== "started") {
|
|
494
493
|
throw new Error(`Execution became ${guarded.entry.state} before local launch`);
|
|
495
494
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
try {
|
|
501
|
-
stopPromise = Promise.resolve(operation());
|
|
502
|
-
}
|
|
503
|
-
catch (error) {
|
|
504
|
-
stopPromise = Promise.reject(error);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
return stopPromise;
|
|
508
|
-
};
|
|
509
|
-
const cancelOnce = () => stopOnce(guarded.handle.cancel);
|
|
510
|
-
supervisorState.abortOnce = () => stopOnce(guarded.handle.abort);
|
|
511
|
-
dependencies.cancellation?.register(cancelOnce);
|
|
512
|
-
startedAt = guarded.entry.processStartedAt ?? processStartedAt;
|
|
495
|
+
if (launchControl.cancel === null) {
|
|
496
|
+
throw new Error("Execution launch cancellation gate was not installed");
|
|
497
|
+
}
|
|
498
|
+
const installedCancel = launchControl.cancel;
|
|
513
499
|
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
514
500
|
type: "execution:started",
|
|
515
501
|
protocolVersion: 1,
|
|
@@ -520,14 +506,14 @@ export async function runExecution(config, input, dependencies) {
|
|
|
520
506
|
timeout = setTimeout(() => {
|
|
521
507
|
timedOut = true;
|
|
522
508
|
try {
|
|
523
|
-
void
|
|
509
|
+
void installedCancel().catch(rejectCancellationFailure);
|
|
524
510
|
}
|
|
525
511
|
catch (error) {
|
|
526
512
|
rejectCancellationFailure(error);
|
|
527
513
|
}
|
|
528
514
|
}, effectiveTimeoutMs);
|
|
529
515
|
}
|
|
530
|
-
return guarded.handle;
|
|
516
|
+
return { ...guarded.handle, cancel: installedCancel };
|
|
531
517
|
}
|
|
532
518
|
finally {
|
|
533
519
|
launchAttempts.delete(launchSettled);
|
|
@@ -543,6 +529,7 @@ export async function runExecution(config, input, dependencies) {
|
|
|
543
529
|
taskKey: spec.workspace.taskKey,
|
|
544
530
|
...(spec.workspace.resumeKey === undefined ? {} : { resumeKey: spec.workspace.resumeKey }),
|
|
545
531
|
...(spec.context.wakeMessageId === undefined ? {} : { wakeMessageId: spec.context.wakeMessageId }),
|
|
532
|
+
...(spec.context.attachments === undefined ? {} : { attachments: spec.context.attachments }),
|
|
546
533
|
systemPrompt: withLocalExecutionFacts(spec.instructions.systemPrompt, config.executionLimits.maxPromptBytes
|
|
547
534
|
- Buffer.byteLength(spec.instructions.wakePrompt, "utf8")),
|
|
548
535
|
wakePrompt: spec.instructions.wakePrompt,
|
|
@@ -575,10 +562,10 @@ export async function runExecution(config, input, dependencies) {
|
|
|
575
562
|
maxTurns: config.sessionMaxTurns,
|
|
576
563
|
},
|
|
577
564
|
};
|
|
578
|
-
const result = await
|
|
565
|
+
const result = await Promise.race([
|
|
579
566
|
execute(localInput, callbacks, localDependencies),
|
|
580
567
|
cancellationFailure,
|
|
581
|
-
])
|
|
568
|
+
]);
|
|
582
569
|
if (timeout !== undefined)
|
|
583
570
|
clearTimeout(timeout);
|
|
584
571
|
const finishedAt = now().toISOString();
|
|
@@ -619,6 +606,9 @@ export async function runExecution(config, input, dependencies) {
|
|
|
619
606
|
...(!spec.reporting.captureFinal || result.finalText === null
|
|
620
607
|
? {}
|
|
621
608
|
: { finalText: result.finalText }),
|
|
609
|
+
...(spec.context.answerStream && result.exitCode === 0 && result.externalAnswer
|
|
610
|
+
? { externalAnswer: result.externalAnswer }
|
|
611
|
+
: {}),
|
|
622
612
|
...(result.usage === undefined ? {} : { usage: result.usage }),
|
|
623
613
|
startedAt,
|
|
624
614
|
finishedAt,
|
|
@@ -627,7 +617,8 @@ export async function runExecution(config, input, dependencies) {
|
|
|
627
617
|
catch (error) {
|
|
628
618
|
if (timeout !== undefined)
|
|
629
619
|
clearTimeout(timeout);
|
|
630
|
-
|
|
620
|
+
const cancelled = error instanceof ExecutionCancelledError || error instanceof RuntimeCancelledError;
|
|
621
|
+
if (cancelled) {
|
|
631
622
|
await closeLaunchGate();
|
|
632
623
|
await dependencies.cancellation?.waitForStop();
|
|
633
624
|
}
|
|
@@ -640,7 +631,7 @@ export async function runExecution(config, input, dependencies) {
|
|
|
640
631
|
throw new AggregateError([error, abortError], `Failed to stop the execution supervisor: ${detail}`);
|
|
641
632
|
}
|
|
642
633
|
}
|
|
643
|
-
completion =
|
|
634
|
+
completion = cancelled
|
|
644
635
|
? ExecutionCompletedSchema.parse({
|
|
645
636
|
type: "execution:completed",
|
|
646
637
|
protocolVersion: 1,
|
|
@@ -44,6 +44,55 @@ async function waitForProcessGroupExit(pid, timeoutMs) {
|
|
|
44
44
|
await new Promise((resolve) => setTimeout(resolve, PROCESS_GROUP_POLL_MS));
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
async function processGroupExists(pid) {
|
|
48
|
+
try {
|
|
49
|
+
process.kill(-pid, 0);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const code = error.code;
|
|
54
|
+
if (code === "ESRCH")
|
|
55
|
+
return false;
|
|
56
|
+
if (code === "EPERM")
|
|
57
|
+
return true;
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function signalOwnedTreeIfPresent(pid, signal, platform, signalTree) {
|
|
62
|
+
try {
|
|
63
|
+
await signalTree(pid, signal, platform);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error.code !== "ESRCH")
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function terminateAndConfirmOwnedTree(pid, platform, timeoutMs, supervisorClosed, signalTree) {
|
|
71
|
+
const waitUntilStopped = () => platform === "win32"
|
|
72
|
+
? waitForExit(supervisorClosed.then(() => ({ exitCode: 0 })), timeoutMs, pid)
|
|
73
|
+
: waitForProcessGroupExit(pid, timeoutMs);
|
|
74
|
+
let termError;
|
|
75
|
+
try {
|
|
76
|
+
await signalOwnedTreeIfPresent(pid, "SIGTERM", platform, signalTree);
|
|
77
|
+
await waitUntilStopped();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
termError = error;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
await signalOwnedTreeIfPresent(pid, "SIGKILL", platform, signalTree);
|
|
85
|
+
await waitUntilStopped();
|
|
86
|
+
}
|
|
87
|
+
catch (killError) {
|
|
88
|
+
throw new AggregateError([termError, killError], "Supervisor process-tree termination failed");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function confirmOrTerminateOwnedTree(pid, platform, timeoutMs, supervisorClosed, signalTree) {
|
|
92
|
+
if (platform !== "win32" && !(await processGroupExists(pid)))
|
|
93
|
+
return;
|
|
94
|
+
await terminateAndConfirmOwnedTree(pid, platform, timeoutMs, supervisorClosed, signalTree);
|
|
95
|
+
}
|
|
47
96
|
async function withTimeout(promise, timeoutMs, phase) {
|
|
48
97
|
let timer;
|
|
49
98
|
try {
|
|
@@ -86,9 +135,13 @@ export async function signalSupervisorTree(pid, signal, platform = process.platf
|
|
|
86
135
|
}
|
|
87
136
|
export async function startDormantSupervisor(launch, options = {}) {
|
|
88
137
|
const platform = options.platform ?? process.platform;
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
91
|
-
|
|
138
|
+
const ownershipMode = options.ownershipMode ?? "durable";
|
|
139
|
+
if (ownershipMode === "durable") {
|
|
140
|
+
const backend = executionBackendCapability(platform);
|
|
141
|
+
if (!backend.supported)
|
|
142
|
+
throw new Error(backend.reason);
|
|
143
|
+
}
|
|
144
|
+
const signalTree = options.signalTree ?? signalSupervisorTree;
|
|
92
145
|
const childEntry = options.childEntry
|
|
93
146
|
?? fileURLToPath(new URL("./execution-supervisor-child.js", import.meta.url));
|
|
94
147
|
const abortTimeoutMs = options.abortTimeoutMs ?? DEFAULT_ABORT_TIMEOUT_MS;
|
|
@@ -118,12 +171,16 @@ export async function startDormantSupervisor(launch, options = {}) {
|
|
|
118
171
|
...(supervisorSpawnError === undefined && signal !== null ? { terminationSignal: signal } : {}),
|
|
119
172
|
}));
|
|
120
173
|
});
|
|
174
|
+
const supervisorClosed = new Promise((resolve) => child.once("close", () => resolve()));
|
|
175
|
+
let treeStopPromise = null;
|
|
176
|
+
const ensureTreeStopped = () => {
|
|
177
|
+
treeStopPromise ??= confirmOrTerminateOwnedTree(pid, platform, abortTimeoutMs, supervisorClosed, signalTree);
|
|
178
|
+
return treeStopPromise;
|
|
179
|
+
};
|
|
121
180
|
const exit = supervisorExit.then(async (result) => {
|
|
122
|
-
|
|
123
|
-
await waitForProcessGroupExit(pid, abortTimeoutMs);
|
|
181
|
+
await ensureTreeStopped();
|
|
124
182
|
return result;
|
|
125
183
|
});
|
|
126
|
-
const supervisorClosed = new Promise((resolve) => child.once("close", () => resolve()));
|
|
127
184
|
let readyResolve;
|
|
128
185
|
let readyReject;
|
|
129
186
|
const ready = new Promise((resolve, reject) => {
|
|
@@ -161,41 +218,30 @@ export async function startDormantSupervisor(launch, options = {}) {
|
|
|
161
218
|
const abort = async () => {
|
|
162
219
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
163
220
|
await supervisorClosed;
|
|
221
|
+
await ensureTreeStopped();
|
|
164
222
|
return;
|
|
165
223
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
catch (error) {
|
|
170
|
-
const code = error instanceof Error && "code" in error ? error.code : undefined;
|
|
171
|
-
if (code !== "ESRCH")
|
|
172
|
-
throw error;
|
|
173
|
-
}
|
|
174
|
-
try {
|
|
175
|
-
await waitForExit(supervisorClosed.then(() => ({ exitCode: 0 })), abortTimeoutMs, pid);
|
|
176
|
-
}
|
|
177
|
-
catch (error) {
|
|
178
|
-
try {
|
|
179
|
-
await signalSupervisorTree(pid, "SIGKILL", platform);
|
|
180
|
-
}
|
|
181
|
-
catch (killError) {
|
|
182
|
-
const code = killError instanceof Error && "code" in killError ? killError.code : undefined;
|
|
183
|
-
if (code !== "ESRCH")
|
|
184
|
-
throw new AggregateError([error, killError], "Supervisor abort failed");
|
|
185
|
-
}
|
|
186
|
-
await waitForExit(supervisorClosed.then(() => ({ exitCode: 0 })), abortTimeoutMs, pid);
|
|
187
|
-
}
|
|
224
|
+
await ensureTreeStopped();
|
|
225
|
+
await waitForExit(supervisorClosed.then(() => ({ exitCode: 0 })), abortTimeoutMs, pid);
|
|
226
|
+
await ensureTreeStopped();
|
|
188
227
|
};
|
|
189
228
|
const cancel = async () => {
|
|
190
229
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
191
230
|
await supervisorClosed;
|
|
231
|
+
await ensureTreeStopped();
|
|
192
232
|
return;
|
|
193
233
|
}
|
|
194
234
|
try {
|
|
195
235
|
await new Promise((resolve, reject) => {
|
|
196
236
|
child.send({ type: "abort" }, (error) => error === null ? resolve() : reject(error));
|
|
197
237
|
});
|
|
198
|
-
|
|
238
|
+
if (platform === "win32") {
|
|
239
|
+
await ensureTreeStopped();
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
await waitForExit(supervisorClosed.then(() => ({ exitCode: 0 })), abortTimeoutMs, pid);
|
|
243
|
+
await ensureTreeStopped();
|
|
244
|
+
}
|
|
199
245
|
}
|
|
200
246
|
catch {
|
|
201
247
|
await abort();
|
|
@@ -218,9 +264,8 @@ export async function startDormantSupervisor(launch, options = {}) {
|
|
|
218
264
|
});
|
|
219
265
|
throw error;
|
|
220
266
|
}
|
|
221
|
-
|
|
267
|
+
const handle = {
|
|
222
268
|
pid,
|
|
223
|
-
parentExitGuard: "pipe-eof",
|
|
224
269
|
stdout: child.stdout,
|
|
225
270
|
stderr: child.stderr,
|
|
226
271
|
exit,
|
|
@@ -249,4 +294,7 @@ export async function startDormantSupervisor(launch, options = {}) {
|
|
|
249
294
|
abort,
|
|
250
295
|
cancel,
|
|
251
296
|
};
|
|
297
|
+
return ownershipMode === "durable"
|
|
298
|
+
? { ...handle, ownershipMode, parentExitGuard: "pipe-eof" }
|
|
299
|
+
: { ...handle, ownershipMode };
|
|
252
300
|
}
|