@indigoai-us/hq-cli 5.294.0 → 5.295.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/command-catalog.generated.d.ts +12 -9
  3. package/dist/command-catalog.generated.js +15 -12
  4. package/dist/commands/auth.d.ts +1 -0
  5. package/dist/commands/auth.js +76 -1
  6. package/dist/commands/doctor.js +0 -74
  7. package/dist/commands/lanes-message.js +23 -5
  8. package/dist/commands/lanes-resume.js +0 -3
  9. package/dist/commands/lanes-shared.d.ts +1 -2
  10. package/dist/commands/lanes.d.ts +1 -9
  11. package/dist/commands/lanes.js +177 -172
  12. package/dist/fast-scaffold.d.ts +0 -5
  13. package/dist/fast-scaffold.js +0 -10
  14. package/dist/index.js +1 -10
  15. package/dist/lib/core-utils/sentry-report.d.ts +0 -19
  16. package/dist/lib/core-utils/sentry-report.js +8 -61
  17. package/dist/lib/doctor/checks/claude-wiring.js +9 -0
  18. package/dist/lib/lanes/flag-keys.d.ts +2 -0
  19. package/dist/lib/lanes/flag-keys.js +2 -0
  20. package/dist/lib/lanes/manager-status-gate.d.ts +0 -17
  21. package/dist/lib/lanes/manager-status-gate.js +2 -43
  22. package/dist/lib/lanes/outpost-dispatch.d.ts +2 -0
  23. package/dist/lib/lanes/outpost-dispatch.js +7 -1
  24. package/dist/lib/lanes/outpost-hire-cache.d.ts +0 -1
  25. package/dist/lib/lanes/outpost-hire-cache.js +0 -1
  26. package/dist/lib/lanes/outpost-snapshot-memo.d.ts +6 -0
  27. package/dist/lib/lanes/outpost-snapshot-memo.js +14 -0
  28. package/dist/lib/lanes/reconcile.d.ts +2 -3
  29. package/dist/lib/lanes/reconcile.js +44 -38
  30. package/dist/lib/lanes/spawn.d.ts +0 -2
  31. package/dist/lib/lanes/spawn.js +18 -5
  32. package/dist/lib/lanes/store.js +22 -0
  33. package/dist/lib/lanes/types.d.ts +18 -1
  34. package/dist/lib/lanes/types.js +1 -1
  35. package/dist/lib/lanes/watcher.d.ts +4 -3
  36. package/dist/lib/lanes/watcher.js +102 -34
  37. package/dist/lib/mesh/live/daemon/run.js +18 -1
  38. package/dist/utils/cognito-session.d.ts +6 -0
  39. package/dist/utils/cognito-session.js +9 -0
  40. package/dist/utils/self-update.js +31 -0
  41. package/dist/utils/version-gate.js +22 -3
  42. package/package.json +1 -1
  43. package/dist/fast-lanes-list-route.d.ts +0 -8
  44. package/dist/fast-lanes-list-route.js +0 -32
  45. package/dist/fast-lanes-list.d.ts +0 -3
  46. package/dist/fast-lanes-list.js +0 -224
  47. package/dist/lib/doctor/client-health-sentry-flag.d.ts +0 -23
  48. package/dist/lib/doctor/client-health-sentry-flag.js +0 -99
  49. package/dist/lib/doctor/client-health-sentry.d.ts +0 -71
  50. package/dist/lib/doctor/client-health-sentry.js +0 -286
  51. package/dist/lib/lanes/outpost-snapshot-cache.d.ts +0 -26
  52. package/dist/lib/lanes/outpost-snapshot-cache.js +0 -215
  53. package/dist/lib/lanes/outpost-snapshot-refresh.d.ts +0 -21
  54. package/dist/lib/lanes/outpost-snapshot-refresh.js +0 -329
package/CHANGELOG.md CHANGED
@@ -2,13 +2,38 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.295.0] — 2026-09-30
6
+ - Remove the unused direct client-health Sentry route; the existing server-side reporting path remains unchanged.
7
+
8
+ - Self-update supervisor copy and cleanup failures now log their error class.
9
+ - Report failed and ineffective self-updates to Sentry with bounded context.
10
+
11
+ - hq-cli release notes now use per-branch fragments that are folded into the next version section, reducing conflicts between parallel pull requests.
12
+ - Claude keep-alive resume events now retain a compact account-load selection snapshot, including when the lane stays on its current account.
13
+ - After a successful missing-envelope repair reports unfinished work, the watcher can queue one automatic continue behind the default-off `lanes.envelope-repair-continue` flag. The repair prompt now gives the required unfinished-story envelope fields.
14
+ - Add a private machine-session token-file command for supported machine-auth handoffs.
15
+ - Apply the 5x fast-route speedup gate only when the base p95 is at or above the 200 ms target; retain the p95 latency and base/PR regression gates.
16
+ - The changelog fragment check runs only on pull requests again, so pushes to main no longer fail it.
17
+ - Refresh terminal Outpost lane mirrors before applying `hq lanes list` state filters, so a lane whose owner is still running is listed as running.
18
+ - Remove the unused Outpost snapshot cache flag and its cache-first lane-list path.
19
+ - The mesh daemon logs spool watcher failures and continues flushing on its polling interval instead of exiting on an unhandled watcher error.
20
+ - Make the manager status gate always active and remove its rollout flag lookup.
21
+ - Compare fast-route latency with a PR-build full-graph baseline so the 5x gate remains valid after the base build gains fast routing.
22
+
5
23
  ## [5.294.0] — 2026-09-30
24
+ - Record automatic keep-alive resumes in each lane's durable, bounded history and expose it in `hq lanes show --json` and `hq lanes list --json`.
25
+
26
+ ### Fixed
6
27
 
28
+ - Outpost `hq lanes message` now reaches primary-hosted lanes through the
29
+ primary owner command, which checks sender rights and includes the
30
+ destination in JSON output.
7
31
  - Split the lane story command handlers into a dedicated module without changing behavior.
8
32
  - On a box, `sudo hq daemon migrate --apply` now finds the login user's own systemd user units (hq-mesh-daemon, hq-lanes-reconcile and the rest) and stops them through that user's systemd manager. `sudo hq daemon uninstall --restore-legacy` restarts them the same way. Before, root looked only in its own home and could not reach the login user's manager.
9
33
 
10
34
  ## [5.293.0] — 2026-09-30
11
35
  - [HQ-CLI-HOOK-TIMEOUT-KIND] Group hook timeout events by normalized kind (`warning` or `exceeded`) across scripts; keep script and platform tags for triage. Existing per-script issues will stop receiving new events, and the lead handles their status.
36
+ - [HQ-CLI-2C] Label unmeasured SessionStart delay as entry Bash startup when BASH_ENV is set; doctor follows the env-prefixed hook command.
12
37
  - `hq lanes move` now reports an already-on-target no-op without dispatching or changing the queued lane.
13
38
  - Keep bundled scheduled-job reconciliation units on the live scaffold runner, and preserve caller spelling in job-validation paths.
14
39
  - On Linux, `hq daemon run` sets XDG_RUNTIME_DIR for its services when the system unit left it unset, so `systemctl --user` from jobs-reconcile no longer fails with "Failed to connect to bus: No medium found".
@@ -860,6 +860,18 @@ export declare const COMMAND_CATALOG: readonly [{
860
860
  readonly arguments: readonly [];
861
861
  readonly options: readonly [];
862
862
  readonly subcommands: readonly [{
863
+ readonly name: "machine-session";
864
+ readonly description: "Write the existing machine identity session to a private file";
865
+ readonly aliases: readonly [];
866
+ readonly hidden: false;
867
+ readonly usage: "[options]";
868
+ readonly arguments: readonly [];
869
+ readonly options: readonly [{
870
+ readonly flags: "--output <path>";
871
+ readonly description: "path for the private machine session JSON";
872
+ }];
873
+ readonly subcommands: readonly [];
874
+ }, {
863
875
  readonly name: "login";
864
876
  readonly description: "Sign in to HQ — opens the Cognito Hosted UI and caches tokens locally";
865
877
  readonly aliases: readonly [];
@@ -6130,15 +6142,6 @@ export declare const COMMAND_CATALOG: readonly [{
6130
6142
  }, {
6131
6143
  readonly flags: "--client-health-report";
6132
6144
  readonly description: "Resolve the opt-in hq-flags gate used by the client-health report hook.";
6133
- }, {
6134
- readonly flags: "--client-health-sentry";
6135
- readonly description: "Send the opt-in client-health Sentry report from a doctor JSON document.";
6136
- }, {
6137
- readonly flags: "--json-input <file>";
6138
- readonly description: "Doctor JSON input for the client-health report.";
6139
- }, {
6140
- readonly flags: "--client-health-fix-outcome <outcome>";
6141
- readonly description: "Safe fix outcome label for the client-health report.";
6142
6145
  }, {
6143
6146
  readonly flags: "--yes";
6144
6147
  readonly description: "Skip the interactive --fix confirmation (non-interactive use).";
@@ -1097,6 +1097,21 @@ export const COMMAND_CATALOG = [
1097
1097
  "arguments": [],
1098
1098
  "options": [],
1099
1099
  "subcommands": [
1100
+ {
1101
+ "name": "machine-session",
1102
+ "description": "Write the existing machine identity session to a private file",
1103
+ "aliases": [],
1104
+ "hidden": false,
1105
+ "usage": "[options]",
1106
+ "arguments": [],
1107
+ "options": [
1108
+ {
1109
+ "flags": "--output <path>",
1110
+ "description": "path for the private machine session JSON"
1111
+ }
1112
+ ],
1113
+ "subcommands": []
1114
+ },
1100
1115
  {
1101
1116
  "name": "login",
1102
1117
  "description": "Sign in to HQ — opens the Cognito Hosted UI and caches tokens locally",
@@ -7883,18 +7898,6 @@ export const COMMAND_CATALOG = [
7883
7898
  "flags": "--client-health-report",
7884
7899
  "description": "Resolve the opt-in hq-flags gate used by the client-health report hook."
7885
7900
  },
7886
- {
7887
- "flags": "--client-health-sentry",
7888
- "description": "Send the opt-in client-health Sentry report from a doctor JSON document."
7889
- },
7890
- {
7891
- "flags": "--json-input <file>",
7892
- "description": "Doctor JSON input for the client-health report."
7893
- },
7894
- {
7895
- "flags": "--client-health-fix-outcome <outcome>",
7896
- "description": "Safe fix outcome label for the client-health report."
7897
- },
7898
7901
  {
7899
7902
  "flags": "--yes",
7900
7903
  "description": "Skip the interactive --fix confirmation (non-interactive use)."
@@ -6,6 +6,7 @@
6
6
  * hq auth logout — clear the cached HQ session
7
7
  * hq auth refresh — ensure a valid cached Cognito session (non-interactive)
8
8
  * hq auth status — show whether a valid session is cached + expiry
9
+ * hq auth machine-session --output <path> — write machine tokens to a private file
9
10
  *
10
11
  * Sign-up is owned by the onboarding web app at
11
12
  * https://onboarding.hq.computer. `hq auth login` signs an existing account
@@ -6,6 +6,7 @@
6
6
  * hq auth logout — clear the cached HQ session
7
7
  * hq auth refresh — ensure a valid cached Cognito session (non-interactive)
8
8
  * hq auth status — show whether a valid session is cached + expiry
9
+ * hq auth machine-session --output <path> — write machine tokens to a private file
9
10
  *
10
11
  * Sign-up is owned by the onboarding web app at
11
12
  * https://onboarding.hq.computer. `hq auth login` signs an existing account
@@ -13,9 +14,12 @@
13
14
  * session is kept valid by `hq auth refresh` / `hq-auth-refresh` and consumed
14
15
  * by the deploy + sync skills.
15
16
  */
17
+ import * as fs from "node:fs";
18
+ import { open, realpath } from "node:fs/promises";
19
+ import path from "node:path";
16
20
  import chalk from "chalk";
17
21
  import { browserLogin, clearCachedTokens, loadCachedTokens, isExpiring, loadMachineCreds, CognitoAuthError, } from "@indigoai-us/hq-cloud";
18
- import { DEFAULT_COGNITO, refreshCachedSession, resolveCognitoTokenSource, } from "../utils/cognito-session.js";
22
+ import { DEFAULT_COGNITO, refreshCachedSession, resolveCognitoTokenSource, ensureMachineSessionTokens, personTokenCacheFile, machineTokenCacheFile, } from "../utils/cognito-session.js";
19
23
  import { resolveLoginProviderSelection, } from "../utils/login-provider.js";
20
24
  import { getLastLoginProviderStatePath, loadLastLoginProvider, storeLastLoginProvider, } from "../lib/auth-login-provider-state.js";
21
25
  import { callbackPortBusyGuidance, DEFAULT_CALLBACK_PORT, isCallbackPortBusy, } from "../utils/callback-port-busy.js";
@@ -128,6 +132,77 @@ export function registerAuthCommands(program) {
128
132
  const authCmd = program
129
133
  .command("auth")
130
134
  .description("Manage the local HQ Cognito session");
135
+ authCmd
136
+ .command("machine-session")
137
+ .description("Write the existing machine identity session to a private file")
138
+ .requiredOption("--output <path>", "path for the private machine session JSON")
139
+ .action(async (options) => {
140
+ const requestedPath = path.resolve(options.output);
141
+ let handle;
142
+ try {
143
+ if (!loadMachineCreds()) {
144
+ throw new Error("Machine credentials are unavailable.");
145
+ }
146
+ const outputDirectory = await realpath(path.dirname(requestedPath));
147
+ const outputPath = path.join(outputDirectory, path.basename(requestedPath));
148
+ const protectedPaths = await Promise.all([personTokenCacheFile(), machineTokenCacheFile()].map(async (protectedPath) => {
149
+ const resolved = path.resolve(protectedPath);
150
+ try {
151
+ return path.join(await realpath(path.dirname(resolved)), path.basename(resolved));
152
+ }
153
+ catch (error) {
154
+ if (error.code === "ENOENT")
155
+ return resolved;
156
+ throw error;
157
+ }
158
+ }));
159
+ if (protectedPaths.includes(outputPath)) {
160
+ throw new Error("Refusing to overwrite a cached Cognito session.");
161
+ }
162
+ const tokens = await ensureMachineSessionTokens();
163
+ const contents = JSON.stringify({
164
+ accessToken: tokens.accessToken,
165
+ idToken: tokens.idToken,
166
+ expiresAt: tokens.expiresAt,
167
+ }) + "\n";
168
+ try {
169
+ handle = await open(outputPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL |
170
+ (fs.constants.O_NOFOLLOW ?? 0), 0o600);
171
+ }
172
+ catch (error) {
173
+ if (error.code !== "EEXIST")
174
+ throw error;
175
+ handle = await open(outputPath, fs.constants.O_WRONLY | (fs.constants.O_NOFOLLOW ?? 0));
176
+ const metadata = await handle.stat();
177
+ if (!metadata.isFile() ||
178
+ metadata.uid !== process.getuid?.() ||
179
+ (metadata.mode & 0o077) !== 0) {
180
+ throw new Error("Existing output file is not a private file owned by this user.");
181
+ }
182
+ await handle.truncate(0);
183
+ }
184
+ await handle.writeFile(contents, { encoding: "utf8" });
185
+ await handle.close();
186
+ handle = undefined;
187
+ }
188
+ catch (error) {
189
+ if (handle)
190
+ await handle.close().catch(() => undefined);
191
+ const code = error && typeof error === "object" && "code" in error &&
192
+ typeof error.code === "string" && /^[A-Z0-9_]+$/.test(error.code)
193
+ ? error.code
194
+ : undefined;
195
+ console.error(code === "EACCES" || code === "ELOOP" || code === "EEXIST"
196
+ ? "Could not write machine session file (unsafe or inaccessible path)."
197
+ : error instanceof Error && error.message === "Machine credentials are unavailable."
198
+ ? "Machine credentials are unavailable."
199
+ : error instanceof Error && (error.message === "Existing output file is not a private file owned by this user." ||
200
+ error.message === "Refusing to overwrite a cached Cognito session.")
201
+ ? error.message
202
+ : "Could not obtain or write machine session.");
203
+ process.exitCode = 1;
204
+ }
205
+ });
131
206
  authCmd
132
207
  .command("login")
133
208
  .description("Sign in to HQ — opens the Cognito Hosted UI and caches tokens locally")
@@ -33,9 +33,6 @@ import { PARITY_FAMILY_ID, PARITY_FAMILY_TITLE, runParityReplay, } from "../lib/
33
33
  import { applyFixes } from "../lib/doctor/fix/apply.js";
34
34
  import { renderDoctorFixJson } from "../lib/doctor/fix/json-output.js";
35
35
  import { clientHealthReasonCodesEnabled as isActiveCompanyClientHealthReasonCodesEnabled, clientHealthReasonCodesEnabledForCompany, } from "../lib/doctor/client-health-reason-codes-flag.js";
36
- import { CLIENT_HEALTH_SENTRY_MAX_INPUT_BYTES, CLIENT_HEALTH_SENTRY_NOT_HANDLED_EXIT_CODE, handleClientHealthSentryReport, } from "../lib/doctor/client-health-sentry.js";
37
- import { clientHealthSentryDirectEnabled, clientHealthSentryDirectEnabledForCompany, } from "../lib/doctor/client-health-sentry-flag.js";
38
- import { reportClientHealthEvents } from "../lib/core-utils/sentry-report.js";
39
36
  import { isCompanyJournalSlug } from "../lib/doctor/checks/sync-health.js";
40
37
  import { readCompaniesManifestMap } from "../lib/work-context/repo-remote.js";
41
38
  import * as readline from "node:readline";
@@ -244,26 +241,6 @@ function createClientHealthReasonCodeCompanyResolver(hqRoot) {
244
241
  });
245
242
  };
246
243
  }
247
- function createClientHealthSentryCompanyResolver(hqRoot) {
248
- const companies = readCompaniesManifestMap(hqRoot);
249
- return async (companySlug) => {
250
- if (companySlug === null)
251
- return clientHealthSentryDirectEnabled();
252
- if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(companySlug))
253
- return false;
254
- const manifestUid = companies?.[companySlug]?.cloud_uid;
255
- const activeSlug = process.env.HQ_COMPANY_SLUG?.trim() || "";
256
- const activeUid = process.env.HQ_COMPANY_UID?.trim() || "";
257
- const companyUid = typeof manifestUid === "string" && manifestUid.trim()
258
- ? manifestUid.trim()
259
- : companySlug === activeSlug
260
- ? activeUid
261
- : "";
262
- if (!/^cmp_[A-Za-z0-9]{3,128}$/.test(companyUid))
263
- return false;
264
- return clientHealthSentryDirectEnabledForCompany({ companyUid, companySlug });
265
- };
266
- }
267
244
  /** Register the top-level `hq doctor` command so it appears in `hq --help`. */
268
245
  export function registerDoctorCommand(program) {
269
246
  program
@@ -279,9 +256,6 @@ export function registerDoctorCommand(program) {
279
256
  .option("--only <families>", "Run only these comma-separated check families (e.g. hooks). Unknown names are rejected; --json then records the scope.")
280
257
  .option("--fix", "Apply the allowlisted safe repairs (backs up first; read-only without this flag).")
281
258
  .addOption(new Option("--client-health-report", "Resolve the opt-in hq-flags gate used by the client-health report hook.").hideHelp())
282
- .addOption(new Option("--client-health-sentry", "Send the opt-in client-health Sentry report from a doctor JSON document.").hideHelp())
283
- .addOption(new Option("--json-input <file>", "Doctor JSON input for the client-health report.").hideHelp())
284
- .addOption(new Option("--client-health-fix-outcome <outcome>", "Safe fix outcome label for the client-health report.").hideHelp())
285
259
  .option("--yes", "Skip the interactive --fix confirmation (non-interactive use).")
286
260
  .option("--force", "Let --fix run despite uncommitted changes under .claude/, .codex/, or .grok/.")
287
261
  .action(async (opts) => {
@@ -298,54 +272,6 @@ export function registerDoctorCommand(program) {
298
272
  process.exitCode = 1;
299
273
  return;
300
274
  }
301
- if (opts.clientHealthSentry === true) {
302
- const hqRoot = resolveHqRoot();
303
- if (!hqRoot || !opts.jsonInput) {
304
- process.stderr.write("hq doctor --client-health-sentry requires an HQ root and --json-input.\n");
305
- process.exitCode = 1;
306
- return;
307
- }
308
- try {
309
- const workspacePath = path.join(hqRoot, "workspace");
310
- const workspaceStat = fs.lstatSync(workspacePath);
311
- if (!workspaceStat.isDirectory() || workspaceStat.isSymbolicLink()) {
312
- throw new Error("workspace is not a local directory");
313
- }
314
- const statePath = path.join(hqRoot, "workspace", ".hq-client-health");
315
- const stateStat = fs.lstatSync(statePath);
316
- if (!stateStat.isDirectory() || stateStat.isSymbolicLink()) {
317
- throw new Error("client-health state is not a local directory");
318
- }
319
- const stateRoot = fs.realpathSync(statePath);
320
- const inputPath = fs.realpathSync(opts.jsonInput);
321
- const relativeInput = path.relative(stateRoot, inputPath);
322
- if (relativeInput === ".." || relativeInput.startsWith(`..${path.sep}`) || path.isAbsolute(relativeInput)) {
323
- throw new Error("input path is outside client-health state");
324
- }
325
- if (fs.statSync(inputPath).size > CLIENT_HEALTH_SENTRY_MAX_INPUT_BYTES) {
326
- throw new Error("input exceeds client-health size limit");
327
- }
328
- const result = await handleClientHealthSentryReport({
329
- hqRoot,
330
- doctorJson: fs.readFileSync(inputPath, "utf8"),
331
- fixOutcome: opts.clientHealthFixOutcome ?? "not_auto_fixable",
332
- }, {
333
- resolveFlagForCompany: createClientHealthSentryCompanyResolver(hqRoot),
334
- sendEvents: async (events) => (await reportClientHealthEvents(events)).code === 0,
335
- });
336
- process.exitCode =
337
- result === "not_handled"
338
- ? CLIENT_HEALTH_SENTRY_NOT_HANDLED_EXIT_CODE
339
- : result === "send_failed"
340
- ? 1
341
- : 0;
342
- }
343
- catch {
344
- process.stderr.write("hq doctor --client-health-sentry could not handle the report.\n");
345
- process.exitCode = 1;
346
- }
347
- return;
348
- }
349
275
  // `--fix` is the only write path. It resolves the tree, applies the
350
276
  // allowlisted repairs behind a backup + confirmation, and returns its own
351
277
  // exit code; the read-only report below never runs in this branch.
@@ -10,12 +10,11 @@ import { isPidAlive, runWatcherLoop, WATCHER_OUT_MAX_BYTES, watcherOutLogPath, w
10
10
  import { laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
11
11
  import { laneRunDir, probePidRecord, readPidRecord, readProcStarttime, spawnLaneWorker } from "../lib/lanes/spawn.js";
12
12
  import { keepAliveExclusion, keepAliveSupersededBy, LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_FLAG, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds } from "../lib/lanes/keep-alive.js";
13
- import { managerStatusGateEnabledForLane } from "../lib/lanes/manager-status-gate.js";
14
13
  import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
15
14
  import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
16
15
  import { accountMoveSummary, accountRequestKeepsCurrentLane, assertCurrentResumeAccountAvailable, callerMatchesSessionSenior, recordedResumeEffort, resetKeepAliveProgress, resolveResumeEffort } from "./lanes-shared.js";
17
16
  export function createLaneMessageHandlers(deps) {
18
- const { archiveResumeMessage, assertLaneRunsHereForAdmission, assertLaneRunsHereForOperatorMutation, assessLaneAdmission, casUpdateLane, dispatchOutpostLaneCommand, drainHookStatusFor, laneIsHostedHere, laneWithRecoveredProviderSession, loadAllLanes, mintMessageId, persistClaudeAccountSelection, prepareAccountMove, printJson, recordResumeSetup, resolveActiveQuestionCompany, resolveResumeProfile, selectAutomaticClaudeResumeAccount, validateResumeProfileModel, withResumeHireAdmission } = deps;
17
+ const { archiveResumeMessage, assertLaneRunsHereForAdmission, assertLaneRunsHereForOperatorMutation, assessLaneAdmission, casUpdateLane, dispatchOutpostLaneCommand, dispatchPrimaryLaneMessage, drainHookStatusFor, laneIsHostedHere, laneWithRecoveredProviderSession, loadAllLanes, mintMessageId, persistClaudeAccountSelection, prepareAccountMove, printJson, recordResumeSetup, resolveActiveQuestionCompany, resolveResumeProfile, selectAutomaticClaudeResumeAccount, validateResumeProfileModel, withResumeHireAdmission } = deps;
19
18
  function currentSuccessorOf(lane, lanes, lineageAware, companyForLane) {
20
19
  const validLanes = lanes.filter((candidate) => Number.isFinite(Date.parse(candidate.created_at)));
21
20
  const newestFirst = [...validLanes].sort((left, right) => Date.parse(right.created_at) - Date.parse(left.created_at));
@@ -221,7 +220,27 @@ export function createLaneMessageHandlers(deps) {
221
220
  if (!text) {
222
221
  throw new LanesError("missing_text", "--text is required", {});
223
222
  }
224
- const resolution = await deps.resolveCommandLane(ctx, laneId);
223
+ let resolution;
224
+ try {
225
+ resolution = await deps.resolveCommandLane(ctx, laneId);
226
+ }
227
+ catch (err) {
228
+ const identity = ctx.readOutpostIdentity();
229
+ const failedOutpostIds = err instanceof LanesError ? err.details.failed_outpost_ids : undefined;
230
+ const probedOutpostIds = err instanceof LanesError ? err.details.probed_outpost_ids : undefined;
231
+ const primaryLookupFailed = Array.isArray(failedOutpostIds) && failedOutpostIds.includes("primary");
232
+ const primaryReportedMissing = Array.isArray(probedOutpostIds) && probedOutpostIds.includes("primary");
233
+ if (!(err instanceof LanesError) ||
234
+ err.error !== "lane_not_found" ||
235
+ identity.kind !== "id" ||
236
+ identity.id === "primary" ||
237
+ primaryLookupFailed ||
238
+ primaryReportedMissing) {
239
+ throw err;
240
+ }
241
+ await dispatchPrimaryLaneMessage(ctx, laneId, { ...opts, text });
242
+ return;
243
+ }
225
244
  let lane = resolution.lane;
226
245
  if (resolution.remoteOnly || (lane.host.kind === "outpost" && !laneIsHostedHere(ctx, lane))) {
227
246
  const callerLaneId = ctx.env.HQ_LANE_ID?.trim();
@@ -465,7 +484,6 @@ export function createLaneMessageHandlers(deps) {
465
484
  markAccepted();
466
485
  }
467
486
  else {
468
- const managerStatusGateEnabled = await managerStatusGateEnabledForLane(hqRoot, lane, { env: ctx.env });
469
487
  const dispatched = await dispatchLaneDelivery(lane, {
470
488
  dropbox: () => {
471
489
  // Live (or about-to-be-live) process: the PreToolUse drain delivers it.
@@ -523,7 +541,6 @@ export function createLaneMessageHandlers(deps) {
523
541
  }
524
542
  const started = spawnLaneWorker(hqRoot, lane, {
525
543
  env: ctx.env,
526
- managerStatusGateEnabled,
527
544
  modelDeps: {
528
545
  readFileSync: ctx.readFileSync,
529
546
  probe: ctx.probeProvider,
@@ -624,6 +641,7 @@ export function createLaneMessageHandlers(deps) {
624
641
  accepted: true,
625
642
  delivered: false,
626
643
  delivery: mechanism,
644
+ delivered_to: { host: lane.host.id, lane_id: laneId, mechanism },
627
645
  delivery_status: deliveryStatus,
628
646
  inbox_path: mechanism === "dropbox" ? dropboxInboxPath(hqRoot, laneId) : null,
629
647
  drain_hook: drainHook,
@@ -5,7 +5,6 @@ import { parseCliEffort } from "../lib/lanes/effort.js";
5
5
  import { parseOutpostHireGrantContext } from "../lib/lanes/hire-grant-dispatch.js";
6
6
  import { assertNoSecretText } from "../lib/lanes/text-scan.js";
7
7
  import { laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
8
- import { managerStatusGateEnabledForLane } from "../lib/lanes/manager-status-gate.js";
9
8
  import { spawnLaneWorker } from "../lib/lanes/spawn.js";
10
9
  import { accountMoveSummary, accountRequestKeepsCurrentLane, assertCurrentResumeAccountAvailable, hasKeepAliveProgress, recordedResumeEffort, resetKeepAliveProgress, resolveResumeEffort } from "./lanes-shared.js";
11
10
  export function createLaneResumeHandlers(deps) {
@@ -328,7 +327,6 @@ export function createLaneResumeHandlers(deps) {
328
327
  resumed = lane;
329
328
  }
330
329
  else {
331
- const managerStatusGateEnabled = await managerStatusGateEnabledForLane(hqRoot, lane, { env: ctx.env });
332
330
  resumed = await withLaneAdmissionLock(hqRoot, lane.provider, async () => {
333
331
  const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
334
332
  if (admission.action === "refuse" && admission.error)
@@ -376,7 +374,6 @@ export function createLaneResumeHandlers(deps) {
376
374
  }
377
375
  return spawnLaneWorker(hqRoot, lane, {
378
376
  env: ctx.env,
379
- managerStatusGateEnabled,
380
377
  modelDeps: {
381
378
  readFileSync: ctx.readFileSync,
382
379
  probe: ctx.probeProvider,
@@ -157,6 +157,7 @@ export interface LaneMessageHandlerDeps {
157
157
  }): Promise<AdmissionResult>;
158
158
  casUpdateLane(hqRoot: string, laneId: string, updater: (current: LaneRecord) => LaneRecord): LaneRecord;
159
159
  dispatchOutpostLaneCommand(ctx: Ctx, lane: LaneRecord, action: "message" | "resume", options: LaneMessageOptions | LaneResumeOptions, behavior?: OutpostLaneDispatchBehavior): Promise<void>;
160
+ dispatchPrimaryLaneMessage(ctx: Ctx, laneId: string, options: LaneMessageOptions): Promise<void>;
160
161
  drainHookStatusFor(ctx: Ctx, lane: LaneRecord): {
161
162
  registered: boolean;
162
163
  kind: string;
@@ -248,8 +249,6 @@ export interface Ctx {
248
249
  backfillOutpostIdentity: (expectedId: string) => "created" | "unchanged";
249
250
  fetchText: (url: string) => Promise<string>;
250
251
  lanePlacementEnabled: (company: LaneCompanyRef | undefined) => Promise<boolean>;
251
- outpostSnapshotCacheEnabled: () => Promise<boolean>;
252
- startOutpostLaneSnapshotRefresh: (hqRoot: string) => void;
253
252
  storyLedgerEnabled: () => boolean;
254
253
  isSsmOwnerDispatch: () => boolean;
255
254
  withGrantHireLocks: typeof withLaneHireLocks;
@@ -14,7 +14,6 @@ import type { OutpostIdentity } from "../lib/lanes/admission-host.js";
14
14
  export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
15
15
  export type { OutpostIdentity } from "../lib/lanes/admission-host.js";
16
16
  import { type OutpostDispatchResult } from "../lib/lanes/outpost-dispatch.js";
17
- import { LANE_OUTPOST_SNAPSHOT_CACHE_FLAG } from "../lib/lanes/outpost-hire-cache.js";
18
17
  import { emitLaneDecisionEvents } from "../lib/lanes/mesh-events.js";
19
18
  import { MAX_LANE_DEPTH, withLaneHireLocks, type LaneCompanyRef, type QuestionBody, type LaneRecord, type QuestionRecord, type ResolvedLaneWorker, type SeniorRef, type LivenessDeps, type MachineIdentity, type MonitorEvidenceReader, type RunCommand, type ThrottleDeps } from "../lib/lanes/index.js";
20
19
  import { questionBodyAbsPath } from "../lib/lanes/questions.js";
@@ -62,7 +61,7 @@ export interface LanesCommandDeps {
62
61
  /** Test seam for worker PID identity checks. */
63
62
  readStarttime?: (pid: number) => number | null;
64
63
  /** Test seams for synchronous dead-worker classification and repair. */
65
- watcherLoopDeps?: Pick<WatcherLoopDeps, "sleep" | "spawnRepair" | "readManagerStatusGateFlag">;
64
+ watcherLoopDeps?: Pick<WatcherLoopDeps, "sleep" | "spawnRepair">;
66
65
  stopSleepMs?: (ms: number) => void;
67
66
  livenessDeps?: LivenessDeps;
68
67
  presenceConnected?: boolean;
@@ -88,10 +87,6 @@ export interface LanesCommandDeps {
88
87
  monitorEvidence?: MonitorEvidenceReader;
89
88
  /** Test seam for the default-off outpost placement rollout flag. */
90
89
  lanePlacementEnabled?: (company: LaneCompanyRef | undefined) => Promise<boolean>;
91
- /** Test seam for the default-off shared Outpost snapshot cache. */
92
- outpostSnapshotCacheEnabled?: () => Promise<boolean>;
93
- /** Test seam for the detached refresh started by the cache-first lane list. */
94
- startOutpostLaneSnapshotRefresh?: (hqRoot: string) => void;
95
90
  /** Test seam for the default-off story ledger flag. */
96
91
  storyLedgerEnabled?: () => boolean;
97
92
  /** Test seam for the SSM-only Outpost owner grant path. */
@@ -103,7 +98,6 @@ export interface LanesCommandDeps {
103
98
  /** Test seam for the repository-merge-hold registry flag. */
104
99
  mergeHoldEnabled?: (company: LaneCompanyRef) => boolean | Promise<boolean>;
105
100
  }
106
- export { LANE_OUTPOST_SNAPSHOT_CACHE_FLAG };
107
101
  export declare function parseSeniorRef(raw: string): SeniorRef;
108
102
  export declare function parseQuestionBody(raw: unknown): QuestionBody | null;
109
103
  /**
@@ -148,8 +142,6 @@ export declare function pickupFailureUpdater(reasonRaw: string, details: Record<
148
142
  */
149
143
  export declare function applyOutpostAccountPickup(hqRoot: string, laneId: string, modelDeps?: KnownModelsDeps): LaneRecord;
150
144
  export declare function cancelPendingQuestionsForLane(hqRoot: string, laneId: string, nowIso: string, operatorReason?: string): QuestionRecord[];
151
- /** Keeps the placement-enabled cache-first path on the full lane command graph. */
152
- export declare function runFastCachedLanesListFromEntrypoint(): Promise<void>;
153
145
  export declare function stopCommittedState(state: LaneRecord["state"]): "cancelled" | "killed";
154
146
  /** Stderr notice that `stop` ends a lane for good, naming the resumable path. */
155
147
  export declare function stopTerminalWarning(laneId: string): string;