@prmichaelsen/acp-visualizer 0.1.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.
Files changed (180) hide show
  1. package/README.md +68 -0
  2. package/agent/commands/acp.clarification-address.md +417 -0
  3. package/agent/commands/acp.clarification-capture.md +386 -0
  4. package/agent/commands/acp.clarification-create.md +437 -0
  5. package/agent/commands/acp.clarifications-research.md +326 -0
  6. package/agent/commands/acp.command-create.md +432 -0
  7. package/agent/commands/acp.design-create.md +286 -0
  8. package/agent/commands/acp.design-reference.md +355 -0
  9. package/agent/commands/acp.handoff.md +270 -0
  10. package/agent/commands/acp.index.md +423 -0
  11. package/agent/commands/acp.init.md +546 -0
  12. package/agent/commands/acp.package-create.md +895 -0
  13. package/agent/commands/acp.package-info.md +212 -0
  14. package/agent/commands/acp.package-install.md +539 -0
  15. package/agent/commands/acp.package-list.md +280 -0
  16. package/agent/commands/acp.package-publish.md +541 -0
  17. package/agent/commands/acp.package-remove.md +293 -0
  18. package/agent/commands/acp.package-search.md +307 -0
  19. package/agent/commands/acp.package-update.md +361 -0
  20. package/agent/commands/acp.package-validate.md +540 -0
  21. package/agent/commands/acp.pattern-create.md +386 -0
  22. package/agent/commands/acp.plan.md +587 -0
  23. package/agent/commands/acp.proceed.md +882 -0
  24. package/agent/commands/acp.project-create.md +675 -0
  25. package/agent/commands/acp.project-info.md +312 -0
  26. package/agent/commands/acp.project-list.md +226 -0
  27. package/agent/commands/acp.project-remove.md +379 -0
  28. package/agent/commands/acp.project-set.md +227 -0
  29. package/agent/commands/acp.project-update.md +307 -0
  30. package/agent/commands/acp.projects-restore.md +228 -0
  31. package/agent/commands/acp.projects-sync.md +347 -0
  32. package/agent/commands/acp.report.md +407 -0
  33. package/agent/commands/acp.resume.md +239 -0
  34. package/agent/commands/acp.sessions.md +301 -0
  35. package/agent/commands/acp.status.md +293 -0
  36. package/agent/commands/acp.sync.md +364 -0
  37. package/agent/commands/acp.task-create.md +500 -0
  38. package/agent/commands/acp.update.md +302 -0
  39. package/agent/commands/acp.validate.md +466 -0
  40. package/agent/commands/acp.version-check-for-updates.md +276 -0
  41. package/agent/commands/acp.version-check.md +191 -0
  42. package/agent/commands/acp.version-update.md +289 -0
  43. package/agent/commands/command.template.md +339 -0
  44. package/agent/commands/git.commit.md +526 -0
  45. package/agent/commands/git.init.md +514 -0
  46. package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
  47. package/agent/commands/tanstack-cloudflare.tail.md +275 -0
  48. package/agent/design/.gitkeep +0 -0
  49. package/agent/design/design.template.md +154 -0
  50. package/agent/design/local.dashboard-layout-routing.md +288 -0
  51. package/agent/design/local.data-model-yaml-parsing.md +310 -0
  52. package/agent/design/local.search-filtering.md +331 -0
  53. package/agent/design/local.server-api-auto-refresh.md +235 -0
  54. package/agent/design/local.table-tree-views.md +299 -0
  55. package/agent/design/local.visualizer-requirements.md +349 -0
  56. package/agent/design/requirements.template.md +387 -0
  57. package/agent/index/.gitkeep +0 -0
  58. package/agent/index/acp.core.yaml +137 -0
  59. package/agent/index/local.main.template.yaml +37 -0
  60. package/agent/manifest.template.yaml +13 -0
  61. package/agent/manifest.yaml +302 -0
  62. package/agent/milestones/.gitkeep +0 -0
  63. package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
  64. package/agent/milestones/milestone-1-{title}.template.md +206 -0
  65. package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
  66. package/agent/package.template.yaml +86 -0
  67. package/agent/patterns/.gitkeep +0 -0
  68. package/agent/patterns/bootstrap.template.md +1237 -0
  69. package/agent/patterns/pattern.template.md +382 -0
  70. package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
  71. package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
  72. package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
  73. package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
  74. package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
  75. package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
  76. package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
  77. package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
  78. package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
  79. package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
  80. package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
  81. package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
  82. package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
  83. package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
  84. package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
  85. package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
  86. package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
  87. package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
  88. package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
  89. package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
  90. package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
  91. package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
  92. package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
  93. package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
  94. package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
  95. package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
  96. package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
  97. package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
  98. package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
  99. package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
  100. package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
  101. package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
  102. package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
  103. package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
  104. package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
  105. package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
  106. package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
  107. package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
  108. package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
  109. package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
  110. package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
  111. package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
  112. package/agent/progress.template.yaml +161 -0
  113. package/agent/progress.yaml +145 -0
  114. package/agent/schemas/package.schema.yaml +276 -0
  115. package/agent/scripts/acp.common.sh +1781 -0
  116. package/agent/scripts/acp.install.sh +333 -0
  117. package/agent/scripts/acp.package-create.sh +924 -0
  118. package/agent/scripts/acp.package-info.sh +288 -0
  119. package/agent/scripts/acp.package-install.sh +893 -0
  120. package/agent/scripts/acp.package-list.sh +311 -0
  121. package/agent/scripts/acp.package-publish.sh +420 -0
  122. package/agent/scripts/acp.package-remove.sh +348 -0
  123. package/agent/scripts/acp.package-search.sh +156 -0
  124. package/agent/scripts/acp.package-update.sh +517 -0
  125. package/agent/scripts/acp.package-validate.sh +1018 -0
  126. package/agent/scripts/acp.uninstall.sh +85 -0
  127. package/agent/scripts/acp.version-check-for-updates.sh +98 -0
  128. package/agent/scripts/acp.version-check.sh +47 -0
  129. package/agent/scripts/acp.version-update.sh +176 -0
  130. package/agent/scripts/acp.yaml-parser.sh +985 -0
  131. package/agent/scripts/acp.yaml-validate.sh +205 -0
  132. package/agent/tasks/.gitkeep +0 -0
  133. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
  134. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
  135. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
  136. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
  137. package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
  138. package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
  139. package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
  140. package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
  141. package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
  142. package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
  143. package/agent/tasks/task-1-{title}.template.md +244 -0
  144. package/bin/visualize.mjs +84 -0
  145. package/package.json +48 -0
  146. package/src/components/ExtraFieldsBadge.tsx +15 -0
  147. package/src/components/FilterBar.tsx +33 -0
  148. package/src/components/Header.tsx +23 -0
  149. package/src/components/MilestoneTable.tsx +167 -0
  150. package/src/components/MilestoneTree.tsx +84 -0
  151. package/src/components/ProgressBar.tsx +20 -0
  152. package/src/components/SearchInput.tsx +22 -0
  153. package/src/components/Sidebar.tsx +54 -0
  154. package/src/components/StatusBadge.tsx +23 -0
  155. package/src/components/StatusDot.tsx +12 -0
  156. package/src/components/TaskList.tsx +36 -0
  157. package/src/components/ViewToggle.tsx +31 -0
  158. package/src/lib/config.ts +8 -0
  159. package/src/lib/file-watcher.ts +43 -0
  160. package/src/lib/search.ts +48 -0
  161. package/src/lib/types.ts +73 -0
  162. package/src/lib/useAutoRefresh.ts +31 -0
  163. package/src/lib/useCollapse.ts +31 -0
  164. package/src/lib/useFilteredData.ts +55 -0
  165. package/src/lib/yaml-loader-real.spec.ts +47 -0
  166. package/src/lib/yaml-loader.spec.ts +201 -0
  167. package/src/lib/yaml-loader.ts +265 -0
  168. package/src/routeTree.gen.ts +140 -0
  169. package/src/router.tsx +10 -0
  170. package/src/routes/__root.tsx +75 -0
  171. package/src/routes/api/watch.ts +29 -0
  172. package/src/routes/index.tsx +115 -0
  173. package/src/routes/milestones.tsx +50 -0
  174. package/src/routes/search.tsx +84 -0
  175. package/src/routes/tasks.tsx +63 -0
  176. package/src/services/progress-database.service.ts +46 -0
  177. package/src/styles.css +25 -0
  178. package/tsconfig.json +24 -0
  179. package/vite.config.ts +16 -0
  180. package/vitest.config.ts +27 -0
@@ -0,0 +1,353 @@
1
+ # Chat Engine
2
+
3
+ **Category**: Architecture
4
+ **Applicable To**: Adding AI chat bots to TanStack + Cloudflare applications with tool calling, streaming, MCP integration, and message persistence
5
+ **Status**: Stable
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ ChatEngine is a provider-agnostic chat orchestration layer that coordinates AI models, message storage, MCP tool servers, and vision processing through dependency-injected interfaces. It handles the complete message lifecycle: token budgeting, system prompt building with prompt injectors, MCP tool discovery and caching, multi-turn tool execution with persistence, streaming responses, message ACL, and conversation management. Designed to be extracted into a standalone package — clients of the `tanstack-cloudflare` ACP package can use this as a clear path to adding chat bots to their application.
12
+
13
+ ---
14
+
15
+ ## When to Use This Pattern
16
+
17
+ **Use this pattern when:**
18
+ - Adding an AI chat bot to a TanStack + Cloudflare application
19
+ - Building a multi-tool AI assistant with MCP server integration
20
+ - Implementing streaming chat with tool call persistence
21
+ - Creating a white-label chat experience with pluggable AI providers
22
+
23
+ **Don't use this pattern when:**
24
+ - Building a simple form-based AI query (use direct API call)
25
+ - The application doesn't need real-time streaming or tool calling
26
+
27
+ ---
28
+
29
+ ## Core Principles
30
+
31
+ 1. **Dependency Injection**: All external services (AI, storage, MCP, vision) are injected as interfaces — swap providers without changing orchestration logic
32
+ 2. **MCP Caching**: Server connections and tool definitions cached per-user with 24h TTL — avoids expensive RPC on every message
33
+ 3. **Tool Persistence**: Tool calls saved as intermediate messages (`is_tool_interaction: true`) so the AI sees prior tool output on continuation
34
+ 4. **Token Budgeting**: Heuristic estimation + optional preflight check via `countTokens` API prevents "prompt too long" errors
35
+ 5. **Fire-and-Forget Non-Critical Ops**: Analytics, usage tracking, and title generation don't block the response stream
36
+
37
+ ---
38
+
39
+ ## Implementation
40
+
41
+ ### Provider Interfaces
42
+
43
+ ```typescript
44
+ interface ChatEngineDependencies {
45
+ aiProvider: IAIProvider
46
+ storageProvider: IStorageProvider
47
+ mcpProvider: IMCPProvider
48
+ visionProvider: IVisionProvider
49
+ logger: ILogger
50
+ env?: Record<string, unknown>
51
+ }
52
+
53
+ interface IAIProvider {
54
+ streamChat(params: {
55
+ messages: ChatMessage[]
56
+ systemPrompt: string
57
+ tools: Tool[]
58
+ onMessage: (msg: ChatEngineMessage) => void
59
+ executeTool: (name: string, input: any, id?: string) => Promise<any>
60
+ signal?: AbortSignal
61
+ }): Promise<void>
62
+ }
63
+
64
+ interface IStorageProvider {
65
+ saveMessage(params): Promise<Message>
66
+ loadMessages(params): Promise<Message[]>
67
+ ensureConversation(params): Promise<string>
68
+ updateConversation(params): Promise<void>
69
+ addToolCall(params): Promise<string> // Persist tool invocation
70
+ updateToolCall(params): Promise<void> // Update with result
71
+ getToolCallsForMessages(params): Promise<Map<string, PersistedToolCall[]>>
72
+ }
73
+
74
+ interface IMCPProvider {
75
+ getAvailableServers(params): Promise<MCPServer[]>
76
+ connectToServers(params): Promise<MCPConnection[]>
77
+ getTools(connections): Promise<Tool[]> // Cached after first call
78
+ executeTool(params): Promise<any>
79
+ disconnect(connections): Promise<void>
80
+ }
81
+
82
+ interface IVisionProvider {
83
+ processImagesInMessage(params): Promise<MessageContent>
84
+ }
85
+ ```
86
+
87
+ ### Instantiation in ChatRoom DO
88
+
89
+ ```typescript
90
+ class ChatRoom extends DurableObject {
91
+ private chatEngine: ChatEngine
92
+ private mcpProvider: MCPProvider // Persisted for caching
93
+
94
+ constructor(state: DurableObjectState, env: Env) {
95
+ super(state, env)
96
+ this.mcpProvider = new MCPProvider()
97
+ this.chatEngine = new ChatEngine({
98
+ aiProvider: new AnthropicAIProvider(),
99
+ storageProvider: new FirebaseStorageProvider(),
100
+ mcpProvider: this.mcpProvider, // Reused across messages for caching
101
+ visionProvider: new GoogleVisionProvider(),
102
+ logger: chatLogger,
103
+ env: env as unknown as Record<string, unknown>,
104
+ })
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### Message Processing Pipeline
110
+
111
+ ```
112
+ processMessage(userId, conversationId, message, onMessage, signal)
113
+
114
+ 1. Token limit check → reject if subscription exceeded
115
+ 2. Ensure conversation exists
116
+ 3. Detect @agent mention → determine if agent should respond
117
+ 4. Resolve @username mentions → @uid:userId
118
+ 5. Assign message ACL (visible_to_user_ids)
119
+ 6. Save user message → emit user_message_saved
120
+ 7. Process images via vision provider
121
+ 8. Load message history (ACL-filtered)
122
+ 9. Get MCP servers + connect (cached 24h)
123
+ 10. Fetch tools (cached) + build system prompt (parallel)
124
+ 11. Apply tool filters from prompt injectors
125
+ 12. Format messages with timestamps + locations
126
+ 13. Token-based truncation (60K budget, oldest first)
127
+ 14. Preflight check if estimate > 180K (countTokens API)
128
+ 15. Stream AI response with tool execution
129
+ 16. Save assistant message + tool call records
130
+ 17. Generate conversation title (fire-and-forget, 2nd message)
131
+ 18. Update conversation metadata
132
+ ```
133
+
134
+ ### Tool Execution Flow
135
+
136
+ ```typescript
137
+ executeTool: async (toolName, toolInput, toolCallId) => {
138
+ // 1. Create persistent record (status: 'pending')
139
+ const id = await storageProvider.addToolCall({ toolName, status: 'pending', inputs: toolInput })
140
+
141
+ // 2. Execute (local tool registry or MCP provider)
142
+ const result = isLocalTool(toolName)
143
+ ? await executeLocalTool(toolName, userId, toolInput, env)
144
+ : await mcpProvider.executeTool({ toolName, toolInput, connections })
145
+
146
+ // 3. Update record (status: 'success', output: result)
147
+ storageProvider.updateToolCall({ id, status: 'success', output: result })
148
+
149
+ // 4. Track analytics (fire-and-forget)
150
+ AnalyticsService.trackServerEvent(userId, 'tool_executed', { tool: toolName })
151
+
152
+ return result // Returned to AI for next turn
153
+ }
154
+ ```
155
+
156
+ ### MCP Caching Strategy
157
+
158
+ ```typescript
159
+ class MCPProvider implements IMCPProvider {
160
+ private serverCache: MCPServer[] = []
161
+ private connectionCache: MCPConnection[] = []
162
+ private toolDefsCache: Tool[] = []
163
+ private toolMapCache: Map<string, MCPConnection> = new Map()
164
+ private cacheExpiry: number = 0
165
+ private cacheUserId: string = ''
166
+ private readonly CACHE_TTL = 24 * 60 * 60 * 1000 // 24 hours
167
+
168
+ // Cache invalidated when: user changes, TTL expires, or OAuth connect/disconnect
169
+ }
170
+ ```
171
+
172
+ ### Token Budgeting
173
+
174
+ ```typescript
175
+ // Heuristic estimation
176
+ const MESSAGE_TOKEN_BUDGET = 60_000 // ~120K after formatting
177
+ const PREFLIGHT_THRESHOLD = 180_000 // 90% of 200K API limit
178
+ const SAFE_TARGET = 170_000 // Leave room for output
179
+
180
+ // Estimation ratios
181
+ Text: ~4 chars/token
182
+ JSON: ~2.5 chars/token
183
+ Base64 img: ~1 token/300 bytes
184
+ Per-message: 50 token overhead
185
+
186
+ // TokenRatioService learns from actual token counts
187
+ // Adjusts future estimates based on preflight feedback
188
+ ```
189
+
190
+ ### System Prompt Building
191
+
192
+ System prompt assembled from:
193
+ 1. Anti-hallucination preamble (tool call requirements)
194
+ 2. Markdown formatting rules
195
+ 3. Web tool instructions
196
+ 4. **Prompt injectors** (modular, priority-ordered extensions):
197
+ - Ghost persona injector (priority 0.9, mutex: ghost)
198
+ - Agent memory injector (priority 0.7, mutex: memory-context)
199
+ - Space/group context injector
200
+ - Each returns: `{ content: string, toolFilters?: { allow?, deny? }[] }`
201
+ 5. Conversation type context (chat/DM/group behavior)
202
+
203
+ ### Message ACL
204
+
205
+ ```typescript
206
+ // Group/DM: @agent responses visible only to sender
207
+ MessageAclService.assignACL('group', hasAgentMention, userId)
208
+ // → { visible_to_user_ids: [userId], created_for_user_id: userId }
209
+
210
+ // History filtering: only load messages the user can see
211
+ MessageAclService.filterMessagesByACL(allMessages, userId)
212
+ ```
213
+
214
+ ### ChatEngineMessage Types (Streaming Events)
215
+
216
+ ```typescript
217
+ type ChatEngineMessage =
218
+ | { type: 'chunk'; content: string }
219
+ | { type: 'tool_call'; toolCall: ToolCall; persistedToolCallId?: string }
220
+ | { type: 'tool_result'; toolResult: ToolResult }
221
+ | { type: 'user_message_saved'; message: Message }
222
+ | { type: 'assistant_message_saved'; message: Message }
223
+ | { type: 'complete' }
224
+ | { type: 'cancelled' }
225
+ | { type: 'error'; error: string }
226
+ | { type: 'usage'; input_tokens: number; output_tokens: number }
227
+ | { type: 'token_limit_warning'; percentage: number }
228
+ | { type: 'progress_start' | 'progress_update' | 'progress_complete' | 'progress_error' }
229
+ | { type: 'status'; status: string }
230
+ | { type: 'debug'; message: string }
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Adding Chat to a New Application
236
+
237
+ ### Step 1: Implement Provider Interfaces
238
+
239
+ ```typescript
240
+ // Minimal: just AI + storage
241
+ const engine = new ChatEngine({
242
+ aiProvider: new AnthropicAIProvider(), // Or OpenAI, etc.
243
+ storageProvider: new MyDatabaseStorageProvider(), // Firestore, Postgres, etc.
244
+ mcpProvider: new NoOpMCPProvider(), // No tools initially
245
+ visionProvider: new NoOpVisionProvider(), // No images initially
246
+ logger: console,
247
+ })
248
+ ```
249
+
250
+ ### Step 2: Create a Durable Object Host
251
+
252
+ ```typescript
253
+ class MyChatRoom extends DurableObject {
254
+ private engine: ChatEngine
255
+
256
+ constructor(state, env) {
257
+ super(state, env)
258
+ this.engine = new ChatEngine({ /* providers */ })
259
+ }
260
+
261
+ async fetch(request: Request) {
262
+ // WebSocket upgrade → session management → message routing
263
+ }
264
+ }
265
+ ```
266
+
267
+ ### Step 3: Connect Client via WebSocket
268
+
269
+ ```typescript
270
+ const ws = new ChatWebSocket({
271
+ userId: user.uid,
272
+ conversationId: 'main',
273
+ onMessage: (msg) => {
274
+ switch (msg.type) {
275
+ case 'chunk': /* append streaming text */ break
276
+ case 'tool_call': /* show tool badge */ break
277
+ case 'complete': /* finalize message */ break
278
+ }
279
+ },
280
+ })
281
+ ws.connect()
282
+ ```
283
+
284
+ ### Step 4: Add Tools (Optional)
285
+
286
+ Register local tools or connect MCP servers:
287
+ ```typescript
288
+ // Local tools
289
+ registerLocalTool('my_search', { description: '...', input_schema: {...} }, handler)
290
+
291
+ // MCP servers
292
+ mcpProvider = new MCPProvider() // Auto-discovers user's connected servers
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Anti-Patterns
298
+
299
+ ### Creating New MCPProvider Per Message
300
+
301
+ ```typescript
302
+ // Bad: Loses tool/connection cache on every message
303
+ async handleMessage(data) {
304
+ const mcp = new MCPProvider() // New instance = no cache
305
+ await this.engine.processMessage({ ... })
306
+ }
307
+
308
+ // Good: Reuse instance across messages (persist in DO)
309
+ constructor() {
310
+ this.mcpProvider = new MCPProvider() // Cached connections/tools
311
+ this.engine = new ChatEngine({ mcpProvider: this.mcpProvider })
312
+ }
313
+ ```
314
+
315
+ ### Blocking on Non-Critical Operations
316
+
317
+ ```typescript
318
+ // Bad: Analytics blocks the response stream
319
+ await AnalyticsService.trackServerEvent(userId, 'tool_executed', {...})
320
+ onMessage({ type: 'complete' })
321
+
322
+ // Good: Fire-and-forget
323
+ AnalyticsService.trackServerEvent(userId, 'tool_executed', {...}).catch(() => {})
324
+ onMessage({ type: 'complete' })
325
+ ```
326
+
327
+ ---
328
+
329
+ ## Checklist
330
+
331
+ - [ ] All providers injected via constructor (no hard dependencies)
332
+ - [ ] MCPProvider instance persisted across messages for caching
333
+ - [ ] Tool calls persisted with `addToolCall`/`updateToolCall`
334
+ - [ ] Message history truncated before AI call (60K token budget)
335
+ - [ ] Preflight check runs if estimate > 180K tokens
336
+ - [ ] System prompt built with prompt injectors (priority-ordered)
337
+ - [ ] Message ACL applied on save and load
338
+ - [ ] Non-critical operations (analytics, titles) are fire-and-forget
339
+ - [ ] AbortSignal passed through for cancellation support
340
+
341
+ ---
342
+
343
+ ## Related Patterns
344
+
345
+ - **[WebSocket Manager](./tanstack-cloudflare.websocket-manager.md)**: Client-side WebSocket that connects to ChatRoom DO
346
+ - **[Firebase Firestore](./tanstack-cloudflare.firebase-firestore.md)**: IStorageProvider implementation
347
+ - **[Firebase Auth](./tanstack-cloudflare.firebase-auth.md)**: User auth for message ownership
348
+
349
+ ---
350
+
351
+ **Status**: Stable
352
+ **Last Updated**: 2026-03-14
353
+ **Contributors**: Community
@@ -0,0 +1,346 @@
1
+ # Confirmation Tokens Pattern
2
+
3
+ **Category**: Architecture
4
+ **Applicable To**: TanStack Start + Cloudflare Workers applications with AI-initiated mutations
5
+ **Status**: Stable
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ The Confirmation Token pattern provides a two-step execution flow for dangerous or irreversible operations, especially when initiated by AI tools. Instead of executing a mutation immediately, the system generates a preview of the action along with a single-use confirmation token. The user (or AI confirmation tool) must explicitly confirm the action by presenting the token, which is then consumed to execute the operation.
12
+
13
+ This pattern prevents accidental mutations, provides a human-in-the-loop checkpoint for AI operations, and ensures that the exact operation previewed is the one that executes (no TOCTOU race conditions).
14
+
15
+ ---
16
+
17
+ ## When to Use This Pattern
18
+
19
+ ✅ **Use this pattern when:**
20
+ - AI agents can trigger mutations (create, update, delete operations)
21
+ - Operations are irreversible or have significant side effects
22
+ - Want a human-in-the-loop confirmation step
23
+ - Need to prevent accidental execution of dangerous operations
24
+ - Building tools that the AI calls which modify user data
25
+
26
+ ❌ **Don't use this pattern when:**
27
+ - Operations are read-only (no mutations)
28
+ - The user is directly clicking a button (standard UI confirmation is sufficient)
29
+ - Operations are trivially reversible
30
+ - Low-risk operations where speed matters more than safety
31
+
32
+ ---
33
+
34
+ ## Core Principles
35
+
36
+ 1. **Two-Step Execution**: Preview + confirm, never direct execution
37
+ 2. **Single-Use Tokens**: Each token can only be consumed once — prevents replay
38
+ 3. **User-Scoped**: Tokens validate that the confirming user matches the initiating user
39
+ 4. **TTL Expiration**: Tokens expire after a configurable time (default: 5 minutes)
40
+ 5. **Tamper-Proof**: Token encodes the exact operation parameters — confirming executes exactly what was previewed
41
+ 6. **In-Memory Store**: Tokens stored in memory within the Durable Object session (no database overhead)
42
+
43
+ ---
44
+
45
+ ## Implementation
46
+
47
+ ### Step 1: Define the Token Service
48
+
49
+ ```typescript
50
+ // src/services/confirmation-token.service.ts
51
+ import { randomBytes } from 'node:crypto'
52
+
53
+ /**
54
+ * A pending action stored against a confirmation token.
55
+ * Encodes the exact operation parameters to prevent tampering.
56
+ */
57
+ export interface PendingAction {
58
+ type: 'create_group' | 'update_group' | 'generate_group_link' | 'delete_resource'
59
+ userId: string
60
+ params: Record<string, unknown>
61
+ summary: string
62
+ createdAt: number
63
+ }
64
+
65
+ const DEFAULT_TTL_MS = 5 * 60 * 1000 // 5 minutes
66
+
67
+ /**
68
+ * Module-level singleton — persists across calls within the same Durable Object session.
69
+ */
70
+ let _instance: ConfirmationTokenService | null = null
71
+ export function getConfirmationTokenService(): ConfirmationTokenService {
72
+ if (!_instance) _instance = new ConfirmationTokenService()
73
+ return _instance
74
+ }
75
+
76
+ export class ConfirmationTokenService {
77
+ private pending = new Map<string, PendingAction>()
78
+ private ttlMs: number
79
+
80
+ constructor(ttlMs: number = DEFAULT_TTL_MS) {
81
+ this.ttlMs = ttlMs
82
+ }
83
+
84
+ /**
85
+ * Generate a confirmation token for a pending action.
86
+ * Returns a 32-char hex string.
87
+ */
88
+ generateToken(action: PendingAction): string {
89
+ this.cleanup()
90
+ const token = randomBytes(16).toString('hex')
91
+ this.pending.set(token, action)
92
+ return token
93
+ }
94
+
95
+ /**
96
+ * Consume a confirmation token.
97
+ * Returns the pending action if valid, null otherwise.
98
+ * Tokens are single-use — consumed on retrieval.
99
+ * Validates userId matches the original initiator.
100
+ */
101
+ consumeToken(token: string, userId: string): PendingAction | null {
102
+ const action = this.pending.get(token)
103
+ if (!action) return null
104
+
105
+ // Always delete — single use
106
+ this.pending.delete(token)
107
+
108
+ // Check TTL
109
+ if (Date.now() - action.createdAt > this.ttlMs) return null
110
+
111
+ // Validate userId matches
112
+ if (action.userId !== userId) return null
113
+
114
+ return action
115
+ }
116
+
117
+ /**
118
+ * Lazy cleanup of expired tokens.
119
+ */
120
+ private cleanup(): void {
121
+ const now = Date.now()
122
+ for (const [token, action] of this.pending) {
123
+ if (now - action.createdAt > this.ttlMs) {
124
+ this.pending.delete(token)
125
+ }
126
+ }
127
+ }
128
+ }
129
+ ```
130
+
131
+ ### Step 2: Create a Mutating Tool (Preview + Token)
132
+
133
+ ```typescript
134
+ // src/lib/chat/tools/create-group.ts
135
+ import { getConfirmationTokenService } from '@/services/confirmation-token.service'
136
+
137
+ export const createGroupTool = {
138
+ name: 'create_group',
139
+ description: 'Create a new group conversation. Returns a preview and confirmation token.',
140
+ input_schema: {
141
+ type: 'object' as const,
142
+ properties: {
143
+ name: { type: 'string', description: 'Group name' },
144
+ description: { type: 'string', description: 'Group description' },
145
+ },
146
+ required: ['name'],
147
+ },
148
+
149
+ async execute(input: { name: string; description?: string }, userId: string) {
150
+ const tokenService = getConfirmationTokenService()
151
+
152
+ // Generate preview + token (don't execute yet)
153
+ const token = tokenService.generateToken({
154
+ type: 'create_group',
155
+ userId,
156
+ params: { name: input.name, description: input.description },
157
+ summary: `Create group "${input.name}"`,
158
+ createdAt: Date.now(),
159
+ })
160
+
161
+ return {
162
+ preview: {
163
+ action: 'create_group',
164
+ name: input.name,
165
+ description: input.description || '(none)',
166
+ },
167
+ confirmation_token: token,
168
+ message: `I'll create a group called "${input.name}". Please confirm to proceed.`,
169
+ }
170
+ },
171
+ }
172
+ ```
173
+
174
+ ### Step 3: Create a Confirm Tool (Execute with Token)
175
+
176
+ ```typescript
177
+ // src/lib/chat/tools/confirm.ts
178
+ import { getConfirmationTokenService } from '@/services/confirmation-token.service'
179
+ import { GroupConversationDatabaseService } from '@/services/group-conversation-database.service'
180
+
181
+ export const confirmTool = {
182
+ name: 'confirm_action',
183
+ description: 'Confirm and execute a previously previewed action using its confirmation token.',
184
+ input_schema: {
185
+ type: 'object' as const,
186
+ properties: {
187
+ confirmation_token: { type: 'string', description: 'The token from the preview step' },
188
+ },
189
+ required: ['confirmation_token'],
190
+ },
191
+
192
+ async execute(input: { confirmation_token: string }, userId: string) {
193
+ const tokenService = getConfirmationTokenService()
194
+ const action = tokenService.consumeToken(input.confirmation_token, userId)
195
+
196
+ if (!action) {
197
+ return {
198
+ success: false,
199
+ error: 'Invalid, expired, or already-used confirmation token.',
200
+ }
201
+ }
202
+
203
+ // Execute the confirmed action
204
+ switch (action.type) {
205
+ case 'create_group': {
206
+ const { name, description } = action.params as { name: string; description?: string }
207
+ const group = await GroupConversationDatabaseService.createGroupConversation(
208
+ userId,
209
+ { name, description }
210
+ )
211
+ return { success: true, group, message: `Group "${name}" created successfully.` }
212
+ }
213
+
214
+ case 'delete_resource': {
215
+ // ... handle deletion
216
+ }
217
+
218
+ default:
219
+ return { success: false, error: `Unknown action type: ${action.type}` }
220
+ }
221
+ },
222
+ }
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Flow Diagram
228
+
229
+ ```
230
+ User: "Create a group called Book Club"
231
+
232
+ AI calls create_group tool
233
+
234
+ Tool returns: { preview: {...}, confirmation_token: "abc123", message: "Please confirm" }
235
+
236
+ AI shows preview to user: "I'll create a group called Book Club. Please confirm."
237
+
238
+ User: "Yes, go ahead"
239
+
240
+ AI calls confirm_action tool with token "abc123"
241
+
242
+ Token consumed → action executed → group created
243
+
244
+ AI: "Done! Group 'Book Club' has been created."
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Security Properties
250
+
251
+ | Property | How It's Enforced |
252
+ |----------|-------------------|
253
+ | **Single-use** | Token deleted on consumption |
254
+ | **Time-limited** | TTL check (default 5 min) |
255
+ | **User-scoped** | userId verified on consumption |
256
+ | **Tamper-proof** | Action params encoded in token, not in confirm request |
257
+ | **No replay** | Consumed tokens return null |
258
+
259
+ ---
260
+
261
+ ## Benefits
262
+
263
+ ### 1. Human-in-the-Loop
264
+ AI shows a preview before executing, giving users a chance to cancel.
265
+
266
+ ### 2. Prevents Accidental Mutations
267
+ No direct execution path — always preview first.
268
+
269
+ ### 3. TOCTOU Safety
270
+ The exact operation previewed is what executes (params stored in token, not re-sent).
271
+
272
+ ### 4. Zero Database Overhead
273
+ In-memory token store within the Durable Object session — no database writes.
274
+
275
+ ---
276
+
277
+ ## Trade-offs
278
+
279
+ ### 1. In-Memory Volatility
280
+ **Downside**: If the Durable Object restarts, all pending tokens are lost.
281
+ **Mitigation**: 5-minute TTL means most tokens are consumed quickly. Users can retry.
282
+
283
+ ### 2. Extra Round Trip
284
+ **Downside**: Two tool calls instead of one for every mutation.
285
+ **Mitigation**: Only use for high-impact operations. Read-only tools execute directly.
286
+
287
+ ---
288
+
289
+ ## Anti-Patterns
290
+
291
+ ### ❌ Anti-Pattern 1: Executing Without Token
292
+
293
+ ```typescript
294
+ // ❌ BAD: Tool directly executes mutation
295
+ async execute(input, userId) {
296
+ const group = await service.createGroup(userId, input) // No confirmation!
297
+ return { group }
298
+ }
299
+
300
+ // ✅ GOOD: Tool returns preview + token
301
+ async execute(input, userId) {
302
+ const token = tokenService.generateToken({ type: 'create_group', userId, params: input })
303
+ return { preview: input, confirmation_token: token }
304
+ }
305
+ ```
306
+
307
+ ### ❌ Anti-Pattern 2: Re-Sending Params in Confirm
308
+
309
+ ```typescript
310
+ // ❌ BAD: Confirm re-sends params (can be tampered)
311
+ confirm_action({ token: 'abc', name: 'Evil Group' }) // Name changed!
312
+
313
+ // ✅ GOOD: Params come from the stored token
314
+ const action = tokenService.consumeToken(token, userId)
315
+ const { name } = action.params // Params from original preview
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Related Patterns
321
+
322
+ - **[ACL Permissions](./tanstack-cloudflare.acl-permissions.md)**: Permission checks happen before token generation
323
+ - **[Durable Objects WebSocket](./tanstack-cloudflare.durable-objects-websocket.md)**: Token service lives within DO session
324
+ - **[API Route Handlers](./tanstack-cloudflare.api-route-handlers.md)**: Can also be used in HTTP API flows
325
+
326
+ ---
327
+
328
+ ## Checklist for Implementation
329
+
330
+ - [ ] `ConfirmationTokenService` with `generateToken` and `consumeToken`
331
+ - [ ] Tokens are cryptographically random (32-char hex)
332
+ - [ ] Single-use: deleted on consumption
333
+ - [ ] TTL enforced (default 5 minutes)
334
+ - [ ] User ID validated on consumption
335
+ - [ ] Action params stored in token, not re-sent on confirm
336
+ - [ ] Mutating tools return preview + token
337
+ - [ ] Confirm tool consumes token and executes stored action
338
+ - [ ] Expired/invalid tokens return clear error messages
339
+ - [ ] Lazy cleanup of expired tokens on new generation
340
+
341
+ ---
342
+
343
+ **Status**: Stable - Proven pattern for AI-initiated mutations
344
+ **Recommendation**: Use for all mutating operations triggered by AI tools
345
+ **Last Updated**: 2026-02-28
346
+ **Contributors**: Patrick Michaelsen