@nowcrew/daemon 0.6.36 → 0.6.38
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/dist/config.js +6 -0
- package/dist/execution-protocol.js +6 -0
- package/dist/execution-runner.js +75 -6
- package/dist/local-executor.js +3 -0
- package/dist/provider-error.js +61 -0
- package/dist/runtime-health.js +14 -1
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -19,6 +19,9 @@ export const DEFAULT_EXECUTION_LIMITS = Object.freeze({
|
|
|
19
19
|
maxStartingPerRuntime: 10,
|
|
20
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,
|
|
@@ -9,6 +9,9 @@ const MIN_SIGNED_32_INTEGER = -2_147_483_648;
|
|
|
9
9
|
const SequenceSchema = z.number().int().min(0).max(MAX_PG_INTEGER);
|
|
10
10
|
const TokenCountSchema = z.number().int().min(0).max(MAX_PG_INTEGER);
|
|
11
11
|
const ExitCodeSchema = z.number().int().min(MIN_SIGNED_32_INTEGER).max(MAX_PG_INTEGER);
|
|
12
|
+
const ProviderErrorCategorySchema = z.enum(["auth", "quota", "rate_limit", "timeout", "network", "upstream", "cli", "unknown"]);
|
|
13
|
+
const ProviderHttpStatusSchema = z.number().int().min(100).max(599);
|
|
14
|
+
const ProviderErrorCodeSchema = z.string().min(1).max(128).regex(/^[A-Za-z0-9_.:-]+$/u);
|
|
12
15
|
const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness"]);
|
|
13
16
|
const UnsafePathCharacterSchema = /[\p{Cc}<>:"/\\|?*]/u;
|
|
14
17
|
const WindowsReservedNameSchema = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i;
|
|
@@ -308,6 +311,9 @@ const RawExecutionCompletedSchema = z.object({
|
|
|
308
311
|
terminationSignal: z.string().min(1).optional(),
|
|
309
312
|
errorCode: z.string().min(1).optional(),
|
|
310
313
|
errorMessage: z.string().min(1).optional(),
|
|
314
|
+
providerErrorCategory: ProviderErrorCategorySchema.optional(),
|
|
315
|
+
providerHttpStatus: ProviderHttpStatusSchema.optional(),
|
|
316
|
+
providerErrorCode: ProviderErrorCodeSchema.optional(),
|
|
311
317
|
runtime: RuntimeSchema,
|
|
312
318
|
model: z.string().optional(),
|
|
313
319
|
resumed: z.boolean(),
|
package/dist/execution-runner.js
CHANGED
|
@@ -296,6 +296,12 @@ class ExecutionCancelledError extends Error {
|
|
|
296
296
|
this.name = "ExecutionCancelledError";
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
|
+
class ExecutionQueueTimeoutError extends Error {
|
|
300
|
+
constructor(timeoutMs) {
|
|
301
|
+
super(`Execution queue wait exceeded ${timeoutMs}ms`);
|
|
302
|
+
this.name = "ExecutionQueueTimeoutError";
|
|
303
|
+
}
|
|
304
|
+
}
|
|
299
305
|
async function cancellable(promise, cancellation) {
|
|
300
306
|
if (cancellation === undefined)
|
|
301
307
|
return promise;
|
|
@@ -306,11 +312,28 @@ async function cancellable(promise, cancellation) {
|
|
|
306
312
|
cancellation.requested.then(() => { throw new ExecutionCancelledError(); }),
|
|
307
313
|
]);
|
|
308
314
|
}
|
|
315
|
+
async function cancellableWithTimeout(promise, timeoutMs, cancellation, timeoutError) {
|
|
316
|
+
let timer;
|
|
317
|
+
try {
|
|
318
|
+
return await Promise.race([
|
|
319
|
+
cancellable(promise, cancellation),
|
|
320
|
+
new Promise((_resolve, reject) => {
|
|
321
|
+
timer = setTimeout(() => reject(timeoutError), timeoutMs);
|
|
322
|
+
}),
|
|
323
|
+
]);
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
if (timer !== undefined)
|
|
327
|
+
clearTimeout(timer);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
309
330
|
function failedCompletion(spec, error, startedAt, finishedAt) {
|
|
310
331
|
const message = error instanceof Error ? error.message : String(error);
|
|
311
|
-
const errorCode = error instanceof
|
|
312
|
-
?
|
|
313
|
-
:
|
|
332
|
+
const errorCode = error instanceof ExecutionQueueTimeoutError
|
|
333
|
+
? "queue_timeout"
|
|
334
|
+
: error instanceof ProjectContextUnavailableError
|
|
335
|
+
? error.code
|
|
336
|
+
: projectSkillProjectionErrorCode(error) ?? "local_execution_failed";
|
|
314
337
|
return ExecutionCompletedSchema.parse({
|
|
315
338
|
type: "execution:completed",
|
|
316
339
|
protocolVersion: 1,
|
|
@@ -455,7 +478,9 @@ export async function runExecution(config, input, dependencies) {
|
|
|
455
478
|
let startedAt = accepted.acceptedAt;
|
|
456
479
|
let runtimeCancel = null;
|
|
457
480
|
let timeout;
|
|
481
|
+
let firstOutputTimer;
|
|
458
482
|
let timedOut = false;
|
|
483
|
+
let firstOutputTimedOut = false;
|
|
459
484
|
let completion;
|
|
460
485
|
let abandonedRuntimeRunningCompletion = null;
|
|
461
486
|
let memoryCaptureFinalText = null;
|
|
@@ -468,7 +493,7 @@ export async function runExecution(config, input, dependencies) {
|
|
|
468
493
|
});
|
|
469
494
|
try {
|
|
470
495
|
if (dependencies.slot !== undefined) {
|
|
471
|
-
await
|
|
496
|
+
await cancellableWithTimeout(dependencies.slot.ready, config.executionLimits.maxQueueWaitMs, dependencies.cancellation, new ExecutionQueueTimeoutError(config.executionLimits.maxQueueWaitMs));
|
|
472
497
|
}
|
|
473
498
|
let projectContext;
|
|
474
499
|
let sessionContextFingerprint;
|
|
@@ -508,7 +533,18 @@ export async function runExecution(config, input, dependencies) {
|
|
|
508
533
|
if (dependencies.cancellation?.isRequested())
|
|
509
534
|
return;
|
|
510
535
|
const cancel = runtimeCancel;
|
|
511
|
-
if (cancel !== null && timeout === undefined) {
|
|
536
|
+
if (cancel !== null && timeout === undefined && !dependencies.cancellation?.isRequested()) {
|
|
537
|
+
if (effectiveTimeoutMs > config.executionLimits.maxFirstOutputWaitMs + config.executionLimits.firstOutputGraceMs) {
|
|
538
|
+
firstOutputTimer = setTimeout(() => {
|
|
539
|
+
firstOutputTimedOut = true;
|
|
540
|
+
try {
|
|
541
|
+
void cancel().catch(rejectCancellationFailure);
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
rejectCancellationFailure(error);
|
|
545
|
+
}
|
|
546
|
+
}, config.executionLimits.maxFirstOutputWaitMs + config.executionLimits.firstOutputGraceMs);
|
|
547
|
+
}
|
|
512
548
|
timeout = setTimeout(() => {
|
|
513
549
|
timedOut = true;
|
|
514
550
|
try {
|
|
@@ -566,6 +602,10 @@ export async function runExecution(config, input, dependencies) {
|
|
|
566
602
|
},
|
|
567
603
|
...(spec.reporting.streamActivity ? {
|
|
568
604
|
onActivity: (activity) => {
|
|
605
|
+
if (firstOutputTimer !== undefined) {
|
|
606
|
+
clearTimeout(firstOutputTimer);
|
|
607
|
+
firstOutputTimer = undefined;
|
|
608
|
+
}
|
|
569
609
|
const frame = DaemonToServerExecutionFrameSchema.parse({
|
|
570
610
|
type: "execution:activity",
|
|
571
611
|
protocolVersion: 1,
|
|
@@ -584,6 +624,10 @@ export async function runExecution(config, input, dependencies) {
|
|
|
584
624
|
} : {}),
|
|
585
625
|
...(spec.reporting.streamConsole ? {
|
|
586
626
|
onConsole: (chunk) => {
|
|
627
|
+
if (firstOutputTimer !== undefined) {
|
|
628
|
+
clearTimeout(firstOutputTimer);
|
|
629
|
+
firstOutputTimer = undefined;
|
|
630
|
+
}
|
|
587
631
|
const frame = DaemonToServerExecutionFrameSchema.parse({
|
|
588
632
|
type: "execution:console",
|
|
589
633
|
protocolVersion: 1,
|
|
@@ -603,6 +647,10 @@ export async function runExecution(config, input, dependencies) {
|
|
|
603
647
|
} : {}),
|
|
604
648
|
...(spec.context.externalResponseSessionId || spec.context.answerStream ? {
|
|
605
649
|
onExternalOutput: (text) => {
|
|
650
|
+
if (firstOutputTimer !== undefined) {
|
|
651
|
+
clearTimeout(firstOutputTimer);
|
|
652
|
+
firstOutputTimer = undefined;
|
|
653
|
+
}
|
|
606
654
|
const frame = DaemonToServerExecutionFrameSchema.parse({
|
|
607
655
|
type: "execution:output",
|
|
608
656
|
protocolVersion: 1,
|
|
@@ -778,6 +826,8 @@ export async function runExecution(config, input, dependencies) {
|
|
|
778
826
|
]);
|
|
779
827
|
if (timeout !== undefined)
|
|
780
828
|
clearTimeout(timeout);
|
|
829
|
+
if (firstOutputTimer !== undefined)
|
|
830
|
+
clearTimeout(firstOutputTimer);
|
|
781
831
|
const finishedAt = now().toISOString();
|
|
782
832
|
if (result.exitCode === 0 && result.finalText?.trim())
|
|
783
833
|
memoryCaptureFinalText = result.finalText;
|
|
@@ -787,7 +837,19 @@ export async function runExecution(config, input, dependencies) {
|
|
|
787
837
|
await resetBoundImDecision(path);
|
|
788
838
|
boundImDecision = selected?.decision ?? "silent";
|
|
789
839
|
}
|
|
790
|
-
completion = ExecutionCompletedSchema.parse(
|
|
840
|
+
completion = ExecutionCompletedSchema.parse(firstOutputTimedOut ? {
|
|
841
|
+
type: "execution:completed",
|
|
842
|
+
protocolVersion: 1,
|
|
843
|
+
executionId: spec.executionId,
|
|
844
|
+
outcome: "cancelled",
|
|
845
|
+
errorCode: "runtime_no_first_output",
|
|
846
|
+
errorMessage: "Runtime produced no activity before the first-output deadline",
|
|
847
|
+
runtime: result.runtime,
|
|
848
|
+
...(result.model === null ? {} : { model: result.model }),
|
|
849
|
+
resumed: result.resumed,
|
|
850
|
+
startedAt,
|
|
851
|
+
finishedAt,
|
|
852
|
+
} : timedOut ? {
|
|
791
853
|
type: "execution:completed",
|
|
792
854
|
protocolVersion: 1,
|
|
793
855
|
executionId: spec.executionId,
|
|
@@ -818,6 +880,11 @@ export async function runExecution(config, input, dependencies) {
|
|
|
818
880
|
...(!spec.reporting.captureFinal || result.finalText === null
|
|
819
881
|
? {}
|
|
820
882
|
: { finalText: result.finalText }),
|
|
883
|
+
...(result.providerError === undefined ? {} : {
|
|
884
|
+
providerErrorCategory: result.providerError.category,
|
|
885
|
+
...(result.providerError.httpStatus === undefined ? {} : { providerHttpStatus: result.providerError.httpStatus }),
|
|
886
|
+
...(result.providerError.code === undefined ? {} : { providerErrorCode: result.providerError.code }),
|
|
887
|
+
}),
|
|
821
888
|
// externalAnswer 是本轮完成的权威结果,与 answerStream(增量流式的可选增强)解耦转发;
|
|
822
889
|
// 否则能力协商降级后模型仍可能沿用同线程记住的 marker 契约,回复却永远发不到频道
|
|
823
890
|
// (incident: 2026-07-30, 2026-08-03)。
|
|
@@ -832,6 +899,8 @@ export async function runExecution(config, input, dependencies) {
|
|
|
832
899
|
catch (error) {
|
|
833
900
|
if (timeout !== undefined)
|
|
834
901
|
clearTimeout(timeout);
|
|
902
|
+
if (firstOutputTimer !== undefined)
|
|
903
|
+
clearTimeout(firstOutputTimer);
|
|
835
904
|
const cancelled = error instanceof ExecutionCancelledError || error instanceof RuntimeCancelledError;
|
|
836
905
|
if (cancelled) {
|
|
837
906
|
await closeLaunchGate();
|
package/dist/local-executor.js
CHANGED
|
@@ -12,6 +12,7 @@ import { applyDeepSeekHarnessMachineEnv, applyProviderEnv, providerFingerprint,
|
|
|
12
12
|
import { augmentedPath } from "./runtime-path.js";
|
|
13
13
|
import { extractFinalText, extractRunMeta, normalizeEvent, parseLine, } from "./normalize.js";
|
|
14
14
|
import { readSession, writeSession, pickResumeId, isNearBudget } from "./session.js";
|
|
15
|
+
import { providerErrorFromExit } from "./provider-error.js";
|
|
15
16
|
import { createConsoleFormatter } from "./console-formatter.js";
|
|
16
17
|
import { withLocalExecutionFacts, } from "./local-execution-prompt.js";
|
|
17
18
|
export { withLocalExecutionFacts, } from "./local-execution-prompt.js";
|
|
@@ -872,6 +873,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
872
873
|
: redactProjectSkillRuntimeRootText(spawnError, activeProjectSkillRuntimeRoot),
|
|
873
874
|
terminationSignal ? `terminated by ${terminationSignal}` : undefined,
|
|
874
875
|
].filter(Boolean).join(" ").trim();
|
|
876
|
+
const providerError = providerErrorFromExit(exitCode, errorTail);
|
|
875
877
|
const finish = exitActivity(runtime.name, exitCode, errorTail);
|
|
876
878
|
if (finish) {
|
|
877
879
|
activities.push(finish);
|
|
@@ -914,6 +916,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
914
916
|
resumed: resuming,
|
|
915
917
|
sessionId,
|
|
916
918
|
errorMessage: errorTail || null,
|
|
919
|
+
...(providerError === undefined ? {} : { providerError }),
|
|
917
920
|
finalText: input.captureFinal && finalText !== null
|
|
918
921
|
? stripExternalAnswerMarkers(finalText)
|
|
919
922
|
: null,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function providerErrorFromExit(exitCode, errorText) {
|
|
2
|
+
if (exitCode === 0)
|
|
3
|
+
return undefined;
|
|
4
|
+
return errorText ? classifyProviderError(errorText) : { category: "cli", code: "process_exit" };
|
|
5
|
+
}
|
|
6
|
+
const STATUS_PATTERN = /\b(?:http(?:\/\d(?:\.\d)?)?\s*)?(?:status(?:_code)?\s*[:=]?\s*|response\s+status\s+|error\s+)([1-5]\d{2})\b/iu;
|
|
7
|
+
const KNOWN_STATUS_PATTERN = /\b(401|403|408|429|500|502|503|504)\b/u;
|
|
8
|
+
const CODE_BY_STATUS = Object.freeze({
|
|
9
|
+
401: "unauthorized", 403: "forbidden", 408: "request_timeout", 429: "rate_limited",
|
|
10
|
+
500: "internal_server_error", 502: "bad_gateway", 503: "service_unavailable", 504: "gateway_timeout",
|
|
11
|
+
});
|
|
12
|
+
function statusFromText(text) {
|
|
13
|
+
const explicit = STATUS_PATTERN.exec(text)?.[1] ?? KNOWN_STATUS_PATTERN.exec(text)?.[1];
|
|
14
|
+
if (explicit === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
const value = Number(explicit);
|
|
17
|
+
return Number.isInteger(value) && value >= 100 && value <= 599 ? value : undefined;
|
|
18
|
+
}
|
|
19
|
+
function categoryFromStatus(status) {
|
|
20
|
+
if (status === 401 || status === 403)
|
|
21
|
+
return "auth";
|
|
22
|
+
if (status === 429)
|
|
23
|
+
return "rate_limit";
|
|
24
|
+
if (status === 408 || status === 504)
|
|
25
|
+
return "timeout";
|
|
26
|
+
if (status >= 500)
|
|
27
|
+
return "upstream";
|
|
28
|
+
return "unknown";
|
|
29
|
+
}
|
|
30
|
+
export function classifyProviderError(raw) {
|
|
31
|
+
const text = raw.trim().slice(-4_000);
|
|
32
|
+
const httpStatus = statusFromText(text);
|
|
33
|
+
if (httpStatus !== undefined) {
|
|
34
|
+
const code = CODE_BY_STATUS[httpStatus];
|
|
35
|
+
return {
|
|
36
|
+
category: categoryFromStatus(httpStatus),
|
|
37
|
+
httpStatus,
|
|
38
|
+
...(code === undefined ? {} : { code }),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const lower = text.toLowerCase();
|
|
42
|
+
if (/quota|credit|billing|insufficient.*(?:fund|balance)/u.test(lower)) {
|
|
43
|
+
return { category: "quota", code: "quota_exhausted" };
|
|
44
|
+
}
|
|
45
|
+
if (/unauthori[sz]ed|authentication failed|invalid api[_ -]?key|\bauth(?:entication)? failed\b/u.test(lower)) {
|
|
46
|
+
return { category: "auth", code: "auth_failed" };
|
|
47
|
+
}
|
|
48
|
+
if (/rate[ -]?limit|too many requests|throttl/u.test(lower)) {
|
|
49
|
+
return { category: "rate_limit", code: "rate_limited" };
|
|
50
|
+
}
|
|
51
|
+
if (/timed? ?out|timeout|deadline exceeded/u.test(lower)) {
|
|
52
|
+
return { category: "timeout", code: "timeout" };
|
|
53
|
+
}
|
|
54
|
+
if (/econn|enotfound|dns|socket|network|tls|certificate/u.test(lower)) {
|
|
55
|
+
return { category: "network", code: "network_error" };
|
|
56
|
+
}
|
|
57
|
+
if (/unknown option|invalid option|command not found|usage:/u.test(lower)) {
|
|
58
|
+
return { category: "cli", code: "cli_error" };
|
|
59
|
+
}
|
|
60
|
+
return { category: "unknown" };
|
|
61
|
+
}
|
package/dist/runtime-health.js
CHANGED
|
@@ -2,6 +2,7 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { augmentedPath } from "./runtime-path.js";
|
|
5
|
+
import { classifyProviderError } from "./provider-error.js";
|
|
5
6
|
export const RUNTIME_HEALTH_PROBE_CAPABILITY = "runtime_health_probe_v1";
|
|
6
7
|
export const RUNTIME_HEALTH_NAMES = ["claude", "codex", "opencode", "kimi"];
|
|
7
8
|
const RuntimeProbeTargetSchema = z.object({
|
|
@@ -140,7 +141,19 @@ export async function probeRuntimeHealth(request, dependencies = {}) {
|
|
|
140
141
|
}
|
|
141
142
|
catch (error) {
|
|
142
143
|
const code = isAbort(error) ? "timeout" : failureCode(error);
|
|
143
|
-
|
|
144
|
+
const providerError = isAbort(error)
|
|
145
|
+
? { category: "timeout", code: "timeout" }
|
|
146
|
+
: classifyProviderError(failureText(error));
|
|
147
|
+
return {
|
|
148
|
+
...base,
|
|
149
|
+
installation: "available",
|
|
150
|
+
model: code,
|
|
151
|
+
latencyMs: latencyMs(),
|
|
152
|
+
detailCode: code,
|
|
153
|
+
providerErrorCategory: providerError.category,
|
|
154
|
+
...(providerError.httpStatus === undefined ? {} : { providerHttpStatus: providerError.httpStatus }),
|
|
155
|
+
...(providerError.code === undefined ? {} : { providerErrorCode: providerError.code }),
|
|
156
|
+
};
|
|
144
157
|
}
|
|
145
158
|
}));
|
|
146
159
|
}
|