@nowcrew/daemon 0.6.101-beta.2 → 0.6.101-beta.4
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 +10 -1
- package/dist/computer-main.js +0 -0
- package/dist/execution-journal-lock.js +51 -2
- package/dist/execution-journal.js +1 -3
- package/dist/execution-protocol.js +1 -1
- package/dist/execution-runner.js +1 -4
- package/dist/host-execution-coordinator.js +10 -0
- package/dist/local-execution-prompt.js +3 -8
- package/dist/local-executor.js +55 -78
- package/dist/local-memory/index-budget.js +3 -9
- package/dist/local-memory/inventory-cli.js +8 -52
- package/dist/local-memory/inventory-source.js +8 -15
- package/dist/local-memory/pending-query.js +2 -5
- package/dist/local-memory/pending-schema.js +1 -1
- package/dist/local-memory/pending.js +2 -16
- package/dist/local-memory/prompt.js +5 -21
- package/dist/local-memory/read-cli.js +76 -98
- package/dist/local-memory/rollout.js +11 -79
- package/dist/local-memory/runtime.js +17 -191
- package/dist/local-memory/write.js +21 -190
- package/dist/local-memory-diagnostics.js +15 -96
- package/dist/local-memory-telemetry.js +3 -20
- package/dist/machine-info.js +3 -4
- package/dist/machine-runtime-environment.js +1 -0
- package/dist/main.js +0 -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-prune-diagnostics.js +4 -74
- package/dist/message-file-preview.js +1 -11
- package/dist/prompt.js +17 -9
- package/dist/runner.js +1 -1
- package/dist/runtime-tool-update-bridge.js +126 -0
- package/dist/runtime-tool-update-controller.js +151 -0
- package/dist/runtime-tool-update-protocol.js +24 -0
- package/dist/runtime-tool-update-serve.js +54 -0
- package/dist/runtimes/claude-thread-runner.js +4 -25
- package/dist/serve.js +12 -38
- package/dist/token.js +0 -1
- package/dist/workspace-control-handler.js +3 -16
- package/dist/workspace.js +2 -13
- package/package.json +10 -9
- package/dist/local-memory/commit-telemetry.js +0 -30
- package/dist/local-memory/execution-workspace.js +0 -62
- package/dist/local-memory/full-index-search.js +0 -47
- package/dist/local-memory/initialization-identity.js +0 -36
- package/dist/local-memory/inventory-v2.js +0 -362
- package/dist/local-memory/legacy-adapter.js +0 -233
- package/dist/local-memory/migration-access.js +0 -242
- package/dist/local-memory/migration-cli.js +0 -54
- package/dist/local-memory/migration-io.js +0 -199
- package/dist/local-memory/migration-pending.js +0 -9
- package/dist/local-memory/migration-plan.js +0 -432
- package/dist/local-memory/migration-schema.js +0 -55
- package/dist/local-memory/migration-state-invariants.js +0 -65
- package/dist/local-memory/migration-store.js +0 -414
- package/dist/local-memory/migration-tool-entry.js +0 -21
- package/dist/local-memory/release-artifact-guard.js +0 -55
- package/dist/local-memory/source-path.js +0 -13
- package/dist/local-memory/storage-format.js +0 -249
- package/dist/local-memory/writer-mode.js +0 -13
- package/dist/memory-migration-controller.js +0 -89
- package/dist/memory-migration-inspect.js +0 -116
- package/dist/memory-migration-journal.js +0 -14
- package/dist/memory-migration-protocol.js +0 -14
- package/dist/memory-migration-runtime.js +0 -305
|
@@ -12,14 +12,6 @@ const InputSchema = z.object({ bin: z.string().min(1), args: z.array(z.string())
|
|
|
12
12
|
wakePrompt: z.string().min(1), sessionId: z.string().min(1).optional(), resume: z.boolean() }).strict();
|
|
13
13
|
class PreflightError extends Error {
|
|
14
14
|
}
|
|
15
|
-
// Claude waits for every merged SessionStart hook before acknowledging SDK
|
|
16
|
-
// initialization. Those hooks may legitimately outlive the old 30s adapter
|
|
17
|
-
// deadline. Cap this phase below the daemon's default 120s fail-closed startup
|
|
18
|
-
// window so wrapper failure propagation and supervisor-owned tree cleanup retain
|
|
19
|
-
// margin. The 100s cap leaves a nominal 20s outside this phase; this runner's
|
|
20
|
-
// local TERM/KILL cleanup is itself bounded to 2s.
|
|
21
|
-
// Resume and cold recovery share this one budget; hook activity never extends it.
|
|
22
|
-
export const CLAUDE_INITIALIZE_TIMEOUT_MS = 100_000;
|
|
23
15
|
function hookCommand(path, nonce) {
|
|
24
16
|
const source = import.meta.url.endsWith(".ts");
|
|
25
17
|
const hook = fileURLToPath(new URL(`./claude-session-start-hook.${source ? "ts" : "js"}`, import.meta.url));
|
|
@@ -60,9 +52,6 @@ async function bounded(promise, milliseconds, label) {
|
|
|
60
52
|
clearTimeout(timer);
|
|
61
53
|
}
|
|
62
54
|
}
|
|
63
|
-
export function waitForClaudeInitialize(initialized, timeoutMs = CLAUDE_INITIALIZE_TIMEOUT_MS) {
|
|
64
|
-
return bounded(initialized, timeoutMs, "claude_initialize_timeout");
|
|
65
|
-
}
|
|
66
55
|
async function attempt(input, sessionId, resume, options) {
|
|
67
56
|
const proofDir = await mkdtemp(join(tmpdir(), "nowwork-claude-session-"));
|
|
68
57
|
const proofPath = join(proofDir, "proof.json");
|
|
@@ -199,7 +188,7 @@ async function attempt(input, sessionId, resume, options) {
|
|
|
199
188
|
try {
|
|
200
189
|
try {
|
|
201
190
|
await send({ type: "control_request", request_id: initId, request: { subtype: "initialize" } });
|
|
202
|
-
await
|
|
191
|
+
await bounded(initialized.promise, options.initializeTimeoutMs ?? 30_000, "claude_initialize_timeout");
|
|
203
192
|
const proof = JSON.parse(await readFile(proofPath, "utf8"));
|
|
204
193
|
if (proof.nonce !== nonce || proof.sessionId !== sessionId || proof.source !== (resume ? "resume" : "startup")) {
|
|
205
194
|
throw new Error("claude_session_proof_mismatch");
|
|
@@ -249,25 +238,15 @@ async function attempt(input, sessionId, resume, options) {
|
|
|
249
238
|
}
|
|
250
239
|
export async function runClaudeThread(input, options = {}) {
|
|
251
240
|
const sessionId = input.sessionId ?? randomUUID();
|
|
252
|
-
const initializeBudgetMs = options.initializeTimeoutMs ?? CLAUDE_INITIALIZE_TIMEOUT_MS;
|
|
253
|
-
const now = options.now ?? (() => performance.now());
|
|
254
|
-
const initializeDeadline = now() + Math.max(0, initializeBudgetMs);
|
|
255
|
-
const runAttempt = (attemptSessionId, resume) => {
|
|
256
|
-
const remainingMs = Math.max(0, Math.ceil(initializeDeadline - now()));
|
|
257
|
-
if (remainingMs === 0)
|
|
258
|
-
return Promise.reject(new PreflightError("claude_native_session_unconfirmed"));
|
|
259
|
-
return attempt(input, attemptSessionId, resume, { ...options, initializeTimeoutMs: remainingMs });
|
|
260
|
-
};
|
|
261
241
|
try {
|
|
262
|
-
return await
|
|
242
|
+
return await attempt(input, sessionId, input.resume, options);
|
|
263
243
|
}
|
|
264
244
|
catch (error) {
|
|
265
245
|
// The failed attempt never received a business user turn or entered its
|
|
266
|
-
// model/tool loop. Native startup hooks may run again on cold recovery
|
|
267
|
-
// within the original aggregate initialize deadline.
|
|
246
|
+
// model/tool loop. Native startup hooks may run again on cold recovery.
|
|
268
247
|
if (!input.resume || !(error instanceof PreflightError || error instanceof ThreadDeliveryColdStartRequired))
|
|
269
248
|
throw error;
|
|
270
|
-
return
|
|
249
|
+
return attempt(input, randomUUID(), false, options);
|
|
271
250
|
}
|
|
272
251
|
}
|
|
273
252
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
package/dist/serve.js
CHANGED
|
@@ -14,7 +14,6 @@ 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";
|
|
18
17
|
import { normalizeScheduledContext } from "./scheduled-report.js";
|
|
19
18
|
import { formatDaemonLogLine } from "./log-format.js";
|
|
20
19
|
import { reportAgentRunComplete } from "./scheduled-run-report.js";
|
|
@@ -49,10 +48,9 @@ import { initializedProjectSkillsReconciler } from "./project-skills/initialized
|
|
|
49
48
|
import { parseMemoryPruneTraceId } from "./memory-prune-diagnostics.js";
|
|
50
49
|
import { probeRuntimeHealth } from "./runtime-health.js";
|
|
51
50
|
import { createServeMarketplace } from "./marketplace-serve.js";
|
|
51
|
+
import { createServeRuntimeToolUpdates } from "./runtime-tool-update-serve.js";
|
|
52
52
|
import { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
53
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";
|
|
56
54
|
import { bindStorageServerIdentity, createStorageIdentityAdmission, } from "./storage-serve-adapter.js";
|
|
57
55
|
export { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
58
56
|
// normalize.ts 的活动种类 → activity 枚举
|
|
@@ -78,8 +76,6 @@ export function serve(config, opts = {}) {
|
|
|
78
76
|
let reconnectTimer = null;
|
|
79
77
|
let stopPromise = null;
|
|
80
78
|
let executionJournal;
|
|
81
|
-
let migrationExecutionJournal;
|
|
82
|
-
let migrationJournalReady = false;
|
|
83
79
|
const injectedExecutionJournal = opts.execution?.journal;
|
|
84
80
|
const agentMemory = opts.execution?.dependencies?.agentMemory
|
|
85
81
|
?? (config.agentMemory === null ? undefined : createAgentMemoryBridge(config.agentMemory));
|
|
@@ -94,15 +90,6 @@ export function serve(config, opts = {}) {
|
|
|
94
90
|
startupGapMs: config.executionLimits.startupGapMs,
|
|
95
91
|
});
|
|
96
92
|
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
|
-
} });
|
|
106
93
|
const sharedSlots = hostCoordinatedSlotManager(localSlots, hostCoordinator);
|
|
107
94
|
const concurrencyTelemetry = createExecutionConcurrencyTelemetry(sharedSlots, {
|
|
108
95
|
...(opts.execution?.concurrencySnapshotIntervalMs === undefined
|
|
@@ -224,6 +211,7 @@ export function serve(config, opts = {}) {
|
|
|
224
211
|
onError: (code, context) => dslog("execution.runtime_detection_failed", "机器环境或 runtime 探测失败", { level: "ERROR", error_code: code, ...context }),
|
|
225
212
|
});
|
|
226
213
|
machineRuntime.prewarm();
|
|
214
|
+
const runtimeToolUpdates = createServeRuntimeToolUpdates(config, localSlots, hostCoordinator, async () => ({ ...(await machineRuntime.provider.acquire()).env }), () => machineRuntime.refresh());
|
|
227
215
|
const initializedProjectSkills = initializedProjectSkillsReconciler(() => projectSkillsController.ensureScanned(), projectSkillsReconciler);
|
|
228
216
|
const configuredCapabilities = opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform);
|
|
229
217
|
const prepareCapabilities = async () => daemonCapabilityBindings(process.platform, [...configuredCapabilities, ...await managedDaemonCapabilities(updateEligibility)]);
|
|
@@ -284,7 +272,7 @@ export function serve(config, opts = {}) {
|
|
|
284
272
|
return null;
|
|
285
273
|
};
|
|
286
274
|
const sendSnapshot = async (reqId) => {
|
|
287
|
-
const entries = (await
|
|
275
|
+
const entries = (await executionJournal.replay()).map(snapshotEntry).filter((entry) => entry !== null);
|
|
288
276
|
safeExecutionSend(ExecutionSnapshotSchema.parse({
|
|
289
277
|
type: "execution:snapshot", protocolVersion: 1, reqId, entries,
|
|
290
278
|
}));
|
|
@@ -366,12 +354,6 @@ export function serve(config, opts = {}) {
|
|
|
366
354
|
machineRuntime.open(openedSocket, { capabilities: capabilities.machineHello, beforePublish: Promise.resolve(), connectionId });
|
|
367
355
|
opts.onOpen?.(ws);
|
|
368
356
|
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" }));
|
|
375
357
|
void executionJournal.replay()
|
|
376
358
|
.then((entries) => {
|
|
377
359
|
for (const entry of entries) {
|
|
@@ -451,8 +433,7 @@ export function serve(config, opts = {}) {
|
|
|
451
433
|
return;
|
|
452
434
|
if (frame.type === "execution:completion-ack") {
|
|
453
435
|
try {
|
|
454
|
-
|
|
455
|
-
await journal.acknowledgeCompletion(frame.executionId);
|
|
436
|
+
await executionJournal.acknowledgeCompletion(frame.executionId);
|
|
456
437
|
const attempts = completionRetransmitter.acknowledge(frame.executionId);
|
|
457
438
|
dslog("execution.completion_acknowledged", "execution completion ACK 已持久化", {
|
|
458
439
|
execution_id: frame.executionId,
|
|
@@ -478,8 +459,6 @@ export function serve(config, opts = {}) {
|
|
|
478
459
|
return;
|
|
479
460
|
}
|
|
480
461
|
if (frame.type === "execution:cancel") {
|
|
481
|
-
if (memoryMigrations.cancel(frame.executionId))
|
|
482
|
-
return;
|
|
483
462
|
if (!knownExecutionHashes.has(frame.executionId)
|
|
484
463
|
&& !executionRuns.has(frame.executionId)
|
|
485
464
|
&& await executionJournal.get(frame.executionId) === null)
|
|
@@ -712,7 +691,6 @@ export function serve(config, opts = {}) {
|
|
|
712
691
|
const execution = executeProtocol(config, spec, {
|
|
713
692
|
...opts.execution?.dependencies,
|
|
714
693
|
machineEnvironment: machineRuntime.provider,
|
|
715
|
-
...(memoryMigrations.getIdentity() === null ? {} : { controlIdentity: memoryMigrations.getIdentity() }),
|
|
716
694
|
capabilities: capabilities.executionRunner,
|
|
717
695
|
projectSkills: opts.execution?.dependencies?.projectSkills ?? initializedProjectSkills,
|
|
718
696
|
abilityRelease: opts.execution?.dependencies?.abilityRelease ?? agentAbilityRuntime.materializer,
|
|
@@ -803,8 +781,6 @@ export function serve(config, opts = {}) {
|
|
|
803
781
|
// ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
|
|
804
782
|
const r = msg;
|
|
805
783
|
setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
|
|
806
|
-
if (r.workspaceId && r.machineId)
|
|
807
|
-
memoryMigrations.setIdentity({ workspaceId: r.workspaceId, machineId: r.machineId });
|
|
808
784
|
// 旧 server 无此字段 → 空集合(daemon 保持全部旧行为)。ackSupported() 依赖它判断是否重试,
|
|
809
785
|
// 必须在任何 republish() 之前就位;否则本轮 republish 看到空集合→不排重试→整套 ack/重连机制哑火。
|
|
810
786
|
serverCapabilities = new Set(Array.isArray(r.serverCapabilities)
|
|
@@ -868,13 +844,17 @@ export function serve(config, opts = {}) {
|
|
|
868
844
|
}
|
|
869
845
|
return;
|
|
870
846
|
}
|
|
847
|
+
const runtimeUpdateSocket = ws;
|
|
848
|
+
if (runtimeToolUpdates.handle(msg, frame => { try {
|
|
849
|
+
runtimeUpdateSocket?.send(JSON.stringify(frame));
|
|
850
|
+
}
|
|
851
|
+
catch { /* Server reconciles via status. */ } }))
|
|
852
|
+
return;
|
|
871
853
|
if (marketplace.handle(msg, frame => { try {
|
|
872
854
|
ws?.send(JSON.stringify(frame));
|
|
873
855
|
}
|
|
874
856
|
catch { /* server 会按请求超时处理 */ } }))
|
|
875
857
|
return;
|
|
876
|
-
if (await memoryMigrations.handle(msg))
|
|
877
|
-
return;
|
|
878
858
|
if (await agentAbilityRuntime.tryHandleControlMessage(msg, serverCapabilities, (frame) => ws?.send(JSON.stringify(frame))))
|
|
879
859
|
return;
|
|
880
860
|
// 导入 raft agent 工作区:inspect 反填 name/description;import 复制用户内容
|
|
@@ -889,7 +869,7 @@ export function serve(config, opts = {}) {
|
|
|
889
869
|
try {
|
|
890
870
|
const data2 = req.type === "raft:inspect"
|
|
891
871
|
? await inspectRaftWorkspace(req.path)
|
|
892
|
-
: await
|
|
872
|
+
: await importRaftWorkspace(req.path, join(config.agentsRoot, req.handle));
|
|
893
873
|
if (req.type === "raft:import") {
|
|
894
874
|
log(`📦 已导入 raft 工作区 → ${req.handle}: 复制 ${data2.copied.length} 项`);
|
|
895
875
|
}
|
|
@@ -1282,7 +1262,6 @@ export function serve(config, opts = {}) {
|
|
|
1282
1262
|
return;
|
|
1283
1263
|
// server 能力随连接失效;下个连接的 ready 帧重新声明(防降级重连后沿用过期能力)。
|
|
1284
1264
|
serverCapabilities = new Set();
|
|
1285
|
-
memoryMigrations.clearIdentity();
|
|
1286
1265
|
completionRetransmitter.pause();
|
|
1287
1266
|
// 4001 = 控制面应用级「鉴权失败」关闭码(见 server control-plane.ts)。即使上面的
|
|
1288
1267
|
// error 帧因 close 抢先而丢失,也能据关闭码识别这是凭证失效——退避拉满,不再每秒热循环。
|
|
@@ -1335,9 +1314,6 @@ export function serve(config, opts = {}) {
|
|
|
1335
1314
|
writeStderr: (line) => process.stderr.write(line),
|
|
1336
1315
|
});
|
|
1337
1316
|
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" }));
|
|
1341
1317
|
await connect();
|
|
1342
1318
|
})();
|
|
1343
1319
|
const stop = () => {
|
|
@@ -1355,10 +1331,10 @@ export function serve(config, opts = {}) {
|
|
|
1355
1331
|
const webSocketClosed = closeWebSocketWithinDeadline(ws, deadline.signal);
|
|
1356
1332
|
const pending = [
|
|
1357
1333
|
machineRuntime.stop(),
|
|
1358
|
-
memoryMigrations.stop(),
|
|
1359
1334
|
webSocketClosed,
|
|
1360
1335
|
updateController.drain(),
|
|
1361
1336
|
marketplace.stop(),
|
|
1337
|
+
runtimeToolUpdates.stop(),
|
|
1362
1338
|
executionFrameQueue,
|
|
1363
1339
|
...executionRuns.values(),
|
|
1364
1340
|
...[...legacyRuns.values()].map((run) => run.done),
|
|
@@ -1374,8 +1350,6 @@ export function serve(config, opts = {}) {
|
|
|
1374
1350
|
if (executionJournal !== undefined) {
|
|
1375
1351
|
await executionJournal.close({ signal: deadline.signal });
|
|
1376
1352
|
}
|
|
1377
|
-
if (migrationExecutionJournal !== undefined)
|
|
1378
|
-
await migrationExecutionJournal.close({ signal: deadline.signal });
|
|
1379
1353
|
}
|
|
1380
1354
|
finally {
|
|
1381
1355
|
deadline.dispose();
|
package/dist/token.js
CHANGED
|
@@ -7,7 +7,6 @@ export async function mintAgentToken(serverUrl, machineToken, handle, displayNam
|
|
|
7
7
|
},
|
|
8
8
|
// 把本轮唤醒的线程根钉到 per-launch 令牌:服务端据此让 task/消息默认绑/回当前线程。
|
|
9
9
|
body: JSON.stringify({
|
|
10
|
-
memoryStateGuardVersion: 1,
|
|
11
10
|
handle,
|
|
12
11
|
...(displayName ? { displayName } : {}),
|
|
13
12
|
...(options.wakeThreadRoot ? { wakeThreadRoot: options.wakeThreadRoot } : {}),
|
|
@@ -1,22 +1,9 @@
|
|
|
1
1
|
/** 控制面文件/技能查看的按需处理,独立于执行生命周期。 */
|
|
2
|
-
import {
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
2
|
+
import { join } from "node:path";
|
|
4
3
|
import { readSharedMessageFile } from "./message-file-preview.js";
|
|
5
4
|
import { listWorkspace, readWorkspaceFile } from "./workspace-fs.js";
|
|
6
5
|
import { listSkills } from "./skills.js";
|
|
7
6
|
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
|
-
}
|
|
20
7
|
export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, models = listRuntimeModels) {
|
|
21
8
|
if (!raw || typeof raw !== "object")
|
|
22
9
|
return null;
|
|
@@ -28,8 +15,8 @@ export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, m
|
|
|
28
15
|
try {
|
|
29
16
|
const root = join(agentsRoot, request.handle);
|
|
30
17
|
const data = request.type === "fs:read-shared" ? await readSharedMessageFile(agentsRoot, raw, registry)
|
|
31
|
-
: request.type === "fs:list" ? await
|
|
32
|
-
: request.type === "fs:read" ? await
|
|
18
|
+
: request.type === "fs:list" ? await listWorkspace(root, request.path)
|
|
19
|
+
: request.type === "fs:read" ? await readWorkspaceFile(root, request.path)
|
|
33
20
|
: request.type === "skills:list" ? await listSkills(request.runtime)
|
|
34
21
|
: { models: await models(request.runtime ?? "") };
|
|
35
22
|
return { type: "fs:result", reqId: request.reqId, ok: true, data };
|
package/dist/workspace.js
CHANGED
|
@@ -8,15 +8,12 @@
|
|
|
8
8
|
*
|
|
9
9
|
* spawn runtime 时把 <ws>/.crew 置于 PATH 最前,于是 agent 的 `crew ...` 命中 wrapper。
|
|
10
10
|
*/
|
|
11
|
-
import { mkdir, writeFile, readFile, chmod, access, lstat
|
|
11
|
+
import { mkdir, writeFile, readFile, chmod, access, lstat } 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";
|
|
20
17
|
/** 文件系统安全的 taskKey:仅留 [\w.-],其余转 _,截断,避免路径穿越/超长。 */
|
|
21
18
|
export function safeKey(key) {
|
|
22
19
|
return key.replace(/[^\w.-]+/g, "_").replace(/^[-.]+/, "").slice(0, 80) || "default";
|
|
@@ -52,15 +49,7 @@ async function hasLocalMemoryV2Marker(agentDir) {
|
|
|
52
49
|
const stat = await lstat(markerPath);
|
|
53
50
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
54
51
|
return false;
|
|
55
|
-
|
|
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;
|
|
52
|
+
return (await readFile(markerPath, "utf8")) === "1\n";
|
|
64
53
|
}
|
|
65
54
|
catch {
|
|
66
55
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowcrew/daemon",
|
|
3
|
-
"version": "0.6.101-beta.
|
|
3
|
+
"version": "0.6.101-beta.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
"publishConfig": {
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:release-critical": "vitest run test/automatic-daemon-installer.test.ts test/automatic-install-target.test.ts test/automatic-service-migration.test.ts test/computer-cli.test.ts test/computer-service.test.ts test/computer-setup-client.test.ts test/computer-setup-orchestration.test.ts test/computer-setup.test.ts test/daemon-release-version.test.ts test/daemon-update-controller.test.ts test/daemon-update-eligibility.test.ts test/daemon-updater.test.ts test/execution-protocol.test.ts test/machine-info.test.ts --maxWorkers=2 --minWorkers=1",
|
|
27
|
+
"typecheck": "tsc --noEmit"
|
|
28
|
+
},
|
|
21
29
|
"dependencies": {
|
|
22
30
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
23
31
|
"@nowcrew/cli": "0.4.17",
|
|
@@ -39,12 +47,5 @@
|
|
|
39
47
|
"tsx": "^4.19.0",
|
|
40
48
|
"typescript": "^5.6.0",
|
|
41
49
|
"vitest": "^2.1.0"
|
|
42
|
-
},
|
|
43
|
-
"scripts": {
|
|
44
|
-
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
45
|
-
"build": "tsc -p tsconfig.json",
|
|
46
|
-
"test": "vitest run",
|
|
47
|
-
"test:release-critical": "vitest run test/automatic-daemon-installer.test.ts test/automatic-install-target.test.ts test/automatic-service-migration.test.ts test/computer-cli.test.ts test/computer-service.test.ts test/computer-setup-client.test.ts test/computer-setup-orchestration.test.ts test/computer-setup.test.ts test/daemon-release-version.test.ts test/daemon-update-controller.test.ts test/daemon-update-eligibility.test.ts test/daemon-updater.test.ts test/execution-protocol.test.ts test/machine-info.test.ts --maxWorkers=2 --minWorkers=1",
|
|
48
|
-
"typecheck": "tsc --noEmit"
|
|
49
50
|
}
|
|
50
|
-
}
|
|
51
|
+
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { dslog } from "../slog.js";
|
|
2
|
-
function boundedIds(ids) {
|
|
3
|
-
return {
|
|
4
|
-
value: (ids ?? []).slice(0, 100).join(","),
|
|
5
|
-
count: ids?.length ?? 0,
|
|
6
|
-
truncated: (ids?.length ?? 0) > 100,
|
|
7
|
-
};
|
|
8
|
-
}
|
|
9
|
-
export function logLocalMemoryCommitApplied(input) {
|
|
10
|
-
const { committed } = input;
|
|
11
|
-
const created = boundedIds(committed.createdIds);
|
|
12
|
-
const updated = boundedIds(committed.updatedIds);
|
|
13
|
-
const statusChanged = boundedIds(committed.statusChangedIds);
|
|
14
|
-
const pending = boundedIds(committed.pendingIds);
|
|
15
|
-
dslog("local_memory.commit_applied", "结构化本地记忆提交已验证", {
|
|
16
|
-
execution_id: input.executionId, agent_handle: input.agentHandle, prune_trace_id: input.pruneTraceId,
|
|
17
|
-
storage_version_before: committed.storageVersionBefore,
|
|
18
|
-
storage_version_after: committed.storageVersionAfter,
|
|
19
|
-
committed_state: committed.committedState,
|
|
20
|
-
writer_mode: committed.pendingReason === "legacy_requires_migration"
|
|
21
|
-
? "pending_only"
|
|
22
|
-
: committed.storageVersionAfter === 2 && committed.committedState ? "v2" : "legacy",
|
|
23
|
-
created_id_count: created.count, created_ids: created.value, created_ids_truncated: created.truncated,
|
|
24
|
-
updated_id_count: updated.count, updated_ids: updated.value, updated_ids_truncated: updated.truncated,
|
|
25
|
-
status_changed_id_count: statusChanged.count, status_changed_ids: statusChanged.value,
|
|
26
|
-
status_changed_ids_truncated: statusChanged.truncated,
|
|
27
|
-
pending_id_count: pending.count, pending_ids: pending.value, pending_ids_truncated: pending.truncated,
|
|
28
|
-
pending_reason: committed.pendingReason,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { prepareWorkspace } from "../workspace.js";
|
|
2
|
-
import { awaitWithCancellation } from "../runtime-cancellation.js";
|
|
3
|
-
import { logLocalMemoryContextPrepareFailure } from "../local-memory-telemetry.js";
|
|
4
|
-
import { resolveLocalMemoryV2Rollout } from "./rollout.js";
|
|
5
|
-
import { initializeLocalMemoryV2ForWorkspace } from "./runtime.js";
|
|
6
|
-
import { withMemoryAccess } from "./migration-access.js";
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
import { readFile } from "node:fs/promises";
|
|
9
|
-
// A fresh Agent may receive the daemon's full ten-wide execution burst. The
|
|
10
|
-
// first turn publishes schema2 durably; the remaining turns must wait to reuse
|
|
11
|
-
// that committed fact instead of timing out on the generic short IO window.
|
|
12
|
-
const EXECUTION_MEMORY_ACCESS_WAIT_MS = 30_000;
|
|
13
|
-
export async function prepareExecutionMemoryWorkspace(input, isolated, cancellation) {
|
|
14
|
-
try {
|
|
15
|
-
const prepare = async (context) => {
|
|
16
|
-
const prepared = await prepareWorkspace({
|
|
17
|
-
agentsRoot: input.launch.agentsRoot, handle: input.handle, cliPath: input.launch.cliPath,
|
|
18
|
-
executionId: input.executionId,
|
|
19
|
-
...(input.keyMode === undefined ? {} : { keyMode: input.keyMode }),
|
|
20
|
-
...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }),
|
|
21
|
-
...(input.resumeKey === undefined ? {} : { resumeKey: input.resumeKey }),
|
|
22
|
-
...(input.sessionContextFingerprint === undefined ? {} : { sessionContextFingerprint: input.sessionContextFingerprint }),
|
|
23
|
-
...(input.launch.description ? { description: input.launch.description } : {}),
|
|
24
|
-
...(isolated ? { skipLocalMemory: true } : {}),
|
|
25
|
-
});
|
|
26
|
-
const workspace = context?.created ? { ...prepared, agentHomePreexisted: false } : prepared;
|
|
27
|
-
const initialMode = await resolveLocalMemoryV2Rollout({ workspace, handle: input.handle, serverUrl: input.launch.serverUrl,
|
|
28
|
-
agentId: input.launch.agentId, requested: input.localMemoryV2Enabled === true, selfTraining: isolated,
|
|
29
|
-
...(input.launch.description ? { description: input.launch.description } : {}) });
|
|
30
|
-
const initializationBinding = input.launch.workspaceId !== undefined && input.launch.machineId !== undefined
|
|
31
|
-
? { workspaceId: input.launch.workspaceId, machineId: input.launch.machineId }
|
|
32
|
-
: undefined;
|
|
33
|
-
const initialized = await initializeLocalMemoryV2ForWorkspace(workspace, initialMode.enabled, input.launch.agentId, initializationBinding);
|
|
34
|
-
const memory = initialized && !workspace.localMemoryV2Initialized
|
|
35
|
-
? await readFile(join(workspace.dir, "MEMORY.md"), "utf8")
|
|
36
|
-
: workspace.memory;
|
|
37
|
-
const currentWorkspace = { ...workspace, memory };
|
|
38
|
-
const mode = initialized && !workspace.localMemoryV2Initialized
|
|
39
|
-
? await resolveLocalMemoryV2Rollout({ workspace: currentWorkspace, handle: input.handle,
|
|
40
|
-
serverUrl: input.launch.serverUrl, agentId: input.launch.agentId,
|
|
41
|
-
requested: input.localMemoryV2Enabled === true, selfTraining: isolated,
|
|
42
|
-
...(input.launch.description ? { description: input.launch.description } : {}) })
|
|
43
|
-
: initialMode;
|
|
44
|
-
return { workspace: { ...currentWorkspace, localMemoryV2Enabled: mode.enabled }, mode, initialized };
|
|
45
|
-
};
|
|
46
|
-
return await awaitWithCancellation(isolated ? prepare()
|
|
47
|
-
: withMemoryAccess(join(input.launch.agentsRoot, input.handle), prepare, {
|
|
48
|
-
create: true,
|
|
49
|
-
waitMs: EXECUTION_MEMORY_ACCESS_WAIT_MS,
|
|
50
|
-
initializedRecoveryIdentity: {
|
|
51
|
-
agentId: input.launch.agentId,
|
|
52
|
-
...(input.launch.workspaceId === undefined ? {} : { workspaceId: input.launch.workspaceId }),
|
|
53
|
-
...(input.launch.machineId === undefined ? {} : { machineId: input.launch.machineId }),
|
|
54
|
-
},
|
|
55
|
-
}), cancellation);
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
logLocalMemoryContextPrepareFailure({ executionId: input.executionId, agentHandle: input.handle,
|
|
59
|
-
...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }) }, "workspace_prepare", error);
|
|
60
|
-
throw error;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { parseFullMemoryIndex, LOCAL_MEMORY_GENERATED_INDEX_PATTERN } from "./storage-format.js";
|
|
2
|
-
const excerpt = (text, size) => Array.from(text).slice(0, size).join("");
|
|
3
|
-
/** 查询完整元数据;只在明确的历史入口返回非有效记录。正文仍按引用和哈希分页读取。 */
|
|
4
|
-
export function searchFullMemoryIndex(memory, sha256, input, outputBytes) {
|
|
5
|
-
const records = parseFullMemoryIndex(memory);
|
|
6
|
-
const block = LOCAL_MEMORY_GENERATED_INDEX_PATTERN.exec(memory);
|
|
7
|
-
const prefix = memory.slice(0, block.index);
|
|
8
|
-
let line = (prefix.match(/\n/gu) ?? []).length + 1;
|
|
9
|
-
let offset = Array.from(prefix).length;
|
|
10
|
-
const positions = new Map();
|
|
11
|
-
for (const part of block[0].matchAll(/[^\n]*(?:\n|$)/gu)) {
|
|
12
|
-
const id = /^- id: ([A-Za-z0-9_-]+)\r?\n?$/u.exec(part[0])?.[1];
|
|
13
|
-
if (id)
|
|
14
|
-
positions.set(id, { line, offset });
|
|
15
|
-
line += 1;
|
|
16
|
-
offset += Array.from(part[0]).length;
|
|
17
|
-
}
|
|
18
|
-
const terms = input.query.normalize("NFKC").toLowerCase().split(/\s+/u).filter(Boolean);
|
|
19
|
-
const candidates = records.filter(entry => {
|
|
20
|
-
const active = entry.status === "active" || entry.status === "pinned";
|
|
21
|
-
const content = `${entry.id} ${entry.title} ${entry.summary} ${entry.triggers.join(" ")}`.normalize("NFKC").toLowerCase();
|
|
22
|
-
return active !== input.history && terms.every(term => content.includes(term));
|
|
23
|
-
});
|
|
24
|
-
const matches = [];
|
|
25
|
-
let bytes = 512, hasMore = false;
|
|
26
|
-
for (const entry of candidates) {
|
|
27
|
-
const position = positions.get(entry.id);
|
|
28
|
-
if (!position)
|
|
29
|
-
throw new Error("memory_storage_position_invalid");
|
|
30
|
-
if (position.line <= input.afterLine)
|
|
31
|
-
continue;
|
|
32
|
-
const item = { ...position, id: entry.id, status: entry.status, revision: entry.revision,
|
|
33
|
-
file: entry.file, anchor: entry.anchor, heading: excerpt(entry.title, 96),
|
|
34
|
-
title: excerpt(entry.title, 120), summary: excerpt(entry.summary, 240), text: excerpt(entry.summary, 240) };
|
|
35
|
-
const size = Buffer.byteLength(JSON.stringify(item), "utf8") + 1;
|
|
36
|
-
if (matches.length >= input.limit || bytes + size > outputBytes) {
|
|
37
|
-
hasMore = true;
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
matches.push(item);
|
|
41
|
-
bytes += size;
|
|
42
|
-
}
|
|
43
|
-
if (hasMore && matches.length === 0)
|
|
44
|
-
throw new Error("memory_output_too_large");
|
|
45
|
-
return { file: "MEMORY.md", storageVersion: 2, sha256, matches, matchCount: candidates.length,
|
|
46
|
-
hasMore, complete: !hasMore, nextAfterLine: hasMore ? matches.at(-1).line : null };
|
|
47
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { lstat, realpath } from "node:fs/promises";
|
|
3
|
-
export const LEGACY_LOCAL_INITIALIZATION_IDENTITY = "local-initialized";
|
|
4
|
-
export async function localMemoryInitializationIdentity(agentDir, agentId, binding) {
|
|
5
|
-
const root = await realpath(agentDir);
|
|
6
|
-
const rootFingerprint = createHash("sha256").update(root).digest("hex");
|
|
7
|
-
const workspaceId = binding?.workspaceId ?? LEGACY_LOCAL_INITIALIZATION_IDENTITY;
|
|
8
|
-
const machineId = binding?.machineId ?? LEGACY_LOCAL_INITIALIZATION_IDENTITY;
|
|
9
|
-
// Keep the legacy digest byte-for-byte stable so an interrupted pre-upgrade
|
|
10
|
-
// initialization can still be inspected. Every newly authenticated daemon
|
|
11
|
-
// initialization includes both control-plane identities in its operation id.
|
|
12
|
-
const operationSeed = workspaceId === LEGACY_LOCAL_INITIALIZATION_IDENTITY
|
|
13
|
-
&& machineId === LEGACY_LOCAL_INITIALIZATION_IDENTITY
|
|
14
|
-
? `${agentId}\0${root}`
|
|
15
|
-
: `${workspaceId}\0${machineId}\0${agentId}\0${root}`;
|
|
16
|
-
const operation = createHash("sha256").update(operationSeed, "utf8").digest("hex").slice(0, 24);
|
|
17
|
-
return {
|
|
18
|
-
workspaceId,
|
|
19
|
-
agentId,
|
|
20
|
-
migrationId: `initialize-${operation}`,
|
|
21
|
-
sourceStorageVersion: 0,
|
|
22
|
-
targetStorageVersion: 2,
|
|
23
|
-
rootFingerprint,
|
|
24
|
-
ownerIdentity: String((await lstat(root)).uid),
|
|
25
|
-
machineId,
|
|
26
|
-
executionId: `initialize-${operation}`,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
export function isLegacyLocalMemoryInitializationIdentity(identity) {
|
|
30
|
-
return identity.workspaceId === LEGACY_LOCAL_INITIALIZATION_IDENTITY
|
|
31
|
-
&& identity.machineId === LEGACY_LOCAL_INITIALIZATION_IDENTITY;
|
|
32
|
-
}
|
|
33
|
-
export function isLocalMemoryInitializationIdentity(actual, expected) {
|
|
34
|
-
return Object.keys(expected)
|
|
35
|
-
.every(key => actual[key] === expected[key]);
|
|
36
|
-
}
|