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,166 @@
1
+ import { existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
2
+ import { isAbsolute, join } from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { canonical, object } from "../common/json.js";
5
+ import { requireThat } from "../common/validation.js";
6
+ import { readJsonFile, writeJsonFile } from "./atomic-json.js";
7
+ import { fileId, privateDirectory } from "./file-access.js";
8
+ const text = (v) => typeof v === "string" && v.length > 0;
9
+ const number = (v) => typeof v === "number" && Number.isFinite(v);
10
+ export function descriptor(v) {
11
+ requireThat(object(v) && text(v.agentId) && text(v.release) && text(v.workspace) && text(v.cwd) && text(v.parentSessionId) &&
12
+ (v.parentLeaf === null || text(v.parentLeaf)) && number(v.createdAt) && object(v.options), "子会话身份无效。");
13
+ fileId(v.agentId);
14
+ return v;
15
+ }
16
+ export function sessionRef(v) {
17
+ requireThat(object(v) && (v.file === null || (text(v.file) && isAbsolute(v.file) && !v.file.includes("\0"))), "子会话引用无效。");
18
+ return v;
19
+ }
20
+ function dshSessionRef(v) {
21
+ requireThat(object(v) && v.host === "dsh" && text(v.sessionId), "dsh 原生会话引用无效。");
22
+ fileId(v.sessionId);
23
+ return v;
24
+ }
25
+ export function launch(v) {
26
+ requireThat(object(v) && text(v.generation) && text(v.name) && Number.isSafeInteger(v.sequence) && typeof v.stop === "boolean" && number(v.startedAt), "子会话启动状态无效。");
27
+ return v;
28
+ }
29
+ export function message(v) {
30
+ requireThat(object(v) && text(v.requestId) && text(v.generation) && Number.isSafeInteger(v.sequence) && typeof v.message === "string" &&
31
+ ["steer", "followUp"].includes(String(v.delivery)) && Array.isArray(v.files) && v.files.every(text), "子会话消息无效。");
32
+ return v;
33
+ }
34
+ export function cycle(v) {
35
+ requireThat(object(v) && text(v.cycleId) && text(v.generation) && number(v.startedAt) &&
36
+ ["running", "completed", "failed", "cancelled", "interrupted"].includes(String(v.state)) &&
37
+ (v.leaf === null || text(v.leaf)) && Array.isArray(v.requestIds) && v.requestIds.every(text) &&
38
+ ["parent", "interactive", "mixed"].includes(String(v.source)) && (v.finishedAt === undefined || number(v.finishedAt)), "子会话周期无效。");
39
+ return v;
40
+ }
41
+ export function status(v) {
42
+ requireThat(object(v) && text(v.generation) && ["starting", "busy", "idle", "stopping", "stopped", "error"].includes(String(v.state)) &&
43
+ typeof v.activity === "string" && number(v.updatedAt), "子会话状态无效。");
44
+ if (v.cycle)
45
+ cycle(v.cycle);
46
+ return v;
47
+ }
48
+ export class AgentFiles {
49
+ root;
50
+ host;
51
+ #failure;
52
+ constructor(root, host = "pi") {
53
+ this.root = root;
54
+ this.host = host;
55
+ }
56
+ check() { if (this.#failure)
57
+ throw this.#failure; }
58
+ fail(error) { this.#failure = error; }
59
+ directory(id) { return join(this.root, fileId(id)); }
60
+ read(id, path, parse) { return readJsonFile(this.directory(id), path, parse); }
61
+ optional(id, path, parse) {
62
+ try {
63
+ return this.read(id, path, parse);
64
+ }
65
+ catch (error) {
66
+ if (error.code === "ENOENT")
67
+ return undefined;
68
+ throw error;
69
+ }
70
+ }
71
+ write(id, path, value, parse, create = false) {
72
+ this.check();
73
+ parse(value);
74
+ let published;
75
+ try {
76
+ published = writeJsonFile(this.directory(id), path, value, parse, create);
77
+ }
78
+ catch (error) {
79
+ this.fail(error);
80
+ throw error;
81
+ }
82
+ requireThat(published, "子会话文件已存在。");
83
+ }
84
+ create(value, run) {
85
+ this.check();
86
+ mkdirSync(this.root, { recursive: true, mode: 0o700 });
87
+ mkdirSync(this.directory(value.agentId), { mode: 0o700 });
88
+ for (const dir of ["inbox", "completions"])
89
+ privateDirectory(this.directory(value.agentId), dir);
90
+ this.write(value.agentId, "descriptor.json", value, descriptor, true);
91
+ this.write(value.agentId, "launch.json", run, launch, true);
92
+ if (this.host === "pi")
93
+ this.write(value.agentId, "session-ref.json", { file: null }, sessionRef, true);
94
+ else
95
+ this.write(value.agentId, "native-session.json", { host: "dsh", sessionId: value.agentId }, dshSessionRef, true);
96
+ }
97
+ list() {
98
+ if (!existsSync(this.root))
99
+ return [];
100
+ return readdirSync(this.root, { withFileTypes: true }).filter(e => e.isDirectory() && !e.name.startsWith(".")).flatMap(e => {
101
+ const d = this.optional(e.name, "descriptor.json", descriptor);
102
+ return d ? [d] : [];
103
+ });
104
+ }
105
+ messages(id) {
106
+ return readdirSync(join(this.directory(id), "inbox")).filter(n => n.endsWith(".json"))
107
+ .map(n => this.read(id, "inbox/" + n, message)).sort((a, b) => a.sequence - b.sequence);
108
+ }
109
+ completions(id) {
110
+ return readdirSync(join(this.directory(id), "completions")).filter(n => n.endsWith(".json"))
111
+ .map(n => this.read(id, "completions/" + n, cycle)).sort((a, b) => (a.finishedAt ?? 0) - (b.finishedAt ?? 0));
112
+ }
113
+ reference(id) {
114
+ if (this.host === "pi")
115
+ return { host: "pi", file: this.session(id) };
116
+ const ref = this.read(id, "native-session.json", dshSessionRef);
117
+ requireThat(ref.sessionId === id, "dsh 原生会话身份不匹配。");
118
+ return ref;
119
+ }
120
+ session(id) {
121
+ requireThat(this.host === "pi", "该宿主不使用 Pi 会话文件引用。");
122
+ const ref = this.optional(id, "session-ref.json", sessionRef);
123
+ requireThat(ref, "缺少原生会话引用;不导入旧记录,请重新 start。");
124
+ return ref.file;
125
+ }
126
+ /** The child fixes the native path before accepting input; never reset it to null. */
127
+ bindSession(id, file) {
128
+ const previous = this.session(id);
129
+ requireThat(previous === null || previous === file, "子会话文件引用不匹配。");
130
+ if (previous === null)
131
+ this.write(id, "session-ref.json", { file }, sessionRef);
132
+ }
133
+ /** Short controller edits are serialized; an abandoned lock fails closed. */
134
+ control(work) {
135
+ this.check();
136
+ mkdirSync(this.root, { recursive: true, mode: 0o700 });
137
+ const path = join(this.root, ".control");
138
+ try {
139
+ mkdirSync(path, { mode: 0o700 });
140
+ }
141
+ catch (error) {
142
+ if (error.code !== "EEXIST")
143
+ throw error;
144
+ throw Object.assign(new Error("子会话控制正在进行;稍后重试。崩溃遗留的 .control 需停止相关执行实例后手动移除。"), { code: "PBOT_CONTROL_BUSY" });
145
+ }
146
+ try {
147
+ return work();
148
+ }
149
+ finally {
150
+ rmSync(path, { recursive: true });
151
+ }
152
+ }
153
+ enqueue(id, text, delivery, files = [], requestId = randomUUID()) {
154
+ fileId(requestId);
155
+ const run = this.read(id, "launch.json", launch), previous = this.optional(id, "inbox/" + requestId + ".json", message);
156
+ if (previous) {
157
+ requireThat(previous.generation === run.generation && previous.message === text && previous.delivery === delivery && canonical(previous.files) === canonical(files), "请求 ID 已被不同输入使用。");
158
+ return previous;
159
+ }
160
+ run.sequence++;
161
+ this.write(id, "launch.json", run, launch);
162
+ const value = { requestId, generation: run.generation, sequence: run.sequence, message: text, delivery, files };
163
+ this.write(id, "inbox/" + value.requestId + ".json", value, message, true);
164
+ return value;
165
+ }
166
+ }
@@ -0,0 +1,11 @@
1
+ /** The root must already exist. Never follow aliases inside internal storage. */
2
+ export declare function internalPath(directory: string, relative: string): string;
3
+ export declare function syncDirectory(directory: string): void;
4
+ /** Validators return plain data and must reject incomplete contracts. */
5
+ export declare function readJsonFile<T>(directory: string, relative: string, validate: (value: unknown) => T): T;
6
+ /**
7
+ * A failure after publication does not mean that the old value remains.
8
+ * Callers must stop writes/sends on I/O failure, even if a subsequent read succeeds.
9
+ * createOnly publishes by link, so an existing destination is never overwritten.
10
+ */
11
+ export declare function writeJsonFile<T>(directory: string, relative: string, value: unknown, validate: (value: unknown) => T, createOnly?: boolean): boolean;
@@ -0,0 +1,101 @@
1
+ import { constants, closeSync, fstatSync, fsyncSync, linkSync, lstatSync, openSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { canonical } from "../common/json.js";
5
+ import { requireThat } from "../common/validation.js";
6
+ /** The root must already exist. Never follow aliases inside internal storage. */
7
+ export function internalPath(directory, relative) {
8
+ const parts = relative.split("/");
9
+ requireThat(parts.every(part => part && part !== "." && part !== ".." && !/[\\\x00-\x1f]/.test(part)), "内部状态路径无效。");
10
+ const info = lstatSync(directory);
11
+ requireThat(info.isDirectory() && !info.isSymbolicLink(), "内部状态必须使用普通目录。");
12
+ let path = realpathSync(directory);
13
+ for (const [index, part] of parts.entries()) {
14
+ path = join(path, part);
15
+ try {
16
+ const info = lstatSync(path);
17
+ requireThat(!info.isSymbolicLink() && (index === parts.length - 1 ? info.isFile() : info.isDirectory()), "内部状态不能使用符号链接或特殊文件。");
18
+ }
19
+ catch (error) {
20
+ if (error.code !== "ENOENT" || index !== parts.length - 1)
21
+ throw error;
22
+ }
23
+ }
24
+ return path;
25
+ }
26
+ export function syncDirectory(directory) {
27
+ const fd = openSync(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
28
+ try {
29
+ fsyncSync(fd);
30
+ }
31
+ finally {
32
+ closeSync(fd);
33
+ }
34
+ }
35
+ /** Validators return plain data and must reject incomplete contracts. */
36
+ export function readJsonFile(directory, relative, validate) {
37
+ const path = internalPath(directory, relative);
38
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
39
+ try {
40
+ requireThat(fstatSync(fd).isFile(), "内部状态必须是普通文件。");
41
+ let value;
42
+ try {
43
+ value = JSON.parse(readFileSync(fd, "utf8"));
44
+ }
45
+ catch (error) {
46
+ throw new Error("内部 JSON 文件不可读取:" + relative, { cause: error });
47
+ }
48
+ return validate(value);
49
+ }
50
+ finally {
51
+ closeSync(fd);
52
+ }
53
+ }
54
+ /**
55
+ * A failure after publication does not mean that the old value remains.
56
+ * Callers must stop writes/sends on I/O failure, even if a subsequent read succeeds.
57
+ * createOnly publishes by link, so an existing destination is never overwritten.
58
+ */
59
+ export function writeJsonFile(directory, relative, value, validate, createOnly = false) {
60
+ const validated = validate(value);
61
+ // Check before touching disk: JSON.stringify alone silently drops unsupported values.
62
+ const expected = canonical(validated);
63
+ const content = JSON.stringify(validated, null, 2) + "\n";
64
+ requireThat(canonical(JSON.parse(content)) === expected, "JSON 序列化不能改变状态值。");
65
+ const path = internalPath(directory, relative), parent = dirname(path);
66
+ const temporary = join(parent, `.tmp-${randomUUID()}`);
67
+ const fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
68
+ try {
69
+ try {
70
+ writeFileSync(fd, content, "utf8");
71
+ fsyncSync(fd);
72
+ }
73
+ finally {
74
+ closeSync(fd);
75
+ }
76
+ requireThat(internalPath(directory, relative) === path, "内部状态路径已变化。");
77
+ if (createOnly) {
78
+ try {
79
+ linkSync(temporary, path);
80
+ }
81
+ catch (error) {
82
+ if (error.code === "EEXIST")
83
+ return false;
84
+ throw error;
85
+ }
86
+ }
87
+ else
88
+ renameSync(temporary, path);
89
+ syncDirectory(parent);
90
+ return true;
91
+ }
92
+ finally {
93
+ try {
94
+ unlinkSync(temporary);
95
+ }
96
+ catch (error) {
97
+ if (error.code !== "ENOENT")
98
+ throw error;
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,2 @@
1
+ export declare function fileId(id: string): string;
2
+ export declare function privateDirectory(root: string, relative: string): void;
@@ -0,0 +1,26 @@
1
+ import { lstatSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { requireThat } from "../common/validation.js";
4
+ import { syncDirectory } from "./atomic-json.js";
5
+ export function fileId(id) {
6
+ requireThat(/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(id), "内部记录 ID 无效。");
7
+ return id;
8
+ }
9
+ export function privateDirectory(root, relative) {
10
+ const parts = relative.split("/");
11
+ let parent = root;
12
+ for (const part of parts) {
13
+ fileId(part);
14
+ const path = join(parent, part);
15
+ try {
16
+ mkdirSync(path, { mode: 0o700 });
17
+ syncDirectory(parent);
18
+ }
19
+ catch (error) {
20
+ if (error.code !== "EEXIST")
21
+ throw error;
22
+ }
23
+ requireThat(lstatSync(path).isDirectory() && !lstatSync(path).isSymbolicLink(), "内部状态目录无效。");
24
+ parent = path;
25
+ }
26
+ }
@@ -0,0 +1,10 @@
1
+ import type { HistoryHost, HistoryRecord } from "../model/history.js";
2
+ export declare class HistoryFiles {
3
+ readonly directory: string;
4
+ readonly host: HistoryHost;
5
+ constructor(directory: string, host?: HistoryHost);
6
+ list(workspace?: string): HistoryRecord[];
7
+ /** The callback is synchronous: no SDK reads or model work can hold this source's write lock. */
8
+ control<T>(operation: () => T): T;
9
+ save(value: HistoryRecord): void;
10
+ }
@@ -0,0 +1,63 @@
1
+ import { existsSync, mkdirSync, readdirSync, rmdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { object } from "../common/json.js";
4
+ import { requireThat } from "../common/validation.js";
5
+ import { readJsonFile, writeJsonFile } from "./atomic-json.js";
6
+ import { fileId } from "./file-access.js";
7
+ const strings = (value) => Array.isArray(value) && value.every(item => typeof item === "string");
8
+ function record(value) {
9
+ requireThat(object(value) && typeof value.id === "string" && typeof value.sessionId === "string" &&
10
+ typeof value.name === "string" && Array.isArray(value.nodes), "历史索引无效。");
11
+ for (const node of value.nodes)
12
+ requireThat(object(node) && typeof node.id === "string" &&
13
+ ["session", "exchange", "summary"].includes(String(node.kind)) && Number.isInteger(node.level) &&
14
+ strings(node.entryIds) && strings(node.parents) && strings(node.children) && typeof node.text === "string" &&
15
+ typeof node.from === "string" && typeof node.to === "string" && typeof node.ready === "boolean" && typeof node.completed === "boolean", "历史节点无效。");
16
+ const result = value, ids = new Set(result.nodes.map(node => node.id));
17
+ requireThat(ids.size === result.nodes.length && result.nodes.some(node => node.id === result.id && node.kind === "session") &&
18
+ result.nodes.every(node => [...node.parents, ...node.children].every(id => ids.has(id))), "历史节点关系无效。");
19
+ return result;
20
+ }
21
+ export class HistoryFiles {
22
+ directory;
23
+ host;
24
+ constructor(directory, host = "pi") {
25
+ this.directory = directory;
26
+ this.host = host;
27
+ }
28
+ list(workspace) {
29
+ if (!existsSync(this.directory))
30
+ return [];
31
+ return readdirSync(this.directory).filter(name => name.endsWith(".json"))
32
+ .map(name => readJsonFile(this.directory, name, value => {
33
+ const result = record(value);
34
+ requireThat(this.host !== "dsh" || result.id.startsWith("dsh-"), "dsh 历史根 ID 缺少来源前缀。");
35
+ return result;
36
+ }))
37
+ .filter(value => workspace === undefined || ("workspace" in value && value.workspace === workspace));
38
+ }
39
+ /** The callback is synchronous: no SDK reads or model work can hold this source's write lock. */
40
+ control(operation) {
41
+ mkdirSync(this.directory, { recursive: true, mode: 0o700 });
42
+ const lock = join(this.directory, ".write");
43
+ try {
44
+ mkdirSync(lock, { mode: 0o700 });
45
+ }
46
+ catch (error) {
47
+ if (error.code === "EEXIST")
48
+ throw Object.assign(new Error("历史来源写锁被占用:" + this.host), { code: "PBOT_HISTORY_BUSY" });
49
+ throw error;
50
+ }
51
+ try {
52
+ return operation();
53
+ }
54
+ finally {
55
+ rmdirSync(lock);
56
+ }
57
+ }
58
+ save(value) {
59
+ requireThat(this.host !== "dsh" || value.id.startsWith("dsh-"), "dsh 历史根 ID 缺少来源前缀。");
60
+ mkdirSync(this.directory, { recursive: true, mode: 0o700 });
61
+ writeJsonFile(this.directory, fileId(value.id) + ".json", value, record);
62
+ }
63
+ }
@@ -0,0 +1,16 @@
1
+ import type { TeamRecord, TeamReady, TeamLink } from "../model/team.js";
2
+ import { AgentFiles } from "./agent-files.js";
3
+ export declare function teamReady(v: unknown): TeamReady;
4
+ export declare function teamRecord(v: unknown): TeamRecord;
5
+ export declare class TeamFiles {
6
+ readonly agents: AgentFiles;
7
+ readonly root: string;
8
+ constructor(agents: AgentFiles);
9
+ check(): void;
10
+ read(id: string): TeamRecord;
11
+ list(): TeamRecord[];
12
+ save(value: TeamRecord, create?: boolean): void;
13
+ ready(id: string): TeamReady | undefined;
14
+ setReady(id: string, generation: string, ready: boolean): void;
15
+ links(records?: TeamRecord[]): Map<string, TeamLink>;
16
+ }
@@ -0,0 +1,111 @@
1
+ import { existsSync, lstatSync, mkdirSync, readdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { object } from "../common/json.js";
4
+ import { requireThat } from "../common/validation.js";
5
+ import { AgentFiles } from "./agent-files.js";
6
+ import { readJsonFile, writeJsonFile } from "./atomic-json.js";
7
+ import { fileId, privateDirectory } from "./file-access.js";
8
+ const text = (v) => typeof v === "string" && v.length > 0;
9
+ const integer = (v, min = 0) => Number.isSafeInteger(v) && v >= min;
10
+ export function teamReady(v) {
11
+ requireThat(object(v) && text(v.generation) && typeof v.ready === "boolean", "团队就绪标记无效。");
12
+ return v;
13
+ }
14
+ export function teamRecord(v) {
15
+ requireThat(object(v) && text(v.teamId) && text(v.release) && text(v.workspace) && text(v.parentSessionId) &&
16
+ (v.parentLeaf === null || text(v.parentLeaf)) && text(v.name) && text(v.brief) && typeof v.closed === "boolean" &&
17
+ Array.isArray(v.members) && v.members.length > 0 && v.members.length <= 50 && Array.isArray(v.runs) && Array.isArray(v.messages), "团队记录无效。");
18
+ fileId(v.teamId);
19
+ const members = new Set(), runs = new Set(), requests = new Map(), operations = new Set();
20
+ for (const m of v.members) {
21
+ requireThat(object(m) && text(m.agentId) && text(m.role) && !members.has(m.agentId), "团队成员无效或重复。");
22
+ fileId(m.agentId);
23
+ members.add(m.agentId);
24
+ }
25
+ let running = 0;
26
+ for (const r of v.runs) {
27
+ requireThat(object(r) && text(r.runId) && !runs.has(r.runId) && text(r.instruction) &&
28
+ ["roundRobin", "parallel"].includes(String(r.mode)) && integer(r.rounds, 1) && r.rounds <= 10 &&
29
+ integer(r.maxConcurrent, 1) && r.maxConcurrent <= members.size && (r.mode !== "roundRobin" || r.maxConcurrent === 1) &&
30
+ integer(r.deadlineAt, 1) && ["running", "paused", "finished"].includes(String(r.state)) &&
31
+ (r.reason === undefined || text(r.reason)) && object(r.generations) && Array.isArray(r.issued), "团队批次无效。");
32
+ fileId(r.runId);
33
+ runs.add(r.runId);
34
+ requireThat(Object.keys(r.generations).length === members.size && [...members].every(id => text(r.generations[id])), "团队代次无效。");
35
+ if (r.state === "running")
36
+ running++;
37
+ const turns = new Set();
38
+ for (const q of r.issued) {
39
+ requireThat(object(q) && text(q.requestId) && !requests.has(q.requestId) && text(q.agentId) && members.has(q.agentId) &&
40
+ q.generation === r.generations[q.agentId] && integer(q.round, 1) && q.round <= r.rounds &&
41
+ integer(q.throughSeq) && q.throughSeq <= v.messages.length && !turns.has(q.agentId + ":" + q.round), "团队请求关联无效。");
42
+ fileId(q.requestId);
43
+ requests.set(q.requestId, { agentId: q.agentId, runId: r.runId });
44
+ turns.add(q.agentId + ":" + q.round);
45
+ }
46
+ }
47
+ requireThat(running <= 1 && (!v.closed || running === 0), "团队运行状态冲突。");
48
+ for (const [i, m] of v.messages.entries()) {
49
+ requireThat(object(m) && m.seq === i + 1 && text(m.operationId) && !operations.has(m.operationId) && typeof m.text === "string" &&
50
+ object(m.author) && ["user", "coordinator", "member"].includes(String(m.author.kind)) && text(m.author.name) &&
51
+ (m.author.kind === "member" ? text(m.author.agentId) && members.has(m.author.agentId) : m.author.agentId === undefined) &&
52
+ (m.replyTo === undefined || (integer(m.replyTo, 1) && m.replyTo <= i)) &&
53
+ (m.runId === undefined || (text(m.runId) && runs.has(m.runId))), "团队消息无效。");
54
+ if (m.source !== undefined)
55
+ requireThat(object(m.source) && text(m.source.requestId) && text(m.source.cycleId) &&
56
+ requests.get(m.source.requestId)?.agentId === m.author.agentId && requests.get(m.source.requestId)?.runId === m.runId, "团队报告关联无效。");
57
+ operations.add(m.operationId);
58
+ }
59
+ return v;
60
+ }
61
+ export class TeamFiles {
62
+ agents;
63
+ root;
64
+ constructor(agents) {
65
+ this.agents = agents;
66
+ this.root = join(dirname(agents.root), "teams");
67
+ }
68
+ check() { this.agents.check(); }
69
+ read(id) {
70
+ const value = readJsonFile(this.root, fileId(id) + "/team.json", teamRecord);
71
+ requireThat(value.teamId === id, "团队路径与身份不匹配。");
72
+ return value;
73
+ }
74
+ list() {
75
+ if (!existsSync(this.root))
76
+ return [];
77
+ return readdirSync(this.root, { withFileTypes: true }).filter(e => !e.name.startsWith(".")).map(e => this.read(e.name));
78
+ }
79
+ save(value, create = false) {
80
+ this.check();
81
+ teamRecord(value);
82
+ let published;
83
+ try {
84
+ mkdirSync(this.root, { recursive: true, mode: 0o700 });
85
+ const directory = lstatSync(this.root);
86
+ requireThat(directory.isDirectory() && !directory.isSymbolicLink(), "团队状态必须使用普通目录。");
87
+ if (create)
88
+ privateDirectory(this.root, fileId(value.teamId));
89
+ published = writeJsonFile(this.root, fileId(value.teamId) + "/team.json", value, teamRecord, create);
90
+ }
91
+ catch (error) {
92
+ this.agents.fail(error);
93
+ throw error;
94
+ }
95
+ requireThat(published, "团队已存在。");
96
+ }
97
+ ready(id) { return this.agents.optional(id, "team-ready.json", teamReady); }
98
+ setReady(id, generation, ready) {
99
+ this.agents.write(id, "team-ready.json", { generation, ready }, teamReady);
100
+ }
101
+ links(records = this.list()) {
102
+ const result = new Map();
103
+ for (const team of records)
104
+ for (const run of team.runs)
105
+ for (const request of run.issued) {
106
+ requireThat(!result.has(request.requestId), "团队请求 ID 重复。");
107
+ result.set(request.requestId, { team, run, request });
108
+ }
109
+ return result;
110
+ }
111
+ }
@@ -0,0 +1,52 @@
1
+ import type { Static } from "typebox";
2
+ import type { AgentService } from "../../services/agents/agent-service.js";
3
+ import type { AgentOptions, AgentOwner } from "../../model/agent.js";
4
+ import type { agentParametersSchema } from "./contract.js";
5
+ export type AgentInput = Omit<Static<typeof agentParametersSchema>, "thinking"> & {
6
+ thinking?: string;
7
+ };
8
+ export declare function executeAgent(service: AgentService, input: AgentInput, owner: AgentOwner, defaults?: AgentOptions): Promise<{
9
+ isError?: boolean;
10
+ content: {
11
+ type: "text";
12
+ text: string;
13
+ }[];
14
+ details: import("../../common/json.js").Json;
15
+ structuredContent: import("../../common/json.js").Json;
16
+ } | {
17
+ details: {
18
+ agentReadKeys?: string[];
19
+ error?: string;
20
+ requestId?: string;
21
+ cycle?: {
22
+ error?: string;
23
+ finishedAt?: number;
24
+ state: "running" | "completed" | "failed" | "cancelled" | "interrupted";
25
+ startedAt: number;
26
+ cycleId: string;
27
+ requestIds: string[];
28
+ source: "parent" | "interactive" | "mixed";
29
+ };
30
+ report?: string;
31
+ messageStatus?: "queued";
32
+ cycles?: {
33
+ state: string;
34
+ cycleId: string;
35
+ }[];
36
+ totalLength?: number;
37
+ nextOffset?: number | null;
38
+ state: "starting" | "busy" | "idle" | "stopping" | "stopped" | "error";
39
+ agentId: string;
40
+ cwd: string;
41
+ createdAt: number;
42
+ name: string;
43
+ activity: string;
44
+ owned: boolean;
45
+ };
46
+ isError?: boolean;
47
+ content: {
48
+ type: "text";
49
+ text: string;
50
+ }[];
51
+ structuredContent: import("../../common/json.js").Json;
52
+ }>;
@@ -0,0 +1,49 @@
1
+ import { markdownResult } from "../result.js";
2
+ function agentOutput(view) {
3
+ const c = view.cycle;
4
+ return {
5
+ agentId: view.agentId, name: view.name, cwd: view.cwd, createdAt: view.createdAt,
6
+ owned: view.owned, state: view.state, activity: view.activity,
7
+ ...(c ? { cycle: { cycleId: c.cycleId, startedAt: c.startedAt, state: c.state, requestIds: c.requestIds, source: c.source,
8
+ ...(c.finishedAt === undefined ? {} : { finishedAt: c.finishedAt }), ...(c.error === undefined ? {} : { error: c.error }) } } : {}),
9
+ ...(view.error === undefined ? {} : { error: view.error }),
10
+ ...(view.requestId === undefined ? {} : { requestId: view.requestId }),
11
+ ...(view.messageStatus === undefined ? {} : { messageStatus: view.messageStatus }),
12
+ ...(view.cycles === undefined ? {} : { cycles: view.cycles }),
13
+ ...(view.report === undefined ? {} : { report: view.report }),
14
+ ...(view.totalLength === undefined ? {} : { totalLength: view.totalLength }),
15
+ ...(view.nextOffset === undefined ? {} : { nextOffset: view.nextOffset }),
16
+ };
17
+ }
18
+ export async function executeAgent(service, input, owner, defaults = {}) {
19
+ let value;
20
+ switch (input.action) {
21
+ case "start": {
22
+ const { action: _action, ...supplied } = input;
23
+ value = await service.start({ ...defaults, ...supplied }, owner);
24
+ break;
25
+ }
26
+ case "list": return markdownResult({ agents: service.list(owner).map(agentOutput) }, "子会话列表");
27
+ case "send":
28
+ value = service.send(input.agentId, input.message, input.delivery ?? "steer", owner);
29
+ break;
30
+ case "rename":
31
+ value = service.rename(input.agentId, input.name, owner);
32
+ break;
33
+ case "resume":
34
+ value = await service.resume(input.agentId, input.message, owner);
35
+ break;
36
+ case "stop":
37
+ value = service.stop(input.agentId, owner);
38
+ break;
39
+ case "status":
40
+ value = service.status(input.agentId, owner);
41
+ break;
42
+ case "read": {
43
+ const page = await service.read(input.agentId, owner, input.cycleId, input.offset, input.limit);
44
+ const data = agentOutput(page), output = markdownResult(data, "子会话报告");
45
+ return { ...output, details: { ...data, ...(page.cycle && page.cycle.state !== "running" ? { agentReadKeys: [page.agentId + ":" + page.cycle.cycleId] } : {}) } };
46
+ }
47
+ }
48
+ return markdownResult(agentOutput(value), "子会话状态");
49
+ }