@nowcrew/daemon 0.6.45 → 0.6.46
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 +16 -5
- package/dist/config.js +9 -3
- package/dist/execution-journal.js +24 -0
- package/dist/execution-runner.js +244 -47
- package/dist/runtimes/progress-watchdog.js +16 -4
- package/dist/serve.js +68 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -376,18 +376,29 @@ CREW_SCHEDULED_MAX_PARALLEL=4
|
|
|
376
376
|
CREW_EXECUTION_MAX_QUEUED_PER_AGENT=32
|
|
377
377
|
CREW_EXECUTION_MAX_PARALLEL_TOTAL=10
|
|
378
378
|
CREW_EXECUTION_MAX_QUEUED_TOTAL=128
|
|
379
|
-
CREW_EXECUTION_MAX_STARTING_TOTAL=
|
|
380
|
-
CREW_EXECUTION_MAX_STARTING_PER_RUNTIME=
|
|
381
|
-
CREW_EXECUTION_START_GAP_MS=
|
|
379
|
+
CREW_EXECUTION_MAX_STARTING_TOTAL=10
|
|
380
|
+
CREW_EXECUTION_MAX_STARTING_PER_RUNTIME=10
|
|
381
|
+
CREW_EXECUTION_START_GAP_MS=500
|
|
382
382
|
CREW_EXECUTION_STARTUP_TIMEOUT_MS=120000
|
|
383
|
+
CREW_EXECUTION_MAX_QUEUE_WAIT_MS=300000
|
|
384
|
+
CREW_EXECUTION_MAX_FIRST_OUTPUT_WAIT_MS=120000
|
|
385
|
+
CREW_EXECUTION_FIRST_OUTPUT_GRACE_MS=180000
|
|
383
386
|
```
|
|
384
387
|
|
|
385
388
|
`CREW_MAX_PARALLEL` limits normal executions per Agent. `CREW_SCHEDULED_MAX_PARALLEL` separately
|
|
386
389
|
limits scheduled executions per Agent. `MAX_PARALLEL_TOTAL` is the machine-wide process cap shared by
|
|
387
390
|
protocol-v1 and legacy work.
|
|
388
|
-
Runtime startup is a separate FIFO gate: by default
|
|
389
|
-
initializing at a time, and launches are spaced by
|
|
391
|
+
Runtime startup is a separate FIFO gate: by default up to ten Claude, Codex, or Kimi processes may be
|
|
392
|
+
initializing at a time, and launches are spaced by 500ms. A process leaves the startup gate
|
|
390
393
|
only after its runtime-specific ready event; startup timeout cancels the owned process tree.
|
|
394
|
+
An accepted execution may wait up to five minutes for a local and host slot. After runtime readiness,
|
|
395
|
+
the daemon waits up to two minutes for the first model event, with a three-minute grace window for
|
|
396
|
+
slow providers. When one of these local liveness limits expires, the supervisor is stopped and the
|
|
397
|
+
terminal result is written to the execution journal before it is reported, so the execution remains
|
|
398
|
+
replayable instead of being silently dropped. Queue and first-output timeouts are retried locally at
|
|
399
|
+
most twice (three total attempts). Each retry releases its current machine/host slot and reserves a
|
|
400
|
+
new one, which puts it behind work already waiting in the queue. A third timeout is terminal and is
|
|
401
|
+
not requeued; other failures are not automatically retried.
|
|
391
402
|
|
|
392
403
|
Local policy can reduce server-requested access and limits; it cannot grant more access than requested.
|
|
393
404
|
Provider credentials and configured environment are prepared locally and never carried in execution
|
package/dist/config.js
CHANGED
|
@@ -15,10 +15,13 @@ export const DEFAULT_EXECUTION_LIMITS = Object.freeze({
|
|
|
15
15
|
maxQueuedPerAgent: 32,
|
|
16
16
|
maxParallelTotal: 10,
|
|
17
17
|
maxQueuedTotal: 128,
|
|
18
|
-
maxStartingTotal:
|
|
19
|
-
maxStartingPerRuntime:
|
|
20
|
-
startupGapMs:
|
|
18
|
+
maxStartingTotal: 10,
|
|
19
|
+
maxStartingPerRuntime: 10,
|
|
20
|
+
startupGapMs: 500,
|
|
21
21
|
startupTimeoutMs: 120_000,
|
|
22
|
+
maxQueueWaitMs: 5 * 60_000,
|
|
23
|
+
maxFirstOutputWaitMs: 2 * 60_000,
|
|
24
|
+
firstOutputGraceMs: 3 * 60_000,
|
|
22
25
|
});
|
|
23
26
|
// Fits the largest mandatory v1 lifecycle envelope (UUID + timestamps + outcome facts) with margin.
|
|
24
27
|
export const MIN_EXECUTION_EVENT_BYTES = 512;
|
|
@@ -85,6 +88,9 @@ export function loadConfig(env = process.env) {
|
|
|
85
88
|
maxStartingPerRuntime: positiveIntegerEnv(env, "CREW_EXECUTION_MAX_STARTING_PER_RUNTIME", DEFAULT_EXECUTION_LIMITS.maxStartingPerRuntime),
|
|
86
89
|
startupGapMs: positiveIntegerEnv(env, "CREW_EXECUTION_START_GAP_MS", DEFAULT_EXECUTION_LIMITS.startupGapMs),
|
|
87
90
|
startupTimeoutMs: positiveIntegerEnv(env, "CREW_EXECUTION_STARTUP_TIMEOUT_MS", DEFAULT_EXECUTION_LIMITS.startupTimeoutMs),
|
|
91
|
+
maxQueueWaitMs: positiveIntegerEnv(env, "CREW_EXECUTION_MAX_QUEUE_WAIT_MS", DEFAULT_EXECUTION_LIMITS.maxQueueWaitMs),
|
|
92
|
+
maxFirstOutputWaitMs: positiveIntegerEnv(env, "CREW_EXECUTION_MAX_FIRST_OUTPUT_WAIT_MS", DEFAULT_EXECUTION_LIMITS.maxFirstOutputWaitMs),
|
|
93
|
+
firstOutputGraceMs: positiveIntegerEnv(env, "CREW_EXECUTION_FIRST_OUTPUT_GRACE_MS", DEFAULT_EXECUTION_LIMITS.firstOutputGraceMs),
|
|
88
94
|
});
|
|
89
95
|
return {
|
|
90
96
|
serverUrl,
|
|
@@ -748,6 +748,30 @@ export function createExecutionJournal(agentsRoot, options = {}) {
|
|
|
748
748
|
return { kind: "created", ...entry };
|
|
749
749
|
});
|
|
750
750
|
},
|
|
751
|
+
retry: async (executionId) => {
|
|
752
|
+
validateExecutionId(executionId);
|
|
753
|
+
return serializedExecution(executionId, "retry", async () => {
|
|
754
|
+
const entry = await requireRecord(executionId);
|
|
755
|
+
if (entry.state !== "accepted" && entry.state !== "running") {
|
|
756
|
+
throw new JournalTransitionError(`Cannot retry ${entry.state} execution`);
|
|
757
|
+
}
|
|
758
|
+
const timestamp = now().toISOString();
|
|
759
|
+
const updated = JournalEntrySchema.parse({
|
|
760
|
+
...entry,
|
|
761
|
+
state: "accepted",
|
|
762
|
+
pid: null,
|
|
763
|
+
completion: null,
|
|
764
|
+
completionAcknowledged: false,
|
|
765
|
+
acceptedAt: timestamp,
|
|
766
|
+
processStartedAt: null,
|
|
767
|
+
runtimeReadyAt: null,
|
|
768
|
+
processIdentity: null,
|
|
769
|
+
updatedAt: timestamp,
|
|
770
|
+
});
|
|
771
|
+
await writeRecord(updated);
|
|
772
|
+
return updated;
|
|
773
|
+
});
|
|
774
|
+
},
|
|
751
775
|
startGuarded: async (executionId, processStartedAt, startDormant, hooks) => {
|
|
752
776
|
validateExecutionId(executionId);
|
|
753
777
|
TimestampSchema.parse(processStartedAt);
|
package/dist/execution-runner.js
CHANGED
|
@@ -4,7 +4,7 @@ import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, Execution
|
|
|
4
4
|
import { JournalConflictError } from "./execution-journal.js";
|
|
5
5
|
import { boundExecutionFrame } from "./execution-event-limit.js";
|
|
6
6
|
import { mintAgentToken } from "./token.js";
|
|
7
|
-
import { executeLocal, withLocalExecutionFacts, } from "./local-executor.js";
|
|
7
|
+
import { executeLocal, RuntimeRunningCallbackTimeoutError, withLocalExecutionFacts, } from "./local-executor.js";
|
|
8
8
|
import { startDormantSupervisor, } from "./execution-supervisor.js";
|
|
9
9
|
import { CLAUDE_EFFORT_LEVELS } from "./runtimes/claude.js";
|
|
10
10
|
import { CODEX_EFFORT_LEVELS } from "./runtimes/codex.js";
|
|
@@ -25,6 +25,7 @@ import { PROJECT_SKILL_PROJECTION_V2_CAPABILITY } from "./project-skills/types.j
|
|
|
25
25
|
import { dslog } from "./slog.js";
|
|
26
26
|
import { identityRosterPromptFacts } from "./identity-roster-telemetry.js";
|
|
27
27
|
export { supervisorLaunch } from "./supervised-runtime.js";
|
|
28
|
+
const MAX_LOCAL_EXECUTION_ATTEMPTS = 3;
|
|
28
29
|
const ACTIVITY_KIND = {
|
|
29
30
|
init: "working",
|
|
30
31
|
text: "thinking",
|
|
@@ -298,6 +299,12 @@ class ExecutionCancelledError extends Error {
|
|
|
298
299
|
this.name = "ExecutionCancelledError";
|
|
299
300
|
}
|
|
300
301
|
}
|
|
302
|
+
class ExecutionQueueTimeoutError extends Error {
|
|
303
|
+
constructor(timeoutMs) {
|
|
304
|
+
super(`Execution queue wait exceeded ${timeoutMs}ms`);
|
|
305
|
+
this.name = "ExecutionQueueTimeoutError";
|
|
306
|
+
}
|
|
307
|
+
}
|
|
301
308
|
async function cancellable(promise, cancellation) {
|
|
302
309
|
if (cancellation === undefined)
|
|
303
310
|
return promise;
|
|
@@ -308,9 +315,24 @@ async function cancellable(promise, cancellation) {
|
|
|
308
315
|
cancellation.requested.then(() => { throw new ExecutionCancelledError(); }),
|
|
309
316
|
]);
|
|
310
317
|
}
|
|
318
|
+
async function cancellableWithTimeout(promise, timeoutMs, cancellation, timeoutError) {
|
|
319
|
+
let timer;
|
|
320
|
+
try {
|
|
321
|
+
return await Promise.race([
|
|
322
|
+
cancellable(promise, cancellation),
|
|
323
|
+
new Promise((_resolve, reject) => {
|
|
324
|
+
timer = setTimeout(() => reject(timeoutError), timeoutMs);
|
|
325
|
+
}),
|
|
326
|
+
]);
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
if (timer !== undefined)
|
|
330
|
+
clearTimeout(timer);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
311
333
|
function failedCompletion(spec, error, startedAt, finishedAt) {
|
|
312
334
|
const message = error instanceof Error ? error.message : String(error);
|
|
313
|
-
const errorCode = error instanceof HostReservationCancelledError
|
|
335
|
+
const errorCode = error instanceof ExecutionQueueTimeoutError || error instanceof HostReservationCancelledError
|
|
314
336
|
? "queue_timeout"
|
|
315
337
|
: error instanceof ProjectContextUnavailableError
|
|
316
338
|
? error.code
|
|
@@ -331,6 +353,10 @@ function failedCompletion(spec, error, startedAt, finishedAt) {
|
|
|
331
353
|
}
|
|
332
354
|
export async function runExecution(config, input, dependencies) {
|
|
333
355
|
const now = dependencies.now ?? (() => new Date());
|
|
356
|
+
const retryAttempt = dependencies.retryAttempt ?? 1;
|
|
357
|
+
if (!Number.isInteger(retryAttempt) || retryAttempt < 1 || retryAttempt > MAX_LOCAL_EXECUTION_ATTEMPTS) {
|
|
358
|
+
throw new RangeError(`retryAttempt must be between 1 and ${MAX_LOCAL_EXECUTION_ATTEMPTS}`);
|
|
359
|
+
}
|
|
334
360
|
const initialAt = now().toISOString();
|
|
335
361
|
const parsed = ExecutionStartSchema.safeParse(input);
|
|
336
362
|
if (!parsed.success) {
|
|
@@ -373,31 +399,38 @@ export async function runExecution(config, input, dependencies) {
|
|
|
373
399
|
await dependencies.report(boundExecutionFrame(replay.completion, config.executionLimits.maxEventBytes));
|
|
374
400
|
return { kind: "existing", entry: replay };
|
|
375
401
|
}
|
|
376
|
-
|
|
377
|
-
type: "execution:accepted",
|
|
378
|
-
protocolVersion: 1,
|
|
379
|
-
executionId: spec.executionId,
|
|
380
|
-
state: dependencies.slot?.state ?? "ready",
|
|
381
|
-
effectivePermission: replayPermission,
|
|
382
|
-
at: replay.acceptedAt,
|
|
383
|
-
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
384
|
-
if (replay.state === "running" && replay.runtimeReadyAt !== null) {
|
|
402
|
+
if (retryAttempt === 1) {
|
|
385
403
|
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
386
|
-
type: "execution:
|
|
404
|
+
type: "execution:accepted",
|
|
387
405
|
protocolVersion: 1,
|
|
388
406
|
executionId: spec.executionId,
|
|
389
|
-
|
|
407
|
+
state: dependencies.slot?.state ?? "ready",
|
|
408
|
+
effectivePermission: replayPermission,
|
|
409
|
+
at: replay.acceptedAt,
|
|
390
410
|
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
411
|
+
if (replay.state === "running" && replay.runtimeReadyAt !== null) {
|
|
412
|
+
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
413
|
+
type: "execution:started",
|
|
414
|
+
protocolVersion: 1,
|
|
415
|
+
executionId: spec.executionId,
|
|
416
|
+
at: replay.runtimeReadyAt,
|
|
417
|
+
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
418
|
+
}
|
|
419
|
+
return { kind: "existing", entry: replay };
|
|
391
420
|
}
|
|
392
|
-
return { kind: "existing", entry: replay };
|
|
393
421
|
}
|
|
394
|
-
|
|
422
|
+
// A retry already owns a freshly admitted slot. Re-running the initial capacity
|
|
423
|
+
// admission against the first attempt's stale facts would reject a valid tail retry.
|
|
424
|
+
const checked = retryAttempt > 1
|
|
425
|
+
? { permission: effectivePermission(spec, config) }
|
|
426
|
+
: admission(spec, config, dependencies, initialAt);
|
|
395
427
|
if ("rejected" in checked) {
|
|
396
428
|
const frame = ExecutionRejectedSchema.parse(boundExecutionFrame(checked.rejected, config.executionLimits.maxEventBytes));
|
|
397
429
|
await dependencies.report(frame);
|
|
398
430
|
return { kind: "rejected", frame };
|
|
399
431
|
}
|
|
400
432
|
const { permission } = checked;
|
|
433
|
+
const effectiveTimeoutMs = spec.runtime.timeoutMs ?? config.executionLimits.maxTimeoutMs;
|
|
401
434
|
const accepted = await dependencies.journal.accept(spec.executionId, specHash, {
|
|
402
435
|
runtime: spec.runtime.name,
|
|
403
436
|
...(spec.runtime.model === undefined ? {} : { model: spec.runtime.model }),
|
|
@@ -410,32 +443,36 @@ export async function runExecution(config, input, dependencies) {
|
|
|
410
443
|
await dependencies.report(boundExecutionFrame(accepted.completion, config.executionLimits.maxEventBytes));
|
|
411
444
|
return { kind: "existing", entry: accepted };
|
|
412
445
|
}
|
|
446
|
+
if (retryAttempt === 1) {
|
|
447
|
+
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
448
|
+
type: "execution:accepted",
|
|
449
|
+
protocolVersion: 1,
|
|
450
|
+
executionId: spec.executionId,
|
|
451
|
+
state: dependencies.slot?.state ?? "ready",
|
|
452
|
+
effectivePermission: accepted.effectivePermission ?? permission,
|
|
453
|
+
at: accepted.acceptedAt,
|
|
454
|
+
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
455
|
+
if (accepted.state === "running" && accepted.runtimeReadyAt !== null) {
|
|
456
|
+
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
457
|
+
type: "execution:started",
|
|
458
|
+
protocolVersion: 1,
|
|
459
|
+
executionId: spec.executionId,
|
|
460
|
+
at: accepted.runtimeReadyAt,
|
|
461
|
+
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
462
|
+
}
|
|
463
|
+
return { kind: "existing", entry: accepted };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (retryAttempt === 1) {
|
|
413
467
|
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
414
468
|
type: "execution:accepted",
|
|
415
469
|
protocolVersion: 1,
|
|
416
470
|
executionId: spec.executionId,
|
|
417
471
|
state: dependencies.slot?.state ?? "ready",
|
|
418
|
-
effectivePermission:
|
|
472
|
+
effectivePermission: permission,
|
|
419
473
|
at: accepted.acceptedAt,
|
|
420
474
|
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
421
|
-
if (accepted.state === "running" && accepted.runtimeReadyAt !== null) {
|
|
422
|
-
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
423
|
-
type: "execution:started",
|
|
424
|
-
protocolVersion: 1,
|
|
425
|
-
executionId: spec.executionId,
|
|
426
|
-
at: accepted.runtimeReadyAt,
|
|
427
|
-
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
428
|
-
}
|
|
429
|
-
return { kind: "existing", entry: accepted };
|
|
430
475
|
}
|
|
431
|
-
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
432
|
-
type: "execution:accepted",
|
|
433
|
-
protocolVersion: 1,
|
|
434
|
-
executionId: spec.executionId,
|
|
435
|
-
state: dependencies.slot?.state ?? "ready",
|
|
436
|
-
effectivePermission: permission,
|
|
437
|
-
at: accepted.acceptedAt,
|
|
438
|
-
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
439
476
|
const mint = dependencies.mintAgentToken ?? mintAgentToken;
|
|
440
477
|
const execute = dependencies.executeLocal ?? executeLocal;
|
|
441
478
|
const startSupervisor = dependencies.startSupervisor ?? startDormantSupervisor;
|
|
@@ -466,6 +503,11 @@ export async function runExecution(config, input, dependencies) {
|
|
|
466
503
|
};
|
|
467
504
|
let startedAt = accepted.acceptedAt;
|
|
468
505
|
let runtimeCancel = null;
|
|
506
|
+
let timeout;
|
|
507
|
+
let timedOut = false;
|
|
508
|
+
let firstOutputTimer;
|
|
509
|
+
let firstOutputTimedOut = false;
|
|
510
|
+
let abandonedRuntimeRunningCompletion = null;
|
|
469
511
|
let completion;
|
|
470
512
|
let memoryCaptureFinalText = null;
|
|
471
513
|
let boundImDecision = spec.reporting.allowBoundImDecision
|
|
@@ -477,7 +519,7 @@ export async function runExecution(config, input, dependencies) {
|
|
|
477
519
|
});
|
|
478
520
|
try {
|
|
479
521
|
if (dependencies.slot !== undefined) {
|
|
480
|
-
await
|
|
522
|
+
await cancellableWithTimeout(dependencies.slot.ready, config.executionLimits.maxQueueWaitMs, dependencies.cancellation, new ExecutionQueueTimeoutError(config.executionLimits.maxQueueWaitMs));
|
|
481
523
|
}
|
|
482
524
|
let projectContext;
|
|
483
525
|
let sessionContextFingerprint;
|
|
@@ -508,23 +550,90 @@ export async function runExecution(config, input, dependencies) {
|
|
|
508
550
|
let consoleSequence = 0;
|
|
509
551
|
let externalOutputSequence = 0;
|
|
510
552
|
const callbacks = {
|
|
511
|
-
|
|
553
|
+
onRuntimeStarting: () => {
|
|
554
|
+
dependencies.onRuntimePhase?.("starting", spec.runtime.name);
|
|
555
|
+
},
|
|
556
|
+
onRuntimeRunning: async () => {
|
|
557
|
+
const callbackStartedAt = Date.now();
|
|
558
|
+
let callbackStage = "journal";
|
|
512
559
|
if (dependencies.cancellation?.isRequested())
|
|
513
560
|
return;
|
|
514
|
-
const
|
|
515
|
-
if (
|
|
516
|
-
|
|
561
|
+
const cancel = runtimeCancel;
|
|
562
|
+
if (cancel !== null && timeout === undefined && !dependencies.cancellation?.isRequested()) {
|
|
563
|
+
const firstOutputDeadline = config.executionLimits.maxFirstOutputWaitMs
|
|
564
|
+
+ config.executionLimits.firstOutputGraceMs;
|
|
565
|
+
if (effectiveTimeoutMs > firstOutputDeadline) {
|
|
566
|
+
firstOutputTimer = setTimeout(() => {
|
|
567
|
+
firstOutputTimedOut = true;
|
|
568
|
+
try {
|
|
569
|
+
void cancel().catch(rejectCancellationFailure);
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
rejectCancellationFailure(error);
|
|
573
|
+
}
|
|
574
|
+
}, firstOutputDeadline);
|
|
575
|
+
}
|
|
576
|
+
timeout = setTimeout(() => {
|
|
577
|
+
timedOut = true;
|
|
578
|
+
try {
|
|
579
|
+
void cancel().catch(rejectCancellationFailure);
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
rejectCancellationFailure(error);
|
|
583
|
+
}
|
|
584
|
+
}, effectiveTimeoutMs);
|
|
585
|
+
}
|
|
586
|
+
dependencies.onRuntimePhase?.("running", spec.runtime.name);
|
|
587
|
+
const journalStartedAt = Date.now();
|
|
588
|
+
try {
|
|
589
|
+
const ready = await dependencies.journal.markRuntimeReady(spec.executionId, now().toISOString());
|
|
590
|
+
const journalMs = Date.now() - journalStartedAt;
|
|
591
|
+
if (ready.runtimeReadyAt === null) {
|
|
592
|
+
throw new Error(`Execution ${spec.executionId} runtime readiness was not persisted`);
|
|
593
|
+
}
|
|
594
|
+
if (abandonedRuntimeRunningCompletion !== null) {
|
|
595
|
+
await dependencies.journal.complete(spec.executionId, abandonedRuntimeRunningCompletion);
|
|
596
|
+
await dependencies.report(abandonedRuntimeRunningCompletion);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
startedAt = ready.runtimeReadyAt;
|
|
600
|
+
callbackStage = "started_report";
|
|
601
|
+
const reportStartedAt = Date.now();
|
|
602
|
+
const reportDelivered = await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
603
|
+
type: "execution:started",
|
|
604
|
+
protocolVersion: 1,
|
|
605
|
+
executionId: spec.executionId,
|
|
606
|
+
at: startedAt,
|
|
607
|
+
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
608
|
+
dslog("execution.runtime_running_callback", "runtime-running callback 已完成", {
|
|
609
|
+
execution_id: spec.executionId,
|
|
610
|
+
runtime: spec.runtime.name,
|
|
611
|
+
outcome: "succeeded",
|
|
612
|
+
journal_ms: journalMs,
|
|
613
|
+
started_report_ms: Date.now() - reportStartedAt,
|
|
614
|
+
started_report_delivered: reportDelivered,
|
|
615
|
+
callback_total_ms: Date.now() - callbackStartedAt,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
catch (error) {
|
|
619
|
+
dslog("execution.runtime_running_callback", "runtime-running callback 失败", {
|
|
620
|
+
level: "ERROR",
|
|
621
|
+
execution_id: spec.executionId,
|
|
622
|
+
runtime: spec.runtime.name,
|
|
623
|
+
outcome: "failed",
|
|
624
|
+
failed_stage: callbackStage,
|
|
625
|
+
callback_total_ms: Date.now() - callbackStartedAt,
|
|
626
|
+
error_message: error instanceof Error ? error.message.slice(0, 500) : String(error).slice(0, 500),
|
|
627
|
+
});
|
|
628
|
+
throw error;
|
|
517
629
|
}
|
|
518
|
-
startedAt = ready.runtimeReadyAt;
|
|
519
|
-
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
520
|
-
type: "execution:started",
|
|
521
|
-
protocolVersion: 1,
|
|
522
|
-
executionId: spec.executionId,
|
|
523
|
-
at: startedAt,
|
|
524
|
-
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
525
630
|
},
|
|
526
631
|
...(spec.reporting.streamActivity ? {
|
|
527
632
|
onActivity: (activity) => {
|
|
633
|
+
if (firstOutputTimer !== undefined) {
|
|
634
|
+
clearTimeout(firstOutputTimer);
|
|
635
|
+
firstOutputTimer = undefined;
|
|
636
|
+
}
|
|
528
637
|
const frame = DaemonToServerExecutionFrameSchema.parse({
|
|
529
638
|
type: "execution:activity",
|
|
530
639
|
protocolVersion: 1,
|
|
@@ -543,6 +652,10 @@ export async function runExecution(config, input, dependencies) {
|
|
|
543
652
|
} : {}),
|
|
544
653
|
...(spec.reporting.streamConsole ? {
|
|
545
654
|
onConsole: (chunk) => {
|
|
655
|
+
if (firstOutputTimer !== undefined) {
|
|
656
|
+
clearTimeout(firstOutputTimer);
|
|
657
|
+
firstOutputTimer = undefined;
|
|
658
|
+
}
|
|
546
659
|
const frame = DaemonToServerExecutionFrameSchema.parse({
|
|
547
660
|
type: "execution:console",
|
|
548
661
|
protocolVersion: 1,
|
|
@@ -562,6 +675,10 @@ export async function runExecution(config, input, dependencies) {
|
|
|
562
675
|
} : {}),
|
|
563
676
|
...(spec.context.externalResponseSessionId || spec.context.answerStream ? {
|
|
564
677
|
onExternalOutput: (text) => {
|
|
678
|
+
if (firstOutputTimer !== undefined) {
|
|
679
|
+
clearTimeout(firstOutputTimer);
|
|
680
|
+
firstOutputTimer = undefined;
|
|
681
|
+
}
|
|
565
682
|
const frame = DaemonToServerExecutionFrameSchema.parse({
|
|
566
683
|
type: "execution:output",
|
|
567
684
|
protocolVersion: 1,
|
|
@@ -735,6 +852,10 @@ export async function runExecution(config, input, dependencies) {
|
|
|
735
852
|
execute(localInput, callbacks, localDependencies),
|
|
736
853
|
cancellationFailure,
|
|
737
854
|
]);
|
|
855
|
+
if (timeout !== undefined)
|
|
856
|
+
clearTimeout(timeout);
|
|
857
|
+
if (firstOutputTimer !== undefined)
|
|
858
|
+
clearTimeout(firstOutputTimer);
|
|
738
859
|
const finishedAt = now().toISOString();
|
|
739
860
|
if (result.exitCode === 0 && result.finalText?.trim())
|
|
740
861
|
memoryCaptureFinalText = result.finalText;
|
|
@@ -744,7 +865,33 @@ export async function runExecution(config, input, dependencies) {
|
|
|
744
865
|
await resetBoundImDecision(path);
|
|
745
866
|
boundImDecision = selected?.decision ?? "silent";
|
|
746
867
|
}
|
|
747
|
-
completion = ExecutionCompletedSchema.parse({
|
|
868
|
+
completion = ExecutionCompletedSchema.parse(firstOutputTimedOut ? {
|
|
869
|
+
type: "execution:completed",
|
|
870
|
+
protocolVersion: 1,
|
|
871
|
+
executionId: spec.executionId,
|
|
872
|
+
outcome: "cancelled",
|
|
873
|
+
errorCode: "runtime_no_first_output",
|
|
874
|
+
errorMessage: "Runtime produced no activity before the first-output deadline",
|
|
875
|
+
runtime: result.runtime,
|
|
876
|
+
...(result.model === null ? {} : { model: result.model }),
|
|
877
|
+
resumed: result.resumed,
|
|
878
|
+
...(boundImDecision ? { boundImDecision } : {}),
|
|
879
|
+
startedAt,
|
|
880
|
+
finishedAt,
|
|
881
|
+
} : timedOut ? {
|
|
882
|
+
type: "execution:completed",
|
|
883
|
+
protocolVersion: 1,
|
|
884
|
+
executionId: spec.executionId,
|
|
885
|
+
outcome: "cancelled",
|
|
886
|
+
errorCode: "timeout",
|
|
887
|
+
errorMessage: "Execution exceeded its local timeout",
|
|
888
|
+
runtime: result.runtime,
|
|
889
|
+
...(result.model === null ? {} : { model: result.model }),
|
|
890
|
+
resumed: result.resumed,
|
|
891
|
+
...(boundImDecision ? { boundImDecision } : {}),
|
|
892
|
+
startedAt,
|
|
893
|
+
finishedAt,
|
|
894
|
+
} : {
|
|
748
895
|
type: "execution:completed",
|
|
749
896
|
protocolVersion: 1,
|
|
750
897
|
executionId: spec.executionId,
|
|
@@ -779,6 +926,10 @@ export async function runExecution(config, input, dependencies) {
|
|
|
779
926
|
});
|
|
780
927
|
}
|
|
781
928
|
catch (error) {
|
|
929
|
+
if (timeout !== undefined)
|
|
930
|
+
clearTimeout(timeout);
|
|
931
|
+
if (firstOutputTimer !== undefined)
|
|
932
|
+
clearTimeout(firstOutputTimer);
|
|
782
933
|
const cancelled = error instanceof ExecutionCancelledError || error instanceof RuntimeCancelledError;
|
|
783
934
|
if (cancelled) {
|
|
784
935
|
await closeLaunchGate();
|
|
@@ -795,7 +946,7 @@ export async function runExecution(config, input, dependencies) {
|
|
|
795
946
|
else {
|
|
796
947
|
await proveActiveSupervisorStopped(error, spec.agent.projectSkillBindingGeneration !== undefined);
|
|
797
948
|
}
|
|
798
|
-
|
|
949
|
+
const failureCompletion = cancelled
|
|
799
950
|
? ExecutionCompletedSchema.parse({
|
|
800
951
|
type: "execution:completed",
|
|
801
952
|
protocolVersion: 1,
|
|
@@ -810,9 +961,55 @@ export async function runExecution(config, input, dependencies) {
|
|
|
810
961
|
finishedAt: now().toISOString(),
|
|
811
962
|
})
|
|
812
963
|
: failedCompletion(spec, error, startedAt, now().toISOString());
|
|
964
|
+
if (error instanceof RuntimeRunningCallbackTimeoutError) {
|
|
965
|
+
abandonedRuntimeRunningCompletion = boundExecutionFrame(failureCompletion, config.executionLimits.maxEventBytes);
|
|
966
|
+
await telemetry.closeAndDrain();
|
|
967
|
+
throw error;
|
|
968
|
+
}
|
|
969
|
+
completion = failureCompletion;
|
|
813
970
|
}
|
|
814
971
|
completion = boundExecutionFrame(completion, config.executionLimits.maxEventBytes);
|
|
815
972
|
await telemetry.closeAndDrain();
|
|
973
|
+
const retryReason = completion.errorCode === "queue_timeout"
|
|
974
|
+
? "queue_timeout"
|
|
975
|
+
: completion.errorCode === "runtime_no_first_output"
|
|
976
|
+
? "runtime_no_first_output"
|
|
977
|
+
: null;
|
|
978
|
+
if (retryReason !== null
|
|
979
|
+
&& retryAttempt < MAX_LOCAL_EXECUTION_ATTEMPTS
|
|
980
|
+
&& dependencies.onRetry !== undefined
|
|
981
|
+
&& dependencies.journal.retry !== undefined
|
|
982
|
+
&& !dependencies.cancellation?.isRequested()) {
|
|
983
|
+
try {
|
|
984
|
+
await dependencies.journal.retry(spec.executionId);
|
|
985
|
+
const nextSlot = await dependencies.onRetry({ attempt: retryAttempt, reason: retryReason });
|
|
986
|
+
if (nextSlot !== undefined) {
|
|
987
|
+
return runExecution(config, spec, {
|
|
988
|
+
...dependencies,
|
|
989
|
+
slot: nextSlot,
|
|
990
|
+
retryAttempt: retryAttempt + 1,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
catch (error) {
|
|
995
|
+
dslog("execution.retry_requeue_failed", "execution 超时重排队失败,保留本次终态", {
|
|
996
|
+
level: "ERROR",
|
|
997
|
+
execution_id: spec.executionId,
|
|
998
|
+
attempt: retryAttempt,
|
|
999
|
+
reason: retryReason,
|
|
1000
|
+
error_message: error instanceof Error ? error.message.slice(0, 500) : String(error).slice(0, 500),
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
if (retryReason !== null && retryAttempt >= MAX_LOCAL_EXECUTION_ATTEMPTS) {
|
|
1005
|
+
dslog("execution.retry_exhausted", "execution 超时重试次数已耗尽", {
|
|
1006
|
+
level: "WARN",
|
|
1007
|
+
execution_id: spec.executionId,
|
|
1008
|
+
attempt: retryAttempt,
|
|
1009
|
+
max_attempts: MAX_LOCAL_EXECUTION_ATTEMPTS,
|
|
1010
|
+
reason: retryReason,
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
816
1013
|
await dependencies.journal.complete(spec.executionId, completion);
|
|
817
1014
|
if (completion.outcome === "succeeded"
|
|
818
1015
|
&& spec.agent.memoryEnabled === true
|
|
@@ -4,11 +4,23 @@ export const DEFAULT_FIRST_PROGRESS_TIMEOUT_MS = 120_000;
|
|
|
4
4
|
* the runtime's configured total timeout remains authoritative; long-running tools are not killed
|
|
5
5
|
* merely because they produce no output.
|
|
6
6
|
*/
|
|
7
|
-
export function startFirstProgressWatchdog(
|
|
7
|
+
export function startFirstProgressWatchdog(onTimeout, timeoutMs = DEFAULT_FIRST_PROGRESS_TIMEOUT_MS) {
|
|
8
8
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
9
9
|
throw new RangeError("First-progress timeout must be a positive finite number");
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
let active = true;
|
|
12
|
+
const timer = setTimeout(() => {
|
|
13
|
+
if (!active)
|
|
14
|
+
return;
|
|
15
|
+
active = false;
|
|
16
|
+
onTimeout();
|
|
17
|
+
}, timeoutMs);
|
|
18
|
+
timer.unref?.();
|
|
19
|
+
const stop = () => {
|
|
20
|
+
if (!active)
|
|
21
|
+
return;
|
|
22
|
+
active = false;
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
};
|
|
25
|
+
return { observe: stop, stop };
|
|
14
26
|
}
|
package/dist/serve.js
CHANGED
|
@@ -516,7 +516,7 @@ export function serve(config, opts = {}) {
|
|
|
516
516
|
const executionClass = pruneTraceId !== null
|
|
517
517
|
? "memory_prune"
|
|
518
518
|
: spec.context.scheduledRunId === undefined ? "normal" : "scheduled";
|
|
519
|
-
|
|
519
|
+
let reservation = sharedSlots.reserve(spec.agent.handle, "execution", executionClass);
|
|
520
520
|
if (!reservation.accepted) {
|
|
521
521
|
dslog("execution.machine_queue_rejected", "机器执行队列已满", {
|
|
522
522
|
level: "WARN",
|
|
@@ -533,7 +533,7 @@ export function serve(config, opts = {}) {
|
|
|
533
533
|
return;
|
|
534
534
|
}
|
|
535
535
|
knownExecutionHashes.set(spec.executionId, hash);
|
|
536
|
-
|
|
536
|
+
let machineQueueEnteredAt = Date.now();
|
|
537
537
|
const executionConcurrencyIdentity = {
|
|
538
538
|
executionId: spec.executionId,
|
|
539
539
|
agentHandle: spec.agent.handle,
|
|
@@ -571,6 +571,70 @@ export function serve(config, opts = {}) {
|
|
|
571
571
|
});
|
|
572
572
|
};
|
|
573
573
|
const cancellation = cancellationFor(spec.executionId);
|
|
574
|
+
const slotForReservation = (attemptReservation, queueEnteredAt) => ({
|
|
575
|
+
state: attemptReservation.state ?? (attemptReservation.isQueued() ? "queued" : "ready"),
|
|
576
|
+
ready: attemptReservation.ready.then(() => {
|
|
577
|
+
markExecutionTaskKeyActive();
|
|
578
|
+
attemptReservation.markPreparing();
|
|
579
|
+
concurrencyTelemetry.transition(executionConcurrencyIdentity, "preparing", {
|
|
580
|
+
queue_ms: Date.now() - queueEnteredAt,
|
|
581
|
+
});
|
|
582
|
+
const snapshot = sharedSlots.snapshot();
|
|
583
|
+
dslog("execution.machine_slot_ready", "execution 获得机器执行名额", {
|
|
584
|
+
execution_id: spec.executionId,
|
|
585
|
+
agent_handle: spec.agent.handle,
|
|
586
|
+
execution_class: executionClass,
|
|
587
|
+
queue_ms: Date.now() - queueEnteredAt,
|
|
588
|
+
active_total: snapshot.runningTotal,
|
|
589
|
+
admitted_total: snapshot.admittedTotal,
|
|
590
|
+
preparing_total: snapshot.preparingTotal,
|
|
591
|
+
starting_total: snapshot.startingTotal,
|
|
592
|
+
running_total: snapshot.runningTotal,
|
|
593
|
+
queued_total: snapshot.queuedTotal,
|
|
594
|
+
memory_prune_admitted_total: snapshot.memoryPruneAdmittedTotal,
|
|
595
|
+
memory_prune_queued_total: snapshot.memoryPruneQueuedTotal,
|
|
596
|
+
memory_prune_preparing_total: snapshot.memoryPrunePreparingTotal,
|
|
597
|
+
memory_prune_starting_total: snapshot.memoryPruneStartingTotal,
|
|
598
|
+
memory_prune_running_total: snapshot.memoryPruneRunningTotal,
|
|
599
|
+
});
|
|
600
|
+
}),
|
|
601
|
+
});
|
|
602
|
+
const onRetry = async ({ attempt, reason, }) => {
|
|
603
|
+
if (stopped || cancellation.isRequested())
|
|
604
|
+
return undefined;
|
|
605
|
+
const previousReservation = reservation;
|
|
606
|
+
previousReservation.release();
|
|
607
|
+
concurrencyTelemetry.release(executionConcurrencyIdentity, `retry:${reason}`);
|
|
608
|
+
const nextReservation = sharedSlots.reserve(spec.agent.handle, "execution", executionClass);
|
|
609
|
+
if (!nextReservation.accepted) {
|
|
610
|
+
executionReservations.delete(spec.executionId);
|
|
611
|
+
dslog("execution.retry_queue_rejected", "execution 超时重排队被机器队列拒绝", {
|
|
612
|
+
level: "WARN",
|
|
613
|
+
execution_id: spec.executionId,
|
|
614
|
+
agent_handle: spec.agent.handle,
|
|
615
|
+
execution_class: executionClass,
|
|
616
|
+
attempt,
|
|
617
|
+
reason,
|
|
618
|
+
...nextReservation.facts,
|
|
619
|
+
});
|
|
620
|
+
return undefined;
|
|
621
|
+
}
|
|
622
|
+
reservation = nextReservation;
|
|
623
|
+
machineQueueEnteredAt = Date.now();
|
|
624
|
+
executionReservations.set(spec.executionId, nextReservation);
|
|
625
|
+
if (nextReservation.isQueued()) {
|
|
626
|
+
concurrencyTelemetry.transition(executionConcurrencyIdentity, "queued");
|
|
627
|
+
dslog("execution.retry_queued", "execution 超时后重新进入机器队列", {
|
|
628
|
+
execution_id: spec.executionId,
|
|
629
|
+
agent_handle: spec.agent.handle,
|
|
630
|
+
execution_class: executionClass,
|
|
631
|
+
attempt: attempt + 1,
|
|
632
|
+
reason,
|
|
633
|
+
...nextReservation.facts,
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
return slotForReservation(nextReservation, machineQueueEnteredAt);
|
|
637
|
+
};
|
|
574
638
|
const cleanupExecutionReservation = (reason = "execution_settled") => {
|
|
575
639
|
if (executionTaskKeyFinished)
|
|
576
640
|
return;
|
|
@@ -652,36 +716,8 @@ export function serve(config, opts = {}) {
|
|
|
652
716
|
memory_prune_running_total: snapshot.memoryPruneRunningTotal,
|
|
653
717
|
});
|
|
654
718
|
},
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
state: reservation.state,
|
|
658
|
-
ready: reservation.ready.then(() => {
|
|
659
|
-
markExecutionTaskKeyActive();
|
|
660
|
-
reservation.markPreparing();
|
|
661
|
-
concurrencyTelemetry.transition(executionConcurrencyIdentity, "preparing", {
|
|
662
|
-
queue_ms: Date.now() - machineQueueEnteredAt,
|
|
663
|
-
});
|
|
664
|
-
const snapshot = sharedSlots.snapshot();
|
|
665
|
-
dslog("execution.machine_slot_ready", "execution 获得机器执行名额", {
|
|
666
|
-
execution_id: spec.executionId,
|
|
667
|
-
agent_handle: spec.agent.handle,
|
|
668
|
-
execution_class: executionClass,
|
|
669
|
-
queue_ms: Date.now() - machineQueueEnteredAt,
|
|
670
|
-
active_total: snapshot.runningTotal,
|
|
671
|
-
admitted_total: snapshot.admittedTotal,
|
|
672
|
-
preparing_total: snapshot.preparingTotal,
|
|
673
|
-
starting_total: snapshot.startingTotal,
|
|
674
|
-
running_total: snapshot.runningTotal,
|
|
675
|
-
queued_total: snapshot.queuedTotal,
|
|
676
|
-
memory_prune_admitted_total: snapshot.memoryPruneAdmittedTotal,
|
|
677
|
-
memory_prune_queued_total: snapshot.memoryPruneQueuedTotal,
|
|
678
|
-
memory_prune_preparing_total: snapshot.memoryPrunePreparingTotal,
|
|
679
|
-
memory_prune_starting_total: snapshot.memoryPruneStartingTotal,
|
|
680
|
-
memory_prune_running_total: snapshot.memoryPruneRunningTotal,
|
|
681
|
-
});
|
|
682
|
-
}),
|
|
683
|
-
},
|
|
684
|
-
}),
|
|
719
|
+
slot: slotForReservation(reservation, machineQueueEnteredAt),
|
|
720
|
+
onRetry,
|
|
685
721
|
cancellation,
|
|
686
722
|
}).finally(() => {
|
|
687
723
|
cleanupExecutionReservation("execution_settled");
|