@indigoai-us/hq-cli 5.301.0 → 5.303.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 (65) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/assets/scaffold/core/scripts/rebuild-projects-index.sh +99 -12
  3. package/assets/scaffold/core/scripts/token-usage-report.sh +72 -20
  4. package/dist/command-catalog.generated.d.ts +4 -1
  5. package/dist/command-catalog.generated.js +5 -1
  6. package/dist/commands/bot.js +1 -0
  7. package/dist/commands/core-checkpoint.js +1 -0
  8. package/dist/commands/core-handoff-fast.js +1 -0
  9. package/dist/commands/core-handoff.js +2 -0
  10. package/dist/commands/deploy-comments.js +5 -2
  11. package/dist/commands/integrations-connect.d.ts +3 -0
  12. package/dist/commands/integrations-connect.js +21 -2
  13. package/dist/commands/lanes-message.js +33 -3
  14. package/dist/commands/lanes-questions.d.ts +4 -2
  15. package/dist/commands/lanes-questions.js +123 -2
  16. package/dist/commands/lanes-resume.js +25 -4
  17. package/dist/commands/lanes-shared.d.ts +13 -1
  18. package/dist/commands/lanes-watch.js +5 -1
  19. package/dist/commands/lanes.js +91 -9
  20. package/dist/commands/monitor.js +17 -2
  21. package/dist/commands/report.js +5 -2
  22. package/dist/commands/rescue.d.ts +4 -0
  23. package/dist/commands/rescue.js +27 -17
  24. package/dist/lib/agent-kit/fallback.d.ts +1 -0
  25. package/dist/lib/agent-kit/fallback.js +1 -0
  26. package/dist/lib/bot/cli-update.d.ts +0 -8
  27. package/dist/lib/bot/cli-update.js +5 -5
  28. package/dist/lib/bot/daemon.d.ts +5 -1
  29. package/dist/lib/bot/daemon.js +4 -4
  30. package/dist/lib/bot/local-jobs.d.ts +1 -21
  31. package/dist/lib/bot/local-jobs.js +12 -12
  32. package/dist/lib/core-utils/soft-timeout.js +1 -1
  33. package/dist/lib/daemon/config.d.ts +0 -1
  34. package/dist/lib/daemon/config.js +1 -1
  35. package/dist/lib/deploy-apps.d.ts +13 -0
  36. package/dist/lib/deploy-apps.js +24 -0
  37. package/dist/lib/index-render/projects.js +82 -5
  38. package/dist/lib/lanes/delivery.d.ts +7 -3
  39. package/dist/lib/lanes/delivery.js +18 -8
  40. package/dist/lib/lanes/index.d.ts +1 -1
  41. package/dist/lib/lanes/keep-alive.d.ts +13 -3
  42. package/dist/lib/lanes/keep-alive.js +29 -5
  43. package/dist/lib/lanes/outpost-dispatch.d.ts +5 -0
  44. package/dist/lib/lanes/outpost-dispatch.js +12 -0
  45. package/dist/lib/lanes/reconcile.d.ts +1 -0
  46. package/dist/lib/lanes/reconcile.js +358 -38
  47. package/dist/lib/lanes/spawn.d.ts +1 -0
  48. package/dist/lib/lanes/spawn.js +20 -10
  49. package/dist/lib/lanes/store.js +24 -0
  50. package/dist/lib/lanes/story-ledger.d.ts +2 -0
  51. package/dist/lib/lanes/story-ledger.js +8 -0
  52. package/dist/lib/lanes/types.d.ts +26 -4
  53. package/dist/lib/lanes/types.js +33 -1
  54. package/dist/lib/lanes/watch.d.ts +1 -0
  55. package/dist/lib/lanes/watch.js +19 -6
  56. package/dist/lib/lanes/watcher.js +139 -9
  57. package/dist/lib/mesh/live/daemon/run.js +36 -2
  58. package/dist/lib/report-snapshot.d.ts +0 -2
  59. package/dist/lib/report-snapshot.js +2 -2
  60. package/dist/lib/search-index/background.d.ts +1 -0
  61. package/dist/lib/search-index/background.js +1 -0
  62. package/dist/utils/run-bundled-script.js +103 -1
  63. package/dist/utils/update-command-supervisor.cjs +1 -0
  64. package/dist/utils/version-gate.js +1 -1
  65. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { buildOutpostQuestionAnswerCommand, buildOutpostQuestionShowCommand, dispatchErrorMessage } from "../lib/lanes/outpost-dispatch.js";
1
+ import { buildOutpostQuestionAnswerCommand, buildOutpostQuestionCancelCommand, buildOutpostQuestionShowCommand, dispatchErrorMessage } from "../lib/lanes/outpost-dispatch.js";
2
2
  import { assertNoSecretText } from "../lib/lanes/text-scan.js";
3
3
  import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
4
4
  import { appendCappedHistory, createQuestion, isLanesError, listSessionLiveness, parseLivenessState, parseQuestionRecord, parseQuestionStatus, readDaemonPresenceConnected, readLane, readQuestion, wakeAnsweredLaneNow, sessionLiveness, appendDropboxMessage, answerDropboxMessage, laneAcceptsDropboxDelivery, laneIdForAnswerDelivery, writeQuestionBody } from "../lib/lanes/index.js";
@@ -20,7 +20,7 @@ export function resolveAnswerChoice(choice, options, freeform) {
20
20
  throw new LanesError("off_menu_choice", `Choice ${JSON.stringify(choice)} is not on the menu. Pass --freeform to allow it.`, { choice, options });
21
21
  }
22
22
  export function createLaneQuestionsHandlers(deps) {
23
- const { CAPACITY_PROBE_TIMEOUT_MS, MAX_PLACEMENT_OUTPOSTS, OUTPOST_QUESTION_LOOKUP_CONCURRENCY, assertLaneRunsHereForOperatorMutation, assertQuestionAnswerAuthorized, assertQuestionCompanyMatchesLane, casUpdateQuestion, effectiveLaneQuestionCompany, emitLaneMeshEvent, groupQuestionLanesByCompany, isQuestionAnswer, laneIsHostedHere, livenessDepsFromCtx, loadAllLanes, loadAllQuestions, mapWithConcurrency, mintMessageId, parseOutpostQuestionAudience, parseOutpostQuestionBody, parseRemoteJson, parseSeniorRef, questionCompanyForLane, questionCompanyRefMatches, questionListCompanyArg, readOutpostQuestionRecords, readQuestionBody, printJson, readSessionMeta, reconcileHostFromCtx, resolveActiveQuestionCompany, resolveAudience, sameQuestionAnswer, sameQuestionCompany, seniorOfSession, writeAnswerDeliveryFailed } = deps;
23
+ const { CAPACITY_PROBE_TIMEOUT_MS, MAX_PLACEMENT_OUTPOSTS, OUTPOST_QUESTION_LOOKUP_CONCURRENCY, assertLaneRunsHereForOperatorMutation, assertQuestionAnswerAuthorized, assertQuestionCompanyMatchesLane, casUpdateQuestion, casUpdateLane, effectiveLaneQuestionCompany, emitLaneMeshEvent, groupQuestionLanesByCompany, isQuestionAnswer, laneIsHostedHere, livenessDepsFromCtx, loadAllLanes, loadAllQuestions, mapWithConcurrency, mintMessageId, parseOutpostQuestionAudience, parseOutpostQuestionBody, parseRemoteJson, parseSeniorRef, questionCompanyForLane, questionCompanyRefMatches, questionListCompanyArg, readOutpostQuestionRecords, readQuestionBody, printJson, readSessionMeta, reconcileHostFromCtx, resolveActiveQuestionCompany, resolveAudience, sameQuestionAnswer, sameQuestionCompany, seniorOfSession, writeAnswerDeliveryFailed } = deps;
24
24
  function requireQuestionBody(hqRoot, question) {
25
25
  const body = readQuestionBody(hqRoot, question);
26
26
  if (!body) {
@@ -52,6 +52,16 @@ export function createLaneQuestionsHandlers(deps) {
52
52
  return audience.kind === "user";
53
53
  return audience.kind === filter.kind && audience.id === filter.id;
54
54
  }
55
+ function questionAudienceMatchesSenior(audience, lane, ctx) {
56
+ if (audienceMatches(audience, lane.senior))
57
+ return true;
58
+ const sessionId = sessionIdFromEnv(ctx.env);
59
+ if (sessionId && audienceMatches(audience, { kind: "session", id: sessionId }))
60
+ return true;
61
+ const callerLaneId = ctx.env.HQ_LANE_ID?.trim();
62
+ return callerLaneId !== undefined && callerLaneId.length > 0 &&
63
+ audienceMatches(audience, { kind: "lane", id: callerLaneId });
64
+ }
55
65
  function parseForFilter(raw, ctx) {
56
66
  const trimmed = raw.trim();
57
67
  if (trimmed === "me") {
@@ -384,6 +394,115 @@ export function createLaneQuestionsHandlers(deps) {
384
394
  errors: [...new Map(errors.map((entry) => [entry.host, entry])).values()],
385
395
  };
386
396
  }
397
+ async function cancelFallbackQuestionsForLane(ctx, lane, trigger, at) {
398
+ const hqRoot = ctx.hqRoot();
399
+ const caller = ctx.env.HQ_LANE_ID?.trim() || sessionIdFromEnv(ctx.env) || "user";
400
+ const reason = `Fallback ${trigger} by ${caller} at ${at}`;
401
+ const failures = [];
402
+ const cancelledQuestionIds = [];
403
+ const remoteOwner = lane.host.kind === "outpost" && !laneIsHostedHere(ctx, lane);
404
+ let pending;
405
+ try {
406
+ pending = remoteOwner
407
+ ? (await readOutpostQuestionRecords(ctx, lane.host.id, effectiveLaneQuestionCompany(lane, hqRoot)))
408
+ .map(({ question }) => question)
409
+ .filter((question) => question.origin.id === lane.lane_id && question.status === "pending")
410
+ : loadAllQuestions(hqRoot).filter((question) => question.origin.id === lane.lane_id && question.status === "pending");
411
+ }
412
+ catch (err) {
413
+ failures.push({ error: dispatchErrorMessage(err) });
414
+ pending = [];
415
+ }
416
+ for (const question of pending) {
417
+ try {
418
+ if (remoteOwner) {
419
+ const outpostId = lane.host.id;
420
+ const dispatched = await ctx.dispatchOutpostExec({
421
+ outpostId,
422
+ command: buildOutpostQuestionCancelCommand({ questionId: question.question_id, reason }),
423
+ timeoutSeconds: Math.ceil(CAPACITY_PROBE_TIMEOUT_MS / 1000),
424
+ waitMs: CAPACITY_PROBE_TIMEOUT_MS,
425
+ });
426
+ const remote = parseRemoteJson(dispatched.stdout, "Outpost question cancel");
427
+ const remoteQuestion = remote?.question !== null && typeof remote?.question === "object"
428
+ ? remote.question
429
+ : null;
430
+ if (dispatched.done !== true || dispatched.status !== "Success" || dispatched.exitCode !== 0 ||
431
+ remote?.ok !== true || remote.action !== "questions.cancel" ||
432
+ remoteQuestion?.question_id !== question.question_id || remoteQuestion.status !== "cancelled") {
433
+ throw new LanesError("outpost_question_cancel_unconfirmed", `Outpost ${outpostId} did not confirm cancellation of question ${question.question_id}`, { lane_id: lane.lane_id, question_id: question.question_id, outpost_id: outpostId });
434
+ }
435
+ }
436
+ else {
437
+ const cancellationEntry = {
438
+ hop: question.hop,
439
+ audience: resolveAudience(hqRoot, question, ctx),
440
+ at,
441
+ reason: "cancelled",
442
+ detail: reason,
443
+ };
444
+ const updated = casUpdateQuestion(hqRoot, question.question_id, (current) => {
445
+ if (current.status !== "pending")
446
+ return current;
447
+ return {
448
+ ...current,
449
+ status: "cancelled",
450
+ routing_history: appendCappedHistory(current.routing_history, cancellationEntry),
451
+ };
452
+ });
453
+ if (updated.status !== "cancelled")
454
+ continue;
455
+ }
456
+ cancelledQuestionIds.push(question.question_id);
457
+ }
458
+ catch (err) {
459
+ failures.push({ question_id: question.question_id, error: dispatchErrorMessage(err) });
460
+ }
461
+ }
462
+ if (failures.length > 0) {
463
+ const failure = {
464
+ at,
465
+ caller,
466
+ trigger,
467
+ question_ids: failures.flatMap((entry) => entry.question_id ? [entry.question_id] : []),
468
+ error: failures.map((entry) => entry.error).join("; "),
469
+ };
470
+ try {
471
+ casUpdateLane(hqRoot, lane.lane_id, (current) => {
472
+ const history = current.fallback_question_cancellation_failures ?? [];
473
+ const duplicate = history.some((entry) => entry.at === failure.at && entry.caller === caller && entry.trigger === trigger);
474
+ return duplicate
475
+ ? current
476
+ : {
477
+ ...current,
478
+ fallback_question_cancellation_failures: [...history, failure].slice(-20),
479
+ };
480
+ });
481
+ }
482
+ catch (err) {
483
+ const message = `Could not record fallback question cancellation failure for lane ${lane.lane_id}: ${dispatchErrorMessage(err)}\n`;
484
+ process.stderr.write(message);
485
+ failures.push({ error: `Failure record could not be persisted: ${dispatchErrorMessage(err)}` });
486
+ }
487
+ }
488
+ return { cancelled_question_ids: cancelledQuestionIds, failures };
489
+ }
490
+ async function hasPendingQuestionForLane(ctx, lane) {
491
+ const hqRoot = ctx.hqRoot();
492
+ const local = loadAllQuestions(hqRoot).some((question) => question.lane_id === lane.lane_id &&
493
+ question.status === "pending" &&
494
+ questionAudienceMatchesSenior(resolveAudience(hqRoot, question, ctx), lane, ctx));
495
+ if (local)
496
+ return true;
497
+ if (lane.host.kind !== "outpost" || laneIsHostedHere(ctx, lane))
498
+ return false;
499
+ // Reuse the owner lookup used by questions list/answer. An unreachable
500
+ // owner leaves no answerable question, so the message/resume path can recover.
501
+ const lookup = await loadPrimaryOwnedOutpostQuestions(ctx, hqRoot);
502
+ return lookup.questions.some(({ question, lane: ownerLane, audience }) => ownerLane.lane_id === lane.lane_id &&
503
+ question.status === "pending" &&
504
+ questionAudienceMatchesSenior(audience, ownerLane, ctx));
505
+ }
387
506
  function syncPrimaryQuestionDelivery(hqRoot, owner) {
388
507
  const deliveredAt = owner.question.delivered_at;
389
508
  if (!deliveredAt)
@@ -870,6 +989,8 @@ export function createLaneQuestionsHandlers(deps) {
870
989
  });
871
990
  }
872
991
  return {
992
+ hasPendingQuestionForLane,
993
+ cancelFallbackQuestionsForLane,
873
994
  runQuestionsList,
874
995
  runQuestionsShow,
875
996
  runQuestionsAnswer,
@@ -75,7 +75,8 @@ export function createLaneResumeHandlers(deps) {
75
75
  }
76
76
  /**
77
77
  * `hq lanes resume <lane-id> [--text <msg>]`: the explicit opt-in that
78
- * continues a lane in `interrupted`, `done` or `blocked`, or a failed lane
78
+ * continues a lane in `interrupted`, `done` or `blocked`, an `awaiting_input`
79
+ * lane with no answerable pending question, or a failed lane
79
80
  * whose only terminal reason is an invalid envelope after its repair turn,
80
81
  * on its recorded
81
82
  * provider session. `hq lanes message` keeps refusing `interrupted`; this
@@ -124,12 +125,21 @@ export function createLaneResumeHandlers(deps) {
124
125
  throw new LanesError("worker_profile_hash_mismatch", "Profile re-pin context is only valid on an Outpost owner dispatch", { lane_id: laneId });
125
126
  }
126
127
  if (resolution.remoteOnly || (lane.host.kind === "outpost" && !laneIsHostedHere(ctx, lane))) {
128
+ const fallbackAt = lane.state === "awaiting_input" ? ctx.now().toISOString() : undefined;
127
129
  if (!resolution.remoteOnly) {
128
130
  await withResumeHireAdmission(ctx, lane, () => undefined);
129
131
  }
130
132
  await dispatchOutpostLaneCommand(ctx, lane, "resume", opts, resolution.remoteOnly
131
- ? { syncPrimary: false, expectedOwnerHost: resolution.ownerHost }
132
- : undefined);
133
+ ? {
134
+ syncPrimary: false,
135
+ expectedOwnerHost: resolution.ownerHost,
136
+ ...(fallbackAt === undefined ? {} : {
137
+ afterFallbackResume: () => deps.cancelFallbackQuestionsForLane(ctx, lane, "resume", fallbackAt),
138
+ }),
139
+ }
140
+ : fallbackAt === undefined
141
+ ? undefined
142
+ : { afterFallbackResume: () => deps.cancelFallbackQuestionsForLane(ctx, lane, "resume", fallbackAt) });
133
143
  return;
134
144
  }
135
145
  // Fail before provider/session work if a terminal child would consume a
@@ -171,7 +181,10 @@ export function createLaneResumeHandlers(deps) {
171
181
  opts.compact === false)) {
172
182
  throw new LanesError("worker_profile_recovery_options_invalid", "Queued worker-profile recovery accepts only --worker; rerun hq lanes resume after reconcile admits the lane", { lane_id: laneId });
173
183
  }
174
- const refusal = queuedProfileRecovery
184
+ const pendingQuestion = lane.state === "awaiting_input"
185
+ ? await deps.hasPendingQuestionForLane(ctx, lane)
186
+ : false;
187
+ const refusal = queuedProfileRecovery || (lane.state === "awaiting_input" && !pendingQuestion)
175
188
  ? null
176
189
  : resumeCommandRefusal(lane, { allowFailed: opts.fromFailed === true });
177
190
  if (refusal !== null) {
@@ -444,6 +457,7 @@ export function createLaneResumeHandlers(deps) {
444
457
  trigger: {
445
458
  kind: "resume",
446
459
  from: resumedFrom,
460
+ ...(resumedFrom === "awaiting_input" && !pendingQuestion ? { allowAwaitingInput: true } : {}),
447
461
  ...(opts.fromFailed === true ? { fromFailed: true } : {}),
448
462
  ...(failedResumeExpectedSenior !== undefined
449
463
  ? { expectedSenior: failedResumeExpectedSenior }
@@ -460,6 +474,9 @@ export function createLaneResumeHandlers(deps) {
460
474
  }, hireGrantContext);
461
475
  }, ctx.sleep);
462
476
  }
477
+ const questionCancellation = resumedFrom === "awaiting_input" && !pendingQuestion
478
+ ? await deps.cancelFallbackQuestionsForLane(ctx, lane, "resume", at)
479
+ : null;
463
480
  if (deliveryText !== undefined && messageId !== undefined) {
464
481
  // Same audit copy a message resume leaves; nothing drains it. Archive only
465
482
  // after a successful spawn so a concurrent resume cannot leave a phantom.
@@ -519,6 +536,10 @@ export function createLaneResumeHandlers(deps) {
519
536
  story_id: next.story_id,
520
537
  stories: laneStories(next),
521
538
  story_warning: storyWarning(next) ?? null,
539
+ ...(questionCancellation === null ? {} : {
540
+ cancelled_questions: questionCancellation.cancelled_question_ids,
541
+ question_cancellation_failures: questionCancellation.failures,
542
+ }),
522
543
  lane: next,
523
544
  });
524
545
  }
@@ -137,11 +137,20 @@ export type CommandLaneResolution = {
137
137
  outpostId: string;
138
138
  ownerHost: LaneRecord["host"];
139
139
  };
140
+ export interface FallbackQuestionCancellationResult {
141
+ cancelled_question_ids: string[];
142
+ failures: Array<{
143
+ question_id?: string;
144
+ error: string;
145
+ }>;
146
+ }
140
147
  export interface OutpostLaneDispatchBehavior {
141
148
  /** Keep remote-only lanes out of the primary lane store. */
142
149
  syncPrimary?: boolean;
143
150
  /** Host identity returned by the remote record lookup for a remote-only lane. */
144
151
  expectedOwnerHost?: LaneRecord["host"];
152
+ /** Run after a successful remote awaiting_input fallback, before printing. */
153
+ afterFallbackResume?: () => Promise<FallbackQuestionCancellationResult>;
145
154
  }
146
155
  export interface LaneMessageHandlerDeps {
147
156
  /** Test seam invoked after a provider worker has been started. */
@@ -177,6 +186,8 @@ export interface LaneMessageHandlerDeps {
177
186
  recordResumeSetup(ctx: Ctx, lane: LaneRecord, input: RecordResumeSetupInput): LaneRecord;
178
187
  resolveCommandLane(ctx: Ctx, laneId: string): Promise<CommandLaneResolution>;
179
188
  resolveActiveQuestionCompany(ctx: Ctx, message?: string): Promise<LaneCompanyRef>;
189
+ hasPendingQuestionForLane(ctx: Ctx, lane: LaneRecord): Promise<boolean>;
190
+ cancelFallbackQuestionsForLane(ctx: Ctx, lane: LaneRecord, trigger: "message" | "resume", at: string): Promise<FallbackQuestionCancellationResult>;
180
191
  resolveResumeProfile(ctx: Ctx, lane: LaneRecord, workerId: string): ResolvedResumeProfile;
181
192
  selectAutomaticClaudeResumeAccount(ctx: Ctx, lane: LaneRecord, workerProfile?: ResolvedResumeProfile, options?: {
182
193
  repairUnavailableAccount?: boolean;
@@ -188,7 +199,7 @@ export interface LaneMessageHandlerDeps {
188
199
  validateResumeProfileModel(ctx: Ctx, lane: LaneRecord, profile: WorkerProfile): void;
189
200
  withResumeHireAdmission<T>(ctx: Ctx, lane: LaneRecord, fn: () => T, grantContext?: OutpostHireGrantContext): Promise<T>;
190
201
  }
191
- export interface LaneResumeHandlerDeps extends Pick<LaneMessageHandlerDeps, "onLaneWorkerStarted" | "archiveResumeMessage" | "assertLaneRunsHereForAdmission" | "assertLaneRunsHereForOperatorMutation" | "assessLaneAdmission" | "casUpdateLane" | "laneIsHostedHere" | "laneWithRecoveredProviderSession" | "mintMessageId" | "prepareAccountMove" | "persistClaudeAccountSelection" | "printJson" | "recordResumeSetup" | "resolveCommandLane" | "resolveResumeProfile" | "selectAutomaticClaudeResumeAccount" | "validateResumeProfileModel" | "withResumeHireAdmission"> {
202
+ export interface LaneResumeHandlerDeps extends Pick<LaneMessageHandlerDeps, "onLaneWorkerStarted" | "archiveResumeMessage" | "assertLaneRunsHereForAdmission" | "assertLaneRunsHereForOperatorMutation" | "assessLaneAdmission" | "casUpdateLane" | "laneIsHostedHere" | "laneWithRecoveredProviderSession" | "mintMessageId" | "prepareAccountMove" | "persistClaudeAccountSelection" | "printJson" | "recordResumeSetup" | "resolveCommandLane" | "hasPendingQuestionForLane" | "cancelFallbackQuestionsForLane" | "resolveResumeProfile" | "selectAutomaticClaudeResumeAccount" | "validateResumeProfileModel" | "withResumeHireAdmission"> {
192
203
  dispatchOutpostLaneCommand(ctx: Ctx, lane: LaneRecord, action: "resume", options: LaneResumeOptions, behavior?: OutpostLaneDispatchBehavior): Promise<void>;
193
204
  authorizeFailedResume(ctx: Ctx, lane: LaneRecord, encodedContext?: string): string;
194
205
  authorizeResumeProfileRepin(ctx: Ctx, lane: LaneRecord, profile: ResolvedResumeProfile | undefined, encodedContext: string | undefined): ResumeProfileRepinAuthorization | undefined;
@@ -382,6 +393,7 @@ export interface LaneQuestionsHandlerDeps {
382
393
  assertQuestionAnswerAuthorized(ctx: Ctx, lane: LaneRecord): void;
383
394
  assertQuestionCompanyMatchesLane(question: QuestionRecord, lane: Pick<LaneRecord, "lane_id" | "company" | "mesh_company" | "project_id">, code: string, hqRoot?: string): void;
384
395
  casUpdateQuestion(hqRoot: string, questionId: string, updater: (current: QuestionRecord) => QuestionRecord): QuestionRecord;
396
+ casUpdateLane(hqRoot: string, laneId: string, updater: (current: LaneRecord) => LaneRecord): LaneRecord;
385
397
  effectiveLaneQuestionCompany(lane: Pick<LaneRecord, "company" | "mesh_company" | "project_id" | "lane_id">, hqRoot?: string): LaneCompanyRef | null;
386
398
  emitLaneMeshEvent(ctx: Ctx, company: LaneCompanyRef | null | undefined, threadId: string | undefined, event: LaneMeshEvent, payload: Record<string, unknown>): Promise<void>;
387
399
  groupQuestionLanesByCompany(hqRoot: string, lanes: LaneRecord[], fallbackCompany?: LaneCompanyRef): QuestionCompanyGroup[];
@@ -1,5 +1,5 @@
1
1
  import { accountStatus, coveredLaneIds, loadLanesConfig, MAX_WATCHED_LANES, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, monitorReminderText, runLaneWatch, } from "../lib/lanes/index.js";
2
- import { LEAD_LANE_KEEP_ALIVE_FLAG } from "../lib/lanes/keep-alive.js";
2
+ import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG } from "../lib/lanes/keep-alive.js";
3
3
  import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
4
4
  import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
5
5
  import { isLanesError, LanesError } from "../lib/lanes/errors.js";
@@ -50,6 +50,10 @@ export function createLaneWatchHandlers(deps) {
50
50
  company: resolveLaneCompany(hqRoot, lane).company ?? undefined,
51
51
  env: ctx.env,
52
52
  }),
53
+ keepAliveNextCheckAtEnabled: (lane) => readLanesRegistryFlag(LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, {
54
+ company: resolveLaneCompany(hqRoot, lane).company ?? undefined,
55
+ env: ctx.env,
56
+ }),
53
57
  readAccountStatuses: async (lanes) => {
54
58
  if (lanes.length === 0)
55
59
  return [];
@@ -55,11 +55,11 @@ import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, lan
55
55
  import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
56
56
  import { listLaneReviews } from "../lib/lanes/reviews.js";
57
57
  import { effectiveAccountHome } from "../lib/lanes/account-home.js";
58
- import { AccountResolutionError, compareLaneQueueOrder, LANE_PROVIDERS, LANE_STATES, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listLaneIds, listQuestionIds, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseQuestionRecord, parseSeniorKind, readAllLanes, readLane, sanitizeHostComponent, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, loadWorkerProfileWithRegistry, registryWorkerRecord, runReconcilePass, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, dropboxDir, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, readWorkerRunResult, resumeCommandRefusal, withLaneHireLocks, raiseLaneQuestion, sha256Text, } from "../lib/lanes/index.js";
58
+ import { AccountResolutionError, compareLaneQueueOrder, LANE_PROVIDERS, LANE_STATES, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listLaneIds, listQuestionIds, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseQuestionRecord, parseSeniorKind, readAllLanes, readLane, sanitizeHostComponent, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, loadWorkerProfileWithRegistry, registryWorkerRecord, runReconcilePass, DEAD_HOST_MS, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, dropboxDir, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, readWorkerRunResult, resumeCommandRefusal, withLaneHireLocks, raiseLaneQuestion, sha256Text, } from "../lib/lanes/index.js";
59
59
  import { laneStories, storyWarning, } from "../lib/lanes/stories.js";
60
60
  import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
61
61
  import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
62
- import { doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
62
+ import { doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveNextCheckAtMs, keepAliveExclusion, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
63
63
  import { questionBodyAbsPath, resolveQuestionCompanyWithFallback, resolveProjectQuestionCompany, } from "../lib/lanes/questions.js";
64
64
  import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
65
65
  import { compactionAfterUsage, } from "../lib/lanes/compaction.js";
@@ -1260,6 +1260,8 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
1260
1260
  unverifiedRemote: new Map(),
1261
1261
  missingOnOwner: new Map(),
1262
1262
  };
1263
+ const now = ctx.now();
1264
+ const nowMs = now.getTime();
1263
1265
  const host = reconcileHostFromCtx(ctx);
1264
1266
  if (ctx.env.HQ_LANES_REMOTE_READ === "1" || parentLaneIds?.size === 0)
1265
1267
  return result;
@@ -1282,6 +1284,9 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
1282
1284
  ((parentLaneIds === undefined || (lane.senior.kind === "lane" &&
1283
1285
  lane.senior.id !== undefined &&
1284
1286
  parentLaneIds.has(lane.senior.id))) || options.laneIds?.has(lane.lane_id) === true) &&
1287
+ !(isTerminalLaneState(lane.state) &&
1288
+ lane.terminal_reason === DISPATCH_TERMINAL_REASON &&
1289
+ lane.last_host_report_at === undefined) &&
1285
1290
  (options.includeTerminalMirrors === true || !isTerminalLaneState(lane.state)))
1286
1291
  .sort((a, b) => {
1287
1292
  const terminalOrder = Number(isTerminalLaneState(a.state)) - Number(isTerminalLaneState(b.state));
@@ -1389,11 +1394,20 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
1389
1394
  result.updated.set(mirror.lane_id, before);
1390
1395
  continue;
1391
1396
  }
1397
+ const ownerReportAtMs = Date.parse(owner.last_host_report_at ?? "");
1398
+ const ownerReportIsFresh = owner.state === "running" &&
1399
+ Number.isFinite(ownerReportAtMs) &&
1400
+ ownerReportAtMs <= nowMs + 60_000 &&
1401
+ nowMs - ownerReportAtMs < DEAD_HOST_MS;
1402
+ const mirroredReportAt = ownerReportIsFresh
1403
+ ? new Date(Math.min(ownerReportAtMs, nowMs)).toISOString()
1404
+ : undefined;
1392
1405
  const needsUpdate = before.state !== owner.state ||
1393
1406
  before.queued_reason !== (owner.state === "queued" ? owner.queued_reason : undefined) ||
1394
1407
  JSON.stringify(before.queued_details) !== JSON.stringify(owner.state === "queued" ? owner.queued_details : undefined) ||
1395
1408
  before.ended_at !== endedAt ||
1396
1409
  before.envelope_summary !== envelopeSummary ||
1410
+ (mirroredReportAt !== undefined && before.last_host_report_at !== mirroredReportAt) ||
1397
1411
  (isTerminalLaneState(owner.state) && (JSON.stringify(before.last_envelope) !== JSON.stringify(owner.last_envelope) ||
1398
1412
  before.story_complete !== owner.story_complete ||
1399
1413
  before.story_complete_reason !== owner.story_complete_reason ||
@@ -1420,6 +1434,8 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
1420
1434
  queued_details: owner.state === "queued" ? owner.queued_details : undefined,
1421
1435
  ended_at: endedAt,
1422
1436
  envelope_summary: envelopeSummary,
1437
+ // Preserve a fresh owner report; clamp small clock skew to the primary's time.
1438
+ ...(mirroredReportAt !== undefined ? { last_host_report_at: mirroredReportAt } : {}),
1423
1439
  ...(isTerminalLaneState(owner.state) ? {
1424
1440
  last_envelope: owner.last_envelope,
1425
1441
  story_complete: owner.story_complete,
@@ -4261,10 +4277,29 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
4261
4277
  remote.delivery_status === "queued_on_target_account" ||
4262
4278
  remote.delivery_status === "queued_on_admission")) {
4263
4279
  const ownerLane = dispatchedOwnerLane(ctx, lane.lane_id, outpostId, remote.lane, behavior);
4280
+ const questionCancellation = lane.state === "awaiting_input" && behavior.afterFallbackResume
4281
+ ? await behavior.afterFallbackResume()
4282
+ : undefined;
4283
+ const ownerCancelledQuestions = Array.isArray(remote.cancelled_questions)
4284
+ ? remote.cancelled_questions.filter((id) => typeof id === "string")
4285
+ : [];
4286
+ const ownerCancellationFailures = Array.isArray(remote.question_cancellation_failures)
4287
+ ? remote.question_cancellation_failures
4288
+ : [];
4264
4289
  printJson({
4265
4290
  ...remote,
4266
4291
  lane_id: ownerLane.lane_id,
4267
4292
  lane: ownerLane,
4293
+ ...(questionCancellation === undefined ? {} : {
4294
+ cancelled_questions: [...new Set([
4295
+ ...ownerCancelledQuestions,
4296
+ ...questionCancellation.cancelled_question_ids,
4297
+ ])],
4298
+ question_cancellation_failures: [
4299
+ ...ownerCancellationFailures,
4300
+ ...questionCancellation.failures,
4301
+ ],
4302
+ }),
4268
4303
  outpost_id: outpostId,
4269
4304
  outpost_command_id: dispatched.commandId,
4270
4305
  outpost_transport: "outposts_exec",
@@ -4281,10 +4316,29 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
4281
4316
  descendants.push(syncLaneRecordFromOwner(ctx.hqRoot(), descendantId, outpostId, ownerDescendant, undefined, { allowNonTerminal: true }));
4282
4317
  }
4283
4318
  }
4319
+ const questionCancellation = action === "resume" && lane.state === "awaiting_input" && behavior.afterFallbackResume
4320
+ ? await behavior.afterFallbackResume()
4321
+ : undefined;
4322
+ const ownerCancelledQuestions = Array.isArray(remote.cancelled_questions)
4323
+ ? remote.cancelled_questions.filter((id) => typeof id === "string")
4324
+ : [];
4325
+ const ownerCancellationFailures = Array.isArray(remote.question_cancellation_failures)
4326
+ ? remote.question_cancellation_failures
4327
+ : [];
4284
4328
  printJson({
4285
4329
  ...remote,
4286
4330
  lane_id: ownerLane.lane_id,
4287
4331
  lane: ownerLane,
4332
+ ...(questionCancellation === undefined ? {} : {
4333
+ cancelled_questions: [...new Set([
4334
+ ...ownerCancelledQuestions,
4335
+ ...questionCancellation.cancelled_question_ids,
4336
+ ])],
4337
+ question_cancellation_failures: [
4338
+ ...ownerCancellationFailures,
4339
+ ...questionCancellation.failures,
4340
+ ],
4341
+ }),
4288
4342
  ...(action === "reparent"
4289
4343
  ? { changed_descendant_ids: changedDescendantIds, descendants }
4290
4344
  : {}),
@@ -5009,7 +5063,7 @@ async function refreshOutpostLaneCopiesForShow(ctx, lane, localLanes) {
5009
5063
  const refreshedLane = refreshed[0] ?? null;
5010
5064
  return refreshedLane ?? lane;
5011
5065
  }
5012
- async function runShow(ctx, laneId) {
5066
+ async function runShow(ctx, laneId, hasPendingQuestionForLane) {
5013
5067
  const hqRoot = ctx.hqRoot();
5014
5068
  const initialLane = readLane(hqRoot, laneId);
5015
5069
  const initialLanes = loadAllLanes(hqRoot);
@@ -5023,10 +5077,24 @@ async function runShow(ctx, laneId) {
5023
5077
  ? { ...storedLane, state: "remote", remote_state: storedLane.state, queued_reason: undefined, queued_details: undefined }
5024
5078
  : storedLane;
5025
5079
  const askDeliveryFailed = readJsonFile(askDeliveryFailedPath(hqRoot, laneId));
5080
+ const delivery = storedLane.state === "awaiting_input" &&
5081
+ await hasPendingQuestionForLane(ctx, storedLane)
5082
+ ? "questions"
5083
+ : laneDeliveryMechanism(storedLane);
5026
5084
  const keepAliveState = resolveLaneKeepAlive(hqRoot, storedLane);
5027
5085
  const keepAliveCompany = resolveLaneCompany(hqRoot, storedLane);
5086
+ const now = ctx.now().getTime();
5087
+ const nextCheckAtEnabled = storedLane.state === "done" && storedLane.keep_alive === true
5088
+ ? await readLanesRegistryFlag(LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, {
5089
+ company: keepAliveCompany.company ?? undefined,
5090
+ env: ctx.env,
5091
+ })
5092
+ : false;
5093
+ const hasPendingWork = storedLane.keep_alive_standing === true || nextCheckAtEnabled
5094
+ ? hasPendingKeepAliveWork(hqRoot, storedLane)
5095
+ : false;
5028
5096
  const doneStateVerdict = storedLane.state === "done" && storedLane.keep_alive === true
5029
- ? doneKeepAliveVerdict(storedLane, { decision: storedLane.last_envelope?.decision, story_complete: storedLane.story_complete }, storedLane.keep_alive_standing === true && hasPendingKeepAliveWork(hqRoot, storedLane))
5097
+ ? doneKeepAliveVerdict(storedLane, { decision: storedLane.last_envelope?.decision, story_complete: storedLane.story_complete, next_check_at: storedLane.last_envelope?.next_check_at }, storedLane.keep_alive_standing === true && hasPendingWork, { nextCheckAtEnabled, hasPendingWork, nowMs: now })
5030
5098
  : null;
5031
5099
  let managerStatusTarget;
5032
5100
  let managerStatusGateError;
@@ -5048,7 +5116,15 @@ async function runShow(ctx, laneId) {
5048
5116
  now: ctx.now().getTime(),
5049
5117
  });
5050
5118
  const laneIsSenior = allLanes.some((item) => item.senior.kind === "lane" && item.senior.id === laneId);
5051
- const now = ctx.now().getTime();
5119
+ const requestedNextCheckMs = nextCheckAtEnabled && !hasPendingWork
5120
+ ? keepAliveNextCheckAtMs(storedLane.last_envelope?.next_check_at, now, Date.parse(storedLane.last_envelope?.at ?? storedLane.updated_at))
5121
+ : undefined;
5122
+ const storedNextEligibleMs = Date.parse(storedLane.keep_alive_next_eligible_at ?? "");
5123
+ const nextEligibleAt = Number.isFinite(storedNextEligibleMs) && requestedNextCheckMs !== undefined
5124
+ ? new Date(Math.max(storedNextEligibleMs, requestedNextCheckMs)).toISOString()
5125
+ : requestedNextCheckMs !== undefined
5126
+ ? new Date(requestedNextCheckMs).toISOString()
5127
+ : storedLane.keep_alive_next_eligible_at ?? null;
5052
5128
  const reviewListing = laneIsSenior
5053
5129
  ? listLaneReviews(hqRoot, laneId)
5054
5130
  : { reviews: [], skipped: 0 };
@@ -5075,7 +5151,7 @@ async function runShow(ctx, laneId) {
5075
5151
  stalled_at: lane.stalled_at ?? null,
5076
5152
  stalled_reason: lane.stalled_reason ?? null,
5077
5153
  ask_delivery_failed: askDeliveryFailed,
5078
- delivery: laneDeliveryMechanism(storedLane),
5154
+ delivery,
5079
5155
  provider_session_id: lane.provider_session_id ?? null,
5080
5156
  provider_session_id_absent: lane.provider_session_id_absent ?? null,
5081
5157
  envelope_repair_mode: lane.envelope_repair?.mode ?? null,
@@ -5108,7 +5184,7 @@ async function runShow(ctx, laneId) {
5108
5184
  consecutive_auto_resumes: lane.keep_alive_resume_count ?? 0,
5109
5185
  last_auto_resume_at: lane.keep_alive_last_resume_at ?? null,
5110
5186
  keep_alive_history: lane.keep_alive_history ?? [],
5111
- next_eligible_at: lane.keep_alive_next_eligible_at ?? null,
5187
+ next_eligible_at: nextEligibleAt,
5112
5188
  exhausted_at: lane.keep_alive_exhausted_at ?? null,
5113
5189
  },
5114
5190
  manager_status_gate: managerStatusGateError !== undefined
@@ -5134,6 +5210,7 @@ async function runShow(ctx, laneId) {
5134
5210
  escalated: blockerFreshness.stale.length > 0 &&
5135
5211
  storedLane.blocker_freshness_state?.escalated_fingerprint ===
5136
5212
  blockerFreshnessFingerprint(blockerFreshness.stale),
5213
+ undeliverable_reason: storedLane.blocker_freshness_state?.undeliverable_reason ?? null,
5137
5214
  }
5138
5215
  : null,
5139
5216
  kr_freshness: krFreshness,
@@ -7807,6 +7884,7 @@ export function registerLanesCommand(parent, deps = {}) {
7807
7884
  assertQuestionAnswerAuthorized,
7808
7885
  assertQuestionCompanyMatchesLane,
7809
7886
  casUpdateQuestion,
7887
+ casUpdateLane,
7810
7888
  effectiveLaneQuestionCompany,
7811
7889
  emitLaneMeshEvent,
7812
7890
  groupQuestionLanesByCompany,
@@ -7857,6 +7935,8 @@ export function registerLanesCommand(parent, deps = {}) {
7857
7935
  recordResumeSetup,
7858
7936
  resolveCommandLane: (commandCtx, laneId) => resolveCommandLaneTarget(commandCtx, laneId),
7859
7937
  resolveActiveQuestionCompany,
7938
+ hasPendingQuestionForLane: questionHandlers.hasPendingQuestionForLane,
7939
+ cancelFallbackQuestionsForLane: questionHandlers.cancelFallbackQuestionsForLane,
7860
7940
  resolveResumeProfile,
7861
7941
  selectAutomaticClaudeResumeAccount,
7862
7942
  validateResumeProfileModel,
@@ -7882,6 +7962,8 @@ export function registerLanesCommand(parent, deps = {}) {
7882
7962
  printJson,
7883
7963
  recordResumeSetup,
7884
7964
  resolveCommandLane: (commandCtx, laneId) => resolveCommandLaneTarget(commandCtx, laneId),
7965
+ hasPendingQuestionForLane: questionHandlers.hasPendingQuestionForLane,
7966
+ cancelFallbackQuestionsForLane: questionHandlers.cancelFallbackQuestionsForLane,
7885
7967
  resolveResumeProfile,
7886
7968
  selectAutomaticClaudeResumeAccount,
7887
7969
  validateResumeProfileModel,
@@ -8153,7 +8235,7 @@ export function registerLanesCommand(parent, deps = {}) {
8153
8235
  .description("Show one lane")
8154
8236
  .argument("<lane-id>", "Lane id")
8155
8237
  .action(wrap(async (laneId) => {
8156
- await runShow(ctx, laneId);
8238
+ await runShow(ctx, laneId, questionHandlers.hasPendingQuestionForLane);
8157
8239
  }));
8158
8240
  const stories = lanes
8159
8241
  .command("story")
@@ -8290,7 +8372,7 @@ export function registerLanesCommand(parent, deps = {}) {
8290
8372
  }));
8291
8373
  lanes
8292
8374
  .command("resume")
8293
- .description("Resume an interrupted, done or blocked lane on its recorded provider session; recover failed lanes with the senior-only --from-failed option")
8375
+ .description("Resume an interrupted, done or blocked lane, or an awaiting_input lane with no answerable question; recover failed lanes with the senior-only --from-failed option")
8294
8376
  .argument("<lane-id>", "Lane id")
8295
8377
  .option("--text <msg>", "Operator message for the resumed turn")
8296
8378
  .option("--from-failed", "Explicitly resume a failed lane as its senior")
@@ -278,6 +278,7 @@ async function startMonitor(options, argv = []) {
278
278
  cwd: meta.cwd,
279
279
  env: { ...env, HQ_ROOT: hqRoot },
280
280
  detached: true,
281
+ windowsHide: true,
281
282
  stdio: "ignore",
282
283
  });
283
284
  child.once("error", (err) => {
@@ -347,7 +348,21 @@ async function showMonitor(id, json) {
347
348
  const state = stateForDisplay(hqRoot, id, readJson(statePath(hqRoot, id)));
348
349
  if (!meta || !state)
349
350
  throw new LanesError("monitor_not_found", `Monitor ${id} was not found.`, { monitor_id: id });
350
- const events = fs.readFileSync(path.join(dir, "events.jsonl"), "utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
351
+ const events = [];
352
+ for (const line of fs.readFileSync(path.join(dir, "events.jsonl"), "utf8").split("\n")) {
353
+ if (!line.trim())
354
+ continue;
355
+ let parsed;
356
+ try {
357
+ parsed = JSON.parse(line);
358
+ }
359
+ catch {
360
+ continue;
361
+ }
362
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
363
+ continue;
364
+ events.push(parsed);
365
+ }
351
366
  if (json)
352
367
  printJson({ meta, state, events });
353
368
  else
@@ -712,7 +727,7 @@ export async function runSupervisor(id, deps = {}) {
712
727
  const args = meta.command !== null ? childArgs : childArgs.slice(1);
713
728
  if (!executable)
714
729
  throw new LanesError("monitor_command_required", "No command was recorded for this monitor.");
715
- commandChild = (deps.spawn ?? spawn)(executable, args, { cwd: meta.cwd, env: process.env, detached: true, stdio: ["ignore", "pipe", "pipe"] });
730
+ commandChild = (deps.spawn ?? spawn)(executable, args, { cwd: meta.cwd, env: process.env, detached: true, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
716
731
  if (commandChild.pid) {
717
732
  state = { ...state, child_pid: commandChild.pid, child_starttime: readProcStarttime(commandChild.pid) };
718
733
  writeJsonAtomic(statePath(hqRoot, id), state);
@@ -6,6 +6,7 @@ import * as path from "node:path";
6
6
  import { ensureCognitoToken, } from "../utils/cognito-session.js";
7
7
  import { vaultApiFetch } from "../utils/vault-api.js";
8
8
  import { DEPLOY_API_URL, deployApiRequest, deployHeaders } from "../lib/deploy-api.js";
9
+ import { listDeployApps } from "../lib/deploy-apps.js";
9
10
  import { listReportTemplates } from "../lib/report-templates.js";
10
11
  import { adoptReport, buildReportTarball, conventionReportDir, deployOrgContext, ReportPublishError, plainFailureReason, publishReport, } from "../lib/report-publish.js";
11
12
  const REPORT_CHANNEL_STATE_FILE = "report-channels.json";
@@ -259,8 +260,10 @@ async function orgFor(token, report) {
259
260
  return deployOrgContext(report, slug);
260
261
  }
261
262
  export async function findOrCreateApp(token, slug, org) {
262
- const listed = await deployJson(token, "/api/apps", "GET", undefined, org.headers);
263
- const apps = Array.isArray(listed.apps) ? listed.apps : [];
263
+ const apps = await listDeployApps((cursor) => {
264
+ const route = cursor ? `/api/apps?cursor=${encodeURIComponent(cursor)}` : "/api/apps";
265
+ return deployJson(token, route, "GET", undefined, org.headers);
266
+ });
264
267
  const mine = (name) => apps.find((app) => !!app && typeof app === "object" && (app.subdomain === name || app.name === name));
265
268
  let app;
266
269
  for (const name of candidateSlugs(slug)) {
@@ -45,5 +45,9 @@ export declare function shouldAttemptSelfUpdate(opts: {
45
45
  check?: boolean;
46
46
  selfUpdate?: boolean;
47
47
  }): boolean;
48
+ /** Latest release tag for a repo, e.g. `v12.3.0`. Throws on failure. */
49
+ export declare function getLatestReleaseTag(repo: string, token?: string): Promise<string>;
50
+ /** Resolve a tag to its commit SHA, dereferencing annotated tags. */
51
+ export declare function resolveTagSha(repo: string, tag: string, token?: string): Promise<string>;
48
52
  export declare function registerRescueCommand(program: Command): void;
49
53
  //# sourceMappingURL=rescue.d.ts.map