@indigoai-us/hq-cli 5.125.0 → 5.127.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.
Files changed (108) hide show
  1. package/CHANGELOG.md +329 -0
  2. package/assets/bot-workers/setup/skills/joining-a-team.md +125 -0
  3. package/assets/bot-workers/setup/skills/your-business.md +270 -0
  4. package/assets/bot-workers/setup/worker.yaml +189 -58
  5. package/assets/lanes-hooks/hq-lanes-drain.json +15 -0
  6. package/assets/lanes-hooks/hq-lanes-drain.mjs +254 -0
  7. package/assets/lanes-hooks/hq-lanes-drain.sh +21 -0
  8. package/assets/lanes-reconcile/hq-lanes-reconcile.service +11 -0
  9. package/assets/lanes-reconcile/hq-lanes-reconcile.timer +12 -0
  10. package/dist/command-catalog.generated.d.ts +643 -6
  11. package/dist/command-catalog.generated.js +836 -6
  12. package/dist/command-registration-plan.d.ts +7 -0
  13. package/dist/command-registration-plan.js +11 -1
  14. package/dist/commands/agents.d.ts +12 -0
  15. package/dist/commands/agents.js +46 -2
  16. package/dist/commands/bot.d.ts +10 -0
  17. package/dist/commands/bot.js +6 -0
  18. package/dist/commands/lanes.d.ts +114 -0
  19. package/dist/commands/lanes.js +2969 -0
  20. package/dist/commands/mesh.js +287 -24
  21. package/dist/commands/onboard-create-identity.d.ts +26 -0
  22. package/dist/commands/onboard-create-identity.js +23 -0
  23. package/dist/commands/onboard.js +13 -4
  24. package/dist/lib/bot/progress.d.ts +6 -0
  25. package/dist/lib/bot/progress.js +16 -0
  26. package/dist/lib/bot/runtime/grok.d.ts +30 -7
  27. package/dist/lib/bot/runtime/grok.js +86 -8
  28. package/dist/lib/bot/runtime/index.js +15 -8
  29. package/dist/lib/bot/runtime/messages-stream.d.ts +12 -3
  30. package/dist/lib/bot/runtime/messages-stream.js +49 -3
  31. package/dist/lib/lanes/accounts.d.ts +103 -0
  32. package/dist/lib/lanes/accounts.js +318 -0
  33. package/dist/lib/lanes/compaction.d.ts +96 -0
  34. package/dist/lib/lanes/compaction.js +197 -0
  35. package/dist/lib/lanes/config.d.ts +71 -0
  36. package/dist/lib/lanes/config.js +408 -0
  37. package/dist/lib/lanes/delivery.d.ts +151 -0
  38. package/dist/lib/lanes/delivery.js +354 -0
  39. package/dist/lib/lanes/dropbox.d.ts +96 -0
  40. package/dist/lib/lanes/dropbox.js +408 -0
  41. package/dist/lib/lanes/effort.d.ts +50 -0
  42. package/dist/lib/lanes/effort.js +109 -0
  43. package/dist/lib/lanes/errors.d.ts +39 -0
  44. package/dist/lib/lanes/errors.js +72 -0
  45. package/dist/lib/lanes/hooks-install.d.ts +81 -0
  46. package/dist/lib/lanes/hooks-install.js +326 -0
  47. package/dist/lib/lanes/hq-root.d.ts +19 -0
  48. package/dist/lib/lanes/hq-root.js +28 -0
  49. package/dist/lib/lanes/index.d.ts +49 -0
  50. package/dist/lib/lanes/index.js +27 -0
  51. package/dist/lib/lanes/liveness.d.ts +95 -0
  52. package/dist/lib/lanes/liveness.js +332 -0
  53. package/dist/lib/lanes/machine-identity.d.ts +34 -0
  54. package/dist/lib/lanes/machine-identity.js +92 -0
  55. package/dist/lib/lanes/mesh-events.d.ts +109 -0
  56. package/dist/lib/lanes/mesh-events.js +275 -0
  57. package/dist/lib/lanes/outpost-dispatch.d.ts +65 -0
  58. package/dist/lib/lanes/outpost-dispatch.js +90 -0
  59. package/dist/lib/lanes/questions.d.ts +75 -0
  60. package/dist/lib/lanes/questions.js +243 -0
  61. package/dist/lib/lanes/quota.d.ts +95 -0
  62. package/dist/lib/lanes/quota.js +320 -0
  63. package/dist/lib/lanes/reconcile-install.d.ts +69 -0
  64. package/dist/lib/lanes/reconcile-install.js +221 -0
  65. package/dist/lib/lanes/reconcile.d.ts +102 -0
  66. package/dist/lib/lanes/reconcile.js +985 -0
  67. package/dist/lib/lanes/registry.d.ts +116 -0
  68. package/dist/lib/lanes/registry.js +521 -0
  69. package/dist/lib/lanes/spawn.d.ts +274 -0
  70. package/dist/lib/lanes/spawn.js +1363 -0
  71. package/dist/lib/lanes/store.d.ts +68 -0
  72. package/dist/lib/lanes/store.js +745 -0
  73. package/dist/lib/lanes/throttle.d.ts +89 -0
  74. package/dist/lib/lanes/throttle.js +297 -0
  75. package/dist/lib/lanes/types.d.ts +535 -0
  76. package/dist/lib/lanes/types.js +350 -0
  77. package/dist/lib/lanes/watcher-main.d.ts +9 -0
  78. package/dist/lib/lanes/watcher-main.js +22 -0
  79. package/dist/lib/lanes/watcher.d.ts +216 -0
  80. package/dist/lib/lanes/watcher.js +859 -0
  81. package/dist/lib/mesh/api.d.ts +71 -7
  82. package/dist/lib/mesh/api.js +167 -28
  83. package/dist/lib/mesh/live/backfill-held.d.ts +18 -7
  84. package/dist/lib/mesh/live/backfill-held.js +66 -21
  85. package/dist/lib/mesh/live/daemon/config.d.ts +27 -0
  86. package/dist/lib/mesh/live/daemon/config.js +75 -0
  87. package/dist/lib/mesh/live/daemon/index.d.ts +4 -3
  88. package/dist/lib/mesh/live/daemon/index.js +3 -3
  89. package/dist/lib/mesh/live/daemon/install.d.ts +143 -1
  90. package/dist/lib/mesh/live/daemon/install.js +444 -2
  91. package/dist/lib/mesh/live/daemon/paths.d.ts +2 -0
  92. package/dist/lib/mesh/live/daemon/paths.js +2 -0
  93. package/dist/lib/mesh/live/daemon/run.d.ts +2 -0
  94. package/dist/lib/mesh/live/daemon/run.js +111 -54
  95. package/dist/lib/mesh/live/daemon/state.d.ts +2 -0
  96. package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +3 -0
  97. package/dist/lib/mesh/live/daemon/transcript-watch.js +31 -8
  98. package/dist/lib/scan-packages/index.d.ts +2 -0
  99. package/dist/lib/scan-packages/index.js +18 -13
  100. package/dist/lib/work-context/config.d.ts +20 -0
  101. package/dist/lib/work-context/config.js +26 -5
  102. package/dist/lib/work-context/reconcile.js +28 -7
  103. package/dist/lib/work-context/state.d.ts +12 -0
  104. package/dist/lib/work-context/state.js +131 -3
  105. package/dist/lib/work-context/windows-acl.d.ts +123 -0
  106. package/dist/lib/work-context/windows-acl.js +425 -0
  107. package/dist/utils/pack-contributions.js +5 -4
  108. package/package.json +3 -1
@@ -0,0 +1,116 @@
1
+ /**
2
+ * HQ worker resolution for lanes (spec §5.3).
3
+ *
4
+ * Tenancy derives from the registry entry's `path:` prefix, never from the
5
+ * optional `company:` field. A company path with a missing or contradictory
6
+ * `company:` is `worker_tenancy_unresolved`, never a global worker.
7
+ *
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.
24
+ */
25
+ import * as fs from "node:fs";
26
+ export declare const WORKER_REGISTRY_REL: string;
27
+ export interface RegistryWorkerEntry {
28
+ id: string;
29
+ path: string;
30
+ type: string;
31
+ visibility?: string;
32
+ company?: string;
33
+ team?: string;
34
+ status?: string;
35
+ description?: string;
36
+ }
37
+ export type WorkerTenancy = {
38
+ kind: "global";
39
+ } | {
40
+ kind: "company";
41
+ slug: string;
42
+ };
43
+ export interface WorkerYamlSkill {
44
+ id?: string;
45
+ file: string;
46
+ }
47
+ export interface WorkerYaml {
48
+ worker?: {
49
+ id?: string;
50
+ name?: string;
51
+ description?: string;
52
+ type?: string;
53
+ company?: string;
54
+ status?: string;
55
+ };
56
+ instructions?: unknown;
57
+ skills?: WorkerYamlSkill[];
58
+ context?: {
59
+ base?: string[];
60
+ };
61
+ knowledge?: unknown;
62
+ verification?: {
63
+ approval_required?: boolean;
64
+ human_checkpoints?: unknown;
65
+ };
66
+ execution?: {
67
+ max_runtime?: unknown;
68
+ model?: unknown;
69
+ codex_model?: unknown;
70
+ codex_flags?: unknown;
71
+ };
72
+ }
73
+ export interface ResolvedLaneWorker {
74
+ id: string;
75
+ path: string;
76
+ type: string;
77
+ status: string;
78
+ visibility?: string;
79
+ description?: string;
80
+ tenancy: WorkerTenancy;
81
+ workerYaml: WorkerYaml;
82
+ workerDir: string;
83
+ resolvedContextBase: string[];
84
+ skillFiles: string[];
85
+ defaultTimeout: unknown;
86
+ approvalRequired: boolean;
87
+ humanCheckpoints: string[];
88
+ /** Set when the worker's realpath lives in a package. */
89
+ pack?: string;
90
+ }
91
+ export interface RegistryDeps {
92
+ existsSync: (p: string) => boolean;
93
+ readFileSync: (p: string, encoding: "utf8") => string;
94
+ statSync: (p: string) => fs.Stats;
95
+ }
96
+ export declare function registryPath(hqRoot: string): string;
97
+ export declare function personalWorkerDir(hqRoot: string, workerId: string): string;
98
+ export declare function personalWorkerExists(hqRoot: string, workerId: string, deps?: RegistryDeps): boolean;
99
+ export declare function readWorkerRegistryFile(hqRoot: string, deps?: RegistryDeps): RegistryWorkerEntry[];
100
+ /**
101
+ * Tenancy from path, not from `company:`. A company path whose field is
102
+ * missing or disagrees is unresolved — never global.
103
+ */
104
+ export declare function tenancyFromRegistryPath(entry: RegistryWorkerEntry): WorkerTenancy | {
105
+ kind: "unresolved";
106
+ pathSlug: string;
107
+ field?: string;
108
+ };
109
+ /**
110
+ * Resolve context.base against HQ root, then core/, then the worker directory.
111
+ * Entries that resolve nowhere are dropped, not passed on.
112
+ */
113
+ export declare function resolveContextBasePaths(hqRoot: string, workerDir: string, bases: string[] | undefined, deps?: RegistryDeps): string[];
114
+ export declare function resolveSkillFiles(workerDir: string, skills: WorkerYamlSkill[] | undefined, deps?: RegistryDeps): string[];
115
+ export declare function resolveLaneWorker(hqRoot: string, workerId: string, laneCompany: string | undefined, deps?: RegistryDeps): ResolvedLaneWorker;
116
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1,521 @@
1
+ /**
2
+ * HQ worker resolution for lanes (spec §5.3).
3
+ *
4
+ * Tenancy derives from the registry entry's `path:` prefix, never from the
5
+ * optional `company:` field. A company path with a missing or contradictory
6
+ * `company:` is `worker_tenancy_unresolved`, never a global worker.
7
+ *
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.
24
+ */
25
+ import * as fs from "node:fs";
26
+ import * as path from "node:path";
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";
30
+ import { WorkerResolutionError } from "./errors.js";
31
+ export const WORKER_REGISTRY_REL = path.join("core", "workers", "registry.yaml");
32
+ function defaultRegistryDeps() {
33
+ return {
34
+ existsSync: (p) => fs.existsSync(p),
35
+ readFileSync: (p, encoding) => fs.readFileSync(p, encoding),
36
+ statSync: (p) => fs.statSync(p),
37
+ };
38
+ }
39
+ function isPlainObject(value) {
40
+ return value !== null && typeof value === "object" && !Array.isArray(value);
41
+ }
42
+ export function registryPath(hqRoot) {
43
+ return path.join(hqRoot, WORKER_REGISTRY_REL);
44
+ }
45
+ export function personalWorkerDir(hqRoot, workerId) {
46
+ return path.join(hqRoot, "personal", "workers", workerId);
47
+ }
48
+ function isExistingPath(p, deps) {
49
+ try {
50
+ const st = deps.statSync(p);
51
+ return st.isFile() || st.isDirectory();
52
+ }
53
+ catch (err) {
54
+ const code = err.code;
55
+ if (code === "ENOENT")
56
+ return false;
57
+ throw err;
58
+ }
59
+ }
60
+ export function personalWorkerExists(hqRoot, workerId, deps = defaultRegistryDeps()) {
61
+ const yamlPath = path.join(personalWorkerDir(hqRoot, workerId), "worker.yaml");
62
+ try {
63
+ return deps.statSync(yamlPath).isFile();
64
+ }
65
+ catch (err) {
66
+ const code = err.code;
67
+ if (code === "ENOENT")
68
+ return false;
69
+ throw err;
70
+ }
71
+ }
72
+ export function readWorkerRegistryFile(hqRoot, deps = defaultRegistryDeps()) {
73
+ const filePath = registryPath(hqRoot);
74
+ if (!deps.existsSync(filePath)) {
75
+ throw new WorkerResolutionError("worker_registry_unreadable", `Worker registry missing at ${filePath}`, { path: filePath });
76
+ }
77
+ let text;
78
+ try {
79
+ text = deps.readFileSync(filePath, "utf8");
80
+ }
81
+ catch (err) {
82
+ throw new WorkerResolutionError("worker_registry_unreadable", `Failed to read ${filePath}: ${err instanceof Error ? err.message : String(err)}`, { path: filePath });
83
+ }
84
+ const doc = parseUserYaml(text, filePath);
85
+ const workers = doc?.workers;
86
+ if (!Array.isArray(workers)) {
87
+ throw new WorkerResolutionError("worker_registry_unreadable", `Worker registry workers key is missing or not an array at ${filePath}`, { path: filePath });
88
+ }
89
+ const entries = [];
90
+ for (let i = 0; i < workers.length; i += 1) {
91
+ const item = workers[i];
92
+ if (!isPlainObject(item)) {
93
+ process.stderr.write(`lanes registry: skipping malformed worker entry in ${filePath} at index ${i}: not an object (type=${typeof item})\n`);
94
+ continue;
95
+ }
96
+ if (typeof item.id !== "string" ||
97
+ item.id.length === 0 ||
98
+ typeof item.path !== "string" ||
99
+ item.path.length === 0) {
100
+ process.stderr.write(`lanes registry: skipping malformed worker entry in ${filePath} at index ${i}: missing id or path (id=${JSON.stringify(item.id)} path=${JSON.stringify(item.path)})\n`);
101
+ continue;
102
+ }
103
+ entries.push({
104
+ id: item.id,
105
+ path: item.path,
106
+ type: typeof item.type === "string" ? item.type : "",
107
+ visibility: typeof item.visibility === "string" ? item.visibility : undefined,
108
+ company: typeof item.company === "string" ? item.company : undefined,
109
+ team: typeof item.team === "string" ? item.team : undefined,
110
+ status: typeof item.status === "string" ? item.status : undefined,
111
+ description: typeof item.description === "string" ? item.description : undefined,
112
+ });
113
+ }
114
+ return entries;
115
+ }
116
+ const COMPANY_PATH = /^companies\/([^/]+)\//;
117
+ const PERSONAL_PATH = /^(?:personal\/workers\/)/;
118
+ const CORE_PATH = /^(?:core\/workers\/)/;
119
+ /**
120
+ * Tenancy from path, not from `company:`. A company path whose field is
121
+ * missing or disagrees is unresolved — never global.
122
+ */
123
+ export function tenancyFromRegistryPath(entry) {
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.
127
+ if (PERSONAL_PATH.test(normalized)) {
128
+ return { kind: "global" };
129
+ }
130
+ const companyMatch = COMPANY_PATH.exec(normalized);
131
+ if (companyMatch) {
132
+ const pathSlug = companyMatch[1] ?? "";
133
+ if (!pathSlug) {
134
+ return { kind: "unresolved", pathSlug, field: entry.company };
135
+ }
136
+ const field = entry.company?.trim() ?? "";
137
+ if (!field || field !== pathSlug) {
138
+ return { kind: "unresolved", pathSlug, field: field || undefined };
139
+ }
140
+ return { kind: "company", slug: pathSlug };
141
+ }
142
+ if (CORE_PATH.test(normalized) || normalized.startsWith("core/")) {
143
+ return { kind: "global" };
144
+ }
145
+ // Unknown tree: do not treat as global.
146
+ return { kind: "unresolved", pathSlug: "", field: entry.company };
147
+ }
148
+ function loadWorkerYaml(hqRoot, relativeDir, deps) {
149
+ const dir = path.join(hqRoot, relativeDir);
150
+ const filePath = path.join(dir, "worker.yaml");
151
+ if (!deps.existsSync(filePath)) {
152
+ throw new WorkerResolutionError("worker_not_found", `worker.yaml missing at ${filePath}`, { path: filePath });
153
+ }
154
+ const text = deps.readFileSync(filePath, "utf8");
155
+ const doc = parseUserYaml(text, filePath);
156
+ if (doc === null || typeof doc !== "object" || Array.isArray(doc)) {
157
+ return {};
158
+ }
159
+ return doc;
160
+ }
161
+ /**
162
+ * Resolve context.base against HQ root, then core/, then the worker directory.
163
+ * Entries that resolve nowhere are dropped, not passed on.
164
+ */
165
+ export function resolveContextBasePaths(hqRoot, workerDir, bases, deps = defaultRegistryDeps()) {
166
+ if (!Array.isArray(bases) || bases.length === 0)
167
+ return [];
168
+ const resolved = [];
169
+ for (const entry of bases) {
170
+ if (typeof entry !== "string" || entry.length === 0)
171
+ continue;
172
+ const candidates = [
173
+ path.isAbsolute(entry) ? entry : path.join(hqRoot, entry),
174
+ path.join(hqRoot, "core", entry),
175
+ path.join(workerDir, entry),
176
+ ];
177
+ const hit = candidates.find((candidate) => isExistingPath(candidate, deps));
178
+ if (hit)
179
+ resolved.push(hit);
180
+ }
181
+ return resolved;
182
+ }
183
+ export function resolveSkillFiles(workerDir, skills, deps = defaultRegistryDeps()) {
184
+ if (!Array.isArray(skills) || skills.length === 0)
185
+ return [];
186
+ const files = [];
187
+ for (const skill of skills) {
188
+ if (!skill || typeof skill.file !== "string" || skill.file.length === 0) {
189
+ continue;
190
+ }
191
+ const candidate = path.isAbsolute(skill.file)
192
+ ? skill.file
193
+ : path.join(workerDir, skill.file);
194
+ if (isExistingPath(candidate, deps))
195
+ files.push(candidate);
196
+ }
197
+ return files;
198
+ }
199
+ function humanCheckpointsOf(yaml) {
200
+ const raw = yaml.verification?.human_checkpoints;
201
+ if (!Array.isArray(raw))
202
+ return [];
203
+ return raw.filter((item) => typeof item === "string");
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
+ }
370
+ export function resolveLaneWorker(hqRoot, workerId, laneCompany, deps = defaultRegistryDeps()) {
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.`, {
444
+ worker: workerId,
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),
449
+ });
450
+ }
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];
461
+ if (!entry) {
462
+ throw new WorkerResolutionError("worker_not_found", `Worker ${JSON.stringify(workerId)} is not in core/workers/registry.yaml`, { worker: workerId });
463
+ }
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
+ });
477
+ if (tenancy.kind === "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`, {
481
+ worker: workerId,
482
+ path: entry.path,
483
+ path_slug: tenancy.pathSlug,
484
+ company_field: tenancy.field ?? null,
485
+ ...(pack ? { pack } : {}),
486
+ });
487
+ }
488
+ if (tenancy.kind === "company") {
489
+ if (!laneCompany || laneCompany !== tenancy.slug) {
490
+ throw new WorkerResolutionError("worker_not_available", `Worker ${JSON.stringify(workerId)} belongs to company ${tenancy.slug} and is not available to ${laneCompany ?? "(none)"}`, {
491
+ worker: workerId,
492
+ worker_company: tenancy.slug,
493
+ lane_company: laneCompany ?? null,
494
+ });
495
+ }
496
+ }
497
+ const status = entry.status && entry.status.length > 0 ? entry.status : "active";
498
+ if (status !== "active") {
499
+ throw new WorkerResolutionError("worker_not_active", `Worker ${JSON.stringify(workerId)} has status ${JSON.stringify(status)} (need active)`, { worker: workerId, status });
500
+ }
501
+ const resolvedContextBase = resolveContextBasePaths(hqRoot, workerDir, workerYaml.context?.base, deps);
502
+ const skillFiles = resolveSkillFiles(workerDir, workerYaml.skills, deps);
503
+ return {
504
+ id: entry.id,
505
+ path: entry.path,
506
+ type: entry.type,
507
+ status,
508
+ visibility: entry.visibility,
509
+ description: entry.description,
510
+ tenancy,
511
+ workerYaml,
512
+ workerDir,
513
+ resolvedContextBase,
514
+ skillFiles,
515
+ defaultTimeout: workerYaml.execution?.max_runtime,
516
+ approvalRequired: workerYaml.verification?.approval_required === true,
517
+ humanCheckpoints: humanCheckpointsOf(workerYaml),
518
+ ...(pack ? { pack } : {}),
519
+ };
520
+ }
521
+ //# sourceMappingURL=registry.js.map