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.
- package/CONTEXT.md +13 -7
- package/README.md +46 -9
- package/README.zh-CN.md +46 -9
- package/bin/pbot.mjs +3 -0
- package/docs/README.md +4 -1
- package/docs/agent-collaboration.md +26 -12
- package/docs/architecture-guidelines.md +2 -2
- package/docs/capability-design.md +6 -4
- package/docs/dsh-implementation-plan.md +158 -0
- package/docs/dsh-support-proposal.md +314 -0
- package/docs/dsh.md +103 -0
- package/docs/file-storage.md +14 -9
- package/docs/history.md +29 -18
- package/docs/installation-design.md +226 -0
- package/docs/interaction-design.md +13 -0
- package/docs/release.md +5 -5
- package/docs/technical-design.md +29 -15
- package/docs/validation.md +53 -3
- package/lib/client.js +1071 -0
- package/lib/common/files.d.ts +2 -0
- package/lib/common/files.js +44 -0
- package/lib/common/json.d.ts +6 -0
- package/lib/common/json.js +16 -0
- package/lib/common/markdown.d.ts +5 -0
- package/lib/common/markdown.js +26 -0
- package/lib/common/package-source.js +16 -0
- package/lib/common/paths.d.ts +1 -0
- package/lib/common/paths.js +2 -0
- package/lib/common/validation.d.ts +3 -0
- package/lib/common/validation.js +14 -0
- package/lib/common/version.d.ts +3 -0
- package/lib/common/version.js +13 -0
- package/lib/dsh.d.ts +4 -0
- package/lib/dsh.js +22 -0
- package/lib/extension/dsh/agent-events.d.ts +24 -0
- package/lib/extension/dsh/agent-events.js +242 -0
- package/lib/extension/dsh/agent-runtime.d.ts +49 -0
- package/lib/extension/dsh/agent-runtime.js +348 -0
- package/lib/extension/dsh/command.d.ts +4 -0
- package/lib/extension/dsh/command.js +83 -0
- package/lib/extension/dsh/files.d.ts +5 -0
- package/lib/extension/dsh/files.js +67 -0
- package/lib/extension/dsh/history.d.ts +15 -0
- package/lib/extension/dsh/history.js +88 -0
- package/lib/extension/dsh/management.d.ts +21 -0
- package/lib/extension/dsh/management.js +217 -0
- package/lib/extension/dsh/workspace.d.ts +48 -0
- package/lib/extension/dsh/workspace.js +290 -0
- package/lib/extension/setup-cli.js +136 -0
- package/lib/extension/setup-guard.js +11 -0
- package/lib/integrations/agent-resources.d.ts +1 -0
- package/lib/integrations/agent-resources.js +18 -0
- package/lib/integrations/dsh/dsh-agents.d.ts +30 -0
- package/lib/integrations/dsh/dsh-agents.js +104 -0
- package/lib/integrations/dsh/history-summary.d.ts +11 -0
- package/lib/integrations/dsh/history-summary.js +40 -0
- package/lib/integrations/dsh/history.d.ts +16 -0
- package/lib/integrations/dsh/history.js +156 -0
- package/lib/integrations/dsh/input.d.ts +21 -0
- package/lib/integrations/dsh/input.js +62 -0
- package/lib/integrations/dsh/messages.d.ts +36 -0
- package/lib/integrations/dsh/messages.js +11 -0
- package/lib/integrations/dsh/notifications.d.ts +8 -0
- package/lib/integrations/dsh/notifications.js +55 -0
- package/lib/integrations/dsh/resources.d.ts +10 -0
- package/lib/integrations/dsh/resources.js +41 -0
- package/lib/integrations/dsh/schema.d.ts +7 -0
- package/lib/integrations/dsh/schema.js +28 -0
- package/lib/integrations/dsh/session.d.ts +14 -0
- package/lib/integrations/dsh/session.js +59 -0
- package/lib/integrations/history-summary.d.ts +2 -0
- package/lib/integrations/history-summary.js +21 -0
- package/lib/integrations/installation/pi-bridge.js +131 -0
- package/lib/integrations/installation/pi-setup.js +193 -0
- package/lib/integrations/installation/processes.js +155 -0
- package/lib/integrations/installation/workspace.js +21 -0
- package/lib/integrations/process.d.ts +14 -0
- package/lib/integrations/process.js +48 -0
- package/lib/integrations/python-activation.d.ts +2 -0
- package/lib/integrations/python-activation.js +29 -0
- package/lib/integrations/team-context.d.ts +18 -0
- package/lib/integrations/team-context.js +57 -0
- package/lib/model/agent.d.ts +92 -0
- package/lib/model/agent.js +1 -0
- package/lib/model/history.d.ts +43 -0
- package/lib/model/history.js +1 -0
- package/lib/model/team.d.ts +85 -0
- package/lib/model/team.js +1 -0
- package/lib/services/agents/agent-notices.d.ts +13 -0
- package/lib/services/agents/agent-notices.js +25 -0
- package/lib/services/agents/agent-run.d.ts +39 -0
- package/lib/services/agents/agent-run.js +138 -0
- package/lib/services/agents/agent-service.d.ts +166 -0
- package/lib/services/agents/agent-service.js +325 -0
- package/lib/services/agents/team-service.d.ts +168 -0
- package/lib/services/agents/team-service.js +343 -0
- package/lib/services/history/history-service.d.ts +103 -0
- package/lib/services/history/history-service.js +269 -0
- package/lib/services/installation/setup-service.js +121 -0
- package/lib/services/workspace/python-environment-service.d.ts +15 -0
- package/lib/services/workspace/python-environment-service.js +50 -0
- package/lib/services/workspace/workspace-files.d.ts +2 -0
- package/lib/services/workspace/workspace-files.js +17 -0
- package/lib/services/workspace/workspace-preparation.d.ts +2 -0
- package/lib/services/workspace/workspace-preparation.js +18 -0
- package/lib/storage/agent-files.d.ts +30 -0
- package/lib/storage/agent-files.js +166 -0
- package/lib/storage/atomic-json.d.ts +11 -0
- package/lib/storage/atomic-json.js +101 -0
- package/lib/storage/file-access.d.ts +2 -0
- package/lib/storage/file-access.js +26 -0
- package/lib/storage/history-files.d.ts +10 -0
- package/lib/storage/history-files.js +63 -0
- package/lib/storage/team-files.d.ts +16 -0
- package/lib/storage/team-files.js +111 -0
- package/lib/tools/agents/actions.d.ts +52 -0
- package/lib/tools/agents/actions.js +49 -0
- package/lib/tools/agents/contract.d.ts +163 -0
- package/lib/tools/agents/contract.js +56 -0
- package/lib/tools/agents/dsh-contract.d.ts +52 -0
- package/lib/tools/agents/dsh-contract.js +15 -0
- package/lib/tools/agents/dsh-tool.d.ts +5 -0
- package/lib/tools/agents/dsh-tool.js +38 -0
- package/lib/tools/history/actions.d.ts +70 -0
- package/lib/tools/history/actions.js +7 -0
- package/lib/tools/history/contract.d.ts +81 -0
- package/lib/tools/history/contract.js +32 -0
- package/lib/tools/history/dsh-tool.d.ts +4 -0
- package/lib/tools/history/dsh-tool.js +22 -0
- package/lib/tools/result.d.ts +18 -0
- package/lib/tools/result.js +10 -0
- package/lib/tools/teams/actions.d.ts +24 -0
- package/lib/tools/teams/actions.js +38 -0
- package/lib/tools/teams/contract.d.ts +232 -0
- package/lib/tools/teams/contract.js +67 -0
- package/lib/tools/teams/dsh-tool.d.ts +8 -0
- package/lib/tools/teams/dsh-tool.js +49 -0
- package/lib/ui/dsh/attachments.d.ts +5 -0
- package/lib/ui/dsh/attachments.js +39 -0
- package/lib/ui/dsh/elements.d.ts +17 -0
- package/lib/ui/dsh/elements.js +79 -0
- package/lib/ui/dsh/index.d.ts +4 -0
- package/lib/ui/dsh/index.js +13 -0
- package/lib/ui/dsh/management.d.ts +4 -0
- package/lib/ui/dsh/management.js +711 -0
- package/lib/ui/dsh/remote.d.ts +15 -0
- package/lib/ui/dsh/remote.js +11 -0
- package/lib/ui/dsh/styles.d.ts +1 -0
- package/lib/ui/dsh/styles.js +132 -0
- package/lib/ui/presentation.d.ts +3 -0
- package/lib/ui/presentation.js +12 -0
- package/lib/ui/setup.js +52 -0
- package/npm-shrinkwrap.json +11118 -783
- package/package.json +113 -9
- package/python/README.md +6 -6
- package/resources/agents/coordinator.md +1 -1
- package/resources/dsh/cordis.patch.yml +3 -0
- package/resources/installer/plugins.json +16 -0
- package/resources/prompts/workspace.md +1 -1
- package/resources/references/compute.md +1 -1
- package/resources/references/task-conductor.md +6 -4
- package/resources/skills/provider-connections/SKILL.md +10 -0
- package/scripts/README.md +13 -1
- package/src/common/package-source.ts +16 -0
- package/src/dsh.ts +22 -0
- package/src/extension/agent-events.ts +51 -46
- package/src/extension/agents-command.ts +48 -34
- package/src/extension/command.ts +42 -23
- package/src/extension/dsh/agent-events.ts +148 -0
- package/src/extension/dsh/agent-runtime.ts +251 -0
- package/src/extension/dsh/command.ts +78 -0
- package/src/extension/dsh/files.ts +61 -0
- package/src/extension/dsh/history.ts +69 -0
- package/src/extension/dsh/management.ts +108 -0
- package/src/extension/dsh/workspace.ts +206 -0
- package/src/extension/events.ts +6 -4
- package/src/extension/history.ts +15 -12
- package/src/extension/setup-cli.ts +109 -0
- package/src/extension/setup-guard.ts +12 -0
- package/src/extension/subagent-entry.ts +34 -63
- package/src/extension/teams-command.ts +53 -46
- package/src/integrations/{pi/agent-resources.ts → agent-resources.ts} +2 -2
- package/src/integrations/dsh/dsh-agents.ts +97 -0
- package/src/integrations/dsh/history-summary.ts +43 -0
- package/src/integrations/dsh/history.ts +76 -0
- package/src/integrations/dsh/input.ts +66 -0
- package/src/integrations/dsh/messages.ts +22 -0
- package/src/integrations/dsh/notifications.ts +46 -0
- package/src/integrations/dsh/resources.ts +44 -0
- package/src/integrations/dsh/schema.ts +26 -0
- package/src/integrations/dsh/session.ts +67 -0
- package/src/integrations/history-summary.ts +20 -0
- package/src/integrations/installation/pi-bridge.ts +107 -0
- package/src/integrations/installation/pi-setup.ts +162 -0
- package/src/integrations/installation/processes.ts +124 -0
- package/src/integrations/installation/workspace.ts +18 -0
- package/src/integrations/pi/history.ts +4 -14
- package/src/integrations/pi/pi-agents.ts +17 -6
- package/src/integrations/pi/team-context.ts +6 -58
- package/src/integrations/team-context.ts +60 -0
- package/src/model/agent.ts +2 -0
- package/src/model/history.ts +3 -1
- package/src/pbot.ts +1 -1
- package/src/services/agents/agent-notices.ts +28 -0
- package/src/services/agents/agent-run.ts +114 -0
- package/src/services/agents/agent-service.ts +127 -64
- package/src/services/history/history-service.ts +120 -59
- package/src/services/installation/setup-service.ts +145 -0
- package/src/storage/agent-files.ts +17 -4
- package/src/storage/history-files.ts +30 -5
- package/src/tools/agents/actions.ts +47 -0
- package/src/tools/agents/contract.ts +8 -5
- package/src/tools/agents/dsh-contract.ts +16 -0
- package/src/tools/agents/dsh-tool.ts +41 -0
- package/src/tools/agents/tool.ts +7 -43
- package/src/tools/history/actions.ts +9 -0
- package/src/tools/history/contract.ts +34 -0
- package/src/tools/history/dsh-tool.ts +22 -0
- package/src/tools/history/tool.ts +7 -30
- package/src/tools/teams/actions.ts +38 -0
- package/src/tools/teams/contract.ts +7 -3
- package/src/tools/teams/dsh-tool.ts +55 -0
- package/src/tools/teams/tool.ts +6 -31
- package/src/ui/agent-display.ts +7 -4
- package/src/ui/dsh/attachments.ts +42 -0
- package/src/ui/dsh/elements.ts +50 -0
- package/src/ui/dsh/index.ts +18 -0
- package/src/ui/dsh/management.ts +412 -0
- package/src/ui/dsh/remote.ts +20 -0
- package/src/ui/dsh/styles.ts +132 -0
- package/src/ui/pi/menu.ts +37 -0
- package/src/ui/pi/reader.ts +46 -0
- package/src/ui/presentation.ts +12 -0
- 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),
|
|
13
|
-
if (ctx.mode !== "tui") { ctx.ui.notify(JSON.stringify(
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
}
|
package/src/extension/command.ts
CHANGED
|
@@ -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: "
|
|
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
|
-
|
|
23
|
-
await ctx.waitForIdle();
|
|
25
|
+
async function execute(command: string | undefined, argument: string) {
|
|
24
26
|
const instance = lifecycle.get();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
+
}
|