@indigoai-us/hq-cli 5.293.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 (97) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +5 -1
  3. package/dist/command-catalog.generated.d.ts +54 -33
  4. package/dist/command-catalog.generated.js +68 -42
  5. package/dist/commands/auth.d.ts +1 -0
  6. package/dist/commands/auth.js +76 -1
  7. package/dist/commands/core-command-catalog.js +3 -2
  8. package/dist/commands/core-delegate-w1b.d.ts +2 -0
  9. package/dist/commands/core-delegate-w1b.js +28 -2
  10. package/dist/commands/core-delegate.d.ts +3 -1
  11. package/dist/commands/core-delegate.js +35 -5
  12. package/dist/commands/core-handoff-fast.d.ts +18 -0
  13. package/dist/commands/core-handoff-fast.js +103 -0
  14. package/dist/commands/core-handoff-open-steps.d.ts +21 -0
  15. package/dist/commands/core-handoff-open-steps.js +264 -0
  16. package/dist/commands/core-handoff.d.ts +4 -2
  17. package/dist/commands/core-handoff.js +22 -249
  18. package/dist/commands/core-jobs-native.d.ts +2 -0
  19. package/dist/commands/core-jobs-native.js +22 -11
  20. package/dist/commands/core-knowledge-prefs.d.ts +2 -0
  21. package/dist/commands/core-knowledge-prefs.js +13 -1
  22. package/dist/commands/core-ontology-native.d.ts +2 -0
  23. package/dist/commands/core-ontology-native.js +22 -2
  24. package/dist/commands/core-policy.d.ts +2 -0
  25. package/dist/commands/core-policy.js +18 -1
  26. package/dist/commands/core.js +2 -1
  27. package/dist/commands/daemon.d.ts +2 -1
  28. package/dist/commands/daemon.js +22 -7
  29. package/dist/commands/doctor.js +0 -74
  30. package/dist/commands/lanes-autopilot.d.ts +11 -0
  31. package/dist/commands/lanes-autopilot.js +336 -0
  32. package/dist/commands/lanes-message.js +23 -5
  33. package/dist/commands/lanes-questions.js +8 -5
  34. package/dist/commands/lanes-resume.js +0 -3
  35. package/dist/commands/lanes-shared.d.ts +18 -2
  36. package/dist/commands/lanes-stories.d.ts +14 -0
  37. package/dist/commands/lanes-stories.js +308 -0
  38. package/dist/commands/lanes.d.ts +1 -9
  39. package/dist/commands/lanes.js +216 -481
  40. package/dist/fast-core.js +53 -25
  41. package/dist/fast-scaffold.d.ts +15 -7
  42. package/dist/fast-scaffold.js +167 -17
  43. package/dist/index.js +1 -10
  44. package/dist/lib/core-utils/sentry-report.d.ts +0 -19
  45. package/dist/lib/core-utils/sentry-report.js +8 -61
  46. package/dist/lib/core-utils/worktree-resume.d.ts +2 -0
  47. package/dist/lib/core-utils/worktree-resume.js +16 -0
  48. package/dist/lib/daemon/migrate.d.ts +8 -1
  49. package/dist/lib/daemon/migrate.js +19 -7
  50. package/dist/lib/doctor/checks/claude-wiring.js +9 -0
  51. package/dist/lib/lanes/autopilot-metrics.d.ts +99 -0
  52. package/dist/lib/lanes/autopilot-metrics.js +301 -0
  53. package/dist/lib/lanes/flag-keys.d.ts +2 -0
  54. package/dist/lib/lanes/flag-keys.js +2 -0
  55. package/dist/lib/lanes/manager-status-gate.d.ts +0 -17
  56. package/dist/lib/lanes/manager-status-gate.js +2 -43
  57. package/dist/lib/lanes/outpost-dispatch.d.ts +2 -0
  58. package/dist/lib/lanes/outpost-dispatch.js +7 -1
  59. package/dist/lib/lanes/outpost-hire-cache.d.ts +0 -1
  60. package/dist/lib/lanes/outpost-hire-cache.js +0 -1
  61. package/dist/lib/lanes/outpost-snapshot-memo.d.ts +6 -0
  62. package/dist/lib/lanes/outpost-snapshot-memo.js +14 -0
  63. package/dist/lib/lanes/reconcile.d.ts +2 -3
  64. package/dist/lib/lanes/reconcile.js +44 -38
  65. package/dist/lib/lanes/spawn.d.ts +0 -2
  66. package/dist/lib/lanes/spawn.js +18 -5
  67. package/dist/lib/lanes/store.d.ts +2 -0
  68. package/dist/lib/lanes/store.js +54 -3
  69. package/dist/lib/lanes/types.d.ts +18 -1
  70. package/dist/lib/lanes/types.js +1 -1
  71. package/dist/lib/lanes/watcher.d.ts +4 -3
  72. package/dist/lib/lanes/watcher.js +102 -34
  73. package/dist/lib/mesh/live/daemon/run.js +18 -1
  74. package/dist/native-fast-route.d.ts +3 -1
  75. package/dist/native-fast-route.js +10 -8
  76. package/dist/utils/cognito-session.d.ts +6 -0
  77. package/dist/utils/cognito-session.js +9 -0
  78. package/dist/utils/contribution-table.d.ts +1 -1
  79. package/dist/utils/fast-stream-errors.d.ts +12 -0
  80. package/dist/utils/fast-stream-errors.js +18 -0
  81. package/dist/utils/scaffold-fast-import-guard.d.ts +2 -2
  82. package/dist/utils/scaffold-fast-import-guard.js +6 -3
  83. package/dist/utils/self-update.js +31 -0
  84. package/dist/utils/version-gate.js +22 -3
  85. package/package.json +1 -1
  86. package/dist/fast-lanes-list-route.d.ts +0 -8
  87. package/dist/fast-lanes-list-route.js +0 -32
  88. package/dist/fast-lanes-list.d.ts +0 -3
  89. package/dist/fast-lanes-list.js +0 -224
  90. package/dist/lib/doctor/client-health-sentry-flag.d.ts +0 -23
  91. package/dist/lib/doctor/client-health-sentry-flag.js +0 -99
  92. package/dist/lib/doctor/client-health-sentry.d.ts +0 -71
  93. package/dist/lib/doctor/client-health-sentry.js +0 -286
  94. package/dist/lib/lanes/outpost-snapshot-cache.d.ts +0 -26
  95. package/dist/lib/lanes/outpost-snapshot-cache.js +0 -215
  96. package/dist/lib/lanes/outpost-snapshot-refresh.d.ts +0 -21
  97. package/dist/lib/lanes/outpost-snapshot-refresh.js +0 -329
@@ -37,7 +37,7 @@ import { LanesError } from "./errors.js";
37
37
  import { questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
38
38
  import { isTerminalLaneState, ENVELOPE_INVALID_REASON, parseLaneEnvelope, STORY_STATUSES, LANE_PROVIDERS, } from "./types.js";
39
39
  import { laneStories, storyStatusForEnvelope } from "./stories.js";
40
- import { appendCappedHistory, readLane, retryUpdateLane, withLaneHireLocks } from "./store.js";
40
+ import { appendCappedHistory, listQuestionIds, readLane, readQuestion, retryUpdateLane, withLaneHireLocks } from "./store.js";
41
41
  import { enforceRegistryApprovalGate } from "./approval-gate.js";
42
42
  import { readWorkerRunResult } from "./delivery.js";
43
43
  import { applyDefaultQuotaHold, quotaSignalFromFields, readQuotaSignal, recordAccountQuota, } from "./quota.js";
@@ -51,13 +51,15 @@ import { readLanesRegistryFlag } from "./registry-flag.js";
51
51
  import { laneCompanyUidSource, resolveLaneCompany } from "./lane-company.js";
52
52
  import { blockerFreshnessFingerprint, resolveBlockerFreshnessForLane, } from "./blocker-freshness.js";
53
53
  import { krFreshnessFingerprint, resolveKrFreshnessForLane, } from "./kr-freshness.js";
54
- import { MANAGER_STATUS_GATE_FLAG, managerStatusWasWritten, readStatusSnapshot, resolveManagerStatusTarget, managerStatusGateEnabledForLane, } from "./manager-status-gate.js";
54
+ import { managerStatusWasWritten, readStatusSnapshot, resolveManagerStatusTarget, } from "./manager-status-gate.js";
55
55
  import { queueLaneTerminalResume, queueManagerStatusGateResume } from "./keep-alive.js";
56
+ import { LANE_ENVELOPE_REPAIR_CONTINUE_FLAG } from "./flag-keys.js";
56
57
  import { monitorSessionDir } from "../monitor.js";
57
58
  import { laneRunDir, missingPidProbe, probePidRecord, readPidRecord, readProcStarttime, readSpawnManifest, envelopeRepairMode, spawnEnvelopeRepair, writePidRecord, retryLaneOnAlternateAccount, } from "./spawn.js";
58
59
  /** Spec §5.7: no signal for 20 minutes while the process is still alive. */
59
60
  export const SILENCE_MS = 20 * 60 * 1000;
60
61
  export const WATCHER_POLL_MS = 5_000;
62
+ export const ENVELOPE_REPAIR_CONTINUE_TEXT = "continue the task; the previous turn ended without an envelope.";
61
63
  /** Synced last_host_report_at cadence. Reconcile dead-host TTL is 20 minutes. */
62
64
  export const WATCHER_HEARTBEAT_MS = 60 * 1000;
63
65
  /**
@@ -72,6 +74,7 @@ export const WATCHER_DECISION_EVENTS = [
72
74
  "envelope_observed",
73
75
  "envelope_invalid",
74
76
  "envelope_repair",
77
+ "envelope_repair_continue",
75
78
  "classification",
76
79
  "terminal_write",
77
80
  "message_recovery",
@@ -659,26 +662,9 @@ export function missingExpectedEnvelopeFile(hqRoot, laneId, envelopePath) {
659
662
  };
660
663
  }
661
664
  async function settleManagerStatusGate(hqRoot, lane, manifestTurnId, at, deps, logDecision) {
662
- const readFlag = deps.readManagerStatusGateFlag ?? readLanesRegistryFlag;
663
- let enabled = false;
664
- try {
665
- const resolution = resolveLaneCompany(hqRoot, lane);
666
- const company = resolution.company ?? undefined;
667
- enabled = await readFlag(MANAGER_STATUS_GATE_FLAG, {
668
- company,
669
- ...(company ? {
670
- companyUidSource: laneCompanyUidSource(resolution.source),
671
- } : {}),
672
- });
673
- }
674
- catch (err) {
675
- process.stderr.write(`hq lanes watcher: manager status gate flag read failed for ${lane.lane_id}: ${err instanceof Error ? err.message : String(err)}\n`);
676
- logDecision({ event: "status_gate_flag_failed", at, error: err instanceof Error ? err.name : "UnknownError" });
677
- }
678
- if (!enabled)
679
- return lane;
680
665
  let target;
681
666
  try {
667
+ resolveLaneCompany(hqRoot, lane);
682
668
  target = resolveManagerStatusTarget(hqRoot, lane);
683
669
  }
684
670
  catch (err) {
@@ -725,8 +711,8 @@ async function settleManagerStatusGate(hqRoot, lane, manifestTurnId, at, deps, l
725
711
  turn_id: gate.turn_id,
726
712
  path: gate.status_path,
727
713
  consecutive_misses: updated.status_gate?.consecutive_misses ?? 0,
728
- resume_queued: miss.resume_queued === true,
729
- resume_skipped_reason: miss.resume_skipped_reason,
714
+ resume_queued: "resume_queued" in miss && miss.resume_queued === true,
715
+ resume_skipped_reason: "resume_skipped_reason" in miss ? miss.resume_skipped_reason : undefined,
730
716
  state: updated.state,
731
717
  });
732
718
  }
@@ -1058,6 +1044,92 @@ function claimWatcherLoop(hqRoot, laneId, deps) {
1058
1044
  });
1059
1045
  };
1060
1046
  }
1047
+ async function maybeContinueAfterMissingEnvelopeRepair(hqRoot, lane, envelope, envelopeRef, at, deps, logDecision) {
1048
+ const repair = lane.envelope_repair;
1049
+ const missingRepair = repair?.attempted === true &&
1050
+ repair.succeeded === true &&
1051
+ repair.envelope_ref === envelopeRef &&
1052
+ repair.first_error.startsWith("Missing expected envelope file:");
1053
+ if (!missingRepair || envelope === null || envelopeRef === undefined) {
1054
+ if (envelope?.decision === "blocked")
1055
+ logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "not_a_successful_missing_repair", missing_repair: missingRepair, envelope_ref: envelopeRef, repair_ref: repair?.envelope_ref, first_error: repair?.first_error, succeeded: repair?.succeeded });
1056
+ return lane;
1057
+ }
1058
+ const incompleteDone = envelope.decision === "done" && envelope.story_complete === false;
1059
+ const repairInstructionBlock = envelope.decision === "blocked" &&
1060
+ (envelope.questions?.length ?? 0) === 0 &&
1061
+ /(?:not to resume|do not resume|don't resume)(?: the)? task/i.test(envelope.blocked_reason ?? "");
1062
+ if (!incompleteDone && !repairInstructionBlock) {
1063
+ if (envelope.decision === "blocked")
1064
+ logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "blocked_reason_not_repair_instruction", blocked_reason: envelope.blocked_reason });
1065
+ return lane;
1066
+ }
1067
+ if (lane.last_resume?.text === ENVELOPE_REPAIR_CONTINUE_TEXT) {
1068
+ logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "continue_already_used" });
1069
+ return lane;
1070
+ }
1071
+ let pendingQuestion = false;
1072
+ try {
1073
+ for (const id of listQuestionIds(hqRoot)) {
1074
+ const question = readQuestion(hqRoot, id);
1075
+ if (question.lane_id === lane.lane_id && question.status === "pending") {
1076
+ pendingQuestion = true;
1077
+ break;
1078
+ }
1079
+ }
1080
+ }
1081
+ catch (error) {
1082
+ logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "question_check_failed", error: error instanceof Error ? error.message : String(error) });
1083
+ return lane;
1084
+ }
1085
+ if (pendingQuestion) {
1086
+ logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "pending_question" });
1087
+ return lane;
1088
+ }
1089
+ const readFlag = deps.readEnvelopeRepairContinueFlag ?? readLanesRegistryFlag;
1090
+ let enabled = false;
1091
+ try {
1092
+ const resolution = resolveLaneCompany(hqRoot, lane);
1093
+ const company = resolution.company ?? undefined;
1094
+ enabled = await readFlag(LANE_ENVELOPE_REPAIR_CONTINUE_FLAG, {
1095
+ company,
1096
+ ...(company ? { companyUidSource: laneCompanyUidSource(resolution.source) } : {}),
1097
+ });
1098
+ }
1099
+ catch (error) {
1100
+ logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "flag_read_failed", error: error instanceof Error ? error.message : String(error) });
1101
+ return lane;
1102
+ }
1103
+ if (!enabled)
1104
+ return lane;
1105
+ const queued = retryUpdateLane(hqRoot, lane.lane_id, "watcher", (current) => {
1106
+ if (current.version !== lane.version ||
1107
+ (current.state !== "done" && current.state !== "blocked") ||
1108
+ current.last_envelope?.ref !== envelopeRef ||
1109
+ current.envelope_repair?.attempted !== true ||
1110
+ current.envelope_repair.succeeded !== true ||
1111
+ current.envelope_repair.envelope_ref !== envelopeRef ||
1112
+ current.envelope_repair.first_error !== repair.first_error ||
1113
+ current.last_resume?.text === ENVELOPE_REPAIR_CONTINUE_TEXT)
1114
+ return current;
1115
+ const result = queueLaneTerminalResume(current, at, ENVELOPE_REPAIR_CONTINUE_TEXT, { allowBlockedResume: true });
1116
+ if (!result.resumeQueued || result.lane.pending_resume?.session_id === undefined)
1117
+ return current;
1118
+ return {
1119
+ ...result.lane,
1120
+ status_gate_history: appendCappedHistory(current.status_gate_history ?? [], {
1121
+ event: "envelope_repair_continued",
1122
+ at,
1123
+ envelope_ref: envelopeRef,
1124
+ session_id: result.lane.pending_resume.session_id,
1125
+ }),
1126
+ };
1127
+ });
1128
+ if (queued.state === "queued" && queued.pending_resume?.text === ENVELOPE_REPAIR_CONTINUE_TEXT) {
1129
+ logDecision({ event: "envelope_repair_continue", at, outcome: "queued", envelope_ref: envelopeRef, session_id: queued.pending_resume.session_id });
1130
+ }
1131
+ return queued;
1132
+ }
1061
1133
  /** Detached and synchronous classifiers share one process-identity claim. */
1062
1134
  export async function runWatcherLoop(hqRoot, laneId, deps = {}) {
1063
1135
  const release = claimWatcherLoop(hqRoot, laneId, deps);
@@ -1144,17 +1216,10 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
1144
1216
  }
1145
1217
  const progressIntervalMs = deps.progressEventMinIntervalMs ?? readProgressIntervalMs(hqRoot, readLaneSoft(hqRoot, laneId));
1146
1218
  const spawnRepair = deps.spawnRepair ?? spawnEnvelopeRepair;
1147
- const retryAccount = deps.retryAccount ?? (async (root, lane, reason) => {
1148
- const managerStatusGateEnabled = await managerStatusGateEnabledForLane(root, lane, {
1149
- env: deps.retrySpawnDeps?.env,
1150
- readFlag: deps.readManagerStatusGateFlag,
1151
- });
1152
- return retryLaneOnAlternateAccount(root, lane, reason, "watcher", {
1153
- ...deps.retrySpawnDeps,
1154
- managerStatusGateEnabled,
1155
- admissionHost: (deps.resolveAdmissionHost ?? resolveProcessAdmissionHost)(),
1156
- });
1157
- });
1219
+ const retryAccount = deps.retryAccount ?? ((root, lane, reason) => retryLaneOnAlternateAccount(root, lane, reason, "watcher", {
1220
+ ...deps.retrySpawnDeps,
1221
+ admissionHost: (deps.resolveAdmissionHost ?? resolveProcessAdmissionHost)(),
1222
+ }));
1158
1223
  let lastSignalMs = now();
1159
1224
  let lastSeenMtime = 0;
1160
1225
  let lastAlive = null;
@@ -1542,7 +1607,10 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
1542
1607
  ((entry.event === "status_missed" && entry.resume_queued === true) ||
1543
1608
  (entry.event === "blocker_stale" && entry.resume_queued === true) ||
1544
1609
  (entry.event === "kr_stalled" && entry.resume_queued === true))) === true;
1545
- if (terminalWrite.changed && deps.recoverQueuedMessages && !managerStatusFollowupQueued) {
1610
+ if (terminalWrite.changed && !managerStatusFollowupQueued) {
1611
+ updated = await maybeContinueAfterMissingEnvelopeRepair(hqRoot, updated, effectiveEnvelope ?? null, latest?.ref, tick.emit.at, deps, logDecision);
1612
+ }
1613
+ if (terminalWrite.changed && deps.recoverQueuedMessages && !managerStatusFollowupQueued && updated.state !== "queued") {
1546
1614
  try {
1547
1615
  if (dropboxInboxHasMessages(hqRoot, laneId)) {
1548
1616
  await deps.recoverQueuedMessages(updated);
@@ -701,13 +701,30 @@ export async function runMeshDaemon(deps = {}) {
701
701
  if (!fs.existsSync(spoolPath)) {
702
702
  fs.writeFileSync(spoolPath, "", { mode: 0o600 });
703
703
  }
704
- watcher = fs.watch(path.dirname(spoolPath), { persistent: true }, (_event, filename) => {
704
+ const spoolWatcher = fs.watch(path.dirname(spoolPath), { persistent: true }, (_event, filename) => {
705
705
  if (stopped)
706
706
  return;
707
707
  if (shouldFlushOnSpoolDirEvent(filename)) {
708
708
  scheduleDebouncedFlush();
709
709
  }
710
710
  });
711
+ watcher = spoolWatcher;
712
+ spoolWatcher.on("error", (err) => {
713
+ // fs.watch can fail asynchronously after returning a watcher. Without
714
+ // an error listener Node treats EMFILE as an uncaught exception; the
715
+ // periodic flush below already provides a polling fallback.
716
+ if (watcher === spoolWatcher)
717
+ watcher = null;
718
+ try {
719
+ spoolWatcher.close();
720
+ }
721
+ catch (closeError) {
722
+ const closeMessage = closeError instanceof Error ? closeError.message : String(closeError);
723
+ log(dir, `fs.watch close failed after watcher error: ${closeMessage.slice(0, 120)}`);
724
+ }
725
+ const msg = err instanceof Error ? err.message : String(err);
726
+ log(dir, `fs.watch error; continuing interval flush: ${msg.slice(0, 120)}`);
727
+ });
711
728
  }
712
729
  catch (err) {
713
730
  const msg = err instanceof Error ? err.message : String(err);
@@ -1,5 +1,7 @@
1
1
  /** Default-off flag check for native entrypoint routes. */
2
2
  export declare function nativeFastRouteEnabled(argv: readonly string[], env?: NodeJS.ProcessEnv): Promise<boolean>;
3
3
  /** Reads a default-off fast-route gate from its local override or fresh snapshot. */
4
- export declare function fastFlagEnabled(flagKey: string, env?: NodeJS.ProcessEnv): Promise<boolean>;
4
+ export declare function fastFlagEnabled(flagKey: string, env?: NodeJS.ProcessEnv, options?: {
5
+ numericEnvironmentOverride?: boolean;
6
+ }): Promise<boolean>;
5
7
  //# sourceMappingURL=native-fast-route.d.ts.map
@@ -8,8 +8,8 @@ export async function nativeFastRouteEnabled(argv, env = process.env) {
8
8
  return fastFlagEnabled(parsed.entry.flagKey, env);
9
9
  }
10
10
  /** Reads a default-off fast-route gate from its local override or fresh snapshot. */
11
- export async function fastFlagEnabled(flagKey, env = process.env) {
12
- const override = nativeRouteOverride(flagKey, env);
11
+ export async function fastFlagEnabled(flagKey, env = process.env, options = {}) {
12
+ const override = nativeRouteOverride(flagKey, env, options.numericEnvironmentOverride !== false);
13
13
  if (override !== null)
14
14
  return override;
15
15
  try {
@@ -29,15 +29,17 @@ export async function fastFlagEnabled(flagKey, env = process.env) {
29
29
  }
30
30
  }
31
31
  /** The route's documented CLI override also accepts the conventional 1/0. */
32
- function nativeRouteOverride(flagKey, env) {
32
+ function nativeRouteOverride(flagKey, env, numericEnvironmentOverride) {
33
33
  const override = readLocalOverride(flagKey, env);
34
34
  if (override !== null)
35
35
  return override;
36
- const raw = env[localOverrideEnvVar(flagKey)];
37
- if (raw === "1")
38
- return true;
39
- if (raw === "0")
40
- return false;
36
+ if (numericEnvironmentOverride) {
37
+ const raw = env[localOverrideEnvVar(flagKey)];
38
+ if (raw === "1")
39
+ return true;
40
+ if (raw === "0")
41
+ return false;
42
+ }
41
43
  return null;
42
44
  }
43
45
  //# sourceMappingURL=native-fast-route.js.map
@@ -237,6 +237,12 @@ export declare function withMachineTokenStateDir<T>(fn: () => Promise<T>, opts?:
237
237
  home?: string;
238
238
  env?: NodeJS.ProcessEnv;
239
239
  }): Promise<T>;
240
+ /**
241
+ * Ensure the current machine identity has a valid session and return only the
242
+ * access/ID token pair needed by supported machine-session handoff scripts.
243
+ * The machine state directory is isolated from the person's session cache.
244
+ */
245
+ export declare function ensureMachineSessionTokens(): Promise<Pick<CognitoTokens, "accessToken" | "idToken" | "expiresAt">>;
240
246
  /**
241
247
  * True when a person-token refresh failed with a definitive Cognito auth
242
248
  * rejection (refresh token invalid/revoked). Transient network/5xx/timeouts
@@ -840,6 +840,15 @@ function withTokenStateDirSection(fn) {
840
840
  async function ensureMachineTokens() {
841
841
  return withMachineTokenStateDir(() => getValidMachineTokens(DEFAULT_COGNITO));
842
842
  }
843
+ /**
844
+ * Ensure the current machine identity has a valid session and return only the
845
+ * access/ID token pair needed by supported machine-session handoff scripts.
846
+ * The machine state directory is isolated from the person's session cache.
847
+ */
848
+ export async function ensureMachineSessionTokens() {
849
+ const tokens = await ensureMachineTokensMaybeFallback({ tokenSource: "machine" });
850
+ return { accessToken: tokens.accessToken, idToken: tokens.idToken, expiresAt: tokens.expiresAt };
851
+ }
843
852
  /**
844
853
  * Mint machine tokens (daemon state dir cache). Logs once when the fresh-box
845
854
  * default-creds fallback is what selected the machine path.
@@ -99,5 +99,5 @@ export declare const CONTRIBUTION_KEYS: ContributionKey[];
99
99
  */
100
100
  export declare function payloadFor(key: ContributionKey, item: string): string;
101
101
  /** Keys whose contributions are wired by a host symlink (skip `merge` rows). */
102
- export declare const SYMLINK_KEYS: ("workers" | "mcp" | "commands" | "hooks" | "skills" | "policies" | "knowledge" | "scripts")[];
102
+ export declare const SYMLINK_KEYS: ("workers" | "mcp" | "commands" | "skills" | "knowledge" | "hooks" | "policies" | "scripts")[];
103
103
  //# sourceMappingURL=contribution-table.d.ts.map
@@ -0,0 +1,12 @@
1
+ type ErrorEventStream = {
2
+ on: (event: "error", listener: (err: unknown) => void) => unknown;
3
+ };
4
+ export type FastStreamErrorDependencies = {
5
+ stdout: ErrorEventStream;
6
+ stderr: ErrorEventStream;
7
+ exit: (code: number) => void;
8
+ };
9
+ /** Match the full CLI's clean handling when a pipe consumer closes early. */
10
+ export declare function installFastPathStreamErrorHandlers(deps?: FastStreamErrorDependencies): void;
11
+ export {};
12
+ //# sourceMappingURL=fast-stream-errors.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { isEpipe } from "./epipe.js";
2
+ /** Match the full CLI's clean handling when a pipe consumer closes early. */
3
+ export function installFastPathStreamErrorHandlers(deps = {
4
+ stdout: process.stdout,
5
+ stderr: process.stderr,
6
+ exit: (code) => process.exit(code),
7
+ }) {
8
+ const handle = (err) => {
9
+ if (isEpipe(err)) {
10
+ deps.exit(0);
11
+ return;
12
+ }
13
+ throw err;
14
+ };
15
+ deps.stdout.on("error", handle);
16
+ deps.stderr.on("error", handle);
17
+ }
18
+ //# sourceMappingURL=fast-stream-errors.js.map
@@ -1,3 +1,3 @@
1
- export declare function forbiddenFastPathModules(loadedModules: readonly string[], distDirectory: string): string[];
2
- export declare function assertFastPathStaysCommandGraphFree(loadedModules: readonly string[], distDirectory: string): void;
1
+ export declare function forbiddenFastPathModules(loadedModules: readonly string[], distDirectory: string, allowedCommandModules?: readonly string[]): string[];
2
+ export declare function assertFastPathStaysCommandGraphFree(loadedModules: readonly string[], distDirectory: string, allowedCommandModules?: readonly string[]): void;
3
3
  //# sourceMappingURL=scaffold-fast-import-guard.d.ts.map
@@ -1,11 +1,12 @@
1
1
  import * as path from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
- export function forbiddenFastPathModules(loadedModules, distDirectory) {
3
+ export function forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandModules = []) {
4
4
  const dist = path.resolve(distDirectory);
5
5
  const main = path.join(dist, "main.js");
6
6
  const commands = `${path.join(dist, "commands")}${path.sep}`;
7
7
  const flagRegistry = path.join(dist, "lib", "flag-registry.js");
8
8
  const coreCommand = path.join(dist, "commands", "core.js");
9
+ const allowedCommands = new Set(allowedCommandModules.map((module) => path.resolve(module)));
9
10
  return [...new Set(loadedModules.map((loaded) => {
10
11
  if (loaded.startsWith("file:")) {
11
12
  try {
@@ -22,10 +23,12 @@ export function forbiddenFastPathModules(loadedModules, distDirectory) {
22
23
  path.resolve(loaded) === flagRegistry ||
23
24
  path.resolve(loaded) === coreCommand ||
24
25
  path.resolve(loaded).includes(`${path.sep}node_modules${path.sep}@sentry${path.sep}node${path.sep}`))
26
+ .filter((loaded) => !allowedCommands.has(path.resolve(loaded)) ||
27
+ path.resolve(loaded) === main || path.resolve(loaded) === coreCommand)
25
28
  .sort();
26
29
  }
27
- export function assertFastPathStaysCommandGraphFree(loadedModules, distDirectory) {
28
- const forbidden = forbiddenFastPathModules(loadedModules, distDirectory);
30
+ export function assertFastPathStaysCommandGraphFree(loadedModules, distDirectory, allowedCommandModules = []) {
31
+ const forbidden = forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandModules);
29
32
  if (forbidden.length > 0) {
30
33
  throw new Error(`fast core loaded forbidden command modules:\n${forbidden.join("\n")}`);
31
34
  }
@@ -62,6 +62,8 @@ import * as fs from "node:fs";
62
62
  import * as path from "node:path";
63
63
  import semver from "semver";
64
64
  import chalk from "chalk";
65
+ import { Sentry } from "../sentry.js";
66
+ import { isCliTelemetryDisabled } from "./cli-telemetry.js";
65
67
  import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
66
68
  import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, checkUpdateConvergence, derivePnpmHome, detachUpdateSupervisor, isBunManagedPackageDir, isLocalDependencyInstall, isPnpmManagedPackageDir, isPrefixWritable, nonWritablePrefixNote, openInstallOutput, pnpmUpdateEnv, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, } from "./version-gate.js";
67
69
  import { canStageInstall, stagedNpmInstall } from "./staged-install.js";
@@ -499,6 +501,7 @@ async function updateAndReexec(argv, flavor, known, deps) {
499
501
  console.error(chalk.dim(` Or repair the install: hq rescue`));
500
502
  }
501
503
  console.error(chalk.dim(` Continuing the ${flavor.noun} on ${current}.`));
504
+ await captureSelfUpdateOutcome("update-failed", current, latestValid, install.manager, result.code, env, flavor.noun === "rescue");
502
505
  return { action: "update-failed", latest };
503
506
  }
504
507
  if (installSnapshot)
@@ -521,6 +524,7 @@ async function updateAndReexec(argv, flavor, known, deps) {
521
524
  if (converged === false) {
522
525
  (deps.markIneffective ?? markLatestIneffective)(latest);
523
526
  console.error(chalk.dim(` Continuing the ${flavor.noun} on ${current}.`));
527
+ await captureSelfUpdateOutcome("update-ineffective", current, latestValid, install.manager, undefined, env, flavor.noun === "rescue");
524
528
  return { action: "update-ineffective", latest };
525
529
  }
526
530
  // A resident mesh daemon keeps the old JS loaded after a global install.
@@ -550,6 +554,33 @@ async function updateAndReexec(argv, flavor, known, deps) {
550
554
  console.error(chalk.dim(`✓ hq-cli updated to ${latest}.`));
551
555
  return { action: "reexec", reexecStatus: status, latest };
552
556
  }
557
+ const SELF_UPDATE_ERROR_CLASSES = new Set(["MODULE_NOT_FOUND", "EACCES", "EEXIST"]);
558
+ async function captureSelfUpdateOutcome(action, fromVersion, targetVersion, installFlavor, code, env, flushBeforeExit) {
559
+ if (isCliTelemetryDisabled(env))
560
+ return;
561
+ const error = new Error(action === "update-failed" ? "Self-update failed" : "Self-update ineffective");
562
+ // This event needs no stack: frames would expose the machine's install path.
563
+ error.stack = undefined;
564
+ Sentry.captureException(error, {
565
+ fingerprint: ["self-update", action],
566
+ tags: {
567
+ from_version: fromVersion,
568
+ target_version: targetVersion,
569
+ install_flavor: installFlavor,
570
+ error_class: code && SELF_UPDATE_ERROR_CLASSES.has(code) ? code : "other",
571
+ },
572
+ });
573
+ if (flushBeforeExit) {
574
+ try {
575
+ if (!(await Sentry.flush(500))) {
576
+ console.error(chalk.dim("hq: Sentry flush timed out after self-update."));
577
+ }
578
+ }
579
+ catch {
580
+ console.error(chalk.dim("hq: Sentry flush failed after self-update."));
581
+ }
582
+ }
583
+ }
553
584
  async function readStagedSelfUpdateFlag(deps, env) {
554
585
  try {
555
586
  if (deps.stagedSelfUpdate)
@@ -706,13 +706,32 @@ export function detachUpdateSupervisor() {
706
706
  try {
707
707
  rmSync(tempDir, { recursive: true, force: true });
708
708
  }
709
- catch {
710
- // Best-effort cleanup of a temp dir.
709
+ catch (error) {
710
+ const failureClass = error &&
711
+ typeof error === "object" &&
712
+ "code" in error &&
713
+ typeof error.code === "string" &&
714
+ /^[A-Z0-9_]{1,32}$/.test(error.code)
715
+ ? error.code
716
+ : error instanceof Error
717
+ ? "Error"
718
+ : "UnknownError";
719
+ console.error(chalk.yellow(`Could not remove update supervisor temp dir (${failureClass}).`));
711
720
  }
712
721
  },
713
722
  };
714
723
  }
715
- catch {
724
+ catch (error) {
725
+ const failureClass = error &&
726
+ typeof error === "object" &&
727
+ "code" in error &&
728
+ typeof error.code === "string" &&
729
+ /^[A-Z0-9_]{1,32}$/.test(error.code)
730
+ ? error.code
731
+ : error instanceof Error
732
+ ? "Error"
733
+ : "UnknownError";
734
+ console.error(chalk.yellow(`Could not copy update supervisor (${failureClass}).`));
716
735
  if (dir)
717
736
  rmSync(dir, { recursive: true, force: true });
718
737
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.293.0",
3
+ "version": "5.295.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,8 +0,0 @@
1
- export interface FastLanesListRouteDeps {
2
- flagEnabled: (flagKey: string, env: NodeJS.ProcessEnv) => Promise<boolean>;
3
- runFastList: () => Promise<void>;
4
- runPlacementList: () => Promise<void>;
5
- }
6
- /** Returns false unless this exact list form is safely enabled in the local flag snapshot. */
7
- export declare function runFastLanesListRequest(argv: readonly string[], env?: NodeJS.ProcessEnv, deps?: FastLanesListRouteDeps): Promise<boolean>;
8
- //# sourceMappingURL=fast-lanes-list-route.d.ts.map
@@ -1,32 +0,0 @@
1
- import { isFastCachedLanesListRequest } from "./fast-scaffold.js";
2
- import { fastFlagEnabled } from "./native-fast-route.js";
3
- import { registerOutpostLaneSnapshotRefreshChild } from "./lib/lanes/outpost-snapshot-refresh.js";
4
- import { LANE_OUTPOST_SNAPSHOT_CACHE_FLAG } from "./lib/lanes/outpost-hire-cache.js";
5
- import { LANE_OUTPOST_LOAD_BALANCING_FLAG } from "./lib/lanes/flag-keys.js";
6
- /** Returns false unless this exact list form is safely enabled in the local flag snapshot. */
7
- export async function runFastLanesListRequest(argv, env = process.env, deps = {
8
- flagEnabled: fastFlagEnabled,
9
- runFastList: async () => {
10
- const { runFastCachedLanesList } = await import("./fast-lanes-list.js");
11
- await runFastCachedLanesList();
12
- },
13
- runPlacementList: async () => {
14
- const { runFastCachedLanesListFromEntrypoint } = await import("./commands/lanes.js");
15
- await runFastCachedLanesListFromEntrypoint();
16
- },
17
- }) {
18
- registerOutpostLaneSnapshotRefreshChild(env);
19
- if (!isFastCachedLanesListRequest(argv))
20
- return false;
21
- if (env.HQ_INTERNAL_LANES_REFRESH_REMOTE_SNAPSHOT === "1")
22
- return false;
23
- if (!await deps.flagEnabled(LANE_OUTPOST_SNAPSHOT_CACHE_FLAG, env))
24
- return false;
25
- const lanePlacementEnabled = await deps.flagEnabled(LANE_OUTPOST_LOAD_BALANCING_FLAG, env);
26
- if (lanePlacementEnabled)
27
- await deps.runPlacementList();
28
- else
29
- await deps.runFastList();
30
- return true;
31
- }
32
- //# sourceMappingURL=fast-lanes-list-route.js.map
@@ -1,3 +0,0 @@
1
- /** Lightweight implementation for the exact cache-first `lanes list --json` entrypoint. */
2
- export declare function runFastCachedLanesList(): Promise<void>;
3
- //# sourceMappingURL=fast-lanes-list.d.ts.map