@indigoai-us/hq-cli 5.345.156 → 5.345.160

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 (188) hide show
  1. package/CHANGELOG.md +255 -0
  2. package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +110 -2
  3. package/assets/scaffold/core/scripts/rebuild-projects-index.sh +101 -5
  4. package/assets/scaffold/core/scripts/worktree.sh +4 -0
  5. package/dist/command-catalog.generated.d.ts +535 -10
  6. package/dist/command-catalog.generated.js +678 -31
  7. package/dist/command-registration-plan.d.ts +18 -0
  8. package/dist/command-registration-plan.js +4 -0
  9. package/dist/commands/_patterns.d.ts +17 -0
  10. package/dist/commands/_patterns.js +22 -0
  11. package/dist/commands/access.d.ts +6 -0
  12. package/dist/commands/access.js +18 -1
  13. package/dist/commands/acl-lock.d.ts +41 -0
  14. package/dist/commands/acl-lock.js +28 -0
  15. package/dist/commands/agent-enroll.d.ts +2 -0
  16. package/dist/commands/agent-enroll.js +9 -4
  17. package/dist/commands/agent-kit.js +8 -6
  18. package/dist/commands/agents.js +5 -11
  19. package/dist/commands/billing-sponsor.d.ts +4 -4
  20. package/dist/commands/billing-sponsor.js +11 -11
  21. package/dist/commands/billing-usage.d.ts +22 -8
  22. package/dist/commands/billing-usage.js +57 -22
  23. package/dist/commands/billing.d.ts +7 -7
  24. package/dist/commands/billing.js +13 -13
  25. package/dist/commands/bot.js +7 -4
  26. package/dist/commands/channel-suggest.d.ts +56 -0
  27. package/dist/commands/channel-suggest.js +170 -0
  28. package/dist/commands/channels.js +7 -1
  29. package/dist/commands/cloud-provision.js +2 -2
  30. package/dist/commands/cloud.d.ts +45 -0
  31. package/dist/commands/cloud.js +157 -17
  32. package/dist/commands/company.js +4 -3
  33. package/dist/commands/core-checkpoint-native.d.ts +18 -0
  34. package/dist/commands/core-checkpoint-native.js +67 -2
  35. package/dist/commands/core-pipeline-driver-native.d.ts +12 -1
  36. package/dist/commands/core-pipeline-driver-native.js +131 -5
  37. package/dist/commands/core.js +12 -0
  38. package/dist/commands/crm.js +83 -6
  39. package/dist/commands/db-remote-deps.js +12 -1
  40. package/dist/commands/deploy-access.d.ts +33 -0
  41. package/dist/commands/deploy-access.js +322 -0
  42. package/dist/commands/dm-attach.d.ts +106 -0
  43. package/dist/commands/dm-attach.js +184 -0
  44. package/dist/commands/dm.d.ts +37 -0
  45. package/dist/commands/dm.js +172 -10
  46. package/dist/commands/feedback-show.d.ts +40 -0
  47. package/dist/commands/feedback-show.js +261 -0
  48. package/dist/commands/feedback.d.ts +21 -0
  49. package/dist/commands/feedback.js +152 -2
  50. package/dist/commands/files.d.ts +24 -0
  51. package/dist/commands/files.js +71 -8
  52. package/dist/commands/integrations-connect.js +7 -3
  53. package/dist/commands/lanes-accounts.js +16 -1
  54. package/dist/commands/lanes-loop.js +14 -4
  55. package/dist/commands/lanes-message.js +6 -4
  56. package/dist/commands/lanes-resume.js +36 -13
  57. package/dist/commands/lanes-shared.d.ts +5 -0
  58. package/dist/commands/lanes-shared.js +13 -3
  59. package/dist/commands/lanes-watch.js +1 -0
  60. package/dist/commands/lanes.d.ts +20 -1
  61. package/dist/commands/lanes.js +201 -51
  62. package/dist/commands/meetings.d.ts +4 -4
  63. package/dist/commands/meetings.js +17 -8
  64. package/dist/commands/mesh.js +149 -1
  65. package/dist/commands/monitor.d.ts +1 -0
  66. package/dist/commands/monitor.js +4 -1
  67. package/dist/commands/repo.d.ts +41 -0
  68. package/dist/commands/repo.js +331 -0
  69. package/dist/commands/scan.d.ts +3 -1
  70. package/dist/commands/scan.js +7 -3
  71. package/dist/commands/secrets.d.ts +21 -0
  72. package/dist/commands/secrets.js +114 -14
  73. package/dist/commands/signals.js +17 -5
  74. package/dist/commands/skill.js +6 -6
  75. package/dist/commands/staff/listings.d.ts +41 -0
  76. package/dist/commands/staff/listings.js +160 -0
  77. package/dist/commands/staff/reset-test-account.d.ts +3 -2
  78. package/dist/commands/staff/reset-test-account.js +4 -3
  79. package/dist/commands/whoami.js +1 -1
  80. package/dist/hook-engine-path.js +13 -2
  81. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +3392 -1243
  82. package/dist/hq-cloud-sync-runner/package.json +1 -1
  83. package/dist/lib/agent-kit/run/inbox.js +2 -1
  84. package/dist/lib/agent-kit/wake-secrets.d.ts +7 -0
  85. package/dist/lib/agent-kit/wake-secrets.js +32 -0
  86. package/dist/lib/agent-kit/wake.d.ts +4 -0
  87. package/dist/lib/agent-kit/wake.js +49 -3
  88. package/dist/lib/billing/plan-limit-refusals.d.ts +13 -3
  89. package/dist/lib/billing/plan-limit-refusals.js +26 -3
  90. package/dist/lib/billing/plan-limit-upgrade-url.d.ts +8 -0
  91. package/dist/lib/billing/plan-limit-upgrade-url.js +18 -0
  92. package/dist/lib/billing/plan-lock.d.ts +22 -6
  93. package/dist/lib/billing/plan-lock.js +58 -30
  94. package/dist/lib/billing/usage-cost.d.ts +24 -2
  95. package/dist/lib/billing/usage-cost.js +103 -3
  96. package/dist/lib/billing/usage-only.d.ts +37 -1
  97. package/dist/lib/billing/usage-only.js +89 -4
  98. package/dist/lib/billing/usage-pause.d.ts +27 -0
  99. package/dist/lib/billing/usage-pause.js +50 -0
  100. package/dist/lib/bot/inflight.d.ts +7 -1
  101. package/dist/lib/bot/inflight.js +10 -2
  102. package/dist/lib/bot/prompt.js +3 -0
  103. package/dist/lib/bot/run.js +72 -20
  104. package/dist/lib/bot/session.d.ts +14 -0
  105. package/dist/lib/bot/session.js +70 -0
  106. package/dist/lib/capacity/collectors/claude-oauth.d.ts +6 -0
  107. package/dist/lib/capacity/collectors/claude-oauth.js +12 -0
  108. package/dist/lib/capacity/lane-login.d.ts +6 -0
  109. package/dist/lib/capacity/lane-login.js +5 -0
  110. package/dist/lib/core-utils/worktree.js +2 -0
  111. package/dist/lib/daemon/exit-cause.d.ts +43 -0
  112. package/dist/lib/daemon/exit-cause.js +88 -0
  113. package/dist/lib/daemon/handoff.d.ts +2 -0
  114. package/dist/lib/daemon/handoff.js +6 -1
  115. package/dist/lib/daemon/hq-child-bootstrap.mjs +20 -3
  116. package/dist/lib/daemon/paths.d.ts +2 -0
  117. package/dist/lib/daemon/paths.js +1 -0
  118. package/dist/lib/daemon/run.js +13 -0
  119. package/dist/lib/daemon/supervisor.d.ts +54 -1
  120. package/dist/lib/daemon/supervisor.js +297 -21
  121. package/dist/lib/daemon/unit-holds.d.ts +22 -0
  122. package/dist/lib/daemon/unit-holds.js +86 -0
  123. package/dist/lib/db/control-plane.d.ts +5 -0
  124. package/dist/lib/db/control-plane.js +10 -0
  125. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  126. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  127. package/dist/lib/hook-engine/resolve.d.ts +14 -0
  128. package/dist/lib/hook-engine/resolve.js +55 -1
  129. package/dist/lib/index-render/company-knowledge.js +1 -3
  130. package/dist/lib/index-render/projects.js +1 -3
  131. package/dist/lib/lanes/account-company.d.ts +28 -0
  132. package/dist/lib/lanes/account-company.js +53 -0
  133. package/dist/lib/lanes/accounts.d.ts +22 -3
  134. package/dist/lib/lanes/accounts.js +38 -2
  135. package/dist/lib/lanes/claude-balance.d.ts +6 -1
  136. package/dist/lib/lanes/claude-balance.js +6 -1
  137. package/dist/lib/lanes/claude-credential.d.ts +78 -0
  138. package/dist/lib/lanes/claude-credential.js +163 -0
  139. package/dist/lib/lanes/compaction.d.ts +2 -0
  140. package/dist/lib/lanes/compaction.js +9 -0
  141. package/dist/lib/lanes/config.js +6 -0
  142. package/dist/lib/lanes/envelope-repair-dispatch.js +2 -1
  143. package/dist/lib/lanes/hooks-install.js +2 -1
  144. package/dist/lib/lanes/host-capacity.d.ts +2 -0
  145. package/dist/lib/lanes/host-capacity.js +1 -0
  146. package/dist/lib/lanes/keep-alive.d.ts +1 -1
  147. package/dist/lib/lanes/keep-alive.js +3 -1
  148. package/dist/lib/lanes/links.js +10 -4
  149. package/dist/lib/lanes/loop.js +16 -1
  150. package/dist/lib/lanes/node-binary.d.ts +17 -0
  151. package/dist/lib/lanes/node-binary.js +30 -0
  152. package/dist/lib/lanes/outpost-dispatch.d.ts +2 -1
  153. package/dist/lib/lanes/outpost-dispatch.js +1 -0
  154. package/dist/lib/lanes/reconcile.js +4 -0
  155. package/dist/lib/lanes/remote-control-lane.js +2 -0
  156. package/dist/lib/lanes/spawn.d.ts +3 -0
  157. package/dist/lib/lanes/spawn.js +119 -9
  158. package/dist/lib/lanes/store.d.ts +6 -0
  159. package/dist/lib/lanes/store.js +107 -18
  160. package/dist/lib/lanes/types.d.ts +2 -0
  161. package/dist/lib/lanes/watch.js +28 -5
  162. package/dist/lib/lanes/watcher-main.js +4 -3
  163. package/dist/lib/lanes/watcher.d.ts +4 -0
  164. package/dist/lib/lanes/watcher.js +110 -21
  165. package/dist/lib/mesh/api.d.ts +32 -0
  166. package/dist/lib/mesh/api.js +75 -0
  167. package/dist/lib/plan-limit-nag.d.ts +0 -16
  168. package/dist/lib/plan-limit-nag.js +17 -18
  169. package/dist/lib/secret-scan/detect.js +9 -0
  170. package/dist/lib/secret-scan/repo-config.d.ts +41 -0
  171. package/dist/lib/secret-scan/repo-config.js +237 -0
  172. package/dist/lib/secret-scan/rules.d.ts +7 -1
  173. package/dist/lib/secret-scan/rules.js +3 -1
  174. package/dist/lib/secret-scan/staged.d.ts +5 -0
  175. package/dist/lib/secret-scan/staged.js +19 -2
  176. package/dist/lib/secret-scan/types.d.ts +2 -0
  177. package/dist/lib/sync/server-owned-local-only.d.ts +29 -0
  178. package/dist/lib/sync/server-owned-local-only.js +75 -0
  179. package/dist/main.d.ts +0 -2
  180. package/dist/main.js +19 -57
  181. package/dist/utils/package-use-lease.js +27 -9
  182. package/dist/utils/plan-gate-error.d.ts +30 -14
  183. package/dist/utils/plan-gate-error.js +60 -46
  184. package/dist/utils/team-upgrade.d.ts +19 -6
  185. package/dist/utils/team-upgrade.js +37 -39
  186. package/dist/utils/version-gate.d.ts +7 -0
  187. package/dist/utils/version-gate.js +66 -0
  188. package/package.json +8 -8
@@ -5,6 +5,7 @@
5
5
  * `accounts login`, which hands the terminal to the vendor's own sign-in.
6
6
  * `watch` and the monitor reminder are the line-oriented text surfaces.
7
7
  */
8
+ import { accountMatchesCompany, accountMayRetainLane, companyMismatchError } from "../lib/lanes/account-company.js";
8
9
  import { spawn, spawnSync } from "node:child_process";
9
10
  import { randomBytes } from "node:crypto";
10
11
  import * as fs from "node:fs";
@@ -104,6 +105,7 @@ import { blockerFreshnessFingerprint, resolveBlockerFreshnessForLane, } from "..
104
105
  import { resolveKrFreshnessForShow } from "../lib/lanes/kr-freshness.js";
105
106
  import { ensureSessionHome } from "../lib/lanes/session-relink.js";
106
107
  import { defaultProbe, validateProfileLaneModel, validateLaneModel, } from "../lib/lanes/models.js";
108
+ import { readLaneClaudeCredential } from "../lib/lanes/claude-credential.js";
107
109
  import { parseCliModel, refuseOutpostSelfTarget, resolveLaneModel, FOREGROUND_ONLY_RULE, STANDING_AUTHORIZATION, tempHomeRule, spawnLaneWorker, spawnEnvelopeRepairRequest, laneRunDir, readSpawnManifest, stopLaneProcesses, } from "../lib/lanes/spawn.js";
108
110
  import { handOffInstallHere } from "../lib/daemon/handoff.js";
109
111
  export { spawnLaneWorker };
@@ -439,6 +441,7 @@ function resolveDeps(deps = {}) {
439
441
  mesh: deps.mesh ?? createDefaultMesh(),
440
442
  sleep: deps.sleep ?? defaultSleep,
441
443
  spawnProcess: deps.spawnProcess,
444
+ statfs: deps.statfs,
442
445
  proveDetached: deps.proveDetached,
443
446
  killProcess: deps.killProcess,
444
447
  isAlive: deps.isAlive,
@@ -460,6 +463,8 @@ function resolveDeps(deps = {}) {
460
463
  }),
461
464
  resolveMachineIdentity: deps.resolveMachineIdentity ?? lookupMachineIdentity,
462
465
  probeProvider: deps.probeProvider ?? defaultProbe,
466
+ readClaudeCredential: deps.readClaudeCredential ??
467
+ (deps.probeProvider === undefined ? readLaneClaudeCredential : undefined),
463
468
  runCompaction: deps.runCompaction,
464
469
  locateTranscript: deps.locateTranscript,
465
470
  monitorEvidence: deps.monitorEvidence ??
@@ -511,9 +516,11 @@ function startAdmittedLane(ctx, root, candidate) {
511
516
  env: ctx.env,
512
517
  hostname: admissionHost.hostname,
513
518
  admissionHost,
519
+ readClaudeCredential: ctx.readClaudeCredential,
514
520
  modelDeps: { readFileSync: ctx.readFileSync, probe: ctx.probeProvider, env: ctx.env },
515
521
  existsSync: ctx.existsSync,
516
522
  spawnProcess: ctx.spawnProcess,
523
+ statfs: ctx.statfs,
517
524
  proveDetached: ctx.proveDetached,
518
525
  killProcess: ctx.killProcess,
519
526
  now: ctx.now,
@@ -2161,6 +2168,7 @@ async function loadClaudeBalanceHostSnapshots(ctx, hqRoot, config, options = {})
2161
2168
  config,
2162
2169
  existsSync: ctx.existsSync,
2163
2170
  probe: ctx.probeProvider,
2171
+ readClaudeCredential: ctx.readClaudeCredential,
2164
2172
  env: ctx.env,
2165
2173
  hostname: ctx.hostname(),
2166
2174
  now: ctx.now(),
@@ -2184,6 +2192,7 @@ async function loadClaudeBalanceHostSnapshots(ctx, hqRoot, config, options = {})
2184
2192
  config,
2185
2193
  existsSync: ctx.existsSync,
2186
2194
  probe: ctx.probeProvider,
2195
+ readClaudeCredential: ctx.readClaudeCredential,
2187
2196
  env: ctx.env,
2188
2197
  hostname: ctx.hostname(),
2189
2198
  now: ctx.now(),
@@ -4012,6 +4021,7 @@ async function runCreate(ctx, opts) {
4012
4021
  existsSync: ctx.existsSync,
4013
4022
  hostname: ctx.hostname(),
4014
4023
  probe: ctx.probeProvider,
4024
+ readClaudeCredential: ctx.readClaudeCredential,
4015
4025
  env: ctx.env,
4016
4026
  }).logged_in === true)
4017
4027
  .sort(),
@@ -4083,7 +4093,25 @@ async function runCreate(ctx, opts) {
4083
4093
  });
4084
4094
  }
4085
4095
  }
4086
- const allowedAccounts = validatedAccounts.size > 0 ? [...validatedAccounts] : profileAccounts;
4096
+ const candidateAccounts = validatedAccounts.size > 0 ? [...validatedAccounts] : profileAccounts;
4097
+ const accountCompany = resolveLaneCompany(hqRoot, { lane_id: "pending-create", company, mesh_company: meshCompany ?? undefined, project_id: project }).company;
4098
+ const allowedAccounts = Object.keys(config.providers[provider].accounts).filter((id) => (candidateAccounts === undefined || candidateAccounts.includes(id)) &&
4099
+ accountMatchesCompany(config.providers[provider].accounts[id], accountCompany, config.providers[provider].accounts));
4100
+ if (pinnedAccount !== undefined && !accountMatchesCompany(config.providers[provider].accounts[pinnedAccount], accountCompany, config.providers[provider].accounts)) {
4101
+ throw new LanesError("account_company_mismatch", `Account ${pinnedAccount} does not belong to the lane company`, { provider, account: pinnedAccount });
4102
+ }
4103
+ // Local creates and Claude balancing select an account here; other Outpost
4104
+ // creates pick theirs on the Outpost, so they are not refused locally.
4105
+ if (pinnedAccount === undefined && (outpostId === undefined || provider === "claude")) {
4106
+ const mismatch = companyMismatchError({
4107
+ provider,
4108
+ providerAccounts: config.providers[provider].accounts,
4109
+ candidates: candidateAccounts ?? Object.keys(config.providers[provider].accounts),
4110
+ company: accountCompany,
4111
+ });
4112
+ if (mismatch !== undefined)
4113
+ throw mismatch;
4114
+ }
4087
4115
  let accountSelection;
4088
4116
  if (provider === "claude") {
4089
4117
  const persistedLocalHost = outpostId === undefined && placementHostOverride === undefined
@@ -4101,6 +4129,7 @@ async function runCreate(ctx, opts) {
4101
4129
  balanceTargetHost = targetHost;
4102
4130
  balanceHostLoad = await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config);
4103
4131
  const selected = selectClaudeAccount({
4132
+ company: accountCompany,
4104
4133
  hosts: balanceHostLoad.snapshots,
4105
4134
  targetHost,
4106
4135
  actualTargetHost,
@@ -4119,11 +4148,7 @@ async function runCreate(ctx, opts) {
4119
4148
  accountSelection = { ...selected, selected_at: ctx.now().toISOString() };
4120
4149
  }
4121
4150
  const balancedAdmissionAccounts = !outpostId && pinnedAccount === undefined
4122
- ? [...new Set(allowedAccounts ??
4123
- Object.entries(config.providers[provider].accounts)
4124
- .filter(([id, account]) => account.enabled &&
4125
- (profileAccounts === undefined || profileAccounts.includes(id)))
4126
- .map(([id]) => id))]
4151
+ ? allowedAccounts.filter((id) => config.providers[provider].accounts[id]?.enabled)
4127
4152
  : undefined;
4128
4153
  // The lane id is created in `persist`; its Work Mesh thread is ensured
4129
4154
  // immediately afterward from that stable identity.
@@ -4167,6 +4192,7 @@ async function runCreate(ctx, opts) {
4167
4192
  config,
4168
4193
  existsSync: ctx.existsSync,
4169
4194
  probe: ctx.probeProvider,
4195
+ readClaudeCredential: ctx.readClaudeCredential,
4170
4196
  env: ctx.env,
4171
4197
  hostname: ctx.hostname(),
4172
4198
  now: ctx.now(),
@@ -4181,6 +4207,7 @@ async function runCreate(ctx, opts) {
4181
4207
  ? { kind: "outpost", id: outpostId }
4182
4208
  : localStableHost(ctx));
4183
4209
  const selected = selectClaudeAccount({
4210
+ company: accountCompany,
4184
4211
  hosts,
4185
4212
  targetHost: balanceTargetHost,
4186
4213
  actualTargetHost: actualTargetHostForPersist,
@@ -4521,6 +4548,7 @@ async function runCreate(ctx, opts) {
4521
4548
  else if (admission.action === "start") {
4522
4549
  started = spawnLaneWorker(hqRoot, lane, {
4523
4550
  env: ctx.env,
4551
+ readClaudeCredential: ctx.readClaudeCredential,
4524
4552
  modelDeps: {
4525
4553
  readFileSync: ctx.readFileSync,
4526
4554
  probe: ctx.probeProvider,
@@ -4850,6 +4878,10 @@ function outpostRejectedGrantContextOption(result) {
4850
4878
  return result.exitCode !== 0 &&
4851
4879
  /unknown option\s+(?:['"])?--grant-context(?:['"])?(?:\s|$)/i.test(`${result.stderr}\n${result.stdout}`);
4852
4880
  }
4881
+ function outpostRejectedSetTargetCommand(result) {
4882
+ return result.exitCode !== 0 &&
4883
+ /unknown command\s+(?:['"])?set-target(?:['"])?(?:\s|$)/i.test(`${result.stderr}\n${result.stdout}`);
4884
+ }
4853
4885
  function outpostRejectedFailedResumeOption(result) {
4854
4886
  return result.exitCode !== 0 &&
4855
4887
  /unknown option\s+(?:['"])?--(?:from-failed|failed-resume-context)(?:['"])?(?:\s|$)/i.test(`${result.stderr}\n${result.stdout}`);
@@ -5230,9 +5262,11 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
5230
5262
  ? "approve-target"
5231
5263
  : action === "repair-company"
5232
5264
  ? "repair-company-target"
5233
- : action,
5265
+ : action === "set"
5266
+ ? "set-target"
5267
+ : action,
5234
5268
  laneId: lane.lane_id,
5235
- hqBinary: action === "grant" || action === "reparent" || action === "repair-company"
5269
+ hqBinary: action === "grant" || action === "reparent" || action === "repair-company" || action === "set"
5236
5270
  ? outpostCliOverride(ctx, outpostId)
5237
5271
  : undefined,
5238
5272
  text: options.text,
@@ -5247,6 +5281,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
5247
5281
  fromFailed: options.fromFailed,
5248
5282
  hire: options.hire,
5249
5283
  senior: options.senior,
5284
+ actor: options.actor,
5250
5285
  reason: options.reason,
5251
5286
  company: options.company,
5252
5287
  grantContext: contexts.grantContext,
@@ -5297,6 +5332,14 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
5297
5332
  required_option: "--from-failed",
5298
5333
  });
5299
5334
  }
5335
+ if (action === "set" && outpostRejectedSetTargetCommand(dispatched)) {
5336
+ throw new LanesError("outpost_cli_upgrade_required", `Outpost ${outpostId} needs an hq-cli release containing remote lane settings before hq lanes set can change lane ${lane.lane_id}`, {
5337
+ lane_id: lane.lane_id,
5338
+ outpost_id: outpostId,
5339
+ command_id: dispatched.commandId,
5340
+ required_command: "set-target",
5341
+ });
5342
+ }
5300
5343
  const ownerError = ownerErrorFromOutput(dispatched.stdout);
5301
5344
  if (options.effort !== undefined && outpostRejectedEffortOption(dispatched)) {
5302
5345
  throw new LanesError("outpost_effort_unsupported", `Outpost ${outpostId} rejected --effort; update hq-cli on that Outpost before changing a lane's effort remotely`, {
@@ -5372,23 +5415,25 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
5372
5415
  remote.lane.company !== null &&
5373
5416
  remote.lane.company !== undefined &&
5374
5417
  remote.lane.company.slug === options.company
5375
- : action === "reparent"
5376
- ? remote.lane !== null && typeof remote.lane === "object" &&
5377
- Array.isArray(remote.changed_descendant_ids)
5378
- : (((remote.delivery_status === "worker_spawned" ||
5379
- remote.delivery_status === "queued_on_target_account") &&
5380
- remote.lane !== undefined) ||
5381
- (remote.delivery_status === "queued_on_admission" &&
5382
- remote.lane !== null &&
5383
- typeof remote.lane === "object" &&
5384
- !Array.isArray(remote.lane) &&
5385
- remote.lane.state === "queued") ||
5386
- (remote.delivery_status === "queued_for_reconcile" &&
5387
- remote.lane !== null &&
5388
- typeof remote.lane === "object" &&
5389
- !Array.isArray(remote.lane) &&
5390
- remote.lane.state === "queued" &&
5391
- remote.lane.queued_reason !== "worker_profile_changed")));
5418
+ : action === "set"
5419
+ ? remote.lane !== null && typeof remote.lane === "object" && !Array.isArray(remote.lane)
5420
+ : action === "reparent"
5421
+ ? remote.lane !== null && typeof remote.lane === "object" &&
5422
+ Array.isArray(remote.changed_descendant_ids)
5423
+ : (((remote.delivery_status === "worker_spawned" ||
5424
+ remote.delivery_status === "queued_on_target_account") &&
5425
+ remote.lane !== undefined) ||
5426
+ (remote.delivery_status === "queued_on_admission" &&
5427
+ remote.lane !== null &&
5428
+ typeof remote.lane === "object" &&
5429
+ !Array.isArray(remote.lane) &&
5430
+ remote.lane.state === "queued") ||
5431
+ (remote.delivery_status === "queued_for_reconcile" &&
5432
+ remote.lane !== null &&
5433
+ typeof remote.lane === "object" &&
5434
+ !Array.isArray(remote.lane) &&
5435
+ remote.lane.state === "queued" &&
5436
+ remote.lane.queued_reason !== "worker_profile_changed")));
5392
5437
  if (!resultConfirmed) {
5393
5438
  throw new LanesError("outpost_delivery_unconfirmed", `Outpost ${outpostId} completed exec without a confirmed ${action} result for lane ${lane.lane_id}`, {
5394
5439
  lane_id: lane.lane_id,
@@ -5447,7 +5492,11 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
5447
5492
  printJson(response);
5448
5493
  return response;
5449
5494
  }
5450
- if (action === "approve" || action === "reparent" || action === "resume" || action === "repair-company") {
5495
+ if (action === "approve" ||
5496
+ action === "reparent" ||
5497
+ action === "resume" ||
5498
+ action === "repair-company" ||
5499
+ action === "set") {
5451
5500
  const ownerLane = dispatchedOwnerLane(ctx, lane.lane_id, outpostId, remote.lane, behavior);
5452
5501
  const descendants = [];
5453
5502
  if (action === "reparent") {
@@ -5777,6 +5826,7 @@ async function runPickup(ctx, opts) {
5777
5826
  try {
5778
5827
  return spawnLaneWorker(hqRoot, current, {
5779
5828
  env: ctx.env,
5829
+ readClaudeCredential: ctx.readClaudeCredential,
5780
5830
  modelDeps: {
5781
5831
  readFileSync: ctx.readFileSync,
5782
5832
  probe: ctx.probeProvider,
@@ -6173,6 +6223,7 @@ async function runCapacity(ctx) {
6173
6223
  config: loadLanesConfig(hqRoot),
6174
6224
  existsSync: ctx.existsSync,
6175
6225
  probe: ctx.probeProvider,
6226
+ readClaudeCredential: ctx.readClaudeCredential,
6176
6227
  env: ctx.env,
6177
6228
  hostname: ctx.hostname(),
6178
6229
  now: ctx.now(),
@@ -6717,6 +6768,10 @@ async function printGrantResult(ctx, hqRoot, updated, hierarchy) {
6717
6768
  * resume. The running turn keeps its values. Both are stored as explicit
6718
6769
  * (`cli`) choices, so keep-alive and `hq lanes resume` keep them until the
6719
6770
  * next `set` or an explicit resume `--effort`/`--worker`.
6771
+ *
6772
+ * A lane whose state lives on another Outpost is resolved the same way
6773
+ * `hq lanes grant` resolves it, and the change is forwarded to the owning
6774
+ * Outpost after the caller is checked against the owner's senior.
6720
6775
  */
6721
6776
  async function runSet(ctx, laneId, opts) {
6722
6777
  if (opts.company !== undefined) {
@@ -6726,6 +6781,60 @@ async function runSet(ctx, laneId, opts) {
6726
6781
  await runRepairCompany(ctx, laneId, { company: opts.company });
6727
6782
  return;
6728
6783
  }
6784
+ const request = parseLaneSetRequest(laneId, opts);
6785
+ const target = await resolveCommandLaneTarget(ctx, laneId);
6786
+ const lane = target.lane;
6787
+ const callerLaneId = ctx.env.HQ_LANE_ID?.trim() || undefined;
6788
+ const authorization = callerLaneId === lane.lane_id
6789
+ ? { authorized: true }
6790
+ : callerSeniorAuthorization(ctx, lane.senior);
6791
+ if (!authorization.authorized) {
6792
+ throw new LanesError("lane_set_unauthorized", appendCallerSeniorReason(`Only lane ${lane.lane_id} or its senior ${seniorKey(lane.senior)} may change its model or effort`, authorization), { lane_id: lane.lane_id, senior: lane.senior, reason: authorization.reason });
6793
+ }
6794
+ const actor = callerLaneId !== undefined
6795
+ ? `lane:${callerLaneId}`
6796
+ : `session:${sessionIdFromEnv(ctx.env) ?? "unknown"}`;
6797
+ if (target.remoteOnly || (lane.host.kind === "outpost" && !laneRunsHereForOperatorMutation(ctx, lane))) {
6798
+ assertLaneSetResumable(lane);
6799
+ // Reject a bad effort here so the owner round trip is not spent on it.
6800
+ parseCliEffort(lane.provider, request.effort);
6801
+ await dispatchOutpostLaneCommand(ctx, lane, "set", {
6802
+ model: request.model,
6803
+ effort: request.effort,
6804
+ reason: request.reason,
6805
+ senior: seniorKey(lane.senior),
6806
+ actor,
6807
+ }, target.remoteOnly
6808
+ ? { syncPrimary: false, expectedOwnerHost: target.ownerHost }
6809
+ : {});
6810
+ return;
6811
+ }
6812
+ const updated = applyLaneSettings(ctx, lane.lane_id, request, { actor });
6813
+ printLaneSetResult(updated);
6814
+ }
6815
+ /**
6816
+ * Owner side of a forwarded `hq lanes set`. The primary has already checked
6817
+ * the caller against the lane's senior; this side accepts only an SSM Run
6818
+ * Command dispatch for a lane hosted here whose senior is still the one the
6819
+ * primary checked.
6820
+ */
6821
+ async function runSetOwnerTarget(ctx, laneId, opts) {
6822
+ if (!ctx.isSsmOwnerDispatch()) {
6823
+ throw new LanesError("lane_set_dispatch_unauthorized", "The Outpost set target is available only to an SSM Run Command dispatch", { lane_id: laneId });
6824
+ }
6825
+ const request = parseLaneSetRequest(laneId, opts);
6826
+ if (opts.senior === undefined) {
6827
+ throw new LanesError("lane_set_senior_required", "--senior is required", { lane_id: laneId });
6828
+ }
6829
+ const expectedSenior = parseSeniorRef(opts.senior);
6830
+ const actor = opts.actor?.trim();
6831
+ if (!actor || !/^(?:lane|session):\S+$/.test(actor)) {
6832
+ throw new LanesError("lane_set_actor_invalid", "--actor must be lane:<id> or session:<id>", { lane_id: laneId });
6833
+ }
6834
+ const updated = applyLaneSettings(ctx, laneId, request, { actor, expectedSenior });
6835
+ printLaneSetResult(updated);
6836
+ }
6837
+ function parseLaneSetRequest(laneId, opts) {
6729
6838
  if (opts.model === undefined && opts.effort === undefined) {
6730
6839
  throw new LanesError("lane_set_empty", "hq lanes set needs --company, --model, or --effort", { lane_id: laneId });
6731
6840
  }
@@ -6735,26 +6844,37 @@ async function runSet(ctx, laneId, opts) {
6735
6844
  }
6736
6845
  if (reason !== undefined)
6737
6846
  assertNoSecretText("set reason", reason);
6847
+ const model = opts.model?.trim();
6848
+ if (opts.model !== undefined && !model) {
6849
+ throw new LanesError("empty_model", "--model is empty or whitespace-only", { value: opts.model });
6850
+ }
6851
+ return {
6852
+ ...(model !== undefined ? { model } : {}),
6853
+ ...(opts.effort !== undefined ? { effort: opts.effort } : {}),
6854
+ ...(reason !== undefined ? { reason } : {}),
6855
+ };
6856
+ }
6857
+ function assertLaneSetResumable(lane) {
6858
+ if (lane.state === "killed" || lane.state === "cancelled") {
6859
+ throw new LanesError("lane_not_resumable", `Lane ${lane.lane_id} is ${lane.state} and will not run again`, { lane_id: lane.lane_id, state: lane.state });
6860
+ }
6861
+ }
6862
+ function assertLaneSetSenior(lane, expectedSenior) {
6863
+ if (expectedSenior === undefined || seniorKey(lane.senior) === seniorKey(expectedSenior))
6864
+ return;
6865
+ throw new LanesError("lane_set_senior_changed", `Lane ${lane.lane_id}'s senior changed after the primary authorized its settings change`, { lane_id: lane.lane_id, expected_senior: expectedSenior, current_senior: lane.senior });
6866
+ }
6867
+ /** Apply a validated set request to a lane whose state lives on this host. */
6868
+ function applyLaneSettings(ctx, laneId, request, options) {
6738
6869
  const hqRoot = ctx.hqRoot();
6739
6870
  const lane = readLane(hqRoot, laneId);
6740
- const callerLaneId = ctx.env.HQ_LANE_ID?.trim() || undefined;
6741
- const authorization = callerLaneId === laneId
6742
- ? { authorized: true }
6743
- : callerSeniorAuthorization(ctx, lane.senior);
6744
- if (!authorization.authorized) {
6745
- throw new LanesError("lane_set_unauthorized", appendCallerSeniorReason(`Only lane ${laneId} or its senior ${seniorKey(lane.senior)} may change its model or effort`, authorization), { lane_id: laneId, senior: lane.senior, reason: authorization.reason });
6746
- }
6747
6871
  if (lane.host.kind === "outpost" && !laneRunsHereForOperatorMutation(ctx, lane)) {
6748
6872
  throw new LanesError("set_remote_unsupported", `Lane ${laneId} runs on Outpost ${lane.host.id}; run hq lanes set on that Outpost`, { lane_id: laneId, outpost_id: lane.host.id });
6749
6873
  }
6750
6874
  assertLaneRunsHereForOperatorMutation(ctx, lane, "set");
6751
- if (lane.state === "killed" || lane.state === "cancelled") {
6752
- throw new LanesError("lane_not_resumable", `Lane ${laneId} is ${lane.state} and will not run again`, { lane_id: laneId, state: lane.state });
6753
- }
6754
- const model = opts.model?.trim();
6755
- if (opts.model !== undefined && !model) {
6756
- throw new LanesError("empty_model", "--model is empty or whitespace-only", { value: opts.model });
6757
- }
6875
+ assertLaneSetSenior(lane, options.expectedSenior);
6876
+ assertLaneSetResumable(lane);
6877
+ const { model, reason } = request;
6758
6878
  if (model !== undefined) {
6759
6879
  const config = loadLanesConfig(hqRoot);
6760
6880
  const account = lane.account === undefined
@@ -6769,16 +6889,13 @@ async function runSet(ctx, laneId, opts) {
6769
6889
  env: ctx.env,
6770
6890
  });
6771
6891
  }
6772
- const effort = parseCliEffort(lane.provider, opts.effort);
6892
+ const effort = parseCliEffort(lane.provider, request.effort);
6773
6893
  const at = ctx.now().toISOString();
6774
- const actor = callerLaneId !== undefined
6775
- ? `lane:${callerLaneId}`
6776
- : `session:${sessionIdFromEnv(ctx.env) ?? "unknown"}`;
6777
- const updated = casUpdateLane(hqRoot, laneId, (current) => {
6894
+ return casUpdateLane(hqRoot, laneId, (current) => {
6778
6895
  if (current.version !== lane.version) {
6779
6896
  throw new LanesError("lane_set_conflict", `Lane ${laneId} changed while the settings were checked; run hq lanes set again`, { lane_id: laneId, prepared_version: lane.version, version: current.version });
6780
6897
  }
6781
- const entry = { at, actor, ...(reason !== undefined ? { reason } : {}) };
6898
+ const entry = { at, actor: options.actor, ...(reason !== undefined ? { reason } : {}) };
6782
6899
  const next = { ...current };
6783
6900
  const pending = { ...(current.pending_settings ?? {}) };
6784
6901
  // A running turn keeps the model and effort it launched with, because the
@@ -6810,9 +6927,12 @@ async function runSet(ctx, laneId, opts) {
6810
6927
  next.settings_history = appendCappedHistory(current.settings_history ?? [], entry);
6811
6928
  return next;
6812
6929
  });
6930
+ }
6931
+ function printLaneSetResult(updated) {
6813
6932
  printJson({
6814
6933
  ok: true,
6815
6934
  action: "set",
6935
+ lane_id: updated.lane_id,
6816
6936
  lane: updated,
6817
6937
  model: updated.pending_settings?.model ?? updated.model ?? null,
6818
6938
  effort: updated.pending_settings?.effort ?? updated.effort ?? null,
@@ -7331,7 +7451,8 @@ function validateResumeProfileModel(ctx, lane, profile) {
7331
7451
  env: ctx.env,
7332
7452
  });
7333
7453
  }
7334
- function applyResumeProfileAndEffort(current, next, input) {
7454
+ /** @internal Exported for regression tests of the profile-refresh company check. */
7455
+ export function applyResumeProfileAndEffort(hqRoot, current, next, input) {
7335
7456
  if (input.profile !== undefined) {
7336
7457
  const profile = input.profile.profile;
7337
7458
  next.worker = input.profile.id;
@@ -7387,6 +7508,12 @@ function applyResumeProfileAndEffort(current, next, input) {
7387
7508
  next.provider = targetProvider;
7388
7509
  next.provider_source = providerPinned ? current.provider_source : "profile";
7389
7510
  if (!accountPinned && profileMatchesProvider) {
7511
+ if (profileAccount !== undefined && profileAccount !== current.account &&
7512
+ !accountMatchesCompany(loadLanesConfig(hqRoot).providers[targetProvider].accounts[profileAccount], resolveLaneCompany(hqRoot, current).company, loadLanesConfig(hqRoot).providers[targetProvider].accounts)) {
7513
+ throw new LanesError("account_company_mismatch", "Worker profile account does not belong to the lane company", {
7514
+ lane_id: current.lane_id, account: profileAccount,
7515
+ });
7516
+ }
7390
7517
  next.account = profileAccount;
7391
7518
  next.account_source = profileAccount === undefined ? "balanced" : "profile";
7392
7519
  next.account_selection = undefined;
@@ -7496,7 +7623,7 @@ function recordResumeSetup(ctx, lane, input) {
7496
7623
  next.keep_alive_blocked_reason = undefined;
7497
7624
  if (input.accountSource !== undefined)
7498
7625
  next.account_source = input.accountSource;
7499
- applyResumeProfileAndEffort(current, next, { ...input, at: ctx.now().toISOString() });
7626
+ applyResumeProfileAndEffort(ctx.hqRoot(), current, next, { ...input, at: ctx.now().toISOString() });
7500
7627
  if (input.accountSource !== undefined)
7501
7628
  next.account_source = input.accountSource;
7502
7629
  if (input.clearWorkerProfileChangedQueue === true) {
@@ -7576,6 +7703,11 @@ async function prepareAccountMove(ctx, lane, input) {
7576
7703
  if (targetCfg === undefined) {
7577
7704
  throw new AccountResolutionError("unknown_account", `Unknown account ${JSON.stringify(target)} for ${lane.provider}. Configured: ${Object.keys(providerCfg.accounts).join(", ") || "(none)"}`, { provider: lane.provider, account: target, enabled: Object.keys(providerCfg.accounts).filter((id) => providerCfg.accounts[id]?.enabled) });
7578
7705
  }
7706
+ if (!accountMatchesCompany(targetCfg, resolveLaneCompany(hqRoot, lane).company, providerCfg.accounts)) {
7707
+ throw new LanesError("account_company_mismatch", "Lane moves require an account assigned to the lane's company", {
7708
+ provider: lane.provider, account: target, company: lane.company,
7709
+ });
7710
+ }
7579
7711
  const targetHome = lane.remote_control === true
7580
7712
  ? remoteControlSessionHome(config, ctx.hostname())
7581
7713
  : effectiveAccountHome(targetCfg, ctx.hostname()).home;
@@ -7586,6 +7718,7 @@ async function prepareAccountMove(ctx, lane, input) {
7586
7718
  existsSync: ctx.existsSync,
7587
7719
  hostname: ctx.hostname(),
7588
7720
  probe: ctx.probeProvider,
7721
+ readClaudeCredential: ctx.readClaudeCredential,
7589
7722
  env: ctx.env,
7590
7723
  }).logged_in !== true) {
7591
7724
  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 });
@@ -7705,7 +7838,7 @@ async function prepareAccountMove(ctx, lane, input) {
7705
7838
  ...input.pending,
7706
7839
  },
7707
7840
  };
7708
- applyResumeProfileAndEffort(current, next, {
7841
+ applyResumeProfileAndEffort(ctx.hqRoot(), current, next, {
7709
7842
  ...(input.workerProfile !== undefined ? { profile: input.workerProfile } : {}),
7710
7843
  ...(input.profileRepin !== undefined ? { profileRepin: input.profileRepin } : {}),
7711
7844
  ...(input.effort !== undefined ? { effort: input.effort } : {}),
@@ -7752,10 +7885,12 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
7752
7885
  existsSync: ctx.existsSync,
7753
7886
  hostname: ctx.hostname(),
7754
7887
  probe: ctx.probeProvider,
7888
+ readClaudeCredential: ctx.readClaudeCredential,
7755
7889
  env: ctx.env,
7756
7890
  }).logged_in;
7757
7891
  const recordedAccountUnavailable = lane.account !== undefined &&
7758
- (currentConfig?.enabled !== true || currentLogin === false);
7892
+ (currentConfig?.enabled !== true || currentLogin === false ||
7893
+ !accountMayRetainLane(currentConfig, resolveLaneCompany(hqRoot, lane).company, config.providers[lane.provider].accounts));
7759
7894
  const profile = workerProfile?.profile ?? (lane.worker_profile !== undefined
7760
7895
  ? loadWorkerProfile(hqRoot, lane.worker_profile)
7761
7896
  : undefined);
@@ -7817,7 +7952,7 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
7817
7952
  const currentTargetAccount = lane.account === undefined
7818
7953
  ? undefined
7819
7954
  : targetHost?.capacity.providers.claude.accounts[lane.account];
7820
- if (!recordedAccountQuotaHeld &&
7955
+ if (!accountNeedsRepair &&
7821
7956
  profilePin !== undefined &&
7822
7957
  lane.account !== undefined &&
7823
7958
  allowedAccounts?.includes(lane.account) &&
@@ -7826,6 +7961,8 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
7826
7961
  currentTargetAccount.logged_in === true)
7827
7962
  return undefined;
7828
7963
  const selectionInput = {
7964
+ company: resolveLaneCompany(hqRoot, lane).company,
7965
+ retainedAccount: lane.account,
7829
7966
  hosts: balanceHosts.snapshots,
7830
7967
  targetHost: lane.host,
7831
7968
  actualTargetHost: lane.host,
@@ -7990,6 +8127,7 @@ function automaticBalanceMoveHistoryReason(ctx, lane) {
7990
8127
  existsSync: ctx.existsSync,
7991
8128
  hostname: ctx.hostname(),
7992
8129
  probe: ctx.probeProvider,
8130
+ readClaudeCredential: ctx.readClaudeCredential,
7993
8131
  env: ctx.env,
7994
8132
  }).logged_in;
7995
8133
  if (login === false)
@@ -9693,6 +9831,7 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
9693
9831
  dryRun: Boolean(opts.dryRun),
9694
9832
  spawnLaneDeps: {
9695
9833
  env: ctx.env,
9834
+ readClaudeCredential: ctx.readClaudeCredential,
9696
9835
  modelDeps: {
9697
9836
  readFileSync: ctx.readFileSync,
9698
9837
  probe: ctx.probeProvider,
@@ -10404,6 +10543,17 @@ export function registerLanesCommand(parent, deps = {}) {
10404
10543
  .action(wrap(async (laneId, opts) => {
10405
10544
  await runGrantOwnerTarget(ctx, laneId, opts);
10406
10545
  }));
10546
+ lanes
10547
+ .command("set-target", { hidden: true })
10548
+ .argument("<lane-id>", "Lane id")
10549
+ .option("--model <name>", "Model for the next resume")
10550
+ .option("--effort <level>", "Effort for the next resume")
10551
+ .option("--reason <text>", "Reason recorded in the lane's settings history")
10552
+ .option("--senior <ref>", "Senior the primary authorized the change against")
10553
+ .option("--actor <ref>", "lane:<id> or session:<id> that requested the change")
10554
+ .action(wrap(async (laneId, opts) => {
10555
+ await runSetOwnerTarget(ctx, laneId, opts);
10556
+ }));
10407
10557
  lanes
10408
10558
  .command("keep-alive")
10409
10559
  .description("Opt a lane into or out of automatic lead-lane resumes")
@@ -3,10 +3,10 @@ import { type PricingPlan } from "../lib/billing/usage-only.js";
3
3
  /** Upper bound on list pages one short-id lookup reads (per cursor chain). */
4
4
  export declare const SHORT_ID_MAX_PAGES = 50;
5
5
  /**
6
- * The 402 copy for meeting bots. A usage-only company (US-012: the body or the
7
- * company's billing summary says `pricingPlan: "usage-only"`) has no Workforce
8
- * plan to buy, so it is told to add a card. Every other company keeps the
9
- * Workforce copy.
6
+ * The 402 copy for meeting bots. A usage pause (US-024) prints the server's
7
+ * message and Billing link. A usage-only company (US-012) is told to add a
8
+ * card. Every other company gets the server's sentence under a line that
9
+ * names no plan: the flat plans are closed.
10
10
  */
11
11
  export declare function meetingPaymentRequiredLines(body: Record<string, unknown>, resolvePlan?: () => Promise<PricingPlan | null>): Promise<string[]>;
12
12
  export declare function registerMeetingsCommand(program: Command): void;
@@ -4,7 +4,7 @@ import { ensureCognitoToken } from "../utils/cognito-session.js";
4
4
  import { vaultApiFetch, getCompanyUid, resolveCallerPersonUid, } from "../utils/vault-api.js";
5
5
  import { isPlanGateError, planGateErrorFromPayload, } from "../utils/plan-gate-error.js";
6
6
  import { mapWithConcurrency } from "./sync-manifest.js";
7
- import { isUsageOnly, pricingPlanFromBody, resolveCompanyPricingPlan, usageOnlyCardRequiredLines, } from "../lib/billing/usage-only.js";
7
+ import { isUsageOnly, pricingPlanFromBody, resolveCompanyPricingPlan, usageOnlyCardRequiredLines, usagePauseFromBody, } from "../lib/billing/usage-only.js";
8
8
  const LIVE_SNAPSHOT_STATUSES = new Set([
9
9
  "joining",
10
10
  "recording",
@@ -318,23 +318,32 @@ async function resolveShortId(token, prefix, query) {
318
318
  process.exit(1);
319
319
  }
320
320
  /**
321
- * The 402 copy for meeting bots. A usage-only company (US-012: the body or the
322
- * company's billing summary says `pricingPlan: "usage-only"`) has no Workforce
323
- * plan to buy, so it is told to add a card. Every other company keeps the
324
- * Workforce copy.
321
+ * The 402 copy for meeting bots. A usage pause (US-024) prints the server's
322
+ * message and Billing link. A usage-only company (US-012) is told to add a
323
+ * card. Every other company gets the server's sentence under a line that
324
+ * names no plan: the flat plans are closed.
325
325
  */
326
326
  export async function meetingPaymentRequiredLines(body, resolvePlan = () => resolveCompanyPricingPlan()) {
327
327
  const plan = pricingPlanFromBody(body) ?? (await resolvePlan().catch(() => null));
328
328
  if (isUsageOnly(plan)) {
329
329
  return usageOnlyCardRequiredLines("A meeting notetaker bot").map((line, i) => i === 0 ? chalk.red(line) : chalk.dim(line));
330
330
  }
331
+ // US-024: a usage pause is the server's own answer: its message and link.
332
+ const pause = usagePauseFromBody(body);
333
+ if (pause) {
334
+ return [
335
+ chalk.red(pause.message),
336
+ ...(pause.actionUrl ? [chalk.dim(`Billing: ${pause.actionUrl}`)] : []),
337
+ ];
338
+ }
339
+ // The server's sentence when it sent one; otherwise copy that names no plan.
331
340
  const detail = typeof body.message === "string"
332
341
  ? body.message
333
342
  : typeof body.error === "string"
334
343
  ? body.error
335
- : "Upgrade your company to HQ Workforce to record meetings.";
344
+ : "Run `hq billing status` or open the company's Billing page to check it.";
336
345
  return [
337
- chalk.red("Meeting notetaker bots require the $500/mo HQ Workforce plan."),
346
+ chalk.red("Meeting notetaker bots are not available on this company's current billing."),
338
347
  chalk.dim(detail),
339
348
  ];
340
349
  }
@@ -660,7 +669,7 @@ export function registerMeetingsCommand(program) {
660
669
  const companySlug = meetings.opts().company;
661
670
  // Always name a company. With no companyId the backend files the bot
662
671
  // under Personal, which can never record, so an entitled user would
663
- // get the HQ Workforce upgrade prompt. Without --company this uses the
672
+ // get the payment-required refusal. Without --company this uses the
664
673
  // caller's only active company, or asks them to pick one.
665
674
  const query = {
666
675
  companyId: await getCompanyUid(token, companySlug),