personal-bot 0.2.3 → 0.2.5
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/CONTEXT.md +13 -7
- package/README.md +46 -9
- package/README.zh-CN.md +46 -9
- package/bin/pbot.mjs +3 -0
- package/docs/README.md +4 -1
- package/docs/agent-collaboration.md +26 -12
- package/docs/architecture-guidelines.md +2 -2
- package/docs/capability-design.md +6 -4
- package/docs/dsh-implementation-plan.md +158 -0
- package/docs/dsh-support-proposal.md +314 -0
- package/docs/dsh.md +103 -0
- package/docs/file-storage.md +14 -9
- package/docs/history.md +29 -18
- package/docs/installation-design.md +226 -0
- package/docs/interaction-design.md +13 -0
- package/docs/release.md +5 -5
- package/docs/technical-design.md +29 -15
- package/docs/validation.md +53 -3
- package/lib/client.js +1071 -0
- package/lib/common/files.d.ts +2 -0
- package/lib/common/files.js +44 -0
- package/lib/common/json.d.ts +6 -0
- package/lib/common/json.js +16 -0
- package/lib/common/markdown.d.ts +5 -0
- package/lib/common/markdown.js +26 -0
- package/lib/common/package-source.js +16 -0
- package/lib/common/paths.d.ts +1 -0
- package/lib/common/paths.js +2 -0
- package/lib/common/validation.d.ts +3 -0
- package/lib/common/validation.js +14 -0
- package/lib/common/version.d.ts +3 -0
- package/lib/common/version.js +13 -0
- package/lib/dsh.d.ts +4 -0
- package/lib/dsh.js +22 -0
- package/lib/extension/dsh/agent-events.d.ts +24 -0
- package/lib/extension/dsh/agent-events.js +242 -0
- package/lib/extension/dsh/agent-runtime.d.ts +49 -0
- package/lib/extension/dsh/agent-runtime.js +348 -0
- package/lib/extension/dsh/command.d.ts +4 -0
- package/lib/extension/dsh/command.js +83 -0
- package/lib/extension/dsh/files.d.ts +5 -0
- package/lib/extension/dsh/files.js +67 -0
- package/lib/extension/dsh/history.d.ts +15 -0
- package/lib/extension/dsh/history.js +88 -0
- package/lib/extension/dsh/management.d.ts +21 -0
- package/lib/extension/dsh/management.js +217 -0
- package/lib/extension/dsh/workspace.d.ts +48 -0
- package/lib/extension/dsh/workspace.js +290 -0
- package/lib/extension/setup-cli.js +136 -0
- package/lib/extension/setup-guard.js +11 -0
- package/lib/integrations/agent-resources.d.ts +1 -0
- package/lib/integrations/agent-resources.js +18 -0
- package/lib/integrations/dsh/dsh-agents.d.ts +30 -0
- package/lib/integrations/dsh/dsh-agents.js +104 -0
- package/lib/integrations/dsh/history-summary.d.ts +11 -0
- package/lib/integrations/dsh/history-summary.js +40 -0
- package/lib/integrations/dsh/history.d.ts +16 -0
- package/lib/integrations/dsh/history.js +156 -0
- package/lib/integrations/dsh/input.d.ts +21 -0
- package/lib/integrations/dsh/input.js +62 -0
- package/lib/integrations/dsh/messages.d.ts +36 -0
- package/lib/integrations/dsh/messages.js +11 -0
- package/lib/integrations/dsh/notifications.d.ts +8 -0
- package/lib/integrations/dsh/notifications.js +55 -0
- package/lib/integrations/dsh/resources.d.ts +10 -0
- package/lib/integrations/dsh/resources.js +41 -0
- package/lib/integrations/dsh/schema.d.ts +7 -0
- package/lib/integrations/dsh/schema.js +28 -0
- package/lib/integrations/dsh/session.d.ts +14 -0
- package/lib/integrations/dsh/session.js +59 -0
- package/lib/integrations/history-summary.d.ts +2 -0
- package/lib/integrations/history-summary.js +21 -0
- package/lib/integrations/installation/pi-bridge.js +131 -0
- package/lib/integrations/installation/pi-setup.js +193 -0
- package/lib/integrations/installation/processes.js +155 -0
- package/lib/integrations/installation/workspace.js +21 -0
- package/lib/integrations/process.d.ts +14 -0
- package/lib/integrations/process.js +48 -0
- package/lib/integrations/python-activation.d.ts +2 -0
- package/lib/integrations/python-activation.js +29 -0
- package/lib/integrations/team-context.d.ts +18 -0
- package/lib/integrations/team-context.js +57 -0
- package/lib/model/agent.d.ts +92 -0
- package/lib/model/agent.js +1 -0
- package/lib/model/history.d.ts +43 -0
- package/lib/model/history.js +1 -0
- package/lib/model/team.d.ts +85 -0
- package/lib/model/team.js +1 -0
- package/lib/services/agents/agent-notices.d.ts +13 -0
- package/lib/services/agents/agent-notices.js +25 -0
- package/lib/services/agents/agent-run.d.ts +39 -0
- package/lib/services/agents/agent-run.js +138 -0
- package/lib/services/agents/agent-service.d.ts +166 -0
- package/lib/services/agents/agent-service.js +325 -0
- package/lib/services/agents/team-service.d.ts +168 -0
- package/lib/services/agents/team-service.js +343 -0
- package/lib/services/history/history-service.d.ts +103 -0
- package/lib/services/history/history-service.js +269 -0
- package/lib/services/installation/setup-service.js +121 -0
- package/lib/services/workspace/python-environment-service.d.ts +15 -0
- package/lib/services/workspace/python-environment-service.js +50 -0
- package/lib/services/workspace/workspace-files.d.ts +2 -0
- package/lib/services/workspace/workspace-files.js +17 -0
- package/lib/services/workspace/workspace-preparation.d.ts +2 -0
- package/lib/services/workspace/workspace-preparation.js +18 -0
- package/lib/storage/agent-files.d.ts +30 -0
- package/lib/storage/agent-files.js +166 -0
- package/lib/storage/atomic-json.d.ts +11 -0
- package/lib/storage/atomic-json.js +101 -0
- package/lib/storage/file-access.d.ts +2 -0
- package/lib/storage/file-access.js +26 -0
- package/lib/storage/history-files.d.ts +10 -0
- package/lib/storage/history-files.js +63 -0
- package/lib/storage/team-files.d.ts +16 -0
- package/lib/storage/team-files.js +111 -0
- package/lib/tools/agents/actions.d.ts +52 -0
- package/lib/tools/agents/actions.js +49 -0
- package/lib/tools/agents/contract.d.ts +163 -0
- package/lib/tools/agents/contract.js +56 -0
- package/lib/tools/agents/dsh-contract.d.ts +52 -0
- package/lib/tools/agents/dsh-contract.js +15 -0
- package/lib/tools/agents/dsh-tool.d.ts +5 -0
- package/lib/tools/agents/dsh-tool.js +38 -0
- package/lib/tools/history/actions.d.ts +70 -0
- package/lib/tools/history/actions.js +7 -0
- package/lib/tools/history/contract.d.ts +81 -0
- package/lib/tools/history/contract.js +32 -0
- package/lib/tools/history/dsh-tool.d.ts +4 -0
- package/lib/tools/history/dsh-tool.js +22 -0
- package/lib/tools/result.d.ts +18 -0
- package/lib/tools/result.js +10 -0
- package/lib/tools/teams/actions.d.ts +24 -0
- package/lib/tools/teams/actions.js +38 -0
- package/lib/tools/teams/contract.d.ts +232 -0
- package/lib/tools/teams/contract.js +67 -0
- package/lib/tools/teams/dsh-tool.d.ts +8 -0
- package/lib/tools/teams/dsh-tool.js +49 -0
- package/lib/ui/dsh/attachments.d.ts +5 -0
- package/lib/ui/dsh/attachments.js +39 -0
- package/lib/ui/dsh/elements.d.ts +17 -0
- package/lib/ui/dsh/elements.js +79 -0
- package/lib/ui/dsh/index.d.ts +4 -0
- package/lib/ui/dsh/index.js +13 -0
- package/lib/ui/dsh/management.d.ts +4 -0
- package/lib/ui/dsh/management.js +711 -0
- package/lib/ui/dsh/remote.d.ts +15 -0
- package/lib/ui/dsh/remote.js +11 -0
- package/lib/ui/dsh/styles.d.ts +1 -0
- package/lib/ui/dsh/styles.js +132 -0
- package/lib/ui/presentation.d.ts +3 -0
- package/lib/ui/presentation.js +12 -0
- package/lib/ui/setup.js +52 -0
- package/npm-shrinkwrap.json +11118 -783
- package/package.json +113 -9
- package/python/README.md +6 -6
- package/resources/agents/coordinator.md +1 -1
- package/resources/dsh/cordis.patch.yml +3 -0
- package/resources/installer/plugins.json +16 -0
- package/resources/prompts/workspace.md +1 -1
- package/resources/references/compute.md +1 -1
- package/resources/references/task-conductor.md +6 -4
- package/resources/skills/provider-connections/SKILL.md +10 -0
- package/scripts/README.md +13 -1
- package/src/common/package-source.ts +16 -0
- package/src/dsh.ts +22 -0
- package/src/extension/agent-events.ts +51 -46
- package/src/extension/agents-command.ts +48 -34
- package/src/extension/command.ts +42 -23
- package/src/extension/dsh/agent-events.ts +148 -0
- package/src/extension/dsh/agent-runtime.ts +251 -0
- package/src/extension/dsh/command.ts +78 -0
- package/src/extension/dsh/files.ts +61 -0
- package/src/extension/dsh/history.ts +69 -0
- package/src/extension/dsh/management.ts +108 -0
- package/src/extension/dsh/workspace.ts +206 -0
- package/src/extension/events.ts +6 -4
- package/src/extension/history.ts +15 -12
- package/src/extension/setup-cli.ts +109 -0
- package/src/extension/setup-guard.ts +12 -0
- package/src/extension/subagent-entry.ts +34 -63
- package/src/extension/teams-command.ts +53 -46
- package/src/integrations/{pi/agent-resources.ts → agent-resources.ts} +2 -2
- package/src/integrations/dsh/dsh-agents.ts +97 -0
- package/src/integrations/dsh/history-summary.ts +43 -0
- package/src/integrations/dsh/history.ts +76 -0
- package/src/integrations/dsh/input.ts +66 -0
- package/src/integrations/dsh/messages.ts +22 -0
- package/src/integrations/dsh/notifications.ts +46 -0
- package/src/integrations/dsh/resources.ts +44 -0
- package/src/integrations/dsh/schema.ts +26 -0
- package/src/integrations/dsh/session.ts +67 -0
- package/src/integrations/history-summary.ts +20 -0
- package/src/integrations/installation/pi-bridge.ts +107 -0
- package/src/integrations/installation/pi-setup.ts +162 -0
- package/src/integrations/installation/processes.ts +124 -0
- package/src/integrations/installation/workspace.ts +18 -0
- package/src/integrations/pi/history.ts +4 -14
- package/src/integrations/pi/pi-agents.ts +17 -6
- package/src/integrations/pi/team-context.ts +6 -58
- package/src/integrations/team-context.ts +60 -0
- package/src/model/agent.ts +2 -0
- package/src/model/history.ts +3 -1
- package/src/pbot.ts +1 -1
- package/src/services/agents/agent-notices.ts +28 -0
- package/src/services/agents/agent-run.ts +114 -0
- package/src/services/agents/agent-service.ts +127 -64
- package/src/services/history/history-service.ts +120 -59
- package/src/services/installation/setup-service.ts +145 -0
- package/src/storage/agent-files.ts +17 -4
- package/src/storage/history-files.ts +30 -5
- package/src/tools/agents/actions.ts +47 -0
- package/src/tools/agents/contract.ts +8 -5
- package/src/tools/agents/dsh-contract.ts +16 -0
- package/src/tools/agents/dsh-tool.ts +41 -0
- package/src/tools/agents/tool.ts +7 -43
- package/src/tools/history/actions.ts +9 -0
- package/src/tools/history/contract.ts +34 -0
- package/src/tools/history/dsh-tool.ts +22 -0
- package/src/tools/history/tool.ts +7 -30
- package/src/tools/teams/actions.ts +38 -0
- package/src/tools/teams/contract.ts +7 -3
- package/src/tools/teams/dsh-tool.ts +55 -0
- package/src/tools/teams/tool.ts +6 -31
- package/src/ui/agent-display.ts +7 -4
- package/src/ui/dsh/attachments.ts +42 -0
- package/src/ui/dsh/elements.ts +50 -0
- package/src/ui/dsh/index.ts +18 -0
- package/src/ui/dsh/management.ts +412 -0
- package/src/ui/dsh/remote.ts +20 -0
- package/src/ui/dsh/styles.ts +132 -0
- package/src/ui/pi/menu.ts +37 -0
- package/src/ui/pi/reader.ts +46 -0
- package/src/ui/presentation.ts +12 -0
- package/src/ui/setup.ts +40 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { mkdir, readFile, realpath } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { executeSetup, packageChoices, parseCatalog, parseSetupArgs } from "../services/installation/setup-service.js";
|
|
5
|
+
import { npmName, ordinaryNpm } from "../common/package-source.js";
|
|
6
|
+
import { PiBootstrapRequired, PiSetup, piPackageName } from "../integrations/installation/pi-setup.js";
|
|
7
|
+
import { inspectSystem, SetupProcesses } from "../integrations/installation/processes.js";
|
|
8
|
+
import { resolveWorkspace } from "../integrations/installation/workspace.js";
|
|
9
|
+
import { SetupUI } from "../ui/setup.js";
|
|
10
|
+
const root = new URL("../../", import.meta.url);
|
|
11
|
+
const quote = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
12
|
+
export async function main(args) {
|
|
13
|
+
let options;
|
|
14
|
+
try {
|
|
15
|
+
options = parseSetupArgs(args);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.error(String(error));
|
|
19
|
+
return 2;
|
|
20
|
+
}
|
|
21
|
+
const manifest = JSON.parse(await readFile(new URL("package.json", root), "utf8"));
|
|
22
|
+
if (options.action === "version") {
|
|
23
|
+
console.log(manifest.version);
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
if (options.action === "help") {
|
|
27
|
+
console.log("Personal Bot " + manifest.version + "\n\n用法:pbot setup [--workspace <目录>]\n" +
|
|
28
|
+
" npx personal-bot@latest setup\n\nsetup 安装或升级 Pi、pbot 和选中的插件,然后启动 Pi 执行 /pbot init。\n" +
|
|
29
|
+
"支持 Linux/macOS,需要 Node.js 22.19+、npm 和 Python 3.11+。\n--help 显示帮助;--version 显示安装器版本。");
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
33
|
+
console.error("setup 需要交互终端。请在终端运行 npx personal-bot@latest setup。");
|
|
34
|
+
return 2;
|
|
35
|
+
}
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const cancel = () => controller.abort(new Error("安装已取消;已完成的步骤保留。"));
|
|
38
|
+
process.on("SIGINT", cancel);
|
|
39
|
+
process.on("SIGTERM", cancel);
|
|
40
|
+
const ui = new SetupUI(controller), processes = new SetupProcesses(controller.signal);
|
|
41
|
+
try {
|
|
42
|
+
const cwd = process.cwd();
|
|
43
|
+
ui.text("Personal Bot " + manifest.version + " — 安装与升级\n正在检查 Node/npm、Python 和 tmux…");
|
|
44
|
+
const system = await inspectSystem(processes, cwd);
|
|
45
|
+
const supplied = options.workspace ?? await ui.ask("工作区目录(当前、已有或新建目录)", cwd);
|
|
46
|
+
const workspace = await resolveWorkspace(supplied, cwd, homedir());
|
|
47
|
+
const host = new PiSetup(processes, system.npm, cwd);
|
|
48
|
+
let pi = await host.inspectPi();
|
|
49
|
+
let installed;
|
|
50
|
+
try {
|
|
51
|
+
installed = await host.inspectPackages(pi, workspace);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (!(error instanceof PiBootstrapRequired))
|
|
55
|
+
throw error;
|
|
56
|
+
const version = await host.latest(piPackageName);
|
|
57
|
+
ui.text(error.message + "\n需先安装 Pi 才能使用其原生接口核对这些来源。目标版本:" + version);
|
|
58
|
+
if (!await ui.confirm("先通过 npm 安装 Pi,再继续选择工作区插件"))
|
|
59
|
+
return 130;
|
|
60
|
+
pi = await host.ensurePi(undefined, version);
|
|
61
|
+
installed = await host.inspectPackages(pi, workspace);
|
|
62
|
+
}
|
|
63
|
+
const catalog = parseCatalog(JSON.parse(await readFile(new URL("resources/installer/plugins.json", root), "utf8")));
|
|
64
|
+
const choices = packageChoices(catalog, installed), optional = choices.filter(choice => !choice.required);
|
|
65
|
+
ui.text("\nPersonal Bot 必装。选择配套插件(取消勾选不会卸载已有插件):");
|
|
66
|
+
const selected = await ui.selectMany(optional.map(choice => choice.plugin.label + " — " + choice.plugin.description), optional.flatMap((choice, index) => choice.selected ? [index] : []));
|
|
67
|
+
optional.forEach((choice, index) => { choice.selected = selected.includes(index); });
|
|
68
|
+
for (const choice of choices.filter(item => item.selected)) {
|
|
69
|
+
if (choice.existing.some(pkg => !ordinaryNpm(pkg.source))) {
|
|
70
|
+
ui.text("\n" + choice.plugin.label + " 的已有来源:\n" + choice.existing.map(pkg => " " + pkg.scope + ": " + pkg.source + " (" + (pkg.version ?? "版本未知") + ")").join("\n"));
|
|
71
|
+
choice.keep = !await ui.confirm("切换到 npm 最新稳定版", false);
|
|
72
|
+
}
|
|
73
|
+
if (choice.keep) {
|
|
74
|
+
if (choice.existing.length !== 1 || !choice.existing[0]?.installedPath)
|
|
75
|
+
choice.error = "保留来源未安装或存在多份来源,请先通过 Pi 原生配置处理。";
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const others = choice.existing.filter(pkg => !npmName(pkg.source));
|
|
79
|
+
if (others.length > 1 || others.some(pkg => pkg.scope === "user")) {
|
|
80
|
+
choice.error = "请先用 pi config 调整全局或重复来源的加载范围,再切换到 npm。";
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
try {
|
|
84
|
+
choice.target = await host.latest(npmName(choice.plugin.source));
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
controller.signal.throwIfAborted();
|
|
88
|
+
choice.error = String(error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const plan = { workspace, piVersion: await host.latest(piPackageName), packages: choices,
|
|
94
|
+
...(pi ? { pi } : {}), ...(system.tmux ? { tmux: system.tmux } : {}) };
|
|
95
|
+
ui.text("\n安装计划\n工作区:" + workspace + "\nPi:" + (pi ? pi.command + " (" + pi.version + ")" : "通过 npm 全局安装") + " → " + plan.piVersion);
|
|
96
|
+
if (system.tmux)
|
|
97
|
+
ui.text("tmux:" + [system.tmux.command, ...system.tmux.args].map(quote).join(" "));
|
|
98
|
+
for (const choice of choices) {
|
|
99
|
+
ui.text(" " + choice.plugin.label + ": " + (!choice.selected ? "本次跳过" : choice.error ??
|
|
100
|
+
(choice.keep ? "保留 " + choice.existing[0]?.source : (choice.existing.map(pkg => pkg.version ?? "未知").join(", ") || "未安装") + " → " + choice.target)));
|
|
101
|
+
if (choice.selected && choice.existing.some(pkg => pkg.filtered))
|
|
102
|
+
ui.text(" 保留已有资源过滤;如 pbot 被禁用,请通过 pi config --local 启用。");
|
|
103
|
+
}
|
|
104
|
+
const blocking = choices.find(choice => choice.required && choice.error);
|
|
105
|
+
if (blocking)
|
|
106
|
+
throw new Error(blocking.error);
|
|
107
|
+
ui.text("升级前请停止活动 pbot 子会话和相关 Python 脚本。Pi 为当前用户共用,插件只处理此工作区。");
|
|
108
|
+
if (!await ui.confirm("执行以上计划"))
|
|
109
|
+
return 130;
|
|
110
|
+
await mkdir(workspace, { recursive: true });
|
|
111
|
+
if (await realpath(workspace) !== workspace)
|
|
112
|
+
throw new Error("工作区路径已发生变化,请重新运行 setup。");
|
|
113
|
+
const result = await executeSetup(plan, host, item => ui.text("[" + item.state + "] " + item.name + ": " + item.detail), controller.signal);
|
|
114
|
+
const failed = result.results.some(item => item.state === "failed");
|
|
115
|
+
if (!result.pi)
|
|
116
|
+
return 1;
|
|
117
|
+
ui.text("\n软件安装阶段结束。初始化、Python 环境和外部服务的状态由 Pi/pbot/插件分别报告。");
|
|
118
|
+
if (await ui.confirm(failed ? "部分插件失败,是否仍先启动 Pi" : "立即启动 Pi", !failed)) {
|
|
119
|
+
process.stdin.pause();
|
|
120
|
+
await host.start(result.pi, workspace);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
ui.text("稍后执行:\ncd " + quote(workspace) + "\n" + quote(result.pi.command) + " --extension " +
|
|
124
|
+
quote(fileURLToPath(new URL("extension/setup-guard.js", new URL("../", import.meta.url)))) + " '/pbot init'");
|
|
125
|
+
}
|
|
126
|
+
return failed ? 1 : 0;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
130
|
+
return controller.signal.aborted ? 130 : 1;
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
process.off("SIGINT", cancel);
|
|
134
|
+
process.off("SIGTERM", cancel);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Native extension commands run before input handlers. Reaching this handler for
|
|
2
|
+
// /pbot init means it was not dispatched; never let the setup input reach a model.
|
|
3
|
+
export default function setupGuard(pi) {
|
|
4
|
+
pi.on("input", (event, ctx) => {
|
|
5
|
+
if (event.text.trim() !== "/pbot init")
|
|
6
|
+
return { action: "continue" };
|
|
7
|
+
ctx.ui.notify("pbot 未加载或命令存在冲突。请检查项目信任和 pi config --local 后重新运行 setup。", "error");
|
|
8
|
+
ctx.shutdown();
|
|
9
|
+
return { action: "handled" };
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function agentText(agent: "coordinator" | "general-purpose", root: string): Promise<string>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resourceRoot } from "../common/paths.js";
|
|
4
|
+
import { requireThat } from "../common/validation.js";
|
|
5
|
+
export async function agentText(agent, root) {
|
|
6
|
+
requireThat(agent === "coordinator" || agent === "general-purpose", "未知职责。");
|
|
7
|
+
let text = await readFile(join(resourceRoot, "agents", agent + ".md"), "utf8");
|
|
8
|
+
try {
|
|
9
|
+
const local = await readFile(join(root, "agents", agent + ".md"), "utf8");
|
|
10
|
+
text += "\n\n工作区职责补充:\n" + local;
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (error.code !== "ENOENT")
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
requireThat(text.trim(), "专家职责为空。");
|
|
17
|
+
return text;
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import type { Agent, AgentSetup } from "@deepseek-ai/dsh-agent";
|
|
3
|
+
import type { AgentDescriptor, AgentSessionReference } from "../../model/agent.js";
|
|
4
|
+
import type { AgentHost } from "../../services/agents/agent-service.js";
|
|
5
|
+
/** Native handles belong to the execution component, independently of browser observers. */
|
|
6
|
+
export declare class DshAgents implements AgentHost {
|
|
7
|
+
#private;
|
|
8
|
+
readonly ctx: Context;
|
|
9
|
+
readonly parent: Agent;
|
|
10
|
+
readonly composition: {
|
|
11
|
+
setup(d: AgentDescriptor, directory: string, generation: string): AgentSetup;
|
|
12
|
+
ready(agent: Agent, generation: string): void;
|
|
13
|
+
stopping(agent: Agent, generation: string): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
readonly lifetime: AbortSignal;
|
|
16
|
+
readonly kind = "dsh";
|
|
17
|
+
constructor(ctx: Context, parent: Agent, composition: {
|
|
18
|
+
setup(d: AgentDescriptor, directory: string, generation: string): AgentSetup;
|
|
19
|
+
ready(agent: Agent, generation: string): void;
|
|
20
|
+
stopping(agent: Agent, generation: string): Promise<void>;
|
|
21
|
+
}, lifetime: AbortSignal);
|
|
22
|
+
available(): void;
|
|
23
|
+
alive(id: string): boolean;
|
|
24
|
+
private reference;
|
|
25
|
+
validateSession(d: AgentDescriptor, reference: AgentSessionReference): Promise<void>;
|
|
26
|
+
launch(d: AgentDescriptor, directory: string, generation: string, reference: AgentSessionReference, resume: boolean, signal: AbortSignal): Promise<void>;
|
|
27
|
+
release(id: string, generation: string): Promise<void>;
|
|
28
|
+
report(d: AgentDescriptor, reference: AgentSessionReference, position: string): Promise<string>;
|
|
29
|
+
close(): Promise<void>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
2
|
+
import { requireThat } from "../../common/validation.js";
|
|
3
|
+
import { openDshAgent, readDshAgentSession, readDshReport } from "./session.js";
|
|
4
|
+
/** Native handles belong to the execution component, independently of browser observers. */
|
|
5
|
+
export class DshAgents {
|
|
6
|
+
ctx;
|
|
7
|
+
parent;
|
|
8
|
+
composition;
|
|
9
|
+
lifetime;
|
|
10
|
+
kind = "dsh";
|
|
11
|
+
#runs = new Map();
|
|
12
|
+
#closed = false;
|
|
13
|
+
constructor(ctx, parent, composition, lifetime) {
|
|
14
|
+
this.ctx = ctx;
|
|
15
|
+
this.parent = parent;
|
|
16
|
+
this.composition = composition;
|
|
17
|
+
this.lifetime = lifetime;
|
|
18
|
+
}
|
|
19
|
+
available() {
|
|
20
|
+
this.lifetime.throwIfAborted();
|
|
21
|
+
requireThat(!this.#closed && this.ctx.agents.get(this.parent.id) === this.parent, "dsh 父会话或执行组件已关闭。");
|
|
22
|
+
}
|
|
23
|
+
alive(id) {
|
|
24
|
+
const run = this.#runs.get(id);
|
|
25
|
+
return Boolean(this.ctx.agents.get(SessionId(id))) || Boolean(run && (run.closing || !run.handle));
|
|
26
|
+
}
|
|
27
|
+
reference(d, reference) {
|
|
28
|
+
requireThat(reference.host === "dsh" && reference.sessionId === d.agentId, "不能接管其他宿主或其他身份的原生会话。");
|
|
29
|
+
}
|
|
30
|
+
async validateSession(d, reference) {
|
|
31
|
+
this.available();
|
|
32
|
+
this.reference(d, reference);
|
|
33
|
+
requireThat(!this.alive(d.agentId), "原生专家仍被占用,不能恢复。");
|
|
34
|
+
await readDshAgentSession(this.ctx, d);
|
|
35
|
+
requireThat(!this.alive(d.agentId), "原生专家仍被占用,不能恢复。");
|
|
36
|
+
}
|
|
37
|
+
async launch(d, directory, generation, reference, resume, signal) {
|
|
38
|
+
this.available();
|
|
39
|
+
this.reference(d, reference);
|
|
40
|
+
requireThat(!this.alive(d.agentId), "dsh 专家仍在运行或释放中。");
|
|
41
|
+
const previous = this.#runs.get(d.agentId);
|
|
42
|
+
if (previous)
|
|
43
|
+
await this.release(d.agentId, previous.generation);
|
|
44
|
+
signal.throwIfAborted();
|
|
45
|
+
this.available();
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const combined = AbortSignal.any([signal, this.lifetime, controller.signal]);
|
|
48
|
+
const opening = Promise.resolve().then(() => openDshAgent(this.ctx, d, this.parent, resume, this.composition.setup(d, directory, generation), combined));
|
|
49
|
+
const run = { generation, controller, opening };
|
|
50
|
+
this.#runs.set(d.agentId, run);
|
|
51
|
+
try {
|
|
52
|
+
run.handle = await opening;
|
|
53
|
+
if (combined.aborted) {
|
|
54
|
+
await this.release(d.agentId, generation);
|
|
55
|
+
combined.throwIfAborted();
|
|
56
|
+
}
|
|
57
|
+
this.composition.ready(run.handle.agent, generation);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (!run.handle && this.#runs.get(d.agentId) === run)
|
|
61
|
+
this.#runs.delete(d.agentId);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
release(id, generation) {
|
|
66
|
+
const run = this.#runs.get(id);
|
|
67
|
+
if (!run)
|
|
68
|
+
return this.ctx.agents.get(SessionId(id)) ? Promise.reject(new Error("原生专家仍由其他执行实例持有;当前组件不能释放。")) : Promise.resolve();
|
|
69
|
+
if (run.generation !== generation)
|
|
70
|
+
return Promise.resolve();
|
|
71
|
+
if (run.closing)
|
|
72
|
+
return run.closing;
|
|
73
|
+
run.controller.abort();
|
|
74
|
+
run.closing = (async () => {
|
|
75
|
+
const handle = run.handle ?? await run.opening.catch(() => undefined);
|
|
76
|
+
let failure;
|
|
77
|
+
if (handle) {
|
|
78
|
+
try {
|
|
79
|
+
await this.composition.stopping(handle.agent, generation);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
failure = error;
|
|
83
|
+
}
|
|
84
|
+
await handle.dispose();
|
|
85
|
+
}
|
|
86
|
+
if (this.#runs.get(id) === run)
|
|
87
|
+
this.#runs.delete(id);
|
|
88
|
+
if (failure)
|
|
89
|
+
throw failure;
|
|
90
|
+
})();
|
|
91
|
+
return run.closing;
|
|
92
|
+
}
|
|
93
|
+
report(d, reference, position) {
|
|
94
|
+
this.reference(d, reference);
|
|
95
|
+
return readDshReport(this.ctx, d, position);
|
|
96
|
+
}
|
|
97
|
+
async close() {
|
|
98
|
+
this.#closed = true;
|
|
99
|
+
const results = await Promise.allSettled([...this.#runs].map(([id, run]) => this.release(id, run.generation)));
|
|
100
|
+
const errors = results.flatMap(r => r.status === "rejected" ? [r.reason] : []);
|
|
101
|
+
if (errors.length)
|
|
102
|
+
throw new AggregateError(errors, "dsh 专家仍有未释放的执行资源。");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
3
|
+
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
4
|
+
import type { HistorySummarizer } from "../../model/history.js";
|
|
5
|
+
declare module "@deepseek-ai/dsh-llm" {
|
|
6
|
+
interface LlmRequestPurposeMap {
|
|
7
|
+
"pbot-history": true;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** Independent text calls through the host's routing and authentication; no Agent or Session messages. */
|
|
11
|
+
export declare function dshHistorySummarizer(ctx: Context, selection: ReturnType<Agent["model"]["current"]>, sessionId: SessionId): HistorySummarizer;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { BlockAssembler } from "@deepseek-ai/dsh-llm";
|
|
4
|
+
import { resourceRoot } from "../../common/paths.js";
|
|
5
|
+
import { requireThat } from "../../common/validation.js";
|
|
6
|
+
import { reduceHistorySummary } from "../history-summary.js";
|
|
7
|
+
/** Independent text calls through the host's routing and authentication; no Agent or Session messages. */
|
|
8
|
+
export function dshHistorySummarizer(ctx, selection, sessionId) {
|
|
9
|
+
return async (text, signal) => {
|
|
10
|
+
requireThat(selection, "请先选择 dsh 模型。");
|
|
11
|
+
signal.throwIfAborted();
|
|
12
|
+
const system = await readFile(join(resourceRoot, "prompts/history-summary.md"), { encoding: "utf8", signal });
|
|
13
|
+
const prepare = () => ctx.llm.prepareCall({ provider: selection.provider, model: selection.model, maxTokens: 2048 }, signal, (controls, model) => ({ ...controls, ...(model.reasoning?.efforts[0] ? { reasoningEffort: model.reasoning.efforts[0].id } : {}) }));
|
|
14
|
+
let first = await prepare();
|
|
15
|
+
const budget = Math.max(2000, Math.min(24000, Math.floor((first.context?.contextWindow ?? 12000) / 2)));
|
|
16
|
+
const ask = async (content, signal) => {
|
|
17
|
+
signal.throwIfAborted();
|
|
18
|
+
const prepared = first ?? await prepare();
|
|
19
|
+
first = undefined;
|
|
20
|
+
const assembler = new BlockAssembler();
|
|
21
|
+
let finished = false;
|
|
22
|
+
for await (const chunk of prepared.stream({ ...prepared.config, system, sessionId, purpose: "pbot-history", signal, tools: [],
|
|
23
|
+
messages: [{ role: "user", content: [{ type: "text", text: content }] }] })) {
|
|
24
|
+
signal.throwIfAborted();
|
|
25
|
+
assembler.push(chunk);
|
|
26
|
+
if (chunk.type === "finish")
|
|
27
|
+
finished = true;
|
|
28
|
+
}
|
|
29
|
+
signal.throwIfAborted();
|
|
30
|
+
const reason = assembler.finish;
|
|
31
|
+
requireThat(finished && reason.kind === "stop", reason.kind === "error" || reason.kind === "aborted" ? reason.failure.message : "历史摘要未正常结束:" + reason.kind);
|
|
32
|
+
const blocks = assembler.blocks();
|
|
33
|
+
requireThat(!blocks.some(block => block.type === "tool-call"), "历史摘要不能执行工具。");
|
|
34
|
+
const answer = blocks.filter(block => block.type === "text").map(block => block.text).join("\n").trim();
|
|
35
|
+
requireThat(answer, "历史摘要为空。");
|
|
36
|
+
return answer;
|
|
37
|
+
};
|
|
38
|
+
return reduceHistorySummary(budget, ask)(text, signal);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import type { SessionEvent, SessionHeader } from "@deepseek-ai/dsh-session";
|
|
3
|
+
import type { HistoryLocation, HistorySession, HistorySource } from "../../model/history.js";
|
|
4
|
+
/** Archive questions and the final/partial answer; raw tool records stay in the native log. */
|
|
5
|
+
export declare function projectDshHistory(header: SessionHeader, events: readonly SessionEvent[], name?: string): HistorySession;
|
|
6
|
+
export declare class DshHistory implements HistorySource {
|
|
7
|
+
private readonly ctx;
|
|
8
|
+
private readonly root;
|
|
9
|
+
private readonly relatedSessions;
|
|
10
|
+
readonly host = "dsh";
|
|
11
|
+
constructor(ctx: Context, root: string, relatedSessions?: () => string[]);
|
|
12
|
+
scan(location: HistoryLocation, signal?: AbortSignal): Promise<{
|
|
13
|
+
sessions: HistorySession[];
|
|
14
|
+
warnings: string[];
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
53
|
+
import { realpath } from "node:fs/promises";
|
|
54
|
+
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
55
|
+
import { requireThat } from "../../common/validation.js";
|
|
56
|
+
import { finalDshAnswer } from "./messages.js";
|
|
57
|
+
const text = (content) => content.flatMap(block => block.type === "text" ? [block.text] :
|
|
58
|
+
block.type === "image" ? ["[图片 " + (block.attachment.name ?? block.attachment.attachmentId) + "]"] :
|
|
59
|
+
block.type === "file" ? ["[附件 " + (block.attachment.name ?? block.attachment.attachmentId) + "]"] : []).join("\n");
|
|
60
|
+
/** Archive questions and the final/partial answer; raw tool records stay in the native log. */
|
|
61
|
+
export function projectDshHistory(header, events, name = "") {
|
|
62
|
+
const exchanges = [];
|
|
63
|
+
let users = [], answer;
|
|
64
|
+
const finish = (completed = false) => {
|
|
65
|
+
if (users.length) {
|
|
66
|
+
const entries = [...users, ...(answer ? [answer] : [])];
|
|
67
|
+
const body = answer ? answer.data.message.content.filter(block => block.type === "text").map(block => block.text).join("\n") : "";
|
|
68
|
+
exchanges.push({ entryIds: entries.map(event => "dsh:" + event.seq), from: new Date(users[0].time).toISOString(),
|
|
69
|
+
to: new Date(entries.at(-1).time).toISOString(),
|
|
70
|
+
completed: completed && Boolean(answer && finalDshAnswer(answer).trim()),
|
|
71
|
+
text: users.map(event => (event.data.source.kind === "user" ? "用户:\n" : "协调者要求:\n") + text(event.data.content)).join("\n\n") +
|
|
72
|
+
(body ? "\n\n助手:\n" + body : "") });
|
|
73
|
+
}
|
|
74
|
+
users = [];
|
|
75
|
+
answer = undefined;
|
|
76
|
+
};
|
|
77
|
+
for (const event of events) {
|
|
78
|
+
if (event.type === "turn/start")
|
|
79
|
+
finish();
|
|
80
|
+
else if (event.type === "user/message" && ["user", "pbot-input"].includes(event.data.source.kind))
|
|
81
|
+
users.push(event);
|
|
82
|
+
else if (event.type === "assistant/message" && users.length)
|
|
83
|
+
answer = event;
|
|
84
|
+
else if (event.type === "turn/end")
|
|
85
|
+
finish(event.data.reason.kind === "completed");
|
|
86
|
+
}
|
|
87
|
+
finish();
|
|
88
|
+
return { sessionId: header.id, name: name || exchanges[0]?.text.replace(/\s+/g, " ").slice(0, 80) || "",
|
|
89
|
+
branches: exchanges.length ? [exchanges] : [] };
|
|
90
|
+
}
|
|
91
|
+
export class DshHistory {
|
|
92
|
+
ctx;
|
|
93
|
+
root;
|
|
94
|
+
relatedSessions;
|
|
95
|
+
host = "dsh";
|
|
96
|
+
constructor(ctx, root, relatedSessions = () => []) {
|
|
97
|
+
this.ctx = ctx;
|
|
98
|
+
this.root = root;
|
|
99
|
+
this.relatedSessions = relatedSessions;
|
|
100
|
+
}
|
|
101
|
+
async scan(location, signal) {
|
|
102
|
+
requireThat(location.host === "dsh", "dsh 历史只读取 dsh 原生会话。");
|
|
103
|
+
signal?.throwIfAborted();
|
|
104
|
+
const records = await this.ctx.sessionQuery.listSessions(signal), selected = new Set(), warnings = [];
|
|
105
|
+
try {
|
|
106
|
+
for (const id of this.relatedSessions())
|
|
107
|
+
selected.add(id);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
warnings.push("专家引用:" + String(error));
|
|
111
|
+
}
|
|
112
|
+
if (location.sessionId)
|
|
113
|
+
selected.add(location.sessionId);
|
|
114
|
+
for (const record of records) {
|
|
115
|
+
signal?.throwIfAborted();
|
|
116
|
+
if ((record.header.origin !== "subagent" || !record.header.parentSession) && record.header.cwd &&
|
|
117
|
+
await realpath(record.header.cwd).catch(() => record.header.cwd) === this.root)
|
|
118
|
+
selected.add(record.header.id);
|
|
119
|
+
}
|
|
120
|
+
// Native subagents may use a different cwd while still belonging to a workspace's parent.
|
|
121
|
+
for (let changed = true; changed;) {
|
|
122
|
+
changed = false;
|
|
123
|
+
for (const record of records)
|
|
124
|
+
if (record.header.origin === "subagent" && record.header.parentSession && selected.has(record.header.parentSession) && !selected.has(record.header.id)) {
|
|
125
|
+
selected.add(record.header.id);
|
|
126
|
+
changed = true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const sessions = [];
|
|
130
|
+
for (const id of selected) {
|
|
131
|
+
signal?.throwIfAborted();
|
|
132
|
+
try {
|
|
133
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
134
|
+
try {
|
|
135
|
+
const observation = __addDisposableResource(env_1, await this.ctx.sessionQuery.observeSession(SessionId(id), { ...(signal ? { signal } : {}), projectionMode: "none" }), false);
|
|
136
|
+
const title = await this.ctx.sessionQuery.readTitle(SessionId(id), signal);
|
|
137
|
+
const session = projectDshHistory(observation.header, observation.events, title?.title);
|
|
138
|
+
if (session.branches.length)
|
|
139
|
+
sessions.push(session);
|
|
140
|
+
}
|
|
141
|
+
catch (e_1) {
|
|
142
|
+
env_1.error = e_1;
|
|
143
|
+
env_1.hasError = true;
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
__disposeResources(env_1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
signal?.throwIfAborted();
|
|
151
|
+
warnings.push(id + ": " + String(error));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return { sessions, warnings };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import { type FileBlock, type ImageBlock } from "@deepseek-ai/dsh-llm";
|
|
3
|
+
import type { AgentDescriptor, AgentMessage } from "../../model/agent.js";
|
|
4
|
+
import "./messages.js";
|
|
5
|
+
export declare function dshInput(ctx: Context, d: AgentDescriptor, request: AgentMessage, signal: AbortSignal): Promise<{
|
|
6
|
+
content: import("@deepseek-ai/dsh-attachment").AdmittedPromptContentPart[];
|
|
7
|
+
source: {
|
|
8
|
+
kind: "pbot-input";
|
|
9
|
+
form: "relay";
|
|
10
|
+
requestId: string;
|
|
11
|
+
generation: string;
|
|
12
|
+
parentSessionId: string;
|
|
13
|
+
};
|
|
14
|
+
} & Pick<import("@deepseek-ai/dsh-llm").UserMessage, "role" | "id">>;
|
|
15
|
+
/** Command attachments have already passed native upload admission; the caller never supplies arbitrary references. */
|
|
16
|
+
export declare function dshHumanInput(ctx: Context, cwd: string, text: string, files: readonly string[], attachments: readonly (ImageBlock | FileBlock)[], signal: AbortSignal): Promise<{
|
|
17
|
+
content: import("@deepseek-ai/dsh-attachment").AdmittedPromptContentPart[];
|
|
18
|
+
source: {
|
|
19
|
+
kind: "user";
|
|
20
|
+
};
|
|
21
|
+
} & Pick<import("@deepseek-ai/dsh-llm").UserMessage, "role" | "id">>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { basename, extname } from "node:path";
|
|
2
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
3
|
+
import { requireThat } from "../../common/validation.js";
|
|
4
|
+
import "./messages.js";
|
|
5
|
+
const imageTypes = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp", ".gif": "image/gif" };
|
|
6
|
+
/** Native file attachments remain verbatim; image admission uses the host's policy. */
|
|
7
|
+
async function contentFor(ctx, cwd, message, files, signal) {
|
|
8
|
+
const content = [];
|
|
9
|
+
let imageCount = 0, imageBytes = 0;
|
|
10
|
+
for (const path of files) {
|
|
11
|
+
signal.throwIfAborted();
|
|
12
|
+
const target = await ctx.fs.resolve(path, { cwd, signal });
|
|
13
|
+
const before = await ctx.fs.stat(target, signal);
|
|
14
|
+
requireThat(before?.type === "file", "附件必须是普通文件:" + path);
|
|
15
|
+
const name = basename(path), mediaType = imageTypes[extname(path).toLowerCase()];
|
|
16
|
+
if (mediaType) {
|
|
17
|
+
const limits = ctx.attachments.imageLimits;
|
|
18
|
+
requireThat(++imageCount <= limits.maxImagesPerMessage &&
|
|
19
|
+
(before.size === undefined || imageBytes + before.size <= limits.maxMessageImageBytes), "图片附件超过宿主消息准入上限。");
|
|
20
|
+
const data = await ctx.fs.readBytes(target, signal, limits.maxImageBytes);
|
|
21
|
+
imageBytes += data.byteLength;
|
|
22
|
+
requireThat(imageBytes <= limits.maxMessageImageBytes, "图片附件超过宿主消息准入上限。");
|
|
23
|
+
content.push({ type: "image", mediaType, name, data: Buffer.from(data).toString("base64") });
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
async function* bytes(file) {
|
|
27
|
+
let offset = 0;
|
|
28
|
+
for (;;) {
|
|
29
|
+
signal.throwIfAborted();
|
|
30
|
+
const chunk = await ctx.fs.readByteRange(file, { offset, length: 256 * 1024 }, signal);
|
|
31
|
+
if (!chunk.length)
|
|
32
|
+
break;
|
|
33
|
+
yield chunk;
|
|
34
|
+
offset += chunk.length;
|
|
35
|
+
}
|
|
36
|
+
requireThat((await ctx.fs.stat(file, signal))?.version === before?.version, "读取期间附件已变化:" + path);
|
|
37
|
+
}
|
|
38
|
+
content.push({ type: "file", attachment: await ctx.attachments.saveFileStream({ data: bytes(target), name, signal }) });
|
|
39
|
+
}
|
|
40
|
+
requireThat((await ctx.fs.stat(target, signal))?.version === before.version, "读取期间附件已变化:" + path);
|
|
41
|
+
content.push({ type: "text", text: "附件原始路径:" + JSON.stringify(path) + "。原生附件保留本次文件内容;需要查看时使用宿主提供的附件路径。" });
|
|
42
|
+
}
|
|
43
|
+
content.push({ type: "text", text: message || "请处理所附文件。" });
|
|
44
|
+
const admitted = await ctx.attachments.admitPromptContent(content);
|
|
45
|
+
signal.throwIfAborted();
|
|
46
|
+
return admitted;
|
|
47
|
+
}
|
|
48
|
+
export async function dshInput(ctx, d, request, signal) {
|
|
49
|
+
const content = await contentFor(ctx, d.cwd, request.message, request.files, signal);
|
|
50
|
+
return createUserMessage({ content, source: { kind: "pbot-input", form: "relay",
|
|
51
|
+
requestId: request.requestId, generation: request.generation, parentSessionId: d.parentSessionId } });
|
|
52
|
+
}
|
|
53
|
+
/** Command attachments have already passed native upload admission; the caller never supplies arbitrary references. */
|
|
54
|
+
export async function dshHumanInput(ctx, cwd, text, files, attachments, signal) {
|
|
55
|
+
requireThat(files.length + attachments.length <= 32, "一次最多附加 32 个文件。");
|
|
56
|
+
requireThat(!files.length || !attachments.length, "一次对话请选择服务器文件或输入框附件,不能同时使用。");
|
|
57
|
+
signal.throwIfAborted();
|
|
58
|
+
const content = attachments.length ? [...attachments, { type: "text", text: text || "请处理所附文件。" }] :
|
|
59
|
+
await contentFor(ctx, cwd, text, files, signal);
|
|
60
|
+
signal.throwIfAborted();
|
|
61
|
+
return createUserMessage({ content, source: { kind: "user" } });
|
|
62
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type UserMessage } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
import type { SessionEvent } from "@deepseek-ai/dsh-session";
|
|
3
|
+
/** Producer metadata travels with native messages and never grants task authorization. */
|
|
4
|
+
declare module "@deepseek-ai/dsh-llm" {
|
|
5
|
+
interface MessageSourceMap {
|
|
6
|
+
"pbot-input": {
|
|
7
|
+
kind: "pbot-input";
|
|
8
|
+
form: "relay";
|
|
9
|
+
requestId: string;
|
|
10
|
+
generation: string;
|
|
11
|
+
parentSessionId: string;
|
|
12
|
+
};
|
|
13
|
+
"pbot-team-context": {
|
|
14
|
+
kind: "pbot-team-context";
|
|
15
|
+
form: "relay";
|
|
16
|
+
teamId: string;
|
|
17
|
+
seqs: number[];
|
|
18
|
+
};
|
|
19
|
+
"pbot-results": {
|
|
20
|
+
kind: "pbot-results";
|
|
21
|
+
form: "notice";
|
|
22
|
+
summary: string;
|
|
23
|
+
keys: string[];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export declare function finalDshAnswer(event: Extract<SessionEvent, {
|
|
28
|
+
type: "assistant/message";
|
|
29
|
+
}>): string;
|
|
30
|
+
export declare function requestSource(message: UserMessage, generation: string): {
|
|
31
|
+
kind: "pbot-input";
|
|
32
|
+
form: "relay";
|
|
33
|
+
requestId: string;
|
|
34
|
+
generation: string;
|
|
35
|
+
parentSessionId: string;
|
|
36
|
+
} | undefined;
|