@indigoai-us/hq-cli 5.294.0 → 5.296.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 (72) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/command-catalog.generated.d.ts +70 -9
  3. package/dist/command-catalog.generated.js +88 -12
  4. package/dist/commands/auth.d.ts +1 -0
  5. package/dist/commands/auth.js +76 -1
  6. package/dist/commands/core-job-native.d.ts +7 -0
  7. package/dist/commands/core-job-native.js +20 -0
  8. package/dist/commands/core-job-probe-native.d.ts +45 -0
  9. package/dist/commands/core-job-probe-native.js +564 -0
  10. package/dist/commands/core-job-remote-launcher.d.ts +7 -0
  11. package/dist/commands/core-job-remote-launcher.js +51 -0
  12. package/dist/commands/core-job-run-native.d.ts +47 -0
  13. package/dist/commands/core-job-run-native.js +1215 -0
  14. package/dist/commands/core.js +1 -1
  15. package/dist/commands/doctor.js +0 -74
  16. package/dist/commands/lanes-message.js +24 -5
  17. package/dist/commands/lanes-resume.js +1 -3
  18. package/dist/commands/lanes-shared.d.ts +1 -2
  19. package/dist/commands/lanes-stories.d.ts +1 -0
  20. package/dist/commands/lanes-stories.js +40 -1
  21. package/dist/commands/lanes.d.ts +1 -9
  22. package/dist/commands/lanes.js +197 -174
  23. package/dist/commands/run.js +5 -0
  24. package/dist/fast-scaffold.d.ts +0 -5
  25. package/dist/fast-scaffold.js +0 -10
  26. package/dist/index.js +1 -10
  27. package/dist/lib/core-utils/sentry-report.d.ts +0 -19
  28. package/dist/lib/core-utils/sentry-report.js +8 -61
  29. package/dist/lib/doctor/checks/claude-wiring.js +9 -0
  30. package/dist/lib/lanes/flag-keys.d.ts +2 -0
  31. package/dist/lib/lanes/flag-keys.js +2 -0
  32. package/dist/lib/lanes/manager-status-gate.d.ts +0 -17
  33. package/dist/lib/lanes/manager-status-gate.js +2 -43
  34. package/dist/lib/lanes/mesh-events.js +3 -0
  35. package/dist/lib/lanes/outpost-dispatch.d.ts +2 -0
  36. package/dist/lib/lanes/outpost-dispatch.js +7 -1
  37. package/dist/lib/lanes/outpost-hire-cache.d.ts +0 -1
  38. package/dist/lib/lanes/outpost-hire-cache.js +0 -1
  39. package/dist/lib/lanes/outpost-snapshot-memo.d.ts +6 -0
  40. package/dist/lib/lanes/outpost-snapshot-memo.js +14 -0
  41. package/dist/lib/lanes/reconcile.d.ts +2 -3
  42. package/dist/lib/lanes/reconcile.js +67 -57
  43. package/dist/lib/lanes/spawn.d.ts +0 -2
  44. package/dist/lib/lanes/spawn.js +57 -5
  45. package/dist/lib/lanes/store.js +54 -0
  46. package/dist/lib/lanes/stories.d.ts +7 -1
  47. package/dist/lib/lanes/stories.js +16 -0
  48. package/dist/lib/lanes/types.d.ts +30 -1
  49. package/dist/lib/lanes/types.js +2 -2
  50. package/dist/lib/lanes/watcher.d.ts +4 -3
  51. package/dist/lib/lanes/watcher.js +122 -56
  52. package/dist/lib/mesh/live/daemon/run.js +18 -1
  53. package/dist/run/env-graph-guard.d.ts +2 -2
  54. package/dist/run/env-graph-guard.js +5 -2
  55. package/dist/run/hq-plugin.js +6 -5
  56. package/dist/utils/cognito-session.d.ts +6 -0
  57. package/dist/utils/cognito-session.js +9 -0
  58. package/dist/utils/self-update.js +31 -0
  59. package/dist/utils/version-gate.js +22 -3
  60. package/package.json +2 -2
  61. package/dist/fast-lanes-list-route.d.ts +0 -8
  62. package/dist/fast-lanes-list-route.js +0 -32
  63. package/dist/fast-lanes-list.d.ts +0 -3
  64. package/dist/fast-lanes-list.js +0 -224
  65. package/dist/lib/doctor/client-health-sentry-flag.d.ts +0 -23
  66. package/dist/lib/doctor/client-health-sentry-flag.js +0 -99
  67. package/dist/lib/doctor/client-health-sentry.d.ts +0 -71
  68. package/dist/lib/doctor/client-health-sentry.js +0 -286
  69. package/dist/lib/lanes/outpost-snapshot-cache.d.ts +0 -26
  70. package/dist/lib/lanes/outpost-snapshot-cache.js +0 -215
  71. package/dist/lib/lanes/outpost-snapshot-refresh.d.ts +0 -21
  72. package/dist/lib/lanes/outpost-snapshot-refresh.js +0 -329
@@ -128,6 +128,11 @@ export function registerRunCommand(program) {
128
128
  let state;
129
129
  const graph = await internal.loadEnvGraph({
130
130
  entryFilePaths: paths,
131
+ // hq run owns its short-lived in-memory resolution. Varlock's default
132
+ // cache auto-detection can write ~/.config/varlock and initialize
133
+ // the native local-encryption helper, neither of which belongs on
134
+ // this command path.
135
+ skipCache: true,
131
136
  afterInit: async (g) => { state = installHqPlugin(g, pluginOpts); },
132
137
  });
133
138
  // Refuse to resolve a graph varlock never finished loading (a malformed
@@ -66,11 +66,6 @@ export type ParsedSecretsEnvFastRequest = {
66
66
  * the full CLI, which remains the source of truth for help and errors.
67
67
  */
68
68
  export declare function parseSecretsEnvFastRequest(argv: readonly string[]): ParsedSecretsEnvFastRequest | null;
69
- /**
70
- * The cache-first list path only handles the bare JSON request. `--fresh`,
71
- * filters, help, and unknown options stay with Commander and the full CLI.
72
- */
73
- export declare function isFastCachedLanesListRequest(argv: readonly string[]): boolean;
74
69
  /**
75
70
  * Parse `hq core [--hq-root <path>] <name> [args...]` when `<name>` is a
76
71
  * fast-path script. Returns null for anything else so the caller defers to the
@@ -272,16 +272,6 @@ export function parseSecretsEnvFastRequest(argv) {
272
272
  }
273
273
  return hasOnly ? { options, scope } : null;
274
274
  }
275
- /**
276
- * The cache-first list path only handles the bare JSON request. `--fresh`,
277
- * filters, help, and unknown options stay with Commander and the full CLI.
278
- */
279
- export function isFastCachedLanesListRequest(argv) {
280
- return argv.length === 5 &&
281
- argv[2] === "lanes" &&
282
- argv[3] === "list" &&
283
- argv[4] === "--json";
284
- }
285
275
  /**
286
276
  * Parse `hq core [--hq-root <path>] <name> [args...]` when `<name>` is a
287
277
  * fast-path script. Returns null for anything else so the caller defers to the
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import "./node-network-compat.js";
6
6
  import { isVersionRequest, wantsLabelledVersionReport } from "./version-request.js";
7
7
  // Dependency-light: a pure parser + table, no command graph. Safe to load on
8
8
  // every path (including --version) without reintroducing the heavy startup.
9
- import { isFastCachedLanesListRequest, isFastCoreRequest, parseFastCore, parseSecretsEnvFastRequest, } from "./fast-scaffold.js";
9
+ import { isFastCoreRequest, parseFastCore, parseSecretsEnvFastRequest, } from "./fast-scaffold.js";
10
10
  // Also dependency-light: registers a process-level rejection boundary; its only
11
11
  // heavy import (main.js) is lazy and reached only when a rejection fires.
12
12
  import { installProcessRejectionBoundary } from "./unhandled-rejection-boundary.js";
@@ -61,15 +61,6 @@ else if (parseSecretsEnvFastRequest(process.argv)) {
61
61
  });
62
62
  });
63
63
  }
64
- else if (isFastCachedLanesListRequest(process.argv)) {
65
- void import("./fast-lanes-list-route.js").then(async ({ runFastLanesListRequest }) => {
66
- const handled = await runFastLanesListRequest(process.argv);
67
- if (!handled) {
68
- const { runCli } = await import("./main.js");
69
- await runCli();
70
- }
71
- });
72
- }
73
64
  else if (isFastCoreRequest(process.argv)) {
74
65
  // Hot relocated plumbing (`hq core hq-session`, `hq core checkpoint-stop-gate`)
75
66
  // dispatches straight to the bundled-script runner, so it never pays for the
@@ -17,7 +17,6 @@
17
17
  */
18
18
  import * as Sentry from "@sentry/node";
19
19
  import type { CaptureContext, SeverityLevel } from "@sentry/node";
20
- import type { ClientHealthSentryEvent } from "../doctor/client-health-sentry.js";
21
20
  export declare const DEFAULT_SENTRY_REPORT_TIMEOUT_MS = 750;
22
21
  export declare const MAX_SENTRY_REPORT_TIMEOUT_MS = 5000;
23
22
  export declare const MAX_SENTRY_REPORT_PAYLOAD_BYTES = 2048;
@@ -96,17 +95,6 @@ export type SentryReportResult = {
96
95
  event?: SentryReportEvent;
97
96
  droppedMetadataKeys?: number;
98
97
  };
99
- export interface ClientHealthSentryScope {
100
- clear(): void;
101
- setLevel(level: SeverityLevel): void;
102
- setFingerprint(fingerprint: string[]): void;
103
- setTags(tags: Record<string, string>): void;
104
- setUser(user: {
105
- id: string;
106
- ip_address: null;
107
- }): void;
108
- }
109
- export type ClientHealthSentryScopeRunner = (callback: (scope: ClientHealthSentryScope) => void) => void;
110
98
  /**
111
99
  * Read no more than the accepted event size. Returning early keeps a malformed
112
100
  * pipe from making a watchdog retain an unbounded hook payload in memory.
@@ -129,11 +117,4 @@ export declare function parseSentryReportEvent(stdin: string): SentryReportResul
129
117
  * transport errors cannot echo event data back to a hook.
130
118
  */
131
119
  export declare function reportSentryEvent(stdin: string, options?: SentryReportOptions, dependencies?: SentryReportDependencies): Promise<SentryReportResult>;
132
- /** Send the client-health projection with a clean Sentry scope and no metadata payload. */
133
- export declare function reportClientHealthEvents(events: readonly ClientHealthSentryEvent[], options?: {
134
- timeoutMs?: number;
135
- scopeRunner?: ClientHealthSentryScopeRunner;
136
- }, dependencies?: SentryReportDependencies): Promise<{
137
- code: 0 | 1;
138
- }>;
139
120
  //# sourceMappingURL=sentry-report.d.ts.map
@@ -577,6 +577,14 @@ function hookTimeoutDebugContextTags(context) {
577
577
  }
578
578
  function safeSlowChild(metadata) {
579
579
  const value = metadata.slow_child;
580
+ const slowChildMs = metadata.slow_child_ms;
581
+ const hasMeasuredChild = typeof slowChildMs === "number" && Number.isFinite(slowChildMs) && slowChildMs >= 0;
582
+ if (metadata.hook_event === "SessionStart" &&
583
+ (metadata.bash_env_set === true || metadata.bash_env_set === "set") &&
584
+ !hasMeasuredChild &&
585
+ (typeof value !== "string" || value.length === 0 || value === "other")) {
586
+ return "entry-bash-startup";
587
+ }
580
588
  if (typeof value !== "string" || !SAFE_HOOK_SCRIPT.test(value))
581
589
  return "other";
582
590
  return value;
@@ -1006,65 +1014,4 @@ export async function reportSentryEvent(stdin, options = {}, dependencies = defa
1006
1014
  dependencies.stderr.write("Sentry report failed.\n");
1007
1015
  return invalidInput();
1008
1016
  }
1009
- function isSafeClientHealthEvent(event) {
1010
- const tags = event.tags;
1011
- const checkClass = tags.check_class;
1012
- const reasonCode = tags.reason_code;
1013
- const classPattern = /^sync\.(?:update\.core|journal\.(?:personal|personal_vault|company)|manifest\.(?:personal|personal_vault|company))$|^other$/;
1014
- const reasonPattern = /^(?:none|core-version-unavailable|core-update-available|no-local-tree|never-synced|invalid-last-sync|stale-threshold|manifest-unavailable|manifest-state-unavailable|no-manifest-scopes|never-uploaded|invalid-upload-time|unresolved-company-uid)$/;
1015
- const versionPattern = /^(?:unknown|\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?(?:\+[A-Za-z0-9.-]+)?)$/;
1016
- const safeTagKeys = [
1017
- "check_class", "reason_code", "fix_outcome", "status", "failing_check_count",
1018
- "cli_version", "core_version", "sync_version", "platform", "arch",
1019
- ];
1020
- return (event.level === "warning" &&
1021
- classPattern.test(checkClass) &&
1022
- reasonPattern.test(reasonCode) &&
1023
- event.message === `Client health: ${checkClass} unresolved after hq doctor --fix` &&
1024
- event.fingerprint.length === 3 &&
1025
- event.fingerprint[0] === "client-health" &&
1026
- event.fingerprint[1] === checkClass &&
1027
- event.fingerprint[2] === reasonCode &&
1028
- (tags.fix_outcome === "not_auto_fixable" || tags.fix_outcome === "fix_failed" || tags.fix_outcome === "fix_timed_out") &&
1029
- (tags.status === "FAIL" || tags.status === "WARN") &&
1030
- /^\d{1,3}$/.test(tags.failing_check_count) &&
1031
- safeTagKeys.every((key) => Object.hasOwn(tags, key)) &&
1032
- Object.keys(tags).length === safeTagKeys.length &&
1033
- [tags.cli_version, tags.core_version, tags.sync_version].every((value) => versionPattern.test(value)) &&
1034
- /^[a-z0-9]+$/.test(tags.platform) &&
1035
- /^[a-z0-9]+$/.test(tags.arch) &&
1036
- /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(event.user.id) &&
1037
- event.user.ip_address === null);
1038
- }
1039
- /** Send the client-health projection with a clean Sentry scope and no metadata payload. */
1040
- export async function reportClientHealthEvents(events, options = {}, dependencies = defaultDependencies) {
1041
- const timeoutMs = resolveTimeout(options.timeoutMs);
1042
- if (timeoutMs === null ||
1043
- events.length === 0 ||
1044
- events.length > 250 ||
1045
- events.some((event) => !isSafeClientHealthEvent(event))) {
1046
- dependencies.stderr.write("Invalid client health Sentry event.\n");
1047
- return { code: 1 };
1048
- }
1049
- const withScope = options.scopeRunner ?? ((callback) => Sentry.withScope((scope) => callback(scope)));
1050
- try {
1051
- for (const event of events) {
1052
- withScope((scope) => {
1053
- scope.clear();
1054
- scope.setLevel(event.level);
1055
- scope.setFingerprint([...event.fingerprint]);
1056
- scope.setTags({ sentry_event_type: "client_health", ...event.tags });
1057
- scope.setUser({ id: event.user.id, ip_address: null });
1058
- dependencies.sentry.captureMessage(event.message);
1059
- });
1060
- }
1061
- if (await flushWithin(dependencies.sentry, timeoutMs))
1062
- return { code: 0 };
1063
- }
1064
- catch {
1065
- // Keep the event and transport details out of hook stderr.
1066
- }
1067
- dependencies.stderr.write("Client health Sentry report failed.\n");
1068
- return { code: 1 };
1069
- }
1070
1017
  //# sourceMappingURL=sentry-report.js.map
@@ -460,6 +460,15 @@ function requiredFromFirstCommand(first) {
460
460
  k += 1;
461
461
  if (k >= first.length)
462
462
  return { required, gatedHookId };
463
+ // SessionStart can use env to set BASH_ENV before Bash itself starts.
464
+ // Walk through that launcher and its assignments just like the shell scanner.
465
+ if (baseName(first[k]) === "env") {
466
+ k += 1;
467
+ while (k < first.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(first[k]))
468
+ k += 1;
469
+ if (k >= first.length || first[k].startsWith("-"))
470
+ return { required, gatedHookId };
471
+ }
463
472
  if (INTERPRETERS.has(baseName(first[k]))) {
464
473
  k += 1;
465
474
  while (k < first.length && first[k].startsWith("-")) {
@@ -1,3 +1,5 @@
1
1
  /** hq-flags keys used by lane list and placement behavior. */
2
2
  export declare const LANE_OUTPOST_LOAD_BALANCING_FLAG = "lanes.outpost-load-balancing";
3
+ /** Enables one automatic continuation after a missing-envelope repair. */
4
+ export declare const LANE_ENVELOPE_REPAIR_CONTINUE_FLAG = "lanes.envelope-repair-continue";
3
5
  //# sourceMappingURL=flag-keys.d.ts.map
@@ -1,3 +1,5 @@
1
1
  /** hq-flags keys used by lane list and placement behavior. */
2
2
  export const LANE_OUTPOST_LOAD_BALANCING_FLAG = "lanes.outpost-load-balancing";
3
+ /** Enables one automatic continuation after a missing-envelope repair. */
4
+ export const LANE_ENVELOPE_REPAIR_CONTINUE_FLAG = "lanes.envelope-repair-continue";
3
5
  //# sourceMappingURL=flag-keys.js.map
@@ -1,6 +1,4 @@
1
- import { readLanesRegistryFlag } from "./registry-flag.js";
2
1
  import type { LaneRecord, ManagerStatusGate } from "./types.js";
3
- export declare const MANAGER_STATUS_GATE_FLAG = "lanes.manager-status-gate";
4
2
  export interface ManagerStatusTarget {
5
3
  manager: boolean;
6
4
  identified_by?: "story_id" | "worker_profile" | "juniors";
@@ -11,23 +9,8 @@ export interface StatusSnapshot {
11
9
  mtime: number | null;
12
10
  updatedAt: string | null;
13
11
  }
14
- export interface ManagerStatusGateResolution {
15
- enabled: boolean;
16
- target?: ManagerStatusTarget;
17
- error?: string;
18
- }
19
12
  /** Resolve manager identity and the single declared status file, if present. */
20
13
  export declare function resolveManagerStatusTarget(root: string, lane: LaneRecord): ManagerStatusTarget;
21
- export declare function resolveManagerStatusGateForLane(root: string, lane: LaneRecord, options?: {
22
- env?: NodeJS.ProcessEnv;
23
- readFlag?: typeof readLanesRegistryFlag;
24
- onError?: (error: unknown) => void;
25
- }): Promise<ManagerStatusGateResolution>;
26
- export declare function managerStatusGateEnabledForLane(root: string, lane: LaneRecord, options?: {
27
- env?: NodeJS.ProcessEnv;
28
- readFlag?: typeof readLanesRegistryFlag;
29
- onError?: (error: unknown) => void;
30
- }): Promise<boolean>;
31
14
  export declare function readStatusSnapshot(filePath: string): StatusSnapshot;
32
15
  export declare function managerStatusGateHeartbeat(root: string, lane: LaneRecord, turnId: string, turnStartedAt: string): ManagerStatusGate | undefined;
33
16
  export declare function managerStatusWasWritten(gate: ManagerStatusGate, snapshot: StatusSnapshot): boolean;
@@ -2,9 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { checkCompanyPath } from "./checked-company-path.js";
4
4
  import { LanesError } from "./errors.js";
5
- import { laneCompanyUidSource, resolveLaneCompany } from "./lane-company.js";
6
- import { readLanesRegistryFlag } from "./registry-flag.js";
7
- export const MANAGER_STATUS_GATE_FLAG = "lanes.manager-status-gate";
5
+ import { resolveLaneCompany } from "./lane-company.js";
8
6
  function isObject(value) {
9
7
  return value !== null && typeof value === "object" && !Array.isArray(value);
10
8
  }
@@ -49,14 +47,6 @@ function matchesLane(rowLane, laneId) {
49
47
  const bareLaneId = laneId.split("_", 1)[0] ?? "";
50
48
  return row === laneId || row === bareLaneId;
51
49
  }
52
- function managerStatusGateErrorCode(err) {
53
- return err instanceof LanesError ? err.error : "manager_status_gate_unavailable";
54
- }
55
- function logManagerStatusGateFailure(lane, err) {
56
- const code = managerStatusGateErrorCode(err);
57
- const message = err instanceof Error ? err.message : String(err);
58
- process.stderr.write(`hq lanes: manager status gate disabled for ${lane.lane_id}: ${code}: ${message}\n`);
59
- }
60
50
  /** Resolve manager identity and the single declared status file, if present. */
61
51
  export function resolveManagerStatusTarget(root, lane) {
62
52
  const directIdentity = lane.story_id === "MANAGER"
@@ -93,38 +83,6 @@ export function resolveManagerStatusTarget(root, lane) {
93
83
  statusPath,
94
84
  };
95
85
  }
96
- export async function resolveManagerStatusGateForLane(root, lane, options = {}) {
97
- const reportError = options.onError ?? ((err) => logManagerStatusGateFailure(lane, err));
98
- let enabled;
99
- try {
100
- const resolution = resolveLaneCompany(root, lane);
101
- const company = resolution.company ?? undefined;
102
- enabled = await (options.readFlag ?? readLanesRegistryFlag)(MANAGER_STATUS_GATE_FLAG, {
103
- company,
104
- ...(company ? {
105
- companyUidSource: laneCompanyUidSource(resolution.source),
106
- } : {}),
107
- env: options.env,
108
- });
109
- }
110
- catch (err) {
111
- reportError(err);
112
- return { enabled: false, error: managerStatusGateErrorCode(err) };
113
- }
114
- if (!enabled)
115
- return { enabled: false };
116
- try {
117
- const target = resolveManagerStatusTarget(root, lane);
118
- return { enabled: target.manager && target.statusPath !== undefined, target };
119
- }
120
- catch (err) {
121
- reportError(err);
122
- return { enabled: false, error: managerStatusGateErrorCode(err) };
123
- }
124
- }
125
- export async function managerStatusGateEnabledForLane(root, lane, options = {}) {
126
- return (await resolveManagerStatusGateForLane(root, lane, options)).enabled;
127
- }
128
86
  export function readStatusSnapshot(filePath) {
129
87
  let stat;
130
88
  try {
@@ -153,6 +111,7 @@ export function readStatusSnapshot(filePath) {
153
111
  return { mtime: stat.mtimeMs, updatedAt };
154
112
  }
155
113
  export function managerStatusGateHeartbeat(root, lane, turnId, turnStartedAt) {
114
+ resolveLaneCompany(root, lane);
156
115
  const target = resolveManagerStatusTarget(root, lane);
157
116
  if (!target.manager || target.statusPath === undefined)
158
117
  return undefined;
@@ -44,6 +44,7 @@
44
44
  */
45
45
  import { appendThreadEvent, clamp, ensureLaneMeshThread, ensureProjectThread, getProjectView, patchStoryStatus, putProjectView, requireToken, registerMeshProject, resolveMeshCompany, } from "../mesh/api.js";
46
46
  import { LanesError } from "./errors.js";
47
+ import { terminalEnvelopeStoryStatus } from "./stories.js";
47
48
  export const LANE_MESH_EVENTS = [
48
49
  "lane_started",
49
50
  "lane_progress",
@@ -454,6 +455,8 @@ export async function emitLaneDecisionEvents(mesh, lane, envelope, opts = {}) {
454
455
  const patchStoryStatus = async (status) => {
455
456
  if (!lane.project_id?.trim() || !lane.story_id?.trim())
456
457
  return;
458
+ if (terminalEnvelopeStoryStatus(lane, envelope.decision) === undefined)
459
+ return;
457
460
  const suppressions = opts.storyStatusSuppressions;
458
461
  const key = laneStoryStatusSuppressionKey(company, lane.project_id, lane.story_id);
459
462
  if (suppressions?.has(key))
@@ -49,6 +49,8 @@ export interface OutpostLaneCommand {
49
49
  compact?: boolean;
50
50
  tail?: string;
51
51
  json?: boolean;
52
+ callerLaneId?: string;
53
+ callerSessionId?: string;
52
54
  }
53
55
  export declare function buildOutpostQuestionAnswerCommand(input: {
54
56
  questionId: string;
@@ -190,7 +190,13 @@ export function buildOutpostLaneCommand(input) {
190
190
  args.push("--tail", input.tail);
191
191
  if (input.action === "logs" && input.json === true)
192
192
  args.push("--json");
193
- return withRemoteHqDir(joinCommandParts(args));
193
+ const callerLaneId = input.callerLaneId?.trim();
194
+ const callerSessionId = input.callerSessionId?.trim();
195
+ const callerEnv = [
196
+ callerLaneId ? `HQ_LANE_ID=${callerLaneId}` : undefined,
197
+ callerSessionId ? `HQ_SESSION_ID=${callerSessionId}` : undefined,
198
+ ].filter((value) => value !== undefined);
199
+ return withRemoteHqDir(joinCommandParts(callerEnv.length > 0 ? ["env", ...callerEnv, ...args] : args));
194
200
  }
195
201
  /** True when a remote CLI exited after rejecting the forwarded effort flag. */
196
202
  export function outpostRejectedEffortOption(result) {
@@ -1,5 +1,4 @@
1
1
  export declare const OUTPOST_HIRE_SUMMARY_CACHE_TTL_MS = 60000;
2
- export declare const LANE_OUTPOST_SNAPSHOT_CACHE_FLAG = "lanes.outpost-snapshot-cache";
3
2
  export interface OutpostHireSummary {
4
3
  hire_grant: number;
5
4
  hire_alive_children: number;
@@ -2,7 +2,6 @@ import { randomBytes } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  export const OUTPOST_HIRE_SUMMARY_CACHE_TTL_MS = 60_000;
5
- export const LANE_OUTPOST_SNAPSHOT_CACHE_FLAG = "lanes.outpost-snapshot-cache";
6
5
  const CACHE_RELATIVE_PATH = path.join("workspace", "lanes-runs", ".cache", "outpost-hire-summaries.json");
7
6
  export function outpostHireSummaryCachePath(hqRoot) {
8
7
  return path.join(hqRoot, CACHE_RELATIVE_PATH);
@@ -0,0 +1,6 @@
1
+ /** Deduplicates identical remote snapshot queries within one command invocation. */
2
+ export declare class OutpostSnapshotInvocationMemo {
3
+ private readonly requests;
4
+ getOrCreate<T>(outpostId: string, query: unknown, fetch: () => Promise<T>): Promise<T>;
5
+ }
6
+ //# sourceMappingURL=outpost-snapshot-memo.d.ts.map
@@ -0,0 +1,14 @@
1
+ /** Deduplicates identical remote snapshot queries within one command invocation. */
2
+ export class OutpostSnapshotInvocationMemo {
3
+ requests = new Map();
4
+ getOrCreate(outpostId, query, fetch) {
5
+ const key = JSON.stringify([outpostId, query]);
6
+ const existing = this.requests.get(key);
7
+ if (existing)
8
+ return existing;
9
+ const request = fetch();
10
+ this.requests.set(key, request);
11
+ return request;
12
+ }
13
+ }
14
+ //# sourceMappingURL=outpost-snapshot-memo.js.map
@@ -11,7 +11,7 @@ import { type ThrottleDeps, type CpuBusyObservation } from "./throttle.js";
11
11
  import { readLanesRegistryFlag } from "./registry-flag.js";
12
12
  import { type RunawayProcessInfo } from "./runaway-tool-guard.js";
13
13
  import { type KeepAliveBlockedReason } from "./keep-alive.js";
14
- import type { LaneCompanyRef, LaneRecord, LivenessState, QueuedReason, QuestionRecord, SeniorRef } from "./types.js";
14
+ import type { LaneCompanyRef, LaneAccountSelection, LaneRecord, LivenessState, QueuedReason, QuestionRecord, SeniorRef } from "./types.js";
15
15
  import { type LaneMesh } from "./mesh-events.js";
16
16
  /** Spec §9: unanswered at the current hop for 60 minutes → one hop. */
17
17
  export declare const QUESTION_AGE_MS: number;
@@ -86,8 +86,6 @@ export interface ReconcileDeps {
86
86
  skipAdmissionWithoutCpuSample?: boolean;
87
87
  /** Flag reader seam used to assert rollout reads happen before admission locks. */
88
88
  readRegistryFlag?: typeof readLanesRegistryFlag;
89
- /** Pre-read manager gate decisions, scoped to each lane for this pass. */
90
- managerStatusGateByLane?: ReadonlyMap<string, boolean>;
91
89
  /** Active-company fallback for questions raised from an unscoped lane. */
92
90
  resolveQuestionActiveCompany?: () => Promise<LaneCompanyRef>;
93
91
  /** Resume queued messages after this pass wins a local dead-worker CAS. */
@@ -102,6 +100,7 @@ export interface ReconcileDeps {
102
100
  prepareKeepAliveClaudeResume?: (lane: LaneRecord) => Promise<{
103
101
  lane: LaneRecord;
104
102
  selectionFailure?: string;
103
+ accountSelection?: LaneAccountSelection;
105
104
  }>;
106
105
  /** Process-table snapshot seam for the opt-in runaway-search guard. */
107
106
  runawayToolProcessLister?: () => readonly RunawayProcessInfo[];