@nowcrew/daemon 0.6.90 → 0.6.91-beta.10
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/agent-ability/types.js +1 -1
- package/dist/console.js +5 -4
- package/dist/daemon-update-controller.js +16 -8
- package/dist/daemon-updater.js +10 -22
- package/dist/execution-concurrency-telemetry.js +1 -0
- package/dist/execution-journal.js +3 -1
- package/dist/execution-protocol.js +2 -2
- package/dist/execution-runner.js +6 -3
- package/dist/external-output.js +2 -1
- package/dist/host-execution-coordinator.js +12 -6
- package/dist/i18n.js +4 -3
- package/dist/local-execution-prompt.js +8 -3
- package/dist/local-executor.js +16 -37
- package/dist/local-memory/execution-workspace.js +36 -0
- package/dist/local-memory/full-index-search.js +47 -0
- package/dist/local-memory/index-budget.js +9 -3
- package/dist/local-memory/migration-access.js +118 -0
- package/dist/local-memory/migration-cli.js +54 -0
- package/dist/local-memory/migration-io.js +199 -0
- package/dist/local-memory/migration-plan.js +283 -0
- package/dist/local-memory/migration-schema.js +55 -0
- package/dist/local-memory/migration-store.js +424 -0
- package/dist/local-memory/migration-tool-entry.js +21 -0
- package/dist/local-memory/prompt.js +20 -4
- package/dist/local-memory/read-cli.js +36 -1
- package/dist/local-memory/rollout.js +53 -8
- package/dist/local-memory/runtime.js +5 -2
- package/dist/local-memory/storage-format.js +152 -0
- package/dist/local-memory/write.js +108 -17
- package/dist/machine-environment-cache.js +18 -4
- package/dist/machine-environment-contract.js +3 -1
- package/dist/machine-environment-process.js +21 -1
- package/dist/machine-info.js +3 -0
- package/dist/marketplace-serve.js +25 -1
- package/dist/memory-migration-controller.js +84 -0
- package/dist/memory-migration-inspect.js +74 -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/message-file-preview.js +11 -1
- package/dist/normalize.js +9 -5
- package/dist/pre-admission-rejection.js +5 -4
- package/dist/prompt.js +5 -1
- package/dist/runner.js +1 -1
- package/dist/runtime-capabilities.js +2 -1
- package/dist/runtime-startup-gate.js +1 -0
- package/dist/runtimes/pi-runner.js +28 -0
- package/dist/runtimes/pi.js +17 -0
- package/dist/serve.js +37 -6
- package/dist/shared-execution-slots.js +2 -0
- package/dist/supervised-runtime.js +20 -0
- package/dist/token.js +1 -0
- package/dist/workspace-control-handler.js +16 -3
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import { ExecutionCompletedSchema, ExecutionRejectedSchema } from "./execution-p
|
|
|
2
2
|
import { hashExecutionSpec } from "./execution-runner.js";
|
|
3
3
|
import { dslog } from "./slog.js";
|
|
4
4
|
/** Queue rejection is admission work too: own the ID before any asynchronous journal write. */
|
|
5
|
-
export async function rejectQueuedAdmission(context, spec, executionClass, facts) {
|
|
5
|
+
export async function rejectQueuedAdmission(context, spec, executionClass, facts, rejectionReason = "machine_queue_full") {
|
|
6
6
|
const id = spec.executionId;
|
|
7
7
|
const hash = hashExecutionSpec(spec);
|
|
8
8
|
if (context.knownExecutionHashes.has(id) || context.executionRuns.has(id)) {
|
|
@@ -16,10 +16,11 @@ export async function rejectQueuedAdmission(context, spec, executionClass, facts
|
|
|
16
16
|
context.knownExecutionHashes.set(id, hash);
|
|
17
17
|
const rejected = ExecutionRejectedSchema.parse({
|
|
18
18
|
type: "execution:rejected", protocolVersion: 1, executionId: id,
|
|
19
|
-
reason: "resource_limit", message: "Local machine
|
|
19
|
+
reason: "resource_limit", message: rejectionReason === "maintenance" ? "Local machine is temporarily unavailable for maintenance"
|
|
20
|
+
: rejectionReason === "agent_queue_full" ? "Local agent execution queue is full" : "Local machine execution queue is full", at: new Date().toISOString(),
|
|
20
21
|
});
|
|
21
|
-
dslog("execution.machine_queue_rejected", "
|
|
22
|
-
level: "WARN", execution_id: id, agent_handle: spec.agent.handle, execution_class: executionClass, ...facts,
|
|
22
|
+
dslog("execution.machine_queue_rejected", rejectionReason === "maintenance" ? "机器维护期间暂停接收执行" : "执行等待队列已满", {
|
|
23
|
+
level: "WARN", execution_id: id, agent_handle: spec.agent.handle, execution_class: executionClass, admission_reason: rejectionReason, ...facts,
|
|
23
24
|
});
|
|
24
25
|
const rejecting = persistPreAdmissionRejection(context.journal, spec, rejected)
|
|
25
26
|
.then(async (entry) => {
|
package/dist/prompt.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* claim-before-work、freshness/draft、"做完所有事再停"、inbox notice 语义、
|
|
6
6
|
* 分层记忆与压缩安全、协作礼仪。措辞为本项目原创。
|
|
7
7
|
*/
|
|
8
|
+
import { detectLocalMemoryStorageVersion, selectLocalMemoryPreview } from "./local-memory/storage-format.js";
|
|
8
9
|
/** work-log 注入上限(字符):防冷启动注入随文件膨胀。超限保头(目标/结论)+尾(最近进展),
|
|
9
10
|
* 截中段并提示读全文——全文仍在盘上,只限"默认推送量"不限"可获取量"。纯函数。 */
|
|
10
11
|
export const WORKLOG_INJECT_CAP = 8000;
|
|
@@ -67,7 +68,10 @@ export function capWorkLogForInject(workLog, cap = WORKLOG_INJECT_CAP) {
|
|
|
67
68
|
const tail = workLog.slice(-WORKLOG_TAIL);
|
|
68
69
|
return `${head}\n…(work-log 过长已截去中段,全文用 Read 读 $CREW_TASK_LOG)\n${tail}`;
|
|
69
70
|
}
|
|
70
|
-
export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY) {
|
|
71
|
+
export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY, query = "") {
|
|
72
|
+
if (detectLocalMemoryStorageVersion(memory) === 2) {
|
|
73
|
+
return selectLocalMemoryPreview(memory, query, { maxCodePoints: cap, maxBytes }).memory;
|
|
74
|
+
}
|
|
71
75
|
if (memory.length <= cap && Buffer.byteLength(memory, "utf8") <= maxBytes)
|
|
72
76
|
return memory;
|
|
73
77
|
const notice = "[MEMORY.md preview truncated; use crew-memory search/read for more.]\n";
|
package/dist/runner.js
CHANGED
|
@@ -80,7 +80,7 @@ async function runAgentWithMachineEnvironment(config, input, onActivity = defaul
|
|
|
80
80
|
? { scheduledExternalNotificationPolicy: input.scheduled.externalNotificationPolicy }
|
|
81
81
|
: {}),
|
|
82
82
|
...(input.wakeOrigin ? { wakeOrigin: input.wakeOrigin } : {}),
|
|
83
|
-
memory: capMemoryForInject(workspace.memory),
|
|
83
|
+
memory: capMemoryForInject(workspace.memory, undefined, undefined, baseWake),
|
|
84
84
|
...(resuming ? {} : { workLog: capWorkLogForInject(workspace.workLog) }),
|
|
85
85
|
collaborationToolsEnabled: config.codexCollaborationTools && runtime === "codex",
|
|
86
86
|
localMemoryV2Enabled: workspace.localMemoryV2Enabled ?? localMemoryV2Enabled,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ATTACHMENT_NATIVE_IMAGE_MAX_BYTES } from "./attachment-limits.js";
|
|
2
2
|
export const LOCAL_EXECUTION_RUNTIMES = [
|
|
3
|
-
"claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness",
|
|
3
|
+
"claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "pi",
|
|
4
4
|
];
|
|
5
5
|
export const LOCAL_RUNTIME_CAPABILITIES = Object.freeze({
|
|
6
6
|
claude: Object.freeze({
|
|
@@ -33,6 +33,7 @@ export const LOCAL_RUNTIME_CAPABILITIES = Object.freeze({
|
|
|
33
33
|
nativeResume: false,
|
|
34
34
|
systemPromptTransport: "protocol",
|
|
35
35
|
}),
|
|
36
|
+
pi: Object.freeze({ transport: "pi-json", nativeResume: false, systemPromptTransport: "protocol" }),
|
|
36
37
|
});
|
|
37
38
|
export function runtimeCapability(runtime) {
|
|
38
39
|
return LOCAL_RUNTIME_CAPABILITIES[runtime];
|
|
@@ -95,6 +95,7 @@ export function createRuntimeStartupGate(limits, now = Date.now) {
|
|
|
95
95
|
kimi: activeByRuntimeAndLane.get(activeKey("kimi", "foreground")) ?? 0,
|
|
96
96
|
hermes: activeByRuntimeAndLane.get(activeKey("hermes", "foreground")) ?? 0,
|
|
97
97
|
opencode: activeByRuntimeAndLane.get(activeKey("opencode", "foreground")) ?? 0,
|
|
98
|
+
pi: activeByRuntimeAndLane.get(activeKey("pi", "foreground")) ?? 0,
|
|
98
99
|
"deepseek-harness": activeByRuntimeAndLane.get(activeKey("deepseek-harness", "foreground")) ?? 0,
|
|
99
100
|
},
|
|
100
101
|
}),
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { once } from "node:events";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import spawn from "cross-spawn";
|
|
6
|
+
import { augmentedPath } from "../runtime-path.js";
|
|
7
|
+
import { buildPiArgs, createPiEventDecoder } from "./pi.js";
|
|
8
|
+
async function readInput() { process.stdin.setEncoding("utf8"); let text = ""; for await (const chunk of process.stdin)
|
|
9
|
+
text += String(chunk); return JSON.parse(text); }
|
|
10
|
+
async function emit(event) { if (process.stdout.write(`${JSON.stringify(event)}\n`))
|
|
11
|
+
return; await once(process.stdout, "drain"); }
|
|
12
|
+
export async function runPi(bin) { const input = await readInput(); const child = spawn(bin, buildPiArgs(input), { cwd: process.cwd(), env: { ...process.env, PATH: augmentedPath(), PWD: process.cwd() }, stdio: ["ignore", "pipe", "pipe"] }); if (!child.stdout || !child.stderr)
|
|
13
|
+
throw new Error("Pi process did not expose stdio"); child.stderr.pipe(process.stderr, { end: false }); const exit = once(child, "close"); const decoder = createPiEventDecoder(); for await (const line of createInterface({ input: child.stdout })) {
|
|
14
|
+
try {
|
|
15
|
+
for (const event of decoder.push(JSON.parse(line)))
|
|
16
|
+
await emit(event);
|
|
17
|
+
}
|
|
18
|
+
catch { }
|
|
19
|
+
} const [code, signal] = await exit; const result = decoder.finish(); if (!result.ok) {
|
|
20
|
+
process.stderr.write(`${result.error}\n`);
|
|
21
|
+
return 1;
|
|
22
|
+
} return code === 0 ? 0 : (code ?? (signal ? 128 : 1)); }
|
|
23
|
+
const { values } = parseArgs({ args: process.argv.slice(2), options: { bin: { type: "string" } } });
|
|
24
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
25
|
+
if (!values.bin)
|
|
26
|
+
throw new Error("--bin is required");
|
|
27
|
+
runPi(values.bin).then(code => { process.exitCode = code; }).catch(error => { process.stderr.write(`Pi runner failed: ${String(error)}\n`); process.exitCode = 1; });
|
|
28
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function buildPiArgs(options) { return ["--mode", "json", "--print", "--no-session", "--system-prompt", options.systemPrompt, ...(options.model ? ["--model", options.model] : []), ...(options.reasoning && options.reasoning !== "default" ? ["--thinking", options.reasoning] : []), options.wakePrompt]; }
|
|
2
|
+
export function createPiEventDecoder() { let sessionId = null; let finalText = ""; let error = null; let sawTurnEnd = false; let usage; return { push(raw) { const e = (raw ?? {}); const out = []; if (e.type === "session" && typeof e.id === "string") {
|
|
3
|
+
sessionId = e.id;
|
|
4
|
+
out.push({ type: "thread.started", thread_id: e.id });
|
|
5
|
+
} const ae = e.assistantMessageEvent; if (e.type === "message_update" && ae?.type === "text_delta" && typeof ae.delta === "string") {
|
|
6
|
+
finalText += ae.delta;
|
|
7
|
+
out.push({ type: "pi.text_delta", text: ae.delta });
|
|
8
|
+
} if (e.type === "message_update" && typeof ae?.type === "string" && /tool(call|_call)/iu.test(ae.type))
|
|
9
|
+
out.push({ type: "pi.tool_call", ...(ae.name ? { title: ae.name } : {}) }); if (e.type === "message_end" && e.message?.role === "assistant")
|
|
10
|
+
usage = e.message.usage; if (e.type === "turn_end") {
|
|
11
|
+
sawTurnEnd = true;
|
|
12
|
+
const u = usage;
|
|
13
|
+
out.push({ type: "turn.completed", ...(u ? { usage: { input_tokens: u.input ?? 0, output_tokens: u.output ?? 0, cache_read_input_tokens: u.cacheRead ?? 0, cache_creation_input_tokens: u.cacheWrite ?? 0 } } : {}) });
|
|
14
|
+
} if (e.type === "error" || e.type === "agent_error") {
|
|
15
|
+
error = typeof e.error === "string" ? e.error : (e.error?.message ?? "Pi error");
|
|
16
|
+
out.push({ type: "pi.error", message: error });
|
|
17
|
+
} return out; }, finish() { return { ok: error === null && sawTurnEnd, ...(error === null && sawTurnEnd ? {} : { error: error ?? "Pi stream ended without turn completion" }), finalText, sessionId, usage }; } }; }
|
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 枚举
|
|
@@ -71,6 +74,8 @@ export function serve(config, opts = {}) {
|
|
|
71
74
|
let reconnectTimer = null;
|
|
72
75
|
let stopPromise = null;
|
|
73
76
|
let executionJournal;
|
|
77
|
+
let migrationExecutionJournal;
|
|
78
|
+
let migrationJournalReady = false;
|
|
74
79
|
const injectedExecutionJournal = opts.execution?.journal;
|
|
75
80
|
const agentMemory = opts.execution?.dependencies?.agentMemory
|
|
76
81
|
?? (config.agentMemory === null ? undefined : createAgentMemoryBridge(config.agentMemory));
|
|
@@ -85,6 +90,15 @@ export function serve(config, opts = {}) {
|
|
|
85
90
|
startupGapMs: config.executionLimits.startupGapMs,
|
|
86
91
|
});
|
|
87
92
|
const localSlots = createSharedSlotManager(config.executionLimits);
|
|
93
|
+
const memoryMigrations = createMemoryMigrationController({ config, coordinator: hostCoordinator,
|
|
94
|
+
isAdmissionClosed: () => stopped || storageIdentityAdmission.isAdmissionClosed(),
|
|
95
|
+
journal: () => { if (!migrationJournalReady)
|
|
96
|
+
throw new Error("memory_migration_journal_unavailable"); return migrationExecutionJournal; }, send: frame => {
|
|
97
|
+
if (frame.type === "execution:completed")
|
|
98
|
+
completionRetransmitter.track(frame);
|
|
99
|
+
else if (ws?.readyState === WebSocket.OPEN)
|
|
100
|
+
ws.send(JSON.stringify(frame));
|
|
101
|
+
} });
|
|
88
102
|
const sharedSlots = hostCoordinatedSlotManager(localSlots, hostCoordinator);
|
|
89
103
|
const concurrencyTelemetry = createExecutionConcurrencyTelemetry(sharedSlots, {
|
|
90
104
|
...(opts.execution?.concurrencySnapshotIntervalMs === undefined
|
|
@@ -105,11 +119,9 @@ export function serve(config, opts = {}) {
|
|
|
105
119
|
install: opts.update?.install ?? ((input) => installExactDaemonUpdate({
|
|
106
120
|
...input,
|
|
107
121
|
localSlots,
|
|
108
|
-
hostCoordinator,
|
|
109
122
|
})),
|
|
110
123
|
prepareRestart: opts.update?.prepareRestart ?? (() => prepareDaemonRestart({
|
|
111
124
|
localSlots,
|
|
112
|
-
hostCoordinator,
|
|
113
125
|
})),
|
|
114
126
|
scheduleRestart: opts.update?.scheduleRestart ?? scheduleServiceRestart,
|
|
115
127
|
sendStatus: (frame) => {
|
|
@@ -234,7 +246,7 @@ export function serve(config, opts = {}) {
|
|
|
234
246
|
return null;
|
|
235
247
|
};
|
|
236
248
|
const sendSnapshot = async (reqId) => {
|
|
237
|
-
const entries = (await executionJournal
|
|
249
|
+
const entries = (await replayExecutionJournals(executionJournal, migrationExecutionJournal)).map(snapshotEntry).filter((entry) => entry !== null);
|
|
238
250
|
safeExecutionSend(ExecutionSnapshotSchema.parse({
|
|
239
251
|
type: "execution:snapshot", protocolVersion: 1, reqId, entries,
|
|
240
252
|
}));
|
|
@@ -315,6 +327,12 @@ export function serve(config, opts = {}) {
|
|
|
315
327
|
machineRuntime.open(openedSocket, { capabilities: capabilities.machineHello, beforePublish: Promise.resolve(), connectionId });
|
|
316
328
|
opts.onOpen?.(ws);
|
|
317
329
|
void sendSnapshot(`reconnect-${randomUUID()}`);
|
|
330
|
+
// 普通回执不等待维护日志IO;两类记录分别重放到同一发送器。
|
|
331
|
+
void migrationExecutionJournal.replay().then(entries => {
|
|
332
|
+
for (const entry of entries)
|
|
333
|
+
if (entry.completion && !entry.completionAcknowledged)
|
|
334
|
+
completionRetransmitter.track(entry.completion);
|
|
335
|
+
}).catch(() => dslog("memory_migration.journal_unavailable", "维护回执重放失败,普通回执继续", { level: "ERROR" }));
|
|
318
336
|
void executionJournal.replay()
|
|
319
337
|
.then((entries) => {
|
|
320
338
|
for (const entry of entries) {
|
|
@@ -389,7 +407,8 @@ export function serve(config, opts = {}) {
|
|
|
389
407
|
return;
|
|
390
408
|
if (frame.type === "execution:completion-ack") {
|
|
391
409
|
try {
|
|
392
|
-
await executionJournal.
|
|
410
|
+
const journal = await executionJournal.get(frame.executionId) ? executionJournal : migrationExecutionJournal;
|
|
411
|
+
await journal.acknowledgeCompletion(frame.executionId);
|
|
393
412
|
const attempts = completionRetransmitter.acknowledge(frame.executionId);
|
|
394
413
|
dslog("execution.completion_acknowledged", "execution completion ACK 已持久化", {
|
|
395
414
|
execution_id: frame.executionId,
|
|
@@ -415,6 +434,8 @@ export function serve(config, opts = {}) {
|
|
|
415
434
|
return;
|
|
416
435
|
}
|
|
417
436
|
if (frame.type === "execution:cancel") {
|
|
437
|
+
if (memoryMigrations.cancel(frame.executionId))
|
|
438
|
+
return;
|
|
418
439
|
if (!knownExecutionHashes.has(frame.executionId)
|
|
419
440
|
&& !executionRuns.has(frame.executionId)
|
|
420
441
|
&& await executionJournal.get(frame.executionId) === null)
|
|
@@ -490,7 +511,7 @@ export function serve(config, opts = {}) {
|
|
|
490
511
|
let reservation = sharedSlots.reserve(spec.agent.handle, "execution", executionClass);
|
|
491
512
|
if (!reservation.accepted) {
|
|
492
513
|
await rejectQueuedAdmission({ journal: executionJournal, knownExecutionHashes, executionRuns,
|
|
493
|
-
report: reportExecutionFrame, replay: sendJournalStatus }, spec, executionClass, reservation.facts);
|
|
514
|
+
report: reportExecutionFrame, replay: sendJournalStatus }, spec, executionClass, reservation.facts, reservation.rejectionReason);
|
|
494
515
|
return;
|
|
495
516
|
}
|
|
496
517
|
knownExecutionHashes.set(spec.executionId, hash);
|
|
@@ -737,6 +758,8 @@ export function serve(config, opts = {}) {
|
|
|
737
758
|
// ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
|
|
738
759
|
const r = msg;
|
|
739
760
|
setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
|
|
761
|
+
if (r.workspaceId && r.machineId)
|
|
762
|
+
memoryMigrations.setIdentity({ workspaceId: r.workspaceId, machineId: r.machineId });
|
|
740
763
|
if (opts.storageRuntime !== undefined && typeof r.workspaceId === "string" && typeof r.machineId === "string") {
|
|
741
764
|
const bindingSocket = ws;
|
|
742
765
|
bindStorageServerIdentity(opts.storageRuntime, { workspaceId: r.workspaceId, machineId: r.machineId }, {
|
|
@@ -803,6 +826,8 @@ export function serve(config, opts = {}) {
|
|
|
803
826
|
}
|
|
804
827
|
catch { /* server 会按请求超时处理 */ } }))
|
|
805
828
|
return;
|
|
829
|
+
if (await memoryMigrations.handle(msg))
|
|
830
|
+
return;
|
|
806
831
|
if (await agentAbilityRuntime.tryHandleControlMessage(msg, serverCapabilities, (frame) => ws?.send(JSON.stringify(frame))))
|
|
807
832
|
return;
|
|
808
833
|
// 导入 raft agent 工作区:inspect 反填 name/description;import 复制用户内容
|
|
@@ -817,7 +842,7 @@ export function serve(config, opts = {}) {
|
|
|
817
842
|
try {
|
|
818
843
|
const data2 = req.type === "raft:inspect"
|
|
819
844
|
? await inspectRaftWorkspace(req.path)
|
|
820
|
-
: await importRaftWorkspace(req.path, join(config.agentsRoot, req.handle));
|
|
845
|
+
: await withMemoryAccess(join(config.agentsRoot, req.handle), () => importRaftWorkspace(req.path, join(config.agentsRoot, req.handle)), { create: true });
|
|
821
846
|
if (req.type === "raft:import") {
|
|
822
847
|
log(`📦 已导入 raft 工作区 → ${req.handle}: 复制 ${data2.copied.length} 项`);
|
|
823
848
|
}
|
|
@@ -1261,6 +1286,9 @@ export function serve(config, opts = {}) {
|
|
|
1261
1286
|
writeStderr: (line) => process.stderr.write(line),
|
|
1262
1287
|
});
|
|
1263
1288
|
await runProjectSkillStartupCleanup(config.agentsRoot, executionJournal, dslog);
|
|
1289
|
+
migrationExecutionJournal = createMemoryMigrationJournal(config.agentsRoot);
|
|
1290
|
+
await migrationExecutionJournal.reconcileAfterRestart().then(() => { migrationJournalReady = true; })
|
|
1291
|
+
.catch(() => dslog("memory_migration.journal_unavailable", "迁移日志恢复失败,普通执行继续,迁移请求单独报错", { level: "ERROR" }));
|
|
1264
1292
|
await connect();
|
|
1265
1293
|
})();
|
|
1266
1294
|
const stop = () => {
|
|
@@ -1278,6 +1306,7 @@ export function serve(config, opts = {}) {
|
|
|
1278
1306
|
const webSocketClosed = closeWebSocketWithinDeadline(ws, deadline.signal);
|
|
1279
1307
|
const pending = [
|
|
1280
1308
|
machineRuntime.stop(),
|
|
1309
|
+
memoryMigrations.stop(),
|
|
1281
1310
|
webSocketClosed,
|
|
1282
1311
|
updateController.drain(),
|
|
1283
1312
|
marketplace.stop(),
|
|
@@ -1296,6 +1325,8 @@ export function serve(config, opts = {}) {
|
|
|
1296
1325
|
if (executionJournal !== undefined) {
|
|
1297
1326
|
await executionJournal.close({ signal: deadline.signal });
|
|
1298
1327
|
}
|
|
1328
|
+
if (migrationExecutionJournal !== undefined)
|
|
1329
|
+
await migrationExecutionJournal.close({ signal: deadline.signal });
|
|
1299
1330
|
}
|
|
1300
1331
|
finally {
|
|
1301
1332
|
deadline.dispose();
|
|
@@ -59,6 +59,7 @@ export function createSharedSlotManager(limits) {
|
|
|
59
59
|
if (exclusive) {
|
|
60
60
|
return {
|
|
61
61
|
accepted: false,
|
|
62
|
+
rejectionReason: "maintenance",
|
|
62
63
|
facts,
|
|
63
64
|
ready: Promise.resolve(),
|
|
64
65
|
isQueued: () => false,
|
|
@@ -77,6 +78,7 @@ export function createSharedSlotManager(limits) {
|
|
|
77
78
|
|| laneQueuedTotal >= limits.maxQueuedTotal)) {
|
|
78
79
|
return {
|
|
79
80
|
accepted: false,
|
|
81
|
+
rejectionReason: queuedForAgent >= limits.maxQueuedPerAgent ? "agent_queue_full" : "machine_queue_full",
|
|
80
82
|
facts,
|
|
81
83
|
ready: Promise.resolve(),
|
|
82
84
|
isQueued: () => false,
|
|
@@ -61,6 +61,26 @@ export function supervisorLaunch(request) {
|
|
|
61
61
|
}),
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
+
if (request.runtime === "pi") {
|
|
65
|
+
if (request.effectivePermission !== "full_access") {
|
|
66
|
+
throw new Error(`Pi cannot enforce ${request.effectivePermission} permission`);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
command: process.execPath,
|
|
70
|
+
args: [
|
|
71
|
+
fileURLToPath(new URL("./runtimes/pi-runner.js", import.meta.url)),
|
|
72
|
+
"--bin", request.bin,
|
|
73
|
+
],
|
|
74
|
+
cwd: request.cwd,
|
|
75
|
+
env: request.env,
|
|
76
|
+
stdinText: JSON.stringify({
|
|
77
|
+
systemPrompt: request.systemPrompt,
|
|
78
|
+
wakePrompt: request.wakePrompt,
|
|
79
|
+
...(request.model === undefined ? {} : { model: request.model }),
|
|
80
|
+
...(request.reasoning === undefined ? {} : { reasoning: request.reasoning }),
|
|
81
|
+
}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
64
84
|
if (request.runtime === "opencode") {
|
|
65
85
|
if (request.effectivePermission !== "full_access") {
|
|
66
86
|
throw new Error(`OpenCode cannot enforce ${request.effectivePermission} permission`);
|
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 };
|