@cursor/july 0.1.7 → 0.1.8

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 (173) hide show
  1. package/dist/bin/agent-serve.js +31 -2
  2. package/dist/channels/slack/channel-watch.d.ts +6 -1
  3. package/dist/channels/slack/channel-watch.d.ts.map +1 -1
  4. package/dist/channels/slack/channel-watch.js +22 -1
  5. package/dist/channels/slack/cursor-account.d.ts +4 -0
  6. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  7. package/dist/channels/slack/cursor-account.js +12 -2
  8. package/dist/channels/slack/defaults.d.ts +10 -5
  9. package/dist/channels/slack/defaults.d.ts.map +1 -1
  10. package/dist/channels/slack/defaults.js +39 -17
  11. package/dist/channels/slack/dispatch.d.ts +18 -0
  12. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  13. package/dist/channels/slack/dispatch.js +141 -38
  14. package/dist/channels/slack/inbound.d.ts +13 -3
  15. package/dist/channels/slack/inbound.d.ts.map +1 -1
  16. package/dist/channels/slack/inbound.js +17 -9
  17. package/dist/channels/slack/index.d.ts +4 -0
  18. package/dist/channels/slack/index.d.ts.map +1 -1
  19. package/dist/channels/slack/index.js +4 -0
  20. package/dist/channels/slack/live-delivery.d.ts.map +1 -1
  21. package/dist/channels/slack/live-delivery.js +1 -0
  22. package/dist/channels/slack/manifest.d.ts.map +1 -1
  23. package/dist/channels/slack/manifest.js +3 -0
  24. package/dist/channels/slack/nudge-carrier.d.ts +19 -0
  25. package/dist/channels/slack/nudge-carrier.d.ts.map +1 -0
  26. package/dist/channels/slack/nudge-carrier.js +166 -0
  27. package/dist/channels/slack/nudge-store.d.ts +61 -0
  28. package/dist/channels/slack/nudge-store.d.ts.map +1 -0
  29. package/dist/channels/slack/nudge-store.js +266 -0
  30. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  31. package/dist/channels/slack/slack-channel.js +74 -25
  32. package/dist/channels/slack/stop.d.ts +14 -0
  33. package/dist/channels/slack/stop.d.ts.map +1 -0
  34. package/dist/channels/slack/stop.js +22 -0
  35. package/dist/channels/slack/thread-context.d.ts +81 -0
  36. package/dist/channels/slack/thread-context.d.ts.map +1 -0
  37. package/dist/channels/slack/thread-context.js +470 -0
  38. package/dist/channels/slack/types.d.ts +27 -0
  39. package/dist/channels/slack/types.d.ts.map +1 -1
  40. package/dist/docs/404.html +2 -2
  41. package/dist/docs/ab.html +4 -4
  42. package/dist/docs/assets/{app.COTN7wgo.js → app.BP8mAVMW.js} +1 -1
  43. package/dist/docs/assets/chunks/@localSearchIndexroot.DevOabKJ.js +1 -0
  44. package/dist/docs/assets/chunks/{VPLocalSearchBox.BW3TBdT0.js → VPLocalSearchBox.1FXIZbz8.js} +1 -1
  45. package/dist/docs/assets/chunks/{theme.BEJW0vE7.js → theme.Dm1R02v9.js} +2 -2
  46. package/dist/docs/assets/example-agents_index.md.CwtWu5ix.js +2 -0
  47. package/dist/docs/assets/{example-agents_index.md.CRqJlnIf.lean.js → example-agents_index.md.CwtWu5ix.lean.js} +1 -1
  48. package/dist/docs/assets/example-agents_oncall.md.BG_sUMly.js +10 -0
  49. package/dist/docs/assets/example-agents_oncall.md.BG_sUMly.lean.js +1 -0
  50. package/dist/docs/assets/{guides_slack.md.CCwqHvSV.js → guides_slack.md.FWale6Ip.js} +4 -3
  51. package/dist/docs/assets/{guides_slack.md.CCwqHvSV.lean.js → guides_slack.md.FWale6Ip.lean.js} +1 -1
  52. package/dist/docs/assets/{reference_sessions.md.DdjWabnN.js → reference_sessions.md.DLd6mvbv.js} +2 -2
  53. package/dist/docs/assets/{reference_sessions.md.DdjWabnN.lean.js → reference_sessions.md.DLd6mvbv.lean.js} +1 -1
  54. package/dist/docs/assets/{storage.md.CVnInNiN.js → storage.md.Cu3MwM2a.js} +3 -2
  55. package/dist/docs/assets/{storage.md.CVnInNiN.lean.js → storage.md.Cu3MwM2a.lean.js} +1 -1
  56. package/dist/docs/building-with-agents.html +4 -4
  57. package/dist/docs/concepts.html +4 -4
  58. package/dist/docs/deployment.html +4 -4
  59. package/dist/docs/evals.html +4 -4
  60. package/dist/docs/example-agents/approval-buddy.html +4 -4
  61. package/dist/docs/example-agents/benny.html +5 -5
  62. package/dist/docs/example-agents/bugbot.html +5 -5
  63. package/dist/docs/example-agents/codebase-wiki.html +4 -4
  64. package/dist/docs/example-agents/codeowners-review.html +4 -4
  65. package/dist/docs/example-agents/concierge.html +4 -4
  66. package/dist/docs/example-agents/fsd.html +4 -4
  67. package/dist/docs/example-agents/index.html +6 -6
  68. package/dist/docs/example-agents/knowledge-base.html +4 -4
  69. package/dist/docs/example-agents/oncall.html +34 -0
  70. package/dist/docs/example-agents/security-reviewer.html +4 -4
  71. package/dist/docs/example-agents/slack-agent.html +4 -4
  72. package/dist/docs/example-agents/weather-agent.html +4 -4
  73. package/dist/docs/guides/agent-to-agent.html +4 -4
  74. package/dist/docs/guides/cloud-runtime.html +4 -4
  75. package/dist/docs/guides/github.html +4 -4
  76. package/dist/docs/guides/human-in-the-loop.html +4 -4
  77. package/dist/docs/guides/slack.html +7 -6
  78. package/dist/docs/guides/webhooks.html +4 -4
  79. package/dist/docs/hashmap.json +1 -1
  80. package/dist/docs/hillclimbing.html +4 -4
  81. package/dist/docs/index.html +4 -4
  82. package/dist/docs/quickstart.html +4 -4
  83. package/dist/docs/reference/agent-config.html +4 -4
  84. package/dist/docs/reference/channels.html +4 -4
  85. package/dist/docs/reference/cli.html +4 -4
  86. package/dist/docs/reference/connections.html +4 -4
  87. package/dist/docs/reference/hooks.html +4 -4
  88. package/dist/docs/reference/http-api.html +4 -4
  89. package/dist/docs/reference/instructions.html +4 -4
  90. package/dist/docs/reference/playground.html +4 -4
  91. package/dist/docs/reference/project-layout.html +4 -4
  92. package/dist/docs/reference/schedules.html +4 -4
  93. package/dist/docs/reference/sessions.html +5 -5
  94. package/dist/docs/reference/skills.html +4 -4
  95. package/dist/docs/reference/subagents.html +4 -4
  96. package/dist/docs/reference/tools.html +4 -4
  97. package/dist/docs/scaffolding-agents.html +4 -4
  98. package/dist/docs/storage.html +7 -6
  99. package/dist/docs/troubleshooting.html +4 -4
  100. package/dist/internal/cli-mcp.d.ts +64 -0
  101. package/dist/internal/cli-mcp.d.ts.map +1 -0
  102. package/dist/internal/cli-mcp.js +570 -0
  103. package/dist/internal/distribution.d.ts.map +1 -1
  104. package/dist/internal/distribution.js +2 -0
  105. package/dist/internal/host-kv.d.ts +17 -0
  106. package/dist/internal/host-kv.d.ts.map +1 -0
  107. package/dist/internal/host-kv.js +77 -0
  108. package/dist/internal/server.d.ts.map +1 -1
  109. package/dist/internal/server.js +8 -0
  110. package/dist/internal/session-engine.d.ts +42 -7
  111. package/dist/internal/session-engine.d.ts.map +1 -1
  112. package/dist/internal/session-engine.js +247 -51
  113. package/dist/internal/storage-coordinator.d.ts +18 -1
  114. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  115. package/dist/internal/storage-coordinator.js +43 -0
  116. package/dist/playground/assets/index-7GJIUFQj.css +1 -0
  117. package/dist/playground/assets/index-Bpr4Th4M.js +319 -0
  118. package/dist/playground/index.html +2 -2
  119. package/dist/storage-backends/file-kv.d.ts +42 -0
  120. package/dist/storage-backends/file-kv.d.ts.map +1 -0
  121. package/dist/storage-backends/file-kv.js +190 -0
  122. package/dist/storage-backends/postgres-kv.d.ts +77 -0
  123. package/dist/storage-backends/postgres-kv.d.ts.map +1 -0
  124. package/dist/storage-backends/postgres-kv.js +180 -0
  125. package/dist/storage.d.ts +10 -1
  126. package/dist/storage.d.ts.map +1 -1
  127. package/dist/storage.js +10 -1
  128. package/dist/types.d.ts +79 -0
  129. package/dist/types.d.ts.map +1 -1
  130. package/docs/example-agents/index.md +14 -9
  131. package/docs/example-agents/oncall.md +216 -0
  132. package/docs/guides/slack.md +11 -2
  133. package/docs/reference/sessions.md +13 -4
  134. package/docs/storage.md +27 -0
  135. package/package.json +15 -1
  136. package/skills/framework-map/SKILL.md +6 -4
  137. package/src/bin/agent-serve.ts +32 -2
  138. package/src/channels/slack/channel-watch.ts +28 -1
  139. package/src/channels/slack/cursor-account.ts +20 -0
  140. package/src/channels/slack/defaults.ts +51 -20
  141. package/src/channels/slack/dispatch.ts +215 -40
  142. package/src/channels/slack/inbound.ts +30 -10
  143. package/src/channels/slack/index.ts +4 -0
  144. package/src/channels/slack/live-delivery.ts +1 -0
  145. package/src/channels/slack/manifest.ts +3 -0
  146. package/src/channels/slack/nudge-carrier.ts +182 -0
  147. package/src/channels/slack/nudge-store.ts +308 -0
  148. package/src/channels/slack/slack-channel.ts +106 -31
  149. package/src/channels/slack/stop.ts +25 -0
  150. package/src/channels/slack/thread-context.ts +572 -0
  151. package/src/channels/slack/types.ts +27 -0
  152. package/src/internal/cli-mcp.ts +708 -0
  153. package/src/internal/distribution.ts +2 -0
  154. package/src/internal/host-kv.ts +85 -0
  155. package/src/internal/server.ts +31 -0
  156. package/src/internal/session-engine.ts +351 -50
  157. package/src/internal/storage-coordinator.ts +41 -0
  158. package/src/storage-backends/file-kv.ts +221 -0
  159. package/src/storage-backends/postgres-kv.ts +248 -0
  160. package/src/storage.ts +11 -1
  161. package/src/types.ts +83 -0
  162. package/dist/channels/slack/post-update-delivery.d.ts +0 -85
  163. package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
  164. package/dist/docs/assets/chunks/@localSearchIndexroot.B7UcKvIn.js +0 -1
  165. package/dist/docs/assets/example-agents_index.md.CRqJlnIf.js +0 -2
  166. package/dist/internal/json-dir-store.d.ts +0 -32
  167. package/dist/internal/json-dir-store.d.ts.map +0 -1
  168. package/dist/internal/persistence-coordinator.d.ts +0 -127
  169. package/dist/internal/persistence-coordinator.d.ts.map +0 -1
  170. package/dist/persistence.d.ts +0 -184
  171. package/dist/persistence.d.ts.map +0 -1
  172. package/dist/playground/assets/index-72vCOBWO.js +0 -86
  173. package/dist/playground/assets/index-BjnMwYoR.css +0 -1
@@ -65,6 +65,10 @@ Usage:
65
65
  ${CLI} call <tool> [--input <json>] [--dir <path> | --url <baseUrl> | --prod]
66
66
  [--session <id>] [--slug <slug>] [--team <id>]
67
67
  ${CLI} init [dir] [--json]
68
+ ${CLI} mcp --prod [--slug <slug>] [--team <id>] [--dir <path>]
69
+ | --url <baseUrl> [--bearer-token <t>]
70
+ ${CLI} mcp install [--name <serverName>] [--print] [--json]
71
+ (same target flags as mcp)
68
72
  ${CLI} eval [--dir <path>] [evalId...] [--list] [--json]
69
73
  [--tag <tag>]... [--verbose] [--timeout-ms <n>]
70
74
  [--no-stream] [--no-wait]
@@ -134,6 +138,15 @@ Commands:
134
138
  run login when unsigned, then print next steps. --json is
135
139
  machine-readable and never blocks on login. No directory:
136
140
  print the setup guide.
141
+ mcp Serve this agent's MCP endpoint over stdio for MCP clients
142
+ that spawn local servers (e.g. Cursor). Proxies
143
+ newline-delimited JSON-RPC to <target>/v1/mcp. --prod resolves
144
+ the hosted deployment with the signed-in Cursor account and
145
+ re-mints short-lived engine credentials as they expire;
146
+ --url targets a running server. stdout is reserved for the
147
+ MCP wire. \`mcp install\` writes the matching entry into
148
+ ~/.cursor/mcp.json (--print to preview; --name to override
149
+ the server name).
137
150
  eval Run filesystem evals under evals/**/*.eval.ts (streams on a TTY).
138
151
  Default: ephemeral local harness against evals on disk. Pass
139
152
  --prod to kick off the batch on the team's hosted deployment
@@ -390,8 +403,9 @@ function main(argv) {
390
403
  });
391
404
  const dir = (_h = values.dir) !== null && _h !== void 0 ? _h : ".";
392
405
  // Best-effort, cached, and silent on failure. `update` does its own fresh
393
- // lookup, and --json runs stay quiet for machine consumers.
394
- if (command !== "update" && values.json !== true) {
406
+ // lookup, --json runs stay quiet for machine consumers, and `mcp` skips it
407
+ // so MCP client spawns stay fast (stdout there is the protocol wire).
408
+ if (command !== "update" && command !== "mcp" && values.json !== true) {
395
409
  yield warnIfOutdated();
396
410
  }
397
411
  switch (command) {
@@ -452,6 +466,21 @@ function main(argv) {
452
466
  json: values.json,
453
467
  });
454
468
  }
469
+ case "mcp": {
470
+ const { cmdMcp } = yield import("../internal/cli-mcp.js");
471
+ return cmdMcp(positionals[0], {
472
+ dir,
473
+ slug: values.slug,
474
+ team: values.team,
475
+ apiKey: values["api-key"],
476
+ url: values.url,
477
+ prod: values.prod,
478
+ bearerToken: values["bearer-token"],
479
+ name: values.name,
480
+ print: values.print,
481
+ json: values.json,
482
+ });
483
+ }
455
484
  case "login": {
456
485
  const { cmdLogin } = yield import("../internal/cli-cursor.js");
457
486
  return cmdLogin({
@@ -6,7 +6,10 @@
6
6
  * - Top-level posts only by default; thread replies never dispatch unless
7
7
  * `posts: "all"` is configured.
8
8
  * - Posts that @mention the bot are skipped here (the `app_mention` path
9
- * owns them), and bot-authored posts are dropped upstream in parsing.
9
+ * owns them).
10
+ * - Bot-authored posts are dropped unless `includeBotPosts` is configured
11
+ * (alert feeds / webhook integrations); the watching bot's own posts are
12
+ * always dropped either way (loop safety).
10
13
  * - An optional debounce window lets rapid edits settle: edits update the
11
14
  * pending text, deleting the post cancels the dispatch.
12
15
  */
@@ -40,6 +43,8 @@ export declare function createChannelPostWatcher(args: {
40
43
  allowlist: SlackChannelAllowlist;
41
44
  /** Bot user id (from `auth.test`) so bot mentions route via app_mention. */
42
45
  getBotUserId: () => string | undefined;
46
+ /** This app's own `bot_id` (from `auth.test`) for self-post loop safety. */
47
+ getBotId?: () => string | undefined;
43
48
  dispatch: (message: SlackMessage, eventId?: string) => void;
44
49
  log?: SlackLog;
45
50
  }): ChannelPostWatcher;
@@ -1 +1 @@
1
- {"version":3,"file":"channel-watch.d.ts","sourceRoot":"","sources":["../../../src/channels/slack/channel-watch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,KAAK,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAIzE,sEAAsE;AACtE,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAE3C,YAAY,OAAO,EAAE,SAAS,MAAM,EAAE,EAerC;IAED,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAElC;IAED,eAAe,IAAI,MAAM,EAAE,CAE1B;IAED,uDAAuD;IACvD,kBAAkB,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAO9D;CACF;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,GAAG,GAAE,QAA0B,GAC9B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAiC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,MAAM,CAAC,KAAK,EAAE,wBAAwB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACnE,2CAA2C;IAC3C,IAAI,IAAI,IAAI,CAAC;CACd;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,wBAAwB,CAAC;IAClC,SAAS,EAAE,qBAAqB,CAAC;IACjC,4EAA4E;IAC5E,YAAY,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,GAAG,CAAC,EAAE,QAAQ,CAAC;CAChB,GAAG,kBAAkB,CAoGrB"}
1
+ {"version":3,"file":"channel-watch.d.ts","sourceRoot":"","sources":["../../../src/channels/slack/channel-watch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,KAAK,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAIzE,sEAAsE;AACtE,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAE3C,YAAY,OAAO,EAAE,SAAS,MAAM,EAAE,EAerC;IAED,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAElC;IAED,eAAe,IAAI,MAAM,EAAE,CAE1B;IAED,uDAAuD;IACvD,kBAAkB,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAO9D;CACF;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,GAAG,GAAE,QAA0B,GAC9B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAiC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,MAAM,CAAC,KAAK,EAAE,wBAAwB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACnE,2CAA2C;IAC3C,IAAI,IAAI,IAAI,CAAC;CACd;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,wBAAwB,CAAC;IAClC,SAAS,EAAE,qBAAqB,CAAC;IACjC,4EAA4E;IAC5E,YAAY,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,GAAG,CAAC,EAAE,QAAQ,CAAC;CAChB,GAAG,kBAAkB,CA0HrB"}
@@ -6,7 +6,10 @@
6
6
  * - Top-level posts only by default; thread replies never dispatch unless
7
7
  * `posts: "all"` is configured.
8
8
  * - Posts that @mention the bot are skipped here (the `app_mention` path
9
- * owns them), and bot-authored posts are dropped upstream in parsing.
9
+ * owns them).
10
+ * - Bot-authored posts are dropped unless `includeBotPosts` is configured
11
+ * (alert feeds / webhook integrations); the watching bot's own posts are
12
+ * always dropped either way (loop safety).
10
13
  * - An optional debounce window lets rapid edits settle: edits update the
11
14
  * pending text, deleting the post cancels the dispatch.
12
15
  */
@@ -100,6 +103,7 @@ export function createChannelPostWatcher(args) {
100
103
  var _a, _b;
101
104
  const log = (_a = args.log) !== null && _a !== void 0 ? _a : defaultSlackLog;
102
105
  const includeReplies = args.options.posts === "all";
106
+ const includeBotPosts = args.options.includeBotPosts === true;
103
107
  const debounceMs = (_b = args.options.debounceMs) !== null && _b !== void 0 ? _b : 0;
104
108
  const pending = new Map();
105
109
  const pendingKey = (channelId, ts) => `${channelId}:${ts}`;
@@ -122,6 +126,7 @@ export function createChannelPostWatcher(args) {
122
126
  }
123
127
  return {
124
128
  handle(event, eventId) {
129
+ var _a;
125
130
  if (event.kind === "deleted") {
126
131
  const key = pendingKey(event.channelId, event.ts);
127
132
  const entry = pending.get(key);
@@ -151,6 +156,22 @@ export function createChannelPostWatcher(args) {
151
156
  return skip("thread reply (posts: top-level)", message);
152
157
  }
153
158
  const botUserId = args.getBotUserId();
159
+ const author = message.author;
160
+ if ((author === null || author === void 0 ? void 0 : author.isBot) === true) {
161
+ if (!includeBotPosts) {
162
+ return skip("bot-authored post (includeBotPosts off)", message);
163
+ }
164
+ const selfBotId = (_a = args.getBotId) === null || _a === void 0 ? void 0 : _a.call(args);
165
+ if (selfBotId === undefined && botUserId === undefined) {
166
+ return skip("bot-authored post (bot identity unknown; loop safety)", message);
167
+ }
168
+ if ((selfBotId !== undefined && author.botId === selfBotId) ||
169
+ (botUserId !== undefined &&
170
+ author.userId !== "" &&
171
+ author.userId === botUserId)) {
172
+ return skip("own post (loop safety)", message);
173
+ }
174
+ }
154
175
  if (botUserId !== undefined && message.text.includes(`<@${botUserId}>`)) {
155
176
  return skip("bot mention (app_mention path owns it)", message);
156
177
  }
@@ -72,5 +72,9 @@ export declare function dispatchCursorSlackEnvelope(args: {
72
72
  envelope: CursorSlackEnvelope;
73
73
  send: SendMessageFn;
74
74
  host: HostContext;
75
+ hasContinuationSession?: (continuationToken: string) => Promise<boolean>;
76
+ isContinuationBusy?: (continuationToken: string) => Promise<boolean>;
77
+ getContinuationLastBotMessageTs?: (continuationToken: string) => Promise<string | undefined>;
78
+ interruptContinuation?: (continuationToken: string) => Promise<boolean>;
75
79
  }): Promise<void>;
76
80
  //# sourceMappingURL=cursor-account.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cursor-account.d.ts","sourceRoot":"","sources":["../../../src/channels/slack/cursor-account.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,EACV,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAEhB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAEpB;;;;GAIG;AACH,MAAM,WAAW,wBAAyB,SAAQ,iBAAiB;IACjE,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;CAC7C;AAED,wBAAgB,8BAA8B,IAAI,wBAAwB,CAezE;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,sEAAsE;AACtE,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,0EAA0E;IAC1E,WAAW,EAAE,gBAAgB,CAAC;IAC9B,SAAS,EAAE,wBAAwB,CAAC;IACpC,QAAQ,EAAE,aAAa,CAAC;IACxB,YAAY,EAAE,mBAAmB,CAAC;IAClC,eAAe,EAAE,mBAAmB,CAAC;IACrC,MAAM,EAAE,MAAM,QAAQ,CAAC;CACxB;AAID,wBAAgB,iCAAiC,CAC/C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,yBAAyB,GACjC,IAAI,CAEN;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,GACjB,yBAAyB,GAAG,SAAS,CAEvC;AAED;;;GAGG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE;IACtD,OAAO,EAAE,yBAAyB,CAAC;IACnC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,WAAW,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDhB"}
1
+ {"version":3,"file":"cursor-account.d.ts","sourceRoot":"","sources":["../../../src/channels/slack/cursor-account.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,EACV,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAEhB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAEpB;;;;GAIG;AACH,MAAM,WAAW,wBAAyB,SAAQ,iBAAiB;IACjE,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;CAC7C;AAED,wBAAgB,8BAA8B,IAAI,wBAAwB,CAezE;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,sEAAsE;AACtE,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,0EAA0E;IAC1E,WAAW,EAAE,gBAAgB,CAAC;IAC9B,SAAS,EAAE,wBAAwB,CAAC;IACpC,QAAQ,EAAE,aAAa,CAAC;IACxB,YAAY,EAAE,mBAAmB,CAAC;IAClC,eAAe,EAAE,mBAAmB,CAAC;IACrC,MAAM,EAAE,MAAM,QAAQ,CAAC;CACxB;AAID,wBAAgB,iCAAiC,CAC/C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,yBAAyB,GACjC,IAAI,CAEN;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,GACjB,yBAAyB,GAAG,SAAS,CAEvC;AAED;;;GAGG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE;IACtD,OAAO,EAAE,yBAAyB,CAAC;IACnC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,WAAW,CAAC;IAClB,sBAAsB,CAAC,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzE,kBAAkB,CAAC,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrE,+BAA+B,CAAC,EAAE,CAChC,iBAAiB,EAAE,MAAM,KACtB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACjC,qBAAqB,CAAC,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACzE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+DhB"}
@@ -81,8 +81,18 @@ export function dispatchCursorSlackEnvelope(args) {
81
81
  const isDm = envelope.channelType === "im" || envelope.channelId.startsWith("D");
82
82
  const kind = isDm ? "direct_message" : "app_mention";
83
83
  const handler = isDm ? runtime.onDirectMessage : runtime.onAppMention;
84
- yield dispatchInboundMessage(Object.assign({ kind,
84
+ yield dispatchInboundMessage(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ kind,
85
85
  message,
86
- handler, send: args.send, credentials: runtime.credentials, host: args.host, log }, (envelope.eventId === undefined ? {} : { eventId: envelope.eventId })));
86
+ handler, send: args.send, credentials: runtime.credentials, host: args.host, log }, (args.hasContinuationSession === undefined
87
+ ? {}
88
+ : { hasContinuationSession: args.hasContinuationSession })), (args.isContinuationBusy === undefined
89
+ ? {}
90
+ : { isContinuationBusy: args.isContinuationBusy })), (args.getContinuationLastBotMessageTs === undefined
91
+ ? {}
92
+ : {
93
+ getContinuationLastBotMessageTs: args.getContinuationLastBotMessageTs,
94
+ })), (args.interruptContinuation === undefined
95
+ ? {}
96
+ : { interruptContinuation: args.interruptContinuation })), (envelope.eventId === undefined ? {} : { eventId: envelope.eventId })));
87
97
  });
88
98
  }
@@ -4,13 +4,16 @@
4
4
  import type { ChannelEventHandlers } from "../../types.js";
5
5
  import { type SlackLog } from "./log.js";
6
6
  import type { SlackChannelState, SlackCredentials, SlackMessageHandler, SlackSuggestedPrompt } from "./types.js";
7
- export declare const defaultOnAppMention: SlackMessageHandler;
8
- export declare const defaultOnDirectMessage: SlackMessageHandler;
7
+ /** Default mention/DM inbound handler (optional custom loading rotation). */
8
+ export declare function createDefaultOnInbound(loadingMessages?: readonly string[]): SlackMessageHandler;
9
9
  /**
10
- * Default watched-channel-post handler: same thread-scoped principal as
11
- * mentions (so a later @mention continues the session), tagged with an
12
- * `engagement: "channel_post"` attribute for auth policies.
10
+ * Default watched-channel-post handler (optional custom loading rotation).
11
+ * Same thread-scoped principal as mentions, tagged with
12
+ * `engagement: "channel_post"` for auth policies.
13
13
  */
14
+ export declare function createDefaultOnChannelPost(loadingMessages?: readonly string[]): SlackMessageHandler;
15
+ export declare const defaultOnAppMention: SlackMessageHandler;
16
+ export declare const defaultOnDirectMessage: SlackMessageHandler;
14
17
  export declare const defaultOnChannelPost: SlackMessageHandler;
15
18
  export interface BuildDefaultEventsOptions {
16
19
  credentials?: SlackCredentials;
@@ -18,6 +21,8 @@ export interface BuildDefaultEventsOptions {
18
21
  thinkingSteps?: boolean;
19
22
  suggestedPrompts?: SlackSuggestedPrompt[];
20
23
  suggestedPromptsTitle?: string;
24
+ /** Rotating assistant status lines; defaults to pack constants. */
25
+ loadingMessages?: readonly string[];
21
26
  /** Resolved per-call so channel onStart can rebind the server logger. */
22
27
  getLog?: () => SlackLog;
23
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../../src/channels/slack/defaults.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAe3D,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAQpB,eAAO,MAAM,mBAAmB,EAAE,mBAAsC,CAAC;AACzE,eAAO,MAAM,sBAAsB,EAAE,mBAAsC,CAAC;AAE5E;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,mBAOlC,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAC1C,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,QAAQ,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,yBAA8B,GACtC,oBAAoB,CAAC,iBAAiB,CAAC,CA2XzC"}
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../../src/channels/slack/defaults.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAe3D,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAWpB,6EAA6E;AAC7E,wBAAgB,sBAAsB,CACpC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,GAClC,mBAAmB,CAOrB;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,GAClC,mBAAmB,CASrB;AAED,eAAO,MAAM,mBAAmB,EAAE,mBACR,CAAC;AAC3B,eAAO,MAAM,sBAAsB,EAAE,mBACX,CAAC;AAC3B,eAAO,MAAM,oBAAoB,EAAE,mBACL,CAAC;AAE/B,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAC1C,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mEAAmE;IACnE,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,QAAQ,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,yBAA8B,GACtC,oBAAoB,CAAC,iBAAiB,CAAC,CAiYzC"}
@@ -17,23 +17,39 @@ import { SLACK_DEFAULT_LOADING_MESSAGES, SLACK_TYPING_MIN_GROWTH, SLACK_TYPING_M
17
17
  import { createStreamDeliveryStrategy } from "./live-delivery.js";
18
18
  import { defaultSlackLog } from "./log.js";
19
19
  import { buildTaskChunkForResult, buildTaskChunksForCalls } from "./stream.js";
20
- const defaultOnInbound = (ctx, message) => __awaiter(void 0, void 0, void 0, function* () {
21
- yield ctx.thread.startTyping("Thinking…");
22
- const auth = defaultSlackAuth(message, ctx);
23
- return auth === null ? null : { auth };
24
- });
25
- export const defaultOnAppMention = defaultOnInbound;
26
- export const defaultOnDirectMessage = defaultOnInbound;
20
+ function resolveLoadingMessages(loadingMessages) {
21
+ if (loadingMessages !== undefined && loadingMessages.length > 0) {
22
+ return [...loadingMessages];
23
+ }
24
+ return [...SLACK_DEFAULT_LOADING_MESSAGES];
25
+ }
26
+ /** Default mention/DM inbound handler (optional custom loading rotation). */
27
+ export function createDefaultOnInbound(loadingMessages) {
28
+ const rotating = resolveLoadingMessages(loadingMessages);
29
+ return (ctx, message) => __awaiter(this, void 0, void 0, function* () {
30
+ yield ctx.thread.startTyping("Thinking…", rotating);
31
+ const auth = defaultSlackAuth(message, ctx);
32
+ return auth === null ? null : { auth };
33
+ });
34
+ }
27
35
  /**
28
- * Default watched-channel-post handler: same thread-scoped principal as
29
- * mentions (so a later @mention continues the session), tagged with an
30
- * `engagement: "channel_post"` attribute for auth policies.
36
+ * Default watched-channel-post handler (optional custom loading rotation).
37
+ * Same thread-scoped principal as mentions, tagged with
38
+ * `engagement: "channel_post"` for auth policies.
31
39
  */
32
- export const defaultOnChannelPost = (ctx, message) => __awaiter(void 0, void 0, void 0, function* () {
33
- yield ctx.thread.startTyping("Thinking…");
34
- const auth = defaultSlackAuth(message, ctx, { engagement: "channel_post" });
35
- return auth === null ? null : { auth };
36
- });
40
+ export function createDefaultOnChannelPost(loadingMessages) {
41
+ const rotating = resolveLoadingMessages(loadingMessages);
42
+ return (ctx, message) => __awaiter(this, void 0, void 0, function* () {
43
+ yield ctx.thread.startTyping("Thinking…", rotating);
44
+ const auth = defaultSlackAuth(message, ctx, {
45
+ engagement: "channel_post",
46
+ });
47
+ return auth === null ? null : { auth };
48
+ });
49
+ }
50
+ export const defaultOnAppMention = createDefaultOnInbound();
51
+ export const defaultOnDirectMessage = createDefaultOnInbound();
52
+ export const defaultOnChannelPost = createDefaultOnChannelPost();
37
53
  /**
38
54
  * Build default stream-event handlers (typing, streaming, replies).
39
55
  * Streaming / thinking default to ON.
@@ -48,6 +64,7 @@ export function buildDefaultEvents(options = {}) {
48
64
  const thinkingSteps = options.thinkingSteps !== false;
49
65
  const suggestedPrompts = (_a = options.suggestedPrompts) !== null && _a !== void 0 ? _a : [];
50
66
  const suggestedPromptsTitle = options.suggestedPromptsTitle;
67
+ const loadingMessages = resolveLoadingMessages(options.loadingMessages);
51
68
  const botToken = credentials === null || credentials === void 0 ? void 0 : credentials.botToken;
52
69
  const getLog = (_b = options.getLog) !== null && _b !== void 0 ? _b : (() => defaultSlackLog);
53
70
  const strategy = createStreamDeliveryStrategy({ botToken, getLog });
@@ -61,6 +78,7 @@ export function buildDefaultEvents(options = {}) {
61
78
  channel.state.streamTs = null;
62
79
  channel.state.streamChannelId = null;
63
80
  channel.state.lastPostedText = null;
81
+ // Keep lastBotMessageTs across turns — warm delta watermark.
64
82
  channel.state.approvalMessageByCallId = {};
65
83
  const state = channel.state;
66
84
  if (state.channelId === null || state.threadTs === null) {
@@ -77,7 +95,7 @@ export function buildDefaultEvents(options = {}) {
77
95
  channelId: state.channelId,
78
96
  threadTs: state.threadTs,
79
97
  status: "Working…",
80
- loadingMessages: [...SLACK_DEFAULT_LOADING_MESSAGES],
98
+ loadingMessages,
81
99
  });
82
100
  // Retry streaming each turn unless a prior call proved the feature
83
101
  // unsupported for this workspace/app. Thinking stays on setStatus
@@ -143,7 +161,10 @@ export function buildDefaultEvents(options = {}) {
143
161
  channelId: channel.state.channelId,
144
162
  threadTs: channel.state.threadTs,
145
163
  status,
146
- loadingMessages: [status, ...SLACK_DEFAULT_LOADING_MESSAGES],
164
+ loadingMessages: [
165
+ status,
166
+ ...loadingMessages.filter((message) => message !== status),
167
+ ],
147
168
  });
148
169
  channel.state.lastReasoningTypingAtMs = now;
149
170
  channel.state.lastReasoningTypingStatus = status;
@@ -285,6 +306,7 @@ export function buildDefaultEvents(options = {}) {
285
306
  threadTs: state.threadTs,
286
307
  });
287
308
  channel.state.lastPostedText = event.data.text;
309
+ channel.state.lastBotMessageTs = posted.ts;
288
310
  log.info("reply delivered via postMessage", {
289
311
  channel: state.channelId,
290
312
  thread_ts: state.threadTs,
@@ -23,6 +23,20 @@ export interface SlackInboundDispatchDeps {
23
23
  directMessagesEnabled?: boolean;
24
24
  /** Opt-in channel watch; absent = channel posts are ignored. */
25
25
  channelWatch?: ChannelPostWatcher;
26
+ /**
27
+ * When set, full-thread hydration runs only for cold-start turns (no
28
+ * existing session). Warm turns load a `<slack_thread_delta>` instead.
29
+ */
30
+ hasContinuationSession?: (continuationToken: string) => Promise<boolean>;
31
+ /** True when a turn is already running for this Slack continuation. */
32
+ isContinuationBusy?: (continuationToken: string) => Promise<boolean>;
33
+ /**
34
+ * Warm-delta watermark from session channel state (`lastBotMessageTs`).
35
+ * When absent, delta falls back to the last matching bot post in-thread.
36
+ */
37
+ getContinuationLastBotMessageTs?: (continuationToken: string) => Promise<string | undefined>;
38
+ /** Cancel the active turn + clear coalesced nudges for this continuation. */
39
+ interruptContinuation?: (continuationToken: string) => Promise<boolean>;
26
40
  }
27
41
  export declare function dispatchInboundMessage(args: {
28
42
  kind: "app_mention" | "direct_message" | "channel_post";
@@ -31,6 +45,10 @@ export declare function dispatchInboundMessage(args: {
31
45
  send: SendMessageFn;
32
46
  credentials: SlackCredentials | undefined;
33
47
  host: HostContext;
48
+ hasContinuationSession?: (continuationToken: string) => Promise<boolean>;
49
+ isContinuationBusy?: (continuationToken: string) => Promise<boolean>;
50
+ getContinuationLastBotMessageTs?: (continuationToken: string) => Promise<string | undefined>;
51
+ interruptContinuation?: (continuationToken: string) => Promise<boolean>;
34
52
  log?: SlackLog;
35
53
  eventId?: string;
36
54
  }): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../../src/channels/slack/dispatch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAa,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG5E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAO1D,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAEhB,YAAY,EACZ,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAKpB,uCAAuC;AACvC,wBAAgB,sBAAsB,CACpC,KAAK,GAAE,OAAO,CAAC,iBAAiB,CAAM,GACrC,iBAAiB,CAgBnB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAC/C,YAAY,EAAE,mBAAmB,CAAC;IAClC,eAAe,EAAE,mBAAmB,CAAC;IACrC,WAAW,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC1C,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,qDAAqD;IACrD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uDAAuD;IACvD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,gEAAgE;IAChE,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,IAAI,EAAE,aAAa,GAAG,gBAAgB,GAAG,cAAc,CAAC;IACxD,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,IAAI,EAAE,aAAa,CAAC;IACpB,WAAW,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC1C,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2IhB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAoET"}
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../../src/channels/slack/dispatch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAa,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG5E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAO1D,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAM1D,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAEhB,YAAY,EACZ,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAIpB,uCAAuC;AACvC,wBAAgB,sBAAsB,CACpC,KAAK,GAAE,OAAO,CAAC,iBAAiB,CAAM,GACrC,iBAAiB,CAiBnB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAC/C,YAAY,EAAE,mBAAmB,CAAC;IAClC,eAAe,EAAE,mBAAmB,CAAC;IACrC,WAAW,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC1C,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,qDAAqD;IACrD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uDAAuD;IACvD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,gEAAgE;IAChE,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzE,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrE;;;OAGG;IACH,+BAA+B,CAAC,EAAE,CAChC,iBAAiB,EAAE,MAAM,KACtB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACjC,6EAA6E;IAC7E,qBAAqB,CAAC,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACzE;AAiBD,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,IAAI,EAAE,aAAa,GAAG,gBAAgB,GAAG,cAAc,CAAC;IACxD,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,IAAI,EAAE,aAAa,CAAC;IACpB,WAAW,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC1C,IAAI,EAAE,WAAW,CAAC;IAClB,sBAAsB,CAAC,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzE,kBAAkB,CAAC,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrE,+BAA+B,CAAC,EAAE,CAChC,iBAAiB,EAAE,MAAM,KACtB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACjC,qBAAqB,CAAC,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxE,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+OhB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAsGT"}
@@ -10,22 +10,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  step((generator = generator.apply(thisArg, _arguments || [])).next());
11
11
  });
12
12
  };
13
- import { SessionBusyError } from "../../internal/session-engine.js";
14
13
  import { buildSlackBinding, slackContinuationKey } from "./api.js";
15
14
  import { firstLineTitle, maybeSetThreadTitle } from "./assistant.js";
16
15
  import { formatSlackContextBlock, parseAppMentionEvent, parseChannelMessageEvent, parseDirectMessageEvent, } from "./inbound.js";
17
16
  import { defaultSlackLog } from "./log.js";
18
- const BUSY_REPLY = "I'm still working on your previous message in this thread — I'll reply when that finishes. Send another message after that.";
17
+ import { isWholeMessageStop } from "./stop.js";
18
+ import { loadSlackThreadContextBlock, loadSlackThreadDeltaBlock, } from "./thread-context.js";
19
+ const FOLLOWUP_TYPING = "Saw your follow-up…";
19
20
  /** Blank per-session adapter state. */
20
21
  export function emptySlackChannelState(patch = {}) {
21
- return Object.assign({ channelId: null, threadTs: null, teamId: null, triggeringUserId: null, pendingToolCallMessage: null, lastReasoningTypingAtMs: null, lastReasoningTypingStatus: null, streamTs: null, streamChannelId: null, lastPostedText: null, streamingUnavailable: false, approvalMessageByCallId: {} }, patch);
22
+ return Object.assign({ channelId: null, threadTs: null, teamId: null, triggeringUserId: null, pendingToolCallMessage: null, lastReasoningTypingAtMs: null, lastReasoningTypingStatus: null, streamTs: null, streamChannelId: null, lastPostedText: null, lastBotMessageTs: null, streamingUnavailable: false, approvalMessageByCallId: {} }, patch);
23
+ }
24
+ function triggeringMergeMessage(message) {
25
+ var _a, _b;
26
+ return Object.assign(Object.assign(Object.assign({ ts: message.ts }, (((_a = message.author) === null || _a === void 0 ? void 0 : _a.userId) === undefined || message.author.userId === ""
27
+ ? {}
28
+ : { user: message.author.userId })), (((_b = message.author) === null || _b === void 0 ? void 0 : _b.botId) === undefined
29
+ ? {}
30
+ : { bot_id: message.author.botId })), { text: message.text });
22
31
  }
23
32
  export function dispatchInboundMessage(args) {
24
33
  return __awaiter(this, void 0, void 0, function* () {
25
- var _a, _b, _c, _d, _e, _f;
26
- var _g, _h, _j, _k, _l, _m, _o;
34
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
35
+ var _m, _o, _p, _q, _r, _s, _t, _u;
27
36
  const { message, kind } = args;
28
- const log = (_g = args.log) !== null && _g !== void 0 ? _g : defaultSlackLog;
37
+ const log = (_m = args.log) !== null && _m !== void 0 ? _m : defaultSlackLog;
29
38
  const binding = buildSlackBinding(Object.assign({ botToken: (_a = args.credentials) === null || _a === void 0 ? void 0 : _a.botToken, channelId: message.channelId, threadTs: message.threadTs, host: args.host, log }, (message.teamId === undefined ? {} : { teamId: message.teamId })));
30
39
  log.info("inbound", {
31
40
  kind,
@@ -36,9 +45,32 @@ export function dispatchInboundMessage(args) {
36
45
  user: (_b = message.author) === null || _b === void 0 ? void 0 : _b.userId,
37
46
  team: message.teamId,
38
47
  });
48
+ const continuation = slackContinuationKey(message.channelId, message.threadTs);
49
+ // Whole-message stop: cancel active turn + clear nudges (Benny parity).
50
+ if (isWholeMessageStop(message.text) ||
51
+ isWholeMessageStop(message.markdown)) {
52
+ log.info("stop directive", {
53
+ kind,
54
+ channel: message.channelId,
55
+ thread_ts: message.threadTs,
56
+ continuation,
57
+ event_id: args.eventId,
58
+ });
59
+ yield (_c = args.host.slackNudges) === null || _c === void 0 ? void 0 : _c.clear(continuation);
60
+ const interrupted = args.interruptContinuation === undefined
61
+ ? false
62
+ : yield args.interruptContinuation(continuation);
63
+ try {
64
+ yield binding.thread.startTyping(interrupted ? "Stopped." : "Nothing to stop.");
65
+ }
66
+ catch (_v) {
67
+ // best-effort status
68
+ }
69
+ return;
70
+ }
39
71
  if (message.ts === message.threadTs) {
40
72
  yield maybeSetThreadTitle({
41
- botToken: (_c = args.credentials) === null || _c === void 0 ? void 0 : _c.botToken,
73
+ botToken: (_d = args.credentials) === null || _d === void 0 ? void 0 : _d.botToken,
42
74
  channelId: message.channelId,
43
75
  threadTs: message.threadTs,
44
76
  title: firstLineTitle(message.markdown),
@@ -67,47 +99,90 @@ export function dispatchInboundMessage(args) {
67
99
  });
68
100
  return;
69
101
  }
70
- const userText = (_h = decision.message) !== null && _h !== void 0 ? _h : message.markdown;
102
+ // Reply-once / delayed parent: claim channel:ts after the handler accepts.
103
+ const claimed = yield (_e = args.host.slackNudges) === null || _e === void 0 ? void 0 : _e.tryClaimAsk(message.channelId, message.ts);
104
+ if (claimed === false) {
105
+ log.info("inbound ask dedupe drop", {
106
+ kind,
107
+ channel: message.channelId,
108
+ message_ts: message.ts,
109
+ continuation,
110
+ event_id: args.eventId,
111
+ });
112
+ return;
113
+ }
114
+ const userText = (_o = decision.message) !== null && _o !== void 0 ? _o : message.markdown;
115
+ const excludeThreadTimestamps = [
116
+ message.ts,
117
+ ...((_p = decision.excludeThreadTimestamps) !== null && _p !== void 0 ? _p : []),
118
+ ];
119
+ const hasSession = args.hasContinuationSession === undefined
120
+ ? false
121
+ : yield args.hasContinuationSession(continuation);
122
+ const mergeMessages = [triggeringMergeMessage(message)];
123
+ const afterTs = hasSession && args.getContinuationLastBotMessageTs !== undefined
124
+ ? yield args.getContinuationLastBotMessageTs(continuation)
125
+ : undefined;
126
+ // Cold start: full <slack_thread>. Warm: <slack_thread_delta> since last
127
+ // agent post (session watermark), else last matching bot post in-thread.
128
+ const threadContext = hasSession
129
+ ? yield loadSlackThreadDeltaBlock(Object.assign(Object.assign({ botToken: (_f = args.credentials) === null || _f === void 0 ? void 0 : _f.botToken, channelId: message.channelId, threadTs: message.threadTs }, (afterTs === undefined ? {} : { afterTs })), { excludeTs: excludeThreadTimestamps, mergeMessages,
130
+ log }))
131
+ : yield loadSlackThreadContextBlock({
132
+ botToken: (_g = args.credentials) === null || _g === void 0 ? void 0 : _g.botToken,
133
+ channelId: message.channelId,
134
+ threadTs: message.threadTs,
135
+ excludeTs: excludeThreadTimestamps,
136
+ mergeMessages,
137
+ log,
138
+ });
139
+ if (threadContext !== undefined) {
140
+ log.info(hasSession ? "thread delta loaded" : "thread context loaded", Object.assign({ kind, channel: message.channelId, thread_ts: message.threadTs, chars: threadContext.length, event_id: args.eventId }, (afterTs === undefined ? {} : { after_ts: afterTs })));
141
+ }
71
142
  const prompt = [
72
- formatSlackContextBlock(Object.assign(Object.assign({ userId: (_j = (_d = message.author) === null || _d === void 0 ? void 0 : _d.userId) !== null && _j !== void 0 ? _j : "", channelId: message.channelId, threadTs: message.threadTs }, (((_e = message.author) === null || _e === void 0 ? void 0 : _e.userName) === undefined
143
+ formatSlackContextBlock(Object.assign(Object.assign({ userId: (_q = (_h = message.author) === null || _h === void 0 ? void 0 : _h.userId) !== null && _q !== void 0 ? _q : "", channelId: message.channelId, threadTs: message.threadTs }, (((_j = message.author) === null || _j === void 0 ? void 0 : _j.userName) === undefined
73
144
  ? {}
74
145
  : { userName: message.author.userName })), (message.teamId === undefined ? {} : { teamId: message.teamId }))),
75
- ...((_k = decision.context) !== null && _k !== void 0 ? _k : []),
146
+ ...(threadContext === undefined ? [] : [threadContext]),
147
+ ...((_r = decision.context) !== null && _r !== void 0 ? _r : []),
76
148
  "",
77
149
  userText,
78
150
  ]
79
151
  .filter((part) => part.length > 0)
80
152
  .join("\n");
81
- const continuation = slackContinuationKey(message.channelId, message.threadTs);
153
+ const sendOptions = {
154
+ auth: decision.auth,
155
+ continuationToken: continuation,
156
+ admission: "coalesce",
157
+ coalesceSourceTs: message.ts,
158
+ state: emptySlackChannelState({
159
+ channelId: message.channelId,
160
+ threadTs: message.threadTs,
161
+ teamId: (_s = message.teamId) !== null && _s !== void 0 ? _s : null,
162
+ triggeringUserId: (_t = (_k = message.author) === null || _k === void 0 ? void 0 : _k.userId) !== null && _t !== void 0 ? _t : null,
163
+ }),
164
+ title: (_u = decision.title) !== null && _u !== void 0 ? _u : `slack:${message.channelId}:${message.threadTs}`,
165
+ workspaceDir: decision.workspaceDir,
166
+ workspaceFiles: decision.workspaceFiles,
167
+ cloud: decision.cloud,
168
+ sdkAgentId: decision.sdkAgentId,
169
+ };
82
170
  try {
83
- const session = yield args.send(prompt, {
84
- auth: decision.auth,
85
- continuationToken: continuation,
86
- state: emptySlackChannelState({
87
- channelId: message.channelId,
88
- threadTs: message.threadTs,
89
- teamId: (_l = message.teamId) !== null && _l !== void 0 ? _l : null,
90
- triggeringUserId: (_m = (_f = message.author) === null || _f === void 0 ? void 0 : _f.userId) !== null && _m !== void 0 ? _m : null,
91
- }),
92
- title: (_o = decision.title) !== null && _o !== void 0 ? _o : `slack:${message.channelId}:${message.threadTs}`,
93
- workspaceDir: decision.workspaceDir,
94
- workspaceFiles: decision.workspaceFiles,
95
- cloud: decision.cloud,
96
- sdkAgentId: decision.sdkAgentId,
97
- });
98
- log.info("session start", {
171
+ const session = yield args.send(prompt, sendOptions);
172
+ log.info(session.coalesced === true ? "session coalesced" : "session start", {
99
173
  kind,
100
174
  session: session.id,
101
175
  is_new: session.isNew,
176
+ coalesced: session.coalesced === true,
102
177
  continuation,
103
178
  channel: message.channelId,
104
179
  thread_ts: message.threadTs,
105
180
  event_id: args.eventId,
106
181
  });
107
- }
108
- catch (error) {
109
- if (error instanceof SessionBusyError) {
110
- log.info("session busy; notifying thread", {
182
+ // Coalesce admits leave the active turn running; follow-ups drain into one
183
+ // boundary turn when it finishes (no mid-turn tool/hook inject).
184
+ if (session.coalesced === true) {
185
+ log.info("session busy; coalescing follow-up", {
111
186
  kind,
112
187
  channel: message.channelId,
113
188
  thread_ts: message.threadTs,
@@ -115,17 +190,21 @@ export function dispatchInboundMessage(args) {
115
190
  event_id: args.eventId,
116
191
  });
117
192
  try {
118
- yield binding.thread.post(BUSY_REPLY);
193
+ yield binding.thread.startTyping(FOLLOWUP_TYPING);
119
194
  }
120
- catch (postError) {
121
- log.error(`busy reply failed: ${postError instanceof Error ? postError.message : String(postError)}`, {
195
+ catch (typingError) {
196
+ log.error(`follow-up typing failed: ${typingError instanceof Error
197
+ ? typingError.message
198
+ : String(typingError)}`, {
122
199
  channel: message.channelId,
123
200
  thread_ts: message.threadTs,
124
201
  event_id: args.eventId,
125
202
  });
126
203
  }
127
- return;
128
204
  }
205
+ }
206
+ catch (error) {
207
+ yield (_l = args.host.slackNudges) === null || _l === void 0 ? void 0 : _l.releaseAsk(message.channelId, message.ts);
129
208
  log.error(`${kind} delivery failed: ${error instanceof Error ? error.message : String(error)}`, {
130
209
  channel: message.channelId,
131
210
  thread_ts: message.threadTs,
@@ -160,7 +239,17 @@ export function dispatchInboundFromEventBody(body, deps) {
160
239
  log.debug("inbound ignored (mentions disabled)", { event_id: eventId });
161
240
  return false;
162
241
  }
163
- deps.waitUntil(dispatchInboundMessage(Object.assign({ kind: "app_mention", message: mention, handler: deps.onAppMention, send: deps.send, credentials: deps.credentials, host: deps.host, log }, (eventId === undefined ? {} : { eventId }))));
242
+ deps.waitUntil(dispatchInboundMessage(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ kind: "app_mention", message: mention, handler: deps.onAppMention, send: deps.send, credentials: deps.credentials, host: deps.host, log }, (deps.hasContinuationSession === undefined
243
+ ? {}
244
+ : { hasContinuationSession: deps.hasContinuationSession })), (deps.isContinuationBusy === undefined
245
+ ? {}
246
+ : { isContinuationBusy: deps.isContinuationBusy })), (deps.getContinuationLastBotMessageTs === undefined
247
+ ? {}
248
+ : {
249
+ getContinuationLastBotMessageTs: deps.getContinuationLastBotMessageTs,
250
+ })), (deps.interruptContinuation === undefined
251
+ ? {}
252
+ : { interruptContinuation: deps.interruptContinuation })), (eventId === undefined ? {} : { eventId }))));
164
253
  return true;
165
254
  }
166
255
  const dm = parseDirectMessageEvent(body);
@@ -171,11 +260,25 @@ export function dispatchInboundFromEventBody(body, deps) {
171
260
  });
172
261
  return false;
173
262
  }
174
- deps.waitUntil(dispatchInboundMessage(Object.assign({ kind: "direct_message", message: dm, handler: deps.onDirectMessage, send: deps.send, credentials: deps.credentials, host: deps.host, log }, (eventId === undefined ? {} : { eventId }))));
263
+ deps.waitUntil(dispatchInboundMessage(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ kind: "direct_message", message: dm, handler: deps.onDirectMessage, send: deps.send, credentials: deps.credentials, host: deps.host, log }, (deps.hasContinuationSession === undefined
264
+ ? {}
265
+ : { hasContinuationSession: deps.hasContinuationSession })), (deps.isContinuationBusy === undefined
266
+ ? {}
267
+ : { isContinuationBusy: deps.isContinuationBusy })), (deps.getContinuationLastBotMessageTs === undefined
268
+ ? {}
269
+ : {
270
+ getContinuationLastBotMessageTs: deps.getContinuationLastBotMessageTs,
271
+ })), (deps.interruptContinuation === undefined
272
+ ? {}
273
+ : { interruptContinuation: deps.interruptContinuation })), (eventId === undefined ? {} : { eventId }))));
175
274
  return true;
176
275
  }
177
276
  if (deps.channelWatch !== undefined) {
178
- const channelEvent = parseChannelMessageEvent(body);
277
+ // Parse keeps bot-authored posts; the watcher enforces the per-channel
278
+ // policy (includeBotPosts, own-post loop safety) with skip logging.
279
+ const channelEvent = parseChannelMessageEvent(body, {
280
+ includeBotAuthored: true,
281
+ });
179
282
  if (channelEvent !== null) {
180
283
  return deps.channelWatch.handle(channelEvent, eventId);
181
284
  }