sparkecoder 0.1.118 → 0.1.120

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 (110) hide show
  1. package/dist/agent/index.d.ts +3 -3
  2. package/dist/agent/index.js +3 -1
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +163 -18
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +2 -2
  7. package/dist/{index-Bcz0aCAR.d.ts → index-DczYH89U.d.ts} +104 -104
  8. package/dist/index.d.ts +5 -5
  9. package/dist/index.js +162 -17
  10. package/dist/index.js.map +1 -1
  11. package/dist/{schema-BWbWmfDQ.d.ts → schema-DxrKyetI.d.ts} +3 -3
  12. package/dist/{search-DOzC4ojH.d.ts → search-CVVfuBPZ.d.ts} +4 -4
  13. package/dist/server/index.js +162 -17
  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/_global-error.html +2 -2
  23. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  25. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
  38. package/web/.next/standalone/web/.next/server/app/agents.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  65. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  74. package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
  78. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  82. package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  85. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  86. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  87. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  88. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  89. package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
  90. package/web/.next/standalone/web/.next/server/app/settings.rsc +1 -1
  91. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +1 -1
  92. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
  93. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
  94. package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
  95. package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  96. package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  97. package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  98. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  99. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  100. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  101. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  102. /package/web/.next/standalone/web/.next/static/{T8x1J_CS0n9FaWBr5GhLe → static/uy1OnyxIm3QeGGgKEmxAj}/_buildManifest.js +0 -0
  103. /package/web/.next/standalone/web/.next/static/{T8x1J_CS0n9FaWBr5GhLe → static/uy1OnyxIm3QeGGgKEmxAj}/_clientMiddlewareManifest.json +0 -0
  104. /package/web/.next/standalone/web/.next/static/{T8x1J_CS0n9FaWBr5GhLe → static/uy1OnyxIm3QeGGgKEmxAj}/_ssgManifest.js +0 -0
  105. /package/web/.next/standalone/web/.next/static/{static/T8x1J_CS0n9FaWBr5GhLe → uy1OnyxIm3QeGGgKEmxAj}/_buildManifest.js +0 -0
  106. /package/web/.next/standalone/web/.next/static/{static/T8x1J_CS0n9FaWBr5GhLe → uy1OnyxIm3QeGGgKEmxAj}/_clientMiddlewareManifest.json +0 -0
  107. /package/web/.next/standalone/web/.next/static/{static/T8x1J_CS0n9FaWBr5GhLe → uy1OnyxIm3QeGGgKEmxAj}/_ssgManifest.js +0 -0
  108. /package/web/.next/static/{T8x1J_CS0n9FaWBr5GhLe → uy1OnyxIm3QeGGgKEmxAj}/_buildManifest.js +0 -0
  109. /package/web/.next/static/{T8x1J_CS0n9FaWBr5GhLe → uy1OnyxIm3QeGGgKEmxAj}/_clientMiddlewareManifest.json +0 -0
  110. /package/web/.next/static/{T8x1J_CS0n9FaWBr5GhLe → uy1OnyxIm3QeGGgKEmxAj}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -646,7 +646,7 @@ function loadConfig(configPath, workingDirectory) {
646
646
  ...config,
647
647
  server: {
648
648
  port: config.server.port,
649
- host: config.server.host ?? "127.0.0.1",
649
+ host: config.server.host ?? "0.0.0.0",
650
650
  publicUrl: config.server.publicUrl
651
651
  },
652
652
  resolvedWorkingDirectory,
@@ -813,7 +813,7 @@ function createDefaultConfig() {
813
813
  },
814
814
  server: {
815
815
  port: 3141,
816
- host: "127.0.0.1"
816
+ host: "0.0.0.0"
817
817
  },
818
818
  databasePath: "./sparkecoder.db"
819
819
  };
@@ -7819,9 +7819,50 @@ function isSlackConfigured() {
7819
7819
  function getSlackSigningSecret() {
7820
7820
  return readSlackConfig()?.signingSecret ?? null;
7821
7821
  }
7822
+ function getSlackBotToken() {
7823
+ return readSlackConfig()?.botToken ?? null;
7824
+ }
7822
7825
  function getDefaultOrchestratorName() {
7823
7826
  return readSlackConfig()?.defaultOrchestratorName ?? null;
7824
7827
  }
7828
+ function getCachedSlackSelfIdentity() {
7829
+ const cfg = readSlackConfig();
7830
+ if (!cfg) return null;
7831
+ if (cachedSelf && cachedSelf.token === cfg.botToken) return cachedSelf.identity;
7832
+ return null;
7833
+ }
7834
+ async function ensureSlackSelfIdentity() {
7835
+ const cfg = readSlackConfig();
7836
+ if (!cfg) return null;
7837
+ if (cachedSelf && cachedSelf.token === cfg.botToken) return cachedSelf.identity;
7838
+ if (selfInflight) return selfInflight;
7839
+ selfInflight = (async () => {
7840
+ try {
7841
+ const res = await fetch("https://slack.com/api/auth.test", {
7842
+ method: "POST",
7843
+ headers: { Authorization: `Bearer ${cfg.botToken}` }
7844
+ });
7845
+ const data = await res.json().catch(() => ({}));
7846
+ if (!data?.ok) {
7847
+ console.warn(`[slack] auth.test failed: ${data?.error || `HTTP ${res.status}`}`);
7848
+ return null;
7849
+ }
7850
+ const identity = {
7851
+ botUserId: String(data.user_id || ""),
7852
+ botId: String(data.bot_id || ""),
7853
+ teamId: data.team_id ? String(data.team_id) : void 0
7854
+ };
7855
+ cachedSelf = { token: cfg.botToken, identity };
7856
+ return identity;
7857
+ } catch (err) {
7858
+ console.warn("[slack] auth.test error:", err?.message || err);
7859
+ return null;
7860
+ } finally {
7861
+ selfInflight = null;
7862
+ }
7863
+ })();
7864
+ return selfInflight;
7865
+ }
7825
7866
  function getSlackAllowlistPolicy() {
7826
7867
  try {
7827
7868
  const cfg = getConfig();
@@ -7835,6 +7876,62 @@ function getSlackAllowlistPolicy() {
7835
7876
  return { allowedUsers: [], allowedChannels: [], allowDmsFromAnyone: true };
7836
7877
  }
7837
7878
  }
7879
+ async function fetchSlackUserName(userId) {
7880
+ const token = getSlackBotToken();
7881
+ if (!token) return null;
7882
+ try {
7883
+ const res = await fetch(`https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`, {
7884
+ headers: { Authorization: `Bearer ${token}` }
7885
+ });
7886
+ const data = await res.json().catch(() => ({}));
7887
+ if (!data?.ok) {
7888
+ console.warn(`[slack] users.info(${userId}) failed: ${data?.error || `HTTP ${res.status}`}`);
7889
+ return null;
7890
+ }
7891
+ const profile = data.user?.profile || {};
7892
+ const name = profile.display_name_normalized || profile.display_name || profile.real_name_normalized || profile.real_name || data.user?.real_name || data.user?.name || null;
7893
+ return name ? String(name) : null;
7894
+ } catch (err) {
7895
+ console.warn(`[slack] users.info(${userId}) error:`, err?.message || err);
7896
+ return null;
7897
+ }
7898
+ }
7899
+ async function resolveSlackUserName(userId) {
7900
+ if (!userId) return null;
7901
+ const now = Date.now();
7902
+ const hit = userNameCache.get(userId);
7903
+ if (hit && hit.expiresAt > now) return hit.name;
7904
+ const inflight = userInflight.get(userId);
7905
+ if (inflight) return inflight;
7906
+ const p = (async () => {
7907
+ const name = await fetchSlackUserName(userId);
7908
+ userNameCache.set(userId, {
7909
+ name,
7910
+ expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
7911
+ });
7912
+ userInflight.delete(userId);
7913
+ return name;
7914
+ })();
7915
+ userInflight.set(userId, p);
7916
+ return p;
7917
+ }
7918
+ async function normalizeSlackMentions(text) {
7919
+ if (!text) return text;
7920
+ const userMentionRe = /<@([UW][A-Z0-9]+)(?:\|([^>]+))?>/g;
7921
+ const userIds = /* @__PURE__ */ new Set();
7922
+ for (const m of text.matchAll(userMentionRe)) {
7923
+ if (!m[2]) userIds.add(m[1]);
7924
+ }
7925
+ if (userIds.size > 0) {
7926
+ await Promise.all([...userIds].map((id) => resolveSlackUserName(id)));
7927
+ }
7928
+ return text.replace(userMentionRe, (_full, id, label) => {
7929
+ if (label) return `${label} <@${id}>`;
7930
+ const cached = userNameCache.get(id);
7931
+ const name = cached?.name;
7932
+ return name ? `${name} <@${id}>` : `<@${id}>`;
7933
+ });
7934
+ }
7838
7935
  function getSlackDeniedReplyPolicy() {
7839
7936
  try {
7840
7937
  const cfg = getConfig();
@@ -7847,11 +7944,17 @@ function getSlackDeniedReplyPolicy() {
7847
7944
  return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
7848
7945
  }
7849
7946
  }
7850
- var DEFAULT_DENIED_TEMPLATE;
7947
+ var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userNameCache, userInflight, DEFAULT_DENIED_TEMPLATE;
7851
7948
  var init_client3 = __esm({
7852
7949
  "src/integrations/slack/client.ts"() {
7853
7950
  "use strict";
7854
7951
  init_config();
7952
+ cachedSelf = null;
7953
+ selfInflight = null;
7954
+ USER_TTL_MS = 60 * 60 * 1e3;
7955
+ USER_FAIL_TTL_MS = 5 * 60 * 1e3;
7956
+ userNameCache = /* @__PURE__ */ new Map();
7957
+ userInflight = /* @__PURE__ */ new Map();
7855
7958
  DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
7856
7959
  }
7857
7960
  });
@@ -7866,21 +7969,36 @@ function markThreadOwned(channel, threadTs) {
7866
7969
  function isThreadOwned(channel, threadTs) {
7867
7970
  return ownedThreads.has(threadKey(channel, threadTs));
7868
7971
  }
7869
- function stripMention(text) {
7870
- return String(text || "").replace(/<@[^>]+>/g, "").trim();
7972
+ function isSelfAuthored(event, self) {
7973
+ if (!self) return true;
7974
+ if (self.botId && event.bot_id && event.bot_id === self.botId) return true;
7975
+ if (self.botUserId && event.user && event.user === self.botUserId) return true;
7976
+ return false;
7871
7977
  }
7872
- function slackEventToInboundResult(event) {
7978
+ function slackEventToInboundResult(event, opts = {}) {
7873
7979
  if (!event) return { event: null, dropReason: "empty_text" };
7874
- if (event.bot_id) return { event: null, dropReason: "bot_message" };
7875
- if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
7980
+ const self = opts.self ?? getCachedSlackSelfIdentity();
7981
+ const isBotAuthored = !!event.bot_id || event.type === "message" && event.subtype === "bot_message";
7982
+ if (isBotAuthored && isSelfAuthored(event, self)) {
7876
7983
  return { event: null, dropReason: "bot_message" };
7877
7984
  }
7985
+ if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
7986
+ return { event: null, dropReason: "ignored_subtype" };
7987
+ }
7878
7988
  const isDm = event.type === "message" && event.channel_type === "im";
7879
7989
  const isThreadReply = event.type === "message" && !isDm && typeof event.thread_ts === "string" && event.thread_ts !== event.ts;
7990
+ const isNonThreadChannelMsg = event.type === "message" && !isDm && !isThreadReply && (event.channel_type === "channel" || event.channel_type === "group" || event.channel_type === "mpim" || // Some payload shapes omit channel_type for channel messages.
7991
+ typeof event.channel === "string");
7880
7992
  if (event.type !== "app_mention" && !isDm && !isThreadReply) {
7993
+ if (isNonThreadChannelMsg) {
7994
+ return { event: null, dropReason: "non_thread_channel_msg" };
7995
+ }
7996
+ if (event.type !== "message") {
7997
+ return { event: null, dropReason: "non_message_event" };
7998
+ }
7881
7999
  return { event: null, dropReason: "unsupported_type" };
7882
8000
  }
7883
- const text = event.type === "app_mention" ? stripMention(event.text) : (event.text ?? "").trim();
8001
+ const text = (event.text ?? "").trim();
7884
8002
  if (!text) return { event: null, dropReason: "empty_text" };
7885
8003
  const policy = getSlackAllowlistPolicy();
7886
8004
  const userAllowlistActive = policy.allowedUsers.length > 0;
@@ -7948,7 +8066,6 @@ var init_slack = __esm({
7948
8066
  }
7949
8067
  };
7950
8068
  IGNORED_MESSAGE_SUBTYPES = /* @__PURE__ */ new Set([
7951
- "bot_message",
7952
8069
  "message_changed",
7953
8070
  "message_deleted",
7954
8071
  "channel_join",
@@ -13625,16 +13742,18 @@ init_webhook_events();
13625
13742
  init_inbox();
13626
13743
  var recentlyHandled = /* @__PURE__ */ new Map();
13627
13744
  var MAX_RECENT = 1e3;
13628
- function alreadyHandled(channel, ts) {
13745
+ function wasHandled(channel, ts) {
13629
13746
  if (!channel || !ts) return false;
13747
+ return recentlyHandled.has(`${channel}\u241F${ts}`);
13748
+ }
13749
+ function markHandled(channel, ts) {
13750
+ if (!channel || !ts) return;
13630
13751
  const key2 = `${channel}\u241F${ts}`;
13631
- if (recentlyHandled.has(key2)) return true;
13632
13752
  recentlyHandled.set(key2, Date.now());
13633
13753
  if (recentlyHandled.size > MAX_RECENT) {
13634
13754
  const oldest = recentlyHandled.keys().next().value;
13635
13755
  if (oldest) recentlyHandled.delete(oldest);
13636
13756
  }
13637
- return false;
13638
13757
  }
13639
13758
  var slack = new Hono6();
13640
13759
  slack.post("/events", async (c) => {
@@ -13671,11 +13790,12 @@ slack.post("/events", async (c) => {
13671
13790
  textSnippet: typeof ev.text === "string" ? ev.text : void 0,
13672
13791
  meta: { ts: ev.ts, thread_ts: ev.thread_ts, team: ev.team, event_subtype: ev.subtype }
13673
13792
  });
13674
- if (alreadyHandled(ev.channel, ev.ts)) {
13793
+ if (wasHandled(ev.channel, ev.ts)) {
13675
13794
  updateEvent(auditId, { status: "dropped", dropReason: "duplicate_delivery" });
13676
13795
  return c.json({ ok: true });
13677
13796
  }
13678
- const { event: inbound, dropReason } = slackEventToInboundResult(ev);
13797
+ const self = await ensureSlackSelfIdentity();
13798
+ const { event: inbound, dropReason } = slackEventToInboundResult(ev, { self });
13679
13799
  if (inbound) {
13680
13800
  const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
13681
13801
  if (isThreadReply) {
@@ -13694,7 +13814,18 @@ slack.post("/events", async (c) => {
13694
13814
  }
13695
13815
  const orchestratorId = await findOrCreateOrchestratorId();
13696
13816
  if (orchestratorId) {
13817
+ inbound.content = await normalizeSlackMentions(inbound.content);
13818
+ if (ev.user) {
13819
+ const speakerName = await resolveSlackUserName(ev.user);
13820
+ if (speakerName) {
13821
+ inbound.content = inbound.content.replace(
13822
+ `user=${ev.user}`,
13823
+ `user=${speakerName} <@${ev.user}>`
13824
+ );
13825
+ }
13826
+ }
13697
13827
  pushToInbox(orchestratorId, inbound);
13828
+ markHandled(ev.channel, ev.ts);
13698
13829
  updateEvent(auditId, { status: "routed", sessionId: orchestratorId });
13699
13830
  } else {
13700
13831
  updateEvent(auditId, { status: "error", error: "no orchestrator session available" });
@@ -14649,13 +14780,15 @@ async function startServer(options = {}) {
14649
14780
  if (!options.quiet) console.warn(`[scheduler] start skipped: ${err.message}`);
14650
14781
  }
14651
14782
  const port = options.port || config.server.port;
14652
- const host = options.host || config.server.host || "0.0.0.0";
14783
+ const envHost = process.env.SPARKECODER_API_HOST || process.env.SPARKECODER_HOST;
14784
+ const host = envHost || options.host || config.server.host || "0.0.0.0";
14785
+ const hostSource = envHost ? process.env.SPARKECODER_API_HOST ? "env SPARKECODER_API_HOST" : "env SPARKECODER_HOST" : options.host ? "--host flag" : config.server.host ? "config.server.host" : "default";
14653
14786
  const publicUrl = options.publicUrl || config.server.publicUrl;
14654
14787
  const app = await createApp({ quiet: options.quiet });
14655
14788
  if (!options.quiet) {
14656
14789
  console.log(`
14657
14790
  \u{1F680} SparkECoder API Server`);
14658
- console.log(` \u2192 Running at http://${host}:${port}`);
14791
+ console.log(` \u2192 Binding to ${host}:${port} (source: ${hostSource})`);
14659
14792
  if (publicUrl) {
14660
14793
  console.log(` \u2192 Public URL: ${publicUrl}`);
14661
14794
  }
@@ -14664,10 +14797,22 @@ async function startServer(options = {}) {
14664
14797
  console.log(` \u2192 OpenAPI spec: http://${host}:${port}/openapi.json
14665
14798
  `);
14666
14799
  }
14800
+ if (host === "127.0.0.1" || host === "localhost") {
14801
+ console.log(`[sparkecoder] \u26A0 API bound to ${host} only \u2014 not reachable from outside the machine.`);
14802
+ console.log(`[sparkecoder] For tunnels/reverse proxies (Modal/Fly/Docker), set --host 0.0.0.0 or SPARKECODER_API_HOST=0.0.0.0.`);
14803
+ }
14667
14804
  serverInstance = serve({
14668
14805
  fetch: app.fetch,
14669
14806
  port,
14670
14807
  hostname: host
14808
+ }, (info) => {
14809
+ const actual = `${info.address}:${info.port}`;
14810
+ const requested = `${host}:${port}`;
14811
+ if (info.address === "127.0.0.1" && host !== "127.0.0.1" && host !== "localhost") {
14812
+ console.warn(`[sparkecoder] \u2717 API listener bound to ${actual} but ${requested} was requested. External requests will be refused.`);
14813
+ } else {
14814
+ console.log(`[sparkecoder] \u2713 API listening on ${actual}`);
14815
+ }
14671
14816
  });
14672
14817
  let webPort;
14673
14818
  let webStarted;