sparkecoder 0.1.110 → 0.1.112

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 (147) hide show
  1. package/dist/agent/index.d.ts +3 -3
  2. package/dist/agent/index.js +10 -0
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +261 -88
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +2 -2
  7. package/dist/{index-Biy5JTop.d.ts → index-Bi8Ek02A.d.ts} +104 -104
  8. package/dist/index.d.ts +5 -5
  9. package/dist/index.js +261 -88
  10. package/dist/index.js.map +1 -1
  11. package/dist/{schema-CYSKJZ3m.d.ts → schema-ecQSnCMz.d.ts} +3 -3
  12. package/dist/{search-CVVfuBPZ.d.ts → search-DOzC4ojH.d.ts} +4 -4
  13. package/dist/server/index.js +261 -88
  14. package/dist/server/index.js.map +1 -1
  15. package/dist/tools/index.d.ts +3 -3
  16. package/dist/tools/index.js.map +1 -1
  17. package/package.json +1 -1
  18. package/web/.next/BUILD_ID +1 -1
  19. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  20. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  21. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  22. package/web/.next/standalone/web/.next/server/app/(main)/agents/page_client-reference-manifest.js +1 -1
  23. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  24. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  25. package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  27. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  42. package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
  43. package/web/.next/standalone/web/.next/server/app/agents.rsc +2 -2
  44. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +2 -2
  48. package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +2 -2
  50. package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +2 -2
  51. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  53. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  54. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  55. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  57. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  58. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  64. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  65. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  66. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  68. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  74. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  75. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  76. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  79. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  82. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  83. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  84. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  85. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  86. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  87. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  88. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  89. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  90. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  91. package/web/.next/standalone/web/.next/server/app/index.rsc +3 -3
  92. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  93. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  94. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +3 -3
  95. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  96. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  97. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  98. package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
  99. package/web/.next/standalone/web/.next/server/app/settings.rsc +3 -3
  100. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +2 -2
  101. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
  102. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
  103. package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  104. package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  105. package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  106. package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  107. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__6097da17._.js +3 -3
  108. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +1 -1
  109. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_settings_page_tsx_eb320e07._.js +1 -31
  110. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  111. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  112. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  113. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  114. package/web/.next/standalone/web/.next/static/chunks/{ae4bb24474ff1ed0.js → a189cacf6d83cf0b.js} +5 -5
  115. package/web/.next/standalone/web/.next/static/chunks/bef6931fdd8428c8.js +1 -0
  116. package/web/.next/standalone/web/.next/static/chunks/c1f73b3fa4353c31.css +1 -0
  117. package/web/.next/standalone/web/.next/static/chunks/c5dd884b71007965.js +1 -0
  118. package/web/.next/standalone/web/.next/static/static/chunks/{ae4bb24474ff1ed0.js → a189cacf6d83cf0b.js} +5 -5
  119. package/web/.next/standalone/web/.next/static/static/chunks/bef6931fdd8428c8.js +1 -0
  120. package/web/.next/standalone/web/.next/static/static/chunks/c1f73b3fa4353c31.css +1 -0
  121. package/web/.next/standalone/web/.next/static/static/chunks/c5dd884b71007965.js +1 -0
  122. package/web/.next/standalone/web/package-lock.json +7 -7
  123. package/web/.next/standalone/web/src/app/(main)/page.tsx +36 -2
  124. package/web/.next/standalone/web/src/app/(main)/settings/page.tsx +76 -35
  125. package/web/.next/standalone/web/src/components/chat-interface.tsx +34 -0
  126. package/web/.next/static/chunks/{ae4bb24474ff1ed0.js → a189cacf6d83cf0b.js} +5 -5
  127. package/web/.next/static/chunks/bef6931fdd8428c8.js +1 -0
  128. package/web/.next/static/chunks/c1f73b3fa4353c31.css +1 -0
  129. package/web/.next/static/chunks/c5dd884b71007965.js +1 -0
  130. package/web/.next/standalone/web/.next/static/chunks/344be859c2c8600b.css +0 -1
  131. package/web/.next/standalone/web/.next/static/chunks/b038e0ff462bfe45.js +0 -31
  132. package/web/.next/standalone/web/.next/static/chunks/f5fe518b79d1bf41.js +0 -1
  133. package/web/.next/standalone/web/.next/static/static/chunks/344be859c2c8600b.css +0 -1
  134. package/web/.next/standalone/web/.next/static/static/chunks/b038e0ff462bfe45.js +0 -31
  135. package/web/.next/standalone/web/.next/static/static/chunks/f5fe518b79d1bf41.js +0 -1
  136. package/web/.next/static/chunks/344be859c2c8600b.css +0 -1
  137. package/web/.next/static/chunks/b038e0ff462bfe45.js +0 -31
  138. package/web/.next/static/chunks/f5fe518b79d1bf41.js +0 -1
  139. /package/web/.next/standalone/web/.next/static/{TSXbqtKMFeFa6H3GGMd86 → static/x3G1ePtJHSb_uWa9Qs8dN}/_buildManifest.js +0 -0
  140. /package/web/.next/standalone/web/.next/static/{TSXbqtKMFeFa6H3GGMd86 → static/x3G1ePtJHSb_uWa9Qs8dN}/_clientMiddlewareManifest.json +0 -0
  141. /package/web/.next/standalone/web/.next/static/{TSXbqtKMFeFa6H3GGMd86 → static/x3G1ePtJHSb_uWa9Qs8dN}/_ssgManifest.js +0 -0
  142. /package/web/.next/standalone/web/.next/static/{static/TSXbqtKMFeFa6H3GGMd86 → x3G1ePtJHSb_uWa9Qs8dN}/_buildManifest.js +0 -0
  143. /package/web/.next/standalone/web/.next/static/{static/TSXbqtKMFeFa6H3GGMd86 → x3G1ePtJHSb_uWa9Qs8dN}/_clientMiddlewareManifest.json +0 -0
  144. /package/web/.next/standalone/web/.next/static/{static/TSXbqtKMFeFa6H3GGMd86 → x3G1ePtJHSb_uWa9Qs8dN}/_ssgManifest.js +0 -0
  145. /package/web/.next/static/{TSXbqtKMFeFa6H3GGMd86 → x3G1ePtJHSb_uWa9Qs8dN}/_buildManifest.js +0 -0
  146. /package/web/.next/static/{TSXbqtKMFeFa6H3GGMd86 → x3G1ePtJHSb_uWa9Qs8dN}/_clientMiddlewareManifest.json +0 -0
  147. /package/web/.next/static/{TSXbqtKMFeFa6H3GGMd86 → x3G1ePtJHSb_uWa9Qs8dN}/_ssgManifest.js +0 -0
@@ -172,8 +172,42 @@ function HomeContent() {
172
172
  return () => window.removeEventListener('keydown', handleKeyDown);
173
173
  }, [creating, sessions.length, config]);
174
174
 
175
- // Show API key setup if not configured
176
- if (config && !config.apiKeyConfigured) {
175
+ // Distinguish three states:
176
+ // 1. config is null → still loading (handled below the early returns)
177
+ // 2. !serverConnected → API unreachable (wrong apiUrl, server down, etc).
178
+ // 3. serverConnected && !apiKeyConfigured → local AI key needed.
179
+ //
180
+ // In remote-inference mode the server reports apiKeyConfigured=true even
181
+ // without a local Gateway key (the remote-server has the key), so the
182
+ // prompt should NOT show. The original check (`!apiKeyConfigured`) was
183
+ // correct for that, but treated state #2 (unreachable) identically to #3.
184
+ if (config && !config.serverConnected) {
185
+ return (
186
+ <div className="flex items-center justify-center h-full bg-gradient-to-b from-background to-muted/20">
187
+ <div className="text-center max-w-lg mx-auto p-8">
188
+ <div className="flex justify-center mb-8">
189
+ <div className="relative size-28 rounded-2xl overflow-hidden shadow-xl ring-1 ring-white/10">
190
+ <Image src="/sparke-coder.png" alt="Sparke" fill className="object-cover" priority />
191
+ </div>
192
+ </div>
193
+ <h1 className="text-3xl font-bold mb-3 text-primary">SparkECoder API unreachable</h1>
194
+ <div className="bg-muted/50 rounded-lg p-6 mb-6 text-left">
195
+ <p className="text-sm text-muted-foreground mb-2">
196
+ The browser couldn&apos;t reach the SparkECoder API at <code className="px-1 py-0.5 rounded bg-muted text-[11px] break-all">{config.apiBaseUrl}</code>.
197
+ </p>
198
+ <p className="text-sm text-muted-foreground mb-2">Common causes:</p>
199
+ <ul className="text-sm text-muted-foreground list-disc pl-5 space-y-1">
200
+ <li>The auto-detected URL is wrong for this hostname. Append <code className="px-1 py-0.5 rounded bg-muted text-[11px]">?apiUrl=https://your-api-host</code> to the page URL to override (saves to localStorage).</li>
201
+ <li>Cloudflared tunnel down or DNS not yet propagated.</li>
202
+ <li>Sparkecoder service stopped — run <code className="px-1 py-0.5 rounded bg-muted text-[11px]">launchctl list | grep sparkecoder</code>.</li>
203
+ </ul>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ );
208
+ }
209
+ // Show API key setup only when server is connected but no AI key is configured.
210
+ if (config && config.serverConnected && !config.apiKeyConfigured) {
177
211
  return (
178
212
  <div className="flex items-center justify-center h-full bg-gradient-to-b from-background to-muted/20">
179
213
  <div className="text-center max-w-lg mx-auto p-8">
@@ -368,6 +368,7 @@ function SlackCard({ data, onChange }: { data: IntegrationsResponse; onChange: (
368
368
  {showSetup && (
369
369
  <SlackSetupModal
370
370
  eventsUrl={data.slack.eventsUrl}
371
+ agentName={data.slack.defaultOrchestratorName || 'orchestrator'}
371
372
  onClose={() => setShowSetup(false)}
372
373
  />
373
374
  )}
@@ -491,9 +492,9 @@ function SlackAllowlistEditor({ data, onChange }: { data: IntegrationsResponse;
491
492
  );
492
493
  }
493
494
 
494
- function SlackSetupModal({ eventsUrl, onClose }: { eventsUrl: string; onClose: () => void }) {
495
+ function SlackSetupModal({ eventsUrl, agentName, onClose }: { eventsUrl: string; agentName: string; onClose: () => void }) {
495
496
  const [copied, setCopied] = useState<string | null>(null);
496
- const manifest = buildSlackManifest(eventsUrl);
497
+ const manifest = buildSlackManifest(eventsUrl, agentName);
497
498
  const copy = async (key: string, text: string) => {
498
499
  await navigator.clipboard.writeText(text);
499
500
  setCopied(key); setTimeout(() => setCopied(null), 1500);
@@ -566,39 +567,79 @@ function CopyBlock({ label, value, onCopy, copied, mono }: { label: string; valu
566
567
  );
567
568
  }
568
569
 
569
- function buildSlackManifest(eventsUrl: string): string {
570
- // Trim trailing whitespace; Slack rejects manifests with weird indents.
571
- return `display_information:
572
- name: sparkecoder
573
- description: Your orchestrator agent. Spawns worker agents, posts updates, answers questions.
574
- background_color: "#1f1f24"
575
- features:
576
- bot_user:
577
- display_name: sparkecoder
578
- always_online: true
579
- oauth_config:
580
- scopes:
581
- bot:
582
- - app_mentions:read
583
- - channels:history
584
- - chat:write
585
- - groups:history
586
- - im:history
587
- - im:read
588
- - im:write
589
- - users:read
590
- settings:
591
- event_subscriptions:
592
- request_url: ${eventsUrl}
593
- bot_events:
594
- - app_mention
595
- - message.im
596
- interactivity:
597
- is_enabled: false
598
- org_deploy_enabled: false
599
- socket_mode_enabled: false
600
- token_rotation_enabled: false
601
- `;
570
+ function buildSlackManifest(eventsUrl: string, agentName: string): string {
571
+ // Slack constraints:
572
+ // display_information.name: 1-35 chars, used as the app's
573
+ // listing name. Visible spaces OK
574
+ // but we keep it safe / app-store-ish.
575
+ // bot_user.display_name: 1-80 chars, what users @-mention.
576
+ // Spaces NOT allowed; Slack rejects.
577
+ // We slug-ify the user-provided agent name for both, preserving case
578
+ // for display_information.name (humans see it) but stripping spaces
579
+ // / punctuation from bot_user.display_name (Slack rule).
580
+ const safe = (agentName || 'orchestrator').trim() || 'orchestrator';
581
+ const displayName = safe.slice(0, 35);
582
+ const botName = safe
583
+ .replace(/[^A-Za-z0-9_-]+/g, '-') // Slack: alphanumerics, _ and - only
584
+ .replace(/^-+|-+$/g, '')
585
+ .slice(0, 80) || 'orchestrator';
586
+
587
+ // JSON manifest. Slack accepts both YAML and JSON, but JSON parses
588
+ // more reliably across browser clipboards (no whitespace surprises).
589
+ const manifest = {
590
+ display_information: {
591
+ name: displayName,
592
+ description: `${displayName} — your orchestrator agent. Spawns worker agents, posts updates, answers questions.`,
593
+ background_color: '#1f1f24',
594
+ },
595
+ features: {
596
+ bot_user: {
597
+ display_name: botName,
598
+ always_online: true,
599
+ },
600
+ // Required for users to DM the app. Without these, Slack disables
601
+ // the message composer and shows "Sending messages to this app
602
+ // has been turned off".
603
+ app_home: {
604
+ home_tab_enabled: false,
605
+ messages_tab_enabled: true,
606
+ messages_tab_read_only_enabled: false,
607
+ },
608
+ },
609
+ oauth_config: {
610
+ scopes: {
611
+ bot: [
612
+ 'app_mentions:read',
613
+ 'channels:history',
614
+ 'chat:write',
615
+ 'groups:history',
616
+ 'im:history',
617
+ 'im:read',
618
+ 'im:write',
619
+ 'users:read',
620
+ ],
621
+ },
622
+ },
623
+ settings: {
624
+ event_subscriptions: {
625
+ request_url: eventsUrl,
626
+ // app_mention → user @-pings the bot in a channel
627
+ // message.im → user DMs the bot (every message in DM is a reply)
628
+ // message.channels → message in a public channel the bot is in.
629
+ // Used so thread replies (no re-mention needed)
630
+ // keep reaching the bot. Server-side filters
631
+ // these to ONLY messages with thread_ts that
632
+ // match a session the bot is participating in.
633
+ // message.groups → same, but for private channels.
634
+ bot_events: ['app_mention', 'message.im', 'message.channels', 'message.groups'],
635
+ },
636
+ interactivity: { is_enabled: false },
637
+ org_deploy_enabled: false,
638
+ socket_mode_enabled: false,
639
+ token_rotation_enabled: false,
640
+ },
641
+ };
642
+ return JSON.stringify(manifest, null, 2);
602
643
  }
603
644
 
604
645
  function CloudflaredCard({ data }: { data: IntegrationsResponse }) {
@@ -1724,6 +1724,40 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
1724
1724
  });
1725
1725
  cancelRef.current = cancel;
1726
1726
  }
1727
+
1728
+ // Also subscribe to coarse-grained session updates (Slack /
1729
+ // inbox / schedule-triggered daemon turns). These don't go
1730
+ // through the resumable-stream pipeline above, so without this
1731
+ // the UI would only see new messages after a refresh.
1732
+ try {
1733
+ const { getApiUrl } = await import('@/lib/config');
1734
+ const updatesUrl = `${getApiUrl()}/sessions/${session.id}/updates`;
1735
+ const es = new EventSource(updatesUrl);
1736
+ es.onmessage = async (msg) => {
1737
+ try {
1738
+ const payload = JSON.parse(msg.data) as { type: string };
1739
+ if (payload.type === 'turn-complete') {
1740
+ const fresh = await getSessionMessages(session.id);
1741
+ if (isStale) return;
1742
+ const sortedFresh = [...fresh].sort((a, b) =>
1743
+ new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
1744
+ );
1745
+ setChatItems(convertApiMessages(sortedFresh));
1746
+ }
1747
+ } catch { /* malformed event — ignore */ }
1748
+ };
1749
+ es.onerror = () => {
1750
+ // Browser will auto-reconnect; nothing to do here.
1751
+ };
1752
+ // Cancel on session change / unmount.
1753
+ const prevCancel = cancelRef.current;
1754
+ cancelRef.current = () => {
1755
+ prevCancel?.();
1756
+ es.close();
1757
+ };
1758
+ } catch (err) {
1759
+ console.warn('Failed to open session-updates EventSource:', err);
1760
+ }
1727
1761
  } catch (err) {
1728
1762
  if (!isStale) {
1729
1763
  console.error('Failed to load messages:', err);