When a session is open, the web app now keeps an always-on all:true SSE
connection for sidebar session-updated events while using a second
session-scoped stream for message delivery. Also bump session activity on
hub sendMessage so web-originated sends refresh list timestamps.
Fixestiann/hapi#693
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): embed agent text in voice ready event for readback
Voice onReady now extracts the last speakable assistant message and
embeds it in the ready inject so ConvAI can summarize without the user
re-prompting. Also formats Codex/Cursor stream-json messages for live
context updates and session history.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(web): use domain-neutral voice formatter fixtures
Replace jellybot/subtitle dogfood strings in tests with generic examples.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): guard extractSpeakableFromContent for non-arrays in formatMessage
extractSpeakableFromContent also handles content arrays (joins text items),
so calling it unconditionally before the existing array loop caused mixed
text+tool_use payloads to return early without formatting the tool_use item.
Guard with !isContentArray so the loop handles arrays as before.
Adds regression test: mixed text+tool_use array must produce both the text
and the tool-call line (was red before this fix).
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix(web): narrow extractSpeakableFromContent to codex type only
The helper matched any object with a string type and a data property,
so sendSessionEvent({ type: 'message', message }) events (which arrive as
{ type: 'event', data: { type: 'message', message } }) were falsely formatted
as speakable assistant text and could be selected as the ready readback.
Narrow the Codex path to content.type === 'codex' as the comment already states.
Adds regression test: session status event must return null from formatMessage.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: HAPI <noreply@hapi.run>
* fix(web): replace message-level aria-expanded with explicit toggle button
Replace the anti-pattern of assigning role=\"button\" and aria-expanded to
the entire message content div. Instead, show an explicit \"Show info\" /
\"Hide info\" button next to the timestamp.
Also add a visible background container to MessageMetadata so users can
actually see when metadata expands/collapses.
Remove now-unused metadataToggle.ts and its tests.
* fix(web): include turnCount in hasMetadata and pass it through codexReview branch
* Add Kimi agent support via ACP protocol
Add full integration for the Kimi Code CLI agent using the standard
Agent Client Protocol (ACP). Includes:
- kimi command and CLI registry wiring
- Local launcher spawning kimi directly
- Remote launcher with ACP stdio transport via AcpSdkBackend
- Session management with resume support
- Permission handler supporting all Kimi permission modes
- Terminal UI display component
- Runtime config resolving model from env and ~/.kimi/config.toml
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* Fix Kimi ACP tool call input decoding on web
Kimi streams tool arguments as JSON text inside the content array
(e.g. {\"command\": \"df -h\"}) instead of rawInput/kind. The handler
now extracts input from three sources in priority order:
1. rawInput (Claude/Codex path)
2. kind + title fallback (Gemini path)
3. content JSON text (Kimi path)
Also handles:
- rawInput: null no longer blocks the kind+title fallback
- Title prefixes like \"Shell: free -h\" are stripped to extract args
- Stale placeholder inputs are re-derived when the title updates
- Normalized kind aliases (shell, run, read_file, write, etc.)
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* Add kimi support to web UI
* Fix some bugs
* fix(kimi): dedupe repeated tool_call display in terminal UI
* fix(web): keep tool block immutable so React detects input/state changes
* fix(web): recognise Kimi subagent titles like 'Agent: ...' as subagent tools
* fix(web): allow-for-session for ACP agents (kimi, cursor)
PermissionFooter treated all non-codex sessions as Claude, sending
Claude-specific acceptEdits/allowTools to ACP agents that don't
support them. Hub rejected acceptEdits for kimi, and the ACP
PermissionAdapter ignored allowTools.
- Only show 'allow all edits' for Claude sessions
- Send decision: approved_for_session for non-Claude ACP agents
- Update status display to check decision field
* fix(web): lookup subagent sidechains by tool-call id instead of msg id
* fix(web): don't trim newest messages when loading older history
fetchOlderMessages was using trimVisible(merged, 'prepend') which kept
the oldest 400 messages and dropped the newest ones. This caused:
1. Latest messages to disappear when user loaded older history
2. User to see no visible change when new old messages were drowned
in the 400-message window.
Remove the incorrect trim so all fetched older messages are retained
alongside the current window. Subsequent ingestIncomingMessages
(append mode) will naturally keep the window bounded when new agent
messages arrive.
* fix(cli): route Kimi session resume to runKimi instead of runCursor
Kimi was present in AGENT_FLAVORS but dispatchLocalResume had no branch
for it, so resuming a Kimi session fell through to the Cursor launcher.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix(cli): pass selected model to Kimi ACP backend via KIMI_MODEL env
createKimiBackend was ignoring opts.model and only setting KIMI_PROJECT_DIR.
Use buildKimiEnv so the selected model reaches the subprocess as KIMI_MODEL.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix(web): bound message window on older loads with dedicated larger cap
fetchOlderMessages was keeping all messages unbounded, causing
sessionStorage bloat on repeated pagination. Reintroduce trimming
with OLDER_LOAD_WINDOW_SIZE (800) so growth is capped while the
newest messages are still preserved for far longer than before.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix(web): revert sidechain lookup to message id, matching tracer/grouping pipeline
tracer.ts sets sidechainId to the parent message id, and reducer.ts groups
by sidechainId. A prior commit changed reducerTimeline.ts to look up by
tool-call id (c.id), which broke sidechain attachment. Revert to msg.id
so the lookup matches the actual grouping key end-to-end.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix(cli): gate ACP title prefix stripping to known tool-kind labels
extractTitleArgument stripped at the first colon unconditionally,
corrupting commands/paths like curl http://localhost:3000 or
Windows paths. Now it only strips when the prefix normalizes to
the same tool kind as the event, verified via regex.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix(shared): include kimi in isCodexFamilyFlavor for ACP permission UI
Kimi is an ACP-style agent that supports the abort decision, but
isCodexFamilyFlavor excluded it, so PermissionFooter rendered the
non-Codex Allow/Deny UI without the Abort button.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
---------
Co-authored-by: HAPI <noreply@hapi.run>
Emit throttled ACP reasoning snapshots with stable stream ids so OpenCode reasoning updates render live without one row per token.
Collapse matching reasoning snapshots in the web timeline and avoid stale session 404 redirects during refetch.
Tests: targeted Vitest suite and bun typecheck.
Co-authored-by: twshe <twshe@outlook.com>