Ongoing fork#1
Open
patroza wants to merge 133 commits into
Open
Conversation
patroza
force-pushed
the
patroza/import-external-sessions
branch
from
July 10, 2026 10:33
f50a087 to
0bac5f8
Compare
Will be reimplemented as checkout hooks instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Document existing scoped search, the in-thread find PR (pingdotgg#3539), the unaddressed cross-thread content search (pingdotgg#3509), why the gap is common, and a design sketch leveraging the event-sourced store. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capture a deferred, provider-agnostic idea queue: write ideas per project without credits or a chosen provider, and promote to a normal turn later. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add integrations section: drop-folder + MCP/API enqueue backbone, Obsidian (file-based), GitHub Issues (API-based, reuses PR machinery), quick-capture front-ends, and task managers, with result write-back and sequencing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add shared error detail text/copy UI with responsive clamping - Keep error toasts readable and prioritize them in stacked layouts - Update error banners to use the shared renderer
- add shared thread model presentation helper - render provider icon and tooltip for thread model selection - cover provider and fallback cases with tests
- Use merge-base diffs for range context while keeping commit logs on the branch range - Reorder and visually refine the sidebar provider icon and metadata layout
Poll the local ai-usage daemon (/dms) every 60s on the server and stream a per-environment snapshot to the web over a new subscribeAiUsage RPC, so providers near or over their plan limits are surfaced where models are picked. - contracts: AiUsageSnapshot schemas + subscribeAiUsage stream RPC; sidebarHideProviderIcons client setting - server: AiUsageMonitor service (HttpClient + Schedule, tolerant of the daemon being down) wired into the ws handler + runtime layer - client-runtime: aiUsage environment subscription atom family - web: two-channel provider marker — dot fill = current usage on the immediate window (red at any 100% cap, orange at >=80%), amber ring = weekly/pace outlook — on thread icons, the composer trigger, and the picker; hover shows per-window stats; picker rail ordered best-to-use-now first - opencode driver hosts opencode-go + z.ai: icon/tooltip resolve the active sub-provider (z.ai overrides go), picker shows both stat blocks - sidebar: dimmer provider icons + "hide unless cmd" toggle that still shows warn/critical markers Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The picker's per-provider usage footer grew tall enough (two blocks with bars for the opencode driver) to crowd out the model list. Make the in-menu stats a compact, height-capped footer so the list stays the primary surface. - AiUsageStats: `compact` mode drops the per-window bars and shows a sub-line only when there's a pace warning (plain reset times stay in the hover form) - ModelPickerContent: render the footer compact, max-h-40, shrink-0, scrollable Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
z.ai's rate-limit error ("Your limit will reset at <ts>") carries a bare
wall-clock timestamp in China time (UTC+8) with no zone marker, relayed
verbatim through opencode, so the work-log entry showed a reset time ~6-7h off
for non-CST viewers.
Rewrite it at the display layer: localizeZaiResetTime() is gated on z.ai's
"limit will reset at <YYYY-MM-DD HH:MM:SS>" wording, interprets the timestamp
as UTC+8, and rewrites it to the viewer's local time with a zone abbreviation.
Other providers' errors pass through untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Apply UTC+8 reset-time localization to rendered headings and previews - Avoid double-shifting localized text by formatting once at render time
Effect's RpcClient multiplexes real requests with transport control frames (Interrupt on request-cancel, Ack for chunk backpressure, Ping/Eof). Our clientProtocol.send forwarded all of them to the agent's stdin, but ACP is plain JSON-RPC and has no such concept: a spec-compliant agent cannot decode them. grok rejects the line with "Method not found" and its ACP handler is left wedged, silently dropping every subsequent session/update. Because Effect emits Interrupt whenever an in-flight request settles, this fired on normal turn completion, not just a user Stop — so a wedge was routine rather than exotic, and looked like the thread had simply gone quiet. Only real ACP messages (Request, including id:"" notifications) now reach the wire. Agent-side cancellation was already expressed via session/cancel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The assistant segment counter restarts at 0 on every session start, but the ACP sessionId survives a resume. Item ids were derived from sessionId + segment alone, so a resumed session re-minted ids that already belonged to messages from its earlier runs. The projector treats a known message id with streaming:true as a continuation and concatenates: `existing.text + new.text`. A fresh answer therefore got glued onto the end of a days-old message instead of appearing as its own — data corruption that looked like "the agent stopped answering", since the reply was rendered far up the transcript. Item ids now carry a per-runtime id, which is stable for a run (streaming deltas still concatenate correctly) and unique across resumes. Wall clock separates runs across process restarts, where a bare counter would reset and collide again. This affects every ACP provider: grok, cursor and xai. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The grok adapter never watched its child process. getEvents() is a queue stream that neither fails nor completes when the agent dies, so the notification fiber just starved: no error, no completion, and a thread left looking live that never advanced again. Watch processExit and report it (mirrors CursorAdapter), but stay quiet when the child was signalled down with us — systemd SIGTERMs the whole cgroup on stop, reaching the agent before our own teardown can mark the session stopped, and reporting that as a grok failure cries wolf on every shutdown. Also covers the item-id reuse regression: resume a session and assert the two runs share no assistant item ids. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nscript repair The projection is independently writable, but it is not how clients learn anything: a warm-cache client reuses its stored snapshot and resumes from `afterSequence`, so it only ever sees events past that cursor and never re-reads the projection. Anything written straight to projection_thread_messages is therefore invisible to it — permanently, across restarts, because the cache is persisted. Repairing a transcript needs an event, not a row. The event carries a rewind point rather than a whole snapshot: everything up to and including `afterMessageId` is known-good and untouched, and only the tail after it is replaced. Rebuilding a thread then costs a splice instead of re-downloading it. `afterMessageId: null` replaces everything, for repairing a transcript whose existing messages are wrong rather than merely missing. The client reducer mirrors thread.reverted: it truncates locally and splices, with no refetch. When it does not hold the anchor it cannot splice precisely, so it reports reload-required and the caller re-reads the thread rather than rendering a wrong transcript. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on log When a grok ACP session drops updates, or the conversation continues outside T3, the thread is left missing the tail while grok keeps recording it. This reconstructs that tail: anchor on T3's last assistant message, walk grok's log after it, and emit one thread.messages-resynced event. Reads updates.jsonl, NOT chat_history.jsonl. chat_history is grok's LLM context, not a transcript — grok compacts it, rewriting and discarding old turns (observed shrinking 736 lines to 34), so it cannot be relied on to still hold what T3 missed. updates.jsonl is the append-only session/update log: it survives compaction, carries real emit timestamps, and is what T3 would have ingested over ACP anyway. Messages the thread already has keep their identity and timestamp and are not re-added; only genuinely new ones are inserted, so it is idempotent. --rebuild-all replaces the whole transcript for a thread whose messages are wrong rather than missing; --force re-emits for clients stranded on a stale cached snapshot. Extracts the sqlite/uuid helpers importSessions had inline, now that a second tool needs them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… opens it Grok records every message to updates.jsonl regardless of who drives the session, so that log is the authority on what was said — including turns T3 never saw because the ACP stream dropped them or the session was driven from another grok client. Opening a thread is when we find out, so check then and dispatch a resync. Dispatching rather than writing the projection is what makes it visible: the event flows through the projector and out over the live stream, so an open thread heals in place with no restart and no cache clearing. It is awaited so that any resync is persisted before the snapshot read and catch-up replay below, instead of racing them. Guards: a running session is skipped because its ACP stream owns the turn and would race a partial streamed message; failures are swallowed because opening a thread must not depend on a provider's log being readable. The command id is derived from the resync's content, so several clients opening the same thread at once collapse to one event via command-receipt dedup instead of a burst. Also fixes isThreadDetailEvent, which whitelisted thread event types and so dropped thread.messages-resynced before it reached any subscriber — the event was written and projected, then silently discarded on the way out, which no client could recover from. It is now exported and tested, since it being untested is why that went unnoticed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ork base Adopts the OpenCode session-resume hardening from upstream pingdotgg#3617 (the pingdotgg#3604 class of silent context loss) onto this fork's adapter. Hand-ported, not cherry-picked: the fork runs ahead of upstream on @opencode-ai/sdk (1.17 vs the PR's 1.3) and passes a per-call `directory` to every client.session.* call, and it already had an independent, strict resume implementation. The upstream diff does not apply cleanly, so the PR's robustness was ported onto the fork's directory-aware base, preserving the per-call `directory` and isOpenCodeMessageAborted. - Schema-versioned cursor (OPENCODE_RESUME_VERSION / parseOpenCodeResume): malformed or foreign cursors are ignored rather than throwing. - isOpenCodeNotFound classifies only structured 404 / NotFoundError signals. A confirmed miss falls back to session.create; transport/auth/5xx errors propagate instead of masquerading as a brand-new empty session. - Fork on cwd change: when the stored directory differs (e.g. the thread moved into a git worktree), session.fork carries history into the requested directory instead of dropping context. - session.update re-applies the current runtimeMode ruleset on reuse/fork, since session.create is skipped on resume. - Race cleanup aborts only a session this call created; sendTurn echoes resumeCursor so the persisted cursor stays fresh. This trades a loud failure for a silent recovery on cwd mismatch: the fork previously hard-errored, and the two tests asserting that are replaced. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s onto fork base" This reverts commit 0e5fb26. Its centerpiece does not work. The adopted code forks a session into the requested directory on a cwd change, on the stated premise that "OpenCode routes tool execution by the per-request `directory`, NOT the session's stored directory". Probed against a live opencode 1.17.20 server, both halves of that are false: create(directory=B) -> session.directory=B, `pwd` -> B (create binds) reuse + directory=B -> `pwd` -> A (session dir wins) fork(A, directory=B) -> session.directory=A, `pwd` -> A (fork ignores it) Tool execution follows the session's OWN directory; the per-request `directory` is a lookup/app-context scope, not a binding. session.fork inherits the parent's directory and cannot be pointed elsewhere, and session.update cannot rebind it (its body only accepts `title`). create({directory, parentID}) does bind the new directory but starts empty — parentID is a child link, not a history copy. So no opencode API moves a session's history into a new directory. The practical effect is worse than what it replaced: the fork's session stays bound to the old tree, so after a thread moves into a git worktree the agent would keep editing the ORIGINAL checkout while T3 reports the worktree — silent wrong-tree writes. The prior strict behaviour at least failed loudly. The PR's other parts are genuinely good (schema-versioned cursor, 404-only fallback so transport/5xx errors propagate, resumeCursor echo, abort-only-if- created) and are worth re-landing without the fork. Reverted wholesale rather than patched in place so the broken path cannot ship while that is sorted out. Note the PR's tests are entirely mocked, which is why this passed review twice: mocks cannot observe where a tool actually runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(chat): interleave mid-turn steers between pre/post AI output Replace the fork workaround that forced steer messages above all live turn work. Split assistant text at client-observed boundaries and sort tools/messages so steers land chronologically between pre-steer and post-steer content on web and mobile. * fix(server): settle orphan provider sessions so restarts/resync recover After server crashes, sessions could stay running with active turns while no process existed, which blocked reaper and Grok transcript resync and left desktop thread open on huge stale snapshots. - Add OrphanSessionRecovery to force-settle session + runtime + turns - Run a full orphan audit on startup instead of shell-session-only interrupt - Reaper force-settles active-turn zombies when no live process remains - Grok resync settles zombie running runtimes before catching up the log - Resync on HTTP thread snapshot (desktop cold load path) - Emit graceful session.exited on Grok signal termination for durable settle
Wire the standard ACP session/set_mode method through effect-acp and prefer it when an agent does not expose a mode config option (Grok). Cursor-style agents that advertise a mode config option still use session/set_config_option. Mode state is seeded/updated from current_mode_update even when session/new omits the modes catalog.
Enable Grok's Plan/Build toggle and map interactionMode onto ACP session/set_mode. Detect agent-initiated mode changes (current_mode_update) and sync thread interaction mode via session.mode.changed. Capture exit_plan_mode permission requests as proposed plans and deny auto-exit so T3 owns plan approval before implementation.
Real Grok presents plans via the exit_plan_mode reverse-RPC (not by denying the tool permission). Auto-allow the permission, emit turn.proposed.completed from planContent, hold the reverse-RPC until implement/refine/abandon, and resolve on the next sendTurn or stop.
Holding the reverse-RPC left the session running, so the UI stayed on Working and hid Plan Ready / implement actions (gated on turn settled). Capture the plan then reject exit immediately (Claude-style wait message) so the turn can complete and T3 can show plan approval UX.
Grok often keeps the turn running for minutes after exit_plan_mode (especially when it enters plan mid-turn from build mode). Plan Ready and Implement were gated on turn settle, so they only appeared after extra messages or a long wait. Show the plan follow-up composer and Plan Ready status from an actionable plan in plan mode without requiring settle; prefer Plan Ready over Working in the sidebar. Soften Grok exit_plan reject wording so the agent is less likely to re-enter a revise loop.
Package and deploy the latest extension build for b18/smart.
Share proposed-plan helpers, wire interaction mode + Plan Ready / Implement / Refine in the VS Code webview, and package the extension for local client, vscode-server, and b18.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.