@indigoai-us/hq-cli 5.292.0 → 5.293.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.293.0] — 2026-09-30
6
+ - [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.
7
+ - `hq lanes move` now reports an already-on-target no-op without dispatching or changing the queued lane.
8
+ - 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".
9
+
10
+ - With the default-off lanes.keep-alive-resume-blocked gate enabled, reconcile
11
+ resumes an opted-in blocked lead after 15 minutes when it has open stories and
12
+ no pending question. After two blocked resumes, it records another blocked
13
+ episode for operator review.
14
+ - Lead autopilot dry runs no longer start workers, append repeat wakes, or write escalation records. Passed stories count as closed and canonical story-ledger lane objects are accepted.
15
+ - Autopilot checks status paths against the lead company, scopes escalation deduplication to each lead, includes carried stories in stale-worker checks, and recovers locks left by reused PIDs.
16
+ - The lead autopilot adds prompts for available grant slots, flat KPI reports, stale blockers, completed projects, stale worker ledgers, and stories without live lanes, with escalation after two unanswered wakes.
17
+
18
+ - Preserve empty directory paths and modes in recorded corpus cases. The recorder rejects tree entries the case format cannot replay, and CI checks committed snapshots after a clean Git archive extraction.
19
+ - Cross-Outpost resume and hire-grant dispatch verify the senior from the
20
+ senior's owning Outpost before signing the caller's grant context.
21
+
22
+ ### Added
23
+
24
+ - Add `hq lanes ask --priority P0|P1|P2` and show question urgency in the queue; warn when omitted during the compatibility release.
25
+
26
+ ### Fixed
27
+
28
+ - CI-only: keep the same-binary Node control case measured without applying performance regression thresholds.
29
+ - Fix npm global self-updates, which have failed with MODULE_NOT_FOUND since 5.290.0. The update moved the hq-cli package aside and then started its installer through a supervisor script inside that package. It now runs the installer through a copy of the supervisor outside the package.
30
+
5
31
  ## [5.292.0] — 2026-09-30
6
32
  - Add native `hq core jobs reconcile` behind `core-native-jobs`, retaining the bundled shell rollback and scheduled-job helpers.
7
33
  - Behind the default-off `cli.staged-self-update` flag, an npm global self-update installs the new version beside the running package, checks that it answers `--version`, then swaps it in with two renames. The running `hq` stays on disk for the whole install, so services the daemon starts during an update no longer fail with "hq: command not found" or MODULE_NOT_FOUND. pnpm, Bun, Windows and linked installs keep the in-place update.
@@ -8655,6 +8655,9 @@ export declare const COMMAND_CATALOG: readonly [{
8655
8655
  }, {
8656
8656
  readonly flags: "--recommended <text>";
8657
8657
  readonly description: "Recommended option";
8658
+ }, {
8659
+ readonly flags: "--priority <P0|P1|P2>";
8660
+ readonly description: "Urgency: P0 blocks now, P1 needed today, P2 can wait";
8658
8661
  }, {
8659
8662
  readonly flags: "--json";
8660
8663
  readonly description: "JSON on stdout (always)";
@@ -11171,6 +11171,10 @@ export const COMMAND_CATALOG = [
11171
11171
  "flags": "--recommended <text>",
11172
11172
  "description": "Recommended option"
11173
11173
  },
11174
+ {
11175
+ "flags": "--priority <P0|P1|P2>",
11176
+ "description": "Urgency: P0 blocks now, P1 needed today, P2 can wait"
11177
+ },
11174
11178
  {
11175
11179
  "flags": "--json",
11176
11180
  "description": "JSON on stdout (always)"
@@ -19,6 +19,38 @@ export function createLaneMoveHandlers(deps) {
19
19
  lane.last_host_report_at === undefined &&
20
20
  lane.pending_resume === undefined;
21
21
  }
22
+ function sameHost(a, b) {
23
+ return a.kind === b.kind && a.id === b.id;
24
+ }
25
+ function reportAlreadyOnTarget(lane) {
26
+ printJson({
27
+ ok: true,
28
+ action: "move",
29
+ lane_id: lane.lane_id,
30
+ state: lane.state,
31
+ host: lane.host,
32
+ account: lane.account ?? null,
33
+ moved: false,
34
+ reason: "already_on_target",
35
+ lane,
36
+ });
37
+ }
38
+ async function reportAlreadyOnTargetIfCurrent(hqRoot, laneId, provider, target, callerHost, runtimeHost, ctx) {
39
+ const current = await withLaneAdmissionLock(hqRoot, provider, async () => {
40
+ const lane = readLane(hqRoot, laneId);
41
+ if (!laneCanMoveBeforeStart(lane)) {
42
+ throw new LanesError(lane.state === "queued" ? "lane_move_started" : "lane_not_queued", `Lane ${laneId} cannot move after it has started; current state is ${lane.state}`, { lane_id: laneId, state: lane.state });
43
+ }
44
+ return sameHost(lane.host, target) ||
45
+ (laneRunsHere(lane, runtimeHost) && sameHost(callerHost, target))
46
+ ? lane
47
+ : undefined;
48
+ }, ctx.sleep);
49
+ if (current === undefined)
50
+ return false;
51
+ reportAlreadyOnTarget(current);
52
+ return true;
53
+ }
22
54
  function readLaneTimeout(hqRoot, laneId) {
23
55
  const raw = readJsonFile(timeoutPath(hqRoot, laneId));
24
56
  if (raw === null)
@@ -51,6 +83,22 @@ export function createLaneMoveHandlers(deps) {
51
83
  throw new LanesError(source.state === "queued" ? "lane_move_started" : "lane_not_queued", `Lane ${laneId} cannot move after it has started; current state is ${source.state}`, { lane_id: laneId, state: source.state });
52
84
  }
53
85
  const sourceHost = source.host;
86
+ const runtimeHost = reconcileHostFromCtx(ctx);
87
+ const runningHere = laneRunsHere(source, runtimeHost);
88
+ const callerHost = runtimeHost.outpostId
89
+ ? { kind: "outpost", id: runtimeHost.outpostId }
90
+ : { kind: "local", id: runtimeHost.hostname };
91
+ const explicitTarget = policy.kind === "local"
92
+ ? callerHost
93
+ : policy.kind === "outpost"
94
+ ? { kind: "outpost", id: policy.id }
95
+ : undefined;
96
+ if (explicitTarget !== undefined &&
97
+ (sameHost(sourceHost, explicitTarget) ||
98
+ (runningHere && sameHost(callerHost, explicitTarget)))) {
99
+ if (await reportAlreadyOnTargetIfCurrent(hqRoot, laneId, source.provider, explicitTarget, callerHost, runtimeHost, ctx))
100
+ return;
101
+ }
54
102
  const config = loadLanesConfig(hqRoot);
55
103
  const capacity = await loadHostCapacitySnapshots(ctx, hqRoot, config);
56
104
  let targetSnapshots = capacity.snapshots.filter((snapshot) => snapshot.host.kind !== sourceHost.kind || snapshot.host.id !== sourceHost.id);
@@ -69,8 +117,13 @@ export function createLaneMoveHandlers(deps) {
69
117
  ...(source.account !== undefined ? { account: source.account } : {}),
70
118
  ...(allowedAccounts !== undefined ? { allowedAccounts } : {}),
71
119
  });
120
+ if (sameHost(sourceHost, choice.host) ||
121
+ (runningHere && sameHost(callerHost, choice.host))) {
122
+ if (await reportAlreadyOnTargetIfCurrent(hqRoot, laneId, source.provider, choice.host, callerHost, runtimeHost, ctx))
123
+ return;
124
+ }
72
125
  let moved;
73
- if (laneRunsHere(source, reconcileHostFromCtx(ctx))) {
126
+ if (runningHere) {
74
127
  moved = await commitLaneMove(ctx, laneId, choice.host, choice.account, placementEnabled);
75
128
  }
76
129
  else if (source.host.kind === "outpost") {
@@ -475,6 +475,8 @@ export function createLaneQuestionsHandlers(deps) {
475
475
  .map((q) => {
476
476
  const row = {
477
477
  ...q,
478
+ priority: q.priority ?? null,
479
+ priority_display: q.priority ?? "-",
478
480
  audience: resolveAudience(hqRoot, q, ctx),
479
481
  ...(q.question_kind === "continue" ? { flag: "continue-loop" } : {}),
480
482
  };
@@ -503,6 +505,8 @@ export function createLaneQuestionsHandlers(deps) {
503
505
  })
504
506
  .map(({ question: q, host, audience }) => ({
505
507
  ...q,
508
+ priority: q.priority ?? null,
509
+ priority_display: q.priority ?? "-",
506
510
  host,
507
511
  audience,
508
512
  ...(q.question_kind === "continue" ? { flag: "continue-loop" } : {}),
@@ -114,6 +114,8 @@ export declare function parseQuestionBody(raw: unknown): QuestionBody | null;
114
114
  */
115
115
  export declare const ASK_BACKOFF_MS: readonly [5000, 15000];
116
116
  export declare const ASK_ATTEMPTS: number;
117
+ /** Flip to true in the next release to make --priority mandatory. */
118
+ export declare const ASK_PRIORITY_REQUIRED = false;
117
119
  export declare function casUpdateQuestion(hqRoot: string, questionId: string, updater: (current: QuestionRecord) => QuestionRecord): QuestionRecord;
118
120
  export declare function casUpdateLane(hqRoot: string, laneId: string, updater: (current: LaneRecord) => LaneRecord): LaneRecord;
119
121
  /**
@@ -490,6 +490,67 @@ function mergeOwnerLaneHierarchy(supplied, ownerLanes) {
490
490
  byId.set(lane.lane_id, lane);
491
491
  return [...byId.values()];
492
492
  }
493
+ function hierarchyWithRemoteSenior(hierarchy, remoteSenior) {
494
+ return [
495
+ ...hierarchy.filter((lane) => lane.lane_id !== remoteSenior.lane_id),
496
+ remoteSenior,
497
+ ];
498
+ }
499
+ async function readRemoteHireGrantSenior(ctx, hqRoot, seniorLaneId) {
500
+ if (!listLaneIds(hqRoot).includes(seniorLaneId))
501
+ return undefined;
502
+ const mirror = readLane(hqRoot, seniorLaneId);
503
+ if (mirror.host.kind !== "outpost" ||
504
+ laneRunsHere(mirror, reconcileHostFromCtx(ctx))) {
505
+ return undefined;
506
+ }
507
+ const outpostId = mirror.host.id;
508
+ const unavailable = (reason) => new LanesError("hire_grant_senior_unavailable", `Cannot verify senior lane ${seniorLaneId} from owning Outpost ${outpostId}; refusing hire-grant admission (${reason})`, { senior_lane_id: seniorLaneId, outpost_id: outpostId, reason });
509
+ let ownerRecords;
510
+ try {
511
+ ownerRecords = await readOutpostLaneRecords(ctx, outpostId, {
512
+ laneIds: [seniorLaneId],
513
+ snapshot: true,
514
+ strictSnapshot: true,
515
+ }, {}, { bypassCache: true });
516
+ }
517
+ catch (error) {
518
+ throw unavailable(isLanesError(error) ? error.error : "outpost_lane_list_unavailable");
519
+ }
520
+ const matches = ownerRecords.filter((lane) => lane.lane_id === seniorLaneId);
521
+ const owner = matches[0];
522
+ const ownerIsHostedByOutpost = owner !== undefined && ((owner.host.kind === "outpost" && owner.host.id === outpostId) ||
523
+ (owner.host.kind === "local" && owner.host.id.trim() !== ""));
524
+ if (matches.length !== 1 || owner === undefined || !ownerIsHostedByOutpost) {
525
+ throw unavailable(matches.length === 0
526
+ ? "owner_did_not_return_senior"
527
+ : matches.length > 1
528
+ ? "owner_returned_duplicate_seniors"
529
+ : "owner_senior_identity_mismatch");
530
+ }
531
+ try {
532
+ const mirrorCompany = resolveLaneCompany(hqRoot, mirror).company;
533
+ const ownerCompany = resolveLaneCompany(hqRoot, owner).company;
534
+ if ((ownerCompany?.uid ?? null) !== (mirrorCompany?.uid ?? null) ||
535
+ (ownerCompany?.slug ?? null) !== (mirrorCompany?.slug ?? null)) {
536
+ throw unavailable("owner_company_did_not_match_mirror");
537
+ }
538
+ }
539
+ catch (error) {
540
+ if (isLanesError(error) && error.error === "hire_grant_senior_unavailable")
541
+ throw error;
542
+ throw unavailable("owner_company_identity_unavailable");
543
+ }
544
+ return owner;
545
+ }
546
+ async function hierarchyWithVerifiedRemoteSenior(ctx, hqRoot, lane, hierarchy) {
547
+ if (lane.senior.kind !== "lane" || !lane.senior.id)
548
+ return [...hierarchy];
549
+ const remoteSenior = await readRemoteHireGrantSenior(ctx, hqRoot, lane.senior.id);
550
+ return remoteSenior === undefined
551
+ ? [...hierarchy]
552
+ : mergeOwnerLaneRecords(hierarchy, [remoteSenior]);
553
+ }
493
554
  function mergeOwnerLaneRecords(supplied, ownerLanes) {
494
555
  const byId = new Map(supplied.map((lane) => [lane.lane_id, lane]));
495
556
  for (const lane of ownerLanes)
@@ -911,8 +972,18 @@ async function withResumeHireAdmission(ctx, lane, fn, grantContext) {
911
972
  throw new LanesError("hire_grant_context_required", `Resume of Outpost lane ${lane.lane_id} requires the primary senior's hire-grant context`, { lane_id: lane.lane_id, senior_lane_id: lane.senior.id });
912
973
  }
913
974
  await refreshRemoteLaneMirrors(ctx, hqRoot, loadAllLanes(hqRoot), new Set([lane.senior.id]));
975
+ const remoteSenior = grantContext === undefined
976
+ ? await readRemoteHireGrantSenior(ctx, hqRoot, lane.senior.id)
977
+ : undefined;
978
+ const suppliedHierarchy = grantContext?.hierarchy ?? (remoteSenior === undefined
979
+ ? undefined
980
+ : hierarchyWithRemoteSenior(loadAllLanes(hqRoot), remoteSenior));
914
981
  return withHireGrantAdmission(hqRoot, lane.senior.id, (snapshot) => {
915
982
  const current = readLane(hqRoot, lane.lane_id);
983
+ if (remoteSenior !== undefined &&
984
+ (current.senior.kind !== "lane" || current.senior.id !== remoteSenior.lane_id)) {
985
+ throw new LanesError("hire_grant_senior_changed", `Lane ${current.lane_id}'s senior changed while its owning Outpost was being checked`, { lane_id: current.lane_id, expected_senior_lane_id: remoteSenior.lane_id, current_senior: current.senior });
986
+ }
916
987
  if (grantContext !== undefined &&
917
988
  !sameHireGrantSenior(current.senior, grantContext.expected_senior)) {
918
989
  throw new LanesError("hire_grant_senior_changed", `Lane ${current.lane_id}'s senior changed before the Outpost resumed it`, {
@@ -929,7 +1000,7 @@ async function withResumeHireAdmission(ctx, lane, fn, grantContext) {
929
1000
  if (refusal !== null)
930
1001
  throw refusal;
931
1002
  return fn();
932
- }, grantContext?.hierarchy);
1003
+ }, suppliedHierarchy, remoteSenior === undefined ? undefined : new Set([remoteSenior.lane_id]));
933
1004
  }
934
1005
  const CAPACITY_PROBE_TIMEOUT_MS = 15_000;
935
1006
  const MAX_PLACEMENT_OUTPOSTS = 16;
@@ -1205,8 +1276,11 @@ async function readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOpt
1205
1276
  }
1206
1277
  return lanes;
1207
1278
  }
1208
- async function readOutpostLaneRecords(ctx, outpostId, requestedQuery = {}, dispatchOptions = {}) {
1279
+ async function readOutpostLaneRecords(ctx, outpostId, requestedQuery = {}, dispatchOptions = {}, options = {}) {
1209
1280
  const query = normalizedLaneListQuery(requestedQuery);
1281
+ if (options.bypassCache === true) {
1282
+ return readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOptions);
1283
+ }
1210
1284
  let invocationMemo = outpostLaneListMemo.get(ctx);
1211
1285
  if (!invocationMemo) {
1212
1286
  invocationMemo = new OutpostSnapshotInvocationMemo();
@@ -2144,6 +2218,10 @@ function assertKnownOutpost(requested, known) {
2144
2218
  */
2145
2219
  export const ASK_BACKOFF_MS = [5_000, 15_000];
2146
2220
  export const ASK_ATTEMPTS = ASK_BACKOFF_MS.length + 1;
2221
+ /** Flip to true in the next release to make --priority mandatory. */
2222
+ export const ASK_PRIORITY_REQUIRED = false;
2223
+ const ASK_PRIORITY_WARNING = "--priority will be required in the next release.";
2224
+ const QUESTION_PRIORITIES = ["P0", "P1", "P2"];
2147
2225
  function withCasRetry(fn, attempts = 5) {
2148
2226
  let last;
2149
2227
  for (let i = 0; i < attempts; i += 1) {
@@ -3980,19 +4058,21 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
3980
4058
  let grantContext;
3981
4059
  let grantHierarchy;
3982
4060
  if (action === "grant") {
3983
- const hierarchy = mergeOwnerLaneRecords(loadAllLanes(ctx.hqRoot()), await readOutpostLaneRecords(ctx, outpostId, {
4061
+ const ownerHierarchy = mergeOwnerLaneRecords(loadAllLanes(ctx.hqRoot()), await readOutpostLaneRecords(ctx, outpostId, {
3984
4062
  hierarchy: [lane.lane_id],
3985
4063
  snapshot: true,
3986
4064
  }));
4065
+ const hierarchy = await hierarchyWithVerifiedRemoteSenior(ctx, ctx.hqRoot(), lane, ownerHierarchy);
3987
4066
  grantHierarchy = hierarchy;
3988
4067
  grantContext = createOutpostHireGrantContext(lane, hierarchy);
3989
4068
  }
3990
4069
  else if ((action === "resume" && lane.senior.kind === "lane") ||
3991
4070
  (action === "message" && (lane.senior.kind === "lane" || lane.senior.kind === "session"))) {
3992
- const hierarchy = mergeOwnerLaneRecords(loadAllLanes(ctx.hqRoot()), await readOutpostLaneRecords(ctx, outpostId, {
4071
+ const ownerHierarchy = mergeOwnerLaneRecords(loadAllLanes(ctx.hqRoot()), await readOutpostLaneRecords(ctx, outpostId, {
3993
4072
  hierarchy: [lane.lane_id],
3994
4073
  snapshot: true,
3995
4074
  }));
4075
+ const hierarchy = await hierarchyWithVerifiedRemoteSenior(ctx, ctx.hqRoot(), lane, ownerHierarchy);
3996
4076
  grantContext = createOutpostHireGrantContext(lane, hierarchy, action === "message" ? options.supersededMessageCallerId : undefined, action === "message" ? options.supersededMessageSessionId : undefined);
3997
4077
  }
3998
4078
  let dispatched;
@@ -5254,6 +5334,10 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
5254
5334
  }
5255
5335
  await refreshRemoteLaneMirrors(ctx, hqRoot, loadAllLanes(hqRoot), parentLaneIds);
5256
5336
  }
5337
+ const remoteSenior = authorization === "senior" &&
5338
+ initial.senior.kind === "lane" && initial.senior.id
5339
+ ? await readRemoteHireGrantSenior(ctx, hqRoot, initial.senior.id)
5340
+ : undefined;
5257
5341
  const updated = ctx.withGrantHireLocks(hqRoot, lockedLaneIds, () => {
5258
5342
  const current = readLane(hqRoot, laneId);
5259
5343
  const currentCallerAuthorization = authorization === "senior"
@@ -5270,11 +5354,18 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
5270
5354
  (!grantContext || !sameHireGrantSenior(current.senior, grantContext.expected_senior))) {
5271
5355
  throw new LanesError("hire_grant_senior_changed", `Lane ${laneId}'s senior changed before the Outpost applied the grant update`, { lane_id: laneId, current_senior: current.senior });
5272
5356
  }
5273
- const hierarchy = grantContext
5357
+ if (remoteSenior !== undefined &&
5358
+ (current.senior.kind !== "lane" || current.senior.id !== remoteSenior.lane_id)) {
5359
+ throw new LanesError("hire_grant_senior_changed", `Lane ${current.lane_id}'s senior changed while its owning Outpost was being checked`, { lane_id: current.lane_id, expected_senior_lane_id: remoteSenior.lane_id, current_senior: current.senior });
5360
+ }
5361
+ const currentHierarchy = grantContext
5274
5362
  ? authorization === "ssm-owner-dispatch"
5275
5363
  ? mergeOwnerLaneHierarchy(grantContext.hierarchy, loadAllLanes(hqRoot))
5276
5364
  : grantContext.hierarchy
5277
5365
  : loadAllLanes(hqRoot);
5366
+ const hierarchy = remoteSenior === undefined
5367
+ ? currentHierarchy
5368
+ : hierarchyWithRemoteSenior(currentHierarchy, remoteSenior);
5278
5369
  if (current.senior.kind === "lane") {
5279
5370
  if (!current.senior.id) {
5280
5371
  throw new LanesError("hire_grant_senior_invalid", `Lane ${laneId} has a lane senior without an id`, {
@@ -7443,7 +7534,23 @@ export function interruptNotResumableWarning(laneId) {
7443
7534
  return `hq lanes: warning: lane ${laneId} was interrupted before its provider reported a session id, ` +
7444
7535
  `so hq lanes resume cannot continue it. Start a new lane for the remaining work.\n`;
7445
7536
  }
7537
+ function parseQuestionPriority(value) {
7538
+ if (value === undefined) {
7539
+ if (ASK_PRIORITY_REQUIRED) {
7540
+ throw new LanesError("ask_priority_required", "--priority is required; choose P0, P1, or P2", {
7541
+ allowed: [...QUESTION_PRIORITIES],
7542
+ });
7543
+ }
7544
+ return undefined;
7545
+ }
7546
+ const normalized = value.toUpperCase();
7547
+ if (!QUESTION_PRIORITIES.includes(normalized)) {
7548
+ throw new LanesError("ask_priority_invalid", `Invalid --priority ${JSON.stringify(value)}. Allowed values: P0, P1, P2`, { allowed: [...QUESTION_PRIORITIES], value });
7549
+ }
7550
+ return normalized;
7551
+ }
7446
7552
  async function runAsk(ctx, opts) {
7553
+ const priority = parseQuestionPriority(opts.priority);
7447
7554
  const hqRoot = ctx.hqRoot();
7448
7555
  const laneId = ctx.env.HQ_LANE_ID?.trim();
7449
7556
  if (!laneId) {
@@ -7482,6 +7589,7 @@ async function runAsk(ctx, opts) {
7482
7589
  at,
7483
7590
  activeCompany: questionCompany,
7484
7591
  questionId,
7592
+ priority,
7485
7593
  });
7486
7594
  questionId = withBody.question_id;
7487
7595
  await emitLaneMeshEvent(ctx, lane.mesh_company ?? lane.company, lane.thread_id, "lane_question", {
@@ -7496,12 +7604,16 @@ async function runAsk(ctx, opts) {
7496
7604
  question_id: withBody.question_id,
7497
7605
  });
7498
7606
  }
7607
+ if (priority === undefined) {
7608
+ process.stderr.write(`hq lanes ask: warning: ${ASK_PRIORITY_WARNING}\n`);
7609
+ }
7499
7610
  printJson({
7500
7611
  ok: true,
7501
7612
  action: "ask",
7502
7613
  question: withBody,
7503
7614
  lane_id: lane.lane_id,
7504
7615
  lane_state: lane.state,
7616
+ ...(priority === undefined ? { warnings: [ASK_PRIORITY_WARNING] } : {}),
7505
7617
  });
7506
7618
  return;
7507
7619
  }
@@ -8493,6 +8605,8 @@ export function registerLanesCommand(parent, deps = {}) {
8493
8605
  .option("--context <text>", "Context for the audience")
8494
8606
  .option("--option <text>", "Answer choice (repeatable)", collectRepeatable)
8495
8607
  .option("--recommended <text>", "Recommended option")
8608
+ .option("--priority <P0|P1|P2>", "Urgency: P0 blocks now, P1 needed today, P2 can wait")
8609
+ .addHelpText("after", "\nThe audience may take up to 24 hours to answer; keep working meanwhile.\n")
8496
8610
  .action(wrap(async (opts) => {
8497
8611
  await runAsk(ctx, opts);
8498
8612
  }));
@@ -677,13 +677,13 @@ function scrubbedSentryEnvelope(event, overrides, additionalTags = {}) {
677
677
  ...sentryEnvelope(event, overrides, additionalTags),
678
678
  }, {});
679
679
  }
680
- /** Keep all hook-timeout conditions together by script while retaining tags for triage. */
680
+ /** Group hook-timeout conditions by normalized kind while retaining triage tags. */
681
681
  function hookTimeoutExceededOverrides(event) {
682
682
  if (!isHookTimeoutEvent(event))
683
683
  return undefined;
684
684
  return {
685
685
  level: "warning",
686
- fingerprint: ["hook-timeout", hookScript(event.metadata)],
686
+ fingerprint: ["hook-timeout", hookTimeoutKind(event)],
687
687
  };
688
688
  }
689
689
  /**
@@ -62,5 +62,16 @@ export declare function rotateLogs(logsDir: string, maxBytes?: number): number;
62
62
  * an on-failure unit down. The daemon's updater handles CLI updates.
63
63
  */
64
64
  export declare function unitProcessEnv(base: NodeJS.ProcessEnv, unit: UnitPlan): NodeJS.ProcessEnv;
65
+ /**
66
+ * A systemd system unit starts the daemon as the login user without the
67
+ * variables a login session sets, so `systemctl --user` from its services
68
+ * cannot find the user bus ("Failed to connect to bus: No medium found").
69
+ * Point them at the user's runtime dir when it exists and nothing set one.
70
+ */
71
+ export declare function withUserRuntimeDir(env: NodeJS.ProcessEnv, host: {
72
+ platform: NodeJS.Platform;
73
+ uid: number;
74
+ exists: (dir: string) => boolean;
75
+ }): NodeJS.ProcessEnv;
65
76
  export declare function runDaemon(opts: RunDaemonOptions): Promise<number>;
66
77
  //# sourceMappingURL=run.d.ts.map
@@ -233,8 +233,24 @@ function isFifo(file) {
233
233
  return false;
234
234
  }
235
235
  }
236
+ /**
237
+ * A systemd system unit starts the daemon as the login user without the
238
+ * variables a login session sets, so `systemctl --user` from its services
239
+ * cannot find the user bus ("Failed to connect to bus: No medium found").
240
+ * Point them at the user's runtime dir when it exists and nothing set one.
241
+ */
242
+ export function withUserRuntimeDir(env, host) {
243
+ if (host.platform !== "linux" || host.uid === 0 || env.XDG_RUNTIME_DIR)
244
+ return env;
245
+ const dir = `/run/user/${host.uid}`;
246
+ return host.exists(dir) ? { ...env, XDG_RUNTIME_DIR: dir } : env;
247
+ }
236
248
  export async function runDaemon(opts) {
237
- const env = opts.env ?? process.env;
249
+ const env = withUserRuntimeDir(opts.env ?? process.env, {
250
+ platform: process.platform,
251
+ uid: process.getuid?.() ?? -1,
252
+ exists: fs.existsSync,
253
+ });
238
254
  const home = opts.home ?? os.homedir();
239
255
  const platform = process.platform;
240
256
  const paths = daemonPaths(home, env);
@@ -12,8 +12,9 @@ export interface RegisteredLead {
12
12
  status: string;
13
13
  prd: string;
14
14
  }
15
+ export type AutopilotCondition = "review_finished_worker" | "fill_grant" | "plan_free_slots" | "flat_kpi" | "blocker_record_needs_update" | "project_ready_to_close" | "stale_worker_ledger" | "story_without_live_lane";
15
16
  export interface AutopilotWake {
16
- condition: "review_finished_worker" | "fill_grant";
17
+ condition: AutopilotCondition;
17
18
  lead_lane: string;
18
19
  story_id: string;
19
20
  worker_lane?: string;
@@ -30,6 +31,14 @@ export interface AutopilotPassResult {
30
31
  story_id: string;
31
32
  worker_lane: string;
32
33
  }>;
34
+ escalated: Array<{
35
+ team: string;
36
+ lead_lane: string;
37
+ condition: AutopilotCondition;
38
+ story_id: string;
39
+ at: string;
40
+ fingerprint: string;
41
+ }>;
33
42
  skipped: Array<{
34
43
  lead_lane: string;
35
44
  reason: string;
@@ -38,15 +47,18 @@ export interface AutopilotPassResult {
38
47
  interface ProjectStory {
39
48
  id: string;
40
49
  status: string;
50
+ passes?: boolean;
41
51
  briefPath?: string;
42
52
  laneIds?: string[];
43
53
  updatedAt?: string;
54
+ externalBlocker?: string;
44
55
  }
45
56
  export declare function planLeadAutopilotWakes(input: {
46
57
  lead: LaneRecord;
47
58
  children: readonly LaneRecord[];
48
59
  stories: readonly ProjectStory[];
49
60
  now: Date;
61
+ managerStatus?: Record<string, unknown>;
50
62
  keepAliveResumed?: boolean;
51
63
  }): AutopilotWake[];
52
64
  export declare function runLeadAutopilotPass(input: {