@agent-native/core 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/action.d.ts +8 -1
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +20 -10
- package/dist/action.js.map +1 -1
- package/dist/cli/app-skill.d.ts +3 -1
- package/dist/cli/app-skill.d.ts.map +1 -1
- package/dist/cli/app-skill.js +50 -8
- package/dist/cli/app-skill.js.map +1 -1
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +39 -5
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +9 -7
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/index.js +42 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-config-writers.d.ts +10 -0
- package/dist/cli/mcp-config-writers.d.ts.map +1 -1
- package/dist/cli/mcp-config-writers.js +60 -6
- package/dist/cli/mcp-config-writers.js.map +1 -1
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +4 -6
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/plan-local.d.ts.map +1 -1
- package/dist/cli/plan-local.js +15 -2
- package/dist/cli/plan-local.js.map +1 -1
- package/dist/cli/plan-publish-store.d.ts +17 -7
- package/dist/cli/plan-publish-store.d.ts.map +1 -1
- package/dist/cli/plan-publish-store.js +33 -8
- package/dist/cli/plan-publish-store.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +63 -5
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +641 -48
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/skills.d.ts +26 -11
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +644 -972
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/templates-meta.d.ts.map +1 -1
- package/dist/cli/templates-meta.js +3 -2
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.js +37 -9
- package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
- package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/DiffBlock.js +44 -12
- package/dist/client/blocks/library/DiffBlock.js.map +1 -1
- package/dist/client/blocks/library/annotation-rail.d.ts +12 -3
- package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
- package/dist/client/blocks/library/annotation-rail.js +29 -3
- package/dist/client/blocks/library/annotation-rail.js.map +1 -1
- package/dist/client/blocks/library/html.d.ts.map +1 -1
- package/dist/client/blocks/library/html.js +3 -1
- package/dist/client/blocks/library/html.js.map +1 -1
- package/dist/client/blocks/library/question-form.d.ts.map +1 -1
- package/dist/client/blocks/library/question-form.js +4 -1
- package/dist/client/blocks/library/question-form.js.map +1 -1
- package/dist/client/components/LiveCursorOverlay.d.ts +46 -0
- package/dist/client/components/LiveCursorOverlay.d.ts.map +1 -0
- package/dist/client/components/LiveCursorOverlay.js +137 -0
- package/dist/client/components/LiveCursorOverlay.js.map +1 -0
- package/dist/client/components/PresenceBar.d.ts +11 -1
- package/dist/client/components/PresenceBar.d.ts.map +1 -1
- package/dist/client/components/PresenceBar.js +39 -7
- package/dist/client/components/PresenceBar.js.map +1 -1
- package/dist/client/components/RemoteSelectionRings.d.ts +43 -0
- package/dist/client/components/RemoteSelectionRings.d.ts.map +1 -0
- package/dist/client/components/RemoteSelectionRings.js +116 -0
- package/dist/client/components/RemoteSelectionRings.js.map +1 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +5 -0
- package/dist/client/index.js.map +1 -1
- package/dist/collab/awareness.d.ts +25 -0
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/awareness.js +42 -5
- package/dist/collab/awareness.js.map +1 -1
- package/dist/collab/client.d.ts +19 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +362 -57
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/follow-mode.d.ts +56 -0
- package/dist/collab/follow-mode.d.ts.map +1 -0
- package/dist/collab/follow-mode.js +54 -0
- package/dist/collab/follow-mode.js.map +1 -0
- package/dist/collab/index.d.ts +3 -1
- package/dist/collab/index.d.ts.map +1 -1
- package/dist/collab/index.js +5 -1
- package/dist/collab/index.js.map +1 -1
- package/dist/collab/presence.d.ts +56 -0
- package/dist/collab/presence.d.ts.map +1 -0
- package/dist/collab/presence.js +98 -0
- package/dist/collab/presence.js.map +1 -0
- package/dist/collab/routes.d.ts.map +1 -1
- package/dist/collab/routes.js +33 -6
- package/dist/collab/routes.js.map +1 -1
- package/dist/collab/struct-routes.d.ts.map +1 -1
- package/dist/collab/struct-routes.js +24 -4
- package/dist/collab/struct-routes.js.map +1 -1
- package/dist/collab/ydoc-manager.d.ts +13 -0
- package/dist/collab/ydoc-manager.d.ts.map +1 -1
- package/dist/collab/ydoc-manager.js +51 -15
- package/dist/collab/ydoc-manager.js.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +2 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/extensions/routes.d.ts +18 -0
- package/dist/extensions/routes.d.ts.map +1 -1
- package/dist/extensions/routes.js +30 -8
- package/dist/extensions/routes.js.map +1 -1
- package/dist/oauth-tokens/store.d.ts.map +1 -1
- package/dist/oauth-tokens/store.js +42 -5
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/scripts/db/index.d.ts.map +1 -1
- package/dist/scripts/db/index.js +1 -0
- package/dist/scripts/db/index.js.map +1 -1
- package/dist/scripts/db/migrate-encrypt-oauth-tokens.d.ts +28 -0
- package/dist/scripts/db/migrate-encrypt-oauth-tokens.d.ts.map +1 -0
- package/dist/scripts/db/migrate-encrypt-oauth-tokens.js +164 -0
- package/dist/scripts/db/migrate-encrypt-oauth-tokens.js.map +1 -0
- package/dist/scripts/db/scoping.d.ts.map +1 -1
- package/dist/scripts/db/scoping.js +7 -5
- package/dist/scripts/db/scoping.js.map +1 -1
- package/dist/secrets/index.d.ts +1 -0
- package/dist/secrets/index.d.ts.map +1 -1
- package/dist/secrets/index.js +4 -0
- package/dist/secrets/index.js.map +1 -1
- package/dist/server/collab-plugin.d.ts +6 -0
- package/dist/server/collab-plugin.d.ts.map +1 -1
- package/dist/server/collab-plugin.js +105 -5
- package/dist/server/collab-plugin.js.map +1 -1
- package/dist/server/poll-events.d.ts +5 -0
- package/dist/server/poll-events.d.ts.map +1 -1
- package/dist/server/poll-events.js +27 -4
- package/dist/server/poll-events.js.map +1 -1
- package/dist/sharing/actions/set-resource-visibility.d.ts.map +1 -1
- package/dist/sharing/actions/set-resource-visibility.js +4 -1
- package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
- package/dist/templates/default/.agents/skills/real-time-collab/SKILL.md +185 -37
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +12 -2
- package/dist/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +185 -37
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +12 -2
- package/docs/content/plan-plugin.md +21 -6
- package/docs/content/pr-visual-recap.md +52 -3
- package/docs/content/real-time-collaboration.md +481 -97
- package/docs/content/skills-guide.md +13 -0
- package/docs/content/template-plan.md +18 -7
- package/package.json +5 -1
- package/src/templates/default/.agents/skills/real-time-collab/SKILL.md +185 -37
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +12 -2
- package/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +185 -37
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +12 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: real-time-collab
|
|
3
3
|
description: >-
|
|
4
|
-
Multi-user collaborative editing with Yjs CRDT
|
|
5
|
-
adding real-time collaborative editing to
|
|
6
|
-
or understanding how the agent and humans
|
|
4
|
+
Multi-user collaborative editing with Yjs CRDT, SSE fast-path transport, and
|
|
5
|
+
granular server-side merge. Use when adding real-time collaborative editing to
|
|
6
|
+
a template, debugging sync issues, or understanding how the agent and humans
|
|
7
|
+
edit documents simultaneously.
|
|
7
8
|
metadata:
|
|
8
9
|
internal: true
|
|
9
10
|
---
|
|
@@ -12,39 +13,57 @@ metadata:
|
|
|
12
13
|
|
|
13
14
|
## Rule
|
|
14
15
|
|
|
15
|
-
Collaborative editing uses Yjs CRDT via TipTap. The agent and human users are
|
|
16
|
+
Collaborative editing uses Yjs CRDT via TipTap. The agent and human users are
|
|
17
|
+
equal participants — both edit the same Y.Doc and changes merge cleanly without
|
|
18
|
+
conflicts. Always set `resourceType` on `createCollabPlugin`.
|
|
16
19
|
|
|
17
20
|
## How It Works
|
|
18
21
|
|
|
19
22
|
- **`Y.Doc`** stores the document as a `Y.XmlFragment` (ProseMirror node tree)
|
|
20
|
-
- **TipTap's Collaboration extension** binds the editor to the Y.XmlFragment
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
|
|
23
|
+
- **TipTap's Collaboration extension** binds the editor to the Y.XmlFragment
|
|
24
|
+
via `ySyncPlugin`
|
|
25
|
+
- **CollaborationCaret extension** renders remote users' cursors with names and
|
|
26
|
+
colors
|
|
27
|
+
- **SSE fast-path** — `/_agent-native/poll-events` `EventSource` delivers collab
|
|
28
|
+
events push-style; while SSE is healthy the collab poll interval relaxes to
|
|
29
|
+
~12 s
|
|
30
|
+
- **Polling fallback** — `/_agent-native/poll` is polled every 2 s when SSE is
|
|
31
|
+
unavailable; this is the universal serverless fallback
|
|
32
|
+
- **Update batching** — local Yjs updates are debounced ~80 ms and coalesced
|
|
33
|
+
with `Y.mergeUpdates` before sending; flushed immediately on
|
|
34
|
+
`visibilitychange` / `pagehide`
|
|
35
|
+
- **SQL `_collab_docs` table** persists Yjs state as base64 (SQLite/Postgres
|
|
36
|
+
compatible). Tombstone compaction fires automatically when the stored blob
|
|
37
|
+
exceeds 4× the fresh encoded size.
|
|
24
38
|
|
|
25
39
|
## Agent + Human Editing
|
|
26
40
|
|
|
27
41
|
1. **Human edits** → TipTap → ySyncPlugin → Y.XmlFragment → `POST /_agent-native/collab/:docId/update`
|
|
28
42
|
2. **Agent edits** → action edits canonical SQL content + bumps `updatedAt` → change-sync refetch → the open editor reconciles the new content into the live Y.Doc (see below) → poll update → all clients
|
|
29
43
|
|
|
30
|
-
Both produce Yjs operations that merge cleanly. Agent edits appear without
|
|
44
|
+
Both produce Yjs operations that merge cleanly. Agent edits appear without
|
|
45
|
+
destroying cursor position, selection, or undo history.
|
|
31
46
|
|
|
32
|
-
|
|
47
|
+
The agent does **not** push edits into Yjs in-process and does **not** call any
|
|
48
|
+
localhost probe — those approaches silently no-op on serverless (the action runs
|
|
49
|
+
in a different process). The peer-editor model below replaced them.
|
|
33
50
|
|
|
34
51
|
## Agent Edits As A Real-Time Peer Editor
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
**SQL is the durable source of truth for document body content.** The agent
|
|
54
|
+
action edits the canonical content column and bumps `updatedAt`. No localhost
|
|
55
|
+
calls, no in-process Yjs mutation.
|
|
37
56
|
|
|
38
|
-
**
|
|
39
|
-
|
|
40
|
-
|
|
57
|
+
**The open editor reconciles authoritative external content into the live
|
|
58
|
+
Y.Doc.** The `updatedAt` bump flows through change-sync, which refetches the
|
|
59
|
+
record. The lead client applies the new content via `setContent`, producing Yjs
|
|
60
|
+
operations that merge with concurrent human edits. Every connected client
|
|
61
|
+
receives the result through normal Yjs sync.
|
|
41
62
|
|
|
42
63
|
### The `updatedAt` gate
|
|
43
64
|
|
|
44
|
-
The editor only adopts content that is genuinely **newer** than what it already reflects. An older-or-equal `updatedAt` is a lagging poll or a stale snapshot and is **ignored**.
|
|
45
|
-
|
|
46
65
|
```ts
|
|
47
|
-
//
|
|
66
|
+
// In the editor's reconcile effect
|
|
48
67
|
if (loaded.updatedAt > lastAppliedUpdatedAt.current) {
|
|
49
68
|
applyAuthoritativeContent(loaded.content); // adopt
|
|
50
69
|
lastAppliedUpdatedAt.current = loaded.updatedAt;
|
|
@@ -52,56 +71,107 @@ if (loaded.updatedAt > lastAppliedUpdatedAt.current) {
|
|
|
52
71
|
// else: lagging poll / stale snapshot → ignore
|
|
53
72
|
```
|
|
54
73
|
|
|
55
|
-
|
|
74
|
+
Without the gate, a slightly-behind poll response re-applies old content and
|
|
75
|
+
the edit "reverts on next poll". A fresh mount always adopts whatever content
|
|
76
|
+
it loaded.
|
|
56
77
|
|
|
57
78
|
### Lead-client election
|
|
58
79
|
|
|
59
|
-
Exactly ONE connected client applies
|
|
80
|
+
Exactly ONE connected client applies the authoritative snapshot; the rest
|
|
81
|
+
receive it through Yjs sync:
|
|
60
82
|
|
|
61
83
|
```ts
|
|
62
84
|
import { isReconcileLeadClient } from "@agent-native/core/client";
|
|
63
85
|
|
|
64
86
|
if (
|
|
65
87
|
loaded.updatedAt > lastAppliedUpdatedAt.current &&
|
|
66
|
-
isReconcileLeadClient(
|
|
88
|
+
isReconcileLeadClient(awareness, ydoc.clientID)
|
|
67
89
|
) {
|
|
68
90
|
applyAuthoritativeContent(loaded.content);
|
|
69
91
|
}
|
|
70
92
|
```
|
|
71
93
|
|
|
72
|
-
|
|
94
|
+
The agent's awareness entry (`AGENT_CLIENT_ID`, max int) can never be the
|
|
95
|
+
lead. A sole client is always the lead. The election is deterministic with no
|
|
96
|
+
coordination round-trip.
|
|
73
97
|
|
|
74
98
|
### v1 limitation
|
|
75
99
|
|
|
76
|
-
|
|
100
|
+
Full-content reconcile is **last-writer-wins** for the rare case where a human
|
|
101
|
+
has unsaved edits in the exact region the agent simultaneously rewrites. Edits
|
|
102
|
+
in **different** regions merge fine through the CRDT.
|
|
103
|
+
|
|
104
|
+
## Security
|
|
105
|
+
|
|
106
|
+
### Always set `resourceType`
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// server/plugins/collab.ts
|
|
110
|
+
import { createCollabPlugin } from "@agent-native/core/server";
|
|
111
|
+
|
|
112
|
+
export default createCollabPlugin({
|
|
113
|
+
table: "documents",
|
|
114
|
+
contentColumn: "content",
|
|
115
|
+
idColumn: "id",
|
|
116
|
+
resourceType: "document", // required
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Without `resourceType`, the server logs a one-time warning and collab push
|
|
121
|
+
events are delivered to **all authenticated users** without document-level
|
|
122
|
+
scoping. Set it to the resource type name registered via
|
|
123
|
+
`registerShareableResource`.
|
|
124
|
+
|
|
125
|
+
Non-owner sharees who have explicit access fall back to state-vector catch-up
|
|
126
|
+
(safe, slightly higher latency). Awareness routes require the same viewer
|
|
127
|
+
access as read routes.
|
|
128
|
+
|
|
129
|
+
### Payload limits
|
|
130
|
+
|
|
131
|
+
Write routes reject payloads exceeding `maxPayloadBytes` (default 2 MB) with
|
|
132
|
+
HTTP 413. Override:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
createCollabPlugin({ resourceType: "document", maxPayloadBytes: 512 * 1024 });
|
|
136
|
+
```
|
|
77
137
|
|
|
78
138
|
## Enabling Collaboration
|
|
79
139
|
|
|
80
140
|
### 1. Install packages
|
|
81
141
|
|
|
82
142
|
```bash
|
|
83
|
-
pnpm add @tiptap/extension-collaboration @tiptap/extension-collaboration-caret @tiptap/y-tiptap
|
|
143
|
+
pnpm add @tiptap/extension-collaboration @tiptap/extension-collaboration-caret @tiptap/y-tiptap @tiptap/core
|
|
84
144
|
```
|
|
85
145
|
|
|
86
|
-
### 2. Add collab server plugin
|
|
146
|
+
### 2. Add collab server plugin (with `resourceType`)
|
|
87
147
|
|
|
88
148
|
```ts
|
|
89
149
|
// server/plugins/collab.ts
|
|
90
|
-
import { createCollabPlugin } from "@agent-native/core/
|
|
150
|
+
import { createCollabPlugin } from "@agent-native/core/server";
|
|
91
151
|
|
|
92
152
|
export default createCollabPlugin({
|
|
93
153
|
table: "documents",
|
|
94
154
|
contentColumn: "content",
|
|
95
155
|
idColumn: "id",
|
|
156
|
+
resourceType: "document",
|
|
96
157
|
});
|
|
97
158
|
```
|
|
98
159
|
|
|
99
160
|
### 3. Use the client hook
|
|
100
161
|
|
|
101
162
|
```ts
|
|
102
|
-
import { useCollaborativeDoc } from "@agent-native/core/client";
|
|
103
|
-
|
|
104
|
-
const { ydoc,
|
|
163
|
+
import { useCollaborativeDoc, emailToColor, emailToName } from "@agent-native/core/client";
|
|
164
|
+
|
|
165
|
+
const { ydoc, awareness, activeUsers, agentActive, agentPresent } =
|
|
166
|
+
useCollaborativeDoc({
|
|
167
|
+
docId: documentId,
|
|
168
|
+
requestSource: TAB_ID,
|
|
169
|
+
user: {
|
|
170
|
+
name: emailToName(session.email),
|
|
171
|
+
email: session.email,
|
|
172
|
+
color: emailToColor(session.email),
|
|
173
|
+
},
|
|
174
|
+
});
|
|
105
175
|
```
|
|
106
176
|
|
|
107
177
|
### 4. Add TipTap extensions
|
|
@@ -112,12 +182,14 @@ import { CollaborationCaret } from "@tiptap/extension-collaboration-caret";
|
|
|
112
182
|
|
|
113
183
|
const editor = useEditor({
|
|
114
184
|
extensions: [
|
|
185
|
+
StarterKit.configure({ history: false }), // Yjs handles undo
|
|
115
186
|
Collaboration.configure({ document: ydoc }),
|
|
116
187
|
CollaborationCaret.configure({
|
|
117
|
-
provider,
|
|
188
|
+
provider: { awareness },
|
|
118
189
|
user: { name: session.email, color: "#6366f1" },
|
|
119
190
|
}),
|
|
120
191
|
],
|
|
192
|
+
// Do NOT pass content — Yjs owns it
|
|
121
193
|
});
|
|
122
194
|
```
|
|
123
195
|
|
|
@@ -126,6 +198,9 @@ const editor = useEditor({
|
|
|
126
198
|
```ts
|
|
127
199
|
optimizeDeps: {
|
|
128
200
|
include: [
|
|
201
|
+
"yjs",
|
|
202
|
+
"y-protocols/awareness",
|
|
203
|
+
"@tiptap/core",
|
|
129
204
|
"@tiptap/extension-collaboration",
|
|
130
205
|
"@tiptap/extension-collaboration-caret",
|
|
131
206
|
"@tiptap/y-tiptap",
|
|
@@ -137,22 +212,95 @@ optimizeDeps: {
|
|
|
137
212
|
|
|
138
213
|
| Route | Purpose |
|
|
139
214
|
| ----- | ------- |
|
|
140
|
-
| `GET /_agent-native/collab/:docId/state` | Fetch full Y.Doc state |
|
|
215
|
+
| `GET /_agent-native/collab/:docId/state` | Fetch full Y.Doc state (accepts `?stateVector=` for diff) |
|
|
141
216
|
| `POST /_agent-native/collab/:docId/update` | Apply client Yjs update |
|
|
142
217
|
| `POST /_agent-native/collab/:docId/text` | Apply full text (diff-based) |
|
|
143
218
|
| `POST /_agent-native/collab/:docId/search-replace` | Surgical find/replace in Y.XmlFragment |
|
|
219
|
+
| `POST /_agent-native/collab/:docId/json` | Apply full JSON diff to Y.Map/Y.Array |
|
|
220
|
+
| `GET /_agent-native/collab/:docId/json` | Read current JSON state |
|
|
221
|
+
| `POST /_agent-native/collab/:docId/patch` | Surgical JSON patch ops |
|
|
144
222
|
| `POST /_agent-native/collab/:docId/awareness` | Sync cursor/presence state |
|
|
145
223
|
| `GET /_agent-native/collab/:docId/users` | List active users |
|
|
146
224
|
|
|
225
|
+
## Granular Server-Side Merge Pattern
|
|
226
|
+
|
|
227
|
+
For structured documents (slides, forms, design files) where body collab would
|
|
228
|
+
cause LWW conflicts at the container level, use **granular server-side merge**:
|
|
229
|
+
define an action with targeted per-item operations.
|
|
230
|
+
|
|
231
|
+
**When to use granular merge vs body collab:**
|
|
232
|
+
|
|
233
|
+
| Scenario | Recommended approach |
|
|
234
|
+
| -------- | -------------------- |
|
|
235
|
+
| Free-form rich text, cursor-level CRDT matters | Body collab (Y.XmlFragment + TipTap) |
|
|
236
|
+
| Structured items (slides, fields) where different users edit different items | Granular server-side merge (action with patch ops) |
|
|
237
|
+
|
|
238
|
+
Example operation shape for slides:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
type PatchDeckOp =
|
|
242
|
+
| { type: "patch"; slideId: string; fields: Partial<SlideFields> }
|
|
243
|
+
| { type: "add"; position: number; slide: SlideData }
|
|
244
|
+
| { type: "delete"; slideId: string }
|
|
245
|
+
| { type: "reorder"; slideId: string; newIndex: number };
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Concurrent edits to different slides both succeed at the action level; there
|
|
249
|
+
is no whole-deck LWW. Forms use the same shape with field-level ops.
|
|
250
|
+
|
|
251
|
+
## Collaborative Undo Scoping (Y.UndoManager)
|
|
252
|
+
|
|
253
|
+
Scope undo/redo to the local user's own edits so peer and agent changes are
|
|
254
|
+
never accidentally reversed:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import * as Y from "yjs";
|
|
258
|
+
|
|
259
|
+
const LOCAL_EDIT_ORIGIN = "local";
|
|
260
|
+
|
|
261
|
+
const undoManager = new Y.UndoManager(ydoc.getText("content"), {
|
|
262
|
+
trackedOrigins: new Set([LOCAL_EDIT_ORIGIN]),
|
|
263
|
+
captureTimeout: 800, // coalesces rapid slider drags into one undo step
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Mark local edits with the tracked origin
|
|
267
|
+
ydoc.transact(() => {
|
|
268
|
+
// apply local change
|
|
269
|
+
}, LOCAL_EDIT_ORIGIN);
|
|
270
|
+
|
|
271
|
+
undoManager.undo(); // only reverses LOCAL_EDIT_ORIGIN transactions
|
|
272
|
+
undoManager.redo();
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Rules:
|
|
276
|
+
- Pass a `Set` to `trackedOrigins` — not an array.
|
|
277
|
+
- Remote (`"remote"`) and agent (`"agent"`) origins are never captured.
|
|
278
|
+
- Recreate and destroy the manager when the active document changes.
|
|
279
|
+
|
|
147
280
|
## Common Pitfalls
|
|
148
281
|
|
|
149
|
-
- **
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
- **
|
|
282
|
+
- **Missing `resourceType`** — The server logs a warning on startup and
|
|
283
|
+
delivers collab events to all authenticated users without access scoping.
|
|
284
|
+
Always set `resourceType`.
|
|
285
|
+
- **Don't pass `content` as a TipTap prop** when Collaboration is enabled —
|
|
286
|
+
Yjs owns the content. Seed via `editor.commands.setContent()` only when the
|
|
287
|
+
Y.XmlFragment is empty.
|
|
288
|
+
- **Don't call `editor.setContent()` ad hoc for agent edits** — the only
|
|
289
|
+
sanctioned `setContent` is gated by `updatedAt` and guarded by
|
|
290
|
+
`isReconcileLeadClient`. Calling it from elsewhere duplicates content across
|
|
291
|
+
the CRDT or re-applies stale snapshots.
|
|
292
|
+
- **Add packages to `optimizeDeps`** — Vite won't pre-bundle Yjs correctly
|
|
293
|
+
otherwise, causing runtime errors in dev.
|
|
294
|
+
- **One `Y.Doc` per document** — Don't create multiple Y.Doc instances for the
|
|
295
|
+
same document ID. `useCollaborativeDoc` caches by ID.
|
|
296
|
+
- **Destroy Y.UndoManager on doc change** — Stale managers hold Y.Doc
|
|
297
|
+
references and grow unboundedly. Recreate on `docId` change.
|
|
153
298
|
|
|
154
299
|
## Related Skills
|
|
155
300
|
|
|
156
|
-
- `real-time-sync` — The change-sync system that delivers the `updatedAt` bump
|
|
157
|
-
|
|
158
|
-
- `
|
|
301
|
+
- `real-time-sync` — The change-sync system that delivers the `updatedAt` bump
|
|
302
|
+
driving editor reconciliation; also `useReconciledState` for non-Yjs surfaces
|
|
303
|
+
- `storing-data` — The `_collab_docs` table and SQL canonical content
|
|
304
|
+
- `security` — `registerShareableResource`, `resolveAccess`, `assertAccess`
|
|
305
|
+
- `self-modifying-code` — Agent edits to collaborative documents edit canonical
|
|
306
|
+
SQL content, not raw Yjs
|
|
@@ -49,7 +49,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
49
49
|
|
|
50
50
|
For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
|
|
51
51
|
|
|
52
|
-
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds. If SSE is disabled or unavailable, polling continues at the
|
|
52
|
+
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback — it detects DB timestamp changes even when the write happened in a different process or invocation.
|
|
53
53
|
|
|
54
54
|
5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
|
|
55
55
|
|
|
@@ -196,6 +196,16 @@ const [title, setTitle] = useReconciledState(props.title, { active: isEditing })
|
|
|
196
196
|
| Local edit state copied from a server value (inputs, popovers, inline editors) | `useReconciledState(externalValue, { active })` |
|
|
197
197
|
| Collaborative rich-text editor (Yjs) | `updatedAt`-gated reconcile + `isReconcileLeadClient` — see `real-time-collab` |
|
|
198
198
|
|
|
199
|
+
## Granular server-side merge for non-body fields
|
|
200
|
+
|
|
201
|
+
For structured documents (slide decks, form builders, design files) where the
|
|
202
|
+
Yjs body collab would cause LWW conflicts at the container level, pair the
|
|
203
|
+
change-sync `updatedAt` bump with a **granular server-side merge action** that
|
|
204
|
+
accepts targeted per-item operations (add/patch/delete/reorder). Concurrent
|
|
205
|
+
edits to different items both survive at the action level; the `collab` source
|
|
206
|
+
version bump then propagates the merged state to all open clients. See
|
|
207
|
+
`real-time-collab` for the pattern and examples.
|
|
208
|
+
|
|
199
209
|
## Related Skills
|
|
200
210
|
|
|
201
211
|
- **storing-data** — Application-state and settings are data stores that sync through change events
|
|
@@ -203,4 +213,4 @@ const [title, setTitle] = useReconciledState(props.title, { active: isEditing })
|
|
|
203
213
|
- **actions** — Mutating actions trigger change events
|
|
204
214
|
- **client-methods** — Route details belong in helpers/hooks, not components
|
|
205
215
|
- **self-modifying-code** — Agent code edits trigger change events; rapid edits can cause event storms
|
|
206
|
-
- **real-time-collab** — Collaborative editors reconcile agent edits into a shared Y.Doc, driven by the same change-sync `updatedAt` bump
|
|
216
|
+
- **real-time-collab** — Collaborative editors reconcile agent edits into a shared Y.Doc, driven by the same change-sync `updatedAt` bump; also the granular server-side merge pattern for structured data
|
|
@@ -12,7 +12,7 @@ The Agent-Native **Plan** app ships as one installable bundle. A single install
|
|
|
12
12
|
One install gives you:
|
|
13
13
|
|
|
14
14
|
- **Two skills** — `/visual-plan` (the canonical entry point) and `/visual-recap`.
|
|
15
|
-
- **The Plan MCP connector** — registered against the hosted app at `https://plan.agent-native.com` (MCP endpoint `https://plan.agent-native.com/_agent-native/mcp`, server name `agent-native-plans`).
|
|
15
|
+
- **The Plan MCP connector** — registered against the hosted app at `https://plan.agent-native.com` (MCP endpoint `https://plan.agent-native.com/_agent-native/mcp`, server name `plan`, with legacy alias `agent-native-plans` during migration).
|
|
16
16
|
|
|
17
17
|
By default, both skills publish to the hosted Plan app — they create a plan via
|
|
18
18
|
the MCP connector and hand you a link or inline plan to review. They never dump
|
|
@@ -49,14 +49,27 @@ npx @agent-native/core@latest skills add visual-plan
|
|
|
49
49
|
agent-native skills add visual-plan
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
This installs `visual-plan` plus the companion `visual-recap` skill, then registers the `agent-native-plans`
|
|
52
|
+
This installs `visual-plan` plus the companion `visual-recap` skill, then registers the `plan` connector and its legacy `agent-native-plans` alias, then runs auth (OAuth prompt for hosted/account-backed sharing). Useful flags:
|
|
53
53
|
|
|
54
54
|
- `--client codex|claude-code|claude-code-cli|cowork|all` — which local agents to write the MCP config for (default `codex`).
|
|
55
55
|
- `--no-connect` — register the connector without authenticating; run `agent-native connect https://plan.agent-native.com` later.
|
|
56
56
|
- `--mcp-url <url>` — point the connector at a custom origin (an ngrok tunnel, a local dev server, or a self-hosted deployment) instead of the hosted default.
|
|
57
57
|
- `--with-github-action` — also write the PR Visual Recap GitHub Action (see [PR Visual Recap](/docs/pr-visual-recap)).
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
Interactive installs also offer the PR Visual Recap Action when no workflow is
|
|
60
|
+
present. Say yes to add it during skill setup, or run the command above later
|
|
61
|
+
with `--with-github-action`. After the workflow is written, run:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
agent-native recap setup
|
|
65
|
+
agent-native recap doctor
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`recap setup` configures the GitHub Action secrets and variables where possible,
|
|
69
|
+
and `recap doctor` verifies the workflow, local publish token, GitHub repo
|
|
70
|
+
access, and required Actions configuration. After install finishes, restart or
|
|
71
|
+
reload the agent client so the new skills and tools load, then run
|
|
72
|
+
`/visual-plan`.
|
|
60
73
|
|
|
61
74
|
> Note: the bare `npx skills add BuilderIO/agent-native --skill visual-plan` (Vercel/open Skills CLI) installs **instructions only** — it does not register the MCP connector. Use the Agent-Native CLI above when you want the connector wired up too.
|
|
62
75
|
|
|
@@ -82,10 +95,12 @@ The same repo is a Codex plugin marketplace. Add it, install the plugin, then au
|
|
|
82
95
|
```bash
|
|
83
96
|
codex plugin marketplace add BuilderIO/agent-native
|
|
84
97
|
codex plugin add agent-native-visual-plans@agent-native-apps
|
|
85
|
-
codex mcp login
|
|
98
|
+
codex mcp login plan # OAuth in the browser
|
|
99
|
+
# Existing installs may already be authenticated as:
|
|
100
|
+
codex mcp login agent-native-plans
|
|
86
101
|
```
|
|
87
102
|
|
|
88
|
-
After install, **start a new Codex thread** so the skills and MCP tools load into the session. The plugin ships
|
|
103
|
+
After install, **start a new Codex thread** so the skills and MCP tools load into the session. The plugin ships URL-only connectors (`[mcp_servers.plan]` and legacy `[mcp_servers.agent-native-plans]` → `https://plan.agent-native.com/_agent-native/mcp`); `codex mcp login` runs the OAuth flow. The universal CLI route above also works for Codex (`agent-native skills add visual-plan --client codex`) if you prefer one command that installs and authenticates together.
|
|
89
104
|
|
|
90
105
|
## Updates {#updates}
|
|
91
106
|
|
|
@@ -93,7 +108,7 @@ The plugin routes auto-update — you do not re-pack or re-add the marketplace f
|
|
|
93
108
|
|
|
94
109
|
- **Claude Code** — the marketplace entry sets `autoUpdate: true` and the plugin uses commit-SHA versioning, so Claude Code pulls new versions from the repo at startup; run `/reload-plugins` to activate. Every push to the repo's default branch reaches installed users automatically.
|
|
95
110
|
- **Codex** — the plugin `version` embeds a content hash of the bundled skills and MCP endpoint (e.g. `1.0.0+codex.<hash>`), so any skill or endpoint change yields a new version. Codex's startup auto-upgrade re-installs configured git marketplaces on its own; just **start a new thread** to pick up the change. No manual `codex plugin marketplace upgrade` is needed for routine updates.
|
|
96
|
-
- **Universal CLI route** —
|
|
111
|
+
- **Universal CLI route** — run `npx @agent-native/core@latest skills status visual-plan` to check copied skill folders, or `npx @agent-native/core@latest skills update visual-plan` to refresh them in place. Re-running `skills add visual-plan` still works when you also want to re-register/authenticate the connector. `@latest` always pulls the current skills from the published `@agent-native/core` package.
|
|
97
112
|
|
|
98
113
|
The connector points at a **hosted** app, so the Plan app's actions and live tool surface always reflect the deployed version regardless of when you installed; only the bundled skill instructions follow the update mechanisms above.
|
|
99
114
|
|
|
@@ -28,13 +28,34 @@ A re-push updates the same plan and the same sticky comment in place — no orph
|
|
|
28
28
|
|
|
29
29
|
## Installing it
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
When you install Plans interactively, the Agent-Native CLI asks whether to add
|
|
32
|
+
automatic PR Visual Recaps. Say yes to write the GitHub Action, or add it
|
|
33
|
+
explicitly at any time:
|
|
32
34
|
|
|
33
35
|
```bash
|
|
34
36
|
agent-native skills add visual-plan --with-github-action
|
|
35
37
|
```
|
|
36
38
|
|
|
37
|
-
This installs the `visual-plan` skill (which includes the `visual-recap` skill the action runs) and writes `.github/workflows/pr-visual-recap.yml` into your repo. The workflow calls **published CLI subcommands** — `agent-native recap scan|build-prompt|shot|comment` — so nothing is copied into your repo as helper scripts.
|
|
39
|
+
This installs the `visual-plan` skill (which includes the `visual-recap` skill the action runs) and writes `.github/workflows/pr-visual-recap.yml` into your repo. The workflow calls **published CLI subcommands** — `agent-native recap scan|build-prompt|shot|comment` — so nothing is copied into your repo as helper scripts.
|
|
40
|
+
|
|
41
|
+
Then run the guided setup helper:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
agent-native recap setup
|
|
45
|
+
agent-native recap doctor
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`recap setup` refreshes the workflow, uses `gh` to set GitHub Actions
|
|
49
|
+
secrets/variables when values are available from env or the local Plans
|
|
50
|
+
publish-token store, and prints exact missing commands for anything it cannot
|
|
51
|
+
set. Secret values are sent to `gh` through stdin, not command arguments. Commit
|
|
52
|
+
the generated workflow file and open a PR to see it run.
|
|
53
|
+
|
|
54
|
+
By default, the workflow builds its agent prompt from the latest bundled
|
|
55
|
+
`visual-recap` guidance in `@agent-native/core@latest`, including any sibling
|
|
56
|
+
reference files the skill ships with. If your repo intentionally customizes and
|
|
57
|
+
pins its committed `visual-recap` folder, set the repository variable
|
|
58
|
+
`VISUAL_RECAP_SKILL_SOURCE=repo`.
|
|
38
59
|
|
|
39
60
|
## Backend selection
|
|
40
61
|
|
|
@@ -53,6 +74,7 @@ Beyond the backend, two repository variables tune _how_ the agent runs:
|
|
|
53
74
|
|
|
54
75
|
- **`VISUAL_RECAP_MODEL`** pins the model passed to the CLI (`--model`) — for example `gpt-5.5` for Codex, or a Claude model id. Leave it unset to use the CLI's own default model.
|
|
55
76
|
- **`VISUAL_RECAP_REASONING`** sets the reasoning depth: `none`, `minimal`, `low`, `medium`, `high`, or `xhigh`. It applies to the Codex backend; Claude's reasoning is model-driven, so this variable is ignored there.
|
|
77
|
+
- **`VISUAL_RECAP_SKILL_SOURCE`** controls prompt freshness: `auto`/unset uses the latest bundled skill guidance, while `repo` pins to the committed repo-local `visual-recap` skill folder.
|
|
56
78
|
|
|
57
79
|
For example, to run the recap on Codex with GPT-5.5 at high reasoning, set the repository variables `VISUAL_RECAP_AGENT=codex`, `VISUAL_RECAP_MODEL=gpt-5.5`, and `VISUAL_RECAP_REASONING=high`.
|
|
58
80
|
|
|
@@ -67,7 +89,18 @@ Set these in your repository's **Settings → Secrets and variables → Actions*
|
|
|
67
89
|
| `PLAN_RECAP_TOKEN` | Per-user, revocable token minted by `agent-native connect`. Authorizes publishing the recap plan and the screenshot upload. |
|
|
68
90
|
| `ANTHROPIC_API_KEY` | The LLM key for the default Claude Code backend. |
|
|
69
91
|
|
|
70
|
-
Mint `PLAN_RECAP_TOKEN` with `agent-native connect` against your Plans app
|
|
92
|
+
Mint `PLAN_RECAP_TOKEN` with `agent-native connect` against your Plans app. For
|
|
93
|
+
the hosted app, this also writes a local publish-token file that
|
|
94
|
+
`agent-native recap setup` can read:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
agent-native connect https://plan.agent-native.com --client codex
|
|
98
|
+
agent-native recap setup
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If you prefer manual setup, paste the token into the GitHub secret. Use a
|
|
102
|
+
placeholder like `plan_recap_xxxxxxxxxxxxxxxx` only for examples — never commit a
|
|
103
|
+
real token.
|
|
71
104
|
|
|
72
105
|
### Optional (only if you change defaults)
|
|
73
106
|
|
|
@@ -91,6 +124,22 @@ The workflow uses the plain `pull_request` trigger, **not** `pull_request_target
|
|
|
91
124
|
|
|
92
125
|
This also means you can merge the workflow file **before** the secrets exist: with no token configured, every run is a quiet no-op until you set the secrets.
|
|
93
126
|
|
|
127
|
+
## Self-modifying guard (sensitive paths)
|
|
128
|
+
|
|
129
|
+
The workflow's gate job skips the recap entirely if a PR touches any of the following paths, so a PR can never rewrite what the trusted recap job runs and exfiltrate secrets:
|
|
130
|
+
|
|
131
|
+
| Path pattern | Reason |
|
|
132
|
+
| ------------------------------------------ | --------------------------------------------------------- |
|
|
133
|
+
| `.github/workflows/pr-visual-recap.yml` | The workflow itself |
|
|
134
|
+
| `**/skills/visual-(recap\|plan\|plans)/**` | The visual-recap skill the agent follows |
|
|
135
|
+
| `**/.claude/**` | Agent settings the runner loads |
|
|
136
|
+
| `**/CLAUDE.md` | Agent instructions the runner loads |
|
|
137
|
+
| `**/AGENTS.md` | Agent instructions the runner loads |
|
|
138
|
+
| `**/.mcp.json` | MCP server config the runner loads |
|
|
139
|
+
| `packages/core/**` | Recap CLI source _(BuilderIO/agent-native monorepo only)_ |
|
|
140
|
+
|
|
141
|
+
The `packages/core/**` rule applies only in the `BuilderIO/agent-native` monorepo where `packages/core` is the recap CLI source. In consumer repos an unrelated `packages/core/` directory does not trigger the guard.
|
|
142
|
+
|
|
94
143
|
## Local-files privacy mode
|
|
95
144
|
|
|
96
145
|
The GitHub Action is designed for hosted, shareable PR review. If you want a
|