@agent-native/core 0.92.2 → 0.92.3

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 (170) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +26 -0
  3. package/corpus/core/docs/content/locales/ar-SA/server.mdx +35 -0
  4. package/corpus/core/docs/content/locales/de-DE/server.mdx +35 -0
  5. package/corpus/core/docs/content/locales/es-ES/server.mdx +35 -0
  6. package/corpus/core/docs/content/locales/fr-FR/server.mdx +35 -0
  7. package/corpus/core/docs/content/locales/hi-IN/server.mdx +35 -0
  8. package/corpus/core/docs/content/locales/ja-JP/server.mdx +35 -0
  9. package/corpus/core/docs/content/locales/ko-KR/server.mdx +35 -0
  10. package/corpus/core/docs/content/locales/pt-BR/server.mdx +35 -0
  11. package/corpus/core/docs/content/locales/zh-CN/server.mdx +35 -0
  12. package/corpus/core/docs/content/locales/zh-TW/server.mdx +35 -0
  13. package/corpus/core/docs/content/server.mdx +35 -0
  14. package/corpus/core/package.json +1 -1
  15. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +14 -4
  16. package/corpus/core/src/agent/engine/anthropic-engine.ts +15 -3
  17. package/corpus/core/src/agent/engine/builder-engine.ts +30 -4
  18. package/corpus/core/src/agent/engine/registry.ts +63 -2
  19. package/corpus/core/src/agent/model-config.ts +0 -2
  20. package/corpus/core/src/agent/production-agent.ts +12 -1
  21. package/corpus/core/src/agent/run-manager.ts +57 -36
  22. package/corpus/core/src/agent/run-store.ts +81 -2
  23. package/corpus/core/src/agent/thread-data-builder.ts +33 -2
  24. package/corpus/core/src/client/AssistantChat.tsx +314 -96
  25. package/corpus/core/src/client/agent-chat-adapter.ts +17 -1
  26. package/corpus/core/src/client/chat/repo-helpers.ts +45 -0
  27. package/corpus/core/src/client/chat/tool-call-display.tsx +36 -27
  28. package/corpus/core/src/client/chat-model-groups.ts +1 -5
  29. package/corpus/core/src/client/composer/TiptapComposer.tsx +0 -2
  30. package/corpus/core/src/client/embed-auth.ts +20 -0
  31. package/corpus/core/src/client/settings/SettingsSection.tsx +66 -5
  32. package/corpus/core/src/client/sse-event-processor.ts +49 -17
  33. package/corpus/core/src/client/use-db-sync.ts +31 -9
  34. package/corpus/core/src/collab/agent-presence.ts +6 -1
  35. package/corpus/core/src/collab/awareness-store.ts +185 -0
  36. package/corpus/core/src/collab/awareness.ts +58 -2
  37. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +7 -8
  38. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +7 -18
  39. package/corpus/core/src/server/core-routes-plugin.ts +21 -2
  40. package/corpus/core/src/server/embed-session.ts +20 -4
  41. package/corpus/core/src/styles/agent-native.css +51 -0
  42. package/corpus/core/src/tracking/providers.ts +33 -2
  43. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +8 -1
  44. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +9 -1
  45. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorDetail.tsx +41 -11
  46. package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +44 -0
  47. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-action-menus-now-close-cleanly-before-opening-hist.md +6 -0
  48. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-email-reports-still-send-when-the-screenshot-captu.md +6 -0
  49. package/corpus/templates/analytics/changelog/2026-07-09-uptime-alerts-now-suppress-recovery-noise-during-flapping-an.md +6 -0
  50. package/corpus/templates/analytics/changelog/2026-07-09-uptime-check-history-now-shows-timing-diagnostics-so-slow-or.md +6 -0
  51. package/corpus/templates/analytics/changelog/2026-07-09-uptime-recovery-alerts-now-send-when-the-original-outage-not.md +6 -0
  52. package/corpus/templates/analytics/docs/uptime-monitoring.md +29 -16
  53. package/corpus/templates/analytics/netlify.toml +3 -0
  54. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +126 -0
  55. package/corpus/templates/analytics/server/db/schema-monitoring.ts +5 -0
  56. package/corpus/templates/analytics/server/jobs/uptime-monitors.ts +5 -1
  57. package/corpus/templates/analytics/server/lib/uptime-monitors.ts +305 -31
  58. package/corpus/templates/analytics/server/plugins/db.ts +15 -0
  59. package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +1 -1
  60. package/corpus/templates/analytics/server/routes/api/uptime-monitors/run.post.ts +57 -0
  61. package/corpus/templates/clips/actions/list-meetings.ts +12 -0
  62. package/corpus/templates/clips/changelog/2026-07-09-meeting-reminders-now-ignore-obvious-solo-personal-calendar-.md +6 -0
  63. package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +1 -0
  64. package/corpus/templates/clips/server/lib/calendar-event-classification.ts +79 -0
  65. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  66. package/dist/agent/engine/ai-sdk-engine.js +13 -4
  67. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  68. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  69. package/dist/agent/engine/anthropic-engine.js +14 -3
  70. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  71. package/dist/agent/engine/builder-engine.d.ts +1 -1
  72. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  73. package/dist/agent/engine/builder-engine.js +26 -4
  74. package/dist/agent/engine/builder-engine.js.map +1 -1
  75. package/dist/agent/engine/registry.d.ts.map +1 -1
  76. package/dist/agent/engine/registry.js +47 -2
  77. package/dist/agent/engine/registry.js.map +1 -1
  78. package/dist/agent/model-config.d.ts +2 -2
  79. package/dist/agent/model-config.d.ts.map +1 -1
  80. package/dist/agent/model-config.js +0 -2
  81. package/dist/agent/model-config.js.map +1 -1
  82. package/dist/agent/production-agent.d.ts.map +1 -1
  83. package/dist/agent/production-agent.js +12 -1
  84. package/dist/agent/production-agent.js.map +1 -1
  85. package/dist/agent/run-manager.d.ts.map +1 -1
  86. package/dist/agent/run-manager.js +52 -33
  87. package/dist/agent/run-manager.js.map +1 -1
  88. package/dist/agent/run-store.d.ts +24 -0
  89. package/dist/agent/run-store.d.ts.map +1 -1
  90. package/dist/agent/run-store.js +64 -2
  91. package/dist/agent/run-store.js.map +1 -1
  92. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  93. package/dist/agent/thread-data-builder.js +28 -2
  94. package/dist/agent/thread-data-builder.js.map +1 -1
  95. package/dist/client/AssistantChat.d.ts +6 -0
  96. package/dist/client/AssistantChat.d.ts.map +1 -1
  97. package/dist/client/AssistantChat.js +243 -36
  98. package/dist/client/AssistantChat.js.map +1 -1
  99. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  100. package/dist/client/agent-chat-adapter.js +15 -1
  101. package/dist/client/agent-chat-adapter.js.map +1 -1
  102. package/dist/client/chat/repo-helpers.d.ts.map +1 -1
  103. package/dist/client/chat/repo-helpers.js +43 -0
  104. package/dist/client/chat/repo-helpers.js.map +1 -1
  105. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  106. package/dist/client/chat/tool-call-display.js +4 -4
  107. package/dist/client/chat/tool-call-display.js.map +1 -1
  108. package/dist/client/chat-model-groups.d.ts.map +1 -1
  109. package/dist/client/chat-model-groups.js +1 -3
  110. package/dist/client/chat-model-groups.js.map +1 -1
  111. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  112. package/dist/client/composer/TiptapComposer.js +0 -2
  113. package/dist/client/composer/TiptapComposer.js.map +1 -1
  114. package/dist/client/embed-auth.d.ts.map +1 -1
  115. package/dist/client/embed-auth.js +14 -0
  116. package/dist/client/embed-auth.js.map +1 -1
  117. package/dist/client/settings/SettingsSection.d.ts.map +1 -1
  118. package/dist/client/settings/SettingsSection.js +32 -3
  119. package/dist/client/settings/SettingsSection.js.map +1 -1
  120. package/dist/client/sse-event-processor.d.ts.map +1 -1
  121. package/dist/client/sse-event-processor.js +44 -17
  122. package/dist/client/sse-event-processor.js.map +1 -1
  123. package/dist/client/use-db-sync.d.ts.map +1 -1
  124. package/dist/client/use-db-sync.js +29 -9
  125. package/dist/client/use-db-sync.js.map +1 -1
  126. package/dist/collab/agent-presence.d.ts.map +1 -1
  127. package/dist/collab/agent-presence.js +5 -2
  128. package/dist/collab/agent-presence.js.map +1 -1
  129. package/dist/collab/awareness-store.d.ts +1 -1
  130. package/dist/collab/awareness-store.d.ts.map +1 -1
  131. package/dist/collab/awareness-store.js +0 -0
  132. package/dist/collab/awareness-store.js.map +1 -1
  133. package/dist/collab/awareness.d.ts +2 -0
  134. package/dist/collab/awareness.d.ts.map +1 -1
  135. package/dist/collab/awareness.js +46 -2
  136. package/dist/collab/awareness.js.map +1 -1
  137. package/dist/collab/routes.d.ts +1 -1
  138. package/dist/notifications/routes.d.ts +1 -1
  139. package/dist/observability/routes.d.ts +5 -5
  140. package/dist/progress/routes.d.ts +1 -1
  141. package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
  142. package/dist/scripts/agent-engines/manage-agent-engine.js +6 -6
  143. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  144. package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
  145. package/dist/scripts/agent-engines/set-agent-engine.js +5 -13
  146. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  147. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  148. package/dist/server/core-routes-plugin.d.ts +5 -0
  149. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  150. package/dist/server/core-routes-plugin.js +10 -3
  151. package/dist/server/core-routes-plugin.js.map +1 -1
  152. package/dist/server/embed-session.d.ts.map +1 -1
  153. package/dist/server/embed-session.js +17 -4
  154. package/dist/server/embed-session.js.map +1 -1
  155. package/dist/styles/agent-native.css +51 -0
  156. package/dist/tracking/providers.d.ts.map +1 -1
  157. package/dist/tracking/providers.js +23 -5
  158. package/dist/tracking/providers.js.map +1 -1
  159. package/docs/content/locales/ar-SA/server.mdx +35 -0
  160. package/docs/content/locales/de-DE/server.mdx +35 -0
  161. package/docs/content/locales/es-ES/server.mdx +35 -0
  162. package/docs/content/locales/fr-FR/server.mdx +35 -0
  163. package/docs/content/locales/hi-IN/server.mdx +35 -0
  164. package/docs/content/locales/ja-JP/server.mdx +35 -0
  165. package/docs/content/locales/ko-KR/server.mdx +35 -0
  166. package/docs/content/locales/pt-BR/server.mdx +35 -0
  167. package/docs/content/locales/zh-CN/server.mdx +35 -0
  168. package/docs/content/locales/zh-TW/server.mdx +35 -0
  169. package/docs/content/server.mdx +35 -0
  170. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"use-db-sync.js","sourceRoot":"","sources":["../../src/client/use-db-sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nimport { ensureDemoModeFetchInterceptor } from \"../demo/fetch-interceptor.js\";\nimport { agentNativePath } from \"./api-path.js\";\nimport {\n ensureEmbedAuthFetchInterceptor,\n isEmbedAuthActive,\n} from \"./embed-auth.js\";\nimport { bumpChangeVersion } from \"./use-change-version.js\";\n\ninterface Query {\n queryKey: readonly unknown[];\n}\n\ninterface QueryClient {\n invalidateQueries(opts?: {\n queryKey?: string[];\n predicate?: (query: Query) => boolean;\n }): void;\n}\n\nconst POLL_ABORT_MIN_MS = 10_000;\nconst SSE_FALLBACK_INTERVAL_MS = 15_000;\nconst POLL_AUTH_FAILURE_COOLDOWN_MS = 60_000;\n/**\n * Max cadence for SSE/poll-driven query invalidation in `useDbSync`. Events\n * that arrive within this window of the first one in a burst are merged into\n * a single `invalidateForEvents` call instead of one call per event — see the\n * `queueInvalidateBatch` comment at the call site.\n */\nconst INVALIDATE_COALESCE_MS = 250;\n\nclass HttpStatusError extends Error {\n status: number;\n\n constructor(status: number) {\n super(\"HTTP \" + status);\n this.status = status;\n }\n}\n\nexport type SyncEvent = {\n version?: number;\n source?: string;\n type?: string;\n key?: string;\n requestSource?: string;\n [k: string]: unknown;\n};\n\ntype PollResponse = {\n version: number;\n events: SyncEvent[];\n};\n\n/** Callback delivered to each transport subscriber for every batch of events. */\ntype EventSubscriber = (\n events: SyncEvent[],\n version: number | undefined,\n) => void;\n\nfunction getPollAbortMs(interval: number): number {\n return Math.max(POLL_ABORT_MIN_MS, interval * 4);\n}\n\nfunction isDocumentHidden(): boolean {\n return (\n typeof document !== \"undefined\" && document.visibilityState === \"hidden\"\n );\n}\n\nfunction resolveSseUrl(sseUrl: string | false | undefined): string | false {\n if (sseUrl === false) return false;\n if (isEmbedAuthActive()) return false;\n return agentNativePath(sseUrl ?? \"/_agent-native/events\");\n}\n\nfunction normalizeEventPayload(payload: unknown): SyncEvent[] {\n if (!payload || typeof payload !== \"object\") return [];\n const record = payload as { type?: unknown; events?: unknown };\n if (record.type === \"batch\" && Array.isArray(record.events)) {\n return record.events.filter(\n (event): event is SyncEvent => !!event && typeof event === \"object\",\n );\n }\n if (Array.isArray(record.events)) {\n return record.events.filter(\n (event): event is SyncEvent => !!event && typeof event === \"object\",\n );\n }\n return [payload as SyncEvent];\n}\n\nfunction isAuthFailure(error: unknown): boolean {\n return (\n !!error &&\n typeof error === \"object\" &&\n \"status\" in error &&\n ((error as { status?: unknown }).status === 401 ||\n (error as { status?: unknown }).status === 403)\n );\n}\n\n/**\n * App-state keys that drive immediate UI navigation/interaction and must\n * never sit behind the invalidation coalesce window (see\n * `isInteractionCriticalSyncEvent`).\n */\nconst INTERACTION_CRITICAL_APP_STATE_KEYS = [\n \"navigate\",\n \"show-questions\",\n \"__set_url__\",\n];\n\n/**\n * True for sync events that drive immediate, agent-initiated UI navigation\n * or interaction rather than passive data invalidation — app-state writes in\n * general (they back `[\"app-state\"]` queries directly), and specifically the\n * `navigate` / `show-questions` / `__set_url__` app-state keys that\n * `invalidateForEvents` special-cases into their own query keys below.\n *\n * `useDbSync` batches ordinary invalidation-driving events (action/collab/db\n * change events) into one flush per `INVALIDATE_COALESCE_MS` so a chatty doc\n * doesn't refetch on every keystroke. That trade-off is wrong for these\n * events: agent-driven navigation, `set-url`, and guided-questions prompts\n * must land as close to instantly as possible, so any batch containing one\n * of these bypasses the coalesce window and flushes immediately instead.\n *\n * Exported as a small pure predicate so this classification is unit-testable\n * independent of the transport/timer plumbing around it.\n */\nexport function isInteractionCriticalSyncEvent(event: SyncEvent): boolean {\n return (\n event.source === \"app-state\" &&\n (event.key === \"*\" ||\n INTERACTION_CRITICAL_APP_STATE_KEYS.some(\n (key) =>\n event.key === key ||\n (typeof event.key === \"string\" && event.key.startsWith(`${key}:`)),\n ))\n );\n}\n\nasync function fetchPollJson<T>(\n pollUrl: string,\n since: number,\n interval: number,\n): Promise<T> {\n const controller =\n typeof AbortController === \"undefined\" ? null : new AbortController();\n const timeout = controller\n ? setTimeout(() => controller.abort(), getPollAbortMs(interval))\n : null;\n\n try {\n const res = await fetch(\n `${pollUrl}?since=${since}`,\n controller ? { signal: controller.signal } : undefined,\n );\n if (!res.ok) throw new HttpStatusError(res.status);\n // Await the json before the finally so a body-stream abort doesn't\n // produce a dangling promise that escapes as an unhandled rejection.\n return await res.json();\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Shared SSE + poll transport\n//\n// One SyncTransport per (pollUrl, sseUrl) pair is held in a module-level\n// registry. Both `useDbSync` and `useScreenRefreshKey` subscribe to it, so a\n// single browser tab opens exactly ONE SSE connection and ONE poll loop\n// regardless of how many hook instances are mounted.\n//\n// Lifecycle: the transport starts when the first subscriber joins and shuts\n// down when the last subscriber leaves. This makes it safe to SSR and to\n// mount/unmount hooks independently.\n// ---------------------------------------------------------------------------\n\ninterface TransportSubscription {\n onEvents: EventSubscriber;\n /**\n * Whether this subscriber wants the transport to pause when the tab is\n * hidden. The transport pauses only when ALL subscribers request it — any\n * subscriber with `pauseWhenHidden: false` keeps the connection alive.\n */\n pauseWhenHidden: boolean;\n /**\n * Requested poll interval in ms. The transport uses the minimum across all\n * subscribers so the most-frequent caller is satisfied.\n */\n interval: number;\n /** Requested fallback interval while SSE is connected. */\n fallbackInterval: number;\n /**\n * Optional: notified when the shared SSE connection opens or closes (also\n * fired once with the current state when the subscriber joins). Lets\n * subscribers with their own fallback loops (e.g. the collab doc poll)\n * relax their cadence while the push path is healthy.\n */\n onSseStateChange?: (connected: boolean) => void;\n}\n\nclass SyncTransport {\n private subscribers = new Map<symbol, TransportSubscription>();\n private versionRef = 0;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private stopped = false;\n private inFlight = false;\n private eventSource: EventSource | null = null;\n private sseConnected = false;\n private authFailureUntil = 0;\n private consecutiveFailures = 0;\n\n constructor(\n private readonly pollUrl: string,\n private readonly sseUrl: string | false,\n ) {}\n\n // -------------------------------------------------------------------------\n // Subscriber management\n // -------------------------------------------------------------------------\n\n add(id: symbol, sub: TransportSubscription): void {\n const wasEmpty = this.subscribers.size === 0;\n this.subscribers.set(id, sub);\n if (wasEmpty) {\n this.stopped = false;\n this.start();\n }\n sub.onSseStateChange?.(this.sseConnected);\n }\n\n remove(id: symbol): void {\n this.subscribers.delete(id);\n if (this.subscribers.size === 0) {\n this.teardown();\n } else {\n // Recalculate poll interval in case the leaving subscriber was the\n // fastest caller; reschedule with the updated cadence.\n this.reschedule();\n }\n }\n\n // -------------------------------------------------------------------------\n // Derived settings (aggregate over active subscribers)\n // -------------------------------------------------------------------------\n\n private get effectivePauseWhenHidden(): boolean {\n // Pause only if every subscriber has opted in.\n for (const sub of this.subscribers.values()) {\n if (!sub.pauseWhenHidden) return false;\n }\n return true;\n }\n\n private get effectiveInterval(): number {\n let min = Infinity;\n for (const sub of this.subscribers.values()) {\n if (sub.interval < min) min = sub.interval;\n }\n return isFinite(min) ? min : 2000;\n }\n\n private get effectiveFallbackInterval(): number {\n let min = Infinity;\n for (const sub of this.subscribers.values()) {\n if (sub.fallbackInterval < min) min = sub.fallbackInterval;\n }\n return isFinite(min) ? min : SSE_FALLBACK_INTERVAL_MS;\n }\n\n // -------------------------------------------------------------------------\n // Event fan-out\n // -------------------------------------------------------------------------\n\n private fan(events: SyncEvent[], version: number | undefined): void {\n for (const sub of this.subscribers.values()) {\n sub.onEvents(events, version);\n }\n }\n\n private setSseConnected(connected: boolean): void {\n if (this.sseConnected === connected) return;\n this.sseConnected = connected;\n for (const sub of this.subscribers.values()) {\n sub.onSseStateChange?.(connected);\n }\n }\n\n // -------------------------------------------------------------------------\n // SSE + poll loop (mirrors the original per-hook logic exactly)\n // -------------------------------------------------------------------------\n\n private authFailureDelayMs(): number {\n return Math.max(0, this.authFailureUntil - Date.now());\n }\n\n private schedulePoll(): void {\n if (this.stopped) return;\n if (this.effectivePauseWhenHidden && isDocumentHidden()) return;\n if (this.timer) clearTimeout(this.timer);\n const authDelay = this.authFailureDelayMs();\n if (authDelay > 0) {\n this.timer = setTimeout(() => {\n this.timer = null;\n void this.poll();\n }, authDelay);\n return;\n }\n const base = this.sseConnected\n ? this.effectiveFallbackInterval\n : this.effectiveInterval;\n // Exponential backoff while polls keep failing (500s during a deploy,\n // DNS blips, a struggling DB). Auth failures have their own cooldown\n // above; this covers everything else so a down server isn't hammered at\n // full cadence. Resets on the first successful poll.\n const delay =\n this.consecutiveFailures > 0\n ? Math.min(base * 2 ** Math.min(this.consecutiveFailures, 5), 30_000)\n : base;\n this.timer = setTimeout(() => {\n this.timer = null;\n void this.poll();\n }, delay);\n }\n\n private reschedule(): void {\n // Only need to act if a timer is already pending; next natural tick will\n // pick up the new effective interval otherwise.\n if (this.timer !== null) {\n clearTimeout(this.timer);\n this.timer = null;\n this.schedulePoll();\n }\n }\n\n private closeEvents(): void {\n if (!this.eventSource) return;\n this.eventSource.close();\n this.eventSource = null;\n this.setSseConnected(false);\n }\n\n private connectEvents(): void {\n if (\n this.stopped ||\n !this.sseUrl ||\n this.eventSource ||\n typeof EventSource === \"undefined\" ||\n (this.effectivePauseWhenHidden && isDocumentHidden())\n ) {\n return;\n }\n\n const source = new EventSource(this.sseUrl);\n this.eventSource = source;\n source.onopen = () => {\n this.setSseConnected(true);\n this.schedulePoll();\n };\n source.onerror = () => {\n this.setSseConnected(false);\n // When the browser gives up permanently (HTTP error → readyState\n // CLOSED), it won't auto-reconnect. Drop the ref so a later\n // connectEvents() (on focus/visibility) can establish a fresh stream;\n // otherwise the non-null closed `eventSource` blocks reconnection and\n // we'd be stuck on polling-only forever.\n if (source.readyState === EventSource.CLOSED) {\n this.eventSource = null;\n }\n this.schedulePoll();\n };\n source.onmessage = (message) => {\n try {\n const payload = JSON.parse(message.data);\n const events = normalizeEventPayload(payload);\n const version =\n typeof payload?.version === \"number\" ? payload.version : undefined;\n this.applyVersion(events, version);\n this.fan(events, version);\n } catch {\n // Ignore malformed SSE frames; polling is the safety net.\n }\n };\n }\n\n /**\n * Advance the transport's shared version cursor. Subscribers receive the\n * raw events and decide independently which ones are \"fresh\" relative to\n * their own cursor, but the transport-level cursor ensures the poll\n * `?since=` parameter always advances.\n */\n private applyVersion(events: SyncEvent[], version: number | undefined): void {\n let max = typeof version === \"number\" ? version : 0;\n for (const evt of events) {\n const v = typeof evt.version === \"number\" ? evt.version : 0;\n if (v > max) max = v;\n }\n if (max > this.versionRef) this.versionRef = max;\n }\n\n private async poll(): Promise<void> {\n if (this.stopped || this.inFlight) return;\n this.inFlight = true;\n try {\n const data = await fetchPollJson<PollResponse>(\n this.pollUrl,\n this.versionRef,\n this.effectiveInterval,\n );\n if (this.stopped) return;\n this.consecutiveFailures = 0;\n const events = data.events ?? [];\n this.applyVersion(events, data.version);\n this.fan(events, data.version);\n } catch (err) {\n if (this.stopped) return;\n this.consecutiveFailures++;\n if (isAuthFailure(err)) {\n this.authFailureUntil = Date.now() + POLL_AUTH_FAILURE_COOLDOWN_MS;\n this.closeEvents();\n }\n // Network error — retried on the next (backed-off) interval.\n } finally {\n this.inFlight = false;\n this.schedulePoll();\n }\n }\n\n private pollNow(): void {\n if (this.effectivePauseWhenHidden && isDocumentHidden()) return;\n if (this.authFailureDelayMs() > 0) {\n this.schedulePoll();\n return;\n }\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n this.connectEvents();\n void this.poll();\n }\n\n private handleVisibilityChange = (): void => {\n if (document.visibilityState === \"visible\") {\n this.connectEvents();\n this.pollNow();\n } else if (this.effectivePauseWhenHidden) {\n this.closeEvents();\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n }\n };\n\n private handleFocus = (): void => {\n this.pollNow();\n };\n\n private start(): void {\n // Universal demo-mode redaction for the UI. Idempotent + browser-only +\n // a no-op until demo mode is on. Lives here because every template root\n // already mounts useDbSync, so this needs zero per-template wiring.\n ensureEmbedAuthFetchInterceptor();\n ensureDemoModeFetchInterceptor();\n\n if (!this.effectivePauseWhenHidden || !isDocumentHidden()) {\n this.connectEvents();\n void this.poll();\n }\n window.addEventListener(\"focus\", this.handleFocus);\n document.addEventListener(\"visibilitychange\", this.handleVisibilityChange);\n }\n\n private teardown(): void {\n this.stopped = true;\n this.closeEvents();\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n window.removeEventListener(\"focus\", this.handleFocus);\n document.removeEventListener(\n \"visibilitychange\",\n this.handleVisibilityChange,\n );\n }\n}\n\n/**\n * Registry of active transports keyed by \"<pollUrl>\\0<sseUrl>\".\n * Module-level singleton: survives React render cycles, shared across all\n * hook instances in the same browser tab.\n */\nconst transportRegistry = new Map<string, SyncTransport>();\n\nfunction getOrCreateTransport(\n pollUrl: string,\n sseUrl: string | false,\n): SyncTransport {\n const key = `${pollUrl}\\0${String(sseUrl)}`;\n let transport = transportRegistry.get(key);\n if (!transport) {\n transport = new SyncTransport(pollUrl, sseUrl);\n transportRegistry.set(key, transport);\n }\n return transport;\n}\n\n/** Remove a transport from the registry once torn down (last subscriber left). */\nfunction releaseTransport(pollUrl: string, sseUrl: string | false): void {\n const key = `${pollUrl}\\0${String(sseUrl)}`;\n // Leave the entry in place: SSE/poll is already stopped inside the class;\n // the next subscriber will re-start it via `add()`. Clearing the map entry\n // prevents any dangling reference from the old SyncTransport instance.\n transportRegistry.delete(key);\n}\n\n// ---------------------------------------------------------------------------\n// Internal test helper — reset transport registry between tests.\n// ---------------------------------------------------------------------------\n/** @internal */\nexport function _resetSyncTransportRegistryForTests(): void {\n transportRegistry.clear();\n}\n\nexport interface SubscribeSyncEventsOptions {\n /** Receives every batch of change events (SSE push or poll). */\n onEvents: (events: SyncEvent[], version: number | undefined) => void;\n /** Notified when the shared SSE connection opens/closes (and once on join). */\n onSseStateChange?: (connected: boolean) => void;\n pollUrl?: string;\n sseUrl?: string | false;\n pauseWhenHidden?: boolean;\n /**\n * Poll cadence this subscriber requests from the SHARED poll loop. The\n * transport uses the minimum across subscribers, so the defaults here are\n * deliberately high: subscribing must not speed up the shared poll —\n * useDbSync (mounted by every template root) already sets the pace.\n */\n interval?: number;\n fallbackInterval?: number;\n}\n\n/**\n * Subscribe to the shared SSE + poll transport without the React Query\n * invalidation behavior of `useDbSync`. Use this instead of opening another\n * `EventSource` to `/_agent-native/events` — a browser tab should hold ONE\n * SSE connection no matter how many features listen to it (extra streams eat\n * the per-origin connection budget and starve data fetches, especially on\n * HTTP/1.1 dev servers).\n *\n * Returns an unsubscribe function. Safe to call only in browser contexts.\n */\nexport function subscribeSyncEvents(\n options: SubscribeSyncEventsOptions,\n): () => void {\n const pollUrl = agentNativePath(options.pollUrl ?? \"/_agent-native/poll\");\n const sseUrl = resolveSseUrl(options.sseUrl);\n const transport = getOrCreateTransport(pollUrl, sseUrl);\n const id = Symbol(\"subscribeSyncEvents\");\n transport.add(id, {\n onEvents: options.onEvents,\n onSseStateChange: options.onSseStateChange,\n pauseWhenHidden: options.pauseWhenHidden ?? true,\n interval: options.interval ?? 60_000,\n fallbackInterval: options.fallbackInterval ?? 60_000,\n });\n return () => {\n transport.remove(id);\n if (!transport[\"subscribers\"].size) {\n releaseTransport(pollUrl, sseUrl);\n }\n };\n}\n\n/**\n * Hook that listens to /_agent-native/events for DB change events and\n * invalidates react-query caches when changes are detected. Falls back to\n * /_agent-native/poll so cross-process/serverless writes still show up.\n *\n * Works in all deployment environments (serverless, edge, long-lived server).\n * SSE is the fast path; polling is the safety net.\n *\n * @param options.queryClient - The react-query QueryClient instance\n * @param options.queryKeys - **Deprecated and ignored.** The hook uses\n * framework-owned fixed prefixes plus per-source change counters instead of\n * caller-supplied key lists. Kept in the type signature for backward\n * compatibility — existing call sites that still pass this option keep\n * working but the value has no effect.\n * @param options.pollUrl - Poll endpoint URL. Default: \"/_agent-native/poll\"\n * @param options.sseUrl - SSE endpoint URL. Default: \"/_agent-native/events\".\n * Pass false to disable SSE and use polling only.\n * @param options.onEvent - Optional callback for each change event\n * @param options.interval - Poll interval in ms. Default: 2000\n * @param options.fallbackInterval - Poll interval while SSE is connected.\n * Default: 15000\n * @param options.pauseWhenHidden - Pause polling while the tab is hidden.\n * Default: true\n * @param options.ignoreSource - Skip events whose `requestSource` matches this\n * value. Use a per-tab ID so the UI ignores its own writes while still\n * picking up changes from other tabs, agents, and scripts.\n * @param options.actionInvalidatePredicate - Optional filter for the broad\n * compatibility invalidate triggered by `action` events. Use this to keep\n * expensive active queries on explicit-refresh semantics while still letting\n * normal source-versioned queries react through `useChangeVersion`.\n * @param options.suppressActionInvalidationFor - Action names whose sync events\n * should not invalidate all action queries. Use only for high-volume\n * background actions that perform their own narrow client invalidation.\n */\nexport function useDbSync(\n options: {\n queryClient?: QueryClient;\n queryKeys?: string[];\n pollUrl?: string;\n sseUrl?: string | false;\n /** @deprecated Use pollUrl instead */\n eventsUrl?: string;\n onEvent?: (data: any) => void;\n interval?: number;\n fallbackInterval?: number;\n pauseWhenHidden?: boolean;\n ignoreSource?: string;\n actionInvalidatePredicate?: (query: Query) => boolean;\n suppressActionInvalidationFor?: string[];\n } = {},\n): void {\n const {\n queryClient,\n pollUrl = agentNativePath(options.eventsUrl ?? \"/_agent-native/poll\"),\n sseUrl = resolveSseUrl(options.sseUrl),\n interval = 2000,\n fallbackInterval = Math.max(\n options.fallbackInterval ?? SSE_FALLBACK_INTERVAL_MS,\n interval,\n ),\n pauseWhenHidden = true,\n } = options;\n\n const onEventRef = useRef(options.onEvent);\n onEventRef.current = options.onEvent;\n\n const ignoreSourceRef = useRef(options.ignoreSource);\n ignoreSourceRef.current = options.ignoreSource;\n const actionInvalidatePredicateRef = useRef(\n options.actionInvalidatePredicate,\n );\n actionInvalidatePredicateRef.current = options.actionInvalidatePredicate;\n const suppressActionInvalidationForRef = useRef(\n options.suppressActionInvalidationFor,\n );\n suppressActionInvalidationForRef.current =\n options.suppressActionInvalidationFor;\n\n useEffect(() => {\n const id = Symbol(\"useDbSync\");\n // Per-subscriber version cursor: tracks which events have already been\n // processed by THIS subscriber so stale poll re-deliveries are ignored.\n let subscriberVersion = 0;\n\n // Coalesce bursts of SSE-driven invalidation into at most one flush per\n // INVALIDATE_COALESCE_MS. A chatty doc (many small agent edits, several\n // peers editing at once) can otherwise deliver a handful of `action`/\n // `collab` events within a few hundred ms, each independently calling\n // `queryClient.invalidateQueries` and firing `onEvent` — every one of\n // those touches whatever query subscribers are mounted (e.g. a\n // full-page editor) even though the end state only needs to be\n // refreshed once. Version bookkeeping stays synchronous (below) so\n // freshness filtering for the NEXT batch is unaffected by the delay.\n //\n // This coalesce window is wrong for interaction-critical events (agent\n // navigation, `set-url`, guided questions — see\n // `isInteractionCriticalSyncEvent`): those must reach the UI immediately,\n // not up to INVALIDATE_COALESCE_MS late. So a fresh batch containing one\n // of those flushes synchronously (queued + new events together,\n // canceling any pending timer) instead of joining the coalesce window.\n // Pure invalidation bursts with no interaction-critical members keep the\n // coalesced behavior.\n let pendingInvalidateEvents: SyncEvent[] = [];\n let invalidateTimer: ReturnType<typeof setTimeout> | null = null;\n\n function flushInvalidateBatch() {\n if (invalidateTimer) {\n clearTimeout(invalidateTimer);\n invalidateTimer = null;\n }\n if (pendingInvalidateEvents.length === 0) return;\n const batch = pendingInvalidateEvents;\n pendingInvalidateEvents = [];\n invalidateForEvents(batch);\n }\n\n function queueInvalidateBatch(events: SyncEvent[]) {\n pendingInvalidateEvents.push(...events);\n if (events.some(isInteractionCriticalSyncEvent)) {\n flushInvalidateBatch();\n return;\n }\n if (invalidateTimer) return;\n invalidateTimer = setTimeout(\n flushInvalidateBatch,\n INVALIDATE_COALESCE_MS,\n );\n }\n\n function hasAppStateEvent(events: SyncEvent[], key: string): boolean {\n return events.some(\n (event) =>\n event.source === \"app-state\" &&\n (event.key === key ||\n event.key === \"*\" ||\n (typeof event.key === \"string\" && event.key.startsWith(`${key}:`))),\n );\n }\n\n function invalidateForEvents(events: SyncEvent[]) {\n const ignore = ignoreSourceRef.current;\n const relevant = ignore\n ? events.filter((e) => e.requestSource !== ignore)\n : events;\n const suppressedActions = new Set(\n suppressActionInvalidationForRef.current ?? [],\n );\n const isSuppressedActionEvent = (evt: SyncEvent) =>\n evt.source === \"action\" &&\n typeof evt.key === \"string\" &&\n suppressedActions.has(evt.key);\n const nonAwareness = relevant.filter((e) => e.source !== \"awareness\");\n const suppressesWholeBatch =\n nonAwareness.length > 0 &&\n nonAwareness.every((evt) => evt.source === \"action\") &&\n nonAwareness.every(isSuppressedActionEvent);\n\n // Bump per-source change counters. Components that read these via\n // `useChangeVersion(source)` and fold the value into a React Query\n // queryKey get a targeted refetch — no whole-cache invalidate, no\n // request storm. See `use-change-version.ts` for the contract.\n for (const evt of relevant) {\n const src = typeof evt.source === \"string\" ? evt.source : \"\";\n const ver = typeof evt.version === \"number\" ? evt.version : 0;\n if (src && ver > 0) bumpChangeVersion(src, ver);\n }\n\n // Awareness (cursor/presence) events never change action/extension/\n // app-state query results, but they arrive on every peer keystroke and\n // carry no version (so the freshness filter always passes them). Keep\n // them out of the invalidate block or an idle collaborative doc turns\n // every peer's cursor move into a framework-wide refetch storm; they\n // still reach onEvent below for callers that render presence.\n const invalidating = relevant.filter((e) => e.source !== \"awareness\");\n\n if (invalidating.length > 0 && queryClient) {\n const hasActionEvent = invalidating.some(\n (evt) => evt.source === \"action\" && !isSuppressedActionEvent(evt),\n );\n if (hasActionEvent) {\n // Custom apps frequently start with raw `useQuery` calls before\n // graduating to `useActionQuery` or source-versioned query keys.\n // A successful mutating action is the core \"agent changed app data\"\n // signal, so refresh active queries broadly as a compatibility\n // safety net. Other event sources stay targeted to avoid request\n // storms from noisy domain-specific writes.\n const predicate = actionInvalidatePredicateRef.current;\n queryClient.invalidateQueries(predicate ? { predicate } : undefined);\n }\n\n // Framework-level invalidate: a small, fixed list of query-key\n // prefixes the framework's own hooks/components use (action results,\n // extension state, application-state, the agent's `set-url` channel,\n // etc.). Templates' own data queries do NOT live here — they react\n // through `useChangeVersion(source)` in their query keys instead, so\n // a single change event doesn't fan out into \"refetch everything\".\n // Suppressed-action-only batches skip this whole list (their\n // mutations perform their own narrow invalidation) — but events must\n // STILL reach the onEvent forwarding below, so guard, don't return.\n if (!suppressesWholeBatch) {\n queryClient.invalidateQueries({ queryKey: [\"action\"] });\n queryClient.invalidateQueries({ queryKey: [\"extension\"] });\n queryClient.invalidateQueries({ queryKey: [\"extensions\"] });\n queryClient.invalidateQueries({ queryKey: [\"extension-slots\"] });\n queryClient.invalidateQueries({ queryKey: [\"slot-installs\"] });\n queryClient.invalidateQueries({ queryKey: [\"slot-available\"] });\n queryClient.invalidateQueries({ queryKey: [\"tool\"] });\n queryClient.invalidateQueries({ queryKey: [\"tools\"] });\n queryClient.invalidateQueries({ queryKey: [\"app-state\"] });\n if (hasAppStateEvent(invalidating, \"navigate\")) {\n queryClient.invalidateQueries({ queryKey: [\"navigate-command\"] });\n }\n if (hasAppStateEvent(invalidating, \"show-questions\")) {\n queryClient.invalidateQueries({ queryKey: [\"show-questions\"] });\n }\n if (hasAppStateEvent(invalidating, \"__set_url__\")) {\n queryClient.invalidateQueries({ queryKey: [\"__set_url__\"] });\n }\n }\n }\n\n // Always forward all events to onEvent — templates can layer surgical\n // logic on top (e.g. ignore their own writes via requestSource, or\n // invalidate inactive queries for a specific source).\n for (const evt of events) {\n onEventRef.current?.(evt);\n }\n }\n\n function onEvents(events: SyncEvent[], version: number | undefined): void {\n const freshEvents = events.filter((event) => {\n const v = typeof event.version === \"number\" ? event.version : 0;\n return v === 0 || v > subscriberVersion;\n });\n\n if (freshEvents.length > 0) {\n queueInvalidateBatch(freshEvents);\n }\n\n const maxEventVersion = freshEvents.reduce(\n (max, event) =>\n Math.max(max, typeof event.version === \"number\" ? event.version : 0),\n 0,\n );\n subscriberVersion = Math.max(\n subscriberVersion,\n version ?? 0,\n maxEventVersion,\n );\n }\n\n const transport = getOrCreateTransport(pollUrl, sseUrl);\n transport.add(id, {\n onEvents,\n pauseWhenHidden,\n interval,\n fallbackInterval,\n });\n\n return () => {\n if (invalidateTimer) {\n clearTimeout(invalidateTimer);\n // Flush synchronously on unmount so a pending batch isn't silently\n // dropped (e.g. a route change right after an agent edit lands).\n flushInvalidateBatch();\n }\n transport.remove(id);\n // If the registry still holds this transport, and the transport is now\n // empty, evict it so the next mount gets a fresh instance rather than a\n // stopped-but-still-registered one (the registry entry being cleared by\n // releaseTransport is the signal to rebuild state).\n if (!transport[\"subscribers\"].size) {\n releaseTransport(pollUrl, sseUrl);\n }\n };\n }, [\n pollUrl,\n sseUrl,\n queryClient,\n interval,\n fallbackInterval,\n pauseWhenHidden,\n ]);\n}\n\n/** @deprecated Use useDbSync instead */\nexport const useFileWatcher = useDbSync;\n\n/**\n * Subscribe to `refresh-screen` events from the agent. Returns an integer\n * that increments every time the agent invokes the framework's `refresh-screen`\n * tool. Apply it as a React `key` on the main content wrapper (the part\n * OUTSIDE the agent chat sidebar) so that region remounts and re-fetches its\n * data while the chat, sidebar, and any other persistent chrome keep their\n * in-flight state.\n *\n * Usage in a template's root:\n *\n * const screenKey = useScreenRefreshKey();\n * return (\n * <AppLayout>\n * <div key={screenKey}>\n * <Outlet />\n * </div>\n * </AppLayout>\n * );\n */\nexport function useScreenRefreshKey(\n options: {\n pollUrl?: string;\n sseUrl?: string | false;\n interval?: number;\n fallbackInterval?: number;\n pauseWhenHidden?: boolean;\n } = {},\n): number {\n const {\n pollUrl = agentNativePath(options.pollUrl ?? \"/_agent-native/poll\"),\n sseUrl = resolveSseUrl(options.sseUrl),\n interval = 2000,\n fallbackInterval = Math.max(\n options.fallbackInterval ?? SSE_FALLBACK_INTERVAL_MS,\n interval,\n ),\n pauseWhenHidden = true,\n } = options;\n const [key, setKey] = useState(0);\n\n useEffect(() => {\n const id = Symbol(\"useScreenRefreshKey\");\n // Per-subscriber version cursor (same freshness logic as useDbSync).\n let subscriberVersion = 0;\n\n function onEvents(events: SyncEvent[], version: number | undefined): void {\n const freshEvents = events.filter((event) => {\n const v = typeof event.version === \"number\" ? event.version : 0;\n return v === 0 || v > subscriberVersion;\n });\n if (freshEvents.some((e) => e.source === \"screen-refresh\")) {\n setKey((k) => k + 1);\n }\n const maxEventVersion = freshEvents.reduce(\n (max, event) =>\n Math.max(max, typeof event.version === \"number\" ? event.version : 0),\n 0,\n );\n subscriberVersion = Math.max(\n subscriberVersion,\n version ?? 0,\n maxEventVersion,\n );\n }\n\n const transport = getOrCreateTransport(pollUrl, sseUrl);\n transport.add(id, {\n onEvents,\n pauseWhenHidden,\n interval,\n fallbackInterval,\n });\n\n return () => {\n transport.remove(id);\n if (!transport[\"subscribers\"].size) {\n releaseTransport(pollUrl, sseUrl);\n }\n };\n }, [pollUrl, sseUrl, interval, fallbackInterval, pauseWhenHidden]);\n\n return key;\n}\n"]}
1
+ {"version":3,"file":"use-db-sync.js","sourceRoot":"","sources":["../../src/client/use-db-sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nimport { ensureDemoModeFetchInterceptor } from \"../demo/fetch-interceptor.js\";\nimport { agentNativePath } from \"./api-path.js\";\nimport {\n ensureEmbedAuthFetchInterceptor,\n isEmbedAuthActive,\n} from \"./embed-auth.js\";\nimport { bumpChangeVersion } from \"./use-change-version.js\";\n\ninterface Query {\n queryKey: readonly unknown[];\n}\n\ninterface QueryClient {\n invalidateQueries(opts?: {\n queryKey?: string[];\n predicate?: (query: Query) => boolean;\n }): void;\n}\n\nconst POLL_ABORT_MIN_MS = 10_000;\nconst SSE_FALLBACK_INTERVAL_MS = 15_000;\nconst POLL_AUTH_FAILURE_COOLDOWN_MS = 60_000;\n/**\n * Max cadence for SSE/poll-driven query invalidation in `useDbSync`. Events\n * that arrive within this window of the first one in a burst are merged into\n * a single `invalidateForEvents` call instead of one call per event — see the\n * `queueInvalidateBatch` comment at the call site.\n */\nconst INVALIDATE_COALESCE_MS = 250;\n\nclass HttpStatusError extends Error {\n status: number;\n\n constructor(status: number) {\n super(\"HTTP \" + status);\n this.status = status;\n }\n}\n\nexport type SyncEvent = {\n version?: number;\n source?: string;\n type?: string;\n key?: string;\n requestSource?: string;\n [k: string]: unknown;\n};\n\ntype PollResponse = {\n version: number;\n events: SyncEvent[];\n};\n\n/** Callback delivered to each transport subscriber for every batch of events. */\ntype EventSubscriber = (\n events: SyncEvent[],\n version: number | undefined,\n) => void;\n\nfunction getPollAbortMs(interval: number): number {\n return Math.max(POLL_ABORT_MIN_MS, interval * 4);\n}\n\nfunction isDocumentHidden(): boolean {\n return (\n typeof document !== \"undefined\" && document.visibilityState === \"hidden\"\n );\n}\n\nfunction resolveSseUrl(sseUrl: string | false | undefined): string | false {\n if (sseUrl === false) return false;\n if (isEmbedAuthActive()) return false;\n return agentNativePath(sseUrl ?? \"/_agent-native/events\");\n}\n\nfunction normalizeEventPayload(payload: unknown): SyncEvent[] {\n if (!payload || typeof payload !== \"object\") return [];\n const record = payload as { type?: unknown; events?: unknown };\n if (record.type === \"batch\" && Array.isArray(record.events)) {\n return record.events.filter(\n (event): event is SyncEvent => !!event && typeof event === \"object\",\n );\n }\n if (Array.isArray(record.events)) {\n return record.events.filter(\n (event): event is SyncEvent => !!event && typeof event === \"object\",\n );\n }\n return [payload as SyncEvent];\n}\n\nfunction isAuthFailure(error: unknown): boolean {\n return (\n !!error &&\n typeof error === \"object\" &&\n \"status\" in error &&\n ((error as { status?: unknown }).status === 401 ||\n (error as { status?: unknown }).status === 403)\n );\n}\n\n/**\n * App-state keys that drive immediate UI navigation/interaction and must\n * never sit behind the invalidation coalesce window (see\n * `isInteractionCriticalSyncEvent`).\n */\nconst INTERACTION_CRITICAL_APP_STATE_KEYS = [\n \"navigate\",\n \"show-questions\",\n \"__set_url__\",\n];\n\n/**\n * True for sync events that drive immediate, agent-initiated UI navigation\n * or interaction rather than passive data invalidation — app-state writes in\n * general (they back `[\"app-state\"]` queries directly), and specifically the\n * `navigate` / `show-questions` / `__set_url__` app-state keys that\n * `invalidateForEvents` special-cases into their own query keys below.\n *\n * `useDbSync` batches ordinary invalidation-driving events (action/collab/db\n * change events) into one flush per `INVALIDATE_COALESCE_MS` so a chatty doc\n * doesn't refetch on every keystroke. That trade-off is wrong for these\n * events: agent-driven navigation, `set-url`, and guided-questions prompts\n * must land as close to instantly as possible, so any batch containing one\n * of these bypasses the coalesce window and flushes immediately instead.\n *\n * Exported as a small pure predicate so this classification is unit-testable\n * independent of the transport/timer plumbing around it.\n */\nexport function isInteractionCriticalSyncEvent(event: SyncEvent): boolean {\n return (\n event.source === \"app-state\" &&\n (event.key === \"*\" ||\n INTERACTION_CRITICAL_APP_STATE_KEYS.some(\n (key) =>\n event.key === key ||\n (typeof event.key === \"string\" && event.key.startsWith(`${key}:`)),\n ))\n );\n}\n\nasync function fetchPollJson<T>(\n pollUrl: string,\n since: number,\n interval: number,\n): Promise<T> {\n const controller =\n typeof AbortController === \"undefined\" ? null : new AbortController();\n const timeout = controller\n ? setTimeout(() => controller.abort(), getPollAbortMs(interval))\n : null;\n\n try {\n const res = await fetch(\n `${pollUrl}?since=${since}`,\n controller ? { signal: controller.signal } : undefined,\n );\n if (!res.ok) throw new HttpStatusError(res.status);\n // Await the json before the finally so a body-stream abort doesn't\n // produce a dangling promise that escapes as an unhandled rejection.\n return await res.json();\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Shared SSE + poll transport\n//\n// One SyncTransport per (pollUrl, sseUrl) pair is held in a module-level\n// registry. Both `useDbSync` and `useScreenRefreshKey` subscribe to it, so a\n// single browser tab opens exactly ONE SSE connection and ONE poll loop\n// regardless of how many hook instances are mounted.\n//\n// Lifecycle: the transport starts when the first subscriber joins and shuts\n// down when the last subscriber leaves. This makes it safe to SSR and to\n// mount/unmount hooks independently.\n// ---------------------------------------------------------------------------\n\ninterface TransportSubscription {\n onEvents: EventSubscriber;\n /**\n * Whether this subscriber wants the transport to pause when the tab is\n * hidden. The transport pauses only when ALL subscribers request it — any\n * subscriber with `pauseWhenHidden: false` keeps the connection alive.\n */\n pauseWhenHidden: boolean;\n /**\n * Requested poll interval in ms. The transport uses the minimum across all\n * subscribers so the most-frequent caller is satisfied.\n */\n interval: number;\n /** Requested fallback interval while SSE is connected. */\n fallbackInterval: number;\n /**\n * Optional: notified when the shared SSE connection opens or closes (also\n * fired once with the current state when the subscriber joins). Lets\n * subscribers with their own fallback loops (e.g. the collab doc poll)\n * relax their cadence while the push path is healthy.\n */\n onSseStateChange?: (connected: boolean) => void;\n}\n\nclass SyncTransport {\n private subscribers = new Map<symbol, TransportSubscription>();\n private versionRef = 0;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private stopped = false;\n private inFlight = false;\n private eventSource: EventSource | null = null;\n private sseConnected = false;\n private authFailureUntil = 0;\n private consecutiveFailures = 0;\n\n constructor(\n private readonly pollUrl: string,\n private readonly sseUrl: string | false,\n ) {}\n\n // -------------------------------------------------------------------------\n // Subscriber management\n // -------------------------------------------------------------------------\n\n add(id: symbol, sub: TransportSubscription): void {\n const wasEmpty = this.subscribers.size === 0;\n this.subscribers.set(id, sub);\n if (wasEmpty) {\n this.stopped = false;\n this.start();\n }\n sub.onSseStateChange?.(this.sseConnected);\n }\n\n remove(id: symbol): void {\n this.subscribers.delete(id);\n if (this.subscribers.size === 0) {\n this.teardown();\n } else {\n // Recalculate poll interval in case the leaving subscriber was the\n // fastest caller; reschedule with the updated cadence.\n this.reschedule();\n }\n }\n\n // -------------------------------------------------------------------------\n // Derived settings (aggregate over active subscribers)\n // -------------------------------------------------------------------------\n\n private get effectivePauseWhenHidden(): boolean {\n // Pause only if every subscriber has opted in.\n for (const sub of this.subscribers.values()) {\n if (!sub.pauseWhenHidden) return false;\n }\n return true;\n }\n\n private get effectiveInterval(): number {\n let min = Infinity;\n for (const sub of this.subscribers.values()) {\n if (sub.interval < min) min = sub.interval;\n }\n return isFinite(min) ? min : 2000;\n }\n\n private get effectiveFallbackInterval(): number {\n let min = Infinity;\n for (const sub of this.subscribers.values()) {\n if (sub.fallbackInterval < min) min = sub.fallbackInterval;\n }\n return isFinite(min) ? min : SSE_FALLBACK_INTERVAL_MS;\n }\n\n // -------------------------------------------------------------------------\n // Event fan-out\n // -------------------------------------------------------------------------\n\n private fan(events: SyncEvent[], version: number | undefined): void {\n for (const sub of this.subscribers.values()) {\n sub.onEvents(events, version);\n }\n }\n\n private setSseConnected(connected: boolean): void {\n if (this.sseConnected === connected) return;\n this.sseConnected = connected;\n for (const sub of this.subscribers.values()) {\n sub.onSseStateChange?.(connected);\n }\n }\n\n // -------------------------------------------------------------------------\n // SSE + poll loop (mirrors the original per-hook logic exactly)\n // -------------------------------------------------------------------------\n\n private authFailureDelayMs(): number {\n return Math.max(0, this.authFailureUntil - Date.now());\n }\n\n private schedulePoll(): void {\n if (this.stopped) return;\n if (this.effectivePauseWhenHidden && isDocumentHidden()) return;\n if (this.timer) clearTimeout(this.timer);\n const authDelay = this.authFailureDelayMs();\n if (authDelay > 0) {\n this.timer = setTimeout(() => {\n this.timer = null;\n void this.poll();\n }, authDelay);\n return;\n }\n const base = this.sseConnected\n ? this.effectiveFallbackInterval\n : this.effectiveInterval;\n // Exponential backoff while polls keep failing (500s during a deploy,\n // DNS blips, a struggling DB). Auth failures have their own cooldown\n // above; this covers everything else so a down server isn't hammered at\n // full cadence. Resets on the first successful poll.\n const delay =\n this.consecutiveFailures > 0\n ? Math.min(base * 2 ** Math.min(this.consecutiveFailures, 5), 30_000)\n : base;\n this.timer = setTimeout(() => {\n this.timer = null;\n void this.poll();\n }, delay);\n }\n\n private reschedule(): void {\n // Only need to act if a timer is already pending; next natural tick will\n // pick up the new effective interval otherwise.\n if (this.timer !== null) {\n clearTimeout(this.timer);\n this.timer = null;\n this.schedulePoll();\n }\n }\n\n private closeEvents(): void {\n if (!this.eventSource) return;\n this.eventSource.close();\n this.eventSource = null;\n this.setSseConnected(false);\n }\n\n private connectEvents(): void {\n if (\n this.stopped ||\n !this.sseUrl ||\n this.eventSource ||\n typeof EventSource === \"undefined\" ||\n (this.effectivePauseWhenHidden && isDocumentHidden())\n ) {\n return;\n }\n\n const source = new EventSource(this.sseUrl);\n this.eventSource = source;\n source.onopen = () => {\n this.setSseConnected(true);\n this.schedulePoll();\n };\n source.onerror = () => {\n this.setSseConnected(false);\n // When the browser gives up permanently (HTTP error → readyState\n // CLOSED), it won't auto-reconnect. Drop the ref so a later\n // connectEvents() (on focus/visibility) can establish a fresh stream;\n // otherwise the non-null closed `eventSource` blocks reconnection and\n // we'd be stuck on polling-only forever.\n if (source.readyState === EventSource.CLOSED) {\n this.eventSource = null;\n }\n this.schedulePoll();\n };\n source.onmessage = (message) => {\n try {\n const payload = JSON.parse(message.data);\n const events = normalizeEventPayload(payload);\n const version =\n typeof payload?.version === \"number\" ? payload.version : undefined;\n this.applyVersion(events, version);\n this.fan(events, version);\n } catch {\n // Ignore malformed SSE frames; polling is the safety net.\n }\n };\n }\n\n /**\n * Advance the transport's shared version cursor. Subscribers receive the\n * raw events and decide independently which ones are \"fresh\" relative to\n * their own cursor, but the transport-level cursor ensures the poll\n * `?since=` parameter always advances.\n */\n private applyVersion(events: SyncEvent[], version: number | undefined): void {\n let max = typeof version === \"number\" ? version : 0;\n for (const evt of events) {\n const v = typeof evt.version === \"number\" ? evt.version : 0;\n if (v > max) max = v;\n }\n if (max > this.versionRef) this.versionRef = max;\n }\n\n private async poll(): Promise<void> {\n if (this.stopped || this.inFlight) return;\n this.inFlight = true;\n try {\n const data = await fetchPollJson<PollResponse>(\n this.pollUrl,\n this.versionRef,\n this.effectiveInterval,\n );\n if (this.stopped) return;\n this.consecutiveFailures = 0;\n const events = data.events ?? [];\n this.applyVersion(events, data.version);\n this.fan(events, data.version);\n } catch (err) {\n if (this.stopped) return;\n this.consecutiveFailures++;\n if (isAuthFailure(err)) {\n this.authFailureUntil = Date.now() + POLL_AUTH_FAILURE_COOLDOWN_MS;\n this.closeEvents();\n }\n // Network error — retried on the next (backed-off) interval.\n } finally {\n this.inFlight = false;\n this.schedulePoll();\n }\n }\n\n private pollNow(): void {\n if (this.effectivePauseWhenHidden && isDocumentHidden()) return;\n if (this.authFailureDelayMs() > 0) {\n this.schedulePoll();\n return;\n }\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n this.connectEvents();\n void this.poll();\n }\n\n private handleVisibilityChange = (): void => {\n if (document.visibilityState === \"visible\") {\n this.connectEvents();\n this.pollNow();\n } else if (this.effectivePauseWhenHidden) {\n this.closeEvents();\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n }\n };\n\n private handleFocus = (): void => {\n this.pollNow();\n };\n\n private start(): void {\n // Universal demo-mode redaction for the UI. Idempotent + browser-only +\n // a no-op until demo mode is on. Lives here because every template root\n // already mounts useDbSync, so this needs zero per-template wiring.\n ensureEmbedAuthFetchInterceptor();\n ensureDemoModeFetchInterceptor();\n\n if (!this.effectivePauseWhenHidden || !isDocumentHidden()) {\n this.connectEvents();\n void this.poll();\n }\n window.addEventListener(\"focus\", this.handleFocus);\n document.addEventListener(\"visibilitychange\", this.handleVisibilityChange);\n }\n\n private teardown(): void {\n this.stopped = true;\n this.closeEvents();\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n window.removeEventListener(\"focus\", this.handleFocus);\n document.removeEventListener(\n \"visibilitychange\",\n this.handleVisibilityChange,\n );\n }\n}\n\n/**\n * Registry of active transports keyed by \"<pollUrl>\\0<sseUrl>\".\n * Module-level singleton: survives React render cycles, shared across all\n * hook instances in the same browser tab.\n */\nconst transportRegistry = new Map<string, SyncTransport>();\n\nfunction getOrCreateTransport(\n pollUrl: string,\n sseUrl: string | false,\n): SyncTransport {\n const key = `${pollUrl}\\0${String(sseUrl)}`;\n let transport = transportRegistry.get(key);\n if (!transport) {\n transport = new SyncTransport(pollUrl, sseUrl);\n transportRegistry.set(key, transport);\n }\n return transport;\n}\n\n/** Remove a transport from the registry once torn down (last subscriber left). */\nfunction releaseTransport(pollUrl: string, sseUrl: string | false): void {\n const key = `${pollUrl}\\0${String(sseUrl)}`;\n // Leave the entry in place: SSE/poll is already stopped inside the class;\n // the next subscriber will re-start it via `add()`. Clearing the map entry\n // prevents any dangling reference from the old SyncTransport instance.\n transportRegistry.delete(key);\n}\n\n// ---------------------------------------------------------------------------\n// Internal test helper — reset transport registry between tests.\n// ---------------------------------------------------------------------------\n/** @internal */\nexport function _resetSyncTransportRegistryForTests(): void {\n transportRegistry.clear();\n}\n\nexport interface SubscribeSyncEventsOptions {\n /** Receives every batch of change events (SSE push or poll). */\n onEvents: (events: SyncEvent[], version: number | undefined) => void;\n /** Notified when the shared SSE connection opens/closes (and once on join). */\n onSseStateChange?: (connected: boolean) => void;\n pollUrl?: string;\n sseUrl?: string | false;\n pauseWhenHidden?: boolean;\n /**\n * Poll cadence this subscriber requests from the SHARED poll loop. The\n * transport uses the minimum across subscribers, so the defaults here are\n * deliberately high: subscribing must not speed up the shared poll —\n * useDbSync (mounted by every template root) already sets the pace.\n */\n interval?: number;\n fallbackInterval?: number;\n}\n\n/**\n * Subscribe to the shared SSE + poll transport without the React Query\n * invalidation behavior of `useDbSync`. Use this instead of opening another\n * `EventSource` to `/_agent-native/events` — a browser tab should hold ONE\n * SSE connection no matter how many features listen to it (extra streams eat\n * the per-origin connection budget and starve data fetches, especially on\n * HTTP/1.1 dev servers).\n *\n * Returns an unsubscribe function. Safe to call only in browser contexts.\n */\nexport function subscribeSyncEvents(\n options: SubscribeSyncEventsOptions,\n): () => void {\n const pollUrl = agentNativePath(options.pollUrl ?? \"/_agent-native/poll\");\n const sseUrl = resolveSseUrl(options.sseUrl);\n const transport = getOrCreateTransport(pollUrl, sseUrl);\n const id = Symbol(\"subscribeSyncEvents\");\n transport.add(id, {\n onEvents: options.onEvents,\n onSseStateChange: options.onSseStateChange,\n pauseWhenHidden: options.pauseWhenHidden ?? true,\n interval: options.interval ?? 60_000,\n fallbackInterval: options.fallbackInterval ?? 60_000,\n });\n return () => {\n transport.remove(id);\n if (!transport[\"subscribers\"].size) {\n releaseTransport(pollUrl, sseUrl);\n }\n };\n}\n\n/**\n * Hook that listens to /_agent-native/events for DB change events and\n * invalidates react-query caches when changes are detected. Falls back to\n * /_agent-native/poll so cross-process/serverless writes still show up.\n *\n * Works in all deployment environments (serverless, edge, long-lived server).\n * SSE is the fast path; polling is the safety net.\n *\n * @param options.queryClient - The react-query QueryClient instance\n * @param options.queryKeys - **Deprecated and ignored.** The hook uses\n * framework-owned fixed prefixes plus per-source change counters instead of\n * caller-supplied key lists. Kept in the type signature for backward\n * compatibility — existing call sites that still pass this option keep\n * working but the value has no effect.\n * @param options.pollUrl - Poll endpoint URL. Default: \"/_agent-native/poll\"\n * @param options.sseUrl - SSE endpoint URL. Default: \"/_agent-native/events\".\n * Pass false to disable SSE and use polling only.\n * @param options.onEvent - Optional callback for each change event\n * @param options.interval - Poll interval in ms. Default: 2000\n * @param options.fallbackInterval - Poll interval while SSE is connected.\n * Default: 15000\n * @param options.pauseWhenHidden - Pause polling while the tab is hidden.\n * Default: true\n * @param options.ignoreSource - Skip events whose `requestSource` matches this\n * value. Use a per-tab ID so the UI ignores its own writes while still\n * picking up changes from other tabs, agents, and scripts.\n * @param options.actionInvalidatePredicate - Optional filter for the broad\n * compatibility invalidate triggered by `action` events. Use this to keep\n * expensive active queries on explicit-refresh semantics while still letting\n * normal source-versioned queries react through `useChangeVersion`.\n * @param options.suppressActionInvalidationFor - Action names whose sync events\n * should not invalidate all action queries. Use only for high-volume\n * background actions that perform their own narrow client invalidation.\n */\nexport function useDbSync(\n options: {\n queryClient?: QueryClient;\n queryKeys?: string[];\n pollUrl?: string;\n sseUrl?: string | false;\n /** @deprecated Use pollUrl instead */\n eventsUrl?: string;\n onEvent?: (data: any) => void;\n interval?: number;\n fallbackInterval?: number;\n pauseWhenHidden?: boolean;\n ignoreSource?: string;\n actionInvalidatePredicate?: (query: Query) => boolean;\n suppressActionInvalidationFor?: string[];\n } = {},\n): void {\n const {\n queryClient,\n pollUrl = agentNativePath(options.eventsUrl ?? \"/_agent-native/poll\"),\n sseUrl = resolveSseUrl(options.sseUrl),\n interval = 2000,\n fallbackInterval = Math.max(\n options.fallbackInterval ?? SSE_FALLBACK_INTERVAL_MS,\n interval,\n ),\n pauseWhenHidden = true,\n } = options;\n\n const onEventRef = useRef(options.onEvent);\n onEventRef.current = options.onEvent;\n\n const ignoreSourceRef = useRef(options.ignoreSource);\n ignoreSourceRef.current = options.ignoreSource;\n const actionInvalidatePredicateRef = useRef(\n options.actionInvalidatePredicate,\n );\n actionInvalidatePredicateRef.current = options.actionInvalidatePredicate;\n const suppressActionInvalidationForRef = useRef(\n options.suppressActionInvalidationFor,\n );\n suppressActionInvalidationForRef.current =\n options.suppressActionInvalidationFor;\n\n useEffect(() => {\n const id = Symbol(\"useDbSync\");\n // Per-subscriber version cursor: tracks which events have already been\n // processed by THIS subscriber so stale poll re-deliveries are ignored.\n let subscriberVersion = 0;\n\n // Coalesce bursts of SSE-driven invalidation into at most one flush per\n // INVALIDATE_COALESCE_MS. A chatty doc (many small agent edits, several\n // peers editing at once) can otherwise deliver a handful of `action`/\n // `collab` events within a few hundred ms, each independently calling\n // `queryClient.invalidateQueries` and firing `onEvent` — every one of\n // those touches whatever query subscribers are mounted (e.g. a\n // full-page editor) even though the end state only needs to be\n // refreshed once. Version bookkeeping stays synchronous (below) so\n // freshness filtering for the NEXT batch is unaffected by the delay.\n //\n // This coalesce window is wrong for interaction-critical events (agent\n // navigation, `set-url`, guided questions — see\n // `isInteractionCriticalSyncEvent`): those must reach the UI immediately,\n // not up to INVALIDATE_COALESCE_MS late. So a fresh batch containing one\n // of those flushes synchronously (queued + new events together,\n // canceling any pending timer) instead of joining the coalesce window.\n // Pure invalidation bursts with no interaction-critical members keep the\n // coalesced behavior.\n let pendingInvalidateEvents: SyncEvent[] = [];\n let invalidateTimer: ReturnType<typeof setTimeout> | null = null;\n\n function flushInvalidateBatch() {\n if (invalidateTimer) {\n clearTimeout(invalidateTimer);\n invalidateTimer = null;\n }\n if (pendingInvalidateEvents.length === 0) return;\n const batch = pendingInvalidateEvents;\n pendingInvalidateEvents = [];\n invalidateForEvents(batch);\n }\n\n function queueInvalidateBatch(events: SyncEvent[]) {\n pendingInvalidateEvents.push(...events);\n if (events.some(isInteractionCriticalSyncEvent)) {\n flushInvalidateBatch();\n return;\n }\n if (invalidateTimer) return;\n invalidateTimer = setTimeout(\n flushInvalidateBatch,\n INVALIDATE_COALESCE_MS,\n );\n }\n\n function hasAppStateEvent(events: SyncEvent[], key: string): boolean {\n return events.some(\n (event) =>\n event.source === \"app-state\" &&\n (event.key === key ||\n event.key === \"*\" ||\n (typeof event.key === \"string\" && event.key.startsWith(`${key}:`))),\n );\n }\n\n function invalidateForEvents(events: SyncEvent[]) {\n const ignore = ignoreSourceRef.current;\n const relevant = ignore\n ? events.filter((e) => e.requestSource !== ignore)\n : events;\n const suppressedActions = new Set(\n suppressActionInvalidationForRef.current ?? [],\n );\n const isSuppressedActionEvent = (evt: SyncEvent) =>\n evt.source === \"action\" &&\n typeof evt.key === \"string\" &&\n suppressedActions.has(evt.key);\n const nonAwareness = relevant.filter((e) => e.source !== \"awareness\");\n const suppressesWholeBatch =\n nonAwareness.length > 0 &&\n nonAwareness.every((evt) => evt.source === \"action\") &&\n nonAwareness.every(isSuppressedActionEvent);\n\n // Bump per-source change counters. Components that read these via\n // `useChangeVersion(source)` and fold the value into a React Query\n // queryKey get a targeted refetch — no whole-cache invalidate, no\n // request storm. See `use-change-version.ts` for the contract.\n for (const evt of relevant) {\n const src = typeof evt.source === \"string\" ? evt.source : \"\";\n const ver = typeof evt.version === \"number\" ? evt.version : 0;\n if (src && ver > 0) bumpChangeVersion(src, ver);\n }\n\n // Awareness (cursor/presence) events never change action/extension/\n // app-state query results, but they arrive on every peer keystroke and\n // carry no version (so the freshness filter always passes them). Keep\n // them out of the invalidate block or an idle collaborative doc turns\n // every peer's cursor move into a framework-wide refetch storm; they\n // still reach onEvent below for callers that render presence.\n const invalidating = relevant.filter((e) => e.source !== \"awareness\");\n\n if (invalidating.length > 0 && queryClient) {\n const hasActionEvent = invalidating.some(\n (evt) => evt.source === \"action\" && !isSuppressedActionEvent(evt),\n );\n if (hasActionEvent) {\n // Custom apps frequently start with raw `useQuery` calls before\n // graduating to `useActionQuery` or source-versioned query keys.\n // A successful mutating action is the core \"agent changed app data\"\n // signal, so refresh active queries broadly as a compatibility\n // safety net. Other event sources stay targeted to avoid request\n // storms from noisy domain-specific writes.\n const predicate = actionInvalidatePredicateRef.current;\n queryClient.invalidateQueries(predicate ? { predicate } : undefined);\n }\n\n // Framework-level invalidate: a small, fixed list of query-key\n // prefixes the framework's own hooks/components use (action results,\n // extension state, application-state, the agent's `set-url` channel,\n // etc.). Templates' own data queries do NOT live here — they react\n // through `useChangeVersion(source)` in their query keys instead, so\n // a single change event doesn't fan out into \"refetch everything\".\n // Suppressed-action-only batches skip this whole list (their\n // mutations perform their own narrow invalidation) — but events must\n // STILL reach the onEvent forwarding below, so guard, don't return.\n //\n // Invalidation is scoped by source. Data-query prefixes (action,\n // extension, tool) refetch only when the batch carries an event that\n // can actually change action/extension-backed data — action\n // mutations, settings, extension, collab, screen-refresh, etc.\n // `app-state` events (agent/UI navigation, selection, and the\n // set-url/questions command channel) drive their OWN keys below and\n // must NEVER fan out into \"refetch every action query\": an active\n // agent session mirrors navigation + selection into application_state\n // continuously, and the serverless poll path replays those writes\n // back to the originating tab (the DB-scan fallback cannot preserve\n // `requestSource`, so `ignoreSource` can't filter them). Fanning each\n // one into a full `[\"action\"]` refetch turned a normal session into a\n // client fetch storm that exhausted the DB connection pool — which in\n // turn starved run heartbeat writes and surfaced as `stale_run`.\n if (!suppressesWholeBatch) {\n const hasDataChangingEvent = invalidating.some(\n (evt) => evt.source !== \"app-state\",\n );\n if (hasDataChangingEvent) {\n queryClient.invalidateQueries({ queryKey: [\"action\"] });\n queryClient.invalidateQueries({ queryKey: [\"extension\"] });\n queryClient.invalidateQueries({ queryKey: [\"extensions\"] });\n queryClient.invalidateQueries({ queryKey: [\"extension-slots\"] });\n queryClient.invalidateQueries({ queryKey: [\"slot-installs\"] });\n queryClient.invalidateQueries({ queryKey: [\"slot-available\"] });\n queryClient.invalidateQueries({ queryKey: [\"tool\"] });\n queryClient.invalidateQueries({ queryKey: [\"tools\"] });\n }\n if (invalidating.some((evt) => evt.source === \"app-state\")) {\n queryClient.invalidateQueries({ queryKey: [\"app-state\"] });\n }\n if (hasAppStateEvent(invalidating, \"navigate\")) {\n queryClient.invalidateQueries({ queryKey: [\"navigate-command\"] });\n }\n if (hasAppStateEvent(invalidating, \"show-questions\")) {\n queryClient.invalidateQueries({ queryKey: [\"show-questions\"] });\n }\n if (hasAppStateEvent(invalidating, \"__set_url__\")) {\n queryClient.invalidateQueries({ queryKey: [\"__set_url__\"] });\n }\n }\n }\n\n // Always forward all events to onEvent — templates can layer surgical\n // logic on top (e.g. ignore their own writes via requestSource, or\n // invalidate inactive queries for a specific source).\n for (const evt of events) {\n onEventRef.current?.(evt);\n }\n }\n\n function onEvents(events: SyncEvent[], version: number | undefined): void {\n const freshEvents = events.filter((event) => {\n const v = typeof event.version === \"number\" ? event.version : 0;\n return v === 0 || v > subscriberVersion;\n });\n\n if (freshEvents.length > 0) {\n queueInvalidateBatch(freshEvents);\n }\n\n const maxEventVersion = freshEvents.reduce(\n (max, event) =>\n Math.max(max, typeof event.version === \"number\" ? event.version : 0),\n 0,\n );\n subscriberVersion = Math.max(\n subscriberVersion,\n version ?? 0,\n maxEventVersion,\n );\n }\n\n const transport = getOrCreateTransport(pollUrl, sseUrl);\n transport.add(id, {\n onEvents,\n pauseWhenHidden,\n interval,\n fallbackInterval,\n });\n\n return () => {\n if (invalidateTimer) {\n clearTimeout(invalidateTimer);\n // Flush synchronously on unmount so a pending batch isn't silently\n // dropped (e.g. a route change right after an agent edit lands).\n flushInvalidateBatch();\n }\n transport.remove(id);\n // If the registry still holds this transport, and the transport is now\n // empty, evict it so the next mount gets a fresh instance rather than a\n // stopped-but-still-registered one (the registry entry being cleared by\n // releaseTransport is the signal to rebuild state).\n if (!transport[\"subscribers\"].size) {\n releaseTransport(pollUrl, sseUrl);\n }\n };\n }, [\n pollUrl,\n sseUrl,\n queryClient,\n interval,\n fallbackInterval,\n pauseWhenHidden,\n ]);\n}\n\n/** @deprecated Use useDbSync instead */\nexport const useFileWatcher = useDbSync;\n\n/**\n * Subscribe to `refresh-screen` events from the agent. Returns an integer\n * that increments every time the agent invokes the framework's `refresh-screen`\n * tool. Apply it as a React `key` on the main content wrapper (the part\n * OUTSIDE the agent chat sidebar) so that region remounts and re-fetches its\n * data while the chat, sidebar, and any other persistent chrome keep their\n * in-flight state.\n *\n * Usage in a template's root:\n *\n * const screenKey = useScreenRefreshKey();\n * return (\n * <AppLayout>\n * <div key={screenKey}>\n * <Outlet />\n * </div>\n * </AppLayout>\n * );\n */\nexport function useScreenRefreshKey(\n options: {\n pollUrl?: string;\n sseUrl?: string | false;\n interval?: number;\n fallbackInterval?: number;\n pauseWhenHidden?: boolean;\n } = {},\n): number {\n const {\n pollUrl = agentNativePath(options.pollUrl ?? \"/_agent-native/poll\"),\n sseUrl = resolveSseUrl(options.sseUrl),\n interval = 2000,\n fallbackInterval = Math.max(\n options.fallbackInterval ?? SSE_FALLBACK_INTERVAL_MS,\n interval,\n ),\n pauseWhenHidden = true,\n } = options;\n const [key, setKey] = useState(0);\n\n useEffect(() => {\n const id = Symbol(\"useScreenRefreshKey\");\n // Per-subscriber version cursor (same freshness logic as useDbSync).\n let subscriberVersion = 0;\n\n function onEvents(events: SyncEvent[], version: number | undefined): void {\n const freshEvents = events.filter((event) => {\n const v = typeof event.version === \"number\" ? event.version : 0;\n return v === 0 || v > subscriberVersion;\n });\n if (freshEvents.some((e) => e.source === \"screen-refresh\")) {\n setKey((k) => k + 1);\n }\n const maxEventVersion = freshEvents.reduce(\n (max, event) =>\n Math.max(max, typeof event.version === \"number\" ? event.version : 0),\n 0,\n );\n subscriberVersion = Math.max(\n subscriberVersion,\n version ?? 0,\n maxEventVersion,\n );\n }\n\n const transport = getOrCreateTransport(pollUrl, sseUrl);\n transport.add(id, {\n onEvents,\n pauseWhenHidden,\n interval,\n fallbackInterval,\n });\n\n return () => {\n transport.remove(id);\n if (!transport[\"subscribers\"].size) {\n releaseTransport(pollUrl, sseUrl);\n }\n };\n }, [pollUrl, sseUrl, interval, fallbackInterval, pauseWhenHidden]);\n\n return key;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"agent-presence.d.ts","sourceRoot":"","sources":["../../src/collab/agent-presence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AASH,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAKtE,6EAA6E;AAC7E,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAoH9C;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,IAAI,CAUN;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,iBAAiB,GAC1B,IAAI,CASN;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,IAAI,CAGN;AAED,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,IAAI,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,iBAAiB,GAC1B,IAAI,CAoBN;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EAC/C,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7B,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;;;;GAKG;AACH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,KAAK,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC,EACF,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7B,OAAO,CAAC,IAAI,CAAC,CAiCf"}
1
+ {"version":3,"file":"agent-presence.d.ts","sourceRoot":"","sources":["../../src/collab/agent-presence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAWH,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAKtE,6EAA6E;AAC7E,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAuH9C;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,IAAI,CAUN;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,iBAAiB,GAC1B,IAAI,CASN;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,IAAI,CAGN;AAED,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,IAAI,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,iBAAiB,GAC1B,IAAI,CAoBN;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EAC/C,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7B,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;;;;GAKG;AACH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,KAAK,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC,EACF,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7B,OAAO,CAAC,IAAI,CAAC,CAiCf"}
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { AGENT_CLIENT_ID, DEFAULT_AGENT_IDENTITY } from "./agent-identity.js";
20
20
  import { deleteAwarenessRow, upsertAwarenessRow } from "./awareness-store.js";
21
- import { emitAwarenessChange, getDocAwareness, } from "./awareness.js";
21
+ import { emitAwarenessChange, forgetAwarenessClear, getDocAwareness, rememberAwarenessClear, } from "./awareness.js";
22
22
  import { appendRecentEdit } from "./recent-edits.js";
23
23
  import { searchAndReplace } from "./ydoc-manager.js";
24
24
  const HEARTBEAT_INTERVAL = 10_000; // 10 seconds
@@ -39,11 +39,13 @@ function cancelLinger(docId) {
39
39
  }
40
40
  function removeAgentPresence(docId) {
41
41
  cancelLinger(docId);
42
+ const clearedAt = Date.now();
43
+ rememberAwarenessClear(docId, AGENT_CLIENT_ID, clearedAt);
42
44
  const map = getDocAwareness(docId);
43
45
  map.delete(AGENT_CLIENT_ID);
44
46
  emitAwarenessChange(docId, currentAwarenessStates(map));
45
47
  // Cross-instance removal (serverless) — best-effort, never blocks.
46
- void deleteAwarenessRow(docId, AGENT_CLIENT_ID);
48
+ void deleteAwarenessRow(docId, AGENT_CLIENT_ID, clearedAt);
47
49
  const interval = _heartbeats.get(docId);
48
50
  if (interval) {
49
51
  clearInterval(interval);
@@ -112,6 +114,7 @@ function readAgentState(docId) {
112
114
  };
113
115
  }
114
116
  function writeAgentState(docId, state) {
117
+ forgetAwarenessClear(docId, AGENT_CLIENT_ID);
115
118
  const entry = {
116
119
  clientId: AGENT_CLIENT_ID,
117
120
  state: JSON.stringify(state),
@@ -1 +1 @@
1
- {"version":3,"file":"agent-presence.js","sourceRoot":"","sources":["../../src/collab/agent-presence.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Server-side agent presence lifecycle for collaborative editing.\n *\n * Provides enter/leave semantics so the agent behaves like a real\n * collaborator — it \"enters\" a document, its edits are visible with\n * durable presence, and it \"leaves\" when done. Actions call these\n * instead of hand-rolling HTTP awareness calls.\n *\n * Presence lingers after the agent finishes editing (default 6s) so viewers\n * get a moment to see who just changed what — the same way a human\n * collaborator's cursor doesn't vanish the instant they stop typing. Recent\n * edits are attributed in the awareness state (`recentEdits`, `lastEditAt`)\n * so clients can render fading highlights over the edited regions.\n *\n * On serverless hosts the linger timer may never fire after the response is\n * sent; the 30s awareness expiry then removes the entry, so presence degrades\n * to \"up to 30s\" instead of leaking.\n */\n\nimport { AGENT_CLIENT_ID, DEFAULT_AGENT_IDENTITY } from \"./agent-identity.js\";\nimport { deleteAwarenessRow, upsertAwarenessRow } from \"./awareness-store.js\";\nimport {\n emitAwarenessChange,\n getDocAwareness,\n type AwarenessEntry,\n} from \"./awareness.js\";\nimport { appendRecentEdit, type RecentEdit } from \"./recent-edits.js\";\nimport { searchAndReplace } from \"./ydoc-manager.js\";\n\nconst HEARTBEAT_INTERVAL = 10_000; // 10 seconds\n\n/** How long agent presence lingers after leave/last edit before clearing. */\nexport const AGENT_PRESENCE_LINGER_MS = 6_000;\n\n// docId → heartbeat interval handle\nconst _heartbeats = new Map<string, NodeJS.Timeout>();\n// docId → reference count (how many concurrent operations are using this doc)\nconst _refCounts = new Map<string, number>();\n// docId → pending linger-removal timer\nconst _lingerTimers = new Map<string, NodeJS.Timeout>();\n\nfunction cancelLinger(docId: string): void {\n const timer = _lingerTimers.get(docId);\n if (timer) {\n clearTimeout(timer);\n _lingerTimers.delete(docId);\n }\n}\n\nfunction removeAgentPresence(docId: string): void {\n cancelLinger(docId);\n const map = getDocAwareness(docId);\n map.delete(AGENT_CLIENT_ID);\n emitAwarenessChange(docId, currentAwarenessStates(map));\n // Cross-instance removal (serverless) — best-effort, never blocks.\n void deleteAwarenessRow(docId, AGENT_CLIENT_ID);\n\n const interval = _heartbeats.get(docId);\n if (interval) {\n clearInterval(interval);\n _heartbeats.delete(docId);\n }\n}\n\nfunction scheduleLingerRemoval(docId: string, lingerMs: number): void {\n cancelLinger(docId);\n if (lingerMs <= 0) {\n removeAgentPresence(docId);\n return;\n }\n const timer = setTimeout(() => {\n _lingerTimers.delete(docId);\n // A new operation may have re-entered while we waited.\n if ((_refCounts.get(docId) ?? 0) > 0) return;\n removeAgentPresence(docId);\n }, lingerMs);\n if (typeof timer === \"object\" && \"unref\" in timer) {\n timer.unref();\n }\n _lingerTimers.set(docId, timer);\n}\n\nfunction ensureHeartbeat(docId: string): void {\n if (_heartbeats.has(docId)) return;\n\n const interval = setInterval(() => {\n const m = getDocAwareness(docId);\n const existing = m.get(AGENT_CLIENT_ID);\n if (existing) {\n existing.lastSeen = Date.now();\n // Keep the SQL mirror fresh too, or remote-instance pollers would see\n // the agent expire mid-operation (store throttles unchanged writes).\n void upsertAwarenessRow(\n docId,\n AGENT_CLIENT_ID,\n existing.state,\n existing.lastSeen,\n );\n }\n }, HEARTBEAT_INTERVAL);\n\n // Don't block Node from exiting if this is the only timer left\n if (typeof interval === \"object\" && \"unref\" in interval) {\n interval.unref();\n }\n\n _heartbeats.set(docId, interval);\n}\n\nfunction currentAwarenessStates(map: Map<number, AwarenessEntry>) {\n return Array.from(map, ([clientId, entry]) => ({\n clientId,\n state: entry.state,\n }));\n}\n\nfunction readAgentState(docId: string): Record<string, unknown> {\n const existing = getDocAwareness(docId).get(AGENT_CLIENT_ID);\n if (existing) {\n try {\n return JSON.parse(existing.state) as Record<string, unknown>;\n } catch {\n // Invalid state — fall through to defaults\n }\n }\n return {\n user: {\n name: DEFAULT_AGENT_IDENTITY.name,\n email: DEFAULT_AGENT_IDENTITY.email,\n color: DEFAULT_AGENT_IDENTITY.color,\n },\n };\n}\n\nfunction writeAgentState(docId: string, state: Record<string, unknown>): void {\n const entry: AwarenessEntry = {\n clientId: AGENT_CLIENT_ID,\n state: JSON.stringify(state),\n lastSeen: Date.now(),\n };\n const map = getDocAwareness(docId);\n map.set(AGENT_CLIENT_ID, entry);\n emitAwarenessChange(docId, currentAwarenessStates(map));\n // Mirror to SQL so pollers on other instances see the agent — an action\n // often runs in a different serverless invocation than the poll route.\n void upsertAwarenessRow(docId, AGENT_CLIENT_ID, entry.state, entry.lastSeen);\n}\n\n/**\n * Mark the agent as present on a document.\n *\n * Sets an awareness entry for the agent and starts a heartbeat that\n * keeps it alive. If the agent is already present on this doc (including\n * lingering after a previous edit), refreshes state without creating a\n * second interval.\n */\nexport function agentEnterDocument(\n docId: string,\n metadata?: Record<string, unknown>,\n): void {\n cancelLinger(docId);\n\n const state = { ...readAgentState(docId), ...metadata };\n writeAgentState(docId, state);\n\n // Increment reference count\n _refCounts.set(docId, (_refCounts.get(docId) ?? 0) + 1);\n\n ensureHeartbeat(docId);\n}\n\nexport interface AgentLeaveOptions {\n /**\n * How long presence lingers before the awareness entry clears.\n * Defaults to {@link AGENT_PRESENCE_LINGER_MS}. Pass 0 to clear\n * immediately (e.g. when an operation failed before editing anything).\n */\n lingerMs?: number;\n}\n\n/**\n * Release the agent's presence on a document.\n *\n * Decrements the reference count; when it reaches zero the awareness entry\n * lingers for `lingerMs` (default 6s) and is then removed. Viewers see the\n * agent avatar/highlights for a beat after the edit completes instead of an\n * instant disappearance.\n */\nexport function agentLeaveDocument(\n docId: string,\n options?: AgentLeaveOptions,\n): void {\n const count = (_refCounts.get(docId) ?? 1) - 1;\n if (count > 0) {\n _refCounts.set(docId, count);\n return;\n }\n _refCounts.delete(docId);\n\n scheduleLingerRemoval(docId, options?.lingerMs ?? AGENT_PRESENCE_LINGER_MS);\n}\n\n/**\n * Update the agent's awareness state to include selection info\n * (e.g., which track, panel, or element the agent is working on).\n */\nexport function agentUpdateSelection(\n docId: string,\n selection: Record<string, unknown>,\n): void {\n const state = { ...readAgentState(docId), ...selection };\n writeAgentState(docId, state);\n}\n\nexport interface AgentTouchOptions {\n /** Region descriptor + label recorded in the agent's recentEdits ring. */\n edit?: Omit<RecentEdit, \"at\"> & { at?: number };\n /** Extra awareness fields to merge (e.g. `selection`). */\n metadata?: Record<string, unknown>;\n /** Linger before auto-clearing when no enter/leave pair is active. */\n lingerMs?: number;\n}\n\n/**\n * Record an agent edit on a document without requiring an explicit\n * enter/leave pair. Used by the collab write paths so ANY agent edit\n * produces visible presence + lingering attribution automatically.\n *\n * - Upserts the agent awareness entry (identity preserved/merged).\n * - Appends to the `recentEdits` ring and bumps `lastEditAt`.\n * - When no explicit operation is in flight (refcount 0), (re)schedules a\n * linger removal so presence fades out on its own.\n */\nexport function agentTouchDocument(\n docId: string,\n options?: AgentTouchOptions,\n): void {\n cancelLinger(docId);\n\n const state = { ...readAgentState(docId), ...(options?.metadata ?? {}) };\n const now = Date.now();\n if (options?.edit) {\n const { at, ...rest } = options.edit;\n state.recentEdits = appendRecentEdit(\n state.recentEdits as RecentEdit[] | undefined,\n { ...rest, at: at ?? now },\n );\n }\n state.lastEditAt = now;\n writeAgentState(docId, state);\n\n ensureHeartbeat(docId);\n\n if ((_refCounts.get(docId) ?? 0) === 0) {\n scheduleLingerRemoval(docId, options?.lingerMs ?? AGENT_PRESENCE_LINGER_MS);\n }\n}\n\n/**\n * Apply search-and-replace edits incrementally so each one appears\n * as a separate poll event to connected clients.\n *\n * Enters the document before editing and leaves in a finally block.\n */\nexport async function agentApplyEditsIncrementally(\n docId: string,\n edits: Array<{ find: string; replace: string }>,\n options?: { delayMs?: number },\n): Promise<void> {\n const delayMs = options?.delayMs ?? 150;\n agentEnterDocument(docId);\n\n try {\n for (const edit of edits) {\n await searchAndReplace(docId, edit.find, edit.replace, \"agent\");\n if (delayMs > 0) {\n await new Promise((r) => setTimeout(r, delayMs));\n }\n }\n } finally {\n agentLeaveDocument(docId);\n }\n}\n\n/**\n * Apply structured data patches incrementally so each one appears\n * as a separate poll event to connected clients.\n *\n * Enters the document before patching and leaves in a finally block.\n */\nexport async function agentApplyPatchesIncrementally(\n docId: string,\n fieldName: string,\n patches: Array<{\n op: string;\n path: string;\n value?: unknown;\n index?: number;\n from?: number;\n to?: number;\n }>,\n options?: { delayMs?: number },\n): Promise<void> {\n const delayMs = options?.delayMs ?? 150;\n agentEnterDocument(docId);\n\n try {\n // Resolve applyPatchOps dynamically so a build that strips it (or a partial\n // upgrade) fails loudly here rather than at module load time.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let applyPatchOps: any;\n try {\n const mod = await import(\"./ydoc-manager.js\");\n applyPatchOps = (mod as Record<string, unknown>).applyPatchOps;\n } catch {\n throw new Error(\n \"applyPatchOps is not available yet — Phase 1 must complete first\",\n );\n }\n\n if (typeof applyPatchOps !== \"function\") {\n throw new Error(\n \"applyPatchOps is not available yet — Phase 1 must complete first\",\n );\n }\n\n for (const patch of patches) {\n await applyPatchOps(docId, [patch], fieldName, \"agent\");\n if (delayMs > 0) {\n await new Promise((r) => setTimeout(r, delayMs));\n }\n }\n } finally {\n agentLeaveDocument(docId);\n }\n}\n"]}
1
+ {"version":3,"file":"agent-presence.js","sourceRoot":"","sources":["../../src/collab/agent-presence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,eAAe,EACf,sBAAsB,GAEvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAmB,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,kBAAkB,GAAG,MAAM,CAAC,CAAC,aAAa;AAEhD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAE9C,oCAAoC;AACpC,MAAM,WAAW,GAAG,IAAI,GAAG,EAA0B,CAAC;AACtD,8EAA8E;AAC9E,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;AAC7C,uCAAuC;AACvC,MAAM,aAAa,GAAG,IAAI,GAAG,EAA0B,CAAC;AAExD,SAAS,YAAY,CAAC,KAAa;IACjC,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,KAAK,EAAE,CAAC;QACV,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,YAAY,CAAC,KAAK,CAAC,CAAC;IACpB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,sBAAsB,CAAC,KAAK,EAAE,eAAe,EAAE,SAAS,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACnC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IAC5B,mBAAmB,CAAC,KAAK,EAAE,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC;IACxD,mEAAmE;IACnE,KAAK,kBAAkB,CAAC,KAAK,EAAE,eAAe,EAAE,SAAS,CAAC,CAAC;IAE3D,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,QAAQ,EAAE,CAAC;QACb,aAAa,CAAC,QAAQ,CAAC,CAAC;QACxB,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAa,EAAE,QAAgB;IAC5D,YAAY,CAAC,KAAK,CAAC,CAAC;IACpB,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QAClB,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAC3B,OAAO;IACT,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,uDAAuD;QACvD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC;YAAE,OAAO;QAC7C,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC,EAAE,QAAQ,CAAC,CAAC;IACb,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC;QAClD,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IACD,aAAa,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,eAAe,CAAC,KAAa;IACpC,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO;IAEnC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;QAChC,MAAM,CAAC,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QACxC,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC/B,sEAAsE;YACtE,qEAAqE;YACrE,KAAK,kBAAkB,CACrB,KAAK,EACL,eAAe,EACf,QAAQ,CAAC,KAAK,EACd,QAAQ,CAAC,QAAQ,CAClB,CAAC;QACJ,CAAC;IACH,CAAC,EAAE,kBAAkB,CAAC,CAAC;IAEvB,+DAA+D;IAC/D,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,OAAO,IAAI,QAAQ,EAAE,CAAC;QACxD,QAAQ,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,sBAAsB,CAAC,GAAgC;IAC9D,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QAC7C,QAAQ;QACR,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC,CAAC,CAAC;AACN,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACnC,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC7D,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAA4B,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,2CAA2C;QAC7C,CAAC;IACH,CAAC;IACD,OAAO;QACL,IAAI,EAAE;YACJ,IAAI,EAAE,sBAAsB,CAAC,IAAI;YACjC,KAAK,EAAE,sBAAsB,CAAC,KAAK;YACnC,KAAK,EAAE,sBAAsB,CAAC,KAAK;SACpC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,KAAa,EAAE,KAA8B;IACpE,oBAAoB,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAmB;QAC5B,QAAQ,EAAE,eAAe;QACzB,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QAC5B,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE;KACrB,CAAC;IACF,MAAM,GAAG,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACnC,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IAChC,mBAAmB,CAAC,KAAK,EAAE,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC;IACxD,wEAAwE;IACxE,uEAAuE;IACvE,KAAK,kBAAkB,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;AAC/E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAa,EACb,QAAkC;IAElC,YAAY,CAAC,KAAK,CAAC,CAAC;IAEpB,MAAM,KAAK,GAAG,EAAE,GAAG,cAAc,CAAC,KAAK,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC;IACxD,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAE9B,4BAA4B;IAC5B,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAExD,eAAe,CAAC,KAAK,CAAC,CAAC;AACzB,CAAC;AAWD;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAa,EACb,OAA2B;IAE3B,MAAM,KAAK,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC7B,OAAO;IACT,CAAC;IACD,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEzB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,IAAI,wBAAwB,CAAC,CAAC;AAC9E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAa,EACb,SAAkC;IAElC,MAAM,KAAK,GAAG,EAAE,GAAG,cAAc,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,EAAE,CAAC;IACzD,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAChC,CAAC;AAWD;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAa,EACb,OAA2B;IAE3B,YAAY,CAAC,KAAK,CAAC,CAAC;IAEpB,MAAM,KAAK,GAAG,EAAE,GAAG,cAAc,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,OAAO,EAAE,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC;IACzE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,OAAO,EAAE,IAAI,EAAE,CAAC;QAClB,MAAM,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;QACrC,KAAK,CAAC,WAAW,GAAG,gBAAgB,CAClC,KAAK,CAAC,WAAuC,EAC7C,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,GAAG,EAAE,CAC3B,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC;IACvB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAE9B,eAAe,CAAC,KAAK,CAAC,CAAC;IAEvB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACvC,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,IAAI,wBAAwB,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,KAAa,EACb,KAA+C,EAC/C,OAA8B;IAE9B,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,GAAG,CAAC;IACxC,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAE1B,IAAI,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAChE,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;gBAChB,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,KAAa,EACb,SAAiB,EACjB,OAOE,EACF,OAA8B;IAE9B,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,GAAG,CAAC;IACxC,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAE1B,IAAI,CAAC;QACH,4EAA4E;QAC5E,8DAA8D;QAC9D,8DAA8D;QAC9D,IAAI,aAAkB,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;YAC9C,aAAa,GAAI,GAA+B,CAAC,aAAa,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAC;QACJ,CAAC;QAED,IAAI,OAAO,aAAa,KAAK,UAAU,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAC;QACJ,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;YACxD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;gBAChB,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC","sourcesContent":["/**\n * Server-side agent presence lifecycle for collaborative editing.\n *\n * Provides enter/leave semantics so the agent behaves like a real\n * collaborator — it \"enters\" a document, its edits are visible with\n * durable presence, and it \"leaves\" when done. Actions call these\n * instead of hand-rolling HTTP awareness calls.\n *\n * Presence lingers after the agent finishes editing (default 6s) so viewers\n * get a moment to see who just changed what — the same way a human\n * collaborator's cursor doesn't vanish the instant they stop typing. Recent\n * edits are attributed in the awareness state (`recentEdits`, `lastEditAt`)\n * so clients can render fading highlights over the edited regions.\n *\n * On serverless hosts the linger timer may never fire after the response is\n * sent; the 30s awareness expiry then removes the entry, so presence degrades\n * to \"up to 30s\" instead of leaking.\n */\n\nimport { AGENT_CLIENT_ID, DEFAULT_AGENT_IDENTITY } from \"./agent-identity.js\";\nimport { deleteAwarenessRow, upsertAwarenessRow } from \"./awareness-store.js\";\nimport {\n emitAwarenessChange,\n forgetAwarenessClear,\n getDocAwareness,\n rememberAwarenessClear,\n type AwarenessEntry,\n} from \"./awareness.js\";\nimport { appendRecentEdit, type RecentEdit } from \"./recent-edits.js\";\nimport { searchAndReplace } from \"./ydoc-manager.js\";\n\nconst HEARTBEAT_INTERVAL = 10_000; // 10 seconds\n\n/** How long agent presence lingers after leave/last edit before clearing. */\nexport const AGENT_PRESENCE_LINGER_MS = 6_000;\n\n// docId → heartbeat interval handle\nconst _heartbeats = new Map<string, NodeJS.Timeout>();\n// docId → reference count (how many concurrent operations are using this doc)\nconst _refCounts = new Map<string, number>();\n// docId → pending linger-removal timer\nconst _lingerTimers = new Map<string, NodeJS.Timeout>();\n\nfunction cancelLinger(docId: string): void {\n const timer = _lingerTimers.get(docId);\n if (timer) {\n clearTimeout(timer);\n _lingerTimers.delete(docId);\n }\n}\n\nfunction removeAgentPresence(docId: string): void {\n cancelLinger(docId);\n const clearedAt = Date.now();\n rememberAwarenessClear(docId, AGENT_CLIENT_ID, clearedAt);\n const map = getDocAwareness(docId);\n map.delete(AGENT_CLIENT_ID);\n emitAwarenessChange(docId, currentAwarenessStates(map));\n // Cross-instance removal (serverless) — best-effort, never blocks.\n void deleteAwarenessRow(docId, AGENT_CLIENT_ID, clearedAt);\n\n const interval = _heartbeats.get(docId);\n if (interval) {\n clearInterval(interval);\n _heartbeats.delete(docId);\n }\n}\n\nfunction scheduleLingerRemoval(docId: string, lingerMs: number): void {\n cancelLinger(docId);\n if (lingerMs <= 0) {\n removeAgentPresence(docId);\n return;\n }\n const timer = setTimeout(() => {\n _lingerTimers.delete(docId);\n // A new operation may have re-entered while we waited.\n if ((_refCounts.get(docId) ?? 0) > 0) return;\n removeAgentPresence(docId);\n }, lingerMs);\n if (typeof timer === \"object\" && \"unref\" in timer) {\n timer.unref();\n }\n _lingerTimers.set(docId, timer);\n}\n\nfunction ensureHeartbeat(docId: string): void {\n if (_heartbeats.has(docId)) return;\n\n const interval = setInterval(() => {\n const m = getDocAwareness(docId);\n const existing = m.get(AGENT_CLIENT_ID);\n if (existing) {\n existing.lastSeen = Date.now();\n // Keep the SQL mirror fresh too, or remote-instance pollers would see\n // the agent expire mid-operation (store throttles unchanged writes).\n void upsertAwarenessRow(\n docId,\n AGENT_CLIENT_ID,\n existing.state,\n existing.lastSeen,\n );\n }\n }, HEARTBEAT_INTERVAL);\n\n // Don't block Node from exiting if this is the only timer left\n if (typeof interval === \"object\" && \"unref\" in interval) {\n interval.unref();\n }\n\n _heartbeats.set(docId, interval);\n}\n\nfunction currentAwarenessStates(map: Map<number, AwarenessEntry>) {\n return Array.from(map, ([clientId, entry]) => ({\n clientId,\n state: entry.state,\n }));\n}\n\nfunction readAgentState(docId: string): Record<string, unknown> {\n const existing = getDocAwareness(docId).get(AGENT_CLIENT_ID);\n if (existing) {\n try {\n return JSON.parse(existing.state) as Record<string, unknown>;\n } catch {\n // Invalid state — fall through to defaults\n }\n }\n return {\n user: {\n name: DEFAULT_AGENT_IDENTITY.name,\n email: DEFAULT_AGENT_IDENTITY.email,\n color: DEFAULT_AGENT_IDENTITY.color,\n },\n };\n}\n\nfunction writeAgentState(docId: string, state: Record<string, unknown>): void {\n forgetAwarenessClear(docId, AGENT_CLIENT_ID);\n const entry: AwarenessEntry = {\n clientId: AGENT_CLIENT_ID,\n state: JSON.stringify(state),\n lastSeen: Date.now(),\n };\n const map = getDocAwareness(docId);\n map.set(AGENT_CLIENT_ID, entry);\n emitAwarenessChange(docId, currentAwarenessStates(map));\n // Mirror to SQL so pollers on other instances see the agent — an action\n // often runs in a different serverless invocation than the poll route.\n void upsertAwarenessRow(docId, AGENT_CLIENT_ID, entry.state, entry.lastSeen);\n}\n\n/**\n * Mark the agent as present on a document.\n *\n * Sets an awareness entry for the agent and starts a heartbeat that\n * keeps it alive. If the agent is already present on this doc (including\n * lingering after a previous edit), refreshes state without creating a\n * second interval.\n */\nexport function agentEnterDocument(\n docId: string,\n metadata?: Record<string, unknown>,\n): void {\n cancelLinger(docId);\n\n const state = { ...readAgentState(docId), ...metadata };\n writeAgentState(docId, state);\n\n // Increment reference count\n _refCounts.set(docId, (_refCounts.get(docId) ?? 0) + 1);\n\n ensureHeartbeat(docId);\n}\n\nexport interface AgentLeaveOptions {\n /**\n * How long presence lingers before the awareness entry clears.\n * Defaults to {@link AGENT_PRESENCE_LINGER_MS}. Pass 0 to clear\n * immediately (e.g. when an operation failed before editing anything).\n */\n lingerMs?: number;\n}\n\n/**\n * Release the agent's presence on a document.\n *\n * Decrements the reference count; when it reaches zero the awareness entry\n * lingers for `lingerMs` (default 6s) and is then removed. Viewers see the\n * agent avatar/highlights for a beat after the edit completes instead of an\n * instant disappearance.\n */\nexport function agentLeaveDocument(\n docId: string,\n options?: AgentLeaveOptions,\n): void {\n const count = (_refCounts.get(docId) ?? 1) - 1;\n if (count > 0) {\n _refCounts.set(docId, count);\n return;\n }\n _refCounts.delete(docId);\n\n scheduleLingerRemoval(docId, options?.lingerMs ?? AGENT_PRESENCE_LINGER_MS);\n}\n\n/**\n * Update the agent's awareness state to include selection info\n * (e.g., which track, panel, or element the agent is working on).\n */\nexport function agentUpdateSelection(\n docId: string,\n selection: Record<string, unknown>,\n): void {\n const state = { ...readAgentState(docId), ...selection };\n writeAgentState(docId, state);\n}\n\nexport interface AgentTouchOptions {\n /** Region descriptor + label recorded in the agent's recentEdits ring. */\n edit?: Omit<RecentEdit, \"at\"> & { at?: number };\n /** Extra awareness fields to merge (e.g. `selection`). */\n metadata?: Record<string, unknown>;\n /** Linger before auto-clearing when no enter/leave pair is active. */\n lingerMs?: number;\n}\n\n/**\n * Record an agent edit on a document without requiring an explicit\n * enter/leave pair. Used by the collab write paths so ANY agent edit\n * produces visible presence + lingering attribution automatically.\n *\n * - Upserts the agent awareness entry (identity preserved/merged).\n * - Appends to the `recentEdits` ring and bumps `lastEditAt`.\n * - When no explicit operation is in flight (refcount 0), (re)schedules a\n * linger removal so presence fades out on its own.\n */\nexport function agentTouchDocument(\n docId: string,\n options?: AgentTouchOptions,\n): void {\n cancelLinger(docId);\n\n const state = { ...readAgentState(docId), ...(options?.metadata ?? {}) };\n const now = Date.now();\n if (options?.edit) {\n const { at, ...rest } = options.edit;\n state.recentEdits = appendRecentEdit(\n state.recentEdits as RecentEdit[] | undefined,\n { ...rest, at: at ?? now },\n );\n }\n state.lastEditAt = now;\n writeAgentState(docId, state);\n\n ensureHeartbeat(docId);\n\n if ((_refCounts.get(docId) ?? 0) === 0) {\n scheduleLingerRemoval(docId, options?.lingerMs ?? AGENT_PRESENCE_LINGER_MS);\n }\n}\n\n/**\n * Apply search-and-replace edits incrementally so each one appears\n * as a separate poll event to connected clients.\n *\n * Enters the document before editing and leaves in a finally block.\n */\nexport async function agentApplyEditsIncrementally(\n docId: string,\n edits: Array<{ find: string; replace: string }>,\n options?: { delayMs?: number },\n): Promise<void> {\n const delayMs = options?.delayMs ?? 150;\n agentEnterDocument(docId);\n\n try {\n for (const edit of edits) {\n await searchAndReplace(docId, edit.find, edit.replace, \"agent\");\n if (delayMs > 0) {\n await new Promise((r) => setTimeout(r, delayMs));\n }\n }\n } finally {\n agentLeaveDocument(docId);\n }\n}\n\n/**\n * Apply structured data patches incrementally so each one appears\n * as a separate poll event to connected clients.\n *\n * Enters the document before patching and leaves in a finally block.\n */\nexport async function agentApplyPatchesIncrementally(\n docId: string,\n fieldName: string,\n patches: Array<{\n op: string;\n path: string;\n value?: unknown;\n index?: number;\n from?: number;\n to?: number;\n }>,\n options?: { delayMs?: number },\n): Promise<void> {\n const delayMs = options?.delayMs ?? 150;\n agentEnterDocument(docId);\n\n try {\n // Resolve applyPatchOps dynamically so a build that strips it (or a partial\n // upgrade) fails loudly here rather than at module load time.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let applyPatchOps: any;\n try {\n const mod = await import(\"./ydoc-manager.js\");\n applyPatchOps = (mod as Record<string, unknown>).applyPatchOps;\n } catch {\n throw new Error(\n \"applyPatchOps is not available yet — Phase 1 must complete first\",\n );\n }\n\n if (typeof applyPatchOps !== \"function\") {\n throw new Error(\n \"applyPatchOps is not available yet — Phase 1 must complete first\",\n );\n }\n\n for (const patch of patches) {\n await applyPatchOps(docId, [patch], fieldName, \"agent\");\n if (delayMs > 0) {\n await new Promise((r) => setTimeout(r, delayMs));\n }\n }\n } finally {\n agentLeaveDocument(docId);\n }\n}\n"]}
@@ -21,7 +21,7 @@ import type { AwarenessEntry } from "./awareness.js";
21
21
  */
22
22
  export declare function upsertAwarenessRow(docId: string, clientId: number, state: string, lastSeen: number): Promise<void>;
23
23
  /** Remove a client's row (participant left). Never throws. */
24
- export declare function deleteAwarenessRow(docId: string, clientId: number): Promise<void>;
24
+ export declare function deleteAwarenessRow(docId: string, clientId: number, maxLastSeen?: number): Promise<void>;
25
25
  /**
26
26
  * Load non-expired awareness rows for a document. Returns [] on any
27
27
  * failure (memory-only degradation).
@@ -1 +1 @@
1
- {"version":3,"file":"awareness-store.d.ts","sourceRoot":"","sources":["../../src/collab/awareness-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAkDrD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAmCf;AAED,8DAA8D;AAC9D,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,GAAG,GAAE,MAAmB,GACvB,OAAO,CAAC,cAAc,EAAE,CAAC,CAiB3B;AAaD,+CAA+C;AAC/C,wBAAgB,4BAA4B,IAAI,IAAI,CAInD"}
1
+ {"version":3,"file":"awareness-store.d.ts","sourceRoot":"","sources":["../../src/collab/awareness-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAkDrD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAmCf;AAED,8DAA8D;AAC9D,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,GAAG,GAAE,MAAmB,GACvB,OAAO,CAAC,cAAc,EAAE,CAAC,CAiB3B;AAaD,+CAA+C;AAC/C,wBAAgB,4BAA4B,IAAI,IAAI,CAInD"}
Binary file
@@ -1 +1 @@
1
- {"version":3,"file":"awareness-store.js","sourceRoot":"","sources":["../../src/collab/awareness-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SQL-backed awareness store — makes presence work across serverless\n * instances.\n *\n * The in-memory awareness map in `awareness.ts` is per-process: on a\n * multi-instance/serverless deployment, a cursor published to instance A is\n * invisible to a client polling instance B, and an agent action running in\n * its own invocation can't reach the SSE instance's memory at all. This\n * store mirrors awareness rows into a `_collab_awareness` table (SQLite +\n * Postgres portable) so every instance serves the same participant set.\n *\n * Everything here is best-effort: presence must never fail or slow down an\n * edit. Writes are throttled per (docId, clientId) and skipped when the\n * state hasn't changed; expired rows are purged opportunistically.\n */\n\nimport { getDbExec, isPostgres } from \"../db/client.js\";\nimport { ensureTableExists } from \"../db/ddl-guard.js\";\nimport type { AwarenessEntry } from \"./awareness.js\";\n\n/** Rows older than this are treated as expired (matches AWARENESS_TIMEOUT). */\nconst ROW_TTL_MS = 30_000;\n\n/** Minimum interval between DB writes for an unchanged (docId, clientId). */\nconst WRITE_THROTTLE_MS = 2_000;\n\n/** Minimum interval between opportunistic purges per document. */\nconst PURGE_INTERVAL_MS = 30_000;\n\nlet _initPromise: Promise<void> | undefined;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n // client_id is a Yjs uint32 (can exceed int32 max) and last_seen is\n // epoch milliseconds — both need BIGINT-range columns.\n const createSql = `\n CREATE TABLE IF NOT EXISTS _collab_awareness (\n doc_id TEXT NOT NULL,\n client_id BIGINT NOT NULL,\n state TEXT NOT NULL,\n last_seen BIGINT NOT NULL,\n PRIMARY KEY (doc_id, client_id)\n )\n `;\n if (isPostgres()) {\n await ensureTableExists(\"_collab_awareness\", createSql);\n return;\n }\n await client.execute(createSql);\n })().catch((err) => {\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\n// (docId\u0000clientId) → { state, writtenAt } for write throttling.\nconst _lastWrites = new Map<string, { state: string; writtenAt: number }>();\n// docId → last purge time.\nconst _lastPurges = new Map<string, number>();\n\nfunction writeKey(docId: string, clientId: number): string {\n return `${docId}\u0000${clientId}`;\n}\n\n/**\n * Mirror a client's awareness state to SQL. Throttled: unchanged state is\n * rewritten at most every {@link WRITE_THROTTLE_MS} (to refresh last_seen).\n * Never throws.\n */\nexport async function upsertAwarenessRow(\n docId: string,\n clientId: number,\n state: string,\n lastSeen: number,\n): Promise<void> {\n try {\n const key = writeKey(docId, clientId);\n const prev = _lastWrites.get(key);\n if (\n prev &&\n prev.state === state &&\n lastSeen - prev.writtenAt < WRITE_THROTTLE_MS\n ) {\n return;\n }\n\n await ensureTable();\n const client = getDbExec();\n if (isPostgres()) {\n await client.execute({\n sql: `INSERT INTO _collab_awareness (doc_id, client_id, state, last_seen)\n VALUES (?, ?, ?, ?)\n ON CONFLICT (doc_id, client_id)\n DO UPDATE SET state = EXCLUDED.state, last_seen = EXCLUDED.last_seen`,\n args: [docId, clientId, state, lastSeen],\n });\n } else {\n await client.execute({\n sql: `INSERT OR REPLACE INTO _collab_awareness (doc_id, client_id, state, last_seen)\n VALUES (?, ?, ?, ?)`,\n args: [docId, clientId, state, lastSeen],\n });\n }\n _lastWrites.set(key, { state, writtenAt: lastSeen });\n\n await maybePurge(docId, lastSeen);\n } catch {\n // Best-effort — presence never fails a request.\n }\n}\n\n/** Remove a client's row (participant left). Never throws. */\nexport async function deleteAwarenessRow(\n docId: string,\n clientId: number,\n): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `DELETE FROM _collab_awareness WHERE doc_id = ? AND client_id = ?`,\n args: [docId, clientId],\n });\n _lastWrites.delete(writeKey(docId, clientId));\n } catch {\n // Best-effort.\n }\n}\n\n/**\n * Load non-expired awareness rows for a document. Returns [] on any\n * failure (memory-only degradation).\n */\nexport async function loadAwarenessRows(\n docId: string,\n now: number = Date.now(),\n): Promise<AwarenessEntry[]> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT client_id, state, last_seen FROM _collab_awareness\n WHERE doc_id = ? AND last_seen >= ?`,\n args: [docId, now - ROW_TTL_MS],\n });\n return rows.map((row: any) => ({\n clientId: Number(row.client_id),\n state: String(row.state),\n lastSeen: Number(row.last_seen),\n }));\n } catch {\n return [];\n }\n}\n\nasync function maybePurge(docId: string, now: number): Promise<void> {\n const last = _lastPurges.get(docId) ?? 0;\n if (now - last < PURGE_INTERVAL_MS) return;\n _lastPurges.set(docId, now);\n const client = getDbExec();\n await client.execute({\n sql: `DELETE FROM _collab_awareness WHERE doc_id = ? AND last_seen < ?`,\n args: [docId, now - ROW_TTL_MS * 2],\n });\n}\n\n/** Test hook: reset module-level throttles. */\nexport function _resetAwarenessStoreForTests(): void {\n _lastWrites.clear();\n _lastPurges.clear();\n _initPromise = undefined;\n}\n"]}
1
+ {"version":3,"file":"awareness-store.js","sourceRoot":"","sources":["../../src/collab/awareness-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SQL-backed awareness store — makes presence work across serverless\n * instances.\n *\n * The in-memory awareness map in `awareness.ts` is per-process: on a\n * multi-instance/serverless deployment, a cursor published to instance A is\n * invisible to a client polling instance B, and an agent action running in\n * its own invocation can't reach the SSE instance's memory at all. This\n * store mirrors awareness rows into a `_collab_awareness` table (SQLite +\n * Postgres portable) so every instance serves the same participant set.\n *\n * Everything here is best-effort: presence must never fail or slow down an\n * edit. Writes are throttled per (docId, clientId) and skipped when the\n * state hasn't changed; expired rows are purged opportunistically.\n */\n\nimport { getDbExec, isPostgres } from \"../db/client.js\";\nimport { ensureTableExists } from \"../db/ddl-guard.js\";\nimport type { AwarenessEntry } from \"./awareness.js\";\n\n/** Rows older than this are treated as expired (matches AWARENESS_TIMEOUT). */\nconst ROW_TTL_MS = 30_000;\n\n/** Minimum interval between DB writes for an unchanged (docId, clientId). */\nconst WRITE_THROTTLE_MS = 2_000;\n\n/** Minimum interval between opportunistic purges per document. */\nconst PURGE_INTERVAL_MS = 30_000;\n\nlet _initPromise: Promise<void> | undefined;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n // client_id is a Yjs uint32 (can exceed int32 max) and last_seen is\n // epoch milliseconds — both need BIGINT-range columns.\n const createSql = `\n CREATE TABLE IF NOT EXISTS _collab_awareness (\n doc_id TEXT NOT NULL,\n client_id BIGINT NOT NULL,\n state TEXT NOT NULL,\n last_seen BIGINT NOT NULL,\n PRIMARY KEY (doc_id, client_id)\n )\n `;\n if (isPostgres()) {\n await ensureTableExists(\"_collab_awareness\", createSql);\n return;\n }\n await client.execute(createSql);\n })().catch((err) => {\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\n// (docId\\0clientId) → { state, writtenAt } for write throttling.\nconst _lastWrites = new Map<string, { state: string; writtenAt: number }>();\n// docId → last purge time.\nconst _lastPurges = new Map<string, number>();\n\nfunction writeKey(docId: string, clientId: number): string {\n return `${docId}\\0${clientId}`;\n}\n\n/**\n * Mirror a client's awareness state to SQL. Throttled: unchanged state is\n * rewritten at most every {@link WRITE_THROTTLE_MS} (to refresh last_seen).\n * Never throws.\n */\nexport async function upsertAwarenessRow(\n docId: string,\n clientId: number,\n state: string,\n lastSeen: number,\n): Promise<void> {\n try {\n const key = writeKey(docId, clientId);\n const prev = _lastWrites.get(key);\n if (\n prev &&\n prev.state === state &&\n lastSeen - prev.writtenAt < WRITE_THROTTLE_MS\n ) {\n return;\n }\n\n await ensureTable();\n const client = getDbExec();\n if (isPostgres()) {\n await client.execute({\n sql: `INSERT INTO _collab_awareness (doc_id, client_id, state, last_seen)\n VALUES (?, ?, ?, ?)\n ON CONFLICT (doc_id, client_id)\n DO UPDATE SET state = EXCLUDED.state, last_seen = EXCLUDED.last_seen`,\n args: [docId, clientId, state, lastSeen],\n });\n } else {\n await client.execute({\n sql: `INSERT OR REPLACE INTO _collab_awareness (doc_id, client_id, state, last_seen)\n VALUES (?, ?, ?, ?)`,\n args: [docId, clientId, state, lastSeen],\n });\n }\n _lastWrites.set(key, { state, writtenAt: lastSeen });\n\n await maybePurge(docId, lastSeen);\n } catch {\n // Best-effort — presence never fails a request.\n }\n}\n\n/** Remove a client's row (participant left). Never throws. */\nexport async function deleteAwarenessRow(\n docId: string,\n clientId: number,\n maxLastSeen?: number,\n): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n if (maxLastSeen == null) {\n await client.execute({\n sql: `DELETE FROM _collab_awareness WHERE doc_id = ? AND client_id = ?`,\n args: [docId, clientId],\n });\n } else {\n await client.execute({\n sql: `DELETE FROM _collab_awareness\n WHERE doc_id = ? AND client_id = ? AND last_seen <= ?`,\n args: [docId, clientId, maxLastSeen],\n });\n }\n _lastWrites.delete(writeKey(docId, clientId));\n } catch {\n // Best-effort.\n }\n}\n\n/**\n * Load non-expired awareness rows for a document. Returns [] on any\n * failure (memory-only degradation).\n */\nexport async function loadAwarenessRows(\n docId: string,\n now: number = Date.now(),\n): Promise<AwarenessEntry[]> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT client_id, state, last_seen FROM _collab_awareness\n WHERE doc_id = ? AND last_seen >= ?`,\n args: [docId, now - ROW_TTL_MS],\n });\n return rows.map((row: any) => ({\n clientId: Number(row.client_id),\n state: String(row.state),\n lastSeen: Number(row.last_seen),\n }));\n } catch {\n return [];\n }\n}\n\nasync function maybePurge(docId: string, now: number): Promise<void> {\n const last = _lastPurges.get(docId) ?? 0;\n if (now - last < PURGE_INTERVAL_MS) return;\n _lastPurges.set(docId, now);\n const client = getDbExec();\n await client.execute({\n sql: `DELETE FROM _collab_awareness WHERE doc_id = ? AND last_seen < ?`,\n args: [docId, now - ROW_TTL_MS * 2],\n });\n}\n\n/** Test hook: reset module-level throttles. */\nexport function _resetAwarenessStoreForTests(): void {\n _lastWrites.clear();\n _lastPurges.clear();\n _initPromise = undefined;\n}\n"]}
@@ -46,6 +46,8 @@ export declare function emitAwarenessChange(docId: string, states: Array<{
46
46
  clientId: number;
47
47
  state: string;
48
48
  }>, scope?: AwarenessScope): void;
49
+ export declare function rememberAwarenessClear(docId: string, clientId: number, clearedAt?: number): void;
50
+ export declare function forgetAwarenessClear(docId: string, clientId: number): void;
49
51
  export declare function getDocAwareness(docId: string): Map<number, AwarenessEntry>;
50
52
  export declare function cleanExpired(map: Map<number, AwarenessEntry>): void;
51
53
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAe3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,IAAI,CAON;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,cAAc,GACrB,IAAI,CAgBN;AAKD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AA6BD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;;kBAqDa,MAAM;eAAS,MAAM;;;GAoB1D,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;kBAYM,MAAM;kBAAY,MAAM;;;GAMvD,CAAC"}
1
+ {"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,IAAI,CAON;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,cAAc,GACrB,IAAI,CAgBN;AAoBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAmB,GAC7B,IAAI,CAON;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1E;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAkCD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;;kBAwDa,MAAM;eAAS,MAAM;;;GAoB1D,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;kBAYM,MAAM;kBAAY,MAAM;;;GAMvD,CAAC"}
@@ -15,6 +15,7 @@ import { getSession } from "../server/auth.js";
15
15
  import { readBody } from "../server/h3-helpers.js";
16
16
  import { deleteAwarenessRow, loadAwarenessRows, upsertAwarenessRow, } from "./awareness-store.js";
17
17
  const AWARENESS_TIMEOUT = 30_000; // 30 seconds
18
+ const CLEAR_TOMBSTONE_TTL = AWARENESS_TIMEOUT + 5_000;
18
19
  // ---------------------------------------------------------------------------
19
20
  // Awareness event emitter — fast-path for push delivery to SSE-connected peers.
20
21
  // The SSE handler (poll-events) subscribes and forwards events to its stream.
@@ -54,6 +55,41 @@ export function emitAwarenessChange(docId, states, scope) {
54
55
  }
55
56
  // docId → Map<clientId, AwarenessEntry>
56
57
  const _awarenessMap = new Map();
58
+ // docId + clientId -> clearedAt. Prevents a stale SQL mirror from resurrecting
59
+ // a participant after an explicit leave/delete raced with the next poll.
60
+ const _awarenessClearTombstones = new Map();
61
+ function awarenessKey(docId, clientId) {
62
+ return `${docId}\0${clientId}`;
63
+ }
64
+ function pruneAwarenessClearTombstones(now) {
65
+ for (const [key, clearedAt] of _awarenessClearTombstones) {
66
+ if (now - clearedAt > CLEAR_TOMBSTONE_TTL) {
67
+ _awarenessClearTombstones.delete(key);
68
+ }
69
+ }
70
+ }
71
+ export function rememberAwarenessClear(docId, clientId, clearedAt = Date.now()) {
72
+ pruneAwarenessClearTombstones(clearedAt);
73
+ const key = awarenessKey(docId, clientId);
74
+ const prev = _awarenessClearTombstones.get(key);
75
+ if (prev == null || clearedAt > prev) {
76
+ _awarenessClearTombstones.set(key, clearedAt);
77
+ }
78
+ }
79
+ export function forgetAwarenessClear(docId, clientId) {
80
+ _awarenessClearTombstones.delete(awarenessKey(docId, clientId));
81
+ }
82
+ function isBlockedByAwarenessClear(docId, clientId, lastSeen, now) {
83
+ pruneAwarenessClearTombstones(now);
84
+ const key = awarenessKey(docId, clientId);
85
+ const clearedAt = _awarenessClearTombstones.get(key);
86
+ if (clearedAt == null)
87
+ return false;
88
+ if (lastSeen <= clearedAt)
89
+ return true;
90
+ _awarenessClearTombstones.delete(key);
91
+ return false;
92
+ }
57
93
  export function getDocAwareness(docId) {
58
94
  let map = _awarenessMap.get(docId);
59
95
  if (!map) {
@@ -84,8 +120,13 @@ function pruneIfEmpty(docId, map) {
84
120
  * is unavailable.
85
121
  */
86
122
  async function mergeStoredAwareness(docId, map) {
87
- const rows = await loadAwarenessRows(docId);
123
+ const now = Date.now();
124
+ const rows = await loadAwarenessRows(docId, now);
88
125
  for (const row of rows) {
126
+ if (isBlockedByAwarenessClear(docId, row.clientId, row.lastSeen, now)) {
127
+ void deleteAwarenessRow(docId, row.clientId, row.lastSeen);
128
+ continue;
129
+ }
89
130
  const existing = map.get(row.clientId);
90
131
  if (!existing || row.lastSeen > existing.lastSeen) {
91
132
  map.set(row.clientId, row);
@@ -118,11 +159,14 @@ export const postAwareness = defineEventHandler(async (event) => {
118
159
  }
119
160
  const map = getDocAwareness(docId);
120
161
  if (state === null) {
162
+ const clearedAt = Date.now();
121
163
  map.delete(clientId);
164
+ rememberAwarenessClear(docId, clientId, clearedAt);
122
165
  // Best-effort cross-instance removal (never blocks the response).
123
- void deleteAwarenessRow(docId, clientId);
166
+ void deleteAwarenessRow(docId, clientId, clearedAt);
124
167
  }
125
168
  else {
169
+ forgetAwarenessClear(docId, clientId);
126
170
  // Store this client's state
127
171
  const entry = { clientId, state, lastSeen: Date.now() };
128
172
  map.set(clientId, entry);