@nowcrew/daemon 0.5.45 → 0.5.47
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 +22 -3
- package/dist/agent-memory/bridge.js +101 -10
- package/dist/daemon-update-controller.js +20 -12
- package/dist/daemon-update-eligibility.js +10 -0
- package/dist/daemon-updater.js +21 -0
- package/dist/execution-journal.js +1 -1
- package/dist/execution-protocol.js +3 -11
- package/dist/execution-runner.js +13 -4
- package/dist/external-output.js +4 -0
- package/dist/list-models.js +102 -8
- package/dist/local-executor.js +87 -14
- package/dist/local-memory-diagnostics.js +336 -0
- package/dist/local-memory-telemetry.js +224 -0
- package/dist/machine-info.js +22 -7
- package/dist/main.js +6 -1
- package/dist/normalize.js +14 -4
- package/dist/runtime-capabilities.js +11 -1
- package/dist/runtime-probe.js +26 -0
- package/dist/runtime-startup-gate.js +2 -0
- package/dist/runtimes/codex-app-server-runner.js +10 -0
- package/dist/runtimes/hermes-models.js +117 -0
- package/dist/runtimes/hermes.js +6 -0
- package/dist/runtimes/kimi-acp-runner.js +116 -36
- package/dist/runtimes/opencode-runner.js +122 -0
- package/dist/runtimes/opencode.js +181 -0
- package/dist/serve.js +36 -16
- package/dist/slog.js +21 -3
- package/dist/supervised-runtime.js +22 -1
- package/dist/workspace.js +34 -3
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -8,7 +8,8 @@ import { randomUUID } from "node:crypto";
|
|
|
8
8
|
import { initSlog, dslog, setSlogDefaults, drainSpool, flushSlog } from "./slog.js";
|
|
9
9
|
import { mergeRunAgentResults, reportScheduledStartFailure, runAgent } from "./runner.js";
|
|
10
10
|
import { buildOriginDecisionRetryPrompt, buildScheduledPrompt } from "./prompt.js";
|
|
11
|
-
import { collectMachineHello, daemonCapabilities, EXECUTION_PROTOCOL, } from "./machine-info.js";
|
|
11
|
+
import { collectMachineHello, cliVersion, daemonVersion, daemonCapabilities, detectExecutionRuntimesWithSignal, EXECUTION_PROTOCOL, } from "./machine-info.js";
|
|
12
|
+
import { conservativeExecutionRuntimes, createRuntimeProbeCoordinator, } from "./runtime-probe.js";
|
|
12
13
|
import { listWorkspace, readWorkspaceFile } from "./workspace-fs.js";
|
|
13
14
|
import { listSkills } from "./skills.js";
|
|
14
15
|
import { inspectRaftWorkspace, importRaftWorkspace } from "./workspace-import.js";
|
|
@@ -31,9 +32,9 @@ import { createCompletionRetransmitter } from "./completion-retransmitter.js";
|
|
|
31
32
|
import { createAgentMemoryBridge } from "./agent-memory/bridge.js";
|
|
32
33
|
import { createRuntimeStartupGate } from "./runtime-startup-gate.js";
|
|
33
34
|
import { createHostExecutionCoordinator, hostCoordinatedSlotManager, hostCoordinatedStartupGate, } from "./host-execution-coordinator.js";
|
|
34
|
-
import { detectDaemonUpdateEligibility, } from "./daemon-update-eligibility.js";
|
|
35
|
+
import { detectDaemonUpdateEligibility, managedDaemonCapabilities, } from "./daemon-update-eligibility.js";
|
|
35
36
|
import { createDaemonUpdateController } from "./daemon-update-controller.js";
|
|
36
|
-
import { installExactDaemonUpdate } from "./daemon-updater.js";
|
|
37
|
+
import { installExactDaemonUpdate, prepareDaemonRestart } from "./daemon-updater.js";
|
|
37
38
|
import { scheduleServiceRestart } from "./computer-service.js";
|
|
38
39
|
import { createProjectRegistry } from "./project-skills/registry.js";
|
|
39
40
|
import { createProjectSkillsController, } from "./project-skills/controller.js";
|
|
@@ -79,6 +80,7 @@ export function serve(config, opts = {}) {
|
|
|
79
80
|
const executeProtocol = opts.execution?.runExecution ?? runExecution;
|
|
80
81
|
let detectedExecutionRuntimes = [];
|
|
81
82
|
let runtimeFacts = null;
|
|
83
|
+
const runtimeProbe = createRuntimeProbeCoordinator();
|
|
82
84
|
const hostCoordinator = opts.execution?.hostCoordinator ?? createHostExecutionCoordinator();
|
|
83
85
|
const localSlots = createSharedSlotManager(config.executionLimits);
|
|
84
86
|
const sharedSlots = hostCoordinatedSlotManager(localSlots, hostCoordinator);
|
|
@@ -98,6 +100,10 @@ export function serve(config, opts = {}) {
|
|
|
98
100
|
localSlots,
|
|
99
101
|
hostCoordinator,
|
|
100
102
|
})),
|
|
103
|
+
prepareRestart: opts.update?.prepareRestart ?? (() => prepareDaemonRestart({
|
|
104
|
+
localSlots,
|
|
105
|
+
hostCoordinator,
|
|
106
|
+
})),
|
|
101
107
|
scheduleRestart: opts.update?.scheduleRestart ?? scheduleServiceRestart,
|
|
102
108
|
sendStatus: (frame) => {
|
|
103
109
|
try {
|
|
@@ -280,7 +286,7 @@ export function serve(config, opts = {}) {
|
|
|
280
286
|
reservation.release();
|
|
281
287
|
};
|
|
282
288
|
let connectedAt = 0; // 本次 WS 连接建立时刻(断开日志算在线时长用)
|
|
283
|
-
initSlog(config.serverUrl, config.machineToken);
|
|
289
|
+
initSlog(config.serverUrl, config.machineToken, { daemonVersion: daemonVersion(), cliVersion: cliVersion(), ...(opts.profileName === undefined ? {} : { profileName: opts.profileName }), agentsRoot: config.agentsRoot });
|
|
284
290
|
dslog("daemon.start", "daemon 常驻模式启动", { server_url: config.serverUrl, runtime: config.runtimeBin });
|
|
285
291
|
// 并行调度:同一 agent 可并行处理多个【不同任务】(线程/频道),每任务隔离 cwd+work-log。
|
|
286
292
|
// scheduled 重复 run 仍由 running 去重;普通同线程 wake 用 legacyTaskTails 串成 FIFO。
|
|
@@ -303,17 +309,28 @@ export function serve(config, opts = {}) {
|
|
|
303
309
|
// 连上了才有机会把离线期间(断连原因/退出前)落盘的日志补传上去
|
|
304
310
|
void drainSpool();
|
|
305
311
|
// 上报本机信息 (hostname/os/daemon 版本/已装 runtimes)
|
|
312
|
+
const { detectInstalled, detectExecutable = detectExecutionRuntimesWithSignal } = opts.machineInfo ?? {};
|
|
306
313
|
const helloPromise = collectMachineHello(config.agentsRoot, config.executionLimits, process.platform, {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
catch {
|
|
312
|
-
return [];
|
|
313
|
-
}
|
|
314
|
-
},
|
|
314
|
+
...(detectInstalled ? { detectInstalled } : {}),
|
|
315
|
+
// First hello must not wait for third-party handshakes; optional transports arrive in the refresh.
|
|
316
|
+
detectExecutable: async (installed) => conservativeExecutionRuntimes(installed),
|
|
317
|
+
additionalCapabilities: () => managedDaemonCapabilities(updateEligibility),
|
|
315
318
|
});
|
|
316
|
-
runtimeFacts = helloPromise
|
|
319
|
+
runtimeFacts = helloPromise
|
|
320
|
+
.then(async (hello) => {
|
|
321
|
+
if (hello.executionProtocol === undefined)
|
|
322
|
+
return [];
|
|
323
|
+
const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable);
|
|
324
|
+
if (ws === openedSocket && openedSocket.readyState === WebSocket.OPEN
|
|
325
|
+
&& detected.some((runtime) => !hello.executionRuntimes.includes(runtime))) {
|
|
326
|
+
detectedExecutionRuntimes = detected;
|
|
327
|
+
latestMachineHello = { ...hello, executionRuntimes: [...detected] };
|
|
328
|
+
latestHelloSocket = openedSocket;
|
|
329
|
+
sendEffectiveMachineHello();
|
|
330
|
+
}
|
|
331
|
+
return detected;
|
|
332
|
+
})
|
|
333
|
+
.catch((error) => {
|
|
317
334
|
dslog("execution.runtime_detection_failed", "runtime 探测失败", {
|
|
318
335
|
level: "ERROR", error_message: error.message,
|
|
319
336
|
});
|
|
@@ -370,9 +387,9 @@ export function serve(config, opts = {}) {
|
|
|
370
387
|
if (typeof decoded !== "object" || decoded === null)
|
|
371
388
|
return;
|
|
372
389
|
const rawType = "type" in decoded && typeof decoded.type === "string" ? decoded.type : "";
|
|
373
|
-
if (rawType === "daemon:update") {
|
|
390
|
+
if (rawType === "daemon:update" || rawType === "daemon:restart") {
|
|
374
391
|
void updateController.handle(decoded).catch((error) => {
|
|
375
|
-
dslog("daemon.
|
|
392
|
+
dslog("daemon.control_failed", "daemon 控制操作处理失败", {
|
|
376
393
|
level: "ERROR", error_message: error.message,
|
|
377
394
|
});
|
|
378
395
|
});
|
|
@@ -552,7 +569,9 @@ export function serve(config, opts = {}) {
|
|
|
552
569
|
let availableRuntimes;
|
|
553
570
|
try {
|
|
554
571
|
availableRuntimes = opts.execution?.availableRuntimes?.()
|
|
555
|
-
??
|
|
572
|
+
?? (detectedExecutionRuntimes.includes(spec.runtime.name)
|
|
573
|
+
? detectedExecutionRuntimes
|
|
574
|
+
: await (runtimeFacts ?? Promise.resolve(detectedExecutionRuntimes)));
|
|
556
575
|
}
|
|
557
576
|
catch (error) {
|
|
558
577
|
cleanupExecutionReservation();
|
|
@@ -1111,6 +1130,7 @@ export function serve(config, opts = {}) {
|
|
|
1111
1130
|
return stopPromise;
|
|
1112
1131
|
stopPromise = (async () => {
|
|
1113
1132
|
stopped = true;
|
|
1133
|
+
runtimeProbe.stop();
|
|
1114
1134
|
completionRetransmitter.stop();
|
|
1115
1135
|
const deadline = createShutdownDeadline(shutdownTimeoutMs);
|
|
1116
1136
|
if (reconnectTimer !== null) {
|
package/dist/slog.js
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { hostname } from "node:os";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
|
-
import { join } from "node:path";
|
|
15
|
+
import { join, resolve } from "node:path";
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
16
17
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
17
18
|
const FLUSH_INTERVAL_MS = 1500;
|
|
18
19
|
const BATCH_SIZE = 50;
|
|
@@ -30,11 +31,28 @@ function spoolDir() {
|
|
|
30
31
|
return process.env.CREW_SLS_SPOOL_DIR ?? join(homedir(), ".crew", "logs", "sls-spool");
|
|
31
32
|
}
|
|
32
33
|
/** serve/run 启动时调用一次;disabled(CREW_SLS_LOG=off)则保持 no-op。 */
|
|
33
|
-
export function initSlog(serverUrl, machineToken) {
|
|
34
|
+
export function initSlog(serverUrl, machineToken, identity = {}) {
|
|
34
35
|
if (process.env.CREW_SLS_LOG === "off" || process.env.CREW_SLS_LOG === "0")
|
|
35
36
|
return;
|
|
36
37
|
cfg = { serverUrl: serverUrl.replace(/\/+$/, ""), token: machineToken };
|
|
37
|
-
|
|
38
|
+
let serverHost;
|
|
39
|
+
try {
|
|
40
|
+
serverHost = new URL(serverUrl).hostname || undefined;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
serverHost = undefined;
|
|
44
|
+
}
|
|
45
|
+
defaults = {
|
|
46
|
+
host: hostname(),
|
|
47
|
+
pid: process.pid,
|
|
48
|
+
daemon_version: identity.daemonVersion,
|
|
49
|
+
cli_version: identity.cliVersion,
|
|
50
|
+
daemon_profile: identity.profileName,
|
|
51
|
+
server_host: serverHost,
|
|
52
|
+
agents_root_fingerprint: identity.agentsRoot === undefined
|
|
53
|
+
? undefined
|
|
54
|
+
: createHash("sha256").update(resolve(identity.agentsRoot), "utf8").digest("hex"),
|
|
55
|
+
};
|
|
38
56
|
// 退出兜底:残余队列同步落 spool(exit 回调只能做同步工作,append 正合适)
|
|
39
57
|
if (!exitHookInstalled) {
|
|
40
58
|
exitHookInstalled = true;
|
|
@@ -56,15 +56,36 @@ export function supervisorLaunch(request) {
|
|
|
56
56
|
}),
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
+
if (request.runtime === "opencode") {
|
|
60
|
+
if (request.effectivePermission !== "full_access") {
|
|
61
|
+
throw new Error(`OpenCode cannot enforce ${request.effectivePermission} permission`);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
command: process.execPath,
|
|
65
|
+
args: [
|
|
66
|
+
fileURLToPath(new URL("./runtimes/opencode-runner.js", import.meta.url)),
|
|
67
|
+
"--bin", request.bin,
|
|
68
|
+
...(request.model === undefined ? [] : ["--model", request.model]),
|
|
69
|
+
...(request.reasoning === undefined ? [] : ["--reasoning", request.reasoning]),
|
|
70
|
+
...(request.sessionId === undefined ? [] : ["--session", request.sessionId]),
|
|
71
|
+
],
|
|
72
|
+
cwd: request.cwd,
|
|
73
|
+
env: { ...request.env, PWD: request.cwd },
|
|
74
|
+
stdinText: `${request.systemPrompt}\n\n${request.wakePrompt}`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
59
77
|
if (request.effectivePermission !== "full_access") {
|
|
60
|
-
|
|
78
|
+
const displayName = request.runtime === "hermes" ? "Hermes" : "Kimi";
|
|
79
|
+
throw new Error(`${displayName} ACP cannot enforce ${request.effectivePermission} permission`);
|
|
61
80
|
}
|
|
62
81
|
return {
|
|
63
82
|
command: process.execPath,
|
|
64
83
|
args: [
|
|
65
84
|
fileURLToPath(new URL("./runtimes/kimi-acp-runner.js", import.meta.url)),
|
|
85
|
+
"--provider", request.runtime,
|
|
66
86
|
"--bin", request.bin,
|
|
67
87
|
...(request.model === undefined ? [] : ["--model", request.model]),
|
|
88
|
+
...(request.reasoning === undefined ? [] : ["--reasoning", request.reasoning]),
|
|
68
89
|
...(request.sessionId === undefined ? [] : ["--session", request.sessionId]),
|
|
69
90
|
...(request.resume ? ["--resume"] : []),
|
|
70
91
|
],
|
package/dist/workspace.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { mkdir, writeFile, readFile, chmod, access } from "node:fs/promises";
|
|
11
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
+
import { dslog } from "./slog.js";
|
|
13
14
|
/** 文件系统安全的 taskKey:仅留 [\w.-],其余转 _,截断,避免路径穿越/超长。 */
|
|
14
15
|
export function safeKey(key) {
|
|
15
16
|
return key.replace(/[^\w.-]+/g, "_").replace(/^[-.]+/, "").slice(0, 80) || "default";
|
|
@@ -38,10 +39,24 @@ export async function prepareWorkspace(input) {
|
|
|
38
39
|
await mkdir(crewDir, { recursive: true });
|
|
39
40
|
// MEMORY.md:首次创建"索引 + Active Context"骨架,之后由 agent 自己维护
|
|
40
41
|
const memoryPath = join(dir, "MEMORY.md");
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const memorySeedCreated = !(await exists(memoryPath));
|
|
43
|
+
if (memorySeedCreated) {
|
|
44
|
+
try {
|
|
45
|
+
await writeFile(memoryPath, memorySeed(input.handle, input.description), "utf8");
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
logMemoryPrepareFailure(input, "memory_seed_write", error);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let memory;
|
|
53
|
+
try {
|
|
54
|
+
memory = await readFile(memoryPath, "utf8");
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
logMemoryPrepareFailure(input, "memory_read", error);
|
|
58
|
+
throw error;
|
|
43
59
|
}
|
|
44
|
-
const memory = await readFile(memoryPath, "utf8");
|
|
45
60
|
// 系统提示词按 execution 隔离;省略 identity 的兼容调用每次使用随机路径。
|
|
46
61
|
const promptDir = join(crewDir, "prompts");
|
|
47
62
|
await mkdir(promptDir, { recursive: true });
|
|
@@ -93,6 +108,7 @@ export async function prepareWorkspace(input) {
|
|
|
93
108
|
crewDir,
|
|
94
109
|
systemPromptPath,
|
|
95
110
|
memory,
|
|
111
|
+
memorySeedCreated,
|
|
96
112
|
homeDir,
|
|
97
113
|
runDir,
|
|
98
114
|
workLogPath,
|
|
@@ -102,6 +118,21 @@ export async function prepareWorkspace(input) {
|
|
|
102
118
|
sessionDir,
|
|
103
119
|
};
|
|
104
120
|
}
|
|
121
|
+
function logMemoryPrepareFailure(input, phase, error) {
|
|
122
|
+
try {
|
|
123
|
+
dslog("local_memory.context_prepare_failed", "本地记忆上下文准备失败", {
|
|
124
|
+
level: "WARN",
|
|
125
|
+
execution_id: input.executionId,
|
|
126
|
+
agent_handle: input.handle,
|
|
127
|
+
failure_phase: phase,
|
|
128
|
+
error_name: error instanceof Error ? error.name : "unknown",
|
|
129
|
+
error_code: error?.code,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Diagnostics cannot replace the original workspace error.
|
|
134
|
+
}
|
|
135
|
+
}
|
|
105
136
|
/**
|
|
106
137
|
* 冷启动时轮换会话 id:写入新 uuid 到 <runDir>/.session 并返回。
|
|
107
138
|
* 已有会话但本轮决定不续用(warm 窗口过期 / CREW_RESUME=off)时调用——起一个全新会话,
|