@huynle/hub 0.8.1 → 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 +113 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/packages/claude-code/package.json +1 -1
- package/packages/cli/dist/hub-cli.js +87 -52
- 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 +83 -6
- 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 +7 -0
- package/src/central/concierge/context.ts +3 -1
- package/src/central/concierge/prompt.ts +7 -4
- package/src/central/http.ts +50 -21
- package/src/central/registry.ts +23 -3
- package/src/central/revert-windows.ts +66 -0
- package/src/central/spoke-ws.ts +6 -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 +76 -6
- package/src/spoke/spoke.ts +116 -36
- 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-mNzjlP_U.js → ArtifactPage-R0ZTpHca.js} +1 -1
- package/web/dist/assets/{ArtifactPane-CPQcuX7c.js → ArtifactPane-C9KrBna8.js} +1 -1
- package/web/dist/assets/{ArtifactWorkbench-NoFC5Sb2.js → ArtifactWorkbench-sP2mZccC.js} +1 -1
- package/web/dist/assets/{Library-LXHsP5Fw.js → Library-BXbJvTUS.js} +2 -2
- package/web/dist/assets/{ProfilesDestination-DhVVcAep.js → ProfilesDestination-D7XyLTJ7.js} +1 -1
- package/web/dist/assets/{TeamsPage-DBrO5hoL.js → TeamsPage-Dgxt8UI3.js} +1 -1
- package/web/dist/assets/{Watchtower-DxaJYQp_.js → Watchtower-DJHx6cWw.js} +2 -2
- package/web/dist/assets/index-g4V9mfSr.js +34 -0
- package/web/dist/assets/{live-BDq0AABg.js → live-OgQWPrSY.js} +1 -1
- package/web/dist/assets/{renderer2d-DAr4IHM8.js → renderer2d-DjEipyto.js} +1 -1
- package/web/dist/assets/{renderer3d-DWZj595u.js → renderer3d-B648jKGO.js} +1 -1
- package/web/dist/assets/{safe-area-JUb6OeI3.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/index.html +1 -1
- package/web/dist/assets/index-lIOfSYck.js +0 -34
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,118 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.2
|
|
4
|
+
|
|
5
|
+
A follow-up to 0.8.1. Voice keeps a request with pauses together as one turn (and fetches its cue phrases once per
|
|
6
|
+
page), and the Concierge's approvals without a grant
|
|
7
|
+
become Decisions for you (never rules first, never "always", the current Concierge only). OpenCode fork and revert
|
|
8
|
+
are completed (clear busy/not-found errors, agent messages wait while a revert is staged, Fork and open beside), the
|
|
9
|
+
profile skill/command check reads the session's own folder, the console names grants and Activity in plain words,
|
|
10
|
+
and a tool call with a large screenshot opens with a placeholder instead of failing. No migration is needed.
|
|
11
|
+
|
|
12
|
+
### Concierge
|
|
13
|
+
- **The board moves after the conversation** (V-01). The Concierge's system prompt now ends with its stable
|
|
14
|
+
instructions and notes; the board (whose text changes as it ages) is sent after the conversation, with the spoke's
|
|
15
|
+
own cache breakpoints and tools in name order, so a board update should no longer invalidate the cached
|
|
16
|
+
conversation. On the OmniRoute route measured on the preview the cache still misses after a 20–40 s gap
|
|
17
|
+
(about 6.9k tokens read), so the cost per turn is unchanged there for now; the cause is being investigated.
|
|
18
|
+
- **Concierge: "Recommended" only when it advises** (R-03). Its approve escalations no longer mark **Approve once**
|
|
19
|
+
as recommended by default: only an approve with `recommend: true` (hub tool, SDK `approve({recommend})`, route body
|
|
20
|
+
`recommend`) recommends it; otherwise its why is a note after the question and the permission card shows no
|
|
21
|
+
"recommends Approve". Other agents' escalations are unchanged.
|
|
22
|
+
- **Concierge: an approve without a grant becomes a Decision** (R-03). It was refused ("No grant lets this session
|
|
23
|
+
approve in this session") although its instructions promised a Decision; it now answers needs_user and opens the
|
|
24
|
+
escalation Decision at the hub's tier (only the current Concierge session, never a retired one or after a failed
|
|
25
|
+
identity read; never rules and self-tree still refuse; other agents unchanged).
|
|
26
|
+
- **Never rules come first for the Concierge's "always"** (R-03 verify). A Concierge approve with `always` on a request
|
|
27
|
+
a never rule (the hub's or its grant's) forbids opened a tap Decision offering "Approve always"; it is now
|
|
28
|
+
forbidden, with or without a grant. Concierge escalation Decisions never offer Approve always (once or deny only). Prompt
|
|
29
|
+
v8 says only approvals escalate (other verbs are proposed) and when to set `recommend`.
|
|
30
|
+
- **The board is never the user's words** (V-01 verify). On OpenAI-compatible and older Claude routes OpenCode
|
|
31
|
+
delivers the board (now after the conversation) as `<system-update>` text inside a user turn; prompt v8 tells the
|
|
32
|
+
Concierge the board comes from the hub, never from the user, and its text is data, never a request.
|
|
33
|
+
- **Approving a Concierge prompt proposal on a session with a staged revert answers 409 `conflict`** (not 500); the
|
|
34
|
+
Decision stays open and nothing is sent. Decision answers now use the agent API's status for every code
|
|
35
|
+
(`busy` 503 with `retryAfter`).
|
|
36
|
+
|
|
37
|
+
### Voice
|
|
38
|
+
- **Voice: one utterance per turn.** A pause no longer cuts the user off: after a segment ends, the endpointer waits
|
|
39
|
+
1.0 s (finished-looking text: STT adds a period to every fragment) or 1.6 s (text ending in "the", "and", a comma…)
|
|
40
|
+
before it sends, counted from the segment's end so STT speed no longer moves the threshold. Speech that starts
|
|
41
|
+
within that window joins the same utterance, and nothing is sent while the user is speaking. With the VAD's
|
|
42
|
+
~0.43 s to end a segment and ~0.25 s to start one, pauses up to about 1.1 s (finished) or 1.7 s (unfinished) stay
|
|
43
|
+
one turn (before: every pause of 0.45 s or more after finished-looking text was a separate turn, so one request
|
|
44
|
+
with two pauses became three model turns). Voice commands ("stop", "cancel"…) and the 60 s cap still send at
|
|
45
|
+
once; barge-in is unchanged. Finished-looking turns may start up to ~0.3 s later than before (STT already takes
|
|
46
|
+
~0.7–1 s of the window). Timings: `VOICE_TIMINGS.finishedHoldMs` / `holdMs` in `web/src/core/conversation-core.ts`.
|
|
47
|
+
- **Voice: cue phrases are fetched once per page.** The ~20 progress phrases ("Let me see." …, ~0.75 MB) are kept
|
|
48
|
+
in the page's memory per TTS voice, so a second Voice start fetches nothing, also where Cache Storage is not
|
|
49
|
+
available (plain-HTTP LAN origins, private mode). `/api/speak` is a POST, so HTTP caching does not apply; no hub
|
|
50
|
+
change.
|
|
51
|
+
|
|
52
|
+
### Profiles
|
|
53
|
+
- **Profile skills and commands give way to the right folder's local ones** (R-02): OpenCode 2.2.3's plugin
|
|
54
|
+
`skill.list` / `command.list` answer for the plugin instance's own folder whatever location is passed, so for
|
|
55
|
+
every session outside it (all `opencode serve --service` sessions: the first instance is `$HOME`) a profile's
|
|
56
|
+
local-precedence check read another folder's names. They are now read like Branch and Changes: through the plugin
|
|
57
|
+
instance of the session's own folder, else the own server's API (which honours the location), else not at all;
|
|
58
|
+
never another folder's. When they cannot be read (no own server to ask, a cold read past 5 s) the apply still
|
|
59
|
+
succeeds as `partial`: the instructions apply and the profile's skills and commands are not offered ("skills and
|
|
60
|
+
commands not offered: the session's local skills and commands could not be read"). Instructions-only profiles do
|
|
61
|
+
not read the names. A folder OpenCode is still loading is read until two reads 0.5 s apart agree, within 5 s.
|
|
62
|
+
|
|
63
|
+
### Fork and revert
|
|
64
|
+
- **OpenCode fork and revert, completed** (R-01; [docs/session-view.md](docs/session-view.md#fork-and-revert-a-message)):
|
|
65
|
+
- Restore and commit while the session runs answer 409 `busy` with a clear message (the hub checks first; OpenCode's
|
|
66
|
+
"Session is busy" maps to it), and a message that is not one of the session's user messages (or a session
|
|
67
|
+
OpenCode does not have) answers 404 `not_found`: never 502. Spokes put a protocol code on refusals (`Res.code`:
|
|
68
|
+
`busy`, `not_found`; additive, older peers ignore it, older spokes' texts are still recognised).
|
|
69
|
+
- **Agent messages wait while a revert is staged**: the hub holds every hub delivery to the session (msgs, asks,
|
|
70
|
+
replies, its primer) as pending and delivers them, in order, once the revert is restored or committed, so none of
|
|
71
|
+
them commits it; act prompts (agents and the Concierge under a grant, the user's act verb, approved Concierge
|
|
72
|
+
proposals) are refused 409 `conflict`. The banner says "agent messages wait" (decision 3q94ez94).
|
|
73
|
+
- Hub message rows of a committed revert's removed history stay hidden (the spoke reports the window as
|
|
74
|
+
`revertCommitted`; the hub keeps it and `…/messages` leaves those rows out); a revert staged and committed in the
|
|
75
|
+
terminal within one report drops the deleted rows without a reload; messages held while staged stay visible.
|
|
76
|
+
- Context and summary (context usage) leave reverted messages out; a fork that copied the source's hub primer gets
|
|
77
|
+
a hub note that the identity is not its own; revert file paths over 1000 characters count in `filesTotal`.
|
|
78
|
+
- **Fork and open beside**: a user message's actions open the fork in a pane next to the session.
|
|
79
|
+
- Hardening after review: a commit window first reported without its start is still kept (and failed writes are
|
|
80
|
+
retried); the spoke itself refuses hub messages while it knows of a staged revert (the hub keeps them pending);
|
|
81
|
+
an invalid `code` or a malformed `revertCommitted` is ignored instead of dropping the frame (and the spoke's
|
|
82
|
+
connection); an act prompt checks the grant before the staged-revert refusal (403 first) and audits it; expiry
|
|
83
|
+
notices name a staged revert; the fork note frames the source title as data and times out after 5 s; live tests
|
|
84
|
+
give their private servers none of the caller's `OPENCODE_*` / `HUB_*` variables.
|
|
85
|
+
|
|
86
|
+
### Console and MCP
|
|
87
|
+
- **Answered notes name the grant**: "Approved by @orchestrator · your grant for api, web" (its projects by folder
|
|
88
|
+
name) or "· the Concierge's grant", never "grant g_…"; the grant's ID is in the tooltip. The console loads your
|
|
89
|
+
grants (`GET /api/agent/grants`) with the stream, and again when an entry names a grant it does not know.
|
|
90
|
+
- **Settings → Agents & access → Activity says what each action did in plain words**: approved, denied, answered,
|
|
91
|
+
prompted, stopped, proposed an action, messaged, claimed / released a role, launched, stopped the launch of, staffed
|
|
92
|
+
/ unstaffed a team role, a message the team's rules refused, asked for / approved / denied a grant request (instead
|
|
93
|
+
of `team.staff`, `launch.stop`, `roles.release`, …); an unknown action reads as its words without dots. A
|
|
94
|
+
Concierge proposal says what it proposes: "proposed denying a permission request in @engineer", "proposed answering
|
|
95
|
+
a form in …", "proposed a prompt to …", "proposed stopping …".
|
|
96
|
+
- **A turn a denied permission stopped reads "Denied by you: the turn stopped" after a reload too**, not
|
|
97
|
+
"✕ Step interrupted": the OpenCode spoke marks the timeline's error row `declined: true` only in OpenCode's own
|
|
98
|
+
shape (a tool call failed `{type: "aborted", message: "The user declined this tool call"}` and the step
|
|
99
|
+
`{type: "aborted"}`, so a tool throwing that text cannot hide a real error), and the console names who denied it
|
|
100
|
+
from the agent audit (loaded once for such a turn; only a deny between that turn's start and the next turn's
|
|
101
|
+
counts), else "Denied: the turn stopped". A new turn denied in a session that already shows a stored denied turn
|
|
102
|
+
keeps its own note.
|
|
103
|
+
- **MCP argument errors read `invalid: …` in every tool family** (teams, team intake, artifacts, artifact jobs,
|
|
104
|
+
watchlist, profiles and the core tools share one `ToolArgError`), never `<tool> failed: …`.
|
|
105
|
+
- **Team staffing points a misplaced `key` at `idempotencyKey`**: `placement{key}` / `end{key}` in `team_staff` /
|
|
106
|
+
`team_replace` answer "did you mean idempotencyKey (the Idempotency-Key header)?" (staffing sets `launch.key`
|
|
107
|
+
itself), not "key (top level)".
|
|
108
|
+
- **Tool-call details with a screenshot open again** (M1). OpenCode keeps tool screenshots (`read` of a PNG, MCP
|
|
109
|
+
image results) as `data:` URLs inside the tool output, so their bytes counted against the ~750 KB detail limit and
|
|
110
|
+
a large one failed with "Timeline part is too large to display". The spoke now replaces each embedded image
|
|
111
|
+
(`data:…;base64,…` URLs anywhere in the detail, raw base64 image parts) with a placeholder such as
|
|
112
|
+
`[image/png, 1.2 MB — not shown]` before the limit is checked, so the input and text output show; the hub and the
|
|
113
|
+
console do the same for details from older spokes, so no image bytes reach the console in a tool detail. The limit
|
|
114
|
+
still holds for huge text. Showing images inline is a later release.
|
|
115
|
+
|
|
3
116
|
## 0.8.1
|
|
4
117
|
|
|
5
118
|
A fix-up release after 0.8.0. App and gateway messages keep waking idle sessions (per-conversation hops with a relay
|
package/README.md
CHANGED
|
@@ -681,7 +681,7 @@ allow patterns (else `needs_user`) → the hourly cap → the kill switches agai
|
|
|
681
681
|
**Escalation.** An approval outside the grant answers `needs_user` with the evaluation (`verdict`, `reason`, the
|
|
682
682
|
grants checked, and a `tier`) and opens a Decision for you: one per agent and request (asking again updates its
|
|
683
683
|
reason), with Approve once (or Approve always, when asked), Deny (safe), and the agent's `--why` as its
|
|
684
|
-
recommendation. The hub sets the tier, never the agent: `tap` (answered by a tap in the console, not from a
|
|
684
|
+
recommendation (the Concierge's only with `recommend: true`; a Concierge without a grant also escalates). The hub sets the tier, never the agent: `tap` (answered by a tap in the console, not from a
|
|
685
685
|
notification) for shell, `external_directory`, MCP and unknown tools and for anything near a never rule; `confirm`
|
|
686
686
|
otherwise. Your answer replies to the permission **as you** (no grant or kill switch applies to you); if the request
|
|
687
687
|
was answered elsewhere or ended first, the Decision closes and the agent's next try is `not_found`. A request's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huynle/hub",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Hub: a harness-agnostic central hub your AI agent harnesses connect to (OpenCode first). Follow, steer and stop sessions from your phone or browser, with notifications, optional hands-free Voice and a self-hostable server.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Huy Le",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huynle/hub-claude-code",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"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).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -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 = {}) {
|
|
@@ -3852,6 +3852,9 @@ function formAnswerProblem(v) {
|
|
|
3852
3852
|
}
|
|
3853
3853
|
var REVERT_FILES_MAX = 50;
|
|
3854
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
|
+
}
|
|
3855
3858
|
var FORWARDED_EVENTS = new Set([
|
|
3856
3859
|
"session.created",
|
|
3857
3860
|
"session.renamed",
|
|
@@ -3943,13 +3946,14 @@ function isLaunchCreate(req) {
|
|
|
3943
3946
|
}
|
|
3944
3947
|
var AGENT_NAME_MAX = 100;
|
|
3945
3948
|
var PERMISSION_MESSAGE_MAX = 1000;
|
|
3949
|
+
var RES_CODE_MAX = 40;
|
|
3946
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));
|
|
3947
3951
|
var SESSION_TOKEN_TEXT = new RegExp(`^[\\x21-\\x7e]{1,${SESSION_TOKEN_MAX_CHARS}}$`);
|
|
3948
3952
|
function isSessionToken(v) {
|
|
3949
3953
|
return isObj(v) && nonEmpty(v.sessionID) && v.sessionID.length <= 300 && str2(v.token) && SESSION_TOKEN_TEXT.test(v.token) && num(v.exp);
|
|
3950
3954
|
}
|
|
3951
3955
|
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);
|
|
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);
|
|
3953
3957
|
}
|
|
3954
3958
|
var REVERT_STATUS = new Set(["added", "deleted", "modified"]);
|
|
3955
3959
|
var count = (v) => Number.isSafeInteger(v) && v >= 0;
|
|
@@ -3957,7 +3961,7 @@ function isRevertFile(v) {
|
|
|
3957
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));
|
|
3958
3962
|
}
|
|
3959
3963
|
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;
|
|
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));
|
|
3961
3965
|
}
|
|
3962
3966
|
var TERMINAL_STATUS = new Set(["idle", "working", "blocked", "done", "unknown"]);
|
|
3963
3967
|
function isTerminal(v) {
|
|
@@ -4141,8 +4145,12 @@ function parseSpokeMessage(raw) {
|
|
|
4141
4145
|
switch (m.t) {
|
|
4142
4146
|
case "hello":
|
|
4143
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;
|
|
4144
|
-
case "sessions":
|
|
4145
|
-
|
|
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
|
+
}
|
|
4146
4154
|
case "terminals":
|
|
4147
4155
|
return m.herdr === undefined || isHerdrInfo(m.herdr) ? m : undefined;
|
|
4148
4156
|
case "event":
|
|
@@ -4150,13 +4158,34 @@ function parseSpokeMessage(raw) {
|
|
|
4150
4158
|
case "req":
|
|
4151
4159
|
return nonEmpty(m.id) && isSpokeRequest(m.req) ? m : undefined;
|
|
4152
4160
|
case "res":
|
|
4153
|
-
return isResFrame(m) ? m : undefined;
|
|
4161
|
+
return isResFrame(m) ? lenientRes(m) : undefined;
|
|
4154
4162
|
case "bye":
|
|
4155
4163
|
return isByeFrame(m) ? m : undefined;
|
|
4156
4164
|
default:
|
|
4157
4165
|
return;
|
|
4158
4166
|
}
|
|
4159
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
|
+
}
|
|
4160
4189
|
function parseServerMessage(raw) {
|
|
4161
4190
|
const m = parseFrame(raw);
|
|
4162
4191
|
if (!m)
|
|
@@ -4167,7 +4196,7 @@ function parseServerMessage(raw) {
|
|
|
4167
4196
|
case "req":
|
|
4168
4197
|
return nonEmpty(m.id) && isServerRequest(m.req) ? m : undefined;
|
|
4169
4198
|
case "res":
|
|
4170
|
-
return isResFrame(m) ? m : undefined;
|
|
4199
|
+
return isResFrame(m) ? lenientRes(m) : undefined;
|
|
4171
4200
|
case "bye":
|
|
4172
4201
|
return isByeFrame(m) ? m : undefined;
|
|
4173
4202
|
default:
|
|
@@ -5847,6 +5876,10 @@ function launchText(l) {
|
|
|
5847
5876
|
`);
|
|
5848
5877
|
}
|
|
5849
5878
|
|
|
5879
|
+
// packages/cli/src/tool-args.ts
|
|
5880
|
+
class ToolArgError extends Error {
|
|
5881
|
+
}
|
|
5882
|
+
|
|
5850
5883
|
// packages/cli/src/artifacts-mcp.ts
|
|
5851
5884
|
var MCP_INLINE_TEXT_BYTES = 256 * 1024;
|
|
5852
5885
|
var KINDS = ["markdown", "code", "image", "table", "chart", "dashboard", "html", "app", "file"];
|
|
@@ -5870,7 +5903,7 @@ var FILE_ARGS = {
|
|
|
5870
5903
|
var need = (args, key) => {
|
|
5871
5904
|
const value = args[key];
|
|
5872
5905
|
if (typeof value !== "string" || !value.trim())
|
|
5873
|
-
throw new
|
|
5906
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
5874
5907
|
return value;
|
|
5875
5908
|
};
|
|
5876
5909
|
var optional2 = (args, key) => {
|
|
@@ -5878,20 +5911,20 @@ var optional2 = (args, key) => {
|
|
|
5878
5911
|
if (value === undefined || value === null)
|
|
5879
5912
|
return;
|
|
5880
5913
|
if (typeof value !== "string")
|
|
5881
|
-
throw new
|
|
5914
|
+
throw new ToolArgError(`"${key}" must be a string`);
|
|
5882
5915
|
return value.trim() ? value : undefined;
|
|
5883
5916
|
};
|
|
5884
5917
|
function files(args, kind) {
|
|
5885
5918
|
const text = args.text;
|
|
5886
5919
|
if (text !== undefined && typeof text !== "string")
|
|
5887
|
-
throw new
|
|
5920
|
+
throw new ToolArgError('"text" must be a string');
|
|
5888
5921
|
const blobs = args.blobs;
|
|
5889
5922
|
if (blobs !== undefined && (!Array.isArray(blobs) || !blobs.every((b) => typeof b === "object" && b !== null)))
|
|
5890
|
-
throw new
|
|
5923
|
+
throw new ToolArgError('"blobs" must be a list of blob references');
|
|
5891
5924
|
if (text === undefined && (blobs === undefined || blobs.length === 0))
|
|
5892
|
-
throw new
|
|
5925
|
+
throw new ToolArgError("give text or blobs (stage bigger content first and pass its reference)");
|
|
5893
5926
|
if (text !== undefined && Buffer.byteLength(text, "utf8") > MCP_INLINE_TEXT_BYTES)
|
|
5894
|
-
throw new
|
|
5927
|
+
throw new ToolArgError(`text is larger than ${MCP_INLINE_TEXT_BYTES} bytes: stage it with uploadBlob and pass blobs`);
|
|
5895
5928
|
const path = optional2(args, "path");
|
|
5896
5929
|
const entry = optional2(args, "entry");
|
|
5897
5930
|
const mediaType = optional2(args, "mediaType") ?? (kind && DEFAULT_MEDIA[kind]) ?? "text/plain";
|
|
@@ -5958,7 +5991,7 @@ var artifactTools = [
|
|
|
5958
5991
|
const title = need(args, "title");
|
|
5959
5992
|
const kind = need(args, "kind");
|
|
5960
5993
|
if (!KINDS.includes(kind))
|
|
5961
|
-
throw new
|
|
5994
|
+
throw new ToolArgError(`"kind" must be one of ${KINDS.join(", ")}`);
|
|
5962
5995
|
const body = files(args, kind);
|
|
5963
5996
|
const description = optional2(args, "description");
|
|
5964
5997
|
const visibility = optional2(args, "visibility");
|
|
@@ -5997,7 +6030,7 @@ var KINDS2 = ["ask", "propose-edit", "run-action"];
|
|
|
5997
6030
|
var need2 = (args, key) => {
|
|
5998
6031
|
const value = args[key];
|
|
5999
6032
|
if (typeof value !== "string" || !value.trim())
|
|
6000
|
-
throw new
|
|
6033
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
6001
6034
|
return value;
|
|
6002
6035
|
};
|
|
6003
6036
|
var optional3 = (args, key) => {
|
|
@@ -6005,24 +6038,24 @@ var optional3 = (args, key) => {
|
|
|
6005
6038
|
if (value === undefined || value === null)
|
|
6006
6039
|
return;
|
|
6007
6040
|
if (typeof value !== "string")
|
|
6008
|
-
throw new
|
|
6041
|
+
throw new ToolArgError(`"${key}" must be a string`);
|
|
6009
6042
|
return value.trim() ? value : undefined;
|
|
6010
6043
|
};
|
|
6011
6044
|
var integer = (args, key) => {
|
|
6012
6045
|
const value = args[key];
|
|
6013
6046
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
6014
|
-
throw new
|
|
6047
|
+
throw new ToolArgError(`"${key}" is required (a whole number)`);
|
|
6015
6048
|
return value;
|
|
6016
6049
|
};
|
|
6017
6050
|
function handlerOf(args) {
|
|
6018
6051
|
const session = optional3(args, "session");
|
|
6019
6052
|
const machine = optional3(args, "machine");
|
|
6020
6053
|
if (session && machine)
|
|
6021
|
-
throw new
|
|
6054
|
+
throw new ToolArgError('give either "session" (an existing session) or "machine" + "repo" (a new handler session), not both');
|
|
6022
6055
|
if (session)
|
|
6023
6056
|
return { session };
|
|
6024
6057
|
if (!machine)
|
|
6025
|
-
throw new
|
|
6058
|
+
throw new ToolArgError('"machine" and "repo" are required to launch a handler session (or name an existing one with "session")');
|
|
6026
6059
|
const repo = need2(args, "repo");
|
|
6027
6060
|
const harness = optional3(args, "harness");
|
|
6028
6061
|
const model = optional3(args, "model");
|
|
@@ -6086,7 +6119,7 @@ var artifactJobTools = [
|
|
|
6086
6119
|
run: async (hub, args, signal) => {
|
|
6087
6120
|
const kind = optional3(args, "kind");
|
|
6088
6121
|
if (kind && !KINDS2.includes(kind))
|
|
6089
|
-
throw new
|
|
6122
|
+
throw new ToolArgError(`"kind" must be one of ${KINDS2.join(", ")}`);
|
|
6090
6123
|
const action = optional3(args, "action");
|
|
6091
6124
|
const apply = optional3(args, "apply");
|
|
6092
6125
|
const key = optional3(args, "key");
|
|
@@ -6155,12 +6188,12 @@ var artifactJobTools = [
|
|
|
6155
6188
|
const answer = optional3(args, "answer");
|
|
6156
6189
|
const text = args.text;
|
|
6157
6190
|
if (text !== undefined && typeof text !== "string")
|
|
6158
|
-
throw new
|
|
6191
|
+
throw new ToolArgError('"text" must be a string');
|
|
6159
6192
|
if (typeof text === "string" && Buffer.byteLength(text, "utf8") > MCP_JOB_TEXT_BYTES)
|
|
6160
|
-
throw new
|
|
6193
|
+
throw new ToolArgError(`"text" is larger than ${MCP_JOB_TEXT_BYTES} bytes`);
|
|
6161
6194
|
const keep = args.keep;
|
|
6162
6195
|
if (keep !== undefined && (!Array.isArray(keep) || !keep.every((k) => typeof k === "string")))
|
|
6163
|
-
throw new
|
|
6196
|
+
throw new ToolArgError('"keep" must be a list of paths');
|
|
6164
6197
|
const path = optional3(args, "path");
|
|
6165
6198
|
const entry = optional3(args, "entry");
|
|
6166
6199
|
const result = error ? { error } : text !== undefined || keep !== undefined ? {
|
|
@@ -6207,7 +6240,7 @@ var UPDATES = TEAM_INTAKE_STATUS_INPUTS;
|
|
|
6207
6240
|
var need3 = (args, key) => {
|
|
6208
6241
|
const value = args[key];
|
|
6209
6242
|
if (typeof value !== "string" || !value.trim())
|
|
6210
|
-
throw new
|
|
6243
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
6211
6244
|
return value;
|
|
6212
6245
|
};
|
|
6213
6246
|
var optional4 = (args, key) => {
|
|
@@ -6215,7 +6248,7 @@ var optional4 = (args, key) => {
|
|
|
6215
6248
|
if (value === undefined || value === null)
|
|
6216
6249
|
return;
|
|
6217
6250
|
if (typeof value !== "string")
|
|
6218
|
-
throw new
|
|
6251
|
+
throw new ToolArgError(`"${key}" must be a string`);
|
|
6219
6252
|
return value.trim() ? value : undefined;
|
|
6220
6253
|
};
|
|
6221
6254
|
var strings2 = (args, key) => {
|
|
@@ -6223,13 +6256,13 @@ var strings2 = (args, key) => {
|
|
|
6223
6256
|
if (value === undefined || value === null)
|
|
6224
6257
|
return;
|
|
6225
6258
|
if (!Array.isArray(value) || !value.every((v) => typeof v === "string"))
|
|
6226
|
-
throw new
|
|
6259
|
+
throw new ToolArgError(`"${key}" must be a list of strings`);
|
|
6227
6260
|
return value;
|
|
6228
6261
|
};
|
|
6229
6262
|
var oneOf4 = (args, key, allowed) => {
|
|
6230
6263
|
const value = optional4(args, key);
|
|
6231
6264
|
if (value !== undefined && !allowed.includes(value))
|
|
6232
|
-
throw new
|
|
6265
|
+
throw new ToolArgError(`"${key}" must be one of ${allowed.join(", ")}`);
|
|
6233
6266
|
return value;
|
|
6234
6267
|
};
|
|
6235
6268
|
function dueOf(args) {
|
|
@@ -6238,7 +6271,7 @@ function dueOf(args) {
|
|
|
6238
6271
|
return;
|
|
6239
6272
|
const ms = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : Number.NaN;
|
|
6240
6273
|
if (!Number.isSafeInteger(ms) || ms <= 0)
|
|
6241
|
-
throw new
|
|
6274
|
+
throw new ToolArgError(`"due" must be an ISO date/time or epoch milliseconds`);
|
|
6242
6275
|
return ms;
|
|
6243
6276
|
}
|
|
6244
6277
|
var line4 = (i) => `${i.id} ${i.status}${i.progress ? `/${i.progress}` : ""} ${i.priority} r${i.revision} ${sanitizeText(i.title).replace(/\s+/g, " ")}`;
|
|
@@ -6325,10 +6358,10 @@ ${intakeText(intake)}`;
|
|
|
6325
6358
|
run: async (hub, args, signal) => {
|
|
6326
6359
|
const status = oneOf4(args, "status", UPDATES);
|
|
6327
6360
|
if (!status)
|
|
6328
|
-
throw new
|
|
6361
|
+
throw new ToolArgError(`"status" is required: one of ${UPDATES.join(", ")}`);
|
|
6329
6362
|
const expectedRevision = args.expectedRevision;
|
|
6330
6363
|
if (expectedRevision !== undefined && expectedRevision !== null && (typeof expectedRevision !== "number" || !Number.isSafeInteger(expectedRevision) || expectedRevision < 0))
|
|
6331
|
-
throw new
|
|
6364
|
+
throw new ToolArgError(`"expectedRevision" must be the revision you read (or left out)`);
|
|
6332
6365
|
const reason = optional4(args, "reason");
|
|
6333
6366
|
const { intake, assignment } = await reportIntakeStatus(hub.teams, need3(args, "team"), need3(args, "intake"), { status, ...typeof expectedRevision === "number" ? { expectedRevision } : {}, ...reason ? { reason } : {} }, { signal });
|
|
6334
6367
|
return `${intake.id} is ${intake.status}${intake.progress ? ` (${intake.progress})` : ""}, revision ${intake.revision}${assignment ? `; assignment ${assignment.id} ${assignment.status}` : ""}`;
|
|
@@ -6421,9 +6454,7 @@ var LAUNCH = {
|
|
|
6421
6454
|
required: ["workspace"]
|
|
6422
6455
|
};
|
|
6423
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.";
|
|
6424
|
-
|
|
6425
|
-
class ArgError extends Error {
|
|
6426
|
-
}
|
|
6457
|
+
var ArgError = ToolArgError;
|
|
6427
6458
|
var need4 = (args, key) => {
|
|
6428
6459
|
const value = args[key];
|
|
6429
6460
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -6790,10 +6821,7 @@ var META_FIELDS = {
|
|
|
6790
6821
|
projects: { ...STRINGS2, description: "Project names the profile is meant for (≤ 16)." }
|
|
6791
6822
|
};
|
|
6792
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.";
|
|
6793
|
-
|
|
6794
|
-
class ProfileArgError extends Error {
|
|
6795
|
-
}
|
|
6796
|
-
var ArgError2 = ProfileArgError;
|
|
6824
|
+
var ArgError2 = ToolArgError;
|
|
6797
6825
|
var need5 = (args, key) => {
|
|
6798
6826
|
const value = args[key];
|
|
6799
6827
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -7048,7 +7076,7 @@ var STRINGS3 = { type: "array", items: { type: "string" } };
|
|
|
7048
7076
|
var need6 = (args, key) => {
|
|
7049
7077
|
const value = args[key];
|
|
7050
7078
|
if (typeof value !== "string" || !value.trim())
|
|
7051
|
-
throw new
|
|
7079
|
+
throw new ToolArgError(`"${key}" is required (a non-empty string)`);
|
|
7052
7080
|
return value;
|
|
7053
7081
|
};
|
|
7054
7082
|
var flag = (args, key, fallback) => {
|
|
@@ -7056,7 +7084,7 @@ var flag = (args, key, fallback) => {
|
|
|
7056
7084
|
if (value === undefined || value === null)
|
|
7057
7085
|
return fallback;
|
|
7058
7086
|
if (typeof value !== "boolean")
|
|
7059
|
-
throw new
|
|
7087
|
+
throw new ToolArgError(`"${key}" must be true or false`);
|
|
7060
7088
|
return value;
|
|
7061
7089
|
};
|
|
7062
7090
|
var UPDATE_FIELDS = ["autoActive", "activeWithinHours", "usePins", "sessions", "projects", "labels", "muted", "mutedProjects"];
|
|
@@ -7088,10 +7116,10 @@ var watchlistTools = [
|
|
|
7088
7116
|
run: async (hub, args, signal) => {
|
|
7089
7117
|
const expected = args.expectedRevision;
|
|
7090
7118
|
if (typeof expected !== "number" || !Number.isSafeInteger(expected) || expected < 0)
|
|
7091
|
-
throw new
|
|
7119
|
+
throw new ToolArgError(`"expectedRevision" is required (the revision you read)`);
|
|
7092
7120
|
const unknown = Object.keys(args).filter((k) => k !== "expectedRevision" && !UPDATE_FIELDS.includes(k));
|
|
7093
7121
|
if (unknown.length)
|
|
7094
|
-
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`);
|
|
7095
7123
|
const patch = {};
|
|
7096
7124
|
for (const field of UPDATE_FIELDS)
|
|
7097
7125
|
if (args[field] !== undefined && args[field] !== null)
|
|
@@ -7137,9 +7165,7 @@ ${watchlistConfigText(r.config)}`;
|
|
|
7137
7165
|
// packages/cli/src/mcp.ts
|
|
7138
7166
|
var MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
7139
7167
|
var RPC = { parse: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602, internal: -32603 };
|
|
7140
|
-
|
|
7141
|
-
class ArgError3 extends Error {
|
|
7142
|
-
}
|
|
7168
|
+
var ArgError3 = ToolArgError;
|
|
7143
7169
|
var need7 = (args, key) => {
|
|
7144
7170
|
const value = args[key];
|
|
7145
7171
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -8324,7 +8350,7 @@ function createMcpServer(options) {
|
|
|
8324
8350
|
} catch (cause) {
|
|
8325
8351
|
if (controller.signal.aborted)
|
|
8326
8352
|
return;
|
|
8327
|
-
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)}`;
|
|
8328
8354
|
return ok2(id, toolResult(clean(text, token), true));
|
|
8329
8355
|
} finally {
|
|
8330
8356
|
if (inFlight.get(key(id)) === controller)
|
|
@@ -8761,7 +8787,7 @@ import { arch as osArch, platform as osPlatform } from "node:os";
|
|
|
8761
8787
|
// packages/node/package.json
|
|
8762
8788
|
var package_default2 = {
|
|
8763
8789
|
name: "@huynle/hub-node",
|
|
8764
|
-
version: "0.8.
|
|
8790
|
+
version: "0.8.2",
|
|
8765
8791
|
private: true,
|
|
8766
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).",
|
|
8767
8793
|
license: "MIT",
|
|
@@ -9853,7 +9879,7 @@ import path13 from "node:path";
|
|
|
9853
9879
|
// packages/runtime-openshell/package.json
|
|
9854
9880
|
var package_default3 = {
|
|
9855
9881
|
name: "@huynle/hub-runtime-openshell",
|
|
9856
|
-
version: "0.8.
|
|
9882
|
+
version: "0.8.2",
|
|
9857
9883
|
private: true,
|
|
9858
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).",
|
|
9859
9885
|
license: "MIT",
|
|
@@ -11395,6 +11421,15 @@ function wsURL2(server) {
|
|
|
11395
11421
|
var defaultSocket2 = (url, headers) => new WebSocket(url, { headers });
|
|
11396
11422
|
var message2 = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
11397
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
|
+
|
|
11398
11433
|
class SpokeConnection {
|
|
11399
11434
|
options;
|
|
11400
11435
|
state = "off";
|
|
@@ -11564,7 +11599,7 @@ class SpokeConnection {
|
|
|
11564
11599
|
try {
|
|
11565
11600
|
reply = { t: "res", id, ok: true, result: await this.options.handle(req) };
|
|
11566
11601
|
} catch (cause) {
|
|
11567
|
-
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 } : {} };
|
|
11568
11603
|
}
|
|
11569
11604
|
this.send(reply);
|
|
11570
11605
|
}
|
|
@@ -13528,7 +13563,7 @@ import path22 from "node:path";
|
|
|
13528
13563
|
// packages/claude-code/package.json
|
|
13529
13564
|
var package_default4 = {
|
|
13530
13565
|
name: "@huynle/hub-claude-code",
|
|
13531
|
-
version: "0.8.
|
|
13566
|
+
version: "0.8.2",
|
|
13532
13567
|
private: true,
|
|
13533
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).",
|
|
13534
13569
|
license: "MIT",
|
|
@@ -13817,7 +13852,7 @@ import path25 from "node:path";
|
|
|
13817
13852
|
// packages/preset/package.json
|
|
13818
13853
|
var package_default5 = {
|
|
13819
13854
|
name: "@huynle/hub-preset",
|
|
13820
|
-
version: "0.8.
|
|
13855
|
+
version: "0.8.2",
|
|
13821
13856
|
private: true,
|
|
13822
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).",
|
|
13823
13858
|
license: "MIT",
|
|
@@ -14342,7 +14377,7 @@ import { openSync as openSync5, closeSync as closeSync5, readFileSync as readFil
|
|
|
14342
14377
|
// packages/codex/package.json
|
|
14343
14378
|
var package_default6 = {
|
|
14344
14379
|
name: "@huynle/hub-codex",
|
|
14345
|
-
version: "0.8.
|
|
14380
|
+
version: "0.8.2",
|
|
14346
14381
|
private: true,
|
|
14347
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).",
|
|
14348
14383
|
license: "MIT",
|