@huynle/hub 0.8.0 → 0.8.2

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 (133) hide show
  1. package/CHANGELOG.md +326 -1
  2. package/README.md +85 -16
  3. package/package.json +1 -1
  4. package/packages/claude-code/package.json +1 -1
  5. package/packages/claude-code/src/paths.ts +3 -3
  6. package/packages/cli/dist/hub-cli.js +723 -281
  7. package/packages/cli/package.json +1 -1
  8. package/packages/cli/src/artifact-jobs-mcp.ts +10 -9
  9. package/packages/cli/src/artifacts-mcp.ts +8 -7
  10. package/packages/cli/src/bootstrap/adapters/node-services/render.ts +2 -2
  11. package/packages/cli/src/bootstrap/discovery/host.ts +15 -1
  12. package/packages/cli/src/bootstrap/discovery/probe.ts +1 -1
  13. package/packages/cli/src/bootstrap/enrollment/enroll.ts +3 -3
  14. package/packages/cli/src/bootstrap/enrollment/policy.ts +13 -0
  15. package/packages/cli/src/cli.ts +3 -3
  16. package/packages/cli/src/http.ts +1 -0
  17. package/packages/cli/src/launches.ts +9 -3
  18. package/packages/cli/src/login.ts +2 -2
  19. package/packages/cli/src/mcp.ts +9 -4
  20. package/packages/cli/src/messaging.ts +4 -2
  21. package/packages/cli/src/profiles-mcp.ts +301 -0
  22. package/packages/cli/src/setup/commands.ts +20 -2
  23. package/packages/cli/src/spoke-services.ts +1 -1
  24. package/packages/cli/src/team-intake-mcp.ts +8 -7
  25. package/packages/cli/src/teams-mcp.ts +61 -3
  26. package/packages/cli/src/tool-args.ts +6 -0
  27. package/packages/cli/src/watchlist.ts +5 -4
  28. package/packages/codex/package.json +1 -1
  29. package/packages/codex/src/paths.ts +3 -3
  30. package/packages/node/package.json +1 -1
  31. package/packages/node/src/config.ts +10 -2
  32. package/packages/node/src/opencode-warm.ts +27 -8
  33. package/packages/node/src/run.ts +3 -2
  34. package/packages/preset/package.json +1 -1
  35. package/packages/protocol/package.json +1 -1
  36. package/packages/protocol/src/concierge.ts +4 -1
  37. package/packages/protocol/src/embedded-media.ts +59 -0
  38. package/packages/protocol/src/index.ts +90 -7
  39. package/packages/protocol/src/launch.ts +14 -0
  40. package/packages/protocol/src/wire.ts +1 -0
  41. package/packages/runtime-openshell/package.json +1 -1
  42. package/packages/runtime-sdk/package.json +1 -1
  43. package/packages/sdk/package.json +1 -1
  44. package/packages/sdk/src/client.ts +3 -1
  45. package/packages/sdk/src/errors.ts +1 -0
  46. package/packages/sdk/src/index.ts +1 -0
  47. package/packages/sdk/src/types.ts +22 -2
  48. package/packages/sdk/src/wake.ts +20 -0
  49. package/packages/spoke-sdk/package.json +1 -1
  50. package/packages/spoke-sdk/src/connection.ts +13 -1
  51. package/src/central/agent/acting-routes.ts +3 -2
  52. package/src/central/agent/acting.ts +70 -12
  53. package/src/central/agent/audit.ts +1 -1
  54. package/src/central/agent/decisions.ts +58 -2
  55. package/src/central/agent/delivery.ts +111 -28
  56. package/src/central/agent/errors.ts +2 -0
  57. package/src/central/agent/escalation.ts +39 -10
  58. package/src/central/agent/events.ts +7 -1
  59. package/src/central/agent/launch-engine.ts +173 -30
  60. package/src/central/agent/launch-routes.ts +20 -9
  61. package/src/central/agent/launch-spec.ts +60 -17
  62. package/src/central/agent/launch-store.ts +55 -2
  63. package/src/central/agent/message-limits.ts +89 -11
  64. package/src/central/agent/message-routes.ts +8 -3
  65. package/src/central/agent/messages.ts +3 -2
  66. package/src/central/agent/placement.ts +93 -18
  67. package/src/central/agent/read-routes.ts +3 -0
  68. package/src/central/agent/routes.ts +11 -2
  69. package/src/central/agent/webhooks.ts +17 -2
  70. package/src/central/artifacts/jobs-routes.ts +4 -1
  71. package/src/central/concierge/board-routes.ts +13 -2
  72. package/src/central/concierge/context.ts +3 -1
  73. package/src/central/concierge/inbound.ts +4 -7
  74. package/src/central/concierge/prompt.ts +10 -6
  75. package/src/central/concierge/proposals.ts +8 -2
  76. package/src/central/concierge/routes.ts +62 -2
  77. package/src/central/concierge/session-routes.ts +62 -6
  78. package/src/central/concierge/supervisor.ts +34 -7
  79. package/src/central/concierge/watchlist-routes.ts +4 -2
  80. package/src/central/config.ts +21 -5
  81. package/src/central/http.ts +87 -35
  82. package/src/central/main.ts +1 -1
  83. package/src/central/registry.ts +49 -4
  84. package/src/central/revert-windows.ts +66 -0
  85. package/src/central/spoke-ws.ts +6 -3
  86. package/src/central/teams/intake.ts +20 -6
  87. package/src/central/teams/staffing.ts +24 -2
  88. package/src/central/workspaces/sidechats-routes.ts +1 -1
  89. package/src/index.ts +63 -30
  90. package/src/migrate.ts +109 -3
  91. package/src/spoke/deliver.ts +6 -0
  92. package/src/spoke/env-inject.ts +8 -1
  93. package/src/spoke/fork-revert.ts +94 -9
  94. package/src/spoke/hub-tool.ts +26 -12
  95. package/src/spoke/launchers.ts +6 -4
  96. package/src/spoke/listing.ts +18 -6
  97. package/src/spoke/localhub.ts +14 -4
  98. package/src/spoke/locations.ts +120 -0
  99. package/src/spoke/profile-apply.ts +24 -14
  100. package/src/spoke/session-context.ts +183 -33
  101. package/src/spoke/spoke.ts +129 -41
  102. package/src/spoke/timeline.ts +30 -3
  103. package/src/spoke/tracker.ts +17 -1
  104. package/src/status.ts +2 -1
  105. package/web/dist/assets/ArtifactPage-R0ZTpHca.js +1 -0
  106. package/web/dist/assets/ArtifactPane-C9KrBna8.js +1 -0
  107. package/web/dist/assets/ArtifactWorkbench-sP2mZccC.js +6 -0
  108. package/web/dist/assets/Library-BXbJvTUS.js +2 -0
  109. package/web/dist/assets/ProfilesDestination-D7XyLTJ7.js +1 -0
  110. package/web/dist/assets/TeamsPage-Dgxt8UI3.js +1 -0
  111. package/web/dist/assets/Watchtower-DJHx6cWw.js +2 -0
  112. package/web/dist/assets/index-BabrNDB3.css +1 -0
  113. package/web/dist/assets/index-g4V9mfSr.js +34 -0
  114. package/web/dist/assets/live-OgQWPrSY.js +5 -0
  115. package/web/dist/assets/{pane-layout-CKC4I00k.js → pane-layout-DWXfDU-1.js} +1 -1
  116. package/web/dist/assets/{renderer2d-CX5j3NcD.js → renderer2d-DjEipyto.js} +1 -1
  117. package/web/dist/assets/{renderer3d-BXIVOvfi.js → renderer3d-B648jKGO.js} +1 -1
  118. package/web/dist/assets/{safe-area-DfPsOdWR.js → safe-area-CNLyboIj.js} +1 -1
  119. package/web/dist/assets/{silero-worker-C8-8vLVS.js → silero-worker-DhlIHyD_.js} +1 -1
  120. package/web/dist/assets/{source-CvUHTc3O.js → source-CDU04kYw.js} +1 -1
  121. package/web/dist/assets/ui-Dh1-78Cl.js +3 -0
  122. package/web/dist/index.html +4 -4
  123. package/web/dist/assets/ArtifactPage-BS0gVkfu.js +0 -1
  124. package/web/dist/assets/ArtifactPane-PLYHNs6s.js +0 -1
  125. package/web/dist/assets/ArtifactWorkbench-LkDyJ5Dv.js +0 -6
  126. package/web/dist/assets/Library-CtxRqTjo.js +0 -2
  127. package/web/dist/assets/ProfilesDestination-BYiHKLmc.js +0 -1
  128. package/web/dist/assets/TeamsPage-BwjsAAUC.js +0 -1
  129. package/web/dist/assets/Watchtower-C4swrWVs.js +0 -2
  130. package/web/dist/assets/index-DaH5GUdV.js +0 -31
  131. package/web/dist/assets/index-WWuKfWxL.css +0 -1
  132. package/web/dist/assets/live-YyTjE47g.js +0 -5
  133. package/web/dist/assets/ui-C9Igxzjm.js +0 -1
@@ -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.0",
10
+ version: "0.8.2",
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",
@@ -863,7 +863,7 @@ class HubClient {
863
863
  return this.call("POST", `/api/decisions/${segment(id)}/answer`, { body, ...signalOf(answer) });
864
864
  }
865
865
  approve(addr, requestID, options = {}) {
866
- const body = { requestID, ...options.always ? { always: true } : {}, ...whyField(options) };
866
+ const body = { requestID, ...options.always ? { always: true } : {}, ...options.recommend ? { recommend: true } : {}, ...whyField(options) };
867
867
  return this.call("POST", `${AGENT}/sessions/${segment(addr)}/approve`, { body, ...signalOf(options) });
868
868
  }
869
869
  deny(addr, requestID, options = {}) {
@@ -961,6 +961,18 @@ function renderEnvelope(input) {
961
961
  return [header, boundary, escapeBody(input.text), boundary, hint].join(`
962
962
  `);
963
963
  }
964
+ // packages/sdk/src/wake.ts
965
+ var WAKE_SKIP_TEXT = {
966
+ wake_false: "sent without a wake",
967
+ subagent: "subagents are never woken",
968
+ hop_cap: "hop limit reached (8 agent messages without a human turn)",
969
+ budget: "wake budget spent (30 per 10 minutes)"
970
+ };
971
+ function wakeSkipNote({ delivery, woke, wakeSkipped }) {
972
+ if (woke !== false || !wakeSkipped || delivery === "steer" || delivery === "turn")
973
+ return "";
974
+ return ` — wake skipped: ${WAKE_SKIP_TEXT[wakeSkipped] ?? wakeSkipped}`;
975
+ }
964
976
  // packages/cli/src/http.ts
965
977
  var EXIT = {
966
978
  ok: 0,
@@ -986,6 +998,7 @@ var EXIT_OF = {
986
998
  rate_limited: EXIT.rateLimited,
987
999
  unsupported: EXIT.unavailable,
988
1000
  offline: EXIT.unavailable,
1001
+ busy: EXIT.unavailable,
989
1002
  gone: EXIT.gone,
990
1003
  unauthorized: EXIT.unauthorized,
991
1004
  token_expired: EXIT.unauthorized
@@ -1133,6 +1146,9 @@ function storedInsecureWarning(value) {
1133
1146
  } catch {}
1134
1147
  return `the stored hub URL ${shown} is plain http: your app token and everything this command sends or receives travel unencrypted on the network; log in again with an https URL (hub login --url https://<hub>)`;
1135
1148
  }
1149
+ function shownURL(value) {
1150
+ return value.trimStart().replace(/^((?:https?:|[a-z][a-z0-9+.-]*:(?=[\/\\]))?[\/\\]*)[^\/\\?#]*@/i, "$1***@").slice(0, 200);
1151
+ }
1136
1152
 
1137
1153
  // packages/cli/src/login.ts
1138
1154
  var LOGIN_MAX_WAIT_MS = 15 * 60000;
@@ -1179,7 +1195,7 @@ async function deviceLogin(options) {
1179
1195
  const server = checkServerURL(options.server, allow);
1180
1196
  if (!server) {
1181
1197
  const why = insecureHTTPRefusal(options.server, allow);
1182
- throw new UsageError(why ? `${why}: ${String(options.server).slice(0, 200)}` : `the hub URL must be https (plain http only for a loopback hub), without user info, query or fragment: ${String(options.server).slice(0, 200)}`);
1198
+ throw new UsageError(why ? `${why}: ${shownURL(String(options.server))}` : `the hub URL must be https (plain http only for a loopback hub), without user info, query or fragment: ${shownURL(String(options.server))}`);
1183
1199
  }
1184
1200
  const insecure = isPlainHTTP(server) && !isLoopbackServer(server) ? insecureHTTPWarning(server) : undefined;
1185
1201
  const fetcher = options.fetch ?? fetch;
@@ -3094,7 +3110,33 @@ import path3 from "node:path";
3094
3110
  var APP_DIR = "hub";
3095
3111
  var LEGACY_APP_DIRS = Object.freeze(["opencode-hub", "opencode-voice"]);
3096
3112
  var configBase = (env, home) => env.XDG_CONFIG_HOME || path3.join(home, ".config");
3097
- var stateBase = (home) => path3.join(home, ".local", "state");
3113
+ var xdgStateHome = (env) => env.XDG_STATE_HOME && path3.isAbsolute(env.XDG_STATE_HOME) ? env.XDG_STATE_HOME : undefined;
3114
+ var HUB_STATE_MARKERS = Object.freeze(["state.json", "token-keys.json", "agent", "blobs", "concierge-sessions.json", "bin", "hub.log", "hub.pid", "claude-code", "codex"]);
3115
+ var holdsHubState = (dir, exists = existsSync) => HUB_STATE_MARKERS.some((marker) => exists(path3.join(dir, marker)));
3116
+ var shellQuote = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
3117
+ function resolveHubStateDir(env, home, exists = existsSync) {
3118
+ const legacy = path3.join(home, ".local", "state", APP_DIR);
3119
+ const xdg = xdgStateHome(env);
3120
+ const xdgHub = xdg ? path3.join(xdg, APP_DIR) : undefined;
3121
+ const defaults = [legacy, ...xdgHub && xdgHub !== legacy ? [xdgHub] : []];
3122
+ const result = (dir, source, hint) => ({
3123
+ dir,
3124
+ source,
3125
+ ...hint ? { hint } : {},
3126
+ candidates: [dir, ...defaults.filter((d) => d !== dir)]
3127
+ });
3128
+ const explicit = hubEnv(env).HUB_STATE_DIR;
3129
+ if (explicit)
3130
+ return result(explicit, "env");
3131
+ const holdsState = (dir) => holdsHubState(dir, exists);
3132
+ if (xdgHub && holdsState(xdgHub))
3133
+ return result(xdgHub, "xdg");
3134
+ if (holdsState(legacy)) {
3135
+ const hint = xdgHub && xdgHub !== legacy ? `XDG_STATE_HOME is set, but the existing ${legacy} is used as the hub state dir; to switch, stop the hub and OpenCode, then: mv ${shellQuote(legacy)} ${shellQuote(xdgHub)}` : undefined;
3136
+ return result(legacy, "legacy", hint);
3137
+ }
3138
+ return xdgHub ? result(xdgHub, "fresh") : result(legacy, "default");
3139
+ }
3098
3140
  var exists = (p) => existsSync(p) || isLink(p);
3099
3141
  function isLink(p) {
3100
3142
  try {
@@ -3810,6 +3852,9 @@ function formAnswerProblem(v) {
3810
3852
  }
3811
3853
  var REVERT_FILES_MAX = 50;
3812
3854
  var REVERT_FILE_MAX_CHARS = 1000;
3855
+ function isRevertCommitted(v) {
3856
+ return isObj(v) && nonEmpty(v.messageID) && v.messageID.length <= 300 && (v.from === undefined || num(v.from)) && num(v.until) && num(v.at);
3857
+ }
3813
3858
  var FORWARDED_EVENTS = new Set([
3814
3859
  "session.created",
3815
3860
  "session.renamed",
@@ -3901,13 +3946,14 @@ function isLaunchCreate(req) {
3901
3946
  }
3902
3947
  var AGENT_NAME_MAX = 100;
3903
3948
  var PERMISSION_MESSAGE_MAX = 1000;
3949
+ var RES_CODE_MAX = 40;
3904
3950
  var isModelSelection = (v) => isObj(v) && typeof v.providerID === "string" && ID.test(v.providerID) && typeof v.modelID === "string" && ID.test(v.modelID) && (v.variant === undefined || typeof v.variant === "string" && ID.test(v.variant));
3905
3951
  var SESSION_TOKEN_TEXT = new RegExp(`^[\\x21-\\x7e]{1,${SESSION_TOKEN_MAX_CHARS}}$`);
3906
3952
  function isSessionToken(v) {
3907
3953
  return isObj(v) && nonEmpty(v.sessionID) && v.sessionID.length <= 300 && str2(v.token) && SESSION_TOKEN_TEXT.test(v.token) && num(v.exp);
3908
3954
  }
3909
3955
  function isSession(v) {
3910
- return isObj(v) && nonEmpty(v.id) && str2(v.title) && str2(v.directory) && num(v.updated) && typeof v.owned === "boolean" && (v.parentID === undefined || str2(v.parentID)) && (v.exposed === undefined || typeof v.exposed === "boolean") && (v.project === undefined || str2(v.project)) && (v.projectName === undefined || str2(v.projectName)) && (v.archived === undefined || typeof v.archived === "boolean") && (v.open === undefined || typeof v.open === "boolean") && (v.terminal === undefined || isTerminal(v.terminal)) && (v.revert === undefined || isSessionRevert(v.revert)) && (v.agent === undefined || nonEmpty(v.agent) && v.agent.length <= AGENT_NAME_MAX) && (v.contextRevision === undefined || Number.isSafeInteger(v.contextRevision) && v.contextRevision >= 0);
3956
+ return isObj(v) && nonEmpty(v.id) && str2(v.title) && str2(v.directory) && num(v.updated) && typeof v.owned === "boolean" && (v.parentID === undefined || str2(v.parentID)) && (v.exposed === undefined || typeof v.exposed === "boolean") && (v.project === undefined || str2(v.project)) && (v.projectName === undefined || str2(v.projectName)) && (v.archived === undefined || typeof v.archived === "boolean") && (v.open === undefined || typeof v.open === "boolean") && (v.terminal === undefined || isTerminal(v.terminal)) && (v.revert === undefined || isSessionRevert(v.revert)) && (v.revertCommitted === undefined || isRevertCommitted(v.revertCommitted)) && (v.agent === undefined || nonEmpty(v.agent) && v.agent.length <= AGENT_NAME_MAX) && (v.contextRevision === undefined || Number.isSafeInteger(v.contextRevision) && v.contextRevision >= 0) && (v.locked === undefined || v.locked === true);
3911
3957
  }
3912
3958
  var REVERT_STATUS = new Set(["added", "deleted", "modified"]);
3913
3959
  var count = (v) => Number.isSafeInteger(v) && v >= 0;
@@ -3915,7 +3961,7 @@ function isRevertFile(v) {
3915
3961
  return isObj(v) && nonEmpty(v.file) && v.file.length <= REVERT_FILE_MAX_CHARS && (v.status === undefined || REVERT_STATUS.has(v.status)) && (v.additions === undefined || count(v.additions)) && (v.deletions === undefined || count(v.deletions));
3916
3962
  }
3917
3963
  function isSessionRevert(v) {
3918
- return isObj(v) && nonEmpty(v.messageID) && v.messageID.length <= 300 && count(v.hidden) && (v.at === undefined || num(v.at)) && Array.isArray(v.files) && v.files.length <= REVERT_FILES_MAX && v.files.every(isRevertFile) && count(v.filesTotal) && v.filesTotal >= v.files.length;
3964
+ return isObj(v) && nonEmpty(v.messageID) && v.messageID.length <= 300 && count(v.hidden) && (v.at === undefined || num(v.at)) && Array.isArray(v.files) && v.files.length <= REVERT_FILES_MAX && v.files.every(isRevertFile) && count(v.filesTotal) && v.filesTotal >= v.files.length && (v.stagedAt === undefined || num(v.stagedAt));
3919
3965
  }
3920
3966
  var TERMINAL_STATUS = new Set(["idle", "working", "blocked", "done", "unknown"]);
3921
3967
  function isTerminal(v) {
@@ -4099,8 +4145,12 @@ function parseSpokeMessage(raw) {
4099
4145
  switch (m.t) {
4100
4146
  case "hello":
4101
4147
  return (m.role === undefined || m.role === "spoke") && num(m.v) && nonEmpty(m.processID) && (m.kind === "service" || m.kind === "standalone") && str2(m.directory) && num(m.pid) && (m.mode === "always" || m.mode === "session") && (str2(m.opencodeVersion) || m.opencodeVersion === undefined && m.harness !== undefined && m.harness !== HARNESS_OPENCODE) && str2(m.pluginVersion) && (m.features === undefined || featureList(m.features)) && (m.harness === undefined || token(m.harness, 64)) && (m.harnessVersion === undefined || str2(m.harnessVersion) && m.harnessVersion.length <= 100) && (m.evidence === undefined || token(m.evidence, 32)) && (m.launchID === undefined || str2(m.launchID) && ID.test(m.launchID)) ? m : undefined;
4102
- case "sessions":
4103
- return Array.isArray(m.sessions) && m.sessions.every(isSession) ? m : undefined;
4148
+ case "sessions": {
4149
+ if (!Array.isArray(m.sessions))
4150
+ return;
4151
+ const sessions = m.sessions.map(lenientSession);
4152
+ return sessions.every(isSession) ? { ...m, sessions } : undefined;
4153
+ }
4104
4154
  case "terminals":
4105
4155
  return m.herdr === undefined || isHerdrInfo(m.herdr) ? m : undefined;
4106
4156
  case "event":
@@ -4108,13 +4158,34 @@ function parseSpokeMessage(raw) {
4108
4158
  case "req":
4109
4159
  return nonEmpty(m.id) && isSpokeRequest(m.req) ? m : undefined;
4110
4160
  case "res":
4111
- return isResFrame(m) ? m : undefined;
4161
+ return isResFrame(m) ? lenientRes(m) : undefined;
4112
4162
  case "bye":
4113
4163
  return isByeFrame(m) ? m : undefined;
4114
4164
  default:
4115
4165
  return;
4116
4166
  }
4117
4167
  }
4168
+ function lenientRes(m) {
4169
+ if (m.code === undefined || nonEmpty(m.code) && m.code.length <= RES_CODE_MAX)
4170
+ return m;
4171
+ const { code: _dropped, ...rest } = m;
4172
+ return rest;
4173
+ }
4174
+ function lenientSession(v) {
4175
+ if (!isObj(v))
4176
+ return v;
4177
+ let out = v;
4178
+ if (out.revertCommitted !== undefined && !isRevertCommitted(out.revertCommitted)) {
4179
+ const { revertCommitted: _dropped, ...rest } = out;
4180
+ out = rest;
4181
+ }
4182
+ const revert = out.revert;
4183
+ if (isObj(revert) && revert.stagedAt !== undefined && !num(revert.stagedAt)) {
4184
+ const { stagedAt: _dropped, ...rest } = revert;
4185
+ out = { ...out, revert: rest };
4186
+ }
4187
+ return out;
4188
+ }
4118
4189
  function parseServerMessage(raw) {
4119
4190
  const m = parseFrame(raw);
4120
4191
  if (!m)
@@ -4125,7 +4196,7 @@ function parseServerMessage(raw) {
4125
4196
  case "req":
4126
4197
  return nonEmpty(m.id) && isServerRequest(m.req) ? m : undefined;
4127
4198
  case "res":
4128
- return isResFrame(m) ? m : undefined;
4199
+ return isResFrame(m) ? lenientRes(m) : undefined;
4129
4200
  case "bye":
4130
4201
  return isByeFrame(m) ? m : undefined;
4131
4202
  default:
@@ -4536,8 +4607,8 @@ async function machineEnroll(options) {
4536
4607
  if (!server) {
4537
4608
  const why = insecureHTTPRefusal(options.server, allow);
4538
4609
  if (why)
4539
- return failure(COMMAND2, "usage", `${why[0].toUpperCase()}${why.slice(1)}: ${line(String(options.server), 200)}`);
4540
- return failure(COMMAND2, "usage", `The hub URL must be https (plain http only for a loopback hub), without user info, query or fragment: ${line(String(options.server), 200)}`);
4610
+ return failure(COMMAND2, "usage", `${why[0].toUpperCase()}${why.slice(1)}: ${line(shownURL(String(options.server)), 200)}`);
4611
+ return failure(COMMAND2, "usage", `The hub URL must be https (plain http only for a loopback hub), without user info, query or fragment: ${line(shownURL(String(options.server)), 200)}`);
4541
4612
  }
4542
4613
  const insecure = isPlainHTTP(server) && !isLoopbackServer(server) ? insecureHTTPWarning(server) : undefined;
4543
4614
  const machineName = options.machineName ?? defaultMachineName(hostname2());
@@ -4799,7 +4870,6 @@ function profileNames(set) {
4799
4870
  // packages/node/src/opencode-warm.ts
4800
4871
  import { closeSync as closeSync3, constants, fstatSync, lstatSync as lstatSync3, openSync as openSync3, readSync, realpathSync, statSync } from "node:fs";
4801
4872
  import path7 from "node:path";
4802
- import { readFile } from "node:fs/promises";
4803
4873
 
4804
4874
  // src/spoke/listing.ts
4805
4875
  import path6 from "node:path";
@@ -4842,9 +4912,12 @@ async function listServiceSessions(client, options = {}) {
4842
4912
  return [];
4843
4913
  const pageSize = options.pageSize ?? 100;
4844
4914
  const maxPages = options.maxPages ?? 20;
4915
+ const { signal } = options;
4916
+ const request = signal ? { signal } : {};
4917
+ signal?.throwIfAborted();
4845
4918
  const projects = new Map;
4846
4919
  try {
4847
- for (const project of await client.project.list())
4920
+ for (const project of await client.project.list(request))
4848
4921
  projects.set(project.id, project);
4849
4922
  } catch {}
4850
4923
  const scope = options.scope;
@@ -4855,7 +4928,8 @@ async function listServiceSessions(client, options = {}) {
4855
4928
  let cursor;
4856
4929
  let ended = false;
4857
4930
  for (let page = 0;page < maxPages && out.length < max && !ended; page++) {
4858
- const result = await client.session.list({ limit: pageSize, parentID: null, ...cursor ? { cursor } : {} });
4931
+ signal?.throwIfAborted();
4932
+ const result = await client.session.list({ limit: pageSize, parentID: null, ...cursor ? { cursor } : {} }, request);
4859
4933
  for (const s of result.data) {
4860
4934
  if (s.parentID || s.time.archived || out.length >= max)
4861
4935
  continue;
@@ -4873,7 +4947,8 @@ async function listServiceSessions(client, options = {}) {
4873
4947
  const have = new Set(out.map((s) => s.id));
4874
4948
  const missing = [...new Set(options.include ?? [])].filter((id) => !have.has(id)).slice(0, INCLUDE_FETCH_MAX);
4875
4949
  if (missing.length && client.session.get) {
4876
- const fetched = await Promise.all(missing.map((sessionID) => client.session.get({ sessionID }).catch(() => {
4950
+ signal?.throwIfAborted();
4951
+ const fetched = await Promise.all(missing.map((sessionID) => client.session.get({ sessionID }, request).catch(() => {
4877
4952
  return;
4878
4953
  })));
4879
4954
  for (const s of fetched)
@@ -4885,12 +4960,13 @@ async function listServiceSessions(client, options = {}) {
4885
4960
 
4886
4961
  // packages/node/src/opencode-warm.ts
4887
4962
  var WARM_INTERVAL_MS = 30000;
4888
- var WARM_TIMEOUT_MS = 1e4;
4963
+ var WARM_TIMEOUT_MS = 30000;
4889
4964
  var WARM_DIRECTORIES_MAX = 8;
4890
4965
  var SESSION_DAYS = 7;
4891
4966
  var SESSION_PAGE = 100;
4892
4967
  var LOOPBACK2 = new Set(["127.0.0.1", "[::1]"]);
4893
4968
  var PASSWORD_FILE_MAX = 4096;
4969
+ var REGISTRATION_MAX = 64 * 1024;
4894
4970
  var isDirectory = (directory) => {
4895
4971
  try {
4896
4972
  return statSync(directory).isDirectory();
@@ -5071,12 +5147,34 @@ class OpenCodeWarmer {
5071
5147
  return problem ? "failed" : "warmed";
5072
5148
  }
5073
5149
  }
5074
- async function readServiceRegistration(file) {
5150
+ async function readServiceRegistration(file, options = {}) {
5151
+ const uid = options.uid ?? (typeof process.getuid === "function" ? process.getuid() : undefined);
5075
5152
  let info;
5153
+ let fd;
5076
5154
  try {
5077
- info = JSON.parse(await readFile(file, "utf8"));
5155
+ const before = lstatSync3(file);
5156
+ if (!before.isFile())
5157
+ return;
5158
+ fd = openSync3(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
5159
+ const stat = fstatSync(fd);
5160
+ if (!stat.isFile() || stat.dev !== before.dev || stat.ino !== before.ino)
5161
+ return;
5162
+ if ((stat.mode & 18) !== 0)
5163
+ return;
5164
+ if (uid !== undefined && stat.uid !== uid)
5165
+ return;
5166
+ if (stat.size > REGISTRATION_MAX)
5167
+ return;
5168
+ const buffer = Buffer.alloc(REGISTRATION_MAX + 1);
5169
+ const length = readSync(fd, buffer, 0, buffer.length, 0);
5170
+ if (length > REGISTRATION_MAX)
5171
+ return;
5172
+ info = JSON.parse(buffer.subarray(0, length).toString("utf8"));
5078
5173
  } catch {
5079
5174
  return;
5175
+ } finally {
5176
+ if (fd !== undefined)
5177
+ closeSync3(fd);
5080
5178
  }
5081
5179
  if (!info || typeof info !== "object")
5082
5180
  return;
@@ -5085,9 +5183,8 @@ async function readServiceRegistration(file) {
5085
5183
  return;
5086
5184
  try {
5087
5185
  process.kill(pid, 0);
5088
- } catch (cause) {
5089
- if (cause.code !== "EPERM")
5090
- return;
5186
+ } catch {
5187
+ return;
5091
5188
  }
5092
5189
  return typeof password === "string" ? { url, auth: { type: "basic", username: "opencode", password } } : { url };
5093
5190
  }
@@ -5287,7 +5384,15 @@ function normalizeOpenCode(raw, problems) {
5287
5384
  problems.push(`opencode.servers: at most ${OPENCODE_SERVERS_MAX} servers; the rest are ignored`);
5288
5385
  servers.length = OPENCODE_SERVERS_MAX;
5289
5386
  }
5290
- return { ...directories ? { directories } : {}, servers };
5387
+ let timeoutSeconds;
5388
+ if (raw.timeoutSeconds !== undefined) {
5389
+ const t = raw.timeoutSeconds;
5390
+ if (typeof t === "number" && Number.isInteger(t) && t >= 1 && t <= 300)
5391
+ timeoutSeconds = t;
5392
+ else
5393
+ problems.push("opencode.timeoutSeconds must be a whole number of seconds from 1 to 300");
5394
+ }
5395
+ return { ...directories ? { directories } : {}, servers, ...timeoutSeconds !== undefined ? { timeoutSeconds } : {} };
5291
5396
  }
5292
5397
  function normalizeHarnesses(raw, problems) {
5293
5398
  if (!isObject3(raw)) {
@@ -5459,14 +5564,15 @@ var DELIVERY = {
5459
5564
  queued: "queued (read at its next turn)",
5460
5565
  retained: "retained (held until its open question is answered)",
5461
5566
  pending: "pending (offline or busy; delivered when it reconnects or its turn ends, for 24 h)",
5462
- dropped: "not delivered (its recipient session ended)"
5567
+ dropped: "not delivered (its recipient session ended)",
5568
+ expired: "not delivered (its recipient stayed offline or busy for 24 h)"
5463
5569
  };
5464
5570
  var deliveryText = (delivery) => delivery ? DELIVERY[delivery] ?? delivery : "stored (in its inbox)";
5465
5571
  function toText(message) {
5466
5572
  const recipient = partyText(message.recipient);
5467
5573
  return message.to === recipient || message.to === message.recipient.sessionID ? recipient : `${message.to} → ${recipient}`;
5468
5574
  }
5469
- var sentText = ({ message, delivery }) => `${message.kind} ${message.id} to ${toText(message)}: ${deliveryText(delivery)}`;
5575
+ var sentText = (sent) => `${sent.message.kind} ${sent.message.id} to ${toText(sent.message)}: ${deliveryText(sent.delivery)}${wakeSkipNote(sent)}`;
5470
5576
  var ASK_EXPIRES_DEFAULT_S = 24 * 60 * 60;
5471
5577
  var UNIT_S = { "": 1, s: 1, m: 60, h: 3600, d: 86400 };
5472
5578
  function durationArg(value, flag) {
@@ -5723,9 +5829,12 @@ function launchedText(launch, created, key = launch.spec.key) {
5723
5829
  return `${launch.id} was already launched with key ${key}: ${summary(launch).slice(launch.id.length + 2)}`;
5724
5830
  }
5725
5831
  var stoppedLaunchText = (launch) => `stopped ${summary(launch)}`;
5832
+ var launcherText = (l) => l.byLabel ? `app ${JSON.stringify(l.byLabel)}` : `app ${l.by?.replace(/^app:/, "") ?? "?"}`;
5726
5833
  function launchLine(l) {
5727
5834
  const where = l.directory ?? `${l.spec.workspace.repo}${l.spec.workspace.branch ? ` (${l.spec.workspace.branch})` : ""}`;
5728
- return [l.id, stateText2(l), sessionText(l) || undefined, where, labelsText(l.spec.labels) || undefined].filter(Boolean).join(" ");
5835
+ const waits = l.state === "queued" && l.waiting ? `waits: ${l.waiting.reason}` : undefined;
5836
+ const revoked = l.launcherRevoked && l.state !== "ended" ? `launcher revoked (${launcherText(l)}): stop it with hub launches stop ${l.id}` : undefined;
5837
+ return [l.id, stateText2(l), sessionText(l) || undefined, where, labelsText(l.spec.labels) || undefined, waits, revoked].filter(Boolean).join(" ");
5729
5838
  }
5730
5839
  var launchesText = (launches) => launches.length ? launches.map(launchLine).join(`
5731
5840
  `) : "no launches";
@@ -5753,8 +5862,10 @@ function launchText(l) {
5753
5862
  lines.push(`runs: ${run.join(" ")}`);
5754
5863
  if (l.spec.end)
5755
5864
  lines.push(`ends: ${[l.spec.end.when, l.spec.end.timeout ? `timeout ${l.spec.end.timeout}` : undefined, l.spec.end.cleanup ? `cleanup ${l.spec.end.cleanup}` : undefined].filter(Boolean).join(", ")}`);
5865
+ if (l.state === "queued" && l.waiting)
5866
+ lines.push(`waits (${l.waiting.wait}) since ${new Date(l.waiting.since).toISOString()}: ${l.waiting.reason}`);
5756
5867
  if (l.by)
5757
- lines.push(`by: ${l.by}`);
5868
+ lines.push(`by: ${l.by}${l.byLabel ? ` (${l.byLabel})` : ""}${l.launcherRevoked ? ", launcher revoked" : ""}`);
5758
5869
  if (labelsText(l.spec.labels))
5759
5870
  lines.push(`labels: ${labelsText(l.spec.labels)}`);
5760
5871
  if (l.artifacts && artifactsText(l.artifacts))
@@ -5765,6 +5876,10 @@ function launchText(l) {
5765
5876
  `);
5766
5877
  }
5767
5878
 
5879
+ // packages/cli/src/tool-args.ts
5880
+ class ToolArgError extends Error {
5881
+ }
5882
+
5768
5883
  // packages/cli/src/artifacts-mcp.ts
5769
5884
  var MCP_INLINE_TEXT_BYTES = 256 * 1024;
5770
5885
  var KINDS = ["markdown", "code", "image", "table", "chart", "dashboard", "html", "app", "file"];
@@ -5788,7 +5903,7 @@ var FILE_ARGS = {
5788
5903
  var need = (args, key) => {
5789
5904
  const value = args[key];
5790
5905
  if (typeof value !== "string" || !value.trim())
5791
- throw new Error(`"${key}" is required (a non-empty string)`);
5906
+ throw new ToolArgError(`"${key}" is required (a non-empty string)`);
5792
5907
  return value;
5793
5908
  };
5794
5909
  var optional2 = (args, key) => {
@@ -5796,20 +5911,20 @@ var optional2 = (args, key) => {
5796
5911
  if (value === undefined || value === null)
5797
5912
  return;
5798
5913
  if (typeof value !== "string")
5799
- throw new Error(`"${key}" must be a string`);
5914
+ throw new ToolArgError(`"${key}" must be a string`);
5800
5915
  return value.trim() ? value : undefined;
5801
5916
  };
5802
5917
  function files(args, kind) {
5803
5918
  const text = args.text;
5804
5919
  if (text !== undefined && typeof text !== "string")
5805
- throw new Error('"text" must be a string');
5920
+ throw new ToolArgError('"text" must be a string');
5806
5921
  const blobs = args.blobs;
5807
5922
  if (blobs !== undefined && (!Array.isArray(blobs) || !blobs.every((b) => typeof b === "object" && b !== null)))
5808
- throw new Error('"blobs" must be a list of blob references');
5923
+ throw new ToolArgError('"blobs" must be a list of blob references');
5809
5924
  if (text === undefined && (blobs === undefined || blobs.length === 0))
5810
- throw new Error("give text or blobs (stage bigger content first and pass its reference)");
5925
+ throw new ToolArgError("give text or blobs (stage bigger content first and pass its reference)");
5811
5926
  if (text !== undefined && Buffer.byteLength(text, "utf8") > MCP_INLINE_TEXT_BYTES)
5812
- throw new Error(`text is larger than ${MCP_INLINE_TEXT_BYTES} bytes: stage it with uploadBlob and pass blobs`);
5927
+ throw new ToolArgError(`text is larger than ${MCP_INLINE_TEXT_BYTES} bytes: stage it with uploadBlob and pass blobs`);
5813
5928
  const path = optional2(args, "path");
5814
5929
  const entry = optional2(args, "entry");
5815
5930
  const mediaType = optional2(args, "mediaType") ?? (kind && DEFAULT_MEDIA[kind]) ?? "text/plain";
@@ -5876,7 +5991,7 @@ var artifactTools = [
5876
5991
  const title = need(args, "title");
5877
5992
  const kind = need(args, "kind");
5878
5993
  if (!KINDS.includes(kind))
5879
- throw new Error(`"kind" must be one of ${KINDS.join(", ")}`);
5994
+ throw new ToolArgError(`"kind" must be one of ${KINDS.join(", ")}`);
5880
5995
  const body = files(args, kind);
5881
5996
  const description = optional2(args, "description");
5882
5997
  const visibility = optional2(args, "visibility");
@@ -5915,7 +6030,7 @@ var KINDS2 = ["ask", "propose-edit", "run-action"];
5915
6030
  var need2 = (args, key) => {
5916
6031
  const value = args[key];
5917
6032
  if (typeof value !== "string" || !value.trim())
5918
- throw new Error(`"${key}" is required (a non-empty string)`);
6033
+ throw new ToolArgError(`"${key}" is required (a non-empty string)`);
5919
6034
  return value;
5920
6035
  };
5921
6036
  var optional3 = (args, key) => {
@@ -5923,24 +6038,24 @@ var optional3 = (args, key) => {
5923
6038
  if (value === undefined || value === null)
5924
6039
  return;
5925
6040
  if (typeof value !== "string")
5926
- throw new Error(`"${key}" must be a string`);
6041
+ throw new ToolArgError(`"${key}" must be a string`);
5927
6042
  return value.trim() ? value : undefined;
5928
6043
  };
5929
6044
  var integer = (args, key) => {
5930
6045
  const value = args[key];
5931
6046
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
5932
- throw new Error(`"${key}" is required (a whole number)`);
6047
+ throw new ToolArgError(`"${key}" is required (a whole number)`);
5933
6048
  return value;
5934
6049
  };
5935
6050
  function handlerOf(args) {
5936
6051
  const session = optional3(args, "session");
5937
6052
  const machine = optional3(args, "machine");
5938
6053
  if (session && machine)
5939
- throw new Error('give either "session" (an existing session) or "machine" + "repo" (a new handler session), not both');
6054
+ throw new ToolArgError('give either "session" (an existing session) or "machine" + "repo" (a new handler session), not both');
5940
6055
  if (session)
5941
6056
  return { session };
5942
6057
  if (!machine)
5943
- throw new Error('"machine" and "repo" are required to launch a handler session (or name an existing one with "session")');
6058
+ throw new ToolArgError('"machine" and "repo" are required to launch a handler session (or name an existing one with "session")');
5944
6059
  const repo = need2(args, "repo");
5945
6060
  const harness = optional3(args, "harness");
5946
6061
  const model = optional3(args, "model");
@@ -6004,7 +6119,7 @@ var artifactJobTools = [
6004
6119
  run: async (hub, args, signal) => {
6005
6120
  const kind = optional3(args, "kind");
6006
6121
  if (kind && !KINDS2.includes(kind))
6007
- throw new Error(`"kind" must be one of ${KINDS2.join(", ")}`);
6122
+ throw new ToolArgError(`"kind" must be one of ${KINDS2.join(", ")}`);
6008
6123
  const action = optional3(args, "action");
6009
6124
  const apply = optional3(args, "apply");
6010
6125
  const key = optional3(args, "key");
@@ -6073,12 +6188,12 @@ var artifactJobTools = [
6073
6188
  const answer = optional3(args, "answer");
6074
6189
  const text = args.text;
6075
6190
  if (text !== undefined && typeof text !== "string")
6076
- throw new Error('"text" must be a string');
6191
+ throw new ToolArgError('"text" must be a string');
6077
6192
  if (typeof text === "string" && Buffer.byteLength(text, "utf8") > MCP_JOB_TEXT_BYTES)
6078
- throw new Error(`"text" is larger than ${MCP_JOB_TEXT_BYTES} bytes`);
6193
+ throw new ToolArgError(`"text" is larger than ${MCP_JOB_TEXT_BYTES} bytes`);
6079
6194
  const keep = args.keep;
6080
6195
  if (keep !== undefined && (!Array.isArray(keep) || !keep.every((k) => typeof k === "string")))
6081
- throw new Error('"keep" must be a list of paths');
6196
+ throw new ToolArgError('"keep" must be a list of paths');
6082
6197
  const path = optional3(args, "path");
6083
6198
  const entry = optional3(args, "entry");
6084
6199
  const result = error ? { error } : text !== undefined || keep !== undefined ? {
@@ -6125,7 +6240,7 @@ var UPDATES = TEAM_INTAKE_STATUS_INPUTS;
6125
6240
  var need3 = (args, key) => {
6126
6241
  const value = args[key];
6127
6242
  if (typeof value !== "string" || !value.trim())
6128
- throw new Error(`"${key}" is required (a non-empty string)`);
6243
+ throw new ToolArgError(`"${key}" is required (a non-empty string)`);
6129
6244
  return value;
6130
6245
  };
6131
6246
  var optional4 = (args, key) => {
@@ -6133,7 +6248,7 @@ var optional4 = (args, key) => {
6133
6248
  if (value === undefined || value === null)
6134
6249
  return;
6135
6250
  if (typeof value !== "string")
6136
- throw new Error(`"${key}" must be a string`);
6251
+ throw new ToolArgError(`"${key}" must be a string`);
6137
6252
  return value.trim() ? value : undefined;
6138
6253
  };
6139
6254
  var strings2 = (args, key) => {
@@ -6141,13 +6256,13 @@ var strings2 = (args, key) => {
6141
6256
  if (value === undefined || value === null)
6142
6257
  return;
6143
6258
  if (!Array.isArray(value) || !value.every((v) => typeof v === "string"))
6144
- throw new Error(`"${key}" must be a list of strings`);
6259
+ throw new ToolArgError(`"${key}" must be a list of strings`);
6145
6260
  return value;
6146
6261
  };
6147
6262
  var oneOf4 = (args, key, allowed) => {
6148
6263
  const value = optional4(args, key);
6149
6264
  if (value !== undefined && !allowed.includes(value))
6150
- throw new Error(`"${key}" must be one of ${allowed.join(", ")}`);
6265
+ throw new ToolArgError(`"${key}" must be one of ${allowed.join(", ")}`);
6151
6266
  return value;
6152
6267
  };
6153
6268
  function dueOf(args) {
@@ -6156,7 +6271,7 @@ function dueOf(args) {
6156
6271
  return;
6157
6272
  const ms = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : Number.NaN;
6158
6273
  if (!Number.isSafeInteger(ms) || ms <= 0)
6159
- throw new Error(`"due" must be an ISO date/time or epoch milliseconds`);
6274
+ throw new ToolArgError(`"due" must be an ISO date/time or epoch milliseconds`);
6160
6275
  return ms;
6161
6276
  }
6162
6277
  var line4 = (i) => `${i.id} ${i.status}${i.progress ? `/${i.progress}` : ""} ${i.priority} r${i.revision} ${sanitizeText(i.title).replace(/\s+/g, " ")}`;
@@ -6243,10 +6358,10 @@ ${intakeText(intake)}`;
6243
6358
  run: async (hub, args, signal) => {
6244
6359
  const status = oneOf4(args, "status", UPDATES);
6245
6360
  if (!status)
6246
- throw new Error(`"status" is required: one of ${UPDATES.join(", ")}`);
6361
+ throw new ToolArgError(`"status" is required: one of ${UPDATES.join(", ")}`);
6247
6362
  const expectedRevision = args.expectedRevision;
6248
6363
  if (expectedRevision !== undefined && expectedRevision !== null && (typeof expectedRevision !== "number" || !Number.isSafeInteger(expectedRevision) || expectedRevision < 0))
6249
- throw new Error(`"expectedRevision" must be the revision you read (or left out)`);
6364
+ throw new ToolArgError(`"expectedRevision" must be the revision you read (or left out)`);
6250
6365
  const reason = optional4(args, "reason");
6251
6366
  const { intake, assignment } = await reportIntakeStatus(hub.teams, need3(args, "team"), need3(args, "intake"), { status, ...typeof expectedRevision === "number" ? { expectedRevision } : {}, ...reason ? { reason } : {} }, { signal });
6252
6367
  return `${intake.id} is ${intake.status}${intake.progress ? ` (${intake.progress})` : ""}, revision ${intake.revision}${assignment ? `; assignment ${assignment.id} ${assignment.status}` : ""}`;
@@ -6285,10 +6400,61 @@ var SPEC_FIELDS = {
6285
6400
  metadata: { type: "object", additionalProperties: STRING4, description: "String key/value pairs (keys are slugs)." },
6286
6401
  projects: { ...STRINGS, description: "Project names the team works on." }
6287
6402
  };
6403
+ var LAUNCH = {
6404
+ type: "object",
6405
+ description: "A launch spec (nested, as POST /api/agent/launches takes; unlike the launch tool's flat fields): workspace{repo, mode, branch, base, remote} is required; placement{machine, labels, mode}, runtime{kind, profile}, end{when, timeout, cleanup} and the top-level fields are optional; key is set by staffing (send idempotencyKey instead). The role's prefs fill harness, model and placement.machine, and its profile fills agentProfile, when not given. A flat field (repo, machine, cleanup at the top) is refused with the nested place it belongs.",
6406
+ properties: {
6407
+ workspace: {
6408
+ type: "object",
6409
+ description: "Where the member works on its machine.",
6410
+ properties: {
6411
+ repo: { ...STRING4, description: "The repository's absolute path on the machine." },
6412
+ mode: { type: "string", enum: ["worktree", "checkout", "clone"], description: "worktree (default; needs branch), checkout, or clone (needs remote)." },
6413
+ branch: { ...STRING4, description: "The branch to create or check out (a safe git branch name; required for worktree)." },
6414
+ base: { ...STRING4, description: "What a new branch starts from." },
6415
+ remote: { ...STRING4, description: "An https or ssh git remote, without credentials (required for clone)." }
6416
+ },
6417
+ required: ["repo"]
6418
+ },
6419
+ placement: {
6420
+ type: "object",
6421
+ description: "Which machine runs it.",
6422
+ properties: {
6423
+ machine: { ...STRING4, description: "A machine ID or name." },
6424
+ labels: { type: "object", additionalProperties: STRING4, description: "Node labels to match." },
6425
+ mode: { type: "string", enum: ["require", "prefer"], description: "require (default) or prefer the machine/labels." }
6426
+ }
6427
+ },
6428
+ runtime: {
6429
+ type: "object",
6430
+ properties: {
6431
+ kind: { type: "string", enum: ["host", "openshell"], description: "host (default) or openshell (clone workspaces only)." },
6432
+ profile: { ...STRING4, description: "An openshell sandbox profile (openshell only)." }
6433
+ }
6434
+ },
6435
+ end: {
6436
+ type: "object",
6437
+ description: "When the launch ends.",
6438
+ properties: {
6439
+ when: { type: "string", enum: ["idle", "asked", "never"], description: "idle (default), asked or never." },
6440
+ timeout: { type: ["string", "number"], description: "1 minute to 30 days: seconds, or 30m, 4h, 2d." },
6441
+ cleanup: { type: "string", enum: ["keep", "remove"], description: "keep (default) or remove the workspace at the end." }
6442
+ }
6443
+ },
6444
+ name: { ...STRING4, description: "The session's @name, without the @." },
6445
+ roles: { ...STRINGS, description: "Roles the session claims (up to 10)." },
6446
+ harness: { ...STRING4, description: "The harness (default opencode)." },
6447
+ agent: { ...STRING4, description: "The harness agent." },
6448
+ model: { ...STRING4, description: "provider/model." },
6449
+ prompt: { ...STRING4, description: "The first prompt (up to 10000 characters)." },
6450
+ intent: { ...STRING4, description: "What the session is for (one line)." },
6451
+ labels: { type: "object", additionalProperties: STRING4, description: "Free-form app labels." },
6452
+ agentProfile: { type: "object", properties: { profileID: STRING4, version: { type: "integer", minimum: 1 } }, required: ["profileID", "version"], description: "The profile version to pin (needs profiles:assign for an app)." }
6453
+ },
6454
+ required: ["workspace"]
6455
+ };
6288
6456
  var SCOPES = "App tokens need scope teams:read (reads) or teams:manage (changes); seats, status and staffing also need sessions:read. Session tokens read only teams they are seated in and never change definitions. Team roles confer no authority.";
6289
-
6290
- class ArgError extends Error {
6291
- }
6457
+ var ArgError = ToolArgError;
6292
6458
  var need4 = (args, key) => {
6293
6459
  const value = args[key];
6294
6460
  if (typeof value !== "string" || !value.trim())
@@ -6581,7 +6747,7 @@ var teamTools = [
6581
6747
  {
6582
6748
  name: "team_staff",
6583
6749
  description: `Staff a role: attach an existing session (sessionID, any machine) or launch a new member (launch: a launch spec as the launch tool takes). An agent needs the user's grants (roles for the session's project; launch for a repo) or the hub answers needs_user; staffing never issues grants. ${SCOPES}`,
6584
- properties: { team: TEAM2, role: STRING4, sessionID: { ...STRING4, description: "An existing session (ses_…)." }, launch: { type: "object", description: "A launch spec (repo, harness, model, prompt, …)." }, idempotencyKey: IDEMPOTENCY },
6750
+ properties: { team: TEAM2, role: STRING4, sessionID: { ...STRING4, description: "An existing session (ses_…)." }, launch: LAUNCH, idempotencyKey: IDEMPOTENCY },
6585
6751
  required: ["team", "role"],
6586
6752
  readOnly: false,
6587
6753
  run: async (hub, args, signal) => {
@@ -6616,7 +6782,7 @@ var teamTools = [
6616
6782
  {
6617
6783
  name: "team_replace",
6618
6784
  description: `Replace a seat by an existing session (sessionID) or a new launch (launch): the old seat ends at once and the new member gets the slot's next generation. Send the seat's revision (team_status). ${SCOPES}`,
6619
- properties: { team: TEAM2, staffingID: STRING4, sessionID: STRING4, launch: { type: "object" }, expectedRevision: { ...REVISION2, description: "The seat's revision you read (team_status)." } },
6785
+ properties: { team: TEAM2, staffingID: STRING4, sessionID: STRING4, launch: LAUNCH, expectedRevision: { ...REVISION2, description: "The seat's revision you read (team_status)." } },
6620
6786
  required: ["team", "staffingID", "expectedRevision"],
6621
6787
  readOnly: false,
6622
6788
  run: async (hub, args, signal) => {
@@ -6633,6 +6799,253 @@ var teamTools = [
6633
6799
  }
6634
6800
  ];
6635
6801
 
6802
+ // packages/cli/src/profiles-mcp.ts
6803
+ var STRING5 = { type: "string" };
6804
+ var STRINGS2 = { type: "array", items: STRING5 };
6805
+ var ID4 = { type: "string", description: "The profile's ID (prof_…; see profiles)." };
6806
+ var REVISION3 = { type: "integer", minimum: 0, description: "The profile's revision you read (profile_show); a stale one answers conflict with the current revision." };
6807
+ var IDEMPOTENCY2 = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
6808
+ var SKILL = { type: "object", properties: { name: { ...STRING5, description: "A slug." }, description: STRING5, body: { ...STRING5, description: "The skill's full text (loaded on demand)." } }, required: ["name", "description", "body"] };
6809
+ var COMMAND3 = { type: "object", properties: { name: { ...STRING5, description: "A slug." }, description: STRING5, template: { ...STRING5, description: "Text with {{name}} parameters and {{secret:NAME}} references (never resolved by the hub)." } }, required: ["name", "description", "template"] };
6810
+ var CONTENT_FIELDS = {
6811
+ instructions: { type: ["string", "null"], description: "The profile's instructions (AGENTS.md-like text)." },
6812
+ model: { type: ["string", "null"], description: "provider/model the profile suggests." },
6813
+ skills: { type: "array", items: SKILL, description: "Skills, wholesale." },
6814
+ commands: { type: "array", items: COMMAND3, description: "Commands, wholesale." }
6815
+ };
6816
+ var META_FIELDS = {
6817
+ title: { ...STRING5, description: "1–120 characters." },
6818
+ description: STRING5,
6819
+ tags: { ...STRINGS2, description: "Slugs." },
6820
+ metadata: { type: "object", additionalProperties: STRING5, description: "String key/value pairs (≤ 32 slug keys)." },
6821
+ projects: { ...STRINGS2, description: "Project names the profile is meant for (≤ 16)." }
6822
+ };
6823
+ var SCOPES2 = "App tokens need scope profiles:read (reads) or profiles:manage (changes; profile_version_create with content fields and the skill/command set/remove tools read the latest version first, so they also need profiles:read); pins need profiles:assign and sessions:read (an app also needs a prompt grant on the session). Session tokens never change profiles. A profile confers no authority.";
6824
+ var ArgError2 = ToolArgError;
6825
+ var need5 = (args, key) => {
6826
+ const value = args[key];
6827
+ if (typeof value !== "string" || !value.trim())
6828
+ throw new ArgError2(`"${key}" is required (a non-empty string)`);
6829
+ return value;
6830
+ };
6831
+ var optional6 = (args, key) => {
6832
+ const value = args[key];
6833
+ if (value === undefined || value === null)
6834
+ return;
6835
+ if (typeof value !== "string")
6836
+ throw new ArgError2(`"${key}" must be a string`);
6837
+ return value.trim() ? value : undefined;
6838
+ };
6839
+ var whole2 = (args, key, min, what) => {
6840
+ const value = args[key];
6841
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min)
6842
+ throw new ArgError2(`"${key}" is required: ${what}`);
6843
+ return value;
6844
+ };
6845
+ var revision2 = (args) => whole2(args, "expectedRevision", 0, "the revision you read (profile_show; 0 for a session with no pin)");
6846
+ var idempotency = (args) => {
6847
+ const key = optional6(args, "idempotencyKey");
6848
+ return key ? { idempotencyKey: key } : {};
6849
+ };
6850
+ var picked2 = (args, keys) => Object.fromEntries(keys.filter((k) => args[k] !== undefined).map((k) => [k, args[k]]));
6851
+ var one2 = (text) => sanitizeText(text).replace(/\s+/g, " ");
6852
+ var profileLine = (p) => `${p.id} ${p.name} v${p.latestVersion} r${p.revision} ${p.state} ${one2(p.title)}`;
6853
+ function summaryLines(v) {
6854
+ if (!v)
6855
+ return [];
6856
+ return [
6857
+ `version ${v.version} (${v.digest.slice(0, 12)}, ${v.bytes} bytes): instructions ${v.instructions ? "yes" : "no"}${v.model ? `, model ${one2(v.model)}` : ""}`,
6858
+ `skills: ${v.skills.length ? v.skills.map((s) => s.name).join(", ") : "none"}`,
6859
+ `commands: ${v.commands.length ? v.commands.map((c) => `${c.name}(${c.params.join(", ")})`).join(", ") : "none"}`,
6860
+ ...v.secretRefs.length ? [`secret references: ${v.secretRefs.join(", ")}`] : []
6861
+ ];
6862
+ }
6863
+ var versionText = (r) => [`new version ${r.version.version}: ${profileLine(r.profile)}`, ...summaryLines(r.version)].join(`
6864
+ `);
6865
+ async function latestContent(profiles, id, signal) {
6866
+ const { profile } = await profiles.get(id, { signal });
6867
+ const { version } = await profiles.version(profile.id, profile.latestVersion, { signal });
6868
+ return { latest: version.version, content: version.content };
6869
+ }
6870
+ var contentTool = (name, description, properties, required, change) => ({
6871
+ name,
6872
+ description: `${description} Writes the latest version's content with this change as the next version (versions are immutable; pinned sessions keep theirs). ${SCOPES2}`,
6873
+ properties: { id: ID4, ...properties, expectedRevision: REVISION3 },
6874
+ required: ["id", ...required, "expectedRevision"],
6875
+ readOnly: false,
6876
+ organizes: true,
6877
+ idempotent: true,
6878
+ run: async (hub, args, signal) => {
6879
+ const expectedRevision = revision2(args);
6880
+ const id = need5(args, "id");
6881
+ const { latest, content } = await latestContent(hub.profiles, id, signal);
6882
+ return versionText(await hub.profiles.createVersion(id, { expectedRevision, content: change(content, args, latest) }, { signal }));
6883
+ }
6884
+ });
6885
+ var setNamed = (items, item) => items.some((i) => i.name === item.name) ? items.map((i) => i.name === item.name ? item : i) : [...items, item];
6886
+ var removeNamed = (items, name, what, latest) => {
6887
+ if (!items.some((i) => i.name === name))
6888
+ throw new ArgError2(`version ${latest} has no ${what} ${name}`);
6889
+ return items.filter((i) => i.name !== name);
6890
+ };
6891
+ var stateTool = (verb, description) => ({
6892
+ name: `profile_${verb}`,
6893
+ description: `${description} ${SCOPES2}`,
6894
+ properties: { id: ID4, expectedRevision: REVISION3 },
6895
+ required: ["id", "expectedRevision"],
6896
+ readOnly: false,
6897
+ idempotent: true,
6898
+ run: async (hub, args, signal) => {
6899
+ const expectedRevision = revision2(args);
6900
+ const { profile } = await hub.profiles.update(need5(args, "id"), { expectedRevision, state: verb === "archive" ? "archived" : "active" }, { signal });
6901
+ return `${verb === "archive" ? "archived" : "restored"}: ${profileLine(profile)}`;
6902
+ }
6903
+ });
6904
+ var profileLifecycleTools = [
6905
+ {
6906
+ name: "profile_show",
6907
+ description: `One profile: name, title, state, revision (send it as expectedRevision), tags, projects, metadata, and its latest version's instructions, model, skill and command names (never bodies). ${SCOPES2}`,
6908
+ properties: { id: ID4 },
6909
+ required: ["id"],
6910
+ readOnly: true,
6911
+ run: async (hub, args, signal) => {
6912
+ const { profile, latest } = await hub.profiles.get(need5(args, "id"), { signal });
6913
+ return [
6914
+ profileLine(profile),
6915
+ ...profile.description ? [`description: ${one2(profile.description)}`] : [],
6916
+ ...profile.tags?.length ? [`tags: ${profile.tags.join(", ")}`] : [],
6917
+ ...profile.projects?.length ? [`projects: ${profile.projects.map(one2).join(", ")}`] : [],
6918
+ ...profile.metadata && Object.keys(profile.metadata).length ? [`metadata: ${Object.entries(profile.metadata).map(([k, v]) => `${k}=${one2(v)}`).join(", ")}`] : [],
6919
+ ...summaryLines(latest)
6920
+ ].join(`
6921
+ `);
6922
+ }
6923
+ },
6924
+ {
6925
+ name: "profile_versions",
6926
+ description: `A profile's retained versions, newest first (version, digest, bytes, when, skill and command counts). ${SCOPES2}`,
6927
+ properties: { id: ID4, limit: { type: "integer", minimum: 1, maximum: 200 }, cursor: STRING5 },
6928
+ required: ["id"],
6929
+ readOnly: true,
6930
+ run: async (hub, args, signal) => {
6931
+ const limit = args.limit === undefined ? undefined : whole2(args, "limit", 1, "a whole number from 1 to 200");
6932
+ const cursor = optional6(args, "cursor");
6933
+ const page = await hub.profiles.versions(need5(args, "id"), { signal, ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {} });
6934
+ const counts = (v) => v.skills !== undefined ? ` ${v.skills} skills, ${v.commands ?? 0} commands${v.instructions ? ", instructions" : ""}` : "";
6935
+ return [...page.items.map((v) => `v${v.version} ${v.digest.slice(0, 12)} ${v.bytes} bytes ${new Date(v.createdAt).toISOString()}${counts(v)}`), ...page.cursor ? [`more: cursor ${page.cursor}`] : []].join(`
6936
+ `) || "no versions";
6937
+ }
6938
+ },
6939
+ {
6940
+ name: "profile_create",
6941
+ description: `Create a profile: a versioned bundle of instructions, skills and commands, published as version 1 at once. The name is unique per user. Attach it to a team role with team_role_set (profile), pin it with profile_assign. ${SCOPES2}`,
6942
+ properties: { name: { ...STRING5, description: "A slug, unique per user." }, ...META_FIELDS, ...CONTENT_FIELDS, idempotencyKey: IDEMPOTENCY2 },
6943
+ required: ["name", "title"],
6944
+ readOnly: false,
6945
+ organizes: true,
6946
+ idempotent: true,
6947
+ run: async (hub, args, signal) => {
6948
+ need5(args, "name");
6949
+ need5(args, "title");
6950
+ const meta = picked2(args, ["name", "title", "description", "tags", "metadata", "projects"]);
6951
+ const text = Object.entries(picked2(args, ["instructions", "model"])).filter(([, v]) => v !== null);
6952
+ const content = { ...Object.fromEntries(text), skills: args.skills ?? [], commands: args.commands ?? [] };
6953
+ const r = await hub.profiles.create({ ...meta, content }, { signal, ...idempotency(args) });
6954
+ return [`created: ${profileLine(r.profile)}`, ...summaryLines(r.version)].join(`
6955
+ `);
6956
+ }
6957
+ },
6958
+ {
6959
+ name: "profile_update",
6960
+ description: `Edit a profile's metadata: title, description, tags, metadata, projects (null clears description, metadata or projects). Content changes are new versions (profile_version_create, profile_skill_set, …). ${SCOPES2}`,
6961
+ properties: { id: ID4, ...META_FIELDS, description: { type: ["string", "null"] }, metadata: { ...META_FIELDS.metadata, type: ["object", "null"] }, projects: { ...META_FIELDS.projects, type: ["array", "null"] }, expectedRevision: REVISION3 },
6962
+ required: ["id", "expectedRevision"],
6963
+ readOnly: false,
6964
+ organizes: true,
6965
+ idempotent: true,
6966
+ run: async (hub, args, signal) => {
6967
+ const expectedRevision = revision2(args);
6968
+ const patch = picked2(args, ["title", "description", "tags", "metadata", "projects"]);
6969
+ if (Object.keys(patch).length === 0)
6970
+ throw new ArgError2("give at least one field to change");
6971
+ const { profile } = await hub.profiles.update(need5(args, "id"), { expectedRevision, ...patch }, { signal });
6972
+ return `updated: ${profileLine(profile)}`;
6973
+ }
6974
+ },
6975
+ {
6976
+ name: "profile_version_create",
6977
+ description: `Write a profile's next version: either content fields (instructions, model, skills, commands; the ones you omit are kept from the latest version, null clears instructions or model) or fromVersion (an older version's content again: an explicit rollback). Versions are immutable; pinned sessions keep theirs until re-pinned. ${SCOPES2}`,
6978
+ properties: { id: ID4, ...CONTENT_FIELDS, fromVersion: { type: "integer", minimum: 1, description: "A retained version to restore as the next one." }, expectedRevision: REVISION3, idempotencyKey: IDEMPOTENCY2 },
6979
+ required: ["id", "expectedRevision"],
6980
+ readOnly: false,
6981
+ organizes: true,
6982
+ idempotent: true,
6983
+ run: async (hub, args, signal) => {
6984
+ const expectedRevision = revision2(args);
6985
+ const id = need5(args, "id");
6986
+ const given = Object.keys(picked2(args, Object.keys(CONTENT_FIELDS)));
6987
+ const options = { signal, ...idempotency(args) };
6988
+ if (args.fromVersion !== undefined) {
6989
+ if (given.length)
6990
+ throw new ArgError2(`give either fromVersion or content fields (${given.join(", ")}), not both`);
6991
+ return versionText(await hub.profiles.createVersion(id, { expectedRevision, fromVersion: whole2(args, "fromVersion", 1, "a version number") }, options));
6992
+ }
6993
+ if (!given.length)
6994
+ throw new ArgError2("give content fields (instructions, model, skills, commands) or fromVersion");
6995
+ const { content } = await latestContent(hub.profiles, id, signal);
6996
+ const next = { ...content };
6997
+ for (const key of given) {
6998
+ if (args[key] === null)
6999
+ delete next[key];
7000
+ else
7001
+ next[key] = args[key];
7002
+ }
7003
+ return versionText(await hub.profiles.createVersion(id, { expectedRevision, content: next }, options));
7004
+ }
7005
+ },
7006
+ contentTool("profile_skill_set", "Attach a skill to a profile, or replace the skill of that name.", { name: { ...STRING5, description: "The skill's name (a slug)." }, description: STRING5, body: { ...STRING5, description: "The skill's full text." } }, ["name", "description", "body"], (content, args) => ({
7007
+ ...content,
7008
+ skills: setNamed(content.skills, { name: need5(args, "name"), description: need5(args, "description"), body: need5(args, "body") })
7009
+ })),
7010
+ contentTool("profile_skill_remove", "Detach a skill from a profile.", { name: { ...STRING5, description: "The skill's name." } }, ["name"], (content, args, latest) => ({
7011
+ ...content,
7012
+ skills: removeNamed(content.skills, need5(args, "name"), "skill", latest)
7013
+ })),
7014
+ contentTool("profile_command_set", "Attach a command to a profile, or replace the command of that name.", { name: { ...STRING5, description: "The command's name (a slug)." }, description: STRING5, template: COMMAND3.properties.template }, ["name", "description", "template"], (content, args) => ({ ...content, commands: setNamed(content.commands, { name: need5(args, "name"), description: need5(args, "description"), template: need5(args, "template") }) })),
7015
+ contentTool("profile_command_remove", "Detach a command from a profile.", { name: { ...STRING5, description: "The command's name." } }, ["name"], (content, args, latest) => ({
7016
+ ...content,
7017
+ commands: removeNamed(content.commands, need5(args, "name"), "command", latest)
7018
+ })),
7019
+ stateTool("archive", "Archive a profile: it takes no new versions or pins; pinned sessions keep theirs; reversible with profile_restore."),
7020
+ stateTool("restore", "Restore an archived profile (it takes versions and pins again)."),
7021
+ {
7022
+ name: "profile_assign",
7023
+ description: `Pin a profile version to a session (applied through its machine; the ack tells whether it took). Send the session's pin revision (0 when it has none; see profile). Pinning confers no authority. ${SCOPES2}`,
7024
+ properties: { addr: { ...STRING5, description: "The session: @name, ses_… ID or other address." }, profileID: ID4, version: { type: "integer", minimum: 1 }, expectedRevision: { ...REVISION3, description: "The session's pin revision (0 when it has none)." } },
7025
+ required: ["addr", "profileID", "version", "expectedRevision"],
7026
+ readOnly: false,
7027
+ idempotent: true,
7028
+ run: async (hub, args, signal) => {
7029
+ const expectedRevision = revision2(args);
7030
+ const { assignment: a } = await hub.profiles.assign(need5(args, "addr"), { profileID: need5(args, "profileID"), version: whole2(args, "version", 1, "a version number"), expectedRevision }, { signal });
7031
+ return `${a.sessionID} pins ${a.profileID} v${a.version} r${a.revision}${a.ack ? `, ack ${a.ack.status}${a.ack.offline ? " (offline)" : ""}` : ""}`;
7032
+ }
7033
+ },
7034
+ {
7035
+ name: "profile_unassign",
7036
+ description: `Remove a session's pin (what it already applied stays until it restarts). Send the pin's revision (profile_assign's answer). ${SCOPES2}`,
7037
+ properties: { addr: { ...STRING5, description: "The session." }, expectedRevision: { ...REVISION3, description: "The session's pin revision." } },
7038
+ required: ["addr", "expectedRevision"],
7039
+ readOnly: false,
7040
+ idempotent: true,
7041
+ run: async (hub, args, signal) => {
7042
+ const addr = need5(args, "addr");
7043
+ await hub.profiles.unassign(addr, revision2(args), { signal });
7044
+ return `unpinned ${addr}`;
7045
+ }
7046
+ }
7047
+ ];
7048
+
6636
7049
  // packages/cli/src/watchlist.ts
6637
7050
  var safe = (value) => sanitizeLabel(String(value), 1024);
6638
7051
  var list2 = (items) => items.length ? items.map(safe).join(", ") : "none";
@@ -6659,11 +7072,11 @@ function watchlistChangedText(verb, r) {
6659
7072
  var PAST = { watch: "watching", unwatch: "not watching", mute: "muted", unmute: "unmuted" };
6660
7073
  var watchlistVerbText = (verb, r) => watchlistChangedText(PAST[verb], r);
6661
7074
  var ADDR = { type: "string", description: "Session address: ses_… (or a unique prefix), @name, @@role, @name.project." };
6662
- var STRINGS2 = { type: "array", items: { type: "string" } };
6663
- var need5 = (args, key) => {
7075
+ var STRINGS3 = { type: "array", items: { type: "string" } };
7076
+ var need6 = (args, key) => {
6664
7077
  const value = args[key];
6665
7078
  if (typeof value !== "string" || !value.trim())
6666
- throw new Error(`"${key}" is required (a non-empty string)`);
7079
+ throw new ToolArgError(`"${key}" is required (a non-empty string)`);
6667
7080
  return value;
6668
7081
  };
6669
7082
  var flag = (args, key, fallback) => {
@@ -6671,7 +7084,7 @@ var flag = (args, key, fallback) => {
6671
7084
  if (value === undefined || value === null)
6672
7085
  return fallback;
6673
7086
  if (typeof value !== "boolean")
6674
- throw new Error(`"${key}" must be true or false`);
7087
+ throw new ToolArgError(`"${key}" must be true or false`);
6675
7088
  return value;
6676
7089
  };
6677
7090
  var UPDATE_FIELDS = ["autoActive", "activeWithinHours", "usePins", "sessions", "projects", "labels", "muted", "mutedProjects"];
@@ -6691,11 +7104,11 @@ var watchlistTools = [
6691
7104
  autoActive: { type: "boolean" },
6692
7105
  activeWithinHours: { type: "integer", minimum: 1, maximum: 24 },
6693
7106
  usePins: { type: "boolean" },
6694
- sessions: STRINGS2,
6695
- projects: STRINGS2,
7107
+ sessions: STRINGS3,
7108
+ projects: STRINGS3,
6696
7109
  labels: { type: "array", items: { type: "object", additionalProperties: { type: "string" } } },
6697
- muted: STRINGS2,
6698
- mutedProjects: STRINGS2
7110
+ muted: STRINGS3,
7111
+ mutedProjects: STRINGS3
6699
7112
  },
6700
7113
  required: ["expectedRevision"],
6701
7114
  readOnly: false,
@@ -6703,10 +7116,10 @@ var watchlistTools = [
6703
7116
  run: async (hub, args, signal) => {
6704
7117
  const expected = args.expectedRevision;
6705
7118
  if (typeof expected !== "number" || !Number.isSafeInteger(expected) || expected < 0)
6706
- throw new Error(`"expectedRevision" is required (the revision you read)`);
7119
+ throw new ToolArgError(`"expectedRevision" is required (the revision you read)`);
6707
7120
  const unknown = Object.keys(args).filter((k) => k !== "expectedRevision" && !UPDATE_FIELDS.includes(k));
6708
7121
  if (unknown.length)
6709
- throw new Error(`Unknown argument${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${safe(k).slice(0, 40)}"`).join(", ")}: use ${UPDATE_FIELDS.join(", ")} and expectedRevision`);
7122
+ throw new ToolArgError(`Unknown argument${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${safe(k).slice(0, 40)}"`).join(", ")}: use ${UPDATE_FIELDS.join(", ")} and expectedRevision`);
6710
7123
  const patch = {};
6711
7124
  for (const field of UPDATE_FIELDS)
6712
7125
  if (args[field] !== undefined && args[field] !== null)
@@ -6727,7 +7140,7 @@ ${watchlistConfigText(r.config)}`;
6727
7140
  idempotent: true,
6728
7141
  run: async (hub, args, signal) => {
6729
7142
  const on = flag(args, "watch", true);
6730
- const addr = need5(args, "addr");
7143
+ const addr = need6(args, "addr");
6731
7144
  const r = on ? await hub.concierge.watchlist.watch(addr, { signal }) : await hub.concierge.watchlist.unwatch(addr, { signal });
6732
7145
  return watchlistVerbText(on ? "watch" : "unwatch", r);
6733
7146
  }
@@ -6742,7 +7155,7 @@ ${watchlistConfigText(r.config)}`;
6742
7155
  idempotent: true,
6743
7156
  run: async (hub, args, signal) => {
6744
7157
  const on = flag(args, "mute", true);
6745
- const addr = need5(args, "addr");
7158
+ const addr = need6(args, "addr");
6746
7159
  const r = on ? await hub.concierge.watchlist.mute(addr, { signal }) : await hub.concierge.watchlist.unmute(addr, { signal });
6747
7160
  return watchlistVerbText(on ? "mute" : "unmute", r);
6748
7161
  }
@@ -6752,21 +7165,19 @@ ${watchlistConfigText(r.config)}`;
6752
7165
  // packages/cli/src/mcp.ts
6753
7166
  var MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
6754
7167
  var RPC = { parse: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602, internal: -32603 };
6755
-
6756
- class ArgError2 extends Error {
6757
- }
6758
- var need6 = (args, key) => {
7168
+ var ArgError3 = ToolArgError;
7169
+ var need7 = (args, key) => {
6759
7170
  const value = args[key];
6760
7171
  if (typeof value !== "string" || !value.trim())
6761
- throw new ArgError2(`"${key}" is required (a non-empty string)`);
7172
+ throw new ArgError3(`"${key}" is required (a non-empty string)`);
6762
7173
  return value;
6763
7174
  };
6764
- var optional6 = (args, key) => {
7175
+ var optional7 = (args, key) => {
6765
7176
  const value = args[key];
6766
7177
  if (value === undefined || value === null)
6767
7178
  return;
6768
7179
  if (typeof value !== "string")
6769
- throw new ArgError2(`"${key}" must be a string`);
7180
+ throw new ArgError3(`"${key}" must be a string`);
6770
7181
  return value.trim() ? value : undefined;
6771
7182
  };
6772
7183
  var optionalInt = (args, key, min, max) => {
@@ -6774,7 +7185,7 @@ var optionalInt = (args, key, min, max) => {
6774
7185
  if (value === undefined || value === null)
6775
7186
  return;
6776
7187
  if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max)
6777
- throw new ArgError2(`"${key}" must be a whole number from ${min} to ${max}`);
7188
+ throw new ArgError3(`"${key}" must be a whole number from ${min} to ${max}`);
6778
7189
  return value;
6779
7190
  };
6780
7191
  var optionalStrings = (args, key) => {
@@ -6782,7 +7193,7 @@ var optionalStrings = (args, key) => {
6782
7193
  if (value === undefined || value === null)
6783
7194
  return;
6784
7195
  if (!Array.isArray(value) || !value.every((v) => typeof v === "string"))
6785
- throw new ArgError2(`"${key}" must be a list of strings`);
7196
+ throw new ArgError3(`"${key}" must be a list of strings`);
6786
7197
  return value;
6787
7198
  };
6788
7199
  var optionalBool = (args, key) => {
@@ -6790,7 +7201,7 @@ var optionalBool = (args, key) => {
6790
7201
  if (value === undefined || value === null)
6791
7202
  return;
6792
7203
  if (typeof value !== "boolean")
6793
- throw new ArgError2(`"${key}" must be true or false`);
7204
+ throw new ArgError3(`"${key}" must be true or false`);
6794
7205
  return value;
6795
7206
  };
6796
7207
  var optionalWait = (args) => {
@@ -6798,7 +7209,7 @@ var optionalWait = (args) => {
6798
7209
  if (value === undefined || value === null)
6799
7210
  return;
6800
7211
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
6801
- throw new ArgError2(`"wait" must be a number of seconds (0–${WAIT_POLL_MAX_S})`);
7212
+ throw new ArgError3(`"wait" must be a number of seconds (0–${WAIT_POLL_MAX_S})`);
6802
7213
  return Math.min(value, WAIT_POLL_MAX_S);
6803
7214
  };
6804
7215
  var optionalExpires = (args) => {
@@ -6812,11 +7223,11 @@ var optionalExpires = (args) => {
6812
7223
  return durationArg(value.trim(), '"expires"');
6813
7224
  } catch (cause) {
6814
7225
  if (cause instanceof UsageError)
6815
- throw new ArgError2(cause.message);
7226
+ throw new ArgError3(cause.message);
6816
7227
  throw cause;
6817
7228
  }
6818
7229
  }
6819
- throw new ArgError2(`"expires" must be seconds or a duration like 30s, 5m, 2h, 1d`);
7230
+ throw new ArgError3(`"expires" must be seconds or a duration like 30s, 5m, 2h, 1d`);
6820
7231
  };
6821
7232
  var sendOptions = (args) => {
6822
7233
  const wake = optionalBool(args, "wake");
@@ -6824,7 +7235,7 @@ var sendOptions = (args) => {
6824
7235
  return { ...wake !== undefined ? { wake } : {}, ...urgent !== undefined ? { urgent } : {} };
6825
7236
  };
6826
7237
  var whyOption = (args) => {
6827
- const why = optional6(args, "why");
7238
+ const why = optional7(args, "why");
6828
7239
  return why !== undefined ? { why } : {};
6829
7240
  };
6830
7241
  var optionalDelivery = (args) => {
@@ -6832,18 +7243,18 @@ var optionalDelivery = (args) => {
6832
7243
  if (value === undefined || value === null)
6833
7244
  return;
6834
7245
  if (!DELIVERIES.includes(value))
6835
- throw new ArgError2(`"delivery" is one of ${DELIVERIES.join(", ")}`);
7246
+ throw new ArgError3(`"delivery" is one of ${DELIVERIES.join(", ")}`);
6836
7247
  return value;
6837
7248
  };
6838
7249
  var formAnswer = (args) => {
6839
7250
  const cancel = optionalBool(args, "cancel") === true;
6840
7251
  const answer = args.answer;
6841
7252
  if (cancel && answer !== undefined && answer !== null)
6842
- throw new ArgError2(`give either "answer" or "cancel", not both`);
7253
+ throw new ArgError3(`give either "answer" or "cancel", not both`);
6843
7254
  if (cancel)
6844
7255
  return "cancel";
6845
7256
  if (typeof answer !== "object" || answer === null || Array.isArray(answer))
6846
- throw new ArgError2(`"answer" is required (an object: field → value), or "cancel": true`);
7257
+ throw new ArgError3(`"answer" is required (an object: field → value), or "cancel": true`);
6847
7258
  return answer;
6848
7259
  };
6849
7260
  var optionalEnum = (args, key, options) => {
@@ -6851,7 +7262,7 @@ var optionalEnum = (args, key, options) => {
6851
7262
  if (value === undefined || value === null)
6852
7263
  return;
6853
7264
  if (!options.includes(value))
6854
- throw new ArgError2(`"${key}" is one of ${options.join(", ")}`);
7265
+ throw new ArgError3(`"${key}" is one of ${options.join(", ")}`);
6855
7266
  return value;
6856
7267
  };
6857
7268
  var optionalLabels = (args, key) => {
@@ -6859,7 +7270,7 @@ var optionalLabels = (args, key) => {
6859
7270
  if (value === undefined || value === null)
6860
7271
  return;
6861
7272
  if (typeof value !== "object" || Array.isArray(value) || !Object.values(value).every((v) => typeof v === "string"))
6862
- throw new ArgError2(`"${key}" must be an object of text values (key → value)`);
7273
+ throw new ArgError3(`"${key}" must be an object of text values (key → value)`);
6863
7274
  return Object.keys(value).length ? value : undefined;
6864
7275
  };
6865
7276
  var definedFields = (fields) => {
@@ -6867,35 +7278,35 @@ var definedFields = (fields) => {
6867
7278
  return Object.keys(out).length ? out : undefined;
6868
7279
  };
6869
7280
  function launchSpec(args) {
6870
- const repo = need6(args, "repo");
7281
+ const repo = need7(args, "repo");
6871
7282
  if (!repo.startsWith("/"))
6872
- throw new ArgError2(`"repo" must be an absolute path on the node`);
7283
+ throw new ArgError3(`"repo" must be an absolute path on the node`);
6873
7284
  const kind = optionalEnum(args, "runtime", RUNTIME_KINDS);
6874
- const profile = optional6(args, "profile");
7285
+ const profile = optional7(args, "profile");
6875
7286
  if (profile !== undefined && kind === undefined)
6876
- throw new ArgError2(`"profile" needs "runtime" (e.g. openshell)`);
7287
+ throw new ArgError3(`"profile" needs "runtime" (e.g. openshell)`);
6877
7288
  const nodeLabels = optionalLabels(args, "nodeLabels");
6878
- const machine = optional6(args, "machine");
7289
+ const machine = optional7(args, "machine");
6879
7290
  const prefer = optionalBool(args, "prefer") === true;
6880
7291
  if (prefer && machine === undefined && nodeLabels === undefined)
6881
- throw new ArgError2(`"prefer" needs "machine" or "nodeLabels"`);
6882
- const workspace = { repo, ...definedFields({ mode: optionalEnum(args, "mode", WORKSPACE_MODES), branch: optional6(args, "branch"), base: optional6(args, "base"), remote: optional6(args, "remote") }) };
7292
+ throw new ArgError3(`"prefer" needs "machine" or "nodeLabels"`);
7293
+ const workspace = { repo, ...definedFields({ mode: optionalEnum(args, "mode", WORKSPACE_MODES), branch: optional7(args, "branch"), base: optional7(args, "base"), remote: optional7(args, "remote") }) };
6883
7294
  return {
6884
7295
  ...definedFields({
6885
- key: optional6(args, "key"),
6886
- name: optional6(args, "name")?.replace(/^@+/, ""),
7296
+ key: optional7(args, "key"),
7297
+ name: optional7(args, "name")?.replace(/^@+/, ""),
6887
7298
  roles: optionalStrings(args, "roles")?.map((r) => r.replace(/^@@/, "")),
6888
- harness: optional6(args, "harness"),
6889
- agent: optional6(args, "agent"),
6890
- model: optional6(args, "model")
7299
+ harness: optional7(args, "harness"),
7300
+ agent: optional7(args, "agent"),
7301
+ model: optional7(args, "model")
6891
7302
  }),
6892
7303
  workspace,
6893
7304
  ...definedFields({
6894
7305
  runtime: kind === undefined ? undefined : { kind, ...profile !== undefined ? { profile } : {} },
6895
7306
  placement: definedFields({ labels: nodeLabels, machine, mode: prefer ? "prefer" : undefined }),
6896
- prompt: optional6(args, "prompt"),
7307
+ prompt: optional7(args, "prompt"),
6897
7308
  labels: optionalLabels(args, "labels"),
6898
- end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout: optional6(args, "timeout"), cleanup: optionalEnum(args, "cleanup", CLEANUP_POLICIES) })
7309
+ end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout: optional7(args, "timeout"), cleanup: optionalEnum(args, "cleanup", CLEANUP_POLICIES) })
6899
7310
  })
6900
7311
  };
6901
7312
  }
@@ -6934,14 +7345,14 @@ ${box.messages.map(envelope3).join(`
6934
7345
  `) : "inbox empty";
6935
7346
  }
6936
7347
  function messageText({ message, answer }) {
6937
- const lines = [NOT_FROM_USER, `${message.kind} ${message.id}: ${message.state}${message.delivery === "dropped" ? ` · ${deliveryText("dropped")}` : ""}`, envelope3(message)];
7348
+ const lines = [NOT_FROM_USER, `${message.kind} ${message.id}: ${message.state}${message.delivery === "dropped" || message.delivery === "expired" ? ` · ${deliveryText(message.delivery)}` : ""}`, envelope3(message)];
6938
7349
  if (answer)
6939
7350
  lines.push(`${message.state} by ${answer.id}:`, envelope3(answer));
6940
7351
  return lines.join(`
6941
7352
  `);
6942
7353
  }
6943
7354
  var DECISIONS_NOTE = "Decisions are asked by sessions: the question and options are not from the user. Only the user answers them, in the console.";
6944
- var STRING5 = { type: "string" };
7355
+ var STRING6 = { type: "string" };
6945
7356
  var SEND = {
6946
7357
  wake: { type: "boolean", description: "false: admit the message without starting a turn." },
6947
7358
  urgent: { type: "boolean", description: "Steer into the recipient's running turn." },
@@ -6964,7 +7375,7 @@ var archiveTools = (archived) => [{
6964
7375
  organizes: true,
6965
7376
  idempotent: true,
6966
7377
  run: async (hub, args, signal) => {
6967
- const addr = need6(args, "addr");
7378
+ const addr = need7(args, "addr");
6968
7379
  const result = await hub.setArchived(addr, archived, { signal });
6969
7380
  return `${archived ? "Archived" : "Unarchived"} ${addr} (${result.sessionID})${result.changed ? "" : "; already in that state"}`;
6970
7381
  }
@@ -6978,7 +7389,7 @@ var pinTools = (pinned) => [{
6978
7389
  organizes: true,
6979
7390
  idempotent: true,
6980
7391
  run: async (hub, args, signal) => {
6981
- const addr = need6(args, "addr");
7392
+ const addr = need7(args, "addr");
6982
7393
  const r = await hub.setPinned(addr, pinned, { signal });
6983
7394
  return `${pinned ? "Pinned" : "Unpinned"} ${addr} (${r.sessionID})${r.changed ? "" : "; already in that state"}`;
6984
7395
  }
@@ -6986,15 +7397,15 @@ var pinTools = (pinned) => [{
6986
7397
  var archiveInactiveTool = {
6987
7398
  name: "archive_inactive",
6988
7399
  description: "Preview an exact bounded set of inactive sessions, then confirm that opaque preview token. Apply revalidates active, open, pinned and archived state. Needs sessions:manage.",
6989
- properties: { preview: { type: "boolean" }, inactiveFor: STRING5, limit: { type: "integer", minimum: 1, maximum: 200 }, previewToken: STRING5, confirm: { type: "boolean" } },
7400
+ properties: { preview: { type: "boolean" }, inactiveFor: STRING6, limit: { type: "integer", minimum: 1, maximum: 200 }, previewToken: STRING6, confirm: { type: "boolean" } },
6990
7401
  readOnly: false,
6991
7402
  organizes: true,
6992
7403
  run: async (hub, args, signal) => {
6993
7404
  if (args.preview === true)
6994
- return JSON.stringify(await hub.previewArchiveInactive(need6(args, "inactiveFor"), optionalInt(args, "limit", 1, 200) ?? 50, { signal }));
7405
+ return JSON.stringify(await hub.previewArchiveInactive(need7(args, "inactiveFor"), optionalInt(args, "limit", 1, 200) ?? 50, { signal }));
6995
7406
  if (args.confirm === true)
6996
- return JSON.stringify(await hub.archiveInactive(need6(args, "previewToken"), { signal }));
6997
- throw new ArgError2("Use preview:true with inactiveFor, then confirm:true with previewToken");
7407
+ return JSON.stringify(await hub.archiveInactive(need7(args, "previewToken"), { signal }));
7408
+ throw new ArgError3("Use preview:true with inactiveFor, then confirm:true with previewToken");
6998
7409
  }
6999
7410
  };
7000
7411
  var intentTools = [
@@ -7004,44 +7415,44 @@ var intentTools = [
7004
7415
  properties: { addr: ADDR2 },
7005
7416
  required: ["addr"],
7006
7417
  readOnly: true,
7007
- run: async (hub, args, signal) => intentLine(need6(args, "addr"), (await hub.intent(need6(args, "addr"), { signal })).intent)
7418
+ run: async (hub, args, signal) => intentLine(need7(args, "addr"), (await hub.intent(need7(args, "addr"), { signal })).intent)
7008
7419
  },
7009
7420
  {
7010
7421
  name: "session_intent_set",
7011
7422
  description: "Set (text, one line, at most 280 characters) or clear (clear: true) a session's intent. Users and apps (sessions:manage) set the user's intent; a session token only its own session's. expectedRevision: the revision read (read first when absent); a stale one is a conflict.",
7012
- properties: { addr: ADDR2, text: STRING5, clear: { type: "boolean" }, expectedRevision: { type: "integer", minimum: 0 } },
7423
+ properties: { addr: ADDR2, text: STRING6, clear: { type: "boolean" }, expectedRevision: { type: "integer", minimum: 0 } },
7013
7424
  required: ["addr"],
7014
7425
  readOnly: false,
7015
7426
  organizes: true,
7016
7427
  idempotent: true,
7017
7428
  run: async (hub, args, signal) => {
7018
- const addr = need6(args, "addr");
7019
- const text = optional6(args, "text");
7429
+ const addr = need7(args, "addr");
7430
+ const text = optional7(args, "text");
7020
7431
  const clear = args.clear === true;
7021
7432
  if (clear === (text !== undefined))
7022
- throw new ArgError2('Give "text" or "clear": true');
7433
+ throw new ArgError3('Give "text" or "clear": true');
7023
7434
  const expectedRevision = optionalInt(args, "expectedRevision", 0, Number.MAX_SAFE_INTEGER);
7024
7435
  return intentLine(addr, await writeIntent(hub, addr, clear ? null : text, { signal, ...expectedRevision !== undefined ? { expectedRevision } : {} }));
7025
7436
  }
7026
7437
  }
7027
7438
  ];
7028
7439
  var TARGET = { ...ADDR2, description: `${ADDR2.description} Required for an app token; a session token defaults to itself.` };
7029
- var ROLE2 = { ...STRING5, description: "A role (reviewer, @@reviewer or reviewer.project)." };
7030
- var ROLE_PROJECT = { ...STRING5, description: "Scope the role to this project (as role.project)." };
7440
+ var ROLE2 = { ...STRING6, description: "A role (reviewer, @@reviewer or reviewer.project)." };
7441
+ var ROLE_PROJECT = { ...STRING6, description: "Scope the role to this project (as role.project)." };
7031
7442
  var MANAGE_NOTE = "Needs scope sessions:manage for an app token";
7032
7443
  var roleText2 = (r) => `@@${r.role}${r.project !== undefined ? `.${r.project}` : ""}`;
7033
7444
  var nameTools = [
7034
7445
  {
7035
7446
  name: "name",
7036
7447
  description: `Name a session (@name); as a session, leave addr out to name yourself (a session token names only its own session). Its previous name stays an alias for 24 h. A taken name fails (name_taken); @me is reserved. ${MANAGE_NOTE}.`,
7037
- properties: { addr: TARGET, name: { ...STRING5, description: "The name: 2–40 characters a-z, 0-9 and - (a leading @ is fine)." } },
7448
+ properties: { addr: TARGET, name: { ...STRING6, description: "The name: 2–40 characters a-z, 0-9 and - (a leading @ is fine)." } },
7038
7449
  required: ["name"],
7039
7450
  readOnly: false,
7040
7451
  organizes: true,
7041
7452
  idempotent: true,
7042
7453
  run: async (hub, args, signal) => {
7043
- const session = optional6(args, "addr");
7044
- const r = await hub.name(need6(args, "name"), { ...session ? { session } : {}, signal });
7454
+ const session = optional7(args, "addr");
7455
+ const r = await hub.name(need7(args, "name"), { ...session ? { session } : {}, signal });
7045
7456
  return `Named ${r.sessionID} @${r.name}${r.alias ? ` (@${r.alias} stays an alias for 24 h)` : ""}`;
7046
7457
  }
7047
7458
  },
@@ -7054,9 +7465,9 @@ var nameTools = [
7054
7465
  organizes: true,
7055
7466
  idempotent: true,
7056
7467
  run: async (hub, args, signal) => {
7057
- const session = optional6(args, "addr");
7058
- const project = optional6(args, "project");
7059
- const r = await hub.claimRole(need6(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, ...optionalBool(args, "exclusive") ? { exclusive: true } : {}, signal });
7468
+ const session = optional7(args, "addr");
7469
+ const project = optional7(args, "project");
7470
+ const r = await hub.claimRole(need7(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, ...optionalBool(args, "exclusive") ? { exclusive: true } : {}, signal });
7060
7471
  return `${r.sessionID} holds ${roleText2(r)} (generation ${r.generation}${r.exclusive ? ", exclusive" : ""})`;
7061
7472
  }
7062
7473
  },
@@ -7068,9 +7479,9 @@ var nameTools = [
7068
7479
  readOnly: false,
7069
7480
  organizes: true,
7070
7481
  run: async (hub, args, signal) => {
7071
- const session = optional6(args, "addr");
7072
- const project = optional6(args, "project");
7073
- const r = await hub.releaseRole(need6(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, signal });
7482
+ const session = optional7(args, "addr");
7483
+ const project = optional7(args, "project");
7484
+ const r = await hub.releaseRole(need7(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, signal });
7074
7485
  return `${r.sessionID} released ${roleText2(r)}`;
7075
7486
  }
7076
7487
  }
@@ -7079,16 +7490,16 @@ var eventsTool = {
7079
7490
  name: "events",
7080
7491
  description: "Compact Hub events (session.*, blocker.*, machine.changed, message.changed, launch.changed, session.archived) after a cursor, as JSON {events, cursor, more, reset, refresh?}. Call without cursor to start at the latest, then pass back the returned cursor. reset:true means the cursor expired: re-read the listed snapshots (status, sessions, inbox, …) and continue with the new cursor. Unknown event types are valid; ignore them. Needs scope events:subscribe (plus each family's scope) for an app token.",
7081
7492
  properties: {
7082
- cursor: { ...STRING5, description: "The cursor from the previous events answer (hev1_…); omit to start at the latest." },
7493
+ cursor: { ...STRING6, description: "The cursor from the previous events answer (hev1_…); omit to start at the latest." },
7083
7494
  wait: { type: "integer", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait while nothing matches (0–${WAIT_POLL_MAX_S}).` },
7084
7495
  limit: { type: "integer", minimum: 1, maximum: 200, description: "At most this many events (default 50)." },
7085
- types: { type: "array", items: STRING5, description: "Only these event types (exact, or a family like launch.*)." },
7086
- sessions: { type: "array", items: STRING5, description: "Only events of these sessions (ses_…, @name, @@role)." }
7496
+ types: { type: "array", items: STRING6, description: "Only these event types (exact, or a family like launch.*)." },
7497
+ sessions: { type: "array", items: STRING6, description: "Only events of these sessions (ses_…, @name, @@role)." }
7087
7498
  },
7088
7499
  readOnly: true,
7089
7500
  run: async (hub, args, signal) => {
7090
7501
  const page = await hub.events({
7091
- cursor: optional6(args, "cursor"),
7502
+ cursor: optional7(args, "cursor"),
7092
7503
  wait: optionalInt(args, "wait", 0, WAIT_POLL_MAX_S),
7093
7504
  limit: optionalInt(args, "limit", 1, 200),
7094
7505
  types: optionalStrings(args, "types"),
@@ -7103,13 +7514,13 @@ var webhookTools = [
7103
7514
  {
7104
7515
  name: "webhook_create",
7105
7516
  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.",
7106
- properties: { url: STRING5, events: { type: "array", items: STRING5 }, label: STRING5 },
7517
+ properties: { url: STRING6, events: { type: "array", items: STRING6 }, label: STRING6 },
7107
7518
  required: ["url"],
7108
7519
  readOnly: false,
7109
7520
  run: async (hub, args, signal) => {
7110
7521
  const events = optionalStrings(args, "events");
7111
- const label = optional6(args, "label");
7112
- const r = await hub.webhookCreate({ url: need6(args, "url"), ...events ? { events } : {}, ...label ? { label } : {} }, { signal });
7522
+ const label = optional7(args, "label");
7523
+ const r = await hub.webhookCreate({ url: need7(args, "url"), ...events ? { events } : {}, ...label ? { label } : {} }, { signal });
7113
7524
  return `${webhookText(r.webhook)}
7114
7525
  signing secret (shown once): ${r.secret}`;
7115
7526
  }
@@ -7128,11 +7539,11 @@ signing secret (shown once): ${r.secret}`;
7128
7539
  {
7129
7540
  name: "webhook_show",
7130
7541
  description: "Show one webhook and its retained delivery attempts; never its secret.",
7131
- properties: { id: STRING5 },
7542
+ properties: { id: STRING6 },
7132
7543
  required: ["id"],
7133
7544
  readOnly: true,
7134
7545
  run: async (hub, args, signal) => {
7135
- const r = await hub.webhook(need6(args, "id"), { signal });
7546
+ const r = await hub.webhook(need7(args, "id"), { signal });
7136
7547
  return `${webhookText(r.webhook)}
7137
7548
  ${JSON.stringify(r.deliveries)}`;
7138
7549
  }
@@ -7140,25 +7551,25 @@ ${JSON.stringify(r.deliveries)}`;
7140
7551
  {
7141
7552
  name: "webhook_delete",
7142
7553
  description: "Delete one webhook and its pending deliveries.",
7143
- properties: { id: STRING5 },
7554
+ properties: { id: STRING6 },
7144
7555
  required: ["id"],
7145
7556
  readOnly: false,
7146
7557
  organizes: true,
7147
7558
  idempotent: false,
7148
7559
  run: async (hub, args, signal) => {
7149
- const id = need6(args, "id");
7560
+ const id = need7(args, "id");
7150
7561
  await hub.webhookDelete(id, { signal });
7151
7562
  return `Deleted webhook ${id}`;
7152
7563
  }
7153
7564
  }
7154
7565
  ];
7155
7566
  async function profileTarget(hub, args, signal) {
7156
- const addr = optional6(args, "addr");
7567
+ const addr = optional7(args, "addr");
7157
7568
  if (addr)
7158
7569
  return addr;
7159
7570
  const { actor } = await hub.whoami({ signal });
7160
7571
  if (actor.kind !== "session")
7161
- throw new ArgError2(`"addr" is required unless this credential is a session's`);
7572
+ throw new ArgError3(`"addr" is required unless this credential is a session's`);
7162
7573
  return actor.sessionID;
7163
7574
  }
7164
7575
  var optionalParams = (args) => optionalLabels(args, "params") ?? {};
@@ -7192,11 +7603,11 @@ var profileTools = [
7192
7603
  {
7193
7604
  name: "profile_skill",
7194
7605
  description: "Load one skill's full text from the profile pinned to a session (your own by default), on demand.",
7195
- properties: { name: STRING5, addr: ADDR2 },
7606
+ properties: { name: STRING6, addr: ADDR2 },
7196
7607
  required: ["name"],
7197
7608
  readOnly: true,
7198
7609
  run: async (hub, args, signal) => {
7199
- const r = await hub.profiles.sessionSkill(await profileTarget(hub, args, signal), need6(args, "name"), { signal });
7610
+ const r = await hub.profiles.sessionSkill(await profileTarget(hub, args, signal), need7(args, "name"), { signal });
7200
7611
  return `skill ${r.skill.name} (${r.profileID} v${r.version}):
7201
7612
  ${sanitizeText(r.skill.body)}`;
7202
7613
  }
@@ -7204,11 +7615,11 @@ ${sanitizeText(r.skill.body)}`;
7204
7615
  {
7205
7616
  name: "profile_command",
7206
7617
  description: "Render one command of the profile pinned to a session (your own by default) with its {{name}} parameters. Returns the text only: nothing is sent. {{secret:NAME}} references stay unresolved.",
7207
- properties: { name: STRING5, params: { type: "object", additionalProperties: STRING5, description: "Parameter name → text value." }, addr: ADDR2 },
7618
+ properties: { name: STRING6, params: { type: "object", additionalProperties: STRING6, description: "Parameter name → text value." }, addr: ADDR2 },
7208
7619
  required: ["name"],
7209
7620
  readOnly: true,
7210
7621
  run: async (hub, args, signal) => {
7211
- const r = await hub.profiles.sessionRender(await profileTarget(hub, args, signal), need6(args, "name"), optionalParams(args), { signal });
7622
+ const r = await hub.profiles.sessionRender(await profileTarget(hub, args, signal), need7(args, "name"), optionalParams(args), { signal });
7212
7623
  return r.prompt;
7213
7624
  }
7214
7625
  },
@@ -7227,13 +7638,13 @@ ${sanitizeText(r.skill.body)}`;
7227
7638
  {
7228
7639
  name: "profile_usage",
7229
7640
  description: "Where one profile is used: pinned sessions (requested vs running version, ack, presence, harness), team roles naming it, launches that named it and the sessions' workspaces, with counts. An app token needs profiles:read, plus sessions:read / teams:read / sessions:launch (its own launches) / workspaces:read for each part; parts it may not read are listed as hidden.",
7230
- properties: { id: STRING5, limit: { type: "integer", minimum: 1, maximum: 200, description: "Sessions per page (default 50)." }, cursor: STRING5 },
7641
+ properties: { id: STRING6, limit: { type: "integer", minimum: 1, maximum: 200, description: "Sessions per page (default 50)." }, cursor: STRING6 },
7231
7642
  required: ["id"],
7232
7643
  readOnly: true,
7233
7644
  run: async (hub, args, signal) => {
7234
7645
  const limit = optionalInt(args, "limit", 1, 200);
7235
- const cursor = optional6(args, "cursor");
7236
- const u = await hub.profiles.usage(need6(args, "id"), { ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {}, signal });
7646
+ const cursor = optional7(args, "cursor");
7647
+ const u = await hub.profiles.usage(need7(args, "id"), { ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {}, signal });
7237
7648
  const c = u.counts;
7238
7649
  const plural = (n, word, many = `${word}s`) => `${n} ${n === 1 ? word : many}`;
7239
7650
  const lines = [`${u.profileID} (latest v${u.latestVersion})`];
@@ -7272,16 +7683,16 @@ ${sanitizeText(r.skill.body)}`;
7272
7683
  var needRevision = (args, key = "expectedRevision") => {
7273
7684
  const value = optionalInt(args, key, 0, Number.MAX_SAFE_INTEGER);
7274
7685
  if (value === undefined)
7275
- throw new ArgError2(`"${key}" is required: the revision you read`);
7686
+ throw new ArgError3(`"${key}" is required: the revision you read`);
7276
7687
  return value;
7277
7688
  };
7278
7689
  var idemOption = (args) => {
7279
- const key = optional6(args, "idempotencyKey");
7690
+ const key = optional7(args, "idempotencyKey");
7280
7691
  return key !== undefined ? { idempotencyKey: key } : {};
7281
7692
  };
7282
7693
  var WORKSPACE = { type: "string", description: "The workspace's ID (ws_…)." };
7283
- var REVISION3 = { type: "integer", minimum: 0, description: "The revision you read (a stale one answers conflict with the current revision)." };
7284
- var IDEMPOTENCY2 = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
7694
+ var REVISION4 = { type: "integer", minimum: 0, description: "The revision you read (a stale one answers conflict with the current revision)." };
7695
+ var IDEMPOTENCY3 = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
7285
7696
  var MEMBER_ROLES = ["worker", "observer"];
7286
7697
  var ASSIGNMENT_STATUSES = ["open", "in_progress", "blocked", "done", "cancelled"];
7287
7698
  var WS_SCOPES = "Needs scope workspaces:read (reads) or workspaces:manage (changes) for an app token; member and assignment tools also need sessions:read. Session tokens are refused. Membership and supervision confer no authority.";
@@ -7306,7 +7717,7 @@ var workspaceTools = [
7306
7717
  required: ["workspace"],
7307
7718
  readOnly: true,
7308
7719
  run: async (hub, args, signal) => {
7309
- const id = need6(args, "workspace");
7720
+ const id = need7(args, "workspace");
7310
7721
  const [{ workspace, bindings }, { members }, graph] = await Promise.all([hub.workspaces.get(id, { signal }), hub.workspaces.members(id, { signal }), hub.workspaces.supervision(id, { signal })]);
7311
7722
  const lines = [wsLine(workspace)];
7312
7723
  if (workspace.masterMemberID)
@@ -7322,39 +7733,39 @@ var workspaceTools = [
7322
7733
  {
7323
7734
  name: "workspace_create",
7324
7735
  description: `Create a coordination workspace. ${WS_SCOPES}`,
7325
- properties: { title: STRING5, description: STRING5, tags: { type: "array", items: STRING5 }, idempotencyKey: IDEMPOTENCY2 },
7736
+ properties: { title: STRING6, description: STRING6, tags: { type: "array", items: STRING6 }, idempotencyKey: IDEMPOTENCY3 },
7326
7737
  required: ["title"],
7327
7738
  readOnly: false,
7328
7739
  run: async (hub, args, signal) => {
7329
- const description = optional6(args, "description");
7740
+ const description = optional7(args, "description");
7330
7741
  const tags = optionalStrings(args, "tags");
7331
- const { workspace } = await hub.workspaces.create({ title: need6(args, "title"), ...description ? { description } : {}, ...tags ? { tags } : {} }, { ...idemOption(args), signal });
7742
+ const { workspace } = await hub.workspaces.create({ title: need7(args, "title"), ...description ? { description } : {}, ...tags ? { tags } : {} }, { ...idemOption(args), signal });
7332
7743
  return `created ${wsLine(workspace)}`;
7333
7744
  }
7334
7745
  },
7335
7746
  {
7336
7747
  name: "workspace_update",
7337
7748
  description: `Change a workspace's title, description or tags under its revision. ${WS_SCOPES}`,
7338
- properties: { workspace: WORKSPACE, expectedRevision: REVISION3, title: STRING5, description: STRING5, tags: { type: "array", items: STRING5 } },
7749
+ properties: { workspace: WORKSPACE, expectedRevision: REVISION4, title: STRING6, description: STRING6, tags: { type: "array", items: STRING6 } },
7339
7750
  required: ["workspace", "expectedRevision"],
7340
7751
  readOnly: false,
7341
7752
  run: async (hub, args, signal) => {
7342
- const patch = definedFields({ title: optional6(args, "title"), description: optional6(args, "description"), tags: optionalStrings(args, "tags") });
7753
+ const patch = definedFields({ title: optional7(args, "title"), description: optional7(args, "description"), tags: optionalStrings(args, "tags") });
7343
7754
  if (!patch)
7344
- throw new ArgError2(`give "title", "description" or "tags"`);
7345
- const { workspace } = await hub.workspaces.update(need6(args, "workspace"), { expectedRevision: needRevision(args), ...patch }, { signal });
7755
+ throw new ArgError3(`give "title", "description" or "tags"`);
7756
+ const { workspace } = await hub.workspaces.update(need7(args, "workspace"), { expectedRevision: needRevision(args), ...patch }, { signal });
7346
7757
  return `updated ${wsLine(workspace)}`;
7347
7758
  }
7348
7759
  },
7349
7760
  ...["archive", "restore"].map((verb) => ({
7350
7761
  name: `workspace_${verb}`,
7351
7762
  description: verb === "archive" ? `Archive a workspace under its revision: current members depart and their supervision links are detached. ${WS_SCOPES}` : `Restore an archived workspace under its revision (nobody re-joins). ${WS_SCOPES}`,
7352
- properties: { workspace: WORKSPACE, expectedRevision: REVISION3 },
7763
+ properties: { workspace: WORKSPACE, expectedRevision: REVISION4 },
7353
7764
  required: ["workspace", "expectedRevision"],
7354
7765
  readOnly: false,
7355
7766
  idempotent: true,
7356
7767
  run: async (hub, args, signal) => {
7357
- const id = need6(args, "workspace");
7768
+ const id = need7(args, "workspace");
7358
7769
  if (verb === "archive") {
7359
7770
  const r = await hub.workspaces.archive(id, needRevision(args), { signal });
7360
7771
  return `archived ${wsLine(r.workspace)}; ${r.departed} member(s) departed, ${r.supervision.detached} supervision link(s) detached`;
@@ -7365,77 +7776,77 @@ var workspaceTools = [
7365
7776
  {
7366
7777
  name: "workspace_bind",
7367
7778
  description: `Bind a workspace to a repository (absolute path) on one of the user's machines. ${WS_SCOPES}`,
7368
- properties: { workspace: WORKSPACE, machineID: STRING5, repo: STRING5, label: STRING5, idempotencyKey: IDEMPOTENCY2 },
7779
+ properties: { workspace: WORKSPACE, machineID: STRING6, repo: STRING6, label: STRING6, idempotencyKey: IDEMPOTENCY3 },
7369
7780
  required: ["workspace", "machineID", "repo"],
7370
7781
  readOnly: false,
7371
7782
  idempotent: true,
7372
7783
  run: async (hub, args, signal) => {
7373
- const label = optional6(args, "label");
7374
- const r = await hub.workspaces.bind(need6(args, "workspace"), { machineID: need6(args, "machineID"), repo: need6(args, "repo"), ...label ? { label } : {} }, { ...idemOption(args), signal });
7784
+ const label = optional7(args, "label");
7785
+ const r = await hub.workspaces.bind(need7(args, "workspace"), { machineID: need7(args, "machineID"), repo: need7(args, "repo"), ...label ? { label } : {} }, { ...idemOption(args), signal });
7375
7786
  return `${r.created ? "bound" : "already bound"} ${r.binding.id} ${r.binding.machineID}:${r.binding.repo} r${r.binding.revision}`;
7376
7787
  }
7377
7788
  },
7378
7789
  {
7379
7790
  name: "workspace_unbind",
7380
7791
  description: `Remove a repository binding under the binding's revision. ${WS_SCOPES}`,
7381
- properties: { workspace: WORKSPACE, bindingID: STRING5, expectedRevision: REVISION3 },
7792
+ properties: { workspace: WORKSPACE, bindingID: STRING6, expectedRevision: REVISION4 },
7382
7793
  required: ["workspace", "bindingID", "expectedRevision"],
7383
7794
  readOnly: false,
7384
7795
  run: async (hub, args, signal) => {
7385
- await hub.workspaces.unbind(need6(args, "workspace"), need6(args, "bindingID"), needRevision(args), { signal });
7386
- return `unbound ${need6(args, "bindingID")}`;
7796
+ await hub.workspaces.unbind(need7(args, "workspace"), need7(args, "bindingID"), needRevision(args), { signal });
7797
+ return `unbound ${need7(args, "bindingID")}`;
7387
7798
  }
7388
7799
  },
7389
7800
  {
7390
7801
  name: "workspace_join",
7391
7802
  description: `Add a session (ses_…) to a workspace as worker or observer (the master is set by explicit replacement). ${WS_SCOPES}`,
7392
- properties: { workspace: WORKSPACE, sessionID: STRING5, role: { type: "string", enum: [...MEMBER_ROLES] }, idempotencyKey: IDEMPOTENCY2 },
7803
+ properties: { workspace: WORKSPACE, sessionID: STRING6, role: { type: "string", enum: [...MEMBER_ROLES] }, idempotencyKey: IDEMPOTENCY3 },
7393
7804
  required: ["workspace", "sessionID", "role"],
7394
7805
  readOnly: false,
7395
7806
  idempotent: true,
7396
7807
  run: async (hub, args, signal) => {
7397
7808
  const role = optionalEnum(args, "role", MEMBER_ROLES);
7398
7809
  if (!role)
7399
- throw new ArgError2(`"role" is one of ${MEMBER_ROLES.join(", ")}`);
7400
- const { member, created } = await hub.workspaces.join(need6(args, "workspace"), { sessionID: need6(args, "sessionID"), role }, { ...idemOption(args), signal });
7810
+ throw new ArgError3(`"role" is one of ${MEMBER_ROLES.join(", ")}`);
7811
+ const { member, created } = await hub.workspaces.join(need7(args, "workspace"), { sessionID: need7(args, "sessionID"), role }, { ...idemOption(args), signal });
7401
7812
  return `member ${member.id} (${member.sessionID}, ${member.role}, generation ${member.generation}, r${member.revision}) ${created ? "joined" : "was already a member"}`;
7402
7813
  }
7403
7814
  },
7404
7815
  {
7405
7816
  name: "workspace_leave",
7406
7817
  description: `Remove a member under its revision; its supervision links are detached. ${WS_SCOPES}`,
7407
- properties: { workspace: WORKSPACE, memberID: STRING5, expectedRevision: REVISION3 },
7818
+ properties: { workspace: WORKSPACE, memberID: STRING6, expectedRevision: REVISION4 },
7408
7819
  required: ["workspace", "memberID", "expectedRevision"],
7409
7820
  readOnly: false,
7410
7821
  idempotent: true,
7411
7822
  run: async (hub, args, signal) => {
7412
- const r = await hub.workspaces.leave(need6(args, "workspace"), need6(args, "memberID"), needRevision(args), { signal });
7823
+ const r = await hub.workspaces.leave(need7(args, "workspace"), need7(args, "memberID"), needRevision(args), { signal });
7413
7824
  return `member ${r.member.id} left (${r.member.leftReason ?? "removed"}); ${r.supervision.detached} supervision link(s) detached`;
7414
7825
  }
7415
7826
  },
7416
7827
  {
7417
7828
  name: "workspace_assign",
7418
7829
  description: `Give a current member an assignment (title and optional outcome criteria; instructions travel as messages). ${WS_SCOPES}`,
7419
- properties: { workspace: WORKSPACE, memberID: STRING5, title: STRING5, criteria: { type: "array", items: STRING5 }, ref: STRING5, idempotencyKey: IDEMPOTENCY2 },
7830
+ properties: { workspace: WORKSPACE, memberID: STRING6, title: STRING6, criteria: { type: "array", items: STRING6 }, ref: STRING6, idempotencyKey: IDEMPOTENCY3 },
7420
7831
  required: ["workspace", "memberID", "title"],
7421
7832
  readOnly: false,
7422
7833
  run: async (hub, args, signal) => {
7423
7834
  const criteria = optionalStrings(args, "criteria");
7424
- const ref = optional6(args, "ref");
7425
- const { assignment } = await hub.workspaces.assign(need6(args, "workspace"), { memberID: need6(args, "memberID"), title: need6(args, "title"), ...criteria ? { criteria } : {}, ...ref ? { ref } : {} }, { ...idemOption(args), signal });
7835
+ const ref = optional7(args, "ref");
7836
+ const { assignment } = await hub.workspaces.assign(need7(args, "workspace"), { memberID: need7(args, "memberID"), title: need7(args, "title"), ...criteria ? { criteria } : {}, ...ref ? { ref } : {} }, { ...idemOption(args), signal });
7426
7837
  return `assignment ${assignment.id} → ${assignment.memberID}: ${assignment.status}, r${assignment.revision}`;
7427
7838
  }
7428
7839
  },
7429
7840
  {
7430
7841
  name: "workspace_assignments",
7431
7842
  description: `A workspace's assignments (id, member, status, revision, title), optionally one member's or one status. ${WS_SCOPES}`,
7432
- properties: { workspace: WORKSPACE, memberID: STRING5, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] } },
7843
+ properties: { workspace: WORKSPACE, memberID: STRING6, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] } },
7433
7844
  required: ["workspace"],
7434
7845
  readOnly: true,
7435
7846
  run: async (hub, args, signal) => {
7436
- const memberID = optional6(args, "memberID");
7847
+ const memberID = optional7(args, "memberID");
7437
7848
  const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
7438
- const r = await hub.workspaces.assignments(need6(args, "workspace"), { ...memberID ? { memberID } : {}, ...status ? { status } : {}, limit: 200, signal });
7849
+ const r = await hub.workspaces.assignments(need7(args, "workspace"), { ...memberID ? { memberID } : {}, ...status ? { status } : {}, limit: 200, signal });
7439
7850
  return r.items.length ? r.items.map((v) => `${v.assignment.id} ${v.assignment.memberID} (${v.member}, ${v.presence}) ${v.assignment.status} r${v.assignment.revision} ${oneLine(sanitizeText(v.assignment.title))}`).join(`
7440
7851
  `) : "no assignments";
7441
7852
  }
@@ -7443,16 +7854,16 @@ var workspaceTools = [
7443
7854
  {
7444
7855
  name: "workspace_assignment_update",
7445
7856
  description: `Set an assignment's status (done and cancelled are final) or move it to another current member, under its revision. ${WS_SCOPES}`,
7446
- properties: { workspace: WORKSPACE, assignmentID: STRING5, expectedRevision: REVISION3, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] }, memberID: STRING5 },
7857
+ properties: { workspace: WORKSPACE, assignmentID: STRING6, expectedRevision: REVISION4, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] }, memberID: STRING6 },
7447
7858
  required: ["workspace", "assignmentID", "expectedRevision"],
7448
7859
  readOnly: false,
7449
7860
  run: async (hub, args, signal) => {
7450
7861
  const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
7451
- const memberID = optional6(args, "memberID");
7862
+ const memberID = optional7(args, "memberID");
7452
7863
  if (status === undefined === (memberID === undefined))
7453
- throw new ArgError2(`give exactly one of "status" or "memberID"`);
7454
- const id = need6(args, "workspace");
7455
- const assignmentID = need6(args, "assignmentID");
7864
+ throw new ArgError3(`give exactly one of "status" or "memberID"`);
7865
+ const id = need7(args, "workspace");
7866
+ const assignmentID = need7(args, "assignmentID");
7456
7867
  const { assignment } = status !== undefined ? await hub.workspaces.setAssignmentStatus(id, assignmentID, status, needRevision(args), { signal }) : await hub.workspaces.reassign(id, assignmentID, memberID, needRevision(args), { signal });
7457
7868
  return `assignment ${assignment.id} → ${assignment.memberID}: ${assignment.status}, r${assignment.revision}`;
7458
7869
  }
@@ -7460,16 +7871,16 @@ var workspaceTools = [
7460
7871
  {
7461
7872
  name: "workspace_supervise",
7462
7873
  description: `Set who supervises a member (supervisor null detaches), under the supervision graph's revision (workspace_show). Acyclic, at most 6 levels. ${WS_SCOPES}`,
7463
- properties: { workspace: WORKSPACE, memberID: STRING5, supervisor: { type: ["string", "null"], description: "The supervising member's ID, or null to detach." }, order: { type: "number" }, expectedRevision: REVISION3 },
7874
+ properties: { workspace: WORKSPACE, memberID: STRING6, supervisor: { type: ["string", "null"], description: "The supervising member's ID, or null to detach." }, order: { type: "number" }, expectedRevision: REVISION4 },
7464
7875
  required: ["workspace", "memberID", "supervisor", "expectedRevision"],
7465
7876
  readOnly: false,
7466
7877
  run: async (hub, args, signal) => {
7467
- const supervisor = args.supervisor === null ? null : need6(args, "supervisor");
7878
+ const supervisor = args.supervisor === null ? null : need7(args, "supervisor");
7468
7879
  const order = args.order;
7469
7880
  if (order !== undefined && (typeof order !== "number" || !Number.isFinite(order)))
7470
- throw new ArgError2(`"order" must be a number`);
7471
- const r = await hub.workspaces.supervise(need6(args, "workspace"), { memberID: need6(args, "memberID"), supervisorMemberID: supervisor, ...order !== undefined ? { order } : {}, expectedRevision: needRevision(args) }, { signal });
7472
- return `${r.change}: ${need6(args, "memberID")}${r.link ? ` ← ${r.link.supervisorMemberID} (order ${r.link.order})` : ""}; supervision revision ${r.revision}${r.pruned ? `, ${r.pruned} stale link(s) dropped` : ""}`;
7881
+ throw new ArgError3(`"order" must be a number`);
7882
+ const r = await hub.workspaces.supervise(need7(args, "workspace"), { memberID: need7(args, "memberID"), supervisorMemberID: supervisor, ...order !== undefined ? { order } : {}, expectedRevision: needRevision(args) }, { signal });
7883
+ return `${r.change}: ${need7(args, "memberID")}${r.link ? ` ← ${r.link.supervisorMemberID} (order ${r.link.order})` : ""}; supervision revision ${r.revision}${r.pruned ? `, ${r.pruned} stale link(s) dropped` : ""}`;
7473
7884
  }
7474
7885
  },
7475
7886
  {
@@ -7479,7 +7890,7 @@ var workspaceTools = [
7479
7890
  required: ["workspace"],
7480
7891
  readOnly: true,
7481
7892
  run: async (hub, args, signal) => {
7482
- const v = await hub.workspaces.master(need6(args, "workspace"), { signal });
7893
+ const v = await hub.workspaces.master(need7(args, "workspace"), { signal });
7483
7894
  const line = (m) => `${m.member.id} ${m.member.sessionID} generation ${m.member.generation} ${m.presence}${m.member.leftReason ? ` (${m.member.leftReason})` : ""}`;
7484
7895
  return [
7485
7896
  `workspace ${v.workspaceID} r${v.revision}`,
@@ -7493,17 +7904,17 @@ var workspaceTools = [
7493
7904
  {
7494
7905
  name: "workspace_master_replace",
7495
7906
  description: `Replace (or first set) a workspace's master under the workspace revision: a session (ses_…) or a running launch's session (launchID). The old master departs as replaced; its supervised members move to the new one. No grants move: the new master's session needs its own grants. ${WS_SCOPES}`,
7496
- properties: { workspace: WORKSPACE, sessionID: STRING5, launchID: STRING5, expectedRevision: REVISION3, idempotencyKey: IDEMPOTENCY2 },
7907
+ properties: { workspace: WORKSPACE, sessionID: STRING6, launchID: STRING6, expectedRevision: REVISION4, idempotencyKey: IDEMPOTENCY3 },
7497
7908
  required: ["workspace", "expectedRevision"],
7498
7909
  readOnly: false,
7499
7910
  idempotent: true,
7500
7911
  run: async (hub, args, signal) => {
7501
- const sessionID = optional6(args, "sessionID");
7502
- const launchID = optional6(args, "launchID");
7912
+ const sessionID = optional7(args, "sessionID");
7913
+ const launchID = optional7(args, "launchID");
7503
7914
  if (sessionID === undefined === (launchID === undefined))
7504
- throw new ArgError2(`give exactly one of "sessionID" or "launchID"`);
7915
+ throw new ArgError3(`give exactly one of "sessionID" or "launchID"`);
7505
7916
  const target = sessionID !== undefined ? { sessionID } : { launchID };
7506
- const r = await hub.workspaces.replaceMaster(need6(args, "workspace"), { ...target, expectedRevision: needRevision(args) }, { ...idemOption(args), signal });
7917
+ const r = await hub.workspaces.replaceMaster(need7(args, "workspace"), { ...target, expectedRevision: needRevision(args) }, { ...idemOption(args), signal });
7507
7918
  if (!r.changed)
7508
7919
  return `${r.master.sessionID} already is the master (${r.master.id}); nothing changed`;
7509
7920
  return [
@@ -7521,7 +7932,7 @@ var workspaceTools = [
7521
7932
  required: ["workspace"],
7522
7933
  readOnly: true,
7523
7934
  run: async (hub, args, signal) => {
7524
- const b = await hub.workspaces.board(need6(args, "workspace"), { signal });
7935
+ const b = await hub.workspaces.board(need7(args, "workspace"), { signal });
7525
7936
  const more = (truncated) => truncated ? " (more not shown)" : "";
7526
7937
  const section = (name, s, line) => s.status === "ok" ? [`${name}:${more(s.truncated)}`, ...s.items.map((i) => ` ${line(i)}`)] : [`${name}: ${s.status}${s.status === "omitted" ? ` (needs ${s.scope})` : ""}`];
7527
7938
  const lines = [wsLine(b.workspace), ...b.workspace.masterMemberID ? [`master: ${b.workspace.masterMemberID}`] : []];
@@ -7546,6 +7957,7 @@ var TOOLS = [
7546
7957
  eventsTool,
7547
7958
  ...webhookTools,
7548
7959
  ...profileTools,
7960
+ ...profileLifecycleTools,
7549
7961
  ...workspaceTools,
7550
7962
  ...artifactTools,
7551
7963
  ...artifactJobTools,
@@ -7570,14 +7982,14 @@ var TOOLS = [
7570
7982
  name: "sessions",
7571
7983
  description: "The sessions as a flat list, optionally filtered.",
7572
7984
  properties: {
7573
- project: { ...STRING5, description: "Only this project." },
7574
- state: { ...STRING5, description: "Only these states (comma-separated, e.g. busy,blocked)." },
7575
- machine: { ...STRING5, description: "Only this machine." },
7576
- label: { type: "array", items: STRING5, description: "key=value labels that must all match." }
7985
+ project: { ...STRING6, description: "Only this project." },
7986
+ state: { ...STRING6, description: "Only these states (comma-separated, e.g. busy,blocked)." },
7987
+ machine: { ...STRING6, description: "Only this machine." },
7988
+ label: { type: "array", items: STRING6, description: "key=value labels that must all match." }
7577
7989
  },
7578
7990
  readOnly: true,
7579
7991
  run: async (hub, args, signal) => {
7580
- const body = await hub.sessions({ project: optional6(args, "project"), state: optional6(args, "state"), machine: optional6(args, "machine"), label: optionalStrings(args, "label"), signal });
7992
+ const body = await hub.sessions({ project: optional7(args, "project"), state: optional7(args, "state"), machine: optional7(args, "machine"), label: optionalStrings(args, "label"), signal });
7581
7993
  return sessionsText(body.sessions ?? []);
7582
7994
  }
7583
7995
  },
@@ -7587,7 +7999,7 @@ var TOOLS = [
7587
7999
  properties: { addr: ADDR2 },
7588
8000
  required: ["addr"],
7589
8001
  readOnly: true,
7590
- run: async (hub, args, signal) => showText(await hub.show(need6(args, "addr"), { signal }))
8002
+ run: async (hub, args, signal) => showText(await hub.show(need7(args, "addr"), { signal }))
7591
8003
  },
7592
8004
  {
7593
8005
  name: "log",
@@ -7596,7 +8008,7 @@ var TOOLS = [
7596
8008
  required: ["addr"],
7597
8009
  readOnly: true,
7598
8010
  run: async (hub, args, signal) => {
7599
- const addr = need6(args, "addr");
8011
+ const addr = need7(args, "addr");
7600
8012
  const n = optionalInt(args, "n", 1, 200);
7601
8013
  return logText((await hub.log(addr, { ...n !== undefined ? { n } : {}, signal })).rows ?? []);
7602
8014
  }
@@ -7605,25 +8017,25 @@ var TOOLS = [
7605
8017
  name: "who",
7606
8018
  description: "Named and role-holding sessions; a team member sees its teams, grouped by team with its reach to each.",
7607
8019
  properties: {
7608
- project: { ...STRING5, description: "Only this project." },
7609
- role: { ...STRING5, description: "Only holders of this role (reviewer, @@reviewer or reviewer#team)." },
7610
- team: { ...STRING5, description: "Only this team (name or ID)." }
8020
+ project: { ...STRING6, description: "Only this project." },
8021
+ role: { ...STRING6, description: "Only holders of this role (reviewer, @@reviewer or reviewer#team)." },
8022
+ team: { ...STRING6, description: "Only this team (name or ID)." }
7611
8023
  },
7612
8024
  readOnly: true,
7613
8025
  run: async (hub, args, signal) => {
7614
- const body = await hub.who({ project: optional6(args, "project"), role: optional6(args, "role"), team: optional6(args, "team"), signal });
8026
+ const body = await hub.who({ project: optional7(args, "project"), role: optional7(args, "role"), team: optional7(args, "team"), signal });
7615
8027
  return whoText(body.sessions ?? [], body);
7616
8028
  }
7617
8029
  },
7618
8030
  {
7619
8031
  name: "msg",
7620
8032
  description: "Tell a session something (fire and forget). Sent as this server's credential, not as an OpenCode session.",
7621
- properties: { to: TO, text: { ...STRING5, description: "The message." }, ...SEND },
8033
+ properties: { to: TO, text: { ...STRING6, description: "The message." }, ...SEND },
7622
8034
  required: ["to", "text"],
7623
8035
  readOnly: false,
7624
8036
  run: async (hub, args, signal) => {
7625
- const to = need6(args, "to");
7626
- const text = need6(args, "text");
8037
+ const to = need7(args, "to");
8038
+ const text = need7(args, "text");
7627
8039
  const options = { ...sendOptions(args), signal };
7628
8040
  if (optionalBool(args, "all"))
7629
8041
  return sentAllText(await hub.msg(to, text, { ...options, all: true }));
@@ -7635,7 +8047,7 @@ var TOOLS = [
7635
8047
  description: `Ask a session a question, tracked until answered, declined or expired. wait (at most ${WAIT_POLL_MAX_S} s) blocks for the answer; otherwise it shows up in the inbox tool.`,
7636
8048
  properties: {
7637
8049
  to: TO,
7638
- text: { ...STRING5, description: "The question." },
8050
+ text: { ...STRING6, description: "The question." },
7639
8051
  wait: { type: "number", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait for the answer (0–${WAIT_POLL_MAX_S}).` },
7640
8052
  expires: { type: ["number", "string"], description: "When the ask expires: seconds, or 30s, 5m, 2h, 1d (1 minute to 7 days; default 24 h)." },
7641
8053
  ...SEND
@@ -7643,14 +8055,14 @@ var TOOLS = [
7643
8055
  required: ["to", "text"],
7644
8056
  readOnly: false,
7645
8057
  run: async (hub, args, signal) => {
7646
- const to = need6(args, "to");
7647
- const text = need6(args, "text");
8058
+ const to = need7(args, "to");
8059
+ const text = need7(args, "text");
7648
8060
  const wait = optionalWait(args);
7649
8061
  const expires = optionalExpires(args);
7650
8062
  const options = { ...sendOptions(args), ...expires !== undefined ? { expires } : {}, signal };
7651
8063
  if (optionalBool(args, "all")) {
7652
8064
  if (wait !== undefined)
7653
- throw new ArgError2(`"wait" cannot be combined with "all"`);
8065
+ throw new ArgError3(`"wait" cannot be combined with "all"`);
7654
8066
  return sentAllText(await hub.ask(to, text, { ...options, all: true }));
7655
8067
  }
7656
8068
  const asked = await hub.ask(to, text, { ...options, ...wait !== undefined ? { wait } : {} });
@@ -7662,12 +8074,12 @@ ${openedText(asked.decision)}` : outcome;
7662
8074
  {
7663
8075
  name: "reply",
7664
8076
  description: "Reply to a message you received; it goes to the message's real sender.",
7665
- properties: { id: MSG_ID, text: { ...STRING5, description: "The reply." }, wake: SEND.wake },
8077
+ properties: { id: MSG_ID, text: { ...STRING6, description: "The reply." }, wake: SEND.wake },
7666
8078
  required: ["id", "text"],
7667
8079
  readOnly: false,
7668
8080
  run: async (hub, args, signal) => {
7669
- const id = need6(args, "id");
7670
- const text = need6(args, "text");
8081
+ const id = need7(args, "id");
8082
+ const text = need7(args, "text");
7671
8083
  const wake = optionalBool(args, "wake");
7672
8084
  return repliedText(await hub.reply(id, text, { ...wake !== undefined ? { wake } : {}, signal }));
7673
8085
  }
@@ -7675,12 +8087,12 @@ ${openedText(asked.decision)}` : outcome;
7675
8087
  {
7676
8088
  name: "decline",
7677
8089
  description: "Decline an ask you received, with a reason.",
7678
- properties: { id: MSG_ID, why: { ...STRING5, description: "The reason." } },
8090
+ properties: { id: MSG_ID, why: { ...STRING6, description: "The reason." } },
7679
8091
  required: ["id", "why"],
7680
8092
  readOnly: false,
7681
8093
  run: async (hub, args, signal) => {
7682
- const id = need6(args, "id");
7683
- return declinedText(await hub.decline(id, need6(args, "why"), { signal }));
8094
+ const id = need7(args, "id");
8095
+ return declinedText(await hub.decline(id, need7(args, "why"), { signal }));
7684
8096
  }
7685
8097
  },
7686
8098
  {
@@ -7700,7 +8112,7 @@ ${openedText(asked.decision)}` : outcome;
7700
8112
  required: ["id"],
7701
8113
  readOnly: true,
7702
8114
  run: async (hub, args, signal) => {
7703
- const id = need6(args, "id");
8115
+ const id = need7(args, "id");
7704
8116
  const wait = optionalWait(args);
7705
8117
  return messageText(await hub.message(id, { ...wait !== undefined ? { wait } : {}, signal }));
7706
8118
  }
@@ -7713,7 +8125,7 @@ ${openedText(asked.decision)}` : outcome;
7713
8125
  readOnly: false,
7714
8126
  organizes: true,
7715
8127
  idempotent: true,
7716
- run: async (hub, args, signal) => mutedText((await hub[verb](need6(args, "addr"), { signal })).muted)
8128
+ run: async (hub, args, signal) => mutedText((await hub[verb](need7(args, "addr"), { signal })).muted)
7717
8129
  })),
7718
8130
  {
7719
8131
  name: "decisions",
@@ -7721,9 +8133,9 @@ ${openedText(asked.decision)}` : outcome;
7721
8133
  properties: { state: { type: "string", enum: [...DECISION_STATES], description: "Only Decisions in this state." } },
7722
8134
  readOnly: true,
7723
8135
  run: async (hub, args, signal) => {
7724
- const state = optional6(args, "state");
8136
+ const state = optional7(args, "state");
7725
8137
  if (state !== undefined && !DECISION_STATES.includes(state))
7726
- throw new ArgError2(`"state" is one of ${DECISION_STATES.join(", ")}`);
8138
+ throw new ArgError3(`"state" is one of ${DECISION_STATES.join(", ")}`);
7727
8139
  const { decisions } = await hub.decisions({ ...state ? { state } : {}, signal });
7728
8140
  return decisions.length ? `${DECISIONS_NOTE}
7729
8141
  ${decisionsText(decisions)}` : decisionsText(decisions);
@@ -7736,7 +8148,7 @@ ${decisionsText(decisions)}` : decisionsText(decisions);
7736
8148
  required: ["id"],
7737
8149
  readOnly: true,
7738
8150
  run: async (hub, args, signal) => `${DECISIONS_NOTE}
7739
- ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
8151
+ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
7740
8152
  },
7741
8153
  {
7742
8154
  name: "approve",
@@ -7745,8 +8157,8 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7745
8157
  required: ["addr", "requestID"],
7746
8158
  readOnly: false,
7747
8159
  run: async (hub, args, signal) => {
7748
- const addr = need6(args, "addr");
7749
- const requestID = need6(args, "requestID");
8160
+ const addr = need7(args, "addr");
8161
+ const requestID = need7(args, "requestID");
7750
8162
  const always = optionalBool(args, "always");
7751
8163
  return approvedText(await hub.approve(addr, requestID, { ...always ? { always } : {}, ...whyOption(args), signal }));
7752
8164
  }
@@ -7758,8 +8170,8 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7758
8170
  required: ["addr", "requestID"],
7759
8171
  readOnly: false,
7760
8172
  run: async (hub, args, signal) => {
7761
- const addr = need6(args, "addr");
7762
- const requestID = need6(args, "requestID");
8173
+ const addr = need7(args, "addr");
8174
+ const requestID = need7(args, "requestID");
7763
8175
  return deniedText(await hub.deny(addr, requestID, { ...whyOption(args), signal }));
7764
8176
  }
7765
8177
  },
@@ -7776,8 +8188,8 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7776
8188
  required: ["addr", "formID"],
7777
8189
  readOnly: false,
7778
8190
  run: async (hub, args, signal) => {
7779
- const addr = need6(args, "addr");
7780
- const formID = need6(args, "formID");
8191
+ const addr = need7(args, "addr");
8192
+ const formID = need7(args, "formID");
7781
8193
  const given = formAnswer(args);
7782
8194
  const options = { ...whyOption(args), signal };
7783
8195
  return answeredText(given === "cancel" ? await hub.cancelForm(addr, formID, options) : await hub.answer(addr, formID, given, options), given === "cancel");
@@ -7788,15 +8200,15 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7788
8200
  description: `Prompt a session (an agent's prompt carries the hub's provenance). ${NEEDS_USER}`,
7789
8201
  properties: {
7790
8202
  addr: ADDR2,
7791
- text: { ...STRING5, description: "The prompt." },
8203
+ text: { ...STRING6, description: "The prompt." },
7792
8204
  delivery: { type: "string", enum: [...DELIVERIES], description: "How it enters a busy session (the spoke picks when unset)." },
7793
8205
  why: WHY
7794
8206
  },
7795
8207
  required: ["addr", "text"],
7796
8208
  readOnly: false,
7797
8209
  run: async (hub, args, signal) => {
7798
- const addr = need6(args, "addr");
7799
- const text = need6(args, "text");
8210
+ const addr = need7(args, "addr");
8211
+ const text = need7(args, "text");
7800
8212
  const delivery = optionalDelivery(args);
7801
8213
  return promptedText(await hub.prompt(addr, text, { ...delivery ? { delivery } : {}, ...whyOption(args), signal }));
7802
8214
  }
@@ -7807,32 +8219,32 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7807
8219
  properties: { addr: ADDR2, why: WHY },
7808
8220
  required: ["addr"],
7809
8221
  readOnly: false,
7810
- run: async (hub, args, signal) => stoppedText(await hub.stop(need6(args, "addr"), { ...whyOption(args), signal }))
8222
+ run: async (hub, args, signal) => stoppedText(await hub.stop(need7(args, "addr"), { ...whyOption(args), signal }))
7811
8223
  },
7812
8224
  {
7813
8225
  name: "launch",
7814
8226
  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.",
7815
8227
  properties: {
7816
- repo: { ...STRING5, description: "Absolute path of the repository on the node (or where a clone goes)." },
8228
+ repo: { ...STRING6, description: "Absolute path of the repository on the node (or where a clone goes)." },
7817
8229
  mode: { type: "string", enum: [...WORKSPACE_MODES], description: "worktree (default: <repo>/.worktrees/<branch>, needs branch), checkout, or clone (needs remote)." },
7818
- branch: { ...STRING5, description: "The branch to create or check out." },
7819
- base: { ...STRING5, description: "What a new branch starts from." },
7820
- remote: { ...STRING5, description: "An https or ssh git remote (for clone)." },
7821
- key: { ...STRING5, description: "Idempotency key: retrying with the same key and arguments answers the same launch." },
7822
- name: { ...STRING5, description: "The session's @name." },
7823
- roles: { type: "array", items: STRING5, description: "Roles the session claims (e.g. reviewer.hub)." },
7824
- harness: { ...STRING5, description: "The harness (default opencode)." },
7825
- agent: { ...STRING5, description: "The harness agent (e.g. build)." },
7826
- model: { ...STRING5, description: "provider/model." },
8230
+ branch: { ...STRING6, description: "The branch to create or check out." },
8231
+ base: { ...STRING6, description: "What a new branch starts from." },
8232
+ remote: { ...STRING6, description: "An https or ssh git remote (for clone)." },
8233
+ key: { ...STRING6, description: "Idempotency key: retrying with the same key and arguments answers the same launch." },
8234
+ name: { ...STRING6, description: "The session's @name." },
8235
+ roles: { type: "array", items: STRING6, description: "Roles the session claims (e.g. reviewer.hub)." },
8236
+ harness: { ...STRING6, description: "The harness (default opencode)." },
8237
+ agent: { ...STRING6, description: "The harness agent (e.g. build)." },
8238
+ model: { ...STRING6, description: "provider/model." },
7827
8239
  runtime: { type: "string", enum: [...RUNTIME_KINDS], description: "host (default) or openshell (a sandbox)." },
7828
- profile: { ...STRING5, description: "The runtime's sandbox profile (needs runtime)." },
7829
- machine: { ...STRING5, description: "The machine (name or ID) to run on." },
8240
+ profile: { ...STRING6, description: "The runtime's sandbox profile (needs runtime)." },
8241
+ machine: { ...STRING6, description: "The machine (name or ID) to run on." },
7830
8242
  nodeLabels: { ...LABELS, description: 'Node labels to place on (e.g. {"os": "darwin"}).' },
7831
8243
  prefer: { type: "boolean", description: "machine and nodeLabels are preferences, not requirements." },
7832
8244
  labels: { ...LABELS, description: "Free-form labels for finding the launch and its session later (never interpreted by the hub)." },
7833
- prompt: { ...STRING5, description: "The session's first prompt." },
8245
+ prompt: { ...STRING6, description: "The session's first prompt." },
7834
8246
  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." },
7835
- timeout: { ...STRING5, description: "Longest run, e.g. 4h or 90m." },
8247
+ timeout: { ...STRING6, description: "Longest run, e.g. 4h or 90m." },
7836
8248
  cleanup: { type: "string", enum: [...CLEANUP_POLICIES], description: "keep (default) or remove the workspace when it ends." }
7837
8249
  },
7838
8250
  required: ["repo"],
@@ -7847,12 +8259,12 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7847
8259
  name: "launches",
7848
8260
  description: "Your launches, oldest first (an app or session sees the ones it started).",
7849
8261
  properties: {
7850
- state: { ...STRING5, description: "Only these states (comma-separated: queued, placing, provisioning, running, ended)." },
7851
- label: { type: "array", items: STRING5, description: "key=value launch labels that must all match." }
8262
+ state: { ...STRING6, description: "Only these states (comma-separated: queued, placing, provisioning, running, ended)." },
8263
+ label: { type: "array", items: STRING6, description: "key=value launch labels that must all match." }
7852
8264
  },
7853
8265
  readOnly: true,
7854
8266
  run: async (hub, args, signal) => {
7855
- const state = optional6(args, "state")?.split(",").filter(Boolean);
8267
+ const state = optional7(args, "state")?.split(",").filter(Boolean);
7856
8268
  const label = optionalStrings(args, "label");
7857
8269
  return launchesText((await hub.launches.list({ ...state?.length ? { state } : {}, ...label ? { label } : {}, signal })).launches ?? []);
7858
8270
  }
@@ -7863,7 +8275,7 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7863
8275
  properties: { id: LAUNCH_ID },
7864
8276
  required: ["id"],
7865
8277
  readOnly: true,
7866
- run: async (hub, args, signal) => launchText((await hub.launches.get(need6(args, "id"), { signal })).launch)
8278
+ run: async (hub, args, signal) => launchText((await hub.launches.get(need7(args, "id"), { signal })).launch)
7867
8279
  },
7868
8280
  {
7869
8281
  name: "launch_stop",
@@ -7871,7 +8283,7 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7871
8283
  properties: { id: LAUNCH_ID },
7872
8284
  required: ["id"],
7873
8285
  readOnly: false,
7874
- run: async (hub, args, signal) => stoppedLaunchText((await hub.launches.stop(need6(args, "id"), { signal })).launch)
8286
+ run: async (hub, args, signal) => stoppedLaunchText((await hub.launches.stop(need7(args, "id"), { signal })).launch)
7875
8287
  }
7876
8288
  ];
7877
8289
  var toolList = () => TOOLS.map((t) => ({
@@ -7938,7 +8350,7 @@ function createMcpServer(options) {
7938
8350
  } catch (cause) {
7939
8351
  if (controller.signal.aborted)
7940
8352
  return;
7941
- const text = cause instanceof ArgError2 ? `invalid: ${cause.message}` : cause instanceof CliError ? errorText(cause) : `${tool.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`;
8353
+ const text = cause instanceof ToolArgError ? `invalid: ${cause.message}` : cause instanceof CliError ? errorText(cause) : `${tool.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`;
7942
8354
  return ok2(id, toolResult(clean(text, token), true));
7943
8355
  } finally {
7944
8356
  if (inFlight.get(key(id)) === controller)
@@ -8375,7 +8787,7 @@ import { arch as osArch, platform as osPlatform } from "node:os";
8375
8787
  // packages/node/package.json
8376
8788
  var package_default2 = {
8377
8789
  name: "@huynle/hub-node",
8378
- version: "0.8.0",
8790
+ version: "0.8.2",
8379
8791
  private: true,
8380
8792
  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).",
8381
8793
  license: "MIT",
@@ -9467,7 +9879,7 @@ import path13 from "node:path";
9467
9879
  // packages/runtime-openshell/package.json
9468
9880
  var package_default3 = {
9469
9881
  name: "@huynle/hub-runtime-openshell",
9470
- version: "0.8.0",
9882
+ version: "0.8.2",
9471
9883
  private: true,
9472
9884
  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).",
9473
9885
  license: "MIT",
@@ -10031,9 +10443,10 @@ var openCodeServiceFile = (env, home) => path14.join(env.XDG_STATE_HOME || path1
10031
10443
  function startOpenCodeWarmers(config, service, log, signal) {
10032
10444
  const roots = () => config.workspaceRoots;
10033
10445
  const directories = config.opencode?.directories;
10034
- new OpenCodeWarmer({ discover: service, log, roots, ...directories ? { directories } : {} }).start(signal);
10446
+ const timeout = config.opencode?.timeoutSeconds !== undefined ? { timeoutMs: config.opencode.timeoutSeconds * 1000 } : {};
10447
+ new OpenCodeWarmer({ discover: service, log, roots, ...timeout, ...directories ? { directories } : {} }).start(signal);
10035
10448
  for (const server of config.opencode?.servers ?? []) {
10036
- new OpenCodeWarmer({ discover: serverEndpoint(server), log, name: "the OpenCode server", roots, ...server.directories ? { directories: server.directories } : {} }).start(signal);
10449
+ new OpenCodeWarmer({ discover: serverEndpoint(server), log, name: "the OpenCode server", roots, ...timeout, ...server.directories ? { directories: server.directories } : {} }).start(signal);
10037
10450
  }
10038
10451
  }
10039
10452
  async function setupOpenShell(config, credentials, options, env, log) {
@@ -10414,8 +10827,8 @@ var EMBEDDED_TEXT = Object.freeze({
10414
10827
  // packages/cli/src/bootstrap/adapters/node-services/render.ts
10415
10828
  var SERVICE_ENVIRONMENT = Object.freeze({
10416
10829
  node: ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_CLAUDE_BIN", "HUB_CODEX_BIN"],
10417
- "claude-code": ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR", "HUB_CLAUDE_SOCKET", "HUB_CLAUDE_BIN"],
10418
- "codex-spoke": ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR", "CODEX_HOME", "HUB_CODEX_BIN", "HUB_CODEX_DAEMON_SOCKET"]
10830
+ "claude-code": ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR", "XDG_STATE_HOME", "HUB_CLAUDE_SOCKET", "HUB_CLAUDE_BIN"],
10831
+ "codex-spoke": ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR", "XDG_STATE_HOME", "CODEX_HOME", "HUB_CODEX_BIN", "HUB_CODEX_DAEMON_SOCKET"]
10419
10832
  });
10420
10833
  var UNIT_ROOT = Object.freeze({ launchd: "launch-agents", "systemd-user": "systemd-user" });
10421
10834
  var assetIDFor = (service, manager) => `service/${manager}/${service}`;
@@ -11008,6 +11421,15 @@ function wsURL2(server) {
11008
11421
  var defaultSocket2 = (url, headers) => new WebSocket(url, { headers });
11009
11422
  var message2 = (cause) => cause instanceof Error ? cause.message : String(cause);
11010
11423
 
11424
+ class CodedError extends Error {
11425
+ code;
11426
+ constructor(message, code) {
11427
+ super(message);
11428
+ this.code = code;
11429
+ this.name = "CodedError";
11430
+ }
11431
+ }
11432
+
11011
11433
  class SpokeConnection {
11012
11434
  options;
11013
11435
  state = "off";
@@ -11177,7 +11599,7 @@ class SpokeConnection {
11177
11599
  try {
11178
11600
  reply = { t: "res", id, ok: true, result: await this.options.handle(req) };
11179
11601
  } catch (cause) {
11180
- reply = { t: "res", id, ok: false, error: message2(cause) };
11602
+ reply = { t: "res", id, ok: false, error: message2(cause), ...cause instanceof CodedError ? { code: cause.code } : {} };
11181
11603
  }
11182
11604
  this.send(reply);
11183
11605
  }
@@ -11358,7 +11780,7 @@ function tail(s) {
11358
11780
  const asks = s.openAsks ? `, ${s.openAsks} open ask${s.openAsks === 1 ? "" : "s"} from you` : "";
11359
11781
  return `${clean2(s.state) || "unknown"}${asks}`;
11360
11782
  }
11361
- function one2(s) {
11783
+ function one3(s) {
11362
11784
  const more = details(s);
11363
11785
  if (s.self)
11364
11786
  return `this session (${[s.sessionID, ...more].join(" · ")}), ${tail(s)}`;
@@ -11371,7 +11793,7 @@ function member(s) {
11371
11793
  }
11372
11794
  function renderReferences(refs) {
11373
11795
  const lines = refs.map((r) => {
11374
- const body = r.sessions.length === 1 ? one2(r.sessions[0]) : `${r.sessions.length} sessions: ${r.sessions.map(member).join("; ")}`;
11796
+ const body = r.sessions.length === 1 ? one3(r.sessions[0]) : `${r.sessions.length} sessions: ${r.sessions.map(member).join("; ")}`;
11375
11797
  return `- ${r.address} = ${body}.`;
11376
11798
  });
11377
11799
  const single = refs.length === 1 && refs[0].sessions.length === 1;
@@ -11841,7 +12263,7 @@ import { homedir as homedir5 } from "node:os";
11841
12263
  import path17 from "node:path";
11842
12264
  var SOCKET_PATH_MAX = 103;
11843
12265
  function claudeStateDir(env = process.env, home = homedir5()) {
11844
- return path17.join(hubEnv(env).HUB_STATE_DIR || path17.join(stateBase(home), APP_DIR), "claude-code");
12266
+ return path17.join(resolveHubStateDir(env, home).dir, "claude-code");
11845
12267
  }
11846
12268
  function socketPath(env = process.env, home = homedir5()) {
11847
12269
  return env.HUB_CLAUDE_SOCKET || path17.join(claudeStateDir(env, home), "daemon.sock");
@@ -13141,7 +13563,7 @@ import path22 from "node:path";
13141
13563
  // packages/claude-code/package.json
13142
13564
  var package_default4 = {
13143
13565
  name: "@huynle/hub-claude-code",
13144
- version: "0.8.0",
13566
+ version: "0.8.2",
13145
13567
  private: true,
13146
13568
  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).",
13147
13569
  license: "MIT",
@@ -13430,7 +13852,7 @@ import path25 from "node:path";
13430
13852
  // packages/preset/package.json
13431
13853
  var package_default5 = {
13432
13854
  name: "@huynle/hub-preset",
13433
- version: "0.8.0",
13855
+ version: "0.8.2",
13434
13856
  private: true,
13435
13857
  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).",
13436
13858
  license: "MIT",
@@ -13955,7 +14377,7 @@ import { openSync as openSync5, closeSync as closeSync5, readFileSync as readFil
13955
14377
  // packages/codex/package.json
13956
14378
  var package_default6 = {
13957
14379
  name: "@huynle/hub-codex",
13958
- version: "0.8.0",
14380
+ version: "0.8.2",
13959
14381
  private: true,
13960
14382
  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).",
13961
14383
  license: "MIT",
@@ -13980,7 +14402,7 @@ function daemonSocketPath(env = process.env, home = homedir6()) {
13980
14402
  return override || path26.join(codexHome(env, home), "app-server-control", "app-server-control.sock");
13981
14403
  }
13982
14404
  function codexStateDir(env = process.env, home = homedir6()) {
13983
- return path26.join(hubEnv(env).HUB_STATE_DIR || path26.join(stateBase(home), APP_DIR), "codex");
14405
+ return path26.join(resolveHubStateDir(env, home).dir, "codex");
13984
14406
  }
13985
14407
  var pidFile = (env = process.env, home = homedir6()) => path26.join(codexStateDir(env, home), "spoke.pid");
13986
14408
  var isPid = (n) => typeof n === "number" && Number.isInteger(n) && n > 1;
@@ -15374,7 +15796,7 @@ async function codexMain(argv, io) {
15374
15796
  import path30 from "node:path";
15375
15797
  var SERVICE_VERBS = ["install", "uninstall", "status"];
15376
15798
  var isServiceVerb = (verb) => SERVICE_VERBS.includes(verb ?? "");
15377
- var HUB_PLACES = ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR"];
15799
+ var HUB_PLACES = ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR", "XDG_STATE_HOME"];
15378
15800
  var CLAUDE_CODE_SERVICE = {
15379
15801
  service: "claude-code",
15380
15802
  command: "hub claude-code",
@@ -15516,7 +15938,7 @@ async function runCodex(args, given) {
15516
15938
  // packages/cli/src/setup/commands.ts
15517
15939
  import { existsSync as existsSync9, readFileSync as readFileSync12, renameSync as renameSync7, writeFileSync as writeFileSync8 } from "node:fs";
15518
15940
  import { hostname as hostname4 } from "node:os";
15519
- import path33 from "node:path";
15941
+ import path34 from "node:path";
15520
15942
 
15521
15943
  // packages/cli/src/bootstrap/transaction/result.ts
15522
15944
  var txOk = (value) => ({ ok: true, value });
@@ -17049,6 +17471,7 @@ function installOf(probe, id, shim, roots, context = "host") {
17049
17471
  var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17050
17472
 
17051
17473
  // packages/cli/src/bootstrap/discovery/host.ts
17474
+ import path32 from "node:path";
17052
17475
  function discoverRoots(probe) {
17053
17476
  const home = normalizeAbs(probe.env.HOME) ?? normalizeAbs(probe.home);
17054
17477
  if (!home)
@@ -17058,7 +17481,7 @@ function discoverRoots(probe) {
17058
17481
  const paths = {
17059
17482
  home,
17060
17483
  config,
17061
- state: xdg("XDG_STATE_HOME", ".local/state"),
17484
+ state: hubStateRoot(probe, home) ?? xdg("XDG_STATE_HOME", ".local/state"),
17062
17485
  data: xdg("XDG_DATA_HOME", ".local/share"),
17063
17486
  "user-bin": join(home, ".local/bin"),
17064
17487
  "system-bin": "/usr/local/bin"
@@ -17069,6 +17492,12 @@ function discoverRoots(probe) {
17069
17492
  paths["systemd-user"] = join(config, "systemd/user");
17070
17493
  return ROOT_IDS.flatMap((id) => paths[id] ? [{ id, path: paths[id] }] : []);
17071
17494
  }
17495
+ function hubStateRoot(probe, home) {
17496
+ const env = { ...probe.env, ...probe.env.XDG_STATE_HOME !== undefined ? { XDG_STATE_HOME: normalizeAbs(probe.env.XDG_STATE_HOME) } : {} };
17497
+ const { dir } = resolveHubStateDir(env, home, (p) => probe.lstat(p) !== "missing");
17498
+ const abs = normalizeAbs(dir);
17499
+ return abs && path32.posix.basename(abs) === "hub" ? path32.posix.dirname(abs) : undefined;
17500
+ }
17072
17501
  var unescapeMount = (s) => s.replace(/\\([0-7]{3})/g, (_, o) => String.fromCharCode(parseInt(o, 8)));
17073
17502
  function parseMountinfo(text) {
17074
17503
  const mounts = [];
@@ -20448,7 +20877,7 @@ function createCodexAdapter(options) {
20448
20877
 
20449
20878
  // packages/cli/src/bootstrap/adapters/node-services/control.ts
20450
20879
  import { spawn as nodeSpawn } from "node:child_process";
20451
- import path32 from "node:path";
20880
+ import path33 from "node:path";
20452
20881
  var HEALTH_WINDOW = Object.freeze({ timeoutMs: 1e4, stableMs: 3000, pollMs: 500 });
20453
20882
  var realClock = { now: () => Date.now(), sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
20454
20883
  var UNITS = new Set(SERVICE_IDS.map((s) => serviceName(s, "systemd-user")));
@@ -20488,7 +20917,7 @@ function checkManagerCall(call) {
20488
20917
  if (verb === "print" || verb === "bootout")
20489
20918
  return words.length === 2 && LABELS2.has(labelOfTarget(target) ?? "");
20490
20919
  if (verb === "bootstrap")
20491
- return words.length === 3 && DOMAIN.test(target) && isAbsolutePath(file) && LABELS2.has(path32.basename(file).replace(/\.plist$/, "")) && file.endsWith(".plist");
20920
+ return words.length === 3 && DOMAIN.test(target) && isAbsolutePath(file) && LABELS2.has(path33.basename(file).replace(/\.plist$/, "")) && file.endsWith(".plist");
20492
20921
  return false;
20493
20922
  }
20494
20923
  return false;
@@ -20690,7 +21119,7 @@ var defaultSpawn = (file, words, options) => new Promise((resolve) => {
20690
21119
  function spawnManagerRunner(options) {
20691
21120
  for (const program of ["systemctl", "launchctl"]) {
20692
21121
  const file = options.programs[program];
20693
- if (!isAbsolutePath(file) || path32.basename(file) !== program)
21122
+ if (!isAbsolutePath(file) || path33.basename(file) !== program)
20694
21123
  throw new Error(`${program} must be given as an absolute path to ${program}`);
20695
21124
  }
20696
21125
  const env = {};
@@ -23164,7 +23593,7 @@ async function profileArg(io, value, fallback) {
23164
23593
  return unwrapProfile(defaultProfile(value));
23165
23594
  if (value !== "-" && !value.includes("/") && !value.endsWith(".json"))
23166
23595
  throw new Refusal("usage", `unknown profile ${value}: use ${PROFILE_IDS.join(", ")}, a profile file, or - for stdin`);
23167
- const text = value === "-" ? await (io.stdin?.() ?? Promise.reject(new Refusal("usage", "cannot read the profile from stdin here"))) : readText2(path33.resolve(io.cwd ?? process.cwd(), value), "profile");
23596
+ const text = value === "-" ? await (io.stdin?.() ?? Promise.reject(new Refusal("usage", "cannot read the profile from stdin here"))) : readText2(path34.resolve(io.cwd ?? process.cwd(), value), "profile");
23168
23597
  const loaded = loadProfile(parseJSON2(text, "profile"));
23169
23598
  if (!loaded.ok)
23170
23599
  throw issueRefusal(loaded.issues);
@@ -23178,7 +23607,7 @@ function unwrapProfile(r) {
23178
23607
  function manifestArg(io, value) {
23179
23608
  if (value === undefined)
23180
23609
  return;
23181
- const parsed = parseManifest(readText2(path33.resolve(io.cwd ?? process.cwd(), value), "manifest"));
23610
+ const parsed = parseManifest(readText2(path34.resolve(io.cwd ?? process.cwd(), value), "manifest"));
23182
23611
  if (!parsed.ok)
23183
23612
  throw new Refusal("signature_invalid", issuesText(parsed.issues));
23184
23613
  const root = shippedTrustRoot(io.bootstrap?.trustRoot);
@@ -23204,10 +23633,23 @@ function readState(snapshot) {
23204
23633
  throw new Refusal("recovery_required", error.message);
23205
23634
  }
23206
23635
  }
23636
+ function launchCredentials(io) {
23637
+ let warned = false;
23638
+ return () => {
23639
+ const found = resolveCredentials(io.env, io.home);
23640
+ if (found?.source === "login" && !warned && isInsecureStoredURL(found.url)) {
23641
+ warned = true;
23642
+ io.stderr(`hubctl: warning: ${storedInsecureWarning(found.url)}
23643
+ `);
23644
+ }
23645
+ return found;
23646
+ };
23647
+ }
23207
23648
  function launchDriverFor(io) {
23208
23649
  const store = credentialStoreFor(io.env, io.home);
23650
+ const login = launchCredentials(io);
23209
23651
  const client = () => {
23210
- const credentials = resolveCredentials(io.env, io.home);
23652
+ const credentials = login();
23211
23653
  if (!credentials)
23212
23654
  throw new Error("not logged in to the hub (run `hub login --scope sessions:launch`; an app token launches under a grant with verb launch)");
23213
23655
  return clientFor(credentials);
@@ -23262,7 +23704,7 @@ function choicesOf(io, flags, profile, snapshot) {
23262
23704
  const enrollment = str(flags, "enrollment");
23263
23705
  if (enrollment !== undefined && !ENROLLMENT_MODES.includes(enrollment))
23264
23706
  throw new Refusal("usage", `--enrollment is ${ENROLLMENT_MODES.join(" or ")}`);
23265
- let roots = flags["workspace-root"]?.map((r) => path33.resolve(io.cwd ?? process.cwd(), r));
23707
+ let roots = flags["workspace-root"]?.map((r) => path34.resolve(io.cwd ?? process.cwd(), r));
23266
23708
  if (!roots?.length && profile.services.includes("node")) {
23267
23709
  const existing = readNodeConfig(nodeConfigPath(io.env, io.home)).config.workspaceRoots;
23268
23710
  if (existing.length > 0)
@@ -23374,7 +23816,7 @@ async function preview3(io, flags, json) {
23374
23816
  const out = str(flags, "out");
23375
23817
  if (out) {
23376
23818
  const request = { schema: "hub.setup-request", version: 1, preview: r.value, profile, choices };
23377
- const file = path33.resolve(io.cwd ?? process.cwd(), out);
23819
+ const file = path34.resolve(io.cwd ?? process.cwd(), out);
23378
23820
  const tmp = `${file}.${process.pid}.tmp`;
23379
23821
  writeFileSync8(tmp, `${JSON.stringify(request, null, 2)}
23380
23822
  `, { mode: 384 });
@@ -23387,7 +23829,7 @@ async function preview3(io, flags, json) {
23387
23829
  function readRequest(io, file) {
23388
23830
  if (!file)
23389
23831
  throw new UsageError("needs --preview <file> (from hubctl setup preview --out <file>)");
23390
- const raw = parseJSON2(readText2(path33.resolve(io.cwd ?? process.cwd(), file), "preview"), "preview", "preview_invalid");
23832
+ const raw = parseJSON2(readText2(path34.resolve(io.cwd ?? process.cwd(), file), "preview"), "preview", "preview_invalid");
23391
23833
  const r = validateSetupRequest(raw);
23392
23834
  if (!r.ok)
23393
23835
  throw new Refusal("preview_invalid", `${file} is not a hubctl setup preview: ${issuesText(r.issues)}`, "Preview again: hubctl setup preview --out <file>");
@@ -23563,7 +24005,7 @@ function observe(plan, snapshot) {
23563
24005
  continue;
23564
24006
  const server = file.exists ? at(file.value, `/mcp/servers/${f.registration.name}`) : { found: false };
23565
24007
  const command = server.value?.command;
23566
- const ours = Array.isArray(command) && command[1] === "mcp" && typeof command[0] === "string" && ["hub", "hubctl"].includes(path33.basename(command[0]));
24008
+ const ours = Array.isArray(command) && command[1] === "mcp" && typeof command[0] === "string" && ["hub", "hubctl"].includes(path34.basename(command[0]));
23567
24009
  registrations.push({ ...f.registration, state: !server.found ? "absent" : ours ? "ours" : "foreign" });
23568
24010
  }
23569
24011
  }
@@ -23696,7 +24138,7 @@ async function ansible(io, flags, json) {
23696
24138
  const planFile = str(flags, "plan");
23697
24139
  let plan;
23698
24140
  if (planFile) {
23699
- const raw = parseJSON2(readText2(path33.resolve(io.cwd ?? process.cwd(), planFile), "plan"), "plan");
24141
+ const raw = parseJSON2(readText2(path34.resolve(io.cwd ?? process.cwd(), planFile), "plan"), "plan");
23700
24142
  plan = raw.schema === "hub.setup-request" ? raw.preview?.plan : raw.schema === "hub.setup-preview" ? raw.plan : raw;
23701
24143
  }
23702
24144
  const r = renderRoleVars({
@@ -23917,7 +24359,7 @@ Options: --json (raw responses), --help, --version
23917
24359
  Addresses: ses_… (or an unambiguous prefix), @name, @@role, @@role#team (quote it: '@@dev#team'), @name.project, @me (the user)
23918
24360
  Credentials: HUB_URL + HUB_SESSION_TOKEN (set in a session's shells), else the \`hub login\` file.
23919
24361
  Exit codes: 0 ok, 1 other, 2 usage, 3 not found/ambiguous/taken, 4 needs user, 5 forbidden/paused,
23920
- 6 rate limited, 7 unsupported/offline, 8 gone, 9 unauthorized/expired`;
24362
+ 6 rate limited, 7 unsupported/offline/busy, 8 gone, 9 unauthorized/expired`;
23921
24363
  var out = (io, text) => io.stdout(text.endsWith(`
23922
24364
  `) ? text : `${text}
23923
24365
  `);
@@ -24400,7 +24842,7 @@ ${openedText(body.decision)}` : outcome.text);
24400
24842
  const server = checkServerURL(url, allow);
24401
24843
  if (!server) {
24402
24844
  const why = insecureHTTPRefusal(url, allow);
24403
- throw new UsageError(why ? `${why}: ${url.slice(0, 200)}` : `the hub URL must be https (plain http only for a loopback hub), without user info, query or fragment: ${url.slice(0, 200)}`);
24845
+ throw new UsageError(why ? `${why}: ${shownURL(url)}` : `the hub URL must be https (plain http only for a loopback hub), without user info, query or fragment: ${shownURL(url)}`);
24404
24846
  }
24405
24847
  if (isPlainHTTP(server) && !isLoopbackServer(server))
24406
24848
  io.stderr(`hub: warning: ${insecureHTTPWarning(server)}