agent-bridge-pa 0.0.0-stage → 1.0.7

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 CHANGED
@@ -1,3 +1,68 @@
1
- # Temporary Holding Version
1
+ # 个人助理管理 Agent
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `agent-bridge-pa` 是通过 AUN 使用的管理 Agent,运行时插件 ID 仍为 `personal-assistant`。安装并启动插件后,先创建一个专用 AID,再把该 AID 绑定到 `personal-assistant`;普通聊天和管理操作都在这个 AUN 会话中完成。
4
+
5
+ ## 配置
6
+
7
+ `endpoint`(完整的 OpenAI 兼容 `/chat/completions` 地址)、`model` 和 `apiKey` 是自然语言工具调度的可选配置。未配置模型时,精确的 `/assistant agent call <agent-id> <任务>` 命令仍可使用核心授权直接转发。`allowedPeers`、`allowedAgents` 默认均为 `[]`(界面留空),接受所有 AUN Peer,允许调用五个桌面 Agent、CLI 及后续安装的普通 Agent。绑定个人助理 AID 后无需进入 ABWeb 设置白名单即可测试,目标 Agent 仍须已就绪;自身和其他管理型 Agent 不可作为派活目标。两项名单留空接受所有,填写具体身份/插件 ID 后才限制;旧 `*` 配置保留兼容。`timeoutMs` 控制单次模型请求,默认 120 秒。密钥只保存在本机配置中,不要写入仓库或消息。白名单留空的规则要求核心 `>=1.0.78`,独立插件会拒绝不满足版本的旧核心。
8
+
9
+ 需要限制时,在 ABWeb 的个人助理配置中将 `allowedPeers` 改为指定的 AID、将 `allowedAgents` 改为允许的插件 ID。清空名单即恢复全部允许,保存和重载后保持此规则。升级保留已有具体名单;缺失字段或空数组均表示全部允许,无需填写 `*`。模型来源及 AID 绑定继续沿用原配置。
10
+
11
+ 个人助理要求支持插件默认配置的核心 `mu-agent-bridge >=1.0.74`,插件使用 Node.js 22.16 或更高版本。仅有旧核心版本时会拒绝加载不兼容的包。
12
+
13
+ 在 ABWeb 的“已安装插件 → 个人助理 → 配置模型”选择来源。插件列表来自 daemon 的能力接口,包含已声明模型来源的五个桌面 Agent(WorkBuddy、千问办公、豆包、DSH、ChatGPT)及 Codex CLI、Claude CLI;未就绪的插件保留显示并给出原因,不会自动替换已选来源。选择“OpenAI 兼容接口”时填写地址和模型名;本地免密服务可以留空密钥。保存表单只修改有变化的字段,空白密钥保留原值,隐藏的 Owner、能力及 AID 配置不会被覆盖。
14
+
15
+ 插件来源配置示例:
16
+
17
+ ```json
18
+ {
19
+ "modelProvider": "plugin",
20
+ "modelPlugin": "qwenwork",
21
+ "timeoutMs": 120000
22
+ }
23
+ ```
24
+
25
+ 上述示例省略白名单,使用默认的所有 AUN Peer 和所有已就绪的普通 Agent。
26
+
27
+ `modelPlugin` 决定个人助理使用哪个插件生成决策,`allowedAgents` 决定允许向哪些 Agent 派发任务,二者独立。桌面来源沿用桌面已登录账号和当前模型,无需填写 API Key;CLI 沿用 daemon 用户的默认模型。WorkBuddy 桌面来源需要 `transport: "cdp"`;CLI 需要先配置独立的绝对工作目录并登录。来源插件及核心都需更新到支持通用模型入口的版本。
28
+
29
+ 同一 AUN 会话的模型上下文保存在个人助理数据目录的 `model-sessions.json` 中,没有按闲置时长失效的计时器。重载后会继续读取;在普通私聊发送 `/new` 会生成新的会话,不带入旧内容(话题内 `/new` 沿用核心的拒绝规则)。最多保留最近 128 个会话,每会话 40 条消息及 120000 个序列化字符,超限按完整轮次删除旧历史。历史包含用户正文和工具结果,请按聊天记录保护本地文件;不写入模型配置密钥或附件二进制。写盘失败会记录警告并保留内存历史,但此时重载可能丢失尚未落盘的内容。
30
+
31
+ 模型会话使用原通道、接收 AID、Peer 与 thread 的逻辑会话及来源 ID 隔离;桌面映射保存在原插件数据目录中,重载后可恢复。模型请求与普通任务共用原插件队列,不发送聊天心跳,不为调用模型启动或重启桌面;用户有草稿、正在生成或等待确认时会拒绝切换。取消先结束个人助理等待;桌面仍在生成时继续占用队列,确认生成结束后才释放,不把迟到正文送入后续会话。
32
+
33
+ 模型返回的 JSON 继续经过个人助理原六轮、八次工具调用上限及参数、工具名、白名单校验,不把 Bridge 管理对象、通道回调或附件交给模型来源。失败回复区分来源、阶段和错误码,固定宿主原因可见,任意异常正文不直接回显。
34
+
35
+ 桌面模型来源沿用客户端已有的文件和工具权限,Bridge 无法统一关闭这些原生能力。个人助理的管理白名单只约束 Bridge 管理工具,不代表桌面客户端运行在无工具环境中。
36
+
37
+ 也可设置 `modelProvider: "claude-cli"`,复用本机 `claude-cli` 插件的默认模型和登录,无需填写 `endpoint`、`model`、`apiKey`;未设置来源时仍使用原 OpenAI 兼容接口。先为 Claude CLI 插件配置已存在的独立绝对工作目录 `cwd`,并在 daemon 的运行用户下完成 CLI 登录。个人助理通过插件内部接口请求结构化决策,不传 `--model`,由 CLI 的用户配置决定默认模型。
38
+
39
+ Claude 模型请求禁用 CLI 原生工具、MCP 和 hooks,且不保存 CLI 会话;管理工具仍由个人助理按既有授权与白名单执行。普通 Claude CLI 聊天保留原行为。Claude 模式还要求核心提供 `modelInputGuard` 隔离能力保证;旧核心会使个人助理降级并提示升级,不会调用 CLI。CLI 需要支持 `--json-schema` 等隔离参数,不支持时会明确失败,不能回退为普通 CLI 任务。
40
+
41
+ 远程能力与协调还需配置 `selfAid`(个人助理 AID)和 `ecPath`(可选,默认 `ec`)。`abilityProviders` 明确列出可访问的 Provider;首期只允许声明为纯只读且无需认证/确认的 Ability。pending 请求只能查询,不会自动重发。
42
+
43
+ 跨 Agent 派活沿用 `allowedAgents`,并要求目标 Agent 已就绪且 AID 与核心报告的绑定一致。每次派活会创建独立 thread;发送回执只表示已受理,需用 `coordination_status` 查询 thread history。进程只保留最近 128 个本机派活 thread 的查询关联,不会自动重派。
44
+
45
+ Owner 共享资料通过 `ownerAid` 与唯一的规范绝对路径 `ownerContextPath` 配置,缓存有效期由 `ownerContextTtlMs` 控制(默认 60 秒,最多 5 分钟)。资料会作为参考内容注入模型,不具备指令或授权效力。可直接发送 `/assistant context refresh` 强制重读;读取失败会保留之前成功的缓存。未设置 Owner 配置时不读取共享资料。
46
+
47
+ ## 创建和绑定专用 AID
48
+
49
+ 先用原有 CLI 或管理后台创建并绑定专用身份;个人助理尚未绑定时无法接收自己的管理命令。创建时必须提供 Owner AID:
50
+
51
+ ```text
52
+ mab aun create assistant.agentid.cn --owner owner.agentid.cn
53
+ mab aun bind assistant.agentid.cn personal-assistant
54
+ ```
55
+
56
+ 创建失败不会自动重试,也不会在回复中显示密钥库路径、seed 或 token。绑定成功后,将 AUN 配置中的 `agent_plugin` 指向 `personal-assistant`,若配置了非空 `allowedPeers`,需确保发消息的 Peer 在名单中。个人助理投入使用后,可用 `/assistant aid create <aid> [--owner <owner-aid>]` 和 `/assistant aid bind <aid> <agent-id> [--replace]` 显式管理后续身份;省略 Owner 时沿用 AUN 的本地默认 Owner。`--replace` 只替换目标 Agent 自己原有关联;已经绑定到其他 Agent 的 AID 一律拒绝转移,请先通过原管理命令解除旧绑定。
57
+
58
+ ## 能力
59
+
60
+ 经授权的请求可以查询 Agent 能力和 AID、调用白名单中的 Agent、查询 AUN 身份、执行只读 `/sessions` 命令、生成今天工作的小结,以及查看个人助理状态。模型只能通过固定工具访问这些能力,不能提交 URL、Peer、session 或 principal 覆盖字段。
61
+
62
+ 需要确定性地转发给某个已授权 Agent 时,可直接发送 `/assistant agent call <agent-id> <任务>`,例如 `/assistant agent call workbuddy 请仅回复联调标记`。核心会再次校验 Agent 白名单和就绪状态;此命令不经过模型工具判断。
63
+
64
+ 用自然语言询问今天的工作小结时,个人助理通过 `sessions_today` 读取已归档记录,归档采集默认关闭。未归档记录和原生插件历史不会纳入该小结;显式 `/sessions today --summary` 按核心命令读取插件提供的原生历史。会话查询的 `allowFrom` 默认 `[]`(留空接受所有),各项查询和小结均允许读取本机其他 Peer 的记录,旧配置缺少此字段也按全部允许处理。需要限制时设置非空的具体 Peer 名单,名单外的来源使用精确的 `grants`/`nativeGrants`。清单编号仍绑定当前来源和逻辑会话;`/new` 后重新列举即可,默认无需再次授权。只读总结器使用核心独立的 summarizer 配置,不会调用普通 Agent 执行总结。
65
+
66
+ 个人助理仅支持文本输入。模型端点必须是本地管理员配置的 HTTPS 或 HTTP 地址,插件禁止跟随重定向;模型响应和工具结果都有大小及调用次数限制。
67
+
68
+ 子 Agent 返回的真实附件随个人助理最终回复回传,模型仅看到附件元数据;是否发送成功由通道确认。即使后续模型调用失败,已经取得的附件也会随错误说明返回。
@@ -0,0 +1,23 @@
1
+ {
2
+ "type": "object",
3
+ "default": { "allowedPeers": [], "allowedAgents": [] },
4
+ "properties": {
5
+ "modelProvider": { "type": "string", "enum": ["openai", "claude-cli", "plugin"], "default": "openai", "description": "模型来源;plugin 使用已安装插件的当前模型" },
6
+ "modelPlugin": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,63}$", "description": "模型来源插件 ID" },
7
+ "endpoint": { "type": "string", "minLength": 1, "maxLength": 2048, "pattern": "^https?://[^\\s]+/chat/completions$", "description": "OpenAI 兼容 chat/completions 完整地址" },
8
+ "aid": { "type": "string", "maxLength": 256, "pattern": "^(?:[A-Za-z0-9][A-Za-z0-9._-]+)?$", "description": "绑定到个人助理的 AUN AID,由核心在绑定时维护;空字符串表示解绑" },
9
+ "model": { "type": "string", "minLength": 1, "maxLength": 256, "description": "模型名称" },
10
+ "apiKey": { "type": "string", "minLength": 1, "maxLength": 4096, "secret": true, "writeOnly": true, "description": "模型服务密钥,仅保存在本机配置中" },
11
+ "allowedPeers": { "type": "array", "uniqueItems": true, "items": { "type": "string", "pattern": "^(?:\\*|[A-Za-z0-9][A-Za-z0-9._-]{2,255})$" }, "default": [], "description": "留空接受所有 AUN Peer;填写具体 AID 后限制来源" },
12
+ "allowedAgents": { "type": "array", "uniqueItems": true, "items": { "type": "string", "pattern": "^(?:\\*|[a-z0-9][a-z0-9-]{0,63})$" }, "default": [], "description": "留空允许调用所有已安装且就绪的普通 Agent;填写具体插件 ID 后限制目标" },
13
+ "timeoutMs": { "type": "integer", "minimum": 1000, "maximum": 120000, "default": 120000, "description": "单次模型请求最长时间(毫秒)" },
14
+ "ecPath": { "type": "string", "minLength": 1, "maxLength": 2048, "description": "EvolCore ec 命令路径,默认使用 PATH 中的 ec" },
15
+ "selfAid": { "type": "string", "maxLength": 256, "pattern": "^(?:[A-Za-z0-9][A-Za-z0-9._-]+)?$", "description": "个人助理自身的 AUN AID;未配置时关闭 Ability 和协调能力" },
16
+ "abilityProviders": { "type": "array", "uniqueItems": true, "items": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{2,255}$" }, "default": [], "description": "允许读取与调用只读 Ability 的 Provider AID 白名单" },
17
+ "ownerAid": { "type": "string", "maxLength": 256, "pattern": "^(?:[A-Za-z0-9][A-Za-z0-9._-]+)?$", "description": "Owner 共享上下文的固定来源 AID" },
18
+ "ownerContextPath": { "type": "string", "maxLength": 2048, "pattern": "^/[^\\\\]*$", "description": "Owner AUN FS 中固定的规范绝对路径" },
19
+ "ownerContextTtlMs": { "type": "integer", "minimum": 1000, "maximum": 300000, "default": 60000, "description": "Owner 共享上下文缓存有效期(毫秒)" }
20
+ },
21
+ "required": ["allowedPeers", "allowedAgents"],
22
+ "additionalProperties": false
23
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentOperationError = exports.AgentError = void 0;
4
+ exports.abortable = abortable;
5
+ exports.errorDetails = errorDetails;
6
+ async function abortable(promise, signal) {
7
+ if (!signal)
8
+ return promise;
9
+ if (signal.aborted) {
10
+ // promise 已在调用方启动,先观察其拒绝,避免预取消时产生未处理拒绝。
11
+ void promise.catch(() => undefined);
12
+ signal.throwIfAborted();
13
+ }
14
+ return new Promise((resolve, reject) => {
15
+ let settled = false;
16
+ const cleanup = () => signal.removeEventListener("abort", abort);
17
+ const abort = () => {
18
+ if (settled)
19
+ return;
20
+ settled = true;
21
+ cleanup();
22
+ reject(signal.reason);
23
+ };
24
+ signal.addEventListener("abort", abort, { once: true });
25
+ promise.then((value) => {
26
+ if (settled)
27
+ return;
28
+ settled = true;
29
+ cleanup();
30
+ resolve(value);
31
+ }, (error) => {
32
+ if (settled)
33
+ return;
34
+ settled = true;
35
+ cleanup();
36
+ reject(error);
37
+ });
38
+ });
39
+ }
40
+ class AgentError extends Error {
41
+ constructor(message, options) {
42
+ super(message, options);
43
+ this.name = "AgentError";
44
+ }
45
+ }
46
+ exports.AgentError = AgentError;
47
+ /** 保留底层错误码,避免只留下 fetch failed 或超时这类表层提示。 */
48
+ function errorDetails(error) {
49
+ const parts = [];
50
+ const seen = new Set();
51
+ for (let current = error; current && !seen.has(current) && seen.size < 5;) {
52
+ seen.add(current);
53
+ const value = current;
54
+ const message = typeof value.message === "string" ? value.message : String(current);
55
+ parts.push(`${message}${value.code ? `(${String(value.code)})` : ""}`);
56
+ current = value.cause;
57
+ }
58
+ return parts.join(";原因:") || String(error);
59
+ }
60
+ class AgentOperationError extends AgentError {
61
+ code;
62
+ stage;
63
+ constructor(code, stage, cause) {
64
+ super(`${code}:${stage}失败:${errorDetails(cause)}`, { cause });
65
+ this.code = code;
66
+ this.stage = stage;
67
+ }
68
+ }
69
+ exports.AgentOperationError = AgentOperationError;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_TOTAL_ATTACHMENT_BYTES = exports.MAX_ATTACHMENTS = exports.MAX_ATTACHMENT_BYTES = void 0;
4
+ exports.detectImageContentType = detectImageContentType;
5
+ exports.attachmentFilename = attachmentFilename;
6
+ exports.validateAttachment = validateAttachment;
7
+ exports.validateAttachments = validateAttachments;
8
+ exports.asAttachments = asAttachments;
9
+ exports.MAX_ATTACHMENT_BYTES = 32 * 1024 * 1024;
10
+ exports.MAX_ATTACHMENTS = 8;
11
+ exports.MAX_TOTAL_ATTACHMENT_BYTES = 64 * 1024 * 1024;
12
+ function detectImageContentType(data) {
13
+ if (data.length >= 8 && data.slice(0, 8).every((value, index) => value === [137, 80, 78, 71, 13, 10, 26, 10][index]))
14
+ return "image/png";
15
+ if (data.length >= 3 && data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff)
16
+ return "image/jpeg";
17
+ if (data.length >= 12 && String.fromCharCode(...data.slice(0, 4)) === "RIFF" && String.fromCharCode(...data.slice(8, 12)) === "WEBP")
18
+ return "image/webp";
19
+ return undefined;
20
+ }
21
+ function attachmentFilename(filename) {
22
+ if (typeof filename !== "string" || /[\u0000-\u001f\u007f]/.test(filename))
23
+ throw new Error("附件文件名无效");
24
+ const name = filename.split(/[\\/]/).pop() ?? "";
25
+ if (!name.trim() || name === "." || name === "..")
26
+ throw new Error("附件文件名无效");
27
+ return name;
28
+ }
29
+ function validateAttachment(item) {
30
+ if (!item || !(item.data instanceof Uint8Array) || item.data.byteLength === 0)
31
+ throw new Error("附件内容不能为空");
32
+ if (item.data.byteLength > exports.MAX_ATTACHMENT_BYTES)
33
+ throw new Error("单个附件不能超过 32 MiB");
34
+ attachmentFilename(item.filename);
35
+ if (item.kind !== "image" && item.kind !== "file")
36
+ throw new Error("附件类型无效");
37
+ if (typeof item.contentType !== "string" || !/^[A-Za-z0-9!#$&^_.+-]+\/[A-Za-z0-9!#$&^_.+-]+$/.test(item.contentType))
38
+ throw new Error("附件 MIME 类型无效");
39
+ if (item.kind === "image") {
40
+ const detected = detectImageContentType(item.data);
41
+ if (!detected || detected !== item.contentType.toLowerCase())
42
+ throw new Error("图片内容与 MIME 类型不一致");
43
+ }
44
+ }
45
+ function validateAttachments(items) {
46
+ if (!items)
47
+ return;
48
+ if (!Array.isArray(items))
49
+ throw new Error("附件列表无效");
50
+ if (items.length > exports.MAX_ATTACHMENTS)
51
+ throw new Error("单条消息最多包含 8 个附件");
52
+ let total = 0;
53
+ for (const item of items) {
54
+ validateAttachment(item);
55
+ total += item.data.byteLength;
56
+ }
57
+ if (total > exports.MAX_TOTAL_ATTACHMENT_BYTES)
58
+ throw new Error("单条消息附件总大小不能超过 64 MiB");
59
+ }
60
+ function asAttachments(images) {
61
+ return (images ?? []).map((image) => ({ ...image, kind: "image" }));
62
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveRuntimePort = resolveRuntimePort;
4
+ function resolveRuntimePort(config, runtime, pluginId, envName, fallback) {
5
+ const injected = runtime?.ports?.[pluginId];
6
+ if (typeof injected === "number" && Number.isInteger(injected) && injected > 0)
7
+ return injected;
8
+ const configured = config.cdpPort;
9
+ if (typeof configured === "number" && Number.isInteger(configured) && configured > 0)
10
+ return configured;
11
+ const environment = Number(process.env[envName] ?? "");
12
+ return Number.isInteger(environment) && environment > 0 ? environment : fallback;
13
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.responseSchema = void 0;
4
+ exports.claudeCliCompletion = claudeCliCompletion;
5
+ const tools_1 = require("./tools");
6
+ exports.responseSchema = {
7
+ type: "object", additionalProperties: false, required: ["role", "content", "tool_calls"],
8
+ properties: {
9
+ role: { type: "string", enum: ["assistant"] },
10
+ content: { type: ["string", "null"], maxLength: 32_000 },
11
+ tool_calls: { type: "array", maxItems: 8, items: {
12
+ type: "object", additionalProperties: false, required: ["id", "type", "function"],
13
+ properties: {
14
+ id: { type: "string", minLength: 1, maxLength: 128 }, type: { type: "string", enum: ["function"] },
15
+ function: { type: "object", additionalProperties: false, required: ["name", "arguments"], properties: {
16
+ name: { type: "string", enum: [...tools_1.TOOL_NAMES] }, arguments: { type: "string", maxLength: 64_000 },
17
+ } },
18
+ },
19
+ } },
20
+ },
21
+ };
22
+ function claudeCliCompletion(callPlugin, timeoutMs) {
23
+ return async (messages, signal) => {
24
+ const response = await callPlugin("claude-cli", {
25
+ task: JSON.stringify({ instructions: "你是个人助理的模型推理器。按照 messages 中的系统要求处理最后一条用户消息;tools 仅描述宿主提供的管理工具。需要查询或执行任务时返回 tool_calls,待工具结果返回后再回答;不要编造结果。每次调用使用不同 id。最终回答时 tool_calls 为空数组。仅返回 Schema 规定的 JSON。", messages, tools: (0, tools_1.toolDefinitions)() }),
26
+ modelResponseSchema: exports.responseSchema, timeoutMs, signal,
27
+ });
28
+ return response.text;
29
+ };
30
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCoordinator = createCoordinator;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const node_util_1 = require("node:util");
6
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
7
+ const isAid = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{2,255}$/.test(value);
8
+ const isAgentId = (value) => typeof value === "string" && /^[a-z0-9][a-z0-9-]{0,63}$/.test(value);
9
+ const isThreadId = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(value);
10
+ function createCoordinator(options) {
11
+ if (!isAid(options.selfAid))
12
+ throw new Error("selfAid 无效");
13
+ const allowed = new Map();
14
+ for (const agent of options.allowedAgents) {
15
+ if (!isAgentId(agent.id) || !Array.isArray(agent.aids) || agent.aids.some((aid) => !isAid(aid)))
16
+ throw new Error("Agent 白名单无效");
17
+ allowed.set(agent.id, new Set(agent.aids));
18
+ }
19
+ const run = options.execFile ?? (async (file, args) => execFileAsync(file, args, { timeout: 30_000, maxBuffer: 1024 * 1024, encoding: "utf8", windowsHide: true }));
20
+ const command = async (args) => {
21
+ const { stdout } = await run(options.ecPath ?? "ec", args);
22
+ let value;
23
+ try {
24
+ value = JSON.parse(stdout);
25
+ }
26
+ catch {
27
+ throw new Error("EvolCore 返回了无效 JSON");
28
+ }
29
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.ok === false)
30
+ throw new Error("EvolCore 命令失败");
31
+ return value;
32
+ };
33
+ return {
34
+ async dispatch({ agentId, aid, task }) {
35
+ if (!isAgentId(agentId) || !isAid(aid) || !allowed.get(agentId)?.has(aid))
36
+ throw new Error("目标 Agent/AID 不在白名单中");
37
+ if (typeof task !== "string" || !task.trim() || task.length > 20_000)
38
+ throw new Error("任务长度无效");
39
+ const thread = await command(["thread", "create", aid, "--name", `个人助理派活-${agentId}`, "--format", "json"]);
40
+ const threadId = thread.thread_id ?? thread.threadId ?? thread.id;
41
+ if (!isThreadId(threadId))
42
+ throw new Error("EvolCore 未返回有效 threadId");
43
+ await command(["msg", "send", options.selfAid, aid, "--text", task, "--thread", threadId, "--return", "required", "--format", "json"]);
44
+ return { status: "pending", agentId, aid, threadId };
45
+ },
46
+ async getStatus({ aid, threadId }) {
47
+ if (!isAid(aid))
48
+ throw new Error("AID 无效");
49
+ if (!isThreadId(threadId))
50
+ throw new Error("threadId 无效");
51
+ const history = await command(["thread", "history", threadId, "--format", "json"]);
52
+ const state = history.status ?? history.state;
53
+ const status = state === "completed" || state === "failed" || state === "cancelled" ? state : "pending";
54
+ return { status, aid, threadId, history };
55
+ },
56
+ };
57
+ }
@@ -0,0 +1,263 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PersonalAssistantPlugin = exports.personalAssistantManifest = void 0;
4
+ const attachments_1 = require("../../attachments");
5
+ const agent_1 = require("../../agent");
6
+ const node_crypto_1 = require("node:crypto");
7
+ const model_client_1 = require("./model-client");
8
+ const tools_1 = require("./tools");
9
+ const remote_ability_1 = require("./remote-ability");
10
+ const owner_context_1 = require("./owner-context");
11
+ const claude_cli_model_1 = require("./claude-cli-model");
12
+ const session_memory_1 = require("./session-memory");
13
+ exports.personalAssistantManifest = {
14
+ id: "personal-assistant",
15
+ version: "1.0.7",
16
+ type: "agent",
17
+ displayName: "个人助理",
18
+ entry: "dist/src-ts/plugins/personal-assistant/index.js",
19
+ apiVersion: "1",
20
+ process: "in-process",
21
+ capabilities: ["chat", "management", "health"],
22
+ configSchema: "config.schema.json",
23
+ };
24
+ class PersonalAssistantPlugin {
25
+ manifest = exports.personalAssistantManifest;
26
+ sessionOperations = ["new"];
27
+ context;
28
+ client;
29
+ state = "failed";
30
+ stateMessage = "插件尚未启动";
31
+ fetcher;
32
+ ownerContext;
33
+ threads = new Map();
34
+ constructor(options = {}) { this.fetcher = options.fetch; }
35
+ async activate(context) {
36
+ this.context = context;
37
+ const config = context.config;
38
+ try {
39
+ const endpoint = validEndpoint(config.endpoint);
40
+ if (!Array.isArray(config.allowedPeers) || !Array.isArray(config.allowedAgents) || !config.allowedPeers.every(value => value === "*" || isAid(value)) || !config.allowedAgents.every(value => value === "*" || isPluginId(value)))
41
+ throw new Error("allowedPeers 或 allowedAgents 配置无效");
42
+ if (config.modelProvider !== undefined && !["openai", "claude-cli", "plugin"].includes(config.modelProvider))
43
+ throw new Error("不支持该模型来源");
44
+ const claudeMode = config.modelProvider === "claude-cli";
45
+ const pluginMode = config.modelProvider === "plugin";
46
+ const modelPlugin = pluginMode && isPluginId(config.modelPlugin) ? config.modelPlugin : undefined;
47
+ if (claudeMode && context.modelInputGuard !== true)
48
+ throw new Error("当前核心不支持安全的 Claude 模型调用,请升级核心");
49
+ if (pluginMode && !modelPlugin)
50
+ throw new Error("请选择有效的模型来源插件");
51
+ if (pluginMode && typeof context.callModelProvider !== "function")
52
+ throw new Error("当前核心不支持通用模型来源,请升级核心");
53
+ const modelConfigured = claudeMode || pluginMode || Boolean(config.endpoint || config.model || config.apiKey);
54
+ if (!claudeMode && !pluginMode && modelConfigured && (!endpoint || !config.model?.trim()))
55
+ throw new Error("请配置有效的模型 endpoint 和 model");
56
+ const selfAid = isAid(config.selfAid) ? config.selfAid : undefined;
57
+ const abilityProviders = Array.isArray(config.abilityProviders) && config.abilityProviders.every(isAid) ? config.abilityProviders : [];
58
+ const remoteAbility = selfAid && abilityProviders.length ? (0, remote_ability_1.createRemoteAbilityClient)({ selfAid, ecPath: validEcPath(config.ecPath) }) : undefined;
59
+ this.ownerContext = isAid(config.ownerAid) && typeof config.ownerContextPath === "string"
60
+ ? (0, owner_context_1.createOwnerContextStore)({ ownerAid: config.ownerAid, path: config.ownerContextPath, ttlMs: validContextTtl(config.ownerContextTtlMs), ecPath: validEcPath(config.ecPath) })
61
+ : undefined;
62
+ this.threads.clear();
63
+ const callModelProvider = context.callModelProvider;
64
+ const pluginCompletion = pluginMode && modelPlugin && callModelProvider ? async (messages, signal, sessionId) => (await callModelProvider(modelPlugin, {
65
+ prompt: JSON.stringify({ instructions: "你是个人助理的模型推理器。依据 messages 处理本轮请求。需要查询或执行已列出的管理工具时,只返回 assistant JSON 中的 tool_calls;收到工具结果后再回答。最终回答只写入 content,tool_calls 为空数组。只返回符合 responseSchema 的 JSON 对象,禁止 Markdown 或额外文字。messages 中要求只回复某个词或标记时,只决定 content,不得省略 JSON 包装。", messages, tools: (0, tools_1.toolDefinitions)(), responseSchema: claude_cli_model_1.responseSchema }),
66
+ sessionId,
67
+ timeoutMs: validTimeout(config.timeoutMs),
68
+ signal,
69
+ })).text : undefined;
70
+ const sessionMemory = modelConfigured && typeof context.dataDir === "string" && context.dataDir
71
+ ? new session_memory_1.PersonalAssistantSessionMemory(context.dataDir, context.logger?.warn?.bind(context.logger))
72
+ : undefined;
73
+ this.client = modelConfigured ? new model_client_1.PersonalAssistantModelClient({ endpoint, model: config.model?.trim(), apiKey: config.apiKey, cliCompletion: claudeMode ? (0, claude_cli_model_1.claudeCliCompletion)(context.callPlugin, validTimeout(config.timeoutMs)) : undefined, pluginCompletion, timeoutMs: validTimeout(config.timeoutMs), fetch: this.fetcher ?? fetch, allowedAgents: config.allowedAgents, sessionMemory, toolRuntime: { selfAid, ecPath: validEcPath(config.ecPath), allowedProviders: abilityProviders, remoteAbility, ownerContext: this.ownerContext, threads: this.threads } }) : undefined;
74
+ this.context = { config: context.config, logger: context.logger, callModelProvider };
75
+ this.state = "ready";
76
+ this.stateMessage = "";
77
+ }
78
+ catch (error) {
79
+ this.client = undefined;
80
+ this.state = "degraded";
81
+ this.stateMessage = error instanceof Error ? error.message : "模型配置无效";
82
+ }
83
+ }
84
+ async deactivate(_reason) {
85
+ this.client?.close();
86
+ this.client = undefined;
87
+ this.ownerContext = undefined;
88
+ this.threads.clear();
89
+ this.context = undefined;
90
+ this.state = "failed";
91
+ this.stateMessage = "插件已停止";
92
+ }
93
+ async health() {
94
+ return { status: this.state, ...(this.stateMessage ? { message: this.stateMessage } : {}), details: { modelConfigured: Boolean(this.client) } };
95
+ }
96
+ async controlSession(request) {
97
+ if (request.operation !== "new")
98
+ throw new Error("个人助理只支持新建会话");
99
+ return { text: "个人助理已创建新会话", sessionId: (0, node_crypto_1.randomUUID)() };
100
+ }
101
+ async handle(request) {
102
+ const management = request.management;
103
+ if (!management)
104
+ return { text: "请通过已授权的 AUN 身份访问管理能力。" };
105
+ const command = (0, tools_1.parseExplicitCommand)(request.task);
106
+ if (command?.kind === "invalid")
107
+ return { text: "不支持该管理命令,请检查命令格式后重试。" };
108
+ if (command?.kind === "sessions") {
109
+ try {
110
+ return { text: await management.runSessionCommand(command.command) };
111
+ }
112
+ catch {
113
+ return { text: "会话管理命令执行失败,请检查权限和命令格式。" };
114
+ }
115
+ }
116
+ if (command?.kind === "context-refresh") {
117
+ if (!this.ownerContext)
118
+ return { text: "Owner 共享上下文尚未配置。" };
119
+ try {
120
+ const refreshed = await this.ownerContext.refresh();
121
+ return { text: `Owner 共享上下文已刷新(${refreshed.content.length} 字符)。` };
122
+ }
123
+ catch {
124
+ return { text: "Owner 共享上下文刷新失败,已保留之前成功读取的缓存。" };
125
+ }
126
+ }
127
+ if (command?.kind === "agent-call") {
128
+ try {
129
+ const result = await management.callAgent(command.agentId, command.task);
130
+ return { ...result, sessionId: request.sessionId };
131
+ }
132
+ catch {
133
+ return { text: `调用 Agent ${command.agentId} 失败,请检查白名单和目标状态。` };
134
+ }
135
+ }
136
+ if (command?.kind === "create") {
137
+ try {
138
+ return { text: safeJson(await management.createAunIdentity({ aid: command.aid, ...(command.ownerAid ? { ownerAid: command.ownerAid } : {}) })) };
139
+ }
140
+ catch {
141
+ return { text: "AUN 身份创建未能确认完成;身份可能已创建,请先查询 AID 列表确认状态,不要重复创建。" };
142
+ }
143
+ }
144
+ if (command?.kind === "bind") {
145
+ try {
146
+ return { text: safeJson(await management.bindAunIdentity({ aid: command.aid, agentId: command.agentId, replace: command.replace })) };
147
+ }
148
+ catch {
149
+ return { text: "AUN 身份绑定失败,请检查身份、目标和权限。" };
150
+ }
151
+ }
152
+ if (!this.client || this.state !== "ready")
153
+ return { text: "个人助理模型尚未配置,暂时无法处理请求。" };
154
+ const attachments = [];
155
+ const scopedManagement = { ...management, callAgent: async (id, task) => {
156
+ const result = await management.callAgent(id, task);
157
+ const items = result.attachments?.length ? result.attachments : (0, attachments_1.asAttachments)(result.images);
158
+ (0, attachments_1.validateAttachments)([...attachments, ...items]);
159
+ attachments.push(...items);
160
+ return result;
161
+ } };
162
+ const requestId = (0, node_crypto_1.randomUUID)();
163
+ const startedAt = Date.now();
164
+ const provider = this.context?.config.modelProvider === "plugin"
165
+ ? String(this.context.config.modelPlugin ?? "未指定插件")
166
+ : String(this.context?.config.modelProvider ?? "openai");
167
+ const sessionKey = (0, node_crypto_1.createHash)("sha256").update(`${this.manifest.id}\0${provider}\0${request.sessionId ?? (0, node_crypto_1.randomUUID)()}`).digest("hex");
168
+ try {
169
+ let context;
170
+ try {
171
+ const ownerContext = await this.ownerContext?.get();
172
+ if (ownerContext)
173
+ context = `读取状态:${ownerContext.status};缓存年龄:${ownerContext.ageMs} 毫秒。\n${ownerContext.content}`;
174
+ }
175
+ catch { /* 共享资料不可用时保留常规助理能力 */ }
176
+ const modelSessionId = sessionKey;
177
+ const result = await this.client.complete(request.task, scopedManagement, request.signal, requestTimeout(request.timeoutMs), context, modelSessionId);
178
+ this.context?.logger?.info("个人助理请求完成", { requestId, provider, durationMs: Date.now() - startedAt });
179
+ return { text: result, ...(attachments.length ? { attachments } : {}) };
180
+ }
181
+ catch (error) {
182
+ const detail = modelFailure(error, provider);
183
+ this.context?.logger?.warn("个人助理请求失败", { requestId, provider, session: sessionKey, stage: detail.stage, code: detail.code, durationMs: Date.now() - startedAt, ...(detail.message ? { error: detail.message } : {}) });
184
+ return { text: detail.text, ...(attachments.length ? { attachments } : {}) };
185
+ }
186
+ }
187
+ }
188
+ exports.PersonalAssistantPlugin = PersonalAssistantPlugin;
189
+ function validEndpoint(value) {
190
+ if (typeof value !== "string" || !value.trim())
191
+ return undefined;
192
+ try {
193
+ const url = new URL(value);
194
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash || url.search || !url.pathname.endsWith("/chat/completions"))
195
+ return undefined;
196
+ return url.toString();
197
+ }
198
+ catch {
199
+ return undefined;
200
+ }
201
+ }
202
+ function isAid(value) { return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{2,255}$/.test(value); }
203
+ function isPluginId(value) { return typeof value === "string" && /^[a-z0-9][a-z0-9-]{0,63}$/.test(value); }
204
+ function validTimeout(value) { return typeof value === "number" && Number.isInteger(value) && value >= 1000 && value <= 120_000 ? value : 120_000; }
205
+ function requestTimeout(value) { return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 120_000; }
206
+ function validEcPath(value) { return typeof value === "string" && value.trim() ? value.trim() : undefined; }
207
+ function validContextTtl(value) { return typeof value === "number" && Number.isSafeInteger(value) && value >= 1_000 && value <= 300_000 ? value : 60_000; }
208
+ function safeJson(value) { try {
209
+ return JSON.stringify(value);
210
+ }
211
+ catch {
212
+ return "操作完成。";
213
+ } }
214
+ function modelFailure(error, provider) {
215
+ const operation = error instanceof agent_1.AgentOperationError ? error : undefined;
216
+ const stage = operation?.stage ?? "模型调用";
217
+ const code = operation?.code ?? "PA_REQUEST_FAILED";
218
+ if (code === "PA_REQUEST_CANCELLED")
219
+ return { text: `个人助理请求已取消(来源 ${provider},阶段:${stage})。`, stage, code };
220
+ if (code === "PA_REQUEST_TIMEOUT")
221
+ return { text: `个人助理请求超时(来源 ${provider},阶段:${stage})。`, stage, code };
222
+ const message = safeFailureMessage(operation?.cause ?? error);
223
+ return { text: `个人助理请求失败(来源 ${provider},阶段:${stage},错误码:${errorCode(error) ?? code})${message ? `:${message}` : "。"}`, stage, code, ...(message ? { message } : {}) };
224
+ }
225
+ function errorCode(error) {
226
+ let result;
227
+ for (let current = error, count = 0; current && count++ < 5;) {
228
+ const code = current.code;
229
+ if (typeof code === "string" && /^[A-Z][A-Z0-9_]{2,31}$/.test(code))
230
+ result = code;
231
+ current = current.cause;
232
+ }
233
+ return result;
234
+ }
235
+ function safeFailureMessage(error) {
236
+ // 只回显宿主定义的诊断,插件任意异常可能含提示正文、凭据或本地路径。
237
+ const message = error instanceof Error ? error.message : "";
238
+ if (/^模型(?:服务请求失败:HTTP \d{3}|服务不允许重定向|响应超过大小限制|响应不是有效的工具决策 JSON|响应格式无效|未返回有效回复|返回了空回复|工具调用次数超过限制|返回的工具调用无效)$/.test(message))
239
+ return message;
240
+ if (/^(?:插件 [a-z0-9-]+ 未声明模型来源能力|模型来源插件 [a-z0-9-]+ 未安装)$/.test(message))
241
+ return message;
242
+ if (/^(?:Claude|Codex) CLI 模型请求(?:必须是包含 responseSchema 的 JSON 对象|缺少有效 responseSchema)$/.test(message))
243
+ return message;
244
+ const unavailable = /^模型来源插件“[^”\r\n]{1,64}”未就绪:([\s\S]+)$/.exec(message);
245
+ const reason = unavailable?.[1] ?? message;
246
+ if (/^(?:ready|degraded|failed|disabled|installed|discovered|starting|stopping|插件未实现模型来源接口)$/.test(reason))
247
+ return `模型来源未就绪:${reason}`;
248
+ const cdp = /(?:无法连接)?(千问办公|WorkBuddy|ChatGPT|豆包) CDP (?:端口 )?(\d{1,5})\b/.exec(reason);
249
+ if (cdp)
250
+ return `无法连接${cdp[1]} CDP 端口 ${cdp[2]}`;
251
+ const desktop = /(?:千问(?:办公)?|WorkBuddy|ChatGPT|豆包) ?(?:发送按钮(?:未就绪|不可用)|输入框(?:未就绪|未找到|含有未发送内容)|当前有未发送内容或正在生成回复,不能(?:新建|切换)会话)/.exec(reason);
252
+ if (desktop)
253
+ return desktop[0];
254
+ if (/^(?:WorkBuddy|ChatGPT|豆包) CDP (?:未连接|已断开),模型请求不会启动或重启桌面$/.test(reason))
255
+ return reason;
256
+ if (reason === "千问办公未连接或不支持隔离模型会话,模型请求不会启动或重启桌面" || reason === "DSH 插件未就绪")
257
+ return reason;
258
+ if (reason === "WorkBuddy 当前有用户草稿、待确认操作或正在生成回复,模型请求未切换会话")
259
+ return reason;
260
+ const code = errorCode(error);
261
+ return code ? `底层错误码 ${code}` : undefined;
262
+ }
263
+ exports.default = PersonalAssistantPlugin;