@huynle/hub 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -0
- package/README.md +36 -8
- package/package.json +1 -1
- package/packages/claude-code/package.json +1 -1
- package/packages/claude-code/src/sessions.ts +4 -1
- package/packages/cli/dist/hub-cli.js +851 -466
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/cli.ts +12 -7
- package/packages/cli/src/decisions.ts +52 -4
- package/packages/cli/src/format.ts +9 -0
- package/packages/cli/src/grants-mcp.ts +65 -0
- package/packages/cli/src/mcp-operations.ts +49 -0
- package/packages/cli/src/mcp.ts +26 -4
- package/packages/cli/src/subscriptions-mcp.ts +76 -0
- package/packages/cli/src/team-intake-mcp.ts +2 -1
- package/packages/cli/src/teams-mcp.ts +9 -3
- package/packages/codex/package.json +1 -1
- package/packages/codex/src/sessions.ts +14 -3
- package/packages/node/package.json +1 -1
- package/packages/preset/package.json +1 -1
- package/packages/protocol/package.json +1 -1
- package/packages/protocol/schema/api/v1/_root.json +119 -0
- package/packages/protocol/schema/api/v1/account.json +419 -0
- package/packages/protocol/schema/api/v1/decisions.json +635 -0
- package/packages/protocol/schema/api/v1/discovery.json +110 -0
- package/packages/protocol/schema/api/v1/events.json +679 -0
- package/packages/protocol/schema/api/v1/session-actions.json +660 -0
- package/packages/protocol/schema/api/v1/sessions.json +2055 -0
- package/packages/protocol/schema/api/v1/speech.json +492 -0
- package/packages/protocol/schema/api/v1/unspecified.json +282 -0
- package/packages/protocol/schema/app-delivery/v1.schema.json +67 -5
- package/packages/protocol/schema/app-front/v1.schema.json +7 -3
- package/packages/protocol/src/api-v1.ts +8 -0
- package/packages/protocol/src/index.ts +2 -0
- package/packages/protocol/src/teams.ts +16 -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/app-front.ts +2 -2
- package/packages/sdk/src/ask-fields.ts +76 -0
- package/packages/sdk/src/client.ts +24 -2
- package/packages/sdk/src/envelope.ts +3 -2
- package/packages/sdk/src/index.ts +1 -0
- package/packages/sdk/src/team-intake.ts +6 -0
- package/packages/sdk/src/teams.ts +4 -0
- package/packages/sdk/src/types.ts +36 -4
- package/packages/sdk/src/wake.ts +9 -4
- package/packages/spoke-sdk/package.json +1 -1
- package/src/central/agent/agent-limits.ts +185 -0
- package/src/central/agent/app-decisions.ts +6 -5
- package/src/central/agent/decisions.ts +136 -9
- package/src/central/agent/delivery.ts +164 -22
- package/src/central/agent/errors.ts +2 -1
- package/src/central/agent/events.ts +20 -2
- package/src/central/agent/grant-routes.ts +14 -1
- package/src/central/agent/grants.ts +23 -0
- package/src/central/agent/launch-engine.ts +9 -6
- package/src/central/agent/message-limits.ts +6 -0
- package/src/central/agent/message-routes.ts +33 -6
- package/src/central/agent/messages.ts +7 -0
- package/src/central/agent/placement.ts +4 -2
- package/src/central/agent/read-routes.ts +5 -0
- package/src/central/agent/routes.ts +13 -0
- package/src/central/agent/subscriptions.ts +15 -4
- package/src/central/api-v1.ts +115 -0
- package/src/central/artifacts/store.ts +4 -2
- package/src/central/concierge/board-results.ts +2 -1
- package/src/central/concierge/notifier.ts +4 -2
- package/src/central/config.ts +24 -3
- package/src/central/health/classify.ts +209 -0
- package/src/central/http.ts +380 -36
- package/src/central/recording/export.ts +57 -3
- package/src/central/recording/index.ts +236 -17
- package/src/central/recording/ingest.ts +11 -2
- package/src/central/recording/routes.ts +61 -7
- package/src/central/recording/schema.ts +68 -3
- 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/registry.ts +34 -3
- 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/budgets.ts +144 -0
- package/src/central/teams/hop-stalls.ts +80 -0
- package/src/central/teams/intake.ts +38 -6
- package/src/central/teams/involvement.ts +95 -0
- package/src/central/teams/item-decisions.ts +60 -0
- package/src/central/teams/item-eligibility.ts +35 -0
- package/src/central/teams/item-stalls.ts +87 -0
- package/src/central/teams/loops.ts +40 -0
- package/src/central/teams/policy.ts +10 -0
- package/src/central/teams/progress.ts +154 -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 +20 -1
- package/src/central/teams/validation.ts +16 -2
- package/src/central/workspaces/coordinator.ts +3 -1
- package/src/spoke/hub-tool.ts +26 -11
- package/web/dist/assets/{ArtifactPage-CsPjam8D.js → ArtifactPage-TbFJi59n.js} +1 -1
- package/web/dist/assets/{ArtifactPane-B9weO8L6.js → ArtifactPane-7D-M-Ich.js} +1 -1
- package/web/dist/assets/{ArtifactWorkbench-DzKhMs3l.js → ArtifactWorkbench-DlCLEjut.js} +1 -1
- package/web/dist/assets/{Diagram-CuRIffka.js → Diagram-PvOPetOI.js} +1 -1
- package/web/dist/assets/{Library-rv5DGtrm.js → Library-Cdi83pOV.js} +2 -2
- package/web/dist/assets/{ProfilesDestination-DUuQ9jmd.js → ProfilesDestination-cryC7P6j.js} +1 -1
- package/web/dist/assets/TeamsPage-B4xrlGhp.js +1 -0
- package/web/dist/assets/Watchtower-60B1JX_J.js +2 -0
- package/web/dist/assets/{artifacts-But87Jfo.js → artifacts-DlQEYmDl.js} +1 -1
- package/web/dist/assets/index-DA0gKIzv.js +33 -0
- package/web/dist/assets/{index-rW6LKQj1.css → index-Dyq-jBeE.css} +1 -1
- package/web/dist/assets/live-B2IfKm37.js +5 -0
- package/web/dist/assets/{live-BUofsQpz.css → live-De-kd3eW.css} +1 -1
- package/web/dist/assets/{pane-layout-BW0RdX5a.js → pane-layout-BAuu_pA3.js} +1 -1
- package/web/dist/assets/{renderer2d-DyANQ1WS.js → renderer2d-CsZnF3_o.js} +1 -1
- package/web/dist/assets/{renderer3d-vYYKFAJq.js → renderer3d-BIMA0wFn.js} +2 -2
- package/web/dist/assets/{safe-area-B2Bk__FK.js → safe-area-dtrnw9NF.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/TeamsPage-D_17MkvN.js +0 -1
- package/web/dist/assets/Watchtower-DlrNOJ7X.js +0 -2
- 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.1",
|
|
11
11
|
private: true,
|
|
12
12
|
description: "The hub CLI (`hub` / `hubctl`) for agents and scripts (internal; bundled into @huynle/hub, not published on its own).",
|
|
13
13
|
license: "MIT",
|
|
@@ -306,6 +306,12 @@ function showText(s) {
|
|
|
306
306
|
if (s.roles?.length)
|
|
307
307
|
field("roles", s.roles.map((r) => `@${at(r)}`).join(" "));
|
|
308
308
|
field("now", s.now);
|
|
309
|
+
if (s.health && s.health.kind !== "ok") {
|
|
310
|
+
const h = s.health;
|
|
311
|
+
const reset = typeof h.resetAt === "number" && Number.isFinite(h.resetAt) && Math.abs(h.resetAt) <= 8640000000000000 ? new Date(h.resetAt).toISOString() : undefined;
|
|
312
|
+
const facts = [h.kind, reset ? `resets ${reset}` : undefined, h.attempt !== undefined ? `attempt ${h.attempt}` : undefined].filter(Boolean).join(", ");
|
|
313
|
+
field("health", h.detail ? `${facts} — ${oneLine(h.detail, 200)}` : facts);
|
|
314
|
+
}
|
|
309
315
|
if (s.children)
|
|
310
316
|
field("subagents", s.children);
|
|
311
317
|
if (s.online === false && s.lastSeen !== undefined)
|
|
@@ -419,6 +425,74 @@ function parseAddress(addr) {
|
|
|
419
425
|
const scope = project ? { project } : {};
|
|
420
426
|
return role ? { kind: "role", role: match[1], ...scope } : { kind: "name", name: match[1], ...scope };
|
|
421
427
|
}
|
|
428
|
+
// packages/sdk/src/ask-fields.ts
|
|
429
|
+
var URGENCIES = ["low", "normal", "high"];
|
|
430
|
+
var TIERS = ["confirm", "tap"];
|
|
431
|
+
var KINDS = ["question", "plan", "merge", "release"];
|
|
432
|
+
var DECISION_ASK_PROPERTIES = {
|
|
433
|
+
options: {
|
|
434
|
+
type: "array",
|
|
435
|
+
items: { type: "object", properties: { id: { type: "string" }, label: { type: "string" }, safe: { type: "boolean" } }, required: ["id", "label"] },
|
|
436
|
+
description: "ask @me: buttons the user taps, [{id, label, safe?}] (at most 8; id: lowercase letters, digits, - or _)."
|
|
437
|
+
},
|
|
438
|
+
recommendation: {
|
|
439
|
+
type: "object",
|
|
440
|
+
properties: { option: { type: "string" }, why: { type: "string" } },
|
|
441
|
+
required: ["option"],
|
|
442
|
+
description: "ask @me: the option you recommend ({option: an option id, why})."
|
|
443
|
+
},
|
|
444
|
+
urgency: { type: "string", enum: [...URGENCIES], description: "ask @me: low, normal (default) or high." },
|
|
445
|
+
tier: { type: "string", enum: [...TIERS], description: "ask @me: confirm (default: any of the user's devices) or tap (only the signed-in console)." },
|
|
446
|
+
kind: { type: "string", enum: [...KINDS], description: "ask @me: what it is about: question (default), plan, merge or release." },
|
|
447
|
+
choices: {
|
|
448
|
+
type: "array",
|
|
449
|
+
items: {
|
|
450
|
+
type: "object",
|
|
451
|
+
properties: {
|
|
452
|
+
id: { type: "string" },
|
|
453
|
+
label: { type: "string" },
|
|
454
|
+
options: { type: "array", items: { type: "object", properties: { id: { type: "string" }, label: { type: "string" }, recommended: { type: "boolean" } }, required: ["id", "label"] } }
|
|
455
|
+
},
|
|
456
|
+
required: ["id", "label", "options"]
|
|
457
|
+
},
|
|
458
|
+
description: 'ask @me: several points in one ask, instead of options: [{id: "D1", label, options: [{id, label, recommended?}]}], 1–12 groups of 2–6 options, at most one recommended each; the user picks one option per group and you get one "D1: label [a]" line per group.'
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
var DECISION_ASK_FIELDS = Object.keys(DECISION_ASK_PROPERTIES);
|
|
462
|
+
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
463
|
+
function decisionAskFields(args) {
|
|
464
|
+
const out = {};
|
|
465
|
+
const { options, recommendation, urgency, tier, kind, choices } = args;
|
|
466
|
+
const objects = (value, field, shape) => {
|
|
467
|
+
if (!Array.isArray(value) || !value.every(isObject))
|
|
468
|
+
throw new Error(`"${field}" must be a list of ${shape}`);
|
|
469
|
+
return value;
|
|
470
|
+
};
|
|
471
|
+
if (options !== undefined)
|
|
472
|
+
out.options = objects(options, "options", "{id, label, safe?}");
|
|
473
|
+
if (recommendation !== undefined) {
|
|
474
|
+
if (!isObject(recommendation) || typeof recommendation.option !== "string")
|
|
475
|
+
throw new Error(`"recommendation" must be {option, why?}`);
|
|
476
|
+
out.recommendation = recommendation;
|
|
477
|
+
}
|
|
478
|
+
const oneOf = (value, field, allowed) => {
|
|
479
|
+
if (value === undefined)
|
|
480
|
+
return;
|
|
481
|
+
if (typeof value !== "string" || !allowed.includes(value))
|
|
482
|
+
throw new Error(`"${field}" is one of ${allowed.join(", ")}`);
|
|
483
|
+
out[field] = value;
|
|
484
|
+
};
|
|
485
|
+
oneOf(urgency, "urgency", URGENCIES);
|
|
486
|
+
oneOf(tier, "tier", TIERS);
|
|
487
|
+
oneOf(kind, "kind", KINDS);
|
|
488
|
+
if (choices !== undefined) {
|
|
489
|
+
const shape = "{id, label, options: [{id, label, recommended?}]}";
|
|
490
|
+
for (const group of objects(choices, "choices", shape))
|
|
491
|
+
objects(group.options, "choices", shape);
|
|
492
|
+
out.choices = choices;
|
|
493
|
+
}
|
|
494
|
+
return out;
|
|
495
|
+
}
|
|
422
496
|
// packages/sdk/src/errors.ts
|
|
423
497
|
class HubError extends Error {
|
|
424
498
|
status;
|
|
@@ -843,7 +917,7 @@ var segment = (value) => encodeURIComponent(value).replace(/%40/g, "@");
|
|
|
843
917
|
var bareRole = (role) => role.replace(/^@@/, "");
|
|
844
918
|
var decisionFields = (options) => {
|
|
845
919
|
const out = {};
|
|
846
|
-
for (const key of ["options", "recommendation", "urgency", "tier", "deadline", "onTimeout", "about"])
|
|
920
|
+
for (const key of ["options", "recommendation", "choices", "kind", "urgency", "tier", "deadline", "onTimeout", "about"])
|
|
847
921
|
if (options[key] !== undefined)
|
|
848
922
|
out[key] = options[key];
|
|
849
923
|
return out;
|
|
@@ -852,6 +926,7 @@ var whyField = (options) => options.why !== undefined ? { why: options.why } : {
|
|
|
852
926
|
var signalOf = (options) => options.signal ? { signal: options.signal } : {};
|
|
853
927
|
var idempotent = (options) => ({ ...signalOf(options), ...options.idempotencyKey ? { headers: { "idempotency-key": options.idempotencyKey } } : {} });
|
|
854
928
|
var sendFields = (options) => ({
|
|
929
|
+
...options.item !== undefined ? { item: options.item } : {},
|
|
855
930
|
...options.wake !== undefined ? { wake: options.wake } : {},
|
|
856
931
|
...options.urgent !== undefined ? { urgent: options.urgent } : {},
|
|
857
932
|
...options.notAfter !== undefined ? { notAfter: options.notAfter } : {}
|
|
@@ -1162,6 +1237,15 @@ class HubClient {
|
|
|
1162
1237
|
grants(options = {}) {
|
|
1163
1238
|
return this.call("GET", `${AGENT2}/grants`, signalOf(options));
|
|
1164
1239
|
}
|
|
1240
|
+
heldGrants(options = {}) {
|
|
1241
|
+
return this.call("GET", `${AGENT2}/grants/mine`, signalOf(options));
|
|
1242
|
+
}
|
|
1243
|
+
heldGrant(id, options = {}) {
|
|
1244
|
+
return this.call("GET", `${AGENT2}/grants/mine/${segment(id)}`, signalOf(options));
|
|
1245
|
+
}
|
|
1246
|
+
revokeHeldGrant(id, options = {}) {
|
|
1247
|
+
return this.call("DELETE", `${AGENT2}/grants/mine/${segment(id)}`, signalOf(options));
|
|
1248
|
+
}
|
|
1165
1249
|
grant(id, options = {}) {
|
|
1166
1250
|
return this.call("GET", `${AGENT2}/grants/${segment(id)}`, signalOf(options));
|
|
1167
1251
|
}
|
|
@@ -1191,7 +1275,7 @@ class HubClient {
|
|
|
1191
1275
|
// packages/sdk/src/envelope.ts
|
|
1192
1276
|
var LABEL_MAX = 120;
|
|
1193
1277
|
var ANSI = /\u001b\[[0-?]*[ -/]*[@-~]|\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)?|\u001b[P^_][^\u001b]*(?:\u001b\\)?|\u001b[ -/]*[0-~]?|\u009b[0-?]*[ -/]*[@-~]/g;
|
|
1194
|
-
var CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u206f\ufeff]/
|
|
1278
|
+
var CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u206f\ufeff\p{Cf}]/gu;
|
|
1195
1279
|
function sanitizeText(text) {
|
|
1196
1280
|
return text.replace(/\r\n?/g, `
|
|
1197
1281
|
`).replace(ANSI, "").replace(CONTROL, "");
|
|
@@ -1238,13 +1322,16 @@ function renderEnvelope(input) {
|
|
|
1238
1322
|
var WAKE_SKIP_TEXT = {
|
|
1239
1323
|
wake_false: "sent without a wake",
|
|
1240
1324
|
subagent: "subagents are never woken",
|
|
1241
|
-
hop_cap: "hop limit reached (
|
|
1242
|
-
budget: "wake budget spent
|
|
1325
|
+
hop_cap: "hop limit reached (too many agent messages without a human turn)",
|
|
1326
|
+
budget: "wake budget spent",
|
|
1327
|
+
item_budget: "item wake budget spent",
|
|
1328
|
+
item_loop: "this pair is looping on the item without progress; the user decides"
|
|
1243
1329
|
};
|
|
1244
|
-
function wakeSkipNote({ delivery, woke, wakeSkipped }) {
|
|
1330
|
+
function wakeSkipNote({ delivery, woke, wakeSkipped, wakeLimit }) {
|
|
1245
1331
|
if (woke !== false || !wakeSkipped || delivery === "steer" || delivery === "turn")
|
|
1246
1332
|
return "";
|
|
1247
|
-
|
|
1333
|
+
const detail = wakeLimit !== undefined && Number.isFinite(wakeLimit) && wakeLimit >= 1 ? wakeSkipped === "hop_cap" ? `hop limit reached (${wakeLimit} agent messages without a human turn)` : wakeSkipped === "budget" ? `wake budget spent (${wakeLimit} per 10 minutes)` : wakeSkipped === "item_budget" ? `item wake budget spent (${wakeLimit} wakes)` : undefined : undefined;
|
|
1334
|
+
return ` — wake skipped: ${detail ?? WAKE_SKIP_TEXT[wakeSkipped] ?? wakeSkipped}`;
|
|
1248
1335
|
}
|
|
1249
1336
|
// packages/cli/src/http.ts
|
|
1250
1337
|
var EXIT = {
|
|
@@ -4325,6 +4412,19 @@ var RECORDING_LIMITS = {
|
|
|
4325
4412
|
hubFlushMs: 2000,
|
|
4326
4413
|
hubFlushEvents: 100
|
|
4327
4414
|
};
|
|
4415
|
+
var DEV_LIMITS = {
|
|
4416
|
+
clipsPerRecording: 200,
|
|
4417
|
+
clipPreMs: 3000,
|
|
4418
|
+
clipPostMs: 2000,
|
|
4419
|
+
clipBytes: 192 * 1024,
|
|
4420
|
+
clipGapMs: 2000,
|
|
4421
|
+
repliesPerRecording: 2000,
|
|
4422
|
+
replyBytes: 1024 * 1024,
|
|
4423
|
+
userHeadroomBytes: 50 * 1024 * 1024,
|
|
4424
|
+
recordingHeadroomBytes: 15 * 1024 * 1024,
|
|
4425
|
+
tracesPerMinute: 30,
|
|
4426
|
+
logsPerMinute: 20
|
|
4427
|
+
};
|
|
4328
4428
|
// packages/protocol/src/app-front.ts
|
|
4329
4429
|
var ACCESS_TOKEN_TTL_MS = 60 * 60000;
|
|
4330
4430
|
var REFRESH_TOKEN_IDLE_MS = 30 * 24 * 60 * 60000;
|
|
@@ -4846,7 +4946,7 @@ function readConfig(file, machineName) {
|
|
|
4846
4946
|
return defaults(machineName);
|
|
4847
4947
|
}
|
|
4848
4948
|
}
|
|
4849
|
-
var
|
|
4949
|
+
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4850
4950
|
function readRawConfig(file) {
|
|
4851
4951
|
let text;
|
|
4852
4952
|
try {
|
|
@@ -4862,7 +4962,7 @@ function readRawConfig(file) {
|
|
|
4862
4962
|
} catch {
|
|
4863
4963
|
parsed = undefined;
|
|
4864
4964
|
}
|
|
4865
|
-
if (!
|
|
4965
|
+
if (!isObject2(parsed))
|
|
4866
4966
|
throw new Error(`${file} is not a JSON object; fix or remove it first`);
|
|
4867
4967
|
return parsed;
|
|
4868
4968
|
}
|
|
@@ -5296,7 +5396,7 @@ class ProfileError extends RuntimeError {
|
|
|
5296
5396
|
this.code = code;
|
|
5297
5397
|
}
|
|
5298
5398
|
}
|
|
5299
|
-
var
|
|
5399
|
+
var isObject3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5300
5400
|
var show = (v) => JSON.stringify(v)?.slice(0, 80) ?? String(v);
|
|
5301
5401
|
var builtinDefault = () => ({ name: DEFAULT_PROFILE, providers: [], endpoints: [], allowGitRemote: false });
|
|
5302
5402
|
function egressHost(v) {
|
|
@@ -5320,7 +5420,7 @@ function egressEndpoint(v) {
|
|
|
5320
5420
|
return;
|
|
5321
5421
|
host = v.slice(0, colon);
|
|
5322
5422
|
port = Number(v.slice(colon + 1));
|
|
5323
|
-
} else if (
|
|
5423
|
+
} else if (isObject3(v)) {
|
|
5324
5424
|
host = v.host;
|
|
5325
5425
|
port = v.port;
|
|
5326
5426
|
}
|
|
@@ -5377,9 +5477,9 @@ function normalizeOpenShellProfiles(raw) {
|
|
|
5377
5477
|
const problems = [];
|
|
5378
5478
|
const profiles = { [DEFAULT_PROFILE]: builtinDefault() };
|
|
5379
5479
|
const unsupported = {};
|
|
5380
|
-
if (raw !== undefined && !
|
|
5480
|
+
if (raw !== undefined && !isObject3(raw))
|
|
5381
5481
|
problems.push("openshell.profiles must be an object of profile name to profile");
|
|
5382
|
-
let entries = Object.entries(
|
|
5482
|
+
let entries = Object.entries(isObject3(raw) ? raw : {});
|
|
5383
5483
|
if (entries.length > PROFILES_MAX) {
|
|
5384
5484
|
problems.push(`openshell.profiles: at most ${PROFILES_MAX} profiles; the rest are ignored`);
|
|
5385
5485
|
entries = entries.slice(0, PROFILES_MAX);
|
|
@@ -5389,7 +5489,7 @@ function normalizeOpenShellProfiles(raw) {
|
|
|
5389
5489
|
problems.push(`openshell.profiles: ${show(name)} is not a profile name (lowercase letters, digits, . _ -; at most ${NAME_MAX})`);
|
|
5390
5490
|
continue;
|
|
5391
5491
|
}
|
|
5392
|
-
if (!
|
|
5492
|
+
if (!isObject3(value)) {
|
|
5393
5493
|
problems.push(`openshell.profiles.${name} must be an object`);
|
|
5394
5494
|
continue;
|
|
5395
5495
|
}
|
|
@@ -5823,7 +5923,7 @@ var HARNESS = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
|
5823
5923
|
function nodeConfigPath(env = process.env, home = homedir2()) {
|
|
5824
5924
|
return path9.join(path9.dirname(configPath(env, home)), NODE_CONFIG_FILE);
|
|
5825
5925
|
}
|
|
5826
|
-
var
|
|
5926
|
+
var isObject4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5827
5927
|
var whole = (v, min, max) => typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
|
|
5828
5928
|
var show2 = (v) => JSON.stringify(v)?.slice(0, 80) ?? String(v);
|
|
5829
5929
|
function configPathOf(raw, options) {
|
|
@@ -5845,7 +5945,7 @@ var isRoot = (p) => p === "/" || /^[A-Z]:\/$/.test(p);
|
|
|
5845
5945
|
function normalizeNodeConfig(raw, options = {}) {
|
|
5846
5946
|
const problems = [];
|
|
5847
5947
|
let r = {};
|
|
5848
|
-
if (
|
|
5948
|
+
if (isObject4(raw))
|
|
5849
5949
|
r = raw;
|
|
5850
5950
|
else if (raw !== undefined)
|
|
5851
5951
|
problems.push("node.json must hold a JSON object");
|
|
@@ -5937,7 +6037,7 @@ function loopbackOrigin(raw) {
|
|
|
5937
6037
|
return url.origin;
|
|
5938
6038
|
}
|
|
5939
6039
|
function normalizeOpenCode(raw, problems, options) {
|
|
5940
|
-
if (!
|
|
6040
|
+
if (!isObject4(raw)) {
|
|
5941
6041
|
problems.push("opencode must be an object");
|
|
5942
6042
|
return;
|
|
5943
6043
|
}
|
|
@@ -5948,7 +6048,7 @@ function normalizeOpenCode(raw, problems, options) {
|
|
|
5948
6048
|
const list = Array.isArray(raw.servers) ? raw.servers : [];
|
|
5949
6049
|
list.forEach((entry, i) => {
|
|
5950
6050
|
const field = `opencode.servers[${i}]`;
|
|
5951
|
-
if (!
|
|
6051
|
+
if (!isObject4(entry))
|
|
5952
6052
|
return void problems.push(`${field} must be an object {url, passwordFile, directories?}`);
|
|
5953
6053
|
const url = loopbackOrigin(entry.url);
|
|
5954
6054
|
if (!url)
|
|
@@ -5976,7 +6076,7 @@ function normalizeOpenCode(raw, problems, options) {
|
|
|
5976
6076
|
return { ...directories ? { directories } : {}, servers, ...timeoutSeconds !== undefined ? { timeoutSeconds } : {} };
|
|
5977
6077
|
}
|
|
5978
6078
|
function normalizeHarnesses(raw, problems) {
|
|
5979
|
-
if (!
|
|
6079
|
+
if (!isObject4(raw)) {
|
|
5980
6080
|
problems.push("harnesses must be an object of harness names to true or false");
|
|
5981
6081
|
return;
|
|
5982
6082
|
}
|
|
@@ -5992,7 +6092,7 @@ function normalizeHarnesses(raw, problems) {
|
|
|
5992
6092
|
return out;
|
|
5993
6093
|
}
|
|
5994
6094
|
function normalizeOpenShell(raw, problems) {
|
|
5995
|
-
if (!
|
|
6095
|
+
if (!isObject4(raw)) {
|
|
5996
6096
|
problems.push("openshell must be an object");
|
|
5997
6097
|
return;
|
|
5998
6098
|
}
|
|
@@ -6234,10 +6334,13 @@ var DECISION_FLAGS = {
|
|
|
6234
6334
|
deadline: "string",
|
|
6235
6335
|
"on-timeout": "string",
|
|
6236
6336
|
tier: "string",
|
|
6237
|
-
about: "multi"
|
|
6337
|
+
about: "multi",
|
|
6338
|
+
choice: "multi",
|
|
6339
|
+
kind: "string"
|
|
6238
6340
|
};
|
|
6239
|
-
var
|
|
6240
|
-
var
|
|
6341
|
+
var URGENCIES2 = ["low", "normal", "high"];
|
|
6342
|
+
var KINDS2 = ["question", "plan", "merge", "release"];
|
|
6343
|
+
var TIERS2 = ["confirm", "tap"];
|
|
6241
6344
|
var STATES = ["open", "answered", "expired", "closed"];
|
|
6242
6345
|
var DECISION_STATES = STATES;
|
|
6243
6346
|
function optionArg(value) {
|
|
@@ -6253,6 +6356,37 @@ function optionArg(value) {
|
|
|
6253
6356
|
throw new UsageError(`--option ${id}: the label is empty`);
|
|
6254
6357
|
return { id, label, ...safe ? { safe: true } : {} };
|
|
6255
6358
|
}
|
|
6359
|
+
function choiceArg(value) {
|
|
6360
|
+
const usage = `--choice takes GROUP=Label:a=Option*,b=Option (got "${value}")`;
|
|
6361
|
+
const eq = value.indexOf("=");
|
|
6362
|
+
if (eq <= 0)
|
|
6363
|
+
throw new UsageError(usage);
|
|
6364
|
+
const id = value.slice(0, eq);
|
|
6365
|
+
const rest = value.slice(eq + 1);
|
|
6366
|
+
let split = -1;
|
|
6367
|
+
for (let i = rest.lastIndexOf(":");i >= 0; i = rest.lastIndexOf(":", i - 1)) {
|
|
6368
|
+
if (/^[^=,:\s][^=,:]*=/.test(rest.slice(i + 1))) {
|
|
6369
|
+
split = i;
|
|
6370
|
+
break;
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
if (split < 0)
|
|
6374
|
+
throw new UsageError(usage);
|
|
6375
|
+
const label = rest.slice(0, split);
|
|
6376
|
+
if (!label.trim())
|
|
6377
|
+
throw new UsageError(`--choice ${id}: the label is empty`);
|
|
6378
|
+
const options = rest.slice(split + 1).split(",").map((option) => {
|
|
6379
|
+
const at = option.indexOf("=");
|
|
6380
|
+
if (at <= 0)
|
|
6381
|
+
throw new UsageError(usage);
|
|
6382
|
+
const recommended = option.endsWith("*");
|
|
6383
|
+
const text = option.slice(at + 1, recommended ? -1 : undefined);
|
|
6384
|
+
if (!text.trim())
|
|
6385
|
+
throw new UsageError(`--choice ${id}: option ${option.slice(0, at)} has an empty label`);
|
|
6386
|
+
return { id: option.slice(0, at), label: text, ...recommended ? { recommended: true } : {} };
|
|
6387
|
+
});
|
|
6388
|
+
return { id, label, options };
|
|
6389
|
+
}
|
|
6256
6390
|
var oneOf2 = (value, allowed, flag) => {
|
|
6257
6391
|
if (value !== undefined && !allowed.includes(value))
|
|
6258
6392
|
throw new UsageError(`${flag} is one of ${allowed.join(", ")}`);
|
|
@@ -6269,13 +6403,19 @@ function decisionOptions(flags, to) {
|
|
|
6269
6403
|
if (why !== undefined && recommend === undefined)
|
|
6270
6404
|
throw new UsageError("--why explains --recommend");
|
|
6271
6405
|
const deadline = str(flags, "deadline");
|
|
6272
|
-
const urgency = oneOf2(str(flags, "urgency"),
|
|
6273
|
-
const tier = oneOf2(str(flags, "tier"),
|
|
6406
|
+
const urgency = oneOf2(str(flags, "urgency"), URGENCIES2, "--urgency");
|
|
6407
|
+
const tier = oneOf2(str(flags, "tier"), TIERS2, "--tier");
|
|
6274
6408
|
const onTimeout = str(flags, "on-timeout");
|
|
6275
6409
|
const options = flags.option;
|
|
6276
6410
|
const about = flags.about;
|
|
6411
|
+
const choices = flags.choice;
|
|
6412
|
+
if (choices && options)
|
|
6413
|
+
throw new UsageError("--choice and --option don't mix: give one or the other");
|
|
6414
|
+
const kind = oneOf2(str(flags, "kind"), KINDS2, "--kind");
|
|
6277
6415
|
return {
|
|
6278
6416
|
...options ? { options: options.map(optionArg) } : {},
|
|
6417
|
+
...choices ? { choices: choices.map(choiceArg) } : {},
|
|
6418
|
+
...kind ? { kind } : {},
|
|
6279
6419
|
...recommend !== undefined ? { recommendation: { option: recommend, ...why !== undefined ? { why } : {} } } : {},
|
|
6280
6420
|
...urgency ? { urgency } : {},
|
|
6281
6421
|
...tier ? { tier } : {},
|
|
@@ -6289,6 +6429,10 @@ function stateArg(value) {
|
|
|
6289
6429
|
}
|
|
6290
6430
|
var iso2 = (ms) => new Date(ms).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
6291
6431
|
function choicesText(decision) {
|
|
6432
|
+
if (decision.choices?.length) {
|
|
6433
|
+
return decision.choices.map((g) => `${g.id} ${oneLine(g.label, 120)}: ${g.options.map((o) => `${o.id} ${oneLine(o.label, 80)}${o.recommended ? " (recommended)" : ""}`).join(" · ")}`).join(`
|
|
6434
|
+
`);
|
|
6435
|
+
}
|
|
6292
6436
|
const options = decision.options.map((o) => `${o.id} ${oneLine(o.label, 80)}${o.safe ? " (safe)" : ""}`).join(" · ");
|
|
6293
6437
|
const r = decision.recommendation;
|
|
6294
6438
|
const recommended = r ? `recommended: ${r.option}${r.why ? ` (${oneLine(r.why, 200)})` : ""}` : "";
|
|
@@ -6297,7 +6441,8 @@ function choicesText(decision) {
|
|
|
6297
6441
|
function stateText(decision) {
|
|
6298
6442
|
if (decision.state === "answered") {
|
|
6299
6443
|
const a = decision.answer;
|
|
6300
|
-
|
|
6444
|
+
const picks = a?.picks ? ` ${Object.entries(a.picks).map(([group, option]) => `${group}=${option}`).join(" ")}` : "";
|
|
6445
|
+
return `answered${a?.option ? ` ${a.option}` : ""}${picks}${a?.answeredBy === "timeout" ? " (timeout)" : ""}`;
|
|
6301
6446
|
}
|
|
6302
6447
|
if (decision.state === "open" && decision.deadline !== undefined)
|
|
6303
6448
|
return `open (deadline ${iso2(decision.deadline)})`;
|
|
@@ -6474,7 +6619,7 @@ class ToolArgError extends Error {
|
|
|
6474
6619
|
|
|
6475
6620
|
// packages/cli/src/artifacts-mcp.ts
|
|
6476
6621
|
var MCP_INLINE_TEXT_BYTES = 256 * 1024;
|
|
6477
|
-
var
|
|
6622
|
+
var KINDS3 = ["markdown", "code", "image", "table", "chart", "dashboard", "html", "app", "file"];
|
|
6478
6623
|
var DEFAULT_MEDIA = { markdown: "text/markdown", html: "text/html", table: "text/csv", chart: "application/json", dashboard: "application/json" };
|
|
6479
6624
|
var PATH3 = "/api/agent/artifacts";
|
|
6480
6625
|
var STRING = { type: "string" };
|
|
@@ -6570,7 +6715,7 @@ var artifactTools = [
|
|
|
6570
6715
|
description: "Create a persistent artifact with its first revision from small inline text or references to staged bytes. Needs scope artifacts:write for an app token.",
|
|
6571
6716
|
properties: {
|
|
6572
6717
|
title: STRING,
|
|
6573
|
-
kind: { type: "string", enum:
|
|
6718
|
+
kind: { type: "string", enum: KINDS3 },
|
|
6574
6719
|
description: STRING,
|
|
6575
6720
|
visibility: { type: "string", enum: ["private", "workspace"] },
|
|
6576
6721
|
workspaceID: STRING,
|
|
@@ -6582,8 +6727,8 @@ var artifactTools = [
|
|
|
6582
6727
|
run: async (hub, args, signal) => {
|
|
6583
6728
|
const title = need(args, "title");
|
|
6584
6729
|
const kind = need(args, "kind");
|
|
6585
|
-
if (!
|
|
6586
|
-
throw new ToolArgError(`"kind" must be one of ${
|
|
6730
|
+
if (!KINDS3.includes(kind))
|
|
6731
|
+
throw new ToolArgError(`"kind" must be one of ${KINDS3.join(", ")}`);
|
|
6587
6732
|
const body = files(args, kind);
|
|
6588
6733
|
const description = optional2(args, "description");
|
|
6589
6734
|
const visibility = optional2(args, "visibility");
|
|
@@ -6618,7 +6763,7 @@ var STRING2 = { type: "string" };
|
|
|
6618
6763
|
var ID3 = { type: "string", description: "The artifact's ID (art_…)." };
|
|
6619
6764
|
var JOB = { type: "string", description: "The job's ID (job_…)." };
|
|
6620
6765
|
var REVISION = { type: "integer", description: "The job revision you read (from artifact_job)." };
|
|
6621
|
-
var
|
|
6766
|
+
var KINDS4 = ["ask", "propose-edit", "run-action"];
|
|
6622
6767
|
var need2 = (args, key) => {
|
|
6623
6768
|
const value = args[key];
|
|
6624
6769
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -6695,7 +6840,7 @@ var artifactJobTools = [
|
|
|
6695
6840
|
id: ID3,
|
|
6696
6841
|
baseRevisionID: { type: "string", description: "The revision the job is about (propose-edit: the latest)." },
|
|
6697
6842
|
instruction: { type: "string", description: "What to do (plain text, at most 8000 characters)." },
|
|
6698
|
-
kind: { type: "string", enum:
|
|
6843
|
+
kind: { type: "string", enum: KINDS4 },
|
|
6699
6844
|
action: { type: "string", description: "run-action: one of the artifact's declared actions." },
|
|
6700
6845
|
machine: { type: "string", description: "Launch a handler session on this machine…" },
|
|
6701
6846
|
repo: { type: "string", description: "…in this absolute repository path." },
|
|
@@ -6710,8 +6855,8 @@ var artifactJobTools = [
|
|
|
6710
6855
|
readOnly: false,
|
|
6711
6856
|
run: async (hub, args, signal) => {
|
|
6712
6857
|
const kind = optional3(args, "kind");
|
|
6713
|
-
if (kind && !
|
|
6714
|
-
throw new ToolArgError(`"kind" must be one of ${
|
|
6858
|
+
if (kind && !KINDS4.includes(kind))
|
|
6859
|
+
throw new ToolArgError(`"kind" must be one of ${KINDS4.join(", ")}`);
|
|
6715
6860
|
const action = optional3(args, "action");
|
|
6716
6861
|
const apply = optional3(args, "apply");
|
|
6717
6862
|
const key = optional3(args, "key");
|
|
@@ -6883,7 +7028,7 @@ function notAfterOf(args) {
|
|
|
6883
7028
|
}
|
|
6884
7029
|
throw new ToolArgError(`"notAfter" must be epoch milliseconds or an ISO 8601 date-time like 2027-01-15T12:00:00Z`);
|
|
6885
7030
|
}
|
|
6886
|
-
var line4 = (i) => `${i.id} ${i.status}${i.progress ? `/${i.progress}` : ""} ${i.priority} r${i.revision} ${sanitizeText(i.title).replace(/\s+/g, " ")}`;
|
|
7031
|
+
var line4 = (i) => `${i.id} ${i.status}${i.progress ? `/${i.progress}` : ""} ${i.priority} r${i.revision} ${sanitizeText(i.title).replace(/\s+/g, " ")}${i.usage ? ` wakes ${i.usage.wakes}/${i.usage.budget.wakes} · ${i.usage.hours.toFixed(1)}/${i.usage.budget.hours}h` : ""}`;
|
|
6887
7032
|
function intakeText(i) {
|
|
6888
7033
|
const from = i.source.kind === "user" ? "the user" : i.source.kind === "app" ? `app ${i.source.tokenID}` : `session ${i.source.sessionID}`;
|
|
6889
7034
|
return [
|
|
@@ -6897,7 +7042,8 @@ function intakeText(i) {
|
|
|
6897
7042
|
...i.criteria?.length ? ["done means:", ...i.criteria.map((c) => `- ${sanitizeText(c)}`)] : [],
|
|
6898
7043
|
...i.ref ? [`ref: ${sanitizeText(i.ref)}`] : [],
|
|
6899
7044
|
...i.reason ? [`reason: ${sanitizeText(i.reason)}`] : [],
|
|
6900
|
-
...i.lastError ? [`last routing error: ${i.lastError} (retried)`] : []
|
|
7045
|
+
...i.lastError ? [`last routing error: ${i.lastError} (retried)`] : [],
|
|
7046
|
+
...i.usage ? [`usage: ${i.usage.wakes}/${i.usage.budget.wakes} wakes · ${i.usage.hours.toFixed(1)}/${i.usage.budget.hours} hours (paused and blocked time counts)${i.usage.chargedTo ? `; last charged to ${i.usage.chargedTo}` : ""}`] : []
|
|
6901
7047
|
].join(`
|
|
6902
7048
|
`);
|
|
6903
7049
|
}
|
|
@@ -6984,35 +7130,215 @@ ${intakeText(intake)}`;
|
|
|
6984
7130
|
}
|
|
6985
7131
|
];
|
|
6986
7132
|
|
|
6987
|
-
// packages/cli/src/
|
|
7133
|
+
// packages/cli/src/subscriptions.ts
|
|
7134
|
+
var SUBSCRIBE_FLAGS = { type: "multi", delivery: "string", batch: "string", expires: "string", "max-per-hour": "string", subscriber: "string", note: "string" };
|
|
7135
|
+
var DELIVERIES2 = ["queue", "steer", "silent"];
|
|
7136
|
+
var safe = (v, max = 120) => sanitizeLabel(String(v), max);
|
|
7137
|
+
function subscribeInput(addrs, flags) {
|
|
7138
|
+
if (!addrs.length)
|
|
7139
|
+
throw new UsageError("needs at least one address (a session, @@role, #team or children)");
|
|
7140
|
+
const delivery = str(flags, "delivery");
|
|
7141
|
+
if (delivery !== undefined && !DELIVERIES2.includes(delivery))
|
|
7142
|
+
throw new UsageError(`--delivery is one of ${DELIVERIES2.join(", ")}`);
|
|
7143
|
+
const batch = str(flags, "batch");
|
|
7144
|
+
const expires = str(flags, "expires");
|
|
7145
|
+
const max = str(flags, "max-per-hour");
|
|
7146
|
+
if (max !== undefined && !/^\d+$/.test(max))
|
|
7147
|
+
throw new UsageError("--max-per-hour takes a whole number (1–120)");
|
|
7148
|
+
const types = flags.type;
|
|
7149
|
+
const subscriber = str(flags, "subscriber");
|
|
7150
|
+
const note = str(flags, "note");
|
|
7151
|
+
return {
|
|
7152
|
+
to: addrs.length === 1 ? addrs[0] : [...addrs],
|
|
7153
|
+
...types?.length ? { types: types.flatMap((t) => t.split(",")).map((t) => t.trim()).filter(Boolean) } : {},
|
|
7154
|
+
...delivery !== undefined ? { delivery } : {},
|
|
7155
|
+
...batch !== undefined ? { batch: durationArg(batch, "--batch") } : {},
|
|
7156
|
+
...expires !== undefined ? { expires: durationArg(expires, "--expires") } : {},
|
|
7157
|
+
...max !== undefined ? { maxPerHour: Number(max) } : {},
|
|
7158
|
+
...subscriber !== undefined ? { subscriber } : {},
|
|
7159
|
+
...note !== undefined ? { note } : {}
|
|
7160
|
+
};
|
|
7161
|
+
}
|
|
7162
|
+
var targets = (s) => s.targets.map((t) => t.kind === "children" ? "its subagents" : safe(t.addr, 80)).join(", ");
|
|
7163
|
+
var iso3 = (ms) => new Date(ms).toISOString().slice(0, 16) + "Z";
|
|
7164
|
+
function subscriptionLine(s) {
|
|
7165
|
+
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`] : []];
|
|
7166
|
+
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)}"` : ""}`;
|
|
7167
|
+
}
|
|
7168
|
+
var subscribedText = (b) => `subscribed ${subscriptionLine(b.subscription)}
|
|
7169
|
+
the subscriber gets one hub message per batch window; stop with \`hub unsubscribe ${safe(b.subscription.id, 40)}\``;
|
|
7170
|
+
var subscriptionsText = (b) => b.subscriptions.length ? b.subscriptions.map(subscriptionLine).join(`
|
|
7171
|
+
`) : "no subscriptions";
|
|
7172
|
+
|
|
7173
|
+
// packages/cli/src/subscriptions-mcp.ts
|
|
6988
7174
|
var STRING4 = { type: "string" };
|
|
6989
|
-
var
|
|
7175
|
+
var need4 = (args, key) => {
|
|
7176
|
+
const value = args[key];
|
|
7177
|
+
if (typeof value !== "string" || !value.trim())
|
|
7178
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
7179
|
+
return value;
|
|
7180
|
+
};
|
|
7181
|
+
var optional5 = (args, key) => {
|
|
7182
|
+
const value = args[key];
|
|
7183
|
+
if (value === undefined || value === null)
|
|
7184
|
+
return;
|
|
7185
|
+
if (typeof value !== "string")
|
|
7186
|
+
throw new ToolArgError(`"${key}" must be a string`);
|
|
7187
|
+
return value.trim() || undefined;
|
|
7188
|
+
};
|
|
7189
|
+
var array = (args, key) => {
|
|
7190
|
+
const value = args[key];
|
|
7191
|
+
if (value === undefined || value === null)
|
|
7192
|
+
return;
|
|
7193
|
+
if (!Array.isArray(value) || !value.every((v) => typeof v === "string"))
|
|
7194
|
+
throw new ToolArgError(`"${key}" must be a list of strings`);
|
|
7195
|
+
return value;
|
|
7196
|
+
};
|
|
7197
|
+
var number = (args, key, min, max) => {
|
|
7198
|
+
const value = args[key];
|
|
7199
|
+
if (value === undefined || value === null)
|
|
7200
|
+
return;
|
|
7201
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max)
|
|
7202
|
+
throw new ToolArgError(`"${key}" must be a whole number from ${min} to ${max}`);
|
|
7203
|
+
return value;
|
|
7204
|
+
};
|
|
7205
|
+
var subscriptionTools = [
|
|
7206
|
+
{
|
|
7207
|
+
name: "subscribe",
|
|
7208
|
+
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.",
|
|
7209
|
+
properties: {
|
|
7210
|
+
to: { type: ["string", "array"], items: STRING4, description: "One or more session/role/team addresses, or children." },
|
|
7211
|
+
subscriber: STRING4,
|
|
7212
|
+
types: { type: "array", items: STRING4 },
|
|
7213
|
+
delivery: { type: "string", enum: ["queue", "steer", "silent"] },
|
|
7214
|
+
batch: { type: "number", description: "Seconds per batch (1–3600)." },
|
|
7215
|
+
expires: { type: "number", description: "Seconds until expiry." },
|
|
7216
|
+
maxPerHour: { type: "integer", minimum: 1, maximum: 120 },
|
|
7217
|
+
note: STRING4
|
|
7218
|
+
},
|
|
7219
|
+
required: ["to"],
|
|
7220
|
+
readOnly: false,
|
|
7221
|
+
run: async (hub, args, signal) => {
|
|
7222
|
+
const to = typeof args.to === "string" && args.to.trim() ? args.to : array(args, "to");
|
|
7223
|
+
if (!to || Array.isArray(to) && !to.length)
|
|
7224
|
+
throw new ToolArgError('"to" needs at least one address');
|
|
7225
|
+
const delivery = optional5(args, "delivery");
|
|
7226
|
+
if (delivery && !["queue", "steer", "silent"].includes(delivery))
|
|
7227
|
+
throw new ToolArgError('"delivery" is queue, steer or silent');
|
|
7228
|
+
const subscriber = optional5(args, "subscriber");
|
|
7229
|
+
const types = array(args, "types");
|
|
7230
|
+
const batch = number(args, "batch", 1, 3600);
|
|
7231
|
+
const expires = number(args, "expires", 1, 30 * 86400);
|
|
7232
|
+
const maxPerHour = number(args, "maxPerHour", 1, 120);
|
|
7233
|
+
const note = optional5(args, "note");
|
|
7234
|
+
const input = { to, ...subscriber ? { subscriber } : {}, ...types ? { types } : {}, ...delivery ? { delivery } : {}, ...batch ? { batch } : {}, ...expires ? { expires } : {}, ...maxPerHour ? { maxPerHour } : {}, ...note ? { note } : {} };
|
|
7235
|
+
return subscribedText(await hub.subscribe(input, { signal }));
|
|
7236
|
+
}
|
|
7237
|
+
},
|
|
7238
|
+
{
|
|
7239
|
+
name: "subscriptions",
|
|
7240
|
+
description: "List subscriptions visible to this credential (session: its own; app: created by it; user: all). Optional session filter; route enforces scopes and tenancy.",
|
|
7241
|
+
properties: { session: STRING4 },
|
|
7242
|
+
readOnly: true,
|
|
7243
|
+
run: async (hub, args, signal) => subscriptionsText(await hub.subscriptions({ ...optional5(args, "session") ? { session: optional5(args, "session") } : {}, signal }))
|
|
7244
|
+
},
|
|
7245
|
+
{
|
|
7246
|
+
name: "unsubscribe",
|
|
7247
|
+
description: "Remove a subscription this credential owns. An unknown or another caller's ID answers not_found.",
|
|
7248
|
+
properties: { id: STRING4 },
|
|
7249
|
+
required: ["id"],
|
|
7250
|
+
readOnly: false,
|
|
7251
|
+
organizes: true,
|
|
7252
|
+
run: async (hub, args, signal) => {
|
|
7253
|
+
const id = need4(args, "id");
|
|
7254
|
+
await hub.unsubscribe(id, { signal });
|
|
7255
|
+
return `unsubscribed ${id}`;
|
|
7256
|
+
}
|
|
7257
|
+
}
|
|
7258
|
+
];
|
|
7259
|
+
|
|
7260
|
+
// packages/cli/src/mcp-operations.ts
|
|
7261
|
+
var STRING5 = { type: "string" };
|
|
7262
|
+
var need5 = (args, key) => {
|
|
7263
|
+
const v = args[key];
|
|
7264
|
+
if (typeof v !== "string" || !v.trim())
|
|
7265
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
7266
|
+
return v;
|
|
7267
|
+
};
|
|
7268
|
+
var userOnly = async (hub, signal) => {
|
|
7269
|
+
if ((await hub.whoami({ signal })).actor.kind !== "user")
|
|
7270
|
+
throw new CliError(EXIT.forbidden, "only the user can use this tool", "Use the user's device credential");
|
|
7271
|
+
};
|
|
7272
|
+
var operationTools = [
|
|
7273
|
+
{
|
|
7274
|
+
name: "session_model_set",
|
|
7275
|
+
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.",
|
|
7276
|
+
properties: { session: STRING5, model: { type: "object", properties: { providerID: STRING5, modelID: STRING5, variant: STRING5 }, required: ["providerID", "modelID"] } },
|
|
7277
|
+
required: ["session", "model"],
|
|
7278
|
+
readOnly: false,
|
|
7279
|
+
run: async (hub, args, signal) => {
|
|
7280
|
+
await userOnly(hub, signal);
|
|
7281
|
+
const model = args.model;
|
|
7282
|
+
if (typeof model !== "object" || model === null || Array.isArray(model))
|
|
7283
|
+
throw new ToolArgError('"model" is required (providerID, modelID, variant?)');
|
|
7284
|
+
const result = await hub.call("PUT", `/api/sessions/${encodeURIComponent(need5(args, "session"))}/model`, { body: { model }, signal });
|
|
7285
|
+
return JSON.stringify(result);
|
|
7286
|
+
}
|
|
7287
|
+
},
|
|
7288
|
+
{
|
|
7289
|
+
name: "machines",
|
|
7290
|
+
description: "The user's own enrolled machines, online status and processes. User device credential only; wraps GET /api/machines. No machine tokens or secrets.",
|
|
7291
|
+
properties: {},
|
|
7292
|
+
readOnly: true,
|
|
7293
|
+
run: async (hub, _args, signal) => {
|
|
7294
|
+
await userOnly(hub, signal);
|
|
7295
|
+
return JSON.stringify(await hub.call("GET", "/api/machines", { signal }));
|
|
7296
|
+
}
|
|
7297
|
+
},
|
|
7298
|
+
{
|
|
7299
|
+
name: "launch_nodes",
|
|
7300
|
+
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.",
|
|
7301
|
+
properties: {},
|
|
7302
|
+
readOnly: true,
|
|
7303
|
+
run: async (hub, _args, signal) => {
|
|
7304
|
+
await userOnly(hub, signal);
|
|
7305
|
+
return JSON.stringify(await hub.call("GET", "/api/agent/launch-nodes", { signal }));
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
];
|
|
7309
|
+
|
|
7310
|
+
// packages/cli/src/teams-mcp.ts
|
|
7311
|
+
var STRING6 = { type: "string" };
|
|
7312
|
+
var STRINGS = { type: "array", items: STRING6 };
|
|
6990
7313
|
var TEAM2 = { type: "string", description: "The team: its ID (team_…) or its name." };
|
|
6991
7314
|
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
7315
|
var IDEMPOTENCY = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
|
|
6993
|
-
var
|
|
7316
|
+
var KINDS5 = { 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
7317
|
var DIRECTION = { type: "string", enum: ["one-way", "two-way"], description: "two-way: `to` may also contact `from` with the same kinds." };
|
|
6995
7318
|
var ROLE_FIELDS = {
|
|
6996
|
-
label: { ...
|
|
6997
|
-
description:
|
|
7319
|
+
label: { ...STRING6, description: "A display label (confers no authority)." },
|
|
7320
|
+
description: STRING6,
|
|
6998
7321
|
externalContact: { type: "boolean", description: "Holders may contact sessions outside all their teams (default false)." },
|
|
6999
7322
|
responsibilities: { ...STRINGS, description: "Short lines (at most 16)." },
|
|
7000
|
-
profile: { type: "object", properties: { profileID:
|
|
7001
|
-
prefs: { type: "object", properties: { harness:
|
|
7323
|
+
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)." },
|
|
7324
|
+
prefs: { type: "object", properties: { harness: STRING6, model: STRING6, machineID: STRING6 }, description: "Placement preferences for launched members (advisory)." },
|
|
7002
7325
|
minMembers: { type: "integer", minimum: 0, maximum: 16 },
|
|
7003
7326
|
maxMembers: { type: "integer", minimum: 1, maximum: 16 }
|
|
7004
7327
|
};
|
|
7005
|
-
var ROLE = { type: "object", properties: { name: { ...
|
|
7006
|
-
var EDGE = { type: "object", properties: { from:
|
|
7328
|
+
var ROLE = { type: "object", properties: { name: { ...STRING6, description: "A slug: lowercase letters, digits, dashes." }, ...ROLE_FIELDS }, required: ["name"] };
|
|
7329
|
+
var EDGE = { type: "object", properties: { from: STRING6, to: STRING6, kinds: KINDS5, direction: DIRECTION }, required: ["from", "to", "kinds", "direction"] };
|
|
7007
7330
|
var SPEC_FIELDS = {
|
|
7008
|
-
title: { ...
|
|
7009
|
-
description:
|
|
7331
|
+
title: { ...STRING6, description: "1–120 characters." },
|
|
7332
|
+
description: STRING6,
|
|
7010
7333
|
mode: { type: "string", enum: ["open", "restricted"], description: "open: every member may msg/ask every other member. restricted: only the drawn paths (edges)." },
|
|
7334
|
+
hopLimit: { type: "integer", minimum: 1, maximum: 1e4, description: "Agent-only wake hop limit between this team's seats; at most the hub's hop ceiling (agent_limits hopCeiling, default 100). The effective limit is the lower of this and the hub-wide hopLimit. Unset uses the hub-wide limit." },
|
|
7335
|
+
itemBudget: { type: "object", properties: { wakes: { type: "integer", minimum: 10, maximum: 5000 }, hours: { type: "integer", minimum: 1, maximum: 720 } }, required: ["wakes", "hours"], description: "User-only per-item budget override: agent wakes and hours in progress." },
|
|
7336
|
+
loop: { type: "object", properties: { messages: { type: "integer", minimum: 4, maximum: 200 }, windowMinutes: { type: "integer", minimum: 5, maximum: 240 } }, required: ["messages", "windowMinutes"], description: "User-only no-progress pair-message threshold and window." },
|
|
7011
7337
|
roles: { type: "array", items: ROLE, description: "1–32 roles." },
|
|
7012
|
-
entryRole: { ...
|
|
7338
|
+
entryRole: { ...STRING6, description: "The role that receives submitted work (e.g. pm) and supervises members." },
|
|
7013
7339
|
edges: { type: "array", items: EDGE, description: "Communication paths between roles (restricted mode)." },
|
|
7014
7340
|
tags: { ...STRINGS, description: "Slugs." },
|
|
7015
|
-
metadata: { type: "object", additionalProperties:
|
|
7341
|
+
metadata: { type: "object", additionalProperties: STRING6, description: "String key/value pairs (keys are slugs)." },
|
|
7016
7342
|
projects: { ...STRINGS, description: "Project names the team works on." }
|
|
7017
7343
|
};
|
|
7018
7344
|
var LAUNCH = {
|
|
@@ -7023,11 +7349,11 @@ var LAUNCH = {
|
|
|
7023
7349
|
type: "object",
|
|
7024
7350
|
description: "Where the member works on its machine.",
|
|
7025
7351
|
properties: {
|
|
7026
|
-
repo: { ...
|
|
7352
|
+
repo: { ...STRING6, description: "The repository's absolute path on the machine." },
|
|
7027
7353
|
mode: { type: "string", enum: ["worktree", "checkout", "clone"], description: "worktree (default; needs branch), checkout, or clone (needs remote)." },
|
|
7028
|
-
branch: { ...
|
|
7029
|
-
base: { ...
|
|
7030
|
-
remote: { ...
|
|
7354
|
+
branch: { ...STRING6, description: "The branch to create or check out (a safe git branch name; required for worktree)." },
|
|
7355
|
+
base: { ...STRING6, description: "What a new branch starts from." },
|
|
7356
|
+
remote: { ...STRING6, description: "An https or ssh git remote, without credentials (required for clone)." }
|
|
7031
7357
|
},
|
|
7032
7358
|
required: ["repo"]
|
|
7033
7359
|
},
|
|
@@ -7035,8 +7361,8 @@ var LAUNCH = {
|
|
|
7035
7361
|
type: "object",
|
|
7036
7362
|
description: "Which machine runs it.",
|
|
7037
7363
|
properties: {
|
|
7038
|
-
machine: { ...
|
|
7039
|
-
labels: { type: "object", additionalProperties:
|
|
7364
|
+
machine: { ...STRING6, description: "A machine ID or name." },
|
|
7365
|
+
labels: { type: "object", additionalProperties: STRING6, description: "Node labels to match." },
|
|
7040
7366
|
mode: { type: "string", enum: ["require", "prefer"], description: "require (default) or prefer the machine/labels." }
|
|
7041
7367
|
}
|
|
7042
7368
|
},
|
|
@@ -7044,7 +7370,7 @@ var LAUNCH = {
|
|
|
7044
7370
|
type: "object",
|
|
7045
7371
|
properties: {
|
|
7046
7372
|
kind: { type: "string", enum: ["host", "openshell"], description: "host (default) or openshell (clone workspaces only)." },
|
|
7047
|
-
profile: { ...
|
|
7373
|
+
profile: { ...STRING6, description: "An openshell sandbox profile (openshell only)." }
|
|
7048
7374
|
}
|
|
7049
7375
|
},
|
|
7050
7376
|
end: {
|
|
@@ -7056,27 +7382,27 @@ var LAUNCH = {
|
|
|
7056
7382
|
cleanup: { type: "string", enum: ["keep", "remove"], description: "keep (default) or remove the workspace at the end." }
|
|
7057
7383
|
}
|
|
7058
7384
|
},
|
|
7059
|
-
name: { ...
|
|
7385
|
+
name: { ...STRING6, description: "The session's @name, without the @." },
|
|
7060
7386
|
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:
|
|
7387
|
+
harness: { ...STRING6, description: "The harness (default opencode)." },
|
|
7388
|
+
agent: { ...STRING6, description: "The harness agent." },
|
|
7389
|
+
model: { ...STRING6, description: "provider/model." },
|
|
7390
|
+
prompt: { ...STRING6, description: "The first prompt (up to 10000 characters)." },
|
|
7391
|
+
intent: { ...STRING6, description: "What the session is for (one line)." },
|
|
7392
|
+
labels: { type: "object", additionalProperties: STRING6, description: "Free-form app labels." },
|
|
7393
|
+
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
7394
|
},
|
|
7069
7395
|
required: ["workspace"]
|
|
7070
7396
|
};
|
|
7071
7397
|
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
7398
|
var ArgError = ToolArgError;
|
|
7073
|
-
var
|
|
7399
|
+
var need6 = (args, key) => {
|
|
7074
7400
|
const value = args[key];
|
|
7075
7401
|
if (typeof value !== "string" || !value.trim())
|
|
7076
7402
|
throw new ArgError(`"${key}" is required (a non-empty string)`);
|
|
7077
7403
|
return value;
|
|
7078
7404
|
};
|
|
7079
|
-
var
|
|
7405
|
+
var optional6 = (args, key) => {
|
|
7080
7406
|
const value = args[key];
|
|
7081
7407
|
if (value === undefined || value === null)
|
|
7082
7408
|
return;
|
|
@@ -7131,6 +7457,9 @@ function specLines(spec) {
|
|
|
7131
7457
|
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
7458
|
return [
|
|
7133
7459
|
`mode: ${spec.mode}${spec.mode === "open" ? " (every member may msg/ask every other)" : " (only the paths below)"}`,
|
|
7460
|
+
...spec.hopLimit !== undefined ? [`hop limit: ${spec.hopLimit}`] : [],
|
|
7461
|
+
...spec.itemBudget ? [`item budget: ${spec.itemBudget.wakes} wakes, ${spec.itemBudget.hours} hours`] : [],
|
|
7462
|
+
...spec.loop ? [`loop: ${spec.loop.messages} messages per ${spec.loop.windowMinutes} minutes`] : [],
|
|
7134
7463
|
`entry role: ${spec.entryRole}`,
|
|
7135
7464
|
"roles:",
|
|
7136
7465
|
...spec.roles.map(role),
|
|
@@ -7149,7 +7478,7 @@ function rosterLines(roster) {
|
|
|
7149
7478
|
const flags = [r.vacant ? "vacant" : "", r.understaffed ? `understaffed (min ${r.minMembers})` : "", r.launching ? `${r.launching} launching` : ""].filter(Boolean);
|
|
7150
7479
|
lines.push(` ${r.role}${r.entry ? " (entry)" : ""} ${r.seats.length}/${r.maxMembers} ${r.online} online${flags.length ? ` ${flags.join(", ")}` : ""}`);
|
|
7151
7480
|
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)"}` : ""}`);
|
|
7481
|
+
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
7482
|
}
|
|
7154
7483
|
const failed = roster.requests.filter((q) => q.state !== "staffed").slice(-5);
|
|
7155
7484
|
for (const q of failed)
|
|
@@ -7166,7 +7495,7 @@ var statusTool = (verb, description, organizes) => ({
|
|
|
7166
7495
|
idempotent: true,
|
|
7167
7496
|
run: async (hub, args, signal) => {
|
|
7168
7497
|
const expectedRevision = revision(args);
|
|
7169
|
-
const { team } = await hub.teams[verb](
|
|
7498
|
+
const { team } = await hub.teams[verb](need6(args, "team"), expectedRevision, { signal });
|
|
7170
7499
|
return `${team.revision === expectedRevision ? "unchanged" : { pause: "paused", resume: "resumed", archive: "archived", restore: "restored" }[verb]}: ${teamLine(team)}`;
|
|
7171
7500
|
}
|
|
7172
7501
|
});
|
|
@@ -7180,7 +7509,7 @@ var draftTool = (name, description, properties, required, edit) => ({
|
|
|
7180
7509
|
idempotent: true,
|
|
7181
7510
|
run: async (hub, args, signal) => {
|
|
7182
7511
|
const expectedRevision = revision(args);
|
|
7183
|
-
const { team, draft } = await edit(hub.teams,
|
|
7512
|
+
const { team, draft } = await edit(hub.teams, need6(args, "team"), args, expectedRevision, signal);
|
|
7184
7513
|
return draftText("draft saved", team, draft);
|
|
7185
7514
|
}
|
|
7186
7515
|
});
|
|
@@ -7191,22 +7520,22 @@ var teamTools = [
|
|
|
7191
7520
|
properties: {
|
|
7192
7521
|
state: { type: "string", enum: ["active", "archived", "all"] },
|
|
7193
7522
|
tags: STRINGS,
|
|
7194
|
-
project:
|
|
7195
|
-
metadata: { type: "object", additionalProperties:
|
|
7523
|
+
project: STRING6,
|
|
7524
|
+
metadata: { type: "object", additionalProperties: STRING6 },
|
|
7196
7525
|
paused: { type: "boolean" },
|
|
7197
7526
|
limit: { type: "integer", minimum: 1, maximum: 200 },
|
|
7198
|
-
cursor:
|
|
7527
|
+
cursor: STRING6
|
|
7199
7528
|
},
|
|
7200
7529
|
readOnly: true,
|
|
7201
7530
|
run: async (hub, args, signal) => {
|
|
7202
|
-
const state =
|
|
7531
|
+
const state = optional6(args, "state");
|
|
7203
7532
|
if (state !== undefined && !["active", "archived", "all"].includes(state))
|
|
7204
7533
|
throw new ArgError(`"state" is one of active, archived, all`);
|
|
7205
7534
|
const tags = strings3(args, "tags");
|
|
7206
|
-
const project =
|
|
7535
|
+
const project = optional6(args, "project");
|
|
7207
7536
|
const metadata = record(args, "metadata");
|
|
7208
7537
|
const limit = int3(args, "limit", 1, 200);
|
|
7209
|
-
const cursor =
|
|
7538
|
+
const cursor = optional6(args, "cursor");
|
|
7210
7539
|
if (args.paused !== undefined && typeof args.paused !== "boolean")
|
|
7211
7540
|
throw new ArgError(`"paused" must be true or false`);
|
|
7212
7541
|
const page = await hub.teams.list({
|
|
@@ -7232,7 +7561,7 @@ var teamTools = [
|
|
|
7232
7561
|
required: ["team"],
|
|
7233
7562
|
readOnly: true,
|
|
7234
7563
|
run: async (hub, args, signal) => {
|
|
7235
|
-
const { team, draft } = await hub.teams.get(
|
|
7564
|
+
const { team, draft } = await hub.teams.get(need6(args, "team"), { signal });
|
|
7236
7565
|
return [teamLine(team), ...specLines(team), ...draft ? [`draft pending (on v${draft.baseVersion}, saved ${when(draft.savedAt)}): see team_diff`] : []].join(`
|
|
7237
7566
|
`);
|
|
7238
7567
|
}
|
|
@@ -7244,7 +7573,7 @@ var teamTools = [
|
|
|
7244
7573
|
required: ["team"],
|
|
7245
7574
|
readOnly: true,
|
|
7246
7575
|
run: async (hub, args, signal) => {
|
|
7247
|
-
const { team, roster, intake } = await hub.teams.status(
|
|
7576
|
+
const { team, roster, intake } = await hub.teams.status(need6(args, "team"), { signal });
|
|
7248
7577
|
const lines = [teamLine(team), ...rosterLines(roster)];
|
|
7249
7578
|
if (intake)
|
|
7250
7579
|
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 +7584,12 @@ var teamTools = [
|
|
|
7255
7584
|
{
|
|
7256
7585
|
name: "team_history",
|
|
7257
7586
|
description: `A team's retained published versions, newest first (version, digest, when). ${SCOPES}`,
|
|
7258
|
-
properties: { team: TEAM2, cursor:
|
|
7587
|
+
properties: { team: TEAM2, cursor: STRING6 },
|
|
7259
7588
|
required: ["team"],
|
|
7260
7589
|
readOnly: true,
|
|
7261
7590
|
run: async (hub, args, signal) => {
|
|
7262
|
-
const cursor =
|
|
7263
|
-
const page = await hub.teams.versions(
|
|
7591
|
+
const cursor = optional6(args, "cursor");
|
|
7592
|
+
const page = await hub.teams.versions(need6(args, "team"), { signal, ...cursor ? { cursor } : {} });
|
|
7264
7593
|
return [...page.items.map((v) => `v${v.version} ${v.digest.slice(0, 12)} ${when(v.publishedAt)}`), ...page.cursor ? [`more: cursor ${page.cursor}`] : []].join(`
|
|
7265
7594
|
`);
|
|
7266
7595
|
}
|
|
@@ -7274,7 +7603,7 @@ var teamTools = [
|
|
|
7274
7603
|
run: async (hub, args, signal) => {
|
|
7275
7604
|
const from = int3(args, "from", 1, 1e9);
|
|
7276
7605
|
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(
|
|
7606
|
+
const { from: a, to: b, diff } = await hub.teams.diff(need6(args, "team"), { signal, ...from !== undefined ? { from } : {}, to });
|
|
7278
7607
|
const part = (label, d) => ["added", "removed", "changed"].flatMap((k) => d[k]?.length ? [`${label} ${k}: ${d[k].join(", ")}`] : []);
|
|
7279
7608
|
const lines = [
|
|
7280
7609
|
...diff.fields.length ? [`fields changed: ${diff.fields.join(", ")}`] : [],
|
|
@@ -7291,16 +7620,16 @@ var teamTools = [
|
|
|
7291
7620
|
{
|
|
7292
7621
|
name: "team_create",
|
|
7293
7622
|
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: { ...
|
|
7623
|
+
properties: { name: { ...STRING6, description: "A slug (2–40 lowercase letters, digits, dashes), unique per user." }, ...SPEC_FIELDS, idempotencyKey: IDEMPOTENCY },
|
|
7295
7624
|
required: ["name", "title", "mode", "roles", "entryRole"],
|
|
7296
7625
|
readOnly: false,
|
|
7297
7626
|
organizes: true,
|
|
7298
7627
|
idempotent: true,
|
|
7299
7628
|
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"]);
|
|
7629
|
+
need6(args, "name");
|
|
7630
|
+
need6(args, "title");
|
|
7631
|
+
const key = optional6(args, "idempotencyKey");
|
|
7632
|
+
const spec = picked(args, ["name", "title", "description", "mode", "hopLimit", "itemBudget", "loop", "roles", "entryRole", "edges", "tags", "metadata", "projects"]);
|
|
7304
7633
|
const { team, created } = await hub.teams.create({ ...spec, edges: spec.edges ?? [] }, { signal, ...key ? { idempotencyKey: key } : {} });
|
|
7305
7634
|
return [`${created ? "created" : "already created (same key)"}: ${teamLine(team)}`, ...specLines(team)].join(`
|
|
7306
7635
|
`);
|
|
@@ -7316,24 +7645,24 @@ var teamTools = [
|
|
|
7316
7645
|
idempotent: true,
|
|
7317
7646
|
run: async (hub, args, signal) => {
|
|
7318
7647
|
const expectedRevision = revision(args);
|
|
7319
|
-
const patch = picked(args, ["title", "description", "mode", "roles", "entryRole", "edges", "tags", "metadata", "projects"]);
|
|
7648
|
+
const patch = picked(args, ["title", "description", "mode", "hopLimit", "itemBudget", "loop", "roles", "entryRole", "edges", "tags", "metadata", "projects"]);
|
|
7320
7649
|
if (Object.keys(patch).length === 0)
|
|
7321
7650
|
throw new ArgError("give at least one field to change");
|
|
7322
|
-
const id =
|
|
7651
|
+
const id = need6(args, "team");
|
|
7323
7652
|
const { team } = await hub.teams.update(id, patch, expectedRevision, { signal });
|
|
7324
7653
|
return draftText("draft saved", team, (await hub.teams.draft(team.id, { signal })).draft);
|
|
7325
7654
|
}
|
|
7326
7655
|
},
|
|
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:
|
|
7656
|
+
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 })),
|
|
7657
|
+
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 })),
|
|
7658
|
+
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: KINDS5, direction: DIRECTION }, ["from", "to", "kinds", "direction"], (teams, team, args, rev, signal) => {
|
|
7330
7659
|
const kinds = strings3(args, "kinds");
|
|
7331
7660
|
if (!kinds?.length)
|
|
7332
7661
|
throw new ArgError(`"kinds" is a list of msg and/or ask`);
|
|
7333
|
-
const direction =
|
|
7334
|
-
return teams.setEdge(team, { from:
|
|
7662
|
+
const direction = need6(args, "direction");
|
|
7663
|
+
return teams.setEdge(team, { from: need6(args, "from"), to: need6(args, "to"), kinds, direction }, rev, { signal });
|
|
7335
7664
|
}),
|
|
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:
|
|
7665
|
+
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
7666
|
{
|
|
7338
7667
|
name: "team_discard_draft",
|
|
7339
7668
|
description: `Drop a team's pending draft (unpublished edits are lost; the published version stays). ${SCOPES}`,
|
|
@@ -7341,7 +7670,7 @@ var teamTools = [
|
|
|
7341
7670
|
required: ["team", "expectedRevision"],
|
|
7342
7671
|
readOnly: false,
|
|
7343
7672
|
idempotent: true,
|
|
7344
|
-
run: async (hub, args, signal) => `draft discarded: ${teamLine((await hub.teams.discardDraft(
|
|
7673
|
+
run: async (hub, args, signal) => `draft discarded: ${teamLine((await hub.teams.discardDraft(need6(args, "team"), revision(args), { signal })).team)}`
|
|
7345
7674
|
},
|
|
7346
7675
|
{
|
|
7347
7676
|
name: "team_publish",
|
|
@@ -7351,7 +7680,7 @@ var teamTools = [
|
|
|
7351
7680
|
readOnly: false,
|
|
7352
7681
|
idempotent: true,
|
|
7353
7682
|
run: async (hub, args, signal) => {
|
|
7354
|
-
const { team, published } = await hub.teams.publish(
|
|
7683
|
+
const { team, published } = await hub.teams.publish(need6(args, "team"), revision(args), { signal });
|
|
7355
7684
|
return `${published ? `published version ${team.version}` : "the draft equalled the published version; dropped"}: ${teamLine(team)}`;
|
|
7356
7685
|
}
|
|
7357
7686
|
},
|
|
@@ -7362,17 +7691,17 @@ var teamTools = [
|
|
|
7362
7691
|
{
|
|
7363
7692
|
name: "team_staff",
|
|
7364
7693
|
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:
|
|
7694
|
+
properties: { team: TEAM2, role: STRING6, sessionID: { ...STRING6, description: "An existing session (ses_…)." }, launch: LAUNCH, idempotencyKey: IDEMPOTENCY },
|
|
7366
7695
|
required: ["team", "role"],
|
|
7367
7696
|
readOnly: false,
|
|
7368
7697
|
run: async (hub, args, signal) => {
|
|
7369
|
-
const team =
|
|
7370
|
-
const role =
|
|
7371
|
-
const sessionID =
|
|
7698
|
+
const team = need6(args, "team");
|
|
7699
|
+
const role = need6(args, "role");
|
|
7700
|
+
const sessionID = optional6(args, "sessionID");
|
|
7372
7701
|
const launch = launchOf(args);
|
|
7373
7702
|
if (sessionID === undefined === (launch === undefined))
|
|
7374
7703
|
throw new ArgError(`give exactly one of sessionID or launch`);
|
|
7375
|
-
const key =
|
|
7704
|
+
const key = optional6(args, "idempotencyKey");
|
|
7376
7705
|
const options = { signal, ...key ? { idempotencyKey: key } : {} };
|
|
7377
7706
|
if (sessionID !== undefined) {
|
|
7378
7707
|
const r = await hub.teams.staff(team, { role, sessionID }, options);
|
|
@@ -7385,29 +7714,29 @@ var teamTools = [
|
|
|
7385
7714
|
{
|
|
7386
7715
|
name: "team_unstaff",
|
|
7387
7716
|
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: { ...
|
|
7717
|
+
properties: { team: TEAM2, staffingID: { ...STRING6, description: "The seat (stf_…)." }, expectedRevision: { ...REVISION2, description: "The seat's revision you read (team_status)." } },
|
|
7389
7718
|
required: ["team", "staffingID", "expectedRevision"],
|
|
7390
7719
|
readOnly: false,
|
|
7391
7720
|
idempotent: true,
|
|
7392
7721
|
run: async (hub, args, signal) => {
|
|
7393
|
-
const { staffing } = await hub.teams.unstaff(
|
|
7722
|
+
const { staffing } = await hub.teams.unstaff(need6(args, "team"), need6(args, "staffingID"), revision(args), { signal });
|
|
7394
7723
|
return `unstaffed ${staffing.id} (${staffing.sessionID} as ${staffing.role})`;
|
|
7395
7724
|
}
|
|
7396
7725
|
},
|
|
7397
7726
|
{
|
|
7398
7727
|
name: "team_replace",
|
|
7399
7728
|
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:
|
|
7729
|
+
properties: { team: TEAM2, staffingID: STRING6, sessionID: STRING6, launch: LAUNCH, expectedRevision: { ...REVISION2, description: "The seat's revision you read (team_status)." } },
|
|
7401
7730
|
required: ["team", "staffingID", "expectedRevision"],
|
|
7402
7731
|
readOnly: false,
|
|
7403
7732
|
run: async (hub, args, signal) => {
|
|
7404
7733
|
const expectedRevision = revision(args);
|
|
7405
|
-
const sessionID =
|
|
7734
|
+
const sessionID = optional6(args, "sessionID");
|
|
7406
7735
|
const launch = launchOf(args);
|
|
7407
7736
|
if (sessionID === undefined === (launch === undefined))
|
|
7408
7737
|
throw new ArgError(`give exactly one of sessionID or launch`);
|
|
7409
7738
|
const input = sessionID !== undefined ? { expectedRevision, sessionID } : { expectedRevision, launch };
|
|
7410
|
-
const r = await hub.teams.replace(
|
|
7739
|
+
const r = await hub.teams.replace(need6(args, "team"), need6(args, "staffingID"), input, { signal });
|
|
7411
7740
|
const next = r.staffing ? `${r.staffing.sessionID} (${r.staffing.id}, generation ${r.staffing.generation})` : `launch ${r.launch?.id ?? "?"} (request ${r.request?.id ?? "?"})`;
|
|
7412
7741
|
return `replaced ${r.ended.sessionID} in ${r.ended.role} by ${next}`;
|
|
7413
7742
|
}
|
|
@@ -7415,13 +7744,13 @@ var teamTools = [
|
|
|
7415
7744
|
];
|
|
7416
7745
|
|
|
7417
7746
|
// packages/cli/src/profiles-mcp.ts
|
|
7418
|
-
var
|
|
7419
|
-
var STRINGS2 = { type: "array", items:
|
|
7747
|
+
var STRING7 = { type: "string" };
|
|
7748
|
+
var STRINGS2 = { type: "array", items: STRING7 };
|
|
7420
7749
|
var ID4 = { type: "string", description: "The profile's ID (prof_…; see profiles)." };
|
|
7421
7750
|
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
7751
|
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: { ...
|
|
7752
|
+
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"] };
|
|
7753
|
+
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
7754
|
var CONTENT_FIELDS = {
|
|
7426
7755
|
instructions: { type: ["string", "null"], description: "The profile's instructions (AGENTS.md-like text)." },
|
|
7427
7756
|
model: { type: ["string", "null"], description: "provider/model the profile suggests." },
|
|
@@ -7429,21 +7758,21 @@ var CONTENT_FIELDS = {
|
|
|
7429
7758
|
commands: { type: "array", items: COMMAND3, description: "Commands, wholesale." }
|
|
7430
7759
|
};
|
|
7431
7760
|
var META_FIELDS = {
|
|
7432
|
-
title: { ...
|
|
7433
|
-
description:
|
|
7761
|
+
title: { ...STRING7, description: "1–120 characters." },
|
|
7762
|
+
description: STRING7,
|
|
7434
7763
|
tags: { ...STRINGS2, description: "Slugs." },
|
|
7435
|
-
metadata: { type: "object", additionalProperties:
|
|
7764
|
+
metadata: { type: "object", additionalProperties: STRING7, description: "String key/value pairs (≤ 32 slug keys)." },
|
|
7436
7765
|
projects: { ...STRINGS2, description: "Project names the profile is meant for (≤ 16)." }
|
|
7437
7766
|
};
|
|
7438
7767
|
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
7768
|
var ArgError2 = ToolArgError;
|
|
7440
|
-
var
|
|
7769
|
+
var need7 = (args, key) => {
|
|
7441
7770
|
const value = args[key];
|
|
7442
7771
|
if (typeof value !== "string" || !value.trim())
|
|
7443
7772
|
throw new ArgError2(`"${key}" is required (a non-empty string)`);
|
|
7444
7773
|
return value;
|
|
7445
7774
|
};
|
|
7446
|
-
var
|
|
7775
|
+
var optional7 = (args, key) => {
|
|
7447
7776
|
const value = args[key];
|
|
7448
7777
|
if (value === undefined || value === null)
|
|
7449
7778
|
return;
|
|
@@ -7459,7 +7788,7 @@ var whole2 = (args, key, min, what) => {
|
|
|
7459
7788
|
};
|
|
7460
7789
|
var revision2 = (args) => whole2(args, "expectedRevision", 0, "the revision you read (profile_show; 0 for a session with no pin)");
|
|
7461
7790
|
var idempotency = (args) => {
|
|
7462
|
-
const key =
|
|
7791
|
+
const key = optional7(args, "idempotencyKey");
|
|
7463
7792
|
return key ? { idempotencyKey: key } : {};
|
|
7464
7793
|
};
|
|
7465
7794
|
var picked2 = (args, keys) => Object.fromEntries(keys.filter((k) => args[k] !== undefined).map((k) => [k, args[k]]));
|
|
@@ -7492,7 +7821,7 @@ var contentTool = (name, description, properties, required, change) => ({
|
|
|
7492
7821
|
idempotent: true,
|
|
7493
7822
|
run: async (hub, args, signal) => {
|
|
7494
7823
|
const expectedRevision = revision2(args);
|
|
7495
|
-
const id =
|
|
7824
|
+
const id = need7(args, "id");
|
|
7496
7825
|
const { latest, content } = await latestContent(hub.profiles, id, signal);
|
|
7497
7826
|
return versionText(await hub.profiles.createVersion(id, { expectedRevision, content: change(content, args, latest) }, { signal }));
|
|
7498
7827
|
}
|
|
@@ -7512,7 +7841,7 @@ var stateTool = (verb, description) => ({
|
|
|
7512
7841
|
idempotent: true,
|
|
7513
7842
|
run: async (hub, args, signal) => {
|
|
7514
7843
|
const expectedRevision = revision2(args);
|
|
7515
|
-
const { profile } = await hub.profiles.update(
|
|
7844
|
+
const { profile } = await hub.profiles.update(need7(args, "id"), { expectedRevision, state: verb === "archive" ? "archived" : "active" }, { signal });
|
|
7516
7845
|
return `${verb === "archive" ? "archived" : "restored"}: ${profileLine(profile)}`;
|
|
7517
7846
|
}
|
|
7518
7847
|
});
|
|
@@ -7524,7 +7853,7 @@ var profileLifecycleTools = [
|
|
|
7524
7853
|
required: ["id"],
|
|
7525
7854
|
readOnly: true,
|
|
7526
7855
|
run: async (hub, args, signal) => {
|
|
7527
|
-
const { profile, latest } = await hub.profiles.get(
|
|
7856
|
+
const { profile, latest } = await hub.profiles.get(need7(args, "id"), { signal });
|
|
7528
7857
|
return [
|
|
7529
7858
|
profileLine(profile),
|
|
7530
7859
|
...profile.description ? [`description: ${one2(profile.description)}`] : [],
|
|
@@ -7539,13 +7868,13 @@ var profileLifecycleTools = [
|
|
|
7539
7868
|
{
|
|
7540
7869
|
name: "profile_versions",
|
|
7541
7870
|
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:
|
|
7871
|
+
properties: { id: ID4, limit: { type: "integer", minimum: 1, maximum: 200 }, cursor: STRING7 },
|
|
7543
7872
|
required: ["id"],
|
|
7544
7873
|
readOnly: true,
|
|
7545
7874
|
run: async (hub, args, signal) => {
|
|
7546
7875
|
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(
|
|
7876
|
+
const cursor = optional7(args, "cursor");
|
|
7877
|
+
const page = await hub.profiles.versions(need7(args, "id"), { signal, ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {} });
|
|
7549
7878
|
const counts = (v) => v.skills !== undefined ? ` ${v.skills} skills, ${v.commands ?? 0} commands${v.instructions ? ", instructions" : ""}` : "";
|
|
7550
7879
|
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
7880
|
`) || "no versions";
|
|
@@ -7554,14 +7883,14 @@ var profileLifecycleTools = [
|
|
|
7554
7883
|
{
|
|
7555
7884
|
name: "profile_create",
|
|
7556
7885
|
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: { ...
|
|
7886
|
+
properties: { name: { ...STRING7, description: "A slug, unique per user." }, ...META_FIELDS, ...CONTENT_FIELDS, idempotencyKey: IDEMPOTENCY2 },
|
|
7558
7887
|
required: ["name", "title"],
|
|
7559
7888
|
readOnly: false,
|
|
7560
7889
|
organizes: true,
|
|
7561
7890
|
idempotent: true,
|
|
7562
7891
|
run: async (hub, args, signal) => {
|
|
7563
|
-
|
|
7564
|
-
|
|
7892
|
+
need7(args, "name");
|
|
7893
|
+
need7(args, "title");
|
|
7565
7894
|
const meta = picked2(args, ["name", "title", "description", "tags", "metadata", "projects"]);
|
|
7566
7895
|
const text = Object.entries(picked2(args, ["instructions", "model"])).filter(([, v]) => v !== null);
|
|
7567
7896
|
const content = { ...Object.fromEntries(text), skills: args.skills ?? [], commands: args.commands ?? [] };
|
|
@@ -7583,7 +7912,7 @@ var profileLifecycleTools = [
|
|
|
7583
7912
|
const patch = picked2(args, ["title", "description", "tags", "metadata", "projects"]);
|
|
7584
7913
|
if (Object.keys(patch).length === 0)
|
|
7585
7914
|
throw new ArgError2("give at least one field to change");
|
|
7586
|
-
const { profile } = await hub.profiles.update(
|
|
7915
|
+
const { profile } = await hub.profiles.update(need7(args, "id"), { expectedRevision, ...patch }, { signal });
|
|
7587
7916
|
return `updated: ${profileLine(profile)}`;
|
|
7588
7917
|
}
|
|
7589
7918
|
},
|
|
@@ -7597,7 +7926,7 @@ var profileLifecycleTools = [
|
|
|
7597
7926
|
idempotent: true,
|
|
7598
7927
|
run: async (hub, args, signal) => {
|
|
7599
7928
|
const expectedRevision = revision2(args);
|
|
7600
|
-
const id =
|
|
7929
|
+
const id = need7(args, "id");
|
|
7601
7930
|
const given = Object.keys(picked2(args, Object.keys(CONTENT_FIELDS)));
|
|
7602
7931
|
const options = { signal, ...idempotency(args) };
|
|
7603
7932
|
if (args.fromVersion !== undefined) {
|
|
@@ -7618,77 +7947,225 @@ var profileLifecycleTools = [
|
|
|
7618
7947
|
return versionText(await hub.profiles.createVersion(id, { expectedRevision, content: next }, options));
|
|
7619
7948
|
}
|
|
7620
7949
|
},
|
|
7621
|
-
contentTool("profile_skill_set", "Attach a skill to a profile, or replace the skill of that name.", { name: { ...
|
|
7950
|
+
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
7951
|
...content,
|
|
7623
|
-
skills: setNamed(content.skills, { name:
|
|
7952
|
+
skills: setNamed(content.skills, { name: need7(args, "name"), description: need7(args, "description"), body: need7(args, "body") })
|
|
7624
7953
|
})),
|
|
7625
|
-
contentTool("profile_skill_remove", "Detach a skill from a profile.", { name: { ...
|
|
7954
|
+
contentTool("profile_skill_remove", "Detach a skill from a profile.", { name: { ...STRING7, description: "The skill's name." } }, ["name"], (content, args, latest) => ({
|
|
7626
7955
|
...content,
|
|
7627
|
-
skills: removeNamed(content.skills,
|
|
7956
|
+
skills: removeNamed(content.skills, need7(args, "name"), "skill", latest)
|
|
7628
7957
|
})),
|
|
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: { ...
|
|
7958
|
+
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") }) })),
|
|
7959
|
+
contentTool("profile_command_remove", "Detach a command from a profile.", { name: { ...STRING7, description: "The command's name." } }, ["name"], (content, args, latest) => ({
|
|
7631
7960
|
...content,
|
|
7632
|
-
commands: removeNamed(content.commands,
|
|
7961
|
+
commands: removeNamed(content.commands, need7(args, "name"), "command", latest)
|
|
7633
7962
|
})),
|
|
7634
7963
|
stateTool("archive", "Archive a profile: it takes no new versions or pins; pinned sessions keep theirs; reversible with profile_restore."),
|
|
7635
7964
|
stateTool("restore", "Restore an archived profile (it takes versions and pins again)."),
|
|
7636
7965
|
{
|
|
7637
7966
|
name: "profile_assign",
|
|
7638
7967
|
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: { ...
|
|
7968
|
+
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
7969
|
required: ["addr", "profileID", "version", "expectedRevision"],
|
|
7641
7970
|
readOnly: false,
|
|
7642
7971
|
idempotent: true,
|
|
7643
7972
|
run: async (hub, args, signal) => {
|
|
7644
7973
|
const expectedRevision = revision2(args);
|
|
7645
|
-
const { assignment: a } = await hub.profiles.assign(
|
|
7974
|
+
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
7975
|
return `${a.sessionID} pins ${a.profileID} v${a.version} r${a.revision}${a.ack ? `, ack ${a.ack.status}${a.ack.offline ? " (offline)" : ""}` : ""}`;
|
|
7647
7976
|
}
|
|
7648
7977
|
},
|
|
7649
7978
|
{
|
|
7650
7979
|
name: "profile_unassign",
|
|
7651
7980
|
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: { ...
|
|
7981
|
+
properties: { addr: { ...STRING7, description: "The session." }, expectedRevision: { ...REVISION3, description: "The session's pin revision." } },
|
|
7653
7982
|
required: ["addr", "expectedRevision"],
|
|
7654
7983
|
readOnly: false,
|
|
7655
7984
|
idempotent: true,
|
|
7656
7985
|
run: async (hub, args, signal) => {
|
|
7657
|
-
const addr =
|
|
7986
|
+
const addr = need7(args, "addr");
|
|
7658
7987
|
await hub.profiles.unassign(addr, revision2(args), { signal });
|
|
7659
7988
|
return `unpinned ${addr}`;
|
|
7660
7989
|
}
|
|
7661
7990
|
}
|
|
7662
7991
|
];
|
|
7663
7992
|
|
|
7993
|
+
// packages/cli/src/grants.ts
|
|
7994
|
+
var GRANT_FLAGS = {
|
|
7995
|
+
json: "json",
|
|
7996
|
+
holder: "string",
|
|
7997
|
+
project: "multi",
|
|
7998
|
+
allow: "multi",
|
|
7999
|
+
permit: "multi",
|
|
8000
|
+
never: "multi",
|
|
8001
|
+
expires: "string",
|
|
8002
|
+
"max-per-hour": "string",
|
|
8003
|
+
"allow-always": "bool"
|
|
8004
|
+
};
|
|
8005
|
+
var GRANT_REQUEST_FLAGS = { ...GRANT_FLAGS, "permission-allow": "multi", why: "string" };
|
|
8006
|
+
var GRANT_USER_HINT = "only the user manages grants: use the console, or `hub login` with the user's device token";
|
|
8007
|
+
function userOnly2(source, what) {
|
|
8008
|
+
if (source === "env")
|
|
8009
|
+
throw new CliError(EXIT.forbidden, `only the user can ${what} a grant, not a session`, GRANT_USER_HINT);
|
|
8010
|
+
}
|
|
8011
|
+
function grantError(error) {
|
|
8012
|
+
if (error instanceof CliError && error.exit === EXIT.forbidden)
|
|
8013
|
+
return new CliError(error.exit, error.message, [error.hint, GRANT_USER_HINT].filter(Boolean).join("; "), error.envelope);
|
|
8014
|
+
return error;
|
|
8015
|
+
}
|
|
8016
|
+
var listOf = (flags, name) => (flags[name] ?? []).flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
8017
|
+
async function grantSpecArg(flags, stdin) {
|
|
8018
|
+
return await grantArg(flags, stdin, true);
|
|
8019
|
+
}
|
|
8020
|
+
function grantRequestArg(flags, stdin) {
|
|
8021
|
+
return grantArg(flags, stdin, false);
|
|
8022
|
+
}
|
|
8023
|
+
async function grantArg(flags, stdin, holderRequired) {
|
|
8024
|
+
const json = typeof flags.json === "string" ? flags.json : undefined;
|
|
8025
|
+
const flagged = Object.keys(GRANT_REQUEST_FLAGS).filter((name) => name !== "json" && name !== "why" && flags[name] !== undefined);
|
|
8026
|
+
if (json !== undefined) {
|
|
8027
|
+
if (flagged.length)
|
|
8028
|
+
throw new UsageError(`give either --json '{…}' or ${flagged.map((f) => `--${f}`).join(", ")}`);
|
|
8029
|
+
return await jsonObjectArg(json, stdin, "grant");
|
|
8030
|
+
}
|
|
8031
|
+
const holder = str(flags, "holder");
|
|
8032
|
+
if (holder === undefined && holderRequired)
|
|
8033
|
+
throw new UsageError("needs --holder (ses_…, @name, @@orchestrator…, or app:<token id>)");
|
|
8034
|
+
const projects = listOf(flags, "project");
|
|
8035
|
+
if (!projects.length)
|
|
8036
|
+
throw new UsageError("needs --project <path> (repeatable)");
|
|
8037
|
+
const allow = listOf(flags, "allow");
|
|
8038
|
+
if (!allow.length)
|
|
8039
|
+
throw new UsageError("needs --allow <verbs> (approve, deny, answer, prompt, stop, roles, launch)");
|
|
8040
|
+
const permit = [...listOf(flags, "permit"), ...listOf(flags, "permission-allow")];
|
|
8041
|
+
const never = listOf(flags, "never");
|
|
8042
|
+
const expires = str(flags, "expires");
|
|
8043
|
+
const cap = str(flags, "max-per-hour");
|
|
8044
|
+
if (cap !== undefined && !/^\d+$/.test(cap))
|
|
8045
|
+
throw new UsageError("--max-per-hour is a whole number");
|
|
8046
|
+
return {
|
|
8047
|
+
...holder !== undefined ? { holder } : {},
|
|
8048
|
+
scope: { projects },
|
|
8049
|
+
allow,
|
|
8050
|
+
...permit.length || never.length ? { permissions: { ...permit.length ? { allow: permit } : {}, ...never.length ? { never } : {} } } : {},
|
|
8051
|
+
...expires !== undefined ? { expires } : {},
|
|
8052
|
+
...cap !== undefined ? { maxActionsPerHour: Number(cap) } : {},
|
|
8053
|
+
...flags["allow-always"] === true ? { allowAlways: true } : {}
|
|
8054
|
+
};
|
|
8055
|
+
}
|
|
8056
|
+
var iso4 = (ms) => new Date(ms).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
8057
|
+
var grantLine = (g) => [g.id, g.state, g.holder, g.allow.join(","), g.scope.projects.join(","), `until ${iso4(g.expiresAt)}`].join(" ");
|
|
8058
|
+
var grantsText = (grants) => grants.length ? grants.map(grantLine).join(`
|
|
8059
|
+
`) : "no grants";
|
|
8060
|
+
function grantText2(g) {
|
|
8061
|
+
const list = (values) => values.length ? values.join(", ") : "none";
|
|
8062
|
+
return [
|
|
8063
|
+
`${g.id} ${g.state}`,
|
|
8064
|
+
`holder: ${g.holder}${g.holderSessionID && g.holderSessionID !== g.holder ? ` (${g.holderSessionID})` : ""}`,
|
|
8065
|
+
`projects: ${list(g.scope.projects)}`,
|
|
8066
|
+
`verbs: ${list(g.allow)}`,
|
|
8067
|
+
`permissions allow: ${list(g.permissions.allow)}`,
|
|
8068
|
+
`permissions never: ${list(g.permissions.never)}`,
|
|
8069
|
+
`always: ${g.allowAlways ? "yes" : "no"}`,
|
|
8070
|
+
`max actions per hour: ${g.maxActionsPerHour}`,
|
|
8071
|
+
g.revokedAt !== undefined ? `revoked: ${iso4(g.revokedAt)}` : `expires: ${iso4(g.expiresAt)}`
|
|
8072
|
+
].join(`
|
|
8073
|
+
`);
|
|
8074
|
+
}
|
|
8075
|
+
var requestedText = (body) => `requested; waiting for the user (Decision ${body.decision.id})`;
|
|
8076
|
+
|
|
8077
|
+
// packages/cli/src/grants-mcp.ts
|
|
8078
|
+
var STRING8 = { type: "string" };
|
|
8079
|
+
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 };
|
|
8080
|
+
var need8 = (args, key) => {
|
|
8081
|
+
if (typeof args[key] !== "string" || !args[key].trim())
|
|
8082
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
8083
|
+
return args[key];
|
|
8084
|
+
};
|
|
8085
|
+
var spec = (args) => {
|
|
8086
|
+
if (typeof args.grant !== "object" || args.grant === null || Array.isArray(args.grant))
|
|
8087
|
+
throw new ToolArgError('"grant" is required (an object)');
|
|
8088
|
+
return args.grant;
|
|
8089
|
+
};
|
|
8090
|
+
var grantTools = [
|
|
8091
|
+
{
|
|
8092
|
+
name: "grant_request",
|
|
8093
|
+
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.",
|
|
8094
|
+
properties: { grant: GRANT, why: STRING8 },
|
|
8095
|
+
required: ["grant", "why"],
|
|
8096
|
+
readOnly: false,
|
|
8097
|
+
run: async (hub, args, signal) => requestedText(await hub.requestGrant(spec(args), need8(args, "why"), { signal }))
|
|
8098
|
+
},
|
|
8099
|
+
{
|
|
8100
|
+
name: "grant_list",
|
|
8101
|
+
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.",
|
|
8102
|
+
properties: {},
|
|
8103
|
+
readOnly: true,
|
|
8104
|
+
run: async (hub, _args, signal) => grantsText((await hub.heldGrants({ signal })).grants)
|
|
8105
|
+
},
|
|
8106
|
+
{
|
|
8107
|
+
name: "grant_show",
|
|
8108
|
+
description: "Show one grant only if this credential currently holds it. Apps need messages:read; another holder's ID answers not_found.",
|
|
8109
|
+
properties: { id: STRING8 },
|
|
8110
|
+
required: ["id"],
|
|
8111
|
+
readOnly: true,
|
|
8112
|
+
run: async (hub, args, signal) => grantText2((await hub.heldGrant(need8(args, "id"), { signal })).grant)
|
|
8113
|
+
},
|
|
8114
|
+
{
|
|
8115
|
+
name: "grant_revoke",
|
|
8116
|
+
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.",
|
|
8117
|
+
properties: { id: STRING8 },
|
|
8118
|
+
required: ["id"],
|
|
8119
|
+
readOnly: false,
|
|
8120
|
+
run: async (hub, args, signal) => grantText2((await hub.revokeHeldGrant(need8(args, "id"), { signal })).grant)
|
|
8121
|
+
},
|
|
8122
|
+
{
|
|
8123
|
+
name: "grant_create",
|
|
8124
|
+
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}.`,
|
|
8125
|
+
properties: { grant: GRANT },
|
|
8126
|
+
required: ["grant"],
|
|
8127
|
+
readOnly: false,
|
|
8128
|
+
run: async (hub, args, signal) => {
|
|
8129
|
+
const actor = (await hub.whoami({ signal })).actor;
|
|
8130
|
+
if (actor.kind !== "user")
|
|
8131
|
+
throw new CliError(EXIT.forbidden, "only the user can create a grant, not a session or app", GRANT_USER_HINT);
|
|
8132
|
+
try {
|
|
8133
|
+
return grantText2((await hub.createGrant(spec(args), { signal })).grant);
|
|
8134
|
+
} catch (cause) {
|
|
8135
|
+
throw grantError(cause);
|
|
8136
|
+
}
|
|
8137
|
+
}
|
|
8138
|
+
}
|
|
8139
|
+
];
|
|
8140
|
+
|
|
7664
8141
|
// packages/cli/src/watchlist.ts
|
|
7665
|
-
var
|
|
7666
|
-
var list2 = (items) => items.length ? items.map(
|
|
8142
|
+
var safe2 = (value) => sanitizeLabel(String(value), 1024);
|
|
8143
|
+
var list2 = (items) => items.length ? items.map(safe2).join(", ") : "none";
|
|
7667
8144
|
function watchlistConfigText(c) {
|
|
7668
8145
|
return [
|
|
7669
|
-
`revision ${
|
|
7670
|
-
`auto-active: ${c.autoActive ? `on (within ${
|
|
8146
|
+
`revision ${safe2(c.revision)}`,
|
|
8147
|
+
`auto-active: ${c.autoActive ? `on (within ${safe2(c.activeWithinHours)} h)` : "off"} pins: ${c.usePins ? "on" : "off"}`,
|
|
7671
8148
|
`sessions: ${list2(c.sessions)}`,
|
|
7672
8149
|
`projects: ${list2(c.projects)}`,
|
|
7673
|
-
`labels: ${c.labels.length ? c.labels.map((l) => Object.entries(l).map(([k, v]) => `${
|
|
8150
|
+
`labels: ${c.labels.length ? c.labels.map((l) => Object.entries(l).map(([k, v]) => `${safe2(k)}=${safe2(v)}`).join(",")).join("; ") : "none"}`,
|
|
7674
8151
|
`muted: ${list2(c.muted)}`,
|
|
7675
8152
|
`muted projects: ${list2(c.mutedProjects)}`
|
|
7676
8153
|
].join(`
|
|
7677
8154
|
`);
|
|
7678
8155
|
}
|
|
7679
8156
|
function watchlistText(view) {
|
|
7680
|
-
const rows = view.entries.map((e) => `${
|
|
8157
|
+
const rows = view.entries.map((e) => `${safe2(e.sessionID)} ${e.reasons.map(safe2).join(",")}${e.lastActiveAt !== undefined ? ` active ${new Date(e.lastActiveAt).toISOString()}` : ""}`);
|
|
7681
8158
|
return [watchlistConfigText(view.config), "", view.entries.length ? `watching ${view.entries.length}${view.truncated ? " (truncated)" : ""}:` : "watching nothing", ...rows].join(`
|
|
7682
8159
|
`);
|
|
7683
8160
|
}
|
|
7684
8161
|
function watchlistChangedText(verb, r) {
|
|
7685
|
-
return `${r.changed ? `${verb}` : `already ${verb}`}: ${
|
|
8162
|
+
return `${r.changed ? `${verb}` : `already ${verb}`}: ${safe2(r.sessionID)} (revision ${safe2(r.config.revision)})`;
|
|
7686
8163
|
}
|
|
7687
8164
|
var PAST = { watch: "watching", unwatch: "not watching", mute: "muted", unmute: "unmuted" };
|
|
7688
8165
|
var watchlistVerbText = (verb, r) => watchlistChangedText(PAST[verb], r);
|
|
7689
8166
|
var ADDR = { type: "string", description: "Session address: ses_… (or a unique prefix), @name, @@role, @name.project." };
|
|
7690
8167
|
var STRINGS3 = { type: "array", items: { type: "string" } };
|
|
7691
|
-
var
|
|
8168
|
+
var need9 = (args, key) => {
|
|
7692
8169
|
const value = args[key];
|
|
7693
8170
|
if (typeof value !== "string" || !value.trim())
|
|
7694
8171
|
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
@@ -7734,13 +8211,13 @@ var watchlistTools = [
|
|
|
7734
8211
|
throw new ToolArgError(`"expectedRevision" is required (the revision you read)`);
|
|
7735
8212
|
const unknown = Object.keys(args).filter((k) => k !== "expectedRevision" && !UPDATE_FIELDS.includes(k));
|
|
7736
8213
|
if (unknown.length)
|
|
7737
|
-
throw new ToolArgError(`Unknown argument${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${
|
|
8214
|
+
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
8215
|
const patch = {};
|
|
7739
8216
|
for (const field of UPDATE_FIELDS)
|
|
7740
8217
|
if (args[field] !== undefined && args[field] !== null)
|
|
7741
8218
|
patch[field] = args[field];
|
|
7742
8219
|
const r = await hub.concierge.watchlist.update(patch, expected, { signal });
|
|
7743
|
-
const head = r.config.revision === expected ? `No change (revision ${
|
|
8220
|
+
const head = r.config.revision === expected ? `No change (revision ${safe2(expected)})` : `Updated the watchlist (revision ${safe2(expected)} → ${safe2(r.config.revision)})`;
|
|
7744
8221
|
return `${head}
|
|
7745
8222
|
${watchlistConfigText(r.config)}`;
|
|
7746
8223
|
}
|
|
@@ -7755,7 +8232,7 @@ ${watchlistConfigText(r.config)}`;
|
|
|
7755
8232
|
idempotent: true,
|
|
7756
8233
|
run: async (hub, args, signal) => {
|
|
7757
8234
|
const on = flag(args, "watch", true);
|
|
7758
|
-
const addr =
|
|
8235
|
+
const addr = need9(args, "addr");
|
|
7759
8236
|
const r = on ? await hub.concierge.watchlist.watch(addr, { signal }) : await hub.concierge.watchlist.unwatch(addr, { signal });
|
|
7760
8237
|
return watchlistVerbText(on ? "watch" : "unwatch", r);
|
|
7761
8238
|
}
|
|
@@ -7770,7 +8247,7 @@ ${watchlistConfigText(r.config)}`;
|
|
|
7770
8247
|
idempotent: true,
|
|
7771
8248
|
run: async (hub, args, signal) => {
|
|
7772
8249
|
const on = flag(args, "mute", true);
|
|
7773
|
-
const addr =
|
|
8250
|
+
const addr = need9(args, "addr");
|
|
7774
8251
|
const r = on ? await hub.concierge.watchlist.mute(addr, { signal }) : await hub.concierge.watchlist.unmute(addr, { signal });
|
|
7775
8252
|
return watchlistVerbText(on ? "mute" : "unmute", r);
|
|
7776
8253
|
}
|
|
@@ -7781,13 +8258,13 @@ ${watchlistConfigText(r.config)}`;
|
|
|
7781
8258
|
var MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
7782
8259
|
var RPC = { parse: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602, internal: -32603 };
|
|
7783
8260
|
var ArgError3 = ToolArgError;
|
|
7784
|
-
var
|
|
8261
|
+
var need10 = (args, key) => {
|
|
7785
8262
|
const value = args[key];
|
|
7786
8263
|
if (typeof value !== "string" || !value.trim())
|
|
7787
8264
|
throw new ArgError3(`"${key}" is required (a non-empty string)`);
|
|
7788
8265
|
return value;
|
|
7789
8266
|
};
|
|
7790
|
-
var
|
|
8267
|
+
var optional8 = (args, key) => {
|
|
7791
8268
|
const value = args[key];
|
|
7792
8269
|
if (value === undefined || value === null)
|
|
7793
8270
|
return;
|
|
@@ -7865,10 +8342,11 @@ var sendOptions = (args) => {
|
|
|
7865
8342
|
const wake = optionalBool(args, "wake");
|
|
7866
8343
|
const urgent = optionalBool(args, "urgent");
|
|
7867
8344
|
const notAfter = optionalNotAfter(args);
|
|
7868
|
-
|
|
8345
|
+
const item = optional8(args, "item");
|
|
8346
|
+
return { ...wake !== undefined ? { wake } : {}, ...urgent !== undefined ? { urgent } : {}, ...notAfter !== undefined ? { notAfter } : {}, ...item !== undefined ? { item } : {} };
|
|
7869
8347
|
};
|
|
7870
8348
|
var whyOption = (args) => {
|
|
7871
|
-
const why =
|
|
8349
|
+
const why = optional8(args, "why");
|
|
7872
8350
|
return why !== undefined ? { why } : {};
|
|
7873
8351
|
};
|
|
7874
8352
|
var optionalDelivery = (args) => {
|
|
@@ -7911,35 +8389,35 @@ var definedFields = (fields) => {
|
|
|
7911
8389
|
return Object.keys(out).length ? out : undefined;
|
|
7912
8390
|
};
|
|
7913
8391
|
function launchSpec(args) {
|
|
7914
|
-
const repo =
|
|
8392
|
+
const repo = need10(args, "repo");
|
|
7915
8393
|
if (!repo.startsWith("/"))
|
|
7916
8394
|
throw new ArgError3(`"repo" must be an absolute path on the node`);
|
|
7917
8395
|
const kind = optionalEnum(args, "runtime", RUNTIME_KINDS);
|
|
7918
|
-
const profile =
|
|
8396
|
+
const profile = optional8(args, "profile");
|
|
7919
8397
|
if (profile !== undefined && kind === undefined)
|
|
7920
8398
|
throw new ArgError3(`"profile" needs "runtime" (e.g. openshell)`);
|
|
7921
8399
|
const nodeLabels = optionalLabels(args, "nodeLabels");
|
|
7922
|
-
const machine =
|
|
8400
|
+
const machine = optional8(args, "machine");
|
|
7923
8401
|
const prefer = optionalBool(args, "prefer") === true;
|
|
7924
8402
|
if (prefer && machine === undefined && nodeLabels === undefined)
|
|
7925
8403
|
throw new ArgError3(`"prefer" needs "machine" or "nodeLabels"`);
|
|
7926
|
-
const workspace = { repo, ...definedFields({ mode: optionalEnum(args, "mode", WORKSPACE_MODES), branch:
|
|
8404
|
+
const workspace = { repo, ...definedFields({ mode: optionalEnum(args, "mode", WORKSPACE_MODES), branch: optional8(args, "branch"), base: optional8(args, "base"), remote: optional8(args, "remote") }) };
|
|
7927
8405
|
return {
|
|
7928
8406
|
...definedFields({
|
|
7929
|
-
key:
|
|
7930
|
-
name:
|
|
8407
|
+
key: optional8(args, "key"),
|
|
8408
|
+
name: optional8(args, "name")?.replace(/^@+/, ""),
|
|
7931
8409
|
roles: optionalStrings(args, "roles")?.map((r) => r.replace(/^@@/, "")),
|
|
7932
|
-
harness:
|
|
7933
|
-
agent:
|
|
7934
|
-
model:
|
|
8410
|
+
harness: optional8(args, "harness"),
|
|
8411
|
+
agent: optional8(args, "agent"),
|
|
8412
|
+
model: optional8(args, "model")
|
|
7935
8413
|
}),
|
|
7936
8414
|
workspace,
|
|
7937
8415
|
...definedFields({
|
|
7938
8416
|
runtime: kind === undefined ? undefined : { kind, ...profile !== undefined ? { profile } : {} },
|
|
7939
8417
|
placement: definedFields({ labels: nodeLabels, machine, mode: prefer ? "prefer" : undefined }),
|
|
7940
|
-
prompt:
|
|
8418
|
+
prompt: optional8(args, "prompt"),
|
|
7941
8419
|
labels: optionalLabels(args, "labels"),
|
|
7942
|
-
end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout:
|
|
8420
|
+
end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout: optional8(args, "timeout"), cleanup: optionalEnum(args, "cleanup", CLEANUP_POLICIES) }),
|
|
7943
8421
|
notAfter: optionalNotAfter(args)
|
|
7944
8422
|
})
|
|
7945
8423
|
};
|
|
@@ -7986,8 +8464,9 @@ function messageText({ message, answer }) {
|
|
|
7986
8464
|
`);
|
|
7987
8465
|
}
|
|
7988
8466
|
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
|
|
8467
|
+
var STRING9 = { type: "string" };
|
|
7990
8468
|
var SEND = {
|
|
8469
|
+
item: { type: "string", description: "msg/ask: optional itk_ ID of an open item; sender and recipient must hold current seats in the item's team (older hubs ignore it)." },
|
|
7991
8470
|
wake: { type: "boolean", description: "false: admit the message without starting a turn." },
|
|
7992
8471
|
urgent: { type: "boolean", description: "Steer into the recipient's running turn." },
|
|
7993
8472
|
notAfter: { type: ["number", "string"], description: "Never deliver after this time: epoch milliseconds or an ISO 8601 date-time (at most 30 days ahead). Already passed: nothing is sent (expired)." },
|
|
@@ -8010,7 +8489,7 @@ var archiveTools = (archived) => [{
|
|
|
8010
8489
|
organizes: true,
|
|
8011
8490
|
idempotent: true,
|
|
8012
8491
|
run: async (hub, args, signal) => {
|
|
8013
|
-
const addr =
|
|
8492
|
+
const addr = need10(args, "addr");
|
|
8014
8493
|
const result = await hub.setArchived(addr, archived, { signal });
|
|
8015
8494
|
return `${archived ? "Archived" : "Unarchived"} ${addr} (${result.sessionID})${result.changed ? "" : "; already in that state"}`;
|
|
8016
8495
|
}
|
|
@@ -8024,7 +8503,7 @@ var pinTools = (pinned) => [{
|
|
|
8024
8503
|
organizes: true,
|
|
8025
8504
|
idempotent: true,
|
|
8026
8505
|
run: async (hub, args, signal) => {
|
|
8027
|
-
const addr =
|
|
8506
|
+
const addr = need10(args, "addr");
|
|
8028
8507
|
const r = await hub.setPinned(addr, pinned, { signal });
|
|
8029
8508
|
return `${pinned ? "Pinned" : "Unpinned"} ${addr} (${r.sessionID})${r.changed ? "" : "; already in that state"}`;
|
|
8030
8509
|
}
|
|
@@ -8032,14 +8511,14 @@ var pinTools = (pinned) => [{
|
|
|
8032
8511
|
var archiveInactiveTool = {
|
|
8033
8512
|
name: "archive_inactive",
|
|
8034
8513
|
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:
|
|
8514
|
+
properties: { preview: { type: "boolean" }, inactiveFor: STRING9, limit: { type: "integer", minimum: 1, maximum: 200 }, previewToken: STRING9, confirm: { type: "boolean" } },
|
|
8036
8515
|
readOnly: false,
|
|
8037
8516
|
organizes: true,
|
|
8038
8517
|
run: async (hub, args, signal) => {
|
|
8039
8518
|
if (args.preview === true)
|
|
8040
|
-
return JSON.stringify(await hub.previewArchiveInactive(
|
|
8519
|
+
return JSON.stringify(await hub.previewArchiveInactive(need10(args, "inactiveFor"), optionalInt(args, "limit", 1, 200) ?? 50, { signal }));
|
|
8041
8520
|
if (args.confirm === true)
|
|
8042
|
-
return JSON.stringify(await hub.archiveInactive(
|
|
8521
|
+
return JSON.stringify(await hub.archiveInactive(need10(args, "previewToken"), { signal }));
|
|
8043
8522
|
throw new ArgError3("Use preview:true with inactiveFor, then confirm:true with previewToken");
|
|
8044
8523
|
}
|
|
8045
8524
|
};
|
|
@@ -8050,19 +8529,19 @@ var intentTools = [
|
|
|
8050
8529
|
properties: { addr: ADDR2 },
|
|
8051
8530
|
required: ["addr"],
|
|
8052
8531
|
readOnly: true,
|
|
8053
|
-
run: async (hub, args, signal) => intentLine(
|
|
8532
|
+
run: async (hub, args, signal) => intentLine(need10(args, "addr"), (await hub.intent(need10(args, "addr"), { signal })).intent)
|
|
8054
8533
|
},
|
|
8055
8534
|
{
|
|
8056
8535
|
name: "session_intent_set",
|
|
8057
8536
|
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:
|
|
8537
|
+
properties: { addr: ADDR2, text: STRING9, clear: { type: "boolean" }, expectedRevision: { type: "integer", minimum: 0 } },
|
|
8059
8538
|
required: ["addr"],
|
|
8060
8539
|
readOnly: false,
|
|
8061
8540
|
organizes: true,
|
|
8062
8541
|
idempotent: true,
|
|
8063
8542
|
run: async (hub, args, signal) => {
|
|
8064
|
-
const addr =
|
|
8065
|
-
const text =
|
|
8543
|
+
const addr = need10(args, "addr");
|
|
8544
|
+
const text = optional8(args, "text");
|
|
8066
8545
|
const clear = args.clear === true;
|
|
8067
8546
|
if (clear === (text !== undefined))
|
|
8068
8547
|
throw new ArgError3('Give "text" or "clear": true');
|
|
@@ -8072,22 +8551,22 @@ var intentTools = [
|
|
|
8072
8551
|
}
|
|
8073
8552
|
];
|
|
8074
8553
|
var TARGET = { ...ADDR2, description: `${ADDR2.description} Required for an app token; a session token defaults to itself.` };
|
|
8075
|
-
var ROLE2 = { ...
|
|
8076
|
-
var ROLE_PROJECT = { ...
|
|
8554
|
+
var ROLE2 = { ...STRING9, description: "A role (reviewer, @@reviewer or reviewer.project)." };
|
|
8555
|
+
var ROLE_PROJECT = { ...STRING9, description: "Scope the role to this project (as role.project)." };
|
|
8077
8556
|
var MANAGE_NOTE = "Needs scope sessions:manage for an app token";
|
|
8078
8557
|
var roleText2 = (r) => `@@${r.role}${r.project !== undefined ? `.${r.project}` : ""}`;
|
|
8079
8558
|
var nameTools = [
|
|
8080
8559
|
{
|
|
8081
8560
|
name: "name",
|
|
8082
8561
|
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: { ...
|
|
8562
|
+
properties: { addr: TARGET, name: { ...STRING9, description: "The name: 2–40 characters a-z, 0-9 and - (a leading @ is fine)." } },
|
|
8084
8563
|
required: ["name"],
|
|
8085
8564
|
readOnly: false,
|
|
8086
8565
|
organizes: true,
|
|
8087
8566
|
idempotent: true,
|
|
8088
8567
|
run: async (hub, args, signal) => {
|
|
8089
|
-
const session =
|
|
8090
|
-
const r = await hub.name(
|
|
8568
|
+
const session = optional8(args, "addr");
|
|
8569
|
+
const r = await hub.name(need10(args, "name"), { ...session ? { session } : {}, signal });
|
|
8091
8570
|
return `Named ${r.sessionID} @${r.name}${r.alias ? ` (@${r.alias} stays an alias for 24 h)` : ""}`;
|
|
8092
8571
|
}
|
|
8093
8572
|
},
|
|
@@ -8100,9 +8579,9 @@ var nameTools = [
|
|
|
8100
8579
|
organizes: true,
|
|
8101
8580
|
idempotent: true,
|
|
8102
8581
|
run: async (hub, args, signal) => {
|
|
8103
|
-
const session =
|
|
8104
|
-
const project =
|
|
8105
|
-
const r = await hub.claimRole(
|
|
8582
|
+
const session = optional8(args, "addr");
|
|
8583
|
+
const project = optional8(args, "project");
|
|
8584
|
+
const r = await hub.claimRole(need10(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, ...optionalBool(args, "exclusive") ? { exclusive: true } : {}, signal });
|
|
8106
8585
|
return `${r.sessionID} holds ${roleText2(r)} (generation ${r.generation}${r.exclusive ? ", exclusive" : ""})`;
|
|
8107
8586
|
}
|
|
8108
8587
|
},
|
|
@@ -8114,9 +8593,9 @@ var nameTools = [
|
|
|
8114
8593
|
readOnly: false,
|
|
8115
8594
|
organizes: true,
|
|
8116
8595
|
run: async (hub, args, signal) => {
|
|
8117
|
-
const session =
|
|
8118
|
-
const project =
|
|
8119
|
-
const r = await hub.releaseRole(
|
|
8596
|
+
const session = optional8(args, "addr");
|
|
8597
|
+
const project = optional8(args, "project");
|
|
8598
|
+
const r = await hub.releaseRole(need10(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, signal });
|
|
8120
8599
|
return `${r.sessionID} released ${roleText2(r)}`;
|
|
8121
8600
|
}
|
|
8122
8601
|
}
|
|
@@ -8125,16 +8604,16 @@ var eventsTool = {
|
|
|
8125
8604
|
name: "events",
|
|
8126
8605
|
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
8606
|
properties: {
|
|
8128
|
-
cursor: { ...
|
|
8607
|
+
cursor: { ...STRING9, description: "The cursor from the previous events answer (hev1_…); omit to start at the latest." },
|
|
8129
8608
|
wait: { type: "integer", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait while nothing matches (0–${WAIT_POLL_MAX_S}).` },
|
|
8130
8609
|
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:
|
|
8610
|
+
types: { type: "array", items: STRING9, description: "Only these event types (exact, or a family like launch.*)." },
|
|
8611
|
+
sessions: { type: "array", items: STRING9, description: "Only events of these sessions (ses_…, @name, @@role)." }
|
|
8133
8612
|
},
|
|
8134
8613
|
readOnly: true,
|
|
8135
8614
|
run: async (hub, args, signal) => {
|
|
8136
8615
|
const page = await hub.events({
|
|
8137
|
-
cursor:
|
|
8616
|
+
cursor: optional8(args, "cursor"),
|
|
8138
8617
|
wait: optionalInt(args, "wait", 0, WAIT_POLL_MAX_S),
|
|
8139
8618
|
limit: optionalInt(args, "limit", 1, 200),
|
|
8140
8619
|
types: optionalStrings(args, "types"),
|
|
@@ -8149,13 +8628,13 @@ var webhookTools = [
|
|
|
8149
8628
|
{
|
|
8150
8629
|
name: "webhook_create",
|
|
8151
8630
|
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:
|
|
8631
|
+
properties: { url: STRING9, events: { type: "array", items: STRING9 }, label: STRING9 },
|
|
8153
8632
|
required: ["url"],
|
|
8154
8633
|
readOnly: false,
|
|
8155
8634
|
run: async (hub, args, signal) => {
|
|
8156
8635
|
const events = optionalStrings(args, "events");
|
|
8157
|
-
const label =
|
|
8158
|
-
const r = await hub.webhookCreate({ url:
|
|
8636
|
+
const label = optional8(args, "label");
|
|
8637
|
+
const r = await hub.webhookCreate({ url: need10(args, "url"), ...events ? { events } : {}, ...label ? { label } : {} }, { signal });
|
|
8159
8638
|
return `${webhookText(r.webhook)}
|
|
8160
8639
|
signing secret (shown once): ${r.secret}`;
|
|
8161
8640
|
}
|
|
@@ -8174,11 +8653,11 @@ signing secret (shown once): ${r.secret}`;
|
|
|
8174
8653
|
{
|
|
8175
8654
|
name: "webhook_show",
|
|
8176
8655
|
description: "Show one webhook and its retained delivery attempts; never its secret.",
|
|
8177
|
-
properties: { id:
|
|
8656
|
+
properties: { id: STRING9 },
|
|
8178
8657
|
required: ["id"],
|
|
8179
8658
|
readOnly: true,
|
|
8180
8659
|
run: async (hub, args, signal) => {
|
|
8181
|
-
const r = await hub.webhook(
|
|
8660
|
+
const r = await hub.webhook(need10(args, "id"), { signal });
|
|
8182
8661
|
return `${webhookText(r.webhook)}
|
|
8183
8662
|
${JSON.stringify(r.deliveries)}`;
|
|
8184
8663
|
}
|
|
@@ -8186,20 +8665,20 @@ ${JSON.stringify(r.deliveries)}`;
|
|
|
8186
8665
|
{
|
|
8187
8666
|
name: "webhook_delete",
|
|
8188
8667
|
description: "Delete one webhook and its pending deliveries.",
|
|
8189
|
-
properties: { id:
|
|
8668
|
+
properties: { id: STRING9 },
|
|
8190
8669
|
required: ["id"],
|
|
8191
8670
|
readOnly: false,
|
|
8192
8671
|
organizes: true,
|
|
8193
8672
|
idempotent: false,
|
|
8194
8673
|
run: async (hub, args, signal) => {
|
|
8195
|
-
const id =
|
|
8674
|
+
const id = need10(args, "id");
|
|
8196
8675
|
await hub.webhookDelete(id, { signal });
|
|
8197
8676
|
return `Deleted webhook ${id}`;
|
|
8198
8677
|
}
|
|
8199
8678
|
}
|
|
8200
8679
|
];
|
|
8201
8680
|
async function profileTarget(hub, args, signal) {
|
|
8202
|
-
const addr =
|
|
8681
|
+
const addr = optional8(args, "addr");
|
|
8203
8682
|
if (addr)
|
|
8204
8683
|
return addr;
|
|
8205
8684
|
const { actor } = await hub.whoami({ signal });
|
|
@@ -8238,11 +8717,11 @@ var profileTools = [
|
|
|
8238
8717
|
{
|
|
8239
8718
|
name: "profile_skill",
|
|
8240
8719
|
description: "Load one skill's full text from the profile pinned to a session (your own by default), on demand.",
|
|
8241
|
-
properties: { name:
|
|
8720
|
+
properties: { name: STRING9, addr: ADDR2 },
|
|
8242
8721
|
required: ["name"],
|
|
8243
8722
|
readOnly: true,
|
|
8244
8723
|
run: async (hub, args, signal) => {
|
|
8245
|
-
const r = await hub.profiles.sessionSkill(await profileTarget(hub, args, signal),
|
|
8724
|
+
const r = await hub.profiles.sessionSkill(await profileTarget(hub, args, signal), need10(args, "name"), { signal });
|
|
8246
8725
|
return `skill ${r.skill.name} (${r.profileID} v${r.version}):
|
|
8247
8726
|
${sanitizeText(r.skill.body)}`;
|
|
8248
8727
|
}
|
|
@@ -8250,11 +8729,11 @@ ${sanitizeText(r.skill.body)}`;
|
|
|
8250
8729
|
{
|
|
8251
8730
|
name: "profile_command",
|
|
8252
8731
|
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:
|
|
8732
|
+
properties: { name: STRING9, params: { type: "object", additionalProperties: STRING9, description: "Parameter name → text value." }, addr: ADDR2 },
|
|
8254
8733
|
required: ["name"],
|
|
8255
8734
|
readOnly: true,
|
|
8256
8735
|
run: async (hub, args, signal) => {
|
|
8257
|
-
const r = await hub.profiles.sessionRender(await profileTarget(hub, args, signal),
|
|
8736
|
+
const r = await hub.profiles.sessionRender(await profileTarget(hub, args, signal), need10(args, "name"), optionalParams(args), { signal });
|
|
8258
8737
|
return r.prompt;
|
|
8259
8738
|
}
|
|
8260
8739
|
},
|
|
@@ -8273,13 +8752,13 @@ ${sanitizeText(r.skill.body)}`;
|
|
|
8273
8752
|
{
|
|
8274
8753
|
name: "profile_usage",
|
|
8275
8754
|
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:
|
|
8755
|
+
properties: { id: STRING9, limit: { type: "integer", minimum: 1, maximum: 200, description: "Sessions per page (default 50)." }, cursor: STRING9 },
|
|
8277
8756
|
required: ["id"],
|
|
8278
8757
|
readOnly: true,
|
|
8279
8758
|
run: async (hub, args, signal) => {
|
|
8280
8759
|
const limit = optionalInt(args, "limit", 1, 200);
|
|
8281
|
-
const cursor =
|
|
8282
|
-
const u = await hub.profiles.usage(
|
|
8760
|
+
const cursor = optional8(args, "cursor");
|
|
8761
|
+
const u = await hub.profiles.usage(need10(args, "id"), { ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {}, signal });
|
|
8283
8762
|
const c = u.counts;
|
|
8284
8763
|
const plural = (n, word, many = `${word}s`) => `${n} ${n === 1 ? word : many}`;
|
|
8285
8764
|
const lines = [`${u.profileID} (latest v${u.latestVersion})`];
|
|
@@ -8322,7 +8801,7 @@ var needRevision = (args, key = "expectedRevision") => {
|
|
|
8322
8801
|
return value;
|
|
8323
8802
|
};
|
|
8324
8803
|
var idemOption = (args) => {
|
|
8325
|
-
const key =
|
|
8804
|
+
const key = optional8(args, "idempotencyKey");
|
|
8326
8805
|
return key !== undefined ? { idempotencyKey: key } : {};
|
|
8327
8806
|
};
|
|
8328
8807
|
var WORKSPACE = { type: "string", description: "The workspace's ID (ws_…)." };
|
|
@@ -8352,7 +8831,7 @@ var workspaceTools = [
|
|
|
8352
8831
|
required: ["workspace"],
|
|
8353
8832
|
readOnly: true,
|
|
8354
8833
|
run: async (hub, args, signal) => {
|
|
8355
|
-
const id =
|
|
8834
|
+
const id = need10(args, "workspace");
|
|
8356
8835
|
const [{ workspace, bindings }, { members }, graph] = await Promise.all([hub.workspaces.get(id, { signal }), hub.workspaces.members(id, { signal }), hub.workspaces.supervision(id, { signal })]);
|
|
8357
8836
|
const lines = [wsLine(workspace)];
|
|
8358
8837
|
if (workspace.masterMemberID)
|
|
@@ -8368,27 +8847,27 @@ var workspaceTools = [
|
|
|
8368
8847
|
{
|
|
8369
8848
|
name: "workspace_create",
|
|
8370
8849
|
description: `Create a coordination workspace. ${WS_SCOPES}`,
|
|
8371
|
-
properties: { title:
|
|
8850
|
+
properties: { title: STRING9, description: STRING9, tags: { type: "array", items: STRING9 }, idempotencyKey: IDEMPOTENCY3 },
|
|
8372
8851
|
required: ["title"],
|
|
8373
8852
|
readOnly: false,
|
|
8374
8853
|
run: async (hub, args, signal) => {
|
|
8375
|
-
const description =
|
|
8854
|
+
const description = optional8(args, "description");
|
|
8376
8855
|
const tags = optionalStrings(args, "tags");
|
|
8377
|
-
const { workspace } = await hub.workspaces.create({ title:
|
|
8856
|
+
const { workspace } = await hub.workspaces.create({ title: need10(args, "title"), ...description ? { description } : {}, ...tags ? { tags } : {} }, { ...idemOption(args), signal });
|
|
8378
8857
|
return `created ${wsLine(workspace)}`;
|
|
8379
8858
|
}
|
|
8380
8859
|
},
|
|
8381
8860
|
{
|
|
8382
8861
|
name: "workspace_update",
|
|
8383
8862
|
description: `Change a workspace's title, description or tags under its revision. ${WS_SCOPES}`,
|
|
8384
|
-
properties: { workspace: WORKSPACE, expectedRevision: REVISION4, title:
|
|
8863
|
+
properties: { workspace: WORKSPACE, expectedRevision: REVISION4, title: STRING9, description: STRING9, tags: { type: "array", items: STRING9 } },
|
|
8385
8864
|
required: ["workspace", "expectedRevision"],
|
|
8386
8865
|
readOnly: false,
|
|
8387
8866
|
run: async (hub, args, signal) => {
|
|
8388
|
-
const patch = definedFields({ title:
|
|
8867
|
+
const patch = definedFields({ title: optional8(args, "title"), description: optional8(args, "description"), tags: optionalStrings(args, "tags") });
|
|
8389
8868
|
if (!patch)
|
|
8390
8869
|
throw new ArgError3(`give "title", "description" or "tags"`);
|
|
8391
|
-
const { workspace } = await hub.workspaces.update(
|
|
8870
|
+
const { workspace } = await hub.workspaces.update(need10(args, "workspace"), { expectedRevision: needRevision(args), ...patch }, { signal });
|
|
8392
8871
|
return `updated ${wsLine(workspace)}`;
|
|
8393
8872
|
}
|
|
8394
8873
|
},
|
|
@@ -8400,7 +8879,7 @@ var workspaceTools = [
|
|
|
8400
8879
|
readOnly: false,
|
|
8401
8880
|
idempotent: true,
|
|
8402
8881
|
run: async (hub, args, signal) => {
|
|
8403
|
-
const id =
|
|
8882
|
+
const id = need10(args, "workspace");
|
|
8404
8883
|
if (verb === "archive") {
|
|
8405
8884
|
const r = await hub.workspaces.archive(id, needRevision(args), { signal });
|
|
8406
8885
|
return `archived ${wsLine(r.workspace)}; ${r.departed} member(s) departed, ${r.supervision.detached} supervision link(s) detached`;
|
|
@@ -8411,31 +8890,31 @@ var workspaceTools = [
|
|
|
8411
8890
|
{
|
|
8412
8891
|
name: "workspace_bind",
|
|
8413
8892
|
description: `Bind a workspace to a repository (absolute path) on one of the user's machines. ${WS_SCOPES}`,
|
|
8414
|
-
properties: { workspace: WORKSPACE, machineID:
|
|
8893
|
+
properties: { workspace: WORKSPACE, machineID: STRING9, repo: STRING9, label: STRING9, idempotencyKey: IDEMPOTENCY3 },
|
|
8415
8894
|
required: ["workspace", "machineID", "repo"],
|
|
8416
8895
|
readOnly: false,
|
|
8417
8896
|
idempotent: true,
|
|
8418
8897
|
run: async (hub, args, signal) => {
|
|
8419
|
-
const label =
|
|
8420
|
-
const r = await hub.workspaces.bind(
|
|
8898
|
+
const label = optional8(args, "label");
|
|
8899
|
+
const r = await hub.workspaces.bind(need10(args, "workspace"), { machineID: need10(args, "machineID"), repo: need10(args, "repo"), ...label ? { label } : {} }, { ...idemOption(args), signal });
|
|
8421
8900
|
return `${r.created ? "bound" : "already bound"} ${r.binding.id} ${r.binding.machineID}:${r.binding.repo} r${r.binding.revision}`;
|
|
8422
8901
|
}
|
|
8423
8902
|
},
|
|
8424
8903
|
{
|
|
8425
8904
|
name: "workspace_unbind",
|
|
8426
8905
|
description: `Remove a repository binding under the binding's revision. ${WS_SCOPES}`,
|
|
8427
|
-
properties: { workspace: WORKSPACE, bindingID:
|
|
8906
|
+
properties: { workspace: WORKSPACE, bindingID: STRING9, expectedRevision: REVISION4 },
|
|
8428
8907
|
required: ["workspace", "bindingID", "expectedRevision"],
|
|
8429
8908
|
readOnly: false,
|
|
8430
8909
|
run: async (hub, args, signal) => {
|
|
8431
|
-
await hub.workspaces.unbind(
|
|
8432
|
-
return `unbound ${
|
|
8910
|
+
await hub.workspaces.unbind(need10(args, "workspace"), need10(args, "bindingID"), needRevision(args), { signal });
|
|
8911
|
+
return `unbound ${need10(args, "bindingID")}`;
|
|
8433
8912
|
}
|
|
8434
8913
|
},
|
|
8435
8914
|
{
|
|
8436
8915
|
name: "workspace_join",
|
|
8437
8916
|
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:
|
|
8917
|
+
properties: { workspace: WORKSPACE, sessionID: STRING9, role: { type: "string", enum: [...MEMBER_ROLES] }, idempotencyKey: IDEMPOTENCY3 },
|
|
8439
8918
|
required: ["workspace", "sessionID", "role"],
|
|
8440
8919
|
readOnly: false,
|
|
8441
8920
|
idempotent: true,
|
|
@@ -8443,45 +8922,45 @@ var workspaceTools = [
|
|
|
8443
8922
|
const role = optionalEnum(args, "role", MEMBER_ROLES);
|
|
8444
8923
|
if (!role)
|
|
8445
8924
|
throw new ArgError3(`"role" is one of ${MEMBER_ROLES.join(", ")}`);
|
|
8446
|
-
const { member, created } = await hub.workspaces.join(
|
|
8925
|
+
const { member, created } = await hub.workspaces.join(need10(args, "workspace"), { sessionID: need10(args, "sessionID"), role }, { ...idemOption(args), signal });
|
|
8447
8926
|
return `member ${member.id} (${member.sessionID}, ${member.role}, generation ${member.generation}, r${member.revision}) ${created ? "joined" : "was already a member"}`;
|
|
8448
8927
|
}
|
|
8449
8928
|
},
|
|
8450
8929
|
{
|
|
8451
8930
|
name: "workspace_leave",
|
|
8452
8931
|
description: `Remove a member under its revision; its supervision links are detached. ${WS_SCOPES}`,
|
|
8453
|
-
properties: { workspace: WORKSPACE, memberID:
|
|
8932
|
+
properties: { workspace: WORKSPACE, memberID: STRING9, expectedRevision: REVISION4 },
|
|
8454
8933
|
required: ["workspace", "memberID", "expectedRevision"],
|
|
8455
8934
|
readOnly: false,
|
|
8456
8935
|
idempotent: true,
|
|
8457
8936
|
run: async (hub, args, signal) => {
|
|
8458
|
-
const r = await hub.workspaces.leave(
|
|
8937
|
+
const r = await hub.workspaces.leave(need10(args, "workspace"), need10(args, "memberID"), needRevision(args), { signal });
|
|
8459
8938
|
return `member ${r.member.id} left (${r.member.leftReason ?? "removed"}); ${r.supervision.detached} supervision link(s) detached`;
|
|
8460
8939
|
}
|
|
8461
8940
|
},
|
|
8462
8941
|
{
|
|
8463
8942
|
name: "workspace_assign",
|
|
8464
8943
|
description: `Give a current member an assignment (title and optional outcome criteria; instructions travel as messages). ${WS_SCOPES}`,
|
|
8465
|
-
properties: { workspace: WORKSPACE, memberID:
|
|
8944
|
+
properties: { workspace: WORKSPACE, memberID: STRING9, title: STRING9, criteria: { type: "array", items: STRING9 }, ref: STRING9, idempotencyKey: IDEMPOTENCY3 },
|
|
8466
8945
|
required: ["workspace", "memberID", "title"],
|
|
8467
8946
|
readOnly: false,
|
|
8468
8947
|
run: async (hub, args, signal) => {
|
|
8469
8948
|
const criteria = optionalStrings(args, "criteria");
|
|
8470
|
-
const ref =
|
|
8471
|
-
const { assignment } = await hub.workspaces.assign(
|
|
8949
|
+
const ref = optional8(args, "ref");
|
|
8950
|
+
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
8951
|
return `assignment ${assignment.id} → ${assignment.memberID}: ${assignment.status}, r${assignment.revision}`;
|
|
8473
8952
|
}
|
|
8474
8953
|
},
|
|
8475
8954
|
{
|
|
8476
8955
|
name: "workspace_assignments",
|
|
8477
8956
|
description: `A workspace's assignments (id, member, status, revision, title), optionally one member's or one status. ${WS_SCOPES}`,
|
|
8478
|
-
properties: { workspace: WORKSPACE, memberID:
|
|
8957
|
+
properties: { workspace: WORKSPACE, memberID: STRING9, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] } },
|
|
8479
8958
|
required: ["workspace"],
|
|
8480
8959
|
readOnly: true,
|
|
8481
8960
|
run: async (hub, args, signal) => {
|
|
8482
|
-
const memberID =
|
|
8961
|
+
const memberID = optional8(args, "memberID");
|
|
8483
8962
|
const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
|
|
8484
|
-
const r = await hub.workspaces.assignments(
|
|
8963
|
+
const r = await hub.workspaces.assignments(need10(args, "workspace"), { ...memberID ? { memberID } : {}, ...status ? { status } : {}, limit: 200, signal });
|
|
8485
8964
|
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
8965
|
`) : "no assignments";
|
|
8487
8966
|
}
|
|
@@ -8489,16 +8968,16 @@ var workspaceTools = [
|
|
|
8489
8968
|
{
|
|
8490
8969
|
name: "workspace_assignment_update",
|
|
8491
8970
|
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:
|
|
8971
|
+
properties: { workspace: WORKSPACE, assignmentID: STRING9, expectedRevision: REVISION4, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] }, memberID: STRING9 },
|
|
8493
8972
|
required: ["workspace", "assignmentID", "expectedRevision"],
|
|
8494
8973
|
readOnly: false,
|
|
8495
8974
|
run: async (hub, args, signal) => {
|
|
8496
8975
|
const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
|
|
8497
|
-
const memberID =
|
|
8976
|
+
const memberID = optional8(args, "memberID");
|
|
8498
8977
|
if (status === undefined === (memberID === undefined))
|
|
8499
8978
|
throw new ArgError3(`give exactly one of "status" or "memberID"`);
|
|
8500
|
-
const id =
|
|
8501
|
-
const assignmentID =
|
|
8979
|
+
const id = need10(args, "workspace");
|
|
8980
|
+
const assignmentID = need10(args, "assignmentID");
|
|
8502
8981
|
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
8982
|
return `assignment ${assignment.id} → ${assignment.memberID}: ${assignment.status}, r${assignment.revision}`;
|
|
8504
8983
|
}
|
|
@@ -8506,16 +8985,16 @@ var workspaceTools = [
|
|
|
8506
8985
|
{
|
|
8507
8986
|
name: "workspace_supervise",
|
|
8508
8987
|
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:
|
|
8988
|
+
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
8989
|
required: ["workspace", "memberID", "supervisor", "expectedRevision"],
|
|
8511
8990
|
readOnly: false,
|
|
8512
8991
|
run: async (hub, args, signal) => {
|
|
8513
|
-
const supervisor = args.supervisor === null ? null :
|
|
8992
|
+
const supervisor = args.supervisor === null ? null : need10(args, "supervisor");
|
|
8514
8993
|
const order = args.order;
|
|
8515
8994
|
if (order !== undefined && (typeof order !== "number" || !Number.isFinite(order)))
|
|
8516
8995
|
throw new ArgError3(`"order" must be a number`);
|
|
8517
|
-
const r = await hub.workspaces.supervise(
|
|
8518
|
-
return `${r.change}: ${
|
|
8996
|
+
const r = await hub.workspaces.supervise(need10(args, "workspace"), { memberID: need10(args, "memberID"), supervisorMemberID: supervisor, ...order !== undefined ? { order } : {}, expectedRevision: needRevision(args) }, { signal });
|
|
8997
|
+
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
8998
|
}
|
|
8520
8999
|
},
|
|
8521
9000
|
{
|
|
@@ -8525,7 +9004,7 @@ var workspaceTools = [
|
|
|
8525
9004
|
required: ["workspace"],
|
|
8526
9005
|
readOnly: true,
|
|
8527
9006
|
run: async (hub, args, signal) => {
|
|
8528
|
-
const v = await hub.workspaces.master(
|
|
9007
|
+
const v = await hub.workspaces.master(need10(args, "workspace"), { signal });
|
|
8529
9008
|
const line = (m) => `${m.member.id} ${m.member.sessionID} generation ${m.member.generation} ${m.presence}${m.member.leftReason ? ` (${m.member.leftReason})` : ""}`;
|
|
8530
9009
|
return [
|
|
8531
9010
|
`workspace ${v.workspaceID} r${v.revision}`,
|
|
@@ -8539,17 +9018,17 @@ var workspaceTools = [
|
|
|
8539
9018
|
{
|
|
8540
9019
|
name: "workspace_master_replace",
|
|
8541
9020
|
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:
|
|
9021
|
+
properties: { workspace: WORKSPACE, sessionID: STRING9, launchID: STRING9, expectedRevision: REVISION4, idempotencyKey: IDEMPOTENCY3 },
|
|
8543
9022
|
required: ["workspace", "expectedRevision"],
|
|
8544
9023
|
readOnly: false,
|
|
8545
9024
|
idempotent: true,
|
|
8546
9025
|
run: async (hub, args, signal) => {
|
|
8547
|
-
const sessionID =
|
|
8548
|
-
const launchID =
|
|
9026
|
+
const sessionID = optional8(args, "sessionID");
|
|
9027
|
+
const launchID = optional8(args, "launchID");
|
|
8549
9028
|
if (sessionID === undefined === (launchID === undefined))
|
|
8550
9029
|
throw new ArgError3(`give exactly one of "sessionID" or "launchID"`);
|
|
8551
9030
|
const target = sessionID !== undefined ? { sessionID } : { launchID };
|
|
8552
|
-
const r = await hub.workspaces.replaceMaster(
|
|
9031
|
+
const r = await hub.workspaces.replaceMaster(need10(args, "workspace"), { ...target, expectedRevision: needRevision(args) }, { ...idemOption(args), signal });
|
|
8553
9032
|
if (!r.changed)
|
|
8554
9033
|
return `${r.master.sessionID} already is the master (${r.master.id}); nothing changed`;
|
|
8555
9034
|
return [
|
|
@@ -8567,7 +9046,7 @@ var workspaceTools = [
|
|
|
8567
9046
|
required: ["workspace"],
|
|
8568
9047
|
readOnly: true,
|
|
8569
9048
|
run: async (hub, args, signal) => {
|
|
8570
|
-
const b = await hub.workspaces.board(
|
|
9049
|
+
const b = await hub.workspaces.board(need10(args, "workspace"), { signal });
|
|
8571
9050
|
const more = (truncated) => truncated ? " (more not shown)" : "";
|
|
8572
9051
|
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
9052
|
const lines = [wsLine(b.workspace), ...b.workspace.masterMemberID ? [`master: ${b.workspace.masterMemberID}`] : []];
|
|
@@ -8593,12 +9072,22 @@ var TOOLS = [
|
|
|
8593
9072
|
...webhookTools,
|
|
8594
9073
|
...profileTools,
|
|
8595
9074
|
...profileLifecycleTools,
|
|
9075
|
+
...grantTools,
|
|
8596
9076
|
...workspaceTools,
|
|
8597
9077
|
...artifactTools,
|
|
8598
9078
|
...artifactJobTools,
|
|
8599
9079
|
...teamIntakeTools,
|
|
9080
|
+
...subscriptionTools,
|
|
9081
|
+
...operationTools,
|
|
8600
9082
|
...teamTools,
|
|
8601
9083
|
...watchlistTools,
|
|
9084
|
+
{
|
|
9085
|
+
name: "agent_limits",
|
|
9086
|
+
description: "Read the hub-wide agent hop limit, the hop ceiling (the highest hop limit the hub accepts) and launch caps, their sources and revision. Read-only; only an admin may change them in the console.",
|
|
9087
|
+
properties: {},
|
|
9088
|
+
readOnly: true,
|
|
9089
|
+
run: async (hub, _args, signal) => JSON.stringify(await hub.call("GET", "/api/agent/agent-limits", { signal }), null, 2)
|
|
9090
|
+
},
|
|
8602
9091
|
{
|
|
8603
9092
|
name: "whoami",
|
|
8604
9093
|
description: "Who this hub credential is: the app token of `hub login` (or the session of HUB_SESSION_TOKEN) and its user.",
|
|
@@ -8617,14 +9106,14 @@ var TOOLS = [
|
|
|
8617
9106
|
name: "sessions",
|
|
8618
9107
|
description: "The sessions as a flat list, optionally filtered.",
|
|
8619
9108
|
properties: {
|
|
8620
|
-
project: { ...
|
|
8621
|
-
state: { ...
|
|
8622
|
-
machine: { ...
|
|
8623
|
-
label: { type: "array", items:
|
|
9109
|
+
project: { ...STRING9, description: "Only this project." },
|
|
9110
|
+
state: { ...STRING9, description: "Only these states (comma-separated, e.g. busy,blocked)." },
|
|
9111
|
+
machine: { ...STRING9, description: "Only this machine." },
|
|
9112
|
+
label: { type: "array", items: STRING9, description: "key=value labels that must all match." }
|
|
8624
9113
|
},
|
|
8625
9114
|
readOnly: true,
|
|
8626
9115
|
run: async (hub, args, signal) => {
|
|
8627
|
-
const body = await hub.sessions({ project:
|
|
9116
|
+
const body = await hub.sessions({ project: optional8(args, "project"), state: optional8(args, "state"), machine: optional8(args, "machine"), label: optionalStrings(args, "label"), signal });
|
|
8628
9117
|
return sessionsText(body.sessions ?? []);
|
|
8629
9118
|
}
|
|
8630
9119
|
},
|
|
@@ -8634,7 +9123,7 @@ var TOOLS = [
|
|
|
8634
9123
|
properties: { addr: ADDR2 },
|
|
8635
9124
|
required: ["addr"],
|
|
8636
9125
|
readOnly: true,
|
|
8637
|
-
run: async (hub, args, signal) => showText(await hub.show(
|
|
9126
|
+
run: async (hub, args, signal) => showText(await hub.show(need10(args, "addr"), { signal }))
|
|
8638
9127
|
},
|
|
8639
9128
|
{
|
|
8640
9129
|
name: "log",
|
|
@@ -8643,7 +9132,7 @@ var TOOLS = [
|
|
|
8643
9132
|
required: ["addr"],
|
|
8644
9133
|
readOnly: true,
|
|
8645
9134
|
run: async (hub, args, signal) => {
|
|
8646
|
-
const addr =
|
|
9135
|
+
const addr = need10(args, "addr");
|
|
8647
9136
|
const n = optionalInt(args, "n", 1, 200);
|
|
8648
9137
|
return logText((await hub.log(addr, { ...n !== undefined ? { n } : {}, signal })).rows ?? []);
|
|
8649
9138
|
}
|
|
@@ -8652,25 +9141,25 @@ var TOOLS = [
|
|
|
8652
9141
|
name: "who",
|
|
8653
9142
|
description: "Named and role-holding sessions; a team member sees its teams, grouped by team with its reach to each.",
|
|
8654
9143
|
properties: {
|
|
8655
|
-
project: { ...
|
|
8656
|
-
role: { ...
|
|
8657
|
-
team: { ...
|
|
9144
|
+
project: { ...STRING9, description: "Only this project." },
|
|
9145
|
+
role: { ...STRING9, description: "Only holders of this role (reviewer, @@reviewer or reviewer#team)." },
|
|
9146
|
+
team: { ...STRING9, description: "Only this team (name or ID)." }
|
|
8658
9147
|
},
|
|
8659
9148
|
readOnly: true,
|
|
8660
9149
|
run: async (hub, args, signal) => {
|
|
8661
|
-
const body = await hub.who({ project:
|
|
9150
|
+
const body = await hub.who({ project: optional8(args, "project"), role: optional8(args, "role"), team: optional8(args, "team"), signal });
|
|
8662
9151
|
return whoText(body.sessions ?? [], body);
|
|
8663
9152
|
}
|
|
8664
9153
|
},
|
|
8665
9154
|
{
|
|
8666
9155
|
name: "msg",
|
|
8667
9156
|
description: "Tell a session something (fire and forget). Sent as this server's credential, not as an OpenCode session.",
|
|
8668
|
-
properties: { to: TO, text: { ...
|
|
9157
|
+
properties: { to: TO, text: { ...STRING9, description: "The message." }, ...SEND, idempotencyKey: IDEMPOTENCY3 },
|
|
8669
9158
|
required: ["to", "text"],
|
|
8670
9159
|
readOnly: false,
|
|
8671
9160
|
run: async (hub, args, signal) => {
|
|
8672
|
-
const to =
|
|
8673
|
-
const text =
|
|
9161
|
+
const to = need10(args, "to");
|
|
9162
|
+
const text = need10(args, "text");
|
|
8674
9163
|
const options = { ...sendOptions(args), ...idemOption(args), signal };
|
|
8675
9164
|
if (optionalBool(args, "all"))
|
|
8676
9165
|
return sentAllText(await hub.msg(to, text, { ...options, all: true }));
|
|
@@ -8679,10 +9168,11 @@ var TOOLS = [
|
|
|
8679
9168
|
},
|
|
8680
9169
|
{
|
|
8681
9170
|
name: "ask",
|
|
8682
|
-
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.`,
|
|
9171
|
+
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. To @me it opens a Decision for the user: give options (buttons) with a recommendation, or choices (several points in one ask), and urgency, tier and kind.`,
|
|
8683
9172
|
properties: {
|
|
8684
9173
|
to: TO,
|
|
8685
|
-
text: { ...
|
|
9174
|
+
text: { ...STRING9, description: "The question." },
|
|
9175
|
+
...DECISION_ASK_PROPERTIES,
|
|
8686
9176
|
wait: { type: "number", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait for the answer (0–${WAIT_POLL_MAX_S}).` },
|
|
8687
9177
|
expires: { type: ["number", "string"], description: "When the ask expires: seconds, or 30s, 5m, 2h, 1d (1 minute to 7 days; default 24 h)." },
|
|
8688
9178
|
...SEND,
|
|
@@ -8691,11 +9181,17 @@ var TOOLS = [
|
|
|
8691
9181
|
required: ["to", "text"],
|
|
8692
9182
|
readOnly: false,
|
|
8693
9183
|
run: async (hub, args, signal) => {
|
|
8694
|
-
const to =
|
|
8695
|
-
const text =
|
|
9184
|
+
const to = need10(args, "to");
|
|
9185
|
+
const text = need10(args, "text");
|
|
8696
9186
|
const wait = optionalWait(args);
|
|
8697
9187
|
const expires = optionalExpires(args);
|
|
8698
|
-
|
|
9188
|
+
let decision;
|
|
9189
|
+
try {
|
|
9190
|
+
decision = decisionAskFields(args);
|
|
9191
|
+
} catch (cause) {
|
|
9192
|
+
throw new ArgError3(cause.message);
|
|
9193
|
+
}
|
|
9194
|
+
const options = { ...sendOptions(args), ...expires !== undefined ? { expires } : {}, ...idemOption(args), ...decision, signal };
|
|
8699
9195
|
if (optionalBool(args, "all")) {
|
|
8700
9196
|
if (wait !== undefined)
|
|
8701
9197
|
throw new ArgError3(`"wait" cannot be combined with "all"`);
|
|
@@ -8710,12 +9206,12 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
8710
9206
|
{
|
|
8711
9207
|
name: "reply",
|
|
8712
9208
|
description: "Reply to a message you received; it goes to the message's real sender.",
|
|
8713
|
-
properties: { id: MSG_ID, text: { ...
|
|
9209
|
+
properties: { id: MSG_ID, text: { ...STRING9, description: "The reply." }, wake: SEND.wake },
|
|
8714
9210
|
required: ["id", "text"],
|
|
8715
9211
|
readOnly: false,
|
|
8716
9212
|
run: async (hub, args, signal) => {
|
|
8717
|
-
const id =
|
|
8718
|
-
const text =
|
|
9213
|
+
const id = need10(args, "id");
|
|
9214
|
+
const text = need10(args, "text");
|
|
8719
9215
|
const wake = optionalBool(args, "wake");
|
|
8720
9216
|
return repliedText(await hub.reply(id, text, { ...wake !== undefined ? { wake } : {}, signal }));
|
|
8721
9217
|
}
|
|
@@ -8723,12 +9219,12 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
8723
9219
|
{
|
|
8724
9220
|
name: "decline",
|
|
8725
9221
|
description: "Decline an ask you received, with a reason.",
|
|
8726
|
-
properties: { id: MSG_ID, why: { ...
|
|
9222
|
+
properties: { id: MSG_ID, why: { ...STRING9, description: "The reason." } },
|
|
8727
9223
|
required: ["id", "why"],
|
|
8728
9224
|
readOnly: false,
|
|
8729
9225
|
run: async (hub, args, signal) => {
|
|
8730
|
-
const id =
|
|
8731
|
-
return declinedText(await hub.decline(id,
|
|
9226
|
+
const id = need10(args, "id");
|
|
9227
|
+
return declinedText(await hub.decline(id, need10(args, "why"), { signal }));
|
|
8732
9228
|
}
|
|
8733
9229
|
},
|
|
8734
9230
|
{
|
|
@@ -8748,7 +9244,7 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
8748
9244
|
required: ["id"],
|
|
8749
9245
|
readOnly: true,
|
|
8750
9246
|
run: async (hub, args, signal) => {
|
|
8751
|
-
const id =
|
|
9247
|
+
const id = need10(args, "id");
|
|
8752
9248
|
const wait = optionalWait(args);
|
|
8753
9249
|
return messageText(await hub.message(id, { ...wait !== undefined ? { wait } : {}, signal }));
|
|
8754
9250
|
}
|
|
@@ -8761,7 +9257,7 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
8761
9257
|
readOnly: false,
|
|
8762
9258
|
organizes: true,
|
|
8763
9259
|
idempotent: true,
|
|
8764
|
-
run: async (hub, args, signal) => mutedText((await hub[verb](
|
|
9260
|
+
run: async (hub, args, signal) => mutedText((await hub[verb](need10(args, "addr"), { signal })).muted)
|
|
8765
9261
|
})),
|
|
8766
9262
|
{
|
|
8767
9263
|
name: "decisions",
|
|
@@ -8769,7 +9265,7 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
8769
9265
|
properties: { state: { type: "string", enum: [...DECISION_STATES], description: "Only Decisions in this state." } },
|
|
8770
9266
|
readOnly: true,
|
|
8771
9267
|
run: async (hub, args, signal) => {
|
|
8772
|
-
const state =
|
|
9268
|
+
const state = optional8(args, "state");
|
|
8773
9269
|
if (state !== undefined && !DECISION_STATES.includes(state))
|
|
8774
9270
|
throw new ArgError3(`"state" is one of ${DECISION_STATES.join(", ")}`);
|
|
8775
9271
|
const { decisions } = await hub.decisions({ ...state ? { state } : {}, signal });
|
|
@@ -8784,7 +9280,7 @@ ${decisionsText(decisions)}` : decisionsText(decisions);
|
|
|
8784
9280
|
required: ["id"],
|
|
8785
9281
|
readOnly: true,
|
|
8786
9282
|
run: async (hub, args, signal) => `${DECISIONS_NOTE}
|
|
8787
|
-
${decisionText((await hub.decision(
|
|
9283
|
+
${decisionText((await hub.decision(need10(args, "id"), { signal })).decision)}`
|
|
8788
9284
|
},
|
|
8789
9285
|
{
|
|
8790
9286
|
name: "approve",
|
|
@@ -8793,8 +9289,8 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8793
9289
|
required: ["addr", "requestID"],
|
|
8794
9290
|
readOnly: false,
|
|
8795
9291
|
run: async (hub, args, signal) => {
|
|
8796
|
-
const addr =
|
|
8797
|
-
const requestID =
|
|
9292
|
+
const addr = need10(args, "addr");
|
|
9293
|
+
const requestID = need10(args, "requestID");
|
|
8798
9294
|
const always = optionalBool(args, "always");
|
|
8799
9295
|
return approvedText(await hub.approve(addr, requestID, { ...always ? { always } : {}, ...whyOption(args), signal }));
|
|
8800
9296
|
}
|
|
@@ -8806,8 +9302,8 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8806
9302
|
required: ["addr", "requestID"],
|
|
8807
9303
|
readOnly: false,
|
|
8808
9304
|
run: async (hub, args, signal) => {
|
|
8809
|
-
const addr =
|
|
8810
|
-
const requestID =
|
|
9305
|
+
const addr = need10(args, "addr");
|
|
9306
|
+
const requestID = need10(args, "requestID");
|
|
8811
9307
|
return deniedText(await hub.deny(addr, requestID, { ...whyOption(args), signal }));
|
|
8812
9308
|
}
|
|
8813
9309
|
},
|
|
@@ -8824,8 +9320,8 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8824
9320
|
required: ["addr", "formID"],
|
|
8825
9321
|
readOnly: false,
|
|
8826
9322
|
run: async (hub, args, signal) => {
|
|
8827
|
-
const addr =
|
|
8828
|
-
const formID =
|
|
9323
|
+
const addr = need10(args, "addr");
|
|
9324
|
+
const formID = need10(args, "formID");
|
|
8829
9325
|
const given = formAnswer(args);
|
|
8830
9326
|
const options = { ...whyOption(args), signal };
|
|
8831
9327
|
return answeredText(given === "cancel" ? await hub.cancelForm(addr, formID, options) : await hub.answer(addr, formID, given, options), given === "cancel");
|
|
@@ -8836,15 +9332,15 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8836
9332
|
description: `Prompt a session (an agent's prompt carries the hub's provenance). ${NEEDS_USER}`,
|
|
8837
9333
|
properties: {
|
|
8838
9334
|
addr: ADDR2,
|
|
8839
|
-
text: { ...
|
|
9335
|
+
text: { ...STRING9, description: "The prompt." },
|
|
8840
9336
|
delivery: { type: "string", enum: [...DELIVERIES], description: "How it enters a busy session (the spoke picks when unset)." },
|
|
8841
9337
|
why: WHY
|
|
8842
9338
|
},
|
|
8843
9339
|
required: ["addr", "text"],
|
|
8844
9340
|
readOnly: false,
|
|
8845
9341
|
run: async (hub, args, signal) => {
|
|
8846
|
-
const addr =
|
|
8847
|
-
const text =
|
|
9342
|
+
const addr = need10(args, "addr");
|
|
9343
|
+
const text = need10(args, "text");
|
|
8848
9344
|
const delivery = optionalDelivery(args);
|
|
8849
9345
|
return promptedText(await hub.prompt(addr, text, { ...delivery ? { delivery } : {}, ...whyOption(args), signal }));
|
|
8850
9346
|
}
|
|
@@ -8855,32 +9351,32 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8855
9351
|
properties: { addr: ADDR2, why: WHY },
|
|
8856
9352
|
required: ["addr"],
|
|
8857
9353
|
readOnly: false,
|
|
8858
|
-
run: async (hub, args, signal) => stoppedText(await hub.stop(
|
|
9354
|
+
run: async (hub, args, signal) => stoppedText(await hub.stop(need10(args, "addr"), { ...whyOption(args), signal }))
|
|
8859
9355
|
},
|
|
8860
9356
|
{
|
|
8861
9357
|
name: "launch",
|
|
8862
9358
|
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
9359
|
properties: {
|
|
8864
|
-
repo: { ...
|
|
9360
|
+
repo: { ...STRING9, description: "Absolute path of the repository on the node (or where a clone goes)." },
|
|
8865
9361
|
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: { ...
|
|
9362
|
+
branch: { ...STRING9, description: "The branch to create or check out." },
|
|
9363
|
+
base: { ...STRING9, description: "What a new branch starts from." },
|
|
9364
|
+
remote: { ...STRING9, description: "An https or ssh git remote (for clone)." },
|
|
9365
|
+
key: { ...STRING9, description: "Idempotency key: retrying with the same key and arguments answers the same launch." },
|
|
9366
|
+
name: { ...STRING9, description: "The session's @name." },
|
|
9367
|
+
roles: { type: "array", items: STRING9, description: "Roles the session claims (e.g. reviewer.hub)." },
|
|
9368
|
+
harness: { ...STRING9, description: "The harness (default opencode)." },
|
|
9369
|
+
agent: { ...STRING9, description: "The harness agent (e.g. build)." },
|
|
9370
|
+
model: { ...STRING9, description: "provider/model." },
|
|
8875
9371
|
runtime: { type: "string", enum: [...RUNTIME_KINDS], description: "host (default) or openshell (a sandbox)." },
|
|
8876
|
-
profile: { ...
|
|
8877
|
-
machine: { ...
|
|
9372
|
+
profile: { ...STRING9, description: "The runtime's sandbox profile (needs runtime)." },
|
|
9373
|
+
machine: { ...STRING9, description: "The machine (name or ID) to run on." },
|
|
8878
9374
|
nodeLabels: { ...LABELS, description: 'Node labels to place on (e.g. {"os": "darwin"}).' },
|
|
8879
9375
|
prefer: { type: "boolean", description: "machine and nodeLabels are preferences, not requirements." },
|
|
8880
9376
|
labels: { ...LABELS, description: "Free-form labels for finding the launch and its session later (never interpreted by the hub)." },
|
|
8881
|
-
prompt: { ...
|
|
9377
|
+
prompt: { ...STRING9, description: "The session's first prompt." },
|
|
8882
9378
|
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: { ...
|
|
9379
|
+
timeout: { ...STRING9, description: "Longest run, e.g. 4h or 90m." },
|
|
8884
9380
|
cleanup: { type: "string", enum: [...CLEANUP_POLICIES], description: "keep (default) or remove the workspace when it ends." },
|
|
8885
9381
|
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
9382
|
},
|
|
@@ -8896,12 +9392,12 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8896
9392
|
name: "launches",
|
|
8897
9393
|
description: "Your launches, oldest first (an app or session sees the ones it started).",
|
|
8898
9394
|
properties: {
|
|
8899
|
-
state: { ...
|
|
8900
|
-
label: { type: "array", items:
|
|
9395
|
+
state: { ...STRING9, description: "Only these states (comma-separated: queued, placing, provisioning, running, ended)." },
|
|
9396
|
+
label: { type: "array", items: STRING9, description: "key=value launch labels that must all match." }
|
|
8901
9397
|
},
|
|
8902
9398
|
readOnly: true,
|
|
8903
9399
|
run: async (hub, args, signal) => {
|
|
8904
|
-
const state =
|
|
9400
|
+
const state = optional8(args, "state")?.split(",").filter(Boolean);
|
|
8905
9401
|
const label = optionalStrings(args, "label");
|
|
8906
9402
|
return launchesText((await hub.launches.list({ ...state?.length ? { state } : {}, ...label ? { label } : {}, signal })).launches ?? []);
|
|
8907
9403
|
}
|
|
@@ -8912,7 +9408,7 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8912
9408
|
properties: { id: LAUNCH_ID },
|
|
8913
9409
|
required: ["id"],
|
|
8914
9410
|
readOnly: true,
|
|
8915
|
-
run: async (hub, args, signal) => launchText((await hub.launches.get(
|
|
9411
|
+
run: async (hub, args, signal) => launchText((await hub.launches.get(need10(args, "id"), { signal })).launch)
|
|
8916
9412
|
},
|
|
8917
9413
|
{
|
|
8918
9414
|
name: "launch_stop",
|
|
@@ -8920,7 +9416,7 @@ ${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
|
8920
9416
|
properties: { id: LAUNCH_ID },
|
|
8921
9417
|
required: ["id"],
|
|
8922
9418
|
readOnly: false,
|
|
8923
|
-
run: async (hub, args, signal) => stoppedLaunchText((await hub.launches.stop(
|
|
9419
|
+
run: async (hub, args, signal) => stoppedLaunchText((await hub.launches.stop(need10(args, "id"), { signal })).launch)
|
|
8924
9420
|
}
|
|
8925
9421
|
];
|
|
8926
9422
|
var toolList = () => TOOLS.map((t) => ({
|
|
@@ -8959,7 +9455,7 @@ var clean = (text, token) => {
|
|
|
8959
9455
|
var toolResult = (text, isError) => ({ content: [{ type: "text", text }], isError });
|
|
8960
9456
|
var ok2 = (id, result) => ({ jsonrpc: "2.0", id, result });
|
|
8961
9457
|
var rpcError = (id, code, message) => ({ jsonrpc: "2.0", id, error: { code, message } });
|
|
8962
|
-
var
|
|
9458
|
+
var isObject5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8963
9459
|
var isId = (value) => typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
8964
9460
|
function createMcpServer(options) {
|
|
8965
9461
|
const initialize = (params) => {
|
|
@@ -8974,7 +9470,7 @@ function createMcpServer(options) {
|
|
|
8974
9470
|
if (!tool)
|
|
8975
9471
|
return rpcError(id, RPC.invalidParams, typeof params.name === "string" ? `unknown tool: ${params.name}` : "params.name (the tool) is required");
|
|
8976
9472
|
const args = params.arguments ?? {};
|
|
8977
|
-
if (!
|
|
9473
|
+
if (!isObject5(args))
|
|
8978
9474
|
return rpcError(id, RPC.invalidParams, "params.arguments must be an object");
|
|
8979
9475
|
let token;
|
|
8980
9476
|
const controller = new AbortController;
|
|
@@ -9005,7 +9501,7 @@ function createMcpServer(options) {
|
|
|
9005
9501
|
inFlight.clear();
|
|
9006
9502
|
},
|
|
9007
9503
|
async handle(message) {
|
|
9008
|
-
if (!
|
|
9504
|
+
if (!isObject5(message))
|
|
9009
9505
|
return rpcError(null, RPC.invalidRequest, Array.isArray(message) ? "batches are not supported" : "not a JSON-RPC message");
|
|
9010
9506
|
const hasId = "id" in message;
|
|
9011
9507
|
const id = isId(message.id) ? message.id : null;
|
|
@@ -9016,12 +9512,12 @@ function createMcpServer(options) {
|
|
|
9016
9512
|
if (typeof message.method !== "string")
|
|
9017
9513
|
return rpcError(id, RPC.invalidRequest, "method must be a string");
|
|
9018
9514
|
if (!hasId) {
|
|
9019
|
-
notify(message.method,
|
|
9515
|
+
notify(message.method, isObject5(message.params) ? message.params : {});
|
|
9020
9516
|
return;
|
|
9021
9517
|
}
|
|
9022
9518
|
if (!isId(message.id))
|
|
9023
9519
|
return rpcError(null, RPC.invalidRequest, "id must be a string or a number");
|
|
9024
|
-
const params =
|
|
9520
|
+
const params = isObject5(message.params) ? message.params : {};
|
|
9025
9521
|
switch (message.method) {
|
|
9026
9522
|
case "initialize":
|
|
9027
9523
|
return ok2(id, initialize(params));
|
|
@@ -9115,90 +9611,6 @@ async function watch(credentials, options, write, signal) {
|
|
|
9115
9611
|
throw new CliError(EXIT.other, "the watch stream ended (the hub closed it)", AGAIN);
|
|
9116
9612
|
}
|
|
9117
9613
|
|
|
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
9614
|
// packages/cli/src/node.ts
|
|
9203
9615
|
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
9616
|
import path16 from "node:path";
|
|
@@ -9424,7 +9836,7 @@ import { arch as osArch, platform as osPlatform } from "node:os";
|
|
|
9424
9836
|
// packages/node/package.json
|
|
9425
9837
|
var package_default2 = {
|
|
9426
9838
|
name: "@huynle/hub-node",
|
|
9427
|
-
version: "0.
|
|
9839
|
+
version: "0.11.1",
|
|
9428
9840
|
private: true,
|
|
9429
9841
|
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
9842
|
license: "MIT",
|
|
@@ -10879,7 +11291,7 @@ import path14 from "node:path";
|
|
|
10879
11291
|
// packages/runtime-openshell/package.json
|
|
10880
11292
|
var package_default3 = {
|
|
10881
11293
|
name: "@huynle/hub-runtime-openshell",
|
|
10882
|
-
version: "0.
|
|
11294
|
+
version: "0.11.1",
|
|
10883
11295
|
private: true,
|
|
10884
11296
|
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
11297
|
license: "MIT",
|
|
@@ -12534,46 +12946,6 @@ function openShellCheckText(report) {
|
|
|
12534
12946
|
`);
|
|
12535
12947
|
}
|
|
12536
12948
|
|
|
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
12949
|
// packages/claude-code/src/main.ts
|
|
12578
12950
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
12579
12951
|
import { realpathSync as realpathSync5 } from "node:fs";
|
|
@@ -12659,7 +13031,7 @@ function ask(socketPath, req, options = {}) {
|
|
|
12659
13031
|
var ENV_NAME3 = /^[A-Z_][A-Z0-9_]*$/;
|
|
12660
13032
|
var DROPPED = ["tool_response"];
|
|
12661
13033
|
var quote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
12662
|
-
var
|
|
13034
|
+
var isObject6 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
12663
13035
|
function flags(argv) {
|
|
12664
13036
|
const rewake = argv.includes("--rewake");
|
|
12665
13037
|
const at = argv.indexOf("--wait");
|
|
@@ -12697,16 +13069,16 @@ async function runHook(io) {
|
|
|
12697
13069
|
const launch = io.env.HUB_CLAUDE_LAUNCH;
|
|
12698
13070
|
const req = { v: WIRE_VERSION, kind: "hook", input: sent, ...launch ? { launch } : {}, ...rewake ? { rewake: true } : {}, ...waitMs !== undefined ? { waitMs } : {} };
|
|
12699
13071
|
const reply = await ask(io.socketPath, req, io.timeoutMs !== undefined ? { timeoutMs: io.timeoutMs } : {});
|
|
12700
|
-
if (!
|
|
13072
|
+
if (!isObject6(reply))
|
|
12701
13073
|
return 0;
|
|
12702
|
-
if (
|
|
13074
|
+
if (isObject6(reply.env))
|
|
12703
13075
|
writeEnvFile(io.env.CLAUDE_ENV_FILE, reply.env);
|
|
12704
13076
|
if (rewake && reply.exit === 2) {
|
|
12705
13077
|
if (typeof reply.stderr === "string")
|
|
12706
13078
|
io.stderr(reply.stderr);
|
|
12707
13079
|
return 2;
|
|
12708
13080
|
}
|
|
12709
|
-
if (
|
|
13081
|
+
if (isObject6(reply.output))
|
|
12710
13082
|
io.stdout(`${JSON.stringify(reply.output)}
|
|
12711
13083
|
`);
|
|
12712
13084
|
return 0;
|
|
@@ -13600,7 +13972,7 @@ function ensurePrivateDir(dir) {
|
|
|
13600
13972
|
import { Socket as Socket2 } from "node:net";
|
|
13601
13973
|
var MCP_END = { hub: "end" };
|
|
13602
13974
|
var ACT_TOOLS = new Set(["approve", "deny", "answer", "prompt", "stop"]);
|
|
13603
|
-
var
|
|
13975
|
+
var isObject7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
13604
13976
|
function createMcpRouter(options) {
|
|
13605
13977
|
const credentials = (pick, what) => () => {
|
|
13606
13978
|
const sessionID = options.sessionID();
|
|
@@ -13613,9 +13985,9 @@ function createMcpRouter(options) {
|
|
|
13613
13985
|
const act = createMcpServer({ version: options.version, credentials: credentials(options.actToken, "hub session act token") });
|
|
13614
13986
|
return {
|
|
13615
13987
|
async handle(message) {
|
|
13616
|
-
if (
|
|
13988
|
+
if (isObject7(message) && message.method === "tools/call" && isObject7(message.params) && ACT_TOOLS.has(message.params.name))
|
|
13617
13989
|
return act.handle(message);
|
|
13618
|
-
if (
|
|
13990
|
+
if (isObject7(message) && !("id" in message))
|
|
13619
13991
|
act.handle(message);
|
|
13620
13992
|
return shell.handle(message);
|
|
13621
13993
|
},
|
|
@@ -14459,9 +14831,10 @@ class ClaudeSessions {
|
|
|
14459
14831
|
this.#settle(s, undefined, true);
|
|
14460
14832
|
s.stopRequested = false;
|
|
14461
14833
|
const detail = [input.error_details, input.error].find((v) => typeof v === "string" && v.length > 0) ?? "the turn failed";
|
|
14834
|
+
const token = typeof input.error === "string" && /^[a-z][a-z0-9_]{0,40}$/.test(input.error) ? input.error : undefined;
|
|
14462
14835
|
const turnID = s.turnID;
|
|
14463
14836
|
s.busy = false;
|
|
14464
|
-
this.#emit({ type: "session.execution.failed", data: { sessionID: s.hubID, ...turnID ? { turnID } : {}, error: { message: cut4(detail, HARNESS_ERROR_MAX) } } });
|
|
14837
|
+
this.#emit({ type: "session.execution.failed", data: { sessionID: s.hubID, ...turnID ? { turnID } : {}, error: { message: cut4(detail, HARNESS_ERROR_MAX), ...token ? { type: `claude.${token}` } : {} } } });
|
|
14465
14838
|
return {};
|
|
14466
14839
|
}
|
|
14467
14840
|
#child(s, input, start = true) {
|
|
@@ -14877,7 +15250,7 @@ import path23 from "node:path";
|
|
|
14877
15250
|
// packages/claude-code/package.json
|
|
14878
15251
|
var package_default4 = {
|
|
14879
15252
|
name: "@huynle/hub-claude-code",
|
|
14880
|
-
version: "0.
|
|
15253
|
+
version: "0.11.1",
|
|
14881
15254
|
private: true,
|
|
14882
15255
|
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
15256
|
license: "MIT",
|
|
@@ -15166,7 +15539,7 @@ import path26 from "node:path";
|
|
|
15166
15539
|
// packages/preset/package.json
|
|
15167
15540
|
var package_default5 = {
|
|
15168
15541
|
name: "@huynle/hub-preset",
|
|
15169
|
-
version: "0.
|
|
15542
|
+
version: "0.11.1",
|
|
15170
15543
|
private: true,
|
|
15171
15544
|
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
15545
|
license: "MIT",
|
|
@@ -15691,7 +16064,7 @@ import { openSync as openSync6, closeSync as closeSync6, readFileSync as readFil
|
|
|
15691
16064
|
// packages/codex/package.json
|
|
15692
16065
|
var package_default6 = {
|
|
15693
16066
|
name: "@huynle/hub-codex",
|
|
15694
|
-
version: "0.
|
|
16067
|
+
version: "0.11.1",
|
|
15695
16068
|
private: true,
|
|
15696
16069
|
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
16070
|
license: "MIT",
|
|
@@ -16041,6 +16414,12 @@ var DEFAULT_TITLE = "Codex session";
|
|
|
16041
16414
|
var TOOL_ITEMS = new Set(["commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall", "webSearch"]);
|
|
16042
16415
|
var APPROVALS = { "item/commandExecution/requestApproval": "shell", "item/fileChange/requestApproval": "edit" };
|
|
16043
16416
|
var RECENT_TURNS = 50;
|
|
16417
|
+
function codexErrorType(info) {
|
|
16418
|
+
const name = typeof info === "string" ? info : isObj3(info) && Object.keys(info).length === 1 ? Object.keys(info)[0] : undefined;
|
|
16419
|
+
if (!name || !/^[a-z][A-Za-z0-9]{0,40}$/.test(name))
|
|
16420
|
+
return;
|
|
16421
|
+
return `codex.${name.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`)}`;
|
|
16422
|
+
}
|
|
16044
16423
|
var isObj3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
16045
16424
|
var str6 = (v) => typeof v === "string" && v !== "" ? v : undefined;
|
|
16046
16425
|
var cut5 = (text, max) => text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
@@ -16428,7 +16807,8 @@ class CodexSessions {
|
|
|
16428
16807
|
return;
|
|
16429
16808
|
const status = typeof turn.status === "string" ? turn.status : "completed";
|
|
16430
16809
|
const error = isObj3(turn.error) && typeof turn.error.message === "string" ? cut5(turn.error.message, HARNESS_ERROR_MAX) : undefined;
|
|
16431
|
-
const
|
|
16810
|
+
const errorType = isObj3(turn.error) ? codexErrorType(turn.error.codexErrorInfo) : undefined;
|
|
16811
|
+
const end = { status, ...error !== undefined ? { error } : {}, ...errorType !== undefined ? { errorType } : {} };
|
|
16432
16812
|
if (thread.turnID === turnID)
|
|
16433
16813
|
thread.turnID = undefined;
|
|
16434
16814
|
thread.ended.set(turnID, end);
|
|
@@ -16441,7 +16821,7 @@ class CodexSessions {
|
|
|
16441
16821
|
if (status === "interrupted")
|
|
16442
16822
|
this.#emit({ type: "session.execution.interrupted", data });
|
|
16443
16823
|
else if (status === "failed")
|
|
16444
|
-
this.#emit({ type: "session.execution.failed", data: { ...data, ...error !== undefined ? { error: { message: error } } : {} } });
|
|
16824
|
+
this.#emit({ type: "session.execution.failed", data: { ...data, ...error !== undefined ? { error: { message: error, ...end.errorType ? { type: end.errorType } : {} } } : {} } });
|
|
16445
16825
|
else
|
|
16446
16826
|
this.#emit({ type: "session.execution.succeeded", data });
|
|
16447
16827
|
thread.items.clear();
|
|
@@ -18444,7 +18824,7 @@ function mergeConfigText(text, format, sets) {
|
|
|
18444
18824
|
return txFail("manual_edit_required", `the config cannot be edited losslessly (${error instanceof Error ? error.message : "unreadable"}); make the edit by hand`);
|
|
18445
18825
|
}
|
|
18446
18826
|
const tree = toValue(current, cst);
|
|
18447
|
-
if (!
|
|
18827
|
+
if (!isObject8(tree))
|
|
18448
18828
|
return txFail("conflict", "the config is not a JSON object");
|
|
18449
18829
|
const changed = applySet(tree, set);
|
|
18450
18830
|
if (!changed.ok)
|
|
@@ -18459,7 +18839,7 @@ function mergeConfigText(text, format, sets) {
|
|
|
18459
18839
|
}
|
|
18460
18840
|
return txOk(current);
|
|
18461
18841
|
}
|
|
18462
|
-
var
|
|
18842
|
+
var isObject8 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18463
18843
|
var same = (a, b) => canonicalJSON(a) === canonicalJSON(b);
|
|
18464
18844
|
var clone = (v) => JSON.parse(JSON.stringify(v));
|
|
18465
18845
|
function segments(pointer) {
|
|
@@ -18483,7 +18863,7 @@ function applySet(root, set) {
|
|
|
18483
18863
|
if (index === undefined)
|
|
18484
18864
|
return txFail("conflict", `${set.pointer}: no array element ${seg}`);
|
|
18485
18865
|
next = current[index];
|
|
18486
|
-
} else if (
|
|
18866
|
+
} else if (isObject8(current)) {
|
|
18487
18867
|
next = Object.hasOwn(current, seg) ? current[seg] : undefined;
|
|
18488
18868
|
if (next === undefined) {
|
|
18489
18869
|
next = segs[i + 1] === "-" ? [] : {};
|
|
@@ -18511,7 +18891,7 @@ function applySet(root, set) {
|
|
|
18511
18891
|
current[index] = value;
|
|
18512
18892
|
return txOk(true);
|
|
18513
18893
|
}
|
|
18514
|
-
if (!
|
|
18894
|
+
if (!isObject8(current))
|
|
18515
18895
|
return txFail("conflict", `${set.pointer}: the parent is not an object or array`);
|
|
18516
18896
|
if (Object.hasOwn(current, last) && same(current[last], value))
|
|
18517
18897
|
return txOk(false);
|
|
@@ -18680,7 +19060,7 @@ var LEGACY_PLUGIN_PACKAGE = "@huynle/opencode-voice";
|
|
|
18680
19060
|
var HUB_MCP_NAME = "hub";
|
|
18681
19061
|
var MCP_SUBCOMMAND = "mcp";
|
|
18682
19062
|
var HUB_PROGRAMS = new Set(["hub", "hubctl"]);
|
|
18683
|
-
var
|
|
19063
|
+
var isObject9 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18684
19064
|
var basename = (p) => p.slice(p.lastIndexOf("/") + 1);
|
|
18685
19065
|
var WINDOWS_PATH = /^[A-Za-z]:[\\/]/;
|
|
18686
19066
|
var programName = (p) => {
|
|
@@ -18715,12 +19095,12 @@ function classifyPluginSpec(spec) {
|
|
|
18715
19095
|
return { source: "custom" };
|
|
18716
19096
|
return;
|
|
18717
19097
|
}
|
|
18718
|
-
var specOf = (entry) => typeof entry === "string" ? entry :
|
|
19098
|
+
var specOf = (entry) => typeof entry === "string" ? entry : isObject9(entry) && typeof entry.package === "string" ? entry.package : undefined;
|
|
18719
19099
|
var parse3 = (text) => {
|
|
18720
19100
|
const parsed = parseJsonc(text);
|
|
18721
19101
|
if (!parsed.ok)
|
|
18722
19102
|
return txFail("manual_edit_required", `the OpenCode config cannot be edited losslessly (${parsed.message}); make the edit by hand`);
|
|
18723
|
-
if (!
|
|
19103
|
+
if (!isObject9(parsed.value))
|
|
18724
19104
|
return txFail("conflict", "the OpenCode config is not a JSON object");
|
|
18725
19105
|
return txOk(parsed.value);
|
|
18726
19106
|
};
|
|
@@ -18736,7 +19116,7 @@ function inspectPlugin(config, desired, findings) {
|
|
|
18736
19116
|
const spec = specOf(entry);
|
|
18737
19117
|
const kind = spec === undefined ? undefined : classifyPluginSpec(spec);
|
|
18738
19118
|
if (kind)
|
|
18739
|
-
found.push({ pointer: `/${key}/${i}`, spec, object:
|
|
19119
|
+
found.push({ pointer: `/${key}/${i}`, spec, object: isObject9(entry), ...kind });
|
|
18740
19120
|
});
|
|
18741
19121
|
}
|
|
18742
19122
|
if (found.length === 0)
|
|
@@ -18767,17 +19147,17 @@ function inspectPlugin(config, desired, findings) {
|
|
|
18767
19147
|
findings.push({ code: "plugin-drift", message: `the Hub plugin is pinned to ${entry.version}; it becomes ${pluginSpec(desired)}` });
|
|
18768
19148
|
return txOk({ state: "drift", ...base, object: entry.object });
|
|
18769
19149
|
}
|
|
18770
|
-
var isHubMcp = (server) =>
|
|
19150
|
+
var isHubMcp = (server) => isObject9(server) && server.type === "local" && Array.isArray(server.command) && server.command.length === 2 && server.command.every((w) => typeof w === "string") && HUB_PROGRAMS.has(programName(server.command[0])) && server.command[1] === MCP_SUBCOMMAND;
|
|
18771
19151
|
function inspectMcp(config, desired, findings) {
|
|
18772
19152
|
const mcp = config.mcp;
|
|
18773
19153
|
if (mcp === undefined)
|
|
18774
19154
|
return txOk({ state: "absent" });
|
|
18775
|
-
if (!
|
|
19155
|
+
if (!isObject9(mcp))
|
|
18776
19156
|
return txFail("conflict", '"mcp" in the OpenCode config is not an object; fix it by hand');
|
|
18777
19157
|
const servers = mcp.servers;
|
|
18778
19158
|
if (servers === undefined)
|
|
18779
19159
|
return txOk({ state: "absent" });
|
|
18780
|
-
if (!
|
|
19160
|
+
if (!isObject9(servers))
|
|
18781
19161
|
return txFail("conflict", '"mcp.servers" in the OpenCode config is not an object; fix it by hand');
|
|
18782
19162
|
const elsewhere = Object.entries(servers).find(([name, server]) => name !== HUB_MCP_NAME && isHubMcp(server));
|
|
18783
19163
|
if (elsewhere)
|
|
@@ -26246,19 +26626,22 @@ Identity
|
|
|
26246
26626
|
hub role release <role[.project]> [--session <addr>]
|
|
26247
26627
|
|
|
26248
26628
|
Messaging (text "-" reads it from stdin)
|
|
26249
|
-
hub msg <addr> "text" [--all] [--no-wake] [--urgent] [--not-after <ms|ISO time>] [--key K]
|
|
26629
|
+
hub msg <addr> "text" [--all] [--no-wake] [--urgent] [--item itk_…] [--not-after <ms|ISO time>] [--key K]
|
|
26250
26630
|
tell: fire and forget; --not-after: never delivered after that time
|
|
26251
|
-
hub ask <addr> "text" [--wait [secs]] [--expires 2h] [--not-after <ms|ISO time>] [--all] [--no-wake] [--urgent] [--key K]
|
|
26631
|
+
hub ask <addr> "text" [--wait [secs]] [--expires 2h] [--item itk_…] [--not-after <ms|ISO time>] [--all] [--no-wake] [--urgent] [--key K]
|
|
26252
26632
|
tracked until answered, declined or expired; --wait alone waits until
|
|
26253
26633
|
it expires; exit 0 answered, 1 declined or no answer yet, 8 expired;
|
|
26254
26634
|
--key (Idempotency-Key): a retry with the same key and text answers
|
|
26255
|
-
the first send again and sends nothing (24 h, per credential)
|
|
26635
|
+
the first send again and sends nothing (24 h, per credential);
|
|
26636
|
+
--item: open team intake item, both members seated (older hubs ignore it)
|
|
26256
26637
|
hub reply <msgID> "text" [--no-wake] goes to the real sender
|
|
26257
26638
|
hub decline <msgID> --why "reason"
|
|
26258
26639
|
hub ask @me "question" [--option id=label[:safe]]... [--recommend id [--why "…"]] [--urgency low|normal|high]
|
|
26259
26640
|
[--deadline 2h [--on-timeout <safe id>]] [--tier confirm|tap] [--about <addr>]...
|
|
26641
|
+
[--choice "D1=Label:a=Option*,b=Option"]... [--kind question|plan|merge|release]
|
|
26260
26642
|
opens a Decision for the user (answered in the console; the answer
|
|
26261
|
-
comes back as a reply); --on-timeout picks a safe option at the deadline
|
|
26643
|
+
comes back as a reply); --on-timeout picks a safe option at the deadline;
|
|
26644
|
+
--choice (instead of --option) asks several points, * marks the recommendation
|
|
26262
26645
|
hub decisions list [--state open|answered|expired|closed] the Decisions you asked
|
|
26263
26646
|
hub inbox asks to answer, your open asks, recent messages, mutes
|
|
26264
26647
|
hub mute <addr> · hub unmute <addr> refuse (or accept again) messages from a session
|
|
@@ -26431,11 +26814,13 @@ var whyOf = (flags) => {
|
|
|
26431
26814
|
var sendFlags = (flags) => {
|
|
26432
26815
|
const notAfter = str(flags, "not-after");
|
|
26433
26816
|
const key = str(flags, "key");
|
|
26817
|
+
const item = str(flags, "item");
|
|
26434
26818
|
return {
|
|
26435
26819
|
...flags["no-wake"] === true ? { wake: false } : {},
|
|
26436
26820
|
...flags.urgent === true ? { urgent: true } : {},
|
|
26437
26821
|
...notAfter === undefined ? {} : { notAfter: notAfterArg(notAfter, "--not-after") },
|
|
26438
|
-
...key !== undefined ? { idempotencyKey: key } : {}
|
|
26822
|
+
...key !== undefined ? { idempotencyKey: key } : {},
|
|
26823
|
+
...item !== undefined ? { item } : {}
|
|
26439
26824
|
};
|
|
26440
26825
|
};
|
|
26441
26826
|
var nodeIo = (io) => ({
|
|
@@ -26559,7 +26944,7 @@ var COMMANDS = {
|
|
|
26559
26944
|
},
|
|
26560
26945
|
msg: {
|
|
26561
26946
|
args: ["<addr>", "<text>"],
|
|
26562
|
-
flags: { all: "bool", "no-wake": "bool", urgent: "bool", "not-after": "string", key: "string" },
|
|
26947
|
+
flags: { all: "bool", "no-wake": "bool", urgent: "bool", "not-after": "string", item: "string", key: "string" },
|
|
26563
26948
|
async handle(context) {
|
|
26564
26949
|
const { flags, args } = context.parsed;
|
|
26565
26950
|
const text = await textArg(args[1], context.io.stdin);
|
|
@@ -26571,7 +26956,7 @@ var COMMANDS = {
|
|
|
26571
26956
|
},
|
|
26572
26957
|
ask: {
|
|
26573
26958
|
args: ["<addr>", "<text>"],
|
|
26574
|
-
flags: { all: "bool", "no-wake": "bool", urgent: "bool", "not-after": "string", key: "string", expires: "string", wait: "optional", ...DECISION_FLAGS },
|
|
26959
|
+
flags: { all: "bool", "no-wake": "bool", urgent: "bool", "not-after": "string", item: "string", key: "string", expires: "string", wait: "optional", ...DECISION_FLAGS },
|
|
26575
26960
|
async handle(context) {
|
|
26576
26961
|
const { flags, args } = context.parsed;
|
|
26577
26962
|
const decision = decisionOptions(flags, args[0]);
|
|
@@ -26715,7 +27100,7 @@ ${openedText(body.decision)}` : outcome.text);
|
|
|
26715
27100
|
"grant create": {
|
|
26716
27101
|
flags: GRANT_FLAGS,
|
|
26717
27102
|
async handle(context) {
|
|
26718
|
-
|
|
27103
|
+
userOnly2(context.credentials().source, "create");
|
|
26719
27104
|
const spec = await grantSpecArg(context.parsed.flags, context.io.stdin);
|
|
26720
27105
|
const body = await grantHub(context, (hub) => hub.createGrant(spec));
|
|
26721
27106
|
answer(context, body, (b) => `created ${grantText2(b.grant)}`);
|
|
@@ -26735,7 +27120,7 @@ ${openedText(body.decision)}` : outcome.text);
|
|
|
26735
27120
|
"grant revoke": {
|
|
26736
27121
|
args: ["<grantID>"],
|
|
26737
27122
|
async handle(context) {
|
|
26738
|
-
|
|
27123
|
+
userOnly2(context.credentials().source, "revoke");
|
|
26739
27124
|
const body = await grantHub(context, (hub) => hub.revokeGrant(context.parsed.args[0]));
|
|
26740
27125
|
answer(context, body, (b) => `revoked ${b.grant.id} (${b.grant.holder})`);
|
|
26741
27126
|
}
|