@nowcrew/daemon 0.6.18 → 0.6.20
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/atomic-no-replace-rename.js +91 -0
- package/dist/control-plane-url.js +4 -2
- package/dist/directory-projection-publication.js +105 -0
- package/dist/directory-projection.js +20 -4
- package/dist/execution-journal.js +40 -4
- package/dist/execution-posix-stop-proof.js +82 -0
- package/dist/execution-runner.js +68 -8
- package/dist/local-executor.js +73 -52
- package/dist/machine-info.js +8 -5
- package/dist/project-skills/capability.js +109 -0
- package/dist/project-skills/controller-convergence.js +57 -0
- package/dist/project-skills/controller.js +80 -24
- package/dist/project-skills/initialized-reconciler.js +4 -4
- package/dist/project-skills/projection-state-domain.js +19 -2
- package/dist/project-skills/projection-state-store.js +3 -2
- package/dist/project-skills/projection-state-transaction.js +5 -1
- package/dist/project-skills/projection-state.js +1 -1
- package/dist/project-skills/reconciler.js +275 -102
- package/dist/project-skills/runtime-launch.js +102 -0
- package/dist/project-skills/runtime-root-bootstrap.js +47 -0
- package/dist/project-skills/runtime-root-domain.js +268 -0
- package/dist/project-skills/runtime-root-gc.js +293 -0
- package/dist/project-skills/runtime-root-lease-artifact.js +46 -0
- package/dist/project-skills/runtime-root-leases.js +487 -0
- package/dist/project-skills/runtime-root-source-identity.js +60 -0
- package/dist/project-skills/runtime-root-startup.js +49 -0
- package/dist/project-skills/runtime-root-state-artifact-domain.js +143 -0
- package/dist/project-skills/runtime-root-state-index.js +356 -0
- package/dist/project-skills/runtime-root-store.js +722 -0
- package/dist/project-skills/serve-capability.js +28 -0
- package/dist/project-skills/serve-startup.js +22 -0
- package/dist/project-skills/types.js +1 -0
- package/dist/provider-env.js +3 -0
- package/dist/runtimes/codex-home.js +50 -0
- package/dist/serve.js +58 -73
- package/dist/supervised-runtime.js +1 -5
- package/package.json +2 -2
package/dist/local-executor.js
CHANGED
|
@@ -2,9 +2,10 @@ import { createInterface } from "node:readline";
|
|
|
2
2
|
import { readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { delimiter, join } from "node:path";
|
|
4
4
|
import { prepareWorkspace, rotateAgentSession, safeKey, } from "./workspace.js";
|
|
5
|
-
import { CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_ENV, isClaudeAdditionalDirectoryInstructionsSupported, probeClaudeVersion,
|
|
5
|
+
import { CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_ENV, isClaudeAdditionalDirectoryInstructionsSupported, probeClaudeVersion, spawnClaude, } from "./runtimes/claude.js";
|
|
6
6
|
import { spawnCodex } from "./runtimes/codex.js";
|
|
7
7
|
import { DEEPSEEK_CODEX_MODEL, DEEPSEEK_CODEX_REASONING_LEVELS, materializeDeepSeekCodexHome, } from "./runtimes/codex-deepseek-config.js";
|
|
8
|
+
import { materializeDefaultCodexHome } from "./runtimes/codex-home.js";
|
|
8
9
|
import { spawnKimi, KIMI_EFFORT_LEVELS } from "./runtimes/kimi.js";
|
|
9
10
|
import { applyDeepSeekHarnessMachineEnv, applyProviderEnv, providerFingerprint, } from "./provider-env.js";
|
|
10
11
|
import { augmentedPath } from "./runtime-path.js";
|
|
@@ -20,7 +21,9 @@ import { awaitWithCancellation, RuntimeCancelledError, } from "./runtime-cancell
|
|
|
20
21
|
import { isRuntimeReadyEvent, } from "./runtime-startup-gate.js";
|
|
21
22
|
import { dslog } from "./slog.js";
|
|
22
23
|
import { boundedDiagnosticJsonArray } from "./diagnostic-json.js";
|
|
24
|
+
import { ProjectProjectionError, } from "./project-skills/reconciler.js";
|
|
23
25
|
import { formatProjectSkillRuntimeWarning } from "./project-skills/runtime-warning.js";
|
|
26
|
+
import { projectSkillRuntimeDirectories, redactProjectSkillRuntimeRootText, redactProjectSkillRuntimeRootValue, } from "./project-skills/runtime-launch.js";
|
|
24
27
|
import { diffMemoryPruneNotes, evaluateMemoryPrunePostcondition, inspectMemoryPruneFilesWithinDeadline, parseMemoryPruneTraceId, } from "./memory-prune-diagnostics.js";
|
|
25
28
|
import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
|
|
26
29
|
import { CodexStartupStageParser } from "./codex-startup-stage.js";
|
|
@@ -194,6 +197,19 @@ async function withKeyedLease(key, operation, cancellation) {
|
|
|
194
197
|
}
|
|
195
198
|
}
|
|
196
199
|
export async function executeLocal(input, callbacks = {}, dependencies = {}) {
|
|
200
|
+
const versionedProjectSkills = input.projectSkills !== undefined
|
|
201
|
+
&& !Array.isArray(input.projectSkills);
|
|
202
|
+
if (versionedProjectSkills && (input.runtime.name !== "codex" && input.runtime.name !== "claude")) {
|
|
203
|
+
throw new ProjectProjectionError("skill_projection_failed");
|
|
204
|
+
}
|
|
205
|
+
if (versionedProjectSkills && dependencies.projectSkills === undefined) {
|
|
206
|
+
throw new ProjectProjectionError("skill_projection_failed");
|
|
207
|
+
}
|
|
208
|
+
if (input.projectSkills !== undefined
|
|
209
|
+
&& dependencies.projectSkills !== undefined
|
|
210
|
+
&& dependencies.projectSkillLeaseSafeLaunchOwner === undefined) {
|
|
211
|
+
throw new ProjectProjectionError("skill_projection_failed");
|
|
212
|
+
}
|
|
197
213
|
const stableProtocolRuntime = dependencies.launchRuntime !== undefined;
|
|
198
214
|
const supportsNativeResume = input.runtime.name === "claude"
|
|
199
215
|
|| (stableProtocolRuntime && runtimeCapability(input.runtime.name).nativeResume);
|
|
@@ -257,6 +273,11 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
257
273
|
let memoryPruneSharedWriteKey = null;
|
|
258
274
|
let executionWorkspace = workspace;
|
|
259
275
|
let localMemoryTelemetry = null;
|
|
276
|
+
const inheritedEnv = { ...process.env };
|
|
277
|
+
for (const key of Object.keys(inheritedEnv)) {
|
|
278
|
+
if (key.startsWith("CREW_AGENT_MEMORY_"))
|
|
279
|
+
delete inheritedEnv[key];
|
|
280
|
+
}
|
|
260
281
|
try {
|
|
261
282
|
if (isDeepSeekCodex && !providerConfig.providerApiKey) {
|
|
262
283
|
throw new Error("DeepSeek API key is not configured for this Agent");
|
|
@@ -264,6 +285,11 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
264
285
|
if (isDeepSeekCodex) {
|
|
265
286
|
await awaitWithCancellation((dependencies.materializeDeepSeekCodexHome ?? materializeDeepSeekCodexHome)(workspace.homeDir), dependencies.cancellation);
|
|
266
287
|
}
|
|
288
|
+
else if (runtime.name === "codex") {
|
|
289
|
+
const sourceCodexHome = inheritedEnv.CODEX_HOME
|
|
290
|
+
?? (inheritedEnv.HOME === undefined ? undefined : join(inheritedEnv.HOME, ".codex"));
|
|
291
|
+
await awaitWithCancellation((dependencies.materializeDefaultCodexHome ?? materializeDefaultCodexHome)(workspace.homeDir, sourceCodexHome), dependencies.cancellation);
|
|
292
|
+
}
|
|
267
293
|
const supportsNativeResume = runtime.name === "claude"
|
|
268
294
|
|| (dependencies.launchRuntime !== undefined && runtimeCapability(runtime.name).nativeResume);
|
|
269
295
|
const storedCurrentPrior = input.session.enabled && supportsNativeResume
|
|
@@ -422,11 +448,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
422
448
|
}
|
|
423
449
|
}
|
|
424
450
|
memoryPruneFailurePhase = "runtime_prepare";
|
|
425
|
-
const inheritedEnv = { ...process.env };
|
|
426
|
-
for (const key of Object.keys(inheritedEnv)) {
|
|
427
|
-
if (key.startsWith("CREW_AGENT_MEMORY_"))
|
|
428
|
-
delete inheritedEnv[key];
|
|
429
|
-
}
|
|
430
451
|
const baseEnv = {
|
|
431
452
|
...inheritedEnv,
|
|
432
453
|
...sanitizeEnvVars(providerConfig.envVars),
|
|
@@ -479,7 +500,9 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
479
500
|
}
|
|
480
501
|
const runtimeLaunchAt = Date.now();
|
|
481
502
|
memoryPruneFailurePhase = "runtime_launch";
|
|
482
|
-
|
|
503
|
+
let activeProjectSkillRuntimeRoot;
|
|
504
|
+
const launchPreparedRuntime = async (runtimeRoot, abilityContext) => {
|
|
505
|
+
activeProjectSkillRuntimeRoot = runtimeRoot;
|
|
483
506
|
const effectiveSystemPrompt = abilityContext === undefined
|
|
484
507
|
? systemPrompt
|
|
485
508
|
: `${systemPrompt}\n\n${abilityContext.prompt}`;
|
|
@@ -515,31 +538,16 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
515
538
|
skill_count: abilityContext.skillCount,
|
|
516
539
|
});
|
|
517
540
|
}
|
|
518
|
-
const codexSkillRoots
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
? abilityContext?.skillRoot === undefined
|
|
529
|
-
? []
|
|
530
|
-
: await resolveOrderedUniqueClaudeDirectories([
|
|
531
|
-
join(workspace.dir, ".crew", "claude-skills"),
|
|
532
|
-
workspace.dir,
|
|
533
|
-
abilityContext.skillRoot,
|
|
534
|
-
])
|
|
535
|
-
: await resolveOrderedUniqueClaudeDirectories([
|
|
536
|
-
...input.projectContext.secondary.map((project) => project.root),
|
|
537
|
-
...(input.projectSkills === undefined
|
|
538
|
-
? []
|
|
539
|
-
: [join(workspace.dir, ".crew", "claude-skills")]),
|
|
540
|
-
...(abilityContext?.skillRoot === undefined ? [] : [abilityContext.skillRoot]),
|
|
541
|
-
], input.projectContext.primary === undefined ? [] : [input.projectContext.primary.root])
|
|
542
|
-
: [];
|
|
541
|
+
const { codexSkillRoots, claudeAdditionalDirectories } = await projectSkillRuntimeDirectories({
|
|
542
|
+
runtime: runtime.name,
|
|
543
|
+
...(runtimeRoot === undefined ? {} : { runtimeRoot }),
|
|
544
|
+
agentRoot: workspace.dir,
|
|
545
|
+
projectSkillsPresent: input.projectSkills !== undefined,
|
|
546
|
+
...(input.projectContext === undefined ? {} : { projectContext: input.projectContext }),
|
|
547
|
+
...(abilityContext?.skillRoot === undefined
|
|
548
|
+
? {}
|
|
549
|
+
: { abilitySkillRoot: abilityContext.skillRoot }),
|
|
550
|
+
});
|
|
543
551
|
let runtimeEnv = childEnv;
|
|
544
552
|
if (runtime.name === "claude"
|
|
545
553
|
&& input.projectContext !== undefined
|
|
@@ -565,7 +573,10 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
565
573
|
runtime: runtime.name,
|
|
566
574
|
bin: runtime.name === "deepseek-harness" ? "dsh-acp-demo" : runtime.name,
|
|
567
575
|
cwd: input.projectContext?.primary?.root ?? executionWorkspace.runDir,
|
|
568
|
-
...(
|
|
576
|
+
...(runtimeRoot !== undefined
|
|
577
|
+
|| (input.projectSkills === undefined && input.abilityRelease === undefined)
|
|
578
|
+
? {}
|
|
579
|
+
: { agentRoot: workspace.dir }),
|
|
569
580
|
systemPromptPath: workspace.systemPromptPath,
|
|
570
581
|
systemPrompt: effectiveSystemPrompt,
|
|
571
582
|
wakePrompt,
|
|
@@ -592,17 +603,22 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
592
603
|
memoryPruneFailurePhase = "runtime_launch";
|
|
593
604
|
return launchRuntime(launchRequest);
|
|
594
605
|
};
|
|
595
|
-
const launchWithAbility = () => input.abilityRelease !== undefined && dependencies.abilityRelease !== undefined
|
|
596
|
-
? dependencies.abilityRelease.prepareAndLaunch(input.launch.agentsRoot, input.handle, input.abilityRelease, launchPreparedRuntime)
|
|
597
|
-
: launchPreparedRuntime();
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
+
const launchWithAbility = (runtimeRoot) => input.abilityRelease !== undefined && dependencies.abilityRelease !== undefined
|
|
607
|
+
? dependencies.abilityRelease.prepareAndLaunch(input.launch.agentsRoot, input.handle, input.abilityRelease, (abilityContext) => launchPreparedRuntime(runtimeRoot, abilityContext))
|
|
608
|
+
: launchPreparedRuntime(runtimeRoot);
|
|
609
|
+
let child;
|
|
610
|
+
if (input.projectSkills !== undefined && dependencies.projectSkills !== undefined) {
|
|
611
|
+
const launchOwner = dependencies.projectSkillLeaseSafeLaunchOwner;
|
|
612
|
+
if (launchOwner === undefined)
|
|
613
|
+
throw new ProjectProjectionError("skill_projection_failed");
|
|
614
|
+
child = await dependencies.projectSkills.prepareAndLaunch(input.launch.agentsRoot, input.executionId, input.handle, input.projectSkills, launchOwner.claim(launchWithAbility), (warning) => callbacks.onConsole?.({
|
|
615
|
+
stream: "system",
|
|
616
|
+
text: formatProjectSkillRuntimeWarning(warning),
|
|
617
|
+
}));
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
child = await launchWithAbility();
|
|
621
|
+
}
|
|
606
622
|
localMemoryTelemetry?.markRuntimeStarted();
|
|
607
623
|
memoryPruneFailurePhase = "runtime_execution";
|
|
608
624
|
if (child.cancel !== undefined) {
|
|
@@ -654,7 +670,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
654
670
|
usage = meta.usage;
|
|
655
671
|
if (meta.model)
|
|
656
672
|
observedModel = meta.model;
|
|
657
|
-
for (const
|
|
673
|
+
for (const rawActivity of normalizeEvent(event)) {
|
|
674
|
+
const activity = redactProjectSkillRuntimeRootValue(rawActivity, activeProjectSkillRuntimeRoot);
|
|
658
675
|
if (activity.kind === "sending")
|
|
659
676
|
sentViaCrew = true;
|
|
660
677
|
activities.push(activity);
|
|
@@ -662,18 +679,20 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
662
679
|
}
|
|
663
680
|
const extracted = extractFinalText(event);
|
|
664
681
|
if (extracted) {
|
|
682
|
+
const safeExtracted = redactProjectSkillRuntimeRootText(extracted, activeProjectSkillRuntimeRoot);
|
|
665
683
|
const incremental = typeof event === "object" && event !== null
|
|
666
684
|
&& "type" in event && (event.type === "kimi.acp.text_delta"
|
|
667
685
|
|| event.type === "hermes.acp.text_delta"
|
|
668
686
|
|| event.type === "deepseek-harness.acp.text_delta"
|
|
669
687
|
|| event.type === "opencode.text_delta");
|
|
670
|
-
finalText = incremental ? `${finalText ?? ""}${
|
|
688
|
+
finalText = incremental ? `${finalText ?? ""}${safeExtracted}` : safeExtracted;
|
|
671
689
|
}
|
|
672
690
|
for (const text of decodeExternalOutputEvent(runtime.name, event, externalOutput)) {
|
|
673
|
-
callbacks.onExternalOutput?.(text);
|
|
691
|
+
callbacks.onExternalOutput?.(redactProjectSkillRuntimeRootText(text, activeProjectSkillRuntimeRoot));
|
|
692
|
+
}
|
|
693
|
+
for (const chunk of consoleFormatter.format(event)) {
|
|
694
|
+
callbacks.onConsole?.(redactProjectSkillRuntimeRootValue(chunk, activeProjectSkillRuntimeRoot));
|
|
674
695
|
}
|
|
675
|
-
for (const chunk of consoleFormatter.format(event))
|
|
676
|
-
callbacks.onConsole?.(chunk);
|
|
677
696
|
});
|
|
678
697
|
let stderrTail = "";
|
|
679
698
|
const codexStartupStageState = { last: null };
|
|
@@ -694,8 +713,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
694
713
|
})
|
|
695
714
|
: null;
|
|
696
715
|
child.stderr.on("data", (data) => {
|
|
697
|
-
|
|
698
|
-
|
|
716
|
+
const text = redactProjectSkillRuntimeRootText(String(data), activeProjectSkillRuntimeRoot);
|
|
717
|
+
process.stderr.write(text);
|
|
699
718
|
stderrTail = (stderrTail + text).slice(-STDERR_TAIL_CAP);
|
|
700
719
|
codexStartupStageParser?.push(text);
|
|
701
720
|
});
|
|
@@ -755,7 +774,9 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
755
774
|
memoryPruneRuntimeExitCode = exitCode;
|
|
756
775
|
const errorTail = [
|
|
757
776
|
stderrTail.trim(),
|
|
758
|
-
spawnError
|
|
777
|
+
spawnError === undefined
|
|
778
|
+
? undefined
|
|
779
|
+
: redactProjectSkillRuntimeRootText(spawnError, activeProjectSkillRuntimeRoot),
|
|
759
780
|
terminationSignal ? `terminated by ${terminationSignal}` : undefined,
|
|
760
781
|
].filter(Boolean).join(" ").trim();
|
|
761
782
|
const finish = exitActivity(runtime.name, exitCode, errorTail);
|
package/dist/machine-info.js
CHANGED
|
@@ -61,7 +61,9 @@ export const daemonCapabilities = (runtimePlatform = process.platform, readiness
|
|
|
61
61
|
export const daemonCapabilityBindings = (runtimePlatform = process.platform, injectedCapabilities) => {
|
|
62
62
|
const snapshot = injectedCapabilities === undefined
|
|
63
63
|
? daemonCapabilities(runtimePlatform)
|
|
64
|
-
: Object.
|
|
64
|
+
: (Object.isFrozen(injectedCapabilities)
|
|
65
|
+
? injectedCapabilities
|
|
66
|
+
: Object.freeze([...injectedCapabilities]));
|
|
65
67
|
return Object.freeze({
|
|
66
68
|
controlPlaneUrl: snapshot,
|
|
67
69
|
machineHello: snapshot,
|
|
@@ -169,6 +171,10 @@ export async function collectMachineHello(agentsRoot, executionLimits, runtimePl
|
|
|
169
171
|
const executionRuntimes = backend.supported
|
|
170
172
|
? await (dependencies.detectExecutable ?? detectExecutionRuntimes)(runtimes)
|
|
171
173
|
: [];
|
|
174
|
+
const baseCapabilities = dependencies.capabilities ?? daemonCapabilities(runtimePlatform);
|
|
175
|
+
const capabilities = additionalCapabilities.length === 0 && Object.isFrozen(baseCapabilities)
|
|
176
|
+
? baseCapabilities
|
|
177
|
+
: Object.freeze([...baseCapabilities, ...additionalCapabilities]);
|
|
172
178
|
return {
|
|
173
179
|
type: "machine:hello",
|
|
174
180
|
hostname: hostname(),
|
|
@@ -176,10 +182,7 @@ export async function collectMachineHello(agentsRoot, executionLimits, runtimePl
|
|
|
176
182
|
daemonVersion: daemonVersion(),
|
|
177
183
|
runtimes,
|
|
178
184
|
executionRuntimes,
|
|
179
|
-
capabilities
|
|
180
|
-
...(dependencies.capabilities ?? daemonCapabilities(runtimePlatform)),
|
|
181
|
-
...additionalCapabilities,
|
|
182
|
-
]),
|
|
185
|
+
capabilities,
|
|
183
186
|
...(dependencies.profileName === undefined ? {} : {
|
|
184
187
|
layoutVersion: 1,
|
|
185
188
|
profileName: dependencies.profileName,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { PROJECT_SKILLS_CAPABILITY, PROJECT_SKILL_PROJECTION_V2_CAPABILITY, } from "./types.js";
|
|
2
|
+
export function projectSkillV2RolloutReady(platform, readiness) {
|
|
3
|
+
const platformReady = platform === "darwin"
|
|
4
|
+
|| platform === "linux"
|
|
5
|
+
|| (platform === "win32" && readiness.nativeWindowsSmokePassed);
|
|
6
|
+
return platformReady
|
|
7
|
+
&& readiness.runtimeRootStorePrimitiveReady
|
|
8
|
+
&& readiness.runtimeRootStoreRecoveryReady
|
|
9
|
+
&& readiness.leaseRecoveryReady
|
|
10
|
+
&& readiness.controllerReady
|
|
11
|
+
&& readiness.executionHandlerReady
|
|
12
|
+
&& readiness.codexAdapterReady
|
|
13
|
+
&& readiness.claudeAdapterReady;
|
|
14
|
+
}
|
|
15
|
+
export function effectiveProjectSkillCapabilities(capabilities, projectSkillsReady, projectSkillV2Ready) {
|
|
16
|
+
const projectSkillCapabilitiesReady = projectSkillsReady && projectSkillV2Ready;
|
|
17
|
+
if (!projectSkillCapabilitiesReady
|
|
18
|
+
&& !capabilities.includes(PROJECT_SKILLS_CAPABILITY)
|
|
19
|
+
&& !capabilities.includes(PROJECT_SKILL_PROJECTION_V2_CAPABILITY)
|
|
20
|
+
&& Object.isFrozen(capabilities))
|
|
21
|
+
return capabilities;
|
|
22
|
+
const filtered = capabilities.filter((capability) => capability !== PROJECT_SKILL_PROJECTION_V2_CAPABILITY
|
|
23
|
+
&& (projectSkillCapabilitiesReady || capability !== PROJECT_SKILLS_CAPABILITY));
|
|
24
|
+
if (!projectSkillCapabilitiesReady || !filtered.includes(PROJECT_SKILLS_CAPABILITY)) {
|
|
25
|
+
return Object.freeze(filtered);
|
|
26
|
+
}
|
|
27
|
+
const v1Index = filtered.indexOf(PROJECT_SKILLS_CAPABILITY);
|
|
28
|
+
return Object.freeze([
|
|
29
|
+
...filtered.slice(0, v1Index + 1),
|
|
30
|
+
PROJECT_SKILL_PROJECTION_V2_CAPABILITY,
|
|
31
|
+
...filtered.slice(v1Index + 1),
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
export function withProjectSkillCapabilityStatus(value, status) {
|
|
35
|
+
return {
|
|
36
|
+
...value,
|
|
37
|
+
projectSkillsStatus: status,
|
|
38
|
+
capabilities: status === "ready"
|
|
39
|
+
? value.capabilities
|
|
40
|
+
: effectiveProjectSkillCapabilities(value.capabilities, false, false),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const settleReadinessGate = async (operation) => {
|
|
44
|
+
try {
|
|
45
|
+
return await operation() === true;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
export async function createProjectSkillConnectionCapabilitySnapshot(input) {
|
|
52
|
+
const runtimeRootStorePrimitiveReady = await settleReadinessGate(input.probeRuntimeRootStorePrimitive);
|
|
53
|
+
const controllerReady = await settleReadinessGate(input.initializeController);
|
|
54
|
+
const recoveryReady = await settleReadinessGate(input.recoverRuntimeRootsAndLeases);
|
|
55
|
+
const readiness = Object.freeze({
|
|
56
|
+
runtimeRootStorePrimitiveReady,
|
|
57
|
+
runtimeRootStoreRecoveryReady: recoveryReady,
|
|
58
|
+
leaseRecoveryReady: recoveryReady,
|
|
59
|
+
controllerReady,
|
|
60
|
+
executionHandlerReady: true,
|
|
61
|
+
codexAdapterReady: true,
|
|
62
|
+
claudeAdapterReady: true,
|
|
63
|
+
nativeWindowsSmokePassed: false,
|
|
64
|
+
});
|
|
65
|
+
const projectSkillsReady = projectSkillV2RolloutReady(input.platform, readiness);
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
capabilities: effectiveProjectSkillCapabilities(input.baseCapabilities, projectSkillsReady, projectSkillsReady),
|
|
68
|
+
projectSkillsReady,
|
|
69
|
+
readiness,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export async function prepareProjectSkillConnectionCapabilities(input) {
|
|
73
|
+
const snapshot = await createProjectSkillConnectionCapabilitySnapshot({
|
|
74
|
+
...input,
|
|
75
|
+
baseCapabilities: [...input.baseCapabilities, ...await input.additionalCapabilities()],
|
|
76
|
+
});
|
|
77
|
+
input.applyReadiness(snapshot.projectSkillsReady);
|
|
78
|
+
return snapshot.capabilities;
|
|
79
|
+
}
|
|
80
|
+
export function createProjectSkillControllerReadiness(input) {
|
|
81
|
+
let status = "initializing";
|
|
82
|
+
let initialization = null;
|
|
83
|
+
const ensure = () => {
|
|
84
|
+
if (status === "ready")
|
|
85
|
+
return Promise.resolve(true);
|
|
86
|
+
if (initialization !== null)
|
|
87
|
+
return initialization;
|
|
88
|
+
status = "initializing";
|
|
89
|
+
const attempt = input.initialize().then(() => {
|
|
90
|
+
status = "ready";
|
|
91
|
+
return true;
|
|
92
|
+
}, (error) => {
|
|
93
|
+
status = "unavailable";
|
|
94
|
+
input.onUnavailable(error);
|
|
95
|
+
return false;
|
|
96
|
+
}).finally(() => {
|
|
97
|
+
if (initialization === attempt)
|
|
98
|
+
initialization = null;
|
|
99
|
+
input.onSettled();
|
|
100
|
+
});
|
|
101
|
+
initialization = attempt;
|
|
102
|
+
return attempt;
|
|
103
|
+
};
|
|
104
|
+
return Object.freeze({
|
|
105
|
+
status: () => status,
|
|
106
|
+
ensure,
|
|
107
|
+
applyPreflight: (ready) => { status = ready ? "ready" : "unavailable"; },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ProjectProjectionError, } from "./reconciler.js";
|
|
2
|
+
export const MAX_REMEMBERED_PROJECT_SKILL_AGENTS = 64;
|
|
3
|
+
const compareBinary = (left, right) => {
|
|
4
|
+
if (left < right)
|
|
5
|
+
return -1;
|
|
6
|
+
if (left > right)
|
|
7
|
+
return 1;
|
|
8
|
+
return 0;
|
|
9
|
+
};
|
|
10
|
+
const copySnapshot = (snapshot) => Object.freeze({
|
|
11
|
+
bindings: Object.freeze(snapshot.bindings.map((binding) => Object.freeze({ ...binding }))),
|
|
12
|
+
generation: snapshot.generation,
|
|
13
|
+
});
|
|
14
|
+
export function createProjectSkillSnapshotEpoch(maxEntries = MAX_REMEMBERED_PROJECT_SKILL_AGENTS) {
|
|
15
|
+
if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
|
|
16
|
+
throw new ProjectProjectionError("skill_projection_failed");
|
|
17
|
+
}
|
|
18
|
+
const snapshots = new Map();
|
|
19
|
+
return Object.freeze({
|
|
20
|
+
begin() {
|
|
21
|
+
snapshots.clear();
|
|
22
|
+
},
|
|
23
|
+
remember(handle, snapshot) {
|
|
24
|
+
const previous = snapshots.get(handle);
|
|
25
|
+
if (previous !== undefined && snapshot.generation < previous.generation)
|
|
26
|
+
return;
|
|
27
|
+
if (previous === undefined && snapshots.size >= maxEntries) {
|
|
28
|
+
throw new ProjectProjectionError("skill_projection_failed");
|
|
29
|
+
}
|
|
30
|
+
snapshots.set(handle, copySnapshot(snapshot));
|
|
31
|
+
},
|
|
32
|
+
forget(handle) {
|
|
33
|
+
snapshots.delete(handle);
|
|
34
|
+
},
|
|
35
|
+
entries() {
|
|
36
|
+
return Object.freeze([...snapshots.entries()]
|
|
37
|
+
.sort(([left], [right]) => compareBinary(left, right))
|
|
38
|
+
.map(([handle, snapshot]) => Object.freeze([handle, copySnapshot(snapshot)])));
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const stableErrorCode = (reason) => reason?.code === "skill_projection_snapshot_corrupt"
|
|
43
|
+
? "skill_projection_snapshot_corrupt"
|
|
44
|
+
: "skill_projection_failed";
|
|
45
|
+
export function toPathPrivateConvergenceResults(entries, results) {
|
|
46
|
+
return Object.freeze(entries.map(([handle], index) => {
|
|
47
|
+
const result = results[index];
|
|
48
|
+
if (result?.status === "fulfilled") {
|
|
49
|
+
return Object.freeze({ handle, status: "fulfilled" });
|
|
50
|
+
}
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
handle,
|
|
53
|
+
status: "rejected",
|
|
54
|
+
errorCode: stableErrorCode(result?.reason),
|
|
55
|
+
});
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
@@ -5,6 +5,7 @@ import { isProjectId, isProjectSkillName, compareProjectSkillRefs, MAX_AGENT_PRO
|
|
|
5
5
|
import { ProjectProjectionError, } from "./reconciler.js";
|
|
6
6
|
import { AgentHandleSchema } from "../execution-protocol.js";
|
|
7
7
|
import { createPromiseTail } from "../promise-tail.js";
|
|
8
|
+
import { createProjectSkillSnapshotEpoch, MAX_REMEMBERED_PROJECT_SKILL_AGENTS, toPathPrivateConvergenceResults, } from "./controller-convergence.js";
|
|
8
9
|
const ProjectIdSchema = z.string().refine(isProjectId);
|
|
9
10
|
const AgentSkillsSyncSchema = z.object({
|
|
10
11
|
type: z.literal("agent:skills:sync"),
|
|
@@ -56,6 +57,13 @@ class ProjectScanTimeoutError extends Error {
|
|
|
56
57
|
this.name = "ProjectScanTimeoutError";
|
|
57
58
|
}
|
|
58
59
|
}
|
|
60
|
+
class ProjectSkillStaleEpochError extends Error {
|
|
61
|
+
code = "skill_projection_stale";
|
|
62
|
+
constructor() {
|
|
63
|
+
super("skill_projection_stale");
|
|
64
|
+
this.name = "ProjectSkillStaleEpochError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
59
67
|
const withinScanDeadline = async (operation, timeoutMs) => {
|
|
60
68
|
let timer;
|
|
61
69
|
try {
|
|
@@ -79,7 +87,12 @@ export function createProjectSkillsController(deps) {
|
|
|
79
87
|
let initializationState = "idle";
|
|
80
88
|
const scan = deps.scan ?? scanProjects;
|
|
81
89
|
const scanTimeoutMs = deps.scanTimeoutMs ?? DEFAULT_PROJECT_SCAN_TIMEOUT_MS;
|
|
82
|
-
const
|
|
90
|
+
const snapshotEpoch = createProjectSkillSnapshotEpoch();
|
|
91
|
+
let connectionEpochIdentity = Object.freeze({});
|
|
92
|
+
const assertCurrentEpoch = (token) => {
|
|
93
|
+
if (token !== undefined && token !== connectionEpochIdentity)
|
|
94
|
+
throw new ProjectSkillStaleEpochError();
|
|
95
|
+
};
|
|
83
96
|
const scanCurrent = async (deferredProjectId) => withinScanDeadline(scan(await deps.registry.list(), undefined, [
|
|
84
97
|
...scanned
|
|
85
98
|
.map((project) => project.inventory.projectId)
|
|
@@ -97,17 +110,25 @@ export function createProjectSkillsController(deps) {
|
|
|
97
110
|
}));
|
|
98
111
|
};
|
|
99
112
|
const enqueue = (operation) => operationTail.enqueue(operation);
|
|
100
|
-
const applyV2Snapshot = async (handle, snapshot) => {
|
|
113
|
+
const applyV2Snapshot = async (handle, snapshot, epochToken) => {
|
|
101
114
|
if (deps.ensureSnapshot === undefined)
|
|
102
115
|
throw new ProjectProjectionError("skill_projection_failed");
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
assertCurrentEpoch(epochToken);
|
|
117
|
+
const acceptedEpochIdentity = connectionEpochIdentity;
|
|
118
|
+
const rememberedEntries = snapshotEpoch.entries();
|
|
119
|
+
const firstAcceptedInEpoch = !rememberedEntries.some(([candidate]) => candidate === handle);
|
|
120
|
+
if (firstAcceptedInEpoch && rememberedEntries.length >= MAX_REMEMBERED_PROJECT_SKILL_AGENTS) {
|
|
121
|
+
throw new ProjectProjectionError("skill_projection_failed");
|
|
122
|
+
}
|
|
123
|
+
if (firstAcceptedInEpoch) {
|
|
124
|
+
await deps.ensureSnapshot(handle, snapshot, Object.freeze({ refreshCopyProjection: true }));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
await deps.ensureSnapshot(handle, snapshot);
|
|
110
128
|
}
|
|
129
|
+
assertCurrentEpoch(epochToken);
|
|
130
|
+
if (acceptedEpochIdentity === connectionEpochIdentity)
|
|
131
|
+
snapshotEpoch.remember(handle, snapshot);
|
|
111
132
|
return Object.freeze(projectSkillResolutionRecords(snapshot.bindings, scanned)
|
|
112
133
|
.map((record) => Object.freeze({
|
|
113
134
|
projectId: record.projectId,
|
|
@@ -115,15 +136,24 @@ export function createProjectSkillsController(deps) {
|
|
|
115
136
|
status: record.mode === "resolved" ? "linked" : "unavailable",
|
|
116
137
|
})));
|
|
117
138
|
};
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
139
|
+
const convergeV2Snapshots = async (refreshCopyProjection, epochToken) => {
|
|
140
|
+
assertCurrentEpoch(epochToken);
|
|
141
|
+
const acceptedEpochIdentity = connectionEpochIdentity;
|
|
142
|
+
const entries = snapshotEpoch.entries();
|
|
143
|
+
if (deps.ensureSnapshot === undefined || entries.length === 0)
|
|
144
|
+
return;
|
|
145
|
+
const results = await Promise.allSettled(entries.map(([handle, snapshot]) => refreshCopyProjection
|
|
146
|
+
? deps.ensureSnapshot(handle, snapshot, Object.freeze({ refreshCopyProjection: true }))
|
|
147
|
+
: deps.ensureSnapshot(handle, snapshot)));
|
|
148
|
+
if (acceptedEpochIdentity !== connectionEpochIdentity) {
|
|
149
|
+
if (epochToken !== undefined)
|
|
150
|
+
throw new ProjectSkillStaleEpochError();
|
|
122
151
|
return;
|
|
123
152
|
}
|
|
124
|
-
|
|
125
|
-
|
|
153
|
+
try {
|
|
154
|
+
deps.onConvergence?.(toPathPrivateConvergenceResults(entries, results));
|
|
126
155
|
}
|
|
156
|
+
catch { /* convergence diagnostics must not change a successful inventory mutation */ }
|
|
127
157
|
};
|
|
128
158
|
const initialize = () => {
|
|
129
159
|
if (initializationState === "ready" && initialization !== null)
|
|
@@ -141,24 +171,40 @@ export function createProjectSkillsController(deps) {
|
|
|
141
171
|
return attempt;
|
|
142
172
|
};
|
|
143
173
|
return {
|
|
174
|
+
beginConnectionEpoch: () => {
|
|
175
|
+
connectionEpochIdentity = Object.freeze({});
|
|
176
|
+
snapshotEpoch.begin();
|
|
177
|
+
return connectionEpochIdentity;
|
|
178
|
+
},
|
|
144
179
|
initialize,
|
|
145
180
|
publishCurrent: () => enqueue(publishCurrent),
|
|
146
181
|
scannedProjects: () => scanned,
|
|
147
|
-
async handle(input) {
|
|
182
|
+
async handle(input, epochToken) {
|
|
148
183
|
const parsed = ProjectCommandSchema.safeParse(input);
|
|
149
184
|
if (!parsed.success)
|
|
150
185
|
return { ok: false, error: "invalid_project_command" };
|
|
186
|
+
try {
|
|
187
|
+
assertCurrentEpoch(epochToken);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
return { ok: false, error: error.code };
|
|
191
|
+
}
|
|
151
192
|
if (parsed.data.type === "agent:skills:sync") {
|
|
152
193
|
try {
|
|
153
194
|
await initialize();
|
|
195
|
+
assertCurrentEpoch(epochToken);
|
|
154
196
|
}
|
|
155
197
|
catch {
|
|
198
|
+
if (epochToken !== undefined && epochToken !== connectionEpochIdentity) {
|
|
199
|
+
return { ok: false, error: "skill_projection_stale" };
|
|
200
|
+
}
|
|
156
201
|
return { ok: false, error: "project_skills_unavailable" };
|
|
157
202
|
}
|
|
158
203
|
}
|
|
159
204
|
return enqueue(async () => {
|
|
160
205
|
const command = parsed.data;
|
|
161
206
|
try {
|
|
207
|
+
assertCurrentEpoch(epochToken);
|
|
162
208
|
if (command.type === "agent:skills:sync") {
|
|
163
209
|
if (command.generation !== undefined) {
|
|
164
210
|
const snapshot = Object.freeze({
|
|
@@ -167,13 +213,14 @@ export function createProjectSkillsController(deps) {
|
|
|
167
213
|
});
|
|
168
214
|
return {
|
|
169
215
|
ok: true,
|
|
170
|
-
data: { bindings: await applyV2Snapshot(command.handle, snapshot) },
|
|
216
|
+
data: { bindings: await applyV2Snapshot(command.handle, snapshot, epochToken) },
|
|
171
217
|
};
|
|
172
218
|
}
|
|
173
219
|
if (deps.reconcile === undefined)
|
|
174
220
|
return { ok: false, error: "skill_projection_failed" };
|
|
175
221
|
const bindings = await deps.reconcile(command.handle, command.bindings);
|
|
176
|
-
|
|
222
|
+
assertCurrentEpoch(epochToken);
|
|
223
|
+
snapshotEpoch.forget(command.handle);
|
|
177
224
|
return {
|
|
178
225
|
ok: true,
|
|
179
226
|
data: { bindings },
|
|
@@ -182,8 +229,11 @@ export function createProjectSkillsController(deps) {
|
|
|
182
229
|
if (command.type === "project:add") {
|
|
183
230
|
const existed = (await deps.registry.list())
|
|
184
231
|
.some((project) => project.projectId === command.projectId);
|
|
232
|
+
assertCurrentEpoch(epochToken);
|
|
185
233
|
await deps.registry.add(command.projectId, command.root);
|
|
234
|
+
assertCurrentEpoch(epochToken);
|
|
186
235
|
const next = await scanCurrent();
|
|
236
|
+
assertCurrentEpoch(epochToken);
|
|
187
237
|
const added = next.find((project) => project.inventory.projectId === command.projectId);
|
|
188
238
|
if (!existed && added?.inventory.errorCode === "machine_project_skill_limit_exceeded") {
|
|
189
239
|
await deps.registry.remove(command.projectId);
|
|
@@ -193,9 +243,11 @@ export function createProjectSkillsController(deps) {
|
|
|
193
243
|
}
|
|
194
244
|
else if (command.type === "project:remove") {
|
|
195
245
|
await deps.registry.remove(command.projectId);
|
|
246
|
+
assertCurrentEpoch(epochToken);
|
|
196
247
|
}
|
|
197
248
|
else {
|
|
198
249
|
const projects = await deps.registry.list();
|
|
250
|
+
assertCurrentEpoch(epochToken);
|
|
199
251
|
if (!projects.some((project) => project.projectId === command.projectId)) {
|
|
200
252
|
return { ok: false, error: "project_not_found" };
|
|
201
253
|
}
|
|
@@ -203,8 +255,10 @@ export function createProjectSkillsController(deps) {
|
|
|
203
255
|
if (command.type !== "project:add") {
|
|
204
256
|
await refresh(command.type === "project:rescan" ? command.projectId : undefined);
|
|
205
257
|
}
|
|
206
|
-
|
|
258
|
+
assertCurrentEpoch(epochToken);
|
|
207
259
|
await publishCurrent();
|
|
260
|
+
assertCurrentEpoch(epochToken);
|
|
261
|
+
await convergeV2Snapshots(command.type === "project:rescan", epochToken);
|
|
208
262
|
return { ok: true, data: { projectId: command.projectId } };
|
|
209
263
|
}
|
|
210
264
|
catch (error) {
|
|
@@ -214,11 +268,13 @@ export function createProjectSkillsController(deps) {
|
|
|
214
268
|
? error.code
|
|
215
269
|
: error.code === "skill_name_conflict"
|
|
216
270
|
? "skill_name_conflict"
|
|
217
|
-
: error.code === "
|
|
218
|
-
? "
|
|
219
|
-
: error.code === "
|
|
220
|
-
? "
|
|
221
|
-
: "
|
|
271
|
+
: error.code === "skill_projection_stale"
|
|
272
|
+
? "skill_projection_stale"
|
|
273
|
+
: error.code === "skill_projection_failed"
|
|
274
|
+
? "skill_projection_failed"
|
|
275
|
+
: error.code === "skill_projection_snapshot_corrupt"
|
|
276
|
+
? "skill_projection_snapshot_corrupt"
|
|
277
|
+
: "project_operation_failed",
|
|
222
278
|
};
|
|
223
279
|
}
|
|
224
280
|
});
|