@huynle/hub 0.10.1 → 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 +54 -0
- package/README.md +17 -3
- package/package.json +1 -1
- package/packages/claude-code/package.json +1 -1
- package/packages/cli/dist/hub-cli.js +622 -394
- 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/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 +102 -20
- 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/web/dist/assets/{ArtifactPage-CsPjam8D.js → ArtifactPage-C5MMqXLN.js} +1 -1
- package/web/dist/assets/{ArtifactPane-B9weO8L6.js → ArtifactPane-CXSCmhyJ.js} +1 -1
- package/web/dist/assets/{ArtifactWorkbench-DzKhMs3l.js → ArtifactWorkbench-S0RxNGWy.js} +1 -1
- package/web/dist/assets/{Diagram-CuRIffka.js → Diagram-BGmyRb-R.js} +1 -1
- package/web/dist/assets/{Library-rv5DGtrm.js → Library-CFmMbXEa.js} +2 -2
- package/web/dist/assets/{ProfilesDestination-DUuQ9jmd.js → ProfilesDestination-7jpOc2HM.js} +1 -1
- package/web/dist/assets/{TeamsPage-D_17MkvN.js → TeamsPage-C1NGrUR-.js} +1 -1
- package/web/dist/assets/{Watchtower-DlrNOJ7X.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-rW6LKQj1.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-DyANQ1WS.js → renderer2d-B2Nh39UN.js} +1 -1
- package/web/dist/assets/{renderer3d-vYYKFAJq.js → renderer3d-CTYbljHH.js} +1 -1
- package/web/dist/assets/{safe-area-B2Bk__FK.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-bX8B6NLo.js +0 -33
- package/web/dist/assets/live-BGLA-peO.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;
|
|
@@ -6984,35 +7006,213 @@ ${intakeText(intake)}`;
|
|
|
6984
7006
|
}
|
|
6985
7007
|
];
|
|
6986
7008
|
|
|
6987
|
-
// 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
|
|
6988
7050
|
var STRING4 = { type: "string" };
|
|
6989
|
-
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 };
|
|
6990
7189
|
var TEAM2 = { type: "string", description: "The team: its ID (team_…) or its name." };
|
|
6991
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." };
|
|
6992
7191
|
var IDEMPOTENCY = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
|
|
6993
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)." };
|
|
6994
7193
|
var DIRECTION = { type: "string", enum: ["one-way", "two-way"], description: "two-way: `to` may also contact `from` with the same kinds." };
|
|
6995
7194
|
var ROLE_FIELDS = {
|
|
6996
|
-
label: { ...
|
|
6997
|
-
description:
|
|
7195
|
+
label: { ...STRING6, description: "A display label (confers no authority)." },
|
|
7196
|
+
description: STRING6,
|
|
6998
7197
|
externalContact: { type: "boolean", description: "Holders may contact sessions outside all their teams (default false)." },
|
|
6999
7198
|
responsibilities: { ...STRINGS, description: "Short lines (at most 16)." },
|
|
7000
|
-
profile: { type: "object", properties: { profileID:
|
|
7001
|
-
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)." },
|
|
7002
7201
|
minMembers: { type: "integer", minimum: 0, maximum: 16 },
|
|
7003
7202
|
maxMembers: { type: "integer", minimum: 1, maximum: 16 }
|
|
7004
7203
|
};
|
|
7005
|
-
var ROLE = { type: "object", properties: { name: { ...
|
|
7006
|
-
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"] };
|
|
7007
7206
|
var SPEC_FIELDS = {
|
|
7008
|
-
title: { ...
|
|
7009
|
-
description:
|
|
7207
|
+
title: { ...STRING6, description: "1–120 characters." },
|
|
7208
|
+
description: STRING6,
|
|
7010
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." },
|
|
7011
7211
|
roles: { type: "array", items: ROLE, description: "1–32 roles." },
|
|
7012
|
-
entryRole: { ...
|
|
7212
|
+
entryRole: { ...STRING6, description: "The role that receives submitted work (e.g. pm) and supervises members." },
|
|
7013
7213
|
edges: { type: "array", items: EDGE, description: "Communication paths between roles (restricted mode)." },
|
|
7014
7214
|
tags: { ...STRINGS, description: "Slugs." },
|
|
7015
|
-
metadata: { type: "object", additionalProperties:
|
|
7215
|
+
metadata: { type: "object", additionalProperties: STRING6, description: "String key/value pairs (keys are slugs)." },
|
|
7016
7216
|
projects: { ...STRINGS, description: "Project names the team works on." }
|
|
7017
7217
|
};
|
|
7018
7218
|
var LAUNCH = {
|
|
@@ -7023,11 +7223,11 @@ var LAUNCH = {
|
|
|
7023
7223
|
type: "object",
|
|
7024
7224
|
description: "Where the member works on its machine.",
|
|
7025
7225
|
properties: {
|
|
7026
|
-
repo: { ...
|
|
7226
|
+
repo: { ...STRING6, description: "The repository's absolute path on the machine." },
|
|
7027
7227
|
mode: { type: "string", enum: ["worktree", "checkout", "clone"], description: "worktree (default; needs branch), checkout, or clone (needs remote)." },
|
|
7028
|
-
branch: { ...
|
|
7029
|
-
base: { ...
|
|
7030
|
-
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)." }
|
|
7031
7231
|
},
|
|
7032
7232
|
required: ["repo"]
|
|
7033
7233
|
},
|
|
@@ -7035,8 +7235,8 @@ var LAUNCH = {
|
|
|
7035
7235
|
type: "object",
|
|
7036
7236
|
description: "Which machine runs it.",
|
|
7037
7237
|
properties: {
|
|
7038
|
-
machine: { ...
|
|
7039
|
-
labels: { type: "object", additionalProperties:
|
|
7238
|
+
machine: { ...STRING6, description: "A machine ID or name." },
|
|
7239
|
+
labels: { type: "object", additionalProperties: STRING6, description: "Node labels to match." },
|
|
7040
7240
|
mode: { type: "string", enum: ["require", "prefer"], description: "require (default) or prefer the machine/labels." }
|
|
7041
7241
|
}
|
|
7042
7242
|
},
|
|
@@ -7044,7 +7244,7 @@ var LAUNCH = {
|
|
|
7044
7244
|
type: "object",
|
|
7045
7245
|
properties: {
|
|
7046
7246
|
kind: { type: "string", enum: ["host", "openshell"], description: "host (default) or openshell (clone workspaces only)." },
|
|
7047
|
-
profile: { ...
|
|
7247
|
+
profile: { ...STRING6, description: "An openshell sandbox profile (openshell only)." }
|
|
7048
7248
|
}
|
|
7049
7249
|
},
|
|
7050
7250
|
end: {
|
|
@@ -7056,27 +7256,27 @@ var LAUNCH = {
|
|
|
7056
7256
|
cleanup: { type: "string", enum: ["keep", "remove"], description: "keep (default) or remove the workspace at the end." }
|
|
7057
7257
|
}
|
|
7058
7258
|
},
|
|
7059
|
-
name: { ...
|
|
7259
|
+
name: { ...STRING6, description: "The session's @name, without the @." },
|
|
7060
7260
|
roles: { ...STRINGS, description: "Roles the session claims (up to 10)." },
|
|
7061
|
-
harness: { ...
|
|
7062
|
-
agent: { ...
|
|
7063
|
-
model: { ...
|
|
7064
|
-
prompt: { ...
|
|
7065
|
-
intent: { ...
|
|
7066
|
-
labels: { type: "object", additionalProperties:
|
|
7067
|
-
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)." }
|
|
7068
7268
|
},
|
|
7069
7269
|
required: ["workspace"]
|
|
7070
7270
|
};
|
|
7071
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.";
|
|
7072
7272
|
var ArgError = ToolArgError;
|
|
7073
|
-
var
|
|
7273
|
+
var need6 = (args, key) => {
|
|
7074
7274
|
const value = args[key];
|
|
7075
7275
|
if (typeof value !== "string" || !value.trim())
|
|
7076
7276
|
throw new ArgError(`"${key}" is required (a non-empty string)`);
|
|
7077
7277
|
return value;
|
|
7078
7278
|
};
|
|
7079
|
-
var
|
|
7279
|
+
var optional6 = (args, key) => {
|
|
7080
7280
|
const value = args[key];
|
|
7081
7281
|
if (value === undefined || value === null)
|
|
7082
7282
|
return;
|
|
@@ -7131,6 +7331,7 @@ function specLines(spec) {
|
|
|
7131
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}` : ""}` : ""}`;
|
|
7132
7332
|
return [
|
|
7133
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}`] : [],
|
|
7134
7335
|
`entry role: ${spec.entryRole}`,
|
|
7135
7336
|
"roles:",
|
|
7136
7337
|
...spec.roles.map(role),
|
|
@@ -7149,7 +7350,7 @@ function rosterLines(roster) {
|
|
|
7149
7350
|
const flags = [r.vacant ? "vacant" : "", r.understaffed ? `understaffed (min ${r.minMembers})` : "", r.launching ? `${r.launching} launching` : ""].filter(Boolean);
|
|
7150
7351
|
lines.push(` ${r.role}${r.entry ? " (entry)" : ""} ${r.seats.length}/${r.maxMembers} ${r.online} online${flags.length ? ` ${flags.join(", ")}` : ""}`);
|
|
7151
7352
|
for (const s of r.seats)
|
|
7152
|
-
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)"}` : ""}`);
|
|
7153
7354
|
}
|
|
7154
7355
|
const failed = roster.requests.filter((q) => q.state !== "staffed").slice(-5);
|
|
7155
7356
|
for (const q of failed)
|
|
@@ -7166,7 +7367,7 @@ var statusTool = (verb, description, organizes) => ({
|
|
|
7166
7367
|
idempotent: true,
|
|
7167
7368
|
run: async (hub, args, signal) => {
|
|
7168
7369
|
const expectedRevision = revision(args);
|
|
7169
|
-
const { team } = await hub.teams[verb](
|
|
7370
|
+
const { team } = await hub.teams[verb](need6(args, "team"), expectedRevision, { signal });
|
|
7170
7371
|
return `${team.revision === expectedRevision ? "unchanged" : { pause: "paused", resume: "resumed", archive: "archived", restore: "restored" }[verb]}: ${teamLine(team)}`;
|
|
7171
7372
|
}
|
|
7172
7373
|
});
|
|
@@ -7180,7 +7381,7 @@ var draftTool = (name, description, properties, required, edit) => ({
|
|
|
7180
7381
|
idempotent: true,
|
|
7181
7382
|
run: async (hub, args, signal) => {
|
|
7182
7383
|
const expectedRevision = revision(args);
|
|
7183
|
-
const { team, draft } = await edit(hub.teams,
|
|
7384
|
+
const { team, draft } = await edit(hub.teams, need6(args, "team"), args, expectedRevision, signal);
|
|
7184
7385
|
return draftText("draft saved", team, draft);
|
|
7185
7386
|
}
|
|
7186
7387
|
});
|
|
@@ -7191,22 +7392,22 @@ var teamTools = [
|
|
|
7191
7392
|
properties: {
|
|
7192
7393
|
state: { type: "string", enum: ["active", "archived", "all"] },
|
|
7193
7394
|
tags: STRINGS,
|
|
7194
|
-
project:
|
|
7195
|
-
metadata: { type: "object", additionalProperties:
|
|
7395
|
+
project: STRING6,
|
|
7396
|
+
metadata: { type: "object", additionalProperties: STRING6 },
|
|
7196
7397
|
paused: { type: "boolean" },
|
|
7197
7398
|
limit: { type: "integer", minimum: 1, maximum: 200 },
|
|
7198
|
-
cursor:
|
|
7399
|
+
cursor: STRING6
|
|
7199
7400
|
},
|
|
7200
7401
|
readOnly: true,
|
|
7201
7402
|
run: async (hub, args, signal) => {
|
|
7202
|
-
const state =
|
|
7403
|
+
const state = optional6(args, "state");
|
|
7203
7404
|
if (state !== undefined && !["active", "archived", "all"].includes(state))
|
|
7204
7405
|
throw new ArgError(`"state" is one of active, archived, all`);
|
|
7205
7406
|
const tags = strings3(args, "tags");
|
|
7206
|
-
const project =
|
|
7407
|
+
const project = optional6(args, "project");
|
|
7207
7408
|
const metadata = record(args, "metadata");
|
|
7208
7409
|
const limit = int3(args, "limit", 1, 200);
|
|
7209
|
-
const cursor =
|
|
7410
|
+
const cursor = optional6(args, "cursor");
|
|
7210
7411
|
if (args.paused !== undefined && typeof args.paused !== "boolean")
|
|
7211
7412
|
throw new ArgError(`"paused" must be true or false`);
|
|
7212
7413
|
const page = await hub.teams.list({
|
|
@@ -7232,7 +7433,7 @@ var teamTools = [
|
|
|
7232
7433
|
required: ["team"],
|
|
7233
7434
|
readOnly: true,
|
|
7234
7435
|
run: async (hub, args, signal) => {
|
|
7235
|
-
const { team, draft } = await hub.teams.get(
|
|
7436
|
+
const { team, draft } = await hub.teams.get(need6(args, "team"), { signal });
|
|
7236
7437
|
return [teamLine(team), ...specLines(team), ...draft ? [`draft pending (on v${draft.baseVersion}, saved ${when(draft.savedAt)}): see team_diff`] : []].join(`
|
|
7237
7438
|
`);
|
|
7238
7439
|
}
|
|
@@ -7244,7 +7445,7 @@ var teamTools = [
|
|
|
7244
7445
|
required: ["team"],
|
|
7245
7446
|
readOnly: true,
|
|
7246
7447
|
run: async (hub, args, signal) => {
|
|
7247
|
-
const { team, roster, intake } = await hub.teams.status(
|
|
7448
|
+
const { team, roster, intake } = await hub.teams.status(need6(args, "team"), { signal });
|
|
7248
7449
|
const lines = [teamLine(team), ...rosterLines(roster)];
|
|
7249
7450
|
if (intake)
|
|
7250
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)}` : ""}`);
|
|
@@ -7255,12 +7456,12 @@ var teamTools = [
|
|
|
7255
7456
|
{
|
|
7256
7457
|
name: "team_history",
|
|
7257
7458
|
description: `A team's retained published versions, newest first (version, digest, when). ${SCOPES}`,
|
|
7258
|
-
properties: { team: TEAM2, cursor:
|
|
7459
|
+
properties: { team: TEAM2, cursor: STRING6 },
|
|
7259
7460
|
required: ["team"],
|
|
7260
7461
|
readOnly: true,
|
|
7261
7462
|
run: async (hub, args, signal) => {
|
|
7262
|
-
const cursor =
|
|
7263
|
-
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 } : {} });
|
|
7264
7465
|
return [...page.items.map((v) => `v${v.version} ${v.digest.slice(0, 12)} ${when(v.publishedAt)}`), ...page.cursor ? [`more: cursor ${page.cursor}`] : []].join(`
|
|
7265
7466
|
`);
|
|
7266
7467
|
}
|
|
@@ -7274,7 +7475,7 @@ var teamTools = [
|
|
|
7274
7475
|
run: async (hub, args, signal) => {
|
|
7275
7476
|
const from = int3(args, "from", 1, 1e9);
|
|
7276
7477
|
const to = args.to === undefined || args.to === null || args.to === "draft" ? "draft" : int3(args, "to", 1, 1e9);
|
|
7277
|
-
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 });
|
|
7278
7479
|
const part = (label, d) => ["added", "removed", "changed"].flatMap((k) => d[k]?.length ? [`${label} ${k}: ${d[k].join(", ")}`] : []);
|
|
7279
7480
|
const lines = [
|
|
7280
7481
|
...diff.fields.length ? [`fields changed: ${diff.fields.join(", ")}`] : [],
|
|
@@ -7291,16 +7492,16 @@ var teamTools = [
|
|
|
7291
7492
|
{
|
|
7292
7493
|
name: "team_create",
|
|
7293
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}`,
|
|
7294
|
-
properties: { name: { ...
|
|
7495
|
+
properties: { name: { ...STRING6, description: "A slug (2–40 lowercase letters, digits, dashes), unique per user." }, ...SPEC_FIELDS, idempotencyKey: IDEMPOTENCY },
|
|
7295
7496
|
required: ["name", "title", "mode", "roles", "entryRole"],
|
|
7296
7497
|
readOnly: false,
|
|
7297
7498
|
organizes: true,
|
|
7298
7499
|
idempotent: true,
|
|
7299
7500
|
run: async (hub, args, signal) => {
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
const key =
|
|
7303
|
-
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"]);
|
|
7304
7505
|
const { team, created } = await hub.teams.create({ ...spec, edges: spec.edges ?? [] }, { signal, ...key ? { idempotencyKey: key } : {} });
|
|
7305
7506
|
return [`${created ? "created" : "already created (same key)"}: ${teamLine(team)}`, ...specLines(team)].join(`
|
|
7306
7507
|
`);
|
|
@@ -7316,24 +7517,24 @@ var teamTools = [
|
|
|
7316
7517
|
idempotent: true,
|
|
7317
7518
|
run: async (hub, args, signal) => {
|
|
7318
7519
|
const expectedRevision = revision(args);
|
|
7319
|
-
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"]);
|
|
7320
7521
|
if (Object.keys(patch).length === 0)
|
|
7321
7522
|
throw new ArgError("give at least one field to change");
|
|
7322
|
-
const id =
|
|
7523
|
+
const id = need6(args, "team");
|
|
7323
7524
|
const { team } = await hub.teams.update(id, patch, expectedRevision, { signal });
|
|
7324
7525
|
return draftText("draft saved", team, (await hub.teams.draft(team.id, { signal })).draft);
|
|
7325
7526
|
}
|
|
7326
7527
|
},
|
|
7327
|
-
draftTool("team_role_set", "Add a role to a team's draft, or replace the role of that name (all its fields).", { role: { ...
|
|
7328
|
-
draftTool("team_role_remove", "Remove a role (not the entry role) and every path touching it from a team's draft.", { role:
|
|
7329
|
-
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) => {
|
|
7330
7531
|
const kinds = strings3(args, "kinds");
|
|
7331
7532
|
if (!kinds?.length)
|
|
7332
7533
|
throw new ArgError(`"kinds" is a list of msg and/or ask`);
|
|
7333
|
-
const direction =
|
|
7334
|
-
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 });
|
|
7335
7536
|
}),
|
|
7336
|
-
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 })),
|
|
7337
7538
|
{
|
|
7338
7539
|
name: "team_discard_draft",
|
|
7339
7540
|
description: `Drop a team's pending draft (unpublished edits are lost; the published version stays). ${SCOPES}`,
|
|
@@ -7341,7 +7542,7 @@ var teamTools = [
|
|
|
7341
7542
|
required: ["team", "expectedRevision"],
|
|
7342
7543
|
readOnly: false,
|
|
7343
7544
|
idempotent: true,
|
|
7344
|
-
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)}`
|
|
7345
7546
|
},
|
|
7346
7547
|
{
|
|
7347
7548
|
name: "team_publish",
|
|
@@ -7351,7 +7552,7 @@ var teamTools = [
|
|
|
7351
7552
|
readOnly: false,
|
|
7352
7553
|
idempotent: true,
|
|
7353
7554
|
run: async (hub, args, signal) => {
|
|
7354
|
-
const { team, published } = await hub.teams.publish(
|
|
7555
|
+
const { team, published } = await hub.teams.publish(need6(args, "team"), revision(args), { signal });
|
|
7355
7556
|
return `${published ? `published version ${team.version}` : "the draft equalled the published version; dropped"}: ${teamLine(team)}`;
|
|
7356
7557
|
}
|
|
7357
7558
|
},
|
|
@@ -7362,17 +7563,17 @@ var teamTools = [
|
|
|
7362
7563
|
{
|
|
7363
7564
|
name: "team_staff",
|
|
7364
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}`,
|
|
7365
|
-
properties: { team: TEAM2, role:
|
|
7566
|
+
properties: { team: TEAM2, role: STRING6, sessionID: { ...STRING6, description: "An existing session (ses_…)." }, launch: LAUNCH, idempotencyKey: IDEMPOTENCY },
|
|
7366
7567
|
required: ["team", "role"],
|
|
7367
7568
|
readOnly: false,
|
|
7368
7569
|
run: async (hub, args, signal) => {
|
|
7369
|
-
const team =
|
|
7370
|
-
const role =
|
|
7371
|
-
const sessionID =
|
|
7570
|
+
const team = need6(args, "team");
|
|
7571
|
+
const role = need6(args, "role");
|
|
7572
|
+
const sessionID = optional6(args, "sessionID");
|
|
7372
7573
|
const launch = launchOf(args);
|
|
7373
7574
|
if (sessionID === undefined === (launch === undefined))
|
|
7374
7575
|
throw new ArgError(`give exactly one of sessionID or launch`);
|
|
7375
|
-
const key =
|
|
7576
|
+
const key = optional6(args, "idempotencyKey");
|
|
7376
7577
|
const options = { signal, ...key ? { idempotencyKey: key } : {} };
|
|
7377
7578
|
if (sessionID !== undefined) {
|
|
7378
7579
|
const r = await hub.teams.staff(team, { role, sessionID }, options);
|
|
@@ -7385,29 +7586,29 @@ var teamTools = [
|
|
|
7385
7586
|
{
|
|
7386
7587
|
name: "team_unstaff",
|
|
7387
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}`,
|
|
7388
|
-
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)." } },
|
|
7389
7590
|
required: ["team", "staffingID", "expectedRevision"],
|
|
7390
7591
|
readOnly: false,
|
|
7391
7592
|
idempotent: true,
|
|
7392
7593
|
run: async (hub, args, signal) => {
|
|
7393
|
-
const { staffing } = await hub.teams.unstaff(
|
|
7594
|
+
const { staffing } = await hub.teams.unstaff(need6(args, "team"), need6(args, "staffingID"), revision(args), { signal });
|
|
7394
7595
|
return `unstaffed ${staffing.id} (${staffing.sessionID} as ${staffing.role})`;
|
|
7395
7596
|
}
|
|
7396
7597
|
},
|
|
7397
7598
|
{
|
|
7398
7599
|
name: "team_replace",
|
|
7399
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}`,
|
|
7400
|
-
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)." } },
|
|
7401
7602
|
required: ["team", "staffingID", "expectedRevision"],
|
|
7402
7603
|
readOnly: false,
|
|
7403
7604
|
run: async (hub, args, signal) => {
|
|
7404
7605
|
const expectedRevision = revision(args);
|
|
7405
|
-
const sessionID =
|
|
7606
|
+
const sessionID = optional6(args, "sessionID");
|
|
7406
7607
|
const launch = launchOf(args);
|
|
7407
7608
|
if (sessionID === undefined === (launch === undefined))
|
|
7408
7609
|
throw new ArgError(`give exactly one of sessionID or launch`);
|
|
7409
7610
|
const input = sessionID !== undefined ? { expectedRevision, sessionID } : { expectedRevision, launch };
|
|
7410
|
-
const r = await hub.teams.replace(
|
|
7611
|
+
const r = await hub.teams.replace(need6(args, "team"), need6(args, "staffingID"), input, { signal });
|
|
7411
7612
|
const next = r.staffing ? `${r.staffing.sessionID} (${r.staffing.id}, generation ${r.staffing.generation})` : `launch ${r.launch?.id ?? "?"} (request ${r.request?.id ?? "?"})`;
|
|
7412
7613
|
return `replaced ${r.ended.sessionID} in ${r.ended.role} by ${next}`;
|
|
7413
7614
|
}
|
|
@@ -7415,13 +7616,13 @@ var teamTools = [
|
|
|
7415
7616
|
];
|
|
7416
7617
|
|
|
7417
7618
|
// packages/cli/src/profiles-mcp.ts
|
|
7418
|
-
var
|
|
7419
|
-
var STRINGS2 = { type: "array", items:
|
|
7619
|
+
var STRING7 = { type: "string" };
|
|
7620
|
+
var STRINGS2 = { type: "array", items: STRING7 };
|
|
7420
7621
|
var ID4 = { type: "string", description: "The profile's ID (prof_…; see profiles)." };
|
|
7421
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." };
|
|
7422
7623
|
var IDEMPOTENCY2 = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
|
|
7423
|
-
var SKILL = { type: "object", properties: { name: { ...
|
|
7424
|
-
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"] };
|
|
7425
7626
|
var CONTENT_FIELDS = {
|
|
7426
7627
|
instructions: { type: ["string", "null"], description: "The profile's instructions (AGENTS.md-like text)." },
|
|
7427
7628
|
model: { type: ["string", "null"], description: "provider/model the profile suggests." },
|
|
@@ -7429,21 +7630,21 @@ var CONTENT_FIELDS = {
|
|
|
7429
7630
|
commands: { type: "array", items: COMMAND3, description: "Commands, wholesale." }
|
|
7430
7631
|
};
|
|
7431
7632
|
var META_FIELDS = {
|
|
7432
|
-
title: { ...
|
|
7433
|
-
description:
|
|
7633
|
+
title: { ...STRING7, description: "1–120 characters." },
|
|
7634
|
+
description: STRING7,
|
|
7434
7635
|
tags: { ...STRINGS2, description: "Slugs." },
|
|
7435
|
-
metadata: { type: "object", additionalProperties:
|
|
7636
|
+
metadata: { type: "object", additionalProperties: STRING7, description: "String key/value pairs (≤ 32 slug keys)." },
|
|
7436
7637
|
projects: { ...STRINGS2, description: "Project names the profile is meant for (≤ 16)." }
|
|
7437
7638
|
};
|
|
7438
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.";
|
|
7439
7640
|
var ArgError2 = ToolArgError;
|
|
7440
|
-
var
|
|
7641
|
+
var need7 = (args, key) => {
|
|
7441
7642
|
const value = args[key];
|
|
7442
7643
|
if (typeof value !== "string" || !value.trim())
|
|
7443
7644
|
throw new ArgError2(`"${key}" is required (a non-empty string)`);
|
|
7444
7645
|
return value;
|
|
7445
7646
|
};
|
|
7446
|
-
var
|
|
7647
|
+
var optional7 = (args, key) => {
|
|
7447
7648
|
const value = args[key];
|
|
7448
7649
|
if (value === undefined || value === null)
|
|
7449
7650
|
return;
|
|
@@ -7459,7 +7660,7 @@ var whole2 = (args, key, min, what) => {
|
|
|
7459
7660
|
};
|
|
7460
7661
|
var revision2 = (args) => whole2(args, "expectedRevision", 0, "the revision you read (profile_show; 0 for a session with no pin)");
|
|
7461
7662
|
var idempotency = (args) => {
|
|
7462
|
-
const key =
|
|
7663
|
+
const key = optional7(args, "idempotencyKey");
|
|
7463
7664
|
return key ? { idempotencyKey: key } : {};
|
|
7464
7665
|
};
|
|
7465
7666
|
var picked2 = (args, keys) => Object.fromEntries(keys.filter((k) => args[k] !== undefined).map((k) => [k, args[k]]));
|
|
@@ -7492,7 +7693,7 @@ var contentTool = (name, description, properties, required, change) => ({
|
|
|
7492
7693
|
idempotent: true,
|
|
7493
7694
|
run: async (hub, args, signal) => {
|
|
7494
7695
|
const expectedRevision = revision2(args);
|
|
7495
|
-
const id =
|
|
7696
|
+
const id = need7(args, "id");
|
|
7496
7697
|
const { latest, content } = await latestContent(hub.profiles, id, signal);
|
|
7497
7698
|
return versionText(await hub.profiles.createVersion(id, { expectedRevision, content: change(content, args, latest) }, { signal }));
|
|
7498
7699
|
}
|
|
@@ -7512,7 +7713,7 @@ var stateTool = (verb, description) => ({
|
|
|
7512
7713
|
idempotent: true,
|
|
7513
7714
|
run: async (hub, args, signal) => {
|
|
7514
7715
|
const expectedRevision = revision2(args);
|
|
7515
|
-
const { profile } = await hub.profiles.update(
|
|
7716
|
+
const { profile } = await hub.profiles.update(need7(args, "id"), { expectedRevision, state: verb === "archive" ? "archived" : "active" }, { signal });
|
|
7516
7717
|
return `${verb === "archive" ? "archived" : "restored"}: ${profileLine(profile)}`;
|
|
7517
7718
|
}
|
|
7518
7719
|
});
|
|
@@ -7524,7 +7725,7 @@ var profileLifecycleTools = [
|
|
|
7524
7725
|
required: ["id"],
|
|
7525
7726
|
readOnly: true,
|
|
7526
7727
|
run: async (hub, args, signal) => {
|
|
7527
|
-
const { profile, latest } = await hub.profiles.get(
|
|
7728
|
+
const { profile, latest } = await hub.profiles.get(need7(args, "id"), { signal });
|
|
7528
7729
|
return [
|
|
7529
7730
|
profileLine(profile),
|
|
7530
7731
|
...profile.description ? [`description: ${one2(profile.description)}`] : [],
|
|
@@ -7539,13 +7740,13 @@ var profileLifecycleTools = [
|
|
|
7539
7740
|
{
|
|
7540
7741
|
name: "profile_versions",
|
|
7541
7742
|
description: `A profile's retained versions, newest first (version, digest, bytes, when, skill and command counts). ${SCOPES2}`,
|
|
7542
|
-
properties: { id: ID4, limit: { type: "integer", minimum: 1, maximum: 200 }, cursor:
|
|
7743
|
+
properties: { id: ID4, limit: { type: "integer", minimum: 1, maximum: 200 }, cursor: STRING7 },
|
|
7543
7744
|
required: ["id"],
|
|
7544
7745
|
readOnly: true,
|
|
7545
7746
|
run: async (hub, args, signal) => {
|
|
7546
7747
|
const limit = args.limit === undefined ? undefined : whole2(args, "limit", 1, "a whole number from 1 to 200");
|
|
7547
|
-
const cursor =
|
|
7548
|
-
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 } : {} });
|
|
7549
7750
|
const counts = (v) => v.skills !== undefined ? ` ${v.skills} skills, ${v.commands ?? 0} commands${v.instructions ? ", instructions" : ""}` : "";
|
|
7550
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(`
|
|
7551
7752
|
`) || "no versions";
|
|
@@ -7554,14 +7755,14 @@ var profileLifecycleTools = [
|
|
|
7554
7755
|
{
|
|
7555
7756
|
name: "profile_create",
|
|
7556
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}`,
|
|
7557
|
-
properties: { name: { ...
|
|
7758
|
+
properties: { name: { ...STRING7, description: "A slug, unique per user." }, ...META_FIELDS, ...CONTENT_FIELDS, idempotencyKey: IDEMPOTENCY2 },
|
|
7558
7759
|
required: ["name", "title"],
|
|
7559
7760
|
readOnly: false,
|
|
7560
7761
|
organizes: true,
|
|
7561
7762
|
idempotent: true,
|
|
7562
7763
|
run: async (hub, args, signal) => {
|
|
7563
|
-
|
|
7564
|
-
|
|
7764
|
+
need7(args, "name");
|
|
7765
|
+
need7(args, "title");
|
|
7565
7766
|
const meta = picked2(args, ["name", "title", "description", "tags", "metadata", "projects"]);
|
|
7566
7767
|
const text = Object.entries(picked2(args, ["instructions", "model"])).filter(([, v]) => v !== null);
|
|
7567
7768
|
const content = { ...Object.fromEntries(text), skills: args.skills ?? [], commands: args.commands ?? [] };
|
|
@@ -7583,7 +7784,7 @@ var profileLifecycleTools = [
|
|
|
7583
7784
|
const patch = picked2(args, ["title", "description", "tags", "metadata", "projects"]);
|
|
7584
7785
|
if (Object.keys(patch).length === 0)
|
|
7585
7786
|
throw new ArgError2("give at least one field to change");
|
|
7586
|
-
const { profile } = await hub.profiles.update(
|
|
7787
|
+
const { profile } = await hub.profiles.update(need7(args, "id"), { expectedRevision, ...patch }, { signal });
|
|
7587
7788
|
return `updated: ${profileLine(profile)}`;
|
|
7588
7789
|
}
|
|
7589
7790
|
},
|
|
@@ -7597,7 +7798,7 @@ var profileLifecycleTools = [
|
|
|
7597
7798
|
idempotent: true,
|
|
7598
7799
|
run: async (hub, args, signal) => {
|
|
7599
7800
|
const expectedRevision = revision2(args);
|
|
7600
|
-
const id =
|
|
7801
|
+
const id = need7(args, "id");
|
|
7601
7802
|
const given = Object.keys(picked2(args, Object.keys(CONTENT_FIELDS)));
|
|
7602
7803
|
const options = { signal, ...idempotency(args) };
|
|
7603
7804
|
if (args.fromVersion !== undefined) {
|
|
@@ -7618,77 +7819,225 @@ var profileLifecycleTools = [
|
|
|
7618
7819
|
return versionText(await hub.profiles.createVersion(id, { expectedRevision, content: next }, options));
|
|
7619
7820
|
}
|
|
7620
7821
|
},
|
|
7621
|
-
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) => ({
|
|
7622
7823
|
...content,
|
|
7623
|
-
skills: setNamed(content.skills, { name:
|
|
7824
|
+
skills: setNamed(content.skills, { name: need7(args, "name"), description: need7(args, "description"), body: need7(args, "body") })
|
|
7624
7825
|
})),
|
|
7625
|
-
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) => ({
|
|
7626
7827
|
...content,
|
|
7627
|
-
skills: removeNamed(content.skills,
|
|
7828
|
+
skills: removeNamed(content.skills, need7(args, "name"), "skill", latest)
|
|
7628
7829
|
})),
|
|
7629
|
-
contentTool("profile_command_set", "Attach a command to a profile, or replace the command of that name.", { name: { ...
|
|
7630
|
-
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) => ({
|
|
7631
7832
|
...content,
|
|
7632
|
-
commands: removeNamed(content.commands,
|
|
7833
|
+
commands: removeNamed(content.commands, need7(args, "name"), "command", latest)
|
|
7633
7834
|
})),
|
|
7634
7835
|
stateTool("archive", "Archive a profile: it takes no new versions or pins; pinned sessions keep theirs; reversible with profile_restore."),
|
|
7635
7836
|
stateTool("restore", "Restore an archived profile (it takes versions and pins again)."),
|
|
7636
7837
|
{
|
|
7637
7838
|
name: "profile_assign",
|
|
7638
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}`,
|
|
7639
|
-
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)." } },
|
|
7640
7841
|
required: ["addr", "profileID", "version", "expectedRevision"],
|
|
7641
7842
|
readOnly: false,
|
|
7642
7843
|
idempotent: true,
|
|
7643
7844
|
run: async (hub, args, signal) => {
|
|
7644
7845
|
const expectedRevision = revision2(args);
|
|
7645
|
-
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 });
|
|
7646
7847
|
return `${a.sessionID} pins ${a.profileID} v${a.version} r${a.revision}${a.ack ? `, ack ${a.ack.status}${a.ack.offline ? " (offline)" : ""}` : ""}`;
|
|
7647
7848
|
}
|
|
7648
7849
|
},
|
|
7649
7850
|
{
|
|
7650
7851
|
name: "profile_unassign",
|
|
7651
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}`,
|
|
7652
|
-
properties: { addr: { ...
|
|
7853
|
+
properties: { addr: { ...STRING7, description: "The session." }, expectedRevision: { ...REVISION3, description: "The session's pin revision." } },
|
|
7653
7854
|
required: ["addr", "expectedRevision"],
|
|
7654
7855
|
readOnly: false,
|
|
7655
7856
|
idempotent: true,
|
|
7656
7857
|
run: async (hub, args, signal) => {
|
|
7657
|
-
const addr =
|
|
7858
|
+
const addr = need7(args, "addr");
|
|
7658
7859
|
await hub.profiles.unassign(addr, revision2(args), { signal });
|
|
7659
7860
|
return `unpinned ${addr}`;
|
|
7660
7861
|
}
|
|
7661
7862
|
}
|
|
7662
7863
|
];
|
|
7663
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
|
+
|
|
7664
8013
|
// packages/cli/src/watchlist.ts
|
|
7665
|
-
var
|
|
7666
|
-
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";
|
|
7667
8016
|
function watchlistConfigText(c) {
|
|
7668
8017
|
return [
|
|
7669
|
-
`revision ${
|
|
7670
|
-
`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"}`,
|
|
7671
8020
|
`sessions: ${list2(c.sessions)}`,
|
|
7672
8021
|
`projects: ${list2(c.projects)}`,
|
|
7673
|
-
`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"}`,
|
|
7674
8023
|
`muted: ${list2(c.muted)}`,
|
|
7675
8024
|
`muted projects: ${list2(c.mutedProjects)}`
|
|
7676
8025
|
].join(`
|
|
7677
8026
|
`);
|
|
7678
8027
|
}
|
|
7679
8028
|
function watchlistText(view) {
|
|
7680
|
-
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()}` : ""}`);
|
|
7681
8030
|
return [watchlistConfigText(view.config), "", view.entries.length ? `watching ${view.entries.length}${view.truncated ? " (truncated)" : ""}:` : "watching nothing", ...rows].join(`
|
|
7682
8031
|
`);
|
|
7683
8032
|
}
|
|
7684
8033
|
function watchlistChangedText(verb, r) {
|
|
7685
|
-
return `${r.changed ? `${verb}` : `already ${verb}`}: ${
|
|
8034
|
+
return `${r.changed ? `${verb}` : `already ${verb}`}: ${safe2(r.sessionID)} (revision ${safe2(r.config.revision)})`;
|
|
7686
8035
|
}
|
|
7687
8036
|
var PAST = { watch: "watching", unwatch: "not watching", mute: "muted", unmute: "unmuted" };
|
|
7688
8037
|
var watchlistVerbText = (verb, r) => watchlistChangedText(PAST[verb], r);
|
|
7689
8038
|
var ADDR = { type: "string", description: "Session address: ses_… (or a unique prefix), @name, @@role, @name.project." };
|
|
7690
8039
|
var STRINGS3 = { type: "array", items: { type: "string" } };
|
|
7691
|
-
var
|
|
8040
|
+
var need9 = (args, key) => {
|
|
7692
8041
|
const value = args[key];
|
|
7693
8042
|
if (typeof value !== "string" || !value.trim())
|
|
7694
8043
|
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
@@ -7734,13 +8083,13 @@ var watchlistTools = [
|
|
|
7734
8083
|
throw new ToolArgError(`"expectedRevision" is required (the revision you read)`);
|
|
7735
8084
|
const unknown = Object.keys(args).filter((k) => k !== "expectedRevision" && !UPDATE_FIELDS.includes(k));
|
|
7736
8085
|
if (unknown.length)
|
|
7737
|
-
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`);
|
|
7738
8087
|
const patch = {};
|
|
7739
8088
|
for (const field of UPDATE_FIELDS)
|
|
7740
8089
|
if (args[field] !== undefined && args[field] !== null)
|
|
7741
8090
|
patch[field] = args[field];
|
|
7742
8091
|
const r = await hub.concierge.watchlist.update(patch, expected, { signal });
|
|
7743
|
-
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)})`;
|
|
7744
8093
|
return `${head}
|
|
7745
8094
|
${watchlistConfigText(r.config)}`;
|
|
7746
8095
|
}
|
|
@@ -7755,7 +8104,7 @@ ${watchlistConfigText(r.config)}`;
|
|
|
7755
8104
|
idempotent: true,
|
|
7756
8105
|
run: async (hub, args, signal) => {
|
|
7757
8106
|
const on = flag(args, "watch", true);
|
|
7758
|
-
const addr =
|
|
8107
|
+
const addr = need9(args, "addr");
|
|
7759
8108
|
const r = on ? await hub.concierge.watchlist.watch(addr, { signal }) : await hub.concierge.watchlist.unwatch(addr, { signal });
|
|
7760
8109
|
return watchlistVerbText(on ? "watch" : "unwatch", r);
|
|
7761
8110
|
}
|
|
@@ -7770,7 +8119,7 @@ ${watchlistConfigText(r.config)}`;
|
|
|
7770
8119
|
idempotent: true,
|
|
7771
8120
|
run: async (hub, args, signal) => {
|
|
7772
8121
|
const on = flag(args, "mute", true);
|
|
7773
|
-
const addr =
|
|
8122
|
+
const addr = need9(args, "addr");
|
|
7774
8123
|
const r = on ? await hub.concierge.watchlist.mute(addr, { signal }) : await hub.concierge.watchlist.unmute(addr, { signal });
|
|
7775
8124
|
return watchlistVerbText(on ? "mute" : "unmute", r);
|
|
7776
8125
|
}
|
|
@@ -7781,13 +8130,13 @@ ${watchlistConfigText(r.config)}`;
|
|
|
7781
8130
|
var MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
7782
8131
|
var RPC = { parse: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602, internal: -32603 };
|
|
7783
8132
|
var ArgError3 = ToolArgError;
|
|
7784
|
-
var
|
|
8133
|
+
var need10 = (args, key) => {
|
|
7785
8134
|
const value = args[key];
|
|
7786
8135
|
if (typeof value !== "string" || !value.trim())
|
|
7787
8136
|
throw new ArgError3(`"${key}" is required (a non-empty string)`);
|
|
7788
8137
|
return value;
|
|
7789
8138
|
};
|
|
7790
|
-
var
|
|
8139
|
+
var optional8 = (args, key) => {
|
|
7791
8140
|
const value = args[key];
|
|
7792
8141
|
if (value === undefined || value === null)
|
|
7793
8142
|
return;
|
|
@@ -7868,7 +8217,7 @@ var sendOptions = (args) => {
|
|
|
7868
8217
|
return { ...wake !== undefined ? { wake } : {}, ...urgent !== undefined ? { urgent } : {}, ...notAfter !== undefined ? { notAfter } : {} };
|
|
7869
8218
|
};
|
|
7870
8219
|
var whyOption = (args) => {
|
|
7871
|
-
const why =
|
|
8220
|
+
const why = optional8(args, "why");
|
|
7872
8221
|
return why !== undefined ? { why } : {};
|
|
7873
8222
|
};
|
|
7874
8223
|
var optionalDelivery = (args) => {
|
|
@@ -7911,35 +8260,35 @@ var definedFields = (fields) => {
|
|
|
7911
8260
|
return Object.keys(out).length ? out : undefined;
|
|
7912
8261
|
};
|
|
7913
8262
|
function launchSpec(args) {
|
|
7914
|
-
const repo =
|
|
8263
|
+
const repo = need10(args, "repo");
|
|
7915
8264
|
if (!repo.startsWith("/"))
|
|
7916
8265
|
throw new ArgError3(`"repo" must be an absolute path on the node`);
|
|
7917
8266
|
const kind = optionalEnum(args, "runtime", RUNTIME_KINDS);
|
|
7918
|
-
const profile =
|
|
8267
|
+
const profile = optional8(args, "profile");
|
|
7919
8268
|
if (profile !== undefined && kind === undefined)
|
|
7920
8269
|
throw new ArgError3(`"profile" needs "runtime" (e.g. openshell)`);
|
|
7921
8270
|
const nodeLabels = optionalLabels(args, "nodeLabels");
|
|
7922
|
-
const machine =
|
|
8271
|
+
const machine = optional8(args, "machine");
|
|
7923
8272
|
const prefer = optionalBool(args, "prefer") === true;
|
|
7924
8273
|
if (prefer && machine === undefined && nodeLabels === undefined)
|
|
7925
8274
|
throw new ArgError3(`"prefer" needs "machine" or "nodeLabels"`);
|
|
7926
|
-
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") }) };
|
|
7927
8276
|
return {
|
|
7928
8277
|
...definedFields({
|
|
7929
|
-
key:
|
|
7930
|
-
name:
|
|
8278
|
+
key: optional8(args, "key"),
|
|
8279
|
+
name: optional8(args, "name")?.replace(/^@+/, ""),
|
|
7931
8280
|
roles: optionalStrings(args, "roles")?.map((r) => r.replace(/^@@/, "")),
|
|
7932
|
-
harness:
|
|
7933
|
-
agent:
|
|
7934
|
-
model:
|
|
8281
|
+
harness: optional8(args, "harness"),
|
|
8282
|
+
agent: optional8(args, "agent"),
|
|
8283
|
+
model: optional8(args, "model")
|
|
7935
8284
|
}),
|
|
7936
8285
|
workspace,
|
|
7937
8286
|
...definedFields({
|
|
7938
8287
|
runtime: kind === undefined ? undefined : { kind, ...profile !== undefined ? { profile } : {} },
|
|
7939
8288
|
placement: definedFields({ labels: nodeLabels, machine, mode: prefer ? "prefer" : undefined }),
|
|
7940
|
-
prompt:
|
|
8289
|
+
prompt: optional8(args, "prompt"),
|
|
7941
8290
|
labels: optionalLabels(args, "labels"),
|
|
7942
|
-
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) }),
|
|
7943
8292
|
notAfter: optionalNotAfter(args)
|
|
7944
8293
|
})
|
|
7945
8294
|
};
|
|
@@ -7986,7 +8335,7 @@ function messageText({ message, answer }) {
|
|
|
7986
8335
|
`);
|
|
7987
8336
|
}
|
|
7988
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.";
|
|
7989
|
-
var
|
|
8338
|
+
var STRING9 = { type: "string" };
|
|
7990
8339
|
var SEND = {
|
|
7991
8340
|
wake: { type: "boolean", description: "false: admit the message without starting a turn." },
|
|
7992
8341
|
urgent: { type: "boolean", description: "Steer into the recipient's running turn." },
|
|
@@ -8010,7 +8359,7 @@ var archiveTools = (archived) => [{
|
|
|
8010
8359
|
organizes: true,
|
|
8011
8360
|
idempotent: true,
|
|
8012
8361
|
run: async (hub, args, signal) => {
|
|
8013
|
-
const addr =
|
|
8362
|
+
const addr = need10(args, "addr");
|
|
8014
8363
|
const result = await hub.setArchived(addr, archived, { signal });
|
|
8015
8364
|
return `${archived ? "Archived" : "Unarchived"} ${addr} (${result.sessionID})${result.changed ? "" : "; already in that state"}`;
|
|
8016
8365
|
}
|
|
@@ -8024,7 +8373,7 @@ var pinTools = (pinned) => [{
|
|
|
8024
8373
|
organizes: true,
|
|
8025
8374
|
idempotent: true,
|
|
8026
8375
|
run: async (hub, args, signal) => {
|
|
8027
|
-
const addr =
|
|
8376
|
+
const addr = need10(args, "addr");
|
|
8028
8377
|
const r = await hub.setPinned(addr, pinned, { signal });
|
|
8029
8378
|
return `${pinned ? "Pinned" : "Unpinned"} ${addr} (${r.sessionID})${r.changed ? "" : "; already in that state"}`;
|
|
8030
8379
|
}
|
|
@@ -8032,14 +8381,14 @@ var pinTools = (pinned) => [{
|
|
|
8032
8381
|
var archiveInactiveTool = {
|
|
8033
8382
|
name: "archive_inactive",
|
|
8034
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.",
|
|
8035
|
-
properties: { preview: { type: "boolean" }, inactiveFor:
|
|
8384
|
+
properties: { preview: { type: "boolean" }, inactiveFor: STRING9, limit: { type: "integer", minimum: 1, maximum: 200 }, previewToken: STRING9, confirm: { type: "boolean" } },
|
|
8036
8385
|
readOnly: false,
|
|
8037
8386
|
organizes: true,
|
|
8038
8387
|
run: async (hub, args, signal) => {
|
|
8039
8388
|
if (args.preview === true)
|
|
8040
|
-
return JSON.stringify(await hub.previewArchiveInactive(
|
|
8389
|
+
return JSON.stringify(await hub.previewArchiveInactive(need10(args, "inactiveFor"), optionalInt(args, "limit", 1, 200) ?? 50, { signal }));
|
|
8041
8390
|
if (args.confirm === true)
|
|
8042
|
-
return JSON.stringify(await hub.archiveInactive(
|
|
8391
|
+
return JSON.stringify(await hub.archiveInactive(need10(args, "previewToken"), { signal }));
|
|
8043
8392
|
throw new ArgError3("Use preview:true with inactiveFor, then confirm:true with previewToken");
|
|
8044
8393
|
}
|
|
8045
8394
|
};
|
|
@@ -8050,19 +8399,19 @@ var intentTools = [
|
|
|
8050
8399
|
properties: { addr: ADDR2 },
|
|
8051
8400
|
required: ["addr"],
|
|
8052
8401
|
readOnly: true,
|
|
8053
|
-
run: async (hub, args, signal) => intentLine(
|
|
8402
|
+
run: async (hub, args, signal) => intentLine(need10(args, "addr"), (await hub.intent(need10(args, "addr"), { signal })).intent)
|
|
8054
8403
|
},
|
|
8055
8404
|
{
|
|
8056
8405
|
name: "session_intent_set",
|
|
8057
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.",
|
|
8058
|
-
properties: { addr: ADDR2, text:
|
|
8407
|
+
properties: { addr: ADDR2, text: STRING9, clear: { type: "boolean" }, expectedRevision: { type: "integer", minimum: 0 } },
|
|
8059
8408
|
required: ["addr"],
|
|
8060
8409
|
readOnly: false,
|
|
8061
8410
|
organizes: true,
|
|
8062
8411
|
idempotent: true,
|
|
8063
8412
|
run: async (hub, args, signal) => {
|
|
8064
|
-
const addr =
|
|
8065
|
-
const text =
|
|
8413
|
+
const addr = need10(args, "addr");
|
|
8414
|
+
const text = optional8(args, "text");
|
|
8066
8415
|
const clear = args.clear === true;
|
|
8067
8416
|
if (clear === (text !== undefined))
|
|
8068
8417
|
throw new ArgError3('Give "text" or "clear": true');
|
|
@@ -8072,22 +8421,22 @@ var intentTools = [
|
|
|
8072
8421
|
}
|
|
8073
8422
|
];
|
|
8074
8423
|
var TARGET = { ...ADDR2, description: `${ADDR2.description} Required for an app token; a session token defaults to itself.` };
|
|
8075
|
-
var ROLE2 = { ...
|
|
8076
|
-
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)." };
|
|
8077
8426
|
var MANAGE_NOTE = "Needs scope sessions:manage for an app token";
|
|
8078
8427
|
var roleText2 = (r) => `@@${r.role}${r.project !== undefined ? `.${r.project}` : ""}`;
|
|
8079
8428
|
var nameTools = [
|
|
8080
8429
|
{
|
|
8081
8430
|
name: "name",
|
|
8082
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}.`,
|
|
8083
|
-
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)." } },
|
|
8084
8433
|
required: ["name"],
|
|
8085
8434
|
readOnly: false,
|
|
8086
8435
|
organizes: true,
|
|
8087
8436
|
idempotent: true,
|
|
8088
8437
|
run: async (hub, args, signal) => {
|
|
8089
|
-
const session =
|
|
8090
|
-
const r = await hub.name(
|
|
8438
|
+
const session = optional8(args, "addr");
|
|
8439
|
+
const r = await hub.name(need10(args, "name"), { ...session ? { session } : {}, signal });
|
|
8091
8440
|
return `Named ${r.sessionID} @${r.name}${r.alias ? ` (@${r.alias} stays an alias for 24 h)` : ""}`;
|
|
8092
8441
|
}
|
|
8093
8442
|
},
|
|
@@ -8100,9 +8449,9 @@ var nameTools = [
|
|
|
8100
8449
|
organizes: true,
|
|
8101
8450
|
idempotent: true,
|
|
8102
8451
|
run: async (hub, args, signal) => {
|
|
8103
|
-
const session =
|
|
8104
|
-
const project =
|
|
8105
|
-
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 });
|
|
8106
8455
|
return `${r.sessionID} holds ${roleText2(r)} (generation ${r.generation}${r.exclusive ? ", exclusive" : ""})`;
|
|
8107
8456
|
}
|
|
8108
8457
|
},
|
|
@@ -8114,9 +8463,9 @@ var nameTools = [
|
|
|
8114
8463
|
readOnly: false,
|
|
8115
8464
|
organizes: true,
|
|
8116
8465
|
run: async (hub, args, signal) => {
|
|
8117
|
-
const session =
|
|
8118
|
-
const project =
|
|
8119
|
-
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 });
|
|
8120
8469
|
return `${r.sessionID} released ${roleText2(r)}`;
|
|
8121
8470
|
}
|
|
8122
8471
|
}
|
|
@@ -8125,16 +8474,16 @@ var eventsTool = {
|
|
|
8125
8474
|
name: "events",
|
|
8126
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.",
|
|
8127
8476
|
properties: {
|
|
8128
|
-
cursor: { ...
|
|
8477
|
+
cursor: { ...STRING9, description: "The cursor from the previous events answer (hev1_…); omit to start at the latest." },
|
|
8129
8478
|
wait: { type: "integer", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait while nothing matches (0–${WAIT_POLL_MAX_S}).` },
|
|
8130
8479
|
limit: { type: "integer", minimum: 1, maximum: 200, description: "At most this many events (default 50)." },
|
|
8131
|
-
types: { type: "array", items:
|
|
8132
|
-
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)." }
|
|
8133
8482
|
},
|
|
8134
8483
|
readOnly: true,
|
|
8135
8484
|
run: async (hub, args, signal) => {
|
|
8136
8485
|
const page = await hub.events({
|
|
8137
|
-
cursor:
|
|
8486
|
+
cursor: optional8(args, "cursor"),
|
|
8138
8487
|
wait: optionalInt(args, "wait", 0, WAIT_POLL_MAX_S),
|
|
8139
8488
|
limit: optionalInt(args, "limit", 1, 200),
|
|
8140
8489
|
types: optionalStrings(args, "types"),
|
|
@@ -8149,13 +8498,13 @@ var webhookTools = [
|
|
|
8149
8498
|
{
|
|
8150
8499
|
name: "webhook_create",
|
|
8151
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).`,
|
|
8152
|
-
properties: { url:
|
|
8501
|
+
properties: { url: STRING9, events: { type: "array", items: STRING9 }, label: STRING9 },
|
|
8153
8502
|
required: ["url"],
|
|
8154
8503
|
readOnly: false,
|
|
8155
8504
|
run: async (hub, args, signal) => {
|
|
8156
8505
|
const events = optionalStrings(args, "events");
|
|
8157
|
-
const label =
|
|
8158
|
-
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 });
|
|
8159
8508
|
return `${webhookText(r.webhook)}
|
|
8160
8509
|
signing secret (shown once): ${r.secret}`;
|
|
8161
8510
|
}
|
|
@@ -8174,11 +8523,11 @@ signing secret (shown once): ${r.secret}`;
|
|
|
8174
8523
|
{
|
|
8175
8524
|
name: "webhook_show",
|
|
8176
8525
|
description: "Show one webhook and its retained delivery attempts; never its secret.",
|
|
8177
|
-
properties: { id:
|
|
8526
|
+
properties: { id: STRING9 },
|
|
8178
8527
|
required: ["id"],
|
|
8179
8528
|
readOnly: true,
|
|
8180
8529
|
run: async (hub, args, signal) => {
|
|
8181
|
-
const r = await hub.webhook(
|
|
8530
|
+
const r = await hub.webhook(need10(args, "id"), { signal });
|
|
8182
8531
|
return `${webhookText(r.webhook)}
|
|
8183
8532
|
${JSON.stringify(r.deliveries)}`;
|
|
8184
8533
|
}
|
|
@@ -8186,20 +8535,20 @@ ${JSON.stringify(r.deliveries)}`;
|
|
|
8186
8535
|
{
|
|
8187
8536
|
name: "webhook_delete",
|
|
8188
8537
|
description: "Delete one webhook and its pending deliveries.",
|
|
8189
|
-
properties: { id:
|
|
8538
|
+
properties: { id: STRING9 },
|
|
8190
8539
|
required: ["id"],
|
|
8191
8540
|
readOnly: false,
|
|
8192
8541
|
organizes: true,
|
|
8193
8542
|
idempotent: false,
|
|
8194
8543
|
run: async (hub, args, signal) => {
|
|
8195
|
-
const id =
|
|
8544
|
+
const id = need10(args, "id");
|
|
8196
8545
|
await hub.webhookDelete(id, { signal });
|
|
8197
8546
|
return `Deleted webhook ${id}`;
|
|
8198
8547
|
}
|
|
8199
8548
|
}
|
|
8200
8549
|
];
|
|
8201
8550
|
async function profileTarget(hub, args, signal) {
|
|
8202
|
-
const addr =
|
|
8551
|
+
const addr = optional8(args, "addr");
|
|
8203
8552
|
if (addr)
|
|
8204
8553
|
return addr;
|
|
8205
8554
|
const { actor } = await hub.whoami({ signal });
|
|
@@ -8238,11 +8587,11 @@ var profileTools = [
|
|
|
8238
8587
|
{
|
|
8239
8588
|
name: "profile_skill",
|
|
8240
8589
|
description: "Load one skill's full text from the profile pinned to a session (your own by default), on demand.",
|
|
8241
|
-
properties: { name:
|
|
8590
|
+
properties: { name: STRING9, addr: ADDR2 },
|
|
8242
8591
|
required: ["name"],
|
|
8243
8592
|
readOnly: true,
|
|
8244
8593
|
run: async (hub, args, signal) => {
|
|
8245
|
-
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 });
|
|
8246
8595
|
return `skill ${r.skill.name} (${r.profileID} v${r.version}):
|
|
8247
8596
|
${sanitizeText(r.skill.body)}`;
|
|
8248
8597
|
}
|
|
@@ -8250,11 +8599,11 @@ ${sanitizeText(r.skill.body)}`;
|
|
|
8250
8599
|
{
|
|
8251
8600
|
name: "profile_command",
|
|
8252
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.",
|
|
8253
|
-
properties: { name:
|
|
8602
|
+
properties: { name: STRING9, params: { type: "object", additionalProperties: STRING9, description: "Parameter name → text value." }, addr: ADDR2 },
|
|
8254
8603
|
required: ["name"],
|
|
8255
8604
|
readOnly: true,
|
|
8256
8605
|
run: async (hub, args, signal) => {
|
|
8257
|
-
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 });
|
|
8258
8607
|
return r.prompt;
|
|
8259
8608
|
}
|
|
8260
8609
|
},
|
|
@@ -8273,13 +8622,13 @@ ${sanitizeText(r.skill.body)}`;
|
|
|
8273
8622
|
{
|
|
8274
8623
|
name: "profile_usage",
|
|
8275
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.",
|
|
8276
|
-
properties: { id:
|
|
8625
|
+
properties: { id: STRING9, limit: { type: "integer", minimum: 1, maximum: 200, description: "Sessions per page (default 50)." }, cursor: STRING9 },
|
|
8277
8626
|
required: ["id"],
|
|
8278
8627
|
readOnly: true,
|
|
8279
8628
|
run: async (hub, args, signal) => {
|
|
8280
8629
|
const limit = optionalInt(args, "limit", 1, 200);
|
|
8281
|
-
const cursor =
|
|
8282
|
-
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 });
|
|
8283
8632
|
const c = u.counts;
|
|
8284
8633
|
const plural = (n, word, many = `${word}s`) => `${n} ${n === 1 ? word : many}`;
|
|
8285
8634
|
const lines = [`${u.profileID} (latest v${u.latestVersion})`];
|
|
@@ -8322,7 +8671,7 @@ var needRevision = (args, key = "expectedRevision") => {
|
|
|
8322
8671
|
return value;
|
|
8323
8672
|
};
|
|
8324
8673
|
var idemOption = (args) => {
|
|
8325
|
-
const key =
|
|
8674
|
+
const key = optional8(args, "idempotencyKey");
|
|
8326
8675
|
return key !== undefined ? { idempotencyKey: key } : {};
|
|
8327
8676
|
};
|
|
8328
8677
|
var WORKSPACE = { type: "string", description: "The workspace's ID (ws_…)." };
|
|
@@ -8352,7 +8701,7 @@ var workspaceTools = [
|
|
|
8352
8701
|
required: ["workspace"],
|
|
8353
8702
|
readOnly: true,
|
|
8354
8703
|
run: async (hub, args, signal) => {
|
|
8355
|
-
const id =
|
|
8704
|
+
const id = need10(args, "workspace");
|
|
8356
8705
|
const [{ workspace, bindings }, { members }, graph] = await Promise.all([hub.workspaces.get(id, { signal }), hub.workspaces.members(id, { signal }), hub.workspaces.supervision(id, { signal })]);
|
|
8357
8706
|
const lines = [wsLine(workspace)];
|
|
8358
8707
|
if (workspace.masterMemberID)
|
|
@@ -8368,27 +8717,27 @@ var workspaceTools = [
|
|
|
8368
8717
|
{
|
|
8369
8718
|
name: "workspace_create",
|
|
8370
8719
|
description: `Create a coordination workspace. ${WS_SCOPES}`,
|
|
8371
|
-
properties: { title:
|
|
8720
|
+
properties: { title: STRING9, description: STRING9, tags: { type: "array", items: STRING9 }, idempotencyKey: IDEMPOTENCY3 },
|
|
8372
8721
|
required: ["title"],
|
|
8373
8722
|
readOnly: false,
|
|
8374
8723
|
run: async (hub, args, signal) => {
|
|
8375
|
-
const description =
|
|
8724
|
+
const description = optional8(args, "description");
|
|
8376
8725
|
const tags = optionalStrings(args, "tags");
|
|
8377
|
-
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 });
|
|
8378
8727
|
return `created ${wsLine(workspace)}`;
|
|
8379
8728
|
}
|
|
8380
8729
|
},
|
|
8381
8730
|
{
|
|
8382
8731
|
name: "workspace_update",
|
|
8383
8732
|
description: `Change a workspace's title, description or tags under its revision. ${WS_SCOPES}`,
|
|
8384
|
-
properties: { workspace: WORKSPACE, expectedRevision: REVISION4, title:
|
|
8733
|
+
properties: { workspace: WORKSPACE, expectedRevision: REVISION4, title: STRING9, description: STRING9, tags: { type: "array", items: STRING9 } },
|
|
8385
8734
|
required: ["workspace", "expectedRevision"],
|
|
8386
8735
|
readOnly: false,
|
|
8387
8736
|
run: async (hub, args, signal) => {
|
|
8388
|
-
const patch = definedFields({ title:
|
|
8737
|
+
const patch = definedFields({ title: optional8(args, "title"), description: optional8(args, "description"), tags: optionalStrings(args, "tags") });
|
|
8389
8738
|
if (!patch)
|
|
8390
8739
|
throw new ArgError3(`give "title", "description" or "tags"`);
|
|
8391
|
-
const { workspace } = await hub.workspaces.update(
|
|
8740
|
+
const { workspace } = await hub.workspaces.update(need10(args, "workspace"), { expectedRevision: needRevision(args), ...patch }, { signal });
|
|
8392
8741
|
return `updated ${wsLine(workspace)}`;
|
|
8393
8742
|
}
|
|
8394
8743
|
},
|
|
@@ -8400,7 +8749,7 @@ var workspaceTools = [
|
|
|
8400
8749
|
readOnly: false,
|
|
8401
8750
|
idempotent: true,
|
|
8402
8751
|
run: async (hub, args, signal) => {
|
|
8403
|
-
const id =
|
|
8752
|
+
const id = need10(args, "workspace");
|
|
8404
8753
|
if (verb === "archive") {
|
|
8405
8754
|
const r = await hub.workspaces.archive(id, needRevision(args), { signal });
|
|
8406
8755
|
return `archived ${wsLine(r.workspace)}; ${r.departed} member(s) departed, ${r.supervision.detached} supervision link(s) detached`;
|
|
@@ -8411,31 +8760,31 @@ var workspaceTools = [
|
|
|
8411
8760
|
{
|
|
8412
8761
|
name: "workspace_bind",
|
|
8413
8762
|
description: `Bind a workspace to a repository (absolute path) on one of the user's machines. ${WS_SCOPES}`,
|
|
8414
|
-
properties: { workspace: WORKSPACE, machineID:
|
|
8763
|
+
properties: { workspace: WORKSPACE, machineID: STRING9, repo: STRING9, label: STRING9, idempotencyKey: IDEMPOTENCY3 },
|
|
8415
8764
|
required: ["workspace", "machineID", "repo"],
|
|
8416
8765
|
readOnly: false,
|
|
8417
8766
|
idempotent: true,
|
|
8418
8767
|
run: async (hub, args, signal) => {
|
|
8419
|
-
const label =
|
|
8420
|
-
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 });
|
|
8421
8770
|
return `${r.created ? "bound" : "already bound"} ${r.binding.id} ${r.binding.machineID}:${r.binding.repo} r${r.binding.revision}`;
|
|
8422
8771
|
}
|
|
8423
8772
|
},
|
|
8424
8773
|
{
|
|
8425
8774
|
name: "workspace_unbind",
|
|
8426
8775
|
description: `Remove a repository binding under the binding's revision. ${WS_SCOPES}`,
|
|
8427
|
-
properties: { workspace: WORKSPACE, bindingID:
|
|
8776
|
+
properties: { workspace: WORKSPACE, bindingID: STRING9, expectedRevision: REVISION4 },
|
|
8428
8777
|
required: ["workspace", "bindingID", "expectedRevision"],
|
|
8429
8778
|
readOnly: false,
|
|
8430
8779
|
run: async (hub, args, signal) => {
|
|
8431
|
-
await hub.workspaces.unbind(
|
|
8432
|
-
return `unbound ${
|
|
8780
|
+
await hub.workspaces.unbind(need10(args, "workspace"), need10(args, "bindingID"), needRevision(args), { signal });
|
|
8781
|
+
return `unbound ${need10(args, "bindingID")}`;
|
|
8433
8782
|
}
|
|
8434
8783
|
},
|
|
8435
8784
|
{
|
|
8436
8785
|
name: "workspace_join",
|
|
8437
8786
|
description: `Add a session (ses_…) to a workspace as worker or observer (the master is set by explicit replacement). ${WS_SCOPES}`,
|
|
8438
|
-
properties: { workspace: WORKSPACE, sessionID:
|
|
8787
|
+
properties: { workspace: WORKSPACE, sessionID: STRING9, role: { type: "string", enum: [...MEMBER_ROLES] }, idempotencyKey: IDEMPOTENCY3 },
|
|
8439
8788
|
required: ["workspace", "sessionID", "role"],
|
|
8440
8789
|
readOnly: false,
|
|
8441
8790
|
idempotent: true,
|
|
@@ -8443,45 +8792,45 @@ var workspaceTools = [
|
|
|
8443
8792
|
const role = optionalEnum(args, "role", MEMBER_ROLES);
|
|
8444
8793
|
if (!role)
|
|
8445
8794
|
throw new ArgError3(`"role" is one of ${MEMBER_ROLES.join(", ")}`);
|
|
8446
|
-
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 });
|
|
8447
8796
|
return `member ${member.id} (${member.sessionID}, ${member.role}, generation ${member.generation}, r${member.revision}) ${created ? "joined" : "was already a member"}`;
|
|
8448
8797
|
}
|
|
8449
8798
|
},
|
|
8450
8799
|
{
|
|
8451
8800
|
name: "workspace_leave",
|
|
8452
8801
|
description: `Remove a member under its revision; its supervision links are detached. ${WS_SCOPES}`,
|
|
8453
|
-
properties: { workspace: WORKSPACE, memberID:
|
|
8802
|
+
properties: { workspace: WORKSPACE, memberID: STRING9, expectedRevision: REVISION4 },
|
|
8454
8803
|
required: ["workspace", "memberID", "expectedRevision"],
|
|
8455
8804
|
readOnly: false,
|
|
8456
8805
|
idempotent: true,
|
|
8457
8806
|
run: async (hub, args, signal) => {
|
|
8458
|
-
const r = await hub.workspaces.leave(
|
|
8807
|
+
const r = await hub.workspaces.leave(need10(args, "workspace"), need10(args, "memberID"), needRevision(args), { signal });
|
|
8459
8808
|
return `member ${r.member.id} left (${r.member.leftReason ?? "removed"}); ${r.supervision.detached} supervision link(s) detached`;
|
|
8460
8809
|
}
|
|
8461
8810
|
},
|
|
8462
8811
|
{
|
|
8463
8812
|
name: "workspace_assign",
|
|
8464
8813
|
description: `Give a current member an assignment (title and optional outcome criteria; instructions travel as messages). ${WS_SCOPES}`,
|
|
8465
|
-
properties: { workspace: WORKSPACE, memberID:
|
|
8814
|
+
properties: { workspace: WORKSPACE, memberID: STRING9, title: STRING9, criteria: { type: "array", items: STRING9 }, ref: STRING9, idempotencyKey: IDEMPOTENCY3 },
|
|
8466
8815
|
required: ["workspace", "memberID", "title"],
|
|
8467
8816
|
readOnly: false,
|
|
8468
8817
|
run: async (hub, args, signal) => {
|
|
8469
8818
|
const criteria = optionalStrings(args, "criteria");
|
|
8470
|
-
const ref =
|
|
8471
|
-
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 });
|
|
8472
8821
|
return `assignment ${assignment.id} → ${assignment.memberID}: ${assignment.status}, r${assignment.revision}`;
|
|
8473
8822
|
}
|
|
8474
8823
|
},
|
|
8475
8824
|
{
|
|
8476
8825
|
name: "workspace_assignments",
|
|
8477
8826
|
description: `A workspace's assignments (id, member, status, revision, title), optionally one member's or one status. ${WS_SCOPES}`,
|
|
8478
|
-
properties: { workspace: WORKSPACE, memberID:
|
|
8827
|
+
properties: { workspace: WORKSPACE, memberID: STRING9, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] } },
|
|
8479
8828
|
required: ["workspace"],
|
|
8480
8829
|
readOnly: true,
|
|
8481
8830
|
run: async (hub, args, signal) => {
|
|
8482
|
-
const memberID =
|
|
8831
|
+
const memberID = optional8(args, "memberID");
|
|
8483
8832
|
const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
|
|
8484
|
-
const r = await hub.workspaces.assignments(
|
|
8833
|
+
const r = await hub.workspaces.assignments(need10(args, "workspace"), { ...memberID ? { memberID } : {}, ...status ? { status } : {}, limit: 200, signal });
|
|
8485
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(`
|
|
8486
8835
|
`) : "no assignments";
|
|
8487
8836
|
}
|
|
@@ -8489,16 +8838,16 @@ var workspaceTools = [
|
|
|
8489
8838
|
{
|
|
8490
8839
|
name: "workspace_assignment_update",
|
|
8491
8840
|
description: `Set an assignment's status (done and cancelled are final) or move it to another current member, under its revision. ${WS_SCOPES}`,
|
|
8492
|
-
properties: { workspace: WORKSPACE, assignmentID:
|
|
8841
|
+
properties: { workspace: WORKSPACE, assignmentID: STRING9, expectedRevision: REVISION4, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] }, memberID: STRING9 },
|
|
8493
8842
|
required: ["workspace", "assignmentID", "expectedRevision"],
|
|
8494
8843
|
readOnly: false,
|
|
8495
8844
|
run: async (hub, args, signal) => {
|
|
8496
8845
|
const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
|
|
8497
|
-
const memberID =
|
|
8846
|
+
const memberID = optional8(args, "memberID");
|
|
8498
8847
|
if (status === undefined === (memberID === undefined))
|
|
8499
8848
|
throw new ArgError3(`give exactly one of "status" or "memberID"`);
|
|
8500
|
-
const id =
|
|
8501
|
-
const assignmentID =
|
|
8849
|
+
const id = need10(args, "workspace");
|
|
8850
|
+
const assignmentID = need10(args, "assignmentID");
|
|
8502
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 });
|
|
8503
8852
|
return `assignment ${assignment.id} → ${assignment.memberID}: ${assignment.status}, r${assignment.revision}`;
|
|
8504
8853
|
}
|
|
@@ -8506,16 +8855,16 @@ var workspaceTools = [
|
|
|
8506
8855
|
{
|
|
8507
8856
|
name: "workspace_supervise",
|
|
8508
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}`,
|
|
8509
|
-
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 },
|
|
8510
8859
|
required: ["workspace", "memberID", "supervisor", "expectedRevision"],
|
|
8511
8860
|
readOnly: false,
|
|
8512
8861
|
run: async (hub, args, signal) => {
|
|
8513
|
-
const supervisor = args.supervisor === null ? null :
|
|
8862
|
+
const supervisor = args.supervisor === null ? null : need10(args, "supervisor");
|
|
8514
8863
|
const order = args.order;
|
|
8515
8864
|
if (order !== undefined && (typeof order !== "number" || !Number.isFinite(order)))
|
|
8516
8865
|
throw new ArgError3(`"order" must be a number`);
|
|
8517
|
-
const r = await hub.workspaces.supervise(
|
|
8518
|
-
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` : ""}`;
|
|
8519
8868
|
}
|
|
8520
8869
|
},
|
|
8521
8870
|
{
|
|
@@ -8525,7 +8874,7 @@ var workspaceTools = [
|
|
|
8525
8874
|
required: ["workspace"],
|
|
8526
8875
|
readOnly: true,
|
|
8527
8876
|
run: async (hub, args, signal) => {
|
|
8528
|
-
const v = await hub.workspaces.master(
|
|
8877
|
+
const v = await hub.workspaces.master(need10(args, "workspace"), { signal });
|
|
8529
8878
|
const line = (m) => `${m.member.id} ${m.member.sessionID} generation ${m.member.generation} ${m.presence}${m.member.leftReason ? ` (${m.member.leftReason})` : ""}`;
|
|
8530
8879
|
return [
|
|
8531
8880
|
`workspace ${v.workspaceID} r${v.revision}`,
|
|
@@ -8539,17 +8888,17 @@ var workspaceTools = [
|
|
|
8539
8888
|
{
|
|
8540
8889
|
name: "workspace_master_replace",
|
|
8541
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}`,
|
|
8542
|
-
properties: { workspace: WORKSPACE, sessionID:
|
|
8891
|
+
properties: { workspace: WORKSPACE, sessionID: STRING9, launchID: STRING9, expectedRevision: REVISION4, idempotencyKey: IDEMPOTENCY3 },
|
|
8543
8892
|
required: ["workspace", "expectedRevision"],
|
|
8544
8893
|
readOnly: false,
|
|
8545
8894
|
idempotent: true,
|
|
8546
8895
|
run: async (hub, args, signal) => {
|
|
8547
|
-
const sessionID =
|
|
8548
|
-
const launchID =
|
|
8896
|
+
const sessionID = optional8(args, "sessionID");
|
|
8897
|
+
const launchID = optional8(args, "launchID");
|
|
8549
8898
|
if (sessionID === undefined === (launchID === undefined))
|
|
8550
8899
|
throw new ArgError3(`give exactly one of "sessionID" or "launchID"`);
|
|
8551
8900
|
const target = sessionID !== undefined ? { sessionID } : { launchID };
|
|
8552
|
-
const r = await hub.workspaces.replaceMaster(
|
|
8901
|
+
const r = await hub.workspaces.replaceMaster(need10(args, "workspace"), { ...target, expectedRevision: needRevision(args) }, { ...idemOption(args), signal });
|
|
8553
8902
|
if (!r.changed)
|
|
8554
8903
|
return `${r.master.sessionID} already is the master (${r.master.id}); nothing changed`;
|
|
8555
8904
|
return [
|
|
@@ -8567,7 +8916,7 @@ var workspaceTools = [
|
|
|
8567
8916
|
required: ["workspace"],
|
|
8568
8917
|
readOnly: true,
|
|
8569
8918
|
run: async (hub, args, signal) => {
|
|
8570
|
-
const b = await hub.workspaces.board(
|
|
8919
|
+
const b = await hub.workspaces.board(need10(args, "workspace"), { signal });
|
|
8571
8920
|
const more = (truncated) => truncated ? " (more not shown)" : "";
|
|
8572
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})` : ""}`];
|
|
8573
8922
|
const lines = [wsLine(b.workspace), ...b.workspace.masterMemberID ? [`master: ${b.workspace.masterMemberID}`] : []];
|
|
@@ -8593,10 +8942,13 @@ var TOOLS = [
|
|
|
8593
8942
|
...webhookTools,
|
|
8594
8943
|
...profileTools,
|
|
8595
8944
|
...profileLifecycleTools,
|
|
8945
|
+
...grantTools,
|
|
8596
8946
|
...workspaceTools,
|
|
8597
8947
|
...artifactTools,
|
|
8598
8948
|
...artifactJobTools,
|
|
8599
8949
|
...teamIntakeTools,
|
|
8950
|
+
...subscriptionTools,
|
|
8951
|
+
...operationTools,
|
|
8600
8952
|
...teamTools,
|
|
8601
8953
|
...watchlistTools,
|
|
8602
8954
|
{
|
|
@@ -8617,14 +8969,14 @@ var TOOLS = [
|
|
|
8617
8969
|
name: "sessions",
|
|
8618
8970
|
description: "The sessions as a flat list, optionally filtered.",
|
|
8619
8971
|
properties: {
|
|
8620
|
-
project: { ...
|
|
8621
|
-
state: { ...
|
|
8622
|
-
machine: { ...
|
|
8623
|
-
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." }
|
|
8624
8976
|
},
|
|
8625
8977
|
readOnly: true,
|
|
8626
8978
|
run: async (hub, args, signal) => {
|
|
8627
|
-
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 });
|
|
8628
8980
|
return sessionsText(body.sessions ?? []);
|
|
8629
8981
|
}
|
|
8630
8982
|
},
|
|
@@ -8634,7 +8986,7 @@ var TOOLS = [
|
|
|
8634
8986
|
properties: { addr: ADDR2 },
|
|
8635
8987
|
required: ["addr"],
|
|
8636
8988
|
readOnly: true,
|
|
8637
|
-
run: async (hub, args, signal) => showText(await hub.show(
|
|
8989
|
+
run: async (hub, args, signal) => showText(await hub.show(need10(args, "addr"), { signal }))
|
|
8638
8990
|
},
|
|
8639
8991
|
{
|
|
8640
8992
|
name: "log",
|
|
@@ -8643,7 +8995,7 @@ var TOOLS = [
|
|
|
8643
8995
|
required: ["addr"],
|
|
8644
8996
|
readOnly: true,
|
|
8645
8997
|
run: async (hub, args, signal) => {
|
|
8646
|
-
const addr =
|
|
8998
|
+
const addr = need10(args, "addr");
|
|
8647
8999
|
const n = optionalInt(args, "n", 1, 200);
|
|
8648
9000
|
return logText((await hub.log(addr, { ...n !== undefined ? { n } : {}, signal })).rows ?? []);
|
|
8649
9001
|
}
|
|
@@ -8652,25 +9004,25 @@ var TOOLS = [
|
|
|
8652
9004
|
name: "who",
|
|
8653
9005
|
description: "Named and role-holding sessions; a team member sees its teams, grouped by team with its reach to each.",
|
|
8654
9006
|
properties: {
|
|
8655
|
-
project: { ...
|
|
8656
|
-
role: { ...
|
|
8657
|
-
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)." }
|
|
8658
9010
|
},
|
|
8659
9011
|
readOnly: true,
|
|
8660
9012
|
run: async (hub, args, signal) => {
|
|
8661
|
-
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 });
|
|
8662
9014
|
return whoText(body.sessions ?? [], body);
|
|
8663
9015
|
}
|
|
8664
9016
|
},
|
|
8665
9017
|
{
|
|
8666
9018
|
name: "msg",
|
|
8667
9019
|
description: "Tell a session something (fire and forget). Sent as this server's credential, not as an OpenCode session.",
|
|
8668
|
-
properties: { to: TO, text: { ...
|
|
9020
|
+
properties: { to: TO, text: { ...STRING9, description: "The message." }, ...SEND, idempotencyKey: IDEMPOTENCY3 },
|
|
8669
9021
|
required: ["to", "text"],
|
|
8670
9022
|
readOnly: false,
|
|
8671
9023
|
run: async (hub, args, signal) => {
|
|
8672
|
-
const to =
|
|
8673
|
-
const text =
|
|
9024
|
+
const to = need10(args, "to");
|
|
9025
|
+
const text = need10(args, "text");
|
|
8674
9026
|
const options = { ...sendOptions(args), ...idemOption(args), signal };
|
|
8675
9027
|
if (optionalBool(args, "all"))
|
|
8676
9028
|
return sentAllText(await hub.msg(to, text, { ...options, all: true }));
|
|
@@ -8682,7 +9034,7 @@ var TOOLS = [
|
|
|
8682
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.`,
|
|
8683
9035
|
properties: {
|
|
8684
9036
|
to: TO,
|
|
8685
|
-
text: { ...
|
|
9037
|
+
text: { ...STRING9, description: "The question." },
|
|
8686
9038
|
wait: { type: "number", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait for the answer (0–${WAIT_POLL_MAX_S}).` },
|
|
8687
9039
|
expires: { type: ["number", "string"], description: "When the ask expires: seconds, or 30s, 5m, 2h, 1d (1 minute to 7 days; default 24 h)." },
|
|
8688
9040
|
...SEND,
|
|
@@ -8691,8 +9043,8 @@ var TOOLS = [
|
|
|
8691
9043
|
required: ["to", "text"],
|
|
8692
9044
|
readOnly: false,
|
|
8693
9045
|
run: async (hub, args, signal) => {
|
|
8694
|
-
const to =
|
|
8695
|
-
const text =
|
|
9046
|
+
const to = need10(args, "to");
|
|
9047
|
+
const text = need10(args, "text");
|
|
8696
9048
|
const wait = optionalWait(args);
|
|
8697
9049
|
const expires = optionalExpires(args);
|
|
8698
9050
|
const options = { ...sendOptions(args), ...expires !== undefined ? { expires } : {}, ...idemOption(args), signal };
|
|
@@ -8710,12 +9062,12 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
8710
9062
|
{
|
|
8711
9063
|
name: "reply",
|
|
8712
9064
|
description: "Reply to a message you received; it goes to the message's real sender.",
|
|
8713
|
-
properties: { id: MSG_ID, text: { ...
|
|
9065
|
+
properties: { id: MSG_ID, text: { ...STRING9, description: "The reply." }, wake: SEND.wake },
|
|
8714
9066
|
required: ["id", "text"],
|
|
8715
9067
|
readOnly: false,
|
|
8716
9068
|
run: async (hub, args, signal) => {
|
|
8717
|
-
const id =
|
|
8718
|
-
const text =
|
|
9069
|
+
const id = need10(args, "id");
|
|
9070
|
+
const text = need10(args, "text");
|
|
8719
9071
|
const wake = optionalBool(args, "wake");
|
|
8720
9072
|
return repliedText(await hub.reply(id, text, { ...wake !== undefined ? { wake } : {}, signal }));
|
|
8721
9073
|
}
|
|
@@ -8723,12 +9075,12 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
8723
9075
|
{
|
|
8724
9076
|
name: "decline",
|
|
8725
9077
|
description: "Decline an ask you received, with a reason.",
|
|
8726
|
-
properties: { id: MSG_ID, why: { ...
|
|
9078
|
+
properties: { id: MSG_ID, why: { ...STRING9, description: "The reason." } },
|
|
8727
9079
|
required: ["id", "why"],
|
|
8728
9080
|
readOnly: false,
|
|
8729
9081
|
run: async (hub, args, signal) => {
|
|
8730
|
-
const id =
|
|
8731
|
-
return declinedText(await hub.decline(id,
|
|
9082
|
+
const id = need10(args, "id");
|
|
9083
|
+
return declinedText(await hub.decline(id, need10(args, "why"), { signal }));
|
|
8732
9084
|
}
|
|
8733
9085
|
},
|
|
8734
9086
|
{
|
|
@@ -8748,7 +9100,7 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
8748
9100
|
required: ["id"],
|
|
8749
9101
|
readOnly: true,
|
|
8750
9102
|
run: async (hub, args, signal) => {
|
|
8751
|
-
const id =
|
|
9103
|
+
const id = need10(args, "id");
|
|
8752
9104
|
const wait = optionalWait(args);
|
|
8753
9105
|
return messageText(await hub.message(id, { ...wait !== undefined ? { wait } : {}, signal }));
|
|
8754
9106
|
}
|
|
@@ -8761,7 +9113,7 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
8761
9113
|
readOnly: false,
|
|
8762
9114
|
organizes: true,
|
|
8763
9115
|
idempotent: true,
|
|
8764
|
-
run: async (hub, args, signal) => mutedText((await hub[verb](
|
|
9116
|
+
run: async (hub, args, signal) => mutedText((await hub[verb](need10(args, "addr"), { signal })).muted)
|
|
8765
9117
|
})),
|
|
8766
9118
|
{
|
|
8767
9119
|
name: "decisions",
|
|
@@ -8769,7 +9121,7 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
8769
9121
|
properties: { state: { type: "string", enum: [...DECISION_STATES], description: "Only Decisions in this state." } },
|
|
8770
9122
|
readOnly: true,
|
|
8771
9123
|
run: async (hub, args, signal) => {
|
|
8772
|
-
const state =
|
|
9124
|
+
const state = optional8(args, "state");
|
|
8773
9125
|
if (state !== undefined && !DECISION_STATES.includes(state))
|
|
8774
9126
|
throw new ArgError3(`"state" is one of ${DECISION_STATES.join(", ")}`);
|
|
8775
9127
|
const { decisions } = await hub.decisions({ ...state ? { state } : {}, signal });
|
|
@@ -8784,7 +9136,7 @@ ${decisionsText(decisions)}` : decisionsText(decisions);
|
|
|
8784
9136
|
required: ["id"],
|
|
8785
9137
|
readOnly: true,
|
|
8786
9138
|
run: async (hub, args, signal) => `${DECISIONS_NOTE}
|
|
8787
|
-
${decisionText((await hub.decision(
|
|
9139
|
+
${decisionText((await hub.decision(need10(args, "id"), { signal })).decision)}`
|
|
8788
9140
|
},
|
|
8789
9141
|
{
|
|
8790
9142
|
name: "approve",
|
|
@@ -8793,8 +9145,8 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8793
9145
|
required: ["addr", "requestID"],
|
|
8794
9146
|
readOnly: false,
|
|
8795
9147
|
run: async (hub, args, signal) => {
|
|
8796
|
-
const addr =
|
|
8797
|
-
const requestID =
|
|
9148
|
+
const addr = need10(args, "addr");
|
|
9149
|
+
const requestID = need10(args, "requestID");
|
|
8798
9150
|
const always = optionalBool(args, "always");
|
|
8799
9151
|
return approvedText(await hub.approve(addr, requestID, { ...always ? { always } : {}, ...whyOption(args), signal }));
|
|
8800
9152
|
}
|
|
@@ -8806,8 +9158,8 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8806
9158
|
required: ["addr", "requestID"],
|
|
8807
9159
|
readOnly: false,
|
|
8808
9160
|
run: async (hub, args, signal) => {
|
|
8809
|
-
const addr =
|
|
8810
|
-
const requestID =
|
|
9161
|
+
const addr = need10(args, "addr");
|
|
9162
|
+
const requestID = need10(args, "requestID");
|
|
8811
9163
|
return deniedText(await hub.deny(addr, requestID, { ...whyOption(args), signal }));
|
|
8812
9164
|
}
|
|
8813
9165
|
},
|
|
@@ -8824,8 +9176,8 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8824
9176
|
required: ["addr", "formID"],
|
|
8825
9177
|
readOnly: false,
|
|
8826
9178
|
run: async (hub, args, signal) => {
|
|
8827
|
-
const addr =
|
|
8828
|
-
const formID =
|
|
9179
|
+
const addr = need10(args, "addr");
|
|
9180
|
+
const formID = need10(args, "formID");
|
|
8829
9181
|
const given = formAnswer(args);
|
|
8830
9182
|
const options = { ...whyOption(args), signal };
|
|
8831
9183
|
return answeredText(given === "cancel" ? await hub.cancelForm(addr, formID, options) : await hub.answer(addr, formID, given, options), given === "cancel");
|
|
@@ -8836,15 +9188,15 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8836
9188
|
description: `Prompt a session (an agent's prompt carries the hub's provenance). ${NEEDS_USER}`,
|
|
8837
9189
|
properties: {
|
|
8838
9190
|
addr: ADDR2,
|
|
8839
|
-
text: { ...
|
|
9191
|
+
text: { ...STRING9, description: "The prompt." },
|
|
8840
9192
|
delivery: { type: "string", enum: [...DELIVERIES], description: "How it enters a busy session (the spoke picks when unset)." },
|
|
8841
9193
|
why: WHY
|
|
8842
9194
|
},
|
|
8843
9195
|
required: ["addr", "text"],
|
|
8844
9196
|
readOnly: false,
|
|
8845
9197
|
run: async (hub, args, signal) => {
|
|
8846
|
-
const addr =
|
|
8847
|
-
const text =
|
|
9198
|
+
const addr = need10(args, "addr");
|
|
9199
|
+
const text = need10(args, "text");
|
|
8848
9200
|
const delivery = optionalDelivery(args);
|
|
8849
9201
|
return promptedText(await hub.prompt(addr, text, { ...delivery ? { delivery } : {}, ...whyOption(args), signal }));
|
|
8850
9202
|
}
|
|
@@ -8855,32 +9207,32 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8855
9207
|
properties: { addr: ADDR2, why: WHY },
|
|
8856
9208
|
required: ["addr"],
|
|
8857
9209
|
readOnly: false,
|
|
8858
|
-
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 }))
|
|
8859
9211
|
},
|
|
8860
9212
|
{
|
|
8861
9213
|
name: "launch",
|
|
8862
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.",
|
|
8863
9215
|
properties: {
|
|
8864
|
-
repo: { ...
|
|
9216
|
+
repo: { ...STRING9, description: "Absolute path of the repository on the node (or where a clone goes)." },
|
|
8865
9217
|
mode: { type: "string", enum: [...WORKSPACE_MODES], description: "worktree (default: <repo>/.worktrees/<branch>, needs branch), checkout, or clone (needs remote)." },
|
|
8866
|
-
branch: { ...
|
|
8867
|
-
base: { ...
|
|
8868
|
-
remote: { ...
|
|
8869
|
-
key: { ...
|
|
8870
|
-
name: { ...
|
|
8871
|
-
roles: { type: "array", items:
|
|
8872
|
-
harness: { ...
|
|
8873
|
-
agent: { ...
|
|
8874
|
-
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." },
|
|
8875
9227
|
runtime: { type: "string", enum: [...RUNTIME_KINDS], description: "host (default) or openshell (a sandbox)." },
|
|
8876
|
-
profile: { ...
|
|
8877
|
-
machine: { ...
|
|
9228
|
+
profile: { ...STRING9, description: "The runtime's sandbox profile (needs runtime)." },
|
|
9229
|
+
machine: { ...STRING9, description: "The machine (name or ID) to run on." },
|
|
8878
9230
|
nodeLabels: { ...LABELS, description: 'Node labels to place on (e.g. {"os": "darwin"}).' },
|
|
8879
9231
|
prefer: { type: "boolean", description: "machine and nodeLabels are preferences, not requirements." },
|
|
8880
9232
|
labels: { ...LABELS, description: "Free-form labels for finding the launch and its session later (never interpreted by the hub)." },
|
|
8881
|
-
prompt: { ...
|
|
9233
|
+
prompt: { ...STRING9, description: "The session's first prompt." },
|
|
8882
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." },
|
|
8883
|
-
timeout: { ...
|
|
9235
|
+
timeout: { ...STRING9, description: "Longest run, e.g. 4h or 90m." },
|
|
8884
9236
|
cleanup: { type: "string", enum: [...CLEANUP_POLICIES], description: "keep (default) or remove the workspace when it ends." },
|
|
8885
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)." }
|
|
8886
9238
|
},
|
|
@@ -8896,12 +9248,12 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8896
9248
|
name: "launches",
|
|
8897
9249
|
description: "Your launches, oldest first (an app or session sees the ones it started).",
|
|
8898
9250
|
properties: {
|
|
8899
|
-
state: { ...
|
|
8900
|
-
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." }
|
|
8901
9253
|
},
|
|
8902
9254
|
readOnly: true,
|
|
8903
9255
|
run: async (hub, args, signal) => {
|
|
8904
|
-
const state =
|
|
9256
|
+
const state = optional8(args, "state")?.split(",").filter(Boolean);
|
|
8905
9257
|
const label = optionalStrings(args, "label");
|
|
8906
9258
|
return launchesText((await hub.launches.list({ ...state?.length ? { state } : {}, ...label ? { label } : {}, signal })).launches ?? []);
|
|
8907
9259
|
}
|
|
@@ -8912,7 +9264,7 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8912
9264
|
properties: { id: LAUNCH_ID },
|
|
8913
9265
|
required: ["id"],
|
|
8914
9266
|
readOnly: true,
|
|
8915
|
-
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)
|
|
8916
9268
|
},
|
|
8917
9269
|
{
|
|
8918
9270
|
name: "launch_stop",
|
|
@@ -8920,7 +9272,7 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8920
9272
|
properties: { id: LAUNCH_ID },
|
|
8921
9273
|
required: ["id"],
|
|
8922
9274
|
readOnly: false,
|
|
8923
|
-
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)
|
|
8924
9276
|
}
|
|
8925
9277
|
];
|
|
8926
9278
|
var toolList = () => TOOLS.map((t) => ({
|
|
@@ -9115,90 +9467,6 @@ async function watch(credentials, options, write, signal) {
|
|
|
9115
9467
|
throw new CliError(EXIT.other, "the watch stream ended (the hub closed it)", AGAIN);
|
|
9116
9468
|
}
|
|
9117
9469
|
|
|
9118
|
-
// packages/cli/src/grants.ts
|
|
9119
|
-
var GRANT_FLAGS = {
|
|
9120
|
-
json: "json",
|
|
9121
|
-
holder: "string",
|
|
9122
|
-
project: "multi",
|
|
9123
|
-
allow: "multi",
|
|
9124
|
-
permit: "multi",
|
|
9125
|
-
never: "multi",
|
|
9126
|
-
expires: "string",
|
|
9127
|
-
"max-per-hour": "string",
|
|
9128
|
-
"allow-always": "bool"
|
|
9129
|
-
};
|
|
9130
|
-
var GRANT_REQUEST_FLAGS = { ...GRANT_FLAGS, "permission-allow": "multi", why: "string" };
|
|
9131
|
-
var GRANT_USER_HINT = "only the user manages grants: use the console, or `hub login` with the user's device token";
|
|
9132
|
-
function userOnly(source, what) {
|
|
9133
|
-
if (source === "env")
|
|
9134
|
-
throw new CliError(EXIT.forbidden, `only the user can ${what} a grant, not a session`, GRANT_USER_HINT);
|
|
9135
|
-
}
|
|
9136
|
-
function grantError(error) {
|
|
9137
|
-
if (error instanceof CliError && error.exit === EXIT.forbidden)
|
|
9138
|
-
return new CliError(error.exit, error.message, [error.hint, GRANT_USER_HINT].filter(Boolean).join("; "), error.envelope);
|
|
9139
|
-
return error;
|
|
9140
|
-
}
|
|
9141
|
-
var listOf = (flags, name) => (flags[name] ?? []).flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
9142
|
-
async function grantSpecArg(flags, stdin) {
|
|
9143
|
-
return await grantArg(flags, stdin, true);
|
|
9144
|
-
}
|
|
9145
|
-
function grantRequestArg(flags, stdin) {
|
|
9146
|
-
return grantArg(flags, stdin, false);
|
|
9147
|
-
}
|
|
9148
|
-
async function grantArg(flags, stdin, holderRequired) {
|
|
9149
|
-
const json = typeof flags.json === "string" ? flags.json : undefined;
|
|
9150
|
-
const flagged = Object.keys(GRANT_REQUEST_FLAGS).filter((name) => name !== "json" && name !== "why" && flags[name] !== undefined);
|
|
9151
|
-
if (json !== undefined) {
|
|
9152
|
-
if (flagged.length)
|
|
9153
|
-
throw new UsageError(`give either --json '{…}' or ${flagged.map((f) => `--${f}`).join(", ")}`);
|
|
9154
|
-
return await jsonObjectArg(json, stdin, "grant");
|
|
9155
|
-
}
|
|
9156
|
-
const holder = str(flags, "holder");
|
|
9157
|
-
if (holder === undefined && holderRequired)
|
|
9158
|
-
throw new UsageError("needs --holder (ses_…, @name, @@orchestrator…, or app:<token id>)");
|
|
9159
|
-
const projects = listOf(flags, "project");
|
|
9160
|
-
if (!projects.length)
|
|
9161
|
-
throw new UsageError("needs --project <path> (repeatable)");
|
|
9162
|
-
const allow = listOf(flags, "allow");
|
|
9163
|
-
if (!allow.length)
|
|
9164
|
-
throw new UsageError("needs --allow <verbs> (approve, deny, answer, prompt, stop, roles, launch)");
|
|
9165
|
-
const permit = [...listOf(flags, "permit"), ...listOf(flags, "permission-allow")];
|
|
9166
|
-
const never = listOf(flags, "never");
|
|
9167
|
-
const expires = str(flags, "expires");
|
|
9168
|
-
const cap = str(flags, "max-per-hour");
|
|
9169
|
-
if (cap !== undefined && !/^\d+$/.test(cap))
|
|
9170
|
-
throw new UsageError("--max-per-hour is a whole number");
|
|
9171
|
-
return {
|
|
9172
|
-
...holder !== undefined ? { holder } : {},
|
|
9173
|
-
scope: { projects },
|
|
9174
|
-
allow,
|
|
9175
|
-
...permit.length || never.length ? { permissions: { ...permit.length ? { allow: permit } : {}, ...never.length ? { never } : {} } } : {},
|
|
9176
|
-
...expires !== undefined ? { expires } : {},
|
|
9177
|
-
...cap !== undefined ? { maxActionsPerHour: Number(cap) } : {},
|
|
9178
|
-
...flags["allow-always"] === true ? { allowAlways: true } : {}
|
|
9179
|
-
};
|
|
9180
|
-
}
|
|
9181
|
-
var iso3 = (ms) => new Date(ms).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
9182
|
-
var grantLine = (g) => [g.id, g.state, g.holder, g.allow.join(","), g.scope.projects.join(","), `until ${iso3(g.expiresAt)}`].join(" ");
|
|
9183
|
-
var grantsText = (grants) => grants.length ? grants.map(grantLine).join(`
|
|
9184
|
-
`) : "no grants";
|
|
9185
|
-
function grantText2(g) {
|
|
9186
|
-
const list = (values) => values.length ? values.join(", ") : "none";
|
|
9187
|
-
return [
|
|
9188
|
-
`${g.id} ${g.state}`,
|
|
9189
|
-
`holder: ${g.holder}${g.holderSessionID && g.holderSessionID !== g.holder ? ` (${g.holderSessionID})` : ""}`,
|
|
9190
|
-
`projects: ${list(g.scope.projects)}`,
|
|
9191
|
-
`verbs: ${list(g.allow)}`,
|
|
9192
|
-
`permissions allow: ${list(g.permissions.allow)}`,
|
|
9193
|
-
`permissions never: ${list(g.permissions.never)}`,
|
|
9194
|
-
`always: ${g.allowAlways ? "yes" : "no"}`,
|
|
9195
|
-
`max actions per hour: ${g.maxActionsPerHour}`,
|
|
9196
|
-
g.revokedAt !== undefined ? `revoked: ${iso3(g.revokedAt)}` : `expires: ${iso3(g.expiresAt)}`
|
|
9197
|
-
].join(`
|
|
9198
|
-
`);
|
|
9199
|
-
}
|
|
9200
|
-
var requestedText = (body) => `requested; waiting for the user (Decision ${body.decision.id})`;
|
|
9201
|
-
|
|
9202
9470
|
// packages/cli/src/node.ts
|
|
9203
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";
|
|
9204
9472
|
import path16 from "node:path";
|
|
@@ -9424,7 +9692,7 @@ import { arch as osArch, platform as osPlatform } from "node:os";
|
|
|
9424
9692
|
// packages/node/package.json
|
|
9425
9693
|
var package_default2 = {
|
|
9426
9694
|
name: "@huynle/hub-node",
|
|
9427
|
-
version: "0.
|
|
9695
|
+
version: "0.11.0",
|
|
9428
9696
|
private: true,
|
|
9429
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).",
|
|
9430
9698
|
license: "MIT",
|
|
@@ -10879,7 +11147,7 @@ import path14 from "node:path";
|
|
|
10879
11147
|
// packages/runtime-openshell/package.json
|
|
10880
11148
|
var package_default3 = {
|
|
10881
11149
|
name: "@huynle/hub-runtime-openshell",
|
|
10882
|
-
version: "0.
|
|
11150
|
+
version: "0.11.0",
|
|
10883
11151
|
private: true,
|
|
10884
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).",
|
|
10885
11153
|
license: "MIT",
|
|
@@ -12534,46 +12802,6 @@ function openShellCheckText(report) {
|
|
|
12534
12802
|
`);
|
|
12535
12803
|
}
|
|
12536
12804
|
|
|
12537
|
-
// packages/cli/src/subscriptions.ts
|
|
12538
|
-
var SUBSCRIBE_FLAGS = { type: "multi", delivery: "string", batch: "string", expires: "string", "max-per-hour": "string", subscriber: "string", note: "string" };
|
|
12539
|
-
var DELIVERIES2 = ["queue", "steer", "silent"];
|
|
12540
|
-
var safe2 = (v, max = 120) => sanitizeLabel(String(v), max);
|
|
12541
|
-
function subscribeInput(addrs, flags) {
|
|
12542
|
-
if (!addrs.length)
|
|
12543
|
-
throw new UsageError("needs at least one address (a session, @@role, #team or children)");
|
|
12544
|
-
const delivery = str(flags, "delivery");
|
|
12545
|
-
if (delivery !== undefined && !DELIVERIES2.includes(delivery))
|
|
12546
|
-
throw new UsageError(`--delivery is one of ${DELIVERIES2.join(", ")}`);
|
|
12547
|
-
const batch = str(flags, "batch");
|
|
12548
|
-
const expires = str(flags, "expires");
|
|
12549
|
-
const max = str(flags, "max-per-hour");
|
|
12550
|
-
if (max !== undefined && !/^\d+$/.test(max))
|
|
12551
|
-
throw new UsageError("--max-per-hour takes a whole number (1–120)");
|
|
12552
|
-
const types = flags.type;
|
|
12553
|
-
const subscriber = str(flags, "subscriber");
|
|
12554
|
-
const note = str(flags, "note");
|
|
12555
|
-
return {
|
|
12556
|
-
to: addrs.length === 1 ? addrs[0] : [...addrs],
|
|
12557
|
-
...types?.length ? { types: types.flatMap((t) => t.split(",")).map((t) => t.trim()).filter(Boolean) } : {},
|
|
12558
|
-
...delivery !== undefined ? { delivery } : {},
|
|
12559
|
-
...batch !== undefined ? { batch: durationArg(batch, "--batch") } : {},
|
|
12560
|
-
...expires !== undefined ? { expires: durationArg(expires, "--expires") } : {},
|
|
12561
|
-
...max !== undefined ? { maxPerHour: Number(max) } : {},
|
|
12562
|
-
...subscriber !== undefined ? { subscriber } : {},
|
|
12563
|
-
...note !== undefined ? { note } : {}
|
|
12564
|
-
};
|
|
12565
|
-
}
|
|
12566
|
-
var targets = (s) => s.targets.map((t) => t.kind === "children" ? "its subagents" : safe2(t.addr, 80)).join(", ");
|
|
12567
|
-
var iso4 = (ms) => new Date(ms).toISOString().slice(0, 16) + "Z";
|
|
12568
|
-
function subscriptionLine(s) {
|
|
12569
|
-
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`] : []];
|
|
12570
|
-
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)}"` : ""}`;
|
|
12571
|
-
}
|
|
12572
|
-
var subscribedText = (b) => `subscribed ${subscriptionLine(b.subscription)}
|
|
12573
|
-
the subscriber gets one hub message per batch window; stop with \`hub unsubscribe ${safe2(b.subscription.id, 40)}\``;
|
|
12574
|
-
var subscriptionsText = (b) => b.subscriptions.length ? b.subscriptions.map(subscriptionLine).join(`
|
|
12575
|
-
`) : "no subscriptions";
|
|
12576
|
-
|
|
12577
12805
|
// packages/claude-code/src/main.ts
|
|
12578
12806
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
12579
12807
|
import { realpathSync as realpathSync5 } from "node:fs";
|
|
@@ -14877,7 +15105,7 @@ import path23 from "node:path";
|
|
|
14877
15105
|
// packages/claude-code/package.json
|
|
14878
15106
|
var package_default4 = {
|
|
14879
15107
|
name: "@huynle/hub-claude-code",
|
|
14880
|
-
version: "0.
|
|
15108
|
+
version: "0.11.0",
|
|
14881
15109
|
private: true,
|
|
14882
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).",
|
|
14883
15111
|
license: "MIT",
|
|
@@ -15166,7 +15394,7 @@ import path26 from "node:path";
|
|
|
15166
15394
|
// packages/preset/package.json
|
|
15167
15395
|
var package_default5 = {
|
|
15168
15396
|
name: "@huynle/hub-preset",
|
|
15169
|
-
version: "0.
|
|
15397
|
+
version: "0.11.0",
|
|
15170
15398
|
private: true,
|
|
15171
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).",
|
|
15172
15400
|
license: "MIT",
|
|
@@ -15691,7 +15919,7 @@ import { openSync as openSync6, closeSync as closeSync6, readFileSync as readFil
|
|
|
15691
15919
|
// packages/codex/package.json
|
|
15692
15920
|
var package_default6 = {
|
|
15693
15921
|
name: "@huynle/hub-codex",
|
|
15694
|
-
version: "0.
|
|
15922
|
+
version: "0.11.0",
|
|
15695
15923
|
private: true,
|
|
15696
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).",
|
|
15697
15925
|
license: "MIT",
|
|
@@ -26715,7 +26943,7 @@ ${openedText(body.decision)}` : outcome.text);
|
|
|
26715
26943
|
"grant create": {
|
|
26716
26944
|
flags: GRANT_FLAGS,
|
|
26717
26945
|
async handle(context) {
|
|
26718
|
-
|
|
26946
|
+
userOnly2(context.credentials().source, "create");
|
|
26719
26947
|
const spec = await grantSpecArg(context.parsed.flags, context.io.stdin);
|
|
26720
26948
|
const body = await grantHub(context, (hub) => hub.createGrant(spec));
|
|
26721
26949
|
answer(context, body, (b) => `created ${grantText2(b.grant)}`);
|
|
@@ -26735,7 +26963,7 @@ ${openedText(body.decision)}` : outcome.text);
|
|
|
26735
26963
|
"grant revoke": {
|
|
26736
26964
|
args: ["<grantID>"],
|
|
26737
26965
|
async handle(context) {
|
|
26738
|
-
|
|
26966
|
+
userOnly2(context.credentials().source, "revoke");
|
|
26739
26967
|
const body = await grantHub(context, (hub) => hub.revokeGrant(context.parsed.args[0]));
|
|
26740
26968
|
answer(context, body, (b) => `revoked ${b.grant.id} (${b.grant.holder})`);
|
|
26741
26969
|
}
|