@huynle/hub 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +20 -4
  3. package/package.json +1 -1
  4. package/packages/claude-code/package.json +1 -1
  5. package/packages/cli/dist/hub-cli.js +625 -395
  6. package/packages/cli/package.json +1 -1
  7. package/packages/cli/src/grants-mcp.ts +65 -0
  8. package/packages/cli/src/mcp-operations.ts +49 -0
  9. package/packages/cli/src/mcp.ts +6 -0
  10. package/packages/cli/src/subscriptions-mcp.ts +76 -0
  11. package/packages/cli/src/teams-mcp.ts +5 -3
  12. package/packages/codex/package.json +1 -1
  13. package/packages/node/package.json +1 -1
  14. package/packages/preset/package.json +1 -1
  15. package/packages/protocol/package.json +1 -1
  16. package/packages/protocol/src/index.ts +13 -1
  17. package/packages/protocol/src/teams.ts +4 -0
  18. package/packages/protocol/src/voice-recording.ts +39 -2
  19. package/packages/runtime-openshell/image/entry.sh +3 -1
  20. package/packages/runtime-openshell/image/start.sh +3 -1
  21. package/packages/runtime-openshell/package.json +1 -1
  22. package/packages/runtime-sdk/package.json +1 -1
  23. package/packages/sdk/package.json +1 -1
  24. package/packages/sdk/src/client.ts +13 -0
  25. package/packages/sdk/src/teams.ts +2 -0
  26. package/packages/spoke-sdk/package.json +1 -1
  27. package/src/central/agent/decisions.ts +7 -1
  28. package/src/central/agent/delivery.ts +39 -9
  29. package/src/central/agent/grant-routes.ts +14 -1
  30. package/src/central/agent/grants.ts +23 -0
  31. package/src/central/agent/launch-engine.ts +3 -3
  32. package/src/central/agent/message-limits.ts +3 -0
  33. package/src/central/agent/routes.ts +11 -0
  34. package/src/central/agent/subscriptions.ts +3 -1
  35. package/src/central/config.ts +9 -2
  36. package/src/central/http.ts +135 -24
  37. package/src/central/recording/export.ts +57 -3
  38. package/src/central/recording/index.ts +234 -16
  39. package/src/central/recording/ingest.ts +11 -2
  40. package/src/central/recording/routes.ts +61 -7
  41. package/src/central/recording/schema.ts +66 -2
  42. package/src/central/recording/settings.ts +19 -5
  43. package/src/central/recording/store.ts +19 -0
  44. package/src/central/recording/taps.ts +45 -1
  45. package/src/central/speech/openai.ts +4 -4
  46. package/src/central/speech/openrouter.ts +4 -4
  47. package/src/central/speech/provider.ts +32 -1
  48. package/src/central/speech/service.ts +6 -5
  49. package/src/central/teams/hop-stalls.ts +72 -0
  50. package/src/central/teams/policy.ts +10 -0
  51. package/src/central/teams/routes.ts +2 -0
  52. package/src/central/teams/seats.ts +3 -3
  53. package/src/central/teams/staffing.ts +5 -0
  54. package/src/central/teams/store.ts +5 -2
  55. package/src/central/teams/validation.ts +8 -5
  56. package/src/spoke/spoke.ts +19 -0
  57. package/web/dist/assets/{ArtifactPage-Bz992ZX6.js → ArtifactPage-C5MMqXLN.js} +1 -1
  58. package/web/dist/assets/{ArtifactPane-BJZNixJC.js → ArtifactPane-CXSCmhyJ.js} +1 -1
  59. package/web/dist/assets/{ArtifactWorkbench-Dti3VP07.js → ArtifactWorkbench-S0RxNGWy.js} +1 -1
  60. package/web/dist/assets/{Diagram-B4HdUxw0.js → Diagram-BGmyRb-R.js} +1 -1
  61. package/web/dist/assets/{Library-CpzD2HDW.js → Library-CFmMbXEa.js} +2 -2
  62. package/web/dist/assets/{ProfilesDestination-DlQeogea.js → ProfilesDestination-7jpOc2HM.js} +1 -1
  63. package/web/dist/assets/{TeamsPage-4xdWCaI-.js → TeamsPage-C1NGrUR-.js} +1 -1
  64. package/web/dist/assets/{Watchtower-CpQdIRns.js → Watchtower-DnvlJaU0.js} +2 -2
  65. package/web/dist/assets/{artifacts-But87Jfo.js → artifacts-DlQEYmDl.js} +1 -1
  66. package/web/dist/assets/index-Da8SKXMw.js +33 -0
  67. package/web/dist/assets/{index-BC0O1gfR.css → index-Dyq-jBeE.css} +1 -1
  68. package/web/dist/assets/live-BmqLASSo.js +5 -0
  69. package/web/dist/assets/{pane-layout-BW0RdX5a.js → pane-layout-BAuu_pA3.js} +1 -1
  70. package/web/dist/assets/{renderer2d-IaJiloPi.js → renderer2d-B2Nh39UN.js} +1 -1
  71. package/web/dist/assets/{renderer3d-ZiD1uKGW.js → renderer3d-CTYbljHH.js} +1 -1
  72. package/web/dist/assets/{safe-area-C7IAlkgY.js → safe-area-CkhEclGo.js} +1 -1
  73. package/web/dist/assets/{silero-worker-e802lB5T.js → silero-worker-CC2etKS3.js} +1 -1
  74. package/web/dist/assets/{source-C-De8L3s.js → source-GYayxUxS.js} +1 -1
  75. package/web/dist/assets/{toast-CXTwTvDw.js → toast-q818mjUF.js} +1 -1
  76. package/web/dist/index.html +5 -5
  77. package/web/dist/assets/index-BS6lA6fN.js +0 -33
  78. package/web/dist/assets/live-CVUFIdUX.js +0 -5
@@ -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.10.0",
10
+ version: "0.11.0",
11
11
  private: true,
12
12
  description: "The hub CLI (`hub` / `hubctl`) for agents and scripts (internal; bundled into @huynle/hub, not published on its own).",
13
13
  license: "MIT",
@@ -1162,6 +1162,15 @@ class HubClient {
1162
1162
  grants(options = {}) {
1163
1163
  return this.call("GET", `${AGENT2}/grants`, signalOf(options));
1164
1164
  }
1165
+ heldGrants(options = {}) {
1166
+ return this.call("GET", `${AGENT2}/grants/mine`, signalOf(options));
1167
+ }
1168
+ heldGrant(id, options = {}) {
1169
+ return this.call("GET", `${AGENT2}/grants/mine/${segment(id)}`, signalOf(options));
1170
+ }
1171
+ revokeHeldGrant(id, options = {}) {
1172
+ return this.call("DELETE", `${AGENT2}/grants/mine/${segment(id)}`, signalOf(options));
1173
+ }
1165
1174
  grant(id, options = {}) {
1166
1175
  return this.call("GET", `${AGENT2}/grants/${segment(id)}`, signalOf(options));
1167
1176
  }
@@ -4325,6 +4334,19 @@ var RECORDING_LIMITS = {
4325
4334
  hubFlushMs: 2000,
4326
4335
  hubFlushEvents: 100
4327
4336
  };
4337
+ var DEV_LIMITS = {
4338
+ clipsPerRecording: 200,
4339
+ clipPreMs: 3000,
4340
+ clipPostMs: 2000,
4341
+ clipBytes: 192 * 1024,
4342
+ clipGapMs: 2000,
4343
+ repliesPerRecording: 2000,
4344
+ replyBytes: 1024 * 1024,
4345
+ userHeadroomBytes: 50 * 1024 * 1024,
4346
+ recordingHeadroomBytes: 15 * 1024 * 1024,
4347
+ tracesPerMinute: 30,
4348
+ logsPerMinute: 20
4349
+ };
4328
4350
  // packages/protocol/src/app-front.ts
4329
4351
  var ACCESS_TOKEN_TTL_MS = 60 * 60000;
4330
4352
  var REFRESH_TOKEN_IDLE_MS = 30 * 24 * 60 * 60000;
@@ -4421,7 +4443,7 @@ var IMPLIED_OPENCODE_FEATURES = [
4421
4443
  "expose",
4422
4444
  "create"
4423
4445
  ];
4424
- var OPTIONAL_SPOKE_FEATURES = [...IMPLIED_OPENCODE_FEATURES, "autoName", "sessionTokens", "deliver", "hubPrompt", "permissionMessage", "actTokens", "launch", "fork", "revert", "dismissError", "timelineMedia", PREVIEW_BUNDLE_FEATURE, "voiceFront", "conciergeFront", "inboxCarried"];
4446
+ var OPTIONAL_SPOKE_FEATURES = [...IMPLIED_OPENCODE_FEATURES, "autoName", "sessionTokens", "deliver", "hubPrompt", "permissionMessage", "actTokens", "launch", "fork", "revert", "dismissError", "timelineMedia", PREVIEW_BUNDLE_FEATURE, "voiceFront", "conciergeFront", "inboxCarried", "setModel"];
4425
4447
  var CORE_OPS = ["turn", "steer", "interrupt", "stop", "permission", "form"];
4426
4448
  var KNOWN_SPOKE_FEATURES = [...CORE_OPS, ...OPTIONAL_SPOKE_FEATURES, "turnEvents", PROFILE_APPLY_FEATURE, SESSION_CONTEXT_FEATURE];
4427
4449
  var SPOKE_FEATURES = [...OPTIONAL_SPOKE_FEATURES, PROFILE_APPLY_FEATURE, SESSION_CONTEXT_FEATURE];
@@ -4620,6 +4642,8 @@ function isServerRequest(v) {
4620
4642
  return nonEmpty(v.sessionID);
4621
4643
  case "setModelPrefs":
4622
4644
  return nonEmpty(v.sessionID) && (v.voice === undefined || v.voice === null || isModelSelection(v.voice)) && (v.worker === undefined || v.worker === null || isModelSelection(v.worker)) && (v.voice !== undefined || v.worker !== undefined);
4645
+ case "setModel":
4646
+ return nonEmpty(v.sessionID) && isModelSelection(v.model);
4623
4647
  case "timeline":
4624
4648
  return nonEmpty(v.sessionID) && (v.limit === undefined || Number.isInteger(v.limit) && Number(v.limit) >= 1 && Number(v.limit) <= 200) && (v.cursor === undefined || str2(v.cursor) && v.cursor.length <= 4096) && (v.order === undefined || v.order === "asc" || v.order === "desc") && (v.before === undefined || nonEmpty(v.before) && v.before.length <= 4096) && (v.cursor === undefined || v.before === undefined && v.order !== "desc");
4625
4649
  case "timelinePart":
@@ -6982,35 +7006,213 @@ ${intakeText(intake)}`;
6982
7006
  }
6983
7007
  ];
6984
7008
 
6985
- // packages/cli/src/teams-mcp.ts
7009
+ // packages/cli/src/subscriptions.ts
7010
+ var SUBSCRIBE_FLAGS = { type: "multi", delivery: "string", batch: "string", expires: "string", "max-per-hour": "string", subscriber: "string", note: "string" };
7011
+ var DELIVERIES2 = ["queue", "steer", "silent"];
7012
+ var safe = (v, max = 120) => sanitizeLabel(String(v), max);
7013
+ function subscribeInput(addrs, flags) {
7014
+ if (!addrs.length)
7015
+ throw new UsageError("needs at least one address (a session, @@role, #team or children)");
7016
+ const delivery = str(flags, "delivery");
7017
+ if (delivery !== undefined && !DELIVERIES2.includes(delivery))
7018
+ throw new UsageError(`--delivery is one of ${DELIVERIES2.join(", ")}`);
7019
+ const batch = str(flags, "batch");
7020
+ const expires = str(flags, "expires");
7021
+ const max = str(flags, "max-per-hour");
7022
+ if (max !== undefined && !/^\d+$/.test(max))
7023
+ throw new UsageError("--max-per-hour takes a whole number (1–120)");
7024
+ const types = flags.type;
7025
+ const subscriber = str(flags, "subscriber");
7026
+ const note = str(flags, "note");
7027
+ return {
7028
+ to: addrs.length === 1 ? addrs[0] : [...addrs],
7029
+ ...types?.length ? { types: types.flatMap((t) => t.split(",")).map((t) => t.trim()).filter(Boolean) } : {},
7030
+ ...delivery !== undefined ? { delivery } : {},
7031
+ ...batch !== undefined ? { batch: durationArg(batch, "--batch") } : {},
7032
+ ...expires !== undefined ? { expires: durationArg(expires, "--expires") } : {},
7033
+ ...max !== undefined ? { maxPerHour: Number(max) } : {},
7034
+ ...subscriber !== undefined ? { subscriber } : {},
7035
+ ...note !== undefined ? { note } : {}
7036
+ };
7037
+ }
7038
+ var targets = (s) => s.targets.map((t) => t.kind === "children" ? "its subagents" : safe(t.addr, 80)).join(", ");
7039
+ var iso3 = (ms) => new Date(ms).toISOString().slice(0, 16) + "Z";
7040
+ function subscriptionLine(s) {
7041
+ const extra = [safe(s.delivery, 10), `batch ${Math.round(s.batchMs / 1000)}s`, `≤${s.maxPerHour}/h`, `expires ${iso3(s.expiresAt)}`, `${s.stats.delivered} delivered`, ...s.pending ? [`${s.pending} pending`] : [], ...s.stats.dropped ? [`${s.stats.dropped} dropped`] : [], ...s.stats.suppressed ? [`${s.stats.suppressed} loop-suppressed`] : []];
7042
+ return `${safe(s.id, 40)} ${safe(s.subscriber, 80)} ← ${targets(s)} [${s.types.map((t) => safe(t, 40)).join(",")}] ${extra.join(" · ")}${s.note ? ` "${safe(s.note, 200)}"` : ""}`;
7043
+ }
7044
+ var subscribedText = (b) => `subscribed ${subscriptionLine(b.subscription)}
7045
+ the subscriber gets one hub message per batch window; stop with \`hub unsubscribe ${safe(b.subscription.id, 40)}\``;
7046
+ var subscriptionsText = (b) => b.subscriptions.length ? b.subscriptions.map(subscriptionLine).join(`
7047
+ `) : "no subscriptions";
7048
+
7049
+ // packages/cli/src/subscriptions-mcp.ts
6986
7050
  var STRING4 = { type: "string" };
6987
- var STRINGS = { type: "array", items: STRING4 };
7051
+ var need4 = (args, key) => {
7052
+ const value = args[key];
7053
+ if (typeof value !== "string" || !value.trim())
7054
+ throw new ToolArgError(`"${key}" is required (a non-empty string)`);
7055
+ return value;
7056
+ };
7057
+ var optional5 = (args, key) => {
7058
+ const value = args[key];
7059
+ if (value === undefined || value === null)
7060
+ return;
7061
+ if (typeof value !== "string")
7062
+ throw new ToolArgError(`"${key}" must be a string`);
7063
+ return value.trim() || undefined;
7064
+ };
7065
+ var array = (args, key) => {
7066
+ const value = args[key];
7067
+ if (value === undefined || value === null)
7068
+ return;
7069
+ if (!Array.isArray(value) || !value.every((v) => typeof v === "string"))
7070
+ throw new ToolArgError(`"${key}" must be a list of strings`);
7071
+ return value;
7072
+ };
7073
+ var number = (args, key, min, max) => {
7074
+ const value = args[key];
7075
+ if (value === undefined || value === null)
7076
+ return;
7077
+ if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max)
7078
+ throw new ToolArgError(`"${key}" must be a whole number from ${min} to ${max}`);
7079
+ return value;
7080
+ };
7081
+ var subscriptionTools = [
7082
+ {
7083
+ name: "subscribe",
7084
+ description: "Follow journal events for a subscriber session. A session subscribes itself; users and apps name subscriber. Apps need events:subscribe, sessions:read, relevant type scopes and messages:send unless silent. Hub checks visibility, rates and loops.",
7085
+ properties: {
7086
+ to: { type: ["string", "array"], items: STRING4, description: "One or more session/role/team addresses, or children." },
7087
+ subscriber: STRING4,
7088
+ types: { type: "array", items: STRING4 },
7089
+ delivery: { type: "string", enum: ["queue", "steer", "silent"] },
7090
+ batch: { type: "number", description: "Seconds per batch (1–3600)." },
7091
+ expires: { type: "number", description: "Seconds until expiry." },
7092
+ maxPerHour: { type: "integer", minimum: 1, maximum: 120 },
7093
+ note: STRING4
7094
+ },
7095
+ required: ["to"],
7096
+ readOnly: false,
7097
+ run: async (hub, args, signal) => {
7098
+ const to = typeof args.to === "string" && args.to.trim() ? args.to : array(args, "to");
7099
+ if (!to || Array.isArray(to) && !to.length)
7100
+ throw new ToolArgError('"to" needs at least one address');
7101
+ const delivery = optional5(args, "delivery");
7102
+ if (delivery && !["queue", "steer", "silent"].includes(delivery))
7103
+ throw new ToolArgError('"delivery" is queue, steer or silent');
7104
+ const subscriber = optional5(args, "subscriber");
7105
+ const types = array(args, "types");
7106
+ const batch = number(args, "batch", 1, 3600);
7107
+ const expires = number(args, "expires", 1, 30 * 86400);
7108
+ const maxPerHour = number(args, "maxPerHour", 1, 120);
7109
+ const note = optional5(args, "note");
7110
+ const input = { to, ...subscriber ? { subscriber } : {}, ...types ? { types } : {}, ...delivery ? { delivery } : {}, ...batch ? { batch } : {}, ...expires ? { expires } : {}, ...maxPerHour ? { maxPerHour } : {}, ...note ? { note } : {} };
7111
+ return subscribedText(await hub.subscribe(input, { signal }));
7112
+ }
7113
+ },
7114
+ {
7115
+ name: "subscriptions",
7116
+ description: "List subscriptions visible to this credential (session: its own; app: created by it; user: all). Optional session filter; route enforces scopes and tenancy.",
7117
+ properties: { session: STRING4 },
7118
+ readOnly: true,
7119
+ run: async (hub, args, signal) => subscriptionsText(await hub.subscriptions({ ...optional5(args, "session") ? { session: optional5(args, "session") } : {}, signal }))
7120
+ },
7121
+ {
7122
+ name: "unsubscribe",
7123
+ description: "Remove a subscription this credential owns. An unknown or another caller's ID answers not_found.",
7124
+ properties: { id: STRING4 },
7125
+ required: ["id"],
7126
+ readOnly: false,
7127
+ organizes: true,
7128
+ run: async (hub, args, signal) => {
7129
+ const id = need4(args, "id");
7130
+ await hub.unsubscribe(id, { signal });
7131
+ return `unsubscribed ${id}`;
7132
+ }
7133
+ }
7134
+ ];
7135
+
7136
+ // packages/cli/src/mcp-operations.ts
7137
+ var STRING5 = { type: "string" };
7138
+ var need5 = (args, key) => {
7139
+ const v = args[key];
7140
+ if (typeof v !== "string" || !v.trim())
7141
+ throw new ToolArgError(`"${key}" is required (a non-empty string)`);
7142
+ return v;
7143
+ };
7144
+ var userOnly = async (hub, signal) => {
7145
+ if ((await hub.whoami({ signal })).actor.kind !== "user")
7146
+ throw new CliError(EXIT.forbidden, "only the user can use this tool", "Use the user's device credential");
7147
+ };
7148
+ var operationTools = [
7149
+ {
7150
+ name: "session_model_set",
7151
+ description: "Set one existing session's model (providerID, modelID and optional variant), exactly as the console Model chip does. User device credential only; the console route checks ownership, catalog, availability and audit. An app or session cannot set it.",
7152
+ properties: { session: STRING5, model: { type: "object", properties: { providerID: STRING5, modelID: STRING5, variant: STRING5 }, required: ["providerID", "modelID"] } },
7153
+ required: ["session", "model"],
7154
+ readOnly: false,
7155
+ run: async (hub, args, signal) => {
7156
+ await userOnly(hub, signal);
7157
+ const model = args.model;
7158
+ if (typeof model !== "object" || model === null || Array.isArray(model))
7159
+ throw new ToolArgError('"model" is required (providerID, modelID, variant?)');
7160
+ const result = await hub.call("PUT", `/api/sessions/${encodeURIComponent(need5(args, "session"))}/model`, { body: { model }, signal });
7161
+ return JSON.stringify(result);
7162
+ }
7163
+ },
7164
+ {
7165
+ name: "machines",
7166
+ description: "The user's own enrolled machines, online status and processes. User device credential only; wraps GET /api/machines. No machine tokens or secrets.",
7167
+ properties: {},
7168
+ readOnly: true,
7169
+ run: async (hub, _args, signal) => {
7170
+ await userOnly(hub, signal);
7171
+ return JSON.stringify(await hub.call("GET", "/api/machines", { signal }));
7172
+ }
7173
+ },
7174
+ {
7175
+ name: "launch_nodes",
7176
+ description: "The user's connected launch nodes: machine, workspace roots, capacity/used/free slots, runtimes (harnesses) and labels. User device credential only; no tokens, connection IDs or mutation.",
7177
+ properties: {},
7178
+ readOnly: true,
7179
+ run: async (hub, _args, signal) => {
7180
+ await userOnly(hub, signal);
7181
+ return JSON.stringify(await hub.call("GET", "/api/agent/launch-nodes", { signal }));
7182
+ }
7183
+ }
7184
+ ];
7185
+
7186
+ // packages/cli/src/teams-mcp.ts
7187
+ var STRING6 = { type: "string" };
7188
+ var STRINGS = { type: "array", items: STRING6 };
6988
7189
  var TEAM2 = { type: "string", description: "The team: its ID (team_…) or its name." };
6989
7190
  var REVISION2 = { type: "integer", minimum: 0, description: "The team's revision you read (team_show); a stale one answers conflict with the current revision." };
6990
7191
  var IDEMPOTENCY = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
6991
7192
  var KINDS3 = { type: "array", items: { type: "string", enum: ["msg", "ask"] }, description: "What the path permits: msg and/or ask (replies to a permitted ask never need a path)." };
6992
7193
  var DIRECTION = { type: "string", enum: ["one-way", "two-way"], description: "two-way: `to` may also contact `from` with the same kinds." };
6993
7194
  var ROLE_FIELDS = {
6994
- label: { ...STRING4, description: "A display label (confers no authority)." },
6995
- description: STRING4,
7195
+ label: { ...STRING6, description: "A display label (confers no authority)." },
7196
+ description: STRING6,
6996
7197
  externalContact: { type: "boolean", description: "Holders may contact sessions outside all their teams (default false)." },
6997
7198
  responsibilities: { ...STRINGS, description: "Short lines (at most 16)." },
6998
- profile: { type: "object", properties: { profileID: STRING4, version: { type: "integer", minimum: 1 } }, required: ["profileID"], description: "The agent profile members should run (reported, never pinned by staffing)." },
6999
- prefs: { type: "object", properties: { harness: STRING4, model: STRING4, machineID: STRING4 }, description: "Placement preferences for launched members (advisory)." },
7199
+ profile: { type: "object", properties: { profileID: STRING6, version: { type: "integer", minimum: 1 } }, required: ["profileID"], description: "The agent profile members should run (reported, never pinned by staffing)." },
7200
+ prefs: { type: "object", properties: { harness: STRING6, model: STRING6, machineID: STRING6 }, description: "Placement preferences for launched members (advisory)." },
7000
7201
  minMembers: { type: "integer", minimum: 0, maximum: 16 },
7001
7202
  maxMembers: { type: "integer", minimum: 1, maximum: 16 }
7002
7203
  };
7003
- var ROLE = { type: "object", properties: { name: { ...STRING4, description: "A slug: lowercase letters, digits, dashes." }, ...ROLE_FIELDS }, required: ["name"] };
7004
- var EDGE = { type: "object", properties: { from: STRING4, to: STRING4, kinds: KINDS3, direction: DIRECTION }, required: ["from", "to", "kinds", "direction"] };
7204
+ var ROLE = { type: "object", properties: { name: { ...STRING6, description: "A slug: lowercase letters, digits, dashes." }, ...ROLE_FIELDS }, required: ["name"] };
7205
+ var EDGE = { type: "object", properties: { from: STRING6, to: STRING6, kinds: KINDS3, direction: DIRECTION }, required: ["from", "to", "kinds", "direction"] };
7005
7206
  var SPEC_FIELDS = {
7006
- title: { ...STRING4, description: "1–120 characters." },
7007
- description: STRING4,
7207
+ title: { ...STRING6, description: "1–120 characters." },
7208
+ description: STRING6,
7008
7209
  mode: { type: "string", enum: ["open", "restricted"], description: "open: every member may msg/ask every other member. restricted: only the drawn paths (edges)." },
7210
+ hopLimit: { type: "integer", minimum: 1, maximum: 100, description: "Agent-only wake hop limit between this team's seats; may not exceed the hub-wide agents.hopLimit. Unset uses the global limit." },
7009
7211
  roles: { type: "array", items: ROLE, description: "1–32 roles." },
7010
- entryRole: { ...STRING4, description: "The role that receives submitted work (e.g. pm) and supervises members." },
7212
+ entryRole: { ...STRING6, description: "The role that receives submitted work (e.g. pm) and supervises members." },
7011
7213
  edges: { type: "array", items: EDGE, description: "Communication paths between roles (restricted mode)." },
7012
7214
  tags: { ...STRINGS, description: "Slugs." },
7013
- metadata: { type: "object", additionalProperties: STRING4, description: "String key/value pairs (keys are slugs)." },
7215
+ metadata: { type: "object", additionalProperties: STRING6, description: "String key/value pairs (keys are slugs)." },
7014
7216
  projects: { ...STRINGS, description: "Project names the team works on." }
7015
7217
  };
7016
7218
  var LAUNCH = {
@@ -7021,11 +7223,11 @@ var LAUNCH = {
7021
7223
  type: "object",
7022
7224
  description: "Where the member works on its machine.",
7023
7225
  properties: {
7024
- repo: { ...STRING4, description: "The repository's absolute path on the machine." },
7226
+ repo: { ...STRING6, description: "The repository's absolute path on the machine." },
7025
7227
  mode: { type: "string", enum: ["worktree", "checkout", "clone"], description: "worktree (default; needs branch), checkout, or clone (needs remote)." },
7026
- branch: { ...STRING4, description: "The branch to create or check out (a safe git branch name; required for worktree)." },
7027
- base: { ...STRING4, description: "What a new branch starts from." },
7028
- remote: { ...STRING4, description: "An https or ssh git remote, without credentials (required for clone)." }
7228
+ branch: { ...STRING6, description: "The branch to create or check out (a safe git branch name; required for worktree)." },
7229
+ base: { ...STRING6, description: "What a new branch starts from." },
7230
+ remote: { ...STRING6, description: "An https or ssh git remote, without credentials (required for clone)." }
7029
7231
  },
7030
7232
  required: ["repo"]
7031
7233
  },
@@ -7033,8 +7235,8 @@ var LAUNCH = {
7033
7235
  type: "object",
7034
7236
  description: "Which machine runs it.",
7035
7237
  properties: {
7036
- machine: { ...STRING4, description: "A machine ID or name." },
7037
- labels: { type: "object", additionalProperties: STRING4, description: "Node labels to match." },
7238
+ machine: { ...STRING6, description: "A machine ID or name." },
7239
+ labels: { type: "object", additionalProperties: STRING6, description: "Node labels to match." },
7038
7240
  mode: { type: "string", enum: ["require", "prefer"], description: "require (default) or prefer the machine/labels." }
7039
7241
  }
7040
7242
  },
@@ -7042,7 +7244,7 @@ var LAUNCH = {
7042
7244
  type: "object",
7043
7245
  properties: {
7044
7246
  kind: { type: "string", enum: ["host", "openshell"], description: "host (default) or openshell (clone workspaces only)." },
7045
- profile: { ...STRING4, description: "An openshell sandbox profile (openshell only)." }
7247
+ profile: { ...STRING6, description: "An openshell sandbox profile (openshell only)." }
7046
7248
  }
7047
7249
  },
7048
7250
  end: {
@@ -7054,27 +7256,27 @@ var LAUNCH = {
7054
7256
  cleanup: { type: "string", enum: ["keep", "remove"], description: "keep (default) or remove the workspace at the end." }
7055
7257
  }
7056
7258
  },
7057
- name: { ...STRING4, description: "The session's @name, without the @." },
7259
+ name: { ...STRING6, description: "The session's @name, without the @." },
7058
7260
  roles: { ...STRINGS, description: "Roles the session claims (up to 10)." },
7059
- harness: { ...STRING4, description: "The harness (default opencode)." },
7060
- agent: { ...STRING4, description: "The harness agent." },
7061
- model: { ...STRING4, description: "provider/model." },
7062
- prompt: { ...STRING4, description: "The first prompt (up to 10000 characters)." },
7063
- intent: { ...STRING4, description: "What the session is for (one line)." },
7064
- labels: { type: "object", additionalProperties: STRING4, description: "Free-form app labels." },
7065
- 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)." }
7261
+ harness: { ...STRING6, description: "The harness (default opencode)." },
7262
+ agent: { ...STRING6, description: "The harness agent." },
7263
+ model: { ...STRING6, description: "provider/model." },
7264
+ prompt: { ...STRING6, description: "The first prompt (up to 10000 characters)." },
7265
+ intent: { ...STRING6, description: "What the session is for (one line)." },
7266
+ labels: { type: "object", additionalProperties: STRING6, description: "Free-form app labels." },
7267
+ agentProfile: { type: "object", properties: { profileID: STRING6, version: { type: "integer", minimum: 1 } }, required: ["profileID", "version"], description: "The profile version to pin (needs profiles:assign for an app)." }
7066
7268
  },
7067
7269
  required: ["workspace"]
7068
7270
  };
7069
7271
  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.";
7070
7272
  var ArgError = ToolArgError;
7071
- var need4 = (args, key) => {
7273
+ var need6 = (args, key) => {
7072
7274
  const value = args[key];
7073
7275
  if (typeof value !== "string" || !value.trim())
7074
7276
  throw new ArgError(`"${key}" is required (a non-empty string)`);
7075
7277
  return value;
7076
7278
  };
7077
- var optional5 = (args, key) => {
7279
+ var optional6 = (args, key) => {
7078
7280
  const value = args[key];
7079
7281
  if (value === undefined || value === null)
7080
7282
  return;
@@ -7129,6 +7331,7 @@ function specLines(spec) {
7129
7331
  const role = (r) => ` ${r.name}${r.name === spec.entryRole ? " (entry)" : ""}${r.label ? ` "${one(r.label)}"` : ""}${r.externalContact ? " external contact" : ""}${r.minMembers !== undefined || r.maxMembers !== undefined ? ` members ${r.minMembers ?? 0}–${r.maxMembers ?? 16}` : ""}${r.profile ? ` profile ${r.profile.profileID}${r.profile.version ? `@${r.profile.version}` : ""}` : ""}`;
7130
7332
  return [
7131
7333
  `mode: ${spec.mode}${spec.mode === "open" ? " (every member may msg/ask every other)" : " (only the paths below)"}`,
7334
+ ...spec.hopLimit !== undefined ? [`hop limit: ${spec.hopLimit}`] : [],
7132
7335
  `entry role: ${spec.entryRole}`,
7133
7336
  "roles:",
7134
7337
  ...spec.roles.map(role),
@@ -7147,7 +7350,7 @@ function rosterLines(roster) {
7147
7350
  const flags = [r.vacant ? "vacant" : "", r.understaffed ? `understaffed (min ${r.minMembers})` : "", r.launching ? `${r.launching} launching` : ""].filter(Boolean);
7148
7351
  lines.push(` ${r.role}${r.entry ? " (entry)" : ""} ${r.seats.length}/${r.maxMembers} ${r.online} online${flags.length ? ` ${flags.join(", ")}` : ""}`);
7149
7352
  for (const s of r.seats)
7150
- lines.push(` ${s.staffing.id} ${s.staffing.sessionID} ${s.presence} gen ${s.staffing.generation} r${s.staffing.revision}${s.profile ? ` profile ${s.profile.status}${s.profile.matches ? "" : " (mismatch)"}` : ""}`);
7353
+ lines.push(` ${s.staffing.id} ${s.staffing.sessionID} ${s.presence}${s.stalled ? ` stalled: ${s.stalled}` : ""} gen ${s.staffing.generation} r${s.staffing.revision}${s.profile ? ` profile ${s.profile.status}${s.profile.matches ? "" : " (mismatch)"}` : ""}`);
7151
7354
  }
7152
7355
  const failed = roster.requests.filter((q) => q.state !== "staffed").slice(-5);
7153
7356
  for (const q of failed)
@@ -7164,7 +7367,7 @@ var statusTool = (verb, description, organizes) => ({
7164
7367
  idempotent: true,
7165
7368
  run: async (hub, args, signal) => {
7166
7369
  const expectedRevision = revision(args);
7167
- const { team } = await hub.teams[verb](need4(args, "team"), expectedRevision, { signal });
7370
+ const { team } = await hub.teams[verb](need6(args, "team"), expectedRevision, { signal });
7168
7371
  return `${team.revision === expectedRevision ? "unchanged" : { pause: "paused", resume: "resumed", archive: "archived", restore: "restored" }[verb]}: ${teamLine(team)}`;
7169
7372
  }
7170
7373
  });
@@ -7178,7 +7381,7 @@ var draftTool = (name, description, properties, required, edit) => ({
7178
7381
  idempotent: true,
7179
7382
  run: async (hub, args, signal) => {
7180
7383
  const expectedRevision = revision(args);
7181
- const { team, draft } = await edit(hub.teams, need4(args, "team"), args, expectedRevision, signal);
7384
+ const { team, draft } = await edit(hub.teams, need6(args, "team"), args, expectedRevision, signal);
7182
7385
  return draftText("draft saved", team, draft);
7183
7386
  }
7184
7387
  });
@@ -7189,22 +7392,22 @@ var teamTools = [
7189
7392
  properties: {
7190
7393
  state: { type: "string", enum: ["active", "archived", "all"] },
7191
7394
  tags: STRINGS,
7192
- project: STRING4,
7193
- metadata: { type: "object", additionalProperties: STRING4 },
7395
+ project: STRING6,
7396
+ metadata: { type: "object", additionalProperties: STRING6 },
7194
7397
  paused: { type: "boolean" },
7195
7398
  limit: { type: "integer", minimum: 1, maximum: 200 },
7196
- cursor: STRING4
7399
+ cursor: STRING6
7197
7400
  },
7198
7401
  readOnly: true,
7199
7402
  run: async (hub, args, signal) => {
7200
- const state = optional5(args, "state");
7403
+ const state = optional6(args, "state");
7201
7404
  if (state !== undefined && !["active", "archived", "all"].includes(state))
7202
7405
  throw new ArgError(`"state" is one of active, archived, all`);
7203
7406
  const tags = strings3(args, "tags");
7204
- const project = optional5(args, "project");
7407
+ const project = optional6(args, "project");
7205
7408
  const metadata = record(args, "metadata");
7206
7409
  const limit = int3(args, "limit", 1, 200);
7207
- const cursor = optional5(args, "cursor");
7410
+ const cursor = optional6(args, "cursor");
7208
7411
  if (args.paused !== undefined && typeof args.paused !== "boolean")
7209
7412
  throw new ArgError(`"paused" must be true or false`);
7210
7413
  const page = await hub.teams.list({
@@ -7230,7 +7433,7 @@ var teamTools = [
7230
7433
  required: ["team"],
7231
7434
  readOnly: true,
7232
7435
  run: async (hub, args, signal) => {
7233
- const { team, draft } = await hub.teams.get(need4(args, "team"), { signal });
7436
+ const { team, draft } = await hub.teams.get(need6(args, "team"), { signal });
7234
7437
  return [teamLine(team), ...specLines(team), ...draft ? [`draft pending (on v${draft.baseVersion}, saved ${when(draft.savedAt)}): see team_diff`] : []].join(`
7235
7438
  `);
7236
7439
  }
@@ -7242,7 +7445,7 @@ var teamTools = [
7242
7445
  required: ["team"],
7243
7446
  readOnly: true,
7244
7447
  run: async (hub, args, signal) => {
7245
- const { team, roster, intake } = await hub.teams.status(need4(args, "team"), { signal });
7448
+ const { team, roster, intake } = await hub.teams.status(need6(args, "team"), { signal });
7246
7449
  const lines = [teamLine(team), ...rosterLines(roster)];
7247
7450
  if (intake)
7248
7451
  lines.push(`intake: ${intake.queued} queued, ${intake.routed} routed (${intake.open} open, ${intake.inProgress} in progress, ${intake.blocked} blocked), ${intake.done} done, ${intake.failed} failed, ${intake.cancelled} cancelled${intake.oldestQueuedAt !== undefined ? `; oldest queued ${when(intake.oldestQueuedAt)}` : ""}`);
@@ -7253,12 +7456,12 @@ var teamTools = [
7253
7456
  {
7254
7457
  name: "team_history",
7255
7458
  description: `A team's retained published versions, newest first (version, digest, when). ${SCOPES}`,
7256
- properties: { team: TEAM2, cursor: STRING4 },
7459
+ properties: { team: TEAM2, cursor: STRING6 },
7257
7460
  required: ["team"],
7258
7461
  readOnly: true,
7259
7462
  run: async (hub, args, signal) => {
7260
- const cursor = optional5(args, "cursor");
7261
- const page = await hub.teams.versions(need4(args, "team"), { signal, ...cursor ? { cursor } : {} });
7463
+ const cursor = optional6(args, "cursor");
7464
+ const page = await hub.teams.versions(need6(args, "team"), { signal, ...cursor ? { cursor } : {} });
7262
7465
  return [...page.items.map((v) => `v${v.version} ${v.digest.slice(0, 12)} ${when(v.publishedAt)}`), ...page.cursor ? [`more: cursor ${page.cursor}`] : []].join(`
7263
7466
  `);
7264
7467
  }
@@ -7272,7 +7475,7 @@ var teamTools = [
7272
7475
  run: async (hub, args, signal) => {
7273
7476
  const from = int3(args, "from", 1, 1e9);
7274
7477
  const to = args.to === undefined || args.to === null || args.to === "draft" ? "draft" : int3(args, "to", 1, 1e9);
7275
- const { from: a, to: b, diff } = await hub.teams.diff(need4(args, "team"), { signal, ...from !== undefined ? { from } : {}, to });
7478
+ const { from: a, to: b, diff } = await hub.teams.diff(need6(args, "team"), { signal, ...from !== undefined ? { from } : {}, to });
7276
7479
  const part = (label, d) => ["added", "removed", "changed"].flatMap((k) => d[k]?.length ? [`${label} ${k}: ${d[k].join(", ")}`] : []);
7277
7480
  const lines = [
7278
7481
  ...diff.fields.length ? [`fields changed: ${diff.fields.join(", ")}`] : [],
@@ -7289,16 +7492,16 @@ var teamTools = [
7289
7492
  {
7290
7493
  name: "team_create",
7291
7494
  description: `Assemble a persistent team: roles (one is the entry role that receives submitted work, e.g. a project manager), open or restricted communication with one-way/two-way paths, tags, projects and metadata. Published as version 1 at once; the name is the team's address (@@role#name) and never changes. ${SCOPES}`,
7292
- properties: { name: { ...STRING4, description: "A slug (2–40 lowercase letters, digits, dashes), unique per user." }, ...SPEC_FIELDS, idempotencyKey: IDEMPOTENCY },
7495
+ properties: { name: { ...STRING6, description: "A slug (2–40 lowercase letters, digits, dashes), unique per user." }, ...SPEC_FIELDS, idempotencyKey: IDEMPOTENCY },
7293
7496
  required: ["name", "title", "mode", "roles", "entryRole"],
7294
7497
  readOnly: false,
7295
7498
  organizes: true,
7296
7499
  idempotent: true,
7297
7500
  run: async (hub, args, signal) => {
7298
- need4(args, "name");
7299
- need4(args, "title");
7300
- const key = optional5(args, "idempotencyKey");
7301
- const spec = picked(args, ["name", "title", "description", "mode", "roles", "entryRole", "edges", "tags", "metadata", "projects"]);
7501
+ need6(args, "name");
7502
+ need6(args, "title");
7503
+ const key = optional6(args, "idempotencyKey");
7504
+ const spec = picked(args, ["name", "title", "description", "mode", "hopLimit", "roles", "entryRole", "edges", "tags", "metadata", "projects"]);
7302
7505
  const { team, created } = await hub.teams.create({ ...spec, edges: spec.edges ?? [] }, { signal, ...key ? { idempotencyKey: key } : {} });
7303
7506
  return [`${created ? "created" : "already created (same key)"}: ${teamLine(team)}`, ...specLines(team)].join(`
7304
7507
  `);
@@ -7314,24 +7517,24 @@ var teamTools = [
7314
7517
  idempotent: true,
7315
7518
  run: async (hub, args, signal) => {
7316
7519
  const expectedRevision = revision(args);
7317
- const patch = picked(args, ["title", "description", "mode", "roles", "entryRole", "edges", "tags", "metadata", "projects"]);
7520
+ const patch = picked(args, ["title", "description", "mode", "hopLimit", "roles", "entryRole", "edges", "tags", "metadata", "projects"]);
7318
7521
  if (Object.keys(patch).length === 0)
7319
7522
  throw new ArgError("give at least one field to change");
7320
- const id = need4(args, "team");
7523
+ const id = need6(args, "team");
7321
7524
  const { team } = await hub.teams.update(id, patch, expectedRevision, { signal });
7322
7525
  return draftText("draft saved", team, (await hub.teams.draft(team.id, { signal })).draft);
7323
7526
  }
7324
7527
  },
7325
- draftTool("team_role_set", "Add a role to a team's draft, or replace the role of that name (all its fields).", { role: { ...STRING4, description: "The role's name (a slug)." }, ...ROLE_FIELDS }, ["role"], (teams, team, args, rev, signal) => teams.setRole(team, { name: need4(args, "role"), ...picked(args, Object.keys(ROLE_FIELDS)) }, rev, { signal })),
7326
- draftTool("team_role_remove", "Remove a role (not the entry role) and every path touching it from a team's draft.", { role: STRING4 }, ["role"], (teams, team, args, rev, signal) => teams.removeRole(team, need4(args, "role"), rev, { signal })),
7327
- draftTool("team_edge_set", "Draw a communication path from one role to another in a team's draft (replaces the path drawn between them).", { from: STRING4, to: STRING4, kinds: KINDS3, direction: DIRECTION }, ["from", "to", "kinds", "direction"], (teams, team, args, rev, signal) => {
7528
+ draftTool("team_role_set", "Add a role to a team's draft, or replace the role of that name (all its fields).", { role: { ...STRING6, description: "The role's name (a slug)." }, ...ROLE_FIELDS }, ["role"], (teams, team, args, rev, signal) => teams.setRole(team, { name: need6(args, "role"), ...picked(args, Object.keys(ROLE_FIELDS)) }, rev, { signal })),
7529
+ draftTool("team_role_remove", "Remove a role (not the entry role) and every path touching it from a team's draft.", { role: STRING6 }, ["role"], (teams, team, args, rev, signal) => teams.removeRole(team, need6(args, "role"), rev, { signal })),
7530
+ draftTool("team_edge_set", "Draw a communication path from one role to another in a team's draft (replaces the path drawn between them).", { from: STRING6, to: STRING6, kinds: KINDS3, direction: DIRECTION }, ["from", "to", "kinds", "direction"], (teams, team, args, rev, signal) => {
7328
7531
  const kinds = strings3(args, "kinds");
7329
7532
  if (!kinds?.length)
7330
7533
  throw new ArgError(`"kinds" is a list of msg and/or ask`);
7331
- const direction = need4(args, "direction");
7332
- return teams.setEdge(team, { from: need4(args, "from"), to: need4(args, "to"), kinds, direction }, rev, { signal });
7534
+ const direction = need6(args, "direction");
7535
+ return teams.setEdge(team, { from: need6(args, "from"), to: need6(args, "to"), kinds, direction }, rev, { signal });
7333
7536
  }),
7334
- draftTool("team_edge_remove", "Remove the path drawn from one role to another (or a two-way path between them) from a team's draft.", { from: STRING4, to: STRING4 }, ["from", "to"], (teams, team, args, rev, signal) => teams.removeEdge(team, need4(args, "from"), need4(args, "to"), rev, { signal })),
7537
+ draftTool("team_edge_remove", "Remove the path drawn from one role to another (or a two-way path between them) from a team's draft.", { from: STRING6, to: STRING6 }, ["from", "to"], (teams, team, args, rev, signal) => teams.removeEdge(team, need6(args, "from"), need6(args, "to"), rev, { signal })),
7335
7538
  {
7336
7539
  name: "team_discard_draft",
7337
7540
  description: `Drop a team's pending draft (unpublished edits are lost; the published version stays). ${SCOPES}`,
@@ -7339,7 +7542,7 @@ var teamTools = [
7339
7542
  required: ["team", "expectedRevision"],
7340
7543
  readOnly: false,
7341
7544
  idempotent: true,
7342
- run: async (hub, args, signal) => `draft discarded: ${teamLine((await hub.teams.discardDraft(need4(args, "team"), revision(args), { signal })).team)}`
7545
+ run: async (hub, args, signal) => `draft discarded: ${teamLine((await hub.teams.discardDraft(need6(args, "team"), revision(args), { signal })).team)}`
7343
7546
  },
7344
7547
  {
7345
7548
  name: "team_publish",
@@ -7349,7 +7552,7 @@ var teamTools = [
7349
7552
  readOnly: false,
7350
7553
  idempotent: true,
7351
7554
  run: async (hub, args, signal) => {
7352
- const { team, published } = await hub.teams.publish(need4(args, "team"), revision(args), { signal });
7555
+ const { team, published } = await hub.teams.publish(need6(args, "team"), revision(args), { signal });
7353
7556
  return `${published ? `published version ${team.version}` : "the draft equalled the published version; dropped"}: ${teamLine(team)}`;
7354
7557
  }
7355
7558
  },
@@ -7360,17 +7563,17 @@ var teamTools = [
7360
7563
  {
7361
7564
  name: "team_staff",
7362
7565
  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}`,
7363
- properties: { team: TEAM2, role: STRING4, sessionID: { ...STRING4, description: "An existing session (ses_…)." }, launch: LAUNCH, idempotencyKey: IDEMPOTENCY },
7566
+ properties: { team: TEAM2, role: STRING6, sessionID: { ...STRING6, description: "An existing session (ses_…)." }, launch: LAUNCH, idempotencyKey: IDEMPOTENCY },
7364
7567
  required: ["team", "role"],
7365
7568
  readOnly: false,
7366
7569
  run: async (hub, args, signal) => {
7367
- const team = need4(args, "team");
7368
- const role = need4(args, "role");
7369
- const sessionID = optional5(args, "sessionID");
7570
+ const team = need6(args, "team");
7571
+ const role = need6(args, "role");
7572
+ const sessionID = optional6(args, "sessionID");
7370
7573
  const launch = launchOf(args);
7371
7574
  if (sessionID === undefined === (launch === undefined))
7372
7575
  throw new ArgError(`give exactly one of sessionID or launch`);
7373
- const key = optional5(args, "idempotencyKey");
7576
+ const key = optional6(args, "idempotencyKey");
7374
7577
  const options = { signal, ...key ? { idempotencyKey: key } : {} };
7375
7578
  if (sessionID !== undefined) {
7376
7579
  const r = await hub.teams.staff(team, { role, sessionID }, options);
@@ -7383,29 +7586,29 @@ var teamTools = [
7383
7586
  {
7384
7587
  name: "team_unstaff",
7385
7588
  description: `End a seat (the session leaves the role and loses the team's policy rights). Send the seat's revision (team_status). ${SCOPES}`,
7386
- properties: { team: TEAM2, staffingID: { ...STRING4, description: "The seat (stf_…)." }, expectedRevision: { ...REVISION2, description: "The seat's revision you read (team_status)." } },
7589
+ properties: { team: TEAM2, staffingID: { ...STRING6, description: "The seat (stf_…)." }, expectedRevision: { ...REVISION2, description: "The seat's revision you read (team_status)." } },
7387
7590
  required: ["team", "staffingID", "expectedRevision"],
7388
7591
  readOnly: false,
7389
7592
  idempotent: true,
7390
7593
  run: async (hub, args, signal) => {
7391
- const { staffing } = await hub.teams.unstaff(need4(args, "team"), need4(args, "staffingID"), revision(args), { signal });
7594
+ const { staffing } = await hub.teams.unstaff(need6(args, "team"), need6(args, "staffingID"), revision(args), { signal });
7392
7595
  return `unstaffed ${staffing.id} (${staffing.sessionID} as ${staffing.role})`;
7393
7596
  }
7394
7597
  },
7395
7598
  {
7396
7599
  name: "team_replace",
7397
7600
  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}`,
7398
- properties: { team: TEAM2, staffingID: STRING4, sessionID: STRING4, launch: LAUNCH, expectedRevision: { ...REVISION2, description: "The seat's revision you read (team_status)." } },
7601
+ properties: { team: TEAM2, staffingID: STRING6, sessionID: STRING6, launch: LAUNCH, expectedRevision: { ...REVISION2, description: "The seat's revision you read (team_status)." } },
7399
7602
  required: ["team", "staffingID", "expectedRevision"],
7400
7603
  readOnly: false,
7401
7604
  run: async (hub, args, signal) => {
7402
7605
  const expectedRevision = revision(args);
7403
- const sessionID = optional5(args, "sessionID");
7606
+ const sessionID = optional6(args, "sessionID");
7404
7607
  const launch = launchOf(args);
7405
7608
  if (sessionID === undefined === (launch === undefined))
7406
7609
  throw new ArgError(`give exactly one of sessionID or launch`);
7407
7610
  const input = sessionID !== undefined ? { expectedRevision, sessionID } : { expectedRevision, launch };
7408
- const r = await hub.teams.replace(need4(args, "team"), need4(args, "staffingID"), input, { signal });
7611
+ const r = await hub.teams.replace(need6(args, "team"), need6(args, "staffingID"), input, { signal });
7409
7612
  const next = r.staffing ? `${r.staffing.sessionID} (${r.staffing.id}, generation ${r.staffing.generation})` : `launch ${r.launch?.id ?? "?"} (request ${r.request?.id ?? "?"})`;
7410
7613
  return `replaced ${r.ended.sessionID} in ${r.ended.role} by ${next}`;
7411
7614
  }
@@ -7413,13 +7616,13 @@ var teamTools = [
7413
7616
  ];
7414
7617
 
7415
7618
  // packages/cli/src/profiles-mcp.ts
7416
- var STRING5 = { type: "string" };
7417
- var STRINGS2 = { type: "array", items: STRING5 };
7619
+ var STRING7 = { type: "string" };
7620
+ var STRINGS2 = { type: "array", items: STRING7 };
7418
7621
  var ID4 = { type: "string", description: "The profile's ID (prof_…; see profiles)." };
7419
7622
  var REVISION3 = { type: "integer", minimum: 0, description: "The profile's revision you read (profile_show); a stale one answers conflict with the current revision." };
7420
7623
  var IDEMPOTENCY2 = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
7421
- 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"] };
7422
- 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"] };
7624
+ var SKILL = { type: "object", properties: { name: { ...STRING7, description: "A slug." }, description: STRING7, body: { ...STRING7, description: "The skill's full text (loaded on demand)." } }, required: ["name", "description", "body"] };
7625
+ var COMMAND3 = { type: "object", properties: { name: { ...STRING7, description: "A slug." }, description: STRING7, template: { ...STRING7, description: "Text with {{name}} parameters and {{secret:NAME}} references (never resolved by the hub)." } }, required: ["name", "description", "template"] };
7423
7626
  var CONTENT_FIELDS = {
7424
7627
  instructions: { type: ["string", "null"], description: "The profile's instructions (AGENTS.md-like text)." },
7425
7628
  model: { type: ["string", "null"], description: "provider/model the profile suggests." },
@@ -7427,21 +7630,21 @@ var CONTENT_FIELDS = {
7427
7630
  commands: { type: "array", items: COMMAND3, description: "Commands, wholesale." }
7428
7631
  };
7429
7632
  var META_FIELDS = {
7430
- title: { ...STRING5, description: "1–120 characters." },
7431
- description: STRING5,
7633
+ title: { ...STRING7, description: "1–120 characters." },
7634
+ description: STRING7,
7432
7635
  tags: { ...STRINGS2, description: "Slugs." },
7433
- metadata: { type: "object", additionalProperties: STRING5, description: "String key/value pairs (≤ 32 slug keys)." },
7636
+ metadata: { type: "object", additionalProperties: STRING7, description: "String key/value pairs (≤ 32 slug keys)." },
7434
7637
  projects: { ...STRINGS2, description: "Project names the profile is meant for (≤ 16)." }
7435
7638
  };
7436
7639
  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.";
7437
7640
  var ArgError2 = ToolArgError;
7438
- var need5 = (args, key) => {
7641
+ var need7 = (args, key) => {
7439
7642
  const value = args[key];
7440
7643
  if (typeof value !== "string" || !value.trim())
7441
7644
  throw new ArgError2(`"${key}" is required (a non-empty string)`);
7442
7645
  return value;
7443
7646
  };
7444
- var optional6 = (args, key) => {
7647
+ var optional7 = (args, key) => {
7445
7648
  const value = args[key];
7446
7649
  if (value === undefined || value === null)
7447
7650
  return;
@@ -7457,7 +7660,7 @@ var whole2 = (args, key, min, what) => {
7457
7660
  };
7458
7661
  var revision2 = (args) => whole2(args, "expectedRevision", 0, "the revision you read (profile_show; 0 for a session with no pin)");
7459
7662
  var idempotency = (args) => {
7460
- const key = optional6(args, "idempotencyKey");
7663
+ const key = optional7(args, "idempotencyKey");
7461
7664
  return key ? { idempotencyKey: key } : {};
7462
7665
  };
7463
7666
  var picked2 = (args, keys) => Object.fromEntries(keys.filter((k) => args[k] !== undefined).map((k) => [k, args[k]]));
@@ -7490,7 +7693,7 @@ var contentTool = (name, description, properties, required, change) => ({
7490
7693
  idempotent: true,
7491
7694
  run: async (hub, args, signal) => {
7492
7695
  const expectedRevision = revision2(args);
7493
- const id = need5(args, "id");
7696
+ const id = need7(args, "id");
7494
7697
  const { latest, content } = await latestContent(hub.profiles, id, signal);
7495
7698
  return versionText(await hub.profiles.createVersion(id, { expectedRevision, content: change(content, args, latest) }, { signal }));
7496
7699
  }
@@ -7510,7 +7713,7 @@ var stateTool = (verb, description) => ({
7510
7713
  idempotent: true,
7511
7714
  run: async (hub, args, signal) => {
7512
7715
  const expectedRevision = revision2(args);
7513
- const { profile } = await hub.profiles.update(need5(args, "id"), { expectedRevision, state: verb === "archive" ? "archived" : "active" }, { signal });
7716
+ const { profile } = await hub.profiles.update(need7(args, "id"), { expectedRevision, state: verb === "archive" ? "archived" : "active" }, { signal });
7514
7717
  return `${verb === "archive" ? "archived" : "restored"}: ${profileLine(profile)}`;
7515
7718
  }
7516
7719
  });
@@ -7522,7 +7725,7 @@ var profileLifecycleTools = [
7522
7725
  required: ["id"],
7523
7726
  readOnly: true,
7524
7727
  run: async (hub, args, signal) => {
7525
- const { profile, latest } = await hub.profiles.get(need5(args, "id"), { signal });
7728
+ const { profile, latest } = await hub.profiles.get(need7(args, "id"), { signal });
7526
7729
  return [
7527
7730
  profileLine(profile),
7528
7731
  ...profile.description ? [`description: ${one2(profile.description)}`] : [],
@@ -7537,13 +7740,13 @@ var profileLifecycleTools = [
7537
7740
  {
7538
7741
  name: "profile_versions",
7539
7742
  description: `A profile's retained versions, newest first (version, digest, bytes, when, skill and command counts). ${SCOPES2}`,
7540
- properties: { id: ID4, limit: { type: "integer", minimum: 1, maximum: 200 }, cursor: STRING5 },
7743
+ properties: { id: ID4, limit: { type: "integer", minimum: 1, maximum: 200 }, cursor: STRING7 },
7541
7744
  required: ["id"],
7542
7745
  readOnly: true,
7543
7746
  run: async (hub, args, signal) => {
7544
7747
  const limit = args.limit === undefined ? undefined : whole2(args, "limit", 1, "a whole number from 1 to 200");
7545
- const cursor = optional6(args, "cursor");
7546
- const page = await hub.profiles.versions(need5(args, "id"), { signal, ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {} });
7748
+ const cursor = optional7(args, "cursor");
7749
+ const page = await hub.profiles.versions(need7(args, "id"), { signal, ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {} });
7547
7750
  const counts = (v) => v.skills !== undefined ? ` ${v.skills} skills, ${v.commands ?? 0} commands${v.instructions ? ", instructions" : ""}` : "";
7548
7751
  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(`
7549
7752
  `) || "no versions";
@@ -7552,14 +7755,14 @@ var profileLifecycleTools = [
7552
7755
  {
7553
7756
  name: "profile_create",
7554
7757
  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}`,
7555
- properties: { name: { ...STRING5, description: "A slug, unique per user." }, ...META_FIELDS, ...CONTENT_FIELDS, idempotencyKey: IDEMPOTENCY2 },
7758
+ properties: { name: { ...STRING7, description: "A slug, unique per user." }, ...META_FIELDS, ...CONTENT_FIELDS, idempotencyKey: IDEMPOTENCY2 },
7556
7759
  required: ["name", "title"],
7557
7760
  readOnly: false,
7558
7761
  organizes: true,
7559
7762
  idempotent: true,
7560
7763
  run: async (hub, args, signal) => {
7561
- need5(args, "name");
7562
- need5(args, "title");
7764
+ need7(args, "name");
7765
+ need7(args, "title");
7563
7766
  const meta = picked2(args, ["name", "title", "description", "tags", "metadata", "projects"]);
7564
7767
  const text = Object.entries(picked2(args, ["instructions", "model"])).filter(([, v]) => v !== null);
7565
7768
  const content = { ...Object.fromEntries(text), skills: args.skills ?? [], commands: args.commands ?? [] };
@@ -7581,7 +7784,7 @@ var profileLifecycleTools = [
7581
7784
  const patch = picked2(args, ["title", "description", "tags", "metadata", "projects"]);
7582
7785
  if (Object.keys(patch).length === 0)
7583
7786
  throw new ArgError2("give at least one field to change");
7584
- const { profile } = await hub.profiles.update(need5(args, "id"), { expectedRevision, ...patch }, { signal });
7787
+ const { profile } = await hub.profiles.update(need7(args, "id"), { expectedRevision, ...patch }, { signal });
7585
7788
  return `updated: ${profileLine(profile)}`;
7586
7789
  }
7587
7790
  },
@@ -7595,7 +7798,7 @@ var profileLifecycleTools = [
7595
7798
  idempotent: true,
7596
7799
  run: async (hub, args, signal) => {
7597
7800
  const expectedRevision = revision2(args);
7598
- const id = need5(args, "id");
7801
+ const id = need7(args, "id");
7599
7802
  const given = Object.keys(picked2(args, Object.keys(CONTENT_FIELDS)));
7600
7803
  const options = { signal, ...idempotency(args) };
7601
7804
  if (args.fromVersion !== undefined) {
@@ -7616,77 +7819,225 @@ var profileLifecycleTools = [
7616
7819
  return versionText(await hub.profiles.createVersion(id, { expectedRevision, content: next }, options));
7617
7820
  }
7618
7821
  },
7619
- 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) => ({
7822
+ contentTool("profile_skill_set", "Attach a skill to a profile, or replace the skill of that name.", { name: { ...STRING7, description: "The skill's name (a slug)." }, description: STRING7, body: { ...STRING7, description: "The skill's full text." } }, ["name", "description", "body"], (content, args) => ({
7620
7823
  ...content,
7621
- skills: setNamed(content.skills, { name: need5(args, "name"), description: need5(args, "description"), body: need5(args, "body") })
7824
+ skills: setNamed(content.skills, { name: need7(args, "name"), description: need7(args, "description"), body: need7(args, "body") })
7622
7825
  })),
7623
- contentTool("profile_skill_remove", "Detach a skill from a profile.", { name: { ...STRING5, description: "The skill's name." } }, ["name"], (content, args, latest) => ({
7826
+ contentTool("profile_skill_remove", "Detach a skill from a profile.", { name: { ...STRING7, description: "The skill's name." } }, ["name"], (content, args, latest) => ({
7624
7827
  ...content,
7625
- skills: removeNamed(content.skills, need5(args, "name"), "skill", latest)
7828
+ skills: removeNamed(content.skills, need7(args, "name"), "skill", latest)
7626
7829
  })),
7627
- 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") }) })),
7628
- contentTool("profile_command_remove", "Detach a command from a profile.", { name: { ...STRING5, description: "The command's name." } }, ["name"], (content, args, latest) => ({
7830
+ contentTool("profile_command_set", "Attach a command to a profile, or replace the command of that name.", { name: { ...STRING7, description: "The command's name (a slug)." }, description: STRING7, template: COMMAND3.properties.template }, ["name", "description", "template"], (content, args) => ({ ...content, commands: setNamed(content.commands, { name: need7(args, "name"), description: need7(args, "description"), template: need7(args, "template") }) })),
7831
+ contentTool("profile_command_remove", "Detach a command from a profile.", { name: { ...STRING7, description: "The command's name." } }, ["name"], (content, args, latest) => ({
7629
7832
  ...content,
7630
- commands: removeNamed(content.commands, need5(args, "name"), "command", latest)
7833
+ commands: removeNamed(content.commands, need7(args, "name"), "command", latest)
7631
7834
  })),
7632
7835
  stateTool("archive", "Archive a profile: it takes no new versions or pins; pinned sessions keep theirs; reversible with profile_restore."),
7633
7836
  stateTool("restore", "Restore an archived profile (it takes versions and pins again)."),
7634
7837
  {
7635
7838
  name: "profile_assign",
7636
7839
  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}`,
7637
- 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)." } },
7840
+ properties: { addr: { ...STRING7, 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)." } },
7638
7841
  required: ["addr", "profileID", "version", "expectedRevision"],
7639
7842
  readOnly: false,
7640
7843
  idempotent: true,
7641
7844
  run: async (hub, args, signal) => {
7642
7845
  const expectedRevision = revision2(args);
7643
- const { assignment: a } = await hub.profiles.assign(need5(args, "addr"), { profileID: need5(args, "profileID"), version: whole2(args, "version", 1, "a version number"), expectedRevision }, { signal });
7846
+ const { assignment: a } = await hub.profiles.assign(need7(args, "addr"), { profileID: need7(args, "profileID"), version: whole2(args, "version", 1, "a version number"), expectedRevision }, { signal });
7644
7847
  return `${a.sessionID} pins ${a.profileID} v${a.version} r${a.revision}${a.ack ? `, ack ${a.ack.status}${a.ack.offline ? " (offline)" : ""}` : ""}`;
7645
7848
  }
7646
7849
  },
7647
7850
  {
7648
7851
  name: "profile_unassign",
7649
7852
  description: `Remove a session's pin (what it already applied stays until it restarts). Send the pin's revision (profile_assign's answer). ${SCOPES2}`,
7650
- properties: { addr: { ...STRING5, description: "The session." }, expectedRevision: { ...REVISION3, description: "The session's pin revision." } },
7853
+ properties: { addr: { ...STRING7, description: "The session." }, expectedRevision: { ...REVISION3, description: "The session's pin revision." } },
7651
7854
  required: ["addr", "expectedRevision"],
7652
7855
  readOnly: false,
7653
7856
  idempotent: true,
7654
7857
  run: async (hub, args, signal) => {
7655
- const addr = need5(args, "addr");
7858
+ const addr = need7(args, "addr");
7656
7859
  await hub.profiles.unassign(addr, revision2(args), { signal });
7657
7860
  return `unpinned ${addr}`;
7658
7861
  }
7659
7862
  }
7660
7863
  ];
7661
7864
 
7865
+ // packages/cli/src/grants.ts
7866
+ var GRANT_FLAGS = {
7867
+ json: "json",
7868
+ holder: "string",
7869
+ project: "multi",
7870
+ allow: "multi",
7871
+ permit: "multi",
7872
+ never: "multi",
7873
+ expires: "string",
7874
+ "max-per-hour": "string",
7875
+ "allow-always": "bool"
7876
+ };
7877
+ var GRANT_REQUEST_FLAGS = { ...GRANT_FLAGS, "permission-allow": "multi", why: "string" };
7878
+ var GRANT_USER_HINT = "only the user manages grants: use the console, or `hub login` with the user's device token";
7879
+ function userOnly2(source, what) {
7880
+ if (source === "env")
7881
+ throw new CliError(EXIT.forbidden, `only the user can ${what} a grant, not a session`, GRANT_USER_HINT);
7882
+ }
7883
+ function grantError(error) {
7884
+ if (error instanceof CliError && error.exit === EXIT.forbidden)
7885
+ return new CliError(error.exit, error.message, [error.hint, GRANT_USER_HINT].filter(Boolean).join("; "), error.envelope);
7886
+ return error;
7887
+ }
7888
+ var listOf = (flags, name) => (flags[name] ?? []).flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
7889
+ async function grantSpecArg(flags, stdin) {
7890
+ return await grantArg(flags, stdin, true);
7891
+ }
7892
+ function grantRequestArg(flags, stdin) {
7893
+ return grantArg(flags, stdin, false);
7894
+ }
7895
+ async function grantArg(flags, stdin, holderRequired) {
7896
+ const json = typeof flags.json === "string" ? flags.json : undefined;
7897
+ const flagged = Object.keys(GRANT_REQUEST_FLAGS).filter((name) => name !== "json" && name !== "why" && flags[name] !== undefined);
7898
+ if (json !== undefined) {
7899
+ if (flagged.length)
7900
+ throw new UsageError(`give either --json '{…}' or ${flagged.map((f) => `--${f}`).join(", ")}`);
7901
+ return await jsonObjectArg(json, stdin, "grant");
7902
+ }
7903
+ const holder = str(flags, "holder");
7904
+ if (holder === undefined && holderRequired)
7905
+ throw new UsageError("needs --holder (ses_…, @name, @@orchestrator…, or app:<token id>)");
7906
+ const projects = listOf(flags, "project");
7907
+ if (!projects.length)
7908
+ throw new UsageError("needs --project <path> (repeatable)");
7909
+ const allow = listOf(flags, "allow");
7910
+ if (!allow.length)
7911
+ throw new UsageError("needs --allow <verbs> (approve, deny, answer, prompt, stop, roles, launch)");
7912
+ const permit = [...listOf(flags, "permit"), ...listOf(flags, "permission-allow")];
7913
+ const never = listOf(flags, "never");
7914
+ const expires = str(flags, "expires");
7915
+ const cap = str(flags, "max-per-hour");
7916
+ if (cap !== undefined && !/^\d+$/.test(cap))
7917
+ throw new UsageError("--max-per-hour is a whole number");
7918
+ return {
7919
+ ...holder !== undefined ? { holder } : {},
7920
+ scope: { projects },
7921
+ allow,
7922
+ ...permit.length || never.length ? { permissions: { ...permit.length ? { allow: permit } : {}, ...never.length ? { never } : {} } } : {},
7923
+ ...expires !== undefined ? { expires } : {},
7924
+ ...cap !== undefined ? { maxActionsPerHour: Number(cap) } : {},
7925
+ ...flags["allow-always"] === true ? { allowAlways: true } : {}
7926
+ };
7927
+ }
7928
+ var iso4 = (ms) => new Date(ms).toISOString().replace(/\.\d{3}Z$/, "Z");
7929
+ var grantLine = (g) => [g.id, g.state, g.holder, g.allow.join(","), g.scope.projects.join(","), `until ${iso4(g.expiresAt)}`].join(" ");
7930
+ var grantsText = (grants) => grants.length ? grants.map(grantLine).join(`
7931
+ `) : "no grants";
7932
+ function grantText2(g) {
7933
+ const list = (values) => values.length ? values.join(", ") : "none";
7934
+ return [
7935
+ `${g.id} ${g.state}`,
7936
+ `holder: ${g.holder}${g.holderSessionID && g.holderSessionID !== g.holder ? ` (${g.holderSessionID})` : ""}`,
7937
+ `projects: ${list(g.scope.projects)}`,
7938
+ `verbs: ${list(g.allow)}`,
7939
+ `permissions allow: ${list(g.permissions.allow)}`,
7940
+ `permissions never: ${list(g.permissions.never)}`,
7941
+ `always: ${g.allowAlways ? "yes" : "no"}`,
7942
+ `max actions per hour: ${g.maxActionsPerHour}`,
7943
+ g.revokedAt !== undefined ? `revoked: ${iso4(g.revokedAt)}` : `expires: ${iso4(g.expiresAt)}`
7944
+ ].join(`
7945
+ `);
7946
+ }
7947
+ var requestedText = (body) => `requested; waiting for the user (Decision ${body.decision.id})`;
7948
+
7949
+ // packages/cli/src/grants-mcp.ts
7950
+ var STRING8 = { type: "string" };
7951
+ var GRANT = { type: "object", description: "The grant spec accepted by POST /api/agent/grants: holder (optional for a request), scope.projects, allow, permissions, allowAlways, maxActionsPerHour and expires. The server validates all fields.", additionalProperties: true };
7952
+ var need8 = (args, key) => {
7953
+ if (typeof args[key] !== "string" || !args[key].trim())
7954
+ throw new ToolArgError(`"${key}" is required (a non-empty string)`);
7955
+ return args[key];
7956
+ };
7957
+ var spec = (args) => {
7958
+ if (typeof args.grant !== "object" || args.grant === null || Array.isArray(args.grant))
7959
+ throw new ToolArgError('"grant" is required (an object)');
7960
+ return args.grant;
7961
+ };
7962
+ var grantTools = [
7963
+ {
7964
+ name: "grant_request",
7965
+ description: "Ask the user for a grant: opens one tap Decision for @me (approve or deny). Sessions and apps with messages:send only; one open request and at most five per hour. Only the user's answer creates authority.",
7966
+ properties: { grant: GRANT, why: STRING8 },
7967
+ required: ["grant", "why"],
7968
+ readOnly: false,
7969
+ run: async (hub, args, signal) => requestedText(await hub.requestGrant(spec(args), need8(args, "why"), { signal }))
7970
+ },
7971
+ {
7972
+ name: "grant_list",
7973
+ description: "List grants (active, expired or revoked) whose holder is this session or app, including grants for roles it holds now. Apps need messages:read. A user credential has no holder: use the console or hub grant list for administration.",
7974
+ properties: {},
7975
+ readOnly: true,
7976
+ run: async (hub, _args, signal) => grantsText((await hub.heldGrants({ signal })).grants)
7977
+ },
7978
+ {
7979
+ name: "grant_show",
7980
+ description: "Show one grant only if this credential currently holds it. Apps need messages:read; another holder's ID answers not_found.",
7981
+ properties: { id: STRING8 },
7982
+ required: ["id"],
7983
+ readOnly: true,
7984
+ run: async (hub, args, signal) => grantText2((await hub.heldGrant(need8(args, "id"), { signal })).grant)
7985
+ },
7986
+ {
7987
+ name: "grant_revoke",
7988
+ description: "Irreversibly revoke a grant held by this session or app (including a held role shared with other holders). Apps need messages:send; another holder's ID answers not_found.",
7989
+ properties: { id: STRING8 },
7990
+ required: ["id"],
7991
+ readOnly: false,
7992
+ run: async (hub, args, signal) => grantText2((await hub.revokeHeldGrant(need8(args, "id"), { signal })).grant)
7993
+ },
7994
+ {
7995
+ name: "grant_create",
7996
+ description: `Create a grant directly ONLY with the user's device credential. Sessions and apps must use grant_request and wait for the user's tap. ${GRANT_USER_HINT}.`,
7997
+ properties: { grant: GRANT },
7998
+ required: ["grant"],
7999
+ readOnly: false,
8000
+ run: async (hub, args, signal) => {
8001
+ const actor = (await hub.whoami({ signal })).actor;
8002
+ if (actor.kind !== "user")
8003
+ throw new CliError(EXIT.forbidden, "only the user can create a grant, not a session or app", GRANT_USER_HINT);
8004
+ try {
8005
+ return grantText2((await hub.createGrant(spec(args), { signal })).grant);
8006
+ } catch (cause) {
8007
+ throw grantError(cause);
8008
+ }
8009
+ }
8010
+ }
8011
+ ];
8012
+
7662
8013
  // packages/cli/src/watchlist.ts
7663
- var safe = (value) => sanitizeLabel(String(value), 1024);
7664
- var list2 = (items) => items.length ? items.map(safe).join(", ") : "none";
8014
+ var safe2 = (value) => sanitizeLabel(String(value), 1024);
8015
+ var list2 = (items) => items.length ? items.map(safe2).join(", ") : "none";
7665
8016
  function watchlistConfigText(c) {
7666
8017
  return [
7667
- `revision ${safe(c.revision)}`,
7668
- `auto-active: ${c.autoActive ? `on (within ${safe(c.activeWithinHours)} h)` : "off"} pins: ${c.usePins ? "on" : "off"}`,
8018
+ `revision ${safe2(c.revision)}`,
8019
+ `auto-active: ${c.autoActive ? `on (within ${safe2(c.activeWithinHours)} h)` : "off"} pins: ${c.usePins ? "on" : "off"}`,
7669
8020
  `sessions: ${list2(c.sessions)}`,
7670
8021
  `projects: ${list2(c.projects)}`,
7671
- `labels: ${c.labels.length ? c.labels.map((l) => Object.entries(l).map(([k, v]) => `${safe(k)}=${safe(v)}`).join(",")).join("; ") : "none"}`,
8022
+ `labels: ${c.labels.length ? c.labels.map((l) => Object.entries(l).map(([k, v]) => `${safe2(k)}=${safe2(v)}`).join(",")).join("; ") : "none"}`,
7672
8023
  `muted: ${list2(c.muted)}`,
7673
8024
  `muted projects: ${list2(c.mutedProjects)}`
7674
8025
  ].join(`
7675
8026
  `);
7676
8027
  }
7677
8028
  function watchlistText(view) {
7678
- const rows = view.entries.map((e) => `${safe(e.sessionID)} ${e.reasons.map(safe).join(",")}${e.lastActiveAt !== undefined ? ` active ${new Date(e.lastActiveAt).toISOString()}` : ""}`);
8029
+ const rows = view.entries.map((e) => `${safe2(e.sessionID)} ${e.reasons.map(safe2).join(",")}${e.lastActiveAt !== undefined ? ` active ${new Date(e.lastActiveAt).toISOString()}` : ""}`);
7679
8030
  return [watchlistConfigText(view.config), "", view.entries.length ? `watching ${view.entries.length}${view.truncated ? " (truncated)" : ""}:` : "watching nothing", ...rows].join(`
7680
8031
  `);
7681
8032
  }
7682
8033
  function watchlistChangedText(verb, r) {
7683
- return `${r.changed ? `${verb}` : `already ${verb}`}: ${safe(r.sessionID)} (revision ${safe(r.config.revision)})`;
8034
+ return `${r.changed ? `${verb}` : `already ${verb}`}: ${safe2(r.sessionID)} (revision ${safe2(r.config.revision)})`;
7684
8035
  }
7685
8036
  var PAST = { watch: "watching", unwatch: "not watching", mute: "muted", unmute: "unmuted" };
7686
8037
  var watchlistVerbText = (verb, r) => watchlistChangedText(PAST[verb], r);
7687
8038
  var ADDR = { type: "string", description: "Session address: ses_… (or a unique prefix), @name, @@role, @name.project." };
7688
8039
  var STRINGS3 = { type: "array", items: { type: "string" } };
7689
- var need6 = (args, key) => {
8040
+ var need9 = (args, key) => {
7690
8041
  const value = args[key];
7691
8042
  if (typeof value !== "string" || !value.trim())
7692
8043
  throw new ToolArgError(`"${key}" is required (a non-empty string)`);
@@ -7732,13 +8083,13 @@ var watchlistTools = [
7732
8083
  throw new ToolArgError(`"expectedRevision" is required (the revision you read)`);
7733
8084
  const unknown = Object.keys(args).filter((k) => k !== "expectedRevision" && !UPDATE_FIELDS.includes(k));
7734
8085
  if (unknown.length)
7735
- throw new ToolArgError(`Unknown argument${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${safe(k).slice(0, 40)}"`).join(", ")}: use ${UPDATE_FIELDS.join(", ")} and expectedRevision`);
8086
+ throw new ToolArgError(`Unknown argument${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${safe2(k).slice(0, 40)}"`).join(", ")}: use ${UPDATE_FIELDS.join(", ")} and expectedRevision`);
7736
8087
  const patch = {};
7737
8088
  for (const field of UPDATE_FIELDS)
7738
8089
  if (args[field] !== undefined && args[field] !== null)
7739
8090
  patch[field] = args[field];
7740
8091
  const r = await hub.concierge.watchlist.update(patch, expected, { signal });
7741
- const head = r.config.revision === expected ? `No change (revision ${safe(expected)})` : `Updated the watchlist (revision ${safe(expected)} → ${safe(r.config.revision)})`;
8092
+ const head = r.config.revision === expected ? `No change (revision ${safe2(expected)})` : `Updated the watchlist (revision ${safe2(expected)} → ${safe2(r.config.revision)})`;
7742
8093
  return `${head}
7743
8094
  ${watchlistConfigText(r.config)}`;
7744
8095
  }
@@ -7753,7 +8104,7 @@ ${watchlistConfigText(r.config)}`;
7753
8104
  idempotent: true,
7754
8105
  run: async (hub, args, signal) => {
7755
8106
  const on = flag(args, "watch", true);
7756
- const addr = need6(args, "addr");
8107
+ const addr = need9(args, "addr");
7757
8108
  const r = on ? await hub.concierge.watchlist.watch(addr, { signal }) : await hub.concierge.watchlist.unwatch(addr, { signal });
7758
8109
  return watchlistVerbText(on ? "watch" : "unwatch", r);
7759
8110
  }
@@ -7768,7 +8119,7 @@ ${watchlistConfigText(r.config)}`;
7768
8119
  idempotent: true,
7769
8120
  run: async (hub, args, signal) => {
7770
8121
  const on = flag(args, "mute", true);
7771
- const addr = need6(args, "addr");
8122
+ const addr = need9(args, "addr");
7772
8123
  const r = on ? await hub.concierge.watchlist.mute(addr, { signal }) : await hub.concierge.watchlist.unmute(addr, { signal });
7773
8124
  return watchlistVerbText(on ? "mute" : "unmute", r);
7774
8125
  }
@@ -7779,13 +8130,13 @@ ${watchlistConfigText(r.config)}`;
7779
8130
  var MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
7780
8131
  var RPC = { parse: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602, internal: -32603 };
7781
8132
  var ArgError3 = ToolArgError;
7782
- var need7 = (args, key) => {
8133
+ var need10 = (args, key) => {
7783
8134
  const value = args[key];
7784
8135
  if (typeof value !== "string" || !value.trim())
7785
8136
  throw new ArgError3(`"${key}" is required (a non-empty string)`);
7786
8137
  return value;
7787
8138
  };
7788
- var optional7 = (args, key) => {
8139
+ var optional8 = (args, key) => {
7789
8140
  const value = args[key];
7790
8141
  if (value === undefined || value === null)
7791
8142
  return;
@@ -7866,7 +8217,7 @@ var sendOptions = (args) => {
7866
8217
  return { ...wake !== undefined ? { wake } : {}, ...urgent !== undefined ? { urgent } : {}, ...notAfter !== undefined ? { notAfter } : {} };
7867
8218
  };
7868
8219
  var whyOption = (args) => {
7869
- const why = optional7(args, "why");
8220
+ const why = optional8(args, "why");
7870
8221
  return why !== undefined ? { why } : {};
7871
8222
  };
7872
8223
  var optionalDelivery = (args) => {
@@ -7909,35 +8260,35 @@ var definedFields = (fields) => {
7909
8260
  return Object.keys(out).length ? out : undefined;
7910
8261
  };
7911
8262
  function launchSpec(args) {
7912
- const repo = need7(args, "repo");
8263
+ const repo = need10(args, "repo");
7913
8264
  if (!repo.startsWith("/"))
7914
8265
  throw new ArgError3(`"repo" must be an absolute path on the node`);
7915
8266
  const kind = optionalEnum(args, "runtime", RUNTIME_KINDS);
7916
- const profile = optional7(args, "profile");
8267
+ const profile = optional8(args, "profile");
7917
8268
  if (profile !== undefined && kind === undefined)
7918
8269
  throw new ArgError3(`"profile" needs "runtime" (e.g. openshell)`);
7919
8270
  const nodeLabels = optionalLabels(args, "nodeLabels");
7920
- const machine = optional7(args, "machine");
8271
+ const machine = optional8(args, "machine");
7921
8272
  const prefer = optionalBool(args, "prefer") === true;
7922
8273
  if (prefer && machine === undefined && nodeLabels === undefined)
7923
8274
  throw new ArgError3(`"prefer" needs "machine" or "nodeLabels"`);
7924
- const workspace = { repo, ...definedFields({ mode: optionalEnum(args, "mode", WORKSPACE_MODES), branch: optional7(args, "branch"), base: optional7(args, "base"), remote: optional7(args, "remote") }) };
8275
+ const workspace = { repo, ...definedFields({ mode: optionalEnum(args, "mode", WORKSPACE_MODES), branch: optional8(args, "branch"), base: optional8(args, "base"), remote: optional8(args, "remote") }) };
7925
8276
  return {
7926
8277
  ...definedFields({
7927
- key: optional7(args, "key"),
7928
- name: optional7(args, "name")?.replace(/^@+/, ""),
8278
+ key: optional8(args, "key"),
8279
+ name: optional8(args, "name")?.replace(/^@+/, ""),
7929
8280
  roles: optionalStrings(args, "roles")?.map((r) => r.replace(/^@@/, "")),
7930
- harness: optional7(args, "harness"),
7931
- agent: optional7(args, "agent"),
7932
- model: optional7(args, "model")
8281
+ harness: optional8(args, "harness"),
8282
+ agent: optional8(args, "agent"),
8283
+ model: optional8(args, "model")
7933
8284
  }),
7934
8285
  workspace,
7935
8286
  ...definedFields({
7936
8287
  runtime: kind === undefined ? undefined : { kind, ...profile !== undefined ? { profile } : {} },
7937
8288
  placement: definedFields({ labels: nodeLabels, machine, mode: prefer ? "prefer" : undefined }),
7938
- prompt: optional7(args, "prompt"),
8289
+ prompt: optional8(args, "prompt"),
7939
8290
  labels: optionalLabels(args, "labels"),
7940
- end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout: optional7(args, "timeout"), cleanup: optionalEnum(args, "cleanup", CLEANUP_POLICIES) }),
8291
+ end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout: optional8(args, "timeout"), cleanup: optionalEnum(args, "cleanup", CLEANUP_POLICIES) }),
7941
8292
  notAfter: optionalNotAfter(args)
7942
8293
  })
7943
8294
  };
@@ -7984,7 +8335,7 @@ function messageText({ message, answer }) {
7984
8335
  `);
7985
8336
  }
7986
8337
  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.";
7987
- var STRING6 = { type: "string" };
8338
+ var STRING9 = { type: "string" };
7988
8339
  var SEND = {
7989
8340
  wake: { type: "boolean", description: "false: admit the message without starting a turn." },
7990
8341
  urgent: { type: "boolean", description: "Steer into the recipient's running turn." },
@@ -8008,7 +8359,7 @@ var archiveTools = (archived) => [{
8008
8359
  organizes: true,
8009
8360
  idempotent: true,
8010
8361
  run: async (hub, args, signal) => {
8011
- const addr = need7(args, "addr");
8362
+ const addr = need10(args, "addr");
8012
8363
  const result = await hub.setArchived(addr, archived, { signal });
8013
8364
  return `${archived ? "Archived" : "Unarchived"} ${addr} (${result.sessionID})${result.changed ? "" : "; already in that state"}`;
8014
8365
  }
@@ -8022,7 +8373,7 @@ var pinTools = (pinned) => [{
8022
8373
  organizes: true,
8023
8374
  idempotent: true,
8024
8375
  run: async (hub, args, signal) => {
8025
- const addr = need7(args, "addr");
8376
+ const addr = need10(args, "addr");
8026
8377
  const r = await hub.setPinned(addr, pinned, { signal });
8027
8378
  return `${pinned ? "Pinned" : "Unpinned"} ${addr} (${r.sessionID})${r.changed ? "" : "; already in that state"}`;
8028
8379
  }
@@ -8030,14 +8381,14 @@ var pinTools = (pinned) => [{
8030
8381
  var archiveInactiveTool = {
8031
8382
  name: "archive_inactive",
8032
8383
  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.",
8033
- properties: { preview: { type: "boolean" }, inactiveFor: STRING6, limit: { type: "integer", minimum: 1, maximum: 200 }, previewToken: STRING6, confirm: { type: "boolean" } },
8384
+ properties: { preview: { type: "boolean" }, inactiveFor: STRING9, limit: { type: "integer", minimum: 1, maximum: 200 }, previewToken: STRING9, confirm: { type: "boolean" } },
8034
8385
  readOnly: false,
8035
8386
  organizes: true,
8036
8387
  run: async (hub, args, signal) => {
8037
8388
  if (args.preview === true)
8038
- return JSON.stringify(await hub.previewArchiveInactive(need7(args, "inactiveFor"), optionalInt(args, "limit", 1, 200) ?? 50, { signal }));
8389
+ return JSON.stringify(await hub.previewArchiveInactive(need10(args, "inactiveFor"), optionalInt(args, "limit", 1, 200) ?? 50, { signal }));
8039
8390
  if (args.confirm === true)
8040
- return JSON.stringify(await hub.archiveInactive(need7(args, "previewToken"), { signal }));
8391
+ return JSON.stringify(await hub.archiveInactive(need10(args, "previewToken"), { signal }));
8041
8392
  throw new ArgError3("Use preview:true with inactiveFor, then confirm:true with previewToken");
8042
8393
  }
8043
8394
  };
@@ -8048,19 +8399,19 @@ var intentTools = [
8048
8399
  properties: { addr: ADDR2 },
8049
8400
  required: ["addr"],
8050
8401
  readOnly: true,
8051
- run: async (hub, args, signal) => intentLine(need7(args, "addr"), (await hub.intent(need7(args, "addr"), { signal })).intent)
8402
+ run: async (hub, args, signal) => intentLine(need10(args, "addr"), (await hub.intent(need10(args, "addr"), { signal })).intent)
8052
8403
  },
8053
8404
  {
8054
8405
  name: "session_intent_set",
8055
8406
  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.",
8056
- properties: { addr: ADDR2, text: STRING6, clear: { type: "boolean" }, expectedRevision: { type: "integer", minimum: 0 } },
8407
+ properties: { addr: ADDR2, text: STRING9, clear: { type: "boolean" }, expectedRevision: { type: "integer", minimum: 0 } },
8057
8408
  required: ["addr"],
8058
8409
  readOnly: false,
8059
8410
  organizes: true,
8060
8411
  idempotent: true,
8061
8412
  run: async (hub, args, signal) => {
8062
- const addr = need7(args, "addr");
8063
- const text = optional7(args, "text");
8413
+ const addr = need10(args, "addr");
8414
+ const text = optional8(args, "text");
8064
8415
  const clear = args.clear === true;
8065
8416
  if (clear === (text !== undefined))
8066
8417
  throw new ArgError3('Give "text" or "clear": true');
@@ -8070,22 +8421,22 @@ var intentTools = [
8070
8421
  }
8071
8422
  ];
8072
8423
  var TARGET = { ...ADDR2, description: `${ADDR2.description} Required for an app token; a session token defaults to itself.` };
8073
- var ROLE2 = { ...STRING6, description: "A role (reviewer, @@reviewer or reviewer.project)." };
8074
- var ROLE_PROJECT = { ...STRING6, description: "Scope the role to this project (as role.project)." };
8424
+ var ROLE2 = { ...STRING9, description: "A role (reviewer, @@reviewer or reviewer.project)." };
8425
+ var ROLE_PROJECT = { ...STRING9, description: "Scope the role to this project (as role.project)." };
8075
8426
  var MANAGE_NOTE = "Needs scope sessions:manage for an app token";
8076
8427
  var roleText2 = (r) => `@@${r.role}${r.project !== undefined ? `.${r.project}` : ""}`;
8077
8428
  var nameTools = [
8078
8429
  {
8079
8430
  name: "name",
8080
8431
  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}.`,
8081
- properties: { addr: TARGET, name: { ...STRING6, description: "The name: 2–40 characters a-z, 0-9 and - (a leading @ is fine)." } },
8432
+ properties: { addr: TARGET, name: { ...STRING9, description: "The name: 2–40 characters a-z, 0-9 and - (a leading @ is fine)." } },
8082
8433
  required: ["name"],
8083
8434
  readOnly: false,
8084
8435
  organizes: true,
8085
8436
  idempotent: true,
8086
8437
  run: async (hub, args, signal) => {
8087
- const session = optional7(args, "addr");
8088
- const r = await hub.name(need7(args, "name"), { ...session ? { session } : {}, signal });
8438
+ const session = optional8(args, "addr");
8439
+ const r = await hub.name(need10(args, "name"), { ...session ? { session } : {}, signal });
8089
8440
  return `Named ${r.sessionID} @${r.name}${r.alias ? ` (@${r.alias} stays an alias for 24 h)` : ""}`;
8090
8441
  }
8091
8442
  },
@@ -8098,9 +8449,9 @@ var nameTools = [
8098
8449
  organizes: true,
8099
8450
  idempotent: true,
8100
8451
  run: async (hub, args, signal) => {
8101
- const session = optional7(args, "addr");
8102
- const project = optional7(args, "project");
8103
- const r = await hub.claimRole(need7(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, ...optionalBool(args, "exclusive") ? { exclusive: true } : {}, signal });
8452
+ const session = optional8(args, "addr");
8453
+ const project = optional8(args, "project");
8454
+ const r = await hub.claimRole(need10(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, ...optionalBool(args, "exclusive") ? { exclusive: true } : {}, signal });
8104
8455
  return `${r.sessionID} holds ${roleText2(r)} (generation ${r.generation}${r.exclusive ? ", exclusive" : ""})`;
8105
8456
  }
8106
8457
  },
@@ -8112,9 +8463,9 @@ var nameTools = [
8112
8463
  readOnly: false,
8113
8464
  organizes: true,
8114
8465
  run: async (hub, args, signal) => {
8115
- const session = optional7(args, "addr");
8116
- const project = optional7(args, "project");
8117
- const r = await hub.releaseRole(need7(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, signal });
8466
+ const session = optional8(args, "addr");
8467
+ const project = optional8(args, "project");
8468
+ const r = await hub.releaseRole(need10(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, signal });
8118
8469
  return `${r.sessionID} released ${roleText2(r)}`;
8119
8470
  }
8120
8471
  }
@@ -8123,16 +8474,16 @@ var eventsTool = {
8123
8474
  name: "events",
8124
8475
  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.",
8125
8476
  properties: {
8126
- cursor: { ...STRING6, description: "The cursor from the previous events answer (hev1_…); omit to start at the latest." },
8477
+ cursor: { ...STRING9, description: "The cursor from the previous events answer (hev1_…); omit to start at the latest." },
8127
8478
  wait: { type: "integer", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait while nothing matches (0–${WAIT_POLL_MAX_S}).` },
8128
8479
  limit: { type: "integer", minimum: 1, maximum: 200, description: "At most this many events (default 50)." },
8129
- types: { type: "array", items: STRING6, description: "Only these event types (exact, or a family like launch.*)." },
8130
- sessions: { type: "array", items: STRING6, description: "Only events of these sessions (ses_…, @name, @@role)." }
8480
+ types: { type: "array", items: STRING9, description: "Only these event types (exact, or a family like launch.*)." },
8481
+ sessions: { type: "array", items: STRING9, description: "Only events of these sessions (ses_…, @name, @@role)." }
8131
8482
  },
8132
8483
  readOnly: true,
8133
8484
  run: async (hub, args, signal) => {
8134
8485
  const page = await hub.events({
8135
- cursor: optional7(args, "cursor"),
8486
+ cursor: optional8(args, "cursor"),
8136
8487
  wait: optionalInt(args, "wait", 0, WAIT_POLL_MAX_S),
8137
8488
  limit: optionalInt(args, "limit", 1, 200),
8138
8489
  types: optionalStrings(args, "types"),
@@ -8147,13 +8498,13 @@ var webhookTools = [
8147
8498
  {
8148
8499
  name: "webhook_create",
8149
8500
  description: `Create a durable signed webhook for canonical compact events (e.g. launch.*, session.*, message.changed, team.intake). The signing secret is returned only here. Needs events:subscribe plus every selected family's scope; events ["team.intake"] alone needs only teams:submit (an app gets only its own submissions).`,
8150
- properties: { url: STRING6, events: { type: "array", items: STRING6 }, label: STRING6 },
8501
+ properties: { url: STRING9, events: { type: "array", items: STRING9 }, label: STRING9 },
8151
8502
  required: ["url"],
8152
8503
  readOnly: false,
8153
8504
  run: async (hub, args, signal) => {
8154
8505
  const events = optionalStrings(args, "events");
8155
- const label = optional7(args, "label");
8156
- const r = await hub.webhookCreate({ url: need7(args, "url"), ...events ? { events } : {}, ...label ? { label } : {} }, { signal });
8506
+ const label = optional8(args, "label");
8507
+ const r = await hub.webhookCreate({ url: need10(args, "url"), ...events ? { events } : {}, ...label ? { label } : {} }, { signal });
8157
8508
  return `${webhookText(r.webhook)}
8158
8509
  signing secret (shown once): ${r.secret}`;
8159
8510
  }
@@ -8172,11 +8523,11 @@ signing secret (shown once): ${r.secret}`;
8172
8523
  {
8173
8524
  name: "webhook_show",
8174
8525
  description: "Show one webhook and its retained delivery attempts; never its secret.",
8175
- properties: { id: STRING6 },
8526
+ properties: { id: STRING9 },
8176
8527
  required: ["id"],
8177
8528
  readOnly: true,
8178
8529
  run: async (hub, args, signal) => {
8179
- const r = await hub.webhook(need7(args, "id"), { signal });
8530
+ const r = await hub.webhook(need10(args, "id"), { signal });
8180
8531
  return `${webhookText(r.webhook)}
8181
8532
  ${JSON.stringify(r.deliveries)}`;
8182
8533
  }
@@ -8184,20 +8535,20 @@ ${JSON.stringify(r.deliveries)}`;
8184
8535
  {
8185
8536
  name: "webhook_delete",
8186
8537
  description: "Delete one webhook and its pending deliveries.",
8187
- properties: { id: STRING6 },
8538
+ properties: { id: STRING9 },
8188
8539
  required: ["id"],
8189
8540
  readOnly: false,
8190
8541
  organizes: true,
8191
8542
  idempotent: false,
8192
8543
  run: async (hub, args, signal) => {
8193
- const id = need7(args, "id");
8544
+ const id = need10(args, "id");
8194
8545
  await hub.webhookDelete(id, { signal });
8195
8546
  return `Deleted webhook ${id}`;
8196
8547
  }
8197
8548
  }
8198
8549
  ];
8199
8550
  async function profileTarget(hub, args, signal) {
8200
- const addr = optional7(args, "addr");
8551
+ const addr = optional8(args, "addr");
8201
8552
  if (addr)
8202
8553
  return addr;
8203
8554
  const { actor } = await hub.whoami({ signal });
@@ -8236,11 +8587,11 @@ var profileTools = [
8236
8587
  {
8237
8588
  name: "profile_skill",
8238
8589
  description: "Load one skill's full text from the profile pinned to a session (your own by default), on demand.",
8239
- properties: { name: STRING6, addr: ADDR2 },
8590
+ properties: { name: STRING9, addr: ADDR2 },
8240
8591
  required: ["name"],
8241
8592
  readOnly: true,
8242
8593
  run: async (hub, args, signal) => {
8243
- const r = await hub.profiles.sessionSkill(await profileTarget(hub, args, signal), need7(args, "name"), { signal });
8594
+ const r = await hub.profiles.sessionSkill(await profileTarget(hub, args, signal), need10(args, "name"), { signal });
8244
8595
  return `skill ${r.skill.name} (${r.profileID} v${r.version}):
8245
8596
  ${sanitizeText(r.skill.body)}`;
8246
8597
  }
@@ -8248,11 +8599,11 @@ ${sanitizeText(r.skill.body)}`;
8248
8599
  {
8249
8600
  name: "profile_command",
8250
8601
  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.",
8251
- properties: { name: STRING6, params: { type: "object", additionalProperties: STRING6, description: "Parameter name → text value." }, addr: ADDR2 },
8602
+ properties: { name: STRING9, params: { type: "object", additionalProperties: STRING9, description: "Parameter name → text value." }, addr: ADDR2 },
8252
8603
  required: ["name"],
8253
8604
  readOnly: true,
8254
8605
  run: async (hub, args, signal) => {
8255
- const r = await hub.profiles.sessionRender(await profileTarget(hub, args, signal), need7(args, "name"), optionalParams(args), { signal });
8606
+ const r = await hub.profiles.sessionRender(await profileTarget(hub, args, signal), need10(args, "name"), optionalParams(args), { signal });
8256
8607
  return r.prompt;
8257
8608
  }
8258
8609
  },
@@ -8271,13 +8622,13 @@ ${sanitizeText(r.skill.body)}`;
8271
8622
  {
8272
8623
  name: "profile_usage",
8273
8624
  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.",
8274
- properties: { id: STRING6, limit: { type: "integer", minimum: 1, maximum: 200, description: "Sessions per page (default 50)." }, cursor: STRING6 },
8625
+ properties: { id: STRING9, limit: { type: "integer", minimum: 1, maximum: 200, description: "Sessions per page (default 50)." }, cursor: STRING9 },
8275
8626
  required: ["id"],
8276
8627
  readOnly: true,
8277
8628
  run: async (hub, args, signal) => {
8278
8629
  const limit = optionalInt(args, "limit", 1, 200);
8279
- const cursor = optional7(args, "cursor");
8280
- const u = await hub.profiles.usage(need7(args, "id"), { ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {}, signal });
8630
+ const cursor = optional8(args, "cursor");
8631
+ const u = await hub.profiles.usage(need10(args, "id"), { ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {}, signal });
8281
8632
  const c = u.counts;
8282
8633
  const plural = (n, word, many = `${word}s`) => `${n} ${n === 1 ? word : many}`;
8283
8634
  const lines = [`${u.profileID} (latest v${u.latestVersion})`];
@@ -8320,7 +8671,7 @@ var needRevision = (args, key = "expectedRevision") => {
8320
8671
  return value;
8321
8672
  };
8322
8673
  var idemOption = (args) => {
8323
- const key = optional7(args, "idempotencyKey");
8674
+ const key = optional8(args, "idempotencyKey");
8324
8675
  return key !== undefined ? { idempotencyKey: key } : {};
8325
8676
  };
8326
8677
  var WORKSPACE = { type: "string", description: "The workspace's ID (ws_…)." };
@@ -8350,7 +8701,7 @@ var workspaceTools = [
8350
8701
  required: ["workspace"],
8351
8702
  readOnly: true,
8352
8703
  run: async (hub, args, signal) => {
8353
- const id = need7(args, "workspace");
8704
+ const id = need10(args, "workspace");
8354
8705
  const [{ workspace, bindings }, { members }, graph] = await Promise.all([hub.workspaces.get(id, { signal }), hub.workspaces.members(id, { signal }), hub.workspaces.supervision(id, { signal })]);
8355
8706
  const lines = [wsLine(workspace)];
8356
8707
  if (workspace.masterMemberID)
@@ -8366,27 +8717,27 @@ var workspaceTools = [
8366
8717
  {
8367
8718
  name: "workspace_create",
8368
8719
  description: `Create a coordination workspace. ${WS_SCOPES}`,
8369
- properties: { title: STRING6, description: STRING6, tags: { type: "array", items: STRING6 }, idempotencyKey: IDEMPOTENCY3 },
8720
+ properties: { title: STRING9, description: STRING9, tags: { type: "array", items: STRING9 }, idempotencyKey: IDEMPOTENCY3 },
8370
8721
  required: ["title"],
8371
8722
  readOnly: false,
8372
8723
  run: async (hub, args, signal) => {
8373
- const description = optional7(args, "description");
8724
+ const description = optional8(args, "description");
8374
8725
  const tags = optionalStrings(args, "tags");
8375
- const { workspace } = await hub.workspaces.create({ title: need7(args, "title"), ...description ? { description } : {}, ...tags ? { tags } : {} }, { ...idemOption(args), signal });
8726
+ const { workspace } = await hub.workspaces.create({ title: need10(args, "title"), ...description ? { description } : {}, ...tags ? { tags } : {} }, { ...idemOption(args), signal });
8376
8727
  return `created ${wsLine(workspace)}`;
8377
8728
  }
8378
8729
  },
8379
8730
  {
8380
8731
  name: "workspace_update",
8381
8732
  description: `Change a workspace's title, description or tags under its revision. ${WS_SCOPES}`,
8382
- properties: { workspace: WORKSPACE, expectedRevision: REVISION4, title: STRING6, description: STRING6, tags: { type: "array", items: STRING6 } },
8733
+ properties: { workspace: WORKSPACE, expectedRevision: REVISION4, title: STRING9, description: STRING9, tags: { type: "array", items: STRING9 } },
8383
8734
  required: ["workspace", "expectedRevision"],
8384
8735
  readOnly: false,
8385
8736
  run: async (hub, args, signal) => {
8386
- const patch = definedFields({ title: optional7(args, "title"), description: optional7(args, "description"), tags: optionalStrings(args, "tags") });
8737
+ const patch = definedFields({ title: optional8(args, "title"), description: optional8(args, "description"), tags: optionalStrings(args, "tags") });
8387
8738
  if (!patch)
8388
8739
  throw new ArgError3(`give "title", "description" or "tags"`);
8389
- const { workspace } = await hub.workspaces.update(need7(args, "workspace"), { expectedRevision: needRevision(args), ...patch }, { signal });
8740
+ const { workspace } = await hub.workspaces.update(need10(args, "workspace"), { expectedRevision: needRevision(args), ...patch }, { signal });
8390
8741
  return `updated ${wsLine(workspace)}`;
8391
8742
  }
8392
8743
  },
@@ -8398,7 +8749,7 @@ var workspaceTools = [
8398
8749
  readOnly: false,
8399
8750
  idempotent: true,
8400
8751
  run: async (hub, args, signal) => {
8401
- const id = need7(args, "workspace");
8752
+ const id = need10(args, "workspace");
8402
8753
  if (verb === "archive") {
8403
8754
  const r = await hub.workspaces.archive(id, needRevision(args), { signal });
8404
8755
  return `archived ${wsLine(r.workspace)}; ${r.departed} member(s) departed, ${r.supervision.detached} supervision link(s) detached`;
@@ -8409,31 +8760,31 @@ var workspaceTools = [
8409
8760
  {
8410
8761
  name: "workspace_bind",
8411
8762
  description: `Bind a workspace to a repository (absolute path) on one of the user's machines. ${WS_SCOPES}`,
8412
- properties: { workspace: WORKSPACE, machineID: STRING6, repo: STRING6, label: STRING6, idempotencyKey: IDEMPOTENCY3 },
8763
+ properties: { workspace: WORKSPACE, machineID: STRING9, repo: STRING9, label: STRING9, idempotencyKey: IDEMPOTENCY3 },
8413
8764
  required: ["workspace", "machineID", "repo"],
8414
8765
  readOnly: false,
8415
8766
  idempotent: true,
8416
8767
  run: async (hub, args, signal) => {
8417
- const label = optional7(args, "label");
8418
- const r = await hub.workspaces.bind(need7(args, "workspace"), { machineID: need7(args, "machineID"), repo: need7(args, "repo"), ...label ? { label } : {} }, { ...idemOption(args), signal });
8768
+ const label = optional8(args, "label");
8769
+ const r = await hub.workspaces.bind(need10(args, "workspace"), { machineID: need10(args, "machineID"), repo: need10(args, "repo"), ...label ? { label } : {} }, { ...idemOption(args), signal });
8419
8770
  return `${r.created ? "bound" : "already bound"} ${r.binding.id} ${r.binding.machineID}:${r.binding.repo} r${r.binding.revision}`;
8420
8771
  }
8421
8772
  },
8422
8773
  {
8423
8774
  name: "workspace_unbind",
8424
8775
  description: `Remove a repository binding under the binding's revision. ${WS_SCOPES}`,
8425
- properties: { workspace: WORKSPACE, bindingID: STRING6, expectedRevision: REVISION4 },
8776
+ properties: { workspace: WORKSPACE, bindingID: STRING9, expectedRevision: REVISION4 },
8426
8777
  required: ["workspace", "bindingID", "expectedRevision"],
8427
8778
  readOnly: false,
8428
8779
  run: async (hub, args, signal) => {
8429
- await hub.workspaces.unbind(need7(args, "workspace"), need7(args, "bindingID"), needRevision(args), { signal });
8430
- return `unbound ${need7(args, "bindingID")}`;
8780
+ await hub.workspaces.unbind(need10(args, "workspace"), need10(args, "bindingID"), needRevision(args), { signal });
8781
+ return `unbound ${need10(args, "bindingID")}`;
8431
8782
  }
8432
8783
  },
8433
8784
  {
8434
8785
  name: "workspace_join",
8435
8786
  description: `Add a session (ses_…) to a workspace as worker or observer (the master is set by explicit replacement). ${WS_SCOPES}`,
8436
- properties: { workspace: WORKSPACE, sessionID: STRING6, role: { type: "string", enum: [...MEMBER_ROLES] }, idempotencyKey: IDEMPOTENCY3 },
8787
+ properties: { workspace: WORKSPACE, sessionID: STRING9, role: { type: "string", enum: [...MEMBER_ROLES] }, idempotencyKey: IDEMPOTENCY3 },
8437
8788
  required: ["workspace", "sessionID", "role"],
8438
8789
  readOnly: false,
8439
8790
  idempotent: true,
@@ -8441,45 +8792,45 @@ var workspaceTools = [
8441
8792
  const role = optionalEnum(args, "role", MEMBER_ROLES);
8442
8793
  if (!role)
8443
8794
  throw new ArgError3(`"role" is one of ${MEMBER_ROLES.join(", ")}`);
8444
- const { member, created } = await hub.workspaces.join(need7(args, "workspace"), { sessionID: need7(args, "sessionID"), role }, { ...idemOption(args), signal });
8795
+ const { member, created } = await hub.workspaces.join(need10(args, "workspace"), { sessionID: need10(args, "sessionID"), role }, { ...idemOption(args), signal });
8445
8796
  return `member ${member.id} (${member.sessionID}, ${member.role}, generation ${member.generation}, r${member.revision}) ${created ? "joined" : "was already a member"}`;
8446
8797
  }
8447
8798
  },
8448
8799
  {
8449
8800
  name: "workspace_leave",
8450
8801
  description: `Remove a member under its revision; its supervision links are detached. ${WS_SCOPES}`,
8451
- properties: { workspace: WORKSPACE, memberID: STRING6, expectedRevision: REVISION4 },
8802
+ properties: { workspace: WORKSPACE, memberID: STRING9, expectedRevision: REVISION4 },
8452
8803
  required: ["workspace", "memberID", "expectedRevision"],
8453
8804
  readOnly: false,
8454
8805
  idempotent: true,
8455
8806
  run: async (hub, args, signal) => {
8456
- const r = await hub.workspaces.leave(need7(args, "workspace"), need7(args, "memberID"), needRevision(args), { signal });
8807
+ const r = await hub.workspaces.leave(need10(args, "workspace"), need10(args, "memberID"), needRevision(args), { signal });
8457
8808
  return `member ${r.member.id} left (${r.member.leftReason ?? "removed"}); ${r.supervision.detached} supervision link(s) detached`;
8458
8809
  }
8459
8810
  },
8460
8811
  {
8461
8812
  name: "workspace_assign",
8462
8813
  description: `Give a current member an assignment (title and optional outcome criteria; instructions travel as messages). ${WS_SCOPES}`,
8463
- properties: { workspace: WORKSPACE, memberID: STRING6, title: STRING6, criteria: { type: "array", items: STRING6 }, ref: STRING6, idempotencyKey: IDEMPOTENCY3 },
8814
+ properties: { workspace: WORKSPACE, memberID: STRING9, title: STRING9, criteria: { type: "array", items: STRING9 }, ref: STRING9, idempotencyKey: IDEMPOTENCY3 },
8464
8815
  required: ["workspace", "memberID", "title"],
8465
8816
  readOnly: false,
8466
8817
  run: async (hub, args, signal) => {
8467
8818
  const criteria = optionalStrings(args, "criteria");
8468
- const ref = optional7(args, "ref");
8469
- const { assignment } = await hub.workspaces.assign(need7(args, "workspace"), { memberID: need7(args, "memberID"), title: need7(args, "title"), ...criteria ? { criteria } : {}, ...ref ? { ref } : {} }, { ...idemOption(args), signal });
8819
+ const ref = optional8(args, "ref");
8820
+ const { assignment } = await hub.workspaces.assign(need10(args, "workspace"), { memberID: need10(args, "memberID"), title: need10(args, "title"), ...criteria ? { criteria } : {}, ...ref ? { ref } : {} }, { ...idemOption(args), signal });
8470
8821
  return `assignment ${assignment.id} → ${assignment.memberID}: ${assignment.status}, r${assignment.revision}`;
8471
8822
  }
8472
8823
  },
8473
8824
  {
8474
8825
  name: "workspace_assignments",
8475
8826
  description: `A workspace's assignments (id, member, status, revision, title), optionally one member's or one status. ${WS_SCOPES}`,
8476
- properties: { workspace: WORKSPACE, memberID: STRING6, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] } },
8827
+ properties: { workspace: WORKSPACE, memberID: STRING9, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] } },
8477
8828
  required: ["workspace"],
8478
8829
  readOnly: true,
8479
8830
  run: async (hub, args, signal) => {
8480
- const memberID = optional7(args, "memberID");
8831
+ const memberID = optional8(args, "memberID");
8481
8832
  const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
8482
- const r = await hub.workspaces.assignments(need7(args, "workspace"), { ...memberID ? { memberID } : {}, ...status ? { status } : {}, limit: 200, signal });
8833
+ const r = await hub.workspaces.assignments(need10(args, "workspace"), { ...memberID ? { memberID } : {}, ...status ? { status } : {}, limit: 200, signal });
8483
8834
  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(`
8484
8835
  `) : "no assignments";
8485
8836
  }
@@ -8487,16 +8838,16 @@ var workspaceTools = [
8487
8838
  {
8488
8839
  name: "workspace_assignment_update",
8489
8840
  description: `Set an assignment's status (done and cancelled are final) or move it to another current member, under its revision. ${WS_SCOPES}`,
8490
- properties: { workspace: WORKSPACE, assignmentID: STRING6, expectedRevision: REVISION4, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] }, memberID: STRING6 },
8841
+ properties: { workspace: WORKSPACE, assignmentID: STRING9, expectedRevision: REVISION4, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] }, memberID: STRING9 },
8491
8842
  required: ["workspace", "assignmentID", "expectedRevision"],
8492
8843
  readOnly: false,
8493
8844
  run: async (hub, args, signal) => {
8494
8845
  const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
8495
- const memberID = optional7(args, "memberID");
8846
+ const memberID = optional8(args, "memberID");
8496
8847
  if (status === undefined === (memberID === undefined))
8497
8848
  throw new ArgError3(`give exactly one of "status" or "memberID"`);
8498
- const id = need7(args, "workspace");
8499
- const assignmentID = need7(args, "assignmentID");
8849
+ const id = need10(args, "workspace");
8850
+ const assignmentID = need10(args, "assignmentID");
8500
8851
  const { assignment } = status !== undefined ? await hub.workspaces.setAssignmentStatus(id, assignmentID, status, needRevision(args), { signal }) : await hub.workspaces.reassign(id, assignmentID, memberID, needRevision(args), { signal });
8501
8852
  return `assignment ${assignment.id} → ${assignment.memberID}: ${assignment.status}, r${assignment.revision}`;
8502
8853
  }
@@ -8504,16 +8855,16 @@ var workspaceTools = [
8504
8855
  {
8505
8856
  name: "workspace_supervise",
8506
8857
  description: `Set who supervises a member (supervisor null detaches), under the supervision graph's revision (workspace_show). Acyclic, at most 6 levels. ${WS_SCOPES}`,
8507
- properties: { workspace: WORKSPACE, memberID: STRING6, supervisor: { type: ["string", "null"], description: "The supervising member's ID, or null to detach." }, order: { type: "number" }, expectedRevision: REVISION4 },
8858
+ properties: { workspace: WORKSPACE, memberID: STRING9, supervisor: { type: ["string", "null"], description: "The supervising member's ID, or null to detach." }, order: { type: "number" }, expectedRevision: REVISION4 },
8508
8859
  required: ["workspace", "memberID", "supervisor", "expectedRevision"],
8509
8860
  readOnly: false,
8510
8861
  run: async (hub, args, signal) => {
8511
- const supervisor = args.supervisor === null ? null : need7(args, "supervisor");
8862
+ const supervisor = args.supervisor === null ? null : need10(args, "supervisor");
8512
8863
  const order = args.order;
8513
8864
  if (order !== undefined && (typeof order !== "number" || !Number.isFinite(order)))
8514
8865
  throw new ArgError3(`"order" must be a number`);
8515
- const r = await hub.workspaces.supervise(need7(args, "workspace"), { memberID: need7(args, "memberID"), supervisorMemberID: supervisor, ...order !== undefined ? { order } : {}, expectedRevision: needRevision(args) }, { signal });
8516
- 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` : ""}`;
8866
+ const r = await hub.workspaces.supervise(need10(args, "workspace"), { memberID: need10(args, "memberID"), supervisorMemberID: supervisor, ...order !== undefined ? { order } : {}, expectedRevision: needRevision(args) }, { signal });
8867
+ return `${r.change}: ${need10(args, "memberID")}${r.link ? ` ← ${r.link.supervisorMemberID} (order ${r.link.order})` : ""}; supervision revision ${r.revision}${r.pruned ? `, ${r.pruned} stale link(s) dropped` : ""}`;
8517
8868
  }
8518
8869
  },
8519
8870
  {
@@ -8523,7 +8874,7 @@ var workspaceTools = [
8523
8874
  required: ["workspace"],
8524
8875
  readOnly: true,
8525
8876
  run: async (hub, args, signal) => {
8526
- const v = await hub.workspaces.master(need7(args, "workspace"), { signal });
8877
+ const v = await hub.workspaces.master(need10(args, "workspace"), { signal });
8527
8878
  const line = (m) => `${m.member.id} ${m.member.sessionID} generation ${m.member.generation} ${m.presence}${m.member.leftReason ? ` (${m.member.leftReason})` : ""}`;
8528
8879
  return [
8529
8880
  `workspace ${v.workspaceID} r${v.revision}`,
@@ -8537,17 +8888,17 @@ var workspaceTools = [
8537
8888
  {
8538
8889
  name: "workspace_master_replace",
8539
8890
  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}`,
8540
- properties: { workspace: WORKSPACE, sessionID: STRING6, launchID: STRING6, expectedRevision: REVISION4, idempotencyKey: IDEMPOTENCY3 },
8891
+ properties: { workspace: WORKSPACE, sessionID: STRING9, launchID: STRING9, expectedRevision: REVISION4, idempotencyKey: IDEMPOTENCY3 },
8541
8892
  required: ["workspace", "expectedRevision"],
8542
8893
  readOnly: false,
8543
8894
  idempotent: true,
8544
8895
  run: async (hub, args, signal) => {
8545
- const sessionID = optional7(args, "sessionID");
8546
- const launchID = optional7(args, "launchID");
8896
+ const sessionID = optional8(args, "sessionID");
8897
+ const launchID = optional8(args, "launchID");
8547
8898
  if (sessionID === undefined === (launchID === undefined))
8548
8899
  throw new ArgError3(`give exactly one of "sessionID" or "launchID"`);
8549
8900
  const target = sessionID !== undefined ? { sessionID } : { launchID };
8550
- const r = await hub.workspaces.replaceMaster(need7(args, "workspace"), { ...target, expectedRevision: needRevision(args) }, { ...idemOption(args), signal });
8901
+ const r = await hub.workspaces.replaceMaster(need10(args, "workspace"), { ...target, expectedRevision: needRevision(args) }, { ...idemOption(args), signal });
8551
8902
  if (!r.changed)
8552
8903
  return `${r.master.sessionID} already is the master (${r.master.id}); nothing changed`;
8553
8904
  return [
@@ -8565,7 +8916,7 @@ var workspaceTools = [
8565
8916
  required: ["workspace"],
8566
8917
  readOnly: true,
8567
8918
  run: async (hub, args, signal) => {
8568
- const b = await hub.workspaces.board(need7(args, "workspace"), { signal });
8919
+ const b = await hub.workspaces.board(need10(args, "workspace"), { signal });
8569
8920
  const more = (truncated) => truncated ? " (more not shown)" : "";
8570
8921
  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})` : ""}`];
8571
8922
  const lines = [wsLine(b.workspace), ...b.workspace.masterMemberID ? [`master: ${b.workspace.masterMemberID}`] : []];
@@ -8591,10 +8942,13 @@ var TOOLS = [
8591
8942
  ...webhookTools,
8592
8943
  ...profileTools,
8593
8944
  ...profileLifecycleTools,
8945
+ ...grantTools,
8594
8946
  ...workspaceTools,
8595
8947
  ...artifactTools,
8596
8948
  ...artifactJobTools,
8597
8949
  ...teamIntakeTools,
8950
+ ...subscriptionTools,
8951
+ ...operationTools,
8598
8952
  ...teamTools,
8599
8953
  ...watchlistTools,
8600
8954
  {
@@ -8615,14 +8969,14 @@ var TOOLS = [
8615
8969
  name: "sessions",
8616
8970
  description: "The sessions as a flat list, optionally filtered.",
8617
8971
  properties: {
8618
- project: { ...STRING6, description: "Only this project." },
8619
- state: { ...STRING6, description: "Only these states (comma-separated, e.g. busy,blocked)." },
8620
- machine: { ...STRING6, description: "Only this machine." },
8621
- label: { type: "array", items: STRING6, description: "key=value labels that must all match." }
8972
+ project: { ...STRING9, description: "Only this project." },
8973
+ state: { ...STRING9, description: "Only these states (comma-separated, e.g. busy,blocked)." },
8974
+ machine: { ...STRING9, description: "Only this machine." },
8975
+ label: { type: "array", items: STRING9, description: "key=value labels that must all match." }
8622
8976
  },
8623
8977
  readOnly: true,
8624
8978
  run: async (hub, args, signal) => {
8625
- const body = await hub.sessions({ project: optional7(args, "project"), state: optional7(args, "state"), machine: optional7(args, "machine"), label: optionalStrings(args, "label"), signal });
8979
+ const body = await hub.sessions({ project: optional8(args, "project"), state: optional8(args, "state"), machine: optional8(args, "machine"), label: optionalStrings(args, "label"), signal });
8626
8980
  return sessionsText(body.sessions ?? []);
8627
8981
  }
8628
8982
  },
@@ -8632,7 +8986,7 @@ var TOOLS = [
8632
8986
  properties: { addr: ADDR2 },
8633
8987
  required: ["addr"],
8634
8988
  readOnly: true,
8635
- run: async (hub, args, signal) => showText(await hub.show(need7(args, "addr"), { signal }))
8989
+ run: async (hub, args, signal) => showText(await hub.show(need10(args, "addr"), { signal }))
8636
8990
  },
8637
8991
  {
8638
8992
  name: "log",
@@ -8641,7 +8995,7 @@ var TOOLS = [
8641
8995
  required: ["addr"],
8642
8996
  readOnly: true,
8643
8997
  run: async (hub, args, signal) => {
8644
- const addr = need7(args, "addr");
8998
+ const addr = need10(args, "addr");
8645
8999
  const n = optionalInt(args, "n", 1, 200);
8646
9000
  return logText((await hub.log(addr, { ...n !== undefined ? { n } : {}, signal })).rows ?? []);
8647
9001
  }
@@ -8650,25 +9004,25 @@ var TOOLS = [
8650
9004
  name: "who",
8651
9005
  description: "Named and role-holding sessions; a team member sees its teams, grouped by team with its reach to each.",
8652
9006
  properties: {
8653
- project: { ...STRING6, description: "Only this project." },
8654
- role: { ...STRING6, description: "Only holders of this role (reviewer, @@reviewer or reviewer#team)." },
8655
- team: { ...STRING6, description: "Only this team (name or ID)." }
9007
+ project: { ...STRING9, description: "Only this project." },
9008
+ role: { ...STRING9, description: "Only holders of this role (reviewer, @@reviewer or reviewer#team)." },
9009
+ team: { ...STRING9, description: "Only this team (name or ID)." }
8656
9010
  },
8657
9011
  readOnly: true,
8658
9012
  run: async (hub, args, signal) => {
8659
- const body = await hub.who({ project: optional7(args, "project"), role: optional7(args, "role"), team: optional7(args, "team"), signal });
9013
+ const body = await hub.who({ project: optional8(args, "project"), role: optional8(args, "role"), team: optional8(args, "team"), signal });
8660
9014
  return whoText(body.sessions ?? [], body);
8661
9015
  }
8662
9016
  },
8663
9017
  {
8664
9018
  name: "msg",
8665
9019
  description: "Tell a session something (fire and forget). Sent as this server's credential, not as an OpenCode session.",
8666
- properties: { to: TO, text: { ...STRING6, description: "The message." }, ...SEND, idempotencyKey: IDEMPOTENCY3 },
9020
+ properties: { to: TO, text: { ...STRING9, description: "The message." }, ...SEND, idempotencyKey: IDEMPOTENCY3 },
8667
9021
  required: ["to", "text"],
8668
9022
  readOnly: false,
8669
9023
  run: async (hub, args, signal) => {
8670
- const to = need7(args, "to");
8671
- const text = need7(args, "text");
9024
+ const to = need10(args, "to");
9025
+ const text = need10(args, "text");
8672
9026
  const options = { ...sendOptions(args), ...idemOption(args), signal };
8673
9027
  if (optionalBool(args, "all"))
8674
9028
  return sentAllText(await hub.msg(to, text, { ...options, all: true }));
@@ -8680,7 +9034,7 @@ var TOOLS = [
8680
9034
  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.`,
8681
9035
  properties: {
8682
9036
  to: TO,
8683
- text: { ...STRING6, description: "The question." },
9037
+ text: { ...STRING9, description: "The question." },
8684
9038
  wait: { type: "number", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait for the answer (0–${WAIT_POLL_MAX_S}).` },
8685
9039
  expires: { type: ["number", "string"], description: "When the ask expires: seconds, or 30s, 5m, 2h, 1d (1 minute to 7 days; default 24 h)." },
8686
9040
  ...SEND,
@@ -8689,8 +9043,8 @@ var TOOLS = [
8689
9043
  required: ["to", "text"],
8690
9044
  readOnly: false,
8691
9045
  run: async (hub, args, signal) => {
8692
- const to = need7(args, "to");
8693
- const text = need7(args, "text");
9046
+ const to = need10(args, "to");
9047
+ const text = need10(args, "text");
8694
9048
  const wait = optionalWait(args);
8695
9049
  const expires = optionalExpires(args);
8696
9050
  const options = { ...sendOptions(args), ...expires !== undefined ? { expires } : {}, ...idemOption(args), signal };
@@ -8708,12 +9062,12 @@ ${openedText(asked.decision)}` : outcome;
8708
9062
  {
8709
9063
  name: "reply",
8710
9064
  description: "Reply to a message you received; it goes to the message's real sender.",
8711
- properties: { id: MSG_ID, text: { ...STRING6, description: "The reply." }, wake: SEND.wake },
9065
+ properties: { id: MSG_ID, text: { ...STRING9, description: "The reply." }, wake: SEND.wake },
8712
9066
  required: ["id", "text"],
8713
9067
  readOnly: false,
8714
9068
  run: async (hub, args, signal) => {
8715
- const id = need7(args, "id");
8716
- const text = need7(args, "text");
9069
+ const id = need10(args, "id");
9070
+ const text = need10(args, "text");
8717
9071
  const wake = optionalBool(args, "wake");
8718
9072
  return repliedText(await hub.reply(id, text, { ...wake !== undefined ? { wake } : {}, signal }));
8719
9073
  }
@@ -8721,12 +9075,12 @@ ${openedText(asked.decision)}` : outcome;
8721
9075
  {
8722
9076
  name: "decline",
8723
9077
  description: "Decline an ask you received, with a reason.",
8724
- properties: { id: MSG_ID, why: { ...STRING6, description: "The reason." } },
9078
+ properties: { id: MSG_ID, why: { ...STRING9, description: "The reason." } },
8725
9079
  required: ["id", "why"],
8726
9080
  readOnly: false,
8727
9081
  run: async (hub, args, signal) => {
8728
- const id = need7(args, "id");
8729
- return declinedText(await hub.decline(id, need7(args, "why"), { signal }));
9082
+ const id = need10(args, "id");
9083
+ return declinedText(await hub.decline(id, need10(args, "why"), { signal }));
8730
9084
  }
8731
9085
  },
8732
9086
  {
@@ -8746,7 +9100,7 @@ ${openedText(asked.decision)}` : outcome;
8746
9100
  required: ["id"],
8747
9101
  readOnly: true,
8748
9102
  run: async (hub, args, signal) => {
8749
- const id = need7(args, "id");
9103
+ const id = need10(args, "id");
8750
9104
  const wait = optionalWait(args);
8751
9105
  return messageText(await hub.message(id, { ...wait !== undefined ? { wait } : {}, signal }));
8752
9106
  }
@@ -8759,7 +9113,7 @@ ${openedText(asked.decision)}` : outcome;
8759
9113
  readOnly: false,
8760
9114
  organizes: true,
8761
9115
  idempotent: true,
8762
- run: async (hub, args, signal) => mutedText((await hub[verb](need7(args, "addr"), { signal })).muted)
9116
+ run: async (hub, args, signal) => mutedText((await hub[verb](need10(args, "addr"), { signal })).muted)
8763
9117
  })),
8764
9118
  {
8765
9119
  name: "decisions",
@@ -8767,7 +9121,7 @@ ${openedText(asked.decision)}` : outcome;
8767
9121
  properties: { state: { type: "string", enum: [...DECISION_STATES], description: "Only Decisions in this state." } },
8768
9122
  readOnly: true,
8769
9123
  run: async (hub, args, signal) => {
8770
- const state = optional7(args, "state");
9124
+ const state = optional8(args, "state");
8771
9125
  if (state !== undefined && !DECISION_STATES.includes(state))
8772
9126
  throw new ArgError3(`"state" is one of ${DECISION_STATES.join(", ")}`);
8773
9127
  const { decisions } = await hub.decisions({ ...state ? { state } : {}, signal });
@@ -8782,7 +9136,7 @@ ${decisionsText(decisions)}` : decisionsText(decisions);
8782
9136
  required: ["id"],
8783
9137
  readOnly: true,
8784
9138
  run: async (hub, args, signal) => `${DECISIONS_NOTE}
8785
- ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
9139
+ ${decisionText((await hub.decision(need10(args, "id"), { signal })).decision)}`
8786
9140
  },
8787
9141
  {
8788
9142
  name: "approve",
@@ -8791,8 +9145,8 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
8791
9145
  required: ["addr", "requestID"],
8792
9146
  readOnly: false,
8793
9147
  run: async (hub, args, signal) => {
8794
- const addr = need7(args, "addr");
8795
- const requestID = need7(args, "requestID");
9148
+ const addr = need10(args, "addr");
9149
+ const requestID = need10(args, "requestID");
8796
9150
  const always = optionalBool(args, "always");
8797
9151
  return approvedText(await hub.approve(addr, requestID, { ...always ? { always } : {}, ...whyOption(args), signal }));
8798
9152
  }
@@ -8804,8 +9158,8 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
8804
9158
  required: ["addr", "requestID"],
8805
9159
  readOnly: false,
8806
9160
  run: async (hub, args, signal) => {
8807
- const addr = need7(args, "addr");
8808
- const requestID = need7(args, "requestID");
9161
+ const addr = need10(args, "addr");
9162
+ const requestID = need10(args, "requestID");
8809
9163
  return deniedText(await hub.deny(addr, requestID, { ...whyOption(args), signal }));
8810
9164
  }
8811
9165
  },
@@ -8822,8 +9176,8 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
8822
9176
  required: ["addr", "formID"],
8823
9177
  readOnly: false,
8824
9178
  run: async (hub, args, signal) => {
8825
- const addr = need7(args, "addr");
8826
- const formID = need7(args, "formID");
9179
+ const addr = need10(args, "addr");
9180
+ const formID = need10(args, "formID");
8827
9181
  const given = formAnswer(args);
8828
9182
  const options = { ...whyOption(args), signal };
8829
9183
  return answeredText(given === "cancel" ? await hub.cancelForm(addr, formID, options) : await hub.answer(addr, formID, given, options), given === "cancel");
@@ -8834,15 +9188,15 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
8834
9188
  description: `Prompt a session (an agent's prompt carries the hub's provenance). ${NEEDS_USER}`,
8835
9189
  properties: {
8836
9190
  addr: ADDR2,
8837
- text: { ...STRING6, description: "The prompt." },
9191
+ text: { ...STRING9, description: "The prompt." },
8838
9192
  delivery: { type: "string", enum: [...DELIVERIES], description: "How it enters a busy session (the spoke picks when unset)." },
8839
9193
  why: WHY
8840
9194
  },
8841
9195
  required: ["addr", "text"],
8842
9196
  readOnly: false,
8843
9197
  run: async (hub, args, signal) => {
8844
- const addr = need7(args, "addr");
8845
- const text = need7(args, "text");
9198
+ const addr = need10(args, "addr");
9199
+ const text = need10(args, "text");
8846
9200
  const delivery = optionalDelivery(args);
8847
9201
  return promptedText(await hub.prompt(addr, text, { ...delivery ? { delivery } : {}, ...whyOption(args), signal }));
8848
9202
  }
@@ -8853,32 +9207,32 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
8853
9207
  properties: { addr: ADDR2, why: WHY },
8854
9208
  required: ["addr"],
8855
9209
  readOnly: false,
8856
- run: async (hub, args, signal) => stoppedText(await hub.stop(need7(args, "addr"), { ...whyOption(args), signal }))
9210
+ run: async (hub, args, signal) => stoppedText(await hub.stop(need10(args, "addr"), { ...whyOption(args), signal }))
8857
9211
  },
8858
9212
  {
8859
9213
  name: "launch",
8860
9214
  description: "Start a new session on a node (a hub node with the repo under its workspace roots). The same key again answers the same launch. With notAfter, a launch still waiting for a machine at that time is never started (it ends expired). Needs an app token with scope sessions:launch and a grant with verb launch covering the repo.",
8861
9215
  properties: {
8862
- repo: { ...STRING6, description: "Absolute path of the repository on the node (or where a clone goes)." },
9216
+ repo: { ...STRING9, description: "Absolute path of the repository on the node (or where a clone goes)." },
8863
9217
  mode: { type: "string", enum: [...WORKSPACE_MODES], description: "worktree (default: <repo>/.worktrees/<branch>, needs branch), checkout, or clone (needs remote)." },
8864
- branch: { ...STRING6, description: "The branch to create or check out." },
8865
- base: { ...STRING6, description: "What a new branch starts from." },
8866
- remote: { ...STRING6, description: "An https or ssh git remote (for clone)." },
8867
- key: { ...STRING6, description: "Idempotency key: retrying with the same key and arguments answers the same launch." },
8868
- name: { ...STRING6, description: "The session's @name." },
8869
- roles: { type: "array", items: STRING6, description: "Roles the session claims (e.g. reviewer.hub)." },
8870
- harness: { ...STRING6, description: "The harness (default opencode)." },
8871
- agent: { ...STRING6, description: "The harness agent (e.g. build)." },
8872
- model: { ...STRING6, description: "provider/model." },
9218
+ branch: { ...STRING9, description: "The branch to create or check out." },
9219
+ base: { ...STRING9, description: "What a new branch starts from." },
9220
+ remote: { ...STRING9, description: "An https or ssh git remote (for clone)." },
9221
+ key: { ...STRING9, description: "Idempotency key: retrying with the same key and arguments answers the same launch." },
9222
+ name: { ...STRING9, description: "The session's @name." },
9223
+ roles: { type: "array", items: STRING9, description: "Roles the session claims (e.g. reviewer.hub)." },
9224
+ harness: { ...STRING9, description: "The harness (default opencode)." },
9225
+ agent: { ...STRING9, description: "The harness agent (e.g. build)." },
9226
+ model: { ...STRING9, description: "provider/model." },
8873
9227
  runtime: { type: "string", enum: [...RUNTIME_KINDS], description: "host (default) or openshell (a sandbox)." },
8874
- profile: { ...STRING6, description: "The runtime's sandbox profile (needs runtime)." },
8875
- machine: { ...STRING6, description: "The machine (name or ID) to run on." },
9228
+ profile: { ...STRING9, description: "The runtime's sandbox profile (needs runtime)." },
9229
+ machine: { ...STRING9, description: "The machine (name or ID) to run on." },
8876
9230
  nodeLabels: { ...LABELS, description: 'Node labels to place on (e.g. {"os": "darwin"}).' },
8877
9231
  prefer: { type: "boolean", description: "machine and nodeLabels are preferences, not requirements." },
8878
9232
  labels: { ...LABELS, description: "Free-form labels for finding the launch and its session later (never interpreted by the hub)." },
8879
- prompt: { ...STRING6, description: "The session's first prompt." },
9233
+ prompt: { ...STRING9, description: "The session's first prompt." },
8880
9234
  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." },
8881
- timeout: { ...STRING6, description: "Longest run, e.g. 4h or 90m." },
9235
+ timeout: { ...STRING9, description: "Longest run, e.g. 4h or 90m." },
8882
9236
  cleanup: { type: "string", enum: [...CLEANUP_POLICIES], description: "keep (default) or remove the workspace when it ends." },
8883
9237
  notAfter: { type: ["number", "string"], description: "Do not start after this time: epoch milliseconds or an ISO 8601 date-time (at most 30 days ahead). Still waiting for a machine then, the launch ends expired; once placed it runs (timeout bounds it). Already passed: nothing is launched (expired)." }
8884
9238
  },
@@ -8894,12 +9248,12 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
8894
9248
  name: "launches",
8895
9249
  description: "Your launches, oldest first (an app or session sees the ones it started).",
8896
9250
  properties: {
8897
- state: { ...STRING6, description: "Only these states (comma-separated: queued, placing, provisioning, running, ended)." },
8898
- label: { type: "array", items: STRING6, description: "key=value launch labels that must all match." }
9251
+ state: { ...STRING9, description: "Only these states (comma-separated: queued, placing, provisioning, running, ended)." },
9252
+ label: { type: "array", items: STRING9, description: "key=value launch labels that must all match." }
8899
9253
  },
8900
9254
  readOnly: true,
8901
9255
  run: async (hub, args, signal) => {
8902
- const state = optional7(args, "state")?.split(",").filter(Boolean);
9256
+ const state = optional8(args, "state")?.split(",").filter(Boolean);
8903
9257
  const label = optionalStrings(args, "label");
8904
9258
  return launchesText((await hub.launches.list({ ...state?.length ? { state } : {}, ...label ? { label } : {}, signal })).launches ?? []);
8905
9259
  }
@@ -8910,7 +9264,7 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
8910
9264
  properties: { id: LAUNCH_ID },
8911
9265
  required: ["id"],
8912
9266
  readOnly: true,
8913
- run: async (hub, args, signal) => launchText((await hub.launches.get(need7(args, "id"), { signal })).launch)
9267
+ run: async (hub, args, signal) => launchText((await hub.launches.get(need10(args, "id"), { signal })).launch)
8914
9268
  },
8915
9269
  {
8916
9270
  name: "launch_stop",
@@ -8918,7 +9272,7 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
8918
9272
  properties: { id: LAUNCH_ID },
8919
9273
  required: ["id"],
8920
9274
  readOnly: false,
8921
- run: async (hub, args, signal) => stoppedLaunchText((await hub.launches.stop(need7(args, "id"), { signal })).launch)
9275
+ run: async (hub, args, signal) => stoppedLaunchText((await hub.launches.stop(need10(args, "id"), { signal })).launch)
8922
9276
  }
8923
9277
  ];
8924
9278
  var toolList = () => TOOLS.map((t) => ({
@@ -9113,90 +9467,6 @@ async function watch(credentials, options, write, signal) {
9113
9467
  throw new CliError(EXIT.other, "the watch stream ended (the hub closed it)", AGAIN);
9114
9468
  }
9115
9469
 
9116
- // packages/cli/src/grants.ts
9117
- var GRANT_FLAGS = {
9118
- json: "json",
9119
- holder: "string",
9120
- project: "multi",
9121
- allow: "multi",
9122
- permit: "multi",
9123
- never: "multi",
9124
- expires: "string",
9125
- "max-per-hour": "string",
9126
- "allow-always": "bool"
9127
- };
9128
- var GRANT_REQUEST_FLAGS = { ...GRANT_FLAGS, "permission-allow": "multi", why: "string" };
9129
- var GRANT_USER_HINT = "only the user manages grants: use the console, or `hub login` with the user's device token";
9130
- function userOnly(source, what) {
9131
- if (source === "env")
9132
- throw new CliError(EXIT.forbidden, `only the user can ${what} a grant, not a session`, GRANT_USER_HINT);
9133
- }
9134
- function grantError(error) {
9135
- if (error instanceof CliError && error.exit === EXIT.forbidden)
9136
- return new CliError(error.exit, error.message, [error.hint, GRANT_USER_HINT].filter(Boolean).join("; "), error.envelope);
9137
- return error;
9138
- }
9139
- var listOf = (flags, name) => (flags[name] ?? []).flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
9140
- async function grantSpecArg(flags, stdin) {
9141
- return await grantArg(flags, stdin, true);
9142
- }
9143
- function grantRequestArg(flags, stdin) {
9144
- return grantArg(flags, stdin, false);
9145
- }
9146
- async function grantArg(flags, stdin, holderRequired) {
9147
- const json = typeof flags.json === "string" ? flags.json : undefined;
9148
- const flagged = Object.keys(GRANT_REQUEST_FLAGS).filter((name) => name !== "json" && name !== "why" && flags[name] !== undefined);
9149
- if (json !== undefined) {
9150
- if (flagged.length)
9151
- throw new UsageError(`give either --json '{…}' or ${flagged.map((f) => `--${f}`).join(", ")}`);
9152
- return await jsonObjectArg(json, stdin, "grant");
9153
- }
9154
- const holder = str(flags, "holder");
9155
- if (holder === undefined && holderRequired)
9156
- throw new UsageError("needs --holder (ses_…, @name, @@orchestrator…, or app:<token id>)");
9157
- const projects = listOf(flags, "project");
9158
- if (!projects.length)
9159
- throw new UsageError("needs --project <path> (repeatable)");
9160
- const allow = listOf(flags, "allow");
9161
- if (!allow.length)
9162
- throw new UsageError("needs --allow <verbs> (approve, deny, answer, prompt, stop, roles, launch)");
9163
- const permit = [...listOf(flags, "permit"), ...listOf(flags, "permission-allow")];
9164
- const never = listOf(flags, "never");
9165
- const expires = str(flags, "expires");
9166
- const cap = str(flags, "max-per-hour");
9167
- if (cap !== undefined && !/^\d+$/.test(cap))
9168
- throw new UsageError("--max-per-hour is a whole number");
9169
- return {
9170
- ...holder !== undefined ? { holder } : {},
9171
- scope: { projects },
9172
- allow,
9173
- ...permit.length || never.length ? { permissions: { ...permit.length ? { allow: permit } : {}, ...never.length ? { never } : {} } } : {},
9174
- ...expires !== undefined ? { expires } : {},
9175
- ...cap !== undefined ? { maxActionsPerHour: Number(cap) } : {},
9176
- ...flags["allow-always"] === true ? { allowAlways: true } : {}
9177
- };
9178
- }
9179
- var iso3 = (ms) => new Date(ms).toISOString().replace(/\.\d{3}Z$/, "Z");
9180
- var grantLine = (g) => [g.id, g.state, g.holder, g.allow.join(","), g.scope.projects.join(","), `until ${iso3(g.expiresAt)}`].join(" ");
9181
- var grantsText = (grants) => grants.length ? grants.map(grantLine).join(`
9182
- `) : "no grants";
9183
- function grantText2(g) {
9184
- const list = (values) => values.length ? values.join(", ") : "none";
9185
- return [
9186
- `${g.id} ${g.state}`,
9187
- `holder: ${g.holder}${g.holderSessionID && g.holderSessionID !== g.holder ? ` (${g.holderSessionID})` : ""}`,
9188
- `projects: ${list(g.scope.projects)}`,
9189
- `verbs: ${list(g.allow)}`,
9190
- `permissions allow: ${list(g.permissions.allow)}`,
9191
- `permissions never: ${list(g.permissions.never)}`,
9192
- `always: ${g.allowAlways ? "yes" : "no"}`,
9193
- `max actions per hour: ${g.maxActionsPerHour}`,
9194
- g.revokedAt !== undefined ? `revoked: ${iso3(g.revokedAt)}` : `expires: ${iso3(g.expiresAt)}`
9195
- ].join(`
9196
- `);
9197
- }
9198
- var requestedText = (body) => `requested; waiting for the user (Decision ${body.decision.id})`;
9199
-
9200
9470
  // packages/cli/src/node.ts
9201
9471
  import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync8, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync8, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
9202
9472
  import path16 from "node:path";
@@ -9422,7 +9692,7 @@ import { arch as osArch, platform as osPlatform } from "node:os";
9422
9692
  // packages/node/package.json
9423
9693
  var package_default2 = {
9424
9694
  name: "@huynle/hub-node",
9425
- version: "0.10.0",
9695
+ version: "0.11.0",
9426
9696
  private: true,
9427
9697
  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).",
9428
9698
  license: "MIT",
@@ -10877,7 +11147,7 @@ import path14 from "node:path";
10877
11147
  // packages/runtime-openshell/package.json
10878
11148
  var package_default3 = {
10879
11149
  name: "@huynle/hub-runtime-openshell",
10880
- version: "0.10.0",
11150
+ version: "0.11.0",
10881
11151
  private: true,
10882
11152
  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).",
10883
11153
  license: "MIT",
@@ -12532,46 +12802,6 @@ function openShellCheckText(report) {
12532
12802
  `);
12533
12803
  }
12534
12804
 
12535
- // packages/cli/src/subscriptions.ts
12536
- var SUBSCRIBE_FLAGS = { type: "multi", delivery: "string", batch: "string", expires: "string", "max-per-hour": "string", subscriber: "string", note: "string" };
12537
- var DELIVERIES2 = ["queue", "steer", "silent"];
12538
- var safe2 = (v, max = 120) => sanitizeLabel(String(v), max);
12539
- function subscribeInput(addrs, flags) {
12540
- if (!addrs.length)
12541
- throw new UsageError("needs at least one address (a session, @@role, #team or children)");
12542
- const delivery = str(flags, "delivery");
12543
- if (delivery !== undefined && !DELIVERIES2.includes(delivery))
12544
- throw new UsageError(`--delivery is one of ${DELIVERIES2.join(", ")}`);
12545
- const batch = str(flags, "batch");
12546
- const expires = str(flags, "expires");
12547
- const max = str(flags, "max-per-hour");
12548
- if (max !== undefined && !/^\d+$/.test(max))
12549
- throw new UsageError("--max-per-hour takes a whole number (1–120)");
12550
- const types = flags.type;
12551
- const subscriber = str(flags, "subscriber");
12552
- const note = str(flags, "note");
12553
- return {
12554
- to: addrs.length === 1 ? addrs[0] : [...addrs],
12555
- ...types?.length ? { types: types.flatMap((t) => t.split(",")).map((t) => t.trim()).filter(Boolean) } : {},
12556
- ...delivery !== undefined ? { delivery } : {},
12557
- ...batch !== undefined ? { batch: durationArg(batch, "--batch") } : {},
12558
- ...expires !== undefined ? { expires: durationArg(expires, "--expires") } : {},
12559
- ...max !== undefined ? { maxPerHour: Number(max) } : {},
12560
- ...subscriber !== undefined ? { subscriber } : {},
12561
- ...note !== undefined ? { note } : {}
12562
- };
12563
- }
12564
- var targets = (s) => s.targets.map((t) => t.kind === "children" ? "its subagents" : safe2(t.addr, 80)).join(", ");
12565
- var iso4 = (ms) => new Date(ms).toISOString().slice(0, 16) + "Z";
12566
- function subscriptionLine(s) {
12567
- const extra = [safe2(s.delivery, 10), `batch ${Math.round(s.batchMs / 1000)}s`, `≤${s.maxPerHour}/h`, `expires ${iso4(s.expiresAt)}`, `${s.stats.delivered} delivered`, ...s.pending ? [`${s.pending} pending`] : [], ...s.stats.dropped ? [`${s.stats.dropped} dropped`] : [], ...s.stats.suppressed ? [`${s.stats.suppressed} loop-suppressed`] : []];
12568
- return `${safe2(s.id, 40)} ${safe2(s.subscriber, 80)} ← ${targets(s)} [${s.types.map((t) => safe2(t, 40)).join(",")}] ${extra.join(" · ")}${s.note ? ` "${safe2(s.note, 200)}"` : ""}`;
12569
- }
12570
- var subscribedText = (b) => `subscribed ${subscriptionLine(b.subscription)}
12571
- the subscriber gets one hub message per batch window; stop with \`hub unsubscribe ${safe2(b.subscription.id, 40)}\``;
12572
- var subscriptionsText = (b) => b.subscriptions.length ? b.subscriptions.map(subscriptionLine).join(`
12573
- `) : "no subscriptions";
12574
-
12575
12805
  // packages/claude-code/src/main.ts
12576
12806
  import { spawnSync as spawnSync2 } from "node:child_process";
12577
12807
  import { realpathSync as realpathSync5 } from "node:fs";
@@ -14875,7 +15105,7 @@ import path23 from "node:path";
14875
15105
  // packages/claude-code/package.json
14876
15106
  var package_default4 = {
14877
15107
  name: "@huynle/hub-claude-code",
14878
- version: "0.10.0",
15108
+ version: "0.11.0",
14879
15109
  private: true,
14880
15110
  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).",
14881
15111
  license: "MIT",
@@ -15164,7 +15394,7 @@ import path26 from "node:path";
15164
15394
  // packages/preset/package.json
15165
15395
  var package_default5 = {
15166
15396
  name: "@huynle/hub-preset",
15167
- version: "0.10.0",
15397
+ version: "0.11.0",
15168
15398
  private: true,
15169
15399
  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).",
15170
15400
  license: "MIT",
@@ -15689,7 +15919,7 @@ import { openSync as openSync6, closeSync as closeSync6, readFileSync as readFil
15689
15919
  // packages/codex/package.json
15690
15920
  var package_default6 = {
15691
15921
  name: "@huynle/hub-codex",
15692
- version: "0.10.0",
15922
+ version: "0.11.0",
15693
15923
  private: true,
15694
15924
  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).",
15695
15925
  license: "MIT",
@@ -26713,7 +26943,7 @@ ${openedText(body.decision)}` : outcome.text);
26713
26943
  "grant create": {
26714
26944
  flags: GRANT_FLAGS,
26715
26945
  async handle(context) {
26716
- userOnly(context.credentials().source, "create");
26946
+ userOnly2(context.credentials().source, "create");
26717
26947
  const spec = await grantSpecArg(context.parsed.flags, context.io.stdin);
26718
26948
  const body = await grantHub(context, (hub) => hub.createGrant(spec));
26719
26949
  answer(context, body, (b) => `created ${grantText2(b.grant)}`);
@@ -26733,7 +26963,7 @@ ${openedText(body.decision)}` : outcome.text);
26733
26963
  "grant revoke": {
26734
26964
  args: ["<grantID>"],
26735
26965
  async handle(context) {
26736
- userOnly(context.credentials().source, "revoke");
26966
+ userOnly2(context.credentials().source, "revoke");
26737
26967
  const body = await grantHub(context, (hub) => hub.revokeGrant(context.parsed.args[0]));
26738
26968
  answer(context, body, (b) => `revoked ${b.grant.id} (${b.grant.holder})`);
26739
26969
  }