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