@indigoai-us/hq-cli 5.345.123 → 5.345.125

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 (44) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/assets/scaffold/core/scripts/work-mesh-project-registration-offer.sh +77 -0
  3. package/assets/scaffold/core/scripts/workflow-gate.sh +247 -0
  4. package/dist/command-catalog.generated.d.ts +239 -0
  5. package/dist/command-catalog.generated.js +291 -0
  6. package/dist/commands/core-bucket-c-work-mesh-offer-native.d.ts +7 -0
  7. package/dist/commands/core-bucket-c-work-mesh-offer-native.js +147 -0
  8. package/dist/commands/core-bucket-c-workflow-gate-fast.d.ts +3 -0
  9. package/dist/commands/core-bucket-c-workflow-gate-fast.js +17 -0
  10. package/dist/commands/core-bucket-c-workflow-gate-native.d.ts +7 -0
  11. package/dist/commands/core-bucket-c-workflow-gate-native.js +250 -0
  12. package/dist/commands/core.js +12 -0
  13. package/dist/commands/lanes-accounts.js +22 -3
  14. package/dist/commands/lanes.js +25 -32
  15. package/dist/commands/mesh.js +457 -16
  16. package/dist/fast-scaffold.js +26 -0
  17. package/dist/lib/lanes/accounts.d.ts +48 -2
  18. package/dist/lib/lanes/accounts.js +160 -9
  19. package/dist/lib/lanes/index.d.ts +2 -2
  20. package/dist/lib/lanes/index.js +1 -1
  21. package/dist/lib/lanes/message-replay.js +6 -1
  22. package/dist/lib/lanes/provider-rejection.d.ts +2 -6
  23. package/dist/lib/lanes/provider-rejection.js +19 -0
  24. package/dist/lib/lanes/quota-notice.d.ts +24 -0
  25. package/dist/lib/lanes/quota-notice.js +61 -0
  26. package/dist/lib/lanes/quota.d.ts +55 -15
  27. package/dist/lib/lanes/quota.js +231 -49
  28. package/dist/lib/lanes/reconcile.js +17 -1
  29. package/dist/lib/lanes/session-relink.d.ts +2 -0
  30. package/dist/lib/lanes/session-relink.js +98 -4
  31. package/dist/lib/lanes/spawn.d.ts +1 -1
  32. package/dist/lib/lanes/spawn.js +58 -12
  33. package/dist/lib/lanes/types.d.ts +5 -0
  34. package/dist/lib/lanes/types.js +1 -1
  35. package/dist/lib/lanes/watcher.js +13 -1
  36. package/dist/lib/mesh/api.d.ts +2 -0
  37. package/dist/lib/mesh/api.js +3 -0
  38. package/dist/lib/mesh/client.d.ts +6 -1
  39. package/dist/lib/mesh/client.js +18 -5
  40. package/dist/lib/mesh/team-coordination.d.ts +106 -0
  41. package/dist/lib/mesh/team-coordination.js +297 -0
  42. package/dist/lib/work-context/auto-bind.d.ts +75 -0
  43. package/dist/lib/work-context/auto-bind.js +245 -0
  44. package/package.json +1 -1
@@ -0,0 +1,250 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ const ok = (stdout = "") => ({ code: 0, stdout, stderr: "" });
4
+ const fail = (message, code = 1) => ({ code, stdout: "", stderr: `${message}\n` });
5
+ const validId = /^[a-z0-9._-]+$/;
6
+ const USAGE = `workflow-gate.sh — inspect and answer human gates opened by codex-workflow.mjs gate().
7
+
8
+ A gate is a self-contained pending question file written by a paused workflow
9
+ run; writing a matching answered/<id>.json resumes that run in place. Answers
10
+ are durable: a re-launched run returns them instantly instead of re-asking
11
+ the human, so never delete answered/ files while their project is live.
12
+
13
+ Usage:
14
+ workflow-gate.sh list
15
+ workflow-gate.sh show <id>
16
+ workflow-gate.sh answer <id> <choice|N> [--notes "..."] [--by name] [--force] [--freeform]
17
+ <choice|N> an option label, or a 1-based option index
18
+ --notes free-text note stored alongside the choice
19
+ --by who answered (recorded as answered_by)
20
+ --force pre-answer a gate that has not opened yet, or overwrite an
21
+ existing answer
22
+ --freeform accept a choice that is not one of the gate's options
23
+ workflow-gate.sh wait-pending [--timeout secs]
24
+ Blocks until any pending gate exists (exit 0) or the timeout elapses
25
+ (exit 1). Monitor-friendly wake condition for watching sessions.
26
+ workflow-gate.sh watch-run <run-dir> [--timeout secs]
27
+ Run-scoped wake condition for the session that launched a workflow.
28
+ Blocks until a gate belonging to THAT run opens (prints its id, exit 0),
29
+ the run finishes (exit 3), or --timeout elapses (exit 1). Match is on
30
+ the run_dir every gate carries, which is the --run-dir the launching
31
+ session chose — no id parsing, no env coupling, no startup race.
32
+ workflow-gate.sh clear <id>
33
+ Remove both the pending and answered files for a gate id.
34
+
35
+ Env: HQ_WORKFLOW_GATES_DIR overrides the gates root (default
36
+ <hq-root>/workspace/gates); legacy CODEX_WORKFLOW_GATES_DIR is honored.
37
+ `;
38
+ function gateId(id) {
39
+ return !validId.test(id) ? fail(`workflow-gate: invalid gate id "${id}" — ids are lowercase [a-z0-9._-]`) : undefined;
40
+ }
41
+ function readJson(file) {
42
+ try {
43
+ return JSON.parse(fs.readFileSync(file, "utf8"));
44
+ }
45
+ catch {
46
+ return undefined;
47
+ }
48
+ }
49
+ function listFiles(dir) {
50
+ try {
51
+ return fs.readdirSync(dir).filter((entry) => entry.endsWith(".json")).sort().map((entry) => path.join(dir, entry));
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ }
57
+ function parseTimeout(args) {
58
+ let timeout;
59
+ for (let index = 0; index < args.length;) {
60
+ if (args[index] !== "--timeout")
61
+ return { error: `workflow-gate: unknown flag: ${args[index]}` };
62
+ if (index + 1 >= args.length)
63
+ return { error: "workflow-gate: --timeout needs a value" };
64
+ const raw = args[index + 1];
65
+ if (!/^\d+$/.test(raw))
66
+ return { error: "workflow-gate: --timeout must be a whole number of seconds" };
67
+ timeout = Number(raw);
68
+ index += 2;
69
+ }
70
+ return { ...(timeout === undefined ? {} : { timeout }) };
71
+ }
72
+ const waitSecond = () => new Promise((resolve) => setTimeout(resolve, 1000));
73
+ export async function runWorkflowGate(args, root, env = process.env, now = Date.now, sleep = waitSecond) {
74
+ const gatesDir = env.HQ_WORKFLOW_GATES_DIR || env.CODEX_WORKFLOW_GATES_DIR || path.join(root, "workspace/gates");
75
+ const pending = path.join(gatesDir, "pending");
76
+ const answered = path.join(gatesDir, "answered");
77
+ const [command, ...rest] = args;
78
+ if (!command)
79
+ return { code: 1, stdout: `${USAGE}\n`, stderr: "" };
80
+ if (command === "-h" || command === "--help" || command === "help")
81
+ return ok(`${USAGE}\n`);
82
+ if (command === "list") {
83
+ const files = listFiles(pending);
84
+ if (!files.length)
85
+ return ok("no pending gates\n");
86
+ return ok(files.map((file) => {
87
+ const gate = readJson(file);
88
+ if (!gate || typeof gate !== "object")
89
+ return `<unreadable> ${file}`;
90
+ const created = Date.parse(String(gate.created_at || 0));
91
+ const ageMin = Math.max(0, Math.round((now() - created) / 60000));
92
+ const options = Array.isArray(gate.options) ? gate.options.map((option, index) => `${index + 1}=${String(option?.label)}`).join(" ") : "";
93
+ return `${String(gate.id)} (${ageMin}m) ${String(gate.question)}${options ? ` [${options}]` : ""}`;
94
+ }).join("\n") + "\n");
95
+ }
96
+ if (command === "show") {
97
+ const id = rest[0] ?? "";
98
+ if (!id)
99
+ return fail("workflow-gate: usage: show <id>");
100
+ const invalid = gateId(id);
101
+ if (invalid)
102
+ return invalid;
103
+ const file = [path.join(pending, `${id}.json`), path.join(answered, `${id}.json`)].find((candidate) => {
104
+ try {
105
+ return fs.statSync(candidate).isFile();
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ });
111
+ if (!file)
112
+ return fail(`workflow-gate: no gate "${id}" in pending or answered`);
113
+ try {
114
+ return ok(fs.readFileSync(file, "utf8"));
115
+ }
116
+ catch {
117
+ return fail(`workflow-gate: no gate "${id}" in pending or answered`);
118
+ }
119
+ }
120
+ if (command === "answer") {
121
+ const [id = "", rawChoice = "", ...flags] = rest;
122
+ if (!id || !rawChoice)
123
+ return fail("workflow-gate: usage: answer <id> <choice|N> [--notes ...] [--by name] [--force] [--freeform]");
124
+ const invalid = gateId(id);
125
+ if (invalid)
126
+ return invalid;
127
+ let notes = "";
128
+ let by = "";
129
+ let force = false;
130
+ let freeform = false;
131
+ for (let index = 0; index < flags.length;) {
132
+ const flag = flags[index];
133
+ if (flag === "--force") {
134
+ force = true;
135
+ index += 1;
136
+ }
137
+ else if (flag === "--freeform") {
138
+ freeform = true;
139
+ index += 1;
140
+ }
141
+ else if (flag === "--notes" || flag === "--by") {
142
+ if (index + 1 >= flags.length)
143
+ return fail(`workflow-gate: ${flag} needs a value`);
144
+ if (flag === "--notes")
145
+ notes = flags[index + 1];
146
+ else
147
+ by = flags[index + 1];
148
+ index += 2;
149
+ }
150
+ else
151
+ return fail(`workflow-gate: unknown flag: ${flag}`);
152
+ }
153
+ const pendingFile = path.join(pending, `${id}.json`);
154
+ const answerFile = path.join(answered, `${id}.json`);
155
+ const gate = readJson(pendingFile);
156
+ if (!gate && !force) {
157
+ const ids = listFiles(pending).map((file) => path.basename(file, ".json"));
158
+ return fail(`workflow-gate: no pending gate "${id}"${ids.length ? ` — pending: ${ids.join(", ")}` : " — nothing is pending"} (use --force to pre-answer)`);
159
+ }
160
+ let choice = rawChoice;
161
+ const options = gate && Array.isArray(gate.options) ? gate.options : [];
162
+ if (options.length) {
163
+ if (/^\d+$/.test(rawChoice)) {
164
+ const number = Number(rawChoice);
165
+ if (number >= 1 && number <= options.length)
166
+ choice = String(options[number - 1].label);
167
+ else
168
+ return fail(`workflow-gate: index ${number} is out of range 1..${options.length} for "${id}"`);
169
+ }
170
+ else {
171
+ const hit = options.find((option) => String(option.label).toLowerCase() === rawChoice.toLowerCase());
172
+ if (hit)
173
+ choice = String(hit.label);
174
+ else if (!freeform)
175
+ return fail(`workflow-gate: "${rawChoice}" is not an option for "${id}" — options: ${options.map((option) => option.label).join(", ")} (use --freeform to record it anyway)`);
176
+ }
177
+ }
178
+ if (fs.existsSync(answerFile) && !force)
179
+ return fail(`workflow-gate: gate "${id}" is already answered (${answerFile}) — use --force to overwrite`);
180
+ try {
181
+ fs.mkdirSync(answered, { recursive: true });
182
+ const temporary = `${answerFile}.tmp-${process.pid}`;
183
+ fs.writeFileSync(temporary, `${JSON.stringify({ id, choice, ...(notes ? { notes } : {}), ...(by ? { answered_by: by } : {}), answered_at: new Date(now()).toISOString() }, null, 2)}\n`);
184
+ fs.renameSync(temporary, answerFile);
185
+ try {
186
+ fs.rmSync(pendingFile, { force: true });
187
+ }
188
+ catch { /* shell is best-effort here */ }
189
+ }
190
+ catch (error) {
191
+ return fail(`workflow-gate: ${String(error.message)}`);
192
+ }
193
+ return ok(`answered ${id} -> ${choice}\n`);
194
+ }
195
+ if (command === "wait-pending" || command === "watch-run") {
196
+ let runDir = "";
197
+ let optionArgs = rest;
198
+ if (command === "watch-run") {
199
+ runDir = rest[0] ?? "";
200
+ if (!runDir || runDir.startsWith("-"))
201
+ return fail("workflow-gate: usage: watch-run <run-dir> [--timeout secs]");
202
+ optionArgs = rest.slice(1);
203
+ }
204
+ const parsed = parseTimeout(optionArgs);
205
+ if (parsed.error)
206
+ return fail(parsed.error);
207
+ let elapsed = 0;
208
+ for (;;) {
209
+ const files = listFiles(pending);
210
+ if (command === "wait-pending" && files.length)
211
+ return ok();
212
+ if (command === "watch-run") {
213
+ const wanted = path.resolve(runDir);
214
+ for (const file of files) {
215
+ const gate = readJson(file);
216
+ if (gate?.run_dir && path.resolve(String(gate.run_dir)) === wanted)
217
+ return ok(`${String(gate.id || path.basename(file, ".json"))}\n`);
218
+ }
219
+ const journal = path.join(runDir, "journal.jsonl");
220
+ try {
221
+ if (fs.readFileSync(journal, "utf8").includes('"event":"run-done"'))
222
+ return { code: 3, stdout: "", stderr: "" };
223
+ }
224
+ catch { /* not finished */ }
225
+ }
226
+ if (parsed.timeout !== undefined && elapsed >= parsed.timeout)
227
+ return { code: 1, stdout: "", stderr: "" };
228
+ await sleep();
229
+ elapsed += 1;
230
+ }
231
+ }
232
+ if (command === "clear") {
233
+ const id = rest[0] ?? "";
234
+ if (!id)
235
+ return fail("workflow-gate: usage: clear <id>");
236
+ const invalid = gateId(id);
237
+ if (invalid)
238
+ return invalid;
239
+ try {
240
+ fs.rmSync(path.join(pending, `${id}.json`), { force: true });
241
+ fs.rmSync(path.join(answered, `${id}.json`), { force: true });
242
+ }
243
+ catch (error) {
244
+ return fail(`workflow-gate: ${String(error.message)}`);
245
+ }
246
+ return ok(`cleared ${id}\n`);
247
+ }
248
+ return fail(`workflow-gate: unknown command: ${command} (list | show | answer | wait-pending | watch-run | clear)`);
249
+ }
250
+ //# sourceMappingURL=core-bucket-c-workflow-gate-native.js.map
@@ -248,6 +248,18 @@ export const SCAFFOLD_COMMANDS = [
248
248
  root: "live",
249
249
  summary: "Check whether a company-scoped skill is installed",
250
250
  },
251
+ {
252
+ name: "work-mesh-project-registration-offer",
253
+ asset: "core/scripts/work-mesh-project-registration-offer.sh",
254
+ root: "live",
255
+ summary: "Check and record the work-mesh project registration offer",
256
+ },
257
+ {
258
+ name: "workflow-gate",
259
+ asset: "core/scripts/workflow-gate.sh",
260
+ root: "live",
261
+ summary: "Inspect, answer, and wait for workflow gates",
262
+ },
251
263
  // Session/harness plumbing relocated out of the HQ tree so a single published
252
264
  // CLI owns it. Both are `root: "live"`: they read and write the live HQ tree
253
265
  // (workspace/sessions, companies/, workspace/orchestrator/hook-state), so the
@@ -4,7 +4,7 @@ import { buildOutpostBalanceMoveCommand, dispatchErrorMessage } from "../lib/lan
4
4
  import { BALANCE_MOVABLE_STATES, accountLoadsForReport, planClaudeBalanceMoves, selectClaudeAccount } from "../lib/lanes/claude-balance.js";
5
5
  import { CompanySelectionError } from "../utils/company-selection-error.js";
6
6
  import { effectiveAccountHome } from "../lib/lanes/account-home.js";
7
- import { AccountResolutionError, DEFAULT_ACCOUNT_HOMES, LANE_PROVIDERS, PROVIDER_HOME_ENV, accountStatus, canonicalizeHome, installDrainHook, isLanesError, listAccountStatuses, loadLanesConfig, parseLaneProvider, readLane, writeLanesConfig } from "../lib/lanes/index.js";
7
+ import { AccountResolutionError, DEFAULT_ACCOUNT_HOMES, LANE_PROVIDERS, accountStatus, canonicalizeHome, claudeIdentityDiffers, installDrainHook, isLanesError, listAccountStatuses, loadLanesConfig, parseLaneProvider, probeInteractiveClaudeIdentity, readLane, withProviderHomeEnv, writeLanesConfig } from "../lib/lanes/index.js";
8
8
  import { assertUniqueAccountHomes, providerCompactAfterMs } from "../lib/lanes/config.js";
9
9
  import { resolveLaneEffort } from "../lib/lanes/effort.js";
10
10
  import { LanesError } from "../lib/lanes/errors.js";
@@ -113,9 +113,16 @@ export function createLaneAccountsHandlers(deps) {
113
113
  // model and effort are what a spawn on each account would start from
114
114
  // (spec: --model/--effort, then the account, then the provider).
115
115
  const quota = loadQuotaState(hqRoot);
116
+ // The login the interactive `claude` uses (no CLAUDE_CONFIG_DIR), so a
117
+ // lane account that resolves to a different keychain credential is
118
+ // visible here instead of only as a 429 on the wrong subscription.
119
+ const interactiveIdentity = providers.includes("claude") && ctx.probeProvider
120
+ ? probeInteractiveClaudeIdentity(ctx.probeProvider, ctx.env)
121
+ : null;
116
122
  const accounts = providers.flatMap((provider) => listAccountStatuses(provider, config, {
117
123
  existsSync: ctx.existsSync,
118
124
  hostname: ctx.hostname(),
125
+ homedir: ctx.homedir,
119
126
  probe: ctx.probeProvider,
120
127
  env: ctx.env,
121
128
  }).map((status) => {
@@ -129,6 +136,9 @@ export function createLaneAccountsHandlers(deps) {
129
136
  providerEffort: config.providers[provider].effort,
130
137
  model,
131
138
  });
139
+ const differs = provider === "claude"
140
+ ? claudeIdentityDiffers(status.identity, interactiveIdentity)
141
+ : null;
132
142
  return {
133
143
  ...status,
134
144
  effective_home: status.home,
@@ -136,6 +146,13 @@ export function createLaneAccountsHandlers(deps) {
136
146
  error: "home_missing",
137
147
  fix_command: `hq lanes accounts set-home ${provider} ${status.id} <host-home>`,
138
148
  } : {}),
149
+ ...(provider === "claude" ? {
150
+ identity: status.identity ?? null,
151
+ credential_matches_interactive: differs === null ? null : !differs,
152
+ ...(differs === true ? {
153
+ warning: `Lane credential ${status.identity?.email ?? status.identity?.org_id ?? "(unknown)"} differs from the interactive claude login ${interactiveIdentity?.email ?? interactiveIdentity?.org_id ?? "(unknown)"}; lanes on this account bill that other subscription`,
154
+ } : {}),
155
+ } : {}),
139
156
  model: accountCfg?.model ?? null,
140
157
  effort: accountCfg?.effort ?? null,
141
158
  effective_model: model ?? null,
@@ -154,6 +171,7 @@ export function createLaneAccountsHandlers(deps) {
154
171
  ok: true,
155
172
  action: "accounts.list",
156
173
  providers: providerDefaults,
174
+ ...(providers.includes("claude") ? { interactive_identity: interactiveIdentity } : {}),
157
175
  accounts,
158
176
  });
159
177
  }
@@ -387,15 +405,16 @@ export function createLaneAccountsHandlers(deps) {
387
405
  });
388
406
  const copied = defaultHome !== home ? copyHookRails(defaultHome, home) : railsPresent(home);
389
407
  installDrainHook(provider, home);
390
- const envVar = PROVIDER_HOME_ENV[provider];
391
408
  const login = VENDOR_LOGIN[provider];
392
409
  const loginArgs = provider === "codex" && opts.deviceAuth
393
410
  ? [...login.args, "--device-auth"]
394
411
  : login.args;
412
+ // Same rule as the worker spawn: the default Claude home logs in with
413
+ // CLAUDE_CONFIG_DIR unset so the credential matches the interactive one.
395
414
  const result = await ctx.spawnLogin({
396
415
  command: login.command,
397
416
  args: loginArgs,
398
- env: { ...ctx.env, [envVar]: home },
417
+ env: withProviderHomeEnv(ctx.env, provider, home, ctx.homedir),
399
418
  cwd: home,
400
419
  });
401
420
  if (result.status !== 0) {
@@ -2331,6 +2331,27 @@ function orchestratorEngine(hqRoot, senior, ctx) {
2331
2331
  return "claude";
2332
2332
  return undefined;
2333
2333
  }
2334
+ /**
2335
+ * Starts the worker lane a lead autopilot fill-grant wake asks for. Provider,
2336
+ * model and effort are left to `runCreate`, so the worker follows the
2337
+ * orchestrator engine (the lead lane provider, then the harness markers) and
2338
+ * that provider default model, and fails with `provider_required` when no
2339
+ * engine is known. There is no hard-coded engine.
2340
+ */
2341
+ function autopilotWorkerStarter(ctx) {
2342
+ return async ({ lead, story }) => {
2343
+ const workerCtx = { ...ctx, env: { ...ctx.env, HQ_LANE_ID: lead.lane_id } };
2344
+ const started = await runCreate(workerCtx, {
2345
+ company: lead.company?.slug,
2346
+ project: lead.project_id,
2347
+ story: story.id,
2348
+ briefFile: story.briefPath,
2349
+ senior: "lane:" + lead.lane_id,
2350
+ quiet: true,
2351
+ });
2352
+ return { lane_id: started.lane_id };
2353
+ };
2354
+ }
2334
2355
  function defaultSenior(ctx) {
2335
2356
  const parent = ctx.env.HQ_PARENT_SESSION_ID?.trim();
2336
2357
  if (parent)
@@ -7389,7 +7410,7 @@ async function prepareAccountMove(ctx, lane, input) {
7389
7410
  });
7390
7411
  const block = quotaBlockFor(loadQuotaState(hqRoot), lane.provider, target, model, now);
7391
7412
  if (block.blocked) {
7392
- throw new LanesError("account_quota_held", `Account ${target} for ${lane.provider} is held${block.quota_reset_at ? ` until ${block.quota_reset_at}` : ""}${block.marker ? ` (${block.marker})` : ""}`, { provider: lane.provider, account: target, quota_reset_at: block.quota_reset_at ?? null, marker: block.marker ?? null, model: model ?? null });
7413
+ throw new LanesError("account_quota_held", `Account ${target} for ${lane.provider} is held${block.quota_reset_at ? ` until ${block.quota_reset_at}` : ""}${block.marker ? ` (${block.marker})` : ""}. Clear the hold with: hq lanes accounts clear-quota ${lane.provider} ${target}`, { provider: lane.provider, account: target, quota_reset_at: block.quota_reset_at ?? null, marker: block.marker ?? null, model: model ?? null, clear_command: `hq lanes accounts clear-quota ${lane.provider} ${target}` });
7393
7414
  }
7394
7415
  if (model !== undefined) {
7395
7416
  validateLaneModel(lane.provider, model, targetHome, {
@@ -7407,7 +7428,7 @@ async function prepareAccountMove(ctx, lane, input) {
7407
7428
  const copy = ensureSessionHome({
7408
7429
  provider: lane.provider,
7409
7430
  sessionId,
7410
- cwd: hqRoot,
7431
+ cwd: lane.working_directory_at_spawn ?? hqRoot,
7411
7432
  targetHome,
7412
7433
  registeredHomes: Object.values(providerCfg.accounts).map((account) => effectiveAccountHome(account, ctx.hostname()).home),
7413
7434
  providerHomeAtSpawn: lane.provider_home_at_spawn ?? sourceHome,
@@ -9399,21 +9420,7 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
9399
9420
  dryRun: Boolean(opts.dryRun),
9400
9421
  readFlag: readLanesRegistryFlag,
9401
9422
  keepAliveResumedLeads: new Set(summary.resumed.filter((entry) => entry.reason === "keep_alive").map((entry) => entry.id)),
9402
- startWorker: async ({ lead, story }) => {
9403
- const workerCtx = { ...ctx, env: { ...ctx.env, HQ_LANE_ID: lead.lane_id } };
9404
- const started = await runCreate(workerCtx, {
9405
- company: lead.company?.slug,
9406
- project: lead.project_id,
9407
- story: story.id,
9408
- briefFile: story.briefPath,
9409
- provider: "codex",
9410
- model: "gpt-6-luna",
9411
- effort: "high",
9412
- senior: `lane:${lead.lane_id}`,
9413
- quiet: true,
9414
- });
9415
- return { lane_id: started.lane_id };
9416
- },
9423
+ startWorker: autopilotWorkerStarter(ctx),
9417
9424
  });
9418
9425
  }
9419
9426
  catch (error) {
@@ -10446,21 +10453,7 @@ export function registerLanesCommand(parent, deps = {}) {
10446
10453
  now: ctx.now(),
10447
10454
  dryRun: Boolean(opts.dryRun),
10448
10455
  readFlag: readLanesRegistryFlag,
10449
- startWorker: async ({ lead, story }) => {
10450
- const workerCtx = { ...ctx, env: { ...ctx.env, HQ_LANE_ID: lead.lane_id } };
10451
- const started = await runCreate(workerCtx, {
10452
- company: lead.company?.slug,
10453
- project: lead.project_id,
10454
- story: story.id,
10455
- briefFile: story.briefPath,
10456
- provider: "codex",
10457
- model: "gpt-6-luna",
10458
- effort: "high",
10459
- senior: `lane:${lead.lane_id}`,
10460
- quiet: true,
10461
- });
10462
- return { lane_id: started.lane_id };
10463
- },
10456
+ startWorker: autopilotWorkerStarter(ctx),
10464
10457
  });
10465
10458
  printJson(result);
10466
10459
  }));