@huynle/hub 0.10.1 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +36 -8
  3. package/package.json +1 -1
  4. package/packages/claude-code/package.json +1 -1
  5. package/packages/claude-code/src/sessions.ts +4 -1
  6. package/packages/cli/dist/hub-cli.js +851 -466
  7. package/packages/cli/package.json +1 -1
  8. package/packages/cli/src/cli.ts +12 -7
  9. package/packages/cli/src/decisions.ts +52 -4
  10. package/packages/cli/src/format.ts +9 -0
  11. package/packages/cli/src/grants-mcp.ts +65 -0
  12. package/packages/cli/src/mcp-operations.ts +49 -0
  13. package/packages/cli/src/mcp.ts +26 -4
  14. package/packages/cli/src/subscriptions-mcp.ts +76 -0
  15. package/packages/cli/src/team-intake-mcp.ts +2 -1
  16. package/packages/cli/src/teams-mcp.ts +9 -3
  17. package/packages/codex/package.json +1 -1
  18. package/packages/codex/src/sessions.ts +14 -3
  19. package/packages/node/package.json +1 -1
  20. package/packages/preset/package.json +1 -1
  21. package/packages/protocol/package.json +1 -1
  22. package/packages/protocol/schema/api/v1/_root.json +119 -0
  23. package/packages/protocol/schema/api/v1/account.json +419 -0
  24. package/packages/protocol/schema/api/v1/decisions.json +635 -0
  25. package/packages/protocol/schema/api/v1/discovery.json +110 -0
  26. package/packages/protocol/schema/api/v1/events.json +679 -0
  27. package/packages/protocol/schema/api/v1/session-actions.json +660 -0
  28. package/packages/protocol/schema/api/v1/sessions.json +2055 -0
  29. package/packages/protocol/schema/api/v1/speech.json +492 -0
  30. package/packages/protocol/schema/api/v1/unspecified.json +282 -0
  31. package/packages/protocol/schema/app-delivery/v1.schema.json +67 -5
  32. package/packages/protocol/schema/app-front/v1.schema.json +7 -3
  33. package/packages/protocol/src/api-v1.ts +8 -0
  34. package/packages/protocol/src/index.ts +2 -0
  35. package/packages/protocol/src/teams.ts +16 -0
  36. package/packages/protocol/src/voice-recording.ts +39 -2
  37. package/packages/runtime-openshell/image/entry.sh +3 -1
  38. package/packages/runtime-openshell/image/start.sh +3 -1
  39. package/packages/runtime-openshell/package.json +1 -1
  40. package/packages/runtime-sdk/package.json +1 -1
  41. package/packages/sdk/package.json +1 -1
  42. package/packages/sdk/src/app-front.ts +2 -2
  43. package/packages/sdk/src/ask-fields.ts +76 -0
  44. package/packages/sdk/src/client.ts +24 -2
  45. package/packages/sdk/src/envelope.ts +3 -2
  46. package/packages/sdk/src/index.ts +1 -0
  47. package/packages/sdk/src/team-intake.ts +6 -0
  48. package/packages/sdk/src/teams.ts +4 -0
  49. package/packages/sdk/src/types.ts +36 -4
  50. package/packages/sdk/src/wake.ts +9 -4
  51. package/packages/spoke-sdk/package.json +1 -1
  52. package/src/central/agent/agent-limits.ts +185 -0
  53. package/src/central/agent/app-decisions.ts +6 -5
  54. package/src/central/agent/decisions.ts +136 -9
  55. package/src/central/agent/delivery.ts +164 -22
  56. package/src/central/agent/errors.ts +2 -1
  57. package/src/central/agent/events.ts +20 -2
  58. package/src/central/agent/grant-routes.ts +14 -1
  59. package/src/central/agent/grants.ts +23 -0
  60. package/src/central/agent/launch-engine.ts +9 -6
  61. package/src/central/agent/message-limits.ts +6 -0
  62. package/src/central/agent/message-routes.ts +33 -6
  63. package/src/central/agent/messages.ts +7 -0
  64. package/src/central/agent/placement.ts +4 -2
  65. package/src/central/agent/read-routes.ts +5 -0
  66. package/src/central/agent/routes.ts +13 -0
  67. package/src/central/agent/subscriptions.ts +15 -4
  68. package/src/central/api-v1.ts +115 -0
  69. package/src/central/artifacts/store.ts +4 -2
  70. package/src/central/concierge/board-results.ts +2 -1
  71. package/src/central/concierge/notifier.ts +4 -2
  72. package/src/central/config.ts +24 -3
  73. package/src/central/health/classify.ts +209 -0
  74. package/src/central/http.ts +380 -36
  75. package/src/central/recording/export.ts +57 -3
  76. package/src/central/recording/index.ts +236 -17
  77. package/src/central/recording/ingest.ts +11 -2
  78. package/src/central/recording/routes.ts +61 -7
  79. package/src/central/recording/schema.ts +68 -3
  80. package/src/central/recording/settings.ts +19 -5
  81. package/src/central/recording/store.ts +19 -0
  82. package/src/central/recording/taps.ts +45 -1
  83. package/src/central/registry.ts +34 -3
  84. package/src/central/speech/openai.ts +4 -4
  85. package/src/central/speech/openrouter.ts +4 -4
  86. package/src/central/speech/provider.ts +32 -1
  87. package/src/central/speech/service.ts +6 -5
  88. package/src/central/teams/budgets.ts +144 -0
  89. package/src/central/teams/hop-stalls.ts +80 -0
  90. package/src/central/teams/intake.ts +38 -6
  91. package/src/central/teams/involvement.ts +95 -0
  92. package/src/central/teams/item-decisions.ts +60 -0
  93. package/src/central/teams/item-eligibility.ts +35 -0
  94. package/src/central/teams/item-stalls.ts +87 -0
  95. package/src/central/teams/loops.ts +40 -0
  96. package/src/central/teams/policy.ts +10 -0
  97. package/src/central/teams/progress.ts +154 -0
  98. package/src/central/teams/routes.ts +2 -0
  99. package/src/central/teams/seats.ts +3 -3
  100. package/src/central/teams/staffing.ts +5 -0
  101. package/src/central/teams/store.ts +20 -1
  102. package/src/central/teams/validation.ts +16 -2
  103. package/src/central/workspaces/coordinator.ts +3 -1
  104. package/src/spoke/hub-tool.ts +26 -11
  105. package/web/dist/assets/{ArtifactPage-CsPjam8D.js → ArtifactPage-TbFJi59n.js} +1 -1
  106. package/web/dist/assets/{ArtifactPane-B9weO8L6.js → ArtifactPane-7D-M-Ich.js} +1 -1
  107. package/web/dist/assets/{ArtifactWorkbench-DzKhMs3l.js → ArtifactWorkbench-DlCLEjut.js} +1 -1
  108. package/web/dist/assets/{Diagram-CuRIffka.js → Diagram-PvOPetOI.js} +1 -1
  109. package/web/dist/assets/{Library-rv5DGtrm.js → Library-Cdi83pOV.js} +2 -2
  110. package/web/dist/assets/{ProfilesDestination-DUuQ9jmd.js → ProfilesDestination-cryC7P6j.js} +1 -1
  111. package/web/dist/assets/TeamsPage-B4xrlGhp.js +1 -0
  112. package/web/dist/assets/Watchtower-60B1JX_J.js +2 -0
  113. package/web/dist/assets/{artifacts-But87Jfo.js → artifacts-DlQEYmDl.js} +1 -1
  114. package/web/dist/assets/index-DA0gKIzv.js +33 -0
  115. package/web/dist/assets/{index-rW6LKQj1.css → index-Dyq-jBeE.css} +1 -1
  116. package/web/dist/assets/live-B2IfKm37.js +5 -0
  117. package/web/dist/assets/{live-BUofsQpz.css → live-De-kd3eW.css} +1 -1
  118. package/web/dist/assets/{pane-layout-BW0RdX5a.js → pane-layout-BAuu_pA3.js} +1 -1
  119. package/web/dist/assets/{renderer2d-DyANQ1WS.js → renderer2d-CsZnF3_o.js} +1 -1
  120. package/web/dist/assets/{renderer3d-vYYKFAJq.js → renderer3d-BIMA0wFn.js} +2 -2
  121. package/web/dist/assets/{safe-area-B2Bk__FK.js → safe-area-dtrnw9NF.js} +1 -1
  122. package/web/dist/assets/{silero-worker-e802lB5T.js → silero-worker-CC2etKS3.js} +1 -1
  123. package/web/dist/assets/{source-C-De8L3s.js → source-GYayxUxS.js} +1 -1
  124. package/web/dist/assets/{toast-CXTwTvDw.js → toast-q818mjUF.js} +1 -1
  125. package/web/dist/index.html +5 -5
  126. package/web/dist/assets/TeamsPage-D_17MkvN.js +0 -1
  127. package/web/dist/assets/Watchtower-DlrNOJ7X.js +0 -2
  128. package/web/dist/assets/index-bX8B6NLo.js +0 -33
  129. package/web/dist/assets/live-BGLA-peO.js +0 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,85 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.1
4
+
5
+ Agents can send structured asks to `@me` (choices, kind, recommendation) from the OpenCode `hub` tool, MCP, the SDK
6
+ and the CLI, and teams tag work items. Also since 0.11.0: team autonomy phase 2 (per-item wake budgets with an
7
+ Extend/Stop Decision, loop detection, progress resets), session health on session reads, a configurable hub hop
8
+ ceiling, the team Renew button, live agent-limit pins and a startup retry, and typed wake-skip limits. Update the
9
+ plugin (npm `@huynle/hub`) and restart OpenCode to get the new hub tool.
10
+
11
+ - Teams: each team card and team page shows `hops N/limit`, a `stalled: hop cap` badge and a **Renew** button that
12
+ resets every current seat's hop depth and closes an open Renew Decision (console cookie only, audited as
13
+ `team.hops.reset` kind `manual`); `GET /api/teams/:id/hops` reads the depths.
14
+ - Agents can send structured asks to `@me`: the OpenCode `hub` tool and the MCP `ask` tool take `options`,
15
+ `recommendation`, `urgency`, `tier`, `kind` and `choices`; the SDK's `AskOptions` take `choices` and `kind` (and
16
+ `answerDecision` takes `picks`); the CLI adds `hub ask @me --choice "D1=Label:a=Option*,b=Option" --kind plan`.
17
+ - Decisions: an ask to `@me` may carry structured `choices` (1–12 groups of 2–6 options, at most one recommended
18
+ per group; not with `options`) and a `kind` (question, plan, merge, release). The user answers with `picks`, one
19
+ option per group, or with text alone; the asker reads one `D1: label [a]` line per group. The API schemas and the
20
+ recording events `decision.answered` and `decision.voice` carry the picks.
21
+ - Hub text sanitising (message text, labels, Decision questions and choices) now also removes every Unicode format
22
+ character (`\p{Cf}`), including TAG characters (U+E0000–E007F) and the soft hyphen. Side effect: emoji built
23
+ from tag sequences, such as the England and Scotland flags, show as a plain black flag. Zero-width joiners and
24
+ directional marks (U+200B–200F) were already removed, so compound emoji split up and RTL marks are dropped in
25
+ labels as before.
26
+ - Wake-skip replies now carry `wakeLimit` for the effective team/hub hop cap or recipient wake budget. The CLI, MCP
27
+ tools and OpenCode hub tool show that number instead of a fixed default; older hubs show a number-free reason.
28
+
29
+ ## 0.11.0
30
+
31
+ Voice recording's Developer mode and the richer `capture.v2` recording events (below), plus the fixes and team
32
+ tools merged since 0.10.1: a launch's `end.cleanup: keep` is honoured when an ended host launch is reconciled, and
33
+ the applied cleanup policy is stored before a launch ends, so kept, `needs_user` and lost workspaces are no longer
34
+ deleted later; a per-team agent message hop limit with a user-only Renew Decision when a team chain stalls (and
35
+ orphaned stall claims expire); team member names show without graph interactions; holder-scoped grant tools and
36
+ read-only subscription, session-model and machine/node tools over MCP; the console's Launch new member form sets
37
+ the model, end policy and timeout; the sandbox entry publishes its PID atomically, so a second start no longer
38
+ starts a duplicate harness. CI cancels superseded test runs without interrupting an image release and can reuse a
39
+ PR's passing result for the same tree.
40
+
41
+ ### Voice recording (developer)
42
+ - **Developer mode** (Settings → Voice, per user, off by default; user request, 2026-11-10). One more field of the
43
+ recording settings record (`developer`, the same revision and `PUT /api/settings/recording {developer}`): on turns
44
+ recording on, recording off turns it off, older consoles' `{enabled}` keep it. While a developer recording runs it
45
+ also keeps, behind the new hub feature `dev.v1` (start answer `developer: true`):
46
+ - **raw microphone audio clips** (3 s before to 2 s after) around near-misses and marks, from a ring buffer of the
47
+ detector's input (`audio.clip`, `PUT/GET /api/recordings/:id/clips/:n`, ≤ 200 per recording);
48
+ - the detector's **frame traces** around speech starts, ends and near-misses (`vad.trace`);
49
+ - **device and network facts** on start and on change (`device.info`: audio devices, mic track processing,
50
+ Network Information, visibility, wake lock, battery), **Resource Timing** of the voice routes
51
+ (`resource.timing`) and **console errors and warnings** (`console.log`, scrubbed, rate-limited);
52
+ - hub side: per turn the **model, provider, agent and context token counts** and each tool call's start and end
53
+ (`turn.detail`; never prompt text), **request lines** of the voice routes (`request`), and the **reply audio**
54
+ kept from `/api/speak` as it streams (`reply.audio`, `GET /api/recordings/:id/replies/:n`).
55
+ The extras count against the same caps; their audio stops first (50 MiB below the user's cap, 15 MiB below the
56
+ recording's; `cap.reached {what: "extras"}`), segments and events go on. Exports add `clips/` and `replies/`,
57
+ listed in the manifest. Turning Developer mode off stops the extras at once. The indicator says
58
+ "● Recording · developer"; `scripts/voice-eval.ts` reports models per turn, devices, routes, clips and errors.
59
+ - **More of what happens is recorded**, so "I talked and nothing happened" and lost words leave a trace. New console
60
+ events, sent only to a hub whose recording start lists the new feature `capture.v2` (an older hub refuses a whole
61
+ batch with an unknown kind, value or field, so toward it the console drops them before they take a `seq`):
62
+ - `vad.near`: what the speech detector almost took for speech: an onset abandoned before it started
63
+ (`short-onset`, `voiced-ratio`) or ≥ 150 ms just under the start threshold (`below-threshold`), with its peak, the
64
+ threshold that applied and whether the echo guard was up (the agent speaking). At most one per 500 ms of audio
65
+ and 20 per minute (the rest are counted in the next summary).
66
+ - `vad.summary`: every 10 s of listening, the detector's frames, max and p90 speech probability, loudest frame,
67
+ noise floor, frames over the start and echo-guard thresholds, time under the echo guard, mode and sensitivity
68
+ (quiet windows after a quiet summary are skipped).
69
+ - `stt.received` adds `junkReason` (why a transcript was dropped as noise), `held` (the text held at that moment)
70
+ and `speechMs`.
71
+ - `stt.slow`: a transcription still unanswered after 2 s (once per segment).
72
+ - `mark`: a bookmark. Say **"mark"**, **"mark that"** or **"bookmark"** alone (not sent to the agent; the held
73
+ text waits on), or press the Voice bar's new **Mark** button (shown only while recording on such a hub). The
74
+ Voice bar notes "Marked".
75
+ - The hub's `stt.result` adds the provider's `httpStatus`, `responseBytes` and `headersMs` (time to the response's
76
+ headers; `providerMs` stays the total).
77
+ - **`scripts/voice-eval.ts`** reads a recording export (zip or folder) and reports, across all its recordings,
78
+ per-turn latency by stage (median / p90 / max, by recording kind and device), dropped segments with their reason
79
+ and held text, early sends, STT stalls over 3 s with the provider's share, near-misses with the echo-guard share
80
+ and marks with ±15 s of context (`--json`, `--no-text`). Older exports work: what they lack is reported as not
81
+ recorded.
82
+
3
83
  ## 0.10.1
4
84
 
5
85
  Clicking the composer's Model chip changes the session's model. No migration is needed. Machines need the new
package/README.md CHANGED
@@ -88,6 +88,9 @@ Formerly **opencode-voice** and, briefly, **opencode-hub**
88
88
  mode), staffed with sessions from any machine. The console's **Teams** page lists them and shows each team's
89
89
  members, work (live intake status) and communication ([docs/teams.md](docs/teams.md),
90
90
  [intake](docs/teams-intake.md)).
91
+ Team members can attach an open intake item to a message with `hub msg|ask --item itk_…`, MCP `msg`/`ask`'s `item`,
92
+ or the OpenCode hub tool's `item`. Both endpoints must hold current seats in that item's team; replies inherit it
93
+ while the item stays open.
91
94
  - **Watchtower** (formerly Bird's-eye view): a 3D, 2D or list picture of machines, sessions, teams and live
92
95
  message flow, with a team editor ([docs/watchtower.md](docs/watchtower.md)).
93
96
  - **Media in sessions**: tool screenshots, attachments and image files a reply names show in the transcript (with a
@@ -312,7 +315,7 @@ The hub serves the web app, pairs devices, enrolls machines, and does push and (
312
315
  |---|---|---|
313
316
  | **`/hub` → On this computer** | Local use; nothing to install (plugin from npm) | started by the plugin with OpenCode's own runtime (`BUN_BE_BUN=1 opencode bin/hub.ts`), or `bun` when installed |
314
317
  | **Standalone binary** | Any server or laptop, no runtime needed | `hub-<os>-<arch>` from the [releases](https://github.com/huynle/hub-releases/releases) (the source repository is private; releases are mirrored to the public `huynle/hub-releases`) |
315
- | **Docker** | Servers | `ghcr.io/huynle/hub` (linux/amd64, linux/arm64) |
318
+ | **Docker** | Servers | `ghcr.io/huynle/hub` (linux/amd64, linux/arm64 for `v*` releases and `latest`; per-commit `git-…` tags from `main` are linux/amd64 only) |
316
319
  | **bunx / from source** | Bun users, development | `bunx --package @huynle/hub hub-server` · `bun bin/hub.ts` |
317
320
  | **Kubernetes (cluster mode)** | Several pods on Postgres + Valkey, rolling updates | [docs/cluster.md](docs/cluster.md), manifests in [`deploy/k8s/`](deploy/k8s/) |
318
321
 
@@ -611,6 +614,13 @@ hand-made token: `GET /connect/authorize` (PKCE S256) → the hub's consent page
611
614
  `POST /connect/token` → a 1-hour `hca_` access token and a rotating `hcr_` refresh token; `POST /connect/revoke` ends
612
615
  it. Clients are registered in Settings → Agents & access. See [docs/app-front.md](docs/app-front.md#connect-an-app).
613
616
 
617
+ An administrator can adjust the hub-wide hop cap, its ceiling (`agents.hopCeiling`, default 100, at most 10,000; a
618
+ raise asks for confirmation), launch limits, per-item wake/time budgets and no-progress loop threshold under
619
+ **Settings → Admin → Agent limits** without restarting the hub. `hub.json` remains each limit group's default until a console value changes it; the UI shows each source separately. Agents may read the effective
620
+ limits with `hub mcp` tool `agent_limits` but cannot change them; see [team hop limits](docs/teams-intake.md#team-message-hop-cap-and-renew)
621
+ and [per-item budgets and progress](docs/teams-intake.md#per-item-budgets-attribution-and-progress), plus
622
+ [launch caps](docs/launches.md#how-the-caps-count).
623
+
614
624
  **Hub as an app's AI layer (app front).** A web app can use Hub as its AI layer for its users: connect (above), launch
615
625
  helper sessions, follow and talk to them (a conversation stream, voice turns through the voice front), speak and
616
626
  transcribe with the user's own speech provider, and answer the everyday confirm-tier Decisions its own sessions ask.
@@ -705,16 +715,19 @@ sessions or apps; see [Decisions and `@me`](#agent-identity-agent-api) for the r
705
715
 
706
716
  **Limits.** Per 10 minutes: 20 messages per sender→recipient pair and 300 per user (`rate_limited`, exit 6, with
707
717
  `retryAfter`), and 30 turns started per recipient (more messages are still delivered, without waking it). A chain of
708
- messages between sessions may run 8 hops without a human turn; past that, messages are delivered without waking and
718
+ messages between sessions may run up to the hub's configured hop limit (`agents.hopLimit` in Settings → Admin,
719
+ default 8, at most the hop ceiling; a team's
720
+ limit may be lower) without a human turn; past that, messages are delivered without waking and
709
721
  you are notified once. Typing into a session (console, Voice, the harness) starts its chain over. Apps (an app
710
722
  token, and the hub's own Concierge, workspace coordinator and team intake) count hops per conversation: a new `msg`
711
723
  or `ask` from an app starts at hop 1 and replies to an app do not count against it, so an app's 5th (or 50th) ask
712
724
  still wakes the session it talks to. Two guards keep apps from restarting chains: a reply or decline counts up from
713
725
  the message it answers (reply ping-pong through an app is capped like any other chain), and for 5 minutes after a
714
726
  session sends an app something, the app's new messages to *other* sessions continue from that hop (a relay A → app →
715
- B → app → A reaches the cap after about 8 hops; spaced-out work starts at hop 1 again). An app answering one session
727
+ B → app → A reaches the configured cap; spaced-out work starts at hop 1 again). An app answering one session
716
728
  with new messages forever is held by the pair limit and the wake budget. A team intake submitted by a session
717
- continues that session's chain. A send's answer says when and why it did not wake (`woke`, `wakeSkipped`); the CLI,
729
+ continues that session's chain. A send's answer says when and why it did not wake (`woke`, `wakeSkipped`, and
730
+ `wakeLimit` for the effective hop or wake budget); the CLI,
718
731
  `hub mcp` and the OpenCode `hub` tool print it.
719
732
 
720
733
  **Never give agents an app token.** A session that holds one (in its environment, an MCP config, a gateway) can send
@@ -819,6 +832,13 @@ the requester and may only be the requester itself or a reserved role it holds.
819
832
  the grant; approving creates it as yours, and the requester is told the grant id in a reply. One open request per
820
833
  requester, 5 an hour.
821
834
 
835
+ `hub mcp` exposes `grant_request` (the same request route and limits), `grant_list`, `grant_show` and
836
+ `grant_revoke` (grants held by the calling session or app; apps need `messages:read` to list/show and
837
+ `messages:send` to revoke), and
838
+ `grant_create` (user device credential only; an agent must request and wait for your tap). A user credential on
839
+ the holder-scoped tools has no holder; manage all grants in the console or with `hub grant` instead. Pinning a
840
+ profile as an app also requires a `prompt` grant on the target session, not only `profiles:assign`.
841
+
822
842
  **Prompts.** An agent's prompt reaches the session as the hub's, never as yours: the spoke wraps it in a provenance
823
843
  envelope (`[hub] prompt from @lead · grant g_… · msg_…`, the body between boundary lines) and admits it as a hub
824
844
  message, so it never resets the loop protection's hop chain the way your own typing does. Agents may prompt root
@@ -925,12 +945,13 @@ hub name <@name> [--session <addr>]
925
945
  hub intent <addr> [text… | --clear] what the session is for (one line, ≤ 280 characters)
926
946
  hub role claim <role[.project]> [--exclusive] [--session <addr>]
927
947
  hub role release <role[.project]> [--session <addr>]
928
- hub msg <addr> "text" [--all] [--no-wake] [--urgent] [--key K] tell: fire and forget
929
- hub ask <addr> "text" [--wait [secs]] [--expires 2h] [--all] [--no-wake] [--urgent] [--key K]
948
+ hub msg <addr> "text" [--all] [--no-wake] [--urgent] [--item itk_…] [--key K] tell: fire and forget
949
+ hub ask <addr> "text" [--wait [secs]] [--expires 2h] [--all] [--no-wake] [--urgent] [--item itk_…] [--key K]
930
950
  hub reply <msgID> "text" [--no-wake] to the message's real sender, never via the role
931
951
  hub decline <msgID> --why "reason"
932
952
  hub ask @me "question" [--option id=label[:safe]]... [--recommend id [--why "…"]] [--urgency low|normal|high]
933
953
  [--deadline 2h [--on-timeout <safe id>]] [--tier confirm|tap] [--about <addr>]... a Decision for the user
954
+ [--choice "D1=Label:a=Option*,b=Option"]... [--kind question|plan|merge|release] several points (* recommended)
934
955
  hub decisions list [--state open|answered|expired|closed] the Decisions you asked
935
956
  hub inbox asks to answer, your open asks, recent messages, mutes
936
957
  hub mute <addr> · hub unmute <addr> refuse (or accept again) messages from a session
@@ -1102,7 +1123,12 @@ required and absolute; `mode`, `branch`, `base`, `remote`, `key`, `name`, `roles
1102
1123
  `cleanup`), `launches` (`state`, `label`), `launch_show` and `launch_stop` (`id`); the profile tools (`profiles`,
1103
1124
  `profile_show`, `profile_create`, `profile_version_create`, `profile_skill_set`, `profile_command_set`,
1104
1125
  `profile_assign`, …; [docs/profiles.md](docs/profiles.md#sdk-and-mcp)), the workspace, artifact and team tools
1105
- ([docs/teams.md](docs/teams.md#mcp-tools); `team_staff` takes the nested launch spec), with the same arguments
1126
+ ([docs/teams.md](docs/teams.md#mcp-tools); `team_staff` takes the nested launch spec), subscription tools
1127
+ (`subscribe`, `subscriptions`, `unsubscribe`; [docs/subscriptions.md](docs/subscriptions.md#surfaces)),
1128
+ the grant tools (`grant_request`, `grant_list`, `grant_show`, `grant_revoke`, `grant_create`;
1129
+ [docs/multi-user.md](docs/multi-user.md#grants-tenancy)), `session_model_set` (user device credential only, same
1130
+ catalog checks as the console), and read-only `machines` and `launch_nodes` (user device credential only; connected
1131
+ node roots, slots, runtimes and labels; [docs/mcp-operations.md](docs/mcp-operations.md)), with the same arguments
1106
1132
  (`to`, `text`, `addr`, `id`, `requestID`, `formID`, `why`, `n`, `wake`, `urgent`, `all`, `project`, `role`, …). The act
1107
1133
  tools need an app token with scope `sessions:act` and a grant, the launch tools scope `sessions:launch` and a
1108
1134
  launch grant; `needs_user` results include the evaluation. `ask` takes `wait` of at
@@ -1904,7 +1930,9 @@ The loop is the package `packages/voice-client` (a `VoiceTransport` over the con
1904
1930
  `voiceTurn.fallback`, `voiceTurn.slow` as URL query parameters (one visit) or localStorage keys. The recording
1905
1931
  mode records each score as `turn.predict` ([docs/voice-recording.md](docs/voice-recording.md)).
1906
1932
  - **Commands**: an utterance that is only "stop", "cancel", "never mind", "stop that" or "hold on"
1907
- calls `POST …/interrupt` and stops speech. "Pause listening" turns the mode off.
1933
+ calls `POST …/interrupt` and stops speech. "Pause listening" turns the mode off. While the recording mode records
1934
+ (on a hub with its `capture.v2` feature), "mark", "mark that" or "bookmark" alone bookmarks the recording instead
1935
+ of reaching the agent ([docs/voice-recording.md](docs/voice-recording.md)).
1908
1936
  - **Speech out**: root replies arrive one sentence at a time while they are written (`say` with `seq`);
1909
1937
  the phone plays them in `seq` order per text part and drops duplicates (`SayAssembler`). A `say` without
1910
1938
  `seq` (a subagent's result) is split into sentences. Abbreviations, decimals, file names and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huynle/hub",
3
- "version": "0.10.1",
3
+ "version": "0.11.1",
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.10.1",
3
+ "version": "0.11.1",
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",
@@ -470,9 +470,12 @@ export class ClaudeSessions {
470
470
  this.#settle(s, undefined, true)
471
471
  s.stopRequested = false
472
472
  const detail = [input.error_details, input.error].find((v): v is string => typeof v === "string" && v.length > 0) ?? "the turn failed"
473
+ // (SS-9) The StopFailure error type (rate_limit, billing_error, authentication_failed, …) as error.type
474
+ // "claude.<token>", so the hub classifies a typed error instead of guessing from the text. Only a token-shaped value.
475
+ const token = typeof input.error === "string" && /^[a-z][a-z0-9_]{0,40}$/.test(input.error) ? input.error : undefined
473
476
  const turnID = s.turnID
474
477
  s.busy = false
475
- this.#emit({ type: "session.execution.failed", data: { sessionID: s.hubID, ...(turnID ? { turnID } : {}), error: { message: cut(detail, HARNESS_ERROR_MAX) } } })
478
+ this.#emit({ type: "session.execution.failed", data: { sessionID: s.hubID, ...(turnID ? { turnID } : {}), error: { message: cut(detail, HARNESS_ERROR_MAX), ...(token ? { type: `claude.${token}` } : {}) } } })
476
479
  return {}
477
480
  }
478
481