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,41 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import type { Agent } from "@deepseek-ai/dsh-agent";
3
+ import { dshParameters, dshOutputSchema } from "../../integrations/dsh/schema.js";
4
+ import type { AgentService } from "../../services/agents/agent-service.js";
5
+ import type { AgentOwner } from "../../model/agent.js";
6
+ import { object, json } from "../../common/json.js";
7
+ import { requireThat, schema } from "../../common/validation.js";
8
+ import { markdownFields } from "../../common/markdown.js";
9
+ import { executeAgent, type AgentInput } from "./actions.js";
10
+ import { agentDescription, agentResultSchema } from "./contract.js";
11
+ import { dshAgentActionSchema, dshAgentParametersSchema } from "./dsh-contract.js";
12
+
13
+ export function registerDshAgentTool(ctx: Context, agent: Agent, service: AgentService, owner: AgentOwner, check: () => void) {
14
+ const validators = new Map(dshAgentActionSchema.anyOf.map(row => [row.properties.action.const, schema(row)]));
15
+ const validOutput = schema(agentResultSchema);
16
+ ctx.tools.register({ name: "pbot_agent", description: agentDescription,
17
+ parameters: dshParameters(dshAgentParametersSchema),
18
+ output: {
19
+ schema: dshOutputSchema(agentResultSchema),
20
+ render: (_args, value) => [{ type: "text", text: "# 专家\n\n" + markdownFields(value) }],
21
+ presentationMeta: (args, value) => {
22
+ if (!object(args) || args.action !== "read" || !object(value) || !object(value.cycle) || value.cycle.state === "running") return {};
23
+ return json({ pbotAgentReadKeys: [String(value.agentId) + ":" + String(value.cycle.cycleId)] });
24
+ },
25
+ },
26
+ async execute(args, exec) {
27
+ check(); exec.signal.throwIfAborted();
28
+ requireThat(exec.agent === agent && object(args) && typeof args.action === "string", "专家工具会话或参数无效。");
29
+ const valid = validators.get(args.action);
30
+ requireThat(valid && valid(args), "dsh 专家参数无效或包含不支持的宿主选项。");
31
+ const selection = agent.model.current();
32
+ const output = await executeAgent(service, args as AgentInput, owner, selection ? {
33
+ provider: selection.provider, model: selection.model,
34
+ ...(selection.reasoningEffort && (!args.provider || args.provider === selection.provider) && (!args.model || args.model === selection.model)
35
+ ? { thinking: selection.reasoningEffort } : {}),
36
+ } : {});
37
+ requireThat(validOutput(output.structuredContent), "专家输出不符合契约。");
38
+ return output.structuredContent;
39
+ },
40
+ });
41
+ }
@@ -1,61 +1,25 @@
1
- import type { Static } from "typebox";
2
1
  import { defineTool } from "@earendil-works/pi-coding-agent";
3
2
  import type { AgentService } from "../../services/agents/agent-service.js";
4
3
  import { agentOwner } from "../../integrations/pi/agent-context.js";
5
- import type { AgentOptions, AgentView } from "../../model/agent.js";
6
4
  import type { CallAccess } from "../access.js";
7
5
  import { requireThat, schema } from "../../common/validation.js";
8
- import { markdownResult } from "../result.js";
9
- import { agentActionSchema, agentParametersSchema, agentResultSchema, agentViewSchema } from "./contract.js";
10
-
11
- type AgentOutput = Static<typeof agentViewSchema>;
12
- function agentOutput(view: AgentView & Partial<AgentOutput>): AgentOutput {
13
- const c = view.cycle;
14
- return {
15
- agentId: view.agentId, name: view.name, cwd: view.cwd, createdAt: view.createdAt,
16
- owned: view.owned, state: view.state, activity: view.activity,
17
- ...(c ? { cycle: { cycleId: c.cycleId, startedAt: c.startedAt, state: c.state, requestIds: c.requestIds, source: c.source,
18
- ...(c.finishedAt === undefined ? {} : { finishedAt: c.finishedAt }), ...(c.error === undefined ? {} : { error: c.error }) } } : {}),
19
- ...(view.error === undefined ? {} : { error: view.error }),
20
- ...(view.requestId === undefined ? {} : { requestId: view.requestId }),
21
- ...(view.messageStatus === undefined ? {} : { messageStatus: view.messageStatus }),
22
- ...(view.cycles === undefined ? {} : { cycles: view.cycles }),
23
- ...(view.report === undefined ? {} : { report: view.report }),
24
- ...(view.totalLength === undefined ? {} : { totalLength: view.totalLength }),
25
- ...(view.nextOffset === undefined ? {} : { nextOffset: view.nextOffset }),
26
- };
27
- }
6
+ import { executeAgent } from "./actions.js";
7
+ import { agentDescription, agentActionSchema, agentParametersSchema, agentResultSchema } from "./contract.js";
28
8
 
29
9
  export function agentTool(service: AgentService, access: CallAccess) {
30
10
  const validators = new Map(agentActionSchema.anyOf.map(action => [action.properties.action.const, schema(action)]));
31
11
  return defineTool({ name: "pbot_agent", label: "子会话",
32
- description: "独立 Pi 子会话,不继承父聊天历史。start 立即返回;未提供 message/prompts/files 时只启动待命进程,不调用模型。完成自动通知,无需轮询。团队占用期间不能私人 send。send 继续对话,rename 改名,status/list 查看状态,status 返回最近周期且不读取报告正文。read 非阻塞读取报告;读取已结束周期会领取该周期的完成通知(包括部分读取),后续不再重复通知。nextOffset 非空时以返回的 cycle.cycleId 固定周期续读。state 是进程状态,cycle.state 是工作结果,queued 仅表示入队。stop 停止并保留历史,resume 携新 message 恢复,不重放旧工作。独立复核须 start 新上下文。子会话使用原生 CLI/MCP,按委派范围工作,不递归委派。每个父会话最多 50 个存活子会话,空闲也计入。",
12
+ description: agentDescription,
33
13
  parameters: agentParametersSchema, outputSchema: agentResultSchema,
34
14
  async execute(_id, args, signal, _update, ctx) {
35
15
  access.check(); signal?.throwIfAborted();
36
16
  const valid = validators.get(args.action);
37
17
  requireThat(valid, "未知的子会话 action:" + args.action);
38
18
  requireThat(valid(args), "子会话 " + args.action + " 参数无效:" + JSON.stringify(valid.errors));
39
- const input = args as Static<typeof agentParametersSchema>, owner = agentOwner(ctx);
40
- let value: AgentView & Partial<AgentOutput>;
41
- switch (input.action) {
42
- case "start": {
43
- const { action: _action, ...supplied } = input;
44
- value = service.start({ provider: ctx.model?.provider, model: ctx.model?.id, thinking: ctx.thinkingLevel, ...supplied } as AgentOptions, owner); break;
45
- }
46
- case "list": return markdownResult({ agents: service.list(owner).map(agentOutput) }, "子会话列表");
47
- case "send": value = service.send(input.agentId!, input.message!, input.delivery ?? "steer", owner); break;
48
- case "rename": value = service.rename(input.agentId!, input.name!, owner); break;
49
- case "resume": value = service.resume(input.agentId!, input.message!, owner); break;
50
- case "stop": value = service.stop(input.agentId!, owner); break;
51
- case "status": value = service.status(input.agentId!, owner); break;
52
- case "read": {
53
- const page = service.read(input.agentId!, owner, input.cycleId, input.offset, input.limit);
54
- const data = agentOutput(page), output = markdownResult(data, "子会话报告");
55
- return { ...output, details: { ...data, ...(page.cycle && page.cycle.state !== "running" ? { agentReadKeys: [page.agentId + ":" + page.cycle.cycleId] } : {}) } };
56
- }
57
- }
58
- return markdownResult(agentOutput(value), "子会话状态");
19
+ return executeAgent(service, args, agentOwner(ctx), {
20
+ ...(ctx.model ? { provider: ctx.model.provider, model: ctx.model.id } : {}),
21
+ ...(ctx.thinkingLevel ? { thinking: ctx.thinkingLevel } : {}),
22
+ });
59
23
  },
60
24
  });
61
25
  }
@@ -0,0 +1,9 @@
1
+ import type { HistoryLocation } from "../../model/history.js";
2
+ import type { HistoryService } from "../../services/history/history-service.js";
3
+ import type { HistoryInput } from "./contract.js";
4
+
5
+ export async function executeHistory(service: HistoryService, args: HistoryInput, location: HistoryLocation, signal?: AbortSignal) {
6
+ signal?.throwIfAborted(); await service.refresh(location, signal); signal?.throwIfAborted();
7
+ return args.action === "search" ? service.search(args) :
8
+ { node: service.read(args.id!, args.offset, args.limit, args.childrenOffset, args.host), warnings: service.warnings };
9
+ }
@@ -0,0 +1,34 @@
1
+ import { Type, type Static } from "typebox";
2
+ import { requireThat, schema } from "../../common/validation.js";
3
+
4
+ const host = Type.Union([Type.Literal("pi"), Type.Literal("dsh")]);
5
+ const nullableOffset = Type.Union([Type.Integer(), Type.Null()]);
6
+ const node = Type.Object({ host, id: Type.String(), kind: Type.Union([Type.Literal("session"), Type.Literal("exchange"), Type.Literal("summary")]),
7
+ sessionId: Type.String(), name: Type.String(), preview: Type.String(), from: Type.String(), to: Type.String(), ready: Type.Boolean(), completed: Type.Boolean() });
8
+ export const historyResultSchema = Type.Object({
9
+ matches: Type.Optional(Type.Array(Type.Object({ ...node.properties, snippet: Type.String(), parents: Type.Array(Type.String()) }))),
10
+ total: Type.Optional(Type.Integer()), nextOffset: Type.Optional(nullableOffset),
11
+ node: Type.Optional(Type.Object({ ...node.properties, text: Type.String(), totalLength: Type.Integer(), nextOffset: nullableOffset,
12
+ parents: Type.Array(node), children: Type.Array(node), nextChildrenOffset: nullableOffset,
13
+ source: Type.Object({ host, sessionId: Type.String({ description: "原始宿主会话 ID;来源不保存文件路径。" }), entryIds: Type.Array(Type.String()) }), warnings: Type.Array(Type.String()) })),
14
+ warnings: Type.Array(Type.String()),
15
+ });
16
+ export const historyDescription = "查询当前工作区的 Pi 与 dsh 历史归档,可用 host 筛选来源。search 对问答原文和各层摘要做不区分大小写的连续文本匹配;省略 query 列出会话。忘记原词时换几组词查询。read 读取命中节点并返回父子节点;沿 parents/children 的 id 展开,nextOffset 续读正文,nextChildrenOffset 续读子节点。摘要是检索线索,重要结论回到 exchange 原文核对。共享前缀可能有多个父节点。查询不调用模型,不恢复或重放旧任务;只更新当前宿主的原生历史索引。问答归档不代替专家的完整工具日志。";
17
+ export const historyParameters = Type.Object({ action: Type.Union([Type.Literal("search"), Type.Literal("read")]),
18
+ host: Type.Optional(host), query: Type.Optional(Type.String()), sessionId: Type.Optional(Type.String()),
19
+ from: Type.Optional(Type.String({ description: "search:ISO 日期或时间,下界。" })),
20
+ to: Type.Optional(Type.String({ description: "search:ISO 日期或时间,上界。" })),
21
+ id: Type.Optional(Type.String({ description: "read 必填:返回的节点 id。" })),
22
+ offset: Type.Optional(Type.Integer({ minimum: 0 })),
23
+ limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 24000, description: "search:最多 50 条,默认 20;read:字符数,默认 12000。" })),
24
+ childrenOffset: Type.Optional(Type.Integer({ minimum: 0 })),
25
+ }, { additionalProperties: false });
26
+ export type HistoryInput = Static<typeof historyParameters>;
27
+ const valid = schema(historyParameters);
28
+ export function historyInput(value: unknown): HistoryInput {
29
+ requireThat(valid(value), "历史参数无效。");
30
+ const args = value as HistoryInput;
31
+ requireThat(args.action !== "read" || args.id, "read 需要节点 id。");
32
+ requireThat(args.action !== "search" || (args.limit ?? 20) <= 50, "search limit 最多 50。");
33
+ return args;
34
+ }
@@ -0,0 +1,22 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import type { Agent } from "@deepseek-ai/dsh-agent";
3
+ import type { HistoryService } from "../../services/history/history-service.js";
4
+ import { dshParameters, dshOutputSchema } from "../../integrations/dsh/schema.js";
5
+ import { requireThat, schema } from "../../common/validation.js";
6
+ import { json } from "../../common/json.js";
7
+ import { markdownFields } from "../../common/markdown.js";
8
+ import { historyDescription, historyInput, historyParameters, historyResultSchema } from "./contract.js";
9
+ import { executeHistory } from "./actions.js";
10
+
11
+ export function registerDshHistoryTool(ctx: Context, agent: Agent, service: HistoryService, lifetime: AbortSignal, check: () => void) {
12
+ const valid = schema(historyResultSchema);
13
+ ctx.tools.register({ name: "pbot_history", description: historyDescription, parameters: dshParameters(historyParameters),
14
+ output: { schema: dshOutputSchema(historyResultSchema), render: (_args, value) => [{ type: "text", text: "# 历史\n\n" + markdownFields(value) }] },
15
+ async execute(value, exec) {
16
+ check(); requireThat(exec.agent === agent, "历史工具不属于当前会话。");
17
+ const signal = AbortSignal.any([exec.signal, lifetime]);
18
+ const result = await executeHistory(service, historyInput(value), { host: "dsh", sessionId: agent.id }, signal);
19
+ check(); signal.throwIfAborted(); requireThat(valid(result), "历史输出不符合契约。"); return json(result);
20
+ },
21
+ });
22
+ }
@@ -1,42 +1,19 @@
1
- import { Type } from "typebox";
2
1
  import { defineTool } from "@earendil-works/pi-coding-agent";
3
2
  import type { HistoryService } from "../../services/history/history-service.js";
4
3
  import type { CallAccess } from "../access.js";
5
4
  import { historyLocation } from "../../integrations/pi/history.js";
6
- import { requireThat } from "../../common/validation.js";
7
5
  import { markdownResult } from "../result.js";
6
+ import { historyDescription, historyInput, historyParameters, historyResultSchema } from "./contract.js";
7
+ import { executeHistory } from "./actions.js";
8
8
 
9
- const nullableOffset = Type.Union([Type.Integer(), Type.Null()]);
10
- const node = Type.Object({ id: Type.String(), kind: Type.Union([Type.Literal("session"), Type.Literal("exchange"), Type.Literal("summary")]),
11
- sessionId: Type.String(), name: Type.String(), preview: Type.String(), from: Type.String(), to: Type.String(), ready: Type.Boolean(), completed: Type.Boolean() });
12
- const resultSchema = Type.Object({
13
- matches: Type.Optional(Type.Array(Type.Object({ ...node.properties, snippet: Type.String(), parents: Type.Array(Type.String()) }))),
14
- total: Type.Optional(Type.Integer()), nextOffset: Type.Optional(nullableOffset),
15
- node: Type.Optional(Type.Object({ ...node.properties, text: Type.String(), totalLength: Type.Integer(), nextOffset: nullableOffset,
16
- parents: Type.Array(node), children: Type.Array(node), nextChildrenOffset: nullableOffset,
17
- source: Type.Object({ sessionId: Type.String({ description: "原始 Pi 会话 ID;来源不保存文件路径。" }), entryIds: Type.Array(Type.String()) }), warnings: Type.Array(Type.String()) })),
18
- warnings: Type.Array(Type.String()),
19
- });
20
9
  export function historyTool(service: HistoryService, access: CallAccess) {
21
- return defineTool({ name: "pbot_history", label: "历史对话",
22
- description: "查询当前工作区的 Pi 历史。search 对问答原文和各层摘要做不区分大小写的连续文本匹配;省略 query 列出会话。忘记原词时换几组词查询。read 读取命中节点并返回父子节点;沿 parents/children 的 id 展开,nextOffset 续读正文,nextChildrenOffset 续读子节点。摘要是检索线索,重要结论回到 exchange 原文核对。共享前缀可能有多个父节点。查询不调用模型,不恢复或重放旧任务。",
23
- parameters: Type.Object({ action: Type.Union([Type.Literal("search"), Type.Literal("read")]),
24
- query: Type.Optional(Type.String()), sessionId: Type.Optional(Type.String()),
25
- from: Type.Optional(Type.String({ description: "search:ISO 日期或时间,下界。" })),
26
- to: Type.Optional(Type.String({ description: "search:ISO 日期或时间,上界。" })),
27
- id: Type.Optional(Type.String({ description: "read 必填:返回的节点 id。" })),
28
- offset: Type.Optional(Type.Integer({ minimum: 0 })),
29
- limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 24000, description: "search:最多 50 条,默认 20;read:字符数,默认 12000。" })),
30
- childrenOffset: Type.Optional(Type.Integer({ minimum: 0 })),
31
- }, { additionalProperties: false }), outputSchema: resultSchema,
10
+ return defineTool({ name: "pbot_history", label: "历史对话", description: historyDescription,
11
+ parameters: historyParameters, outputSchema: historyResultSchema,
32
12
  async execute(_id, args, signal, _update, ctx) {
33
- access.check(); signal?.throwIfAborted();
34
- requireThat(args.action !== "read" || args.id, "read 需要节点 id。");
35
- requireThat(args.action !== "search" || (args.limit ?? 20) <= 50, "search limit 最多 50。");
36
- await service.refresh(historyLocation(ctx), signal);
37
13
  access.check();
38
- return args.action === "search" ? markdownResult(service.search(args), "历史搜索")
39
- : markdownResult({ node: service.read(args.id!, args.offset, args.limit, args.childrenOffset), warnings: service.warnings }, "历史正文");
14
+ const result = await executeHistory(service, historyInput(args), historyLocation(ctx), signal);
15
+ access.check();
16
+ return markdownResult(result, args.action === "search" ? "历史搜索" : "历史正文");
40
17
  },
41
18
  });
42
19
  }
@@ -0,0 +1,38 @@
1
+ import type { Static } from "typebox";
2
+ import type { TeamService } from "../../services/agents/team-service.js";
3
+ import type { AgentService } from "../../services/agents/agent-service.js";
4
+ import type { TeamActor } from "../../model/team.js";
5
+ import type { AgentOwner } from "../../model/agent.js";
6
+ import { teamMessagesMarkdown } from "../../integrations/team-context.js";
7
+ import { markdownResult, result } from "../result.js";
8
+ import type { teamParametersSchema, teamMessageParametersSchema } from "./contract.js";
9
+
10
+ export function executeTeam(service: TeamService, agents: AgentService, input: Static<typeof teamParametersSchema>, owner: AgentOwner) {
11
+ const views = agents.list(owner);
12
+ service.recover(owner);
13
+ let id = input.teamId!;
14
+ switch (input.action) {
15
+ case "create": id = service.create(owner, { name: input.name!, brief: input.brief!, members: input.members! }, views).teamId; break;
16
+ case "list": return markdownResult({ teams: service.list(owner, views) }, "团队列表");
17
+ case "read": {
18
+ const page = service.read(id, { kind: "coordinator", owner }, input);
19
+ return result(page, false, teamMessagesMarkdown(page));
20
+ }
21
+ case "run": service.run(id, owner, { instruction: input.instruction!, mode: input.mode!,
22
+ ...(input.rounds === undefined ? {} : { rounds: input.rounds }),
23
+ ...(input.maxConcurrent === undefined ? {} : { maxConcurrent: input.maxConcurrent }),
24
+ ...(input.timeoutSeconds === undefined ? {} : { timeoutSeconds: input.timeoutSeconds }) }, views); break;
25
+ case "pause": service.pause(id, owner); break;
26
+ case "close": service.close(id, owner, views); break;
27
+ }
28
+ return markdownResult(service.status(id, owner, views), "团队状态");
29
+ }
30
+
31
+ export function executeTeamMessage(service: TeamService, input: Static<typeof teamMessageParametersSchema>, actor: TeamActor, requestId: string) {
32
+ if (input.action === "read") {
33
+ const page = service.read(input.teamId, actor, input);
34
+ return result(page, false, teamMessagesMarkdown(page));
35
+ }
36
+ const message = service.post(input.teamId, actor, requestId, input.text!, input.replyTo);
37
+ return markdownResult({ teamId: input.teamId, seq: message.seq, status: "stored" }, "团队消息已保存");
38
+ }
@@ -1,4 +1,8 @@
1
1
  import { Type } from "typebox";
2
+
3
+ export const teamDescription = "主会话管理固定成员团队。create 不启动模型;run 开展有限 roundRobin 轮流讨论或 parallel 并行轮次,默认 2 轮、整批 15 分钟,成员直接取得公共原文。团队运行时占用成员;pause 停止新安排和领取,允许在途工作收尾,之后可私人 send;需要中止成员用 pbot_agent stop。到期时子入口尽力中止未被人工接管的团队工作。close 保留成员会话和历史;换成员须关闭旧团队再创建。继续须明确 run 新批次,不重放旧请求。完成仍需业务验收。";
4
+ export const teamMessageDescription = "直接读取/发布团队公共原文。post 不唤醒模型、不增加轮次,所有成员可见,不提供私信或递归委派。成员只能在自己的团队工作中 post。read 用 afterSeq 分页;长消息用 seq/offset/limit 继续,nextOffset 非空表示该条正文不完整。保存成功不是模型已读或业务完成。";
5
+
2
6
  import type { TSchema } from "typebox";
3
7
 
4
8
  const id = Type.String({ minLength: 1, maxLength: 128 });
@@ -23,7 +27,7 @@ const page = {
23
27
  const object = <T extends Record<string, TSchema>>(fields: T) => Type.Object(fields, { additionalProperties: false });
24
28
  export const teamParametersSchema = object({
25
29
  action: Type.Union(["create", "list", "status", "read", "run", "pause", "close"].map(v => Type.Literal(v)), {
26
- description: "create 只接受并必填 name/brief/members;list 只接受 action;status/pause/close 只接受并必填 teamId;read 必填 teamId,可选 afterSeq/seq/offset/limit;run 必填 teamId/instruction/mode,可选 rounds/maxConcurrent/timeoutSeconds。禁止携带其他 action 的字段。run 要求所有成员存活、idle、当前代次团队接入就绪、无待处理私人输入,且团队未关闭、无运行批次或在途工作。pause 仅停止新安排和未领取请求;close 要求先暂停并等待在途工作结束,不停止成员进程。",
30
+ description: "create 只接受并必填 name/brief/members;list 只接受 action;status/pause/close 只接受并必填 teamId;read 必填 teamId,可选 afterSeq/seq/offset/limit;run 必填 teamId/instruction/mode,可选 rounds/maxConcurrent/timeoutSeconds。禁止携带其他 action 的字段。run 要求所有成员存活、idle、当前代次团队接入就绪、无待处理私人输入,且团队未关闭、无运行批次或在途工作。pause 仅停止新安排和未领取请求;close 要求先暂停并等待在途工作结束,不停止成员会话。",
27
31
  }),
28
32
  teamId: Type.Optional(id), name: Type.Optional(name), brief: Type.Optional(text), members: Type.Optional(members),
29
33
  instruction: Type.Optional(run.instruction), mode: Type.Optional(mode), rounds: run.rounds, maxConcurrent: run.maxConcurrent, timeoutSeconds: run.timeoutSeconds, ...page,
@@ -55,9 +59,9 @@ export const teamPageSchema = object({ teamId: id, messages: Type.Array(message)
55
59
  nextAfterSeq: Type.Union([Type.Integer(), Type.Null()], { description: "后续消息列表的 afterSeq;null 只表示列表已到末尾,仍须检查每条消息的 nextOffset。指定 seq 时始终为 null。" }) });
56
60
  const ownedView = object({ teamId: id, name: Type.String(), brief: Type.String(), closed: Type.Boolean(), owned: Type.Literal(true),
57
61
  members: Type.Array(object({ agentId: id, role: Type.String(), name: Type.String(), state: Type.String() })),
58
- lastSeq: Type.Integer(), activeCount: Type.Integer({ description: "所有批次中已领取、进程仍存活且尚无完成记录的请求数;paused 仍可能大于 0。" }),
62
+ lastSeq: Type.Integer(), activeCount: Type.Integer({ description: "所有批次中已领取、执行实例仍存活且尚无完成记录的请求数;paused 仍可能大于 0。" }),
59
63
  unresolved: Type.Array(object({ requestId: id, agentId: id, runId: id, round: Type.Integer(), active: Type.Boolean(),
60
- state: Type.String({ description: "queued=运行批次中尚未领取;accepted=已领取且仍在途;uncertain=已领取但进程/代次不再匹配、无完成记录;cancelled=批次已停止且未领取,或周期取消;unpublished=已完成但尚未公开有效原文;mixed/interactive=含人工或私人工作,不自动公开;failed/interrupted=失败或中断。不能将 unresolved 当作成功或自动重试许可。" }), cycleId: Type.Optional(id) }), {
64
+ state: Type.String({ description: "queued=运行批次中尚未领取;accepted=已领取且仍在途;uncertain=已领取但执行实例/代次不再匹配、无完成记录;cancelled=批次已停止且未领取,或周期取消;unpublished=已完成但尚未公开有效原文;mixed/interactive=含人工或私人工作,不自动公开;failed/interrupted=失败或中断。不能将 unresolved 当作成功或自动重试许可。" }), cycleId: Type.Optional(id) }), {
61
65
  description: "所有批次中尚未确认完成且公开的请求,包含排队、在途、失败、结果不明和不宜公开的工作;不是仅列出运行中的请求。先核对原请求和业务结果,避免重复执行。",
62
66
  }),
63
67
  run: Type.Optional(object({ runId: id, instruction: Type.String(), mode, state: Type.Union([Type.Literal("running"), Type.Literal("paused"), Type.Literal("finished")]),
@@ -0,0 +1,55 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import type { Agent } from "@deepseek-ai/dsh-agent";
3
+ import { dshParameters, dshOutputSchema } from "../../integrations/dsh/schema.js";
4
+ import type { Static } from "typebox";
5
+ import type { TeamService } from "../../services/agents/team-service.js";
6
+ import type { AgentService } from "../../services/agents/agent-service.js";
7
+ import type { AgentOwner } from "../../model/agent.js";
8
+ import type { TeamActor } from "../../model/team.js";
9
+ import { object } from "../../common/json.js";
10
+ import { requireThat, schema } from "../../common/validation.js";
11
+ import { markdownFields } from "../../common/markdown.js";
12
+ import { teamMessagesMarkdown } from "../../integrations/team-context.js";
13
+ import { executeTeam, executeTeamMessage } from "./actions.js";
14
+ import { teamDescription, teamMessageDescription, teamParametersSchema, teamActionSchema, teamResultSchema,
15
+ teamMessageParametersSchema, teamMessageActionSchema, teamMessageResultSchema } from "./contract.js";
16
+
17
+ export function registerDshTeamTool(ctx: Context, agent: Agent, service: TeamService, agents: AgentService, owner: AgentOwner, check: () => void) {
18
+ const validators = new Map(teamActionSchema.anyOf.map(row => [row.properties.action.const, schema(row)]));
19
+ const validOutput = schema(teamResultSchema);
20
+ ctx.tools.register({ name: "pbot_team", description: teamDescription, parameters: dshParameters(teamParametersSchema),
21
+ output: {
22
+ schema: dshOutputSchema(teamResultSchema),
23
+ render: (args, value) => [{ type: "text", text: object(args) && args.action === "read"
24
+ ? teamMessagesMarkdown(value as unknown as ReturnType<TeamService["read"]>) : "# 团队\n\n" + markdownFields(value) }],
25
+ },
26
+ async execute(args, exec) {
27
+ check(); exec.signal.throwIfAborted();
28
+ requireThat(exec.agent === agent && object(args) && typeof args.action === "string", "团队工具会话或参数无效。");
29
+ const valid = validators.get(args.action);
30
+ requireThat(valid && valid(args), "团队参数无效。");
31
+ const value = executeTeam(service, agents, args as Static<typeof teamParametersSchema>, owner).structuredContent;
32
+ requireThat(validOutput(value), "团队输出不符合契约。"); return value;
33
+ },
34
+ });
35
+ }
36
+
37
+ export function registerDshTeamMessageTool(ctx: Context, agent: Agent, service: TeamService, actor: () => TeamActor, check: () => void) {
38
+ const validators = new Map<string, ReturnType<typeof schema>>(teamMessageActionSchema.anyOf.map(row => [row.properties.action.const, schema(row)]));
39
+ const validOutput = schema(teamMessageResultSchema);
40
+ ctx.tools.register({ name: "pbot_team_message", description: teamMessageDescription, parameters: dshParameters(teamMessageParametersSchema),
41
+ output: {
42
+ schema: dshOutputSchema(teamMessageResultSchema),
43
+ render: (args, value) => [{ type: "text", text: object(args) && args.action === "read"
44
+ ? teamMessagesMarkdown(value as unknown as ReturnType<TeamService["read"]>) : "# 团队消息\n\n" + markdownFields(value) }],
45
+ },
46
+ async execute(args, exec) {
47
+ check(); exec.signal.throwIfAborted();
48
+ requireThat(exec.agent === agent && object(args) && typeof args.action === "string", "团队消息会话或参数无效。");
49
+ const valid = validators.get(args.action);
50
+ requireThat(valid && valid(args), "团队消息参数无效。");
51
+ const value = executeTeamMessage(service, args as Static<typeof teamMessageParametersSchema>, actor(), exec.callId).structuredContent;
52
+ requireThat(validOutput(value), "团队消息输出不符合契约。"); return value;
53
+ },
54
+ });
55
+ }
@@ -1,63 +1,38 @@
1
- import type { Static } from "typebox";
2
1
  import { defineTool, type ExtensionContext } from "@earendil-works/pi-coding-agent";
3
2
  import type { TeamService } from "../../services/agents/team-service.js";
4
3
  import type { AgentService } from "../../services/agents/agent-service.js";
5
4
  import type { TeamActor } from "../../model/team.js";
6
5
  import { agentOwner } from "../../integrations/pi/agent-context.js";
7
- import { teamMessagesMarkdown } from "../../integrations/pi/team-context.js";
8
6
  import { requireThat, schema } from "../../common/validation.js";
9
7
  import type { CallAccess } from "../access.js";
10
- import { markdownResult, result } from "../result.js";
11
- import { teamParametersSchema, teamActionSchema, teamResultSchema, teamMessageParametersSchema, teamMessageActionSchema, teamMessageResultSchema } from "./contract.js";
8
+ import { executeTeam, executeTeamMessage } from "./actions.js";
9
+ import { teamDescription, teamMessageDescription, teamParametersSchema, teamActionSchema, teamResultSchema, teamMessageParametersSchema, teamMessageActionSchema, teamMessageResultSchema } from "./contract.js";
12
10
 
13
11
  export function teamTool(service: TeamService, agents: AgentService, access: Pick<CallAccess, "check">) {
14
12
  const validators = new Map(teamActionSchema.anyOf.map(action => [action.properties.action.const, schema(action)]));
15
13
  return defineTool({ name: "pbot_team", label: "团队",
16
- description: "主会话管理固定成员团队。create 不启动模型;run 开展有限 roundRobin 轮流讨论或 parallel 并行轮次,默认 2 轮、整批 15 分钟,成员直接取得公共原文。团队运行时占用成员;pause 停止新安排和领取,允许在途工作收尾,之后可私人 send;需要中止成员用 pbot_agent stop。到期时子入口尽力中止未被人工接管的团队工作。close 保留成员进程和历史;换成员须关闭旧团队再创建。继续须明确 run 新批次,不重放旧请求。完成仍需业务验收。",
14
+ description: teamDescription,
17
15
  parameters: teamParametersSchema, outputSchema: teamResultSchema,
18
16
  async execute(_id, args, signal, _update, ctx) {
19
17
  access.check(); signal?.throwIfAborted();
20
18
  const valid = validators.get(args.action);
21
19
  requireThat(valid, "未知的团队 action:" + args.action);
22
20
  requireThat(valid(args), "团队 " + args.action + " 参数无效:" + JSON.stringify(valid.errors));
23
- const input = args as Static<typeof teamParametersSchema>, owner = agentOwner(ctx), views = agents.list(owner);
24
- service.recover(owner);
25
- let id = input.teamId!;
26
- switch (input.action) {
27
- case "create": id = service.create(owner, { name: input.name!, brief: input.brief!, members: input.members! }, views).teamId; break;
28
- case "list": return markdownResult({ teams: service.list(owner, views) }, "团队列表");
29
- case "read": {
30
- const page = service.read(id, { kind: "coordinator", owner }, input);
31
- return result(page, false, teamMessagesMarkdown(page));
32
- }
33
- case "run": service.run(id, owner, { instruction: input.instruction!, mode: input.mode!,
34
- ...(input.rounds === undefined ? {} : { rounds: input.rounds }),
35
- ...(input.maxConcurrent === undefined ? {} : { maxConcurrent: input.maxConcurrent }),
36
- ...(input.timeoutSeconds === undefined ? {} : { timeoutSeconds: input.timeoutSeconds }) }, views); break;
37
- case "pause": service.pause(id, owner); break;
38
- case "close": service.close(id, owner, views); break;
39
- }
40
- return markdownResult(service.status(id, owner, views), "团队状态");
21
+ return executeTeam(service, agents, args, agentOwner(ctx));
41
22
  },
42
23
  });
43
24
  }
44
25
  export function teamMessageTool(service: TeamService, access: Pick<CallAccess, "check">, actor: (ctx: ExtensionContext) => TeamActor) {
45
26
  const validators = new Map(teamMessageActionSchema.anyOf.map(action => [action.properties.action.const, schema(action)]));
46
27
  return defineTool({ name: "pbot_team_message", label: "团队消息",
47
- description: "直接读取/发布团队公共原文。post 不唤醒模型、不增加轮次,所有成员可见,不提供私信或递归委派。成员只能在自己的团队工作中 post。read 用 afterSeq 分页;长消息用 seq/offset/limit 继续,nextOffset 非空表示该条正文不完整。保存成功不是模型已读或业务完成。",
28
+ description: teamMessageDescription,
48
29
  parameters: teamMessageParametersSchema, outputSchema: teamMessageResultSchema,
49
30
  async execute(id, args, signal, _update, ctx) {
50
31
  access.check(); signal?.throwIfAborted();
51
32
  const valid = validators.get(args.action);
52
33
  requireThat(valid, "未知的团队消息 action:" + args.action);
53
34
  requireThat(valid(args), "团队消息 " + args.action + " 参数无效:" + JSON.stringify(valid.errors));
54
- const input = args as Static<typeof teamMessageParametersSchema>, sender = actor(ctx);
55
- if (input.action === "read") {
56
- const page = service.read(input.teamId, sender, input);
57
- return result(page, false, teamMessagesMarkdown(page));
58
- }
59
- const message = service.post(input.teamId, sender, id, input.text!, input.replyTo);
60
- return markdownResult({ teamId: input.teamId, seq: message.seq, status: "stored" }, "团队消息已保存");
35
+ return executeTeamMessage(service, args, actor(ctx), id);
61
36
  },
62
37
  });
63
38
  }
@@ -1,11 +1,14 @@
1
1
  import { truncateToWidth, type Component } from "@earendil-works/pi-tui";
2
2
  import type { Theme } from "@earendil-works/pi-coding-agent";
3
3
  import type { AgentView } from "../model/agent.js";
4
- export const agentSummary = (v: AgentView) => `${v.name} · ${v.agentId.slice(0, 8)} · ${v.state} · ${v.activity.replace(/\s+/g, " ")}`;
4
+ import { duration, oneLine, stateLabel } from "./presentation.js";
5
+ export const agentSummary = (v: AgentView) => `${v.name} · ${stateLabel(v.state)} · ${v.agentId.slice(0, 8)}`;
5
6
  export function agentBoard(values: AgentView[], theme: Theme): Component {
6
7
  return { invalidate() {}, render(width) {
7
- return values.filter(v => ["starting", "busy", "stopping"].includes(v.state)).map(v => truncateToWidth(
8
- theme.fg("accent", "● " + v.name) + theme.fg("dim", ` · ${v.state} · ${Math.max(0, Math.floor((Date.now() - (v.cycle?.startedAt ?? v.createdAt)) / 1000))}s · `) +
9
- v.activity.replace(/\s+/g, " "), Math.max(1, width)));
8
+ const active = values.filter(v => ["starting", "busy", "stopping"].includes(v.state));
9
+ return [...active.slice(0, 4).map(v => truncateToWidth(
10
+ theme.fg("accent", v.name) + theme.fg("dim", ` · ${stateLabel(v.state)} · ${duration(Date.now() - (v.cycle?.startedAt ?? v.createdAt))} · `) +
11
+ oneLine(v.activity), Math.max(1, width))),
12
+ ...(active.length > 4 ? [truncateToWidth(theme.fg("dim", `另有 ${active.length - 4} 位专家 · /pbot agents`), Math.max(1, width))] : [])];
10
13
  } };
11
14
  }
@@ -0,0 +1,42 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import type {} from "@deepseek-ai/dsh-client-file-upload/client";
3
+ import type { CommandSubmitAttachment } from "@deepseek-ai/dsh-commands/types";
4
+ import type { ImageMediaType } from "@deepseek-ai/dsh-attachment/types";
5
+ import type { SessionId } from "@deepseek-ai/dsh-session/types";
6
+
7
+ const images = new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
8
+
9
+ function encodedImage(file: File, signal: AbortSignal): Promise<string> {
10
+ signal.throwIfAborted();
11
+ return new Promise((resolve, reject) => {
12
+ const reader = new FileReader(), abort = () => reader.abort();
13
+ signal.addEventListener("abort", abort, { once: true });
14
+ reader.onloadend = () => signal.removeEventListener("abort", abort);
15
+ reader.onerror = () => reject(reader.error ?? new Error("图片读取失败。"));
16
+ reader.onabort = () => reject(signal.reason ?? new DOMException("图片读取已取消。", "AbortError"));
17
+ reader.onload = () => {
18
+ if (typeof reader.result !== "string") reject(new Error("图片读取失败。"));
19
+ else resolve(reader.result.slice(reader.result.indexOf(",") + 1));
20
+ };
21
+ reader.readAsDataURL(file);
22
+ });
23
+ }
24
+
25
+ /** Native commands admit encoded images and resolve session-owned file receipts. */
26
+ export async function commandAttachments(ctx: Context, sessionId: SessionId, files: readonly File[],
27
+ signal: AbortSignal, progress: (message: string) => void): Promise<CommandSubmitAttachment[]> {
28
+ if (files.length > 32) throw new Error("一次最多附加 32 个文件。");
29
+ const attachments: CommandSubmitAttachment[] = [];
30
+ for (const file of files) {
31
+ signal.throwIfAborted(); progress("正在准备附件:" + file.name);
32
+ if (images.has(file.type)) {
33
+ attachments.push({ type: "image", mediaType: file.type as ImageMediaType, name: file.name, data: await encodedImage(file, signal) });
34
+ } else {
35
+ const result = await ctx.fileUpload.upload(sessionId, file, file.name, signal,
36
+ value => progress(`正在上传 ${file.name}:${value.loaded} / ${value.total ?? file.size} 字节`));
37
+ if (!result.ok) throw new Error(result.error.message);
38
+ attachments.push({ type: "file", receiptId: result.value.receiptId });
39
+ }
40
+ }
41
+ signal.throwIfAborted(); return attachments;
42
+ }
@@ -0,0 +1,50 @@
1
+ import { object } from "../../common/json.js";
2
+ import { stateLabel } from "../presentation.js";
3
+
4
+ export const record = (value: unknown): Record<string, unknown> => { if (!object(value)) throw new Error("管理数据无效。"); return value; };
5
+ export const rows = (value: unknown) => Array.isArray(value) ? value.map(record) : [];
6
+ export const text = (value: unknown) => value === null || value === undefined ? "" : String(value);
7
+ export const objectInput = (value: string) => record(JSON.parse(value || "{}"));
8
+ export const lines = (value: string) => value.split(/\r?\n/).map(item => item.trim()).filter(Boolean);
9
+
10
+ export function element<K extends keyof HTMLElementTagNameMap>(tag: K, content = "", className = "") {
11
+ const node = document.createElement(tag); node.textContent = content; node.className = className; return node;
12
+ }
13
+ export function block(value: unknown) { return element("pre", typeof value === "string" ? value : JSON.stringify(value, null, 2), "pbot-prose"); }
14
+ export function note(value: string) { return element("p", value, "pbot-muted"); }
15
+ export function group(parent: HTMLElement, className = "pbot-stack") {
16
+ const node = element("div", "", className); parent.append(node); return node;
17
+ }
18
+ export function details(parent: HTMLElement, title: string) {
19
+ const node = element("details", "", "pbot-disclosure"); node.append(element("summary", title)); parent.append(node); return node;
20
+ }
21
+ export function field(parent: HTMLElement, label: string, multiline = false, value = "") {
22
+ const wrapper = element("label", "", "pbot-field"), input = multiline ? element("textarea") : element("input");
23
+ wrapper.append(element("span", label)); input.value = value; input.setAttribute("aria-label", label);
24
+ if (input instanceof HTMLTextAreaElement) input.rows = 3;
25
+ wrapper.append(input); parent.append(wrapper); return input;
26
+ }
27
+ export function select(parent: HTMLElement, label: string, values: [string, string][]) {
28
+ const wrapper = element("label", "", "pbot-field"), input = element("select");
29
+ wrapper.append(element("span", label)); input.setAttribute("aria-label", label);
30
+ for (const [value, title] of values) { const option = element("option", title); option.value = value; input.append(option); }
31
+ wrapper.append(input); parent.append(wrapper); return input;
32
+ }
33
+ export function badge(state: unknown) {
34
+ const node = element("span", stateLabel(state), "pbot-badge"); node.dataset.state = text(state); return node;
35
+ }
36
+ export function facts(values: [string, unknown][]) {
37
+ const node = element("dl", "", "pbot-facts");
38
+ for (const [label, value] of values) node.append(element("dt", label), element("dd", text(value) || "—"));
39
+ return node;
40
+ }
41
+ export function empty(title: string, description: string) {
42
+ const node = element("div", "", "pbot-empty"); node.append(element("h3", title), note(description)); return node;
43
+ }
44
+ export function pageTitle(parent: HTMLElement, title: string, description: string) {
45
+ const node = element("header", "", "pbot-page-title"); node.append(element("h2", title)); if (description) node.append(note(description)); parent.append(node); return node;
46
+ }
47
+ export function time(value: unknown) {
48
+ const date = new Date(typeof value === "number" ? value : text(value));
49
+ return Number.isNaN(date.getTime()) ? "" : date.toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
50
+ }
@@ -0,0 +1,18 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import type {} from "@deepseek-ai/dsh-api-remotes/client";
3
+ import type {} from "@deepseek-ai/dsh-client-ui-commands/client";
4
+ import { pbotReadRemote } from "./remote.js";
5
+ import { showManagement } from "./management.js";
6
+
7
+ export const name = "personal-bot";
8
+ export const inject = ["remote"];
9
+
10
+ export async function apply(ctx: Context) {
11
+ ctx.remote.$mount(pbotReadRemote);
12
+ await ctx.plugin({ name: "pbot-management-view", inject: ["commandUi", "remote", "remote.commands", "remote.pbot", "fileUpload"],
13
+ apply(view) {
14
+ view.commandUi.decorate({ name: "pbot", available: () => true,
15
+ ui: { kind: "action", run: session => { showManagement(view, session.sessionId); } } });
16
+ },
17
+ });
18
+ }