@huynle/hub 0.8.7 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/README.md +69 -12
  3. package/package.json +5 -1
  4. package/packages/claude-code/package.json +1 -1
  5. package/packages/cli/dist/hub-cli.js +278 -35
  6. package/packages/cli/package.json +1 -1
  7. package/packages/cli/src/cli.ts +23 -10
  8. package/packages/cli/src/http.ts +1 -0
  9. package/packages/cli/src/launches.ts +5 -1
  10. package/packages/cli/src/mcp.ts +28 -8
  11. package/packages/cli/src/messaging.ts +14 -0
  12. package/packages/cli/src/team-intake-mcp.ts +26 -2
  13. package/packages/codex/package.json +1 -1
  14. package/packages/node/package.json +1 -1
  15. package/packages/preset/package.json +1 -1
  16. package/packages/protocol/package.json +1 -1
  17. package/packages/protocol/schema/app-delivery/v1.schema.json +677 -0
  18. package/packages/protocol/src/artifacts.ts +43 -0
  19. package/packages/protocol/src/credentials.ts +38 -0
  20. package/packages/protocol/src/index.ts +56 -5
  21. package/packages/protocol/src/launch.ts +7 -2
  22. package/packages/protocol/src/markdown.ts +753 -0
  23. package/packages/protocol/src/media-allowlist.ts +240 -0
  24. package/packages/protocol/src/media.ts +120 -0
  25. package/packages/protocol/src/preview-bundle.ts +221 -0
  26. package/packages/protocol/src/voice-recording.ts +8 -0
  27. package/packages/runtime-openshell/package.json +1 -1
  28. package/packages/runtime-sdk/package.json +1 -1
  29. package/packages/sdk/package.json +1 -1
  30. package/packages/sdk/src/client.ts +17 -3
  31. package/packages/sdk/src/errors.ts +1 -0
  32. package/packages/sdk/src/team-intake.ts +8 -0
  33. package/packages/sdk/src/types.ts +16 -2
  34. package/packages/spoke-sdk/package.json +1 -1
  35. package/src/central/agent/delivery.ts +35 -6
  36. package/src/central/agent/errors.ts +2 -0
  37. package/src/central/agent/events.ts +11 -2
  38. package/src/central/agent/launch-engine.ts +65 -4
  39. package/src/central/agent/launch-routes.ts +8 -0
  40. package/src/central/agent/launch-spec.ts +18 -1
  41. package/src/central/agent/launch-store.ts +2 -2
  42. package/src/central/agent/message-routes.ts +49 -11
  43. package/src/central/agent/messages.ts +17 -4
  44. package/src/central/agent/not-after.ts +28 -0
  45. package/src/central/agent/placement.ts +36 -4
  46. package/src/central/agent/routes.ts +3 -1
  47. package/src/central/agent/webhook-routes.ts +21 -9
  48. package/src/central/agent/webhooks.ts +27 -9
  49. package/src/central/artifacts/host.ts +3 -1
  50. package/src/central/artifacts/maintenance.ts +2 -1
  51. package/src/central/artifacts/preview-bundle.ts +81 -0
  52. package/src/central/artifacts/previews.ts +94 -0
  53. package/src/central/artifacts/routes.ts +12 -1
  54. package/src/central/artifacts/store.ts +198 -8
  55. package/src/central/config.ts +34 -0
  56. package/src/central/fetch-budget.ts +59 -0
  57. package/src/central/fetcher.ts +372 -0
  58. package/src/central/http.ts +277 -22
  59. package/src/central/media-remote.ts +248 -0
  60. package/src/central/media-timeline.ts +317 -0
  61. package/src/central/media-unfurl.ts +119 -0
  62. package/src/central/profiles/content.ts +3 -35
  63. package/src/central/recording/routes.ts +2 -2
  64. package/src/central/recording/schema.ts +3 -1
  65. package/src/central/registry.ts +449 -21
  66. package/src/central/spoke-ws.ts +17 -3
  67. package/src/central/store.ts +21 -0
  68. package/src/central/teams/intake-routes.ts +2 -1
  69. package/src/central/teams/intake.ts +200 -15
  70. package/src/central/unfurl-parse.ts +313 -0
  71. package/src/index.ts +68 -8
  72. package/src/speakable.ts +33 -1
  73. package/src/spoke/conversation.ts +7 -4
  74. package/src/spoke/deliver.ts +19 -0
  75. package/src/spoke/media-bytes.ts +171 -0
  76. package/src/spoke/media-policy.ts +394 -0
  77. package/src/spoke/media.ts +189 -0
  78. package/src/spoke/own-server.ts +30 -0
  79. package/src/spoke/preview-bundle.ts +538 -0
  80. package/src/spoke/spoke.ts +387 -17
  81. package/src/spoke/timeline.ts +59 -5
  82. package/src/spoke/voice-front.ts +335 -0
  83. package/web/dist/assets/24BXRY42-Zfr7jm_p.js +2 -0
  84. package/web/dist/assets/4FHPYW2L-C6Z67098.js +2 -0
  85. package/web/dist/assets/4LWW3ZNH-CUiYeygH.js +1 -0
  86. package/web/dist/assets/5THPTBWS-Ce_hS451.js +1 -0
  87. package/web/dist/assets/5XYWFMKW-CW-BCKiK.js +4 -0
  88. package/web/dist/assets/64PMEAB3-BjpCDGU9.js +8 -0
  89. package/web/dist/assets/6BEWHPQE-BpNa96Ze.js +2 -0
  90. package/web/dist/assets/6PIGVMFT-YXn9Hgnb.js +1 -0
  91. package/web/dist/assets/75JW6PN6-BFpDBuGN.js +2 -0
  92. package/web/dist/assets/7WLXJQOR-D6QVYUu9.js +1 -0
  93. package/web/dist/assets/7XWI72E6-BMuIspEy.js +2 -0
  94. package/web/dist/assets/A4UD2L5Z-BQzcD9dU.js +8 -0
  95. package/web/dist/assets/ArtifactPage-4GoBqSpU.js +1 -0
  96. package/web/dist/assets/ArtifactPane-Dx_0iWwH.js +1 -0
  97. package/web/dist/assets/ArtifactWorkbench-CuHgARMk.js +6 -0
  98. package/web/dist/assets/BGW36YJW-CJMhya8S.js +4 -0
  99. package/web/dist/assets/CGIZI67J-DYDSLME8.js +2 -0
  100. package/web/dist/assets/DKTLEL5E-DciMwFJ1.js +1 -0
  101. package/web/dist/assets/DNNCRC2E-BIeKf5t-.js +5 -0
  102. package/web/dist/assets/Diagram-CZM_R7MG.js +1 -0
  103. package/web/dist/assets/Diagram-vdpmp3xu.css +1 -0
  104. package/web/dist/assets/E67EBMDD--AucIw59.js +5 -0
  105. package/web/dist/assets/EFYWJBJK-CVywe_D5.js +1 -0
  106. package/web/dist/assets/EXE5EH6O-DR1IHCTr.js +4 -0
  107. package/web/dist/assets/FTXIJQGB-BZVuiBn1.js +1 -0
  108. package/web/dist/assets/G3MDBVQH-BZmnMkFw.js +1 -0
  109. package/web/dist/assets/G6GPDDNC-DEz8F449.js +2 -0
  110. package/web/dist/assets/G76NGGFM-CrB2V34K.js +1 -0
  111. package/web/dist/assets/GH6G3TJC-D3RTYEH4.js +2 -0
  112. package/web/dist/assets/HPNUA7RO-B19NeNOJ.js +1 -0
  113. package/web/dist/assets/HQRGC5D7-Dmpzq6Rn.js +4 -0
  114. package/web/dist/assets/IX6Z2TVR-DphRHnek.js +5 -0
  115. package/web/dist/assets/J77NQRCJ-B7AGcxvw.js +3 -0
  116. package/web/dist/assets/JAD36DTZ-B9-dbsS0.js +5 -0
  117. package/web/dist/assets/LF3NJQAX-koL91UZ9.js +2 -0
  118. package/web/dist/assets/LRZCOFJZ-Cdjasfuf.js +6 -0
  119. package/web/dist/assets/Library-TwvrwqSk.js +2 -0
  120. package/web/dist/assets/Lightbox-C8KNKwOA.css +1 -0
  121. package/web/dist/assets/Lightbox-Du56MXGX.js +1 -0
  122. package/web/dist/assets/MediaImage-Ds_JUx3a.js +4 -0
  123. package/web/dist/assets/N5NF2FGY-OuapYTfr.js +2 -0
  124. package/web/dist/assets/N7OWWQHY-BNLd-Q_U.js +2 -0
  125. package/web/dist/assets/NT5JY5WP-1lJM-aun.js +1 -0
  126. package/web/dist/assets/OT7W3RNY-jDL3drzX.js +4 -0
  127. package/web/dist/assets/P67P66DB-CE-opRb8.js +3 -0
  128. package/web/dist/assets/PFLLLM7K-DDJFgzW3.js +2 -0
  129. package/web/dist/assets/PUAMC4MJ-CtBm-WuU.js +12 -0
  130. package/web/dist/assets/PVFCHRLY-CG5JXv60.js +2 -0
  131. package/web/dist/assets/ProfilesDestination-rKNDpLjR.js +1 -0
  132. package/web/dist/assets/QQW2P742-BbcEkpa4.js +1 -0
  133. package/web/dist/assets/RVARI57O-CgA1WoRv.js +1 -0
  134. package/web/dist/assets/S63OEDPG-CKlhpvl2.js +3 -0
  135. package/web/dist/assets/SG5TVYLW-BBtiwXPL.js +2 -0
  136. package/web/dist/assets/TeamsPage-DtJylLrt.js +1 -0
  137. package/web/dist/assets/UGRSUGA2-urZ_hx83.js +5 -0
  138. package/web/dist/assets/V44WA6U7-DYfX3OlM.js +1 -0
  139. package/web/dist/assets/V67O4WWV-xYxJ00QW.js +1 -0
  140. package/web/dist/assets/VBUCM3YD-JHGjuJC_.js +1 -0
  141. package/web/dist/assets/VINGRCPA-DZuJa3SH.js +7 -0
  142. package/web/dist/assets/WL6DNKX4-BHqripdA.js +2 -0
  143. package/web/dist/assets/WQ63CJYP-D6G5frDY.js +2 -0
  144. package/web/dist/assets/Watchtower-Bmgdyfjb.js +2 -0
  145. package/web/dist/assets/XOYAOB3S-L-KeA1EF.js +13 -0
  146. package/web/dist/assets/Y5LQLG3M-dr2-erb8.js +2 -0
  147. package/web/dist/assets/YIK6JTN6--1t-Ud8k.js +2 -0
  148. package/web/dist/assets/artifacts-DU9uRgs1.js +1 -0
  149. package/web/dist/assets/index-C3ci5UtY.css +1 -0
  150. package/web/dist/assets/index-D-xG9nrc.js +36 -0
  151. package/web/dist/assets/lazy-f3CuUNJZ.js +43 -0
  152. package/web/dist/assets/live-7q72Lyj2.js +5 -0
  153. package/web/dist/assets/mermaid.worker-WDVRAh32.js +1 -0
  154. package/web/dist/assets/pane-layout-0RrLHGDr.js +1 -0
  155. package/web/dist/assets/preload-helper-BLdHbfdN.js +1 -0
  156. package/web/dist/assets/{renderer2d-CYag1CnR.js → renderer2d-hfZxM6Xl.js} +1 -1
  157. package/web/dist/assets/{renderer3d-BRB6rgDJ.js → renderer3d-Km9vipTM.js} +1 -1
  158. package/web/dist/assets/{safe-area-fOy0AOuG.js → safe-area-BAbgBiFk.js} +1 -1
  159. package/web/dist/assets/{silero-worker-DhlIHyD_.js → silero-worker-e802lB5T.js} +1 -1
  160. package/web/dist/assets/smart-turn-v3.2-cpu-WpeLRbPA.onnx +0 -0
  161. package/web/dist/assets/smart-turn-worker-DaVJPdvJ.js +11 -0
  162. package/web/dist/assets/source-BWlvaODa.js +1 -0
  163. package/web/dist/assets/toast-Cuu2K0hx.js +1 -0
  164. package/web/dist/assets/ui-CFB4wQfF.js +3 -0
  165. package/web/dist/index.html +8 -6
  166. package/web/src/voice/turn/LICENSE +28 -0
  167. package/web/dist/assets/ArtifactPage-Dq7VW-bU.js +0 -1
  168. package/web/dist/assets/ArtifactPane-DKwbMTX9.js +0 -1
  169. package/web/dist/assets/ArtifactWorkbench-vrgIfncX.js +0 -6
  170. package/web/dist/assets/Library-D6-Go6Cu.js +0 -2
  171. package/web/dist/assets/ProfilesDestination-BOML2Dgf.js +0 -1
  172. package/web/dist/assets/TeamsPage-CNBTE8gB.js +0 -1
  173. package/web/dist/assets/Watchtower-Dor1Ubjy.js +0 -2
  174. package/web/dist/assets/hub-CrbItiwP.js +0 -13
  175. package/web/dist/assets/index-82Ce0IAf.css +0 -1
  176. package/web/dist/assets/index-D9SP3_0z.js +0 -22
  177. package/web/dist/assets/live-BNJgy0vg.js +0 -5
  178. package/web/dist/assets/pane-layout-EdR1kRrG.js +0 -1
  179. package/web/dist/assets/preload-helper-DqrJTq0t.js +0 -1
  180. package/web/dist/assets/source-Wbq35qGp.js +0 -1
  181. package/web/dist/assets/ui-j5FFk90B.js +0 -3
@@ -7,7 +7,7 @@ import { createInterface } from "readline";
7
7
  // packages/cli/package.json
8
8
  var package_default = {
9
9
  name: "@huynle/hub-cli",
10
- version: "0.8.7",
10
+ version: "0.9.0",
11
11
  private: true,
12
12
  description: "The hub CLI (`hub` / `hubctl`) for agents and scripts (internal; bundled into @huynle/hub, not published on its own).",
13
13
  license: "MIT",
@@ -710,7 +710,11 @@ var decisionFields = (options) => {
710
710
  var whyField = (options) => options.why !== undefined ? { why: options.why } : {};
711
711
  var signalOf = (options) => options.signal ? { signal: options.signal } : {};
712
712
  var idempotent = (options) => ({ ...signalOf(options), ...options.idempotencyKey ? { headers: { "idempotency-key": options.idempotencyKey } } : {} });
713
- var sendFields = (options) => ({ ...options.wake !== undefined ? { wake: options.wake } : {}, ...options.urgent !== undefined ? { urgent: options.urgent } : {} });
713
+ var sendFields = (options) => ({
714
+ ...options.wake !== undefined ? { wake: options.wake } : {},
715
+ ...options.urgent !== undefined ? { urgent: options.urgent } : {},
716
+ ...options.notAfter !== undefined ? { notAfter: options.notAfter } : {}
717
+ });
714
718
  var roleTarget = (options) => ({ ...options.session ? { session: options.session } : {}, ...options.project !== undefined ? { project: options.project } : {} });
715
719
 
716
720
  class HubClient {
@@ -916,7 +920,7 @@ class HubClient {
916
920
  return this.call("POST", `${AGENT}/roles/release`, { body: { role: bareRole(role), ...roleTarget(options) }, ...signalOf(options) });
917
921
  }
918
922
  msg(to, text, options = {}) {
919
- return this.call("POST", `${AGENT}/msg`, { body: { to, text, ...sendFields(options), ...options.all ? { all: true } : {} }, ...signalOf(options) });
923
+ return this.call("POST", `${AGENT}/msg`, { body: { to, text, ...sendFields(options), ...options.all ? { all: true } : {} }, ...idempotent(options) });
920
924
  }
921
925
  async ask(to, text, options = {}) {
922
926
  if (options.all && options.wait !== undefined)
@@ -933,7 +937,7 @@ class HubClient {
933
937
  ...options.wait !== undefined ? { wait: first } : {},
934
938
  ...decisionFields(options)
935
939
  };
936
- const asked = await this.call("POST", `${AGENT}/ask`, { body, ...signalOf(options) });
940
+ const asked = await this.call("POST", `${AGENT}/ask`, { body, ...idempotent(options) });
937
941
  if ("sent" in asked)
938
942
  return asked;
939
943
  let current = asked;
@@ -1110,6 +1114,7 @@ var EXIT_OF = {
1110
1114
  offline: EXIT.unavailable,
1111
1115
  busy: EXIT.unavailable,
1112
1116
  gone: EXIT.gone,
1117
+ expired: EXIT.gone,
1113
1118
  unauthorized: EXIT.unauthorized,
1114
1119
  token_expired: EXIT.unauthorized
1115
1120
  };
@@ -3551,6 +3556,113 @@ function sessionContextProblem(v) {
3551
3556
  return;
3552
3557
  }
3553
3558
 
3559
+ // packages/protocol/src/media.ts
3560
+ var MEDIA_LIMITS = {
3561
+ maxBytes: 8 * 1024 * 1024,
3562
+ maxPixels: 40000000,
3563
+ maxSide: 16384,
3564
+ chunkBytes: 512 * 1024,
3565
+ perRow: 8
3566
+ };
3567
+ var MEDIA_PATH_MAX = 4096;
3568
+ var MEDIA_REF_MAX = 2 + Math.ceil(MEDIA_PATH_MAX * 4 / 3) + 4;
3569
+ var FILE_REF = /^f(0|[1-9]\d{0,2})$/;
3570
+ var TOOL_REF = /^t(0|[1-9]\d{0,3})\.(0|[1-9]\d{0,2})$/;
3571
+ var PATH_REF = /^p:([A-Za-z0-9_-]+)$/;
3572
+ function base64url(bytes) {
3573
+ let binary = "";
3574
+ for (const byte of bytes)
3575
+ binary += String.fromCharCode(byte);
3576
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3577
+ }
3578
+ function fromBase64url(text) {
3579
+ if (text.length % 4 === 1)
3580
+ return;
3581
+ try {
3582
+ const binary = atob(text.replace(/-/g, "+").replace(/_/g, "/"));
3583
+ return Uint8Array.from(binary, (c) => c.charCodeAt(0));
3584
+ } catch {
3585
+ return;
3586
+ }
3587
+ }
3588
+ function mediaPathRef(path) {
3589
+ return `p:${base64url(new TextEncoder().encode(path))}`;
3590
+ }
3591
+ function parseMediaRef(ref) {
3592
+ if (typeof ref !== "string" || ref.length > MEDIA_REF_MAX)
3593
+ return;
3594
+ const file = FILE_REF.exec(ref);
3595
+ if (file)
3596
+ return { kind: "file", index: Number(file[1]) };
3597
+ const tool = TOOL_REF.exec(ref);
3598
+ if (tool)
3599
+ return { kind: "tool", part: Number(tool[1]), item: Number(tool[2]) };
3600
+ const path = PATH_REF.exec(ref);
3601
+ if (!path)
3602
+ return;
3603
+ const bytes = fromBase64url(path[1]);
3604
+ if (!bytes || bytes.length === 0 || bytes.length > MEDIA_PATH_MAX)
3605
+ return;
3606
+ let text;
3607
+ try {
3608
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
3609
+ } catch {
3610
+ return;
3611
+ }
3612
+ if (text.includes("\x00") || mediaPathRef(text) !== ref)
3613
+ return;
3614
+ return { kind: "path", path: text };
3615
+ }
3616
+ var isMediaOffset = (offset) => Number.isInteger(offset) && offset >= 0 && offset <= MEDIA_LIMITS.maxBytes;
3617
+
3618
+ // packages/protocol/src/preview-bundle.ts
3619
+ var PREVIEW_BUNDLE_FEATURE = "previewBundle";
3620
+ var PREVIEW_BUNDLE_LIMITS = {
3621
+ files: 200,
3622
+ fileBytes: 5 * 1024 * 1024,
3623
+ totalBytes: 20 * 1024 * 1024,
3624
+ chunkBytes: 512 * 1024,
3625
+ inlineBytes: 384 * 1024,
3626
+ bundlePathMax: 200,
3627
+ pathMax: 4096,
3628
+ refMax: 300,
3629
+ skipped: 200
3630
+ };
3631
+ var PREVIEW_MEDIA_TYPES = {
3632
+ html: "text/html",
3633
+ htm: "text/html",
3634
+ css: "text/css",
3635
+ js: "text/javascript",
3636
+ mjs: "text/javascript",
3637
+ json: "application/json",
3638
+ txt: "text/plain",
3639
+ csv: "text/csv",
3640
+ md: "text/markdown",
3641
+ pdf: "application/pdf",
3642
+ png: "image/png",
3643
+ jpg: "image/jpeg",
3644
+ jpeg: "image/jpeg",
3645
+ gif: "image/gif",
3646
+ webp: "image/webp",
3647
+ svg: "image/svg+xml"
3648
+ };
3649
+ var MEDIA_TYPES = new Set(Object.values(PREVIEW_MEDIA_TYPES));
3650
+ var HEX64 = /^[0-9a-f]{64}$/;
3651
+ var int2 = (v, min, max) => Number.isSafeInteger(v) && v >= min && v <= max;
3652
+ var isPreviewPath = (v) => typeof v === "string" && v.length > 0 && v.length <= PREVIEW_BUNDLE_LIMITS.pathMax && !/[\u0000\r\n]/.test(v);
3653
+ function isPreviewBundleRequest(v) {
3654
+ if (typeof v.sessionID !== "string" || !v.sessionID)
3655
+ return false;
3656
+ if (typeof v.messageID !== "string" || !v.messageID || v.messageID.length > 300)
3657
+ return false;
3658
+ if (!isPreviewPath(v.path))
3659
+ return false;
3660
+ const chunk = [v.bundle, v.file, v.offset].filter((x) => x !== undefined).length;
3661
+ if (chunk === 0)
3662
+ return true;
3663
+ return chunk === 3 && typeof v.bundle === "string" && HEX64.test(v.bundle) && int2(v.file, 0, PREVIEW_BUNDLE_LIMITS.files - 1) && int2(v.offset, 0, PREVIEW_BUNDLE_LIMITS.fileBytes - 1);
3664
+ }
3665
+
3554
3666
  // packages/protocol/src/harness-events.ts
3555
3667
  var HARNESS_EVENTS = [
3556
3668
  "session.created",
@@ -3936,6 +4048,60 @@ var ARTIFACT_LIMITS = {
3936
4048
  aiJobsPerArtifact: 2,
3937
4049
  aiInstructionChars: 8000
3938
4050
  };
4051
+ // packages/protocol/src/media-allowlist.ts
4052
+ var PUBLIC_SUFFIXES = new Set([
4053
+ ...["co", "org", "ac", "gov", "net", "me", "ltd", "plc", "sch", "nhs"].map((s) => `${s}.uk`),
4054
+ ...["com", "net", "org", "edu", "gov", "id", "asn"].map((s) => `${s}.au`),
4055
+ ...["co", "ne", "or", "ac", "go", "gr", "ed", "ad", "lg"].map((s) => `${s}.jp`),
4056
+ ...["co", "org", "net", "ac", "govt", "school", "geek", "gen", "kiwi", "maori"].map((s) => `${s}.nz`),
4057
+ ...["com", "net", "org", "gov", "edu"].map((s) => `${s}.br`),
4058
+ ...["com", "net", "org", "gov", "edu", "ac"].map((s) => `${s}.cn`),
4059
+ ...["com", "net", "org", "gob", "edu"].map((s) => `${s}.mx`),
4060
+ ...["co", "net", "org", "firm", "gen", "ind", "ac", "edu", "gov", "res"].map((s) => `${s}.in`),
4061
+ ...["co", "org", "net", "ac", "gov", "web"].map((s) => `${s}.za`),
4062
+ ...["com", "net", "org", "gov", "edu"].map((s) => `${s}.tr`),
4063
+ ...["com", "net", "org", "gov", "edu"].map((s) => `${s}.sg`),
4064
+ ...["com", "net", "org", "gov", "edu"].map((s) => `${s}.hk`),
4065
+ ...["co", "or", "ne", "go", "ac", "re"].map((s) => `${s}.kr`),
4066
+ ...["com", "net", "org", "gov", "edu"].map((s) => `${s}.tw`),
4067
+ ...["com", "net", "org", "gov", "edu"].map((s) => `${s}.ar`),
4068
+ ...["co", "or", "ac", "go"].map((s) => `${s}.id`),
4069
+ ...["co", "or", "ac", "go"].map((s) => `${s}.th`),
4070
+ ...["com", "net", "org", "gov", "edu"].map((s) => `${s}.my`),
4071
+ ...["com", "net", "org", "gov", "edu"].map((s) => `${s}.ph`),
4072
+ ...["com", "net", "org", "gov", "edu"].map((s) => `${s}.pk`),
4073
+ ...["com", "net", "org", "gov", "edu"].map((s) => `${s}.ua`),
4074
+ ...["com", "net", "org", "gov", "edu"].map((s) => `${s}.pl`),
4075
+ ...["co", "or", "ac"].map((s) => `${s}.at`),
4076
+ ...["co", "org", "ac"].map((s) => `${s}.il`),
4077
+ "com.es",
4078
+ "org.es",
4079
+ "co.it",
4080
+ "com.ru",
4081
+ "co.ca",
4082
+ "eu.org",
4083
+ "github.io",
4084
+ "githubpreview.dev",
4085
+ "gitlab.io",
4086
+ "pages.dev",
4087
+ "workers.dev",
4088
+ "netlify.app",
4089
+ "vercel.app",
4090
+ "herokuapp.com",
4091
+ "appspot.com",
4092
+ "blogspot.com",
4093
+ "cloudfront.net",
4094
+ "azurewebsites.net",
4095
+ "web.app",
4096
+ "firebaseapp.com",
4097
+ "s3.amazonaws.com",
4098
+ "ngrok.io",
4099
+ "ngrok-free.app",
4100
+ "glitch.me",
4101
+ "repl.co",
4102
+ "fly.dev",
4103
+ "onrender.com"
4104
+ ]);
3939
4105
  // packages/protocol/src/graph.ts
3940
4106
  var GRAPH_LIMITS = {
3941
4107
  nodes: 2000,
@@ -4054,7 +4220,7 @@ var IMPLIED_OPENCODE_FEATURES = [
4054
4220
  "expose",
4055
4221
  "create"
4056
4222
  ];
4057
- var OPTIONAL_SPOKE_FEATURES = [...IMPLIED_OPENCODE_FEATURES, "autoName", "sessionTokens", "deliver", "hubPrompt", "permissionMessage", "actTokens", "launch", "fork", "revert", "dismissError"];
4223
+ var OPTIONAL_SPOKE_FEATURES = [...IMPLIED_OPENCODE_FEATURES, "autoName", "sessionTokens", "deliver", "hubPrompt", "permissionMessage", "actTokens", "launch", "fork", "revert", "dismissError", "timelineMedia", PREVIEW_BUNDLE_FEATURE, "voiceFront"];
4058
4224
  var CORE_OPS = ["turn", "steer", "interrupt", "stop", "permission", "form"];
4059
4225
  var KNOWN_SPOKE_FEATURES = [...CORE_OPS, ...OPTIONAL_SPOKE_FEATURES, "turnEvents", PROFILE_APPLY_FEATURE, SESSION_CONTEXT_FEATURE];
4060
4226
  var SPOKE_FEATURES = [...OPTIONAL_SPOKE_FEATURES, PROFILE_APPLY_FEATURE, SESSION_CONTEXT_FEATURE];
@@ -4072,6 +4238,8 @@ function featureForOp(op) {
4072
4238
  case "revertRestore":
4073
4239
  case "revertCommit":
4074
4240
  return "revert";
4241
+ case "removeFront":
4242
+ return "voiceFront";
4075
4243
  default:
4076
4244
  return op;
4077
4245
  }
@@ -4252,11 +4420,16 @@ function isServerRequest(v) {
4252
4420
  return nonEmpty(v.sessionID) && promptBody(v);
4253
4421
  case "timelineFile":
4254
4422
  return nonEmpty(v.sessionID) && nonEmpty(v.messageID) && v.messageID.length <= 300 && Number.isInteger(v.index) && Number(v.index) >= 0 && Number(v.index) < 1000;
4423
+ case "timelineMedia":
4424
+ return nonEmpty(v.sessionID) && nonEmpty(v.messageID) && v.messageID.length <= 300 && str2(v.ref) && parseMediaRef(v.ref) !== undefined && (v.offset === undefined || isMediaOffset(v.offset));
4425
+ case "previewBundle":
4426
+ return isPreviewBundleRequest(v);
4255
4427
  case "diff":
4256
4428
  return nonEmpty(v.sessionID) && nonEmpty(v.file) && v.file.length <= 4096;
4257
4429
  case "rename":
4258
4430
  return nonEmpty(v.sessionID) && str2(v.title) && v.title.length <= 1000;
4259
4431
  case "conversation":
4432
+ return nonEmpty(v.sessionID) && typeof v.on === "boolean" && (v.frontID === undefined || nonEmpty(v.frontID) && v.frontID.length <= 300);
4260
4433
  case "expose":
4261
4434
  return nonEmpty(v.sessionID) && typeof v.on === "boolean";
4262
4435
  case "create":
@@ -4279,6 +4452,7 @@ function isServerRequest(v) {
4279
4452
  case "revertRestore":
4280
4453
  case "revertCommit":
4281
4454
  case "dismissError":
4455
+ case "removeFront":
4282
4456
  return nonEmpty(v.sessionID);
4283
4457
  default:
4284
4458
  return false;
@@ -5739,6 +5913,14 @@ function durationArg(value, flag) {
5739
5913
  throw new UsageError(`${flag} takes seconds or a duration like 30s, 5m, 2h, 1d (got "${value}")`);
5740
5914
  return Number(match[1]) * UNIT_S[match[2]];
5741
5915
  }
5916
+ var ISO_DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:?\d{2})?$/i;
5917
+ function notAfterArg(value, what) {
5918
+ const text = value.trim();
5919
+ const ms = /^\d+$/.test(text) ? Number(text) : ISO_DATE_TIME.test(text) ? Date.parse(text) : Number.NaN;
5920
+ if (!Number.isSafeInteger(ms))
5921
+ throw new UsageError(`${what} takes epoch milliseconds or an ISO 8601 date-time like 2027-01-15T12:00:00Z (got "${value}")`);
5922
+ return ms;
5923
+ }
5742
5924
  var iso = (ms) => new Date(ms).toISOString().replace(/\.\d{3}Z$/, "Z");
5743
5925
  function askOutcome(asked, waited) {
5744
5926
  const { message, answer } = asked;
@@ -5911,7 +6093,8 @@ var LAUNCH_FLAGS = {
5911
6093
  prompt: "string",
5912
6094
  end: "string",
5913
6095
  timeout: "string",
5914
- cleanup: "string"
6096
+ cleanup: "string",
6097
+ "not-after": "string"
5915
6098
  };
5916
6099
  var choices = (options) => options.length === 2 ? options.join(" or ") : `${options.slice(0, -1).join(", ")} or ${options.at(-1)}`;
5917
6100
  function oneOf3(flags, flag, options) {
@@ -5970,10 +6153,12 @@ async function launchSpecArg(flags, context) {
5970
6153
  const runtime = kind === undefined ? undefined : { kind, ...profile !== undefined ? { profile } : {} };
5971
6154
  const placement = defined({ labels: nodeLabels, machine, mode: flags.prefer === true ? "prefer" : undefined });
5972
6155
  const end = defined({ when, timeout: str(flags, "timeout"), cleanup });
6156
+ const notAfterText = str(flags, "not-after");
6157
+ const notAfter = notAfterText === undefined ? undefined : notAfterArg(notAfterText, "--not-after");
5973
6158
  return {
5974
6159
  ...defined({ key: str(flags, "key"), name, roles, harness: str(flags, "harness"), agent: str(flags, "agent"), model: str(flags, "model") }),
5975
6160
  workspace,
5976
- ...defined({ runtime, placement, prompt, labels, end })
6161
+ ...defined({ runtime, placement, prompt, labels, end, notAfter })
5977
6162
  };
5978
6163
  }
5979
6164
  var at2 = (name) => `@${name.replace(/^@+/, "")}`;
@@ -6432,6 +6617,23 @@ function dueOf(args) {
6432
6617
  throw new ToolArgError(`"due" must be an ISO date/time or epoch milliseconds`);
6433
6618
  return ms;
6434
6619
  }
6620
+ function notAfterOf(args) {
6621
+ const value = args.notAfter;
6622
+ if (value === undefined || value === null || value === "")
6623
+ return;
6624
+ if (typeof value === "number" && Number.isSafeInteger(value))
6625
+ return value;
6626
+ if (typeof value === "string") {
6627
+ try {
6628
+ return notAfterArg(value, `"notAfter"`);
6629
+ } catch (cause) {
6630
+ if (cause instanceof UsageError)
6631
+ throw new ToolArgError(cause.message);
6632
+ throw cause;
6633
+ }
6634
+ }
6635
+ throw new ToolArgError(`"notAfter" must be epoch milliseconds or an ISO 8601 date-time like 2027-01-15T12:00:00Z`);
6636
+ }
6435
6637
  var line4 = (i) => `${i.id} ${i.status}${i.progress ? `/${i.progress}` : ""} ${i.priority} r${i.revision} ${sanitizeText(i.title).replace(/\s+/g, " ")}`;
6436
6638
  function intakeText(i) {
6437
6639
  const from = i.source.kind === "user" ? "the user" : i.source.kind === "app" ? `app ${i.source.tokenID}` : `session ${i.source.sessionID}`;
@@ -6441,6 +6643,7 @@ function intakeText(i) {
6441
6643
  `status: ${i.status}${i.progress ? ` (${i.progress})` : ""} priority: ${i.priority} from: ${from}`,
6442
6644
  ...i.assignmentID ? [`assignment: ${i.assignmentID} in workspace ${i.workspaceID ?? "?"}${i.sessionID ? `, delivered to ${i.sessionID}` : ""}`] : [],
6443
6645
  ...i.due !== undefined ? [`due: ${new Date(i.due).toISOString()}`] : [],
6646
+ ...i.notAfter !== undefined ? [`not after: ${new Date(i.notAfter).toISOString()}`] : [],
6444
6647
  ...i.labels?.length ? [`labels: ${i.labels.join(", ")}`] : [],
6445
6648
  ...i.criteria?.length ? ["done means:", ...i.criteria.map((c) => `- ${sanitizeText(c)}`)] : [],
6446
6649
  ...i.ref ? [`ref: ${sanitizeText(i.ref)}`] : [],
@@ -6452,7 +6655,7 @@ function intakeText(i) {
6452
6655
  var teamIntakeTools = [
6453
6656
  {
6454
6657
  name: "team_submit",
6455
- description: "Give work to a team: its entry role (e.g. the project manager) receives the task with the team's context and owns it as an assignment in the team's workspace; while the role is vacant it waits queued. Pass `key` to make retries idempotent. App tokens need scope teams:submit; sessions submit under the team's communication policy.",
6658
+ description: "Give work to a team: its entry role (e.g. the project manager) receives the task with the team's context and owns it as an assignment in the team's workspace; while the role is vacant it waits queued (with `notAfter`, only until then: still queued at that time, it is cancelled with reason not_after instead of delivered late). Pass `key` to make retries idempotent. App tokens need scope teams:submit; sessions submit under the team's communication policy.",
6456
6659
  properties: {
6457
6660
  team: TEAM,
6458
6661
  title: { ...STRING3, description: "One line, at most 120 characters." },
@@ -6460,6 +6663,10 @@ var teamIntakeTools = [
6460
6663
  priority: { type: "string", enum: PRIORITIES, description: "Default normal." },
6461
6664
  labels: { type: "array", items: STRING3, description: "Slugs (lowercase letters, digits, dashes)." },
6462
6665
  due: { ...STRING3, description: "When it is due: an ISO date/time." },
6666
+ notAfter: {
6667
+ type: ["number", "string"],
6668
+ description: "Never deliver after this time: epoch milliseconds or an ISO 8601 date-time (at most 30 days ahead). Already passed: nothing is submitted (expired). Still queued then: cancelled (reason not_after)."
6669
+ },
6463
6670
  criteria: { type: "array", items: STRING3, description: "What done means (at most 8 lines)." },
6464
6671
  ref: { ...STRING3, description: "An external reference, e.g. a Brain task ID." },
6465
6672
  key: { ...STRING3, description: "Idempotency-Key: the same key and body return the original submission." }
@@ -6472,10 +6679,11 @@ var teamIntakeTools = [
6472
6679
  const labels = strings2(args, "labels");
6473
6680
  const criteria = strings2(args, "criteria");
6474
6681
  const due = dueOf(args);
6682
+ const notAfter = notAfterOf(args);
6475
6683
  const brief = optional4(args, "brief");
6476
6684
  const ref = optional4(args, "ref");
6477
6685
  const key = optional4(args, "key");
6478
- const { intake, created } = await hub.teams.submit(need3(args, "team"), { title: need3(args, "title"), ...brief ? { brief } : {}, ...priority ? { priority } : {}, ...labels ? { labels } : {}, ...due !== undefined ? { due } : {}, ...criteria ? { criteria } : {}, ...ref ? { ref } : {} }, { signal, ...key ? { idempotencyKey: key } : {} });
6686
+ const { intake, created } = await hub.teams.submit(need3(args, "team"), { title: need3(args, "title"), ...brief ? { brief } : {}, ...priority ? { priority } : {}, ...labels ? { labels } : {}, ...due !== undefined ? { due } : {}, ...notAfter !== undefined ? { notAfter } : {}, ...criteria ? { criteria } : {}, ...ref ? { ref } : {} }, { signal, ...key ? { idempotencyKey: key } : {} });
6479
6687
  return `${created ? "Submitted" : "Already submitted (same key)"}: ${intake.id} is ${intake.status}${intake.status === "queued" ? " (waiting for the entry role)" : ""}
6480
6688
  ${intakeText(intake)}`;
6481
6689
  }
@@ -6641,7 +6849,7 @@ var revision = (args) => {
6641
6849
  throw new ArgError(`"expectedRevision" is required: the team's revision you read (team_show)`);
6642
6850
  return value;
6643
6851
  };
6644
- var int2 = (args, key, min, max) => {
6852
+ var int3 = (args, key, min, max) => {
6645
6853
  const value = args[key];
6646
6854
  if (value === undefined || value === null)
6647
6855
  return;
@@ -6748,7 +6956,7 @@ var teamTools = [
6748
6956
  const tags = strings3(args, "tags");
6749
6957
  const project = optional5(args, "project");
6750
6958
  const metadata = record(args, "metadata");
6751
- const limit = int2(args, "limit", 1, 200);
6959
+ const limit = int3(args, "limit", 1, 200);
6752
6960
  const cursor = optional5(args, "cursor");
6753
6961
  if (args.paused !== undefined && typeof args.paused !== "boolean")
6754
6962
  throw new ArgError(`"paused" must be true or false`);
@@ -6815,8 +7023,8 @@ var teamTools = [
6815
7023
  required: ["team"],
6816
7024
  readOnly: true,
6817
7025
  run: async (hub, args, signal) => {
6818
- const from = int2(args, "from", 1, 1e9);
6819
- const to = args.to === undefined || args.to === null || args.to === "draft" ? "draft" : int2(args, "to", 1, 1e9);
7026
+ const from = int3(args, "from", 1, 1e9);
7027
+ const to = args.to === undefined || args.to === null || args.to === "draft" ? "draft" : int3(args, "to", 1, 1e9);
6820
7028
  const { from: a, to: b, diff } = await hub.teams.diff(need4(args, "team"), { signal, ...from !== undefined ? { from } : {}, to });
6821
7029
  const part = (label, d) => ["added", "removed", "changed"].flatMap((k) => d[k]?.length ? [`${label} ${k}: ${d[k].join(", ")}`] : []);
6822
7030
  const lines = [
@@ -7387,10 +7595,28 @@ var optionalExpires = (args) => {
7387
7595
  }
7388
7596
  throw new ArgError3(`"expires" must be seconds or a duration like 30s, 5m, 2h, 1d`);
7389
7597
  };
7598
+ var optionalNotAfter = (args) => {
7599
+ const value = args.notAfter;
7600
+ if (value === undefined || value === null)
7601
+ return;
7602
+ if (typeof value === "number" && Number.isSafeInteger(value))
7603
+ return value;
7604
+ if (typeof value === "string") {
7605
+ try {
7606
+ return notAfterArg(value, '"notAfter"');
7607
+ } catch (cause) {
7608
+ if (cause instanceof UsageError)
7609
+ throw new ArgError3(cause.message);
7610
+ throw cause;
7611
+ }
7612
+ }
7613
+ throw new ArgError3(`"notAfter" must be epoch milliseconds or an ISO 8601 date-time like 2027-01-15T12:00:00Z`);
7614
+ };
7390
7615
  var sendOptions = (args) => {
7391
7616
  const wake = optionalBool(args, "wake");
7392
7617
  const urgent = optionalBool(args, "urgent");
7393
- return { ...wake !== undefined ? { wake } : {}, ...urgent !== undefined ? { urgent } : {} };
7618
+ const notAfter = optionalNotAfter(args);
7619
+ return { ...wake !== undefined ? { wake } : {}, ...urgent !== undefined ? { urgent } : {}, ...notAfter !== undefined ? { notAfter } : {} };
7394
7620
  };
7395
7621
  var whyOption = (args) => {
7396
7622
  const why = optional7(args, "why");
@@ -7464,7 +7690,8 @@ function launchSpec(args) {
7464
7690
  placement: definedFields({ labels: nodeLabels, machine, mode: prefer ? "prefer" : undefined }),
7465
7691
  prompt: optional7(args, "prompt"),
7466
7692
  labels: optionalLabels(args, "labels"),
7467
- end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout: optional7(args, "timeout"), cleanup: optionalEnum(args, "cleanup", CLEANUP_POLICIES) })
7693
+ end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout: optional7(args, "timeout"), cleanup: optionalEnum(args, "cleanup", CLEANUP_POLICIES) }),
7694
+ notAfter: optionalNotAfter(args)
7468
7695
  })
7469
7696
  };
7470
7697
  }
@@ -7514,6 +7741,7 @@ var STRING6 = { type: "string" };
7514
7741
  var SEND = {
7515
7742
  wake: { type: "boolean", description: "false: admit the message without starting a turn." },
7516
7743
  urgent: { type: "boolean", description: "Steer into the recipient's running turn." },
7744
+ notAfter: { type: ["number", "string"], description: "Never deliver after this time: epoch milliseconds or an ISO 8601 date-time (at most 30 days ahead). Already passed: nothing is sent (expired)." },
7517
7745
  all: { type: "boolean", description: "Send to every session the address names (each holder of a role)." }
7518
7746
  };
7519
7747
  var TO = { type: "string", description: "The recipient: @name, @@role, @@role#team (a role of one of your teams), @name.project, ses_…, or @me (the user)." };
@@ -7671,7 +7899,7 @@ var webhookText = (w) => `${w.id}${w.label ? ` ${w.label}` : ""} ${w.url} ${w
7671
7899
  var webhookTools = [
7672
7900
  {
7673
7901
  name: "webhook_create",
7674
- description: "Create a durable signed webhook for canonical compact events. The signing secret is returned only here. Needs events:subscribe plus every selected family's scope.",
7902
+ description: `Create a durable signed webhook for canonical compact events (e.g. launch.*, session.*, message.changed, team.intake). The signing secret is returned only here. Needs events:subscribe plus every selected family's scope; events ["team.intake"] alone needs only teams:submit (an app gets only its own submissions).`,
7675
7903
  properties: { url: STRING6, events: { type: "array", items: STRING6 }, label: STRING6 },
7676
7904
  required: ["url"],
7677
7905
  readOnly: false,
@@ -8188,13 +8416,13 @@ var TOOLS = [
8188
8416
  {
8189
8417
  name: "msg",
8190
8418
  description: "Tell a session something (fire and forget). Sent as this server's credential, not as an OpenCode session.",
8191
- properties: { to: TO, text: { ...STRING6, description: "The message." }, ...SEND },
8419
+ properties: { to: TO, text: { ...STRING6, description: "The message." }, ...SEND, idempotencyKey: IDEMPOTENCY3 },
8192
8420
  required: ["to", "text"],
8193
8421
  readOnly: false,
8194
8422
  run: async (hub, args, signal) => {
8195
8423
  const to = need7(args, "to");
8196
8424
  const text = need7(args, "text");
8197
- const options = { ...sendOptions(args), signal };
8425
+ const options = { ...sendOptions(args), ...idemOption(args), signal };
8198
8426
  if (optionalBool(args, "all"))
8199
8427
  return sentAllText(await hub.msg(to, text, { ...options, all: true }));
8200
8428
  return sentText(await hub.msg(to, text, options));
@@ -8208,7 +8436,8 @@ var TOOLS = [
8208
8436
  text: { ...STRING6, description: "The question." },
8209
8437
  wait: { type: "number", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait for the answer (0–${WAIT_POLL_MAX_S}).` },
8210
8438
  expires: { type: ["number", "string"], description: "When the ask expires: seconds, or 30s, 5m, 2h, 1d (1 minute to 7 days; default 24 h)." },
8211
- ...SEND
8439
+ ...SEND,
8440
+ idempotencyKey: IDEMPOTENCY3
8212
8441
  },
8213
8442
  required: ["to", "text"],
8214
8443
  readOnly: false,
@@ -8217,7 +8446,7 @@ var TOOLS = [
8217
8446
  const text = need7(args, "text");
8218
8447
  const wait = optionalWait(args);
8219
8448
  const expires = optionalExpires(args);
8220
- const options = { ...sendOptions(args), ...expires !== undefined ? { expires } : {}, signal };
8449
+ const options = { ...sendOptions(args), ...expires !== undefined ? { expires } : {}, ...idemOption(args), signal };
8221
8450
  if (optionalBool(args, "all")) {
8222
8451
  if (wait !== undefined)
8223
8452
  throw new ArgError3(`"wait" cannot be combined with "all"`);
@@ -8381,7 +8610,7 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
8381
8610
  },
8382
8611
  {
8383
8612
  name: "launch",
8384
- description: "Start a new session on a node (a hub node with the repo under its workspace roots). The same key again answers the same launch. Needs an app token with scope sessions:launch and a grant with verb launch covering the repo.",
8613
+ description: "Start a new session on a node (a hub node with the repo under its workspace roots). The same key again answers the same launch. With notAfter, a launch still waiting for a machine at that time is never started (it ends expired). Needs an app token with scope sessions:launch and a grant with verb launch covering the repo.",
8385
8614
  properties: {
8386
8615
  repo: { ...STRING6, description: "Absolute path of the repository on the node (or where a clone goes)." },
8387
8616
  mode: { type: "string", enum: [...WORKSPACE_MODES], description: "worktree (default: <repo>/.worktrees/<branch>, needs branch), checkout, or clone (needs remote)." },
@@ -8403,7 +8632,8 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
8403
8632
  prompt: { ...STRING6, description: "The session's first prompt." },
8404
8633
  end: { type: "string", enum: [...LAUNCH_END_WHEN], description: "When it ends: idle (default; idle with nothing open for 30 s), asked (with a question open: needs_user), never." },
8405
8634
  timeout: { ...STRING6, description: "Longest run, e.g. 4h or 90m." },
8406
- cleanup: { type: "string", enum: [...CLEANUP_POLICIES], description: "keep (default) or remove the workspace when it ends." }
8635
+ cleanup: { type: "string", enum: [...CLEANUP_POLICIES], description: "keep (default) or remove the workspace when it ends." },
8636
+ notAfter: { type: ["number", "string"], description: "Do not start after this time: epoch milliseconds or an ISO 8601 date-time (at most 30 days ahead). Still waiting for a machine then, the launch ends expired; once placed it runs (timeout bounds it). Already passed: nothing is launched (expired)." }
8407
8637
  },
8408
8638
  required: ["repo"],
8409
8639
  readOnly: false,
@@ -8945,7 +9175,7 @@ import { arch as osArch, platform as osPlatform } from "node:os";
8945
9175
  // packages/node/package.json
8946
9176
  var package_default2 = {
8947
9177
  name: "@huynle/hub-node",
8948
- version: "0.8.7",
9178
+ version: "0.9.0",
8949
9179
  private: true,
8950
9180
  description: "`hub node`: the per-machine daemon that prepares, collects and cleans up launch workspaces (internal; shipped inside @huynle/hub, not published on its own).",
8951
9181
  license: "MIT",
@@ -10037,7 +10267,7 @@ import path14 from "node:path";
10037
10267
  // packages/runtime-openshell/package.json
10038
10268
  var package_default3 = {
10039
10269
  name: "@huynle/hub-runtime-openshell",
10040
- version: "0.8.7",
10270
+ version: "0.9.0",
10041
10271
  private: true,
10042
10272
  description: "The `openshell` runtime of `hub node`: launches in NVIDIA OpenShell sandboxes, driven through the openshell CLI (internal; shipped inside @huynle/hub, not published on its own).",
10043
10273
  license: "MIT",
@@ -13721,7 +13951,7 @@ import path23 from "node:path";
13721
13951
  // packages/claude-code/package.json
13722
13952
  var package_default4 = {
13723
13953
  name: "@huynle/hub-claude-code",
13724
- version: "0.8.7",
13954
+ version: "0.9.0",
13725
13955
  private: true,
13726
13956
  description: "The Claude Code hooks spoke: per-user daemon, hook bridge, plugin dir, `hub claude` wrapper and daemon-backed MCP server (internal; shipped inside @huynle/hub, not published on its own).",
13727
13957
  license: "MIT",
@@ -14010,7 +14240,7 @@ import path26 from "node:path";
14010
14240
  // packages/preset/package.json
14011
14241
  var package_default5 = {
14012
14242
  name: "@huynle/hub-preset",
14013
- version: "0.8.7",
14243
+ version: "0.9.0",
14014
14244
  private: true,
14015
14245
  description: "The preset spoke: drives a harness without a spoke of its own through a terminal pane (tmux), described by a data-only preset; official presets in presets/ (internal; shipped inside @huynle/hub, not published on its own).",
14016
14246
  license: "MIT",
@@ -14535,7 +14765,7 @@ import { openSync as openSync6, closeSync as closeSync6, readFileSync as readFil
14535
14765
  // packages/codex/package.json
14536
14766
  var package_default6 = {
14537
14767
  name: "@huynle/hub-codex",
14538
- version: "0.8.7",
14768
+ version: "0.9.0",
14539
14769
  private: true,
14540
14770
  description: "The Codex app-server spoke: attaches to Codex's app-server (its own over stdio, and the shared daemon's socket when present), and the `hub codex` wrapper (internal; shipped inside @huynle/hub, not published on its own).",
14541
14771
  license: "MIT",
@@ -25130,10 +25360,13 @@ Identity
25130
25360
  hub role release <role[.project]> [--session <addr>]
25131
25361
 
25132
25362
  Messaging (text "-" reads it from stdin)
25133
- hub msg <addr> "text" [--all] [--no-wake] [--urgent] tell: fire and forget
25134
- hub ask <addr> "text" [--wait [secs]] [--expires 2h] [--all] [--no-wake] [--urgent]
25363
+ hub msg <addr> "text" [--all] [--no-wake] [--urgent] [--not-after <ms|ISO time>] [--key K]
25364
+ tell: fire and forget; --not-after: never delivered after that time
25365
+ hub ask <addr> "text" [--wait [secs]] [--expires 2h] [--not-after <ms|ISO time>] [--all] [--no-wake] [--urgent] [--key K]
25135
25366
  tracked until answered, declined or expired; --wait alone waits until
25136
- it expires; exit 0 answered, 1 declined or no answer yet, 8 expired
25367
+ it expires; exit 0 answered, 1 declined or no answer yet, 8 expired;
25368
+ --key (Idempotency-Key): a retry with the same key and text answers
25369
+ the first send again and sends nothing (24 h, per credential)
25137
25370
  hub reply <msgID> "text" [--no-wake] goes to the real sender
25138
25371
  hub decline <msgID> --why "reason"
25139
25372
  hub ask @me "question" [--option id=label[:safe]]... [--recommend id [--why "…"]] [--urgency low|normal|high]
@@ -25168,9 +25401,10 @@ Launching (users; agents and app tokens with sessions:launch under a grant with
25168
25401
  [--name <@name>] [--role R]... [--harness H] [--agent A] [--model provider/model]
25169
25402
  [--runtime host|openshell [--profile P]] [--machine M] [--node-label k=v]... [--prefer]
25170
25403
  [--label k=v]... [--prompt "text" | --prompt -] [--end idle|asked|never] [--timeout 4h]
25171
- [--cleanup keep|remove] [--key K]
25404
+ [--cleanup keep|remove] [--key K] [--not-after <ms|ISO time>]
25172
25405
  start a session; the same --key again answers the same launch;
25173
- --label tags it for apps, --node-label picks nodes (--prefer: not required)
25406
+ --label tags it for apps, --node-label picks nodes (--prefer: not required);
25407
+ --not-after: still waiting for a machine then, it never starts (expired)
25174
25408
  hub launches list [--state S[,S]] [--label k=v]... your launches (agents: the ones they started)
25175
25409
  hub launches show <launchID> · hub launches stop <launchID> (stop: exit 8 when it already ended)
25176
25410
 
@@ -25299,7 +25533,16 @@ var whyOf = (flags) => {
25299
25533
  const why = str(flags, "why");
25300
25534
  return why !== undefined ? { why } : {};
25301
25535
  };
25302
- var sendFlags = (flags) => ({ ...flags["no-wake"] === true ? { wake: false } : {}, ...flags.urgent === true ? { urgent: true } : {} });
25536
+ var sendFlags = (flags) => {
25537
+ const notAfter = str(flags, "not-after");
25538
+ const key = str(flags, "key");
25539
+ return {
25540
+ ...flags["no-wake"] === true ? { wake: false } : {},
25541
+ ...flags.urgent === true ? { urgent: true } : {},
25542
+ ...notAfter === undefined ? {} : { notAfter: notAfterArg(notAfter, "--not-after") },
25543
+ ...key !== undefined ? { idempotencyKey: key } : {}
25544
+ };
25545
+ };
25303
25546
  var nodeIo = (io) => ({
25304
25547
  env: io.env,
25305
25548
  home: io.home,
@@ -25421,7 +25664,7 @@ var COMMANDS = {
25421
25664
  },
25422
25665
  msg: {
25423
25666
  args: ["<addr>", "<text>"],
25424
- flags: { all: "bool", "no-wake": "bool", urgent: "bool" },
25667
+ flags: { all: "bool", "no-wake": "bool", urgent: "bool", "not-after": "string", key: "string" },
25425
25668
  async handle(context) {
25426
25669
  const { flags, args } = context.parsed;
25427
25670
  const text = await textArg(args[1], context.io.stdin);
@@ -25433,7 +25676,7 @@ var COMMANDS = {
25433
25676
  },
25434
25677
  ask: {
25435
25678
  args: ["<addr>", "<text>"],
25436
- flags: { all: "bool", "no-wake": "bool", urgent: "bool", expires: "string", wait: "optional", ...DECISION_FLAGS },
25679
+ flags: { all: "bool", "no-wake": "bool", urgent: "bool", "not-after": "string", key: "string", expires: "string", wait: "optional", ...DECISION_FLAGS },
25437
25680
  async handle(context) {
25438
25681
  const { flags, args } = context.parsed;
25439
25682
  const decision = decisionOptions(flags, args[0]);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huynle/hub-cli",
3
- "version": "0.8.7",
3
+ "version": "0.9.0",
4
4
  "private": true,
5
5
  "description": "The hub CLI (`hub` / `hubctl`) for agents and scripts (internal; bundled into @huynle/hub, not published on its own).",
6
6
  "license": "MIT",