@nowcrew/daemon 0.6.88-beta.28 → 0.6.88-beta.29
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 +7 -0
- package/dist/agent-ability/materializer.js +21 -10
- 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/config.js +7 -0
- package/dist/execution-runner.js +2 -0
- package/dist/list-models.js +12 -12
- package/dist/local-executor.js +20 -48
- 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 +20 -15
- 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 +0 -0
- package/dist/message-file-preview.js +239 -0
- package/dist/provider-env.js +26 -12
- package/dist/runner.js +14 -0
- package/dist/runtime-environment-merge.js +16 -0
- package/dist/runtime-health.js +3 -1
- package/dist/runtime-path.js +22 -12
- package/dist/runtime-probe.js +2 -2
- package/dist/runtimes/claude-models.js +20 -9
- package/dist/runtimes/deepseek-harness.js +17 -12
- package/dist/runtimes/hermes-models.js +2 -1
- package/dist/runtimes/kimi-acp-runner.js +7 -6
- package/dist/runtimes/opencode.js +2 -2
- package/dist/serve.js +50 -105
- package/dist/token.js +0 -1
- package/dist/workspace-control-handler.js +27 -0
- package/dist/workspace-fs.js +66 -15
- package/package.json +9 -10
|
@@ -14,6 +14,7 @@ import { tmpdir } from "node:os";
|
|
|
14
14
|
import { createInterface } from "node:readline";
|
|
15
15
|
import { promisify } from "node:util";
|
|
16
16
|
import { isWin } from "../platform.js";
|
|
17
|
+
import { augmentedPath } from "../runtime-path.js";
|
|
17
18
|
const execFileP = promisify(execFile);
|
|
18
19
|
const PROBE_TIMEOUT_MS = 8_000;
|
|
19
20
|
// 本机丢弃端口:连不上也无所谓,init 事件在任何网络请求之前就已输出。
|
|
@@ -26,18 +27,27 @@ const CLAUDE_ALIASES = [
|
|
|
26
27
|
];
|
|
27
28
|
const DEFAULT_ALIAS = "sonnet";
|
|
28
29
|
const cacheByVersion = new Map();
|
|
30
|
+
let cacheByEnvironment = new WeakMap();
|
|
29
31
|
/** "2.1.258 (Claude Code)" → "2.1.258";空/异常输出 → ""。 */
|
|
30
32
|
export function normalizeClaudeVersion(raw) {
|
|
31
33
|
return raw.replace(/\s*\(Claude Code\)\s*$/, "").trim();
|
|
32
34
|
}
|
|
33
|
-
export async function listClaudeModels(deps =
|
|
34
|
-
const
|
|
35
|
-
|
|
35
|
+
export async function listClaudeModels(deps, env, environmentGeneration = "") {
|
|
36
|
+
const probes = deps ?? { probeVersion: () => probeVersion(env), resolveAlias: (alias) => resolveAlias(alias, env) };
|
|
37
|
+
let cache = env ? cacheByEnvironment.get(env) : cacheByVersion;
|
|
38
|
+
if (!cache) {
|
|
39
|
+
cache = new Map();
|
|
40
|
+
if (env)
|
|
41
|
+
cacheByEnvironment.set(env, cache);
|
|
42
|
+
}
|
|
43
|
+
const version = normalizeClaudeVersion(await probes.probeVersion());
|
|
44
|
+
const cacheKey = JSON.stringify([environmentGeneration, version]);
|
|
45
|
+
const cached = version ? cache.get(cacheKey) : undefined;
|
|
36
46
|
if (cached)
|
|
37
47
|
return cached;
|
|
38
48
|
const resolved = await Promise.all(CLAUDE_ALIASES.map(async ([alias]) => {
|
|
39
49
|
try {
|
|
40
|
-
return await
|
|
50
|
+
return await probes.resolveAlias(alias);
|
|
41
51
|
}
|
|
42
52
|
catch {
|
|
43
53
|
return null;
|
|
@@ -53,22 +63,24 @@ export async function listClaudeModels(deps = defaultDeps) {
|
|
|
53
63
|
};
|
|
54
64
|
});
|
|
55
65
|
if (version && resolved.every((model) => model !== null))
|
|
56
|
-
|
|
66
|
+
cache.set(cacheKey, models);
|
|
57
67
|
return models;
|
|
58
68
|
}
|
|
59
69
|
/** 仅测试用:清空版本缓存。 */
|
|
60
70
|
export function resetClaudeModelCache() {
|
|
61
71
|
cacheByVersion.clear();
|
|
72
|
+
cacheByEnvironment = new WeakMap();
|
|
62
73
|
}
|
|
63
|
-
async function probeVersion() {
|
|
74
|
+
async function probeVersion(env = { ...process.env, PATH: augmentedPath() }) {
|
|
64
75
|
const { stdout } = await execFileP("claude", ["--version"], {
|
|
65
76
|
shell: isWin(),
|
|
66
77
|
timeout: PROBE_TIMEOUT_MS,
|
|
67
78
|
killSignal: "SIGKILL",
|
|
79
|
+
env,
|
|
68
80
|
});
|
|
69
81
|
return stdout;
|
|
70
82
|
}
|
|
71
|
-
function resolveAlias(alias) {
|
|
83
|
+
function resolveAlias(alias, env = { ...process.env, PATH: augmentedPath() }) {
|
|
72
84
|
return new Promise((resolve) => {
|
|
73
85
|
const child = spawn("claude", [
|
|
74
86
|
"--model", alias,
|
|
@@ -78,7 +90,7 @@ function resolveAlias(alias) {
|
|
|
78
90
|
], {
|
|
79
91
|
cwd: tmpdir(),
|
|
80
92
|
env: {
|
|
81
|
-
...
|
|
93
|
+
...env,
|
|
82
94
|
ANTHROPIC_BASE_URL: UNROUTABLE_API_BASE,
|
|
83
95
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
84
96
|
},
|
|
@@ -120,4 +132,3 @@ export function initModelOf(line) {
|
|
|
120
132
|
return null;
|
|
121
133
|
}
|
|
122
134
|
}
|
|
123
|
-
const defaultDeps = { probeVersion, resolveAlias };
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import spawn from "cross-spawn";
|
|
2
|
-
import { isAbsolute } from "node:path";
|
|
2
|
+
import { isAbsolute, win32, join } from "node:path";
|
|
3
3
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
5
|
import { deepSeekHarnessLaunch } from "./deepseek-harness-launch.js";
|
|
7
6
|
import { prepareHarnessProfile } from "./deepseek-harness-profile.js";
|
|
8
7
|
import { probeKimiAcp, AcpProbeCleanupError } from "./kimi-acp-runner.js";
|
|
8
|
+
import { runtimeEnvironmentValue } from "../runtime-environment-merge.js";
|
|
9
|
+
import { augmentedPath } from "../runtime-path.js";
|
|
9
10
|
export const DEEPSEEK_HARNESS_CONFIG_ENV = "NOWCREW_DEEPSEEK_HARNESS_CONFIG";
|
|
10
11
|
export const DEEPSEEK_HARNESS_PATCH_ENV = "NOWCREW_DEEPSEEK_HARNESS_PATCH";
|
|
11
|
-
export function deepSeekHarnessLauncher(env = process.env) {
|
|
12
|
-
const configPath = env
|
|
13
|
-
const patchPath = env
|
|
14
|
-
|
|
12
|
+
export function deepSeekHarnessLauncher(env = process.env, platform = process.platform) {
|
|
13
|
+
const configPath = runtimeEnvironmentValue(env, DEEPSEEK_HARNESS_CONFIG_ENV, platform)?.trim();
|
|
14
|
+
const patchPath = runtimeEnvironmentValue(env, DEEPSEEK_HARNESS_PATCH_ENV, platform)?.trim();
|
|
15
|
+
const absolute = platform === "win32" ? win32.isAbsolute : isAbsolute;
|
|
16
|
+
if (configPath && !absolute(configPath))
|
|
15
17
|
throw new Error(`${DEEPSEEK_HARNESS_CONFIG_ENV} must be an absolute path`);
|
|
16
|
-
if (patchPath && !
|
|
18
|
+
if (patchPath && !absolute(patchPath))
|
|
17
19
|
throw new Error(`${DEEPSEEK_HARNESS_PATCH_ENV} must be an absolute path`);
|
|
18
20
|
if (configPath && patchPath)
|
|
19
21
|
throw new Error("DeepSeek Harness legacy config and profile patch are mutually exclusive");
|
|
@@ -21,24 +23,27 @@ export function deepSeekHarnessLauncher(env = process.env) {
|
|
|
21
23
|
? { kind: "legacy", bin: "dsh-acp-demo", configPath }
|
|
22
24
|
: { kind: "profile", bin: "dsh", ...(patchPath ? { patchPath } : {}) };
|
|
23
25
|
}
|
|
24
|
-
export function deepSeekHarnessConfigPath(env = process.env) {
|
|
25
|
-
const path = env
|
|
26
|
-
return path && isAbsolute(path) ? path : null;
|
|
26
|
+
export function deepSeekHarnessConfigPath(env = process.env, platform = process.platform) {
|
|
27
|
+
const path = runtimeEnvironmentValue(env, DEEPSEEK_HARNESS_CONFIG_ENV, platform)?.trim();
|
|
28
|
+
return path && (platform === "win32" ? win32.isAbsolute(path) : isAbsolute(path)) ? path : null;
|
|
27
29
|
}
|
|
28
30
|
/** The published DeepSeek Harness ACP executable speaks JSON-RPC directly on stdio. */
|
|
29
31
|
export async function probeDeepSeekHarnessAcp(options, spawnProcess = spawn) {
|
|
30
32
|
if (options.signal?.aborted)
|
|
31
33
|
return false;
|
|
34
|
+
const sourceEnv = options.env ?? { ...process.env, PATH: augmentedPath() };
|
|
32
35
|
let root;
|
|
33
36
|
try {
|
|
34
37
|
const launcher = options.configPath === undefined
|
|
35
|
-
? deepSeekHarnessLauncher()
|
|
38
|
+
? deepSeekHarnessLauncher(sourceEnv)
|
|
36
39
|
: deepSeekHarnessLauncher({
|
|
40
|
+
...sourceEnv,
|
|
37
41
|
[DEEPSEEK_HARNESS_CONFIG_ENV]: options.configPath,
|
|
38
42
|
});
|
|
39
43
|
if (launcher.kind === "legacy")
|
|
40
44
|
return await probeKimiAcp({
|
|
41
45
|
...options,
|
|
46
|
+
env: sourceEnv,
|
|
42
47
|
provider: "deepseek-harness",
|
|
43
48
|
configPath: launcher.configPath,
|
|
44
49
|
effectivePermission: "sandboxed",
|
|
@@ -50,7 +55,7 @@ export async function probeDeepSeekHarnessAcp(options, spawnProcess = spawn) {
|
|
|
50
55
|
systemPrompt: "",
|
|
51
56
|
skillRoots: [],
|
|
52
57
|
effectivePermission: "sandboxed",
|
|
53
|
-
env:
|
|
58
|
+
env: sourceEnv,
|
|
54
59
|
...(launcher.patchPath ? { patchPath: launcher.patchPath } : {}),
|
|
55
60
|
});
|
|
56
61
|
const launch = deepSeekHarnessLaunch(options.bin, profile.args);
|
|
@@ -71,9 +71,10 @@ async function stopChild(child) {
|
|
|
71
71
|
}
|
|
72
72
|
export async function listHermesModels(options = {}) {
|
|
73
73
|
const spawnProcess = options.spawnProcess ?? spawn;
|
|
74
|
+
const env = options.env ?? { ...process.env, PATH: augmentedPath() };
|
|
74
75
|
const child = spawnProcess("hermes", ["acp"], {
|
|
75
76
|
cwd: process.cwd(),
|
|
76
|
-
env
|
|
77
|
+
env,
|
|
77
78
|
stdio: ["pipe", "pipe", "pipe"],
|
|
78
79
|
});
|
|
79
80
|
if (child.stdin === null || child.stdout === null || child.stderr === null)
|
|
@@ -136,10 +136,10 @@ function deepSeekPermissionMode(permission) {
|
|
|
136
136
|
return "workspace-write";
|
|
137
137
|
return "danger-full-access";
|
|
138
138
|
}
|
|
139
|
-
function providerEnv(provider, permission) {
|
|
139
|
+
function providerEnv(provider, permission, env = process.env) {
|
|
140
140
|
return provider === "deepseek-harness"
|
|
141
|
-
? { ...
|
|
142
|
-
:
|
|
141
|
+
? { ...env, DSH_PERMISSION_MODE: deepSeekPermissionMode(permission) }
|
|
142
|
+
: env;
|
|
143
143
|
}
|
|
144
144
|
function providerDisplayName(provider) {
|
|
145
145
|
if (provider === "deepseek-harness")
|
|
@@ -220,11 +220,12 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
|
220
220
|
...transport.fields(), spawn_confirmed_ms: spawnConfirmedMs, child_pid: child.pid,
|
|
221
221
|
reason, protocol_version: protocolVersion, stderr_seen: stderrSeen, aborted: options.signal?.aborted ?? false });
|
|
222
222
|
dslog("execution.acp_probe_started", "ACP 探测开始", { probe_id: probeId, runtime: provider, node_version: process.version });
|
|
223
|
-
const
|
|
223
|
+
const sourceEnv = options.env ?? { ...process.env, PATH: augmentedPath() };
|
|
224
|
+
const launchEnv = options.launchEnv ?? providerEnv(provider, permission, sourceEnv);
|
|
224
225
|
const child = spawnProcess(options.bin, options.launchArgs ?? providerArgs(provider, options.configPath), {
|
|
225
226
|
cwd: options.launchCwd ?? process.cwd(),
|
|
226
|
-
// 显式 launchEnv
|
|
227
|
-
env:
|
|
227
|
+
// 显式 launchEnv 已准备好 PATH/HOME,不能二次补 PATH 或混入父进程环境。
|
|
228
|
+
env: launchEnv,
|
|
228
229
|
stdio: ["pipe", "pipe", "pipe"],
|
|
229
230
|
});
|
|
230
231
|
// spawn errors are emitted by ChildProcess, not its stdio streams. A missing
|
|
@@ -11,7 +11,7 @@ const REQUIRED_RUN_FLAGS = [
|
|
|
11
11
|
"--variant",
|
|
12
12
|
"--session",
|
|
13
13
|
];
|
|
14
|
-
export async function probeOpenCodeRun(signal) {
|
|
14
|
+
export async function probeOpenCodeRun(signal, env = { ...process.env, PATH: augmentedPath() }) {
|
|
15
15
|
try {
|
|
16
16
|
const { stdout, stderr } = await execFileP("opencode", ["run", "--help"], {
|
|
17
17
|
shell: isWin(),
|
|
@@ -19,7 +19,7 @@ export async function probeOpenCodeRun(signal) {
|
|
|
19
19
|
killSignal: "SIGKILL",
|
|
20
20
|
maxBuffer: 1024 * 1024,
|
|
21
21
|
signal,
|
|
22
|
-
env
|
|
22
|
+
env,
|
|
23
23
|
});
|
|
24
24
|
const help = `${stdout}\n${stderr}`;
|
|
25
25
|
return REQUIRED_RUN_FLAGS.every((flag) => help.includes(flag));
|
package/dist/serve.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/** 常驻控制面桥接;按请求拉起本地 runtime。 */
|
|
2
|
+
import { handleAgentEnvironmentFrame } from "./agent-environment-control.js";
|
|
3
|
+
import { handleMachineEnvironmentFrame } from "./machine-environment-control.js";
|
|
4
|
+
import { createMachineRuntimeEnvironment } from "./machine-runtime-environment.js";
|
|
5
5
|
import { WebSocket } from "ws";
|
|
6
6
|
import { persistPreAdmissionRejection, rejectQueuedAdmission } from "./pre-admission-rejection.js";
|
|
7
7
|
import { join } from "node:path";
|
|
@@ -9,14 +9,11 @@ import { randomUUID } from "node:crypto";
|
|
|
9
9
|
import { dslog, setSlogDefaults, drainSpool, flushSlog } from "./slog.js";
|
|
10
10
|
import { mergeRunAgentResults, reportScheduledStartFailure, runAgent } from "./runner.js";
|
|
11
11
|
import { buildOriginDecisionRetryPrompt, buildScheduledPrompt } from "./prompt.js";
|
|
12
|
-
import {
|
|
12
|
+
import { daemonCapabilities, daemonCapabilityBindings, } from "./machine-info.js";
|
|
13
13
|
import { reconcileExecutionJournal } from "./execution-recovery.js";
|
|
14
14
|
import { initReleaseSlog, reportDaemonConnectionClose, reportReleaseError } from "./release-channel-guard.js";
|
|
15
|
-
import {
|
|
16
|
-
import { listWorkspace, readWorkspaceFile } from "./workspace-fs.js";
|
|
17
|
-
import { listSkills } from "./skills.js";
|
|
15
|
+
import { handleWorkspaceControlRequest } from "./workspace-control-handler.js";
|
|
18
16
|
import { inspectRaftWorkspace, importRaftWorkspace } from "./workspace-import.js";
|
|
19
|
-
import { listRuntimeModels } from "./list-models.js";
|
|
20
17
|
import { normalizeScheduledContext } from "./scheduled-report.js";
|
|
21
18
|
import { formatDaemonLogLine } from "./log-format.js";
|
|
22
19
|
import { reportAgentRunComplete } from "./scheduled-run-report.js";
|
|
@@ -50,7 +47,6 @@ import { createProjectSkillsReconciler, } from "./project-skills/reconciler.js";
|
|
|
50
47
|
import { initializedProjectSkillsReconciler } from "./project-skills/initialized-reconciler.js";
|
|
51
48
|
import { parseMemoryPruneTraceId } from "./memory-prune-diagnostics.js";
|
|
52
49
|
import { probeRuntimeHealth } from "./runtime-health.js";
|
|
53
|
-
import { handleRuntimeHealthProbeFrame } from "./runtime-health-handler.js";
|
|
54
50
|
import { createServeMarketplace } from "./marketplace-serve.js";
|
|
55
51
|
import { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
56
52
|
import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
|
|
@@ -80,9 +76,6 @@ export function serve(config, opts = {}) {
|
|
|
80
76
|
?? (config.agentMemory === null ? undefined : createAgentMemoryBridge(config.agentMemory));
|
|
81
77
|
const executionTelemetry = createExecutionTelemetryJournal(config.agentsRoot);
|
|
82
78
|
const executeProtocol = opts.execution?.runExecution ?? runExecution;
|
|
83
|
-
let detectedExecutionRuntimes = [], executionRuntimesKnown = false;
|
|
84
|
-
let runtimeFacts = null;
|
|
85
|
-
const runtimeProbe = createRuntimeProbeCoordinator();
|
|
86
79
|
const localHome = opts.localHome ?? resolveDaemonLocalStateRootFromEnv();
|
|
87
80
|
const hostCoordinator = opts.execution?.hostCoordinator
|
|
88
81
|
?? createHostExecutionCoordinator({
|
|
@@ -171,6 +164,16 @@ export function serve(config, opts = {}) {
|
|
|
171
164
|
},
|
|
172
165
|
onSent: (hello) => log(`📤 已上报机器信息: ${hello.hostname} · ${hello.os} · installed=[${hello.runtimes.join(",")}] · executable=[${hello.executionRuntimes.join(",")}] · agents=[${hello.agentHandles.join(",")}]`),
|
|
173
166
|
});
|
|
167
|
+
const machineRuntime = createMachineRuntimeEnvironment({
|
|
168
|
+
config,
|
|
169
|
+
...(opts.machineEnvironment === undefined ? {} : { provider: opts.machineEnvironment }),
|
|
170
|
+
...(opts.machineInfo === undefined ? {} : { machineInfo: opts.machineInfo }),
|
|
171
|
+
...(opts.profileName === undefined ? {} : { profileName: opts.profileName }),
|
|
172
|
+
additionalCapabilities: async () => managedDaemonCapabilities(updateEligibility),
|
|
173
|
+
publish: machineHelloPublisher.publish,
|
|
174
|
+
onError: (code, context) => dslog("execution.runtime_detection_failed", "机器环境或 runtime 探测失败", { level: "ERROR", error_code: code, ...context }),
|
|
175
|
+
});
|
|
176
|
+
machineRuntime.prewarm();
|
|
174
177
|
const initializedProjectSkills = initializedProjectSkillsReconciler(() => projectSkillsController.ensureScanned(), projectSkillsReconciler);
|
|
175
178
|
const configuredCapabilities = opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform);
|
|
176
179
|
const prepareCapabilities = async () => daemonCapabilityBindings(process.platform, [...configuredCapabilities, ...await managedDaemonCapabilities(updateEligibility)]);
|
|
@@ -287,12 +290,20 @@ export function serve(config, opts = {}) {
|
|
|
287
290
|
if (stopped)
|
|
288
291
|
return;
|
|
289
292
|
const wsUrl = (opts.buildControlPlaneUrl ?? buildControlPlaneUrl)(config.serverUrl, config.machineToken, process.platform, undefined, capabilities.controlPlaneUrl);
|
|
290
|
-
|
|
293
|
+
const connectionSocket = createWebSocket(wsUrl);
|
|
294
|
+
ws = connectionSocket;
|
|
295
|
+
const sendToConnection = (frame) => {
|
|
296
|
+
try {
|
|
297
|
+
if (!stopped && ws === connectionSocket && connectionSocket.readyState === WebSocket.OPEN)
|
|
298
|
+
connectionSocket.send(JSON.stringify(frame));
|
|
299
|
+
}
|
|
300
|
+
catch { /* 旧连接请求由服务端超时处理。 */ }
|
|
301
|
+
};
|
|
291
302
|
let connectionEpochToken;
|
|
292
|
-
|
|
303
|
+
connectionSocket.on("open", () => {
|
|
293
304
|
connectionEpochToken = projectSkillsController.beginConnectionEpoch();
|
|
294
305
|
storageIdentityAdmission.beginIdentityBinding();
|
|
295
|
-
const openedSocket =
|
|
306
|
+
const openedSocket = connectionSocket;
|
|
296
307
|
const connectionId = randomUUID();
|
|
297
308
|
backoff = 1000;
|
|
298
309
|
connectedAt = Date.now();
|
|
@@ -301,60 +312,7 @@ export function serve(config, opts = {}) {
|
|
|
301
312
|
// 连上了才有机会把离线期间(断连原因/退出前)落盘的日志补传上去
|
|
302
313
|
void drainSpool();
|
|
303
314
|
// 上报本机信息 (hostname/os/daemon 版本/已装 runtimes)
|
|
304
|
-
|
|
305
|
-
const helloPromise = (opts.machineInfo?.collect ?? collectMachineHello)(config.agentsRoot, config.executionLimits, process.platform, {
|
|
306
|
-
...(detectInstalled ? { detectInstalled } : {}),
|
|
307
|
-
...(opts.profileName === undefined ? {} : { profileName: opts.profileName }),
|
|
308
|
-
// First hello must not wait for third-party handshakes; optional transports arrive in the refresh.
|
|
309
|
-
detectExecutable: async (installed) => conservativeExecutionRuntimes(installed),
|
|
310
|
-
additionalCapabilities: async () => managedDaemonCapabilities(updateEligibility),
|
|
311
|
-
capabilities: capabilities.machineHello,
|
|
312
|
-
});
|
|
313
|
-
runtimeFacts = helloPromise
|
|
314
|
-
.then(async (hello) => {
|
|
315
|
-
if (hello.executionProtocol === undefined) {
|
|
316
|
-
executionRuntimesKnown = true;
|
|
317
|
-
dslog("runtime_probe.hello_refresh", "runtime hello 刷新决策", {
|
|
318
|
-
connection_id: connectionId, reason: "protocol_unavailable",
|
|
319
|
-
current_socket: ws === openedSocket, socket_open: openedSocket.readyState === WebSocket.OPEN,
|
|
320
|
-
socket_ready_state: openedSocket.readyState, installed_runtimes: JSON.stringify(hello.runtimes),
|
|
321
|
-
execution_runtimes: JSON.stringify(hello.executionRuntimes),
|
|
322
|
-
});
|
|
323
|
-
return [];
|
|
324
|
-
}
|
|
325
|
-
const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable, connectionId);
|
|
326
|
-
executionRuntimesKnown = true;
|
|
327
|
-
const currentSocket = ws === openedSocket;
|
|
328
|
-
const socketOpen = openedSocket.readyState === WebSocket.OPEN;
|
|
329
|
-
const hasNewRuntime = detected.some((runtime) => !hello.executionRuntimes.includes(runtime));
|
|
330
|
-
dslog("runtime_probe.hello_refresh", "runtime hello 刷新决策", {
|
|
331
|
-
connection_id: connectionId, current_socket: currentSocket, socket_open: socketOpen,
|
|
332
|
-
socket_ready_state: openedSocket.readyState, installed_runtimes: JSON.stringify(hello.runtimes),
|
|
333
|
-
initial_execution_runtimes: JSON.stringify(hello.executionRuntimes), execution_runtimes: JSON.stringify(detected),
|
|
334
|
-
reason: !currentSocket ? "stale_connection" : !socketOpen ? "socket_not_open" : !hasNewRuntime ? "unchanged" : "publish",
|
|
335
|
-
});
|
|
336
|
-
if (currentSocket && socketOpen && hasNewRuntime) {
|
|
337
|
-
detectedExecutionRuntimes = detected;
|
|
338
|
-
machineHelloPublisher.publish({ ...hello, executionRuntimes: [...detected] }, openedSocket);
|
|
339
|
-
}
|
|
340
|
-
return detected;
|
|
341
|
-
})
|
|
342
|
-
.catch(() => {
|
|
343
|
-
executionRuntimesKnown = true;
|
|
344
|
-
dslog("execution.runtime_detection_failed", "runtime 探测失败", {
|
|
345
|
-
level: "ERROR", reason: "detection_threw", connection_id: connectionId,
|
|
346
|
-
current_socket: ws === openedSocket, socket_open: openedSocket.readyState === WebSocket.OPEN,
|
|
347
|
-
});
|
|
348
|
-
return [];
|
|
349
|
-
});
|
|
350
|
-
void helloPromise
|
|
351
|
-
.then(async (hello) => {
|
|
352
|
-
if (ws !== openedSocket || openedSocket.readyState !== WebSocket.OPEN)
|
|
353
|
-
return;
|
|
354
|
-
detectedExecutionRuntimes = hello.executionRuntimes;
|
|
355
|
-
machineHelloPublisher.publish(hello, openedSocket);
|
|
356
|
-
})
|
|
357
|
-
.catch(() => { });
|
|
315
|
+
machineRuntime.open(openedSocket, { capabilities: capabilities.machineHello, beforePublish: Promise.resolve(), connectionId });
|
|
358
316
|
opts.onOpen?.(ws);
|
|
359
317
|
void sendSnapshot(`reconnect-${randomUUID()}`);
|
|
360
318
|
void executionJournal.replay()
|
|
@@ -383,8 +341,8 @@ export function serve(config, opts = {}) {
|
|
|
383
341
|
});
|
|
384
342
|
});
|
|
385
343
|
});
|
|
386
|
-
|
|
387
|
-
if (stopped)
|
|
344
|
+
connectionSocket.on("message", async (data) => {
|
|
345
|
+
if (stopped || ws !== connectionSocket)
|
|
388
346
|
return;
|
|
389
347
|
let decoded;
|
|
390
348
|
try {
|
|
@@ -396,6 +354,10 @@ export function serve(config, opts = {}) {
|
|
|
396
354
|
if (typeof decoded !== "object" || decoded === null)
|
|
397
355
|
return;
|
|
398
356
|
const rawType = "type" in decoded && typeof decoded.type === "string" ? decoded.type : "";
|
|
357
|
+
if (rawType === "machine:environment:get" || rawType === "machine:environment:refresh") {
|
|
358
|
+
await handleMachineEnvironmentFrame(decoded, machineRuntime.provider, sendToConnection);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
399
361
|
if (rawType === "daemon:update" || rawType === "daemon:restart") {
|
|
400
362
|
void updateController.handle(decoded).catch((error) => {
|
|
401
363
|
dslog("daemon.control_failed", "daemon 控制操作处理失败", {
|
|
@@ -667,9 +629,7 @@ export function serve(config, opts = {}) {
|
|
|
667
629
|
let availableRuntimes;
|
|
668
630
|
try {
|
|
669
631
|
availableRuntimes = opts.execution?.availableRuntimes?.()
|
|
670
|
-
??
|
|
671
|
-
? detectedExecutionRuntimes
|
|
672
|
-
: await (runtimeFacts ?? Promise.resolve(detectedExecutionRuntimes)));
|
|
632
|
+
?? await machineRuntime.availableRuntimes(spec.runtime.name);
|
|
673
633
|
}
|
|
674
634
|
catch (error) {
|
|
675
635
|
cleanupExecutionReservation("runtime_detection_failed");
|
|
@@ -686,6 +646,7 @@ export function serve(config, opts = {}) {
|
|
|
686
646
|
}
|
|
687
647
|
const execution = executeProtocol(config, spec, {
|
|
688
648
|
...opts.execution?.dependencies,
|
|
649
|
+
machineEnvironment: machineRuntime.provider,
|
|
689
650
|
capabilities: capabilities.executionRunner,
|
|
690
651
|
projectSkills: opts.execution?.dependencies?.projectSkills ?? initializedProjectSkills,
|
|
691
652
|
abilityRelease: opts.execution?.dependencies?.abilityRelease ?? agentAbilityRuntime.materializer,
|
|
@@ -831,12 +792,9 @@ export function serve(config, opts = {}) {
|
|
|
831
792
|
return;
|
|
832
793
|
}
|
|
833
794
|
if (msg.type === "runtime-health:probe") {
|
|
834
|
-
const result = await
|
|
795
|
+
const result = await machineRuntime.health(msg, opts.runtimeHealth?.probe);
|
|
835
796
|
if (result !== null) {
|
|
836
|
-
|
|
837
|
-
ws?.send(JSON.stringify({ type: "fs:result", ...result }));
|
|
838
|
-
}
|
|
839
|
-
catch { /* server 会超时 */ }
|
|
797
|
+
sendToConnection({ type: "fs:result", ...result });
|
|
840
798
|
}
|
|
841
799
|
return;
|
|
842
800
|
}
|
|
@@ -870,26 +828,11 @@ export function serve(config, opts = {}) {
|
|
|
870
828
|
}
|
|
871
829
|
return;
|
|
872
830
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
try {
|
|
879
|
-
ws?.send(JSON.stringify({ type: "fs:result", reqId: req.reqId, ...r }));
|
|
880
|
-
}
|
|
881
|
-
catch { /* 忽略 */ }
|
|
882
|
-
};
|
|
883
|
-
try {
|
|
884
|
-
const data2 = req.type === "fs:list" ? await listWorkspace(root, req.path)
|
|
885
|
-
: req.type === "fs:read" ? await readWorkspaceFile(root, req.path)
|
|
886
|
-
: req.type === "skills:list" ? await listSkills(req.runtime)
|
|
887
|
-
: { models: await listRuntimeModels(req.type === "probe-models" ? (req.runtime ?? "") : "") };
|
|
888
|
-
reply({ ok: true, data: data2 });
|
|
889
|
-
}
|
|
890
|
-
catch (e) {
|
|
891
|
-
reply({ ok: false, error: e.message });
|
|
892
|
-
}
|
|
831
|
+
if (await handleAgentEnvironmentFrame(msg, config.agentsRoot, sendToConnection))
|
|
832
|
+
return;
|
|
833
|
+
const fileResult = await handleWorkspaceControlRequest(msg, config.agentsRoot, projectWorkspaceRegistry, machineRuntime.models);
|
|
834
|
+
if (fileResult) {
|
|
835
|
+
sendToConnection(fileResult);
|
|
893
836
|
return;
|
|
894
837
|
}
|
|
895
838
|
if (msg.type !== "agent:start")
|
|
@@ -1097,6 +1040,7 @@ export function serve(config, opts = {}) {
|
|
|
1097
1040
|
...(!scheduled && msg.wake?.seq !== undefined ? { wakeContextUpToSeq: msg.wake.seq } : {}),
|
|
1098
1041
|
...(attemptWake ? { wake: attemptWake } : {}),
|
|
1099
1042
|
}, reportActivity, reportConsole, {
|
|
1043
|
+
machineEnvironment: machineRuntime.provider,
|
|
1100
1044
|
cancellation: controller.cancellation,
|
|
1101
1045
|
startupGate: runtimeStartupGate,
|
|
1102
1046
|
startupTimeoutMs: config.executionLimits.startupTimeoutMs,
|
|
@@ -1259,8 +1203,9 @@ export function serve(config, opts = {}) {
|
|
|
1259
1203
|
void flushSlog(); // 每轮收尾冲一次,保证 run.end 尽快可查
|
|
1260
1204
|
}
|
|
1261
1205
|
});
|
|
1262
|
-
|
|
1263
|
-
|
|
1206
|
+
connectionSocket.on("close", (code) => {
|
|
1207
|
+
machineRuntime.close(connectionSocket);
|
|
1208
|
+
if (stopped || ws !== connectionSocket)
|
|
1264
1209
|
return;
|
|
1265
1210
|
// server 能力随连接失效;下个连接的 ready 帧重新声明(防降级重连后沿用过期能力)。
|
|
1266
1211
|
serverCapabilities = new Set();
|
|
@@ -1284,9 +1229,9 @@ export function serve(config, opts = {}) {
|
|
|
1284
1229
|
}, backoff);
|
|
1285
1230
|
backoff = Math.min(backoff * 2, maxBackoff);
|
|
1286
1231
|
});
|
|
1287
|
-
|
|
1232
|
+
connectionSocket.on("error", () => {
|
|
1288
1233
|
if (!stopped)
|
|
1289
|
-
|
|
1234
|
+
connectionSocket.close();
|
|
1290
1235
|
});
|
|
1291
1236
|
}
|
|
1292
1237
|
const ready = (async () => {
|
|
@@ -1324,7 +1269,6 @@ export function serve(config, opts = {}) {
|
|
|
1324
1269
|
stopPromise = (async () => {
|
|
1325
1270
|
stopped = true;
|
|
1326
1271
|
concurrencyTelemetry.stop();
|
|
1327
|
-
runtimeProbe.stop();
|
|
1328
1272
|
completionRetransmitter.stop();
|
|
1329
1273
|
const deadline = createShutdownDeadline(shutdownTimeoutMs);
|
|
1330
1274
|
if (reconnectTimer !== null) {
|
|
@@ -1333,6 +1277,7 @@ export function serve(config, opts = {}) {
|
|
|
1333
1277
|
}
|
|
1334
1278
|
const webSocketClosed = closeWebSocketWithinDeadline(ws, deadline.signal);
|
|
1335
1279
|
const pending = [
|
|
1280
|
+
machineRuntime.stop(),
|
|
1336
1281
|
webSocketClosed,
|
|
1337
1282
|
updateController.drain(),
|
|
1338
1283
|
marketplace.stop(),
|
package/dist/token.js
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** 控制面文件/技能查看的按需处理,独立于执行生命周期。 */
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { readSharedMessageFile } from "./message-file-preview.js";
|
|
4
|
+
import { listWorkspace, readWorkspaceFile } from "./workspace-fs.js";
|
|
5
|
+
import { listSkills } from "./skills.js";
|
|
6
|
+
import { listRuntimeModels } from "./list-models.js";
|
|
7
|
+
export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, models = listRuntimeModels) {
|
|
8
|
+
if (!raw || typeof raw !== "object")
|
|
9
|
+
return null;
|
|
10
|
+
const request = raw;
|
|
11
|
+
if (!["fs:list", "fs:read", "fs:read-shared", "skills:list", "probe-models"].includes(request.type))
|
|
12
|
+
return null;
|
|
13
|
+
if (typeof request.reqId !== "string" || request.reqId.length > 128)
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
const root = join(agentsRoot, request.handle);
|
|
17
|
+
const data = request.type === "fs:read-shared" ? await readSharedMessageFile(agentsRoot, raw, registry)
|
|
18
|
+
: request.type === "fs:list" ? await listWorkspace(root, request.path)
|
|
19
|
+
: request.type === "fs:read" ? await readWorkspaceFile(root, request.path)
|
|
20
|
+
: request.type === "skills:list" ? await listSkills(request.runtime)
|
|
21
|
+
: { models: await models(request.runtime ?? "") };
|
|
22
|
+
return { type: "fs:result", reqId: request.reqId, ok: true, data };
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return { type: "fs:result", reqId: request.reqId, ok: false, error: request.type === "probe-models" ? "runtime_model_probe_failed" : error.message };
|
|
26
|
+
}
|
|
27
|
+
}
|