Skip to content

feat(plugins): add live timeline rows and client slash commands - #4192

Merged
boudra merged 23 commits into
mainfrom
plugin-slash-commands-timeline
Sep 2, 2026
Merged

feat(plugins): add live timeline rows and client slash commands#4192
boudra merged 23 commits into
mainfrom
plugin-slash-commands-timeline

Conversation

@boudra

@boudra boudra commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Plugins can transform live timeline source items at render time, append durable plugin rows from daemon handlers, and register client-only slash commands in the composer.

Related to #3940 and #3952.

Phase 1 — transformers at render time

  • Projects plugin timeline items once in the view, before history-window calculation, and feeds already-projected head/tail into the render model.
  • Preserves source identity across streaming updates and memoizes by source-item reference.
  • Gives transformers recursively frozen, detached source snapshots so mutations cannot corrupt stream state.
  • Removes projected-tail refetch, reprojection debounce, forced replacement, and catalog-refetch machinery.
  • Exports useRevealedText; the inline-thinking example replaces Claude reasoning from the first streamed delta.
  • Adds a regression proving a filtering transformer before the history-window start does not change mounted items.

Gate evidence:

  • packages/app/src/agent-stream/model.test.ts — 10 passed.

  • packages/app/src/plugins/timeline/model.test.ts — 7 passed.

  • packages/app/src/plugins/timeline/projection.test.ts — 5 passed, including strict-mode mutation containment.

  • packages/app/src/timeline/viewed-timeline-sync.test.ts — 31 passed.

  • packages/app/src/types/stream.test.ts — 58 passed.

  • plugin-examples/timeline-items/pi-tasks.test.ts — 4 passed.

  • 114 focused tests passed; the history-window regression was observed red before the fix.

  • npm run typecheck, npm run lint, and npm run format passed.

  • Worktree-local browser QA on daemon 127.0.0.1:6782: Claude Opus 4.6 reasoning rendered inline while streaming with zero built-in thinking labels. A marked plugin DOM node stayed connected and identical while revealed text grew from 1,696 to 2,088 characters across deltas.

  • Screenshot retained outside the repository: /home/moboudra/.paseo/qa/pr-4192/inline-thinking-midstream.png.

CI follow-up gate:

  • Reverted the two speculative history-window commits (d7ee9e725, 3cbe7cade) before comparison testing.
  • The reverted branch exposed one real regression: Phase 1 had removed the forced-reset semantics from fetchLatestTail along with the plugin reprojection callers, but catch-up overflow also uses that port. The overflow tail response is once again applied with reset: true; no history-window generation or reducer/view recenter mechanism remains.
  • packages/app/src/timeline/viewed-timeline-sync.test.ts — 31 passed.
  • packages/app/e2e/browser/agent-timeline-resume.spec.ts — 4 passed with --repeat-each=2; this covers the exact branch-only failure and unchanged-resume scroll preservation.
  • npm run typecheck, npm run lint, and npm run format passed.

Reverted-branch vs main comparison (main pinned at 78f7796e):

Playwright spec Reverted branch main Classification
agent-timeline-resume.spec.ts 1 failed, 1 passed 2 passed Branch-only regression from lost forced-reset semantics; restored and then passed 4/4
agent-tab-image-stability.spec.ts 5 passed 5 passed Passes both
agent-message-submission.spec.ts 27 passed 27 passed Passes both
agent-stream-ui.spec.ts 8 passed 1 failed, 7 passed Fails on main; out of scope
agent-consecutive-turns.spec.ts 5 passed 5 passed Passes both

Phase 2 — plugin timeline rows

  • Adds the plugin wire variant, shared timelineItemIdentity, append RPC, capability flag, and SDK agent.timeline.append handle.
  • Daemon-stamps pluginId from a shared plugin-session identity helper and rejects non-plugin callers.
  • Enforces a 64 KiB serialized-data limit before append and rejects oversized rows; append versions must be positive integers.
  • Generalizes daemon/client collapse through stable identity without allocating fake timeline objects in the reducer hot path.
  • Makes pluginItemId required in stream/cache state; legacy malformed cache rows are dropped.
  • Uses per-type lifecycle merging: tool calls retain turn scoping and plugin rows are last-wins.
  • Persists plugin rows in the replica cache and renders the unavailable placeholder when the owning plugin is absent.
  • Resets a failed renderer only when the streaming row data reference changes, allowing recovery without resets on unchanged renders.

Gate evidence:

  • packages/protocol/src/messages.plugins.test.ts — 12 passed.

  • packages/protocol/src/timeline-identity.test.ts — 1 passed.

  • packages/server/src/server/agent/timeline-projection.test.ts — 20 passed.

  • packages/server/src/server/agent/agent-timeline-content.test.ts — 1 passed.

  • packages/server/src/server/session.test.ts — 147 passed.

  • packages/server/src/server/plugins/plugin-paseo-api.e2e.test.ts — 2 passed.

  • packages/server/src/server/websocket-server.relay-reconnect.test.ts — 25 passed.

  • packages/app/src/types/stream.test.ts — 59 passed.

  • packages/app/src/timeline/session-stream-reducers.test.ts — 120 passed.

  • packages/app/src/runtime/replica-cache/index.test.ts — 19 passed.

  • packages/app/src/plugins/timeline/projection.test.ts — 4 passed.

  • packages/app/src/plugins/timeline/view.test.tsx — 3 passed, including recovery from an intermediate renderer failure.

  • packages/app/src/plugins/surface-error-boundary.test.tsx — 1 passed.

  • packages/client/src/index.test.ts — 14 passed.

  • 426 focused tests passed; positive-version, size-cap, and cache-schema regressions were observed red before their fixes.

  • npm run build:server, npm run typecheck, npm run lint, and npm run format passed.

  • The same real browser session ran /review foo through a throwaway client slash command. Its plugin subprocess appended the row through timeline.append; after the plugin was removed, the canonical row remained and rendered Plugin timeline item unavailable.

  • Screenshots retained outside the repository:

    • /home/moboudra/.paseo/qa/pr-4192/review-command-row.png
    • /home/moboudra/.paseo/qa/pr-4192/review-row-unavailable.png

Phase 3 — client slash commands

  • Adds addClientSlashCommand with agent/workspace contexts and raw trimmed args.
  • Strips client registration from server bundles and validates registrations in the app registry.
  • Owns precedence in one merge site: built-in → plugin → provider, including aliases and deterministic plugin collisions.
  • Executes client commands before queue/send, clears the input, reports callback failures by toast, rejects attachments, and never forwards handled text to the agent. Plugin work does not hold the composer pending.

Gate evidence:

  • packages/app/src/plugins/client-slash-commands/model.test.ts — 4 passed, including a never-resolving callback that leaves submission available.
  • packages/server/src/server/plugins/compiler.test.ts — 9 passed.
  • packages/plugin/src/host.test.ts — 5 passed.
  • 17 focused tests passed; the built-in-alias collision and automatic-JSX regressions were observed red first.
  • npm run build:plugin, npm run typecheck, npm run lint, and npm run format passed.
  • Real browser QA verified /review foo cleared the composer, did not reach Claude, invoked the plugin RPC, and produced the appended row shown above.

Phase 4 — docs and examples

  • Documents render-time transform phases, stable identities, canonical appended rows, capability gating, nonblocking client slash-command ownership, and the 64 KiB serialized-data limit in internal and public plugin docs.
  • Updates the timeline examples and pins automatic JSX in the client plugin compiler, independent of any surrounding tsconfig.json.

Gate evidence:

  • packages/server/src/server/plugins/compiler.test.ts — 9 passed.
  • plugin-examples/timeline-items/pi-tasks.test.ts — 4 passed.
  • The inline-thinking plugin first failed in the real browser with React is not defined; after the compiler fix it loaded from the worktree-local daemon without a default React import. The compiler regression proves a TSX client module with no React import emits react/jsx-runtime calls and no React.createElement.
  • npm run typecheck, npm run lint, and npm run format passed.

Known flaky on main

  • agent-stream-ui.spec.ts failed in the clean pinned-main comparison while passing on the reverted PR branch. The file also has a historical shard-1 failure on main in Actions run 33303057502 (a different test case), so it is not attributed to this PR.

Not verified

  • Manual UI QA covered browser web against the worktree-local daemon and real Claude provider. iOS, Android, and Electron were not manually exercised in this pass.
  • The full local test suite was intentionally not run per repository policy; focused suites ran locally and GitHub Actions is the full-suite gate.

Final CI

  • Latest head 986ad28ea is green: builds, format, lint, typecheck, app/SDK/server tests, and all four Playwright shards passed in CI run 33614592311. Platform jobs excluded by the changes gate were skipped.

Notes

  • The worktree supplied branch plugin-slash-commands-timeline; all phase and fix-up commits remain on that branch.
  • The managing daemon on port 6767 was not touched.

Keep canonical stream rows in state and project plugin replacements only when building the render model. This removes tail refetch coordination and keeps streaming row identity tied to the source item.
A resume tail can preserve overlapping item identities while atomically adding enough history to overflow the mounted window. Carry the reducer's authoritative tail-reset decision into the view so the window recenters without disturbing live appends or unchanged resumes.
Index zero means the complete loaded window is intentionally visible. Keeping a row identity there hid canonical rows inserted before an optimistic submission, so retain the show-all sentinel and reserve explicit resets for authoritative resume tails.
A resumed tail can append enough canonical history to overflow an initially complete mounted window while retaining every old item identity. The history hook therefore cannot distinguish the authoritative resume from a live append. Carry the reducer-owned resume decision into the view and keep the null show-all boundary so canonical prepends remain visible.\n\nProven by agent-timeline-resume.spec.ts, which failed consistently after reverting d7ee9e7 and 3cbe7ca and passes twice with this focused signal.
fetchUntilCurrent at packages/app/src/timeline/viewed-timeline-sync.ts:558 depends on fetchLatestTail applying its response as a reset when catch-up overflows. Restore that producer contract without restoring any plugin reprojection callers or debounce machinery.\n\nProven by agent-timeline-resume.spec.ts with --repeat-each=2.
@boudra boudra changed the title feat(plugins): add live timeline rows and client commands feat(plugins): add live timeline rows and client slash commands Sep 2, 2026
@boudra
boudra marked this pull request as ready for review September 2, 2026 11:11
@boudra
boudra merged commit e34aea2 into main Sep 2, 2026
24 checks passed
@boudra
boudra deleted the plugin-slash-commands-timeline branch September 2, 2026 11:12
@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR extends plugins with render-time timeline transformation, durable daemon-appended timeline rows, and client-only composer commands.

  • Projects transformed timeline items before history-window rendering while preserving source identity.
  • Adds the plugin timeline wire variant, append API, daemon-owned plugin identity, projection, cache persistence, and unavailable rendering.
  • Adds client slash-command registration, precedence, context binding, composer execution, documentation, and examples.

Confidence Score: 4/5

The PR appears safe to merge, with one non-blocking request to replace the mocked JSDOM renderer-recovery test with coverage through an approved interface.

The changed runtime paths preserve plugin identity, authorization, timeline replacement, and command ownership invariants; the remaining concern is limited to the fidelity and maintainability of one new test.

Files Needing Attention: packages/app/src/plugins/surface-error-boundary.test.tsx

Important Files Changed

Filename Overview
packages/app/src/agent-stream/view.tsx Applies plugin timeline projection to retained history and live head before history-window and render-model construction.
packages/app/src/composer/index.tsx Resolves and executes client slash commands before normal send or queue behavior while preserving nonblocking callback execution.
packages/app/src/plugins/timeline/projection.ts Projects detached frozen source snapshots into stable plugin rows with source-reference memoization.
packages/app/src/plugins/surface-error-boundary.test.tsx Adds renderer-recovery coverage, but uses a repository-prohibited mocked JSDOM component test.
packages/server/src/server/session.ts Restricts timeline append requests to plugin sessions and stamps the runtime plugin identity before persistence.
packages/server/src/server/agent/timeline-projection.ts Collapses canonical plugin lifecycle rows by stable plugin identity using last-wins semantics.
packages/protocol/src/messages.ts Adds validated plugin timeline items, append request/response messages, and capability signaling.
packages/client/src/index.ts Exposes durable plugin timeline append through the high-level agent SDK handle.

Sequence Diagram

sequenceDiagram
    participant Plugin as Plugin handler
    participant SDK as Paseo SDK
    participant Daemon as Daemon session
    participant Timeline as Canonical timeline
    participant App as App stream
    participant Renderer as Plugin renderer
    Plugin->>SDK: agent.timeline.append(item)
    SDK->>Daemon: append request
    Daemon->>Daemon: Derive pluginId from session
    Daemon->>Daemon: Validate version and data size
    Daemon->>Timeline: Persist canonical row
    Timeline-->>App: Stream plugin row
    App->>App: Collapse by stable identity
    App->>Renderer: Validate and render data
    Renderer-->>App: Plugin view or unavailable placeholder
Loading

Reviews (1): Last reviewed commit: "fix(app): restore catch-up tail replacem..." | Re-trigger Greptile

import { Text } from "react-native";
import { SurfaceErrorBoundary } from "./surface-error-boundary";

vi.mock("react-native-unistyles", () => ({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Mocked renderer recovery test

This component test replaces react-native-unistyles with vi.mock and mounts under JSDOM, so it can pass against behavior supplied by the mock rather than verifying renderer recovery through the production application boundary. Cover this behavior through the real app harness or an extracted module interface.

Rule Used: # Code Review Pattern Reference: Slop, Tests, Feat... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant