@huynle/hub 0.7.4 → 0.8.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.
- package/CHANGELOG.md +430 -1
- package/Dockerfile +7 -2
- package/README.md +230 -32
- package/package.json +5 -3
- package/packages/claude-code/package.json +1 -1
- package/packages/claude-code/src/daemon.ts +8 -1
- package/packages/claude-code/src/launcher.ts +29 -9
- package/packages/claude-code/src/paths.ts +3 -3
- package/packages/claude-code/src/profile.ts +173 -0
- package/packages/claude-code/src/sessions.ts +22 -4
- package/packages/cli/dist/hub-cli.js +6871 -3460
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/artifact-jobs-mcp.ts +226 -0
- package/packages/cli/src/artifacts-mcp.ts +160 -0
- package/packages/cli/src/bootstrap/adapters/node-services/render.ts +2 -2
- package/packages/cli/src/bootstrap/discovery/host.ts +15 -1
- package/packages/cli/src/bootstrap/discovery/probe.ts +1 -1
- package/packages/cli/src/bootstrap/enrollment/enroll.ts +21 -9
- package/packages/cli/src/bootstrap/enrollment/policy.ts +138 -12
- package/packages/cli/src/bootstrap/enrollment/status.ts +3 -2
- package/packages/cli/src/cli.ts +96 -14
- package/packages/cli/src/format.ts +48 -10
- package/packages/cli/src/http.ts +2 -0
- package/packages/cli/src/intents.ts +23 -0
- package/packages/cli/src/launches.ts +9 -3
- package/packages/cli/src/login.ts +20 -6
- package/packages/cli/src/mcp.ts +385 -10
- package/packages/cli/src/messaging.ts +7 -3
- package/packages/cli/src/profiles-mcp.ts +301 -0
- package/packages/cli/src/setup/commands.ts +23 -4
- package/packages/cli/src/setup/runtime.ts +3 -2
- package/packages/cli/src/spoke-services.ts +1 -1
- package/packages/cli/src/team-intake-mcp.ts +158 -0
- package/packages/cli/src/teams-mcp.ts +460 -0
- package/packages/cli/src/watchlist.ts +135 -0
- package/packages/codex/package.json +1 -1
- package/packages/codex/src/paths.ts +3 -3
- package/packages/codex/src/profile.ts +163 -0
- package/packages/codex/src/sessions.ts +12 -0
- package/packages/codex/src/spoke.ts +19 -1
- package/packages/node/package.json +1 -1
- package/packages/node/src/config.ts +104 -2
- package/packages/node/src/opencode-warm.ts +344 -0
- package/packages/node/src/run.ts +28 -1
- package/packages/preset/package.json +1 -1
- package/packages/preset/src/profile.ts +34 -0
- package/packages/preset/src/spoke.ts +4 -0
- package/packages/protocol/package.json +1 -1
- package/packages/protocol/src/artifacts.ts +142 -0
- package/packages/protocol/src/concierge.ts +366 -0
- package/packages/protocol/src/contracts.ts +104 -0
- package/packages/protocol/src/graph.ts +70 -0
- package/packages/protocol/src/hub-workspace.ts +201 -0
- package/packages/protocol/src/index.ts +175 -6
- package/packages/protocol/src/launch.ts +36 -0
- package/packages/protocol/src/profiles.ts +301 -0
- package/packages/protocol/src/teams.ts +204 -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/address.ts +50 -2
- package/packages/sdk/src/artifact-jobs.ts +194 -0
- package/packages/sdk/src/artifacts.ts +187 -0
- package/packages/sdk/src/client.ts +226 -5
- package/packages/sdk/src/concierge.ts +88 -0
- package/packages/sdk/src/errors.ts +4 -1
- package/packages/sdk/src/index.ts +6 -0
- package/packages/sdk/src/team-intake.ts +122 -0
- package/packages/sdk/src/teams.ts +346 -0
- package/packages/sdk/src/types.ts +568 -1
- package/packages/sdk/src/wake.ts +20 -0
- package/packages/spoke-sdk/package.json +1 -1
- package/packages/spoke-sdk/src/index.ts +1 -0
- package/packages/spoke-sdk/src/profile-apply.ts +245 -0
- package/src/central/actions.ts +7 -0
- package/src/central/agent/acting-routes.ts +2 -1
- package/src/central/agent/acting-switch.ts +2 -1
- package/src/central/agent/acting.ts +218 -10
- package/src/central/agent/app-tokens.ts +3 -2
- package/src/central/agent/audit.ts +18 -1
- package/src/central/agent/body.ts +163 -0
- package/src/central/agent/decisions.ts +125 -2
- package/src/central/agent/delivery.ts +194 -40
- package/src/central/agent/directory-routes.ts +37 -7
- package/src/central/agent/directory.ts +70 -14
- package/src/central/agent/errors.ts +7 -0
- package/src/central/agent/escalation.ts +10 -2
- package/src/central/agent/events-routes.ts +21 -4
- package/src/central/agent/events.ts +18 -4
- package/src/central/agent/form-check.ts +85 -0
- package/src/central/agent/grant-requests.ts +2 -1
- package/src/central/agent/grant-routes.ts +4 -3
- package/src/central/agent/grants.ts +29 -2
- package/src/central/agent/idempotency.ts +133 -0
- package/src/central/agent/intent-routes.ts +116 -0
- package/src/central/agent/intents.ts +505 -0
- package/src/central/agent/interaction-graph-routes.ts +212 -0
- package/src/central/agent/interaction-graph.ts +865 -0
- package/src/central/agent/launch-engine.ts +189 -32
- package/src/central/agent/launch-routes.ts +26 -10
- package/src/central/agent/launch-spec.ts +40 -3
- package/src/central/agent/launch-store.ts +55 -2
- package/src/central/agent/message-limits.ts +89 -11
- package/src/central/agent/message-routes.ts +10 -4
- package/src/central/agent/messages.ts +28 -2
- package/src/central/agent/names.ts +12 -2
- package/src/central/agent/placement.ts +93 -18
- package/src/central/agent/read-routes.ts +89 -19
- package/src/central/agent/routes.ts +178 -5
- package/src/central/agent/webhook-routes.ts +2 -1
- package/src/central/agent/webhooks.ts +17 -2
- package/src/central/agent-data.ts +4 -0
- package/src/central/artifacts/access.ts +44 -0
- package/src/central/artifacts/blob-store.ts +113 -0
- package/src/central/artifacts/blob-support.ts +98 -0
- package/src/central/artifacts/events.ts +27 -0
- package/src/central/artifacts/host-caps.ts +162 -0
- package/src/central/artifacts/host.ts +315 -0
- package/src/central/artifacts/idempotency.ts +109 -0
- package/src/central/artifacts/jobs-routes.ts +776 -0
- package/src/central/artifacts/jobs.ts +344 -0
- package/src/central/artifacts/maintenance.ts +103 -0
- package/src/central/artifacts/routes.ts +360 -0
- package/src/central/artifacts/store.ts +776 -0
- package/src/central/artifacts/workspaces.ts +31 -0
- package/src/central/backend/file-blobs.ts +377 -0
- package/src/central/backend/index.ts +12 -3
- package/src/central/backend/memory-blobs.ts +116 -0
- package/src/central/backend/postgres-blobs.ts +513 -0
- package/src/central/backend/postgres-migrate.ts +33 -0
- package/src/central/backend/postgres-state.ts +19 -0
- package/src/central/backend/types.ts +8 -0
- package/src/central/concierge/activity.ts +142 -0
- package/src/central/concierge/arm.ts +63 -0
- package/src/central/concierge/board-render.ts +190 -0
- package/src/central/concierge/board-results.ts +169 -0
- package/src/central/concierge/board-routes.ts +350 -0
- package/src/central/concierge/board.ts +249 -0
- package/src/central/concierge/context.ts +221 -0
- package/src/central/concierge/entitlements.ts +13 -0
- package/src/central/concierge/guard.ts +120 -0
- package/src/central/concierge/inbound.ts +86 -0
- package/src/central/concierge/index.ts +84 -0
- package/src/central/concierge/notes.ts +41 -0
- package/src/central/concierge/notifier.ts +986 -0
- package/src/central/concierge/prompt.ts +131 -0
- package/src/central/concierge/proposals.ts +268 -0
- package/src/central/concierge/push-remap.ts +49 -0
- package/src/central/concierge/routes.ts +122 -0
- package/src/central/concierge/schedule.ts +113 -0
- package/src/central/concierge/service.ts +243 -0
- package/src/central/concierge/session-routes.ts +125 -0
- package/src/central/concierge/settings.ts +217 -0
- package/src/central/concierge/supervisor.ts +767 -0
- package/src/central/concierge/tiers.ts +260 -0
- package/src/central/concierge/watchlist-routes.ts +279 -0
- package/src/central/concierge/watchlist.ts +273 -0
- package/src/central/config.ts +28 -6
- package/src/central/http.ts +660 -54
- package/src/central/main.ts +1 -1
- package/src/central/mcp-http.ts +10 -21
- package/src/central/profiles/apply.ts +333 -0
- package/src/central/profiles/content.ts +174 -0
- package/src/central/profiles/events.ts +40 -0
- package/src/central/profiles/idempotency.ts +14 -0
- package/src/central/profiles/import.ts +139 -0
- package/src/central/profiles/routes.ts +356 -0
- package/src/central/profiles/store.ts +597 -0
- package/src/central/profiles/usage-ports.ts +120 -0
- package/src/central/profiles/usage.ts +364 -0
- package/src/central/push.ts +7 -0
- package/src/central/registry.ts +69 -7
- package/src/central/store.ts +4 -0
- package/src/central/teams/collections.ts +34 -0
- package/src/central/teams/discovery.ts +346 -0
- package/src/central/teams/draft-edits.ts +46 -0
- package/src/central/teams/events.ts +46 -0
- package/src/central/teams/intake-routes.ts +100 -0
- package/src/central/teams/intake.ts +786 -0
- package/src/central/teams/policy.ts +231 -0
- package/src/central/teams/routes.ts +516 -0
- package/src/central/teams/seats.ts +272 -0
- package/src/central/teams/staffing.ts +910 -0
- package/src/central/teams/store.ts +667 -0
- package/src/central/teams/validation.ts +256 -0
- package/src/central/workspaces/board.ts +408 -0
- package/src/central/workspaces/checkpoints.ts +316 -0
- package/src/central/workspaces/coordinator.ts +841 -0
- package/src/central/workspaces/events.ts +85 -0
- package/src/central/workspaces/lifecycle.ts +93 -0
- package/src/central/workspaces/master.ts +490 -0
- package/src/central/workspaces/members.ts +632 -0
- package/src/central/workspaces/routes.ts +354 -0
- package/src/central/workspaces/sidechats-routes.ts +410 -0
- package/src/central/workspaces/sidechats.ts +482 -0
- package/src/central/workspaces/store.ts +571 -0
- package/src/central/workspaces/supervision.ts +355 -0
- package/src/index.ts +126 -23
- package/src/migrate.ts +109 -3
- package/src/spoke/concierge-agent.ts +33 -0
- package/src/spoke/deliver.ts +22 -4
- package/src/spoke/env-inject.ts +8 -1
- package/src/spoke/fork-revert.ts +142 -0
- package/src/spoke/hub-rpc.ts +41 -5
- package/src/spoke/hub-tool.ts +404 -25
- package/src/spoke/launchers.ts +6 -4
- package/src/spoke/listing.ts +65 -10
- package/src/spoke/localhub.ts +14 -4
- package/src/spoke/locations.ts +40 -0
- package/src/spoke/profile-apply.ts +298 -0
- package/src/spoke/session-context.ts +256 -0
- package/src/spoke/spoke.ts +330 -23
- package/src/spoke/timeline.ts +6 -1
- package/src/spoke/tracker.ts +23 -1
- package/src/spoke/who-teams.ts +81 -0
- package/src/tui-messaging.ts +32 -18
- package/web/dist/assets/ArtifactPage-mNzjlP_U.js +1 -0
- package/web/dist/assets/ArtifactPane-CPQcuX7c.js +1 -0
- package/web/dist/assets/ArtifactWorkbench-9eTj1CeW.css +1 -0
- package/web/dist/assets/ArtifactWorkbench-NoFC5Sb2.js +6 -0
- package/web/dist/assets/Library-DYsFj_5c.css +1 -0
- package/web/dist/assets/Library-LXHsP5Fw.js +2 -0
- package/web/dist/assets/ProfilesDestination-DhVVcAep.js +1 -0
- package/web/dist/assets/TeamsPage-DBrO5hoL.js +1 -0
- package/web/dist/assets/Watchtower-CTgqHk5g.css +1 -0
- package/web/dist/assets/Watchtower-DxaJYQp_.js +2 -0
- package/web/dist/assets/index-BabrNDB3.css +1 -0
- package/web/dist/assets/index-lIOfSYck.js +34 -0
- package/web/dist/assets/live-BDq0AABg.js +5 -0
- package/web/dist/assets/live-BUofsQpz.css +1 -0
- package/web/dist/assets/pane-layout-DWXfDU-1.js +1 -0
- package/web/dist/assets/plural-Bspvoqfq.js +1 -0
- package/web/dist/assets/renderer2d-DAr4IHM8.js +1 -0
- package/web/dist/assets/renderer3d-DWZj595u.js +5393 -0
- package/web/dist/assets/safe-area-JUb6OeI3.js +1 -0
- package/web/dist/assets/source-CDU04kYw.js +1 -0
- package/web/dist/assets/ui-Dh1-78Cl.js +3 -0
- package/web/dist/index.html +5 -2
- package/web/dist/sw.js +22 -2
- package/web/dist/assets/index-Dm8OmkaO.css +0 -1
- package/web/dist/assets/index-FUJbj_Du.js +0 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,435 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
A fix-up release after 0.8.0. App and gateway messages keep waking idle sessions (per-conversation hops with a relay
|
|
6
|
+
guard, and the reason when a wake is skipped); launches say why they wait; the Concierge gets per-machine Directory
|
|
7
|
+
and Model dropdowns, full-detail Decision cards (tap-tier ones too, never by voice), plainer Activity and fail-closed
|
|
8
|
+
hardening; the console uses in-page dialogs, collapsible sidebar sections and names instead of raw IDs; the right
|
|
9
|
+
rail's Branch and Changes work for background-service sessions; the hub state dir honours `XDG_STATE_HOME` without
|
|
10
|
+
moving existing state; MCP gains profile lifecycle tools. No migration is needed. Codex and Claude Code fork/revert
|
|
11
|
+
remain for a later release.
|
|
12
|
+
|
|
13
|
+
### MCP: profile lifecycle and team staffing
|
|
14
|
+
- **Profile lifecycle over MCP** (`hub mcp`, `POST /mcp`): `profile_show`, `profile_versions`, `profile_create`,
|
|
15
|
+
`profile_update`, `profile_version_create` (content or `fromVersion` rollback), `profile_skill_set` /
|
|
16
|
+
`profile_skill_remove`, `profile_command_set` / `profile_command_remove` (attach or detach one skill or command as
|
|
17
|
+
the next immutable version), `profile_archive` / `profile_restore` and `profile_assign` / `profile_unassign`. Each
|
|
18
|
+
is an existing `/api/agent/profiles…` route with its scopes (`profiles:read`, `profiles:manage`,
|
|
19
|
+
`profiles:assign` + `sessions:read`); no server change ([docs/profiles.md](docs/profiles.md#sdk-and-mcp)).
|
|
20
|
+
- **`team_staff` / `team_replace` describe the nested launch spec** (`workspace{repo, mode, branch, base, remote}`,
|
|
21
|
+
`placement{machine, labels, mode}`, `runtime{kind, profile}`, `end{when, timeout, cleanup}` and the top-level
|
|
22
|
+
fields) in their input schema ([docs/teams.md](docs/teams.md#mcp-tools)).
|
|
23
|
+
- **Launch specs name misplaced fields**: an unknown launch field answers every unknown field at once and where each
|
|
24
|
+
belongs (`Unknown launch fields repo, cleanup: did you mean workspace.repo, end.cleanup?`), with the nested shape
|
|
25
|
+
as the hint, for `POST /api/agent/launches`, team staffing and the MCP tools.
|
|
26
|
+
|
|
27
|
+
### Messaging and Concierge settings
|
|
28
|
+
- **Settings → Concierge: Directory and Model are dropdowns of the picked machine.** Directory lists the projects its
|
|
29
|
+
OpenCode service reports under (or at) its node's workspace roots, then the other roots, with **Other folder…** for
|
|
30
|
+
a typed path (a project that is the root itself is listed once, as a project, and the hint names the roots: FX-11);
|
|
31
|
+
picking another machine clears a directory that machine does not report. Model lists the machine's OpenCode models
|
|
32
|
+
with **Machine default** and **Other model…**; picking another machine resets a model its list lacks. Loading,
|
|
33
|
+
offline and error states keep the typed fallback; hints are linked to the dropdowns (`aria-describedby`). New
|
|
34
|
+
user-only route `GET /api/agent/concierge/machines/:id/models` (404 for a machine that is not yours, 503
|
|
35
|
+
`offline` without a connected service, a reported session, or a spoke answer).
|
|
36
|
+
- **App asks stop waking sessions after 4 round trips (S1, wge0vjdz).** An app token was one hop-depth party for all
|
|
37
|
+
its conversations and had no human turn to reset it, so every ask/reply round trip raised it by 2 and from the 5th
|
|
38
|
+
ask on (hop 9 > the cap of 8) nothing it sent woke anyone for 24 h; the spoke answered a bare `queued`. Apps now
|
|
39
|
+
count hops per conversation: a new `msg`/`ask` from an app is hop 1, replies and declines to an app raise nothing,
|
|
40
|
+
and an app's own reply or decline is the hop of the message it answers + 1 (reply ping-pong still hits the cap).
|
|
41
|
+
Session-to-session chains, human-turn resets, the pair limit (20/10 min) and the wake budget (30/10 min, which
|
|
42
|
+
bound an app answering one session with new messages forever) are unchanged. Covers app tokens, `app:concierge`,
|
|
43
|
+
the workspace coordinator and team intake.
|
|
44
|
+
- **Relay guard for apps.** For 5 minutes (the relay window, a code-level default, not a config setting) after a session sends an app something, the app's new
|
|
45
|
+
msg/ask to *another* session carries that hop + 1, so a relay loop A → app → B → app → A reaches the hop cap after
|
|
46
|
+
about 8 hops instead of only the rate limits; spaced-out asks and one-to-one app conversations still start at hop
|
|
47
|
+
1. The hub's own parties (Concierge, workspace coordinator, team intake) keep no relay depth: they send on events
|
|
48
|
+
and submissions, not on what they receive.
|
|
49
|
+
- **Team intake continues the submitter's chain.** A session's submission is delivered from `app:team-intake` at
|
|
50
|
+
the submitter's depth + 1 (kept while it is queued), not hop 1.
|
|
51
|
+
- **Hop-cap alert wording.** "Agent messages went 8 hops without you (latest: A → B) …" instead of "Sessions
|
|
52
|
+
messaged each other", which was wrong when an app was in the chain.
|
|
53
|
+
- **Never give agents an app token** (README): it lets them send at a fresh hop depth.
|
|
54
|
+
- **Why a message did not wake.** `POST /api/agent/msg|ask|reply|decline` answers carry `woke` and, when false,
|
|
55
|
+
`wakeSkipped: wake_false | subagent | hop_cap | budget` (additive; SDK `Sent.woke`, `Sent.wakeSkipped`, type
|
|
56
|
+
`WakeSkip`, and `wakeSkipNote`/`WAKE_SKIP_TEXT` for the shared wording). `hub msg`/`ask`/`reply`/`decline`, the
|
|
57
|
+
MCP tools and the OpenCode `hub` tool (text and metadata) print it: `queued (read at its next turn) — wake
|
|
58
|
+
skipped: hop limit reached (8 agent messages without a human turn)`; `wake_false` reads "sent without a wake".
|
|
59
|
+
|
|
60
|
+
### Launches say why they wait
|
|
61
|
+
- A queued launch stores why it waits: `GET /api/agent/launches/:id` carries `waiting: {wait, reason, since}` and
|
|
62
|
+
`reason` is the same text. Written only when it changes, announced as a `launch` event and logged once per change
|
|
63
|
+
(`[hub] launch <id> waits (<wait>): <reason>`); cleared once placed. Before, it said only "Queued".
|
|
64
|
+
- Specific reasons: the active cap names who holds it (`4 of 4 active launches: 3 by app "ci-bot", 1 by you`), a
|
|
65
|
+
full node gives its counts (`baruch (4/4 used, 0 reserved)`), a node that does not fit its first problem.
|
|
66
|
+
- `hub launches list|show` and the MCP launch tools print the wait; Settings → Agents & access has a new **Launches**
|
|
67
|
+
list (live launches, updated from the event stream, why each waits, Stop with a per-row accessible name; the
|
|
68
|
+
Concierge's own launch is managed in Settings → Concierge); the Concierge's status says "Waiting: …" instead of
|
|
69
|
+
"Starting".
|
|
70
|
+
- An app or session sees its own launch's wait in a launcher-safe form: counts only for the cap ("1 yours, 3 by
|
|
71
|
+
others"), no machine names. The user sees the full text.
|
|
72
|
+
- Only a new wait kind is a launch change (journal `launch.changed` with `data.wait`, SSE); never a webhook, so
|
|
73
|
+
webhooks keep one delivery per state (`launch.queued` once). Text-only updates (slot counts) go to the console's
|
|
74
|
+
live stream only; artifact jobs show them without a new job revision.
|
|
75
|
+
- The Concierge's own launch no longer counts toward `agents.launches.maxActive` (it still takes a node slot).
|
|
76
|
+
- Revoking an app token does not stop its launches; they are flagged (`launcherRevoked`) in the user's lists and in
|
|
77
|
+
the cap's waiting reason so the user can stop them. Launches record the app token's label (`byLabel`).
|
|
78
|
+
- A node cleanup that failed while the node stayed connected is retried (capacity reports, the 15 s sweep, up to 5
|
|
79
|
+
times; and on reconnect), so the node no longer keeps a slot for an ended launch.
|
|
80
|
+
- A running launch ends `lost` when the same node instance that reported it leaves it out of its reports for 5
|
|
81
|
+
minutes while no spoke reports its session, freeing its reserved slot. Never on a disconnected node, a short gap,
|
|
82
|
+
or a restarted node daemon (a new instance) that never reported it; a lost launch whose session is still reported
|
|
83
|
+
keeps its workspace. `agents.launches.lostAfterMinutes`: 0–1440, or 0/false to turn it off.
|
|
84
|
+
- A launch stopped between placement's placing and provisioning steps (e.g. the Concierge reaping an orphan) is
|
|
85
|
+
skipped quietly instead of logging a placement error.
|
|
86
|
+
- Engine timers are clamped to setTimeout's 32-bit limit; an `end.timeout` over 24.8 days (up to 30) no longer fires
|
|
87
|
+
at once.
|
|
88
|
+
|
|
89
|
+
### Concierge hardening
|
|
90
|
+
- **Activity in plain words.** Settings → Agents → Activity says what the Concierge did without internal terms:
|
|
91
|
+
"Approved by the Concierge inside your grant. Its note: «…»", "Needed your tap: shell commands, outside folders
|
|
92
|
+
and tools always do", "The Concierge proposed this for your answer", "Refused: only you can message the
|
|
93
|
+
Concierge". Its note is framed as data (its own « » become ‹ ›, bidi and invisible controls dropped, one line, at
|
|
94
|
+
most 300 characters), so it cannot fake text after it.
|
|
95
|
+
No `inGrant`, `auto` or raw Decision IDs; the Concierge is told to write its why in plain words too
|
|
96
|
+
(Concierge prompt v7).
|
|
97
|
+
- **Decision lookups in the Concierge panel** skip IDs in a reply that is still streaming (half an ID was looked up
|
|
98
|
+
and remembered as unknown), back off on 429/5xx/network failures (2 s doubling, ≤ 1 min) instead of retrying on
|
|
99
|
+
every render, and run at most 4 at once.
|
|
100
|
+
- **Watchlist after a reconnect.** A process counts as reporting its sessions only after its first sessions frame
|
|
101
|
+
(catalog `sessionsPending`, additive), so the watchlist and board no longer empty for a moment after `hello`.
|
|
102
|
+
- **`concierge_find`** with no letters or digits ("@", "?!") no longer lists every session: the hub's
|
|
103
|
+
`GET /api/agent/sessions?q=` matches nothing for such a `q` (a blank `q` still filters nothing).
|
|
104
|
+
- **Concierge session lock file** (`concierge-sessions.json`): writes from several OpenCode processes are serialized
|
|
105
|
+
under an exclusive `<file>.lock` (a stale one is broken), so no process drops another's locks.
|
|
106
|
+
- **Identity:** a Concierge session older than the 500 the hub keeps still counts as one while its machine reports
|
|
107
|
+
it locked (spokes mark locked sessions `locked: true` from their first sessions frame after a restart). Concierge
|
|
108
|
+
identity reads (route gate, manage guard, acting guard, inbound/outbound rules, team staffing, Decision and audit
|
|
109
|
+
labels) wait at most 5 s, and a user whose read timed out is answered at once for the next 5 s; a stuck or failed
|
|
110
|
+
read refuses with a neutral 503 `busy` "Hub state is busy, try again" (new error code, CLI exit 7), audited as
|
|
111
|
+
decision `busy`, outcome `refused`; team staffing
|
|
112
|
+
(attach, replace, a staffing launch) refuses a Concierge session in the service as well as the route.
|
|
113
|
+
- **Arm badge:** the console lapses an arm at its expiry itself, so a missed `armed: null` never leaves a stale ✨.
|
|
114
|
+
|
|
115
|
+
### Console polish
|
|
116
|
+
- **Collapsible sidebar sections**: Needs you, Running, Pinned, the session groups, Recents and Workspaces collapse
|
|
117
|
+
from their heading (mouse or keyboard, `aria-expanded`), remembered on this device. A collapsed section shows its
|
|
118
|
+
count; Needs you always shows its count, so collapsing it never hides that something waits.
|
|
119
|
+
- **In-page confirm dialog** instead of the browser's `window.confirm` (Revert, stopping a launch, turning the Concierge off, revoking app tokens, grants,
|
|
120
|
+
machines and devices, turning agent actions off, signing out, admin user actions, resetting speech settings):
|
|
121
|
+
focus starts on the primary action (Enter confirms), on Cancel for destructive (red) actions; Tab stays inside; a
|
|
122
|
+
held Enter (key auto-repeat) is ignored; Escape or Cancel cancels (Escape closes only the dialog, not the
|
|
123
|
+
Concierge panel or a popover behind it); 44 px buttons on touch screens.
|
|
124
|
+
- **/new**: a machine that reports exactly one project has it chosen; Enter without a project says so and moves the
|
|
125
|
+
focus to the Project chip, which stands out while nothing is chosen.
|
|
126
|
+
- **Concierge panel** is fitted with the sidebar and rail: at 1280 px the transcript keeps 420 px (it had 345); at
|
|
127
|
+
1024 px it gets 304 px with every panel at its minimum (it had 119), 420 px without a rail.
|
|
128
|
+
- Fixes: a path with a leading dot in the Changes list (`.claude/settings.local.json`) no longer shows the dot at the
|
|
129
|
+
end; "Denied by you" shows once when a denial stops the turn; a grant's expiry rounds to the nearest whole day
|
|
130
|
+
everywhere (no more "7 d" on one page and "6 d" on another); Decision links in the Concierge panel no longer push
|
|
131
|
+
the text after them to a new line; the panel's disabled Send button looks disabled.
|
|
132
|
+
- **The right rail's Branch and Changes come from the session's own directory** (FX-10). OpenCode's plugin `vcs`
|
|
133
|
+
API answers for the plugin instance's own location whatever location is passed, and the spoke used the first
|
|
134
|
+
instance's (the background service's is its `$HOME`), so a service session in a project showed "No changes." and
|
|
135
|
+
Branch "—" (and the per-file diff read the wrong tree). The spoke now reads through the plugin instance whose
|
|
136
|
+
location is the session's directory, else the own server's API (which honours the location), else shows nothing;
|
|
137
|
+
a read that takes over 5 s (a hung server) is left out, so the rest of the summary still answers.
|
|
138
|
+
|
|
139
|
+
### Robustness and the hub state directory
|
|
140
|
+
- **State dir honours `XDG_STATE_HOME`, sticky**: the plugin (launchers, Concierge lock), the local hub, the hub
|
|
141
|
+
server and the Claude Code and Codex spokes resolve it the same way: `$HUB_STATE_DIR`; else `$XDG_STATE_HOME/hub`
|
|
142
|
+
if it exists; else an existing `~/.local/state/hub`; else `$XDG_STATE_HOME/hub` when XDG_STATE_HOME is set (fresh
|
|
143
|
+
installs); else `~/.local/state/hub`. The legacy `opencode-hub` / `opencode-voice` state migrates from under
|
|
144
|
+
`$XDG_STATE_HOME` and `~/.local/state`. `hub claude-code install` / `hub codex-spoke install` carry
|
|
145
|
+
`XDG_STATE_HOME` into the service. The Concierge lock is read from both default places (written to the resolved
|
|
146
|
+
one), so a session locked before the upgrade stays locked; with `HUB_STATE_DIR` set (an isolated preview or test)
|
|
147
|
+
it is read and written only there.
|
|
148
|
+
**Upgrade note:** nothing moves. If `XDG_STATE_HOME` is set and you have `~/.local/state/hub`, the hub keeps using
|
|
149
|
+
it and logs one line at startup with the command to switch: stop the hub and OpenCode, then
|
|
150
|
+
`mv ~/.local/state/hub "$XDG_STATE_HOME/hub"`. Isolated test/preview instances must set `HUB_STATE_DIR` and their
|
|
151
|
+
own `HOME`; `XDG_STATE_HOME` alone does not isolate them from an existing `~/.local/state/hub`.
|
|
152
|
+
A dir counts only when it holds hub state (`state.json`, `token-keys.json`, `agent/`, `blobs/`,
|
|
153
|
+
`concierge-sessions.json`, `bin/`, `hub.log`, `hub.pid`, `claude-code/`, `codex/`), never `bootstrap/` alone, and
|
|
154
|
+
`hubctl setup` now writes `bootstrap/` into the dir the hub resolves (its state root follows the resolver; other
|
|
155
|
+
roots unchanged). The legacy `opencode-hub` / `opencode-voice` copy also fills a hub dir that holds no hub state
|
|
156
|
+
yet (e.g. only `bootstrap/` from an earlier setup), next to `bootstrap/` and never over it. A relative
|
|
157
|
+
`XDG_STATE_HOME` is ignored (XDG spec). **Service units must carry the same `XDG_STATE_HOME` as your shell** (or `HUB_STATE_DIR`), or a
|
|
158
|
+
shell and a unit can use different dirs: hub's own `claude-code` / `codex-spoke` units already pass it; a unit you
|
|
159
|
+
wrote for `opencode serve --service` needs it set.
|
|
160
|
+
- **Warmer hardening** (`hub node`): OpenCode's `service.json` is used only when it is a regular file owned by this
|
|
161
|
+
user and not group/world-writable (no symlinks), and its pid is this user's live process (EPERM is not ours). Warm
|
|
162
|
+
requests wait 30 s by default (was 10 s), configurable with node.json `opencode.timeoutSeconds` (1–300).
|
|
163
|
+
- `hubctl setup --verify-launch` warns about a stored plain-http (non-loopback) login, like every `hub` command.
|
|
164
|
+
- Refused hub URLs (`hub login`, `hubctl machine enroll`) no longer echo user info: shown as `https://***@host`
|
|
165
|
+
(also `http:/u:p@h`, `http:u:p@h`, `http:\\u:p@h`, leading whitespace and scheme-less `user:pass@host`).
|
|
166
|
+
- **Listings are cancelled**: a session listing that times out aborts its signal (`SpokeHost.listRecent` gets
|
|
167
|
+
`signal`, passed to every OpenCode request), so a hung server does not collect abandoned listings.
|
|
168
|
+
- **Pending messages tell their sender when they expire**: a message still pending after 24 h (recipient offline or
|
|
169
|
+
busy) gets delivery `"expired"` (new `MessageDelivery` value), its sending session gets a quiet hub notice
|
|
170
|
+
(`wake: false`, never the text), the user an alert (push kind `message`), and the change is journaled
|
|
171
|
+
(`message.changed`).
|
|
172
|
+
- `HEAD /healthz` and `HEAD /readyz` answer like GET (same status and headers, no body) instead of 404, for uptime
|
|
173
|
+
monitors and `curl -I`. No other route changed.
|
|
174
|
+
- Docs: README "Testing and preview isolation" (`HUB_STATE_DIR`, own `HOME`/`XDG_*`, `herdr: false`,
|
|
175
|
+
`autoStartLocalHub: false`, sessions only in scratch repos).
|
|
176
|
+
|
|
177
|
+
### Decision cards and the Concierge panel
|
|
178
|
+
- **Decision cards say what they ask for** (FX-05): the Concierge panel's armed and listed cards, and every Decision
|
|
179
|
+
card on Home and in sessions, show the requested action as a session's own permission card does: the permission
|
|
180
|
+
kind, the exact command, paths or patterns (the session's live request when the console has it), who asked, why it
|
|
181
|
+
is outside the grant and the stated purpose. A Concierge proposal shows its verb ("Send a prompt as you", "Stop the
|
|
182
|
+
session", …), the target session, the exact prompt or form answer, and the Concierge's why; its question heads the
|
|
183
|
+
card without repeating the body. A plain ask about a session that waits on one permission shows that request.
|
|
184
|
+
Long commands and prompts wrap and are cut with **Show all** (a session's permission card too). All of it is
|
|
185
|
+
rendered as plain text, from the user's own console data: the Concierge's board and update lines still carry no
|
|
186
|
+
blocker summaries or Decision text.
|
|
187
|
+
- **The card says where its detail comes from** (FX-05): "Live request" (the session's pending request), "The hub's
|
|
188
|
+
record of the request" (new structured fields on Decisions: `action.request {action, resources, save?}` and
|
|
189
|
+
`action.reason` on escalations, `action.request` on deny proposals; bounded to 20 × 2,000 characters), or "From
|
|
190
|
+
the request text" for older Decisions, now parsed without splitting a command at ", " or at a ". Outside its
|
|
191
|
+
grant: " inside it. Bidi and invisible characters in commands, paths and prompts show as markers ("⟨U+202E⟩"),
|
|
192
|
+
also on a session's permission card (tag characters U+E0000–E007F, separators, fillers too; a valid subdivision
|
|
193
|
+
flag and ZWJ emoji stay intact); text is cut by grapheme; **Show all** buttons name their field and control the
|
|
194
|
+
text they open. What the hub did not store is recorded with the request (`resourcesTotal`, `savesTotal`, `cut`
|
|
195
|
+
[{field, index, length}], `truncated`) and shown on the card ("… cut at 2,000 characters (of 3,017)", "+10 more
|
|
196
|
+
items not stored", "The hub received this request already shortened"), so a shortened command never reads as
|
|
197
|
+
complete. The structured request and reason are for the user's console only: the Concierge's Decision reads
|
|
198
|
+
(`/api/agent/concierge/decisions`, `concierge_decision`) return the records exactly as before.
|
|
199
|
+
- **Tap-tier Decisions in the Concierge panel** (FX-05b): the panel lists the Concierge's tap-tier Decisions (shell,
|
|
200
|
+
outside folders, tools, prompt/answer proposals) with the same detail, answered by a tap on the card. They are
|
|
201
|
+
never armed and never answered by voice or text; the button's badge and the panel's list now agree.
|
|
202
|
+
- **Fix: Decisions and the armed card no longer vanish at random** (quirk pgd96twg): the open list and the armed
|
|
203
|
+
Decision reloaded on every stream open replaced what the stream had said while the reload was out. A reload's
|
|
204
|
+
snapshot now merges the Decision events that came meanwhile (and an older reload never overrides a newer one),
|
|
205
|
+
and an arm event that came while `GET …/arm` was out wins over its answer. A load out for over 30 s is forgotten
|
|
206
|
+
(no events buffered forever), and an older `GET …/arm` never overrides a newer one.
|
|
207
|
+
|
|
208
|
+
- **Sessions by name, never a raw ID** (FX-12): Decision cards (their heading, "from", Requested by and Session), a
|
|
209
|
+
session's permission card (the recommender, who answered), hub message rows, Settings → Agents & access Activity
|
|
210
|
+
and Settings → Concierge's watch and mute lists name a session by its @name (the hub directory), else its title,
|
|
211
|
+
else its project folder and a short ID ("hub · ses_e6256f…"); the full ID is in the tooltip. An escalation's
|
|
212
|
+
heading is built from the hub's structured request ("API refactor asks to approve shell"; the command and reason
|
|
213
|
+
are in the details), other hub-written questions have their `ses_…` IDs named. Activity says "a permission
|
|
214
|
+
request", "a form" and "grant", with the IDs on hover. Display only: the data, the hub and what the Concierge
|
|
215
|
+
model sees are unchanged.
|
|
216
|
+
|
|
217
|
+
## 0.8.0
|
|
218
|
+
|
|
219
|
+
Workspaces, supervision, split panes and side chats, shared agent profiles, persistent AI-powered artifacts, teams,
|
|
220
|
+
and the Watchtower. All new state is additive. Cluster hubs need `hub-server migrate` (schema v2) before the new
|
|
221
|
+
pods start. See [docs/operations.md](docs/operations.md) for the rollout, rollback, backup and front-proxy notes.
|
|
222
|
+
|
|
223
|
+
### Fork and revert a message
|
|
224
|
+
- Hovering (or focusing) one of your messages in the console shows **Copy · Fork · Revert**, like the OpenCode app;
|
|
225
|
+
on a phone, long-press it or tap its ⋯ button. **Fork** opens a new session with the conversation before the
|
|
226
|
+
message and the message in its composer. **Revert** hides the message and everything after it, restores the files
|
|
227
|
+
the session changed since then (confirming first, with the list), and puts the message back in the composer; a
|
|
228
|
+
banner offers **Restore**; sending the next message makes it permanent. Works across devices and in panes.
|
|
229
|
+
- New console routes (user-only, audited): `POST /api/sessions/:id/fork`, `GET …/revert/preview`, `POST …/revert`,
|
|
230
|
+
`POST …/revert/restore`, `POST …/revert/commit`. Spoke features `fork` and `revert` (OpenCode processes that reach
|
|
231
|
+
their own server) with ops `fork`, `revertPreview`, `revert`, `revertRestore`, `revertCommit`; a staged revert is
|
|
232
|
+
reported as `SessionInfo.revert` (additive: older hubs ignore it). Other harnesses show the actions disabled
|
|
233
|
+
("not supported yet"). Contract: [docs/session-view.md](docs/session-view.md#fork-and-revert-a-message).
|
|
234
|
+
|
|
235
|
+
### Workspaces and supervision
|
|
236
|
+
- **Workspaces** ([docs/workspaces.md](docs/workspaces.md)): durable, cross-machine coordination units with host repo
|
|
237
|
+
bindings, members (`master`/`worker`/`observer`, generations), explicit assignments and a supervision graph
|
|
238
|
+
(acyclic, ≤ 6 levels) kept apart from native `parentID`. Membership and supervision grant nothing. New routes under
|
|
239
|
+
`/api/agent/workspaces…`, SDK `hub.workspaces`, 15 `workspace_*` MCP tools, and compact `workspace.*` events.
|
|
240
|
+
- **Drag to supervise**: the sidebar's **Workspaces** tree shows each workspace's members under their supervisors.
|
|
241
|
+
Drag (or use Alt+arrows or the Move menu) to supervise, reorder or detach. "Spawned by" and "supervised by" are
|
|
242
|
+
shown separately.
|
|
243
|
+
- **Situation board** (`GET …/board`): one bounded, text-free snapshot of members, presence, blockers, assignments,
|
|
244
|
+
Decisions and artifacts, with `stale`/`omitted`/`failed` sections instead of guesses.
|
|
245
|
+
- **Supervisor digests and checkpoints**: masters and supervisors get batched, at-least-once digests as hub messages
|
|
246
|
+
from `app:workspace-coordinator`. `POST …/checkpoints` acknowledges them durably (flushed before the answer).
|
|
247
|
+
- **Quieter digests**: routine updates (including snapshots) are delivered without waking an idle supervisor (2 min
|
|
248
|
+
quiet, 15 min max) and repeats of already-known states are dropped; assignment closures wake without interrupting
|
|
249
|
+
(30 s quiet, 5 min max); blockers, errors and urgent snapshots still wake and interrupt. A cleared error is still
|
|
250
|
+
reported, followed by the recovery. A member's session that disappears and comes back within one batch (a spoke
|
|
251
|
+
restart or reconnect) is not reported.
|
|
252
|
+
- **Master replacement** (`POST …/master {sessionID | launchID}`): explicit, fenced, with the next generation. Supervised
|
|
253
|
+
members move to the new master. No grants move.
|
|
254
|
+
|
|
255
|
+
### Panes and side chats
|
|
256
|
+
- **Split panes**: **Open beside** shows up to 4 sessions or artifacts side by side, stacked or in a grid. The focused
|
|
257
|
+
pane owns the composer and Voice. Layouts are per device. On phones the panes are tabs. `/api/events` takes
|
|
258
|
+
`sessions=a,b` for the visible panes.
|
|
259
|
+
- **Drag into panes**: drag a session (sidebar, Workspaces tree, Watchtower detail), an artifact (library) or a pane
|
|
260
|
+
(header grip) onto a pane: edges split side by side or stacked, the centre replaces, the layout reflows to a grid
|
|
261
|
+
when needed, and a fifth pane is refused with a notice. **Open in pane → left/right/top/bottom** in the session
|
|
262
|
+
menu is the keyboard equivalent. The layout switch is now a compact segmented control with icons.
|
|
263
|
+
- **Side chats**: **Start side chat…** copies a bounded snapshot of a session into a new session on the workspace's
|
|
264
|
+
host binding (any machine), in a pane beside it, with **Refresh** and a confirmed **Send to main**. Neither
|
|
265
|
+
interrupts a running turn.
|
|
266
|
+
|
|
267
|
+
### Agent profiles
|
|
268
|
+
- **Shared, versioned profiles** ([docs/profiles.md](docs/profiles.md)): instructions, skills and commands (with
|
|
269
|
+
`{{param}}`, `{{arguments}}` and spoke-side `{{secret:NAME}}`), immutable versions, and import from
|
|
270
|
+
`AGENTS.md`/`CLAUDE.md`, `skills/*/SKILL.md` and `commands/*.md`. You pin an exact version to a session at runtime
|
|
271
|
+
(the profile chip), to a launch (`agentProfile`) or to a team role. The **Agent profiles** page is the library.
|
|
272
|
+
**Ctrl+Alt+P** inserts a rendered command into the composer.
|
|
273
|
+
- **Per-harness application** through the new spoke feature `profileApply`, with honest acknowledgments (`applied`,
|
|
274
|
+
`partial`, `restart_required`, `unsupported`, `rejected`, `pending`): OpenCode (system prompt + `hub_profile`
|
|
275
|
+
tool), Claude Code (hook context, `set_model` in launched sessions), Codex (developer message + skill files);
|
|
276
|
+
presets are always `unsupported`. Local instructions win. No global harness config is written.
|
|
277
|
+
- **Profile metadata and usage**: optional `metadata` and `projects` on a profile, and **Used by**
|
|
278
|
+
(`GET /api/agent/profiles/:id/usage`, SDK `hub.profiles.usage()`, MCP `profile_usage`): pinned sessions with
|
|
279
|
+
requested vs running version and an explicit Upgrade, team roles, launches and workspaces, each shown only under the
|
|
280
|
+
caller's existing read rules (`hidden` names what was left out). The library rows show usage chips, with filters and
|
|
281
|
+
sort.
|
|
282
|
+
|
|
283
|
+
### Artifacts and AI
|
|
284
|
+
- **Persistent artifacts** ([docs/artifacts.md](docs/artifacts.md)): revisions of content-addressed blobs (files
|
|
285
|
+
standalone, chunked Postgres in a cluster), `private` or `workspace` visibility, archive and delete, staged uploads up
|
|
286
|
+
to 25 MiB, and downloads always served as attachments. New routes, SDK `hub.artifacts`, and `artifact_*` MCP tools.
|
|
287
|
+
- **Viewers and dashboards**: markdown, code, images, tables, charts, and declarative dashboards (schema v1, bindings
|
|
288
|
+
to other artifacts only, no URLs or scripts), plus a text editor with conflict-safe drafts.
|
|
289
|
+
- **AI jobs**: ask, propose an edit or run a declared action. The job runs on a handler you choose (a new launch or an
|
|
290
|
+
existing session), always with the requester's authority. Proposals apply only when you **Accept**. Jobs survive hub
|
|
291
|
+
restarts, and each job times out after 2 h.
|
|
292
|
+
- **Viewer redesign**: a header bar with kind icon, version picker (**Back to latest**), Copy, Download, Edit, Open in
|
|
293
|
+
pane and a details popover; content in a framed card (code with file/language header, line numbers, wrap; markdown
|
|
294
|
+
at reading width; dashboards and apps full-bleed); inline editing; and AI as a chat-style side panel (thread,
|
|
295
|
+
composer, Ask / Edit / Action or slash commands, compact handler picker that never preselects), resizable, a
|
|
296
|
+
bottom sheet on phones.
|
|
297
|
+
|
|
298
|
+
### Isolated artifact host
|
|
299
|
+
- Interactive `html`/`app` artifacts run only at `/artifact-host/v1/<capability>/…`: an opaque-origin sandbox with a
|
|
300
|
+
strict CSP, short-lived, viewer-bound and revocable capabilities, and a bridge (`resize`, `askAI`). Every `askAI`
|
|
301
|
+
becomes a consent card in the artifact's AI panel thread ([docs/artifact-host.md](docs/artifact-host.md)).
|
|
302
|
+
|
|
303
|
+
### Teams
|
|
304
|
+
- **Teams** ([docs/teams.md](docs/teams.md)): versioned definitions with roles, one entry role, `open` or `restricted`
|
|
305
|
+
communication paths (`one-way`/`two-way`, `msg`/`ask`), and a draft → publish flow. A session can belong to many
|
|
306
|
+
teams. The hub **enforces** the policy on delivery (`403 forbidden` with `extra.policy`). Team members cannot contact
|
|
307
|
+
sessions outside their teams unless a role has `externalContact`. Sessions in no team are unchanged.
|
|
308
|
+
- **Staffing** with existing or newly launched sessions under your grants (`403 needs_user` lists what is missing).
|
|
309
|
+
**Intake** gives work to the team's entry role and tracks it as an assignment ([docs/teams-intake.md](docs/teams-intake.md)).
|
|
310
|
+
- **Team addresses** `@@role#team` and `@@role.project#team` (quote `#` in shells). `/who`, `status` and `show` are
|
|
311
|
+
team-aware for team members.
|
|
312
|
+
- Malformed addresses answer `Not an address: use ses_…, @name, @@role or @@role#team` with a syntax-only hint (for
|
|
313
|
+
`@engineer#support`: `@@engineer#support` or `@engineer`); the hint never reveals whether a name exists.
|
|
314
|
+
- Entry-role sessions report and close their intake from the harness: OpenCode `hub` tool verbs `intake` and
|
|
315
|
+
`intake_update`, MCP `team_intake_update` (`expectedRevision` now optional). The intake message says how.
|
|
316
|
+
- **Teams page** (`/teams`, in the primary navigation): team cards (mode, entry role, seats, vacant roles, open work),
|
|
317
|
+
**New team** in a dialog, and per team **Members** (one block per role, **+ Add member** with a searchable session
|
|
318
|
+
picker that shows other memberships, **Launch new …**), **Work** (Submit a task, live intake status with links to
|
|
319
|
+
the entry session and its assignment), **Communication** and **Details** (draft → Review and publish, history),
|
|
320
|
+
Pause/Archive and **Edit in Watchtower**. The Watchtower's team panel uses the same components and links back.
|
|
321
|
+
- **Team isolation** (owner setting, default off): Settings → Teams, or `GET/PUT /api/agent/team-settings`
|
|
322
|
+
`{teamIsolation, expectedRevision}` (users only; SDK `hub.teams.settings()` / `setSettings()`). On: team members
|
|
323
|
+
see only their teams' sessions.
|
|
324
|
+
|
|
325
|
+
### Watchtower
|
|
326
|
+
- Renamed from **Bird's-eye view** (new lookout icon). `/birds-eye` links redirect to `/watchtower` with their
|
|
327
|
+
filters, and the browser's saved view and presets (`birdsEye.*`) move to `watchtower.*` once.
|
|
328
|
+
- A full-screen page ([docs/watchtower.md](docs/watchtower.md)) of machines, sessions, you, your apps and teams, with
|
|
329
|
+
live message flow: 3D (lazy `3d-force-graph`), a deterministic 2D canvas, or a list (also used for reduced motion).
|
|
330
|
+
It has URL-state filters and presets, team clusters, drawn vs actual paths and denied attempts, and a **team
|
|
331
|
+
editor** to assemble, staff, publish and submit work. New read API `GET /api/agent/graph` (+ `/edges/:id`,
|
|
332
|
+
`/nodes/:id`).
|
|
333
|
+
- Graph first: the controls are a floating **dock** at the bottom centre (view, Filters with an active count, Legend,
|
|
334
|
+
Teams, Hub details, Reset view, Pause motion, Search) that fades when idle. **Filters** is closed by default and
|
|
335
|
+
shows only the filters you add; active filters are removable chips over the canvas. The **Legend** has a visible
|
|
336
|
+
close and Escape closes it. On phones, filters, legend, details and the team editor are bottom sheets.
|
|
337
|
+
- A look in the style of Brain Graph (MIT, credited in the docs): glowing points in coloured group regions, fibers that
|
|
338
|
+
brighten towards the receiver, depth of field, labels on the most connected nodes, and a stable layout kept per
|
|
339
|
+
device (`watchtower.layout`). Flow pulses fade with message age and stop once nothing on a link is working or
|
|
340
|
+
pending. Rendering is on demand (none while still or hidden), and the 3D view draws in about 25 batched calls.
|
|
341
|
+
- Staffed seats are tied to their role by a tether and vacant roles are drawn hollow; the legend uses the same glyphs;
|
|
342
|
+
**More → Re-layout** clears the saved layout; the empty state sits below the hub core with **Connect a machine**.
|
|
343
|
+
|
|
344
|
+
### Navigation
|
|
345
|
+
- A primary navigation above the session groups: **All sessions**, **Artifacts** (a library across workspaces),
|
|
346
|
+
**Watchtower** and **Agent profiles**.
|
|
347
|
+
- **Settings sections**: General, Notifications, Voice & speech, Machines & devices, Agents & access, Teams and Admin,
|
|
348
|
+
each its own page `/settings/<section>` (deep links, back/forward; old `/settings#speech`-style links still work).
|
|
349
|
+
A section sidebar on desktop, a section list with drill-in on phones, and a **Search settings** box that jumps to a
|
|
350
|
+
setting and highlights it. Notification kinds are collapsed by group with an on/off summary; the device's push
|
|
351
|
+
status is one line with **Details**.
|
|
352
|
+
|
|
353
|
+
### Security hardening
|
|
354
|
+
- **Bounded request bodies** everywhere: agent JSON 1 MiB, profiles 4 MiB, console JSON 4 MiB, `/mcp` 1 MiB, staged
|
|
355
|
+
blobs 25 MiB, all read by a rate-budgeted streaming reader. Oversized bodies are refused with `Connection: close`.
|
|
356
|
+
**Set a front-proxy request-body limit (26 MiB)**: the k8s ingress example now uses `proxy-body-size: 26m` (it was
|
|
357
|
+
`16m`).
|
|
358
|
+
- A session seated in a team no longer sees sessions seated only in *other* teams: they are left out of `who`,
|
|
359
|
+
`status`, `show`, `log`, `/watch` and `/events`, and their addresses answer `404` like unknown ones. Sessions in no
|
|
360
|
+
team stay visible unless the owner turns on `teamIsolation` (Settings → Teams). Users, apps and team-less sessions
|
|
361
|
+
keep the owner-wide view.
|
|
362
|
+
- New app-token scopes `workspaces:*`, `profiles:*`, `artifacts:*` and `teams:*`. **Existing tokens gain nothing**,
|
|
363
|
+
and session tokens are refused on workspace routes.
|
|
364
|
+
- **Plain http only by opt-in**: `hub login` (device code, `--token` or a piped token) and `hub`/`hubctl machine enroll`
|
|
365
|
+
refuse plain http except on loopback, or with `--allow-insecure-http` for a private-network IP address (never a
|
|
366
|
+
host name), with a warning every time; nothing remembers the flag, and later commands using a stored plain-http
|
|
367
|
+
login warn on stderr. A loopback hub's approval link on this machine's LAN address is shown; other origins are
|
|
368
|
+
replaced with a warning.
|
|
369
|
+
|
|
370
|
+
### Fixes
|
|
371
|
+
- Counts pluralise correctly ("2 OpenCode processes", not "processs"); a team role's seat counter reads "1 of 2
|
|
372
|
+
required", "3 members · up to 16" or "full (16)" instead of "1 of 0–16".
|
|
373
|
+
- The artifact AI panel lists handler sessions by `@name` (searchable by name, title or ID) and names a handler picked
|
|
374
|
+
in the console by its `@name` instead of its session ID.
|
|
375
|
+
- The console's live stream opens at once when it resumes (the hub writes a first chunk immediately); before, it waited
|
|
376
|
+
for the 20 s heartbeat and **Reconnecting…** stayed up, across pages. Over plain http, Chrome still allows only 6
|
|
377
|
+
connections per host, so more than about 6 console tabs stall; https avoids this.
|
|
378
|
+
- The sidebar, Details rail and pane resize handles show their resize pointer again (a hidden character had broken
|
|
379
|
+
those CSS rules).
|
|
380
|
+
- `hub node` keeps OpenCode's background service warm (loopback only, every 30 s) so a freshly started service's
|
|
381
|
+
machine comes online without anyone opening a project (OpenCode has no server-start plugin hook).
|
|
382
|
+
- **Routine `wake: false` messages no longer cost an OpenCode session a turn** (routine digests, `--no-wake`
|
|
383
|
+
messages). OpenCode gives every queued inbox item its own model call once a turn ends, resume or not, so a
|
|
384
|
+
supervisor answered each routine digest in a turn of its own. The OpenCode spoke now admits every non-waking
|
|
385
|
+
message steered without resume, and refuses a routine one (a `wake: false` `msg` that is not urgent, to a top-level
|
|
386
|
+
session) while a turn runs: the hub keeps it `pending` and delivers it after the turn, so it reaches the model with
|
|
387
|
+
the next real turn, before its prompt (CLI and MCP say "pending (offline or busy; …)"). A waking message to the
|
|
388
|
+
same session sends the pending ones first (if the session is still busy, the waking one goes first, queued after
|
|
389
|
+
the turn). A flush stops at a session's first failed delivery, so a busy session costs one try per sessions frame.
|
|
390
|
+
Urgent
|
|
391
|
+
ones, asks, replies, primers and anything for a subagent still go into a running turn. Live check:
|
|
392
|
+
`HUB_IT_OPENCODE=1 bun test test/e2e-quiet-delivery.it.test.ts`.
|
|
393
|
+
- **Repeated blocks of one member no longer wake its supervisor each time**: blocked again for the same reason (same
|
|
394
|
+
blocker kind, or the same blocked/error state) within 15 min of a wake about it that the hub carried out, the
|
|
395
|
+
digest line is routine.
|
|
396
|
+
- **A run that fails at once shows as failed** (a model or provider that does not exist, auth, quota). OpenCode ends
|
|
397
|
+
such a run within ~50 ms of `session.created`, before the spoke's debounced sessions snapshot (500 ms), and the hub
|
|
398
|
+
keeps status only for sessions it was told about, so it dropped the failure and listed the session idle: no banner,
|
|
399
|
+
no "!" badge, not under Needs you, no `providerError` push (also in 0.7.3). The spoke now sends the snapshot first
|
|
400
|
+
when a run event is about a session the hub has not been told about yet (creating sessions alone still waits for the
|
|
401
|
+
debounce), and resends a failure once a snapshot lists its session if the session's details were still being
|
|
402
|
+
fetched when it failed. Live check: `HUB_IT_OPENCODE=1 bun test test/spoke-run-failure.it.test.ts`.
|
|
403
|
+
- **A session is never routed to another harness's spoke.** When no process owns a session and none of its
|
|
404
|
+
reporters is a service, the hub falls back to a background service on the reporter's machine; it took any service,
|
|
405
|
+
so an OpenCode session reported by a foreground `opencode serve` on a machine that also runs the Claude Code (or
|
|
406
|
+
Codex) service went to that service: "The claude-code spoke does not serve "timeline"" and `unsupported: summary`
|
|
407
|
+
(also in 0.7.3). The fallback now takes only a service of the session's own harness, else the reporter itself.
|
|
408
|
+
|
|
409
|
+
### Runtime
|
|
410
|
+
- **Bun 1.4.4**, pinned: the image (`oven/bun:1.4.4-slim` by digest, both stages), CI and release workflows, and
|
|
411
|
+
`engines.bun` (`>=1.4.4`). Bun 1.3.x's `Bun.SQL` auto-pipelining could stall a pooled Postgres connection under
|
|
412
|
+
concurrent writes (oven-sh/bun#33985; fixed in Bun 1.4.0 by oven-sh/bun#33627): a cluster pod's state queries then
|
|
413
|
+
never answered (the supervision IT's "many concurrent internal moves" hung or timed out in 3 of 15 runs on 1.3.5,
|
|
414
|
+
0 of 40 on 1.4.4).
|
|
415
|
+
- Cluster: the Postgres pools set `statement_timeout` (30 s by default, `HUB_DATABASE_STATEMENT_TIMEOUT_MS`, `0`
|
|
416
|
+
off), so a query stuck on the database fails with an error instead of holding its caller and connection.
|
|
417
|
+
- Bun 1.4 enforces the server's request ceiling on chunked bodies too; the ceiling now leaves 16 MiB above the
|
|
418
|
+
largest route bound, so an over-limit upload still gets the route's own `400`/`413` with `Connection: close`. Bun
|
|
419
|
+
1.4 then closes the socket instead of reading and discarding whatever the client keeps sending.
|
|
420
|
+
- The image build copies the protocol and SDK sources into the console stage, which imports them (without them
|
|
421
|
+
`docker build` failed in the console's `vite build`).
|
|
422
|
+
|
|
423
|
+
### Behavior notes
|
|
424
|
+
- New agent error code `conflict` (409, with `currentRevision`) for stale `expectedRevision`s and reused
|
|
425
|
+
`Idempotency-Key`s. The SDK `ErrorCode` includes it, and the CLI exits 3.
|
|
426
|
+
- The server's request ceiling rises from about 10 MiB to about 41 MiB (25 MiB blobs plus drain headroom). A JSON body
|
|
427
|
+
that was accepted before can now be refused as too large (by its route's bound).
|
|
428
|
+
- SDK `parseAddress` refuses `#` in a role's project scope (`@@role#team` is only ever team-resolved).
|
|
429
|
+
- Send to main and AI jobs handled by a session use normal delivery: they never interrupt a running turn. The first
|
|
430
|
+
message to an idle session right after the hub's primer may be delivered as steer.
|
|
431
|
+
- Cluster: schema v2 (`artifact-blobs`). Run `hub-server migrate` before rolling pods, and don't create restricted
|
|
432
|
+
teams while old and new pods are mixed.
|
|
4
433
|
|
|
5
434
|
## 0.7.4
|
|
6
435
|
|
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.
|
|
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.
|
|
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" \
|