@runuai/host 0.9.156 → 0.9.158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -346,7 +346,7 @@ function untilLeavingLegacy<T>(legacy: LegacyStartPermit | null, wait: Promise<T
346
346
 
347
347
  /** A start the runner does not take while it switches its tasks to cloud
348
348
  * credentials: the cloud starts it again shortly. */
349
- function switchingToCloudCredentials(): TaskUpOutcome {
349
+ function switchingToCloudCredentials(): HostCommandResult<never> {
350
350
  return {
351
351
  ok: false,
352
352
  code: HostErrorCode.HostUnavailable,
@@ -621,8 +621,9 @@ async function taskUpOn(
621
621
  // that failed after Compose created its network can retain an empty subnet
622
622
  // while it waits for Resume. Reclaim only networks whose trusted host row,
623
623
  // exact Docker identity and two empty-container inventories all agree. The
624
- // sweep runs before this task's lifecycle slot so concurrent failed starts
625
- // can never deadlock while trying to inspect one another.
624
+ // sweep runs before this task's lifecycle slot and never waits on another
625
+ // task's: a candidate whose slot is busy is skipped, so concurrent starts
626
+ // neither deadlock nor queue behind one another's provisioning.
626
627
  if (
627
628
  !sidecarMode() &&
628
629
  !machineTasksEnabled() &&
@@ -642,6 +643,12 @@ async function taskUpOn(
642
643
  "[host-agent] skipped task-network recovery because Docker returned an invalid network inventory",
643
644
  );
644
645
  }
646
+ if (networkRecovery.busy > 0) {
647
+ console.log(
648
+ `[host-agent] task-network recovery left ${String(networkRecovery.busy)} ` +
649
+ `network(s) alone while their task was busy`,
650
+ );
651
+ }
645
652
  } catch (error) {
646
653
  // Best-effort capacity recovery must not block a task when Docker still
647
654
  // has room. `docker compose up` remains the authoritative allocation.
@@ -1976,11 +1983,17 @@ export const hostCommands: HostCommands = {
1976
1983
 
1977
1984
  async channelEnsure(ctx, input) {
1978
1985
  logCommand(ctx, "channelEnsure", input.taskId);
1986
+ if (credentialsMode() === "pending") {
1987
+ return switchingToCloudCredentials();
1988
+ }
1979
1989
  const admissionFailure = managedMaintenanceUnavailable();
1980
1990
  if (admissionFailure) return admissionFailure;
1981
1991
  const runtimeFailure = runtimeUnavailable();
1982
1992
  if (runtimeFailure) return runtimeFailure;
1983
1993
  await agentClisReady;
1994
+ if (credentialsMode() === "pending") {
1995
+ return switchingToCloudCredentials();
1996
+ }
1984
1997
  const readyAdmissionFailure = managedMaintenanceUnavailable();
1985
1998
  if (readyAdmissionFailure) return readyAdmissionFailure;
1986
1999
  const readyRuntimeFailure = runtimeUnavailable();
@@ -2025,6 +2038,9 @@ export const hostCommands: HostCommands = {
2025
2038
 
2026
2039
  async channelDeliver(ctx, taskId, agentId, text, origin) {
2027
2040
  logCommand(ctx, "channelDeliver", taskId, agentId);
2041
+ if (credentialsMode() === "pending") {
2042
+ return switchingToCloudCredentials();
2043
+ }
2028
2044
  const runtimeFailure = runtimeUnavailable();
2029
2045
  if (runtimeFailure) return runtimeFailure;
2030
2046
  try {
package/src/main.ts CHANGED
@@ -302,6 +302,7 @@ import {
302
302
  LIVE_ROSTER_EDIT_PROTOCOL_FEATURE,
303
303
  AGENT_TOKEN_GENERATION_PROTOCOL_FEATURE,
304
304
  isEngineAccountScope,
305
+ isTaskMachineRetireAckFrame,
305
306
  HOST_LOGS_PROTOCOL_FEATURE,
306
307
  HOST_TASK_INVENTORY_PROTOCOL_FEATURE,
307
308
  MAX_ENGINE_ACCOUNT_LABEL_CHARS,
@@ -413,11 +414,29 @@ import {
413
414
  import { taskContainerBackend, taskContainerCli } from "../lib/task-container-cli";
414
415
  import { CloudCredentialRuntime } from "../lib/cloud-credential-runtime";
415
416
  import { createTaskFilesV1Consumer } from "../lib/credential-consumers/files";
416
- import { taskFilesV1Readiness } from "../lib/task-files-readiness";
417
417
  import {
418
+ TASK_FILES_SESSION_ROOT,
419
+ taskFilesV1Readiness,
420
+ } from "../lib/task-files-readiness";
421
+ import {
422
+ latchDamagedTaskFilesMachineRetirement,
418
423
  requestTaskFilesMachineRetirement,
424
+ setTaskFilesMachineRetirementHandler,
419
425
  taskFilesMachineRetirementReady,
420
426
  } from "../lib/task-files-machine-retirement";
427
+ import {
428
+ kernelBootId,
429
+ TaskFilesMachineRetirementOutbox,
430
+ TASK_FILES_MACHINE_RETIREMENT_RECORD,
431
+ taskMachinePeerProcessIds,
432
+ } from "../lib/task-files-machine-retirement-outbox";
433
+ import {
434
+ planFreshTaskMachineCredentialsActivation,
435
+ taskMachineCredentialsV1Required,
436
+ verifyFreshTaskMachineCredentialsActivation,
437
+ type TaskMachineCredentialsActivationDeps,
438
+ type TaskMachineCredentialsActivationPlan,
439
+ } from "../lib/task-machine-credentials-activation";
421
440
  import { ProjectEnvTaskReplicaManager } from "../lib/project-env-task-replica";
422
441
  import {
423
442
  purgeDefaultEngineSlots,
@@ -669,27 +688,82 @@ try {
669
688
  );
670
689
  }
671
690
  migrateHostDb();
691
+ const taskMachineCredentialsRequired =
692
+ sidecarMode() && taskMachineCredentialsV1Required();
693
+ const taskFilesReadiness = taskFilesV1Readiness(sidecarMode());
694
+ const taskFilesMachineRetirementOutbox = sidecarMode()
695
+ ? new TaskFilesMachineRetirementOutbox({
696
+ // Root-only tmpfs survives runner restarts but not a machine reboot,
697
+ // exactly matching the retirement proof boundary. It also keeps data
698
+ // disk exhaustion from blocking the first durable handoff.
699
+ recordPath: join(
700
+ TASK_FILES_SESSION_ROOT,
701
+ TASK_FILES_MACHINE_RETIREMENT_RECORD,
702
+ ),
703
+ send: sendCredentialFrame,
704
+ log: (line) => console.warn(line),
705
+ })
706
+ : null;
707
+ if (taskFilesMachineRetirementOutbox !== null) {
708
+ setTaskFilesMachineRetirementHandler((request) => {
709
+ try {
710
+ return taskFilesMachineRetirementOutbox.handler(request);
711
+ } finally {
712
+ // The terminal latch closes before the handler runs. Withdraw Files-v1
713
+ // even if persisting the outbox record fails and the same exact handoff
714
+ // must be retried.
715
+ sendCapabilities();
716
+ }
717
+ });
718
+ if (taskFilesMachineRetirementOutbox.startup.kind === "pending") {
719
+ requestTaskFilesMachineRetirement(
720
+ taskFilesMachineRetirementOutbox.startup.request,
721
+ );
722
+ } else if (taskFilesMachineRetirementOutbox.startup.kind === "damaged") {
723
+ latchDamagedTaskFilesMachineRetirement(
724
+ taskFilesMachineRetirementOutbox.startup.error,
725
+ );
726
+ taskFilesMachineRetirementOutbox.quarantineDamagedStartup();
727
+ }
728
+ }
672
729
  // ADR-154 §13: the activation authority, before anything reads the mode
673
730
  // (the refreshers, the v0 lease, the runtime's recovery, the first
674
- // capability frame). It moves only owned runners in this release: a task
675
- // machine needs its run's Files session on v1 first, and the sign-in box
676
- // runs no task.
677
- const credentialsActivation = new CredentialsActivationAuthority({
731
+ // capability frame). A cloud-marked task machine starts pending here—before
732
+ // a v0 lease can be requested—and activates only after its local Files and
733
+ // exact-machine retirement boundaries are installed.
734
+ type CredentialsRetirementPlan =
735
+ | { kind: "owned-runner"; plan: RetirementPlan }
736
+ | TaskMachineCredentialsActivationPlan;
737
+ const credentialsActivation = new CredentialsActivationAuthority<CredentialsRetirementPlan>({
678
738
  recordPath: join(env.dataDir, CREDENTIALS_ACTIVATION_RECORD),
679
739
  floorClaimed: () => credentialsCapabilityFloorExists(),
680
- eligible: () => !sidecarMode() && !signInBoxMode(),
740
+ eligible: () =>
741
+ taskMachineCredentialsRequired
742
+ ? sidecarTaskId() !== null
743
+ : !sidecarMode() && !signInBoxMode(),
744
+ initiallyPending: () => taskMachineCredentialsRequired,
681
745
  ask: askCredentialsActivation,
682
746
  pauseLegacyStarts,
683
- planRetirement: () => planLegacyTaskRuntimeRetirement(retirementPlanDeps()),
747
+ planRetirement: planCredentialsRetirement,
684
748
  commitLocally: (record) => commitCredentialsSwitchLocally(record),
685
- retire: retireTaskRuntimesForCloudCredentials,
749
+ retire: retireCredentialsPlan,
686
750
  reportStopped: reportRetiredTasksStopped,
687
751
  claimFloor: claimCredentialsCapabilityFloor,
688
752
  leaveLegacy,
689
753
  purge: purgeCredentialStores,
690
754
  swept: cloudCredentialsSwept,
691
755
  sweep: recoverCloudCredentials,
692
- onActive: cloudCredentialsActive,
756
+ onActive: (how) => {
757
+ if (
758
+ sidecarMode() &&
759
+ (!taskFilesReadiness.ready || !taskFilesMachineRetirementReady())
760
+ ) {
761
+ throw new Error(
762
+ "the task machine cannot activate cloud credentials without its Files and retirement boundaries",
763
+ );
764
+ }
765
+ cloudCredentialsActive(how);
766
+ },
693
767
  // Pending again: the next capability frame advertises no v1.
694
768
  onActiveWithdrawn: () => sendCapabilities(),
695
769
  restart: (why) => {
@@ -699,9 +773,13 @@ const credentialsActivation = new CredentialsActivationAuthority({
699
773
  log: (line) => console.warn(line),
700
774
  });
701
775
  setCredentialsModeSource(() => credentialsActivation.mode());
702
- /** ADR-154 §13: how often a runner not yet on cloud credentials asks again,
703
- * so the operator turning them on reaches it within minutes. */
776
+ /** ADR-154 §13: the steady/operator cadence. Active runners report their
777
+ * purge again here too, so keep it deliberately quiet. */
704
778
  const CREDENTIALS_ACTIVATION_RETRY_MS = 5 * 60_000;
779
+ /** A newly assigned marked machine is already behind its floor. Retry a
780
+ * transient freshness/Files proof inside Cloud's boot budget, without making
781
+ * its eventual active purge report a five-second heartbeat. */
782
+ const TASK_MACHINE_CREDENTIALS_ACTIVATION_RETRY_MS = 5_000;
705
783
  const CREDENTIALS_ACTIVATION_ANSWER_MS = 60_000;
706
784
  // Started here, first fired long after the bridge's state exists.
707
785
  const credentialsActivationTimer = setInterval(() => {
@@ -714,6 +792,18 @@ const credentialsActivationTimer = setInterval(() => {
714
792
  void credentialsActivation.advance();
715
793
  }, CREDENTIALS_ACTIVATION_RETRY_MS);
716
794
  credentialsActivationTimer.unref?.();
795
+ const taskMachineCredentialsActivationTimer =
796
+ taskMachineCredentialsRequired && sidecarTaskId() !== null
797
+ ? setInterval(() => {
798
+ if (
799
+ stopping ||
800
+ !credentialFramesReady ||
801
+ credentialsMode() !== "pending"
802
+ ) return;
803
+ void credentialsActivation.advance();
804
+ }, TASK_MACHINE_CREDENTIALS_ACTIVATION_RETRY_MS)
805
+ : null;
806
+ taskMachineCredentialsActivationTimer?.unref?.();
717
807
 
718
808
  const credentialsActivationAsks = new Map<
719
809
  string,
@@ -914,7 +1004,6 @@ if (signInBoxMode()) {
914
1004
  // ADR-154: the composer for tasks on cloud credentials. Nothing runs on it
915
1005
  // until the runner's activation authority makes cloud credentials its
916
1006
  // tasks' only ones (lib/cloud-credential-host.ts).
917
- const taskFilesReadiness = taskFilesV1Readiness(sidecarMode());
918
1007
  const taskFiles = sidecarMode()
919
1008
  ? createTaskFilesV1Consumer({
920
1009
  recoveredBinding: (assignment) => {
@@ -979,8 +1068,12 @@ if (sidecarMode()) {
979
1068
  }
980
1069
  setCloudCredentialRuntime(cloudCredentials);
981
1070
  // A runner that was already on cloud credentials follows them from here:
982
- // once its boot recovery sweep (the container runtime's own pass) is done.
983
- credentialsActivation.booted();
1071
+ // once its boot recovery sweep is done. Owned runners get that sweep from
1072
+ // container-runtime activation; a task machine has no container runtime, so
1073
+ // its authority must run the composer sweep itself before it can reattach a
1074
+ // surviving agent or ask for a new grant.
1075
+ if (sidecarMode()) credentialsActivation.sweepItself();
1076
+ else credentialsActivation.booted();
984
1077
  void cloudCredentialsRecovered().then(() => {
985
1078
  if (!stopping) void credentialsActivation.advance();
986
1079
  });
@@ -1641,6 +1734,7 @@ function sendCredentialFrame(frame: Parameters<typeof send>[1]): boolean {
1641
1734
  */
1642
1735
  function bridgeResumed(): void {
1643
1736
  credentialFramesReady = true;
1737
+ taskFilesMachineRetirementOutbox?.bridgeAuthenticated();
1644
1738
  const mode = credentialsMode();
1645
1739
  if (mode === "legacy") leaseManager?.reconnected();
1646
1740
  else if (mode === "active") {
@@ -1688,6 +1782,47 @@ function reportCredentialsPurged(): void {
1688
1782
  /** ADR-154 §13: what the retirement reads (lib/legacy-runtime-retirement.ts):
1689
1783
  * the task rows once, the identity fence over those same rows, the pinned
1690
1784
  * engine. */
1785
+ async function planCredentialsRetirement(): Promise<CredentialsRetirementPlan> {
1786
+ if (sidecarMode()) {
1787
+ return planFreshTaskMachineCredentialsActivation(
1788
+ taskMachineCredentialsActivationDeps(),
1789
+ );
1790
+ }
1791
+ return {
1792
+ kind: "owned-runner",
1793
+ plan: await planLegacyTaskRuntimeRetirement(retirementPlanDeps()),
1794
+ };
1795
+ }
1796
+
1797
+ async function retireCredentialsPlan(
1798
+ plan: CredentialsRetirementPlan,
1799
+ ): Promise<{ stopped: StoppedReport[]; problem: string | null }> {
1800
+ if (plan.kind === "owned-runner") {
1801
+ return retireTaskRuntimesForCloudCredentials(plan.plan);
1802
+ }
1803
+ verifyFreshTaskMachineCredentialsActivation(
1804
+ plan,
1805
+ taskMachineCredentialsActivationDeps(),
1806
+ );
1807
+ return { stopped: [], problem: null };
1808
+ }
1809
+
1810
+ function taskMachineCredentialsActivationDeps(): TaskMachineCredentialsActivationDeps {
1811
+ return {
1812
+ required: () => taskMachineCredentialsRequired,
1813
+ taskId: sidecarTaskId,
1814
+ machineBootId: kernelBootId,
1815
+ filesReady: () => taskFilesReadiness.ready,
1816
+ retirementReady: taskFilesMachineRetirementReady,
1817
+ taskRows: () =>
1818
+ getDb()
1819
+ .select({ taskId: schema.hostTasks.taskId })
1820
+ .from(schema.hostTasks)
1821
+ .all(),
1822
+ peerProcessIds: taskMachinePeerProcessIds,
1823
+ };
1824
+ }
1825
+
1691
1826
  function retirementPlanDeps(): RetirementPlanDeps {
1692
1827
  return {
1693
1828
  snapshot: () => {
@@ -2890,6 +3025,9 @@ async function connect(): Promise<void> {
2890
3025
  eventOutbox.ack(frame.seq);
2891
3026
  flushEvents();
2892
3027
  break;
3028
+ case "task.machine.retire.ack":
3029
+ taskFilesMachineRetirementOutbox?.acknowledge(frame);
3030
+ break;
2893
3031
  case "command":
2894
3032
  void handleCommand(socket, frame);
2895
3033
  break;
@@ -4632,6 +4770,7 @@ function parseCloudFrame(data: RawData): CloudToHost | null {
4632
4770
  if (frame.kind === "event.ack" && typeof frame.seq === "number") {
4633
4771
  return { kind: "event.ack", seq: frame.seq };
4634
4772
  }
4773
+ if (isTaskMachineRetireAckFrame(frame)) return frame;
4635
4774
  if (
4636
4775
  frame.kind === "host.inventory.request" &&
4637
4776
  isSafeInventoryScanId(frame.scanId) &&
@@ -5203,6 +5342,7 @@ function rejectFrameDuringShutdown(
5203
5342
  case "credential.changed":
5204
5343
  case "credential.refresh.result":
5205
5344
  case "credentials.activate.result":
5345
+ case "task.machine.retire.ack":
5206
5346
  return false;
5207
5347
  }
5208
5348
  }
@@ -5802,6 +5942,7 @@ function requestShutdown(signal: NodeJS.Signals): void {
5802
5942
  void stopRemoteOperationManagers();
5803
5943
  stopMcpGatewaySubscription();
5804
5944
  stopAgentClisReadySubscription();
5945
+ taskFilesMachineRetirementOutbox?.stop();
5805
5946
  // ADR-154: no credential is served or taken after this; what was accepted
5806
5947
  // drains, bounded, and no cleanup starts (the next process's recovery runs
5807
5948
  // it). Exit waits for it.
package/src/protocol.ts CHANGED
@@ -2961,6 +2961,26 @@ export const MAX_REMOTE_HOST_MESSAGE_BYTES = 512;
2961
2961
  export const MAX_HOST_TASK_INVENTORY_PAGE_SIZE = 256;
2962
2962
  export const MAX_HOST_TASK_INVENTORY_SCAN_ID_CHARS = 128;
2963
2963
  export const MAX_HOST_TASK_INVENTORY_TASK_ID_CHARS = 128;
2964
+ /** A refusal/staleness explanation is diagnostic only and must stay bounded
2965
+ * on the authenticated control socket. */
2966
+ export const MAX_TASK_MACHINE_RETIRE_ERROR_BYTES = 512;
2967
+ /** Files-v1 terminal failures are the only reasons a task sidecar may ask the
2968
+ * cloud to cold-stop its exact machine incarnation. Keep this vocabulary
2969
+ * closed: the cloud persists it as audit provenance. */
2970
+ export const TASK_MACHINE_RETIRE_REASONS = [
2971
+ "setup-unproven",
2972
+ "visibility-unproven",
2973
+ "mount-retirement-unproven",
2974
+ ] as const;
2975
+ export type TaskMachineRetireReason =
2976
+ (typeof TASK_MACHINE_RETIRE_REASONS)[number];
2977
+ export const TASK_MACHINE_RETIRE_OUTCOMES = [
2978
+ "accepted",
2979
+ "stale",
2980
+ "refused",
2981
+ ] as const;
2982
+ export type TaskMachineRetireOutcome =
2983
+ (typeof TASK_MACHINE_RETIRE_OUTCOMES)[number];
2964
2984
  /**
2965
2985
  * A redeem response carrying this exact header proves the cloud checks a spent
2966
2986
  * token before expiry. That lets a new CLI distinguish a definitive 404/410
@@ -3561,6 +3581,65 @@ export function isHostOpId(value: unknown): value is HostOpId {
3561
3581
  );
3562
3582
  }
3563
3583
 
3584
+ function isTaskMachineRetireTaskId(value: unknown): value is string {
3585
+ return (
3586
+ typeof value === "string" &&
3587
+ value.length > 0 &&
3588
+ value.length <= MAX_HOST_TASK_INVENTORY_TASK_ID_CHARS &&
3589
+ /^[a-z0-9][a-z0-9_-]*$/.test(value)
3590
+ );
3591
+ }
3592
+
3593
+ function isTaskMachineRetireRunEpoch(value: unknown): value is number {
3594
+ return Number.isSafeInteger(value) && (value as number) > 0;
3595
+ }
3596
+
3597
+ export function isTaskMachineRetireFrame(
3598
+ value: unknown,
3599
+ ): value is TaskMachineRetireFrame {
3600
+ const frame = exactWireObject(value, [
3601
+ "kind",
3602
+ "opId",
3603
+ "taskId",
3604
+ "runEpoch",
3605
+ "reason",
3606
+ ]);
3607
+ return Boolean(
3608
+ frame &&
3609
+ frame.kind === "task.machine.retire" &&
3610
+ isHostOpId(frame.opId) &&
3611
+ isTaskMachineRetireTaskId(frame.taskId) &&
3612
+ isTaskMachineRetireRunEpoch(frame.runEpoch) &&
3613
+ (TASK_MACHINE_RETIRE_REASONS as readonly unknown[]).includes(
3614
+ frame.reason,
3615
+ ),
3616
+ );
3617
+ }
3618
+
3619
+ export function isTaskMachineRetireAckFrame(
3620
+ value: unknown,
3621
+ ): value is TaskMachineRetireAckFrame {
3622
+ const frame = exactWireObject(
3623
+ value,
3624
+ ["kind", "opId", "taskId", "runEpoch", "outcome"],
3625
+ ["error"],
3626
+ );
3627
+ return Boolean(
3628
+ frame &&
3629
+ frame.kind === "task.machine.retire.ack" &&
3630
+ isHostOpId(frame.opId) &&
3631
+ isTaskMachineRetireTaskId(frame.taskId) &&
3632
+ isTaskMachineRetireRunEpoch(frame.runEpoch) &&
3633
+ (TASK_MACHINE_RETIRE_OUTCOMES as readonly unknown[]).includes(
3634
+ frame.outcome,
3635
+ ) &&
3636
+ (frame.error === undefined ||
3637
+ (typeof frame.error === "string" &&
3638
+ utf8ByteLength(frame.error) <=
3639
+ MAX_TASK_MACHINE_RETIRE_ERROR_BYTES)),
3640
+ );
3641
+ }
3642
+
3564
3643
  export function isHostLogsStartFrame(
3565
3644
  value: unknown,
3566
3645
  ): value is HostLogsStartFrame {
@@ -4470,6 +4549,31 @@ export type GitHubRepositoryAccessErrorCode =
4470
4549
  | "github_unavailable"
4471
4550
  | "invalid_response";
4472
4551
 
4552
+ /**
4553
+ * A one-task sidecar cannot prove a Files-v1 boundary closed in this boot, so
4554
+ * it asks the cloud to stop the exact EC2 incarnation. `runEpoch` fences a
4555
+ * delayed request from a successor assignment; the authenticated connection
4556
+ * supplies the host identity and is deliberately not sender-controlled here.
4557
+ */
4558
+ export interface TaskMachineRetireFrame {
4559
+ kind: "task.machine.retire";
4560
+ opId: string;
4561
+ taskId: string;
4562
+ runEpoch: number;
4563
+ reason: TaskMachineRetireReason;
4564
+ }
4565
+
4566
+ /** Durable acceptance, not completion: after `accepted` the cloud owns
4567
+ * retrying the persisted exact-instance stop even if this socket disappears. */
4568
+ export interface TaskMachineRetireAckFrame {
4569
+ kind: "task.machine.retire.ack";
4570
+ opId: string;
4571
+ taskId: string;
4572
+ runEpoch: number;
4573
+ outcome: TaskMachineRetireOutcome;
4574
+ error?: string;
4575
+ }
4576
+
4473
4577
  export type CloudToHost =
4474
4578
  | {
4475
4579
  kind: "command";
@@ -4633,6 +4737,7 @@ export type CloudToHost =
4633
4737
  | CredentialChangedFrame
4634
4738
  | CredentialRefreshResultFrame
4635
4739
  | CredentialsActivateResultFrame
4740
+ | TaskMachineRetireAckFrame
4636
4741
  | McpCaptureProbeFrame
4637
4742
  | McpCaptureCompleteFrame
4638
4743
  | McpCaptureCancelFrame
@@ -4751,6 +4856,7 @@ export type HostToCloud =
4751
4856
  | CredentialRefreshFrame
4752
4857
  | CredentialsActivateFrame
4753
4858
  | CredentialsActivatedFrame
4859
+ | TaskMachineRetireFrame
4754
4860
  | McpCaptureAckFrame
4755
4861
  | PersonCaptureAckFrame
4756
4862
  | GithubRevokeAckFrame