- New /api/goals + /api/goals/{id} endpoints serving list + task tree
+ cost rollup + billing breakdown + spawned agents + timeline.
- New Svelte /goals and /goals/[id] pages with sidebar link.
- goals.Service.EvaluateBudget returns a soft/hard verdict; agent
runner posts the 80% warning once and auto-pauses at 100%.
- Auto-quarantine: after each reputation append the agent runner
checks rolling score < 0.3 and writes quarantined_at; reactor
refuses new reactive dispatches to quarantined agents.
- Reactor exposes SetSecretProvider; main.go wires secrets.Store
so reactive subprocess runs inherit user/agent-scoped env vars.
- cli-verifier demonstrates the resource-request protocol: checks
MCPPROXY_API_KEY, posts to #requests + resource_requests row if
missing. New `synapbus secrets set/list` CLI (direct-DB) closes
the loop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
docgardener: each leaf task now launches a real subprocess via
exec.CommandContext and records a full reactive_runs + harness_runs
row chain with task_id populated, captured prompt, captured response,
exit code, duration, tokens, cost. The Agent Runs page and
/runs/:id detail page now show real data for the doc-gardener demo
— including "What the model saw" and "What the model said" panels —
without needing the coordinator LLM loop.
agents store: agentSelectSQL and both scanAgent functions extended
to read the feature-018 columns (config_hash, parent_agent_id,
spawn_depth, system_prompt, autonomy_tier, tool_scope_json,
quarantined_at, quarantine_reason). /api/agents and
/api/agents/:name now return these fields end-to-end.
Web UI agent detail (web/src/routes/agents/[name]/+page.svelte):
adds a Trust & Spawn section (config_hash, autonomy tier, spawn
depth, parent agent, tool scope chips) and a full-height System
Prompt pre block. Rebuilt internal/web/dist/.
Verified in Chrome against a fresh ./start.sh && ./run_task.sh run:
- Agent Runs page lists 3 completed runs (docs-scanner, cli-verifier,
drift-reporter) with task.claim event and non-zero durations
- /runs/1 detail page renders captured prompt + structured #finding
output with 12 flags
- /agents/docs-scanner shows config_hash=a0b5c6538b2d…, parent=#1,
depth=1, tool-scope chips, and the 170-char system prompt
- #goal-... channel loads all 12 messages (no "Joining..." hang)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Makes the Web UI reflect what agents are actually doing: reactions
on DMs that trigger a subprocess run, a per-run detail page that
shows the exact prompt the model received and the raw response, and
cross-linked reactive_runs ↔ harness_runs data for a single composite
API call.
Migration 020 (internal/storage/schema/020_harness_run_detail.sql):
ALTER TABLE harness_runs ADD COLUMN reactive_run_id INTEGER;
ALTER TABLE harness_runs ADD COLUMN prompt TEXT;
ALTER TABLE harness_runs ADD COLUMN response TEXT;
CREATE INDEX idx_harness_runs_reactive ON harness_runs(reactive_run_id);
internal/harness:
* ExecRequest.ReactiveRunID — reactor pins the reactive_runs row id
so the observer can JOIN the two tables.
* ExecResult.Prompt / Response — the subprocess harness reads
prompt.txt / response.txt that wrappers write into the workdir,
and runs.Store persists them (capped at 32 KiB each).
* runs.Run struct now has JSON tags — previously the API returned
PascalCase field names that didn't match the Web UI's snake_case
TypeScript types.
* New runs.Store.GetByReactiveRunID for the composite API endpoint.
* Test schema updated to include the new columns.
internal/reactor:
* New ReactionNotifier interface + SetReactionNotifier.
* dispatchHarness now reacts `in_progress` on the triggering DM
before spawning the goroutine.
* runHarness reacts `done` on success, `reject` on failure. The
existing reactionPriority ordering means the terminal reaction
wins for badge display — no need to remove in_progress first.
* dispatchHarness sets ExecRequest.ReactiveRunID.
cmd/synapbus/main.go:
* reactorReactionAdapter: adapts reactions.Service.Toggle to the
reactor's one-shot AddReaction signature.
* HarnessRunsStore wired into the API router config.
internal/api/runs_handler.go — GetRun composite endpoint:
The GET /api/runs/{id} response now returns everything the Web UI
needs to render the run detail page in one call:
{
"run": <reactive_runs row>,
"harness_run": <linked harness_runs row with prompt/response>,
"agent": <current agent snapshot with harness_config_json>,
"trigger_message": <DM that started the run>,
"outgoing_message": <first DM the agent produced after startedAt>
}
The outgoing-message lookup wraps both sides of the created_at
comparison in datetime() so SQLite parses the stored 'YYYY-MM-DD
HH:MM:SS' and the Go-emitted RFC3339 into the same canonical form
before comparing — a raw string compare was silently returning no
rows.
internal/api/router.go: HarnessRunsStore field in RouterConfig, wired
through to NewRunsHandler.
examples/cold-topic-explainer/wrapper.sh:
Writes prompt.txt and response.txt alongside gemini.stdout.raw so
the subprocess harness can capture "what the model saw" and "what
the model said" post-hoc.
web/src/lib/components/MessageList.svelte:
New ReactionPills render below each message body when the message
carries a `reactions` array (already populated by
EnrichMessages/ReactionEnricher on the server side). Makes the
👀 in_progress / ✔ done / ❌ reject lifecycle visible in every DM
view and conversation.
web/src/routes/runs/[id]/+page.svelte (NEW):
New run detail page at /runs/:id with sections:
1. Header strip — agent, status pill, backend badge, trigger
info, duration, tokens in/out, cost, exit code, trace id.
2. Triggering message — body + sender.
3. What the model saw — GEMINI.md / CLAUDE.md from agent snapshot
+ the captured rendered prompt (byte count on each summary
bar, collapsible details).
4. What the model said — captured response, falling back to
logs_excerpt or error_log when unavailable.
5. Outgoing message — body + recipient + status.
6. Metadata — reactive_run.id, harness_run.run_id, backend,
session_id, tokens_cached, k8s_job, agent trigger config.
Styled against the existing dark tailwind system — no design
overhaul, fits the current aesthetic (editorial sectioning,
monospace for code-like content, accent-blue for links,
accent-purple for system-instructions, accent-green for model
output, accent-red for errors).
web/src/routes/runs/+page.svelte: the inline expand panel now has
a "View full details →" link next to the Retry button.
E2E VERIFIED on a live subprocess run:
* Topic: "why does the subprocess harness materialise GEMINI.md
alongside .gemini/settings.json in the per-run workdir?"
* 3 subprocess runs + 3 reactive_runs + 3 harness_runs, all linked.
* message_reactions: 6 rows — in_progress + done for each hop.
* GET /api/runs/1 returns a composite with
harness_run.prompt=883 bytes, harness_run.response=550 bytes,
reactive_run_id=1, trigger_message populated, outgoing_message
populated (decomposer-pro → writer-flash), agent.gemini_md=747
bytes. All keys are snake_case as the Svelte types expect.
Full go test ./... green. `vite build` green. Demo instance still
running on port 18088 for browser verification.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Makes the subprocess backend fully self-contained: each agent carries
its instructions, MCP servers, skills, and subagents in its
harness_config_json column, viewable in the Web UI, editable via CLI.
internal/harness/subprocess/config.go (NEW):
AgentConfig struct with optional fields:
- claude_md → workdir/CLAUDE.md
- agents_md → workdir/AGENTS.md
- mcp_servers → workdir/.mcp.json (Claude Code format)
- skills → workdir/.claude/skills/<name>/SKILL.md
- subagents → workdir/.claude/agents/<name>.md
- env → layered into child env (after k8s_env_json,
before caller overrides)
ParseAgentConfig tolerates empty / returns error on invalid JSON.
MaterialiseAgentConfig writes all artifacts into the workdir with
path-traversal sanitisation on skill/subagent names.
subprocess.Harness.Execute now calls Parse + Materialise before exec,
so an agent's declarative config is on disk by the time the child
CLI's cwd lookup fires. buildEnv takes the parsed config and overlays
cfg.Env on top of k8s_env_json.
Tests:
config_test.go — 6 cases: empty, invalid JSON, full round-trip,
materialise writes all artefacts, empty is a no-op, skill names
are sanitised against "../escape" / "/etc/passwd", mcp entries
without a name are dropped.
subprocess_test.go — 2 new e2e cases: agent with CLAUDE.md + mcp
servers + skills + env sees all of them from inside the child via
cat/echo; invalid harness_config_json surfaces as Execute error.
internal/agents/store.go:
AgentStore gains UpdateHarnessConfig(ctx, name, harnessName,
localCommand, harnessConfigJSON). Empty strings leave a field
unchanged; literal "-" clears (sets to NULL). Returns sql.ErrNoRows
on missing agent. AgentService exposes Store() so admin handlers
can reach it without adding a full service method for a
config-set-style operation.
store_test.go: 6-subcase test covers set-all, partial update, clear,
unknown agent, and no-field no-op.
internal/admin/socket.go:
Two new admin commands:
harness.config_get {agent_name} → {harness_name, local_command,
harness_config_json, harness_config (parsed), parse_error?}
harness.config_set {agent_name, harness_name?, local_command?,
harness_config_json?} → updated fields
config_set validates JSON shape before calling the store; null /
"-" literals clear the column.
cmd/synapbus/admin.go:
New top-level `harness config` command group:
synapbus harness config get --agent <name> [--raw]
synapbus harness config set --agent <name>
[--harness-name subprocess]
[--local-command '["claude","--print"]']
[--file config.json] # or pipe from stdin
[--clear]
synapbus harness config edit --agent <name>
# fetches current config, opens $VISUAL/$EDITOR/vi,
# validates JSON on save, writes back via config_set
web/src/routes/agents/[name]/+page.svelte:
New read-only "Harness" panel on the agent detail page:
- Resolved backend badge (explicit or inferred from k8s_image /
local_command / harness_config_json.url)
- Grid summary: CLAUDE.md size, AGENTS.md size, MCP server count,
skills count
- Collapsible details for CLAUDE.md, AGENTS.md, each MCP server
(name / type / url|command / header count), skill filenames,
subagent filenames, env vars
- Footer hint showing the CLI edit command
No edit controls — editing is CLI-only by design (safer, fits an
ops-heavy workflow).
Verified: full project test suite (40+ packages including integration
tests) plus `vite build` of the Svelte app all green; `go vet ./...`
clean; the existing TestSubprocess_Execute_MaterialisesHarnessConfig
e2e test proves the round-trip from harness_config_json → workdir →
child process works end-to-end.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wrong password now shows "Invalid username or password" (was "Session expired")
- Client API differentiates 401 on login page vs elsewhere
- Added per-IP login rate limiter: 3 failures → blocked 1 minute
- 429 status code returned with remaining seconds in message
- Rate limit cleared on successful login
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New API: GET /api/dm/partners — returns DM conversation partners
ordered by most recent message, with unread counts
- Sidebar DM section now shows actual conversation partners (agents
you've exchanged messages with) instead of owned agents
- Each partner shows name, unread badge, clickable to /dm/{name}
- Fixes issue where all DMs were shown mixed in one view
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The onMount + async pattern wasn't triggering Svelte 5 reactivity
properly. Switched to $effect with $user dependency (same pattern
used by Sidebar and other components). Also waits for auth before
loading data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New /runs route with agent summary cards, run list, filtering
- Agent cards show budget usage, cooldown status, current state
- Expandable run rows with error logs and retry button
- API client: runs.list, runs.get, runs.retry, runs.reactiveAgents
- Sidebar navigation updated with "Agent Runs" link
- Rebuilt web dist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Show attachment previews on DM messages (was missing, only channels had it)
- Add file upload button to DM compose bar with paperclip icon
- Enrich messages with attachment data in all MCP bridge functions
(read_inbox, claim_messages, search, channel_messages, list_by_state)
- Remove file type restrictions — allow any file type, keep 50MB size limit
- Rebuild web dist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DM messages from agents were cut off at 300 characters in the
MessageList view. Increased to 800 to show more context while
still keeping long messages manageable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MCP tool descriptions: react, unreact, list_by_state, get_trust,
post_task, bid_task now include workflow context so agents discover
the coordination pattern from tool descriptions alone.
Channel creation UI: added channel type selector (standard/blackboard/
auction) and workflow enabled toggle to the create form.
Channel info panel: workflow settings section with toggles for
workflow_enabled, auto_approve, threshold sliders, and stalemate
timeout inputs. Changes apply via PUT /api/channels/{name}/settings.
Agent skill docs: created stigmergy-workflow.md and task-auction.md
reference skills for agent workspaces.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Trust scores: per (agent, action_type) pair, stored in agent_trust
table. Auto-adjusts when human reacts to AI agent messages (approve
+0.05, reject -0.1). Scores clamped [0.0, 1.0]. MCP get_trust action
+ REST API /api/trust/{agent}. Web UI shows trust progress bars on
agent detail pages.
Claim semantics: only one in_progress reaction per message enforced.
First agent to claim wins, duplicates rejected with clear error.
State-change webhooks: StateChangeNotifier interface fires
workflow.state_changed events through existing webhook infrastructure
when reactions change a message's derived workflow state.
Channel thresholds: publish_threshold and approve_threshold fields
on channels for configuring autonomy gates.
Migration 014_trust_claims.sql. 17 new test cases across trust
model + store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Channel queries failed on prod because workflow_enabled column was
missing (migration ran before column was added). Fixed prod DB.
- Added WorkflowBadge + ReactionPills to DM page view so reactions
work in DMs, not just channels
- Filtered agent-to-agent DMs from sidebar — only show AI agents when
they have unread messages for the human owner
- Updated 4 agent gitops repos with SynapBus reactions workflow
instructions (react in_progress/done, thread replies, self-update)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bug 1 (DM disappearing): GetDMMessages used ORDER BY created_at ASC
with LIMIT 100, so newest messages were cut off when >100 DMs exist
between owned agents and a peer. Changed to DESC + reverse in handler
so the most recent messages are always included.
Bug 2 (empty thread panel): ThreadPanel loaded messages by
conversation_id, but reply_to links messages across different
conversations. Rewrote to use GET /api/messages/{id}/replies which
correctly finds all replies to a parent message. Added getReplies
method to the API client.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add workflow_enabled column to channels (default false) — reactions
and workflow badges only show on opted-in channels
- ReactionPills: add "+" button with picker dropdown to add reactions
when none exist yet (was missing, only showed existing reactions)
- Update CLAUDE.md protocol docs with reactions workflow guidance
- Update channel store queries for new workflow_enabled column
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add typed reactions (approve/reject/in_progress/done/published) with
toggle semantics. Workflow state derived from highest-priority reaction.
New reactions package with model, SQLite store, and service layer.
REST API: POST/GET/DELETE /api/messages/{id}/reactions for toggle/query,
PUT /api/channels/{name}/settings for workflow config, GET by-state
endpoint for listing messages by workflow state.
MCP: react/unreact/get_reactions/list_by_state actions via bridge.
Web UI: WorkflowBadge (colored state pills) and ReactionPills (toggle
pills with agent names) components integrated into channel view.
Channel settings: auto_approve, stalemate_remind_after,
stalemate_escalate_after columns. CLI: channels update command.
Migration 013_reactions.sql adds message_reactions table and channel
workflow columns. 29+ new test cases across model and store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Web UI: paperclip button for file upload (images, PDFs, text), inline
attachment cards with file icon/name/size, image thumbnails with
fullscreen overlay, attachment display in thread panel.
Threads: always-visible reply count badges on messages, clickable to
open thread panel. reply_count and attachments enriched in all API
responses via batch queries.
MCP: attachments parameter on send_message tool, updated tool
descriptions for threading and attachment workflow guidance.
Backend: file type validation (allowlist), AttachmentLinker interface
to avoid circular deps, GetReplyCounts batch query, EnrichMessages
method on MessagingService.
Admin CLI: synapbus attachments backup/restore with tar.gz archives,
dedup-safe restore.
24 new test cases across 4 packages. All 24 test packages pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The browser PushSubscription nests keys under .keys but the backend
expects flat key_p256dh and key_auth fields.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Fix push toggle error: VAPID key field name mismatch (public_key → vapid_public_key)
2. Fix textarea auto-resize: proper height reset, overflow handling, mobile Enter
inserts newline instead of sending (send via button on mobile)
3. Fix mobile viewport overflow: add overflow-x hidden to html/body, overflow-x
hidden on content container
4. Fix dashboard cards: always 4 columns with responsive text sizing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Analytics: time-series message graph with 5 time spans (1h/4h/24h/7d/30d),
top-5 agents and channels leaderboards, summary cards. 4 new REST endpoints.
PWA: web app manifest, service worker with cache-first static/network-only
API strategy, push notifications via Web Push API with VAPID keys, push
subscription management endpoints, SQLite migration for subscriptions.
UX fixes: auto-resize compose textarea (3-12 lines), inline editable agent
display name, editable human display name in settings, smart mention/channel
highlighting (existing→link, deleted→inactive badge, unknown→plain text),
font size -/+ preference (12-24px persisted in localStorage), version footer
with GitHub link.
MCP: 4 prompts — daily-digest, agent-health-check, channel-overview,
debug-agent. Registered with prompt capabilities enabled.
Code review fixes: scoped push unsubscribe to user, capped analytics limit
at 100, hardened HTML strip regex, bounded SW cache, backend push unsub on
disable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On viewports < 768px (md breakpoint):
- Sidebar slides in as a drawer with dark overlay backdrop
- Hamburger button in the header toggles the sidebar
- Nav link clicks auto-close the drawer
- Sidebar items get 44px min-height for touch-friendly tapping
- Search input uses fluid width instead of fixed 320px
Desktop (>= 768px) behavior is unchanged: sidebar always visible,
main content offset by 260px, no hamburger button.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The regex matched against HTML-escaped text where & entity chars
broke URL patterns. Now URLs are extracted and replaced with placeholders
before escapeHtml runs, then restored after all other inline processing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the Conversations page with a dedicated search page featuring:
- Large, prominent search input with autofocus
- Collapsible filter panel with time range presets (24h, week, month,
3 months, custom date range), channel filter (comma-separated,
- prefix to exclude), and agent filter (same syntax)
- Search-results-only display with helpful empty state when no search
has been performed
- Remove duplicate "Conversations" heading, rename to "Search"
- Update sidebar nav label and icon to match
Backend changes:
- Add channel, agent, after, before query parameters to
GET /api/messages/search endpoint
- Add Channels, ExcludeChannels, Agents, ExcludeAgents fields to
SearchOptions with SQL filter generation in store.go
- Support include/exclude semantics via - prefix for both channel
and agent filters
API client updated to pass new filter parameters.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace plain-text message rendering with a rich MessageBody component
that supports bold, italic, inline code, fenced code blocks, lists,
headers, auto-linked URLs, @mention pills (linking to /dm/{name}), and
#channel pills (linking to /channels/{name}). Input is HTML-sanitized
before processing to prevent XSS. Updated all 5 rendering locations:
channels, DMs, conversations, MessageList, and ThreadPanel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
API keys are managed via admin CLI, not the web UI. Remove the
Management section from Settings, delete the api-keys route, and
clean up the Header page-title mapping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the duplicate "Search messages" button from the sidebar and make
the header search input bigger (w-80, text-sm, larger padding/icon) with
"Search messages..." placeholder matching the removed sidebar text.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace generic cube favicon and OAuth layered-planes logo with the
same constellation icon used in the sidebar and login page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Auto-join public channels on first send (bug #1)
- Channel broadcasts no longer create duplicate DM copies; inbox DMs
only sent for @mentions (bug #2)
- Embedding pipeline auto-enqueues new messages via MessageListener
callback instead of requiring pod restart (bug #3)
- Admin socket defaults to /tmp in containers to avoid PVC filesystem
incompatibility with Unix sockets (bug #5)
- SSE events now fire for MCP-sent messages (not just REST API),
enabling live notification badges without page reload
- Fixed frontend SSE field name mismatch (channel_name → channel)
- Fixed SSE client not connecting after login redirect
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use human agent's perspective only for unread counts (avoids system agent inflation)
- Replace onDestroy + get() with $effect cleanup in channel/DM pages (Svelte 5 compat)
- Fix notification store to parse array-of-objects API response format
- Add last_read_message_id to DM messages endpoint
- Fix test agent type seeding for GetHumanAgentForUser
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- GET /api/notifications/unread returns channel + DM unread counts
- POST /api/notifications/mark-read updates inbox_state for channels/DMs
- SSE broadcaster wired into message send for real-time push
- last_read_message_id added to channel/DM message responses
Frontend:
- Notification store tracks unread counts per channel/DM
- SSE listener for new_message and unread_update events
- Red circular badges in sidebar (Slack-style)
- "New messages" separator line in channel/DM views
- Auto mark-as-read after 2 seconds of viewing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Agent registration success screen now shows tabbed client selector
(Claude Code, Gemini, Cursor, Windsurf, VS Code, Claude Desktop)
with per-client CLI commands, JSON config, and API Key/OAuth toggle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix OAuth agent identity: add GetAgentName/GetUserID methods to fositeSession
so the introspection type assertion succeeds and MCP uses the selected agent
(e.g., "Alice Bot") instead of the human username ("alice")
- Fix channel broadcast: create a proper channel message (with channel_id) so
messages sent via MCP send_channel_message appear in the Web UI channel view
- Fix thread replies: pass conversation_id from thread panel so replies go into
the same conversation instead of creating a new one
- Fix OAuth token exchange: normalize localhost→127.0.0.1 in redirect_uri to
match what was stored during authorization (fixes Gemini CLI callback timeout)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix leave channel: handle ErrOwnerCannotLeave with error display, remove
all owned agents from channel
- Close thread panel when navigating between channels/DMs
- Remove Type dropdown from agent registration (agents are always AI;
human accounts created via CLI)
- Fix dashboard showing "Untitled conversation" — now shows last agent name
- Fix "1 msgs" → "1 msg" singular form on dashboard
- Fix conversation detail "-- N messages" → "— N message(s)" with em-dash
- Hide "done" status badge in MessageList and conversation detail
(consistent with DM view behavior)
- Add thread reply buttons and reply count to channel and DM messages
- Add agent selector for multi-agent users in channel and DM compose
- Add "Join Channel" prompt for non-members in channel compose area
- Show "(you)" indicator on channel member list for owned agents
- Add agent detail page with messages endpoint
- Improve release workflow and Dockerfile
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Channel pages now show message feed with compose bar instead of
just member lists. Messages display with agent avatars, names,
and timestamps. Collapsible info panel shows channel details.
- New /dm/[name] route for direct message conversations between
agents with from→to indicators and status badges.
- Sidebar DM links now navigate to /dm/{name} instead of agent
edit forms.
- Backend: add GetChannelMessages and GetDMMessages store/service
methods with corresponding API handlers and routes.
- Makefile: build target now depends on web target so binary
always embeds latest UI assets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Major feature additions across backend and frontend:
Backend:
- Unix domain socket admin server with JSON-RPC protocol
- CLI subcommands: user/agent management, audit, backup, messages, channels
- Managed API keys (sb_ prefix) with permissions, channel limits, expiry
- Thread/reply support in messaging core (reply_to column)
- Context-based trace owner_id propagation for proper audit filtering
- Two-step auth middleware supporting both agent keys and managed API keys
Frontend:
- Complete Slack-like dark theme redesign with custom CSS properties
- Sidebar with Channels, Direct Messages, and Admin sections
- Thread panel (slide-in) for viewing message replies
- API key management page with create form, key display, and
ready-to-use MCP/Claude Code config snippets with copy-to-clipboard
- All pages restyled: login, dashboard, agents, conversations, settings
E2E Tests:
- Fixed test runner binary path resolution
- Added pyproject.toml for test dependencies
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
onMount callbacks never fire in Svelte 5 runes mode compiled output,
causing the SPA to show a loading spinner indefinitely. Replace all
onMount calls with $effect + _initialized guard pattern, and convert
$: reactive statements to $derived(). Rebuild embedded SPA.
- Replace onMount with $effect in +layout.svelte and all 7 page components
- Convert $: reactive assignments to $derived() (runes mode requirement)
- Rebuild SPA with fixes (internal/web/dist/index.html updated)
- Add synapbus binary to .gitignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add complete Web UI infrastructure:
Go backend:
- REST API handlers for messages, agents, channels (internal/api/)
- SSE hub for real-time event streaming
- Session-to-owner middleware bridging auth sessions to API context
- SPA file server with go:embed for static assets
- GetMessageByID on MessagingService, RevokeKey on AgentService
- Updated router with RouterConfig for full service wiring
Svelte 5 SPA (web/):
- SvelteKit with static adapter for SPA mode
- Tailwind CSS with dark mode (class-based, localStorage persisted)
- API client with auto-redirect on 401
- SSE client with exponential backoff reconnect
- Pages: Login, Dashboard, Conversations, Channels, Agents, Settings
- Components: Sidebar, Header, MessageList, ComposeForm, AgentCard, TraceViewer
- Responsive layout with mobile sidebar toggle
Build:
- Placeholder index.html in internal/web/dist/ for go:embed compilation
- Updated Makefile web target to copy build output
- All existing Go tests pass, CGO_ENABLED=0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>