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
package/dist/cli.js CHANGED
@@ -1340,9 +1340,10 @@ function createDefaultConfig() {
1340
1340
  }
1341
1341
  function loadStoredAuthKey() {
1342
1342
  const locations = [
1343
+ process.env.SPARKECODER_AUTH_KEY_PATH,
1343
1344
  join(process.cwd(), ".sparkecoder", AUTH_KEY_FILE),
1344
1345
  join(getAppDataDirectory(), AUTH_KEY_FILE)
1345
- ];
1346
+ ].filter((p) => !!p);
1346
1347
  for (const keysPath of locations) {
1347
1348
  if (!existsSync(keysPath)) continue;
1348
1349
  try {
@@ -1361,15 +1362,44 @@ function saveAuthKey(authKey3, userId) {
1361
1362
  userId
1362
1363
  };
1363
1364
  const json = JSON.stringify(data, null, 2);
1364
- const appDir = ensureAppDataDirectory();
1365
- writeFileSync(join(appDir, AUTH_KEY_FILE), json, { mode: 384 });
1365
+ const targets = [];
1366
+ if (process.env.SPARKECODER_AUTH_KEY_PATH) {
1367
+ targets.push({
1368
+ label: "SPARKECODER_AUTH_KEY_PATH",
1369
+ path: process.env.SPARKECODER_AUTH_KEY_PATH
1370
+ });
1371
+ }
1366
1372
  try {
1367
- const workspaceAuthDir = join(process.cwd(), ".sparkecoder");
1368
- if (!existsSync(workspaceAuthDir)) {
1369
- mkdirSync(workspaceAuthDir, { recursive: true });
1373
+ const appDir = ensureAppDataDirectory();
1374
+ targets.push({ label: "app-data", path: join(appDir, AUTH_KEY_FILE) });
1375
+ } catch (err) {
1376
+ console.warn(`[auth-key] could not ensure app data dir: ${err?.message ?? err}`);
1377
+ }
1378
+ targets.push({
1379
+ label: "workspace",
1380
+ path: join(process.cwd(), ".sparkecoder", AUTH_KEY_FILE)
1381
+ });
1382
+ const successes = [];
1383
+ const failures = [];
1384
+ for (const { label, path } of targets) {
1385
+ try {
1386
+ mkdirSync(dirname(path), { recursive: true });
1387
+ writeFileSync(path, json, { mode: 384 });
1388
+ successes.push(`${label}:${path}`);
1389
+ } catch (err) {
1390
+ failures.push({ label, path, error: err?.message ?? String(err) });
1370
1391
  }
1371
- writeFileSync(join(workspaceAuthDir, AUTH_KEY_FILE), json, { mode: 384 });
1372
- } catch {
1392
+ }
1393
+ if (successes.length === 0) {
1394
+ const detail = failures.map((f) => `${f.label} (${f.path}): ${f.error}`).join("; ");
1395
+ throw new Error(`Failed to persist auth-key.json to any location. ${detail}`);
1396
+ }
1397
+ if (failures.length > 0) {
1398
+ console.warn(
1399
+ `[auth-key] wrote to ${successes.length}/${targets.length} locations. Success: ${successes.join(", ")}. Failed: ${failures.map((f) => `${f.label} (${f.path}): ${f.error}`).join("; ")}`
1400
+ );
1401
+ } else if (process.env.SPARKECODER_VERBOSE_CONFIG) {
1402
+ console.log(`[auth-key] saved to: ${successes.join(", ")}`);
1373
1403
  }
1374
1404
  }
1375
1405
  function getStoredAuthKeyInfo() {
@@ -9033,15 +9063,30 @@ var init_client3 = __esm({
9033
9063
  });
9034
9064
 
9035
9065
  // src/integrations/channels/slack.ts
9066
+ function threadKey(channel, threadTs) {
9067
+ return `${channel}\u241F${threadTs}`;
9068
+ }
9069
+ function markThreadOwned(channel, threadTs) {
9070
+ ownedThreads.add(threadKey(channel, threadTs));
9071
+ }
9072
+ function isThreadOwned(channel, threadTs) {
9073
+ return ownedThreads.has(threadKey(channel, threadTs));
9074
+ }
9036
9075
  function stripMention(text) {
9037
9076
  return String(text || "").replace(/<@[^>]+>/g, "").trim();
9038
9077
  }
9039
9078
  function slackEventToInboundResult(event) {
9040
9079
  if (!event) return { event: null, dropReason: "empty_text" };
9041
- if (event.bot_id || event.subtype === "bot_message") return { event: null, dropReason: "bot_message" };
9080
+ if (event.bot_id) return { event: null, dropReason: "bot_message" };
9081
+ if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
9082
+ return { event: null, dropReason: "bot_message" };
9083
+ }
9042
9084
  const isDm = event.type === "message" && event.channel_type === "im";
9043
- if (event.type !== "app_mention" && !isDm) return { event: null, dropReason: "unsupported_type" };
9044
- const text = stripMention(event.text);
9085
+ const isThreadReply = event.type === "message" && !isDm && typeof event.thread_ts === "string" && event.thread_ts !== event.ts;
9086
+ if (event.type !== "app_mention" && !isDm && !isThreadReply) {
9087
+ return { event: null, dropReason: "unsupported_type" };
9088
+ }
9089
+ const text = event.type === "app_mention" ? stripMention(event.text) : (event.text ?? "").trim();
9045
9090
  if (!text) return { event: null, dropReason: "empty_text" };
9046
9091
  const policy = getSlackAllowlistPolicy();
9047
9092
  const userAllowlistActive = policy.allowedUsers.length > 0;
@@ -9076,11 +9121,12 @@ function slackEventToInboundResult(event) {
9076
9121
  }
9077
9122
  };
9078
9123
  }
9079
- var slackChannel;
9124
+ var ownedThreads, slackChannel, IGNORED_MESSAGE_SUBTYPES;
9080
9125
  var init_slack = __esm({
9081
9126
  "src/integrations/channels/slack.ts"() {
9082
9127
  "use strict";
9083
9128
  init_client3();
9129
+ ownedThreads = /* @__PURE__ */ new Set();
9084
9130
  slackChannel = {
9085
9131
  id: "slack",
9086
9132
  canSend: () => isSlackConfigured(),
@@ -9094,6 +9140,9 @@ var init_slack = __esm({
9094
9140
  threadTs: r.threadTs
9095
9141
  });
9096
9142
  if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
9143
+ if (r.slackChannel && r.threadTs) {
9144
+ markThreadOwned(r.slackChannel, r.threadTs);
9145
+ }
9097
9146
  },
9098
9147
  displayLabel(ref) {
9099
9148
  const r = ref;
@@ -9104,6 +9153,29 @@ var init_slack = __esm({
9104
9153
  return parts.join(" ");
9105
9154
  }
9106
9155
  };
9156
+ IGNORED_MESSAGE_SUBTYPES = /* @__PURE__ */ new Set([
9157
+ "bot_message",
9158
+ "message_changed",
9159
+ "message_deleted",
9160
+ "channel_join",
9161
+ "channel_leave",
9162
+ "channel_topic",
9163
+ "channel_purpose",
9164
+ "channel_name",
9165
+ "channel_archive",
9166
+ "channel_unarchive",
9167
+ "pinned_item",
9168
+ "unpinned_item",
9169
+ "thread_broadcast",
9170
+ // also-broadcast-to-channel replies; the regular thread reply already fires
9171
+ "message_replied",
9172
+ // legacy parent-thread bump
9173
+ "file_share",
9174
+ // we'd handle these later; for now skip to avoid double-handling
9175
+ "reply_broadcast",
9176
+ "tombstone",
9177
+ "huddle_thread"
9178
+ ]);
9107
9179
  }
9108
9180
  });
9109
9181
 
@@ -11405,6 +11477,81 @@ var init_session_lock = __esm({
11405
11477
  }
11406
11478
  });
11407
11479
 
11480
+ // src/orchestrator/daemon.ts
11481
+ var daemon_exports = {};
11482
+ __export(daemon_exports, {
11483
+ startOrchestratorDaemon: () => startOrchestratorDaemon,
11484
+ subscribeToDaemonOutput: () => subscribeToDaemonOutput
11485
+ });
11486
+ function subscribeToDaemonOutput(sessionId, fn) {
11487
+ let set = listeners.get(sessionId);
11488
+ if (!set) {
11489
+ set = /* @__PURE__ */ new Set();
11490
+ listeners.set(sessionId, set);
11491
+ }
11492
+ set.add(fn);
11493
+ return () => {
11494
+ const s = listeners.get(sessionId);
11495
+ if (!s) return;
11496
+ s.delete(fn);
11497
+ if (s.size === 0) listeners.delete(sessionId);
11498
+ };
11499
+ }
11500
+ function broadcast(out) {
11501
+ const set = listeners.get(out.sessionId);
11502
+ if (!set) return;
11503
+ for (const fn of set) {
11504
+ try {
11505
+ fn(out);
11506
+ } catch (err) {
11507
+ console.error("[daemon] listener threw:", err?.message || err);
11508
+ }
11509
+ }
11510
+ }
11511
+ function startOrchestratorDaemon() {
11512
+ setFlushHandler(async (sessionId, events) => {
11513
+ await runDaemonTurn(sessionId, events);
11514
+ });
11515
+ }
11516
+ async function runDaemonTurn(sessionId, events) {
11517
+ const startedAt = /* @__PURE__ */ new Date();
11518
+ const session = await sessionQueries.getById(sessionId).catch(() => void 0);
11519
+ if (!session) {
11520
+ console.warn(`[daemon] flush for unknown session ${sessionId}; dropping ${events.length} event(s)`);
11521
+ return;
11522
+ }
11523
+ const prompt = events.map((e) => e.content).join("\n\n");
11524
+ let text = "";
11525
+ let error;
11526
+ await withSessionLock(sessionId, async () => {
11527
+ try {
11528
+ const agent = await Agent.create({ sessionId });
11529
+ const result = await agent.stream({ prompt });
11530
+ for await (const part of result.stream.fullStream) {
11531
+ if (part.type === "text-delta") text += part.text || "";
11532
+ }
11533
+ await result.saveResponseMessages();
11534
+ } catch (err) {
11535
+ error = err?.message || String(err);
11536
+ console.error(`[daemon] turn failed for ${sessionId}:`, error);
11537
+ }
11538
+ });
11539
+ const finishedAt = /* @__PURE__ */ new Date();
11540
+ const trimmed = text.trim();
11541
+ broadcast({ sessionId, text: trimmed, triggeredBy: events, startedAt, finishedAt, error });
11542
+ }
11543
+ var listeners;
11544
+ var init_daemon = __esm({
11545
+ "src/orchestrator/daemon.ts"() {
11546
+ "use strict";
11547
+ init_agent();
11548
+ init_session_lock();
11549
+ init_db();
11550
+ init_inbox();
11551
+ listeners = /* @__PURE__ */ new Map();
11552
+ }
11553
+ });
11554
+
11408
11555
  // src/tasks/boot-recovery.ts
11409
11556
  var boot_recovery_exports = {};
11410
11557
  __export(boot_recovery_exports, {
@@ -11492,81 +11639,6 @@ var init_ensure_orchestrator = __esm({
11492
11639
  }
11493
11640
  });
11494
11641
 
11495
- // src/orchestrator/daemon.ts
11496
- var daemon_exports = {};
11497
- __export(daemon_exports, {
11498
- startOrchestratorDaemon: () => startOrchestratorDaemon,
11499
- subscribeToDaemonOutput: () => subscribeToDaemonOutput
11500
- });
11501
- function subscribeToDaemonOutput(sessionId, fn) {
11502
- let set = listeners.get(sessionId);
11503
- if (!set) {
11504
- set = /* @__PURE__ */ new Set();
11505
- listeners.set(sessionId, set);
11506
- }
11507
- set.add(fn);
11508
- return () => {
11509
- const s = listeners.get(sessionId);
11510
- if (!s) return;
11511
- s.delete(fn);
11512
- if (s.size === 0) listeners.delete(sessionId);
11513
- };
11514
- }
11515
- function broadcast(out) {
11516
- const set = listeners.get(out.sessionId);
11517
- if (!set) return;
11518
- for (const fn of set) {
11519
- try {
11520
- fn(out);
11521
- } catch (err) {
11522
- console.error("[daemon] listener threw:", err?.message || err);
11523
- }
11524
- }
11525
- }
11526
- function startOrchestratorDaemon() {
11527
- setFlushHandler(async (sessionId, events) => {
11528
- await runDaemonTurn(sessionId, events);
11529
- });
11530
- }
11531
- async function runDaemonTurn(sessionId, events) {
11532
- const startedAt = /* @__PURE__ */ new Date();
11533
- const session = await sessionQueries.getById(sessionId).catch(() => void 0);
11534
- if (!session) {
11535
- console.warn(`[daemon] flush for unknown session ${sessionId}; dropping ${events.length} event(s)`);
11536
- return;
11537
- }
11538
- const prompt = events.map((e) => e.content).join("\n\n");
11539
- let text = "";
11540
- let error;
11541
- await withSessionLock(sessionId, async () => {
11542
- try {
11543
- const agent = await Agent.create({ sessionId });
11544
- const result = await agent.stream({ prompt });
11545
- for await (const part of result.stream.fullStream) {
11546
- if (part.type === "text-delta") text += part.text || "";
11547
- }
11548
- await result.saveResponseMessages();
11549
- } catch (err) {
11550
- error = err?.message || String(err);
11551
- console.error(`[daemon] turn failed for ${sessionId}:`, error);
11552
- }
11553
- });
11554
- const finishedAt = /* @__PURE__ */ new Date();
11555
- const trimmed = text.trim();
11556
- broadcast({ sessionId, text: trimmed, triggeredBy: events, startedAt, finishedAt, error });
11557
- }
11558
- var listeners;
11559
- var init_daemon = __esm({
11560
- "src/orchestrator/daemon.ts"() {
11561
- "use strict";
11562
- init_agent();
11563
- init_session_lock();
11564
- init_db();
11565
- init_inbox();
11566
- listeners = /* @__PURE__ */ new Map();
11567
- }
11568
- });
11569
-
11570
11642
  // src/tasks/scheduler.ts
11571
11643
  var scheduler_exports = {};
11572
11644
  __export(scheduler_exports, {
@@ -11781,6 +11853,7 @@ function deriveAgentStatus(input) {
11781
11853
 
11782
11854
  // src/server/routes/sessions.ts
11783
11855
  init_pending_input();
11856
+ init_daemon();
11784
11857
 
11785
11858
  // src/server/devtools-store.ts
11786
11859
  var devtoolsContextStore = /* @__PURE__ */ new Map();
@@ -11923,6 +11996,63 @@ sessions2.post(
11923
11996
  }, 201);
11924
11997
  }
11925
11998
  );
11999
+ sessions2.get("/:id/updates", async (c) => {
12000
+ const id = c.req.param("id");
12001
+ const session = await sessionQueries.getById(id);
12002
+ if (!session) return c.json({ error: "Session not found" }, 404);
12003
+ const stream = new ReadableStream({
12004
+ start(controller) {
12005
+ const encoder = new TextEncoder();
12006
+ const send = (data) => {
12007
+ try {
12008
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
12009
+
12010
+ `));
12011
+ } catch {
12012
+ }
12013
+ };
12014
+ send({ type: "ready", sessionId: id });
12015
+ const unsubscribe = subscribeToDaemonOutput(id, (output) => {
12016
+ send({
12017
+ type: "turn-complete",
12018
+ sessionId: id,
12019
+ finishedAt: output.finishedAt.toISOString(),
12020
+ triggeredBy: output.triggeredBy.map((e) => e.content?.slice(0, 80)),
12021
+ error: output.error ?? null
12022
+ });
12023
+ });
12024
+ let cleaned = false;
12025
+ const cleanup2 = () => {
12026
+ if (cleaned) return;
12027
+ cleaned = true;
12028
+ clearInterval(keepalive);
12029
+ unsubscribe();
12030
+ try {
12031
+ controller.close();
12032
+ } catch {
12033
+ }
12034
+ };
12035
+ const keepalive = setInterval(() => {
12036
+ try {
12037
+ controller.enqueue(encoder.encode(`: keepalive
12038
+
12039
+ `));
12040
+ } catch {
12041
+ cleanup2();
12042
+ }
12043
+ }, 25e3);
12044
+ c.req.raw.signal?.addEventListener("abort", cleanup2);
12045
+ }
12046
+ });
12047
+ return new Response(stream, {
12048
+ headers: {
12049
+ "Content-Type": "text/event-stream",
12050
+ "Cache-Control": "no-cache",
12051
+ Connection: "keep-alive",
12052
+ "X-Accel-Buffering": "no"
12053
+ }
12054
+ });
12055
+ });
11926
12056
  sessions2.get("/:id", async (c) => {
11927
12057
  const id = c.req.param("id");
11928
12058
  const session = await sessionQueries.getById(id);
@@ -14670,6 +14800,19 @@ function verifySlackSignature(opts) {
14670
14800
  init_client3();
14671
14801
  init_slack();
14672
14802
  init_inbox();
14803
+ var recentlyHandled = /* @__PURE__ */ new Map();
14804
+ var MAX_RECENT = 1e3;
14805
+ function alreadyHandled(channel, ts) {
14806
+ if (!channel || !ts) return false;
14807
+ const key2 = `${channel}\u241F${ts}`;
14808
+ if (recentlyHandled.has(key2)) return true;
14809
+ recentlyHandled.set(key2, Date.now());
14810
+ if (recentlyHandled.size > MAX_RECENT) {
14811
+ const oldest = recentlyHandled.keys().next().value;
14812
+ if (oldest) recentlyHandled.delete(oldest);
14813
+ }
14814
+ return false;
14815
+ }
14673
14816
  var slack = new Hono6();
14674
14817
  slack.post("/events", async (c) => {
14675
14818
  const signingSecret = getSlackSigningSecret();
@@ -14695,8 +14838,26 @@ slack.post("/events", async (c) => {
14695
14838
  return c.json({ challenge: payload.challenge });
14696
14839
  }
14697
14840
  if (payload?.type === "event_callback" && payload?.event) {
14698
- const { event: inbound, dropReason } = slackEventToInboundResult(payload.event);
14841
+ const ev = payload.event;
14842
+ if (alreadyHandled(ev.channel, ev.ts)) {
14843
+ return c.json({ ok: true });
14844
+ }
14845
+ const { event: inbound, dropReason } = slackEventToInboundResult(ev);
14699
14846
  if (inbound) {
14847
+ const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
14848
+ if (isThreadReply) {
14849
+ const ours = isThreadOwned(ev.channel, ev.thread_ts) || await threadBelongsToUs(ev.channel, ev.thread_ts);
14850
+ if (!ours) {
14851
+ console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
14852
+ return c.json({ ok: true });
14853
+ }
14854
+ }
14855
+ if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
14856
+ markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
14857
+ }
14858
+ if (ev.type === "message" && ev.channel_type === "im" && ev.channel && (ev.thread_ts || ev.ts)) {
14859
+ markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
14860
+ }
14700
14861
  const orchestratorId = await findOrCreateOrchestratorId();
14701
14862
  if (orchestratorId) {
14702
14863
  pushToInbox(orchestratorId, inbound);
@@ -14713,6 +14874,18 @@ slack.post("/events", async (c) => {
14713
14874
  }
14714
14875
  return c.json({ ok: true });
14715
14876
  });
14877
+ async function threadBelongsToUs(channel, threadTs) {
14878
+ try {
14879
+ const sessions3 = await sessionQueries.list(500, 0);
14880
+ return sessions3.some((s) => {
14881
+ const slack2 = s.config?.slack;
14882
+ return slack2?.channel === channel && slack2?.threadTs === threadTs;
14883
+ });
14884
+ } catch (err) {
14885
+ console.warn("[slack] threadBelongsToUs lookup failed:", err?.message ?? err);
14886
+ return false;
14887
+ }
14888
+ }
14716
14889
  async function findOrCreateOrchestratorId() {
14717
14890
  try {
14718
14891
  const all = await sessionQueries.list(500, 0);