@huynle/hub 0.7.3 → 0.8.0

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 (221) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/Dockerfile +7 -2
  3. package/README.md +153 -24
  4. package/package.json +5 -3
  5. package/packages/claude-code/package.json +1 -1
  6. package/packages/claude-code/src/daemon.ts +8 -1
  7. package/packages/claude-code/src/launcher.ts +29 -9
  8. package/packages/claude-code/src/profile.ts +173 -0
  9. package/packages/claude-code/src/sessions.ts +22 -4
  10. package/packages/cli/dist/hub-cli.js +6474 -3470
  11. package/packages/cli/package.json +1 -1
  12. package/packages/cli/src/artifact-jobs-mcp.ts +226 -0
  13. package/packages/cli/src/artifacts-mcp.ts +160 -0
  14. package/packages/cli/src/bootstrap/enrollment/enroll.ts +20 -8
  15. package/packages/cli/src/bootstrap/enrollment/policy.ts +125 -12
  16. package/packages/cli/src/bootstrap/enrollment/status.ts +3 -2
  17. package/packages/cli/src/cli.ts +95 -13
  18. package/packages/cli/src/format.ts +48 -10
  19. package/packages/cli/src/http.ts +1 -0
  20. package/packages/cli/src/intents.ts +23 -0
  21. package/packages/cli/src/login.ts +20 -6
  22. package/packages/cli/src/mcp.ts +380 -9
  23. package/packages/cli/src/messaging.ts +3 -1
  24. package/packages/cli/src/setup/commands.ts +3 -2
  25. package/packages/cli/src/setup/runtime.ts +3 -2
  26. package/packages/cli/src/team-intake-mcp.ts +158 -0
  27. package/packages/cli/src/teams-mcp.ts +403 -0
  28. package/packages/cli/src/watchlist.ts +135 -0
  29. package/packages/codex/package.json +1 -1
  30. package/packages/codex/src/profile.ts +163 -0
  31. package/packages/codex/src/sessions.ts +12 -0
  32. package/packages/codex/src/spoke.ts +19 -1
  33. package/packages/node/package.json +1 -1
  34. package/packages/node/src/config.ts +96 -2
  35. package/packages/node/src/opencode-warm.ts +325 -0
  36. package/packages/node/src/run.ts +27 -1
  37. package/packages/preset/package.json +1 -1
  38. package/packages/preset/src/profile.ts +34 -0
  39. package/packages/preset/src/spoke.ts +4 -0
  40. package/packages/protocol/package.json +1 -1
  41. package/packages/protocol/src/artifacts.ts +142 -0
  42. package/packages/protocol/src/concierge.ts +366 -0
  43. package/packages/protocol/src/contracts.ts +104 -0
  44. package/packages/protocol/src/graph.ts +70 -0
  45. package/packages/protocol/src/hub-workspace.ts +201 -0
  46. package/packages/protocol/src/index.ts +169 -6
  47. package/packages/protocol/src/launch.ts +22 -0
  48. package/packages/protocol/src/profiles.ts +301 -0
  49. package/packages/protocol/src/teams.ts +204 -0
  50. package/packages/runtime-openshell/package.json +1 -1
  51. package/packages/runtime-sdk/package.json +1 -1
  52. package/packages/sdk/package.json +1 -1
  53. package/packages/sdk/src/address.ts +50 -2
  54. package/packages/sdk/src/artifact-jobs.ts +194 -0
  55. package/packages/sdk/src/artifacts.ts +187 -0
  56. package/packages/sdk/src/client.ts +226 -5
  57. package/packages/sdk/src/concierge.ts +88 -0
  58. package/packages/sdk/src/errors.ts +3 -1
  59. package/packages/sdk/src/index.ts +5 -0
  60. package/packages/sdk/src/team-intake.ts +122 -0
  61. package/packages/sdk/src/teams.ts +346 -0
  62. package/packages/sdk/src/types.ts +548 -1
  63. package/packages/spoke-sdk/package.json +1 -1
  64. package/packages/spoke-sdk/src/index.ts +1 -0
  65. package/packages/spoke-sdk/src/profile-apply.ts +245 -0
  66. package/src/central/actions.ts +7 -0
  67. package/src/central/agent/acting-routes.ts +2 -1
  68. package/src/central/agent/acting-switch.ts +2 -1
  69. package/src/central/agent/acting.ts +215 -10
  70. package/src/central/agent/app-tokens.ts +3 -2
  71. package/src/central/agent/audit.ts +17 -0
  72. package/src/central/agent/body.ts +163 -0
  73. package/src/central/agent/decisions.ts +69 -2
  74. package/src/central/agent/delivery.ts +95 -15
  75. package/src/central/agent/directory-routes.ts +37 -7
  76. package/src/central/agent/directory.ts +70 -14
  77. package/src/central/agent/errors.ts +5 -0
  78. package/src/central/agent/events-routes.ts +21 -4
  79. package/src/central/agent/events.ts +11 -3
  80. package/src/central/agent/form-check.ts +85 -0
  81. package/src/central/agent/grant-requests.ts +2 -1
  82. package/src/central/agent/grant-routes.ts +4 -3
  83. package/src/central/agent/grants.ts +29 -2
  84. package/src/central/agent/idempotency.ts +133 -0
  85. package/src/central/agent/intent-routes.ts +116 -0
  86. package/src/central/agent/intents.ts +505 -0
  87. package/src/central/agent/interaction-graph-routes.ts +212 -0
  88. package/src/central/agent/interaction-graph.ts +865 -0
  89. package/src/central/agent/launch-engine.ts +16 -2
  90. package/src/central/agent/launch-routes.ts +6 -1
  91. package/src/central/agent/launch-spec.ts +14 -1
  92. package/src/central/agent/message-routes.ts +2 -1
  93. package/src/central/agent/messages.ts +27 -2
  94. package/src/central/agent/names.ts +12 -2
  95. package/src/central/agent/read-routes.ts +86 -19
  96. package/src/central/agent/routes.ts +176 -5
  97. package/src/central/agent/webhook-routes.ts +2 -1
  98. package/src/central/agent-data.ts +4 -0
  99. package/src/central/artifacts/access.ts +44 -0
  100. package/src/central/artifacts/blob-store.ts +113 -0
  101. package/src/central/artifacts/blob-support.ts +98 -0
  102. package/src/central/artifacts/events.ts +27 -0
  103. package/src/central/artifacts/host-caps.ts +162 -0
  104. package/src/central/artifacts/host.ts +315 -0
  105. package/src/central/artifacts/idempotency.ts +109 -0
  106. package/src/central/artifacts/jobs-routes.ts +773 -0
  107. package/src/central/artifacts/jobs.ts +344 -0
  108. package/src/central/artifacts/maintenance.ts +103 -0
  109. package/src/central/artifacts/routes.ts +360 -0
  110. package/src/central/artifacts/store.ts +776 -0
  111. package/src/central/artifacts/workspaces.ts +31 -0
  112. package/src/central/backend/file-blobs.ts +377 -0
  113. package/src/central/backend/index.ts +12 -3
  114. package/src/central/backend/memory-blobs.ts +116 -0
  115. package/src/central/backend/postgres-blobs.ts +513 -0
  116. package/src/central/backend/postgres-migrate.ts +33 -0
  117. package/src/central/backend/postgres-state.ts +19 -0
  118. package/src/central/backend/types.ts +8 -0
  119. package/src/central/concierge/activity.ts +142 -0
  120. package/src/central/concierge/arm.ts +63 -0
  121. package/src/central/concierge/board-render.ts +190 -0
  122. package/src/central/concierge/board-results.ts +169 -0
  123. package/src/central/concierge/board-routes.ts +339 -0
  124. package/src/central/concierge/board.ts +249 -0
  125. package/src/central/concierge/context.ts +221 -0
  126. package/src/central/concierge/entitlements.ts +13 -0
  127. package/src/central/concierge/guard.ts +120 -0
  128. package/src/central/concierge/inbound.ts +89 -0
  129. package/src/central/concierge/index.ts +84 -0
  130. package/src/central/concierge/notes.ts +41 -0
  131. package/src/central/concierge/notifier.ts +986 -0
  132. package/src/central/concierge/prompt.ts +130 -0
  133. package/src/central/concierge/proposals.ts +262 -0
  134. package/src/central/concierge/push-remap.ts +49 -0
  135. package/src/central/concierge/routes.ts +62 -0
  136. package/src/central/concierge/schedule.ts +113 -0
  137. package/src/central/concierge/service.ts +243 -0
  138. package/src/central/concierge/session-routes.ts +69 -0
  139. package/src/central/concierge/settings.ts +217 -0
  140. package/src/central/concierge/supervisor.ts +740 -0
  141. package/src/central/concierge/tiers.ts +260 -0
  142. package/src/central/concierge/watchlist-routes.ts +277 -0
  143. package/src/central/concierge/watchlist.ts +273 -0
  144. package/src/central/config.ts +7 -1
  145. package/src/central/http.ts +633 -50
  146. package/src/central/mcp-http.ts +10 -21
  147. package/src/central/profiles/apply.ts +333 -0
  148. package/src/central/profiles/content.ts +174 -0
  149. package/src/central/profiles/events.ts +40 -0
  150. package/src/central/profiles/idempotency.ts +14 -0
  151. package/src/central/profiles/import.ts +139 -0
  152. package/src/central/profiles/routes.ts +356 -0
  153. package/src/central/profiles/store.ts +597 -0
  154. package/src/central/profiles/usage-ports.ts +120 -0
  155. package/src/central/profiles/usage.ts +364 -0
  156. package/src/central/push.ts +7 -0
  157. package/src/central/registry.ts +43 -6
  158. package/src/central/store.ts +4 -0
  159. package/src/central/teams/collections.ts +34 -0
  160. package/src/central/teams/discovery.ts +346 -0
  161. package/src/central/teams/draft-edits.ts +46 -0
  162. package/src/central/teams/events.ts +46 -0
  163. package/src/central/teams/intake-routes.ts +100 -0
  164. package/src/central/teams/intake.ts +772 -0
  165. package/src/central/teams/policy.ts +231 -0
  166. package/src/central/teams/routes.ts +516 -0
  167. package/src/central/teams/seats.ts +272 -0
  168. package/src/central/teams/staffing.ts +888 -0
  169. package/src/central/teams/store.ts +667 -0
  170. package/src/central/teams/validation.ts +256 -0
  171. package/src/central/workspaces/board.ts +408 -0
  172. package/src/central/workspaces/checkpoints.ts +316 -0
  173. package/src/central/workspaces/coordinator.ts +841 -0
  174. package/src/central/workspaces/events.ts +85 -0
  175. package/src/central/workspaces/lifecycle.ts +93 -0
  176. package/src/central/workspaces/master.ts +490 -0
  177. package/src/central/workspaces/members.ts +632 -0
  178. package/src/central/workspaces/routes.ts +354 -0
  179. package/src/central/workspaces/sidechats-routes.ts +410 -0
  180. package/src/central/workspaces/sidechats.ts +482 -0
  181. package/src/central/workspaces/store.ts +571 -0
  182. package/src/central/workspaces/supervision.ts +355 -0
  183. package/src/index.ts +97 -15
  184. package/src/spoke/concierge-agent.ts +33 -0
  185. package/src/spoke/deliver.ts +22 -4
  186. package/src/spoke/fork-revert.ts +142 -0
  187. package/src/spoke/hub-rpc.ts +41 -5
  188. package/src/spoke/hub-tool.ts +383 -18
  189. package/src/spoke/listing.ts +47 -4
  190. package/src/spoke/profile-apply.ts +298 -0
  191. package/src/spoke/session-context.ts +176 -0
  192. package/src/spoke/spoke.ts +319 -20
  193. package/src/spoke/timeline.ts +6 -1
  194. package/src/spoke/tracker.ts +23 -1
  195. package/src/spoke/who-teams.ts +81 -0
  196. package/src/tui-messaging.ts +32 -18
  197. package/web/dist/assets/ArtifactPage-BS0gVkfu.js +1 -0
  198. package/web/dist/assets/ArtifactPane-PLYHNs6s.js +1 -0
  199. package/web/dist/assets/ArtifactWorkbench-9eTj1CeW.css +1 -0
  200. package/web/dist/assets/ArtifactWorkbench-LkDyJ5Dv.js +6 -0
  201. package/web/dist/assets/Library-CtxRqTjo.js +2 -0
  202. package/web/dist/assets/Library-DYsFj_5c.css +1 -0
  203. package/web/dist/assets/ProfilesDestination-BYiHKLmc.js +1 -0
  204. package/web/dist/assets/TeamsPage-BwjsAAUC.js +1 -0
  205. package/web/dist/assets/Watchtower-C4swrWVs.js +2 -0
  206. package/web/dist/assets/Watchtower-CTgqHk5g.css +1 -0
  207. package/web/dist/assets/index-DaH5GUdV.js +31 -0
  208. package/web/dist/assets/index-WWuKfWxL.css +1 -0
  209. package/web/dist/assets/live-BUofsQpz.css +1 -0
  210. package/web/dist/assets/live-YyTjE47g.js +5 -0
  211. package/web/dist/assets/pane-layout-CKC4I00k.js +1 -0
  212. package/web/dist/assets/plural-Bspvoqfq.js +1 -0
  213. package/web/dist/assets/renderer2d-CX5j3NcD.js +1 -0
  214. package/web/dist/assets/renderer3d-BXIVOvfi.js +5393 -0
  215. package/web/dist/assets/safe-area-DfPsOdWR.js +1 -0
  216. package/web/dist/assets/source-CvUHTc3O.js +1 -0
  217. package/web/dist/assets/ui-C9Igxzjm.js +1 -0
  218. package/web/dist/index.html +5 -2
  219. package/web/dist/sw.js +22 -2
  220. package/web/dist/assets/index-Dm8OmkaO.css +0 -1
  221. package/web/dist/assets/index-FUJbj_Du.js +0 -20
package/CHANGELOG.md CHANGED
@@ -2,6 +2,235 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.8.0
6
+
7
+ Workspaces, supervision, split panes and side chats, shared agent profiles, persistent AI-powered artifacts, teams,
8
+ and the Watchtower. All new state is additive. Cluster hubs need `hub-server migrate` (schema v2) before the new
9
+ pods start. See [docs/operations.md](docs/operations.md) for the rollout, rollback, backup and front-proxy notes.
10
+
11
+ ### Fork and revert a message
12
+ - Hovering (or focusing) one of your messages in the console shows **Copy · Fork · Revert**, like the OpenCode app;
13
+ on a phone, long-press it or tap its ⋯ button. **Fork** opens a new session with the conversation before the
14
+ message and the message in its composer. **Revert** hides the message and everything after it, restores the files
15
+ the session changed since then (confirming first, with the list), and puts the message back in the composer; a
16
+ banner offers **Restore**; sending the next message makes it permanent. Works across devices and in panes.
17
+ - New console routes (user-only, audited): `POST /api/sessions/:id/fork`, `GET …/revert/preview`, `POST …/revert`,
18
+ `POST …/revert/restore`, `POST …/revert/commit`. Spoke features `fork` and `revert` (OpenCode processes that reach
19
+ their own server) with ops `fork`, `revertPreview`, `revert`, `revertRestore`, `revertCommit`; a staged revert is
20
+ reported as `SessionInfo.revert` (additive: older hubs ignore it). Other harnesses show the actions disabled
21
+ ("not supported yet"). Contract: [docs/session-view.md](docs/session-view.md#fork-and-revert-a-message).
22
+
23
+ ### Workspaces and supervision
24
+ - **Workspaces** ([docs/workspaces.md](docs/workspaces.md)): durable, cross-machine coordination units with host repo
25
+ bindings, members (`master`/`worker`/`observer`, generations), explicit assignments and a supervision graph
26
+ (acyclic, ≤ 6 levels) kept apart from native `parentID`. Membership and supervision grant nothing. New routes under
27
+ `/api/agent/workspaces…`, SDK `hub.workspaces`, 15 `workspace_*` MCP tools, and compact `workspace.*` events.
28
+ - **Drag to supervise**: the sidebar's **Workspaces** tree shows each workspace's members under their supervisors.
29
+ Drag (or use Alt+arrows or the Move menu) to supervise, reorder or detach. "Spawned by" and "supervised by" are
30
+ shown separately.
31
+ - **Situation board** (`GET …/board`): one bounded, text-free snapshot of members, presence, blockers, assignments,
32
+ Decisions and artifacts, with `stale`/`omitted`/`failed` sections instead of guesses.
33
+ - **Supervisor digests and checkpoints**: masters and supervisors get batched, at-least-once digests as hub messages
34
+ from `app:workspace-coordinator`. `POST …/checkpoints` acknowledges them durably (flushed before the answer).
35
+ - **Quieter digests**: routine updates (including snapshots) are delivered without waking an idle supervisor (2 min
36
+ quiet, 15 min max) and repeats of already-known states are dropped; assignment closures wake without interrupting
37
+ (30 s quiet, 5 min max); blockers, errors and urgent snapshots still wake and interrupt. A cleared error is still
38
+ reported, followed by the recovery. A member's session that disappears and comes back within one batch (a spoke
39
+ restart or reconnect) is not reported.
40
+ - **Master replacement** (`POST …/master {sessionID | launchID}`): explicit, fenced, with the next generation. Supervised
41
+ members move to the new master. No grants move.
42
+
43
+ ### Panes and side chats
44
+ - **Split panes**: **Open beside** shows up to 4 sessions or artifacts side by side, stacked or in a grid. The focused
45
+ pane owns the composer and Voice. Layouts are per device. On phones the panes are tabs. `/api/events` takes
46
+ `sessions=a,b` for the visible panes.
47
+ - **Drag into panes**: drag a session (sidebar, Workspaces tree, Watchtower detail), an artifact (library) or a pane
48
+ (header grip) onto a pane: edges split side by side or stacked, the centre replaces, the layout reflows to a grid
49
+ when needed, and a fifth pane is refused with a notice. **Open in pane → left/right/top/bottom** in the session
50
+ menu is the keyboard equivalent. The layout switch is now a compact segmented control with icons.
51
+ - **Side chats**: **Start side chat…** copies a bounded snapshot of a session into a new session on the workspace's
52
+ host binding (any machine), in a pane beside it, with **Refresh** and a confirmed **Send to main**. Neither
53
+ interrupts a running turn.
54
+
55
+ ### Agent profiles
56
+ - **Shared, versioned profiles** ([docs/profiles.md](docs/profiles.md)): instructions, skills and commands (with
57
+ `{{param}}`, `{{arguments}}` and spoke-side `{{secret:NAME}}`), immutable versions, and import from
58
+ `AGENTS.md`/`CLAUDE.md`, `skills/*/SKILL.md` and `commands/*.md`. You pin an exact version to a session at runtime
59
+ (the profile chip), to a launch (`agentProfile`) or to a team role. The **Agent profiles** page is the library.
60
+ **Ctrl+Alt+P** inserts a rendered command into the composer.
61
+ - **Per-harness application** through the new spoke feature `profileApply`, with honest acknowledgments (`applied`,
62
+ `partial`, `restart_required`, `unsupported`, `rejected`, `pending`): OpenCode (system prompt + `hub_profile`
63
+ tool), Claude Code (hook context, `set_model` in launched sessions), Codex (developer message + skill files);
64
+ presets are always `unsupported`. Local instructions win. No global harness config is written.
65
+ - **Profile metadata and usage**: optional `metadata` and `projects` on a profile, and **Used by**
66
+ (`GET /api/agent/profiles/:id/usage`, SDK `hub.profiles.usage()`, MCP `profile_usage`): pinned sessions with
67
+ requested vs running version and an explicit Upgrade, team roles, launches and workspaces, each shown only under the
68
+ caller's existing read rules (`hidden` names what was left out). The library rows show usage chips, with filters and
69
+ sort.
70
+
71
+ ### Artifacts and AI
72
+ - **Persistent artifacts** ([docs/artifacts.md](docs/artifacts.md)): revisions of content-addressed blobs (files
73
+ standalone, chunked Postgres in a cluster), `private` or `workspace` visibility, archive and delete, staged uploads up
74
+ to 25 MiB, and downloads always served as attachments. New routes, SDK `hub.artifacts`, and `artifact_*` MCP tools.
75
+ - **Viewers and dashboards**: markdown, code, images, tables, charts, and declarative dashboards (schema v1, bindings
76
+ to other artifacts only, no URLs or scripts), plus a text editor with conflict-safe drafts.
77
+ - **AI jobs**: ask, propose an edit or run a declared action. The job runs on a handler you choose (a new launch or an
78
+ existing session), always with the requester's authority. Proposals apply only when you **Accept**. Jobs survive hub
79
+ restarts, and each job times out after 2 h.
80
+ - **Viewer redesign**: a header bar with kind icon, version picker (**Back to latest**), Copy, Download, Edit, Open in
81
+ pane and a details popover; content in a framed card (code with file/language header, line numbers, wrap; markdown
82
+ at reading width; dashboards and apps full-bleed); inline editing; and AI as a chat-style side panel (thread,
83
+ composer, Ask / Edit / Action or slash commands, compact handler picker that never preselects), resizable, a
84
+ bottom sheet on phones.
85
+
86
+ ### Isolated artifact host
87
+ - Interactive `html`/`app` artifacts run only at `/artifact-host/v1/<capability>/…`: an opaque-origin sandbox with a
88
+ strict CSP, short-lived, viewer-bound and revocable capabilities, and a bridge (`resize`, `askAI`). Every `askAI`
89
+ becomes a consent card in the artifact's AI panel thread ([docs/artifact-host.md](docs/artifact-host.md)).
90
+
91
+ ### Teams
92
+ - **Teams** ([docs/teams.md](docs/teams.md)): versioned definitions with roles, one entry role, `open` or `restricted`
93
+ communication paths (`one-way`/`two-way`, `msg`/`ask`), and a draft → publish flow. A session can belong to many
94
+ teams. The hub **enforces** the policy on delivery (`403 forbidden` with `extra.policy`). Team members cannot contact
95
+ sessions outside their teams unless a role has `externalContact`. Sessions in no team are unchanged.
96
+ - **Staffing** with existing or newly launched sessions under your grants (`403 needs_user` lists what is missing).
97
+ **Intake** gives work to the team's entry role and tracks it as an assignment ([docs/teams-intake.md](docs/teams-intake.md)).
98
+ - **Team addresses** `@@role#team` and `@@role.project#team` (quote `#` in shells). `/who`, `status` and `show` are
99
+ team-aware for team members.
100
+ - Malformed addresses answer `Not an address: use ses_…, @name, @@role or @@role#team` with a syntax-only hint (for
101
+ `@engineer#support`: `@@engineer#support` or `@engineer`); the hint never reveals whether a name exists.
102
+ - Entry-role sessions report and close their intake from the harness: OpenCode `hub` tool verbs `intake` and
103
+ `intake_update`, MCP `team_intake_update` (`expectedRevision` now optional). The intake message says how.
104
+ - **Teams page** (`/teams`, in the primary navigation): team cards (mode, entry role, seats, vacant roles, open work),
105
+ **New team** in a dialog, and per team **Members** (one block per role, **+ Add member** with a searchable session
106
+ picker that shows other memberships, **Launch new …**), **Work** (Submit a task, live intake status with links to
107
+ the entry session and its assignment), **Communication** and **Details** (draft → Review and publish, history),
108
+ Pause/Archive and **Edit in Watchtower**. The Watchtower's team panel uses the same components and links back.
109
+ - **Team isolation** (owner setting, default off): Settings → Teams, or `GET/PUT /api/agent/team-settings`
110
+ `{teamIsolation, expectedRevision}` (users only; SDK `hub.teams.settings()` / `setSettings()`). On: team members
111
+ see only their teams' sessions.
112
+
113
+ ### Watchtower
114
+ - Renamed from **Bird's-eye view** (new lookout icon). `/birds-eye` links redirect to `/watchtower` with their
115
+ filters, and the browser's saved view and presets (`birdsEye.*`) move to `watchtower.*` once.
116
+ - A full-screen page ([docs/watchtower.md](docs/watchtower.md)) of machines, sessions, you, your apps and teams, with
117
+ live message flow: 3D (lazy `3d-force-graph`), a deterministic 2D canvas, or a list (also used for reduced motion).
118
+ It has URL-state filters and presets, team clusters, drawn vs actual paths and denied attempts, and a **team
119
+ editor** to assemble, staff, publish and submit work. New read API `GET /api/agent/graph` (+ `/edges/:id`,
120
+ `/nodes/:id`).
121
+ - Graph first: the controls are a floating **dock** at the bottom centre (view, Filters with an active count, Legend,
122
+ Teams, Hub details, Reset view, Pause motion, Search) that fades when idle. **Filters** is closed by default and
123
+ shows only the filters you add; active filters are removable chips over the canvas. The **Legend** has a visible
124
+ close and Escape closes it. On phones, filters, legend, details and the team editor are bottom sheets.
125
+ - A look in the style of Brain Graph (MIT, credited in the docs): glowing points in coloured group regions, fibers that
126
+ brighten towards the receiver, depth of field, labels on the most connected nodes, and a stable layout kept per
127
+ device (`watchtower.layout`). Flow pulses fade with message age and stop once nothing on a link is working or
128
+ pending. Rendering is on demand (none while still or hidden), and the 3D view draws in about 25 batched calls.
129
+ - Staffed seats are tied to their role by a tether and vacant roles are drawn hollow; the legend uses the same glyphs;
130
+ **More → Re-layout** clears the saved layout; the empty state sits below the hub core with **Connect a machine**.
131
+
132
+ ### Navigation
133
+ - A primary navigation above the session groups: **All sessions**, **Artifacts** (a library across workspaces),
134
+ **Watchtower** and **Agent profiles**.
135
+ - **Settings sections**: General, Notifications, Voice & speech, Machines & devices, Agents & access, Teams and Admin,
136
+ each its own page `/settings/<section>` (deep links, back/forward; old `/settings#speech`-style links still work).
137
+ A section sidebar on desktop, a section list with drill-in on phones, and a **Search settings** box that jumps to a
138
+ setting and highlights it. Notification kinds are collapsed by group with an on/off summary; the device's push
139
+ status is one line with **Details**.
140
+
141
+ ### Security hardening
142
+ - **Bounded request bodies** everywhere: agent JSON 1 MiB, profiles 4 MiB, console JSON 4 MiB, `/mcp` 1 MiB, staged
143
+ blobs 25 MiB, all read by a rate-budgeted streaming reader. Oversized bodies are refused with `Connection: close`.
144
+ **Set a front-proxy request-body limit (26 MiB)**: the k8s ingress example now uses `proxy-body-size: 26m` (it was
145
+ `16m`).
146
+ - A session seated in a team no longer sees sessions seated only in *other* teams: they are left out of `who`,
147
+ `status`, `show`, `log`, `/watch` and `/events`, and their addresses answer `404` like unknown ones. Sessions in no
148
+ team stay visible unless the owner turns on `teamIsolation` (Settings → Teams). Users, apps and team-less sessions
149
+ keep the owner-wide view.
150
+ - New app-token scopes `workspaces:*`, `profiles:*`, `artifacts:*` and `teams:*`. **Existing tokens gain nothing**,
151
+ and session tokens are refused on workspace routes.
152
+ - **Plain http only by opt-in**: `hub login` (device code, `--token` or a piped token) and `hub`/`hubctl machine enroll`
153
+ refuse plain http except on loopback, or with `--allow-insecure-http` for a private-network IP address (never a
154
+ host name), with a warning every time; nothing remembers the flag, and later commands using a stored plain-http
155
+ login warn on stderr. A loopback hub's approval link on this machine's LAN address is shown; other origins are
156
+ replaced with a warning.
157
+
158
+ ### Fixes
159
+ - Counts pluralise correctly ("2 OpenCode processes", not "processs"); a team role's seat counter reads "1 of 2
160
+ required", "3 members · up to 16" or "full (16)" instead of "1 of 0–16".
161
+ - The artifact AI panel lists handler sessions by `@name` (searchable by name, title or ID) and names a handler picked
162
+ in the console by its `@name` instead of its session ID.
163
+ - The console's live stream opens at once when it resumes (the hub writes a first chunk immediately); before, it waited
164
+ for the 20 s heartbeat and **Reconnecting…** stayed up, across pages. Over plain http, Chrome still allows only 6
165
+ connections per host, so more than about 6 console tabs stall; https avoids this.
166
+ - The sidebar, Details rail and pane resize handles show their resize pointer again (a hidden character had broken
167
+ those CSS rules).
168
+ - `hub node` keeps OpenCode's background service warm (loopback only, every 30 s) so a freshly started service's
169
+ machine comes online without anyone opening a project (OpenCode has no server-start plugin hook).
170
+ - **Routine `wake: false` messages no longer cost an OpenCode session a turn** (routine digests, `--no-wake`
171
+ messages). OpenCode gives every queued inbox item its own model call once a turn ends, resume or not, so a
172
+ supervisor answered each routine digest in a turn of its own. The OpenCode spoke now admits every non-waking
173
+ message steered without resume, and refuses a routine one (a `wake: false` `msg` that is not urgent, to a top-level
174
+ session) while a turn runs: the hub keeps it `pending` and delivers it after the turn, so it reaches the model with
175
+ the next real turn, before its prompt (CLI and MCP say "pending (offline or busy; …)"). A waking message to the
176
+ same session sends the pending ones first (if the session is still busy, the waking one goes first, queued after
177
+ the turn). A flush stops at a session's first failed delivery, so a busy session costs one try per sessions frame.
178
+ Urgent
179
+ ones, asks, replies, primers and anything for a subagent still go into a running turn. Live check:
180
+ `HUB_IT_OPENCODE=1 bun test test/e2e-quiet-delivery.it.test.ts`.
181
+ - **Repeated blocks of one member no longer wake its supervisor each time**: blocked again for the same reason (same
182
+ blocker kind, or the same blocked/error state) within 15 min of a wake about it that the hub carried out, the
183
+ digest line is routine.
184
+ - **A run that fails at once shows as failed** (a model or provider that does not exist, auth, quota). OpenCode ends
185
+ such a run within ~50 ms of `session.created`, before the spoke's debounced sessions snapshot (500 ms), and the hub
186
+ keeps status only for sessions it was told about, so it dropped the failure and listed the session idle: no banner,
187
+ no "!" badge, not under Needs you, no `providerError` push (also in 0.7.3). The spoke now sends the snapshot first
188
+ when a run event is about a session the hub has not been told about yet (creating sessions alone still waits for the
189
+ debounce), and resends a failure once a snapshot lists its session if the session's details were still being
190
+ fetched when it failed. Live check: `HUB_IT_OPENCODE=1 bun test test/spoke-run-failure.it.test.ts`.
191
+ - **A session is never routed to another harness's spoke.** When no process owns a session and none of its
192
+ reporters is a service, the hub falls back to a background service on the reporter's machine; it took any service,
193
+ so an OpenCode session reported by a foreground `opencode serve` on a machine that also runs the Claude Code (or
194
+ Codex) service went to that service: "The claude-code spoke does not serve "timeline"" and `unsupported: summary`
195
+ (also in 0.7.3). The fallback now takes only a service of the session's own harness, else the reporter itself.
196
+
197
+ ### Runtime
198
+ - **Bun 1.4.4**, pinned: the image (`oven/bun:1.4.4-slim` by digest, both stages), CI and release workflows, and
199
+ `engines.bun` (`>=1.4.4`). Bun 1.3.x's `Bun.SQL` auto-pipelining could stall a pooled Postgres connection under
200
+ concurrent writes (oven-sh/bun#33985; fixed in Bun 1.4.0 by oven-sh/bun#33627): a cluster pod's state queries then
201
+ never answered (the supervision IT's "many concurrent internal moves" hung or timed out in 3 of 15 runs on 1.3.5,
202
+ 0 of 40 on 1.4.4).
203
+ - Cluster: the Postgres pools set `statement_timeout` (30 s by default, `HUB_DATABASE_STATEMENT_TIMEOUT_MS`, `0`
204
+ off), so a query stuck on the database fails with an error instead of holding its caller and connection.
205
+ - Bun 1.4 enforces the server's request ceiling on chunked bodies too; the ceiling now leaves 16 MiB above the
206
+ largest route bound, so an over-limit upload still gets the route's own `400`/`413` with `Connection: close`. Bun
207
+ 1.4 then closes the socket instead of reading and discarding whatever the client keeps sending.
208
+ - The image build copies the protocol and SDK sources into the console stage, which imports them (without them
209
+ `docker build` failed in the console's `vite build`).
210
+
211
+ ### Behavior notes
212
+ - New agent error code `conflict` (409, with `currentRevision`) for stale `expectedRevision`s and reused
213
+ `Idempotency-Key`s. The SDK `ErrorCode` includes it, and the CLI exits 3.
214
+ - The server's request ceiling rises from about 10 MiB to about 41 MiB (25 MiB blobs plus drain headroom). A JSON body
215
+ that was accepted before can now be refused as too large (by its route's bound).
216
+ - SDK `parseAddress` refuses `#` in a role's project scope (`@@role#team` is only ever team-resolved).
217
+ - Send to main and AI jobs handled by a session use normal delivery: they never interrupt a running turn. The first
218
+ message to an idle session right after the hub's primer may be delivered as steer.
219
+ - Cluster: schema v2 (`artifact-blobs`). Run `hub-server migrate` before rolling pods, and don't create restricted
220
+ teams while old and new pods are mixed.
221
+
222
+ ## 0.7.4
223
+
224
+ Release downloads move to a public repository. The source repository is private now, so the standalone `hub`
225
+ binaries, `hubctl`, its signed manifest and the checksums are published to
226
+ [`huynle/hub-releases`](https://github.com/huynle/hub-releases) (the release workflow mirrors every release there,
227
+ byte for byte, so the signed manifest and checksums stay valid). The image `ghcr.io/huynle/hub` and the npm package
228
+ `@huynle/hub` are unchanged.
229
+
230
+ - **Install**: the README, the Ansible role README and the web shell hint use `huynle/hub-releases`; pass
231
+ `--release-url https://github.com/huynle/hub-releases/releases/download/v<version>` to `hubctl setup ansible`.
232
+ - **Plugin rename notice** no longer suggests installing from `github:huynle/hub`.
233
+
5
234
  ## 0.7.3
6
235
 
7
236
  Session errors are visible: a quota, auth or rate-limit error in an OpenCode session now shows up in the hub.
package/Dockerfile CHANGED
@@ -7,6 +7,8 @@
7
7
  # Legacy VOICE_* variables still work, except that the HUB_HOST/HUB_PORT/HUB_STATE_DIR defaults below
8
8
  # win over VOICE_HOST/VOICE_PORT/VOICE_STATE_DIR: rename those overrides to HUB_*.
9
9
  # The console (web/, Solid + Vite) is built in its own stage; the image ships only web/dist.
10
+ # Bun is pinned (tag and multi-arch index digest) to 1.4.4: it has the Bun.SQL pipelining fix (oven-sh/bun#33627,
11
+ # in since 1.4.0; 1.3.x stalls pooled Postgres connections in cluster mode). Bump both stages together.
10
12
  FROM node:22-slim AS web
11
13
  WORKDIR /build
12
14
  COPY package.json package-lock.json ./
@@ -24,9 +26,12 @@ COPY packages/preset/package.json ./packages/preset/
24
26
  COPY packages/codex/package.json ./packages/codex/
25
27
  RUN npm ci --no-audit --no-fund --ignore-scripts
26
28
  COPY web ./web
29
+ # The console imports the protocol and SDK sources (web/src/… ../packages/{protocol,sdk}/src).
30
+ COPY packages/protocol/src ./packages/protocol/src
31
+ COPY packages/sdk/src ./packages/sdk/src
27
32
  RUN npx vite build --config web/vite.config.ts
28
33
 
29
- FROM oven/bun:1.3-slim AS deps
34
+ FROM oven/bun:1.4.4-slim@sha256:1846adf22496bd711036aae8be147e57bdebbaea2e5264e3e0b5af1db187d9a4 AS deps
30
35
  WORKDIR /build
31
36
  COPY package.json package-lock.json ./
32
37
  COPY packages/protocol/package.json ./packages/protocol/
@@ -47,7 +52,7 @@ COPY docker/hub-deps.ts ./
47
52
  # Keep only what the hub loads at runtime (the @opencode/* packages are for the plugin).
48
53
  RUN bun hub-deps.ts /deps web-push
49
54
 
50
- FROM oven/bun:1.3-slim
55
+ FROM oven/bun:1.4.4-slim@sha256:1846adf22496bd711036aae8be147e57bdebbaea2e5264e3e0b5af1db187d9a4
51
56
  LABEL org.opencontainers.image.title="hub" \
52
57
  org.opencontainers.image.description="Hub: a central hub for AI agent harnesses (OpenCode first): remote console, pairing, push and optional Voice" \
53
58
  org.opencontainers.image.licenses="MIT" \