@indigoai-us/hq-cli 5.159.0 → 5.160.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 CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.160.0] — 2026-09-24
6
+
7
+ ### Added
8
+
9
+ - Worker profiles can deliver validated policy files through an installed HQ
10
+ hook or include them in the prompt when the hook is unavailable. Resume and
11
+ message can attach a profile, and resume can attach a mesh company. Bot-owned
12
+ Claude profiles may rely on provider configuration for optional model,
13
+ account, effort, and fast-mode values.
14
+
15
+ - Added `hq agents login-code`, which submits a Claude subscription code from
16
+ an argument or stdin. Provisioning help shows the Agents v2 command and
17
+ account requirement. Claude subscription creates print the console URL and
18
+ login-code command.
19
+
20
+ ### Fixed
21
+
22
+ - If a Codex or Claude worker exits without writing this run's envelope, hq lanes
23
+ gives its session one resume turn to write the expected file. The watcher and
24
+ dead-host recovery ignore earlier envelopes. If the repair still writes no
25
+ valid envelope, the lane remains interrupted.
26
+
5
27
  ## [5.159.0] — 2026-09-24
6
28
 
7
29
  ### Fixed
@@ -3933,10 +3933,10 @@ export declare const COMMAND_CATALOG: readonly [{
3933
3933
  readonly description: "Agent slug (defaults to a slug of <name>)";
3934
3934
  }, {
3935
3935
  readonly flags: "--provider <provider>";
3936
- readonly description: "Runtime: agents-v2 (recommended) | codex | grok | claude (legacy). A --model selects agents-v2 by default";
3936
+ readonly description: "Runtime: agents-v2 (recommended) | codex | grok | claude (accepted for script compatibility; use agents-v2 for new Claude agents)";
3937
3937
  }, {
3938
3938
  readonly flags: "--model <model>";
3939
- readonly description: "Brain model, for example gpt-5.5 or grok-4.6";
3939
+ readonly description: "Brain model, for example claude-opus-5-5, gpt-5.5, or grok-4.6";
3940
3940
  }, {
3941
3941
  readonly flags: "--auth-mode <mode>";
3942
3942
  readonly description: "Auth: subscription | apiKey (default subscription)";
@@ -3969,6 +3969,23 @@ export declare const COMMAND_CATALOG: readonly [{
3969
3969
  readonly description: "Emit machine-readable output and never open a browser";
3970
3970
  }];
3971
3971
  readonly subcommands: readonly [];
3972
+ }, {
3973
+ readonly name: "login-code";
3974
+ readonly description: "Submit a Claude subscription sign-in code for an agent";
3975
+ readonly aliases: readonly [];
3976
+ readonly hidden: false;
3977
+ readonly usage: "[options] <agentUid> [code]";
3978
+ readonly arguments: readonly [{
3979
+ readonly name: "agentUid";
3980
+ readonly required: true;
3981
+ readonly variadic: false;
3982
+ }, {
3983
+ readonly name: "code";
3984
+ readonly required: false;
3985
+ readonly variadic: false;
3986
+ }];
3987
+ readonly options: readonly [];
3988
+ readonly subcommands: readonly [];
3972
3989
  }, {
3973
3990
  readonly name: "list";
3974
3991
  readonly description: "List the company's agents";
@@ -6784,7 +6801,7 @@ export declare const COMMAND_CATALOG: readonly [{
6784
6801
  readonly description: "Path to the lane brief";
6785
6802
  }, {
6786
6803
  readonly flags: "--worker <name>";
6787
- readonly description: "HQ worker id from the registry";
6804
+ readonly description: "HQ worker or reusable profile id";
6788
6805
  }, {
6789
6806
  readonly flags: "--provider <name>";
6790
6807
  readonly description: "claude | codex | grok";
@@ -7166,6 +7183,9 @@ export declare const COMMAND_CATALOG: readonly [{
7166
7183
  }, {
7167
7184
  readonly flags: "--model <id>";
7168
7185
  readonly description: "Model for the moved lane (with --account only)";
7186
+ }, {
7187
+ readonly flags: "--worker <profile>";
7188
+ readonly description: "Attach a personal worker profile when this message resumes the lane";
7169
7189
  }, {
7170
7190
  readonly flags: "--no-compact";
7171
7191
  readonly description: "Skip the resume-time compaction check for this message";
@@ -7200,6 +7220,12 @@ export declare const COMMAND_CATALOG: readonly [{
7200
7220
  }, {
7201
7221
  readonly flags: "--model <id>";
7202
7222
  readonly description: "Model for the moved lane (with --account only)";
7223
+ }, {
7224
+ readonly flags: "--worker <profile>";
7225
+ readonly description: "Attach a personal worker profile for the resumed turn";
7226
+ }, {
7227
+ readonly flags: "--mesh-company <slug>";
7228
+ readonly description: "Attach Work Mesh attribution after verifying company membership";
7203
7229
  }, {
7204
7230
  readonly flags: "--no-compact";
7205
7231
  readonly description: "Skip the resume-time compaction check for this resume";
@@ -5084,11 +5084,11 @@ export const COMMAND_CATALOG = [
5084
5084
  },
5085
5085
  {
5086
5086
  "flags": "--provider <provider>",
5087
- "description": "Runtime: agents-v2 (recommended) | codex | grok | claude (legacy). A --model selects agents-v2 by default"
5087
+ "description": "Runtime: agents-v2 (recommended) | codex | grok | claude (accepted for script compatibility; use agents-v2 for new Claude agents)"
5088
5088
  },
5089
5089
  {
5090
5090
  "flags": "--model <model>",
5091
- "description": "Brain model, for example gpt-5.5 or grok-4.6"
5091
+ "description": "Brain model, for example claude-opus-5-5, gpt-5.5, or grok-4.6"
5092
5092
  },
5093
5093
  {
5094
5094
  "flags": "--auth-mode <mode>",
@@ -5133,6 +5133,27 @@ export const COMMAND_CATALOG = [
5133
5133
  ],
5134
5134
  "subcommands": []
5135
5135
  },
5136
+ {
5137
+ "name": "login-code",
5138
+ "description": "Submit a Claude subscription sign-in code for an agent",
5139
+ "aliases": [],
5140
+ "hidden": false,
5141
+ "usage": "[options] <agentUid> [code]",
5142
+ "arguments": [
5143
+ {
5144
+ "name": "agentUid",
5145
+ "required": true,
5146
+ "variadic": false
5147
+ },
5148
+ {
5149
+ "name": "code",
5150
+ "required": false,
5151
+ "variadic": false
5152
+ }
5153
+ ],
5154
+ "options": [],
5155
+ "subcommands": []
5156
+ },
5136
5157
  {
5137
5158
  "name": "list",
5138
5159
  "description": "List the company's agents",
@@ -8767,7 +8788,7 @@ export const COMMAND_CATALOG = [
8767
8788
  },
8768
8789
  {
8769
8790
  "flags": "--worker <name>",
8770
- "description": "HQ worker id from the registry"
8791
+ "description": "HQ worker or reusable profile id"
8771
8792
  },
8772
8793
  {
8773
8794
  "flags": "--provider <name>",
@@ -9265,6 +9286,10 @@ export const COMMAND_CATALOG = [
9265
9286
  "flags": "--model <id>",
9266
9287
  "description": "Model for the moved lane (with --account only)"
9267
9288
  },
9289
+ {
9290
+ "flags": "--worker <profile>",
9291
+ "description": "Attach a personal worker profile when this message resumes the lane"
9292
+ },
9268
9293
  {
9269
9294
  "flags": "--no-compact",
9270
9295
  "description": "Skip the resume-time compaction check for this message"
@@ -9310,6 +9335,14 @@ export const COMMAND_CATALOG = [
9310
9335
  "flags": "--model <id>",
9311
9336
  "description": "Model for the moved lane (with --account only)"
9312
9337
  },
9338
+ {
9339
+ "flags": "--worker <profile>",
9340
+ "description": "Attach a personal worker profile for the resumed turn"
9341
+ },
9342
+ {
9343
+ "flags": "--mesh-company <slug>",
9344
+ "description": "Attach Work Mesh attribution after verifying company membership"
9345
+ },
9313
9346
  {
9314
9347
  "flags": "--no-compact",
9315
9348
  "description": "Skip the resume-time compaction check for this resume"
@@ -275,6 +275,11 @@ export declare function startStopAgent(token: string, agentUid: string, action:
275
275
  uid: string;
276
276
  runtime?: Record<string, unknown>;
277
277
  }>;
278
+ /** Submit a human-provided Claude subscription code through hq-pro's route. */
279
+ export declare function submitAgentLoginCode(token: string, agentUid: string, code: string): Promise<{
280
+ uid: string;
281
+ ok: boolean;
282
+ }>;
278
283
  /** `POST /v1/agents/{uid}/terminal` wire shape: vended session or 202 starting. */
279
284
  export type AgentTerminalResponse = ({
280
285
  ok: true;
@@ -31,8 +31,10 @@ import open from "open";
31
31
  import { randomUUID } from "node:crypto";
32
32
  import * as readline from "node:readline";
33
33
  import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
34
+ import { DEFAULT_VAULT_API_URL } from "../utils/cognito-session.js";
34
35
  import { gateApiKeyCapabilities } from "../utils/api-key-command-gate.js";
35
36
  import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
37
+ import { consoleOrigin } from "./integrations-connect.js";
36
38
  import { launchSessionManagerPlugin, SessionManagerPluginLaunchError, TerminalSessionTimeoutError, waitForTerminalSession, } from "@indigoai-us/hq-cloud/outposts/node";
37
39
  import { peekIdToken } from "../utils/id-token.js";
38
40
  import { isPlanGateError } from "../utils/plan-gate-error.js";
@@ -54,6 +56,16 @@ export const VALID_PROVIDERS = new Set(["codex", "grok", "claude", "agents-v2"])
54
56
  export const VALID_AUTH_MODES = new Set(["subscription", "apiKey"]);
55
57
  /** Customer-facing agent size keys served by hq-pro's authoritative catalog. */
56
58
  export const VALID_AGENT_SIZE_KEYS = new Set(["basic", "power", "dev"]);
59
+ const CLAUDE_PROVISION_COMMAND = "hq agents provision <name> --provider agents-v2 --model claude-opus-5-5";
60
+ const CLAUDE_API_KEY_ARGS = "--auth-mode apiKey --api-key-env <VAR>";
61
+ function claudeProvisionGuidance() {
62
+ return [
63
+ "The account must be enabled for Claude. Subscription auth is the default.",
64
+ `Create an agent with: ${CLAUDE_PROVISION_COMMAND}`,
65
+ `For API-key auth, add: ${CLAUDE_API_KEY_ARGS}`,
66
+ "The CLI accepts --provider claude for script compatibility, but the server rejects new agents that use it.",
67
+ ].join("\n");
68
+ }
57
69
  /**
58
70
  * `hq agents create` exits with this distinct status when payment is required.
59
71
  * Scripts can distinguish the expected upgrade state from an ordinary command
@@ -693,6 +705,54 @@ export async function startStopAgent(token, agentUid, action) {
693
705
  method: "POST",
694
706
  });
695
707
  }
708
+ /** Submit a human-provided Claude subscription code through hq-pro's route. */
709
+ export async function submitAgentLoginCode(token, agentUid, code) {
710
+ return agentsRequest({
711
+ token,
712
+ path: `/v1/agents/${encodeURIComponent(agentUid)}/login-code`,
713
+ method: "POST",
714
+ body: { code },
715
+ });
716
+ }
717
+ async function readLoginCode(code) {
718
+ let value = code;
719
+ if (value === undefined) {
720
+ if (process.stdin.isTTY) {
721
+ console.error("Enter the Claude sign-in code, then press Enter:");
722
+ }
723
+ const rl = readline.createInterface({ input: process.stdin });
724
+ try {
725
+ value = await new Promise((resolve, reject) => {
726
+ rl.once("error", reject);
727
+ rl.once("close", () => resolve(""));
728
+ rl.question("", resolve);
729
+ });
730
+ }
731
+ finally {
732
+ rl.close();
733
+ }
734
+ }
735
+ const trimmed = value.trim();
736
+ if (!trimmed) {
737
+ throw new Error("Claude sign-in code required. Pass it as an argument or provide it on stdin.");
738
+ }
739
+ return trimmed;
740
+ }
741
+ function redactLoginCode(message, code) {
742
+ return code ? message.split(code).join("[redacted]") : message;
743
+ }
744
+ function agentConsoleResolveUrl(agentUid) {
745
+ const apiUrl = process.env.HQ_VAULT_API_URL?.trim() || DEFAULT_VAULT_API_URL;
746
+ const origin = consoleOrigin(apiUrl);
747
+ if (!origin)
748
+ return undefined;
749
+ // Keep the owner's canonical production sign-in URL while honoring an
750
+ // explicit console override or deriving the console for another API host.
751
+ const consoleBase = !process.env.HQ_CONSOLE_URL?.trim() && origin === "https://hq.computer"
752
+ ? "https://hq.getindigo.ai"
753
+ : origin;
754
+ return `${consoleBase}/resolve/agents/${encodeURIComponent(agentUid)}`;
755
+ }
696
756
  export async function openAgentTerminal(token, agentUid, body) {
697
757
  return agentsRequest({
698
758
  token,
@@ -1213,8 +1273,8 @@ export function registerAgentsCommand(program) {
1213
1273
  .description("Provision a new cloud agent (company-specific monthly price shown before creation)")
1214
1274
  .option("--company <slug>", "Company slug (resolves to companyUid)")
1215
1275
  .option("--slug <slug>", "Agent slug (defaults to a slug of <name>)")
1216
- .option("--provider <provider>", "Runtime: agents-v2 (recommended) | codex | grok | claude (legacy). A --model selects agents-v2 by default")
1217
- .option("--model <model>", "Brain model, for example gpt-5.5 or grok-4.6")
1276
+ .option("--provider <provider>", "Runtime: agents-v2 (recommended) | codex | grok | claude (accepted for script compatibility; use agents-v2 for new Claude agents)")
1277
+ .option("--model <model>", "Brain model, for example claude-opus-5-5, gpt-5.5, or grok-4.6")
1218
1278
  .option("--auth-mode <mode>", "Auth: subscription | apiKey (default subscription)", "subscription")
1219
1279
  .option("--api-key-env <VAR>", "Env var holding the API key for --auth-mode apiKey (never pass the key as a flag)")
1220
1280
  .option("--slack-bot-token <xoxb-token>", "Reuse an existing Slack bot token; HQ_SLACK_BOT_TOKEN or --slack-tokens-stdin avoid shell history")
@@ -1225,6 +1285,7 @@ export function registerAgentsCommand(program) {
1225
1285
  .option("--size <size>", "Agent box size: basic | power | dev (omitted keeps the current default)")
1226
1286
  .option("--yes", "Confirm the quoted monthly cost and provision the agent")
1227
1287
  .option("--json", "Emit machine-readable output and never open a browser")
1288
+ .addHelpText("after", `\n${claudeProvisionGuidance()}\n`)
1228
1289
  .action(async function (name, opts) {
1229
1290
  try {
1230
1291
  // Both of these previously fell back to their default on an
@@ -1251,7 +1312,7 @@ export function registerAgentsCommand(program) {
1251
1312
  // AFTER clearing the charge gate. The server stays authoritative; this is
1252
1313
  // only a friendlier, earlier copy of the same rule.
1253
1314
  if (provider === "claude" && authMode === "apiKey") {
1254
- console.error(chalk.red("The claude provider is subscription-only — drop --auth-mode apiKey."));
1315
+ console.error(chalk.red(`Use agents-v2 for new Claude agents. The claude provider remains accepted for script compatibility.\n${claudeProvisionGuidance()}`));
1255
1316
  process.exit(1);
1256
1317
  }
1257
1318
  // Resolve the API key from the environment (never from a flag value) when
@@ -1317,6 +1378,15 @@ export function registerAgentsCommand(program) {
1317
1378
  surface: CLI_AGENT_CREATE_SURFACE,
1318
1379
  });
1319
1380
  const identity = provisionedAgentIdentity(result);
1381
+ const claudeSignIn = provider === "agents-v2" &&
1382
+ authMode === "subscription" &&
1383
+ opts.model?.toLowerCase().startsWith("claude-") &&
1384
+ identity
1385
+ ? {
1386
+ consoleUrl: agentConsoleResolveUrl(identity.uid) ?? null,
1387
+ loginCodeCommand: `hq agents login-code ${identity.uid}`,
1388
+ }
1389
+ : undefined;
1320
1390
  const status = identity
1321
1391
  ? await getAgentStatus(token, identity.uid)
1322
1392
  : null;
@@ -1336,10 +1406,20 @@ export function registerAgentsCommand(program) {
1336
1406
  slackInstallUrl: slackInstall.url,
1337
1407
  }
1338
1408
  : {}),
1409
+ ...(claudeSignIn ? { claudeSignIn } : {}),
1339
1410
  }));
1340
1411
  }
1341
1412
  else {
1342
1413
  console.log(chalk.green(`Provisioning started for agent "${name}".`));
1414
+ if (claudeSignIn) {
1415
+ if (claudeSignIn.consoleUrl) {
1416
+ console.log(chalk.yellow(`To sign in, open ${claudeSignIn.consoleUrl}, authorize Claude, then paste the code on that page.`));
1417
+ }
1418
+ else {
1419
+ console.log(chalk.yellow("The console URL for this control plane is unknown. Set HQ_CONSOLE_URL to configure it."));
1420
+ }
1421
+ console.log(chalk.dim(`A human CLI session can also submit the code with: ${claudeSignIn.loginCodeCommand}`));
1422
+ }
1343
1423
  if (actions.length > 0) {
1344
1424
  printPendingOperatorActions(identity?.slug ?? null, actions);
1345
1425
  }
@@ -1349,6 +1429,11 @@ export function registerAgentsCommand(program) {
1349
1429
  }
1350
1430
  }
1351
1431
  catch (err) {
1432
+ if (provider === "claude" &&
1433
+ err instanceof AgentsHttpError &&
1434
+ err.code === "LEGACY_MANAGED_PROVIDER_RETIRED") {
1435
+ throw new Error(`${err.message}\n\n${claudeProvisionGuidance()}`);
1436
+ }
1352
1437
  if (err instanceof AgentsHttpError &&
1353
1438
  err.status === 403 &&
1354
1439
  err.code === "AGENT_PLAN_LIMIT") {
@@ -1377,6 +1462,32 @@ export function registerAgentsCommand(program) {
1377
1462
  fail(err);
1378
1463
  }
1379
1464
  });
1465
+ agents
1466
+ .command("login-code <agentUid> [code]")
1467
+ .description("Submit a Claude subscription sign-in code for an agent")
1468
+ .action(async (agentUid, codeArg) => {
1469
+ let code = "";
1470
+ try {
1471
+ code = await readLoginCode(codeArg);
1472
+ const token = (await resolveVaultCredential()).token;
1473
+ await submitAgentLoginCode(token, agentUid, code);
1474
+ console.log(chalk.green(`Sent the Claude sign-in code to agent ${agentUid}.`));
1475
+ }
1476
+ catch (err) {
1477
+ if (err instanceof AgentsHttpError &&
1478
+ err.status === 403 &&
1479
+ err.code === "OUTPOST_ACCOUNT_MANAGEMENT_FORBIDDEN") {
1480
+ const consoleUrl = agentConsoleResolveUrl(agentUid);
1481
+ console.error(chalk.yellow(consoleUrl
1482
+ ? `A person must paste this code in the agent console: ${consoleUrl}`
1483
+ : "A person must paste this code in the agent console. Set HQ_CONSOLE_URL to configure it for this control plane."));
1484
+ process.exit(1);
1485
+ }
1486
+ const detail = err instanceof Error ? err.message : String(err);
1487
+ console.error(chalk.red(`Could not submit Claude sign-in code: ${redactLoginCode(detail, code)}`));
1488
+ process.exit(1);
1489
+ }
1490
+ });
1380
1491
  agents
1381
1492
  .command("list")
1382
1493
  .description("List the company's agents")
@@ -3312,6 +3312,77 @@ async function runAdmit(ctx, laneId, opts) {
3312
3312
  });
3313
3313
  }, ctx.sleep);
3314
3314
  }
3315
+ function resolveResumeProfile(ctx, lane, workerId) {
3316
+ const worker = resolveLaneWorker(ctx.hqRoot(), workerId.trim(), lane.company?.slug ?? lane.mesh_company?.slug);
3317
+ if (worker.profile === undefined) {
3318
+ throw new LanesError("worker_profile_required", `Worker ${JSON.stringify(workerId)} resolves to a registry worker, not a reusable provider profile; resume --worker requires personal/workers/<id>/meta.yaml`, { worker: workerId, path: worker.path });
3319
+ }
3320
+ if (worker.profile.meta.provider !== lane.provider) {
3321
+ throw new LanesError("worker_profile_provider_mismatch", `Worker profile ${JSON.stringify(workerId)} uses provider ${worker.profile.meta.provider}, but lane ${lane.lane_id} uses ${lane.provider}; resume cannot change a lane's provider`, {
3322
+ worker: workerId,
3323
+ lane_id: lane.lane_id,
3324
+ lane_provider: lane.provider,
3325
+ profile_provider: worker.profile.meta.provider,
3326
+ });
3327
+ }
3328
+ return worker;
3329
+ }
3330
+ function validateResumeProfileModel(ctx, lane, profile) {
3331
+ if (profile.meta.provider !== lane.provider ||
3332
+ profile.meta.model === undefined ||
3333
+ lane.model_source === "cli") {
3334
+ return;
3335
+ }
3336
+ const hqRoot = ctx.hqRoot();
3337
+ const config = loadLanesConfig(hqRoot);
3338
+ const account = resolveAccount(lane.provider, lane.account, config, {
3339
+ hqRoot,
3340
+ existsSync: ctx.existsSync,
3341
+ model: profile.meta.model,
3342
+ });
3343
+ validateProfileLaneModel(lane.provider, profile.meta.model, account.home, profile.id, {
3344
+ readFileSync: ctx.readFileSync,
3345
+ probe: ctx.probeProvider,
3346
+ env: ctx.env,
3347
+ });
3348
+ }
3349
+ function recordResumeSetup(ctx, lane, input) {
3350
+ if (input.profile === undefined && input.meshCompany === undefined)
3351
+ return lane;
3352
+ return casUpdateLane(ctx.hqRoot(), lane.lane_id, (current) => {
3353
+ if (current.version !== lane.version) {
3354
+ throw new LanesError("lane_resume_setup_conflict", `Lane ${lane.lane_id} changed while resume settings were prepared; run the resume again`, { lane_id: lane.lane_id, prepared_version: lane.version, version: current.version });
3355
+ }
3356
+ const next = { ...current };
3357
+ if (input.profile !== undefined) {
3358
+ const profile = input.profile.profile;
3359
+ next.worker = input.profile.id;
3360
+ next.worker_path = input.profile.path;
3361
+ next.worker_pack = input.profile.pack;
3362
+ next.worker_profile = profile.id;
3363
+ next.worker_profile_hash = profile.contentHash;
3364
+ next.worker_profile_ignored = ignoredWorkerProfileFields(current.provider);
3365
+ next.worker_policy_delivery = undefined;
3366
+ if (current.model_source !== "cli") {
3367
+ if (profile.meta.provider === current.provider && profile.meta.model !== undefined) {
3368
+ next.model = profile.meta.model;
3369
+ next.model_source = "profile";
3370
+ }
3371
+ else if (profile.meta.provider === current.provider) {
3372
+ next.model = undefined;
3373
+ next.model_source = "none";
3374
+ }
3375
+ }
3376
+ }
3377
+ if (input.meshCompany !== undefined) {
3378
+ if (current.company === null)
3379
+ next.mesh_company = input.meshCompany;
3380
+ if (input.threadId !== undefined)
3381
+ next.thread_id = input.threadId;
3382
+ }
3383
+ return next;
3384
+ });
3385
+ }
3315
3386
  /**
3316
3387
  * US-023: move the lane's provider session to `--account` (same provider)
3317
3388
  * before a resume or a message resume. Every check runs before anything is
@@ -3379,15 +3450,18 @@ async function prepareAccountMove(ctx, lane, input) {
3379
3450
  }).logged_in !== true) {
3380
3451
  throw new LanesError("account_not_logged_in", `Account ${target} for ${lane.provider} is not logged in (no credential file)`, { provider: lane.provider, account: target, lane_id: laneId });
3381
3452
  }
3382
- const profile = lane.worker_profile !== undefined
3453
+ const profile = input.workerProfile ?? (lane.worker_profile !== undefined
3383
3454
  ? loadWorkerProfile(hqRoot, lane.worker_profile)
3384
- : undefined;
3455
+ : undefined);
3385
3456
  const profileDefaultsApply = profile !== undefined && profile.meta.provider === lane.provider;
3386
- const pinnedModel = lane.model_source === undefined || lane.model_source === "cli"
3387
- ? lane.model
3388
- : undefined;
3389
- if (input.cliModel === undefined && profileDefaultsApply && pinnedModel === undefined) {
3390
- validateProfileLaneModel(lane.provider, profile.meta.model, targetCfg.home, profile.id, {
3457
+ const pinnedModel = input.workerProfile !== undefined
3458
+ ? lane.model_source === "cli" ? lane.model : undefined
3459
+ : lane.model_source === undefined || lane.model_source === "cli"
3460
+ ? lane.model
3461
+ : undefined;
3462
+ const profileModel = profileDefaultsApply ? profile?.meta.model : undefined;
3463
+ if (input.cliModel === undefined && profileDefaultsApply && profileModel !== undefined && pinnedModel === undefined) {
3464
+ validateProfileLaneModel(lane.provider, profileModel, targetCfg.home, profile.id, {
3391
3465
  readFileSync: ctx.readFileSync,
3392
3466
  probe: ctx.probeProvider,
3393
3467
  env: ctx.env,
@@ -3400,12 +3474,13 @@ async function prepareAccountMove(ctx, lane, input) {
3400
3474
  // here, before anything is copied. A target that pins a different model
3401
3475
  // refuses the lane's model unless the operator names one for the move.
3402
3476
  const model = resolveLaneModel({
3403
- cliModel: input.cliModel ?? lane.model,
3477
+ cliModel: input.cliModel ?? pinnedModel,
3478
+ profileModel,
3404
3479
  accountModel: targetCfg.model,
3405
3480
  providerModel: providerCfg.model,
3406
3481
  });
3407
- if (input.cliModel === undefined && lane.model !== undefined && targetCfg.model !== undefined && targetCfg.model !== lane.model) {
3408
- throw new LanesError("model_unsupported", `Account ${target} pins model ${targetCfg.model}; lane ${laneId} runs ${lane.model}. Pass --model ${targetCfg.model} to move it onto that model`, { provider: lane.provider, account: target, model: lane.model, account_model: targetCfg.model, lane_id: laneId });
3482
+ if (input.cliModel === undefined && lane.model !== undefined && targetCfg.model !== undefined && model !== targetCfg.model) {
3483
+ throw new LanesError("model_unsupported", `Account ${target} pins model ${targetCfg.model}; lane ${laneId} resolves to ${model ?? lane.model}. Pass --model ${targetCfg.model} to move it onto that model`, { provider: lane.provider, account: target, model: model ?? lane.model, account_model: targetCfg.model, lane_id: laneId });
3409
3484
  }
3410
3485
  const block = quotaBlockFor(loadQuotaState(hqRoot), lane.provider, target, model, now);
3411
3486
  if (block.blocked) {
@@ -3521,6 +3596,12 @@ async function runMessage(ctx, laneId, opts) {
3521
3596
  message,
3522
3597
  });
3523
3598
  }
3599
+ if (opts.worker !== undefined && mechanism === "dropbox") {
3600
+ throw new LanesError("worker_profile_requires_resume", `Lane ${laneId} is still running; --worker can only be applied when message starts a resumed provider turn`, { lane_id: laneId, state: lane.state });
3601
+ }
3602
+ const resumeProfile = opts.worker !== undefined
3603
+ ? resolveResumeProfile(ctx, lane, opts.worker)
3604
+ : undefined;
3524
3605
  if (opts.model !== undefined && opts.account === undefined) {
3525
3606
  throw new LanesError("model_requires_account", "--model on hq lanes message is only accepted together with --account", {});
3526
3607
  }
@@ -3544,6 +3625,7 @@ async function runMessage(ctx, laneId, opts) {
3544
3625
  move = await prepareAccountMove(ctx, lane, {
3545
3626
  account: opts.account,
3546
3627
  cliModel: opts.model?.trim() || undefined,
3628
+ ...(resumeProfile !== undefined ? { workerProfile: resumeProfile.profile } : {}),
3547
3629
  pending: { trigger: "message", text, message_id: messageId, compact: opts.compact !== false },
3548
3630
  });
3549
3631
  lane = move.lane;
@@ -3551,6 +3633,11 @@ async function runMessage(ctx, laneId, opts) {
3551
3633
  archiveResumeMessage(hqRoot, laneId, dropboxMessage);
3552
3634
  }
3553
3635
  }
3636
+ if (resumeProfile !== undefined) {
3637
+ if (opts.account === undefined)
3638
+ validateResumeProfileModel(ctx, lane, resumeProfile.profile);
3639
+ lane = recordResumeSetup(ctx, lane, { profile: resumeProfile });
3640
+ }
3554
3641
  if (move?.queued) {
3555
3642
  // Parked at the target's cap: reconcile resumes the session there.
3556
3643
  }
@@ -3790,11 +3877,32 @@ async function runResume(ctx, laneId, opts) {
3790
3877
  const storyId = opts.story?.trim() || lane.story_id;
3791
3878
  const attributionChanged = projectId !== lane.project_id || storyId !== lane.story_id;
3792
3879
  const messageId = text !== undefined || attributionChanged ? mintMessageId() : undefined;
3880
+ let requestedMeshCompany;
3881
+ let meshThreadId;
3882
+ if (opts.meshCompany !== undefined) {
3883
+ const meshCompanySlug = opts.meshCompany.trim();
3884
+ if (!meshCompanySlug) {
3885
+ throw new LanesError("missing_mesh_company", "--mesh-company must be a non-empty company slug", {});
3886
+ }
3887
+ requestedMeshCompany = await ctx.resolveCompany(meshCompanySlug);
3888
+ if (lane.company !== null &&
3889
+ (lane.company.uid !== requestedMeshCompany.uid || lane.company.slug !== requestedMeshCompany.slug)) {
3890
+ throw new LanesError("mesh_company_mismatch", "--mesh-company must match the lane's company when the lane is company-owned", { company: lane.company.slug, mesh_company: requestedMeshCompany.slug });
3891
+ }
3892
+ meshThreadId = await ctx.mesh.ensureProjectThread(requestedMeshCompany, projectId);
3893
+ }
3894
+ const resumeProfile = opts.worker !== undefined
3895
+ ? resolveResumeProfile(ctx, lane, opts.worker)
3896
+ : undefined;
3897
+ if (resumeProfile !== undefined && opts.account === undefined) {
3898
+ validateResumeProfileModel(ctx, lane, resumeProfile.profile);
3899
+ }
3793
3900
  let move = null;
3794
3901
  if (opts.account !== undefined) {
3795
3902
  move = await prepareAccountMove(ctx, lane, {
3796
3903
  account: opts.account,
3797
3904
  cliModel: opts.model?.trim() || undefined,
3905
+ ...(resumeProfile !== undefined ? { workerProfile: resumeProfile.profile } : {}),
3798
3906
  pending: {
3799
3907
  trigger: "resume",
3800
3908
  resumed_from: resumedFrom,
@@ -3805,6 +3913,12 @@ async function runResume(ctx, laneId, opts) {
3805
3913
  });
3806
3914
  lane = move.lane;
3807
3915
  }
3916
+ lane = recordResumeSetup(ctx, lane, {
3917
+ ...(resumeProfile !== undefined ? { profile: resumeProfile } : {}),
3918
+ ...(requestedMeshCompany !== undefined
3919
+ ? { meshCompany: requestedMeshCompany, threadId: meshThreadId }
3920
+ : {}),
3921
+ });
3808
3922
  let queuedOnAdmission = false;
3809
3923
  let resumed;
3810
3924
  if (move?.queued) {
@@ -5032,7 +5146,7 @@ export function registerLanesCommand(parent, deps = {}) {
5032
5146
  .requiredOption("--story <id>", "Story id from the project's prd.json")
5033
5147
  .option("--additional-story <id>", "Additional story carried by this lane; repeatable and discouraged when active", collectRepeatable)
5034
5148
  .requiredOption("--brief-file <path>", "Path to the lane brief")
5035
- .option("--worker <name>", "HQ worker id from the registry")
5149
+ .option("--worker <name>", "HQ worker or reusable profile id")
5036
5150
  .option("--provider <name>", "claude | codex | grok")
5037
5151
  .option("--account <id>", "Provider account id")
5038
5152
  .option("--model <id>", "Model id for the provider CLI")
@@ -5183,6 +5297,7 @@ export function registerLanesCommand(parent, deps = {}) {
5183
5297
  .option("--story <id>", "Re-point story attribution from this message")
5184
5298
  .option("--account <id>", "Move the lane's session to this account of its provider before the message resumes it")
5185
5299
  .option("--model <id>", "Model for the moved lane (with --account only)")
5300
+ .option("--worker <profile>", "Attach a personal worker profile when this message resumes the lane")
5186
5301
  .option("--no-compact", "Skip the resume-time compaction check for this message")
5187
5302
  .action(wrap(async (laneId, opts) => {
5188
5303
  await runMessage(ctx, laneId, opts);
@@ -5196,6 +5311,8 @@ export function registerLanesCommand(parent, deps = {}) {
5196
5311
  .option("--story <id>", "Re-point story attribution from this resume")
5197
5312
  .option("--account <id>", "Move the lane's session to this account of its provider before resuming")
5198
5313
  .option("--model <id>", "Model for the moved lane (with --account only)")
5314
+ .option("--worker <profile>", "Attach a personal worker profile for the resumed turn")
5315
+ .option("--mesh-company <slug>", "Attach Work Mesh attribution after verifying company membership")
5199
5316
  .option("--no-compact", "Skip the resume-time compaction check for this resume")
5200
5317
  .action(wrap(async (laneId, opts) => {
5201
5318
  await runResume(ctx, laneId, opts);
@@ -34,6 +34,6 @@ export declare class AccountResolutionError extends LanesError {
34
34
  constructor(error: "unknown_account" | "account_disabled" | "no_enabled_account" | "account_model_unavailable" | "account_quota_exhausted", message: string, details?: Record<string, unknown>);
35
35
  }
36
36
  export declare class WorkerResolutionError extends LanesError {
37
- constructor(error: "worker_not_found" | "worker_tenancy_unresolved" | "worker_not_available" | "worker_not_active" | "worker_id_conflict" | "worker_profile_invalid" | "worker_profile_ambiguous" | "package_workers_unmounted" | "worker_registry_unreadable", message: string, details?: Record<string, unknown>);
37
+ constructor(error: "worker_not_found" | "worker_tenancy_unresolved" | "worker_not_available" | "worker_not_active" | "worker_id_conflict" | "worker_profile_invalid" | "worker_policy_invalid" | "worker_profile_ambiguous" | "package_workers_unmounted" | "worker_registry_unreadable", message: string, details?: Record<string, unknown>);
38
38
  }
39
39
  //# sourceMappingURL=errors.d.ts.map
@@ -10,10 +10,10 @@ import { type LaneProvider } from "./types.js";
10
10
  export interface WorkerProfileMeta {
11
11
  version?: string;
12
12
  provider: LaneProvider;
13
- model: string;
14
- account: string;
15
- effort: string;
16
- fast_mode: boolean;
13
+ model?: string;
14
+ account?: string;
15
+ effort?: string;
16
+ fast_mode?: boolean;
17
17
  max_concurrent?: number;
18
18
  source?: string;
19
19
  accounts?: string[];
@@ -36,7 +36,7 @@ export declare function personalWorkerProfileDir(hqRoot: string, id: string): st
36
36
  /** A meta.yaml file is the explicit marker for a personal worker profile. */
37
37
  export declare function workerProfileMarker(hqRoot: string, id: string): "absent" | "present";
38
38
  export declare function loadWorkerProfile(hqRoot: string, id: string): WorkerProfile;
39
- export declare function workerProfilePrompt(profile: WorkerProfile): string;
39
+ export declare function workerProfilePrompt(profile: WorkerProfile, delivery?: "hook" | "prompt"): string;
40
40
  export declare function ignoredWorkerProfileFields(provider: LaneProvider): string[];
41
41
  /**
42
42
  * Materialize provider settings and hook files in the lane run directory.