@huynle/hub 0.8.1 → 0.8.3
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 +193 -0
- package/README.md +32 -1
- package/package.json +1 -1
- package/packages/claude-code/package.json +1 -1
- package/packages/cli/dist/hub-cli.js +116 -53
- 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/mcp.ts +4 -3
- package/packages/cli/src/profiles-mcp.ts +3 -3
- package/packages/cli/src/team-intake-mcp.ts +8 -7
- package/packages/cli/src/teams-mcp.ts +2 -1
- 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/node/package.json +1 -1
- 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 +92 -7
- package/packages/protocol/src/voice-recording.ts +107 -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/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 +67 -12
- package/src/central/agent/delivery.ts +12 -3
- package/src/central/agent/escalation.ts +30 -9
- package/src/central/agent/launch-spec.ts +38 -19
- package/src/central/agent/routes.ts +14 -1
- package/src/central/artifacts/blob-store.ts +6 -1
- package/src/central/artifacts/blob-support.ts +3 -1
- package/src/central/concierge/board-render.ts +34 -11
- package/src/central/concierge/context.ts +3 -1
- package/src/central/concierge/index.ts +1 -1
- package/src/central/concierge/notifier.ts +2 -1
- package/src/central/concierge/prompt.ts +7 -4
- package/src/central/config.ts +4 -0
- package/src/central/csp.ts +55 -0
- package/src/central/http.ts +134 -29
- package/src/central/profiles/content.ts +16 -0
- package/src/central/recording/clock.ts +76 -0
- package/src/central/recording/concierge-taps.ts +104 -0
- package/src/central/recording/export.ts +215 -0
- package/src/central/recording/index.ts +658 -0
- package/src/central/recording/ingest.ts +209 -0
- package/src/central/recording/retention.ts +71 -0
- package/src/central/recording/routes.ts +281 -0
- package/src/central/recording/schema.ts +232 -0
- package/src/central/recording/settings.ts +35 -0
- package/src/central/recording/store.ts +316 -0
- package/src/central/recording/taps.ts +105 -0
- package/src/central/recording/zip.ts +138 -0
- package/src/central/registry.ts +91 -6
- package/src/central/revert-windows.ts +66 -0
- package/src/central/spoke-ws.ts +8 -3
- package/src/central/teams/staffing.ts +2 -2
- package/src/index.ts +26 -14
- package/src/spoke/deliver.ts +6 -0
- package/src/spoke/fork-revert.ts +94 -9
- package/src/spoke/hub-tool.ts +2 -2
- package/src/spoke/locations.ts +80 -0
- package/src/spoke/profile-apply.ts +24 -14
- package/src/spoke/session-context.ts +96 -6
- package/src/spoke/spoke.ts +119 -36
- package/src/spoke/timeline.ts +30 -3
- package/src/spoke/tracker.ts +23 -2
- package/src/status.ts +33 -6
- package/web/dist/assets/{ArtifactPage-mNzjlP_U.js → ArtifactPage-CSotAWob.js} +1 -1
- package/web/dist/assets/{ArtifactPane-CPQcuX7c.js → ArtifactPane-DPHjiWce.js} +1 -1
- package/web/dist/assets/{ArtifactWorkbench-NoFC5Sb2.js → ArtifactWorkbench-CkmWnP6L.js} +1 -1
- package/web/dist/assets/{Library-LXHsP5Fw.js → Library-DTLEyekn.js} +2 -2
- package/web/dist/assets/ProfilesDestination-GriuOpiZ.js +1 -0
- package/web/dist/assets/{TeamsPage-DBrO5hoL.js → TeamsPage-C-5lYeeM.js} +1 -1
- package/web/dist/assets/Watchtower-DOlPsiIy.js +2 -0
- package/web/dist/assets/index-6mdQ7eUY.js +34 -0
- package/web/dist/assets/index-C5OmwA1y.css +1 -0
- package/web/dist/assets/{live-BDq0AABg.js → live-CDWcyavI.js} +1 -1
- package/web/dist/assets/{pane-layout-DWXfDU-1.js → pane-layout-Csk-vPNe.js} +1 -1
- package/web/dist/assets/{renderer2d-DAr4IHM8.js → renderer2d-CNFdTDTr.js} +1 -1
- package/web/dist/assets/{renderer3d-DWZj595u.js → renderer3d-C45jPvYX.js} +4 -56
- package/web/dist/assets/renderer3d-Co5yCKMg.css +1 -0
- package/web/dist/assets/{safe-area-JUb6OeI3.js → safe-area-sA9a8vYJ.js} +1 -1
- package/web/dist/assets/{silero-worker-C8-8vLVS.js → silero-worker-DhlIHyD_.js} +1 -1
- package/web/dist/assets/{source-CDU04kYw.js → source-BFLJQ43_.js} +1 -1
- package/web/dist/assets/{ui-Dh1-78Cl.js → ui-Qo2Pd9-O.js} +1 -1
- package/web/dist/index.html +5 -16
- package/web/dist/theme-boot.js +12 -0
- package/web/dist/assets/ProfilesDestination-DhVVcAep.js +0 -1
- package/web/dist/assets/Watchtower-DxaJYQp_.js +0 -2
- package/web/dist/assets/index-BabrNDB3.css +0 -1
- package/web/dist/assets/index-lIOfSYck.js +0 -34
|
@@ -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.3",
|
|
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 = {}) {
|
|
@@ -3826,6 +3826,33 @@ var GRAPH_LIMITS = {
|
|
|
3826
3826
|
defaultWindowMs: 24 * 60 * 60000
|
|
3827
3827
|
};
|
|
3828
3828
|
var WATCHTOWER_CHUNK_BUDGET = 700 * 1024;
|
|
3829
|
+
// packages/protocol/src/voice-recording.ts
|
|
3830
|
+
var RECORDING_LIMITS = {
|
|
3831
|
+
retentionMs: 7 * 24 * 3600000,
|
|
3832
|
+
userBytes: 500 * 1024 * 1024,
|
|
3833
|
+
recordingBytes: 150 * 1024 * 1024,
|
|
3834
|
+
segmentBytes: 2 * 1024 * 1024,
|
|
3835
|
+
segmentsPerRecording: 5000,
|
|
3836
|
+
eventsPerBatch: 200,
|
|
3837
|
+
batchBytes: 256 * 1024,
|
|
3838
|
+
keepaliveBatchBytes: 60 * 1024,
|
|
3839
|
+
eventsPerRecording: 50000,
|
|
3840
|
+
eventBytesPerRecording: 32 * 1024 * 1024,
|
|
3841
|
+
recordingsStored: 200,
|
|
3842
|
+
activePerUser: 4,
|
|
3843
|
+
maxDurationMs: 4 * 3600000,
|
|
3844
|
+
idleEndMs: 10 * 60000,
|
|
3845
|
+
sweepMs: 15 * 60000,
|
|
3846
|
+
textChars: 2000,
|
|
3847
|
+
utteranceChars: 4000,
|
|
3848
|
+
replyChars: 32000,
|
|
3849
|
+
messageChars: 300,
|
|
3850
|
+
consoleBufferEvents: 2000,
|
|
3851
|
+
flushMs: 5000,
|
|
3852
|
+
flushEvents: 100,
|
|
3853
|
+
hubFlushMs: 2000,
|
|
3854
|
+
hubFlushEvents: 100
|
|
3855
|
+
};
|
|
3829
3856
|
// packages/protocol/src/index.ts
|
|
3830
3857
|
var PROTOCOL_VERSION = 1;
|
|
3831
3858
|
var FORM_ANSWER_MAX_KEYS = 50;
|
|
@@ -3852,6 +3879,9 @@ function formAnswerProblem(v) {
|
|
|
3852
3879
|
}
|
|
3853
3880
|
var REVERT_FILES_MAX = 50;
|
|
3854
3881
|
var REVERT_FILE_MAX_CHARS = 1000;
|
|
3882
|
+
function isRevertCommitted(v) {
|
|
3883
|
+
return isObj(v) && nonEmpty(v.messageID) && v.messageID.length <= 300 && (v.from === undefined || num(v.from)) && num(v.until) && num(v.at);
|
|
3884
|
+
}
|
|
3855
3885
|
var FORWARDED_EVENTS = new Set([
|
|
3856
3886
|
"session.created",
|
|
3857
3887
|
"session.renamed",
|
|
@@ -3906,7 +3936,7 @@ var IMPLIED_OPENCODE_FEATURES = [
|
|
|
3906
3936
|
"expose",
|
|
3907
3937
|
"create"
|
|
3908
3938
|
];
|
|
3909
|
-
var OPTIONAL_SPOKE_FEATURES = [...IMPLIED_OPENCODE_FEATURES, "autoName", "sessionTokens", "deliver", "hubPrompt", "permissionMessage", "actTokens", "launch", "fork", "revert"];
|
|
3939
|
+
var OPTIONAL_SPOKE_FEATURES = [...IMPLIED_OPENCODE_FEATURES, "autoName", "sessionTokens", "deliver", "hubPrompt", "permissionMessage", "actTokens", "launch", "fork", "revert", "dismissError"];
|
|
3910
3940
|
var CORE_OPS = ["turn", "steer", "interrupt", "stop", "permission", "form"];
|
|
3911
3941
|
var KNOWN_SPOKE_FEATURES = [...CORE_OPS, ...OPTIONAL_SPOKE_FEATURES, "turnEvents", PROFILE_APPLY_FEATURE, SESSION_CONTEXT_FEATURE];
|
|
3912
3942
|
var SPOKE_FEATURES = [...OPTIONAL_SPOKE_FEATURES, PROFILE_APPLY_FEATURE, SESSION_CONTEXT_FEATURE];
|
|
@@ -3943,13 +3973,14 @@ function isLaunchCreate(req) {
|
|
|
3943
3973
|
}
|
|
3944
3974
|
var AGENT_NAME_MAX = 100;
|
|
3945
3975
|
var PERMISSION_MESSAGE_MAX = 1000;
|
|
3976
|
+
var RES_CODE_MAX = 40;
|
|
3946
3977
|
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));
|
|
3947
3978
|
var SESSION_TOKEN_TEXT = new RegExp(`^[\\x21-\\x7e]{1,${SESSION_TOKEN_MAX_CHARS}}$`);
|
|
3948
3979
|
function isSessionToken(v) {
|
|
3949
3980
|
return isObj(v) && nonEmpty(v.sessionID) && v.sessionID.length <= 300 && str2(v.token) && SESSION_TOKEN_TEXT.test(v.token) && num(v.exp);
|
|
3950
3981
|
}
|
|
3951
3982
|
function isSession(v) {
|
|
3952
|
-
return isObj(v) && nonEmpty(v.id) && str2(v.title) && str2(v.directory) && num(v.updated) && typeof v.owned === "boolean" && (v.parentID === undefined || str2(v.parentID)) && (v.exposed === undefined || typeof v.exposed === "boolean") && (v.project === undefined || str2(v.project)) && (v.projectName === undefined || str2(v.projectName)) && (v.archived === undefined || typeof v.archived === "boolean") && (v.open === undefined || typeof v.open === "boolean") && (v.terminal === undefined || isTerminal(v.terminal)) && (v.revert === undefined || isSessionRevert(v.revert)) && (v.agent === undefined || nonEmpty(v.agent) && v.agent.length <= AGENT_NAME_MAX) && (v.contextRevision === undefined || Number.isSafeInteger(v.contextRevision) && v.contextRevision >= 0) && (v.locked === undefined || v.locked === true);
|
|
3983
|
+
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);
|
|
3953
3984
|
}
|
|
3954
3985
|
var REVERT_STATUS = new Set(["added", "deleted", "modified"]);
|
|
3955
3986
|
var count = (v) => Number.isSafeInteger(v) && v >= 0;
|
|
@@ -3957,7 +3988,7 @@ function isRevertFile(v) {
|
|
|
3957
3988
|
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));
|
|
3958
3989
|
}
|
|
3959
3990
|
function isSessionRevert(v) {
|
|
3960
|
-
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;
|
|
3991
|
+
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));
|
|
3961
3992
|
}
|
|
3962
3993
|
var TERMINAL_STATUS = new Set(["idle", "working", "blocked", "done", "unknown"]);
|
|
3963
3994
|
function isTerminal(v) {
|
|
@@ -4129,6 +4160,7 @@ function isServerRequest(v) {
|
|
|
4129
4160
|
return nonEmpty(v.sessionID) && nonEmpty(v.messageID) && v.messageID.length <= 300 && typeof v.files === "boolean";
|
|
4130
4161
|
case "revertRestore":
|
|
4131
4162
|
case "revertCommit":
|
|
4163
|
+
case "dismissError":
|
|
4132
4164
|
return nonEmpty(v.sessionID);
|
|
4133
4165
|
default:
|
|
4134
4166
|
return false;
|
|
@@ -4141,8 +4173,12 @@ function parseSpokeMessage(raw) {
|
|
|
4141
4173
|
switch (m.t) {
|
|
4142
4174
|
case "hello":
|
|
4143
4175
|
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;
|
|
4144
|
-
case "sessions":
|
|
4145
|
-
|
|
4176
|
+
case "sessions": {
|
|
4177
|
+
if (!Array.isArray(m.sessions))
|
|
4178
|
+
return;
|
|
4179
|
+
const sessions = m.sessions.map(lenientSession);
|
|
4180
|
+
return sessions.every(isSession) ? { ...m, sessions } : undefined;
|
|
4181
|
+
}
|
|
4146
4182
|
case "terminals":
|
|
4147
4183
|
return m.herdr === undefined || isHerdrInfo(m.herdr) ? m : undefined;
|
|
4148
4184
|
case "event":
|
|
@@ -4150,13 +4186,34 @@ function parseSpokeMessage(raw) {
|
|
|
4150
4186
|
case "req":
|
|
4151
4187
|
return nonEmpty(m.id) && isSpokeRequest(m.req) ? m : undefined;
|
|
4152
4188
|
case "res":
|
|
4153
|
-
return isResFrame(m) ? m : undefined;
|
|
4189
|
+
return isResFrame(m) ? lenientRes(m) : undefined;
|
|
4154
4190
|
case "bye":
|
|
4155
4191
|
return isByeFrame(m) ? m : undefined;
|
|
4156
4192
|
default:
|
|
4157
4193
|
return;
|
|
4158
4194
|
}
|
|
4159
4195
|
}
|
|
4196
|
+
function lenientRes(m) {
|
|
4197
|
+
if (m.code === undefined || nonEmpty(m.code) && m.code.length <= RES_CODE_MAX)
|
|
4198
|
+
return m;
|
|
4199
|
+
const { code: _dropped, ...rest } = m;
|
|
4200
|
+
return rest;
|
|
4201
|
+
}
|
|
4202
|
+
function lenientSession(v) {
|
|
4203
|
+
if (!isObj(v))
|
|
4204
|
+
return v;
|
|
4205
|
+
let out = v;
|
|
4206
|
+
if (out.revertCommitted !== undefined && !isRevertCommitted(out.revertCommitted)) {
|
|
4207
|
+
const { revertCommitted: _dropped, ...rest } = out;
|
|
4208
|
+
out = rest;
|
|
4209
|
+
}
|
|
4210
|
+
const revert = out.revert;
|
|
4211
|
+
if (isObj(revert) && revert.stagedAt !== undefined && !num(revert.stagedAt)) {
|
|
4212
|
+
const { stagedAt: _dropped, ...rest } = revert;
|
|
4213
|
+
out = { ...out, revert: rest };
|
|
4214
|
+
}
|
|
4215
|
+
return out;
|
|
4216
|
+
}
|
|
4160
4217
|
function parseServerMessage(raw) {
|
|
4161
4218
|
const m = parseFrame(raw);
|
|
4162
4219
|
if (!m)
|
|
@@ -4167,7 +4224,7 @@ function parseServerMessage(raw) {
|
|
|
4167
4224
|
case "req":
|
|
4168
4225
|
return nonEmpty(m.id) && isServerRequest(m.req) ? m : undefined;
|
|
4169
4226
|
case "res":
|
|
4170
|
-
return isResFrame(m) ? m : undefined;
|
|
4227
|
+
return isResFrame(m) ? lenientRes(m) : undefined;
|
|
4171
4228
|
case "bye":
|
|
4172
4229
|
return isByeFrame(m) ? m : undefined;
|
|
4173
4230
|
default:
|
|
@@ -5847,6 +5904,10 @@ function launchText(l) {
|
|
|
5847
5904
|
`);
|
|
5848
5905
|
}
|
|
5849
5906
|
|
|
5907
|
+
// packages/cli/src/tool-args.ts
|
|
5908
|
+
class ToolArgError extends Error {
|
|
5909
|
+
}
|
|
5910
|
+
|
|
5850
5911
|
// packages/cli/src/artifacts-mcp.ts
|
|
5851
5912
|
var MCP_INLINE_TEXT_BYTES = 256 * 1024;
|
|
5852
5913
|
var KINDS = ["markdown", "code", "image", "table", "chart", "dashboard", "html", "app", "file"];
|
|
@@ -5870,7 +5931,7 @@ var FILE_ARGS = {
|
|
|
5870
5931
|
var need = (args, key) => {
|
|
5871
5932
|
const value = args[key];
|
|
5872
5933
|
if (typeof value !== "string" || !value.trim())
|
|
5873
|
-
throw new
|
|
5934
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
5874
5935
|
return value;
|
|
5875
5936
|
};
|
|
5876
5937
|
var optional2 = (args, key) => {
|
|
@@ -5878,20 +5939,20 @@ var optional2 = (args, key) => {
|
|
|
5878
5939
|
if (value === undefined || value === null)
|
|
5879
5940
|
return;
|
|
5880
5941
|
if (typeof value !== "string")
|
|
5881
|
-
throw new
|
|
5942
|
+
throw new ToolArgError(`"${key}" must be a string`);
|
|
5882
5943
|
return value.trim() ? value : undefined;
|
|
5883
5944
|
};
|
|
5884
5945
|
function files(args, kind) {
|
|
5885
5946
|
const text = args.text;
|
|
5886
5947
|
if (text !== undefined && typeof text !== "string")
|
|
5887
|
-
throw new
|
|
5948
|
+
throw new ToolArgError('"text" must be a string');
|
|
5888
5949
|
const blobs = args.blobs;
|
|
5889
5950
|
if (blobs !== undefined && (!Array.isArray(blobs) || !blobs.every((b) => typeof b === "object" && b !== null)))
|
|
5890
|
-
throw new
|
|
5951
|
+
throw new ToolArgError('"blobs" must be a list of blob references');
|
|
5891
5952
|
if (text === undefined && (blobs === undefined || blobs.length === 0))
|
|
5892
|
-
throw new
|
|
5953
|
+
throw new ToolArgError("give text or blobs (stage bigger content first and pass its reference)");
|
|
5893
5954
|
if (text !== undefined && Buffer.byteLength(text, "utf8") > MCP_INLINE_TEXT_BYTES)
|
|
5894
|
-
throw new
|
|
5955
|
+
throw new ToolArgError(`text is larger than ${MCP_INLINE_TEXT_BYTES} bytes: stage it with uploadBlob and pass blobs`);
|
|
5895
5956
|
const path = optional2(args, "path");
|
|
5896
5957
|
const entry = optional2(args, "entry");
|
|
5897
5958
|
const mediaType = optional2(args, "mediaType") ?? (kind && DEFAULT_MEDIA[kind]) ?? "text/plain";
|
|
@@ -5958,7 +6019,7 @@ var artifactTools = [
|
|
|
5958
6019
|
const title = need(args, "title");
|
|
5959
6020
|
const kind = need(args, "kind");
|
|
5960
6021
|
if (!KINDS.includes(kind))
|
|
5961
|
-
throw new
|
|
6022
|
+
throw new ToolArgError(`"kind" must be one of ${KINDS.join(", ")}`);
|
|
5962
6023
|
const body = files(args, kind);
|
|
5963
6024
|
const description = optional2(args, "description");
|
|
5964
6025
|
const visibility = optional2(args, "visibility");
|
|
@@ -5997,7 +6058,7 @@ var KINDS2 = ["ask", "propose-edit", "run-action"];
|
|
|
5997
6058
|
var need2 = (args, key) => {
|
|
5998
6059
|
const value = args[key];
|
|
5999
6060
|
if (typeof value !== "string" || !value.trim())
|
|
6000
|
-
throw new
|
|
6061
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
6001
6062
|
return value;
|
|
6002
6063
|
};
|
|
6003
6064
|
var optional3 = (args, key) => {
|
|
@@ -6005,24 +6066,24 @@ var optional3 = (args, key) => {
|
|
|
6005
6066
|
if (value === undefined || value === null)
|
|
6006
6067
|
return;
|
|
6007
6068
|
if (typeof value !== "string")
|
|
6008
|
-
throw new
|
|
6069
|
+
throw new ToolArgError(`"${key}" must be a string`);
|
|
6009
6070
|
return value.trim() ? value : undefined;
|
|
6010
6071
|
};
|
|
6011
6072
|
var integer = (args, key) => {
|
|
6012
6073
|
const value = args[key];
|
|
6013
6074
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
6014
|
-
throw new
|
|
6075
|
+
throw new ToolArgError(`"${key}" is required (a whole number)`);
|
|
6015
6076
|
return value;
|
|
6016
6077
|
};
|
|
6017
6078
|
function handlerOf(args) {
|
|
6018
6079
|
const session = optional3(args, "session");
|
|
6019
6080
|
const machine = optional3(args, "machine");
|
|
6020
6081
|
if (session && machine)
|
|
6021
|
-
throw new
|
|
6082
|
+
throw new ToolArgError('give either "session" (an existing session) or "machine" + "repo" (a new handler session), not both');
|
|
6022
6083
|
if (session)
|
|
6023
6084
|
return { session };
|
|
6024
6085
|
if (!machine)
|
|
6025
|
-
throw new
|
|
6086
|
+
throw new ToolArgError('"machine" and "repo" are required to launch a handler session (or name an existing one with "session")');
|
|
6026
6087
|
const repo = need2(args, "repo");
|
|
6027
6088
|
const harness = optional3(args, "harness");
|
|
6028
6089
|
const model = optional3(args, "model");
|
|
@@ -6086,7 +6147,7 @@ var artifactJobTools = [
|
|
|
6086
6147
|
run: async (hub, args, signal) => {
|
|
6087
6148
|
const kind = optional3(args, "kind");
|
|
6088
6149
|
if (kind && !KINDS2.includes(kind))
|
|
6089
|
-
throw new
|
|
6150
|
+
throw new ToolArgError(`"kind" must be one of ${KINDS2.join(", ")}`);
|
|
6090
6151
|
const action = optional3(args, "action");
|
|
6091
6152
|
const apply = optional3(args, "apply");
|
|
6092
6153
|
const key = optional3(args, "key");
|
|
@@ -6155,12 +6216,12 @@ var artifactJobTools = [
|
|
|
6155
6216
|
const answer = optional3(args, "answer");
|
|
6156
6217
|
const text = args.text;
|
|
6157
6218
|
if (text !== undefined && typeof text !== "string")
|
|
6158
|
-
throw new
|
|
6219
|
+
throw new ToolArgError('"text" must be a string');
|
|
6159
6220
|
if (typeof text === "string" && Buffer.byteLength(text, "utf8") > MCP_JOB_TEXT_BYTES)
|
|
6160
|
-
throw new
|
|
6221
|
+
throw new ToolArgError(`"text" is larger than ${MCP_JOB_TEXT_BYTES} bytes`);
|
|
6161
6222
|
const keep = args.keep;
|
|
6162
6223
|
if (keep !== undefined && (!Array.isArray(keep) || !keep.every((k) => typeof k === "string")))
|
|
6163
|
-
throw new
|
|
6224
|
+
throw new ToolArgError('"keep" must be a list of paths');
|
|
6164
6225
|
const path = optional3(args, "path");
|
|
6165
6226
|
const entry = optional3(args, "entry");
|
|
6166
6227
|
const result = error ? { error } : text !== undefined || keep !== undefined ? {
|
|
@@ -6207,7 +6268,7 @@ var UPDATES = TEAM_INTAKE_STATUS_INPUTS;
|
|
|
6207
6268
|
var need3 = (args, key) => {
|
|
6208
6269
|
const value = args[key];
|
|
6209
6270
|
if (typeof value !== "string" || !value.trim())
|
|
6210
|
-
throw new
|
|
6271
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
6211
6272
|
return value;
|
|
6212
6273
|
};
|
|
6213
6274
|
var optional4 = (args, key) => {
|
|
@@ -6215,7 +6276,7 @@ var optional4 = (args, key) => {
|
|
|
6215
6276
|
if (value === undefined || value === null)
|
|
6216
6277
|
return;
|
|
6217
6278
|
if (typeof value !== "string")
|
|
6218
|
-
throw new
|
|
6279
|
+
throw new ToolArgError(`"${key}" must be a string`);
|
|
6219
6280
|
return value.trim() ? value : undefined;
|
|
6220
6281
|
};
|
|
6221
6282
|
var strings2 = (args, key) => {
|
|
@@ -6223,13 +6284,13 @@ var strings2 = (args, key) => {
|
|
|
6223
6284
|
if (value === undefined || value === null)
|
|
6224
6285
|
return;
|
|
6225
6286
|
if (!Array.isArray(value) || !value.every((v) => typeof v === "string"))
|
|
6226
|
-
throw new
|
|
6287
|
+
throw new ToolArgError(`"${key}" must be a list of strings`);
|
|
6227
6288
|
return value;
|
|
6228
6289
|
};
|
|
6229
6290
|
var oneOf4 = (args, key, allowed) => {
|
|
6230
6291
|
const value = optional4(args, key);
|
|
6231
6292
|
if (value !== undefined && !allowed.includes(value))
|
|
6232
|
-
throw new
|
|
6293
|
+
throw new ToolArgError(`"${key}" must be one of ${allowed.join(", ")}`);
|
|
6233
6294
|
return value;
|
|
6234
6295
|
};
|
|
6235
6296
|
function dueOf(args) {
|
|
@@ -6238,7 +6299,7 @@ function dueOf(args) {
|
|
|
6238
6299
|
return;
|
|
6239
6300
|
const ms = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : Number.NaN;
|
|
6240
6301
|
if (!Number.isSafeInteger(ms) || ms <= 0)
|
|
6241
|
-
throw new
|
|
6302
|
+
throw new ToolArgError(`"due" must be an ISO date/time or epoch milliseconds`);
|
|
6242
6303
|
return ms;
|
|
6243
6304
|
}
|
|
6244
6305
|
var line4 = (i) => `${i.id} ${i.status}${i.progress ? `/${i.progress}` : ""} ${i.priority} r${i.revision} ${sanitizeText(i.title).replace(/\s+/g, " ")}`;
|
|
@@ -6325,10 +6386,10 @@ ${intakeText(intake)}`;
|
|
|
6325
6386
|
run: async (hub, args, signal) => {
|
|
6326
6387
|
const status = oneOf4(args, "status", UPDATES);
|
|
6327
6388
|
if (!status)
|
|
6328
|
-
throw new
|
|
6389
|
+
throw new ToolArgError(`"status" is required: one of ${UPDATES.join(", ")}`);
|
|
6329
6390
|
const expectedRevision = args.expectedRevision;
|
|
6330
6391
|
if (expectedRevision !== undefined && expectedRevision !== null && (typeof expectedRevision !== "number" || !Number.isSafeInteger(expectedRevision) || expectedRevision < 0))
|
|
6331
|
-
throw new
|
|
6392
|
+
throw new ToolArgError(`"expectedRevision" must be the revision you read (or left out)`);
|
|
6332
6393
|
const reason = optional4(args, "reason");
|
|
6333
6394
|
const { intake, assignment } = await reportIntakeStatus(hub.teams, need3(args, "team"), need3(args, "intake"), { status, ...typeof expectedRevision === "number" ? { expectedRevision } : {}, ...reason ? { reason } : {} }, { signal });
|
|
6334
6395
|
return `${intake.id} is ${intake.status}${intake.progress ? ` (${intake.progress})` : ""}, revision ${intake.revision}${assignment ? `; assignment ${assignment.id} ${assignment.status}` : ""}`;
|
|
@@ -6421,9 +6482,7 @@ var LAUNCH = {
|
|
|
6421
6482
|
required: ["workspace"]
|
|
6422
6483
|
};
|
|
6423
6484
|
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.";
|
|
6424
|
-
|
|
6425
|
-
class ArgError extends Error {
|
|
6426
|
-
}
|
|
6485
|
+
var ArgError = ToolArgError;
|
|
6427
6486
|
var need4 = (args, key) => {
|
|
6428
6487
|
const value = args[key];
|
|
6429
6488
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -6790,10 +6849,7 @@ var META_FIELDS = {
|
|
|
6790
6849
|
projects: { ...STRINGS2, description: "Project names the profile is meant for (≤ 16)." }
|
|
6791
6850
|
};
|
|
6792
6851
|
var SCOPES2 = "App tokens need scope profiles:read (reads) or profiles:manage (changes; profile_version_create with content fields and the skill/command set/remove tools read the latest version first, so they also need profiles:read); pins need profiles:assign and sessions:read (an app also needs a prompt grant on the session). Session tokens never change profiles. A profile confers no authority.";
|
|
6793
|
-
|
|
6794
|
-
class ProfileArgError extends Error {
|
|
6795
|
-
}
|
|
6796
|
-
var ArgError2 = ProfileArgError;
|
|
6852
|
+
var ArgError2 = ToolArgError;
|
|
6797
6853
|
var need5 = (args, key) => {
|
|
6798
6854
|
const value = args[key];
|
|
6799
6855
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -7048,7 +7104,7 @@ var STRINGS3 = { type: "array", items: { type: "string" } };
|
|
|
7048
7104
|
var need6 = (args, key) => {
|
|
7049
7105
|
const value = args[key];
|
|
7050
7106
|
if (typeof value !== "string" || !value.trim())
|
|
7051
|
-
throw new
|
|
7107
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
7052
7108
|
return value;
|
|
7053
7109
|
};
|
|
7054
7110
|
var flag = (args, key, fallback) => {
|
|
@@ -7056,7 +7112,7 @@ var flag = (args, key, fallback) => {
|
|
|
7056
7112
|
if (value === undefined || value === null)
|
|
7057
7113
|
return fallback;
|
|
7058
7114
|
if (typeof value !== "boolean")
|
|
7059
|
-
throw new
|
|
7115
|
+
throw new ToolArgError(`"${key}" must be true or false`);
|
|
7060
7116
|
return value;
|
|
7061
7117
|
};
|
|
7062
7118
|
var UPDATE_FIELDS = ["autoActive", "activeWithinHours", "usePins", "sessions", "projects", "labels", "muted", "mutedProjects"];
|
|
@@ -7088,10 +7144,10 @@ var watchlistTools = [
|
|
|
7088
7144
|
run: async (hub, args, signal) => {
|
|
7089
7145
|
const expected = args.expectedRevision;
|
|
7090
7146
|
if (typeof expected !== "number" || !Number.isSafeInteger(expected) || expected < 0)
|
|
7091
|
-
throw new
|
|
7147
|
+
throw new ToolArgError(`"expectedRevision" is required (the revision you read)`);
|
|
7092
7148
|
const unknown = Object.keys(args).filter((k) => k !== "expectedRevision" && !UPDATE_FIELDS.includes(k));
|
|
7093
7149
|
if (unknown.length)
|
|
7094
|
-
throw new
|
|
7150
|
+
throw new ToolArgError(`Unknown argument${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${safe(k).slice(0, 40)}"`).join(", ")}: use ${UPDATE_FIELDS.join(", ")} and expectedRevision`);
|
|
7095
7151
|
const patch = {};
|
|
7096
7152
|
for (const field of UPDATE_FIELDS)
|
|
7097
7153
|
if (args[field] !== undefined && args[field] !== null)
|
|
@@ -7137,9 +7193,7 @@ ${watchlistConfigText(r.config)}`;
|
|
|
7137
7193
|
// packages/cli/src/mcp.ts
|
|
7138
7194
|
var MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
7139
7195
|
var RPC = { parse: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602, internal: -32603 };
|
|
7140
|
-
|
|
7141
|
-
class ArgError3 extends Error {
|
|
7142
|
-
}
|
|
7196
|
+
var ArgError3 = ToolArgError;
|
|
7143
7197
|
var need7 = (args, key) => {
|
|
7144
7198
|
const value = args[key];
|
|
7145
7199
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -8324,7 +8378,7 @@ function createMcpServer(options) {
|
|
|
8324
8378
|
} catch (cause) {
|
|
8325
8379
|
if (controller.signal.aborted)
|
|
8326
8380
|
return;
|
|
8327
|
-
const text = cause instanceof
|
|
8381
|
+
const text = cause instanceof ToolArgError ? `invalid: ${cause.message}` : cause instanceof CliError ? errorText(cause) : `${tool.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
8328
8382
|
return ok2(id, toolResult(clean(text, token), true));
|
|
8329
8383
|
} finally {
|
|
8330
8384
|
if (inFlight.get(key(id)) === controller)
|
|
@@ -8761,7 +8815,7 @@ import { arch as osArch, platform as osPlatform } from "node:os";
|
|
|
8761
8815
|
// packages/node/package.json
|
|
8762
8816
|
var package_default2 = {
|
|
8763
8817
|
name: "@huynle/hub-node",
|
|
8764
|
-
version: "0.8.
|
|
8818
|
+
version: "0.8.3",
|
|
8765
8819
|
private: true,
|
|
8766
8820
|
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).",
|
|
8767
8821
|
license: "MIT",
|
|
@@ -9853,7 +9907,7 @@ import path13 from "node:path";
|
|
|
9853
9907
|
// packages/runtime-openshell/package.json
|
|
9854
9908
|
var package_default3 = {
|
|
9855
9909
|
name: "@huynle/hub-runtime-openshell",
|
|
9856
|
-
version: "0.8.
|
|
9910
|
+
version: "0.8.3",
|
|
9857
9911
|
private: true,
|
|
9858
9912
|
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).",
|
|
9859
9913
|
license: "MIT",
|
|
@@ -11395,6 +11449,15 @@ function wsURL2(server) {
|
|
|
11395
11449
|
var defaultSocket2 = (url, headers) => new WebSocket(url, { headers });
|
|
11396
11450
|
var message2 = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
11397
11451
|
|
|
11452
|
+
class CodedError extends Error {
|
|
11453
|
+
code;
|
|
11454
|
+
constructor(message, code) {
|
|
11455
|
+
super(message);
|
|
11456
|
+
this.code = code;
|
|
11457
|
+
this.name = "CodedError";
|
|
11458
|
+
}
|
|
11459
|
+
}
|
|
11460
|
+
|
|
11398
11461
|
class SpokeConnection {
|
|
11399
11462
|
options;
|
|
11400
11463
|
state = "off";
|
|
@@ -11564,7 +11627,7 @@ class SpokeConnection {
|
|
|
11564
11627
|
try {
|
|
11565
11628
|
reply = { t: "res", id, ok: true, result: await this.options.handle(req) };
|
|
11566
11629
|
} catch (cause) {
|
|
11567
|
-
reply = { t: "res", id, ok: false, error: message2(cause) };
|
|
11630
|
+
reply = { t: "res", id, ok: false, error: message2(cause), ...cause instanceof CodedError ? { code: cause.code } : {} };
|
|
11568
11631
|
}
|
|
11569
11632
|
this.send(reply);
|
|
11570
11633
|
}
|
|
@@ -13528,7 +13591,7 @@ import path22 from "node:path";
|
|
|
13528
13591
|
// packages/claude-code/package.json
|
|
13529
13592
|
var package_default4 = {
|
|
13530
13593
|
name: "@huynle/hub-claude-code",
|
|
13531
|
-
version: "0.8.
|
|
13594
|
+
version: "0.8.3",
|
|
13532
13595
|
private: true,
|
|
13533
13596
|
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).",
|
|
13534
13597
|
license: "MIT",
|
|
@@ -13817,7 +13880,7 @@ import path25 from "node:path";
|
|
|
13817
13880
|
// packages/preset/package.json
|
|
13818
13881
|
var package_default5 = {
|
|
13819
13882
|
name: "@huynle/hub-preset",
|
|
13820
|
-
version: "0.8.
|
|
13883
|
+
version: "0.8.3",
|
|
13821
13884
|
private: true,
|
|
13822
13885
|
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).",
|
|
13823
13886
|
license: "MIT",
|
|
@@ -14342,7 +14405,7 @@ import { openSync as openSync5, closeSync as closeSync5, readFileSync as readFil
|
|
|
14342
14405
|
// packages/codex/package.json
|
|
14343
14406
|
var package_default6 = {
|
|
14344
14407
|
name: "@huynle/hub-codex",
|
|
14345
|
-
version: "0.8.
|
|
14408
|
+
version: "0.8.3",
|
|
14346
14409
|
private: true,
|
|
14347
14410
|
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).",
|
|
14348
14411
|
license: "MIT",
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// (+ artifacts:read) for an app token. Content never travels in requests or job listings, only in the context.
|
|
8
8
|
import type { ArtifactJob, ArtifactJobContext, ArtifactJobKind, ArtifactJobRequest } from "../../sdk/src/artifact-jobs.ts"
|
|
9
9
|
import type { Artifacts } from "../../sdk/src/artifacts.ts"
|
|
10
|
+
import { ToolArgError } from "./tool-args"
|
|
10
11
|
|
|
11
12
|
/** UTF-8 bytes of the inline proposal text one tool call may carry. */
|
|
12
13
|
export const MCP_JOB_TEXT_BYTES = 256 * 1024
|
|
@@ -33,27 +34,27 @@ const KINDS: readonly ArtifactJobKind[] = ["ask", "propose-edit", "run-action"]
|
|
|
33
34
|
|
|
34
35
|
const need = (args: Args, key: string): string => {
|
|
35
36
|
const value = args[key]
|
|
36
|
-
if (typeof value !== "string" || !value.trim()) throw new
|
|
37
|
+
if (typeof value !== "string" || !value.trim()) throw new ToolArgError(`"${key}" is required (a non-empty string)`)
|
|
37
38
|
return value
|
|
38
39
|
}
|
|
39
40
|
const optional = (args: Args, key: string): string | undefined => {
|
|
40
41
|
const value = args[key]
|
|
41
42
|
if (value === undefined || value === null) return undefined
|
|
42
|
-
if (typeof value !== "string") throw new
|
|
43
|
+
if (typeof value !== "string") throw new ToolArgError(`"${key}" must be a string`)
|
|
43
44
|
return value.trim() ? value : undefined
|
|
44
45
|
}
|
|
45
46
|
const integer = (args: Args, key: string): number => {
|
|
46
47
|
const value = args[key]
|
|
47
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new
|
|
48
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new ToolArgError(`"${key}" is required (a whole number)`)
|
|
48
49
|
return value
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
function handlerOf(args: Args): ArtifactJobRequest["handler"] {
|
|
52
53
|
const session = optional(args, "session")
|
|
53
54
|
const machine = optional(args, "machine")
|
|
54
|
-
if (session && machine) throw new
|
|
55
|
+
if (session && machine) throw new ToolArgError('give either "session" (an existing session) or "machine" + "repo" (a new handler session), not both')
|
|
55
56
|
if (session) return { session }
|
|
56
|
-
if (!machine) throw new
|
|
57
|
+
if (!machine) throw new ToolArgError('"machine" and "repo" are required to launch a handler session (or name an existing one with "session")')
|
|
57
58
|
const repo = need(args, "repo")
|
|
58
59
|
const harness = optional(args, "harness")
|
|
59
60
|
const model = optional(args, "model")
|
|
@@ -114,7 +115,7 @@ export const artifactJobTools: readonly ArtifactJobTool[] = [
|
|
|
114
115
|
readOnly: false,
|
|
115
116
|
run: async (hub, args, signal) => {
|
|
116
117
|
const kind = optional(args, "kind") as ArtifactJobKind | undefined
|
|
117
|
-
if (kind && !KINDS.includes(kind)) throw new
|
|
118
|
+
if (kind && !KINDS.includes(kind)) throw new ToolArgError(`"kind" must be one of ${KINDS.join(", ")}`)
|
|
118
119
|
const action = optional(args, "action")
|
|
119
120
|
const apply = optional(args, "apply") as "manual" | "auto" | undefined
|
|
120
121
|
const key = optional(args, "key")
|
|
@@ -181,10 +182,10 @@ export const artifactJobTools: readonly ArtifactJobTool[] = [
|
|
|
181
182
|
const error = optional(args, "error")
|
|
182
183
|
const answer = optional(args, "answer")
|
|
183
184
|
const text = args.text
|
|
184
|
-
if (text !== undefined && typeof text !== "string") throw new
|
|
185
|
-
if (typeof text === "string" && Buffer.byteLength(text, "utf8") > MCP_JOB_TEXT_BYTES) throw new
|
|
185
|
+
if (text !== undefined && typeof text !== "string") throw new ToolArgError('"text" must be a string')
|
|
186
|
+
if (typeof text === "string" && Buffer.byteLength(text, "utf8") > MCP_JOB_TEXT_BYTES) throw new ToolArgError(`"text" is larger than ${MCP_JOB_TEXT_BYTES} bytes`)
|
|
186
187
|
const keep = args.keep
|
|
187
|
-
if (keep !== undefined && (!Array.isArray(keep) || !keep.every((k) => typeof k === "string"))) throw new
|
|
188
|
+
if (keep !== undefined && (!Array.isArray(keep) || !keep.every((k) => typeof k === "string"))) throw new ToolArgError('"keep" must be a list of paths')
|
|
188
189
|
const path = optional(args, "path")
|
|
189
190
|
const entry = optional(args, "entry")
|
|
190
191
|
const result = error
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// tool ever reads local files). Needs scope artifacts:read / artifacts:write for an app token; sessions create and
|
|
6
6
|
// read workspace-shared artifacts only (revising stays with the user in v1).
|
|
7
7
|
import type { Artifact, ArtifactKind, ArtifactRevision, Artifacts, BlobRef } from "../../sdk/src/artifacts.ts"
|
|
8
|
+
import { ToolArgError } from "./tool-args"
|
|
8
9
|
|
|
9
10
|
/** UTF-8 bytes of inline text one tool call may carry (bigger content: stage it, then pass `blobs`). */
|
|
10
11
|
export const MCP_INLINE_TEXT_BYTES = 256 * 1024
|
|
@@ -45,24 +46,24 @@ const FILE_ARGS = {
|
|
|
45
46
|
|
|
46
47
|
const need = (args: Args, key: string): string => {
|
|
47
48
|
const value = args[key]
|
|
48
|
-
if (typeof value !== "string" || !value.trim()) throw new
|
|
49
|
+
if (typeof value !== "string" || !value.trim()) throw new ToolArgError(`"${key}" is required (a non-empty string)`)
|
|
49
50
|
return value
|
|
50
51
|
}
|
|
51
52
|
const optional = (args: Args, key: string): string | undefined => {
|
|
52
53
|
const value = args[key]
|
|
53
54
|
if (value === undefined || value === null) return undefined
|
|
54
|
-
if (typeof value !== "string") throw new
|
|
55
|
+
if (typeof value !== "string") throw new ToolArgError(`"${key}" must be a string`)
|
|
55
56
|
return value.trim() ? value : undefined
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
/** The files of a publish/revise: inline text and/or blob references (one of them required). */
|
|
59
60
|
function files(args: Args, kind: ArtifactKind | undefined): { blobs?: BlobRef[]; upload?: { mediaType: string; data: string; path?: string }[]; entry?: string } {
|
|
60
61
|
const text = args.text
|
|
61
|
-
if (text !== undefined && typeof text !== "string") throw new
|
|
62
|
+
if (text !== undefined && typeof text !== "string") throw new ToolArgError('"text" must be a string')
|
|
62
63
|
const blobs = args.blobs
|
|
63
|
-
if (blobs !== undefined && (!Array.isArray(blobs) || !blobs.every((b) => typeof b === "object" && b !== null))) throw new
|
|
64
|
-
if (text === undefined && (blobs === undefined || blobs.length === 0)) throw new
|
|
65
|
-
if (text !== undefined && Buffer.byteLength(text, "utf8") > MCP_INLINE_TEXT_BYTES) throw new
|
|
64
|
+
if (blobs !== undefined && (!Array.isArray(blobs) || !blobs.every((b) => typeof b === "object" && b !== null))) throw new ToolArgError('"blobs" must be a list of blob references')
|
|
65
|
+
if (text === undefined && (blobs === undefined || blobs.length === 0)) throw new ToolArgError('give text or blobs (stage bigger content first and pass its reference)')
|
|
66
|
+
if (text !== undefined && Buffer.byteLength(text, "utf8") > MCP_INLINE_TEXT_BYTES) throw new ToolArgError(`text is larger than ${MCP_INLINE_TEXT_BYTES} bytes: stage it with uploadBlob and pass blobs`)
|
|
66
67
|
const path = optional(args, "path")
|
|
67
68
|
const entry = optional(args, "entry")
|
|
68
69
|
const mediaType = optional(args, "mediaType") ?? (kind && DEFAULT_MEDIA[kind]) ?? "text/plain"
|
|
@@ -126,7 +127,7 @@ export const artifactTools: readonly ArtifactTool[] = [
|
|
|
126
127
|
run: async (hub, args, signal) => {
|
|
127
128
|
const title = need(args, "title")
|
|
128
129
|
const kind = need(args, "kind") as ArtifactKind
|
|
129
|
-
if (!KINDS.includes(kind)) throw new
|
|
130
|
+
if (!KINDS.includes(kind)) throw new ToolArgError(`"kind" must be one of ${KINDS.join(", ")}`)
|
|
130
131
|
const body = files(args, kind)
|
|
131
132
|
const description = optional(args, "description")
|
|
132
133
|
const visibility = optional(args, "visibility") as "private" | "workspace" | undefined
|
package/packages/cli/src/mcp.ts
CHANGED
|
@@ -26,7 +26,8 @@ import { artifactTools } from "./artifacts-mcp"
|
|
|
26
26
|
import { artifactJobTools } from "./artifact-jobs-mcp"
|
|
27
27
|
import { teamIntakeTools } from "./team-intake-mcp"
|
|
28
28
|
import { teamTools } from "./teams-mcp"
|
|
29
|
-
import {
|
|
29
|
+
import { profileLifecycleTools } from "./profiles-mcp"
|
|
30
|
+
import { ToolArgError } from "./tool-args"
|
|
30
31
|
import { watchlistTools } from "./watchlist"
|
|
31
32
|
|
|
32
33
|
/** The protocol revisions the server speaks, newest first: a client's supported one is echoed, else the newest. */
|
|
@@ -48,7 +49,7 @@ type Id = string | number | null
|
|
|
48
49
|
type Args = Record<string, unknown>
|
|
49
50
|
|
|
50
51
|
/** A bad tool argument (an isError result, not a protocol error). */
|
|
51
|
-
|
|
52
|
+
const ArgError = ToolArgError
|
|
52
53
|
|
|
53
54
|
const need = (args: Args, key: string): string => {
|
|
54
55
|
const value = args[key]
|
|
@@ -1143,7 +1144,7 @@ export function createMcpServer(options: McpServerOptions): McpServer {
|
|
|
1143
1144
|
return controller.signal.aborted ? undefined : ok(id, toolResult(clean(text, token), false))
|
|
1144
1145
|
} catch (cause) {
|
|
1145
1146
|
if (controller.signal.aborted) return undefined // cancelled: no answer (MCP cancellation)
|
|
1146
|
-
const text = cause instanceof
|
|
1147
|
+
const text = cause instanceof ToolArgError ? `invalid: ${cause.message}` : cause instanceof CliError ? errorText(cause) : `${tool.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
1147
1148
|
return ok(id, toolResult(clean(text, token), true))
|
|
1148
1149
|
} finally {
|
|
1149
1150
|
if (inFlight.get(key(id)) === controller) inFlight.delete(key(id))
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// version can be restored with fromVersion); archive/restore (no new versions or pins) and pins (applied to a running
|
|
15
15
|
// session) keep the default destructiveHint. Changes send expectedRevision, so repeating one is idempotent.
|
|
16
16
|
import { sanitizeText } from "../../sdk/src/envelope.ts"
|
|
17
|
+
import { ToolArgError } from "./tool-args"
|
|
17
18
|
import type { Profiles } from "../../sdk/src/client.ts"
|
|
18
19
|
import type { ProfileCommand, ProfileContent, ProfileSkill, ProfileVersionSummary, ProfileView } from "../../sdk/src/types.ts"
|
|
19
20
|
|
|
@@ -53,9 +54,8 @@ const META_FIELDS = {
|
|
|
53
54
|
} as const
|
|
54
55
|
const SCOPES = "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."
|
|
55
56
|
|
|
56
|
-
/** A bad tool argument: mcp.ts answers it as `invalid: …` (an isError result). */
|
|
57
|
-
|
|
58
|
-
const ArgError = ProfileArgError
|
|
57
|
+
/** A bad tool argument: mcp.ts answers it as `invalid: …` (an isError result; tool-args.ts). */
|
|
58
|
+
const ArgError = ToolArgError
|
|
59
59
|
|
|
60
60
|
const need = (args: Args, key: string): string => {
|
|
61
61
|
const value = args[key]
|