@indigoai-us/hq-cli 5.300.0 → 5.302.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 (99) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/assets/scaffold/core/scripts/rebuild-projects-index.sh +99 -12
  3. package/assets/scaffold/core/scripts/token-usage-report.sh +9 -0
  4. package/dist/command-catalog.generated.d.ts +32 -3
  5. package/dist/command-catalog.generated.js +39 -3
  6. package/dist/commands/__fixtures__/access-vault.d.ts +2 -0
  7. package/dist/commands/__fixtures__/access-vault.js +1 -1
  8. package/dist/commands/access.d.ts +2 -1
  9. package/dist/commands/access.js +58 -49
  10. package/dist/commands/bot.js +1 -0
  11. package/dist/commands/cloud-provision.js +51 -2
  12. package/dist/commands/core-checkpoint.js +1 -0
  13. package/dist/commands/core-company-index-scope-flag.d.ts +3 -0
  14. package/dist/commands/core-company-index-scope-flag.js +6 -0
  15. package/dist/commands/core-handoff-fast.js +1 -0
  16. package/dist/commands/core-handoff.js +2 -0
  17. package/dist/commands/core-job-native-utils.d.ts +6 -0
  18. package/dist/commands/core-job-native-utils.js +80 -0
  19. package/dist/commands/core-job-native.d.ts +2 -4
  20. package/dist/commands/core-job-native.js +6 -10
  21. package/dist/commands/core-job-notify-native.d.ts +36 -0
  22. package/dist/commands/core-job-notify-native.js +359 -0
  23. package/dist/commands/core-job-remediate-native.d.ts +26 -0
  24. package/dist/commands/core-job-remediate-native.js +374 -0
  25. package/dist/commands/core.js +26 -8
  26. package/dist/commands/deploy-comments.js +5 -2
  27. package/dist/commands/files.js +31 -14
  28. package/dist/commands/integrations-api.d.ts +6 -6
  29. package/dist/commands/integrations-api.js +4 -13
  30. package/dist/commands/integrations-connect.d.ts +3 -0
  31. package/dist/commands/integrations-connect.js +21 -2
  32. package/dist/commands/integrations-manage.js +1 -4
  33. package/dist/commands/lanes-message.js +43 -5
  34. package/dist/commands/lanes-questions.d.ts +4 -2
  35. package/dist/commands/lanes-questions.js +123 -2
  36. package/dist/commands/lanes-resume.js +25 -4
  37. package/dist/commands/lanes-shared.d.ts +15 -1
  38. package/dist/commands/lanes-watch.js +5 -1
  39. package/dist/commands/lanes.d.ts +2 -0
  40. package/dist/commands/lanes.js +92 -9
  41. package/dist/commands/meetings.js +35 -16
  42. package/dist/commands/monitor.js +17 -2
  43. package/dist/commands/report.js +5 -2
  44. package/dist/commands/rescue.d.ts +4 -0
  45. package/dist/commands/rescue.js +27 -17
  46. package/dist/commands/safe-extract.d.ts +4 -4
  47. package/dist/commands/safe-extract.js +6 -7
  48. package/dist/lib/agent-kit/fallback.d.ts +1 -0
  49. package/dist/lib/agent-kit/fallback.js +1 -0
  50. package/dist/lib/bot/daemon.d.ts +5 -1
  51. package/dist/lib/bot/daemon.js +4 -4
  52. package/dist/lib/core-utils/soft-timeout.js +1 -1
  53. package/dist/lib/core-utils/token-usage-report.js +17 -3
  54. package/dist/lib/deploy-apps.d.ts +13 -0
  55. package/dist/lib/deploy-apps.js +24 -0
  56. package/dist/lib/index-render/companies.d.ts +4 -1
  57. package/dist/lib/index-render/companies.js +66 -3
  58. package/dist/lib/index-render/index.d.ts +1 -0
  59. package/dist/lib/index-render/index.js +2 -1
  60. package/dist/lib/index-render/projects.js +82 -5
  61. package/dist/lib/index-render/shared.d.ts +1 -1
  62. package/dist/lib/index-render/shared.js +4 -2
  63. package/dist/lib/lanes/delivery.d.ts +7 -3
  64. package/dist/lib/lanes/delivery.js +18 -8
  65. package/dist/lib/lanes/index.d.ts +1 -1
  66. package/dist/lib/lanes/keep-alive.d.ts +13 -3
  67. package/dist/lib/lanes/keep-alive.js +29 -5
  68. package/dist/lib/lanes/outpost-dispatch.d.ts +5 -0
  69. package/dist/lib/lanes/outpost-dispatch.js +12 -0
  70. package/dist/lib/lanes/reconcile.d.ts +1 -0
  71. package/dist/lib/lanes/reconcile.js +358 -38
  72. package/dist/lib/lanes/spawn.d.ts +1 -0
  73. package/dist/lib/lanes/spawn.js +20 -10
  74. package/dist/lib/lanes/store.js +24 -0
  75. package/dist/lib/lanes/story-ledger.d.ts +2 -0
  76. package/dist/lib/lanes/story-ledger.js +8 -0
  77. package/dist/lib/lanes/types.d.ts +26 -4
  78. package/dist/lib/lanes/types.js +33 -1
  79. package/dist/lib/lanes/watch.d.ts +1 -0
  80. package/dist/lib/lanes/watch.js +19 -6
  81. package/dist/lib/lanes/watcher.js +139 -9
  82. package/dist/lib/mesh/live/daemon/mqtt-events.d.ts +7 -0
  83. package/dist/lib/mesh/live/daemon/mqtt-events.js +2 -0
  84. package/dist/lib/mesh/live/daemon/mqtt-events.test-d.d.ts +2 -0
  85. package/dist/lib/mesh/live/daemon/mqtt-events.test-d.js +10 -0
  86. package/dist/lib/mesh/live/daemon/presence.d.ts +2 -2
  87. package/dist/lib/mesh/live/daemon/run.js +36 -2
  88. package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +2 -2
  89. package/dist/lib/search-index/background.d.ts +1 -0
  90. package/dist/lib/search-index/background.js +1 -0
  91. package/dist/utils/self-update.js +51 -4
  92. package/dist/utils/staged-npm-install.d.ts +16 -0
  93. package/dist/utils/staged-npm-install.js +264 -0
  94. package/dist/utils/update-command-supervisor.cjs +54 -1
  95. package/dist/utils/version-gate.d.ts +8 -0
  96. package/dist/utils/version-gate.js +83 -16
  97. package/package.json +2 -2
  98. package/dist/lib/install-path-compatibility-flag.d.ts +0 -9
  99. package/dist/lib/install-path-compatibility-flag.js +0 -12
@@ -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,
@@ -7838,6 +7916,7 @@ export function registerLanesCommand(parent, deps = {}) {
7838
7916
  });
7839
7917
  const messageHandlerDeps = {
7840
7918
  onLaneWorkerStarted: deps.onLaneWorkerStarted,
7919
+ onLaneMessageQueued: deps.onLaneMessageQueued,
7841
7920
  archiveResumeMessage,
7842
7921
  assertLaneRunsHereForAdmission,
7843
7922
  assertLaneRunsHereForOperatorMutation,
@@ -7856,6 +7935,8 @@ export function registerLanesCommand(parent, deps = {}) {
7856
7935
  recordResumeSetup,
7857
7936
  resolveCommandLane: (commandCtx, laneId) => resolveCommandLaneTarget(commandCtx, laneId),
7858
7937
  resolveActiveQuestionCompany,
7938
+ hasPendingQuestionForLane: questionHandlers.hasPendingQuestionForLane,
7939
+ cancelFallbackQuestionsForLane: questionHandlers.cancelFallbackQuestionsForLane,
7859
7940
  resolveResumeProfile,
7860
7941
  selectAutomaticClaudeResumeAccount,
7861
7942
  validateResumeProfileModel,
@@ -7881,6 +7962,8 @@ export function registerLanesCommand(parent, deps = {}) {
7881
7962
  printJson,
7882
7963
  recordResumeSetup,
7883
7964
  resolveCommandLane: (commandCtx, laneId) => resolveCommandLaneTarget(commandCtx, laneId),
7965
+ hasPendingQuestionForLane: questionHandlers.hasPendingQuestionForLane,
7966
+ cancelFallbackQuestionsForLane: questionHandlers.cancelFallbackQuestionsForLane,
7884
7967
  resolveResumeProfile,
7885
7968
  selectAutomaticClaudeResumeAccount,
7886
7969
  validateResumeProfileModel,
@@ -8152,7 +8235,7 @@ export function registerLanesCommand(parent, deps = {}) {
8152
8235
  .description("Show one lane")
8153
8236
  .argument("<lane-id>", "Lane id")
8154
8237
  .action(wrap(async (laneId) => {
8155
- await runShow(ctx, laneId);
8238
+ await runShow(ctx, laneId, questionHandlers.hasPendingQuestionForLane);
8156
8239
  }));
8157
8240
  const stories = lanes
8158
8241
  .command("story")
@@ -8289,7 +8372,7 @@ export function registerLanesCommand(parent, deps = {}) {
8289
8372
  }));
8290
8373
  lanes
8291
8374
  .command("resume")
8292
- .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")
8293
8376
  .argument("<lane-id>", "Lane id")
8294
8377
  .option("--text <msg>", "Operator message for the resumed turn")
8295
8378
  .option("--from-failed", "Explicitly resume a failed lane as its senior")
@@ -3,6 +3,25 @@ import { readFile } from "node:fs/promises";
3
3
  import { ensureCognitoToken } from "../utils/cognito-session.js";
4
4
  import { vaultApiFetch, getCompanyUid, resolveCallerPersonUid, } from "../utils/vault-api.js";
5
5
  import { isPlanGateError, planGateErrorFromPayload, } from "../utils/plan-gate-error.js";
6
+ // Two minutes gives a large transcript time to arrive over a slow connection,
7
+ // while keeping the interactive command from waiting indefinitely.
8
+ const MEETING_DOCUMENT_DOWNLOAD_TIMEOUT_MS = 120_000;
9
+ async function downloadMeetingDocument(url, readBody) {
10
+ const signal = AbortSignal.timeout(MEETING_DOCUMENT_DOWNLOAD_TIMEOUT_MS);
11
+ try {
12
+ const response = await fetch(url, { signal });
13
+ if (!response.ok)
14
+ return { response, ok: false };
15
+ return { response, ok: true, body: await readBody(response) };
16
+ }
17
+ catch (err) {
18
+ if ((signal.aborted && signal.reason instanceof DOMException && signal.reason.name === "TimeoutError") ||
19
+ (err instanceof DOMException && err.name === "TimeoutError")) {
20
+ throw new Error(`Meeting document download timed out after ${MEETING_DOCUMENT_DOWNLOAD_TIMEOUT_MS / 1000} seconds. Check your network connection and try again.`);
21
+ }
22
+ throw err;
23
+ }
24
+ }
6
25
  function formatDuration(seconds) {
7
26
  const h = Math.floor(seconds / 3600);
8
27
  const m = Math.floor((seconds % 3600) / 60);
@@ -495,12 +514,12 @@ export function registerMeetingsCommand(program) {
495
514
  console.error(chalk.red("No document available for this meeting."));
496
515
  process.exit(1);
497
516
  }
498
- const docRes = await fetch(documentUrl);
499
- if (!docRes.ok) {
500
- console.error(chalk.red(`Failed to download meeting document (${docRes.status})`));
517
+ const download = await downloadMeetingDocument(documentUrl, (response) => response.text());
518
+ if (!download.ok) {
519
+ console.error(chalk.red(`Failed to download meeting document (${download.response.status})`));
501
520
  process.exit(1);
502
521
  }
503
- const markdown = await docRes.text();
522
+ const markdown = download.body;
504
523
  if (meetings.opts().json) {
505
524
  console.log(JSON.stringify({ meetingId: detail.meetingId, sourceShape: "markdown", markdown }, null, 2));
506
525
  return;
@@ -513,12 +532,12 @@ export function registerMeetingsCommand(program) {
513
532
  console.error(chalk.red("No document URL available for this meeting."));
514
533
  process.exit(1);
515
534
  }
516
- const docRes = await fetch(detail.documentUrl);
517
- if (!docRes.ok) {
518
- console.error(chalk.red(`Failed to download meeting document (${docRes.status})`));
535
+ const download = await downloadMeetingDocument(detail.documentUrl, (response) => response.json());
536
+ if (!download.ok) {
537
+ console.error(chalk.red(`Failed to download meeting document (${download.response.status})`));
519
538
  process.exit(1);
520
539
  }
521
- const doc = (await docRes.json());
540
+ const doc = download.body;
522
541
  if (meetings.opts().json) {
523
542
  console.log(JSON.stringify(doc.transcript, null, 2));
524
543
  return;
@@ -573,12 +592,12 @@ export function registerMeetingsCommand(program) {
573
592
  console.log(chalk.yellow("No notes available for this meeting."));
574
593
  return;
575
594
  }
576
- const docRes = await fetch(documentUrl);
577
- if (!docRes.ok) {
578
- console.error(chalk.red(`Failed to download meeting document (${docRes.status})`));
595
+ const download = await downloadMeetingDocument(documentUrl, (response) => response.text());
596
+ if (!download.ok) {
597
+ console.error(chalk.red(`Failed to download meeting document (${download.response.status})`));
579
598
  process.exit(1);
580
599
  }
581
- const markdown = await docRes.text();
600
+ const markdown = download.body;
582
601
  if (meetings.opts().json) {
583
602
  console.log(JSON.stringify({ meetingId: detail.meetingId, sourceShape: "markdown", markdown }, null, 2));
584
603
  return;
@@ -591,12 +610,12 @@ export function registerMeetingsCommand(program) {
591
610
  console.error(chalk.red("No document URL available for this meeting."));
592
611
  process.exit(1);
593
612
  }
594
- const docRes = await fetch(detail.documentUrl);
595
- if (!docRes.ok) {
596
- console.error(chalk.red(`Failed to download meeting document (${docRes.status})`));
613
+ const download = await downloadMeetingDocument(detail.documentUrl, (response) => response.json());
614
+ if (!download.ok) {
615
+ console.error(chalk.red(`Failed to download meeting document (${download.response.status})`));
597
616
  process.exit(1);
598
617
  }
599
- const doc = (await docRes.json());
618
+ const doc = download.body;
600
619
  if (meetings.opts().json) {
601
620
  console.log(JSON.stringify(doc.notes, null, 2));
602
621
  return;
@@ -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
@@ -53,38 +53,48 @@ function ghHeaders(token) {
53
53
  headers.Authorization = `Bearer ${token}`;
54
54
  return headers;
55
55
  }
56
+ // Thirty seconds gives GitHub time to respond while keeping the recovery command bounded.
57
+ const GITHUB_REQUEST_TIMEOUT_MS = 30_000;
58
+ async function githubJsonRequest(url, token) {
59
+ const signal = AbortSignal.timeout(GITHUB_REQUEST_TIMEOUT_MS);
60
+ try {
61
+ const response = await fetch(url, { headers: ghHeaders(token), signal });
62
+ if (!response.ok)
63
+ return { response };
64
+ const body = (await response.json());
65
+ return { response, body };
66
+ }
67
+ catch (error) {
68
+ if (signal.aborted)
69
+ throw new Error('GitHub request timed out', { cause: error });
70
+ throw error;
71
+ }
72
+ }
56
73
  /** Latest release tag for a repo, e.g. `v12.3.0`. Throws on failure. */
57
- async function getLatestReleaseTag(repo, token) {
58
- const res = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
59
- headers: ghHeaders(token),
60
- });
61
- if (!res.ok) {
62
- throw new Error(`GitHub releases/latest for ${repo}: HTTP ${res.status}`);
74
+ export async function getLatestReleaseTag(repo, token) {
75
+ const { response, body } = await githubJsonRequest(`https://api.github.com/repos/${repo}/releases/latest`, token);
76
+ if (!response.ok) {
77
+ throw new Error(`GitHub releases/latest for ${repo}: HTTP ${response.status}`);
63
78
  }
64
- const body = (await res.json());
65
- if (!body.tag_name)
79
+ if (!body?.tag_name)
66
80
  throw new Error(`no tag_name in latest release for ${repo}`);
67
81
  return body.tag_name;
68
82
  }
69
83
  /** Resolve a tag to its commit SHA, dereferencing annotated tags. */
70
- async function resolveTagSha(repo, tag, token) {
71
- const refRes = await fetch(`https://api.github.com/repos/${repo}/git/ref/tags/${encodeURIComponent(tag)}`, { headers: ghHeaders(token) });
84
+ export async function resolveTagSha(repo, tag, token) {
85
+ const { response: refRes, body: ref } = await githubJsonRequest(`https://api.github.com/repos/${repo}/git/ref/tags/${encodeURIComponent(tag)}`, token);
72
86
  if (!refRes.ok)
73
87
  throw new Error(`git/ref/tags/${tag} for ${repo}: HTTP ${refRes.status}`);
74
- const ref = (await refRes.json());
75
- const obj = ref.object;
88
+ const obj = ref?.object;
76
89
  if (!obj?.sha)
77
90
  throw new Error(`no object.sha for tag ${tag} in ${repo}`);
78
91
  if (obj.type !== 'tag')
79
92
  return obj.sha; // lightweight tag → already a commit
80
93
  // Annotated tag → dereference to the commit it points at.
81
- const tagRes = await fetch(`https://api.github.com/repos/${repo}/git/tags/${obj.sha}`, {
82
- headers: ghHeaders(token),
83
- });
94
+ const { response: tagRes, body: annotated } = await githubJsonRequest(`https://api.github.com/repos/${repo}/git/tags/${obj.sha}`, token);
84
95
  if (!tagRes.ok)
85
96
  throw new Error(`git/tags/${obj.sha} for ${repo}: HTTP ${tagRes.status}`);
86
- const annotated = (await tagRes.json());
87
- if (!annotated.object?.sha)
97
+ if (!annotated?.object?.sha)
88
98
  throw new Error(`annotated tag ${tag} has no target sha`);
89
99
  return annotated.object.sha;
90
100
  }
@@ -111,10 +111,10 @@ export declare function assertLinkTargetContained(packDir: string, entryAbs: str
111
111
  /**
112
112
  * List a gzip-compressed tarball's entries via `tar -tvf` (no extraction).
113
113
  * argv form, never a shell string, keeps hostile filenames out of shell syntax.
114
- * When the default-off compatibility flag is enabled, Windows drive paths use
115
- * a basename relative to the archive directory. GNU tar cannot parse `C:\\...`
116
- * as remote `host:path`, and Windows' bundled bsdtar accepts the same relative
117
- * argv without GNU-only `--force-local`.
114
+ * Windows drive paths use a basename relative to the archive directory by
115
+ * default. GNU tar cannot parse `C:\\...` as remote `host:path`, and Windows'
116
+ * bundled bsdtar accepts the same relative argv without GNU-only `--force-local`.
117
+ * Callers can still explicitly opt out with `useRelativeWindowsDrivePath: false`.
118
118
  */
119
119
  export declare function listTarball(tarballPath: string, opts?: Pick<SafeExtractOptions, 'platform' | 'useRelativeWindowsDrivePath' | 'execFileSync'>): TarEntry[];
120
120
  /**
@@ -35,7 +35,6 @@
35
35
  import { execFileSync } from 'child_process';
36
36
  import * as fs from 'fs';
37
37
  import * as path from 'path';
38
- import { installPathCompatibilityEnabled } from '../lib/install-path-compatibility-flag.js';
39
38
  export const DEFAULT_CAPS = {
40
39
  // A content pack is docs/skills/policies/small scripts. 256 MiB is already
41
40
  // generous; anything larger is almost certainly a bomb or a mistake.
@@ -218,14 +217,14 @@ function tarballArgvLocation(tarballPath, platform, useRelativeWindowsDrivePath)
218
217
  /**
219
218
  * List a gzip-compressed tarball's entries via `tar -tvf` (no extraction).
220
219
  * argv form, never a shell string, keeps hostile filenames out of shell syntax.
221
- * When the default-off compatibility flag is enabled, Windows drive paths use
222
- * a basename relative to the archive directory. GNU tar cannot parse `C:\\...`
223
- * as remote `host:path`, and Windows' bundled bsdtar accepts the same relative
224
- * argv without GNU-only `--force-local`.
220
+ * Windows drive paths use a basename relative to the archive directory by
221
+ * default. GNU tar cannot parse `C:\\...` as remote `host:path`, and Windows'
222
+ * bundled bsdtar accepts the same relative argv without GNU-only `--force-local`.
223
+ * Callers can still explicitly opt out with `useRelativeWindowsDrivePath: false`.
225
224
  */
226
225
  export function listTarball(tarballPath, opts = {}) {
227
226
  const platform = opts.platform ?? process.platform;
228
- const useRelativeWindowsDrivePath = opts.useRelativeWindowsDrivePath ?? installPathCompatibilityEnabled();
227
+ const useRelativeWindowsDrivePath = opts.useRelativeWindowsDrivePath ?? true;
229
228
  const location = tarballArgvLocation(tarballPath, platform, useRelativeWindowsDrivePath);
230
229
  const out = (opts.execFileSync ?? execFileSync)('tar', ['-tvf', location.archiveArg], {
231
230
  encoding: 'utf-8',
@@ -330,7 +329,7 @@ export function safeExtractTarball(tarballPath, finalDir, opts = {}) {
330
329
  }
331
330
  // Resolve once so listing and extraction use the same archive location.
332
331
  const platform = opts.platform ?? process.platform;
333
- const useRelativeWindowsDrivePath = opts.useRelativeWindowsDrivePath ?? installPathCompatibilityEnabled();
332
+ const useRelativeWindowsDrivePath = opts.useRelativeWindowsDrivePath ?? true;
334
333
  const exec = opts.execFileSync ?? execFileSync;
335
334
  const location = tarballArgvLocation(tarballPath, platform, useRelativeWindowsDrivePath);
336
335
  // 1. Pre-flight against headers (no bytes written yet).
@@ -37,6 +37,7 @@ export declare function stopFallback(paths: Pick<AgentKitPaths, "agentDir">, kil
37
37
  export interface DetachedSpawn {
38
38
  (cmd: string, args: string[], opts: {
39
39
  detached: true;
40
+ windowsHide: true;
40
41
  stdio: ["ignore", number, number];
41
42
  env: NodeJS.ProcessEnv;
42
43
  cwd: string;
@@ -113,6 +113,7 @@ export function startFallback(paths, host, deps = {}) {
113
113
  try {
114
114
  const child = doSpawn(cmd, args, {
115
115
  detached: true,
116
+ windowsHide: true,
116
117
  stdio: ["ignore", fd, fd],
117
118
  env: { ...(deps.env ?? process.env), HOME: host.home, ...kitServiceEnv(paths) },
118
119
  cwd: paths.agentDir,
@@ -78,7 +78,11 @@ export interface BotDaemonDeps {
78
78
  */
79
79
  bootout?: "inline" | "detached";
80
80
  /** Injectable detached spawn, so tests never reach the real service manager. */
81
- spawnDetached?: (command: string, args: string[]) => void;
81
+ spawnDetached?: (command: string, args: string[], options: {
82
+ detached: true;
83
+ windowsHide: true;
84
+ stdio: "ignore";
85
+ }) => void;
82
86
  /**
83
87
  * Called once the removal is durable (plist gone) but before the bootout —
84
88
  * the last moment a self-booting-out caller is still alive to say anything.
@@ -149,8 +149,8 @@ function shQuote(value) {
149
149
  return `'${value.replace(/'/g, `'\\''`)}'`;
150
150
  }
151
151
  /** Detached `launchctl bootout` that outlives the process asking for it. */
152
- function defaultSpawnDetached(command, args) {
153
- const child = spawn(command, args, { detached: true, stdio: "ignore" });
152
+ function defaultSpawnDetached(command, args, options) {
153
+ const child = spawn(command, args, options);
154
154
  child.unref();
155
155
  }
156
156
  /**
@@ -165,7 +165,7 @@ function defaultSpawnDetached(command, args) {
165
165
  function bootoutLaunchd(p, deps) {
166
166
  const target = `${launchdDomain(deps.uid)}/${p.label}`;
167
167
  if (deps.bootout === "detached") {
168
- (deps.spawnDetached ?? defaultSpawnDetached)("/bin/sh", ["-c", `sleep 1; exec launchctl bootout ${shQuote(target)}`]);
168
+ (deps.spawnDetached ?? defaultSpawnDetached)("/bin/sh", ["-c", `sleep 1; exec launchctl bootout ${shQuote(target)}`], { detached: true, windowsHide: true, stdio: "ignore" });
169
169
  return;
170
170
  }
171
171
  deps.launchctl?.(["bootout", target]);
@@ -336,7 +336,7 @@ export function healBotDaemonNodeFromJob(p, deps = {}) {
336
336
  `for d in 0 1 2 4; do sleep $d; launchctl bootstrap ${shQuote(domain)} ${shQuote(p.plistPath)} >/dev/null 2>&1 && exit 0; done`,
337
337
  "exit 1",
338
338
  ].join("; ");
339
- (deps.spawnDetached ?? defaultSpawnDetached)("/bin/sh", ["-c", script]);
339
+ (deps.spawnDetached ?? defaultSpawnDetached)("/bin/sh", ["-c", script], { detached: true, windowsHide: true, stdio: "ignore" });
340
340
  return fileNeedsRewrite ? { from: file.nodeBinary, to: fileTarget } : { from: loaded, to: loadedTarget };
341
341
  }
342
342
  /** `program = …` of the loaded job (`launchctl print`), or null when not loaded or unknown. */
@@ -110,7 +110,7 @@ export function runSoftTimeout(parsed, deps = {}) {
110
110
  // orphaned grandchild (e.g. run-project's builder) keeps running after the
111
111
  // wrapper returns. stdio inherited so the command's stdin/stdout/stderr —
112
112
  // including piped or interactive input — pass through unchanged.
113
- const child = spawnFn(command[0], command.slice(1), { stdio: "inherit", detached: true });
113
+ const child = spawnFn(command[0], command.slice(1), { stdio: "inherit", detached: true, windowsHide: true });
114
114
  let capped = false;
115
115
  let ticks = 0;
116
116
  // Signal the child's process group when we can (detached leader), else the