@nowcrew/daemon 0.6.96 → 0.6.97-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cloud-report-upload.js +1 -10
- package/dist/execution-journal.js +3 -1
- package/dist/execution-protocol.js +1 -1
- package/dist/execution-runner.js +4 -1
- package/dist/local-execution-prompt.js +8 -3
- package/dist/local-executor.js +63 -39
- package/dist/local-memory/commit-telemetry.js +30 -0
- package/dist/local-memory/execution-workspace.js +62 -0
- package/dist/local-memory/full-index-search.js +47 -0
- package/dist/local-memory/index-budget.js +9 -3
- package/dist/local-memory/initialization-identity.js +36 -0
- package/dist/local-memory/inventory-cli.js +49 -8
- package/dist/local-memory/inventory-source.js +15 -8
- package/dist/local-memory/inventory-v2.js +361 -0
- package/dist/local-memory/legacy-adapter.js +233 -0
- package/dist/local-memory/migration-access.js +242 -0
- package/dist/local-memory/migration-cli.js +54 -0
- package/dist/local-memory/migration-io.js +199 -0
- package/dist/local-memory/migration-pending.js +9 -0
- package/dist/local-memory/migration-plan.js +317 -0
- package/dist/local-memory/migration-schema.js +55 -0
- package/dist/local-memory/migration-state-invariants.js +65 -0
- package/dist/local-memory/migration-store.js +414 -0
- package/dist/local-memory/migration-tool-entry.js +21 -0
- package/dist/local-memory/pending-query.js +5 -2
- package/dist/local-memory/pending-schema.js +1 -1
- package/dist/local-memory/pending.js +16 -2
- package/dist/local-memory/prompt.js +21 -5
- package/dist/local-memory/read-cli.js +98 -76
- package/dist/local-memory/rollout.js +79 -11
- package/dist/local-memory/runtime.js +181 -13
- package/dist/local-memory/storage-format.js +152 -0
- package/dist/local-memory/write.js +189 -21
- package/dist/local-memory/writer-mode.js +13 -0
- package/dist/local-memory-diagnostics.js +94 -13
- package/dist/local-memory-telemetry.js +19 -2
- package/dist/machine-info.js +3 -0
- package/dist/marketplace-protocol.js +1 -1
- package/dist/marketplace-remote.js +1 -1
- package/dist/marketplace-runner.js +1 -1
- package/dist/memory-migration-controller.js +86 -0
- package/dist/memory-migration-inspect.js +108 -0
- package/dist/memory-migration-journal.js +14 -0
- package/dist/memory-migration-protocol.js +14 -0
- package/dist/memory-migration-runtime.js +305 -0
- package/dist/memory-prune-diagnostics.js +60 -4
- package/dist/message-file-preview.js +11 -1
- package/dist/prompt.js +6 -2
- package/dist/runner.js +1 -1
- package/dist/runtimes/claude-thread-runner.js +25 -4
- package/dist/serve.js +38 -3
- package/dist/token.js +1 -0
- package/dist/workspace-control-handler.js +16 -3
- package/dist/workspace.js +13 -2
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -14,6 +14,7 @@ import { reconcileExecutionJournal } from "./execution-recovery.js";
|
|
|
14
14
|
import { initReleaseSlog, reportDaemonConnectionClose, reportReleaseError } from "./release-channel-guard.js";
|
|
15
15
|
import { handleWorkspaceControlRequest } from "./workspace-control-handler.js";
|
|
16
16
|
import { inspectRaftWorkspace, importRaftWorkspace } from "./workspace-import.js";
|
|
17
|
+
import { withMemoryAccess } from "./local-memory/migration-access.js";
|
|
17
18
|
import { normalizeScheduledContext } from "./scheduled-report.js";
|
|
18
19
|
import { formatDaemonLogLine } from "./log-format.js";
|
|
19
20
|
import { reportAgentRunComplete } from "./scheduled-run-report.js";
|
|
@@ -50,6 +51,8 @@ import { probeRuntimeHealth } from "./runtime-health.js";
|
|
|
50
51
|
import { createServeMarketplace } from "./marketplace-serve.js";
|
|
51
52
|
import { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
52
53
|
import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
|
|
54
|
+
import { createMemoryMigrationController } from "./memory-migration-controller.js";
|
|
55
|
+
import { createMemoryMigrationJournal, replayExecutionJournals } from "./memory-migration-journal.js";
|
|
53
56
|
import { bindStorageServerIdentity, createStorageIdentityAdmission, } from "./storage-serve-adapter.js";
|
|
54
57
|
export { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
55
58
|
// normalize.ts 的活动种类 → activity 枚举
|
|
@@ -75,6 +78,8 @@ export function serve(config, opts = {}) {
|
|
|
75
78
|
let reconnectTimer = null;
|
|
76
79
|
let stopPromise = null;
|
|
77
80
|
let executionJournal;
|
|
81
|
+
let migrationExecutionJournal;
|
|
82
|
+
let migrationJournalReady = false;
|
|
78
83
|
const injectedExecutionJournal = opts.execution?.journal;
|
|
79
84
|
const agentMemory = opts.execution?.dependencies?.agentMemory
|
|
80
85
|
?? (config.agentMemory === null ? undefined : createAgentMemoryBridge(config.agentMemory));
|
|
@@ -89,6 +94,15 @@ export function serve(config, opts = {}) {
|
|
|
89
94
|
startupGapMs: config.executionLimits.startupGapMs,
|
|
90
95
|
});
|
|
91
96
|
const localSlots = createSharedSlotManager(config.executionLimits);
|
|
97
|
+
const memoryMigrations = createMemoryMigrationController({ config, coordinator: hostCoordinator,
|
|
98
|
+
isAdmissionClosed: () => stopped || storageIdentityAdmission.isAdmissionClosed(),
|
|
99
|
+
journal: () => { if (!migrationJournalReady)
|
|
100
|
+
throw new Error("memory_migration_journal_unavailable"); return migrationExecutionJournal; }, send: frame => {
|
|
101
|
+
if (frame.type === "execution:completed")
|
|
102
|
+
completionRetransmitter.track(frame);
|
|
103
|
+
else if (ws?.readyState === WebSocket.OPEN)
|
|
104
|
+
ws.send(JSON.stringify(frame));
|
|
105
|
+
} });
|
|
92
106
|
const sharedSlots = hostCoordinatedSlotManager(localSlots, hostCoordinator);
|
|
93
107
|
const concurrencyTelemetry = createExecutionConcurrencyTelemetry(sharedSlots, {
|
|
94
108
|
...(opts.execution?.concurrencySnapshotIntervalMs === undefined
|
|
@@ -270,7 +284,7 @@ export function serve(config, opts = {}) {
|
|
|
270
284
|
return null;
|
|
271
285
|
};
|
|
272
286
|
const sendSnapshot = async (reqId) => {
|
|
273
|
-
const entries = (await executionJournal
|
|
287
|
+
const entries = (await replayExecutionJournals(executionJournal, migrationExecutionJournal)).map(snapshotEntry).filter((entry) => entry !== null);
|
|
274
288
|
safeExecutionSend(ExecutionSnapshotSchema.parse({
|
|
275
289
|
type: "execution:snapshot", protocolVersion: 1, reqId, entries,
|
|
276
290
|
}));
|
|
@@ -352,6 +366,12 @@ export function serve(config, opts = {}) {
|
|
|
352
366
|
machineRuntime.open(openedSocket, { capabilities: capabilities.machineHello, beforePublish: Promise.resolve(), connectionId });
|
|
353
367
|
opts.onOpen?.(ws);
|
|
354
368
|
void sendSnapshot(`reconnect-${randomUUID()}`);
|
|
369
|
+
// 普通回执不等待维护日志IO;两类记录分别重放到同一发送器。
|
|
370
|
+
void migrationExecutionJournal.replay().then(entries => {
|
|
371
|
+
for (const entry of entries)
|
|
372
|
+
if (entry.completion && !entry.completionAcknowledged)
|
|
373
|
+
completionRetransmitter.track(entry.completion);
|
|
374
|
+
}).catch(() => dslog("memory_migration.journal_unavailable", "维护回执重放失败,普通回执继续", { level: "ERROR" }));
|
|
355
375
|
void executionJournal.replay()
|
|
356
376
|
.then((entries) => {
|
|
357
377
|
for (const entry of entries) {
|
|
@@ -431,7 +451,8 @@ export function serve(config, opts = {}) {
|
|
|
431
451
|
return;
|
|
432
452
|
if (frame.type === "execution:completion-ack") {
|
|
433
453
|
try {
|
|
434
|
-
await executionJournal.
|
|
454
|
+
const journal = await executionJournal.get(frame.executionId) ? executionJournal : migrationExecutionJournal;
|
|
455
|
+
await journal.acknowledgeCompletion(frame.executionId);
|
|
435
456
|
const attempts = completionRetransmitter.acknowledge(frame.executionId);
|
|
436
457
|
dslog("execution.completion_acknowledged", "execution completion ACK 已持久化", {
|
|
437
458
|
execution_id: frame.executionId,
|
|
@@ -457,6 +478,8 @@ export function serve(config, opts = {}) {
|
|
|
457
478
|
return;
|
|
458
479
|
}
|
|
459
480
|
if (frame.type === "execution:cancel") {
|
|
481
|
+
if (memoryMigrations.cancel(frame.executionId))
|
|
482
|
+
return;
|
|
460
483
|
if (!knownExecutionHashes.has(frame.executionId)
|
|
461
484
|
&& !executionRuns.has(frame.executionId)
|
|
462
485
|
&& await executionJournal.get(frame.executionId) === null)
|
|
@@ -689,6 +712,7 @@ export function serve(config, opts = {}) {
|
|
|
689
712
|
const execution = executeProtocol(config, spec, {
|
|
690
713
|
...opts.execution?.dependencies,
|
|
691
714
|
machineEnvironment: machineRuntime.provider,
|
|
715
|
+
...(memoryMigrations.getIdentity() === null ? {} : { controlIdentity: memoryMigrations.getIdentity() }),
|
|
692
716
|
capabilities: capabilities.executionRunner,
|
|
693
717
|
projectSkills: opts.execution?.dependencies?.projectSkills ?? initializedProjectSkills,
|
|
694
718
|
abilityRelease: opts.execution?.dependencies?.abilityRelease ?? agentAbilityRuntime.materializer,
|
|
@@ -779,6 +803,8 @@ export function serve(config, opts = {}) {
|
|
|
779
803
|
// ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
|
|
780
804
|
const r = msg;
|
|
781
805
|
setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
|
|
806
|
+
if (r.workspaceId && r.machineId)
|
|
807
|
+
memoryMigrations.setIdentity({ workspaceId: r.workspaceId, machineId: r.machineId });
|
|
782
808
|
// 旧 server 无此字段 → 空集合(daemon 保持全部旧行为)。ackSupported() 依赖它判断是否重试,
|
|
783
809
|
// 必须在任何 republish() 之前就位;否则本轮 republish 看到空集合→不排重试→整套 ack/重连机制哑火。
|
|
784
810
|
serverCapabilities = new Set(Array.isArray(r.serverCapabilities)
|
|
@@ -847,6 +873,8 @@ export function serve(config, opts = {}) {
|
|
|
847
873
|
}
|
|
848
874
|
catch { /* server 会按请求超时处理 */ } }))
|
|
849
875
|
return;
|
|
876
|
+
if (await memoryMigrations.handle(msg))
|
|
877
|
+
return;
|
|
850
878
|
if (await agentAbilityRuntime.tryHandleControlMessage(msg, serverCapabilities, (frame) => ws?.send(JSON.stringify(frame))))
|
|
851
879
|
return;
|
|
852
880
|
// 导入 raft agent 工作区:inspect 反填 name/description;import 复制用户内容
|
|
@@ -861,7 +889,7 @@ export function serve(config, opts = {}) {
|
|
|
861
889
|
try {
|
|
862
890
|
const data2 = req.type === "raft:inspect"
|
|
863
891
|
? await inspectRaftWorkspace(req.path)
|
|
864
|
-
: await importRaftWorkspace(req.path, join(config.agentsRoot, req.handle));
|
|
892
|
+
: await withMemoryAccess(join(config.agentsRoot, req.handle), () => importRaftWorkspace(req.path, join(config.agentsRoot, req.handle)), { create: true });
|
|
865
893
|
if (req.type === "raft:import") {
|
|
866
894
|
log(`📦 已导入 raft 工作区 → ${req.handle}: 复制 ${data2.copied.length} 项`);
|
|
867
895
|
}
|
|
@@ -1254,6 +1282,7 @@ export function serve(config, opts = {}) {
|
|
|
1254
1282
|
return;
|
|
1255
1283
|
// server 能力随连接失效;下个连接的 ready 帧重新声明(防降级重连后沿用过期能力)。
|
|
1256
1284
|
serverCapabilities = new Set();
|
|
1285
|
+
memoryMigrations.clearIdentity();
|
|
1257
1286
|
completionRetransmitter.pause();
|
|
1258
1287
|
// 4001 = 控制面应用级「鉴权失败」关闭码(见 server control-plane.ts)。即使上面的
|
|
1259
1288
|
// error 帧因 close 抢先而丢失,也能据关闭码识别这是凭证失效——退避拉满,不再每秒热循环。
|
|
@@ -1306,6 +1335,9 @@ export function serve(config, opts = {}) {
|
|
|
1306
1335
|
writeStderr: (line) => process.stderr.write(line),
|
|
1307
1336
|
});
|
|
1308
1337
|
await runProjectSkillStartupCleanup(config.agentsRoot, executionJournal, dslog);
|
|
1338
|
+
migrationExecutionJournal = createMemoryMigrationJournal(config.agentsRoot);
|
|
1339
|
+
await migrationExecutionJournal.reconcileAfterRestart().then(() => { migrationJournalReady = true; })
|
|
1340
|
+
.catch(() => dslog("memory_migration.journal_unavailable", "迁移日志恢复失败,普通执行继续,迁移请求单独报错", { level: "ERROR" }));
|
|
1309
1341
|
await connect();
|
|
1310
1342
|
})();
|
|
1311
1343
|
const stop = () => {
|
|
@@ -1323,6 +1355,7 @@ export function serve(config, opts = {}) {
|
|
|
1323
1355
|
const webSocketClosed = closeWebSocketWithinDeadline(ws, deadline.signal);
|
|
1324
1356
|
const pending = [
|
|
1325
1357
|
machineRuntime.stop(),
|
|
1358
|
+
memoryMigrations.stop(),
|
|
1326
1359
|
webSocketClosed,
|
|
1327
1360
|
updateController.drain(),
|
|
1328
1361
|
marketplace.stop(),
|
|
@@ -1341,6 +1374,8 @@ export function serve(config, opts = {}) {
|
|
|
1341
1374
|
if (executionJournal !== undefined) {
|
|
1342
1375
|
await executionJournal.close({ signal: deadline.signal });
|
|
1343
1376
|
}
|
|
1377
|
+
if (migrationExecutionJournal !== undefined)
|
|
1378
|
+
await migrationExecutionJournal.close({ signal: deadline.signal });
|
|
1344
1379
|
}
|
|
1345
1380
|
finally {
|
|
1346
1381
|
deadline.dispose();
|
package/dist/token.js
CHANGED
|
@@ -7,6 +7,7 @@ export async function mintAgentToken(serverUrl, machineToken, handle, displayNam
|
|
|
7
7
|
},
|
|
8
8
|
// 把本轮唤醒的线程根钉到 per-launch 令牌:服务端据此让 task/消息默认绑/回当前线程。
|
|
9
9
|
body: JSON.stringify({
|
|
10
|
+
memoryStateGuardVersion: 1,
|
|
10
11
|
handle,
|
|
11
12
|
...(displayName ? { displayName } : {}),
|
|
12
13
|
...(options.wakeThreadRoot ? { wakeThreadRoot: options.wakeThreadRoot } : {}),
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
/** 控制面文件/技能查看的按需处理,独立于执行生命周期。 */
|
|
2
|
-
import {
|
|
2
|
+
import { lstat } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
3
4
|
import { readSharedMessageFile } from "./message-file-preview.js";
|
|
4
5
|
import { listWorkspace, readWorkspaceFile } from "./workspace-fs.js";
|
|
5
6
|
import { listSkills } from "./skills.js";
|
|
6
7
|
import { listRuntimeModels } from "./list-models.js";
|
|
8
|
+
import { withMemoryAccess } from "./local-memory/migration-access.js";
|
|
9
|
+
async function listProtectedWorkspace(root, path) {
|
|
10
|
+
try {
|
|
11
|
+
await lstat(root);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (error.code !== "ENOENT")
|
|
15
|
+
throw error;
|
|
16
|
+
return { root: resolve(root), path: path.replace(/^\/+/, ""), entries: [] };
|
|
17
|
+
}
|
|
18
|
+
return withMemoryAccess(root, () => listWorkspace(root, path));
|
|
19
|
+
}
|
|
7
20
|
export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, models = listRuntimeModels) {
|
|
8
21
|
if (!raw || typeof raw !== "object")
|
|
9
22
|
return null;
|
|
@@ -15,8 +28,8 @@ export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, m
|
|
|
15
28
|
try {
|
|
16
29
|
const root = join(agentsRoot, request.handle);
|
|
17
30
|
const data = request.type === "fs:read-shared" ? await readSharedMessageFile(agentsRoot, raw, registry)
|
|
18
|
-
: request.type === "fs:list" ? await
|
|
19
|
-
: request.type === "fs:read" ? await readWorkspaceFile(root, request.path)
|
|
31
|
+
: request.type === "fs:list" ? await listProtectedWorkspace(root, request.path)
|
|
32
|
+
: request.type === "fs:read" ? await withMemoryAccess(root, () => readWorkspaceFile(root, request.path))
|
|
20
33
|
: request.type === "skills:list" ? await listSkills(request.runtime)
|
|
21
34
|
: { models: await models(request.runtime ?? "") };
|
|
22
35
|
return { type: "fs:result", reqId: request.reqId, ok: true, data };
|
package/dist/workspace.js
CHANGED
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
*
|
|
9
9
|
* spawn runtime 时把 <ws>/.crew 置于 PATH 最前,于是 agent 的 `crew ...` 命中 wrapper。
|
|
10
10
|
*/
|
|
11
|
-
import { mkdir, writeFile, readFile, chmod, access, lstat } from "node:fs/promises";
|
|
11
|
+
import { mkdir, writeFile, readFile, chmod, access, lstat, realpath } from "node:fs/promises";
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15
15
|
import { createRequire } from "node:module";
|
|
16
16
|
import { dslog } from "./slog.js";
|
|
17
|
+
import { MigrationStateSchema } from "./local-memory/migration-schema.js";
|
|
18
|
+
import { assertMigrationStateIntegrity } from "./local-memory/migration-state-invariants.js";
|
|
19
|
+
import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
|
|
17
20
|
/** 文件系统安全的 taskKey:仅留 [\w.-],其余转 _,截断,避免路径穿越/超长。 */
|
|
18
21
|
export function safeKey(key) {
|
|
19
22
|
return key.replace(/[^\w.-]+/g, "_").replace(/^[-.]+/, "").slice(0, 80) || "default";
|
|
@@ -49,7 +52,15 @@ async function hasLocalMemoryV2Marker(agentDir) {
|
|
|
49
52
|
const stat = await lstat(markerPath);
|
|
50
53
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
51
54
|
return false;
|
|
52
|
-
|
|
55
|
+
if ((await readFile(markerPath, "utf8")) !== "1\n")
|
|
56
|
+
return false;
|
|
57
|
+
const root = await realpath(agentDir);
|
|
58
|
+
const state = MigrationStateSchema.parse(JSON.parse(await readFile(join(memoryDir, "migration.v1.json"), "utf8")));
|
|
59
|
+
assertMigrationStateIntegrity(state, state.identity);
|
|
60
|
+
return state.phase === "committed" && state.storageVersion === 2
|
|
61
|
+
&& state.identity.rootFingerprint === createHash("sha256").update(root).digest("hex")
|
|
62
|
+
&& state.identity.ownerIdentity === String((await lstat(root)).uid)
|
|
63
|
+
&& detectLocalMemoryStorageVersion(await readFile(join(root, "MEMORY.md"), "utf8")) === 2;
|
|
53
64
|
}
|
|
54
65
|
catch {
|
|
55
66
|
return false;
|