@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.
- package/CHANGELOG.md +78 -0
- package/README.md +20 -4
- package/package.json +1 -1
- package/packages/claude-code/package.json +1 -1
- package/packages/cli/dist/hub-cli.js +625 -395
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/grants-mcp.ts +65 -0
- package/packages/cli/src/mcp-operations.ts +49 -0
- package/packages/cli/src/mcp.ts +6 -0
- package/packages/cli/src/subscriptions-mcp.ts +76 -0
- package/packages/cli/src/teams-mcp.ts +5 -3
- package/packages/codex/package.json +1 -1
- package/packages/node/package.json +1 -1
- package/packages/preset/package.json +1 -1
- package/packages/protocol/package.json +1 -1
- package/packages/protocol/src/index.ts +13 -1
- package/packages/protocol/src/teams.ts +4 -0
- package/packages/protocol/src/voice-recording.ts +39 -2
- package/packages/runtime-openshell/image/entry.sh +3 -1
- package/packages/runtime-openshell/image/start.sh +3 -1
- package/packages/runtime-openshell/package.json +1 -1
- package/packages/runtime-sdk/package.json +1 -1
- package/packages/sdk/package.json +1 -1
- package/packages/sdk/src/client.ts +13 -0
- package/packages/sdk/src/teams.ts +2 -0
- package/packages/spoke-sdk/package.json +1 -1
- package/src/central/agent/decisions.ts +7 -1
- package/src/central/agent/delivery.ts +39 -9
- package/src/central/agent/grant-routes.ts +14 -1
- package/src/central/agent/grants.ts +23 -0
- package/src/central/agent/launch-engine.ts +3 -3
- package/src/central/agent/message-limits.ts +3 -0
- package/src/central/agent/routes.ts +11 -0
- package/src/central/agent/subscriptions.ts +3 -1
- package/src/central/config.ts +9 -2
- package/src/central/http.ts +135 -24
- package/src/central/recording/export.ts +57 -3
- package/src/central/recording/index.ts +234 -16
- package/src/central/recording/ingest.ts +11 -2
- package/src/central/recording/routes.ts +61 -7
- package/src/central/recording/schema.ts +66 -2
- package/src/central/recording/settings.ts +19 -5
- package/src/central/recording/store.ts +19 -0
- package/src/central/recording/taps.ts +45 -1
- package/src/central/speech/openai.ts +4 -4
- package/src/central/speech/openrouter.ts +4 -4
- package/src/central/speech/provider.ts +32 -1
- package/src/central/speech/service.ts +6 -5
- package/src/central/teams/hop-stalls.ts +72 -0
- package/src/central/teams/policy.ts +10 -0
- package/src/central/teams/routes.ts +2 -0
- package/src/central/teams/seats.ts +3 -3
- package/src/central/teams/staffing.ts +5 -0
- package/src/central/teams/store.ts +5 -2
- package/src/central/teams/validation.ts +8 -5
- package/src/spoke/spoke.ts +19 -0
- package/web/dist/assets/{ArtifactPage-Bz992ZX6.js → ArtifactPage-C5MMqXLN.js} +1 -1
- package/web/dist/assets/{ArtifactPane-BJZNixJC.js → ArtifactPane-CXSCmhyJ.js} +1 -1
- package/web/dist/assets/{ArtifactWorkbench-Dti3VP07.js → ArtifactWorkbench-S0RxNGWy.js} +1 -1
- package/web/dist/assets/{Diagram-B4HdUxw0.js → Diagram-BGmyRb-R.js} +1 -1
- package/web/dist/assets/{Library-CpzD2HDW.js → Library-CFmMbXEa.js} +2 -2
- package/web/dist/assets/{ProfilesDestination-DlQeogea.js → ProfilesDestination-7jpOc2HM.js} +1 -1
- package/web/dist/assets/{TeamsPage-4xdWCaI-.js → TeamsPage-C1NGrUR-.js} +1 -1
- package/web/dist/assets/{Watchtower-CpQdIRns.js → Watchtower-DnvlJaU0.js} +2 -2
- package/web/dist/assets/{artifacts-But87Jfo.js → artifacts-DlQEYmDl.js} +1 -1
- package/web/dist/assets/index-Da8SKXMw.js +33 -0
- package/web/dist/assets/{index-BC0O1gfR.css → index-Dyq-jBeE.css} +1 -1
- package/web/dist/assets/live-BmqLASSo.js +5 -0
- package/web/dist/assets/{pane-layout-BW0RdX5a.js → pane-layout-BAuu_pA3.js} +1 -1
- package/web/dist/assets/{renderer2d-IaJiloPi.js → renderer2d-B2Nh39UN.js} +1 -1
- package/web/dist/assets/{renderer3d-ZiD1uKGW.js → renderer3d-CTYbljHH.js} +1 -1
- package/web/dist/assets/{safe-area-C7IAlkgY.js → safe-area-CkhEclGo.js} +1 -1
- package/web/dist/assets/{silero-worker-e802lB5T.js → silero-worker-CC2etKS3.js} +1 -1
- package/web/dist/assets/{source-C-De8L3s.js → source-GYayxUxS.js} +1 -1
- package/web/dist/assets/{toast-CXTwTvDw.js → toast-q818mjUF.js} +1 -1
- package/web/dist/index.html +5 -5
- package/web/dist/assets/index-BS6lA6fN.js +0 -33
- 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
|
+
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/
|
|
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
|
|
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: { ...
|
|
6995
|
-
description:
|
|
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:
|
|
6999
|
-
prefs: { type: "object", properties: { harness:
|
|
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: { ...
|
|
7004
|
-
var EDGE = { type: "object", properties: { from:
|
|
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: { ...
|
|
7007
|
-
description:
|
|
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: { ...
|
|
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:
|
|
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: { ...
|
|
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: { ...
|
|
7027
|
-
base: { ...
|
|
7028
|
-
remote: { ...
|
|
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: { ...
|
|
7037
|
-
labels: { type: "object", additionalProperties:
|
|
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: { ...
|
|
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: { ...
|
|
7259
|
+
name: { ...STRING6, description: "The session's @name, without the @." },
|
|
7058
7260
|
roles: { ...STRINGS, description: "Roles the session claims (up to 10)." },
|
|
7059
|
-
harness: { ...
|
|
7060
|
-
agent: { ...
|
|
7061
|
-
model: { ...
|
|
7062
|
-
prompt: { ...
|
|
7063
|
-
intent: { ...
|
|
7064
|
-
labels: { type: "object", additionalProperties:
|
|
7065
|
-
agentProfile: { type: "object", properties: { profileID:
|
|
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
|
|
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
|
|
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](
|
|
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,
|
|
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:
|
|
7193
|
-
metadata: { type: "object", additionalProperties:
|
|
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:
|
|
7399
|
+
cursor: STRING6
|
|
7197
7400
|
},
|
|
7198
7401
|
readOnly: true,
|
|
7199
7402
|
run: async (hub, args, signal) => {
|
|
7200
|
-
const 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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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:
|
|
7459
|
+
properties: { team: TEAM2, cursor: STRING6 },
|
|
7257
7460
|
required: ["team"],
|
|
7258
7461
|
readOnly: true,
|
|
7259
7462
|
run: async (hub, args, signal) => {
|
|
7260
|
-
const cursor =
|
|
7261
|
-
const page = await hub.teams.versions(
|
|
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(
|
|
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: { ...
|
|
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
|
-
|
|
7299
|
-
|
|
7300
|
-
const key =
|
|
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 =
|
|
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: { ...
|
|
7326
|
-
draftTool("team_role_remove", "Remove a role (not the entry role) and every path touching it from a team's draft.", { role:
|
|
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:
|
|
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 =
|
|
7332
|
-
return teams.setEdge(team, { from:
|
|
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:
|
|
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(
|
|
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(
|
|
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:
|
|
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 =
|
|
7368
|
-
const role =
|
|
7369
|
-
const 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 =
|
|
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: { ...
|
|
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(
|
|
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:
|
|
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 =
|
|
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(
|
|
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
|
|
7417
|
-
var STRINGS2 = { type: "array", items:
|
|
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: { ...
|
|
7422
|
-
var COMMAND3 = { type: "object", properties: { name: { ...
|
|
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: { ...
|
|
7431
|
-
description:
|
|
7633
|
+
title: { ...STRING7, description: "1–120 characters." },
|
|
7634
|
+
description: STRING7,
|
|
7432
7635
|
tags: { ...STRINGS2, description: "Slugs." },
|
|
7433
|
-
metadata: { type: "object", additionalProperties:
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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:
|
|
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 =
|
|
7546
|
-
const page = await hub.profiles.versions(
|
|
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: { ...
|
|
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
|
-
|
|
7562
|
-
|
|
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(
|
|
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 =
|
|
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: { ...
|
|
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:
|
|
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: { ...
|
|
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,
|
|
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: { ...
|
|
7628
|
-
contentTool("profile_command_remove", "Detach a command from a profile.", { name: { ...
|
|
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,
|
|
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: { ...
|
|
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(
|
|
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: { ...
|
|
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 =
|
|
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
|
|
7664
|
-
var list2 = (items) => items.length ? items.map(
|
|
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 ${
|
|
7668
|
-
`auto-active: ${c.autoActive ? `on (within ${
|
|
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]) => `${
|
|
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) => `${
|
|
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}`}: ${
|
|
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
|
|
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) => `"${
|
|
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 ${
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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:
|
|
7928
|
-
name:
|
|
8278
|
+
key: optional8(args, "key"),
|
|
8279
|
+
name: optional8(args, "name")?.replace(/^@+/, ""),
|
|
7929
8280
|
roles: optionalStrings(args, "roles")?.map((r) => r.replace(/^@@/, "")),
|
|
7930
|
-
harness:
|
|
7931
|
-
agent:
|
|
7932
|
-
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:
|
|
8289
|
+
prompt: optional8(args, "prompt"),
|
|
7939
8290
|
labels: optionalLabels(args, "labels"),
|
|
7940
|
-
end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout:
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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 =
|
|
8063
|
-
const 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 = { ...
|
|
8074
|
-
var 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: { ...
|
|
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 =
|
|
8088
|
-
const r = await hub.name(
|
|
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 =
|
|
8102
|
-
const project =
|
|
8103
|
-
const r = await hub.claimRole(
|
|
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 =
|
|
8116
|
-
const project =
|
|
8117
|
-
const r = await hub.releaseRole(
|
|
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: { ...
|
|
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:
|
|
8130
|
-
sessions: { type: "array", items:
|
|
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:
|
|
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:
|
|
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 =
|
|
8156
|
-
const r = await hub.webhookCreate({ url:
|
|
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:
|
|
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(
|
|
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:
|
|
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 =
|
|
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 =
|
|
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:
|
|
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),
|
|
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:
|
|
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),
|
|
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:
|
|
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 =
|
|
8280
|
-
const u = await hub.profiles.usage(
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
8724
|
+
const description = optional8(args, "description");
|
|
8374
8725
|
const tags = optionalStrings(args, "tags");
|
|
8375
|
-
const { workspace } = await hub.workspaces.create({ title:
|
|
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:
|
|
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:
|
|
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(
|
|
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 =
|
|
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:
|
|
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 =
|
|
8418
|
-
const r = await hub.workspaces.bind(
|
|
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:
|
|
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(
|
|
8430
|
-
return `unbound ${
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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 =
|
|
8469
|
-
const { assignment } = await hub.workspaces.assign(
|
|
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:
|
|
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 =
|
|
8831
|
+
const memberID = optional8(args, "memberID");
|
|
8481
8832
|
const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
|
|
8482
|
-
const r = await hub.workspaces.assignments(
|
|
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:
|
|
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 =
|
|
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 =
|
|
8499
|
-
const 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:
|
|
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 :
|
|
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(
|
|
8516
|
-
return `${r.change}: ${
|
|
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(
|
|
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:
|
|
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 =
|
|
8546
|
-
const 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(
|
|
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(
|
|
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: { ...
|
|
8619
|
-
state: { ...
|
|
8620
|
-
machine: { ...
|
|
8621
|
-
label: { type: "array", items:
|
|
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:
|
|
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(
|
|
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 =
|
|
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: { ...
|
|
8654
|
-
role: { ...
|
|
8655
|
-
team: { ...
|
|
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:
|
|
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: { ...
|
|
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 =
|
|
8671
|
-
const 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: { ...
|
|
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 =
|
|
8693
|
-
const 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: { ...
|
|
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 =
|
|
8716
|
-
const 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: { ...
|
|
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 =
|
|
8729
|
-
return declinedText(await hub.decline(id,
|
|
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 =
|
|
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](
|
|
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 =
|
|
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(
|
|
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 =
|
|
8795
|
-
const 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 =
|
|
8808
|
-
const 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 =
|
|
8826
|
-
const 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: { ...
|
|
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 =
|
|
8845
|
-
const 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(
|
|
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: { ...
|
|
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: { ...
|
|
8865
|
-
base: { ...
|
|
8866
|
-
remote: { ...
|
|
8867
|
-
key: { ...
|
|
8868
|
-
name: { ...
|
|
8869
|
-
roles: { type: "array", items:
|
|
8870
|
-
harness: { ...
|
|
8871
|
-
agent: { ...
|
|
8872
|
-
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: { ...
|
|
8875
|
-
machine: { ...
|
|
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: { ...
|
|
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: { ...
|
|
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: { ...
|
|
8898
|
-
label: { type: "array", items:
|
|
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 =
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|