@indigoai-us/hq-cli 5.126.0 → 5.128.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/CHANGELOG.md +218 -0
- package/assets/lanes-hooks/hq-lanes-drain.json +15 -0
- package/assets/lanes-hooks/hq-lanes-drain.mjs +254 -0
- package/assets/lanes-hooks/hq-lanes-drain.sh +21 -0
- package/assets/lanes-reconcile/hq-lanes-reconcile.service +11 -0
- package/assets/lanes-reconcile/hq-lanes-reconcile.timer +12 -0
- package/dist/command-catalog.generated.d.ts +245 -11
- package/dist/command-catalog.generated.js +323 -11
- package/dist/command-registration-plan.d.ts +1 -0
- package/dist/command-registration-plan.js +11 -2
- package/dist/commands/agents.d.ts +12 -0
- package/dist/commands/agents.js +46 -2
- package/dist/commands/lanes.d.ts +55 -16
- package/dist/commands/lanes.js +1187 -250
- package/dist/commands/mesh.js +254 -19
- package/dist/lib/core-utils/sentry-report.d.ts +2 -0
- package/dist/lib/core-utils/sentry-report.js +47 -7
- package/dist/lib/lanes/accounts.d.ts +67 -7
- package/dist/lib/lanes/accounts.js +233 -43
- package/dist/lib/lanes/compaction.d.ts +96 -0
- package/dist/lib/lanes/compaction.js +197 -0
- package/dist/lib/lanes/config.d.ts +15 -0
- package/dist/lib/lanes/config.js +65 -8
- package/dist/lib/lanes/delivery.d.ts +151 -0
- package/dist/lib/lanes/delivery.js +354 -0
- package/dist/lib/lanes/dropbox.d.ts +96 -0
- package/dist/lib/lanes/dropbox.js +408 -0
- package/dist/lib/lanes/effort.d.ts +50 -0
- package/dist/lib/lanes/effort.js +109 -0
- package/dist/lib/lanes/errors.d.ts +2 -2
- package/dist/lib/lanes/hooks-install.d.ts +81 -0
- package/dist/lib/lanes/hooks-install.js +326 -0
- package/dist/lib/lanes/hq-root.d.ts +19 -0
- package/dist/lib/lanes/hq-root.js +28 -0
- package/dist/lib/lanes/index.d.ts +35 -5
- package/dist/lib/lanes/index.js +19 -4
- package/dist/lib/lanes/liveness.d.ts +7 -1
- package/dist/lib/lanes/liveness.js +64 -5
- package/dist/lib/lanes/machine-identity.d.ts +34 -0
- package/dist/lib/lanes/machine-identity.js +92 -0
- package/dist/lib/lanes/mesh-events.d.ts +109 -0
- package/dist/lib/lanes/mesh-events.js +275 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +65 -0
- package/dist/lib/lanes/outpost-dispatch.js +90 -0
- package/dist/lib/lanes/questions.d.ts +75 -0
- package/dist/lib/lanes/questions.js +243 -0
- package/dist/lib/lanes/quota.d.ts +95 -0
- package/dist/lib/lanes/quota.js +320 -0
- package/dist/lib/lanes/reconcile-install.d.ts +69 -0
- package/dist/lib/lanes/reconcile-install.js +221 -0
- package/dist/lib/lanes/reconcile.d.ts +102 -0
- package/dist/lib/lanes/reconcile.js +985 -0
- package/dist/lib/lanes/registry.d.ts +18 -5
- package/dist/lib/lanes/registry.js +293 -19
- package/dist/lib/lanes/spawn.d.ts +274 -0
- package/dist/lib/lanes/spawn.js +1363 -0
- package/dist/lib/lanes/store.d.ts +7 -3
- package/dist/lib/lanes/store.js +150 -32
- package/dist/lib/lanes/types.d.ts +346 -4
- package/dist/lib/lanes/types.js +138 -5
- package/dist/lib/lanes/watcher-main.d.ts +9 -0
- package/dist/lib/lanes/watcher-main.js +22 -0
- package/dist/lib/lanes/watcher.d.ts +216 -0
- package/dist/lib/lanes/watcher.js +859 -0
- package/dist/lib/mesh/api.d.ts +71 -7
- package/dist/lib/mesh/api.js +167 -28
- package/dist/lib/mesh/live/backfill-held.d.ts +18 -7
- package/dist/lib/mesh/live/backfill-held.js +66 -21
- package/dist/lib/mesh/live/daemon/run.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/run.js +111 -54
- package/dist/lib/mesh/live/daemon/state.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/transcript-watch.js +31 -8
- package/dist/lib/scan-packages/index.d.ts +2 -0
- package/dist/lib/scan-packages/index.js +1 -1
- package/dist/lib/work-context/state.d.ts +12 -0
- package/dist/lib/work-context/state.js +131 -3
- package/package.json +3 -1
|
@@ -5,9 +5,22 @@
|
|
|
5
5
|
* optional `company:` field. A company path with a missing or contradictory
|
|
6
6
|
* `company:` is `worker_tenancy_unresolved`, never a global worker.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* same-named entry in another tree.
|
|
8
|
+
* Personal workers are indexed by the registry generator when they have a
|
|
9
|
+
* worker.yaml. A personal directory that exists without one fails by name
|
|
10
|
+
* and does not fall through to a same-named entry in another tree.
|
|
11
|
+
*
|
|
12
|
+
* Package workers are not a fourth generator scan root. `hq core
|
|
13
|
+
* scan-packages` mounts a declared `contributes.workers` entry into
|
|
14
|
+
* `core/workers/public/<item>` (a symlink; origin/main writes that link
|
|
15
|
+
* relative to the host directory, this branch still writes it absolute).
|
|
16
|
+
* The generator then indexes the mount, and `packAttribution` keeps
|
|
17
|
+
* recognising it by realpath. Scanning `core/packages` as well would
|
|
18
|
+
* double-register that mount. A pack that declares a worker whose symlink
|
|
19
|
+
* is missing fails with `package_workers_unmounted` instead of resolving
|
|
20
|
+
* some other tree's copy. Tenancy for a mount follows the package: global
|
|
21
|
+
* when package.yaml has no company and access is public (or omitted),
|
|
22
|
+
* company-owned when package.yaml names `company`, and
|
|
23
|
+
* `worker_tenancy_unresolved` when that cannot be determined.
|
|
11
24
|
*/
|
|
12
25
|
import * as fs from "node:fs";
|
|
13
26
|
export declare const WORKER_REGISTRY_REL: string;
|
|
@@ -72,6 +85,8 @@ export interface ResolvedLaneWorker {
|
|
|
72
85
|
defaultTimeout: unknown;
|
|
73
86
|
approvalRequired: boolean;
|
|
74
87
|
humanCheckpoints: string[];
|
|
88
|
+
/** Set when the worker's realpath lives in a package. */
|
|
89
|
+
pack?: string;
|
|
75
90
|
}
|
|
76
91
|
export interface RegistryDeps {
|
|
77
92
|
existsSync: (p: string) => boolean;
|
|
@@ -90,8 +105,6 @@ export declare function tenancyFromRegistryPath(entry: RegistryWorkerEntry): Wor
|
|
|
90
105
|
kind: "unresolved";
|
|
91
106
|
pathSlug: string;
|
|
92
107
|
field?: string;
|
|
93
|
-
} | {
|
|
94
|
-
kind: "personal";
|
|
95
108
|
};
|
|
96
109
|
/**
|
|
97
110
|
* Resolve context.base against HQ root, then core/, then the worker directory.
|
|
@@ -5,13 +5,28 @@
|
|
|
5
5
|
* optional `company:` field. A company path with a missing or contradictory
|
|
6
6
|
* `company:` is `worker_tenancy_unresolved`, never a global worker.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* same-named entry in another tree.
|
|
8
|
+
* Personal workers are indexed by the registry generator when they have a
|
|
9
|
+
* worker.yaml. A personal directory that exists without one fails by name
|
|
10
|
+
* and does not fall through to a same-named entry in another tree.
|
|
11
|
+
*
|
|
12
|
+
* Package workers are not a fourth generator scan root. `hq core
|
|
13
|
+
* scan-packages` mounts a declared `contributes.workers` entry into
|
|
14
|
+
* `core/workers/public/<item>` (a symlink; origin/main writes that link
|
|
15
|
+
* relative to the host directory, this branch still writes it absolute).
|
|
16
|
+
* The generator then indexes the mount, and `packAttribution` keeps
|
|
17
|
+
* recognising it by realpath. Scanning `core/packages` as well would
|
|
18
|
+
* double-register that mount. A pack that declares a worker whose symlink
|
|
19
|
+
* is missing fails with `package_workers_unmounted` instead of resolving
|
|
20
|
+
* some other tree's copy. Tenancy for a mount follows the package: global
|
|
21
|
+
* when package.yaml has no company and access is public (or omitted),
|
|
22
|
+
* company-owned when package.yaml names `company`, and
|
|
23
|
+
* `worker_tenancy_unresolved` when that cannot be determined.
|
|
11
24
|
*/
|
|
12
25
|
import * as fs from "node:fs";
|
|
13
26
|
import * as path from "node:path";
|
|
14
27
|
import { parseUserYaml } from "../../utils/user-yaml-error.js";
|
|
28
|
+
import { readContributes } from "../scan-packages/index.js";
|
|
29
|
+
import { contributionLinks, linkStatus, } from "../../utils/pack-contributions.js";
|
|
15
30
|
import { WorkerResolutionError } from "./errors.js";
|
|
16
31
|
export const WORKER_REGISTRY_REL = path.join("core", "workers", "registry.yaml");
|
|
17
32
|
function defaultRegistryDeps() {
|
|
@@ -107,8 +122,10 @@ const CORE_PATH = /^(?:core\/workers\/)/;
|
|
|
107
122
|
*/
|
|
108
123
|
export function tenancyFromRegistryPath(entry) {
|
|
109
124
|
const normalized = entry.path.replaceAll("\\", "/");
|
|
125
|
+
// personal/workers is the operator's tree, not a tenant. Path still wins
|
|
126
|
+
// over a company: field, which is ignored here.
|
|
110
127
|
if (PERSONAL_PATH.test(normalized)) {
|
|
111
|
-
return { kind: "
|
|
128
|
+
return { kind: "global" };
|
|
112
129
|
}
|
|
113
130
|
const companyMatch = COMPANY_PATH.exec(normalized);
|
|
114
131
|
if (companyMatch) {
|
|
@@ -146,7 +163,7 @@ function loadWorkerYaml(hqRoot, relativeDir, deps) {
|
|
|
146
163
|
* Entries that resolve nowhere are dropped, not passed on.
|
|
147
164
|
*/
|
|
148
165
|
export function resolveContextBasePaths(hqRoot, workerDir, bases, deps = defaultRegistryDeps()) {
|
|
149
|
-
if (!bases || bases.length === 0)
|
|
166
|
+
if (!Array.isArray(bases) || bases.length === 0)
|
|
150
167
|
return [];
|
|
151
168
|
const resolved = [];
|
|
152
169
|
for (const entry of bases) {
|
|
@@ -164,7 +181,7 @@ export function resolveContextBasePaths(hqRoot, workerDir, bases, deps = default
|
|
|
164
181
|
return resolved;
|
|
165
182
|
}
|
|
166
183
|
export function resolveSkillFiles(workerDir, skills, deps = defaultRegistryDeps()) {
|
|
167
|
-
if (!skills)
|
|
184
|
+
if (!Array.isArray(skills) || skills.length === 0)
|
|
168
185
|
return [];
|
|
169
186
|
const files = [];
|
|
170
187
|
for (const skill of skills) {
|
|
@@ -185,28 +202,287 @@ function humanCheckpointsOf(yaml) {
|
|
|
185
202
|
return [];
|
|
186
203
|
return raw.filter((item) => typeof item === "string");
|
|
187
204
|
}
|
|
205
|
+
function normalizeRel(value) {
|
|
206
|
+
const norm = value.replaceAll("\\", "/").replace(/\/+$/, "");
|
|
207
|
+
return norm.length > 0 ? `${norm}/` : norm;
|
|
208
|
+
}
|
|
209
|
+
function realpathOf(hqRoot, relativeDir) {
|
|
210
|
+
const abs = path.join(hqRoot, relativeDir.replace(/\/+$/, ""));
|
|
211
|
+
try {
|
|
212
|
+
return fs.realpathSync(abs);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return abs;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function workerIdInYaml(filePath, deps) {
|
|
219
|
+
if (!deps.existsSync(filePath))
|
|
220
|
+
return null;
|
|
221
|
+
let doc;
|
|
222
|
+
try {
|
|
223
|
+
doc = parseUserYaml(deps.readFileSync(filePath, "utf8"), filePath);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
if (!isPlainObject(doc) || !isPlainObject(doc.worker))
|
|
229
|
+
return null;
|
|
230
|
+
const worker = doc.worker;
|
|
231
|
+
const id = typeof worker.id === "string" ? worker.id.trim() : "";
|
|
232
|
+
if (!id)
|
|
233
|
+
return null;
|
|
234
|
+
return {
|
|
235
|
+
id,
|
|
236
|
+
company: typeof worker.company === "string" ? worker.company : undefined,
|
|
237
|
+
status: typeof worker.status === "string" ? worker.status : undefined,
|
|
238
|
+
type: typeof worker.type === "string" ? worker.type : undefined,
|
|
239
|
+
description: typeof worker.description === "string" ? worker.description : undefined,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* A personal directory with files but no worker.yaml claims the id. An
|
|
244
|
+
* empty directory is a leftover and does not.
|
|
245
|
+
*/
|
|
246
|
+
function personalDirClaim(hqRoot, workerId, deps) {
|
|
247
|
+
const dir = personalWorkerDir(hqRoot, workerId);
|
|
248
|
+
let st;
|
|
249
|
+
try {
|
|
250
|
+
st = deps.statSync(dir);
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
const code = err.code;
|
|
254
|
+
if (code === "ENOENT")
|
|
255
|
+
return "none";
|
|
256
|
+
throw err;
|
|
257
|
+
}
|
|
258
|
+
if (!st.isDirectory())
|
|
259
|
+
return "none";
|
|
260
|
+
const yamlPath = path.join(dir, "worker.yaml");
|
|
261
|
+
try {
|
|
262
|
+
if (deps.statSync(yamlPath).isFile())
|
|
263
|
+
return "yaml";
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
const code = err.code;
|
|
267
|
+
if (code !== "ENOENT")
|
|
268
|
+
throw err;
|
|
269
|
+
}
|
|
270
|
+
let names;
|
|
271
|
+
try {
|
|
272
|
+
names = fs.readdirSync(dir);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return "none";
|
|
276
|
+
}
|
|
277
|
+
const rest = names.filter((name) => name !== "worker.yaml");
|
|
278
|
+
return rest.length > 0 ? "missing_yaml" : "none";
|
|
279
|
+
}
|
|
280
|
+
function packNameFromRealpath(hqRoot, real) {
|
|
281
|
+
const rel = path.relative(hqRoot, real).replaceAll("\\", "/");
|
|
282
|
+
const match = /^core\/packages\/([^/]+)\//.exec(rel);
|
|
283
|
+
return match?.[1] ?? null;
|
|
284
|
+
}
|
|
285
|
+
function declaredPackWorkers(hqRoot, deps) {
|
|
286
|
+
const packagesDir = path.join(hqRoot, "core", "packages");
|
|
287
|
+
if (!deps.existsSync(packagesDir))
|
|
288
|
+
return [];
|
|
289
|
+
let names;
|
|
290
|
+
try {
|
|
291
|
+
names = fs.readdirSync(packagesDir);
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
296
|
+
const found = [];
|
|
297
|
+
for (const pack of names) {
|
|
298
|
+
if (pack.startsWith("."))
|
|
299
|
+
continue;
|
|
300
|
+
const packDir = path.join(packagesDir, pack);
|
|
301
|
+
const manifest = path.join(packDir, "package.yaml");
|
|
302
|
+
if (!deps.existsSync(manifest))
|
|
303
|
+
continue;
|
|
304
|
+
let contributes;
|
|
305
|
+
try {
|
|
306
|
+
contributes = readContributes(manifest);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
for (const link of contributionLinks(hqRoot, packDir, contributes)) {
|
|
312
|
+
if (link.key !== "workers")
|
|
313
|
+
continue;
|
|
314
|
+
const fields = workerIdInYaml(path.join(link.src, "worker.yaml"), deps);
|
|
315
|
+
found.push({ pack, item: link.item, link, workerId: fields?.id ?? null });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return found;
|
|
319
|
+
}
|
|
320
|
+
function declarationMatches(row, workerId) {
|
|
321
|
+
return row.workerId === workerId || row.item === workerId;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* A package is global unless it declares company ownership. `company: <slug>`
|
|
325
|
+
* in package.yaml is that declaration. No company and access public (or
|
|
326
|
+
* access omitted, which is how the shipped public packs read) is global.
|
|
327
|
+
* A missing manifest, a non-string company, a private access with no slug,
|
|
328
|
+
* or a worker.yaml company that disagrees with the package is unresolved.
|
|
329
|
+
*/
|
|
330
|
+
function tenancyFromPackage(hqRoot, pack, workerCompany, deps) {
|
|
331
|
+
const manifest = path.join(hqRoot, "core", "packages", pack, "package.yaml");
|
|
332
|
+
if (!deps.existsSync(manifest)) {
|
|
333
|
+
return { kind: "unresolved", pathSlug: "", field: undefined };
|
|
334
|
+
}
|
|
335
|
+
let doc;
|
|
336
|
+
try {
|
|
337
|
+
doc = parseUserYaml(deps.readFileSync(manifest, "utf8"), manifest);
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
return { kind: "unresolved", pathSlug: "", field: undefined };
|
|
341
|
+
}
|
|
342
|
+
if (!isPlainObject(doc)) {
|
|
343
|
+
return { kind: "unresolved", pathSlug: "", field: undefined };
|
|
344
|
+
}
|
|
345
|
+
const access = typeof doc.access === "string" ? doc.access.trim() : "";
|
|
346
|
+
if (doc.company !== undefined) {
|
|
347
|
+
if (typeof doc.company !== "string" || doc.company.trim().length === 0) {
|
|
348
|
+
return {
|
|
349
|
+
kind: "unresolved",
|
|
350
|
+
pathSlug: "",
|
|
351
|
+
field: typeof doc.company === "string" ? doc.company : undefined,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
const slug = doc.company.trim();
|
|
355
|
+
const field = workerCompany?.trim() ?? "";
|
|
356
|
+
if (field && field !== slug) {
|
|
357
|
+
return { kind: "unresolved", pathSlug: slug, field };
|
|
358
|
+
}
|
|
359
|
+
return { kind: "company", slug };
|
|
360
|
+
}
|
|
361
|
+
if (access === "" || access === "public")
|
|
362
|
+
return { kind: "global" };
|
|
363
|
+
return { kind: "unresolved", pathSlug: "", field: access };
|
|
364
|
+
}
|
|
365
|
+
function readRegistryOrEmpty(hqRoot, deps) {
|
|
366
|
+
if (!deps.existsSync(registryPath(hqRoot)))
|
|
367
|
+
return [];
|
|
368
|
+
return readWorkerRegistryFile(hqRoot, deps);
|
|
369
|
+
}
|
|
188
370
|
export function resolveLaneWorker(hqRoot, workerId, laneCompany, deps = defaultRegistryDeps()) {
|
|
189
|
-
|
|
190
|
-
|
|
371
|
+
const personalRel = path.posix.join("personal", "workers", workerId);
|
|
372
|
+
const personalClaim = personalDirClaim(hqRoot, workerId, deps);
|
|
373
|
+
if (personalClaim === "missing_yaml") {
|
|
374
|
+
throw new WorkerResolutionError("worker_not_found", `Worker ${JSON.stringify(workerId)} is at ${personalRel}/ but worker.yaml is missing`, { worker: workerId, path: `${personalRel}/`, missing: "worker.yaml" });
|
|
375
|
+
}
|
|
376
|
+
const entries = readRegistryOrEmpty(hqRoot, deps);
|
|
377
|
+
const candidates = [];
|
|
378
|
+
for (const entry of entries) {
|
|
379
|
+
if (entry.id !== workerId)
|
|
380
|
+
continue;
|
|
381
|
+
const rel = normalizeRel(entry.path);
|
|
382
|
+
candidates.push({
|
|
383
|
+
id: entry.id,
|
|
384
|
+
path: rel,
|
|
385
|
+
type: entry.type,
|
|
386
|
+
status: entry.status && entry.status.length > 0 ? entry.status : "active",
|
|
387
|
+
visibility: entry.visibility,
|
|
388
|
+
description: entry.description,
|
|
389
|
+
company: entry.company,
|
|
390
|
+
realpath: realpathOf(hqRoot, rel),
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
if (personalClaim === "yaml") {
|
|
394
|
+
const rel = normalizeRel(personalRel);
|
|
395
|
+
const real = realpathOf(hqRoot, rel);
|
|
396
|
+
if (!candidates.some((item) => item.realpath === real)) {
|
|
397
|
+
const fields = workerIdInYaml(path.join(hqRoot, rel, "worker.yaml"), deps);
|
|
398
|
+
candidates.push({
|
|
399
|
+
id: workerId,
|
|
400
|
+
path: rel,
|
|
401
|
+
type: fields?.type ?? "",
|
|
402
|
+
status: fields?.status && fields.status.length > 0 ? fields.status : "active",
|
|
403
|
+
description: fields?.description,
|
|
404
|
+
company: fields?.company,
|
|
405
|
+
realpath: real,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const declarations = declaredPackWorkers(hqRoot, deps).filter((row) => declarationMatches(row, workerId));
|
|
410
|
+
const unmounted = [];
|
|
411
|
+
for (const row of declarations) {
|
|
412
|
+
const status = linkStatus(row.link, path.join(hqRoot, "core", "packages", row.pack));
|
|
413
|
+
if (status !== "live") {
|
|
414
|
+
unmounted.push(row);
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const yamlPath = path.join(row.link.src, "worker.yaml");
|
|
418
|
+
if (!deps.existsSync(yamlPath)) {
|
|
419
|
+
throw new WorkerResolutionError("worker_not_found", `Worker ${JSON.stringify(workerId)} is mounted from pack ${row.pack} but worker.yaml is missing`, {
|
|
420
|
+
worker: workerId,
|
|
421
|
+
path: path.relative(hqRoot, row.link.dst).replaceAll("\\", "/"),
|
|
422
|
+
pack: row.pack,
|
|
423
|
+
missing: "worker.yaml",
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
const rel = normalizeRel(path.relative(hqRoot, row.link.dst));
|
|
427
|
+
const real = realpathOf(hqRoot, rel);
|
|
428
|
+
if (candidates.some((item) => item.realpath === real))
|
|
429
|
+
continue;
|
|
430
|
+
const fields = workerIdInYaml(yamlPath, deps);
|
|
431
|
+
candidates.push({
|
|
432
|
+
id: workerId,
|
|
433
|
+
path: rel,
|
|
434
|
+
type: fields?.type ?? "",
|
|
435
|
+
status: fields?.status && fields.status.length > 0 ? fields.status : "active",
|
|
436
|
+
description: fields?.description,
|
|
437
|
+
company: fields?.company,
|
|
438
|
+
realpath: real,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
if (unmounted.length > 0) {
|
|
442
|
+
const row = unmounted[0];
|
|
443
|
+
throw new WorkerResolutionError("package_workers_unmounted", `Worker ${JSON.stringify(workerId)} is declared by pack ${row.pack} but ${path.relative(hqRoot, row.link.dst)} is not a symlink to that pack. Run hq core scan-packages, then regenerate the worker registry.`, {
|
|
191
444
|
worker: workerId,
|
|
192
|
-
|
|
445
|
+
pack: row.pack,
|
|
446
|
+
item: row.item,
|
|
447
|
+
expected: path.relative(hqRoot, row.link.dst).replaceAll("\\", "/"),
|
|
448
|
+
also_registered_at: candidates.map((item) => item.path),
|
|
193
449
|
});
|
|
194
450
|
}
|
|
195
|
-
const
|
|
196
|
-
const
|
|
451
|
+
const distinct = [];
|
|
452
|
+
for (const candidate of candidates) {
|
|
453
|
+
if (distinct.some((item) => item.realpath === candidate.realpath))
|
|
454
|
+
continue;
|
|
455
|
+
distinct.push(candidate);
|
|
456
|
+
}
|
|
457
|
+
if (distinct.length > 1) {
|
|
458
|
+
throw new WorkerResolutionError("worker_id_conflict", `Worker ${JSON.stringify(workerId)} exists in more than one tree: ${distinct.map((item) => item.path).join(", ")}. Refusing to pick one.`, { worker: workerId, paths: distinct.map((item) => item.path) });
|
|
459
|
+
}
|
|
460
|
+
const entry = distinct[0];
|
|
197
461
|
if (!entry) {
|
|
198
462
|
throw new WorkerResolutionError("worker_not_found", `Worker ${JSON.stringify(workerId)} is not in core/workers/registry.yaml`, { worker: workerId });
|
|
199
463
|
}
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
464
|
+
const relativeDir = entry.path.replace(/\/+$/, "");
|
|
465
|
+
const workerDir = path.join(hqRoot, relativeDir);
|
|
466
|
+
const workerYaml = loadWorkerYaml(hqRoot, relativeDir, deps);
|
|
467
|
+
const pack = packNameFromRealpath(hqRoot, entry.realpath);
|
|
468
|
+
const workerCompany = typeof workerYaml.worker?.company === "string" ? workerYaml.worker.company : entry.company;
|
|
469
|
+
const tenancy = pack
|
|
470
|
+
? tenancyFromPackage(hqRoot, pack, workerCompany, deps)
|
|
471
|
+
: tenancyFromRegistryPath({
|
|
472
|
+
id: entry.id,
|
|
473
|
+
path: entry.path,
|
|
474
|
+
type: entry.type,
|
|
475
|
+
company: workerCompany,
|
|
476
|
+
});
|
|
204
477
|
if (tenancy.kind === "unresolved") {
|
|
205
|
-
throw new WorkerResolutionError("worker_tenancy_unresolved",
|
|
478
|
+
throw new WorkerResolutionError("worker_tenancy_unresolved", pack
|
|
479
|
+
? `Worker ${JSON.stringify(workerId)} is mounted from pack ${pack} whose company ownership cannot be determined`
|
|
480
|
+
: `Worker ${JSON.stringify(workerId)} has a company path whose company field is missing or contradicts the path`, {
|
|
206
481
|
worker: workerId,
|
|
207
482
|
path: entry.path,
|
|
208
483
|
path_slug: tenancy.pathSlug,
|
|
209
484
|
company_field: tenancy.field ?? null,
|
|
485
|
+
...(pack ? { pack } : {}),
|
|
210
486
|
});
|
|
211
487
|
}
|
|
212
488
|
if (tenancy.kind === "company") {
|
|
@@ -222,9 +498,6 @@ export function resolveLaneWorker(hqRoot, workerId, laneCompany, deps = defaultR
|
|
|
222
498
|
if (status !== "active") {
|
|
223
499
|
throw new WorkerResolutionError("worker_not_active", `Worker ${JSON.stringify(workerId)} has status ${JSON.stringify(status)} (need active)`, { worker: workerId, status });
|
|
224
500
|
}
|
|
225
|
-
const relativeDir = entry.path.replace(/\/+$/, "");
|
|
226
|
-
const workerDir = path.join(hqRoot, relativeDir);
|
|
227
|
-
const workerYaml = loadWorkerYaml(hqRoot, relativeDir, deps);
|
|
228
501
|
const resolvedContextBase = resolveContextBasePaths(hqRoot, workerDir, workerYaml.context?.base, deps);
|
|
229
502
|
const skillFiles = resolveSkillFiles(workerDir, workerYaml.skills, deps);
|
|
230
503
|
return {
|
|
@@ -242,6 +515,7 @@ export function resolveLaneWorker(hqRoot, workerId, laneCompany, deps = defaultR
|
|
|
242
515
|
defaultTimeout: workerYaml.execution?.max_runtime,
|
|
243
516
|
approvalRequired: workerYaml.verification?.approval_required === true,
|
|
244
517
|
humanCheckpoints: humanCheckpointsOf(workerYaml),
|
|
518
|
+
...(pack ? { pack } : {}),
|
|
245
519
|
};
|
|
246
520
|
}
|
|
247
521
|
//# sourceMappingURL=registry.js.map
|