@ycodium-ai/plugin-im 0.2.2554 → 0.2.2570
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 +12 -4
- package/dist/index.js +695 -60
- package/locales/en.json +46 -2
- package/locales/zh-CN.json +46 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -51,14 +51,22 @@ written as plugin facts (`ctx.events.append`) and folded back by a pure function
|
|
|
51
51
|
archived or moved to another project is dropped on the next message.
|
|
52
52
|
|
|
53
53
|
Chat commands: `/stop`, `/status`, `/new`, `/sessions`, `/resume`, `/model`,
|
|
54
|
-
`/plan`, `/team`, `/thread`, `/help`. `/sessions` and `/resume` only list and
|
|
54
|
+
`/plan`, `/team`, `/thread`, `/help`, `/panel`. `/sessions` and `/resume` only list and
|
|
55
55
|
switch threads this plugin opened, since the host lets a plugin send only into
|
|
56
56
|
its own threads.
|
|
57
57
|
|
|
58
|
+
On Lark, `/panel` sends a command panel card (`/help` stays text and ends with a pointer to it; other platforms' `/help` does not mention it): every command above
|
|
59
|
+
is a button, and the ones that take an argument (a thread, a team, a model) open
|
|
60
|
+
a second view on the same card with a Back button. Each card keeps its own view
|
|
61
|
+
stack in memory (`src/panelController.ts`), so after a restart an old card only
|
|
62
|
+
answers that it has expired. A tap runs the same handler as the typed command and
|
|
63
|
+
waits in the same per-group queue as typed messages. Another platform opts in by
|
|
64
|
+
implementing the optional `panel` surface on its adapter (`src/adapter.ts`).
|
|
65
|
+
|
|
58
66
|
The **Reply language** setting (Chinese by default, or English) picks the
|
|
59
|
-
language of everything the bot says itself
|
|
60
|
-
lives in `locales/en.json` and
|
|
61
|
-
only work in Chinese.
|
|
67
|
+
language of everything the bot says itself. The settings card follows the
|
|
68
|
+
interface language instead. The copy lives in `locales/en.json` and
|
|
69
|
+
`locales/zh-CN.json`; Chinese shortcut commands only work in Chinese.
|
|
62
70
|
|
|
63
71
|
## Requirements
|
|
64
72
|
|
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));
|
|
@@ -132147,8 +132207,13 @@ var startLarkInbound = (input) => {
|
|
|
132147
132207
|
// src/larkAdapter.ts
|
|
132148
132208
|
var TOKEN_CACHE_MAX_AGE_MS = 30 * 6e4;
|
|
132149
132209
|
var COLUMNS = [
|
|
132150
|
-
{
|
|
132151
|
-
|
|
132210
|
+
{
|
|
132211
|
+
key: "larkAppId",
|
|
132212
|
+
kind: "text",
|
|
132213
|
+
labelKey: "column.larkAppId.label",
|
|
132214
|
+
placeholderKey: "column.larkAppId.placeholder"
|
|
132215
|
+
},
|
|
132216
|
+
{ key: "larkAppSecret", kind: "password", labelKey: "column.larkAppSecret.label" }
|
|
132152
132217
|
];
|
|
132153
132218
|
var credentials = (binding) => ({
|
|
132154
132219
|
appId: binding.values["larkAppId"] ?? "",
|
|
@@ -132180,6 +132245,13 @@ var makeLarkAdapter = (deps) => {
|
|
|
132180
132245
|
appSecret,
|
|
132181
132246
|
onError: handlers.onError,
|
|
132182
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,
|
|
132183
132255
|
onMessage: (message) => {
|
|
132184
132256
|
handlers.onMessage({
|
|
132185
132257
|
messageId: message.messageId,
|
|
@@ -132258,6 +132330,22 @@ var makeLarkAdapter = (deps) => {
|
|
|
132258
132330
|
});
|
|
132259
132331
|
}
|
|
132260
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
|
+
},
|
|
132261
132349
|
acknowledge: async (binding, messageId) => addReaction({
|
|
132262
132350
|
http: deps.http,
|
|
132263
132351
|
token: await tenantToken(binding),
|
|
@@ -132328,7 +132416,8 @@ var en_default = {
|
|
|
132328
132416
|
label: "Platform"
|
|
132329
132417
|
},
|
|
132330
132418
|
larkAppId: {
|
|
132331
|
-
label: "Lark app ID"
|
|
132419
|
+
label: "Lark app ID",
|
|
132420
|
+
placeholder: "cli_\u2026"
|
|
132332
132421
|
},
|
|
132333
132422
|
larkAppSecret: {
|
|
132334
132423
|
label: "Lark app secret"
|
|
@@ -132419,7 +132508,8 @@ var en_default = {
|
|
|
132419
132508
|
sessions: "\xB7 /sessions \u2014 list the sessions this bot opened for this group's project",
|
|
132420
132509
|
resume: "\xB7 /resume number \u2014 switch to one of them (a number from /sessions, or the start of its id)",
|
|
132421
132510
|
model: "\xB7 /model \u2014 see the model this group uses; /model number or /model instance/model switches it",
|
|
132422
|
-
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."
|
|
132423
132513
|
},
|
|
132424
132514
|
sessions: {
|
|
132425
132515
|
title: "Sessions this bot opened for this group's project ({{count}}):",
|
|
@@ -132492,6 +132582,48 @@ var en_default = {
|
|
|
132492
132582
|
chosen: "Chosen: {{label}}",
|
|
132493
132583
|
elsewhere: "Already handled elsewhere (the web app or another client)."
|
|
132494
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
|
+
}
|
|
132495
132627
|
}
|
|
132496
132628
|
};
|
|
132497
132629
|
|
|
@@ -132534,7 +132666,8 @@ var zh_CN_default = {
|
|
|
132534
132666
|
label: "\u5E73\u53F0"
|
|
132535
132667
|
},
|
|
132536
132668
|
larkAppId: {
|
|
132537
|
-
label: "\u98DE\u4E66\u5E94\u7528 ID"
|
|
132669
|
+
label: "\u98DE\u4E66\u5E94\u7528 ID",
|
|
132670
|
+
placeholder: "cli_\u2026"
|
|
132538
132671
|
},
|
|
132539
132672
|
larkAppSecret: {
|
|
132540
132673
|
label: "\u98DE\u4E66\u5E94\u7528\u5BC6\u94A5"
|
|
@@ -132625,7 +132758,8 @@ var zh_CN_default = {
|
|
|
132625
132758
|
sessions: "\xB7 /sessions \u2014\u2014 \u5217\u51FA\u672C\u673A\u5668\u4EBA\u4E3A\u8FD9\u4E2A\u7FA4\u7684\u9879\u76EE\u5F00\u8FC7\u7684\u4F1A\u8BDD",
|
|
132626
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",
|
|
132627
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",
|
|
132628
|
-
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"
|
|
132629
132763
|
},
|
|
132630
132764
|
sessions: {
|
|
132631
132765
|
title: "\u672C\u673A\u5668\u4EBA\u4E3A\u8FD9\u4E2A\u7FA4\u7684\u9879\u76EE\u5F00\u8FC7\u7684\u4F1A\u8BDD\uFF08{{count}} \u6761\uFF09\uFF1A",
|
|
@@ -132698,6 +132832,48 @@ var zh_CN_default = {
|
|
|
132698
132832
|
chosen: "\u5DF2\u9009\uFF1A{{label}}",
|
|
132699
132833
|
elsewhere: "\u5DF2\u5728\u522B\u5904\u5904\u7406\uFF08\u7F51\u9875\u6216\u5176\u4ED6\u5BA2\u6237\u7AEF\uFF09\u3002"
|
|
132700
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
|
+
}
|
|
132701
132877
|
}
|
|
132702
132878
|
};
|
|
132703
132879
|
|
|
@@ -132838,6 +133014,385 @@ var pickSession = (arg, listed, rows) => {
|
|
|
132838
133014
|
return matches.length === 1 ? matches[0]?.threadId ?? null : null;
|
|
132839
133015
|
};
|
|
132840
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
|
+
|
|
132841
133396
|
// src/telegramApi.ts
|
|
132842
133397
|
var API_BASE = "https://api.telegram.org";
|
|
132843
133398
|
var POLL_TIMEOUT_SECONDS = 45;
|
|
@@ -132983,7 +133538,7 @@ var makeTelegramApi = (input) => {
|
|
|
132983
133538
|
|
|
132984
133539
|
// src/telegramAdapter.ts
|
|
132985
133540
|
var COLUMNS2 = [
|
|
132986
|
-
{ key: "telegramToken", kind: "password",
|
|
133541
|
+
{ key: "telegramToken", kind: "password", labelKey: "column.telegramToken.label" }
|
|
132987
133542
|
];
|
|
132988
133543
|
var BACKOFF_START_MS = 1e3;
|
|
132989
133544
|
var BACKOFF_MAX_MS = 6e4;
|
|
@@ -133499,8 +134054,8 @@ var runWecomClient = (input) => {
|
|
|
133499
134054
|
var BACKOFF_START_MS2 = 1e3;
|
|
133500
134055
|
var BACKOFF_MAX_MS2 = 6e4;
|
|
133501
134056
|
var COLUMNS3 = [
|
|
133502
|
-
{ key: "wecomBotId", kind: "text",
|
|
133503
|
-
{ key: "wecomBotSecret", kind: "password",
|
|
134057
|
+
{ key: "wecomBotId", kind: "text", labelKey: "column.wecomBotId.label" },
|
|
134058
|
+
{ key: "wecomBotSecret", kind: "password", labelKey: "column.wecomBotSecret.label" }
|
|
133504
134059
|
];
|
|
133505
134060
|
var defaultSleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
133506
134061
|
var credentials2 = (binding) => ({
|
|
@@ -133695,99 +134250,99 @@ var imPlugin = definePlugin({
|
|
|
133695
134250
|
const platformColumn = {
|
|
133696
134251
|
key: "platform",
|
|
133697
134252
|
kind: "select",
|
|
133698
|
-
|
|
134253
|
+
labelKey: "column.platform.label",
|
|
133699
134254
|
options: adapters.map((adapter) => ({
|
|
133700
134255
|
value: adapter.platform,
|
|
133701
|
-
//
|
|
133702
|
-
|
|
134256
|
+
// 平台名在目录里有本地叫法(飞书、企业微信)。
|
|
134257
|
+
labelKey: `platform.${adapter.platform}`,
|
|
133703
134258
|
icon: adapter.icon
|
|
133704
134259
|
}))
|
|
133705
134260
|
};
|
|
133706
134261
|
const credentialColumns = adapters.flatMap(
|
|
133707
134262
|
(adapter) => adapter.columns.map((column) => ({
|
|
133708
134263
|
...column,
|
|
133709
|
-
// 凭据列的字也走目录;适配器自己写的英文只是目录里没有这个键时的回落。
|
|
133710
|
-
label: t.optional(`column.${column.key}.label`) ?? column.label,
|
|
133711
134264
|
showWhen: { key: platformColumn.key, equals: [adapter.platform] }
|
|
133712
134265
|
}))
|
|
133713
134266
|
);
|
|
133714
134267
|
ctx.settingsCard({
|
|
133715
|
-
|
|
133716
|
-
|
|
134268
|
+
titleKey: "settings.title",
|
|
134269
|
+
descriptionKey: "settings.description",
|
|
133717
134270
|
fields: [
|
|
133718
134271
|
{
|
|
133719
134272
|
key: REPLY_LANGUAGE_FIELD,
|
|
133720
134273
|
kind: "select",
|
|
133721
|
-
|
|
133722
|
-
|
|
134274
|
+
labelKey: "settings.language.label",
|
|
134275
|
+
descriptionKey: "settings.language.description",
|
|
133723
134276
|
options: [
|
|
133724
|
-
{ value: "zh-CN",
|
|
133725
|
-
{ value: "en",
|
|
134277
|
+
{ value: "zh-CN", labelKey: "settings.language.zhCN" },
|
|
134278
|
+
{ value: "en", labelKey: "settings.language.en" }
|
|
133726
134279
|
]
|
|
133727
134280
|
},
|
|
133728
134281
|
{
|
|
133729
134282
|
key: GROUPS_FIELD,
|
|
133730
134283
|
kind: "table",
|
|
133731
|
-
|
|
134284
|
+
labelKey: "settings.groups",
|
|
133732
134285
|
columns: [
|
|
133733
134286
|
{
|
|
133734
134287
|
key: "displayName",
|
|
133735
134288
|
kind: "text",
|
|
133736
|
-
|
|
133737
|
-
|
|
134289
|
+
labelKey: "column.displayName.label",
|
|
134290
|
+
placeholderKey: "column.displayName.placeholder"
|
|
133738
134291
|
},
|
|
133739
134292
|
platformColumn,
|
|
133740
134293
|
...credentialColumns,
|
|
133741
134294
|
{
|
|
133742
134295
|
key: "chatId",
|
|
133743
134296
|
kind: "text",
|
|
133744
|
-
|
|
133745
|
-
|
|
134297
|
+
labelKey: "column.chatId.label",
|
|
134298
|
+
descriptionKey: "column.chatId.description"
|
|
133746
134299
|
},
|
|
133747
134300
|
{
|
|
133748
134301
|
key: "allowedSenders",
|
|
133749
134302
|
kind: "text",
|
|
133750
|
-
|
|
133751
|
-
|
|
134303
|
+
labelKey: "column.allowedSenders.label",
|
|
134304
|
+
descriptionKey: "column.allowedSenders.description"
|
|
133752
134305
|
},
|
|
133753
134306
|
{
|
|
133754
134307
|
// 选项由宿主填,连三态一起——插件只声明「这个格子装的是代理」,宿主因此
|
|
133755
134308
|
// 也就认得出这一行引用了哪个代理,删代理时拦得住(路线图 #64)。
|
|
133756
134309
|
key: "proxy",
|
|
133757
134310
|
kind: "proxy",
|
|
133758
|
-
|
|
133759
|
-
|
|
134311
|
+
labelKey: "column.proxy.label",
|
|
134312
|
+
descriptionKey: "column.proxy.description"
|
|
133760
134313
|
},
|
|
133761
134314
|
{
|
|
133762
134315
|
key: "projectId",
|
|
133763
134316
|
kind: "select",
|
|
133764
|
-
|
|
133765
|
-
|
|
134317
|
+
labelKey: "column.projectId.label",
|
|
134318
|
+
descriptionKey: "column.projectId.description",
|
|
133766
134319
|
options: [
|
|
133767
|
-
{ value: "",
|
|
134320
|
+
{ value: "", labelKey: "settings.reportOnly" },
|
|
134321
|
+
// 项目名是数据,只能写成字面量;已归档的那段后缀宿主翻不了(选项没有带参数的
|
|
134322
|
+
// 键),所以仍按回话语言取。
|
|
133768
134323
|
...imProjectOptions(projects, boundProjectIds, t("settings.archivedSuffix"))
|
|
133769
134324
|
]
|
|
133770
134325
|
},
|
|
133771
134326
|
{
|
|
133772
134327
|
key: "runtimeMode",
|
|
133773
134328
|
kind: "select",
|
|
133774
|
-
|
|
133775
|
-
|
|
134329
|
+
labelKey: "column.runtimeMode.label",
|
|
134330
|
+
descriptionKey: "column.runtimeMode.description",
|
|
133776
134331
|
options: [
|
|
133777
|
-
{ value: "approval-required",
|
|
133778
|
-
{ value: "auto-accept-edits",
|
|
133779
|
-
{ value: "auto",
|
|
133780
|
-
{ value: "full-access",
|
|
134332
|
+
{ value: "approval-required", labelKey: "settings.runtimeMode.approvalRequired" },
|
|
134333
|
+
{ value: "auto-accept-edits", labelKey: "settings.runtimeMode.autoAcceptEdits" },
|
|
134334
|
+
{ value: "auto", labelKey: "settings.runtimeMode.auto" },
|
|
134335
|
+
{ value: "full-access", labelKey: "settings.runtimeMode.fullAccess" }
|
|
133781
134336
|
]
|
|
133782
134337
|
},
|
|
133783
134338
|
{
|
|
133784
134339
|
key: "mode",
|
|
133785
134340
|
kind: "select",
|
|
133786
|
-
|
|
133787
|
-
|
|
134341
|
+
labelKey: "column.mode.label",
|
|
134342
|
+
descriptionKey: "column.mode.description",
|
|
133788
134343
|
options: [
|
|
133789
|
-
{ value: "turn",
|
|
133790
|
-
{ value: "message",
|
|
134344
|
+
{ value: "turn", labelKey: "settings.notifyMode.turn" },
|
|
134345
|
+
{ value: "message", labelKey: "settings.notifyMode.message" }
|
|
133791
134346
|
]
|
|
133792
134347
|
}
|
|
133793
134348
|
]
|
|
@@ -134398,9 +134953,10 @@ ${final.text}`;
|
|
|
134398
134953
|
commit("chat-updated", { chatKey, plan: next });
|
|
134399
134954
|
await deliver(binding, t(next ? "plan.on" : "plan.off"));
|
|
134400
134955
|
};
|
|
134401
|
-
const
|
|
134956
|
+
const hasPanel = (binding) => adapterFor(binding.platform)?.panel !== void 0;
|
|
134957
|
+
const handleCommand = async (binding, text2, teams) => {
|
|
134402
134958
|
const command = parseChatCommand({
|
|
134403
|
-
text:
|
|
134959
|
+
text: text2,
|
|
134404
134960
|
teams,
|
|
134405
134961
|
// 中文别名只在回话语言是中文时认,免得英文用户打一个「停」就被当成命令。
|
|
134406
134962
|
chinese: t.language === "zh-CN"
|
|
@@ -134444,7 +135000,20 @@ ${final.text}`;
|
|
|
134444
135000
|
await deliver(binding, t("command.noTeams"));
|
|
134445
135001
|
return true;
|
|
134446
135002
|
case "help":
|
|
134447
|
-
await deliver(binding, helpText(t));
|
|
135003
|
+
await deliver(binding, helpText(t, { panel: hasPanel(binding) }));
|
|
135004
|
+
return true;
|
|
135005
|
+
case "panel":
|
|
135006
|
+
if (hasPanel(binding)) {
|
|
135007
|
+
try {
|
|
135008
|
+
await panel.open(chatKey);
|
|
135009
|
+
return true;
|
|
135010
|
+
} catch (error) {
|
|
135011
|
+
ctx.log.warn("chat could not open the command panel", {
|
|
135012
|
+
error: describeError(error)
|
|
135013
|
+
});
|
|
135014
|
+
}
|
|
135015
|
+
}
|
|
135016
|
+
await deliver(binding, helpText(t, { panel: false }));
|
|
134448
135017
|
return true;
|
|
134449
135018
|
case "sessions":
|
|
134450
135019
|
await showSessions(binding);
|
|
@@ -134633,9 +135202,75 @@ ${final.text}`;
|
|
|
134633
135202
|
});
|
|
134634
135203
|
chatQueues.set(chatKey, run);
|
|
134635
135204
|
};
|
|
135205
|
+
const panel = makePanelController({
|
|
135206
|
+
t,
|
|
135207
|
+
surface: (chatKey) => {
|
|
135208
|
+
const binding = bindingOfChat(chatKey);
|
|
135209
|
+
const surface = binding === void 0 ? void 0 : adapterFor(binding.platform)?.panel;
|
|
135210
|
+
if (binding === void 0 || surface === void 0) return null;
|
|
135211
|
+
return {
|
|
135212
|
+
open: (card) => surface.open(binding, card),
|
|
135213
|
+
update: (cardId, card) => surface.update(binding, cardId, card)
|
|
135214
|
+
};
|
|
135215
|
+
},
|
|
135216
|
+
planOn: (chatKey) => chatOf(state, chatKey).plan,
|
|
135217
|
+
loadSessions: async (chatKey) => {
|
|
135218
|
+
const binding = bindingOfChat(chatKey);
|
|
135219
|
+
if (binding === void 0) return [];
|
|
135220
|
+
const rows = await sessionRowsFor(binding);
|
|
135221
|
+
listedByChat.set(chatKey, listedThreadIds(rows));
|
|
135222
|
+
return rows;
|
|
135223
|
+
},
|
|
135224
|
+
loadTeams: async (chatKey) => {
|
|
135225
|
+
const binding = bindingOfChat(chatKey);
|
|
135226
|
+
return binding === void 0 ? null : fetchTeams(binding);
|
|
135227
|
+
},
|
|
135228
|
+
loadModels: async (chatKey) => {
|
|
135229
|
+
const binding = bindingOfChat(chatKey);
|
|
135230
|
+
const instances = await ctx.providers.listInstances().catch(() => null);
|
|
135231
|
+
if (binding === void 0 || instances === null) return null;
|
|
135232
|
+
return { current: await currentModelOf(binding), choices: modelChoices(instances) };
|
|
135233
|
+
},
|
|
135234
|
+
runLine: async (chatKey, line) => {
|
|
135235
|
+
const binding = bindingOfChat(chatKey);
|
|
135236
|
+
if (binding === void 0) return;
|
|
135237
|
+
await handleCommand(binding, line, await fetchTeams(binding) ?? []);
|
|
135238
|
+
},
|
|
135239
|
+
sendHelpText: async (chatKey) => {
|
|
135240
|
+
const binding = bindingOfChat(chatKey);
|
|
135241
|
+
if (binding !== void 0) await deliver(binding, helpText(t));
|
|
135242
|
+
},
|
|
135243
|
+
enqueue: enqueueForChat,
|
|
135244
|
+
// 改卡必须排在回调应答之后:飞书在应答不带改卡时会把卡恢复成点击前的样子。
|
|
135245
|
+
defer: (run) => {
|
|
135246
|
+
setTimeout(run, 0);
|
|
135247
|
+
},
|
|
135248
|
+
now: () => Date.now(),
|
|
135249
|
+
warn: (message, error) => {
|
|
135250
|
+
ctx.log.warn(`chat ${message}`, error === void 0 ? {} : { error: describeError(error) });
|
|
135251
|
+
}
|
|
135252
|
+
});
|
|
135253
|
+
const handlePanelClick = (platform, click) => {
|
|
135254
|
+
const target = bindingForChat(platform, click.chatId);
|
|
135255
|
+
if (target === void 0 || target.projectId === "") return null;
|
|
135256
|
+
if (!isAllowedSender(target, click.operatorId)) {
|
|
135257
|
+
ctx.log.warn("chat ignored a panel click from someone not on the allow list", {
|
|
135258
|
+
group: target.displayName,
|
|
135259
|
+
operator: click.operatorId
|
|
135260
|
+
});
|
|
135261
|
+
return t("reply.notAllowed");
|
|
135262
|
+
}
|
|
135263
|
+
return panel.click({
|
|
135264
|
+
chatKey: chatKeyOf(target),
|
|
135265
|
+
cardId: click.cardId,
|
|
135266
|
+
op: click.op,
|
|
135267
|
+
arg: click.arg
|
|
135268
|
+
});
|
|
135269
|
+
};
|
|
134636
135270
|
const inboundHandlersFor = (platform) => ({
|
|
134637
135271
|
onError: (message) => ctx.log.warn("chat inbound connection failed", { platform, message }),
|
|
134638
135272
|
onApproval: handleApprovalClick,
|
|
135273
|
+
onPanelClick: (click) => handlePanelClick(platform, click),
|
|
134639
135274
|
onMessage: (message) => {
|
|
134640
135275
|
const target = bindingForChat(platform, message.chatId);
|
|
134641
135276
|
if (target === void 0 || target.projectId === "") return;
|
|
@@ -134661,7 +135296,7 @@ ${final.text}`;
|
|
|
134661
135296
|
}
|
|
134662
135297
|
enqueueForChat(chatKeyOf(target), async () => {
|
|
134663
135298
|
const teams = await fetchTeams(target);
|
|
134664
|
-
if (await handleCommand(target, message, teams ?? [])) return;
|
|
135299
|
+
if (await handleCommand(target, message.text, teams ?? [])) return;
|
|
134665
135300
|
const project = await ctx.projections.getProject(target.projectId);
|
|
134666
135301
|
if (project === null || project.archivedAt != null) {
|
|
134667
135302
|
if (project === null) {
|
package/locales/en.json
CHANGED
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"label": "Platform"
|
|
37
37
|
},
|
|
38
38
|
"larkAppId": {
|
|
39
|
-
"label": "Lark app ID"
|
|
39
|
+
"label": "Lark app ID",
|
|
40
|
+
"placeholder": "cli_…"
|
|
40
41
|
},
|
|
41
42
|
"larkAppSecret": {
|
|
42
43
|
"label": "Lark app secret"
|
|
@@ -127,7 +128,8 @@
|
|
|
127
128
|
"sessions": "· /sessions — list the sessions this bot opened for this group's project",
|
|
128
129
|
"resume": "· /resume number — switch to one of them (a number from /sessions, or the start of its id)",
|
|
129
130
|
"model": "· /model — see the model this group uses; /model number or /model instance/model switches it",
|
|
130
|
-
"plan": "· /plan — toggle plan mode (the agent proposes a plan instead of acting); /plan on, /plan off set it"
|
|
131
|
+
"plan": "· /plan — toggle plan mode (the agent proposes a plan instead of acting); /plan on, /plan off set it",
|
|
132
|
+
"panel": "Send /panel to open the command panel with a button for every command."
|
|
131
133
|
},
|
|
132
134
|
"sessions": {
|
|
133
135
|
"title": "Sessions this bot opened for this group's project ({{count}}):",
|
|
@@ -200,5 +202,47 @@
|
|
|
200
202
|
"chosen": "Chosen: {{label}}",
|
|
201
203
|
"elsewhere": "Already handled elsewhere (the web app or another client)."
|
|
202
204
|
}
|
|
205
|
+
},
|
|
206
|
+
"panel": {
|
|
207
|
+
"title": "Command panel",
|
|
208
|
+
"hint": "A button does the same as typing its command. You can still type.",
|
|
209
|
+
"category": {
|
|
210
|
+
"session": "Session",
|
|
211
|
+
"target": "Send to",
|
|
212
|
+
"system": "Settings and status"
|
|
213
|
+
},
|
|
214
|
+
"cmd": {
|
|
215
|
+
"new": "New session",
|
|
216
|
+
"stop": "Stop this turn",
|
|
217
|
+
"sessions": "Sessions",
|
|
218
|
+
"team": "Send to a team",
|
|
219
|
+
"thread": "Normal session",
|
|
220
|
+
"model": "Model",
|
|
221
|
+
"planOn": "Plan mode: on",
|
|
222
|
+
"planOff": "Plan mode: off",
|
|
223
|
+
"status": "Status",
|
|
224
|
+
"help": "Text help"
|
|
225
|
+
},
|
|
226
|
+
"back": "Back",
|
|
227
|
+
"prev": "Previous",
|
|
228
|
+
"next": "Next",
|
|
229
|
+
"page": "Page {{page}} of {{total}}",
|
|
230
|
+
"loading": "Loading…",
|
|
231
|
+
"working": "Working on it…",
|
|
232
|
+
"expired": "This panel has expired. Send /panel again.",
|
|
233
|
+
"busy": "The last tap is still being handled. Give it a moment.",
|
|
234
|
+
"loadFailed": "Could not read that just now. Try again in a moment.",
|
|
235
|
+
"sessions": {
|
|
236
|
+
"title": "Pick a session",
|
|
237
|
+
"hint": "Tap one to switch to it."
|
|
238
|
+
},
|
|
239
|
+
"teams": {
|
|
240
|
+
"title": "Pick a team",
|
|
241
|
+
"hint": "Tap a team and the next messages go to it.",
|
|
242
|
+
"unavailable": "Could not read the team list."
|
|
243
|
+
},
|
|
244
|
+
"models": {
|
|
245
|
+
"title": "Pick a model"
|
|
246
|
+
}
|
|
203
247
|
}
|
|
204
248
|
}
|
package/locales/zh-CN.json
CHANGED
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"label": "平台"
|
|
37
37
|
},
|
|
38
38
|
"larkAppId": {
|
|
39
|
-
"label": "飞书应用 ID"
|
|
39
|
+
"label": "飞书应用 ID",
|
|
40
|
+
"placeholder": "cli_…"
|
|
40
41
|
},
|
|
41
42
|
"larkAppSecret": {
|
|
42
43
|
"label": "飞书应用密钥"
|
|
@@ -127,7 +128,8 @@
|
|
|
127
128
|
"sessions": "· /sessions —— 列出本机器人为这个群的项目开过的会话",
|
|
128
129
|
"resume": "· /resume 编号 —— 切到其中一条(/sessions 里的编号,或会话 id 的开头)",
|
|
129
130
|
"model": "· /model —— 看这个群用的模型;/model 编号 或 /model 实例/模型 切换",
|
|
130
|
-
"plan": "· /plan —— 开关计划模式(agent 先出计划、不动手);/plan on、/plan off 明确设定"
|
|
131
|
+
"plan": "· /plan —— 开关计划模式(agent 先出计划、不动手);/plan on、/plan off 明确设定",
|
|
132
|
+
"panel": "发 /panel 打开可点的命令面板。"
|
|
131
133
|
},
|
|
132
134
|
"sessions": {
|
|
133
135
|
"title": "本机器人为这个群的项目开过的会话({{count}} 条):",
|
|
@@ -200,5 +202,47 @@
|
|
|
200
202
|
"chosen": "已选:{{label}}",
|
|
201
203
|
"elsewhere": "已在别处处理(网页或其他客户端)。"
|
|
202
204
|
}
|
|
205
|
+
},
|
|
206
|
+
"panel": {
|
|
207
|
+
"title": "命令面板",
|
|
208
|
+
"hint": "点按钮等于打出对应的命令,也可以直接打字。",
|
|
209
|
+
"category": {
|
|
210
|
+
"session": "会话",
|
|
211
|
+
"target": "派给谁",
|
|
212
|
+
"system": "设置与状态"
|
|
213
|
+
},
|
|
214
|
+
"cmd": {
|
|
215
|
+
"new": "新会话",
|
|
216
|
+
"stop": "停止本轮",
|
|
217
|
+
"sessions": "会话列表",
|
|
218
|
+
"team": "派给团队",
|
|
219
|
+
"thread": "普通会话",
|
|
220
|
+
"model": "模型",
|
|
221
|
+
"planOn": "计划模式:开",
|
|
222
|
+
"planOff": "计划模式:关",
|
|
223
|
+
"status": "状态",
|
|
224
|
+
"help": "文字说明"
|
|
225
|
+
},
|
|
226
|
+
"back": "返回",
|
|
227
|
+
"prev": "上一页",
|
|
228
|
+
"next": "下一页",
|
|
229
|
+
"page": "第 {{page}} / {{total}} 页",
|
|
230
|
+
"loading": "加载中…",
|
|
231
|
+
"working": "处理中…",
|
|
232
|
+
"expired": "面板已过期,再发一次 /panel。",
|
|
233
|
+
"busy": "上一步还在处理,稍等一下。",
|
|
234
|
+
"loadFailed": "刚才没读出来,稍后再试。",
|
|
235
|
+
"sessions": {
|
|
236
|
+
"title": "选一条会话",
|
|
237
|
+
"hint": "点一条切过去。"
|
|
238
|
+
},
|
|
239
|
+
"teams": {
|
|
240
|
+
"title": "选一支队",
|
|
241
|
+
"hint": "点一支队,接下来的活派给它。",
|
|
242
|
+
"unavailable": "读不到团队列表。"
|
|
243
|
+
},
|
|
244
|
+
"models": {
|
|
245
|
+
"title": "选模型"
|
|
246
|
+
}
|
|
203
247
|
}
|
|
204
248
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ycodium-ai/plugin-im",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2570",
|
|
4
4
|
"description": "Talk to your agents from Telegram, WeCom or Lark.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"im",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"esbuild": "0.28.1",
|
|
28
28
|
"markdown-it": "^15.0.2",
|
|
29
29
|
"vite-plus": "0.2.2",
|
|
30
|
-
"@ycodium-ai/plugin-api": "0.2.
|
|
30
|
+
"@ycodium-ai/plugin-api": "0.2.2570"
|
|
31
31
|
},
|
|
32
32
|
"ycodium": {
|
|
33
33
|
"plugin": "./dist/index.js",
|