sparkecoder 0.1.126 → 0.1.127

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 (106) hide show
  1. package/dist/agent/index.js +52 -3
  2. package/dist/agent/index.js.map +1 -1
  3. package/dist/cli.js +118 -17
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.js +118 -17
  6. package/dist/index.js.map +1 -1
  7. package/dist/server/index.js +118 -17
  8. package/dist/server/index.js.map +1 -1
  9. package/dist/skills/default/slack-messaging.md +268 -0
  10. package/dist/tools/index.js +3 -1
  11. package/dist/tools/index.js.map +1 -1
  12. package/package.json +1 -1
  13. package/src/skills/default/slack-messaging.md +268 -0
  14. package/web/.next/BUILD_ID +1 -1
  15. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  16. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  17. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  18. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  19. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  20. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  21. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  22. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  23. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  25. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
  34. package/web/.next/standalone/web/.next/server/app/agents.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  43. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  52. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  61. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  70. package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  78. package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  82. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  85. package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
  86. package/web/.next/standalone/web/.next/server/app/settings.rsc +1 -1
  87. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +1 -1
  88. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
  89. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
  90. package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
  91. package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  92. package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  93. package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  94. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  95. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  96. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  97. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  98. /package/web/.next/standalone/web/.next/static/{BSOUN7vf8epF685UYalET → l63ZK5juWEj8lzzuVVSpx}/_buildManifest.js +0 -0
  99. /package/web/.next/standalone/web/.next/static/{BSOUN7vf8epF685UYalET → l63ZK5juWEj8lzzuVVSpx}/_clientMiddlewareManifest.json +0 -0
  100. /package/web/.next/standalone/web/.next/static/{BSOUN7vf8epF685UYalET → l63ZK5juWEj8lzzuVVSpx}/_ssgManifest.js +0 -0
  101. /package/web/.next/standalone/web/.next/static/static/{BSOUN7vf8epF685UYalET → l63ZK5juWEj8lzzuVVSpx}/_buildManifest.js +0 -0
  102. /package/web/.next/standalone/web/.next/static/static/{BSOUN7vf8epF685UYalET → l63ZK5juWEj8lzzuVVSpx}/_clientMiddlewareManifest.json +0 -0
  103. /package/web/.next/standalone/web/.next/static/static/{BSOUN7vf8epF685UYalET → l63ZK5juWEj8lzzuVVSpx}/_ssgManifest.js +0 -0
  104. /package/web/.next/static/{BSOUN7vf8epF685UYalET → l63ZK5juWEj8lzzuVVSpx}/_buildManifest.js +0 -0
  105. /package/web/.next/static/{BSOUN7vf8epF685UYalET → l63ZK5juWEj8lzzuVVSpx}/_clientMiddlewareManifest.json +0 -0
  106. /package/web/.next/static/{BSOUN7vf8epF685UYalET → l63ZK5juWEj8lzzuVVSpx}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -4256,8 +4256,10 @@ async function loadAgentsMd(agentsMdPath) {
4256
4256
  }
4257
4257
  async function loadSkillContent(skillName, directories) {
4258
4258
  const allSkills = await loadAllSkills(directories);
4259
+ const normalize2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
4260
+ const wanted = normalize2(skillName);
4259
4261
  const skill = allSkills.find(
4260
- (s) => s.name.toLowerCase() === skillName.toLowerCase()
4262
+ (s) => normalize2(s.name) === wanted || normalize2(basename(s.filePath, extname4(s.filePath))) === wanted
4261
4263
  );
4262
4264
  if (!skill) {
4263
4265
  return null;
@@ -7055,7 +7057,7 @@ Every user-message you see is tagged at the front with a channel pill describing
7055
7057
  Pill formats:
7056
7058
 
7057
7059
  - \`[WEB] ...\` \u2014 typed in the web dashboard. Your assistant text streams back automatically. No tool call needed.
7058
- - \`[SLACK channel=C0123 thread=1700000000.001 user=U0123] ...\` \u2014 a Slack mention or DM. To reply: \`messenger({action:'post', channel:'slack', to:'C0123', threadTs:'1700000000.001', text:'...'})\`. **Always set \`threadTs\`** so the reply lands in the same thread.
7060
+ - \`[SLACK channel=C0123 thread=1700000000.001 user=Ryan Trattner <@U0123> (ryan@example.com)] ...\` \u2014 a Slack mention or DM. To reply: \`messenger({action:'post', channel:'slack', to:'C0123', threadTs:'1700000000.001', text:'...'})\`. **Always set \`threadTs\`** so the reply lands in the same thread. The \`user=\` field is enriched in-place: when the bot has the right scopes you get \`<Name> <@U_ID> (email)\`; if scopes are missing you may see just the raw id \u2014 that's not an error, just less context. Any \`<@U\u2026>\` mentions inside the message text are similarly normalized to \`<Name> <@U_ID>\` so you can immediately tell who's being addressed.
7059
7061
  - \`[SYSTEM worker.completed worker-name] ...\` \u2014 a worker you spawned finished. Look back at the conversation: where did the request originate?
7060
7062
  - If the original request was \`[WEB]\`, a normal text reply is enough (the user is watching).
7061
7063
  - If it was \`[SLACK ...]\`, post the result to that Slack thread via \`messenger\`.
@@ -7075,6 +7077,48 @@ If \`messenger({action:'post', ...})\` returns \`{ok:false, error:'...'}\` (e.g.
7075
7077
  **Never silently swallow a delivery failure.**
7076
7078
  </delivery_failures>
7077
7079
 
7080
+ <reaching_specific_people>
7081
+
7082
+ When a request needs a *specific human's attention* (e.g. "ask Ryan to review this", "let Sarah know the deploy is done", "wait for Charlie's OK before merging") \u2014 don't just reply into thin air, actually surface it to that person. Two mechanisms, in order of preference:
7083
+
7084
+ **1. @-ping them in a Slack channel they're already in (the cheap path)**
7085
+
7086
+ If you know the person's Slack user id and the conversation has a relevant channel/thread, include a literal mention token in the message text. Slack renders \`<@U0123>\` as a clickable @-mention and pushes a notification to that user.
7087
+
7088
+ \`\`\`
7089
+ messenger({
7090
+ action: 'post', channel: 'slack',
7091
+ to: 'C0B5CESSBGD', threadTs: '1700000000.001',
7092
+ text: '<@U05C6RWBDPC> heads up \u2014 the deploy hit a flaky test, can you check?'
7093
+ })
7094
+ \`\`\`
7095
+
7096
+ You already have the user id from inbound \`[SLACK ... user=U0123]\` pills. If multiple people are involved in a thread, you've seen their ids in the recent messages \u2014 use those. **The mention must be wrapped in \`<@...>\` \u2014 bare \`@username\` does NOT notify.**
7097
+
7098
+ **2. DM them directly (when channel context is missing OR you only know their email)**
7099
+
7100
+ For "DM Alice" / "shoot Bob a note" / pinging someone not in the current thread, use the **\`slack-messaging\` skill** \u2014 load it first with \`load_skill slack-messaging\`. It documents the lookup recipe (\`users.lookupByEmail\` \u2192 user id \u2192 \`messenger\` with the user id as \`to\`). Slack auto-opens the DM; no extra step.
7101
+
7102
+ Quick form (when you've already loaded the skill earlier in the turn): look up the user id by email via the Slack API, then \`messenger({action:'post', channel:'slack', to:'<U_ID>', text:'...'})\`. No \`threadTs\` for DMs \u2014 they're standalone.
7103
+
7104
+ **When to use which**
7105
+
7106
+ | Situation | Use |
7107
+ |---|---|
7108
+ | Inbound Slack thread + you know the user is in it | ping with \`<@U\u2026>\` in the existing thread |
7109
+ | Need them but they're not in this channel/thread | DM (skill) |
7110
+ | Have only their email, not their id | DM (skill \u2014 it looks up the id) |
7111
+ | Need a broadcast-style heads-up | ping in their team's channel |
7112
+ | Sensitive / 1:1 conversation | always DM, never ping in a shared channel |
7113
+
7114
+ **Don'ts**
7115
+
7116
+ - Don't @-mention everyone in a channel ("FYI" pings \u2014 leave that to humans).
7117
+ - Don't ping AND DM the same person for the same thing \u2014 pick one.
7118
+ - Don't fabricate user ids or emails. If you don't know how to reach them, ask the requester ("which Sarah?") instead of guessing.
7119
+ - Don't ping in a thread the person hasn't shown up in unless the asker explicitly named them \u2014 surprise pings are annoying.
7120
+ </reaching_specific_people>
7121
+
7078
7122
  <hard_rules>
7079
7123
 
7080
7124
  - Avoid direct workspace work. Do not directly edit product code, run builds, or perform substantive implementation yourself; spawn workers for that.
@@ -7995,7 +8039,12 @@ function load() {
7995
8039
  const now = Date.now();
7996
8040
  for (const [id, e] of Object.entries(parsed.users || {})) {
7997
8041
  if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
7998
- userMap.set(id, { name: e.name ?? null, expiresAt: e.expiresAt });
8042
+ userMap.set(id, {
8043
+ name: e.name ?? null,
8044
+ realName: e.realName ?? null,
8045
+ email: e.email ?? null,
8046
+ expiresAt: e.expiresAt
8047
+ });
7999
8048
  }
8000
8049
  }
8001
8050
  for (const [key2, e] of Object.entries(parsed.threads || {})) {
@@ -8190,7 +8239,7 @@ function getSlackAllowlistPolicy() {
8190
8239
  return { allowedUsers: [], allowedChannels: [], allowDmsFromAnyone: true };
8191
8240
  }
8192
8241
  }
8193
- async function fetchSlackUserName(userId) {
8242
+ async function fetchSlackUserInfo(userId) {
8194
8243
  const token = getSlackBotToken();
8195
8244
  if (!token) return null;
8196
8245
  try {
@@ -8204,31 +8253,45 @@ async function fetchSlackUserName(userId) {
8204
8253
  }
8205
8254
  const profile = data.user?.profile || {};
8206
8255
  const name = profile.display_name_normalized || profile.display_name || profile.real_name_normalized || profile.real_name || data.user?.real_name || data.user?.name || null;
8207
- return name ? String(name) : null;
8256
+ const realName = profile.real_name_normalized || profile.real_name || data.user?.real_name || null;
8257
+ const email = profile.email || null;
8258
+ return {
8259
+ name: name ? String(name) : null,
8260
+ realName: realName ? String(realName) : null,
8261
+ email: email ? String(email) : null
8262
+ };
8208
8263
  } catch (err) {
8209
8264
  console.warn(`[slack] users.info(${userId}) error:`, err?.message || err);
8210
8265
  return null;
8211
8266
  }
8212
8267
  }
8213
- async function resolveSlackUserName(userId) {
8268
+ async function resolveSlackUserInfo(userId) {
8214
8269
  if (!userId) return null;
8215
8270
  const now = Date.now();
8216
8271
  const hit = getCachedUserName(userId);
8217
- if (hit && hit.expiresAt > now) return hit.name;
8272
+ if (hit && hit.expiresAt > now) {
8273
+ return { name: hit.name, realName: hit.realName ?? null, email: hit.email ?? null };
8274
+ }
8218
8275
  const inflight = userInflight.get(userId);
8219
8276
  if (inflight) return inflight;
8220
8277
  const p = (async () => {
8221
- const name = await fetchSlackUserName(userId);
8278
+ const info = await fetchSlackUserInfo(userId);
8222
8279
  setCachedUserName(userId, {
8223
- name,
8224
- expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
8280
+ name: info?.name ?? null,
8281
+ realName: info?.realName ?? null,
8282
+ email: info?.email ?? null,
8283
+ expiresAt: now + (info?.name ? USER_TTL_MS : USER_FAIL_TTL_MS)
8225
8284
  });
8226
8285
  userInflight.delete(userId);
8227
- return name;
8286
+ return info;
8228
8287
  })();
8229
8288
  userInflight.set(userId, p);
8230
8289
  return p;
8231
8290
  }
8291
+ async function resolveSlackUserName(userId) {
8292
+ const info = await resolveSlackUserInfo(userId);
8293
+ return info?.name ?? null;
8294
+ }
8232
8295
  async function normalizeSlackMentions(text) {
8233
8296
  if (!text) return text;
8234
8297
  const userMentionRe = /<@([UW][A-Z0-9]+)(?:\|([^>]+))?>/g;
@@ -8461,6 +8524,13 @@ var init_slack = __esm({
8461
8524
  });
8462
8525
 
8463
8526
  // src/integrations/channels/system.ts
8527
+ var system_exports = {};
8528
+ __export(system_exports, {
8529
+ systemChannel: () => systemChannel,
8530
+ workerCompletedEvent: () => workerCompletedEvent,
8531
+ workerFailedEvent: () => workerFailedEvent,
8532
+ workerQuestionEvent: () => workerQuestionEvent
8533
+ });
8464
8534
  function workerCompletedEvent(workerId, workerName, summary) {
8465
8535
  const ref = { channel: "system", kind: "worker.completed", workerId, workerName };
8466
8536
  return {
@@ -9448,6 +9518,14 @@ var init_pending_input = __esm({
9448
9518
  });
9449
9519
 
9450
9520
  // src/orchestrator/inbox.ts
9521
+ var inbox_exports = {};
9522
+ __export(inbox_exports, {
9523
+ clearInbox: () => clearInbox,
9524
+ flush: () => flush,
9525
+ peekInbox: () => peekInbox,
9526
+ pushToInbox: () => pushToInbox,
9527
+ setFlushHandler: () => setFlushHandler
9528
+ });
9451
9529
  function setFlushHandler(fn) {
9452
9530
  flushHandler = fn;
9453
9531
  }
@@ -9493,6 +9571,18 @@ async function flush(sessionId) {
9493
9571
  console.error("[orchestrator-inbox] flush handler threw:", err?.message || err);
9494
9572
  }
9495
9573
  }
9574
+ function peekInbox(sessionId) {
9575
+ return inboxes.get(sessionId)?.pending.slice() ?? [];
9576
+ }
9577
+ function clearInbox(sessionId) {
9578
+ const e = inboxes.get(sessionId);
9579
+ if (!e) return;
9580
+ if (e.timer) {
9581
+ clearTimeout(e.timer);
9582
+ e.timer = void 0;
9583
+ }
9584
+ e.pending.length = 0;
9585
+ }
9496
9586
  var inboxes, FLUSH_DEBOUNCE_MS, flushHandler;
9497
9587
  var init_inbox = __esm({
9498
9588
  "src/orchestrator/inbox.ts"() {
@@ -14139,6 +14229,18 @@ tasks.post(
14139
14229
  data: { status: "failed", error: errorMsg }
14140
14230
  });
14141
14231
  }
14232
+ if (body.orchestratorSessionId) {
14233
+ try {
14234
+ const { pushToInbox: pushToInbox2 } = await Promise.resolve().then(() => (init_inbox(), inbox_exports));
14235
+ const { workerFailedEvent: workerFailedEvent2 } = await Promise.resolve().then(() => (init_system(), system_exports));
14236
+ pushToInbox2(
14237
+ body.orchestratorSessionId,
14238
+ workerFailedEvent2(taskId, body.name || "worker", `(uncaught) ${errorMsg}`)
14239
+ );
14240
+ } catch (notifyErr) {
14241
+ console.error(`[TASK] failed to notify orchestrator of crash:`, notifyErr?.message || notifyErr);
14242
+ }
14243
+ }
14142
14244
  }
14143
14245
  } finally {
14144
14246
  await writeSSE("[DONE]");
@@ -14388,12 +14490,11 @@ slack.post("/events", async (c) => {
14388
14490
  if (orchestratorId) {
14389
14491
  inbound.content = await normalizeSlackMentions(inbound.content);
14390
14492
  if (ev.user) {
14391
- const speakerName = await resolveSlackUserName(ev.user);
14392
- if (speakerName) {
14393
- inbound.content = inbound.content.replace(
14394
- `user=${ev.user}`,
14395
- `user=${speakerName} <@${ev.user}>`
14396
- );
14493
+ const info = await resolveSlackUserInfo(ev.user);
14494
+ if (info?.name) {
14495
+ const emailSuffix = info.email ? ` (${info.email})` : "";
14496
+ const enriched = `user=${info.name} <@${ev.user}>${emailSuffix}`;
14497
+ inbound.content = inbound.content.replace(`user=${ev.user}`, () => enriched);
14397
14498
  }
14398
14499
  }
14399
14500
  pushToInbox(orchestratorId, inbound);