@indigoai-us/hq-cli 5.299.0 → 5.301.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 (62) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/assets/scaffold/core/scripts/token-usage-report.sh +9 -0
  3. package/dist/command-catalog.generated.d.ts +32 -6
  4. package/dist/command-catalog.generated.js +38 -6
  5. package/dist/commands/__fixtures__/access-vault.d.ts +2 -0
  6. package/dist/commands/__fixtures__/access-vault.js +1 -1
  7. package/dist/commands/access.d.ts +2 -1
  8. package/dist/commands/access.js +58 -49
  9. package/dist/commands/cloud-provision.js +51 -2
  10. package/dist/commands/cloud.js +12 -7
  11. package/dist/commands/core-company-index-scope-flag.d.ts +3 -0
  12. package/dist/commands/core-company-index-scope-flag.js +6 -0
  13. package/dist/commands/core-job-native-utils.d.ts +6 -0
  14. package/dist/commands/core-job-native-utils.js +80 -0
  15. package/dist/commands/core-job-native.d.ts +2 -4
  16. package/dist/commands/core-job-native.js +6 -10
  17. package/dist/commands/core-job-notify-native.d.ts +36 -0
  18. package/dist/commands/core-job-notify-native.js +359 -0
  19. package/dist/commands/core-job-remediate-native.d.ts +26 -0
  20. package/dist/commands/core-job-remediate-native.js +374 -0
  21. package/dist/commands/core.js +26 -8
  22. package/dist/commands/files.d.ts +1 -0
  23. package/dist/commands/files.js +90 -14
  24. package/dist/commands/integrations-api.d.ts +6 -6
  25. package/dist/commands/integrations-api.js +4 -13
  26. package/dist/commands/integrations-manage.js +1 -4
  27. package/dist/commands/lanes-message.js +78 -37
  28. package/dist/commands/lanes-resume.js +247 -210
  29. package/dist/commands/lanes-shared.d.ts +5 -1
  30. package/dist/commands/lanes.d.ts +4 -0
  31. package/dist/commands/lanes.js +7 -4
  32. package/dist/commands/meetings.js +35 -16
  33. package/dist/commands/safe-extract.d.ts +4 -4
  34. package/dist/commands/safe-extract.js +6 -7
  35. package/dist/lib/core-utils/token-usage-report.js +17 -3
  36. package/dist/lib/index-render/companies.d.ts +4 -1
  37. package/dist/lib/index-render/companies.js +66 -3
  38. package/dist/lib/index-render/index.d.ts +1 -0
  39. package/dist/lib/index-render/index.js +2 -1
  40. package/dist/lib/index-render/shared.d.ts +1 -1
  41. package/dist/lib/index-render/shared.js +4 -2
  42. package/dist/lib/lanes/reconcile.js +22 -4
  43. package/dist/lib/lanes/story-switch.d.ts +21 -0
  44. package/dist/lib/lanes/story-switch.js +75 -0
  45. package/dist/lib/lanes/types.d.ts +25 -0
  46. package/dist/lib/lanes/types.js +1 -1
  47. package/dist/lib/lanes/watcher.js +15 -4
  48. package/dist/lib/mesh/live/daemon/mqtt-events.d.ts +7 -0
  49. package/dist/lib/mesh/live/daemon/mqtt-events.js +2 -0
  50. package/dist/lib/mesh/live/daemon/mqtt-events.test-d.d.ts +2 -0
  51. package/dist/lib/mesh/live/daemon/mqtt-events.test-d.js +10 -0
  52. package/dist/lib/mesh/live/daemon/presence.d.ts +2 -2
  53. package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +2 -2
  54. package/dist/utils/self-update.js +51 -4
  55. package/dist/utils/staged-npm-install.d.ts +16 -0
  56. package/dist/utils/staged-npm-install.js +264 -0
  57. package/dist/utils/update-command-supervisor.cjs +53 -1
  58. package/dist/utils/version-gate.d.ts +8 -0
  59. package/dist/utils/version-gate.js +82 -15
  60. package/package.json +2 -2
  61. package/dist/lib/install-path-compatibility-flag.d.ts +0 -9
  62. package/dist/lib/install-path-compatibility-flag.js +0 -12
@@ -74,33 +74,24 @@ export async function installIntegration(token, companyUid, input) {
74
74
  }
75
75
  return (await res.json());
76
76
  }
77
- export async function uninstallIntegration(token, companyUid, installationId) {
77
+ export async function uninstallIntegration(token, companyUid, target) {
78
78
  const res = await vaultApiFetch({
79
79
  token,
80
80
  path: "/v1/integrations/factory/uninstall",
81
81
  method: "POST",
82
- body: { companyUid, installationId },
82
+ body: { companyUid, ...target },
83
83
  });
84
84
  if (!res.ok)
85
85
  await raiseForResponse(res, "Failed to disconnect the app");
86
86
  return (await res.json());
87
87
  }
88
- /**
89
- * Permanently remove a revoked connection tombstone. The admin purge endpoint
90
- * deliberately accepts only the connection id: it derives the company and
91
- * enforces owner authorization from the authenticated connection record.
92
- */
88
+ /** Permanently remove a revoked connection tombstone for one company. */
93
89
  export async function purgeConnection(token, companyUid, connectionId) {
94
- // Keep the company in this client's call signature with the other
95
- // connection mutations. It is resolved before the target connection so a
96
- // caller cannot use a slug from a different company, but the server's purge
97
- // contract intentionally takes only connectionId in its body.
98
- void companyUid;
99
90
  const res = await vaultApiFetch({
100
91
  token,
101
92
  path: "/v1/integrations/admin/purge",
102
93
  method: "POST",
103
- body: { connectionId },
94
+ body: { companyUid, connectionId },
104
95
  });
105
96
  if (!res.ok)
106
97
  await raiseForResponse(res, "Failed to purge the revoked connection");
@@ -610,9 +610,6 @@ export function registerManageCommands(integrations) {
610
610
  const companyUid = await getCompanyUid(token, opts.company);
611
611
  const connection = await resolveConnection(token, companyUid, app, opts);
612
612
  const installationId = connection.installation?.id;
613
- if (!installationId) {
614
- throw new IntegrationsCliError(`${connectionLabel(connection)} was not installed through the app catalog, so it cannot be disconnected from here.`, { expected: true });
615
- }
616
613
  if (!opts.yes) {
617
614
  const label = connectionLabel(connection);
618
615
  // Warning + prompt go to stderr: with --json, stdout must stay parseable,
@@ -625,7 +622,7 @@ export function registerManageCommands(integrations) {
625
622
  throw new IntegrationsCliError("Not disconnected. Re-run with --yes if you are sure.", { expected: true });
626
623
  }
627
624
  }
628
- const result = await uninstallIntegration(token, companyUid, installationId);
625
+ const result = await uninstallIntegration(token, companyUid, installationId ? { installationId } : { connectionId: connection.id });
629
626
  if (opts.json) {
630
627
  printJson(result);
631
628
  return;
@@ -8,6 +8,7 @@ import { dispatchLaneDelivery } from "../lib/lanes/delivery-core.js";
8
8
  import { dropboxInboxHasMessages, drainDropbox, formatDropboxMessages, formatMonitorEvent, requeueDropboxMessages } from "../lib/lanes/dropbox.js";
9
9
  import { isPidAlive, runWatcherLoop, WATCHER_OUT_MAX_BYTES, watcherOutLogPath, writeWatcherDecision } from "../lib/lanes/watcher.js";
10
10
  import { laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
11
+ import { artifactIdentityKeys, rollbackStorySwitchAttribution, storySwitchNotice } from "../lib/lanes/story-switch.js";
11
12
  import { laneRunDir, probePidRecord, readPidRecord, readProcStarttime, spawnLaneWorker } from "../lib/lanes/spawn.js";
12
13
  import { keepAliveExclusion, keepAliveSupersededBy, LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_FLAG, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds } from "../lib/lanes/keep-alive.js";
13
14
  import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
@@ -212,8 +213,11 @@ export function createLaneMessageHandlers(deps) {
212
213
  })();
213
214
  let accepted = false;
214
215
  let claimedMessages = [];
216
+ let storySwitchRollback;
217
+ let storySwitchDeliveryStarted = false;
215
218
  const markAccepted = () => {
216
219
  accepted = true;
220
+ storySwitchDeliveryStarted = true;
217
221
  opts.onAccepted?.();
218
222
  };
219
223
  let text = opts.text;
@@ -392,19 +396,21 @@ export function createLaneMessageHandlers(deps) {
392
396
  if ((opts.project !== undefined || opts.story !== undefined) && ctx.storyLedgerEnabled()) {
393
397
  validateProjectStories(hqRoot, projectId, [storyId], lane.company?.slug);
394
398
  }
399
+ const notice = storySwitchNotice(lane.project_id, lane.story_id, projectId, storyId);
400
+ const promptedText = notice === undefined ? text : `${notice}\n\n${text}`;
395
401
  const framedMonitorEvent = opts.monitorEvent === undefined
396
402
  ? undefined
397
403
  : formatMonitorEvent({
398
404
  monitorId: opts.monitorEvent.monitor_id,
399
405
  description: opts.monitorEvent.description,
400
406
  sequence: opts.monitorEvent.sequence,
401
- text,
407
+ text: promptedText,
402
408
  });
403
- const deliveryText = framedMonitorEvent ?? text;
409
+ const deliveryText = framedMonitorEvent ?? promptedText;
404
410
  const dropboxMessage = {
405
411
  at,
406
412
  message_id: messageId,
407
- text,
413
+ text: promptedText,
408
414
  project_id: projectId,
409
415
  story_id: storyId,
410
416
  ...(opts.monitorEvent === undefined ? {} : {
@@ -414,6 +420,52 @@ export function createLaneMessageHandlers(deps) {
414
420
  sequence: opts.monitorEvent.sequence,
415
421
  }),
416
422
  };
423
+ if (projectId !== lane.project_id || storyId !== lane.story_id) {
424
+ lane = casUpdateLane(hqRoot, laneId, (current) => {
425
+ const already = current.attribution_history.some((entry) => entry.message_id === messageId);
426
+ if (already)
427
+ return current;
428
+ const existingStory = laneStory(current, projectId, storyId);
429
+ storySwitchRollback = {
430
+ message_id: messageId,
431
+ project_id: current.project_id,
432
+ story_id: current.story_id,
433
+ no_story_reason: current.no_story_reason,
434
+ ...(!existingStory ? {
435
+ added_story: { project_id: projectId, story_id: storyId, status: "in_progress", added_at: at, updated_at: at },
436
+ } : {}),
437
+ story_switch_unacknowledged: current.story_switch_unacknowledged,
438
+ };
439
+ return {
440
+ ...current,
441
+ project_id: projectId,
442
+ story_id: storyId,
443
+ no_story_reason: storyId ? undefined : current.no_story_reason,
444
+ stories: laneStory(current, projectId, storyId)
445
+ ? laneStories(current)
446
+ : [
447
+ ...laneStories(current),
448
+ { project_id: projectId, story_id: storyId, status: "in_progress", added_at: at, updated_at: at },
449
+ ],
450
+ attribution_history: appendCappedHistory(current.attribution_history, {
451
+ project_id: projectId,
452
+ story_id: storyId,
453
+ message_id: messageId,
454
+ at,
455
+ }),
456
+ story_switch_pending: {
457
+ previous_project_id: current.project_id,
458
+ previous_story_id: current.story_id,
459
+ project_id: projectId,
460
+ story_id: storyId,
461
+ message_id: messageId,
462
+ at,
463
+ previous_artifact_keys: artifactIdentityKeys(current.last_envelope?.artifacts),
464
+ },
465
+ story_switch_unacknowledged: undefined,
466
+ };
467
+ });
468
+ }
417
469
  let resumed = null;
418
470
  let move = null;
419
471
  let queuedOnAdmission = false;
@@ -436,6 +488,8 @@ export function createLaneMessageHandlers(deps) {
436
488
  });
437
489
  lane = move.lane;
438
490
  if (move.queued) {
491
+ storySwitchDeliveryStarted = true;
492
+ deps.onLaneMessageQueued?.(laneId, "account_move");
439
493
  archiveResumeMessage(hqRoot, laneId, dropboxMessage);
440
494
  }
441
495
  }
@@ -461,8 +515,11 @@ export function createLaneMessageHandlers(deps) {
461
515
  accountSelection: selection,
462
516
  });
463
517
  lane = move.lane;
464
- if (move.queued)
518
+ if (move.queued) {
519
+ storySwitchDeliveryStarted = true;
520
+ deps.onLaneMessageQueued?.(laneId, "account_move");
465
521
  archiveResumeMessage(hqRoot, laneId, dropboxMessage);
522
+ }
466
523
  }
467
524
  else {
468
525
  lane = persistClaudeAccountSelection(hqRoot, lane, selection);
@@ -491,6 +548,7 @@ export function createLaneMessageHandlers(deps) {
491
548
  throw new LanesError("lane_not_accepting_messages", operatorMessageRefusalReason(lane.state), { lane_id: laneId, state: lane.state });
492
549
  }
493
550
  appendDropboxMessage(hqRoot, laneId, dropboxMessage);
551
+ storySwitchDeliveryStarted = true;
494
552
  return null;
495
553
  },
496
554
  resume: async () => {
@@ -512,7 +570,7 @@ export function createLaneMessageHandlers(deps) {
512
570
  }
513
571
  archiveResumeMessage(hqRoot, laneId, dropboxMessage);
514
572
  queuedOnAdmission = true;
515
- return casUpdateLane(hqRoot, laneId, (current) => {
573
+ const queued = casUpdateLane(hqRoot, laneId, (current) => {
516
574
  if (current.version !== lane.version || current.state !== lane.state) {
517
575
  throw new LanesError("lane_admission_conflict", `Lane ${laneId} changed while message admission was checked`, {
518
576
  lane_id: laneId,
@@ -539,6 +597,9 @@ export function createLaneMessageHandlers(deps) {
539
597
  },
540
598
  };
541
599
  });
600
+ storySwitchDeliveryStarted = true;
601
+ deps.onLaneMessageQueued?.(laneId, "admission");
602
+ return queued;
542
603
  }
543
604
  const started = spawnLaneWorker(hqRoot, lane, {
544
605
  env: ctx.env,
@@ -565,6 +626,8 @@ export function createLaneMessageHandlers(deps) {
565
626
  compact: opts.compact !== false,
566
627
  effort: resumeEffort ?? recordedResumeEffort(lane),
567
628
  });
629
+ storySwitchDeliveryStarted = true;
630
+ deps.onLaneWorkerStarted?.(laneId, "message");
568
631
  archiveResumeMessage(hqRoot, laneId, dropboxMessage);
569
632
  return started;
570
633
  }, hireGrantContext);
@@ -576,41 +639,10 @@ export function createLaneMessageHandlers(deps) {
576
639
  markAccepted();
577
640
  }
578
641
  // Every accepted message moves the compaction reference forward.
579
- let next = casUpdateLane(hqRoot, laneId, (current) => ({
642
+ const next = casUpdateLane(hqRoot, laneId, (current) => ({
580
643
  ...resetKeepAliveProgress(current),
581
644
  last_message_at: at,
582
645
  }));
583
- if (projectId !== lane.project_id || storyId !== lane.story_id) {
584
- next = casUpdateLane(hqRoot, laneId, (current) => {
585
- const already = current.attribution_history.some((entry) => entry.message_id === messageId);
586
- if (already)
587
- return current;
588
- return {
589
- ...current,
590
- project_id: projectId,
591
- story_id: storyId,
592
- no_story_reason: storyId ? undefined : current.no_story_reason,
593
- stories: laneStory(current, projectId, storyId)
594
- ? laneStories(current)
595
- : [
596
- ...laneStories(current),
597
- {
598
- project_id: projectId,
599
- story_id: storyId,
600
- status: "in_progress",
601
- added_at: at,
602
- updated_at: at,
603
- },
604
- ],
605
- attribution_history: appendCappedHistory(current.attribution_history, {
606
- project_id: projectId,
607
- story_id: storyId,
608
- message_id: messageId,
609
- at,
610
- }),
611
- };
612
- });
613
- }
614
646
  // What this command can claim when it returns: the message is durably
615
647
  // written (accepted). Delivery to the model is asynchronous in both
616
648
  // mechanisms. `dropbox` delivers on the worker's next tool call, and only
@@ -660,6 +692,15 @@ export function createLaneMessageHandlers(deps) {
660
692
  });
661
693
  }
662
694
  catch (err) {
695
+ if (storySwitchRollback !== undefined && !storySwitchDeliveryStarted) {
696
+ try {
697
+ casUpdateLane(hqRoot, laneId, (current) => rollbackStorySwitchAttribution(current, storySwitchRollback));
698
+ }
699
+ catch (rollbackErr) {
700
+ const rollbackMessage = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
701
+ process.stderr.write(`Could not roll back story attribution after message failure: ${rollbackMessage}\n`);
702
+ }
703
+ }
663
704
  if (claimedMessages.length > 0 && !accepted) {
664
705
  const error = err instanceof Error ? err.message : String(err);
665
706
  writeWatcherDecision(watcherOutLogPath(hqRoot, laneId), {