@cursor/july 0.1.7 → 0.1.9

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 (210) 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/post-update-delivery.js +318 -0
  31. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  32. package/dist/channels/slack/slack-channel.js +74 -25
  33. package/dist/channels/slack/stop.d.ts +14 -0
  34. package/dist/channels/slack/stop.d.ts.map +1 -0
  35. package/dist/channels/slack/stop.js +22 -0
  36. package/dist/channels/slack/thread-context.d.ts +81 -0
  37. package/dist/channels/slack/thread-context.d.ts.map +1 -0
  38. package/dist/channels/slack/thread-context.js +470 -0
  39. package/dist/channels/slack/types.d.ts +27 -0
  40. package/dist/channels/slack/types.d.ts.map +1 -1
  41. package/dist/docs/404.html +2 -2
  42. package/dist/docs/ab.html +4 -4
  43. package/dist/docs/assets/{app.COTN7wgo.js → app.Ciosf24M.js} +1 -1
  44. package/dist/docs/assets/chunks/@localSearchIndexroot.D154Detj.js +1 -0
  45. package/dist/docs/assets/chunks/{VPLocalSearchBox.BW3TBdT0.js → VPLocalSearchBox.BcRDly7n.js} +1 -1
  46. package/dist/docs/assets/chunks/{theme.BEJW0vE7.js → theme.CMpbjdfx.js} +2 -2
  47. package/dist/docs/assets/{deployment.md.BtfEsc9S.js → deployment.md.DC5-ImcM.js} +1 -1
  48. package/dist/docs/assets/{example-agents_approval-buddy.md.8R5phXb5.js → example-agents_approval-buddy.md.DFGBYLcc.js} +2 -2
  49. package/dist/docs/assets/{example-agents_approval-buddy.md.8R5phXb5.lean.js → example-agents_approval-buddy.md.DFGBYLcc.lean.js} +1 -1
  50. package/dist/docs/assets/example-agents_index.md.BPXjUtRB.js +2 -0
  51. package/dist/docs/assets/example-agents_index.md.BPXjUtRB.lean.js +1 -0
  52. package/dist/docs/assets/example-agents_oncall.md.BG_sUMly.js +10 -0
  53. package/dist/docs/assets/example-agents_oncall.md.BG_sUMly.lean.js +1 -0
  54. package/dist/docs/assets/{example-agents_security-reviewer.md.D2rtwDTO.js → example-agents_security-reviewer.md.CE9v-aKm.js} +2 -2
  55. package/dist/docs/assets/{example-agents_security-reviewer.md.D2rtwDTO.lean.js → example-agents_security-reviewer.md.CE9v-aKm.lean.js} +1 -1
  56. package/dist/docs/assets/{guides_slack.md.CCwqHvSV.js → guides_slack.md.FWale6Ip.js} +4 -3
  57. package/dist/docs/assets/{guides_slack.md.CCwqHvSV.lean.js → guides_slack.md.FWale6Ip.lean.js} +1 -1
  58. package/dist/docs/assets/{reference_sessions.md.DdjWabnN.js → reference_sessions.md.DLd6mvbv.js} +2 -2
  59. package/dist/docs/assets/{reference_sessions.md.DdjWabnN.lean.js → reference_sessions.md.DLd6mvbv.lean.js} +1 -1
  60. package/dist/docs/assets/{storage.md.CVnInNiN.js → storage.md.DRTdnFvd.js} +9 -2
  61. package/dist/docs/assets/{storage.md.CVnInNiN.lean.js → storage.md.DRTdnFvd.lean.js} +1 -1
  62. package/dist/docs/building-with-agents.html +4 -4
  63. package/dist/docs/concepts.html +4 -4
  64. package/dist/docs/deployment.html +6 -6
  65. package/dist/docs/evals.html +4 -4
  66. package/dist/docs/example-agents/approval-buddy.html +5 -5
  67. package/dist/docs/example-agents/benny.html +5 -5
  68. package/dist/docs/example-agents/bugbot.html +5 -5
  69. package/dist/docs/example-agents/codebase-wiki.html +4 -4
  70. package/dist/docs/example-agents/codeowners-review.html +4 -4
  71. package/dist/docs/example-agents/concierge.html +4 -4
  72. package/dist/docs/example-agents/fsd.html +4 -4
  73. package/dist/docs/example-agents/index.html +6 -6
  74. package/dist/docs/example-agents/knowledge-base.html +4 -4
  75. package/dist/docs/example-agents/oncall.html +34 -0
  76. package/dist/docs/example-agents/security-reviewer.html +6 -6
  77. package/dist/docs/example-agents/slack-agent.html +4 -4
  78. package/dist/docs/example-agents/weather-agent.html +4 -4
  79. package/dist/docs/guides/agent-to-agent.html +4 -4
  80. package/dist/docs/guides/cloud-runtime.html +4 -4
  81. package/dist/docs/guides/github.html +4 -4
  82. package/dist/docs/guides/human-in-the-loop.html +4 -4
  83. package/dist/docs/guides/slack.html +7 -6
  84. package/dist/docs/guides/webhooks.html +4 -4
  85. package/dist/docs/hashmap.json +1 -1
  86. package/dist/docs/hillclimbing.html +4 -4
  87. package/dist/docs/index.html +4 -4
  88. package/dist/docs/quickstart.html +4 -4
  89. package/dist/docs/reference/agent-config.html +4 -4
  90. package/dist/docs/reference/channels.html +4 -4
  91. package/dist/docs/reference/cli.html +4 -4
  92. package/dist/docs/reference/connections.html +4 -4
  93. package/dist/docs/reference/hooks.html +4 -4
  94. package/dist/docs/reference/http-api.html +4 -4
  95. package/dist/docs/reference/instructions.html +4 -4
  96. package/dist/docs/reference/playground.html +4 -4
  97. package/dist/docs/reference/project-layout.html +4 -4
  98. package/dist/docs/reference/schedules.html +4 -4
  99. package/dist/docs/reference/sessions.html +5 -5
  100. package/dist/docs/reference/skills.html +4 -4
  101. package/dist/docs/reference/subagents.html +4 -4
  102. package/dist/docs/reference/tools.html +4 -4
  103. package/dist/docs/scaffolding-agents.html +4 -4
  104. package/dist/docs/storage.html +13 -6
  105. package/dist/docs/troubleshooting.html +4 -4
  106. package/dist/internal/cli-deploy.d.ts.map +1 -1
  107. package/dist/internal/cli-deploy.js +45 -18
  108. package/dist/internal/cli-mcp.d.ts +64 -0
  109. package/dist/internal/cli-mcp.d.ts.map +1 -0
  110. package/dist/internal/cli-mcp.js +570 -0
  111. package/dist/internal/deploy-client.d.ts +7 -1
  112. package/dist/internal/deploy-client.d.ts.map +1 -1
  113. package/dist/internal/deploy-client.js +3 -0
  114. package/dist/internal/deploy-source.d.ts +3 -0
  115. package/dist/internal/deploy-source.d.ts.map +1 -1
  116. package/dist/internal/deploy-source.js +25 -7
  117. package/dist/internal/discovery.js +1 -1
  118. package/dist/internal/distribution.d.ts.map +1 -1
  119. package/dist/internal/distribution.js +3 -0
  120. package/dist/internal/host-kv.d.ts +17 -0
  121. package/dist/internal/host-kv.d.ts.map +1 -0
  122. package/dist/internal/host-kv.js +77 -0
  123. package/dist/internal/hosting.d.ts +12 -2
  124. package/dist/internal/hosting.d.ts.map +1 -1
  125. package/dist/internal/hosting.js +21 -4
  126. package/dist/internal/json-dir-store.js +100 -0
  127. package/dist/internal/logs-client.d.ts +16 -2
  128. package/dist/internal/logs-client.d.ts.map +1 -1
  129. package/dist/internal/logs-client.js +51 -16
  130. package/dist/internal/server.d.ts.map +1 -1
  131. package/dist/internal/server.js +8 -0
  132. package/dist/internal/session-engine.d.ts +42 -7
  133. package/dist/internal/session-engine.d.ts.map +1 -1
  134. package/dist/internal/session-engine.js +276 -66
  135. package/dist/internal/storage-coordinator.d.ts +18 -1
  136. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  137. package/dist/internal/storage-coordinator.js +43 -0
  138. package/dist/internal/workspace.d.ts +19 -0
  139. package/dist/internal/workspace.d.ts.map +1 -1
  140. package/dist/internal/workspace.js +47 -13
  141. package/dist/playground/assets/{index-72vCOBWO.js → index-C0-QEG4O.js} +42 -42
  142. package/dist/playground/assets/index-z3JY9O2x.css +1 -0
  143. package/dist/playground/index.html +2 -2
  144. package/dist/storage-backends/cursor-hosted.d.ts +65 -0
  145. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -0
  146. package/dist/storage-backends/cursor-hosted.js +203 -0
  147. package/dist/storage-backends/file-kv.d.ts +42 -0
  148. package/dist/storage-backends/file-kv.d.ts.map +1 -0
  149. package/dist/storage-backends/file-kv.js +190 -0
  150. package/dist/storage-backends/postgres-kv.d.ts +82 -0
  151. package/dist/storage-backends/postgres-kv.d.ts.map +1 -0
  152. package/dist/storage-backends/postgres-kv.js +185 -0
  153. package/dist/storage.d.ts +10 -1
  154. package/dist/storage.d.ts.map +1 -1
  155. package/dist/storage.js +10 -1
  156. package/dist/types.d.ts +79 -0
  157. package/dist/types.d.ts.map +1 -1
  158. package/docs/.vitepress/cache/deps/package.json +3 -0
  159. package/docs/deployment.md +7 -3
  160. package/docs/example-agents/approval-buddy.md +1 -1
  161. package/docs/example-agents/index.md +15 -10
  162. package/docs/example-agents/oncall.md +216 -0
  163. package/docs/example-agents/security-reviewer.md +5 -5
  164. package/docs/guides/slack.md +11 -2
  165. package/docs/reference/sessions.md +13 -4
  166. package/docs/storage.md +41 -0
  167. package/package.json +22 -1
  168. package/skills/framework-map/SKILL.md +6 -4
  169. package/src/bin/agent-serve.ts +32 -2
  170. package/src/channels/slack/channel-watch.ts +28 -1
  171. package/src/channels/slack/cursor-account.ts +20 -0
  172. package/src/channels/slack/defaults.ts +51 -20
  173. package/src/channels/slack/dispatch.ts +215 -40
  174. package/src/channels/slack/inbound.ts +30 -10
  175. package/src/channels/slack/index.ts +4 -0
  176. package/src/channels/slack/live-delivery.ts +1 -0
  177. package/src/channels/slack/manifest.ts +3 -0
  178. package/src/channels/slack/nudge-carrier.ts +182 -0
  179. package/src/channels/slack/nudge-store.ts +308 -0
  180. package/src/channels/slack/slack-channel.ts +106 -31
  181. package/src/channels/slack/stop.ts +25 -0
  182. package/src/channels/slack/thread-context.ts +572 -0
  183. package/src/channels/slack/types.ts +27 -0
  184. package/src/internal/cli-deploy.ts +58 -20
  185. package/src/internal/cli-mcp.ts +708 -0
  186. package/src/internal/deploy-client.ts +11 -0
  187. package/src/internal/deploy-source.ts +24 -6
  188. package/src/internal/discovery.ts +1 -1
  189. package/src/internal/distribution.ts +3 -0
  190. package/src/internal/host-kv.ts +85 -0
  191. package/src/internal/hosting.ts +24 -4
  192. package/src/internal/logs-client.ts +67 -21
  193. package/src/internal/server.ts +31 -0
  194. package/src/internal/session-engine.ts +379 -61
  195. package/src/internal/storage-coordinator.ts +41 -0
  196. package/src/internal/workspace.ts +48 -11
  197. package/src/storage-backends/cursor-hosted.ts +246 -0
  198. package/src/storage-backends/file-kv.ts +221 -0
  199. package/src/storage-backends/postgres-kv.ts +253 -0
  200. package/src/storage.ts +11 -1
  201. package/src/types.ts +83 -0
  202. package/dist/docs/assets/chunks/@localSearchIndexroot.B7UcKvIn.js +0 -1
  203. package/dist/docs/assets/example-agents_index.md.CRqJlnIf.js +0 -2
  204. package/dist/docs/assets/example-agents_index.md.CRqJlnIf.lean.js +0 -1
  205. package/dist/internal/persistence-coordinator.d.ts +0 -127
  206. package/dist/internal/persistence-coordinator.d.ts.map +0 -1
  207. package/dist/persistence.d.ts +0 -184
  208. package/dist/persistence.d.ts.map +0 -1
  209. package/dist/playground/assets/index-BjnMwYoR.css +0 -1
  210. /package/dist/docs/assets/{deployment.md.BtfEsc9S.lean.js → deployment.md.DC5-ImcM.lean.js} +0 -0
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Durable Slack follow-up nudge store (Benny-style).
3
+ *
4
+ * Mid-turn inject and boundary resume share one exactly-once contract:
5
+ * pending nudges rename into delivered/ keyed by source message ts.
6
+ */
7
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
8
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
9
+ return new (P || (P = Promise))(function (resolve, reject) {
10
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
11
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
12
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
13
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
14
+ });
15
+ };
16
+ import { createHash, randomBytes } from "node:crypto";
17
+ import { mkdir, readdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
18
+ import { join } from "node:path";
19
+ /**
20
+ * Map an arbitrary key onto a single path segment under the store root.
21
+ * Rejects empty / `.` / `..` and strips remaining dots so path traversal
22
+ * cannot escape `<stateRoot>/slack-nudges` (or `slack-asks`).
23
+ */
24
+ function safeKey(continuationKey) {
25
+ const trimmed = continuationKey.trim();
26
+ if (trimmed === "" || trimmed === "." || trimmed === "..") {
27
+ return "_empty";
28
+ }
29
+ const sanitized = trimmed
30
+ .replace(/[^a-zA-Z0-9._-]+/g, "_")
31
+ .replace(/^\.+/, "")
32
+ .replace(/\.+/g, "_")
33
+ .slice(0, 180);
34
+ if (sanitized === "" || sanitized === "." || sanitized === "..") {
35
+ return "_empty";
36
+ }
37
+ return sanitized;
38
+ }
39
+ function contentHash(text) {
40
+ return createHash("sha256").update(text).digest("hex").slice(0, 16);
41
+ }
42
+ function newNudgeId() {
43
+ return `nudge_${randomBytes(8).toString("hex")}`;
44
+ }
45
+ export function formatSlackFollowupNudgeBlock(nudges) {
46
+ if (nudges.length === 0) {
47
+ return undefined;
48
+ }
49
+ const lines = [
50
+ "<slack_followup>",
51
+ "New Slack follow-up(s) arrived while you were working. Incorporate them before your final reply. If an ask was already answered in-thread, ack briefly instead of re-answering.",
52
+ "",
53
+ ];
54
+ for (const nudge of nudges) {
55
+ const who = nudge.author === undefined || nudge.author === ""
56
+ ? "someone"
57
+ : nudge.author;
58
+ lines.push(`- ${who} @ ${nudge.sourceTs}:`);
59
+ lines.push(nudge.text.trim() === "" ? "(empty)" : nudge.text.trim());
60
+ lines.push("");
61
+ }
62
+ lines.push("</slack_followup>");
63
+ return lines.join("\n").trimEnd();
64
+ }
65
+ /**
66
+ * Filesystem nudge store under `<root>/<continuation>/pending|delivered`.
67
+ */
68
+ export class SlackNudgeStore {
69
+ constructor(rootDir) {
70
+ this.rootDir = rootDir;
71
+ }
72
+ continuationDir(continuationKey) {
73
+ return join(this.rootDir, safeKey(continuationKey));
74
+ }
75
+ pendingDir(continuationKey) {
76
+ return join(this.continuationDir(continuationKey), "pending");
77
+ }
78
+ deliveredDir(continuationKey) {
79
+ return join(this.continuationDir(continuationKey), "delivered");
80
+ }
81
+ deliveredPath(continuationKey, sourceTs) {
82
+ return join(this.deliveredDir(continuationKey), safeKey(sourceTs));
83
+ }
84
+ append(input) {
85
+ return __awaiter(this, void 0, void 0, function* () {
86
+ const continuationKey = input.continuationKey.trim();
87
+ const sourceTs = input.sourceTs.trim();
88
+ const text = input.text.trim();
89
+ if (continuationKey === "" || sourceTs === "" || text === "") {
90
+ return false;
91
+ }
92
+ if (yield this.isDelivered(continuationKey, sourceTs)) {
93
+ return false;
94
+ }
95
+ const pending = yield this.listPending(continuationKey);
96
+ const hash = contentHash(text);
97
+ if (pending.some((item) => item.sourceTs === sourceTs || item.contentHash === hash)) {
98
+ return false;
99
+ }
100
+ const record = Object.assign(Object.assign({ id: newNudgeId(), continuationKey,
101
+ sourceTs,
102
+ text }, (input.author === undefined || input.author === ""
103
+ ? {}
104
+ : { author: input.author })), { createdAt: new Date().toISOString(), contentHash: hash });
105
+ const dir = this.pendingDir(continuationKey);
106
+ yield mkdir(dir, { recursive: true });
107
+ const target = join(dir, `${record.id}.json`);
108
+ const tmp = `${target}.${process.pid}.tmp`;
109
+ yield writeFile(tmp, `${JSON.stringify(record)}\n`, "utf8");
110
+ yield rename(tmp, target);
111
+ return true;
112
+ });
113
+ }
114
+ listPending(continuationKey) {
115
+ return __awaiter(this, void 0, void 0, function* () {
116
+ const dir = this.pendingDir(continuationKey);
117
+ let names;
118
+ try {
119
+ names = yield readdir(dir);
120
+ }
121
+ catch (_a) {
122
+ return [];
123
+ }
124
+ const records = [];
125
+ for (const name of names) {
126
+ if (!name.endsWith(".json")) {
127
+ continue;
128
+ }
129
+ try {
130
+ const raw = yield readFile(join(dir, name), "utf8");
131
+ const parsed = JSON.parse(raw);
132
+ if (typeof parsed.id === "string" &&
133
+ typeof parsed.sourceTs === "string" &&
134
+ typeof parsed.text === "string") {
135
+ records.push(parsed);
136
+ }
137
+ }
138
+ catch (_b) {
139
+ // Skip corrupt pending files.
140
+ }
141
+ }
142
+ records.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
143
+ return records;
144
+ });
145
+ }
146
+ isDelivered(continuationKey, sourceTs) {
147
+ return __awaiter(this, void 0, void 0, function* () {
148
+ if (continuationKey.trim() === "" || sourceTs.trim() === "") {
149
+ return false;
150
+ }
151
+ try {
152
+ yield readFile(this.deliveredPath(continuationKey, sourceTs), "utf8");
153
+ return true;
154
+ }
155
+ catch (_a) {
156
+ return false;
157
+ }
158
+ });
159
+ }
160
+ hasUndelivered(continuationKey) {
161
+ return __awaiter(this, void 0, void 0, function* () {
162
+ return (yield this.listPending(continuationKey)).length > 0;
163
+ });
164
+ }
165
+ /**
166
+ * Atomically move all pending nudges to delivered and return them.
167
+ * Already-delivered source ts values are dropped (exactly-once).
168
+ */
169
+ drainForInject(continuationKey) {
170
+ return __awaiter(this, void 0, void 0, function* () {
171
+ const pending = yield this.listPending(continuationKey);
172
+ if (pending.length === 0) {
173
+ return [];
174
+ }
175
+ yield mkdir(this.deliveredDir(continuationKey), { recursive: true });
176
+ const delivered = [];
177
+ for (const record of pending) {
178
+ const pendingPath = join(this.pendingDir(continuationKey), `${record.id}.json`);
179
+ if (yield this.isDelivered(continuationKey, record.sourceTs)) {
180
+ yield rm(pendingPath, { force: true });
181
+ continue;
182
+ }
183
+ const dest = this.deliveredPath(continuationKey, record.sourceTs);
184
+ const tmp = `${dest}.${process.pid}.tmp`;
185
+ try {
186
+ yield writeFile(tmp, `${JSON.stringify(record)}\n`, "utf8");
187
+ yield rename(tmp, dest);
188
+ yield rm(pendingPath, { force: true });
189
+ delivered.push(record);
190
+ }
191
+ catch (_a) {
192
+ yield rm(tmp, { force: true }).catch(() => { });
193
+ // Race: another drain won — treat as delivered.
194
+ yield rm(pendingPath, { force: true }).catch(() => { });
195
+ }
196
+ }
197
+ return delivered;
198
+ });
199
+ }
200
+ clear(continuationKey) {
201
+ return __awaiter(this, void 0, void 0, function* () {
202
+ yield rm(this.continuationDir(continuationKey), {
203
+ recursive: true,
204
+ force: true,
205
+ });
206
+ });
207
+ }
208
+ /** Drain + format for tool/hook inject. */
209
+ drainFormatted(continuationKey) {
210
+ return __awaiter(this, void 0, void 0, function* () {
211
+ return formatSlackFollowupNudgeBlock(yield this.drainForInject(continuationKey));
212
+ });
213
+ }
214
+ }
215
+ /**
216
+ * Durable ask claim store: one marker per `channelId:message.ts` so delayed
217
+ * Slack redelivery cannot start a second full answer.
218
+ */
219
+ export class SlackAskDedupeStore {
220
+ constructor(rootDir) {
221
+ this.rootDir = rootDir;
222
+ }
223
+ markerPath(channelId, messageTs) {
224
+ return join(this.rootDir, `${safeKey(channelId)}__${safeKey(messageTs)}.claimed`);
225
+ }
226
+ /** Returns true the first time this ask is claimed. */
227
+ tryClaim(channelId, messageTs) {
228
+ return __awaiter(this, void 0, void 0, function* () {
229
+ if (channelId.trim() === "" || messageTs.trim() === "") {
230
+ return false;
231
+ }
232
+ const path = this.markerPath(channelId, messageTs);
233
+ yield mkdir(this.rootDir, { recursive: true });
234
+ try {
235
+ yield writeFile(path, `${new Date().toISOString()}\n`, {
236
+ encoding: "utf8",
237
+ flag: "wx",
238
+ });
239
+ return true;
240
+ }
241
+ catch (_a) {
242
+ return false;
243
+ }
244
+ });
245
+ }
246
+ /** Drop a claim so a failed delivery can be retried. */
247
+ release(channelId, messageTs) {
248
+ return __awaiter(this, void 0, void 0, function* () {
249
+ if (channelId.trim() === "" || messageTs.trim() === "") {
250
+ return;
251
+ }
252
+ yield rm(this.markerPath(channelId, messageTs), { force: true });
253
+ });
254
+ }
255
+ wasClaimed(channelId, messageTs) {
256
+ return __awaiter(this, void 0, void 0, function* () {
257
+ try {
258
+ yield readFile(this.markerPath(channelId, messageTs), "utf8");
259
+ return true;
260
+ }
261
+ catch (_a) {
262
+ return false;
263
+ }
264
+ });
265
+ }
266
+ }
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Live-reply delivery strategies for the Slack default handlers.
3
+ *
4
+ * A single strategy owns how an in-progress reply is opened, updated,
5
+ * finalized, and cleaned up so the handlers in `defaults.ts` don't fork on the
6
+ * delivery mode at every event:
7
+ *
8
+ * - `stream`: Slack chat.startStream / appendStream / stopStream.
9
+ * - `post-update`: a chat.postMessage skeleton edited with chat.update. This is
10
+ * the only path that preserves Cursor-account identity pinning (which only
11
+ * covers postMessage).
12
+ *
13
+ * `buildDefaultEvents` selects one via {@link selectLiveDeliveryStrategy} up
14
+ * front and calls into it. All calls keep the same soft-fail / log behavior as
15
+ * the inlined logic they replaced.
16
+ */
17
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
19
+ return new (P || (P = Promise))(function (resolve, reject) {
20
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
21
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
22
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
23
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
24
+ });
25
+ };
26
+ import { appendChatStream, deleteMessage, postMessage, setThreadStatus, startChatStream, stopChatStream, updateMessage, } from "./api.js";
27
+ import { SLACK_POST_UPDATE_MIN_INTERVAL_MS } from "./constants.js";
28
+ import { computeStreamAppend } from "./stream.js";
29
+ function createPostUpdateStrategy(deps) {
30
+ const { botToken, getLog } = deps;
31
+ return {
32
+ inlineToolSteps: false,
33
+ start(_a) {
34
+ return __awaiter(this, arguments, void 0, function* ({ channel, channelId, threadTs, sessionId }) {
35
+ const log = getLog();
36
+ try {
37
+ const posted = yield postMessage({
38
+ botToken,
39
+ channel: channelId,
40
+ text: "…",
41
+ threadTs,
42
+ });
43
+ if (posted.ts !== "") {
44
+ channel.state.streamTs = posted.ts;
45
+ channel.state.streamChannelId = channelId;
46
+ channel.state.lastPostedText = "";
47
+ log.info("postMessage skeleton ok", {
48
+ channel: channelId,
49
+ message_ts: posted.ts,
50
+ session: sessionId,
51
+ });
52
+ }
53
+ }
54
+ catch (error) {
55
+ log.warn("postMessage skeleton failed", {
56
+ error: error instanceof Error ? error.message : String(error),
57
+ channel: channelId,
58
+ session: sessionId,
59
+ });
60
+ // Transient proxy/network failures must not permanently disable
61
+ // live delivery — only the stream path sets streamingUnavailable
62
+ // when Slack reports the feature unsupported.
63
+ }
64
+ });
65
+ },
66
+ append(_a) {
67
+ return __awaiter(this, arguments, void 0, function* ({ channel, channelId, streamTs, text, sessionId }) {
68
+ var _b;
69
+ const now = Date.now();
70
+ const isFirstUpdate = channel.state.lastPostedText === null ||
71
+ channel.state.lastPostedText === "";
72
+ const lastAt = (_b = channel.state.lastPostUpdateAtMs) !== null && _b !== void 0 ? _b : 0;
73
+ const due = isFirstUpdate || now - lastAt >= SLACK_POST_UPDATE_MIN_INTERVAL_MS;
74
+ if (!due) {
75
+ // Skip intermediate update; message.completed flushes full text.
76
+ return;
77
+ }
78
+ const result = yield updateMessage({
79
+ botToken,
80
+ channel: channelId,
81
+ ts: streamTs,
82
+ text,
83
+ });
84
+ if (!result.ok) {
85
+ getLog().warn("chat.update failed", {
86
+ error: result.error,
87
+ channel: channelId,
88
+ message_ts: streamTs,
89
+ session: sessionId,
90
+ });
91
+ return;
92
+ }
93
+ channel.state.lastPostUpdateAtMs = now;
94
+ channel.state.lastPostedText = text;
95
+ });
96
+ },
97
+ finalize(_a) {
98
+ return __awaiter(this, arguments, void 0, function* ({ channel, channelId, streamTs, text, sessionId }) {
99
+ const log = getLog();
100
+ const result = yield updateMessage({
101
+ botToken,
102
+ channel: channelId,
103
+ ts: streamTs,
104
+ text,
105
+ });
106
+ channel.state.streamTs = null;
107
+ if (result.ok) {
108
+ channel.state.lastPostedText = text;
109
+ log.info("reply delivered via chat.update", {
110
+ channel: channelId,
111
+ message_ts: streamTs,
112
+ session: sessionId,
113
+ chars: text.length,
114
+ });
115
+ return { delivered: true };
116
+ }
117
+ log.warn("chat.update finalize failed; falling back to postMessage", {
118
+ error: result.error,
119
+ channel: channelId,
120
+ message_ts: streamTs,
121
+ session: sessionId,
122
+ });
123
+ // Only suppress the fallback when the skeleton already holds the exact
124
+ // final text. Partial updates must not permanently truncate.
125
+ if (channel.state.lastPostedText === text) {
126
+ return { delivered: true };
127
+ }
128
+ // The skeleton still shows the placeholder or a stale partial and we're
129
+ // about to post the full reply as a fresh message. Best-effort delete the
130
+ // orphan so the thread doesn't show a duplicate.
131
+ yield deleteMessage({ botToken, channel: channelId, ts: streamTs });
132
+ return { delivered: false };
133
+ });
134
+ },
135
+ finalizeEmpty(_a) {
136
+ return __awaiter(this, arguments, void 0, function* ({ channelId, streamTs }) {
137
+ yield updateMessage({
138
+ botToken,
139
+ channel: channelId,
140
+ ts: streamTs,
141
+ text: " ",
142
+ });
143
+ });
144
+ },
145
+ cleanupOrphan(_a) {
146
+ return __awaiter(this, arguments, void 0, function* ({ channel }) {
147
+ var _b;
148
+ // A skeleton that was never finalized (e.g. an empty turn that produced
149
+ // no message.completed) would otherwise linger as a "…" placeholder
150
+ // forever. Best-effort delete it and clear typing.
151
+ const orphanTs = channel.state.streamTs;
152
+ if (orphanTs === null || orphanTs === "") {
153
+ return;
154
+ }
155
+ const orphanChannelId = (_b = channel.state.streamChannelId) !== null && _b !== void 0 ? _b : channel.state.channelId;
156
+ if (orphanChannelId !== null && orphanChannelId !== "") {
157
+ yield deleteMessage({
158
+ botToken,
159
+ channel: orphanChannelId,
160
+ ts: orphanTs,
161
+ });
162
+ }
163
+ channel.state.streamTs = null;
164
+ if (channel.state.channelId !== null && channel.state.threadTs !== null) {
165
+ yield setThreadStatus({
166
+ botToken,
167
+ channelId: channel.state.channelId,
168
+ threadTs: channel.state.threadTs,
169
+ status: "",
170
+ });
171
+ }
172
+ });
173
+ },
174
+ fail(_a) {
175
+ return __awaiter(this, arguments, void 0, function* ({ channel, channelId, streamTs, errorText }) {
176
+ const updated = yield updateMessage({
177
+ botToken,
178
+ channel: channelId,
179
+ ts: streamTs,
180
+ text: errorText,
181
+ });
182
+ channel.state.streamTs = null;
183
+ return { delivered: updated.ok };
184
+ });
185
+ },
186
+ };
187
+ }
188
+ function createStreamStrategy(deps) {
189
+ const { botToken, getLog } = deps;
190
+ return {
191
+ inlineToolSteps: true,
192
+ start(_a) {
193
+ return __awaiter(this, arguments, void 0, function* ({ channel, channelId, threadTs, sessionId }) {
194
+ const log = getLog();
195
+ const started = yield startChatStream({
196
+ botToken,
197
+ channel: channelId,
198
+ thread_ts: threadTs,
199
+ markdown_text: "",
200
+ });
201
+ if (!started.ok) {
202
+ log.warn("chat.startStream unavailable", {
203
+ error: started.error,
204
+ channel: channelId,
205
+ thread_ts: threadTs,
206
+ session: sessionId,
207
+ });
208
+ if (started.unsupported === true) {
209
+ channel.state.streamingUnavailable = true;
210
+ }
211
+ return;
212
+ }
213
+ channel.state.streamTs = started.ts;
214
+ channel.state.streamChannelId = started.channel;
215
+ channel.state.lastPostedText = "";
216
+ log.info("chat.startStream ok", {
217
+ channel: started.channel,
218
+ stream_ts: started.ts,
219
+ session: sessionId,
220
+ });
221
+ });
222
+ },
223
+ append(_a) {
224
+ return __awaiter(this, arguments, void 0, function* ({ channel, channelId, streamTs, text, sessionId }) {
225
+ const appendText = computeStreamAppend(channel.state.lastPostedText, text);
226
+ if (appendText === "") {
227
+ return;
228
+ }
229
+ const result = yield appendChatStream({
230
+ botToken,
231
+ channel: channelId,
232
+ ts: streamTs,
233
+ markdown_text: appendText,
234
+ });
235
+ if (!result.ok) {
236
+ getLog().warn("appendStream failed", {
237
+ error: result.error,
238
+ channel: channelId,
239
+ stream_ts: streamTs,
240
+ session: sessionId,
241
+ });
242
+ if (result.unsupported === true) {
243
+ channel.state.streamingUnavailable = true;
244
+ }
245
+ return;
246
+ }
247
+ channel.state.lastPostedText = text;
248
+ });
249
+ },
250
+ finalize(_a) {
251
+ return __awaiter(this, arguments, void 0, function* ({ channel, channelId, streamTs, text, sessionId }) {
252
+ const log = getLog();
253
+ // Only send the unsent suffix — full text was already appended.
254
+ const appendText = computeStreamAppend(channel.state.lastPostedText, text);
255
+ const stopped = yield stopChatStream({
256
+ botToken,
257
+ channel: channelId,
258
+ ts: streamTs,
259
+ markdown_text: appendText === "" ? undefined : appendText,
260
+ });
261
+ if (stopped.ok) {
262
+ channel.state.streamTs = null;
263
+ channel.state.lastPostedText = text;
264
+ log.info("reply delivered via stream", {
265
+ channel: channelId,
266
+ stream_ts: streamTs,
267
+ session: sessionId,
268
+ chars: text.length,
269
+ });
270
+ return { delivered: true };
271
+ }
272
+ log.warn("chat.stopStream failed; falling back to postMessage", {
273
+ error: stopped.error,
274
+ channel: channelId,
275
+ stream_ts: streamTs,
276
+ session: sessionId,
277
+ });
278
+ if (stopped.unsupported === true) {
279
+ channel.state.streamingUnavailable = true;
280
+ }
281
+ channel.state.streamTs = null;
282
+ // If we already streamed content, do not post the full reply again.
283
+ const alreadyStreamed = typeof channel.state.lastPostedText === "string" &&
284
+ channel.state.lastPostedText.length > 0;
285
+ return { delivered: alreadyStreamed };
286
+ });
287
+ },
288
+ finalizeEmpty(_a) {
289
+ return __awaiter(this, arguments, void 0, function* ({ channelId, streamTs }) {
290
+ yield stopChatStream({ botToken, channel: channelId, ts: streamTs });
291
+ });
292
+ },
293
+ cleanupOrphan() {
294
+ return __awaiter(this, void 0, void 0, function* () {
295
+ // The stream path closes its message in message.completed / turn.failed;
296
+ // there is nothing to clean up after turn.completed.
297
+ });
298
+ },
299
+ fail(_a) {
300
+ return __awaiter(this, arguments, void 0, function* ({ channel, channelId, streamTs, errorText }) {
301
+ const stopped = yield stopChatStream({
302
+ botToken,
303
+ channel: channelId,
304
+ ts: streamTs,
305
+ markdown_text: errorText,
306
+ });
307
+ channel.state.streamTs = null;
308
+ return { delivered: stopped.ok };
309
+ });
310
+ },
311
+ };
312
+ }
313
+ /** Pick the live-reply delivery strategy for a `buildDefaultEvents` instance. */
314
+ export function selectLiveDeliveryStrategy(deps, usePostUpdate) {
315
+ return usePostUpdate
316
+ ? createPostUpdateStrategy(deps)
317
+ : createStreamStrategy(deps);
318
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"slack-channel.d.ts","sourceRoot":"","sources":["../../../src/channels/slack/slack-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EACV,iBAAiB,EAMlB,MAAM,gBAAgB,CAAC;AAuCxB,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAC7D,OAAO,GAAE,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,GAAG;IAChD,SAAS,CAAC,EAAE,oBAAoB,CAAC,IAAI,CAAC,CAAC;CACnC,GACL,iBAAiB,CAAC,iBAAiB,CAAC,CAyTtC"}
1
+ {"version":3,"file":"slack-channel.d.ts","sourceRoot":"","sources":["../../../src/channels/slack/slack-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EACV,iBAAiB,EAMlB,MAAM,gBAAgB,CAAC;AAsCxB,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAC7D,OAAO,GAAE,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,GAAG;IAChD,SAAS,CAAC,EAAE,oBAAoB,CAAC,IAAI,CAAC,CAAC;CACnC,GACL,iBAAiB,CAAC,iBAAiB,CAAC,CAqYtC"}