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,76 @@
1
+ import { realpath } from "node:fs/promises";
2
+ import type { Context } from "@deepseek-ai/cordis";
3
+ import type { SessionEvent, SessionHeader } from "@deepseek-ai/dsh-session";
4
+ import { SessionId } from "@deepseek-ai/dsh-session";
5
+ import type {} from "@deepseek-ai/dsh-session-query";
6
+ import type { HistoryExchange, HistoryLocation, HistorySession, HistorySource } from "../../model/history.js";
7
+ import { requireThat } from "../../common/validation.js";
8
+ import { finalDshAnswer } from "./messages.js";
9
+
10
+ type Question = Extract<SessionEvent, { type: "user/message" }>;
11
+ type Answer = Extract<SessionEvent, { type: "assistant/message" }>;
12
+ const text = (content: Question["data"]["content"]) => content.flatMap(block => block.type === "text" ? [block.text] :
13
+ block.type === "image" ? ["[图片 " + (block.attachment.name ?? block.attachment.attachmentId) + "]"] :
14
+ block.type === "file" ? ["[附件 " + (block.attachment.name ?? block.attachment.attachmentId) + "]"] : []).join("\n");
15
+
16
+ /** Archive questions and the final/partial answer; raw tool records stay in the native log. */
17
+ export function projectDshHistory(header: SessionHeader, events: readonly SessionEvent[], name = ""): HistorySession {
18
+ const exchanges: HistoryExchange[] = [];
19
+ let users: Question[] = [], answer: Answer | undefined;
20
+ const finish = (completed = false) => {
21
+ if (users.length) {
22
+ const entries = [...users, ...(answer ? [answer] : [])];
23
+ const body = answer ? answer.data.message.content.filter(block => block.type === "text").map(block => block.text).join("\n") : "";
24
+ exchanges.push({ entryIds: entries.map(event => "dsh:" + event.seq), from: new Date(users[0]!.time).toISOString(),
25
+ to: new Date(entries.at(-1)!.time).toISOString(),
26
+ completed: completed && Boolean(answer && finalDshAnswer(answer).trim()),
27
+ text: users.map(event => (event.data.source.kind === "user" ? "用户:\n" : "协调者要求:\n") + text(event.data.content)).join("\n\n") +
28
+ (body ? "\n\n助手:\n" + body : "") });
29
+ }
30
+ users = []; answer = undefined;
31
+ };
32
+ for (const event of events) {
33
+ if (event.type === "turn/start") finish();
34
+ else if (event.type === "user/message" && ["user", "pbot-input"].includes(event.data.source.kind)) users.push(event);
35
+ else if (event.type === "assistant/message" && users.length) answer = event;
36
+ else if (event.type === "turn/end") finish(event.data.reason.kind === "completed");
37
+ }
38
+ finish();
39
+ return { sessionId: header.id, name: name || exchanges[0]?.text.replace(/\s+/g, " ").slice(0, 80) || "",
40
+ branches: exchanges.length ? [exchanges] : [] };
41
+ }
42
+
43
+ export class DshHistory implements HistorySource {
44
+ readonly host = "dsh";
45
+ constructor(private readonly ctx: Context, private readonly root: string, private readonly relatedSessions: () => string[] = () => []) {}
46
+ async scan(location: HistoryLocation, signal?: AbortSignal) {
47
+ requireThat(location.host === "dsh", "dsh 历史只读取 dsh 原生会话。");
48
+ signal?.throwIfAborted();
49
+ const records = await this.ctx.sessionQuery.listSessions(signal), selected = new Set<string>(), warnings: string[] = [];
50
+ try { for (const id of this.relatedSessions()) selected.add(id); } catch (error) { warnings.push("专家引用:" + String(error)); }
51
+ if (location.sessionId) selected.add(location.sessionId);
52
+ for (const record of records) {
53
+ signal?.throwIfAborted();
54
+ if ((record.header.origin !== "subagent" || !record.header.parentSession) && record.header.cwd &&
55
+ await realpath(record.header.cwd).catch(() => record.header.cwd) === this.root) selected.add(record.header.id);
56
+ }
57
+ // Native subagents may use a different cwd while still belonging to a workspace's parent.
58
+ for (let changed = true; changed;) {
59
+ changed = false;
60
+ for (const record of records) if (record.header.origin === "subagent" && record.header.parentSession && selected.has(record.header.parentSession) && !selected.has(record.header.id)) {
61
+ selected.add(record.header.id); changed = true;
62
+ }
63
+ }
64
+ const sessions: HistorySession[] = [];
65
+ for (const id of selected) {
66
+ signal?.throwIfAborted();
67
+ try {
68
+ using observation = await this.ctx.sessionQuery.observeSession(SessionId(id), { ...(signal ? { signal } : {}), projectionMode: "none" });
69
+ const title = await this.ctx.sessionQuery.readTitle(SessionId(id), signal);
70
+ const session = projectDshHistory(observation.header, observation.events, title?.title);
71
+ if (session.branches.length) sessions.push(session);
72
+ } catch (error) { signal?.throwIfAborted(); warnings.push(id + ": " + String(error)); }
73
+ }
74
+ return { sessions, warnings };
75
+ }
76
+ }
@@ -0,0 +1,66 @@
1
+ import { basename, extname } from "node:path";
2
+ import type { Context } from "@deepseek-ai/cordis";
3
+ import { createUserMessage, type FileBlock, type ImageBlock } from "@deepseek-ai/dsh-llm";
4
+ import type { AttachmentAdmissionPart, ImageMediaType } from "@deepseek-ai/dsh-attachment";
5
+ import type { FsTarget } from "@deepseek-ai/dsh-fs";
6
+ import type { AgentDescriptor, AgentMessage } from "../../model/agent.js";
7
+ import { requireThat } from "../../common/validation.js";
8
+ import "./messages.js";
9
+
10
+ const imageTypes: Record<string, ImageMediaType> = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp", ".gif": "image/gif" };
11
+
12
+ /** Native file attachments remain verbatim; image admission uses the host's policy. */
13
+ async function contentFor(ctx: Context, cwd: string, message: string, files: readonly string[], signal: AbortSignal) {
14
+ const content: AttachmentAdmissionPart[] = [];
15
+ let imageCount = 0, imageBytes = 0;
16
+ for (const path of files) {
17
+ signal.throwIfAborted();
18
+ const target = await ctx.fs.resolve(path, { cwd, signal });
19
+ const before = await ctx.fs.stat(target, signal);
20
+ requireThat(before?.type === "file", "附件必须是普通文件:" + path);
21
+ const name = basename(path), mediaType = imageTypes[extname(path).toLowerCase()];
22
+ if (mediaType) {
23
+ const limits = ctx.attachments.imageLimits;
24
+ requireThat(++imageCount <= limits.maxImagesPerMessage &&
25
+ (before.size === undefined || imageBytes + before.size <= limits.maxMessageImageBytes), "图片附件超过宿主消息准入上限。");
26
+ const data = await ctx.fs.readBytes(target, signal, limits.maxImageBytes);
27
+ imageBytes += data.byteLength;
28
+ requireThat(imageBytes <= limits.maxMessageImageBytes, "图片附件超过宿主消息准入上限。");
29
+ content.push({ type: "image", mediaType, name, data: Buffer.from(data).toString("base64") });
30
+ } else {
31
+ async function* bytes(file: FsTarget) {
32
+ let offset = 0;
33
+ for (;;) {
34
+ signal.throwIfAborted();
35
+ const chunk = await ctx.fs.readByteRange(file, { offset, length: 256 * 1024 }, signal);
36
+ if (!chunk.length) break;
37
+ yield chunk; offset += chunk.length;
38
+ }
39
+ requireThat((await ctx.fs.stat(file, signal))?.version === before?.version, "读取期间附件已变化:" + path);
40
+ }
41
+ content.push({ type: "file", attachment: await ctx.attachments.saveFileStream({ data: bytes(target), name, signal }) });
42
+ }
43
+ requireThat((await ctx.fs.stat(target, signal))?.version === before.version, "读取期间附件已变化:" + path);
44
+ content.push({ type: "text", text: "附件原始路径:" + JSON.stringify(path) + "。原生附件保留本次文件内容;需要查看时使用宿主提供的附件路径。" });
45
+ }
46
+ content.push({ type: "text", text: message || "请处理所附文件。" });
47
+ const admitted = await ctx.attachments.admitPromptContent(content);
48
+ signal.throwIfAborted(); return admitted;
49
+ }
50
+
51
+ export async function dshInput(ctx: Context, d: AgentDescriptor, request: AgentMessage, signal: AbortSignal) {
52
+ const content = await contentFor(ctx, d.cwd, request.message, request.files, signal);
53
+ return createUserMessage({ content, source: { kind: "pbot-input", form: "relay",
54
+ requestId: request.requestId, generation: request.generation, parentSessionId: d.parentSessionId } });
55
+ }
56
+
57
+ /** Command attachments have already passed native upload admission; the caller never supplies arbitrary references. */
58
+ export async function dshHumanInput(ctx: Context, cwd: string, text: string, files: readonly string[],
59
+ attachments: readonly (ImageBlock | FileBlock)[], signal: AbortSignal) {
60
+ requireThat(files.length + attachments.length <= 32, "一次最多附加 32 个文件。");
61
+ requireThat(!files.length || !attachments.length, "一次对话请选择服务器文件或输入框附件,不能同时使用。");
62
+ signal.throwIfAborted();
63
+ const content = attachments.length ? [...attachments, { type: "text" as const, text: text || "请处理所附文件。" }] :
64
+ await contentFor(ctx, cwd, text, files, signal);
65
+ signal.throwIfAborted(); return createUserMessage({ content, source: { kind: "user" } });
66
+ }
@@ -0,0 +1,22 @@
1
+ import { lastAssistantStreamChunk, type UserMessage } from "@deepseek-ai/dsh-llm";
2
+ import type { SessionEvent } from "@deepseek-ai/dsh-session";
3
+
4
+ /** Producer metadata travels with native messages and never grants task authorization. */
5
+ declare module "@deepseek-ai/dsh-llm" {
6
+ interface MessageSourceMap {
7
+ "pbot-input": { kind: "pbot-input"; form: "relay"; requestId: string; generation: string; parentSessionId: string };
8
+ "pbot-team-context": { kind: "pbot-team-context"; form: "relay"; teamId: string; seqs: number[] };
9
+ "pbot-results": { kind: "pbot-results"; form: "notice"; summary: string; keys: string[] };
10
+ }
11
+ }
12
+
13
+ export function finalDshAnswer(event: Extract<SessionEvent, { type: "assistant/message" }>) {
14
+ const { message, interrupted, stream } = event.data;
15
+ if (interrupted || message.content.some(block => block.type === "tool-call") || lastAssistantStreamChunk(stream, "finish")?.reason.kind !== "stop") return "";
16
+ return message.content.filter(block => block.type === "text").map(block => block.text).join("\n");
17
+ }
18
+
19
+ export function requestSource(message: UserMessage, generation: string) {
20
+ const source = message.source;
21
+ return source.kind === "pbot-input" && source.generation === generation ? source : undefined;
22
+ }
@@ -0,0 +1,46 @@
1
+ import type { SessionEvent } from "@deepseek-ai/dsh-session";
2
+ import type { UserMessage } from "@deepseek-ai/dsh-llm";
3
+ import type {} from "@deepseek-ai/dsh-tool-executor-ptc";
4
+ import { object } from "../../common/json.js";
5
+ import "./messages.js";
6
+
7
+ function keys(value: unknown, field: string) {
8
+ if (!object(value)) return [];
9
+ const items = value[field];
10
+ return Array.isArray(items) && items.every(item => typeof item === "string") ? items as string[] : [];
11
+ }
12
+
13
+ /** Only committed native context or successful model tool results claim notices.
14
+ * PTC inner reads require their enclosing transport to have committed success. */
15
+ export function deliveredDshNotices(events: readonly SessionEvent[]) {
16
+ const delivered = new Set<string>();
17
+ const reads = new Set<string>(), nested = new Map<string, string[]>();
18
+ for (const event of events) {
19
+ if (event.type === "turn/start" || event.type === "step/start" || event.type === "turn/end") { reads.clear(); nested.clear(); }
20
+ if (event.type === "user/message" && event.data.source.kind === "pbot-results") {
21
+ for (const key of keys(event.data.source, "keys")) delivered.add(key);
22
+ }
23
+ if (event.type === "tool/call" && event.data.name === "pbot_agent") {
24
+ try { const args: unknown = JSON.parse(event.data.arguments); if (object(args) && args.action === "read") reads.add(event.data.callId); } catch { /* An invalid call cannot claim a report. */ }
25
+ }
26
+ if (event.type === "tool/ptc-dispatch" && event.data.name === "pbot_agent" && !event.data.isError &&
27
+ object(event.data.arguments) && event.data.arguments.action === "read") {
28
+ const values = nested.get(event.data.rootCallId) ?? [];
29
+ values.push(...keys(event.data.meta, "pbotAgentReadKeys")); nested.set(event.data.rootCallId, values);
30
+ }
31
+ if (event.type === "tool/result") {
32
+ const message = event.data.message;
33
+ if (!message.isError) {
34
+ if (reads.has(message.toolCallId)) for (const key of keys(event.data.meta, "pbotAgentReadKeys")) delivered.add(key);
35
+ for (const key of nested.get(message.toolCallId) ?? []) delivered.add(key);
36
+ }
37
+ reads.delete(message.toolCallId); nested.delete(message.toolCallId);
38
+ }
39
+ }
40
+ return delivered;
41
+ }
42
+
43
+ /** Pending inbox entries prevent duplicate submission, but are not delivery receipts. */
44
+ export function pendingDshNotices(messages: readonly UserMessage[]) {
45
+ return messages.flatMap(message => message.source.kind === "pbot-results" ? keys(message.source, "keys") : []);
46
+ }
@@ -0,0 +1,44 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import type { Context } from "@deepseek-ai/cordis";
4
+ import type { Agent } from "@deepseek-ai/dsh-agent";
5
+ import { FileSystemSkillProvider } from "@deepseek-ai/dsh-skill-filesystem";
6
+ import type {} from "@deepseek-ai/dsh-working-directory";
7
+ import type {} from "@deepseek-ai/dsh-tools";
8
+ import { agentText } from "../agent-resources.js";
9
+ import { resourceRoot } from "../../common/paths.js";
10
+ import { requireThat } from "../../common/validation.js";
11
+ import type { AgentOptions } from "../../model/agent.js";
12
+
13
+ export async function dshResources(ctx: Context, agent: Agent, root: string,
14
+ role: "coordinator" | "general-purpose", python: () => { state: string; directory?: string; error?: string }, check: () => void) {
15
+ let text = await agentText(role, root);
16
+ const workspace = await readFile(join(resourceRoot, "prompts/workspace.md"), "utf8");
17
+ check();
18
+ ctx.systemPrompt.section({ name: "pbot:role", order: 1, text: () => text, interpolate: false });
19
+ ctx.systemPrompt.section({ name: "pbot:workspace", order: 2, text: workspace, interpolate: false });
20
+ ctx.systemPrompt.context({ name: "pbot:environment", order: 300, required: true, interpolate: false,
21
+ text: () => JSON.stringify({ host: "dsh", workspaceRoot: root, cwd: ctx.workingDirectory.get(agent.session), resourceRoot, python: python() }) });
22
+ ctx.skills.registerProvider(control => new FileSystemSkillProvider(ctx, control, {
23
+ providerName: "pbot", includeDefaultRoots: false, customSkillDirs: [join(root, ".agents/skills")],
24
+ bundledSkillDir: join(resourceRoot, "skills"), watch: false,
25
+ }));
26
+ ctx.on("agent/pre-step", async (_payload, next) => {
27
+ check(); text = await agentText(role, root); check();
28
+ return next();
29
+ });
30
+ }
31
+
32
+ /** Map the expert's native loadout; this is not isolation from Bash, CLI, or MCP. */
33
+ export function dshExpertTools(ctx: Context, agent: Agent, options: AgentOptions) {
34
+ const names = new Set(ctx.tools.definitions(agent).map(tool => tool.name));
35
+ const forbidden = new Set(["pbot_agent", "pbot_team", "pbot_history", "subagent", "list_subagent_models", "list_agents", "send_message", "interrupt_agent"]);
36
+ if (options.noSkills) forbidden.add("skill");
37
+ if (options.tools) {
38
+ for (const name of options.tools) requireThat((names.has(name) || name === "pbot_team_message") && !forbidden.has(name), "专家工具不可用:" + name);
39
+ ctx.tools.restrict({ allow: options.tools.filter(name => name !== "pbot_team_message") });
40
+ }
41
+ const deny = [...forbidden].filter(name => names.has(name));
42
+ if (deny.length) ctx.tools.restrict({ deny });
43
+ return !options.tools || options.tools.includes("pbot_team_message");
44
+ }
@@ -0,0 +1,26 @@
1
+ import type { TSchema } from "typebox";
2
+ import type { JsonSchemaNode } from "@deepseek-ai/dsh-tools";
3
+ import { requireThat } from "../../common/validation.js";
4
+
5
+ /** Remove TypeBox's non-JSON metadata before passing a schema to the native host. */
6
+ export function dshParameters(value: TSchema): Record<string, unknown> {
7
+ return JSON.parse(JSON.stringify(value)) as Record<string, unknown>;
8
+ }
9
+
10
+ /** Collaboration result unions are disjoint. rc.1 enforces structural oneOf schemas;
11
+ * the producing tool also validates the complete contract, including these bounds. */
12
+ export function dshOutputSchema(value: TSchema): JsonSchemaNode {
13
+ const bounds = new Set(["minLength", "maxLength", "pattern", "minimum", "maximum", "minItems", "maxItems"]);
14
+ const supported = new Set(["type", "required", "additionalProperties", "enum", "const", "description", "title", "default", "examples"]);
15
+ function convert(node: Record<string, unknown>): Record<string, unknown> {
16
+ const result: Record<string, unknown> = {};
17
+ for (const [key, item] of Object.entries(node)) {
18
+ if (key === "anyOf") result.oneOf = (item as Record<string, unknown>[]).map(convert);
19
+ else if (key === "properties") result.properties = Object.fromEntries(Object.entries(item as Record<string, Record<string, unknown>>).map(([name, child]) => [name, convert(child)]));
20
+ else if (key === "items") result.items = convert(item as Record<string, unknown>);
21
+ else if (!bounds.has(key)) { requireThat(supported.has(key), "dsh 输出 schema 不支持字段:" + key); result[key] = item; }
22
+ }
23
+ return result;
24
+ }
25
+ return convert(dshParameters(value)) as JsonSchemaNode;
26
+ }
@@ -0,0 +1,67 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import type { Agent, AgentHandle, AgentSetup } from "@deepseek-ai/dsh-agent";
3
+ import { SessionId } from "@deepseek-ai/dsh-session";
4
+ import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
5
+ import type { SessionHeader } from "@deepseek-ai/dsh-session";
6
+ import type {} from "@deepseek-ai/dsh-session-persistence";
7
+ import type { AgentDescriptor } from "../../model/agent.js";
8
+ import { requireThat } from "../../common/validation.js";
9
+
10
+ function validateHeader(d: AgentDescriptor, header: SessionHeader) {
11
+ requireThat(header.id === d.agentId && header.cwd === d.cwd && header.parentSession === d.parentSessionId &&
12
+ header.origin === "subagent" && !header.isSeeded, "dsh 专家会话身份、父会话或工作目录不匹配。");
13
+ }
14
+
15
+ /** Read access must neither claim a writer nor activate an Agent. */
16
+ export async function readDshAgentSession(ctx: Context, d: AgentDescriptor) {
17
+ const reader = await ctx.sessionPersistence.open(SessionId(d.agentId), "read");
18
+ try {
19
+ validateHeader(d, reader.header);
20
+ return { header: reader.header, events: (await reader.read()).events };
21
+ } finally {
22
+ await reader.close();
23
+ }
24
+ }
25
+
26
+ /** An empty Agent is not recoverable until this native barrier has materialized it. */
27
+ export async function flushDshAgent(agent: Agent) {
28
+ requireThat(await agent.ctx.sessions.flush(agent.session), "dsh 会话没有持久化屏障。");
29
+ }
30
+
31
+ export async function openDshAgent(ctx: Context, d: AgentDescriptor, parent: Agent, resume: boolean,
32
+ setup: AgentSetup, signal: AbortSignal): Promise<AgentHandle> {
33
+ signal.throwIfAborted();
34
+ requireThat(parent.id === d.parentSessionId && ctx.agents.get(parent.id) === parent, "dsh 父会话已失效。");
35
+ if (resume) await readDshAgentSession(ctx, d);
36
+ await flushDshAgent(parent);
37
+ signal.throwIfAborted();
38
+ const compose: AgentSetup = async (agentCtx, agent) => {
39
+ validateHeader(d, agent.session.header);
40
+ if (resume) agent.inbox.clear();
41
+ const commit = await setup(agentCtx, agent);
42
+ await flushDshAgent(agent);
43
+ return commit;
44
+ };
45
+ const agentOptions = { ...(d.options.provider ? { provider: d.options.provider } : {}),
46
+ ...(d.options.model ? { model: d.options.model } : {}),
47
+ ...(d.options.thinking ? { reasoningEffort: ReasoningEffortId(d.options.thinking) } : {}) };
48
+ return resume
49
+ ? ctx.agents.resume({ resumeSessionId: SessionId(d.agentId), parentAgent: parent, agentOptions, setup: compose, signal })
50
+ : ctx.agents.create({ sessionId: SessionId(d.agentId), parentAgent: parent,
51
+ meta: { cwd: d.cwd, parentSession: parent.id, origin: "subagent", delegationDepth: 1 },
52
+ agentOptions, setup: compose, signal });
53
+ }
54
+
55
+ /** A report refers to one durable turn boundary, never the latest answer. */
56
+ export async function readDshReport(ctx: Context, d: AgentDescriptor, reference: string) {
57
+ const match = /^dsh:(0|[1-9]\d*)$/.exec(reference);
58
+ requireThat(match, "dsh 报告位置无效。");
59
+ const end = Number(match[1]);
60
+ requireThat(Number.isSafeInteger(end), "dsh 报告位置无效。");
61
+ const { events } = await readDshAgentSession(ctx, d);
62
+ const boundary = events[end];
63
+ requireThat(boundary?.type === "turn/end", "dsh 报告结束位置不存在。");
64
+ const last = events.slice(0, end).findLast(event => event.type === "assistant/message" && event.data.turn === boundary.data.turn);
65
+ return last?.type === "assistant/message"
66
+ ? last.data.message.content.filter(block => block.type === "text").map(block => block.text).join("\n") : "";
67
+ }
@@ -0,0 +1,20 @@
1
+ import type { HistorySummarizer } from "../model/history.js";
2
+ import { requireThat } from "../common/validation.js";
3
+
4
+ export function reduceHistorySummary(budget: number, ask: HistorySummarizer): HistorySummarizer {
5
+ return async (text, signal) => {
6
+ while (text.length > budget) {
7
+ const parts: string[] = [];
8
+ for (let offset = 0; offset < text.length;) {
9
+ signal.throwIfAborted();
10
+ let end = Math.min(text.length, offset + budget);
11
+ if (end < text.length && /[\uD800-\uDBFF]/.test(text[end - 1]!)) end--;
12
+ parts.push(await ask(text.slice(offset, end), signal)); offset = end;
13
+ }
14
+ const smaller = parts.join("\n\n");
15
+ requireThat(smaller.length < text.length, "历史摘要未缩短,稍后重试。");
16
+ text = smaller;
17
+ }
18
+ signal.throwIfAborted(); return ask(text, signal);
19
+ };
20
+ }
@@ -0,0 +1,107 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+ import type { InstalledPackage } from "../../services/installation/setup-service.js";
6
+ import { npmName } from "../../common/package-source.js";
7
+
8
+ type PiSdk = typeof import("@earendil-works/pi-coding-agent");
9
+ async function sdkAt(root: string): Promise<PiSdk> {
10
+ const manifest = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
11
+ if (manifest.name !== "@earendil-works/pi-coding-agent") throw new Error("不是支持的 Pi 安装。");
12
+ return import(pathToFileURL(join(root, "dist/index.js")).href) as Promise<PiSdk>;
13
+ }
14
+ const sourceOf = (entry: unknown): string => {
15
+ if (typeof entry === "string") return entry;
16
+ if (entry && typeof entry === "object" && "source" in entry && typeof entry.source === "string") return entry.source;
17
+ throw new Error("无效的 Pi packages 声明。");
18
+ };
19
+
20
+ export async function inspectPackages(root: string, workspace: string): Promise<InstalledPackage[]> {
21
+ const sdk = await sdkAt(root), agentDir = sdk.getAgentDir();
22
+ // Inspect project declarations as data only. This does not load extensions, execute
23
+ // project npmCommand, grant project trust, or persist migrated settings.
24
+ const settings = sdk.SettingsManager.fromStorage({ withLock(scope, read) {
25
+ const file = scope === "global" ? join(agentDir, "settings.json") : join(workspace, sdk.CONFIG_DIR_NAME, "settings.json");
26
+ let content: string | undefined;
27
+ try { content = readFileSync(file, "utf8"); }
28
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; }
29
+ if (content !== undefined) {
30
+ const value: unknown = JSON.parse(content);
31
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`无效的 Pi 配置:${file}`);
32
+ const packages = (value as { packages?: unknown }).packages;
33
+ if (packages !== undefined) {
34
+ if (!Array.isArray(packages)) throw new Error(`packages 必须是数组:${file}`);
35
+ packages.forEach(sourceOf);
36
+ }
37
+ }
38
+ if (read(content) !== undefined) throw new Error("安装检查不得修改 Pi 配置。");
39
+ } }, { projectTrusted: true });
40
+ const errors = settings.drainErrors();
41
+ if (errors.length) throw new Error(errors.map(item => `${item.scope}: ${item.error.message}`).join("\n"));
42
+ const manager = new sdk.DefaultPackageManager({ cwd: workspace, agentDir, settingsManager: settings });
43
+ const result: InstalledPackage[] = [];
44
+ for (const pkg of manager.listConfiguredPackages()) {
45
+ const entries = (pkg.scope === "project" ? settings.getProjectSettings() : settings.getGlobalSettings()).packages ?? [];
46
+ const entry = entries.find(item => sourceOf(item) === pkg.source);
47
+ let name = npmName(pkg.source), version: string | undefined;
48
+ if (pkg.installedPath) {
49
+ let file = pkg.installedPath;
50
+ if ((await stat(file)).isDirectory()) file = join(file, "package.json");
51
+ else file = ""; // Single-file extensions have no npm identity.
52
+ if (file) {
53
+ try {
54
+ const manifest = JSON.parse(await readFile(file, "utf8")) as { name?: string; version?: string };
55
+ if (name && manifest.name !== name) throw new Error(`安装包身份不匹配:${pkg.source}`);
56
+ name = typeof manifest.name === "string" ? manifest.name : name;
57
+ version = typeof manifest.version === "string" ? manifest.version : undefined;
58
+ } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; }
59
+ }
60
+ }
61
+ result.push({ source: pkg.source, scope: pkg.scope, filtered: pkg.filtered, entry,
62
+ ...(pkg.installedPath ? { installedPath: pkg.installedPath } : {}), ...(name ? { name } : {}), ...(version ? { version } : {}) });
63
+ }
64
+ return result;
65
+ }
66
+
67
+ export async function replaceProjectSource(root: string, workspace: string, old: InstalledPackage, target: string): Promise<void> {
68
+ const fromProject = old.scope === "project" && !npmName(old.source);
69
+ const fromUser = old.scope === "user" && !!npmName(old.source);
70
+ if (!fromProject && !fromUser) throw new Error("此操作只替换工作区自定义来源,或继承全局 npm 资源过滤。");
71
+ const sdk = await sdkAt(root);
72
+ const settings = sdk.SettingsManager.create(workspace, sdk.getAgentDir(), { projectTrusted: true });
73
+ const errors = settings.drainErrors();
74
+ if (errors.length) throw new Error(errors.map(item => item.error.message).join("\n"));
75
+ const packages = settings.getProjectSettings().packages ?? [];
76
+ const originals = fromProject ? packages : settings.getGlobalSettings().packages ?? [];
77
+ const previous = originals.find(entry => sourceOf(entry) === old.source);
78
+ if (previous === undefined || JSON.stringify(previous) !== JSON.stringify(old.entry)) throw new Error("插件配置已发生变化,请重新运行 setup。");
79
+ if (!packages.some(entry => sourceOf(entry) === target)) throw new Error("目标包尚未安装,保留原声明。");
80
+ const manager = new sdk.DefaultPackageManager({ cwd: workspace, agentDir: sdk.getAgentDir(), settingsManager: settings });
81
+ const installed = manager.getInstalledPath(target, "project");
82
+ if (!installed || !npmName(target)) throw new Error("目标 npm 包尚未安装,保留原声明。");
83
+ const manifest = JSON.parse(await readFile(join(installed, "package.json"), "utf8"));
84
+ if (manifest.name !== npmName(target)) throw new Error("目标安装包身份不符,保留原声明。");
85
+ // Change only declarations; do not call remove(), which deletes managed git caches.
86
+ const replacement = typeof previous === "string" ? target : { ...previous, source: target };
87
+ const next = fromProject
88
+ ? packages.filter(entry => sourceOf(entry) !== target).map(entry => sourceOf(entry) === old.source ? replacement : entry)
89
+ : packages.map(entry => sourceOf(entry) === target ? replacement : entry);
90
+ settings.setProjectPackages(next);
91
+ await settings.flush();
92
+ const failures = settings.drainErrors();
93
+ if (failures.length) throw new Error(failures.map(item => item.error.message).join("\n"));
94
+ }
95
+
96
+ async function main() {
97
+ const [action, root, workspace, input] = process.argv.slice(2);
98
+ if (!root || !workspace) throw new Error("缺少 Pi 路径或工作区。");
99
+ if (action === "inspect") console.log(JSON.stringify(await inspectPackages(root, workspace)));
100
+ else if (action === "replace" && input) {
101
+ const { old, target } = JSON.parse(input) as { old: InstalledPackage; target: string };
102
+ await replaceProjectSource(root, workspace, old, target);
103
+ } else throw new Error("无效的安装检查操作。");
104
+ }
105
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
106
+ await main().catch(error => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; });
107
+ }