@nowcrew/daemon 0.6.68 → 0.6.69
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 +30 -1
- package/dist/agent-ability/resolver.js +27 -16
- package/dist/agent-ability/runtime.js +3 -0
- package/dist/agent-ability/types.js +6 -11
- package/dist/automatic-install-target.js +15 -10
- package/dist/background-job.js +50 -0
- package/dist/computer-cli.js +10 -1
- package/dist/computer-service.js +41 -1
- package/dist/config.js +44 -0
- package/dist/console-formatter.js +76 -1
- package/dist/console.js +23 -4
- package/dist/daemon-migration-controller.js +16 -18
- package/dist/execution-protocol.js +0 -1
- package/dist/execution-runner.js +60 -11
- package/dist/i18n.js +2 -0
- package/dist/local-execution-prompt.js +32 -13
- package/dist/local-executor.js +107 -55
- package/dist/local-memory/index-budget.js +74 -0
- package/dist/local-memory/pending.js +111 -0
- package/dist/local-memory/runtime.js +87 -0
- package/dist/local-memory/types.js +1 -0
- package/dist/local-memory/write.js +448 -0
- package/dist/machine-info.js +3 -4
- package/dist/main.js +83 -0
- package/dist/managed-service-lifecycle.js +47 -2
- package/dist/managed-service-startup.js +2 -0
- package/dist/profile-layout.js +10 -4
- package/dist/project-dependencies.js +139 -124
- package/dist/project-dependency-lock.js +87 -0
- package/dist/project-dependency-manifest.js +2 -12
- package/dist/project-registry-wiring.js +1 -4
- package/dist/project-skills/controller.js +2 -0
- package/dist/project-skills/runtime-launch.js +10 -7
- package/dist/prompt.js +19 -1
- package/dist/provider-env.js +3 -21
- package/dist/repository-identity.js +45 -0
- package/dist/runner.js +4 -2
- package/dist/runtimes/codex-app-server-runner.js +55 -23
- package/dist/runtimes/codex-command-safety.js +75 -0
- package/dist/runtimes/codex-contract.js +20 -5
- package/dist/runtimes/codex-deepseek-config.js +7 -17
- package/dist/runtimes/codex-turn-retry.js +69 -0
- package/dist/runtimes/codex.js +4 -3
- package/dist/serve.js +68 -3
- package/dist/storage-config.js +175 -0
- package/dist/storage-control.js +193 -0
- package/dist/storage-copy.js +628 -0
- package/dist/storage-credential-guard.js +21 -0
- package/dist/storage-doctor.js +228 -0
- package/dist/storage-identity.js +29 -0
- package/dist/storage-layout.js +143 -0
- package/dist/storage-manifest.js +855 -0
- package/dist/storage-migration-controller.js +420 -0
- package/dist/storage-migration-manifest.js +105 -0
- package/dist/storage-migration-policy.js +100 -0
- package/dist/storage-runtime-controller.js +339 -0
- package/dist/storage-runtime-readiness.js +184 -0
- package/dist/storage-runtime.js +39 -0
- package/dist/storage-safe-fs.js +302 -0
- package/dist/storage-serve-adapter.js +65 -0
- package/dist/storage-status-delivery.js +209 -0
- package/dist/storage-status-replay.js +125 -0
- package/dist/storage-target-confirmation.js +129 -0
- package/dist/storage-worker-command.js +37 -0
- package/dist/storage-worker-executor.js +430 -0
- package/dist/storage-worker-handoff.js +151 -0
- package/dist/storage-worker-operation.js +284 -0
- package/dist/storage-worker-status.js +259 -0
- package/dist/supervised-runtime.js +3 -1
- package/dist/workspace.js +34 -14
- package/package.json +1 -1
- package/dist/self-training-workspace.js +0 -15
package/dist/local-executor.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
2
|
import { readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { delimiter, join } from "node:path";
|
|
4
|
-
import { cleanupSelfTrainingRunDir } from "./self-training-workspace.js";
|
|
5
4
|
import { prepareWorkspace, rotateAgentSession, safeKey, } from "./workspace.js";
|
|
6
5
|
import { CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_ENV, isClaudeAdditionalDirectoryInstructionsSupported, probeClaudeVersion, spawnClaude, } from "./runtimes/claude.js";
|
|
7
6
|
import { spawnCodex } from "./runtimes/codex.js";
|
|
@@ -9,7 +8,7 @@ import { DEEPSEEK_CODEX_MODEL, DEEPSEEK_CODEX_REASONING_LEVELS, materializeDeepS
|
|
|
9
8
|
import { materializeDefaultCodexHome } from "./runtimes/codex-home.js";
|
|
10
9
|
import { materializePrivateCodexHome } from "./codex-home-launch.js";
|
|
11
10
|
import { spawnKimi, KIMI_EFFORT_LEVELS } from "./runtimes/kimi.js";
|
|
12
|
-
import { applyDeepSeekHarnessMachineEnv, applyProviderEnv, providerFingerprint,
|
|
11
|
+
import { applyDeepSeekHarnessMachineEnv, applyProviderEnv, providerFingerprint, } from "./provider-env.js";
|
|
13
12
|
import { augmentedPath } from "./runtime-path.js";
|
|
14
13
|
import { extractFinalText, extractRunMeta, normalizeEvent, parseLine, } from "./normalize.js";
|
|
15
14
|
import { readSession, writeSession, pickResumeId, isNearBudget } from "./session.js";
|
|
@@ -32,6 +31,7 @@ import { diffMemoryPruneNotes, evaluateMemoryPrunePostcondition, inspectMemoryPr
|
|
|
32
31
|
import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
|
|
33
32
|
import { CodexStartupStageParser } from "./codex-startup-stage.js";
|
|
34
33
|
import { formatRuntimeStartupError } from "./runtime-startup-error.js";
|
|
34
|
+
import { commitLocalMemoryResultAfterExit, initializeLocalMemoryV2ForWorkspace, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, renderLocalMemoryResultInstructions, } from "./local-memory/runtime.js";
|
|
35
35
|
function memoryPruneFileFactFields(fileFacts) {
|
|
36
36
|
const fields = {};
|
|
37
37
|
for (const [label, fact] of Object.entries(fileFacts)) {
|
|
@@ -78,6 +78,7 @@ const RUNTIME_RUNNING_CALLBACK_TIMEOUT_MS = 30_000;
|
|
|
78
78
|
const MEMORY_PRUNE_DIAGNOSTICS_TIMEOUT_MS = 2_000;
|
|
79
79
|
const LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS = 250;
|
|
80
80
|
const CLAUDE_INSTRUCTION_WARNING = "claude_additional_directory_instructions_unverified";
|
|
81
|
+
const CODEX_COLLABORATION_EXEC_WARNING = "codex_collaboration_disabled_on_exec";
|
|
81
82
|
const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
82
83
|
const RESERVED_ENV = new Set([
|
|
83
84
|
"PATH",
|
|
@@ -97,6 +98,14 @@ function withoutClaudeInstructionFlag(env) {
|
|
|
97
98
|
const warnRuntimeLocally = (code) => {
|
|
98
99
|
process.stderr.write(`[runtime-warning] ${code}\n`);
|
|
99
100
|
};
|
|
101
|
+
export function sanitizeEnvVars(raw) {
|
|
102
|
+
if (!raw)
|
|
103
|
+
return {};
|
|
104
|
+
return Object.fromEntries(Object.entries(raw).filter(([key, value]) => typeof value === "string"
|
|
105
|
+
&& ENV_KEY_RE.test(key)
|
|
106
|
+
&& !key.toUpperCase().startsWith("CREW_")
|
|
107
|
+
&& !RESERVED_ENV.has(key.toUpperCase())));
|
|
108
|
+
}
|
|
100
109
|
export function awaitExit(child) {
|
|
101
110
|
return new Promise((resolve) => {
|
|
102
111
|
let settled = false;
|
|
@@ -147,7 +156,6 @@ async function launchLegacyRuntime(request) {
|
|
|
147
156
|
effectivePermission: request.effectivePermission,
|
|
148
157
|
...(request.model === undefined ? {} : { model: request.model }),
|
|
149
158
|
...(request.reasoning === undefined ? {} : { reasoning: request.reasoning }),
|
|
150
|
-
...(request.networkAccess === undefined ? {} : { networkAccess: request.networkAccess }),
|
|
151
159
|
};
|
|
152
160
|
if (request.runtime === "claude") {
|
|
153
161
|
const additionalDirectories = request.claudeAdditionalDirectories
|
|
@@ -237,7 +245,6 @@ export async function executeLocal(input, callbacks = {}, dependencies = {}) {
|
|
|
237
245
|
async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
238
246
|
const providerConfig = input.launch.providerConfig ?? {};
|
|
239
247
|
const { runtime } = input;
|
|
240
|
-
const isolatedMaintenance = input.maintenancePurpose === "self_training";
|
|
241
248
|
const isDeepSeekCodex = runtime.name === "codex" && providerConfig.provider === "deepseek";
|
|
242
249
|
const currentModel = isDeepSeekCodex ? DEEPSEEK_CODEX_MODEL : runtime.model ?? null;
|
|
243
250
|
const launchModel = isDeepSeekCodex ? DEEPSEEK_CODEX_MODEL : runtime.model;
|
|
@@ -262,7 +269,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
262
269
|
? {}
|
|
263
270
|
: { sessionContextFingerprint: input.sessionContextFingerprint }),
|
|
264
271
|
...(input.launch.description ? { description: input.launch.description } : {}),
|
|
265
|
-
...(isolatedMaintenance ? { skipLocalMemory: true } : {}),
|
|
266
272
|
}), dependencies.cancellation);
|
|
267
273
|
}
|
|
268
274
|
catch (error) {
|
|
@@ -273,6 +279,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
273
279
|
}, "workspace_prepare", error);
|
|
274
280
|
throw error;
|
|
275
281
|
}
|
|
282
|
+
const localMemoryV2Initialized = await initializeLocalMemoryV2ForWorkspace(workspace, input.localMemoryV2Enabled === true);
|
|
276
283
|
let materialized = null;
|
|
277
284
|
let knownAttachmentDirectory = null;
|
|
278
285
|
const startupReservationState = { current: null };
|
|
@@ -282,6 +289,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
282
289
|
let memoryPruneFailurePhase = "diagnostics_before";
|
|
283
290
|
let memoryPruneBeforeSnapshot = null;
|
|
284
291
|
let memoryPruneSharedWriteKey = null;
|
|
292
|
+
let localMemoryResultPath = null;
|
|
285
293
|
let executionWorkspace = workspace;
|
|
286
294
|
let localMemoryTelemetry = null;
|
|
287
295
|
const inheritedEnv = { ...process.env };
|
|
@@ -291,23 +299,12 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
291
299
|
}
|
|
292
300
|
const sourceCodexHome = inheritedEnv.CODEX_HOME
|
|
293
301
|
?? (inheritedEnv.HOME === undefined ? undefined : join(inheritedEnv.HOME, ".codex"));
|
|
294
|
-
let materializedDeepSeekHome;
|
|
295
302
|
try {
|
|
296
303
|
if (isDeepSeekCodex && !providerConfig.providerApiKey) {
|
|
297
304
|
throw new Error("DeepSeek API key is not configured for this Agent");
|
|
298
305
|
}
|
|
299
306
|
if (isDeepSeekCodex) {
|
|
300
|
-
|
|
301
|
-
? new URL(providerConfig.providerBaseUrl, input.launch.serverUrl).toString()
|
|
302
|
-
: undefined;
|
|
303
|
-
const materialize = dependencies.materializeDeepSeekCodexHome ?? materializeDeepSeekCodexHome;
|
|
304
|
-
const materializeOptions = {
|
|
305
|
-
...(providerBaseUrl === undefined ? {} : { baseUrl: providerBaseUrl }),
|
|
306
|
-
...(isolatedMaintenance ? { selfTraining: true } : {}),
|
|
307
|
-
};
|
|
308
|
-
materializedDeepSeekHome = await awaitWithCancellation(providerBaseUrl === undefined && !isolatedMaintenance
|
|
309
|
-
? materialize(workspace.homeDir)
|
|
310
|
-
: materialize(workspace.homeDir, materializeOptions), dependencies.cancellation);
|
|
307
|
+
await awaitWithCancellation((dependencies.materializeDeepSeekCodexHome ?? materializeDeepSeekCodexHome)(workspace.homeDir), dependencies.cancellation);
|
|
311
308
|
}
|
|
312
309
|
const supportsNativeResume = runtime.name === "claude"
|
|
313
310
|
|| (dependencies.launchRuntime !== undefined && runtimeCapability(runtime.name).nativeResume);
|
|
@@ -377,8 +374,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
377
374
|
rotatedForBudget,
|
|
378
375
|
nearBudget,
|
|
379
376
|
...(input.projectContext === undefined ? {} : { projectContext: input.projectContext }),
|
|
377
|
+
...(input.trainingProjectContext === undefined ? {} : { trainingProjectContext: input.trainingProjectContext }),
|
|
380
378
|
};
|
|
381
|
-
const systemPrompt = resolvePrompt(input.systemPrompt, promptContext);
|
|
382
379
|
if (input.attachments && input.attachments.length > 0) {
|
|
383
380
|
if (dependencies.cancellation?.isRequested())
|
|
384
381
|
throw new RuntimeCancelledError();
|
|
@@ -412,30 +409,52 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
412
409
|
const attachmentPlan = routeRuntimeAttachments(runtime.name, materialized?.attachments ?? []);
|
|
413
410
|
const wakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
|
|
414
411
|
memoryPruneTraceId = parseMemoryPruneTraceId(wakePrompt);
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
412
|
+
if (input.localMemoryV2Enabled === true && memoryPruneTraceId !== null
|
|
413
|
+
&& isLocalMemoryV2Eligible({
|
|
414
|
+
enabledForHandle: true,
|
|
415
|
+
agentHomePreexisted: workspace.agentHomePreexisted === true,
|
|
416
|
+
memorySeedCreated: executionWorkspace.memorySeedCreated === true && (localMemoryV2Initialized || workspace.localMemoryV2Initialized !== false),
|
|
417
|
+
localMemoryV2Initialized,
|
|
418
|
+
memoryPrune: true,
|
|
419
|
+
})) {
|
|
420
|
+
localMemoryResultPath = makeLocalMemoryResultPath(executionWorkspace.runDir, input.executionId);
|
|
421
|
+
}
|
|
422
|
+
const baseSystemPrompt = resolvePrompt(input.systemPrompt, promptContext);
|
|
423
|
+
const structuredMemoryInstructions = localMemoryResultPath === null
|
|
424
|
+
? null
|
|
425
|
+
: renderLocalMemoryResultInstructions(localMemoryResultPath);
|
|
426
|
+
const structuredPromptFits = structuredMemoryInstructions === null
|
|
427
|
+
|| input.maxSystemPromptBytes === undefined
|
|
428
|
+
|| Buffer.byteLength(baseSystemPrompt, "utf8") + Buffer.byteLength(structuredMemoryInstructions, "utf8")
|
|
429
|
+
<= input.maxSystemPromptBytes;
|
|
430
|
+
if (!structuredPromptFits)
|
|
431
|
+
localMemoryResultPath = null;
|
|
432
|
+
const systemPrompt = localMemoryResultPath === null
|
|
433
|
+
? baseSystemPrompt
|
|
434
|
+
: `${baseSystemPrompt}${structuredMemoryInstructions ?? ""}`;
|
|
435
|
+
try {
|
|
436
|
+
localMemoryTelemetry = createLocalMemoryTelemetry({
|
|
437
|
+
executionId: input.executionId,
|
|
438
|
+
agentHandle: input.handle,
|
|
439
|
+
...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }),
|
|
440
|
+
runtime: runtime.name,
|
|
441
|
+
stableProtocolRuntime: dependencies.launchRuntime !== undefined,
|
|
442
|
+
agentDir: executionWorkspace.dir,
|
|
443
|
+
runDir: executionWorkspace.runDir,
|
|
444
|
+
isMemoryPrune: memoryPruneTraceId !== null,
|
|
445
|
+
diagnosticsTimeoutMs: dependencies.localMemoryDiagnosticsTimeoutMs
|
|
446
|
+
?? LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS,
|
|
447
|
+
});
|
|
448
|
+
await localMemoryTelemetry.captureBefore();
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
localMemoryTelemetry = null;
|
|
452
|
+
logLocalMemoryDiagnosticsFailure({
|
|
453
|
+
executionId: input.executionId,
|
|
454
|
+
agentHandle: input.handle,
|
|
455
|
+
...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }),
|
|
456
|
+
}, "initialize", error);
|
|
457
|
+
}
|
|
439
458
|
if (memoryPruneTraceId !== null) {
|
|
440
459
|
memoryPruneSharedWriteKey = JSON.stringify([input.launch.agentsRoot, input.handle]);
|
|
441
460
|
const activePruneCount = (activeMemoryPrunes.get(memoryPruneSharedWriteKey) ?? 0) + 1;
|
|
@@ -469,7 +488,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
469
488
|
}
|
|
470
489
|
}
|
|
471
490
|
memoryPruneFailurePhase = "runtime_prepare";
|
|
472
|
-
const crewHome = isolatedMaintenance ? executionWorkspace.runDir : workspace.dir;
|
|
473
491
|
const baseEnv = {
|
|
474
492
|
...inheritedEnv,
|
|
475
493
|
...sanitizeEnvVars(providerConfig.envVars),
|
|
@@ -478,10 +496,10 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
478
496
|
CREW_SERVER_URL: input.launch.serverUrl,
|
|
479
497
|
CREW_TOKEN: input.launch.token,
|
|
480
498
|
CREW_CHANNEL: input.channelId,
|
|
481
|
-
CREW_HOME:
|
|
499
|
+
CREW_HOME: workspace.dir,
|
|
482
500
|
// Expose only the training root; ability-specific workspace layout stays
|
|
483
501
|
// owned by the training package rather than the NowWork daemon.
|
|
484
|
-
QA_RUNTIME_ROOT: join(
|
|
502
|
+
QA_RUNTIME_ROOT: join(workspace.dir, "training"),
|
|
485
503
|
CREW_TASK_DIR: executionWorkspace.runDir,
|
|
486
504
|
CREW_TASK_LOG: executionWorkspace.workLogPath,
|
|
487
505
|
...(input.wakeMessageId ? { CREW_WAKE_MESSAGE_ID: input.wakeMessageId } : {}),
|
|
@@ -497,7 +515,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
497
515
|
};
|
|
498
516
|
const providerEnv = applyProviderEnv(baseEnv, runtime.name, input.launch.codexHomeMode === undefined
|
|
499
517
|
? providerConfig
|
|
500
|
-
: { ...providerConfig, codexHomeMode: input.launch.codexHomeMode }, workspace.homeDir
|
|
518
|
+
: { ...providerConfig, codexHomeMode: input.launch.codexHomeMode }, workspace.homeDir);
|
|
501
519
|
const childEnv = runtime.name === "deepseek-harness"
|
|
502
520
|
? applyDeepSeekHarnessMachineEnv(providerEnv, inheritedEnv)
|
|
503
521
|
: providerEnv;
|
|
@@ -508,7 +526,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
508
526
|
qa_runtime_root: childEnv.QA_RUNTIME_ROOT ?? null,
|
|
509
527
|
qa_runtime_root_present: childEnv.QA_RUNTIME_ROOT !== undefined,
|
|
510
528
|
inherited_qa_runtime_root_present: inheritedEnv.QA_RUNTIME_ROOT !== undefined,
|
|
511
|
-
training_root: join(
|
|
529
|
+
training_root: join(workspace.dir, "training"),
|
|
512
530
|
project_context_present: input.projectContext !== undefined,
|
|
513
531
|
project_ids: JSON.stringify(input.projectContext === undefined
|
|
514
532
|
? []
|
|
@@ -522,7 +540,13 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
522
540
|
ability_release_id: input.abilityRelease?.releaseId,
|
|
523
541
|
ability_root_commit: input.abilityRelease?.rootCommit,
|
|
524
542
|
});
|
|
525
|
-
const
|
|
543
|
+
const warn = dependencies.warnRuntime ?? warnRuntimeLocally;
|
|
544
|
+
const launchRuntime = dependencies.launchRuntime ?? (async (launchReq) => {
|
|
545
|
+
if (launchReq.runtime === "codex" && launchReq.codexCollaborationTools === true) {
|
|
546
|
+
warn(CODEX_COLLABORATION_EXEC_WARNING);
|
|
547
|
+
}
|
|
548
|
+
return launchLegacyRuntime(launchReq);
|
|
549
|
+
});
|
|
526
550
|
if (dependencies.cancellation?.isRequested())
|
|
527
551
|
throw new RuntimeCancelledError();
|
|
528
552
|
let activeProjectSkillRuntimeRoot;
|
|
@@ -567,19 +591,23 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
567
591
|
skill_count: abilityContext.skillCount,
|
|
568
592
|
});
|
|
569
593
|
}
|
|
594
|
+
const trainingRoots = input.trainingProjectContext?.projects.map((project) => project.root) ?? [];
|
|
570
595
|
const { codexSkillRoots, claudeAdditionalDirectories } = await projectSkillRuntimeDirectories({
|
|
571
596
|
runtime: runtime.name,
|
|
572
597
|
...(runtimeRoot === undefined ? {} : { runtimeRoot }),
|
|
573
598
|
agentRoot: workspace.dir,
|
|
574
599
|
projectSkillsPresent: input.projectSkills !== undefined,
|
|
575
600
|
...(input.projectContext === undefined ? {} : { projectContext: input.projectContext }),
|
|
601
|
+
...(input.trainingProjectContext === undefined ? {} : {
|
|
602
|
+
trainingProjectRoots: input.trainingProjectContext.projects.map((project) => project.root),
|
|
603
|
+
}),
|
|
576
604
|
...(abilityContext?.skillRoot === undefined
|
|
577
605
|
? {}
|
|
578
606
|
: { abilitySkillRoot: abilityContext.skillRoot }),
|
|
579
607
|
});
|
|
580
608
|
let runtimeEnv = childEnv;
|
|
581
609
|
if (runtime.name === "claude"
|
|
582
|
-
&& input.projectContext !== undefined
|
|
610
|
+
&& (input.projectContext !== undefined || trainingRoots.length > 0)
|
|
583
611
|
&& claudeAdditionalDirectories.length > 0) {
|
|
584
612
|
const version = await (dependencies.probeClaudeVersion ?? probeClaudeVersion)(runtime.name, childEnv)
|
|
585
613
|
.catch(() => null);
|
|
@@ -591,13 +619,15 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
591
619
|
}
|
|
592
620
|
else {
|
|
593
621
|
runtimeEnv = withoutClaudeInstructionFlag(childEnv);
|
|
594
|
-
(
|
|
622
|
+
warn(CLAUDE_INSTRUCTION_WARNING);
|
|
595
623
|
callbacks.onConsole?.({
|
|
596
624
|
stream: "system",
|
|
597
625
|
text: `[runtime-warning] ${CLAUDE_INSTRUCTION_WARNING}`,
|
|
598
626
|
});
|
|
599
627
|
}
|
|
600
628
|
}
|
|
629
|
+
const legacyRoots = input.projectContext?.secondary.map((project) => project.root) ?? [];
|
|
630
|
+
const workspaceRoots = [...legacyRoots, ...trainingRoots];
|
|
601
631
|
const launchRequest = {
|
|
602
632
|
runtime: runtime.name,
|
|
603
633
|
bin: runtime.name === "deepseek-harness" ? "dsh-acp-demo" : runtime.name,
|
|
@@ -615,6 +645,9 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
615
645
|
...(launchReasoning === undefined ? {} : { reasoning: launchReasoning }),
|
|
616
646
|
...(launchSessionId === null ? {} : { sessionId: launchSessionId }),
|
|
617
647
|
resume: resuming,
|
|
648
|
+
...(runtime.codexCollaborationTools === undefined
|
|
649
|
+
? {}
|
|
650
|
+
: { codexCollaborationTools: runtime.codexCollaborationTools }),
|
|
618
651
|
...(attachmentPlan.nativeImagePaths.length > 0
|
|
619
652
|
? { imagePaths: attachmentPlan.nativeImagePaths }
|
|
620
653
|
: {}),
|
|
@@ -625,10 +658,14 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
625
658
|
? {}
|
|
626
659
|
: { claudeAdditionalDirectories }),
|
|
627
660
|
...(input.projectContext === undefined ? {} : {
|
|
628
|
-
workspaceRoots: Object.freeze(input.projectContext.secondary.map((project) => project.root)),
|
|
629
661
|
projectContext: input.projectContext,
|
|
630
662
|
}),
|
|
631
|
-
...(
|
|
663
|
+
...(input.projectContext === undefined && workspaceRoots.length === 0
|
|
664
|
+
? {}
|
|
665
|
+
: { workspaceRoots: Object.freeze(workspaceRoots) }),
|
|
666
|
+
...(input.trainingProjectContext === undefined ? {} : {
|
|
667
|
+
trainingProjectContext: input.trainingProjectContext,
|
|
668
|
+
}),
|
|
632
669
|
};
|
|
633
670
|
return launchRequest;
|
|
634
671
|
};
|
|
@@ -902,6 +939,24 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
902
939
|
const { exitCode, spawnError, terminationSignal } = runtimeExit;
|
|
903
940
|
localMemoryTelemetry?.markRuntimeExited(exitCode);
|
|
904
941
|
memoryPruneRuntimeExitCode = exitCode;
|
|
942
|
+
if (localMemoryResultPath !== null) {
|
|
943
|
+
const committed = await commitLocalMemoryResultAfterExit({
|
|
944
|
+
resultPath: localMemoryResultPath,
|
|
945
|
+
agentDir: executionWorkspace.dir,
|
|
946
|
+
executionId: input.executionId,
|
|
947
|
+
pruneTraceId: memoryPruneTraceId,
|
|
948
|
+
exitCode,
|
|
949
|
+
});
|
|
950
|
+
if (committed.status === "failed") {
|
|
951
|
+
dslog("local_memory.commit_failed", "结构化本地记忆候选未落地,保留结果文件", {
|
|
952
|
+
level: "WARN",
|
|
953
|
+
execution_id: input.executionId,
|
|
954
|
+
agent_handle: input.handle,
|
|
955
|
+
error_code: committed.error,
|
|
956
|
+
result_path: localMemoryResultPath,
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
905
960
|
const errorTail = [
|
|
906
961
|
stderrTail.trim(),
|
|
907
962
|
spawnError === undefined
|
|
@@ -1075,8 +1130,5 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
1075
1130
|
const detail = error instanceof Error ? error.message : String(error);
|
|
1076
1131
|
process.stderr.write(`[execution] failed to remove prompt ${workspace.systemPromptPath}: ${detail}\n`);
|
|
1077
1132
|
}
|
|
1078
|
-
if (isolatedMaintenance) {
|
|
1079
|
-
await cleanupSelfTrainingRunDir(join(workspace.dir, "tasks"), executionWorkspace.runDir);
|
|
1080
|
-
}
|
|
1081
1133
|
}
|
|
1082
1134
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|