@nowcrew/daemon 0.5.51 → 0.6.0
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/controller.js +19 -0
- package/dist/agent-ability/materializer.js +327 -0
- package/dist/agent-ability/resolver.js +408 -0
- package/dist/agent-ability/runtime.js +27 -0
- package/dist/agent-ability/types.js +111 -0
- package/dist/control-plane-url.js +16 -0
- package/dist/control-request-types.js +1 -0
- package/dist/execution-protocol.js +20 -0
- package/dist/execution-runner.js +2 -0
- package/dist/local-executor.js +6 -3
- package/dist/machine-info.js +5 -1
- package/dist/remote/claude-bridge.js +558 -0
- package/dist/remote/claude-channel.js +164 -0
- package/dist/remote/codex-client.js +451 -0
- package/dist/remote/codex-runtime.js +77 -0
- package/dist/remote/config.js +135 -0
- package/dist/remote/gateway.js +879 -0
- package/dist/remote/identity.js +39 -0
- package/dist/remote/owner.js +77 -0
- package/dist/remote/protocol.js +211 -0
- package/dist/remote/remote-cli.js +254 -0
- package/dist/remote/runtime-probe.js +182 -0
- package/dist/remote/session-discovery.js +249 -0
- package/dist/remote/wrapper.js +40 -0
- package/dist/remote-web/assets/index-B_6VM_tw.js +94 -0
- package/dist/remote-web/assets/index-L6EiQbJn.css +1 -0
- package/dist/remote-web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/remote-web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/remote-web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/remote-web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/remote-web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/remote-web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/remote-web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/remote-web/icons/nowwork-192.png +0 -0
- package/dist/remote-web/icons/nowwork-512.png +0 -0
- package/dist/remote-web/icons/nowwork.svg +7 -0
- package/dist/remote-web/index.html +20 -0
- package/dist/remote-web/manifest.webmanifest +13 -0
- package/dist/remote-web/sw.js +12 -0
- package/dist/runtime-health.js +158 -0
- package/dist/serve.js +19 -15
- package/package.json +2 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AbilityResolveCommandSchema } from "./types.js";
|
|
2
|
+
import { resolveAbilityRelease } from "./resolver.js";
|
|
3
|
+
export function createAgentAbilityController(deps) {
|
|
4
|
+
const resolver = deps.resolve ?? resolveAbilityRelease;
|
|
5
|
+
return {
|
|
6
|
+
async handle(input) {
|
|
7
|
+
const parsed = AbilityResolveCommandSchema.safeParse(input);
|
|
8
|
+
if (!parsed.success)
|
|
9
|
+
return { ok: false, error: "invalid_ability_command" };
|
|
10
|
+
try {
|
|
11
|
+
return { ok: true, data: await resolver(deps.agentsRoot, parsed.data) };
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
const message = error instanceof Error ? error.message : "ability_resolve_failed";
|
|
15
|
+
return { ok: false, error: /^[a-z0-9_:-]+$/u.test(message) ? message.slice(0, 200) : "ability_resolve_failed" };
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { chmod, cp, lstat, mkdir, readFile, readdir, readlink, rename, rm, symlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { parse } from "yaml";
|
|
5
|
+
import { dslog } from "../slog.js";
|
|
6
|
+
import { abilitySha256, abilityTreeDigest, collectAbilityFiles, safeAbilityName, } from "./resolver.js";
|
|
7
|
+
import { DaemonAbilityLockSchema, DaemonAbilityManifestSchema } from "./types.js";
|
|
8
|
+
const exists = (path) => lstat(path).then(() => true, () => false);
|
|
9
|
+
const repositoryKey = (url) => createHash("sha256").update(url).digest("hex");
|
|
10
|
+
const posixPath = (value) => value.split(sep).join("/");
|
|
11
|
+
const sameSnapshot = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
12
|
+
const readonlyTree = async (root) => {
|
|
13
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const path = join(root, entry.name);
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
await readonlyTree(path);
|
|
18
|
+
await chmod(path, 0o555);
|
|
19
|
+
}
|
|
20
|
+
else if (entry.isFile()) {
|
|
21
|
+
const current = await lstat(path);
|
|
22
|
+
await chmod(path, current.mode & 0o111 ? 0o555 : 0o444);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
await chmod(root, 0o555);
|
|
26
|
+
};
|
|
27
|
+
const writableTree = async (root) => {
|
|
28
|
+
const info = await lstat(root);
|
|
29
|
+
if (info.isSymbolicLink())
|
|
30
|
+
return;
|
|
31
|
+
if (!info.isDirectory()) {
|
|
32
|
+
await chmod(root, 0o600);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await chmod(root, 0o700);
|
|
36
|
+
for (const entry of await readdir(root))
|
|
37
|
+
await writableTree(join(root, entry));
|
|
38
|
+
};
|
|
39
|
+
const removeManagedTree = async (root) => {
|
|
40
|
+
if (!await exists(root))
|
|
41
|
+
return;
|
|
42
|
+
await writableTree(root);
|
|
43
|
+
await rm(root, { recursive: true, force: true });
|
|
44
|
+
};
|
|
45
|
+
const switchDirectory = async (target, staging, backup) => {
|
|
46
|
+
const hadPrevious = await exists(target);
|
|
47
|
+
try {
|
|
48
|
+
if (hadPrevious)
|
|
49
|
+
await rename(target, backup);
|
|
50
|
+
await rename(staging, target);
|
|
51
|
+
if (hadPrevious)
|
|
52
|
+
await rm(backup, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (await exists(target))
|
|
56
|
+
await rm(target, { recursive: true, force: true }).catch(() => { });
|
|
57
|
+
if (hadPrevious && await exists(backup))
|
|
58
|
+
await rename(backup, target).catch(() => { });
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const copyExistingSkills = async (target, staging, managedReleaseRoot) => {
|
|
63
|
+
const names = new Set();
|
|
64
|
+
if (!await exists(target))
|
|
65
|
+
return names;
|
|
66
|
+
for (const entry of await readdir(target, { withFileTypes: true })) {
|
|
67
|
+
const source = join(target, entry.name);
|
|
68
|
+
const destination = join(staging, entry.name);
|
|
69
|
+
const info = await lstat(source);
|
|
70
|
+
if (info.isSymbolicLink()) {
|
|
71
|
+
const link = await readlink(source);
|
|
72
|
+
const resolvedLink = resolve(dirname(source), link);
|
|
73
|
+
if (resolvedLink === managedReleaseRoot || resolvedLink.startsWith(`${managedReleaseRoot}${sep}`))
|
|
74
|
+
continue;
|
|
75
|
+
await symlink(link, destination, "dir");
|
|
76
|
+
}
|
|
77
|
+
else
|
|
78
|
+
await cp(source, destination, { recursive: true, preserveTimestamps: true });
|
|
79
|
+
names.add(entry.name);
|
|
80
|
+
}
|
|
81
|
+
return names;
|
|
82
|
+
};
|
|
83
|
+
const projectNativeSkills = async (agentRoot, releaseRoot, skills) => {
|
|
84
|
+
const nativeSkills = [];
|
|
85
|
+
for (const skill of skills) {
|
|
86
|
+
const source = join(releaseRoot, "skills", safeAbilityName(skill.name));
|
|
87
|
+
if (await exists(join(source, "SKILL.md"))) {
|
|
88
|
+
nativeSkills.push({ name: skill.name, source });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
for (const entry of await readdir(source, { withFileTypes: true })) {
|
|
92
|
+
if (entry.isDirectory() && await exists(join(source, entry.name, "SKILL.md"))) {
|
|
93
|
+
nativeSkills.push({ name: entry.name, source: join(source, entry.name) });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const id = randomUUID();
|
|
98
|
+
const targets = [
|
|
99
|
+
join(agentRoot, ".agents", "skills"),
|
|
100
|
+
join(agentRoot, ".crew", "claude-skills", ".claude", "skills"),
|
|
101
|
+
];
|
|
102
|
+
const managedReleaseRoot = join(agentRoot, ".nowcrew", "ability", "releases");
|
|
103
|
+
for (const target of targets) {
|
|
104
|
+
const staging = join(dirname(target), `.ability-skills-next-${id}`);
|
|
105
|
+
const backup = join(dirname(target), `.ability-skills-previous-${id}`);
|
|
106
|
+
await mkdir(dirname(target), { recursive: true });
|
|
107
|
+
await mkdir(staging, { mode: 0o700 });
|
|
108
|
+
try {
|
|
109
|
+
const names = await copyExistingSkills(target, staging, managedReleaseRoot);
|
|
110
|
+
for (const skill of nativeSkills) {
|
|
111
|
+
if (names.has(skill.name))
|
|
112
|
+
throw new Error(`ability_skill_name_conflict:${skill.name}`);
|
|
113
|
+
await symlink(skill.source, join(staging, skill.name), "dir");
|
|
114
|
+
}
|
|
115
|
+
await switchDirectory(target, staging, backup);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
await rm(staging, { recursive: true, force: true });
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const verifyCache = async (agentsRoot, ability) => {
|
|
124
|
+
const root = join(agentsRoot, ".crew", "ability-cache", "artifacts", repositoryKey(ability.repositoryUrl), ability.rootCommit);
|
|
125
|
+
if (!await exists(root))
|
|
126
|
+
throw new Error("ability_artifact_cache_missing");
|
|
127
|
+
const rawManifest = await readFile(join(root, "agent.yaml"));
|
|
128
|
+
const manifest = DaemonAbilityManifestSchema.parse(parse(rawManifest.toString("utf8")));
|
|
129
|
+
const lock = DaemonAbilityLockSchema.parse(JSON.parse(await readFile(join(root, "agent.lock.json"), "utf8")));
|
|
130
|
+
if (lock.manifestDigest !== abilitySha256(rawManifest))
|
|
131
|
+
throw new Error("ability_manifest_lock_drift");
|
|
132
|
+
const files = await collectAbilityFiles(root);
|
|
133
|
+
for (const asset of lock.assets) {
|
|
134
|
+
if (abilityTreeDigest(asset.path, files) !== asset.contentDigest) {
|
|
135
|
+
throw new Error(`ability_asset_lock_drift:${asset.path}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const externalPaths = new Map();
|
|
139
|
+
const external = lock.dependencies.map((dependency) => {
|
|
140
|
+
const dependencyRoot = join(root, ".nowcrew-external", safeAbilityName(dependency.name));
|
|
141
|
+
const dependencyFiles = files.filter((file) => file.path.startsWith(`${posixPath(relative(root, dependencyRoot))}/`))
|
|
142
|
+
.map((file) => ({
|
|
143
|
+
path: file.path.slice(posixPath(relative(root, dependencyRoot)).length + 1),
|
|
144
|
+
content: file.content,
|
|
145
|
+
}));
|
|
146
|
+
if (abilityTreeDigest(dependency.path, dependencyFiles) !== dependency.contentDigest) {
|
|
147
|
+
throw new Error(`ability_dependency_lock_drift:${dependency.name}`);
|
|
148
|
+
}
|
|
149
|
+
const path = posixPath(join(".nowcrew-external", safeAbilityName(dependency.name), dependency.path));
|
|
150
|
+
externalPaths.set(dependency.name, path);
|
|
151
|
+
return [dependency.name, path];
|
|
152
|
+
}).sort(([left], [right]) => left.localeCompare(right));
|
|
153
|
+
const artifactDigest = abilitySha256(JSON.stringify({
|
|
154
|
+
repositoryUrl: ability.repositoryUrl,
|
|
155
|
+
rootCommit: ability.rootCommit,
|
|
156
|
+
treeDigest: ability.treeDigest,
|
|
157
|
+
lock,
|
|
158
|
+
external,
|
|
159
|
+
}));
|
|
160
|
+
if (artifactDigest !== ability.artifactDigest)
|
|
161
|
+
throw new Error("ability_artifact_digest_mismatch");
|
|
162
|
+
if (manifest.spec.instructions.path !== ability.instructions.path
|
|
163
|
+
|| (await readFile(join(root, ability.instructions.path), "utf8")) !== ability.instructions.content) {
|
|
164
|
+
throw new Error("ability_instruction_snapshot_mismatch");
|
|
165
|
+
}
|
|
166
|
+
const expectedSkills = manifest.spec.skills.map((skill) => ({
|
|
167
|
+
name: skill.name,
|
|
168
|
+
path: skill.source.type === "local" ? skill.source.path : externalPaths.get(skill.name),
|
|
169
|
+
mode: skill.mode,
|
|
170
|
+
}));
|
|
171
|
+
if (expectedSkills.some((skill) => skill.path === undefined)
|
|
172
|
+
|| !sameSnapshot(ability.skills, expectedSkills)) {
|
|
173
|
+
throw new Error("ability_skill_snapshot_mismatch");
|
|
174
|
+
}
|
|
175
|
+
if (new Set(ability.skills.map((skill) => safeAbilityName(skill.name))).size !== ability.skills.length) {
|
|
176
|
+
throw new Error("ability_skill_projection_collision");
|
|
177
|
+
}
|
|
178
|
+
if (!sameSnapshot(ability.workspace, manifest.spec.workspace.assets)) {
|
|
179
|
+
throw new Error("ability_workspace_snapshot_mismatch");
|
|
180
|
+
}
|
|
181
|
+
if (!sameSnapshot(ability.evalProvenance.cases, manifest.spec.evals.cases)) {
|
|
182
|
+
throw new Error("ability_eval_snapshot_mismatch");
|
|
183
|
+
}
|
|
184
|
+
const releaseMemory = ability.memory.filter((memory) => memory.layer === "release-policy" || memory.layer === "release-seed");
|
|
185
|
+
for (const memory of releaseMemory) {
|
|
186
|
+
const mounts = memory.layer === "release-policy" ? manifest.spec.memory.policy : manifest.spec.memory.seeds;
|
|
187
|
+
if (!mounts.some((mount) => memory.path === mount.path || memory.path.startsWith(`${mount.path}/`))
|
|
188
|
+
|| await readFile(join(root, memory.path), "utf8") !== memory.content) {
|
|
189
|
+
throw new Error("ability_memory_snapshot_mismatch");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (ability.memory.reduce((bytes, memory) => bytes + Buffer.byteLength(memory.content, "utf8"), 0) > 64 * 1024) {
|
|
193
|
+
throw new Error("ability_memory_snapshot_size_exceeded");
|
|
194
|
+
}
|
|
195
|
+
return root;
|
|
196
|
+
};
|
|
197
|
+
const materializeRelease = async (agentsRoot, handle, ability) => {
|
|
198
|
+
const sourceRoot = await verifyCache(agentsRoot, ability);
|
|
199
|
+
const agentRoot = join(agentsRoot, handle);
|
|
200
|
+
const abilityRoot = join(agentRoot, ".nowcrew", "ability");
|
|
201
|
+
const releaseRoot = join(abilityRoot, "releases", ability.releaseId);
|
|
202
|
+
const catalogPath = join(releaseRoot, "catalog.json");
|
|
203
|
+
if (await exists(catalogPath)) {
|
|
204
|
+
const catalog = JSON.parse(await readFile(catalogPath, "utf8"));
|
|
205
|
+
if (catalog.artifactDigest === ability.artifactDigest)
|
|
206
|
+
return releaseRoot;
|
|
207
|
+
await removeManagedTree(releaseRoot);
|
|
208
|
+
}
|
|
209
|
+
const staging = join(abilityRoot, "releases", `.next-${ability.releaseId}-${randomUUID()}`);
|
|
210
|
+
await mkdir(staging, { recursive: true, mode: 0o700 });
|
|
211
|
+
try {
|
|
212
|
+
const instructionsTarget = join(staging, "instructions", ability.instructions.path);
|
|
213
|
+
await mkdir(dirname(instructionsTarget), { recursive: true });
|
|
214
|
+
await cp(join(sourceRoot, ability.instructions.path), instructionsTarget, { preserveTimestamps: true });
|
|
215
|
+
for (const skill of ability.skills) {
|
|
216
|
+
const target = join(staging, "skills", safeAbilityName(skill.name));
|
|
217
|
+
await mkdir(dirname(target), { recursive: true });
|
|
218
|
+
await cp(join(sourceRoot, skill.path), target, { recursive: true, preserveTimestamps: true });
|
|
219
|
+
}
|
|
220
|
+
for (const memory of ability.memory) {
|
|
221
|
+
const target = join(staging, "memory", safeAbilityName(memory.layer), `${safeAbilityName(memory.id)}.md`);
|
|
222
|
+
await mkdir(dirname(target), { recursive: true });
|
|
223
|
+
await writeFile(target, memory.content, { encoding: "utf8", mode: 0o600 });
|
|
224
|
+
}
|
|
225
|
+
for (const asset of ability.workspace) {
|
|
226
|
+
const relativeTarget = asset.target.replace(/^\.nowcrew\/ability\//u, "");
|
|
227
|
+
const target = join(staging, "workspace", relativeTarget);
|
|
228
|
+
await mkdir(dirname(target), { recursive: true });
|
|
229
|
+
await cp(join(sourceRoot, asset.source), target, { recursive: true, preserveTimestamps: true });
|
|
230
|
+
}
|
|
231
|
+
await writeFile(join(staging, "catalog.json"), JSON.stringify({
|
|
232
|
+
releaseId: ability.releaseId,
|
|
233
|
+
rootCommit: ability.rootCommit,
|
|
234
|
+
artifactDigest: ability.artifactDigest,
|
|
235
|
+
instructions: ability.instructions.path,
|
|
236
|
+
skills: ability.skills.map((skill) => skill.name),
|
|
237
|
+
memory: ability.memory.map((memory) => ({ id: memory.id, layer: memory.layer })),
|
|
238
|
+
workspace: ability.workspace,
|
|
239
|
+
evalProvenance: ability.evalProvenance,
|
|
240
|
+
}, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
241
|
+
await readonlyTree(staging);
|
|
242
|
+
await rename(staging, releaseRoot);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
await removeManagedTree(staging).catch(() => { });
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
return releaseRoot;
|
|
249
|
+
};
|
|
250
|
+
const activatePointerAndWorkspace = async (agentsRoot, handle, ability, releaseRoot) => {
|
|
251
|
+
const agentRoot = join(agentsRoot, handle);
|
|
252
|
+
const abilityRoot = join(agentRoot, ".nowcrew", "ability");
|
|
253
|
+
const next = join(abilityRoot, `.current-next-${randomUUID()}`);
|
|
254
|
+
const previous = join(abilityRoot, `.current-previous-${randomUUID()}`);
|
|
255
|
+
await mkdir(abilityRoot, { recursive: true });
|
|
256
|
+
await symlink(releaseRoot, next, "dir");
|
|
257
|
+
const current = join(abilityRoot, "current");
|
|
258
|
+
const hadCurrent = await exists(current);
|
|
259
|
+
if (hadCurrent)
|
|
260
|
+
await rename(current, previous);
|
|
261
|
+
try {
|
|
262
|
+
await rename(next, current);
|
|
263
|
+
if (hadCurrent)
|
|
264
|
+
await rm(previous, { recursive: true, force: true });
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
if (hadCurrent)
|
|
268
|
+
await rename(previous, current).catch(() => { });
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
for (const asset of ability.workspace) {
|
|
272
|
+
const relativeTarget = asset.target.replace(/^\.nowcrew\/ability\//u, "");
|
|
273
|
+
const source = join(current, "workspace", relativeTarget);
|
|
274
|
+
const target = join(agentRoot, ...asset.target.split("/"));
|
|
275
|
+
await mkdir(dirname(target), { recursive: true });
|
|
276
|
+
if (asset.mode === "managed-copy") {
|
|
277
|
+
if (!await exists(target))
|
|
278
|
+
await cp(source, target, { recursive: true, preserveTimestamps: true });
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const staging = `${target}.next-${randomUUID()}`;
|
|
282
|
+
await symlink(source, staging, "dir");
|
|
283
|
+
if (await exists(target))
|
|
284
|
+
await rm(target, { recursive: true, force: true });
|
|
285
|
+
await rename(staging, target);
|
|
286
|
+
}
|
|
287
|
+
await projectNativeSkills(agentRoot, releaseRoot, ability.skills);
|
|
288
|
+
await writeFile(join(agentRoot, ".nowwork-root"), "", { encoding: "utf8", mode: 0o600 });
|
|
289
|
+
};
|
|
290
|
+
export function createAgentAbilityMaterializer(expectedAgentsRoot) {
|
|
291
|
+
const tails = new Map();
|
|
292
|
+
return {
|
|
293
|
+
async prepareAndLaunch(agentsRoot, handle, ability, launch) {
|
|
294
|
+
if (agentsRoot !== expectedAgentsRoot)
|
|
295
|
+
throw new Error("ability_agents_root_mismatch");
|
|
296
|
+
const previous = tails.get(handle) ?? Promise.resolve();
|
|
297
|
+
let release;
|
|
298
|
+
const turn = new Promise((resolveTurn) => { release = resolveTurn; });
|
|
299
|
+
const tail = previous.then(() => turn);
|
|
300
|
+
tails.set(handle, tail);
|
|
301
|
+
await previous;
|
|
302
|
+
const started = Date.now();
|
|
303
|
+
try {
|
|
304
|
+
const releaseRoot = await materializeRelease(agentsRoot, handle, ability);
|
|
305
|
+
await activatePointerAndWorkspace(agentsRoot, handle, ability, releaseRoot);
|
|
306
|
+
dslog("ability.execution.materialized", "Agent ability release materialized", {
|
|
307
|
+
level: "INFO", agent_handle: handle, release_id: ability.releaseId,
|
|
308
|
+
root_commit: ability.rootCommit, artifact_digest: ability.artifactDigest,
|
|
309
|
+
duration_ms: Date.now() - started,
|
|
310
|
+
});
|
|
311
|
+
return await launch();
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
dslog("ability.execution.rejected", "Agent ability release materialization failed", {
|
|
315
|
+
level: "ERROR", agent_handle: handle, release_id: ability.releaseId,
|
|
316
|
+
root_commit: ability.rootCommit, error_code: error instanceof Error ? error.message.slice(0, 120) : "ability_materialization_failed",
|
|
317
|
+
});
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
release();
|
|
322
|
+
if (tails.get(handle) === tail)
|
|
323
|
+
tails.delete(handle);
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
}
|