cli-jaw 2.2.8 → 2.2.10

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 (220) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/agent-driven.js +33 -0
  3. package/dist/bin/agent-driven.js.map +1 -0
  4. package/dist/bin/commands/doctor.js +66 -1
  5. package/dist/bin/commands/doctor.js.map +1 -1
  6. package/dist/bin/commands/init.js +107 -8
  7. package/dist/bin/commands/init.js.map +1 -1
  8. package/dist/bin/interactive-confirm.js +110 -0
  9. package/dist/bin/interactive-confirm.js.map +1 -0
  10. package/dist/bin/star-prompt.js +46 -15
  11. package/dist/bin/star-prompt.js.map +1 -1
  12. package/dist/lib/upload.js +5 -1
  13. package/dist/lib/upload.js.map +1 -1
  14. package/dist/server.js +1 -0
  15. package/dist/server.js.map +1 -1
  16. package/dist/src/agent/alert-escalation.js +1 -1
  17. package/dist/src/agent/alert-escalation.js.map +1 -1
  18. package/dist/src/agent/codex-app-catalog.js +57 -0
  19. package/dist/src/agent/codex-app-catalog.js.map +1 -0
  20. package/dist/src/agent/codex-app-client.js +89 -7
  21. package/dist/src/agent/codex-app-client.js.map +1 -1
  22. package/dist/src/agent/pi-rpc-verdict.js +22 -0
  23. package/dist/src/agent/pi-rpc-verdict.js.map +1 -0
  24. package/dist/src/agent/pi-runtime.js +240 -0
  25. package/dist/src/agent/pi-runtime.js.map +1 -1
  26. package/dist/src/agent/runtime-pool.js +445 -0
  27. package/dist/src/agent/runtime-pool.js.map +1 -0
  28. package/dist/src/agent/spawn.js +395 -240
  29. package/dist/src/agent/spawn.js.map +1 -1
  30. package/dist/src/cli/command-context.js +5 -2
  31. package/dist/src/cli/command-context.js.map +1 -1
  32. package/dist/src/cli/commands.js +43 -39
  33. package/dist/src/cli/commands.js.map +1 -1
  34. package/dist/src/cli/handlers-runtime.js +15 -7
  35. package/dist/src/cli/handlers-runtime.js.map +1 -1
  36. package/dist/src/cli/handlers-search.js +1 -1
  37. package/dist/src/cli/handlers-search.js.map +1 -1
  38. package/dist/src/cli/handlers-skill-invoke.js +1 -1
  39. package/dist/src/cli/handlers-skill-invoke.js.map +1 -1
  40. package/dist/src/cli/handlers-workflows.js +1 -1
  41. package/dist/src/cli/handlers-workflows.js.map +1 -1
  42. package/dist/src/cli/handlers.js +1 -1
  43. package/dist/src/cli/handlers.js.map +1 -1
  44. package/dist/src/cli/opencodex-models.js +40 -7
  45. package/dist/src/cli/opencodex-models.js.map +1 -1
  46. package/dist/src/cli/registry-live.js +6 -5
  47. package/dist/src/cli/registry-live.js.map +1 -1
  48. package/dist/src/command-contract/catalog.js +4 -0
  49. package/dist/src/command-contract/catalog.js.map +1 -1
  50. package/dist/src/core/config.js +69 -4
  51. package/dist/src/core/config.js.map +1 -1
  52. package/dist/src/core/runtime-settings.js +17 -0
  53. package/dist/src/core/runtime-settings.js.map +1 -1
  54. package/dist/src/core/settings-merge.js +1 -1
  55. package/dist/src/core/settings-merge.js.map +1 -1
  56. package/dist/src/discord/bot.js +29 -14
  57. package/dist/src/discord/bot.js.map +1 -1
  58. package/dist/src/discord/commands.js +5 -4
  59. package/dist/src/discord/commands.js.map +1 -1
  60. package/dist/src/discord/discord-file.js +8 -2
  61. package/dist/src/discord/discord-file.js.map +1 -1
  62. package/dist/src/discord/forwarder.js +17 -19
  63. package/dist/src/discord/forwarder.js.map +1 -1
  64. package/dist/src/discord/send-only-client.js +9 -1
  65. package/dist/src/discord/send-only-client.js.map +1 -1
  66. package/dist/src/manager/dashboard-service.js +38 -1
  67. package/dist/src/manager/dashboard-service.js.map +1 -1
  68. package/dist/src/manager/telegram-hub/hub-bot.js +47 -29
  69. package/dist/src/manager/telegram-hub/hub-bot.js.map +1 -1
  70. package/dist/src/messaging/channel-health.js +32 -0
  71. package/dist/src/messaging/channel-health.js.map +1 -1
  72. package/dist/src/messaging/chunk.js +376 -0
  73. package/dist/src/messaging/chunk.js.map +1 -0
  74. package/dist/src/messaging/dedupe.js +104 -0
  75. package/dist/src/messaging/dedupe.js.map +1 -0
  76. package/dist/src/messaging/fold.js +210 -0
  77. package/dist/src/messaging/fold.js.map +1 -0
  78. package/dist/src/messaging/redact.js +577 -0
  79. package/dist/src/messaging/redact.js.map +1 -0
  80. package/dist/src/messaging/retry.js +76 -0
  81. package/dist/src/messaging/retry.js.map +1 -0
  82. package/dist/src/messaging/runtime.js +6 -3
  83. package/dist/src/messaging/runtime.js.map +1 -1
  84. package/dist/src/messaging/send.js +45 -15
  85. package/dist/src/messaging/send.js.map +1 -1
  86. package/dist/src/messaging/slack-target.js +50 -0
  87. package/dist/src/messaging/slack-target.js.map +1 -0
  88. package/dist/src/messaging/types.js.map +1 -1
  89. package/dist/src/orchestrator/pipeline.js +4 -4
  90. package/dist/src/orchestrator/pipeline.js.map +1 -1
  91. package/dist/src/routes/command.js +11 -7
  92. package/dist/src/routes/command.js.map +1 -1
  93. package/dist/src/routes/messaging.js +28 -12
  94. package/dist/src/routes/messaging.js.map +1 -1
  95. package/dist/src/routes/settings.js +22 -6
  96. package/dist/src/routes/settings.js.map +1 -1
  97. package/dist/src/slack/api.js +116 -0
  98. package/dist/src/slack/api.js.map +1 -0
  99. package/dist/src/slack/bot.js +262 -0
  100. package/dist/src/slack/bot.js.map +1 -0
  101. package/dist/src/slack/commands.js +98 -0
  102. package/dist/src/slack/commands.js.map +1 -0
  103. package/dist/src/slack/events.js +116 -0
  104. package/dist/src/slack/events.js.map +1 -0
  105. package/dist/src/slack/format.js +58 -0
  106. package/dist/src/slack/format.js.map +1 -0
  107. package/dist/src/slack/forwarder.js +57 -0
  108. package/dist/src/slack/forwarder.js.map +1 -0
  109. package/dist/src/slack/register.js +13 -0
  110. package/dist/src/slack/register.js.map +1 -0
  111. package/dist/src/slack/send-handler.js +41 -0
  112. package/dist/src/slack/send-handler.js.map +1 -0
  113. package/dist/src/slack/send-only-client.js +46 -0
  114. package/dist/src/slack/send-only-client.js.map +1 -0
  115. package/dist/src/slack/slack-file.js +72 -0
  116. package/dist/src/slack/slack-file.js.map +1 -0
  117. package/dist/src/slack/socket.js +341 -0
  118. package/dist/src/slack/socket.js.map +1 -0
  119. package/dist/src/telegram/bot.js +117 -37
  120. package/dist/src/telegram/bot.js.map +1 -1
  121. package/dist/src/telegram/elicitation-buttons.js +7 -2
  122. package/dist/src/telegram/elicitation-buttons.js.map +1 -1
  123. package/dist/src/telegram/forwarder.js +15 -7
  124. package/dist/src/telegram/forwarder.js.map +1 -1
  125. package/dist/src/telegram/rich-message.js +209 -24
  126. package/dist/src/telegram/rich-message.js.map +1 -1
  127. package/dist/src/telegram/telegram-file.js +35 -4
  128. package/dist/src/telegram/telegram-file.js.map +1 -1
  129. package/dist/src/telegram/voice.js +4 -3
  130. package/dist/src/telegram/voice.js.map +1 -1
  131. package/package.json +5 -1
  132. package/public/assets/providers/slack.svg +1 -0
  133. package/public/dist/assets/ChannelsDiscord-D9FLnZso.js +1 -0
  134. package/public/dist/assets/ChannelsSlack-ybvupgdK.js +1 -0
  135. package/public/dist/assets/ChannelsTelegram-BOCOPrBc.js +1 -0
  136. package/public/dist/assets/HealthBadge-megTlF4n.js +1 -0
  137. package/public/dist/assets/TransportStatusChips-CjEySMis.js +1 -0
  138. package/public/dist/assets/app-BGIAy4he.js +55 -0
  139. package/public/dist/assets/{bgtask-badge-CdBqBy1L.js → bgtask-badge-DXca8E7c.js} +1 -1
  140. package/public/dist/assets/bgtask-badge-GRSiNILQ.js +1 -0
  141. package/public/dist/assets/{employees-C8qufeJl.js → employees-BL4qMxJj.js} +1 -1
  142. package/public/dist/assets/iframe-renderer-BUSk0Rw9.js +1 -0
  143. package/public/dist/assets/{iframe-renderer-CZ67aeTO.js → iframe-renderer-DUI0LYBy.js} +2 -2
  144. package/public/dist/assets/{manager-CLXT60M5.js → manager-B-gpf-3j.js} +3 -3
  145. package/public/dist/assets/{memory-lWramLVw.js → memory-B_Y0L0KD.js} +1 -1
  146. package/public/dist/assets/memory-CX6YahTc.js +1 -0
  147. package/public/dist/assets/message-history-Det7u76S.js +1 -0
  148. package/public/dist/assets/{message-history-2NUN3UxM.js → message-history-Dl8dJzez.js} +1 -1
  149. package/public/dist/assets/{provider-icons-stsNmQKx.js → provider-icons-VDfahiUp.js} +3 -2
  150. package/public/dist/assets/{render-CsF-ojLe.js → render-BI1aqKoc.js} +1 -1
  151. package/public/dist/assets/{settings-BMLowRCh.js → settings-B0Sj4sUH.js} +1 -1
  152. package/public/dist/assets/settings-C7NutP6X.js +1 -0
  153. package/public/dist/assets/settings-core-CyX-px6_.js +100 -0
  154. package/public/dist/assets/settings-core-DVpFmrhY.js +1 -0
  155. package/public/dist/assets/{sidebar-gKjYLv49.js → sidebar-DOR9fTgN.js} +3 -3
  156. package/public/dist/assets/skills-BWhKreQ2.js +1 -0
  157. package/public/dist/assets/{skills-CKTbomdm.js → skills-wt0fkV9Y.js} +1 -1
  158. package/public/dist/assets/slash-commands-BZbqTg3v.js +20 -0
  159. package/public/dist/assets/slash-commands-Di4NDA4w.js +1 -0
  160. package/public/dist/assets/{trace-drawer-C3G4mmB1.js → trace-drawer-kMYzaVFW.js} +1 -1
  161. package/public/dist/assets/ui-BghexWrB.js +1 -0
  162. package/public/dist/assets/{ui-CVVC_flX.js → ui-D4D5HUfo.js} +1 -1
  163. package/public/dist/index.html +62 -1
  164. package/public/dist/manager/index.html +1 -1
  165. package/public/index.html +61 -0
  166. package/public/js/features/command-info.ts +1 -0
  167. package/public/js/features/help-content.ts +8 -0
  168. package/public/js/features/settings-channel.ts +5 -1
  169. package/public/js/features/settings-core.ts +2 -0
  170. package/public/js/features/settings-slack.ts +74 -0
  171. package/public/js/features/settings-types.ts +5 -1
  172. package/public/js/features/settings.ts +1 -0
  173. package/public/js/features/transport-status-row.ts +15 -3
  174. package/public/js/main.ts +19 -0
  175. package/public/js/provider-icons.ts +4 -1
  176. package/public/js/ws.ts +1 -1
  177. package/public/locales/en.json +21 -0
  178. package/public/locales/ja.json +21 -0
  179. package/public/locales/ko.json +21 -0
  180. package/public/locales/zh.json +21 -0
  181. package/public/manager/src/settings/SettingsShell.tsx +1 -0
  182. package/public/manager/src/settings/SettingsSidebar.tsx +1 -0
  183. package/public/manager/src/settings/pages/ChannelsSlack.tsx +323 -0
  184. package/public/manager/src/settings/pages/components/ActiveChannelToggle.tsx +2 -1
  185. package/public/manager/src/settings/pages/components/TransportStatusChips.tsx +13 -4
  186. package/public/manager/src/settings/types.ts +1 -0
  187. package/scripts/check-redaction-sinks.mjs +276 -0
  188. package/scripts/pi-rpc-probe.mts +361 -0
  189. package/scripts/release-gates.mjs +83 -0
  190. package/scripts/release-preview.sh +8 -1
  191. package/scripts/release.sh +13 -2
  192. package/scripts/sync-electron-version.cjs +93 -0
  193. package/public/dist/assets/ChannelsDiscord-Bt1_rtNC.js +0 -1
  194. package/public/dist/assets/ChannelsTelegram-BLV0m1oq.js +0 -1
  195. package/public/dist/assets/TransportStatusChips-Bld0yXi7.js +0 -1
  196. package/public/dist/assets/app-iSJhsP20.js +0 -55
  197. package/public/dist/assets/bgtask-badge-CSkSsoB6.js +0 -1
  198. package/public/dist/assets/iframe-renderer-DXdo0YXN.js +0 -1
  199. package/public/dist/assets/memory-DeKUlD2M.js +0 -1
  200. package/public/dist/assets/message-history-CWTQ2woA.js +0 -1
  201. package/public/dist/assets/settings-CDjs7hTT.js +0 -1
  202. package/public/dist/assets/settings-core-Cs_oq-b3.js +0 -99
  203. package/public/dist/assets/settings-core-D-TOh1a6.js +0 -1
  204. package/public/dist/assets/skills-GuIfNASs.js +0 -1
  205. package/public/dist/assets/slash-commands-BtZa093n.js +0 -20
  206. package/public/dist/assets/slash-commands-DGJKfpMT.js +0 -1
  207. package/public/dist/assets/ui-DJg5Q_NJ.js +0 -1
  208. /package/public/dist/assets/{DashboardMeta-B7CVqXcU.js → DashboardMeta-DennaaA_.js} +0 -0
  209. /package/public/dist/assets/{Display-BF-rACK6.js → Display-hGuSn_W5.js} +0 -0
  210. /package/public/dist/assets/{Employees-D3hnciP0.js → Employees-B5YXk5ma.js} +0 -0
  211. /package/public/dist/assets/{Heartbeat-D4Pn5O3O.js → Heartbeat-vDGhPfct.js} +0 -0
  212. /package/public/dist/assets/{Mcp-SfytKWGX.js → Mcp-BHWnJ6SP.js} +0 -0
  213. /package/public/dist/assets/{Memory-DNxBOQ7N.js → Memory-B_UZ-iCA.js} +0 -0
  214. /package/public/dist/assets/{ModelProvider-CXwtlG7l.js → ModelProvider-CgUvs6pE.js} +0 -0
  215. /package/public/dist/assets/{Network-DISUhige.js → Network-AWpSkALD.js} +0 -0
  216. /package/public/dist/assets/{Profile-B8yBO3Ju.js → Profile-DnIE2ios.js} +0 -0
  217. /package/public/dist/assets/{Prompts-5lNQ5qOm.js → Prompts-B9aKLall.js} +0 -0
  218. /package/public/dist/assets/{SpeechKeys-BepaIHBa.js → SpeechKeys-K4Be_o7z.js} +0 -0
  219. /package/public/dist/assets/{TelegramHub--DNjRAwW.js → TelegramHub-CFwSkzHP.js} +0 -0
  220. /package/public/dist/assets/{TerminalPanel-DQovYfwz.js → TerminalPanel-CdlFZHAK.js} +0 -0
@@ -0,0 +1,376 @@
1
+ // ─── Shared Message Chunking ─────────────────────────
2
+ // One splitter for every channel. Promoted from src/slack/format.ts, which is
3
+ // the only implementation that survived an adversarial audit; Discord and
4
+ // Telegram each grew their own and each lost content in a different way.
5
+ //
6
+ // The guarantees, in order of importance:
7
+ // 1. No content loss. Chunks concatenate back to the input, apart from the
8
+ // ``` markers deliberately injected to keep a split code block closed.
9
+ // 2. No split surrogate pairs. Half an emoji is worse than an oversized
10
+ // chunk, and a zero-width cut loops forever.
11
+ // 3. Balanced fences. Agent output is code-heavy; a split inside a fence
12
+ // renders the remainder as prose on every channel that speaks markdown.
13
+ /** Below this, fence-aware wrapping cannot fit; fall back to a plain split. */
14
+ const MIN_FENCE_AWARE_LIMIT = 24;
15
+ /**
16
+ * A language tag longer than this is not a real language. Truncating bounds
17
+ * the reserve computation: an unbounded tag could consume the whole budget
18
+ * and leave no room for payload.
19
+ */
20
+ const MAX_FENCE_LANG = 20;
21
+ /**
22
+ * Bytes a chunk must reserve to reopen an inherited fence and close its own.
23
+ * NOT a constant: reopening as ```typescript costs 14, not the 4 a bare fence
24
+ * costs, and a fixed reserve pushes the chunk past the caller's limit.
25
+ */
26
+ export function fenceReserve(lang, marker = '```') {
27
+ return `${marker}${lang}\n`.length + `\n${marker}`.length;
28
+ }
29
+ /**
30
+ * Fences are LINE constructs: the delimiter must start a line (up to three
31
+ * spaces of indent), and a closer must be at least as long as its opener and
32
+ * carry no info string.
33
+ *
34
+ * Counting every ``` in the text instead treats a backtick run inside a code
35
+ * block — `const marker = "```";` is ordinary agent output — as a closer, and
36
+ * ships the remainder of the message as prose.
37
+ *
38
+ * SCOPE: top-level fences only. A fence nested in a block quote or list item
39
+ * ("> ```ts", "- ```ts") is not tracked, so such a block splits as prose
40
+ * rather than being reopened. Repairing it would mean carrying the container
41
+ * prefix into every injected marker, which is a larger change than the defect
42
+ * warrants — agent output puts code at the top level.
43
+ */
44
+ const FENCE_LINE = /^ {0,3}(`{3,}|~{3,})[ \t]*([^\n`]*)$/;
45
+ /** True when the text contains a line-start fence of any delimiter. */
46
+ export function hasFence(text) {
47
+ return FENCE_ANYWHERE.test(text);
48
+ }
49
+ const FENCE_ANYWHERE = /^ {0,3}(?:`{3,}|~{3,})/m;
50
+ /** The fence still open after `text`, or null when everything is closed. */
51
+ export function scanOpenFence(text, initial = null,
52
+ /**
53
+ * False when `text` continues a line rather than starting one. A chunker
54
+ * hands us slices, and a delimiter that sits mid-line in the source must
55
+ * not become a fence just because a slice happens to begin there.
56
+ */
57
+ atLineStart = true) {
58
+ let open = initial;
59
+ const lines = text.split('\n');
60
+ for (const [index, line] of lines.entries()) {
61
+ if (index === 0 && !atLineStart)
62
+ continue;
63
+ const m = FENCE_LINE.exec(line);
64
+ if (!m)
65
+ continue;
66
+ const marker = m[1] ?? '';
67
+ const info = (m[2] ?? '').trim();
68
+ if (open) {
69
+ // A closer uses the same character, is at least as long, and has
70
+ // no info string. Anything else is content inside the block.
71
+ const sameChar = marker[0] === open.marker[0];
72
+ if (sameChar && marker.length >= open.marker.length && info === '')
73
+ open = null;
74
+ continue;
75
+ }
76
+ open = { marker, lang: fenceLangOf(info) };
77
+ }
78
+ return open;
79
+ }
80
+ function fenceLangOf(info) {
81
+ return /^[A-Za-z0-9_+#.-]*$/.test(info) ? info.slice(0, MAX_FENCE_LANG) : '';
82
+ }
83
+ /** Language tag of the fence left open by `text`, or '' when none is open. */
84
+ export function trailingFenceLang(text) {
85
+ return scanOpenFence(text)?.lang ?? '';
86
+ }
87
+ /**
88
+ * Split at a code-point boundary so a cut never separates a surrogate pair.
89
+ * When backing off would produce an empty chunk (a limit so small that a
90
+ * single astral character does not fit), advance instead: emitting one
91
+ * slightly oversized chunk is better than shipping half an emoji, and better
92
+ * than looping forever on a zero-length cut.
93
+ */
94
+ export function safeCut(text, index) {
95
+ if (index >= text.length)
96
+ return text.length;
97
+ const code = text.charCodeAt(index);
98
+ const splitsPair = code >= 0xDC00 && code <= 0xDFFF;
99
+ if (!splitsPair)
100
+ return index;
101
+ return index > 1 ? index - 1 : index + 1;
102
+ }
103
+ /**
104
+ * Split text into chunks of at most `limit` characters WITHOUT losing content.
105
+ * Prefers newline boundaries; the newline stays with the preceding chunk so
106
+ * chunks concatenate back to the input exactly.
107
+ */
108
+ export function splitPreservingContent(text, limit) {
109
+ const out = [];
110
+ let rest = text;
111
+ while (rest.length > limit) {
112
+ // Keep the newline in this chunk (+1) rather than dropping it.
113
+ const nl = rest.lastIndexOf('\n', limit - 1);
114
+ let cut = nl > 0 ? nl + 1 : safeCut(rest, limit);
115
+ // A zero cut would never consume input. Take one whole code point.
116
+ if (cut <= 0)
117
+ cut = safeCut(rest, Math.max(1, Math.min(limit, rest.length)));
118
+ if (cut <= 0)
119
+ cut = Math.min(2, rest.length);
120
+ out.push(rest.slice(0, cut));
121
+ rest = rest.slice(cut);
122
+ }
123
+ if (rest.length > 0)
124
+ out.push(rest);
125
+ return out;
126
+ }
127
+ /**
128
+ * Split for a channel that renders ``` code fences.
129
+ *
130
+ * Content is never lost: chunks concatenate back to the input apart from the
131
+ * ``` markers injected to keep each chunk's code block closed.
132
+ */
133
+ export function chunkFenceAware(text, limit) {
134
+ if (!text)
135
+ return [text];
136
+ const cap = Math.max(1, Math.floor(limit));
137
+ if (text.length <= cap)
138
+ return [text];
139
+ // No fence in the input means no fence can straddle a boundary, so
140
+ // reserving room for injected markers only wastes budget — and a wasted
141
+ // budget costs the user a whole extra outbound message. The check goes
142
+ // through the lexer so a ~~~ block is not silently sent down the plain
143
+ // path, and an inline ``` run does not force the fence path.
144
+ if (!hasFence(text))
145
+ return splitPreservingContent(text, cap);
146
+ // Too small to wrap anything in fences — split plainly rather than spin.
147
+ if (cap < MIN_FENCE_AWARE_LIMIT)
148
+ return splitPreservingContent(text, cap);
149
+ // Reserve for the worst case this text can produce, so a piece can always
150
+ // carry its reopener and closer. Using the widest tag present keeps the
151
+ // pieces uniform; per-piece reserve would need the split to already exist.
152
+ const worst = widestFenceOpener(text);
153
+ const reserve = fenceReserve(worst.lang, worst.marker);
154
+ // A pathological tag could eat the whole budget. Fall back to bare fences
155
+ // rather than emit zero-payload pieces and never terminate.
156
+ const usableReserve = reserve < cap ? reserve : fenceReserve('');
157
+ const budget = Math.max(1, cap - usableReserve);
158
+ // Every piece is produced from the ORIGINAL text and strictly consumes it,
159
+ // so termination does not depend on how many fences we inject afterwards.
160
+ const rawPieces = splitPreservingContent(text, budget);
161
+ // A piece holding only fence markers and whitespace would post as an empty
162
+ // code block. MERGE it forward into the next piece rather than dropping it
163
+ // — skipping it silently deleted the source's own blank lines.
164
+ // Merging must not push a piece past the budget, or the injected fences
165
+ // would carry it over the caller's limit.
166
+ const pieces = [];
167
+ let carry = '';
168
+ for (const piece of rawPieces) {
169
+ const candidate = carry + piece;
170
+ if (candidate.length > budget) {
171
+ // Cannot merge without overflowing: emit what we have separately.
172
+ if (carry)
173
+ pieces.push(carry);
174
+ pieces.push(piece);
175
+ carry = '';
176
+ continue;
177
+ }
178
+ if (candidate.replace(/```/g, '').trim() === '') {
179
+ carry = candidate;
180
+ continue;
181
+ }
182
+ pieces.push(candidate);
183
+ carry = '';
184
+ }
185
+ if (carry) {
186
+ const last = pieces[pieces.length - 1];
187
+ if (last !== undefined && last.length + carry.length <= budget) {
188
+ pieces[pieces.length - 1] = last + carry;
189
+ }
190
+ else {
191
+ pieces.push(carry);
192
+ }
193
+ }
194
+ const out = closeAndReopen(pieces, reserve < cap, null, usableReserve);
195
+ return out.length > 0 ? out : [text];
196
+ }
197
+ /**
198
+ * Close a fence a piece leaves open and reopen it on the next piece, so every
199
+ * chunk renders as code on its own. `keepLang` is false only when reserving
200
+ * for the tag would not fit; highlighting is sacrificed before the size limit.
201
+ *
202
+ * Shared with the Telegram rich path so both agree on fence repair, and so a
203
+ * remainder re-split inherits the fence instead of starting as prose.
204
+ */
205
+ export function closeAndReopen(pieces, keepLang = true, inheritedStart = null,
206
+ /**
207
+ * Largest reopener the caller budgeted for. A split can turn a run that was
208
+ * NOT an opener in the source ("````… x 한한````" carries extra backticks,
209
+ * so it is not a fence) into one, and reopening with that longer delimiter
210
+ * would blow the limit the caller sized for the source's own fences.
211
+ */
212
+ maxReserve = Number.POSITIVE_INFINITY) {
213
+ // Pieces were built to fit the payload budget, so the seam fix must respect
214
+ // the same ceiling. Infer it rather than take another parameter: the pieces
215
+ // are the authority on what the splitter considered a full piece.
216
+ const budget = Math.max(...pieces.map((p) => p.length), 1);
217
+ const adjusted = avoidMidLineDelimiterAtSeam(pieces, budget);
218
+ const merged = mergeBareOpenerPieces(adjusted, budget);
219
+ const out = [];
220
+ let open = inheritedStart;
221
+ // A piece starts mid-line whenever the previous one did not end with a
222
+ // newline. Without this, a delimiter that sits inside a line in the source
223
+ // becomes a fence the moment a split lands just before it — inventing a
224
+ // code block and overshooting the caller's limit.
225
+ let atLineStart = true;
226
+ for (const piece of merged) {
227
+ const inherited = open;
228
+ // Scan the ORIGINAL piece: appending the closer below would make the
229
+ // scan report a balanced chunk and drop the carry-over.
230
+ //
231
+ // A reopener ends with a newline, so when one is prepended the piece
232
+ // DOES begin a line in the emitted chunk even if it continued one in
233
+ // the source. Reading the source state there left a real fence
234
+ // unrecognised and the chunk went out unclosed.
235
+ const startsLine = inherited !== null ? true : atLineStart;
236
+ const after = scanOpenFence(piece, inherited, startsLine);
237
+ let chunk = piece;
238
+ // KNOWN LIMITATION. A delimiter run longer than the budget cannot be
239
+ // reopened without breaking the caller's limit, so its block is not
240
+ // carried across chunks. Usually such a run is an artifact of the split
241
+ // (a backtick sequence that was not an opener in the source), but a
242
+ // genuine `-run wider than the budget is treated the same way.
243
+ //
244
+ // What still holds for those inputs: no content loss, no split
245
+ // surrogate, no chunk over the limit. What does not: the continuation
246
+ // will render as prose rather than code. Delivery beats formatting.
247
+ const repairable = (f) => f !== null && fenceReserve(f.lang, f.marker) <= maxReserve;
248
+ if (repairable(inherited)) {
249
+ const tag = keepLang ? inherited.lang : '';
250
+ chunk = `${inherited.marker}${tag}\n${chunk}`;
251
+ }
252
+ if (repairable(after)) {
253
+ chunk = chunk.endsWith('\n') ? `${chunk}${after.marker}` : `${chunk}\n${after.marker}`;
254
+ }
255
+ out.push(chunk);
256
+ // Carrying an unrepairable fence forward would make every later chunk
257
+ // try (and fail) to reopen it.
258
+ open = repairable(after) ? after : null;
259
+ // A reopener always ends with a newline, so a chunk we reopened still
260
+ // reflects the source's own line state at its end.
261
+ atLineStart = piece.endsWith('\n');
262
+ }
263
+ return out;
264
+ }
265
+ /**
266
+ * A piece holding nothing but an opening fence line would be emitted as an
267
+ * empty code block, and the next chunk would reopen the same fence — one
268
+ * wasted message carrying no content. Fold it into the piece that follows and
269
+ * push that piece's overflow down the line, exactly as the seam fix does.
270
+ */
271
+ function mergeBareOpenerPieces(pieces, budget) {
272
+ const out = [...pieces];
273
+ // Bounded by the piece count. Merging can push a spill forward that is
274
+ // itself a bare opener, and rewinding to re-examine the same index let
275
+ // that repeat without end: a 43-character message never returned.
276
+ let merges = 0;
277
+ for (let i = 0; i < out.length - 1; i += 1) {
278
+ const piece = out[i] ?? '';
279
+ if (!/^ {0,3}(?:`{3,}|~{3,})[A-Za-z0-9_+#.-]*\n$/.test(piece))
280
+ continue;
281
+ if (merges >= pieces.length)
282
+ break;
283
+ merges += 1;
284
+ out[i + 1] = piece + (out[i + 1] ?? '');
285
+ out.splice(i, 1);
286
+ i -= 1;
287
+ for (let k = Math.max(i + 1, 0); k < out.length; k += 1) {
288
+ const current = out[k] ?? '';
289
+ if (current.length <= budget)
290
+ break;
291
+ const keep = safeCut(current, budget);
292
+ out[k] = current.slice(0, keep);
293
+ const spill = current.slice(keep);
294
+ if (k + 1 < out.length)
295
+ out[k + 1] = spill + (out[k + 1] ?? '');
296
+ else
297
+ out.push(spill);
298
+ }
299
+ }
300
+ return out;
301
+ }
302
+ /**
303
+ * A reopener ends with a newline, so whatever the next piece starts with lands
304
+ * at column zero. A delimiter run that sat MID-LINE in the source becomes a
305
+ * real fence there, closing the block a message early.
306
+ *
307
+ * Move one code point back across the seam so the run keeps company on its
308
+ * line. Rewriting the payload is not an option: these chunks must still
309
+ * concatenate back to the source.
310
+ */
311
+ function avoidMidLineDelimiterAtSeam(pieces, budget) {
312
+ const out = [...pieces];
313
+ for (let i = 1; i < out.length; i += 1) {
314
+ const prev = out[i - 1] ?? '';
315
+ const piece = out[i] ?? '';
316
+ if (prev.endsWith('\n'))
317
+ continue;
318
+ if (!/^ {0,3}(?:`{3,}|~{3,})/.test(piece))
319
+ continue;
320
+ // Take a whole code point, and never empty the previous piece — that
321
+ // would stall the walk without fixing anything.
322
+ const low = prev.charCodeAt(prev.length - 1);
323
+ const step = low >= 0xDC00 && low <= 0xDFFF && prev.length >= 2 ? 2 : 1;
324
+ if (prev.length <= step)
325
+ continue;
326
+ // The receiving piece is usually already full, so growing it would push
327
+ // the chunk past the limit. Push the overflow down the line instead;
328
+ // each piece keeps at most `budget`, and the tail grows a new piece if
329
+ // needed. Content is conserved — nothing is added or dropped, only
330
+ // moved.
331
+ out[i - 1] = prev.slice(0, prev.length - step);
332
+ out[i] = prev.slice(prev.length - step) + piece;
333
+ for (let k = i; k < out.length; k += 1) {
334
+ const current = out[k] ?? '';
335
+ if (current.length <= budget)
336
+ break;
337
+ const keep = safeCut(current, budget);
338
+ out[k] = current.slice(0, keep);
339
+ const spill = current.slice(keep);
340
+ if (k + 1 < out.length)
341
+ out[k + 1] = spill + (out[k + 1] ?? '');
342
+ else
343
+ out.push(spill);
344
+ }
345
+ }
346
+ return out;
347
+ }
348
+ /**
349
+ * Widest fence opener in the text, for reserve sizing. `initial` lets a caller
350
+ * account for a fence inherited from earlier content: the remainder may close
351
+ * it and open a WIDER one, and missing that overshoots the limit.
352
+ */
353
+ export function widestFenceOpener(text, initial = null) {
354
+ let widest = initial ?? { marker: '```', lang: '' };
355
+ let open = initial;
356
+ for (const line of text.split('\n')) {
357
+ const m = FENCE_LINE.exec(line);
358
+ if (!m)
359
+ continue;
360
+ const marker = m[1] ?? '';
361
+ const info = (m[2] ?? '').trim();
362
+ if (open) {
363
+ const sameChar = marker[0] === open.marker[0];
364
+ if (sameChar && marker.length >= open.marker.length && info === '')
365
+ open = null;
366
+ continue;
367
+ }
368
+ const lang = fenceLangOf(info);
369
+ open = { marker, lang };
370
+ if (fenceReserve(lang, marker) > fenceReserve(widest.lang, widest.marker)) {
371
+ widest = { marker, lang };
372
+ }
373
+ }
374
+ return widest;
375
+ }
376
+ //# sourceMappingURL=chunk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk.js","sourceRoot":"","sources":["../../../src/messaging/chunk.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ // ─── Delivery Deduplication ──────────────────────────
2
+ // A time-bounded seen-set, shared by the channels that can be handed the same
3
+ // delivery twice: a reconnecting updater replays, and a vendor retries when an
4
+ // ack is slow. Processing one twice runs the agent twice.
5
+ //
6
+ // Promoted from the Slack socket client, which is where this was first got
7
+ // right; the shape of the mistakes it avoids is recorded below.
8
+ import { log } from '../core/logger.js';
9
+ /**
10
+ * @param ttlMs how long a delivery id stays remembered
11
+ * @param sweepAt size that triggers an expiry sweep
12
+ */
13
+ export function createSeenSet(ttlMs, sweepAt = 5_000, maxTracked = MAX_TRACKED_DELIVERIES) {
14
+ // A Map iterates in insertion order and every entry is recorded with the
15
+ // current time, so the oldest entries are always at the front. That makes
16
+ // expiry a walk from the head that stops at the first live entry —
17
+ // proportional to what actually expired, not to the size of the set.
18
+ //
19
+ // Scanning the whole map instead was O(n) per insertion once it passed the
20
+ // sweep threshold, which is O(n²) over a burst: 60,000 ids took five
21
+ // seconds of synchronous work in a path that runs per inbound message.
22
+ const seenAt = new Map();
23
+ let warnedAtBudget = false;
24
+ // Head-ordered expiry assumes entries are stamped in non-decreasing order.
25
+ // Date.now() can go backwards — NTP correction, a manual clock change — and
26
+ // one older stamp behind a newer one parks an expired entry in front of the
27
+ // walk's stopping point, where it holds a slot until the newer one expires.
28
+ // Clamping to the last observation keeps the sequence monotonic.
29
+ let lastObserved = 0;
30
+ const monotonicNow = () => {
31
+ lastObserved = Math.max(lastObserved, Date.now());
32
+ return lastObserved;
33
+ };
34
+ const dropExpired = (now) => {
35
+ for (const [id, when] of seenAt) {
36
+ if (now - when < ttlMs)
37
+ break; // everything after this is younger
38
+ seenAt.delete(id);
39
+ }
40
+ };
41
+ return {
42
+ seen(key) {
43
+ const now = monotonicNow();
44
+ const at = seenAt.get(key);
45
+ if (at !== undefined && now - at < ttlMs)
46
+ return true;
47
+ // An expired key being seen again is re-recorded below. Delete it
48
+ // first so the Map re-inserts it at the BACK: insertion order is
49
+ // what makes expiry a walk from the head, and refreshing a key in
50
+ // place would leave a young entry sitting in front of older ones.
51
+ if (at !== undefined)
52
+ seenAt.delete(key);
53
+ // Expiry drops EXPIRED entries only. Evicting an unexpired id to
54
+ // hit a size target would let a delayed retry through — precisely
55
+ // the duplicate this exists to stop.
56
+ //
57
+ // It runs lazily rather than on a timer, because a timer here
58
+ // outlives the channel that created it; leaked timers were their
59
+ // own defect class in this codebase.
60
+ if (seenAt.size > sweepAt)
61
+ dropExpired(now);
62
+ // A TTL is not a bound on how many DISTINCT ids arrive inside it.
63
+ // Past the budget, stop admitting new keys: already-tracked ids
64
+ // keep their guarantee, and the set stops growing.
65
+ //
66
+ // The trade is explicit. Under a flood this degrades to "may
67
+ // reprocess a message" rather than "may exhaust memory", and only
68
+ // for ids arriving after the budget is reached.
69
+ if (seenAt.size >= maxTracked) {
70
+ // Reclaim before refusing: the budget is about live entries,
71
+ // and expired ones are not live. Refusing while dead entries
72
+ // hold slots would drop dedupe for traffic the set has room
73
+ // for — which is what a backwards clock step produced, since
74
+ // a stale stamp can sit ahead of the head-walk's stopping
75
+ // point and outlive its own window.
76
+ dropExpired(now);
77
+ }
78
+ if (seenAt.size >= maxTracked) {
79
+ if (!warnedAtBudget) {
80
+ warnedAtBudget = true;
81
+ log.warn(`[dedupe] tracking budget reached (${maxTracked}); new ids are no longer recorded`);
82
+ }
83
+ return false;
84
+ }
85
+ seenAt.set(key, now);
86
+ return false;
87
+ },
88
+ size: () => seenAt.size,
89
+ };
90
+ }
91
+ /**
92
+ * Distinct deliveries tracked at once.
93
+ *
94
+ * Reaching this is an anomaly, not routine: a single user's channel traffic is
95
+ * bounded by how fast a person types. Sized so a busy workspace never sees it
96
+ * while a flood cannot walk memory upward without limit.
97
+ */
98
+ const MAX_TRACKED_DELIVERIES = 50_000;
99
+ /**
100
+ * Ten minutes covers a vendor's retry horizon with room to spare, without
101
+ * holding ids long enough for the map to matter.
102
+ */
103
+ export const DELIVERY_DEDUPE_TTL_MS = 10 * 60 * 1000;
104
+ //# sourceMappingURL=dedupe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../../../src/messaging/dedupe.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,8EAA8E;AAC9E,+EAA+E;AAC/E,0DAA0D;AAC1D,EAAE;AACF,2EAA2E;AAC3E,gEAAgE;AAEhE,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AASxC;;;GAGG;AACH,MAAM,UAAU,aAAa,CACzB,KAAa,EACb,OAAO,GAAG,KAAK,EACf,UAAU,GAAG,sBAAsB;IAEnC,yEAAyE;IACzE,0EAA0E;IAC1E,mEAAmE;IACnE,qEAAqE;IACrE,EAAE;IACF,2EAA2E;IAC3E,qEAAqE;IACrE,uEAAuE;IACvE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,2EAA2E;IAC3E,4EAA4E;IAC5E,4EAA4E;IAC5E,4EAA4E;IAC5E,iEAAiE;IACjE,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,MAAM,YAAY,GAAG,GAAG,EAAE;QACtB,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAClD,OAAO,YAAY,CAAC;IACxB,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,GAAW,EAAE,EAAE;QAChC,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,MAAM,EAAE,CAAC;YAC9B,IAAI,GAAG,GAAG,IAAI,GAAG,KAAK;gBAAE,MAAM,CAAC,mCAAmC;YAClE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;IACL,CAAC,CAAC;IAEF,OAAO;QACH,IAAI,CAAC,GAAW;YACZ,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;YAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,EAAE,KAAK,SAAS,IAAI,GAAG,GAAG,EAAE,GAAG,KAAK;gBAAE,OAAO,IAAI,CAAC;YACtD,kEAAkE;YAClE,iEAAiE;YACjE,kEAAkE;YAClE,kEAAkE;YAClE,IAAI,EAAE,KAAK,SAAS;gBAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAEzC,iEAAiE;YACjE,kEAAkE;YAClE,qCAAqC;YACrC,EAAE;YACF,8DAA8D;YAC9D,iEAAiE;YACjE,qCAAqC;YACrC,IAAI,MAAM,CAAC,IAAI,GAAG,OAAO;gBAAE,WAAW,CAAC,GAAG,CAAC,CAAC;YAE5C,kEAAkE;YAClE,gEAAgE;YAChE,mDAAmD;YACnD,EAAE;YACF,6DAA6D;YAC7D,kEAAkE;YAClE,gDAAgD;YAChD,IAAI,MAAM,CAAC,IAAI,IAAI,UAAU,EAAE,CAAC;gBAC5B,6DAA6D;gBAC7D,6DAA6D;gBAC7D,4DAA4D;gBAC5D,6DAA6D;gBAC7D,0DAA0D;gBAC1D,oCAAoC;gBACpC,WAAW,CAAC,GAAG,CAAC,CAAC;YACrB,CAAC;YACD,IAAI,MAAM,CAAC,IAAI,IAAI,UAAU,EAAE,CAAC;gBAC5B,IAAI,CAAC,cAAc,EAAE,CAAC;oBAClB,cAAc,GAAG,IAAI,CAAC;oBACtB,GAAG,CAAC,IAAI,CAAC,qCAAqC,UAAU,mCAAmC,CAAC,CAAC;gBACjG,CAAC;gBACD,OAAO,KAAK,CAAC;YACjB,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACrB,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI;KAC1B,CAAC;AACN,CAAC;AAED;;;;;;GAMG;AACH,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAEtC;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC"}