@huynle/hub 0.8.0 → 0.8.2
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 +326 -1
- package/README.md +85 -16
- 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 +723 -281
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/artifact-jobs-mcp.ts +10 -9
- package/packages/cli/src/artifacts-mcp.ts +8 -7
- 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 +9 -4
- 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/team-intake-mcp.ts +8 -7
- package/packages/cli/src/teams-mcp.ts +61 -3
- package/packages/cli/src/tool-args.ts +6 -0
- package/packages/cli/src/watchlist.ts +5 -4
- 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/concierge.ts +4 -1
- package/packages/protocol/src/embedded-media.ts +59 -0
- package/packages/protocol/src/index.ts +90 -7
- package/packages/protocol/src/launch.ts +14 -0
- package/packages/protocol/src/wire.ts +1 -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/client.ts +3 -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/packages/spoke-sdk/src/connection.ts +13 -1
- package/src/central/agent/acting-routes.ts +3 -2
- package/src/central/agent/acting.ts +70 -12
- package/src/central/agent/audit.ts +1 -1
- package/src/central/agent/decisions.ts +58 -2
- package/src/central/agent/delivery.ts +111 -28
- package/src/central/agent/errors.ts +2 -0
- package/src/central/agent/escalation.ts +39 -10
- 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 +60 -17
- 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 +11 -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/context.ts +3 -1
- package/src/central/concierge/inbound.ts +4 -7
- package/src/central/concierge/prompt.ts +10 -6
- 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 +87 -35
- package/src/central/main.ts +1 -1
- package/src/central/registry.ts +49 -4
- package/src/central/revert-windows.ts +66 -0
- package/src/central/spoke-ws.ts +6 -3
- package/src/central/teams/intake.ts +20 -6
- package/src/central/teams/staffing.ts +24 -2
- package/src/central/workspaces/sidechats-routes.ts +1 -1
- package/src/index.ts +63 -30
- package/src/migrate.ts +109 -3
- package/src/spoke/deliver.ts +6 -0
- package/src/spoke/env-inject.ts +8 -1
- package/src/spoke/fork-revert.ts +94 -9
- package/src/spoke/hub-tool.ts +26 -12
- 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 +120 -0
- package/src/spoke/profile-apply.ts +24 -14
- package/src/spoke/session-context.ts +183 -33
- package/src/spoke/spoke.ts +129 -41
- package/src/spoke/timeline.ts +30 -3
- package/src/spoke/tracker.ts +17 -1
- package/src/status.ts +2 -1
- package/web/dist/assets/ArtifactPage-R0ZTpHca.js +1 -0
- package/web/dist/assets/ArtifactPane-C9KrBna8.js +1 -0
- package/web/dist/assets/ArtifactWorkbench-sP2mZccC.js +6 -0
- package/web/dist/assets/Library-BXbJvTUS.js +2 -0
- package/web/dist/assets/ProfilesDestination-D7XyLTJ7.js +1 -0
- package/web/dist/assets/TeamsPage-Dgxt8UI3.js +1 -0
- package/web/dist/assets/Watchtower-DJHx6cWw.js +2 -0
- package/web/dist/assets/index-BabrNDB3.css +1 -0
- package/web/dist/assets/index-g4V9mfSr.js +34 -0
- package/web/dist/assets/live-OgQWPrSY.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-DjEipyto.js} +1 -1
- package/web/dist/assets/{renderer3d-BXIVOvfi.js → renderer3d-B648jKGO.js} +1 -1
- package/web/dist/assets/{safe-area-DfPsOdWR.js → safe-area-CNLyboIj.js} +1 -1
- package/web/dist/assets/{silero-worker-C8-8vLVS.js → silero-worker-DhlIHyD_.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.2",
|
|
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",
|
|
@@ -863,7 +863,7 @@ class HubClient {
|
|
|
863
863
|
return this.call("POST", `/api/decisions/${segment(id)}/answer`, { body, ...signalOf(answer) });
|
|
864
864
|
}
|
|
865
865
|
approve(addr, requestID, options = {}) {
|
|
866
|
-
const body = { requestID, ...options.always ? { always: true } : {}, ...whyField(options) };
|
|
866
|
+
const body = { requestID, ...options.always ? { always: true } : {}, ...options.recommend ? { recommend: true } : {}, ...whyField(options) };
|
|
867
867
|
return this.call("POST", `${AGENT}/sessions/${segment(addr)}/approve`, { body, ...signalOf(options) });
|
|
868
868
|
}
|
|
869
869
|
deny(addr, requestID, options = {}) {
|
|
@@ -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 {
|
|
@@ -3810,6 +3852,9 @@ function formAnswerProblem(v) {
|
|
|
3810
3852
|
}
|
|
3811
3853
|
var REVERT_FILES_MAX = 50;
|
|
3812
3854
|
var REVERT_FILE_MAX_CHARS = 1000;
|
|
3855
|
+
function isRevertCommitted(v) {
|
|
3856
|
+
return isObj(v) && nonEmpty(v.messageID) && v.messageID.length <= 300 && (v.from === undefined || num(v.from)) && num(v.until) && num(v.at);
|
|
3857
|
+
}
|
|
3813
3858
|
var FORWARDED_EVENTS = new Set([
|
|
3814
3859
|
"session.created",
|
|
3815
3860
|
"session.renamed",
|
|
@@ -3901,13 +3946,14 @@ function isLaunchCreate(req) {
|
|
|
3901
3946
|
}
|
|
3902
3947
|
var AGENT_NAME_MAX = 100;
|
|
3903
3948
|
var PERMISSION_MESSAGE_MAX = 1000;
|
|
3949
|
+
var RES_CODE_MAX = 40;
|
|
3904
3950
|
var isModelSelection = (v) => isObj(v) && typeof v.providerID === "string" && ID.test(v.providerID) && typeof v.modelID === "string" && ID.test(v.modelID) && (v.variant === undefined || typeof v.variant === "string" && ID.test(v.variant));
|
|
3905
3951
|
var SESSION_TOKEN_TEXT = new RegExp(`^[\\x21-\\x7e]{1,${SESSION_TOKEN_MAX_CHARS}}$`);
|
|
3906
3952
|
function isSessionToken(v) {
|
|
3907
3953
|
return isObj(v) && nonEmpty(v.sessionID) && v.sessionID.length <= 300 && str2(v.token) && SESSION_TOKEN_TEXT.test(v.token) && num(v.exp);
|
|
3908
3954
|
}
|
|
3909
3955
|
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);
|
|
3956
|
+
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.revertCommitted === undefined || isRevertCommitted(v.revertCommitted)) && (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
3957
|
}
|
|
3912
3958
|
var REVERT_STATUS = new Set(["added", "deleted", "modified"]);
|
|
3913
3959
|
var count = (v) => Number.isSafeInteger(v) && v >= 0;
|
|
@@ -3915,7 +3961,7 @@ function isRevertFile(v) {
|
|
|
3915
3961
|
return isObj(v) && nonEmpty(v.file) && v.file.length <= REVERT_FILE_MAX_CHARS && (v.status === undefined || REVERT_STATUS.has(v.status)) && (v.additions === undefined || count(v.additions)) && (v.deletions === undefined || count(v.deletions));
|
|
3916
3962
|
}
|
|
3917
3963
|
function isSessionRevert(v) {
|
|
3918
|
-
return isObj(v) && nonEmpty(v.messageID) && v.messageID.length <= 300 && count(v.hidden) && (v.at === undefined || num(v.at)) && Array.isArray(v.files) && v.files.length <= REVERT_FILES_MAX && v.files.every(isRevertFile) && count(v.filesTotal) && v.filesTotal >= v.files.length;
|
|
3964
|
+
return isObj(v) && nonEmpty(v.messageID) && v.messageID.length <= 300 && count(v.hidden) && (v.at === undefined || num(v.at)) && Array.isArray(v.files) && v.files.length <= REVERT_FILES_MAX && v.files.every(isRevertFile) && count(v.filesTotal) && v.filesTotal >= v.files.length && (v.stagedAt === undefined || num(v.stagedAt));
|
|
3919
3965
|
}
|
|
3920
3966
|
var TERMINAL_STATUS = new Set(["idle", "working", "blocked", "done", "unknown"]);
|
|
3921
3967
|
function isTerminal(v) {
|
|
@@ -4099,8 +4145,12 @@ function parseSpokeMessage(raw) {
|
|
|
4099
4145
|
switch (m.t) {
|
|
4100
4146
|
case "hello":
|
|
4101
4147
|
return (m.role === undefined || m.role === "spoke") && num(m.v) && nonEmpty(m.processID) && (m.kind === "service" || m.kind === "standalone") && str2(m.directory) && num(m.pid) && (m.mode === "always" || m.mode === "session") && (str2(m.opencodeVersion) || m.opencodeVersion === undefined && m.harness !== undefined && m.harness !== HARNESS_OPENCODE) && str2(m.pluginVersion) && (m.features === undefined || featureList(m.features)) && (m.harness === undefined || token(m.harness, 64)) && (m.harnessVersion === undefined || str2(m.harnessVersion) && m.harnessVersion.length <= 100) && (m.evidence === undefined || token(m.evidence, 32)) && (m.launchID === undefined || str2(m.launchID) && ID.test(m.launchID)) ? m : undefined;
|
|
4102
|
-
case "sessions":
|
|
4103
|
-
|
|
4148
|
+
case "sessions": {
|
|
4149
|
+
if (!Array.isArray(m.sessions))
|
|
4150
|
+
return;
|
|
4151
|
+
const sessions = m.sessions.map(lenientSession);
|
|
4152
|
+
return sessions.every(isSession) ? { ...m, sessions } : undefined;
|
|
4153
|
+
}
|
|
4104
4154
|
case "terminals":
|
|
4105
4155
|
return m.herdr === undefined || isHerdrInfo(m.herdr) ? m : undefined;
|
|
4106
4156
|
case "event":
|
|
@@ -4108,13 +4158,34 @@ function parseSpokeMessage(raw) {
|
|
|
4108
4158
|
case "req":
|
|
4109
4159
|
return nonEmpty(m.id) && isSpokeRequest(m.req) ? m : undefined;
|
|
4110
4160
|
case "res":
|
|
4111
|
-
return isResFrame(m) ? m : undefined;
|
|
4161
|
+
return isResFrame(m) ? lenientRes(m) : undefined;
|
|
4112
4162
|
case "bye":
|
|
4113
4163
|
return isByeFrame(m) ? m : undefined;
|
|
4114
4164
|
default:
|
|
4115
4165
|
return;
|
|
4116
4166
|
}
|
|
4117
4167
|
}
|
|
4168
|
+
function lenientRes(m) {
|
|
4169
|
+
if (m.code === undefined || nonEmpty(m.code) && m.code.length <= RES_CODE_MAX)
|
|
4170
|
+
return m;
|
|
4171
|
+
const { code: _dropped, ...rest } = m;
|
|
4172
|
+
return rest;
|
|
4173
|
+
}
|
|
4174
|
+
function lenientSession(v) {
|
|
4175
|
+
if (!isObj(v))
|
|
4176
|
+
return v;
|
|
4177
|
+
let out = v;
|
|
4178
|
+
if (out.revertCommitted !== undefined && !isRevertCommitted(out.revertCommitted)) {
|
|
4179
|
+
const { revertCommitted: _dropped, ...rest } = out;
|
|
4180
|
+
out = rest;
|
|
4181
|
+
}
|
|
4182
|
+
const revert = out.revert;
|
|
4183
|
+
if (isObj(revert) && revert.stagedAt !== undefined && !num(revert.stagedAt)) {
|
|
4184
|
+
const { stagedAt: _dropped, ...rest } = revert;
|
|
4185
|
+
out = { ...out, revert: rest };
|
|
4186
|
+
}
|
|
4187
|
+
return out;
|
|
4188
|
+
}
|
|
4118
4189
|
function parseServerMessage(raw) {
|
|
4119
4190
|
const m = parseFrame(raw);
|
|
4120
4191
|
if (!m)
|
|
@@ -4125,7 +4196,7 @@ function parseServerMessage(raw) {
|
|
|
4125
4196
|
case "req":
|
|
4126
4197
|
return nonEmpty(m.id) && isServerRequest(m.req) ? m : undefined;
|
|
4127
4198
|
case "res":
|
|
4128
|
-
return isResFrame(m) ? m : undefined;
|
|
4199
|
+
return isResFrame(m) ? lenientRes(m) : undefined;
|
|
4129
4200
|
case "bye":
|
|
4130
4201
|
return isByeFrame(m) ? m : undefined;
|
|
4131
4202
|
default:
|
|
@@ -4536,8 +4607,8 @@ async function machineEnroll(options) {
|
|
|
4536
4607
|
if (!server) {
|
|
4537
4608
|
const why = insecureHTTPRefusal(options.server, allow);
|
|
4538
4609
|
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)}`);
|
|
4610
|
+
return failure(COMMAND2, "usage", `${why[0].toUpperCase()}${why.slice(1)}: ${line(shownURL(String(options.server)), 200)}`);
|
|
4611
|
+
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
4612
|
}
|
|
4542
4613
|
const insecure = isPlainHTTP(server) && !isLoopbackServer(server) ? insecureHTTPWarning(server) : undefined;
|
|
4543
4614
|
const machineName = options.machineName ?? defaultMachineName(hostname2());
|
|
@@ -4799,7 +4870,6 @@ function profileNames(set) {
|
|
|
4799
4870
|
// packages/node/src/opencode-warm.ts
|
|
4800
4871
|
import { closeSync as closeSync3, constants, fstatSync, lstatSync as lstatSync3, openSync as openSync3, readSync, realpathSync, statSync } from "node:fs";
|
|
4801
4872
|
import path7 from "node:path";
|
|
4802
|
-
import { readFile } from "node:fs/promises";
|
|
4803
4873
|
|
|
4804
4874
|
// src/spoke/listing.ts
|
|
4805
4875
|
import path6 from "node:path";
|
|
@@ -4842,9 +4912,12 @@ async function listServiceSessions(client, options = {}) {
|
|
|
4842
4912
|
return [];
|
|
4843
4913
|
const pageSize = options.pageSize ?? 100;
|
|
4844
4914
|
const maxPages = options.maxPages ?? 20;
|
|
4915
|
+
const { signal } = options;
|
|
4916
|
+
const request = signal ? { signal } : {};
|
|
4917
|
+
signal?.throwIfAborted();
|
|
4845
4918
|
const projects = new Map;
|
|
4846
4919
|
try {
|
|
4847
|
-
for (const project of await client.project.list())
|
|
4920
|
+
for (const project of await client.project.list(request))
|
|
4848
4921
|
projects.set(project.id, project);
|
|
4849
4922
|
} catch {}
|
|
4850
4923
|
const scope = options.scope;
|
|
@@ -4855,7 +4928,8 @@ async function listServiceSessions(client, options = {}) {
|
|
|
4855
4928
|
let cursor;
|
|
4856
4929
|
let ended = false;
|
|
4857
4930
|
for (let page = 0;page < maxPages && out.length < max && !ended; page++) {
|
|
4858
|
-
|
|
4931
|
+
signal?.throwIfAborted();
|
|
4932
|
+
const result = await client.session.list({ limit: pageSize, parentID: null, ...cursor ? { cursor } : {} }, request);
|
|
4859
4933
|
for (const s of result.data) {
|
|
4860
4934
|
if (s.parentID || s.time.archived || out.length >= max)
|
|
4861
4935
|
continue;
|
|
@@ -4873,7 +4947,8 @@ async function listServiceSessions(client, options = {}) {
|
|
|
4873
4947
|
const have = new Set(out.map((s) => s.id));
|
|
4874
4948
|
const missing = [...new Set(options.include ?? [])].filter((id) => !have.has(id)).slice(0, INCLUDE_FETCH_MAX);
|
|
4875
4949
|
if (missing.length && client.session.get) {
|
|
4876
|
-
|
|
4950
|
+
signal?.throwIfAborted();
|
|
4951
|
+
const fetched = await Promise.all(missing.map((sessionID) => client.session.get({ sessionID }, request).catch(() => {
|
|
4877
4952
|
return;
|
|
4878
4953
|
})));
|
|
4879
4954
|
for (const s of fetched)
|
|
@@ -4885,12 +4960,13 @@ async function listServiceSessions(client, options = {}) {
|
|
|
4885
4960
|
|
|
4886
4961
|
// packages/node/src/opencode-warm.ts
|
|
4887
4962
|
var WARM_INTERVAL_MS = 30000;
|
|
4888
|
-
var WARM_TIMEOUT_MS =
|
|
4963
|
+
var WARM_TIMEOUT_MS = 30000;
|
|
4889
4964
|
var WARM_DIRECTORIES_MAX = 8;
|
|
4890
4965
|
var SESSION_DAYS = 7;
|
|
4891
4966
|
var SESSION_PAGE = 100;
|
|
4892
4967
|
var LOOPBACK2 = new Set(["127.0.0.1", "[::1]"]);
|
|
4893
4968
|
var PASSWORD_FILE_MAX = 4096;
|
|
4969
|
+
var REGISTRATION_MAX = 64 * 1024;
|
|
4894
4970
|
var isDirectory = (directory) => {
|
|
4895
4971
|
try {
|
|
4896
4972
|
return statSync(directory).isDirectory();
|
|
@@ -5071,12 +5147,34 @@ class OpenCodeWarmer {
|
|
|
5071
5147
|
return problem ? "failed" : "warmed";
|
|
5072
5148
|
}
|
|
5073
5149
|
}
|
|
5074
|
-
async function readServiceRegistration(file) {
|
|
5150
|
+
async function readServiceRegistration(file, options = {}) {
|
|
5151
|
+
const uid = options.uid ?? (typeof process.getuid === "function" ? process.getuid() : undefined);
|
|
5075
5152
|
let info;
|
|
5153
|
+
let fd;
|
|
5076
5154
|
try {
|
|
5077
|
-
|
|
5155
|
+
const before = lstatSync3(file);
|
|
5156
|
+
if (!before.isFile())
|
|
5157
|
+
return;
|
|
5158
|
+
fd = openSync3(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
|
|
5159
|
+
const stat = fstatSync(fd);
|
|
5160
|
+
if (!stat.isFile() || stat.dev !== before.dev || stat.ino !== before.ino)
|
|
5161
|
+
return;
|
|
5162
|
+
if ((stat.mode & 18) !== 0)
|
|
5163
|
+
return;
|
|
5164
|
+
if (uid !== undefined && stat.uid !== uid)
|
|
5165
|
+
return;
|
|
5166
|
+
if (stat.size > REGISTRATION_MAX)
|
|
5167
|
+
return;
|
|
5168
|
+
const buffer = Buffer.alloc(REGISTRATION_MAX + 1);
|
|
5169
|
+
const length = readSync(fd, buffer, 0, buffer.length, 0);
|
|
5170
|
+
if (length > REGISTRATION_MAX)
|
|
5171
|
+
return;
|
|
5172
|
+
info = JSON.parse(buffer.subarray(0, length).toString("utf8"));
|
|
5078
5173
|
} catch {
|
|
5079
5174
|
return;
|
|
5175
|
+
} finally {
|
|
5176
|
+
if (fd !== undefined)
|
|
5177
|
+
closeSync3(fd);
|
|
5080
5178
|
}
|
|
5081
5179
|
if (!info || typeof info !== "object")
|
|
5082
5180
|
return;
|
|
@@ -5085,9 +5183,8 @@ async function readServiceRegistration(file) {
|
|
|
5085
5183
|
return;
|
|
5086
5184
|
try {
|
|
5087
5185
|
process.kill(pid, 0);
|
|
5088
|
-
} catch
|
|
5089
|
-
|
|
5090
|
-
return;
|
|
5186
|
+
} catch {
|
|
5187
|
+
return;
|
|
5091
5188
|
}
|
|
5092
5189
|
return typeof password === "string" ? { url, auth: { type: "basic", username: "opencode", password } } : { url };
|
|
5093
5190
|
}
|
|
@@ -5287,7 +5384,15 @@ function normalizeOpenCode(raw, problems) {
|
|
|
5287
5384
|
problems.push(`opencode.servers: at most ${OPENCODE_SERVERS_MAX} servers; the rest are ignored`);
|
|
5288
5385
|
servers.length = OPENCODE_SERVERS_MAX;
|
|
5289
5386
|
}
|
|
5290
|
-
|
|
5387
|
+
let timeoutSeconds;
|
|
5388
|
+
if (raw.timeoutSeconds !== undefined) {
|
|
5389
|
+
const t = raw.timeoutSeconds;
|
|
5390
|
+
if (typeof t === "number" && Number.isInteger(t) && t >= 1 && t <= 300)
|
|
5391
|
+
timeoutSeconds = t;
|
|
5392
|
+
else
|
|
5393
|
+
problems.push("opencode.timeoutSeconds must be a whole number of seconds from 1 to 300");
|
|
5394
|
+
}
|
|
5395
|
+
return { ...directories ? { directories } : {}, servers, ...timeoutSeconds !== undefined ? { timeoutSeconds } : {} };
|
|
5291
5396
|
}
|
|
5292
5397
|
function normalizeHarnesses(raw, problems) {
|
|
5293
5398
|
if (!isObject3(raw)) {
|
|
@@ -5459,14 +5564,15 @@ var DELIVERY = {
|
|
|
5459
5564
|
queued: "queued (read at its next turn)",
|
|
5460
5565
|
retained: "retained (held until its open question is answered)",
|
|
5461
5566
|
pending: "pending (offline or busy; delivered when it reconnects or its turn ends, for 24 h)",
|
|
5462
|
-
dropped: "not delivered (its recipient session ended)"
|
|
5567
|
+
dropped: "not delivered (its recipient session ended)",
|
|
5568
|
+
expired: "not delivered (its recipient stayed offline or busy for 24 h)"
|
|
5463
5569
|
};
|
|
5464
5570
|
var deliveryText = (delivery) => delivery ? DELIVERY[delivery] ?? delivery : "stored (in its inbox)";
|
|
5465
5571
|
function toText(message) {
|
|
5466
5572
|
const recipient = partyText(message.recipient);
|
|
5467
5573
|
return message.to === recipient || message.to === message.recipient.sessionID ? recipient : `${message.to} → ${recipient}`;
|
|
5468
5574
|
}
|
|
5469
|
-
var sentText = (
|
|
5575
|
+
var sentText = (sent) => `${sent.message.kind} ${sent.message.id} to ${toText(sent.message)}: ${deliveryText(sent.delivery)}${wakeSkipNote(sent)}`;
|
|
5470
5576
|
var ASK_EXPIRES_DEFAULT_S = 24 * 60 * 60;
|
|
5471
5577
|
var UNIT_S = { "": 1, s: 1, m: 60, h: 3600, d: 86400 };
|
|
5472
5578
|
function durationArg(value, flag) {
|
|
@@ -5723,9 +5829,12 @@ function launchedText(launch, created, key = launch.spec.key) {
|
|
|
5723
5829
|
return `${launch.id} was already launched with key ${key}: ${summary(launch).slice(launch.id.length + 2)}`;
|
|
5724
5830
|
}
|
|
5725
5831
|
var stoppedLaunchText = (launch) => `stopped ${summary(launch)}`;
|
|
5832
|
+
var launcherText = (l) => l.byLabel ? `app ${JSON.stringify(l.byLabel)}` : `app ${l.by?.replace(/^app:/, "") ?? "?"}`;
|
|
5726
5833
|
function launchLine(l) {
|
|
5727
5834
|
const where = l.directory ?? `${l.spec.workspace.repo}${l.spec.workspace.branch ? ` (${l.spec.workspace.branch})` : ""}`;
|
|
5728
|
-
|
|
5835
|
+
const waits = l.state === "queued" && l.waiting ? `waits: ${l.waiting.reason}` : undefined;
|
|
5836
|
+
const revoked = l.launcherRevoked && l.state !== "ended" ? `launcher revoked (${launcherText(l)}): stop it with hub launches stop ${l.id}` : undefined;
|
|
5837
|
+
return [l.id, stateText2(l), sessionText(l) || undefined, where, labelsText(l.spec.labels) || undefined, waits, revoked].filter(Boolean).join(" ");
|
|
5729
5838
|
}
|
|
5730
5839
|
var launchesText = (launches) => launches.length ? launches.map(launchLine).join(`
|
|
5731
5840
|
`) : "no launches";
|
|
@@ -5753,8 +5862,10 @@ function launchText(l) {
|
|
|
5753
5862
|
lines.push(`runs: ${run.join(" ")}`);
|
|
5754
5863
|
if (l.spec.end)
|
|
5755
5864
|
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(", ")}`);
|
|
5865
|
+
if (l.state === "queued" && l.waiting)
|
|
5866
|
+
lines.push(`waits (${l.waiting.wait}) since ${new Date(l.waiting.since).toISOString()}: ${l.waiting.reason}`);
|
|
5756
5867
|
if (l.by)
|
|
5757
|
-
lines.push(`by: ${l.by}`);
|
|
5868
|
+
lines.push(`by: ${l.by}${l.byLabel ? ` (${l.byLabel})` : ""}${l.launcherRevoked ? ", launcher revoked" : ""}`);
|
|
5758
5869
|
if (labelsText(l.spec.labels))
|
|
5759
5870
|
lines.push(`labels: ${labelsText(l.spec.labels)}`);
|
|
5760
5871
|
if (l.artifacts && artifactsText(l.artifacts))
|
|
@@ -5765,6 +5876,10 @@ function launchText(l) {
|
|
|
5765
5876
|
`);
|
|
5766
5877
|
}
|
|
5767
5878
|
|
|
5879
|
+
// packages/cli/src/tool-args.ts
|
|
5880
|
+
class ToolArgError extends Error {
|
|
5881
|
+
}
|
|
5882
|
+
|
|
5768
5883
|
// packages/cli/src/artifacts-mcp.ts
|
|
5769
5884
|
var MCP_INLINE_TEXT_BYTES = 256 * 1024;
|
|
5770
5885
|
var KINDS = ["markdown", "code", "image", "table", "chart", "dashboard", "html", "app", "file"];
|
|
@@ -5788,7 +5903,7 @@ var FILE_ARGS = {
|
|
|
5788
5903
|
var need = (args, key) => {
|
|
5789
5904
|
const value = args[key];
|
|
5790
5905
|
if (typeof value !== "string" || !value.trim())
|
|
5791
|
-
throw new
|
|
5906
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
5792
5907
|
return value;
|
|
5793
5908
|
};
|
|
5794
5909
|
var optional2 = (args, key) => {
|
|
@@ -5796,20 +5911,20 @@ var optional2 = (args, key) => {
|
|
|
5796
5911
|
if (value === undefined || value === null)
|
|
5797
5912
|
return;
|
|
5798
5913
|
if (typeof value !== "string")
|
|
5799
|
-
throw new
|
|
5914
|
+
throw new ToolArgError(`"${key}" must be a string`);
|
|
5800
5915
|
return value.trim() ? value : undefined;
|
|
5801
5916
|
};
|
|
5802
5917
|
function files(args, kind) {
|
|
5803
5918
|
const text = args.text;
|
|
5804
5919
|
if (text !== undefined && typeof text !== "string")
|
|
5805
|
-
throw new
|
|
5920
|
+
throw new ToolArgError('"text" must be a string');
|
|
5806
5921
|
const blobs = args.blobs;
|
|
5807
5922
|
if (blobs !== undefined && (!Array.isArray(blobs) || !blobs.every((b) => typeof b === "object" && b !== null)))
|
|
5808
|
-
throw new
|
|
5923
|
+
throw new ToolArgError('"blobs" must be a list of blob references');
|
|
5809
5924
|
if (text === undefined && (blobs === undefined || blobs.length === 0))
|
|
5810
|
-
throw new
|
|
5925
|
+
throw new ToolArgError("give text or blobs (stage bigger content first and pass its reference)");
|
|
5811
5926
|
if (text !== undefined && Buffer.byteLength(text, "utf8") > MCP_INLINE_TEXT_BYTES)
|
|
5812
|
-
throw new
|
|
5927
|
+
throw new ToolArgError(`text is larger than ${MCP_INLINE_TEXT_BYTES} bytes: stage it with uploadBlob and pass blobs`);
|
|
5813
5928
|
const path = optional2(args, "path");
|
|
5814
5929
|
const entry = optional2(args, "entry");
|
|
5815
5930
|
const mediaType = optional2(args, "mediaType") ?? (kind && DEFAULT_MEDIA[kind]) ?? "text/plain";
|
|
@@ -5876,7 +5991,7 @@ var artifactTools = [
|
|
|
5876
5991
|
const title = need(args, "title");
|
|
5877
5992
|
const kind = need(args, "kind");
|
|
5878
5993
|
if (!KINDS.includes(kind))
|
|
5879
|
-
throw new
|
|
5994
|
+
throw new ToolArgError(`"kind" must be one of ${KINDS.join(", ")}`);
|
|
5880
5995
|
const body = files(args, kind);
|
|
5881
5996
|
const description = optional2(args, "description");
|
|
5882
5997
|
const visibility = optional2(args, "visibility");
|
|
@@ -5915,7 +6030,7 @@ var KINDS2 = ["ask", "propose-edit", "run-action"];
|
|
|
5915
6030
|
var need2 = (args, key) => {
|
|
5916
6031
|
const value = args[key];
|
|
5917
6032
|
if (typeof value !== "string" || !value.trim())
|
|
5918
|
-
throw new
|
|
6033
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
5919
6034
|
return value;
|
|
5920
6035
|
};
|
|
5921
6036
|
var optional3 = (args, key) => {
|
|
@@ -5923,24 +6038,24 @@ var optional3 = (args, key) => {
|
|
|
5923
6038
|
if (value === undefined || value === null)
|
|
5924
6039
|
return;
|
|
5925
6040
|
if (typeof value !== "string")
|
|
5926
|
-
throw new
|
|
6041
|
+
throw new ToolArgError(`"${key}" must be a string`);
|
|
5927
6042
|
return value.trim() ? value : undefined;
|
|
5928
6043
|
};
|
|
5929
6044
|
var integer = (args, key) => {
|
|
5930
6045
|
const value = args[key];
|
|
5931
6046
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
5932
|
-
throw new
|
|
6047
|
+
throw new ToolArgError(`"${key}" is required (a whole number)`);
|
|
5933
6048
|
return value;
|
|
5934
6049
|
};
|
|
5935
6050
|
function handlerOf(args) {
|
|
5936
6051
|
const session = optional3(args, "session");
|
|
5937
6052
|
const machine = optional3(args, "machine");
|
|
5938
6053
|
if (session && machine)
|
|
5939
|
-
throw new
|
|
6054
|
+
throw new ToolArgError('give either "session" (an existing session) or "machine" + "repo" (a new handler session), not both');
|
|
5940
6055
|
if (session)
|
|
5941
6056
|
return { session };
|
|
5942
6057
|
if (!machine)
|
|
5943
|
-
throw new
|
|
6058
|
+
throw new ToolArgError('"machine" and "repo" are required to launch a handler session (or name an existing one with "session")');
|
|
5944
6059
|
const repo = need2(args, "repo");
|
|
5945
6060
|
const harness = optional3(args, "harness");
|
|
5946
6061
|
const model = optional3(args, "model");
|
|
@@ -6004,7 +6119,7 @@ var artifactJobTools = [
|
|
|
6004
6119
|
run: async (hub, args, signal) => {
|
|
6005
6120
|
const kind = optional3(args, "kind");
|
|
6006
6121
|
if (kind && !KINDS2.includes(kind))
|
|
6007
|
-
throw new
|
|
6122
|
+
throw new ToolArgError(`"kind" must be one of ${KINDS2.join(", ")}`);
|
|
6008
6123
|
const action = optional3(args, "action");
|
|
6009
6124
|
const apply = optional3(args, "apply");
|
|
6010
6125
|
const key = optional3(args, "key");
|
|
@@ -6073,12 +6188,12 @@ var artifactJobTools = [
|
|
|
6073
6188
|
const answer = optional3(args, "answer");
|
|
6074
6189
|
const text = args.text;
|
|
6075
6190
|
if (text !== undefined && typeof text !== "string")
|
|
6076
|
-
throw new
|
|
6191
|
+
throw new ToolArgError('"text" must be a string');
|
|
6077
6192
|
if (typeof text === "string" && Buffer.byteLength(text, "utf8") > MCP_JOB_TEXT_BYTES)
|
|
6078
|
-
throw new
|
|
6193
|
+
throw new ToolArgError(`"text" is larger than ${MCP_JOB_TEXT_BYTES} bytes`);
|
|
6079
6194
|
const keep = args.keep;
|
|
6080
6195
|
if (keep !== undefined && (!Array.isArray(keep) || !keep.every((k) => typeof k === "string")))
|
|
6081
|
-
throw new
|
|
6196
|
+
throw new ToolArgError('"keep" must be a list of paths');
|
|
6082
6197
|
const path = optional3(args, "path");
|
|
6083
6198
|
const entry = optional3(args, "entry");
|
|
6084
6199
|
const result = error ? { error } : text !== undefined || keep !== undefined ? {
|
|
@@ -6125,7 +6240,7 @@ var UPDATES = TEAM_INTAKE_STATUS_INPUTS;
|
|
|
6125
6240
|
var need3 = (args, key) => {
|
|
6126
6241
|
const value = args[key];
|
|
6127
6242
|
if (typeof value !== "string" || !value.trim())
|
|
6128
|
-
throw new
|
|
6243
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
6129
6244
|
return value;
|
|
6130
6245
|
};
|
|
6131
6246
|
var optional4 = (args, key) => {
|
|
@@ -6133,7 +6248,7 @@ var optional4 = (args, key) => {
|
|
|
6133
6248
|
if (value === undefined || value === null)
|
|
6134
6249
|
return;
|
|
6135
6250
|
if (typeof value !== "string")
|
|
6136
|
-
throw new
|
|
6251
|
+
throw new ToolArgError(`"${key}" must be a string`);
|
|
6137
6252
|
return value.trim() ? value : undefined;
|
|
6138
6253
|
};
|
|
6139
6254
|
var strings2 = (args, key) => {
|
|
@@ -6141,13 +6256,13 @@ var strings2 = (args, key) => {
|
|
|
6141
6256
|
if (value === undefined || value === null)
|
|
6142
6257
|
return;
|
|
6143
6258
|
if (!Array.isArray(value) || !value.every((v) => typeof v === "string"))
|
|
6144
|
-
throw new
|
|
6259
|
+
throw new ToolArgError(`"${key}" must be a list of strings`);
|
|
6145
6260
|
return value;
|
|
6146
6261
|
};
|
|
6147
6262
|
var oneOf4 = (args, key, allowed) => {
|
|
6148
6263
|
const value = optional4(args, key);
|
|
6149
6264
|
if (value !== undefined && !allowed.includes(value))
|
|
6150
|
-
throw new
|
|
6265
|
+
throw new ToolArgError(`"${key}" must be one of ${allowed.join(", ")}`);
|
|
6151
6266
|
return value;
|
|
6152
6267
|
};
|
|
6153
6268
|
function dueOf(args) {
|
|
@@ -6156,7 +6271,7 @@ function dueOf(args) {
|
|
|
6156
6271
|
return;
|
|
6157
6272
|
const ms = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : Number.NaN;
|
|
6158
6273
|
if (!Number.isSafeInteger(ms) || ms <= 0)
|
|
6159
|
-
throw new
|
|
6274
|
+
throw new ToolArgError(`"due" must be an ISO date/time or epoch milliseconds`);
|
|
6160
6275
|
return ms;
|
|
6161
6276
|
}
|
|
6162
6277
|
var line4 = (i) => `${i.id} ${i.status}${i.progress ? `/${i.progress}` : ""} ${i.priority} r${i.revision} ${sanitizeText(i.title).replace(/\s+/g, " ")}`;
|
|
@@ -6243,10 +6358,10 @@ ${intakeText(intake)}`;
|
|
|
6243
6358
|
run: async (hub, args, signal) => {
|
|
6244
6359
|
const status = oneOf4(args, "status", UPDATES);
|
|
6245
6360
|
if (!status)
|
|
6246
|
-
throw new
|
|
6361
|
+
throw new ToolArgError(`"status" is required: one of ${UPDATES.join(", ")}`);
|
|
6247
6362
|
const expectedRevision = args.expectedRevision;
|
|
6248
6363
|
if (expectedRevision !== undefined && expectedRevision !== null && (typeof expectedRevision !== "number" || !Number.isSafeInteger(expectedRevision) || expectedRevision < 0))
|
|
6249
|
-
throw new
|
|
6364
|
+
throw new ToolArgError(`"expectedRevision" must be the revision you read (or left out)`);
|
|
6250
6365
|
const reason = optional4(args, "reason");
|
|
6251
6366
|
const { intake, assignment } = await reportIntakeStatus(hub.teams, need3(args, "team"), need3(args, "intake"), { status, ...typeof expectedRevision === "number" ? { expectedRevision } : {}, ...reason ? { reason } : {} }, { signal });
|
|
6252
6367
|
return `${intake.id} is ${intake.status}${intake.progress ? ` (${intake.progress})` : ""}, revision ${intake.revision}${assignment ? `; assignment ${assignment.id} ${assignment.status}` : ""}`;
|
|
@@ -6285,10 +6400,61 @@ var SPEC_FIELDS = {
|
|
|
6285
6400
|
metadata: { type: "object", additionalProperties: STRING4, description: "String key/value pairs (keys are slugs)." },
|
|
6286
6401
|
projects: { ...STRINGS, description: "Project names the team works on." }
|
|
6287
6402
|
};
|
|
6403
|
+
var LAUNCH = {
|
|
6404
|
+
type: "object",
|
|
6405
|
+
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.",
|
|
6406
|
+
properties: {
|
|
6407
|
+
workspace: {
|
|
6408
|
+
type: "object",
|
|
6409
|
+
description: "Where the member works on its machine.",
|
|
6410
|
+
properties: {
|
|
6411
|
+
repo: { ...STRING4, description: "The repository's absolute path on the machine." },
|
|
6412
|
+
mode: { type: "string", enum: ["worktree", "checkout", "clone"], description: "worktree (default; needs branch), checkout, or clone (needs remote)." },
|
|
6413
|
+
branch: { ...STRING4, description: "The branch to create or check out (a safe git branch name; required for worktree)." },
|
|
6414
|
+
base: { ...STRING4, description: "What a new branch starts from." },
|
|
6415
|
+
remote: { ...STRING4, description: "An https or ssh git remote, without credentials (required for clone)." }
|
|
6416
|
+
},
|
|
6417
|
+
required: ["repo"]
|
|
6418
|
+
},
|
|
6419
|
+
placement: {
|
|
6420
|
+
type: "object",
|
|
6421
|
+
description: "Which machine runs it.",
|
|
6422
|
+
properties: {
|
|
6423
|
+
machine: { ...STRING4, description: "A machine ID or name." },
|
|
6424
|
+
labels: { type: "object", additionalProperties: STRING4, description: "Node labels to match." },
|
|
6425
|
+
mode: { type: "string", enum: ["require", "prefer"], description: "require (default) or prefer the machine/labels." }
|
|
6426
|
+
}
|
|
6427
|
+
},
|
|
6428
|
+
runtime: {
|
|
6429
|
+
type: "object",
|
|
6430
|
+
properties: {
|
|
6431
|
+
kind: { type: "string", enum: ["host", "openshell"], description: "host (default) or openshell (clone workspaces only)." },
|
|
6432
|
+
profile: { ...STRING4, description: "An openshell sandbox profile (openshell only)." }
|
|
6433
|
+
}
|
|
6434
|
+
},
|
|
6435
|
+
end: {
|
|
6436
|
+
type: "object",
|
|
6437
|
+
description: "When the launch ends.",
|
|
6438
|
+
properties: {
|
|
6439
|
+
when: { type: "string", enum: ["idle", "asked", "never"], description: "idle (default), asked or never." },
|
|
6440
|
+
timeout: { type: ["string", "number"], description: "1 minute to 30 days: seconds, or 30m, 4h, 2d." },
|
|
6441
|
+
cleanup: { type: "string", enum: ["keep", "remove"], description: "keep (default) or remove the workspace at the end." }
|
|
6442
|
+
}
|
|
6443
|
+
},
|
|
6444
|
+
name: { ...STRING4, description: "The session's @name, without the @." },
|
|
6445
|
+
roles: { ...STRINGS, description: "Roles the session claims (up to 10)." },
|
|
6446
|
+
harness: { ...STRING4, description: "The harness (default opencode)." },
|
|
6447
|
+
agent: { ...STRING4, description: "The harness agent." },
|
|
6448
|
+
model: { ...STRING4, description: "provider/model." },
|
|
6449
|
+
prompt: { ...STRING4, description: "The first prompt (up to 10000 characters)." },
|
|
6450
|
+
intent: { ...STRING4, description: "What the session is for (one line)." },
|
|
6451
|
+
labels: { type: "object", additionalProperties: STRING4, description: "Free-form app labels." },
|
|
6452
|
+
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)." }
|
|
6453
|
+
},
|
|
6454
|
+
required: ["workspace"]
|
|
6455
|
+
};
|
|
6288
6456
|
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
|
-
|
|
6290
|
-
class ArgError extends Error {
|
|
6291
|
-
}
|
|
6457
|
+
var ArgError = ToolArgError;
|
|
6292
6458
|
var need4 = (args, key) => {
|
|
6293
6459
|
const value = args[key];
|
|
6294
6460
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -6581,7 +6747,7 @@ var teamTools = [
|
|
|
6581
6747
|
{
|
|
6582
6748
|
name: "team_staff",
|
|
6583
6749
|
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:
|
|
6750
|
+
properties: { team: TEAM2, role: STRING4, sessionID: { ...STRING4, description: "An existing session (ses_…)." }, launch: LAUNCH, idempotencyKey: IDEMPOTENCY },
|
|
6585
6751
|
required: ["team", "role"],
|
|
6586
6752
|
readOnly: false,
|
|
6587
6753
|
run: async (hub, args, signal) => {
|
|
@@ -6616,7 +6782,7 @@ var teamTools = [
|
|
|
6616
6782
|
{
|
|
6617
6783
|
name: "team_replace",
|
|
6618
6784
|
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:
|
|
6785
|
+
properties: { team: TEAM2, staffingID: STRING4, sessionID: STRING4, launch: LAUNCH, expectedRevision: { ...REVISION2, description: "The seat's revision you read (team_status)." } },
|
|
6620
6786
|
required: ["team", "staffingID", "expectedRevision"],
|
|
6621
6787
|
readOnly: false,
|
|
6622
6788
|
run: async (hub, args, signal) => {
|
|
@@ -6633,6 +6799,253 @@ var teamTools = [
|
|
|
6633
6799
|
}
|
|
6634
6800
|
];
|
|
6635
6801
|
|
|
6802
|
+
// packages/cli/src/profiles-mcp.ts
|
|
6803
|
+
var STRING5 = { type: "string" };
|
|
6804
|
+
var STRINGS2 = { type: "array", items: STRING5 };
|
|
6805
|
+
var ID4 = { type: "string", description: "The profile's ID (prof_…; see profiles)." };
|
|
6806
|
+
var REVISION3 = { type: "integer", minimum: 0, description: "The profile's revision you read (profile_show); a stale one answers conflict with the current revision." };
|
|
6807
|
+
var IDEMPOTENCY2 = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
|
|
6808
|
+
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"] };
|
|
6809
|
+
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"] };
|
|
6810
|
+
var CONTENT_FIELDS = {
|
|
6811
|
+
instructions: { type: ["string", "null"], description: "The profile's instructions (AGENTS.md-like text)." },
|
|
6812
|
+
model: { type: ["string", "null"], description: "provider/model the profile suggests." },
|
|
6813
|
+
skills: { type: "array", items: SKILL, description: "Skills, wholesale." },
|
|
6814
|
+
commands: { type: "array", items: COMMAND3, description: "Commands, wholesale." }
|
|
6815
|
+
};
|
|
6816
|
+
var META_FIELDS = {
|
|
6817
|
+
title: { ...STRING5, description: "1–120 characters." },
|
|
6818
|
+
description: STRING5,
|
|
6819
|
+
tags: { ...STRINGS2, description: "Slugs." },
|
|
6820
|
+
metadata: { type: "object", additionalProperties: STRING5, description: "String key/value pairs (≤ 32 slug keys)." },
|
|
6821
|
+
projects: { ...STRINGS2, description: "Project names the profile is meant for (≤ 16)." }
|
|
6822
|
+
};
|
|
6823
|
+
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.";
|
|
6824
|
+
var ArgError2 = ToolArgError;
|
|
6825
|
+
var need5 = (args, key) => {
|
|
6826
|
+
const value = args[key];
|
|
6827
|
+
if (typeof value !== "string" || !value.trim())
|
|
6828
|
+
throw new ArgError2(`"${key}" is required (a non-empty string)`);
|
|
6829
|
+
return value;
|
|
6830
|
+
};
|
|
6831
|
+
var optional6 = (args, key) => {
|
|
6832
|
+
const value = args[key];
|
|
6833
|
+
if (value === undefined || value === null)
|
|
6834
|
+
return;
|
|
6835
|
+
if (typeof value !== "string")
|
|
6836
|
+
throw new ArgError2(`"${key}" must be a string`);
|
|
6837
|
+
return value.trim() ? value : undefined;
|
|
6838
|
+
};
|
|
6839
|
+
var whole2 = (args, key, min, what) => {
|
|
6840
|
+
const value = args[key];
|
|
6841
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min)
|
|
6842
|
+
throw new ArgError2(`"${key}" is required: ${what}`);
|
|
6843
|
+
return value;
|
|
6844
|
+
};
|
|
6845
|
+
var revision2 = (args) => whole2(args, "expectedRevision", 0, "the revision you read (profile_show; 0 for a session with no pin)");
|
|
6846
|
+
var idempotency = (args) => {
|
|
6847
|
+
const key = optional6(args, "idempotencyKey");
|
|
6848
|
+
return key ? { idempotencyKey: key } : {};
|
|
6849
|
+
};
|
|
6850
|
+
var picked2 = (args, keys) => Object.fromEntries(keys.filter((k) => args[k] !== undefined).map((k) => [k, args[k]]));
|
|
6851
|
+
var one2 = (text) => sanitizeText(text).replace(/\s+/g, " ");
|
|
6852
|
+
var profileLine = (p) => `${p.id} ${p.name} v${p.latestVersion} r${p.revision} ${p.state} ${one2(p.title)}`;
|
|
6853
|
+
function summaryLines(v) {
|
|
6854
|
+
if (!v)
|
|
6855
|
+
return [];
|
|
6856
|
+
return [
|
|
6857
|
+
`version ${v.version} (${v.digest.slice(0, 12)}, ${v.bytes} bytes): instructions ${v.instructions ? "yes" : "no"}${v.model ? `, model ${one2(v.model)}` : ""}`,
|
|
6858
|
+
`skills: ${v.skills.length ? v.skills.map((s) => s.name).join(", ") : "none"}`,
|
|
6859
|
+
`commands: ${v.commands.length ? v.commands.map((c) => `${c.name}(${c.params.join(", ")})`).join(", ") : "none"}`,
|
|
6860
|
+
...v.secretRefs.length ? [`secret references: ${v.secretRefs.join(", ")}`] : []
|
|
6861
|
+
];
|
|
6862
|
+
}
|
|
6863
|
+
var versionText = (r) => [`new version ${r.version.version}: ${profileLine(r.profile)}`, ...summaryLines(r.version)].join(`
|
|
6864
|
+
`);
|
|
6865
|
+
async function latestContent(profiles, id, signal) {
|
|
6866
|
+
const { profile } = await profiles.get(id, { signal });
|
|
6867
|
+
const { version } = await profiles.version(profile.id, profile.latestVersion, { signal });
|
|
6868
|
+
return { latest: version.version, content: version.content };
|
|
6869
|
+
}
|
|
6870
|
+
var contentTool = (name, description, properties, required, change) => ({
|
|
6871
|
+
name,
|
|
6872
|
+
description: `${description} Writes the latest version's content with this change as the next version (versions are immutable; pinned sessions keep theirs). ${SCOPES2}`,
|
|
6873
|
+
properties: { id: ID4, ...properties, expectedRevision: REVISION3 },
|
|
6874
|
+
required: ["id", ...required, "expectedRevision"],
|
|
6875
|
+
readOnly: false,
|
|
6876
|
+
organizes: true,
|
|
6877
|
+
idempotent: true,
|
|
6878
|
+
run: async (hub, args, signal) => {
|
|
6879
|
+
const expectedRevision = revision2(args);
|
|
6880
|
+
const id = need5(args, "id");
|
|
6881
|
+
const { latest, content } = await latestContent(hub.profiles, id, signal);
|
|
6882
|
+
return versionText(await hub.profiles.createVersion(id, { expectedRevision, content: change(content, args, latest) }, { signal }));
|
|
6883
|
+
}
|
|
6884
|
+
});
|
|
6885
|
+
var setNamed = (items, item) => items.some((i) => i.name === item.name) ? items.map((i) => i.name === item.name ? item : i) : [...items, item];
|
|
6886
|
+
var removeNamed = (items, name, what, latest) => {
|
|
6887
|
+
if (!items.some((i) => i.name === name))
|
|
6888
|
+
throw new ArgError2(`version ${latest} has no ${what} ${name}`);
|
|
6889
|
+
return items.filter((i) => i.name !== name);
|
|
6890
|
+
};
|
|
6891
|
+
var stateTool = (verb, description) => ({
|
|
6892
|
+
name: `profile_${verb}`,
|
|
6893
|
+
description: `${description} ${SCOPES2}`,
|
|
6894
|
+
properties: { id: ID4, expectedRevision: REVISION3 },
|
|
6895
|
+
required: ["id", "expectedRevision"],
|
|
6896
|
+
readOnly: false,
|
|
6897
|
+
idempotent: true,
|
|
6898
|
+
run: async (hub, args, signal) => {
|
|
6899
|
+
const expectedRevision = revision2(args);
|
|
6900
|
+
const { profile } = await hub.profiles.update(need5(args, "id"), { expectedRevision, state: verb === "archive" ? "archived" : "active" }, { signal });
|
|
6901
|
+
return `${verb === "archive" ? "archived" : "restored"}: ${profileLine(profile)}`;
|
|
6902
|
+
}
|
|
6903
|
+
});
|
|
6904
|
+
var profileLifecycleTools = [
|
|
6905
|
+
{
|
|
6906
|
+
name: "profile_show",
|
|
6907
|
+
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}`,
|
|
6908
|
+
properties: { id: ID4 },
|
|
6909
|
+
required: ["id"],
|
|
6910
|
+
readOnly: true,
|
|
6911
|
+
run: async (hub, args, signal) => {
|
|
6912
|
+
const { profile, latest } = await hub.profiles.get(need5(args, "id"), { signal });
|
|
6913
|
+
return [
|
|
6914
|
+
profileLine(profile),
|
|
6915
|
+
...profile.description ? [`description: ${one2(profile.description)}`] : [],
|
|
6916
|
+
...profile.tags?.length ? [`tags: ${profile.tags.join(", ")}`] : [],
|
|
6917
|
+
...profile.projects?.length ? [`projects: ${profile.projects.map(one2).join(", ")}`] : [],
|
|
6918
|
+
...profile.metadata && Object.keys(profile.metadata).length ? [`metadata: ${Object.entries(profile.metadata).map(([k, v]) => `${k}=${one2(v)}`).join(", ")}`] : [],
|
|
6919
|
+
...summaryLines(latest)
|
|
6920
|
+
].join(`
|
|
6921
|
+
`);
|
|
6922
|
+
}
|
|
6923
|
+
},
|
|
6924
|
+
{
|
|
6925
|
+
name: "profile_versions",
|
|
6926
|
+
description: `A profile's retained versions, newest first (version, digest, bytes, when, skill and command counts). ${SCOPES2}`,
|
|
6927
|
+
properties: { id: ID4, limit: { type: "integer", minimum: 1, maximum: 200 }, cursor: STRING5 },
|
|
6928
|
+
required: ["id"],
|
|
6929
|
+
readOnly: true,
|
|
6930
|
+
run: async (hub, args, signal) => {
|
|
6931
|
+
const limit = args.limit === undefined ? undefined : whole2(args, "limit", 1, "a whole number from 1 to 200");
|
|
6932
|
+
const cursor = optional6(args, "cursor");
|
|
6933
|
+
const page = await hub.profiles.versions(need5(args, "id"), { signal, ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {} });
|
|
6934
|
+
const counts = (v) => v.skills !== undefined ? ` ${v.skills} skills, ${v.commands ?? 0} commands${v.instructions ? ", instructions" : ""}` : "";
|
|
6935
|
+
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(`
|
|
6936
|
+
`) || "no versions";
|
|
6937
|
+
}
|
|
6938
|
+
},
|
|
6939
|
+
{
|
|
6940
|
+
name: "profile_create",
|
|
6941
|
+
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}`,
|
|
6942
|
+
properties: { name: { ...STRING5, description: "A slug, unique per user." }, ...META_FIELDS, ...CONTENT_FIELDS, idempotencyKey: IDEMPOTENCY2 },
|
|
6943
|
+
required: ["name", "title"],
|
|
6944
|
+
readOnly: false,
|
|
6945
|
+
organizes: true,
|
|
6946
|
+
idempotent: true,
|
|
6947
|
+
run: async (hub, args, signal) => {
|
|
6948
|
+
need5(args, "name");
|
|
6949
|
+
need5(args, "title");
|
|
6950
|
+
const meta = picked2(args, ["name", "title", "description", "tags", "metadata", "projects"]);
|
|
6951
|
+
const text = Object.entries(picked2(args, ["instructions", "model"])).filter(([, v]) => v !== null);
|
|
6952
|
+
const content = { ...Object.fromEntries(text), skills: args.skills ?? [], commands: args.commands ?? [] };
|
|
6953
|
+
const r = await hub.profiles.create({ ...meta, content }, { signal, ...idempotency(args) });
|
|
6954
|
+
return [`created: ${profileLine(r.profile)}`, ...summaryLines(r.version)].join(`
|
|
6955
|
+
`);
|
|
6956
|
+
}
|
|
6957
|
+
},
|
|
6958
|
+
{
|
|
6959
|
+
name: "profile_update",
|
|
6960
|
+
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}`,
|
|
6961
|
+
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 },
|
|
6962
|
+
required: ["id", "expectedRevision"],
|
|
6963
|
+
readOnly: false,
|
|
6964
|
+
organizes: true,
|
|
6965
|
+
idempotent: true,
|
|
6966
|
+
run: async (hub, args, signal) => {
|
|
6967
|
+
const expectedRevision = revision2(args);
|
|
6968
|
+
const patch = picked2(args, ["title", "description", "tags", "metadata", "projects"]);
|
|
6969
|
+
if (Object.keys(patch).length === 0)
|
|
6970
|
+
throw new ArgError2("give at least one field to change");
|
|
6971
|
+
const { profile } = await hub.profiles.update(need5(args, "id"), { expectedRevision, ...patch }, { signal });
|
|
6972
|
+
return `updated: ${profileLine(profile)}`;
|
|
6973
|
+
}
|
|
6974
|
+
},
|
|
6975
|
+
{
|
|
6976
|
+
name: "profile_version_create",
|
|
6977
|
+
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}`,
|
|
6978
|
+
properties: { id: ID4, ...CONTENT_FIELDS, fromVersion: { type: "integer", minimum: 1, description: "A retained version to restore as the next one." }, expectedRevision: REVISION3, idempotencyKey: IDEMPOTENCY2 },
|
|
6979
|
+
required: ["id", "expectedRevision"],
|
|
6980
|
+
readOnly: false,
|
|
6981
|
+
organizes: true,
|
|
6982
|
+
idempotent: true,
|
|
6983
|
+
run: async (hub, args, signal) => {
|
|
6984
|
+
const expectedRevision = revision2(args);
|
|
6985
|
+
const id = need5(args, "id");
|
|
6986
|
+
const given = Object.keys(picked2(args, Object.keys(CONTENT_FIELDS)));
|
|
6987
|
+
const options = { signal, ...idempotency(args) };
|
|
6988
|
+
if (args.fromVersion !== undefined) {
|
|
6989
|
+
if (given.length)
|
|
6990
|
+
throw new ArgError2(`give either fromVersion or content fields (${given.join(", ")}), not both`);
|
|
6991
|
+
return versionText(await hub.profiles.createVersion(id, { expectedRevision, fromVersion: whole2(args, "fromVersion", 1, "a version number") }, options));
|
|
6992
|
+
}
|
|
6993
|
+
if (!given.length)
|
|
6994
|
+
throw new ArgError2("give content fields (instructions, model, skills, commands) or fromVersion");
|
|
6995
|
+
const { content } = await latestContent(hub.profiles, id, signal);
|
|
6996
|
+
const next = { ...content };
|
|
6997
|
+
for (const key of given) {
|
|
6998
|
+
if (args[key] === null)
|
|
6999
|
+
delete next[key];
|
|
7000
|
+
else
|
|
7001
|
+
next[key] = args[key];
|
|
7002
|
+
}
|
|
7003
|
+
return versionText(await hub.profiles.createVersion(id, { expectedRevision, content: next }, options));
|
|
7004
|
+
}
|
|
7005
|
+
},
|
|
7006
|
+
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) => ({
|
|
7007
|
+
...content,
|
|
7008
|
+
skills: setNamed(content.skills, { name: need5(args, "name"), description: need5(args, "description"), body: need5(args, "body") })
|
|
7009
|
+
})),
|
|
7010
|
+
contentTool("profile_skill_remove", "Detach a skill from a profile.", { name: { ...STRING5, description: "The skill's name." } }, ["name"], (content, args, latest) => ({
|
|
7011
|
+
...content,
|
|
7012
|
+
skills: removeNamed(content.skills, need5(args, "name"), "skill", latest)
|
|
7013
|
+
})),
|
|
7014
|
+
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") }) })),
|
|
7015
|
+
contentTool("profile_command_remove", "Detach a command from a profile.", { name: { ...STRING5, description: "The command's name." } }, ["name"], (content, args, latest) => ({
|
|
7016
|
+
...content,
|
|
7017
|
+
commands: removeNamed(content.commands, need5(args, "name"), "command", latest)
|
|
7018
|
+
})),
|
|
7019
|
+
stateTool("archive", "Archive a profile: it takes no new versions or pins; pinned sessions keep theirs; reversible with profile_restore."),
|
|
7020
|
+
stateTool("restore", "Restore an archived profile (it takes versions and pins again)."),
|
|
7021
|
+
{
|
|
7022
|
+
name: "profile_assign",
|
|
7023
|
+
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}`,
|
|
7024
|
+
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)." } },
|
|
7025
|
+
required: ["addr", "profileID", "version", "expectedRevision"],
|
|
7026
|
+
readOnly: false,
|
|
7027
|
+
idempotent: true,
|
|
7028
|
+
run: async (hub, args, signal) => {
|
|
7029
|
+
const expectedRevision = revision2(args);
|
|
7030
|
+
const { assignment: a } = await hub.profiles.assign(need5(args, "addr"), { profileID: need5(args, "profileID"), version: whole2(args, "version", 1, "a version number"), expectedRevision }, { signal });
|
|
7031
|
+
return `${a.sessionID} pins ${a.profileID} v${a.version} r${a.revision}${a.ack ? `, ack ${a.ack.status}${a.ack.offline ? " (offline)" : ""}` : ""}`;
|
|
7032
|
+
}
|
|
7033
|
+
},
|
|
7034
|
+
{
|
|
7035
|
+
name: "profile_unassign",
|
|
7036
|
+
description: `Remove a session's pin (what it already applied stays until it restarts). Send the pin's revision (profile_assign's answer). ${SCOPES2}`,
|
|
7037
|
+
properties: { addr: { ...STRING5, description: "The session." }, expectedRevision: { ...REVISION3, description: "The session's pin revision." } },
|
|
7038
|
+
required: ["addr", "expectedRevision"],
|
|
7039
|
+
readOnly: false,
|
|
7040
|
+
idempotent: true,
|
|
7041
|
+
run: async (hub, args, signal) => {
|
|
7042
|
+
const addr = need5(args, "addr");
|
|
7043
|
+
await hub.profiles.unassign(addr, revision2(args), { signal });
|
|
7044
|
+
return `unpinned ${addr}`;
|
|
7045
|
+
}
|
|
7046
|
+
}
|
|
7047
|
+
];
|
|
7048
|
+
|
|
6636
7049
|
// packages/cli/src/watchlist.ts
|
|
6637
7050
|
var safe = (value) => sanitizeLabel(String(value), 1024);
|
|
6638
7051
|
var list2 = (items) => items.length ? items.map(safe).join(", ") : "none";
|
|
@@ -6659,11 +7072,11 @@ function watchlistChangedText(verb, r) {
|
|
|
6659
7072
|
var PAST = { watch: "watching", unwatch: "not watching", mute: "muted", unmute: "unmuted" };
|
|
6660
7073
|
var watchlistVerbText = (verb, r) => watchlistChangedText(PAST[verb], r);
|
|
6661
7074
|
var ADDR = { type: "string", description: "Session address: ses_… (or a unique prefix), @name, @@role, @name.project." };
|
|
6662
|
-
var
|
|
6663
|
-
var
|
|
7075
|
+
var STRINGS3 = { type: "array", items: { type: "string" } };
|
|
7076
|
+
var need6 = (args, key) => {
|
|
6664
7077
|
const value = args[key];
|
|
6665
7078
|
if (typeof value !== "string" || !value.trim())
|
|
6666
|
-
throw new
|
|
7079
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
6667
7080
|
return value;
|
|
6668
7081
|
};
|
|
6669
7082
|
var flag = (args, key, fallback) => {
|
|
@@ -6671,7 +7084,7 @@ var flag = (args, key, fallback) => {
|
|
|
6671
7084
|
if (value === undefined || value === null)
|
|
6672
7085
|
return fallback;
|
|
6673
7086
|
if (typeof value !== "boolean")
|
|
6674
|
-
throw new
|
|
7087
|
+
throw new ToolArgError(`"${key}" must be true or false`);
|
|
6675
7088
|
return value;
|
|
6676
7089
|
};
|
|
6677
7090
|
var UPDATE_FIELDS = ["autoActive", "activeWithinHours", "usePins", "sessions", "projects", "labels", "muted", "mutedProjects"];
|
|
@@ -6691,11 +7104,11 @@ var watchlistTools = [
|
|
|
6691
7104
|
autoActive: { type: "boolean" },
|
|
6692
7105
|
activeWithinHours: { type: "integer", minimum: 1, maximum: 24 },
|
|
6693
7106
|
usePins: { type: "boolean" },
|
|
6694
|
-
sessions:
|
|
6695
|
-
projects:
|
|
7107
|
+
sessions: STRINGS3,
|
|
7108
|
+
projects: STRINGS3,
|
|
6696
7109
|
labels: { type: "array", items: { type: "object", additionalProperties: { type: "string" } } },
|
|
6697
|
-
muted:
|
|
6698
|
-
mutedProjects:
|
|
7110
|
+
muted: STRINGS3,
|
|
7111
|
+
mutedProjects: STRINGS3
|
|
6699
7112
|
},
|
|
6700
7113
|
required: ["expectedRevision"],
|
|
6701
7114
|
readOnly: false,
|
|
@@ -6703,10 +7116,10 @@ var watchlistTools = [
|
|
|
6703
7116
|
run: async (hub, args, signal) => {
|
|
6704
7117
|
const expected = args.expectedRevision;
|
|
6705
7118
|
if (typeof expected !== "number" || !Number.isSafeInteger(expected) || expected < 0)
|
|
6706
|
-
throw new
|
|
7119
|
+
throw new ToolArgError(`"expectedRevision" is required (the revision you read)`);
|
|
6707
7120
|
const unknown = Object.keys(args).filter((k) => k !== "expectedRevision" && !UPDATE_FIELDS.includes(k));
|
|
6708
7121
|
if (unknown.length)
|
|
6709
|
-
throw new
|
|
7122
|
+
throw new ToolArgError(`Unknown argument${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${safe(k).slice(0, 40)}"`).join(", ")}: use ${UPDATE_FIELDS.join(", ")} and expectedRevision`);
|
|
6710
7123
|
const patch = {};
|
|
6711
7124
|
for (const field of UPDATE_FIELDS)
|
|
6712
7125
|
if (args[field] !== undefined && args[field] !== null)
|
|
@@ -6727,7 +7140,7 @@ ${watchlistConfigText(r.config)}`;
|
|
|
6727
7140
|
idempotent: true,
|
|
6728
7141
|
run: async (hub, args, signal) => {
|
|
6729
7142
|
const on = flag(args, "watch", true);
|
|
6730
|
-
const addr =
|
|
7143
|
+
const addr = need6(args, "addr");
|
|
6731
7144
|
const r = on ? await hub.concierge.watchlist.watch(addr, { signal }) : await hub.concierge.watchlist.unwatch(addr, { signal });
|
|
6732
7145
|
return watchlistVerbText(on ? "watch" : "unwatch", r);
|
|
6733
7146
|
}
|
|
@@ -6742,7 +7155,7 @@ ${watchlistConfigText(r.config)}`;
|
|
|
6742
7155
|
idempotent: true,
|
|
6743
7156
|
run: async (hub, args, signal) => {
|
|
6744
7157
|
const on = flag(args, "mute", true);
|
|
6745
|
-
const addr =
|
|
7158
|
+
const addr = need6(args, "addr");
|
|
6746
7159
|
const r = on ? await hub.concierge.watchlist.mute(addr, { signal }) : await hub.concierge.watchlist.unmute(addr, { signal });
|
|
6747
7160
|
return watchlistVerbText(on ? "mute" : "unmute", r);
|
|
6748
7161
|
}
|
|
@@ -6752,21 +7165,19 @@ ${watchlistConfigText(r.config)}`;
|
|
|
6752
7165
|
// packages/cli/src/mcp.ts
|
|
6753
7166
|
var MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
6754
7167
|
var RPC = { parse: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602, internal: -32603 };
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
}
|
|
6758
|
-
var need6 = (args, key) => {
|
|
7168
|
+
var ArgError3 = ToolArgError;
|
|
7169
|
+
var need7 = (args, key) => {
|
|
6759
7170
|
const value = args[key];
|
|
6760
7171
|
if (typeof value !== "string" || !value.trim())
|
|
6761
|
-
throw new
|
|
7172
|
+
throw new ArgError3(`"${key}" is required (a non-empty string)`);
|
|
6762
7173
|
return value;
|
|
6763
7174
|
};
|
|
6764
|
-
var
|
|
7175
|
+
var optional7 = (args, key) => {
|
|
6765
7176
|
const value = args[key];
|
|
6766
7177
|
if (value === undefined || value === null)
|
|
6767
7178
|
return;
|
|
6768
7179
|
if (typeof value !== "string")
|
|
6769
|
-
throw new
|
|
7180
|
+
throw new ArgError3(`"${key}" must be a string`);
|
|
6770
7181
|
return value.trim() ? value : undefined;
|
|
6771
7182
|
};
|
|
6772
7183
|
var optionalInt = (args, key, min, max) => {
|
|
@@ -6774,7 +7185,7 @@ var optionalInt = (args, key, min, max) => {
|
|
|
6774
7185
|
if (value === undefined || value === null)
|
|
6775
7186
|
return;
|
|
6776
7187
|
if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max)
|
|
6777
|
-
throw new
|
|
7188
|
+
throw new ArgError3(`"${key}" must be a whole number from ${min} to ${max}`);
|
|
6778
7189
|
return value;
|
|
6779
7190
|
};
|
|
6780
7191
|
var optionalStrings = (args, key) => {
|
|
@@ -6782,7 +7193,7 @@ var optionalStrings = (args, key) => {
|
|
|
6782
7193
|
if (value === undefined || value === null)
|
|
6783
7194
|
return;
|
|
6784
7195
|
if (!Array.isArray(value) || !value.every((v) => typeof v === "string"))
|
|
6785
|
-
throw new
|
|
7196
|
+
throw new ArgError3(`"${key}" must be a list of strings`);
|
|
6786
7197
|
return value;
|
|
6787
7198
|
};
|
|
6788
7199
|
var optionalBool = (args, key) => {
|
|
@@ -6790,7 +7201,7 @@ var optionalBool = (args, key) => {
|
|
|
6790
7201
|
if (value === undefined || value === null)
|
|
6791
7202
|
return;
|
|
6792
7203
|
if (typeof value !== "boolean")
|
|
6793
|
-
throw new
|
|
7204
|
+
throw new ArgError3(`"${key}" must be true or false`);
|
|
6794
7205
|
return value;
|
|
6795
7206
|
};
|
|
6796
7207
|
var optionalWait = (args) => {
|
|
@@ -6798,7 +7209,7 @@ var optionalWait = (args) => {
|
|
|
6798
7209
|
if (value === undefined || value === null)
|
|
6799
7210
|
return;
|
|
6800
7211
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
6801
|
-
throw new
|
|
7212
|
+
throw new ArgError3(`"wait" must be a number of seconds (0–${WAIT_POLL_MAX_S})`);
|
|
6802
7213
|
return Math.min(value, WAIT_POLL_MAX_S);
|
|
6803
7214
|
};
|
|
6804
7215
|
var optionalExpires = (args) => {
|
|
@@ -6812,11 +7223,11 @@ var optionalExpires = (args) => {
|
|
|
6812
7223
|
return durationArg(value.trim(), '"expires"');
|
|
6813
7224
|
} catch (cause) {
|
|
6814
7225
|
if (cause instanceof UsageError)
|
|
6815
|
-
throw new
|
|
7226
|
+
throw new ArgError3(cause.message);
|
|
6816
7227
|
throw cause;
|
|
6817
7228
|
}
|
|
6818
7229
|
}
|
|
6819
|
-
throw new
|
|
7230
|
+
throw new ArgError3(`"expires" must be seconds or a duration like 30s, 5m, 2h, 1d`);
|
|
6820
7231
|
};
|
|
6821
7232
|
var sendOptions = (args) => {
|
|
6822
7233
|
const wake = optionalBool(args, "wake");
|
|
@@ -6824,7 +7235,7 @@ var sendOptions = (args) => {
|
|
|
6824
7235
|
return { ...wake !== undefined ? { wake } : {}, ...urgent !== undefined ? { urgent } : {} };
|
|
6825
7236
|
};
|
|
6826
7237
|
var whyOption = (args) => {
|
|
6827
|
-
const why =
|
|
7238
|
+
const why = optional7(args, "why");
|
|
6828
7239
|
return why !== undefined ? { why } : {};
|
|
6829
7240
|
};
|
|
6830
7241
|
var optionalDelivery = (args) => {
|
|
@@ -6832,18 +7243,18 @@ var optionalDelivery = (args) => {
|
|
|
6832
7243
|
if (value === undefined || value === null)
|
|
6833
7244
|
return;
|
|
6834
7245
|
if (!DELIVERIES.includes(value))
|
|
6835
|
-
throw new
|
|
7246
|
+
throw new ArgError3(`"delivery" is one of ${DELIVERIES.join(", ")}`);
|
|
6836
7247
|
return value;
|
|
6837
7248
|
};
|
|
6838
7249
|
var formAnswer = (args) => {
|
|
6839
7250
|
const cancel = optionalBool(args, "cancel") === true;
|
|
6840
7251
|
const answer = args.answer;
|
|
6841
7252
|
if (cancel && answer !== undefined && answer !== null)
|
|
6842
|
-
throw new
|
|
7253
|
+
throw new ArgError3(`give either "answer" or "cancel", not both`);
|
|
6843
7254
|
if (cancel)
|
|
6844
7255
|
return "cancel";
|
|
6845
7256
|
if (typeof answer !== "object" || answer === null || Array.isArray(answer))
|
|
6846
|
-
throw new
|
|
7257
|
+
throw new ArgError3(`"answer" is required (an object: field → value), or "cancel": true`);
|
|
6847
7258
|
return answer;
|
|
6848
7259
|
};
|
|
6849
7260
|
var optionalEnum = (args, key, options) => {
|
|
@@ -6851,7 +7262,7 @@ var optionalEnum = (args, key, options) => {
|
|
|
6851
7262
|
if (value === undefined || value === null)
|
|
6852
7263
|
return;
|
|
6853
7264
|
if (!options.includes(value))
|
|
6854
|
-
throw new
|
|
7265
|
+
throw new ArgError3(`"${key}" is one of ${options.join(", ")}`);
|
|
6855
7266
|
return value;
|
|
6856
7267
|
};
|
|
6857
7268
|
var optionalLabels = (args, key) => {
|
|
@@ -6859,7 +7270,7 @@ var optionalLabels = (args, key) => {
|
|
|
6859
7270
|
if (value === undefined || value === null)
|
|
6860
7271
|
return;
|
|
6861
7272
|
if (typeof value !== "object" || Array.isArray(value) || !Object.values(value).every((v) => typeof v === "string"))
|
|
6862
|
-
throw new
|
|
7273
|
+
throw new ArgError3(`"${key}" must be an object of text values (key → value)`);
|
|
6863
7274
|
return Object.keys(value).length ? value : undefined;
|
|
6864
7275
|
};
|
|
6865
7276
|
var definedFields = (fields) => {
|
|
@@ -6867,35 +7278,35 @@ var definedFields = (fields) => {
|
|
|
6867
7278
|
return Object.keys(out).length ? out : undefined;
|
|
6868
7279
|
};
|
|
6869
7280
|
function launchSpec(args) {
|
|
6870
|
-
const repo =
|
|
7281
|
+
const repo = need7(args, "repo");
|
|
6871
7282
|
if (!repo.startsWith("/"))
|
|
6872
|
-
throw new
|
|
7283
|
+
throw new ArgError3(`"repo" must be an absolute path on the node`);
|
|
6873
7284
|
const kind = optionalEnum(args, "runtime", RUNTIME_KINDS);
|
|
6874
|
-
const profile =
|
|
7285
|
+
const profile = optional7(args, "profile");
|
|
6875
7286
|
if (profile !== undefined && kind === undefined)
|
|
6876
|
-
throw new
|
|
7287
|
+
throw new ArgError3(`"profile" needs "runtime" (e.g. openshell)`);
|
|
6877
7288
|
const nodeLabels = optionalLabels(args, "nodeLabels");
|
|
6878
|
-
const machine =
|
|
7289
|
+
const machine = optional7(args, "machine");
|
|
6879
7290
|
const prefer = optionalBool(args, "prefer") === true;
|
|
6880
7291
|
if (prefer && machine === undefined && nodeLabels === undefined)
|
|
6881
|
-
throw new
|
|
6882
|
-
const workspace = { repo, ...definedFields({ mode: optionalEnum(args, "mode", WORKSPACE_MODES), branch:
|
|
7292
|
+
throw new ArgError3(`"prefer" needs "machine" or "nodeLabels"`);
|
|
7293
|
+
const workspace = { repo, ...definedFields({ mode: optionalEnum(args, "mode", WORKSPACE_MODES), branch: optional7(args, "branch"), base: optional7(args, "base"), remote: optional7(args, "remote") }) };
|
|
6883
7294
|
return {
|
|
6884
7295
|
...definedFields({
|
|
6885
|
-
key:
|
|
6886
|
-
name:
|
|
7296
|
+
key: optional7(args, "key"),
|
|
7297
|
+
name: optional7(args, "name")?.replace(/^@+/, ""),
|
|
6887
7298
|
roles: optionalStrings(args, "roles")?.map((r) => r.replace(/^@@/, "")),
|
|
6888
|
-
harness:
|
|
6889
|
-
agent:
|
|
6890
|
-
model:
|
|
7299
|
+
harness: optional7(args, "harness"),
|
|
7300
|
+
agent: optional7(args, "agent"),
|
|
7301
|
+
model: optional7(args, "model")
|
|
6891
7302
|
}),
|
|
6892
7303
|
workspace,
|
|
6893
7304
|
...definedFields({
|
|
6894
7305
|
runtime: kind === undefined ? undefined : { kind, ...profile !== undefined ? { profile } : {} },
|
|
6895
7306
|
placement: definedFields({ labels: nodeLabels, machine, mode: prefer ? "prefer" : undefined }),
|
|
6896
|
-
prompt:
|
|
7307
|
+
prompt: optional7(args, "prompt"),
|
|
6897
7308
|
labels: optionalLabels(args, "labels"),
|
|
6898
|
-
end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout:
|
|
7309
|
+
end: definedFields({ when: optionalEnum(args, "end", LAUNCH_END_WHEN), timeout: optional7(args, "timeout"), cleanup: optionalEnum(args, "cleanup", CLEANUP_POLICIES) })
|
|
6899
7310
|
})
|
|
6900
7311
|
};
|
|
6901
7312
|
}
|
|
@@ -6934,14 +7345,14 @@ ${box.messages.map(envelope3).join(`
|
|
|
6934
7345
|
`) : "inbox empty";
|
|
6935
7346
|
}
|
|
6936
7347
|
function messageText({ message, answer }) {
|
|
6937
|
-
const lines = [NOT_FROM_USER, `${message.kind} ${message.id}: ${message.state}${message.delivery === "dropped" ? ` · ${deliveryText(
|
|
7348
|
+
const lines = [NOT_FROM_USER, `${message.kind} ${message.id}: ${message.state}${message.delivery === "dropped" || message.delivery === "expired" ? ` · ${deliveryText(message.delivery)}` : ""}`, envelope3(message)];
|
|
6938
7349
|
if (answer)
|
|
6939
7350
|
lines.push(`${message.state} by ${answer.id}:`, envelope3(answer));
|
|
6940
7351
|
return lines.join(`
|
|
6941
7352
|
`);
|
|
6942
7353
|
}
|
|
6943
7354
|
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
|
|
7355
|
+
var STRING6 = { type: "string" };
|
|
6945
7356
|
var SEND = {
|
|
6946
7357
|
wake: { type: "boolean", description: "false: admit the message without starting a turn." },
|
|
6947
7358
|
urgent: { type: "boolean", description: "Steer into the recipient's running turn." },
|
|
@@ -6964,7 +7375,7 @@ var archiveTools = (archived) => [{
|
|
|
6964
7375
|
organizes: true,
|
|
6965
7376
|
idempotent: true,
|
|
6966
7377
|
run: async (hub, args, signal) => {
|
|
6967
|
-
const addr =
|
|
7378
|
+
const addr = need7(args, "addr");
|
|
6968
7379
|
const result = await hub.setArchived(addr, archived, { signal });
|
|
6969
7380
|
return `${archived ? "Archived" : "Unarchived"} ${addr} (${result.sessionID})${result.changed ? "" : "; already in that state"}`;
|
|
6970
7381
|
}
|
|
@@ -6978,7 +7389,7 @@ var pinTools = (pinned) => [{
|
|
|
6978
7389
|
organizes: true,
|
|
6979
7390
|
idempotent: true,
|
|
6980
7391
|
run: async (hub, args, signal) => {
|
|
6981
|
-
const addr =
|
|
7392
|
+
const addr = need7(args, "addr");
|
|
6982
7393
|
const r = await hub.setPinned(addr, pinned, { signal });
|
|
6983
7394
|
return `${pinned ? "Pinned" : "Unpinned"} ${addr} (${r.sessionID})${r.changed ? "" : "; already in that state"}`;
|
|
6984
7395
|
}
|
|
@@ -6986,15 +7397,15 @@ var pinTools = (pinned) => [{
|
|
|
6986
7397
|
var archiveInactiveTool = {
|
|
6987
7398
|
name: "archive_inactive",
|
|
6988
7399
|
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:
|
|
7400
|
+
properties: { preview: { type: "boolean" }, inactiveFor: STRING6, limit: { type: "integer", minimum: 1, maximum: 200 }, previewToken: STRING6, confirm: { type: "boolean" } },
|
|
6990
7401
|
readOnly: false,
|
|
6991
7402
|
organizes: true,
|
|
6992
7403
|
run: async (hub, args, signal) => {
|
|
6993
7404
|
if (args.preview === true)
|
|
6994
|
-
return JSON.stringify(await hub.previewArchiveInactive(
|
|
7405
|
+
return JSON.stringify(await hub.previewArchiveInactive(need7(args, "inactiveFor"), optionalInt(args, "limit", 1, 200) ?? 50, { signal }));
|
|
6995
7406
|
if (args.confirm === true)
|
|
6996
|
-
return JSON.stringify(await hub.archiveInactive(
|
|
6997
|
-
throw new
|
|
7407
|
+
return JSON.stringify(await hub.archiveInactive(need7(args, "previewToken"), { signal }));
|
|
7408
|
+
throw new ArgError3("Use preview:true with inactiveFor, then confirm:true with previewToken");
|
|
6998
7409
|
}
|
|
6999
7410
|
};
|
|
7000
7411
|
var intentTools = [
|
|
@@ -7004,44 +7415,44 @@ var intentTools = [
|
|
|
7004
7415
|
properties: { addr: ADDR2 },
|
|
7005
7416
|
required: ["addr"],
|
|
7006
7417
|
readOnly: true,
|
|
7007
|
-
run: async (hub, args, signal) => intentLine(
|
|
7418
|
+
run: async (hub, args, signal) => intentLine(need7(args, "addr"), (await hub.intent(need7(args, "addr"), { signal })).intent)
|
|
7008
7419
|
},
|
|
7009
7420
|
{
|
|
7010
7421
|
name: "session_intent_set",
|
|
7011
7422
|
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:
|
|
7423
|
+
properties: { addr: ADDR2, text: STRING6, clear: { type: "boolean" }, expectedRevision: { type: "integer", minimum: 0 } },
|
|
7013
7424
|
required: ["addr"],
|
|
7014
7425
|
readOnly: false,
|
|
7015
7426
|
organizes: true,
|
|
7016
7427
|
idempotent: true,
|
|
7017
7428
|
run: async (hub, args, signal) => {
|
|
7018
|
-
const addr =
|
|
7019
|
-
const text =
|
|
7429
|
+
const addr = need7(args, "addr");
|
|
7430
|
+
const text = optional7(args, "text");
|
|
7020
7431
|
const clear = args.clear === true;
|
|
7021
7432
|
if (clear === (text !== undefined))
|
|
7022
|
-
throw new
|
|
7433
|
+
throw new ArgError3('Give "text" or "clear": true');
|
|
7023
7434
|
const expectedRevision = optionalInt(args, "expectedRevision", 0, Number.MAX_SAFE_INTEGER);
|
|
7024
7435
|
return intentLine(addr, await writeIntent(hub, addr, clear ? null : text, { signal, ...expectedRevision !== undefined ? { expectedRevision } : {} }));
|
|
7025
7436
|
}
|
|
7026
7437
|
}
|
|
7027
7438
|
];
|
|
7028
7439
|
var TARGET = { ...ADDR2, description: `${ADDR2.description} Required for an app token; a session token defaults to itself.` };
|
|
7029
|
-
var ROLE2 = { ...
|
|
7030
|
-
var ROLE_PROJECT = { ...
|
|
7440
|
+
var ROLE2 = { ...STRING6, description: "A role (reviewer, @@reviewer or reviewer.project)." };
|
|
7441
|
+
var ROLE_PROJECT = { ...STRING6, description: "Scope the role to this project (as role.project)." };
|
|
7031
7442
|
var MANAGE_NOTE = "Needs scope sessions:manage for an app token";
|
|
7032
7443
|
var roleText2 = (r) => `@@${r.role}${r.project !== undefined ? `.${r.project}` : ""}`;
|
|
7033
7444
|
var nameTools = [
|
|
7034
7445
|
{
|
|
7035
7446
|
name: "name",
|
|
7036
7447
|
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: { ...
|
|
7448
|
+
properties: { addr: TARGET, name: { ...STRING6, description: "The name: 2–40 characters a-z, 0-9 and - (a leading @ is fine)." } },
|
|
7038
7449
|
required: ["name"],
|
|
7039
7450
|
readOnly: false,
|
|
7040
7451
|
organizes: true,
|
|
7041
7452
|
idempotent: true,
|
|
7042
7453
|
run: async (hub, args, signal) => {
|
|
7043
|
-
const session =
|
|
7044
|
-
const r = await hub.name(
|
|
7454
|
+
const session = optional7(args, "addr");
|
|
7455
|
+
const r = await hub.name(need7(args, "name"), { ...session ? { session } : {}, signal });
|
|
7045
7456
|
return `Named ${r.sessionID} @${r.name}${r.alias ? ` (@${r.alias} stays an alias for 24 h)` : ""}`;
|
|
7046
7457
|
}
|
|
7047
7458
|
},
|
|
@@ -7054,9 +7465,9 @@ var nameTools = [
|
|
|
7054
7465
|
organizes: true,
|
|
7055
7466
|
idempotent: true,
|
|
7056
7467
|
run: async (hub, args, signal) => {
|
|
7057
|
-
const session =
|
|
7058
|
-
const project =
|
|
7059
|
-
const r = await hub.claimRole(
|
|
7468
|
+
const session = optional7(args, "addr");
|
|
7469
|
+
const project = optional7(args, "project");
|
|
7470
|
+
const r = await hub.claimRole(need7(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, ...optionalBool(args, "exclusive") ? { exclusive: true } : {}, signal });
|
|
7060
7471
|
return `${r.sessionID} holds ${roleText2(r)} (generation ${r.generation}${r.exclusive ? ", exclusive" : ""})`;
|
|
7061
7472
|
}
|
|
7062
7473
|
},
|
|
@@ -7068,9 +7479,9 @@ var nameTools = [
|
|
|
7068
7479
|
readOnly: false,
|
|
7069
7480
|
organizes: true,
|
|
7070
7481
|
run: async (hub, args, signal) => {
|
|
7071
|
-
const session =
|
|
7072
|
-
const project =
|
|
7073
|
-
const r = await hub.releaseRole(
|
|
7482
|
+
const session = optional7(args, "addr");
|
|
7483
|
+
const project = optional7(args, "project");
|
|
7484
|
+
const r = await hub.releaseRole(need7(args, "role"), { ...session ? { session } : {}, ...project !== undefined ? { project } : {}, signal });
|
|
7074
7485
|
return `${r.sessionID} released ${roleText2(r)}`;
|
|
7075
7486
|
}
|
|
7076
7487
|
}
|
|
@@ -7079,16 +7490,16 @@ var eventsTool = {
|
|
|
7079
7490
|
name: "events",
|
|
7080
7491
|
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
7492
|
properties: {
|
|
7082
|
-
cursor: { ...
|
|
7493
|
+
cursor: { ...STRING6, description: "The cursor from the previous events answer (hev1_…); omit to start at the latest." },
|
|
7083
7494
|
wait: { type: "integer", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait while nothing matches (0–${WAIT_POLL_MAX_S}).` },
|
|
7084
7495
|
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:
|
|
7496
|
+
types: { type: "array", items: STRING6, description: "Only these event types (exact, or a family like launch.*)." },
|
|
7497
|
+
sessions: { type: "array", items: STRING6, description: "Only events of these sessions (ses_…, @name, @@role)." }
|
|
7087
7498
|
},
|
|
7088
7499
|
readOnly: true,
|
|
7089
7500
|
run: async (hub, args, signal) => {
|
|
7090
7501
|
const page = await hub.events({
|
|
7091
|
-
cursor:
|
|
7502
|
+
cursor: optional7(args, "cursor"),
|
|
7092
7503
|
wait: optionalInt(args, "wait", 0, WAIT_POLL_MAX_S),
|
|
7093
7504
|
limit: optionalInt(args, "limit", 1, 200),
|
|
7094
7505
|
types: optionalStrings(args, "types"),
|
|
@@ -7103,13 +7514,13 @@ var webhookTools = [
|
|
|
7103
7514
|
{
|
|
7104
7515
|
name: "webhook_create",
|
|
7105
7516
|
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:
|
|
7517
|
+
properties: { url: STRING6, events: { type: "array", items: STRING6 }, label: STRING6 },
|
|
7107
7518
|
required: ["url"],
|
|
7108
7519
|
readOnly: false,
|
|
7109
7520
|
run: async (hub, args, signal) => {
|
|
7110
7521
|
const events = optionalStrings(args, "events");
|
|
7111
|
-
const label =
|
|
7112
|
-
const r = await hub.webhookCreate({ url:
|
|
7522
|
+
const label = optional7(args, "label");
|
|
7523
|
+
const r = await hub.webhookCreate({ url: need7(args, "url"), ...events ? { events } : {}, ...label ? { label } : {} }, { signal });
|
|
7113
7524
|
return `${webhookText(r.webhook)}
|
|
7114
7525
|
signing secret (shown once): ${r.secret}`;
|
|
7115
7526
|
}
|
|
@@ -7128,11 +7539,11 @@ signing secret (shown once): ${r.secret}`;
|
|
|
7128
7539
|
{
|
|
7129
7540
|
name: "webhook_show",
|
|
7130
7541
|
description: "Show one webhook and its retained delivery attempts; never its secret.",
|
|
7131
|
-
properties: { id:
|
|
7542
|
+
properties: { id: STRING6 },
|
|
7132
7543
|
required: ["id"],
|
|
7133
7544
|
readOnly: true,
|
|
7134
7545
|
run: async (hub, args, signal) => {
|
|
7135
|
-
const r = await hub.webhook(
|
|
7546
|
+
const r = await hub.webhook(need7(args, "id"), { signal });
|
|
7136
7547
|
return `${webhookText(r.webhook)}
|
|
7137
7548
|
${JSON.stringify(r.deliveries)}`;
|
|
7138
7549
|
}
|
|
@@ -7140,25 +7551,25 @@ ${JSON.stringify(r.deliveries)}`;
|
|
|
7140
7551
|
{
|
|
7141
7552
|
name: "webhook_delete",
|
|
7142
7553
|
description: "Delete one webhook and its pending deliveries.",
|
|
7143
|
-
properties: { id:
|
|
7554
|
+
properties: { id: STRING6 },
|
|
7144
7555
|
required: ["id"],
|
|
7145
7556
|
readOnly: false,
|
|
7146
7557
|
organizes: true,
|
|
7147
7558
|
idempotent: false,
|
|
7148
7559
|
run: async (hub, args, signal) => {
|
|
7149
|
-
const id =
|
|
7560
|
+
const id = need7(args, "id");
|
|
7150
7561
|
await hub.webhookDelete(id, { signal });
|
|
7151
7562
|
return `Deleted webhook ${id}`;
|
|
7152
7563
|
}
|
|
7153
7564
|
}
|
|
7154
7565
|
];
|
|
7155
7566
|
async function profileTarget(hub, args, signal) {
|
|
7156
|
-
const addr =
|
|
7567
|
+
const addr = optional7(args, "addr");
|
|
7157
7568
|
if (addr)
|
|
7158
7569
|
return addr;
|
|
7159
7570
|
const { actor } = await hub.whoami({ signal });
|
|
7160
7571
|
if (actor.kind !== "session")
|
|
7161
|
-
throw new
|
|
7572
|
+
throw new ArgError3(`"addr" is required unless this credential is a session's`);
|
|
7162
7573
|
return actor.sessionID;
|
|
7163
7574
|
}
|
|
7164
7575
|
var optionalParams = (args) => optionalLabels(args, "params") ?? {};
|
|
@@ -7192,11 +7603,11 @@ var profileTools = [
|
|
|
7192
7603
|
{
|
|
7193
7604
|
name: "profile_skill",
|
|
7194
7605
|
description: "Load one skill's full text from the profile pinned to a session (your own by default), on demand.",
|
|
7195
|
-
properties: { name:
|
|
7606
|
+
properties: { name: STRING6, addr: ADDR2 },
|
|
7196
7607
|
required: ["name"],
|
|
7197
7608
|
readOnly: true,
|
|
7198
7609
|
run: async (hub, args, signal) => {
|
|
7199
|
-
const r = await hub.profiles.sessionSkill(await profileTarget(hub, args, signal),
|
|
7610
|
+
const r = await hub.profiles.sessionSkill(await profileTarget(hub, args, signal), need7(args, "name"), { signal });
|
|
7200
7611
|
return `skill ${r.skill.name} (${r.profileID} v${r.version}):
|
|
7201
7612
|
${sanitizeText(r.skill.body)}`;
|
|
7202
7613
|
}
|
|
@@ -7204,11 +7615,11 @@ ${sanitizeText(r.skill.body)}`;
|
|
|
7204
7615
|
{
|
|
7205
7616
|
name: "profile_command",
|
|
7206
7617
|
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:
|
|
7618
|
+
properties: { name: STRING6, params: { type: "object", additionalProperties: STRING6, description: "Parameter name → text value." }, addr: ADDR2 },
|
|
7208
7619
|
required: ["name"],
|
|
7209
7620
|
readOnly: true,
|
|
7210
7621
|
run: async (hub, args, signal) => {
|
|
7211
|
-
const r = await hub.profiles.sessionRender(await profileTarget(hub, args, signal),
|
|
7622
|
+
const r = await hub.profiles.sessionRender(await profileTarget(hub, args, signal), need7(args, "name"), optionalParams(args), { signal });
|
|
7212
7623
|
return r.prompt;
|
|
7213
7624
|
}
|
|
7214
7625
|
},
|
|
@@ -7227,13 +7638,13 @@ ${sanitizeText(r.skill.body)}`;
|
|
|
7227
7638
|
{
|
|
7228
7639
|
name: "profile_usage",
|
|
7229
7640
|
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:
|
|
7641
|
+
properties: { id: STRING6, limit: { type: "integer", minimum: 1, maximum: 200, description: "Sessions per page (default 50)." }, cursor: STRING6 },
|
|
7231
7642
|
required: ["id"],
|
|
7232
7643
|
readOnly: true,
|
|
7233
7644
|
run: async (hub, args, signal) => {
|
|
7234
7645
|
const limit = optionalInt(args, "limit", 1, 200);
|
|
7235
|
-
const cursor =
|
|
7236
|
-
const u = await hub.profiles.usage(
|
|
7646
|
+
const cursor = optional7(args, "cursor");
|
|
7647
|
+
const u = await hub.profiles.usage(need7(args, "id"), { ...limit !== undefined ? { limit } : {}, ...cursor ? { cursor } : {}, signal });
|
|
7237
7648
|
const c = u.counts;
|
|
7238
7649
|
const plural = (n, word, many = `${word}s`) => `${n} ${n === 1 ? word : many}`;
|
|
7239
7650
|
const lines = [`${u.profileID} (latest v${u.latestVersion})`];
|
|
@@ -7272,16 +7683,16 @@ ${sanitizeText(r.skill.body)}`;
|
|
|
7272
7683
|
var needRevision = (args, key = "expectedRevision") => {
|
|
7273
7684
|
const value = optionalInt(args, key, 0, Number.MAX_SAFE_INTEGER);
|
|
7274
7685
|
if (value === undefined)
|
|
7275
|
-
throw new
|
|
7686
|
+
throw new ArgError3(`"${key}" is required: the revision you read`);
|
|
7276
7687
|
return value;
|
|
7277
7688
|
};
|
|
7278
7689
|
var idemOption = (args) => {
|
|
7279
|
-
const key =
|
|
7690
|
+
const key = optional7(args, "idempotencyKey");
|
|
7280
7691
|
return key !== undefined ? { idempotencyKey: key } : {};
|
|
7281
7692
|
};
|
|
7282
7693
|
var WORKSPACE = { type: "string", description: "The workspace's ID (ws_…)." };
|
|
7283
|
-
var
|
|
7284
|
-
var
|
|
7694
|
+
var REVISION4 = { type: "integer", minimum: 0, description: "The revision you read (a stale one answers conflict with the current revision)." };
|
|
7695
|
+
var IDEMPOTENCY3 = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
|
|
7285
7696
|
var MEMBER_ROLES = ["worker", "observer"];
|
|
7286
7697
|
var ASSIGNMENT_STATUSES = ["open", "in_progress", "blocked", "done", "cancelled"];
|
|
7287
7698
|
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 +7717,7 @@ var workspaceTools = [
|
|
|
7306
7717
|
required: ["workspace"],
|
|
7307
7718
|
readOnly: true,
|
|
7308
7719
|
run: async (hub, args, signal) => {
|
|
7309
|
-
const id =
|
|
7720
|
+
const id = need7(args, "workspace");
|
|
7310
7721
|
const [{ workspace, bindings }, { members }, graph] = await Promise.all([hub.workspaces.get(id, { signal }), hub.workspaces.members(id, { signal }), hub.workspaces.supervision(id, { signal })]);
|
|
7311
7722
|
const lines = [wsLine(workspace)];
|
|
7312
7723
|
if (workspace.masterMemberID)
|
|
@@ -7322,39 +7733,39 @@ var workspaceTools = [
|
|
|
7322
7733
|
{
|
|
7323
7734
|
name: "workspace_create",
|
|
7324
7735
|
description: `Create a coordination workspace. ${WS_SCOPES}`,
|
|
7325
|
-
properties: { title:
|
|
7736
|
+
properties: { title: STRING6, description: STRING6, tags: { type: "array", items: STRING6 }, idempotencyKey: IDEMPOTENCY3 },
|
|
7326
7737
|
required: ["title"],
|
|
7327
7738
|
readOnly: false,
|
|
7328
7739
|
run: async (hub, args, signal) => {
|
|
7329
|
-
const description =
|
|
7740
|
+
const description = optional7(args, "description");
|
|
7330
7741
|
const tags = optionalStrings(args, "tags");
|
|
7331
|
-
const { workspace } = await hub.workspaces.create({ title:
|
|
7742
|
+
const { workspace } = await hub.workspaces.create({ title: need7(args, "title"), ...description ? { description } : {}, ...tags ? { tags } : {} }, { ...idemOption(args), signal });
|
|
7332
7743
|
return `created ${wsLine(workspace)}`;
|
|
7333
7744
|
}
|
|
7334
7745
|
},
|
|
7335
7746
|
{
|
|
7336
7747
|
name: "workspace_update",
|
|
7337
7748
|
description: `Change a workspace's title, description or tags under its revision. ${WS_SCOPES}`,
|
|
7338
|
-
properties: { workspace: WORKSPACE, expectedRevision:
|
|
7749
|
+
properties: { workspace: WORKSPACE, expectedRevision: REVISION4, title: STRING6, description: STRING6, tags: { type: "array", items: STRING6 } },
|
|
7339
7750
|
required: ["workspace", "expectedRevision"],
|
|
7340
7751
|
readOnly: false,
|
|
7341
7752
|
run: async (hub, args, signal) => {
|
|
7342
|
-
const patch = definedFields({ title:
|
|
7753
|
+
const patch = definedFields({ title: optional7(args, "title"), description: optional7(args, "description"), tags: optionalStrings(args, "tags") });
|
|
7343
7754
|
if (!patch)
|
|
7344
|
-
throw new
|
|
7345
|
-
const { workspace } = await hub.workspaces.update(
|
|
7755
|
+
throw new ArgError3(`give "title", "description" or "tags"`);
|
|
7756
|
+
const { workspace } = await hub.workspaces.update(need7(args, "workspace"), { expectedRevision: needRevision(args), ...patch }, { signal });
|
|
7346
7757
|
return `updated ${wsLine(workspace)}`;
|
|
7347
7758
|
}
|
|
7348
7759
|
},
|
|
7349
7760
|
...["archive", "restore"].map((verb) => ({
|
|
7350
7761
|
name: `workspace_${verb}`,
|
|
7351
7762
|
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:
|
|
7763
|
+
properties: { workspace: WORKSPACE, expectedRevision: REVISION4 },
|
|
7353
7764
|
required: ["workspace", "expectedRevision"],
|
|
7354
7765
|
readOnly: false,
|
|
7355
7766
|
idempotent: true,
|
|
7356
7767
|
run: async (hub, args, signal) => {
|
|
7357
|
-
const id =
|
|
7768
|
+
const id = need7(args, "workspace");
|
|
7358
7769
|
if (verb === "archive") {
|
|
7359
7770
|
const r = await hub.workspaces.archive(id, needRevision(args), { signal });
|
|
7360
7771
|
return `archived ${wsLine(r.workspace)}; ${r.departed} member(s) departed, ${r.supervision.detached} supervision link(s) detached`;
|
|
@@ -7365,77 +7776,77 @@ var workspaceTools = [
|
|
|
7365
7776
|
{
|
|
7366
7777
|
name: "workspace_bind",
|
|
7367
7778
|
description: `Bind a workspace to a repository (absolute path) on one of the user's machines. ${WS_SCOPES}`,
|
|
7368
|
-
properties: { workspace: WORKSPACE, machineID:
|
|
7779
|
+
properties: { workspace: WORKSPACE, machineID: STRING6, repo: STRING6, label: STRING6, idempotencyKey: IDEMPOTENCY3 },
|
|
7369
7780
|
required: ["workspace", "machineID", "repo"],
|
|
7370
7781
|
readOnly: false,
|
|
7371
7782
|
idempotent: true,
|
|
7372
7783
|
run: async (hub, args, signal) => {
|
|
7373
|
-
const label =
|
|
7374
|
-
const r = await hub.workspaces.bind(
|
|
7784
|
+
const label = optional7(args, "label");
|
|
7785
|
+
const r = await hub.workspaces.bind(need7(args, "workspace"), { machineID: need7(args, "machineID"), repo: need7(args, "repo"), ...label ? { label } : {} }, { ...idemOption(args), signal });
|
|
7375
7786
|
return `${r.created ? "bound" : "already bound"} ${r.binding.id} ${r.binding.machineID}:${r.binding.repo} r${r.binding.revision}`;
|
|
7376
7787
|
}
|
|
7377
7788
|
},
|
|
7378
7789
|
{
|
|
7379
7790
|
name: "workspace_unbind",
|
|
7380
7791
|
description: `Remove a repository binding under the binding's revision. ${WS_SCOPES}`,
|
|
7381
|
-
properties: { workspace: WORKSPACE, bindingID:
|
|
7792
|
+
properties: { workspace: WORKSPACE, bindingID: STRING6, expectedRevision: REVISION4 },
|
|
7382
7793
|
required: ["workspace", "bindingID", "expectedRevision"],
|
|
7383
7794
|
readOnly: false,
|
|
7384
7795
|
run: async (hub, args, signal) => {
|
|
7385
|
-
await hub.workspaces.unbind(
|
|
7386
|
-
return `unbound ${
|
|
7796
|
+
await hub.workspaces.unbind(need7(args, "workspace"), need7(args, "bindingID"), needRevision(args), { signal });
|
|
7797
|
+
return `unbound ${need7(args, "bindingID")}`;
|
|
7387
7798
|
}
|
|
7388
7799
|
},
|
|
7389
7800
|
{
|
|
7390
7801
|
name: "workspace_join",
|
|
7391
7802
|
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:
|
|
7803
|
+
properties: { workspace: WORKSPACE, sessionID: STRING6, role: { type: "string", enum: [...MEMBER_ROLES] }, idempotencyKey: IDEMPOTENCY3 },
|
|
7393
7804
|
required: ["workspace", "sessionID", "role"],
|
|
7394
7805
|
readOnly: false,
|
|
7395
7806
|
idempotent: true,
|
|
7396
7807
|
run: async (hub, args, signal) => {
|
|
7397
7808
|
const role = optionalEnum(args, "role", MEMBER_ROLES);
|
|
7398
7809
|
if (!role)
|
|
7399
|
-
throw new
|
|
7400
|
-
const { member, created } = await hub.workspaces.join(
|
|
7810
|
+
throw new ArgError3(`"role" is one of ${MEMBER_ROLES.join(", ")}`);
|
|
7811
|
+
const { member, created } = await hub.workspaces.join(need7(args, "workspace"), { sessionID: need7(args, "sessionID"), role }, { ...idemOption(args), signal });
|
|
7401
7812
|
return `member ${member.id} (${member.sessionID}, ${member.role}, generation ${member.generation}, r${member.revision}) ${created ? "joined" : "was already a member"}`;
|
|
7402
7813
|
}
|
|
7403
7814
|
},
|
|
7404
7815
|
{
|
|
7405
7816
|
name: "workspace_leave",
|
|
7406
7817
|
description: `Remove a member under its revision; its supervision links are detached. ${WS_SCOPES}`,
|
|
7407
|
-
properties: { workspace: WORKSPACE, memberID:
|
|
7818
|
+
properties: { workspace: WORKSPACE, memberID: STRING6, expectedRevision: REVISION4 },
|
|
7408
7819
|
required: ["workspace", "memberID", "expectedRevision"],
|
|
7409
7820
|
readOnly: false,
|
|
7410
7821
|
idempotent: true,
|
|
7411
7822
|
run: async (hub, args, signal) => {
|
|
7412
|
-
const r = await hub.workspaces.leave(
|
|
7823
|
+
const r = await hub.workspaces.leave(need7(args, "workspace"), need7(args, "memberID"), needRevision(args), { signal });
|
|
7413
7824
|
return `member ${r.member.id} left (${r.member.leftReason ?? "removed"}); ${r.supervision.detached} supervision link(s) detached`;
|
|
7414
7825
|
}
|
|
7415
7826
|
},
|
|
7416
7827
|
{
|
|
7417
7828
|
name: "workspace_assign",
|
|
7418
7829
|
description: `Give a current member an assignment (title and optional outcome criteria; instructions travel as messages). ${WS_SCOPES}`,
|
|
7419
|
-
properties: { workspace: WORKSPACE, memberID:
|
|
7830
|
+
properties: { workspace: WORKSPACE, memberID: STRING6, title: STRING6, criteria: { type: "array", items: STRING6 }, ref: STRING6, idempotencyKey: IDEMPOTENCY3 },
|
|
7420
7831
|
required: ["workspace", "memberID", "title"],
|
|
7421
7832
|
readOnly: false,
|
|
7422
7833
|
run: async (hub, args, signal) => {
|
|
7423
7834
|
const criteria = optionalStrings(args, "criteria");
|
|
7424
|
-
const ref =
|
|
7425
|
-
const { assignment } = await hub.workspaces.assign(
|
|
7835
|
+
const ref = optional7(args, "ref");
|
|
7836
|
+
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
7837
|
return `assignment ${assignment.id} → ${assignment.memberID}: ${assignment.status}, r${assignment.revision}`;
|
|
7427
7838
|
}
|
|
7428
7839
|
},
|
|
7429
7840
|
{
|
|
7430
7841
|
name: "workspace_assignments",
|
|
7431
7842
|
description: `A workspace's assignments (id, member, status, revision, title), optionally one member's or one status. ${WS_SCOPES}`,
|
|
7432
|
-
properties: { workspace: WORKSPACE, memberID:
|
|
7843
|
+
properties: { workspace: WORKSPACE, memberID: STRING6, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] } },
|
|
7433
7844
|
required: ["workspace"],
|
|
7434
7845
|
readOnly: true,
|
|
7435
7846
|
run: async (hub, args, signal) => {
|
|
7436
|
-
const memberID =
|
|
7847
|
+
const memberID = optional7(args, "memberID");
|
|
7437
7848
|
const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
|
|
7438
|
-
const r = await hub.workspaces.assignments(
|
|
7849
|
+
const r = await hub.workspaces.assignments(need7(args, "workspace"), { ...memberID ? { memberID } : {}, ...status ? { status } : {}, limit: 200, signal });
|
|
7439
7850
|
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
7851
|
`) : "no assignments";
|
|
7441
7852
|
}
|
|
@@ -7443,16 +7854,16 @@ var workspaceTools = [
|
|
|
7443
7854
|
{
|
|
7444
7855
|
name: "workspace_assignment_update",
|
|
7445
7856
|
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:
|
|
7857
|
+
properties: { workspace: WORKSPACE, assignmentID: STRING6, expectedRevision: REVISION4, status: { type: "string", enum: [...ASSIGNMENT_STATUSES] }, memberID: STRING6 },
|
|
7447
7858
|
required: ["workspace", "assignmentID", "expectedRevision"],
|
|
7448
7859
|
readOnly: false,
|
|
7449
7860
|
run: async (hub, args, signal) => {
|
|
7450
7861
|
const status = optionalEnum(args, "status", ASSIGNMENT_STATUSES);
|
|
7451
|
-
const memberID =
|
|
7862
|
+
const memberID = optional7(args, "memberID");
|
|
7452
7863
|
if (status === undefined === (memberID === undefined))
|
|
7453
|
-
throw new
|
|
7454
|
-
const id =
|
|
7455
|
-
const assignmentID =
|
|
7864
|
+
throw new ArgError3(`give exactly one of "status" or "memberID"`);
|
|
7865
|
+
const id = need7(args, "workspace");
|
|
7866
|
+
const assignmentID = need7(args, "assignmentID");
|
|
7456
7867
|
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
7868
|
return `assignment ${assignment.id} → ${assignment.memberID}: ${assignment.status}, r${assignment.revision}`;
|
|
7458
7869
|
}
|
|
@@ -7460,16 +7871,16 @@ var workspaceTools = [
|
|
|
7460
7871
|
{
|
|
7461
7872
|
name: "workspace_supervise",
|
|
7462
7873
|
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:
|
|
7874
|
+
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
7875
|
required: ["workspace", "memberID", "supervisor", "expectedRevision"],
|
|
7465
7876
|
readOnly: false,
|
|
7466
7877
|
run: async (hub, args, signal) => {
|
|
7467
|
-
const supervisor = args.supervisor === null ? null :
|
|
7878
|
+
const supervisor = args.supervisor === null ? null : need7(args, "supervisor");
|
|
7468
7879
|
const order = args.order;
|
|
7469
7880
|
if (order !== undefined && (typeof order !== "number" || !Number.isFinite(order)))
|
|
7470
|
-
throw new
|
|
7471
|
-
const r = await hub.workspaces.supervise(
|
|
7472
|
-
return `${r.change}: ${
|
|
7881
|
+
throw new ArgError3(`"order" must be a number`);
|
|
7882
|
+
const r = await hub.workspaces.supervise(need7(args, "workspace"), { memberID: need7(args, "memberID"), supervisorMemberID: supervisor, ...order !== undefined ? { order } : {}, expectedRevision: needRevision(args) }, { signal });
|
|
7883
|
+
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
7884
|
}
|
|
7474
7885
|
},
|
|
7475
7886
|
{
|
|
@@ -7479,7 +7890,7 @@ var workspaceTools = [
|
|
|
7479
7890
|
required: ["workspace"],
|
|
7480
7891
|
readOnly: true,
|
|
7481
7892
|
run: async (hub, args, signal) => {
|
|
7482
|
-
const v = await hub.workspaces.master(
|
|
7893
|
+
const v = await hub.workspaces.master(need7(args, "workspace"), { signal });
|
|
7483
7894
|
const line = (m) => `${m.member.id} ${m.member.sessionID} generation ${m.member.generation} ${m.presence}${m.member.leftReason ? ` (${m.member.leftReason})` : ""}`;
|
|
7484
7895
|
return [
|
|
7485
7896
|
`workspace ${v.workspaceID} r${v.revision}`,
|
|
@@ -7493,17 +7904,17 @@ var workspaceTools = [
|
|
|
7493
7904
|
{
|
|
7494
7905
|
name: "workspace_master_replace",
|
|
7495
7906
|
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:
|
|
7907
|
+
properties: { workspace: WORKSPACE, sessionID: STRING6, launchID: STRING6, expectedRevision: REVISION4, idempotencyKey: IDEMPOTENCY3 },
|
|
7497
7908
|
required: ["workspace", "expectedRevision"],
|
|
7498
7909
|
readOnly: false,
|
|
7499
7910
|
idempotent: true,
|
|
7500
7911
|
run: async (hub, args, signal) => {
|
|
7501
|
-
const sessionID =
|
|
7502
|
-
const launchID =
|
|
7912
|
+
const sessionID = optional7(args, "sessionID");
|
|
7913
|
+
const launchID = optional7(args, "launchID");
|
|
7503
7914
|
if (sessionID === undefined === (launchID === undefined))
|
|
7504
|
-
throw new
|
|
7915
|
+
throw new ArgError3(`give exactly one of "sessionID" or "launchID"`);
|
|
7505
7916
|
const target = sessionID !== undefined ? { sessionID } : { launchID };
|
|
7506
|
-
const r = await hub.workspaces.replaceMaster(
|
|
7917
|
+
const r = await hub.workspaces.replaceMaster(need7(args, "workspace"), { ...target, expectedRevision: needRevision(args) }, { ...idemOption(args), signal });
|
|
7507
7918
|
if (!r.changed)
|
|
7508
7919
|
return `${r.master.sessionID} already is the master (${r.master.id}); nothing changed`;
|
|
7509
7920
|
return [
|
|
@@ -7521,7 +7932,7 @@ var workspaceTools = [
|
|
|
7521
7932
|
required: ["workspace"],
|
|
7522
7933
|
readOnly: true,
|
|
7523
7934
|
run: async (hub, args, signal) => {
|
|
7524
|
-
const b = await hub.workspaces.board(
|
|
7935
|
+
const b = await hub.workspaces.board(need7(args, "workspace"), { signal });
|
|
7525
7936
|
const more = (truncated) => truncated ? " (more not shown)" : "";
|
|
7526
7937
|
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
7938
|
const lines = [wsLine(b.workspace), ...b.workspace.masterMemberID ? [`master: ${b.workspace.masterMemberID}`] : []];
|
|
@@ -7546,6 +7957,7 @@ var TOOLS = [
|
|
|
7546
7957
|
eventsTool,
|
|
7547
7958
|
...webhookTools,
|
|
7548
7959
|
...profileTools,
|
|
7960
|
+
...profileLifecycleTools,
|
|
7549
7961
|
...workspaceTools,
|
|
7550
7962
|
...artifactTools,
|
|
7551
7963
|
...artifactJobTools,
|
|
@@ -7570,14 +7982,14 @@ var TOOLS = [
|
|
|
7570
7982
|
name: "sessions",
|
|
7571
7983
|
description: "The sessions as a flat list, optionally filtered.",
|
|
7572
7984
|
properties: {
|
|
7573
|
-
project: { ...
|
|
7574
|
-
state: { ...
|
|
7575
|
-
machine: { ...
|
|
7576
|
-
label: { type: "array", items:
|
|
7985
|
+
project: { ...STRING6, description: "Only this project." },
|
|
7986
|
+
state: { ...STRING6, description: "Only these states (comma-separated, e.g. busy,blocked)." },
|
|
7987
|
+
machine: { ...STRING6, description: "Only this machine." },
|
|
7988
|
+
label: { type: "array", items: STRING6, description: "key=value labels that must all match." }
|
|
7577
7989
|
},
|
|
7578
7990
|
readOnly: true,
|
|
7579
7991
|
run: async (hub, args, signal) => {
|
|
7580
|
-
const body = await hub.sessions({ project:
|
|
7992
|
+
const body = await hub.sessions({ project: optional7(args, "project"), state: optional7(args, "state"), machine: optional7(args, "machine"), label: optionalStrings(args, "label"), signal });
|
|
7581
7993
|
return sessionsText(body.sessions ?? []);
|
|
7582
7994
|
}
|
|
7583
7995
|
},
|
|
@@ -7587,7 +7999,7 @@ var TOOLS = [
|
|
|
7587
7999
|
properties: { addr: ADDR2 },
|
|
7588
8000
|
required: ["addr"],
|
|
7589
8001
|
readOnly: true,
|
|
7590
|
-
run: async (hub, args, signal) => showText(await hub.show(
|
|
8002
|
+
run: async (hub, args, signal) => showText(await hub.show(need7(args, "addr"), { signal }))
|
|
7591
8003
|
},
|
|
7592
8004
|
{
|
|
7593
8005
|
name: "log",
|
|
@@ -7596,7 +8008,7 @@ var TOOLS = [
|
|
|
7596
8008
|
required: ["addr"],
|
|
7597
8009
|
readOnly: true,
|
|
7598
8010
|
run: async (hub, args, signal) => {
|
|
7599
|
-
const addr =
|
|
8011
|
+
const addr = need7(args, "addr");
|
|
7600
8012
|
const n = optionalInt(args, "n", 1, 200);
|
|
7601
8013
|
return logText((await hub.log(addr, { ...n !== undefined ? { n } : {}, signal })).rows ?? []);
|
|
7602
8014
|
}
|
|
@@ -7605,25 +8017,25 @@ var TOOLS = [
|
|
|
7605
8017
|
name: "who",
|
|
7606
8018
|
description: "Named and role-holding sessions; a team member sees its teams, grouped by team with its reach to each.",
|
|
7607
8019
|
properties: {
|
|
7608
|
-
project: { ...
|
|
7609
|
-
role: { ...
|
|
7610
|
-
team: { ...
|
|
8020
|
+
project: { ...STRING6, description: "Only this project." },
|
|
8021
|
+
role: { ...STRING6, description: "Only holders of this role (reviewer, @@reviewer or reviewer#team)." },
|
|
8022
|
+
team: { ...STRING6, description: "Only this team (name or ID)." }
|
|
7611
8023
|
},
|
|
7612
8024
|
readOnly: true,
|
|
7613
8025
|
run: async (hub, args, signal) => {
|
|
7614
|
-
const body = await hub.who({ project:
|
|
8026
|
+
const body = await hub.who({ project: optional7(args, "project"), role: optional7(args, "role"), team: optional7(args, "team"), signal });
|
|
7615
8027
|
return whoText(body.sessions ?? [], body);
|
|
7616
8028
|
}
|
|
7617
8029
|
},
|
|
7618
8030
|
{
|
|
7619
8031
|
name: "msg",
|
|
7620
8032
|
description: "Tell a session something (fire and forget). Sent as this server's credential, not as an OpenCode session.",
|
|
7621
|
-
properties: { to: TO, text: { ...
|
|
8033
|
+
properties: { to: TO, text: { ...STRING6, description: "The message." }, ...SEND },
|
|
7622
8034
|
required: ["to", "text"],
|
|
7623
8035
|
readOnly: false,
|
|
7624
8036
|
run: async (hub, args, signal) => {
|
|
7625
|
-
const to =
|
|
7626
|
-
const text =
|
|
8037
|
+
const to = need7(args, "to");
|
|
8038
|
+
const text = need7(args, "text");
|
|
7627
8039
|
const options = { ...sendOptions(args), signal };
|
|
7628
8040
|
if (optionalBool(args, "all"))
|
|
7629
8041
|
return sentAllText(await hub.msg(to, text, { ...options, all: true }));
|
|
@@ -7635,7 +8047,7 @@ var TOOLS = [
|
|
|
7635
8047
|
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
8048
|
properties: {
|
|
7637
8049
|
to: TO,
|
|
7638
|
-
text: { ...
|
|
8050
|
+
text: { ...STRING6, description: "The question." },
|
|
7639
8051
|
wait: { type: "number", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait for the answer (0–${WAIT_POLL_MAX_S}).` },
|
|
7640
8052
|
expires: { type: ["number", "string"], description: "When the ask expires: seconds, or 30s, 5m, 2h, 1d (1 minute to 7 days; default 24 h)." },
|
|
7641
8053
|
...SEND
|
|
@@ -7643,14 +8055,14 @@ var TOOLS = [
|
|
|
7643
8055
|
required: ["to", "text"],
|
|
7644
8056
|
readOnly: false,
|
|
7645
8057
|
run: async (hub, args, signal) => {
|
|
7646
|
-
const to =
|
|
7647
|
-
const text =
|
|
8058
|
+
const to = need7(args, "to");
|
|
8059
|
+
const text = need7(args, "text");
|
|
7648
8060
|
const wait = optionalWait(args);
|
|
7649
8061
|
const expires = optionalExpires(args);
|
|
7650
8062
|
const options = { ...sendOptions(args), ...expires !== undefined ? { expires } : {}, signal };
|
|
7651
8063
|
if (optionalBool(args, "all")) {
|
|
7652
8064
|
if (wait !== undefined)
|
|
7653
|
-
throw new
|
|
8065
|
+
throw new ArgError3(`"wait" cannot be combined with "all"`);
|
|
7654
8066
|
return sentAllText(await hub.ask(to, text, { ...options, all: true }));
|
|
7655
8067
|
}
|
|
7656
8068
|
const asked = await hub.ask(to, text, { ...options, ...wait !== undefined ? { wait } : {} });
|
|
@@ -7662,12 +8074,12 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
7662
8074
|
{
|
|
7663
8075
|
name: "reply",
|
|
7664
8076
|
description: "Reply to a message you received; it goes to the message's real sender.",
|
|
7665
|
-
properties: { id: MSG_ID, text: { ...
|
|
8077
|
+
properties: { id: MSG_ID, text: { ...STRING6, description: "The reply." }, wake: SEND.wake },
|
|
7666
8078
|
required: ["id", "text"],
|
|
7667
8079
|
readOnly: false,
|
|
7668
8080
|
run: async (hub, args, signal) => {
|
|
7669
|
-
const id =
|
|
7670
|
-
const text =
|
|
8081
|
+
const id = need7(args, "id");
|
|
8082
|
+
const text = need7(args, "text");
|
|
7671
8083
|
const wake = optionalBool(args, "wake");
|
|
7672
8084
|
return repliedText(await hub.reply(id, text, { ...wake !== undefined ? { wake } : {}, signal }));
|
|
7673
8085
|
}
|
|
@@ -7675,12 +8087,12 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
7675
8087
|
{
|
|
7676
8088
|
name: "decline",
|
|
7677
8089
|
description: "Decline an ask you received, with a reason.",
|
|
7678
|
-
properties: { id: MSG_ID, why: { ...
|
|
8090
|
+
properties: { id: MSG_ID, why: { ...STRING6, description: "The reason." } },
|
|
7679
8091
|
required: ["id", "why"],
|
|
7680
8092
|
readOnly: false,
|
|
7681
8093
|
run: async (hub, args, signal) => {
|
|
7682
|
-
const id =
|
|
7683
|
-
return declinedText(await hub.decline(id,
|
|
8094
|
+
const id = need7(args, "id");
|
|
8095
|
+
return declinedText(await hub.decline(id, need7(args, "why"), { signal }));
|
|
7684
8096
|
}
|
|
7685
8097
|
},
|
|
7686
8098
|
{
|
|
@@ -7700,7 +8112,7 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
7700
8112
|
required: ["id"],
|
|
7701
8113
|
readOnly: true,
|
|
7702
8114
|
run: async (hub, args, signal) => {
|
|
7703
|
-
const id =
|
|
8115
|
+
const id = need7(args, "id");
|
|
7704
8116
|
const wait = optionalWait(args);
|
|
7705
8117
|
return messageText(await hub.message(id, { ...wait !== undefined ? { wait } : {}, signal }));
|
|
7706
8118
|
}
|
|
@@ -7713,7 +8125,7 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
7713
8125
|
readOnly: false,
|
|
7714
8126
|
organizes: true,
|
|
7715
8127
|
idempotent: true,
|
|
7716
|
-
run: async (hub, args, signal) => mutedText((await hub[verb](
|
|
8128
|
+
run: async (hub, args, signal) => mutedText((await hub[verb](need7(args, "addr"), { signal })).muted)
|
|
7717
8129
|
})),
|
|
7718
8130
|
{
|
|
7719
8131
|
name: "decisions",
|
|
@@ -7721,9 +8133,9 @@ ${openedText(asked.decision)}` : outcome;
|
|
|
7721
8133
|
properties: { state: { type: "string", enum: [...DECISION_STATES], description: "Only Decisions in this state." } },
|
|
7722
8134
|
readOnly: true,
|
|
7723
8135
|
run: async (hub, args, signal) => {
|
|
7724
|
-
const state =
|
|
8136
|
+
const state = optional7(args, "state");
|
|
7725
8137
|
if (state !== undefined && !DECISION_STATES.includes(state))
|
|
7726
|
-
throw new
|
|
8138
|
+
throw new ArgError3(`"state" is one of ${DECISION_STATES.join(", ")}`);
|
|
7727
8139
|
const { decisions } = await hub.decisions({ ...state ? { state } : {}, signal });
|
|
7728
8140
|
return decisions.length ? `${DECISIONS_NOTE}
|
|
7729
8141
|
${decisionsText(decisions)}` : decisionsText(decisions);
|
|
@@ -7736,7 +8148,7 @@ ${decisionsText(decisions)}` : decisionsText(decisions);
|
|
|
7736
8148
|
required: ["id"],
|
|
7737
8149
|
readOnly: true,
|
|
7738
8150
|
run: async (hub, args, signal) => `${DECISIONS_NOTE}
|
|
7739
|
-
${decisionText((await hub.decision(
|
|
8151
|
+
${decisionText((await hub.decision(need7(args, "id"), { signal })).decision)}`
|
|
7740
8152
|
},
|
|
7741
8153
|
{
|
|
7742
8154
|
name: "approve",
|
|
@@ -7745,8 +8157,8 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
|
|
|
7745
8157
|
required: ["addr", "requestID"],
|
|
7746
8158
|
readOnly: false,
|
|
7747
8159
|
run: async (hub, args, signal) => {
|
|
7748
|
-
const addr =
|
|
7749
|
-
const requestID =
|
|
8160
|
+
const addr = need7(args, "addr");
|
|
8161
|
+
const requestID = need7(args, "requestID");
|
|
7750
8162
|
const always = optionalBool(args, "always");
|
|
7751
8163
|
return approvedText(await hub.approve(addr, requestID, { ...always ? { always } : {}, ...whyOption(args), signal }));
|
|
7752
8164
|
}
|
|
@@ -7758,8 +8170,8 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
|
|
|
7758
8170
|
required: ["addr", "requestID"],
|
|
7759
8171
|
readOnly: false,
|
|
7760
8172
|
run: async (hub, args, signal) => {
|
|
7761
|
-
const addr =
|
|
7762
|
-
const requestID =
|
|
8173
|
+
const addr = need7(args, "addr");
|
|
8174
|
+
const requestID = need7(args, "requestID");
|
|
7763
8175
|
return deniedText(await hub.deny(addr, requestID, { ...whyOption(args), signal }));
|
|
7764
8176
|
}
|
|
7765
8177
|
},
|
|
@@ -7776,8 +8188,8 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
|
|
|
7776
8188
|
required: ["addr", "formID"],
|
|
7777
8189
|
readOnly: false,
|
|
7778
8190
|
run: async (hub, args, signal) => {
|
|
7779
|
-
const addr =
|
|
7780
|
-
const formID =
|
|
8191
|
+
const addr = need7(args, "addr");
|
|
8192
|
+
const formID = need7(args, "formID");
|
|
7781
8193
|
const given = formAnswer(args);
|
|
7782
8194
|
const options = { ...whyOption(args), signal };
|
|
7783
8195
|
return answeredText(given === "cancel" ? await hub.cancelForm(addr, formID, options) : await hub.answer(addr, formID, given, options), given === "cancel");
|
|
@@ -7788,15 +8200,15 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
|
|
|
7788
8200
|
description: `Prompt a session (an agent's prompt carries the hub's provenance). ${NEEDS_USER}`,
|
|
7789
8201
|
properties: {
|
|
7790
8202
|
addr: ADDR2,
|
|
7791
|
-
text: { ...
|
|
8203
|
+
text: { ...STRING6, description: "The prompt." },
|
|
7792
8204
|
delivery: { type: "string", enum: [...DELIVERIES], description: "How it enters a busy session (the spoke picks when unset)." },
|
|
7793
8205
|
why: WHY
|
|
7794
8206
|
},
|
|
7795
8207
|
required: ["addr", "text"],
|
|
7796
8208
|
readOnly: false,
|
|
7797
8209
|
run: async (hub, args, signal) => {
|
|
7798
|
-
const addr =
|
|
7799
|
-
const text =
|
|
8210
|
+
const addr = need7(args, "addr");
|
|
8211
|
+
const text = need7(args, "text");
|
|
7800
8212
|
const delivery = optionalDelivery(args);
|
|
7801
8213
|
return promptedText(await hub.prompt(addr, text, { ...delivery ? { delivery } : {}, ...whyOption(args), signal }));
|
|
7802
8214
|
}
|
|
@@ -7807,32 +8219,32 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
|
|
|
7807
8219
|
properties: { addr: ADDR2, why: WHY },
|
|
7808
8220
|
required: ["addr"],
|
|
7809
8221
|
readOnly: false,
|
|
7810
|
-
run: async (hub, args, signal) => stoppedText(await hub.stop(
|
|
8222
|
+
run: async (hub, args, signal) => stoppedText(await hub.stop(need7(args, "addr"), { ...whyOption(args), signal }))
|
|
7811
8223
|
},
|
|
7812
8224
|
{
|
|
7813
8225
|
name: "launch",
|
|
7814
8226
|
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
8227
|
properties: {
|
|
7816
|
-
repo: { ...
|
|
8228
|
+
repo: { ...STRING6, description: "Absolute path of the repository on the node (or where a clone goes)." },
|
|
7817
8229
|
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: { ...
|
|
8230
|
+
branch: { ...STRING6, description: "The branch to create or check out." },
|
|
8231
|
+
base: { ...STRING6, description: "What a new branch starts from." },
|
|
8232
|
+
remote: { ...STRING6, description: "An https or ssh git remote (for clone)." },
|
|
8233
|
+
key: { ...STRING6, description: "Idempotency key: retrying with the same key and arguments answers the same launch." },
|
|
8234
|
+
name: { ...STRING6, description: "The session's @name." },
|
|
8235
|
+
roles: { type: "array", items: STRING6, description: "Roles the session claims (e.g. reviewer.hub)." },
|
|
8236
|
+
harness: { ...STRING6, description: "The harness (default opencode)." },
|
|
8237
|
+
agent: { ...STRING6, description: "The harness agent (e.g. build)." },
|
|
8238
|
+
model: { ...STRING6, description: "provider/model." },
|
|
7827
8239
|
runtime: { type: "string", enum: [...RUNTIME_KINDS], description: "host (default) or openshell (a sandbox)." },
|
|
7828
|
-
profile: { ...
|
|
7829
|
-
machine: { ...
|
|
8240
|
+
profile: { ...STRING6, description: "The runtime's sandbox profile (needs runtime)." },
|
|
8241
|
+
machine: { ...STRING6, description: "The machine (name or ID) to run on." },
|
|
7830
8242
|
nodeLabels: { ...LABELS, description: 'Node labels to place on (e.g. {"os": "darwin"}).' },
|
|
7831
8243
|
prefer: { type: "boolean", description: "machine and nodeLabels are preferences, not requirements." },
|
|
7832
8244
|
labels: { ...LABELS, description: "Free-form labels for finding the launch and its session later (never interpreted by the hub)." },
|
|
7833
|
-
prompt: { ...
|
|
8245
|
+
prompt: { ...STRING6, description: "The session's first prompt." },
|
|
7834
8246
|
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: { ...
|
|
8247
|
+
timeout: { ...STRING6, description: "Longest run, e.g. 4h or 90m." },
|
|
7836
8248
|
cleanup: { type: "string", enum: [...CLEANUP_POLICIES], description: "keep (default) or remove the workspace when it ends." }
|
|
7837
8249
|
},
|
|
7838
8250
|
required: ["repo"],
|
|
@@ -7847,12 +8259,12 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
|
|
|
7847
8259
|
name: "launches",
|
|
7848
8260
|
description: "Your launches, oldest first (an app or session sees the ones it started).",
|
|
7849
8261
|
properties: {
|
|
7850
|
-
state: { ...
|
|
7851
|
-
label: { type: "array", items:
|
|
8262
|
+
state: { ...STRING6, description: "Only these states (comma-separated: queued, placing, provisioning, running, ended)." },
|
|
8263
|
+
label: { type: "array", items: STRING6, description: "key=value launch labels that must all match." }
|
|
7852
8264
|
},
|
|
7853
8265
|
readOnly: true,
|
|
7854
8266
|
run: async (hub, args, signal) => {
|
|
7855
|
-
const state =
|
|
8267
|
+
const state = optional7(args, "state")?.split(",").filter(Boolean);
|
|
7856
8268
|
const label = optionalStrings(args, "label");
|
|
7857
8269
|
return launchesText((await hub.launches.list({ ...state?.length ? { state } : {}, ...label ? { label } : {}, signal })).launches ?? []);
|
|
7858
8270
|
}
|
|
@@ -7863,7 +8275,7 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
|
|
|
7863
8275
|
properties: { id: LAUNCH_ID },
|
|
7864
8276
|
required: ["id"],
|
|
7865
8277
|
readOnly: true,
|
|
7866
|
-
run: async (hub, args, signal) => launchText((await hub.launches.get(
|
|
8278
|
+
run: async (hub, args, signal) => launchText((await hub.launches.get(need7(args, "id"), { signal })).launch)
|
|
7867
8279
|
},
|
|
7868
8280
|
{
|
|
7869
8281
|
name: "launch_stop",
|
|
@@ -7871,7 +8283,7 @@ ${decisionText((await hub.decision(need6(args, "id"), { signal })).decision)}`
|
|
|
7871
8283
|
properties: { id: LAUNCH_ID },
|
|
7872
8284
|
required: ["id"],
|
|
7873
8285
|
readOnly: false,
|
|
7874
|
-
run: async (hub, args, signal) => stoppedLaunchText((await hub.launches.stop(
|
|
8286
|
+
run: async (hub, args, signal) => stoppedLaunchText((await hub.launches.stop(need7(args, "id"), { signal })).launch)
|
|
7875
8287
|
}
|
|
7876
8288
|
];
|
|
7877
8289
|
var toolList = () => TOOLS.map((t) => ({
|
|
@@ -7938,7 +8350,7 @@ function createMcpServer(options) {
|
|
|
7938
8350
|
} catch (cause) {
|
|
7939
8351
|
if (controller.signal.aborted)
|
|
7940
8352
|
return;
|
|
7941
|
-
const text = cause instanceof
|
|
8353
|
+
const text = cause instanceof ToolArgError ? `invalid: ${cause.message}` : cause instanceof CliError ? errorText(cause) : `${tool.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
7942
8354
|
return ok2(id, toolResult(clean(text, token), true));
|
|
7943
8355
|
} finally {
|
|
7944
8356
|
if (inFlight.get(key(id)) === controller)
|
|
@@ -8375,7 +8787,7 @@ import { arch as osArch, platform as osPlatform } from "node:os";
|
|
|
8375
8787
|
// packages/node/package.json
|
|
8376
8788
|
var package_default2 = {
|
|
8377
8789
|
name: "@huynle/hub-node",
|
|
8378
|
-
version: "0.8.
|
|
8790
|
+
version: "0.8.2",
|
|
8379
8791
|
private: true,
|
|
8380
8792
|
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
8793
|
license: "MIT",
|
|
@@ -9467,7 +9879,7 @@ import path13 from "node:path";
|
|
|
9467
9879
|
// packages/runtime-openshell/package.json
|
|
9468
9880
|
var package_default3 = {
|
|
9469
9881
|
name: "@huynle/hub-runtime-openshell",
|
|
9470
|
-
version: "0.8.
|
|
9882
|
+
version: "0.8.2",
|
|
9471
9883
|
private: true,
|
|
9472
9884
|
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
9885
|
license: "MIT",
|
|
@@ -10031,9 +10443,10 @@ var openCodeServiceFile = (env, home) => path14.join(env.XDG_STATE_HOME || path1
|
|
|
10031
10443
|
function startOpenCodeWarmers(config, service, log, signal) {
|
|
10032
10444
|
const roots = () => config.workspaceRoots;
|
|
10033
10445
|
const directories = config.opencode?.directories;
|
|
10034
|
-
|
|
10446
|
+
const timeout = config.opencode?.timeoutSeconds !== undefined ? { timeoutMs: config.opencode.timeoutSeconds * 1000 } : {};
|
|
10447
|
+
new OpenCodeWarmer({ discover: service, log, roots, ...timeout, ...directories ? { directories } : {} }).start(signal);
|
|
10035
10448
|
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);
|
|
10449
|
+
new OpenCodeWarmer({ discover: serverEndpoint(server), log, name: "the OpenCode server", roots, ...timeout, ...server.directories ? { directories: server.directories } : {} }).start(signal);
|
|
10037
10450
|
}
|
|
10038
10451
|
}
|
|
10039
10452
|
async function setupOpenShell(config, credentials, options, env, log) {
|
|
@@ -10414,8 +10827,8 @@ var EMBEDDED_TEXT = Object.freeze({
|
|
|
10414
10827
|
// packages/cli/src/bootstrap/adapters/node-services/render.ts
|
|
10415
10828
|
var SERVICE_ENVIRONMENT = Object.freeze({
|
|
10416
10829
|
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"]
|
|
10830
|
+
"claude-code": ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR", "XDG_STATE_HOME", "HUB_CLAUDE_SOCKET", "HUB_CLAUDE_BIN"],
|
|
10831
|
+
"codex-spoke": ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR", "XDG_STATE_HOME", "CODEX_HOME", "HUB_CODEX_BIN", "HUB_CODEX_DAEMON_SOCKET"]
|
|
10419
10832
|
});
|
|
10420
10833
|
var UNIT_ROOT = Object.freeze({ launchd: "launch-agents", "systemd-user": "systemd-user" });
|
|
10421
10834
|
var assetIDFor = (service, manager) => `service/${manager}/${service}`;
|
|
@@ -11008,6 +11421,15 @@ function wsURL2(server) {
|
|
|
11008
11421
|
var defaultSocket2 = (url, headers) => new WebSocket(url, { headers });
|
|
11009
11422
|
var message2 = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
11010
11423
|
|
|
11424
|
+
class CodedError extends Error {
|
|
11425
|
+
code;
|
|
11426
|
+
constructor(message, code) {
|
|
11427
|
+
super(message);
|
|
11428
|
+
this.code = code;
|
|
11429
|
+
this.name = "CodedError";
|
|
11430
|
+
}
|
|
11431
|
+
}
|
|
11432
|
+
|
|
11011
11433
|
class SpokeConnection {
|
|
11012
11434
|
options;
|
|
11013
11435
|
state = "off";
|
|
@@ -11177,7 +11599,7 @@ class SpokeConnection {
|
|
|
11177
11599
|
try {
|
|
11178
11600
|
reply = { t: "res", id, ok: true, result: await this.options.handle(req) };
|
|
11179
11601
|
} catch (cause) {
|
|
11180
|
-
reply = { t: "res", id, ok: false, error: message2(cause) };
|
|
11602
|
+
reply = { t: "res", id, ok: false, error: message2(cause), ...cause instanceof CodedError ? { code: cause.code } : {} };
|
|
11181
11603
|
}
|
|
11182
11604
|
this.send(reply);
|
|
11183
11605
|
}
|
|
@@ -11358,7 +11780,7 @@ function tail(s) {
|
|
|
11358
11780
|
const asks = s.openAsks ? `, ${s.openAsks} open ask${s.openAsks === 1 ? "" : "s"} from you` : "";
|
|
11359
11781
|
return `${clean2(s.state) || "unknown"}${asks}`;
|
|
11360
11782
|
}
|
|
11361
|
-
function
|
|
11783
|
+
function one3(s) {
|
|
11362
11784
|
const more = details(s);
|
|
11363
11785
|
if (s.self)
|
|
11364
11786
|
return `this session (${[s.sessionID, ...more].join(" · ")}), ${tail(s)}`;
|
|
@@ -11371,7 +11793,7 @@ function member(s) {
|
|
|
11371
11793
|
}
|
|
11372
11794
|
function renderReferences(refs) {
|
|
11373
11795
|
const lines = refs.map((r) => {
|
|
11374
|
-
const body = r.sessions.length === 1 ?
|
|
11796
|
+
const body = r.sessions.length === 1 ? one3(r.sessions[0]) : `${r.sessions.length} sessions: ${r.sessions.map(member).join("; ")}`;
|
|
11375
11797
|
return `- ${r.address} = ${body}.`;
|
|
11376
11798
|
});
|
|
11377
11799
|
const single = refs.length === 1 && refs[0].sessions.length === 1;
|
|
@@ -11841,7 +12263,7 @@ import { homedir as homedir5 } from "node:os";
|
|
|
11841
12263
|
import path17 from "node:path";
|
|
11842
12264
|
var SOCKET_PATH_MAX = 103;
|
|
11843
12265
|
function claudeStateDir(env = process.env, home = homedir5()) {
|
|
11844
|
-
return path17.join(
|
|
12266
|
+
return path17.join(resolveHubStateDir(env, home).dir, "claude-code");
|
|
11845
12267
|
}
|
|
11846
12268
|
function socketPath(env = process.env, home = homedir5()) {
|
|
11847
12269
|
return env.HUB_CLAUDE_SOCKET || path17.join(claudeStateDir(env, home), "daemon.sock");
|
|
@@ -13141,7 +13563,7 @@ import path22 from "node:path";
|
|
|
13141
13563
|
// packages/claude-code/package.json
|
|
13142
13564
|
var package_default4 = {
|
|
13143
13565
|
name: "@huynle/hub-claude-code",
|
|
13144
|
-
version: "0.8.
|
|
13566
|
+
version: "0.8.2",
|
|
13145
13567
|
private: true,
|
|
13146
13568
|
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
13569
|
license: "MIT",
|
|
@@ -13430,7 +13852,7 @@ import path25 from "node:path";
|
|
|
13430
13852
|
// packages/preset/package.json
|
|
13431
13853
|
var package_default5 = {
|
|
13432
13854
|
name: "@huynle/hub-preset",
|
|
13433
|
-
version: "0.8.
|
|
13855
|
+
version: "0.8.2",
|
|
13434
13856
|
private: true,
|
|
13435
13857
|
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
13858
|
license: "MIT",
|
|
@@ -13955,7 +14377,7 @@ import { openSync as openSync5, closeSync as closeSync5, readFileSync as readFil
|
|
|
13955
14377
|
// packages/codex/package.json
|
|
13956
14378
|
var package_default6 = {
|
|
13957
14379
|
name: "@huynle/hub-codex",
|
|
13958
|
-
version: "0.8.
|
|
14380
|
+
version: "0.8.2",
|
|
13959
14381
|
private: true,
|
|
13960
14382
|
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
14383
|
license: "MIT",
|
|
@@ -13980,7 +14402,7 @@ function daemonSocketPath(env = process.env, home = homedir6()) {
|
|
|
13980
14402
|
return override || path26.join(codexHome(env, home), "app-server-control", "app-server-control.sock");
|
|
13981
14403
|
}
|
|
13982
14404
|
function codexStateDir(env = process.env, home = homedir6()) {
|
|
13983
|
-
return path26.join(
|
|
14405
|
+
return path26.join(resolveHubStateDir(env, home).dir, "codex");
|
|
13984
14406
|
}
|
|
13985
14407
|
var pidFile = (env = process.env, home = homedir6()) => path26.join(codexStateDir(env, home), "spoke.pid");
|
|
13986
14408
|
var isPid = (n) => typeof n === "number" && Number.isInteger(n) && n > 1;
|
|
@@ -15374,7 +15796,7 @@ async function codexMain(argv, io) {
|
|
|
15374
15796
|
import path30 from "node:path";
|
|
15375
15797
|
var SERVICE_VERBS = ["install", "uninstall", "status"];
|
|
15376
15798
|
var isServiceVerb = (verb) => SERVICE_VERBS.includes(verb ?? "");
|
|
15377
|
-
var HUB_PLACES = ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR"];
|
|
15799
|
+
var HUB_PLACES = ["HUB_SPOKE_CONFIG", "XDG_CONFIG_HOME", "HUB_STATE_DIR", "XDG_STATE_HOME"];
|
|
15378
15800
|
var CLAUDE_CODE_SERVICE = {
|
|
15379
15801
|
service: "claude-code",
|
|
15380
15802
|
command: "hub claude-code",
|
|
@@ -15516,7 +15938,7 @@ async function runCodex(args, given) {
|
|
|
15516
15938
|
// packages/cli/src/setup/commands.ts
|
|
15517
15939
|
import { existsSync as existsSync9, readFileSync as readFileSync12, renameSync as renameSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
15518
15940
|
import { hostname as hostname4 } from "node:os";
|
|
15519
|
-
import
|
|
15941
|
+
import path34 from "node:path";
|
|
15520
15942
|
|
|
15521
15943
|
// packages/cli/src/bootstrap/transaction/result.ts
|
|
15522
15944
|
var txOk = (value) => ({ ok: true, value });
|
|
@@ -17049,6 +17471,7 @@ function installOf(probe, id, shim, roots, context = "host") {
|
|
|
17049
17471
|
var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17050
17472
|
|
|
17051
17473
|
// packages/cli/src/bootstrap/discovery/host.ts
|
|
17474
|
+
import path32 from "node:path";
|
|
17052
17475
|
function discoverRoots(probe) {
|
|
17053
17476
|
const home = normalizeAbs(probe.env.HOME) ?? normalizeAbs(probe.home);
|
|
17054
17477
|
if (!home)
|
|
@@ -17058,7 +17481,7 @@ function discoverRoots(probe) {
|
|
|
17058
17481
|
const paths = {
|
|
17059
17482
|
home,
|
|
17060
17483
|
config,
|
|
17061
|
-
state: xdg("XDG_STATE_HOME", ".local/state"),
|
|
17484
|
+
state: hubStateRoot(probe, home) ?? xdg("XDG_STATE_HOME", ".local/state"),
|
|
17062
17485
|
data: xdg("XDG_DATA_HOME", ".local/share"),
|
|
17063
17486
|
"user-bin": join(home, ".local/bin"),
|
|
17064
17487
|
"system-bin": "/usr/local/bin"
|
|
@@ -17069,6 +17492,12 @@ function discoverRoots(probe) {
|
|
|
17069
17492
|
paths["systemd-user"] = join(config, "systemd/user");
|
|
17070
17493
|
return ROOT_IDS.flatMap((id) => paths[id] ? [{ id, path: paths[id] }] : []);
|
|
17071
17494
|
}
|
|
17495
|
+
function hubStateRoot(probe, home) {
|
|
17496
|
+
const env = { ...probe.env, ...probe.env.XDG_STATE_HOME !== undefined ? { XDG_STATE_HOME: normalizeAbs(probe.env.XDG_STATE_HOME) } : {} };
|
|
17497
|
+
const { dir } = resolveHubStateDir(env, home, (p) => probe.lstat(p) !== "missing");
|
|
17498
|
+
const abs = normalizeAbs(dir);
|
|
17499
|
+
return abs && path32.posix.basename(abs) === "hub" ? path32.posix.dirname(abs) : undefined;
|
|
17500
|
+
}
|
|
17072
17501
|
var unescapeMount = (s) => s.replace(/\\([0-7]{3})/g, (_, o) => String.fromCharCode(parseInt(o, 8)));
|
|
17073
17502
|
function parseMountinfo(text) {
|
|
17074
17503
|
const mounts = [];
|
|
@@ -20448,7 +20877,7 @@ function createCodexAdapter(options) {
|
|
|
20448
20877
|
|
|
20449
20878
|
// packages/cli/src/bootstrap/adapters/node-services/control.ts
|
|
20450
20879
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
20451
|
-
import
|
|
20880
|
+
import path33 from "node:path";
|
|
20452
20881
|
var HEALTH_WINDOW = Object.freeze({ timeoutMs: 1e4, stableMs: 3000, pollMs: 500 });
|
|
20453
20882
|
var realClock = { now: () => Date.now(), sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
|
|
20454
20883
|
var UNITS = new Set(SERVICE_IDS.map((s) => serviceName(s, "systemd-user")));
|
|
@@ -20488,7 +20917,7 @@ function checkManagerCall(call) {
|
|
|
20488
20917
|
if (verb === "print" || verb === "bootout")
|
|
20489
20918
|
return words.length === 2 && LABELS2.has(labelOfTarget(target) ?? "");
|
|
20490
20919
|
if (verb === "bootstrap")
|
|
20491
|
-
return words.length === 3 && DOMAIN.test(target) && isAbsolutePath(file) && LABELS2.has(
|
|
20920
|
+
return words.length === 3 && DOMAIN.test(target) && isAbsolutePath(file) && LABELS2.has(path33.basename(file).replace(/\.plist$/, "")) && file.endsWith(".plist");
|
|
20492
20921
|
return false;
|
|
20493
20922
|
}
|
|
20494
20923
|
return false;
|
|
@@ -20690,7 +21119,7 @@ var defaultSpawn = (file, words, options) => new Promise((resolve) => {
|
|
|
20690
21119
|
function spawnManagerRunner(options) {
|
|
20691
21120
|
for (const program of ["systemctl", "launchctl"]) {
|
|
20692
21121
|
const file = options.programs[program];
|
|
20693
|
-
if (!isAbsolutePath(file) ||
|
|
21122
|
+
if (!isAbsolutePath(file) || path33.basename(file) !== program)
|
|
20694
21123
|
throw new Error(`${program} must be given as an absolute path to ${program}`);
|
|
20695
21124
|
}
|
|
20696
21125
|
const env = {};
|
|
@@ -23164,7 +23593,7 @@ async function profileArg(io, value, fallback) {
|
|
|
23164
23593
|
return unwrapProfile(defaultProfile(value));
|
|
23165
23594
|
if (value !== "-" && !value.includes("/") && !value.endsWith(".json"))
|
|
23166
23595
|
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(
|
|
23596
|
+
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
23597
|
const loaded = loadProfile(parseJSON2(text, "profile"));
|
|
23169
23598
|
if (!loaded.ok)
|
|
23170
23599
|
throw issueRefusal(loaded.issues);
|
|
@@ -23178,7 +23607,7 @@ function unwrapProfile(r) {
|
|
|
23178
23607
|
function manifestArg(io, value) {
|
|
23179
23608
|
if (value === undefined)
|
|
23180
23609
|
return;
|
|
23181
|
-
const parsed = parseManifest(readText2(
|
|
23610
|
+
const parsed = parseManifest(readText2(path34.resolve(io.cwd ?? process.cwd(), value), "manifest"));
|
|
23182
23611
|
if (!parsed.ok)
|
|
23183
23612
|
throw new Refusal("signature_invalid", issuesText(parsed.issues));
|
|
23184
23613
|
const root = shippedTrustRoot(io.bootstrap?.trustRoot);
|
|
@@ -23204,10 +23633,23 @@ function readState(snapshot) {
|
|
|
23204
23633
|
throw new Refusal("recovery_required", error.message);
|
|
23205
23634
|
}
|
|
23206
23635
|
}
|
|
23636
|
+
function launchCredentials(io) {
|
|
23637
|
+
let warned = false;
|
|
23638
|
+
return () => {
|
|
23639
|
+
const found = resolveCredentials(io.env, io.home);
|
|
23640
|
+
if (found?.source === "login" && !warned && isInsecureStoredURL(found.url)) {
|
|
23641
|
+
warned = true;
|
|
23642
|
+
io.stderr(`hubctl: warning: ${storedInsecureWarning(found.url)}
|
|
23643
|
+
`);
|
|
23644
|
+
}
|
|
23645
|
+
return found;
|
|
23646
|
+
};
|
|
23647
|
+
}
|
|
23207
23648
|
function launchDriverFor(io) {
|
|
23208
23649
|
const store = credentialStoreFor(io.env, io.home);
|
|
23650
|
+
const login = launchCredentials(io);
|
|
23209
23651
|
const client = () => {
|
|
23210
|
-
const credentials =
|
|
23652
|
+
const credentials = login();
|
|
23211
23653
|
if (!credentials)
|
|
23212
23654
|
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
23655
|
return clientFor(credentials);
|
|
@@ -23262,7 +23704,7 @@ function choicesOf(io, flags, profile, snapshot) {
|
|
|
23262
23704
|
const enrollment = str(flags, "enrollment");
|
|
23263
23705
|
if (enrollment !== undefined && !ENROLLMENT_MODES.includes(enrollment))
|
|
23264
23706
|
throw new Refusal("usage", `--enrollment is ${ENROLLMENT_MODES.join(" or ")}`);
|
|
23265
|
-
let roots = flags["workspace-root"]?.map((r) =>
|
|
23707
|
+
let roots = flags["workspace-root"]?.map((r) => path34.resolve(io.cwd ?? process.cwd(), r));
|
|
23266
23708
|
if (!roots?.length && profile.services.includes("node")) {
|
|
23267
23709
|
const existing = readNodeConfig(nodeConfigPath(io.env, io.home)).config.workspaceRoots;
|
|
23268
23710
|
if (existing.length > 0)
|
|
@@ -23374,7 +23816,7 @@ async function preview3(io, flags, json) {
|
|
|
23374
23816
|
const out = str(flags, "out");
|
|
23375
23817
|
if (out) {
|
|
23376
23818
|
const request = { schema: "hub.setup-request", version: 1, preview: r.value, profile, choices };
|
|
23377
|
-
const file =
|
|
23819
|
+
const file = path34.resolve(io.cwd ?? process.cwd(), out);
|
|
23378
23820
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
23379
23821
|
writeFileSync8(tmp, `${JSON.stringify(request, null, 2)}
|
|
23380
23822
|
`, { mode: 384 });
|
|
@@ -23387,7 +23829,7 @@ async function preview3(io, flags, json) {
|
|
|
23387
23829
|
function readRequest(io, file) {
|
|
23388
23830
|
if (!file)
|
|
23389
23831
|
throw new UsageError("needs --preview <file> (from hubctl setup preview --out <file>)");
|
|
23390
|
-
const raw = parseJSON2(readText2(
|
|
23832
|
+
const raw = parseJSON2(readText2(path34.resolve(io.cwd ?? process.cwd(), file), "preview"), "preview", "preview_invalid");
|
|
23391
23833
|
const r = validateSetupRequest(raw);
|
|
23392
23834
|
if (!r.ok)
|
|
23393
23835
|
throw new Refusal("preview_invalid", `${file} is not a hubctl setup preview: ${issuesText(r.issues)}`, "Preview again: hubctl setup preview --out <file>");
|
|
@@ -23563,7 +24005,7 @@ function observe(plan, snapshot) {
|
|
|
23563
24005
|
continue;
|
|
23564
24006
|
const server = file.exists ? at(file.value, `/mcp/servers/${f.registration.name}`) : { found: false };
|
|
23565
24007
|
const command = server.value?.command;
|
|
23566
|
-
const ours = Array.isArray(command) && command[1] === "mcp" && typeof command[0] === "string" && ["hub", "hubctl"].includes(
|
|
24008
|
+
const ours = Array.isArray(command) && command[1] === "mcp" && typeof command[0] === "string" && ["hub", "hubctl"].includes(path34.basename(command[0]));
|
|
23567
24009
|
registrations.push({ ...f.registration, state: !server.found ? "absent" : ours ? "ours" : "foreign" });
|
|
23568
24010
|
}
|
|
23569
24011
|
}
|
|
@@ -23696,7 +24138,7 @@ async function ansible(io, flags, json) {
|
|
|
23696
24138
|
const planFile = str(flags, "plan");
|
|
23697
24139
|
let plan;
|
|
23698
24140
|
if (planFile) {
|
|
23699
|
-
const raw = parseJSON2(readText2(
|
|
24141
|
+
const raw = parseJSON2(readText2(path34.resolve(io.cwd ?? process.cwd(), planFile), "plan"), "plan");
|
|
23700
24142
|
plan = raw.schema === "hub.setup-request" ? raw.preview?.plan : raw.schema === "hub.setup-preview" ? raw.plan : raw;
|
|
23701
24143
|
}
|
|
23702
24144
|
const r = renderRoleVars({
|
|
@@ -23917,7 +24359,7 @@ Options: --json (raw responses), --help, --version
|
|
|
23917
24359
|
Addresses: ses_… (or an unambiguous prefix), @name, @@role, @@role#team (quote it: '@@dev#team'), @name.project, @me (the user)
|
|
23918
24360
|
Credentials: HUB_URL + HUB_SESSION_TOKEN (set in a session's shells), else the \`hub login\` file.
|
|
23919
24361
|
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`;
|
|
24362
|
+
6 rate limited, 7 unsupported/offline/busy, 8 gone, 9 unauthorized/expired`;
|
|
23921
24363
|
var out = (io, text) => io.stdout(text.endsWith(`
|
|
23922
24364
|
`) ? text : `${text}
|
|
23923
24365
|
`);
|
|
@@ -24400,7 +24842,7 @@ ${openedText(body.decision)}` : outcome.text);
|
|
|
24400
24842
|
const server = checkServerURL(url, allow);
|
|
24401
24843
|
if (!server) {
|
|
24402
24844
|
const why = insecureHTTPRefusal(url, allow);
|
|
24403
|
-
throw new UsageError(why ? `${why}: ${url
|
|
24845
|
+
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
24846
|
}
|
|
24405
24847
|
if (isPlainHTTP(server) && !isLoopbackServer(server))
|
|
24406
24848
|
io.stderr(`hub: warning: ${insecureHTTPWarning(server)}
|