sparkecoder 0.1.111 → 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 (139) hide show
  1. package/dist/agent/index.js +10 -0
  2. package/dist/agent/index.js.map +1 -1
  3. package/dist/cli.js +261 -88
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.js +261 -88
  6. package/dist/index.js.map +1 -1
  7. package/dist/server/index.js +261 -88
  8. package/dist/server/index.js.map +1 -1
  9. package/dist/tools/index.js.map +1 -1
  10. package/package.json +1 -1
  11. package/web/.next/BUILD_ID +1 -1
  12. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  13. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  14. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  15. package/web/.next/standalone/web/.next/server/app/(main)/agents/page_client-reference-manifest.js +1 -1
  16. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  17. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  18. package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +1 -1
  19. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  20. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  21. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  22. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  23. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  25. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  29. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  30. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  32. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  35. package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
  36. package/web/.next/standalone/web/.next/server/app/agents.rsc +2 -2
  37. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +2 -2
  41. package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +2 -2
  43. package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +2 -2
  44. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  51. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  57. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  58. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  61. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  62. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  67. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  71. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  72. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  76. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  77. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  80. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  81. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  82. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  84. package/web/.next/standalone/web/.next/server/app/index.rsc +3 -3
  85. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  86. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  87. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +3 -3
  88. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  89. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  90. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  91. package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
  92. package/web/.next/standalone/web/.next/server/app/settings.rsc +3 -3
  93. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +2 -2
  94. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
  95. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
  96. package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  97. package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  98. package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  99. package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  100. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__6097da17._.js +3 -3
  101. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +1 -1
  102. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_settings_page_tsx_eb320e07._.js +1 -1
  103. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  104. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  105. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  106. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  107. package/web/.next/standalone/web/.next/static/chunks/{ae4bb24474ff1ed0.js → a189cacf6d83cf0b.js} +5 -5
  108. package/web/.next/standalone/web/.next/static/chunks/bef6931fdd8428c8.js +1 -0
  109. package/web/.next/standalone/web/.next/static/chunks/c1f73b3fa4353c31.css +1 -0
  110. package/web/.next/standalone/web/.next/static/chunks/c5dd884b71007965.js +1 -0
  111. package/web/.next/standalone/web/.next/static/static/chunks/{ae4bb24474ff1ed0.js → a189cacf6d83cf0b.js} +5 -5
  112. package/web/.next/standalone/web/.next/static/static/chunks/bef6931fdd8428c8.js +1 -0
  113. package/web/.next/standalone/web/.next/static/static/chunks/c1f73b3fa4353c31.css +1 -0
  114. package/web/.next/standalone/web/.next/static/static/chunks/c5dd884b71007965.js +1 -0
  115. package/web/.next/standalone/web/src/app/(main)/page.tsx +36 -2
  116. package/web/.next/standalone/web/src/app/(main)/settings/page.tsx +40 -7
  117. package/web/.next/standalone/web/src/components/chat-interface.tsx +34 -0
  118. package/web/.next/static/chunks/{ae4bb24474ff1ed0.js → a189cacf6d83cf0b.js} +5 -5
  119. package/web/.next/static/chunks/bef6931fdd8428c8.js +1 -0
  120. package/web/.next/static/chunks/c1f73b3fa4353c31.css +1 -0
  121. package/web/.next/static/chunks/c5dd884b71007965.js +1 -0
  122. package/web/.next/standalone/web/.next/static/chunks/344be859c2c8600b.css +0 -1
  123. package/web/.next/standalone/web/.next/static/chunks/f5fe518b79d1bf41.js +0 -1
  124. package/web/.next/standalone/web/.next/static/chunks/f6e2bbd3014e1fc9.js +0 -1
  125. package/web/.next/standalone/web/.next/static/static/chunks/344be859c2c8600b.css +0 -1
  126. package/web/.next/standalone/web/.next/static/static/chunks/f5fe518b79d1bf41.js +0 -1
  127. package/web/.next/standalone/web/.next/static/static/chunks/f6e2bbd3014e1fc9.js +0 -1
  128. package/web/.next/static/chunks/344be859c2c8600b.css +0 -1
  129. package/web/.next/static/chunks/f5fe518b79d1bf41.js +0 -1
  130. package/web/.next/static/chunks/f6e2bbd3014e1fc9.js +0 -1
  131. /package/web/.next/standalone/web/.next/static/{Tm_6r8b-tCAfCBoIt-U0X → static/x3G1ePtJHSb_uWa9Qs8dN}/_buildManifest.js +0 -0
  132. /package/web/.next/standalone/web/.next/static/{Tm_6r8b-tCAfCBoIt-U0X → static/x3G1ePtJHSb_uWa9Qs8dN}/_clientMiddlewareManifest.json +0 -0
  133. /package/web/.next/standalone/web/.next/static/{Tm_6r8b-tCAfCBoIt-U0X → static/x3G1ePtJHSb_uWa9Qs8dN}/_ssgManifest.js +0 -0
  134. /package/web/.next/standalone/web/.next/static/{static/Tm_6r8b-tCAfCBoIt-U0X → x3G1ePtJHSb_uWa9Qs8dN}/_buildManifest.js +0 -0
  135. /package/web/.next/standalone/web/.next/static/{static/Tm_6r8b-tCAfCBoIt-U0X → x3G1ePtJHSb_uWa9Qs8dN}/_clientMiddlewareManifest.json +0 -0
  136. /package/web/.next/standalone/web/.next/static/{static/Tm_6r8b-tCAfCBoIt-U0X → x3G1ePtJHSb_uWa9Qs8dN}/_ssgManifest.js +0 -0
  137. /package/web/.next/static/{Tm_6r8b-tCAfCBoIt-U0X → x3G1ePtJHSb_uWa9Qs8dN}/_buildManifest.js +0 -0
  138. /package/web/.next/static/{Tm_6r8b-tCAfCBoIt-U0X → x3G1ePtJHSb_uWa9Qs8dN}/_clientMiddlewareManifest.json +0 -0
  139. /package/web/.next/static/{Tm_6r8b-tCAfCBoIt-U0X → x3G1ePtJHSb_uWa9Qs8dN}/_ssgManifest.js +0 -0
@@ -1339,9 +1339,10 @@ function createDefaultConfig() {
1339
1339
  }
1340
1340
  function loadStoredAuthKey() {
1341
1341
  const locations = [
1342
+ process.env.SPARKECODER_AUTH_KEY_PATH,
1342
1343
  join(process.cwd(), ".sparkecoder", AUTH_KEY_FILE),
1343
1344
  join(getAppDataDirectory(), AUTH_KEY_FILE)
1344
- ];
1345
+ ].filter((p) => !!p);
1345
1346
  for (const keysPath of locations) {
1346
1347
  if (!existsSync(keysPath)) continue;
1347
1348
  try {
@@ -1360,15 +1361,44 @@ function saveAuthKey(authKey3, userId) {
1360
1361
  userId
1361
1362
  };
1362
1363
  const json = JSON.stringify(data, null, 2);
1363
- const appDir = ensureAppDataDirectory();
1364
- writeFileSync(join(appDir, AUTH_KEY_FILE), json, { mode: 384 });
1364
+ const targets = [];
1365
+ if (process.env.SPARKECODER_AUTH_KEY_PATH) {
1366
+ targets.push({
1367
+ label: "SPARKECODER_AUTH_KEY_PATH",
1368
+ path: process.env.SPARKECODER_AUTH_KEY_PATH
1369
+ });
1370
+ }
1365
1371
  try {
1366
- const workspaceAuthDir = join(process.cwd(), ".sparkecoder");
1367
- if (!existsSync(workspaceAuthDir)) {
1368
- mkdirSync(workspaceAuthDir, { recursive: true });
1372
+ const appDir = ensureAppDataDirectory();
1373
+ targets.push({ label: "app-data", path: join(appDir, AUTH_KEY_FILE) });
1374
+ } catch (err) {
1375
+ console.warn(`[auth-key] could not ensure app data dir: ${err?.message ?? err}`);
1376
+ }
1377
+ targets.push({
1378
+ label: "workspace",
1379
+ path: join(process.cwd(), ".sparkecoder", AUTH_KEY_FILE)
1380
+ });
1381
+ const successes = [];
1382
+ const failures = [];
1383
+ for (const { label, path } of targets) {
1384
+ try {
1385
+ mkdirSync(dirname(path), { recursive: true });
1386
+ writeFileSync(path, json, { mode: 384 });
1387
+ successes.push(`${label}:${path}`);
1388
+ } catch (err) {
1389
+ failures.push({ label, path, error: err?.message ?? String(err) });
1369
1390
  }
1370
- writeFileSync(join(workspaceAuthDir, AUTH_KEY_FILE), json, { mode: 384 });
1371
- } catch {
1391
+ }
1392
+ if (successes.length === 0) {
1393
+ const detail = failures.map((f) => `${f.label} (${f.path}): ${f.error}`).join("; ");
1394
+ throw new Error(`Failed to persist auth-key.json to any location. ${detail}`);
1395
+ }
1396
+ if (failures.length > 0) {
1397
+ console.warn(
1398
+ `[auth-key] wrote to ${successes.length}/${targets.length} locations. Success: ${successes.join(", ")}. Failed: ${failures.map((f) => `${f.label} (${f.path}): ${f.error}`).join("; ")}`
1399
+ );
1400
+ } else if (process.env.SPARKECODER_VERBOSE_CONFIG) {
1401
+ console.log(`[auth-key] saved to: ${successes.join(", ")}`);
1372
1402
  }
1373
1403
  }
1374
1404
  function getStoredAuthKeyInfo() {
@@ -8278,15 +8308,30 @@ var init_client3 = __esm({
8278
8308
  });
8279
8309
 
8280
8310
  // src/integrations/channels/slack.ts
8311
+ function threadKey(channel, threadTs) {
8312
+ return `${channel}\u241F${threadTs}`;
8313
+ }
8314
+ function markThreadOwned(channel, threadTs) {
8315
+ ownedThreads.add(threadKey(channel, threadTs));
8316
+ }
8317
+ function isThreadOwned(channel, threadTs) {
8318
+ return ownedThreads.has(threadKey(channel, threadTs));
8319
+ }
8281
8320
  function stripMention(text) {
8282
8321
  return String(text || "").replace(/<@[^>]+>/g, "").trim();
8283
8322
  }
8284
8323
  function slackEventToInboundResult(event) {
8285
8324
  if (!event) return { event: null, dropReason: "empty_text" };
8286
- if (event.bot_id || event.subtype === "bot_message") return { event: null, dropReason: "bot_message" };
8325
+ if (event.bot_id) return { event: null, dropReason: "bot_message" };
8326
+ if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
8327
+ return { event: null, dropReason: "bot_message" };
8328
+ }
8287
8329
  const isDm = event.type === "message" && event.channel_type === "im";
8288
- if (event.type !== "app_mention" && !isDm) return { event: null, dropReason: "unsupported_type" };
8289
- const text = stripMention(event.text);
8330
+ const isThreadReply = event.type === "message" && !isDm && typeof event.thread_ts === "string" && event.thread_ts !== event.ts;
8331
+ if (event.type !== "app_mention" && !isDm && !isThreadReply) {
8332
+ return { event: null, dropReason: "unsupported_type" };
8333
+ }
8334
+ const text = event.type === "app_mention" ? stripMention(event.text) : (event.text ?? "").trim();
8290
8335
  if (!text) return { event: null, dropReason: "empty_text" };
8291
8336
  const policy = getSlackAllowlistPolicy();
8292
8337
  const userAllowlistActive = policy.allowedUsers.length > 0;
@@ -8321,11 +8366,12 @@ function slackEventToInboundResult(event) {
8321
8366
  }
8322
8367
  };
8323
8368
  }
8324
- var slackChannel;
8369
+ var ownedThreads, slackChannel, IGNORED_MESSAGE_SUBTYPES;
8325
8370
  var init_slack = __esm({
8326
8371
  "src/integrations/channels/slack.ts"() {
8327
8372
  "use strict";
8328
8373
  init_client3();
8374
+ ownedThreads = /* @__PURE__ */ new Set();
8329
8375
  slackChannel = {
8330
8376
  id: "slack",
8331
8377
  canSend: () => isSlackConfigured(),
@@ -8339,6 +8385,9 @@ var init_slack = __esm({
8339
8385
  threadTs: r.threadTs
8340
8386
  });
8341
8387
  if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
8388
+ if (r.slackChannel && r.threadTs) {
8389
+ markThreadOwned(r.slackChannel, r.threadTs);
8390
+ }
8342
8391
  },
8343
8392
  displayLabel(ref) {
8344
8393
  const r = ref;
@@ -8349,6 +8398,29 @@ var init_slack = __esm({
8349
8398
  return parts.join(" ");
8350
8399
  }
8351
8400
  };
8401
+ IGNORED_MESSAGE_SUBTYPES = /* @__PURE__ */ new Set([
8402
+ "bot_message",
8403
+ "message_changed",
8404
+ "message_deleted",
8405
+ "channel_join",
8406
+ "channel_leave",
8407
+ "channel_topic",
8408
+ "channel_purpose",
8409
+ "channel_name",
8410
+ "channel_archive",
8411
+ "channel_unarchive",
8412
+ "pinned_item",
8413
+ "unpinned_item",
8414
+ "thread_broadcast",
8415
+ // also-broadcast-to-channel replies; the regular thread reply already fires
8416
+ "message_replied",
8417
+ // legacy parent-thread bump
8418
+ "file_share",
8419
+ // we'd handle these later; for now skip to avoid double-handling
8420
+ "reply_broadcast",
8421
+ "tombstone",
8422
+ "huddle_thread"
8423
+ ]);
8352
8424
  }
8353
8425
  });
8354
8426
 
@@ -10650,6 +10722,81 @@ var init_session_lock = __esm({
10650
10722
  }
10651
10723
  });
10652
10724
 
10725
+ // src/orchestrator/daemon.ts
10726
+ var daemon_exports = {};
10727
+ __export(daemon_exports, {
10728
+ startOrchestratorDaemon: () => startOrchestratorDaemon,
10729
+ subscribeToDaemonOutput: () => subscribeToDaemonOutput
10730
+ });
10731
+ function subscribeToDaemonOutput(sessionId, fn) {
10732
+ let set = listeners.get(sessionId);
10733
+ if (!set) {
10734
+ set = /* @__PURE__ */ new Set();
10735
+ listeners.set(sessionId, set);
10736
+ }
10737
+ set.add(fn);
10738
+ return () => {
10739
+ const s = listeners.get(sessionId);
10740
+ if (!s) return;
10741
+ s.delete(fn);
10742
+ if (s.size === 0) listeners.delete(sessionId);
10743
+ };
10744
+ }
10745
+ function broadcast(out) {
10746
+ const set = listeners.get(out.sessionId);
10747
+ if (!set) return;
10748
+ for (const fn of set) {
10749
+ try {
10750
+ fn(out);
10751
+ } catch (err) {
10752
+ console.error("[daemon] listener threw:", err?.message || err);
10753
+ }
10754
+ }
10755
+ }
10756
+ function startOrchestratorDaemon() {
10757
+ setFlushHandler(async (sessionId, events) => {
10758
+ await runDaemonTurn(sessionId, events);
10759
+ });
10760
+ }
10761
+ async function runDaemonTurn(sessionId, events) {
10762
+ const startedAt = /* @__PURE__ */ new Date();
10763
+ const session = await sessionQueries.getById(sessionId).catch(() => void 0);
10764
+ if (!session) {
10765
+ console.warn(`[daemon] flush for unknown session ${sessionId}; dropping ${events.length} event(s)`);
10766
+ return;
10767
+ }
10768
+ const prompt = events.map((e) => e.content).join("\n\n");
10769
+ let text = "";
10770
+ let error;
10771
+ await withSessionLock(sessionId, async () => {
10772
+ try {
10773
+ const agent = await Agent.create({ sessionId });
10774
+ const result = await agent.stream({ prompt });
10775
+ for await (const part of result.stream.fullStream) {
10776
+ if (part.type === "text-delta") text += part.text || "";
10777
+ }
10778
+ await result.saveResponseMessages();
10779
+ } catch (err) {
10780
+ error = err?.message || String(err);
10781
+ console.error(`[daemon] turn failed for ${sessionId}:`, error);
10782
+ }
10783
+ });
10784
+ const finishedAt = /* @__PURE__ */ new Date();
10785
+ const trimmed = text.trim();
10786
+ broadcast({ sessionId, text: trimmed, triggeredBy: events, startedAt, finishedAt, error });
10787
+ }
10788
+ var listeners;
10789
+ var init_daemon = __esm({
10790
+ "src/orchestrator/daemon.ts"() {
10791
+ "use strict";
10792
+ init_agent();
10793
+ init_session_lock();
10794
+ init_db();
10795
+ init_inbox();
10796
+ listeners = /* @__PURE__ */ new Map();
10797
+ }
10798
+ });
10799
+
10653
10800
  // src/tasks/boot-recovery.ts
10654
10801
  var boot_recovery_exports = {};
10655
10802
  __export(boot_recovery_exports, {
@@ -10737,81 +10884,6 @@ var init_ensure_orchestrator = __esm({
10737
10884
  }
10738
10885
  });
10739
10886
 
10740
- // src/orchestrator/daemon.ts
10741
- var daemon_exports = {};
10742
- __export(daemon_exports, {
10743
- startOrchestratorDaemon: () => startOrchestratorDaemon,
10744
- subscribeToDaemonOutput: () => subscribeToDaemonOutput
10745
- });
10746
- function subscribeToDaemonOutput(sessionId, fn) {
10747
- let set = listeners.get(sessionId);
10748
- if (!set) {
10749
- set = /* @__PURE__ */ new Set();
10750
- listeners.set(sessionId, set);
10751
- }
10752
- set.add(fn);
10753
- return () => {
10754
- const s = listeners.get(sessionId);
10755
- if (!s) return;
10756
- s.delete(fn);
10757
- if (s.size === 0) listeners.delete(sessionId);
10758
- };
10759
- }
10760
- function broadcast(out) {
10761
- const set = listeners.get(out.sessionId);
10762
- if (!set) return;
10763
- for (const fn of set) {
10764
- try {
10765
- fn(out);
10766
- } catch (err) {
10767
- console.error("[daemon] listener threw:", err?.message || err);
10768
- }
10769
- }
10770
- }
10771
- function startOrchestratorDaemon() {
10772
- setFlushHandler(async (sessionId, events) => {
10773
- await runDaemonTurn(sessionId, events);
10774
- });
10775
- }
10776
- async function runDaemonTurn(sessionId, events) {
10777
- const startedAt = /* @__PURE__ */ new Date();
10778
- const session = await sessionQueries.getById(sessionId).catch(() => void 0);
10779
- if (!session) {
10780
- console.warn(`[daemon] flush for unknown session ${sessionId}; dropping ${events.length} event(s)`);
10781
- return;
10782
- }
10783
- const prompt = events.map((e) => e.content).join("\n\n");
10784
- let text = "";
10785
- let error;
10786
- await withSessionLock(sessionId, async () => {
10787
- try {
10788
- const agent = await Agent.create({ sessionId });
10789
- const result = await agent.stream({ prompt });
10790
- for await (const part of result.stream.fullStream) {
10791
- if (part.type === "text-delta") text += part.text || "";
10792
- }
10793
- await result.saveResponseMessages();
10794
- } catch (err) {
10795
- error = err?.message || String(err);
10796
- console.error(`[daemon] turn failed for ${sessionId}:`, error);
10797
- }
10798
- });
10799
- const finishedAt = /* @__PURE__ */ new Date();
10800
- const trimmed = text.trim();
10801
- broadcast({ sessionId, text: trimmed, triggeredBy: events, startedAt, finishedAt, error });
10802
- }
10803
- var listeners;
10804
- var init_daemon = __esm({
10805
- "src/orchestrator/daemon.ts"() {
10806
- "use strict";
10807
- init_agent();
10808
- init_session_lock();
10809
- init_db();
10810
- init_inbox();
10811
- listeners = /* @__PURE__ */ new Map();
10812
- }
10813
- });
10814
-
10815
10887
  // src/tasks/scheduler.ts
10816
10888
  var scheduler_exports = {};
10817
10889
  __export(scheduler_exports, {
@@ -10940,6 +11012,7 @@ function deriveAgentStatus(input) {
10940
11012
 
10941
11013
  // src/server/routes/sessions.ts
10942
11014
  init_pending_input();
11015
+ init_daemon();
10943
11016
 
10944
11017
  // src/server/devtools-store.ts
10945
11018
  var devtoolsContextStore = /* @__PURE__ */ new Map();
@@ -11082,6 +11155,63 @@ sessions2.post(
11082
11155
  }, 201);
11083
11156
  }
11084
11157
  );
11158
+ sessions2.get("/:id/updates", async (c) => {
11159
+ const id = c.req.param("id");
11160
+ const session = await sessionQueries.getById(id);
11161
+ if (!session) return c.json({ error: "Session not found" }, 404);
11162
+ const stream = new ReadableStream({
11163
+ start(controller) {
11164
+ const encoder = new TextEncoder();
11165
+ const send = (data) => {
11166
+ try {
11167
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
11168
+
11169
+ `));
11170
+ } catch {
11171
+ }
11172
+ };
11173
+ send({ type: "ready", sessionId: id });
11174
+ const unsubscribe = subscribeToDaemonOutput(id, (output) => {
11175
+ send({
11176
+ type: "turn-complete",
11177
+ sessionId: id,
11178
+ finishedAt: output.finishedAt.toISOString(),
11179
+ triggeredBy: output.triggeredBy.map((e) => e.content?.slice(0, 80)),
11180
+ error: output.error ?? null
11181
+ });
11182
+ });
11183
+ let cleaned = false;
11184
+ const cleanup2 = () => {
11185
+ if (cleaned) return;
11186
+ cleaned = true;
11187
+ clearInterval(keepalive);
11188
+ unsubscribe();
11189
+ try {
11190
+ controller.close();
11191
+ } catch {
11192
+ }
11193
+ };
11194
+ const keepalive = setInterval(() => {
11195
+ try {
11196
+ controller.enqueue(encoder.encode(`: keepalive
11197
+
11198
+ `));
11199
+ } catch {
11200
+ cleanup2();
11201
+ }
11202
+ }, 25e3);
11203
+ c.req.raw.signal?.addEventListener("abort", cleanup2);
11204
+ }
11205
+ });
11206
+ return new Response(stream, {
11207
+ headers: {
11208
+ "Content-Type": "text/event-stream",
11209
+ "Cache-Control": "no-cache",
11210
+ Connection: "keep-alive",
11211
+ "X-Accel-Buffering": "no"
11212
+ }
11213
+ });
11214
+ });
11085
11215
  sessions2.get("/:id", async (c) => {
11086
11216
  const id = c.req.param("id");
11087
11217
  const session = await sessionQueries.getById(id);
@@ -13829,6 +13959,19 @@ function verifySlackSignature(opts) {
13829
13959
  init_client3();
13830
13960
  init_slack();
13831
13961
  init_inbox();
13962
+ var recentlyHandled = /* @__PURE__ */ new Map();
13963
+ var MAX_RECENT = 1e3;
13964
+ function alreadyHandled(channel, ts) {
13965
+ if (!channel || !ts) return false;
13966
+ const key2 = `${channel}\u241F${ts}`;
13967
+ if (recentlyHandled.has(key2)) return true;
13968
+ recentlyHandled.set(key2, Date.now());
13969
+ if (recentlyHandled.size > MAX_RECENT) {
13970
+ const oldest = recentlyHandled.keys().next().value;
13971
+ if (oldest) recentlyHandled.delete(oldest);
13972
+ }
13973
+ return false;
13974
+ }
13832
13975
  var slack = new Hono6();
13833
13976
  slack.post("/events", async (c) => {
13834
13977
  const signingSecret = getSlackSigningSecret();
@@ -13854,8 +13997,26 @@ slack.post("/events", async (c) => {
13854
13997
  return c.json({ challenge: payload.challenge });
13855
13998
  }
13856
13999
  if (payload?.type === "event_callback" && payload?.event) {
13857
- const { event: inbound, dropReason } = slackEventToInboundResult(payload.event);
14000
+ const ev = payload.event;
14001
+ if (alreadyHandled(ev.channel, ev.ts)) {
14002
+ return c.json({ ok: true });
14003
+ }
14004
+ const { event: inbound, dropReason } = slackEventToInboundResult(ev);
13858
14005
  if (inbound) {
14006
+ const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
14007
+ if (isThreadReply) {
14008
+ const ours = isThreadOwned(ev.channel, ev.thread_ts) || await threadBelongsToUs(ev.channel, ev.thread_ts);
14009
+ if (!ours) {
14010
+ console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
14011
+ return c.json({ ok: true });
14012
+ }
14013
+ }
14014
+ if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
14015
+ markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
14016
+ }
14017
+ if (ev.type === "message" && ev.channel_type === "im" && ev.channel && (ev.thread_ts || ev.ts)) {
14018
+ markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
14019
+ }
13859
14020
  const orchestratorId = await findOrCreateOrchestratorId();
13860
14021
  if (orchestratorId) {
13861
14022
  pushToInbox(orchestratorId, inbound);
@@ -13872,6 +14033,18 @@ slack.post("/events", async (c) => {
13872
14033
  }
13873
14034
  return c.json({ ok: true });
13874
14035
  });
14036
+ async function threadBelongsToUs(channel, threadTs) {
14037
+ try {
14038
+ const sessions3 = await sessionQueries.list(500, 0);
14039
+ return sessions3.some((s) => {
14040
+ const slack2 = s.config?.slack;
14041
+ return slack2?.channel === channel && slack2?.threadTs === threadTs;
14042
+ });
14043
+ } catch (err) {
14044
+ console.warn("[slack] threadBelongsToUs lookup failed:", err?.message ?? err);
14045
+ return false;
14046
+ }
14047
+ }
13875
14048
  async function findOrCreateOrchestratorId() {
13876
14049
  try {
13877
14050
  const all = await sessionQueries.list(500, 0);