@nowcrew/daemon 0.6.88-beta.3 → 0.6.88-beta.31
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 +89 -14
- package/dist/agent-ability/controller.js +11 -1
- package/dist/agent-ability/git-errors.js +22 -0
- package/dist/agent-ability/materializer.js +21 -10
- package/dist/agent-ability/resolver.js +12 -4
- package/dist/agent-environment-contract.js +55 -0
- package/dist/agent-environment-control.js +24 -0
- package/dist/agent-environment-execution.js +98 -0
- package/dist/agent-environments.js +275 -0
- package/dist/agent-memory/policy.js +3 -1
- package/dist/automatic-daemon-installer.js +51 -2
- package/dist/automatic-install-target.js +8 -0
- package/dist/computer-main.js +18 -0
- package/dist/computer-profile.js +17 -4
- package/dist/computer-setup-client.js +318 -0
- package/dist/computer-setup-orchestration.js +203 -0
- package/dist/computer-setup.js +244 -0
- package/dist/config.js +30 -12
- package/dist/console-formatter.js +3 -3
- package/dist/console.js +15 -2
- package/dist/daemon-update-eligibility.js +1 -1
- package/dist/execution-journal.js +9 -6
- package/dist/execution-protocol.js +2 -1
- package/dist/execution-runner.js +16 -10
- package/dist/i18n.js +19 -0
- package/dist/list-models.js +13 -11
- package/dist/local-execution-lease.js +22 -0
- package/dist/local-executor.js +88 -92
- package/dist/local-memory/candidate.js +43 -0
- package/dist/local-memory/index-budget.js +8 -0
- package/dist/local-memory/inventory-cli.js +61 -0
- package/dist/local-memory/inventory-preview.js +159 -0
- package/dist/local-memory/inventory-source.js +159 -0
- package/dist/local-memory/pending-match.js +29 -0
- package/dist/local-memory/pending-query.js +100 -0
- package/dist/local-memory/pending-schema.js +29 -0
- package/dist/local-memory/pending-source.js +126 -0
- package/dist/local-memory/pending.js +34 -84
- package/dist/local-memory/prompt.js +20 -12
- package/dist/local-memory/read-cli.js +97 -12
- package/dist/local-memory/rollout.js +82 -0
- package/dist/local-memory/runtime.js +24 -17
- package/dist/local-memory/work-log-execution.js +31 -0
- package/dist/local-memory/work-log.js +58 -0
- package/dist/local-memory/write.js +27 -58
- package/dist/local-runtime-environment.js +50 -0
- package/dist/machine-environment-cache.js +147 -0
- package/dist/machine-environment-contract.js +10 -0
- package/dist/machine-environment-control.js +27 -0
- package/dist/machine-environment-policy.js +75 -0
- package/dist/machine-environment-process.js +110 -0
- package/dist/machine-info.js +70 -25
- package/dist/machine-runtime-environment.js +189 -0
- package/dist/machine-shell-environment-capture.js +135 -0
- package/dist/machine-windows-environment-capture.js +215 -0
- package/dist/machine-windows-environment-script.js +158 -0
- package/dist/main.js +4 -69
- package/dist/marketplace-controller.js +75 -0
- package/dist/marketplace-protocol.js +38 -0
- package/dist/marketplace-runner.js +234 -0
- package/dist/marketplace-serve.js +17 -0
- package/dist/message-file-preview.js +246 -0
- package/dist/native-thread-context.js +59 -0
- package/dist/pre-admission-rejection.js +64 -0
- package/dist/project-skills/execution-adapter.js +3 -2
- package/dist/project-skills/runtime-launch.js +14 -0
- package/dist/prompt.js +15 -15
- package/dist/provider-env.js +26 -15
- package/dist/runner.js +31 -19
- package/dist/runtime-environment-merge.js +16 -0
- package/dist/runtime-health-handler.js +10 -0
- package/dist/runtime-health.js +3 -1
- package/dist/runtime-path.js +22 -12
- package/dist/runtime-probe.js +35 -6
- package/dist/runtimes/acp-probe-transport.js +69 -0
- package/dist/runtimes/claude-models.js +20 -9
- package/dist/runtimes/claude-session-start-hook.js +24 -0
- package/dist/runtimes/claude-thread-runner.js +266 -0
- package/dist/runtimes/claude.js +23 -4
- package/dist/runtimes/codex-app-server-runner.js +43 -4
- package/dist/runtimes/deepseek-harness-launch.js +10 -0
- package/dist/runtimes/deepseek-harness-profile.js +92 -0
- package/dist/runtimes/deepseek-harness-runner.js +226 -0
- package/dist/runtimes/deepseek-harness.js +76 -15
- package/dist/runtimes/harness-probe-retry.js +42 -0
- package/dist/runtimes/hermes-models.js +2 -1
- package/dist/runtimes/kimi-acp-runner.js +84 -15
- package/dist/runtimes/opencode-runner.js +7 -1
- package/dist/runtimes/opencode.js +2 -2
- package/dist/serve.js +95 -184
- package/dist/server-url.js +25 -0
- package/dist/session.js +4 -0
- package/dist/slog.js +2 -1
- package/dist/storage-identity.js +0 -3
- package/dist/storage-runtime.js +2 -2
- package/dist/storage-serve-adapter.js +6 -72
- package/dist/supervised-runtime.js +28 -18
- package/dist/thread-input-delivery.js +217 -0
- package/dist/token.js +0 -1
- package/dist/workspace-control-handler.js +27 -0
- package/dist/workspace-fs.js +66 -15
- package/dist/workspace.js +1 -1
- package/package.json +5 -4
- package/dist/codex-home-launch.js +0 -7
- package/dist/codex-home-mode-controller.js +0 -68
- package/dist/daemon-migration-controller.js +0 -211
- package/dist/daemon-migration-wiring.js +0 -22
- package/dist/runtimes/codex-home-migration-cli.js +0 -26
- package/dist/runtimes/codex-home-migration.js +0 -112
- package/dist/runtimes/codex-home.js +0 -236
- package/dist/storage-control.js +0 -212
- package/dist/storage-copy.js +0 -628
- package/dist/storage-credential-guard.js +0 -21
- package/dist/storage-doctor.js +0 -228
- package/dist/storage-manifest.js +0 -1027
- package/dist/storage-migration-controller.js +0 -440
- package/dist/storage-migration-manifest.js +0 -105
- package/dist/storage-migration-policy.js +0 -100
- package/dist/storage-runtime-controller.js +0 -381
- package/dist/storage-runtime-readiness.js +0 -240
- package/dist/storage-safe-fs.js +0 -302
- package/dist/storage-status-delivery.js +0 -224
- package/dist/storage-status-replay.js +0 -140
- package/dist/storage-target-confirmation.js +0 -129
- package/dist/storage-worker-command.js +0 -37
- package/dist/storage-worker-executor.js +0 -449
- package/dist/storage-worker-handoff.js +0 -151
- package/dist/storage-worker-operation.js +0 -294
- 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
|
-
//
|
|
80
|
-
// 测试完成后删除此常量,恢复未配置时默认关闭的行为。
|
|
79
|
+
// 保留本地开发的原默认名单;远端 dev 全量不再依赖该集合。
|
|
81
80
|
const TEMP_LOCAL_MEMORY_V2_TEST_HANDLES = [
|
|
82
|
-
"94533bf3-b5b7-4a1e-9c41-6a1ab0a0e1ea", // memory-v2-test-
|
|
83
|
-
"967ab9ce-724c-4f22-a664-2f855cd798af", // memory-v2-test-
|
|
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
|
|
115
|
-
const value = env.
|
|
116
|
-
if (value ===
|
|
117
|
-
return
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
|
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
|
-
|
|
897
|
-
|
|
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);
|
|
@@ -200,7 +200,7 @@ export const ExecutionStartSchema = z.object({
|
|
|
200
200
|
language: z.enum(["zh", "en"]),
|
|
201
201
|
systemPrompt: z.string(),
|
|
202
202
|
wakePrompt: z.string(),
|
|
203
|
-
maintenancePurpose: z.
|
|
203
|
+
maintenancePurpose: z.enum(["self_training", "memory_prune"]).optional(),
|
|
204
204
|
}).strict(),
|
|
205
205
|
runtime: z.object({
|
|
206
206
|
name: RuntimeSchema,
|
|
@@ -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"),
|
package/dist/execution-runner.js
CHANGED
|
@@ -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
|
|
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
|
|
833
|
-
|
|
834
|
-
|
|
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);
|
|
@@ -844,6 +850,7 @@ export async function runExecution(config, input, dependencies) {
|
|
|
844
850
|
executionId: spec.executionId,
|
|
845
851
|
handle: spec.agent.handle,
|
|
846
852
|
channelId: spec.context.channelId,
|
|
853
|
+
...(spec.context.threadId === undefined ? {} : { threadId: spec.context.threadId }),
|
|
847
854
|
keyMode: "opaque",
|
|
848
855
|
taskKey: spec.workspace.taskKey,
|
|
849
856
|
...(spec.workspace.resumeKey === undefined ? {} : { resumeKey: spec.workspace.resumeKey }),
|
|
@@ -876,7 +883,6 @@ export async function runExecution(config, input, dependencies) {
|
|
|
876
883
|
...(config.qaReportRoot === undefined ? {} : { qaReportRoot: config.qaReportRoot }),
|
|
877
884
|
cliPath: config.cliPath,
|
|
878
885
|
providerConfig,
|
|
879
|
-
codexHomeMode: config.codexHomeMode,
|
|
880
886
|
...(providerConfig.description ? { description: providerConfig.description } : {}),
|
|
881
887
|
...(spec.reporting.allowBoundImDecision ? {
|
|
882
888
|
systemEnv: {
|
|
@@ -885,13 +891,13 @@ export async function runExecution(config, input, dependencies) {
|
|
|
885
891
|
} : {}),
|
|
886
892
|
},
|
|
887
893
|
session: {
|
|
888
|
-
enabled:
|
|
894
|
+
enabled: spec.instructions.maintenancePurpose !== undefined ? false : config.resume,
|
|
889
895
|
warmMs: config.resumeWarmMs,
|
|
890
896
|
budgetTokens: config.sessionBudgetTokens,
|
|
891
897
|
softTokens: config.sessionSoftTokens,
|
|
892
898
|
maxTurns: config.sessionMaxTurns,
|
|
893
899
|
},
|
|
894
|
-
...(
|
|
900
|
+
...(spec.instructions.maintenancePurpose === undefined ? {} : { maintenancePurpose: spec.instructions.maintenancePurpose }),
|
|
895
901
|
localMemoryV2Enabled,
|
|
896
902
|
};
|
|
897
903
|
const result = await Promise.race([
|
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": "常驻,连接到",
|
package/dist/list-models.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
import { isWin } from "./platform.js";
|
|
4
|
+
import { augmentedPath } from "./runtime-path.js";
|
|
4
5
|
import { listClaudeModels } from "./runtimes/claude-models.js";
|
|
5
6
|
import { listHermesModels } from "./runtimes/hermes-models.js";
|
|
6
7
|
const execFileRaw = promisify(execFile);
|
|
7
8
|
const MODEL_PROBE_TIMEOUT_MS = 8_000;
|
|
8
9
|
const MODEL_PROBE_MAX_BUFFER_BYTES = 1024 * 1024;
|
|
9
10
|
// win32 上 npm CLI 是 .cmd shim,execFile 需 shell 才能执行;参数全是固定字面量,无注入面。
|
|
10
|
-
const execFileP = (bin, args) => execFileRaw(bin, args, {
|
|
11
|
+
const execFileP = (bin, args, env = { ...process.env, PATH: augmentedPath() }) => execFileRaw(bin, args, {
|
|
12
|
+
env,
|
|
11
13
|
shell: isWin(),
|
|
12
14
|
timeout: MODEL_PROBE_TIMEOUT_MS,
|
|
13
15
|
killSignal: "SIGKILL",
|
|
@@ -16,17 +18,17 @@ const execFileP = (bin, args) => execFileRaw(bin, args, {
|
|
|
16
18
|
export async function listRuntimeModels(runtime, options = {}) {
|
|
17
19
|
switch (runtime) {
|
|
18
20
|
case "claude":
|
|
19
|
-
return listClaudeModels();
|
|
21
|
+
return listClaudeModels(undefined, options.env, options.environmentGeneration);
|
|
20
22
|
case "codex":
|
|
21
|
-
return parseCodexModels((await execFileP("codex", ["debug", "models"])).stdout);
|
|
23
|
+
return parseCodexModels((await execFileP("codex", ["debug", "models"], options.env)).stdout);
|
|
22
24
|
case "cursor":
|
|
23
|
-
return parseCursorModels((await execFileP("cursor-agent", ["--list-models"])).stdout);
|
|
25
|
+
return parseCursorModels((await execFileP("cursor-agent", ["--list-models"], options.env)).stdout);
|
|
24
26
|
case "hermes":
|
|
25
27
|
return listHermesModels(options);
|
|
26
28
|
case "opencode":
|
|
27
|
-
return listOpencodeModels();
|
|
29
|
+
return listOpencodeModels(options.env);
|
|
28
30
|
case "pi":
|
|
29
|
-
return parsePiModels((await execFileP("pi", ["--list-models"])).stdout);
|
|
31
|
+
return parsePiModels((await execFileP("pi", ["--list-models"], options.env)).stdout);
|
|
30
32
|
default:
|
|
31
33
|
return null;
|
|
32
34
|
}
|
|
@@ -82,9 +84,9 @@ function parseCursorModels(stdout) {
|
|
|
82
84
|
return { id: id.trim(), label: (label || id).trim(), ...(index === 0 ? { default: true } : {}) };
|
|
83
85
|
});
|
|
84
86
|
}
|
|
85
|
-
async function outputEvenOnFailure(bin, args) {
|
|
87
|
+
async function outputEvenOnFailure(bin, args, env) {
|
|
86
88
|
try {
|
|
87
|
-
return (await execFileP(bin, args)).stdout;
|
|
89
|
+
return (await execFileP(bin, args, env)).stdout;
|
|
88
90
|
}
|
|
89
91
|
catch (error) {
|
|
90
92
|
const stdout = error.stdout;
|
|
@@ -95,11 +97,11 @@ async function outputEvenOnFailure(bin, args) {
|
|
|
95
97
|
return "";
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
|
-
async function listOpencodeModels() {
|
|
99
|
-
const verbose = parseOpencodeModels(await outputEvenOnFailure("opencode", ["models", "--verbose"]));
|
|
100
|
+
async function listOpencodeModels(env) {
|
|
101
|
+
const verbose = parseOpencodeModels(await outputEvenOnFailure("opencode", ["models", "--verbose"], env));
|
|
100
102
|
if (verbose.length > 0)
|
|
101
103
|
return verbose;
|
|
102
|
-
return parseOpencodeModels(await outputEvenOnFailure("opencode", ["models"]));
|
|
104
|
+
return parseOpencodeModels(await outputEvenOnFailure("opencode", ["models"], env));
|
|
103
105
|
}
|
|
104
106
|
const VARIANT_ORDER = new Map([
|
|
105
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
|
+
}
|