botmux 2.23.2 → 2.23.3-canary.2

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 (56) hide show
  1. package/dist/adapters/backend/tmux-backend.d.ts +1 -0
  2. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  3. package/dist/adapters/backend/tmux-backend.js +23 -0
  4. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  5. package/dist/adapters/backend/tmux-pipe-backend.d.ts +1 -0
  6. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  7. package/dist/adapters/backend/tmux-pipe-backend.js +25 -0
  8. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  9. package/dist/bot-registry.d.ts +26 -0
  10. package/dist/bot-registry.d.ts.map +1 -1
  11. package/dist/bot-registry.js +21 -0
  12. package/dist/bot-registry.js.map +1 -1
  13. package/dist/core/command-handler.d.ts.map +1 -1
  14. package/dist/core/command-handler.js +12 -10
  15. package/dist/core/command-handler.js.map +1 -1
  16. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  17. package/dist/core/dashboard-ipc-server.js +73 -53
  18. package/dist/core/dashboard-ipc-server.js.map +1 -1
  19. package/dist/daemon.d.ts.map +1 -1
  20. package/dist/daemon.js +108 -11
  21. package/dist/daemon.js.map +1 -1
  22. package/dist/dashboard/web/app.js +3 -0
  23. package/dist/dashboard/web/app.js.map +1 -1
  24. package/dist/dashboard/web/bot-defaults.d.ts +2 -0
  25. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -0
  26. package/dist/dashboard/web/bot-defaults.js +201 -0
  27. package/dist/dashboard/web/bot-defaults.js.map +1 -0
  28. package/dist/dashboard-web/app.js +113 -72
  29. package/dist/dashboard-web/index.html +1 -0
  30. package/dist/dashboard-web/style.css +12 -0
  31. package/dist/dashboard.js +43 -0
  32. package/dist/dashboard.js.map +1 -1
  33. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  34. package/dist/im/lark/event-dispatcher.js +13 -3
  35. package/dist/im/lark/event-dispatcher.js.map +1 -1
  36. package/dist/services/chat-first-seen-store.d.ts +18 -3
  37. package/dist/services/chat-first-seen-store.d.ts.map +1 -1
  38. package/dist/services/chat-first-seen-store.js +20 -14
  39. package/dist/services/chat-first-seen-store.js.map +1 -1
  40. package/dist/services/group-creator.d.ts +23 -0
  41. package/dist/services/group-creator.d.ts.map +1 -0
  42. package/dist/services/group-creator.js +75 -0
  43. package/dist/services/group-creator.js.map +1 -0
  44. package/dist/services/oncall-store.d.ts +79 -5
  45. package/dist/services/oncall-store.d.ts.map +1 -1
  46. package/dist/services/oncall-store.js +243 -43
  47. package/dist/services/oncall-store.js.map +1 -1
  48. package/dist/utils/bot-mention-dedup.d.ts +11 -0
  49. package/dist/utils/bot-mention-dedup.d.ts.map +1 -1
  50. package/dist/utils/bot-mention-dedup.js +18 -0
  51. package/dist/utils/bot-mention-dedup.js.map +1 -1
  52. package/dist/utils/file-lock.d.ts +2 -0
  53. package/dist/utils/file-lock.d.ts.map +1 -0
  54. package/dist/utils/file-lock.js +114 -0
  55. package/dist/utils/file-lock.js.map +1 -0
  56. package/package.json +1 -1
@@ -6,49 +6,89 @@
6
6
  * Permission model is intentionally simple: anyone in the bot's allowedUsers
7
7
  * can bind/unbind/edit (enforced at the call sites — daemon command handler
8
8
  * + dashboard token gate). No per-chat owner list.
9
+ *
10
+ * Multi-process safety: 12 daemon processes + 1 dashboard process all share
11
+ * a single `bots.json`. Every write path goes through `withFileLock(path)`
12
+ * so a burst of concurrent auto-binds (each daemon sees a new chat for its
13
+ * own bot at roughly the same time) doesn't lose updates via read-modify-
14
+ * write race. The lock is also re-acquired around the read so the modify
15
+ * step always works against the latest on-disk snapshot.
9
16
  */
10
- import { readFileSync, writeFileSync } from 'node:fs';
17
+ import { promises as fsp } from 'node:fs';
18
+ import { readFileSync } from 'node:fs';
11
19
  import { getBot, getLoadedConfigPath } from '../bot-registry.js';
20
+ import { withFileLock } from '../utils/file-lock.js';
12
21
  import { logger } from '../utils/logger.js';
13
- function loadRawConfig() {
14
- const path = getLoadedConfigPath();
15
- if (!path)
16
- throw new Error('Bot config path unknown — cannot persist oncall bindings');
17
- const raw = JSON.parse(readFileSync(path, 'utf-8'));
22
+ async function readRawConfig(path) {
23
+ const raw = JSON.parse(await fsp.readFile(path, 'utf-8'));
18
24
  if (!Array.isArray(raw))
19
25
  throw new Error(`Config file is not a JSON array: ${path}`);
20
- return { path, raw };
26
+ return raw;
21
27
  }
22
- function writeRawConfig(path, raw) {
23
- writeFileSync(path, JSON.stringify(raw, null, 2) + '\n', 'utf-8');
28
+ async function writeRawConfigAtomic(path, raw) {
29
+ const tmp = path + '.tmp.' + process.pid;
30
+ await fsp.writeFile(tmp, JSON.stringify(raw, null, 2) + '\n', 'utf-8');
31
+ await fsp.rename(tmp, path);
24
32
  }
25
33
  function findEntryIndex(raw, larkAppId) {
26
34
  return raw.findIndex((e) => e?.larkAppId === larkAppId);
27
35
  }
36
+ function requireConfigPath() {
37
+ const p = getLoadedConfigPath();
38
+ if (!p)
39
+ throw new Error('Bot config path unknown — cannot persist oncall bindings');
40
+ return p;
41
+ }
42
+ /**
43
+ * Run a read-modify-write critical section against the bot config file under
44
+ * a cross-process lock. `mutate` runs against the freshest on-disk snapshot
45
+ * and decides what to write back; returning `undefined` means "no write".
46
+ */
47
+ async function rmwBotEntry(larkAppId, mutate) {
48
+ const path = requireConfigPath();
49
+ return withFileLock(path, async () => {
50
+ const raw = await readRawConfig(path);
51
+ const idx = findEntryIndex(raw, larkAppId);
52
+ if (idx < 0)
53
+ return { ok: false, reason: 'bot_not_in_config' };
54
+ const entry = raw[idx];
55
+ const out = mutate(entry, raw);
56
+ if (out && typeof out === 'object' && 'write' in out) {
57
+ const wrap = out;
58
+ if (wrap.write)
59
+ await writeRawConfigAtomic(path, raw);
60
+ return { ok: true, result: wrap.result };
61
+ }
62
+ await writeRawConfigAtomic(path, raw);
63
+ return { ok: true, result: out };
64
+ });
65
+ }
66
+ // ─── Manual binding ───────────────────────────────────────────────────────
28
67
  /**
29
68
  * Upsert an oncall binding. Returns whether it was newly created.
30
69
  */
31
- export function bindOncall(larkAppId, chatId, workingDir) {
32
- const bot = getBot(larkAppId);
33
- const existingList = bot.config.oncallChats ?? [];
34
- const existing = existingList.find(c => c.chatId === chatId);
35
- const next = { chatId, workingDir };
36
- const { path, raw } = loadRawConfig();
37
- const idx = findEntryIndex(raw, larkAppId);
38
- if (idx < 0)
39
- return { ok: false, reason: 'bot_not_in_config' };
40
- const cur = Array.isArray(raw[idx].oncallChats) ? raw[idx].oncallChats : [];
41
- const curIdx = cur.findIndex((c) => c?.chatId === chatId);
42
- if (curIdx >= 0) {
43
- // Replace wholesale — strips legacy fields (e.g. `owners`) so bots.json
44
- // converges on the current schema rather than carrying dead keys.
45
- cur[curIdx] = next;
70
+ export async function bindOncall(larkAppId, chatId, workingDir) {
71
+ let bot;
72
+ try {
73
+ bot = getBot(larkAppId);
46
74
  }
47
- else {
48
- cur.push(next);
75
+ catch {
76
+ return { ok: false, reason: 'bot_not_registered' };
49
77
  }
50
- raw[idx].oncallChats = cur;
51
- writeRawConfig(path, raw);
78
+ const next = { chatId, workingDir };
79
+ const r = await rmwBotEntry(larkAppId, (entry) => {
80
+ const cur = Array.isArray(entry.oncallChats) ? entry.oncallChats : [];
81
+ const curIdx = cur.findIndex((c) => c?.chatId === chatId);
82
+ const created = curIdx < 0;
83
+ if (created)
84
+ cur.push(next);
85
+ else
86
+ cur[curIdx] = next; // wholesale replace strips legacy keys
87
+ entry.oncallChats = cur;
88
+ return { write: true, result: { created } };
89
+ });
90
+ if (!r.ok)
91
+ return { ok: false, reason: r.reason };
52
92
  // Keep in-memory config in sync
53
93
  const inMem = (bot.config.oncallChats ??= []);
54
94
  const memIdx = inMem.findIndex(c => c.chatId === chatId);
@@ -57,25 +97,50 @@ export function bindOncall(larkAppId, chatId, workingDir) {
57
97
  else
58
98
  inMem.push(next);
59
99
  logger.info(`[oncall:${larkAppId}] bind chat=${chatId} dir=${workingDir}`);
60
- return { ok: true, entry: next, created: !existing };
100
+ return { ok: true, entry: next, created: r.result.created };
61
101
  }
62
- export function unbindOncall(larkAppId, chatId) {
63
- const bot = getBot(larkAppId);
64
- const existing = bot.config.oncallChats?.find(c => c.chatId === chatId);
65
- if (!existing)
66
- return { ok: false, reason: 'not_bound' };
67
- const { path, raw } = loadRawConfig();
68
- const idx = findEntryIndex(raw, larkAppId);
69
- if (idx < 0)
70
- return { ok: false, reason: 'bot_not_in_config' };
71
- const cur = Array.isArray(raw[idx].oncallChats) ? raw[idx].oncallChats : [];
72
- raw[idx].oncallChats = cur.filter((c) => c.chatId !== chatId);
73
- writeRawConfig(path, raw);
102
+ /**
103
+ * Unbind oncall for `chatId` and ALWAYS write a tombstone into
104
+ * `defaultOncallAutoboundChats`. The tombstone protects against the case
105
+ * where a user manually fiddled with a chat (bound then unbound, or just
106
+ * unbound) and we then mis-classify it as "new" on the next observation
107
+ * and re-auto-bind. Treating unbind as "default's one shot is spent" is
108
+ * symmetric with auto-bind already adding to the same list.
109
+ *
110
+ * Idempotent: never errors on "not bound". `wasBound` reports whether an
111
+ * existing binding was actually removed so callers can phrase UI text
112
+ * accordingly (the Lark `/oncall unbind` command still wants to say "未绑定"
113
+ * vs "已解绑").
114
+ */
115
+ export async function unbindOncall(larkAppId, chatId) {
116
+ let bot;
117
+ try {
118
+ bot = getBot(larkAppId);
119
+ }
120
+ catch {
121
+ return { ok: false, reason: 'bot_not_registered' };
122
+ }
123
+ const r = await rmwBotEntry(larkAppId, (entry) => {
124
+ const cur = Array.isArray(entry.oncallChats) ? entry.oncallChats : [];
125
+ const wasBound = cur.some(c => c?.chatId === chatId);
126
+ entry.oncallChats = cur.filter((c) => c?.chatId !== chatId);
127
+ const tomb = Array.isArray(entry.defaultOncallAutoboundChats)
128
+ ? entry.defaultOncallAutoboundChats : [];
129
+ if (!tomb.includes(chatId))
130
+ tomb.push(chatId);
131
+ entry.defaultOncallAutoboundChats = tomb;
132
+ return { write: true, result: { wasBound } };
133
+ });
134
+ if (!r.ok)
135
+ return { ok: false, reason: r.reason };
74
136
  if (bot.config.oncallChats) {
75
137
  bot.config.oncallChats = bot.config.oncallChats.filter(c => c.chatId !== chatId);
76
138
  }
77
- logger.info(`[oncall:${larkAppId}] unbind chat=${chatId}`);
78
- return { ok: true };
139
+ const inMemTomb = (bot.config.defaultOncallAutoboundChats ??= []);
140
+ if (!inMemTomb.includes(chatId))
141
+ inMemTomb.push(chatId);
142
+ logger.info(`[oncall:${larkAppId}] unbind chat=${chatId} wasBound=${r.result.wasBound} (tombstoned)`);
143
+ return { ok: true, wasBound: r.result.wasBound };
79
144
  }
80
145
  export function getOncallStatus(larkAppId, chatId) {
81
146
  // Defensive: dashboard callers may probe with an app id whose bot isn't
@@ -91,4 +156,139 @@ export function getOncallStatus(larkAppId, chatId) {
91
156
  }
92
157
  return bot.config.oncallChats?.find(c => c.chatId === chatId);
93
158
  }
159
+ // ─── Per-bot defaultOncall ───────────────────────────────────────────────
160
+ /** Read the current defaultOncall config + autobound list for a bot. Used by
161
+ * the dashboard GET route and by the daemon's auto-bind judge. Sync because
162
+ * it only reads the in-memory snapshot — file-level consistency comes from
163
+ * the daemon never racing with itself on reads. */
164
+ export function getBotDefaultOncall(larkAppId) {
165
+ let bot;
166
+ try {
167
+ bot = getBot(larkAppId);
168
+ }
169
+ catch {
170
+ return { defaultOncall: undefined, autoboundChats: [] };
171
+ }
172
+ return {
173
+ defaultOncall: bot.config.defaultOncall,
174
+ autoboundChats: [...(bot.config.defaultOncallAutoboundChats ?? [])],
175
+ };
176
+ }
177
+ /**
178
+ * Persist a defaultOncall change for the given bot. The dashboard PUT route
179
+ * is the only authorized caller — `since` is server-side authoritative so the
180
+ * frontend can't backdate the cut-off and accidentally include existing chats.
181
+ *
182
+ * `since` is stamped on every enabled save, not just the first transition.
183
+ * This matches the dashboard copy/requirement and prevents a later workingDir
184
+ * edit from reaching chats that were first observed before that edit.
185
+ *
186
+ * When disabled with an empty `workingDir`, the prior workingDir is preserved
187
+ * so the UI can round-trip (toggle off → toggle back on) without forcing the
188
+ * user to retype the path. Disable with a non-empty workingDir overwrites.
189
+ */
190
+ export async function updateBotDefaultOncall(larkAppId, patch) {
191
+ let bot;
192
+ try {
193
+ bot = getBot(larkAppId);
194
+ }
195
+ catch {
196
+ return { ok: false, reason: 'bot_not_registered' };
197
+ }
198
+ let next = null;
199
+ const r = await rmwBotEntry(larkAppId, (entry) => {
200
+ const prior = entry.defaultOncall;
201
+ // Cut-off line: every enabled save re-stamps so a workingDir edit while
202
+ // enabled doesn't reach back to chats observed under the old setting.
203
+ const nextSince = patch.enabled ? Date.now() : (prior?.since ?? 0);
204
+ const trimmed = (patch.workingDir ?? '').trim();
205
+ const resolvedWorkingDir = patch.enabled
206
+ ? trimmed
207
+ // Disabled + empty input → keep the prior path so the toggle is round-
208
+ // trippable. Disabled + explicit non-empty → user is replacing it.
209
+ : (trimmed || prior?.workingDir || '');
210
+ next = {
211
+ enabled: !!patch.enabled,
212
+ workingDir: resolvedWorkingDir,
213
+ since: nextSince,
214
+ };
215
+ entry.defaultOncall = next;
216
+ return { write: true, result: next };
217
+ });
218
+ if (!r.ok)
219
+ return { ok: false, reason: r.reason };
220
+ bot.config.defaultOncall = next;
221
+ logger.info(`[oncall:${larkAppId}] defaultOncall ${next.enabled ? 'enabled' : 'disabled'} ` +
222
+ `(workingDir=${next.workingDir || '∅'}, since=${next.since})`);
223
+ return { ok: true, defaultOncall: next };
224
+ }
225
+ /**
226
+ * Auto-bind a chat as part of the defaultOncall flow. Atomically:
227
+ * 1. RE-CHECK tombstone + existing binding against the freshest on-disk
228
+ * snapshot. The daemon's fast-path tombstone check is informational —
229
+ * if a concurrent `unbindOncall` wrote a tombstone between then and
230
+ * now, the lock-internal view sees it and we skip.
231
+ * 2. Upsert the oncallChats entry (same shape as manual bindOncall).
232
+ * 3. Append chatId to defaultOncallAutoboundChats (idempotent).
233
+ *
234
+ * Returns `skipped: 'tombstoned'` when the lock-internal tombstone check
235
+ * trips, `skipped: 'already_bound'` when another writer (manual bind by
236
+ * the user, or a sibling daemon) bound the chat between the fast-path read
237
+ * and the lock acquisition. Neither is an error.
238
+ */
239
+ export async function autoBindOncallFromDefault(larkAppId, chatId, workingDir) {
240
+ let bot;
241
+ try {
242
+ bot = getBot(larkAppId);
243
+ }
244
+ catch {
245
+ return { ok: false, reason: 'bot_not_registered' };
246
+ }
247
+ const next = { chatId, workingDir };
248
+ const r = await rmwBotEntry(larkAppId, (entry) => {
249
+ // Authoritative re-check #1: tombstone wins. If a concurrent unbind or
250
+ // earlier autoBind wrote one, the user has effectively opted out — never
251
+ // overwrite that decision from the auto-bind path.
252
+ const tomb = Array.isArray(entry.defaultOncallAutoboundChats)
253
+ ? entry.defaultOncallAutoboundChats : [];
254
+ if (tomb.includes(chatId)) {
255
+ return { write: false, result: { kind: 'skipped', reason: 'tombstoned' } };
256
+ }
257
+ // Authoritative re-check #2: existing binding wins. Could be from
258
+ // a sibling daemon, a manual /oncall bind, or a dashboard PUT racing
259
+ // with us. We never overwrite an existing binding with the default —
260
+ // the user's explicit choice (or a sibling's earlier auto-bind to its
261
+ // own default) is authoritative.
262
+ const cur = Array.isArray(entry.oncallChats) ? entry.oncallChats : [];
263
+ if (cur.some(c => c?.chatId === chatId)) {
264
+ return { write: false, result: { kind: 'skipped', reason: 'already_bound' } };
265
+ }
266
+ cur.push(next);
267
+ entry.oncallChats = cur;
268
+ tomb.push(chatId);
269
+ entry.defaultOncallAutoboundChats = tomb;
270
+ return { write: true, result: { kind: 'bound', created: true } };
271
+ });
272
+ if (!r.ok)
273
+ return { ok: false, reason: r.reason };
274
+ if (r.result.kind === 'skipped') {
275
+ return { ok: true, skipped: r.result.reason };
276
+ }
277
+ // Sync in-memory
278
+ const inMem = (bot.config.oncallChats ??= []);
279
+ const memIdx = inMem.findIndex(c => c.chatId === chatId);
280
+ if (memIdx >= 0)
281
+ inMem[memIdx] = next;
282
+ else
283
+ inMem.push(next);
284
+ const inMemAutobound = (bot.config.defaultOncallAutoboundChats ??= []);
285
+ if (!inMemAutobound.includes(chatId))
286
+ inMemAutobound.push(chatId);
287
+ logger.info(`[oncall:${larkAppId}] auto-bind (default) chat=${chatId} dir=${workingDir}`);
288
+ return { ok: true, entry: next, created: r.result.created };
289
+ }
290
+ // Test helper — read raw bots.json synchronously. Not for production use.
291
+ export function _readRawConfigSyncForTesting(path) {
292
+ return JSON.parse(readFileSync(path, 'utf-8'));
293
+ }
94
294
  //# sourceMappingURL=oncall-store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"oncall-store.js","sourceRoot":"","sources":["../../src/services/oncall-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAmB,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,SAAS,aAAa;IACpB,MAAM,IAAI,GAAG,mBAAmB,EAAE,CAAC;IACnC,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IACvF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,EAAE,CAAC,CAAC;IACrF,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC;AAED,SAAS,cAAc,CAAC,IAAY,EAAE,GAAU;IAC9C,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,cAAc,CAAC,GAAU,EAAE,SAAiB;IACnD,OAAO,GAAG,CAAC,SAAS,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC;AAC/D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,SAAiB,EACjB,MAAc,EACd,UAAkB;IAElB,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC9B,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;IAClD,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IAE7D,MAAM,IAAI,GAAe,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAEhD,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;IACtC,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAC3C,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAE/D,MAAM,GAAG,GAAU,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IACnF,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAC/D,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QAChB,wEAAwE;QACxE,kEAAkE;QAClE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACrB,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IACD,GAAG,CAAC,GAAG,CAAC,CAAC,WAAW,GAAG,GAAG,CAAC;IAC3B,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAE1B,gCAAgC;IAChC,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IACzD,IAAI,MAAM,IAAI,CAAC;QAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;;QAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE7D,MAAM,CAAC,IAAI,CAAC,WAAW,SAAS,eAAe,MAAM,QAAQ,UAAU,EAAE,CAAC,CAAC;IAC3E,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,SAAiB,EACjB,MAAc;IAEd,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC9B,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IACxE,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAEzD,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;IACtC,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAC3C,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC/D,MAAM,GAAG,GAAiB,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1F,GAAG,CAAC,GAAG,CAAC,CAAC,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IAC1E,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAE1B,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAC3B,GAAG,CAAC,MAAM,CAAC,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,WAAW,SAAS,iBAAiB,MAAM,EAAE,CAAC,CAAC;IAC3D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,MAAc;IAC/D,wEAAwE;IACxE,wEAAwE;IACxE,sEAAsE;IACtE,kDAAkD;IAClD,IAAI,GAAG,CAAC;IACR,IAAI,CAAC;QAAC,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,SAAS,CAAC;IAAC,CAAC;IAC5D,OAAO,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;AAChE,CAAC"}
1
+ {"version":3,"file":"oncall-store.js","sourceRoot":"","sources":["../../src/services/oncall-store.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,17 @@ export declare function markBotMentionMessageHandled(messageId: string | undefin
21
21
  /** Did we already route this messageId? Returns true if the other path got
22
22
  * there first within the TTL window. */
23
23
  export declare function isBotMentionMessageHandled(messageId: string | undefined): boolean;
24
+ /** Atomic check-and-set: claim this messageId for processing. Returns `true`
25
+ * iff the caller is the first to claim within the TTL window — only that
26
+ * caller may enqueue the message.
27
+ *
28
+ * Use this instead of `isBotMentionMessageHandled` + `markBotMentionMessageHandled`
29
+ * whenever there is an `await` between the two: JS is single-threaded but the
30
+ * yield lets the OTHER dedup path slip in, pass its own check, mark, and
31
+ * enqueue — we'd then re-mark (no-op) and enqueue a second time. `tryClaim`
32
+ * collapses both halves into one synchronous step so the two paths can't both
33
+ * win, regardless of who runs first. */
34
+ export declare function tryClaimBotMentionMessage(messageId: string | undefined): boolean;
24
35
  /** Test seam — drop everything. */
25
36
  export declare function _resetForTest(): void;
26
37
  //# sourceMappingURL=bot-mention-dedup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bot-mention-dedup.d.ts","sourceRoot":"","sources":["../../src/utils/bot-mention-dedup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH;uDACuD;AACvD,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAIhF;AAED;yCACyC;AACzC,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CASjF;AAED,mCAAmC;AACnC,wBAAgB,aAAa,IAAI,IAAI,CAEpC"}
1
+ {"version":3,"file":"bot-mention-dedup.d.ts","sourceRoot":"","sources":["../../src/utils/bot-mention-dedup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH;uDACuD;AACvD,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAIhF;AAED;yCACyC;AACzC,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CASjF;AAED;;;;;;;;;yCASyC;AACzC,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAKhF;AAED,mCAAmC;AACnC,wBAAgB,aAAa,IAAI,IAAI,CAEpC"}
@@ -39,6 +39,24 @@ export function isBotMentionMessageHandled(messageId) {
39
39
  }
40
40
  return true;
41
41
  }
42
+ /** Atomic check-and-set: claim this messageId for processing. Returns `true`
43
+ * iff the caller is the first to claim within the TTL window — only that
44
+ * caller may enqueue the message.
45
+ *
46
+ * Use this instead of `isBotMentionMessageHandled` + `markBotMentionMessageHandled`
47
+ * whenever there is an `await` between the two: JS is single-threaded but the
48
+ * yield lets the OTHER dedup path slip in, pass its own check, mark, and
49
+ * enqueue — we'd then re-mark (no-op) and enqueue a second time. `tryClaim`
50
+ * collapses both halves into one synchronous step so the two paths can't both
51
+ * win, regardless of who runs first. */
52
+ export function tryClaimBotMentionMessage(messageId) {
53
+ if (!messageId)
54
+ return false;
55
+ if (isBotMentionMessageHandled(messageId))
56
+ return false;
57
+ markBotMentionMessageHandled(messageId);
58
+ return true;
59
+ }
42
60
  /** Test seam — drop everything. */
43
61
  export function _resetForTest() {
44
62
  seen.clear();
@@ -1 +1 @@
1
- {"version":3,"file":"bot-mention-dedup.js","sourceRoot":"","sources":["../../src/utils/bot-mention-dedup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,GAAG,MAAM,CAAC;AAEtB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,mCAAmC;AAE3E;uDACuD;AACvD,MAAM,UAAU,4BAA4B,CAAC,SAA6B;IACxE,IAAI,CAAC,SAAS;QAAE,OAAO;IACvB,EAAE,EAAE,CAAC;IACL,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED;yCACyC;AACzC,MAAM,UAAU,0BAA0B,CAAC,SAA6B;IACtE,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC1C,IAAI,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,KAAK,EAAE,CAAC;AACf,CAAC;AAED,SAAS,EAAE;IACT,oEAAoE;IACpE,IAAI,IAAI,CAAC,IAAI,GAAG,EAAE;QAAE,OAAO;IAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,GAAG,GAAG;YAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"bot-mention-dedup.js","sourceRoot":"","sources":["../../src/utils/bot-mention-dedup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,GAAG,MAAM,CAAC;AAEtB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,mCAAmC;AAE3E;uDACuD;AACvD,MAAM,UAAU,4BAA4B,CAAC,SAA6B;IACxE,IAAI,CAAC,SAAS;QAAE,OAAO;IACvB,EAAE,EAAE,CAAC;IACL,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED;yCACyC;AACzC,MAAM,UAAU,0BAA0B,CAAC,SAA6B;IACtE,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC1C,IAAI,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;yCASyC;AACzC,MAAM,UAAU,yBAAyB,CAAC,SAA6B;IACrE,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,IAAI,0BAA0B,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IACxD,4BAA4B,CAAC,SAAS,CAAC,CAAC;IACxC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,KAAK,EAAE,CAAC;AACf,CAAC;AAED,SAAS,EAAE;IACT,oEAAoE;IACpE,IAAI,IAAI,CAAC,IAAI,GAAG,EAAE;QAAE,OAAO;IAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,GAAG,GAAG;YAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function withFileLock<T>(targetPath: string, fn: () => Promise<T>): Promise<T>;
2
+ //# sourceMappingURL=file-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock.d.ts","sourceRoot":"","sources":["../../src/utils/file-lock.ts"],"names":[],"mappings":"AAsCA,wBAAsB,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CA6D1F"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Cross-process advisory lock for a single file. Used to serialize
3
+ * read-modify-write of shared JSON config (e.g. `bots.json` from multiple
4
+ * daemon processes + the dashboard).
5
+ *
6
+ * Acquisition: atomic `open(path + '.lock', 'wx')`. The filesystem makes
7
+ * O_CREAT|O_EXCL atomic, so exactly one waiter wins.
8
+ *
9
+ * Stale-break: a holder that crashes mid-section leaves the lock file
10
+ * behind with its dead PID. To reclaim it we use the atomic POSIX
11
+ * `rename(lock, lock.stale-<random>)`: rename succeeds for exactly ONE
12
+ * caller (the source has to exist), so only ONE waiter is the rightful
13
+ * stale-breaker. Everyone else gets ENOENT and loops back to acquire.
14
+ * This avoids the classic "two waiters both unlink, one deletes the other's
15
+ * just-acquired live lock" race that read+unlink-based schemes have.
16
+ *
17
+ * Not reentrant. Don't nest `withFileLock` calls on the same path within
18
+ * the same process — the inner call would wait MAX_WAIT_MS and then time
19
+ * out. (We could allow reentrancy via PID-equal check, but our callers
20
+ * don't need it and the equality check would re-open the stale-break race.)
21
+ */
22
+ import { promises as fsp } from 'node:fs';
23
+ import { randomBytes } from 'node:crypto';
24
+ import { logger } from './logger.js';
25
+ const MAX_WAIT_MS = 5_000;
26
+ const RETRY_BASE_MS = 25;
27
+ // Minimum age before we'll consider stale-breaking a lock with a dead PID.
28
+ // Prevents racing on freshly-acquired locks where the holder might not have
29
+ // finished writing its PID file yet.
30
+ const MIN_STALE_AGE_MS = 100;
31
+ async function isPidAlive(pid) {
32
+ if (!pid)
33
+ return false;
34
+ if (pid === process.pid)
35
+ return true;
36
+ try {
37
+ process.kill(pid, 0);
38
+ return true;
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }
44
+ export async function withFileLock(targetPath, fn) {
45
+ const lockPath = targetPath + '.lock';
46
+ const start = Date.now();
47
+ while (true) {
48
+ try {
49
+ const fh = await fsp.open(lockPath, 'wx');
50
+ await fh.writeFile(String(process.pid));
51
+ await fh.close();
52
+ try {
53
+ return await fn();
54
+ }
55
+ finally {
56
+ try {
57
+ await fsp.unlink(lockPath);
58
+ }
59
+ catch { /* already gone, tolerate */ }
60
+ }
61
+ }
62
+ catch (e) {
63
+ if (e.code !== 'EEXIST')
64
+ throw e;
65
+ // EEXIST: someone holds the lock. Check whether it's stale (dead PID
66
+ // + old enough). If so, attempt an atomic rename — POSIX guarantees
67
+ // exactly one caller succeeds.
68
+ let holder = 0;
69
+ let lockAgeMs = Infinity;
70
+ try {
71
+ const [raw, stat] = await Promise.all([
72
+ fsp.readFile(lockPath, 'utf-8'),
73
+ fsp.stat(lockPath),
74
+ ]);
75
+ holder = parseInt(raw, 10) || 0;
76
+ lockAgeMs = Date.now() - stat.mtimeMs;
77
+ }
78
+ catch (re) {
79
+ if (re.code === 'ENOENT')
80
+ continue; // released between EEXIST and read
81
+ throw re;
82
+ }
83
+ const breakable = holder
84
+ && lockAgeMs >= MIN_STALE_AGE_MS
85
+ && !(await isPidAlive(holder));
86
+ if (breakable) {
87
+ // Atomic rename: only ONE caller wins. The winner is responsible
88
+ // for cleaning up the stale carcass; losers get ENOENT and retry
89
+ // the lock acquisition on the next iteration.
90
+ const stalePath = `${lockPath}.stale.${process.pid}.${randomBytes(4).toString('hex')}`;
91
+ try {
92
+ await fsp.rename(lockPath, stalePath);
93
+ logger.warn(`[file-lock] broke stale lock at ${lockPath} (dead pid ${holder}, age ${lockAgeMs}ms)`);
94
+ try {
95
+ await fsp.unlink(stalePath);
96
+ }
97
+ catch { /* tolerate */ }
98
+ continue;
99
+ }
100
+ catch (renameErr) {
101
+ if (renameErr.code === 'ENOENT')
102
+ continue; // another waiter beat us
103
+ throw renameErr;
104
+ }
105
+ }
106
+ if (Date.now() - start > MAX_WAIT_MS) {
107
+ throw new Error(`file-lock timeout waiting for ${lockPath} ` +
108
+ `(held by pid ${holder || '?'}, age ${Math.round(lockAgeMs)}ms)`);
109
+ }
110
+ await new Promise(r => setTimeout(r, RETRY_BASE_MS + Math.random() * RETRY_BASE_MS));
111
+ }
112
+ }
113
+ }
114
+ //# sourceMappingURL=file-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../../src/utils/file-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,QAAQ,IAAI,GAAG,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,WAAW,GAAG,KAAK,CAAC;AAC1B,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB,2EAA2E;AAC3E,4EAA4E;AAC5E,qCAAqC;AACrC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,KAAK,UAAU,UAAU,CAAC,GAAW;IACnC,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,CAAC;QAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAI,UAAkB,EAAE,EAAoB;IAC5E,MAAM,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;IACtC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAC1C,MAAM,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YACxC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;YACjB,IAAI,CAAC;gBACH,OAAO,MAAM,EAAE,EAAE,CAAC;YACpB,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC;oBAAC,MAAM,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,4BAA4B,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,CAAC,CAAC;YAEjC,qEAAqE;YACrE,oEAAoE;YACpE,+BAA+B;YAC/B,IAAI,MAAM,GAAG,CAAC,CAAC;YACf,IAAI,SAAS,GAAG,QAAQ,CAAC;YACzB,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;oBACpC,GAAG,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;oBAC/B,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;iBACnB,CAAC,CAAC;gBACH,MAAM,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;gBAChC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;YACxC,CAAC;YAAC,OAAO,EAAO,EAAE,CAAC;gBACjB,IAAI,EAAE,CAAC,IAAI,KAAK,QAAQ;oBAAE,SAAS,CAAC,mCAAmC;gBACvE,MAAM,EAAE,CAAC;YACX,CAAC;YAED,MAAM,SAAS,GAAG,MAAM;mBACnB,SAAS,IAAI,gBAAgB;mBAC7B,CAAC,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;YACjC,IAAI,SAAS,EAAE,CAAC;gBACd,iEAAiE;gBACjE,iEAAiE;gBACjE,8CAA8C;gBAC9C,MAAM,SAAS,GAAG,GAAG,QAAQ,UAAU,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACvF,IAAI,CAAC;oBACH,MAAM,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;oBACtC,MAAM,CAAC,IAAI,CAAC,mCAAmC,QAAQ,cAAc,MAAM,SAAS,SAAS,KAAK,CAAC,CAAC;oBACpG,IAAI,CAAC;wBAAC,MAAM,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;oBAAC,CAAC;oBAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;oBAC7D,SAAS;gBACX,CAAC;gBAAC,OAAO,SAAc,EAAE,CAAC;oBACxB,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ;wBAAE,SAAS,CAAC,yBAAyB;oBACpE,MAAM,SAAS,CAAC;gBAClB,CAAC;YACH,CAAC;YAED,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,WAAW,EAAE,CAAC;gBACrC,MAAM,IAAI,KAAK,CACb,iCAAiC,QAAQ,GAAG;oBAC5C,gBAAgB,MAAM,IAAI,GAAG,SAAS,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CACjE,CAAC;YACJ,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botmux",
3
- "version": "2.23.2",
3
+ "version": "2.23.3-canary.2",
4
4
  "description": "Bridge between IM platforms and AI coding CLIs — one topic, one CLI session with live streaming",
5
5
  "type": "module",
6
6
  "main": "dist/index-daemon.js",