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
@@ -5,43 +5,57 @@ import type { WorkspaceInstance } from "./lifecycle.js";
5
5
  import { agentOwner } from "../integrations/pi/agent-context.js";
6
6
  import { tmuxName } from "../integrations/pi/pi-agents.js";
7
7
  import { agentSummary } from "../ui/agent-display.js";
8
+ import { readPage } from "../ui/pi/reader.js";
9
+ import { choose } from "../ui/pi/menu.js";
10
+ import { stateLabel } from "../ui/presentation.js";
8
11
  import type { Confirmations } from "../ui/confirmation.js";
9
12
 
10
13
  export async function showAgents(_pi: ExtensionAPI, instance: WorkspaceInstance, ctx: ExtensionCommandContext, confirmations: Confirmations, agentIds?: string[]) {
11
14
  instance.check();
12
- const owner = () => agentOwner(ctx), values = instance.agents.list(owner()).filter(v => !agentIds || agentIds.includes(v.agentId));
13
- if (ctx.mode !== "tui") { ctx.ui.notify(JSON.stringify(values), "info"); return; }
14
- if (!values.length) { ctx.ui.notify("没有子会话;用 pbot_agent start 创建。", "info"); return; }
15
- if (confirmations.presenting) { ctx.ui.notify("请先处理主会话批准。", "info"); return; }
16
- const options = values.map(v => agentSummary(v) + (v.owned ? "" : " · 其他分支"));
17
- const selected = await ctx.ui.select("pbot 子会话", options);
18
- const value = values[options.indexOf(selected ?? "")]; if (!value) return;
19
- if (!value.owned) { ctx.ui.notify("请返回所属分支操作。", "info"); return; }
20
- const inTmux = Boolean(process.env.TMUX);
21
- const enterTui = "进入 TUI(返回:Ctrl+B → " + (inTmux ? "l" : "d") + ")";
22
- const action = await ctx.ui.select(value.name, [enterTui, "补充要求", "重命名", "停止"]);
23
- instance.check();
24
- if (action === "停止") { instance.agents.stop(value.agentId, owner()); return; }
25
- if (action === "重命名") { const name = await ctx.ui.input("名称"); instance.check(); if (name?.trim()) instance.agents.rename(value.agentId, name.trim(), owner()); return; }
26
- if (action === "补充要求") {
27
- const message = await ctx.ui.editor("任务或补充要求");
28
- instance.check();
29
- if (message?.trim()) instance.agents.send(value.agentId, message, "steer", owner());
30
- return;
15
+ const owner = () => agentOwner(ctx), views = () => instance.agents.list(owner()).filter(v => !agentIds || agentIds.includes(v.agentId));
16
+ if (ctx.mode !== "tui") { ctx.ui.notify(JSON.stringify(views()), "info"); return; }
17
+ if (confirmations.presenting) { ctx.ui.notify("请先处理待确认操作。", "info"); return; }
18
+ for (;;) {
19
+ instance.check(); const values = views();
20
+ if (!values.length) { ctx.ui.notify("还没有专家。告诉主助手需要什么专家即可创建。", "info"); return; }
21
+ const options = values.map(v => agentSummary(v) + (v.owned ? "" : " · 其他分支"));
22
+ const selected = await choose(ctx, "专家", options), picked = values[options.indexOf(selected ?? "")]; if (!picked) return;
23
+ if (!picked.owned) { ctx.ui.notify("请返回所属分支操作。", "info"); continue; }
24
+ for (;;) {
25
+ instance.check(); const value = instance.agents.status(picked.agentId, owner()), inTmux = Boolean(process.env.TMUX);
26
+ const enterTui = value.alive ? "进入会话" : "打开原会话(待命)";
27
+ const action = await choose(ctx, `${value.name} · ${stateLabel(value.state)}`, [enterTui, "详情", ...(value.alive ? ["补充要求"] : []), "重命名", ...(value.alive ? ["停止"] : [])]);
28
+ if (!action) break;
29
+ instance.check();
30
+ try {
31
+ if (action === "详情") {
32
+ await readPage(ctx, value.name, [`状态 ${stateLabel(value.state)}`, `活动 ${value.activity}`, `模型 ${value.options.provider}/${value.options.model}`,
33
+ `目录 ${value.cwd}`, `ID ${value.agentId}`, ...(value.error ? ["", value.error] : []), "", `返回主会话:Ctrl+B → ${inTmux ? "l" : "d"}`].join("\n")); continue;
34
+ }
35
+ if (action === "停止") { instance.agents.stop(value.agentId, owner()); ctx.ui.notify("已请求停止。", "info"); continue; }
36
+ if (action === "重命名") {
37
+ const name = await ctx.ui.input("新名称", value.name); instance.check();
38
+ if (name?.trim()) { instance.agents.rename(value.agentId, name.trim(), owner()); ctx.ui.notify("名称已更新。", "info"); } continue;
39
+ }
40
+ if (action === "补充要求") {
41
+ const message = await ctx.ui.editor("补充要求"); instance.check();
42
+ if (message?.trim()) { instance.agents.send(value.agentId, message, "steer", owner()); ctx.ui.notify("要求已排队。", "info"); } continue;
43
+ }
44
+ if (action !== enterTui) continue;
45
+ ctx.ui.notify(`返回主会话:Ctrl+B → ${inTmux ? "l" : "d"}`, "info");
46
+ let tui: TUI | undefined;
47
+ await ctx.ui.custom<void>((terminal, _theme, _keys, done) => { tui = terminal; queueMicrotask(() => done()); return new Text("", 0, 0); });
48
+ if (!tui) return;
49
+ instance.check(); await instance.agents.enter(value.agentId, owner()); const terminal = tui; terminal.stop();
50
+ try {
51
+ const code = await new Promise<number | null>(resolve => {
52
+ const child = spawn("tmux", [inTmux ? "switch-client" : "attach-session", "-t", "=" + tmuxName(value.agentId)], { stdio: "inherit" });
53
+ child.on("error", () => resolve(null)); child.on("close", resolve);
54
+ });
55
+ if (code !== 0) ctx.ui.notify("无法进入专家会话。", "error");
56
+ } finally { terminal.start(); terminal.requestRender(true); }
57
+ return;
58
+ } catch (error) { ctx.ui.notify(error instanceof Error ? error.message : String(error), "error"); }
59
+ }
31
60
  }
32
- if (action !== enterTui) return;
33
- let tui: TUI | undefined;
34
- await ctx.ui.custom<void>((terminal, _theme, _keys, done) => { tui = terminal; queueMicrotask(() => done()); return new Text("", 0, 0); });
35
- if (!tui) return;
36
- instance.check();
37
- instance.agents.enter(value.agentId, owner());
38
- const terminal = tui;
39
- terminal.stop();
40
- try {
41
- const code = await new Promise<number | null>(resolve => {
42
- const child = spawn("tmux", [inTmux ? "switch-client" : "attach-session", "-t", "=" + tmuxName(value.agentId)], { stdio: "inherit" });
43
- child.on("error", () => resolve(null)); child.on("close", resolve);
44
- });
45
- if (code !== 0) ctx.ui.notify("无法进入子会话。", "error");
46
- } finally { terminal.start(); terminal.requestRender(true); }
47
61
  }
@@ -2,6 +2,10 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { packageVersion } from "../common/version.js";
3
3
  import { prepareWorkspace } from "../services/workspace/workspace-preparation.js";
4
4
  import type { Confirmations } from "../ui/confirmation.js";
5
+ import { readPage } from "../ui/pi/reader.js";
6
+ import { choose } from "../ui/pi/menu.js";
7
+ import { stateLabel } from "../ui/presentation.js";
8
+ import { agentOwner } from "../integrations/pi/agent-context.js";
5
9
  import type { Lifecycle } from "./lifecycle.js";
6
10
  import { showAgents } from "./agents-command.js";
7
11
  import { showTeams } from "./teams-command.js";
@@ -9,39 +13,54 @@ import { showHistory } from "./history.js";
9
13
 
10
14
  export function registerCommand(pi: ExtensionAPI, lifecycle: Lifecycle, confirmations: Confirmations) {
11
15
  pi.registerCommand("pbot", {
12
- description: "工作区:init、status、history、agents、teams、python setup",
16
+ description: "工作区、专家、团队与历史",
13
17
  getArgumentCompletions: prefix => {
14
18
  if (/\s/.test(prefix)) return null;
15
19
  return ["init", "status", "history", "agents", "teams", "python"]
16
- .filter(command => command.startsWith(prefix))
17
- .map(command => ({ value: command, label: command }));
20
+ .filter(command => command.startsWith(prefix)).map(command => ({ value: command, label: command }));
18
21
  },
19
22
  handler: async (args, ctx) => {
20
23
  const [, command, argument = ""] = args.trim().match(/^(\S+)(?:\s+([\s\S]*))?$/) ?? [];
21
24
  confirmations.resume();
22
- if (command === "init" && !argument) {
23
- await ctx.waitForIdle();
25
+ async function execute(command: string | undefined, argument: string) {
24
26
  const instance = lifecycle.get();
25
- await prepareWorkspace(instance.root);
26
- ctx.ui.notify("资料目录已准备;已有 AGENTS.md 保持不变。", "info");
27
- return;
27
+ if (command === "init" && !argument) {
28
+ await ctx.waitForIdle(); instance.check(); await prepareWorkspace(instance.root);
29
+ ctx.ui.notify("资料目录已准备。", "info"); return;
30
+ }
31
+ if (command === "status" && !argument) {
32
+ const python = instance.pythonEnvironment.status;
33
+ if (ctx.mode !== "tui") { ctx.ui.notify(JSON.stringify({ workspaceRoot: instance.root, release: packageVersion, python }, null, 2), "info"); return; }
34
+ const agents = instance.agents.list(agentOwner(ctx)), history = instance.history.status;
35
+ await readPage(ctx, "工作区", [instance.root, "", `版本 ${packageVersion}`,
36
+ `模型 ${ctx.model && !(ctx.model.provider === "unknown" && ctx.model.id === "unknown") ? ctx.model.provider + "/" + ctx.model.id : "未选择"}`,
37
+ `专家 ${agents.length} 位 · ${agents.filter(value => value.alive).length} 位存活`,
38
+ `Python ${stateLabel(python.state)}`, ...(python.directory ? [python.directory] : []), ...(python.error ? ["", python.error] : []),
39
+ "", `历史 ${history.organizing ? "整理中" : history.pending === null ? "查询时更新索引" : history.pending + " 项待整理"}`,
40
+ ...history.warnings].join("\n")); return;
41
+ }
42
+ if (command === "history") { await showHistory(instance, ctx, argument); return; }
43
+ if (command === "agents") { await showAgents(pi, instance, ctx, confirmations); return; }
44
+ if (command === "teams") { await showTeams(pi, instance, ctx, confirmations); return; }
45
+ if (command === "python" && argument === "setup") {
46
+ const cancel = instance.signal(ctx.signal); await ctx.waitForIdle();
47
+ ctx.ui.notify("正在准备 Python…", "info");
48
+ const result = await instance.preparePython(cancel);
49
+ ctx.ui.notify(ctx.mode === "tui" ? "Python 已就绪。" : JSON.stringify(result, null, 2), "info"); return;
50
+ }
51
+ ctx.ui.notify("/pbot init | status | history [关键词] | agents | teams | python setup", "info");
28
52
  }
29
- if (command === "status" && !argument) {
30
- const instance = lifecycle.get();
31
- ctx.ui.notify(JSON.stringify({ workspaceRoot: instance.root, release: packageVersion, python: instance.pythonEnvironment.status }, null, 2), "info"); return;
32
- }
33
- if (command === "history") { await showHistory(lifecycle.get(), ctx, argument); return; }
34
- if (command === "agents") { await showAgents(pi, lifecycle.get(), ctx, confirmations); return; }
35
- if (command === "teams") { await showTeams(pi, lifecycle.get(), ctx, confirmations); return; }
36
- if (command === "python" && argument === "setup") {
37
- const instance = lifecycle.get(), cancel = instance.signal(ctx.signal);
38
- await ctx.waitForIdle();
39
- ctx.ui.notify("正在准备共享 Python 环境;安装可能需要数分钟。", "info");
40
- const result = await instance.preparePython(cancel);
41
- ctx.ui.notify(JSON.stringify(result, null, 2), "info");
42
- return;
53
+ if (command || ctx.mode !== "tui") { await execute(command, argument); return; }
54
+ if (confirmations.presenting) { ctx.ui.notify("请先处理待确认操作。", "info"); return; }
55
+ for (;;) {
56
+ const instance = lifecycle.get(), agents = instance.agents.list(agentOwner(ctx));
57
+ const choices = ["专家 · " + agents.length, "团队", "历史", "工作区状态", "准备资料目录", "准备 Python"];
58
+ const choice = await choose(ctx, `Personal Bot · Python ${stateLabel(instance.pythonEnvironment.status.state)}`, choices);
59
+ if (!choice) return;
60
+ const command = ["agents", "teams", "history", "status", "init", "python"][choices.indexOf(choice)]!;
61
+ try { await execute(command, command === "python" ? "setup" : ""); }
62
+ catch (error) { ctx.ui.notify(error instanceof Error ? error.message : String(error), "error"); }
43
63
  }
44
- ctx.ui.notify("用法:/pbot init | status | history [关键词] | agents | teams | python setup", "info");
45
64
  },
46
65
  });
47
66
  }
@@ -0,0 +1,148 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { Context } from "@deepseek-ai/cordis";
3
+ import { createUserMessage, type UserMessage } from "@deepseek-ai/dsh-llm";
4
+ import { agentNotice, teamNotice } from "../../services/agents/agent-notices.js";
5
+ import { deliveredDshNotices, pendingDshNotices } from "../../integrations/dsh/notifications.js";
6
+ import { flushDshAgent } from "../../integrations/dsh/session.js";
7
+ import type { Agent } from "@deepseek-ai/dsh-agent";
8
+ import type { AgentService } from "../../services/agents/agent-service.js";
9
+ import type { TeamService } from "../../services/agents/team-service.js";
10
+ import type { AgentOwner, AgentCompletion } from "../../model/agent.js";
11
+ import type { TeamInput } from "../../model/team.js";
12
+
13
+ /** Server-owned scheduling survives browser disconnects; native cancellation pauses dispatch. */
14
+ export class DshAgentEvents {
15
+ readonly #dispatching = new Map<string, TeamInput>();
16
+ readonly #signals = new Map<AbortSignal, () => void>();
17
+ #timer: ReturnType<typeof setInterval> | undefined;
18
+ #scan: Promise<void> | undefined;
19
+ #closed = false;
20
+ #pausePending = false;
21
+ #paused = false;
22
+ #error: string | undefined;
23
+ #errorKey: string | undefined;
24
+ #busySince: number | undefined;
25
+ #recovery: "retry" | "repair" | "reload" | undefined;
26
+ #completed: AgentCompletion[] = [];
27
+ #autoWake = false;
28
+ constructor(readonly ctx: Context, readonly agent: Agent, readonly agents: AgentService,
29
+ readonly teams: TeamService, readonly owner: AgentOwner, readonly check: () => void) {}
30
+ get error() { return this.#error; }
31
+ start() {
32
+ this.teams.recover(this.owner);
33
+ this.ctx.on("agent/pre-step", async ({ signal }, next) => {
34
+ if (!this.#signals.has(signal)) {
35
+ const cancel = () => { if (!this.#closed) {
36
+ this.#paused = true; this.#autoWake = false; this.#pausePending = true;
37
+ try { this.pauseTeams(); } catch (error) { this.recordError(error); }
38
+ void this.refresh();
39
+ } };
40
+ this.#signals.set(signal, cancel); signal.addEventListener("abort", cancel, { once: true });
41
+ if (signal.aborted) cancel();
42
+ }
43
+ await this.refresh(); this.check();
44
+ const decision = await next(); this.check();
45
+ if (decision.kind !== "enter" || this.#paused) return decision;
46
+ const note = this.notice(decision.messages);
47
+ return note ? { ...decision, messages: [...decision.messages, note] } : decision;
48
+ });
49
+ this.ctx.on("session/event", (session, event) => {
50
+ if (session !== this.agent.session || this.#closed) return;
51
+ if (event.type === "agent/inbox/spliced" && event.data.inserted.some(message => message.source.kind === "user")) { this.#paused = false; this.#autoWake = true; }
52
+ if (event.type === "user/message" && event.data.source.kind === "pbot-results") this.#autoWake = true;
53
+ if (event.type === "turn/end") {
54
+ for (const [signal, cancel] of this.#signals) signal.removeEventListener("abort", cancel); this.#signals.clear();
55
+ if (event.data.reason.kind === "aborted") {
56
+ this.#paused = true; this.#autoWake = false; this.#pausePending = true;
57
+ try { this.pauseTeams(); } catch (error) { this.recordError(error); }
58
+ }
59
+ void this.refresh();
60
+ }
61
+ });
62
+ this.#timer = setInterval(() => { void this.refresh(); }, 1000); this.#timer.unref();
63
+ this.ctx.effect(() => () => this.close());
64
+ }
65
+ private pauseTeams() {
66
+ if (!this.#pausePending) return;
67
+ try { this.teams.pauseAll(this.owner, "主会话中断"); this.#pausePending = false; this.#dispatching.clear(); }
68
+ catch (error) {
69
+ if ((error as NodeJS.ErrnoException).code !== "PBOT_CONTROL_BUSY") { this.teams.fail(error); this.#dispatching.clear(); }
70
+ throw error;
71
+ }
72
+ }
73
+ private recordError(error: unknown) {
74
+ if (this.#closed || (error as Error).name === "AbortError") return;
75
+ if ((error as NodeJS.ErrnoException).code === "PBOT_CONTROL_BUSY") {
76
+ this.#busySince ??= Date.now(); if (Date.now() - this.#busySince < 5000) return;
77
+ } else this.#busySince = undefined;
78
+ const message = String(error);
79
+ let recovery: "retry" | "repair" | "reload" = (error as NodeJS.ErrnoException).code === "PBOT_CONTROL_BUSY" ? "retry" : "repair";
80
+ try { this.teams.check(); } catch { recovery = "reload"; }
81
+ if (message !== this.#error || recovery !== this.#recovery) {
82
+ this.ctx.logger.warn("pbot 协作扫描失败:" + message);
83
+ this.#errorKey = "agent-error:" + randomUUID();
84
+ }
85
+ this.#error = message; this.#recovery = recovery;
86
+ }
87
+ private notice(proposed: readonly UserMessage[] = []) {
88
+ const known = new Set([...deliveredDshNotices(this.agent.session.snapshotEvents()),
89
+ ...pendingDshNotices([...this.agent.inbox.nextTurn, ...this.agent.inbox.nextStep, ...proposed])]);
90
+ let value: { keys: string[]; content: string } | undefined;
91
+ if (this.#error && this.#errorKey) {
92
+ if (!known.has(this.#errorKey)) value = { keys: [this.#errorKey], content: "# 协作扫描故障\n\n" + this.#error +
93
+ "\n\n先核对控制记录和原生报告;结果未知不是重跑许可。" +
94
+ (this.#recovery === "reload" ? "团队服务已停用;修复文件后还需重载服务端 personal-bot 插件。" :
95
+ this.#recovery === "retry" ? "控制锁持续占用,后台会继续重试;不会自动删除锁或重新提交任务。" : "报告或控制记录读取失败,修复后后台会重试扫描。") +
96
+ (this.#pausePending ? "\n\n团队暂停尚未确认,不能据此开始成员私人任务。" : "") };
97
+ } else {
98
+ value = teamNotice(this.teams.notifications(this.owner, this.agents.list(this.owner)), known) ??
99
+ agentNotice(this.#completed, this.teams.links(), known);
100
+ }
101
+ return value ? createUserMessage({ content: [{ type: "text", text: value.content }],
102
+ source: { kind: "pbot-results", form: "notice", summary: "Personal Bot 协作更新", keys: value.keys } }) : undefined;
103
+ }
104
+ refresh() {
105
+ if (this.#closed || this.#scan) return this.#scan ?? Promise.resolve();
106
+ this.#scan = (async () => {
107
+ try { await this.scan(); } catch (error) { this.recordError(error); }
108
+ if (this.#closed || this.#paused || !this.#autoWake || this.agent.status !== "idle") return;
109
+ try {
110
+ this.check(); const note = this.notice();
111
+ if (note) {
112
+ // A failed admission cannot start an automatic retry loop. Actual receipt or new human input re-arms wakeup.
113
+ this.#autoWake = false; this.agent.followup(note); await flushDshAgent(this.agent);
114
+ }
115
+ } catch (error) { this.recordError(error); }
116
+ })().finally(() => { this.#scan = undefined; });
117
+ return this.#scan;
118
+ }
119
+ private async scan() {
120
+ this.check(); this.pauseTeams(); await this.agents.refresh(this.owner);
121
+ if (this.#closed) return; this.check();
122
+ const completed = await this.agents.completions(this.owner);
123
+ if (this.#closed) return; this.check(); this.#completed = completed;
124
+ try {
125
+ this.pauseTeams();
126
+ for (const input of this.teams.advance(this.owner, this.agents.list(this.owner), completed)) this.#dispatching.set(input.requestId, input);
127
+ for (const [id, input] of this.#dispatching) {
128
+ try { this.agents.enqueueTeam(input, this.owner); this.#dispatching.delete(id); }
129
+ catch (error) {
130
+ const code = (error as NodeJS.ErrnoException).code;
131
+ if (code === "PBOT_CONTROL_BUSY") throw error;
132
+ if (code !== "TEAM_INPUT_STALE") throw error;
133
+ this.teams.pauseMember(input.agentId, "安排失效;未自动重发"); this.#dispatching.delete(id);
134
+ }
135
+ }
136
+ } catch (error) {
137
+ if ((error as NodeJS.ErrnoException).code !== "PBOT_CONTROL_BUSY") { this.teams.fail(error); this.#dispatching.clear(); }
138
+ throw error;
139
+ }
140
+ this.#error = undefined; this.#errorKey = undefined; this.#busySince = undefined; this.#recovery = undefined;
141
+ }
142
+ async close() {
143
+ this.#closed = true;
144
+ if (this.#timer) clearInterval(this.#timer); this.#timer = undefined;
145
+ for (const [signal, cancel] of this.#signals) signal.removeEventListener("abort", cancel); this.#signals.clear();
146
+ await this.#scan; this.#dispatching.clear();
147
+ }
148
+ }
@@ -0,0 +1,251 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import type { Agent, InboxTarget } from "@deepseek-ai/dsh-agent";
3
+ import { foldConsumedWork } from "@deepseek-ai/dsh-agent";
4
+ import { createUserMessage, type UserMessage, type FileBlock, type ImageBlock } from "@deepseek-ai/dsh-llm";
5
+ import type { SessionEvent } from "@deepseek-ai/dsh-session";
6
+ import { AgentRun } from "../../services/agents/agent-run.js";
7
+ import type { TeamService } from "../../services/agents/team-service.js";
8
+ import { AgentFiles, launch } from "../../storage/agent-files.js";
9
+ import type { AgentDescriptor } from "../../model/agent.js";
10
+ import type { TeamLink } from "../../model/team.js";
11
+ import { requireThat } from "../../common/validation.js";
12
+ import { dshInput, dshHumanInput } from "../../integrations/dsh/input.js";
13
+ import { requestSource, finalDshAnswer } from "../../integrations/dsh/messages.js";
14
+ import { flushDshAgent } from "../../integrations/dsh/session.js";
15
+ import { createTeamContext, teamBrief } from "../../integrations/team-context.js";
16
+
17
+ /** One expert's native event adapter. AgentRun and TeamService own durable business state. */
18
+ export class DshAgentRuntime {
19
+ readonly execution: AgentRun;
20
+ readonly #lifetime = new AbortController();
21
+ readonly #inbox: Record<InboxTarget, UserMessage[]> = { "next-turn": [], "next-step": [] };
22
+ readonly #seen = new Map<string, Set<number>>();
23
+ #events = Promise.resolve();
24
+ #turnEvents: SessionEvent[] = [];
25
+ #answer = "";
26
+ #hasAnswer = false;
27
+ #activeTeam: TeamLink | undefined;
28
+ #pendingPause: string | undefined;
29
+ #pump: Promise<void> | undefined;
30
+ #timer: ReturnType<typeof setInterval> | undefined;
31
+ #stopping: Promise<void> | undefined;
32
+ #closing = false;
33
+ #failure: unknown;
34
+ #ready = false;
35
+ #teamTool = false;
36
+ constructor(readonly ctx: Context, readonly agent: Agent, readonly d: AgentDescriptor, readonly generation: string,
37
+ readonly files: AgentFiles, readonly teams: TeamService, readonly owner: { check(): void; release(): Promise<void> }) {
38
+ this.execution = new AgentRun(files, d.agentId, generation);
39
+ }
40
+ check = () => {
41
+ this.#lifetime.signal.throwIfAborted(); this.owner.check(); this.files.check();
42
+ const run = this.files.read(this.d.agentId, "launch.json", launch);
43
+ requireThat(run.generation === this.generation && !run.stop, "专家已停止或启动代次变化。");
44
+ };
45
+ actor = () => ({ kind: "member" as const, agentId: this.d.agentId, generation: this.generation,
46
+ ...(this.#activeTeam && this.execution.current?.source === "parent" ? { requestId: this.#activeTeam.request.requestId } : {}) });
47
+ private current() { return this.files.read(this.d.agentId, "launch.json", launch).generation === this.generation; }
48
+ private pause(reason: string) {
49
+ this.#pendingPause = reason;
50
+ try { this.teams.pauseMember(this.d.agentId, reason); this.#pendingPause = undefined; }
51
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== "PBOT_CONTROL_BUSY") throw error; }
52
+ }
53
+ private enqueue(work: () => void | Promise<void>) {
54
+ this.#events = this.#events.then(work).catch(error => this.fail(error));
55
+ }
56
+ private fail(error: unknown) {
57
+ if (this.#failure) return;
58
+ this.#failure = error; this.#lifetime.abort();
59
+ try {
60
+ if (this.current()) {
61
+ this.teams.files.setReady(this.d.agentId, this.generation, false);
62
+ this.execution.save({ state: "error", activity: "控制失败", error: String(error) });
63
+ this.pause("专家控制失败");
64
+ }
65
+ } catch (failure) { this.ctx.logger.error("pbot 专家状态保存失败", failure); }
66
+ void this.owner.release().catch(failure => this.ctx.logger.error("pbot 专家释放失败", failure));
67
+ }
68
+ initialize(teamTool: boolean) {
69
+ this.check(); this.#teamTool = teamTool;
70
+ this.teams.files.setReady(this.d.agentId, this.generation, false);
71
+ this.pause("专家载入:旧批次不自动恢复");
72
+ this.execution.recover();
73
+ for (const event of this.agent.session.snapshotEvents()) if (event.type === "user/message" && event.data.source.kind === "pbot-team-context") {
74
+ const { teamId, seqs } = event.data.source, seen = this.#seen.get(teamId) ?? new Set<number>();
75
+ for (const seq of seqs) seen.add(seq); this.#seen.set(teamId, seen);
76
+ }
77
+ this.ctx.on("session/event", (session, event) => {
78
+ if (session === this.agent.session) this.enqueue(() => this.event(event));
79
+ });
80
+ this.ctx.on("agent/status", ({ agent, status }) => {
81
+ if (agent !== this.agent) return;
82
+ this.enqueue(() => {
83
+ if (!this.current() || this.#closing || this.#failure) return;
84
+ this.execution.save({ state: status === "idle" ? "idle" : "busy", activity: status === "idle" ? "空闲" : "执行" });
85
+ });
86
+ });
87
+ this.ctx.on("agent/pre-step", async (_payload, next) => {
88
+ await this.#events; this.check();
89
+ if (this.#activeTeam && !this.humanPending() && this.execution.current?.source === "parent" && Date.now() >= this.#activeTeam.run.deadlineAt) {
90
+ this.pause("已到期限"); this.agent.cancel({ kind: "hook", reason: "团队请求已到期限" });
91
+ return { kind: "reject" };
92
+ }
93
+ const decision = await next();
94
+ this.check();
95
+ if (decision.kind !== "enter" || !this.#activeTeam || this.execution.current?.source !== "parent" || this.humanPending() ||
96
+ decision.messages.some(message => message.source.kind === "user")) return decision;
97
+ const link = this.teams.links().get(this.#activeTeam.request.requestId);
98
+ requireThat(link, "团队关联不可读取。");
99
+ const seen = this.#seen.get(link.team.teamId) ?? new Set<number>();
100
+ const context = createTeamContext(link, seen);
101
+ if (!context) return decision;
102
+ return { ...decision, messages: [...decision.messages, createUserMessage({ content: [{ type: "text", text: context.content }],
103
+ source: { kind: "pbot-team-context", form: "relay", teamId: link.team.teamId, seqs: context.seqs } })] };
104
+ });
105
+ this.ctx.systemPrompt.section({ name: "pbot:team", order: 600, interpolate: false,
106
+ text: () => this.#activeTeam && !this.humanPending() && this.execution.current?.source === "parent" ? teamBrief(this.#activeTeam) : "" });
107
+ this.execution.save({ state: "idle", activity: "空闲" });
108
+ this.ctx.effect(() => () => this.stop());
109
+ }
110
+ /** Called only after native Agent creation has resolved, never from setup. */
111
+ start() {
112
+ this.check();
113
+ this.#timer = setInterval(() => { void this.pump(); }, 250); this.#timer.unref();
114
+ void this.pump();
115
+ }
116
+ async interact(text: string, delivery: "steer" | "followUp" = "steer", files: readonly string[] = [],
117
+ attachments: readonly (ImageBlock | FileBlock)[] = [], caller?: AbortSignal) {
118
+ this.check(); requireThat((text.trim().length > 0 || files.length || attachments.length) && text.length <= 24000, "请输入消息或附加文件;消息最多 24000 字符。");
119
+ const signal = caller ? AbortSignal.any([caller, this.#lifetime.signal]) : this.#lifetime.signal;
120
+ const message = await dshHumanInput(this.ctx, this.d.cwd, text, files, attachments, signal);
121
+ this.check(); signal.throwIfAborted();
122
+ this.teams.pauseMember(this.d.agentId, "成员由人工接管");
123
+ if (delivery === "followUp") this.agent.followup(message); else this.agent.steer(message);
124
+ // Native inbox admission transfers ownership to the expert, independently of the UI request.
125
+ await flushDshAgent(this.agent);
126
+ return { status: "queued" as const, messageId: message.id };
127
+ }
128
+ private async event(event: SessionEvent) {
129
+ if (!this.current()) return;
130
+ if (event.type === "turn/start") {
131
+ this.#turnEvents = []; this.#answer = ""; this.#hasAnswer = false;
132
+ this.execution.begin();
133
+ }
134
+ if (this.execution.current) this.#turnEvents.push(event);
135
+ if (event.type === "agent/inbox/spliced") {
136
+ const change = event.data;
137
+ const removed = this.#inbox[change.target].splice(change.start, change.removedCount ?? 0, ...change.inserted);
138
+ if (change.inserted.some(message => message.source.kind === "user")) this.pause("成员由人工接管");
139
+ for (const message of removed) {
140
+ if (message.source.kind === "user" && change.outcome !== "canceled") this.execution.enterHuman();
141
+ const source = requestSource(message, this.generation);
142
+ if (!source) continue;
143
+ if (change.outcome === "canceled") {
144
+ this.execution.cancelQueued(source.requestId);
145
+ if (this.#activeTeam?.request.requestId === source.requestId && !this.execution.current?.requestIds.includes(source.requestId)) this.#activeTeam = undefined;
146
+ } else this.execution.enter(source.requestId);
147
+ }
148
+ }
149
+ if (event.type === "user/message") {
150
+ const source = requestSource(event.data, this.generation);
151
+ if (source) this.execution.consume(source.requestId);
152
+ if (event.data.source.kind === "user") this.execution.humanInput();
153
+ if (event.data.source.kind === "pbot-team-context") {
154
+ const { teamId, seqs } = event.data.source, seen = this.#seen.get(teamId) ?? new Set<number>();
155
+ for (const seq of seqs) seen.add(seq); this.#seen.set(teamId, seen);
156
+ }
157
+ }
158
+ if (event.type === "assistant/message") {
159
+ this.#answer = finalDshAnswer(event); this.#hasAnswer = true;
160
+ }
161
+ if (event.type === "turn/end") {
162
+ const accounted = foldConsumedWork(this.#turnEvents).end?.seq === event.seq;
163
+ await flushDshAgent(this.agent);
164
+ const reason = event.data.reason;
165
+ this.execution.finish({
166
+ state: reason.kind === "aborted" ? "cancelled" : reason.kind === "interrupted" || reason.kind === "forked" ? "interrupted"
167
+ : reason.kind === "completed" && accounted ? "completed" : "failed",
168
+ position: this.#hasAnswer ? "dsh:" + event.seq : null,
169
+ answer: this.#answer,
170
+ ...(reason.kind === "error" ? { error: reason.error.message } : !["completed", "aborted"].includes(reason.kind) ? { error: reason.kind } : {}),
171
+ });
172
+ this.#activeTeam = undefined; this.#turnEvents = [];
173
+ }
174
+ }
175
+ private humanPending() { return [...this.agent.inbox.nextTurn, ...this.agent.inbox.nextStep].some(message => message.source.kind === "user"); }
176
+ private pump() {
177
+ if (this.#pump || this.#closing) return this.#pump ?? Promise.resolve();
178
+ this.#pump = this.deliver().catch(error => {
179
+ if ((error as NodeJS.ErrnoException).code === "PBOT_CONTROL_BUSY" || (this.#closing && this.#lifetime.signal.aborted)) return;
180
+ this.fail(error);
181
+ }).finally(() => { this.#pump = undefined; });
182
+ return this.#pump;
183
+ }
184
+ private async deliver() {
185
+ const run = this.files.read(this.d.agentId, "launch.json", launch);
186
+ if (run.generation !== this.generation || run.stop) { void this.owner.release().catch(error => this.fail(error)); return; }
187
+ await this.#events; this.check();
188
+ if (this.#pendingPause) { this.pause(this.#pendingPause); if (this.#pendingPause) return; }
189
+ const capable = this.#teamTool && !!this.ctx.tools.get("pbot_team_message", this.agent);
190
+ if (capable !== this.#ready) {
191
+ this.teams.files.setReady(this.d.agentId, this.generation, capable); this.#ready = capable;
192
+ if (!capable) this.pause("成员团队工具已停用");
193
+ }
194
+ if (this.#activeTeam) {
195
+ if (Date.now() >= this.#activeTeam.run.deadlineAt) {
196
+ this.pause("已到期限");
197
+ if (!this.humanPending() && !this.execution.interactive && this.execution.current?.source !== "mixed" && this.execution.current?.source !== "interactive") this.agent.cancel({ kind: "hook", reason: "团队请求已到期限" });
198
+ }
199
+ return;
200
+ }
201
+ const links = this.teams.links();
202
+ const idle = () => this.agent.status === "idle" && !this.agent.inbox.nextTurn.length && !this.agent.inbox.nextStep.length && !this.execution.current && !this.execution.pending && !this.execution.interactive;
203
+ for (const request of this.files.messages(this.d.agentId)) {
204
+ if (request.generation !== this.generation || this.execution.accepted(request.requestId)) continue;
205
+ const link = links.get(request.requestId);
206
+ if (link && (link.team.closed || link.run.state !== "running" || link.run.deadlineAt <= Date.now())) continue;
207
+ if (link && !idle()) break;
208
+ const message = await dshInput(this.ctx, this.d, request, this.#lifetime.signal);
209
+ this.check();
210
+ const submit = async () => {
211
+ this.check();
212
+ const accept = () => this.execution.claim(request.requestId);
213
+ if (link) {
214
+ if (!idle()) return;
215
+ this.#activeTeam = this.teams.claim(this.d.agentId, this.generation, request.requestId, accept);
216
+ if (!this.#activeTeam) return;
217
+ } else accept();
218
+ if (request.delivery === "followUp") this.agent.followup(message); else this.agent.steer(message);
219
+ await flushDshAgent(this.agent);
220
+ };
221
+ if (link) {
222
+ // Public status stays idle during native maintenance. Its idle admission is authoritative.
223
+ let entered = false;
224
+ try { await this.agent.runMaintenance(async signal => { entered = true; signal.throwIfAborted(); await submit(); }); }
225
+ catch (error) { if (entered) throw error; this.check(); }
226
+ } else await submit();
227
+ break;
228
+ }
229
+ }
230
+ stop() {
231
+ if (this.#stopping) return this.#stopping;
232
+ this.#closing = true; this.#lifetime.abort();
233
+ if (this.#timer) clearInterval(this.#timer); this.#timer = undefined;
234
+ this.#stopping = (async () => {
235
+ try {
236
+ if (this.current()) {
237
+ this.files.control(() => {
238
+ const run = this.files.read(this.d.agentId, "launch.json", launch);
239
+ if (run.generation === this.generation && !run.stop) { run.stop = true; this.files.write(this.d.agentId, "launch.json", run, launch); }
240
+ });
241
+ this.teams.files.setReady(this.d.agentId, this.generation, false); this.#ready = false;
242
+ this.pause("专家停止");
243
+ if (!this.#failure) this.execution.save({ state: "stopping", activity: "停止" });
244
+ }
245
+ } finally { this.agent.cancel({ kind: "disposed" }); }
246
+ await this.agent.whenIdle(); await this.#pump; await this.#events;
247
+ if (this.current() && !this.#failure) this.execution.save({ state: "stopped", activity: "已停止" });
248
+ })();
249
+ return this.#stopping;
250
+ }
251
+ }