Skip to content

Ongoing fork#1

Open
patroza wants to merge 133 commits into
mainfrom
patroza/import-external-sessions
Open

Ongoing fork#1
patroza wants to merge 133 commits into
mainfrom
patroza/import-external-sessions

Conversation

@patroza

@patroza patroza commented Jul 4, 2026

Copy link
Copy Markdown
Owner

No description provided.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Jul 10, 2026
@patroza
patroza force-pushed the patroza/import-external-sessions branch from f50a087 to 0bac5f8 Compare July 10, 2026 10:33
patroza and others added 27 commits July 12, 2026 18:24
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
patroza and others added 30 commits July 12, 2026 20:43
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant