@nowcrew/daemon 0.6.88 → 0.6.90

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.
Files changed (122) hide show
  1. package/README.md +66 -4
  2. package/dist/agent-ability/controller.js +11 -1
  3. package/dist/agent-ability/git-errors.js +22 -0
  4. package/dist/agent-ability/materializer.js +21 -10
  5. package/dist/agent-ability/resolver.js +12 -4
  6. package/dist/agent-environment-contract.js +55 -0
  7. package/dist/agent-environment-control.js +24 -0
  8. package/dist/agent-environment-execution.js +98 -0
  9. package/dist/agent-environments.js +275 -0
  10. package/dist/automatic-daemon-installer.js +51 -2
  11. package/dist/automatic-install-target.js +8 -0
  12. package/dist/computer-main.js +18 -0
  13. package/dist/computer-profile.js +17 -4
  14. package/dist/computer-setup-client.js +318 -0
  15. package/dist/computer-setup-orchestration.js +203 -0
  16. package/dist/computer-setup.js +244 -0
  17. package/dist/config.js +30 -12
  18. package/dist/console-formatter.js +3 -3
  19. package/dist/console.js +15 -2
  20. package/dist/daemon-update-eligibility.js +1 -1
  21. package/dist/execution-journal.js +9 -6
  22. package/dist/execution-protocol.js +1 -0
  23. package/dist/execution-runner.js +13 -8
  24. package/dist/i18n.js +19 -0
  25. package/dist/list-models.js +12 -12
  26. package/dist/local-execution-lease.js +22 -0
  27. package/dist/local-executor.js +57 -84
  28. package/dist/local-memory/candidate.js +43 -0
  29. package/dist/local-memory/index-budget.js +8 -0
  30. package/dist/local-memory/inventory-cli.js +61 -0
  31. package/dist/local-memory/inventory-preview.js +159 -0
  32. package/dist/local-memory/inventory-source.js +159 -0
  33. package/dist/local-memory/pending-match.js +29 -0
  34. package/dist/local-memory/pending-query.js +100 -0
  35. package/dist/local-memory/pending-schema.js +29 -0
  36. package/dist/local-memory/pending-source.js +126 -0
  37. package/dist/local-memory/pending.js +34 -84
  38. package/dist/local-memory/prompt.js +19 -12
  39. package/dist/local-memory/read-cli.js +97 -12
  40. package/dist/local-memory/rollout.js +82 -0
  41. package/dist/local-memory/runtime.js +23 -17
  42. package/dist/local-memory/work-log-execution.js +31 -0
  43. package/dist/local-memory/work-log.js +58 -0
  44. package/dist/local-memory/write.js +27 -58
  45. package/dist/local-runtime-environment.js +50 -0
  46. package/dist/machine-environment-cache.js +147 -0
  47. package/dist/machine-environment-contract.js +10 -0
  48. package/dist/machine-environment-control.js +27 -0
  49. package/dist/machine-environment-policy.js +75 -0
  50. package/dist/machine-environment-process.js +110 -0
  51. package/dist/machine-info.js +70 -25
  52. package/dist/machine-runtime-environment.js +189 -0
  53. package/dist/machine-shell-environment-capture.js +135 -0
  54. package/dist/machine-windows-environment-capture.js +215 -0
  55. package/dist/machine-windows-environment-script.js +158 -0
  56. package/dist/main.js +4 -69
  57. package/dist/marketplace-command.js +21 -0
  58. package/dist/marketplace-controller.js +80 -0
  59. package/dist/marketplace-protocol.js +38 -0
  60. package/dist/marketplace-remote.js +48 -0
  61. package/dist/marketplace-runner.js +235 -0
  62. package/dist/marketplace-serve.js +17 -0
  63. package/dist/marketplace-source.js +103 -0
  64. package/dist/message-file-preview.js +246 -0
  65. package/dist/pre-admission-rejection.js +64 -0
  66. package/dist/project-skills/execution-adapter.js +3 -2
  67. package/dist/project-skills/runtime-launch.js +14 -0
  68. package/dist/provider-env.js +26 -15
  69. package/dist/runner.js +17 -4
  70. package/dist/runtime-environment-merge.js +16 -0
  71. package/dist/runtime-health-handler.js +10 -0
  72. package/dist/runtime-health.js +3 -1
  73. package/dist/runtime-path.js +22 -12
  74. package/dist/runtime-probe.js +35 -6
  75. package/dist/runtimes/acp-probe-transport.js +69 -0
  76. package/dist/runtimes/claude-models.js +20 -9
  77. package/dist/runtimes/deepseek-harness-launch.js +10 -0
  78. package/dist/runtimes/deepseek-harness-profile.js +92 -0
  79. package/dist/runtimes/deepseek-harness-runner.js +226 -0
  80. package/dist/runtimes/deepseek-harness.js +76 -15
  81. package/dist/runtimes/harness-probe-retry.js +42 -0
  82. package/dist/runtimes/hermes-models.js +2 -1
  83. package/dist/runtimes/kimi-acp-runner.js +78 -13
  84. package/dist/runtimes/opencode.js +2 -2
  85. package/dist/serve.js +88 -176
  86. package/dist/server-url.js +25 -0
  87. package/dist/slog.js +2 -1
  88. package/dist/storage-identity.js +0 -3
  89. package/dist/storage-runtime.js +2 -2
  90. package/dist/storage-serve-adapter.js +6 -72
  91. package/dist/supervised-runtime.js +17 -2
  92. package/dist/token.js +0 -1
  93. package/dist/workspace-control-handler.js +27 -0
  94. package/dist/workspace-fs.js +66 -15
  95. package/dist/workspace.js +1 -1
  96. package/package.json +6 -4
  97. package/dist/codex-home-launch.js +0 -7
  98. package/dist/codex-home-mode-controller.js +0 -68
  99. package/dist/daemon-migration-controller.js +0 -211
  100. package/dist/daemon-migration-wiring.js +0 -22
  101. package/dist/runtimes/codex-home-migration-cli.js +0 -26
  102. package/dist/runtimes/codex-home-migration.js +0 -112
  103. package/dist/runtimes/codex-home.js +0 -236
  104. package/dist/storage-control.js +0 -212
  105. package/dist/storage-copy.js +0 -628
  106. package/dist/storage-credential-guard.js +0 -21
  107. package/dist/storage-doctor.js +0 -228
  108. package/dist/storage-manifest.js +0 -1027
  109. package/dist/storage-migration-controller.js +0 -440
  110. package/dist/storage-migration-manifest.js +0 -105
  111. package/dist/storage-migration-policy.js +0 -100
  112. package/dist/storage-runtime-controller.js +0 -381
  113. package/dist/storage-runtime-readiness.js +0 -240
  114. package/dist/storage-safe-fs.js +0 -302
  115. package/dist/storage-status-delivery.js +0 -224
  116. package/dist/storage-status-replay.js +0 -140
  117. package/dist/storage-target-confirmation.js +0 -129
  118. package/dist/storage-worker-command.js +0 -37
  119. package/dist/storage-worker-executor.js +0 -449
  120. package/dist/storage-worker-handoff.js +0 -151
  121. package/dist/storage-worker-operation.js +0 -294
  122. package/dist/storage-worker-status.js +0 -259
@@ -0,0 +1,244 @@
1
+ import { parseArgs } from "node:util";
2
+ import { z } from "zod";
3
+ import { DEFAULT_SERVER_URL } from "./config.js";
4
+ import { installDaemonAutomatically, } from "./automatic-daemon-installer.js";
5
+ import { createComputerSetupClient, openSetupBrowser, redactSetupSecrets, } from "./computer-setup-client.js";
6
+ import { runComputerSetupOrchestration, } from "./computer-setup-orchestration.js";
7
+ import { detectDaemonLang, formatDaemonText } from "./i18n.js";
8
+ import { systemCommandRunner } from "./computer-service.js";
9
+ const WorkspaceSlugSchema = z.string()
10
+ .min(2)
11
+ .max(40)
12
+ .regex(/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u);
13
+ const WorkspaceIdSchema = z.string().uuid();
14
+ const MachineIdSchema = z.string().uuid();
15
+ const ServerUrlSchema = z.string().url().refine((value) => {
16
+ const url = new URL(value);
17
+ return (url.protocol === "http:" || url.protocol === "https:")
18
+ && url.username.length === 0
19
+ && url.password.length === 0
20
+ && (url.pathname === "" || url.pathname === "/")
21
+ && url.search.length === 0
22
+ && url.hash.length === 0;
23
+ }, "server URL must be an HTTP(S) origin without embedded credentials");
24
+ function stringValue(values, key) {
25
+ const value = values[key];
26
+ return typeof value === "string" ? value : undefined;
27
+ }
28
+ function booleanValue(values, key) {
29
+ return values[key] === true;
30
+ }
31
+ function optionOccurrences(argv, name) {
32
+ const prefix = `--${name}`;
33
+ const values = [];
34
+ for (let index = 0; index < argv.length; index += 1) {
35
+ const value = argv[index];
36
+ if (value === prefix) {
37
+ const next = argv[index + 1];
38
+ if (next !== undefined && !next.startsWith("--"))
39
+ values.push(next);
40
+ index += 1;
41
+ continue;
42
+ }
43
+ if (value?.startsWith(`${prefix}=`))
44
+ values.push(value.slice(prefix.length + 1));
45
+ }
46
+ return values;
47
+ }
48
+ function assertRepeatedOptionValuesAgree(argv, name, normalize) {
49
+ const normalized = optionOccurrences(argv, name).map(normalize);
50
+ if (new Set(normalized).size > 1) {
51
+ throw new Error(`Repeated --${name} values conflict`);
52
+ }
53
+ }
54
+ function normalizedServerOrigin(value) {
55
+ const parsed = new URL(ServerUrlSchema.parse(value));
56
+ return `${parsed.protocol}//${parsed.host}`;
57
+ }
58
+ function workspaceFromSlug(value) {
59
+ if (!value.startsWith("/") || value.startsWith("//")) {
60
+ throw new Error("Workspace slug must use exactly one leading slash, for example /nowcoder-app");
61
+ }
62
+ return WorkspaceSlugSchema.parse(value.slice(1));
63
+ }
64
+ function workspaceFromAlias(value) {
65
+ return value.startsWith("/") ? workspaceFromSlug(value) : WorkspaceSlugSchema.parse(value);
66
+ }
67
+ export function parseSetupArgs(argv) {
68
+ assertRepeatedOptionValuesAgree(argv, "workspace", (value) => workspaceFromAlias(value));
69
+ assertRepeatedOptionValuesAgree(argv, "workspace-id", (value) => WorkspaceIdSchema.parse(value).toLowerCase());
70
+ assertRepeatedOptionValuesAgree(argv, "machine", (value) => MachineIdSchema.parse(value).toLowerCase());
71
+ assertRepeatedOptionValuesAgree(argv, "machine-id", (value) => MachineIdSchema.parse(value).toLowerCase());
72
+ assertRepeatedOptionValuesAgree(argv, "server-url", normalizedServerOrigin);
73
+ const parsed = parseArgs({
74
+ args: [...argv],
75
+ allowPositionals: true,
76
+ strict: true,
77
+ options: {
78
+ workspace: { type: "string" },
79
+ "workspace-id": { type: "string" },
80
+ machine: { type: "string" },
81
+ "machine-id": { type: "string" },
82
+ "server-url": { type: "string" },
83
+ json: { type: "boolean", default: false },
84
+ "no-browser": { type: "boolean", default: false },
85
+ help: { type: "boolean", short: "h", default: false },
86
+ },
87
+ });
88
+ const values = parsed.values;
89
+ const [command, ...positionals] = parsed.positionals;
90
+ if (command !== "setup")
91
+ throw new Error("Expected 'setup'");
92
+ if (booleanValue(values, "help"))
93
+ throw new Error(setupUsage());
94
+ if (positionals.length > 1)
95
+ throw new Error("Setup accepts one Workspace argument");
96
+ const workspaceAlias = stringValue(values, "workspace");
97
+ const workspaceIdAlias = stringValue(values, "workspace-id");
98
+ if (workspaceAlias !== undefined && workspaceIdAlias !== undefined) {
99
+ throw new Error("Choose one Workspace source: positional, --workspace, or --workspace-id");
100
+ }
101
+ const positionalWorkspace = positionals[0] === undefined ? undefined : workspaceFromSlug(positionals[0]);
102
+ const aliasWorkspace = workspaceAlias === undefined ? undefined : workspaceFromAlias(workspaceAlias);
103
+ if (positionalWorkspace !== undefined && workspaceIdAlias !== undefined) {
104
+ throw new Error("A positional Workspace slug cannot be combined with --workspace-id");
105
+ }
106
+ if (positionalWorkspace !== undefined && aliasWorkspace !== undefined && positionalWorkspace !== aliasWorkspace) {
107
+ throw new Error("Positional Workspace conflicts with --workspace");
108
+ }
109
+ const workspace = positionalWorkspace !== undefined
110
+ ? Object.freeze({ kind: "slug", value: positionalWorkspace })
111
+ : aliasWorkspace !== undefined
112
+ ? Object.freeze({ kind: "slug", value: aliasWorkspace })
113
+ : workspaceIdAlias === undefined
114
+ ? (() => { throw new Error("Missing Workspace; use /<slug> or --workspace-id <uuid>"); })()
115
+ : Object.freeze({ kind: "id", value: WorkspaceIdSchema.parse(workspaceIdAlias).toLowerCase() });
116
+ const machineAlias = stringValue(values, "machine");
117
+ const machineIdAlias = stringValue(values, "machine-id");
118
+ const parsedMachine = MachineIdSchema.parse(machineAlias ?? machineIdAlias ?? (() => {
119
+ throw new Error("Missing --machine <uuid>");
120
+ })()).toLowerCase();
121
+ if (machineAlias !== undefined && machineIdAlias !== undefined
122
+ && parsedMachine !== MachineIdSchema.parse(machineIdAlias).toLowerCase()) {
123
+ throw new Error("--machine conflicts with --machine-id");
124
+ }
125
+ const machineId = parsedMachine;
126
+ const serverUrl = stringValue(values, "server-url");
127
+ return Object.freeze({
128
+ command: "setup",
129
+ workspace,
130
+ machineId,
131
+ ...(serverUrl === undefined ? {} : { serverUrl: ServerUrlSchema.parse(serverUrl) }),
132
+ ...(booleanValue(values, "json") ? { json: true } : {}),
133
+ ...(booleanValue(values, "no-browser") ? { noBrowser: true } : {}),
134
+ });
135
+ }
136
+ function resolveSetupServerUrl(args, env) {
137
+ const configured = args.serverUrl ?? env.CREW_SERVER_URL?.trim();
138
+ if (configured !== undefined && configured.length > 0)
139
+ return normalizedServerOrigin(configured);
140
+ if (env.NODE_ENV === "development" || env.NODE_ENV === "test")
141
+ return DEFAULT_SERVER_URL;
142
+ throw new Error("server URL is required; pass --server-url or set CREW_SERVER_URL");
143
+ }
144
+ export function setupUsage(td = (message) => message) {
145
+ return [
146
+ td("Usage:"),
147
+ " nowwork-computer setup /workspace --machine <uuid>",
148
+ " nowwork-computer setup --workspace /workspace --machine <uuid>",
149
+ " nowwork-computer setup --workspace-id <uuid> --machine-id <uuid>",
150
+ "",
151
+ "Options: --server-url <url> --json --no-browser",
152
+ ].join("\n") + "\n";
153
+ }
154
+ function defaults() {
155
+ return {
156
+ env: process.env,
157
+ platform: process.platform,
158
+ fetch: globalThis.fetch,
159
+ stdout: (text) => process.stdout.write(text),
160
+ stderr: (text) => process.stderr.write(text),
161
+ createClient: (serverUrl) => createComputerSetupClient({ serverUrl }),
162
+ installAutomatically: installDaemonAutomatically,
163
+ openBrowser: (url) => openSetupBrowser(url, process.platform, systemCommandRunner),
164
+ sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
165
+ now: Date.now,
166
+ };
167
+ }
168
+ function humanSummary(result, td) {
169
+ return [
170
+ td("Workspace: {{value}}", { value: result.workspaceId }),
171
+ td("Machine: {{value}}", { value: result.machineId }),
172
+ td("Environment: {{value}}", { value: result.environment }),
173
+ td("Daemon: profile {{profile}} / v{{version}} / {{status}}", {
174
+ profile: result.profile, version: result.daemonVersion, status: result.serviceStatus,
175
+ }),
176
+ td("Local: {{value}}", { value: result.localNowworkHome }),
177
+ td("Config: {{value}}", { value: result.daemonHome }),
178
+ td("Agents: {{value}}", { value: result.agentsRoot }),
179
+ ...(result.storageId === undefined ? [] : [td("Storage ID: {{value}}", { value: result.storageId })]),
180
+ td(result.helloConfirmed ? "Status: connected and ready" : "Status: not ready"),
181
+ ].join("\n") + "\n";
182
+ }
183
+ function localizedPairingMessage(lang, message) {
184
+ if (message.startsWith("Open ")) {
185
+ return formatDaemonText(lang, "Open {{url}}", { url: message.slice("Open ".length) });
186
+ }
187
+ if (message.startsWith("Code ")) {
188
+ return formatDaemonText(lang, "Code {{code}}", { code: message.slice("Code ".length) });
189
+ }
190
+ if (message.startsWith("Browser open skipped: ")) {
191
+ return formatDaemonText(lang, "Browser open skipped: {{reason}}", {
192
+ reason: message.slice("Browser open skipped: ".length),
193
+ });
194
+ }
195
+ return formatDaemonText(lang, message);
196
+ }
197
+ export async function runComputerSetupCommand(argv, overrides = {}) {
198
+ if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) {
199
+ const deps = { ...defaults(), ...overrides };
200
+ const lang = detectDaemonLang(deps.env);
201
+ deps.stdout(setupUsage((message, values) => formatDaemonText(lang, message, values)));
202
+ return 0;
203
+ }
204
+ if (argv[0] !== "setup")
205
+ return null;
206
+ const deps = { ...defaults(), ...overrides };
207
+ const lang = detectDaemonLang(deps.env);
208
+ try {
209
+ if (argv.includes("--help") || argv.includes("-h")) {
210
+ deps.stdout(setupUsage((message, values) => formatDaemonText(lang, message, values)));
211
+ return 0;
212
+ }
213
+ let args;
214
+ try {
215
+ args = parseSetupArgs(argv);
216
+ }
217
+ catch (error) {
218
+ deps.stderr(`nowwork-computer: ${redactSetupSecrets(error.message)}\n`);
219
+ return 2;
220
+ }
221
+ if (args.json === true) {
222
+ deps.stderr(`${formatDaemonText(lang, "Starting computer setup pairing...")}\n`);
223
+ }
224
+ const serverUrl = resolveSetupServerUrl(args, deps.env);
225
+ const createClient = overrides.createClient
226
+ ?? ((value) => createComputerSetupClient({ serverUrl: value, fetch: deps.fetch }));
227
+ const result = await runComputerSetupOrchestration({ args, serverUrl }, {
228
+ client: createClient(serverUrl),
229
+ installAutomatically: deps.installAutomatically,
230
+ openBrowser: args.noBrowser === true ? async () => { } : deps.openBrowser,
231
+ sleep: deps.sleep,
232
+ reportPairing: (message) => deps[args.json === true ? "stderr" : "stdout"](`${localizedPairingMessage(lang, message)}\n`),
233
+ now: deps.now,
234
+ });
235
+ deps.stdout(args.json === true
236
+ ? `${JSON.stringify(result)}\n`
237
+ : humanSummary(result, (message, values) => formatDaemonText(lang, message, values)));
238
+ return 0;
239
+ }
240
+ catch (error) {
241
+ deps.stderr(`nowwork-computer: ${redactSetupSecrets(error.message)}\n`);
242
+ return 1;
243
+ }
244
+ }
package/dist/config.js CHANGED
@@ -76,12 +76,32 @@ function defaultCliPath() {
76
76
  return resolve(here, "../../cli/dist/main.js");
77
77
  }
78
78
  }
79
- // 临时 dev 验收名单。这里必须填写 Server 生成的 Agent handle,而不是显示名称或频道 ID。
80
- // 测试完成后删除此常量,恢复未配置时默认关闭的行为。
79
+ // 保留本地开发的原默认名单;远端 dev 全量不再依赖该集合。
81
80
  const TEMP_LOCAL_MEMORY_V2_TEST_HANDLES = [
82
- "94533bf3-b5b7-4a1e-9c41-6a1ab0a0e1ea", // memory-v2-test-a
83
- "967ab9ce-724c-4f22-a664-2f855cd798af", // memory-v2-test-b
81
+ "94533bf3-b5b7-4a1e-9c41-6a1ab0a0e1ea", // memory-v2-test-b
82
+ "967ab9ce-724c-4f22-a664-2f855cd798af", // memory-v2-test-a
84
83
  ];
84
+ export function localMemoryV2Environment(serverUrl) {
85
+ try {
86
+ const url = new URL(serverUrl);
87
+ if (url.protocol !== "http:" && url.protocol !== "https:")
88
+ return "custom";
89
+ const hostname = url.hostname.toLowerCase().replace(/\.$/u, "");
90
+ if (hostname === "nowwork-dev.nowcoder.com")
91
+ return "dev";
92
+ if (hostname === "nowwork-in.nowcoder.com" || hostname === "nowwork.nowcoder.com")
93
+ return "production";
94
+ }
95
+ catch { /* 其它地址保留原配置行为。 */ }
96
+ return "custom";
97
+ }
98
+ /** 环境资格不代表旧格式已兼容;实际格式由本轮工作区准备阶段核对。 */
99
+ export function isLocalMemoryV2Enabled(config, handle) {
100
+ const environment = localMemoryV2Environment(config.serverUrl);
101
+ if (environment !== "custom")
102
+ return environment === "dev";
103
+ return config.localMemoryV2Handles?.has(handle) === true;
104
+ }
85
105
  function isTemporaryDevServer(serverUrl) {
86
106
  try {
87
107
  const hostname = new URL(serverUrl).hostname.toLowerCase();
@@ -111,13 +131,11 @@ function localMemoryV2Handles(env, serverUrl) {
111
131
  return new Set(handles);
112
132
  }
113
133
  export const DEFAULT_SERVER_URL = "http://127.0.0.1:3001";
114
- function codexHomeModeEnv(env) {
115
- const value = env.CREW_CODEX_HOME_MODE;
116
- if (value === undefined || value === "shared")
117
- return "shared";
118
- if (value === "private")
119
- return "private";
120
- throw new ConfigError("CREW_CODEX_HOME_MODE must be shared or private");
134
+ function machineEnvironmentModeEnv(env) {
135
+ const value = env.NOWWORK_MACHINE_ENV_MODE ?? "auto";
136
+ if (value === "auto" || value === "zsh" || value === "bash" || value === "windows" || value === "off")
137
+ return value;
138
+ throw new ConfigError("NOWWORK_MACHINE_ENV_MODE must be auto, zsh, bash, windows, or off");
121
139
  }
122
140
  function codexCollaborationToolsEnv(env) {
123
141
  const value = (env.CREW_CODEX_COLLABORATION_TOOLS ?? "").trim().toLowerCase();
@@ -168,7 +186,7 @@ export function loadConfig(env = process.env) {
168
186
  : defaultQaReportRoot,
169
187
  cliPath: env.CREW_CLI_PATH ?? defaultCliPath(),
170
188
  runtimeBin: env.CREW_RUNTIME ?? "claude",
171
- codexHomeMode: codexHomeModeEnv(env),
189
+ machineEnvironmentMode: machineEnvironmentModeEnv(env),
172
190
  codexCollaborationTools: codexCollaborationToolsEnv(env),
173
191
  dangerous: env.CREW_RUNTIME_SAFE !== "1", // 默认开启 (headless agent 在自有 workspace 内运行)
174
192
  resume: env.CREW_RESUME !== "off" && env.CREW_RESUME !== "0", // 默认开启;一键回退现状用 CREW_RESUME=off
@@ -22,7 +22,7 @@ const clip = (text) => text.length > TOOL_RESULT_CAP
22
22
  ? `${text.slice(0, TOOL_RESULT_CAP)}… (+${text.length - TOOL_RESULT_CAP})`
23
23
  : text;
24
24
  const SCRIPT_TOOLS = new Set(["Bash", "PowerShell", "Shell"]);
25
- export function createConsoleFormatter() {
25
+ export function createConsoleFormatter(runtime) {
26
26
  const pending = new Map();
27
27
  // listId → 上一条 todos 快照的指纹;仅用于丢弃「连续两次完全相同」的重复事件(无损去重)。
28
28
  // 只比对相邻快照,不做跨快照合并——进度推进的中间态必须逐条下发,否则前端进度条会停在旧值。
@@ -86,7 +86,7 @@ export function createConsoleFormatter() {
86
86
  const filler = consumePendingThinking();
87
87
  if (filler) {
88
88
  const content = message.content.map((block) => block.type === "thinking" && !block.thinking?.trim() ? { ...block, thinking: filler } : block);
89
- return dedupeTodos(toConsoleLines({ ...rec, message: { ...message, content } }));
89
+ return dedupeTodos(toConsoleLines({ ...rec, message: { ...message, content } }, runtime));
90
90
  }
91
91
  }
92
92
  }
@@ -146,7 +146,7 @@ export function createConsoleFormatter() {
146
146
  }
147
147
  return out;
148
148
  }
149
- return dedupeTodos(toConsoleLines(event));
149
+ return dedupeTodos(toConsoleLines(event, runtime));
150
150
  },
151
151
  };
152
152
  }
package/dist/console.js CHANGED
@@ -263,7 +263,7 @@ function codexItemChunks(item, td) {
263
263
  return [];
264
264
  }
265
265
  /** 把一个 stream-json 事件转成 0..N 条 console 行(完全透传)。 */
266
- export function toConsoleLines(event) {
266
+ export function toConsoleLines(event, runtime) {
267
267
  const e = (event ?? {});
268
268
  const lang = detectDaemonLang();
269
269
  const td = (message) => translateDaemon(lang, message);
@@ -284,7 +284,20 @@ export function toConsoleLines(event) {
284
284
  return [{ stream: "system", text: `● ${td("Claude session started")}` }];
285
285
  }
286
286
  if (e.type === "thread.started") {
287
- return [{ stream: "system", text: `● ${td("Codex session started")}` }];
287
+ // thread.started is shared by Codex and ACP adapters; its shape does not
288
+ // identify the runtime. Use the executor-owned selection, not event fields.
289
+ const messages = {
290
+ claude: "Claude session started",
291
+ codex: "Codex session started",
292
+ kimi: "Kimi session started",
293
+ hermes: "Hermes session started",
294
+ opencode: "OpenCode session started",
295
+ "deepseek-harness": "DeepSeek Harness session started",
296
+ };
297
+ const message = runtime && Object.hasOwn(messages, runtime)
298
+ ? messages[runtime]
299
+ : "Session started";
300
+ return [{ stream: "system", text: `● ${td(message)}` }];
288
301
  }
289
302
  if (e.type === "turn.plan.updated") {
290
303
  const plan = boundedCodexPlan(e.plan);
@@ -14,7 +14,7 @@ import { assertRegistryCoversDescriptors, listManagedServiceDescriptorPaths, man
14
14
  export async function managedDaemonCapabilities(eligibility) {
15
15
  try {
16
16
  return (await eligibility()).eligible
17
- ? ["daemon_update_v1", "daemon_restart_v1", "daemon_update_registry_v1", "daemon_layout_migration_v1"]
17
+ ? ["daemon_update_v1", "daemon_restart_v1", "daemon_update_registry_v1"]
18
18
  : [];
19
19
  }
20
20
  catch {
@@ -878,23 +878,26 @@ export function createExecutionJournal(agentsRoot, options = {}) {
878
878
  },
879
879
  acknowledgeCompletion: async (executionId) => {
880
880
  validateExecutionId(executionId);
881
- const updated = await serializedExecution(executionId, "acknowledge_completion", async () => {
881
+ const result = await serializedExecution(executionId, "acknowledge_completion", async () => {
882
882
  const entry = await requireRecord(executionId);
883
883
  if (entry.state !== "completed" && entry.state !== "interrupted") {
884
884
  throw new JournalTransitionError(`Cannot acknowledge ${entry.state} execution`);
885
885
  }
886
- if (entry.completionAcknowledged)
887
- return confirmDurable(entry);
886
+ if (entry.completionAcknowledged) {
887
+ return { entry: await confirmDurable(entry), newlyAcknowledged: false };
888
+ }
888
889
  const updated = JournalEntrySchema.parse({
889
890
  ...entry,
890
891
  completionAcknowledged: true,
891
892
  updatedAt: now().toISOString(),
892
893
  });
893
894
  await writeRecord(updated);
894
- return updated;
895
+ return { entry: updated, newlyAcknowledged: true };
895
896
  });
896
- await serializedDirectory(pruneInternal);
897
- return updated;
897
+ // 重复 ACK 仍确认持久化,但不能反复全目录清理并阻塞后续控制帧。
898
+ if (result.newlyAcknowledged)
899
+ await serializedDirectory(pruneInternal);
900
+ return result.entry;
898
901
  },
899
902
  get: async (executionId) => {
900
903
  validateExecutionId(executionId);
@@ -264,6 +264,7 @@ export const ExecutionRejectedSchema = z.object({
264
264
  reason: RejectionReasonSchema,
265
265
  message: z.string().optional(),
266
266
  at: TimestampSchema,
267
+ preAdmissionProof: z.object({ specHash: z.string().regex(/^[a-f0-9]{64}$/u) }).strict().optional(),
267
268
  }).strict();
268
269
  export const ExecutionStartedSchema = z.object({
269
270
  type: z.literal("execution:started"),
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { dirname, join, resolve } from "node:path";
3
- import { resolveReposRoot } from "./config.js";
3
+ import { isLocalMemoryV2Enabled, localMemoryV2Environment, resolveReposRoot } from "./config.js";
4
4
  import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, ExecutionRejectedSchema, ExecutionStartSchema, } from "./execution-protocol.js";
5
5
  import { JournalConflictError } from "./execution-journal.js";
6
6
  import { boundExecutionFrame } from "./execution-event-limit.js";
@@ -210,6 +210,7 @@ function launchProviderConfig(config) {
210
210
  ? {}
211
211
  : { providerSmallFastModel: config.providerSmallFastModel }),
212
212
  ...(config.envVars === undefined ? {} : { envVars: config.envVars }),
213
+ ...(config.environmentPlan === undefined ? {} : { environmentPlan: config.environmentPlan }),
213
214
  ...(config.description === undefined ? {} : { description: config.description }),
214
215
  };
215
216
  }
@@ -408,7 +409,8 @@ export async function runExecution(config, input, dependencies) {
408
409
  ? { permission: effectivePermission(spec, config) }
409
410
  : admission(spec, config, dependencies, initialAt);
410
411
  if ("rejected" in checked) {
411
- const frame = ExecutionRejectedSchema.parse(boundExecutionFrame(checked.rejected, config.executionLimits.maxEventBytes));
412
+ const proof = await dependencies.confirmPreAdmission?.(checked.rejected).catch(() => false);
413
+ const frame = ExecutionRejectedSchema.parse(boundExecutionFrame({ ...checked.rejected, ...(proof ? { preAdmissionProof: { specHash } } : {}) }, config.executionLimits.maxEventBytes));
412
414
  await dependencies.report(frame);
413
415
  return { kind: "rejected", frame };
414
416
  }
@@ -745,6 +747,7 @@ export async function runExecution(config, input, dependencies) {
745
747
  },
746
748
  });
747
749
  const localDependencies = {
750
+ ...(dependencies.machineEnvironment === undefined ? {} : { machineEnvironment: dependencies.machineEnvironment }),
748
751
  ...(dependencies.cancellation === undefined ? {} : { cancellation: dependencies.cancellation }),
749
752
  ...(dependencies.startupGate === undefined ? {} : { startupGate: dependencies.startupGate }),
750
753
  ...(dependencies.startupTimeoutMs === undefined
@@ -829,12 +832,15 @@ export async function runExecution(config, input, dependencies) {
829
832
  const baseSystemPromptBudget = spec.agent.abilityRelease === undefined
830
833
  ? systemPromptBudget
831
834
  : Math.max(0, Math.min(systemPromptBudget, Math.max(32 * 1024, systemPromptBudget - 168 * 1024)));
832
- const localMemoryV2Enabled = config.localMemoryV2Handles?.has(spec.agent.handle) === true;
833
- const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${localMemoryV2Enabled ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
834
- ...(selfTraining ? { isolateLocalMemory: true } : {}),
835
- collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex",
836
- });
835
+ const localMemoryV2Enabled = !selfTraining && isLocalMemoryV2Enabled(config, spec.agent.handle);
836
+ // 开发环境的系统/当前轮写保护由 prepareLocalMemoryPrompt 统一注入并预留预算。
837
+ const localMemoryGuardInBase = localMemoryV2Environment(config.serverUrl) === "custom";
837
838
  const boundedSystemPrompt = (context) => {
839
+ const enabled = context.workspace.localMemoryV2Enabled ?? localMemoryV2Enabled;
840
+ const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${enabled && localMemoryGuardInBase ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
841
+ ...(selfTraining ? { isolateLocalMemory: true } : {}),
842
+ collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex",
843
+ });
838
844
  const base = typeof systemPromptWithLocalFacts === "string"
839
845
  ? systemPromptWithLocalFacts
840
846
  : systemPromptWithLocalFacts(context);
@@ -877,7 +883,6 @@ export async function runExecution(config, input, dependencies) {
877
883
  ...(config.qaReportRoot === undefined ? {} : { qaReportRoot: config.qaReportRoot }),
878
884
  cliPath: config.cliPath,
879
885
  providerConfig,
880
- codexHomeMode: config.codexHomeMode,
881
886
  ...(providerConfig.description ? { description: providerConfig.description } : {}),
882
887
  ...(spec.reporting.allowBoundImDecision ? {
883
888
  systemEnv: {
package/dist/i18n.js CHANGED
@@ -18,6 +18,11 @@ export function detectDaemonLang(env = process.env) {
18
18
  const zh = {
19
19
  "Claude session started": "Claude 会话启动",
20
20
  "Codex session started": "Codex 会话启动",
21
+ "Kimi session started": "Kimi 会话启动",
22
+ "Hermes session started": "Hermes 会话启动",
23
+ "OpenCode session started": "OpenCode 会话启动",
24
+ "DeepSeek Harness session started": "DeepSeek Harness 会话启动",
25
+ "Session started": "会话启动",
21
26
  "Plan updated": "计划更新",
22
27
  "Thinking (~{{tokens}} tokens, content not streamed by Claude Code)": "思考(约 {{tokens}} tokens,Claude Code 未输出思考正文)",
23
28
  "Thinking (content not streamed by Claude Code)": "思考(Claude Code 未输出思考正文)",
@@ -26,6 +31,20 @@ const zh = {
26
31
  "Run finished": "本轮结束",
27
32
  "Missing CREW_MACHINE_TOKEN (sk_machine_*, printed by seed)": "缺少 CREW_MACHINE_TOKEN(sk_machine_*,由 seed 打印)",
28
33
  "Usage:": "用法:",
34
+ "Open {{url}}": "打开 {{url}}",
35
+ "Code {{code}}": "验证码 {{code}}",
36
+ "Browser open skipped: {{reason}}": "未能自动打开浏览器:{{reason}}",
37
+ "Starting computer setup pairing...": "正在发起电脑接入授权…",
38
+ "Workspace: {{value}}": "工作区:{{value}}",
39
+ "Machine: {{value}}": "机器: {{value}}",
40
+ "Environment: {{value}}": "环境:{{value}}",
41
+ "Daemon: profile {{profile}} / v{{version}} / {{status}}": "Daemon:配置 {{profile}} / v{{version}} / {{status}}",
42
+ "Local: {{value}}": "本地目录:{{value}}",
43
+ "Config: {{value}}": "配置目录:{{value}}",
44
+ "Agents: {{value}}": "Agent 目录:{{value}}",
45
+ "Storage ID: {{value}}": "存储标识:{{value}}",
46
+ "Status: connected and ready": "状态:已连接并就绪",
47
+ "Status: not ready": "状态:尚未就绪",
29
48
  "connect and stay resident": "连接并常驻",
30
49
  "run once manually": "手动运行一次",
31
50
  "resident, connecting to": "常驻,连接到",
@@ -8,8 +8,8 @@ const execFileRaw = promisify(execFile);
8
8
  const MODEL_PROBE_TIMEOUT_MS = 8_000;
9
9
  const MODEL_PROBE_MAX_BUFFER_BYTES = 1024 * 1024;
10
10
  // win32 上 npm CLI 是 .cmd shim,execFile 需 shell 才能执行;参数全是固定字面量,无注入面。
11
- const execFileP = (bin, args) => execFileRaw(bin, args, {
12
- env: { ...process.env, PATH: augmentedPath() },
11
+ const execFileP = (bin, args, env = { ...process.env, PATH: augmentedPath() }) => execFileRaw(bin, args, {
12
+ env,
13
13
  shell: isWin(),
14
14
  timeout: MODEL_PROBE_TIMEOUT_MS,
15
15
  killSignal: "SIGKILL",
@@ -18,17 +18,17 @@ const execFileP = (bin, args) => execFileRaw(bin, args, {
18
18
  export async function listRuntimeModels(runtime, options = {}) {
19
19
  switch (runtime) {
20
20
  case "claude":
21
- return listClaudeModels();
21
+ return listClaudeModels(undefined, options.env, options.environmentGeneration);
22
22
  case "codex":
23
- return parseCodexModels((await execFileP("codex", ["debug", "models"])).stdout);
23
+ return parseCodexModels((await execFileP("codex", ["debug", "models"], options.env)).stdout);
24
24
  case "cursor":
25
- return parseCursorModels((await execFileP("cursor-agent", ["--list-models"])).stdout);
25
+ return parseCursorModels((await execFileP("cursor-agent", ["--list-models"], options.env)).stdout);
26
26
  case "hermes":
27
27
  return listHermesModels(options);
28
28
  case "opencode":
29
- return listOpencodeModels();
29
+ return listOpencodeModels(options.env);
30
30
  case "pi":
31
- return parsePiModels((await execFileP("pi", ["--list-models"])).stdout);
31
+ return parsePiModels((await execFileP("pi", ["--list-models"], options.env)).stdout);
32
32
  default:
33
33
  return null;
34
34
  }
@@ -84,9 +84,9 @@ function parseCursorModels(stdout) {
84
84
  return { id: id.trim(), label: (label || id).trim(), ...(index === 0 ? { default: true } : {}) };
85
85
  });
86
86
  }
87
- async function outputEvenOnFailure(bin, args) {
87
+ async function outputEvenOnFailure(bin, args, env) {
88
88
  try {
89
- return (await execFileP(bin, args)).stdout;
89
+ return (await execFileP(bin, args, env)).stdout;
90
90
  }
91
91
  catch (error) {
92
92
  const stdout = error.stdout;
@@ -97,11 +97,11 @@ async function outputEvenOnFailure(bin, args) {
97
97
  return "";
98
98
  }
99
99
  }
100
- async function listOpencodeModels() {
101
- const verbose = parseOpencodeModels(await outputEvenOnFailure("opencode", ["models", "--verbose"]));
100
+ async function listOpencodeModels(env) {
101
+ const verbose = parseOpencodeModels(await outputEvenOnFailure("opencode", ["models", "--verbose"], env));
102
102
  if (verbose.length > 0)
103
103
  return verbose;
104
- return parseOpencodeModels(await outputEvenOnFailure("opencode", ["models"]));
104
+ return parseOpencodeModels(await outputEvenOnFailure("opencode", ["models"], env));
105
105
  }
106
106
  const VARIANT_ORDER = new Map([
107
107
  ["none", 0], ["minimal", 1], ["low", 2], ["medium", 3],
@@ -0,0 +1,22 @@
1
+ import { awaitWithCancellation } from "./runtime-cancellation.js";
2
+ const leaseTails = new Map();
3
+ /** 同一进程内的短操作或原生会话排队;等待取消也不会越过前一位持有者。 */
4
+ export async function withKeyedLease(key, operation, cancellation) {
5
+ const predecessor = leaseTails.get(key) ?? Promise.resolve();
6
+ // 解锁函数在等待 predecessor 前可用,保留取消等待时的队列顺序。
7
+ let unlock;
8
+ const current = new Promise(resolve => { unlock = resolve; });
9
+ const queued = predecessor.then(() => current);
10
+ leaseTails.set(key, queued);
11
+ try {
12
+ await awaitWithCancellation(predecessor, cancellation);
13
+ return await operation();
14
+ }
15
+ finally {
16
+ unlock();
17
+ void queued.then(() => {
18
+ if (leaseTails.get(key) === queued)
19
+ leaseTails.delete(key);
20
+ });
21
+ }
22
+ }