@nowcrew/daemon 0.6.65 → 0.6.66
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/materializer.js +1 -20
- package/dist/agent-ability/resolver.js +10 -7
- package/dist/agent-ability/runtime.js +0 -3
- package/dist/automatic-install-target.js +10 -15
- package/dist/computer-cli.js +1 -10
- package/dist/computer-service.js +1 -41
- package/dist/config.js +0 -44
- package/dist/daemon-migration-controller.js +18 -16
- package/dist/execution-runner.js +1 -3
- package/dist/local-execution-prompt.js +9 -14
- package/dist/local-executor.js +3 -56
- package/dist/machine-info.js +2 -3
- package/dist/main.js +0 -83
- package/dist/managed-service-lifecycle.js +2 -47
- package/dist/managed-service-startup.js +0 -2
- package/dist/profile-layout.js +4 -10
- package/dist/project-skills/controller.js +0 -2
- package/dist/prompt.js +1 -19
- package/dist/runner.js +0 -3
- package/dist/runtimes/codex-app-server-runner.js +14 -54
- package/dist/runtimes/codex-contract.js +5 -20
- package/dist/runtimes/codex.js +0 -4
- package/dist/serve.js +3 -68
- package/dist/supervised-runtime.js +0 -3
- package/dist/workspace.js +1 -24
- package/package.json +9 -10
- package/dist/local-memory/index-budget.js +0 -74
- package/dist/local-memory/pending.js +0 -111
- package/dist/local-memory/runtime.js +0 -87
- package/dist/local-memory/types.js +0 -1
- package/dist/local-memory/write.js +0 -448
- package/dist/runtimes/codex-command-safety.js +0 -75
- package/dist/runtimes/codex-turn-retry.js +0 -69
- package/dist/storage-config.js +0 -175
- package/dist/storage-control.js +0 -193
- package/dist/storage-copy.js +0 -609
- package/dist/storage-credential-guard.js +0 -21
- package/dist/storage-doctor.js +0 -228
- package/dist/storage-identity.js +0 -29
- package/dist/storage-layout.js +0 -143
- package/dist/storage-manifest.js +0 -855
- package/dist/storage-migration-controller.js +0 -417
- package/dist/storage-migration-manifest.js +0 -105
- package/dist/storage-migration-policy.js +0 -100
- package/dist/storage-runtime-controller.js +0 -339
- package/dist/storage-runtime-readiness.js +0 -184
- package/dist/storage-runtime.js +0 -39
- package/dist/storage-safe-fs.js +0 -302
- package/dist/storage-serve-adapter.js +0 -65
- package/dist/storage-status-delivery.js +0 -209
- package/dist/storage-status-replay.js +0 -125
- package/dist/storage-target-confirmation.js +0 -129
- package/dist/storage-worker-command.js +0 -27
- package/dist/storage-worker-executor.js +0 -384
- package/dist/storage-worker-handoff.js +0 -151
- package/dist/storage-worker-operation.js +0 -284
- package/dist/storage-worker-status.js +0 -105
package/dist/serve.js
CHANGED
|
@@ -51,7 +51,6 @@ import { handleRuntimeProbeFrame, probeRuntimeHealth } from "./runtime-health.js
|
|
|
51
51
|
import { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
52
52
|
import { createServeMigrationController, handleMigrationControlMessage } from "./daemon-migration-wiring.js";
|
|
53
53
|
import { createCodexHomeModeController } from "./codex-home-mode-controller.js";
|
|
54
|
-
import { attachStorageMigrationRuntime, createServeStorageStatusDelivery, handleStorageMigrationControl, replayStorageMigrationStatus, storageMigrationCapabilities, } from "./storage-serve-adapter.js";
|
|
55
54
|
export { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
56
55
|
// normalize.ts 的活动种类 → activity 枚举
|
|
57
56
|
const ACTIVITY_MAP = {
|
|
@@ -123,7 +122,6 @@ export function serve(config, opts = {}) {
|
|
|
123
122
|
catch { /* reconnect/timeout reconciliation handles a lost status frame */ }
|
|
124
123
|
},
|
|
125
124
|
});
|
|
126
|
-
let storageMigrationAdmissionClosed = () => false;
|
|
127
125
|
const migrationController = createServeMigrationController({
|
|
128
126
|
config,
|
|
129
127
|
...(opts.profileName === undefined ? {} : { profileName: opts.profileName }),
|
|
@@ -144,7 +142,6 @@ export function serve(config, opts = {}) {
|
|
|
144
142
|
const agentAbilityRuntime = createAgentAbilityRuntime(config.agentsRoot, {
|
|
145
143
|
...opts.agentAbility,
|
|
146
144
|
coordinator: projectionCoordinator,
|
|
147
|
-
isAdmissionClosed: () => storageMigrationAdmissionClosed(),
|
|
148
145
|
});
|
|
149
146
|
let projectSkillsController;
|
|
150
147
|
const projectSkillsReconciler = opts.projectSkills?.reconciler ?? createProjectSkillsReconciler({
|
|
@@ -164,7 +161,6 @@ export function serve(config, opts = {}) {
|
|
|
164
161
|
}
|
|
165
162
|
catch { /* 下一次 ready 或项目操作会重新发送完整快照 */ }
|
|
166
163
|
},
|
|
167
|
-
isAdmissionClosed: () => storageMigrationAdmissionClosed(),
|
|
168
164
|
reconcile: (handle, bindings) => projectSkillsReconciler.reconcile(handle, bindings),
|
|
169
165
|
ensureSnapshot: (handle, snapshot, options) => projectSkillsReconciler.ensureSnapshot(handle, snapshot, options),
|
|
170
166
|
});
|
|
@@ -179,37 +175,13 @@ export function serve(config, opts = {}) {
|
|
|
179
175
|
});
|
|
180
176
|
const machineHelloPublisher = createProjectSkillMachineHelloPublisher({
|
|
181
177
|
status: projectSkillControllerReadiness.status,
|
|
182
|
-
effective:
|
|
183
|
-
const effective = withProjectSkillCapabilityStatus(hello, status);
|
|
184
|
-
const configuredIdentity = opts.storageMigration?.identity;
|
|
185
|
-
const currentIdentity = typeof configuredIdentity === "function"
|
|
186
|
-
? configuredIdentity()
|
|
187
|
-
: configuredIdentity;
|
|
188
|
-
return currentIdentity === undefined ? effective : { ...effective, ...currentIdentity };
|
|
189
|
-
},
|
|
178
|
+
effective: withProjectSkillCapabilityStatus,
|
|
190
179
|
onSent: (hello) => log(`📤 已上报机器信息: ${hello.hostname} · ${hello.os} · installed=[${hello.runtimes.join(",")}] · executable=[${hello.executionRuntimes.join(",")}] · agents=[${hello.agentHandles.join(",")}]`),
|
|
191
180
|
});
|
|
192
181
|
const initializedProjectSkills = initializedProjectSkillsReconciler(projectSkillControllerReadiness.ensure, projectSkillsReconciler);
|
|
193
|
-
const configuredCapabilities = opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform);
|
|
194
|
-
const storageCapabilities = storageMigrationCapabilities(configuredCapabilities, opts.storageMigration);
|
|
195
|
-
const storageStatusDelivery = createServeStorageStatusDelivery(opts.storageMigration, {
|
|
196
|
-
onPersistenceError: (error) => dslog("daemon.storage_status_outbox_failed", "存储迁移状态 outbox 持久化失败", { level: "ERROR", error_message: error.message }),
|
|
197
|
-
transmit: (frame) => { try {
|
|
198
|
-
if (ws?.readyState === WebSocket.OPEN)
|
|
199
|
-
ws.send(JSON.stringify(frame));
|
|
200
|
-
}
|
|
201
|
-
catch { /* retry */ } },
|
|
202
|
-
});
|
|
203
|
-
const storageMigrationRuntime = attachStorageMigrationRuntime(opts.storageMigration, {
|
|
204
|
-
isBusy: () => executionRuns.size > 0 || legacyRuns.size > 0,
|
|
205
|
-
waitForExecutionFrames: () => executionFrameQueue,
|
|
206
|
-
flushLocalState: flushSlog,
|
|
207
|
-
sendStatus: storageStatusDelivery.send,
|
|
208
|
-
});
|
|
209
|
-
storageMigrationAdmissionClosed = storageMigrationRuntime.isAdmissionClosed;
|
|
210
182
|
const prepareProjectSkillCapabilities = createProjectSkillConnectionCapabilityProvider({
|
|
211
183
|
platform: process.platform,
|
|
212
|
-
baseCapabilities:
|
|
184
|
+
baseCapabilities: opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform),
|
|
213
185
|
additionalCapabilities: () => managedDaemonCapabilities(updateEligibility),
|
|
214
186
|
probeRuntimeRootStorePrimitive: projectSkillRuntimeRootStartup.probeNativePublicationPrimitiveReadiness,
|
|
215
187
|
initializeController: projectSkillControllerReadiness.ensure,
|
|
@@ -338,12 +310,6 @@ export function serve(config, opts = {}) {
|
|
|
338
310
|
// 连上了才有机会把离线期间(断连原因/退出前)落盘的日志补传上去
|
|
339
311
|
void drainSpool();
|
|
340
312
|
// 上报本机信息 (hostname/os/daemon 版本/已装 runtimes)
|
|
341
|
-
storageStatusDelivery.replayPending();
|
|
342
|
-
const storageReplay = replayStorageMigrationStatus(opts.storageMigration, openedSocket, () => ws === openedSocket, (error) => {
|
|
343
|
-
dslog("daemon.storage_status_replay_failed", "存储迁移状态重放失败", {
|
|
344
|
-
level: "ERROR", error_message: error.message,
|
|
345
|
-
});
|
|
346
|
-
}, storageStatusDelivery.send);
|
|
347
313
|
const { detectInstalled, detectExecutable = detectExecutionRuntimesWithSignal } = opts.machineInfo ?? {};
|
|
348
314
|
const helloPromise = (opts.machineInfo?.collect ?? collectMachineHello)(config.agentsRoot, config.executionLimits, process.platform, {
|
|
349
315
|
...(detectInstalled ? { detectInstalled } : {}),
|
|
@@ -359,7 +325,6 @@ export function serve(config, opts = {}) {
|
|
|
359
325
|
if (hello.executionProtocol === undefined)
|
|
360
326
|
return [];
|
|
361
327
|
const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable);
|
|
362
|
-
await storageReplay;
|
|
363
328
|
if (ws === openedSocket && openedSocket.readyState === WebSocket.OPEN
|
|
364
329
|
&& detected.some((runtime) => !hello.executionRuntimes.includes(runtime))) {
|
|
365
330
|
detectedExecutionRuntimes = detected;
|
|
@@ -374,8 +339,7 @@ export function serve(config, opts = {}) {
|
|
|
374
339
|
return [];
|
|
375
340
|
});
|
|
376
341
|
void helloPromise
|
|
377
|
-
.then(
|
|
378
|
-
await storageReplay;
|
|
342
|
+
.then((hello) => {
|
|
379
343
|
if (ws !== openedSocket || openedSocket.readyState !== WebSocket.OPEN)
|
|
380
344
|
return;
|
|
381
345
|
detectedExecutionRuntimes = hello.executionRuntimes;
|
|
@@ -423,8 +387,6 @@ export function serve(config, opts = {}) {
|
|
|
423
387
|
if (typeof decoded !== "object" || decoded === null)
|
|
424
388
|
return;
|
|
425
389
|
const rawType = "type" in decoded && typeof decoded.type === "string" ? decoded.type : "";
|
|
426
|
-
if (storageStatusDelivery.acknowledge(decoded))
|
|
427
|
-
return;
|
|
428
390
|
if (rawType === "daemon:update" || rawType === "daemon:restart") {
|
|
429
391
|
void updateController.handle(decoded).catch((error) => {
|
|
430
392
|
dslog("daemon.control_failed", "daemon 控制操作处理失败", {
|
|
@@ -441,11 +403,6 @@ export function serve(config, opts = {}) {
|
|
|
441
403
|
void codexHomeModeController.handle(decoded).catch((error) => dslog("daemon.codex_home_mode_failed", "Codex Home 模式切换失败", { level: "ERROR", error_message: error.message }));
|
|
442
404
|
return;
|
|
443
405
|
}
|
|
444
|
-
if (handleStorageMigrationControl(opts.storageMigration, rawType, decoded, storageStatusDelivery.send, (kind, error) => {
|
|
445
|
-
dslog(kind === "migration" ? "daemon.storage_migration_failed" : "daemon.storage_migration_confirmation_failed", kind === "migration" ? "存储迁移控制操作失败" : "存储迁移 hello 确认处理失败", { level: "ERROR", error_message: error.message });
|
|
446
|
-
})) {
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
406
|
if (rawType.startsWith("execution:")) {
|
|
450
407
|
const parsedExecution = ServerToDaemonExecutionFrameSchema.safeParse(decoded);
|
|
451
408
|
if (!parsedExecution.success) {
|
|
@@ -507,14 +464,6 @@ export function serve(config, opts = {}) {
|
|
|
507
464
|
return;
|
|
508
465
|
}
|
|
509
466
|
const spec = frame;
|
|
510
|
-
if (storageMigrationRuntime.isAdmissionClosed()) {
|
|
511
|
-
safeExecutionSend(ExecutionRejectedSchema.parse({
|
|
512
|
-
type: "execution:rejected", protocolVersion: 1, executionId: spec.executionId,
|
|
513
|
-
reason: "resource_limit", message: "Storage migration is quiescing this machine",
|
|
514
|
-
at: new Date().toISOString(),
|
|
515
|
-
}));
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
467
|
const workspaceRejection = projectWorkspaceCapabilityRejection(spec, capabilities.executionRunner, new Date().toISOString());
|
|
519
468
|
if (workspaceRejection !== null)
|
|
520
469
|
return void safeExecutionSend(workspaceRejection);
|
|
@@ -821,17 +770,6 @@ export function serve(config, opts = {}) {
|
|
|
821
770
|
// ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
|
|
822
771
|
const r = msg;
|
|
823
772
|
setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
|
|
824
|
-
if (opts.storageMigration?.ready === true && typeof r.workspaceId === "string") {
|
|
825
|
-
try {
|
|
826
|
-
opts.storageMigration.bindServerIdentity?.({ workspaceId: r.workspaceId });
|
|
827
|
-
machineHelloPublisher.republish();
|
|
828
|
-
}
|
|
829
|
-
catch (error) {
|
|
830
|
-
dslog("daemon.storage_identity_bind_failed", "存储迁移 Workspace 身份绑定失败", {
|
|
831
|
-
level: "ERROR", error_message: error.message,
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
773
|
// 旧 server 无此字段 → 空集合(daemon 保持全部旧行为)。
|
|
836
774
|
serverCapabilities = new Set(Array.isArray(r.serverCapabilities)
|
|
837
775
|
? r.serverCapabilities.filter((c) => typeof c === "string")
|
|
@@ -930,8 +868,6 @@ export function serve(config, opts = {}) {
|
|
|
930
868
|
}
|
|
931
869
|
if (msg.type !== "agent:start")
|
|
932
870
|
return; // ready/error 等忽略
|
|
933
|
-
if (storageMigrationRuntime.isAdmissionClosed())
|
|
934
|
-
return;
|
|
935
871
|
// 任务键:scheduled run 用 runId(每次运行独立 cwd/work-log;overlap 由 server 端 skip_if_running 管,
|
|
936
872
|
// daemon 的 running 去重只兜底"同一 run 重复投递");普通唤醒仍是 线程锚点 ?? 频道。
|
|
937
873
|
const scheduled = msg.reason === "scheduled_job" && msg.scheduledRun
|
|
@@ -1347,7 +1283,6 @@ export function serve(config, opts = {}) {
|
|
|
1347
1283
|
concurrencyTelemetry.stop();
|
|
1348
1284
|
runtimeProbe.stop();
|
|
1349
1285
|
completionRetransmitter.stop();
|
|
1350
|
-
storageStatusDelivery.stop();
|
|
1351
1286
|
const deadline = createShutdownDeadline(shutdownTimeoutMs);
|
|
1352
1287
|
if (reconnectTimer !== null) {
|
|
1353
1288
|
clearTimeout(reconnectTimer);
|
|
@@ -58,9 +58,6 @@ export function supervisorLaunch(request) {
|
|
|
58
58
|
...(request.codexSkillRoots === undefined ? {} : {
|
|
59
59
|
skillRoots: request.codexSkillRoots,
|
|
60
60
|
}),
|
|
61
|
-
...(request.codexCollaborationTools === undefined ? {} : {
|
|
62
|
-
codexCollaborationTools: request.codexCollaborationTools,
|
|
63
|
-
}),
|
|
64
61
|
resume: request.resume,
|
|
65
62
|
}),
|
|
66
63
|
};
|
package/dist/workspace.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* spawn runtime 时把 <ws>/.crew 置于 PATH 最前,于是 agent 的 `crew ...` 命中 wrapper。
|
|
10
10
|
*/
|
|
11
|
-
import { mkdir, writeFile, readFile, chmod, access
|
|
11
|
+
import { mkdir, writeFile, readFile, chmod, access } from "node:fs/promises";
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import { dslog } from "./slog.js";
|
|
@@ -34,28 +34,8 @@ async function exists(p) {
|
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
async function hasLocalMemoryV2Marker(agentDir) {
|
|
38
|
-
const crewDir = join(agentDir, ".crew");
|
|
39
|
-
const memoryDir = join(crewDir, "memory");
|
|
40
|
-
const markerPath = join(agentDir, ".crew", "memory", "v2-enabled");
|
|
41
|
-
try {
|
|
42
|
-
const crewStat = await lstat(crewDir);
|
|
43
|
-
const memoryStat = await lstat(memoryDir);
|
|
44
|
-
if (!crewStat.isDirectory() || crewStat.isSymbolicLink()
|
|
45
|
-
|| !memoryStat.isDirectory() || memoryStat.isSymbolicLink())
|
|
46
|
-
return false;
|
|
47
|
-
const stat = await lstat(markerPath);
|
|
48
|
-
if (!stat.isFile() || stat.isSymbolicLink())
|
|
49
|
-
return false;
|
|
50
|
-
return (await readFile(markerPath, "utf8")) === "1\n";
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
37
|
export async function prepareWorkspace(input) {
|
|
57
38
|
const dir = join(input.agentsRoot, input.handle);
|
|
58
|
-
const agentHomePreexisted = await exists(dir);
|
|
59
39
|
const crewDir = join(dir, ".crew");
|
|
60
40
|
await mkdir(crewDir, { recursive: true });
|
|
61
41
|
// 预先创建明细目录,让收尾流程可以按功能创建 notes/<topic>.md。
|
|
@@ -63,7 +43,6 @@ export async function prepareWorkspace(input) {
|
|
|
63
43
|
// MEMORY.md:首次创建"持久身份 + 知识索引"骨架,之后由 agent 自己维护
|
|
64
44
|
const memoryPath = join(dir, "MEMORY.md");
|
|
65
45
|
const memorySeedCreated = !(await exists(memoryPath));
|
|
66
|
-
const localMemoryV2Initialized = await hasLocalMemoryV2Marker(dir);
|
|
67
46
|
if (memorySeedCreated) {
|
|
68
47
|
try {
|
|
69
48
|
await writeFile(memoryPath, memorySeed(input.handle, input.description), "utf8");
|
|
@@ -145,9 +124,7 @@ export async function prepareWorkspace(input) {
|
|
|
145
124
|
crewDir,
|
|
146
125
|
systemPromptPath,
|
|
147
126
|
memory,
|
|
148
|
-
agentHomePreexisted,
|
|
149
127
|
memorySeedCreated,
|
|
150
|
-
localMemoryV2Initialized,
|
|
151
128
|
homeDir,
|
|
152
129
|
runDir,
|
|
153
130
|
workLogPath,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowcrew/daemon",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.66",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,14 +16,6 @@
|
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
21
|
-
"build": "tsc -p tsconfig.json",
|
|
22
|
-
"codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
|
|
23
|
-
"prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
|
|
24
|
-
"test": "vitest run",
|
|
25
|
-
"typecheck": "tsc --noEmit"
|
|
26
|
-
},
|
|
27
19
|
"dependencies": {
|
|
28
20
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
29
21
|
"@nowcrew/cli": "^0.4.13",
|
|
@@ -42,5 +34,12 @@
|
|
|
42
34
|
"tsx": "^4.19.0",
|
|
43
35
|
"typescript": "^5.6.0",
|
|
44
36
|
"vitest": "^2.1.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
40
|
+
"build": "tsc -p tsconfig.json",
|
|
41
|
+
"codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"typecheck": "tsc --noEmit"
|
|
45
44
|
}
|
|
46
|
-
}
|
|
45
|
+
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
export const LOCAL_MEMORY_TOTAL_CODE_POINT_CAP = 6_000;
|
|
2
|
-
export const LOCAL_MEMORY_INDEX_CODE_POINT_CAP = 3_000;
|
|
3
|
-
export const LOCAL_MEMORY_INDEX_ENTRY_CAP = 20;
|
|
4
|
-
const PROJECTABLE_STATUSES = new Set(["pinned", "active"]);
|
|
5
|
-
function codePointLength(value) {
|
|
6
|
-
return Array.from(value).length;
|
|
7
|
-
}
|
|
8
|
-
export function renderLocalMemoryIndexCard(entry) {
|
|
9
|
-
return `### ${entry.title}\n`
|
|
10
|
-
+ `- id: ${entry.id}\n`
|
|
11
|
-
+ `- file: ${entry.file}\n`
|
|
12
|
-
+ `- type: ${entry.type}\n`
|
|
13
|
-
+ `- status: ${entry.status}\n`
|
|
14
|
-
+ `- summary: ${entry.summary}\n`
|
|
15
|
-
+ `- triggers: ${entry.triggers.join(", ")}\n`
|
|
16
|
-
+ `- priority: ${entry.priority}\n`
|
|
17
|
-
+ `- revision: ${entry.revision}\n`
|
|
18
|
-
+ `- anchor: ${entry.anchor ?? ""}\n`
|
|
19
|
-
+ `- updated: ${entry.updated}\n`
|
|
20
|
-
+ (entry.reviewAfter === null ? "" : `- review_after: ${entry.reviewAfter}\n`);
|
|
21
|
-
}
|
|
22
|
-
function compareEntries(left, right) {
|
|
23
|
-
const statusRank = (status) => status === "pinned" ? 0 : 1;
|
|
24
|
-
const priorityRank = (priority) => (priority === "high" ? 0 : priority === "medium" ? 1 : 2);
|
|
25
|
-
return statusRank(left.status) - statusRank(right.status)
|
|
26
|
-
|| priorityRank(left.priority) - priorityRank(right.priority)
|
|
27
|
-
|| right.updated.localeCompare(left.updated)
|
|
28
|
-
|| left.id.localeCompare(right.id);
|
|
29
|
-
}
|
|
30
|
-
function cardLength(entry, hasPrevious) {
|
|
31
|
-
return codePointLength(renderLocalMemoryIndexCard(entry)) + (hasPrevious ? 1 : 0);
|
|
32
|
-
}
|
|
33
|
-
function overBudgetReason(entries) {
|
|
34
|
-
return entries.some((entry) => entry.status === "pinned")
|
|
35
|
-
? "index_over_budget_pinned"
|
|
36
|
-
: "index_over_budget_active";
|
|
37
|
-
}
|
|
38
|
-
export function selectLocalMemoryIndexEntries(entries, budget, options = {}) {
|
|
39
|
-
const projectable = entries.filter((entry) => PROJECTABLE_STATUSES.has(entry.status));
|
|
40
|
-
const omittedIds = entries
|
|
41
|
-
.filter((entry) => !PROJECTABLE_STATUSES.has(entry.status))
|
|
42
|
-
.map((entry) => entry.id);
|
|
43
|
-
const protectedIds = options.protectedIds ?? new Set();
|
|
44
|
-
const protectedEntries = projectable.filter((entry) => protectedIds.has(entry.id)).sort(compareEntries);
|
|
45
|
-
const candidates = projectable.filter((entry) => !protectedIds.has(entry.id)).sort(compareEntries);
|
|
46
|
-
const selected = [];
|
|
47
|
-
let renderedLength = 0;
|
|
48
|
-
for (const entry of protectedEntries) {
|
|
49
|
-
selected.push(entry);
|
|
50
|
-
renderedLength += cardLength(entry, selected.length > 1);
|
|
51
|
-
}
|
|
52
|
-
if (selected.length > budget.entryCap || renderedLength > budget.indexCodePointCap) {
|
|
53
|
-
return {
|
|
54
|
-
entries: selected,
|
|
55
|
-
omittedIds: [...omittedIds, ...candidates.map((entry) => entry.id)],
|
|
56
|
-
reason: overBudgetReason(selected),
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
for (const entry of candidates) {
|
|
60
|
-
if (selected.length >= budget.entryCap || renderedLength + cardLength(entry, selected.length > 0) > budget.indexCodePointCap) {
|
|
61
|
-
omittedIds.push(entry.id);
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
selected.push(entry);
|
|
65
|
-
renderedLength += cardLength(entry, selected.length > 1);
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
entries: selected,
|
|
69
|
-
omittedIds,
|
|
70
|
-
reason: omittedIds.length === entries.filter((item) => !PROJECTABLE_STATUSES.has(item.status)).length
|
|
71
|
-
? null
|
|
72
|
-
: "index_over_budget",
|
|
73
|
-
};
|
|
74
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { lstat, mkdir, readdir, realpath, rename, rm, writeFile, readFile } from "node:fs/promises";
|
|
3
|
-
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
const CandidateSchema = z.object({
|
|
6
|
-
operation: z.enum(["create", "update", "supersede"]),
|
|
7
|
-
memoryId: z.string().nullable(),
|
|
8
|
-
baseRevision: z.number().int().nonnegative().nullable(),
|
|
9
|
-
supersedesMemoryId: z.string().nullable(),
|
|
10
|
-
title: z.string().min(1),
|
|
11
|
-
kind: z.enum(["procedural", "semantic", "episodic", "preference"]),
|
|
12
|
-
summary: z.string().min(1),
|
|
13
|
-
triggers: z.array(z.string()).min(1),
|
|
14
|
-
priority: z.enum(["high", "medium", "low"]),
|
|
15
|
-
detailCategory: z.enum(["preferences", "project-rules", "testing", "debugging", "git", "incidents", "lessons"]),
|
|
16
|
-
detailBody: z.string().min(1),
|
|
17
|
-
reviewAfter: z.string().nullable(),
|
|
18
|
-
}).strict();
|
|
19
|
-
const PendingSchema = z.object({
|
|
20
|
-
schemaVersion: z.literal(1),
|
|
21
|
-
pendingId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
|
|
22
|
-
createdAt: z.string().min(1),
|
|
23
|
-
reason: z.literal("index_over_budget"),
|
|
24
|
-
source: z.object({
|
|
25
|
-
executionId: z.string().min(1),
|
|
26
|
-
pruneTraceId: z.string().min(1).nullable(),
|
|
27
|
-
}).strict(),
|
|
28
|
-
candidate: CandidateSchema,
|
|
29
|
-
}).strict();
|
|
30
|
-
function within(root, candidate) {
|
|
31
|
-
const path = relative(root, candidate);
|
|
32
|
-
return path === "" || (path !== ".." && !path.startsWith(`..${sep}`));
|
|
33
|
-
}
|
|
34
|
-
async function pendingDirectory(agentDir) {
|
|
35
|
-
const agentRoot = await realpath(agentDir);
|
|
36
|
-
const memoryRoot = resolve(agentRoot, ".crew", "memory");
|
|
37
|
-
await mkdir(memoryRoot, { recursive: true });
|
|
38
|
-
const memoryStat = await lstat(memoryRoot);
|
|
39
|
-
if (!memoryStat.isDirectory() || memoryStat.isSymbolicLink())
|
|
40
|
-
throw new Error("local_memory_pending_path_invalid");
|
|
41
|
-
const resolvedMemoryRoot = await realpath(memoryRoot);
|
|
42
|
-
if (resolvedMemoryRoot !== memoryRoot || !within(agentRoot, resolvedMemoryRoot)) {
|
|
43
|
-
throw new Error("local_memory_pending_path_invalid");
|
|
44
|
-
}
|
|
45
|
-
const directory = join(memoryRoot, "pending");
|
|
46
|
-
try {
|
|
47
|
-
const stat = await lstat(directory);
|
|
48
|
-
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
49
|
-
throw new Error("local_memory_pending_path_invalid");
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
if (error.code !== "ENOENT")
|
|
53
|
-
throw error;
|
|
54
|
-
await mkdir(directory);
|
|
55
|
-
}
|
|
56
|
-
const resolvedDirectory = await realpath(directory);
|
|
57
|
-
if (resolvedDirectory !== directory || !within(agentRoot, resolvedDirectory)) {
|
|
58
|
-
throw new Error("local_memory_pending_path_invalid");
|
|
59
|
-
}
|
|
60
|
-
return directory;
|
|
61
|
-
}
|
|
62
|
-
async function assertRegularFile(path) {
|
|
63
|
-
try {
|
|
64
|
-
const stat = await lstat(path);
|
|
65
|
-
if (!stat.isFile() || stat.isSymbolicLink())
|
|
66
|
-
throw new Error("local_memory_pending_path_invalid");
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
if (error.code !== "ENOENT")
|
|
70
|
-
throw error;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
async function atomicWrite(path, content) {
|
|
74
|
-
const temporary = join(dirname(path), `.${basename(path)}.tmp-${randomUUID()}`);
|
|
75
|
-
try {
|
|
76
|
-
await writeFile(temporary, content, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
77
|
-
await rename(temporary, path);
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
await rm(temporary, { force: true }).catch(() => undefined);
|
|
81
|
-
throw error;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
export async function persistPendingMemoryCandidate(input) {
|
|
85
|
-
const parsed = PendingSchema.safeParse(input.candidate);
|
|
86
|
-
if (!parsed.success)
|
|
87
|
-
throw new Error("local_memory_pending_invalid");
|
|
88
|
-
const directory = await pendingDirectory(input.agentDir);
|
|
89
|
-
const target = join(directory, `${parsed.data.pendingId}.json`);
|
|
90
|
-
await assertRegularFile(target);
|
|
91
|
-
await atomicWrite(target, `${JSON.stringify(parsed.data, null, 2)}\n`);
|
|
92
|
-
return target;
|
|
93
|
-
}
|
|
94
|
-
export async function readPendingMemoryCandidates(input) {
|
|
95
|
-
const directory = await pendingDirectory(input.agentDir);
|
|
96
|
-
const names = (await readdir(directory)).filter((name) => name.endsWith(".json")).sort();
|
|
97
|
-
const result = [];
|
|
98
|
-
for (const name of names) {
|
|
99
|
-
const path = join(directory, name);
|
|
100
|
-
try {
|
|
101
|
-
await assertRegularFile(path);
|
|
102
|
-
const parsed = PendingSchema.safeParse(JSON.parse(await readFile(path, "utf8")));
|
|
103
|
-
if (parsed.success && `${parsed.data.pendingId}.json` === name)
|
|
104
|
-
result.push(parsed.data);
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// A damaged pending item is diagnosable and must not hide other candidates.
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return result;
|
|
111
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { lstat, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { applyLocalMemoryResult, parseLocalMemoryResult } from "./write.js";
|
|
5
|
-
export function isLocalMemoryV2Eligible(input) {
|
|
6
|
-
return input.enabledForHandle
|
|
7
|
-
&& (input.localMemoryV2Initialized || (!input.agentHomePreexisted && input.memorySeedCreated))
|
|
8
|
-
&& input.memoryPrune;
|
|
9
|
-
}
|
|
10
|
-
const LOCAL_MEMORY_V2_MARKER = "v2-enabled";
|
|
11
|
-
export async function initializeLocalMemoryV2(agentDir) {
|
|
12
|
-
const crewDir = join(agentDir, ".crew");
|
|
13
|
-
const memoryDir = join(crewDir, "memory");
|
|
14
|
-
await mkdir(crewDir, { recursive: true });
|
|
15
|
-
const crewStat = await lstat(crewDir);
|
|
16
|
-
if (!crewStat.isDirectory() || crewStat.isSymbolicLink())
|
|
17
|
-
throw new Error("local_memory_marker_path_invalid");
|
|
18
|
-
await mkdir(memoryDir, { recursive: true });
|
|
19
|
-
const memoryStat = await lstat(memoryDir);
|
|
20
|
-
if (!memoryStat.isDirectory() || memoryStat.isSymbolicLink())
|
|
21
|
-
throw new Error("local_memory_marker_path_invalid");
|
|
22
|
-
try {
|
|
23
|
-
await writeFile(join(memoryDir, LOCAL_MEMORY_V2_MARKER), "1\n", { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
if (error.code !== "EEXIST")
|
|
27
|
-
throw error;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
export async function initializeLocalMemoryV2ForWorkspace(workspace, enabledForHandle) {
|
|
31
|
-
const initialized = workspace.localMemoryV2Initialized === true;
|
|
32
|
-
if (!enabledForHandle || initialized || workspace.localMemoryV2Initialized !== false
|
|
33
|
-
|| workspace.agentHomePreexisted === true || workspace.memorySeedCreated !== true)
|
|
34
|
-
return initialized;
|
|
35
|
-
try {
|
|
36
|
-
await initializeLocalMemoryV2(workspace.dir);
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
export function localMemoryResultPath(runDir, executionId) {
|
|
44
|
-
const suffix = createHash("sha256").update(executionId, "utf8").digest("hex").slice(0, 32);
|
|
45
|
-
return join(runDir, `.memory-result.v1-${suffix}.json`);
|
|
46
|
-
}
|
|
47
|
-
export function renderLocalMemoryResultInstructions(resultPath) {
|
|
48
|
-
return `
|
|
49
|
-
|
|
50
|
-
## Structured memory prune (daemon-managed)
|
|
51
|
-
本轮长期记忆收尾只提交候选,不要直接编辑 $CREW_HOME/MEMORY.md 或 $CREW_HOME/notes/。
|
|
52
|
-
将一个合法 JSON 对象写入以下 daemon 提供的文件,写入完成后再结束本轮:
|
|
53
|
-
${resultPath}
|
|
54
|
-
|
|
55
|
-
JSON 顶层必须包含:
|
|
56
|
-
{"schemaVersion":1,"purpose":"memory_prune","baseCatalogRevision":0,"outcome":"no_change","changes":[],"statusChanges":[]}
|
|
57
|
-
每个 changes 项使用 operation=create|update|supersede、title、kind、summary、triggers、priority、detailCategory、detailBody、reviewAfter;create 的 memoryId/baseRevision/supersedesMemoryId 必须为 null,update/supersede 按现有索引填写版本和 id。
|
|
58
|
-
只提交可复用、与本次任务明确相关的少量候选;不要写入凭证、token、隐私或内部控制标记。daemon 会校验、去重、限制 MEMORY.md 大小,超出索引容量的候选会保存在 pending 中等待后续治理。
|
|
59
|
-
`;
|
|
60
|
-
}
|
|
61
|
-
export async function commitLocalMemoryResultAfterExit(input) {
|
|
62
|
-
if (input.exitCode !== 0)
|
|
63
|
-
return { status: "skipped" };
|
|
64
|
-
let raw;
|
|
65
|
-
try {
|
|
66
|
-
raw = await readFile(input.resultPath, "utf8");
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
if (error.code === "ENOENT")
|
|
70
|
-
return { status: "missing" };
|
|
71
|
-
return { status: "failed", error: error instanceof Error ? error.message : "read_failed" };
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
const result = parseLocalMemoryResult(raw);
|
|
75
|
-
await applyLocalMemoryResult({
|
|
76
|
-
agentDir: input.agentDir,
|
|
77
|
-
result,
|
|
78
|
-
executionId: input.executionId,
|
|
79
|
-
pruneTraceId: input.pruneTraceId,
|
|
80
|
-
});
|
|
81
|
-
await rm(input.resultPath, { force: true });
|
|
82
|
-
return { status: "applied" };
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
return { status: "failed", error: error instanceof Error ? error.message : "apply_failed" };
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|