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.
Files changed (234) hide show
  1. package/CONTEXT.md +13 -7
  2. package/README.md +46 -9
  3. package/README.zh-CN.md +46 -9
  4. package/bin/pbot.mjs +3 -0
  5. package/docs/README.md +4 -1
  6. package/docs/agent-collaboration.md +26 -12
  7. package/docs/architecture-guidelines.md +2 -2
  8. package/docs/capability-design.md +6 -4
  9. package/docs/dsh-implementation-plan.md +158 -0
  10. package/docs/dsh-support-proposal.md +314 -0
  11. package/docs/dsh.md +103 -0
  12. package/docs/file-storage.md +14 -9
  13. package/docs/history.md +29 -18
  14. package/docs/installation-design.md +226 -0
  15. package/docs/interaction-design.md +13 -0
  16. package/docs/release.md +5 -5
  17. package/docs/technical-design.md +29 -15
  18. package/docs/validation.md +53 -3
  19. package/lib/client.js +1071 -0
  20. package/lib/common/files.d.ts +2 -0
  21. package/lib/common/files.js +44 -0
  22. package/lib/common/json.d.ts +6 -0
  23. package/lib/common/json.js +16 -0
  24. package/lib/common/markdown.d.ts +5 -0
  25. package/lib/common/markdown.js +26 -0
  26. package/lib/common/package-source.js +16 -0
  27. package/lib/common/paths.d.ts +1 -0
  28. package/lib/common/paths.js +2 -0
  29. package/lib/common/validation.d.ts +3 -0
  30. package/lib/common/validation.js +14 -0
  31. package/lib/common/version.d.ts +3 -0
  32. package/lib/common/version.js +13 -0
  33. package/lib/dsh.d.ts +4 -0
  34. package/lib/dsh.js +22 -0
  35. package/lib/extension/dsh/agent-events.d.ts +24 -0
  36. package/lib/extension/dsh/agent-events.js +242 -0
  37. package/lib/extension/dsh/agent-runtime.d.ts +49 -0
  38. package/lib/extension/dsh/agent-runtime.js +348 -0
  39. package/lib/extension/dsh/command.d.ts +4 -0
  40. package/lib/extension/dsh/command.js +83 -0
  41. package/lib/extension/dsh/files.d.ts +5 -0
  42. package/lib/extension/dsh/files.js +67 -0
  43. package/lib/extension/dsh/history.d.ts +15 -0
  44. package/lib/extension/dsh/history.js +88 -0
  45. package/lib/extension/dsh/management.d.ts +21 -0
  46. package/lib/extension/dsh/management.js +217 -0
  47. package/lib/extension/dsh/workspace.d.ts +48 -0
  48. package/lib/extension/dsh/workspace.js +290 -0
  49. package/lib/extension/setup-cli.js +136 -0
  50. package/lib/extension/setup-guard.js +11 -0
  51. package/lib/integrations/agent-resources.d.ts +1 -0
  52. package/lib/integrations/agent-resources.js +18 -0
  53. package/lib/integrations/dsh/dsh-agents.d.ts +30 -0
  54. package/lib/integrations/dsh/dsh-agents.js +104 -0
  55. package/lib/integrations/dsh/history-summary.d.ts +11 -0
  56. package/lib/integrations/dsh/history-summary.js +40 -0
  57. package/lib/integrations/dsh/history.d.ts +16 -0
  58. package/lib/integrations/dsh/history.js +156 -0
  59. package/lib/integrations/dsh/input.d.ts +21 -0
  60. package/lib/integrations/dsh/input.js +62 -0
  61. package/lib/integrations/dsh/messages.d.ts +36 -0
  62. package/lib/integrations/dsh/messages.js +11 -0
  63. package/lib/integrations/dsh/notifications.d.ts +8 -0
  64. package/lib/integrations/dsh/notifications.js +55 -0
  65. package/lib/integrations/dsh/resources.d.ts +10 -0
  66. package/lib/integrations/dsh/resources.js +41 -0
  67. package/lib/integrations/dsh/schema.d.ts +7 -0
  68. package/lib/integrations/dsh/schema.js +28 -0
  69. package/lib/integrations/dsh/session.d.ts +14 -0
  70. package/lib/integrations/dsh/session.js +59 -0
  71. package/lib/integrations/history-summary.d.ts +2 -0
  72. package/lib/integrations/history-summary.js +21 -0
  73. package/lib/integrations/installation/pi-bridge.js +131 -0
  74. package/lib/integrations/installation/pi-setup.js +193 -0
  75. package/lib/integrations/installation/processes.js +155 -0
  76. package/lib/integrations/installation/workspace.js +21 -0
  77. package/lib/integrations/process.d.ts +14 -0
  78. package/lib/integrations/process.js +48 -0
  79. package/lib/integrations/python-activation.d.ts +2 -0
  80. package/lib/integrations/python-activation.js +29 -0
  81. package/lib/integrations/team-context.d.ts +18 -0
  82. package/lib/integrations/team-context.js +57 -0
  83. package/lib/model/agent.d.ts +92 -0
  84. package/lib/model/agent.js +1 -0
  85. package/lib/model/history.d.ts +43 -0
  86. package/lib/model/history.js +1 -0
  87. package/lib/model/team.d.ts +85 -0
  88. package/lib/model/team.js +1 -0
  89. package/lib/services/agents/agent-notices.d.ts +13 -0
  90. package/lib/services/agents/agent-notices.js +25 -0
  91. package/lib/services/agents/agent-run.d.ts +39 -0
  92. package/lib/services/agents/agent-run.js +138 -0
  93. package/lib/services/agents/agent-service.d.ts +166 -0
  94. package/lib/services/agents/agent-service.js +325 -0
  95. package/lib/services/agents/team-service.d.ts +168 -0
  96. package/lib/services/agents/team-service.js +343 -0
  97. package/lib/services/history/history-service.d.ts +103 -0
  98. package/lib/services/history/history-service.js +269 -0
  99. package/lib/services/installation/setup-service.js +121 -0
  100. package/lib/services/workspace/python-environment-service.d.ts +15 -0
  101. package/lib/services/workspace/python-environment-service.js +50 -0
  102. package/lib/services/workspace/workspace-files.d.ts +2 -0
  103. package/lib/services/workspace/workspace-files.js +17 -0
  104. package/lib/services/workspace/workspace-preparation.d.ts +2 -0
  105. package/lib/services/workspace/workspace-preparation.js +18 -0
  106. package/lib/storage/agent-files.d.ts +30 -0
  107. package/lib/storage/agent-files.js +166 -0
  108. package/lib/storage/atomic-json.d.ts +11 -0
  109. package/lib/storage/atomic-json.js +101 -0
  110. package/lib/storage/file-access.d.ts +2 -0
  111. package/lib/storage/file-access.js +26 -0
  112. package/lib/storage/history-files.d.ts +10 -0
  113. package/lib/storage/history-files.js +63 -0
  114. package/lib/storage/team-files.d.ts +16 -0
  115. package/lib/storage/team-files.js +111 -0
  116. package/lib/tools/agents/actions.d.ts +52 -0
  117. package/lib/tools/agents/actions.js +49 -0
  118. package/lib/tools/agents/contract.d.ts +163 -0
  119. package/lib/tools/agents/contract.js +56 -0
  120. package/lib/tools/agents/dsh-contract.d.ts +52 -0
  121. package/lib/tools/agents/dsh-contract.js +15 -0
  122. package/lib/tools/agents/dsh-tool.d.ts +5 -0
  123. package/lib/tools/agents/dsh-tool.js +38 -0
  124. package/lib/tools/history/actions.d.ts +70 -0
  125. package/lib/tools/history/actions.js +7 -0
  126. package/lib/tools/history/contract.d.ts +81 -0
  127. package/lib/tools/history/contract.js +32 -0
  128. package/lib/tools/history/dsh-tool.d.ts +4 -0
  129. package/lib/tools/history/dsh-tool.js +22 -0
  130. package/lib/tools/result.d.ts +18 -0
  131. package/lib/tools/result.js +10 -0
  132. package/lib/tools/teams/actions.d.ts +24 -0
  133. package/lib/tools/teams/actions.js +38 -0
  134. package/lib/tools/teams/contract.d.ts +232 -0
  135. package/lib/tools/teams/contract.js +67 -0
  136. package/lib/tools/teams/dsh-tool.d.ts +8 -0
  137. package/lib/tools/teams/dsh-tool.js +49 -0
  138. package/lib/ui/dsh/attachments.d.ts +5 -0
  139. package/lib/ui/dsh/attachments.js +39 -0
  140. package/lib/ui/dsh/elements.d.ts +17 -0
  141. package/lib/ui/dsh/elements.js +79 -0
  142. package/lib/ui/dsh/index.d.ts +4 -0
  143. package/lib/ui/dsh/index.js +13 -0
  144. package/lib/ui/dsh/management.d.ts +4 -0
  145. package/lib/ui/dsh/management.js +711 -0
  146. package/lib/ui/dsh/remote.d.ts +15 -0
  147. package/lib/ui/dsh/remote.js +11 -0
  148. package/lib/ui/dsh/styles.d.ts +1 -0
  149. package/lib/ui/dsh/styles.js +132 -0
  150. package/lib/ui/presentation.d.ts +3 -0
  151. package/lib/ui/presentation.js +12 -0
  152. package/lib/ui/setup.js +52 -0
  153. package/npm-shrinkwrap.json +11118 -783
  154. package/package.json +113 -9
  155. package/python/README.md +6 -6
  156. package/resources/agents/coordinator.md +1 -1
  157. package/resources/dsh/cordis.patch.yml +3 -0
  158. package/resources/installer/plugins.json +16 -0
  159. package/resources/prompts/workspace.md +1 -1
  160. package/resources/references/compute.md +1 -1
  161. package/resources/references/task-conductor.md +6 -4
  162. package/resources/skills/provider-connections/SKILL.md +10 -0
  163. package/scripts/README.md +13 -1
  164. package/src/common/package-source.ts +16 -0
  165. package/src/dsh.ts +22 -0
  166. package/src/extension/agent-events.ts +51 -46
  167. package/src/extension/agents-command.ts +48 -34
  168. package/src/extension/command.ts +42 -23
  169. package/src/extension/dsh/agent-events.ts +148 -0
  170. package/src/extension/dsh/agent-runtime.ts +251 -0
  171. package/src/extension/dsh/command.ts +78 -0
  172. package/src/extension/dsh/files.ts +61 -0
  173. package/src/extension/dsh/history.ts +69 -0
  174. package/src/extension/dsh/management.ts +108 -0
  175. package/src/extension/dsh/workspace.ts +206 -0
  176. package/src/extension/events.ts +6 -4
  177. package/src/extension/history.ts +15 -12
  178. package/src/extension/setup-cli.ts +109 -0
  179. package/src/extension/setup-guard.ts +12 -0
  180. package/src/extension/subagent-entry.ts +34 -63
  181. package/src/extension/teams-command.ts +53 -46
  182. package/src/integrations/{pi/agent-resources.ts → agent-resources.ts} +2 -2
  183. package/src/integrations/dsh/dsh-agents.ts +97 -0
  184. package/src/integrations/dsh/history-summary.ts +43 -0
  185. package/src/integrations/dsh/history.ts +76 -0
  186. package/src/integrations/dsh/input.ts +66 -0
  187. package/src/integrations/dsh/messages.ts +22 -0
  188. package/src/integrations/dsh/notifications.ts +46 -0
  189. package/src/integrations/dsh/resources.ts +44 -0
  190. package/src/integrations/dsh/schema.ts +26 -0
  191. package/src/integrations/dsh/session.ts +67 -0
  192. package/src/integrations/history-summary.ts +20 -0
  193. package/src/integrations/installation/pi-bridge.ts +107 -0
  194. package/src/integrations/installation/pi-setup.ts +162 -0
  195. package/src/integrations/installation/processes.ts +124 -0
  196. package/src/integrations/installation/workspace.ts +18 -0
  197. package/src/integrations/pi/history.ts +4 -14
  198. package/src/integrations/pi/pi-agents.ts +17 -6
  199. package/src/integrations/pi/team-context.ts +6 -58
  200. package/src/integrations/team-context.ts +60 -0
  201. package/src/model/agent.ts +2 -0
  202. package/src/model/history.ts +3 -1
  203. package/src/pbot.ts +1 -1
  204. package/src/services/agents/agent-notices.ts +28 -0
  205. package/src/services/agents/agent-run.ts +114 -0
  206. package/src/services/agents/agent-service.ts +127 -64
  207. package/src/services/history/history-service.ts +120 -59
  208. package/src/services/installation/setup-service.ts +145 -0
  209. package/src/storage/agent-files.ts +17 -4
  210. package/src/storage/history-files.ts +30 -5
  211. package/src/tools/agents/actions.ts +47 -0
  212. package/src/tools/agents/contract.ts +8 -5
  213. package/src/tools/agents/dsh-contract.ts +16 -0
  214. package/src/tools/agents/dsh-tool.ts +41 -0
  215. package/src/tools/agents/tool.ts +7 -43
  216. package/src/tools/history/actions.ts +9 -0
  217. package/src/tools/history/contract.ts +34 -0
  218. package/src/tools/history/dsh-tool.ts +22 -0
  219. package/src/tools/history/tool.ts +7 -30
  220. package/src/tools/teams/actions.ts +38 -0
  221. package/src/tools/teams/contract.ts +7 -3
  222. package/src/tools/teams/dsh-tool.ts +55 -0
  223. package/src/tools/teams/tool.ts +6 -31
  224. package/src/ui/agent-display.ts +7 -4
  225. package/src/ui/dsh/attachments.ts +42 -0
  226. package/src/ui/dsh/elements.ts +50 -0
  227. package/src/ui/dsh/index.ts +18 -0
  228. package/src/ui/dsh/management.ts +412 -0
  229. package/src/ui/dsh/remote.ts +20 -0
  230. package/src/ui/dsh/styles.ts +132 -0
  231. package/src/ui/pi/menu.ts +37 -0
  232. package/src/ui/pi/reader.ts +46 -0
  233. package/src/ui/presentation.ts +12 -0
  234. package/src/ui/setup.ts +40 -0
@@ -0,0 +1,155 @@
1
+ import { spawn } from "node:child_process";
2
+ import { access, realpath, stat } from "node:fs/promises";
3
+ import { constants } from "node:fs";
4
+ import { delimiter, isAbsolute, join, resolve } from "node:path";
5
+ export async function findExecutable(name, env = process.env) {
6
+ const directories = (env.PATH ?? "").split(delimiter).filter(Boolean);
7
+ for (const directory of directories) {
8
+ const path = resolve(directory, name);
9
+ // npm exec prepends transient bins. Never use a cached peer as the user's Pi.
10
+ if (name === "pi" && path.split(/[\\/]/).includes("_npx"))
11
+ continue;
12
+ try {
13
+ await access(path, constants.X_OK);
14
+ if (!(await stat(path)).isFile())
15
+ continue;
16
+ const target = await realpath(path);
17
+ if (name === "pi" && target.split(/[\\/]/).includes("_npx"))
18
+ continue;
19
+ return path;
20
+ }
21
+ catch (error) {
22
+ if (!["ENOENT", "ENOTDIR", "EACCES"].includes(error.code ?? ""))
23
+ throw error;
24
+ }
25
+ }
26
+ return undefined;
27
+ }
28
+ export class SetupProcesses {
29
+ signal;
30
+ env;
31
+ constructor(signal, env = process.env) {
32
+ this.signal = signal;
33
+ this.env = env;
34
+ }
35
+ async run(command, args, cwd, options = {}) {
36
+ this.signal.throwIfAborted();
37
+ if (!isAbsolute(cwd))
38
+ throw new Error("进程工作目录必须是绝对路径。");
39
+ const interactive = options.interactive ?? false;
40
+ const timeoutMs = options.timeoutMs ?? (interactive ? 15 * 60_000 : 60_000);
41
+ return new Promise((resolveResult, reject) => {
42
+ const child = spawn(command, args, { cwd, env: this.env, shell: false, detached: !interactive,
43
+ stdio: interactive ? "inherit" : ["pipe", "pipe", "pipe"] });
44
+ const stdout = [], stderr = [];
45
+ let bytes = 0, failure, escalation;
46
+ const kill = (signal) => {
47
+ if (!child.pid)
48
+ return;
49
+ try {
50
+ if (interactive)
51
+ child.kill(signal);
52
+ else
53
+ process.kill(-child.pid, signal);
54
+ }
55
+ catch { /* Process already exited. */ }
56
+ };
57
+ const stop = (reason) => {
58
+ if (failure)
59
+ return;
60
+ failure = reason;
61
+ kill("SIGTERM");
62
+ escalation = setTimeout(() => kill("SIGKILL"), 2000);
63
+ };
64
+ const cancel = () => stop(new Error("安装已取消。"));
65
+ const timer = timeoutMs > 0 ? setTimeout(() => stop(new Error(`命令超时:${command}`)), timeoutMs) : undefined;
66
+ this.signal.addEventListener("abort", cancel, { once: true });
67
+ const collect = (chunks) => (chunk) => {
68
+ bytes += chunk.length;
69
+ if (bytes > 2 * 1024 * 1024)
70
+ stop(new Error(`命令输出超过限制:${command}`));
71
+ else
72
+ chunks.push(chunk);
73
+ };
74
+ child.stdout?.on("data", collect(stdout));
75
+ child.stderr?.on("data", collect(stderr));
76
+ child.stdin?.on("error", () => { });
77
+ child.on("error", error => { failure = error; });
78
+ child.on("close", (code, exitSignal) => {
79
+ clearTimeout(timer);
80
+ clearTimeout(escalation);
81
+ this.signal.removeEventListener("abort", cancel);
82
+ if (this.signal.aborted)
83
+ return reject(this.signal.reason);
84
+ if (failure)
85
+ return reject(failure);
86
+ if (code !== 0)
87
+ return reject(new Error(`${command} 失败 (${exitSignal ?? code})\n${Buffer.concat(stderr).toString("utf8").slice(-8000)}`));
88
+ resolveResult(Buffer.concat(stdout).toString("utf8").trim());
89
+ });
90
+ child.stdin?.end(options.input);
91
+ if (this.signal.aborted)
92
+ cancel();
93
+ });
94
+ }
95
+ }
96
+ export function tmuxInstallCommand(platform, commands, root) {
97
+ let command, args;
98
+ if (platform === "darwin") {
99
+ command = commands.get("brew");
100
+ args = ["install", "tmux"];
101
+ }
102
+ else {
103
+ const manager = ["apt-get", "dnf", "pacman", "zypper", "apk"].find(name => commands.has(name));
104
+ command = manager ? commands.get(manager) : undefined;
105
+ args = manager === "apt-get" ? ["install", "-y", "tmux"] : manager === "dnf" ? ["install", "-y", "tmux"]
106
+ : manager === "pacman" ? ["-S", "--needed", "--noconfirm", "tmux"]
107
+ : manager === "zypper" ? ["--non-interactive", "install", "tmux"] : ["add", "tmux"];
108
+ if (command && !root) {
109
+ const sudo = commands.get("sudo");
110
+ if (!sudo)
111
+ throw new Error("安装 tmux 需要管理员权限;请手动安装 tmux 后重新运行 setup。");
112
+ return { command: sudo, args: [command, ...args] };
113
+ }
114
+ }
115
+ if (!command)
116
+ throw new Error(platform === "darwin"
117
+ ? "未找到 Homebrew。请先通过现有环境安装 tmux,再运行 setup。"
118
+ : "未找到支持的系统包管理器。请手动安装 tmux,再运行 setup。");
119
+ return { command, args };
120
+ }
121
+ export async function inspectSystem(processes, cwd) {
122
+ if (!["linux", "darwin"].includes(process.platform))
123
+ throw new Error("setup 支持 Linux 和 macOS;Windows 请使用 WSL。");
124
+ const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
125
+ if (major < 22 || major === 22 && minor < 19)
126
+ throw new Error("需要 Node.js 22.19 或更高版本。");
127
+ const npm = await findExecutable("npm", processes.env);
128
+ if (!npm)
129
+ throw new Error("未找到 npm。请先安装 Node.js/npm。");
130
+ const python = await findExecutable("python3", processes.env) ?? await findExecutable("python", processes.env);
131
+ if (!python)
132
+ throw new Error("需要 Python 3.11+ 和 venv/ensurepip。请先安装后重试。");
133
+ try {
134
+ await processes.run(python, ["-I", "-B", "-c", [
135
+ "import sys, tempfile, venv, ensurepip",
136
+ "assert sys.version_info >= (3, 11), 'Python 3.11+ required'",
137
+ "with tempfile.TemporaryDirectory(prefix='pbot-python-check-') as path:",
138
+ " venv.EnvBuilder(with_pip=True).create(path)",
139
+ ].join("\n")], cwd);
140
+ }
141
+ catch (error) {
142
+ processes.signal.throwIfAborted();
143
+ throw new Error("基础 Python 检查失败。需要 Python 3.11+ 和 venv/ensurepip;Debian/Ubuntu 还需 python3-venv。\n" + String(error));
144
+ }
145
+ const tmux = await findExecutable("tmux", processes.env);
146
+ if (tmux) {
147
+ await processes.run(tmux, ["-V"], cwd);
148
+ return { npm };
149
+ }
150
+ const names = process.platform === "darwin" ? ["brew"] : ["apt-get", "dnf", "pacman", "zypper", "apk", "sudo"];
151
+ const entries = await Promise.all(names.map(async (name) => [name, await findExecutable(name, processes.env)]));
152
+ const commands = new Map(entries.filter((entry) => entry[1] !== undefined));
153
+ return { npm, tmux: tmuxInstallCommand(process.platform, commands, process.getuid?.() === 0) };
154
+ }
155
+ export function npmGlobalBinary(prefix) { return join(prefix, "bin", "pi"); }
@@ -0,0 +1,21 @@
1
+ import { stat, realpath } from "node:fs/promises";
2
+ import { dirname, join, relative, resolve } from "node:path";
3
+ export async function resolveWorkspace(value, cwd, home) {
4
+ const path = resolve(cwd, value === "~" ? home : value.startsWith("~/") ? join(home, value.slice(2)) : value);
5
+ let existing = path;
6
+ while (true) {
7
+ try {
8
+ if (!(await stat(existing)).isDirectory())
9
+ throw new Error("工作区或其父路径不是目录:" + existing);
10
+ return resolve(await realpath(existing), relative(existing, path));
11
+ }
12
+ catch (error) {
13
+ if (error.code !== "ENOENT")
14
+ throw error;
15
+ const parent = dirname(existing);
16
+ if (parent === existing)
17
+ throw error;
18
+ existing = parent;
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,14 @@
1
+ import type { Json } from "../common/json.js";
2
+ interface ProcessResult {
3
+ success: boolean;
4
+ data: Json;
5
+ raw: Json;
6
+ }
7
+ export declare function processEnvironment(env: Record<string, string>): {
8
+ PATH: string;
9
+ LANG: string;
10
+ };
11
+ export declare function runCommand(command: string, args: string[], input: string | undefined, cwd: string, env: Record<string, string>, signal?: AbortSignal, options?: {
12
+ timeoutMs?: number;
13
+ }): Promise<ProcessResult>;
14
+ export {};
@@ -0,0 +1,48 @@
1
+ import { spawn } from "node:child_process";
2
+ export function processEnvironment(env) {
3
+ return { PATH: process.env.PATH ?? "/usr/bin:/bin", LANG: "C.UTF-8", ...env };
4
+ }
5
+ export async function runCommand(command, args, input, cwd, env, signal, options = {}) {
6
+ signal?.throwIfAborted();
7
+ return new Promise(resolve => {
8
+ const child = spawn(command, args, { cwd, env: processEnvironment(env), shell: false,
9
+ detached: true, stdio: ["pipe", "pipe", "pipe"] });
10
+ let bytes = 0, failed = false;
11
+ const out = [], err = [];
12
+ const kill = () => { if (child.pid) {
13
+ try {
14
+ process.kill(-child.pid, "SIGKILL");
15
+ }
16
+ catch { /* Already stopped. */ }
17
+ } };
18
+ const stop = () => { failed = true; kill(); };
19
+ const timer = setTimeout(stop, options.timeoutMs ?? 30_000);
20
+ signal?.addEventListener("abort", stop, { once: true });
21
+ const collect = (target) => (chunk) => {
22
+ bytes += chunk.length;
23
+ if (bytes > 2 * 1024 * 1024)
24
+ stop();
25
+ else
26
+ target.push(chunk);
27
+ };
28
+ child.stdout.on("data", collect(out));
29
+ child.stderr.on("data", collect(err));
30
+ child.on("error", stop);
31
+ child.stdin.on("error", stop);
32
+ child.once("close", code => {
33
+ clearTimeout(timer);
34
+ signal?.removeEventListener("abort", stop);
35
+ kill();
36
+ const text = Buffer.concat(out).toString("utf8");
37
+ let data = { stdout: text, stderr: Buffer.concat(err).toString("utf8"), exitCode: code };
38
+ try {
39
+ data = JSON.parse(text);
40
+ }
41
+ catch { /* Preserve text and exit code. */ }
42
+ resolve({ success: !failed && !signal?.aborted && code === 0, data, raw: { stdout: text, stderr: Buffer.concat(err).toString("utf8"), exitCode: code, incomplete: failed || !!signal?.aborted } });
43
+ });
44
+ if (signal?.aborted)
45
+ stop();
46
+ child.stdin.end(input);
47
+ });
48
+ }
@@ -0,0 +1,2 @@
1
+ /** Like venv activation, scoped to this Pi process; never edits shell configuration. */
2
+ export declare function activatePython(directory: string): () => void;
@@ -0,0 +1,29 @@
1
+ import { delimiter, join } from "node:path";
2
+ /** Like venv activation, scoped to this Pi process; never edits shell configuration. */
3
+ export function activatePython(directory) {
4
+ const values = {
5
+ PATH: join(directory, "bin") + delimiter + (process.env.PATH ?? "/usr/bin:/bin"),
6
+ VIRTUAL_ENV: directory,
7
+ PYTHONHOME: undefined,
8
+ PYTHONPATH: undefined,
9
+ PYTHONNOUSERSITE: "1",
10
+ };
11
+ const previous = new Map(Object.keys(values).map(key => [key, process.env[key]]));
12
+ const assign = (key, value) => {
13
+ if (value === undefined)
14
+ delete process.env[key];
15
+ else
16
+ process.env[key] = value;
17
+ };
18
+ for (const [key, value] of Object.entries(values))
19
+ assign(key, value);
20
+ let restored = false;
21
+ return () => {
22
+ if (restored)
23
+ return;
24
+ restored = true;
25
+ for (const [key, value] of previous)
26
+ if (process.env[key] === values[key])
27
+ assign(key, value);
28
+ };
29
+ }
@@ -0,0 +1,18 @@
1
+ import type { TeamLink, TeamMessage } from "../model/team.js";
2
+ type MessagePage = Pick<TeamMessage, "seq" | "author" | "text" | "replyTo" | "runId" | "source"> & {
3
+ offset: number;
4
+ totalLength: number;
5
+ nextOffset: number | null;
6
+ };
7
+ export declare function teamMessagesMarkdown(page: {
8
+ teamId: string;
9
+ messages: MessagePage[];
10
+ lastSeq: number;
11
+ nextAfterSeq: number | null;
12
+ }): string;
13
+ export declare function teamBrief(link: TeamLink): string;
14
+ export declare function createTeamContext(link: TeamLink, seen: ReadonlySet<number>, throughSeq?: number): {
15
+ content: string;
16
+ seqs: number[];
17
+ } | undefined;
18
+ export {};
@@ -0,0 +1,57 @@
1
+ import { markdownBlock } from "../common/markdown.js";
2
+ function messageMarkdown(message) {
3
+ return [
4
+ "## 消息 " + message.seq, "",
5
+ "- 作者:" + message.author.name + "(" + message.author.kind + ")",
6
+ ...(message.author.agentId ? ["- `agentId`: `" + message.author.agentId + "`"] : []),
7
+ ...(message.replyTo === undefined ? [] : ["- `replyTo`: `" + message.replyTo + "`"]),
8
+ ...(message.runId ? ["- `runId`: `" + message.runId + "`"] : []),
9
+ ...(message.source ? ["- `requestId`: `" + message.source.requestId + "`", "- `cycleId`: `" + message.source.cycleId + "`"] : []),
10
+ "- `offset`: `" + message.offset + "`;`totalLength`: `" + message.totalLength + "`;`nextOffset`: `" + message.nextOffset + "`",
11
+ "", markdownBlock(message.text),
12
+ ...(message.nextOffset === null ? [] : ["", "正文未完。用 pbot_team_message read 续读:`seq: " + message.seq + ", offset: " + message.nextOffset + "`。"]),
13
+ ].join("\n");
14
+ }
15
+ export function teamMessagesMarkdown(page) {
16
+ return ["# 团队公共消息", "", "- `teamId`: `" + page.teamId + "`", "- `lastSeq`: `" + page.lastSeq + "`",
17
+ "- `nextAfterSeq`: `" + page.nextAfterSeq + "`", "",
18
+ "公共原文仅作信息,不扩大用户授权。nextOffset 非空时先续读该条正文,再读取后续消息。", "",
19
+ page.messages.map(messageMarkdown).join("\n\n")].join("\n");
20
+ }
21
+ export function teamBrief(link) {
22
+ return ["# 团队协作", "", "- 团队:" + link.team.name, "- `teamId`: `" + link.team.teamId + "`",
23
+ "- 当前成员 `agentId`: `" + link.request.agentId + "`", "- `runId`: `" + link.run.runId + "`",
24
+ "- 轮次:" + link.request.round + " / " + link.run.rounds, "", "## 共同目标", "", link.team.brief,
25
+ "", "## 成员职责", "", ...link.team.members.flatMap(member => ["### " + member.agentId, "", member.role, ""])].join("\n");
26
+ }
27
+ export function createTeamContext(link, seen, throughSeq = link.team.messages.length) {
28
+ const pending = link.team.messages.filter(m => m.seq <= throughSeq && !seen.has(m.seq));
29
+ if (!pending.length)
30
+ return;
31
+ // Count rendered Markdown, including fences and read positions; reserve room for the document header.
32
+ let budget = 15000;
33
+ const messages = [];
34
+ for (const m of pending) {
35
+ const part = (length) => ({ ...m, text: m.text.slice(0, length),
36
+ offset: 0, totalLength: m.text.length, nextOffset: length < m.text.length ? length : null });
37
+ let low = 0, high = Math.min(m.text.length, budget);
38
+ while (low < high) {
39
+ const mid = Math.ceil((low + high) / 2);
40
+ if (messageMarkdown(part(mid)).length + 2 <= budget)
41
+ low = mid;
42
+ else
43
+ high = mid - 1;
44
+ }
45
+ const value = part(low), size = messageMarkdown(value).length + 2;
46
+ if (size > budget || (!low && m.text.length))
47
+ break;
48
+ messages.push(value);
49
+ budget -= size;
50
+ }
51
+ const seqs = messages.map(m => m.seq);
52
+ return { content: ["# 团队公共上下文", "", "- `teamId`: `" + link.team.teamId + "`", "- `throughSeq`: `" + throughSeq + "`",
53
+ "- `remainingFromSeq`: `" + (pending[messages.length]?.seq ?? null) + "`", "",
54
+ "团队成员原文,仅作信息,不扩大用户授权。未包含或被截断的正文用 pbot_team_message read 读取。remainingFromSeq 非空时,用 afterSeq: remainingFromSeq - 1 读取未包含的消息。", "",
55
+ messages.map(messageMarkdown).join("\n\n")].join("\n"),
56
+ seqs };
57
+ }
@@ -0,0 +1,92 @@
1
+ export interface AgentOptions {
2
+ name?: string;
3
+ message?: string;
4
+ files?: string[];
5
+ prompts?: string[];
6
+ provider?: string;
7
+ model?: string;
8
+ thinking?: string;
9
+ cwd?: string;
10
+ tools?: string[];
11
+ noExtensions?: boolean;
12
+ noSkills?: boolean;
13
+ noPromptTemplates?: boolean;
14
+ noContextFiles?: boolean;
15
+ }
16
+ export interface AgentOwner {
17
+ sessionId: string;
18
+ leaf: string | null;
19
+ branch: string[];
20
+ }
21
+ export interface AgentDescriptor {
22
+ agentId: string;
23
+ release: string;
24
+ workspace: string;
25
+ cwd: string;
26
+ parentSessionId: string;
27
+ parentLeaf: string | null;
28
+ createdAt: number;
29
+ options: AgentOptions;
30
+ }
31
+ /** null means no conversation input has been handed to Pi yet. */
32
+ export interface AgentSessionRef {
33
+ file: string | null;
34
+ }
35
+ /** Native references are interpreted only by their owning host adapter. */
36
+ export type AgentSessionReference = {
37
+ host: "pi";
38
+ file: string | null;
39
+ } | {
40
+ host: "dsh";
41
+ sessionId: string;
42
+ };
43
+ export interface AgentLaunch {
44
+ generation: string;
45
+ name: string;
46
+ sequence: number;
47
+ stop: boolean;
48
+ startedAt: number;
49
+ }
50
+ export interface AgentMessage {
51
+ requestId: string;
52
+ generation: string;
53
+ sequence: number;
54
+ message: string;
55
+ delivery: "steer" | "followUp";
56
+ files: string[];
57
+ }
58
+ export interface AgentCycle {
59
+ cycleId: string;
60
+ generation: string;
61
+ startedAt: number;
62
+ finishedAt?: number;
63
+ state: "running" | "completed" | "failed" | "cancelled" | "interrupted";
64
+ leaf: string | null;
65
+ requestIds: string[];
66
+ source: "parent" | "interactive" | "mixed";
67
+ error?: string;
68
+ }
69
+ export interface AgentStatus {
70
+ generation: string;
71
+ state: "starting" | "busy" | "idle" | "stopping" | "stopped" | "error";
72
+ activity: string;
73
+ updatedAt: number;
74
+ accepted?: string[];
75
+ cycle?: AgentCycle;
76
+ error?: string;
77
+ }
78
+ export interface AgentView extends AgentDescriptor {
79
+ name: string;
80
+ generation: string;
81
+ state: AgentStatus["state"];
82
+ activity: string;
83
+ cycle?: AgentCycle;
84
+ error?: string;
85
+ owned: boolean;
86
+ }
87
+ export interface AgentCompletion {
88
+ agentId: string;
89
+ name: string;
90
+ cycle: AgentCycle;
91
+ report: string;
92
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ export interface HistoryExchange {
2
+ entryIds: string[];
3
+ text: string;
4
+ from: string;
5
+ to: string;
6
+ completed: boolean;
7
+ }
8
+ export interface HistorySession {
9
+ sessionId: string;
10
+ name: string;
11
+ branches: HistoryExchange[][];
12
+ }
13
+ export interface HistoryNode extends HistoryExchange {
14
+ id: string;
15
+ kind: "session" | "exchange" | "summary";
16
+ level: number;
17
+ children: string[];
18
+ parents: string[];
19
+ ready: boolean;
20
+ }
21
+ export interface HistoryRecord {
22
+ id: string;
23
+ sessionId: string;
24
+ name: string;
25
+ nodes: HistoryNode[];
26
+ }
27
+ export type HistoryHost = "pi" | "dsh";
28
+ export type HistoryLocation = {
29
+ host?: "pi";
30
+ directory: string;
31
+ file?: string;
32
+ } | {
33
+ host: "dsh";
34
+ sessionId?: string;
35
+ };
36
+ export interface HistorySource {
37
+ readonly host: HistoryHost;
38
+ scan(location: HistoryLocation, signal?: AbortSignal): Promise<{
39
+ sessions: HistorySession[];
40
+ warnings: string[];
41
+ }>;
42
+ }
43
+ export type HistorySummarizer = (text: string, signal: AbortSignal) => Promise<string>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,85 @@
1
+ import type { AgentOwner, AgentView } from "./agent.js";
2
+ export interface TeamRequest {
3
+ requestId: string;
4
+ agentId: string;
5
+ generation: string;
6
+ round: number;
7
+ throughSeq: number;
8
+ }
9
+ export interface TeamRun {
10
+ runId: string;
11
+ instruction: string;
12
+ mode: "roundRobin" | "parallel";
13
+ rounds: number;
14
+ maxConcurrent: number;
15
+ deadlineAt: number;
16
+ generations: Record<string, string>;
17
+ state: "running" | "paused" | "finished";
18
+ reason?: string;
19
+ issued: TeamRequest[];
20
+ }
21
+ export interface TeamMessage {
22
+ seq: number;
23
+ operationId: string;
24
+ author: {
25
+ kind: "user" | "coordinator" | "member";
26
+ agentId?: string;
27
+ name: string;
28
+ };
29
+ text: string;
30
+ replyTo?: number;
31
+ runId?: string;
32
+ source?: {
33
+ requestId: string;
34
+ cycleId: string;
35
+ };
36
+ }
37
+ export interface TeamRecord {
38
+ teamId: string;
39
+ release: string;
40
+ workspace: string;
41
+ parentSessionId: string;
42
+ parentLeaf: string | null;
43
+ name: string;
44
+ brief: string;
45
+ closed: boolean;
46
+ members: {
47
+ agentId: string;
48
+ role: string;
49
+ }[];
50
+ runs: TeamRun[];
51
+ messages: TeamMessage[];
52
+ }
53
+ export interface TeamReady {
54
+ generation: string;
55
+ ready: boolean;
56
+ }
57
+ export type TeamAgent = AgentView & {
58
+ alive: boolean;
59
+ };
60
+ export type TeamActor = {
61
+ kind: "user" | "coordinator";
62
+ owner: AgentOwner;
63
+ } | {
64
+ kind: "member";
65
+ agentId: string;
66
+ generation: string;
67
+ requestId?: string;
68
+ };
69
+ export interface TeamInput extends TeamRequest {
70
+ teamId: string;
71
+ runId: string;
72
+ message: string;
73
+ }
74
+ export interface TeamLink {
75
+ team: TeamRecord;
76
+ run: TeamRun;
77
+ request: TeamRequest;
78
+ }
79
+ export interface TeamRunOptions {
80
+ instruction: string;
81
+ mode: TeamRun["mode"];
82
+ rounds?: number;
83
+ maxConcurrent?: number;
84
+ timeoutSeconds?: number;
85
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import type { AgentCompletion } from "../../model/agent.js";
2
+ import type { TeamLink } from "../../model/team.js";
3
+ import type { TeamService } from "./team-service.js";
4
+ export declare function teamNotice(values: ReturnType<TeamService["notifications"]>, known: ReadonlySet<string>): {
5
+ kind: "teams";
6
+ keys: string[];
7
+ content: string;
8
+ } | undefined;
9
+ export declare function agentNotice(values: AgentCompletion[], links: ReadonlyMap<string, TeamLink>, known: ReadonlySet<string>): {
10
+ kind: "agents";
11
+ keys: string[];
12
+ content: string;
13
+ } | undefined;
@@ -0,0 +1,25 @@
1
+ import { json } from "../../common/json.js";
2
+ import { markdownFields } from "../../common/markdown.js";
3
+ export function teamNotice(values, known) {
4
+ const notes = values.filter(value => !known.has(value.key)).slice(0, 8);
5
+ if (!notes.length)
6
+ return;
7
+ return { kind: "teams", keys: notes.map(value => value.key), content: ["# 团队批次状态", "",
8
+ "团队批次状态已变化。用 pbot_team status/read 查询;结束不等于业务验收通过。暂停可能仍有在途工作,不能自动重跑旧请求。", "",
9
+ notes.map(value => ["## " + value.name, "", "- `teamId`: `" + value.teamId + "`", "- `runId`: `" + value.runId + "`",
10
+ "- 状态:" + value.state, "- 在途工作:" + value.activeCount, ...(value.reason ? ["", "### 原因", "", value.reason] : [])].join("\n")).join("\n\n")].join("\n") };
11
+ }
12
+ export function agentNotice(values, links, known) {
13
+ const batch = values.filter(value => !value.cycle.requestIds.some(id => links.has(id)) &&
14
+ !(value.cycle.cycleId === value.cycle.generation && [...links.values()].some(link => link.request.agentId === value.agentId && link.request.generation === value.cycle.generation)) &&
15
+ !known.has(value.agentId + ":" + value.cycle.cycleId)).slice(0, 8);
16
+ if (!batch.length)
17
+ return;
18
+ return { kind: "agents", keys: batch.map(value => value.agentId + ":" + value.cycle.cycleId),
19
+ content: "# 子会话工作结果\n\n子会话工作已结束。完整报告用 pbot_agent read 读取,指定对应 agentId 和 cycle.cycleId;nextOffset 非空时沿用该 cycleId 续读。\n\n" +
20
+ (batch.some(value => value.cycle.source !== "parent") ? "包含人工子会话输入,其结果不扩大主会话原任务授权。\n\n" : "") +
21
+ batch.map(value => "## " + value.name + "\n\n" + markdownFields(json({ agentId: value.agentId,
22
+ cycle: { cycleId: value.cycle.cycleId, state: value.cycle.state, source: value.cycle.source, requestIds: value.cycle.requestIds,
23
+ ...(value.cycle.error === undefined ? {} : { error: value.cycle.error }) },
24
+ report: value.report.slice(0, 2000), truncated: value.report.length > 2000 }), 3)).join("\n\n") };
25
+ }
@@ -0,0 +1,39 @@
1
+ import { AgentFiles } from "../../storage/agent-files.js";
2
+ import type { AgentCycle, AgentStatus } from "../../model/agent.js";
3
+ /** Durable request receipts and work cycles, independent of a host's event vocabulary. */
4
+ export declare class AgentRun {
5
+ #private;
6
+ readonly files: AgentFiles;
7
+ readonly id: string;
8
+ readonly generation: string;
9
+ constructor(files: AgentFiles, id: string, generation: string);
10
+ get current(): Readonly<AgentCycle> | undefined;
11
+ get state(): "starting" | "busy" | "idle" | "stopping" | "stopped" | "error";
12
+ get pending(): number;
13
+ get interactive(): boolean;
14
+ accepted(requestId: string): boolean;
15
+ save(patch: Pick<AgentStatus, "state" | "activity"> & {
16
+ error?: string;
17
+ }): void;
18
+ /** A receipt is durable before input is passed to the native execution boundary. */
19
+ claim(requestId: string): void;
20
+ /** Native ownership associates work even when preparation fails before model admission. */
21
+ enter(requestId: string): void;
22
+ /** Model admission is a separate fact from a native inbox claim. */
23
+ consume(requestId: string): void;
24
+ cancelQueued(requestId: string): void;
25
+ humanInput(): void;
26
+ enterHuman(): void;
27
+ begin(): void;
28
+ finish(end: {
29
+ state: Exclude<AgentCycle["state"], "running">;
30
+ position: string | null;
31
+ answer: string;
32
+ error?: string;
33
+ }): void;
34
+ /** Reload seals uncertainty. It never turns a receipt into permission to replay. */
35
+ recover(receipts?: Iterable<{
36
+ generation: string;
37
+ requestId: string;
38
+ }>): void;
39
+ }