Expose the LLM token-level streaming deltas (EventReasoningDelta /
EventContentDelta) to every client channel and add user-initiated
interrupt (cancel generation / send interrupt message) to all three
frontends, preserving the existing interrupt-injection semantics.
SDK/events:
- EventReasoningDelta, EventContentDelta constants exported in the
public/internal SDK event alias tables.
CLI plugin:
- handleChat subscribes to both delta events and forwards
reasoning_delta / content_delta JSON frames (channel-filtered);
aggregated reasoning/tool_call/response frames still fire as before.
- New /stop (alias /interrupt) builtin injects an interrupt via
InjectInterrupt(cliSource, cliChannel) - matches interceptLoop
semantics: cancels an active stream and re-injects the message as
a [中断消息] for a restarted turn; with no active LLM it behaves
as a plain input.
Waiter client (line mode + TUI):
- streamRender accumulates delta chunks and redraws the current line;
a reset frame (stream abandoned, e.g. user interrupt) flushes the
partial buffer so the next turn does not concatenate onto stale
content. Aggregated frames terminate the delta line and render the
final text (old servers without deltas behave exactly as before).
- TUI merges content_delta into the in-flight agent message and seals
it (final flag) on response/tool_call/error so subsequent deltas
never append to a finished message.
WebUI:
- SSE handler subscribes to the two delta events but does NOT record
them into the replay ring - reconnection replays only aggregated
events (the final truth), avoiding duplicate delta accumulation.
- POST /api/v1/chat/interrupt calls InjectInterrupt(webui, webui)
with optional message; fronted by a Stop button shown only while
a generation is in flight.
dashboard.html / GUI app.js:
- Stop button next to Send (hidden until chatLoading); interruptChat
POSTs /chat/interrupt. Delta listeners append incrementally;
agent_output (aggregated) now replaces (not appends) the in-flight
content and marks _final; reset frames finalize the partial message.
process.go:
- chatStreamWithFallback preserves the context.Canceled/
DeadlineExceeded contract: a user interrupt returns the canceled
error (never a partial-content success) so the existing continue
branch restarts the turn with the [中断消息]. A reset
EventContentDelta is published so connected clients drop stale
partial renderings before the new turn begins.
Verified: /stop 'msg' via waiter triggers 'interrupt from cli/cli' in
interceptLoop; unit TestChatStreamCancelPreservesInterrupt confirms the
canceled error propagates instead of being swallowed.
Replace the line-based REPL with a full-screen Bubble Tea TUI in
interactive mode (non-TTY still falls back to the line editor).
Layout (deveco-code inspired, no emoji):
- Top status bar: HomeAgent brand + connection dot + mode + addr
- Scrollable chat viewport with role-based rendering:
You (purple) user messages
小宅 (orange) agent responses
· reasoning dim gray italic, streaming-merged
[ok]/[fail] tool calls with status + truncated result
[sys] builtin command output
[err] errors
- Rounded-border input box with placeholder
- Bottom status bar: spinner while busy / hints + connection state
Key design points:
- reader generation counter prevents stale errors from the old
reader being mistaken for the new one after reconnect
- handleSubmit always returns waitServer when reader is alive,
so server responses to builtin commands like /status are received
- History stored as *History (was copying sync.Mutex by value)
- Chinese CJK wide-char aware word wrap with hanging indent
- /clear /exit /quit handled in TUI; other /cmds still go through
handleBuiltin with output captured to message area
Verified via tmux PTY: /help, /status, /clear, real chat with LLM
(reasoning -> tool_call -> response chain all render correctly).
The local llmsproxy AUTO chain tries 6+ slots across 3 tiers sequentially.
Each failed tier incurs busyWait (2s) + upstream timeout, so a full chain
exhaustion can exceed 120s. The llmsproxy logs showed 143 'context canceled'
errors for the homeagent key — the client gave up before the chain finished.
180s gives the chain enough room to complete before the client timeout fires.
Also remove stale backup files under /usr/local/bin/.
- Drop skill.NewManager from homed bootstrap; skills dir no longer core-managed
- Remove GetInjectedPrompt system-prompt injection (skills are not first-class)
- Delete internal/skill package, SkillAPI, webui /api/v1/skills, status skills block
- ConfigRegistry: plugin config tables now created only via RegisterDef; arbitrary
scope Set/Get no longer implicitly creates config_<name> tables (fixes stray
config_today_task table from SKILL directory name being used as a scope)
- handleChat now injects with source=webui so the LLM no longer sees
cli as the input channel
- subscribe to EventRawInput/EventAgentOutput to persist every
conversation turn from all channels (cli/qq/webui), replacing the
manual webui-only addChatMsg to avoid duplicates
- ChatMsg gains a source field; GUI shows a channel badge for
non-webui messages
- Six-phase plan complete: webui/cli/healthcheck/pluginmgr/clawhubadapter
now interact with the kernel exclusively via internal/sdk interfaces;
all Configure() calls and package-level global injection removed
- buildSDK in internal/plugin/registry.go is the single assembly point
- Add internal/sdk/events.go exporting event types/constants
- Fix ProviderManager cooldown sharing: LuaAdaptedProvider.Name() now
returns the source name instead of lua_<adapter>, so multiple sources
sharing an adapter (single script load via shared VM AdapterCache) no
longer share failure-cooldown state
- Verified: build/vet/tests green, deployed to homeagent.service with
full plugin capability testing via local OpenAI-compatible mock
rawmode_linux.go only cleared ICANON|ECHO but left ISIG enabled,
so the terminal driver caught Ctrl+C and generated SIGINT.
signal.Notify caught the SIGINT but no goroutine consumed the
channel, effectively swallowing the signal.
Fix: also clear ISIG so Ctrl+C is delivered as byte 0x03,
which the line editor handles with os.Exit(130). Also set
VMIN=1, VTIME=0 for proper blocking read behavior.
editor.go read bytes one at a time; byte >= 0x20 was treated as
a single rune, so multi-byte UTF-8 chars (Chinese, emoji, etc.)
became garbled. Add decodeRune to reassemble multi-byte sequences
from the raw byte stream.
- Add core.agent.embedding_model_path to seedDBValues and seedCoreDefs
- Wire cfgReg.GetString in main.go to AgentConfig.EmbeddingModelPath
- Follow existing core.agent.* ConfigRegistry pattern