mu-agent-bridge 1.0.39 → 1.0.41
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 +16 -0
- package/dist/src-ts/cli-commands.js +6 -2
- package/dist/src-ts/cli.js +1 -1
- package/dist/src-ts/control-server.js +2 -0
- package/dist/src-ts/daemon-process.js +3 -2
- package/dist/src-ts/plugin-manager.js +1 -1
- package/dist/src-ts/plugins/aun/index.js +1 -1
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +3 -2
- package/dist/src-ts/plugins/chatgpt/index.js +1 -1
- package/dist/src-ts/plugins/feishu/index.js +21 -8
- package/dist/src-ts/plugins/feishu/public/app.js +20 -8
- package/dist/src-ts/plugins/feishu/public/index.html +1 -1
- package/dist/src-ts/secret-input.js +47 -0
- package/dist/src-ts/session-management.js +34 -13
- package/dist/tests-ts/aun-plugin.test.js +5 -0
- package/dist/tests-ts/chatgpt-history.test.js +33 -0
- package/dist/tests-ts/cli-commands.test.js +58 -0
- package/dist/tests-ts/daemon-process.test.js +52 -0
- package/dist/tests-ts/feishu-page.test.js +10 -3
- package/dist/tests-ts/feishu-plugin.test.js +24 -1
- package/dist/tests-ts/installer-assets.test.js +99 -0
- package/dist/tests-ts/plugin-manager.test.js +3 -0
- package/dist/tests-ts/session-management.test.js +38 -4
- package/package.json +3 -1
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +1 -1
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +3 -2
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +1 -1
- package/plugins/chatgpt/package.json +1 -1
- package/plugins/chatgpt/plugin.json +1 -14
- package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +21 -8
- package/plugins/feishu/dist/src-ts/plugins/feishu/public/app.js +20 -8
- package/plugins/feishu/dist/src-ts/plugins/feishu/public/index.html +1 -1
package/README.md
CHANGED
|
@@ -146,3 +146,19 @@ Windows 路径时,可设置 `DSH_INSTALL_DIR`。
|
|
|
146
146
|
## AUN 身份管理
|
|
147
147
|
|
|
148
148
|
创建、连接、关联和 AUN 通信统一由独立 `agent-bridge-aun` 插件完成。安装并启动 AUN 后,在 ABWeb 的“通道 → AUN”创建或选择身份,再将已连接 AID 关联到已启动的 Agent;用 `mab status` 查看状态。Owner AID 仅作本地归属配置,不代表 AUN Owner 关系已激活。完整步骤见 [AUN 插件与 AID 身份管理](docs/aun-plugin-identities.md);给 Codex、Claude Code 直接执行的 CLI 说明见 [使用说明](使用说明.md)。
|
|
149
|
+
|
|
150
|
+
### 飞书凭据与办公 Agent 关联
|
|
151
|
+
|
|
152
|
+
在 ABWeb 的“飞书通道 → 手动配置凭据”中填写 App ID、App Secret,并选择“关联办公 Agent”。Secret 使用密码框显示;已保存的密钥保持掩码,不通过状态接口返回。WorkBuddy、千问办公、豆包根据运行后台的机器上是否存在可执行文件检测,文件缺失或插件未就绪的选项置灰。自定义安装位置请在对应插件配置中填写 `executablePath`。
|
|
153
|
+
|
|
154
|
+
也可以使用 CLI,Secret 在命令执行后隐藏输入,不写在命令参数中:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
mab feishu configure cli_你的应用ID --agent workbuddy
|
|
158
|
+
mab feishu agents
|
|
159
|
+
mab feishu list
|
|
160
|
+
mab feishu bind cli_你的应用ID workbuddy
|
|
161
|
+
mab feishu unbind cli_你的应用ID
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`configure` 保存凭据及可选关联后,需要在 ABWeb 重载飞书插件以连接机器人。非交互终端请使用网页密码框;不要把 Secret 写进命令行、脚本或仓库。
|
|
@@ -5,6 +5,7 @@ exports.resolveLocalControlHost = resolveLocalControlHost;
|
|
|
5
5
|
exports.controlUrl = controlUrl;
|
|
6
6
|
exports.runPluginCommand = runPluginCommand;
|
|
7
7
|
exports.runCoreHelp = runCoreHelp;
|
|
8
|
+
const secret_input_1 = require("./secret-input");
|
|
8
9
|
const node_http_1 = require("node:http");
|
|
9
10
|
const MAX_BODY_BYTES = 1024 * 1024;
|
|
10
11
|
const PLUGIN_ID = /^[a-z][a-z0-9-]{1,63}$/;
|
|
@@ -108,6 +109,7 @@ function commandManifest(value, plugin) {
|
|
|
108
109
|
|| !Array.isArray(value.commands) || value.commands.some((command) => !object(command)
|
|
109
110
|
|| typeof command.name !== "string" || !/^[a-z][a-z0-9-]*$/.test(command.name)
|
|
110
111
|
|| typeof command.summary !== "string" || !command.summary.trim() || typeof command.usage !== "string" || !command.usage.trim()
|
|
112
|
+
|| (command.secretPrompt !== undefined && (typeof command.secretPrompt !== "string" || !command.secretPrompt.trim()))
|
|
111
113
|
|| (command.examples !== undefined && (!Array.isArray(command.examples) || command.examples.some((example) => typeof example !== "string"))))) {
|
|
112
114
|
throw new CliError("插件命令清单协议无效(需要 protocolVersion: 1)");
|
|
113
115
|
}
|
|
@@ -126,7 +128,7 @@ async function runPluginCommand(id, args, port, options = {}) {
|
|
|
126
128
|
throw new CliError(`插件“${id}”未安装;请先到 ABWeb 安装插件`, 2);
|
|
127
129
|
if (!plugin.capabilities.includes("cli"))
|
|
128
130
|
throw new CliError(`插件“${plugin.displayName}”未提供 CLI 命令;请升级对应插件`, 2);
|
|
129
|
-
if (plugin.status
|
|
131
|
+
if (!["ready", "degraded"].includes(plugin.status))
|
|
130
132
|
throw new CliError(`插件“${plugin.displayName}”未就绪(${plugin.status})`, 2);
|
|
131
133
|
const path = `/api/plugins/${id}/commands`;
|
|
132
134
|
const response = await requestJson(port, path, options);
|
|
@@ -146,7 +148,9 @@ async function runPluginCommand(id, args, port, options = {}) {
|
|
|
146
148
|
])].join("\n");
|
|
147
149
|
return output({ ok: true, message, data: { ...manifest, commands: selected } }, 0, json);
|
|
148
150
|
}
|
|
149
|
-
const
|
|
151
|
+
const prompt = manifest.commands.find((command) => command.name === argv[0])?.secretPrompt;
|
|
152
|
+
const secret = prompt ? await (options.readSecret ?? secret_input_1.readSecret)(prompt) : undefined;
|
|
153
|
+
const executed = await requestJson(port, path, options, { protocolVersion: 1, argv, ...(secret === undefined ? {} : { secret }) });
|
|
150
154
|
const result = executed.value;
|
|
151
155
|
if (!object(result) || typeof result.ok !== "boolean" || typeof result.message !== "string"
|
|
152
156
|
|| (executed.status >= 200 && executed.status < 300 ? !result.ok : result.ok)
|
package/dist/src-ts/cli.js
CHANGED
|
@@ -235,7 +235,6 @@ async function serve() {
|
|
|
235
235
|
await manager.discover();
|
|
236
236
|
await manager.versions.load();
|
|
237
237
|
const coreVersion = await Promise.resolve().then(() => __importStar(require("node:fs/promises"))).then(({ readFile }) => readFile((0, node_path_1.resolve)(__dirname, "../../package.json"), "utf8").then((value) => JSON.parse(value).version).catch(() => "0.0.0"));
|
|
238
|
-
await checkAndUpgradePlugins(manager);
|
|
239
238
|
const port = Number(process.env.AGENT_BRIDGE_PORT ?? "26107");
|
|
240
239
|
const host = process.env.AGENT_BRIDGE_HOST ?? "127.0.0.1";
|
|
241
240
|
if (host !== "127.0.0.1" && host !== "localhost" && host !== "::1" && !process.env.AGENT_BRIDGE_TOKEN) {
|
|
@@ -264,6 +263,7 @@ async function serve() {
|
|
|
264
263
|
logger.error(`plugin failed: ${id}`, { error: error instanceof Error ? error.message : String(error) });
|
|
265
264
|
}
|
|
266
265
|
}
|
|
266
|
+
await checkAndUpgradePlugins(manager);
|
|
267
267
|
const intervalMs = Math.max(60_000, Number(process.env.AGENT_BRIDGE_PLUGIN_UPDATE_INTERVAL_MS ?? 300_000) || 300_000);
|
|
268
268
|
const updateTimer = setInterval(() => void checkAndUpgradePlugins(manager), intervalMs);
|
|
269
269
|
updateTimer.unref?.();
|
|
@@ -251,6 +251,8 @@ class ControlServer {
|
|
|
251
251
|
const result = await identity.create(body.aid, body.ownerAid);
|
|
252
252
|
return this.json(res, result.status === "等待 Owner 确认" ? 202 : result.status === "existing" ? 200 : 201, result);
|
|
253
253
|
}
|
|
254
|
+
if (method === "GET" && url.pathname === "/api/agents/availability")
|
|
255
|
+
return this.json(res, 200, { agents: await this.options.manager.availableAgents() });
|
|
254
256
|
if (method === "GET" && url.pathname === "/api/plugins")
|
|
255
257
|
return this.json(res, 200, { ok: true, plugins: this.options.manager.list() });
|
|
256
258
|
if (method === "GET" && url.pathname === "/api/plugins/catalog")
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.isDaemonCommand = isDaemonCommand;
|
|
4
4
|
/** 只识别直接运行本安装 CLI 的后台进程,避免旧地址文件中的 PID 被其他程序复用。 */
|
|
5
5
|
function isDaemonCommand(commandLine, cli) {
|
|
6
|
-
const
|
|
7
|
-
|
|
6
|
+
const normalize = (value) => value.replace(/[\\/]+/g, "/").toLowerCase();
|
|
7
|
+
const args = [...commandLine.matchAll(/"([^"]*)"|(\S+)/g)].map((match) => normalize(match[1] ?? match[2]));
|
|
8
|
+
return args[1] === normalize(cli) && ["start", "--serve"].includes(args[2]);
|
|
8
9
|
}
|
|
@@ -385,7 +385,7 @@ class PluginManager {
|
|
|
385
385
|
if (trustedChannel && (typeof message.peerId !== "string" || !message.peerId.trim() || [message.peerId, message.recipientId, message.chatId, message.threadId].some(value => value !== undefined && (typeof value !== "string" || value.length > 512 || /[\u0000-\u001f*]/.test(value)))))
|
|
386
386
|
throw new agent_1.AgentError("通道来源标识无效");
|
|
387
387
|
const target = message.agentPlugin || "qwenwork";
|
|
388
|
-
if (/^\/sessions(?:\s|$)/.test(message.text.trimStart())) {
|
|
388
|
+
if (/^\/sessions(?:\s|$)/.test(message.text.trimStart()) || (0, session_management_1.isDailySummaryRequest)(message.text)) {
|
|
389
389
|
if (!trustedChannel)
|
|
390
390
|
throw new agent_1.AgentError("当前通道不支持会话管理");
|
|
391
391
|
const current = await this.sessions.current(message, target);
|
|
@@ -671,7 +671,7 @@ class AunPlugin {
|
|
|
671
671
|
method,
|
|
672
672
|
headers: { accept: "application/json", ...(body === undefined ? {} : { "content-type": "application/json" }) },
|
|
673
673
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
674
|
-
signal: AbortSignal.timeout(5000),
|
|
674
|
+
signal: AbortSignal.timeout(method === "PATCH" ? 120_000 : 5000),
|
|
675
675
|
redirect: "error",
|
|
676
676
|
});
|
|
677
677
|
}
|
|
@@ -111,7 +111,8 @@ class ChatgptCdpClient {
|
|
|
111
111
|
if (selected?.getAttribute('data-app-action-sidebar-thread-id') !== ${JSON.stringify(session.id)}
|
|
112
112
|
|| selected?.getAttribute('data-app-action-sidebar-thread-host-id') !== ${JSON.stringify(session.host)}) return { supported: false, reason: '读取期间活动会话身份发生变化' };
|
|
113
113
|
if (document.querySelector(${JSON.stringify(stopButtonSelector)})) return { supported: false, reason: 'ChatGPT 正在生成回复,暂不读取历史' };
|
|
114
|
-
const
|
|
114
|
+
const legacyTurns = [...document.querySelectorAll(${JSON.stringify(exports.turnSelector)})];
|
|
115
|
+
const turns = legacyTurns.length ? legacyTurns : [...document.querySelectorAll('[data-content-search-turn-key]')];
|
|
115
116
|
const output = [];
|
|
116
117
|
const missingReasons = [];
|
|
117
118
|
let chars = 0;
|
|
@@ -121,7 +122,7 @@ class ChatgptCdpClient {
|
|
|
121
122
|
output.push({ role, text });
|
|
122
123
|
};
|
|
123
124
|
for (const turn of turns) {
|
|
124
|
-
const turnId = turn.getAttribute(${JSON.stringify(turnIdAttribute)});
|
|
125
|
+
const turnId = turn.getAttribute(${JSON.stringify(turnIdAttribute)}) || turn.getAttribute('data-content-search-turn-key');
|
|
125
126
|
if (!turnId) {
|
|
126
127
|
missingReasons.push('部分消息缺少可验证的 turn 身份');
|
|
127
128
|
continue;
|
|
@@ -6,7 +6,7 @@ const cdp_client_1 = require("./cdp-client");
|
|
|
6
6
|
const session_store_1 = require("./session-store");
|
|
7
7
|
exports.chatgptManifest = {
|
|
8
8
|
id: "chatgpt",
|
|
9
|
-
version: "1.0.
|
|
9
|
+
version: "1.0.10",
|
|
10
10
|
displayName: "ChatGPT(Codex)",
|
|
11
11
|
type: "agent",
|
|
12
12
|
entry: "dist/index.js",
|
|
@@ -11,6 +11,7 @@ const commands = [
|
|
|
11
11
|
{ name: "status", summary: "查看飞书通道状态", usage: "mab feishu status" },
|
|
12
12
|
{ name: "list", summary: "列出机器人连接状态和关联 Agent", usage: "mab feishu list" },
|
|
13
13
|
{ name: "agents", summary: "列出已启动的 Agent 插件", usage: "mab feishu agents" },
|
|
14
|
+
{ name: "configure", summary: "保存机器人凭据并关联办公 Agent", usage: "mab feishu configure <app-id> [--agent <plugin-id>]", secretPrompt: "请输入飞书 App Secret" },
|
|
14
15
|
{ name: "bind", summary: "将已连接的机器人关联到 Agent", usage: "mab feishu bind <bot-id> <plugin-id>" },
|
|
15
16
|
{ name: "unbind", summary: "解除机器人关联并保留凭据", usage: "mab feishu unbind <bot-id>" },
|
|
16
17
|
];
|
|
@@ -149,8 +150,10 @@ class FeishuPlugin {
|
|
|
149
150
|
}
|
|
150
151
|
if (body.protocolVersion !== 1 || !Array.isArray(body.argv) || !body.argv.every((arg) => typeof arg === "string"))
|
|
151
152
|
return this.json(response, 400, { ok: false, message: "命令协议版本或 argv 无效" });
|
|
152
|
-
const [command, appId,
|
|
153
|
-
const
|
|
153
|
+
const [command, appId, pluginArg] = body.argv;
|
|
154
|
+
const pluginId = command === "configure" && body.argv[2] === "--agent" ? body.argv[3] : pluginArg;
|
|
155
|
+
const secret = typeof body.secret === "string" ? body.secret.trim() : "";
|
|
156
|
+
const expectedLength = command === "bind" ? 3 : command === "unbind" ? 2 : command === "configure" ? (body.argv.length === 4 ? 4 : 2) : 1;
|
|
154
157
|
if (!commands.some((item) => item.name === command) || body.argv.length !== expectedLength || body.argv.some((arg) => !arg.trim()))
|
|
155
158
|
return this.json(response, 400, { ok: false, message: "命令或参数无效,请查看 mab feishu help" });
|
|
156
159
|
if (command === "status") {
|
|
@@ -161,34 +164,44 @@ class FeishuPlugin {
|
|
|
161
164
|
const bots = this.bots.map((bot) => ({ app_id: bot.app_id, status: this.botStates.get(bot.app_id)?.status || "degraded", pluginId: this.bindings[bot.app_id] || "" }));
|
|
162
165
|
return this.json(response, 200, { ok: true, message: bots.map((bot) => `${bot.app_id}:${bot.status},Agent:${bot.pluginId || "未关联"}`).join("\n") || "尚未配置飞书机器人", data: bots });
|
|
163
166
|
}
|
|
164
|
-
if (command
|
|
167
|
+
if (command === "configure" && (!appId || !secret || body.argv.length === 4 && (body.argv[2] !== "--agent" || !pluginId)))
|
|
168
|
+
return this.json(response, 400, { ok: false, message: "用法:mab feishu configure <app-id> [--agent <plugin-id>],Secret 必须输入" });
|
|
169
|
+
if (command !== "configure" && command !== "agents" && !this.bots.some((bot) => bot.app_id === appId))
|
|
165
170
|
return this.json(response, 400, { ok: false, message: "飞书机器人标识不存在" });
|
|
166
171
|
if (command === "bind" && this.botStates.get(appId)?.status !== "ready")
|
|
167
172
|
return this.json(response, 400, { ok: false, message: "飞书机器人当前未连接" });
|
|
168
|
-
if (command === "agents" || command === "bind") {
|
|
173
|
+
if (command === "agents" || command === "bind" || command === "configure" && pluginId) {
|
|
169
174
|
const runtime = this.context?.runtime;
|
|
170
175
|
const controlUrl = runtime && "controlUrl" in runtime ? runtime.controlUrl : undefined;
|
|
171
176
|
if (typeof controlUrl !== "string" || !controlUrl)
|
|
172
177
|
return this.json(response, 503, { ok: false, message: "运行时未提供控制 API 地址,请升级核心并重启后台" });
|
|
173
178
|
let agents;
|
|
174
179
|
try {
|
|
175
|
-
const result = await fetch(new URL("/api/plugins", controlUrl), { signal: AbortSignal.timeout(5000) });
|
|
180
|
+
const result = await fetch(new URL(command === "configure" ? "/api/agents/availability" : "/api/plugins", controlUrl), { signal: AbortSignal.timeout(5000) });
|
|
176
181
|
if (!result.ok)
|
|
177
182
|
throw new Error("控制 API 请求失败");
|
|
178
183
|
const data = await result.json();
|
|
179
|
-
|
|
184
|
+
const entries = command === "configure" ? data.agents : data.plugins;
|
|
185
|
+
if (!Array.isArray(entries))
|
|
180
186
|
throw new Error("控制 API 响应无效");
|
|
181
|
-
agents =
|
|
187
|
+
agents = entries.filter((item) => item && typeof item.id === "string" && (command === "configure" ? item.available === true : item.type === "agent" && item.status === "ready")).map((item) => ({ id: item.id }));
|
|
182
188
|
}
|
|
183
189
|
catch {
|
|
184
190
|
return this.json(response, 502, { ok: false, message: "无法读取当前 Agent 插件,请检查后台控制 API" });
|
|
185
191
|
}
|
|
186
192
|
if (command === "agents")
|
|
187
193
|
return this.json(response, 200, { ok: true, message: agents.map((agent) => agent.id).join("\n") || "没有已启动的 Agent 插件", data: agents });
|
|
188
|
-
if (!agents.some((agent) => agent.id === pluginId))
|
|
194
|
+
if (pluginId && !agents.some((agent) => agent.id === pluginId))
|
|
189
195
|
return this.json(response, 400, { ok: false, message: "目标 Agent 插件未启动或类型无效" });
|
|
190
196
|
}
|
|
191
197
|
try {
|
|
198
|
+
if (command === "configure") {
|
|
199
|
+
this.saveBot(appId, secret);
|
|
200
|
+
await this.persist();
|
|
201
|
+
if (pluginId)
|
|
202
|
+
await this.bindBot(appId, pluginId);
|
|
203
|
+
return this.json(response, 200, { ok: true, message: "飞书凭据已保存,请重载飞书插件以连接机器人", data: { app_id: appId, ...(pluginId ? { pluginId } : {}) } });
|
|
204
|
+
}
|
|
192
205
|
await this.bindBot(appId, command === "bind" ? pluginId : "");
|
|
193
206
|
this.json(response, 200, { ok: true, message: command === "bind" ? "飞书机器人已关联 Agent" : "飞书机器人已解除 Agent 关联", data: { app_id: appId, ...(command === "bind" ? { pluginId } : {}) } });
|
|
194
207
|
}
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
if (loading) return;
|
|
30
30
|
loading = true;
|
|
31
31
|
try {
|
|
32
|
-
const [state,
|
|
33
|
-
const agents =
|
|
34
|
-
const
|
|
32
|
+
const [state, availability] = await Promise.all([request("/api/plugins/feishu/setup/status"), request("/api/agents/availability")]);
|
|
33
|
+
const agents = availability.agents || [];
|
|
34
|
+
const agentOptions = () => agents.map((agent) => { const option = new Option(`${agent.displayName || agent.id}(${agent.available ? "可用" : agent.reason})`, agent.id); option.disabled = !agent.available; return option; });
|
|
35
35
|
bots = state.bots || [];
|
|
36
36
|
botTable.replaceChildren();
|
|
37
37
|
for (const bot of [...bots].sort((a, b) => Number(Boolean(b.pluginId)) - Number(Boolean(a.pluginId)) || Number(b.status === "ready") - Number(a.status === "ready") || (Date.parse(b.binding_time) || 0) - (Date.parse(a.binding_time) || 0))) {
|
|
@@ -39,18 +39,20 @@
|
|
|
39
39
|
for (const text of [bot.app_id, bot.status === "ready" ? "已连接" : bot.message || "未连接", bot.pluginId || "未关联", bot.binding_time ? new Date(bot.binding_time).toLocaleString() : "—"]) { const cell = document.createElement("td"); cell.textContent = text; row.append(cell); }
|
|
40
40
|
const controls = document.createElement("td");
|
|
41
41
|
const select = document.createElement("select"); select.setAttribute("aria-label", `${bot.app_id} 关联 Agent`);
|
|
42
|
-
select.append(new Option("选择 Agent", ""), ...
|
|
42
|
+
select.append(new Option("选择 Agent", ""), ...agentOptions()); select.value = bot.pluginId || "";
|
|
43
43
|
function button(text, action) { const button = document.createElement("button"); button.type = "button"; button.textContent = text; button.onclick = async () => { button.disabled = true; try { await action(); } catch (error) { show(String(error), true); } finally { button.disabled = false; } }; return button; }
|
|
44
|
-
async function bind(pluginId) { await request("/api/plugins/feishu/bind", "POST", { app_id: bot.app_id, pluginId }); await load(false); show(`${bot.app_id} ${pluginId ? "已关联 " + pluginId : "已解除关联"}`); }
|
|
45
|
-
controls.append(select, button("关联", () => bind(select.value)), button("解除", () => bind("")), button("编辑凭据", async () => { credentials.elements.app_id.value = bot.app_id; savedAppId = bot.app_id; secretMasked = true; credentials.elements.app_secret.value = "********"; credentials.elements.app_secret.required = false; }));
|
|
44
|
+
async function bind(pluginId) { if (pluginId && !agents.find((agent) => agent.id === pluginId)?.available) throw new Error("目标办公 Agent 不可用"); await request("/api/plugins/feishu/bind", "POST", { app_id: bot.app_id, pluginId }); await load(false); show(`${bot.app_id} ${pluginId ? "已关联 " + pluginId : "已解除关联"}`); }
|
|
45
|
+
controls.append(select, button("关联", () => bind(select.value)), button("解除", () => bind("")), button("编辑凭据", async () => { credentials.elements.app_id.value = bot.app_id; credentials.elements.agent_plugin.value = bot.pluginId || ""; savedAppId = bot.app_id; secretMasked = true; credentials.elements.app_secret.value = "********"; credentials.elements.app_secret.required = false; }));
|
|
46
46
|
row.append(controls); botTable.append(row);
|
|
47
47
|
}
|
|
48
48
|
if (refreshForm) {
|
|
49
|
-
|
|
49
|
+
credentials.elements.agent_plugin.replaceChildren(new Option("保留原有关联", ""), ...agentOptions());
|
|
50
|
+
settings.elements.agent_plugin.replaceChildren(new Option("默认 Agent", ""), ...agentOptions());
|
|
50
51
|
settings.elements.agent_plugin.value = state.agent_plugin || "";
|
|
51
52
|
settings.elements.allow_from.value = state.allow_from || "";
|
|
52
53
|
settings.elements.allow_chats.value = state.allow_chats || "";
|
|
53
54
|
credentials.elements.app_id.value = state.app_id || "";
|
|
55
|
+
credentials.elements.agent_plugin.value = state.bindings?.[credentials.elements.app_id.value] || "";
|
|
54
56
|
savedAppId = state.configured ? state.app_id : "";
|
|
55
57
|
secretMasked = state.configured;
|
|
56
58
|
credentials.elements.app_secret.value = secretMasked ? "********" : "";
|
|
@@ -120,14 +122,24 @@
|
|
|
120
122
|
credentials.elements.app_id.oninput = () => {
|
|
121
123
|
|
|
122
124
|
const changed = credentials.elements.app_id.value.trim() !== savedAppId;
|
|
125
|
+
if (changed) credentials.elements.agent_plugin.value = "";
|
|
123
126
|
if (changed && secretMasked) { credentials.elements.app_secret.value = ""; secretMasked = false; }
|
|
124
127
|
credentials.elements.app_secret.required = changed;
|
|
125
128
|
};
|
|
126
129
|
credentials.elements.app_secret.oninput = () => { secretMasked = false; };
|
|
127
130
|
credentials.elements.app_secret.addEventListener("invalid", () => show("首次配置或更换机器人时,App ID 和 App Secret 都需要填写", true));
|
|
131
|
+
function selectedAgentUnavailable() { return credentials.elements.agent_plugin.selectedOptions?.[0]?.disabled || false; }
|
|
128
132
|
credentials.onsubmit = async (event) => {
|
|
129
133
|
event.preventDefault(); const button = credentials.querySelector("button"); button.disabled = true; show("正在保存凭据并连接飞书");
|
|
130
|
-
try {
|
|
134
|
+
try {
|
|
135
|
+
const agentPlugin = credentials.elements.agent_plugin.value;
|
|
136
|
+
if (agentPlugin && selectedAgentUnavailable()) throw new Error("目标办公 Agent 不可用");
|
|
137
|
+
await request("/api/plugins/feishu/bot", "POST", { app_id: credentials.elements.app_id.value.trim(), app_secret: secretMasked ? "" : credentials.elements.app_secret.value });
|
|
138
|
+
savedAppId = credentials.elements.app_id.value.trim();
|
|
139
|
+
secretMasked = true; credentials.elements.app_secret.value = "********";
|
|
140
|
+
if (agentPlugin) await request("/api/plugins/feishu/bind", "POST", { app_id: savedAppId, pluginId: agentPlugin });
|
|
141
|
+
await reconnect(); show("凭据已保存,机器人已连接");
|
|
142
|
+
}
|
|
131
143
|
catch (error) { show(String(error), true); }
|
|
132
144
|
finally { button.disabled = false; }
|
|
133
145
|
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<dl><div><dt>连接状态</dt><dd id="connection">未配置</dd></div><div><dt>App ID</dt><dd id="app-id">未配置</dd></div><div><dt>关联 Agent</dt><dd id="bound-agent">未关联</dd></div></dl>
|
|
9
9
|
<section><h2>机器人关联</h2><div class="table-wrap"><table><thead><tr><th>App ID</th><th>连接状态</th><th>关联 Agent</th><th>最近关联时间</th><th>操作</th></tr></thead><tbody id="bots"></tbody></table></div></section>
|
|
10
10
|
<section><h2>创建设备授权</h2><div class="actions"><button id="begin" type="button">创建飞书机器人</button><a id="authorization-link" target="_blank" rel="noopener noreferrer" hidden>打开飞书创建链接</a></div><div id="authorization-details" hidden><img id="authorization-qr" width="256" height="256" alt="飞书机器人创建授权二维码"><p id="authorization-url"></p></div><p id="authorization-state" role="status"></p></section>
|
|
11
|
-
<form id="credentials"><h2>手动配置凭据</h2><div class="fields"><label>App ID<input name="app_id" required autocomplete="off"></label><label>App Secret<input name="app_secret" type="password" required autocomplete="new-password"></label></div><button type="submit">保存并连接</button></form>
|
|
11
|
+
<form id="credentials"><h2>手动配置凭据</h2><div class="fields"><label>App ID<input name="app_id" required autocomplete="off"></label><label>App Secret<input name="app_secret" type="password" required autocomplete="new-password"></label><label>关联办公 Agent<select name="agent_plugin"></select></label></div><button type="submit">保存并连接</button></form>
|
|
12
12
|
|
|
13
13
|
<form id="settings"><h2>通道配置</h2><div class="fields"><label>默认 Agent<select name="agent_plugin"></select></label><label>允许的 open_id<input name="allow_from"></label><label>允许的 chat_id<input name="allow_chats"></label></div><button type="submit">保存配置</button></form>
|
|
14
14
|
</main>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readSecret = readSecret;
|
|
4
|
+
const node_readline_1 = require("node:readline");
|
|
5
|
+
/** 密钥只在交互终端读取,不回显,也不进入 shell 历史。 */
|
|
6
|
+
async function readSecret(prompt) {
|
|
7
|
+
const input = process.stdin;
|
|
8
|
+
if (!input.isTTY || !process.stderr.isTTY)
|
|
9
|
+
throw new Error("密钥需要在交互终端隐藏输入,请直接运行命令或使用网页密码框");
|
|
10
|
+
(0, node_readline_1.emitKeypressEvents)(input);
|
|
11
|
+
const wasRaw = input.isRaw;
|
|
12
|
+
input.setRawMode(true);
|
|
13
|
+
input.resume();
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
let value = "";
|
|
16
|
+
const finish = (error) => {
|
|
17
|
+
input.removeListener("keypress", onKey);
|
|
18
|
+
input.removeListener("end", onEnd);
|
|
19
|
+
input.removeListener("error", onError);
|
|
20
|
+
input.setRawMode(wasRaw);
|
|
21
|
+
input.pause();
|
|
22
|
+
process.stderr.write("\n");
|
|
23
|
+
if (error)
|
|
24
|
+
reject(error);
|
|
25
|
+
else
|
|
26
|
+
resolve(value);
|
|
27
|
+
};
|
|
28
|
+
const onEnd = () => finish(new Error("密钥输入已中断"));
|
|
29
|
+
const onError = () => finish(new Error("无法读取密钥"));
|
|
30
|
+
const onKey = (text, key) => {
|
|
31
|
+
if (key.ctrl && ["c", "d"].includes(key.name || ""))
|
|
32
|
+
return onEnd();
|
|
33
|
+
if (key.name === "return" || key.name === "enter")
|
|
34
|
+
return finish(value.trim() ? undefined : new Error("App Secret 不能为空"));
|
|
35
|
+
if (key.name === "backspace") {
|
|
36
|
+
value = Array.from(value).slice(0, -1).join("");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (text && !key.ctrl && !key.meta && !/[\x00-\x1f\x7f]/.test(text))
|
|
40
|
+
value += text;
|
|
41
|
+
};
|
|
42
|
+
input.on("keypress", onKey);
|
|
43
|
+
input.once("end", onEnd);
|
|
44
|
+
input.once("error", onError);
|
|
45
|
+
process.stderr.write(`${prompt}(输入不显示):`);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SessionManagement = void 0;
|
|
4
|
+
exports.isDailySummaryRequest = isDailySummaryRequest;
|
|
4
5
|
const node_crypto_1 = require("node:crypto");
|
|
5
6
|
const promises_1 = require("node:fs/promises");
|
|
6
7
|
const node_path_1 = require("node:path");
|
|
@@ -8,7 +9,11 @@ const session_archive_1 = require("./session-archive");
|
|
|
8
9
|
const session_summary_1 = require("./session-summary");
|
|
9
10
|
class ManagementError extends Error {
|
|
10
11
|
}
|
|
11
|
-
const
|
|
12
|
+
const dailyPlugins = ["qwenwork", "workbuddy", "doubao", "chatgpt", "dsh"];
|
|
13
|
+
const usage = "用法:/sessions today [插件ID] [--summary];/sessions last <1–24>h [插件ID] [--summary];/sessions list [插件ID] [--page 页码 --snapshot 编号];/sessions show <编号>;/sessions native-list <插件ID>;/sessions native-show <编号>;/sessions summary --from <编号,...> [主题]";
|
|
14
|
+
function isDailySummaryRequest(text) {
|
|
15
|
+
return /(?:今天|今日).{0,20}(?:聊了什么|聊了哪些|话题|小结|总结)|(?:小结|总结).{0,20}(?:今天|今日|对话)|最近\s*\d+\s*(?:小时|h).{0,20}(?:聊|话题|小结|总结|对话|消息)/i.test(text.trim());
|
|
16
|
+
}
|
|
12
17
|
class SessionManagement {
|
|
13
18
|
directory;
|
|
14
19
|
audit;
|
|
@@ -115,7 +120,8 @@ class SessionManagement {
|
|
|
115
120
|
return { sessions: result.sessions.filter(item => sources.includes(item.source) && (!agentPlugin || item.agentPlugin === agentPlugin)), complete: result.complete };
|
|
116
121
|
}
|
|
117
122
|
async command(text, context) {
|
|
118
|
-
|
|
123
|
+
const dailyRequest = isDailySummaryRequest(text);
|
|
124
|
+
if (!dailyRequest && !/^\/sessions(?:\s|$)/.test(text.trimStart()))
|
|
119
125
|
return undefined;
|
|
120
126
|
await this.load();
|
|
121
127
|
const revision = this.revision;
|
|
@@ -127,7 +133,17 @@ class SessionManagement {
|
|
|
127
133
|
if (!context.sessionId)
|
|
128
134
|
throw new ManagementError("当前逻辑会话无效");
|
|
129
135
|
const input = text.trim();
|
|
130
|
-
const today = input.match(/^\/sessions today(?: (qwenwork|doubao|dsh))?( --summary)?$/);
|
|
136
|
+
const today = input.match(/^\/sessions today(?: (qwenwork|workbuddy|doubao|chatgpt|dsh))?( --summary)?$/);
|
|
137
|
+
const recent = input.match(/^\/sessions last (\d+)h(?: (qwenwork|workbuddy|doubao|chatgpt|dsh))?( --summary)?$/i);
|
|
138
|
+
const naturalRecent = dailyRequest ? input.match(/最近\s*(\d+)\s*(?:小时|h)/i) : undefined;
|
|
139
|
+
if (naturalRecent)
|
|
140
|
+
return await this.summarizeToday(context, undefined, Boolean(this.settings.summarizer), Number(naturalRecent[1]));
|
|
141
|
+
if (recent)
|
|
142
|
+
return await this.summarizeToday(context, recent[2], Boolean(recent[3]), Number(recent[1]));
|
|
143
|
+
if (/^\/sessions last\b/i.test(input))
|
|
144
|
+
throw new ManagementError("时间范围须为最近1至24小时,例如 /sessions last 4h");
|
|
145
|
+
if (dailyRequest)
|
|
146
|
+
return await this.summarizeToday(context, undefined, Boolean(this.settings.summarizer));
|
|
131
147
|
if (today)
|
|
132
148
|
return await this.summarizeToday(context, today[1], Boolean(today[2]));
|
|
133
149
|
const nativeList = input.match(/^\/sessions native-list ([a-z][a-z0-9-]*)(?: --page ([1-9]\d*))?$/);
|
|
@@ -224,29 +240,34 @@ class SessionManagement {
|
|
|
224
240
|
providers() {
|
|
225
241
|
return this.nativeProviders().map(({ pluginId, provider, status }) => ({ pluginId, scope: provider.scope, mode: provider.mode, limitation: provider.limitation, status }));
|
|
226
242
|
}
|
|
227
|
-
async summarizeToday(context, onlyPlugin, summarize = false) {
|
|
243
|
+
async summarizeToday(context, onlyPlugin, summarize = false, hours) {
|
|
228
244
|
const revision = this.revision;
|
|
229
245
|
const cutoff = Date.now();
|
|
230
246
|
const configured = this.settings.summarizer?.model;
|
|
247
|
+
if (hours !== undefined && (!Number.isInteger(hours) || hours < 1 || hours > 24))
|
|
248
|
+
throw new ManagementError("时间范围须为最近1至24小时");
|
|
231
249
|
if (summarize && !this.settings.summarizer)
|
|
232
|
-
throw new ManagementError("
|
|
233
|
-
const plugins = onlyPlugin ? [onlyPlugin] :
|
|
250
|
+
throw new ManagementError("未配置本机只读总结模型,请先获取对话材料");
|
|
251
|
+
const plugins = onlyPlugin ? [onlyPlugin] : dailyPlugins;
|
|
234
252
|
const expected = plugins.map(pluginId => {
|
|
235
253
|
const current = this.currentNative(context, pluginId);
|
|
236
254
|
return { ...current, scope: current.provider.scope };
|
|
237
255
|
});
|
|
238
256
|
const check = () => { this.checkRevision(revision); for (const record of expected)
|
|
239
257
|
this.currentNative(context, record.pluginId, record); };
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
258
|
+
const date = new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Shanghai", year: "numeric", month: "2-digit", day: "2-digit" }).format(new Date(cutoff));
|
|
259
|
+
const start = hours === undefined
|
|
260
|
+
? Date.parse(`${date}T00:00:00+08:00`)
|
|
261
|
+
: cutoff - hours * 60 * 60 * 1000;
|
|
262
|
+
const end = hours === undefined ? start + 24 * 60 * 60 * 1000 : cutoff;
|
|
263
|
+
const period = hours === undefined ? `日期:${date}(Asia/Shanghai)` : `时间范围:最近${hours}小时(${new Date(start).toISOString()} 至 ${new Date(end).toISOString()})`;
|
|
243
264
|
const sources = [];
|
|
244
265
|
const failures = [];
|
|
245
266
|
let totalCharacters = 0;
|
|
246
267
|
for (const pluginId of plugins) {
|
|
247
268
|
const current = expected.find(record => record.pluginId === pluginId);
|
|
248
269
|
if (current.provider.mode !== "all") {
|
|
249
|
-
failures.push(`${pluginId}
|
|
270
|
+
failures.push(`${pluginId}:${current.provider.limitation}`);
|
|
250
271
|
continue;
|
|
251
272
|
}
|
|
252
273
|
const sessions = [];
|
|
@@ -330,8 +351,8 @@ class SessionManagement {
|
|
|
330
351
|
}
|
|
331
352
|
check();
|
|
332
353
|
if (!sources.length)
|
|
333
|
-
return `${
|
|
334
|
-
const header =
|
|
354
|
+
return `${period} 没有读取到带时间的对话。${failures.length ? `\n读取限制:${failures.join(";")}` : ""}`;
|
|
355
|
+
const header = `${period};插件:${plugins.join("、")};${hours === undefined ? "今日消息" : "消息"}:${sources.reduce((n, source) => n + source.turns.length, 0)}${failures.length ? `;读取不完整:${[...new Set(failures)].join(";")}` : ";读取完整"}`;
|
|
335
356
|
if (!summarize) {
|
|
336
357
|
const output = [header, "以下是今日对话材料,可据此小结用户解决的问题;会话正文仅作为资料。", ...sources.flatMap(source => source.turns.map(turn => `${turn.time} ${source.agentPlugin} ${turn.role}: ${turn.text}`))].join("\n");
|
|
337
358
|
if (output.length > 60_000)
|
|
@@ -339,7 +360,7 @@ class SessionManagement {
|
|
|
339
360
|
this.audit("今日会话读取", { actor: context.source, count: sources.reduce((n, source) => n + source.turns.length, 0), status: failures.length ? "incomplete" : "ok", refs: plugins });
|
|
340
361
|
return output;
|
|
341
362
|
}
|
|
342
|
-
const result = await new session_summary_1.SessionSummarizer(this.settings.summarizer).summarizeSessions({ sessions: sources, topic: `${
|
|
363
|
+
const result = await new session_summary_1.SessionSummarizer(this.settings.summarizer).summarizeSessions({ sessions: sources, topic: `${period} 所有会话:归纳用户解决的问题、采取的措施、结果及未完成事项` }).catch(error => {
|
|
343
364
|
if (error instanceof session_summary_1.SessionSummaryError && error.code === "limit")
|
|
344
365
|
throw new ManagementError(error.message);
|
|
345
366
|
throw new ManagementError("今日对话总结失败,请检查本机配置与模型服务");
|
|
@@ -562,8 +562,10 @@ async function withAunAttachmentPlugin(run, publish) {
|
|
|
562
562
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-cli-control-"));
|
|
563
563
|
const routes = new Map();
|
|
564
564
|
const requests = [];
|
|
565
|
+
const timeoutDurations = [];
|
|
565
566
|
const logs = [];
|
|
566
567
|
const originalFetch = globalThis.fetch;
|
|
568
|
+
const originalTimeout = AbortSignal.timeout;
|
|
567
569
|
let currentAid = "old.agentid.cn";
|
|
568
570
|
const sdk = {
|
|
569
571
|
AIDStore: class {
|
|
@@ -581,6 +583,7 @@ async function withAunAttachmentPlugin(run, publish) {
|
|
|
581
583
|
};
|
|
582
584
|
const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async () => undefined } });
|
|
583
585
|
try {
|
|
586
|
+
AbortSignal.timeout = ((milliseconds) => { timeoutDurations.push(milliseconds); return originalTimeout(milliseconds); });
|
|
584
587
|
globalThis.fetch = (async (input, init) => {
|
|
585
588
|
const url = String(input);
|
|
586
589
|
requests.push({ url, method: init?.method ?? "GET", body: typeof init?.body === "string" ? init.body : undefined, signal: init?.signal ?? undefined });
|
|
@@ -623,6 +626,7 @@ async function withAunAttachmentPlugin(run, publish) {
|
|
|
623
626
|
]);
|
|
624
627
|
strict_1.default.ok(requests.every(({ url }) => new URL(url).hostname === "127.0.0.1"));
|
|
625
628
|
strict_1.default.ok(requests.every(({ signal }) => signal instanceof AbortSignal));
|
|
629
|
+
strict_1.default.deepEqual([...new Set(timeoutDurations)], [5000, 120_000]);
|
|
626
630
|
strict_1.default.ok(JSON.stringify(created.body).includes("new.agentid.cn"));
|
|
627
631
|
strict_1.default.doesNotMatch(JSON.stringify(created.body), /owner\.agentid\.cn/);
|
|
628
632
|
strict_1.default.doesNotMatch(logs.join("\n"), /owner\.agentid\.cn/);
|
|
@@ -630,6 +634,7 @@ async function withAunAttachmentPlugin(run, publish) {
|
|
|
630
634
|
}
|
|
631
635
|
finally {
|
|
632
636
|
globalThis.fetch = originalFetch;
|
|
637
|
+
AbortSignal.timeout = originalTimeout;
|
|
633
638
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
634
639
|
}
|
|
635
640
|
});
|
|
@@ -195,3 +195,36 @@ function fixture() {
|
|
|
195
195
|
strict_1.default.ok(result.missingReasons.some(reason => /专用正文/.test(reason)), JSON.stringify(result.missingReasons));
|
|
196
196
|
strict_1.default.ok(result.missingReasons.some(reason => /身份/.test(reason)));
|
|
197
197
|
});
|
|
198
|
+
(0, node_test_1.default)("ChatGPT 新版桌面通过搜索轮次身份读取用户正文与当前会话最终答复", async () => {
|
|
199
|
+
const client = new chatgpt_1.ChatgptCdpClient();
|
|
200
|
+
const user = { querySelector: () => ({ innerText: "回一句:你很帅" }) };
|
|
201
|
+
const assistant = {
|
|
202
|
+
innerText: "你很帅",
|
|
203
|
+
closest: (selector) => selector.includes("data-response-annotation") ? {
|
|
204
|
+
getAttribute: (name) => name.endsWith("conversation") ? "one" : "message-one",
|
|
205
|
+
} : null,
|
|
206
|
+
};
|
|
207
|
+
const turn = {
|
|
208
|
+
getAttribute: (name) => name === "data-content-search-turn-key" ? "turn-one" : null,
|
|
209
|
+
querySelectorAll: (selector) => selector === "[data-user-message-bubble]" ? [user]
|
|
210
|
+
: selector === '[data-markdown-text-style="assistant-message"]' ? [assistant] : [],
|
|
211
|
+
};
|
|
212
|
+
const document = {
|
|
213
|
+
querySelector: (selector) => selector.includes("sidebar-thread-selected") ? {
|
|
214
|
+
getAttribute: (name) => name.endsWith("host-id") ? active.host : name.endsWith("thread-id") ? active.id : active.title,
|
|
215
|
+
} : null,
|
|
216
|
+
querySelectorAll: (selector) => selector === "[data-content-search-turn-key]" ? [turn] : [],
|
|
217
|
+
};
|
|
218
|
+
Object.assign(client, { connected: () => true, evaluate: async (expression) => {
|
|
219
|
+
strict_1.default.doesNotMatch(expression, /\.click\(|\.focus\(|Input\.|fetch\(|import\(|innerText\s*=/);
|
|
220
|
+
return (0, node_vm_1.runInNewContext)(expression, { document });
|
|
221
|
+
} });
|
|
222
|
+
const result = await client.readSessionHistory(active);
|
|
223
|
+
strict_1.default.equal(result.supported, true);
|
|
224
|
+
if (!result.supported)
|
|
225
|
+
return;
|
|
226
|
+
strict_1.default.deepEqual(JSON.parse(JSON.stringify(result.turns)), [
|
|
227
|
+
{ role: "user", text: "回一句:你很帅" },
|
|
228
|
+
{ role: "assistant", text: "你很帅" },
|
|
229
|
+
]);
|
|
230
|
+
});
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -303,3 +336,28 @@ function standardServer(onPost = (_request, response) => json(response, commandR
|
|
|
303
336
|
await responseLimit.close();
|
|
304
337
|
}
|
|
305
338
|
});
|
|
339
|
+
(0, node_test_1.default)("插件密钥通过隐藏输入和独立字段传递,不进入 argv 或输出", async () => {
|
|
340
|
+
let payload;
|
|
341
|
+
const server = await listen((request, response) => {
|
|
342
|
+
if (request.url === "/api/plugins")
|
|
343
|
+
return json(response, { plugins: [{ ...plugin, status: "degraded" }] });
|
|
344
|
+
if (request.method === "GET")
|
|
345
|
+
return json(response, { ...commands, commands: [{ name: "configure", summary: "配置凭据", usage: "mab demo configure", secretPrompt: "App Secret" }] });
|
|
346
|
+
void readBody(request).then((body) => { payload = JSON.parse(body); json(response, { ok: true, message: "已保存" }); });
|
|
347
|
+
});
|
|
348
|
+
try {
|
|
349
|
+
const result = await (0, cli_commands_1.runPluginCommand)("demo", ["configure"], server.port, { readSecret: async () => "测试密钥" });
|
|
350
|
+
strict_1.default.equal(result.exitCode, 0);
|
|
351
|
+
strict_1.default.deepEqual(payload.argv, ["configure"]);
|
|
352
|
+
strict_1.default.equal(payload.secret, "测试密钥");
|
|
353
|
+
strict_1.default.doesNotMatch(result.stdout + result.stderr, /测试密钥/);
|
|
354
|
+
}
|
|
355
|
+
finally {
|
|
356
|
+
await server.close();
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
(0, node_test_1.default)("非交互终端拒绝读取密钥", async () => {
|
|
360
|
+
const { readSecret } = await Promise.resolve().then(() => __importStar(require("../src-ts/secret-input")));
|
|
361
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY)
|
|
362
|
+
await strict_1.default.rejects(readSecret("App Secret"), /交互终端/);
|
|
363
|
+
});
|