@nowcrew/daemon 0.6.38 → 0.6.40
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 +57 -3
- package/dist/agent-ability/resolver.js +19 -0
- package/dist/agent-ability/types.js +13 -3
- package/dist/execution-runner.js +61 -2
- package/dist/local-executor.js +24 -0
- package/dist/machine-info.js +2 -0
- package/dist/project-dependencies.js +272 -0
- package/dist/project-dependency-manifest.js +35 -0
- package/dist/project-registry-wiring.js +29 -0
- package/dist/serve.js +5 -2
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@ const isRealDirectory = (path) => lstat(path)
|
|
|
15
15
|
const repositoryKey = (url) => createHash("sha256").update(url).digest("hex");
|
|
16
16
|
const posixPath = (value) => value.split(sep).join("/");
|
|
17
17
|
const sameSnapshot = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
18
|
-
const readonlyTree = async (root) => {
|
|
18
|
+
const readonlyTree = async (root, options = {}) => {
|
|
19
19
|
const entries = await readdir(root, { withFileTypes: true });
|
|
20
20
|
for (const entry of entries) {
|
|
21
21
|
const path = join(root, entry.name);
|
|
@@ -28,7 +28,8 @@ const readonlyTree = async (root) => {
|
|
|
28
28
|
await chmod(path, current.mode & 0o111 ? 0o555 : 0o444);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
// Keep the training root renameable on macOS; its children still carry the package ownership mode.
|
|
32
|
+
await chmod(root, options.rootWritable === true ? 0o700 : 0o555);
|
|
32
33
|
};
|
|
33
34
|
const writableTree = async (root) => {
|
|
34
35
|
const info = await lstat(root);
|
|
@@ -42,6 +43,44 @@ const writableTree = async (root) => {
|
|
|
42
43
|
for (const entry of await readdir(root))
|
|
43
44
|
await writableTree(join(root, entry));
|
|
44
45
|
};
|
|
46
|
+
const writableDirectories = async (root) => {
|
|
47
|
+
const info = await lstat(root);
|
|
48
|
+
if (info.isSymbolicLink() || !info.isDirectory())
|
|
49
|
+
return;
|
|
50
|
+
await chmod(root, 0o700);
|
|
51
|
+
for (const entry of await readdir(root))
|
|
52
|
+
await writableDirectories(join(root, entry));
|
|
53
|
+
};
|
|
54
|
+
const copyMissingEntries = async (source, target) => {
|
|
55
|
+
const sourceInfo = await lstat(source).catch(() => null);
|
|
56
|
+
if (sourceInfo === null || sourceInfo.isSymbolicLink() || !sourceInfo.isDirectory())
|
|
57
|
+
return;
|
|
58
|
+
await mkdir(target, { recursive: true });
|
|
59
|
+
for (const entry of await readdir(source, { withFileTypes: true })) {
|
|
60
|
+
const sourceEntry = join(source, entry.name);
|
|
61
|
+
const targetEntry = join(target, entry.name);
|
|
62
|
+
if (await exists(targetEntry)) {
|
|
63
|
+
if (entry.isDirectory() && await isRealDirectory(targetEntry)) {
|
|
64
|
+
await copyMissingEntries(sourceEntry, targetEntry);
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (entry.isSymbolicLink())
|
|
69
|
+
continue;
|
|
70
|
+
if (entry.isDirectory()) {
|
|
71
|
+
await mkdir(targetEntry, { recursive: true });
|
|
72
|
+
await copyMissingEntries(sourceEntry, targetEntry);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const sourceEntryInfo = await lstat(sourceEntry);
|
|
76
|
+
// Read-only files belong to the package, so a removed package file must not
|
|
77
|
+
// be mistaken for runtime state during a later Release refresh.
|
|
78
|
+
if ((sourceEntryInfo.mode & 0o222) === 0)
|
|
79
|
+
continue;
|
|
80
|
+
await cp(sourceEntry, targetEntry, { recursive: true, preserveTimestamps: true });
|
|
81
|
+
await writableTree(targetEntry);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
45
84
|
const removeManagedTree = async (root) => {
|
|
46
85
|
if (!await exists(root))
|
|
47
86
|
return;
|
|
@@ -271,14 +310,17 @@ const finishTrainingRestore = async (agentRoot) => {
|
|
|
271
310
|
const hasRollback = await assertManagedTrainingTreeIfPresent(rollbackRoot, "ability_training_rollback_invalid");
|
|
272
311
|
const hasDiscard = await assertManagedTrainingTreeIfPresent(discardRoot, "ability_training_discard_invalid");
|
|
273
312
|
if (hasTraining && hasRollback && !hasDiscard) {
|
|
313
|
+
await chmod(trainingRoot, 0o700);
|
|
274
314
|
await rename(trainingRoot, discardRoot);
|
|
275
315
|
await writeTrainingSwitchJournal(agentRoot, { operation: "restore", phase: "current-moved" });
|
|
276
316
|
}
|
|
277
317
|
if (!await exists(trainingRoot) && await exists(rollbackRoot)) {
|
|
318
|
+
await chmod(rollbackRoot, 0o700);
|
|
278
319
|
await rename(rollbackRoot, trainingRoot);
|
|
279
320
|
await writeTrainingSwitchJournal(agentRoot, { operation: "restore", phase: "restored" });
|
|
280
321
|
}
|
|
281
322
|
if (!await exists(trainingRoot) && !await exists(rollbackRoot) && await exists(discardRoot)) {
|
|
323
|
+
await chmod(discardRoot, 0o700);
|
|
282
324
|
await rename(discardRoot, trainingRoot);
|
|
283
325
|
}
|
|
284
326
|
if (!await exists(trainingRoot))
|
|
@@ -304,9 +346,11 @@ const recoverTrainingSwitch = async (agentRoot) => {
|
|
|
304
346
|
await removeManagedTree(staging);
|
|
305
347
|
}
|
|
306
348
|
else if (await exists(staging)) {
|
|
349
|
+
await chmod(staging, 0o700);
|
|
307
350
|
await rename(staging, trainingRoot);
|
|
308
351
|
}
|
|
309
352
|
else if (await exists(rollbackRoot)) {
|
|
353
|
+
await chmod(rollbackRoot, 0o700);
|
|
310
354
|
await rename(rollbackRoot, trainingRoot);
|
|
311
355
|
}
|
|
312
356
|
else {
|
|
@@ -327,6 +371,9 @@ const switchTrainingDirectory = async (agentRoot, staging) => {
|
|
|
327
371
|
});
|
|
328
372
|
try {
|
|
329
373
|
if (await exists(trainingRoot)) {
|
|
374
|
+
// Older materializer versions made the root itself read-only. macOS requires
|
|
375
|
+
// a writable source directory for rename, while its package children stay protected.
|
|
376
|
+
await chmod(trainingRoot, 0o700);
|
|
330
377
|
await rename(trainingRoot, rollbackRoot);
|
|
331
378
|
await writeTrainingSwitchJournal(agentRoot, {
|
|
332
379
|
operation: "install", phase: "previous-renamed", stagingName: basename(staging),
|
|
@@ -489,11 +536,18 @@ const materializeWorkspaceTraining = async (agentsRoot, handle, ability) => {
|
|
|
489
536
|
})),
|
|
490
537
|
evals: evalPaths.map((path) => `training/evals/${layerRelativePath(path, "evals")}`),
|
|
491
538
|
}, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
492
|
-
await readonlyTree(staging);
|
|
539
|
+
await readonlyTree(staging, { rootWritable: true });
|
|
493
540
|
for (const asset of ability.workspace) {
|
|
494
541
|
if (asset.mode === "managed-copy") {
|
|
495
542
|
await writableTree(join(staging, "workspace", abilityWorkspaceProjectionPath(asset.target)));
|
|
496
543
|
}
|
|
544
|
+
else if (asset.mode === "managed-runtime") {
|
|
545
|
+
const target = join(staging, "workspace", abilityWorkspaceProjectionPath(asset.target));
|
|
546
|
+
await writableDirectories(target);
|
|
547
|
+
const previous = join(trainingRoot, "workspace", abilityWorkspaceProjectionPath(asset.target));
|
|
548
|
+
if (previousCatalog !== null && await exists(previous))
|
|
549
|
+
await copyMissingEntries(previous, target);
|
|
550
|
+
}
|
|
497
551
|
}
|
|
498
552
|
await switchTrainingDirectory(agentRoot, staging);
|
|
499
553
|
}
|
|
@@ -6,6 +6,7 @@ import { promisify } from "node:util";
|
|
|
6
6
|
import { parse } from "yaml";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { abilityWorkspaceProjectionPath, DaemonAbilityLockSchema, DaemonAbilityManifestSchema, matchesAbilityBranch, } from "./types.js";
|
|
9
|
+
import { PROJECT_MANIFEST_SOURCE_PATH, ProjectDependencyManifestSchema, } from "../project-dependency-manifest.js";
|
|
9
10
|
const runFile = promisify(execFile);
|
|
10
11
|
const MAX_FILES = 5_000;
|
|
11
12
|
const MAX_TOTAL_BYTES = 25 * 1024 * 1024;
|
|
@@ -76,6 +77,22 @@ const git = async (args, cwd) => {
|
|
|
76
77
|
});
|
|
77
78
|
return result.stdout.trim();
|
|
78
79
|
};
|
|
80
|
+
export const projectDependencyIds = (files, manifest) => {
|
|
81
|
+
const assets = manifest.spec.workspace.assets.filter((asset) => asset.source === PROJECT_MANIFEST_SOURCE_PATH);
|
|
82
|
+
if (assets.length === 0)
|
|
83
|
+
return Object.freeze([]);
|
|
84
|
+
if (assets.length > 1)
|
|
85
|
+
throw new Error("ability_project_dependency_manifest_duplicate");
|
|
86
|
+
const file = files.find((candidate) => candidate.path === PROJECT_MANIFEST_SOURCE_PATH);
|
|
87
|
+
if (file === undefined)
|
|
88
|
+
throw new Error("ability_project_dependency_manifest_missing");
|
|
89
|
+
try {
|
|
90
|
+
return Object.freeze(ProjectDependencyManifestSchema.parse(parse(file.content.toString("utf8"))).projects.map((project) => project.id));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new Error("ability_project_dependency_manifest_invalid");
|
|
94
|
+
}
|
|
95
|
+
};
|
|
79
96
|
export const collectAbilityFiles = async (root) => {
|
|
80
97
|
const files = [];
|
|
81
98
|
let totalBytes = 0;
|
|
@@ -404,6 +421,7 @@ export async function resolveAbilityRelease(agentsRoot, command) {
|
|
|
404
421
|
if (memory.reduce((bytes, entry) => bytes + Buffer.byteLength(entry.content, "utf8"), 0) > MAX_MEMORY_BYTES) {
|
|
405
422
|
throw new Error("ability_memory_size_exceeded");
|
|
406
423
|
}
|
|
424
|
+
const projectDependencies = projectDependencyIds(files, manifest);
|
|
407
425
|
return {
|
|
408
426
|
branch: resolved.branch, rootCommit: resolved.commit, treeDigest: resolved.treeDigest,
|
|
409
427
|
artifactDigest, schemaVersion: manifest.apiVersion, manifest, lock, assetSummary,
|
|
@@ -419,6 +437,7 @@ export async function resolveAbilityRelease(agentsRoot, command) {
|
|
|
419
437
|
mode: skill.mode,
|
|
420
438
|
})),
|
|
421
439
|
workspace: manifest.spec.workspace.assets, evals: manifest.spec.evals.cases,
|
|
440
|
+
projectDependencies,
|
|
422
441
|
},
|
|
423
442
|
};
|
|
424
443
|
}
|
|
@@ -48,7 +48,7 @@ export function matchesAbilityBranch(glob, branch) {
|
|
|
48
48
|
}
|
|
49
49
|
return new RegExp(`${pattern}$`, "u").test(branch);
|
|
50
50
|
}
|
|
51
|
-
const ModeSchema = z.enum(["managed-readonly", "managed-copy", "overlay", "generated"]);
|
|
51
|
+
const ModeSchema = z.enum(["managed-readonly", "managed-copy", "managed-runtime", "overlay", "generated"]);
|
|
52
52
|
const LocalSkillSchema = z.object({ type: z.literal("local"), path: SafeAbilityPathSchema, catalog: SafeAbilityPathSchema.optional() }).strict();
|
|
53
53
|
const GitSkillSchema = z.object({
|
|
54
54
|
type: z.literal("git"), url: AbilityRepositoryUrlSchema, ref: z.string().min(1).max(256), path: SafeAbilityPathSchema,
|
|
@@ -80,8 +80,8 @@ export const DaemonAbilityManifestSchema = z.object({
|
|
|
80
80
|
minNowCrewVersion: z.string().regex(/^\d+\.\d+\.\d+$/u),
|
|
81
81
|
}).strict().optional(),
|
|
82
82
|
permissions: z.object({
|
|
83
|
-
network: z.literal("declared-only"), executableAssets: z.
|
|
84
|
-
secrets: z.
|
|
83
|
+
network: z.literal("declared-only"), executableAssets: z.enum(["skill-declared-only", "declared-only"]),
|
|
84
|
+
secrets: z.enum(["runtime-only", "controlled-memory"]), productionWrites: z.literal("human-confirmed").optional(),
|
|
85
85
|
}).strict(),
|
|
86
86
|
rollout: z.object({ mode: z.enum(["manual", "auto_after_gates", "scheduled", "pinned"]), canaryExecutions: z.number().int().min(0).max(100), rollbackOnGateFailure: z.boolean().optional() }).strict(),
|
|
87
87
|
}).strict(),
|
|
@@ -95,6 +95,16 @@ export const DaemonAbilityManifestSchema = z.object({
|
|
|
95
95
|
unique(manifest.spec.skills.map((skill) => skill.name.replace(/[^A-Za-z0-9._-]/gu, "_")), ["spec", "skills"], "Skill projection");
|
|
96
96
|
unique(manifest.spec.skills.map((skill) => skill.mount), ["spec", "skills"], "Skill mount");
|
|
97
97
|
unique(manifest.spec.workspace.assets.map((asset) => asset.target), ["spec", "workspace", "assets"], "Workspace target");
|
|
98
|
+
for (const asset of manifest.spec.workspace.assets) {
|
|
99
|
+
if (asset.source === "workspace/project-dependencies.yaml"
|
|
100
|
+
&& asset.target !== "training/workspace/project-dependencies.yaml") {
|
|
101
|
+
ctx.addIssue({
|
|
102
|
+
code: z.ZodIssueCode.custom,
|
|
103
|
+
path: ["spec", "workspace", "assets"],
|
|
104
|
+
message: "project dependency manifest target must be training/workspace/project-dependencies.yaml",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
98
108
|
unique(manifest.spec.evals.cases, ["spec", "evals", "cases"], "Eval case");
|
|
99
109
|
});
|
|
100
110
|
export const DaemonAbilityLockSchema = z.object({
|
package/dist/execution-runner.js
CHANGED
|
@@ -18,6 +18,7 @@ import { ProjectSkillRuntimeOwnershipUnverifiedError, } from "./project-skills/r
|
|
|
18
18
|
import { projectSkillExecutionProjection, projectSkillProjectionErrorCode } from "./project-skills/execution-adapter.js";
|
|
19
19
|
import { redactProjectSkillRuntimeRootError, } from "./project-skills/runtime-launch.js";
|
|
20
20
|
import { createProjectRegistry } from "./project-skills/registry.js";
|
|
21
|
+
import { ProjectDependencyError, } from "./project-dependencies.js";
|
|
21
22
|
import { ProjectContextUnavailableError, resolveProjectContext } from "./project-workspaces/resolver.js";
|
|
22
23
|
import { PROJECT_WORKSPACES_CAPABILITY } from "./machine-info.js";
|
|
23
24
|
import { PROJECT_SKILL_PROJECTION_V2_CAPABILITY } from "./project-skills/types.js";
|
|
@@ -333,7 +334,9 @@ function failedCompletion(spec, error, startedAt, finishedAt) {
|
|
|
333
334
|
? "queue_timeout"
|
|
334
335
|
: error instanceof ProjectContextUnavailableError
|
|
335
336
|
? error.code
|
|
336
|
-
:
|
|
337
|
+
: error instanceof ProjectDependencyError
|
|
338
|
+
? error.code
|
|
339
|
+
: projectSkillProjectionErrorCode(error) ?? "local_execution_failed";
|
|
337
340
|
return ExecutionCompletedSchema.parse({
|
|
338
341
|
type: "execution:completed",
|
|
339
342
|
protocolVersion: 1,
|
|
@@ -498,6 +501,19 @@ export async function runExecution(config, input, dependencies) {
|
|
|
498
501
|
let projectContext;
|
|
499
502
|
let sessionContextFingerprint;
|
|
500
503
|
const logicalProjectContext = spec.workspace.projectContext;
|
|
504
|
+
dslog("project_dependencies.preflight", "项目依赖准备前置状态", {
|
|
505
|
+
execution_id: spec.executionId,
|
|
506
|
+
agent_handle: spec.agent.handle,
|
|
507
|
+
has_ability_release: spec.agent.abilityRelease !== undefined,
|
|
508
|
+
ability_release_id: spec.agent.abilityRelease?.releaseId,
|
|
509
|
+
ability_root_commit: spec.agent.abilityRelease?.rootCommit,
|
|
510
|
+
has_project_context: logicalProjectContext !== undefined,
|
|
511
|
+
project_ids: JSON.stringify(logicalProjectContext?.projectIds ?? []),
|
|
512
|
+
project_context_primary: logicalProjectContext?.primaryProjectId,
|
|
513
|
+
dependency_prepare_expected: spec.agent.abilityRelease !== undefined
|
|
514
|
+
&& logicalProjectContext !== undefined
|
|
515
|
+
&& logicalProjectContext.projectIds.length > 0,
|
|
516
|
+
});
|
|
501
517
|
if (logicalProjectContext !== undefined && logicalProjectContext.projectIds.length > 0) {
|
|
502
518
|
const projectSnapshot = {
|
|
503
519
|
projectIds: logicalProjectContext.projectIds,
|
|
@@ -505,7 +521,50 @@ export async function runExecution(config, input, dependencies) {
|
|
|
505
521
|
? {}
|
|
506
522
|
: { primaryProjectId: logicalProjectContext.primaryProjectId }),
|
|
507
523
|
};
|
|
508
|
-
|
|
524
|
+
const projectWorkspaceRegistry = dependencies.projectWorkspaceRegistry
|
|
525
|
+
?? dependencies.projectRegistry
|
|
526
|
+
?? createProjectRegistry(config.agentsRoot);
|
|
527
|
+
if (spec.agent.abilityRelease !== undefined && dependencies.projectDependencies !== undefined) {
|
|
528
|
+
if (dependencies.abilityRelease !== undefined) {
|
|
529
|
+
await cancellable(dependencies.abilityRelease.apply(config.agentsRoot, spec.agent.handle, spec.agent.abilityRelease), dependencies.cancellation);
|
|
530
|
+
}
|
|
531
|
+
const preparedProjectDependencies = await cancellable(dependencies.projectDependencies.prepare({
|
|
532
|
+
trainingRoot: join(config.agentsRoot, spec.agent.handle, "training"),
|
|
533
|
+
projectsRoot: join(config.agentsRoot, "Projects"),
|
|
534
|
+
projectIds: projectSnapshot.projectIds,
|
|
535
|
+
}), dependencies.cancellation);
|
|
536
|
+
for (const project of preparedProjectDependencies) {
|
|
537
|
+
dslog("project_dependencies.prepared", "Training project dependency prepared", {
|
|
538
|
+
execution_id: spec.executionId,
|
|
539
|
+
agent_handle: spec.agent.handle,
|
|
540
|
+
project_id: project.projectId,
|
|
541
|
+
project_branch: project.branch,
|
|
542
|
+
project_branch_source: project.branchSource,
|
|
543
|
+
project_commit: project.resolvedCommit,
|
|
544
|
+
project_checkout_mode: project.checkoutMode,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
else if (spec.agent.abilityRelease === undefined) {
|
|
549
|
+
dslog("project_dependencies.skipped", "缺少 abilityRelease,未执行项目依赖准备", {
|
|
550
|
+
level: "WARN",
|
|
551
|
+
execution_id: spec.executionId,
|
|
552
|
+
agent_handle: spec.agent.handle,
|
|
553
|
+
project_ids: JSON.stringify(projectSnapshot.projectIds),
|
|
554
|
+
reason: "ability_release_missing",
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
dslog("project_dependencies.skipped", "Daemon 未提供项目依赖准备器", {
|
|
559
|
+
level: "WARN",
|
|
560
|
+
execution_id: spec.executionId,
|
|
561
|
+
agent_handle: spec.agent.handle,
|
|
562
|
+
project_ids: JSON.stringify(projectSnapshot.projectIds),
|
|
563
|
+
ability_release_id: spec.agent.abilityRelease.releaseId,
|
|
564
|
+
reason: "provisioner_missing",
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
projectContext = await cancellable(resolveProjectContext(projectSnapshot, projectWorkspaceRegistry), dependencies.cancellation);
|
|
509
568
|
sessionContextFingerprint = projectSessionContextFingerprint(spec.runtime.name, projectSnapshot);
|
|
510
569
|
}
|
|
511
570
|
let recalledMemory = "";
|
package/dist/local-executor.js
CHANGED
|
@@ -469,6 +469,9 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
469
469
|
CREW_TOKEN: input.launch.token,
|
|
470
470
|
CREW_CHANNEL: input.channelId,
|
|
471
471
|
CREW_HOME: workspace.dir,
|
|
472
|
+
// Expose only the training root; ability-specific workspace layout stays
|
|
473
|
+
// owned by the training package rather than the NowWork daemon.
|
|
474
|
+
QA_RUNTIME_ROOT: join(workspace.dir, "training"),
|
|
472
475
|
CREW_TASK_DIR: executionWorkspace.runDir,
|
|
473
476
|
CREW_TASK_LOG: executionWorkspace.workLogPath,
|
|
474
477
|
...(input.wakeMessageId ? { CREW_WAKE_MESSAGE_ID: input.wakeMessageId } : {}),
|
|
@@ -488,6 +491,27 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
488
491
|
const childEnv = runtime.name === "deepseek-harness"
|
|
489
492
|
? applyDeepSeekHarnessMachineEnv(providerEnv, inheritedEnv)
|
|
490
493
|
: providerEnv;
|
|
494
|
+
dslog("runtime.environment_contract", "Runtime 子进程环境契约", {
|
|
495
|
+
execution_id: input.executionId,
|
|
496
|
+
agent_handle: input.handle,
|
|
497
|
+
runtime: runtime.name,
|
|
498
|
+
qa_runtime_root: childEnv.QA_RUNTIME_ROOT ?? null,
|
|
499
|
+
qa_runtime_root_present: childEnv.QA_RUNTIME_ROOT !== undefined,
|
|
500
|
+
inherited_qa_runtime_root_present: inheritedEnv.QA_RUNTIME_ROOT !== undefined,
|
|
501
|
+
training_root: join(workspace.dir, "training"),
|
|
502
|
+
project_context_present: input.projectContext !== undefined,
|
|
503
|
+
project_ids: JSON.stringify(input.projectContext === undefined
|
|
504
|
+
? []
|
|
505
|
+
: [
|
|
506
|
+
...(input.projectContext.primary === undefined
|
|
507
|
+
? []
|
|
508
|
+
: [input.projectContext.primary.projectId]),
|
|
509
|
+
...input.projectContext.secondary.map((project) => project.projectId),
|
|
510
|
+
]),
|
|
511
|
+
ability_release_present: input.abilityRelease !== undefined,
|
|
512
|
+
ability_release_id: input.abilityRelease?.releaseId,
|
|
513
|
+
ability_root_commit: input.abilityRelease?.rootCommit,
|
|
514
|
+
});
|
|
491
515
|
const launchRuntime = dependencies.launchRuntime ?? launchLegacyRuntime;
|
|
492
516
|
if (dependencies.cancellation?.isRequested())
|
|
493
517
|
throw new RuntimeCancelledError();
|
package/dist/machine-info.js
CHANGED
|
@@ -23,6 +23,7 @@ import { daemonHome } from "./computer-profile.js";
|
|
|
23
23
|
export { executableRuntimes } from "./runtime-capabilities.js";
|
|
24
24
|
const execFileP = promisify(execFile);
|
|
25
25
|
export const PROJECT_WORKSPACES_CAPABILITY = "project_workspaces_v1";
|
|
26
|
+
export const TRAINING_PROJECT_DEPENDENCIES_CAPABILITY = "training_project_dependencies_v1";
|
|
26
27
|
export const NATIVE_PROJECT_WORKSPACE_READINESS = Object.freeze({
|
|
27
28
|
resolverReady: true,
|
|
28
29
|
nativeRuntimeAdaptersReady: true,
|
|
@@ -40,6 +41,7 @@ export const DAEMON_CAPABILITIES = Object.freeze([
|
|
|
40
41
|
"execution_agent_memory_mapping_v1",
|
|
41
42
|
"project_skills_v1",
|
|
42
43
|
PROJECT_WORKSPACES_CAPABILITY,
|
|
44
|
+
TRAINING_PROJECT_DEPENDENCIES_CAPABILITY,
|
|
43
45
|
RUNTIME_HEALTH_PROBE_CAPABILITY,
|
|
44
46
|
"agent_ability_release_v1",
|
|
45
47
|
"agent_ability_workspace_v1",
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { lstat, mkdir, readFile, rm } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { parse } from "yaml";
|
|
6
|
+
import { validateAbilityGitHost } from "./agent-ability/resolver.js";
|
|
7
|
+
import { dslog } from "./slog.js";
|
|
8
|
+
import { PROJECT_BRANCH, PROJECT_BRANCH_FORBIDDEN, PROJECT_MANIFEST_SOURCE_PATH, ProjectDependencyManifestSchema, } from "./project-dependency-manifest.js";
|
|
9
|
+
const runFile = promisify(execFile);
|
|
10
|
+
const PROJECT_MANIFEST_RELATIVE_PATH = PROJECT_MANIFEST_SOURCE_PATH;
|
|
11
|
+
const PROJECT_MANIFEST_MAX_BYTES = 128 * 1024;
|
|
12
|
+
const GIT_ARGS = ["-c", "core.hooksPath=/dev/null"];
|
|
13
|
+
export { ProjectDependencyManifestSchema } from "./project-dependency-manifest.js";
|
|
14
|
+
export class ProjectDependencyError extends Error {
|
|
15
|
+
code;
|
|
16
|
+
detail;
|
|
17
|
+
projectId;
|
|
18
|
+
constructor(code, detail = undefined, projectId) {
|
|
19
|
+
super(code);
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.detail = detail;
|
|
22
|
+
this.projectId = projectId;
|
|
23
|
+
this.name = "ProjectDependencyError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const gitEnv = () => ({
|
|
27
|
+
...process.env,
|
|
28
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
29
|
+
GIT_CONFIG_GLOBAL: process.platform === "win32" ? "NUL" : "/dev/null",
|
|
30
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
31
|
+
});
|
|
32
|
+
const defaultGit = {
|
|
33
|
+
async run(args, cwd) {
|
|
34
|
+
try {
|
|
35
|
+
const result = await runFile("git", [...GIT_ARGS, ...args], {
|
|
36
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
37
|
+
env: gitEnv(),
|
|
38
|
+
timeout: 120_000,
|
|
39
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
40
|
+
});
|
|
41
|
+
return result.stdout.trim();
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
45
|
+
throw new ProjectDependencyError("project_dependency_checkout_failed", detail);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const safeBranchDirectory = (branch) => branch
|
|
50
|
+
.replaceAll("/", "__")
|
|
51
|
+
.replace(/[^A-Za-z0-9._-]/gu, "_")
|
|
52
|
+
.replace(/^[-.]+/u, "_")
|
|
53
|
+
.slice(0, 128) || "default";
|
|
54
|
+
const projectManifestPath = (trainingRoot) => resolve(trainingRoot, PROJECT_MANIFEST_RELATIVE_PATH);
|
|
55
|
+
const isWithin = (root, target) => {
|
|
56
|
+
const resolvedRoot = resolve(root);
|
|
57
|
+
const resolvedTarget = resolve(target);
|
|
58
|
+
return resolvedTarget === resolvedRoot || resolvedTarget.startsWith(`${resolvedRoot}${sep}`);
|
|
59
|
+
};
|
|
60
|
+
async function readProjectManifest(trainingRoot) {
|
|
61
|
+
const path = projectManifestPath(trainingRoot);
|
|
62
|
+
let raw;
|
|
63
|
+
try {
|
|
64
|
+
raw = await readFile(path, "utf8");
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error.code === "ENOENT")
|
|
68
|
+
return null;
|
|
69
|
+
throw new ProjectDependencyError("project_dependency_manifest_invalid", "cannot read project dependency manifest");
|
|
70
|
+
}
|
|
71
|
+
if (Buffer.byteLength(raw, "utf8") > PROJECT_MANIFEST_MAX_BYTES) {
|
|
72
|
+
throw new ProjectDependencyError("project_dependency_manifest_invalid", "project dependency manifest is too large");
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
return ProjectDependencyManifestSchema.parse(parse(raw));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new ProjectDependencyError("project_dependency_manifest_invalid", "project dependency manifest schema is invalid");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function resolveDefaultBranch(git, repo, projectId) {
|
|
82
|
+
let output;
|
|
83
|
+
try {
|
|
84
|
+
output = await git.run(["ls-remote", "--symref", repo, "HEAD"]);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw new ProjectDependencyError("project_dependency_default_branch_unavailable", `cannot resolve remote default branch for ${projectId}`, projectId);
|
|
88
|
+
}
|
|
89
|
+
const branch = output.split(/\r?\n/u)
|
|
90
|
+
.map((line) => line.match(/^ref:\s+refs\/heads\/([^\s]+)\s+HEAD$/u)?.[1])
|
|
91
|
+
.find((value) => value !== undefined);
|
|
92
|
+
if (branch === undefined || !PROJECT_BRANCH.test(branch) || PROJECT_BRANCH_FORBIDDEN.test(branch)) {
|
|
93
|
+
throw new ProjectDependencyError("project_dependency_default_branch_unavailable", `remote default branch is unavailable for ${projectId}`, projectId);
|
|
94
|
+
}
|
|
95
|
+
return branch;
|
|
96
|
+
}
|
|
97
|
+
async function checkoutProject(git, project, branch, target) {
|
|
98
|
+
const targetInfo = await lstat(target).catch(() => null);
|
|
99
|
+
if (targetInfo?.isSymbolicLink() || (targetInfo !== null && !targetInfo.isDirectory())) {
|
|
100
|
+
throw new ProjectDependencyError("project_dependency_checkout_conflict", "checkout target is not a directory", project.id);
|
|
101
|
+
}
|
|
102
|
+
const created = targetInfo === null;
|
|
103
|
+
if (created) {
|
|
104
|
+
await mkdir(dirname(target), { recursive: true });
|
|
105
|
+
try {
|
|
106
|
+
await git.run(["clone", "--single-branch", "--branch", branch, project.repo, target]);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
await rm(target, { recursive: true, force: true }).catch(() => { });
|
|
110
|
+
throw new ProjectDependencyError("project_dependency_checkout_failed", error instanceof Error ? error.message : String(error), project.id);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
let origin;
|
|
115
|
+
try {
|
|
116
|
+
origin = await git.run(["remote", "get-url", "origin"], target);
|
|
117
|
+
if (origin !== project.repo) {
|
|
118
|
+
throw new ProjectDependencyError("project_dependency_checkout_conflict", "checkout origin does not match manifest", project.id);
|
|
119
|
+
}
|
|
120
|
+
const dirty = await git.run(["status", "--porcelain"], target);
|
|
121
|
+
if (dirty.length > 0) {
|
|
122
|
+
throw new ProjectDependencyError("project_dependency_checkout_dirty", "checkout has local changes", project.id);
|
|
123
|
+
}
|
|
124
|
+
await git.run(["fetch", "--no-tags", "origin", branch], target);
|
|
125
|
+
await git.run(["checkout", "--force", "-B", branch, `origin/${branch}`], target);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (error instanceof ProjectDependencyError)
|
|
129
|
+
throw error;
|
|
130
|
+
throw new ProjectDependencyError("project_dependency_checkout_conflict", `cannot reuse checkout for ${project.id}`, project.id);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
if (project.commit !== undefined) {
|
|
135
|
+
await git.run(["checkout", "--force", "--detach", project.commit], target);
|
|
136
|
+
}
|
|
137
|
+
const resolvedCommit = await git.run(["rev-parse", "HEAD"], target);
|
|
138
|
+
if (project.commit !== undefined && resolvedCommit !== project.commit) {
|
|
139
|
+
throw new ProjectDependencyError("project_dependency_commit_mismatch", `project ${project.id} resolved to an unexpected commit`, project.id);
|
|
140
|
+
}
|
|
141
|
+
return { resolvedCommit, created };
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
if (project.commit !== undefined) {
|
|
145
|
+
throw new ProjectDependencyError("project_dependency_commit_mismatch", `project ${project.id} could not be checked out at the requested commit`, project.id);
|
|
146
|
+
}
|
|
147
|
+
throw new ProjectDependencyError("project_dependency_checkout_failed", "checkout commit cannot be resolved", project.id);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const existingAncestorIsSymlink = async (root, target) => {
|
|
151
|
+
const resolvedRoot = resolve(root);
|
|
152
|
+
let current = resolve(target);
|
|
153
|
+
while (current !== resolvedRoot && isWithin(resolvedRoot, current)) {
|
|
154
|
+
const info = await lstat(current).catch(() => null);
|
|
155
|
+
if (info?.isSymbolicLink())
|
|
156
|
+
return true;
|
|
157
|
+
current = dirname(current);
|
|
158
|
+
}
|
|
159
|
+
const rootInfo = await lstat(resolvedRoot).catch(() => null);
|
|
160
|
+
return rootInfo?.isSymbolicLink() === true;
|
|
161
|
+
};
|
|
162
|
+
async function ensureRegistered(registry, project, root) {
|
|
163
|
+
let registrations;
|
|
164
|
+
try {
|
|
165
|
+
registrations = await registry.list();
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
throw new ProjectDependencyError("project_dependency_registration_conflict", `cannot read project registry for ${project.id}`, project.id);
|
|
169
|
+
}
|
|
170
|
+
const existing = registrations.find((candidate) => candidate.projectId === project.id);
|
|
171
|
+
if (existing === undefined) {
|
|
172
|
+
try {
|
|
173
|
+
await registry.add(project.id, root);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
throw new ProjectDependencyError("project_dependency_registration_conflict", `cannot register project ${project.id}`, project.id);
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (resolve(existing.root) === resolve(root))
|
|
181
|
+
return;
|
|
182
|
+
// A manifest branch change intentionally moves the logical project ID to the
|
|
183
|
+
// new checkout. Keep the old registration if the replacement cannot finish.
|
|
184
|
+
try {
|
|
185
|
+
const removed = await registry.remove(project.id);
|
|
186
|
+
if (!removed) {
|
|
187
|
+
throw new Error("project registration disappeared before replacement");
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
await registry.add(project.id, root);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
await registry.add(project.id, existing.root).catch(() => { });
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
throw new ProjectDependencyError("project_dependency_registration_conflict", `cannot move project ${project.id} registration to the prepared checkout`, project.id);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
export function createProjectDependencyProvisioner(options) {
|
|
202
|
+
const git = options.git ?? defaultGit;
|
|
203
|
+
return {
|
|
204
|
+
async prepare(input) {
|
|
205
|
+
const manifest = await readProjectManifest(input.trainingRoot);
|
|
206
|
+
dslog("project_dependencies.manifest", "读取项目依赖清单", {
|
|
207
|
+
training_root: input.trainingRoot,
|
|
208
|
+
manifest_present: manifest !== null,
|
|
209
|
+
requested_project_ids: JSON.stringify(input.projectIds),
|
|
210
|
+
declared_project_ids: JSON.stringify(manifest?.projects.map((project) => project.id) ?? []),
|
|
211
|
+
});
|
|
212
|
+
if (input.projectIds.length === 0)
|
|
213
|
+
return Object.freeze([]);
|
|
214
|
+
if (manifest === null) {
|
|
215
|
+
throw new ProjectDependencyError("project_dependency_manifest_missing", "project dependency manifest is missing from the active training package");
|
|
216
|
+
}
|
|
217
|
+
if (!isWithin(input.trainingRoot, projectManifestPath(input.trainingRoot))) {
|
|
218
|
+
throw new ProjectDependencyError("project_dependency_manifest_invalid", "project manifest path escapes training root");
|
|
219
|
+
}
|
|
220
|
+
const byId = new Map(manifest.projects.map((project) => [project.id, project]));
|
|
221
|
+
const requested = [...new Set(input.projectIds)];
|
|
222
|
+
const results = [];
|
|
223
|
+
for (const projectId of requested) {
|
|
224
|
+
const project = byId.get(projectId);
|
|
225
|
+
if (project === undefined) {
|
|
226
|
+
throw new ProjectDependencyError("project_dependency_not_declared", `project ${projectId} is not declared by the active training package`, projectId);
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
validateAbilityGitHost(project.repo);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
throw new ProjectDependencyError("project_dependency_host_not_allowed", "repository host is not allowed", project.id);
|
|
233
|
+
}
|
|
234
|
+
const branchSource = project.branch === undefined ? "remote-default" : "manifest";
|
|
235
|
+
const branch = project.branch ?? await resolveDefaultBranch(git, project.repo, project.id);
|
|
236
|
+
const root = resolve(input.projectsRoot, project.id, safeBranchDirectory(branch));
|
|
237
|
+
if (!isWithin(input.projectsRoot, root)) {
|
|
238
|
+
throw new ProjectDependencyError("project_dependency_checkout_conflict", "checkout path escapes Projects root", project.id);
|
|
239
|
+
}
|
|
240
|
+
if (await existingAncestorIsSymlink(input.projectsRoot, root)) {
|
|
241
|
+
throw new ProjectDependencyError("project_dependency_checkout_conflict", "checkout path contains a symbolic link", project.id);
|
|
242
|
+
}
|
|
243
|
+
const checkout = await checkoutProject(git, project, branch, root);
|
|
244
|
+
try {
|
|
245
|
+
await ensureRegistered(options.registry, project, root);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (checkout.created)
|
|
249
|
+
await rm(root, { recursive: true, force: true }).catch(() => { });
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
results.push(Object.freeze({
|
|
253
|
+
projectId: project.id,
|
|
254
|
+
repo: project.repo,
|
|
255
|
+
branch,
|
|
256
|
+
branchSource,
|
|
257
|
+
...(project.commit === undefined ? {} : { requestedCommit: project.commit }),
|
|
258
|
+
resolvedCommit: checkout.resolvedCommit,
|
|
259
|
+
checkoutMode: "shared",
|
|
260
|
+
localRoot: root,
|
|
261
|
+
summary: project.summary,
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
return Object.freeze(results);
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
export async function listProjectDependencies(trainingRoot) {
|
|
269
|
+
const manifest = await readProjectManifest(trainingRoot);
|
|
270
|
+
return Object.freeze(manifest?.projects ?? []);
|
|
271
|
+
}
|
|
272
|
+
export { PROJECT_MANIFEST_RELATIVE_PATH };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AbilityRepositoryUrlSchema } from "./agent-ability/types.js";
|
|
3
|
+
import { isProjectId } from "./project-skills/types.js";
|
|
4
|
+
export const PROJECT_MANIFEST_SOURCE_PATH = "workspace/project-dependencies.yaml";
|
|
5
|
+
export const PROJECT_BRANCH = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$/u;
|
|
6
|
+
export const PROJECT_BRANCH_FORBIDDEN = /(?:^|\/)\.\.?(?:\/|$)|[\s\p{Cc}\\]/u;
|
|
7
|
+
export const PROJECT_SUMMARY_MAX_LENGTH = 1_000;
|
|
8
|
+
export const PROJECT_COMMIT = /^[a-f0-9]{40}$/u;
|
|
9
|
+
const ProjectDependencySchema = z.object({
|
|
10
|
+
id: z.string().min(1).max(64),
|
|
11
|
+
repo: AbilityRepositoryUrlSchema,
|
|
12
|
+
branch: z.string().min(1).max(256).optional(),
|
|
13
|
+
commit: z.string().optional(),
|
|
14
|
+
summary: z.string().min(1).max(PROJECT_SUMMARY_MAX_LENGTH),
|
|
15
|
+
}).strict().superRefine((project, ctx) => {
|
|
16
|
+
if (!isProjectId(project.id)) {
|
|
17
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["id"], message: "invalid project id" });
|
|
18
|
+
}
|
|
19
|
+
if (project.branch !== undefined
|
|
20
|
+
&& (!PROJECT_BRANCH.test(project.branch) || PROJECT_BRANCH_FORBIDDEN.test(project.branch))) {
|
|
21
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["branch"], message: "invalid project branch" });
|
|
22
|
+
}
|
|
23
|
+
if (project.commit !== undefined && !PROJECT_COMMIT.test(project.commit)) {
|
|
24
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["commit"], message: "invalid project commit" });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
export const ProjectDependencyManifestSchema = z.object({
|
|
28
|
+
version: z.literal(1),
|
|
29
|
+
projects: z.array(ProjectDependencySchema).min(1).max(32),
|
|
30
|
+
}).strict().superRefine((manifest, ctx) => {
|
|
31
|
+
const ids = manifest.projects.map((project) => project.id);
|
|
32
|
+
if (new Set(ids).size !== ids.length) {
|
|
33
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["projects"], message: "duplicate project id" });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createProjectDependencyProvisioner } from "./project-dependencies.js";
|
|
2
|
+
import { createProjectRegistry } from "./project-skills/registry.js";
|
|
3
|
+
const writableRegistry = (registry) => {
|
|
4
|
+
if (registry === undefined)
|
|
5
|
+
return undefined;
|
|
6
|
+
const candidate = registry;
|
|
7
|
+
return typeof candidate.add === "function" && typeof candidate.remove === "function"
|
|
8
|
+
? registry
|
|
9
|
+
: undefined;
|
|
10
|
+
};
|
|
11
|
+
export function createProjectRegistryWiring(agentsRoot, input = {}) {
|
|
12
|
+
const writableInjectedRegistry = writableRegistry(input.projectWorkspaceRegistry);
|
|
13
|
+
const projectRegistry = input.projectRegistry
|
|
14
|
+
?? writableInjectedRegistry
|
|
15
|
+
?? createProjectRegistry(agentsRoot);
|
|
16
|
+
const projectWorkspaceRegistry = input.projectWorkspaceRegistry ?? projectRegistry;
|
|
17
|
+
if (input.projectDependencies !== undefined && input.projectRegistry === undefined) {
|
|
18
|
+
throw new Error("project_dependency_injection_requires_project_registry");
|
|
19
|
+
}
|
|
20
|
+
if (input.projectWorkspaceRegistry !== undefined && projectWorkspaceRegistry !== projectRegistry) {
|
|
21
|
+
throw new Error("project_registry_injection_requires_writable_registry");
|
|
22
|
+
}
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
projectRegistry,
|
|
25
|
+
projectWorkspaceRegistry,
|
|
26
|
+
projectDependencyProvisioner: input.projectDependencies
|
|
27
|
+
?? createProjectDependencyProvisioner({ registry: projectRegistry }),
|
|
28
|
+
});
|
|
29
|
+
}
|
package/dist/serve.js
CHANGED
|
@@ -35,7 +35,7 @@ import { detectDaemonUpdateEligibility, managedDaemonCapabilities, } from "./dae
|
|
|
35
35
|
import { createDaemonUpdateController } from "./daemon-update-controller.js";
|
|
36
36
|
import { installExactDaemonUpdate, prepareDaemonRestart } from "./daemon-updater.js";
|
|
37
37
|
import { scheduleServiceRestart } from "./computer-service.js";
|
|
38
|
-
import {
|
|
38
|
+
import { createProjectRegistryWiring } from "./project-registry-wiring.js";
|
|
39
39
|
import { createProjectSkillsController, } from "./project-skills/controller.js";
|
|
40
40
|
import { PROJECT_SKILLS_CAPABILITY } from "./project-skills/types.js";
|
|
41
41
|
import { createAgentProjectionCoordinator } from "./project-skills/agent-projection-coordinator.js";
|
|
@@ -138,6 +138,7 @@ export function serve(config, opts = {}) {
|
|
|
138
138
|
}
|
|
139
139
|
catch { /* reconnect */ } } });
|
|
140
140
|
const projectionCoordinator = createAgentProjectionCoordinator();
|
|
141
|
+
const { projectRegistry, projectWorkspaceRegistry, projectDependencyProvisioner } = createProjectRegistryWiring(config.agentsRoot, opts.execution?.dependencies);
|
|
141
142
|
const agentAbilityRuntime = createAgentAbilityRuntime(config.agentsRoot, {
|
|
142
143
|
...opts.agentAbility,
|
|
143
144
|
coordinator: projectionCoordinator,
|
|
@@ -150,7 +151,7 @@ export function serve(config, opts = {}) {
|
|
|
150
151
|
protectedExecutionIds,
|
|
151
152
|
});
|
|
152
153
|
projectSkillsController = opts.projectSkills?.controller ?? createProjectSkillsController({
|
|
153
|
-
registry:
|
|
154
|
+
registry: projectRegistry,
|
|
154
155
|
publish: (frame) => {
|
|
155
156
|
if (!serverCapabilities.has(PROJECT_SKILLS_CAPABILITY))
|
|
156
157
|
return;
|
|
@@ -618,6 +619,8 @@ export function serve(config, opts = {}) {
|
|
|
618
619
|
capabilities: capabilities.executionRunner,
|
|
619
620
|
projectSkills: opts.execution?.dependencies?.projectSkills ?? initializedProjectSkills,
|
|
620
621
|
abilityRelease: opts.execution?.dependencies?.abilityRelease ?? agentAbilityRuntime.materializer,
|
|
622
|
+
projectWorkspaceRegistry,
|
|
623
|
+
projectDependencies: projectDependencyProvisioner,
|
|
621
624
|
...(agentMemory === undefined ? {} : { agentMemory }),
|
|
622
625
|
journal: executionJournal,
|
|
623
626
|
facts: {
|