@nowcrew/daemon 0.6.89 → 0.6.91-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -3
- package/dist/agent-ability/materializer.js +21 -10
- package/dist/agent-environment-contract.js +55 -0
- package/dist/agent-environment-control.js +24 -0
- package/dist/agent-environment-execution.js +98 -0
- package/dist/agent-environments.js +275 -0
- package/dist/automatic-daemon-installer.js +49 -1
- package/dist/computer-main.js +18 -0
- package/dist/computer-profile.js +17 -4
- package/dist/computer-setup-client.js +318 -0
- package/dist/computer-setup-orchestration.js +203 -0
- package/dist/computer-setup.js +244 -0
- package/dist/config.js +30 -12
- package/dist/console-formatter.js +3 -3
- package/dist/console.js +15 -2
- package/dist/daemon-update-eligibility.js +1 -1
- package/dist/daemon-updater.js +2 -12
- package/dist/execution-journal.js +9 -6
- package/dist/execution-protocol.js +1 -0
- package/dist/execution-runner.js +13 -8
- package/dist/i18n.js +19 -0
- package/dist/list-models.js +12 -12
- package/dist/local-execution-lease.js +22 -0
- package/dist/local-executor.js +57 -84
- package/dist/local-memory/candidate.js +43 -0
- package/dist/local-memory/index-budget.js +8 -0
- package/dist/local-memory/inventory-cli.js +61 -0
- package/dist/local-memory/inventory-preview.js +159 -0
- package/dist/local-memory/inventory-source.js +159 -0
- package/dist/local-memory/pending-match.js +29 -0
- package/dist/local-memory/pending-query.js +100 -0
- package/dist/local-memory/pending-schema.js +29 -0
- package/dist/local-memory/pending-source.js +126 -0
- package/dist/local-memory/pending.js +34 -84
- package/dist/local-memory/prompt.js +19 -12
- package/dist/local-memory/read-cli.js +97 -12
- package/dist/local-memory/rollout.js +82 -0
- package/dist/local-memory/runtime.js +23 -17
- package/dist/local-memory/work-log-execution.js +31 -0
- package/dist/local-memory/work-log.js +58 -0
- package/dist/local-memory/write.js +27 -58
- package/dist/local-runtime-environment.js +50 -0
- package/dist/machine-environment-cache.js +147 -0
- package/dist/machine-environment-contract.js +10 -0
- package/dist/machine-environment-control.js +27 -0
- package/dist/machine-environment-policy.js +75 -0
- package/dist/machine-environment-process.js +110 -0
- package/dist/machine-info.js +70 -25
- package/dist/machine-runtime-environment.js +189 -0
- package/dist/machine-shell-environment-capture.js +135 -0
- package/dist/machine-windows-environment-capture.js +215 -0
- package/dist/machine-windows-environment-script.js +158 -0
- package/dist/main.js +4 -69
- package/dist/marketplace-command.js +21 -0
- package/dist/marketplace-controller.js +80 -0
- package/dist/marketplace-protocol.js +38 -0
- package/dist/marketplace-remote.js +48 -0
- package/dist/marketplace-runner.js +235 -0
- package/dist/marketplace-serve.js +41 -0
- package/dist/marketplace-source.js +103 -0
- package/dist/message-file-preview.js +246 -0
- package/dist/pre-admission-rejection.js +64 -0
- package/dist/project-skills/execution-adapter.js +3 -2
- package/dist/project-skills/runtime-launch.js +14 -0
- package/dist/provider-env.js +26 -15
- package/dist/runner.js +17 -4
- package/dist/runtime-environment-merge.js +16 -0
- package/dist/runtime-health-handler.js +10 -0
- package/dist/runtime-health.js +3 -1
- package/dist/runtime-path.js +22 -12
- package/dist/runtime-probe.js +35 -6
- package/dist/runtimes/acp-probe-transport.js +69 -0
- package/dist/runtimes/claude-models.js +20 -9
- package/dist/runtimes/deepseek-harness-launch.js +10 -0
- package/dist/runtimes/deepseek-harness-profile.js +92 -0
- package/dist/runtimes/deepseek-harness-runner.js +226 -0
- package/dist/runtimes/deepseek-harness.js +76 -15
- package/dist/runtimes/harness-probe-retry.js +42 -0
- package/dist/runtimes/hermes-models.js +2 -1
- package/dist/runtimes/kimi-acp-runner.js +78 -13
- package/dist/runtimes/opencode.js +2 -2
- package/dist/serve.js +88 -178
- package/dist/storage-identity.js +0 -3
- package/dist/storage-serve-adapter.js +6 -72
- package/dist/supervised-runtime.js +17 -2
- package/dist/token.js +0 -1
- package/dist/workspace-control-handler.js +27 -0
- package/dist/workspace-fs.js +66 -15
- package/dist/workspace.js +1 -1
- package/package.json +13 -12
- package/dist/codex-home-launch.js +0 -7
- package/dist/codex-home-mode-controller.js +0 -68
- package/dist/daemon-migration-controller.js +0 -211
- package/dist/daemon-migration-wiring.js +0 -22
- package/dist/runtimes/codex-home-migration-cli.js +0 -26
- package/dist/runtimes/codex-home-migration.js +0 -112
- package/dist/runtimes/codex-home.js +0 -236
- package/dist/storage-control.js +0 -212
- package/dist/storage-copy.js +0 -628
- package/dist/storage-credential-guard.js +0 -21
- package/dist/storage-doctor.js +0 -228
- package/dist/storage-manifest.js +0 -1027
- package/dist/storage-migration-controller.js +0 -440
- package/dist/storage-migration-manifest.js +0 -105
- package/dist/storage-migration-policy.js +0 -100
- package/dist/storage-runtime-controller.js +0 -381
- package/dist/storage-runtime-readiness.js +0 -241
- package/dist/storage-safe-fs.js +0 -302
- package/dist/storage-status-delivery.js +0 -224
- package/dist/storage-status-replay.js +0 -140
- package/dist/storage-target-confirmation.js +0 -129
- package/dist/storage-worker-command.js +0 -37
- package/dist/storage-worker-executor.js +0 -447
- package/dist/storage-worker-handoff.js +0 -151
- package/dist/storage-worker-operation.js +0 -294
- package/dist/storage-worker-status.js +0 -259
package/dist/execution-runner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { resolveReposRoot } from "./config.js";
|
|
3
|
+
import { isLocalMemoryV2Enabled, localMemoryV2Environment, resolveReposRoot } from "./config.js";
|
|
4
4
|
import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, ExecutionRejectedSchema, ExecutionStartSchema, } from "./execution-protocol.js";
|
|
5
5
|
import { JournalConflictError } from "./execution-journal.js";
|
|
6
6
|
import { boundExecutionFrame } from "./execution-event-limit.js";
|
|
@@ -210,6 +210,7 @@ function launchProviderConfig(config) {
|
|
|
210
210
|
? {}
|
|
211
211
|
: { providerSmallFastModel: config.providerSmallFastModel }),
|
|
212
212
|
...(config.envVars === undefined ? {} : { envVars: config.envVars }),
|
|
213
|
+
...(config.environmentPlan === undefined ? {} : { environmentPlan: config.environmentPlan }),
|
|
213
214
|
...(config.description === undefined ? {} : { description: config.description }),
|
|
214
215
|
};
|
|
215
216
|
}
|
|
@@ -408,7 +409,8 @@ export async function runExecution(config, input, dependencies) {
|
|
|
408
409
|
? { permission: effectivePermission(spec, config) }
|
|
409
410
|
: admission(spec, config, dependencies, initialAt);
|
|
410
411
|
if ("rejected" in checked) {
|
|
411
|
-
const
|
|
412
|
+
const proof = await dependencies.confirmPreAdmission?.(checked.rejected).catch(() => false);
|
|
413
|
+
const frame = ExecutionRejectedSchema.parse(boundExecutionFrame({ ...checked.rejected, ...(proof ? { preAdmissionProof: { specHash } } : {}) }, config.executionLimits.maxEventBytes));
|
|
412
414
|
await dependencies.report(frame);
|
|
413
415
|
return { kind: "rejected", frame };
|
|
414
416
|
}
|
|
@@ -745,6 +747,7 @@ export async function runExecution(config, input, dependencies) {
|
|
|
745
747
|
},
|
|
746
748
|
});
|
|
747
749
|
const localDependencies = {
|
|
750
|
+
...(dependencies.machineEnvironment === undefined ? {} : { machineEnvironment: dependencies.machineEnvironment }),
|
|
748
751
|
...(dependencies.cancellation === undefined ? {} : { cancellation: dependencies.cancellation }),
|
|
749
752
|
...(dependencies.startupGate === undefined ? {} : { startupGate: dependencies.startupGate }),
|
|
750
753
|
...(dependencies.startupTimeoutMs === undefined
|
|
@@ -829,12 +832,15 @@ export async function runExecution(config, input, dependencies) {
|
|
|
829
832
|
const baseSystemPromptBudget = spec.agent.abilityRelease === undefined
|
|
830
833
|
? systemPromptBudget
|
|
831
834
|
: Math.max(0, Math.min(systemPromptBudget, Math.max(32 * 1024, systemPromptBudget - 168 * 1024)));
|
|
832
|
-
const localMemoryV2Enabled = config
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex",
|
|
836
|
-
});
|
|
835
|
+
const localMemoryV2Enabled = !selfTraining && isLocalMemoryV2Enabled(config, spec.agent.handle);
|
|
836
|
+
// 开发环境的系统/当前轮写保护由 prepareLocalMemoryPrompt 统一注入并预留预算。
|
|
837
|
+
const localMemoryGuardInBase = localMemoryV2Environment(config.serverUrl) === "custom";
|
|
837
838
|
const boundedSystemPrompt = (context) => {
|
|
839
|
+
const enabled = context.workspace.localMemoryV2Enabled ?? localMemoryV2Enabled;
|
|
840
|
+
const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${enabled && localMemoryGuardInBase ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
|
|
841
|
+
...(selfTraining ? { isolateLocalMemory: true } : {}),
|
|
842
|
+
collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex",
|
|
843
|
+
});
|
|
838
844
|
const base = typeof systemPromptWithLocalFacts === "string"
|
|
839
845
|
? systemPromptWithLocalFacts
|
|
840
846
|
: systemPromptWithLocalFacts(context);
|
|
@@ -877,7 +883,6 @@ export async function runExecution(config, input, dependencies) {
|
|
|
877
883
|
...(config.qaReportRoot === undefined ? {} : { qaReportRoot: config.qaReportRoot }),
|
|
878
884
|
cliPath: config.cliPath,
|
|
879
885
|
providerConfig,
|
|
880
|
-
codexHomeMode: config.codexHomeMode,
|
|
881
886
|
...(providerConfig.description ? { description: providerConfig.description } : {}),
|
|
882
887
|
...(spec.reporting.allowBoundImDecision ? {
|
|
883
888
|
systemEnv: {
|
package/dist/i18n.js
CHANGED
|
@@ -18,6 +18,11 @@ export function detectDaemonLang(env = process.env) {
|
|
|
18
18
|
const zh = {
|
|
19
19
|
"Claude session started": "Claude 会话启动",
|
|
20
20
|
"Codex session started": "Codex 会话启动",
|
|
21
|
+
"Kimi session started": "Kimi 会话启动",
|
|
22
|
+
"Hermes session started": "Hermes 会话启动",
|
|
23
|
+
"OpenCode session started": "OpenCode 会话启动",
|
|
24
|
+
"DeepSeek Harness session started": "DeepSeek Harness 会话启动",
|
|
25
|
+
"Session started": "会话启动",
|
|
21
26
|
"Plan updated": "计划更新",
|
|
22
27
|
"Thinking (~{{tokens}} tokens, content not streamed by Claude Code)": "思考(约 {{tokens}} tokens,Claude Code 未输出思考正文)",
|
|
23
28
|
"Thinking (content not streamed by Claude Code)": "思考(Claude Code 未输出思考正文)",
|
|
@@ -26,6 +31,20 @@ const zh = {
|
|
|
26
31
|
"Run finished": "本轮结束",
|
|
27
32
|
"Missing CREW_MACHINE_TOKEN (sk_machine_*, printed by seed)": "缺少 CREW_MACHINE_TOKEN(sk_machine_*,由 seed 打印)",
|
|
28
33
|
"Usage:": "用法:",
|
|
34
|
+
"Open {{url}}": "打开 {{url}}",
|
|
35
|
+
"Code {{code}}": "验证码 {{code}}",
|
|
36
|
+
"Browser open skipped: {{reason}}": "未能自动打开浏览器:{{reason}}",
|
|
37
|
+
"Starting computer setup pairing...": "正在发起电脑接入授权…",
|
|
38
|
+
"Workspace: {{value}}": "工作区:{{value}}",
|
|
39
|
+
"Machine: {{value}}": "机器: {{value}}",
|
|
40
|
+
"Environment: {{value}}": "环境:{{value}}",
|
|
41
|
+
"Daemon: profile {{profile}} / v{{version}} / {{status}}": "Daemon:配置 {{profile}} / v{{version}} / {{status}}",
|
|
42
|
+
"Local: {{value}}": "本地目录:{{value}}",
|
|
43
|
+
"Config: {{value}}": "配置目录:{{value}}",
|
|
44
|
+
"Agents: {{value}}": "Agent 目录:{{value}}",
|
|
45
|
+
"Storage ID: {{value}}": "存储标识:{{value}}",
|
|
46
|
+
"Status: connected and ready": "状态:已连接并就绪",
|
|
47
|
+
"Status: not ready": "状态:尚未就绪",
|
|
29
48
|
"connect and stay resident": "连接并常驻",
|
|
30
49
|
"run once manually": "手动运行一次",
|
|
31
50
|
"resident, connecting to": "常驻,连接到",
|
package/dist/list-models.js
CHANGED
|
@@ -8,8 +8,8 @@ const execFileRaw = promisify(execFile);
|
|
|
8
8
|
const MODEL_PROBE_TIMEOUT_MS = 8_000;
|
|
9
9
|
const MODEL_PROBE_MAX_BUFFER_BYTES = 1024 * 1024;
|
|
10
10
|
// win32 上 npm CLI 是 .cmd shim,execFile 需 shell 才能执行;参数全是固定字面量,无注入面。
|
|
11
|
-
const execFileP = (bin, args) => execFileRaw(bin, args, {
|
|
12
|
-
env
|
|
11
|
+
const execFileP = (bin, args, env = { ...process.env, PATH: augmentedPath() }) => execFileRaw(bin, args, {
|
|
12
|
+
env,
|
|
13
13
|
shell: isWin(),
|
|
14
14
|
timeout: MODEL_PROBE_TIMEOUT_MS,
|
|
15
15
|
killSignal: "SIGKILL",
|
|
@@ -18,17 +18,17 @@ const execFileP = (bin, args) => execFileRaw(bin, args, {
|
|
|
18
18
|
export async function listRuntimeModels(runtime, options = {}) {
|
|
19
19
|
switch (runtime) {
|
|
20
20
|
case "claude":
|
|
21
|
-
return listClaudeModels();
|
|
21
|
+
return listClaudeModels(undefined, options.env, options.environmentGeneration);
|
|
22
22
|
case "codex":
|
|
23
|
-
return parseCodexModels((await execFileP("codex", ["debug", "models"])).stdout);
|
|
23
|
+
return parseCodexModels((await execFileP("codex", ["debug", "models"], options.env)).stdout);
|
|
24
24
|
case "cursor":
|
|
25
|
-
return parseCursorModels((await execFileP("cursor-agent", ["--list-models"])).stdout);
|
|
25
|
+
return parseCursorModels((await execFileP("cursor-agent", ["--list-models"], options.env)).stdout);
|
|
26
26
|
case "hermes":
|
|
27
27
|
return listHermesModels(options);
|
|
28
28
|
case "opencode":
|
|
29
|
-
return listOpencodeModels();
|
|
29
|
+
return listOpencodeModels(options.env);
|
|
30
30
|
case "pi":
|
|
31
|
-
return parsePiModels((await execFileP("pi", ["--list-models"])).stdout);
|
|
31
|
+
return parsePiModels((await execFileP("pi", ["--list-models"], options.env)).stdout);
|
|
32
32
|
default:
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
@@ -84,9 +84,9 @@ function parseCursorModels(stdout) {
|
|
|
84
84
|
return { id: id.trim(), label: (label || id).trim(), ...(index === 0 ? { default: true } : {}) };
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
|
-
async function outputEvenOnFailure(bin, args) {
|
|
87
|
+
async function outputEvenOnFailure(bin, args, env) {
|
|
88
88
|
try {
|
|
89
|
-
return (await execFileP(bin, args)).stdout;
|
|
89
|
+
return (await execFileP(bin, args, env)).stdout;
|
|
90
90
|
}
|
|
91
91
|
catch (error) {
|
|
92
92
|
const stdout = error.stdout;
|
|
@@ -97,11 +97,11 @@ async function outputEvenOnFailure(bin, args) {
|
|
|
97
97
|
return "";
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
-
async function listOpencodeModels() {
|
|
101
|
-
const verbose = parseOpencodeModels(await outputEvenOnFailure("opencode", ["models", "--verbose"]));
|
|
100
|
+
async function listOpencodeModels(env) {
|
|
101
|
+
const verbose = parseOpencodeModels(await outputEvenOnFailure("opencode", ["models", "--verbose"], env));
|
|
102
102
|
if (verbose.length > 0)
|
|
103
103
|
return verbose;
|
|
104
|
-
return parseOpencodeModels(await outputEvenOnFailure("opencode", ["models"]));
|
|
104
|
+
return parseOpencodeModels(await outputEvenOnFailure("opencode", ["models"], env));
|
|
105
105
|
}
|
|
106
106
|
const VARIANT_ORDER = new Map([
|
|
107
107
|
["none", 0], ["minimal", 1], ["low", 2], ["medium", 3],
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { awaitWithCancellation } from "./runtime-cancellation.js";
|
|
2
|
+
const leaseTails = new Map();
|
|
3
|
+
/** 同一进程内的短操作或原生会话排队;等待取消也不会越过前一位持有者。 */
|
|
4
|
+
export async function withKeyedLease(key, operation, cancellation) {
|
|
5
|
+
const predecessor = leaseTails.get(key) ?? Promise.resolve();
|
|
6
|
+
// 解锁函数在等待 predecessor 前可用,保留取消等待时的队列顺序。
|
|
7
|
+
let unlock;
|
|
8
|
+
const current = new Promise(resolve => { unlock = resolve; });
|
|
9
|
+
const queued = predecessor.then(() => current);
|
|
10
|
+
leaseTails.set(key, queued);
|
|
11
|
+
try {
|
|
12
|
+
await awaitWithCancellation(predecessor, cancellation);
|
|
13
|
+
return await operation();
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
unlock();
|
|
17
|
+
void queued.then(() => {
|
|
18
|
+
if (leaseTails.get(key) === queued)
|
|
19
|
+
leaseTails.delete(key);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/local-executor.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
|
+
import { deepSeekHarnessLauncher } from "./runtimes/deepseek-harness.js";
|
|
2
|
+
import { buildLocalRuntimeEnvironment } from "./local-runtime-environment.js";
|
|
3
|
+
import { environmentForExecution } from "./agent-environments.js";
|
|
4
|
+
import { prepareEnvironmentExecutionSnapshot } from "./agent-environment-execution.js";
|
|
1
5
|
import { createInterface } from "node:readline";
|
|
2
6
|
import { readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import {
|
|
7
|
+
import { join } from "node:path";
|
|
4
8
|
import { cleanupSelfTrainingRunDir } from "./self-training-workspace.js";
|
|
5
9
|
import { prepareWorkspace, rotateAgentSession, safeKey, } from "./workspace.js";
|
|
6
10
|
import { CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_ENV, isClaudeAdditionalDirectoryInstructionsSupported, probeClaudeVersion, spawnClaude, } from "./runtimes/claude.js";
|
|
7
11
|
import { spawnCodex } from "./runtimes/codex.js";
|
|
8
12
|
import { DEEPSEEK_CODEX_MODEL, DEEPSEEK_CODEX_REASONING_LEVELS, materializeDeepSeekCodexHome, } from "./runtimes/codex-deepseek-config.js";
|
|
9
|
-
import {
|
|
10
|
-
import { materializePrivateCodexHome } from "./codex-home-launch.js";
|
|
11
|
-
import { spawnKimi, KIMI_EFFORT_LEVELS } from "./runtimes/kimi.js";
|
|
13
|
+
import { spawnKimi } from "./runtimes/kimi.js";
|
|
12
14
|
import { applyDeepSeekHarnessMachineEnv, applyProviderEnv, providerFingerprint, } from "./provider-env.js";
|
|
13
|
-
import { augmentedPath } from "./runtime-path.js";
|
|
14
15
|
import { deliverCloudReports, prepareCloudReportExecution } from "./cloud-report-uploader.js";
|
|
15
16
|
import { reportDeliveryContext } from "./cloud-report-receipt.js";
|
|
16
17
|
import { extractFinalText, extractRunMeta, normalizeEvent, parseLine, } from "./normalize.js";
|
|
17
18
|
import { readSession, writeSession, pickResumeId, isNearBudget } from "./session.js";
|
|
18
19
|
import { NativeThreadContext, isolateMaintenanceSession } from "./native-thread-context.js";
|
|
19
20
|
import { prepareThreadDelivery } from "./thread-input-delivery.js";
|
|
21
|
+
import { resolveMemoryPruneWorkLog, workLogSha256 } from "./local-memory/work-log.js";
|
|
22
|
+
import { withKeyedLease } from "./local-execution-lease.js";
|
|
23
|
+
import { withWorkLogExecution, withWorkLogCommit, workLogExecutionKey } from "./local-memory/work-log-execution.js";
|
|
20
24
|
import { providerErrorFromExit } from "./provider-error.js";
|
|
21
25
|
import { createConsoleFormatter } from "./console-formatter.js";
|
|
22
26
|
import { withLocalExecutionFacts, } from "./local-execution-prompt.js";
|
|
@@ -37,11 +41,11 @@ import { diffMemoryPruneNotes, evaluateMemoryPrunePostcondition, inspectMemoryPr
|
|
|
37
41
|
import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
|
|
38
42
|
import { CodexStartupStageParser } from "./codex-startup-stage.js";
|
|
39
43
|
import { formatRuntimeStartupError } from "./runtime-startup-error.js";
|
|
40
|
-
import { machinePackageManagerEnv } from "./package-manager-env.js";
|
|
41
44
|
import { logRuntimeEnvironmentContract } from "./runtime-environment-telemetry.js";
|
|
42
|
-
import { sanitizeEnvVars } from "./runtime-env.js";
|
|
43
45
|
import { commitLocalMemoryResultAfterExit, initializeLocalMemoryV2ForWorkspace, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, } from "./local-memory/runtime.js";
|
|
44
46
|
import { prepareLocalMemoryPrompt } from "./local-memory/prompt.js";
|
|
47
|
+
import { reportLocalMemoryV2Deferred, resolveLocalMemoryV2Rollout } from "./local-memory/rollout.js";
|
|
48
|
+
import { localMemoryV2Environment } from "./config.js";
|
|
45
49
|
export class RuntimeRunningCallbackTimeoutError extends Error {
|
|
46
50
|
constructor(runtime, timeoutMs) {
|
|
47
51
|
super(`${runtime} runtime-running callback timed out after ${timeoutMs}ms`);
|
|
@@ -159,28 +163,12 @@ async function launchLegacyRuntime(request) {
|
|
|
159
163
|
function resolvePrompt(prompt, context) {
|
|
160
164
|
return typeof prompt === "string" ? prompt : prompt(context);
|
|
161
165
|
}
|
|
162
|
-
const nativeSessionLeaseTails = new Map();
|
|
163
166
|
const activeMemoryPrunes = new Map();
|
|
164
|
-
async function withKeyedLease(key, operation, cancellation) {
|
|
165
|
-
const predecessor = nativeSessionLeaseTails.get(key) ?? Promise.resolve();
|
|
166
|
-
let release;
|
|
167
|
-
const current = new Promise((resolve) => { release = resolve; });
|
|
168
|
-
const tail = predecessor.then(() => current);
|
|
169
|
-
nativeSessionLeaseTails.set(key, tail);
|
|
170
|
-
try {
|
|
171
|
-
await awaitWithCancellation(predecessor, cancellation);
|
|
172
|
-
return await operation();
|
|
173
|
-
}
|
|
174
|
-
finally {
|
|
175
|
-
release();
|
|
176
|
-
void tail.then(() => {
|
|
177
|
-
if (nativeSessionLeaseTails.get(key) === tail)
|
|
178
|
-
nativeSessionLeaseTails.delete(key);
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
167
|
export async function executeLocal(input, callbacks = {}, dependencies = {}) {
|
|
183
168
|
input = isolateMaintenanceSession(input);
|
|
169
|
+
return withWorkLogExecution(workLogExecutionKey(input.launch.agentsRoot, input.handle, input.taskKey), () => executeLocalWithSession(input, callbacks, dependencies), dependencies.cancellation);
|
|
170
|
+
}
|
|
171
|
+
async function executeLocalWithSession(input, callbacks, dependencies) {
|
|
184
172
|
const versionedProjectSkills = input.projectSkills !== undefined
|
|
185
173
|
&& !Array.isArray(input.projectSkills);
|
|
186
174
|
if (versionedProjectSkills && (input.runtime.name !== "codex" && input.runtime.name !== "claude")) {
|
|
@@ -248,7 +236,12 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
248
236
|
}, "workspace_prepare", error);
|
|
249
237
|
throw error;
|
|
250
238
|
}
|
|
251
|
-
const
|
|
239
|
+
const memoryRollout = await resolveLocalMemoryV2Rollout({ workspace, handle: input.handle,
|
|
240
|
+
serverUrl: input.launch.serverUrl, requested: input.localMemoryV2Enabled === true,
|
|
241
|
+
selfTraining: isolatedMaintenance, ...(input.launch.description ? { description: input.launch.description } : {}) });
|
|
242
|
+
workspace = { ...workspace, localMemoryV2Enabled: memoryRollout.enabled };
|
|
243
|
+
input = { ...input, localMemoryV2Enabled: memoryRollout.enabled };
|
|
244
|
+
const localMemoryV2Initialized = await initializeLocalMemoryV2ForWorkspace(workspace, memoryRollout.enabled);
|
|
252
245
|
let materialized = null;
|
|
253
246
|
let knownAttachmentDirectory = null;
|
|
254
247
|
const startupReservationState = { current: null };
|
|
@@ -259,10 +252,12 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
259
252
|
let memoryPruneBeforeSnapshot = null;
|
|
260
253
|
let memoryPruneSharedWriteKey = null;
|
|
261
254
|
let localMemoryResultPath = null;
|
|
255
|
+
let environmentSnapshot;
|
|
262
256
|
let executionWorkspace = workspace;
|
|
263
257
|
let localMemoryTelemetry = null;
|
|
264
258
|
let nativeThreadContext = null;
|
|
265
|
-
const
|
|
259
|
+
const machineSnapshot = dependencies.machineEnvironment ? await awaitWithCancellation(dependencies.machineEnvironment.acquire(), dependencies.cancellation) : undefined;
|
|
260
|
+
const inheritedEnv = { ...(machineSnapshot?.env ?? process.env) };
|
|
266
261
|
const reportRoots = {
|
|
267
262
|
root: workspace.dir,
|
|
268
263
|
...(input.launch.qaReportRoot === undefined ? {} : { qaReportRoot: input.launch.qaReportRoot }),
|
|
@@ -274,8 +269,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
274
269
|
if (key.startsWith("CREW_AGENT_MEMORY_") || ["CREW_CLOUD_PROVIDER", "CREW_OSS_UPLOAD_SCRIPT", "CREW_OSS_REPORT_UPLOAD"].includes(key))
|
|
275
270
|
delete inheritedEnv[key];
|
|
276
271
|
}
|
|
277
|
-
const sourceCodexHome = inheritedEnv.CODEX_HOME
|
|
278
|
-
?? (inheritedEnv.HOME === undefined ? undefined : join(inheritedEnv.HOME, ".codex"));
|
|
279
272
|
let materializedDeepSeekHome;
|
|
280
273
|
try {
|
|
281
274
|
if (isDeepSeekCodex && !providerConfig.providerApiKey) {
|
|
@@ -356,8 +349,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
356
349
|
const launchSessionId = resumeSessionId ?? (rotated
|
|
357
350
|
? await rotateAgentSession(workspace.sessionDir)
|
|
358
351
|
: workspace.agentSessionId);
|
|
359
|
-
|
|
360
|
-
const promptContext = {
|
|
352
|
+
let promptContext = {
|
|
361
353
|
workspace: executionWorkspace,
|
|
362
354
|
resuming,
|
|
363
355
|
rotatedForBudget,
|
|
@@ -396,8 +388,15 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
396
388
|
}
|
|
397
389
|
}
|
|
398
390
|
const attachmentPlan = routeRuntimeAttachments(runtime.name, materialized?.attachments ?? []);
|
|
399
|
-
|
|
400
|
-
memoryPruneTraceId = parseMemoryPruneTraceId(baseWakePrompt);
|
|
391
|
+
let baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
|
|
392
|
+
memoryPruneTraceId = isolatedMaintenance ? null : parseMemoryPruneTraceId(baseWakePrompt);
|
|
393
|
+
if (memoryRollout.deferred)
|
|
394
|
+
reportLocalMemoryV2Deferred(input.executionId, input.handle, memoryPruneTraceId !== null || input.maintenancePurpose === "memory_prune");
|
|
395
|
+
if (memoryPruneTraceId !== null) {
|
|
396
|
+
executionWorkspace = await resolveMemoryPruneWorkLog(executionWorkspace, input);
|
|
397
|
+
promptContext = { ...promptContext, workspace: executionWorkspace };
|
|
398
|
+
baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
|
|
399
|
+
}
|
|
401
400
|
if (input.localMemoryV2Enabled === true && memoryPruneTraceId !== null
|
|
402
401
|
&& isLocalMemoryV2Eligible({
|
|
403
402
|
enabledForHandle: true,
|
|
@@ -412,6 +411,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
412
411
|
agentDir: executionWorkspace.dir, executionId: input.executionId, agentHandle: input.handle,
|
|
413
412
|
enabled: input.localMemoryV2Enabled === true, query: baseWakePrompt,
|
|
414
413
|
resultPath: localMemoryResultPath, maxBytes: input.maxSystemPromptBytes,
|
|
414
|
+
strictRollout: !isolatedMaintenance && localMemoryV2Environment(input.launch.serverUrl) === "dev",
|
|
415
|
+
deferred: memoryRollout.deferred,
|
|
415
416
|
renderBase: (reservedPromptBytes) => resolvePrompt(input.systemPrompt, { ...promptContext, reservedPromptBytes }),
|
|
416
417
|
});
|
|
417
418
|
localMemoryResultPath = memoryPrompt.resultPath;
|
|
@@ -480,48 +481,9 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
480
481
|
nativeThreadContext = new NativeThreadContext(join(executionWorkspace.runDir, `.crew-context-${input.executionId}.json`), contextThreadId, runtime.name, resumeSessionId, prior);
|
|
481
482
|
await nativeThreadContext.initialize();
|
|
482
483
|
}
|
|
483
|
-
const baseEnv = {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
...(input.trainingProjectContext === undefined
|
|
487
|
-
? {}
|
|
488
|
-
: machinePackageManagerEnv(inheritedEnv)),
|
|
489
|
-
...input.launch.systemEnv,
|
|
490
|
-
PATH: `${workspace.crewDir}${delimiter}${augmentedPath()}`,
|
|
491
|
-
CREW_SERVER_URL: input.launch.serverUrl,
|
|
492
|
-
CREW_TOKEN: input.launch.token,
|
|
493
|
-
CREW_CHANNEL: input.channelId,
|
|
494
|
-
CREW_HOME: crewHome,
|
|
495
|
-
// Empty values override any stale inherited hints from an enclosing runtime.
|
|
496
|
-
CREW_THREAD_ROOT: contextThreadId ?? "",
|
|
497
|
-
CREW_THREAD_CONTEXT_FILE: nativeThreadContext?.path ?? "",
|
|
498
|
-
// Keep the daemon-selected storage root authoritative for runtimes.
|
|
499
|
-
// In profile-backed NowWork sessions the daemon config may differ from
|
|
500
|
-
// the daemon process environment, so inheriting CREW_AGENTS_ROOT is not
|
|
501
|
-
// sufficient for QA/project diagnostics.
|
|
502
|
-
CREW_AGENTS_ROOT: input.launch.agentsRoot,
|
|
503
|
-
...(input.launch.reposRoot === undefined ? {} : { NOWWORK_REPOS_ROOT: input.launch.reposRoot }),
|
|
504
|
-
...(input.launch.qaReportRoot === undefined ? {} : { QA_REPORT_HUB: input.launch.qaReportRoot }),
|
|
505
|
-
// Expose only the training root; ability-specific workspace layout stays
|
|
506
|
-
// owned by the training package rather than the NowWork daemon.
|
|
507
|
-
QA_RUNTIME_ROOT: join(crewHome, "training"),
|
|
508
|
-
QA_REPORT_DELIVERY_CONTEXT: JSON.stringify(reportContext),
|
|
509
|
-
CREW_TASK_DIR: executionWorkspace.runDir,
|
|
510
|
-
CREW_TASK_LOG: executionWorkspace.workLogPath,
|
|
511
|
-
...(input.wakeMessageId ? { CREW_WAKE_MESSAGE_ID: input.wakeMessageId } : {}),
|
|
512
|
-
XDG_CONFIG_HOME: join(workspace.homeDir, ".config"),
|
|
513
|
-
XDG_DATA_HOME: join(workspace.homeDir, ".local", "share"),
|
|
514
|
-
XDG_CACHE_HOME: join(workspace.homeDir, ".cache"),
|
|
515
|
-
GH_CONFIG_DIR: join(workspace.homeDir, ".config", "gh"),
|
|
516
|
-
CLOUDSDK_CONFIG: join(workspace.homeDir, ".config", "gcloud"),
|
|
517
|
-
...(runtime.name === "kimi" && runtime.reasoning
|
|
518
|
-
&& KIMI_EFFORT_LEVELS.includes(runtime.reasoning)
|
|
519
|
-
? { KIMI_MODEL_THINKING_EFFORT: runtime.reasoning }
|
|
520
|
-
: {}),
|
|
521
|
-
};
|
|
522
|
-
const providerEnv = applyProviderEnv(baseEnv, runtime.name, input.launch.codexHomeMode === undefined
|
|
523
|
-
? providerConfig
|
|
524
|
-
: { ...providerConfig, codexHomeMode: input.launch.codexHomeMode }, workspace.homeDir, materializedDeepSeekHome);
|
|
484
|
+
const baseEnv = buildLocalRuntimeEnvironment({ input, workspace, executionWorkspace, inheritedEnv, providerConfig,
|
|
485
|
+
crewHome, contextThreadId, nativeThreadContextPath: nativeThreadContext?.path, reportContext, runtime });
|
|
486
|
+
const providerEnv = applyProviderEnv(baseEnv, runtime.name, providerConfig, workspace.homeDir, materializedDeepSeekHome);
|
|
525
487
|
const childEnv = runtime.name === "deepseek-harness"
|
|
526
488
|
? applyDeepSeekHarnessMachineEnv(providerEnv, inheritedEnv)
|
|
527
489
|
: providerEnv;
|
|
@@ -538,10 +500,15 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
538
500
|
const abilityContext = input.abilityRelease !== undefined && dependencies.abilityRelease !== undefined
|
|
539
501
|
? await dependencies.abilityRelease.prepareAndLaunch(input.launch.agentsRoot, input.handle, input.abilityRelease, async (context) => context)
|
|
540
502
|
: undefined;
|
|
503
|
+
if (providerConfig.environmentPlan !== undefined && !isolatedMaintenance) {
|
|
504
|
+
environmentSnapshot = await prepareEnvironmentExecutionSnapshot({ plan: providerConfig.environmentPlan, agentRoot: workspace.dir, runDir: executionWorkspace.runDir, executionId: input.executionId, ambient: inheritedEnv, runtime: runtime.name, providerConfig, runtimeEnvironment: childEnv, authoritativeKeys: Object.keys(input.launch.systemEnv ?? {}),
|
|
505
|
+
executionBaseEnvironment: childEnv,
|
|
506
|
+
...(machineSnapshot === undefined ? {} : { machineEnvironmentVersion: { bootId: machineSnapshot.status.bootId, generation: machineSnapshot.status.generation } }),
|
|
507
|
+
selectedEnvironment: environmentForExecution(providerConfig.environmentPlan, workspace.dir, input.projectContext?.primary?.root ?? executionWorkspace.runDir, input.projectContext?.primary?.projectId),
|
|
508
|
+
projectRoots: [...(input.projectContext?.primary ? [input.projectContext.primary] : []), ...(input.projectContext?.secondary ?? [])] });
|
|
509
|
+
}
|
|
541
510
|
const prepareRuntimeLaunch = async (runtimeRoot) => {
|
|
542
|
-
const effectiveSystemPrompt = abilityContext
|
|
543
|
-
? systemPrompt
|
|
544
|
-
: `${systemPrompt}\n\n${abilityContext.prompt}`;
|
|
511
|
+
const effectiveSystemPrompt = [systemPrompt, abilityContext?.prompt, environmentSnapshot?.prompt].filter(Boolean).join("\n\n");
|
|
545
512
|
if (Buffer.byteLength(effectiveSystemPrompt, "utf8") + Buffer.byteLength(memoryPrompt.wakePromptSuffix, "utf8")
|
|
546
513
|
> (input.maxSystemPromptBytes ?? Number.MAX_SAFE_INTEGER)) {
|
|
547
514
|
throw new Error("ability_runtime_context_size_exceeded");
|
|
@@ -577,7 +544,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
577
544
|
});
|
|
578
545
|
}
|
|
579
546
|
const trainingRoots = input.trainingProjectContext?.projects.map((project) => project.root) ?? [];
|
|
580
|
-
const { codexSkillRoots, claudeAdditionalDirectories } = await projectSkillRuntimeDirectories({
|
|
547
|
+
const { codexSkillRoots, claudeAdditionalDirectories, harnessSkillRoots } = await projectSkillRuntimeDirectories({
|
|
581
548
|
runtime: runtime.name,
|
|
582
549
|
...(runtimeRoot === undefined ? {} : { runtimeRoot }),
|
|
583
550
|
agentRoot: workspace.dir,
|
|
@@ -615,7 +582,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
615
582
|
const workspaceRoots = [...legacyRoots, ...trainingRoots];
|
|
616
583
|
const launchRequest = {
|
|
617
584
|
runtime: runtime.name,
|
|
618
|
-
bin: runtime.name === "deepseek-harness" ?
|
|
585
|
+
bin: runtime.name === "deepseek-harness" ? deepSeekHarnessLauncher(runtimeEnv).bin : runtime.name,
|
|
586
|
+
...(runtime.name === "deepseek-harness" ? { harnessStateRoot: executionWorkspace.runDir, harnessSkillRoots: harnessSkillRoots ?? [] } : {}),
|
|
619
587
|
cwd: input.projectContext?.primary?.root ?? executionWorkspace.runDir,
|
|
620
588
|
...(runtimeRoot !== undefined
|
|
621
589
|
|| (input.projectSkills === undefined && input.abilityRelease === undefined)
|
|
@@ -731,7 +699,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
731
699
|
// (incident: 2026-09-02, boss-alarm 排查结论未投递)。所有 runtime 的文本都喂给它。
|
|
732
700
|
const externalOutput = new ExternalAnswerDecoder();
|
|
733
701
|
// 每轮独立:tool_use/result 关联状态不能跨 execution 泄漏。
|
|
734
|
-
const consoleFormatter = createConsoleFormatter();
|
|
702
|
+
const consoleFormatter = createConsoleFormatter(runtime.name);
|
|
735
703
|
let runtimeReady = false;
|
|
736
704
|
let runtimeRunning = false;
|
|
737
705
|
let resolveRuntimeReady;
|
|
@@ -937,14 +905,17 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
937
905
|
memoryPruneRuntimeExitCode = exitCode;
|
|
938
906
|
if (localMemoryResultPath !== null) {
|
|
939
907
|
memoryPruneFailurePhase = "memory_commit";
|
|
940
|
-
const
|
|
941
|
-
|
|
908
|
+
const resultPath = localMemoryResultPath;
|
|
909
|
+
const commit = () => commitLocalMemoryResultAfterExit({
|
|
910
|
+
resultPath,
|
|
942
911
|
agentDir: executionWorkspace.dir,
|
|
943
912
|
executionId: input.executionId,
|
|
944
913
|
pruneTraceId: memoryPruneTraceId,
|
|
945
914
|
exitCode,
|
|
946
915
|
workLogPath: executionWorkspace.workLogPath,
|
|
916
|
+
expectedWorkLogSha256: workLogSha256(executionWorkspace.workLog),
|
|
947
917
|
});
|
|
918
|
+
const committed = exitCode !== 0 ? await commit() : await withWorkLogCommit(workLogExecutionKey(input.launch.agentsRoot, input.handle, input.taskKey), commit, dependencies.cancellation);
|
|
948
919
|
if (committed.status === "failed" || committed.status === "missing") {
|
|
949
920
|
const reason = committed.error ?? "local_memory_result_missing";
|
|
950
921
|
dslog("local_memory.commit_failed", "结构化本地记忆提交或清理未完成,保留可用恢复材料", {
|
|
@@ -1122,6 +1093,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
1122
1093
|
active_prune_count: activePruneCount,
|
|
1123
1094
|
});
|
|
1124
1095
|
}
|
|
1096
|
+
if (environmentSnapshot)
|
|
1097
|
+
await rm(environmentSnapshot.directory, { recursive: true, force: true }).catch(() => { });
|
|
1125
1098
|
const attachmentDirectories = new Set([
|
|
1126
1099
|
...(knownAttachmentDirectory === null ? [] : [knownAttachmentDirectory]),
|
|
1127
1100
|
...(materialized === null ? [] : [materialized.directory]),
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { containsLikelySecret } from "../agent-memory/policy.js";
|
|
3
|
+
const CategorySchema = z.enum([
|
|
4
|
+
"preferences", "project-rules", "testing", "debugging", "git", "incidents", "lessons",
|
|
5
|
+
]);
|
|
6
|
+
export const LocalMemorySingleLineText = z.string().trim().min(1).max(500).regex(/^[^\r\n]+$/u);
|
|
7
|
+
export const LocalMemoryChangeSchema = z.object({
|
|
8
|
+
operation: z.enum(["create", "update", "supersede"]),
|
|
9
|
+
memoryId: LocalMemorySingleLineText.regex(/^[^<>]+$/u).nullable(),
|
|
10
|
+
baseRevision: z.number().int().nonnegative().nullable(),
|
|
11
|
+
supersedesMemoryId: LocalMemorySingleLineText.regex(/^[^<>]+$/u).nullable(),
|
|
12
|
+
title: LocalMemorySingleLineText.max(120),
|
|
13
|
+
kind: z.enum(["procedural", "semantic", "episodic", "preference"]),
|
|
14
|
+
summary: LocalMemorySingleLineText,
|
|
15
|
+
triggers: z.array(LocalMemorySingleLineText.max(80)).min(1).max(16),
|
|
16
|
+
priority: z.enum(["high", "medium", "low"]),
|
|
17
|
+
detailCategory: CategorySchema,
|
|
18
|
+
detailBody: z.string().trim().min(1).max(12_000),
|
|
19
|
+
reviewAfter: LocalMemorySingleLineText.nullable(),
|
|
20
|
+
}).strict();
|
|
21
|
+
const CONTROL_CHARACTER_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u;
|
|
22
|
+
const RESERVED_MEMORY_MARKER = /NOWWORK:(?:MEMORY_ENTRY|MEMORY_INDEX):/iu;
|
|
23
|
+
export function validateLocalMemoryChange(change) {
|
|
24
|
+
const text = [change.title, change.summary, ...change.triggers, change.detailBody].join("\n");
|
|
25
|
+
if (containsLikelySecret(text))
|
|
26
|
+
throw new Error("local_memory_secret_candidate");
|
|
27
|
+
if (CONTROL_CHARACTER_PATTERN.test(text))
|
|
28
|
+
throw new Error("local_memory_control_character");
|
|
29
|
+
if (RESERVED_MEMORY_MARKER.test(text))
|
|
30
|
+
throw new Error("local_memory_reserved_marker");
|
|
31
|
+
if (change.operation === "create"
|
|
32
|
+
&& (change.memoryId !== null || change.baseRevision !== null || change.supersedesMemoryId !== null)) {
|
|
33
|
+
throw new Error("local_memory_create_revision_invalid");
|
|
34
|
+
}
|
|
35
|
+
if (change.operation === "update"
|
|
36
|
+
&& (change.memoryId === null || change.baseRevision === null || change.supersedesMemoryId !== null)) {
|
|
37
|
+
throw new Error("local_memory_update_revision_missing");
|
|
38
|
+
}
|
|
39
|
+
if (change.operation === "supersede"
|
|
40
|
+
&& (change.memoryId !== null || change.baseRevision === null || change.supersedesMemoryId === null)) {
|
|
41
|
+
throw new Error("local_memory_supersede_revision_missing");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -19,6 +19,14 @@ export function renderLocalMemoryIndexCard(entry) {
|
|
|
19
19
|
+ `- updated: ${entry.updated}\n`
|
|
20
20
|
+ (entry.reviewAfter === null ? "" : `- review_after: ${entry.reviewAfter}\n`);
|
|
21
21
|
}
|
|
22
|
+
/** 写入和预演共用完整索引,预算包含起止标记与分隔换行。 */
|
|
23
|
+
export function renderGeneratedIndex(entries) {
|
|
24
|
+
const cards = entries
|
|
25
|
+
.filter(entry => PROJECTABLE_STATUSES.has(entry.status))
|
|
26
|
+
.map(renderLocalMemoryIndexCard)
|
|
27
|
+
.join("\n");
|
|
28
|
+
return `<!-- NOWWORK:MEMORY_INDEX:BEGIN schema=1 -->\n${cards}<!-- NOWWORK:MEMORY_INDEX:END -->`;
|
|
29
|
+
}
|
|
22
30
|
function compareEntries(left, right) {
|
|
23
31
|
const statusRank = (status) => status === "pinned" ? 0 : 1;
|
|
24
32
|
const priorityRank = (priority) => (priority === "high" ? 0 : priority === "medium" ? 1 : 2);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const CommandSchema = z.object({
|
|
2
|
+
"agent-dir": z.string().min(1).refine(isAbsolute),
|
|
3
|
+
"dry-run": z.literal(true),
|
|
4
|
+
}).strict();
|
|
5
|
+
const USAGE = "只读记忆盘点与转换预演,不写文件,不支持 --apply。\n"
|
|
6
|
+
+ "pnpm --filter @nowcrew/daemon exec tsx src/local-memory/inventory-cli.ts --agent-dir <单个Agent绝对路径> --dry-run\n"
|
|
7
|
+
+ "报告包含原始记忆,需作为敏感本地资料保存;退出码0仅代表盘点及格式预演无阻塞,不代表批准迁移。\n";
|
|
8
|
+
export async function runMemoryInventoryCommand(args, io) {
|
|
9
|
+
if (args.length === 1 && args[0] === "--help") {
|
|
10
|
+
io.out(USAGE);
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
let command;
|
|
14
|
+
try {
|
|
15
|
+
const parsed = parseArgs({ args, allowPositionals: false, options: {
|
|
16
|
+
"agent-dir": { type: "string" }, "dry-run": { type: "boolean" },
|
|
17
|
+
} });
|
|
18
|
+
command = CommandSchema.parse(parsed.values);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
io.err(USAGE);
|
|
22
|
+
return 2;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const scan = await scanMemorySources(command["agent-dir"]);
|
|
26
|
+
const scanComplete = scan.issues.length === 0;
|
|
27
|
+
const analysis = previewMemoryInventory(scan.sources, scanComplete);
|
|
28
|
+
const complete = scanComplete && !analysis.issues.some((issue) => issue.code === "missing_detail");
|
|
29
|
+
const issues = [...scan.issues, ...analysis.issues];
|
|
30
|
+
const referenced = new Set(analysis.entries.map((entry) => entry.sourceFields.file));
|
|
31
|
+
const files = scan.sources.map(({ path, bytes, sha256 }) => ({ path, bytes, sha256 }));
|
|
32
|
+
const report = {
|
|
33
|
+
schemaVersion: 1, mode: "dry-run", agentDir: scan.root, applySupported: false,
|
|
34
|
+
complete, scanComplete, requiresHumanReview: true,
|
|
35
|
+
coverage: ["MEMORY.md", "notes/**", ".crew/memory/pending/**", "根目录的 .memory-result.v1-*.json"],
|
|
36
|
+
limitations: ["不扫描历史执行目录;result清单仅覆盖Agent根目录。", "两遍摘要比对不等同跨文件原子快照,正式迁移仍需停止写入并重新核对。"],
|
|
37
|
+
limits: inventoryLimits, totalBytes: scan.totalBytes, files, ...analysis, issues,
|
|
38
|
+
pending: files.filter((file) => file.path.startsWith(".crew/memory/pending/")).map((file) => file.path),
|
|
39
|
+
orphanNotes: files.filter((file) => file.path.startsWith("notes/") && !referenced.has(file.path)).map((file) => file.path),
|
|
40
|
+
resultFiles: files.filter((file) => file.path.startsWith(".memory-result.v1-")).map((file) => file.path),
|
|
41
|
+
};
|
|
42
|
+
io.out(JSON.stringify(report, null, 2) + "\n");
|
|
43
|
+
return issues.length ? 1 : 0;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const code = error instanceof Error ? error.code ?? error.message : "inventory_failed";
|
|
47
|
+
io.err(`记忆盘点失败:${code}\n`);
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
52
|
+
process.exitCode = await runMemoryInventoryCommand(process.argv.slice(2), {
|
|
53
|
+
out: (text) => process.stdout.write(text), err: (text) => process.stderr.write(text),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
import { isAbsolute, resolve } from "node:path";
|
|
57
|
+
import { pathToFileURL } from "node:url";
|
|
58
|
+
import { parseArgs } from "node:util";
|
|
59
|
+
import { z } from "zod";
|
|
60
|
+
import { inventoryLimits, scanMemorySources } from "./inventory-source.js";
|
|
61
|
+
import { previewMemoryInventory } from "./inventory-preview.js";
|