ever-works-node 0.2.4 → 0.2.6

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 (3) hide show
  1. package/README.md +8 -0
  2. package/cli.js +3572 -105
  3. package/package.json +1 -1
package/cli.js CHANGED
@@ -3509,10 +3509,10 @@ var {
3509
3509
  // src/cli/program.ts
3510
3510
  var import_node_path7 = require("node:path");
3511
3511
 
3512
- // ../../node_modules/.pnpm/@ever-co+connect-contracts@1.0.0-rc.2/node_modules/@ever-co/connect-contracts/dist/index.js
3512
+ // ../../node_modules/.pnpm/@ever-co+connect-contracts@1.0.0-rc.7/node_modules/@ever-co/connect-contracts/dist/index.js
3513
3513
  var CONSTANTS = {
3514
3514
  "$comment": "Wire constants of the Ever Platform modules. tools/split-integrations.mjs refreshes the fields read from the contract (contracts_version, code patterns, install_sources, products, stats_headers, feed_event_types, integration_keys, root_keys); every other value is a default a product may override by its own configuration.",
3515
- "contracts_version": "1.0.0-rc.2",
3515
+ "contracts_version": "1.0.0-rc.7",
3516
3516
  "connect_code_pattern": "^EVC(-[0-9A-HJKMNP-TV-Za-hj-kmnp-tv-z]{4}){3}$",
3517
3517
  "link_code_pattern": "^EVL(-[0-9A-HJKMNP-TV-Za-hj-kmnp-tv-z]{4}){3}$",
3518
3518
  "install_sources": "^(cloud|self-hosted|ever\\.sh|works_app|desktop|partner:[a-z0-9-]{2,32})$",
@@ -3660,6 +3660,15 @@ var CONSTANTS = {
3660
3660
  "x": "Ync-MW21JL72GOiS-RVrlvS3gyFH5Krh5f6I66jw1Qc",
3661
3661
  "use": "sig",
3662
3662
  "alg": "EdDSA"
3663
+ },
3664
+ {
3665
+ "kid": "ever-202610-025b",
3666
+ "iss": "https://api.ever.co",
3667
+ "kty": "OKP",
3668
+ "crv": "Ed25519",
3669
+ "x": "AwXJEZdyiK3QEhUnNB-0fIsN9PPLgbF6O3tyZT3moDc",
3670
+ "use": "sig",
3671
+ "alg": "EdDSA"
3663
3672
  }
3664
3673
  ],
3665
3674
  "root_keys_file_env": "EVER_PLATFORM_ROOT_KEYS_FILE",
@@ -3727,6 +3736,7 @@ var PROBLEM_CODES = [
3727
3736
  "already_connected",
3728
3737
  "already_linked",
3729
3738
  "already_claimed",
3739
+ "claim_locked",
3730
3740
  "invalid_client",
3731
3741
  "public_jwk_invalid",
3732
3742
  "identifier_claimed",
@@ -3752,6 +3762,11 @@ var PROBLEM_CODES = [
3752
3762
  "database_absent",
3753
3763
  "database_unreachable",
3754
3764
  "topic_private",
3765
+ "realtime_disabled",
3766
+ "topic_forbidden",
3767
+ "topic_invalid",
3768
+ "realtime_busy",
3769
+ "too_many_streams",
3755
3770
  "keys_unavailable",
3756
3771
  "internal_error"
3757
3772
  ];
@@ -4967,6 +4982,36 @@ var SKILL_READINESS_STATES = [
4967
4982
  ];
4968
4983
  var SKILL_CARD_STATES = [...SKILL_READINESS_STATES, "disabled", "needs_review"];
4969
4984
  var SKILL_READINESS_FILTERS = [...SKILL_CARD_STATES, "attention"];
4985
+ var FLEET_CONTROL_POLICY_DEFAULTS = Object.freeze({
4986
+ view: "org-members",
4987
+ sessions: "owner",
4988
+ desktop: "owner",
4989
+ logs: "owner",
4990
+ services: "owner",
4991
+ docker: "owner",
4992
+ processes: "owner",
4993
+ ports: "owner",
4994
+ files: "owner",
4995
+ commands: "owner",
4996
+ schedules: "owner",
4997
+ checkouts: "owner",
4998
+ machines: "owner"
4999
+ });
5000
+ var FLEET_ACTION_FAMILY_HINT_LABELS = Object.freeze({
5001
+ view: "view",
5002
+ sessions: "sessions",
5003
+ desktop: "desktop",
5004
+ logs: "logs",
5005
+ services: "services",
5006
+ docker: "Docker",
5007
+ processes: "processes",
5008
+ ports: "ports",
5009
+ files: "files",
5010
+ commands: "commands",
5011
+ schedules: "schedules",
5012
+ checkouts: "checkouts",
5013
+ machines: "lifecycle"
5014
+ });
4970
5015
  var FLEET_AGENT_CREDENTIAL_FAMILIES = [
4971
5016
  {
4972
5017
  cli: "claude-code",
@@ -5006,6 +5051,41 @@ function resolveExclusiveAgentCredentials(grantedNames, env) {
5006
5051
  return { names: grantedNames.filter((name) => !dropped.has(name)), notes };
5007
5052
  }
5008
5053
  __name(resolveExclusiveAgentCredentials, "resolveExclusiveAgentCredentials");
5054
+ var CONVENTIONAL_SUBJECT = /^([a-z]+)(\([^)\n]{1,40}\))?(!)?:\s+(\S.*)$/;
5055
+ function classifyFleetChangedPaths(paths) {
5056
+ const list = (paths ?? []).map((path2) => path2.replace(/\\/g, "/").trim()).filter(Boolean);
5057
+ if (list.length === 0) return "none";
5058
+ if (list.every(isDocsPath)) return "docs";
5059
+ if (list.every(isTestPath)) return "test";
5060
+ return "mixed";
5061
+ }
5062
+ __name(classifyFleetChangedPaths, "classifyFleetChangedPaths");
5063
+ function retypeFleetCommitMessage(message, changedPaths) {
5064
+ if (typeof message !== "string" || !message) return message;
5065
+ const newline = message.indexOf("\n");
5066
+ const firstLine = newline === -1 ? message : message.slice(0, newline);
5067
+ const rest = newline === -1 ? "" : message.slice(newline);
5068
+ const parsed = CONVENTIONAL_SUBJECT.exec(firstLine);
5069
+ if (!parsed) return message;
5070
+ const change = classifyFleetChangedPaths(changedPaths);
5071
+ const type = change === "docs" ? "docs" : change === "test" ? "test" : null;
5072
+ if (!type || parsed[1] === type) return message;
5073
+ return `${type}${firstLine.slice(parsed[1].length)}${rest}`;
5074
+ }
5075
+ __name(retypeFleetCommitMessage, "retypeFleetCommitMessage");
5076
+ var DOC_EXTENSIONS = /\.(?:md|mdx|markdown|rst|adoc|txt)$/i;
5077
+ var DOC_DIRECTORY = /(?:^|\/)(?:docs?|documentation)\//i;
5078
+ var DOC_FILENAMES = /(?:^|\/)(?:readme|changelog|contributing|license|licence|notice|authors|code_of_conduct)(?:\.[a-z]+)?$/i;
5079
+ function isDocsPath(path2) {
5080
+ return DOC_EXTENSIONS.test(path2) || DOC_DIRECTORY.test(path2) || DOC_FILENAMES.test(path2);
5081
+ }
5082
+ __name(isDocsPath, "isDocsPath");
5083
+ var TEST_FILE = /(?:^|[./_-])(?:spec|test|tests|e2e)\.[cm]?[jt]sx?$|_test\.(?:go|py|rb)$|(?:^|\/)test_[^/]+\.py$/i;
5084
+ var TEST_DIRECTORY = /(?:^|\/)(?:__tests__|__snapshots__|__mocks__|__fixtures__|tests?|e2e|spec|fixtures)\//i;
5085
+ function isTestPath(path2) {
5086
+ return TEST_FILE.test(path2) || TEST_DIRECTORY.test(path2) || /\.snap$/i.test(path2);
5087
+ }
5088
+ __name(isTestPath, "isTestPath");
5009
5089
  var INBOX_MAX_TITLE_CHARS = 300;
5010
5090
  var FLEET_TASK_WORKSPACE_MAX_MOUNTS = 8;
5011
5091
  var FLEET_TASK_WORKSPACE_MOUNT_DIR_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,62}[A-Za-z0-9_-])?$/;
@@ -5519,6 +5599,850 @@ function truncateToUtf8Bytes(value, maxBytes) {
5519
5599
  return { value: decoded.replace(/\uFFFD+$/, "").trimEnd(), truncated: true };
5520
5600
  }
5521
5601
  __name(truncateToUtf8Bytes, "truncateToUtf8Bytes");
5602
+ var FLEET_LINK_VERSION = 1;
5603
+ var FLEET_LINK_WS_PATH = "/ws/fleet/link";
5604
+ var FLEET_LINK_MAX_FRAME_BYTES = 16 * 1024 * 1024;
5605
+ var FLEET_LINK_FRAME_HEADER_BYTES = 9;
5606
+ var FLEET_LINK_MAX_PAYLOAD_BYTES = FLEET_LINK_MAX_FRAME_BYTES - FLEET_LINK_FRAME_HEADER_BYTES;
5607
+ var FLEET_LINK_MAX_JSON_BYTES = 1024 * 1024;
5608
+ var FLEET_LINK_MAX_CHANNEL_NUMBER = 4294967295;
5609
+ var FLEET_LINK_MAX_CHANNELS = 64;
5610
+ var FLEET_LINK_PING_INTERVAL_MS = 15e3;
5611
+ var FLEET_LINK_SILENCE_MS = 45e3;
5612
+ var FLEET_LINK_BACKOFF_BASE_MS = 1e3;
5613
+ var FLEET_LINK_BACKOFF_MAX_MS = 3e4;
5614
+ var FLEET_LINK_REFUSED_RETRY_MS = 5 * 6e4;
5615
+ var FLEET_LINK_HELLO_BLOCK_MS = 6e4;
5616
+ var FLEET_LINK_AUTH_TIMEOUT_MS = 5e3;
5617
+ var FLEET_LINK_INLINE_REPLY_MS = 2e3;
5618
+ var FLEET_LINK_RESUME_WINDOW_MS = 12e4;
5619
+ var FLEET_OPERATION_RETENTION_MS = 60 * 6e4;
5620
+ var FLEET_LINK_DEFAULT_LIMITS = {
5621
+ maxFrameBytes: FLEET_LINK_MAX_FRAME_BYTES,
5622
+ maxChannels: FLEET_LINK_MAX_CHANNELS,
5623
+ pingIntervalMs: FLEET_LINK_PING_INTERVAL_MS,
5624
+ silenceMs: FLEET_LINK_SILENCE_MS,
5625
+ inlineReplyMs: FLEET_LINK_INLINE_REPLY_MS
5626
+ };
5627
+ var FLEET_LINK_CLOSE_CODES = {
5628
+ /** Normal close (a node shutting down, a channel-less link released). */
5629
+ normal: 1e3,
5630
+ /** The API process is shutting down. */
5631
+ shutdown: 1001,
5632
+ /** Undifferentiated credential refusal. */
5633
+ auth: 4001,
5634
+ /** 45 s without a frame. */
5635
+ silence: 4002,
5636
+ /** This deployment does not accept node links (`FLEET_NODE_LINK_ENABLED` off). */
5637
+ disabled: 4003,
5638
+ /** Too many hellos (CONTRACTS U9). */
5639
+ rateLimited: 4008,
5640
+ /** The other side broke the protocol (bad first frame, a node-opened channel, …). */
5641
+ protocol: 4009,
5642
+ /** A newer link from the same node replaced this one. */
5643
+ replaced: 4010,
5644
+ /** The node presented no consent that allows a link (work, attend, manage all off). */
5645
+ noConsent: 4012
5646
+ };
5647
+ var FLEET_LINK_FRAME_KINDS = {
5648
+ /** Raw bytes: terminal output/input, log lines, file chunks. */
5649
+ DATA: 1,
5650
+ /** `[cols u16][rows u16]`. */
5651
+ RESIZE: 2,
5652
+ /** `[code i32]`. */
5653
+ EXIT: 3,
5654
+ /** UTF-8 JSON control message. */
5655
+ JSON: 10,
5656
+ /** UTF-8 reason code. */
5657
+ CLOSE: 11,
5658
+ /** A full picture (the existing computer-frame encoding). */
5659
+ FRAME: 20,
5660
+ /** A pointer / key / text event (the existing computer input encoding). */
5661
+ INPUT: 21,
5662
+ /** Changed tiles `[w][h][count]{x,y,w,h,len,jpeg}`. */
5663
+ TILES: 23,
5664
+ /** `[bytes u32]` credit. */
5665
+ ACK: 30,
5666
+ /** End of a stream. */
5667
+ END: 31,
5668
+ /** UTF-8 text ≤ 256 KiB, pasted into a desktop. */
5669
+ CLIP: 40
5670
+ };
5671
+ var FRAME_KIND_VALUES = new Set(Object.values(FLEET_LINK_FRAME_KINDS));
5672
+ function isFleetLinkFrameKind(value) {
5673
+ return typeof value === "number" && FRAME_KIND_VALUES.has(value);
5674
+ }
5675
+ __name(isFleetLinkFrameKind, "isFleetLinkFrameKind");
5676
+ var FLEET_LINK_MAX_CLIP_BYTES = 256 * 1024;
5677
+ var FLEET_LINK_CREDIT_WINDOW_BYTES = 8 * 1024 * 1024;
5678
+ var FLEET_LINK_CREDIT_FAMILIES = ["files", "logs"];
5679
+ var FLEET_CONSENT_FLAGS = {
5680
+ work: "--work",
5681
+ attend: "--attend",
5682
+ manage: "--manage",
5683
+ usageScan: "--usage-scan"
5684
+ };
5685
+ var FLEET_NO_CONSENTS = Object.freeze({
5686
+ work: false,
5687
+ attend: false,
5688
+ manage: false,
5689
+ usageScan: false
5690
+ });
5691
+ function normalizeFleetConsents(value) {
5692
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
5693
+ const record = value;
5694
+ return {
5695
+ work: record.work === true,
5696
+ attend: record.attend === true,
5697
+ manage: record.manage === true,
5698
+ usageScan: record.usageScan === true
5699
+ };
5700
+ }
5701
+ __name(normalizeFleetConsents, "normalizeFleetConsents");
5702
+ function isFleetLinkEligible(consents) {
5703
+ return Boolean(consents && (consents.work || consents.attend || consents.manage));
5704
+ }
5705
+ __name(isFleetLinkEligible, "isFleetLinkEligible");
5706
+ var FLEET_LINK_FAMILIES = [
5707
+ "session",
5708
+ "desktop",
5709
+ "logs",
5710
+ "machine",
5711
+ "files",
5712
+ "command",
5713
+ "schedule",
5714
+ "tunnel",
5715
+ "clip"
5716
+ ];
5717
+ function isFleetLinkFamily(value) {
5718
+ return typeof value === "string" && FLEET_LINK_FAMILIES.includes(value);
5719
+ }
5720
+ __name(isFleetLinkFamily, "isFleetLinkFamily");
5721
+ var FLEET_LINK_FAMILY_CONSENT = {
5722
+ session: "work-or-manage",
5723
+ desktop: "attend",
5724
+ logs: "manage",
5725
+ machine: "manage",
5726
+ files: "manage",
5727
+ command: "manage",
5728
+ schedule: "manage",
5729
+ tunnel: "manage",
5730
+ clip: "attend"
5731
+ };
5732
+ var FLEET_LINK_VERB_CONSENT_OVERRIDES = {
5733
+ /** A read allowed on any open link. */
5734
+ "health.now": "link"
5735
+ };
5736
+ function fleetLinkVerbConsent(verb) {
5737
+ return Object.prototype.hasOwnProperty.call(FLEET_LINK_VERB_CONSENT_OVERRIDES, verb) ? FLEET_LINK_VERB_CONSENT_OVERRIDES[verb] : FLEET_LINK_FAMILY_CONSENT.machine;
5738
+ }
5739
+ __name(fleetLinkVerbConsent, "fleetLinkVerbConsent");
5740
+ function fleetLinkChannelOpenConsent(family) {
5741
+ if (family === "machine" && Object.keys(FLEET_LINK_VERB_CONSENT_OVERRIDES).length > 0) return "link";
5742
+ return FLEET_LINK_FAMILY_CONSENT[family];
5743
+ }
5744
+ __name(fleetLinkChannelOpenConsent, "fleetLinkChannelOpenConsent");
5745
+ function fleetConsentSatisfies(consents, requirement) {
5746
+ if (!consents) return false;
5747
+ switch (requirement) {
5748
+ case "link":
5749
+ return isFleetLinkEligible(consents);
5750
+ case "work":
5751
+ return consents.work;
5752
+ case "attend":
5753
+ return consents.attend;
5754
+ case "manage":
5755
+ return consents.manage;
5756
+ case "work-or-manage":
5757
+ return consents.work || consents.manage;
5758
+ default:
5759
+ return false;
5760
+ }
5761
+ }
5762
+ __name(fleetConsentSatisfies, "fleetConsentSatisfies");
5763
+ function fleetConsentFlagsFor(requirement) {
5764
+ switch (requirement) {
5765
+ case "work":
5766
+ return [FLEET_CONSENT_FLAGS.work];
5767
+ case "attend":
5768
+ return [FLEET_CONSENT_FLAGS.attend];
5769
+ case "manage":
5770
+ return [FLEET_CONSENT_FLAGS.manage];
5771
+ case "work-or-manage":
5772
+ return [FLEET_CONSENT_FLAGS.work, FLEET_CONSENT_FLAGS.manage];
5773
+ case "link":
5774
+ default:
5775
+ return [FLEET_CONSENT_FLAGS.work, FLEET_CONSENT_FLAGS.attend, FLEET_CONSENT_FLAGS.manage];
5776
+ }
5777
+ }
5778
+ __name(fleetConsentFlagsFor, "fleetConsentFlagsFor");
5779
+ function fleetConsentMissingHint(requirement, nodeName) {
5780
+ const flags = fleetConsentFlagsFor(requirement);
5781
+ const joined = flags.length === 1 ? flags[0] : `${flags.slice(0, -1).join(", ")} or ${flags[flags.length - 1]}`;
5782
+ return `start the node with ${joined} on ${nodeName}`;
5783
+ }
5784
+ __name(fleetConsentMissingHint, "fleetConsentMissingHint");
5785
+ var VERB_PATTERN = /^[a-z][a-zA-Z0-9]{0,31}(?:\.[a-z][a-zA-Z0-9]{0,31}){0,3}$/;
5786
+ function isFleetLinkVerb(value) {
5787
+ return typeof value === "string" && VERB_PATTERN.test(value);
5788
+ }
5789
+ __name(isFleetLinkVerb, "isFleetLinkVerb");
5790
+ var FLEET_LINK_ERROR_CODES = [
5791
+ "not_found",
5792
+ "invalid",
5793
+ "unreachable",
5794
+ "consent_missing",
5795
+ "permission_denied",
5796
+ "confirmation_required",
5797
+ "root_required",
5798
+ "kill_switch",
5799
+ "limit_exceeded",
5800
+ "unsupported_on_node",
5801
+ "busy",
5802
+ "operation_failed",
5803
+ "internal",
5804
+ /** A channel the reconnect could not resume (CONTRACTS §3.1). */
5805
+ "resume_failed"
5806
+ ];
5807
+ function isFleetLinkErrorCode(value) {
5808
+ return typeof value === "string" && FLEET_LINK_ERROR_CODES.includes(value);
5809
+ }
5810
+ __name(isFleetLinkErrorCode, "isFleetLinkErrorCode");
5811
+ var FLEET_LINK_MAX_HINT_LENGTH = 500;
5812
+ var FLEET_LINK_CONTROL_TYPES = [
5813
+ "link.hello",
5814
+ "link.ready",
5815
+ "link.ping",
5816
+ "link.pong",
5817
+ "link.pause",
5818
+ "link.resume",
5819
+ "link.drain",
5820
+ "channel.open",
5821
+ "channel.ready",
5822
+ "channel.refused",
5823
+ "channel.close",
5824
+ "operation.progress",
5825
+ "operation.done",
5826
+ "operation.cancel"
5827
+ ];
5828
+ var ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
5829
+ function isFleetLinkId(value) {
5830
+ return typeof value === "string" && ID_PATTERN.test(value);
5831
+ }
5832
+ __name(isFleetLinkId, "isFleetLinkId");
5833
+ var FLEET_LINK_MAX_JSON_DEPTH = 32;
5834
+ var MAX_TOKEN_LENGTH = 4096;
5835
+ var MAX_TAGS = 64;
5836
+ var MAX_TAG_LENGTH = 128;
5837
+ var MAX_VERSION_LENGTH = 64;
5838
+ var MAX_RESUME_KEY_LENGTH = 128;
5839
+ var MAX_SERVER_TIME_LENGTH = 64;
5840
+ var MAX_PROGRESS_MESSAGE_LENGTH = 500;
5841
+ var MAX_DRAIN_GRACE_SEC = 3600;
5842
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5843
+ var EXTENSION_TYPE_PATTERN = /^[a-z][a-zA-Z0-9]{0,31}(?:\.[a-z][a-zA-Z0-9-]{0,31}){1,3}$/;
5844
+ var RESUME_KEY_PATTERN = /^[\x21-\x7e]+$/;
5845
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
5846
+ var CONTROL_TYPES = new Set(FLEET_LINK_CONTROL_TYPES);
5847
+ var KIND_JSON = FLEET_LINK_FRAME_KINDS.JSON;
5848
+ var utf8Encoder = new TextEncoder();
5849
+ function utf8Decode(bytes) {
5850
+ try {
5851
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
5852
+ } catch {
5853
+ return null;
5854
+ }
5855
+ }
5856
+ __name(utf8Decode, "utf8Decode");
5857
+ function isRecord4(value) {
5858
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5859
+ }
5860
+ __name(isRecord4, "isRecord4");
5861
+ function isChannelNumber(value, allowControl = false) {
5862
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= (allowControl ? 0 : 1) && value <= FLEET_LINK_MAX_CHANNEL_NUMBER;
5863
+ }
5864
+ __name(isChannelNumber, "isChannelNumber");
5865
+ function isBoundedInt3(value, min, max) {
5866
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= min && value <= max;
5867
+ }
5868
+ __name(isBoundedInt3, "isBoundedInt3");
5869
+ function boundedString(value, max) {
5870
+ return typeof value === "string" && value.length <= max ? value : null;
5871
+ }
5872
+ __name(boundedString, "boundedString");
5873
+ function toBytes(raw) {
5874
+ if (raw instanceof Uint8Array) return raw;
5875
+ if (raw instanceof ArrayBuffer) return new Uint8Array(raw);
5876
+ if (ArrayBuffer.isView(raw)) return new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
5877
+ return null;
5878
+ }
5879
+ __name(toBytes, "toBytes");
5880
+ function encodeFleetLinkFrame(channel, kind, payload) {
5881
+ if (!isChannelNumber(channel, true) || !isFleetLinkFrameKind(kind) || !(payload instanceof Uint8Array)) {
5882
+ return null;
5883
+ }
5884
+ const total = FLEET_LINK_FRAME_HEADER_BYTES + payload.byteLength;
5885
+ if (total > FLEET_LINK_MAX_FRAME_BYTES) return null;
5886
+ const out = new Uint8Array(total);
5887
+ const view = new DataView(out.buffer);
5888
+ view.setUint32(0, channel, false);
5889
+ view.setUint8(4, kind);
5890
+ view.setUint32(5, payload.byteLength, false);
5891
+ out.set(payload, FLEET_LINK_FRAME_HEADER_BYTES);
5892
+ return out;
5893
+ }
5894
+ __name(encodeFleetLinkFrame, "encodeFleetLinkFrame");
5895
+ function decodeFleetLinkFrame(raw) {
5896
+ const bytes = toBytes(raw);
5897
+ if (!bytes) return null;
5898
+ if (bytes.byteLength > FLEET_LINK_MAX_FRAME_BYTES) return null;
5899
+ if (bytes.byteLength < FLEET_LINK_FRAME_HEADER_BYTES) return null;
5900
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
5901
+ const channel = view.getUint32(0, false);
5902
+ const kind = view.getUint8(4);
5903
+ const length = view.getUint32(5, false);
5904
+ if (!isFleetLinkFrameKind(kind)) return null;
5905
+ if (length !== bytes.byteLength - FLEET_LINK_FRAME_HEADER_BYTES) return null;
5906
+ return {
5907
+ channel,
5908
+ kind,
5909
+ payload: bytes.subarray(FLEET_LINK_FRAME_HEADER_BYTES)
5910
+ };
5911
+ }
5912
+ __name(decodeFleetLinkFrame, "decodeFleetLinkFrame");
5913
+ function peekFleetLinkFrameChannel(raw) {
5914
+ const bytes = toBytes(raw);
5915
+ if (!bytes || bytes.byteLength < 4) return null;
5916
+ return new DataView(bytes.buffer, bytes.byteOffset, 4).getUint32(0, false);
5917
+ }
5918
+ __name(peekFleetLinkFrameChannel, "peekFleetLinkFrameChannel");
5919
+ function depthWithin(value, maxDepth) {
5920
+ const stack = [{ value, depth: 1 }];
5921
+ while (stack.length > 0) {
5922
+ const next = stack.pop();
5923
+ if (next.depth > maxDepth) return false;
5924
+ if (typeof next.value === "object" && next.value !== null) {
5925
+ for (const child of Object.values(next.value)) {
5926
+ if (typeof child === "object" && child !== null) stack.push({ value: child, depth: next.depth + 1 });
5927
+ }
5928
+ }
5929
+ }
5930
+ return true;
5931
+ }
5932
+ __name(depthWithin, "depthWithin");
5933
+ function decodeFleetLinkJson(payload, maxBytes = FLEET_LINK_MAX_JSON_BYTES) {
5934
+ if (!(payload instanceof Uint8Array) || payload.byteLength === 0 || payload.byteLength > maxBytes) return null;
5935
+ const text = utf8Decode(payload);
5936
+ if (text === null) return null;
5937
+ let parsed;
5938
+ try {
5939
+ parsed = JSON.parse(text, (key, value) => FORBIDDEN_KEYS.has(key) ? void 0 : value);
5940
+ } catch {
5941
+ return null;
5942
+ }
5943
+ if (!isRecord4(parsed) || !depthWithin(parsed, FLEET_LINK_MAX_JSON_DEPTH)) return null;
5944
+ return parsed;
5945
+ }
5946
+ __name(decodeFleetLinkJson, "decodeFleetLinkJson");
5947
+ function encodeFleetLinkJson(channel, value, maxBytes = channel === 0 ? FLEET_LINK_MAX_JSON_BYTES : FLEET_LINK_MAX_FRAME_BYTES - FLEET_LINK_FRAME_HEADER_BYTES) {
5948
+ let text;
5949
+ try {
5950
+ text = JSON.stringify(value);
5951
+ } catch {
5952
+ return null;
5953
+ }
5954
+ if (typeof text !== "string") return null;
5955
+ const payload = utf8Encoder.encode(text);
5956
+ if (payload.byteLength > maxBytes) return null;
5957
+ return encodeFleetLinkFrame(channel, KIND_JSON, payload);
5958
+ }
5959
+ __name(encodeFleetLinkJson, "encodeFleetLinkJson");
5960
+ function normalizeFleetLinkError(value) {
5961
+ if (!isRecord4(value) || !isFleetLinkErrorCode(value.code)) return null;
5962
+ const hint = typeof value.hint === "string" ? value.hint.slice(0, FLEET_LINK_MAX_HINT_LENGTH) : "";
5963
+ const error = { code: value.code, hint };
5964
+ if (isRecord4(value.details)) error.details = value.details;
5965
+ return error;
5966
+ }
5967
+ __name(normalizeFleetLinkError, "normalizeFleetLinkError");
5968
+ function makeFleetLinkError(code, hint, details) {
5969
+ const error = { code, hint: hint.slice(0, FLEET_LINK_MAX_HINT_LENGTH) };
5970
+ if (details) error.details = details;
5971
+ return error;
5972
+ }
5973
+ __name(makeFleetLinkError, "makeFleetLinkError");
5974
+ function closeReason(value) {
5975
+ if (value === "normal") return "normal";
5976
+ return isFleetLinkErrorCode(value) ? value : null;
5977
+ }
5978
+ __name(closeReason, "closeReason");
5979
+ function decodeCapabilities(value) {
5980
+ if (!Array.isArray(value) || value.length > MAX_TAGS) return null;
5981
+ const out = [];
5982
+ for (const tag of value) {
5983
+ if (typeof tag !== "string" || tag.length === 0 || tag.length > MAX_TAG_LENGTH) return null;
5984
+ out.push(tag);
5985
+ }
5986
+ return out;
5987
+ }
5988
+ __name(decodeCapabilities, "decodeCapabilities");
5989
+ function decodeResumeChannels(value) {
5990
+ if (!Array.isArray(value) || value.length > FLEET_LINK_MAX_CHANNELS) return null;
5991
+ const out = [];
5992
+ for (const entry of value) {
5993
+ if (!isRecord4(entry) || !isChannelNumber(entry.channel) || !isFleetLinkFamily(entry.family)) return null;
5994
+ const channel = { channel: entry.channel, family: entry.family };
5995
+ if (entry.resumeKey !== void 0) {
5996
+ const key = boundedString(entry.resumeKey, MAX_RESUME_KEY_LENGTH);
5997
+ if (key === null || !RESUME_KEY_PATTERN.test(key)) return null;
5998
+ channel.resumeKey = key;
5999
+ }
6000
+ out.push(channel);
6001
+ }
6002
+ return out;
6003
+ }
6004
+ __name(decodeResumeChannels, "decodeResumeChannels");
6005
+ function decodeChannelList(value) {
6006
+ if (!Array.isArray(value) || value.length > FLEET_LINK_MAX_CHANNELS) return null;
6007
+ return value.every((entry) => isChannelNumber(entry)) ? [...value] : null;
6008
+ }
6009
+ __name(decodeChannelList, "decodeChannelList");
6010
+ function decodeLimits(value) {
6011
+ if (!isRecord4(value)) return null;
6012
+ const { maxFrameBytes, maxChannels, pingIntervalMs, silenceMs, inlineReplyMs } = value;
6013
+ if (!isBoundedInt3(maxFrameBytes, FLEET_LINK_FRAME_HEADER_BYTES, FLEET_LINK_MAX_FRAME_BYTES) || !isBoundedInt3(maxChannels, 1, FLEET_LINK_MAX_CHANNELS) || !isBoundedInt3(pingIntervalMs, 1, 36e5) || !isBoundedInt3(silenceMs, 1, 36e5) || !isBoundedInt3(inlineReplyMs, 1, 36e5)) {
6014
+ return null;
6015
+ }
6016
+ return { maxFrameBytes, maxChannels, pingIntervalMs, silenceMs, inlineReplyMs };
6017
+ }
6018
+ __name(decodeLimits, "decodeLimits");
6019
+ function decodeHello(record) {
6020
+ const nodeId = boundedString(record.nodeId, 36);
6021
+ const token = boundedString(record.token, MAX_TOKEN_LENGTH);
6022
+ const capabilities = decodeCapabilities(record.capabilities);
6023
+ const consents = normalizeFleetConsents(record.consents);
6024
+ if (nodeId === null || !UUID_PATTERN.test(nodeId) || !isBoundedInt3(record.linkVersion, 1, 1e3) || capabilities === null || consents === null || token === null || token.length === 0) {
6025
+ return null;
6026
+ }
6027
+ const hello = {
6028
+ type: "link.hello",
6029
+ nodeId: nodeId.toLowerCase(),
6030
+ linkVersion: record.linkVersion,
6031
+ capabilities,
6032
+ consents,
6033
+ token
6034
+ };
6035
+ if (record.nodeVersion !== void 0) {
6036
+ const version = boundedString(record.nodeVersion, MAX_VERSION_LENGTH);
6037
+ if (version === null) return null;
6038
+ hello.nodeVersion = version;
6039
+ }
6040
+ if (record.resume !== void 0) {
6041
+ if (!isRecord4(record.resume) || !isBoundedInt3(record.resume.linkGeneration, 0, Number.MAX_SAFE_INTEGER)) {
6042
+ return null;
6043
+ }
6044
+ const channels = decodeResumeChannels(record.resume.channels ?? []);
6045
+ if (channels === null) return null;
6046
+ hello.resume = { linkGeneration: record.resume.linkGeneration, channels };
6047
+ }
6048
+ return hello;
6049
+ }
6050
+ __name(decodeHello, "decodeHello");
6051
+ function decodeKnownControl(record) {
6052
+ switch (record.type) {
6053
+ case "link.hello":
6054
+ return decodeHello(record);
6055
+ case "link.ready": {
6056
+ const limits = decodeLimits(record.limits);
6057
+ const serverTime = boundedString(record.serverTime, MAX_SERVER_TIME_LENGTH);
6058
+ const resumed = decodeChannelList(record.resumed ?? []);
6059
+ const closed = decodeChannelList(record.closed ?? []);
6060
+ if (!isBoundedInt3(record.linkGeneration, 1, Number.MAX_SAFE_INTEGER) || limits === null || serverTime === null || resumed === null || closed === null) {
6061
+ return null;
6062
+ }
6063
+ return { type: "link.ready", linkGeneration: record.linkGeneration, limits, serverTime, resumed, closed };
6064
+ }
6065
+ case "link.ping":
6066
+ case "link.pong":
6067
+ return typeof record.t === "number" && Number.isFinite(record.t) ? { type: record.type === "link.ping" ? "link.ping" : "link.pong", t: record.t } : null;
6068
+ case "link.pause":
6069
+ return record.reason === "kill_switch" ? { type: "link.pause", reason: "kill_switch" } : null;
6070
+ case "link.resume":
6071
+ return { type: "link.resume" };
6072
+ case "link.drain":
6073
+ return isBoundedInt3(record.graceSec, 0, MAX_DRAIN_GRACE_SEC) ? { type: "link.drain", graceSec: record.graceSec } : null;
6074
+ case "channel.open": {
6075
+ if (!isChannelNumber(record.channel) || !isFleetLinkFamily(record.family) || !isFleetLinkId(record.requestId) || record.params !== void 0 && !isRecord4(record.params)) {
6076
+ return null;
6077
+ }
6078
+ return {
6079
+ type: "channel.open",
6080
+ channel: record.channel,
6081
+ family: record.family,
6082
+ params: record.params ?? {},
6083
+ requestId: record.requestId
6084
+ };
6085
+ }
6086
+ case "channel.ready": {
6087
+ if (!isChannelNumber(record.channel) || !isFleetLinkId(record.requestId)) return null;
6088
+ const ready = {
6089
+ type: "channel.ready",
6090
+ channel: record.channel,
6091
+ requestId: record.requestId
6092
+ };
6093
+ if (record.resumeKey !== void 0) {
6094
+ const key = boundedString(record.resumeKey, MAX_RESUME_KEY_LENGTH);
6095
+ if (key === null || !RESUME_KEY_PATTERN.test(key)) return null;
6096
+ ready.resumeKey = key;
6097
+ }
6098
+ return ready;
6099
+ }
6100
+ case "channel.refused": {
6101
+ const error = normalizeFleetLinkError(
6102
+ record.error !== void 0 ? record.error : { code: record.code, hint: record.hint, details: record.details }
6103
+ );
6104
+ if (!isChannelNumber(record.channel) || !isFleetLinkId(record.requestId) || error === null) return null;
6105
+ return { type: "channel.refused", channel: record.channel, requestId: record.requestId, error };
6106
+ }
6107
+ case "channel.close": {
6108
+ const reason = closeReason(record.reason);
6109
+ if (!isChannelNumber(record.channel) || reason === null) return null;
6110
+ const close = { type: "channel.close", channel: record.channel, reason };
6111
+ if (typeof record.hint === "string") close.hint = record.hint.slice(0, FLEET_LINK_MAX_HINT_LENGTH);
6112
+ return close;
6113
+ }
6114
+ case "operation.progress": {
6115
+ if (!isFleetLinkId(record.operationId) || !isRecord4(record.progress)) return null;
6116
+ const progress = {};
6117
+ if (record.progress.percent !== void 0) {
6118
+ const percent2 = record.progress.percent;
6119
+ if (typeof percent2 !== "number" || !Number.isFinite(percent2) || percent2 < 0 || percent2 > 100)
6120
+ return null;
6121
+ progress.percent = percent2;
6122
+ }
6123
+ if (record.progress.message !== void 0) {
6124
+ if (typeof record.progress.message !== "string") return null;
6125
+ progress.message = record.progress.message.slice(0, MAX_PROGRESS_MESSAGE_LENGTH);
6126
+ }
6127
+ return { type: "operation.progress", operationId: record.operationId, progress };
6128
+ }
6129
+ case "operation.done": {
6130
+ if (!isFleetLinkId(record.operationId)) return null;
6131
+ const done = { type: "operation.done", operationId: record.operationId };
6132
+ if (record.error !== void 0) {
6133
+ const error = normalizeFleetLinkError(record.error);
6134
+ if (error === null) return null;
6135
+ done.error = error;
6136
+ } else if (record.result !== void 0) {
6137
+ done.result = record.result;
6138
+ }
6139
+ return done;
6140
+ }
6141
+ case "operation.cancel":
6142
+ return isFleetLinkId(record.operationId) ? { type: "operation.cancel", operationId: record.operationId } : null;
6143
+ default:
6144
+ return null;
6145
+ }
6146
+ }
6147
+ __name(decodeKnownControl, "decodeKnownControl");
6148
+ function decodeFleetLinkControl(payload) {
6149
+ const record = decodeFleetLinkJson(payload);
6150
+ if (!record || typeof record.type !== "string") return null;
6151
+ if (CONTROL_TYPES.has(record.type)) return decodeKnownControl(record);
6152
+ if (!EXTENSION_TYPE_PATTERN.test(record.type)) return null;
6153
+ return record;
6154
+ }
6155
+ __name(decodeFleetLinkControl, "decodeFleetLinkControl");
6156
+ function isFleetLinkControlMessage(message) {
6157
+ return CONTROL_TYPES.has(message.type);
6158
+ }
6159
+ __name(isFleetLinkControlMessage, "isFleetLinkControlMessage");
6160
+ function encodeFleetLinkControl(message) {
6161
+ return encodeFleetLinkJson(0, message, FLEET_LINK_MAX_JSON_BYTES);
6162
+ }
6163
+ __name(encodeFleetLinkControl, "encodeFleetLinkControl");
6164
+ function asFleetLinkRequest(record) {
6165
+ if (!isFleetLinkId(record.requestId) || typeof record.type !== "string" || record.type.length > 128) return null;
6166
+ if (record.params !== void 0 && !isRecord4(record.params)) return null;
6167
+ const request = { requestId: record.requestId, type: record.type };
6168
+ if (record.params !== void 0) request.params = record.params;
6169
+ return request;
6170
+ }
6171
+ __name(asFleetLinkRequest, "asFleetLinkRequest");
6172
+ function decodeFleetLinkAck(payload) {
6173
+ if (!(payload instanceof Uint8Array) || payload.byteLength !== 4) return null;
6174
+ return new DataView(payload.buffer, payload.byteOffset, 4).getUint32(0, false);
6175
+ }
6176
+ __name(decodeFleetLinkAck, "decodeFleetLinkAck");
6177
+ var FLEET_JOB_FAILURE_KIND_MODEL_USAGE_LIMIT = "model_usage_limit";
6178
+ var FLEET_MODEL_USAGE_LIMIT_FALLBACK_MS = 60 * 60 * 1e3;
6179
+ var FLEET_MODEL_USAGE_LIMIT_MIN_MS = 60 * 1e3;
6180
+ var FLEET_MODEL_USAGE_LIMIT_MAX_MS = 8 * 24 * 60 * 60 * 1e3;
6181
+ var FLEET_MODEL_USAGE_LIMIT_RESET_SLACK_MS = 60 * 1e3;
6182
+ var FLEET_MODEL_USAGE_LIMIT_MAX_MESSAGE_LENGTH = 300;
6183
+ var LIMIT_SIGNATURES = [
6184
+ { pattern: /\bhit (?:your|the) (?:[\w-]+ )?(?:usage |rate )?limit\b/i, strict: true },
6185
+ {
6186
+ pattern: /\b(?:usage|weekly|daily|session|5[- ]hour|five[- ]hour) limit (?:has been )?(?:reached|exceeded|hit)\b/i,
6187
+ strict: true
6188
+ },
6189
+ { pattern: /\busage[_ ]limit[_ ](?:reached|exceeded)\b/i, strict: true },
6190
+ { pattern: /\brate_limit_error\b/, strict: true },
6191
+ { pattern: /\bexceeded retry limit,? last status:? 429\b/i, strict: true },
6192
+ { pattern: /\b429\b[^\n]{0,40}\btoo many requests\b/i, strict: true },
6193
+ { pattern: /\bAPI Error:? 429\b/i, strict: true },
6194
+ { pattern: /\brate[- ]limit(?:ed)?\b[^\n]{0,20}\b(?:reached|exceeded)\b/i, strict: false },
6195
+ { pattern: /\bout of (?:extra )?usage\b/i, strict: false }
6196
+ ];
6197
+ function detectFleetModelUsageLimit(text, options = {}) {
6198
+ if (typeof text !== "string" || !text.trim()) return null;
6199
+ const strict = options.strict === true;
6200
+ const match = LIMIT_SIGNATURES.find((signature) => (!strict || signature.strict) && signature.pattern.test(text));
6201
+ if (!match) return null;
6202
+ const nowMs = toEpochMs(options.now);
6203
+ const provider = typeof options.provider === "string" && options.provider.trim() ? options.provider.trim() : "claude-code";
6204
+ const resetsAtMs = parseResetInstant(text, nowMs, options.timeZone);
6205
+ const resetsAt = resetsAtMs !== null && resetsAtMs > nowMs ? resetsAtMs : null;
6206
+ return {
6207
+ provider,
6208
+ window: classifyWindow(text),
6209
+ resetsAt: resetsAt === null ? null : new Date(resetsAt).toISOString(),
6210
+ retryAfter: new Date(
6211
+ clampRetryAfterMs(
6212
+ resetsAt === null ? nowMs + FLEET_MODEL_USAGE_LIMIT_FALLBACK_MS : resetsAt + FLEET_MODEL_USAGE_LIMIT_RESET_SLACK_MS,
6213
+ nowMs
6214
+ )
6215
+ ).toISOString(),
6216
+ message: describeLimitSentence(text, match.pattern)
6217
+ };
6218
+ }
6219
+ __name(detectFleetModelUsageLimit, "detectFleetModelUsageLimit");
6220
+ function clampRetryAfterMs(retryAfterMs, nowMs) {
6221
+ const floor = nowMs + FLEET_MODEL_USAGE_LIMIT_MIN_MS;
6222
+ const ceiling = nowMs + FLEET_MODEL_USAGE_LIMIT_MAX_MS;
6223
+ if (!Number.isFinite(retryAfterMs)) return nowMs + FLEET_MODEL_USAGE_LIMIT_FALLBACK_MS;
6224
+ return Math.min(Math.max(retryAfterMs, floor), ceiling);
6225
+ }
6226
+ __name(clampRetryAfterMs, "clampRetryAfterMs");
6227
+ function describeFleetModelProvider(provider) {
6228
+ switch (provider) {
6229
+ case "claude-code":
6230
+ return "Claude Code";
6231
+ case "codex":
6232
+ return "Codex";
6233
+ default:
6234
+ return provider;
6235
+ }
6236
+ }
6237
+ __name(describeFleetModelProvider, "describeFleetModelProvider");
6238
+ function describeFleetModelUsageLimitWindow(window) {
6239
+ switch (window) {
6240
+ case "weekly":
6241
+ return "weekly limit";
6242
+ case "session":
6243
+ return "5-hour limit";
6244
+ case "daily":
6245
+ return "daily limit";
6246
+ case "rate":
6247
+ return "rate limit";
6248
+ default:
6249
+ return "usage limit";
6250
+ }
6251
+ }
6252
+ __name(describeFleetModelUsageLimitWindow, "describeFleetModelUsageLimitWindow");
6253
+ function toEpochMs(value) {
6254
+ if (value instanceof Date) return value.getTime();
6255
+ if (typeof value === "number" && Number.isFinite(value)) return value;
6256
+ return Date.now();
6257
+ }
6258
+ __name(toEpochMs, "toEpochMs");
6259
+ function classifyWindow(text) {
6260
+ if (/\bweekly\b|\bper week\b/i.test(text)) return "weekly";
6261
+ if (/\b(?:5|five)[- ]hour\b|\bsession limit\b/i.test(text)) return "session";
6262
+ if (/\bdaily\b|\bper day\b/i.test(text)) return "daily";
6263
+ if (/\b429\b|rate_limit_error|\brate[- ]limit/i.test(text)) return "rate";
6264
+ return "usage";
6265
+ }
6266
+ __name(classifyWindow, "classifyWindow");
6267
+ function trimSentence(text) {
6268
+ const clean = text.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim();
6269
+ const points = Array.from(clean);
6270
+ return points.length > FLEET_MODEL_USAGE_LIMIT_MAX_MESSAGE_LENGTH ? `${points.slice(0, FLEET_MODEL_USAGE_LIMIT_MAX_MESSAGE_LENGTH - 1).join("").trimEnd()}\u2026` : clean;
6271
+ }
6272
+ __name(trimSentence, "trimSentence");
6273
+ function describeLimitSentence(text, pattern) {
6274
+ const line = text.split(/\r?\n/).find((candidate) => pattern.test(candidate));
6275
+ return trimSentence(line ?? text);
6276
+ }
6277
+ __name(describeLimitSentence, "describeLimitSentence");
6278
+ var MONTHS = {
6279
+ jan: 0,
6280
+ feb: 1,
6281
+ mar: 2,
6282
+ apr: 3,
6283
+ may: 4,
6284
+ jun: 5,
6285
+ jul: 6,
6286
+ aug: 7,
6287
+ sep: 8,
6288
+ sept: 8,
6289
+ oct: 9,
6290
+ nov: 10,
6291
+ dec: 11
6292
+ };
6293
+ function parseResetInstant(text, nowMs, timeZone) {
6294
+ const epoch = /usage limit reached\|(\d{10,13})\b/i.exec(text);
6295
+ if (epoch) {
6296
+ const value = Number(epoch[1]);
6297
+ return epoch[1].length >= 13 ? value : value * 1e3;
6298
+ }
6299
+ const iso = /\b(?:resets?|reset at|try again at|available again at|until)\s+(?:at\s+)?(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2}))/i.exec(
6300
+ text
6301
+ );
6302
+ if (iso) {
6303
+ const value = Date.parse(iso[1]);
6304
+ if (Number.isFinite(value)) return value;
6305
+ }
6306
+ const relative6 = /\b(?:resets?|reset|try again|available again|retry)\s+in\s+((?:\d+\s*(?:d|days?|h|hrs?|hours?|m|mins?|minutes?|s|secs?|seconds?)\b(?:[\s,]+(?:and )?)?)+)/i.exec(
6307
+ text
6308
+ );
6309
+ if (relative6) {
6310
+ const ms = parseDuration(relative6[1]);
6311
+ if (ms > 0) return nowMs + ms;
6312
+ }
6313
+ const wall = /\b(?:resets?|reset at|will reset at|try again at|available again at)\s+(?:at\s+|on\s+)?(?:(jan|feb|mar|apr|may|jun|jul|aug|sept?|oct|nov|dec)[a-z]*\.?\s+(\d{1,2})(?:st|nd|rd|th)?,?\s+(?:at\s+)?)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\b(?:\s*\(([A-Za-z_]+(?:\/[A-Za-z0-9_+-]+){0,2}|UTC|GMT)\))?/i.exec(
6314
+ text
6315
+ );
6316
+ if (wall) {
6317
+ const [, monthName, dayText, hourText, minuteText, meridiem, zoneText] = wall;
6318
+ let hour = Number(hourText);
6319
+ const minute = minuteText ? Number(minuteText) : 0;
6320
+ if (!meridiem && !minuteText && !monthName) {
6321
+ return null;
6322
+ }
6323
+ if (meridiem) {
6324
+ if (hour < 1 || hour > 12) return null;
6325
+ hour = hour % 12 + (meridiem.toLowerCase() === "pm" ? 12 : 0);
6326
+ }
6327
+ if (hour > 23 || minute > 59) return null;
6328
+ const zone = resolveZone(zoneText) ?? resolveZone(timeZone) ?? "UTC";
6329
+ const today = zonedParts(nowMs, zone);
6330
+ if (monthName) {
6331
+ const month = MONTHS[monthName.toLowerCase()];
6332
+ const day = Number(dayText);
6333
+ if (month === void 0 || day < 1 || day > 31) return null;
6334
+ let candidate2 = zonedWallTimeToUtc(today.year, month, day, hour, minute, zone);
6335
+ if (candidate2 < nowMs - 24 * 60 * 60 * 1e3) {
6336
+ candidate2 = zonedWallTimeToUtc(today.year + 1, month, day, hour, minute, zone);
6337
+ }
6338
+ return candidate2;
6339
+ }
6340
+ let candidate = zonedWallTimeToUtc(today.year, today.month, today.day, hour, minute, zone);
6341
+ if (candidate <= nowMs) {
6342
+ candidate = zonedWallTimeToUtc(today.year, today.month, today.day + 1, hour, minute, zone);
6343
+ }
6344
+ return candidate;
6345
+ }
6346
+ return null;
6347
+ }
6348
+ __name(parseResetInstant, "parseResetInstant");
6349
+ function parseDuration(text) {
6350
+ let total = 0;
6351
+ let index = 0;
6352
+ const length = Math.min(text.length, 200);
6353
+ while (index < length) {
6354
+ if (!isDigit(text.charCodeAt(index))) {
6355
+ index += 1;
6356
+ continue;
6357
+ }
6358
+ let value = 0;
6359
+ while (index < length && isDigit(text.charCodeAt(index))) {
6360
+ value = Math.min(value * 10 + (text.charCodeAt(index) - 48), 1e6);
6361
+ index += 1;
6362
+ }
6363
+ while (index < length && text[index] === " ") index += 1;
6364
+ let word = "";
6365
+ while (index < length && /[a-z]/i.test(text[index])) {
6366
+ word += text[index].toLowerCase();
6367
+ index += 1;
6368
+ }
6369
+ const unitMs = DURATION_UNITS[word];
6370
+ if (unitMs !== void 0) total += value * unitMs;
6371
+ }
6372
+ return total;
6373
+ }
6374
+ __name(parseDuration, "parseDuration");
6375
+ var DURATION_UNITS = {
6376
+ d: 864e5,
6377
+ day: 864e5,
6378
+ days: 864e5,
6379
+ h: 36e5,
6380
+ hr: 36e5,
6381
+ hrs: 36e5,
6382
+ hour: 36e5,
6383
+ hours: 36e5,
6384
+ m: 6e4,
6385
+ min: 6e4,
6386
+ mins: 6e4,
6387
+ minute: 6e4,
6388
+ minutes: 6e4,
6389
+ s: 1e3,
6390
+ sec: 1e3,
6391
+ secs: 1e3,
6392
+ second: 1e3,
6393
+ seconds: 1e3
6394
+ };
6395
+ function isDigit(code) {
6396
+ return code >= 48 && code <= 57;
6397
+ }
6398
+ __name(isDigit, "isDigit");
6399
+ function resolveZone(zone) {
6400
+ if (typeof zone !== "string" || !zone.trim()) return null;
6401
+ const candidate = zone.trim();
6402
+ try {
6403
+ new Intl.DateTimeFormat("en-US", { timeZone: candidate });
6404
+ return candidate;
6405
+ } catch {
6406
+ return null;
6407
+ }
6408
+ }
6409
+ __name(resolveZone, "resolveZone");
6410
+ function zonedParts(instantMs, zone) {
6411
+ const parts = new Intl.DateTimeFormat("en-US", {
6412
+ timeZone: zone,
6413
+ hourCycle: "h23",
6414
+ year: "numeric",
6415
+ month: "2-digit",
6416
+ day: "2-digit",
6417
+ hour: "2-digit",
6418
+ minute: "2-digit",
6419
+ second: "2-digit"
6420
+ }).formatToParts(new Date(instantMs));
6421
+ const get = /* @__PURE__ */ __name((type) => Number(parts.find((part) => part.type === type)?.value ?? 0), "get");
6422
+ return {
6423
+ year: get("year"),
6424
+ month: get("month") - 1,
6425
+ day: get("day"),
6426
+ hour: get("hour") % 24,
6427
+ minute: get("minute"),
6428
+ second: get("second")
6429
+ };
6430
+ }
6431
+ __name(zonedParts, "zonedParts");
6432
+ function zoneOffsetMs(instantMs, zone) {
6433
+ const whole = Math.floor(instantMs / 1e3) * 1e3;
6434
+ const p = zonedParts(whole, zone);
6435
+ return Date.UTC(p.year, p.month, p.day, p.hour, p.minute, p.second) - whole;
6436
+ }
6437
+ __name(zoneOffsetMs, "zoneOffsetMs");
6438
+ function zonedWallTimeToUtc(year, month, day, hour, minute, zone) {
6439
+ const asIfUtc = Date.UTC(year, month, day, hour, minute, 0);
6440
+ const firstOffset = zoneOffsetMs(asIfUtc, zone);
6441
+ const guess = asIfUtc - firstOffset;
6442
+ const secondOffset = zoneOffsetMs(guess, zone);
6443
+ return secondOffset === firstOffset ? guess : asIfUtc - secondOffset;
6444
+ }
6445
+ __name(zonedWallTimeToUtc, "zonedWallTimeToUtc");
5522
6446
  var FLEET_ENROLLABLE_NODE_KINDS = ["desktop-node", "node"];
5523
6447
  function isFleetEnrollableNodeKind(value) {
5524
6448
  return typeof value === "string" && FLEET_ENROLLABLE_NODE_KINDS.includes(value);
@@ -5601,10 +6525,109 @@ var FLEET_CREDENTIAL_MAX_LENGTH = 256;
5601
6525
  var FLEET_DEFAULT_ENROLLMENT_TOKEN_TTL_MS = 15 * 6e4;
5602
6526
  var FLEET_DEFAULT_NODE_OFFLINE_AFTER_MS = 5 * 6e4;
5603
6527
  var FLEET_DEFAULT_NODE_OFFLINE_NOTICE_AFTER_MS = 30 * 6e4;
5604
- var FLEET_DEFAULT_MAX_CAPABILITY_TAGS = 16;
6528
+ var FLEET_DEFAULT_MAX_CAPABILITY_TAGS = 32;
6529
+ var FLEET_LEGACY_MAX_CAPABILITY_TAGS = 16;
5605
6530
  var FLEET_DEFAULT_MAX_CAPABILITY_TAG_LENGTH = 32;
5606
6531
  var FLEET_DEFAULT_CREDENTIAL_ROTATION_OVERLAP_MS = 15 * 6e4;
5607
6532
  var FLEET_MAX_CREDENTIAL_ROTATION_OVERLAP_MS = 24 * 60 * 6e4;
6533
+ var FLEET_HEALTH_PROBE_TIMEOUT_MS = 2e3;
6534
+ var FLEET_HEALTH_RAW_RETENTION_MS = 7 * 24 * 60 * 6e4;
6535
+ var FLEET_HEALTH_HOURLY_RETENTION_MS = 90 * 24 * 60 * 6e4;
6536
+ var FLEET_HEALTH_DISK_CRITICAL_MARGIN_BYTES = 1024 ** 3;
6537
+ var FLEET_HEALTH_MAX_GPU_MODEL_LENGTH = 64;
6538
+ var FLEET_HEALTH_MAX_GPUS = 16;
6539
+ var FLEET_HEALTH_MAX_MIB = 2 ** 40;
6540
+ var FLEET_HEALTH_MAX_LOAD_AVG = 1e5;
6541
+ var FLEET_HEALTH_MAX_CORES = 4096;
6542
+ var FLEET_HEALTH_MAX_UPTIME_SEC = 100 * 365 * 24 * 60 * 60;
6543
+ var FLEET_HEALTH_MAX_FAILED_SERVICES = 1e4;
6544
+ var FLEET_NODE_GPU_VENDORS = ["nvidia", "amd", "intel", "apple", "other"];
6545
+ function finiteIn(value, min, max) {
6546
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
6547
+ if (value < min || value > max) return null;
6548
+ return value;
6549
+ }
6550
+ __name(finiteIn, "finiteIn");
6551
+ function round(value, decimals) {
6552
+ const factor = 10 ** decimals;
6553
+ return Math.round(value * factor) / factor;
6554
+ }
6555
+ __name(round, "round");
6556
+ function percent(value) {
6557
+ const v = finiteIn(value, 0, 100);
6558
+ return v === null ? null : round(v, 1);
6559
+ }
6560
+ __name(percent, "percent");
6561
+ function wholeIn(value, min, max) {
6562
+ const v = finiteIn(value, min, max);
6563
+ return v === null ? null : Math.floor(v);
6564
+ }
6565
+ __name(wholeIn, "wholeIn");
6566
+ function boundedText(value, max) {
6567
+ if (typeof value !== "string") return null;
6568
+ const cleaned = value.replace(/[\u0000-\u001F\u007F]+/g, " ").replace(/\s+/g, " ").trim();
6569
+ return cleaned ? cleaned.slice(0, max) : null;
6570
+ }
6571
+ __name(boundedText, "boundedText");
6572
+ function sanitizeOneGpu(raw) {
6573
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
6574
+ const source = raw;
6575
+ const vendor = FLEET_NODE_GPU_VENDORS.includes(source.vendor) ? source.vendor : "other";
6576
+ const gpu = {
6577
+ vendor,
6578
+ name: boundedText(source.name, FLEET_HEALTH_MAX_GPU_MODEL_LENGTH)
6579
+ };
6580
+ const util = percent(source.utilPercent);
6581
+ if (util !== null) gpu.utilPercent = util;
6582
+ const used = wholeIn(source.memUsedMiB, 0, FLEET_HEALTH_MAX_MIB);
6583
+ const total = wholeIn(source.memTotalMiB, 0, FLEET_HEALTH_MAX_MIB);
6584
+ if (used !== null && total !== null && used > total) return gpu;
6585
+ if (used !== null) gpu.memUsedMiB = used;
6586
+ if (total !== null) gpu.memTotalMiB = total;
6587
+ return gpu;
6588
+ }
6589
+ __name(sanitizeOneGpu, "sanitizeOneGpu");
6590
+ function sanitizeGpu(raw) {
6591
+ if (raw === null) return null;
6592
+ if (!Array.isArray(raw)) return void 0;
6593
+ const gpus = raw.slice(0, FLEET_HEALTH_MAX_GPUS).map(sanitizeOneGpu).filter((gpu) => gpu !== null);
6594
+ if (gpus.length === 0) return raw.length === 0 ? null : void 0;
6595
+ return gpus;
6596
+ }
6597
+ __name(sanitizeGpu, "sanitizeGpu");
6598
+ function sanitizeFleetNodeHealthSnapshot(raw) {
6599
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
6600
+ const source = raw;
6601
+ const out = {};
6602
+ const cpu = percent(source.cpuPercent);
6603
+ if (cpu !== null) out.cpuPercent = cpu;
6604
+ const mem = percent(source.memPercent);
6605
+ if (mem !== null) out.memPercent = mem;
6606
+ const memTotal = wholeIn(source.memTotalMiB, 0, FLEET_HEALTH_MAX_MIB);
6607
+ const memUsed = wholeIn(source.memUsedMiB, 0, FLEET_HEALTH_MAX_MIB);
6608
+ if (memTotal !== null) out.memTotalMiB = memTotal;
6609
+ if (memUsed !== null && (memTotal === null || memUsed <= memTotal)) out.memUsedMiB = memUsed;
6610
+ const disk = percent(source.diskPercent);
6611
+ if (disk !== null) out.diskPercent = disk;
6612
+ const diskFree = wholeIn(source.diskFreeMiB, 0, FLEET_HEALTH_MAX_MIB);
6613
+ const diskTotal = wholeIn(source.diskTotalMiB, 0, FLEET_HEALTH_MAX_MIB);
6614
+ if (diskTotal !== null) out.diskTotalMiB = diskTotal;
6615
+ if (diskFree !== null && (diskTotal === null || diskFree <= diskTotal)) out.diskFreeMiB = diskFree;
6616
+ const load = finiteIn(source.loadAvg1, 0, FLEET_HEALTH_MAX_LOAD_AVG);
6617
+ if (load !== null) out.loadAvg1 = round(load, 2);
6618
+ const cores = wholeIn(source.cpuCores, 1, FLEET_HEALTH_MAX_CORES);
6619
+ if (cores !== null) out.cpuCores = cores;
6620
+ const uptime2 = wholeIn(source.uptimeSec, 0, FLEET_HEALTH_MAX_UPTIME_SEC);
6621
+ if (uptime2 !== null) out.uptimeSec = uptime2;
6622
+ const gpu = sanitizeGpu(source.gpu);
6623
+ if (gpu !== void 0) out.gpu = gpu;
6624
+ if (typeof source.pendingReboot === "boolean") out.pendingReboot = source.pendingReboot;
6625
+ const failed = wholeIn(source.failedServices, 0, FLEET_HEALTH_MAX_FAILED_SERVICES);
6626
+ if (failed !== null) out.failedServices = failed;
6627
+ return Object.keys(out).length > 0 ? out : null;
6628
+ }
6629
+ __name(sanitizeFleetNodeHealthSnapshot, "sanitizeFleetNodeHealthSnapshot");
6630
+ var FLEET_HEALTH_BYTES_PER_MIB = 1024 * 1024;
5608
6631
  var FLEET_PUSH_CAPABILITY = "git-push";
5609
6632
  var FLEET_PUSH_MIN_GIT_VERSION = "2.31.0";
5610
6633
  var FLEET_PUSH_CREDENTIAL_USERNAME = "x-access-token";
@@ -6124,6 +7147,8 @@ var EVER_STATS_KEY_ID_HEADER = CONSTANTS.stats_headers.key_id;
6124
7147
  var EVER_STATS_SIGNATURE_PREFIX = CONSTANTS.stats_signature_prefix;
6125
7148
  var EVER_STATS_MAX_BODY_BYTES = CONSTANTS.stats.max_bytes;
6126
7149
  var DOMAIN_EVENT_CATALOG = [
7150
+ // EW-P2-6 — `org:write` (and the app) create Organizations.
7151
+ { type: "organization.created", domain: "organization", aggregateType: "organization", phase: "P2" },
6127
7152
  { type: "organization.updated", domain: "organization", aggregateType: "organization", phase: "P1" },
6128
7153
  {
6129
7154
  type: "organization.member.added",
@@ -6170,10 +7195,10 @@ var DOMAIN_EVENT_CATALOG = [
6170
7195
  { type: "mission.updated", domain: "missions", aggregateType: "mission", phase: "P2" },
6171
7196
  { type: "mission.status_changed", domain: "missions", aggregateType: "mission", phase: "P2" },
6172
7197
  { type: "mission.deleted", domain: "missions", aggregateType: "mission", phase: "P2" },
6173
- { type: "mission.goal.linked", domain: "missions", aggregateType: "mission", phase: "P2" },
6174
- { type: "mission.goal.unlinked", domain: "missions", aggregateType: "mission", phase: "P2" },
6175
- { type: "mission.work.linked", domain: "missions", aggregateType: "mission", phase: "P2" },
6176
- { type: "mission.work.unlinked", domain: "missions", aggregateType: "mission", phase: "P2" },
7198
+ { type: "mission.goal.linked", domain: "missions", aggregateType: "mission", phase: "P2", child: "goal" },
7199
+ { type: "mission.goal.unlinked", domain: "missions", aggregateType: "mission", phase: "P2", child: "goal" },
7200
+ { type: "mission.work.linked", domain: "missions", aggregateType: "mission", phase: "P2", child: "work" },
7201
+ { type: "mission.work.unlinked", domain: "missions", aggregateType: "mission", phase: "P2", child: "work" },
6177
7202
  { type: "goal.created", domain: "goals", aggregateType: "goal", phase: "P2" },
6178
7203
  { type: "goal.updated", domain: "goals", aggregateType: "goal", phase: "P2" },
6179
7204
  { type: "goal.status_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
@@ -6230,6 +7255,11 @@ var DOMAIN_EVENT_TYPES = DOMAIN_EVENT_CATALOG.map((entry) => entry.type);
6230
7255
  var CATALOG_BY_TYPE = new Map(
6231
7256
  DOMAIN_EVENT_CATALOG.map((entry) => [entry.type, entry])
6232
7257
  );
7258
+ var RECIPIENT_SCOPED_EVENT_DOMAINS = {
7259
+ inbox: "inbox:read",
7260
+ email: "email:read"
7261
+ };
7262
+ var RECIPIENT_EVENT_SCOPES = Object.values(RECIPIENT_SCOPED_EVENT_DOMAINS);
6233
7263
  var INTEGRATION_SCOPES = [
6234
7264
  "org:read",
6235
7265
  "org:write",
@@ -6263,6 +7293,56 @@ var EVER_GAUZY_PRESET_SCOPES = INTEGRATION_SCOPES.filter(
6263
7293
  (scope) => !INTEGRATION_OPT_IN_SCOPES.includes(scope)
6264
7294
  );
6265
7295
 
7296
+ // src/core/link/modules/control.ts
7297
+ var FLEET_MANAGED_CAPABILITY = "managed";
7298
+ function createLinkControlState() {
7299
+ let paused = false;
7300
+ let drain = null;
7301
+ const listeners = /* @__PURE__ */ new Set();
7302
+ const notify = /* @__PURE__ */ __name(() => {
7303
+ const snapshot = { paused, drain };
7304
+ for (const listener of [...listeners]) {
7305
+ try {
7306
+ listener(snapshot);
7307
+ } catch {
7308
+ }
7309
+ }
7310
+ }, "notify");
7311
+ return {
7312
+ get paused() {
7313
+ return paused;
7314
+ },
7315
+ get drain() {
7316
+ return drain;
7317
+ },
7318
+ onChange(listener) {
7319
+ listeners.add(listener);
7320
+ return () => {
7321
+ listeners.delete(listener);
7322
+ };
7323
+ },
7324
+ pause() {
7325
+ if (paused) return;
7326
+ paused = true;
7327
+ notify();
7328
+ },
7329
+ resume() {
7330
+ if (!paused) return;
7331
+ paused = false;
7332
+ notify();
7333
+ },
7334
+ startDrain(message, receivedAt) {
7335
+ drain = { graceSec: message.graceSec, receivedAt };
7336
+ notify();
7337
+ }
7338
+ };
7339
+ }
7340
+ __name(createLinkControlState, "createLinkControlState");
7341
+ var controlModule = {
7342
+ id: "control",
7343
+ tags: /* @__PURE__ */ __name((environment) => environment.consents.manage ? [FLEET_MANAGED_CAPABILITY] : [], "tags")
7344
+ };
7345
+
6266
7346
  // src/core/gpu-probe.ts
6267
7347
  var VENDOR_PATTERNS = [
6268
7348
  { vendor: "nvidia", pattern: /\b(nvidia|geforce|quadro|tesla|rtx|gtx)\b/i },
@@ -6341,6 +7421,11 @@ function extractChipsetLines(stdout) {
6341
7421
  }
6342
7422
  __name(extractChipsetLines, "extractChipsetLines");
6343
7423
  async function detectGpu(runner, platform) {
7424
+ return (await detectGpuWithEvidence(runner, platform)).gpu;
7425
+ }
7426
+ __name(detectGpu, "detectGpu");
7427
+ async function detectGpuWithEvidence(runner, platform) {
7428
+ let answered = false;
6344
7429
  for (const probe of gpuProbesFor(platform)) {
6345
7430
  let stdout;
6346
7431
  try {
@@ -6352,6 +7437,7 @@ async function detectGpu(runner, platform) {
6352
7437
  } catch {
6353
7438
  continue;
6354
7439
  }
7440
+ if (probe.command !== "nvidia-smi") answered = true;
6355
7441
  let text = stdout;
6356
7442
  if (probe.command === "lspci") {
6357
7443
  text = extractDisplayLines(stdout);
@@ -6362,11 +7448,82 @@ async function detectGpu(runner, platform) {
6362
7448
  if (!model) {
6363
7449
  continue;
6364
7450
  }
6365
- return { vendor: classifyGpuVendor(text), model };
7451
+ return { gpu: { vendor: classifyGpuVendor(text), model }, answered: true };
6366
7452
  }
6367
- return null;
7453
+ return { gpu: null, answered };
6368
7454
  }
6369
- __name(detectGpu, "detectGpu");
7455
+ __name(detectGpuWithEvidence, "detectGpuWithEvidence");
7456
+ var NVIDIA_SMI_HEALTH_ARGS = [
7457
+ "--query-gpu=name,utilization.gpu,memory.used,memory.total",
7458
+ "--format=csv,noheader,nounits"
7459
+ ];
7460
+ function numericField(text) {
7461
+ if (typeof text !== "string") return void 0;
7462
+ const trimmed = text.trim();
7463
+ if (!/^\d+(\.\d+)?$/.test(trimmed)) return void 0;
7464
+ const value = Number(trimmed);
7465
+ return Number.isFinite(value) ? value : void 0;
7466
+ }
7467
+ __name(numericField, "numericField");
7468
+ function parseNvidiaSmiHealth(stdout) {
7469
+ const readings = [];
7470
+ for (const raw of (stdout ?? "").split(/\r?\n/)) {
7471
+ const reading = parseNvidiaSmiLine(raw.trim());
7472
+ if (reading) readings.push(reading);
7473
+ }
7474
+ return readings;
7475
+ }
7476
+ __name(parseNvidiaSmiHealth, "parseNvidiaSmiHealth");
7477
+ function parseNvidiaSmiLine(line) {
7478
+ if (!line) return null;
7479
+ const cells = line.split(",");
7480
+ if (cells.length < 4) return null;
7481
+ const memTotal = numericField(cells[cells.length - 1]);
7482
+ const memUsed = numericField(cells[cells.length - 2]);
7483
+ const util = numericField(cells[cells.length - 3]);
7484
+ const name = cells.slice(0, cells.length - 3).join(",").trim().slice(0, 120);
7485
+ if (!name) return null;
7486
+ const reading = { name };
7487
+ if (util !== void 0) reading.utilPercent = util;
7488
+ if (memUsed !== void 0) reading.memUsedMiB = Math.round(memUsed);
7489
+ if (memTotal !== void 0) reading.memTotalMiB = Math.round(memTotal);
7490
+ return reading;
7491
+ }
7492
+ __name(parseNvidiaSmiLine, "parseNvidiaSmiLine");
7493
+ var ROCM_SMI_HEALTH_ARGS = ["--showuse", "--showmeminfo", "vram", "--json"];
7494
+ function parseRocmSmiHealth(stdout) {
7495
+ let parsed;
7496
+ try {
7497
+ parsed = JSON.parse((stdout ?? "").trim());
7498
+ } catch {
7499
+ return null;
7500
+ }
7501
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
7502
+ const card = Object.entries(parsed).find(([key]) => /^card\d+$/i.test(key))?.[1];
7503
+ if (!card || typeof card !== "object") return null;
7504
+ const fields = Object.entries(card);
7505
+ const read = /* @__PURE__ */ __name((pattern) => {
7506
+ const entry = fields.find(([key]) => pattern.test(key));
7507
+ return entry ? numericField(String(entry[1])) : void 0;
7508
+ }, "read");
7509
+ const util = read(/gpu use \(%\)/i);
7510
+ const totalBytes = read(/vram total memory \(b\)/i);
7511
+ const usedBytes = read(/vram total used memory \(b\)/i);
7512
+ const reading = {};
7513
+ if (util !== void 0) reading.utilPercent = util;
7514
+ if (usedBytes !== void 0) reading.memUsedMiB = Math.round(usedBytes / (1024 * 1024));
7515
+ if (totalBytes !== void 0) reading.memTotalMiB = Math.round(totalBytes / (1024 * 1024));
7516
+ return Object.keys(reading).length > 0 ? reading : null;
7517
+ }
7518
+ __name(parseRocmSmiHealth, "parseRocmSmiHealth");
7519
+ var IOREG_GPU_ARGS = ["-r", "-d", "1", "-w", "0", "-c", "IOAccelerator"];
7520
+ function parseIoregGpuUtil(stdout) {
7521
+ const match = /"Device Utilization %"\s*=\s*(\d+)/.exec(stdout ?? "");
7522
+ if (!match) return null;
7523
+ const value = Number(match[1]);
7524
+ return Number.isFinite(value) ? value : null;
7525
+ }
7526
+ __name(parseIoregGpuUtil, "parseIoregGpuUtil");
6370
7527
 
6371
7528
  // src/core/screen/capture-backend.ts
6372
7529
  var HEADLESS_BROWSER_CAPTURE_AVAILABILITY = Object.freeze({
@@ -6397,7 +7554,7 @@ __name(safeAvailable, "safeAvailable");
6397
7554
  var MAX_CAPABILITY_TAGS = FLEET_DEFAULT_MAX_CAPABILITY_TAGS;
6398
7555
  var MAX_CAPABILITY_TAG_LENGTH = FLEET_DEFAULT_MAX_CAPABILITY_TAG_LENGTH;
6399
7556
  var MAX_PLATFORM_LENGTH = FLEET_MAX_PLATFORM_LENGTH;
6400
- var MAX_VERSION_LENGTH = FLEET_MAX_VERSION_LENGTH;
7557
+ var MAX_VERSION_LENGTH2 = FLEET_MAX_VERSION_LENGTH;
6401
7558
  var MIN_CREDENTIAL_LENGTH = FLEET_CREDENTIAL_MIN_LENGTH;
6402
7559
  var MAX_CREDENTIAL_LENGTH = FLEET_CREDENTIAL_MAX_LENGTH;
6403
7560
  var DEFAULT_HEARTBEAT_INTERVAL_MS = 6e4;
@@ -6526,6 +7683,20 @@ function clampWorkspaceGcPolicy(input) {
6526
7683
  };
6527
7684
  }
6528
7685
  __name(clampWorkspaceGcPolicy, "clampWorkspaceGcPolicy");
7686
+ var DEFAULT_NODE_EXECUTION_POLICY = Object.freeze({
7687
+ allowSkipPermissions: false,
7688
+ allowShellCommands: false,
7689
+ restrictedMode: true
7690
+ });
7691
+ function resolveNodeExecutionPolicy(input) {
7692
+ if (!input || typeof input !== "object") return { ...DEFAULT_NODE_EXECUTION_POLICY };
7693
+ return {
7694
+ allowSkipPermissions: input.allowSkipPermissions === true,
7695
+ allowShellCommands: input.allowShellCommands === true,
7696
+ restrictedMode: input.restrictedMode !== false
7697
+ };
7698
+ }
7699
+ __name(resolveNodeExecutionPolicy, "resolveNodeExecutionPolicy");
6529
7700
  function redactConfig(config) {
6530
7701
  const redacted = {
6531
7702
  apiUrl: config.apiUrl,
@@ -6537,7 +7708,8 @@ function redactConfig(config) {
6537
7708
  enrolledAt: config.enrolledAt,
6538
7709
  hasSecret: typeof config.secret === "string" && config.secret.length > 0,
6539
7710
  secretStorage: config.secretStorage ?? "file",
6540
- paused: config.paused === true
7711
+ paused: config.paused === true,
7712
+ executionPolicy: resolveNodeExecutionPolicy(config.executionPolicy)
6541
7713
  };
6542
7714
  if (config.capabilitySelection !== void 0) {
6543
7715
  redacted.capabilitySelection = [...config.capabilitySelection];
@@ -6697,7 +7869,10 @@ async function detectCapabilities(runner, environment) {
6697
7869
  // SAME rule the lane used, and the two cannot disagree. `input` is
6698
7870
  // NOT advertised: nothing on this node injects input yet.
6699
7871
  environment.attended === true ? FLEET_ATTENDED_CAPABILITY : null,
6700
- environment.attended === true && isScreenCaptureAvailable(environment, environment.captureBackends) ? FLEET_SCREEN_CAPABILITY : null
7872
+ environment.attended === true && isScreenCaptureAvailable(environment, environment.captureBackends) ? FLEET_SCREEN_CAPABILITY : null,
7873
+ // Fleet node link (FL-02): the module tags, LAST, so a node that already
7874
+ // advertised everything above keeps advertising exactly that.
7875
+ ...environment.linkModuleTags ?? []
6701
7876
  ]);
6702
7877
  }
6703
7878
  __name(detectCapabilities, "detectCapabilities");
@@ -6712,7 +7887,10 @@ var NON_SELECTABLE_TAGS = /* @__PURE__ */ new Set([
6712
7887
  // A capability selection written before these tags existed must not
6713
7888
  // silently withhold a live view its owner just switched on.
6714
7889
  FLEET_ATTENDED_CAPABILITY,
6715
- FLEET_SCREEN_CAPABILITY
7890
+ FLEET_SCREEN_CAPABILITY,
7891
+ // Fleet node link (FL-02): `managed` exists only under `--manage`, which IS
7892
+ // the opt-in — a selection written before the tag existed must not withhold it.
7893
+ FLEET_MANAGED_CAPABILITY
6716
7894
  ]);
6717
7895
  function isAlwaysAdvertisedCapability(tag) {
6718
7896
  return isIdentityCapability(tag) || NON_SELECTABLE_TAGS.has(tag);
@@ -6727,10 +7905,11 @@ function applyCapabilitySelection(detected, selection) {
6727
7905
  }
6728
7906
  __name(applyCapabilitySelection, "applyCapabilitySelection");
6729
7907
  async function describeSelf(runner, environment, version, selection, telemetry = {}) {
7908
+ const healthReading = resolveTelemetry(telemetry.health);
6730
7909
  const detected = await detectCapabilities(runner, environment);
6731
7910
  const description = {
6732
7911
  platform: describePlatform(environment),
6733
- version: version.slice(0, MAX_VERSION_LENGTH),
7912
+ version: version.slice(0, MAX_VERSION_LENGTH2),
6734
7913
  capabilities: applyCapabilitySelection(detected, selection)
6735
7914
  };
6736
7915
  const cliVersion = await resolveTelemetry(telemetry.cliVersion);
@@ -6782,6 +7961,19 @@ async function describeSelf(runner, environment, version, selection, telemetry =
6782
7961
  description.maxCpuPercent = typeof limits.maxCpuPercent === "number" ? limits.maxCpuPercent : null;
6783
7962
  description.maxMemoryMb = typeof limits.maxMemoryMb === "number" ? limits.maxMemoryMb : null;
6784
7963
  }
7964
+ const health2 = sanitizeFleetNodeHealthSnapshot(await healthReading);
7965
+ if (health2) {
7966
+ description.health = health2;
7967
+ }
7968
+ const consents = await resolveTelemetry(telemetry.consents);
7969
+ if (consents) {
7970
+ description.consents = {
7971
+ work: consents.work === true,
7972
+ attend: consents.attend === true,
7973
+ manage: consents.manage === true,
7974
+ usageScan: consents.usageScan === true
7975
+ };
7976
+ }
6785
7977
  return description;
6786
7978
  }
6787
7979
  __name(describeSelf, "describeSelf");
@@ -6877,6 +8069,9 @@ function parseConfig(raw) {
6877
8069
  if (typeof candidate.workspaceRoot === "string" && ABSOLUTE_PATH.test(candidate.workspaceRoot.trim())) {
6878
8070
  config.workspaceRoot = candidate.workspaceRoot.trim();
6879
8071
  }
8072
+ if (candidate.executionPolicy && typeof candidate.executionPolicy === "object") {
8073
+ config.executionPolicy = resolveNodeExecutionPolicy(candidate.executionPolicy);
8074
+ }
6880
8075
  if (candidate.unsafe && typeof candidate.unsafe === "object" && typeof candidate.unsafe.since === "string" && Number.isFinite(Date.parse(candidate.unsafe.since)) && typeof candidate.unsafe.reason === "string" && candidate.unsafe.reason.trim()) {
6881
8076
  config.unsafe = {
6882
8077
  since: new Date(candidate.unsafe.since).toISOString(),
@@ -7313,6 +8508,17 @@ function selfDescription(source) {
7313
8508
  if (source.maxMemoryMb !== void 0) {
7314
8509
  out.maxMemoryMb = source.maxMemoryMb;
7315
8510
  }
8511
+ if (source.health !== void 0) {
8512
+ out.health = source.health;
8513
+ }
8514
+ if (source.consents !== void 0) {
8515
+ out.consents = {
8516
+ work: source.consents.work === true,
8517
+ attend: source.consents.attend === true,
8518
+ manage: source.consents.manage === true,
8519
+ usageScan: source.consents.usageScan === true
8520
+ };
8521
+ }
7316
8522
  return out;
7317
8523
  }
7318
8524
  __name(selfDescription, "selfDescription");
@@ -7381,7 +8587,18 @@ var systemScheduler = {
7381
8587
  clearTimeout: /* @__PURE__ */ __name((handle) => clearTimeout(handle), "clearTimeout")
7382
8588
  };
7383
8589
  var OPTIONAL_DESCRIPTION_FIELD_TIERS = [
7384
- // NEWEST first. Node lifecycle + remote node limits (self-build slices
8590
+ // NEWEST first. Computer health (FL-03): the health snapshot. Its own
8591
+ // tier, so a platform that predates it costs exactly this one field —
8592
+ // the consents, pinned CLI versions and enforced limits below keep
8593
+ // flowing (FR-3, U9). Newer than the consents: FL-02 reached `develop`
8594
+ // first, so a platform that knows the consents but not the snapshot
8595
+ // exists, and the reverse never will.
8596
+ ["health"],
8597
+ // Fleet node link (FL-02): the consents. A platform that predates them
8598
+ // answers 400; the node strips this tier, keeps beating with everything
8599
+ // else, and does not open the link (spec U11).
8600
+ ["consents"],
8601
+ // Then Node lifecycle + remote node limits (self-build slices
7385
8602
  // AR, AS). Tiered (review): `cliVersions` goes out on EVERY beat of a
7386
8603
  // node with a pinned CLI, so an upgraded node talking to a platform that
7387
8604
  // predates it would otherwise drop the worker state and housekeeping
@@ -7446,6 +8663,12 @@ var HeartbeatLoop = class {
7446
8663
  * will never accept them.
7447
8664
  */
7448
8665
  strippedTiers = 0;
8666
+ /**
8667
+ * Fleet node link (FL-02): the platform refused more than the legacy
8668
+ * {@link FLEET_LEGACY_MAX_CAPABILITY_TAGS} tags; beats carry the first
8669
+ * sixteen from then on. Process-scoped for the same reason as the tiers.
8670
+ */
8671
+ legacyTagCap = false;
7449
8672
  state = {
7450
8673
  state: "idle",
7451
8674
  lastHeartbeatAt: null,
@@ -7468,6 +8691,17 @@ var HeartbeatLoop = class {
7468
8691
  getState() {
7469
8692
  return { ...this.state };
7470
8693
  }
8694
+ /**
8695
+ * Whether the platform has NOT proven it predates `field` — i.e. the beat
8696
+ * still carries it. The node link opens only while `consents` is still
8697
+ * accepted: a platform that rejected the field cannot authorise a channel
8698
+ * by consent, so this node does not ask it to (spec U11).
8699
+ */
8700
+ acceptsField(field) {
8701
+ return !OPTIONAL_DESCRIPTION_FIELD_TIERS.slice(0, this.strippedTiers).some(
8702
+ (tier) => tier.includes(field)
8703
+ );
8704
+ }
7471
8705
  onChange(listener) {
7472
8706
  this.listeners.add(listener);
7473
8707
  return () => {
@@ -7545,22 +8779,36 @@ var HeartbeatLoop = class {
7545
8779
  */
7546
8780
  async beat(description) {
7547
8781
  const credential = { nodeId: this.options.nodeId, secret: this.options.secret };
7548
- let sent = stripTiers(description, this.strippedTiers);
8782
+ const shape = /* @__PURE__ */ __name((tiers) => capTags(stripTiers(description, tiers), this.legacyTagCap), "shape");
8783
+ let sent = shape(this.strippedTiers);
7549
8784
  try {
7550
8785
  return await this.options.client.heartbeat({ ...credential, ...sent });
7551
8786
  } catch (error) {
7552
8787
  if (!isFieldRejection(error)) throw error;
7553
8788
  let lastError = error;
8789
+ if (!this.legacyTagCap && (description.capabilities?.length ?? 0) > FLEET_LEGACY_MAX_CAPABILITY_TAGS) {
8790
+ sent = capTags(stripTiers(description, this.strippedTiers), true);
8791
+ try {
8792
+ const result = await this.options.client.heartbeat({ ...credential, ...sent });
8793
+ this.legacyTagCap = true;
8794
+ this.options.logger?.warn(
8795
+ `Platform refused more than ${FLEET_LEGACY_MAX_CAPABILITY_TAGS} capability tags; reporting the first ${FLEET_LEGACY_MAX_CAPABILITY_TAGS} until this node restarts.`
8796
+ );
8797
+ return result;
8798
+ } catch (retryError) {
8799
+ if (!isFieldRejection(retryError)) throw retryError;
8800
+ lastError = retryError;
8801
+ this.legacyTagCap = true;
8802
+ }
8803
+ }
7554
8804
  for (let tiers = this.strippedTiers + 1; tiers <= OPTIONAL_DESCRIPTION_FIELD_TIERS.length; tiers += 1) {
7555
- const candidate = stripTiers(description, tiers);
8805
+ const candidate = shape(tiers);
7556
8806
  if (Object.keys(candidate).length === Object.keys(sent).length) continue;
7557
8807
  sent = candidate;
7558
8808
  try {
7559
8809
  const result = await this.options.client.heartbeat({ ...credential, ...candidate });
7560
8810
  this.strippedTiers = tiers;
7561
- this.options.logger?.warn(
7562
- tiers >= OPTIONAL_DESCRIPTION_FIELD_TIERS.length ? "Platform rejected the optional self-description fields (worker state, housekeeping); it predates them. Reporting liveness only until this node restarts." : "Platform rejected the newest optional self-description fields (pinned CLI versions, enforced limits); it predates them. Still reporting worker state and housekeeping; the newer fields stay off until this node restarts."
7563
- );
8811
+ this.options.logger?.warn(describeStrippedTiers(tiers));
7564
8812
  return result;
7565
8813
  } catch (retryError) {
7566
8814
  if (!isFieldRejection(retryError)) throw retryError;
@@ -7631,6 +8879,25 @@ var HeartbeatLoop = class {
7631
8879
  }
7632
8880
  }
7633
8881
  };
8882
+ function capTags(description, cap) {
8883
+ if (!cap || !Array.isArray(description.capabilities)) return description;
8884
+ if (description.capabilities.length <= FLEET_LEGACY_MAX_CAPABILITY_TAGS) return description;
8885
+ return { ...description, capabilities: description.capabilities.slice(0, FLEET_LEGACY_MAX_CAPABILITY_TAGS) };
8886
+ }
8887
+ __name(capTags, "capTags");
8888
+ function describeStrippedTiers(tiers) {
8889
+ if (tiers >= OPTIONAL_DESCRIPTION_FIELD_TIERS.length) {
8890
+ return "Platform rejected the optional self-description fields (worker state, housekeeping); it predates them. Reporting liveness only until this node restarts.";
8891
+ }
8892
+ if (tiers >= 3) {
8893
+ return "Platform rejected the newest optional self-description fields (pinned CLI versions, enforced limits); it predates them. Still reporting worker state and housekeeping; the newer fields stay off until this node restarts.";
8894
+ }
8895
+ if (tiers >= 2) {
8896
+ return "Platform rejected the consents field; it predates the node link. Still reporting pinned CLI versions, enforced limits, worker state and housekeeping; the node link stays closed and the health snapshot off until this node restarts.";
8897
+ }
8898
+ return "Platform rejected the health snapshot; it predates it. Still reporting everything else; health stays off until this node restarts.";
8899
+ }
8900
+ __name(describeStrippedTiers, "describeStrippedTiers");
7634
8901
  function stripTiers(description, tiers) {
7635
8902
  const out = {};
7636
8903
  const dropped = new Set(OPTIONAL_DESCRIPTION_FIELD_TIERS.slice(0, tiers).flat());
@@ -7803,6 +9070,8 @@ __name(cacheProbe, "cacheProbe");
7803
9070
  // src/core/executors/model-cli.ts
7804
9071
  var MODEL_CLI_EMPTY_MCP_CONFIG = `${JSON.stringify({ mcpServers: {} }, null, 2)}
7805
9072
  `;
9073
+ var MODEL_CLI_FLEET_SETTINGS = `${JSON.stringify({ disableAllHooks: true }, null, 2)}
9074
+ `;
7806
9075
  var CODEX_SETTING_VALUE = /^[A-Za-z0-9._-]{1,64}$/;
7807
9076
  var TOML_STRING_ASSIGNMENT = /^([A-Za-z0-9_-]+)[ \t]*=[ \t]*(?:"([^"\\]*)"|'([^']*)')(?:[ \t]+#.*|#.*)?$/;
7808
9077
  var TOML_MULTILINE_VALUE_START = /^[^=#]*=[ \t]*("""|''')/;
@@ -8018,6 +9287,10 @@ function buildModelCliCommand(input) {
8018
9287
  if (execution.effort && !omitted.has("--effort")) args.push("--effort", execution.effort);
8019
9288
  if (budget && !omitted.has("--max-budget-usd")) args.push("--max-budget-usd", budget);
8020
9289
  if (execution.skipPermissions === true) args.push("--dangerously-skip-permissions");
9290
+ if (input.settingsPath !== void 0) {
9291
+ args.push("--setting-sources", "user", "--settings", quoteShellPath(input.settingsPath, platform));
9292
+ }
9293
+ if (input.restricted === true && execution.skipPermissions !== true) args.push("--restricted");
8021
9294
  if (mcp) {
8022
9295
  args.push(
8023
9296
  "--mcp-config",
@@ -8082,6 +9355,16 @@ function assertMountGrantsInCommand(input) {
8082
9355
  __name(assertMountGrantsInCommand, "assertMountGrantsInCommand");
8083
9356
  var STRICT_MCP_CONFIG_IN_COMMAND = / --mcp-config "[^"]+" --strict-mcp-config /;
8084
9357
  var CODEX_IGNORE_USER_CONFIG_IN_COMMAND = / exec --ignore-user-config /;
9358
+ var SETTINGS_ISOLATION_IN_COMMAND = / --setting-sources user --settings "[^"]+" /;
9359
+ function assertSettingsIsolationInCommand(input) {
9360
+ if (input.execution.provider !== "claude-code") return;
9361
+ if (!SETTINGS_ISOLATION_IN_COMMAND.test(input.command)) {
9362
+ throw new ModelCliCommandError(
9363
+ "The claude-code command line does not carry --setting-sources user --settings <file>, so the run would load the repository's own .claude/settings.json (its hooks, env and apiKeyHelper) and run hooks"
9364
+ );
9365
+ }
9366
+ }
9367
+ __name(assertSettingsIsolationInCommand, "assertSettingsIsolationInCommand");
8085
9368
  function assertMcpIsolationInCommand(input) {
8086
9369
  if (input.execution.provider === "codex") {
8087
9370
  if (!CODEX_IGNORE_USER_CONFIG_IN_COMMAND.test(input.command)) {
@@ -8417,9 +9700,27 @@ var MODEL_CLI_REQUIRED_FLAGS = {
8417
9700
  // since the MCP isolation fix (#2584): the empty strict config, or the
8418
9701
  // bridge's. A CLI without them cannot run fleet work at all, so `doctor`
8419
9702
  // must say INCOMPATIBLE, not "runs that need them will fail".
8420
- "claude-code": ["-p", "--output-format", "--permission-mode", "--mcp-config", "--strict-mcp-config"],
9703
+ //
9704
+ // `--setting-sources` + `--settings` ride EVERY fleet Claude Code run since
9705
+ // control 17 (2026-10-18): the worktree's own `.claude/settings*.json` is
9706
+ // never loaded and every hook is off. A CLI without them cannot keep a
9707
+ // repository's hooks from running as the machine owner, so it is
9708
+ // INCOMPATIBLE rather than "runs go without".
9709
+ "claude-code": [
9710
+ "-p",
9711
+ "--output-format",
9712
+ "--permission-mode",
9713
+ "--mcp-config",
9714
+ "--strict-mcp-config",
9715
+ "--setting-sources",
9716
+ "--settings"
9717
+ ],
8421
9718
  codex: ["--json", "--sandbox", "-C", "--ignore-user-config"]
8422
9719
  };
9720
+ var MODEL_CLI_HARDENING_FLAGS = {
9721
+ "claude-code": ["--restricted"],
9722
+ codex: []
9723
+ };
8423
9724
  var MODEL_CLI_CONDITIONAL_FLAGS = {
8424
9725
  "claude-code": [
8425
9726
  "--model",
@@ -8553,6 +9854,7 @@ function judgeModelCliHelp(provider, executable, version, help) {
8553
9854
  missingRequiredFlags: [],
8554
9855
  missingConditionalFlags: [],
8555
9856
  unsupportedOptionalFlags: [],
9857
+ missingHardeningFlags: [],
8556
9858
  note: null
8557
9859
  };
8558
9860
  if (help === null) {
@@ -8571,7 +9873,8 @@ function judgeModelCliHelp(provider, executable, version, help) {
8571
9873
  supportedFlags: advertised,
8572
9874
  missingRequiredFlags: missing(required),
8573
9875
  missingConditionalFlags: missing(MODEL_CLI_CONDITIONAL_FLAGS[provider]),
8574
- unsupportedOptionalFlags: missing(MODEL_CLI_OPTIONAL_FLAGS[provider])
9876
+ unsupportedOptionalFlags: missing(MODEL_CLI_OPTIONAL_FLAGS[provider]),
9877
+ missingHardeningFlags: missing(MODEL_CLI_HARDENING_FLAGS[provider])
8575
9878
  };
8576
9879
  }
8577
9880
  __name(judgeModelCliHelp, "judgeModelCliHelp");
@@ -8667,11 +9970,16 @@ __name(formatBytes, "formatBytes");
8667
9970
  var import_node_fs = require("node:fs");
8668
9971
  var import_node_path = require("node:path");
8669
9972
  async function measureWorkspaceFreeBytes(probe, rootPath) {
9973
+ const candidate = await nearestExistingPath(rootPath);
9974
+ return candidate === null ? null : detectDiskFreeBytes(probe, candidate);
9975
+ }
9976
+ __name(measureWorkspaceFreeBytes, "measureWorkspaceFreeBytes");
9977
+ async function nearestExistingPath(rootPath) {
8670
9978
  let candidate = rootPath;
8671
9979
  for (; ; ) {
8672
9980
  try {
8673
9981
  await import_node_fs.promises.lstat(candidate);
8674
- break;
9982
+ return candidate;
8675
9983
  } catch (error) {
8676
9984
  if (error.code !== "ENOENT") {
8677
9985
  return null;
@@ -8683,12 +9991,12 @@ async function measureWorkspaceFreeBytes(probe, rootPath) {
8683
9991
  candidate = parent;
8684
9992
  }
8685
9993
  }
8686
- return detectDiskFreeBytes(probe, candidate);
8687
9994
  }
8688
- __name(measureWorkspaceFreeBytes, "measureWorkspaceFreeBytes");
9995
+ __name(nearestExistingPath, "nearestExistingPath");
8689
9996
 
8690
9997
  // src/core/worker-loop.ts
8691
9998
  var DEFAULT_IDLE_POLL_MS = 5e3;
9999
+ var MODEL_THROTTLE_SYNC_GRACE_MS = 12e4;
8692
10000
  var WORKER_BACKOFF_BASE_MS = 1e3;
8693
10001
  var WORKER_BACKOFF_MAX_MS = 6e4;
8694
10002
  var MIN_KEEPALIVE_MS = 5e3;
@@ -8787,6 +10095,8 @@ var WorkerLoop = class {
8787
10095
  monotonicNow;
8788
10096
  leasePollDrainTimeoutMs;
8789
10097
  publishFenceMarginMs;
10098
+ /** Fleet usage limits: provider → the throttle in force (expiry checked on read). */
10099
+ modelThrottles = /* @__PURE__ */ new Map();
8790
10100
  running = false;
8791
10101
  stopping = false;
8792
10102
  paused = false;
@@ -8846,7 +10156,118 @@ var WorkerLoop = class {
8846
10156
  return [...this.executors.keys()];
8847
10157
  }
8848
10158
  getState() {
8849
- return { ...this.state, activeJobIds: [...this.state.activeJobIds] };
10159
+ const modelThrottles = this.activeModelThrottles();
10160
+ return {
10161
+ ...this.state,
10162
+ activeJobIds: [...this.state.activeJobIds],
10163
+ ...modelThrottles.length > 0 ? { modelThrottles } : {}
10164
+ };
10165
+ }
10166
+ /**
10167
+ * Fleet usage limits — record that `limit.provider` is out of quota on this
10168
+ * machine until `limit.retryAfter`. From the next poll the loop leases no
10169
+ * `agent-task` for that CLI (all of `agent-task` when every CLI this node
10170
+ * has is out), and refuses — without starting it — any such job the
10171
+ * platform still hands it. Other job kinds keep leasing. A later reset
10172
+ * replaces an earlier one; an earlier one never shortens a later one.
10173
+ */
10174
+ noteModelUsageLimit(limit) {
10175
+ const provider = typeof limit.provider === "string" ? limit.provider.trim() : "";
10176
+ const until = Date.parse(limit.retryAfter);
10177
+ if (!provider || !Number.isFinite(until) || until <= this.now()) return;
10178
+ const previous = this.modelThrottles.get(provider);
10179
+ if (previous && previous.until >= until) return;
10180
+ this.modelThrottles.set(provider, { until, window: limit.window, reason: limit.message, setAt: this.now() });
10181
+ if (!previous || previous.until <= this.now()) {
10182
+ this.options.logger?.warn(
10183
+ `${describeFleetModelProvider(provider)} hit its ${describeFleetModelUsageLimitWindow(limit.window)} on this node \u2014 not leasing ${provider} agent-task jobs until ${new Date(until).toISOString()} (other work still runs)`
10184
+ );
10185
+ }
10186
+ this.patch({});
10187
+ }
10188
+ /**
10189
+ * Fleet usage limits — reconcile with the platform's view of this node's
10190
+ * throttles, as carried on every heartbeat response (`node.modelThrottles`).
10191
+ *
10192
+ * The platform is authoritative both ways:
10193
+ * - a throttle it holds that this loop does not (a restarted daemon, a
10194
+ * limit another path recorded) is ADOPTED, so the node stops offering
10195
+ * itself for that CLI and says so on its next beat;
10196
+ * - a throttle this loop holds that the platform no longer does — the
10197
+ * owner pressed "clear limit now" — is LIFTED, but only once it is older
10198
+ * than `graceMs`: a heartbeat answered just before the platform recorded
10199
+ * the limit this node just reported must not undo it.
10200
+ * `null` / absent (an API older than the field) changes nothing.
10201
+ */
10202
+ syncModelThrottles(server, graceMs = MODEL_THROTTLE_SYNC_GRACE_MS) {
10203
+ if (!Array.isArray(server)) return;
10204
+ const now = this.now();
10205
+ const held = /* @__PURE__ */ new Set();
10206
+ let changed = false;
10207
+ for (const entry of server) {
10208
+ const provider = typeof entry?.provider === "string" ? entry.provider.trim() : "";
10209
+ const until = typeof entry?.until === "string" ? Date.parse(entry.until) : Number.NaN;
10210
+ if (!provider || !Number.isFinite(until) || until <= now) continue;
10211
+ held.add(provider);
10212
+ const local = this.modelThrottles.get(provider);
10213
+ if (local && local.until >= until) continue;
10214
+ const window = typeof entry.window === "string" ? entry.window : "usage";
10215
+ const reason = typeof entry.reason === "string" ? entry.reason : `${provider} usage limit reached`;
10216
+ this.modelThrottles.set(provider, { until, window, reason, setAt: local?.setAt ?? now });
10217
+ changed = true;
10218
+ if (!local) {
10219
+ this.options.logger?.info(
10220
+ `${describeFleetModelProvider(provider)} is out of quota on this node per the platform \u2014 not leasing ${provider} agent-task jobs until ${new Date(until).toISOString()}`
10221
+ );
10222
+ }
10223
+ }
10224
+ for (const [provider, local] of this.modelThrottles) {
10225
+ if (held.has(provider) || now - local.setAt < graceMs) continue;
10226
+ this.modelThrottles.delete(provider);
10227
+ changed = true;
10228
+ this.options.logger?.info(
10229
+ `${describeFleetModelProvider(provider)} usage limit was cleared on the platform \u2014 leasing ${provider} agent-task jobs again`
10230
+ );
10231
+ }
10232
+ if (changed) {
10233
+ this.patch({});
10234
+ this.pollSoon();
10235
+ }
10236
+ }
10237
+ /** The usage-limit throttles still in force; expired ones are dropped (and logged) here. */
10238
+ activeModelThrottles() {
10239
+ const now = this.now();
10240
+ const active = [];
10241
+ for (const [provider, throttle] of this.modelThrottles) {
10242
+ if (throttle.until <= now) {
10243
+ this.modelThrottles.delete(provider);
10244
+ this.options.logger?.info(
10245
+ `${describeFleetModelProvider(provider)} usage limit has reset \u2014 leasing ${provider} agent-task jobs again`
10246
+ );
10247
+ continue;
10248
+ }
10249
+ active.push({
10250
+ provider,
10251
+ window: throttle.window,
10252
+ until: new Date(throttle.until).toISOString(),
10253
+ reason: throttle.reason
10254
+ });
10255
+ }
10256
+ return active.sort((a, b) => a.provider.localeCompare(b.provider));
10257
+ }
10258
+ /** True when every model CLI this node can run an agent-task with is out of quota. */
10259
+ allAgentCliThrottled() {
10260
+ const active = this.activeModelThrottles();
10261
+ if (active.length === 0) return false;
10262
+ let providers = [];
10263
+ try {
10264
+ providers = this.options.modelProviders?.() ?? [];
10265
+ } catch {
10266
+ providers = [];
10267
+ }
10268
+ if (providers.length === 0) return true;
10269
+ const out = new Set(active.map((throttle) => throttle.provider));
10270
+ return providers.every((provider) => out.has(provider));
8850
10271
  }
8851
10272
  onChange(listener) {
8852
10273
  this.listeners.add(listener);
@@ -9124,8 +10545,10 @@ var WorkerLoop = class {
9124
10545
  };
9125
10546
  if (this.options.capabilities) request.capabilities = this.options.capabilities;
9126
10547
  if (this.options.kinds && this.options.kinds.length > 0) request.kinds = [...this.options.kinds];
9127
- if (this.options.excludeKinds && this.options.excludeKinds.length > 0) {
9128
- request.excludeKinds = [...this.options.excludeKinds];
10548
+ const excludeKinds = new Set(this.options.excludeKinds ?? []);
10549
+ if (this.allAgentCliThrottled()) excludeKinds.add("agent-task");
10550
+ if (excludeKinds.size > 0) {
10551
+ request.excludeKinds = [...excludeKinds];
9129
10552
  }
9130
10553
  if (this.options.client.leaseOutcome) {
9131
10554
  const outcome = await this.options.client.leaseOutcome(request, pollController.signal);
@@ -9362,6 +10785,14 @@ var WorkerLoop = class {
9362
10785
  );
9363
10786
  return;
9364
10787
  }
10788
+ const throttled = this.throttledResultFor(job);
10789
+ if (throttled) {
10790
+ this.options.logger?.info(
10791
+ `Fleet job ${job.id} not started: ${String(throttled.failureReason)} \u2014 handed back for the platform to park`
10792
+ );
10793
+ await this.report(job.id, { success: true, result: throttled }, generation, run);
10794
+ return;
10795
+ }
9365
10796
  const keepAlive = this.startKeepAlive(run);
9366
10797
  let successAccepted = false;
9367
10798
  try {
@@ -9370,6 +10801,7 @@ var WorkerLoop = class {
9370
10801
  );
9371
10802
  const result = await executor(job, signal, keepAlive.handle);
9372
10803
  throwIfJobAborted(signal);
10804
+ this.noteUsageLimitResult(result);
9373
10805
  if (this.settleDeferred(job.id, keepAlive.deferral())) return;
9374
10806
  const accepted = await this.report(
9375
10807
  job.id,
@@ -9404,6 +10836,57 @@ var WorkerLoop = class {
9404
10836
  keepAlive.stop();
9405
10837
  }
9406
10838
  }
10839
+ /** A result that says its CLI hit a usage limit throttles that CLI here. */
10840
+ noteUsageLimitResult(result) {
10841
+ if (!result || result.failureKind !== FLEET_JOB_FAILURE_KIND_MODEL_USAGE_LIMIT) return;
10842
+ const limit = result.modelUsageLimit;
10843
+ if (!limit || typeof limit.provider !== "string" || typeof limit.retryAfter !== "string") return;
10844
+ this.noteModelUsageLimit({
10845
+ provider: limit.provider,
10846
+ window: limit.window ?? "usage",
10847
+ retryAfter: limit.retryAfter,
10848
+ message: typeof limit.message === "string" ? limit.message : `${limit.provider} usage limit reached`
10849
+ });
10850
+ }
10851
+ /**
10852
+ * The usage-limit outcome for an `agent-task` whose CLI is out of quota on
10853
+ * this node, or null. Shaped like a real run's result (model block
10854
+ * included) so the platform — current or older — reads it the same way.
10855
+ */
10856
+ throttledResultFor(job) {
10857
+ if (job.kind !== "agent-task") return null;
10858
+ const payload = job.payload ?? {};
10859
+ const execution = payload.execution;
10860
+ const provider = typeof execution?.provider === "string" ? execution.provider : null;
10861
+ if (!provider) return null;
10862
+ const throttle = this.activeModelThrottles().find((entry) => entry.provider === provider);
10863
+ if (!throttle) return null;
10864
+ const failureReason = `${provider} is out of quota on this node until ${throttle.until} (${describeFleetModelUsageLimitWindow(throttle.window)}) \u2014 not started`;
10865
+ return {
10866
+ status: "failed",
10867
+ taskId: typeof payload.taskId === "string" ? payload.taskId : "",
10868
+ runId: typeof payload.runId === "string" ? payload.runId : null,
10869
+ workspace: null,
10870
+ steps: [],
10871
+ model: {
10872
+ provider,
10873
+ status: "failed",
10874
+ exitCode: null,
10875
+ durationMs: 0,
10876
+ summary: throttle.reason
10877
+ },
10878
+ gateStatus: "none",
10879
+ failureReason,
10880
+ failureKind: FLEET_JOB_FAILURE_KIND_MODEL_USAGE_LIMIT,
10881
+ modelUsageLimit: {
10882
+ provider,
10883
+ window: throttle.window,
10884
+ resetsAt: null,
10885
+ retryAfter: throttle.until,
10886
+ message: throttle.reason
10887
+ }
10888
+ };
10889
+ }
9407
10890
  /**
9408
10891
  * Record a job the executor handed back UNSETTLED, and report nothing.
9409
10892
  *
@@ -10106,17 +11589,22 @@ __name(keychainDisabledByEnv, "keychainDisabledByEnv");
10106
11589
  // src/node-io.ts
10107
11590
  function createCommandRunner() {
10108
11591
  return {
10109
- run: /* @__PURE__ */ __name((command, args) => new Promise((resolve9) => {
11592
+ run: /* @__PURE__ */ __name((command, args, options) => new Promise((resolve9) => {
11593
+ const timeout = typeof options?.timeoutMs === "number" && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : 1e4;
10110
11594
  (0, import_node_child_process.execFile)(
10111
11595
  command,
10112
11596
  args,
10113
- { shell: process.platform === "win32", windowsHide: true, timeout: 1e4 },
11597
+ { shell: process.platform === "win32", windowsHide: true, timeout },
10114
11598
  (error, stdout, stderr) => {
10115
11599
  const code = error ? error.code ?? 1 : 0;
10116
11600
  resolve9({
10117
11601
  code: typeof code === "number" ? code : 1,
10118
11602
  stdout: String(stdout),
10119
11603
  stderr: String(stderr),
11604
+ // A string code is a spawn failure (`ENOENT`: not installed),
11605
+ // not an exit status; `code` above still reads 1 for callers
11606
+ // that only ask "did it succeed".
11607
+ ...typeof code === "string" ? { spawnError: code } : {},
10120
11608
  // `execFile` sets `killed` when it terminated the child at
10121
11609
  // `timeout`; what the child printed until then is partial.
10122
11610
  timedOut: Boolean(error && error.killed)
@@ -10295,7 +11783,8 @@ function readCpuTicks() {
10295
11783
  }
10296
11784
  __name(readCpuTicks, "readCpuTicks");
10297
11785
  function createResourceProbe(options = {}) {
10298
- const windowMs = Math.max(options.sampleWindowMs ?? 0, 0);
11786
+ const everyWindowMs = Math.max(options.sampleWindowMs ?? 0, 0);
11787
+ const firstWindowMs = Math.max(options.firstSampleWindowMs ?? 0, 0);
10299
11788
  let previous = null;
10300
11789
  const measure = /* @__PURE__ */ __name((current, baseline) => {
10301
11790
  if (!baseline) return 0;
@@ -10308,6 +11797,7 @@ function createResourceProbe(options = {}) {
10308
11797
  return {
10309
11798
  sample: /* @__PURE__ */ __name(async () => {
10310
11799
  let baseline = previous;
11800
+ const windowMs = everyWindowMs > 0 ? everyWindowMs : previous === null ? firstWindowMs : 0;
10311
11801
  if (windowMs > 0) {
10312
11802
  baseline = readCpuTicks();
10313
11803
  await new Promise((resolve9) => setTimeout(resolve9, windowMs));
@@ -10336,10 +11826,51 @@ function createDiskProbe() {
10336
11826
  } catch {
10337
11827
  return null;
10338
11828
  }
10339
- }, "freeBytes")
11829
+ }, "freeBytes"),
11830
+ // Computer health (FL-03): the same `statfs`, read whole. Null on the
11831
+ // same failures, and on any figure that is not a sane byte count.
11832
+ volume: /* @__PURE__ */ __name(async (targetPath) => {
11833
+ try {
11834
+ const stats = await fsp.statfs(targetPath);
11835
+ const block = Number(stats.bsize);
11836
+ const reading = {
11837
+ totalBytes: Number(stats.blocks) * block,
11838
+ freeBytes: Number(stats.bfree) * block,
11839
+ availableBytes: Number(stats.bavail) * block
11840
+ };
11841
+ const sane = Object.values(reading).every((value) => Number.isFinite(value) && value >= 0);
11842
+ return sane && reading.totalBytes > 0 ? reading : null;
11843
+ } catch {
11844
+ return null;
11845
+ }
11846
+ }, "volume")
10340
11847
  };
10341
11848
  }
10342
11849
  __name(createDiskProbe, "createDiskProbe");
11850
+ function createHealthProbeIo(runner) {
11851
+ return {
11852
+ platform: process.platform,
11853
+ runner,
11854
+ // Its own sampler — see `createResourceProbe` on why it is never shared
11855
+ // with the admission gate — whose first reading is a real 250 ms one.
11856
+ resourceProbe: createResourceProbe({ firstSampleWindowMs: 250 }),
11857
+ host: {
11858
+ loadAvg1: /* @__PURE__ */ __name(() => os.loadavg()[0] ?? null, "loadAvg1"),
11859
+ uptimeSec: /* @__PURE__ */ __name(() => os.uptime(), "uptimeSec"),
11860
+ cores: /* @__PURE__ */ __name(() => typeof os.availableParallelism === "function" ? os.availableParallelism() : os.cpus().length, "cores")
11861
+ },
11862
+ disk: createDiskProbe(),
11863
+ pathExists: /* @__PURE__ */ __name(async (target) => {
11864
+ try {
11865
+ await fsp.access(target);
11866
+ return true;
11867
+ } catch {
11868
+ return false;
11869
+ }
11870
+ }, "pathExists")
11871
+ };
11872
+ }
11873
+ __name(createHealthProbeIo, "createHealthProbeIo");
10343
11874
  var systemFetch = /* @__PURE__ */ __name((url, init) => fetch(url, {
10344
11875
  method: init.method,
10345
11876
  headers: init.headers,
@@ -10809,6 +12340,27 @@ var FleetJobClient = class {
10809
12340
  expiresInSec: typeof payload.expiresInSec === "number" ? payload.expiresInSec : 60
10810
12341
  };
10811
12342
  }
12343
+ /**
12344
+ * Fleet node link (FL-02) — a single-use, five-minute token for this
12345
+ * machine's one outbound link socket, minted through the node credential.
12346
+ * Never logged. A platform that does not accept links answers 404, which
12347
+ * reaches the caller as an `invalid-request` error with `status: 404`.
12348
+ */
12349
+ async mintLinkToken() {
12350
+ const payload = await this.post("api/fleet/link-token", "link-token", {
12351
+ nodeId: this.nodeId,
12352
+ secret: this.secret
12353
+ });
12354
+ if (typeof payload?.token !== "string" || !payload.token || typeof payload.wsPath !== "string") {
12355
+ throw new FleetClientError("malformed", "Link token response did not contain a token");
12356
+ }
12357
+ this.logger?.protect(payload.token);
12358
+ return {
12359
+ token: payload.token,
12360
+ wsPath: payload.wsPath,
12361
+ expiresInSec: typeof payload.expiresInSec === "number" ? payload.expiresInSec : 300
12362
+ };
12363
+ }
10812
12364
  /** Report what the watched Agent's profile holds here (counts only — never a path, never a cookie). */
10813
12365
  async reportComputerProfile(sessionId, report) {
10814
12366
  const payload = await this.post(
@@ -10906,6 +12458,274 @@ function errorForJobStatus(status, operation) {
10906
12458
  }
10907
12459
  __name(errorForJobStatus, "errorForJobStatus");
10908
12460
 
12461
+ // src/core/health-probe.ts
12462
+ var BYTES_PER_MIB = 1024 * 1024;
12463
+ var DEFAULT_SLOW_FACT_TTL_MS = 5 * 6e4;
12464
+ var DEFAULT_SLOW_FACT_DEADLINE_MS = 15e3;
12465
+ var GPU_IDENTITY_TTL_MS = 10 * 6e4;
12466
+ var LINUX_REBOOT_REQUIRED_PATH = "/var/run/reboot-required";
12467
+ var LINUX_DEBIAN_RELEASE_PATH = "/etc/debian_version";
12468
+ var NEEDS_RESTARTING_REBOOT_PATTERN = /reboot is required/i;
12469
+ var WINDOWS_HEALTH_SCRIPT = [
12470
+ "$ErrorActionPreference = 'Stop'",
12471
+ "try {",
12472
+ " $pending = (Test-Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending') -or (Test-Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired')",
12473
+ ' "reboot=$pending"',
12474
+ "} catch {}",
12475
+ "try {",
12476
+ ` $failed = @(Get-CimInstance -ClassName Win32_Service -Filter "StartMode='Auto' AND State='Stopped' AND ExitCode<>0 AND ExitCode<>1077")`,
12477
+ ' "failed=$($failed.Count)"',
12478
+ "} catch {}",
12479
+ "try {",
12480
+ " $adapters = @(Get-CimInstance -ClassName Win32_VideoController)",
12481
+ ' "adapters=$($adapters.Count)"',
12482
+ ' foreach ($adapter in $adapters) { "gpu=$($adapter.Name)" }',
12483
+ "} catch {}"
12484
+ ].join("\n");
12485
+ function encodePowerShell(script) {
12486
+ return Buffer.from(script, "utf16le").toString("base64");
12487
+ }
12488
+ __name(encodePowerShell, "encodePowerShell");
12489
+ function parseWindowsHealth(stdout) {
12490
+ const facts = {};
12491
+ let adapterCount = null;
12492
+ const names = [];
12493
+ for (const raw of (stdout ?? "").split(/\r?\n/)) {
12494
+ const line = raw.trim();
12495
+ const eq = line.indexOf("=");
12496
+ if (eq <= 0) continue;
12497
+ const key = line.slice(0, eq);
12498
+ const value = line.slice(eq + 1).trim();
12499
+ if (key === "reboot" && /^(true|false)$/i.test(value)) facts.pendingReboot = /^true$/i.test(value);
12500
+ if (key === "failed" && /^\d+$/.test(value)) facts.failedServices = Number(value);
12501
+ if (key === "adapters" && /^\d+$/.test(value)) adapterCount = Number(value);
12502
+ if (key === "gpu" && value) names.push(value);
12503
+ }
12504
+ if (adapterCount !== null) facts.adapters = adapterCount > 0 ? names : [];
12505
+ return facts;
12506
+ }
12507
+ __name(parseWindowsHealth, "parseWindowsHealth");
12508
+ function countSystemdFailedUnits(stdout) {
12509
+ return (stdout ?? "").split(/\r?\n/).filter((line) => line.trim().length > 0).length;
12510
+ }
12511
+ __name(countSystemdFailedUnits, "countSystemdFailedUnits");
12512
+ function refreshInBackground(probe, ttlMs, now = Date.now) {
12513
+ let value = null;
12514
+ let answered = false;
12515
+ let expiresAt = Number.NEGATIVE_INFINITY;
12516
+ let inFlight = null;
12517
+ const refresh = /* @__PURE__ */ __name(() => {
12518
+ if (!inFlight) {
12519
+ inFlight = Promise.resolve().then(probe).catch(() => null).then((fresh) => {
12520
+ value = fresh;
12521
+ answered = true;
12522
+ expiresAt = now() + ttlMs;
12523
+ return fresh;
12524
+ }).finally(() => {
12525
+ inFlight = null;
12526
+ });
12527
+ }
12528
+ return inFlight;
12529
+ }, "refresh");
12530
+ return async () => {
12531
+ if (now() < expiresAt) return value;
12532
+ const pending = refresh();
12533
+ return answered ? value : pending;
12534
+ };
12535
+ }
12536
+ __name(refreshInBackground, "refreshInBackground");
12537
+ function withDeadline(work, ms) {
12538
+ return new Promise((resolve9) => {
12539
+ let settled = false;
12540
+ const timer = setTimeout(() => {
12541
+ if (settled) return;
12542
+ settled = true;
12543
+ resolve9(void 0);
12544
+ }, ms);
12545
+ timer.unref?.();
12546
+ const finish = /* @__PURE__ */ __name((value) => {
12547
+ if (settled) return;
12548
+ settled = true;
12549
+ clearTimeout(timer);
12550
+ resolve9(value);
12551
+ }, "finish");
12552
+ try {
12553
+ Promise.resolve(work()).then(finish, () => finish(void 0));
12554
+ } catch {
12555
+ finish(void 0);
12556
+ }
12557
+ });
12558
+ }
12559
+ __name(withDeadline, "withDeadline");
12560
+ function finiteOrNull(value) {
12561
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
12562
+ }
12563
+ __name(finiteOrNull, "finiteOrNull");
12564
+ function createHealthProbe(io, options) {
12565
+ const budget = options.timeoutMs ?? FLEET_HEALTH_PROBE_TIMEOUT_MS;
12566
+ const slowTtl = options.slowFactTtlMs ?? DEFAULT_SLOW_FACT_TTL_MS;
12567
+ const slowDeadline = options.slowFactDeadlineMs ?? DEFAULT_SLOW_FACT_DEADLINE_MS;
12568
+ const now = options.now ?? Date.now;
12569
+ const run = /* @__PURE__ */ __name(async (command, args, timeoutMs) => {
12570
+ try {
12571
+ return await io.runner.run(command, [...args], { timeoutMs });
12572
+ } catch {
12573
+ return null;
12574
+ }
12575
+ }, "run");
12576
+ const cpuAndMemory = /* @__PURE__ */ __name(async () => {
12577
+ const sample = await io.resourceProbe.sample();
12578
+ const out = {};
12579
+ const cpu = finiteOrNull(sample.cpuPercent);
12580
+ if (cpu !== null) out.cpuPercent = cpu;
12581
+ const total = finiteOrNull(sample.totalMemoryMb);
12582
+ const used = finiteOrNull(sample.usedMemoryMb);
12583
+ if (total !== null && total > 0) {
12584
+ out.memTotalMiB = Math.floor(total);
12585
+ if (used !== null && used >= 0) {
12586
+ out.memUsedMiB = Math.round(used);
12587
+ out.memPercent = used / total * 100;
12588
+ }
12589
+ }
12590
+ return out;
12591
+ }, "cpuAndMemory");
12592
+ const disk = /* @__PURE__ */ __name(async () => {
12593
+ const probe = io.disk;
12594
+ if (!probe?.volume) return {};
12595
+ const target = await nearestExistingPath(options.workspacePath);
12596
+ if (!target) return {};
12597
+ const reading = await probe.volume(target);
12598
+ if (!reading) return {};
12599
+ const used = Math.max(reading.totalBytes - reading.freeBytes, 0);
12600
+ const denominator = used + reading.availableBytes;
12601
+ const out = {
12602
+ diskFreeMiB: Math.floor(reading.availableBytes / BYTES_PER_MIB),
12603
+ diskTotalMiB: Math.floor(reading.totalBytes / BYTES_PER_MIB)
12604
+ };
12605
+ if (denominator > 0) out.diskPercent = used / denominator * 100;
12606
+ return out;
12607
+ }, "disk");
12608
+ const hostFacts = /* @__PURE__ */ __name(() => {
12609
+ const out = {};
12610
+ const cores = finiteOrNull(io.host.cores());
12611
+ if (cores !== null) out.cpuCores = cores;
12612
+ const uptime2 = finiteOrNull(io.host.uptimeSec());
12613
+ if (uptime2 !== null) out.uptimeSec = uptime2;
12614
+ if (io.platform !== "win32") {
12615
+ const load = finiteOrNull(io.host.loadAvg1());
12616
+ if (load !== null) out.loadAvg1 = load;
12617
+ }
12618
+ return out;
12619
+ }, "hostFacts");
12620
+ const windowsFacts = refreshInBackground(
12621
+ async () => {
12622
+ const result = await run(
12623
+ "powershell",
12624
+ ["-NoProfile", "-NonInteractive", "-EncodedCommand", encodePowerShell(WINDOWS_HEALTH_SCRIPT)],
12625
+ slowDeadline
12626
+ );
12627
+ return result && result.code === 0 && !result.timedOut ? parseWindowsHealth(result.stdout) : null;
12628
+ },
12629
+ slowTtl,
12630
+ now
12631
+ );
12632
+ const linuxPendingReboot = refreshInBackground(
12633
+ async () => {
12634
+ if (await io.pathExists(LINUX_REBOOT_REQUIRED_PATH)) return true;
12635
+ if (await io.pathExists(LINUX_DEBIAN_RELEASE_PATH)) return false;
12636
+ const result = await run("needs-restarting", ["-r"], slowDeadline);
12637
+ if (!result || result.timedOut || result.spawnError) return null;
12638
+ if (result.code === 0) return false;
12639
+ if (result.code === 1 && NEEDS_RESTARTING_REBOOT_PATTERN.test(result.stdout)) return true;
12640
+ return null;
12641
+ },
12642
+ slowTtl,
12643
+ now
12644
+ );
12645
+ const linuxFailedServices = refreshInBackground(
12646
+ async () => {
12647
+ const result = await run(
12648
+ "systemctl",
12649
+ ["list-units", "--state=failed", "--no-legend", "--plain", "--no-pager"],
12650
+ slowDeadline
12651
+ );
12652
+ return result && result.code === 0 && !result.timedOut ? countSystemdFailedUnits(result.stdout) : null;
12653
+ },
12654
+ slowTtl,
12655
+ now
12656
+ );
12657
+ const gpuIdentity = refreshInBackground(
12658
+ () => detectGpuWithEvidence(io.runner, io.platform),
12659
+ GPU_IDENTITY_TTL_MS,
12660
+ now
12661
+ );
12662
+ const pendingRebootAndServices = /* @__PURE__ */ __name(async () => {
12663
+ const out = {};
12664
+ if (io.platform === "win32") {
12665
+ const facts = await windowsFacts();
12666
+ if (facts?.pendingReboot !== void 0) out.pendingReboot = facts.pendingReboot;
12667
+ if (facts?.failedServices !== void 0) out.failedServices = facts.failedServices;
12668
+ return out;
12669
+ }
12670
+ if (io.platform === "linux") {
12671
+ const [reboot, failed] = await Promise.all([
12672
+ withDeadline(linuxPendingReboot, budget),
12673
+ withDeadline(linuxFailedServices, budget)
12674
+ ]);
12675
+ if (typeof reboot === "boolean") out.pendingReboot = reboot;
12676
+ if (typeof failed === "number") out.failedServices = failed;
12677
+ }
12678
+ return out;
12679
+ }, "pendingRebootAndServices");
12680
+ const gpu = /* @__PURE__ */ __name(async () => {
12681
+ const nvidia = await run("nvidia-smi", NVIDIA_SMI_HEALTH_ARGS, budget);
12682
+ if (nvidia && nvidia.code === 0 && !nvidia.timedOut) {
12683
+ const parsed = parseNvidiaSmiHealth(nvidia.stdout);
12684
+ if (parsed.length > 0) return { gpu: parsed.map((gpu2) => ({ vendor: "nvidia", ...gpu2 })) };
12685
+ }
12686
+ if (io.platform === "win32") {
12687
+ const facts = await windowsFacts();
12688
+ if (!facts || facts.adapters === void 0) return {};
12689
+ if (facts.adapters.length === 0) return { gpu: null };
12690
+ return { gpu: facts.adapters.map((name) => ({ vendor: classifyGpuVendor(name), name })) };
12691
+ }
12692
+ const identity = await gpuIdentity();
12693
+ if (!identity) return {};
12694
+ if (!identity.gpu) return identity.answered ? { gpu: null } : {};
12695
+ const found = { vendor: identity.gpu.vendor, name: identity.gpu.model };
12696
+ if (identity.gpu.vendor === "amd" && io.platform === "linux") {
12697
+ const rocm = await run("rocm-smi", ROCM_SMI_HEALTH_ARGS, budget);
12698
+ const usage = rocm && rocm.code === 0 && !rocm.timedOut ? parseRocmSmiHealth(rocm.stdout) : null;
12699
+ if (usage) Object.assign(found, usage);
12700
+ }
12701
+ if (identity.gpu.vendor === "apple" && io.platform === "darwin") {
12702
+ const ioreg = await run("ioreg", IOREG_GPU_ARGS, budget);
12703
+ const util = ioreg && ioreg.code === 0 && !ioreg.timedOut ? parseIoregGpuUtil(ioreg.stdout) : null;
12704
+ if (util !== null) found.utilPercent = util;
12705
+ }
12706
+ return { gpu: [found] };
12707
+ }, "gpu");
12708
+ return async () => {
12709
+ try {
12710
+ const parts = await Promise.all([
12711
+ withDeadline(cpuAndMemory, budget),
12712
+ withDeadline(disk, budget),
12713
+ withDeadline(hostFacts, budget),
12714
+ withDeadline(pendingRebootAndServices, budget),
12715
+ withDeadline(gpu, budget)
12716
+ ]);
12717
+ const merged = {};
12718
+ for (const part of parts) {
12719
+ if (part) Object.assign(merged, part);
12720
+ }
12721
+ return sanitizeFleetNodeHealthSnapshot(merged);
12722
+ } catch {
12723
+ return null;
12724
+ }
12725
+ };
12726
+ }
12727
+ __name(createHealthProbe, "createHealthProbe");
12728
+
10909
12729
  // src/core/housekeeping-report.ts
10910
12730
  function summarizeWorkspaceReap(result) {
10911
12731
  const dryRun = result.dryRun === true;
@@ -10994,6 +12814,32 @@ __name(isReportableBytes, "isReportableBytes");
10994
12814
  // src/core/worker-health.ts
10995
12815
  function describeWorkerHealth(state) {
10996
12816
  if (!state) return null;
12817
+ const projected = projectWorkerState(state);
12818
+ const usage = describeModelThrottles(state);
12819
+ if (!usage) return projected;
12820
+ switch (projected.workerState) {
12821
+ case "idle":
12822
+ return health("throttled", usage);
12823
+ case "throttled":
12824
+ case "working":
12825
+ return health(
12826
+ projected.workerState,
12827
+ projected.workerStateReason ? `${projected.workerStateReason}; ${usage}` : usage
12828
+ );
12829
+ default:
12830
+ return projected;
12831
+ }
12832
+ }
12833
+ __name(describeWorkerHealth, "describeWorkerHealth");
12834
+ function describeModelThrottles(state) {
12835
+ const throttles = state.modelThrottles ?? [];
12836
+ if (throttles.length === 0) return null;
12837
+ return throttles.map(
12838
+ (throttle) => `${throttle.provider}: ${describeFleetModelUsageLimitWindow(throttle.window)} until ${throttle.until}`
12839
+ ).join("; ");
12840
+ }
12841
+ __name(describeModelThrottles, "describeModelThrottles");
12842
+ function projectWorkerState(state) {
10997
12843
  switch (state.state) {
10998
12844
  // The fail-closed stop. `lastError` carries the quarantine's own
10999
12845
  // message (`markUnsafe` writes it there), which is the single most
@@ -11023,7 +12869,7 @@ function describeWorkerHealth(state) {
11023
12869
  return health("idle", null);
11024
12870
  }
11025
12871
  }
11026
- __name(describeWorkerHealth, "describeWorkerHealth");
12872
+ __name(projectWorkerState, "projectWorkerState");
11027
12873
  function health(workerState, reason) {
11028
12874
  const trimmed = typeof reason === "string" ? reason.trim() : "";
11029
12875
  if (!trimmed) {
@@ -11039,6 +12885,50 @@ var import_fs2 = require("fs");
11039
12885
  var import_fs3 = require("fs");
11040
12886
  var import_os = require("os");
11041
12887
  var import_path2 = require("path");
12888
+
12889
+ // src/core/execution-policy.ts
12890
+ var NodeExecutionPolicyError = class extends Error {
12891
+ static {
12892
+ __name(this, "NodeExecutionPolicyError");
12893
+ }
12894
+ constructor(message) {
12895
+ super(message);
12896
+ this.name = "NodeExecutionPolicyError";
12897
+ }
12898
+ };
12899
+ var NODE_POLICY_COMMAND_HINT = "The machine owner can allow it on this PC with `ever-works-node policy <flag> on` (then restart the node; for the desktop app, run the command with EVER_WORKS_NODE_CONFIG set to the app\u2019s node-config.json); the platform cannot change this setting";
12900
+ function describePhase(name, count) {
12901
+ return count > 0 ? `${count} ${name}${count === 1 ? "" : "s"}` : null;
12902
+ }
12903
+ __name(describePhase, "describePhase");
12904
+ function enforceAgentTaskExecutionPolicy(input, policy) {
12905
+ if (!policy) return;
12906
+ if (input.execution?.skipPermissions === true && !policy.allowSkipPermissions) {
12907
+ throw new NodeExecutionPolicyError(
12908
+ `Refused by this node's local execution policy: the job asks to run ${String(input.execution.provider)} with its permission checks switched off (skip-permissions), which gives an untrusted prompt an unrestricted shell as the machine owner. ${NODE_POLICY_COMMAND_HINT} (flag: --allow-skip-permissions).`
12909
+ );
12910
+ }
12911
+ if (policy.allowShellCommands) return;
12912
+ const phases = [
12913
+ describePhase("setup command", input.setupCount),
12914
+ describePhase(input.execution ? "command step" : "command-template step", input.stepCount),
12915
+ describePhase("acceptance check", input.checkCount)
12916
+ ].filter((phase) => phase !== null);
12917
+ if (phases.length === 0) return;
12918
+ throw new NodeExecutionPolicyError(
12919
+ `Refused by this node's local execution policy: the job carries ${phases.join(", ")}. Those are shell commands the node itself would run with the machine owner\u2019s privileges, executing repository code` + (input.execution ? " \u2014 after the model step, code the model may have just written" : "") + `. ${NODE_POLICY_COMMAND_HINT} (flag: --allow-shell-commands), or run this Work with checks off.`
12920
+ );
12921
+ }
12922
+ __name(enforceAgentTaskExecutionPolicy, "enforceAgentTaskExecutionPolicy");
12923
+ function enforceAcceptanceChecksExecutionPolicy(checkCount, policy) {
12924
+ if (!policy || policy.allowShellCommands || checkCount === 0) return;
12925
+ throw new NodeExecutionPolicyError(
12926
+ `Refused by this node's local execution policy: the job carries ${describePhase("acceptance check", checkCount)}, shell commands the node would run with the machine owner\u2019s privileges. ${NODE_POLICY_COMMAND_HINT} (flag: --allow-shell-commands).`
12927
+ );
12928
+ }
12929
+ __name(enforceAcceptanceChecksExecutionPolicy, "enforceAcceptanceChecksExecutionPolicy");
12930
+
12931
+ // src/core/executors/acceptance-checks.ts
11042
12932
  var DEFAULT_CHECK_TIMEOUT_SEC = 600;
11043
12933
  var MAX_CHECK_TIMEOUT_SEC = 1800;
11044
12934
  var CHECK_LOG_TAIL_BYTES = 4096;
@@ -11085,6 +12975,7 @@ async function runAcceptanceChecksJob(job, io = {}, signal) {
11085
12975
  throwIfCommandAborted(signal);
11086
12976
  return { gateStatus: "none", results: [] };
11087
12977
  }
12978
+ enforceAcceptanceChecksExecutionPolicy(checks.length, io.executionPolicy);
11088
12979
  const results = [];
11089
12980
  for (const check of checks) {
11090
12981
  throwIfCommandAborted(signal);
@@ -12816,6 +14707,10 @@ async function runAgentTaskJob(job, io = {}, signal) {
12816
14707
  const checks = withRunEnvGrants(resolveAcceptanceChecks(payload), runEnvGrants);
12817
14708
  const grantedSteps = withRunEnvGrants(steps, runEnvGrants);
12818
14709
  const setup = withRunEnvGrants(normalizeAgentTaskSetup(payload.setup), runEnvGrants);
14710
+ enforceAgentTaskExecutionPolicy(
14711
+ { execution, setupCount: setup.length, stepCount: grantedSteps.length, checkCount: checks.length },
14712
+ io.executionPolicy
14713
+ );
12819
14714
  let workspaceResolution;
12820
14715
  try {
12821
14716
  workspaceResolution = await resolveAgentTaskWorkspace(taskId, payload, io, signal);
@@ -12983,6 +14878,31 @@ async function runResolvedAgentTask(context, io, signal) {
12983
14878
  containment = modelStep.containment;
12984
14879
  throwIfAgentTaskAborted(signal);
12985
14880
  if (model.status !== "succeeded") {
14881
+ const usageLimit = recognizeModelUsageLimit(model, io);
14882
+ if (usageLimit) {
14883
+ if (workspaceResolution.descriptor && io.removeRunEnvFiles) {
14884
+ await io.removeRunEnvFiles(taskId, workspaceResolution.descriptor).catch(() => void 0);
14885
+ }
14886
+ io.logger?.warn(
14887
+ `[fleet-node] job ${job.id}: ${model.provider} hit a usage limit (${usageLimit.window}) \u2014 retry after ${usageLimit.retryAfter}; steps, checks and publish skipped`
14888
+ );
14889
+ return fitNodeOutcomeToResultBudget({
14890
+ status: "failed",
14891
+ taskId,
14892
+ runId,
14893
+ workspace: workspaceResolution.descriptor,
14894
+ steps: [],
14895
+ ...setupResults.length > 0 ? { setup: setupResults } : {},
14896
+ ...setup.length > 0 ? { setupStatus } : {},
14897
+ model,
14898
+ gateStatus: "none",
14899
+ ...mcp ? { mcp } : {},
14900
+ ...containment ? { containment } : {},
14901
+ failureReason: `${describeModelFailure(model)} \u2014 usage limit, retry after ${usageLimit.retryAfter}`,
14902
+ failureKind: FLEET_JOB_FAILURE_KIND_MODEL_USAGE_LIMIT,
14903
+ modelUsageLimit: usageLimit
14904
+ });
14905
+ }
12986
14906
  failures.push(describeModelFailure(model));
12987
14907
  }
12988
14908
  }
@@ -13192,6 +15112,7 @@ async function runModelStep(jobId, execution, workspacePath, mounts, bridgeSpec,
13192
15112
  resultPath: (0, import_path4.join)(scratchDir, "model-output.json")
13193
15113
  };
13194
15114
  const mcpConfigPath = (0, import_path4.join)(scratchDir, "mcp.json");
15115
+ const settingsPath = (0, import_path4.join)(scratchDir, "claude-settings.json");
13195
15116
  const containment = await establishModelContainment(execution.provider, scratchDir, scratchFs, io);
13196
15117
  const bridge = await startBridge(jobId, bridgeSpec, mcpConfigPath, scratchFs, io);
13197
15118
  const recordEvidence = io.modelTranscript !== "off";
@@ -13207,7 +15128,12 @@ async function runModelStep(jobId, execution, workspacePath, mounts, bridgeSpec,
13207
15128
  await scratchFs.writeFile(mcpConfigPath, MODEL_CLI_EMPTY_MCP_CONFIG);
13208
15129
  emptyMcpConfigPath = mcpConfigPath;
13209
15130
  }
13210
- const droppedFlags = await optionalFlagsToDrop(jobId, execution, executable, io);
15131
+ let claudeSettingsPath;
15132
+ if (execution.provider === "claude-code") {
15133
+ await scratchFs.writeFile(settingsPath, MODEL_CLI_FLEET_SETTINGS);
15134
+ claudeSettingsPath = settingsPath;
15135
+ }
15136
+ const { dropped: droppedFlags, restricted } = await probeModelCliFlags(jobId, execution, executable, io);
13211
15137
  const codexUserSettings = execution.provider === "codex" ? await readOwnerCodexSettings(
13212
15138
  jobId,
13213
15139
  io,
@@ -13225,6 +15151,8 @@ async function runModelStep(jobId, execution, workspacePath, mounts, bridgeSpec,
13225
15151
  ...mounts && mounts.length > 0 ? { mounts } : {},
13226
15152
  ...bridge.cli ? { mcp: bridge.cli } : {},
13227
15153
  ...emptyMcpConfigPath ? { emptyMcpConfigPath } : {},
15154
+ ...claudeSettingsPath ? { settingsPath: claudeSettingsPath } : {},
15155
+ ...restricted ? { restricted: true } : {},
13228
15156
  ...codexUserSettings ? { codexUserSettings } : {},
13229
15157
  ...resumeSessionId ? { resumeSessionId } : {},
13230
15158
  ...io.platform ? { platform: io.platform } : {},
@@ -13232,6 +15160,7 @@ async function runModelStep(jobId, execution, workspacePath, mounts, bridgeSpec,
13232
15160
  ...droppedFlags.length > 0 ? { omitFlags: droppedFlags } : {}
13233
15161
  });
13234
15162
  assertMcpIsolationInCommand({ command, execution });
15163
+ assertSettingsIsolationInCommand({ command, execution });
13235
15164
  assertMountGrantsInCommand({
13236
15165
  command,
13237
15166
  execution,
@@ -13310,14 +15239,21 @@ async function refuseUnderClaudeManagedMcpConfig(provider, io) {
13310
15239
  );
13311
15240
  }
13312
15241
  __name(refuseUnderClaudeManagedMcpConfig, "refuseUnderClaudeManagedMcpConfig");
13313
- async function optionalFlagsToDrop(jobId, execution, executable, io) {
13314
- if (!io.modelCliCompat) return [];
15242
+ async function probeModelCliFlags(jobId, execution, executable, io) {
15243
+ if (!io.modelCliCompat) return { dropped: [], restricted: false };
13315
15244
  let compat = null;
13316
15245
  try {
13317
15246
  compat = await io.modelCliCompat(execution.provider, executable);
13318
15247
  } catch {
13319
- return [];
15248
+ return { dropped: [], restricted: false };
13320
15249
  }
15250
+ return {
15251
+ dropped: optionalFlagsToDrop(jobId, execution, compat, io),
15252
+ restricted: supportsRestrictedMode(jobId, execution, compat, io)
15253
+ };
15254
+ }
15255
+ __name(probeModelCliFlags, "probeModelCliFlags");
15256
+ function optionalFlagsToDrop(jobId, execution, compat, io) {
13321
15257
  const dropped = unsupportedOptionalModelCliFlags(execution, compat?.supportedFlags ?? null);
13322
15258
  if (dropped.length > 0) {
13323
15259
  io.logger?.warn(
@@ -13327,6 +15263,18 @@ async function optionalFlagsToDrop(jobId, execution, executable, io) {
13327
15263
  return dropped;
13328
15264
  }
13329
15265
  __name(optionalFlagsToDrop, "optionalFlagsToDrop");
15266
+ function supportsRestrictedMode(jobId, execution, compat, io) {
15267
+ if (execution.provider !== "claude-code" || execution.skipPermissions === true) return false;
15268
+ if (io.executionPolicy?.restrictedMode === false) return false;
15269
+ const supported = compat?.supportedFlags?.has("--restricted") === true;
15270
+ if (!supported && compat?.supportedFlags) {
15271
+ io.logger?.info(
15272
+ `[fleet-node] job ${jobId}: the pinned claude-code CLI (${compat.version ?? "version unknown"}) does not advertise --restricted \u2014 running with the baseline settings isolation only. Upgrade Claude Code on this machine for the stricter mode.`
15273
+ );
15274
+ }
15275
+ return supported;
15276
+ }
15277
+ __name(supportsRestrictedMode, "supportsRestrictedMode");
13330
15278
  async function establishModelContainment(provider, scratchDir, scratchFs, io) {
13331
15279
  const downgrades = [
13332
15280
  {
@@ -13688,6 +15636,22 @@ function resumeNeverOpened(model) {
13688
15636
  return RESUME_NEVER_OPENED_SIGNATURES.some((signature) => signature.test(said));
13689
15637
  }
13690
15638
  __name(resumeNeverOpened, "resumeNeverOpened");
15639
+ function recognizeModelUsageLimit(model, io = {}) {
15640
+ if (model.status === "succeeded" || model.status === "timeout") return null;
15641
+ const now = io.usageLimitClock?.now() ?? Date.now();
15642
+ const timeZone = io.usageLimitClock?.timeZone ?? localTimeZone();
15643
+ const options = { provider: model.provider, now, ...timeZone ? { timeZone } : {} };
15644
+ return detectFleetModelUsageLimit(model.summary, options) ?? detectFleetModelUsageLimit(model.outputTail, { ...options, strict: true });
15645
+ }
15646
+ __name(recognizeModelUsageLimit, "recognizeModelUsageLimit");
15647
+ function localTimeZone() {
15648
+ try {
15649
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || void 0;
15650
+ } catch {
15651
+ return void 0;
15652
+ }
15653
+ }
15654
+ __name(localTimeZone, "localTimeZone");
13691
15655
  function describeModelFailure(model) {
13692
15656
  switch (model.status) {
13693
15657
  case "timeout":
@@ -14792,17 +16756,9 @@ function describe(error) {
14792
16756
  }
14793
16757
  __name(describe, "describe");
14794
16758
 
14795
- // src/core/screen/node-leg.ts
14796
- var NODE_LEG_RECONNECT_BASE_MS = 2e3;
14797
- var NODE_LEG_RECONNECT_MAX_MS = 3e4;
14798
- var NODE_LEG_ACCEPTED_AFTER_MS = 3e4;
16759
+ // src/core/link/link-socket.ts
14799
16760
  var LOOPBACK_HOSTNAMES2 = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
14800
- function toComputerWsUrl(apiUrl, wsPath) {
14801
- const origin = new URL(apiUrl).origin;
14802
- return `${origin.replace(/^http/, "ws")}${wsPath.startsWith("/") ? wsPath : `/${wsPath}`}`;
14803
- }
14804
- __name(toComputerWsUrl, "toComputerWsUrl");
14805
- function isSecureLegOrigin(apiUrl) {
16761
+ function isSecureLinkOrigin(apiUrl) {
14806
16762
  let parsed;
14807
16763
  try {
14808
16764
  parsed = new URL(apiUrl);
@@ -14812,6 +16768,83 @@ function isSecureLegOrigin(apiUrl) {
14812
16768
  if (parsed.protocol === "https:") return true;
14813
16769
  return parsed.protocol === "http:" && LOOPBACK_HOSTNAMES2.has(parsed.hostname);
14814
16770
  }
16771
+ __name(isSecureLinkOrigin, "isSecureLinkOrigin");
16772
+ function toLinkWsUrl(apiUrl, wsPath) {
16773
+ const origin = new URL(apiUrl).origin;
16774
+ return `${origin.replace(/^http/, "ws")}${wsPath.startsWith("/") ? wsPath : `/${wsPath}`}`;
16775
+ }
16776
+ __name(toLinkWsUrl, "toLinkWsUrl");
16777
+ var LinkBackoff = class {
16778
+ static {
16779
+ __name(this, "LinkBackoff");
16780
+ }
16781
+ attempt = 0;
16782
+ baseMs;
16783
+ maxMs;
16784
+ jitter;
16785
+ random;
16786
+ constructor(options = {}) {
16787
+ this.baseMs = options.baseMs ?? FLEET_LINK_BACKOFF_BASE_MS;
16788
+ this.maxMs = Math.max(this.baseMs, options.maxMs ?? FLEET_LINK_BACKOFF_MAX_MS);
16789
+ this.jitter = Math.min(Math.max(options.jitter ?? 0.2, 0), 0.9);
16790
+ this.random = options.random ?? Math.random;
16791
+ }
16792
+ /** Attempts made since the last reset. */
16793
+ get attempts() {
16794
+ return this.attempt;
16795
+ }
16796
+ /** The nominal (un-jittered) wait for attempt `n`. */
16797
+ nominal(attempt) {
16798
+ return Math.min(this.baseMs * 2 ** Math.min(Math.max(attempt, 0), 20), this.maxMs);
16799
+ }
16800
+ next() {
16801
+ const nominal = this.nominal(this.attempt);
16802
+ this.attempt += 1;
16803
+ const spread = nominal * this.jitter * (this.random() * 2 - 1);
16804
+ return Math.round(Math.min(Math.max(nominal + spread, this.baseMs), this.maxMs));
16805
+ }
16806
+ reset() {
16807
+ this.attempt = 0;
16808
+ }
16809
+ };
16810
+ var LINK_SOCKET_OPEN = 1;
16811
+ function defaultLinkSocketFactory() {
16812
+ const Ctor = globalThis.WebSocket;
16813
+ if (typeof Ctor !== "function") return null;
16814
+ return (url) => {
16815
+ const socket = new Ctor(url);
16816
+ try {
16817
+ socket.binaryType = "arraybuffer";
16818
+ } catch {
16819
+ }
16820
+ return socket;
16821
+ };
16822
+ }
16823
+ __name(defaultLinkSocketFactory, "defaultLinkSocketFactory");
16824
+ function linkMessageBytes(data) {
16825
+ if (data instanceof Uint8Array) return data;
16826
+ if (data instanceof ArrayBuffer) return new Uint8Array(data);
16827
+ if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
16828
+ return null;
16829
+ }
16830
+ __name(linkMessageBytes, "linkMessageBytes");
16831
+ function linkCloseCode(event) {
16832
+ const code = event?.code;
16833
+ return typeof code === "number" ? code : null;
16834
+ }
16835
+ __name(linkCloseCode, "linkCloseCode");
16836
+
16837
+ // src/core/screen/node-leg.ts
16838
+ var NODE_LEG_RECONNECT_BASE_MS = 2e3;
16839
+ var NODE_LEG_RECONNECT_MAX_MS = 3e4;
16840
+ var NODE_LEG_ACCEPTED_AFTER_MS = 3e4;
16841
+ function toComputerWsUrl(apiUrl, wsPath) {
16842
+ return toLinkWsUrl(apiUrl, wsPath);
16843
+ }
16844
+ __name(toComputerWsUrl, "toComputerWsUrl");
16845
+ function isSecureLegOrigin(apiUrl) {
16846
+ return isSecureLinkOrigin(apiUrl);
16847
+ }
14815
16848
  __name(isSecureLegOrigin, "isSecureLegOrigin");
14816
16849
  function openNodeLeg(options) {
14817
16850
  if (!isSecureLegOrigin(options.apiUrl)) {
@@ -14980,13 +17013,13 @@ var ComputerSessionPayloadError = class extends Error {
14980
17013
  this.name = "ComputerSessionPayloadError";
14981
17014
  }
14982
17015
  };
14983
- var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
17016
+ var UUID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14984
17017
  function normalizeComputerSessionPayload(raw, nodeId) {
14985
17018
  if (!raw || typeof raw !== "object") throw new ComputerSessionPayloadError("Live view payload is missing");
14986
17019
  const payload = raw;
14987
17020
  const sessionId = typeof payload.sessionId === "string" ? payload.sessionId : "";
14988
17021
  const agentId = typeof payload.agentId === "string" ? payload.agentId : "";
14989
- if (!UUID_PATTERN.test(sessionId) || !UUID_PATTERN.test(agentId)) {
17022
+ if (!UUID_PATTERN2.test(sessionId) || !UUID_PATTERN2.test(agentId)) {
14990
17023
  throw new ComputerSessionPayloadError("Live view payload has no valid session or Agent");
14991
17024
  }
14992
17025
  if (payload.nodeId !== nodeId) {
@@ -15243,6 +17276,7 @@ var PROBLEM_CODES2 = [
15243
17276
  "already_connected",
15244
17277
  "already_linked",
15245
17278
  "already_claimed",
17279
+ "claim_locked",
15246
17280
  "invalid_client",
15247
17281
  "public_jwk_invalid",
15248
17282
  "identifier_claimed",
@@ -15268,6 +17302,11 @@ var PROBLEM_CODES2 = [
15268
17302
  "database_absent",
15269
17303
  "database_unreachable",
15270
17304
  "topic_private",
17305
+ "realtime_disabled",
17306
+ "topic_forbidden",
17307
+ "topic_invalid",
17308
+ "realtime_busy",
17309
+ "too_many_streams",
15271
17310
  "keys_unavailable",
15272
17311
  "internal_error"
15273
17312
  ];
@@ -15852,6 +17891,49 @@ var FLEET_AGENT_CREDENTIAL_FAMILIES2 = [
15852
17891
  FLEET_AGENT_CREDENTIAL_FAMILIES2.flatMap(
15853
17892
  (family) => family.envNames
15854
17893
  );
17894
+ var FLEET_LINK_FRAME_KINDS2 = {
17895
+ /** Raw bytes: terminal output/input, log lines, file chunks. */
17896
+ DATA: 1,
17897
+ /** `[cols u16][rows u16]`. */
17898
+ RESIZE: 2,
17899
+ /** `[code i32]`. */
17900
+ EXIT: 3,
17901
+ /** UTF-8 JSON control message. */
17902
+ JSON: 10,
17903
+ /** UTF-8 reason code. */
17904
+ CLOSE: 11,
17905
+ /** A full picture (the existing computer-frame encoding). */
17906
+ FRAME: 20,
17907
+ /** A pointer / key / text event (the existing computer input encoding). */
17908
+ INPUT: 21,
17909
+ /** Changed tiles `[w][h][count]{x,y,w,h,len,jpeg}`. */
17910
+ TILES: 23,
17911
+ /** `[bytes u32]` credit. */
17912
+ ACK: 30,
17913
+ /** End of a stream. */
17914
+ END: 31,
17915
+ /** UTF-8 text ≤ 256 KiB, pasted into a desktop. */
17916
+ CLIP: 40
17917
+ };
17918
+ new Set(Object.values(FLEET_LINK_FRAME_KINDS2));
17919
+ var FLEET_LINK_CONTROL_TYPES2 = [
17920
+ "link.hello",
17921
+ "link.ready",
17922
+ "link.ping",
17923
+ "link.pong",
17924
+ "link.pause",
17925
+ "link.resume",
17926
+ "link.drain",
17927
+ "channel.open",
17928
+ "channel.ready",
17929
+ "channel.refused",
17930
+ "channel.close",
17931
+ "operation.progress",
17932
+ "operation.done",
17933
+ "operation.cancel"
17934
+ ];
17935
+ new Set(FLEET_LINK_CONTROL_TYPES2);
17936
+ new TextEncoder();
15855
17937
  var EMAIL_INBOX_CAP_FIELDS2 = [
15856
17938
  "inboxDailySends",
15857
17939
  "inboxBurstSends",
@@ -15995,6 +18077,8 @@ Object.freeze(
15995
18077
  ACTION_CATEGORIES2.filter((c) => c !== "read.internal")
15996
18078
  );
15997
18079
  var DOMAIN_EVENT_CATALOG2 = [
18080
+ // EW-P2-6 — `org:write` (and the app) create Organizations.
18081
+ { type: "organization.created", domain: "organization", aggregateType: "organization", phase: "P2" },
15998
18082
  { type: "organization.updated", domain: "organization", aggregateType: "organization", phase: "P1" },
15999
18083
  {
16000
18084
  type: "organization.member.added",
@@ -16041,10 +18125,10 @@ var DOMAIN_EVENT_CATALOG2 = [
16041
18125
  { type: "mission.updated", domain: "missions", aggregateType: "mission", phase: "P2" },
16042
18126
  { type: "mission.status_changed", domain: "missions", aggregateType: "mission", phase: "P2" },
16043
18127
  { type: "mission.deleted", domain: "missions", aggregateType: "mission", phase: "P2" },
16044
- { type: "mission.goal.linked", domain: "missions", aggregateType: "mission", phase: "P2" },
16045
- { type: "mission.goal.unlinked", domain: "missions", aggregateType: "mission", phase: "P2" },
16046
- { type: "mission.work.linked", domain: "missions", aggregateType: "mission", phase: "P2" },
16047
- { type: "mission.work.unlinked", domain: "missions", aggregateType: "mission", phase: "P2" },
18128
+ { type: "mission.goal.linked", domain: "missions", aggregateType: "mission", phase: "P2", child: "goal" },
18129
+ { type: "mission.goal.unlinked", domain: "missions", aggregateType: "mission", phase: "P2", child: "goal" },
18130
+ { type: "mission.work.linked", domain: "missions", aggregateType: "mission", phase: "P2", child: "work" },
18131
+ { type: "mission.work.unlinked", domain: "missions", aggregateType: "mission", phase: "P2", child: "work" },
16048
18132
  { type: "goal.created", domain: "goals", aggregateType: "goal", phase: "P2" },
16049
18133
  { type: "goal.updated", domain: "goals", aggregateType: "goal", phase: "P2" },
16050
18134
  { type: "goal.status_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
@@ -16591,6 +18675,7 @@ var LocalWorkspacePlugin = class _LocalWorkspacePlugin {
16591
18675
  );
16592
18676
  const dirty = status.stdout.trim().length > 0;
16593
18677
  if (dirty) {
18678
+ const commitMessage = rewriteCommitMessage(opts, status.stdout);
16594
18679
  const identity = opts.identity;
16595
18680
  await this.gitOrThrow(
16596
18681
  [
@@ -16602,7 +18687,7 @@ var LocalWorkspacePlugin = class _LocalWorkspacePlugin {
16602
18687
  ...opts.hardenRepoHooks ? ["--no-verify"] : [],
16603
18688
  ...identity ? [`--author=${identity.authorName} <${identity.authorEmail}>`] : [],
16604
18689
  "-m",
16605
- opts.commitMessage
18690
+ commitMessage
16606
18691
  ],
16607
18692
  dir,
16608
18693
  opts.auth,
@@ -17845,6 +19930,31 @@ function isSafeCloneArgument(url) {
17845
19930
  return !parsed.hostname.startsWith("-");
17846
19931
  }
17847
19932
  __name(isSafeCloneArgument, "isSafeCloneArgument");
19933
+ function parsePorcelainPaths(stdout) {
19934
+ const paths = [];
19935
+ for (const line of stdout.split(/\r?\n/)) {
19936
+ if (line.length < 4) continue;
19937
+ let path2 = line.slice(3);
19938
+ const arrow = path2.lastIndexOf(" -> ");
19939
+ if (arrow !== -1) path2 = path2.slice(arrow + 4);
19940
+ if (path2.startsWith('"') && path2.endsWith('"') && path2.length >= 2) {
19941
+ path2 = path2.slice(1, -1).replace(/\\(.)/g, "$1");
19942
+ }
19943
+ if (path2) paths.push(path2);
19944
+ }
19945
+ return paths;
19946
+ }
19947
+ __name(parsePorcelainPaths, "parsePorcelainPaths");
19948
+ function rewriteCommitMessage(opts, porcelain) {
19949
+ if (typeof opts.commitMessageFor !== "function") return opts.commitMessage;
19950
+ try {
19951
+ const next = opts.commitMessageFor(parsePorcelainPaths(porcelain), opts.commitMessage);
19952
+ return typeof next === "string" && next.trim() && !/[\0\r]/.test(next) ? next : opts.commitMessage;
19953
+ } catch {
19954
+ return opts.commitMessage;
19955
+ }
19956
+ }
19957
+ __name(rewriteCommitMessage, "rewriteCommitMessage");
17848
19958
 
17849
19959
  // src/core/workspaces/push-credential.ts
17850
19960
  var PUSH_CREDENTIAL_REVOKE_TIMEOUT_MS = 5e3;
@@ -18763,6 +20873,11 @@ var FleetTaskWorkspaceProvisioner = class {
18763
20873
  // pool config. The credentialed push already does this; the flag
18764
20874
  // extends it to the commit.
18765
20875
  hardenRepoHooks: true,
20876
+ // Fleet commit messages: once the change is staged, a
20877
+ // docs-only change commits as `docs`, a tests-only one as
20878
+ // `test`, whatever the Task title implied. Only the type token
20879
+ // of the subject changes; the body and trailers are untouched.
20880
+ commitMessageFor: /* @__PURE__ */ __name((changedPaths, message) => retypeFleetCommitMessage(message, changedPaths), "commitMessageFor"),
18766
20881
  ...signal ? { signal } : {},
18767
20882
  ...opts.publishFence ? { publishFence: opts.publishFence } : {},
18768
20883
  ...authorization.identity ? { identity: authorization.identity } : {},
@@ -19561,6 +21676,7 @@ var PROBLEM_CODES3 = [
19561
21676
  "already_connected",
19562
21677
  "already_linked",
19563
21678
  "already_claimed",
21679
+ "claim_locked",
19564
21680
  "invalid_client",
19565
21681
  "public_jwk_invalid",
19566
21682
  "identifier_claimed",
@@ -19586,6 +21702,11 @@ var PROBLEM_CODES3 = [
19586
21702
  "database_absent",
19587
21703
  "database_unreachable",
19588
21704
  "topic_private",
21705
+ "realtime_disabled",
21706
+ "topic_forbidden",
21707
+ "topic_invalid",
21708
+ "realtime_busy",
21709
+ "too_many_streams",
19589
21710
  "keys_unavailable",
19590
21711
  "internal_error"
19591
21712
  ];
@@ -20170,6 +22291,49 @@ var FLEET_AGENT_CREDENTIAL_FAMILIES3 = [
20170
22291
  FLEET_AGENT_CREDENTIAL_FAMILIES3.flatMap(
20171
22292
  (family) => family.envNames
20172
22293
  );
22294
+ var FLEET_LINK_FRAME_KINDS3 = {
22295
+ /** Raw bytes: terminal output/input, log lines, file chunks. */
22296
+ DATA: 1,
22297
+ /** `[cols u16][rows u16]`. */
22298
+ RESIZE: 2,
22299
+ /** `[code i32]`. */
22300
+ EXIT: 3,
22301
+ /** UTF-8 JSON control message. */
22302
+ JSON: 10,
22303
+ /** UTF-8 reason code. */
22304
+ CLOSE: 11,
22305
+ /** A full picture (the existing computer-frame encoding). */
22306
+ FRAME: 20,
22307
+ /** A pointer / key / text event (the existing computer input encoding). */
22308
+ INPUT: 21,
22309
+ /** Changed tiles `[w][h][count]{x,y,w,h,len,jpeg}`. */
22310
+ TILES: 23,
22311
+ /** `[bytes u32]` credit. */
22312
+ ACK: 30,
22313
+ /** End of a stream. */
22314
+ END: 31,
22315
+ /** UTF-8 text ≤ 256 KiB, pasted into a desktop. */
22316
+ CLIP: 40
22317
+ };
22318
+ new Set(Object.values(FLEET_LINK_FRAME_KINDS3));
22319
+ var FLEET_LINK_CONTROL_TYPES3 = [
22320
+ "link.hello",
22321
+ "link.ready",
22322
+ "link.ping",
22323
+ "link.pong",
22324
+ "link.pause",
22325
+ "link.resume",
22326
+ "link.drain",
22327
+ "channel.open",
22328
+ "channel.ready",
22329
+ "channel.refused",
22330
+ "channel.close",
22331
+ "operation.progress",
22332
+ "operation.done",
22333
+ "operation.cancel"
22334
+ ];
22335
+ new Set(FLEET_LINK_CONTROL_TYPES3);
22336
+ new TextEncoder();
20173
22337
  var EMAIL_INBOX_CAP_FIELDS3 = [
20174
22338
  "inboxDailySends",
20175
22339
  "inboxBurstSends",
@@ -20313,6 +22477,8 @@ Object.freeze(
20313
22477
  ACTION_CATEGORIES3.filter((c) => c !== "read.internal")
20314
22478
  );
20315
22479
  var DOMAIN_EVENT_CATALOG3 = [
22480
+ // EW-P2-6 — `org:write` (and the app) create Organizations.
22481
+ { type: "organization.created", domain: "organization", aggregateType: "organization", phase: "P2" },
20316
22482
  { type: "organization.updated", domain: "organization", aggregateType: "organization", phase: "P1" },
20317
22483
  {
20318
22484
  type: "organization.member.added",
@@ -20359,10 +22525,10 @@ var DOMAIN_EVENT_CATALOG3 = [
20359
22525
  { type: "mission.updated", domain: "missions", aggregateType: "mission", phase: "P2" },
20360
22526
  { type: "mission.status_changed", domain: "missions", aggregateType: "mission", phase: "P2" },
20361
22527
  { type: "mission.deleted", domain: "missions", aggregateType: "mission", phase: "P2" },
20362
- { type: "mission.goal.linked", domain: "missions", aggregateType: "mission", phase: "P2" },
20363
- { type: "mission.goal.unlinked", domain: "missions", aggregateType: "mission", phase: "P2" },
20364
- { type: "mission.work.linked", domain: "missions", aggregateType: "mission", phase: "P2" },
20365
- { type: "mission.work.unlinked", domain: "missions", aggregateType: "mission", phase: "P2" },
22528
+ { type: "mission.goal.linked", domain: "missions", aggregateType: "mission", phase: "P2", child: "goal" },
22529
+ { type: "mission.goal.unlinked", domain: "missions", aggregateType: "mission", phase: "P2", child: "goal" },
22530
+ { type: "mission.work.linked", domain: "missions", aggregateType: "mission", phase: "P2", child: "work" },
22531
+ { type: "mission.work.unlinked", domain: "missions", aggregateType: "mission", phase: "P2", child: "work" },
20366
22532
  { type: "goal.created", domain: "goals", aggregateType: "goal", phase: "P2" },
20367
22533
  { type: "goal.updated", domain: "goals", aggregateType: "goal", phase: "P2" },
20368
22534
  { type: "goal.status_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
@@ -21462,25 +23628,1106 @@ async function defaultReadTextFile(path2) {
21462
23628
  }
21463
23629
  __name(defaultReadTextFile, "defaultReadTextFile");
21464
23630
 
21465
- // src/core/runtime.ts
21466
- function buildSelfDescriptionTelemetry(io, cliCompat) {
21467
- const pinned = /* @__PURE__ */ __name(() => io.environment.modelCli ?? {}, "pinned");
21468
- const telemetry = {
21469
- cliVersion: cliCompat ? async () => primaryPinnedCliVersion(await cliCompat.probeAll(pinned())) ?? detectAgentCliVersion(io.runner) : () => detectAgentCliVersion(io.runner),
21470
- modelIdentity: cacheProbe(() => detectModelIdentity(io.runner, io.environment.modelCli ?? {}))
21471
- };
21472
- if (cliCompat) {
21473
- telemetry.cliVersions = async () => describeCliVersions(await cliCompat.probeAll(pinned()));
23631
+ // src/core/link/link-client.ts
23632
+ var import_node_crypto3 = require("node:crypto");
23633
+
23634
+ // src/core/link/channel-mux.ts
23635
+ var ChannelMux = class {
23636
+ constructor(options) {
23637
+ this.options = options;
21474
23638
  }
21475
- if (io.diskProbe) {
21476
- const probe = io.diskProbe;
21477
- const path2 = io.workspacePath ?? process.cwd();
21478
- telemetry.diskFreeBytes = () => measureWorkspaceFreeBytes(probe, path2);
23639
+ options;
23640
+ static {
23641
+ __name(this, "ChannelMux");
23642
+ }
23643
+ channels = /* @__PURE__ */ new Map();
23644
+ /** Numbers closed on the current generation. */
23645
+ retired = /* @__PURE__ */ new Set();
23646
+ sender = null;
23647
+ /** Channels currently open (suspended ones included). */
23648
+ get size() {
23649
+ return this.channels.size;
23650
+ }
23651
+ /** Point the table at a socket (null while the link is down). */
23652
+ bind(sender) {
23653
+ this.sender = sender;
23654
+ }
23655
+ has(channel) {
23656
+ return this.channels.has(channel);
23657
+ }
23658
+ /** Send a raw frame through the bound socket. */
23659
+ sendFrame(frame) {
23660
+ if (!frame || !this.sender) return false;
23661
+ try {
23662
+ return this.sender(frame);
23663
+ } catch {
23664
+ return false;
23665
+ }
21479
23666
  }
21480
- return telemetry;
21481
- }
21482
- __name(buildSelfDescriptionTelemetry, "buildSelfDescriptionTelemetry");
21483
- function clampHeartbeatInterval(intervalMs) {
23667
+ sendControl(message) {
23668
+ return this.sendFrame(encodeFleetLinkControl(message));
23669
+ }
23670
+ /** `channel.open` from the platform: answer `channel.ready` or `channel.refused`. */
23671
+ async open(message) {
23672
+ const { channel, family, params, requestId } = message;
23673
+ const refuse = /* @__PURE__ */ __name((error) => {
23674
+ this.sendControl({ type: "channel.refused", channel, requestId, error });
23675
+ }, "refuse");
23676
+ if (this.channels.has(channel) || this.retired.has(channel)) {
23677
+ refuse(makeFleetLinkError("invalid", `channel ${channel} is already in use on this link`));
23678
+ return;
23679
+ }
23680
+ if (this.channels.size >= FLEET_LINK_MAX_CHANNELS) {
23681
+ refuse(
23682
+ makeFleetLinkError(
23683
+ "limit_exceeded",
23684
+ `${FLEET_LINK_MAX_CHANNELS} channels are open on ${this.options.nodeName}; close one`
23685
+ )
23686
+ );
23687
+ return;
23688
+ }
23689
+ if (this.options.control.paused) {
23690
+ refuse(makeFleetLinkError("kill_switch", "clear it under Settings \u2192 Fleet"));
23691
+ return;
23692
+ }
23693
+ const provider = this.options.modules.family(family);
23694
+ if (!provider) {
23695
+ refuse(
23696
+ makeFleetLinkError(
23697
+ "unsupported_on_node",
23698
+ `${this.options.nodeName} (node ${this.options.nodeVersion}) cannot do this yet: ${family}`,
23699
+ { family, nodeVersion: this.options.nodeVersion }
23700
+ )
23701
+ );
23702
+ return;
23703
+ }
23704
+ let requirement;
23705
+ try {
23706
+ requirement = this.options.modules.familyConsent(family, params);
23707
+ } catch (thrown) {
23708
+ this.options.logger?.warn(
23709
+ `Link channel ${channel} (${family}) consent check failed: ${thrown instanceof Error ? thrown.message : String(thrown)}`
23710
+ );
23711
+ refuse(makeFleetLinkError("internal", `${this.options.nodeName} could not check this channel's consent`));
23712
+ return;
23713
+ }
23714
+ if (!fleetConsentSatisfies(this.options.consents, requirement)) {
23715
+ refuse(
23716
+ makeFleetLinkError("consent_missing", fleetConsentMissingHint(requirement, this.options.nodeName), {
23717
+ family,
23718
+ consent: requirement
23719
+ })
23720
+ );
23721
+ return;
23722
+ }
23723
+ const placeholder = { channel, family, handler: {}, credit: null, suspended: false };
23724
+ this.channels.set(channel, placeholder);
23725
+ let result;
23726
+ try {
23727
+ result = await provider.open(this.contextFor(channel, family, params));
23728
+ } catch (thrown) {
23729
+ result = {
23730
+ ok: false,
23731
+ error: makeFleetLinkError(
23732
+ "operation_failed",
23733
+ (thrown instanceof Error ? thrown.message : String(thrown)).split(/\r?\n/, 1)[0].slice(0, 300)
23734
+ )
23735
+ };
23736
+ }
23737
+ if (this.channels.get(channel) !== placeholder) {
23738
+ if (result.ok) result.handler.onClose?.("normal");
23739
+ return;
23740
+ }
23741
+ if (!result.ok) {
23742
+ this.channels.delete(channel);
23743
+ this.retired.add(channel);
23744
+ refuse(result.error);
23745
+ return;
23746
+ }
23747
+ placeholder.handler = result.handler;
23748
+ placeholder.credit = FLEET_LINK_CREDIT_FAMILIES.includes(family) ? FLEET_LINK_CREDIT_WINDOW_BYTES : null;
23749
+ const ready = {
23750
+ type: "channel.ready",
23751
+ channel,
23752
+ requestId
23753
+ };
23754
+ if (result.handler.resumeKey) ready.resumeKey = result.handler.resumeKey;
23755
+ this.sendControl(ready);
23756
+ }
23757
+ /** A frame for a channel above 0. Frames for an unknown channel are dropped. */
23758
+ deliver(frame) {
23759
+ const entry = this.channels.get(frame.channel);
23760
+ if (!entry || entry.suspended) return;
23761
+ if (frame.kind === FLEET_LINK_FRAME_KINDS.ACK && entry.credit !== null) {
23762
+ const granted = decodeFleetLinkAck(frame.payload);
23763
+ if (granted !== null && granted > 0) {
23764
+ entry.credit = Math.min(entry.credit + granted, FLEET_LINK_CREDIT_WINDOW_BYTES);
23765
+ try {
23766
+ entry.handler.onDrain?.();
23767
+ } catch {
23768
+ }
23769
+ }
23770
+ return;
23771
+ }
23772
+ try {
23773
+ if (frame.kind === FLEET_LINK_FRAME_KINDS.JSON) {
23774
+ const message = decodeFleetLinkJson(frame.payload, FLEET_LINK_MAX_PAYLOAD_BYTES);
23775
+ if (message) entry.handler.onJson?.(message);
23776
+ return;
23777
+ }
23778
+ entry.handler.onBinary?.(frame.kind, frame.payload);
23779
+ } catch (thrown) {
23780
+ this.options.logger?.warn(
23781
+ `Link channel ${frame.channel} (${entry.family}) handler failed: ${thrown instanceof Error ? thrown.message : String(thrown)}`
23782
+ );
23783
+ }
23784
+ }
23785
+ /** `channel.close` from the platform. */
23786
+ closeRemote(channel, reason) {
23787
+ this.finish(channel, reason, false);
23788
+ }
23789
+ /** Close one channel from the node's side, telling the platform. */
23790
+ closeLocal(channel, reason, hint) {
23791
+ if (!this.channels.has(channel)) return;
23792
+ this.finish(channel, reason, true, hint);
23793
+ }
23794
+ /**
23795
+ * The link dropped. Channels that cannot resume are closed now with
23796
+ * `resume_failed`; the rest are suspended and listed for the reconnect.
23797
+ */
23798
+ suspend() {
23799
+ this.sender = null;
23800
+ const resume = [];
23801
+ for (const entry of [...this.channels.values()]) {
23802
+ if (entry.handler.resumeKey) {
23803
+ entry.suspended = true;
23804
+ resume.push({ channel: entry.channel, family: entry.family, resumeKey: entry.handler.resumeKey });
23805
+ } else {
23806
+ this.finish(entry.channel, "resume_failed", false);
23807
+ }
23808
+ }
23809
+ return resume;
23810
+ }
23811
+ /**
23812
+ * A new generation is ready. Channels it `resumed` carry on; every other
23813
+ * suspended channel closes with the reason the platform gave, or
23814
+ * `resume_failed`. Numbers retired on the previous generation are free
23815
+ * again — retirement is per generation.
23816
+ */
23817
+ resumeGeneration(resumed, closed, closedReason = "resume_failed") {
23818
+ this.retired = /* @__PURE__ */ new Set();
23819
+ const keep = new Set(resumed);
23820
+ for (const entry of [...this.channels.values()]) {
23821
+ if (!entry.suspended) continue;
23822
+ if (keep.has(entry.channel)) {
23823
+ entry.suspended = false;
23824
+ try {
23825
+ entry.handler.onResume?.();
23826
+ } catch {
23827
+ }
23828
+ } else {
23829
+ this.finish(entry.channel, closed.includes(entry.channel) ? closedReason : "resume_failed", false);
23830
+ }
23831
+ }
23832
+ }
23833
+ /** Close every channel (shutdown, or a link that will not come back). */
23834
+ closeAll(reason) {
23835
+ for (const channel of [...this.channels.keys()]) this.finish(channel, reason, false);
23836
+ }
23837
+ finish(channel, reason, tellPlatform, hint) {
23838
+ const entry = this.channels.get(channel);
23839
+ if (!entry) return;
23840
+ this.channels.delete(channel);
23841
+ this.retired.add(channel);
23842
+ if (tellPlatform) {
23843
+ const close = {
23844
+ type: "channel.close",
23845
+ channel,
23846
+ reason
23847
+ };
23848
+ if (hint) close.hint = hint;
23849
+ this.sendControl(close);
23850
+ }
23851
+ try {
23852
+ entry.handler.onClose?.(reason);
23853
+ } catch {
23854
+ }
23855
+ }
23856
+ contextFor(channel, family, params) {
23857
+ const live = /* @__PURE__ */ __name(() => {
23858
+ const entry = this.channels.get(channel);
23859
+ return entry && !entry.suspended ? entry : null;
23860
+ }, "live");
23861
+ return {
23862
+ channel,
23863
+ family,
23864
+ params,
23865
+ consents: this.options.consents,
23866
+ control: this.options.control,
23867
+ nodeVersion: this.options.nodeVersion,
23868
+ nodeName: this.options.nodeName,
23869
+ logger: this.options.logger,
23870
+ operations: this.options.operations,
23871
+ sendJson: /* @__PURE__ */ __name((message) => live() ? this.sendFrame(encodeFleetLinkJson(channel, message)) : false, "sendJson"),
23872
+ sendBinary: /* @__PURE__ */ __name((kind, payload) => {
23873
+ const entry = live();
23874
+ if (!entry) return false;
23875
+ if (kind === FLEET_LINK_FRAME_KINDS.DATA && entry.credit !== null) {
23876
+ if (payload.byteLength > entry.credit) return false;
23877
+ const sent = this.sendFrame(encodeFleetLinkFrame(channel, kind, payload));
23878
+ if (sent) entry.credit -= payload.byteLength;
23879
+ return sent;
23880
+ }
23881
+ return this.sendFrame(encodeFleetLinkFrame(channel, kind, payload));
23882
+ }, "sendBinary"),
23883
+ sendControl: /* @__PURE__ */ __name((message) => this.sendControl(message), "sendControl"),
23884
+ close: /* @__PURE__ */ __name((reason, hint) => this.closeLocal(channel, reason, hint), "close")
23885
+ };
23886
+ }
23887
+ };
23888
+
23889
+ // src/core/link/modules/registry.ts
23890
+ var MachineVerbError = class extends Error {
23891
+ constructor(error) {
23892
+ super(error.hint);
23893
+ this.error = error;
23894
+ this.name = "MachineVerbError";
23895
+ }
23896
+ error;
23897
+ static {
23898
+ __name(this, "MachineVerbError");
23899
+ }
23900
+ };
23901
+ var MODULE_ID_PATTERN = /^[a-z][a-z0-9-]{0,47}$/;
23902
+ function composeNodeModules(modules) {
23903
+ const ids = /* @__PURE__ */ new Set();
23904
+ const families = /* @__PURE__ */ new Map();
23905
+ const verbs = /* @__PURE__ */ new Map();
23906
+ const controls = /* @__PURE__ */ new Map();
23907
+ for (const module2 of modules) {
23908
+ if (!MODULE_ID_PATTERN.test(module2.id)) throw new Error(`Node module id "${module2.id}" is not kebab-case`);
23909
+ if (ids.has(module2.id)) throw new Error(`Node module "${module2.id}" is registered twice`);
23910
+ ids.add(module2.id);
23911
+ for (const provider of module2.families ?? []) {
23912
+ if (families.has(provider.family)) {
23913
+ throw new Error(
23914
+ `Channel family "${provider.family}" is claimed by two node modules (second: ${module2.id})`
23915
+ );
23916
+ }
23917
+ families.set(provider.family, provider);
23918
+ }
23919
+ for (const [name, verb] of Object.entries(module2.machineVerbs ?? {})) {
23920
+ if (!isFleetLinkVerb(name))
23921
+ throw new Error(`Machine verb "${name}" (module ${module2.id}) is not a valid verb name`);
23922
+ if (verbs.has(name))
23923
+ throw new Error(`Machine verb "${name}" is registered by two node modules (second: ${module2.id})`);
23924
+ verbs.set(name, verb);
23925
+ }
23926
+ for (const [type, handler] of Object.entries(module2.controlHandlers ?? {})) {
23927
+ if (controls.has(type))
23928
+ throw new Error(`Control type "${type}" is handled by two node modules (second: ${module2.id})`);
23929
+ controls.set(type, handler);
23930
+ }
23931
+ }
23932
+ const frozen = Object.freeze([...modules]);
23933
+ return {
23934
+ modules: frozen,
23935
+ tags(environment) {
23936
+ const out = [];
23937
+ for (const module2 of frozen) {
23938
+ for (const tag of module2.tags?.(environment) ?? []) {
23939
+ if (typeof tag === "string" && tag.length > 0 && !out.includes(tag)) out.push(tag);
23940
+ }
23941
+ }
23942
+ return out;
23943
+ },
23944
+ family: /* @__PURE__ */ __name((name) => families.get(name), "family"),
23945
+ familyConsent(name, params) {
23946
+ const provider = families.get(name);
23947
+ const declared = provider?.consent;
23948
+ if (typeof declared === "function") return declared(params);
23949
+ return declared ?? fleetLinkChannelOpenConsent(name);
23950
+ },
23951
+ machineVerb: /* @__PURE__ */ __name((name) => verbs.get(name), "machineVerb"),
23952
+ machineVerbs: /* @__PURE__ */ __name(() => [...verbs.keys()].sort(), "machineVerbs"),
23953
+ controlHandler: /* @__PURE__ */ __name((type) => controls.get(type), "controlHandler")
23954
+ };
23955
+ }
23956
+ __name(composeNodeModules, "composeNodeModules");
23957
+
23958
+ // src/core/link/modules/machine.ts
23959
+ var MACHINE_CHANNEL_MAX_IN_FLIGHT = 32;
23960
+ function errorFrom(thrown, signal) {
23961
+ if (thrown instanceof MachineVerbError) return thrown.error;
23962
+ if (signal.aborted) return makeFleetLinkError("operation_failed", "cancelled");
23963
+ const message = thrown instanceof Error ? thrown.message : String(thrown);
23964
+ const firstLine = message.split(/\r?\n/, 1)[0]?.trim() || "the request failed on the node";
23965
+ return makeFleetLinkError("operation_failed", firstLine.slice(0, 300));
23966
+ }
23967
+ __name(errorFrom, "errorFrom");
23968
+ function openMachineChannel(context, options) {
23969
+ const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
23970
+ const clearTimer = options.clearTimer ?? ((handle2) => clearTimeout(handle2));
23971
+ const inFlight = /* @__PURE__ */ new Map();
23972
+ const reply = /* @__PURE__ */ __name((message) => {
23973
+ if (!context.sendJson(message) && typeof message.requestId === "string") {
23974
+ context.sendJson({
23975
+ requestId: message.requestId,
23976
+ error: makeFleetLinkError("internal", "the reply could not be sent")
23977
+ });
23978
+ }
23979
+ }, "reply");
23980
+ const refuse = /* @__PURE__ */ __name((requestId, error) => {
23981
+ context.sendJson({ requestId, error });
23982
+ }, "refuse");
23983
+ const handle = /* @__PURE__ */ __name((raw) => {
23984
+ const request = asFleetLinkRequest(raw);
23985
+ if (!request) {
23986
+ if (isFleetLinkId(raw.requestId)) {
23987
+ refuse(
23988
+ raw.requestId,
23989
+ makeFleetLinkError("invalid", "a request needs a requestId, a type and object params")
23990
+ );
23991
+ }
23992
+ return;
23993
+ }
23994
+ const { requestId, type: name } = request;
23995
+ if (inFlight.has(requestId)) {
23996
+ refuse(requestId, makeFleetLinkError("invalid", "this requestId is already in flight"));
23997
+ return;
23998
+ }
23999
+ const verb = options.verb(name);
24000
+ if (!verb) {
24001
+ refuse(
24002
+ requestId,
24003
+ makeFleetLinkError(
24004
+ "unsupported_on_node",
24005
+ `${context.nodeName} (node ${context.nodeVersion}) cannot do this yet: ${name}`,
24006
+ { verb: name, nodeVersion: context.nodeVersion }
24007
+ )
24008
+ );
24009
+ return;
24010
+ }
24011
+ const consent = fleetLinkVerbConsent(name);
24012
+ if (!fleetConsentSatisfies(context.consents, consent)) {
24013
+ refuse(
24014
+ requestId,
24015
+ makeFleetLinkError("consent_missing", fleetConsentMissingHint(consent, context.nodeName), {
24016
+ verb: name,
24017
+ consent
24018
+ })
24019
+ );
24020
+ return;
24021
+ }
24022
+ if (verb.stateChanging && context.control.paused) {
24023
+ refuse(requestId, makeFleetLinkError("kill_switch", "clear it under Settings \u2192 Fleet"));
24024
+ return;
24025
+ }
24026
+ if (inFlight.size >= MACHINE_CHANNEL_MAX_IN_FLIGHT) {
24027
+ refuse(
24028
+ requestId,
24029
+ makeFleetLinkError(
24030
+ "busy",
24031
+ `${MACHINE_CHANNEL_MAX_IN_FLIGHT} requests are already running on this channel`
24032
+ )
24033
+ );
24034
+ return;
24035
+ }
24036
+ const abort = new AbortController();
24037
+ inFlight.set(requestId, abort);
24038
+ let operationId = null;
24039
+ let lastProgress = null;
24040
+ let settled = false;
24041
+ const becomeOperation = /* @__PURE__ */ __name(() => {
24042
+ if (settled || operationId !== null) return;
24043
+ inFlight.delete(requestId);
24044
+ operationId = context.operations.start(() => abort.abort());
24045
+ reply({ requestId, operationId });
24046
+ if (lastProgress) context.operations.progress(operationId, lastProgress);
24047
+ }, "becomeOperation");
24048
+ const inlineMs = Math.max(0, options.inlineReplyMs?.() ?? FLEET_LINK_INLINE_REPLY_MS);
24049
+ const timer = setTimer(becomeOperation, inlineMs);
24050
+ const finish = /* @__PURE__ */ __name((outcome) => {
24051
+ if (settled) return;
24052
+ settled = true;
24053
+ clearTimer(timer);
24054
+ if (operationId === null) {
24055
+ inFlight.delete(requestId);
24056
+ if (abort.signal.aborted) return;
24057
+ if (outcome.error) refuse(requestId, outcome.error);
24058
+ else reply({ requestId, result: outcome.result ?? null });
24059
+ return;
24060
+ }
24061
+ context.operations.done(operationId, outcome);
24062
+ }, "finish");
24063
+ let running;
24064
+ try {
24065
+ running = Promise.resolve(
24066
+ verb.run({
24067
+ verb: name,
24068
+ params: request.params ?? {},
24069
+ consents: context.consents,
24070
+ control: context.control,
24071
+ nodeVersion: context.nodeVersion,
24072
+ nodeName: context.nodeName,
24073
+ signal: abort.signal,
24074
+ logger: context.logger,
24075
+ progress: /* @__PURE__ */ __name((progress) => {
24076
+ lastProgress = progress;
24077
+ if (operationId !== null) context.operations.progress(operationId, progress);
24078
+ }, "progress")
24079
+ })
24080
+ );
24081
+ } catch (thrown) {
24082
+ running = Promise.reject(thrown);
24083
+ }
24084
+ running.then(
24085
+ (result) => finish({ result }),
24086
+ (thrown) => finish({ error: errorFrom(thrown, abort.signal) })
24087
+ );
24088
+ }, "handle");
24089
+ return {
24090
+ resumeKey: "machine",
24091
+ onJson: handle,
24092
+ onClose: /* @__PURE__ */ __name(() => {
24093
+ for (const abort of inFlight.values()) abort.abort();
24094
+ inFlight.clear();
24095
+ }, "onClose")
24096
+ };
24097
+ }
24098
+ __name(openMachineChannel, "openMachineChannel");
24099
+ function createMachineModule(options) {
24100
+ return {
24101
+ id: "machine",
24102
+ families: [
24103
+ {
24104
+ // Opening needs only the link (`fleetLinkChannelOpenConsent`, CONTRACTS
24105
+ // R-31); every request is checked against its verb's consent above.
24106
+ family: "machine",
24107
+ open: /* @__PURE__ */ __name((context) => ({ ok: true, handler: openMachineChannel(context, options) }), "open")
24108
+ }
24109
+ ]
24110
+ };
24111
+ }
24112
+ __name(createMachineModule, "createMachineModule");
24113
+
24114
+ // src/core/link/modules/index.ts
24115
+ var NODE_LINK_MODULES = [];
24116
+ function composeNodeLinkModules(options = {}) {
24117
+ const { extra = [], ...machineOptions } = options;
24118
+ let composed = null;
24119
+ const machine = createMachineModule({
24120
+ ...machineOptions,
24121
+ verb: /* @__PURE__ */ __name((name) => composed?.machineVerb(name), "verb")
24122
+ });
24123
+ composed = composeNodeModules([controlModule, machine, ...NODE_LINK_MODULES, ...extra]);
24124
+ return composed;
24125
+ }
24126
+ __name(composeNodeLinkModules, "composeNodeLinkModules");
24127
+
24128
+ // src/core/link/link-client.ts
24129
+ var MAX_PENDING_OPERATION_RESULTS = 64;
24130
+ var READY_TIMEOUT_MS = FLEET_LINK_AUTH_TIMEOUT_MS * 2;
24131
+ var MAX_HELLO_TAGS = 64;
24132
+ function createNodeLink(options) {
24133
+ return new NodeLinkClient(options);
24134
+ }
24135
+ __name(createNodeLink, "createNodeLink");
24136
+ var NodeLinkClient = class {
24137
+ constructor(options) {
24138
+ this.options = options;
24139
+ this.scheduler = options.scheduler ?? systemScheduler;
24140
+ this.now = options.now ?? (() => Date.now());
24141
+ this.backoff = new LinkBackoff({ random: options.random });
24142
+ this.modules = options.modules ?? composeNodeLinkModules({
24143
+ inlineReplyMs: /* @__PURE__ */ __name(() => this.limits.inlineReplyMs, "inlineReplyMs"),
24144
+ setTimer: /* @__PURE__ */ __name((callback, ms) => this.scheduler.setTimeout(callback, ms), "setTimer"),
24145
+ clearTimer: /* @__PURE__ */ __name((handle) => this.scheduler.clearTimeout(handle), "clearTimer")
24146
+ });
24147
+ const operations = {
24148
+ start: /* @__PURE__ */ __name((cancel) => {
24149
+ const id = `op-${(0, import_node_crypto3.randomUUID)()}`;
24150
+ this.operations.set(id, { cancel });
24151
+ return id;
24152
+ }, "start"),
24153
+ progress: /* @__PURE__ */ __name((operationId, progress) => {
24154
+ if (!this.operations.has(operationId)) return;
24155
+ if (this.state.phase === "connected") {
24156
+ this.mux.sendControl({ type: "operation.progress", operationId, progress });
24157
+ }
24158
+ }, "progress"),
24159
+ done: /* @__PURE__ */ __name((operationId, outcome) => {
24160
+ if (!this.operations.delete(operationId)) return;
24161
+ let done = { type: "operation.done", operationId };
24162
+ if (outcome.error) done.error = outcome.error;
24163
+ else done.result = outcome.result ?? null;
24164
+ if (encodeFleetLinkControl(done) === null) {
24165
+ done = {
24166
+ type: "operation.done",
24167
+ operationId,
24168
+ error: makeFleetLinkError(
24169
+ "operation_failed",
24170
+ `the result was too large to send back (over ${Math.round(FLEET_LINK_MAX_JSON_BYTES / (1024 * 1024))} MiB)`
24171
+ )
24172
+ };
24173
+ }
24174
+ if (this.state.phase !== "connected" || !this.mux.sendControl(done)) this.keepResult(done);
24175
+ }, "done")
24176
+ };
24177
+ this.mux = new ChannelMux({
24178
+ modules: this.modules,
24179
+ consents: options.consents,
24180
+ control: this.control,
24181
+ operations,
24182
+ nodeVersion: options.nodeVersion,
24183
+ nodeName: options.nodeName?.trim() || "this computer",
24184
+ logger: options.logger
24185
+ });
24186
+ this.control.onChange(({ paused }) => this.patch({ paused }));
24187
+ }
24188
+ options;
24189
+ static {
24190
+ __name(this, "NodeLinkClient");
24191
+ }
24192
+ control = createLinkControlState();
24193
+ modules;
24194
+ scheduler;
24195
+ now;
24196
+ backoff;
24197
+ mux;
24198
+ listeners = /* @__PURE__ */ new Set();
24199
+ operations = /* @__PURE__ */ new Map();
24200
+ pendingResults = [];
24201
+ limits = FLEET_LINK_DEFAULT_LIMITS;
24202
+ started = false;
24203
+ closed = false;
24204
+ socket = null;
24205
+ reconnectTimer = null;
24206
+ tickTimer = null;
24207
+ readyTimer = null;
24208
+ resumeExpiry = null;
24209
+ lastFrameAt = 0;
24210
+ /** The generation to present on the next hello (the last one this node held). */
24211
+ previousGeneration = null;
24212
+ resumeChannels = [];
24213
+ refusalLogged = false;
24214
+ /** The single-use token of the attempt in flight, protected in the logger until the attempt ends. */
24215
+ attemptToken = null;
24216
+ dropLogged = false;
24217
+ state = {
24218
+ phase: "idle",
24219
+ detail: null,
24220
+ linkGeneration: null,
24221
+ connectedSince: null,
24222
+ lastDisconnectAt: null,
24223
+ bytesIn: 0,
24224
+ bytesOut: 0,
24225
+ openChannels: 0,
24226
+ paused: false,
24227
+ nextAttemptInMs: null
24228
+ };
24229
+ getState() {
24230
+ return { ...this.state, openChannels: this.mux.size };
24231
+ }
24232
+ onChange(listener) {
24233
+ this.listeners.add(listener);
24234
+ return () => {
24235
+ this.listeners.delete(listener);
24236
+ };
24237
+ }
24238
+ start() {
24239
+ if (this.started || this.closed) return;
24240
+ this.started = true;
24241
+ if (!isFleetLinkEligible(this.options.consents)) {
24242
+ this.patch({
24243
+ phase: "not-open",
24244
+ detail: "not open: this computer only reports health (start it with --work, --attend or --manage)"
24245
+ });
24246
+ this.options.logger?.info(
24247
+ "Node link not opened: no --work, --attend or --manage consent \u2014 this computer only reports health."
24248
+ );
24249
+ return;
24250
+ }
24251
+ if (!isSecureLinkOrigin(this.options.apiUrl)) {
24252
+ this.patch({ phase: "insecure", detail: "not open: the platform is reached over plain http" });
24253
+ this.options.logger?.warn(
24254
+ "Node link not opened: the platform is reached over plain http, and a link token is only sent over https (or to localhost)."
24255
+ );
24256
+ return;
24257
+ }
24258
+ if (!this.options.factory) {
24259
+ this.patch({ phase: "unavailable", detail: "not open: this runtime has no WebSocket client" });
24260
+ this.options.logger?.warn("Node link not opened: this runtime has no WebSocket client.");
24261
+ return;
24262
+ }
24263
+ void this.connect();
24264
+ }
24265
+ close() {
24266
+ if (this.closed) return;
24267
+ this.closed = true;
24268
+ this.clearTimer("reconnectTimer");
24269
+ this.clearTimer("tickTimer");
24270
+ this.clearTimer("readyTimer");
24271
+ this.clearTimer("resumeExpiry");
24272
+ this.mux.closeAll("normal");
24273
+ const socket = this.socket;
24274
+ this.socket = null;
24275
+ this.mux.bind(null);
24276
+ if (socket) {
24277
+ this.detach(socket);
24278
+ try {
24279
+ socket.close(FLEET_LINK_CLOSE_CODES.normal, "node shutting down");
24280
+ } catch {
24281
+ }
24282
+ }
24283
+ this.forgetToken();
24284
+ for (const operation of this.operations.values()) operation.cancel();
24285
+ this.operations.clear();
24286
+ this.patch({ phase: "closed", detail: null, nextAttemptInMs: null, linkGeneration: null });
24287
+ }
24288
+ // ─── Connecting ────────────────────────────────────────────────────────
24289
+ async connect() {
24290
+ if (this.closed) return;
24291
+ this.patch({
24292
+ phase: this.previousGeneration === null ? "connecting" : "reconnecting",
24293
+ nextAttemptInMs: null
24294
+ });
24295
+ let minted;
24296
+ try {
24297
+ minted = await this.options.mintToken();
24298
+ } catch (error) {
24299
+ if (this.closed) return;
24300
+ if (error instanceof FleetClientError && error.status === 404) {
24301
+ this.refused();
24302
+ return;
24303
+ }
24304
+ this.options.logger?.warn(`Node link: could not get a link token: ${describe4(error)}`);
24305
+ this.scheduleReconnect(this.backoff.next());
24306
+ return;
24307
+ }
24308
+ if (this.closed) return;
24309
+ this.forgetToken();
24310
+ this.attemptToken = minted.token;
24311
+ this.options.logger?.protect(minted.token);
24312
+ let capabilities;
24313
+ try {
24314
+ capabilities = [...await this.options.capabilities()].slice(0, MAX_HELLO_TAGS);
24315
+ } catch {
24316
+ capabilities = [];
24317
+ }
24318
+ if (this.closed) return;
24319
+ let socket;
24320
+ try {
24321
+ socket = this.options.factory(toLinkWsUrl(this.options.apiUrl, minted.wsPath || FLEET_LINK_WS_PATH));
24322
+ } catch (error) {
24323
+ this.forgetToken();
24324
+ this.options.logger?.warn(`Node link: could not open a socket: ${describe4(error)}`);
24325
+ this.scheduleReconnect(this.backoff.next());
24326
+ return;
24327
+ }
24328
+ this.socket = socket;
24329
+ this.patch({ bytesIn: 0, bytesOut: 0 });
24330
+ socket.onopen = () => {
24331
+ if (this.socket !== socket) return;
24332
+ const hello = {
24333
+ type: "link.hello",
24334
+ nodeId: this.options.nodeId,
24335
+ linkVersion: FLEET_LINK_VERSION,
24336
+ capabilities,
24337
+ consents: { ...this.options.consents },
24338
+ token: minted.token,
24339
+ nodeVersion: this.options.nodeVersion
24340
+ };
24341
+ if (this.previousGeneration !== null) {
24342
+ hello.resume = { linkGeneration: this.previousGeneration, channels: this.resumeChannels };
24343
+ }
24344
+ this.mux.bind((frame) => this.sendOn(socket, frame));
24345
+ this.mux.sendControl(hello);
24346
+ this.lastFrameAt = this.now();
24347
+ this.clearTimer("readyTimer");
24348
+ this.readyTimer = this.scheduler.setTimeout(() => {
24349
+ this.readyTimer = null;
24350
+ if (this.socket === socket && this.state.phase !== "connected") {
24351
+ this.dropSocket(socket, FLEET_LINK_CLOSE_CODES.protocol, "no link.ready");
24352
+ }
24353
+ }, READY_TIMEOUT_MS);
24354
+ };
24355
+ socket.onmessage = (event) => {
24356
+ if (this.socket === socket) this.onBytes(event.data);
24357
+ };
24358
+ socket.onerror = () => void 0;
24359
+ socket.onclose = (event) => {
24360
+ if (this.socket === socket) this.dropSocket(socket, linkCloseCode(event), null);
24361
+ };
24362
+ }
24363
+ refused() {
24364
+ this.patch({
24365
+ phase: "refused",
24366
+ detail: "refused by the platform: this deployment does not accept node links yet"
24367
+ });
24368
+ if (!this.refusalLogged) {
24369
+ this.refusalLogged = true;
24370
+ this.options.logger?.info("The platform does not accept node links yet \u2014 trying again in 5 minutes.");
24371
+ }
24372
+ this.scheduleReconnect(FLEET_LINK_REFUSED_RETRY_MS);
24373
+ }
24374
+ scheduleReconnect(delayMs) {
24375
+ if (this.closed) return;
24376
+ this.clearTimer("reconnectTimer");
24377
+ this.patch({ nextAttemptInMs: delayMs });
24378
+ this.reconnectTimer = this.scheduler.setTimeout(() => {
24379
+ this.reconnectTimer = null;
24380
+ void this.connect();
24381
+ }, delayMs);
24382
+ }
24383
+ // ─── Receiving ─────────────────────────────────────────────────────────
24384
+ onBytes(data) {
24385
+ const bytes = linkMessageBytes(data);
24386
+ if (!bytes) return;
24387
+ this.lastFrameAt = this.now();
24388
+ this.state.bytesIn += bytes.byteLength;
24389
+ if (bytes.byteLength > FLEET_LINK_MAX_FRAME_BYTES) {
24390
+ const channel = peekFleetLinkFrameChannel(bytes);
24391
+ if (channel !== null && channel > 0) this.mux.closeLocal(channel, "invalid", "frame over 16 MiB");
24392
+ return;
24393
+ }
24394
+ const frame = decodeFleetLinkFrame(bytes);
24395
+ if (!frame) return;
24396
+ if (frame.channel !== 0) {
24397
+ if (this.state.phase === "connected") this.mux.deliver(frame);
24398
+ return;
24399
+ }
24400
+ const message = decodeFleetLinkControl(frame.payload);
24401
+ if (!message) return;
24402
+ if (isFleetLinkControlMessage(message)) this.onControl(message);
24403
+ else if (this.state.phase === "connected") this.onExtension(message);
24404
+ }
24405
+ onControl(message) {
24406
+ if (this.state.phase !== "connected") {
24407
+ if (message.type === "link.ready") this.onReady(message);
24408
+ return;
24409
+ }
24410
+ switch (message.type) {
24411
+ case "link.ping":
24412
+ this.mux.sendControl({ type: "link.pong", t: message.t });
24413
+ return;
24414
+ case "link.pause":
24415
+ if (!this.control.paused) {
24416
+ this.options.logger?.warn("The fleet kill switch is on: live actions on this computer are paused.");
24417
+ }
24418
+ this.control.pause();
24419
+ return;
24420
+ case "link.resume":
24421
+ if (this.control.paused)
24422
+ this.options.logger?.info("The fleet kill switch was cleared: live actions resume.");
24423
+ this.control.resume();
24424
+ return;
24425
+ case "link.drain":
24426
+ this.options.logger?.warn(`Drain all: owner sessions on this computer end in ${message.graceSec} s.`);
24427
+ this.control.startDrain(message, this.now());
24428
+ return;
24429
+ case "channel.open":
24430
+ this.mux.open(message).catch((error) => {
24431
+ this.options.logger?.warn(
24432
+ `Node link: opening channel ${message.channel} failed: ${describe4(error)}`
24433
+ );
24434
+ });
24435
+ return;
24436
+ case "channel.close":
24437
+ this.mux.closeRemote(message.channel, message.reason);
24438
+ return;
24439
+ case "operation.cancel":
24440
+ this.operations.get(message.operationId)?.cancel();
24441
+ return;
24442
+ default:
24443
+ return;
24444
+ }
24445
+ }
24446
+ onReady(message) {
24447
+ const socket = this.socket;
24448
+ if (!socket) return;
24449
+ this.clearTimer("readyTimer");
24450
+ this.clearTimer("resumeExpiry");
24451
+ this.forgetToken();
24452
+ this.limits = message.limits;
24453
+ this.previousGeneration = message.linkGeneration;
24454
+ if (this.control.paused) {
24455
+ this.control.resume();
24456
+ this.options.logger?.info(
24457
+ "Node link reconnected: the earlier pause is lifted unless the platform repeats it."
24458
+ );
24459
+ }
24460
+ this.resumeChannels = [];
24461
+ this.backoff.reset();
24462
+ this.refusalLogged = false;
24463
+ this.dropLogged = false;
24464
+ this.patch({
24465
+ phase: "connected",
24466
+ detail: null,
24467
+ linkGeneration: message.linkGeneration,
24468
+ connectedSince: this.now(),
24469
+ nextAttemptInMs: null
24470
+ });
24471
+ this.mux.resumeGeneration(message.resumed, message.closed);
24472
+ this.options.logger?.info(
24473
+ `Node link connected (generation ${message.linkGeneration}${message.resumed.length > 0 ? `, ${message.resumed.length} channel(s) resumed` : ""}).`
24474
+ );
24475
+ this.flushResults();
24476
+ this.scheduleTick();
24477
+ }
24478
+ onExtension(message) {
24479
+ const handler = this.modules.controlHandler(message.type);
24480
+ if (!handler) return;
24481
+ try {
24482
+ handler(message, { consents: this.options.consents, control: this.control, logger: this.options.logger });
24483
+ } catch (error) {
24484
+ this.options.logger?.warn(`Node link: ${message.type} handler failed: ${describe4(error)}`);
24485
+ }
24486
+ }
24487
+ // ─── Keeping it alive ──────────────────────────────────────────────────
24488
+ scheduleTick() {
24489
+ this.clearTimer("tickTimer");
24490
+ this.tickTimer = this.scheduler.setTimeout(() => {
24491
+ this.tickTimer = null;
24492
+ this.tick();
24493
+ }, this.limits.pingIntervalMs);
24494
+ }
24495
+ tick() {
24496
+ const socket = this.socket;
24497
+ if (!socket || this.state.phase !== "connected") return;
24498
+ if (this.now() - this.lastFrameAt >= this.limits.silenceMs) {
24499
+ this.options.logger?.warn(
24500
+ `Node link: nothing heard for ${Math.round(this.limits.silenceMs / 1e3)} s \u2014 reconnecting.`
24501
+ );
24502
+ this.dropSocket(socket, FLEET_LINK_CLOSE_CODES.silence, "silence");
24503
+ return;
24504
+ }
24505
+ this.mux.sendControl({ type: "link.ping", t: this.now() });
24506
+ this.scheduleTick();
24507
+ }
24508
+ /**
24509
+ * The socket is gone (closed by the platform, by silence, or by us). Run
24510
+ * the drop once: suspend resumable channels, then decide whether and when
24511
+ * to come back.
24512
+ */
24513
+ dropSocket(socket, code, reason) {
24514
+ if (this.socket !== socket) return;
24515
+ this.socket = null;
24516
+ this.detach(socket);
24517
+ this.forgetToken();
24518
+ if (reason !== null) {
24519
+ try {
24520
+ socket.close(code ?? FLEET_LINK_CLOSE_CODES.normal, reason);
24521
+ } catch {
24522
+ }
24523
+ }
24524
+ this.clearTimer("tickTimer");
24525
+ this.clearTimer("readyTimer");
24526
+ const wasConnected = this.state.phase === "connected";
24527
+ this.resumeChannels = this.mux.suspend();
24528
+ this.patch({ linkGeneration: null, connectedSince: null, lastDisconnectAt: this.now() });
24529
+ if (this.closed) return;
24530
+ if (this.resumeChannels.length > 0) this.armResumeExpiry();
24531
+ if (code === FLEET_LINK_CLOSE_CODES.disabled) {
24532
+ this.refused();
24533
+ return;
24534
+ }
24535
+ if (code === FLEET_LINK_CLOSE_CODES.noConsent) {
24536
+ this.mux.closeAll("consent_missing");
24537
+ this.patch({ phase: "not-open", detail: "refused by the platform: no consent that opens a link" });
24538
+ this.options.logger?.warn("The platform refused the node link: no --work, --attend or --manage consent.");
24539
+ return;
24540
+ }
24541
+ if (code === FLEET_LINK_CLOSE_CODES.replaced) {
24542
+ this.options.logger?.warn("Node link replaced by a newer link for this node \u2014 is it running twice?");
24543
+ }
24544
+ const wait = code === FLEET_LINK_CLOSE_CODES.rateLimited ? Math.max(this.backoff.next(), FLEET_LINK_HELLO_BLOCK_MS) : this.backoff.next();
24545
+ if (wasConnected && !this.dropLogged) {
24546
+ this.dropLogged = true;
24547
+ this.options.logger?.warn(
24548
+ `Node link lost${code !== null ? ` (code ${code})` : ""} \u2014 reconnecting in ${Math.round(wait / 1e3)} s.`
24549
+ );
24550
+ }
24551
+ this.patch({ phase: "reconnecting", detail: "reconnecting" });
24552
+ this.scheduleReconnect(wait);
24553
+ }
24554
+ /** Channels held for a reconnect close `resume_failed` if none comes within the window. */
24555
+ armResumeExpiry() {
24556
+ this.clearTimer("resumeExpiry");
24557
+ this.resumeExpiry = this.scheduler.setTimeout(() => {
24558
+ this.resumeExpiry = null;
24559
+ if (this.state.phase === "connected") return;
24560
+ this.mux.closeAll("resume_failed");
24561
+ this.resumeChannels = [];
24562
+ }, FLEET_LINK_RESUME_WINDOW_MS);
24563
+ }
24564
+ // ─── Sending ───────────────────────────────────────────────────────────
24565
+ sendOn(socket, frame) {
24566
+ if (this.socket !== socket || socket.readyState !== LINK_SOCKET_OPEN) return false;
24567
+ try {
24568
+ socket.send(frame);
24569
+ } catch {
24570
+ return false;
24571
+ }
24572
+ this.state.bytesOut += frame.byteLength;
24573
+ return true;
24574
+ }
24575
+ keepResult(done) {
24576
+ this.pendingResults.push(done);
24577
+ if (this.pendingResults.length > MAX_PENDING_OPERATION_RESULTS) this.pendingResults.shift();
24578
+ }
24579
+ flushResults() {
24580
+ const pending = this.pendingResults;
24581
+ this.pendingResults = [];
24582
+ for (const done of pending) {
24583
+ if (!this.mux.sendControl(done)) this.keepResult(done);
24584
+ }
24585
+ }
24586
+ // ─── Plumbing ──────────────────────────────────────────────────────────
24587
+ /** A link token is single-use: once its attempt ended it is dead, and need not stay in the redactor. */
24588
+ forgetToken() {
24589
+ if (this.attemptToken === null) return;
24590
+ this.options.logger?.unprotect(this.attemptToken);
24591
+ this.attemptToken = null;
24592
+ }
24593
+ detach(socket) {
24594
+ socket.onopen = null;
24595
+ socket.onmessage = null;
24596
+ socket.onclose = null;
24597
+ socket.onerror = null;
24598
+ this.mux.bind(null);
24599
+ }
24600
+ clearTimer(name) {
24601
+ const handle = this[name];
24602
+ if (handle !== null) {
24603
+ this.scheduler.clearTimeout(handle);
24604
+ this[name] = null;
24605
+ }
24606
+ }
24607
+ patch(next) {
24608
+ this.state = { ...this.state, ...next };
24609
+ const snapshot = this.getState();
24610
+ for (const listener of [...this.listeners]) {
24611
+ try {
24612
+ listener(snapshot);
24613
+ } catch {
24614
+ }
24615
+ }
24616
+ }
24617
+ };
24618
+ function describe4(error) {
24619
+ return error instanceof Error ? error.message : String(error);
24620
+ }
24621
+ __name(describe4, "describe");
24622
+
24623
+ // src/core/link/link-record.ts
24624
+ var PHASES = /* @__PURE__ */ new Set([
24625
+ "idle",
24626
+ "not-open",
24627
+ "insecure",
24628
+ "unavailable",
24629
+ "connecting",
24630
+ "connected",
24631
+ "reconnecting",
24632
+ "refused",
24633
+ "closed"
24634
+ ]);
24635
+ function nodeLinkRecordPath(configPath) {
24636
+ return `${configPath}.link.json`;
24637
+ }
24638
+ __name(nodeLinkRecordPath, "nodeLinkRecordPath");
24639
+ function toNodeLinkRecord(consents, state, now) {
24640
+ return {
24641
+ version: 1,
24642
+ recordedAt: new Date(now).toISOString(),
24643
+ consents: { ...consents },
24644
+ phase: state.phase,
24645
+ detail: state.detail,
24646
+ linkGeneration: state.linkGeneration,
24647
+ connectedSince: state.connectedSince === null ? null : new Date(state.connectedSince).toISOString()
24648
+ };
24649
+ }
24650
+ __name(toNodeLinkRecord, "toNodeLinkRecord");
24651
+ function parseNodeLinkRecord(raw) {
24652
+ if (!raw) return null;
24653
+ let parsed;
24654
+ try {
24655
+ parsed = JSON.parse(raw);
24656
+ } catch {
24657
+ return null;
24658
+ }
24659
+ if (!parsed || typeof parsed !== "object") return null;
24660
+ const record = parsed;
24661
+ const consents = normalizeFleetConsents(record.consents);
24662
+ if (record.version !== 1 || typeof record.recordedAt !== "string" || !Number.isFinite(Date.parse(record.recordedAt)) || consents === null || typeof record.phase !== "string" || !PHASES.has(record.phase)) {
24663
+ return null;
24664
+ }
24665
+ return {
24666
+ version: 1,
24667
+ recordedAt: record.recordedAt,
24668
+ consents,
24669
+ phase: record.phase,
24670
+ detail: typeof record.detail === "string" ? record.detail.slice(0, 300) : null,
24671
+ linkGeneration: typeof record.linkGeneration === "number" && Number.isSafeInteger(record.linkGeneration) ? record.linkGeneration : null,
24672
+ connectedSince: typeof record.connectedSince === "string" && Number.isFinite(Date.parse(record.connectedSince)) ? record.connectedSince : null
24673
+ };
24674
+ }
24675
+ __name(parseNodeLinkRecord, "parseNodeLinkRecord");
24676
+ async function readNodeLinkRecord(fs10, configPath) {
24677
+ try {
24678
+ return parseNodeLinkRecord(await fs10.readFile(nodeLinkRecordPath(configPath)));
24679
+ } catch {
24680
+ return null;
24681
+ }
24682
+ }
24683
+ __name(readNodeLinkRecord, "readNodeLinkRecord");
24684
+ async function writeNodeLinkRecord(fs10, configPath, record) {
24685
+ const path2 = nodeLinkRecordPath(configPath);
24686
+ await fs10.mkdir(fs10.dirname(path2));
24687
+ await fs10.writeFile(path2, `${JSON.stringify(record, null, 2)}
24688
+ `);
24689
+ }
24690
+ __name(writeNodeLinkRecord, "writeNodeLinkRecord");
24691
+ function describeConsents(consents) {
24692
+ const on = ["work", "attend", "manage", "usageScan"].filter((key) => consents[key]).map((key) => key === "usageScan" ? "usage-scan" : key);
24693
+ return on.length > 0 ? on.join(" \xB7 ") : "none (enrolled only \u2014 reports health)";
24694
+ }
24695
+ __name(describeConsents, "describeConsents");
24696
+ function describeLinkRecord(record, now) {
24697
+ if (!record) return "unknown (the service has not reported its link yet)";
24698
+ const age = Math.max(0, Math.round((now - Date.parse(record.recordedAt)) / 1e3));
24699
+ const heard = `as of ${record.recordedAt}${age > 120 ? ` (${age}s ago)` : ""}`;
24700
+ switch (record.phase) {
24701
+ case "connected":
24702
+ return `connected since ${record.connectedSince ?? record.recordedAt} (generation ${record.linkGeneration ?? "?"}) \u2014 ${heard}`;
24703
+ case "reconnecting":
24704
+ case "connecting":
24705
+ return `${record.phase}\u2026 \u2014 ${heard}`;
24706
+ default:
24707
+ return `${record.detail ?? record.phase} \u2014 ${heard}`;
24708
+ }
24709
+ }
24710
+ __name(describeLinkRecord, "describeLinkRecord");
24711
+
24712
+ // src/core/runtime.ts
24713
+ function buildSelfDescriptionTelemetry(io, cliCompat) {
24714
+ const pinned = /* @__PURE__ */ __name(() => io.environment.modelCli ?? {}, "pinned");
24715
+ const telemetry = {
24716
+ cliVersion: cliCompat ? async () => primaryPinnedCliVersion(await cliCompat.probeAll(pinned())) ?? detectAgentCliVersion(io.runner) : () => detectAgentCliVersion(io.runner),
24717
+ modelIdentity: cacheProbe(() => detectModelIdentity(io.runner, io.environment.modelCli ?? {}))
24718
+ };
24719
+ if (cliCompat) {
24720
+ telemetry.cliVersions = async () => describeCliVersions(await cliCompat.probeAll(pinned()));
24721
+ }
24722
+ if (io.diskProbe) {
24723
+ const probe = io.diskProbe;
24724
+ const path2 = io.workspacePath ?? process.cwd();
24725
+ telemetry.diskFreeBytes = () => measureWorkspaceFreeBytes(probe, path2);
24726
+ }
24727
+ return telemetry;
24728
+ }
24729
+ __name(buildSelfDescriptionTelemetry, "buildSelfDescriptionTelemetry");
24730
+ function clampHeartbeatInterval(intervalMs) {
21484
24731
  if (typeof intervalMs !== "number" || !Number.isFinite(intervalMs)) {
21485
24732
  return DEFAULT_HEARTBEAT_INTERVAL_MS;
21486
24733
  }
@@ -21554,6 +24801,7 @@ function createNodeRuntime(config, io, options = {}) {
21554
24801
  });
21555
24802
  const selection = config.capabilitySelection ?? null;
21556
24803
  const workspaceRoot = options.agentTaskWorkspaceRoot ?? defaultFleetTaskWorkspaceRoot();
24804
+ const executionPolicy = resolveNodeExecutionPolicy(config.executionPolicy);
21557
24805
  const cliCompat = new ModelCliCompatibilityProbe({
21558
24806
  runner: io.runner,
21559
24807
  platform: io.environment.platform,
@@ -21569,15 +24817,45 @@ function createNodeRuntime(config, io, options = {}) {
21569
24817
  },
21570
24818
  cliCompat
21571
24819
  );
24820
+ let loopRef = null;
24821
+ if (io.health) {
24822
+ const probeHealth = createHealthProbe(io.health, {
24823
+ workspacePath: io.workspacePath ?? workspaceRoot,
24824
+ ...io.now ? { now: io.now } : {}
24825
+ });
24826
+ telemetry.health = () => loopRef && !loopRef.acceptsField("health") ? null : probeHealth();
24827
+ }
21572
24828
  const attendedCapture = options.attendEnabled ? selectAttendedCapture(io.environment, options) : null;
21573
- const environment = attendedCapture ? {
24829
+ const consents = {
24830
+ work: options.workerEnabled === true,
24831
+ attend: attendedCapture !== null,
24832
+ manage: options.manageEnabled === true,
24833
+ usageScan: false
24834
+ };
24835
+ const linkModules = composeNodeLinkModules({
24836
+ ...options.linkModules ? { extra: options.linkModules } : {},
24837
+ ...io.scheduler ? {
24838
+ setTimer: /* @__PURE__ */ __name((callback, ms) => io.scheduler.setTimeout(callback, ms), "setTimer"),
24839
+ clearTimer: /* @__PURE__ */ __name((handle) => io.scheduler.clearTimeout(handle), "clearTimer")
24840
+ } : {}
24841
+ });
24842
+ const linkModuleTags = linkModules.tags({ consents, platform: io.environment.platform, nodeVersion: io.version });
24843
+ const environment = {
21574
24844
  ...io.environment,
21575
- attended: true,
21576
- captureBackends: attendedCapture.captureBackend ? [attendedCapture.captureBackend] : []
21577
- } : io.environment;
24845
+ ...attendedCapture ? {
24846
+ attended: true,
24847
+ captureBackends: attendedCapture.captureBackend ? [attendedCapture.captureBackend] : []
24848
+ } : {},
24849
+ ...consents.manage ? { managed: true } : {},
24850
+ ...linkModuleTags.length > 0 ? { linkModuleTags } : {}
24851
+ };
24852
+ telemetry.consents = () => consents;
24853
+ let lastDescribedCapabilities = [];
24854
+ let startLink = null;
21578
24855
  let attendedWake = null;
21579
24856
  const holdableLanes = [];
21580
24857
  let limitTarget = null;
24858
+ let throttleTarget = null;
21581
24859
  const lifecycle = new NodeLifecycleTracker({
21582
24860
  daemonVersion: io.version,
21583
24861
  logger: io.logger,
@@ -21593,7 +24871,11 @@ function createNodeRuntime(config, io, options = {}) {
21593
24871
  // Re-probed on EVERY beat, like the capability tags: installing an
21594
24872
  // agent CLI or filling a disk is exactly the kind of change an
21595
24873
  // operator needs to see without restarting the node.
21596
- describe: /* @__PURE__ */ __name(() => describeSelf(io.runner, environment, io.version, selection, telemetry), "describe"),
24874
+ describe: /* @__PURE__ */ __name(async () => {
24875
+ const description = await describeSelf(io.runner, environment, io.version, selection, telemetry);
24876
+ lastDescribedCapabilities = description.capabilities;
24877
+ return description;
24878
+ }, "describe"),
21597
24879
  intervalMs: clampHeartbeatInterval(config.heartbeatIntervalMs),
21598
24880
  logger: io.logger,
21599
24881
  ...io.scheduler ? { scheduler: io.scheduler } : {},
@@ -21601,9 +24883,13 @@ function createNodeRuntime(config, io, options = {}) {
21601
24883
  onAccepted: /* @__PURE__ */ __name((response) => {
21602
24884
  attendedWake?.(response.pendingComputerSessions);
21603
24885
  lifecycle.applyHeartbeat(response);
24886
+ throttleTarget?.(response.node?.modelThrottles);
24887
+ startLink?.();
21604
24888
  }, "onAccepted")
21605
24889
  };
21606
- const runtime = { client, loop: new HeartbeatLoop(loopOptions), lifecycle, cliCompat };
24890
+ const loop = new HeartbeatLoop(loopOptions);
24891
+ loopRef = loop;
24892
+ const runtime = { client, loop, lifecycle, cliCompat, consents };
21607
24893
  let sharedJobClient = null;
21608
24894
  const jobClientFor = /* @__PURE__ */ __name(() => sharedJobClient ??= new FleetJobClient({
21609
24895
  apiUrl: apiBase.url,
@@ -21613,6 +24899,46 @@ function createNodeRuntime(config, io, options = {}) {
21613
24899
  logger: io.logger,
21614
24900
  userAgent
21615
24901
  }), "jobClientFor");
24902
+ if (options.nodeLink) {
24903
+ const link = createNodeLink({
24904
+ apiUrl: apiBase.url,
24905
+ nodeId: config.nodeId,
24906
+ ...config.name ? { nodeName: config.name } : {},
24907
+ nodeVersion: io.version,
24908
+ consents,
24909
+ capabilities: /* @__PURE__ */ __name(() => lastDescribedCapabilities, "capabilities"),
24910
+ mintToken: /* @__PURE__ */ __name(() => jobClientFor().mintLinkToken(), "mintToken"),
24911
+ factory: options.linkSocketFactory === void 0 ? defaultLinkSocketFactory() : options.linkSocketFactory,
24912
+ modules: linkModules,
24913
+ logger: io.logger,
24914
+ ...io.scheduler ? { scheduler: io.scheduler } : {},
24915
+ ...io.now ? { now: io.now } : {}
24916
+ });
24917
+ runtime.link = link;
24918
+ let predatesLogged = false;
24919
+ startLink = /* @__PURE__ */ __name(() => {
24920
+ if (runtime.loop.acceptsField("consents")) {
24921
+ link.start();
24922
+ } else if (!predatesLogged) {
24923
+ predatesLogged = true;
24924
+ io.logger.warn("Node link not opened: the platform predates consents in the heartbeat.");
24925
+ }
24926
+ }, "startLink");
24927
+ if (options.persistLinkState) {
24928
+ const persist = options.persistLinkState;
24929
+ let lastKey = "";
24930
+ link.onChange((state) => {
24931
+ const key = `${state.phase}|${state.linkGeneration ?? ""}|${state.detail ?? ""}`;
24932
+ if (key === lastKey) return;
24933
+ lastKey = key;
24934
+ void Promise.resolve().then(() => persist(toNodeLinkRecord(consents, state, io.now ? io.now() : Date.now()))).catch(
24935
+ (error) => io.logger.warn(
24936
+ `Could not record the node link state: ${error instanceof Error ? error.message : String(error)}`
24937
+ )
24938
+ );
24939
+ });
24940
+ }
24941
+ }
21616
24942
  if (options.workerEnabled) {
21617
24943
  const jobClient = jobClientFor();
21618
24944
  const limits = clampResourceLimits(
@@ -21640,9 +24966,22 @@ function createNodeRuntime(config, io, options = {}) {
21640
24966
  ...io.monotonicNow ? { monotonicNow: io.monotonicNow } : {},
21641
24967
  // Agent computers: on an attended machine, live views belong to the
21642
24968
  // attended lane alone — never parked in (or holding a slot of) the
21643
- // work lane. Sent only under `--attend`, so a work-only node's lease
21644
- // body is exactly what it always was.
21645
- ...options.attendEnabled ? { excludeKinds: ["computer-session"] } : {}
24969
+ // work lane (only under `--attend`).
24970
+ //
24971
+ // Control 18: a machine whose owner has not allowed node-run shell
24972
+ // commands does not lease `acceptance-checks` jobs at all (every one
24973
+ // is a shell command in a directory the platform names), so they
24974
+ // stay with a node that will run them instead of failing here.
24975
+ ...(() => {
24976
+ const excluded = [];
24977
+ if (options.attendEnabled) excluded.push("computer-session");
24978
+ if (!executionPolicy.allowShellCommands) excluded.push("acceptance-checks");
24979
+ return excluded.length > 0 ? { excludeKinds: excluded } : {};
24980
+ })(),
24981
+ // Fleet usage limits: the CLIs an agent-task can run with here, so a
24982
+ // node whose only CLI is out of quota stops leasing agent-task work
24983
+ // until the reset (other kinds keep leasing).
24984
+ modelProviders: /* @__PURE__ */ __name(() => Object.entries(options.modelCli ?? io.environment.modelCli ?? {}).filter(([, path2]) => typeof path2 === "string" && path2.length > 0).map(([provider]) => provider), "modelProviders")
21646
24985
  });
21647
24986
  holdableLanes.push(worker);
21648
24987
  worker.onUpgradeRequired((hold) => lifecycle.applyLeaseRefusal(hold.minNodeVersion));
@@ -21654,6 +24993,9 @@ function createNodeRuntime(config, io, options = {}) {
21654
24993
  }
21655
24994
  }, "limitTarget");
21656
24995
  telemetry.limits = () => worker.resourceLimits;
24996
+ throttleTarget = /* @__PURE__ */ __name((throttles) => {
24997
+ if (Array.isArray(throttles)) worker.syncModelThrottles(throttles);
24998
+ }, "throttleTarget");
21657
24999
  telemetry.workerHealth = () => describeWorkerHealth(worker.getState());
21658
25000
  const housekeeping = new NodeHousekeepingReporter({
21659
25001
  minFreeDiskBytes: io.diskProbe ? effectiveMinFreeDiskBytes(limits) : null,
@@ -21666,7 +25008,13 @@ function createNodeRuntime(config, io, options = {}) {
21666
25008
  ...io.diskProbe ? { diskProbe: io.diskProbe } : {},
21667
25009
  minFreeDiskBytes: effectiveMinFreeDiskBytes(limits)
21668
25010
  });
21669
- worker.register("acceptance-checks", (job, signal) => runAcceptanceChecksJob(job, {}, signal));
25011
+ worker.register(
25012
+ "acceptance-checks",
25013
+ (job, signal) => (
25014
+ // Control 18: refused here too, should one arrive despite the lease filter.
25015
+ runAcceptanceChecksJob(job, { executionPolicy }, signal)
25016
+ )
25017
+ );
21670
25018
  const modelCli = options.modelCli ?? io.environment.modelCli ?? {};
21671
25019
  worker.register("agent-task", async (job, signal, lease) => {
21672
25020
  const pushCredentials = lease ? new PushCredentialSession({
@@ -21773,6 +25121,11 @@ function createNodeRuntime(config, io, options = {}) {
21773
25121
  // read from the wire: a job cannot talk a node into
21774
25122
  // believing it is another one.
21775
25123
  nodeId: config.nodeId,
25124
+ // Control 18 — the machine owner's local execution policy,
25125
+ // from the LOCAL config only (OFF/OFF unless the owner ran
25126
+ // `ever-works-node policy`). Always passed: an executor that
25127
+ // gets no policy enforces none.
25128
+ executionPolicy,
21776
25129
  // Self-build slice Z (EW-796) — the platform side of the
21777
25130
  // MCP bridge, wired through the SAME authenticated job
21778
25131
  // client the lease protocol uses. No new endpoint, no new
@@ -21937,7 +25290,7 @@ function installShutdownHandlers(signals, shutdown) {
21937
25290
  __name(installShutdownHandlers, "installShutdownHandlers");
21938
25291
 
21939
25292
  // src/core/worker-safety-store.ts
21940
- var import_node_crypto3 = require("node:crypto");
25293
+ var import_node_crypto4 = require("node:crypto");
21941
25294
  var MARKER_VERSION = 1;
21942
25295
  var WorkerSafetyPrerequisiteError = class extends Error {
21943
25296
  static {
@@ -21955,7 +25308,7 @@ __name(workerSafetyMarkerPath, "workerSafetyMarkerPath");
21955
25308
  function createConfigWorkerSafetyGate(fs10, configPath, options = {}) {
21956
25309
  const markerPath = workerSafetyMarkerPath(configPath);
21957
25310
  const now = options.now ?? (() => Date.now());
21958
- const createSessionId = options.createSessionId ?? import_node_crypto3.randomUUID;
25311
+ const createSessionId = options.createSessionId ?? import_node_crypto4.randomUUID;
21959
25312
  const readRaw = /* @__PURE__ */ __name(async () => {
21960
25313
  try {
21961
25314
  return await fs10.readFile(markerPath);
@@ -22066,7 +25419,7 @@ function detail(error) {
22066
25419
  __name(detail, "detail");
22067
25420
 
22068
25421
  // src/core/workspaces/workspace-inventory.ts
22069
- var import_node_crypto4 = require("node:crypto");
25422
+ var import_node_crypto5 = require("node:crypto");
22070
25423
  var import_node_fs5 = require("node:fs");
22071
25424
  var import_node_path5 = require("node:path");
22072
25425
  var FLEET_WORKSPACE_STAMP_FILE = "ew-workspace.json";
@@ -22493,7 +25846,7 @@ async function readStamp(gitDir) {
22493
25846
  }
22494
25847
  __name(readStamp, "readStamp");
22495
25848
  function intentPath(poolPath, bindingKey) {
22496
- const key = (0, import_node_crypto4.createHash)("sha256").update("workspace-intent-v1\0").update(bindingKey).digest("hex");
25849
+ const key = (0, import_node_crypto5.createHash)("sha256").update("workspace-intent-v1\0").update(bindingKey).digest("hex");
22497
25850
  return (0, import_node_path5.join)(poolPath, FLEET_WORKSPACE_INTENTS_DIR, `${key}.json`);
22498
25851
  }
22499
25852
  __name(intentPath, "intentPath");
@@ -23244,6 +26597,7 @@ async function runStart(deps, options) {
23244
26597
  };
23245
26598
  const workerEnabled = options.work === true;
23246
26599
  const attendEnabled = options.attend === true;
26600
+ const manageEnabled = options.manage === true;
23247
26601
  const startPaused = config.paused === true;
23248
26602
  const concurrency = parseConcurrency(options.concurrency);
23249
26603
  const limitOverrides = parseLimitFlags(options);
@@ -23281,32 +26635,49 @@ async function runStart(deps, options) {
23281
26635
  // Written directly (not via a conditional spread) so excess-property
23282
26636
  // checking keeps this name tied to the runtime option it feeds.
23283
26637
  agentTaskWorkspaceRoot: workspaceRoot,
23284
- persistUnsafe: /* @__PURE__ */ __name(async (unsafe) => saveConfig(
23285
- deps.fs,
23286
- deps.configPath,
23287
- { ...config, unsafe },
23288
- {
23289
- platform: deps.platform,
26638
+ // Re-read before writing: the file may have changed since this service
26639
+ // started (`ever-works-node policy` run meanwhile), and a quarantine must
26640
+ // record `unsafe` without reverting anything else in it.
26641
+ persistUnsafe: /* @__PURE__ */ __name(async (unsafe) => {
26642
+ const current = await loadConfig(deps.fs, deps.configPath, {
23290
26643
  secrets: deps.secrets ?? null,
23291
26644
  logger: deps.io.logger
23292
- }
23293
- ), "persistUnsafe"),
26645
+ }).catch(() => null) ?? config;
26646
+ await saveConfig(
26647
+ deps.fs,
26648
+ deps.configPath,
26649
+ { ...current, unsafe },
26650
+ {
26651
+ platform: deps.platform,
26652
+ secrets: deps.secrets ?? null,
26653
+ logger: deps.io.logger
26654
+ }
26655
+ );
26656
+ }, "persistUnsafe"),
23294
26657
  workerSafetyGate: createConfigWorkerSafetyGate(deps.fs, deps.configPath, { platform: deps.platform }),
23295
26658
  // Node lifecycle (slice AR): what the platform last said about this
23296
26659
  // daemon's version, recorded beside the config for `status`/`doctor`.
23297
26660
  persistLifecycle: /* @__PURE__ */ __name((record) => writeNodeLifecycleRecord(deps.fs, deps.configPath, record), "persistLifecycle"),
23298
26661
  ...deps.resourceProbe ? { resourceProbe: deps.resourceProbe } : {},
23299
- attendEnabled
26662
+ attendEnabled,
26663
+ // Fleet node link (FL-02): the fourth consent, and the link itself. The
26664
+ // link opens on the first accepted heartbeat when any of --work,
26665
+ // --attend, --manage is on; its state is recorded for `status`.
26666
+ manageEnabled,
26667
+ nodeLink: true,
26668
+ persistLinkState: /* @__PURE__ */ __name((record) => writeNodeLinkRecord(deps.fs, deps.configPath, record), "persistLinkState")
23300
26669
  });
23301
26670
  if (workerEnabled) {
23302
26671
  void logModelCliCompatibility(deps, runtime.cliCompat, modelCli.paths);
23303
26672
  }
23304
26673
  const { loop, worker } = runtime;
23305
26674
  const attendedWorker = runtime.attended?.worker ?? null;
26675
+ let lastLoggedBeat = null;
23306
26676
  loop.onChange((state) => {
23307
- if (state.state === "connected") {
23308
- deps.io.logger.info(`Heartbeat accepted \u2014 node ${config.nodeId} is online`);
23309
- }
26677
+ if (state.state !== "connected") return;
26678
+ if (lastLoggedBeat && lastLoggedBeat.node === state.node && lastLoggedBeat.at === state.lastHeartbeatAt) return;
26679
+ lastLoggedBeat = { node: state.node, at: state.lastHeartbeatAt };
26680
+ deps.io.logger.info(`Heartbeat accepted \u2014 node ${config.nodeId} is online`);
23310
26681
  });
23311
26682
  deps.out(`Starting node ${config.nodeId} \u2192 ${config.apiUrl} (every ${config.heartbeatIntervalMs / 1e3}s)`);
23312
26683
  let requestShutdown = /* @__PURE__ */ __name(() => void 0, "requestShutdown");
@@ -23398,9 +26769,13 @@ async function runStart(deps, options) {
23398
26769
  if (!worker) {
23399
26770
  deps.out("Worker host disabled \u2014 reporting liveness only (pass --work to execute platform work)");
23400
26771
  }
26772
+ deps.out(
26773
+ isFleetLinkEligible(runtime.consents) ? `Consents \u2014 ${describeConsents(runtime.consents)}; the node link opens after the first accepted heartbeat` : "Consents \u2014 none: the node link stays closed and this computer only reports health (pass --work, --attend or --manage)"
26774
+ );
23401
26775
  await (deps.waitForShutdown?.() ?? (deps.signals ? signalled : Promise.resolve()));
23402
26776
  reaper?.stop();
23403
26777
  shuttingDown = true;
26778
+ runtime.link?.close();
23404
26779
  loop.stop();
23405
26780
  await loop.settled();
23406
26781
  await lanesStarted;
@@ -23448,6 +26823,61 @@ function parseConcurrency(raw) {
23448
26823
  return parsed;
23449
26824
  }
23450
26825
  __name(parseConcurrency, "parseConcurrency");
26826
+ function describeExecutionPolicy(policy) {
26827
+ return `skip-permissions ${policy.allowSkipPermissions ? "ALLOWED" : "refused"}, node-run shell commands ${policy.allowShellCommands ? "ALLOWED" : "refused"}, Claude Code restricted mode ${policy.restrictedMode ? "on" : "OFF"}`;
26828
+ }
26829
+ __name(describeExecutionPolicy, "describeExecutionPolicy");
26830
+ function parsePolicySwitch(flag, value) {
26831
+ if (value === void 0) return void 0;
26832
+ const normalized = value.trim().toLowerCase();
26833
+ if (normalized === "on" || normalized === "true" || normalized === "yes") return true;
26834
+ if (normalized === "off" || normalized === "false" || normalized === "no") return false;
26835
+ throw new CliError(`${flag} takes 'on' or 'off', not '${value}'`);
26836
+ }
26837
+ __name(parsePolicySwitch, "parsePolicySwitch");
26838
+ async function runPolicy(deps, options) {
26839
+ const config = await requireConfig(deps);
26840
+ const current = resolveNodeExecutionPolicy(config.executionPolicy);
26841
+ const skip = parsePolicySwitch("--allow-skip-permissions", options.allowSkipPermissions);
26842
+ const shell = parsePolicySwitch("--allow-shell-commands", options.allowShellCommands);
26843
+ const restricted = parsePolicySwitch("--restricted-mode", options.restrictedMode);
26844
+ if (skip === void 0 && shell === void 0 && restricted === void 0) {
26845
+ deps.out(`policy ${describeExecutionPolicy(current)}`);
26846
+ deps.out(
26847
+ "Change it with `ever-works-node policy --allow-skip-permissions on|off --allow-shell-commands on|off --restricted-mode on|off`."
26848
+ );
26849
+ return;
26850
+ }
26851
+ const next = {
26852
+ allowSkipPermissions: skip ?? current.allowSkipPermissions,
26853
+ allowShellCommands: shell ?? current.allowShellCommands,
26854
+ restrictedMode: restricted ?? current.restrictedMode
26855
+ };
26856
+ await saveConfig(
26857
+ deps.fs,
26858
+ deps.configPath,
26859
+ { ...config, executionPolicy: next },
26860
+ { platform: deps.platform, secrets: deps.secrets ?? null, logger: deps.io.logger }
26861
+ );
26862
+ deps.out(`policy ${describeExecutionPolicy(next)}`);
26863
+ if (next.allowSkipPermissions) {
26864
+ deps.out(
26865
+ "WARNING: jobs may now run the model CLI with its permission checks OFF. A prompt injection in a Task, a repository or a review comment then has an unrestricted shell as this account, with no filesystem or network boundary."
26866
+ );
26867
+ }
26868
+ if (next.allowShellCommands) {
26869
+ deps.out(
26870
+ "WARNING: jobs may now make this node run shell commands (setup, acceptance checks). They execute repository code as this account \u2014 after a model step, code the model may have written."
26871
+ );
26872
+ }
26873
+ if (!next.restrictedMode) {
26874
+ deps.out(
26875
+ "Note: Claude Code restricted mode is OFF \u2014 runs keep the baseline isolation (no repository settings, no hooks) but get the command-running built-ins back and read your ~/.claude/settings.json."
26876
+ );
26877
+ }
26878
+ deps.out("Restart the node service for a running worker to apply the change.");
26879
+ }
26880
+ __name(runPolicy, "runPolicy");
23451
26881
  async function runPause(deps, paused, options = {}) {
23452
26882
  const config = await requireConfig(deps);
23453
26883
  const verb = paused ? "Paused" : "Resumed";
@@ -23595,12 +27025,18 @@ async function runStatus(deps) {
23595
27025
  deps.out(`work ${view.paused ? "PAUSED (draining \u2014 no new work)" : "accepting new work"}`);
23596
27026
  const lifecycleRecord = await readNodeLifecycleRecord(deps.fs, deps.configPath);
23597
27027
  deps.out(`daemon ${describeDaemonLifecycle(deps, lifecycleRecord)}`);
27028
+ const linkRecord = await readNodeLinkRecord(deps.fs, deps.configPath);
27029
+ deps.out(
27030
+ `consents ${linkRecord ? describeConsents(linkRecord.consents) : "unknown (the service has not reported them yet)"}`
27031
+ );
27032
+ deps.out(`link ${describeLinkRecord(linkRecord, (deps.now ?? (() => Date.now()))())}`);
23598
27033
  deps.out(
23599
27034
  `offering ${view.capabilitySelection ? view.capabilitySelection.join(", ") || "(identity only)" : "(everything detected)"}`
23600
27035
  );
23601
27036
  deps.out(`limits ${describeLimits(view.limits)}`);
23602
27037
  deps.out(`ceiling ${describePlatformCeiling(lifecycleRecord, view.limits)}`);
23603
27038
  deps.out(`workspace gc ${describeWorkspaceGc(view.workspaceGc ?? DEFAULT_WORKSPACE_GC_POLICY)}`);
27039
+ deps.out(`policy ${describeExecutionPolicy(view.executionPolicy)}`);
23604
27040
  deps.out(`credential ${view.hasSecret ? "stored" : "MISSING"}`);
23605
27041
  deps.out(`config ${deps.configPath}`);
23606
27042
  }
@@ -23807,6 +27243,11 @@ function describeModelCli(compat) {
23807
27243
  if (compat.missingConditionalFlags.length > 0) {
23808
27244
  notes.push(`runs that need ${compat.missingConditionalFlags.join(", ")} will fail here`);
23809
27245
  }
27246
+ if ((compat.missingHardeningFlags ?? []).length > 0) {
27247
+ notes.push(
27248
+ `runs go without the stricter ${compat.missingHardeningFlags.join(", ")} mode (upgrade the CLI to get it)`
27249
+ );
27250
+ }
23810
27251
  return `${head} \u2014 compatible${notes.length > 0 ? `; ${notes.join("; ")}` : ""}`;
23811
27252
  }
23812
27253
  __name(describeModelCli, "describeModelCli");
@@ -23874,6 +27315,7 @@ function lifecycleJson(deps, report) {
23874
27315
  missingRequiredFlags: compat.missingRequiredFlags,
23875
27316
  missingConditionalFlags: compat.missingConditionalFlags,
23876
27317
  unsupportedOptionalFlags: compat.unsupportedOptionalFlags,
27318
+ missingHardeningFlags: compat.missingHardeningFlags ?? [],
23877
27319
  note: compat.note
23878
27320
  }))
23879
27321
  };
@@ -23902,6 +27344,11 @@ async function runDoctor(deps, options) {
23902
27344
  }
23903
27345
  printHousekeepingHeader(deps, report, now);
23904
27346
  printLifecycle(deps, lifecycle);
27347
+ const linkRecord = await readNodeLinkRecord(deps.fs, deps.configPath);
27348
+ deps.out(
27349
+ `consents ${linkRecord ? describeConsents(linkRecord.consents) : "unknown (the service has not reported them yet)"}`
27350
+ );
27351
+ deps.out(`link ${describeLinkRecord(linkRecord, now)}`);
23905
27352
  printPlan(deps, report.plan, now);
23906
27353
  }
23907
27354
  __name(runDoctor, "runDoctor");
@@ -23988,6 +27435,9 @@ function buildProgram(deps) {
23988
27435
  program2.command("start").description("Run the heartbeat loop (and, with --work, the job worker host) until SIGINT/SIGTERM").option("-i, --heartbeat-interval <seconds>", "Override the stored heartbeat cadence, in seconds").option("-w, --work", "Lease and execute platform work on this machine (off by default)").option(
23989
27436
  "--attend",
23990
27437
  "Allow live viewing of this machine from the dashboard (off by default; independent of --work)"
27438
+ ).option(
27439
+ "--manage",
27440
+ "Allow managing this machine from the dashboard \u2014 owner sessions, logs, services, Docker, processes, ports, files, commands, schedules (off by default; independent of --work and --attend)"
23991
27441
  ).option(
23992
27442
  "-c, --concurrency <count>",
23993
27443
  "Max jobs to execute at once when --work is set (overrides the stored limit)"
@@ -24012,6 +27462,20 @@ function buildProgram(deps) {
24012
27462
  program2.command("resume").description("Take work again after a pause").option("--local-only", "Only clear the local flag; do not tell the platform").action(async (options) => {
24013
27463
  await runPause(deps, false, options);
24014
27464
  });
27465
+ program2.command("policy").description(
27466
+ "Show or change what platform jobs may make this machine do (local only; the platform cannot change it)"
27467
+ ).option(
27468
+ "--allow-skip-permissions <on|off>",
27469
+ "Let jobs run the model CLI with its permission checks off (--dangerously-skip-permissions). Default off."
27470
+ ).option(
27471
+ "--allow-shell-commands <on|off>",
27472
+ "Let jobs make this node run shell commands: setup, command steps, acceptance checks. Default off."
27473
+ ).option(
27474
+ "--restricted-mode <on|off>",
27475
+ "Run Claude Code with --restricted when it supports it (no shell tools, no settings files). Default on."
27476
+ ).action(async (options) => {
27477
+ await runPolicy(deps, options);
27478
+ });
24015
27479
  program2.command("clear-quarantine").description("Clear a persisted unsafe worker state after verifying every prior process tree is stopped").option(
24016
27480
  "--confirm-process-tree-stopped",
24017
27481
  "Assert that the prior Git/command process tree has been independently verified stopped"
@@ -24105,15 +27569,16 @@ function createLogger(options = {}) {
24105
27569
  __name(createLogger, "createLogger");
24106
27570
 
24107
27571
  // src/version.ts
24108
- var NODE_APP_VERSION = "0.2.4";
27572
+ var NODE_APP_VERSION = "0.2.6";
24109
27573
 
24110
27574
  // src/cli.ts
24111
27575
  function buildDeps() {
24112
27576
  const logger = createLogger();
27577
+ const runner = createCommandRunner();
24113
27578
  return {
24114
27579
  io: {
24115
27580
  fetchFn: systemFetch,
24116
- runner: createCommandRunner(),
27581
+ runner,
24117
27582
  environment: currentEnvironment(),
24118
27583
  logger,
24119
27584
  version: NODE_APP_VERSION,
@@ -24124,7 +27589,9 @@ function buildDeps() {
24124
27589
  diskProbe: createDiskProbe(),
24125
27590
  // Node lifecycle (slice AR): the cache key of the model-CLI
24126
27591
  // compatibility probe, so an upgraded CLI is re-probed at once.
24127
- statFile: statModelCliFile
27592
+ statFile: statModelCliFile,
27593
+ // Computer health (FL-03): the snapshot every heartbeat carries.
27594
+ health: createHealthProbeIo(runner)
24128
27595
  },
24129
27596
  fs: createConfigFileSystem(),
24130
27597
  configPath: defaultConfigPath(),