feat: compact and other improvements#1
Conversation
## Summary This PR implements dual model support (main + turbo) for cost-optimized operations and adds model sorting functionality. ### Key Changes #### Dual Model Support - Added `turbo_model` and `turbo_cost_threshold` config options - New dual-pane model selector (Tab to switch panes) - Visual indicators: ⚡ (turbo), 🧠 (reasoning), 🔧 (tools) - Smart turbo model selection with fallback logic - Fixed Anthropic auth showing incorrect turbo status #### Model Sorting - Added `release_date` and `last_updated` fields to model schema - Press 'S' to cycle through sort modes: Name, Last Updated, Release Date - Stable multi-level sorting with consistent ordering - Helps discover newest models and track updates ### Impact Enables cost optimization for PRs anomalyco#269 (status verbs) and anomalyco#275 (window titles) to use configured turbo models instead of hardcoded ones. ### Demo https://github.com/user-attachments/assets/d076e840-d790-4e23-8cf7-a179282d1f0b
…ompaction feat: intelligent compaction with model-initiated trigger, handoff prompts, and persistent memory
- Add VoyageEmbeddingProvider for Voyage AI embeddings - Add MemoryStore with high-level store/search/list API - Configure Nebius cloud API for Qwen3-Embedding-8B (#1 on MTEB) - Fix LocalEmbeddingProvider to support both /v1/embeddings and /embeddings - Fix QdrantVectorStorage to set collection on existing collections Model: Qwen3-Embedding-8B (70.58 MTEB score, 99% zero-shot, 4096 dims) Provider: Nebius API (https://api.tokenfactory.nebius.com/v1) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…s / smaller nodes
Address user's 6-point list after first visual pass:
1. Trace overlap + session id readability
- SessionPicker now displays session TITLE (not the cryptic id), pulled
from sync.data.session. Fallback to "…<last8>" only if no Session
record loaded yet.
- SessionSummary type gains `title` field.
- .rt-pop-title CSS clamps to 240px with ellipsis so long titles never
overflow the trigger row or dropdown.
- .rt-pop-menu / .rt-pop-scrim z-index bumped (60/70) so the dropdown
floats above any module body.
2. Rail icons + hierarchy
- Replaced @opencode-ai/ui Icon usage with inline SVG ICONS map
(workflow / knowledge / trace) matching the design template's
stroke-1.5 rounded-cap shapes.
- .rune-rail-subs gained a 1px left guide line + 26px margin-left,
and each sub gets a 4px dot prefix that turns accent-blue when
active. Hierarchy is now visually obvious instead of the previous
flush-with-parent layout.
3. Rail badges
- Knowledge: refiner-page publishes `pendingReviewCount` (count of
experiences with review_status === "pending") via the bridge as
`railBadges.knowledge`. Shows the bubble next to "Knowledge".
- Workflow: session.tsx publishes `runningCount` (root sessions
updated within last 60s as a freshness proxy for "running") via
`railBadges.workflow`.
4. Trace session by title — same as anomalyco#1 above.
5. Workflow tasks as Rail sub-list
- session.tsx now publishes `railSubs` with the same root-sessions
list it pushes to the Tasks Drawer. Each task renders as a Rail
sub-row under Workflow. Drawer stays as the richer overlay.
6. Workflow node cards smaller + minimal status
- workflow-node.tsx rewritten to compact form: type icon (24px),
single-line title with truncate (was 2-line wrap), tiny status
dot/spinner, arrow. Removed inline progress percent + summary
chips (those bloated the card to 80+px tall).
- Progress bar moved to a 2px full-width strip beneath the row.
- .wf-node-compact CSS: 6px×10px padding, max-width 240px, 8px radius.
Cards now fit ~3-up in a 760px lane and scan at a glance.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…yout Batch fix for issues found in the user's workflow session: 1. **Master not woken on final node.completed (anomalyco#7)**. The wake subscriber at workflow/index.ts:1820 dropped ALL wakes when workflow was in a terminal state. The user reported the FIRST nodes wake master fine, but the LAST node's completion races with the master's `workflow_finalize` call — by the time the node.completed event hits the subscriber, workflow.status is already "completed" and the wake gets silently dropped. Now "signal" kinds (node.completed / failed / interrupted / attempt_reported / budget_exceeded / stalled / checkpoint.*) ALWAYS wake master regardless of workflow status; chrome kinds (wake_*, pulled, command_acked, graph.edit.*) still drop on terminal so we don't wake forever. 2. **Session compaction silent failure (anomalyco#8)**. compaction.ts only published `session.compacted` on success — failure paths only set `message.error` on the last assistant message which was invisible unless the user scrolled back. The session just "froze" with no indication. Added a new `session.compaction_failed` bus event, wired the frontend event-reducer to park it on a new `sessionError[sessionID]` slot, and the workflow chat panel now appends a system message banner (`⚠ 会话压缩失败:…`) with actionable guidance (new session, bigger model context window). Successful re-compaction clears the banner. 3. **Question dialog deadlock in node sessions (anomalyco#9)**. Frontend polling for /question and /permission was gated on `status() === "running"` — but a sub-agent calling question() transitions to idle BEFORE the master picks it up, so polling stopped and the dialog never appeared, locking the session. Removed the gate; polling runs unconditionally every 2s now (cheap, returns [] when empty). 4. **Per-task node-card state leak (anomalyco#4)**. `workflowOpenTabs` was a global array — opening a node card under Task A left it visible after switching to Task B. Re-keyed by sessionId (now `Record<sessionID, OpenTab[]>`). Substrip render reads `workflowOpenTabs[params.id]`, opens/closes write through the current sessionId. Switching tasks now shows that task's own opened tabs. 5. **Refiner merge button no-op (anomalyco#1)**. Merge button was reading `selectedIds()` but the actual list-level multi-select bound to `mergeIDs` (a separate signal used by the MergeTray). Header button now reads/writes the same `mergeIDs` set the list uses, so shift-click selection -> merge button enable -> merge action all flow through one state. 6. **Refiner search input only takes one char per keystroke (anomalyco#11)**. The two search `<input>`s (line ~3815 and ~6107 of refiner-page) were missing `onCompositionStart/End` handlers. Chinese pinyin IME fires intermediate input events during composition; the controlled signal update reset the input every keystroke, breaking multi-char input. Added composition tracking that suppresses setQuery() while composing. 7. **Node-session layout (anomalyco#5)**. The outer `<div className="flex h-full flex-col">` of NodeSessionView had no horizontal sizing — `flex-col` parent's `flex-1` granted vertical grow but not width fill. Result: chat collapsed to content width, plugin slot didn't reach right edge, splitbar drag moved the whole right column left instead of resizing chat. Adding `w-full min-w-0 flex-1` to the root + `w-full min-w-0` to the body row fixes all three symptoms (whitespace left, narrow right, drag misbehavior). 8. **Rail overflow with many tasks (anomalyco#10)**. `.rune-rail-sec` had no `overflow-y: auto` or flex sizing — task list pushed beyond viewport when >N tasks. Set `flex: 1 1 auto; min-height: 0; overflow-y: auto` so the section scrolls independently while the rail brand / footer stay anchored. Not addressed in this batch (deferred for separate discussion): - Bug 2 / Bug 3 (action-budget architecture): structural change - Bug 6 (context limit on node create): largely covered by the earlier workflow_read trim; ship and observe. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
anomalyco#1 question 跨 task 显示: backend /question 列出所有 session 的 pending 请求,前端 chatsWithDialogs 现在按 ownedSessionIDs (root + workflow nodes' session_id) 过滤;其他 task 的 question/permission 不再串台 anomalyco#6 slave 调用 task 被禁: workflowSlaveAllow 加 `task: {"*":"deny"}`, 所有 workflow node agent (coding/debug/build-flash/deploy/explore) 现在禁用 task 工具——子节点不能再 spawn 子子任务,避免 off-canvas helper 绕过 workflow graph anomalyco#9 refiner log 显示 session 名: RefinerLogsModal 加 useSync 反查 sync.data.session[id].title, 列表行渲染 labelForSessionID(id) 取代 s.id.slice(0,12); manual entry 仍显示 "manual / 手动新建" 附带修复 anomalyco#9 配套问题——新建 task session 完全不触发 refiner: 之前的 NON_REFINABLE_AGENTS=["orchestrator"] gate 太狠把所有 workflow task 的 user 消息一棒打死。改成 META_COMMAND_PATTERN 只 跳过纯命令式短消息 (go/run/approve/继续/确认/批准/中止//cmd 等), 正常 domain query 走 refiner #2a 创建 workflow 后 inspector 模型空白要切换 task 才显示: newWorkflow Task 现在在 navigate 前 sync.session.sync({force:true}), 让新建路由 挂载时 snapshot 已经在手 #2b 改一个 slave 模型连 master 都跟着变: 之前 fix 是行为层 (clear- before-pick), 这次补视觉层——AgentCard 的 inherited 状态用 italic + 60% opacity + (继承) 后缀 + 显眼的 "继承 master" 黄色 chip。让用户 一眼看出 "这不是 slave 自己的模型,是临时继承 master session 的", 改 master 时 inherited slaves 跟着显示也不再误以为 "全部被改了" anomalyco#3 slave update success 不唤醒 master: classify() 之前只在 result !== "success" 时把 node.attempt_reported 算作 wake 事件,理由是 "node.completed 会跟着来,到时一起唤醒"。但 slave 不总转 completed 状态——经常只 update state_json + history 就停在 running/waiting 等 master 决定下一步,master 永远不被唤醒。改为 attempt_reported 无条件 wake (master 看到 status 已 completed 时可以自行 no-op) anomalyco#10 timeline 看不到 slave 在干什么: 两个 fix 一起 - workflow events projection 现在同时接受 payload.summary 和 payload.summary_short (后者是上一版 trim 后的字段)。之前只读 summary 导致整个 attempt_reported 摘要丢失,显示空字符串 - events-panel 给 node.attempt_reported 和 workflow.* 加 data-primary + data-scope, CSS 让 attempt summary 用更大字号 + bold + dot 加 halo;workflow-level 事件用 accent ribbon 左竖条,从 node-level chatter 中视觉分组分开。tool/update 等次要事件保留但回归背景 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rectory tree UI anomalyco#3 — Model picker "改一个变所有": root-fix Root cause: nodes are persisted with `node.model = null` when INSERT_NODE doesn't carry an explicit model (e.g., orchestrator-generated nodes). Inspector renders via `node.model || fallback`, where fallback is master's `local.model.current()`. When pickModel clears local.model briefly to detect "same model picked = no change" and reopens with the user's selection M, every inheriting slave's fallback display flips to M (until forceRouteNodes' .finally restores). Looks like "all change". Fix: proactive auto-route — new createEffect in workflow-panel.tsx watches `nodes()` and PATCHes every node lacking model with `local.model.current()` immediately. After this, every node has its own explicit `node.model`; inspector reads it directly; substrip pick has zero cascade; inspector pill for slave X touches only X. `routedThisSession` Set guards against re-PATCHing the same node on every snapshot poll. Rollback markers on failure so a transient route- service failure can retry on the next tick. anomalyco#1 — Nested-git multi-layer code change Shadow git was treating nested `.git` dirs as gitlinks (mode 160000) and skipping their contents. New mechanism in snapshot/index.ts: - findNestedGitDirs walks worktree to depth 4 (skipping node_modules / dist / build / target / out / .git / .pnpm / .yarn). Records every nested `.git` directory under sub-paths. - withHiddenNestedGit wraps the diff/stage pipeline: 1. Writes the planned rename list to `<gitdir>/snapshot_pending_ renames.json` BEFORE any rename (crash-recovery marker). 2. Renames each `<dir>/.git → <dir>/.git__snapshot_hidden__` (stable name, not a random suffix). 3. Runs the shadow git pipeline as `addUnsafe` (the renamed dir is now a plain directory, git recurses into it normally). 4. `Effect.ensuring` guarantees the restore loop runs even on exception — every hidden marker gets renamed back to `.git`. 5. Marker file deleted after clean restore. - recoverPendingRenames runs FIRST inside `track()` on every snapshot init: if a previous process died between forward-rename and restore, the boot scan finds the marker, renames each `.git__snapshot_ hidden__` back to `.git`, then deletes the marker. User's nested repos are guaranteed-usable before snapshot touches the worktree. anomalyco#1 (cont) — Code change page now shows directory tree Flat horizontal tab strip replaced with a vertical indented tree: - buildFileTree groups files by their directory path. - Single-child dir chains collapse into combined names so deep paths like `packages/opencode/src/foo` read as ONE row, not four nested. - FileTreeRow recursively renders dirs (collapsible chevron + folder icon) and files (FileCode2 icon + +/− deltas right-aligned). - State: per-component `collapsedDirs` Set + selected file index. - New `.wf-detail-file-tree` + `.wf-detail-file-tree-row` CSS in theme.css; supports active highlight, hover, max-height 240px with internal scroll. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
anomalyco#3 slave card state: persist `sessionNode` overlay to localStorage keyed by the workflow's root session id. Switching to Trace / Knowledge rails no longer wipes the opened slave-card overlay; the key is scoped per workflow so unrelated runs don't bleed state. anomalyco#10 image attachments in chat composer: paste / drop image files into the chat textarea, render thumbnails above the input, send as `file` parts via promptAsync(). Optimistic update mirrors the on-wire shape so the user sees their thumbnail in their own bubble immediately. Backward compatible — old callers that ignore the new `attachments` callback arg still work. anomalyco#1+anomalyco#8 push-driven runtime commands: split the slave-session prompt helper into `kickoffSlave()` (full preamble, fires once when a node starts) and `pushRuntimeCommand()` (just the command+ack, fires on every workflow_control). Previous code re-injected the full "You are executing workflow node X" preamble on every command push, so the slave saw the same framing 5+ times per run. New `pushRuntimeCommand` includes the actual command payload inline and explicitly tells the slave NOT to call workflow_pull — push IS the authoritative path; pull is fault-tolerance only. anomalyco#4 reasoning-step re-retrieve + compact re-injection: prompt loop now re-retrieves experiences every 6 tool-call steps (was: only step 1 of a turn), to counter slave drift on long chains. After compaction, the auto-continue path detects workflow sessions via Workflow.nodeBySession and prepends a brief "[compact-restoration]" framing that lists the node id, status, and any pending commands — so the slave doesn't think it's plain chat after history truncation. anomalyco#5 real pause semantics: pauseNode now flips status BEFORE cancelling the session, so any tool-call mid-flight sees the status-machine guard and its workflow_update gets rejected as illegal transition. Workflow.control refuses to enqueue new commands when status is `paused` or `cancelled`, returning a structured `refused: true` so the orchestrator stops retrying. anomalyco#6 serial-port multiplex: Serial.create checks for an existing session on the same device path with matching transport params before opening a fresh SerialPort instance. Returns the existing SerialID so the Serial Monitor UI and the debug-node serial_create tool can share a single physical port instead of racing for EBUSY. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…loop retrieve anomalyco#1 Global LLM refine — one shot, structured plan: - New Refiner.globalReRefineLLMPlan() sends the entire active library (compact projection: id/kind/title/abstract/scope/ categories/observation_count) to one LLM call, asks it to classify each entry as merge / delete / conflict / keep, and returns a structured plan with reasons. - Refiner.globalReRefineLLMApply() commits via existing mergeExperiences / deleteExperience so refinement_history and audit logs stay coherent and the time-windowed undo button can roll the whole batch back. - Server routes: POST /experimental/refiner/global-rerefine/llm/plan and /llm/apply. Cap default 200 exps (newest-refined first); user can pass `unbounded: true`. - UI: new 🪄 LLM 整理 button in the global re-refine modal; confirm dialog shows the LLM's overall_summary + counts before anything mutates. anomalyco#3 Live-push refiner changes via SSE: - New Refiner.Event.ExperienceChanged emitted from writeExperience and deleteExperience. The instance SSE relay already proxies every Bus event, so the frontend just subscribes by event-type string. - refiner-page subscribes to "refiner.experience.changed" via useSDK().event.on and triggers refetch() on any mutation — no more waiting 5 minutes for the poll interval to fire. anomalyco#2 Mid-loop user-message retrieve trigger: - Track `lastRetrievedUserID` across the reasoning loop. When a new user message arrives mid-loop (lastUser.id changes but step keeps marching), force a retrieve on the next step instead of waiting up to 5 more steps for the step % 6 cadence to align. This unblocks the case where the user types a follow-up while the agent is still tool-calling. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
STATUS - 2026-05-15T18:21:38.675ZLifecycle started: branch=issue/1-opencode, worktree=/root/CODE/issue-1-opencode |
DECISION - 2026-05-15T18:22:01.145Z选择外部同步层而不是 OpenCode 源码 patch Details用户确认从“改 OpenCode 源码做服务端持久化”切换为“不改 OpenCode 源码的第三方 sync service + 浏览器扩展/userscript”。理由:当前 OpenCode 插件机制无法稳定接管 Web UI sidebar / server route / DB schema;浏览器端同步层更符合上游更新不失效的目标。 |
STATUS - 2026-05-15T18:23:39.573ZRecorded design: thoughts/shared/designs/2026-05-16-opencode-sidebar-external-sync-design.md |
HANDOFF - 2026-05-15T18:24:03.793Zdesign complete; planner picks up external sidebar sync design DetailsDesign doc written and recorded at thoughts/shared/designs/2026-05-16-opencode-sidebar-external-sync-design.md. Key constraints: no OpenCode source patch as primary path; extension/userscript sync layer; /restart download and pairing page; no credentials/session/prompt sync. |
STATUS - 2026-05-15T18:24:47.082ZCommitted 32b5562, pushed=true |
HANDOFF - 2026-05-15T18:36:26.702Zready for executor at thoughts/shared/plans/2026-05-16-opencode-sidebar-external-sync.md DetailsExecutor should start from thoughts/shared/plans/2026-05-16-opencode-sidebar-external-sync.md. Atlas unavailable in this worktree; Project Memory lookup had no matching entries. Contract is frozen at thoughts/shared/plans/2026-05-16-opencode-sidebar-external-sync-contract.md. |
STATUS - 2026-05-15T18:36:26.690Zplan complete: 4 batches, 17 tasks DetailsCreated implementation plan for validated external sync design: 4 batches, 17 tasks, companion contract at thoughts/shared/plans/2026-05-16-opencode-sidebar-external-sync-contract.md. Plan keeps OpenCode source patching minimal and uses new packages/sidebar-sync plus Worker API and /restart route. |
STATUS - 2026-05-15T18:38:12.997ZCommitted 9b4c113, pushed=true |
STATUS - 2026-05-15T18:39:04.695ZRecorded plan: thoughts/shared/plans/2026-05-16-opencode-sidebar-external-sync.md |
STATUS - 2026-05-15T18:53:17.677Zbatch 1 complete: 5 tasks DetailsBatch 1 tasks 1.1-1.5 completed and reviewed. Note: packages/sidebar-sync bun typecheck remains blocked by current dependency install state (`tsgo: command not found`), but package config now declares @typescript/native-preview and reviewer approved as environment/install issue. |
STATUS - 2026-05-15T19:09:48.808Zbatch 2 complete: 5 tasks DetailsBatch 2 tasks 2.1-2.5 completed and reviewed. Fix cycles: 2.3 added safe fetch rejection/conflict/debounce/precedence tests; 2.4 added top-level request body whitelist validation for sidebar-sync routes. Tests passed: packages/sidebar-sync persisted-store/api-client/sync-controller/build; packages/function sidebar-sync.test. |
STATUS - 2026-05-15T19:19:45.867Zbatch 3 complete: 4 tasks DetailsBatch 3 tasks 3.1-3.4 completed and reviewed. Tests passed: packages/sidebar-sync extension-content/userscript, packages/app restart page. packages/app bun typecheck remains environment/install blocked by missing tsgo, but reviewer approved route code as correct. |
STATUS - 2026-05-15T19:40:32.666Zbatch 4 complete: 3 tasks DetailsBatch 4 tasks 4.1-4.3 completed and reviewed. Verified artifact copy via sidebar-sync build + app build, two-device sync E2E including 409 conflict handling, and worker security regression including forbidden keys/log safety/share smoke. |
STATUS - 2026-05-15T19:59:27.834ZCommitted a20d2794eb4f53c988557eaf042c989e8b34b554, pushed=false |
HANDOFF - 2026-05-15T19:59:40.941Zimplementation complete; ready for finish DetailsAll planned batches completed, reviewed, and committed via lifecycle_commit. Commit SHA: a20d2794eb4f53c988557eaf042c989e8b34b554. push=false per executor call. Verification: sidebar-sync tests/typecheck pass; function sidebar-sync/security tests pass; app restart test passes; sidebar-sync build + app build pass. App typecheck remains blocked by pre-existing packages/app/src/custom-elements.d.ts parse error unrelated to this route change. |
STATUS - 2026-05-15T20:00:56.924ZRecorded commit: a20d2794eb4f53c988557eaf042c989e8b34b554 |
STATUS - 2026-05-15T20:01:12.485ZFinished: merged=false, prUrl=(none) |
STATUS - 2026-05-15T20:02:28.380ZFinished: merged=false, prUrl=(none) |
STATUS - 2026-05-15T20:11:07.689ZCommitted 1e0d1cf, pushed=false |
STATUS - 2026-05-15T20:11:32.118ZCommitted (no-op), pushed=false |
STATUS - 2026-05-15T20:12:38.457ZFinished: merged=false, prUrl=(none) |
STATUS - 2026-05-15T20:13:45.248ZRecorded pr: Wuxie233#2 |
STATUS - 2026-05-15T20:44:16.489ZFinished: merged=false, prUrl=(none) |
STATUS - 2026-05-15T20:44:31.151Zmanual remote finish completed; lifecycle_finish local cleanup blocked by worktree checkout limitation Detailslifecycle_finish repeatedly failed locally because the tool attempted to create/check out dev while dev is already checked out at /root/CODE/opencode, and earlier PR creation targeted the wrong GitHub owner. Manual safe finish was completed against fork repository Wuxie233/opencode: PR https://github.com/Wuxie233/pull/2 merged at 2026-05-15T20:43:44Z with merge commit 4080a50; issue https://github.com/Wuxie233/issues/1 closed at 2026-05-15T20:43:45Z. No OpenCode restart performed. |
No description provided.