@huynle/hub 0.8.0 → 0.8.1

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 (112) hide show
  1. package/CHANGELOG.md +213 -1
  2. package/README.md +84 -15
  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 +649 -242
  7. package/packages/cli/package.json +1 -1
  8. package/packages/cli/src/bootstrap/adapters/node-services/render.ts +2 -2
  9. package/packages/cli/src/bootstrap/discovery/host.ts +15 -1
  10. package/packages/cli/src/bootstrap/discovery/probe.ts +1 -1
  11. package/packages/cli/src/bootstrap/enrollment/enroll.ts +3 -3
  12. package/packages/cli/src/bootstrap/enrollment/policy.ts +13 -0
  13. package/packages/cli/src/cli.ts +3 -3
  14. package/packages/cli/src/http.ts +1 -0
  15. package/packages/cli/src/launches.ts +9 -3
  16. package/packages/cli/src/login.ts +2 -2
  17. package/packages/cli/src/mcp.ts +7 -3
  18. package/packages/cli/src/messaging.ts +4 -2
  19. package/packages/cli/src/profiles-mcp.ts +301 -0
  20. package/packages/cli/src/setup/commands.ts +20 -2
  21. package/packages/cli/src/spoke-services.ts +1 -1
  22. package/packages/cli/src/teams-mcp.ts +59 -2
  23. package/packages/codex/package.json +1 -1
  24. package/packages/codex/src/paths.ts +3 -3
  25. package/packages/node/package.json +1 -1
  26. package/packages/node/src/config.ts +10 -2
  27. package/packages/node/src/opencode-warm.ts +27 -8
  28. package/packages/node/src/run.ts +3 -2
  29. package/packages/preset/package.json +1 -1
  30. package/packages/protocol/package.json +1 -1
  31. package/packages/protocol/src/index.ts +7 -1
  32. package/packages/protocol/src/launch.ts +14 -0
  33. package/packages/runtime-openshell/package.json +1 -1
  34. package/packages/runtime-sdk/package.json +1 -1
  35. package/packages/sdk/package.json +1 -1
  36. package/packages/sdk/src/errors.ts +1 -0
  37. package/packages/sdk/src/index.ts +1 -0
  38. package/packages/sdk/src/types.ts +22 -2
  39. package/packages/sdk/src/wake.ts +20 -0
  40. package/packages/spoke-sdk/package.json +1 -1
  41. package/src/central/agent/acting.ts +3 -0
  42. package/src/central/agent/audit.ts +1 -1
  43. package/src/central/agent/decisions.ts +58 -2
  44. package/src/central/agent/delivery.ts +101 -27
  45. package/src/central/agent/errors.ts +2 -0
  46. package/src/central/agent/escalation.ts +10 -2
  47. package/src/central/agent/events.ts +7 -1
  48. package/src/central/agent/launch-engine.ts +173 -30
  49. package/src/central/agent/launch-routes.ts +20 -9
  50. package/src/central/agent/launch-spec.ts +26 -2
  51. package/src/central/agent/launch-store.ts +55 -2
  52. package/src/central/agent/message-limits.ts +89 -11
  53. package/src/central/agent/message-routes.ts +8 -3
  54. package/src/central/agent/messages.ts +3 -2
  55. package/src/central/agent/placement.ts +93 -18
  56. package/src/central/agent/read-routes.ts +3 -0
  57. package/src/central/agent/routes.ts +4 -2
  58. package/src/central/agent/webhooks.ts +17 -2
  59. package/src/central/artifacts/jobs-routes.ts +4 -1
  60. package/src/central/concierge/board-routes.ts +13 -2
  61. package/src/central/concierge/inbound.ts +4 -7
  62. package/src/central/concierge/prompt.ts +4 -3
  63. package/src/central/concierge/proposals.ts +8 -2
  64. package/src/central/concierge/routes.ts +62 -2
  65. package/src/central/concierge/session-routes.ts +62 -6
  66. package/src/central/concierge/supervisor.ts +34 -7
  67. package/src/central/concierge/watchlist-routes.ts +4 -2
  68. package/src/central/config.ts +21 -5
  69. package/src/central/http.ts +38 -15
  70. package/src/central/main.ts +1 -1
  71. package/src/central/registry.ts +26 -1
  72. package/src/central/teams/intake.ts +20 -6
  73. package/src/central/teams/staffing.ts +22 -0
  74. package/src/central/workspaces/sidechats-routes.ts +1 -1
  75. package/src/index.ts +39 -18
  76. package/src/migrate.ts +109 -3
  77. package/src/spoke/env-inject.ts +8 -1
  78. package/src/spoke/hub-tool.ts +24 -10
  79. package/src/spoke/launchers.ts +6 -4
  80. package/src/spoke/listing.ts +18 -6
  81. package/src/spoke/localhub.ts +14 -4
  82. package/src/spoke/locations.ts +40 -0
  83. package/src/spoke/session-context.ts +107 -27
  84. package/src/spoke/spoke.ts +13 -5
  85. package/web/dist/assets/ArtifactPage-mNzjlP_U.js +1 -0
  86. package/web/dist/assets/ArtifactPane-CPQcuX7c.js +1 -0
  87. package/web/dist/assets/ArtifactWorkbench-NoFC5Sb2.js +6 -0
  88. package/web/dist/assets/Library-LXHsP5Fw.js +2 -0
  89. package/web/dist/assets/ProfilesDestination-DhVVcAep.js +1 -0
  90. package/web/dist/assets/TeamsPage-DBrO5hoL.js +1 -0
  91. package/web/dist/assets/Watchtower-DxaJYQp_.js +2 -0
  92. package/web/dist/assets/index-BabrNDB3.css +1 -0
  93. package/web/dist/assets/index-lIOfSYck.js +34 -0
  94. package/web/dist/assets/live-BDq0AABg.js +5 -0
  95. package/web/dist/assets/{pane-layout-CKC4I00k.js → pane-layout-DWXfDU-1.js} +1 -1
  96. package/web/dist/assets/{renderer2d-CX5j3NcD.js → renderer2d-DAr4IHM8.js} +1 -1
  97. package/web/dist/assets/{renderer3d-BXIVOvfi.js → renderer3d-DWZj595u.js} +1 -1
  98. package/web/dist/assets/{safe-area-DfPsOdWR.js → safe-area-JUb6OeI3.js} +1 -1
  99. package/web/dist/assets/{source-CvUHTc3O.js → source-CDU04kYw.js} +1 -1
  100. package/web/dist/assets/ui-Dh1-78Cl.js +3 -0
  101. package/web/dist/index.html +4 -4
  102. package/web/dist/assets/ArtifactPage-BS0gVkfu.js +0 -1
  103. package/web/dist/assets/ArtifactPane-PLYHNs6s.js +0 -1
  104. package/web/dist/assets/ArtifactWorkbench-LkDyJ5Dv.js +0 -6
  105. package/web/dist/assets/Library-CtxRqTjo.js +0 -2
  106. package/web/dist/assets/ProfilesDestination-BYiHKLmc.js +0 -1
  107. package/web/dist/assets/TeamsPage-BwjsAAUC.js +0 -1
  108. package/web/dist/assets/Watchtower-C4swrWVs.js +0 -2
  109. package/web/dist/assets/index-DaH5GUdV.js +0 -31
  110. package/web/dist/assets/index-WWuKfWxL.css +0 -1
  111. package/web/dist/assets/live-YyTjE47g.js +0 -5
  112. 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.1",
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",
@@ -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 {
@@ -3907,7 +3949,7 @@ function isSessionToken(v) {
3907
3949
  return isObj(v) && nonEmpty(v.sessionID) && v.sessionID.length <= 300 && str2(v.token) && SESSION_TOKEN_TEXT.test(v.token) && num(v.exp);
3908
3950
  }
3909
3951
  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);
3952
+ 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) && (v.locked === undefined || v.locked === true);
3911
3953
  }
3912
3954
  var REVERT_STATUS = new Set(["added", "deleted", "modified"]);
3913
3955
  var count = (v) => Number.isSafeInteger(v) && v >= 0;
@@ -4536,8 +4578,8 @@ async function machineEnroll(options) {
4536
4578
  if (!server) {
4537
4579
  const why = insecureHTTPRefusal(options.server, allow);
4538
4580
  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)}`);
4581
+ return failure(COMMAND2, "usage", `${why[0].toUpperCase()}${why.slice(1)}: ${line(shownURL(String(options.server)), 200)}`);
4582
+ 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
4583
  }
4542
4584
  const insecure = isPlainHTTP(server) && !isLoopbackServer(server) ? insecureHTTPWarning(server) : undefined;
4543
4585
  const machineName = options.machineName ?? defaultMachineName(hostname2());
@@ -4799,7 +4841,6 @@ function profileNames(set) {
4799
4841
  // packages/node/src/opencode-warm.ts
4800
4842
  import { closeSync as closeSync3, constants, fstatSync, lstatSync as lstatSync3, openSync as openSync3, readSync, realpathSync, statSync } from "node:fs";
4801
4843
  import path7 from "node:path";
4802
- import { readFile } from "node:fs/promises";
4803
4844
 
4804
4845
  // src/spoke/listing.ts
4805
4846
  import path6 from "node:path";
@@ -4842,9 +4883,12 @@ async function listServiceSessions(client, options = {}) {
4842
4883
  return [];
4843
4884
  const pageSize = options.pageSize ?? 100;
4844
4885
  const maxPages = options.maxPages ?? 20;
4886
+ const { signal } = options;
4887
+ const request = signal ? { signal } : {};
4888
+ signal?.throwIfAborted();
4845
4889
  const projects = new Map;
4846
4890
  try {
4847
- for (const project of await client.project.list())
4891
+ for (const project of await client.project.list(request))
4848
4892
  projects.set(project.id, project);
4849
4893
  } catch {}
4850
4894
  const scope = options.scope;
@@ -4855,7 +4899,8 @@ async function listServiceSessions(client, options = {}) {
4855
4899
  let cursor;
4856
4900
  let ended = false;
4857
4901
  for (let page = 0;page < maxPages && out.length < max && !ended; page++) {
4858
- const result = await client.session.list({ limit: pageSize, parentID: null, ...cursor ? { cursor } : {} });
4902
+ signal?.throwIfAborted();
4903
+ const result = await client.session.list({ limit: pageSize, parentID: null, ...cursor ? { cursor } : {} }, request);
4859
4904
  for (const s of result.data) {
4860
4905
  if (s.parentID || s.time.archived || out.length >= max)
4861
4906
  continue;
@@ -4873,7 +4918,8 @@ async function listServiceSessions(client, options = {}) {
4873
4918
  const have = new Set(out.map((s) => s.id));
4874
4919
  const missing = [...new Set(options.include ?? [])].filter((id) => !have.has(id)).slice(0, INCLUDE_FETCH_MAX);
4875
4920
  if (missing.length && client.session.get) {
4876
- const fetched = await Promise.all(missing.map((sessionID) => client.session.get({ sessionID }).catch(() => {
4921
+ signal?.throwIfAborted();
4922
+ const fetched = await Promise.all(missing.map((sessionID) => client.session.get({ sessionID }, request).catch(() => {
4877
4923
  return;
4878
4924
  })));
4879
4925
  for (const s of fetched)
@@ -4885,12 +4931,13 @@ async function listServiceSessions(client, options = {}) {
4885
4931
 
4886
4932
  // packages/node/src/opencode-warm.ts
4887
4933
  var WARM_INTERVAL_MS = 30000;
4888
- var WARM_TIMEOUT_MS = 1e4;
4934
+ var WARM_TIMEOUT_MS = 30000;
4889
4935
  var WARM_DIRECTORIES_MAX = 8;
4890
4936
  var SESSION_DAYS = 7;
4891
4937
  var SESSION_PAGE = 100;
4892
4938
  var LOOPBACK2 = new Set(["127.0.0.1", "[::1]"]);
4893
4939
  var PASSWORD_FILE_MAX = 4096;
4940
+ var REGISTRATION_MAX = 64 * 1024;
4894
4941
  var isDirectory = (directory) => {
4895
4942
  try {
4896
4943
  return statSync(directory).isDirectory();
@@ -5071,12 +5118,34 @@ class OpenCodeWarmer {
5071
5118
  return problem ? "failed" : "warmed";
5072
5119
  }
5073
5120
  }
5074
- async function readServiceRegistration(file) {
5121
+ async function readServiceRegistration(file, options = {}) {
5122
+ const uid = options.uid ?? (typeof process.getuid === "function" ? process.getuid() : undefined);
5075
5123
  let info;
5124
+ let fd;
5076
5125
  try {
5077
- info = JSON.parse(await readFile(file, "utf8"));
5126
+ const before = lstatSync3(file);
5127
+ if (!before.isFile())
5128
+ return;
5129
+ fd = openSync3(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
5130
+ const stat = fstatSync(fd);
5131
+ if (!stat.isFile() || stat.dev !== before.dev || stat.ino !== before.ino)
5132
+ return;
5133
+ if ((stat.mode & 18) !== 0)
5134
+ return;
5135
+ if (uid !== undefined && stat.uid !== uid)
5136
+ return;
5137
+ if (stat.size > REGISTRATION_MAX)
5138
+ return;
5139
+ const buffer = Buffer.alloc(REGISTRATION_MAX + 1);
5140
+ const length = readSync(fd, buffer, 0, buffer.length, 0);
5141
+ if (length > REGISTRATION_MAX)
5142
+ return;
5143
+ info = JSON.parse(buffer.subarray(0, length).toString("utf8"));
5078
5144
  } catch {
5079
5145
  return;
5146
+ } finally {
5147
+ if (fd !== undefined)
5148
+ closeSync3(fd);
5080
5149
  }
5081
5150
  if (!info || typeof info !== "object")
5082
5151
  return;
@@ -5085,9 +5154,8 @@ async function readServiceRegistration(file) {
5085
5154
  return;
5086
5155
  try {
5087
5156
  process.kill(pid, 0);
5088
- } catch (cause) {
5089
- if (cause.code !== "EPERM")
5090
- return;
5157
+ } catch {
5158
+ return;
5091
5159
  }
5092
5160
  return typeof password === "string" ? { url, auth: { type: "basic", username: "opencode", password } } : { url };
5093
5161
  }
@@ -5287,7 +5355,15 @@ function normalizeOpenCode(raw, problems) {
5287
5355
  problems.push(`opencode.servers: at most ${OPENCODE_SERVERS_MAX} servers; the rest are ignored`);
5288
5356
  servers.length = OPENCODE_SERVERS_MAX;
5289
5357
  }
5290
- return { ...directories ? { directories } : {}, servers };
5358
+ let timeoutSeconds;
5359
+ if (raw.timeoutSeconds !== undefined) {
5360
+ const t = raw.timeoutSeconds;
5361
+ if (typeof t === "number" && Number.isInteger(t) && t >= 1 && t <= 300)
5362
+ timeoutSeconds = t;
5363
+ else
5364
+ problems.push("opencode.timeoutSeconds must be a whole number of seconds from 1 to 300");
5365
+ }
5366
+ return { ...directories ? { directories } : {}, servers, ...timeoutSeconds !== undefined ? { timeoutSeconds } : {} };
5291
5367
  }
5292
5368
  function normalizeHarnesses(raw, problems) {
5293
5369
  if (!isObject3(raw)) {
@@ -5459,14 +5535,15 @@ var DELIVERY = {
5459
5535
  queued: "queued (read at its next turn)",
5460
5536
  retained: "retained (held until its open question is answered)",
5461
5537
  pending: "pending (offline or busy; delivered when it reconnects or its turn ends, for 24 h)",
5462
- dropped: "not delivered (its recipient session ended)"
5538
+ dropped: "not delivered (its recipient session ended)",
5539
+ expired: "not delivered (its recipient stayed offline or busy for 24 h)"
5463
5540
  };
5464
5541
  var deliveryText = (delivery) => delivery ? DELIVERY[delivery] ?? delivery : "stored (in its inbox)";
5465
5542
  function toText(message) {
5466
5543
  const recipient = partyText(message.recipient);
5467
5544
  return message.to === recipient || message.to === message.recipient.sessionID ? recipient : `${message.to} → ${recipient}`;
5468
5545
  }
5469
- var sentText = ({ message, delivery }) => `${message.kind} ${message.id} to ${toText(message)}: ${deliveryText(delivery)}`;
5546
+ var sentText = (sent) => `${sent.message.kind} ${sent.message.id} to ${toText(sent.message)}: ${deliveryText(sent.delivery)}${wakeSkipNote(sent)}`;
5470
5547
  var ASK_EXPIRES_DEFAULT_S = 24 * 60 * 60;
5471
5548
  var UNIT_S = { "": 1, s: 1, m: 60, h: 3600, d: 86400 };
5472
5549
  function durationArg(value, flag) {
@@ -5723,9 +5800,12 @@ function launchedText(launch, created, key = launch.spec.key) {
5723
5800
  return `${launch.id} was already launched with key ${key}: ${summary(launch).slice(launch.id.length + 2)}`;
5724
5801
  }
5725
5802
  var stoppedLaunchText = (launch) => `stopped ${summary(launch)}`;
5803
+ var launcherText = (l) => l.byLabel ? `app ${JSON.stringify(l.byLabel)}` : `app ${l.by?.replace(/^app:/, "") ?? "?"}`;
5726
5804
  function launchLine(l) {
5727
5805
  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(" ");
5806
+ const waits = l.state === "queued" && l.waiting ? `waits: ${l.waiting.reason}` : undefined;
5807
+ const revoked = l.launcherRevoked && l.state !== "ended" ? `launcher revoked (${launcherText(l)}): stop it with hub launches stop ${l.id}` : undefined;
5808
+ return [l.id, stateText2(l), sessionText(l) || undefined, where, labelsText(l.spec.labels) || undefined, waits, revoked].filter(Boolean).join(" ");
5729
5809
  }
5730
5810
  var launchesText = (launches) => launches.length ? launches.map(launchLine).join(`
5731
5811
  `) : "no launches";
@@ -5753,8 +5833,10 @@ function launchText(l) {
5753
5833
  lines.push(`runs: ${run.join(" ")}`);
5754
5834
  if (l.spec.end)
5755
5835
  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(", ")}`);
5836
+ if (l.state === "queued" && l.waiting)
5837
+ lines.push(`waits (${l.waiting.wait}) since ${new Date(l.waiting.since).toISOString()}: ${l.waiting.reason}`);
5756
5838
  if (l.by)
5757
- lines.push(`by: ${l.by}`);
5839
+ lines.push(`by: ${l.by}${l.byLabel ? ` (${l.byLabel})` : ""}${l.launcherRevoked ? ", launcher revoked" : ""}`);
5758
5840
  if (labelsText(l.spec.labels))
5759
5841
  lines.push(`labels: ${labelsText(l.spec.labels)}`);
5760
5842
  if (l.artifacts && artifactsText(l.artifacts))
@@ -6285,6 +6367,59 @@ var SPEC_FIELDS = {
6285
6367
  metadata: { type: "object", additionalProperties: STRING4, description: "String key/value pairs (keys are slugs)." },
6286
6368
  projects: { ...STRINGS, description: "Project names the team works on." }
6287
6369
  };
6370
+ var LAUNCH = {
6371
+ type: "object",
6372
+ 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.",
6373
+ properties: {
6374
+ workspace: {
6375
+ type: "object",
6376
+ description: "Where the member works on its machine.",
6377
+ properties: {
6378
+ repo: { ...STRING4, description: "The repository's absolute path on the machine." },
6379
+ mode: { type: "string", enum: ["worktree", "checkout", "clone"], description: "worktree (default; needs branch), checkout, or clone (needs remote)." },
6380
+ branch: { ...STRING4, description: "The branch to create or check out (a safe git branch name; required for worktree)." },
6381
+ base: { ...STRING4, description: "What a new branch starts from." },
6382
+ remote: { ...STRING4, description: "An https or ssh git remote, without credentials (required for clone)." }
6383
+ },
6384
+ required: ["repo"]
6385
+ },
6386
+ placement: {
6387
+ type: "object",
6388
+ description: "Which machine runs it.",
6389
+ properties: {
6390
+ machine: { ...STRING4, description: "A machine ID or name." },
6391
+ labels: { type: "object", additionalProperties: STRING4, description: "Node labels to match." },
6392
+ mode: { type: "string", enum: ["require", "prefer"], description: "require (default) or prefer the machine/labels." }
6393
+ }
6394
+ },
6395
+ runtime: {
6396
+ type: "object",
6397
+ properties: {
6398
+ kind: { type: "string", enum: ["host", "openshell"], description: "host (default) or openshell (clone workspaces only)." },
6399
+ profile: { ...STRING4, description: "An openshell sandbox profile (openshell only)." }
6400
+ }
6401
+ },
6402
+ end: {
6403
+ type: "object",
6404
+ description: "When the launch ends.",
6405
+ properties: {
6406
+ when: { type: "string", enum: ["idle", "asked", "never"], description: "idle (default), asked or never." },
6407
+ timeout: { type: ["string", "number"], description: "1 minute to 30 days: seconds, or 30m, 4h, 2d." },
6408
+ cleanup: { type: "string", enum: ["keep", "remove"], description: "keep (default) or remove the workspace at the end." }
6409
+ }
6410
+ },
6411
+ name: { ...STRING4, description: "The session's @name, without the @." },
6412
+ roles: { ...STRINGS, description: "Roles the session claims (up to 10)." },
6413
+ harness: { ...STRING4, description: "The harness (default opencode)." },
6414
+ agent: { ...STRING4, description: "The harness agent." },
6415
+ model: { ...STRING4, description: "provider/model." },
6416
+ prompt: { ...STRING4, description: "The first prompt (up to 10000 characters)." },
6417
+ intent: { ...STRING4, description: "What the session is for (one line)." },
6418
+ labels: { type: "object", additionalProperties: STRING4, description: "Free-form app labels." },
6419
+ 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)." }
6420
+ },
6421
+ required: ["workspace"]
6422
+ };
6288
6423
  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
6424
 
6290
6425
  class ArgError extends Error {
@@ -6581,7 +6716,7 @@ var teamTools = [
6581
6716
  {
6582
6717
  name: "team_staff",
6583
6718
  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 },
6719
+ properties: { team: TEAM2, role: STRING4, sessionID: { ...STRING4, description: "An existing session (ses_…)." }, launch: LAUNCH, idempotencyKey: IDEMPOTENCY },
6585
6720
  required: ["team", "role"],
6586
6721
  readOnly: false,
6587
6722
  run: async (hub, args, signal) => {
@@ -6616,7 +6751,7 @@ var teamTools = [
6616
6751
  {
6617
6752
  name: "team_replace",
6618
6753
  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)." } },
6754
+ properties: { team: TEAM2, staffingID: STRING4, sessionID: STRING4, launch: LAUNCH, expectedRevision: { ...REVISION2, description: "The seat's revision you read (team_status)." } },
6620
6755
  required: ["team", "staffingID", "expectedRevision"],
6621
6756
  readOnly: false,
6622
6757
  run: async (hub, args, signal) => {
@@ -6633,6 +6768,256 @@ var teamTools = [
6633
6768
  }
6634
6769
  ];
6635
6770
 
6771
+ // packages/cli/src/profiles-mcp.ts
6772
+ var STRING5 = { type: "string" };
6773
+ var STRINGS2 = { type: "array", items: STRING5 };
6774
+ var ID4 = { type: "string", description: "The profile's ID (prof_…; see profiles)." };
6775
+ var REVISION3 = { type: "integer", minimum: 0, description: "The profile's revision you read (profile_show); a stale one answers conflict with the current revision." };
6776
+ var IDEMPOTENCY2 = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
6777
+ 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"] };
6778
+ 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"] };
6779
+ var CONTENT_FIELDS = {
6780
+ instructions: { type: ["string", "null"], description: "The profile's instructions (AGENTS.md-like text)." },
6781
+ model: { type: ["string", "null"], description: "provider/model the profile suggests." },
6782
+ skills: { type: "array", items: SKILL, description: "Skills, wholesale." },
6783
+ commands: { type: "array", items: COMMAND3, description: "Commands, wholesale." }
6784
+ };
6785
+ var META_FIELDS = {
6786
+ title: { ...STRING5, description: "1–120 characters." },
6787
+ description: STRING5,
6788
+ tags: { ...STRINGS2, description: "Slugs." },
6789
+ metadata: { type: "object", additionalProperties: STRING5, description: "String key/value pairs (≤ 32 slug keys)." },
6790
+ projects: { ...STRINGS2, description: "Project names the profile is meant for (≤ 16)." }
6791
+ };
6792
+ 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.";
6793
+
6794
+ class ProfileArgError extends Error {
6795
+ }
6796
+ var ArgError2 = ProfileArgError;
6797
+ var need5 = (args, key) => {
6798
+ const value = args[key];
6799
+ if (typeof value !== "string" || !value.trim())
6800
+ throw new ArgError2(`"${key}" is required (a non-empty string)`);
6801
+ return value;
6802
+ };
6803
+ var optional6 = (args, key) => {
6804
+ const value = args[key];
6805
+ if (value === undefined || value === null)
6806
+ return;
6807
+ if (typeof value !== "string")
6808
+ throw new ArgError2(`"${key}" must be a string`);
6809
+ return value.trim() ? value : undefined;
6810
+ };
6811
+ var whole2 = (args, key, min, what) => {
6812
+ const value = args[key];
6813
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min)
6814
+ throw new ArgError2(`"${key}" is required: ${what}`);
6815
+ return value;
6816
+ };
6817
+ var revision2 = (args) => whole2(args, "expectedRevision", 0, "the revision you read (profile_show; 0 for a session with no pin)");
6818
+ var idempotency = (args) => {
6819
+ const key = optional6(args, "idempotencyKey");
6820
+ return key ? { idempotencyKey: key } : {};
6821
+ };
6822
+ var picked2 = (args, keys) => Object.fromEntries(keys.filter((k) => args[k] !== undefined).map((k) => [k, args[k]]));
6823
+ var one2 = (text) => sanitizeText(text).replace(/\s+/g, " ");
6824
+ var profileLine = (p) => `${p.id} ${p.name} v${p.latestVersion} r${p.revision} ${p.state} ${one2(p.title)}`;
6825
+ function summaryLines(v) {
6826
+ if (!v)
6827
+ return [];
6828
+ return [
6829
+ `version ${v.version} (${v.digest.slice(0, 12)}, ${v.bytes} bytes): instructions ${v.instructions ? "yes" : "no"}${v.model ? `, model ${one2(v.model)}` : ""}`,
6830
+ `skills: ${v.skills.length ? v.skills.map((s) => s.name).join(", ") : "none"}`,
6831
+ `commands: ${v.commands.length ? v.commands.map((c) => `${c.name}(${c.params.join(", ")})`).join(", ") : "none"}`,
6832
+ ...v.secretRefs.length ? [`secret references: ${v.secretRefs.join(", ")}`] : []
6833
+ ];
6834
+ }
6835
+ var versionText = (r) => [`new version ${r.version.version}: ${profileLine(r.profile)}`, ...summaryLines(r.version)].join(`
6836
+ `);
6837
+ async function latestContent(profiles, id, signal) {
6838
+ const { profile } = await profiles.get(id, { signal });
6839
+ const { version } = await profiles.version(profile.id, profile.latestVersion, { signal });
6840
+ return { latest: version.version, content: version.content };
6841
+ }
6842
+ var contentTool = (name, description, properties, required, change) => ({
6843
+ name,
6844
+ description: `${description} Writes the latest version's content with this change as the next version (versions are immutable; pinned sessions keep theirs). ${SCOPES2}`,
6845
+ properties: { id: ID4, ...properties, expectedRevision: REVISION3 },
6846
+ required: ["id", ...required, "expectedRevision"],
6847
+ readOnly: false,
6848
+ organizes: true,
6849
+ idempotent: true,
6850
+ run: async (hub, args, signal) => {
6851
+ const expectedRevision = revision2(args);
6852
+ const id = need5(args, "id");
6853
+ const { latest, content } = await latestContent(hub.profiles, id, signal);
6854
+ return versionText(await hub.profiles.createVersion(id, { expectedRevision, content: change(content, args, latest) }, { signal }));
6855
+ }
6856
+ });
6857
+ var setNamed = (items, item) => items.some((i) => i.name === item.name) ? items.map((i) => i.name === item.name ? item : i) : [...items, item];
6858
+ var removeNamed = (items, name, what, latest) => {
6859
+ if (!items.some((i) => i.name === name))
6860
+ throw new ArgError2(`version ${latest} has no ${what} ${name}`);
6861
+ return items.filter((i) => i.name !== name);
6862
+ };
6863
+ var stateTool = (verb, description) => ({
6864
+ name: `profile_${verb}`,
6865
+ description: `${description} ${SCOPES2}`,
6866
+ properties: { id: ID4, expectedRevision: REVISION3 },
6867
+ required: ["id", "expectedRevision"],
6868
+ readOnly: false,
6869
+ idempotent: true,
6870
+ run: async (hub, args, signal) => {
6871
+ const expectedRevision = revision2(args);
6872
+ const { profile } = await hub.profiles.update(need5(args, "id"), { expectedRevision, state: verb === "archive" ? "archived" : "active" }, { signal });
6873
+ return `${verb === "archive" ? "archived" : "restored"}: ${profileLine(profile)}`;
6874
+ }
6875
+ });
6876
+ var profileLifecycleTools = [
6877
+ {
6878
+ name: "profile_show",
6879
+ 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}`,
6880
+ properties: { id: ID4 },
6881
+ required: ["id"],
6882
+ readOnly: true,
6883
+ run: async (hub, args, signal) => {
6884
+ const { profile, latest } = await hub.profiles.get(need5(args, "id"), { signal });
6885
+ return [
6886
+ profileLine(profile),
6887
+ ...profile.description ? [`description: ${one2(profile.description)}`] : [],
6888
+ ...profile.tags?.length ? [`tags: ${profile.tags.join(", ")}`] : [],
6889
+ ...profile.projects?.length ? [`projects: ${profile.projects.map(one2).join(", ")}`] : [],
6890
+ ...profile.metadata && Object.keys(profile.metadata).length ? [`metadata: ${Object.entries(profile.metadata).map(([k, v]) => `${k}=${one2(v)}`).join(", ")}`] : [],
6891
+ ...summaryLines(latest)
6892
+ ].join(`
6893
+ `);
6894
+ }
6895
+ },
6896
+ {
6897
+ name: "profile_versions",
6898
+ description: `A profile's retained versions, newest first (version, digest, bytes, when, skill and command counts). ${SCOPES2}`,
6899
+ properties: { id: ID4, limit: { type: "integer", minimum: 1, maximum: 200 }, cursor: STRING5 },
6900
+ required: ["id"],
6901
+ readOnly: true,
6902
+ run: async (hub, args, signal) => {
6903
+ const limit = args.limit === undefined ? undefined : whole2(args, "limit", 1, "a whole number from 1 to 200");
6904
+ const cursor = optional6(args, "cursor");
6905
+ const page = await hub.profiles.versions(need5(args, "id"), { signal, ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {} });
6906
+ const counts = (v) => v.skills !== undefined ? ` ${v.skills} skills, ${v.commands ?? 0} commands${v.instructions ? ", instructions" : ""}` : "";
6907
+ 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(`
6908
+ `) || "no versions";
6909
+ }
6910
+ },
6911
+ {
6912
+ name: "profile_create",
6913
+ 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}`,
6914
+ properties: { name: { ...STRING5, description: "A slug, unique per user." }, ...META_FIELDS, ...CONTENT_FIELDS, idempotencyKey: IDEMPOTENCY2 },
6915
+ required: ["name", "title"],
6916
+ readOnly: false,
6917
+ organizes: true,
6918
+ idempotent: true,
6919
+ run: async (hub, args, signal) => {
6920
+ need5(args, "name");
6921
+ need5(args, "title");
6922
+ const meta = picked2(args, ["name", "title", "description", "tags", "metadata", "projects"]);
6923
+ const text = Object.entries(picked2(args, ["instructions", "model"])).filter(([, v]) => v !== null);
6924
+ const content = { ...Object.fromEntries(text), skills: args.skills ?? [], commands: args.commands ?? [] };
6925
+ const r = await hub.profiles.create({ ...meta, content }, { signal, ...idempotency(args) });
6926
+ return [`created: ${profileLine(r.profile)}`, ...summaryLines(r.version)].join(`
6927
+ `);
6928
+ }
6929
+ },
6930
+ {
6931
+ name: "profile_update",
6932
+ 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}`,
6933
+ 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 },
6934
+ required: ["id", "expectedRevision"],
6935
+ readOnly: false,
6936
+ organizes: true,
6937
+ idempotent: true,
6938
+ run: async (hub, args, signal) => {
6939
+ const expectedRevision = revision2(args);
6940
+ const patch = picked2(args, ["title", "description", "tags", "metadata", "projects"]);
6941
+ if (Object.keys(patch).length === 0)
6942
+ throw new ArgError2("give at least one field to change");
6943
+ const { profile } = await hub.profiles.update(need5(args, "id"), { expectedRevision, ...patch }, { signal });
6944
+ return `updated: ${profileLine(profile)}`;
6945
+ }
6946
+ },
6947
+ {
6948
+ name: "profile_version_create",
6949
+ 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}`,
6950
+ properties: { id: ID4, ...CONTENT_FIELDS, fromVersion: { type: "integer", minimum: 1, description: "A retained version to restore as the next one." }, expectedRevision: REVISION3, idempotencyKey: IDEMPOTENCY2 },
6951
+ required: ["id", "expectedRevision"],
6952
+ readOnly: false,
6953
+ organizes: true,
6954
+ idempotent: true,
6955
+ run: async (hub, args, signal) => {
6956
+ const expectedRevision = revision2(args);
6957
+ const id = need5(args, "id");
6958
+ const given = Object.keys(picked2(args, Object.keys(CONTENT_FIELDS)));
6959
+ const options = { signal, ...idempotency(args) };
6960
+ if (args.fromVersion !== undefined) {
6961
+ if (given.length)
6962
+ throw new ArgError2(`give either fromVersion or content fields (${given.join(", ")}), not both`);
6963
+ return versionText(await hub.profiles.createVersion(id, { expectedRevision, fromVersion: whole2(args, "fromVersion", 1, "a version number") }, options));
6964
+ }
6965
+ if (!given.length)
6966
+ throw new ArgError2("give content fields (instructions, model, skills, commands) or fromVersion");
6967
+ const { content } = await latestContent(hub.profiles, id, signal);
6968
+ const next = { ...content };
6969
+ for (const key of given) {
6970
+ if (args[key] === null)
6971
+ delete next[key];
6972
+ else
6973
+ next[key] = args[key];
6974
+ }
6975
+ return versionText(await hub.profiles.createVersion(id, { expectedRevision, content: next }, options));
6976
+ }
6977
+ },
6978
+ 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) => ({
6979
+ ...content,
6980
+ skills: setNamed(content.skills, { name: need5(args, "name"), description: need5(args, "description"), body: need5(args, "body") })
6981
+ })),
6982
+ contentTool("profile_skill_remove", "Detach a skill from a profile.", { name: { ...STRING5, description: "The skill's name." } }, ["name"], (content, args, latest) => ({
6983
+ ...content,
6984
+ skills: removeNamed(content.skills, need5(args, "name"), "skill", latest)
6985
+ })),
6986
+ 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") }) })),
6987
+ contentTool("profile_command_remove", "Detach a command from a profile.", { name: { ...STRING5, description: "The command's name." } }, ["name"], (content, args, latest) => ({
6988
+ ...content,
6989
+ commands: removeNamed(content.commands, need5(args, "name"), "command", latest)
6990
+ })),
6991
+ stateTool("archive", "Archive a profile: it takes no new versions or pins; pinned sessions keep theirs; reversible with profile_restore."),
6992
+ stateTool("restore", "Restore an archived profile (it takes versions and pins again)."),
6993
+ {
6994
+ name: "profile_assign",
6995
+ 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}`,
6996
+ 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)." } },
6997
+ required: ["addr", "profileID", "version", "expectedRevision"],
6998
+ readOnly: false,
6999
+ idempotent: true,
7000
+ run: async (hub, args, signal) => {
7001
+ const expectedRevision = revision2(args);
7002
+ const { assignment: a } = await hub.profiles.assign(need5(args, "addr"), { profileID: need5(args, "profileID"), version: whole2(args, "version", 1, "a version number"), expectedRevision }, { signal });
7003
+ return `${a.sessionID} pins ${a.profileID} v${a.version} r${a.revision}${a.ack ? `, ack ${a.ack.status}${a.ack.offline ? " (offline)" : ""}` : ""}`;
7004
+ }
7005
+ },
7006
+ {
7007
+ name: "profile_unassign",
7008
+ description: `Remove a session's pin (what it already applied stays until it restarts). Send the pin's revision (profile_assign's answer). ${SCOPES2}`,
7009
+ properties: { addr: { ...STRING5, description: "The session." }, expectedRevision: { ...REVISION3, description: "The session's pin revision." } },
7010
+ required: ["addr", "expectedRevision"],
7011
+ readOnly: false,
7012
+ idempotent: true,
7013
+ run: async (hub, args, signal) => {
7014
+ const addr = need5(args, "addr");
7015
+ await hub.profiles.unassign(addr, revision2(args), { signal });
7016
+ return `unpinned ${addr}`;
7017
+ }
7018
+ }
7019
+ ];
7020
+
6636
7021
  // packages/cli/src/watchlist.ts
6637
7022
  var safe = (value) => sanitizeLabel(String(value), 1024);
6638
7023
  var list2 = (items) => items.length ? items.map(safe).join(", ") : "none";
@@ -6659,8 +7044,8 @@ function watchlistChangedText(verb, r) {
6659
7044
  var PAST = { watch: "watching", unwatch: "not watching", mute: "muted", unmute: "unmuted" };
6660
7045
  var watchlistVerbText = (verb, r) => watchlistChangedText(PAST[verb], r);
6661
7046
  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) => {
7047
+ var STRINGS3 = { type: "array", items: { type: "string" } };
7048
+ var need6 = (args, key) => {
6664
7049
  const value = args[key];
6665
7050
  if (typeof value !== "string" || !value.trim())
6666
7051
  throw new Error(`"${key}" is required (a non-empty string)`);
@@ -6691,11 +7076,11 @@ var watchlistTools = [
6691
7076
  autoActive: { type: "boolean" },
6692
7077
  activeWithinHours: { type: "integer", minimum: 1, maximum: 24 },
6693
7078
  usePins: { type: "boolean" },
6694
- sessions: STRINGS2,
6695
- projects: STRINGS2,
7079
+ sessions: STRINGS3,
7080
+ projects: STRINGS3,
6696
7081
  labels: { type: "array", items: { type: "object", additionalProperties: { type: "string" } } },
6697
- muted: STRINGS2,
6698
- mutedProjects: STRINGS2
7082
+ muted: STRINGS3,
7083
+ mutedProjects: STRINGS3
6699
7084
  },
6700
7085
  required: ["expectedRevision"],
6701
7086
  readOnly: false,
@@ -6727,7 +7112,7 @@ ${watchlistConfigText(r.config)}`;
6727
7112
  idempotent: true,
6728
7113
  run: async (hub, args, signal) => {
6729
7114
  const on = flag(args, "watch", true);
6730
- const addr = need5(args, "addr");
7115
+ const addr = need6(args, "addr");
6731
7116
  const r = on ? await hub.concierge.watchlist.watch(addr, { signal }) : await hub.concierge.watchlist.unwatch(addr, { signal });
6732
7117
  return watchlistVerbText(on ? "watch" : "unwatch", r);
6733
7118
  }
@@ -6742,7 +7127,7 @@ ${watchlistConfigText(r.config)}`;
6742
7127
  idempotent: true,
6743
7128
  run: async (hub, args, signal) => {
6744
7129
  const on = flag(args, "mute", true);
6745
- const addr = need5(args, "addr");
7130
+ const addr = need6(args, "addr");
6746
7131
  const r = on ? await hub.concierge.watchlist.mute(addr, { signal }) : await hub.concierge.watchlist.unmute(addr, { signal });
6747
7132
  return watchlistVerbText(on ? "mute" : "unmute", r);
6748
7133
  }
@@ -6753,20 +7138,20 @@ ${watchlistConfigText(r.config)}`;
6753
7138
  var MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
6754
7139
  var RPC = { parse: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602, internal: -32603 };
6755
7140
 
6756
- class ArgError2 extends Error {
7141
+ class ArgError3 extends Error {
6757
7142
  }
6758
- var need6 = (args, key) => {
7143
+ var need7 = (args, key) => {
6759
7144
  const value = args[key];
6760
7145
  if (typeof value !== "string" || !value.trim())
6761
- throw new ArgError2(`"${key}" is required (a non-empty string)`);
7146
+ throw new ArgError3(`"${key}" is required (a non-empty string)`);
6762
7147
  return value;
6763
7148
  };
6764
- var optional6 = (args, key) => {
7149
+ var optional7 = (args, key) => {
6765
7150
  const value = args[key];
6766
7151
  if (value === undefined || value === null)
6767
7152
  return;
6768
7153
  if (typeof value !== "string")
6769
- throw new ArgError2(`"${key}" must be a string`);
7154
+ throw new ArgError3(`"${key}" must be a string`);
6770
7155
  return value.trim() ? value : undefined;
6771
7156
  };
6772
7157
  var optionalInt = (args, key, min, max) => {
@@ -6774,7 +7159,7 @@ var optionalInt = (args, key, min, max) => {
6774
7159
  if (value === undefined || value === null)
6775
7160
  return;
6776
7161
  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}`);
7162
+ throw new ArgError3(`"${key}" must be a whole number from ${min} to ${max}`);
6778
7163
  return value;
6779
7164
  };
6780
7165
  var optionalStrings = (args, key) => {
@@ -6782,7 +7167,7 @@ var optionalStrings = (args, key) => {
6782
7167
  if (value === undefined || value === null)
6783
7168
  return;
6784
7169
  if (!Array.isArray(value) || !value.every((v) => typeof v === "string"))
6785
- throw new ArgError2(`"${key}" must be a list of strings`);
7170
+ throw new ArgError3(`"${key}" must be a list of strings`);
6786
7171
  return value;
6787
7172
  };
6788
7173
  var optionalBool = (args, key) => {
@@ -6790,7 +7175,7 @@ var optionalBool = (args, key) => {
6790
7175
  if (value === undefined || value === null)
6791
7176
  return;
6792
7177
  if (typeof value !== "boolean")
6793
- throw new ArgError2(`"${key}" must be true or false`);
7178
+ throw new ArgError3(`"${key}" must be true or false`);
6794
7179
  return value;
6795
7180
  };
6796
7181
  var optionalWait = (args) => {
@@ -6798,7 +7183,7 @@ var optionalWait = (args) => {
6798
7183
  if (value === undefined || value === null)
6799
7184
  return;
6800
7185
  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})`);
7186
+ throw new ArgError3(`"wait" must be a number of seconds (0–${WAIT_POLL_MAX_S})`);
6802
7187
  return Math.min(value, WAIT_POLL_MAX_S);
6803
7188
  };
6804
7189
  var optionalExpires = (args) => {
@@ -6812,11 +7197,11 @@ var optionalExpires = (args) => {
6812
7197
  return durationArg(value.trim(), '"expires"');
6813
7198
  } catch (cause) {
6814
7199
  if (cause instanceof UsageError)
6815
- throw new ArgError2(cause.message);
7200
+ throw new ArgError3(cause.message);
6816
7201
  throw cause;
6817
7202
  }
6818
7203
  }
6819
- throw new ArgError2(`"expires" must be seconds or a duration like 30s, 5m, 2h, 1d`);
7204
+ throw new ArgError3(`"expires" must be seconds or a duration like 30s, 5m, 2h, 1d`);
6820
7205
  };
6821
7206
  var sendOptions = (args) => {
6822
7207
  const wake = optionalBool(args, "wake");
@@ -6824,7 +7209,7 @@ var sendOptions = (args) => {
6824
7209
  return { ...wake !== undefined ? { wake } : {}, ...urgent !== undefined ? { urgent } : {} };
6825
7210
  };
6826
7211
  var whyOption = (args) => {
6827
- const why = optional6(args, "why");
7212
+ const why = optional7(args, "why");
6828
7213
  return why !== undefined ? { why } : {};
6829
7214
  };
6830
7215
  var optionalDelivery = (args) => {
@@ -6832,18 +7217,18 @@ var optionalDelivery = (args) => {
6832
7217
  if (value === undefined || value === null)
6833
7218
  return;
6834
7219
  if (!DELIVERIES.includes(value))
6835
- throw new ArgError2(`"delivery" is one of ${DELIVERIES.join(", ")}`);
7220
+ throw new ArgError3(`"delivery" is one of ${DELIVERIES.join(", ")}`);
6836
7221
  return value;
6837
7222
  };
6838
7223
  var formAnswer = (args) => {
6839
7224
  const cancel = optionalBool(args, "cancel") === true;
6840
7225
  const answer = args.answer;
6841
7226
  if (cancel && answer !== undefined && answer !== null)
6842
- throw new ArgError2(`give either "answer" or "cancel", not both`);
7227
+ throw new ArgError3(`give either "answer" or "cancel", not both`);
6843
7228
  if (cancel)
6844
7229
  return "cancel";
6845
7230
  if (typeof answer !== "object" || answer === null || Array.isArray(answer))
6846
- throw new ArgError2(`"answer" is required (an object: field → value), or "cancel": true`);
7231
+ throw new ArgError3(`"answer" is required (an object: field → value), or "cancel": true`);
6847
7232
  return answer;
6848
7233
  };
6849
7234
  var optionalEnum = (args, key, options) => {
@@ -6851,7 +7236,7 @@ var optionalEnum = (args, key, options) => {
6851
7236
  if (value === undefined || value === null)
6852
7237
  return;
6853
7238
  if (!options.includes(value))
6854
- throw new ArgError2(`"${key}" is one of ${options.join(", ")}`);
7239
+ throw new ArgError3(`"${key}" is one of ${options.join(", ")}`);
6855
7240
  return value;
6856
7241
  };
6857
7242
  var optionalLabels = (args, key) => {
@@ -6859,7 +7244,7 @@ var optionalLabels = (args, key) => {
6859
7244
  if (value === undefined || value === null)
6860
7245
  return;
6861
7246
  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)`);
7247
+ throw new ArgError3(`"${key}" must be an object of text values (key → value)`);
6863
7248
  return Object.keys(value).length ? value : undefined;
6864
7249
  };
6865
7250
  var definedFields = (fields) => {
@@ -6867,35 +7252,35 @@ var definedFields = (fields) => {
6867
7252
  return Object.keys(out).length ? out : undefined;
6868
7253
  };
6869
7254
  function launchSpec(args) {
6870
- const repo = need6(args, "repo");
7255
+ const repo = need7(args, "repo");
6871
7256
  if (!repo.startsWith("/"))
6872
- throw new ArgError2(`"repo" must be an absolute path on the node`);
7257
+ throw new ArgError3(`"repo" must be an absolute path on the node`);
6873
7258
  const kind = optionalEnum(args, "runtime", RUNTIME_KINDS);
6874
- const profile = optional6(args, "profile");
7259
+ const profile = optional7(args, "profile");
6875
7260
  if (profile !== undefined && kind === undefined)
6876
- throw new ArgError2(`"profile" needs "runtime" (e.g. openshell)`);
7261
+ throw new ArgError3(`"profile" needs "runtime" (e.g. openshell)`);
6877
7262
  const nodeLabels = optionalLabels(args, "nodeLabels");
6878
- const machine = optional6(args, "machine");
7263
+ const machine = optional7(args, "machine");
6879
7264
  const prefer = optionalBool(args, "prefer") === true;
6880
7265
  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") }) };
7266
+ throw new ArgError3(`"prefer" needs "machine" or "nodeLabels"`);
7267
+ const workspace = { repo, ...definedFields({ mode: optionalEnum(args, "mode", WORKSPACE_MODES), branch: optional7(args, "branch"), base: optional7(args, "base"), remote: optional7(args, "remote") }) };
6883
7268
  return {
6884
7269
  ...definedFields({
6885
- key: optional6(args, "key"),
6886
- name: optional6(args, "name")?.replace(/^@+/, ""),
7270
+ key: optional7(args, "key"),
7271
+ name: optional7(args, "name")?.replace(/^@+/, ""),
6887
7272
  roles: optionalStrings(args, "roles")?.map((r) => r.replace(/^@@/, "")),
6888
- harness: optional6(args, "harness"),
6889
- agent: optional6(args, "agent"),
6890
- model: optional6(args, "model")
7273
+ harness: optional7(args, "harness"),
7274
+ agent: optional7(args, "agent"),
7275
+ model: optional7(args, "model")
6891
7276
  }),
6892
7277
  workspace,
6893
7278
  ...definedFields({
6894
7279
  runtime: kind === undefined ? undefined : { kind, ...profile !== undefined ? { profile } : {} },
6895
7280
  placement: definedFields({ labels: nodeLabels, machine, mode: prefer ? "prefer" : undefined }),
6896
- prompt: optional6(args, "prompt"),
7281
+ prompt: optional7(args, "prompt"),
6897
7282
  labels: optionalLabels(args, "labels"),
6898
- end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout: optional6(args, "timeout"), cleanup: optionalEnum(args, "cleanup", CLEANUP_POLICIES) })
7283
+ end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout: optional7(args, "timeout"), cleanup: optionalEnum(args, "cleanup", CLEANUP_POLICIES) })
6899
7284
  })
6900
7285
  };
6901
7286
  }
@@ -6934,14 +7319,14 @@ ${box.messages.map(envelope3).join(`
6934
7319
  `) : "inbox empty";
6935
7320
  }
6936
7321
  function messageText({ message, answer }) {
6937
- const lines = [NOT_FROM_USER, `${message.kind} ${message.id}: ${message.state}${message.delivery === "dropped" ? ` · ${deliveryText("dropped")}` : ""}`, envelope3(message)];
7322
+ const lines = [NOT_FROM_USER, `${message.kind} ${message.id}: ${message.state}${message.delivery === "dropped" || message.delivery === "expired" ? ` · ${deliveryText(message.delivery)}` : ""}`, envelope3(message)];
6938
7323
  if (answer)
6939
7324
  lines.push(`${message.state} by ${answer.id}:`, envelope3(answer));
6940
7325
  return lines.join(`
6941
7326
  `);
6942
7327
  }
6943
7328
  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" };
7329
+ var STRING6 = { type: "string" };
6945
7330
  var SEND = {
6946
7331
  wake: { type: "boolean", description: "false: admit the message without starting a turn." },
6947
7332
  urgent: { type: "boolean", description: "Steer into the recipient's running turn." },
@@ -6964,7 +7349,7 @@ var archiveTools = (archived) => [{
6964
7349
  organizes: true,
6965
7350
  idempotent: true,
6966
7351
  run: async (hub, args, signal) => {
6967
- const addr = need6(args, "addr");
7352
+ const addr = need7(args, "addr");
6968
7353
  const result = await hub.setArchived(addr, archived, { signal });
6969
7354
  return `${archived ? "Archived" : "Unarchived"} ${addr} (${result.sessionID})${result.changed ? "" : "; already in that state"}`;
6970
7355
  }
@@ -6978,7 +7363,7 @@ var pinTools = (pinned) => [{
6978
7363
  organizes: true,
6979
7364
  idempotent: true,
6980
7365
  run: async (hub, args, signal) => {
6981
- const addr = need6(args, "addr");
7366
+ const addr = need7(args, "addr");
6982
7367
  const r = await hub.setPinned(addr, pinned, { signal });
6983
7368
  return `${pinned ? "Pinned" : "Unpinned"} ${addr} (${r.sessionID})${r.changed ? "" : "; already in that state"}`;
6984
7369
  }
@@ -6986,15 +7371,15 @@ var pinTools = (pinned) => [{
6986
7371
  var archiveInactiveTool = {
6987
7372
  name: "archive_inactive",
6988
7373
  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" } },
7374
+ properties: { preview: { type: "boolean" }, inactiveFor: STRING6, limit: { type: "integer", minimum: 1, maximum: 200 }, previewToken: STRING6, confirm: { type: "boolean" } },
6990
7375
  readOnly: false,
6991
7376
  organizes: true,
6992
7377
  run: async (hub, args, signal) => {
6993
7378
  if (args.preview === true)
6994
- return JSON.stringify(await hub.previewArchiveInactive(need6(args, "inactiveFor"), optionalInt(args, "limit", 1, 200) ?? 50, { signal }));
7379
+ return JSON.stringify(await hub.previewArchiveInactive(need7(args, "inactiveFor"), optionalInt(args, "limit", 1, 200) ?? 50, { signal }));
6995
7380
  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");
7381
+ return JSON.stringify(await hub.archiveInactive(need7(args, "previewToken"), { signal }));
7382
+ throw new ArgError3("Use preview:true with inactiveFor, then confirm:true with previewToken");
6998
7383
  }
6999
7384
  };
7000
7385
  var intentTools = [
@@ -7004,44 +7389,44 @@ var intentTools = [
7004
7389
  properties: { addr: ADDR2 },
7005
7390
  required: ["addr"],
7006
7391
  readOnly: true,
7007
- run: async (hub, args, signal) => intentLine(need6(args, "addr"), (await hub.intent(need6(args, "addr"), { signal })).intent)
7392
+ run: async (hub, args, signal) => intentLine(need7(args, "addr"), (await hub.intent(need7(args, "addr"), { signal })).intent)
7008
7393
  },
7009
7394
  {
7010
7395
  name: "session_intent_set",
7011
7396
  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 } },
7397
+ properties: { addr: ADDR2, text: STRING6, clear: { type: "boolean" }, expectedRevision: { type: "integer", minimum: 0 } },
7013
7398
  required: ["addr"],
7014
7399
  readOnly: false,
7015
7400
  organizes: true,
7016
7401
  idempotent: true,
7017
7402
  run: async (hub, args, signal) => {
7018
- const addr = need6(args, "addr");
7019
- const text = optional6(args, "text");
7403
+ const addr = need7(args, "addr");
7404
+ const text = optional7(args, "text");
7020
7405
  const clear = args.clear === true;
7021
7406
  if (clear === (text !== undefined))
7022
- throw new ArgError2('Give "text" or "clear": true');
7407
+ throw new ArgError3('Give "text" or "clear": true');
7023
7408
  const expectedRevision = optionalInt(args, "expectedRevision", 0, Number.MAX_SAFE_INTEGER);
7024
7409
  return intentLine(addr, await writeIntent(hub, addr, clear ? null : text, { signal, ...expectedRevision !== undefined ? { expectedRevision } : {} }));
7025
7410
  }
7026
7411
  }
7027
7412
  ];
7028
7413
  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)." };
7414
+ var ROLE2 = { ...STRING6, description: "A role (reviewer, @@reviewer or reviewer.project)." };
7415
+ var ROLE_PROJECT = { ...STRING6, description: "Scope the role to this project (as role.project)." };
7031
7416
  var MANAGE_NOTE = "Needs scope sessions:manage for an app token";
7032
7417
  var roleText2 = (r) => `@@${r.role}${r.project !== undefined ? `.${r.project}` : ""}`;
7033
7418
  var nameTools = [
7034
7419
  {
7035
7420
  name: "name",
7036
7421
  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)." } },
7422
+ properties: { addr: TARGET, name: { ...STRING6, description: "The name: 2–40 characters a-z, 0-9 and - (a leading @ is fine)." } },
7038
7423
  required: ["name"],
7039
7424
  readOnly: false,
7040
7425
  organizes: true,
7041
7426
  idempotent: true,
7042
7427
  run: async (hub, args, signal) => {
7043
- const session = optional6(args, "addr");
7044
- const r = await hub.name(need6(args, "name"), { ...session ? { session } : {}, signal });
7428
+ const session = optional7(args, "addr");
7429
+ const r = await hub.name(need7(args, "name"), { ...session ? { session } : {}, signal });
7045
7430
  return `Named ${r.sessionID} @${r.name}${r.alias ? ` (@${r.alias} stays an alias for 24 h)` : ""}`;
7046
7431
  }
7047
7432
  },
@@ -7054,9 +7439,9 @@ var nameTools = [
7054
7439
  organizes: true,
7055
7440
  idempotent: true,
7056
7441
  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 });
7442
+ const session = optional7(args, "addr");
7443
+ const project = optional7(args, "project");
7444
+ const r = await hub.claimRole(need7(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, ...optionalBool(args, "exclusive") ? { exclusive: true } : {}, signal });
7060
7445
  return `${r.sessionID} holds ${roleText2(r)} (generation ${r.generation}${r.exclusive ? ", exclusive" : ""})`;
7061
7446
  }
7062
7447
  },
@@ -7068,9 +7453,9 @@ var nameTools = [
7068
7453
  readOnly: false,
7069
7454
  organizes: true,
7070
7455
  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 });
7456
+ const session = optional7(args, "addr");
7457
+ const project = optional7(args, "project");
7458
+ const r = await hub.releaseRole(need7(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, signal });
7074
7459
  return `${r.sessionID} released ${roleText2(r)}`;
7075
7460
  }
7076
7461
  }
@@ -7079,16 +7464,16 @@ var eventsTool = {
7079
7464
  name: "events",
7080
7465
  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
7466
  properties: {
7082
- cursor: { ...STRING5, description: "The cursor from the previous events answer (hev1_…); omit to start at the latest." },
7467
+ cursor: { ...STRING6, description: "The cursor from the previous events answer (hev1_…); omit to start at the latest." },
7083
7468
  wait: { type: "integer", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait while nothing matches (0–${WAIT_POLL_MAX_S}).` },
7084
7469
  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)." }
7470
+ types: { type: "array", items: STRING6, description: "Only these event types (exact, or a family like launch.*)." },
7471
+ sessions: { type: "array", items: STRING6, description: "Only events of these sessions (ses_…, @name, @@role)." }
7087
7472
  },
7088
7473
  readOnly: true,
7089
7474
  run: async (hub, args, signal) => {
7090
7475
  const page = await hub.events({
7091
- cursor: optional6(args, "cursor"),
7476
+ cursor: optional7(args, "cursor"),
7092
7477
  wait: optionalInt(args, "wait", 0, WAIT_POLL_MAX_S),
7093
7478
  limit: optionalInt(args, "limit", 1, 200),
7094
7479
  types: optionalStrings(args, "types"),
@@ -7103,13 +7488,13 @@ var webhookTools = [
7103
7488
  {
7104
7489
  name: "webhook_create",
7105
7490
  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 },
7491
+ properties: { url: STRING6, events: { type: "array", items: STRING6 }, label: STRING6 },
7107
7492
  required: ["url"],
7108
7493
  readOnly: false,
7109
7494
  run: async (hub, args, signal) => {
7110
7495
  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 });
7496
+ const label = optional7(args, "label");
7497
+ const r = await hub.webhookCreate({ url: need7(args, "url"), ...events ? { events } : {}, ...label ? { label } : {} }, { signal });
7113
7498
  return `${webhookText(r.webhook)}
7114
7499
  signing secret (shown once): ${r.secret}`;
7115
7500
  }
@@ -7128,11 +7513,11 @@ signing secret (shown once): ${r.secret}`;
7128
7513
  {
7129
7514
  name: "webhook_show",
7130
7515
  description: "Show one webhook and its retained delivery attempts; never its secret.",
7131
- properties: { id: STRING5 },
7516
+ properties: { id: STRING6 },
7132
7517
  required: ["id"],
7133
7518
  readOnly: true,
7134
7519
  run: async (hub, args, signal) => {
7135
- const r = await hub.webhook(need6(args, "id"), { signal });
7520
+ const r = await hub.webhook(need7(args, "id"), { signal });
7136
7521
  return `${webhookText(r.webhook)}
7137
7522
  ${JSON.stringify(r.deliveries)}`;
7138
7523
  }
@@ -7140,25 +7525,25 @@ ${JSON.stringify(r.deliveries)}`;
7140
7525
  {
7141
7526
  name: "webhook_delete",
7142
7527
  description: "Delete one webhook and its pending deliveries.",
7143
- properties: { id: STRING5 },
7528
+ properties: { id: STRING6 },
7144
7529
  required: ["id"],
7145
7530
  readOnly: false,
7146
7531
  organizes: true,
7147
7532
  idempotent: false,
7148
7533
  run: async (hub, args, signal) => {
7149
- const id = need6(args, "id");
7534
+ const id = need7(args, "id");
7150
7535
  await hub.webhookDelete(id, { signal });
7151
7536
  return `Deleted webhook ${id}`;
7152
7537
  }
7153
7538
  }
7154
7539
  ];
7155
7540
  async function profileTarget(hub, args, signal) {
7156
- const addr = optional6(args, "addr");
7541
+ const addr = optional7(args, "addr");
7157
7542
  if (addr)
7158
7543
  return addr;
7159
7544
  const { actor } = await hub.whoami({ signal });
7160
7545
  if (actor.kind !== "session")
7161
- throw new ArgError2(`"addr" is required unless this credential is a session's`);
7546
+ throw new ArgError3(`"addr" is required unless this credential is a session's`);
7162
7547
  return actor.sessionID;
7163
7548
  }
7164
7549
  var optionalParams = (args) => optionalLabels(args, "params") ?? {};
@@ -7192,11 +7577,11 @@ var profileTools = [
7192
7577
  {
7193
7578
  name: "profile_skill",
7194
7579
  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 },
7580
+ properties: { name: STRING6, addr: ADDR2 },
7196
7581
  required: ["name"],
7197
7582
  readOnly: true,
7198
7583
  run: async (hub, args, signal) => {
7199
- const r = await hub.profiles.sessionSkill(await profileTarget(hub, args, signal), need6(args, "name"), { signal });
7584
+ const r = await hub.profiles.sessionSkill(await profileTarget(hub, args, signal), need7(args, "name"), { signal });
7200
7585
  return `skill ${r.skill.name} (${r.profileID} v${r.version}):
7201
7586
  ${sanitizeText(r.skill.body)}`;
7202
7587
  }
@@ -7204,11 +7589,11 @@ ${sanitizeText(r.skill.body)}`;
7204
7589
  {
7205
7590
  name: "profile_command",
7206
7591
  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 },
7592
+ properties: { name: STRING6, params: { type: "object", additionalProperties: STRING6, description: "Parameter name → text value." }, addr: ADDR2 },
7208
7593
  required: ["name"],
7209
7594
  readOnly: true,
7210
7595
  run: async (hub, args, signal) => {
7211
- const r = await hub.profiles.sessionRender(await profileTarget(hub, args, signal), need6(args, "name"), optionalParams(args), { signal });
7596
+ const r = await hub.profiles.sessionRender(await profileTarget(hub, args, signal), need7(args, "name"), optionalParams(args), { signal });
7212
7597
  return r.prompt;
7213
7598
  }
7214
7599
  },
@@ -7227,13 +7612,13 @@ ${sanitizeText(r.skill.body)}`;
7227
7612
  {
7228
7613
  name: "profile_usage",
7229
7614
  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 },
7615
+ properties: { id: STRING6, limit: { type: "integer", minimum: 1, maximum: 200, description: "Sessions per page (default 50)." }, cursor: STRING6 },
7231
7616
  required: ["id"],
7232
7617
  readOnly: true,
7233
7618
  run: async (hub, args, signal) => {
7234
7619
  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 });
7620
+ const cursor = optional7(args, "cursor");
7621
+ const u = await hub.profiles.usage(need7(args, "id"), { ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {}, signal });
7237
7622
  const c = u.counts;
7238
7623
  const plural = (n, word, many = `${word}s`) => `${n} ${n === 1 ? word : many}`;
7239
7624
  const lines = [`${u.profileID} (latest v${u.latestVersion})`];
@@ -7272,16 +7657,16 @@ ${sanitizeText(r.skill.body)}`;
7272
7657
  var needRevision = (args, key = "expectedRevision") => {
7273
7658
  const value = optionalInt(args, key, 0, Number.MAX_SAFE_INTEGER);
7274
7659
  if (value === undefined)
7275
- throw new ArgError2(`"${key}" is required: the revision you read`);
7660
+ throw new ArgError3(`"${key}" is required: the revision you read`);
7276
7661
  return value;
7277
7662
  };
7278
7663
  var idemOption = (args) => {
7279
- const key = optional6(args, "idempotencyKey");
7664
+ const key = optional7(args, "idempotencyKey");
7280
7665
  return key !== undefined ? { idempotencyKey: key } : {};
7281
7666
  };
7282
7667
  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." };
7668
+ var REVISION4 = { type: "integer", minimum: 0, description: "The revision you read (a stale one answers conflict with the current revision)." };
7669
+ var IDEMPOTENCY3 = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
7285
7670
  var MEMBER_ROLES = ["worker", "observer"];
7286
7671
  var ASSIGNMENT_STATUSES = ["open", "in_progress", "blocked", "done", "cancelled"];
7287
7672
  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 +7691,7 @@ var workspaceTools = [
7306
7691
  required: ["workspace"],
7307
7692
  readOnly: true,
7308
7693
  run: async (hub, args, signal) => {
7309
- const id = need6(args, "workspace");
7694
+ const id = need7(args, "workspace");
7310
7695
  const [{ workspace, bindings }, { members }, graph] = await Promise.all([hub.workspaces.get(id, { signal }), hub.workspaces.members(id, { signal }), hub.workspaces.supervision(id, { signal })]);
7311
7696
  const lines = [wsLine(workspace)];
7312
7697
  if (workspace.masterMemberID)
@@ -7322,39 +7707,39 @@ var workspaceTools = [
7322
7707
  {
7323
7708
  name: "workspace_create",
7324
7709
  description: `Create a coordination workspace. ${WS_SCOPES}`,
7325
- properties: { title: STRING5, description: STRING5, tags: { type: "array", items: STRING5 }, idempotencyKey: IDEMPOTENCY2 },
7710
+ properties: { title: STRING6, description: STRING6, tags: { type: "array", items: STRING6 }, idempotencyKey: IDEMPOTENCY3 },
7326
7711
  required: ["title"],
7327
7712
  readOnly: false,
7328
7713
  run: async (hub, args, signal) => {
7329
- const description = optional6(args, "description");
7714
+ const description = optional7(args, "description");
7330
7715
  const tags = optionalStrings(args, "tags");
7331
- const { workspace } = await hub.workspaces.create({ title: need6(args, "title"), ...description ? { description } : {}, ...tags ? { tags } : {} }, { ...idemOption(args), signal });
7716
+ const { workspace } = await hub.workspaces.create({ title: need7(args, "title"), ...description ? { description } : {}, ...tags ? { tags } : {} }, { ...idemOption(args), signal });
7332
7717
  return `created ${wsLine(workspace)}`;
7333
7718
  }
7334
7719
  },
7335
7720
  {
7336
7721
  name: "workspace_update",
7337
7722
  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 } },
7723
+ properties: { workspace: WORKSPACE, expectedRevision: REVISION4, title: STRING6, description: STRING6, tags: { type: "array", items: STRING6 } },
7339
7724
  required: ["workspace", "expectedRevision"],
7340
7725
  readOnly: false,
7341
7726
  run: async (hub, args, signal) => {
7342
- const patch = definedFields({ title: optional6(args, "title"), description: optional6(args, "description"), tags: optionalStrings(args, "tags") });
7727
+ const patch = definedFields({ title: optional7(args, "title"), description: optional7(args, "description"), tags: optionalStrings(args, "tags") });
7343
7728
  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 });
7729
+ throw new ArgError3(`give "title", "description" or "tags"`);
7730
+ const { workspace } = await hub.workspaces.update(need7(args, "workspace"), { expectedRevision: needRevision(args), ...patch }, { signal });
7346
7731
  return `updated ${wsLine(workspace)}`;
7347
7732
  }
7348
7733
  },
7349
7734
  ...["archive", "restore"].map((verb) => ({
7350
7735
  name: `workspace_${verb}`,
7351
7736
  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 },
7737
+ properties: { workspace: WORKSPACE, expectedRevision: REVISION4 },
7353
7738
  required: ["workspace", "expectedRevision"],
7354
7739
  readOnly: false,
7355
7740
  idempotent: true,
7356
7741
  run: async (hub, args, signal) => {
7357
- const id = need6(args, "workspace");
7742
+ const id = need7(args, "workspace");
7358
7743
  if (verb === "archive") {
7359
7744
  const r = await hub.workspaces.archive(id, needRevision(args), { signal });
7360
7745
  return `archived ${wsLine(r.workspace)}; ${r.departed} member(s) departed, ${r.supervision.detached} supervision link(s) detached`;
@@ -7365,77 +7750,77 @@ var workspaceTools = [
7365
7750
  {
7366
7751
  name: "workspace_bind",
7367
7752
  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 },
7753
+ properties: { workspace: WORKSPACE, machineID: STRING6, repo: STRING6, label: STRING6, idempotencyKey: IDEMPOTENCY3 },
7369
7754
  required: ["workspace", "machineID", "repo"],
7370
7755
  readOnly: false,
7371
7756
  idempotent: true,
7372
7757
  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 });
7758
+ const label = optional7(args, "label");
7759
+ const r = await hub.workspaces.bind(need7(args, "workspace"), { machineID: need7(args, "machineID"), repo: need7(args, "repo"), ...label ? { label } : {} }, { ...idemOption(args), signal });
7375
7760
  return `${r.created ? "bound" : "already bound"} ${r.binding.id} ${r.binding.machineID}:${r.binding.repo} r${r.binding.revision}`;
7376
7761
  }
7377
7762
  },
7378
7763
  {
7379
7764
  name: "workspace_unbind",
7380
7765
  description: `Remove a repository binding under the binding's revision. ${WS_SCOPES}`,
7381
- properties: { workspace: WORKSPACE, bindingID: STRING5, expectedRevision: REVISION3 },
7766
+ properties: { workspace: WORKSPACE, bindingID: STRING6, expectedRevision: REVISION4 },
7382
7767
  required: ["workspace", "bindingID", "expectedRevision"],
7383
7768
  readOnly: false,
7384
7769
  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")}`;
7770
+ await hub.workspaces.unbind(need7(args, "workspace"), need7(args, "bindingID"), needRevision(args), { signal });
7771
+ return `unbound ${need7(args, "bindingID")}`;
7387
7772
  }
7388
7773
  },
7389
7774
  {
7390
7775
  name: "workspace_join",
7391
7776
  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 },
7777
+ properties: { workspace: WORKSPACE, sessionID: STRING6, role: { type: "string", enum: [...MEMBER_ROLES] }, idempotencyKey: IDEMPOTENCY3 },
7393
7778
  required: ["workspace", "sessionID", "role"],
7394
7779
  readOnly: false,
7395
7780
  idempotent: true,
7396
7781
  run: async (hub, args, signal) => {
7397
7782
  const role = optionalEnum(args, "role", MEMBER_ROLES);
7398
7783
  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 });
7784
+ throw new ArgError3(`"role" is one of ${MEMBER_ROLES.join(", ")}`);
7785
+ const { member, created } = await hub.workspaces.join(need7(args, "workspace"), { sessionID: need7(args, "sessionID"), role }, { ...idemOption(args), signal });
7401
7786
  return `member ${member.id} (${member.sessionID}, ${member.role}, generation ${member.generation}, r${member.revision}) ${created ? "joined" : "was already a member"}`;
7402
7787
  }
7403
7788
  },
7404
7789
  {
7405
7790
  name: "workspace_leave",
7406
7791
  description: `Remove a member under its revision; its supervision links are detached. ${WS_SCOPES}`,
7407
- properties: { workspace: WORKSPACE, memberID: STRING5, expectedRevision: REVISION3 },
7792
+ properties: { workspace: WORKSPACE, memberID: STRING6, expectedRevision: REVISION4 },
7408
7793
  required: ["workspace", "memberID", "expectedRevision"],
7409
7794
  readOnly: false,
7410
7795
  idempotent: true,
7411
7796
  run: async (hub, args, signal) => {
7412
- const r = await hub.workspaces.leave(need6(args, "workspace"), need6(args, "memberID"), needRevision(args), { signal });
7797
+ const r = await hub.workspaces.leave(need7(args, "workspace"), need7(args, "memberID"), needRevision(args), { signal });
7413
7798
  return `member ${r.member.id} left (${r.member.leftReason ?? "removed"}); ${r.supervision.detached} supervision link(s) detached`;
7414
7799
  }
7415
7800
  },
7416
7801
  {
7417
7802
  name: "workspace_assign",
7418
7803
  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 },
7804
+ properties: { workspace: WORKSPACE, memberID: STRING6, title: STRING6, criteria: { type: "array", items: STRING6 }, ref: STRING6, idempotencyKey: IDEMPOTENCY3 },
7420
7805
  required: ["workspace", "memberID", "title"],
7421
7806
  readOnly: false,
7422
7807
  run: async (hub, args, signal) => {
7423
7808
  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 });
7809
+ const ref = optional7(args, "ref");
7810
+ 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
7811
  return `assignment ${assignment.id} → ${assignment.memberID}: ${assignment.status}, r${assignment.revision}`;
7427
7812
  }
7428
7813
  },
7429
7814
  {
7430
7815
  name: "workspace_assignments",
7431
7816
  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] } },
7817
+ properties: { workspace: WORKSPACE, memberID: STRING6, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] } },
7433
7818
  required: ["workspace"],
7434
7819
  readOnly: true,
7435
7820
  run: async (hub, args, signal) => {
7436
- const memberID = optional6(args, "memberID");
7821
+ const memberID = optional7(args, "memberID");
7437
7822
  const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
7438
- const r = await hub.workspaces.assignments(need6(args, "workspace"), { ...memberID ? { memberID } : {}, ...status ? { status } : {}, limit: 200, signal });
7823
+ const r = await hub.workspaces.assignments(need7(args, "workspace"), { ...memberID ? { memberID } : {}, ...status ? { status } : {}, limit: 200, signal });
7439
7824
  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
7825
  `) : "no assignments";
7441
7826
  }
@@ -7443,16 +7828,16 @@ var workspaceTools = [
7443
7828
  {
7444
7829
  name: "workspace_assignment_update",
7445
7830
  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 },
7831
+ properties: { workspace: WORKSPACE, assignmentID: STRING6, expectedRevision: REVISION4, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] }, memberID: STRING6 },
7447
7832
  required: ["workspace", "assignmentID", "expectedRevision"],
7448
7833
  readOnly: false,
7449
7834
  run: async (hub, args, signal) => {
7450
7835
  const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
7451
- const memberID = optional6(args, "memberID");
7836
+ const memberID = optional7(args, "memberID");
7452
7837
  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");
7838
+ throw new ArgError3(`give exactly one of "status" or "memberID"`);
7839
+ const id = need7(args, "workspace");
7840
+ const assignmentID = need7(args, "assignmentID");
7456
7841
  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
7842
  return `assignment ${assignment.id} → ${assignment.memberID}: ${assignment.status}, r${assignment.revision}`;
7458
7843
  }
@@ -7460,16 +7845,16 @@ var workspaceTools = [
7460
7845
  {
7461
7846
  name: "workspace_supervise",
7462
7847
  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 },
7848
+ 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
7849
  required: ["workspace", "memberID", "supervisor", "expectedRevision"],
7465
7850
  readOnly: false,
7466
7851
  run: async (hub, args, signal) => {
7467
- const supervisor = args.supervisor === null ? null : need6(args, "supervisor");
7852
+ const supervisor = args.supervisor === null ? null : need7(args, "supervisor");
7468
7853
  const order = args.order;
7469
7854
  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` : ""}`;
7855
+ throw new ArgError3(`"order" must be a number`);
7856
+ const r = await hub.workspaces.supervise(need7(args, "workspace"), { memberID: need7(args, "memberID"), supervisorMemberID: supervisor, ...order !== undefined ? { order } : {}, expectedRevision: needRevision(args) }, { signal });
7857
+ 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
7858
  }
7474
7859
  },
7475
7860
  {
@@ -7479,7 +7864,7 @@ var workspaceTools = [
7479
7864
  required: ["workspace"],
7480
7865
  readOnly: true,
7481
7866
  run: async (hub, args, signal) => {
7482
- const v = await hub.workspaces.master(need6(args, "workspace"), { signal });
7867
+ const v = await hub.workspaces.master(need7(args, "workspace"), { signal });
7483
7868
  const line = (m) => `${m.member.id} ${m.member.sessionID} generation ${m.member.generation} ${m.presence}${m.member.leftReason ? ` (${m.member.leftReason})` : ""}`;
7484
7869
  return [
7485
7870
  `workspace ${v.workspaceID} r${v.revision}`,
@@ -7493,17 +7878,17 @@ var workspaceTools = [
7493
7878
  {
7494
7879
  name: "workspace_master_replace",
7495
7880
  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 },
7881
+ properties: { workspace: WORKSPACE, sessionID: STRING6, launchID: STRING6, expectedRevision: REVISION4, idempotencyKey: IDEMPOTENCY3 },
7497
7882
  required: ["workspace", "expectedRevision"],
7498
7883
  readOnly: false,
7499
7884
  idempotent: true,
7500
7885
  run: async (hub, args, signal) => {
7501
- const sessionID = optional6(args, "sessionID");
7502
- const launchID = optional6(args, "launchID");
7886
+ const sessionID = optional7(args, "sessionID");
7887
+ const launchID = optional7(args, "launchID");
7503
7888
  if (sessionID === undefined === (launchID === undefined))
7504
- throw new ArgError2(`give exactly one of "sessionID" or "launchID"`);
7889
+ throw new ArgError3(`give exactly one of "sessionID" or "launchID"`);
7505
7890
  const target = sessionID !== undefined ? { sessionID } : { launchID };
7506
- const r = await hub.workspaces.replaceMaster(need6(args, "workspace"), { ...target, expectedRevision: needRevision(args) }, { ...idemOption(args), signal });
7891
+ const r = await hub.workspaces.replaceMaster(need7(args, "workspace"), { ...target, expectedRevision: needRevision(args) }, { ...idemOption(args), signal });
7507
7892
  if (!r.changed)
7508
7893
  return `${r.master.sessionID} already is the master (${r.master.id}); nothing changed`;
7509
7894
  return [
@@ -7521,7 +7906,7 @@ var workspaceTools = [
7521
7906
  required: ["workspace"],
7522
7907
  readOnly: true,
7523
7908
  run: async (hub, args, signal) => {
7524
- const b = await hub.workspaces.board(need6(args, "workspace"), { signal });
7909
+ const b = await hub.workspaces.board(need7(args, "workspace"), { signal });
7525
7910
  const more = (truncated) => truncated ? " (more not shown)" : "";
7526
7911
  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
7912
  const lines = [wsLine(b.workspace), ...b.workspace.masterMemberID ? [`master: ${b.workspace.masterMemberID}`] : []];
@@ -7546,6 +7931,7 @@ var TOOLS = [
7546
7931
  eventsTool,
7547
7932
  ...webhookTools,
7548
7933
  ...profileTools,
7934
+ ...profileLifecycleTools,
7549
7935
  ...workspaceTools,
7550
7936
  ...artifactTools,
7551
7937
  ...artifactJobTools,
@@ -7570,14 +7956,14 @@ var TOOLS = [
7570
7956
  name: "sessions",
7571
7957
  description: "The sessions as a flat list, optionally filtered.",
7572
7958
  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." }
7959
+ project: { ...STRING6, description: "Only this project." },
7960
+ state: { ...STRING6, description: "Only these states (comma-separated, e.g. busy,blocked)." },
7961
+ machine: { ...STRING6, description: "Only this machine." },
7962
+ label: { type: "array", items: STRING6, description: "key=value labels that must all match." }
7577
7963
  },
7578
7964
  readOnly: true,
7579
7965
  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 });
7966
+ const body = await hub.sessions({ project: optional7(args, "project"), state: optional7(args, "state"), machine: optional7(args, "machine"), label: optionalStrings(args, "label"), signal });
7581
7967
  return sessionsText(body.sessions ?? []);
7582
7968
  }
7583
7969
  },
@@ -7587,7 +7973,7 @@ var TOOLS = [
7587
7973
  properties: { addr: ADDR2 },
7588
7974
  required: ["addr"],
7589
7975
  readOnly: true,
7590
- run: async (hub, args, signal) => showText(await hub.show(need6(args, "addr"), { signal }))
7976
+ run: async (hub, args, signal) => showText(await hub.show(need7(args, "addr"), { signal }))
7591
7977
  },
7592
7978
  {
7593
7979
  name: "log",
@@ -7596,7 +7982,7 @@ var TOOLS = [
7596
7982
  required: ["addr"],
7597
7983
  readOnly: true,
7598
7984
  run: async (hub, args, signal) => {
7599
- const addr = need6(args, "addr");
7985
+ const addr = need7(args, "addr");
7600
7986
  const n = optionalInt(args, "n", 1, 200);
7601
7987
  return logText((await hub.log(addr, { ...n !== undefined ? { n } : {}, signal })).rows ?? []);
7602
7988
  }
@@ -7605,25 +7991,25 @@ var TOOLS = [
7605
7991
  name: "who",
7606
7992
  description: "Named and role-holding sessions; a team member sees its teams, grouped by team with its reach to each.",
7607
7993
  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)." }
7994
+ project: { ...STRING6, description: "Only this project." },
7995
+ role: { ...STRING6, description: "Only holders of this role (reviewer, @@reviewer or reviewer#team)." },
7996
+ team: { ...STRING6, description: "Only this team (name or ID)." }
7611
7997
  },
7612
7998
  readOnly: true,
7613
7999
  run: async (hub, args, signal) => {
7614
- const body = await hub.who({ project: optional6(args, "project"), role: optional6(args, "role"), team: optional6(args, "team"), signal });
8000
+ const body = await hub.who({ project: optional7(args, "project"), role: optional7(args, "role"), team: optional7(args, "team"), signal });
7615
8001
  return whoText(body.sessions ?? [], body);
7616
8002
  }
7617
8003
  },
7618
8004
  {
7619
8005
  name: "msg",
7620
8006
  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 },
8007
+ properties: { to: TO, text: { ...STRING6, description: "The message." }, ...SEND },
7622
8008
  required: ["to", "text"],
7623
8009
  readOnly: false,
7624
8010
  run: async (hub, args, signal) => {
7625
- const to = need6(args, "to");
7626
- const text = need6(args, "text");
8011
+ const to = need7(args, "to");
8012
+ const text = need7(args, "text");
7627
8013
  const options = { ...sendOptions(args), signal };
7628
8014
  if (optionalBool(args, "all"))
7629
8015
  return sentAllText(await hub.msg(to, text, { ...options, all: true }));
@@ -7635,7 +8021,7 @@ var TOOLS = [
7635
8021
  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
8022
  properties: {
7637
8023
  to: TO,
7638
- text: { ...STRING5, description: "The question." },
8024
+ text: { ...STRING6, description: "The question." },
7639
8025
  wait: { type: "number", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait for the answer (0–${WAIT_POLL_MAX_S}).` },
7640
8026
  expires: { type: ["number", "string"], description: "When the ask expires: seconds, or 30s, 5m, 2h, 1d (1 minute to 7 days; default 24 h)." },
7641
8027
  ...SEND
@@ -7643,14 +8029,14 @@ var TOOLS = [
7643
8029
  required: ["to", "text"],
7644
8030
  readOnly: false,
7645
8031
  run: async (hub, args, signal) => {
7646
- const to = need6(args, "to");
7647
- const text = need6(args, "text");
8032
+ const to = need7(args, "to");
8033
+ const text = need7(args, "text");
7648
8034
  const wait = optionalWait(args);
7649
8035
  const expires = optionalExpires(args);
7650
8036
  const options = { ...sendOptions(args), ...expires !== undefined ? { expires } : {}, signal };
7651
8037
  if (optionalBool(args, "all")) {
7652
8038
  if (wait !== undefined)
7653
- throw new ArgError2(`"wait" cannot be combined with "all"`);
8039
+ throw new ArgError3(`"wait" cannot be combined with "all"`);
7654
8040
  return sentAllText(await hub.ask(to, text, { ...options, all: true }));
7655
8041
  }
7656
8042
  const asked = await hub.ask(to, text, { ...options, ...wait !== undefined ? { wait } : {} });
@@ -7662,12 +8048,12 @@ ${openedText(asked.decision)}` : outcome;
7662
8048
  {
7663
8049
  name: "reply",
7664
8050
  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 },
8051
+ properties: { id: MSG_ID, text: { ...STRING6, description: "The reply." }, wake: SEND.wake },
7666
8052
  required: ["id", "text"],
7667
8053
  readOnly: false,
7668
8054
  run: async (hub, args, signal) => {
7669
- const id = need6(args, "id");
7670
- const text = need6(args, "text");
8055
+ const id = need7(args, "id");
8056
+ const text = need7(args, "text");
7671
8057
  const wake = optionalBool(args, "wake");
7672
8058
  return repliedText(await hub.reply(id, text, { ...wake !== undefined ? { wake } : {}, signal }));
7673
8059
  }
@@ -7675,12 +8061,12 @@ ${openedText(asked.decision)}` : outcome;
7675
8061
  {
7676
8062
  name: "decline",
7677
8063
  description: "Decline an ask you received, with a reason.",
7678
- properties: { id: MSG_ID, why: { ...STRING5, description: "The reason." } },
8064
+ properties: { id: MSG_ID, why: { ...STRING6, description: "The reason." } },
7679
8065
  required: ["id", "why"],
7680
8066
  readOnly: false,
7681
8067
  run: async (hub, args, signal) => {
7682
- const id = need6(args, "id");
7683
- return declinedText(await hub.decline(id, need6(args, "why"), { signal }));
8068
+ const id = need7(args, "id");
8069
+ return declinedText(await hub.decline(id, need7(args, "why"), { signal }));
7684
8070
  }
7685
8071
  },
7686
8072
  {
@@ -7700,7 +8086,7 @@ ${openedText(asked.decision)}` : outcome;
7700
8086
  required: ["id"],
7701
8087
  readOnly: true,
7702
8088
  run: async (hub, args, signal) => {
7703
- const id = need6(args, "id");
8089
+ const id = need7(args, "id");
7704
8090
  const wait = optionalWait(args);
7705
8091
  return messageText(await hub.message(id, { ...wait !== undefined ? { wait } : {}, signal }));
7706
8092
  }
@@ -7713,7 +8099,7 @@ ${openedText(asked.decision)}` : outcome;
7713
8099
  readOnly: false,
7714
8100
  organizes: true,
7715
8101
  idempotent: true,
7716
- run: async (hub, args, signal) => mutedText((await hub[verb](need6(args, "addr"), { signal })).muted)
8102
+ run: async (hub, args, signal) => mutedText((await hub[verb](need7(args, "addr"), { signal })).muted)
7717
8103
  })),
7718
8104
  {
7719
8105
  name: "decisions",
@@ -7721,9 +8107,9 @@ ${openedText(asked.decision)}` : outcome;
7721
8107
  properties: { state: { type: "string", enum: [...DECISION_STATES], description: "Only Decisions in this state." } },
7722
8108
  readOnly: true,
7723
8109
  run: async (hub, args, signal) => {
7724
- const state = optional6(args, "state");
8110
+ const state = optional7(args, "state");
7725
8111
  if (state !== undefined && !DECISION_STATES.includes(state))
7726
- throw new ArgError2(`"state" is one of ${DECISION_STATES.join(", ")}`);
8112
+ throw new ArgError3(`"state" is one of ${DECISION_STATES.join(", ")}`);
7727
8113
  const { decisions } = await hub.decisions({ ...state ? { state } : {}, signal });
7728
8114
  return decisions.length ? `${DECISIONS_NOTE}
7729
8115
  ${decisionsText(decisions)}` : decisionsText(decisions);
@@ -7736,7 +8122,7 @@ ${decisionsText(decisions)}` : decisionsText(decisions);
7736
8122
  required: ["id"],
7737
8123
  readOnly: true,
7738
8124
  run: async (hub, args, signal) => `${DECISIONS_NOTE}
7739
- ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
8125
+ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
7740
8126
  },
7741
8127
  {
7742
8128
  name: "approve",
@@ -7745,8 +8131,8 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7745
8131
  required: ["addr", "requestID"],
7746
8132
  readOnly: false,
7747
8133
  run: async (hub, args, signal) => {
7748
- const addr = need6(args, "addr");
7749
- const requestID = need6(args, "requestID");
8134
+ const addr = need7(args, "addr");
8135
+ const requestID = need7(args, "requestID");
7750
8136
  const always = optionalBool(args, "always");
7751
8137
  return approvedText(await hub.approve(addr, requestID, { ...always ? { always } : {}, ...whyOption(args), signal }));
7752
8138
  }
@@ -7758,8 +8144,8 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7758
8144
  required: ["addr", "requestID"],
7759
8145
  readOnly: false,
7760
8146
  run: async (hub, args, signal) => {
7761
- const addr = need6(args, "addr");
7762
- const requestID = need6(args, "requestID");
8147
+ const addr = need7(args, "addr");
8148
+ const requestID = need7(args, "requestID");
7763
8149
  return deniedText(await hub.deny(addr, requestID, { ...whyOption(args), signal }));
7764
8150
  }
7765
8151
  },
@@ -7776,8 +8162,8 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7776
8162
  required: ["addr", "formID"],
7777
8163
  readOnly: false,
7778
8164
  run: async (hub, args, signal) => {
7779
- const addr = need6(args, "addr");
7780
- const formID = need6(args, "formID");
8165
+ const addr = need7(args, "addr");
8166
+ const formID = need7(args, "formID");
7781
8167
  const given = formAnswer(args);
7782
8168
  const options = { ...whyOption(args), signal };
7783
8169
  return answeredText(given === "cancel" ? await hub.cancelForm(addr, formID, options) : await hub.answer(addr, formID, given, options), given === "cancel");
@@ -7788,15 +8174,15 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7788
8174
  description: `Prompt a session (an agent's prompt carries the hub's provenance). ${NEEDS_USER}`,
7789
8175
  properties: {
7790
8176
  addr: ADDR2,
7791
- text: { ...STRING5, description: "The prompt." },
8177
+ text: { ...STRING6, description: "The prompt." },
7792
8178
  delivery: { type: "string", enum: [...DELIVERIES], description: "How it enters a busy session (the spoke picks when unset)." },
7793
8179
  why: WHY
7794
8180
  },
7795
8181
  required: ["addr", "text"],
7796
8182
  readOnly: false,
7797
8183
  run: async (hub, args, signal) => {
7798
- const addr = need6(args, "addr");
7799
- const text = need6(args, "text");
8184
+ const addr = need7(args, "addr");
8185
+ const text = need7(args, "text");
7800
8186
  const delivery = optionalDelivery(args);
7801
8187
  return promptedText(await hub.prompt(addr, text, { ...delivery ? { delivery } : {}, ...whyOption(args), signal }));
7802
8188
  }
@@ -7807,32 +8193,32 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7807
8193
  properties: { addr: ADDR2, why: WHY },
7808
8194
  required: ["addr"],
7809
8195
  readOnly: false,
7810
- run: async (hub, args, signal) => stoppedText(await hub.stop(need6(args, "addr"), { ...whyOption(args), signal }))
8196
+ run: async (hub, args, signal) => stoppedText(await hub.stop(need7(args, "addr"), { ...whyOption(args), signal }))
7811
8197
  },
7812
8198
  {
7813
8199
  name: "launch",
7814
8200
  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
8201
  properties: {
7816
- repo: { ...STRING5, description: "Absolute path of the repository on the node (or where a clone goes)." },
8202
+ repo: { ...STRING6, description: "Absolute path of the repository on the node (or where a clone goes)." },
7817
8203
  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." },
8204
+ branch: { ...STRING6, description: "The branch to create or check out." },
8205
+ base: { ...STRING6, description: "What a new branch starts from." },
8206
+ remote: { ...STRING6, description: "An https or ssh git remote (for clone)." },
8207
+ key: { ...STRING6, description: "Idempotency key: retrying with the same key and arguments answers the same launch." },
8208
+ name: { ...STRING6, description: "The session's @name." },
8209
+ roles: { type: "array", items: STRING6, description: "Roles the session claims (e.g. reviewer.hub)." },
8210
+ harness: { ...STRING6, description: "The harness (default opencode)." },
8211
+ agent: { ...STRING6, description: "The harness agent (e.g. build)." },
8212
+ model: { ...STRING6, description: "provider/model." },
7827
8213
  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." },
8214
+ profile: { ...STRING6, description: "The runtime's sandbox profile (needs runtime)." },
8215
+ machine: { ...STRING6, description: "The machine (name or ID) to run on." },
7830
8216
  nodeLabels: { ...LABELS, description: 'Node labels to place on (e.g. {"os": "darwin"}).' },
7831
8217
  prefer: { type: "boolean", description: "machine and nodeLabels are preferences, not requirements." },
7832
8218
  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." },
8219
+ prompt: { ...STRING6, description: "The session's first prompt." },
7834
8220
  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." },
8221
+ timeout: { ...STRING6, description: "Longest run, e.g. 4h or 90m." },
7836
8222
  cleanup: { type: "string", enum: [...CLEANUP_POLICIES], description: "keep (default) or remove the workspace when it ends." }
7837
8223
  },
7838
8224
  required: ["repo"],
@@ -7847,12 +8233,12 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7847
8233
  name: "launches",
7848
8234
  description: "Your launches, oldest first (an app or session sees the ones it started).",
7849
8235
  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." }
8236
+ state: { ...STRING6, description: "Only these states (comma-separated: queued, placing, provisioning, running, ended)." },
8237
+ label: { type: "array", items: STRING6, description: "key=value launch labels that must all match." }
7852
8238
  },
7853
8239
  readOnly: true,
7854
8240
  run: async (hub, args, signal) => {
7855
- const state = optional6(args, "state")?.split(",").filter(Boolean);
8241
+ const state = optional7(args, "state")?.split(",").filter(Boolean);
7856
8242
  const label = optionalStrings(args, "label");
7857
8243
  return launchesText((await hub.launches.list({ ...state?.length ? { state } : {}, ...label ? { label } : {}, signal })).launches ?? []);
7858
8244
  }
@@ -7863,7 +8249,7 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7863
8249
  properties: { id: LAUNCH_ID },
7864
8250
  required: ["id"],
7865
8251
  readOnly: true,
7866
- run: async (hub, args, signal) => launchText((await hub.launches.get(need6(args, "id"), { signal })).launch)
8252
+ run: async (hub, args, signal) => launchText((await hub.launches.get(need7(args, "id"), { signal })).launch)
7867
8253
  },
7868
8254
  {
7869
8255
  name: "launch_stop",
@@ -7871,7 +8257,7 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
7871
8257
  properties: { id: LAUNCH_ID },
7872
8258
  required: ["id"],
7873
8259
  readOnly: false,
7874
- run: async (hub, args, signal) => stoppedLaunchText((await hub.launches.stop(need6(args, "id"), { signal })).launch)
8260
+ run: async (hub, args, signal) => stoppedLaunchText((await hub.launches.stop(need7(args, "id"), { signal })).launch)
7875
8261
  }
7876
8262
  ];
7877
8263
  var toolList = () => TOOLS.map((t) => ({
@@ -7938,7 +8324,7 @@ function createMcpServer(options) {
7938
8324
  } catch (cause) {
7939
8325
  if (controller.signal.aborted)
7940
8326
  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)}`;
8327
+ const text = cause instanceof ArgError3 || cause instanceof ProfileArgError ? `invalid: ${cause.message}` : cause instanceof CliError ? errorText(cause) : `${tool.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`;
7942
8328
  return ok2(id, toolResult(clean(text, token), true));
7943
8329
  } finally {
7944
8330
  if (inFlight.get(key(id)) === controller)
@@ -8375,7 +8761,7 @@ import { arch as osArch, platform as osPlatform } from "node:os";
8375
8761
  // packages/node/package.json
8376
8762
  var package_default2 = {
8377
8763
  name: "@huynle/hub-node",
8378
- version: "0.8.0",
8764
+ version: "0.8.1",
8379
8765
  private: true,
8380
8766
  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
8767
  license: "MIT",
@@ -9467,7 +9853,7 @@ import path13 from "node:path";
9467
9853
  // packages/runtime-openshell/package.json
9468
9854
  var package_default3 = {
9469
9855
  name: "@huynle/hub-runtime-openshell",
9470
- version: "0.8.0",
9856
+ version: "0.8.1",
9471
9857
  private: true,
9472
9858
  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
9859
  license: "MIT",
@@ -10031,9 +10417,10 @@ var openCodeServiceFile = (env, home) => path14.join(env.XDG_STATE_HOME || path1
10031
10417
  function startOpenCodeWarmers(config, service, log, signal) {
10032
10418
  const roots = () => config.workspaceRoots;
10033
10419
  const directories = config.opencode?.directories;
10034
- new OpenCodeWarmer({ discover: service, log, roots, ...directories ? { directories } : {} }).start(signal);
10420
+ const timeout = config.opencode?.timeoutSeconds !== undefined ? { timeoutMs: config.opencode.timeoutSeconds * 1000 } : {};
10421
+ new OpenCodeWarmer({ discover: service, log, roots, ...timeout, ...directories ? { directories } : {} }).start(signal);
10035
10422
  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);
10423
+ new OpenCodeWarmer({ discover: serverEndpoint(server), log, name: "the OpenCode server", roots, ...timeout, ...server.directories ? { directories: server.directories } : {} }).start(signal);
10037
10424
  }
10038
10425
  }
10039
10426
  async function setupOpenShell(config, credentials, options, env, log) {
@@ -10414,8 +10801,8 @@ var EMBEDDED_TEXT = Object.freeze({
10414
10801
  // packages/cli/src/bootstrap/adapters/node-services/render.ts
10415
10802
  var SERVICE_ENVIRONMENT = Object.freeze({
10416
10803
  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"]
10804
+ "claude-code": ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR", "XDG_STATE_HOME", "HUB_CLAUDE_SOCKET", "HUB_CLAUDE_BIN"],
10805
+ "codex-spoke": ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR", "XDG_STATE_HOME", "CODEX_HOME", "HUB_CODEX_BIN", "HUB_CODEX_DAEMON_SOCKET"]
10419
10806
  });
10420
10807
  var UNIT_ROOT = Object.freeze({ launchd: "launch-agents", "systemd-user": "systemd-user" });
10421
10808
  var assetIDFor = (service, manager) => `service/${manager}/${service}`;
@@ -11358,7 +11745,7 @@ function tail(s) {
11358
11745
  const asks = s.openAsks ? `, ${s.openAsks} open ask${s.openAsks === 1 ? "" : "s"} from you` : "";
11359
11746
  return `${clean2(s.state) || "unknown"}${asks}`;
11360
11747
  }
11361
- function one2(s) {
11748
+ function one3(s) {
11362
11749
  const more = details(s);
11363
11750
  if (s.self)
11364
11751
  return `this session (${[s.sessionID, ...more].join(" · ")}), ${tail(s)}`;
@@ -11371,7 +11758,7 @@ function member(s) {
11371
11758
  }
11372
11759
  function renderReferences(refs) {
11373
11760
  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("; ")}`;
11761
+ const body = r.sessions.length === 1 ? one3(r.sessions[0]) : `${r.sessions.length} sessions: ${r.sessions.map(member).join("; ")}`;
11375
11762
  return `- ${r.address} = ${body}.`;
11376
11763
  });
11377
11764
  const single = refs.length === 1 && refs[0].sessions.length === 1;
@@ -11841,7 +12228,7 @@ import { homedir as homedir5 } from "node:os";
11841
12228
  import path17 from "node:path";
11842
12229
  var SOCKET_PATH_MAX = 103;
11843
12230
  function claudeStateDir(env = process.env, home = homedir5()) {
11844
- return path17.join(hubEnv(env).HUB_STATE_DIR || path17.join(stateBase(home), APP_DIR), "claude-code");
12231
+ return path17.join(resolveHubStateDir(env, home).dir, "claude-code");
11845
12232
  }
11846
12233
  function socketPath(env = process.env, home = homedir5()) {
11847
12234
  return env.HUB_CLAUDE_SOCKET || path17.join(claudeStateDir(env, home), "daemon.sock");
@@ -13141,7 +13528,7 @@ import path22 from "node:path";
13141
13528
  // packages/claude-code/package.json
13142
13529
  var package_default4 = {
13143
13530
  name: "@huynle/hub-claude-code",
13144
- version: "0.8.0",
13531
+ version: "0.8.1",
13145
13532
  private: true,
13146
13533
  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
13534
  license: "MIT",
@@ -13430,7 +13817,7 @@ import path25 from "node:path";
13430
13817
  // packages/preset/package.json
13431
13818
  var package_default5 = {
13432
13819
  name: "@huynle/hub-preset",
13433
- version: "0.8.0",
13820
+ version: "0.8.1",
13434
13821
  private: true,
13435
13822
  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
13823
  license: "MIT",
@@ -13955,7 +14342,7 @@ import { openSync as openSync5, closeSync as closeSync5, readFileSync as readFil
13955
14342
  // packages/codex/package.json
13956
14343
  var package_default6 = {
13957
14344
  name: "@huynle/hub-codex",
13958
- version: "0.8.0",
14345
+ version: "0.8.1",
13959
14346
  private: true,
13960
14347
  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
14348
  license: "MIT",
@@ -13980,7 +14367,7 @@ function daemonSocketPath(env = process.env, home = homedir6()) {
13980
14367
  return override || path26.join(codexHome(env, home), "app-server-control", "app-server-control.sock");
13981
14368
  }
13982
14369
  function codexStateDir(env = process.env, home = homedir6()) {
13983
- return path26.join(hubEnv(env).HUB_STATE_DIR || path26.join(stateBase(home), APP_DIR), "codex");
14370
+ return path26.join(resolveHubStateDir(env, home).dir, "codex");
13984
14371
  }
13985
14372
  var pidFile = (env = process.env, home = homedir6()) => path26.join(codexStateDir(env, home), "spoke.pid");
13986
14373
  var isPid = (n) => typeof n === "number" && Number.isInteger(n) && n > 1;
@@ -15374,7 +15761,7 @@ async function codexMain(argv, io) {
15374
15761
  import path30 from "node:path";
15375
15762
  var SERVICE_VERBS = ["install", "uninstall", "status"];
15376
15763
  var isServiceVerb = (verb) => SERVICE_VERBS.includes(verb ?? "");
15377
- var HUB_PLACES = ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR"];
15764
+ var HUB_PLACES = ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR", "XDG_STATE_HOME"];
15378
15765
  var CLAUDE_CODE_SERVICE = {
15379
15766
  service: "claude-code",
15380
15767
  command: "hub claude-code",
@@ -15516,7 +15903,7 @@ async function runCodex(args, given) {
15516
15903
  // packages/cli/src/setup/commands.ts
15517
15904
  import { existsSync as existsSync9, readFileSync as readFileSync12, renameSync as renameSync7, writeFileSync as writeFileSync8 } from "node:fs";
15518
15905
  import { hostname as hostname4 } from "node:os";
15519
- import path33 from "node:path";
15906
+ import path34 from "node:path";
15520
15907
 
15521
15908
  // packages/cli/src/bootstrap/transaction/result.ts
15522
15909
  var txOk = (value) => ({ ok: true, value });
@@ -17049,6 +17436,7 @@ function installOf(probe, id, shim, roots, context = "host") {
17049
17436
  var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17050
17437
 
17051
17438
  // packages/cli/src/bootstrap/discovery/host.ts
17439
+ import path32 from "node:path";
17052
17440
  function discoverRoots(probe) {
17053
17441
  const home = normalizeAbs(probe.env.HOME) ?? normalizeAbs(probe.home);
17054
17442
  if (!home)
@@ -17058,7 +17446,7 @@ function discoverRoots(probe) {
17058
17446
  const paths = {
17059
17447
  home,
17060
17448
  config,
17061
- state: xdg("XDG_STATE_HOME", ".local/state"),
17449
+ state: hubStateRoot(probe, home) ?? xdg("XDG_STATE_HOME", ".local/state"),
17062
17450
  data: xdg("XDG_DATA_HOME", ".local/share"),
17063
17451
  "user-bin": join(home, ".local/bin"),
17064
17452
  "system-bin": "/usr/local/bin"
@@ -17069,6 +17457,12 @@ function discoverRoots(probe) {
17069
17457
  paths["systemd-user"] = join(config, "systemd/user");
17070
17458
  return ROOT_IDS.flatMap((id) => paths[id] ? [{ id, path: paths[id] }] : []);
17071
17459
  }
17460
+ function hubStateRoot(probe, home) {
17461
+ const env = { ...probe.env, ...probe.env.XDG_STATE_HOME !== undefined ? { XDG_STATE_HOME: normalizeAbs(probe.env.XDG_STATE_HOME) } : {} };
17462
+ const { dir } = resolveHubStateDir(env, home, (p) => probe.lstat(p) !== "missing");
17463
+ const abs = normalizeAbs(dir);
17464
+ return abs && path32.posix.basename(abs) === "hub" ? path32.posix.dirname(abs) : undefined;
17465
+ }
17072
17466
  var unescapeMount = (s) => s.replace(/\\([0-7]{3})/g, (_, o) => String.fromCharCode(parseInt(o, 8)));
17073
17467
  function parseMountinfo(text) {
17074
17468
  const mounts = [];
@@ -20448,7 +20842,7 @@ function createCodexAdapter(options) {
20448
20842
 
20449
20843
  // packages/cli/src/bootstrap/adapters/node-services/control.ts
20450
20844
  import { spawn as nodeSpawn } from "node:child_process";
20451
- import path32 from "node:path";
20845
+ import path33 from "node:path";
20452
20846
  var HEALTH_WINDOW = Object.freeze({ timeoutMs: 1e4, stableMs: 3000, pollMs: 500 });
20453
20847
  var realClock = { now: () => Date.now(), sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
20454
20848
  var UNITS = new Set(SERVICE_IDS.map((s) => serviceName(s, "systemd-user")));
@@ -20488,7 +20882,7 @@ function checkManagerCall(call) {
20488
20882
  if (verb === "print" || verb === "bootout")
20489
20883
  return words.length === 2 && LABELS2.has(labelOfTarget(target) ?? "");
20490
20884
  if (verb === "bootstrap")
20491
- return words.length === 3 && DOMAIN.test(target) && isAbsolutePath(file) && LABELS2.has(path32.basename(file).replace(/\.plist$/, "")) && file.endsWith(".plist");
20885
+ return words.length === 3 && DOMAIN.test(target) && isAbsolutePath(file) && LABELS2.has(path33.basename(file).replace(/\.plist$/, "")) && file.endsWith(".plist");
20492
20886
  return false;
20493
20887
  }
20494
20888
  return false;
@@ -20690,7 +21084,7 @@ var defaultSpawn = (file, words, options) => new Promise((resolve) => {
20690
21084
  function spawnManagerRunner(options) {
20691
21085
  for (const program of ["systemctl", "launchctl"]) {
20692
21086
  const file = options.programs[program];
20693
- if (!isAbsolutePath(file) || path32.basename(file) !== program)
21087
+ if (!isAbsolutePath(file) || path33.basename(file) !== program)
20694
21088
  throw new Error(`${program} must be given as an absolute path to ${program}`);
20695
21089
  }
20696
21090
  const env = {};
@@ -23164,7 +23558,7 @@ async function profileArg(io, value, fallback) {
23164
23558
  return unwrapProfile(defaultProfile(value));
23165
23559
  if (value !== "-" && !value.includes("/") && !value.endsWith(".json"))
23166
23560
  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");
23561
+ 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
23562
  const loaded = loadProfile(parseJSON2(text, "profile"));
23169
23563
  if (!loaded.ok)
23170
23564
  throw issueRefusal(loaded.issues);
@@ -23178,7 +23572,7 @@ function unwrapProfile(r) {
23178
23572
  function manifestArg(io, value) {
23179
23573
  if (value === undefined)
23180
23574
  return;
23181
- const parsed = parseManifest(readText2(path33.resolve(io.cwd ?? process.cwd(), value), "manifest"));
23575
+ const parsed = parseManifest(readText2(path34.resolve(io.cwd ?? process.cwd(), value), "manifest"));
23182
23576
  if (!parsed.ok)
23183
23577
  throw new Refusal("signature_invalid", issuesText(parsed.issues));
23184
23578
  const root = shippedTrustRoot(io.bootstrap?.trustRoot);
@@ -23204,10 +23598,23 @@ function readState(snapshot) {
23204
23598
  throw new Refusal("recovery_required", error.message);
23205
23599
  }
23206
23600
  }
23601
+ function launchCredentials(io) {
23602
+ let warned = false;
23603
+ return () => {
23604
+ const found = resolveCredentials(io.env, io.home);
23605
+ if (found?.source === "login" && !warned && isInsecureStoredURL(found.url)) {
23606
+ warned = true;
23607
+ io.stderr(`hubctl: warning: ${storedInsecureWarning(found.url)}
23608
+ `);
23609
+ }
23610
+ return found;
23611
+ };
23612
+ }
23207
23613
  function launchDriverFor(io) {
23208
23614
  const store = credentialStoreFor(io.env, io.home);
23615
+ const login = launchCredentials(io);
23209
23616
  const client = () => {
23210
- const credentials = resolveCredentials(io.env, io.home);
23617
+ const credentials = login();
23211
23618
  if (!credentials)
23212
23619
  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
23620
  return clientFor(credentials);
@@ -23262,7 +23669,7 @@ function choicesOf(io, flags, profile, snapshot) {
23262
23669
  const enrollment = str(flags, "enrollment");
23263
23670
  if (enrollment !== undefined && !ENROLLMENT_MODES.includes(enrollment))
23264
23671
  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));
23672
+ let roots = flags["workspace-root"]?.map((r) => path34.resolve(io.cwd ?? process.cwd(), r));
23266
23673
  if (!roots?.length && profile.services.includes("node")) {
23267
23674
  const existing = readNodeConfig(nodeConfigPath(io.env, io.home)).config.workspaceRoots;
23268
23675
  if (existing.length > 0)
@@ -23374,7 +23781,7 @@ async function preview3(io, flags, json) {
23374
23781
  const out = str(flags, "out");
23375
23782
  if (out) {
23376
23783
  const request = { schema: "hub.setup-request", version: 1, preview: r.value, profile, choices };
23377
- const file = path33.resolve(io.cwd ?? process.cwd(), out);
23784
+ const file = path34.resolve(io.cwd ?? process.cwd(), out);
23378
23785
  const tmp = `${file}.${process.pid}.tmp`;
23379
23786
  writeFileSync8(tmp, `${JSON.stringify(request, null, 2)}
23380
23787
  `, { mode: 384 });
@@ -23387,7 +23794,7 @@ async function preview3(io, flags, json) {
23387
23794
  function readRequest(io, file) {
23388
23795
  if (!file)
23389
23796
  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");
23797
+ const raw = parseJSON2(readText2(path34.resolve(io.cwd ?? process.cwd(), file), "preview"), "preview", "preview_invalid");
23391
23798
  const r = validateSetupRequest(raw);
23392
23799
  if (!r.ok)
23393
23800
  throw new Refusal("preview_invalid", `${file} is not a hubctl setup preview: ${issuesText(r.issues)}`, "Preview again: hubctl setup preview --out <file>");
@@ -23563,7 +23970,7 @@ function observe(plan, snapshot) {
23563
23970
  continue;
23564
23971
  const server = file.exists ? at(file.value, `/mcp/servers/${f.registration.name}`) : { found: false };
23565
23972
  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]));
23973
+ const ours = Array.isArray(command) && command[1] === "mcp" && typeof command[0] === "string" && ["hub", "hubctl"].includes(path34.basename(command[0]));
23567
23974
  registrations.push({ ...f.registration, state: !server.found ? "absent" : ours ? "ours" : "foreign" });
23568
23975
  }
23569
23976
  }
@@ -23696,7 +24103,7 @@ async function ansible(io, flags, json) {
23696
24103
  const planFile = str(flags, "plan");
23697
24104
  let plan;
23698
24105
  if (planFile) {
23699
- const raw = parseJSON2(readText2(path33.resolve(io.cwd ?? process.cwd(), planFile), "plan"), "plan");
24106
+ const raw = parseJSON2(readText2(path34.resolve(io.cwd ?? process.cwd(), planFile), "plan"), "plan");
23700
24107
  plan = raw.schema === "hub.setup-request" ? raw.preview?.plan : raw.schema === "hub.setup-preview" ? raw.plan : raw;
23701
24108
  }
23702
24109
  const r = renderRoleVars({
@@ -23917,7 +24324,7 @@ Options: --json (raw responses), --help, --version
23917
24324
  Addresses: ses_… (or an unambiguous prefix), @name, @@role, @@role#team (quote it: '@@dev#team'), @name.project, @me (the user)
23918
24325
  Credentials: HUB_URL + HUB_SESSION_TOKEN (set in a session's shells), else the \`hub login\` file.
23919
24326
  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`;
24327
+ 6 rate limited, 7 unsupported/offline/busy, 8 gone, 9 unauthorized/expired`;
23921
24328
  var out = (io, text) => io.stdout(text.endsWith(`
23922
24329
  `) ? text : `${text}
23923
24330
  `);
@@ -24400,7 +24807,7 @@ ${openedText(body.decision)}` : outcome.text);
24400
24807
  const server = checkServerURL(url, allow);
24401
24808
  if (!server) {
24402
24809
  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)}`);
24810
+ 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
24811
  }
24405
24812
  if (isPlainHTTP(server) && !isLoopbackServer(server))
24406
24813
  io.stderr(`hub: warning: ${insecureHTTPWarning(server)}