clisbot 0.1.29 → 0.1.32
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.
- package/dist/main.js +1866 -572
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
7
12
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
8
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
22
|
for (let key of __getOwnPropNames(mod))
|
|
11
23
|
if (!__hasOwnProp.call(to, key))
|
|
12
24
|
__defProp(to, key, {
|
|
13
|
-
get: (
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
14
26
|
enumerable: true
|
|
15
27
|
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
16
30
|
return to;
|
|
17
31
|
};
|
|
18
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
19
37
|
var __export = (target, all) => {
|
|
20
38
|
for (var name in all)
|
|
21
39
|
__defProp(target, name, {
|
|
22
40
|
get: all[name],
|
|
23
41
|
enumerable: true,
|
|
24
42
|
configurable: true,
|
|
25
|
-
set: (
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
26
44
|
});
|
|
27
45
|
};
|
|
28
46
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
@@ -62543,6 +62561,421 @@ function prependAttachmentMentions(text, attachmentPaths) {
|
|
|
62543
62561
|
return normalizedText ? `${mentions} ${normalizedText}` : mentions;
|
|
62544
62562
|
}
|
|
62545
62563
|
|
|
62564
|
+
// src/agents/commands.ts
|
|
62565
|
+
function parseAgentCommand(text, options = {}) {
|
|
62566
|
+
const normalized = text.trim();
|
|
62567
|
+
const commandPrefixes = options.commandPrefixes ?? {
|
|
62568
|
+
slash: ["::", "\\"],
|
|
62569
|
+
bash: ["!"]
|
|
62570
|
+
};
|
|
62571
|
+
const bashPrefix = findMatchingPrefix(normalized, commandPrefixes.bash);
|
|
62572
|
+
if (bashPrefix) {
|
|
62573
|
+
const command2 = normalized.slice(bashPrefix.length).trim();
|
|
62574
|
+
return {
|
|
62575
|
+
type: "bash",
|
|
62576
|
+
command: command2,
|
|
62577
|
+
source: "shortcut"
|
|
62578
|
+
};
|
|
62579
|
+
}
|
|
62580
|
+
const slashPrefix = findMatchingPrefix(normalized, ["/", ...commandPrefixes.slash]);
|
|
62581
|
+
if (!slashPrefix) {
|
|
62582
|
+
return null;
|
|
62583
|
+
}
|
|
62584
|
+
const withoutSlash = normalized.slice(slashPrefix.length).trim();
|
|
62585
|
+
if (!withoutSlash) {
|
|
62586
|
+
return {
|
|
62587
|
+
type: "control",
|
|
62588
|
+
name: "help"
|
|
62589
|
+
};
|
|
62590
|
+
}
|
|
62591
|
+
const [command] = withoutSlash.split(/\s+/, 1);
|
|
62592
|
+
const lowered = normalizeSlashCommandName(command, options.botUsername);
|
|
62593
|
+
if (lowered === "start") {
|
|
62594
|
+
return {
|
|
62595
|
+
type: "control",
|
|
62596
|
+
name: "start"
|
|
62597
|
+
};
|
|
62598
|
+
}
|
|
62599
|
+
if (lowered === "status") {
|
|
62600
|
+
return {
|
|
62601
|
+
type: "control",
|
|
62602
|
+
name: "status"
|
|
62603
|
+
};
|
|
62604
|
+
}
|
|
62605
|
+
if (lowered === "help") {
|
|
62606
|
+
return {
|
|
62607
|
+
type: "control",
|
|
62608
|
+
name: "help"
|
|
62609
|
+
};
|
|
62610
|
+
}
|
|
62611
|
+
if (lowered === "whoami") {
|
|
62612
|
+
return {
|
|
62613
|
+
type: "control",
|
|
62614
|
+
name: "whoami"
|
|
62615
|
+
};
|
|
62616
|
+
}
|
|
62617
|
+
if (lowered === "transcript") {
|
|
62618
|
+
return {
|
|
62619
|
+
type: "control",
|
|
62620
|
+
name: "transcript"
|
|
62621
|
+
};
|
|
62622
|
+
}
|
|
62623
|
+
if (lowered === "attach") {
|
|
62624
|
+
return {
|
|
62625
|
+
type: "control",
|
|
62626
|
+
name: "attach"
|
|
62627
|
+
};
|
|
62628
|
+
}
|
|
62629
|
+
if (lowered === "detach") {
|
|
62630
|
+
return {
|
|
62631
|
+
type: "control",
|
|
62632
|
+
name: "detach"
|
|
62633
|
+
};
|
|
62634
|
+
}
|
|
62635
|
+
if (lowered === "watch") {
|
|
62636
|
+
const parsed = parseWatchCommand(withoutSlash.slice(command.length).trim());
|
|
62637
|
+
if (parsed) {
|
|
62638
|
+
return {
|
|
62639
|
+
type: "control",
|
|
62640
|
+
name: "watch",
|
|
62641
|
+
intervalMs: parsed.intervalMs,
|
|
62642
|
+
durationMs: parsed.durationMs
|
|
62643
|
+
};
|
|
62644
|
+
}
|
|
62645
|
+
return {
|
|
62646
|
+
type: "control",
|
|
62647
|
+
name: "help"
|
|
62648
|
+
};
|
|
62649
|
+
}
|
|
62650
|
+
if (lowered === "stop") {
|
|
62651
|
+
return {
|
|
62652
|
+
type: "control",
|
|
62653
|
+
name: "stop"
|
|
62654
|
+
};
|
|
62655
|
+
}
|
|
62656
|
+
if (lowered === "nudge") {
|
|
62657
|
+
return {
|
|
62658
|
+
type: "control",
|
|
62659
|
+
name: "nudge"
|
|
62660
|
+
};
|
|
62661
|
+
}
|
|
62662
|
+
if (lowered === "followup") {
|
|
62663
|
+
const action = withoutSlash.slice(command.length).trim().toLowerCase();
|
|
62664
|
+
if (!action || action === "status") {
|
|
62665
|
+
return {
|
|
62666
|
+
type: "control",
|
|
62667
|
+
name: "followup",
|
|
62668
|
+
action: "status"
|
|
62669
|
+
};
|
|
62670
|
+
}
|
|
62671
|
+
if (action === "auto") {
|
|
62672
|
+
return {
|
|
62673
|
+
type: "control",
|
|
62674
|
+
name: "followup",
|
|
62675
|
+
action: "auto",
|
|
62676
|
+
mode: "auto"
|
|
62677
|
+
};
|
|
62678
|
+
}
|
|
62679
|
+
if (action === "mention-only") {
|
|
62680
|
+
return {
|
|
62681
|
+
type: "control",
|
|
62682
|
+
name: "followup",
|
|
62683
|
+
action: "mention-only",
|
|
62684
|
+
mode: "mention-only"
|
|
62685
|
+
};
|
|
62686
|
+
}
|
|
62687
|
+
if (action === "pause") {
|
|
62688
|
+
return {
|
|
62689
|
+
type: "control",
|
|
62690
|
+
name: "followup",
|
|
62691
|
+
action: "pause",
|
|
62692
|
+
mode: "paused"
|
|
62693
|
+
};
|
|
62694
|
+
}
|
|
62695
|
+
if (action === "resume") {
|
|
62696
|
+
return {
|
|
62697
|
+
type: "control",
|
|
62698
|
+
name: "followup",
|
|
62699
|
+
action: "resume"
|
|
62700
|
+
};
|
|
62701
|
+
}
|
|
62702
|
+
return {
|
|
62703
|
+
type: "control",
|
|
62704
|
+
name: "followup",
|
|
62705
|
+
action: "status"
|
|
62706
|
+
};
|
|
62707
|
+
}
|
|
62708
|
+
if (lowered === "responsemode") {
|
|
62709
|
+
const action = withoutSlash.slice(command.length).trim().toLowerCase();
|
|
62710
|
+
if (!action || action === "status") {
|
|
62711
|
+
return {
|
|
62712
|
+
type: "control",
|
|
62713
|
+
name: "responsemode",
|
|
62714
|
+
action: "status"
|
|
62715
|
+
};
|
|
62716
|
+
}
|
|
62717
|
+
if (action === "capture-pane" || action === "message-tool") {
|
|
62718
|
+
return {
|
|
62719
|
+
type: "control",
|
|
62720
|
+
name: "responsemode",
|
|
62721
|
+
action,
|
|
62722
|
+
responseMode: action
|
|
62723
|
+
};
|
|
62724
|
+
}
|
|
62725
|
+
return {
|
|
62726
|
+
type: "control",
|
|
62727
|
+
name: "responsemode",
|
|
62728
|
+
action: "status"
|
|
62729
|
+
};
|
|
62730
|
+
}
|
|
62731
|
+
if (lowered === "streaming") {
|
|
62732
|
+
const action = withoutSlash.slice(command.length).trim().toLowerCase();
|
|
62733
|
+
if (!action || action === "status") {
|
|
62734
|
+
return {
|
|
62735
|
+
type: "control",
|
|
62736
|
+
name: "streaming",
|
|
62737
|
+
action: "status"
|
|
62738
|
+
};
|
|
62739
|
+
}
|
|
62740
|
+
if (action === "on") {
|
|
62741
|
+
return {
|
|
62742
|
+
type: "control",
|
|
62743
|
+
name: "streaming",
|
|
62744
|
+
action: "on",
|
|
62745
|
+
streaming: "all"
|
|
62746
|
+
};
|
|
62747
|
+
}
|
|
62748
|
+
if (action === "off" || action === "latest" || action === "all") {
|
|
62749
|
+
return {
|
|
62750
|
+
type: "control",
|
|
62751
|
+
name: "streaming",
|
|
62752
|
+
action,
|
|
62753
|
+
streaming: action
|
|
62754
|
+
};
|
|
62755
|
+
}
|
|
62756
|
+
return {
|
|
62757
|
+
type: "control",
|
|
62758
|
+
name: "streaming",
|
|
62759
|
+
action: "status"
|
|
62760
|
+
};
|
|
62761
|
+
}
|
|
62762
|
+
if (lowered === "additionalmessagemode") {
|
|
62763
|
+
const action = withoutSlash.slice(command.length).trim().toLowerCase();
|
|
62764
|
+
if (!action || action === "status") {
|
|
62765
|
+
return {
|
|
62766
|
+
type: "control",
|
|
62767
|
+
name: "additionalmessagemode",
|
|
62768
|
+
action: "status"
|
|
62769
|
+
};
|
|
62770
|
+
}
|
|
62771
|
+
if (action === "queue" || action === "steer") {
|
|
62772
|
+
return {
|
|
62773
|
+
type: "control",
|
|
62774
|
+
name: "additionalmessagemode",
|
|
62775
|
+
action,
|
|
62776
|
+
additionalMessageMode: action
|
|
62777
|
+
};
|
|
62778
|
+
}
|
|
62779
|
+
return {
|
|
62780
|
+
type: "control",
|
|
62781
|
+
name: "additionalmessagemode",
|
|
62782
|
+
action: "status"
|
|
62783
|
+
};
|
|
62784
|
+
}
|
|
62785
|
+
if (lowered === "bash") {
|
|
62786
|
+
return {
|
|
62787
|
+
type: "bash",
|
|
62788
|
+
command: withoutSlash.slice(command.length).trim(),
|
|
62789
|
+
source: "slash"
|
|
62790
|
+
};
|
|
62791
|
+
}
|
|
62792
|
+
if (lowered === "loop") {
|
|
62793
|
+
const loopText = withoutSlash.slice(command.length).trim();
|
|
62794
|
+
const loweredLoopText = loopText.toLowerCase();
|
|
62795
|
+
if (!loweredLoopText || loweredLoopText === "help") {
|
|
62796
|
+
return {
|
|
62797
|
+
type: "control",
|
|
62798
|
+
name: "loop-help"
|
|
62799
|
+
};
|
|
62800
|
+
}
|
|
62801
|
+
if (loweredLoopText === "status") {
|
|
62802
|
+
return {
|
|
62803
|
+
type: "loop-control",
|
|
62804
|
+
action: "status"
|
|
62805
|
+
};
|
|
62806
|
+
}
|
|
62807
|
+
if (loweredLoopText === "cancel" || loweredLoopText.startsWith("cancel ")) {
|
|
62808
|
+
const cancelArgs = loopText.slice("cancel".length).trim();
|
|
62809
|
+
if (hasLoopFlag(cancelArgs, LOOP_FORCE_FLAG)) {
|
|
62810
|
+
return {
|
|
62811
|
+
type: "loop-error",
|
|
62812
|
+
message: `Use \`/loop cancel --all ${LOOP_APP_FLAG}\` for app-wide cancellation.`
|
|
62813
|
+
};
|
|
62814
|
+
}
|
|
62815
|
+
const all = hasLoopFlag(cancelArgs, LOOP_ALL_FLAG);
|
|
62816
|
+
const app = hasLoopFlag(cancelArgs, LOOP_APP_FLAG);
|
|
62817
|
+
if (app && !all) {
|
|
62818
|
+
return {
|
|
62819
|
+
type: "loop-error",
|
|
62820
|
+
message: `\`${LOOP_APP_FLAG}\` only works with \`/loop cancel ${LOOP_ALL_FLAG}\`.`
|
|
62821
|
+
};
|
|
62822
|
+
}
|
|
62823
|
+
const loopId = cancelArgs.split(/\s+/).map((token) => token.trim()).find((token) => token && token !== LOOP_ALL_FLAG && token !== LOOP_APP_FLAG);
|
|
62824
|
+
return {
|
|
62825
|
+
type: "loop-control",
|
|
62826
|
+
action: "cancel",
|
|
62827
|
+
loopId: loopId || undefined,
|
|
62828
|
+
all,
|
|
62829
|
+
app
|
|
62830
|
+
};
|
|
62831
|
+
}
|
|
62832
|
+
const parsed = parseLoopSlashCommand(loopText);
|
|
62833
|
+
if ("error" in parsed) {
|
|
62834
|
+
return {
|
|
62835
|
+
type: "loop-error",
|
|
62836
|
+
message: parsed.error
|
|
62837
|
+
};
|
|
62838
|
+
}
|
|
62839
|
+
return {
|
|
62840
|
+
type: "loop",
|
|
62841
|
+
params: parsed
|
|
62842
|
+
};
|
|
62843
|
+
}
|
|
62844
|
+
if (lowered === "queue" || lowered === "q") {
|
|
62845
|
+
const queueText = withoutSlash.slice(command.length).trim();
|
|
62846
|
+
const normalizedQueueText = queueText.toLowerCase();
|
|
62847
|
+
if (lowered === "queue") {
|
|
62848
|
+
if (normalizedQueueText === "help") {
|
|
62849
|
+
return {
|
|
62850
|
+
type: "control",
|
|
62851
|
+
name: "queue-help"
|
|
62852
|
+
};
|
|
62853
|
+
}
|
|
62854
|
+
if (normalizedQueueText === "list") {
|
|
62855
|
+
return {
|
|
62856
|
+
type: "control",
|
|
62857
|
+
name: "queue-list"
|
|
62858
|
+
};
|
|
62859
|
+
}
|
|
62860
|
+
if (normalizedQueueText === "clear") {
|
|
62861
|
+
return {
|
|
62862
|
+
type: "control",
|
|
62863
|
+
name: "queue-clear"
|
|
62864
|
+
};
|
|
62865
|
+
}
|
|
62866
|
+
}
|
|
62867
|
+
return {
|
|
62868
|
+
type: "queue",
|
|
62869
|
+
text: queueText
|
|
62870
|
+
};
|
|
62871
|
+
}
|
|
62872
|
+
if (lowered === "queue-list" || lowered === "queuelist") {
|
|
62873
|
+
return {
|
|
62874
|
+
type: "control",
|
|
62875
|
+
name: "queue-list"
|
|
62876
|
+
};
|
|
62877
|
+
}
|
|
62878
|
+
if (lowered === "queue-clear" || lowered === "queueclear") {
|
|
62879
|
+
return {
|
|
62880
|
+
type: "control",
|
|
62881
|
+
name: "queue-clear"
|
|
62882
|
+
};
|
|
62883
|
+
}
|
|
62884
|
+
if (lowered === "steer" || lowered === "s") {
|
|
62885
|
+
return {
|
|
62886
|
+
type: "steer",
|
|
62887
|
+
text: withoutSlash.slice(command.length).trim()
|
|
62888
|
+
};
|
|
62889
|
+
}
|
|
62890
|
+
return {
|
|
62891
|
+
type: "native",
|
|
62892
|
+
text: normalized
|
|
62893
|
+
};
|
|
62894
|
+
}
|
|
62895
|
+
function findMatchingPrefix(text, prefixes) {
|
|
62896
|
+
return [...prefixes].sort((left, right) => right.length - left.length).find((prefix) => prefix.length > 0 && text.startsWith(prefix));
|
|
62897
|
+
}
|
|
62898
|
+
function normalizeSlashCommandName(command, botUsername) {
|
|
62899
|
+
const lowered = command?.toLowerCase() ?? "";
|
|
62900
|
+
const normalizedBotUsername = (botUsername ?? "").trim().toLowerCase().replace(/^@/, "");
|
|
62901
|
+
if (!normalizedBotUsername) {
|
|
62902
|
+
return lowered;
|
|
62903
|
+
}
|
|
62904
|
+
const suffix = `@${normalizedBotUsername}`;
|
|
62905
|
+
if (!lowered.endsWith(suffix)) {
|
|
62906
|
+
return lowered;
|
|
62907
|
+
}
|
|
62908
|
+
return lowered.slice(0, lowered.length - suffix.length);
|
|
62909
|
+
}
|
|
62910
|
+
function renderAgentControlSlashHelp() {
|
|
62911
|
+
return [
|
|
62912
|
+
"Slash commands",
|
|
62913
|
+
"",
|
|
62914
|
+
"- `/start`: show onboarding help for the current surface",
|
|
62915
|
+
"- `/status`: show the current route status and operator setup commands",
|
|
62916
|
+
"- `/help`: show available control slash commands",
|
|
62917
|
+
"- `/whoami`: show the current platform, route, and sender identity details",
|
|
62918
|
+
"- `/transcript`: show the current conversation session transcript when the route verbose policy allows it",
|
|
62919
|
+
"- `/attach`: attach this thread to the active run and resume live updates when it is still processing",
|
|
62920
|
+
"- `/detach`: stop live updates for this thread, switch to sparse progress updates, and still allow final settlement here",
|
|
62921
|
+
"- `/watch every 30s [for 10m]`: post the latest state on an interval until the run settles or the watch window ends",
|
|
62922
|
+
"- `/stop`: send Escape to interrupt the current conversation session",
|
|
62923
|
+
"- `/nudge`: send one extra Enter to the current tmux session without resending the prompt text",
|
|
62924
|
+
"- `/followup status`: show the current conversation follow-up policy",
|
|
62925
|
+
"- `/followup auto`: allow natural follow-up after the bot has replied in-thread",
|
|
62926
|
+
"- `/followup mention-only`: require explicit mention for each later turn",
|
|
62927
|
+
"- `/followup pause`: stop passive follow-up until the next explicit mention",
|
|
62928
|
+
"- `/followup resume`: clear the runtime override and restore config defaults",
|
|
62929
|
+
"- `/streaming status|on|off|latest|all`: show or change streaming mode for this surface",
|
|
62930
|
+
"- `/responsemode status`: show the configured response mode for this surface",
|
|
62931
|
+
"- `/responsemode capture-pane`: settle replies from captured pane output for this surface",
|
|
62932
|
+
"- `/responsemode message-tool`: expect the agent to reply through `clisbot message send` for this surface",
|
|
62933
|
+
"- `/additionalmessagemode status`: show how extra messages behave while a run is already active",
|
|
62934
|
+
"- `/additionalmessagemode steer`: send later user messages straight into the active session",
|
|
62935
|
+
"- `/additionalmessagemode queue`: queue later user messages behind the active run for this surface",
|
|
62936
|
+
"- `/queue <message>` or `\\q <message>`: enqueue a later message behind the active run and let clisbot deliver it in order",
|
|
62937
|
+
"- `/queue help`: show queue-specific help and examples",
|
|
62938
|
+
"- `/steer <message>` or `\\s <message>`: inject a steering message into the active run immediately",
|
|
62939
|
+
"- `/queue list`: show queued messages that have not started yet",
|
|
62940
|
+
"- `/queue clear`: clear queued messages that have not started yet",
|
|
62941
|
+
"- `/loop help`: show loop-specific help and syntax examples",
|
|
62942
|
+
...renderLoopHelpLines(),
|
|
62943
|
+
"- `/bash` followed by a shell command: requires `shellExecute` on the resolved agent role",
|
|
62944
|
+
"- shortcut prefixes such as `!` run bash only when the resolved agent role allows `shellExecute`",
|
|
62945
|
+
"",
|
|
62946
|
+
"Other slash commands are forwarded to the agent unchanged."
|
|
62947
|
+
].join(`
|
|
62948
|
+
`);
|
|
62949
|
+
}
|
|
62950
|
+
function renderQueueHelpLines() {
|
|
62951
|
+
return [
|
|
62952
|
+
"- `/queue <message>` or `\\q <message>`: enqueue one later message behind the active run",
|
|
62953
|
+
"- `/queue list`: show queued messages that have not started yet",
|
|
62954
|
+
"- `/queue clear`: clear queued messages that have not started yet",
|
|
62955
|
+
"- `/queue help`: show this queue help again",
|
|
62956
|
+
"- `/steer <message>` or `\\s <message>`: inject an immediate steering message instead of queueing"
|
|
62957
|
+
];
|
|
62958
|
+
}
|
|
62959
|
+
function parseWatchCommand(raw) {
|
|
62960
|
+
const match = raw.match(/^every\s+(\S+)(?:\s+for\s+(\S+))?$/i);
|
|
62961
|
+
if (!match) {
|
|
62962
|
+
return null;
|
|
62963
|
+
}
|
|
62964
|
+
const intervalMs = parseCommandDurationMs(match[1] ?? "");
|
|
62965
|
+
if (!intervalMs) {
|
|
62966
|
+
return null;
|
|
62967
|
+
}
|
|
62968
|
+
const durationToken = match[2];
|
|
62969
|
+
const parsedDurationMs = durationToken ? parseCommandDurationMs(durationToken) : null;
|
|
62970
|
+
if (durationToken && !parsedDurationMs) {
|
|
62971
|
+
return null;
|
|
62972
|
+
}
|
|
62973
|
+
return {
|
|
62974
|
+
intervalMs,
|
|
62975
|
+
durationMs: parsedDurationMs ?? undefined
|
|
62976
|
+
};
|
|
62977
|
+
}
|
|
62978
|
+
|
|
62546
62979
|
// src/agents/agent-service.ts
|
|
62547
62980
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
62548
62981
|
|
|
@@ -62604,6 +63037,7 @@ class SessionStore {
|
|
|
62604
63037
|
followUp: existing?.followUp,
|
|
62605
63038
|
runtime: existing?.runtime,
|
|
62606
63039
|
intervalLoops: existing?.intervalLoops,
|
|
63040
|
+
recentConversation: existing?.recentConversation,
|
|
62607
63041
|
updatedAt: Date.now()
|
|
62608
63042
|
});
|
|
62609
63043
|
}
|
|
@@ -62647,6 +63081,85 @@ class SessionStore {
|
|
|
62647
63081
|
}
|
|
62648
63082
|
}
|
|
62649
63083
|
|
|
63084
|
+
// src/shared/recent-message-context.ts
|
|
63085
|
+
var RECENT_CONVERSATION_MESSAGE_LIMIT = 5;
|
|
63086
|
+
function normalizeMessage(message) {
|
|
63087
|
+
return {
|
|
63088
|
+
marker: message.marker.trim(),
|
|
63089
|
+
text: message.text?.trim() || undefined,
|
|
63090
|
+
senderId: message.senderId?.trim() || undefined,
|
|
63091
|
+
senderName: message.senderName?.trim() || undefined
|
|
63092
|
+
};
|
|
63093
|
+
}
|
|
63094
|
+
function normalizeReplayLine(text) {
|
|
63095
|
+
return text.replace(/\s+/g, " ").trim();
|
|
63096
|
+
}
|
|
63097
|
+
function renderSenderLabel(message) {
|
|
63098
|
+
return message.senderName ?? message.senderId;
|
|
63099
|
+
}
|
|
63100
|
+
function appendRecentConversationMessage(state, message) {
|
|
63101
|
+
const normalized = normalizeMessage(message);
|
|
63102
|
+
if (!normalized.marker) {
|
|
63103
|
+
return state ?? { messages: [] };
|
|
63104
|
+
}
|
|
63105
|
+
const currentMessages = state?.messages ?? [];
|
|
63106
|
+
const nextMessages = [
|
|
63107
|
+
...currentMessages.filter((entry) => entry.marker !== normalized.marker),
|
|
63108
|
+
normalized
|
|
63109
|
+
].slice(-RECENT_CONVERSATION_MESSAGE_LIMIT);
|
|
63110
|
+
return {
|
|
63111
|
+
lastProcessedMarker: state?.lastProcessedMarker,
|
|
63112
|
+
messages: nextMessages
|
|
63113
|
+
};
|
|
63114
|
+
}
|
|
63115
|
+
function markRecentConversationProcessed(state, marker) {
|
|
63116
|
+
const normalizedMarker = marker.trim();
|
|
63117
|
+
return {
|
|
63118
|
+
lastProcessedMarker: normalizedMarker || state?.lastProcessedMarker,
|
|
63119
|
+
messages: state?.messages ?? []
|
|
63120
|
+
};
|
|
63121
|
+
}
|
|
63122
|
+
function collectRecentConversationReplayMessages(state, params = {}) {
|
|
63123
|
+
const messages = state?.messages ?? [];
|
|
63124
|
+
if (messages.length === 0) {
|
|
63125
|
+
return [];
|
|
63126
|
+
}
|
|
63127
|
+
const excludeMarker = params.excludeMarker?.trim();
|
|
63128
|
+
const pending = [];
|
|
63129
|
+
for (let index = messages.length - 1;index >= 0; index -= 1) {
|
|
63130
|
+
const message = messages[index];
|
|
63131
|
+
if (state?.lastProcessedMarker && message.marker === state.lastProcessedMarker) {
|
|
63132
|
+
break;
|
|
63133
|
+
}
|
|
63134
|
+
if (excludeMarker && message.marker === excludeMarker) {
|
|
63135
|
+
continue;
|
|
63136
|
+
}
|
|
63137
|
+
pending.push(message);
|
|
63138
|
+
}
|
|
63139
|
+
return pending.reverse();
|
|
63140
|
+
}
|
|
63141
|
+
function prependRecentConversationContext(params) {
|
|
63142
|
+
const replayLines = params.recentMessages.map((message) => {
|
|
63143
|
+
const text = normalizeReplayLine(message.text ?? "");
|
|
63144
|
+
if (!text) {
|
|
63145
|
+
return "";
|
|
63146
|
+
}
|
|
63147
|
+
const sender = renderSenderLabel(message);
|
|
63148
|
+
return sender ? `- ${sender}: ${text}` : `- ${text}`;
|
|
63149
|
+
}).filter(Boolean);
|
|
63150
|
+
if (replayLines.length === 0) {
|
|
63151
|
+
return params.currentText;
|
|
63152
|
+
}
|
|
63153
|
+
return [
|
|
63154
|
+
"Before answering, catch up on these newer messages from this conversation that were not processed yet:",
|
|
63155
|
+
...replayLines,
|
|
63156
|
+
"",
|
|
63157
|
+
"Current message:",
|
|
63158
|
+
params.currentText
|
|
63159
|
+
].join(`
|
|
63160
|
+
`);
|
|
63161
|
+
}
|
|
63162
|
+
|
|
62650
63163
|
// src/agents/session-state.ts
|
|
62651
63164
|
class AgentSessionState {
|
|
62652
63165
|
sessionStore;
|
|
@@ -62665,7 +63178,8 @@ class AgentSessionState {
|
|
|
62665
63178
|
followUp: existing?.followUp,
|
|
62666
63179
|
runnerCommand: params.runnerCommand ?? existing?.runnerCommand ?? resolved.runner.command,
|
|
62667
63180
|
runtime: params.runtime ?? existing?.runtime,
|
|
62668
|
-
intervalLoops: existing?.intervalLoops
|
|
63181
|
+
intervalLoops: existing?.intervalLoops,
|
|
63182
|
+
recentConversation: existing?.recentConversation
|
|
62669
63183
|
}));
|
|
62670
63184
|
}
|
|
62671
63185
|
async clearSessionIdEntry(resolved, params = {}) {
|
|
@@ -62676,7 +63190,8 @@ class AgentSessionState {
|
|
|
62676
63190
|
runtime: {
|
|
62677
63191
|
state: "idle"
|
|
62678
63192
|
},
|
|
62679
|
-
intervalLoops: existing?.intervalLoops
|
|
63193
|
+
intervalLoops: existing?.intervalLoops,
|
|
63194
|
+
recentConversation: existing?.recentConversation
|
|
62680
63195
|
}));
|
|
62681
63196
|
}
|
|
62682
63197
|
async setSessionRuntime(resolved, runtime) {
|
|
@@ -62685,7 +63200,8 @@ class AgentSessionState {
|
|
|
62685
63200
|
followUp: existing?.followUp,
|
|
62686
63201
|
runnerCommand: existing?.runnerCommand ?? resolved.runner.command,
|
|
62687
63202
|
runtime,
|
|
62688
|
-
intervalLoops: existing?.intervalLoops
|
|
63203
|
+
intervalLoops: existing?.intervalLoops,
|
|
63204
|
+
recentConversation: existing?.recentConversation
|
|
62689
63205
|
}));
|
|
62690
63206
|
}
|
|
62691
63207
|
async getConversationFollowUpState(target) {
|
|
@@ -62733,7 +63249,8 @@ class AgentSessionState {
|
|
|
62733
63249
|
followUp: existing?.followUp,
|
|
62734
63250
|
runnerCommand: existing?.runnerCommand ?? resolved.runner.command,
|
|
62735
63251
|
runtime: existing?.runtime,
|
|
62736
|
-
intervalLoops: [...(existing?.intervalLoops ?? []).filter((item) => item.id !== loop.id), loop]
|
|
63252
|
+
intervalLoops: [...(existing?.intervalLoops ?? []).filter((item) => item.id !== loop.id), loop],
|
|
63253
|
+
recentConversation: existing?.recentConversation
|
|
62737
63254
|
}));
|
|
62738
63255
|
}
|
|
62739
63256
|
async replaceIntervalLoopIfPresent(resolved, loop) {
|
|
@@ -62753,6 +63270,7 @@ class AgentSessionState {
|
|
|
62753
63270
|
followUp: existing?.followUp,
|
|
62754
63271
|
runtime: existing?.runtime,
|
|
62755
63272
|
intervalLoops: currentLoops.map((item) => item.id === loop.id ? loop : item),
|
|
63273
|
+
recentConversation: existing?.recentConversation,
|
|
62756
63274
|
updatedAt: Date.now()
|
|
62757
63275
|
};
|
|
62758
63276
|
});
|
|
@@ -62764,7 +63282,8 @@ class AgentSessionState {
|
|
|
62764
63282
|
followUp: existing?.followUp,
|
|
62765
63283
|
runnerCommand: existing?.runnerCommand ?? resolved.runner.command,
|
|
62766
63284
|
runtime: existing?.runtime,
|
|
62767
|
-
intervalLoops: (existing?.intervalLoops ?? []).filter((item) => item.id !== loopId)
|
|
63285
|
+
intervalLoops: (existing?.intervalLoops ?? []).filter((item) => item.id !== loopId),
|
|
63286
|
+
recentConversation: existing?.recentConversation
|
|
62768
63287
|
}));
|
|
62769
63288
|
}
|
|
62770
63289
|
async clearIntervalLoops(resolved) {
|
|
@@ -62773,7 +63292,8 @@ class AgentSessionState {
|
|
|
62773
63292
|
followUp: existing?.followUp,
|
|
62774
63293
|
runnerCommand: existing?.runnerCommand ?? resolved.runner.command,
|
|
62775
63294
|
runtime: existing?.runtime,
|
|
62776
|
-
intervalLoops: []
|
|
63295
|
+
intervalLoops: [],
|
|
63296
|
+
recentConversation: existing?.recentConversation
|
|
62777
63297
|
}));
|
|
62778
63298
|
}
|
|
62779
63299
|
async removeIntervalLoopById(loopId) {
|
|
@@ -62826,7 +63346,8 @@ class AgentSessionState {
|
|
|
62826
63346
|
overrideMode: mode
|
|
62827
63347
|
},
|
|
62828
63348
|
runnerCommand: existing?.runnerCommand ?? resolved.runner.command,
|
|
62829
|
-
intervalLoops: existing?.intervalLoops
|
|
63349
|
+
intervalLoops: existing?.intervalLoops,
|
|
63350
|
+
recentConversation: existing?.recentConversation
|
|
62830
63351
|
}));
|
|
62831
63352
|
}
|
|
62832
63353
|
async resetConversationFollowUpMode(resolved) {
|
|
@@ -62837,7 +63358,8 @@ class AgentSessionState {
|
|
|
62837
63358
|
overrideMode: undefined
|
|
62838
63359
|
} : undefined,
|
|
62839
63360
|
runnerCommand: existing?.runnerCommand ?? resolved.runner.command,
|
|
62840
|
-
intervalLoops: existing?.intervalLoops
|
|
63361
|
+
intervalLoops: existing?.intervalLoops,
|
|
63362
|
+
recentConversation: existing?.recentConversation
|
|
62841
63363
|
}));
|
|
62842
63364
|
}
|
|
62843
63365
|
async reactivateConversationFollowUp(resolved) {
|
|
@@ -62868,7 +63390,32 @@ class AgentSessionState {
|
|
|
62868
63390
|
} : {}
|
|
62869
63391
|
} : {}
|
|
62870
63392
|
} : existing?.runtime,
|
|
62871
|
-
intervalLoops: existing?.intervalLoops
|
|
63393
|
+
intervalLoops: existing?.intervalLoops,
|
|
63394
|
+
recentConversation: existing?.recentConversation
|
|
63395
|
+
}));
|
|
63396
|
+
}
|
|
63397
|
+
async appendRecentConversationMessage(resolved, message) {
|
|
63398
|
+
return this.upsertSessionEntry(resolved, (existing) => ({
|
|
63399
|
+
sessionId: existing?.sessionId,
|
|
63400
|
+
followUp: existing?.followUp,
|
|
63401
|
+
runnerCommand: existing?.runnerCommand ?? resolved.runner.command,
|
|
63402
|
+
runtime: existing?.runtime,
|
|
63403
|
+
intervalLoops: existing?.intervalLoops,
|
|
63404
|
+
recentConversation: appendRecentConversationMessage(existing?.recentConversation, message)
|
|
63405
|
+
}));
|
|
63406
|
+
}
|
|
63407
|
+
async getRecentConversationReplayMessages(target, params = {}) {
|
|
63408
|
+
const entry = await this.sessionStore.get(target.sessionKey);
|
|
63409
|
+
return collectRecentConversationReplayMessages(entry?.recentConversation, params);
|
|
63410
|
+
}
|
|
63411
|
+
async markRecentConversationProcessed(resolved, marker) {
|
|
63412
|
+
return this.upsertSessionEntry(resolved, (existing) => ({
|
|
63413
|
+
sessionId: existing?.sessionId,
|
|
63414
|
+
followUp: existing?.followUp,
|
|
63415
|
+
runnerCommand: existing?.runnerCommand ?? resolved.runner.command,
|
|
63416
|
+
runtime: existing?.runtime,
|
|
63417
|
+
intervalLoops: existing?.intervalLoops,
|
|
63418
|
+
recentConversation: markRecentConversationProcessed(existing?.recentConversation, marker)
|
|
62872
63419
|
}));
|
|
62873
63420
|
}
|
|
62874
63421
|
async upsertSessionEntry(resolved, update) {
|
|
@@ -62883,6 +63430,7 @@ class AgentSessionState {
|
|
|
62883
63430
|
followUp: next.followUp,
|
|
62884
63431
|
runtime: next.runtime ?? existing?.runtime,
|
|
62885
63432
|
intervalLoops: next.intervalLoops ?? existing?.intervalLoops,
|
|
63433
|
+
recentConversation: next.recentConversation ?? existing?.recentConversation,
|
|
62886
63434
|
updatedAt: Date.now()
|
|
62887
63435
|
};
|
|
62888
63436
|
});
|
|
@@ -62901,6 +63449,7 @@ You are operating inside clisbot.
|
|
|
62901
63449
|
{{delivery_intro}}
|
|
62902
63450
|
{{reply_command}}
|
|
62903
63451
|
{{reply_rules}}
|
|
63452
|
+
{{reply_style_hint}}
|
|
62904
63453
|
${CONFIGURATION_GUIDANCE}{{protected_control_suffix}}
|
|
62905
63454
|
</system>
|
|
62906
63455
|
|
|
@@ -62943,14 +63492,21 @@ var FINAL_RULE_REQUIRED = "send exactly 1 final user-facing response";
|
|
|
62943
63492
|
var FINAL_RULE_OPTIONAL = "final response is optional";
|
|
62944
63493
|
var EMPTY_REPLY_COMMAND = "";
|
|
62945
63494
|
var EMPTY_REPLY_RULES = "";
|
|
63495
|
+
var EMPTY_REPLY_STYLE_HINT = "";
|
|
62946
63496
|
var SLACK_REPLY_COMMAND_BASE = `{{command}} message send \\
|
|
62947
63497
|
--channel slack \\
|
|
62948
63498
|
{{account_clause}} --target channel:{{channel_id}} \\
|
|
62949
|
-
{{thread_clause}}
|
|
63499
|
+
{{thread_clause}} --input md \\
|
|
63500
|
+
--render blocks \\
|
|
63501
|
+
`;
|
|
62950
63502
|
var TELEGRAM_REPLY_COMMAND_BASE = `{{command}} message send \\
|
|
62951
63503
|
--channel telegram \\
|
|
62952
63504
|
{{account_clause}} --target {{chat_id}} \\
|
|
62953
|
-
{{thread_clause}}
|
|
63505
|
+
{{thread_clause}} --input md \\
|
|
63506
|
+
--render native \\
|
|
63507
|
+
`;
|
|
63508
|
+
var SLACK_REPLY_STYLE_HINT = "Put readable hierarchical Markdown in the --message body.";
|
|
63509
|
+
var TELEGRAM_REPLY_STYLE_HINT = "Put readable hierarchical Markdown in the --message body.";
|
|
62954
63510
|
var ACCOUNT_CLAUSE = " --account {{account_id}} \\\n";
|
|
62955
63511
|
var EMPTY_ACCOUNT_CLAUSE = "";
|
|
62956
63512
|
var SLACK_THREAD_CLAUSE = " --thread-id {{thread_ts}} \\\n";
|
|
@@ -62991,6 +63547,7 @@ function buildChannelPromptText(params) {
|
|
|
62991
63547
|
delivery_intro: promptParts.deliveryIntro,
|
|
62992
63548
|
reply_command: promptParts.replyCommand,
|
|
62993
63549
|
reply_rules: promptParts.replyRules,
|
|
63550
|
+
reply_style_hint: promptParts.replyStyleHint,
|
|
62994
63551
|
protected_control_suffix: renderProtectedControlSuffix(params.protectedControlMutationRule),
|
|
62995
63552
|
message_body: params.text
|
|
62996
63553
|
});
|
|
@@ -63001,7 +63558,8 @@ function renderMessagePromptParts(params) {
|
|
|
63001
63558
|
return {
|
|
63002
63559
|
deliveryIntro: DELIVERY_INTRO_CAPTURE_PANE,
|
|
63003
63560
|
replyCommand: EMPTY_REPLY_COMMAND,
|
|
63004
|
-
replyRules: EMPTY_REPLY_RULES
|
|
63561
|
+
replyRules: EMPTY_REPLY_RULES,
|
|
63562
|
+
replyStyleHint: EMPTY_REPLY_STYLE_HINT
|
|
63005
63563
|
};
|
|
63006
63564
|
}
|
|
63007
63565
|
const allowProgress = (params.streaming ?? "off") === "off";
|
|
@@ -63025,9 +63583,13 @@ function renderMessagePromptParts(params) {
|
|
|
63025
63583
|
max_progress_messages: String(params.config.maxProgressMessages)
|
|
63026
63584
|
}),
|
|
63027
63585
|
final_rule_line: finalRuleLine
|
|
63028
|
-
})
|
|
63586
|
+
}),
|
|
63587
|
+
replyStyleHint: buildReplyStyleHint(params.identity)
|
|
63029
63588
|
};
|
|
63030
63589
|
}
|
|
63590
|
+
function buildReplyStyleHint(identity) {
|
|
63591
|
+
return identity.platform === "slack" ? SLACK_REPLY_STYLE_HINT : TELEGRAM_REPLY_STYLE_HINT;
|
|
63592
|
+
}
|
|
63031
63593
|
function renderProtectedControlSuffix(rule) {
|
|
63032
63594
|
if (!rule) {
|
|
63033
63595
|
return "";
|
|
@@ -64027,6 +64589,17 @@ function extractScrolledAppend(previous, current) {
|
|
|
64027
64589
|
}
|
|
64028
64590
|
return "";
|
|
64029
64591
|
}
|
|
64592
|
+
function deriveRunningInteractionText(previousSnapshot, currentSnapshot) {
|
|
64593
|
+
const previous = cleanRunningInteractionSnapshot(previousSnapshot);
|
|
64594
|
+
const current = cleanRunningInteractionSnapshot(currentSnapshot);
|
|
64595
|
+
if (!current || current === previous) {
|
|
64596
|
+
return "";
|
|
64597
|
+
}
|
|
64598
|
+
if (!previous) {
|
|
64599
|
+
return current;
|
|
64600
|
+
}
|
|
64601
|
+
return extractScrolledAppend(previous, current);
|
|
64602
|
+
}
|
|
64030
64603
|
function deriveRunningInteractionSnapshot(currentSnapshot) {
|
|
64031
64604
|
return cleanRunningInteractionSnapshot(currentSnapshot);
|
|
64032
64605
|
}
|
|
@@ -64382,6 +64955,8 @@ var PASTE_SETTLE_POLL_INTERVAL_MS = 40;
|
|
|
64382
64955
|
var PASTE_SETTLE_QUIET_WINDOW_MS = 60;
|
|
64383
64956
|
var PASTE_SETTLE_MULTILINE_MAX_WAIT_MS = 800;
|
|
64384
64957
|
var PASTE_SETTLE_SINGLE_LINE_MAX_WAIT_MS = 80;
|
|
64958
|
+
var PASTE_CAPTURE_REVALIDATE_POLL_INTERVAL_MS = 40;
|
|
64959
|
+
var PASTE_CAPTURE_REVALIDATE_MAX_WAIT_MS = 160;
|
|
64385
64960
|
var SUBMIT_CONFIRM_POLL_INTERVAL_MS = 40;
|
|
64386
64961
|
var SUBMIT_CONFIRM_MAX_WAIT_MS = 160;
|
|
64387
64962
|
var TMUX_MISSING_TARGET_PATTERN = /(?:no current target|can't find pane|can't find window)/i;
|
|
@@ -64398,14 +64973,35 @@ class TmuxBootstrapSessionLostError extends Error {
|
|
|
64398
64973
|
}
|
|
64399
64974
|
async function submitTmuxSessionInput(params) {
|
|
64400
64975
|
const prePasteState = await params.tmux.getPaneState(params.sessionName);
|
|
64976
|
+
const captureLines = estimatePasteCaptureLines(params.text);
|
|
64977
|
+
const prePasteSnapshot = normalizePaneText(await params.tmux.capturePane(params.sessionName, captureLines));
|
|
64401
64978
|
await params.tmux.sendLiteral(params.sessionName, params.text);
|
|
64402
|
-
const
|
|
64979
|
+
const pasteSettlement = await waitForPanePasteSettlement({
|
|
64403
64980
|
tmux: params.tmux,
|
|
64404
64981
|
sessionName: params.sessionName,
|
|
64405
64982
|
baseline: prePasteState,
|
|
64406
64983
|
text: params.text,
|
|
64407
64984
|
minDelayMs: params.promptSubmitDelayMs
|
|
64408
64985
|
});
|
|
64986
|
+
let preSubmitState = pasteSettlement.state;
|
|
64987
|
+
if (!pasteSettlement.visible) {
|
|
64988
|
+
logLatencyDebug("tmux-paste-retry", params.timingContext, {
|
|
64989
|
+
sessionName: params.sessionName
|
|
64990
|
+
});
|
|
64991
|
+
const snapshotConfirmed = await waitForPanePasteSnapshotConfirmation({
|
|
64992
|
+
tmux: params.tmux,
|
|
64993
|
+
sessionName: params.sessionName,
|
|
64994
|
+
baselineSnapshot: prePasteSnapshot,
|
|
64995
|
+
captureLines
|
|
64996
|
+
});
|
|
64997
|
+
if (!snapshotConfirmed) {
|
|
64998
|
+
logLatencyDebug("tmux-paste-unconfirmed", params.timingContext, {
|
|
64999
|
+
sessionName: params.sessionName
|
|
65000
|
+
});
|
|
65001
|
+
throw new Error("tmux paste was not confirmed before Enter. The pane state did not change, so clisbot did not treat the prompt as visibly delivered.");
|
|
65002
|
+
}
|
|
65003
|
+
preSubmitState = await params.tmux.getPaneState(params.sessionName);
|
|
65004
|
+
}
|
|
64409
65005
|
await params.tmux.sendKey(params.sessionName, "Enter");
|
|
64410
65006
|
if (await waitForPaneSubmitConfirmation({
|
|
64411
65007
|
tmux: params.tmux,
|
|
@@ -64609,11 +65205,17 @@ async function waitForPanePasteSettlement(params) {
|
|
|
64609
65205
|
const deadline = Date.now() + (shouldWaitForVisiblePaste(params.text) ? PASTE_SETTLE_MULTILINE_MAX_WAIT_MS : PASTE_SETTLE_SINGLE_LINE_MAX_WAIT_MS);
|
|
64610
65206
|
while (true) {
|
|
64611
65207
|
if (sawChange && Date.now() - lastChangeAt >= PASTE_SETTLE_QUIET_WINDOW_MS) {
|
|
64612
|
-
return
|
|
65208
|
+
return {
|
|
65209
|
+
visible: true,
|
|
65210
|
+
state: currentState
|
|
65211
|
+
};
|
|
64613
65212
|
}
|
|
64614
65213
|
const remainingMs = deadline - Date.now();
|
|
64615
65214
|
if (remainingMs <= 0) {
|
|
64616
|
-
return
|
|
65215
|
+
return {
|
|
65216
|
+
visible: sawChange,
|
|
65217
|
+
state: currentState
|
|
65218
|
+
};
|
|
64617
65219
|
}
|
|
64618
65220
|
await sleep(Math.min(PASTE_SETTLE_POLL_INTERVAL_MS, remainingMs));
|
|
64619
65221
|
const nextState = await params.tmux.getPaneState(params.sessionName);
|
|
@@ -64626,6 +65228,24 @@ async function waitForPanePasteSettlement(params) {
|
|
|
64626
65228
|
}
|
|
64627
65229
|
}
|
|
64628
65230
|
}
|
|
65231
|
+
async function waitForPanePasteSnapshotConfirmation(params) {
|
|
65232
|
+
const deadline = Date.now() + PASTE_CAPTURE_REVALIDATE_MAX_WAIT_MS;
|
|
65233
|
+
while (true) {
|
|
65234
|
+
const snapshot = normalizePaneText(await params.tmux.capturePane(params.sessionName, params.captureLines));
|
|
65235
|
+
if (snapshot !== params.baselineSnapshot) {
|
|
65236
|
+
return true;
|
|
65237
|
+
}
|
|
65238
|
+
const remainingMs = deadline - Date.now();
|
|
65239
|
+
if (remainingMs <= 0) {
|
|
65240
|
+
return false;
|
|
65241
|
+
}
|
|
65242
|
+
await sleep(Math.min(PASTE_CAPTURE_REVALIDATE_POLL_INTERVAL_MS, remainingMs));
|
|
65243
|
+
}
|
|
65244
|
+
}
|
|
65245
|
+
function estimatePasteCaptureLines(text) {
|
|
65246
|
+
return Math.max(40, Math.min(160, text.split(`
|
|
65247
|
+
`).length + 24));
|
|
65248
|
+
}
|
|
64629
65249
|
function hasPaneStateChanged(left, right) {
|
|
64630
65250
|
return left.cursorX !== right.cursorX || left.cursorY !== right.cursorY || left.historySize !== right.historySize;
|
|
64631
65251
|
}
|
|
@@ -64914,6 +65534,12 @@ class RunnerService {
|
|
|
64914
65534
|
nextAllowFreshRetry: false
|
|
64915
65535
|
});
|
|
64916
65536
|
}
|
|
65537
|
+
async retryAfterStartupTimeout(target, resolved, allowFreshRetry) {
|
|
65538
|
+
return this.retryFreshStartWithClearedSessionId(target, resolved, {
|
|
65539
|
+
allowRetry: allowFreshRetry,
|
|
65540
|
+
nextAllowFreshRetry: false
|
|
65541
|
+
});
|
|
65542
|
+
}
|
|
64917
65543
|
async abortUnreadySession(resolved, reason, snapshot) {
|
|
64918
65544
|
await this.tmux.killSession(resolved.sessionName);
|
|
64919
65545
|
throw new Error(`${reason}${summarizeSnapshot(snapshot)}`);
|
|
@@ -65058,6 +65684,10 @@ class RunnerService {
|
|
|
65058
65684
|
await this.abortUnreadySession(resolved, bootstrapResult.message, bootstrapResult.snapshot);
|
|
65059
65685
|
}
|
|
65060
65686
|
if (bootstrapResult.status === "timeout" && resolved.runner.startupReadyPattern) {
|
|
65687
|
+
const retried = await this.retryAfterStartupTimeout(target, resolved, options.allowFreshRetry);
|
|
65688
|
+
if (retried) {
|
|
65689
|
+
return retried;
|
|
65690
|
+
}
|
|
65061
65691
|
await this.abortUnreadySession(resolved, `Runner session "${resolved.sessionName}" did not reach the configured ready state within ${resolved.runner.startupDelayMs}ms.`, bootstrapResult.snapshot);
|
|
65062
65692
|
}
|
|
65063
65693
|
await this.finalizeSessionStartup(resolved, {
|
|
@@ -65322,7 +65952,7 @@ async function monitorTmuxRun(params) {
|
|
|
65322
65952
|
await sleep(sawActivity ? params.updateIntervalMs : Math.min(params.updateIntervalMs, FIRST_OUTPUT_POLL_INTERVAL_MS));
|
|
65323
65953
|
const snapshot = normalizePaneText(await params.tmux.capturePane(params.sessionName, params.captureLines));
|
|
65324
65954
|
const now = Date.now();
|
|
65325
|
-
const runningSnapshot = deriveRunningInteractionSnapshot(snapshot);
|
|
65955
|
+
const runningSnapshot = params.initialSnapshot ? deriveRunningInteractionText(params.initialSnapshot, snapshot) : deriveRunningInteractionSnapshot(snapshot);
|
|
65326
65956
|
previousSnapshot = snapshot;
|
|
65327
65957
|
if (runningSnapshot && runningSnapshot !== previousRunningSnapshot) {
|
|
65328
65958
|
previousRunningSnapshot = runningSnapshot;
|
|
@@ -66057,6 +66687,15 @@ class AgentService {
|
|
|
66057
66687
|
async recordConversationReply(target, kind = "reply", source = "channel") {
|
|
66058
66688
|
return this.sessionState.recordConversationReply(this.resolveTarget(target), kind, source);
|
|
66059
66689
|
}
|
|
66690
|
+
async appendRecentConversationMessage(target, message) {
|
|
66691
|
+
return this.sessionState.appendRecentConversationMessage(this.resolveTarget(target), message);
|
|
66692
|
+
}
|
|
66693
|
+
async getRecentConversationReplayMessages(target, params = {}) {
|
|
66694
|
+
return this.sessionState.getRecentConversationReplayMessages(target, params);
|
|
66695
|
+
}
|
|
66696
|
+
async markRecentConversationProcessed(target, marker) {
|
|
66697
|
+
return this.sessionState.markRecentConversationProcessed(this.resolveTarget(target), marker);
|
|
66698
|
+
}
|
|
66060
66699
|
async runShellCommand(target, command) {
|
|
66061
66700
|
return this.queue.enqueue(`${target.sessionKey}:bash`, async () => this.runnerSessions.runShellCommand(target, command)).result;
|
|
66062
66701
|
}
|
|
@@ -66566,421 +67205,6 @@ class AgentService {
|
|
|
66566
67205
|
}
|
|
66567
67206
|
}
|
|
66568
67207
|
|
|
66569
|
-
// src/agents/commands.ts
|
|
66570
|
-
function parseAgentCommand(text, options = {}) {
|
|
66571
|
-
const normalized = text.trim();
|
|
66572
|
-
const commandPrefixes = options.commandPrefixes ?? {
|
|
66573
|
-
slash: ["::", "\\"],
|
|
66574
|
-
bash: ["!"]
|
|
66575
|
-
};
|
|
66576
|
-
const bashPrefix = findMatchingPrefix(normalized, commandPrefixes.bash);
|
|
66577
|
-
if (bashPrefix) {
|
|
66578
|
-
const command2 = normalized.slice(bashPrefix.length).trim();
|
|
66579
|
-
return {
|
|
66580
|
-
type: "bash",
|
|
66581
|
-
command: command2,
|
|
66582
|
-
source: "shortcut"
|
|
66583
|
-
};
|
|
66584
|
-
}
|
|
66585
|
-
const slashPrefix = findMatchingPrefix(normalized, ["/", ...commandPrefixes.slash]);
|
|
66586
|
-
if (!slashPrefix) {
|
|
66587
|
-
return null;
|
|
66588
|
-
}
|
|
66589
|
-
const withoutSlash = normalized.slice(slashPrefix.length).trim();
|
|
66590
|
-
if (!withoutSlash) {
|
|
66591
|
-
return {
|
|
66592
|
-
type: "control",
|
|
66593
|
-
name: "help"
|
|
66594
|
-
};
|
|
66595
|
-
}
|
|
66596
|
-
const [command] = withoutSlash.split(/\s+/, 1);
|
|
66597
|
-
const lowered = normalizeSlashCommandName(command, options.botUsername);
|
|
66598
|
-
if (lowered === "start") {
|
|
66599
|
-
return {
|
|
66600
|
-
type: "control",
|
|
66601
|
-
name: "start"
|
|
66602
|
-
};
|
|
66603
|
-
}
|
|
66604
|
-
if (lowered === "status") {
|
|
66605
|
-
return {
|
|
66606
|
-
type: "control",
|
|
66607
|
-
name: "status"
|
|
66608
|
-
};
|
|
66609
|
-
}
|
|
66610
|
-
if (lowered === "help") {
|
|
66611
|
-
return {
|
|
66612
|
-
type: "control",
|
|
66613
|
-
name: "help"
|
|
66614
|
-
};
|
|
66615
|
-
}
|
|
66616
|
-
if (lowered === "whoami") {
|
|
66617
|
-
return {
|
|
66618
|
-
type: "control",
|
|
66619
|
-
name: "whoami"
|
|
66620
|
-
};
|
|
66621
|
-
}
|
|
66622
|
-
if (lowered === "transcript") {
|
|
66623
|
-
return {
|
|
66624
|
-
type: "control",
|
|
66625
|
-
name: "transcript"
|
|
66626
|
-
};
|
|
66627
|
-
}
|
|
66628
|
-
if (lowered === "attach") {
|
|
66629
|
-
return {
|
|
66630
|
-
type: "control",
|
|
66631
|
-
name: "attach"
|
|
66632
|
-
};
|
|
66633
|
-
}
|
|
66634
|
-
if (lowered === "detach") {
|
|
66635
|
-
return {
|
|
66636
|
-
type: "control",
|
|
66637
|
-
name: "detach"
|
|
66638
|
-
};
|
|
66639
|
-
}
|
|
66640
|
-
if (lowered === "watch") {
|
|
66641
|
-
const parsed = parseWatchCommand(withoutSlash.slice(command.length).trim());
|
|
66642
|
-
if (parsed) {
|
|
66643
|
-
return {
|
|
66644
|
-
type: "control",
|
|
66645
|
-
name: "watch",
|
|
66646
|
-
intervalMs: parsed.intervalMs,
|
|
66647
|
-
durationMs: parsed.durationMs
|
|
66648
|
-
};
|
|
66649
|
-
}
|
|
66650
|
-
return {
|
|
66651
|
-
type: "control",
|
|
66652
|
-
name: "help"
|
|
66653
|
-
};
|
|
66654
|
-
}
|
|
66655
|
-
if (lowered === "stop") {
|
|
66656
|
-
return {
|
|
66657
|
-
type: "control",
|
|
66658
|
-
name: "stop"
|
|
66659
|
-
};
|
|
66660
|
-
}
|
|
66661
|
-
if (lowered === "nudge") {
|
|
66662
|
-
return {
|
|
66663
|
-
type: "control",
|
|
66664
|
-
name: "nudge"
|
|
66665
|
-
};
|
|
66666
|
-
}
|
|
66667
|
-
if (lowered === "followup") {
|
|
66668
|
-
const action = withoutSlash.slice(command.length).trim().toLowerCase();
|
|
66669
|
-
if (!action || action === "status") {
|
|
66670
|
-
return {
|
|
66671
|
-
type: "control",
|
|
66672
|
-
name: "followup",
|
|
66673
|
-
action: "status"
|
|
66674
|
-
};
|
|
66675
|
-
}
|
|
66676
|
-
if (action === "auto") {
|
|
66677
|
-
return {
|
|
66678
|
-
type: "control",
|
|
66679
|
-
name: "followup",
|
|
66680
|
-
action: "auto",
|
|
66681
|
-
mode: "auto"
|
|
66682
|
-
};
|
|
66683
|
-
}
|
|
66684
|
-
if (action === "mention-only") {
|
|
66685
|
-
return {
|
|
66686
|
-
type: "control",
|
|
66687
|
-
name: "followup",
|
|
66688
|
-
action: "mention-only",
|
|
66689
|
-
mode: "mention-only"
|
|
66690
|
-
};
|
|
66691
|
-
}
|
|
66692
|
-
if (action === "pause") {
|
|
66693
|
-
return {
|
|
66694
|
-
type: "control",
|
|
66695
|
-
name: "followup",
|
|
66696
|
-
action: "pause",
|
|
66697
|
-
mode: "paused"
|
|
66698
|
-
};
|
|
66699
|
-
}
|
|
66700
|
-
if (action === "resume") {
|
|
66701
|
-
return {
|
|
66702
|
-
type: "control",
|
|
66703
|
-
name: "followup",
|
|
66704
|
-
action: "resume"
|
|
66705
|
-
};
|
|
66706
|
-
}
|
|
66707
|
-
return {
|
|
66708
|
-
type: "control",
|
|
66709
|
-
name: "followup",
|
|
66710
|
-
action: "status"
|
|
66711
|
-
};
|
|
66712
|
-
}
|
|
66713
|
-
if (lowered === "responsemode") {
|
|
66714
|
-
const action = withoutSlash.slice(command.length).trim().toLowerCase();
|
|
66715
|
-
if (!action || action === "status") {
|
|
66716
|
-
return {
|
|
66717
|
-
type: "control",
|
|
66718
|
-
name: "responsemode",
|
|
66719
|
-
action: "status"
|
|
66720
|
-
};
|
|
66721
|
-
}
|
|
66722
|
-
if (action === "capture-pane" || action === "message-tool") {
|
|
66723
|
-
return {
|
|
66724
|
-
type: "control",
|
|
66725
|
-
name: "responsemode",
|
|
66726
|
-
action,
|
|
66727
|
-
responseMode: action
|
|
66728
|
-
};
|
|
66729
|
-
}
|
|
66730
|
-
return {
|
|
66731
|
-
type: "control",
|
|
66732
|
-
name: "responsemode",
|
|
66733
|
-
action: "status"
|
|
66734
|
-
};
|
|
66735
|
-
}
|
|
66736
|
-
if (lowered === "streaming") {
|
|
66737
|
-
const action = withoutSlash.slice(command.length).trim().toLowerCase();
|
|
66738
|
-
if (!action || action === "status") {
|
|
66739
|
-
return {
|
|
66740
|
-
type: "control",
|
|
66741
|
-
name: "streaming",
|
|
66742
|
-
action: "status"
|
|
66743
|
-
};
|
|
66744
|
-
}
|
|
66745
|
-
if (action === "on") {
|
|
66746
|
-
return {
|
|
66747
|
-
type: "control",
|
|
66748
|
-
name: "streaming",
|
|
66749
|
-
action: "on",
|
|
66750
|
-
streaming: "all"
|
|
66751
|
-
};
|
|
66752
|
-
}
|
|
66753
|
-
if (action === "off" || action === "latest" || action === "all") {
|
|
66754
|
-
return {
|
|
66755
|
-
type: "control",
|
|
66756
|
-
name: "streaming",
|
|
66757
|
-
action,
|
|
66758
|
-
streaming: action
|
|
66759
|
-
};
|
|
66760
|
-
}
|
|
66761
|
-
return {
|
|
66762
|
-
type: "control",
|
|
66763
|
-
name: "streaming",
|
|
66764
|
-
action: "status"
|
|
66765
|
-
};
|
|
66766
|
-
}
|
|
66767
|
-
if (lowered === "additionalmessagemode") {
|
|
66768
|
-
const action = withoutSlash.slice(command.length).trim().toLowerCase();
|
|
66769
|
-
if (!action || action === "status") {
|
|
66770
|
-
return {
|
|
66771
|
-
type: "control",
|
|
66772
|
-
name: "additionalmessagemode",
|
|
66773
|
-
action: "status"
|
|
66774
|
-
};
|
|
66775
|
-
}
|
|
66776
|
-
if (action === "queue" || action === "steer") {
|
|
66777
|
-
return {
|
|
66778
|
-
type: "control",
|
|
66779
|
-
name: "additionalmessagemode",
|
|
66780
|
-
action,
|
|
66781
|
-
additionalMessageMode: action
|
|
66782
|
-
};
|
|
66783
|
-
}
|
|
66784
|
-
return {
|
|
66785
|
-
type: "control",
|
|
66786
|
-
name: "additionalmessagemode",
|
|
66787
|
-
action: "status"
|
|
66788
|
-
};
|
|
66789
|
-
}
|
|
66790
|
-
if (lowered === "bash") {
|
|
66791
|
-
return {
|
|
66792
|
-
type: "bash",
|
|
66793
|
-
command: withoutSlash.slice(command.length).trim(),
|
|
66794
|
-
source: "slash"
|
|
66795
|
-
};
|
|
66796
|
-
}
|
|
66797
|
-
if (lowered === "loop") {
|
|
66798
|
-
const loopText = withoutSlash.slice(command.length).trim();
|
|
66799
|
-
const loweredLoopText = loopText.toLowerCase();
|
|
66800
|
-
if (!loweredLoopText || loweredLoopText === "help") {
|
|
66801
|
-
return {
|
|
66802
|
-
type: "control",
|
|
66803
|
-
name: "loop-help"
|
|
66804
|
-
};
|
|
66805
|
-
}
|
|
66806
|
-
if (loweredLoopText === "status") {
|
|
66807
|
-
return {
|
|
66808
|
-
type: "loop-control",
|
|
66809
|
-
action: "status"
|
|
66810
|
-
};
|
|
66811
|
-
}
|
|
66812
|
-
if (loweredLoopText === "cancel" || loweredLoopText.startsWith("cancel ")) {
|
|
66813
|
-
const cancelArgs = loopText.slice("cancel".length).trim();
|
|
66814
|
-
if (hasLoopFlag(cancelArgs, LOOP_FORCE_FLAG)) {
|
|
66815
|
-
return {
|
|
66816
|
-
type: "loop-error",
|
|
66817
|
-
message: `Use \`/loop cancel --all ${LOOP_APP_FLAG}\` for app-wide cancellation.`
|
|
66818
|
-
};
|
|
66819
|
-
}
|
|
66820
|
-
const all = hasLoopFlag(cancelArgs, LOOP_ALL_FLAG);
|
|
66821
|
-
const app = hasLoopFlag(cancelArgs, LOOP_APP_FLAG);
|
|
66822
|
-
if (app && !all) {
|
|
66823
|
-
return {
|
|
66824
|
-
type: "loop-error",
|
|
66825
|
-
message: `\`${LOOP_APP_FLAG}\` only works with \`/loop cancel ${LOOP_ALL_FLAG}\`.`
|
|
66826
|
-
};
|
|
66827
|
-
}
|
|
66828
|
-
const loopId = cancelArgs.split(/\s+/).map((token) => token.trim()).find((token) => token && token !== LOOP_ALL_FLAG && token !== LOOP_APP_FLAG);
|
|
66829
|
-
return {
|
|
66830
|
-
type: "loop-control",
|
|
66831
|
-
action: "cancel",
|
|
66832
|
-
loopId: loopId || undefined,
|
|
66833
|
-
all,
|
|
66834
|
-
app
|
|
66835
|
-
};
|
|
66836
|
-
}
|
|
66837
|
-
const parsed = parseLoopSlashCommand(loopText);
|
|
66838
|
-
if ("error" in parsed) {
|
|
66839
|
-
return {
|
|
66840
|
-
type: "loop-error",
|
|
66841
|
-
message: parsed.error
|
|
66842
|
-
};
|
|
66843
|
-
}
|
|
66844
|
-
return {
|
|
66845
|
-
type: "loop",
|
|
66846
|
-
params: parsed
|
|
66847
|
-
};
|
|
66848
|
-
}
|
|
66849
|
-
if (lowered === "queue" || lowered === "q") {
|
|
66850
|
-
const queueText = withoutSlash.slice(command.length).trim();
|
|
66851
|
-
const normalizedQueueText = queueText.toLowerCase();
|
|
66852
|
-
if (lowered === "queue") {
|
|
66853
|
-
if (normalizedQueueText === "help") {
|
|
66854
|
-
return {
|
|
66855
|
-
type: "control",
|
|
66856
|
-
name: "queue-help"
|
|
66857
|
-
};
|
|
66858
|
-
}
|
|
66859
|
-
if (normalizedQueueText === "list") {
|
|
66860
|
-
return {
|
|
66861
|
-
type: "control",
|
|
66862
|
-
name: "queue-list"
|
|
66863
|
-
};
|
|
66864
|
-
}
|
|
66865
|
-
if (normalizedQueueText === "clear") {
|
|
66866
|
-
return {
|
|
66867
|
-
type: "control",
|
|
66868
|
-
name: "queue-clear"
|
|
66869
|
-
};
|
|
66870
|
-
}
|
|
66871
|
-
}
|
|
66872
|
-
return {
|
|
66873
|
-
type: "queue",
|
|
66874
|
-
text: queueText
|
|
66875
|
-
};
|
|
66876
|
-
}
|
|
66877
|
-
if (lowered === "queue-list" || lowered === "queuelist") {
|
|
66878
|
-
return {
|
|
66879
|
-
type: "control",
|
|
66880
|
-
name: "queue-list"
|
|
66881
|
-
};
|
|
66882
|
-
}
|
|
66883
|
-
if (lowered === "queue-clear" || lowered === "queueclear") {
|
|
66884
|
-
return {
|
|
66885
|
-
type: "control",
|
|
66886
|
-
name: "queue-clear"
|
|
66887
|
-
};
|
|
66888
|
-
}
|
|
66889
|
-
if (lowered === "steer" || lowered === "s") {
|
|
66890
|
-
return {
|
|
66891
|
-
type: "steer",
|
|
66892
|
-
text: withoutSlash.slice(command.length).trim()
|
|
66893
|
-
};
|
|
66894
|
-
}
|
|
66895
|
-
return {
|
|
66896
|
-
type: "native",
|
|
66897
|
-
text: normalized
|
|
66898
|
-
};
|
|
66899
|
-
}
|
|
66900
|
-
function findMatchingPrefix(text, prefixes) {
|
|
66901
|
-
return [...prefixes].sort((left, right) => right.length - left.length).find((prefix) => prefix.length > 0 && text.startsWith(prefix));
|
|
66902
|
-
}
|
|
66903
|
-
function normalizeSlashCommandName(command, botUsername) {
|
|
66904
|
-
const lowered = command?.toLowerCase() ?? "";
|
|
66905
|
-
const normalizedBotUsername = (botUsername ?? "").trim().toLowerCase().replace(/^@/, "");
|
|
66906
|
-
if (!normalizedBotUsername) {
|
|
66907
|
-
return lowered;
|
|
66908
|
-
}
|
|
66909
|
-
const suffix = `@${normalizedBotUsername}`;
|
|
66910
|
-
if (!lowered.endsWith(suffix)) {
|
|
66911
|
-
return lowered;
|
|
66912
|
-
}
|
|
66913
|
-
return lowered.slice(0, lowered.length - suffix.length);
|
|
66914
|
-
}
|
|
66915
|
-
function renderAgentControlSlashHelp() {
|
|
66916
|
-
return [
|
|
66917
|
-
"Slash commands",
|
|
66918
|
-
"",
|
|
66919
|
-
"- `/start`: show onboarding help for the current surface",
|
|
66920
|
-
"- `/status`: show the current route status and operator setup commands",
|
|
66921
|
-
"- `/help`: show available control slash commands",
|
|
66922
|
-
"- `/whoami`: show the current platform, route, and sender identity details",
|
|
66923
|
-
"- `/transcript`: show the current conversation session transcript when the route verbose policy allows it",
|
|
66924
|
-
"- `/attach`: attach this thread to the active run and resume live updates when it is still processing",
|
|
66925
|
-
"- `/detach`: stop live updates for this thread, switch to sparse progress updates, and still allow final settlement here",
|
|
66926
|
-
"- `/watch every 30s [for 10m]`: post the latest state on an interval until the run settles or the watch window ends",
|
|
66927
|
-
"- `/stop`: send Escape to interrupt the current conversation session",
|
|
66928
|
-
"- `/nudge`: send one extra Enter to the current tmux session without resending the prompt text",
|
|
66929
|
-
"- `/followup status`: show the current conversation follow-up policy",
|
|
66930
|
-
"- `/followup auto`: allow natural follow-up after the bot has replied in-thread",
|
|
66931
|
-
"- `/followup mention-only`: require explicit mention for each later turn",
|
|
66932
|
-
"- `/followup pause`: stop passive follow-up until the next explicit mention",
|
|
66933
|
-
"- `/followup resume`: clear the runtime override and restore config defaults",
|
|
66934
|
-
"- `/streaming status|on|off|latest|all`: show or change streaming mode for this surface",
|
|
66935
|
-
"- `/responsemode status`: show the configured response mode for this surface",
|
|
66936
|
-
"- `/responsemode capture-pane`: settle replies from captured pane output for this surface",
|
|
66937
|
-
"- `/responsemode message-tool`: expect the agent to reply through `clisbot message send` for this surface",
|
|
66938
|
-
"- `/additionalmessagemode status`: show how extra messages behave while a run is already active",
|
|
66939
|
-
"- `/additionalmessagemode steer`: send later user messages straight into the active session",
|
|
66940
|
-
"- `/additionalmessagemode queue`: queue later user messages behind the active run for this surface",
|
|
66941
|
-
"- `/queue <message>` or `\\q <message>`: enqueue a later message behind the active run and let clisbot deliver it in order",
|
|
66942
|
-
"- `/queue help`: show queue-specific help and examples",
|
|
66943
|
-
"- `/steer <message>` or `\\s <message>`: inject a steering message into the active run immediately",
|
|
66944
|
-
"- `/queue list`: show queued messages that have not started yet",
|
|
66945
|
-
"- `/queue clear`: clear queued messages that have not started yet",
|
|
66946
|
-
"- `/loop help`: show loop-specific help and syntax examples",
|
|
66947
|
-
...renderLoopHelpLines(),
|
|
66948
|
-
"- `/bash` followed by a shell command: requires `shellExecute` on the resolved agent role",
|
|
66949
|
-
"- shortcut prefixes such as `!` run bash only when the resolved agent role allows `shellExecute`",
|
|
66950
|
-
"",
|
|
66951
|
-
"Other slash commands are forwarded to the agent unchanged."
|
|
66952
|
-
].join(`
|
|
66953
|
-
`);
|
|
66954
|
-
}
|
|
66955
|
-
function renderQueueHelpLines() {
|
|
66956
|
-
return [
|
|
66957
|
-
"- `/queue <message>` or `\\q <message>`: enqueue one later message behind the active run",
|
|
66958
|
-
"- `/queue list`: show queued messages that have not started yet",
|
|
66959
|
-
"- `/queue clear`: clear queued messages that have not started yet",
|
|
66960
|
-
"- `/queue help`: show this queue help again",
|
|
66961
|
-
"- `/steer <message>` or `\\s <message>`: inject an immediate steering message instead of queueing"
|
|
66962
|
-
];
|
|
66963
|
-
}
|
|
66964
|
-
function parseWatchCommand(raw) {
|
|
66965
|
-
const match = raw.match(/^every\s+(\S+)(?:\s+for\s+(\S+))?$/i);
|
|
66966
|
-
if (!match) {
|
|
66967
|
-
return null;
|
|
66968
|
-
}
|
|
66969
|
-
const intervalMs = parseCommandDurationMs(match[1] ?? "");
|
|
66970
|
-
if (!intervalMs) {
|
|
66971
|
-
return null;
|
|
66972
|
-
}
|
|
66973
|
-
const durationToken = match[2];
|
|
66974
|
-
const parsedDurationMs = durationToken ? parseCommandDurationMs(durationToken) : null;
|
|
66975
|
-
if (durationToken && !parsedDurationMs) {
|
|
66976
|
-
return null;
|
|
66977
|
-
}
|
|
66978
|
-
return {
|
|
66979
|
-
intervalMs,
|
|
66980
|
-
durationMs: parsedDurationMs ?? undefined
|
|
66981
|
-
};
|
|
66982
|
-
}
|
|
66983
|
-
|
|
66984
67208
|
// src/channels/rendering.ts
|
|
66985
67209
|
function buildRenderedMessageState(params) {
|
|
66986
67210
|
const body = params.snapshot.trim() || (params.status === "completed" || params.status === "timeout" || params.status === "detached" ? params.previousState?.body ?? "" : "");
|
|
@@ -67706,6 +67930,9 @@ async function executePromptDelivery(params) {
|
|
|
67706
67930
|
}
|
|
67707
67931
|
});
|
|
67708
67932
|
queueStartPending = positionAhead > 0 && (params.queueStartMode ?? "none") !== "none";
|
|
67933
|
+
if (params.onPromptAccepted) {
|
|
67934
|
+
await params.onPromptAccepted();
|
|
67935
|
+
}
|
|
67709
67936
|
if (previewEnabled) {
|
|
67710
67937
|
const placeholderText = buildInitialPlaceholderText(positionAhead);
|
|
67711
67938
|
const postedNew2 = await renderResponseText(placeholderText);
|
|
@@ -68375,9 +68602,12 @@ ${escapeCodeFence(shellResult.output)}
|
|
|
68375
68602
|
return interactionResult;
|
|
68376
68603
|
}
|
|
68377
68604
|
await params.agentService.submitSessionInput(params.sessionTarget, buildSteeringPromptText({
|
|
68378
|
-
text: explicitSteerMessage,
|
|
68605
|
+
text: params.transformSessionInputText?.(explicitSteerMessage) ?? explicitSteerMessage,
|
|
68379
68606
|
protectedControlMutationRule: params.protectedControlMutationRule
|
|
68380
68607
|
}));
|
|
68608
|
+
if (params.onPromptAccepted) {
|
|
68609
|
+
await params.onPromptAccepted();
|
|
68610
|
+
}
|
|
68381
68611
|
await params.postText("Steered.");
|
|
68382
68612
|
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
68383
68613
|
return {
|
|
@@ -68387,9 +68617,12 @@ ${escapeCodeFence(shellResult.output)}
|
|
|
68387
68617
|
if (!forceQueuedDelivery && params.route.additionalMessageMode === "steer") {
|
|
68388
68618
|
if (sessionBusy && canSteerActiveRun) {
|
|
68389
68619
|
await params.agentService.submitSessionInput(params.sessionTarget, buildSteeringPromptText({
|
|
68390
|
-
text: params.text,
|
|
68620
|
+
text: params.transformSessionInputText?.(params.text) ?? params.text,
|
|
68391
68621
|
protectedControlMutationRule: params.protectedControlMutationRule
|
|
68392
68622
|
}));
|
|
68623
|
+
if (params.onPromptAccepted) {
|
|
68624
|
+
await params.onPromptAccepted();
|
|
68625
|
+
}
|
|
68393
68626
|
return {
|
|
68394
68627
|
processingIndicatorLifecycle: "active-run"
|
|
68395
68628
|
};
|
|
@@ -68408,7 +68641,8 @@ ${escapeCodeFence(shellResult.output)}
|
|
|
68408
68641
|
reconcileText: params.reconcileText,
|
|
68409
68642
|
observerId,
|
|
68410
68643
|
timingContext: params.timingContext,
|
|
68411
|
-
forceQueuedDelivery
|
|
68644
|
+
forceQueuedDelivery,
|
|
68645
|
+
onPromptAccepted: params.onPromptAccepted
|
|
68412
68646
|
});
|
|
68413
68647
|
return interactionResult;
|
|
68414
68648
|
}
|
|
@@ -68463,6 +68697,16 @@ function isTelegramSenderAllowed(params) {
|
|
|
68463
68697
|
return false;
|
|
68464
68698
|
}
|
|
68465
68699
|
|
|
68700
|
+
// src/channels/mention-follow-up.ts
|
|
68701
|
+
function buildMentionOnlyFollowUpPrompt(params) {
|
|
68702
|
+
const scope = params.threaded ? "this thread" : params.conversationKind === "dm" ? "this conversation" : "the recent conversation here";
|
|
68703
|
+
return [
|
|
68704
|
+
"The user explicitly mentioned you without any additional text.",
|
|
68705
|
+
`Review the recent context in ${scope} and respond to the latest unresolved request.`,
|
|
68706
|
+
"If the next step is still unclear, ask one short clarifying question."
|
|
68707
|
+
].join(" ");
|
|
68708
|
+
}
|
|
68709
|
+
|
|
68466
68710
|
// src/auth/resolve.ts
|
|
68467
68711
|
function mergeRoleDefinitions(inherited, override) {
|
|
68468
68712
|
return {
|
|
@@ -69219,13 +69463,21 @@ function canUseImplicitSlackFollowUp(params) {
|
|
|
69219
69463
|
return params.conversationKind !== "dm" && typeof params.event.thread_ts === "string" && params.event.thread_ts.length > 0;
|
|
69220
69464
|
}
|
|
69221
69465
|
function hasBotMention(text, botUserId) {
|
|
69222
|
-
|
|
69466
|
+
const mentions = extractSlackMentionedUserIds(text);
|
|
69467
|
+
if (botUserId) {
|
|
69468
|
+
return mentions.includes(botUserId);
|
|
69469
|
+
}
|
|
69470
|
+
return mentions.length > 0;
|
|
69471
|
+
}
|
|
69472
|
+
function hasForeignSlackUserMention(text, botUserId) {
|
|
69473
|
+
const mentions = extractSlackMentionedUserIds(text);
|
|
69474
|
+
if (mentions.length === 0) {
|
|
69223
69475
|
return false;
|
|
69224
69476
|
}
|
|
69225
|
-
if (botUserId) {
|
|
69226
|
-
return
|
|
69477
|
+
if (!botUserId) {
|
|
69478
|
+
return true;
|
|
69227
69479
|
}
|
|
69228
|
-
return
|
|
69480
|
+
return !mentions.includes(botUserId);
|
|
69229
69481
|
}
|
|
69230
69482
|
function stripBotMention(text, botUserId) {
|
|
69231
69483
|
if (!botUserId) {
|
|
@@ -69241,6 +69493,12 @@ function resolveSlackDirectReplyThreadTs(params) {
|
|
|
69241
69493
|
const messageTs = (params.messageTs ?? "").trim();
|
|
69242
69494
|
return messageTs || undefined;
|
|
69243
69495
|
}
|
|
69496
|
+
function extractSlackMentionedUserIds(text) {
|
|
69497
|
+
if (!text) {
|
|
69498
|
+
return [];
|
|
69499
|
+
}
|
|
69500
|
+
return [...text.matchAll(/<@([A-Z0-9_]+)(?:\|[^>]+)?>/gi)].map((match) => match[1]?.trim()).filter((value) => Boolean(value));
|
|
69501
|
+
}
|
|
69244
69502
|
|
|
69245
69503
|
// src/channels/slack/reactions.ts
|
|
69246
69504
|
function normalizeSlackReactionName(value) {
|
|
@@ -69343,7 +69601,7 @@ function isSlackCommandLikeMessage(params) {
|
|
|
69343
69601
|
}) !== null;
|
|
69344
69602
|
}
|
|
69345
69603
|
function renderSlackRouteChoiceMessage(params) {
|
|
69346
|
-
const
|
|
69604
|
+
const botReference = params.botLabel?.trim() ? `mention this bot (${params.botLabel.trim()})` : "mention this bot";
|
|
69347
69605
|
return [
|
|
69348
69606
|
"clisbot: this Slack channel is not configured yet.",
|
|
69349
69607
|
"",
|
|
@@ -69351,19 +69609,34 @@ function renderSlackRouteChoiceMessage(params) {
|
|
|
69351
69609
|
`- \`clisbot channels add slack-channel ${params.channelId}\``,
|
|
69352
69610
|
`- \`clisbot channels add slack-channel ${params.channelId} --agent <id>\``,
|
|
69353
69611
|
"",
|
|
69354
|
-
`After that,
|
|
69612
|
+
`After that, ${botReference} and send \`\\start\` or \`\\status\` here.`
|
|
69355
69613
|
].join(`
|
|
69356
69614
|
`);
|
|
69357
69615
|
}
|
|
69358
69616
|
function renderSlackMentionRequiredMessage(botLabel) {
|
|
69359
|
-
const
|
|
69617
|
+
const botReference = botLabel?.trim() ? `mention this bot (${botLabel.trim()})` : "mention this bot";
|
|
69360
69618
|
return [
|
|
69361
69619
|
"clisbot: this Slack channel requires a bot mention for new commands.",
|
|
69362
|
-
`Try
|
|
69620
|
+
`Try ${botReference} and send \`\\start\` or \`\\status\` here.`,
|
|
69363
69621
|
"After the bot replies in a thread, normal follow-up messages there can continue according to the follow-up policy."
|
|
69364
69622
|
].join(`
|
|
69365
69623
|
`);
|
|
69366
69624
|
}
|
|
69625
|
+
function shouldSendSlackMentionRequiredGuidance(params) {
|
|
69626
|
+
return params.conversationKind === "dm" && params.isCommandLike;
|
|
69627
|
+
}
|
|
69628
|
+
function shouldGuideUnroutedSlackEvent(params) {
|
|
69629
|
+
if (params.isBotOriginated) {
|
|
69630
|
+
return false;
|
|
69631
|
+
}
|
|
69632
|
+
if (!params.isCommandLike) {
|
|
69633
|
+
return false;
|
|
69634
|
+
}
|
|
69635
|
+
if (params.conversationKind === "dm") {
|
|
69636
|
+
return true;
|
|
69637
|
+
}
|
|
69638
|
+
return params.wasMentioned;
|
|
69639
|
+
}
|
|
69367
69640
|
async function sendSlackGuidanceOnce(params) {
|
|
69368
69641
|
if (!params.eventId) {
|
|
69369
69642
|
await params.send();
|
|
@@ -70100,6 +70373,14 @@ class SlackSocketService {
|
|
|
70100
70373
|
conversationKind: params.conversationKind,
|
|
70101
70374
|
replyToMode: params.route.replyToMode
|
|
70102
70375
|
});
|
|
70376
|
+
if (hasForeignSlackUserMention(event.text ?? "", this.botUserId)) {
|
|
70377
|
+
debugSlackEvent("drop-foreign-mention", {
|
|
70378
|
+
eventId,
|
|
70379
|
+
channelId
|
|
70380
|
+
});
|
|
70381
|
+
await this.processedEventsStore.markCompleted(eventId);
|
|
70382
|
+
return;
|
|
70383
|
+
}
|
|
70103
70384
|
const explicitMention = params.wasMentioned || hasBotMention(event.text ?? "", this.botUserId);
|
|
70104
70385
|
const followUpState = await this.agentService.getConversationFollowUpState(sessionTarget);
|
|
70105
70386
|
const effectiveFollowUpMode = resolveFollowUpMode({
|
|
@@ -70115,12 +70396,27 @@ class SlackSocketService {
|
|
|
70115
70396
|
lastBotReplyAt: followUpState.lastBotReplyAt,
|
|
70116
70397
|
directReplyToBot: isImplicitBotThreadReply(event, this.botUserId)
|
|
70117
70398
|
});
|
|
70399
|
+
const rawText = explicitMention ? stripBotMention(event.text ?? "", this.botUserId) : `${event.text ?? ""}`.trim();
|
|
70400
|
+
const recentMessageMarker = messageTs?.trim();
|
|
70401
|
+
if (recentMessageMarker && (rawText || explicitMention)) {
|
|
70402
|
+
await this.agentService.appendRecentConversationMessage(sessionTarget, {
|
|
70403
|
+
marker: recentMessageMarker,
|
|
70404
|
+
text: parseAgentCommand(rawText, {
|
|
70405
|
+
commandPrefixes: params.route.commandPrefixes
|
|
70406
|
+
}) ? "" : rawText,
|
|
70407
|
+
senderId: typeof event.user === "string" ? event.user.trim().toUpperCase() : undefined
|
|
70408
|
+
});
|
|
70409
|
+
}
|
|
70118
70410
|
if (requiresMention && !wasMentioned) {
|
|
70119
|
-
|
|
70411
|
+
const isCommandLike = isSlackCommandLikeMessage({
|
|
70120
70412
|
text: event.text ?? "",
|
|
70121
70413
|
botUserId: this.botUserId,
|
|
70122
70414
|
botUsername: this.botLabel,
|
|
70123
70415
|
commandPrefixes: params.route.commandPrefixes
|
|
70416
|
+
});
|
|
70417
|
+
if (shouldSendSlackMentionRequiredGuidance({
|
|
70418
|
+
conversationKind: params.conversationKind,
|
|
70419
|
+
isCommandLike
|
|
70124
70420
|
})) {
|
|
70125
70421
|
try {
|
|
70126
70422
|
await postSlackText(this.app.client, {
|
|
@@ -70137,6 +70433,7 @@ class SlackSocketService {
|
|
|
70137
70433
|
channelId,
|
|
70138
70434
|
requiresMention,
|
|
70139
70435
|
explicitMention,
|
|
70436
|
+
isCommandLike,
|
|
70140
70437
|
effectiveFollowUpMode
|
|
70141
70438
|
});
|
|
70142
70439
|
await this.processedEventsStore.markCompleted(eventId);
|
|
@@ -70145,7 +70442,10 @@ class SlackSocketService {
|
|
|
70145
70442
|
if (explicitMention && followUpState.overrideMode === "paused") {
|
|
70146
70443
|
await this.agentService.reactivateConversationFollowUp(sessionTarget);
|
|
70147
70444
|
}
|
|
70148
|
-
const
|
|
70445
|
+
const effectivePromptText = rawText || (explicitMention ? buildMentionOnlyFollowUpPrompt({
|
|
70446
|
+
conversationKind: params.conversationKind,
|
|
70447
|
+
threaded: Boolean(threadTs)
|
|
70448
|
+
}) : "");
|
|
70149
70449
|
const attachmentPaths = await resolveSlackAttachmentPaths({
|
|
70150
70450
|
client: this.app.client,
|
|
70151
70451
|
event,
|
|
@@ -70157,12 +70457,19 @@ class SlackSocketService {
|
|
|
70157
70457
|
sessionKey: sessionTarget.sessionKey,
|
|
70158
70458
|
messageId: messageTs ?? threadTs ?? `${Date.now()}`
|
|
70159
70459
|
});
|
|
70160
|
-
const text = prependAttachmentMentions(
|
|
70460
|
+
const text = prependAttachmentMentions(effectivePromptText, attachmentPaths);
|
|
70161
70461
|
if (!text) {
|
|
70162
70462
|
debugSlackEvent("drop-empty-text", { eventId, channelId });
|
|
70163
70463
|
await this.processedEventsStore.markCompleted(eventId);
|
|
70164
70464
|
return;
|
|
70165
70465
|
}
|
|
70466
|
+
const recentConversationReplay = recentMessageMarker ? await this.agentService.getRecentConversationReplayMessages(sessionTarget, {
|
|
70467
|
+
excludeMarker: recentMessageMarker
|
|
70468
|
+
}) : [];
|
|
70469
|
+
const enrichPromptText = (nextText) => prependRecentConversationContext({
|
|
70470
|
+
currentText: nextText,
|
|
70471
|
+
recentMessages: recentConversationReplay
|
|
70472
|
+
});
|
|
70166
70473
|
debugSlackEvent("process-message", {
|
|
70167
70474
|
eventId,
|
|
70168
70475
|
channelId,
|
|
@@ -70197,7 +70504,7 @@ class SlackSocketService {
|
|
|
70197
70504
|
});
|
|
70198
70505
|
const protectedControlMutationRule = auth.mayManageProtectedResources ? undefined : DEFAULT_PROTECTED_CONTROL_RULE;
|
|
70199
70506
|
const agentPromptText = buildAgentPromptText({
|
|
70200
|
-
text,
|
|
70507
|
+
text: enrichPromptText(text),
|
|
70201
70508
|
identity,
|
|
70202
70509
|
config: this.loadedConfig.raw.channels.slack.agentPrompt,
|
|
70203
70510
|
cliTool,
|
|
@@ -70250,7 +70557,7 @@ class SlackSocketService {
|
|
|
70250
70557
|
text,
|
|
70251
70558
|
agentPromptText,
|
|
70252
70559
|
agentPromptBuilder: (nextText) => buildAgentPromptText({
|
|
70253
|
-
text: nextText,
|
|
70560
|
+
text: enrichPromptText(nextText),
|
|
70254
70561
|
identity,
|
|
70255
70562
|
config: this.loadedConfig.raw.channels.slack.agentPrompt,
|
|
70256
70563
|
cliTool,
|
|
@@ -70259,6 +70566,10 @@ class SlackSocketService {
|
|
|
70259
70566
|
protectedControlMutationRule
|
|
70260
70567
|
}),
|
|
70261
70568
|
protectedControlMutationRule,
|
|
70569
|
+
transformSessionInputText: enrichPromptText,
|
|
70570
|
+
onPromptAccepted: recentMessageMarker ? async () => {
|
|
70571
|
+
await this.agentService.markRecentConversationProcessed(sessionTarget, recentMessageMarker);
|
|
70572
|
+
} : undefined,
|
|
70262
70573
|
route: params.route,
|
|
70263
70574
|
maxChars: this.getSlackMaxChars(params.route.agentId),
|
|
70264
70575
|
timingContext,
|
|
@@ -70323,6 +70634,13 @@ class SlackSocketService {
|
|
|
70323
70634
|
const resolvedRoute = resolveSlackConversationRoute(this.loadedConfig, normalizedEvent, { accountId: this.accountId });
|
|
70324
70635
|
const route = resolvedRoute.route;
|
|
70325
70636
|
if (!route) {
|
|
70637
|
+
if (isBotOriginatedSlackEvent(normalizedEvent) && !this.loadedConfig.raw.channels.slack.allowBots) {
|
|
70638
|
+
debugSlackEvent("drop-unrouted-bot-mention", {
|
|
70639
|
+
eventId: body.event_id,
|
|
70640
|
+
allowBots: this.loadedConfig.raw.channels.slack.allowBots
|
|
70641
|
+
});
|
|
70642
|
+
return;
|
|
70643
|
+
}
|
|
70326
70644
|
try {
|
|
70327
70645
|
await this.maybeGuideUnroutedSlackEvent({
|
|
70328
70646
|
eventId: body.event_id,
|
|
@@ -70358,11 +70676,16 @@ class SlackSocketService {
|
|
|
70358
70676
|
const resolvedRoute = resolveSlackConversationRoute(this.loadedConfig, normalizedEvent, { accountId: this.accountId });
|
|
70359
70677
|
const route = resolvedRoute.route;
|
|
70360
70678
|
if (!route) {
|
|
70361
|
-
const shouldGuide =
|
|
70362
|
-
|
|
70363
|
-
|
|
70364
|
-
|
|
70365
|
-
|
|
70679
|
+
const shouldGuide = shouldGuideUnroutedSlackEvent({
|
|
70680
|
+
conversationKind: resolvedRoute.conversationKind,
|
|
70681
|
+
isCommandLike: isSlackCommandLikeMessage({
|
|
70682
|
+
text: normalizedEvent.text ?? "",
|
|
70683
|
+
botUserId: this.botUserId,
|
|
70684
|
+
botUsername: this.botLabel,
|
|
70685
|
+
commandPrefixes: this.loadedConfig.raw.channels.slack.commandPrefixes
|
|
70686
|
+
}),
|
|
70687
|
+
wasMentioned: hasBotMention(normalizedEvent.text ?? "", this.botUserId),
|
|
70688
|
+
isBotOriginated: isBotOriginatedSlackEvent(normalizedEvent) && !this.loadedConfig.raw.channels.slack.allowBots
|
|
70366
70689
|
});
|
|
70367
70690
|
if (shouldGuide) {
|
|
70368
70691
|
try {
|
|
@@ -70433,6 +70756,399 @@ class SlackSocketService {
|
|
|
70433
70756
|
var import_bolt = __toESM(require_dist7(), 1);
|
|
70434
70757
|
import { basename } from "node:path";
|
|
70435
70758
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
70759
|
+
|
|
70760
|
+
// src/channels/slack/content.ts
|
|
70761
|
+
var SLACK_MAX_BLOCKS = 50;
|
|
70762
|
+
function normalizeMarkdownLinks(text) {
|
|
70763
|
+
return text.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_match, label, href) => {
|
|
70764
|
+
const trimmedHref = href.trim();
|
|
70765
|
+
if (!trimmedHref) {
|
|
70766
|
+
return label;
|
|
70767
|
+
}
|
|
70768
|
+
return `<${trimmedHref}|${label}>`;
|
|
70769
|
+
});
|
|
70770
|
+
}
|
|
70771
|
+
function renderInlineMarkdownToSlackMrkdwn(text) {
|
|
70772
|
+
return normalizeMarkdownLinks(text).replace(/~~([^~]+)~~/g, "~$1~").replace(/\*\*([^*\n][\s\S]*?[^*\n])\*\*/g, "*$1*");
|
|
70773
|
+
}
|
|
70774
|
+
function stripMarkdownInline(text) {
|
|
70775
|
+
return text.replace(/`([^`\n]+)`/g, "$1").replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, "$1").replace(/\*\*([^*\n][\s\S]*?[^*\n])\*\*/g, "$1").replace(/\*([^*\n][\s\S]*?[^*\n])\*/g, "$1").replace(/~~([^~]+)~~/g, "$1").trim();
|
|
70776
|
+
}
|
|
70777
|
+
function normalizeSlackHeaderText(text) {
|
|
70778
|
+
const normalized = stripMarkdownInline(text);
|
|
70779
|
+
if (!normalized) {
|
|
70780
|
+
return "Untitled";
|
|
70781
|
+
}
|
|
70782
|
+
return normalized.slice(0, 150);
|
|
70783
|
+
}
|
|
70784
|
+
function buildSlackBlocksFallbackText(blocks) {
|
|
70785
|
+
for (const block of blocks) {
|
|
70786
|
+
if (typeof block !== "object" || !block) {
|
|
70787
|
+
continue;
|
|
70788
|
+
}
|
|
70789
|
+
const text = block.text?.text;
|
|
70790
|
+
if (typeof text === "string" && text.trim()) {
|
|
70791
|
+
return stripMarkdownInline(text).replace(/\s+/g, " ").trim();
|
|
70792
|
+
}
|
|
70793
|
+
const elements = block.elements;
|
|
70794
|
+
if (!Array.isArray(elements)) {
|
|
70795
|
+
continue;
|
|
70796
|
+
}
|
|
70797
|
+
for (const element of elements) {
|
|
70798
|
+
if (typeof element?.text === "string" && element.text.trim()) {
|
|
70799
|
+
return stripMarkdownInline(element.text).replace(/\s+/g, " ").trim();
|
|
70800
|
+
}
|
|
70801
|
+
}
|
|
70802
|
+
const rows = block.rows;
|
|
70803
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
70804
|
+
continue;
|
|
70805
|
+
}
|
|
70806
|
+
const firstRow = rows[0];
|
|
70807
|
+
if (!Array.isArray(firstRow)) {
|
|
70808
|
+
continue;
|
|
70809
|
+
}
|
|
70810
|
+
const rowText = firstRow.map((cell) => {
|
|
70811
|
+
if (!cell || typeof cell !== "object") {
|
|
70812
|
+
return "";
|
|
70813
|
+
}
|
|
70814
|
+
const rawText = cell.text;
|
|
70815
|
+
return typeof rawText === "string" ? stripMarkdownInline(rawText) : "";
|
|
70816
|
+
}).filter(Boolean).join(" | ").trim();
|
|
70817
|
+
if (rowText) {
|
|
70818
|
+
return rowText;
|
|
70819
|
+
}
|
|
70820
|
+
}
|
|
70821
|
+
return "Shared a Block Kit message";
|
|
70822
|
+
}
|
|
70823
|
+
function validateSlackBlocksArray(raw) {
|
|
70824
|
+
if (!Array.isArray(raw)) {
|
|
70825
|
+
throw new Error("Slack blocks input must be a JSON array");
|
|
70826
|
+
}
|
|
70827
|
+
if (raw.length === 0) {
|
|
70828
|
+
throw new Error("Slack blocks input cannot be empty");
|
|
70829
|
+
}
|
|
70830
|
+
if (raw.length > SLACK_MAX_BLOCKS) {
|
|
70831
|
+
throw new Error(`Slack blocks cannot exceed ${SLACK_MAX_BLOCKS} items`);
|
|
70832
|
+
}
|
|
70833
|
+
for (const block of raw) {
|
|
70834
|
+
if (!block || typeof block !== "object" || Array.isArray(block)) {
|
|
70835
|
+
throw new Error("Each Slack block must be an object");
|
|
70836
|
+
}
|
|
70837
|
+
const type = block.type;
|
|
70838
|
+
if (typeof type !== "string" || !type.trim()) {
|
|
70839
|
+
throw new Error("Each Slack block must include a non-empty string type");
|
|
70840
|
+
}
|
|
70841
|
+
}
|
|
70842
|
+
return raw;
|
|
70843
|
+
}
|
|
70844
|
+
function parseSlackBlocksInput(text) {
|
|
70845
|
+
let parsed;
|
|
70846
|
+
try {
|
|
70847
|
+
parsed = JSON.parse(text);
|
|
70848
|
+
} catch {
|
|
70849
|
+
throw new Error("Slack blocks input must be valid JSON");
|
|
70850
|
+
}
|
|
70851
|
+
return validateSlackBlocksArray(parsed);
|
|
70852
|
+
}
|
|
70853
|
+
function renderMarkdownToSlackMrkdwn(markdown) {
|
|
70854
|
+
const normalized = markdown.replaceAll(`\r
|
|
70855
|
+
`, `
|
|
70856
|
+
`).replaceAll("\r", `
|
|
70857
|
+
`).trim();
|
|
70858
|
+
if (!normalized) {
|
|
70859
|
+
return "";
|
|
70860
|
+
}
|
|
70861
|
+
return normalized.split(`
|
|
70862
|
+
`).map((line) => {
|
|
70863
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
70864
|
+
if (headingMatch) {
|
|
70865
|
+
const level = headingMatch[1]?.length ?? 1;
|
|
70866
|
+
const content = renderInlineMarkdownToSlackMrkdwn(headingMatch[2] ?? "");
|
|
70867
|
+
if (level <= 3) {
|
|
70868
|
+
return `*${content}*`;
|
|
70869
|
+
}
|
|
70870
|
+
return content;
|
|
70871
|
+
}
|
|
70872
|
+
const bulletMatch = line.match(/^\s*[-*+]\s+(.+)$/);
|
|
70873
|
+
if (bulletMatch) {
|
|
70874
|
+
return `• ${renderInlineMarkdownToSlackMrkdwn(bulletMatch[1] ?? "")}`;
|
|
70875
|
+
}
|
|
70876
|
+
const orderedMatch = line.match(/^\s*(\d+)\.\s+(.+)$/);
|
|
70877
|
+
if (orderedMatch) {
|
|
70878
|
+
return `${orderedMatch[1]}. ${renderInlineMarkdownToSlackMrkdwn(orderedMatch[2] ?? "")}`;
|
|
70879
|
+
}
|
|
70880
|
+
return renderInlineMarkdownToSlackMrkdwn(line);
|
|
70881
|
+
}).join(`
|
|
70882
|
+
`);
|
|
70883
|
+
}
|
|
70884
|
+
function splitMarkdownTableCells(line) {
|
|
70885
|
+
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
70886
|
+
}
|
|
70887
|
+
function isMarkdownTableSeparator(line) {
|
|
70888
|
+
const cells = splitMarkdownTableCells(line);
|
|
70889
|
+
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
70890
|
+
}
|
|
70891
|
+
function isMarkdownTableLine(line) {
|
|
70892
|
+
const trimmed = line.trim();
|
|
70893
|
+
return trimmed.includes("|") && splitMarkdownTableCells(trimmed).length >= 2;
|
|
70894
|
+
}
|
|
70895
|
+
function renderSlackTableRow(headers, row) {
|
|
70896
|
+
if (headers.length === 2 && row.length >= 2) {
|
|
70897
|
+
return `*${renderInlineMarkdownToSlackMrkdwn(row[0] ?? "")}*: ${renderInlineMarkdownToSlackMrkdwn(row[1] ?? "")}`;
|
|
70898
|
+
}
|
|
70899
|
+
return headers.map((header, index) => {
|
|
70900
|
+
const value = row[index] ?? "";
|
|
70901
|
+
return `*${renderInlineMarkdownToSlackMrkdwn(header)}:* ${renderInlineMarkdownToSlackMrkdwn(value)}`;
|
|
70902
|
+
}).join(" • ");
|
|
70903
|
+
}
|
|
70904
|
+
function normalizeSlackTableCellText(text) {
|
|
70905
|
+
return stripMarkdownInline(text).slice(0, 3000);
|
|
70906
|
+
}
|
|
70907
|
+
function buildSlackTableCell(text) {
|
|
70908
|
+
return {
|
|
70909
|
+
type: "raw_text",
|
|
70910
|
+
text: normalizeSlackTableCellText(text)
|
|
70911
|
+
};
|
|
70912
|
+
}
|
|
70913
|
+
function renderMarkdownTableToNativeSlackBlock(headers, rows) {
|
|
70914
|
+
if (headers.length === 0 || rows.length === 0) {
|
|
70915
|
+
return null;
|
|
70916
|
+
}
|
|
70917
|
+
return {
|
|
70918
|
+
type: "table",
|
|
70919
|
+
column_settings: headers.map((_header, index) => ({
|
|
70920
|
+
is_wrapped: index === 0
|
|
70921
|
+
})),
|
|
70922
|
+
rows: [headers, ...rows].map((row) => row.map((cell) => buildSlackTableCell(cell)))
|
|
70923
|
+
};
|
|
70924
|
+
}
|
|
70925
|
+
function renderMarkdownTableToFallbackSlackBlock(headers, rows) {
|
|
70926
|
+
if (headers.length === 0 || rows.length === 0) {
|
|
70927
|
+
return null;
|
|
70928
|
+
}
|
|
70929
|
+
const text = rows.map((row) => renderSlackTableRow(headers, row)).filter(Boolean).join(`
|
|
70930
|
+
`);
|
|
70931
|
+
if (!text.trim()) {
|
|
70932
|
+
return null;
|
|
70933
|
+
}
|
|
70934
|
+
return {
|
|
70935
|
+
type: "section",
|
|
70936
|
+
text: {
|
|
70937
|
+
type: "mrkdwn",
|
|
70938
|
+
text
|
|
70939
|
+
}
|
|
70940
|
+
};
|
|
70941
|
+
}
|
|
70942
|
+
function renderMarkdownToSlackBlocks(markdown) {
|
|
70943
|
+
const normalized = markdown.replaceAll(`\r
|
|
70944
|
+
`, `
|
|
70945
|
+
`).replaceAll("\r", `
|
|
70946
|
+
`).trim();
|
|
70947
|
+
if (!normalized) {
|
|
70948
|
+
return [];
|
|
70949
|
+
}
|
|
70950
|
+
const blocks = [];
|
|
70951
|
+
const lines = normalized.split(`
|
|
70952
|
+
`);
|
|
70953
|
+
const firstHeadingLineIndex = lines.findIndex((line) => /^(#{1,6})\s+.+$/.test(line));
|
|
70954
|
+
const paragraph = [];
|
|
70955
|
+
const codeLines = [];
|
|
70956
|
+
let inCodeFence = false;
|
|
70957
|
+
let hasVisibleContent = false;
|
|
70958
|
+
let hasSeenHeading = false;
|
|
70959
|
+
let majorHeadingCount = 0;
|
|
70960
|
+
let hasNativeTableBlock = false;
|
|
70961
|
+
const pushBlock = (block) => {
|
|
70962
|
+
blocks.push(block);
|
|
70963
|
+
hasVisibleContent = true;
|
|
70964
|
+
};
|
|
70965
|
+
const pushDividerIfNeeded = () => {
|
|
70966
|
+
if (!hasVisibleContent) {
|
|
70967
|
+
return;
|
|
70968
|
+
}
|
|
70969
|
+
const lastBlock = blocks[blocks.length - 1];
|
|
70970
|
+
if (lastBlock?.type === "divider") {
|
|
70971
|
+
return;
|
|
70972
|
+
}
|
|
70973
|
+
blocks.push({ type: "divider" });
|
|
70974
|
+
};
|
|
70975
|
+
const flushParagraph = () => {
|
|
70976
|
+
if (paragraph.length === 0) {
|
|
70977
|
+
return;
|
|
70978
|
+
}
|
|
70979
|
+
const text = paragraph.join(`
|
|
70980
|
+
`).trim();
|
|
70981
|
+
if (text) {
|
|
70982
|
+
const shouldRenderAsPreamble = !hasSeenHeading && firstHeadingLineIndex > 0 && blocks.length === 0;
|
|
70983
|
+
pushBlock({
|
|
70984
|
+
...shouldRenderAsPreamble ? {
|
|
70985
|
+
type: "context",
|
|
70986
|
+
elements: [
|
|
70987
|
+
{
|
|
70988
|
+
type: "mrkdwn",
|
|
70989
|
+
text
|
|
70990
|
+
}
|
|
70991
|
+
]
|
|
70992
|
+
} : {
|
|
70993
|
+
type: "section",
|
|
70994
|
+
text: {
|
|
70995
|
+
type: "mrkdwn",
|
|
70996
|
+
text
|
|
70997
|
+
}
|
|
70998
|
+
}
|
|
70999
|
+
});
|
|
71000
|
+
}
|
|
71001
|
+
paragraph.length = 0;
|
|
71002
|
+
};
|
|
71003
|
+
const flushCodeFence = () => {
|
|
71004
|
+
if (codeLines.length === 0) {
|
|
71005
|
+
return;
|
|
71006
|
+
}
|
|
71007
|
+
pushBlock({
|
|
71008
|
+
type: "section",
|
|
71009
|
+
text: {
|
|
71010
|
+
type: "mrkdwn",
|
|
71011
|
+
text: `\`\`\`
|
|
71012
|
+
${codeLines.join(`
|
|
71013
|
+
`)}
|
|
71014
|
+
\`\`\``
|
|
71015
|
+
}
|
|
71016
|
+
});
|
|
71017
|
+
codeLines.length = 0;
|
|
71018
|
+
};
|
|
71019
|
+
for (let lineIndex = 0;lineIndex < lines.length; lineIndex += 1) {
|
|
71020
|
+
const line = lines[lineIndex] ?? "";
|
|
71021
|
+
const fenceMatch = line.match(/^```([^\n`]*)$/);
|
|
71022
|
+
if (fenceMatch) {
|
|
71023
|
+
if (inCodeFence) {
|
|
71024
|
+
flushCodeFence();
|
|
71025
|
+
inCodeFence = false;
|
|
71026
|
+
} else {
|
|
71027
|
+
flushParagraph();
|
|
71028
|
+
inCodeFence = true;
|
|
71029
|
+
}
|
|
71030
|
+
continue;
|
|
71031
|
+
}
|
|
71032
|
+
if (inCodeFence) {
|
|
71033
|
+
codeLines.push(line);
|
|
71034
|
+
continue;
|
|
71035
|
+
}
|
|
71036
|
+
if (line.trim().length === 0) {
|
|
71037
|
+
flushParagraph();
|
|
71038
|
+
continue;
|
|
71039
|
+
}
|
|
71040
|
+
const nextLine = lines[lineIndex + 1] ?? "";
|
|
71041
|
+
if (isMarkdownTableLine(line) && isMarkdownTableSeparator(nextLine)) {
|
|
71042
|
+
flushParagraph();
|
|
71043
|
+
const headers = splitMarkdownTableCells(line);
|
|
71044
|
+
const rows = [];
|
|
71045
|
+
lineIndex += 2;
|
|
71046
|
+
while (lineIndex < lines.length) {
|
|
71047
|
+
const tableLine = lines[lineIndex] ?? "";
|
|
71048
|
+
if (!tableLine.trim() || !isMarkdownTableLine(tableLine)) {
|
|
71049
|
+
lineIndex -= 1;
|
|
71050
|
+
break;
|
|
71051
|
+
}
|
|
71052
|
+
rows.push(splitMarkdownTableCells(tableLine));
|
|
71053
|
+
lineIndex += 1;
|
|
71054
|
+
}
|
|
71055
|
+
const tableBlock = !hasNativeTableBlock ? renderMarkdownTableToNativeSlackBlock(headers, rows) : renderMarkdownTableToFallbackSlackBlock(headers, rows);
|
|
71056
|
+
if (tableBlock) {
|
|
71057
|
+
pushBlock(tableBlock);
|
|
71058
|
+
if (tableBlock.type === "table") {
|
|
71059
|
+
hasNativeTableBlock = true;
|
|
71060
|
+
}
|
|
71061
|
+
}
|
|
71062
|
+
continue;
|
|
71063
|
+
}
|
|
71064
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
71065
|
+
if (headingMatch) {
|
|
71066
|
+
flushParagraph();
|
|
71067
|
+
hasSeenHeading = true;
|
|
71068
|
+
const level = headingMatch[1]?.length ?? 1;
|
|
71069
|
+
const content = headingMatch[2] ?? "";
|
|
71070
|
+
if (level <= 2) {
|
|
71071
|
+
if (majorHeadingCount > 0) {
|
|
71072
|
+
pushDividerIfNeeded();
|
|
71073
|
+
}
|
|
71074
|
+
pushBlock({
|
|
71075
|
+
type: "header",
|
|
71076
|
+
text: {
|
|
71077
|
+
type: "plain_text",
|
|
71078
|
+
text: normalizeSlackHeaderText(content)
|
|
71079
|
+
}
|
|
71080
|
+
});
|
|
71081
|
+
majorHeadingCount += 1;
|
|
71082
|
+
} else if (level === 3) {
|
|
71083
|
+
pushBlock({
|
|
71084
|
+
type: "section",
|
|
71085
|
+
text: {
|
|
71086
|
+
type: "mrkdwn",
|
|
71087
|
+
text: `*${renderInlineMarkdownToSlackMrkdwn(content)}*`
|
|
71088
|
+
}
|
|
71089
|
+
});
|
|
71090
|
+
} else {
|
|
71091
|
+
paragraph.push(`*${renderInlineMarkdownToSlackMrkdwn(content)}*`);
|
|
71092
|
+
}
|
|
71093
|
+
continue;
|
|
71094
|
+
}
|
|
71095
|
+
const bulletMatch = line.match(/^\s*[-*+]\s+(.+)$/);
|
|
71096
|
+
if (bulletMatch) {
|
|
71097
|
+
paragraph.push(`• ${renderInlineMarkdownToSlackMrkdwn(bulletMatch[1] ?? "")}`);
|
|
71098
|
+
continue;
|
|
71099
|
+
}
|
|
71100
|
+
const orderedMatch = line.match(/^\s*(\d+)\.\s+(.+)$/);
|
|
71101
|
+
if (orderedMatch) {
|
|
71102
|
+
paragraph.push(`${orderedMatch[1]}. ${renderInlineMarkdownToSlackMrkdwn(orderedMatch[2] ?? "")}`);
|
|
71103
|
+
continue;
|
|
71104
|
+
}
|
|
71105
|
+
paragraph.push(renderInlineMarkdownToSlackMrkdwn(line));
|
|
71106
|
+
}
|
|
71107
|
+
if (inCodeFence) {
|
|
71108
|
+
flushCodeFence();
|
|
71109
|
+
}
|
|
71110
|
+
flushParagraph();
|
|
71111
|
+
return blocks;
|
|
71112
|
+
}
|
|
71113
|
+
function resolveSlackMessageContent(params) {
|
|
71114
|
+
const { text, inputFormat, renderMode } = params;
|
|
71115
|
+
if (inputFormat === "blocks") {
|
|
71116
|
+
if (renderMode !== "none" && renderMode !== "blocks") {
|
|
71117
|
+
throw new Error("Slack blocks input supports only --render none or --render blocks");
|
|
71118
|
+
}
|
|
71119
|
+
const blocks = parseSlackBlocksInput(text);
|
|
71120
|
+
return {
|
|
71121
|
+
text: buildSlackBlocksFallbackText(blocks),
|
|
71122
|
+
blocks,
|
|
71123
|
+
apiText: ""
|
|
71124
|
+
};
|
|
71125
|
+
}
|
|
71126
|
+
if (inputFormat === "html") {
|
|
71127
|
+
throw new Error("Slack does not support HTML input; use --input md, mrkdwn, plain, or blocks");
|
|
71128
|
+
}
|
|
71129
|
+
if (renderMode === "blocks") {
|
|
71130
|
+
const blocks = renderMarkdownToSlackBlocks(text);
|
|
71131
|
+
return {
|
|
71132
|
+
text: buildSlackBlocksFallbackText(blocks),
|
|
71133
|
+
blocks,
|
|
71134
|
+
apiText: ""
|
|
71135
|
+
};
|
|
71136
|
+
}
|
|
71137
|
+
if (renderMode === "html") {
|
|
71138
|
+
throw new Error("Slack does not support --render html");
|
|
71139
|
+
}
|
|
71140
|
+
if (inputFormat === "mrkdwn" || renderMode === "mrkdwn") {
|
|
71141
|
+
return { text };
|
|
71142
|
+
}
|
|
71143
|
+
if (inputFormat === "md" || renderMode === "native") {
|
|
71144
|
+
return {
|
|
71145
|
+
text: renderMarkdownToSlackMrkdwn(text)
|
|
71146
|
+
};
|
|
71147
|
+
}
|
|
71148
|
+
return { text };
|
|
71149
|
+
}
|
|
71150
|
+
|
|
71151
|
+
// src/channels/slack/message-actions.ts
|
|
70436
71152
|
var { WebClient } = import_bolt.webApi;
|
|
70437
71153
|
function createSlackClient(botToken) {
|
|
70438
71154
|
return new WebClient(botToken);
|
|
@@ -70500,13 +71216,18 @@ async function loadSlackMedia(media) {
|
|
|
70500
71216
|
async function sendSlackMessage(params) {
|
|
70501
71217
|
const client = createSlackClient(params.botToken);
|
|
70502
71218
|
const target = await resolveSlackTarget(client, params.target, params.threadId, params.replyTo);
|
|
71219
|
+
const resolvedMessage = resolveSlackMessageContent({
|
|
71220
|
+
text: params.message ?? "",
|
|
71221
|
+
inputFormat: params.inputFormat ?? "md",
|
|
71222
|
+
renderMode: params.renderMode ?? "native"
|
|
71223
|
+
});
|
|
70503
71224
|
if (params.media) {
|
|
70504
71225
|
const media = await loadSlackMedia(params.media);
|
|
70505
71226
|
const filesClient = client.files;
|
|
70506
71227
|
await filesClient.uploadV2({
|
|
70507
71228
|
channel_id: target.channelId,
|
|
70508
71229
|
thread_ts: target.threadTs,
|
|
70509
|
-
initial_comment:
|
|
71230
|
+
initial_comment: resolvedMessage.text,
|
|
70510
71231
|
filename: media.filename,
|
|
70511
71232
|
file: media.data
|
|
70512
71233
|
});
|
|
@@ -70518,10 +71239,20 @@ async function sendSlackMessage(params) {
|
|
|
70518
71239
|
filename: media.filename
|
|
70519
71240
|
};
|
|
70520
71241
|
}
|
|
70521
|
-
const posted =
|
|
71242
|
+
const posted = resolvedMessage.blocks ? [
|
|
71243
|
+
{
|
|
71244
|
+
text: resolvedMessage.text,
|
|
71245
|
+
ts: (await client.chat.postMessage({
|
|
71246
|
+
channel: target.channelId,
|
|
71247
|
+
thread_ts: target.threadTs,
|
|
71248
|
+
text: resolvedMessage.apiText ?? resolvedMessage.text,
|
|
71249
|
+
blocks: resolvedMessage.blocks
|
|
71250
|
+
})).ts ?? ""
|
|
71251
|
+
}
|
|
71252
|
+
].filter((entry) => entry.ts) : await postSlackText(client, {
|
|
70522
71253
|
channel: target.channelId,
|
|
70523
71254
|
threadTs: target.threadTs,
|
|
70524
|
-
text:
|
|
71255
|
+
text: resolvedMessage.text
|
|
70525
71256
|
});
|
|
70526
71257
|
return {
|
|
70527
71258
|
ok: true,
|
|
@@ -70583,10 +71314,16 @@ async function editSlackMessage(params) {
|
|
|
70583
71314
|
}
|
|
70584
71315
|
const client = createSlackClient(params.botToken);
|
|
70585
71316
|
const target = await resolveSlackTarget(client, params.target);
|
|
71317
|
+
const resolvedMessage = resolveSlackMessageContent({
|
|
71318
|
+
text: params.message,
|
|
71319
|
+
inputFormat: params.inputFormat ?? "md",
|
|
71320
|
+
renderMode: params.renderMode ?? "native"
|
|
71321
|
+
});
|
|
70586
71322
|
await client.chat.update({
|
|
70587
71323
|
channel: target.channelId,
|
|
70588
71324
|
ts: params.messageId,
|
|
70589
|
-
text:
|
|
71325
|
+
text: resolvedMessage.apiText ?? resolvedMessage.text,
|
|
71326
|
+
...resolvedMessage.blocks ? { blocks: resolvedMessage.blocks } : {}
|
|
70590
71327
|
});
|
|
70591
71328
|
return { ok: true };
|
|
70592
71329
|
}
|
|
@@ -70780,7 +71517,9 @@ var slackChannelPlugin = {
|
|
|
70780
71517
|
limit: command.limit,
|
|
70781
71518
|
query: command.query,
|
|
70782
71519
|
pollQuestion: command.pollQuestion,
|
|
70783
|
-
pollOptions: command.pollOptions
|
|
71520
|
+
pollOptions: command.pollOptions,
|
|
71521
|
+
inputFormat: command.inputFormat,
|
|
71522
|
+
renderMode: command.renderMode
|
|
70784
71523
|
};
|
|
70785
71524
|
switch (command.action) {
|
|
70786
71525
|
case "send":
|
|
@@ -70924,8 +71663,18 @@ function hasTelegramBotMention(text, botUsername) {
|
|
|
70924
71663
|
if (!text || !normalizedBotUsername) {
|
|
70925
71664
|
return false;
|
|
70926
71665
|
}
|
|
70927
|
-
|
|
70928
|
-
|
|
71666
|
+
return extractTelegramMentionTargets(text).includes(normalizedBotUsername.toLowerCase());
|
|
71667
|
+
}
|
|
71668
|
+
function hasForeignTelegramMention(text, botUsername) {
|
|
71669
|
+
const mentions = extractTelegramMentionTargets(text);
|
|
71670
|
+
if (mentions.length === 0) {
|
|
71671
|
+
return false;
|
|
71672
|
+
}
|
|
71673
|
+
const normalizedBotUsername = (botUsername ?? "").trim().replace(/^@/, "").toLowerCase();
|
|
71674
|
+
if (!normalizedBotUsername) {
|
|
71675
|
+
return true;
|
|
71676
|
+
}
|
|
71677
|
+
return !mentions.includes(normalizedBotUsername);
|
|
70929
71678
|
}
|
|
70930
71679
|
function stripTelegramBotMention(text, botUsername) {
|
|
70931
71680
|
const normalizedBotUsername = (botUsername ?? "").trim().replace(/^@/, "");
|
|
@@ -70944,6 +71693,27 @@ function isReplyToTelegramBot(message, botUserId) {
|
|
|
70944
71693
|
function escapeRegExp2(raw) {
|
|
70945
71694
|
return raw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
70946
71695
|
}
|
|
71696
|
+
function extractTelegramMentionTargets(text) {
|
|
71697
|
+
if (!text) {
|
|
71698
|
+
return [];
|
|
71699
|
+
}
|
|
71700
|
+
const matches = new Set;
|
|
71701
|
+
const mentionPattern = /(^|\s)@([A-Za-z0-9_]{2,32})\b/g;
|
|
71702
|
+
const slashCommandTargetPattern = /(^|\s)\/[A-Za-z0-9_]+@([A-Za-z0-9_]{2,32})\b/g;
|
|
71703
|
+
for (const match of text.matchAll(mentionPattern)) {
|
|
71704
|
+
const username = match[2]?.trim().toLowerCase();
|
|
71705
|
+
if (username) {
|
|
71706
|
+
matches.add(username);
|
|
71707
|
+
}
|
|
71708
|
+
}
|
|
71709
|
+
for (const match of text.matchAll(slashCommandTargetPattern)) {
|
|
71710
|
+
const username = match[2]?.trim().toLowerCase();
|
|
71711
|
+
if (username) {
|
|
71712
|
+
matches.add(username);
|
|
71713
|
+
}
|
|
71714
|
+
}
|
|
71715
|
+
return [...matches];
|
|
71716
|
+
}
|
|
70947
71717
|
|
|
70948
71718
|
// src/channels/telegram/session-routing.ts
|
|
70949
71719
|
function resolveTelegramConversationTarget(params) {
|
|
@@ -71075,6 +71845,20 @@ async function paceTelegramEdit(params) {
|
|
|
71075
71845
|
function recordTelegramEdit(params) {
|
|
71076
71846
|
lastTelegramEditAtByMessage.set(getTelegramEditKey(params), Date.now());
|
|
71077
71847
|
}
|
|
71848
|
+
function buildTelegramTextPayload(params) {
|
|
71849
|
+
if (params.wireFormat !== "html") {
|
|
71850
|
+
return {
|
|
71851
|
+
text: params.text
|
|
71852
|
+
};
|
|
71853
|
+
}
|
|
71854
|
+
return {
|
|
71855
|
+
text: params.text,
|
|
71856
|
+
parseMode: "HTML"
|
|
71857
|
+
};
|
|
71858
|
+
}
|
|
71859
|
+
function isTelegramHtmlParseError(error) {
|
|
71860
|
+
return error instanceof TelegramApiError && error.errorCode === 400 && /can't parse entities|unsupported start tag|unexpected end tag|entity beginning/i.test(error.description);
|
|
71861
|
+
}
|
|
71078
71862
|
function splitTelegramText(text, maxChars = 3900) {
|
|
71079
71863
|
if (!text) {
|
|
71080
71864
|
return [];
|
|
@@ -71109,83 +71893,136 @@ function splitTelegramText(text, maxChars = 3900) {
|
|
|
71109
71893
|
}
|
|
71110
71894
|
async function postTelegramText(params) {
|
|
71111
71895
|
const posted = [];
|
|
71112
|
-
const
|
|
71113
|
-
|
|
71114
|
-
const
|
|
71115
|
-
|
|
71116
|
-
|
|
71117
|
-
|
|
71118
|
-
|
|
71119
|
-
|
|
71120
|
-
|
|
71121
|
-
|
|
71122
|
-
|
|
71896
|
+
const rawChunks = splitTelegramText(params.text);
|
|
71897
|
+
try {
|
|
71898
|
+
for (const chunk of rawChunks) {
|
|
71899
|
+
const payload = buildTelegramTextPayload({
|
|
71900
|
+
text: chunk,
|
|
71901
|
+
wireFormat: params.wireFormat
|
|
71902
|
+
});
|
|
71903
|
+
const response = await callTelegramApi(params.token, "sendMessage", {
|
|
71904
|
+
chat_id: params.chatId,
|
|
71905
|
+
text: payload.text,
|
|
71906
|
+
...payload.parseMode ? { parse_mode: payload.parseMode } : {},
|
|
71907
|
+
...params.topicId != null && !params.omitThreadId ? { message_thread_id: params.topicId } : {}
|
|
71908
|
+
});
|
|
71909
|
+
posted.push({
|
|
71910
|
+
text: payload.text,
|
|
71911
|
+
messageId: response.message_id
|
|
71912
|
+
});
|
|
71913
|
+
}
|
|
71914
|
+
} catch (error) {
|
|
71915
|
+
if (params.wireFormat !== "html" || !isTelegramHtmlParseError(error)) {
|
|
71916
|
+
throw error;
|
|
71917
|
+
}
|
|
71918
|
+
for (const chunk of posted) {
|
|
71919
|
+
await callTelegramApi(params.token, "deleteMessage", {
|
|
71920
|
+
chat_id: params.chatId,
|
|
71921
|
+
message_id: chunk.messageId
|
|
71922
|
+
});
|
|
71923
|
+
lastTelegramEditAtByMessage.delete(getTelegramEditKey({
|
|
71924
|
+
token: params.token,
|
|
71925
|
+
chatId: params.chatId,
|
|
71926
|
+
messageId: chunk.messageId
|
|
71927
|
+
}));
|
|
71928
|
+
}
|
|
71929
|
+
posted.length = 0;
|
|
71930
|
+
for (const chunk of rawChunks) {
|
|
71931
|
+
const response = await callTelegramApi(params.token, "sendMessage", {
|
|
71932
|
+
chat_id: params.chatId,
|
|
71933
|
+
text: chunk,
|
|
71934
|
+
...params.topicId != null && !params.omitThreadId ? { message_thread_id: params.topicId } : {}
|
|
71935
|
+
});
|
|
71936
|
+
posted.push({
|
|
71937
|
+
text: chunk,
|
|
71938
|
+
messageId: response.message_id
|
|
71939
|
+
});
|
|
71940
|
+
}
|
|
71123
71941
|
}
|
|
71124
71942
|
return posted;
|
|
71125
71943
|
}
|
|
71126
71944
|
async function reconcileTelegramText(params) {
|
|
71127
|
-
const
|
|
71128
|
-
const
|
|
71129
|
-
|
|
71130
|
-
|
|
71131
|
-
|
|
71132
|
-
|
|
71133
|
-
|
|
71134
|
-
|
|
71135
|
-
|
|
71136
|
-
|
|
71137
|
-
|
|
71138
|
-
|
|
71139
|
-
|
|
71945
|
+
const rawNextTexts = splitTelegramText(params.text);
|
|
71946
|
+
const reconcileWithTexts = async (nextTexts, parseMode) => {
|
|
71947
|
+
const reconciled = [];
|
|
71948
|
+
const sharedCount = Math.min(params.chunks.length, nextTexts.length);
|
|
71949
|
+
for (let index = 0;index < sharedCount; index += 1) {
|
|
71950
|
+
const existingChunk = params.chunks[index];
|
|
71951
|
+
const nextText = nextTexts[index];
|
|
71952
|
+
if (!existingChunk || !nextText) {
|
|
71953
|
+
continue;
|
|
71954
|
+
}
|
|
71955
|
+
if (existingChunk.text !== nextText) {
|
|
71956
|
+
await paceTelegramEdit({
|
|
71957
|
+
token: params.token,
|
|
71958
|
+
chatId: params.chatId,
|
|
71959
|
+
messageId: existingChunk.messageId
|
|
71960
|
+
});
|
|
71961
|
+
await callTelegramApi(params.token, "editMessageText", {
|
|
71962
|
+
chat_id: params.chatId,
|
|
71963
|
+
message_id: existingChunk.messageId,
|
|
71964
|
+
text: nextText,
|
|
71965
|
+
...parseMode ? { parse_mode: parseMode } : {}
|
|
71966
|
+
});
|
|
71967
|
+
recordTelegramEdit({
|
|
71968
|
+
token: params.token,
|
|
71969
|
+
chatId: params.chatId,
|
|
71970
|
+
messageId: existingChunk.messageId
|
|
71971
|
+
});
|
|
71972
|
+
}
|
|
71973
|
+
reconciled.push({
|
|
71974
|
+
text: nextText,
|
|
71140
71975
|
messageId: existingChunk.messageId
|
|
71141
71976
|
});
|
|
71142
|
-
|
|
71977
|
+
}
|
|
71978
|
+
for (let index = sharedCount;index < nextTexts.length; index += 1) {
|
|
71979
|
+
const nextText = nextTexts[index];
|
|
71980
|
+
if (!nextText) {
|
|
71981
|
+
continue;
|
|
71982
|
+
}
|
|
71983
|
+
const response = await callTelegramApi(params.token, "sendMessage", {
|
|
71143
71984
|
chat_id: params.chatId,
|
|
71144
|
-
|
|
71145
|
-
|
|
71985
|
+
text: nextText,
|
|
71986
|
+
...parseMode ? { parse_mode: parseMode } : {},
|
|
71987
|
+
...params.topicId != null && !params.omitThreadId ? { message_thread_id: params.topicId } : {}
|
|
71146
71988
|
});
|
|
71147
|
-
|
|
71148
|
-
|
|
71149
|
-
|
|
71150
|
-
messageId: existingChunk.messageId
|
|
71989
|
+
reconciled.push({
|
|
71990
|
+
text: nextText,
|
|
71991
|
+
messageId: response.message_id
|
|
71151
71992
|
});
|
|
71152
71993
|
}
|
|
71153
|
-
|
|
71154
|
-
|
|
71155
|
-
|
|
71156
|
-
|
|
71157
|
-
|
|
71158
|
-
|
|
71159
|
-
|
|
71160
|
-
|
|
71161
|
-
|
|
71994
|
+
for (let index = nextTexts.length;index < params.chunks.length; index += 1) {
|
|
71995
|
+
const staleChunk = params.chunks[index];
|
|
71996
|
+
if (!staleChunk) {
|
|
71997
|
+
continue;
|
|
71998
|
+
}
|
|
71999
|
+
await callTelegramApi(params.token, "deleteMessage", {
|
|
72000
|
+
chat_id: params.chatId,
|
|
72001
|
+
message_id: staleChunk.messageId
|
|
72002
|
+
});
|
|
72003
|
+
lastTelegramEditAtByMessage.delete(getTelegramEditKey({
|
|
72004
|
+
token: params.token,
|
|
72005
|
+
chatId: params.chatId,
|
|
72006
|
+
messageId: staleChunk.messageId
|
|
72007
|
+
}));
|
|
71162
72008
|
}
|
|
71163
|
-
|
|
71164
|
-
|
|
71165
|
-
|
|
71166
|
-
|
|
71167
|
-
});
|
|
71168
|
-
reconciled.push({
|
|
71169
|
-
text: nextText,
|
|
71170
|
-
messageId: response.message_id
|
|
71171
|
-
});
|
|
72009
|
+
return reconciled;
|
|
72010
|
+
};
|
|
72011
|
+
if (params.wireFormat !== "html") {
|
|
72012
|
+
return await reconcileWithTexts(rawNextTexts);
|
|
71172
72013
|
}
|
|
71173
|
-
|
|
71174
|
-
const
|
|
71175
|
-
|
|
71176
|
-
|
|
72014
|
+
try {
|
|
72015
|
+
const renderedTexts = rawNextTexts.map((text) => buildTelegramTextPayload({
|
|
72016
|
+
text,
|
|
72017
|
+
wireFormat: params.wireFormat
|
|
72018
|
+
}).text);
|
|
72019
|
+
return await reconcileWithTexts(renderedTexts, "HTML");
|
|
72020
|
+
} catch (error) {
|
|
72021
|
+
if (!isTelegramHtmlParseError(error)) {
|
|
72022
|
+
throw error;
|
|
71177
72023
|
}
|
|
71178
|
-
await
|
|
71179
|
-
chat_id: params.chatId,
|
|
71180
|
-
message_id: staleChunk.messageId
|
|
71181
|
-
});
|
|
71182
|
-
lastTelegramEditAtByMessage.delete(getTelegramEditKey({
|
|
71183
|
-
token: params.token,
|
|
71184
|
-
chatId: params.chatId,
|
|
71185
|
-
messageId: staleChunk.messageId
|
|
71186
|
-
}));
|
|
72024
|
+
return await reconcileWithTexts(rawNextTexts);
|
|
71187
72025
|
}
|
|
71188
|
-
return reconciled;
|
|
71189
72026
|
}
|
|
71190
72027
|
function shouldOmitTelegramThreadId(topicId) {
|
|
71191
72028
|
return topicId === 1;
|
|
@@ -71194,6 +72031,235 @@ function getTelegramMaxChars(maxMessageChars) {
|
|
|
71194
72031
|
return Math.min(maxMessageChars, 3900);
|
|
71195
72032
|
}
|
|
71196
72033
|
|
|
72034
|
+
// src/channels/telegram/html-safe.ts
|
|
72035
|
+
var TOKEN_PREFIX = "\x00TGH";
|
|
72036
|
+
function escapeHtml(text) {
|
|
72037
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
72038
|
+
}
|
|
72039
|
+
function sanitizeTelegramHref(rawHref) {
|
|
72040
|
+
const href = rawHref.trim();
|
|
72041
|
+
if (!href) {
|
|
72042
|
+
return null;
|
|
72043
|
+
}
|
|
72044
|
+
const lowered = href.toLowerCase();
|
|
72045
|
+
if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("tg://") || lowered.startsWith("mailto:")) {
|
|
72046
|
+
return escapeHtml(href);
|
|
72047
|
+
}
|
|
72048
|
+
return null;
|
|
72049
|
+
}
|
|
72050
|
+
function storeToken(tokens, value) {
|
|
72051
|
+
const token = `${TOKEN_PREFIX}${tokens.length};\x00`;
|
|
72052
|
+
tokens.push(value);
|
|
72053
|
+
return token;
|
|
72054
|
+
}
|
|
72055
|
+
function restoreTokens(text, tokens) {
|
|
72056
|
+
let restored = text;
|
|
72057
|
+
for (let index = 0;index < tokens.length; index += 1) {
|
|
72058
|
+
restored = restored.replaceAll(`${TOKEN_PREFIX}${index};\x00`, tokens[index] ?? "");
|
|
72059
|
+
}
|
|
72060
|
+
return restored;
|
|
72061
|
+
}
|
|
72062
|
+
function applyInlineFormatting(text) {
|
|
72063
|
+
return text.replaceAll(/~~([^~]+)~~/g, "<s>$1</s>").replaceAll(/\*\*([^*\n][\s\S]*?[^*\n])\*\*/g, "<b>$1</b>").replaceAll(/\*([^*\n][\s\S]*?[^*\n])\*/g, "<i>$1</i>");
|
|
72064
|
+
}
|
|
72065
|
+
function renderInlineMarkdownToTelegramHtml(text) {
|
|
72066
|
+
const tokens = [];
|
|
72067
|
+
let working = text;
|
|
72068
|
+
working = working.replaceAll(/`([^`\n]+)`/g, (_match, code) => storeToken(tokens, `<code>${escapeHtml(code)}</code>`));
|
|
72069
|
+
working = working.replaceAll(/\[([^\]]+)\]\(([^)\s]+)\)/g, (match, label, href) => {
|
|
72070
|
+
const safeHref = sanitizeTelegramHref(href);
|
|
72071
|
+
if (!safeHref) {
|
|
72072
|
+
return match;
|
|
72073
|
+
}
|
|
72074
|
+
return storeToken(tokens, `<a href="${safeHref}">${escapeHtml(label)}</a>`);
|
|
72075
|
+
});
|
|
72076
|
+
working = escapeHtml(working);
|
|
72077
|
+
working = applyInlineFormatting(working);
|
|
72078
|
+
return restoreTokens(working, tokens);
|
|
72079
|
+
}
|
|
72080
|
+
function renderHeadingLine(level, content) {
|
|
72081
|
+
const renderedContent = renderInlineMarkdownToTelegramHtml(content);
|
|
72082
|
+
if (level <= 2) {
|
|
72083
|
+
return {
|
|
72084
|
+
kind: "section-heading",
|
|
72085
|
+
text: `<b>${renderedContent}</b>`
|
|
72086
|
+
};
|
|
72087
|
+
}
|
|
72088
|
+
if (level === 3) {
|
|
72089
|
+
return {
|
|
72090
|
+
kind: "subsection-heading",
|
|
72091
|
+
text: `<b>${renderedContent}</b>`
|
|
72092
|
+
};
|
|
72093
|
+
}
|
|
72094
|
+
return {
|
|
72095
|
+
kind: "text",
|
|
72096
|
+
text: renderedContent
|
|
72097
|
+
};
|
|
72098
|
+
}
|
|
72099
|
+
function renderMarkdownLine(line) {
|
|
72100
|
+
if (line.trim().length === 0) {
|
|
72101
|
+
return { kind: "blank", text: "" };
|
|
72102
|
+
}
|
|
72103
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
72104
|
+
if (headingMatch) {
|
|
72105
|
+
return renderHeadingLine(headingMatch[1]?.length ?? 1, headingMatch[2] ?? "");
|
|
72106
|
+
}
|
|
72107
|
+
const bulletMatch = line.match(/^\s*[-*+]\s+(.+)$/);
|
|
72108
|
+
if (bulletMatch) {
|
|
72109
|
+
return {
|
|
72110
|
+
kind: "text",
|
|
72111
|
+
text: `• ${renderInlineMarkdownToTelegramHtml(bulletMatch[1] ?? "")}`
|
|
72112
|
+
};
|
|
72113
|
+
}
|
|
72114
|
+
const orderedMatch = line.match(/^\s*(\d+)\.\s+(.+)$/);
|
|
72115
|
+
if (orderedMatch) {
|
|
72116
|
+
return {
|
|
72117
|
+
kind: "text",
|
|
72118
|
+
text: `${orderedMatch[1]}. ${renderInlineMarkdownToTelegramHtml(orderedMatch[2] ?? "")}`
|
|
72119
|
+
};
|
|
72120
|
+
}
|
|
72121
|
+
const quoteMatch = line.match(/^\s*>\s?(.*)$/);
|
|
72122
|
+
if (quoteMatch) {
|
|
72123
|
+
return {
|
|
72124
|
+
kind: "text",
|
|
72125
|
+
text: `> ${renderInlineMarkdownToTelegramHtml(quoteMatch[1] ?? "")}`
|
|
72126
|
+
};
|
|
72127
|
+
}
|
|
72128
|
+
return {
|
|
72129
|
+
kind: "text",
|
|
72130
|
+
text: renderInlineMarkdownToTelegramHtml(line)
|
|
72131
|
+
};
|
|
72132
|
+
}
|
|
72133
|
+
function renderMarkdownTextBlock(text) {
|
|
72134
|
+
const lines = text.split(`
|
|
72135
|
+
`).map(renderMarkdownLine);
|
|
72136
|
+
const rendered = [];
|
|
72137
|
+
const ensureBlankSeparator = () => {
|
|
72138
|
+
if (rendered.length === 0 || rendered[rendered.length - 1] === "") {
|
|
72139
|
+
return;
|
|
72140
|
+
}
|
|
72141
|
+
rendered.push("");
|
|
72142
|
+
};
|
|
72143
|
+
const hasLaterVisibleLine = (startIndex) => lines.slice(startIndex + 1).some((line) => line.kind !== "blank");
|
|
72144
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
72145
|
+
const line = lines[index];
|
|
72146
|
+
if (!line) {
|
|
72147
|
+
continue;
|
|
72148
|
+
}
|
|
72149
|
+
if (line.kind === "blank") {
|
|
72150
|
+
ensureBlankSeparator();
|
|
72151
|
+
continue;
|
|
72152
|
+
}
|
|
72153
|
+
if (line.kind === "section-heading") {
|
|
72154
|
+
ensureBlankSeparator();
|
|
72155
|
+
rendered.push(line.text);
|
|
72156
|
+
if (hasLaterVisibleLine(index)) {
|
|
72157
|
+
rendered.push("");
|
|
72158
|
+
}
|
|
72159
|
+
continue;
|
|
72160
|
+
}
|
|
72161
|
+
if (line.kind === "subsection-heading") {
|
|
72162
|
+
rendered.push(line.text);
|
|
72163
|
+
continue;
|
|
72164
|
+
}
|
|
72165
|
+
rendered.push(line.text);
|
|
72166
|
+
}
|
|
72167
|
+
while (rendered[rendered.length - 1] === "") {
|
|
72168
|
+
rendered.pop();
|
|
72169
|
+
}
|
|
72170
|
+
return rendered.join(`
|
|
72171
|
+
`);
|
|
72172
|
+
}
|
|
72173
|
+
function renderCodeFence(language, code) {
|
|
72174
|
+
const trimmedLanguage = language.trim();
|
|
72175
|
+
const safeLanguage = /^[a-z0-9_+-]+$/i.test(trimmedLanguage) ? trimmedLanguage : "";
|
|
72176
|
+
const escapedCode = escapeHtml(code.replace(/\n$/, ""));
|
|
72177
|
+
if (safeLanguage) {
|
|
72178
|
+
return `<pre><code class="language-${safeLanguage}">${escapedCode}</code></pre>`;
|
|
72179
|
+
}
|
|
72180
|
+
return `<pre><code>${escapedCode}</code></pre>`;
|
|
72181
|
+
}
|
|
72182
|
+
function renderTelegramHtmlSafeFromMarkdown(markdown) {
|
|
72183
|
+
const normalized = markdown.replaceAll(`\r
|
|
72184
|
+
`, `
|
|
72185
|
+
`).replaceAll("\r", `
|
|
72186
|
+
`).trim();
|
|
72187
|
+
if (!normalized) {
|
|
72188
|
+
return "";
|
|
72189
|
+
}
|
|
72190
|
+
const segments = [];
|
|
72191
|
+
const codeFencePattern = /```([^\n`]*)\n([\s\S]*?)```/g;
|
|
72192
|
+
let cursor = 0;
|
|
72193
|
+
for (const match of normalized.matchAll(codeFencePattern)) {
|
|
72194
|
+
const matchText = match[0];
|
|
72195
|
+
const matchIndex = match.index ?? -1;
|
|
72196
|
+
if (matchIndex < 0) {
|
|
72197
|
+
continue;
|
|
72198
|
+
}
|
|
72199
|
+
const textBefore = normalized.slice(cursor, matchIndex);
|
|
72200
|
+
if (textBefore) {
|
|
72201
|
+
segments.push(renderMarkdownTextBlock(textBefore));
|
|
72202
|
+
}
|
|
72203
|
+
segments.push(renderCodeFence(match[1] ?? "", match[2] ?? ""));
|
|
72204
|
+
cursor = matchIndex + matchText.length;
|
|
72205
|
+
}
|
|
72206
|
+
const tail = normalized.slice(cursor);
|
|
72207
|
+
if (tail) {
|
|
72208
|
+
const unmatchedFenceMatch = tail.match(/([\s\S]*?)```([^\n`]*)\n([\s\S]*)$/);
|
|
72209
|
+
if (unmatchedFenceMatch) {
|
|
72210
|
+
const textBeforeFence = unmatchedFenceMatch[1] ?? "";
|
|
72211
|
+
const language = unmatchedFenceMatch[2] ?? "";
|
|
72212
|
+
const codeTail = unmatchedFenceMatch[3] ?? "";
|
|
72213
|
+
if (textBeforeFence) {
|
|
72214
|
+
segments.push(renderMarkdownTextBlock(textBeforeFence));
|
|
72215
|
+
}
|
|
72216
|
+
segments.push(renderCodeFence(language, codeTail));
|
|
72217
|
+
} else {
|
|
72218
|
+
segments.push(renderMarkdownTextBlock(tail));
|
|
72219
|
+
}
|
|
72220
|
+
}
|
|
72221
|
+
return segments.filter((segment) => segment.length > 0).join(`
|
|
72222
|
+
`).replaceAll(/\n{3,}/g, `
|
|
72223
|
+
|
|
72224
|
+
`).trim();
|
|
72225
|
+
}
|
|
72226
|
+
|
|
72227
|
+
// src/channels/telegram/content.ts
|
|
72228
|
+
function resolveTelegramMessageContent(params) {
|
|
72229
|
+
const { text, inputFormat, renderMode } = params;
|
|
72230
|
+
if (inputFormat === "blocks" || renderMode === "blocks") {
|
|
72231
|
+
throw new Error("Telegram does not support block payloads");
|
|
72232
|
+
}
|
|
72233
|
+
if (inputFormat === "mrkdwn" || renderMode === "mrkdwn") {
|
|
72234
|
+
throw new Error("Telegram does not support Slack mrkdwn payloads");
|
|
72235
|
+
}
|
|
72236
|
+
if (inputFormat === "html") {
|
|
72237
|
+
if (renderMode !== "none" && renderMode !== "html" && renderMode !== "native") {
|
|
72238
|
+
throw new Error("Telegram HTML input supports only --render none, html, or native");
|
|
72239
|
+
}
|
|
72240
|
+
return {
|
|
72241
|
+
text,
|
|
72242
|
+
wireFormat: "html"
|
|
72243
|
+
};
|
|
72244
|
+
}
|
|
72245
|
+
if (renderMode === "html") {
|
|
72246
|
+
return {
|
|
72247
|
+
text: renderTelegramHtmlSafeFromMarkdown(text),
|
|
72248
|
+
wireFormat: "html"
|
|
72249
|
+
};
|
|
72250
|
+
}
|
|
72251
|
+
if (renderMode === "none" || inputFormat === "plain") {
|
|
72252
|
+
return {
|
|
72253
|
+
text,
|
|
72254
|
+
wireFormat: "text"
|
|
72255
|
+
};
|
|
72256
|
+
}
|
|
72257
|
+
return {
|
|
72258
|
+
text: renderTelegramHtmlSafeFromMarkdown(text),
|
|
72259
|
+
wireFormat: "html"
|
|
72260
|
+
};
|
|
72261
|
+
}
|
|
72262
|
+
|
|
71197
72263
|
// src/channels/telegram/attachments.ts
|
|
71198
72264
|
import { basename as basename2 } from "node:path";
|
|
71199
72265
|
function pickTelegramPhoto(photo) {
|
|
@@ -71463,12 +72529,18 @@ class TelegramPollingService {
|
|
|
71463
72529
|
return;
|
|
71464
72530
|
}
|
|
71465
72531
|
const topicId = binding.topicId ? Number(binding.topicId) : undefined;
|
|
72532
|
+
const renderedNotification = resolveTelegramMessageContent({
|
|
72533
|
+
text,
|
|
72534
|
+
inputFormat: "md",
|
|
72535
|
+
renderMode: "native"
|
|
72536
|
+
});
|
|
71466
72537
|
await postTelegramText({
|
|
71467
72538
|
token: this.accountConfig.botToken,
|
|
71468
72539
|
chatId,
|
|
71469
|
-
text,
|
|
72540
|
+
text: renderedNotification.text,
|
|
71470
72541
|
topicId: Number.isFinite(topicId) ? topicId : undefined,
|
|
71471
|
-
omitThreadId: shouldOmitTelegramThreadId(Number.isFinite(topicId) ? topicId : undefined)
|
|
72542
|
+
omitThreadId: shouldOmitTelegramThreadId(Number.isFinite(topicId) ? topicId : undefined),
|
|
72543
|
+
wireFormat: renderedNotification.wireFormat
|
|
71472
72544
|
});
|
|
71473
72545
|
}
|
|
71474
72546
|
});
|
|
@@ -71699,6 +72771,10 @@ class TelegramPollingService {
|
|
|
71699
72771
|
}
|
|
71700
72772
|
}
|
|
71701
72773
|
}
|
|
72774
|
+
if (hasForeignTelegramMention(rawText, this.botUsername)) {
|
|
72775
|
+
await this.processedEventsStore.markCompleted(eventId);
|
|
72776
|
+
return;
|
|
72777
|
+
}
|
|
71702
72778
|
const explicitMention = hasTelegramBotMention(rawText, this.botUsername) || Boolean(slashCommand && rawText.startsWith("/"));
|
|
71703
72779
|
const followUpState = await this.agentService.getConversationFollowUpState(routeInfo.sessionTarget);
|
|
71704
72780
|
const effectiveFollowUpMode = resolveFollowUpMode({
|
|
@@ -71712,6 +72788,16 @@ class TelegramPollingService {
|
|
|
71712
72788
|
lastBotReplyAt: followUpState.lastBotReplyAt,
|
|
71713
72789
|
directReplyToBot: isReplyToTelegramBot(message, this.botUserId)
|
|
71714
72790
|
});
|
|
72791
|
+
const textBody = explicitMention ? stripTelegramBotMention(rawText, this.botUsername) : rawText;
|
|
72792
|
+
const recentMessageMarker = String(message.message_id);
|
|
72793
|
+
if (rawText || explicitMention || slashCommand) {
|
|
72794
|
+
await this.agentService.appendRecentConversationMessage(routeInfo.sessionTarget, {
|
|
72795
|
+
marker: recentMessageMarker,
|
|
72796
|
+
text: slashCommand ? "" : textBody,
|
|
72797
|
+
senderId: message.from?.id != null ? String(message.from.id).trim() : undefined,
|
|
72798
|
+
senderName: [message.from?.first_name, message.from?.last_name].filter((value) => typeof value === "string" && value.trim().length > 0).join(" ").trim() || message.from?.username?.trim() || undefined
|
|
72799
|
+
});
|
|
72800
|
+
}
|
|
71715
72801
|
if (routeInfo.route.requireMention && !wasMentioned) {
|
|
71716
72802
|
await this.processedEventsStore.markCompleted(eventId);
|
|
71717
72803
|
return;
|
|
@@ -71719,7 +72805,10 @@ class TelegramPollingService {
|
|
|
71719
72805
|
if (explicitMention && followUpState.overrideMode === "paused") {
|
|
71720
72806
|
await this.agentService.reactivateConversationFollowUp(routeInfo.sessionTarget);
|
|
71721
72807
|
}
|
|
71722
|
-
const
|
|
72808
|
+
const effectivePromptText = textBody || (explicitMention ? buildMentionOnlyFollowUpPrompt({
|
|
72809
|
+
conversationKind: routeInfo.conversationKind === "topic" ? "group" : routeInfo.conversationKind,
|
|
72810
|
+
threaded: routeInfo.topicId != null || message.message_thread_id != null && Number.isFinite(message.message_thread_id)
|
|
72811
|
+
}) : "");
|
|
71723
72812
|
const attachmentPaths = await resolveTelegramAttachmentPaths({
|
|
71724
72813
|
message,
|
|
71725
72814
|
botToken: this.accountConfig.botToken,
|
|
@@ -71727,11 +72816,18 @@ class TelegramPollingService {
|
|
|
71727
72816
|
sessionKey: routeInfo.sessionTarget.sessionKey,
|
|
71728
72817
|
messageId: String(message.message_id)
|
|
71729
72818
|
});
|
|
71730
|
-
const text = prependAttachmentMentions(
|
|
72819
|
+
const text = prependAttachmentMentions(effectivePromptText, attachmentPaths);
|
|
71731
72820
|
if (!text) {
|
|
71732
72821
|
await this.processedEventsStore.markCompleted(eventId);
|
|
71733
72822
|
return;
|
|
71734
72823
|
}
|
|
72824
|
+
const recentConversationReplay = await this.agentService.getRecentConversationReplayMessages(routeInfo.sessionTarget, {
|
|
72825
|
+
excludeMarker: recentMessageMarker
|
|
72826
|
+
});
|
|
72827
|
+
const enrichPromptText = (nextText) => prependRecentConversationContext({
|
|
72828
|
+
currentText: nextText,
|
|
72829
|
+
recentMessages: recentConversationReplay
|
|
72830
|
+
});
|
|
71735
72831
|
await this.processedEventsStore.markProcessing(eventId);
|
|
71736
72832
|
await this.activityStore.record({
|
|
71737
72833
|
agentId: routeInfo.route.agentId,
|
|
@@ -71759,7 +72855,7 @@ class TelegramPollingService {
|
|
|
71759
72855
|
});
|
|
71760
72856
|
const protectedControlMutationRule = auth.mayManageProtectedResources ? undefined : DEFAULT_PROTECTED_CONTROL_RULE;
|
|
71761
72857
|
const agentPromptText = buildAgentPromptText({
|
|
71762
|
-
text,
|
|
72858
|
+
text: enrichPromptText(text),
|
|
71763
72859
|
identity,
|
|
71764
72860
|
config: this.loadedConfig.raw.channels.telegram.agentPrompt,
|
|
71765
72861
|
cliTool,
|
|
@@ -71802,7 +72898,7 @@ class TelegramPollingService {
|
|
|
71802
72898
|
text,
|
|
71803
72899
|
agentPromptText,
|
|
71804
72900
|
agentPromptBuilder: (nextText) => buildAgentPromptText({
|
|
71805
|
-
text: nextText,
|
|
72901
|
+
text: enrichPromptText(nextText),
|
|
71806
72902
|
identity,
|
|
71807
72903
|
config: this.loadedConfig.raw.channels.telegram.agentPrompt,
|
|
71808
72904
|
cliTool,
|
|
@@ -71811,27 +72907,43 @@ class TelegramPollingService {
|
|
|
71811
72907
|
protectedControlMutationRule
|
|
71812
72908
|
}),
|
|
71813
72909
|
protectedControlMutationRule,
|
|
72910
|
+
transformSessionInputText: enrichPromptText,
|
|
72911
|
+
onPromptAccepted: async () => {
|
|
72912
|
+
await this.agentService.markRecentConversationProcessed(routeInfo.sessionTarget, recentMessageMarker);
|
|
72913
|
+
},
|
|
71814
72914
|
route: routeInfo.route,
|
|
71815
72915
|
maxChars: this.getTelegramMaxChars(routeInfo.route.agentId),
|
|
71816
72916
|
timingContext,
|
|
71817
72917
|
postText: async (nextText) => {
|
|
72918
|
+
const renderedReply = resolveTelegramMessageContent({
|
|
72919
|
+
text: nextText,
|
|
72920
|
+
inputFormat: "md",
|
|
72921
|
+
renderMode: "native"
|
|
72922
|
+
});
|
|
71818
72923
|
responseChunks = await postTelegramText({
|
|
71819
72924
|
token: this.accountConfig.botToken,
|
|
71820
72925
|
chatId: message.chat.id,
|
|
71821
|
-
text:
|
|
72926
|
+
text: renderedReply.text,
|
|
71822
72927
|
topicId: routeInfo.topicId,
|
|
71823
|
-
omitThreadId: shouldOmitTelegramThreadId(routeInfo.topicId)
|
|
72928
|
+
omitThreadId: shouldOmitTelegramThreadId(routeInfo.topicId),
|
|
72929
|
+
wireFormat: renderedReply.wireFormat
|
|
71824
72930
|
});
|
|
71825
72931
|
return responseChunks;
|
|
71826
72932
|
},
|
|
71827
72933
|
reconcileText: async (chunks, nextText) => {
|
|
72934
|
+
const renderedReply = resolveTelegramMessageContent({
|
|
72935
|
+
text: nextText,
|
|
72936
|
+
inputFormat: "md",
|
|
72937
|
+
renderMode: "native"
|
|
72938
|
+
});
|
|
71828
72939
|
responseChunks = await reconcileTelegramText({
|
|
71829
72940
|
token: this.accountConfig.botToken,
|
|
71830
72941
|
chatId: message.chat.id,
|
|
71831
72942
|
chunks,
|
|
71832
|
-
text:
|
|
72943
|
+
text: renderedReply.text,
|
|
71833
72944
|
topicId: routeInfo.topicId,
|
|
71834
|
-
omitThreadId: shouldOmitTelegramThreadId(routeInfo.topicId)
|
|
72945
|
+
omitThreadId: shouldOmitTelegramThreadId(routeInfo.topicId),
|
|
72946
|
+
wireFormat: renderedReply.wireFormat
|
|
71835
72947
|
});
|
|
71836
72948
|
return responseChunks;
|
|
71837
72949
|
}
|
|
@@ -71932,6 +73044,23 @@ function resolveRouteAndTarget(params) {
|
|
|
71932
73044
|
// src/channels/telegram/message-actions.ts
|
|
71933
73045
|
import { basename as basename3, extname as extname2 } from "node:path";
|
|
71934
73046
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
73047
|
+
function isTelegramHtmlParseError2(error) {
|
|
73048
|
+
return error instanceof TelegramApiError && error.errorCode === 400 && /can't parse entities|unsupported start tag|unexpected end tag|entity beginning/i.test(error.description);
|
|
73049
|
+
}
|
|
73050
|
+
function buildTelegramMessagePayload(params) {
|
|
73051
|
+
if (!params.text) {
|
|
73052
|
+
return null;
|
|
73053
|
+
}
|
|
73054
|
+
const resolved = resolveTelegramMessageContent({
|
|
73055
|
+
text: params.text,
|
|
73056
|
+
inputFormat: params.inputFormat ?? "md",
|
|
73057
|
+
renderMode: params.renderMode ?? "native"
|
|
73058
|
+
});
|
|
73059
|
+
return {
|
|
73060
|
+
text: resolved.text,
|
|
73061
|
+
parse_mode: resolved.wireFormat === "html" ? "HTML" : undefined
|
|
73062
|
+
};
|
|
73063
|
+
}
|
|
71935
73064
|
function parseTelegramChatId(raw) {
|
|
71936
73065
|
const value = raw.trim();
|
|
71937
73066
|
if (value.startsWith("@")) {
|
|
@@ -71944,7 +73073,11 @@ function parseTelegramChatId(raw) {
|
|
|
71944
73073
|
throw new Error(`Invalid Telegram target: ${raw}`);
|
|
71945
73074
|
}
|
|
71946
73075
|
function parseTelegramThreadId(threadId) {
|
|
71947
|
-
const
|
|
73076
|
+
const raw = threadId?.trim();
|
|
73077
|
+
if (!raw) {
|
|
73078
|
+
return;
|
|
73079
|
+
}
|
|
73080
|
+
const parsed = Number(raw);
|
|
71948
73081
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
71949
73082
|
}
|
|
71950
73083
|
async function loadTelegramMedia(media) {
|
|
@@ -71999,6 +73132,11 @@ async function sendTelegramMessage(params) {
|
|
|
71999
73132
|
const chatId = parseTelegramChatId(params.target);
|
|
72000
73133
|
const threadId = parseTelegramThreadId(params.threadId);
|
|
72001
73134
|
const replyTo = params.replyTo ? Number(params.replyTo) : undefined;
|
|
73135
|
+
const formattedMessage = buildTelegramMessagePayload({
|
|
73136
|
+
text: params.message,
|
|
73137
|
+
inputFormat: params.inputFormat,
|
|
73138
|
+
renderMode: params.renderMode
|
|
73139
|
+
});
|
|
72002
73140
|
if (params.media) {
|
|
72003
73141
|
const media = await loadTelegramMedia(params.media);
|
|
72004
73142
|
const kind = inferTelegramMediaKind(media.filename, params.forceDocument);
|
|
@@ -72018,35 +73156,75 @@ async function sendTelegramMessage(params) {
|
|
|
72018
73156
|
};
|
|
72019
73157
|
const method = methodByKind[kind];
|
|
72020
73158
|
const fileField = fieldByKind[kind];
|
|
72021
|
-
const
|
|
73159
|
+
const payload2 = {
|
|
72022
73160
|
chat_id: chatId,
|
|
72023
|
-
...
|
|
73161
|
+
...formattedMessage ? { caption: formattedMessage.text } : {},
|
|
73162
|
+
...formattedMessage?.parse_mode ? { parse_mode: formattedMessage.parse_mode } : {},
|
|
72024
73163
|
...threadId != null && threadId !== 1 ? { message_thread_id: threadId } : {},
|
|
72025
73164
|
...replyTo != null ? { reply_to_message_id: replyTo } : {},
|
|
72026
73165
|
...params.silent ? { disable_notification: true } : {}
|
|
72027
73166
|
};
|
|
72028
|
-
|
|
72029
|
-
|
|
72030
|
-
|
|
72031
|
-
|
|
73167
|
+
try {
|
|
73168
|
+
if (media.remoteUrl) {
|
|
73169
|
+
return await callTelegramApi(params.botToken, method, {
|
|
73170
|
+
...payload2,
|
|
73171
|
+
[fileField]: media.remoteUrl
|
|
73172
|
+
});
|
|
73173
|
+
}
|
|
73174
|
+
return await callTelegramMultipartApi({
|
|
73175
|
+
token: params.botToken,
|
|
73176
|
+
method,
|
|
73177
|
+
payload: payload2,
|
|
73178
|
+
fileField,
|
|
73179
|
+
file: media.file,
|
|
73180
|
+
filename: media.filename
|
|
73181
|
+
});
|
|
73182
|
+
} catch (error) {
|
|
73183
|
+
if (!formattedMessage || !isTelegramHtmlParseError2(error)) {
|
|
73184
|
+
throw error;
|
|
73185
|
+
}
|
|
73186
|
+
const plainPayload = {
|
|
73187
|
+
...payload2,
|
|
73188
|
+
caption: params.message ?? ""
|
|
73189
|
+
};
|
|
73190
|
+
delete plainPayload.parse_mode;
|
|
73191
|
+
if (media.remoteUrl) {
|
|
73192
|
+
return await callTelegramApi(params.botToken, method, {
|
|
73193
|
+
...plainPayload,
|
|
73194
|
+
[fileField]: media.remoteUrl
|
|
73195
|
+
});
|
|
73196
|
+
}
|
|
73197
|
+
return await callTelegramMultipartApi({
|
|
73198
|
+
token: params.botToken,
|
|
73199
|
+
method,
|
|
73200
|
+
payload: plainPayload,
|
|
73201
|
+
fileField,
|
|
73202
|
+
file: media.file,
|
|
73203
|
+
filename: media.filename
|
|
72032
73204
|
});
|
|
72033
73205
|
}
|
|
72034
|
-
return await callTelegramMultipartApi({
|
|
72035
|
-
token: params.botToken,
|
|
72036
|
-
method,
|
|
72037
|
-
payload,
|
|
72038
|
-
fileField,
|
|
72039
|
-
file: media.file,
|
|
72040
|
-
filename: media.filename
|
|
72041
|
-
});
|
|
72042
73206
|
}
|
|
72043
|
-
|
|
73207
|
+
const payload = {
|
|
72044
73208
|
chat_id: chatId,
|
|
72045
|
-
text: params.message ?? "",
|
|
73209
|
+
text: formattedMessage?.text ?? params.message ?? "",
|
|
73210
|
+
...formattedMessage?.parse_mode ? { parse_mode: formattedMessage.parse_mode } : {},
|
|
72046
73211
|
...threadId != null && threadId !== 1 ? { message_thread_id: threadId } : {},
|
|
72047
73212
|
...replyTo != null ? { reply_to_message_id: replyTo } : {},
|
|
72048
73213
|
...params.silent ? { disable_notification: true } : {}
|
|
72049
|
-
}
|
|
73214
|
+
};
|
|
73215
|
+
try {
|
|
73216
|
+
return await callTelegramApi(params.botToken, "sendMessage", payload);
|
|
73217
|
+
} catch (error) {
|
|
73218
|
+
if (!formattedMessage || !isTelegramHtmlParseError2(error)) {
|
|
73219
|
+
throw error;
|
|
73220
|
+
}
|
|
73221
|
+
const plainPayload = {
|
|
73222
|
+
...payload,
|
|
73223
|
+
text: params.message ?? ""
|
|
73224
|
+
};
|
|
73225
|
+
delete plainPayload.parse_mode;
|
|
73226
|
+
return await callTelegramApi(params.botToken, "sendMessage", plainPayload);
|
|
73227
|
+
}
|
|
72050
73228
|
}
|
|
72051
73229
|
async function sendTelegramPoll(params) {
|
|
72052
73230
|
if (!params.pollQuestion || !params.pollOptions?.length) {
|
|
@@ -72064,11 +73242,30 @@ async function editTelegramMessage(params) {
|
|
|
72064
73242
|
if (!params.messageId || !params.message) {
|
|
72065
73243
|
throw new Error("--message-id and --message are required");
|
|
72066
73244
|
}
|
|
72067
|
-
|
|
73245
|
+
const formattedMessage = buildTelegramMessagePayload({
|
|
73246
|
+
text: params.message,
|
|
73247
|
+
inputFormat: params.inputFormat,
|
|
73248
|
+
renderMode: params.renderMode
|
|
73249
|
+
});
|
|
73250
|
+
const payload = {
|
|
72068
73251
|
chat_id: parseTelegramChatId(params.target),
|
|
72069
73252
|
message_id: Number(params.messageId),
|
|
72070
|
-
text: params.message
|
|
72071
|
-
|
|
73253
|
+
text: formattedMessage?.text ?? params.message,
|
|
73254
|
+
...formattedMessage?.parse_mode ? { parse_mode: formattedMessage.parse_mode } : {}
|
|
73255
|
+
};
|
|
73256
|
+
try {
|
|
73257
|
+
return await callTelegramApi(params.botToken, "editMessageText", payload);
|
|
73258
|
+
} catch (error) {
|
|
73259
|
+
if (!formattedMessage || !isTelegramHtmlParseError2(error)) {
|
|
73260
|
+
throw error;
|
|
73261
|
+
}
|
|
73262
|
+
const plainPayload = {
|
|
73263
|
+
...payload,
|
|
73264
|
+
text: params.message
|
|
73265
|
+
};
|
|
73266
|
+
delete plainPayload.parse_mode;
|
|
73267
|
+
return await callTelegramApi(params.botToken, "editMessageText", plainPayload);
|
|
73268
|
+
}
|
|
72072
73269
|
}
|
|
72073
73270
|
async function deleteTelegramMessageAction(params) {
|
|
72074
73271
|
if (!params.messageId) {
|
|
@@ -72186,7 +73383,9 @@ var telegramChannelPlugin = {
|
|
|
72186
73383
|
pollQuestion: command.pollQuestion,
|
|
72187
73384
|
pollOptions: command.pollOptions,
|
|
72188
73385
|
forceDocument: command.forceDocument,
|
|
72189
|
-
silent: command.silent
|
|
73386
|
+
silent: command.silent,
|
|
73387
|
+
inputFormat: command.inputFormat,
|
|
73388
|
+
renderMode: command.renderMode
|
|
72190
73389
|
};
|
|
72191
73390
|
switch (command.action) {
|
|
72192
73391
|
case "send":
|
|
@@ -72387,6 +73586,7 @@ function buildOwnerAlertCommand(params) {
|
|
|
72387
73586
|
account: params.accountId,
|
|
72388
73587
|
target: params.platform === "slack" ? `user:${params.userId}` : params.userId,
|
|
72389
73588
|
message: params.message,
|
|
73589
|
+
messageFile: undefined,
|
|
72390
73590
|
media: undefined,
|
|
72391
73591
|
messageId: undefined,
|
|
72392
73592
|
emoji: undefined,
|
|
@@ -72401,7 +73601,9 @@ function buildOwnerAlertCommand(params) {
|
|
|
72401
73601
|
silent: false,
|
|
72402
73602
|
progress: false,
|
|
72403
73603
|
final: false,
|
|
72404
|
-
json: false
|
|
73604
|
+
json: false,
|
|
73605
|
+
inputFormat: "md",
|
|
73606
|
+
renderMode: "native"
|
|
72405
73607
|
};
|
|
72406
73608
|
}
|
|
72407
73609
|
async function sendOwnerAlert(params) {
|
|
@@ -74573,6 +75775,47 @@ async function runLoopsCli(args) {
|
|
|
74573
75775
|
throw new Error(renderLoopsHelp());
|
|
74574
75776
|
}
|
|
74575
75777
|
|
|
75778
|
+
// src/control/message-cli.ts
|
|
75779
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
75780
|
+
|
|
75781
|
+
// src/channels/message-format.ts
|
|
75782
|
+
var DEFAULT_MESSAGE_INPUT_FORMAT = "md";
|
|
75783
|
+
var DEFAULT_MESSAGE_RENDER_MODE = "native";
|
|
75784
|
+
var MESSAGE_INPUT_FORMATS = [
|
|
75785
|
+
"plain",
|
|
75786
|
+
"md",
|
|
75787
|
+
"html",
|
|
75788
|
+
"mrkdwn",
|
|
75789
|
+
"blocks"
|
|
75790
|
+
];
|
|
75791
|
+
var MESSAGE_RENDER_MODES = [
|
|
75792
|
+
"native",
|
|
75793
|
+
"none",
|
|
75794
|
+
"html",
|
|
75795
|
+
"mrkdwn",
|
|
75796
|
+
"blocks"
|
|
75797
|
+
];
|
|
75798
|
+
function parseMessageInputFormat(raw) {
|
|
75799
|
+
const normalized = raw?.trim().toLowerCase();
|
|
75800
|
+
if (!normalized) {
|
|
75801
|
+
return DEFAULT_MESSAGE_INPUT_FORMAT;
|
|
75802
|
+
}
|
|
75803
|
+
if (MESSAGE_INPUT_FORMATS.includes(normalized)) {
|
|
75804
|
+
return normalized;
|
|
75805
|
+
}
|
|
75806
|
+
throw new Error(`--input must be one of: ${MESSAGE_INPUT_FORMATS.join(", ")}`);
|
|
75807
|
+
}
|
|
75808
|
+
function parseMessageRenderMode(raw) {
|
|
75809
|
+
const normalized = raw?.trim().toLowerCase();
|
|
75810
|
+
if (!normalized) {
|
|
75811
|
+
return DEFAULT_MESSAGE_RENDER_MODE;
|
|
75812
|
+
}
|
|
75813
|
+
if (MESSAGE_RENDER_MODES.includes(normalized)) {
|
|
75814
|
+
return normalized;
|
|
75815
|
+
}
|
|
75816
|
+
throw new Error(`--render must be one of: ${MESSAGE_RENDER_MODES.join(", ")}`);
|
|
75817
|
+
}
|
|
75818
|
+
|
|
74576
75819
|
// src/control/message-cli.ts
|
|
74577
75820
|
function getConfigPath() {
|
|
74578
75821
|
return process.env.CLISBOT_CONFIG_PATH;
|
|
@@ -74604,6 +75847,14 @@ function parseOptionValue3(args, name) {
|
|
|
74604
75847
|
const values = parseRepeatedOption3(args, name);
|
|
74605
75848
|
return values.length > 0 ? values.at(-1) : undefined;
|
|
74606
75849
|
}
|
|
75850
|
+
function parseMessageBodyFileOption(args) {
|
|
75851
|
+
const bodyFileValues = parseRepeatedOption3(args, "--body-file");
|
|
75852
|
+
const messageFileValues = parseRepeatedOption3(args, "--message-file");
|
|
75853
|
+
if (bodyFileValues.length > 0 && messageFileValues.length > 0) {
|
|
75854
|
+
throw new Error("--body-file and --message-file are aliases; use only one");
|
|
75855
|
+
}
|
|
75856
|
+
return bodyFileValues.at(-1) ?? messageFileValues.at(-1);
|
|
75857
|
+
}
|
|
74607
75858
|
function parseIntegerOption(args, name) {
|
|
74608
75859
|
const raw = parseOptionValue3(args, name);
|
|
74609
75860
|
if (!raw) {
|
|
@@ -74644,6 +75895,7 @@ function parseMessageCommand(args) {
|
|
|
74644
75895
|
account: parseOptionValue3(rest, "--account"),
|
|
74645
75896
|
target: parseOptionValue3(rest, "--target"),
|
|
74646
75897
|
message: parseOptionValue3(rest, "--message") ?? parseOptionValue3(rest, "-m"),
|
|
75898
|
+
messageFile: parseMessageBodyFileOption(rest),
|
|
74647
75899
|
media: parseOptionValue3(rest, "--media"),
|
|
74648
75900
|
messageId: parseOptionValue3(rest, "--message-id"),
|
|
74649
75901
|
emoji: parseOptionValue3(rest, "--emoji"),
|
|
@@ -74658,7 +75910,9 @@ function parseMessageCommand(args) {
|
|
|
74658
75910
|
silent: hasFlag4(rest, "--silent"),
|
|
74659
75911
|
progress: hasFlag4(rest, "--progress"),
|
|
74660
75912
|
final: hasFlag4(rest, "--final"),
|
|
74661
|
-
json: hasFlag4(rest, "--json")
|
|
75913
|
+
json: hasFlag4(rest, "--json"),
|
|
75914
|
+
inputFormat: parseMessageInputFormat(parseOptionValue3(rest, "--input")),
|
|
75915
|
+
renderMode: parseMessageRenderMode(parseOptionValue3(rest, "--render"))
|
|
74662
75916
|
};
|
|
74663
75917
|
}
|
|
74664
75918
|
function renderMessageHelp() {
|
|
@@ -74666,20 +75920,59 @@ function renderMessageHelp() {
|
|
|
74666
75920
|
"clisbot message",
|
|
74667
75921
|
"",
|
|
74668
75922
|
"Usage:",
|
|
74669
|
-
" clisbot message send --channel <slack|telegram> --target <dest> --message <text> [--account <id>] [--media <path-or-url>] [--reply-to <id>] [--thread-id <id>] [--force-document] [--silent] [--progress|--final]",
|
|
75923
|
+
" clisbot message send --channel <slack|telegram> --target <dest> [--message <text> | --body-file <path>] [--input <plain|md|html|mrkdwn|blocks>] [--render <native|none|html|mrkdwn|blocks>] [--account <id>] [--media <path-or-url>] [--reply-to <id>] [--thread-id <id>] [--force-document] [--silent] [--progress|--final]",
|
|
74670
75924
|
" clisbot message poll --channel <slack|telegram> --target <dest> --poll-question <text> --poll-option <value> [--poll-option <value>] [--account <id>] [--thread-id <id>] [--silent]",
|
|
74671
75925
|
" clisbot message react --channel <slack|telegram> --target <dest> --message-id <id> --emoji <emoji> [--account <id>] [--remove]",
|
|
74672
75926
|
" clisbot message reactions --channel <slack|telegram> --target <dest> --message-id <id> [--account <id>]",
|
|
74673
75927
|
" clisbot message read --channel <slack|telegram> --target <dest> [--account <id>] [--limit <n>]",
|
|
74674
|
-
" clisbot message edit --channel <slack|telegram> --target <dest> --message-id <id> --message <text> [--account <id>]",
|
|
75928
|
+
" clisbot message edit --channel <slack|telegram> --target <dest> --message-id <id> [--message <text> | --body-file <path>] [--input <plain|md|html|mrkdwn|blocks>] [--render <native|none|html|mrkdwn|blocks>] [--account <id>]",
|
|
74675
75929
|
" clisbot message delete --channel <slack|telegram> --target <dest> --message-id <id> [--account <id>]",
|
|
74676
75930
|
" clisbot message pin --channel <slack|telegram> --target <dest> --message-id <id> [--account <id>]",
|
|
74677
75931
|
" clisbot message unpin --channel <slack|telegram> --target <dest> [--message-id <id>] [--account <id>]",
|
|
74678
75932
|
" clisbot message pins --channel <slack|telegram> --target <dest> [--account <id>]",
|
|
74679
|
-
" clisbot message search --channel <slack|telegram> --target <dest> --query <text> [--account <id>] [--limit <n>]"
|
|
75933
|
+
" clisbot message search --channel <slack|telegram> --target <dest> --query <text> [--account <id>] [--limit <n>]",
|
|
75934
|
+
"",
|
|
75935
|
+
"Send/Edit Content Options:",
|
|
75936
|
+
" --message <text> Inline message body",
|
|
75937
|
+
" --body-file <path> Read the message body from a file",
|
|
75938
|
+
" Alias: --message-file (compat only)",
|
|
75939
|
+
" --input <plain|md|html|mrkdwn|blocks>",
|
|
75940
|
+
" Input content format. Default: md",
|
|
75941
|
+
" --render <native|none|html|mrkdwn|blocks>",
|
|
75942
|
+
" Output rendering mode. Default: native",
|
|
75943
|
+
"",
|
|
75944
|
+
"Render Rules:",
|
|
75945
|
+
" native Channel-owned default rendering",
|
|
75946
|
+
" - Telegram: Markdown/plain -> safe HTML",
|
|
75947
|
+
" - Slack: Markdown/plain -> mrkdwn",
|
|
75948
|
+
" none Content is already destination-native",
|
|
75949
|
+
" - Telegram: use with --input html",
|
|
75950
|
+
" - Slack: use with --input mrkdwn or blocks",
|
|
75951
|
+
" blocks Slack only. Render Markdown into Block Kit",
|
|
75952
|
+
" html Telegram only",
|
|
75953
|
+
" mrkdwn Slack only",
|
|
75954
|
+
"",
|
|
75955
|
+
"Examples:",
|
|
75956
|
+
' clisbot message send --channel telegram --target -1001234567890 --thread-id 42 --message "## Status"',
|
|
75957
|
+
' clisbot message send --channel telegram --target -1001234567890 --thread-id 42 --input html --render none --message "<b>Status</b>"',
|
|
75958
|
+
' clisbot message send --channel slack --target channel:C1234567890 --thread-id 1712345678.123456 --message "## Status"',
|
|
75959
|
+
' clisbot message send --channel slack --target channel:C1234567890 --thread-id 1712345678.123456 --input mrkdwn --render none --message "*Status*"',
|
|
75960
|
+
" clisbot message send --channel slack --target channel:C1234567890 --thread-id 1712345678.123456 --input blocks --render none --body-file ./reply-blocks.json"
|
|
74680
75961
|
].join(`
|
|
74681
75962
|
`);
|
|
74682
75963
|
}
|
|
75964
|
+
async function resolveCommandMessage(command) {
|
|
75965
|
+
if (!command.messageFile) {
|
|
75966
|
+
return command;
|
|
75967
|
+
}
|
|
75968
|
+
if (command.message) {
|
|
75969
|
+
throw new Error("--message cannot be used together with --body-file or --message-file");
|
|
75970
|
+
}
|
|
75971
|
+
return {
|
|
75972
|
+
...command,
|
|
75973
|
+
message: await readFile4(command.messageFile, "utf8")
|
|
75974
|
+
};
|
|
75975
|
+
}
|
|
74683
75976
|
function assertTarget(command) {
|
|
74684
75977
|
if (!command.target) {
|
|
74685
75978
|
throw new Error("--target is required");
|
|
@@ -74691,21 +75984,22 @@ async function runMessageCli(args, dependencies = defaultMessageCliDependencies)
|
|
|
74691
75984
|
dependencies.print(renderMessageHelp());
|
|
74692
75985
|
return;
|
|
74693
75986
|
}
|
|
74694
|
-
|
|
75987
|
+
const resolvedCommand = await resolveCommandMessage(command);
|
|
75988
|
+
if (resolvedCommand.progress && resolvedCommand.final) {
|
|
74695
75989
|
throw new Error("--progress and --final cannot be used together");
|
|
74696
75990
|
}
|
|
74697
|
-
assertTarget(
|
|
75991
|
+
assertTarget(resolvedCommand);
|
|
74698
75992
|
const loadedConfig = await dependencies.loadConfig(getConfigPath(), {
|
|
74699
|
-
materializeChannels: [
|
|
75993
|
+
materializeChannels: [resolvedCommand.channel]
|
|
74700
75994
|
});
|
|
74701
|
-
const plugin = dependencies.plugins.find((entry) => entry.id ===
|
|
75995
|
+
const plugin = dependencies.plugins.find((entry) => entry.id === resolvedCommand.channel);
|
|
74702
75996
|
if (!plugin) {
|
|
74703
|
-
throw new Error(`Unsupported message channel: ${
|
|
75997
|
+
throw new Error(`Unsupported message channel: ${resolvedCommand.channel}`);
|
|
74704
75998
|
}
|
|
74705
|
-
const execution = await plugin.runMessageCommand(loadedConfig,
|
|
74706
|
-
const replyTarget =
|
|
75999
|
+
const execution = await plugin.runMessageCommand(loadedConfig, resolvedCommand);
|
|
76000
|
+
const replyTarget = resolvedCommand.action === "send" || resolvedCommand.action === "poll" ? plugin.resolveMessageReplyTarget({
|
|
74707
76001
|
loadedConfig,
|
|
74708
|
-
command,
|
|
76002
|
+
command: resolvedCommand,
|
|
74709
76003
|
accountId: execution.accountId
|
|
74710
76004
|
}) : null;
|
|
74711
76005
|
if (replyTarget) {
|
|
@@ -75768,7 +77062,7 @@ import { statSync as statSync5, watch } from "node:fs";
|
|
|
75768
77062
|
import { basename as basename4, dirname as dirname15 } from "node:path";
|
|
75769
77063
|
|
|
75770
77064
|
// src/channels/processed-events-store.ts
|
|
75771
|
-
import { mkdir as mkdir2, readFile as
|
|
77065
|
+
import { mkdir as mkdir2, readFile as readFile5, writeFile as writeFile2 } from "node:fs/promises";
|
|
75772
77066
|
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
75773
77067
|
var PROCESSING_STALE_MS = 30 * 60 * 1000;
|
|
75774
77068
|
|
|
@@ -75786,7 +77080,7 @@ class ProcessedEventsStore {
|
|
|
75786
77080
|
return;
|
|
75787
77081
|
}
|
|
75788
77082
|
try {
|
|
75789
|
-
const text = await
|
|
77083
|
+
const text = await readFile5(this.filePath, "utf8");
|
|
75790
77084
|
this.document = JSON.parse(text);
|
|
75791
77085
|
} catch {
|
|
75792
77086
|
this.document = { events: {} };
|