@nowcrew/daemon 0.6.67 → 0.6.68
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/agent-ability/materializer.js +1 -20
- package/dist/agent-ability/resolver.js +10 -7
- package/dist/agent-ability/runtime.js +0 -3
- package/dist/automatic-install-target.js +10 -15
- package/dist/computer-cli.js +1 -10
- package/dist/computer-service.js +1 -41
- package/dist/config.js +0 -44
- package/dist/daemon-migration-controller.js +18 -16
- package/dist/execution-protocol.js +1 -0
- package/dist/execution-runner.js +8 -5
- package/dist/local-execution-prompt.js +11 -15
- package/dist/local-executor.js +53 -93
- package/dist/machine-info.js +4 -3
- package/dist/main.js +0 -83
- package/dist/managed-service-lifecycle.js +2 -47
- package/dist/managed-service-startup.js +0 -2
- package/dist/profile-layout.js +4 -10
- package/dist/project-skills/controller.js +0 -2
- package/dist/prompt.js +1 -19
- package/dist/provider-env.js +21 -3
- package/dist/runner.js +2 -4
- package/dist/runtimes/codex-app-server-runner.js +23 -55
- package/dist/runtimes/codex-contract.js +5 -20
- package/dist/runtimes/codex-deepseek-config.js +17 -7
- package/dist/runtimes/codex.js +3 -4
- package/dist/self-training-workspace.js +15 -0
- package/dist/serve.js +3 -68
- package/dist/supervised-runtime.js +1 -3
- package/dist/workspace.js +14 -34
- package/package.json +1 -1
- package/dist/local-memory/index-budget.js +0 -74
- package/dist/local-memory/pending.js +0 -111
- package/dist/local-memory/runtime.js +0 -87
- package/dist/local-memory/types.js +0 -1
- package/dist/local-memory/write.js +0 -448
- package/dist/runtimes/codex-command-safety.js +0 -75
- package/dist/runtimes/codex-turn-retry.js +0 -69
- package/dist/storage-config.js +0 -175
- package/dist/storage-control.js +0 -193
- package/dist/storage-copy.js +0 -609
- package/dist/storage-credential-guard.js +0 -21
- package/dist/storage-doctor.js +0 -228
- package/dist/storage-identity.js +0 -29
- package/dist/storage-layout.js +0 -143
- package/dist/storage-manifest.js +0 -855
- package/dist/storage-migration-controller.js +0 -417
- package/dist/storage-migration-manifest.js +0 -105
- package/dist/storage-migration-policy.js +0 -100
- package/dist/storage-runtime-controller.js +0 -339
- package/dist/storage-runtime-readiness.js +0 -184
- package/dist/storage-runtime.js +0 -39
- package/dist/storage-safe-fs.js +0 -302
- package/dist/storage-serve-adapter.js +0 -65
- package/dist/storage-status-delivery.js +0 -209
- package/dist/storage-status-replay.js +0 -125
- package/dist/storage-target-confirmation.js +0 -129
- package/dist/storage-worker-command.js +0 -27
- package/dist/storage-worker-executor.js +0 -384
- package/dist/storage-worker-handoff.js +0 -151
- package/dist/storage-worker-operation.js +0 -284
- package/dist/storage-worker-status.js +0 -232
package/dist/provider-env.js
CHANGED
|
@@ -11,6 +11,24 @@
|
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { DEEPSEEK_CODEX_KEY_ENV, deepSeekCodexHome, } from "./runtimes/codex-deepseek-config.js";
|
|
13
13
|
import { DEEPSEEK_HARNESS_CONFIG_ENV } from "./runtimes/deepseek-harness.js";
|
|
14
|
+
const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
15
|
+
const RESERVED_ENV = new Set([
|
|
16
|
+
"PATH",
|
|
17
|
+
"XDG_CONFIG_HOME",
|
|
18
|
+
"XDG_DATA_HOME",
|
|
19
|
+
"XDG_CACHE_HOME",
|
|
20
|
+
"GH_CONFIG_DIR",
|
|
21
|
+
"CLOUDSDK_CONFIG",
|
|
22
|
+
]);
|
|
23
|
+
/** Keep user-provided variables away from daemon-owned runtime and credential fields. */
|
|
24
|
+
export function sanitizeEnvVars(raw) {
|
|
25
|
+
if (!raw)
|
|
26
|
+
return {};
|
|
27
|
+
return Object.fromEntries(Object.entries(raw).filter(([key, value]) => typeof value === "string"
|
|
28
|
+
&& ENV_KEY_RE.test(key)
|
|
29
|
+
&& !key.toUpperCase().startsWith("CREW_")
|
|
30
|
+
&& !RESERVED_ENV.has(key.toUpperCase())));
|
|
31
|
+
}
|
|
14
32
|
/** custom 模式下必须从继承 env 中剔除的全局路由/凭证变量(避免与注入值叠加或抢优先级)。 */
|
|
15
33
|
const CONFLICTING_ENV = [
|
|
16
34
|
"ANTHROPIC_BASE_URL",
|
|
@@ -39,12 +57,12 @@ const CONFLICTING_CODEX_ENV = [
|
|
|
39
57
|
DEEPSEEK_CODEX_KEY_ENV,
|
|
40
58
|
];
|
|
41
59
|
/** 返回叠加了 provider 隔离的新 env(不改入参);不支持的 runtime/provider 组合原样返回。 */
|
|
42
|
-
export function applyProviderEnv(base, runtime, cfg, homeDir) {
|
|
60
|
+
export function applyProviderEnv(base, runtime, cfg, homeDir, deepSeekHomeOverride) {
|
|
43
61
|
if (runtime === "codex" && cfg.provider === "deepseek") {
|
|
44
62
|
const env = { ...base };
|
|
45
63
|
for (const key of CONFLICTING_CODEX_ENV)
|
|
46
64
|
delete env[key];
|
|
47
|
-
env.CODEX_HOME = deepSeekCodexHome(homeDir);
|
|
65
|
+
env.CODEX_HOME = deepSeekHomeOverride ?? deepSeekCodexHome(homeDir);
|
|
48
66
|
if (cfg.providerApiKey)
|
|
49
67
|
env[DEEPSEEK_CODEX_KEY_ENV] = cfg.providerApiKey;
|
|
50
68
|
return env;
|
|
@@ -100,7 +118,7 @@ export function applyDeepSeekHarnessMachineEnv(base, machineEnv) {
|
|
|
100
118
|
*/
|
|
101
119
|
export function providerFingerprint(runtime, cfg) {
|
|
102
120
|
if (runtime === "codex" && cfg.provider === "deepseek") {
|
|
103
|
-
return `deepseek|${cfg.providerApiKey ? "key" : "nokey"}`;
|
|
121
|
+
return `deepseek|${cfg.providerBaseUrl ?? "default"}|${cfg.providerApiKey ? "key" : "nokey"}`;
|
|
104
122
|
}
|
|
105
123
|
if (runtime !== "claude" || cfg.provider !== "custom")
|
|
106
124
|
return null;
|
package/dist/runner.js
CHANGED
|
@@ -10,7 +10,8 @@ import { readOriginDecisionFile, resetOriginDecisionFile } from "./origin-decisi
|
|
|
10
10
|
import { readBoundImDecisionFile, resetBoundImDecisionFile } from "./bound-im-decision.js";
|
|
11
11
|
import { launchSupervisedRuntime } from "./supervised-runtime.js";
|
|
12
12
|
import { awaitWithCancellation, } from "./runtime-cancellation.js";
|
|
13
|
-
export { awaitExit, exitActivity
|
|
13
|
+
export { awaitExit, exitActivity } from "./local-executor.js";
|
|
14
|
+
export { sanitizeEnvVars } from "./provider-env.js";
|
|
14
15
|
const ICON = {
|
|
15
16
|
init: "🟢", text: "💬", reading: "📖", sending: "📨", checking: "🔎",
|
|
16
17
|
claiming: "📌", crew: "⚙️", tool: "🛠️", tool_result: "↩️", done: "✅", error: "❌",
|
|
@@ -58,7 +59,6 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
|
|
|
58
59
|
...(input.wakeOrigin ? { wakeOrigin: input.wakeOrigin } : {}),
|
|
59
60
|
memory: capMemoryForInject(workspace.memory),
|
|
60
61
|
...(resuming ? {} : { workLog: capWorkLogForInject(workspace.workLog) }),
|
|
61
|
-
collaborationToolsEnabled: config.codexCollaborationTools && runtime === "codex",
|
|
62
62
|
}),
|
|
63
63
|
wakePrompt: ({ resuming, rotatedForBudget, nearBudget }) => {
|
|
64
64
|
const distillHint = nearBudget
|
|
@@ -75,7 +75,6 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
|
|
|
75
75
|
name: runtime,
|
|
76
76
|
...(providerConfig.model ? { model: providerConfig.model } : {}),
|
|
77
77
|
...(reasoning.success ? { reasoning: reasoning.data } : {}),
|
|
78
|
-
codexCollaborationTools: config.codexCollaborationTools,
|
|
79
78
|
},
|
|
80
79
|
effectivePermission: config.dangerous ? "full_access" : "workspace_write",
|
|
81
80
|
captureFinal: true,
|
|
@@ -107,7 +106,6 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
|
|
|
107
106
|
softTokens: config.sessionSoftTokens,
|
|
108
107
|
maxTurns: config.sessionMaxTurns,
|
|
109
108
|
},
|
|
110
|
-
localMemoryV2Enabled: config.localMemoryV2Handles?.has(input.handle) === true,
|
|
111
109
|
}, {
|
|
112
110
|
onActivity,
|
|
113
111
|
onConsole,
|
|
@@ -6,8 +6,7 @@ import { pathToFileURL } from "node:url";
|
|
|
6
6
|
import spawn from "cross-spawn";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { signalSupervisorTree } from "../execution-supervisor.js";
|
|
9
|
-
import {
|
|
10
|
-
import { decideTurnRetry, itemMakesTurnUnsafe } from "./codex-turn-retry.js";
|
|
9
|
+
import { assertCodexNativeWorkspaceVersion, assertCodexProjectWorkspaceResponse, assertCodexSkillsListResponse, boundedCodexPlan, codexProjectWorkspaceUnsupported, } from "./codex-contract.js";
|
|
11
10
|
import { startFirstProgressWatchdog } from "./progress-watchdog.js";
|
|
12
11
|
const RunnerInputSchema = z.object({
|
|
13
12
|
systemPrompt: z.string().min(1),
|
|
@@ -21,9 +20,13 @@ const RunnerInputSchema = z.object({
|
|
|
21
20
|
cwd: z.string().min(1).optional(),
|
|
22
21
|
workspaceRoots: z.array(z.string().min(1)).optional(),
|
|
23
22
|
skillRoots: z.array(z.string().min(1)).optional(),
|
|
24
|
-
|
|
23
|
+
networkAccess: z.boolean().optional(),
|
|
25
24
|
resume: z.boolean(),
|
|
26
|
-
}).strict()
|
|
25
|
+
}).strict().superRefine((input, ctx) => {
|
|
26
|
+
if (input.networkAccess === true && input.effectivePermission !== "workspace_write") {
|
|
27
|
+
ctx.addIssue({ code: "custom", message: "network_access_requires_workspace_write" });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
27
30
|
const RPC_TIMEOUT_MS = 30_000;
|
|
28
31
|
const INITIALIZE_RPC_TIMEOUT_MS = 60_000;
|
|
29
32
|
const ERROR_MESSAGE_CAP = 2_000;
|
|
@@ -39,17 +42,12 @@ const MAX_TRANSIENT_TURN_RETRIES = 2;
|
|
|
39
42
|
const TRANSIENT_TURN_RETRY_DELAY_MS = 15_000;
|
|
40
43
|
const TRANSIENT_TURN_RETRY_BACKOFF_FACTOR = 3;
|
|
41
44
|
const PROCESS_TREE_STOP_TIMEOUT_MS = 1_000;
|
|
42
|
-
/** 进程级:一旦某个 collabAgentToolCall 的 item/completed 没有对应 item/started,本执行后续永不重试(spec R4-3)。 */
|
|
43
|
-
let collabStartedMissing = false;
|
|
44
45
|
const USER_INPUT_UNAVAILABLE_MESSAGE = "NowWork cannot collect interactive user input in this headless run; ask the user in the final response instead of assuming an answer.";
|
|
45
|
-
export function codexAppServerArgs(
|
|
46
|
+
export function codexAppServerArgs(projectRootMarkers) {
|
|
46
47
|
return [
|
|
47
|
-
...(
|
|
48
|
+
...(projectRootMarkers === undefined
|
|
48
49
|
? []
|
|
49
|
-
: ["-c", `project_root_markers=${JSON.stringify(
|
|
50
|
-
// multi_agent 按开关;multi_agent_v2 无条件关,钉死已验证的 v1 契约(spec 2b)。
|
|
51
|
-
"-c", `features.multi_agent=${input.collaborationTools === true}`,
|
|
52
|
-
"-c", "features.multi_agent_v2=false",
|
|
50
|
+
: ["-c", `project_root_markers=${JSON.stringify(projectRootMarkers)}`]),
|
|
53
51
|
"app-server", "--listen", "stdio://",
|
|
54
52
|
];
|
|
55
53
|
}
|
|
@@ -60,8 +58,7 @@ const TRANSIENT_TURN_ERROR_PATTERNS = [
|
|
|
60
58
|
/too many requests/i,
|
|
61
59
|
];
|
|
62
60
|
/** 模型侧瞬态失败(网关过载/限流)→ 换个时间整轮重试大概率成功。
|
|
63
|
-
* interrupted
|
|
64
|
-
* 连带把父 turn 置为 interrupted。两类原因都只在本 turn 无白名单外工具活动时重试(codex-turn-retry.ts)。 */
|
|
61
|
+
* interrupted 不算:那是取消信号或 codex collab 连带中断,重跑语义不明确。 */
|
|
65
62
|
export function isTransientTurnFailure(detail) {
|
|
66
63
|
return TRANSIENT_TURN_ERROR_PATTERNS.some((pattern) => pattern.test(detail));
|
|
67
64
|
}
|
|
@@ -421,10 +418,7 @@ async function stopChildTree(child) {
|
|
|
421
418
|
}
|
|
422
419
|
async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs) {
|
|
423
420
|
const attemptStartedAt = Date.now();
|
|
424
|
-
const child = spawn(bin, codexAppServerArgs({
|
|
425
|
-
...(input.projectRootMarkers === undefined ? {} : { projectRootMarkers: input.projectRootMarkers }),
|
|
426
|
-
...(input.codexCollaborationTools === undefined ? {} : { collaborationTools: input.codexCollaborationTools }),
|
|
427
|
-
}), {
|
|
421
|
+
const child = spawn(bin, codexAppServerArgs(input.projectRootMarkers), {
|
|
428
422
|
cwd: process.cwd(),
|
|
429
423
|
env: process.env,
|
|
430
424
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -445,8 +439,6 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
|
|
|
445
439
|
let threadId = null;
|
|
446
440
|
let announcedThreadId = null;
|
|
447
441
|
let turnId = null;
|
|
448
|
-
let turnHasUnsafeSideEffect = false;
|
|
449
|
-
const startedItemIds = new Set();
|
|
450
442
|
let lastUsage;
|
|
451
443
|
let completionResolve;
|
|
452
444
|
let completionReject;
|
|
@@ -465,20 +457,6 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
|
|
|
465
457
|
|| method === "item/agentMessage/delta" || method === "item/completed") {
|
|
466
458
|
firstProgress.observe();
|
|
467
459
|
}
|
|
468
|
-
if (method === "item/started" || method === "item/completed") {
|
|
469
|
-
const item = params !== null && typeof params === "object"
|
|
470
|
-
? params.item
|
|
471
|
-
: undefined;
|
|
472
|
-
if (method === "item/started" && typeof item?.id === "string")
|
|
473
|
-
startedItemIds.add(item.id);
|
|
474
|
-
if (method === "item/completed" && item?.type === "collabAgentToolCall"
|
|
475
|
-
&& (typeof item.id !== "string" || !startedItemIds.has(item.id)) && !collabStartedMissing) {
|
|
476
|
-
collabStartedMissing = true;
|
|
477
|
-
process.stderr.write("[codex-app-server] collab_item_started_missing\n");
|
|
478
|
-
}
|
|
479
|
-
if (itemMakesTurnUnsafe(item))
|
|
480
|
-
turnHasUnsafeSideEffect = true;
|
|
481
|
-
}
|
|
482
460
|
for (const event of mapCodexNotification(method, params)) {
|
|
483
461
|
const eventThreadId = typeof event.thread_id === "string" ? event.thread_id : null;
|
|
484
462
|
if (event.type === "thread.started" && eventThreadId === announcedThreadId)
|
|
@@ -523,8 +501,6 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
|
|
|
523
501
|
if (input.workspaceRoots !== undefined || input.skillRoots !== undefined) {
|
|
524
502
|
assertCodexNativeWorkspaceVersion(initialized);
|
|
525
503
|
}
|
|
526
|
-
if (input.codexCollaborationTools === true)
|
|
527
|
-
assertCodexCollaborationVersion(initialized);
|
|
528
504
|
logStage(activeStage, "ok", attempt, stageStartedAt);
|
|
529
505
|
rpc.notify("initialized");
|
|
530
506
|
activeStage = input.resume ? "thread_resume" : "thread_start";
|
|
@@ -589,6 +565,9 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
|
|
|
589
565
|
...(input.reasoning === undefined || input.reasoning === "default"
|
|
590
566
|
? {}
|
|
591
567
|
: { effort: input.reasoning }),
|
|
568
|
+
...(input.networkAccess === true ? {
|
|
569
|
+
sandboxPolicy: { type: "workspaceWrite", networkAccess: true },
|
|
570
|
+
} : {}),
|
|
592
571
|
});
|
|
593
572
|
logStage(activeStage, "ok", attempt, stageStartedAt);
|
|
594
573
|
turnId = started.turn.id;
|
|
@@ -609,19 +588,15 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
|
|
|
609
588
|
}),
|
|
610
589
|
});
|
|
611
590
|
if (completed.turn?.status === "completed")
|
|
612
|
-
return { code: 0, initializeTimedOut: false
|
|
591
|
+
return { code: 0, initializeTimedOut: false };
|
|
613
592
|
const detail = completed.turn?.error?.message ?? `turn status ${completed.turn?.status ?? "unknown"}`;
|
|
614
593
|
process.stderr.write(sanitizeCodexDiagnostic(`Codex turn failed: ${detail.slice(0, ERROR_MESSAGE_CAP)}\n`, secrets));
|
|
615
|
-
const status = completed.turn?.status;
|
|
616
|
-
const retryReason = cancelling ? undefined
|
|
617
|
-
: status === "interrupted" ? "collab_interrupted"
|
|
618
|
-
: status === "failed" && isTransientTurnFailure(detail) ? "transient_turn_failure"
|
|
619
|
-
: undefined;
|
|
620
594
|
return {
|
|
621
|
-
code: status === "interrupted" ? 130 : 1,
|
|
595
|
+
code: completed.turn?.status === "interrupted" ? 130 : 1,
|
|
622
596
|
initializeTimedOut: false,
|
|
623
|
-
|
|
624
|
-
|
|
597
|
+
transientTurnFailure: !cancelling
|
|
598
|
+
&& completed.turn?.status === "failed"
|
|
599
|
+
&& isTransientTurnFailure(detail),
|
|
625
600
|
};
|
|
626
601
|
}
|
|
627
602
|
catch (error) {
|
|
@@ -634,7 +609,7 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
|
|
|
634
609
|
logStage(activeStage, "error", attempt, stageStartedAt);
|
|
635
610
|
}
|
|
636
611
|
process.stderr.write(`Codex app-server execution failed: ${safeErrorMessage(error, secrets)}\n`);
|
|
637
|
-
return { code: 1, initializeTimedOut
|
|
612
|
+
return { code: 1, initializeTimedOut };
|
|
638
613
|
}
|
|
639
614
|
finally {
|
|
640
615
|
firstProgress.stop();
|
|
@@ -666,20 +641,13 @@ export async function runCodexAppServer(bin, options = {}) {
|
|
|
666
641
|
process.stderr.write(`[codex-app-server] stage=retry status=start attempt=${attempt + 1} elapsed_ms=0 reason=initialize_timeout\n`);
|
|
667
642
|
continue;
|
|
668
643
|
}
|
|
669
|
-
|
|
670
|
-
if (decision.kind === "retry") {
|
|
644
|
+
if (result.transientTurnFailure && turnRetries < MAX_TRANSIENT_TURN_RETRIES) {
|
|
671
645
|
const delayMs = turnRetryDelayMs * TRANSIENT_TURN_RETRY_BACKOFF_FACTOR ** turnRetries;
|
|
672
646
|
turnRetries += 1;
|
|
673
|
-
process.stderr.write(`[codex-app-server] stage=retry status=start attempt=${attempt + 1} elapsed_ms=0 reason
|
|
647
|
+
process.stderr.write(`[codex-app-server] stage=retry status=start attempt=${attempt + 1} elapsed_ms=0 reason=transient_turn_failure\n`);
|
|
674
648
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
675
649
|
continue;
|
|
676
650
|
}
|
|
677
|
-
if (decision.kind === "skipped") {
|
|
678
|
-
process.stderr.write(`[codex-app-server] stage=retry status=skipped attempt=${attempt} reason=${decision.reason}_unsafe\n`);
|
|
679
|
-
}
|
|
680
|
-
else if (decision.kind === "exhausted") {
|
|
681
|
-
process.stderr.write(`[codex-app-server] stage=retry status=exhausted attempt=${attempt} reason=${decision.reason}\n`);
|
|
682
|
-
}
|
|
683
651
|
return result.code;
|
|
684
652
|
}
|
|
685
653
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CODEX_NATIVE_WORKSPACE_MIN_VERSION } from "./codex.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
export const CODEX_PROJECT_WORKSPACE_UNSUPPORTED = "codex_project_workspace_unsupported";
|
|
4
|
-
export const CODEX_COLLABORATION_UNSUPPORTED = "codex_collaboration_unsupported";
|
|
5
4
|
export const CODEX_PLAN_ENTRY_LIMIT = 16;
|
|
6
5
|
// 16 steps remain inside the server's 4,000 UTF-16-unit plan payload cap even
|
|
7
6
|
// when every retained code point is a surrogate pair (for example, emoji).
|
|
@@ -52,32 +51,18 @@ const versionTuple = (version) => {
|
|
|
52
51
|
const versionAtLeast = (actual, minimum) => actual[0] > minimum[0]
|
|
53
52
|
|| (actual[0] === minimum[0] && (actual[1] > minimum[1]
|
|
54
53
|
|| (actual[1] === minimum[1] && actual[2] >= minimum[2])));
|
|
55
|
-
|
|
54
|
+
/** Native roots require the app-server version returned by initialize, not a separate shell probe. */
|
|
55
|
+
export function assertCodexNativeWorkspaceVersion(response) {
|
|
56
56
|
const parsed = NativeInitializeResponseSchema.safeParse(response);
|
|
57
57
|
if (!parsed.success)
|
|
58
|
-
|
|
58
|
+
throw codexProjectWorkspaceUnsupported();
|
|
59
59
|
const match = /(?:^|[/\s])(\d+\.\d+\.\d+)(?=$|[\s(])/u.exec(parsed.data.userAgent);
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
/** Native roots require the app-server version returned by initialize, not a separate shell probe. */
|
|
63
|
-
export function assertCodexNativeWorkspaceVersion(response) {
|
|
64
|
-
const actual = initializeVersion(response);
|
|
60
|
+
const actual = match === null ? null : versionTuple(match[1] ?? "");
|
|
65
61
|
const minimum = versionTuple(CODEX_NATIVE_WORKSPACE_MIN_VERSION);
|
|
66
62
|
if (actual === null || minimum === null || !versionAtLeast(actual, minimum)) {
|
|
67
63
|
throw codexProjectWorkspaceUnsupported();
|
|
68
64
|
}
|
|
69
65
|
}
|
|
70
|
-
/** Collaboration tools are exposed only on an app-server whose contract was verified (spec 2b). */
|
|
71
|
-
export function assertCodexCollaborationVersion(response) {
|
|
72
|
-
const actual = initializeVersion(response);
|
|
73
|
-
const minimum = versionTuple(CODEX_COLLABORATION_MIN_VERSION);
|
|
74
|
-
if (actual === null || minimum === null || !versionAtLeast(actual, minimum)) {
|
|
75
|
-
throw codexCollaborationUnsupported();
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
export function codexCollaborationUnsupported() {
|
|
79
|
-
return new Error(CODEX_COLLABORATION_UNSUPPORTED);
|
|
80
|
-
}
|
|
81
66
|
const PLAN_STATUSES = new Set(["pending", "inProgress", "completed"]);
|
|
82
67
|
const clipCharacters = (value, limit) => [...value.trim().replace(/\s+/gu, " ")].slice(0, limit).join("");
|
|
83
68
|
/** Treat plan updates as bounded user-visible data; any malformed retained step rejects the event. */
|
|
@@ -7,10 +7,20 @@ export const DEEPSEEK_CODEX_BASE_URL = "https://api.deepseek.com/";
|
|
|
7
7
|
export const DEEPSEEK_CODEX_KEY_ENV = "NOWCREW_DEEPSEEK_API_KEY";
|
|
8
8
|
export const DEEPSEEK_CODEX_REASONING_LEVELS = ["low", "high", "max"];
|
|
9
9
|
const PRIVATE_CODEX_HOME = ".codex-deepseek";
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const SELF_TRAINING_PRIVATE_CODEX_HOME = ".codex-deepseek-self-training";
|
|
11
|
+
export function deepSeekCodexHome(homeDir, selfTraining = false) {
|
|
12
|
+
return resolve(homeDir, selfTraining ? SELF_TRAINING_PRIVATE_CODEX_HOME : PRIVATE_CODEX_HOME);
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
+
function normalizedBaseUrl(value) {
|
|
15
|
+
const url = new URL(value);
|
|
16
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
17
|
+
throw new Error("DeepSeek provider base URL must use HTTP(S)");
|
|
18
|
+
}
|
|
19
|
+
if (!url.pathname.endsWith("/"))
|
|
20
|
+
url.pathname = `${url.pathname}/`;
|
|
21
|
+
return url.toString();
|
|
22
|
+
}
|
|
23
|
+
export function renderDeepSeekConfig(codexHome, baseUrl = DEEPSEEK_CODEX_BASE_URL) {
|
|
14
24
|
const catalogPath = join(codexHome, "models.json");
|
|
15
25
|
return [
|
|
16
26
|
`model = ${JSON.stringify(DEEPSEEK_CODEX_MODEL)}`,
|
|
@@ -20,7 +30,7 @@ export function renderDeepSeekConfig(codexHome) {
|
|
|
20
30
|
"",
|
|
21
31
|
"[model_providers.deepseek]",
|
|
22
32
|
'name = "deepseek"',
|
|
23
|
-
`base_url = ${JSON.stringify(
|
|
33
|
+
`base_url = ${JSON.stringify(normalizedBaseUrl(baseUrl))}`,
|
|
24
34
|
'wire_api = "responses"',
|
|
25
35
|
`env_key = ${JSON.stringify(DEEPSEEK_CODEX_KEY_ENV)}`,
|
|
26
36
|
"",
|
|
@@ -40,11 +50,11 @@ async function writePrivateAtomic(path, content) {
|
|
|
40
50
|
await rm(temporary, { force: true });
|
|
41
51
|
}
|
|
42
52
|
}
|
|
43
|
-
export async function materializeDeepSeekCodexHome(homeDir) {
|
|
44
|
-
const codexHome = deepSeekCodexHome(homeDir);
|
|
53
|
+
export async function materializeDeepSeekCodexHome(homeDir, options = {}) {
|
|
54
|
+
const codexHome = deepSeekCodexHome(homeDir, options.selfTraining === true);
|
|
45
55
|
await mkdir(codexHome, { recursive: true, mode: 0o700 });
|
|
46
56
|
await chmod(codexHome, 0o700);
|
|
47
57
|
await writePrivateAtomic(join(codexHome, "models.json"), DEEPSEEK_CODEX_CATALOG);
|
|
48
|
-
await writePrivateAtomic(join(codexHome, "config.toml"), renderDeepSeekConfig(codexHome));
|
|
58
|
+
await writePrivateAtomic(join(codexHome, "config.toml"), renderDeepSeekConfig(codexHome, options.baseUrl));
|
|
49
59
|
return codexHome;
|
|
50
60
|
}
|
package/dist/runtimes/codex.js
CHANGED
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
import spawn from "cross-spawn";
|
|
6
6
|
/** Minimum CLI whose real app-server contract supports NowCrew native project workspaces. */
|
|
7
7
|
export const CODEX_NATIVE_WORKSPACE_MIN_VERSION = "0.148.0";
|
|
8
|
-
/** Minimum CLI whose `multi_agent` collaboration tool contract was empirically verified (2026-08-28). */
|
|
9
|
-
export const CODEX_COLLABORATION_MIN_VERSION = "0.149.1";
|
|
10
8
|
// Codex CLI 原生 model_reasoning_effort 档位(codex 0.135.0 实测:非法值时 config 解析报错枚举这六档)。
|
|
11
9
|
// 注意:codex 对非法值是硬失败(进程直接退出),所以必须白名单过滤;白名单外(含 "default"、
|
|
12
10
|
// claude 专属的 "max")回落 CODEX_DEFAULT_EFFORT。
|
|
@@ -24,8 +22,6 @@ export function buildCodexArgs(input) {
|
|
|
24
22
|
? input.reasoning
|
|
25
23
|
: CODEX_DEFAULT_EFFORT;
|
|
26
24
|
args.push("-c", `model_reasoning_effort=${effort}`);
|
|
27
|
-
// 纵深防御:exec 启动器没有 initialize 握手、无法断言版本,协作工具恒关(spec 2b)。
|
|
28
|
-
args.push("-c", "features.multi_agent=false", "-c", "features.multi_agent_v2=false");
|
|
29
25
|
if (input.projectRootMarkers) {
|
|
30
26
|
args.push("-c", `project_root_markers=${JSON.stringify(input.projectRootMarkers)}`);
|
|
31
27
|
}
|
|
@@ -36,6 +32,9 @@ export function buildCodexArgs(input) {
|
|
|
36
32
|
else if (input.effectivePermission === "full_access" || (input.effectivePermission === undefined && input.dangerous)) {
|
|
37
33
|
args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
38
34
|
}
|
|
35
|
+
if (input.effectivePermission === "workspace_write" && input.networkAccess === true) {
|
|
36
|
+
args.push("-c", "sandbox_workspace_write.network_access=true");
|
|
37
|
+
}
|
|
39
38
|
for (const imagePath of input.imagePaths ?? [])
|
|
40
39
|
args.push("--image", imagePath);
|
|
41
40
|
// `-` instructs codex exec to read the prompt from stdin. Keeping the complete prompt out of argv
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, relative, sep } from "node:path";
|
|
3
|
+
/** Delete only a concrete child of this Agent's tasks root; never follow an ambiguous target. */
|
|
4
|
+
export async function cleanupSelfTrainingRunDir(tasksRoot, runDir) {
|
|
5
|
+
const child = relative(tasksRoot, runDir);
|
|
6
|
+
if (child === "" || isAbsolute(child) || child === ".." || child.startsWith(`..${sep}`))
|
|
7
|
+
return;
|
|
8
|
+
try {
|
|
9
|
+
await rm(runDir, { recursive: true, force: true });
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
13
|
+
process.stderr.write(`[execution] failed to remove self-training run dir ${runDir}: ${detail}\n`);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/dist/serve.js
CHANGED
|
@@ -51,7 +51,6 @@ import { handleRuntimeProbeFrame, probeRuntimeHealth } from "./runtime-health.js
|
|
|
51
51
|
import { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
52
52
|
import { createServeMigrationController, handleMigrationControlMessage } from "./daemon-migration-wiring.js";
|
|
53
53
|
import { createCodexHomeModeController } from "./codex-home-mode-controller.js";
|
|
54
|
-
import { attachStorageMigrationRuntime, createServeStorageStatusDelivery, handleStorageMigrationControl, replayStorageMigrationStatus, storageMigrationCapabilities, } from "./storage-serve-adapter.js";
|
|
55
54
|
export { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
56
55
|
// normalize.ts 的活动种类 → activity 枚举
|
|
57
56
|
const ACTIVITY_MAP = {
|
|
@@ -123,7 +122,6 @@ export function serve(config, opts = {}) {
|
|
|
123
122
|
catch { /* reconnect/timeout reconciliation handles a lost status frame */ }
|
|
124
123
|
},
|
|
125
124
|
});
|
|
126
|
-
let storageMigrationAdmissionClosed = () => false;
|
|
127
125
|
const migrationController = createServeMigrationController({
|
|
128
126
|
config,
|
|
129
127
|
...(opts.profileName === undefined ? {} : { profileName: opts.profileName }),
|
|
@@ -144,7 +142,6 @@ export function serve(config, opts = {}) {
|
|
|
144
142
|
const agentAbilityRuntime = createAgentAbilityRuntime(config.agentsRoot, {
|
|
145
143
|
...opts.agentAbility,
|
|
146
144
|
coordinator: projectionCoordinator,
|
|
147
|
-
isAdmissionClosed: () => storageMigrationAdmissionClosed(),
|
|
148
145
|
});
|
|
149
146
|
let projectSkillsController;
|
|
150
147
|
const projectSkillsReconciler = opts.projectSkills?.reconciler ?? createProjectSkillsReconciler({
|
|
@@ -164,7 +161,6 @@ export function serve(config, opts = {}) {
|
|
|
164
161
|
}
|
|
165
162
|
catch { /* 下一次 ready 或项目操作会重新发送完整快照 */ }
|
|
166
163
|
},
|
|
167
|
-
isAdmissionClosed: () => storageMigrationAdmissionClosed(),
|
|
168
164
|
reconcile: (handle, bindings) => projectSkillsReconciler.reconcile(handle, bindings),
|
|
169
165
|
ensureSnapshot: (handle, snapshot, options) => projectSkillsReconciler.ensureSnapshot(handle, snapshot, options),
|
|
170
166
|
});
|
|
@@ -179,37 +175,13 @@ export function serve(config, opts = {}) {
|
|
|
179
175
|
});
|
|
180
176
|
const machineHelloPublisher = createProjectSkillMachineHelloPublisher({
|
|
181
177
|
status: projectSkillControllerReadiness.status,
|
|
182
|
-
effective:
|
|
183
|
-
const effective = withProjectSkillCapabilityStatus(hello, status);
|
|
184
|
-
const configuredIdentity = opts.storageMigration?.identity;
|
|
185
|
-
const currentIdentity = typeof configuredIdentity === "function"
|
|
186
|
-
? configuredIdentity()
|
|
187
|
-
: configuredIdentity;
|
|
188
|
-
return currentIdentity === undefined ? effective : { ...effective, ...currentIdentity };
|
|
189
|
-
},
|
|
178
|
+
effective: withProjectSkillCapabilityStatus,
|
|
190
179
|
onSent: (hello) => log(`📤 已上报机器信息: ${hello.hostname} · ${hello.os} · installed=[${hello.runtimes.join(",")}] · executable=[${hello.executionRuntimes.join(",")}] · agents=[${hello.agentHandles.join(",")}]`),
|
|
191
180
|
});
|
|
192
181
|
const initializedProjectSkills = initializedProjectSkillsReconciler(projectSkillControllerReadiness.ensure, projectSkillsReconciler);
|
|
193
|
-
const configuredCapabilities = opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform);
|
|
194
|
-
const storageCapabilities = storageMigrationCapabilities(configuredCapabilities, opts.storageMigration);
|
|
195
|
-
const storageStatusDelivery = createServeStorageStatusDelivery(opts.storageMigration, {
|
|
196
|
-
onPersistenceError: (error) => dslog("daemon.storage_status_outbox_failed", "存储迁移状态 outbox 持久化失败", { level: "ERROR", error_message: error.message }),
|
|
197
|
-
transmit: (frame) => { try {
|
|
198
|
-
if (ws?.readyState === WebSocket.OPEN)
|
|
199
|
-
ws.send(JSON.stringify(frame));
|
|
200
|
-
}
|
|
201
|
-
catch { /* retry */ } },
|
|
202
|
-
});
|
|
203
|
-
const storageMigrationRuntime = attachStorageMigrationRuntime(opts.storageMigration, {
|
|
204
|
-
isBusy: () => executionRuns.size > 0 || legacyRuns.size > 0,
|
|
205
|
-
waitForExecutionFrames: () => executionFrameQueue,
|
|
206
|
-
flushLocalState: flushSlog,
|
|
207
|
-
sendStatus: storageStatusDelivery.send,
|
|
208
|
-
});
|
|
209
|
-
storageMigrationAdmissionClosed = storageMigrationRuntime.isAdmissionClosed;
|
|
210
182
|
const prepareProjectSkillCapabilities = createProjectSkillConnectionCapabilityProvider({
|
|
211
183
|
platform: process.platform,
|
|
212
|
-
baseCapabilities:
|
|
184
|
+
baseCapabilities: opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform),
|
|
213
185
|
additionalCapabilities: () => managedDaemonCapabilities(updateEligibility),
|
|
214
186
|
probeRuntimeRootStorePrimitive: projectSkillRuntimeRootStartup.probeNativePublicationPrimitiveReadiness,
|
|
215
187
|
initializeController: projectSkillControllerReadiness.ensure,
|
|
@@ -338,12 +310,6 @@ export function serve(config, opts = {}) {
|
|
|
338
310
|
// 连上了才有机会把离线期间(断连原因/退出前)落盘的日志补传上去
|
|
339
311
|
void drainSpool();
|
|
340
312
|
// 上报本机信息 (hostname/os/daemon 版本/已装 runtimes)
|
|
341
|
-
storageStatusDelivery.replayPending();
|
|
342
|
-
const storageReplay = replayStorageMigrationStatus(opts.storageMigration, openedSocket, () => ws === openedSocket, (error) => {
|
|
343
|
-
dslog("daemon.storage_status_replay_failed", "存储迁移状态重放失败", {
|
|
344
|
-
level: "ERROR", error_message: error.message,
|
|
345
|
-
});
|
|
346
|
-
}, storageStatusDelivery.send);
|
|
347
313
|
const { detectInstalled, detectExecutable = detectExecutionRuntimesWithSignal } = opts.machineInfo ?? {};
|
|
348
314
|
const helloPromise = (opts.machineInfo?.collect ?? collectMachineHello)(config.agentsRoot, config.executionLimits, process.platform, {
|
|
349
315
|
...(detectInstalled ? { detectInstalled } : {}),
|
|
@@ -359,7 +325,6 @@ export function serve(config, opts = {}) {
|
|
|
359
325
|
if (hello.executionProtocol === undefined)
|
|
360
326
|
return [];
|
|
361
327
|
const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable);
|
|
362
|
-
await storageReplay;
|
|
363
328
|
if (ws === openedSocket && openedSocket.readyState === WebSocket.OPEN
|
|
364
329
|
&& detected.some((runtime) => !hello.executionRuntimes.includes(runtime))) {
|
|
365
330
|
detectedExecutionRuntimes = detected;
|
|
@@ -374,8 +339,7 @@ export function serve(config, opts = {}) {
|
|
|
374
339
|
return [];
|
|
375
340
|
});
|
|
376
341
|
void helloPromise
|
|
377
|
-
.then(
|
|
378
|
-
await storageReplay;
|
|
342
|
+
.then((hello) => {
|
|
379
343
|
if (ws !== openedSocket || openedSocket.readyState !== WebSocket.OPEN)
|
|
380
344
|
return;
|
|
381
345
|
detectedExecutionRuntimes = hello.executionRuntimes;
|
|
@@ -423,8 +387,6 @@ export function serve(config, opts = {}) {
|
|
|
423
387
|
if (typeof decoded !== "object" || decoded === null)
|
|
424
388
|
return;
|
|
425
389
|
const rawType = "type" in decoded && typeof decoded.type === "string" ? decoded.type : "";
|
|
426
|
-
if (storageStatusDelivery.acknowledge(decoded))
|
|
427
|
-
return;
|
|
428
390
|
if (rawType === "daemon:update" || rawType === "daemon:restart") {
|
|
429
391
|
void updateController.handle(decoded).catch((error) => {
|
|
430
392
|
dslog("daemon.control_failed", "daemon 控制操作处理失败", {
|
|
@@ -441,11 +403,6 @@ export function serve(config, opts = {}) {
|
|
|
441
403
|
void codexHomeModeController.handle(decoded).catch((error) => dslog("daemon.codex_home_mode_failed", "Codex Home 模式切换失败", { level: "ERROR", error_message: error.message }));
|
|
442
404
|
return;
|
|
443
405
|
}
|
|
444
|
-
if (handleStorageMigrationControl(opts.storageMigration, rawType, decoded, storageStatusDelivery.send, (kind, error) => {
|
|
445
|
-
dslog(kind === "migration" ? "daemon.storage_migration_failed" : "daemon.storage_migration_confirmation_failed", kind === "migration" ? "存储迁移控制操作失败" : "存储迁移 hello 确认处理失败", { level: "ERROR", error_message: error.message });
|
|
446
|
-
})) {
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
406
|
if (rawType.startsWith("execution:")) {
|
|
450
407
|
const parsedExecution = ServerToDaemonExecutionFrameSchema.safeParse(decoded);
|
|
451
408
|
if (!parsedExecution.success) {
|
|
@@ -507,14 +464,6 @@ export function serve(config, opts = {}) {
|
|
|
507
464
|
return;
|
|
508
465
|
}
|
|
509
466
|
const spec = frame;
|
|
510
|
-
if (storageMigrationRuntime.isAdmissionClosed()) {
|
|
511
|
-
safeExecutionSend(ExecutionRejectedSchema.parse({
|
|
512
|
-
type: "execution:rejected", protocolVersion: 1, executionId: spec.executionId,
|
|
513
|
-
reason: "resource_limit", message: "Storage migration is quiescing this machine",
|
|
514
|
-
at: new Date().toISOString(),
|
|
515
|
-
}));
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
467
|
const workspaceRejection = projectWorkspaceCapabilityRejection(spec, capabilities.executionRunner, new Date().toISOString());
|
|
519
468
|
if (workspaceRejection !== null)
|
|
520
469
|
return void safeExecutionSend(workspaceRejection);
|
|
@@ -821,17 +770,6 @@ export function serve(config, opts = {}) {
|
|
|
821
770
|
// ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
|
|
822
771
|
const r = msg;
|
|
823
772
|
setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
|
|
824
|
-
if (opts.storageMigration?.ready === true && typeof r.workspaceId === "string") {
|
|
825
|
-
try {
|
|
826
|
-
opts.storageMigration.bindServerIdentity?.({ workspaceId: r.workspaceId });
|
|
827
|
-
machineHelloPublisher.republish();
|
|
828
|
-
}
|
|
829
|
-
catch (error) {
|
|
830
|
-
dslog("daemon.storage_identity_bind_failed", "存储迁移 Workspace 身份绑定失败", {
|
|
831
|
-
level: "ERROR", error_message: error.message,
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
773
|
// 旧 server 无此字段 → 空集合(daemon 保持全部旧行为)。
|
|
836
774
|
serverCapabilities = new Set(Array.isArray(r.serverCapabilities)
|
|
837
775
|
? r.serverCapabilities.filter((c) => typeof c === "string")
|
|
@@ -930,8 +868,6 @@ export function serve(config, opts = {}) {
|
|
|
930
868
|
}
|
|
931
869
|
if (msg.type !== "agent:start")
|
|
932
870
|
return; // ready/error 等忽略
|
|
933
|
-
if (storageMigrationRuntime.isAdmissionClosed())
|
|
934
|
-
return;
|
|
935
871
|
// 任务键:scheduled run 用 runId(每次运行独立 cwd/work-log;overlap 由 server 端 skip_if_running 管,
|
|
936
872
|
// daemon 的 running 去重只兜底"同一 run 重复投递");普通唤醒仍是 线程锚点 ?? 频道。
|
|
937
873
|
const scheduled = msg.reason === "scheduled_job" && msg.scheduledRun
|
|
@@ -1347,7 +1283,6 @@ export function serve(config, opts = {}) {
|
|
|
1347
1283
|
concurrencyTelemetry.stop();
|
|
1348
1284
|
runtimeProbe.stop();
|
|
1349
1285
|
completionRetransmitter.stop();
|
|
1350
|
-
storageStatusDelivery.stop();
|
|
1351
1286
|
const deadline = createShutdownDeadline(shutdownTimeoutMs);
|
|
1352
1287
|
if (reconnectTimer !== null) {
|
|
1353
1288
|
clearTimeout(reconnectTimer);
|
|
@@ -46,6 +46,7 @@ export function supervisorLaunch(request) {
|
|
|
46
46
|
effectivePermission: request.effectivePermission,
|
|
47
47
|
...(request.model === undefined ? {} : { model: request.model }),
|
|
48
48
|
...(request.reasoning === undefined ? {} : { reasoning: request.reasoning }),
|
|
49
|
+
...(request.networkAccess === undefined ? {} : { networkAccess: request.networkAccess }),
|
|
49
50
|
...(request.sessionId === undefined ? {} : { sessionId: request.sessionId }),
|
|
50
51
|
...(request.imagePaths === undefined ? {} : { imagePaths: request.imagePaths }),
|
|
51
52
|
...(request.agentRoot === undefined ? {} : {
|
|
@@ -58,9 +59,6 @@ export function supervisorLaunch(request) {
|
|
|
58
59
|
...(request.codexSkillRoots === undefined ? {} : {
|
|
59
60
|
skillRoots: request.codexSkillRoots,
|
|
60
61
|
}),
|
|
61
|
-
...(request.codexCollaborationTools === undefined ? {} : {
|
|
62
|
-
codexCollaborationTools: request.codexCollaborationTools,
|
|
63
|
-
}),
|
|
64
62
|
resume: request.resume,
|
|
65
63
|
}),
|
|
66
64
|
};
|