@ycodium-ai/plugin-im 0.2.2566 → 0.2.2578
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/README.md +37 -13
- package/dist/index.js +1018 -37
- package/locales/en.json +44 -1
- package/locales/zh-CN.json +44 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -126208,6 +126208,7 @@ var ZH_ALIASES = {
|
|
|
126208
126208
|
\u72B6\u6001: "status",
|
|
126209
126209
|
\u65B0\u5BF9\u8BDD: "new",
|
|
126210
126210
|
\u5E2E\u52A9: "help",
|
|
126211
|
+
\u9762\u677F: "panel",
|
|
126211
126212
|
\u4F1A\u8BDD\u5217\u8868: "sessions"
|
|
126212
126213
|
};
|
|
126213
126214
|
var resolveTeam = (query, teams) => {
|
|
@@ -126225,6 +126226,7 @@ var parseChatCommand = (input) => {
|
|
|
126225
126226
|
if (/^\/status$/i.test(trimmed)) return { kind: "status" };
|
|
126226
126227
|
if (/^\/new$/i.test(trimmed)) return { kind: "new" };
|
|
126227
126228
|
if (/^\/help$/i.test(trimmed)) return { kind: "help" };
|
|
126229
|
+
if (/^\/panel$/i.test(trimmed)) return { kind: "panel" };
|
|
126228
126230
|
if (/^\/sessions$/i.test(trimmed)) return { kind: "sessions" };
|
|
126229
126231
|
const resume = resumeArgument(input.text, input.chinese);
|
|
126230
126232
|
if (resume !== void 0) {
|
|
@@ -126249,7 +126251,7 @@ var parseChatCommand = (input) => {
|
|
|
126249
126251
|
}
|
|
126250
126252
|
return { kind: "message" };
|
|
126251
126253
|
};
|
|
126252
|
-
var helpText = (t) => [
|
|
126254
|
+
var helpText = (t, options = {}) => [
|
|
126253
126255
|
t("help.title"),
|
|
126254
126256
|
t("help.stop"),
|
|
126255
126257
|
t("help.status"),
|
|
@@ -126260,7 +126262,8 @@ var helpText = (t) => [
|
|
|
126260
126262
|
t("help.plan"),
|
|
126261
126263
|
t("help.team"),
|
|
126262
126264
|
t("help.thread"),
|
|
126263
|
-
t("help.aliases")
|
|
126265
|
+
t("help.aliases"),
|
|
126266
|
+
...options.panel === true ? ["", t("help.panel")] : []
|
|
126264
126267
|
].join("\n");
|
|
126265
126268
|
var describeTargets = (target, teams, t) => {
|
|
126266
126269
|
const current = target.kind === "thread" ? t("targets.currentThread") : t("targets.currentTeam", {
|
|
@@ -131972,6 +131975,40 @@ var buildApprovalSettledCard = (settlement, t) => ({
|
|
|
131972
131975
|
}
|
|
131973
131976
|
]
|
|
131974
131977
|
});
|
|
131978
|
+
var PANEL_TEMPLATE = {
|
|
131979
|
+
menu: "blue",
|
|
131980
|
+
busy: "grey",
|
|
131981
|
+
notice: "orange"
|
|
131982
|
+
};
|
|
131983
|
+
var panelButton = (button) => ({
|
|
131984
|
+
tag: "button",
|
|
131985
|
+
text: { tag: "plain_text", content: button.label },
|
|
131986
|
+
type: button.danger === true ? "danger" : "default",
|
|
131987
|
+
value: button.action.arg === void 0 ? { pn: button.action.op } : { pn: button.action.op, a: button.action.arg }
|
|
131988
|
+
});
|
|
131989
|
+
var buildPanelCard = (card) => {
|
|
131990
|
+
const elements = [];
|
|
131991
|
+
if (card.text !== "") elements.push({ tag: "markdown", content: stripAngleBrackets(card.text) });
|
|
131992
|
+
for (const group of card.groups) {
|
|
131993
|
+
if (group.buttons.length === 0) continue;
|
|
131994
|
+
if (group.heading !== null) {
|
|
131995
|
+
elements.push({ tag: "markdown", content: `**${stripAngleBrackets(group.heading)}**` });
|
|
131996
|
+
}
|
|
131997
|
+
elements.push({ tag: "action", actions: group.buttons.map(panelButton) });
|
|
131998
|
+
}
|
|
131999
|
+
if (card.nav.length > 0) {
|
|
132000
|
+
elements.push({ tag: "hr" }, { tag: "action", actions: card.nav.map(panelButton) });
|
|
132001
|
+
}
|
|
132002
|
+
if (elements.length === 0) elements.push({ tag: "markdown", content: " " });
|
|
132003
|
+
return {
|
|
132004
|
+
config: { wide_screen_mode: true },
|
|
132005
|
+
header: {
|
|
132006
|
+
title: { tag: "plain_text", content: card.title },
|
|
132007
|
+
template: PANEL_TEMPLATE[card.tone]
|
|
132008
|
+
},
|
|
132009
|
+
elements
|
|
132010
|
+
};
|
|
132011
|
+
};
|
|
131975
132012
|
|
|
131976
132013
|
// src/larkInbound.ts
|
|
131977
132014
|
var import_node_sdk = __toESM(require_lib2(), 1);
|
|
@@ -132087,26 +132124,44 @@ var fetchMessageResource = async (input) => {
|
|
|
132087
132124
|
}
|
|
132088
132125
|
return new Uint8Array(await response.arrayBuffer());
|
|
132089
132126
|
};
|
|
132090
|
-
var
|
|
132127
|
+
var cardActionValue = (event) => {
|
|
132091
132128
|
const roots = [event, event?.event];
|
|
132092
132129
|
for (const root of roots) {
|
|
132093
132130
|
const value = root?.action?.value;
|
|
132094
132131
|
if (value === void 0 || value === null) continue;
|
|
132095
|
-
|
|
132096
|
-
|
|
132097
|
-
|
|
132098
|
-
|
|
132099
|
-
|
|
132100
|
-
record = void 0;
|
|
132101
|
-
}
|
|
132102
|
-
} else {
|
|
132103
|
-
record = value;
|
|
132132
|
+
if (typeof value !== "string") return value;
|
|
132133
|
+
try {
|
|
132134
|
+
return JSON.parse(value);
|
|
132135
|
+
} catch {
|
|
132136
|
+
continue;
|
|
132104
132137
|
}
|
|
132105
|
-
const token = record?.["tk"];
|
|
132106
|
-
if (typeof token === "string" && token.length > 0) return token;
|
|
132107
132138
|
}
|
|
132108
132139
|
return null;
|
|
132109
132140
|
};
|
|
132141
|
+
var cardActionToken = (event) => {
|
|
132142
|
+
const token = cardActionValue(event)?.["tk"];
|
|
132143
|
+
return typeof token === "string" && token.length > 0 ? token : null;
|
|
132144
|
+
};
|
|
132145
|
+
var cardActionPanel = (event) => {
|
|
132146
|
+
const value = cardActionValue(event);
|
|
132147
|
+
const op = value?.["pn"];
|
|
132148
|
+
if (typeof op !== "string" || op === "") return null;
|
|
132149
|
+
const arg = value?.["a"];
|
|
132150
|
+
const roots = [event, event?.event];
|
|
132151
|
+
const pick = (read) => {
|
|
132152
|
+
for (const root of roots) {
|
|
132153
|
+
if (typeof root !== "object" || root === null) continue;
|
|
132154
|
+
const candidate = read(root);
|
|
132155
|
+
if (typeof candidate === "string" && candidate !== "") return candidate;
|
|
132156
|
+
}
|
|
132157
|
+
return null;
|
|
132158
|
+
};
|
|
132159
|
+
const context = (root) => typeof root["context"] === "object" && root["context"] !== null ? root["context"] : void 0;
|
|
132160
|
+
const cardId = pick((root) => context(root)?.["open_message_id"] ?? root["open_message_id"]);
|
|
132161
|
+
const chatId = pick((root) => context(root)?.["open_chat_id"] ?? root["open_chat_id"]);
|
|
132162
|
+
if (cardId === null || chatId === null) return null;
|
|
132163
|
+
return { op, arg: typeof arg === "string" ? arg : null, cardId, chatId };
|
|
132164
|
+
};
|
|
132110
132165
|
var cardActionOperator = (event) => {
|
|
132111
132166
|
const roots = [event, event?.event];
|
|
132112
132167
|
for (const root of roots) {
|
|
@@ -132127,6 +132182,11 @@ var startLarkInbound = (input) => {
|
|
|
132127
132182
|
// 卡片点击走的也是 event 帧(实测过),所以在同一个 dispatcher 上注册就行,不用开
|
|
132128
132183
|
// webhook。这条不需要 @:点按钮直接推给应用。
|
|
132129
132184
|
"card.action.trigger": (event) => {
|
|
132185
|
+
const panel = cardActionPanel(event);
|
|
132186
|
+
if (panel !== null) {
|
|
132187
|
+
const notice2 = input.onPanelClick?.(panel, cardActionOperator(event)) ?? null;
|
|
132188
|
+
return notice2 === null ? {} : { toast: { type: "info", content: notice2 } };
|
|
132189
|
+
}
|
|
132130
132190
|
const token = cardActionToken(event);
|
|
132131
132191
|
if (token === null) return {};
|
|
132132
132192
|
const notice = input.onCardAction(token, cardActionOperator(event));
|
|
@@ -132185,6 +132245,13 @@ var makeLarkAdapter = (deps) => {
|
|
|
132185
132245
|
appSecret,
|
|
132186
132246
|
onError: handlers.onError,
|
|
132187
132247
|
onCardAction: handlers.onApproval,
|
|
132248
|
+
onPanelClick: (click, operatorOpenId) => handlers.onPanelClick?.({
|
|
132249
|
+
chatId: click.chatId,
|
|
132250
|
+
cardId: click.cardId,
|
|
132251
|
+
operatorId: operatorOpenId,
|
|
132252
|
+
op: click.op,
|
|
132253
|
+
arg: click.arg
|
|
132254
|
+
}) ?? null,
|
|
132188
132255
|
onMessage: (message) => {
|
|
132189
132256
|
handlers.onMessage({
|
|
132190
132257
|
messageId: message.messageId,
|
|
@@ -132263,6 +132330,22 @@ var makeLarkAdapter = (deps) => {
|
|
|
132263
132330
|
});
|
|
132264
132331
|
}
|
|
132265
132332
|
},
|
|
132333
|
+
panel: {
|
|
132334
|
+
open: async (binding, card) => sendCard({
|
|
132335
|
+
http: deps.http,
|
|
132336
|
+
token: await tenantToken(binding),
|
|
132337
|
+
chatId: binding.chatId,
|
|
132338
|
+
card: buildPanelCard(card)
|
|
132339
|
+
}),
|
|
132340
|
+
update: async (binding, cardId, card) => {
|
|
132341
|
+
await patchCard({
|
|
132342
|
+
http: deps.http,
|
|
132343
|
+
token: await tenantToken(binding),
|
|
132344
|
+
messageId: cardId,
|
|
132345
|
+
card: buildPanelCard(card)
|
|
132346
|
+
});
|
|
132347
|
+
}
|
|
132348
|
+
},
|
|
132266
132349
|
acknowledge: async (binding, messageId) => addReaction({
|
|
132267
132350
|
http: deps.http,
|
|
132268
132351
|
token: await tenantToken(binding),
|
|
@@ -132425,7 +132508,8 @@ var en_default = {
|
|
|
132425
132508
|
sessions: "\xB7 /sessions \u2014 list the sessions this bot opened for this group's project",
|
|
132426
132509
|
resume: "\xB7 /resume number \u2014 switch to one of them (a number from /sessions, or the start of its id)",
|
|
132427
132510
|
model: "\xB7 /model \u2014 see the model this group uses; /model number or /model instance/model switches it",
|
|
132428
|
-
plan: "\xB7 /plan \u2014 toggle plan mode (the agent proposes a plan instead of acting); /plan on, /plan off set it"
|
|
132511
|
+
plan: "\xB7 /plan \u2014 toggle plan mode (the agent proposes a plan instead of acting); /plan on, /plan off set it",
|
|
132512
|
+
panel: "Send /panel to open the command panel with a button for every command."
|
|
132429
132513
|
},
|
|
132430
132514
|
sessions: {
|
|
132431
132515
|
title: "Sessions this bot opened for this group's project ({{count}}):",
|
|
@@ -132498,6 +132582,48 @@ var en_default = {
|
|
|
132498
132582
|
chosen: "Chosen: {{label}}",
|
|
132499
132583
|
elsewhere: "Already handled elsewhere (the web app or another client)."
|
|
132500
132584
|
}
|
|
132585
|
+
},
|
|
132586
|
+
panel: {
|
|
132587
|
+
title: "Command panel",
|
|
132588
|
+
hint: "A button does the same as typing its command. You can still type.",
|
|
132589
|
+
category: {
|
|
132590
|
+
session: "Session",
|
|
132591
|
+
target: "Send to",
|
|
132592
|
+
system: "Settings and status"
|
|
132593
|
+
},
|
|
132594
|
+
cmd: {
|
|
132595
|
+
new: "New session",
|
|
132596
|
+
stop: "Stop this turn",
|
|
132597
|
+
sessions: "Sessions",
|
|
132598
|
+
team: "Send to a team",
|
|
132599
|
+
thread: "Normal session",
|
|
132600
|
+
model: "Model",
|
|
132601
|
+
planOn: "Plan mode: on",
|
|
132602
|
+
planOff: "Plan mode: off",
|
|
132603
|
+
status: "Status",
|
|
132604
|
+
help: "Text help"
|
|
132605
|
+
},
|
|
132606
|
+
back: "Back",
|
|
132607
|
+
prev: "Previous",
|
|
132608
|
+
next: "Next",
|
|
132609
|
+
page: "Page {{page}} of {{total}}",
|
|
132610
|
+
loading: "Loading\u2026",
|
|
132611
|
+
working: "Working on it\u2026",
|
|
132612
|
+
expired: "This panel has expired. Send /panel again.",
|
|
132613
|
+
busy: "The last tap is still being handled. Give it a moment.",
|
|
132614
|
+
loadFailed: "Could not read that just now. Try again in a moment.",
|
|
132615
|
+
sessions: {
|
|
132616
|
+
title: "Pick a session",
|
|
132617
|
+
hint: "Tap one to switch to it."
|
|
132618
|
+
},
|
|
132619
|
+
teams: {
|
|
132620
|
+
title: "Pick a team",
|
|
132621
|
+
hint: "Tap a team and the next messages go to it.",
|
|
132622
|
+
unavailable: "Could not read the team list."
|
|
132623
|
+
},
|
|
132624
|
+
models: {
|
|
132625
|
+
title: "Pick a model"
|
|
132626
|
+
}
|
|
132501
132627
|
}
|
|
132502
132628
|
};
|
|
132503
132629
|
|
|
@@ -132632,7 +132758,8 @@ var zh_CN_default = {
|
|
|
132632
132758
|
sessions: "\xB7 /sessions \u2014\u2014 \u5217\u51FA\u672C\u673A\u5668\u4EBA\u4E3A\u8FD9\u4E2A\u7FA4\u7684\u9879\u76EE\u5F00\u8FC7\u7684\u4F1A\u8BDD",
|
|
132633
132759
|
resume: "\xB7 /resume \u7F16\u53F7 \u2014\u2014 \u5207\u5230\u5176\u4E2D\u4E00\u6761\uFF08/sessions \u91CC\u7684\u7F16\u53F7\uFF0C\u6216\u4F1A\u8BDD id \u7684\u5F00\u5934\uFF09",
|
|
132634
132760
|
model: "\xB7 /model \u2014\u2014 \u770B\u8FD9\u4E2A\u7FA4\u7528\u7684\u6A21\u578B\uFF1B/model \u7F16\u53F7 \u6216 /model \u5B9E\u4F8B/\u6A21\u578B \u5207\u6362",
|
|
132635
|
-
plan: "\xB7 /plan \u2014\u2014 \u5F00\u5173\u8BA1\u5212\u6A21\u5F0F\uFF08agent \u5148\u51FA\u8BA1\u5212\u3001\u4E0D\u52A8\u624B\uFF09\uFF1B/plan on\u3001/plan off \u660E\u786E\u8BBE\u5B9A"
|
|
132761
|
+
plan: "\xB7 /plan \u2014\u2014 \u5F00\u5173\u8BA1\u5212\u6A21\u5F0F\uFF08agent \u5148\u51FA\u8BA1\u5212\u3001\u4E0D\u52A8\u624B\uFF09\uFF1B/plan on\u3001/plan off \u660E\u786E\u8BBE\u5B9A",
|
|
132762
|
+
panel: "\u53D1 /panel \u6253\u5F00\u53EF\u70B9\u7684\u547D\u4EE4\u9762\u677F\u3002"
|
|
132636
132763
|
},
|
|
132637
132764
|
sessions: {
|
|
132638
132765
|
title: "\u672C\u673A\u5668\u4EBA\u4E3A\u8FD9\u4E2A\u7FA4\u7684\u9879\u76EE\u5F00\u8FC7\u7684\u4F1A\u8BDD\uFF08{{count}} \u6761\uFF09\uFF1A",
|
|
@@ -132705,6 +132832,48 @@ var zh_CN_default = {
|
|
|
132705
132832
|
chosen: "\u5DF2\u9009\uFF1A{{label}}",
|
|
132706
132833
|
elsewhere: "\u5DF2\u5728\u522B\u5904\u5904\u7406\uFF08\u7F51\u9875\u6216\u5176\u4ED6\u5BA2\u6237\u7AEF\uFF09\u3002"
|
|
132707
132834
|
}
|
|
132835
|
+
},
|
|
132836
|
+
panel: {
|
|
132837
|
+
title: "\u547D\u4EE4\u9762\u677F",
|
|
132838
|
+
hint: "\u70B9\u6309\u94AE\u7B49\u4E8E\u6253\u51FA\u5BF9\u5E94\u7684\u547D\u4EE4\uFF0C\u4E5F\u53EF\u4EE5\u76F4\u63A5\u6253\u5B57\u3002",
|
|
132839
|
+
category: {
|
|
132840
|
+
session: "\u4F1A\u8BDD",
|
|
132841
|
+
target: "\u6D3E\u7ED9\u8C01",
|
|
132842
|
+
system: "\u8BBE\u7F6E\u4E0E\u72B6\u6001"
|
|
132843
|
+
},
|
|
132844
|
+
cmd: {
|
|
132845
|
+
new: "\u65B0\u4F1A\u8BDD",
|
|
132846
|
+
stop: "\u505C\u6B62\u672C\u8F6E",
|
|
132847
|
+
sessions: "\u4F1A\u8BDD\u5217\u8868",
|
|
132848
|
+
team: "\u6D3E\u7ED9\u56E2\u961F",
|
|
132849
|
+
thread: "\u666E\u901A\u4F1A\u8BDD",
|
|
132850
|
+
model: "\u6A21\u578B",
|
|
132851
|
+
planOn: "\u8BA1\u5212\u6A21\u5F0F\uFF1A\u5F00",
|
|
132852
|
+
planOff: "\u8BA1\u5212\u6A21\u5F0F\uFF1A\u5173",
|
|
132853
|
+
status: "\u72B6\u6001",
|
|
132854
|
+
help: "\u6587\u5B57\u8BF4\u660E"
|
|
132855
|
+
},
|
|
132856
|
+
back: "\u8FD4\u56DE",
|
|
132857
|
+
prev: "\u4E0A\u4E00\u9875",
|
|
132858
|
+
next: "\u4E0B\u4E00\u9875",
|
|
132859
|
+
page: "\u7B2C {{page}} / {{total}} \u9875",
|
|
132860
|
+
loading: "\u52A0\u8F7D\u4E2D\u2026",
|
|
132861
|
+
working: "\u5904\u7406\u4E2D\u2026",
|
|
132862
|
+
expired: "\u9762\u677F\u5DF2\u8FC7\u671F\uFF0C\u518D\u53D1\u4E00\u6B21 /panel\u3002",
|
|
132863
|
+
busy: "\u4E0A\u4E00\u6B65\u8FD8\u5728\u5904\u7406\uFF0C\u7A0D\u7B49\u4E00\u4E0B\u3002",
|
|
132864
|
+
loadFailed: "\u521A\u624D\u6CA1\u8BFB\u51FA\u6765\uFF0C\u7A0D\u540E\u518D\u8BD5\u3002",
|
|
132865
|
+
sessions: {
|
|
132866
|
+
title: "\u9009\u4E00\u6761\u4F1A\u8BDD",
|
|
132867
|
+
hint: "\u70B9\u4E00\u6761\u5207\u8FC7\u53BB\u3002"
|
|
132868
|
+
},
|
|
132869
|
+
teams: {
|
|
132870
|
+
title: "\u9009\u4E00\u652F\u961F",
|
|
132871
|
+
hint: "\u70B9\u4E00\u652F\u961F\uFF0C\u63A5\u4E0B\u6765\u7684\u6D3B\u6D3E\u7ED9\u5B83\u3002",
|
|
132872
|
+
unavailable: "\u8BFB\u4E0D\u5230\u56E2\u961F\u5217\u8868\u3002"
|
|
132873
|
+
},
|
|
132874
|
+
models: {
|
|
132875
|
+
title: "\u9009\u6A21\u578B"
|
|
132876
|
+
}
|
|
132708
132877
|
}
|
|
132709
132878
|
};
|
|
132710
132879
|
|
|
@@ -132845,18 +133014,401 @@ var pickSession = (arg, listed, rows) => {
|
|
|
132845
133014
|
return matches.length === 1 ? matches[0]?.threadId ?? null : null;
|
|
132846
133015
|
};
|
|
132847
133016
|
|
|
133017
|
+
// src/panelCommands.ts
|
|
133018
|
+
var PANEL_CATEGORIES = ["session", "target", "system"];
|
|
133019
|
+
var PANEL_COMMANDS = [
|
|
133020
|
+
{
|
|
133021
|
+
id: "new",
|
|
133022
|
+
category: "session",
|
|
133023
|
+
line: "/new",
|
|
133024
|
+
kind: "new",
|
|
133025
|
+
labelKey: "panel.cmd.new",
|
|
133026
|
+
action: { op: "run", arg: "new" }
|
|
133027
|
+
},
|
|
133028
|
+
{
|
|
133029
|
+
id: "stop",
|
|
133030
|
+
category: "session",
|
|
133031
|
+
line: "/stop",
|
|
133032
|
+
kind: "stop",
|
|
133033
|
+
labelKey: "panel.cmd.stop",
|
|
133034
|
+
action: { op: "run", arg: "stop" }
|
|
133035
|
+
},
|
|
133036
|
+
{
|
|
133037
|
+
id: "sessions",
|
|
133038
|
+
category: "session",
|
|
133039
|
+
line: "/sessions",
|
|
133040
|
+
kind: "sessions",
|
|
133041
|
+
labelKey: "panel.cmd.sessions",
|
|
133042
|
+
action: { op: "open", arg: "sessions" }
|
|
133043
|
+
},
|
|
133044
|
+
{
|
|
133045
|
+
id: "team",
|
|
133046
|
+
category: "target",
|
|
133047
|
+
line: "/team",
|
|
133048
|
+
kind: "showTargets",
|
|
133049
|
+
labelKey: "panel.cmd.team",
|
|
133050
|
+
action: { op: "open", arg: "teams" }
|
|
133051
|
+
},
|
|
133052
|
+
{
|
|
133053
|
+
id: "thread",
|
|
133054
|
+
category: "target",
|
|
133055
|
+
line: "/thread",
|
|
133056
|
+
kind: "selectThread",
|
|
133057
|
+
labelKey: "panel.cmd.thread",
|
|
133058
|
+
action: { op: "run", arg: "thread" }
|
|
133059
|
+
},
|
|
133060
|
+
{
|
|
133061
|
+
id: "model",
|
|
133062
|
+
category: "system",
|
|
133063
|
+
line: "/model",
|
|
133064
|
+
kind: "model",
|
|
133065
|
+
labelKey: "panel.cmd.model",
|
|
133066
|
+
action: { op: "open", arg: "models" }
|
|
133067
|
+
},
|
|
133068
|
+
{
|
|
133069
|
+
id: "plan",
|
|
133070
|
+
category: "system",
|
|
133071
|
+
line: "/plan",
|
|
133072
|
+
kind: "plan",
|
|
133073
|
+
// 实际标签随当前状态在「开 / 关」间翻,见控制器;这里放关着时的那个。
|
|
133074
|
+
labelKey: "panel.cmd.planOff",
|
|
133075
|
+
action: { op: "run", arg: "plan" }
|
|
133076
|
+
},
|
|
133077
|
+
{
|
|
133078
|
+
id: "status",
|
|
133079
|
+
category: "system",
|
|
133080
|
+
line: "/status",
|
|
133081
|
+
kind: "status",
|
|
133082
|
+
labelKey: "panel.cmd.status",
|
|
133083
|
+
action: { op: "run", arg: "status" }
|
|
133084
|
+
},
|
|
133085
|
+
{
|
|
133086
|
+
id: "help",
|
|
133087
|
+
category: "system",
|
|
133088
|
+
// 这个按钮发的是文字版命令列表,不是又开一张面板。
|
|
133089
|
+
line: "/help",
|
|
133090
|
+
kind: "help",
|
|
133091
|
+
labelKey: "panel.cmd.help",
|
|
133092
|
+
action: { op: "run", arg: "help" }
|
|
133093
|
+
}
|
|
133094
|
+
];
|
|
133095
|
+
var PANEL_RUN_LINES = Object.fromEntries(
|
|
133096
|
+
PANEL_COMMANDS.filter((spec) => spec.action.op === "run" && spec.id !== "help").map((spec) => [
|
|
133097
|
+
spec.id,
|
|
133098
|
+
spec.line
|
|
133099
|
+
])
|
|
133100
|
+
);
|
|
133101
|
+
|
|
133102
|
+
// src/panelController.ts
|
|
133103
|
+
var PANEL_PAGE_SIZE = 8;
|
|
133104
|
+
var PANEL_CARD_LIMIT = 200;
|
|
133105
|
+
var LABEL_MAX_CHARS = 60;
|
|
133106
|
+
var PICK_KINDS = /* @__PURE__ */ new Set(["sessions", "teams", "models"]);
|
|
133107
|
+
var clip = (label) => {
|
|
133108
|
+
const chars = Array.from(label);
|
|
133109
|
+
return chars.length <= LABEL_MAX_CHARS ? label : `${chars.slice(0, LABEL_MAX_CHARS - 1).join("")}\u2026`;
|
|
133110
|
+
};
|
|
133111
|
+
var pageCount = (total) => Math.max(1, Math.ceil(total / PANEL_PAGE_SIZE));
|
|
133112
|
+
var makePanelController = (deps) => {
|
|
133113
|
+
const { t } = deps;
|
|
133114
|
+
const cards = /* @__PURE__ */ new Map();
|
|
133115
|
+
const tails = /* @__PURE__ */ new Map();
|
|
133116
|
+
const post = (cardId, job) => {
|
|
133117
|
+
const run = (tails.get(cardId) ?? Promise.resolve()).then(job).catch((error) => deps.warn("panel could not update a card", error)).then(() => {
|
|
133118
|
+
if (tails.get(cardId) === run) tails.delete(cardId);
|
|
133119
|
+
});
|
|
133120
|
+
tails.set(cardId, run);
|
|
133121
|
+
return run;
|
|
133122
|
+
};
|
|
133123
|
+
const remember = (entry) => {
|
|
133124
|
+
cards.delete(entry.cardId);
|
|
133125
|
+
cards.set(entry.cardId, entry);
|
|
133126
|
+
if (cards.size > PANEL_CARD_LIMIT) {
|
|
133127
|
+
const oldest = cards.keys().next().value;
|
|
133128
|
+
if (oldest !== void 0) cards.delete(oldest);
|
|
133129
|
+
}
|
|
133130
|
+
};
|
|
133131
|
+
const button = (label, op, arg, danger) => ({
|
|
133132
|
+
label: clip(label),
|
|
133133
|
+
action: { op, arg },
|
|
133134
|
+
...danger === true ? { danger } : {}
|
|
133135
|
+
});
|
|
133136
|
+
const busyCard = (title, text2) => ({
|
|
133137
|
+
title,
|
|
133138
|
+
tone: "busy",
|
|
133139
|
+
text: text2,
|
|
133140
|
+
groups: [],
|
|
133141
|
+
nav: []
|
|
133142
|
+
});
|
|
133143
|
+
const viewTitle = (kind) => t(`panel.${kind}.title`);
|
|
133144
|
+
const menuCard = (chatKey) => ({
|
|
133145
|
+
title: t("panel.title"),
|
|
133146
|
+
tone: "menu",
|
|
133147
|
+
text: t("panel.hint"),
|
|
133148
|
+
groups: PANEL_CATEGORIES.map(
|
|
133149
|
+
(category) => ({
|
|
133150
|
+
heading: t(`panel.category.${category}`),
|
|
133151
|
+
buttons: PANEL_COMMANDS.filter((spec) => spec.category === category).map(
|
|
133152
|
+
(spec) => button(
|
|
133153
|
+
// 计划模式的按钮把当前状态写在标签上,点了之后标签跟着翻。
|
|
133154
|
+
spec.id === "plan" ? t(deps.planOn(chatKey) ? "panel.cmd.planOn" : "panel.cmd.planOff") : t(spec.labelKey),
|
|
133155
|
+
spec.action.op,
|
|
133156
|
+
spec.action.arg,
|
|
133157
|
+
spec.id === "stop"
|
|
133158
|
+
)
|
|
133159
|
+
)
|
|
133160
|
+
})
|
|
133161
|
+
),
|
|
133162
|
+
nav: []
|
|
133163
|
+
});
|
|
133164
|
+
const pickerCard = (input) => {
|
|
133165
|
+
const total = pageCount(input.items.length);
|
|
133166
|
+
const page = Math.min(Math.max(input.page, 0), total - 1);
|
|
133167
|
+
const shown = input.items.slice(page * PANEL_PAGE_SIZE, (page + 1) * PANEL_PAGE_SIZE);
|
|
133168
|
+
const nav = [];
|
|
133169
|
+
if (input.depth > 1) nav.push(button(t("panel.back"), "back"));
|
|
133170
|
+
if (page > 0) nav.push(button(t("panel.prev"), "page", String(page - 1)));
|
|
133171
|
+
if (page < total - 1) nav.push(button(t("panel.next"), "page", String(page + 1)));
|
|
133172
|
+
const pageNote = total > 1 ? t("panel.page", { page: page + 1, total }) : "";
|
|
133173
|
+
return {
|
|
133174
|
+
title: viewTitle(input.kind),
|
|
133175
|
+
tone: "menu",
|
|
133176
|
+
text: [input.text, pageNote].filter((part) => part !== "").join("\n"),
|
|
133177
|
+
groups: shown.map((item) => ({ heading: null, buttons: [item] })),
|
|
133178
|
+
nav
|
|
133179
|
+
};
|
|
133180
|
+
};
|
|
133181
|
+
const render = (entry) => {
|
|
133182
|
+
const view = entry.stack.at(-1) ?? { kind: "menu" };
|
|
133183
|
+
const depth = entry.stack.length;
|
|
133184
|
+
switch (view.kind) {
|
|
133185
|
+
case "menu":
|
|
133186
|
+
return menuCard(entry.chatKey);
|
|
133187
|
+
case "notice":
|
|
133188
|
+
return {
|
|
133189
|
+
title: view.title,
|
|
133190
|
+
tone: "notice",
|
|
133191
|
+
text: view.text,
|
|
133192
|
+
groups: [],
|
|
133193
|
+
nav: depth > 1 ? [button(t("panel.back"), "back")] : []
|
|
133194
|
+
};
|
|
133195
|
+
case "sessions": {
|
|
133196
|
+
const now = deps.now();
|
|
133197
|
+
return pickerCard({
|
|
133198
|
+
kind: "sessions",
|
|
133199
|
+
page: view.page,
|
|
133200
|
+
depth,
|
|
133201
|
+
text: view.rows.length === 0 ? t("sessions.empty") : t("panel.sessions.hint"),
|
|
133202
|
+
items: view.rows.map(
|
|
133203
|
+
(row, index) => button(sessionLine(row, index + 1, now, t), "pick", `sessions:${row.threadId}`)
|
|
133204
|
+
)
|
|
133205
|
+
});
|
|
133206
|
+
}
|
|
133207
|
+
case "teams":
|
|
133208
|
+
return pickerCard({
|
|
133209
|
+
kind: "teams",
|
|
133210
|
+
page: view.page,
|
|
133211
|
+
depth,
|
|
133212
|
+
text: view.teams === null ? t("panel.teams.unavailable") : view.teams.length === 0 ? t("command.noTeams") : t("panel.teams.hint"),
|
|
133213
|
+
items: (view.teams ?? []).map(
|
|
133214
|
+
(team) => button(team.title, "pick", `teams:${team.teamId}`)
|
|
133215
|
+
)
|
|
133216
|
+
});
|
|
133217
|
+
case "models": {
|
|
133218
|
+
const { models } = view;
|
|
133219
|
+
const current = models?.current ?? null;
|
|
133220
|
+
const isCurrent = (choice) => current !== null && choice.instanceId === current.instanceId && choice.model === current.model;
|
|
133221
|
+
return pickerCard({
|
|
133222
|
+
kind: "models",
|
|
133223
|
+
page: view.page,
|
|
133224
|
+
depth,
|
|
133225
|
+
text: models === null || models.choices.length === 0 ? t("model.unavailable") : current === null ? t("model.currentUnknown") : t("model.current", {
|
|
133226
|
+
model: modelLabel(
|
|
133227
|
+
models.choices.find(isCurrent) ?? {
|
|
133228
|
+
instanceName: current.instanceId,
|
|
133229
|
+
modelName: current.model
|
|
133230
|
+
}
|
|
133231
|
+
)
|
|
133232
|
+
}),
|
|
133233
|
+
items: (models?.choices ?? []).map(
|
|
133234
|
+
(choice) => button(
|
|
133235
|
+
`${isCurrent(choice) ? "\u2605 " : ""}${modelLabel(choice)}`,
|
|
133236
|
+
"pick",
|
|
133237
|
+
`models:${choice.instanceId}/${choice.model}`
|
|
133238
|
+
)
|
|
133239
|
+
)
|
|
133240
|
+
});
|
|
133241
|
+
}
|
|
133242
|
+
}
|
|
133243
|
+
};
|
|
133244
|
+
const show = (entry) => post(entry.cardId, async () => {
|
|
133245
|
+
const surface = deps.surface(entry.chatKey);
|
|
133246
|
+
if (surface === null) return;
|
|
133247
|
+
const card = render(entry);
|
|
133248
|
+
try {
|
|
133249
|
+
await surface.update(entry.cardId, card);
|
|
133250
|
+
} catch (error) {
|
|
133251
|
+
deps.warn("panel could not update a card, posting a new one", error);
|
|
133252
|
+
const replacement = await surface.open(card);
|
|
133253
|
+
if (replacement !== null) {
|
|
133254
|
+
cards.delete(entry.cardId);
|
|
133255
|
+
entry.cardId = replacement;
|
|
133256
|
+
remember(entry);
|
|
133257
|
+
}
|
|
133258
|
+
}
|
|
133259
|
+
});
|
|
133260
|
+
const placeholder = (entry, title, text2) => {
|
|
133261
|
+
void post(entry.cardId, async () => {
|
|
133262
|
+
await deps.surface(entry.chatKey)?.update(entry.cardId, busyCard(title, text2));
|
|
133263
|
+
});
|
|
133264
|
+
};
|
|
133265
|
+
const loadView = async (chatKey, kind) => {
|
|
133266
|
+
switch (kind) {
|
|
133267
|
+
case "sessions":
|
|
133268
|
+
return { kind, page: 0, rows: await deps.loadSessions(chatKey) };
|
|
133269
|
+
case "teams":
|
|
133270
|
+
return { kind, page: 0, teams: await deps.loadTeams(chatKey) };
|
|
133271
|
+
case "models":
|
|
133272
|
+
return { kind, page: 0, models: await deps.loadModels(chatKey) };
|
|
133273
|
+
}
|
|
133274
|
+
};
|
|
133275
|
+
const backToMenu = (entry) => {
|
|
133276
|
+
entry.stack = [{ kind: "menu" }];
|
|
133277
|
+
return show(entry);
|
|
133278
|
+
};
|
|
133279
|
+
const guardBusy = (entry, work) => {
|
|
133280
|
+
entry.busy = true;
|
|
133281
|
+
return work().finally(() => {
|
|
133282
|
+
entry.busy = false;
|
|
133283
|
+
});
|
|
133284
|
+
};
|
|
133285
|
+
const operate = (entry, job) => {
|
|
133286
|
+
entry.busy = true;
|
|
133287
|
+
deps.defer(() => {
|
|
133288
|
+
placeholder(entry, t("panel.title"), t("panel.working"));
|
|
133289
|
+
deps.enqueue(
|
|
133290
|
+
entry.chatKey,
|
|
133291
|
+
() => guardBusy(entry, async () => {
|
|
133292
|
+
try {
|
|
133293
|
+
await job();
|
|
133294
|
+
} catch (error) {
|
|
133295
|
+
deps.warn("panel could not run a command", error);
|
|
133296
|
+
}
|
|
133297
|
+
await backToMenu(entry);
|
|
133298
|
+
})
|
|
133299
|
+
);
|
|
133300
|
+
});
|
|
133301
|
+
};
|
|
133302
|
+
const enter = (entry, kind) => {
|
|
133303
|
+
entry.busy = true;
|
|
133304
|
+
deps.defer(() => {
|
|
133305
|
+
placeholder(entry, viewTitle(kind), t("panel.loading"));
|
|
133306
|
+
deps.enqueue(
|
|
133307
|
+
entry.chatKey,
|
|
133308
|
+
() => guardBusy(entry, async () => {
|
|
133309
|
+
try {
|
|
133310
|
+
entry.stack.push(await loadView(entry.chatKey, kind));
|
|
133311
|
+
} catch (error) {
|
|
133312
|
+
deps.warn("panel could not load a view", error);
|
|
133313
|
+
entry.stack.push({
|
|
133314
|
+
kind: "notice",
|
|
133315
|
+
title: viewTitle(kind),
|
|
133316
|
+
text: t("panel.loadFailed")
|
|
133317
|
+
});
|
|
133318
|
+
}
|
|
133319
|
+
await show(entry);
|
|
133320
|
+
})
|
|
133321
|
+
);
|
|
133322
|
+
});
|
|
133323
|
+
};
|
|
133324
|
+
const navigate = (entry, change) => {
|
|
133325
|
+
change();
|
|
133326
|
+
deps.defer(() => void show(entry));
|
|
133327
|
+
};
|
|
133328
|
+
return {
|
|
133329
|
+
/** 开一张新面板卡(`/help`)。发不出就抛,调用方退回文字版。 */
|
|
133330
|
+
open: async (chatKey) => {
|
|
133331
|
+
const surface = deps.surface(chatKey);
|
|
133332
|
+
if (surface === null) throw new Error("this chat has no panel");
|
|
133333
|
+
const entry = { cardId: "", chatKey, stack: [{ kind: "menu" }], busy: false };
|
|
133334
|
+
const cardId = await surface.open(menuCard(chatKey));
|
|
133335
|
+
if (cardId === null) throw new Error("the panel card was sent without an id");
|
|
133336
|
+
entry.cardId = cardId;
|
|
133337
|
+
remember(entry);
|
|
133338
|
+
},
|
|
133339
|
+
/**
|
|
133340
|
+
* 处理一次点击,同步回给点击者的一句提示(null 就是没话说)。取数据、跑命令、改卡都
|
|
133341
|
+
* 在回调应答之后做。
|
|
133342
|
+
*/
|
|
133343
|
+
click: (click) => {
|
|
133344
|
+
const entry = cards.get(click.cardId);
|
|
133345
|
+
if (entry === void 0 || entry.chatKey !== click.chatKey) return t("panel.expired");
|
|
133346
|
+
if (entry.busy) return t("panel.busy");
|
|
133347
|
+
const top = entry.stack.at(-1) ?? { kind: "menu" };
|
|
133348
|
+
const arg = click.arg ?? "";
|
|
133349
|
+
switch (click.op) {
|
|
133350
|
+
case "back":
|
|
133351
|
+
if (entry.stack.length > 1) navigate(entry, () => void entry.stack.pop());
|
|
133352
|
+
return null;
|
|
133353
|
+
case "page": {
|
|
133354
|
+
const page = Number(arg);
|
|
133355
|
+
if ((top.kind === "sessions" || top.kind === "teams" || top.kind === "models") && Number.isInteger(page) && page >= 0) {
|
|
133356
|
+
navigate(entry, () => {
|
|
133357
|
+
entry.stack[entry.stack.length - 1] = { ...top, page };
|
|
133358
|
+
});
|
|
133359
|
+
}
|
|
133360
|
+
return null;
|
|
133361
|
+
}
|
|
133362
|
+
case "open":
|
|
133363
|
+
if (top.kind === "menu" && PICK_KINDS.has(arg)) enter(entry, arg);
|
|
133364
|
+
return null;
|
|
133365
|
+
case "run": {
|
|
133366
|
+
if (top.kind !== "menu") return null;
|
|
133367
|
+
if (arg === "help") {
|
|
133368
|
+
operate(entry, () => deps.sendHelpText(entry.chatKey));
|
|
133369
|
+
return null;
|
|
133370
|
+
}
|
|
133371
|
+
const line = PANEL_RUN_LINES[arg];
|
|
133372
|
+
if (line !== void 0) operate(entry, () => deps.runLine(entry.chatKey, line));
|
|
133373
|
+
return null;
|
|
133374
|
+
}
|
|
133375
|
+
case "pick": {
|
|
133376
|
+
const split = arg.indexOf(":");
|
|
133377
|
+
const key = split < 0 ? "" : arg.slice(split + 1);
|
|
133378
|
+
const line = top.kind === "sessions" && arg.startsWith("sessions:") ? top.rows.find((row) => row.threadId === key) === void 0 ? null : `/resume ${key}` : top.kind === "teams" && arg.startsWith("teams:") ? top.teams?.find((team) => team.teamId === key) === void 0 ? null : `/team ${key}` : top.kind === "models" && arg.startsWith("models:") ? top.models?.choices.find(
|
|
133379
|
+
(choice) => `${choice.instanceId}/${choice.model}` === key
|
|
133380
|
+
) === void 0 ? null : `/model ${key}` : null;
|
|
133381
|
+
if (line === null) {
|
|
133382
|
+
deps.warn("panel ignored a pick that is not in the current view");
|
|
133383
|
+
return null;
|
|
133384
|
+
}
|
|
133385
|
+
operate(entry, () => deps.runLine(entry.chatKey, line));
|
|
133386
|
+
return null;
|
|
133387
|
+
}
|
|
133388
|
+
default:
|
|
133389
|
+
deps.warn(`panel ignored an unknown action: ${click.op}`);
|
|
133390
|
+
return null;
|
|
133391
|
+
}
|
|
133392
|
+
}
|
|
133393
|
+
};
|
|
133394
|
+
};
|
|
133395
|
+
|
|
132848
133396
|
// src/telegramApi.ts
|
|
132849
133397
|
var API_BASE = "https://api.telegram.org";
|
|
132850
133398
|
var POLL_TIMEOUT_SECONDS = 45;
|
|
132851
133399
|
var TELEGRAM_TEXT_MAX_CHARS = 4e3;
|
|
132852
133400
|
var TelegramApiError = class extends Error {
|
|
132853
133401
|
code;
|
|
132854
|
-
|
|
133402
|
+
/** 限流(429)时平台给的等待秒数;别的错误没有。 */
|
|
133403
|
+
retryAfterSeconds;
|
|
133404
|
+
constructor(method, code2, description, retryAfterSeconds) {
|
|
132855
133405
|
super(`telegram ${method} failed (${String(code2 ?? "?")}): ${description}`);
|
|
132856
133406
|
this.name = "TelegramApiError";
|
|
132857
133407
|
this.code = code2;
|
|
133408
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
132858
133409
|
}
|
|
132859
133410
|
};
|
|
133411
|
+
var isNotModified = (error) => error instanceof TelegramApiError && error.code === 400 && error.message.toLowerCase().includes("message is not modified");
|
|
132860
133412
|
var PHOTO_NAME = "photo.jpg";
|
|
132861
133413
|
var PHOTO_MIME = "image/jpeg";
|
|
132862
133414
|
var mimeFromName2 = (name) => {
|
|
@@ -132915,10 +133467,20 @@ var makeTelegramApi = (input) => {
|
|
|
132915
133467
|
});
|
|
132916
133468
|
const envelope = await response.json();
|
|
132917
133469
|
if (envelope.ok !== true) {
|
|
132918
|
-
throw new TelegramApiError(
|
|
133470
|
+
throw new TelegramApiError(
|
|
133471
|
+
method,
|
|
133472
|
+
envelope.error_code,
|
|
133473
|
+
envelope.description ?? "no reason",
|
|
133474
|
+
envelope.parameters?.retry_after
|
|
133475
|
+
);
|
|
132919
133476
|
}
|
|
132920
133477
|
return envelope.result;
|
|
132921
133478
|
};
|
|
133479
|
+
const keyboard = (rows) => ({
|
|
133480
|
+
inline_keyboard: rows.map(
|
|
133481
|
+
(row) => row.map((button) => ({ text: button.text, callback_data: button.data }))
|
|
133482
|
+
)
|
|
133483
|
+
});
|
|
132922
133484
|
return {
|
|
132923
133485
|
/** 机器人自己是谁;群里的命令靠它的 `username` 认出是不是发给自己的。 */
|
|
132924
133486
|
getMe: () => call("getMe", {}),
|
|
@@ -132938,16 +133500,45 @@ var makeTelegramApi = (input) => {
|
|
|
132938
133500
|
await call("sendMessage", { chat_id: chatId, text: chunk });
|
|
132939
133501
|
}
|
|
132940
133502
|
},
|
|
132941
|
-
/**
|
|
132942
|
-
|
|
132943
|
-
|
|
132944
|
-
|
|
132945
|
-
|
|
132946
|
-
|
|
132947
|
-
|
|
132948
|
-
|
|
133503
|
+
/**
|
|
133504
|
+
* 发一条消息,回它的 id。调用方保证 `text` 不超过单条上限(边写边更新的回复要自己
|
|
133505
|
+
* 在上限处翻篇,这里不替它切)。带 `rows` 就挂一块内联键盘。
|
|
133506
|
+
*/
|
|
133507
|
+
sendOne: async (chatId, text2, rows) => {
|
|
133508
|
+
const sent = await call("sendMessage", {
|
|
133509
|
+
chat_id: chatId,
|
|
133510
|
+
text: text2,
|
|
133511
|
+
...rows === void 0 ? {} : { reply_markup: keyboard(rows) }
|
|
133512
|
+
});
|
|
133513
|
+
return sent?.message_id === void 0 ? null : String(sent.message_id);
|
|
133514
|
+
},
|
|
133515
|
+
/**
|
|
133516
|
+
* 改一条已发出的消息。内容没变(`message is not modified`)当成功。`rows` 不给就是
|
|
133517
|
+
* 去掉按钮:改了文字又要保留按钮的调用方得自己再传一次。
|
|
133518
|
+
*/
|
|
133519
|
+
editMessage: async (chatId, messageId, text2, rows = []) => {
|
|
133520
|
+
try {
|
|
133521
|
+
await call("editMessageText", {
|
|
133522
|
+
chat_id: chatId,
|
|
133523
|
+
message_id: Number(messageId),
|
|
133524
|
+
text: text2,
|
|
133525
|
+
reply_markup: keyboard(rows)
|
|
133526
|
+
});
|
|
133527
|
+
} catch (error) {
|
|
133528
|
+
if (!isNotModified(error)) throw error;
|
|
132949
133529
|
}
|
|
132950
|
-
}
|
|
133530
|
+
},
|
|
133531
|
+
/** 带按钮的审批消息,一行一个按钮;回消息 id,之后才改得了它(平台没给就是 null)。 */
|
|
133532
|
+
sendButtons: async (chatId, text2, buttons) => {
|
|
133533
|
+
const sent = await call("sendMessage", {
|
|
133534
|
+
chat_id: chatId,
|
|
133535
|
+
text: text2,
|
|
133536
|
+
reply_markup: keyboard(
|
|
133537
|
+
buttons.map((button) => [{ text: button.label, data: button.token }])
|
|
133538
|
+
)
|
|
133539
|
+
});
|
|
133540
|
+
return sent?.message_id === void 0 ? null : String(sent.message_id);
|
|
133541
|
+
},
|
|
132951
133542
|
/** 答完点击,不然客户端上那个按钮一直转圈。带 `text` 时点击者会看到一句提示。 */
|
|
132952
133543
|
answerCallback: (callbackQueryId, text2) => call("answerCallbackQuery", {
|
|
132953
133544
|
callback_query_id: callbackQueryId,
|
|
@@ -132988,16 +133579,168 @@ var makeTelegramApi = (input) => {
|
|
|
132988
133579
|
};
|
|
132989
133580
|
};
|
|
132990
133581
|
|
|
133582
|
+
// src/telegramPanel.ts
|
|
133583
|
+
var PANEL_DATA_PREFIX = "pn:";
|
|
133584
|
+
var renderPanel = (card, version) => {
|
|
133585
|
+
const actions = [];
|
|
133586
|
+
const rowOf = (buttons) => buttons.map((button) => {
|
|
133587
|
+
actions.push(button.action);
|
|
133588
|
+
return {
|
|
133589
|
+
text: button.label,
|
|
133590
|
+
data: `${PANEL_DATA_PREFIX}${String(version)}:${String(actions.length - 1)}`
|
|
133591
|
+
};
|
|
133592
|
+
});
|
|
133593
|
+
const rows = [];
|
|
133594
|
+
for (const group of card.groups) {
|
|
133595
|
+
if (group.buttons.length > 0) rows.push(rowOf(group.buttons));
|
|
133596
|
+
}
|
|
133597
|
+
if (card.nav.length > 0) rows.push(rowOf(card.nav));
|
|
133598
|
+
return {
|
|
133599
|
+
text: [card.title, card.text].filter((part) => part !== "").join("\n\n"),
|
|
133600
|
+
rows,
|
|
133601
|
+
actions
|
|
133602
|
+
};
|
|
133603
|
+
};
|
|
133604
|
+
var parsePanelData = (data) => {
|
|
133605
|
+
const match = /^pn:(\d+):(\d+)$/.exec(data);
|
|
133606
|
+
if (match === null) return null;
|
|
133607
|
+
return { version: Number(match[1]), index: Number(match[2]) };
|
|
133608
|
+
};
|
|
133609
|
+
|
|
133610
|
+
// src/plainTurnText.ts
|
|
133611
|
+
var RUNNING_LABEL_MAX_CHARS = 200;
|
|
133612
|
+
var clip2 = (text2, max) => {
|
|
133613
|
+
const chars = Array.from(text2);
|
|
133614
|
+
return chars.length <= max ? text2 : `${chars.slice(0, max - 1).join("")}\u2026`;
|
|
133615
|
+
};
|
|
133616
|
+
var turnTextParts = (view, t) => {
|
|
133617
|
+
const names = view.steps.map((kind) => t(`card.step.${kind}`));
|
|
133618
|
+
if (view.omitted > 0) names.unshift(t("card.step.omitted", { count: view.omitted }));
|
|
133619
|
+
const headLines = [view.title === "" ? t("card.untitled") : view.title];
|
|
133620
|
+
if (names.length > 0) headLines.push(names.join(" \u2192 "));
|
|
133621
|
+
if (view.running !== null) headLines.push(`\u{1F527} ${clip2(view.running, RUNNING_LABEL_MAX_CHARS)}`);
|
|
133622
|
+
const body = view.body.trim();
|
|
133623
|
+
const footLines = [];
|
|
133624
|
+
if (view.status === "working") {
|
|
133625
|
+
if (body === "" && names.length === 0) footLines.push(t("card.status.working"));
|
|
133626
|
+
} else {
|
|
133627
|
+
footLines.push(t(`card.status.${view.status}`));
|
|
133628
|
+
if (view.errorText !== null && view.errorText !== "") footLines.push(view.errorText);
|
|
133629
|
+
}
|
|
133630
|
+
return { head: headLines.join("\n"), body, foot: footLines.join("\n") };
|
|
133631
|
+
};
|
|
133632
|
+
var joinParts = (...parts) => parts.filter((part) => part !== "").join("\n\n");
|
|
133633
|
+
|
|
133634
|
+
// src/telegramTurn.ts
|
|
133635
|
+
var makeTurnWriter = (deps) => {
|
|
133636
|
+
let first = deps.resume ?? null;
|
|
133637
|
+
let live = deps.resume ?? null;
|
|
133638
|
+
let liveText = null;
|
|
133639
|
+
let frozen = 0;
|
|
133640
|
+
let consumed = 0;
|
|
133641
|
+
const put = async (text2) => {
|
|
133642
|
+
const bounded = text2.length <= deps.limit ? text2 : text2.slice(0, deps.limit);
|
|
133643
|
+
if (live === null) {
|
|
133644
|
+
const id = await deps.messages.send(bounded);
|
|
133645
|
+
if (id === null) throw new Error("telegram sent a message without an id");
|
|
133646
|
+
live = id;
|
|
133647
|
+
first ??= id;
|
|
133648
|
+
} else if (bounded === liveText) {
|
|
133649
|
+
return;
|
|
133650
|
+
} else {
|
|
133651
|
+
await deps.messages.edit(live, bounded);
|
|
133652
|
+
}
|
|
133653
|
+
liveText = bounded;
|
|
133654
|
+
};
|
|
133655
|
+
return {
|
|
133656
|
+
firstId: () => first,
|
|
133657
|
+
write: async (view) => {
|
|
133658
|
+
const { head, body, foot } = turnTextParts(view, deps.t);
|
|
133659
|
+
consumed = Math.min(consumed, body.length);
|
|
133660
|
+
for (; ; ) {
|
|
133661
|
+
const rest = body.slice(consumed);
|
|
133662
|
+
const prefix = frozen === 0 ? head.slice(0, Math.floor(deps.limit / 2)) : "";
|
|
133663
|
+
const whole = joinParts(prefix, rest, foot);
|
|
133664
|
+
if (whole.length <= deps.limit) {
|
|
133665
|
+
if (whole !== "") await put(whole);
|
|
133666
|
+
return;
|
|
133667
|
+
}
|
|
133668
|
+
const room = deps.limit - prefix.length - 2;
|
|
133669
|
+
const chunk = splitMessage(rest, room)[0] ?? "";
|
|
133670
|
+
if (chunk === "") {
|
|
133671
|
+
await put(whole);
|
|
133672
|
+
return;
|
|
133673
|
+
}
|
|
133674
|
+
await put(joinParts(prefix, chunk));
|
|
133675
|
+
frozen += 1;
|
|
133676
|
+
consumed += chunk.length;
|
|
133677
|
+
consumed += body.slice(consumed).length - body.slice(consumed).trimStart().length;
|
|
133678
|
+
live = null;
|
|
133679
|
+
liveText = null;
|
|
133680
|
+
}
|
|
133681
|
+
}
|
|
133682
|
+
};
|
|
133683
|
+
};
|
|
133684
|
+
|
|
132991
133685
|
// src/telegramAdapter.ts
|
|
132992
133686
|
var COLUMNS2 = [
|
|
132993
133687
|
{ key: "telegramToken", kind: "password", labelKey: "column.telegramToken.label" }
|
|
132994
133688
|
];
|
|
132995
133689
|
var BACKOFF_START_MS = 1e3;
|
|
132996
133690
|
var BACKOFF_MAX_MS = 6e4;
|
|
133691
|
+
var TELEGRAM_CARD_THROTTLE_MS = 1200;
|
|
133692
|
+
var FLOOD_WAIT_MAX_SECONDS = 30;
|
|
133693
|
+
var REMEMBERED_CARD_LIMIT = 200;
|
|
132997
133694
|
var tokenOf = (binding) => binding.values["telegramToken"] ?? "";
|
|
132998
133695
|
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
132999
133696
|
var makeTelegramAdapter = (deps) => {
|
|
133000
133697
|
const apis = /* @__PURE__ */ new Map();
|
|
133698
|
+
const sleep = deps.sleep ?? defaultSleep;
|
|
133699
|
+
const t = deps.translate;
|
|
133700
|
+
const withFloodWait = async (run) => {
|
|
133701
|
+
try {
|
|
133702
|
+
return await run();
|
|
133703
|
+
} catch (error) {
|
|
133704
|
+
if (error instanceof TelegramApiError && error.code === 429 && error.retryAfterSeconds !== void 0 && error.retryAfterSeconds <= FLOOD_WAIT_MAX_SECONDS) {
|
|
133705
|
+
await sleep(error.retryAfterSeconds * 1e3);
|
|
133706
|
+
return run();
|
|
133707
|
+
}
|
|
133708
|
+
throw error;
|
|
133709
|
+
}
|
|
133710
|
+
};
|
|
133711
|
+
const remembering = () => {
|
|
133712
|
+
const entries = /* @__PURE__ */ new Map();
|
|
133713
|
+
return {
|
|
133714
|
+
get: (key) => entries.get(key),
|
|
133715
|
+
set: (key, value) => {
|
|
133716
|
+
entries.delete(key);
|
|
133717
|
+
entries.set(key, value);
|
|
133718
|
+
if (entries.size > REMEMBERED_CARD_LIMIT) {
|
|
133719
|
+
const oldest = entries.keys().next().value;
|
|
133720
|
+
if (oldest !== void 0) entries.delete(oldest);
|
|
133721
|
+
}
|
|
133722
|
+
},
|
|
133723
|
+
delete: (key) => entries.delete(key)
|
|
133724
|
+
};
|
|
133725
|
+
};
|
|
133726
|
+
const turnWriters = remembering();
|
|
133727
|
+
const panelKeyboards = remembering();
|
|
133728
|
+
const panelClick = (callback, operatorId, handlers) => {
|
|
133729
|
+
const messageId = callback.message?.message_id;
|
|
133730
|
+
const chatId = callback.message?.chat.id;
|
|
133731
|
+
if (messageId === void 0 || chatId === void 0) return t("panel.expired");
|
|
133732
|
+
const keyboard = panelKeyboards.get(`${String(chatId)}:${String(messageId)}`);
|
|
133733
|
+
const parsed = parsePanelData(callback.data ?? "");
|
|
133734
|
+
const action = keyboard === void 0 || parsed === null || parsed.version !== keyboard.version ? void 0 : keyboard.actions[parsed.index];
|
|
133735
|
+
if (action === void 0) return t("panel.expired");
|
|
133736
|
+
return handlers.onPanelClick?.({
|
|
133737
|
+
chatId: String(chatId),
|
|
133738
|
+
cardId: String(messageId),
|
|
133739
|
+
operatorId,
|
|
133740
|
+
op: action.op,
|
|
133741
|
+
arg: action.arg ?? null
|
|
133742
|
+
}) ?? null;
|
|
133743
|
+
};
|
|
133001
133744
|
const apiFor = async (binding) => {
|
|
133002
133745
|
const existing = apis.get(binding.rowId);
|
|
133003
133746
|
if (existing !== void 0) return existing;
|
|
@@ -133006,6 +133749,15 @@ var makeTelegramAdapter = (deps) => {
|
|
|
133006
133749
|
apis.set(binding.rowId, api);
|
|
133007
133750
|
return api;
|
|
133008
133751
|
};
|
|
133752
|
+
const makeWriter = (api, chatId, resume) => makeTurnWriter({
|
|
133753
|
+
t,
|
|
133754
|
+
limit: TELEGRAM_TEXT_MAX_CHARS,
|
|
133755
|
+
...resume === void 0 ? {} : { resume },
|
|
133756
|
+
messages: {
|
|
133757
|
+
send: (text2) => withFloodWait(() => api.sendOne(chatId, text2)),
|
|
133758
|
+
edit: (messageId, text2) => withFloodWait(() => api.editMessage(chatId, messageId, text2))
|
|
133759
|
+
}
|
|
133760
|
+
});
|
|
133009
133761
|
return {
|
|
133010
133762
|
platform: "telegram",
|
|
133011
133763
|
icon: "telegram",
|
|
@@ -133033,10 +133785,8 @@ var makeTelegramAdapter = (deps) => {
|
|
|
133033
133785
|
for (const update of updates) {
|
|
133034
133786
|
const callback = update.callback_query;
|
|
133035
133787
|
if (callback?.data !== void 0) {
|
|
133036
|
-
const
|
|
133037
|
-
|
|
133038
|
-
callback.from === void 0 ? null : String(callback.from.id)
|
|
133039
|
-
);
|
|
133788
|
+
const operatorId = callback.from === void 0 ? null : String(callback.from.id);
|
|
133789
|
+
const notice = callback.data.startsWith(PANEL_DATA_PREFIX) ? panelClick(callback, operatorId, handlers) : handlers.onApproval(callback.data, operatorId);
|
|
133040
133790
|
await api.answerCallback(callback.id, notice ?? void 0).catch(() => void 0);
|
|
133041
133791
|
continue;
|
|
133042
133792
|
}
|
|
@@ -133089,8 +133839,72 @@ var makeTelegramAdapter = (deps) => {
|
|
|
133089
133839
|
},
|
|
133090
133840
|
askApproval: async (binding, request) => {
|
|
133091
133841
|
const api = await apiFor(binding);
|
|
133092
|
-
|
|
133093
|
-
|
|
133842
|
+
return api.sendButtons(binding.chatId, request.body, request.options);
|
|
133843
|
+
},
|
|
133844
|
+
settleApproval: async (binding, cardId, settlement) => {
|
|
133845
|
+
const api = await apiFor(binding);
|
|
133846
|
+
const line = settlement.kind === "chosen" ? t("card.approval.chosen", { label: settlement.label }) : t("card.approval.elsewhere");
|
|
133847
|
+
await withFloodWait(
|
|
133848
|
+
() => api.editMessage(
|
|
133849
|
+
binding.chatId,
|
|
133850
|
+
cardId,
|
|
133851
|
+
`${t("card.approval.doneTitle")}
|
|
133852
|
+
${line}`
|
|
133853
|
+
)
|
|
133854
|
+
);
|
|
133855
|
+
},
|
|
133856
|
+
turnCards: {
|
|
133857
|
+
throttleMs: TELEGRAM_CARD_THROTTLE_MS,
|
|
133858
|
+
open: async (binding, view) => {
|
|
133859
|
+
const api = await apiFor(binding);
|
|
133860
|
+
const writer = makeWriter(api, binding.chatId);
|
|
133861
|
+
try {
|
|
133862
|
+
await writer.write(view);
|
|
133863
|
+
} catch (error) {
|
|
133864
|
+
if (writer.firstId() === null) throw error;
|
|
133865
|
+
deps.log.warn("telegram could not finish the first card write", {
|
|
133866
|
+
error: error instanceof Error ? error.message : String(error)
|
|
133867
|
+
});
|
|
133868
|
+
}
|
|
133869
|
+
const cardId = writer.firstId();
|
|
133870
|
+
if (cardId !== null && view.status === "working") {
|
|
133871
|
+
turnWriters.set(`${binding.chatId}:${cardId}`, writer);
|
|
133872
|
+
}
|
|
133873
|
+
return cardId;
|
|
133874
|
+
},
|
|
133875
|
+
update: async (binding, cardId, view) => {
|
|
133876
|
+
const key = `${binding.chatId}:${cardId}`;
|
|
133877
|
+
const writer = turnWriters.get(key) ?? makeWriter(await apiFor(binding), binding.chatId, cardId);
|
|
133878
|
+
await writer.write(view);
|
|
133879
|
+
if (view.status !== "working") turnWriters.delete(key);
|
|
133880
|
+
else turnWriters.set(key, writer);
|
|
133881
|
+
}
|
|
133882
|
+
},
|
|
133883
|
+
panel: {
|
|
133884
|
+
open: async (binding, card) => {
|
|
133885
|
+
const api = await apiFor(binding);
|
|
133886
|
+
const rendered = renderPanel(card, 1);
|
|
133887
|
+
const cardId = await withFloodWait(
|
|
133888
|
+
() => api.sendOne(binding.chatId, rendered.text, rendered.rows)
|
|
133889
|
+
);
|
|
133890
|
+
if (cardId !== null) {
|
|
133891
|
+
panelKeyboards.set(`${binding.chatId}:${cardId}`, {
|
|
133892
|
+
version: 1,
|
|
133893
|
+
actions: rendered.actions
|
|
133894
|
+
});
|
|
133895
|
+
}
|
|
133896
|
+
return cardId;
|
|
133897
|
+
},
|
|
133898
|
+
update: async (binding, cardId, card) => {
|
|
133899
|
+
const api = await apiFor(binding);
|
|
133900
|
+
const key = `${binding.chatId}:${cardId}`;
|
|
133901
|
+
const version = (panelKeyboards.get(key)?.version ?? 0) + 1;
|
|
133902
|
+
const rendered = renderPanel(card, version);
|
|
133903
|
+
await withFloodWait(
|
|
133904
|
+
() => api.editMessage(binding.chatId, cardId, rendered.text, rendered.rows)
|
|
133905
|
+
);
|
|
133906
|
+
panelKeyboards.set(key, { version, actions: rendered.actions });
|
|
133907
|
+
}
|
|
133094
133908
|
},
|
|
133095
133909
|
fetchResource: async (binding, input) => {
|
|
133096
133910
|
const api = await apiFor(binding);
|
|
@@ -133360,6 +134174,7 @@ var runWecomClient = (input) => {
|
|
|
133360
134174
|
const nextRequestId = (kind) => `${kind}_${++sequence}`;
|
|
133361
134175
|
const pendingAcks = /* @__PURE__ */ new Map();
|
|
133362
134176
|
const pendingRequests = /* @__PURE__ */ new Map();
|
|
134177
|
+
const openStreams = /* @__PURE__ */ new Map();
|
|
133363
134178
|
const subscribeId = nextRequestId("aibot_subscribe");
|
|
133364
134179
|
let missedPong = 0;
|
|
133365
134180
|
let closed = false;
|
|
@@ -133389,6 +134204,8 @@ var runWecomClient = (input) => {
|
|
|
133389
134204
|
return;
|
|
133390
134205
|
}
|
|
133391
134206
|
const error = typeof frame.errcode === "number" && frame.errcode !== 0 ? new Error(`WeCom ack failed: ${String(frame.errcode)} ${frame.errmsg ?? ""}`) : null;
|
|
134207
|
+
const stream = openStreams.get(requestId);
|
|
134208
|
+
if (stream !== void 0 && error !== null) stream.failed = true;
|
|
133392
134209
|
settleAck(requestId, error);
|
|
133393
134210
|
return;
|
|
133394
134211
|
}
|
|
@@ -133400,6 +134217,7 @@ var runWecomClient = (input) => {
|
|
|
133400
134217
|
if (finished) return;
|
|
133401
134218
|
finished = true;
|
|
133402
134219
|
clearInterval(heartbeat);
|
|
134220
|
+
openStreams.clear();
|
|
133403
134221
|
void input.socket.close().catch(() => void 0);
|
|
133404
134222
|
for (const requestId of pendingAcks.keys()) {
|
|
133405
134223
|
settleAck(requestId, new Error("WeCom connection closed"));
|
|
@@ -133487,8 +134305,38 @@ var runWecomClient = (input) => {
|
|
|
133487
134305
|
}
|
|
133488
134306
|
}
|
|
133489
134307
|
};
|
|
134308
|
+
const openStream = (chatId) => {
|
|
134309
|
+
const requestId = pendingRequests.get(chatId);
|
|
134310
|
+
if (requestId === void 0) return null;
|
|
134311
|
+
pendingRequests.delete(chatId);
|
|
134312
|
+
const id = nextRequestId("stream");
|
|
134313
|
+
const state = { failed: false, finished: false };
|
|
134314
|
+
openStreams.set(requestId, state);
|
|
134315
|
+
return {
|
|
134316
|
+
id,
|
|
134317
|
+
send: async (content, finish2) => {
|
|
134318
|
+
if (state.failed) return false;
|
|
134319
|
+
if (state.finished) return true;
|
|
134320
|
+
if (finish2) state.finished = true;
|
|
134321
|
+
try {
|
|
134322
|
+
await send({
|
|
134323
|
+
cmd: "aibot_respond_msg",
|
|
134324
|
+
headers: { req_id: requestId },
|
|
134325
|
+
body: { msgtype: "stream", stream: { id, finish: finish2, content } }
|
|
134326
|
+
});
|
|
134327
|
+
} catch (error) {
|
|
134328
|
+
state.failed = true;
|
|
134329
|
+
openStreams.delete(requestId);
|
|
134330
|
+
throw error;
|
|
134331
|
+
}
|
|
134332
|
+
if (finish2) openStreams.delete(requestId);
|
|
134333
|
+
return true;
|
|
134334
|
+
}
|
|
134335
|
+
};
|
|
134336
|
+
};
|
|
133490
134337
|
return {
|
|
133491
134338
|
sendText,
|
|
134339
|
+
openStream,
|
|
133492
134340
|
rememberRequest: (chatId, requestId) => {
|
|
133493
134341
|
if (requestId !== null) pendingRequests.set(chatId, requestId);
|
|
133494
134342
|
},
|
|
@@ -133503,6 +134351,8 @@ var runWecomClient = (input) => {
|
|
|
133503
134351
|
};
|
|
133504
134352
|
|
|
133505
134353
|
// src/wecomAdapter.ts
|
|
134354
|
+
var WECOM_CARD_THROTTLE_MS = 400;
|
|
134355
|
+
var LIVE_STREAM_LIMIT = 200;
|
|
133506
134356
|
var BACKOFF_START_MS2 = 1e3;
|
|
133507
134357
|
var BACKOFF_MAX_MS2 = 6e4;
|
|
133508
134358
|
var COLUMNS3 = [
|
|
@@ -133523,6 +134373,36 @@ var optionIndexFrom = (text2, count) => {
|
|
|
133523
134373
|
};
|
|
133524
134374
|
var makeWecomAdapter = (deps) => {
|
|
133525
134375
|
const clients = /* @__PURE__ */ new Map();
|
|
134376
|
+
const t = deps.translate;
|
|
134377
|
+
const liveStreams = /* @__PURE__ */ new Map();
|
|
134378
|
+
const rememberStream = (key, entry) => {
|
|
134379
|
+
liveStreams.set(key, entry);
|
|
134380
|
+
if (liveStreams.size > LIVE_STREAM_LIMIT) {
|
|
134381
|
+
const oldest = liveStreams.keys().next().value;
|
|
134382
|
+
if (oldest !== void 0) liveStreams.delete(oldest);
|
|
134383
|
+
}
|
|
134384
|
+
};
|
|
134385
|
+
const emit = async (entry, view) => {
|
|
134386
|
+
const { head, body, foot } = turnTextParts(view, t);
|
|
134387
|
+
if (view.status === "working") {
|
|
134388
|
+
let shown = body;
|
|
134389
|
+
const whole = joinParts(head, body, foot);
|
|
134390
|
+
if (whole.length > WECOM_TEXT_MAX_CHARS) {
|
|
134391
|
+
const marker = t("card.truncated", { count: 9999999 });
|
|
134392
|
+
const room = WECOM_TEXT_MAX_CHARS - head.length - foot.length - marker.length - 6;
|
|
134393
|
+
const kept = room > 0 ? body.slice(Math.max(0, body.length - room)) : "";
|
|
134394
|
+
shown = `${t("card.truncated", { count: body.length - kept.length })}
|
|
134395
|
+
${kept}`;
|
|
134396
|
+
}
|
|
134397
|
+
await entry.stream.send(joinParts(head, shown, foot), false);
|
|
134398
|
+
return;
|
|
134399
|
+
}
|
|
134400
|
+
const chunks = splitForWecom(joinParts(head, body, foot));
|
|
134401
|
+
const delivered = await entry.stream.send(chunks[0] ?? "", true);
|
|
134402
|
+
for (const chunk of delivered ? chunks.slice(1) : chunks) {
|
|
134403
|
+
await entry.client.sendText(entry.chatId, chunk);
|
|
134404
|
+
}
|
|
134405
|
+
};
|
|
133526
134406
|
return {
|
|
133527
134407
|
platform: "wecom",
|
|
133528
134408
|
icon: "wecom",
|
|
@@ -133621,6 +134501,25 @@ var makeWecomAdapter = (deps) => {
|
|
|
133621
134501
|
await (clients.get(binding.rowId)?.sendText(binding.chatId, lines.join("\n")) ?? Promise.resolve());
|
|
133622
134502
|
return null;
|
|
133623
134503
|
},
|
|
134504
|
+
turnCards: {
|
|
134505
|
+
throttleMs: WECOM_CARD_THROTTLE_MS,
|
|
134506
|
+
open: async (binding, view) => {
|
|
134507
|
+
const client = clients.get(binding.rowId);
|
|
134508
|
+
const stream = client?.openStream(binding.chatId) ?? null;
|
|
134509
|
+
if (client === void 0 || stream === null) return null;
|
|
134510
|
+
const entry = { stream, client, chatId: binding.chatId };
|
|
134511
|
+
await emit(entry, view);
|
|
134512
|
+
if (view.status === "working") rememberStream(`${binding.rowId}:${stream.id}`, entry);
|
|
134513
|
+
return stream.id;
|
|
134514
|
+
},
|
|
134515
|
+
update: async (binding, cardId, view) => {
|
|
134516
|
+
const key = `${binding.rowId}:${cardId}`;
|
|
134517
|
+
const entry = liveStreams.get(key);
|
|
134518
|
+
if (entry === void 0) return;
|
|
134519
|
+
await emit(entry, view);
|
|
134520
|
+
if (view.status !== "working") liveStreams.delete(key);
|
|
134521
|
+
}
|
|
134522
|
+
},
|
|
133624
134523
|
answerByText: (_binding, text2, options) => {
|
|
133625
134524
|
const index = optionIndexFrom(text2, options.length);
|
|
133626
134525
|
return index === null ? null : options[index - 1]?.token ?? null;
|
|
@@ -133674,6 +134573,7 @@ var imPlugin = definePlugin({
|
|
|
133674
134573
|
// Telegram 在不少网络里要绕代理,而绕不绕、绕哪个是按行配的 —— 这是插件面上
|
|
133675
134574
|
// `ctx.proxies` 的第一个真实消费者。
|
|
133676
134575
|
fetchVia: (proxy) => ctx.proxies.fetch(proxy),
|
|
134576
|
+
translate: t,
|
|
133677
134577
|
log: {
|
|
133678
134578
|
info: (message, fields) => ctx.log.info(message, fields),
|
|
133679
134579
|
warn: (message, fields) => ctx.log.warn(message, fields)
|
|
@@ -133682,6 +134582,7 @@ var imPlugin = definePlugin({
|
|
|
133682
134582
|
makeWecomAdapter({
|
|
133683
134583
|
// 企微不需要厂商 SDK:全程 JSON over WebSocket,走插件面那条出网长连接。
|
|
133684
134584
|
connectSocket: (url) => ctx.socket.connect({ url }),
|
|
134585
|
+
translate: t,
|
|
133685
134586
|
log: {
|
|
133686
134587
|
info: (message, fields) => ctx.log.info(message, fields),
|
|
133687
134588
|
warn: (message, fields) => ctx.log.warn(message, fields)
|
|
@@ -134405,9 +135306,10 @@ ${final.text}`;
|
|
|
134405
135306
|
commit("chat-updated", { chatKey, plan: next });
|
|
134406
135307
|
await deliver(binding, t(next ? "plan.on" : "plan.off"));
|
|
134407
135308
|
};
|
|
134408
|
-
const
|
|
135309
|
+
const hasPanel = (binding) => adapterFor(binding.platform)?.panel !== void 0;
|
|
135310
|
+
const handleCommand = async (binding, text2, teams) => {
|
|
134409
135311
|
const command = parseChatCommand({
|
|
134410
|
-
text:
|
|
135312
|
+
text: text2,
|
|
134411
135313
|
teams,
|
|
134412
135314
|
// 中文别名只在回话语言是中文时认,免得英文用户打一个「停」就被当成命令。
|
|
134413
135315
|
chinese: t.language === "zh-CN"
|
|
@@ -134451,7 +135353,20 @@ ${final.text}`;
|
|
|
134451
135353
|
await deliver(binding, t("command.noTeams"));
|
|
134452
135354
|
return true;
|
|
134453
135355
|
case "help":
|
|
134454
|
-
await deliver(binding, helpText(t));
|
|
135356
|
+
await deliver(binding, helpText(t, { panel: hasPanel(binding) }));
|
|
135357
|
+
return true;
|
|
135358
|
+
case "panel":
|
|
135359
|
+
if (hasPanel(binding)) {
|
|
135360
|
+
try {
|
|
135361
|
+
await panel.open(chatKey);
|
|
135362
|
+
return true;
|
|
135363
|
+
} catch (error) {
|
|
135364
|
+
ctx.log.warn("chat could not open the command panel", {
|
|
135365
|
+
error: describeError(error)
|
|
135366
|
+
});
|
|
135367
|
+
}
|
|
135368
|
+
}
|
|
135369
|
+
await deliver(binding, helpText(t, { panel: false }));
|
|
134455
135370
|
return true;
|
|
134456
135371
|
case "sessions":
|
|
134457
135372
|
await showSessions(binding);
|
|
@@ -134640,9 +135555,75 @@ ${final.text}`;
|
|
|
134640
135555
|
});
|
|
134641
135556
|
chatQueues.set(chatKey, run);
|
|
134642
135557
|
};
|
|
135558
|
+
const panel = makePanelController({
|
|
135559
|
+
t,
|
|
135560
|
+
surface: (chatKey) => {
|
|
135561
|
+
const binding = bindingOfChat(chatKey);
|
|
135562
|
+
const surface = binding === void 0 ? void 0 : adapterFor(binding.platform)?.panel;
|
|
135563
|
+
if (binding === void 0 || surface === void 0) return null;
|
|
135564
|
+
return {
|
|
135565
|
+
open: (card) => surface.open(binding, card),
|
|
135566
|
+
update: (cardId, card) => surface.update(binding, cardId, card)
|
|
135567
|
+
};
|
|
135568
|
+
},
|
|
135569
|
+
planOn: (chatKey) => chatOf(state, chatKey).plan,
|
|
135570
|
+
loadSessions: async (chatKey) => {
|
|
135571
|
+
const binding = bindingOfChat(chatKey);
|
|
135572
|
+
if (binding === void 0) return [];
|
|
135573
|
+
const rows = await sessionRowsFor(binding);
|
|
135574
|
+
listedByChat.set(chatKey, listedThreadIds(rows));
|
|
135575
|
+
return rows;
|
|
135576
|
+
},
|
|
135577
|
+
loadTeams: async (chatKey) => {
|
|
135578
|
+
const binding = bindingOfChat(chatKey);
|
|
135579
|
+
return binding === void 0 ? null : fetchTeams(binding);
|
|
135580
|
+
},
|
|
135581
|
+
loadModels: async (chatKey) => {
|
|
135582
|
+
const binding = bindingOfChat(chatKey);
|
|
135583
|
+
const instances = await ctx.providers.listInstances().catch(() => null);
|
|
135584
|
+
if (binding === void 0 || instances === null) return null;
|
|
135585
|
+
return { current: await currentModelOf(binding), choices: modelChoices(instances) };
|
|
135586
|
+
},
|
|
135587
|
+
runLine: async (chatKey, line) => {
|
|
135588
|
+
const binding = bindingOfChat(chatKey);
|
|
135589
|
+
if (binding === void 0) return;
|
|
135590
|
+
await handleCommand(binding, line, await fetchTeams(binding) ?? []);
|
|
135591
|
+
},
|
|
135592
|
+
sendHelpText: async (chatKey) => {
|
|
135593
|
+
const binding = bindingOfChat(chatKey);
|
|
135594
|
+
if (binding !== void 0) await deliver(binding, helpText(t));
|
|
135595
|
+
},
|
|
135596
|
+
enqueue: enqueueForChat,
|
|
135597
|
+
// 改卡必须排在回调应答之后:飞书在应答不带改卡时会把卡恢复成点击前的样子。
|
|
135598
|
+
defer: (run) => {
|
|
135599
|
+
setTimeout(run, 0);
|
|
135600
|
+
},
|
|
135601
|
+
now: () => Date.now(),
|
|
135602
|
+
warn: (message, error) => {
|
|
135603
|
+
ctx.log.warn(`chat ${message}`, error === void 0 ? {} : { error: describeError(error) });
|
|
135604
|
+
}
|
|
135605
|
+
});
|
|
135606
|
+
const handlePanelClick = (platform, click) => {
|
|
135607
|
+
const target = bindingForChat(platform, click.chatId);
|
|
135608
|
+
if (target === void 0 || target.projectId === "") return null;
|
|
135609
|
+
if (!isAllowedSender(target, click.operatorId)) {
|
|
135610
|
+
ctx.log.warn("chat ignored a panel click from someone not on the allow list", {
|
|
135611
|
+
group: target.displayName,
|
|
135612
|
+
operator: click.operatorId
|
|
135613
|
+
});
|
|
135614
|
+
return t("reply.notAllowed");
|
|
135615
|
+
}
|
|
135616
|
+
return panel.click({
|
|
135617
|
+
chatKey: chatKeyOf(target),
|
|
135618
|
+
cardId: click.cardId,
|
|
135619
|
+
op: click.op,
|
|
135620
|
+
arg: click.arg
|
|
135621
|
+
});
|
|
135622
|
+
};
|
|
134643
135623
|
const inboundHandlersFor = (platform) => ({
|
|
134644
135624
|
onError: (message) => ctx.log.warn("chat inbound connection failed", { platform, message }),
|
|
134645
135625
|
onApproval: handleApprovalClick,
|
|
135626
|
+
onPanelClick: (click) => handlePanelClick(platform, click),
|
|
134646
135627
|
onMessage: (message) => {
|
|
134647
135628
|
const target = bindingForChat(platform, message.chatId);
|
|
134648
135629
|
if (target === void 0 || target.projectId === "") return;
|
|
@@ -134668,7 +135649,7 @@ ${final.text}`;
|
|
|
134668
135649
|
}
|
|
134669
135650
|
enqueueForChat(chatKeyOf(target), async () => {
|
|
134670
135651
|
const teams = await fetchTeams(target);
|
|
134671
|
-
if (await handleCommand(target, message, teams ?? [])) return;
|
|
135652
|
+
if (await handleCommand(target, message.text, teams ?? [])) return;
|
|
134672
135653
|
const project = await ctx.projections.getProject(target.projectId);
|
|
134673
135654
|
if (project === null || project.archivedAt != null) {
|
|
134674
135655
|
if (project === null) {
|