@indigoai-us/hq-cli 5.125.0 → 5.126.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 (57) hide show
  1. package/CHANGELOG.md +118 -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/dist/command-catalog.generated.d.ts +407 -4
  6. package/dist/command-catalog.generated.js +522 -4
  7. package/dist/command-registration-plan.d.ts +6 -0
  8. package/dist/command-registration-plan.js +1 -0
  9. package/dist/commands/bot.d.ts +10 -0
  10. package/dist/commands/bot.js +6 -0
  11. package/dist/commands/lanes.d.ts +75 -0
  12. package/dist/commands/lanes.js +2032 -0
  13. package/dist/commands/mesh.js +37 -9
  14. package/dist/commands/onboard-create-identity.d.ts +26 -0
  15. package/dist/commands/onboard-create-identity.js +23 -0
  16. package/dist/commands/onboard.js +13 -4
  17. package/dist/lib/bot/progress.d.ts +6 -0
  18. package/dist/lib/bot/progress.js +16 -0
  19. package/dist/lib/bot/runtime/grok.d.ts +30 -7
  20. package/dist/lib/bot/runtime/grok.js +86 -8
  21. package/dist/lib/bot/runtime/index.js +15 -8
  22. package/dist/lib/bot/runtime/messages-stream.d.ts +12 -3
  23. package/dist/lib/bot/runtime/messages-stream.js +49 -3
  24. package/dist/lib/lanes/accounts.d.ts +43 -0
  25. package/dist/lib/lanes/accounts.js +128 -0
  26. package/dist/lib/lanes/config.d.ts +56 -0
  27. package/dist/lib/lanes/config.js +351 -0
  28. package/dist/lib/lanes/errors.d.ts +39 -0
  29. package/dist/lib/lanes/errors.js +72 -0
  30. package/dist/lib/lanes/index.d.ts +19 -0
  31. package/dist/lib/lanes/index.js +12 -0
  32. package/dist/lib/lanes/liveness.d.ts +89 -0
  33. package/dist/lib/lanes/liveness.js +273 -0
  34. package/dist/lib/lanes/registry.d.ts +103 -0
  35. package/dist/lib/lanes/registry.js +247 -0
  36. package/dist/lib/lanes/store.d.ts +64 -0
  37. package/dist/lib/lanes/store.js +627 -0
  38. package/dist/lib/lanes/throttle.d.ts +89 -0
  39. package/dist/lib/lanes/throttle.js +297 -0
  40. package/dist/lib/lanes/types.d.ts +193 -0
  41. package/dist/lib/lanes/types.js +217 -0
  42. package/dist/lib/mesh/live/daemon/config.d.ts +27 -0
  43. package/dist/lib/mesh/live/daemon/config.js +75 -0
  44. package/dist/lib/mesh/live/daemon/index.d.ts +4 -3
  45. package/dist/lib/mesh/live/daemon/index.js +3 -3
  46. package/dist/lib/mesh/live/daemon/install.d.ts +143 -1
  47. package/dist/lib/mesh/live/daemon/install.js +444 -2
  48. package/dist/lib/mesh/live/daemon/paths.d.ts +2 -0
  49. package/dist/lib/mesh/live/daemon/paths.js +2 -0
  50. package/dist/lib/scan-packages/index.js +17 -12
  51. package/dist/lib/work-context/config.d.ts +20 -0
  52. package/dist/lib/work-context/config.js +26 -5
  53. package/dist/lib/work-context/reconcile.js +28 -7
  54. package/dist/lib/work-context/windows-acl.d.ts +123 -0
  55. package/dist/lib/work-context/windows-acl.js +425 -0
  56. package/dist/utils/pack-contributions.js +5 -4
  57. package/package.json +1 -1
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Provider account resolution (spec §5.5).
3
+ *
4
+ * Naming an account and omitting one mean different things: an explicit
5
+ * disabled id is refused; omitting rotates past a disabled default.
6
+ * Status reports `configured` and `logged_in` separately. logged_in is
7
+ * derived from whether <home>/auth.json EXISTS — this module never reads,
8
+ * prints, copies, or hashes that file.
9
+ */
10
+ import * as fs from "node:fs";
11
+ import * as path from "node:path";
12
+ import { AccountResolutionError } from "./errors.js";
13
+ export const PROVIDER_HOME_ENV = {
14
+ claude: "CLAUDE_CONFIG_DIR",
15
+ codex: "CODEX_HOME",
16
+ grok: "GROK_HOME",
17
+ };
18
+ export const AUTH_JSON_NAME = "auth.json";
19
+ function defaultAccountDeps() {
20
+ return { existsSync: (p) => fs.existsSync(p) };
21
+ }
22
+ function enabledIds(provider) {
23
+ return Object.entries(provider.accounts)
24
+ .filter(([, account]) => account.enabled)
25
+ .map(([id]) => id);
26
+ }
27
+ function rotateEnabled(providerCfg, skipId) {
28
+ for (const [id, account] of Object.entries(providerCfg.accounts)) {
29
+ if (!account.enabled)
30
+ continue;
31
+ if (skipId !== undefined && id === skipId)
32
+ continue;
33
+ return id;
34
+ }
35
+ return undefined;
36
+ }
37
+ export function resolveAccount(provider, requestedId, config) {
38
+ const providerCfg = config.providers[provider];
39
+ const envVar = PROVIDER_HOME_ENV[provider];
40
+ const enabled = enabledIds(providerCfg);
41
+ if (requestedId !== undefined && requestedId.length > 0) {
42
+ const account = providerCfg.accounts[requestedId];
43
+ if (account === undefined) {
44
+ throw new AccountResolutionError("unknown_account", `Unknown account ${JSON.stringify(requestedId)} for ${provider}. Enabled: ${enabled.join(", ") || "(none)"}`, {
45
+ provider,
46
+ account: requestedId,
47
+ enabled,
48
+ });
49
+ }
50
+ if (!account.enabled) {
51
+ throw new AccountResolutionError("account_disabled", `Account ${JSON.stringify(requestedId)} is disabled${account.disabled_reason ? `: ${account.disabled_reason}` : ""}`, {
52
+ account: requestedId,
53
+ reason: account.disabled_reason ?? "",
54
+ });
55
+ }
56
+ return {
57
+ provider,
58
+ id: requestedId,
59
+ home: account.home,
60
+ enabled: true,
61
+ envVar,
62
+ };
63
+ }
64
+ const defaultId = providerCfg.default_account;
65
+ const defaultAccount = providerCfg.accounts[defaultId];
66
+ if (defaultAccount?.enabled) {
67
+ return {
68
+ provider,
69
+ id: defaultId,
70
+ home: defaultAccount.home,
71
+ enabled: true,
72
+ envVar,
73
+ };
74
+ }
75
+ const rotated = rotateEnabled(providerCfg, defaultAccount ? defaultId : undefined);
76
+ if (rotated === undefined) {
77
+ throw new AccountResolutionError("no_enabled_account", `No enabled account for provider ${provider}`, { provider });
78
+ }
79
+ const rotatedAccount = providerCfg.accounts[rotated];
80
+ return {
81
+ provider,
82
+ id: rotated,
83
+ home: rotatedAccount.home,
84
+ enabled: true,
85
+ envVar,
86
+ };
87
+ }
88
+ export function authJsonPath(home) {
89
+ return path.join(home, AUTH_JSON_NAME);
90
+ }
91
+ export function accountStatus(provider, accountId, config, deps = defaultAccountDeps()) {
92
+ const envVar = PROVIDER_HOME_ENV[provider];
93
+ const account = config.providers[provider].accounts[accountId];
94
+ if (account === undefined) {
95
+ return {
96
+ provider,
97
+ id: accountId,
98
+ home: "",
99
+ enabled: false,
100
+ configured: false,
101
+ logged_in: false,
102
+ envVar,
103
+ };
104
+ }
105
+ const authPath = authJsonPath(account.home);
106
+ const logged_in = deps.existsSync(authPath);
107
+ const status = {
108
+ provider,
109
+ id: accountId,
110
+ home: account.home,
111
+ enabled: account.enabled,
112
+ configured: true,
113
+ logged_in,
114
+ envVar,
115
+ };
116
+ if (account.disabled_reason !== undefined) {
117
+ status.disabled_reason = account.disabled_reason;
118
+ }
119
+ return status;
120
+ }
121
+ export function listAccountStatuses(provider, config, deps = defaultAccountDeps()) {
122
+ return Object.keys(config.providers[provider].accounts).map((id) => accountStatus(provider, id, config, deps));
123
+ }
124
+ /** Environment assignment the worker process should receive. */
125
+ export function accountEnv(resolved) {
126
+ return { [resolved.envVar]: resolved.home };
127
+ }
128
+ //# sourceMappingURL=accounts.js.map
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Load and validate personal/lanes/config.json (spec §5.5).
3
+ *
4
+ * A missing file is the documented defaults, not an error. A `providers` key
5
+ * outside {claude, codex, grok} is rejected with the accepted list. Account
6
+ * homes are canonicalised with realpath; two account ids for one provider
7
+ * that resolve to the same home are refused (identity-keyed-cap rule).
8
+ */
9
+ import * as fs from "node:fs";
10
+ import { type LaneProvider, type LanesConfig, type LivenessWindows, type ProviderConfig } from "./types.js";
11
+ export declare const LANES_CONFIG_REL: string;
12
+ export declare const DEFAULT_MAX_QUEUED = 3;
13
+ export declare const DEFAULT_CPU_BUSY_PAUSE = 0.85;
14
+ export declare const DEFAULT_CPU_BUSY_RESUME = 0.7;
15
+ export declare const DEFAULT_MIN_AVAILABLE_MEM_MB = 1024;
16
+ /** Spec §9: last event within 10 minutes → live. */
17
+ export declare const DEFAULT_LIVE_WITHIN_MS: number;
18
+ /** Spec §9: turn_start with no turn_end after 2 hours → lost. */
19
+ export declare const DEFAULT_TURN_START_LOST_MS: number;
20
+ /** Spec §9: no event for 12 hours (and no session_end) → lost. */
21
+ export declare const DEFAULT_SILENT_LOST_MS: number;
22
+ export declare const DEFAULT_LIVENESS_WINDOWS: LivenessWindows;
23
+ export declare const DEFAULT_PROVIDER_MAX_RUNNING: Record<LaneProvider, number>;
24
+ export declare const DEFAULT_ACCOUNT_HOMES: Record<LaneProvider, string>;
25
+ export interface ConfigLoadDeps {
26
+ homedir: () => string;
27
+ realpathSync: (p: string) => string;
28
+ existsSync: (p: string) => boolean;
29
+ readFileSync: (p: string, encoding: "utf8") => string;
30
+ }
31
+ export declare function expandHome(home: string, homedir: string): string;
32
+ /**
33
+ * Canonical principal for an account home: realpath when the path exists,
34
+ * otherwise resolve existing ancestors and rejoin the missing tail so two
35
+ * not-yet-created spellings of the same directory still collide.
36
+ */
37
+ export declare function canonicalizeHome(home: string, deps?: Pick<ConfigLoadDeps, "homedir" | "realpathSync" | "existsSync">): string;
38
+ export declare function documentedDefaultConfig(deps?: ConfigLoadDeps): LanesConfig;
39
+ export declare function parseLanesConfig(raw: unknown, deps?: ConfigLoadDeps): LanesConfig;
40
+ export declare function lanesConfigPath(hqRoot: string): string;
41
+ /**
42
+ * Load operator config. Missing file → documented defaults. Unreadable or
43
+ * invalid JSON is an error (never a silent default).
44
+ */
45
+ export declare function loadLanesConfig(hqRoot: string, deps?: ConfigLoadDeps): LanesConfig;
46
+ export interface ConfigWriteDeps {
47
+ mkdirSync: typeof fs.mkdirSync;
48
+ writeFileSync: typeof fs.writeFileSync;
49
+ renameSync: typeof fs.renameSync;
50
+ unlinkSync: typeof fs.unlinkSync;
51
+ }
52
+ /** Persist operator config. Homes are stored as already-canonicalised paths. */
53
+ export declare function writeLanesConfig(hqRoot: string, config: LanesConfig, deps?: ConfigWriteDeps): void;
54
+ /** Account running cap, defaulting to the provider ceiling when unset. */
55
+ export declare function accountMaxRunning(provider: ProviderConfig, accountId: string): number;
56
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Load and validate personal/lanes/config.json (spec §5.5).
3
+ *
4
+ * A missing file is the documented defaults, not an error. A `providers` key
5
+ * outside {claude, codex, grok} is rejected with the accepted list. Account
6
+ * homes are canonicalised with realpath; two account ids for one provider
7
+ * that resolve to the same home are refused (identity-keyed-cap rule).
8
+ */
9
+ import * as fs from "node:fs";
10
+ import * as os from "node:os";
11
+ import * as path from "node:path";
12
+ import { LanesConfigError } from "./errors.js";
13
+ import { CONFIG_SCHEMA_VERSION, LANE_PROVIDERS, isLaneProvider, parseMajorSchema, } from "./types.js";
14
+ export const LANES_CONFIG_REL = path.join("personal", "lanes", "config.json");
15
+ export const DEFAULT_MAX_QUEUED = 3;
16
+ export const DEFAULT_CPU_BUSY_PAUSE = 0.85;
17
+ export const DEFAULT_CPU_BUSY_RESUME = 0.7;
18
+ export const DEFAULT_MIN_AVAILABLE_MEM_MB = 1024;
19
+ /** Spec §9: last event within 10 minutes → live. */
20
+ export const DEFAULT_LIVE_WITHIN_MS = 10 * 60 * 1000;
21
+ /** Spec §9: turn_start with no turn_end after 2 hours → lost. */
22
+ export const DEFAULT_TURN_START_LOST_MS = 2 * 60 * 60 * 1000;
23
+ /** Spec §9: no event for 12 hours (and no session_end) → lost. */
24
+ export const DEFAULT_SILENT_LOST_MS = 12 * 60 * 60 * 1000;
25
+ export const DEFAULT_LIVENESS_WINDOWS = {
26
+ live_within_ms: DEFAULT_LIVE_WITHIN_MS,
27
+ turn_start_lost_ms: DEFAULT_TURN_START_LOST_MS,
28
+ silent_lost_ms: DEFAULT_SILENT_LOST_MS,
29
+ };
30
+ export const DEFAULT_PROVIDER_MAX_RUNNING = {
31
+ claude: 2,
32
+ codex: 4,
33
+ grok: 4,
34
+ };
35
+ export const DEFAULT_ACCOUNT_HOMES = {
36
+ claude: "~/.claude",
37
+ codex: "~/.codex",
38
+ grok: "~/.grok",
39
+ };
40
+ function defaultDeps() {
41
+ return {
42
+ homedir: () => os.homedir(),
43
+ realpathSync: (p) => fs.realpathSync(p),
44
+ existsSync: (p) => fs.existsSync(p),
45
+ readFileSync: (p, encoding) => fs.readFileSync(p, encoding),
46
+ };
47
+ }
48
+ export function expandHome(home, homedir) {
49
+ if (home === "~")
50
+ return homedir;
51
+ if (home.startsWith("~/") || home.startsWith("~\\")) {
52
+ return path.join(homedir, home.slice(2));
53
+ }
54
+ return home;
55
+ }
56
+ /**
57
+ * Canonical principal for an account home: realpath when the path exists,
58
+ * otherwise resolve existing ancestors and rejoin the missing tail so two
59
+ * not-yet-created spellings of the same directory still collide.
60
+ */
61
+ export function canonicalizeHome(home, deps = defaultDeps()) {
62
+ const expanded = path.resolve(expandHome(home, deps.homedir()));
63
+ try {
64
+ return deps.realpathSync(expanded);
65
+ }
66
+ catch (err) {
67
+ const code = err.code;
68
+ if (code !== "ENOENT")
69
+ throw err;
70
+ }
71
+ let cur = expanded;
72
+ const missing = [];
73
+ for (let i = 0; i < 64; i += 1) {
74
+ if (deps.existsSync(cur)) {
75
+ try {
76
+ return path.join(deps.realpathSync(cur), ...missing.reverse());
77
+ }
78
+ catch (err) {
79
+ const code = err.code;
80
+ if (code !== "ENOENT")
81
+ throw err;
82
+ }
83
+ }
84
+ const parent = path.dirname(cur);
85
+ if (parent === cur)
86
+ break;
87
+ missing.push(path.basename(cur));
88
+ cur = parent;
89
+ }
90
+ return expanded;
91
+ }
92
+ function isPlainObject(value) {
93
+ return value !== null && typeof value === "object" && !Array.isArray(value);
94
+ }
95
+ function positiveInt(value, field, fallback) {
96
+ if (value === undefined && fallback !== undefined)
97
+ return fallback;
98
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
99
+ throw new LanesConfigError("invalid_config", `${field} must be a positive integer`, { field, value });
100
+ }
101
+ return value;
102
+ }
103
+ function unitInterval(value, field, fallback) {
104
+ if (value === undefined)
105
+ return fallback;
106
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
107
+ throw new LanesConfigError("invalid_config", `${field} must be a number in [0, 1]`, { field, value });
108
+ }
109
+ return value;
110
+ }
111
+ function parseAccount(provider, id, raw, deps) {
112
+ if (!isPlainObject(raw)) {
113
+ throw new LanesConfigError("invalid_config", `providers.${provider}.accounts.${id} must be an object`, { provider, account: id });
114
+ }
115
+ if (typeof raw.home !== "string" || raw.home.length === 0) {
116
+ throw new LanesConfigError("invalid_config", `providers.${provider}.accounts.${id}.home must be a non-empty string`, { provider, account: id });
117
+ }
118
+ if (typeof raw.enabled !== "boolean") {
119
+ throw new LanesConfigError("invalid_config", `providers.${provider}.accounts.${id}.enabled must be a boolean`, { provider, account: id });
120
+ }
121
+ const account = {
122
+ home: canonicalizeHome(raw.home, deps),
123
+ enabled: raw.enabled,
124
+ };
125
+ if (raw.disabled_reason !== undefined) {
126
+ if (typeof raw.disabled_reason !== "string") {
127
+ throw new LanesConfigError("invalid_config", `providers.${provider}.accounts.${id}.disabled_reason must be a string`, { provider, account: id });
128
+ }
129
+ account.disabled_reason = raw.disabled_reason;
130
+ }
131
+ if (raw.max_running !== undefined) {
132
+ account.max_running = positiveInt(raw.max_running, `providers.${provider}.accounts.${id}.max_running`);
133
+ }
134
+ return account;
135
+ }
136
+ function rejectDuplicateHomes(provider, accounts) {
137
+ const seen = new Map();
138
+ for (const [id, account] of Object.entries(accounts)) {
139
+ const prior = seen.get(account.home);
140
+ if (prior !== undefined) {
141
+ throw new LanesConfigError("duplicate_account_home", `providers.${provider} accounts ${JSON.stringify(prior)} and ${JSON.stringify(id)} resolve to the same home ${account.home}`, {
142
+ provider,
143
+ accounts: [prior, id],
144
+ home: account.home,
145
+ });
146
+ }
147
+ seen.set(account.home, id);
148
+ }
149
+ }
150
+ function defaultAccountMap(provider, deps) {
151
+ return {
152
+ a: {
153
+ home: canonicalizeHome(DEFAULT_ACCOUNT_HOMES[provider], deps),
154
+ enabled: true,
155
+ },
156
+ };
157
+ }
158
+ function parseProvider(provider, raw, deps) {
159
+ if (!isPlainObject(raw)) {
160
+ throw new LanesConfigError("invalid_config", `providers.${provider} must be an object`, { provider });
161
+ }
162
+ const accountsRaw = raw.accounts;
163
+ const accounts = {};
164
+ if (accountsRaw === undefined) {
165
+ Object.assign(accounts, defaultAccountMap(provider, deps));
166
+ }
167
+ else {
168
+ if (!isPlainObject(accountsRaw)) {
169
+ throw new LanesConfigError("invalid_config", `providers.${provider}.accounts must be an object`, { provider });
170
+ }
171
+ for (const [id, value] of Object.entries(accountsRaw)) {
172
+ if (id.length === 0) {
173
+ throw new LanesConfigError("invalid_config", `providers.${provider} account ids must be non-empty strings`, { provider });
174
+ }
175
+ accounts[id] = parseAccount(provider, id, value, deps);
176
+ }
177
+ }
178
+ rejectDuplicateHomes(provider, accounts);
179
+ const defaultAccount = typeof raw.default_account === "string" && raw.default_account.length > 0
180
+ ? raw.default_account
181
+ : "a";
182
+ if (Object.keys(accounts).length > 0 && accounts[defaultAccount] === undefined) {
183
+ throw new LanesConfigError("invalid_config", `providers.${provider}.default_account ${JSON.stringify(defaultAccount)} is not a configured account`, { provider, default_account: defaultAccount, accounts: Object.keys(accounts) });
184
+ }
185
+ return {
186
+ max_running: positiveInt(raw.max_running, `providers.${provider}.max_running`, DEFAULT_PROVIDER_MAX_RUNNING[provider]),
187
+ max_queued: positiveInt(raw.max_queued, `providers.${provider}.max_queued`, DEFAULT_MAX_QUEUED),
188
+ default_account: defaultAccount,
189
+ accounts,
190
+ };
191
+ }
192
+ function parseThrottle(raw) {
193
+ const obj = raw === undefined ? {} : raw;
194
+ if (!isPlainObject(obj)) {
195
+ throw new LanesConfigError("invalid_config", "throttle must be an object");
196
+ }
197
+ const pause = unitInterval(obj.cpu_busy_pause, "throttle.cpu_busy_pause", DEFAULT_CPU_BUSY_PAUSE);
198
+ const resume = unitInterval(obj.cpu_busy_resume, "throttle.cpu_busy_resume", DEFAULT_CPU_BUSY_RESUME);
199
+ if (resume > pause) {
200
+ throw new LanesConfigError("invalid_config", "throttle.cpu_busy_resume must be <= throttle.cpu_busy_pause", { cpu_busy_pause: pause, cpu_busy_resume: resume });
201
+ }
202
+ let minMem = DEFAULT_MIN_AVAILABLE_MEM_MB;
203
+ if (obj.min_available_mem_mb !== undefined) {
204
+ if (typeof obj.min_available_mem_mb !== "number" ||
205
+ !Number.isFinite(obj.min_available_mem_mb) ||
206
+ obj.min_available_mem_mb < 0) {
207
+ throw new LanesConfigError("invalid_config", "throttle.min_available_mem_mb must be a non-negative number", { value: obj.min_available_mem_mb });
208
+ }
209
+ minMem = obj.min_available_mem_mb;
210
+ }
211
+ return {
212
+ cpu_busy_pause: pause,
213
+ cpu_busy_resume: resume,
214
+ min_available_mem_mb: minMem,
215
+ };
216
+ }
217
+ function parseLiveness(raw) {
218
+ const obj = raw === undefined ? {} : raw;
219
+ if (!isPlainObject(obj)) {
220
+ throw new LanesConfigError("invalid_config", "liveness must be an object");
221
+ }
222
+ return {
223
+ live_within_ms: positiveInt(obj.live_within_ms, "liveness.live_within_ms", DEFAULT_LIVE_WITHIN_MS),
224
+ turn_start_lost_ms: positiveInt(obj.turn_start_lost_ms, "liveness.turn_start_lost_ms", DEFAULT_TURN_START_LOST_MS),
225
+ silent_lost_ms: positiveInt(obj.silent_lost_ms, "liveness.silent_lost_ms", DEFAULT_SILENT_LOST_MS),
226
+ };
227
+ }
228
+ export function documentedDefaultConfig(deps = defaultDeps()) {
229
+ const providers = {};
230
+ for (const provider of LANE_PROVIDERS) {
231
+ providers[provider] = {
232
+ max_running: DEFAULT_PROVIDER_MAX_RUNNING[provider],
233
+ max_queued: DEFAULT_MAX_QUEUED,
234
+ default_account: "a",
235
+ accounts: defaultAccountMap(provider, deps),
236
+ };
237
+ }
238
+ return {
239
+ schema_version: CONFIG_SCHEMA_VERSION,
240
+ providers,
241
+ throttle: {
242
+ cpu_busy_pause: DEFAULT_CPU_BUSY_PAUSE,
243
+ cpu_busy_resume: DEFAULT_CPU_BUSY_RESUME,
244
+ min_available_mem_mb: DEFAULT_MIN_AVAILABLE_MEM_MB,
245
+ },
246
+ liveness: { ...DEFAULT_LIVENESS_WINDOWS },
247
+ };
248
+ }
249
+ export function parseLanesConfig(raw, deps = defaultDeps()) {
250
+ if (!isPlainObject(raw)) {
251
+ throw new LanesConfigError("invalid_config", "lanes config must be a JSON object");
252
+ }
253
+ if (typeof raw.schema_version === "string") {
254
+ parseMajorSchema(raw.schema_version, "hq-lanes-config", 1);
255
+ }
256
+ const providersRaw = raw.providers;
257
+ if (providersRaw !== undefined && !isPlainObject(providersRaw)) {
258
+ throw new LanesConfigError("invalid_config", "providers must be an object");
259
+ }
260
+ if (isPlainObject(providersRaw)) {
261
+ const unknown = Object.keys(providersRaw).filter((key) => !isLaneProvider(key));
262
+ if (unknown.length > 0) {
263
+ throw new LanesConfigError("unknown_provider", `Unknown provider(s) ${unknown.join(", ")}. Accepted: ${LANE_PROVIDERS.join(", ")}`, { accepted: [...LANE_PROVIDERS], value: unknown });
264
+ }
265
+ }
266
+ const providers = {};
267
+ for (const provider of LANE_PROVIDERS) {
268
+ const entry = isPlainObject(providersRaw) ? providersRaw[provider] : undefined;
269
+ providers[provider] =
270
+ entry === undefined
271
+ ? documentedDefaultConfig(deps).providers[provider]
272
+ : parseProvider(provider, entry, deps);
273
+ }
274
+ return {
275
+ schema_version: typeof raw.schema_version === "string"
276
+ ? raw.schema_version
277
+ : CONFIG_SCHEMA_VERSION,
278
+ providers,
279
+ throttle: parseThrottle(raw.throttle),
280
+ liveness: parseLiveness(raw.liveness),
281
+ };
282
+ }
283
+ export function lanesConfigPath(hqRoot) {
284
+ return path.join(hqRoot, LANES_CONFIG_REL);
285
+ }
286
+ /**
287
+ * Load operator config. Missing file → documented defaults. Unreadable or
288
+ * invalid JSON is an error (never a silent default).
289
+ */
290
+ export function loadLanesConfig(hqRoot, deps = defaultDeps()) {
291
+ const filePath = lanesConfigPath(hqRoot);
292
+ if (!deps.existsSync(filePath)) {
293
+ return documentedDefaultConfig(deps);
294
+ }
295
+ let text;
296
+ try {
297
+ text = deps.readFileSync(filePath, "utf8");
298
+ }
299
+ catch (err) {
300
+ throw new LanesConfigError("config_unreadable", `Failed to read ${filePath}: ${err instanceof Error ? err.message : String(err)}`, { path: filePath });
301
+ }
302
+ let parsed;
303
+ try {
304
+ parsed = JSON.parse(text);
305
+ }
306
+ catch (err) {
307
+ throw new LanesConfigError("invalid_config", `Invalid JSON in ${filePath}: ${err instanceof Error ? err.message : String(err)}`, { path: filePath });
308
+ }
309
+ return parseLanesConfig(parsed, deps);
310
+ }
311
+ function defaultWriteDeps() {
312
+ return {
313
+ mkdirSync: fs.mkdirSync.bind(fs),
314
+ writeFileSync: fs.writeFileSync.bind(fs),
315
+ renameSync: fs.renameSync.bind(fs),
316
+ unlinkSync: fs.unlinkSync.bind(fs),
317
+ };
318
+ }
319
+ /** Persist operator config. Homes are stored as already-canonicalised paths. */
320
+ export function writeLanesConfig(hqRoot, config, deps = defaultWriteDeps()) {
321
+ const filePath = lanesConfigPath(hqRoot);
322
+ const dir = path.dirname(filePath);
323
+ deps.mkdirSync(dir, { recursive: true });
324
+ const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
325
+ try {
326
+ deps.writeFileSync(tmp, `${JSON.stringify(config, null, 2)}\n`, {
327
+ encoding: "utf8",
328
+ });
329
+ deps.renameSync(tmp, filePath);
330
+ }
331
+ catch (err) {
332
+ try {
333
+ deps.unlinkSync(tmp);
334
+ }
335
+ catch (unlinkErr) {
336
+ const code = unlinkErr.code;
337
+ if (code !== "ENOENT") {
338
+ process.stderr.write(`lanes config: unlink tmp failed for ${tmp}: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}\n`);
339
+ }
340
+ }
341
+ throw err;
342
+ }
343
+ }
344
+ /** Account running cap, defaulting to the provider ceiling when unset. */
345
+ export function accountMaxRunning(provider, accountId) {
346
+ const account = provider.accounts[accountId];
347
+ if (account?.max_running !== undefined)
348
+ return account.max_running;
349
+ return provider.max_running;
350
+ }
351
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Distinguishable errors for the lanes library. `error` matches the spec's
3
+ * JSON `error` field so the command layer can print `err.toJSON()` as-is.
4
+ */
5
+ export declare class LanesError extends Error {
6
+ readonly error: string;
7
+ readonly details: Record<string, unknown>;
8
+ constructor(error: string, message: string, details?: Record<string, unknown>);
9
+ toJSON(): Record<string, unknown>;
10
+ }
11
+ export declare function isLanesError(err: unknown): err is LanesError;
12
+ /** Compare-and-set miss. Callers retry after re-reading the record. */
13
+ export declare class LaneVersionConflict extends LanesError {
14
+ readonly expectedVersion: number;
15
+ readonly actualVersion: number;
16
+ readonly recordPath: string;
17
+ constructor(opts: {
18
+ path: string;
19
+ expectedVersion: number;
20
+ actualVersion: number;
21
+ });
22
+ }
23
+ export declare function isLaneVersionConflict(err: unknown): err is LaneVersionConflict;
24
+ export declare class LaneNotFoundError extends LanesError {
25
+ constructor(kind: "lane" | "question", id: string);
26
+ }
27
+ export declare class LaneSchemaError extends LanesError {
28
+ constructor(message: string, details?: Record<string, unknown>);
29
+ }
30
+ export declare class LanesConfigError extends LanesError {
31
+ constructor(error: string, message: string, details?: Record<string, unknown>);
32
+ }
33
+ export declare class AccountResolutionError extends LanesError {
34
+ constructor(error: "unknown_account" | "account_disabled" | "no_enabled_account", message: string, details?: Record<string, unknown>);
35
+ }
36
+ export declare class WorkerResolutionError extends LanesError {
37
+ constructor(error: "worker_not_found" | "worker_tenancy_unresolved" | "worker_not_available" | "worker_not_active" | "personal_worker_unindexed" | "worker_registry_unreadable", message: string, details?: Record<string, unknown>);
38
+ }
39
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Distinguishable errors for the lanes library. `error` matches the spec's
3
+ * JSON `error` field so the command layer can print `err.toJSON()` as-is.
4
+ */
5
+ export class LanesError extends Error {
6
+ error;
7
+ details;
8
+ constructor(error, message, details = {}) {
9
+ super(message);
10
+ this.name = "LanesError";
11
+ this.error = error;
12
+ this.details = details;
13
+ Object.setPrototypeOf(this, new.target.prototype);
14
+ }
15
+ toJSON() {
16
+ return { error: this.error, ...this.details };
17
+ }
18
+ }
19
+ export function isLanesError(err) {
20
+ return err instanceof LanesError;
21
+ }
22
+ /** Compare-and-set miss. Callers retry after re-reading the record. */
23
+ export class LaneVersionConflict extends LanesError {
24
+ expectedVersion;
25
+ actualVersion;
26
+ recordPath;
27
+ constructor(opts) {
28
+ super("version_conflict", `Lane record version conflict at ${opts.path}: expected ${opts.expectedVersion}, found ${opts.actualVersion}`, {
29
+ path: opts.path,
30
+ expected: opts.expectedVersion,
31
+ actual: opts.actualVersion,
32
+ });
33
+ this.name = "LaneVersionConflict";
34
+ this.expectedVersion = opts.expectedVersion;
35
+ this.actualVersion = opts.actualVersion;
36
+ this.recordPath = opts.path;
37
+ }
38
+ }
39
+ export function isLaneVersionConflict(err) {
40
+ return err instanceof LaneVersionConflict;
41
+ }
42
+ export class LaneNotFoundError extends LanesError {
43
+ constructor(kind, id) {
44
+ super(kind === "lane" ? "lane_not_found" : "question_not_found", `${kind} ${id} not found`, { [`${kind}_id`]: id });
45
+ this.name = "LaneNotFoundError";
46
+ }
47
+ }
48
+ export class LaneSchemaError extends LanesError {
49
+ constructor(message, details = {}) {
50
+ super("schema_error", message, details);
51
+ this.name = "LaneSchemaError";
52
+ }
53
+ }
54
+ export class LanesConfigError extends LanesError {
55
+ constructor(error, message, details = {}) {
56
+ super(error, message, details);
57
+ this.name = "LanesConfigError";
58
+ }
59
+ }
60
+ export class AccountResolutionError extends LanesError {
61
+ constructor(error, message, details = {}) {
62
+ super(error, message, details);
63
+ this.name = "AccountResolutionError";
64
+ }
65
+ }
66
+ export class WorkerResolutionError extends LanesError {
67
+ constructor(error, message, details = {}) {
68
+ super(error, message, details);
69
+ this.name = "WorkerResolutionError";
70
+ }
71
+ }
72
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Library layer for `hq lanes`. The command module should import from here.
3
+ */
4
+ export { AccountResolutionError, LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesConfigError, LanesError, WorkerResolutionError, isLaneVersionConflict, isLanesError, } from "./errors.js";
5
+ export { CONFIG_SCHEMA_VERSION, ENVELOPE_DECISIONS, ENVELOPE_SCHEMA_VERSION, HISTORY_CAP, HOST_KINDS, LANE_PROVIDERS, LANE_SCHEMA_VERSION, LANE_STATES, LIVENESS_STATES, QUESTION_BODY_STORES, QUESTION_SCHEMA_VERSION, QUESTION_STATUSES, QUEUED_REASONS, RAISED_VIA, ROUTING_REASONS, SENIOR_KINDS, STORY_STATUSES, TERMINAL_LANE_STATES, isEnvelopeDecision, isHostKind, isLaneProvider, isLaneState, isLivenessState, isQuestionStatus, isSeniorKind, isTerminalLaneState, parseLaneEnvelope, parseLaneProvider, parseLaneState, parseLivenessState, parseMajorSchema, parseQuestionStatus, parseSeniorKind, } from "./types.js";
6
+ export type { AccountConfig, AttributionHistoryEntry, EnvelopeDecision, EnvelopeQuestion, HostKind, LaneCompanyRef, LaneEnvelope, LaneHost, LaneProvider, LaneRecord, LaneState, LanesConfig, LastEnvelopeRef, LivenessState, LivenessWindows, ProviderConfig, QuestionAnswer, QuestionBodyRef, QuestionRecord, QuestionStatus, QueuedReason, RaisedVia, RoutingHistoryEntry, RoutingReason, SeniorHistoryEntry, SeniorKind, SeniorRef, StoryStatus, TerminalLaneState, ThrottleConfig, } from "./types.js";
7
+ export { DEFAULT_ACCOUNT_HOMES, DEFAULT_CPU_BUSY_PAUSE, DEFAULT_CPU_BUSY_RESUME, DEFAULT_LIVE_WITHIN_MS, DEFAULT_LIVENESS_WINDOWS, DEFAULT_MAX_QUEUED, DEFAULT_MIN_AVAILABLE_MEM_MB, DEFAULT_PROVIDER_MAX_RUNNING, DEFAULT_SILENT_LOST_MS, DEFAULT_TURN_START_LOST_MS, LANES_CONFIG_REL, accountMaxRunning, canonicalizeHome, documentedDefaultConfig, expandHome, lanesConfigPath, loadLanesConfig, parseLanesConfig, writeLanesConfig, } from "./config.js";
8
+ export type { ConfigLoadDeps, ConfigWriteDeps } from "./config.js";
9
+ export { LANES_STORE_REL, LANE_RECORDS_DIR, PENDING_INDEX_DIR, QUESTION_RECORDS_DIR, appendCappedHistory, appendLaneAttribution, appendLaneSeniorHistory, appendQuestionRouting, createLane, createQuestion, isConflictSidecarName, laneRecordPath, lanesStoreRoot, listLaneIds, listQuestionIds, mergeLaneRecords, mergeQuestionRecords, mintLaneId, mintQuestionId, parseLaneRecord, parseQuestionRecord, pendingMarkerPath, questionRecordPath, readLane, readQuestion, resolveStoreHqRoot, sanitizeHostComponent, tryReadLane, tryReadQuestion, updateLane, updateQuestion, } from "./store.js";
10
+ export type { StoreDeps } from "./store.js";
11
+ export { CPU_SAMPLE_MS, LANES_CPU_BUSY_OVERRIDE, LANES_CPU_CHECK, PROC_MEMINFO, STARVATION_MAX_DEFERRALS, STARVATION_MAX_MS, applyThrottleDecision, busyFractionFromDeltas, cpuTimesSnapshot, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, parseMemAvailableKb, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyFraction, starvationBoundReached, throttleStatePath, writeThrottleState, } from "./throttle.js";
12
+ export type { CpuTimes, ThrottleAdmitReason, ThrottleDecision, ThrottleDeferReason, ThrottleDeps, ThrottleState, } from "./throttle.js";
13
+ export { AUTH_JSON_NAME, PROVIDER_HOME_ENV, accountEnv, accountStatus, authJsonPath, listAccountStatuses, resolveAccount, } from "./accounts.js";
14
+ export type { AccountStatus, ResolvedAccount } from "./accounts.js";
15
+ export { LIVE_WITHIN_MS, LIVENESS_EVENT_KINDS, SILENT_LOST_MS, TURN_START_LOST_MS, buildLivenessIndexFromSpool, classifySessionLiveness, defaultLivenessDeps, defaultSpoolPath, ingestSpoolLine, listSessionLiveness, livenessIndexPath, loadLivenessIndex, readLivenessIndex, rebuildLivenessIndex, sessionLiveness, } from "./liveness.js";
16
+ export type { LivenessDeps, LivenessEventKind, LivenessIndex, SessionIndexEntry, } from "./liveness.js";
17
+ export { WORKER_REGISTRY_REL, personalWorkerDir, personalWorkerExists, readWorkerRegistryFile, registryPath, resolveContextBasePaths, resolveLaneWorker, resolveSkillFiles, tenancyFromRegistryPath, } from "./registry.js";
18
+ export type { RegistryWorkerEntry, ResolvedLaneWorker, WorkerTenancy, WorkerYaml, } from "./registry.js";
19
+ //# sourceMappingURL=index.d.ts.map