feat(plugins): add live timeline rows and client slash commands - #4192
Conversation
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.
This reverts commit 3cbe7ca.
This reverts commit d7ee9e7.
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.
This reverts commit 02f8963.
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.
|
| 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
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", () => ({ |
There was a problem hiding this comment.
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!
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
useRevealedText; the inline-thinking example replaces Claude reasoning from the first streamed delta.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, andnpm run formatpassed.Worktree-local browser QA on daemon
127.0.0.1:6782: Claude Opus 4.6 reasoning rendered inline while streaming with zero built-inthinkinglabels. 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:
d7ee9e725,3cbe7cade) before comparison testing.fetchLatestTailalong with the plugin reprojection callers, but catch-up overflow also uses that port. The overflow tail response is once again applied withreset: 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, andnpm run formatpassed.Reverted-branch vs
maincomparison (main pinned at78f7796e):mainagent-timeline-resume.spec.tsagent-tab-image-stability.spec.tsagent-message-submission.spec.tsagent-stream-ui.spec.tsagent-consecutive-turns.spec.tsPhase 2 — plugin timeline rows
pluginwire variant, sharedtimelineItemIdentity, append RPC, capability flag, and SDKagent.timeline.appendhandle.pluginIdfrom a shared plugin-session identity helper and rejects non-plugin callers.pluginItemIdrequired in stream/cache state; legacy malformed cache rows are dropped.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, andnpm run formatpassed.The same real browser session ran
/review foothrough a throwaway client slash command. Its plugin subprocess appended the row throughtimeline.append; after the plugin was removed, the canonical row remained and renderedPlugin 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.pngPhase 3 — client slash commands
addClientSlashCommandwith agent/workspace contexts and raw trimmedargs.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.npm run build:plugin,npm run typecheck,npm run lint, andnpm run formatpassed./review foocleared the composer, did not reach Claude, invoked the plugin RPC, and produced the appended row shown above.Phase 4 — docs and examples
tsconfig.json.Gate evidence:
packages/server/src/server/plugins/compiler.test.ts— 9 passed.plugin-examples/timeline-items/pi-tasks.test.ts— 4 passed.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 emitsreact/jsx-runtimecalls and noReact.createElement.npm run typecheck,npm run lint, andnpm run formatpassed.Known flaky on main
agent-stream-ui.spec.tsfailed 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
Final CI
986ad28eais 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
plugin-slash-commands-timeline; all phase and fix-up commits remain on that branch.