@nowcrew/daemon 0.6.88 → 0.6.90
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 +66 -4
- package/dist/agent-ability/controller.js +11 -1
- package/dist/agent-ability/git-errors.js +22 -0
- package/dist/agent-ability/materializer.js +21 -10
- package/dist/agent-ability/resolver.js +12 -4
- 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 +51 -2
- package/dist/automatic-install-target.js +8 -0
- 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/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 +17 -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 -176
- package/dist/server-url.js +25 -0
- package/dist/slog.js +2 -1
- package/dist/storage-identity.js +0 -3
- package/dist/storage-runtime.js +2 -2
- 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 +6 -4
- 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 -240
- 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 -449
- 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/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";
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { LOCAL_MEMORY_INDEX_CODE_POINT_CAP, LOCAL_MEMORY_INDEX_ENTRY_CAP, LOCAL_MEMORY_TOTAL_CODE_POINT_CAP, renderGeneratedIndex, } from "./index-budget.js";
|
|
3
|
+
import { parseLocalMemoryIndex } from "./write.js";
|
|
4
|
+
const GENERATED = /<!--\s*NOWWORK:MEMORY_INDEX:BEGIN\s+schema=1\s*-->[\s\S]*?<!--\s*NOWWORK:MEMORY_INDEX:END\s*-->/gu;
|
|
5
|
+
const REFERENCE = /^notes\/(?!.*(?:^|\/)\.\.?\/)(?:[^/\\\u0000-\u001f]+\/)*[^/\\\u0000-\u001f]+\.md$/u;
|
|
6
|
+
const FIELD = /^-\s+([a-z][a-z_]*)\s*:\s*(.*?)\s*$/iu;
|
|
7
|
+
const WRITABLE_FILES = new Set(["preferences", "project-rules", "testing", "debugging", "git", "incidents", "lessons"]
|
|
8
|
+
.map((category) => `notes/${category}.md`));
|
|
9
|
+
const length = (text) => Array.from(text).length;
|
|
10
|
+
const digest = (text) => createHash("sha256").update(text).digest("hex");
|
|
11
|
+
export function previewMemoryInventory(sources, readComplete, mode = "migration") {
|
|
12
|
+
const issues = [];
|
|
13
|
+
const entries = [];
|
|
14
|
+
const sourceMap = new Map(sources.map((source) => [source.path, source]));
|
|
15
|
+
const memory = sourceMap.get("MEMORY.md")?.content ?? "";
|
|
16
|
+
const add = (code, path = "MEMORY.md") => issues.push({ code, path });
|
|
17
|
+
// 掩盖注释但保留UTF-16位置,避免把模板示例当成条目,也保留原文供人工复核。
|
|
18
|
+
const comments = [...memory.matchAll(/<!--[\s\S]*?-->/gu)];
|
|
19
|
+
// 现有写入器允许正文包含箭头 A --> B;未闭合的注释仍须拒绝。
|
|
20
|
+
if ((memory.match(/<!--/gu) ?? []).length !== comments.length
|
|
21
|
+
|| (mode === "migration" && (memory.match(/-->/gu) ?? []).length !== comments.length))
|
|
22
|
+
add("comment_boundary_invalid");
|
|
23
|
+
const masked = memory.replace(/<!--[\s\S]*?-->/gu, (comment) => comment.replace(/[^\r\n]/gu, (character) => " ".repeat(character.length)));
|
|
24
|
+
const sections = [...masked.matchAll(/^##[\t ]+Knowledge Index[\t ]*\r?$/gmu)];
|
|
25
|
+
const heading = sections[0];
|
|
26
|
+
if (sections.length !== 1 || heading?.index === undefined)
|
|
27
|
+
add("index_heading_invalid");
|
|
28
|
+
const start = heading === undefined ? 0 : heading.index + heading[0].length;
|
|
29
|
+
const next = /^##[\t ]+(?!#)/mu.exec(masked.slice(start));
|
|
30
|
+
const end = next?.index === undefined ? memory.length : start + next.index;
|
|
31
|
+
const generated = [...memory.matchAll(GENERATED)];
|
|
32
|
+
const markerCount = (memory.match(/NOWWORK:MEMORY_INDEX:/gu) ?? []).length;
|
|
33
|
+
if (markerCount !== generated.length * 2 || generated.length > 1
|
|
34
|
+
|| generated.some((block) => block.index < start || block.index + block[0].length > end))
|
|
35
|
+
add("index_marker_invalid");
|
|
36
|
+
const heads = [...masked.slice(start, end).matchAll(/^###[\t ]+(.+?)[\t ]*\r?$/gmu)];
|
|
37
|
+
if (heads.length > 1000)
|
|
38
|
+
add("card_limit_exceeded");
|
|
39
|
+
const ranges = [];
|
|
40
|
+
for (const [i, head] of heads.slice(0, 1000).entries()) {
|
|
41
|
+
const cardStart = start + head.index;
|
|
42
|
+
const cardEnd = heads[i + 1]?.index === undefined ? end : start + heads[i + 1].index;
|
|
43
|
+
const tailStart = cardStart + head[0].length;
|
|
44
|
+
const tail = masked.slice(tailStart, cardEnd);
|
|
45
|
+
const fields = {};
|
|
46
|
+
const issueStart = issues.length;
|
|
47
|
+
let lastFieldEnd = tailStart;
|
|
48
|
+
let proseSeen = false;
|
|
49
|
+
for (const line of tail.matchAll(/[^\n]*(?:\n|$)/gu)) {
|
|
50
|
+
const field = FIELD.exec(line[0].replace(/\r?\n$/u, ""));
|
|
51
|
+
if (field) {
|
|
52
|
+
const key = field[1].toLowerCase();
|
|
53
|
+
if (fields[key] !== undefined)
|
|
54
|
+
add("duplicate_field");
|
|
55
|
+
if (proseSeen)
|
|
56
|
+
add("ambiguous_card_body");
|
|
57
|
+
fields[key] = field[2];
|
|
58
|
+
lastFieldEnd = tailStart + line.index + line[0].length;
|
|
59
|
+
}
|
|
60
|
+
else if (line[0].trim())
|
|
61
|
+
proseSeen = true;
|
|
62
|
+
}
|
|
63
|
+
const rawCard = memory.slice(cardStart, lastFieldEnd);
|
|
64
|
+
const format = generated.some((block) => cardStart >= block.index && cardStart < block.index + block[0].length)
|
|
65
|
+
? "managed" : "legacy";
|
|
66
|
+
const parsed = parseLocalMemoryIndex(`## Knowledge Index\n${rawCard}`)[0];
|
|
67
|
+
// 状态更新会把已归一化的空触发词数组写回;字段缺失仍是格式错误。
|
|
68
|
+
const missingTriggers = mode === "existing_v2" && format === "managed" ? fields.triggers === undefined : !fields.triggers;
|
|
69
|
+
if (!parsed || !fields.type || !fields.summary || missingTriggers || !fields.priority || !fields.updated
|
|
70
|
+
|| (format === "legacy" && !fields.scope)
|
|
71
|
+
|| !["procedural", "semantic", "episodic", "preference"].includes(fields.type)
|
|
72
|
+
|| !["high", "medium", "low"].includes(fields.priority))
|
|
73
|
+
add("invalid_card");
|
|
74
|
+
const file = fields.file ?? "";
|
|
75
|
+
if (!REFERENCE.test(file) || file.split("/").some((part) => part === "." || part === ".."))
|
|
76
|
+
add("invalid_reference", file);
|
|
77
|
+
const detail = sourceMap.get(file)?.content ?? null;
|
|
78
|
+
if (detail === null)
|
|
79
|
+
add("missing_detail", file);
|
|
80
|
+
let proposed = null;
|
|
81
|
+
if (parsed) {
|
|
82
|
+
const id = format === "legacy" ? `mem_${digest(rawCard).slice(0, 32)}` : parsed.id;
|
|
83
|
+
proposed = { ...parsed, id, anchor: id, revision: format === "legacy" ? 1 : parsed.revision };
|
|
84
|
+
if (format === "managed") {
|
|
85
|
+
if (!fields.id || !/^[a-zA-Z0-9_-]+$/u.test(fields.id) || fields.anchor !== fields.id
|
|
86
|
+
|| !/^[1-9][0-9]*$/u.test(fields.revision ?? "") || !Number.isSafeInteger(parsed.revision)
|
|
87
|
+
|| !["active", "pinned"].includes(fields.status ?? ""))
|
|
88
|
+
add("managed_metadata_invalid");
|
|
89
|
+
const begin = `<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${parsed.id} revision=${parsed.revision} -->`;
|
|
90
|
+
const finish = `<!-- NOWWORK:MEMORY_ENTRY:END id=${parsed.id} -->`;
|
|
91
|
+
if (!detail?.includes(begin) || !detail.includes(finish)
|
|
92
|
+
|| detail.indexOf(finish) < detail.indexOf(begin)
|
|
93
|
+
|| detail.split(begin).length !== 2 || detail.split(finish).length !== 2)
|
|
94
|
+
add("managed_anchor_invalid", file);
|
|
95
|
+
}
|
|
96
|
+
else if (fields.status && fields.status !== "active") {
|
|
97
|
+
add("legacy_status_requires_review", file);
|
|
98
|
+
}
|
|
99
|
+
if (format === "legacy" && /NOWWORK:MEMORY_(?:ENTRY|INDEX):/u.test(detail ?? ""))
|
|
100
|
+
add("legacy_detail_contains_markers", file);
|
|
101
|
+
}
|
|
102
|
+
entries.push({
|
|
103
|
+
title: head[1], format, disposition: issues.length === issueStart ? "review_required" : "blocked",
|
|
104
|
+
sourceFields: fields, rawCard, detail, proposed,
|
|
105
|
+
});
|
|
106
|
+
ranges.push({ start: cardStart, end: lastFieldEnd });
|
|
107
|
+
}
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
if (entries.filter((other) => other.sourceFields.file === entry.sourceFields.file).length > 1
|
|
110
|
+
&& entry.format === "legacy")
|
|
111
|
+
add("shared_legacy_detail", entry.sourceFields.file);
|
|
112
|
+
if (entries.filter((other) => other.proposed?.id === entry.proposed?.id).length > 1)
|
|
113
|
+
add("duplicate_id");
|
|
114
|
+
// 已管理条目由ID区分,现有update允许同名;迁移预演仍要求人工审核同名来源。
|
|
115
|
+
if ((mode === "migration" || entry.format === "legacy")
|
|
116
|
+
&& entries.filter((other) => other.title.trim().toLowerCase() === entry.title.trim().toLowerCase()).length > 1)
|
|
117
|
+
add("duplicate_title");
|
|
118
|
+
}
|
|
119
|
+
const compatibilityIssues = entries.filter((entry) => !WRITABLE_FILES.has(entry.sourceFields.file ?? ""))
|
|
120
|
+
.map((entry) => ({ code: "detail_category_mapping_required", path: entry.sourceFields.file ?? "" }));
|
|
121
|
+
let preview = null;
|
|
122
|
+
if (readComplete && issues.length === 0) {
|
|
123
|
+
const index = renderGeneratedIndex(entries.map(entry => entry.proposed));
|
|
124
|
+
let retained = memory;
|
|
125
|
+
for (const range of [...ranges].reverse())
|
|
126
|
+
retained = retained.slice(0, range.start) + retained.slice(range.end);
|
|
127
|
+
retained = retained.replace(GENERATED, "");
|
|
128
|
+
// 新生成块仅替换索引卡片,角色、目标、维护说明和其它段落保留。
|
|
129
|
+
const insertAt = start;
|
|
130
|
+
const projected = retained.slice(0, insertAt) + `\n${index}\n` + retained.slice(insertAt);
|
|
131
|
+
const budget = {
|
|
132
|
+
entries: entries.length, indexCodePoints: length(index), totalCodePoints: length(projected),
|
|
133
|
+
entryCap: LOCAL_MEMORY_INDEX_ENTRY_CAP, indexCodePointCap: LOCAL_MEMORY_INDEX_CODE_POINT_CAP,
|
|
134
|
+
totalCodePointCap: LOCAL_MEMORY_TOTAL_CODE_POINT_CAP,
|
|
135
|
+
fits: entries.length <= LOCAL_MEMORY_INDEX_ENTRY_CAP && length(index) <= LOCAL_MEMORY_INDEX_CODE_POINT_CAP
|
|
136
|
+
&& length(projected) <= LOCAL_MEMORY_TOTAL_CODE_POINT_CAP,
|
|
137
|
+
};
|
|
138
|
+
preview = {
|
|
139
|
+
memory: projected, budget, writerCompatible: compatibilityIssues.length === 0,
|
|
140
|
+
notes: entries.filter((entry) => entry.format === "legacy").map((entry) => ({
|
|
141
|
+
path: entry.sourceFields.file,
|
|
142
|
+
content: `<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${entry.proposed.id} revision=1 -->\n`
|
|
143
|
+
+ `## ${entry.title}\n\n旧索引范围(仍需结合正文人工确认):${entry.sourceFields.scope}\n\n`
|
|
144
|
+
+ `${entry.detail}${entry.detail.endsWith("\n") ? "" : "\n"}`
|
|
145
|
+
+ `<!-- NOWWORK:MEMORY_ENTRY:END id=${entry.proposed.id} -->\n`,
|
|
146
|
+
})),
|
|
147
|
+
};
|
|
148
|
+
if (!budget.fits)
|
|
149
|
+
add("projection_over_budget");
|
|
150
|
+
}
|
|
151
|
+
const shownDetails = new Set();
|
|
152
|
+
const compactEntries = entries.map((entry) => {
|
|
153
|
+
const file = entry.sourceFields.file ?? "";
|
|
154
|
+
const detail = shownDetails.has(file) ? null : entry.detail;
|
|
155
|
+
shownDetails.add(file);
|
|
156
|
+
return { ...entry, detail };
|
|
157
|
+
});
|
|
158
|
+
return { entries: compactEntries, issues: [...issues, ...compatibilityIssues], preview, rawMemory: memory };
|
|
159
|
+
}
|