personal-bot 0.2.4 → 0.2.6
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 +9 -7
- package/README.md +15 -8
- package/README.zh-CN.md +15 -8
- package/docs/README.md +3 -1
- package/docs/agent-collaboration.md +27 -13
- 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 +3 -1
- package/docs/interaction-design.md +13 -0
- package/docs/release.md +3 -3
- package/docs/subagent-upgrade-proposal.md +3 -3
- package/docs/technical-design.md +29 -15
- package/docs/validation.md +49 -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/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/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/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/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/npm-shrinkwrap.json +11123 -793
- package/package.json +94 -11
- package/python/README.md +6 -6
- package/resources/agents/coordinator.md +1 -1
- package/resources/dsh/cordis.patch.yml +3 -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 +9 -1
- 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/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/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/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
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { existsSync, realpathSync } from "node:fs";
|
|
4
|
-
import { randomUUID } from "node:crypto";
|
|
5
4
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { AgentFiles, descriptor, launch
|
|
5
|
+
import { AgentFiles, descriptor, launch } from "../storage/agent-files.js";
|
|
6
|
+
import { AgentRun } from "../services/agents/agent-run.js";
|
|
7
7
|
import { TeamFiles } from "../storage/team-files.js";
|
|
8
8
|
import { TeamService } from "../services/agents/team-service.js";
|
|
9
9
|
import { teamMessageTool } from "../tools/teams/tool.js";
|
|
10
|
-
import {
|
|
10
|
+
import { teamContext } from "../integrations/pi/team-context.js";
|
|
11
|
+
import { teamBrief } from "../integrations/team-context.js";
|
|
11
12
|
import { readAgentSession } from "../integrations/pi/pi-agents.js";
|
|
12
13
|
import type { TeamLink } from "../model/team.js";
|
|
13
14
|
import { PythonEnvironmentService } from "../services/workspace/python-environment-service.js";
|
|
14
15
|
import { activatePython } from "../integrations/python-activation.js";
|
|
15
16
|
import { authorizeMutation } from "../services/workspace/workspace-files.js";
|
|
16
|
-
import { agentText } from "../integrations/
|
|
17
|
+
import { agentText } from "../integrations/agent-resources.js";
|
|
17
18
|
import { agentInput } from "../integrations/pi/agent-input.js";
|
|
18
19
|
import { agentFileTools } from "../integrations/pi/agent-tools.js";
|
|
19
20
|
import { resourceRoot } from "../common/paths.js";
|
|
20
21
|
import { requireThat } from "../common/validation.js";
|
|
21
22
|
import { packageVersion, releaseLine } from "../common/version.js";
|
|
22
|
-
import type { AgentCycle, AgentStatus } from "../model/agent.js";
|
|
23
23
|
|
|
24
24
|
/** Explicit child composition; never installs main commands or approval capabilities. */
|
|
25
25
|
export default function subagent(pi: ExtensionAPI) {
|
|
@@ -36,19 +36,10 @@ export default function subagent(pi: ExtensionAPI) {
|
|
|
36
36
|
let ready = false, disposed = false;
|
|
37
37
|
let ctx: ExtensionContext | undefined, timer: ReturnType<typeof setInterval> | undefined;
|
|
38
38
|
let python: PythonEnvironmentService | undefined, restorePython: (() => void) | undefined;
|
|
39
|
-
let processing = false, closing = false,
|
|
40
|
-
|
|
41
|
-
const accepted = new Set<string>();
|
|
42
|
-
const saved = files.optional(id, "status.json", status);
|
|
43
|
-
let snapshot: AgentStatus = { generation, state: "starting", activity: "启动", updatedAt: Date.now() };
|
|
44
|
-
if (saved?.generation === generation) for (const requestId of saved.accepted ?? []) accepted.add(requestId);
|
|
39
|
+
let processing = false, closing = false, startLeaf: string | null = null;
|
|
40
|
+
const execution = new AgentRun(files, id, generation);
|
|
45
41
|
let lastActivity = 0;
|
|
46
|
-
const save = (
|
|
47
|
-
snapshot = { ...snapshot, ...patch, updatedAt: Date.now() };
|
|
48
|
-
if (current) snapshot.cycle = structuredClone(current);
|
|
49
|
-
snapshot.accepted = [...accepted];
|
|
50
|
-
files.write(id, "status.json", snapshot, status);
|
|
51
|
-
};
|
|
42
|
+
const save = execution.save.bind(execution);
|
|
52
43
|
const ensure = () => {
|
|
53
44
|
lifetime.signal.throwIfAborted(); files.check();
|
|
54
45
|
const run = files.read(id, "launch.json", launch);
|
|
@@ -66,18 +57,16 @@ export default function subagent(pi: ExtensionAPI) {
|
|
|
66
57
|
catch (error) { if ((error as NodeJS.ErrnoException).code !== "PBOT_CONTROL_BUSY") throw error; }
|
|
67
58
|
};
|
|
68
59
|
const finish = (aborted: boolean) => {
|
|
69
|
-
if (!ctx || !current) return;
|
|
60
|
+
if (!ctx || !execution.current) return;
|
|
70
61
|
const branch = ctx.sessionManager.getBranch();
|
|
71
62
|
const at = startLeaf === null ? -1 : branch.findIndex(e => e.id === startLeaf);
|
|
72
63
|
const last = branch.slice(at + 1).filter(e => e.type === "message" && e.message.role === "assistant").at(-1);
|
|
73
64
|
const assistant = last?.type === "message" && last.message.role === "assistant" ? last.message : undefined;
|
|
74
65
|
const text = assistant?.content.filter(p => p.type === "text").map(p => p.text).join("\n") ?? "";
|
|
75
|
-
|
|
76
|
-
state: aborted ? "cancelled" : assistant?.stopReason === "stop"
|
|
77
|
-
...(!assistant || assistant.stopReason !== "stop" ? { error: assistant?.errorMessage ?? "没有有效最终回答。" } : {}) };
|
|
78
|
-
|
|
79
|
-
save({ state: "idle", activity: current.state });
|
|
80
|
-
current = undefined; activeTeam = undefined;
|
|
66
|
+
execution.finish({ position: assistant ? ctx.sessionManager.getLeafId() : null, answer: text,
|
|
67
|
+
state: aborted ? "cancelled" : assistant?.stopReason === "stop" ? "completed" : "failed",
|
|
68
|
+
...(!assistant || assistant.stopReason !== "stop" ? { error: assistant?.errorMessage ?? "没有有效最终回答。" } : {}) });
|
|
69
|
+
activeTeam = undefined;
|
|
81
70
|
};
|
|
82
71
|
const shutdown = async () => {
|
|
83
72
|
if (closing) return;
|
|
@@ -105,33 +94,31 @@ export default function subagent(pi: ExtensionAPI) {
|
|
|
105
94
|
if (activeTeam) {
|
|
106
95
|
if (Date.now() >= activeTeam.run.deadlineAt) {
|
|
107
96
|
pauseTeam("已到期限");
|
|
108
|
-
if (!interactive && current?.source !== "mixed") {
|
|
97
|
+
if (!execution.interactive && execution.current?.source !== "mixed") {
|
|
109
98
|
ctx.abort();
|
|
110
|
-
if (!current && ctx.isIdle()) await shutdown();
|
|
99
|
+
if (!execution.current && ctx.isIdle()) await shutdown();
|
|
111
100
|
}
|
|
112
101
|
}
|
|
113
102
|
return;
|
|
114
103
|
}
|
|
115
104
|
const links = teams.links();
|
|
116
105
|
for (const request of files.messages(id)) {
|
|
117
|
-
if (request.generation !== generation || accepted
|
|
106
|
+
if (request.generation !== generation || execution.accepted(request.requestId)) continue;
|
|
118
107
|
const link = links.get(request.requestId);
|
|
119
108
|
if (link && (link.team.closed || link.run.state !== "running" || link.run.deadlineAt <= Date.now())) continue;
|
|
120
|
-
if (link && (!ctx.isIdle() || ctx.hasPendingMessages() || current || interactive ||
|
|
109
|
+
if (link && (!ctx.isIdle() || ctx.hasPendingMessages() || execution.current || execution.interactive || execution.pending)) break;
|
|
121
110
|
const content = await agentInput(d.cwd, request, lifetime.signal);
|
|
122
111
|
requireThat(ctx.model, "子会话没有可用模型。");
|
|
123
112
|
requireThat(ctx.modelRegistry.hasConfiguredAuth(ctx.model) || await ctx.modelRegistry.getProviderAuth(ctx.model.provider), "子会话模型未认证,请配置原生 Pi 认证后显式恢复。");
|
|
124
113
|
ensure();
|
|
125
114
|
const accept = () => {
|
|
126
115
|
bindSession();
|
|
127
|
-
accepted.add(request.requestId);
|
|
128
116
|
pi.appendEntry("pbot.agent-input", { requestId: request.requestId, generation });
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
save({ state: "busy", activity: "消息已接收" });
|
|
117
|
+
execution.claim(request.requestId);
|
|
118
|
+
execution.consume(request.requestId);
|
|
132
119
|
};
|
|
133
120
|
if (link) {
|
|
134
|
-
if (!ctx.isIdle() || ctx.hasPendingMessages() || current || interactive ||
|
|
121
|
+
if (!ctx.isIdle() || ctx.hasPendingMessages() || execution.current || execution.interactive || execution.pending) break;
|
|
135
122
|
activeTeam = teams.claim(id, generation, request.requestId, accept);
|
|
136
123
|
if (!activeTeam) continue;
|
|
137
124
|
} else accept();
|
|
@@ -146,7 +133,7 @@ export default function subagent(pi: ExtensionAPI) {
|
|
|
146
133
|
};
|
|
147
134
|
pi.registerTool({ ...teamMessageTool(teams, { check: ensure }, () => ({
|
|
148
135
|
kind: "member", agentId: id, generation,
|
|
149
|
-
...(activeTeam && current?.source === "parent" ? { requestId: activeTeam.request.requestId } : {}),
|
|
136
|
+
...(activeTeam && execution.current?.source === "parent" ? { requestId: activeTeam.request.requestId } : {}),
|
|
150
137
|
})), namespace: { name: "pbot", description: "团队公共通信,不提供管理或递归委派。" } });
|
|
151
138
|
pi.on("resources_discover", async () => {
|
|
152
139
|
if (d.options.noSkills) return {};
|
|
@@ -171,22 +158,12 @@ export default function subagent(pi: ExtensionAPI) {
|
|
|
171
158
|
(previous === null || previous === file), "子会话身份、工作目录或文件不匹配。");
|
|
172
159
|
// Native setup-only sessions have a path but no file. Do not manufacture a conversation to persist it.
|
|
173
160
|
if (previous !== null || existsSync(file)) { readAgentSession(d, file, true); bindSession(); }
|
|
161
|
+
const receipts: { requestId: string; generation: string }[] = [];
|
|
174
162
|
for (const entry of ctx.sessionManager.getEntries()) if (entry.type === "custom" && entry.customType === "pbot.agent-input") {
|
|
175
|
-
const receipt = entry.data as { requestId?: string
|
|
176
|
-
|
|
177
|
-
if (saved?.generation === generation && saved.cycle?.state === "running") {
|
|
178
|
-
const interrupted: AgentCycle = { ...saved.cycle, state: "interrupted", finishedAt: Date.now() };
|
|
179
|
-
if (!files.completions(id).some(c => c.cycleId === interrupted.cycleId)) files.write(id, "completions/" + interrupted.cycleId + ".json", interrupted, cycle, true);
|
|
180
|
-
}
|
|
181
|
-
// A receipt without a cycle is uncertain, not permission to replay after reload.
|
|
182
|
-
for (const link of teams.links().values()) {
|
|
183
|
-
const q = link.request;
|
|
184
|
-
if (q.agentId !== id || q.generation !== generation || !accepted.has(q.requestId) ||
|
|
185
|
-
files.completions(id).some(c => c.generation === generation && c.requestIds.includes(q.requestId))) continue;
|
|
186
|
-
const interrupted: AgentCycle = { cycleId: q.requestId, generation, startedAt: files.read(id, "launch.json", launch).startedAt,
|
|
187
|
-
finishedAt: Date.now(), state: "interrupted", leaf: null, requestIds: [q.requestId], source: "parent", error: "领取后执行结果不明;未重跑。" };
|
|
188
|
-
files.write(id, "completions/" + interrupted.cycleId + ".json", interrupted, cycle, true);
|
|
163
|
+
const receipt = entry.data as { requestId?: string; generation?: string };
|
|
164
|
+
if (typeof receipt.requestId === "string" && typeof receipt.generation === "string") receipts.push({ requestId: receipt.requestId, generation: receipt.generation });
|
|
189
165
|
}
|
|
166
|
+
execution.recover(receipts);
|
|
190
167
|
const active = pi.getActiveTools();
|
|
191
168
|
for (const tool of agentFileTools(d.cwd, lifetime.signal, async (path, change, cancel) => {
|
|
192
169
|
ensure();
|
|
@@ -220,7 +197,7 @@ export default function subagent(pi: ExtensionAPI) {
|
|
|
220
197
|
});
|
|
221
198
|
event.systemPromptOptions.sections.pbot_workspace = await readFile(join(resourceRoot, "prompts/workspace.md"), "utf8");
|
|
222
199
|
event.systemPromptOptions.sections.pbot_child = await agentText("general-purpose", d.workspace);
|
|
223
|
-
if (activeTeam && !interactive && current?.source !== "mixed") {
|
|
200
|
+
if (activeTeam && !execution.interactive && execution.current?.source !== "mixed") {
|
|
224
201
|
const link = teams.links().get(activeTeam.request.requestId);
|
|
225
202
|
requireThat(link, "团队关联不可读取。");
|
|
226
203
|
event.systemPromptOptions.sections.pbot_team = teamBrief(link);
|
|
@@ -230,7 +207,7 @@ export default function subagent(pi: ExtensionAPI) {
|
|
|
230
207
|
return;
|
|
231
208
|
});
|
|
232
209
|
pi.on("turn_end", (event, context) => {
|
|
233
|
-
if (!activeTeam || current?.source !== "parent" || event.outcome === "aborted") return;
|
|
210
|
+
if (!activeTeam || execution.current?.source !== "parent" || event.outcome === "aborted") return;
|
|
234
211
|
const link = teams.links().get(activeTeam.request.requestId);
|
|
235
212
|
if (!link) return;
|
|
236
213
|
const message = teamContext(link, context);
|
|
@@ -246,34 +223,28 @@ export default function subagent(pi: ExtensionAPI) {
|
|
|
246
223
|
return { action: "handled" };
|
|
247
224
|
}
|
|
248
225
|
if (event.source !== "extension") {
|
|
249
|
-
|
|
250
|
-
else interactive = true;
|
|
251
|
-
save({ state: "busy", activity: "人工输入" });
|
|
226
|
+
execution.humanInput();
|
|
252
227
|
pauseTeam("成员由人工接管");
|
|
253
228
|
}
|
|
254
229
|
return;
|
|
255
230
|
});
|
|
256
231
|
pi.on("agent_start", (_event, context) => {
|
|
257
232
|
ctx = context;
|
|
258
|
-
if (current)
|
|
259
|
-
|
|
260
|
-
current = { cycleId: randomUUID(), generation, startedAt: Date.now(), state: "running", leaf: null,
|
|
261
|
-
requestIds: queuedRequests, source: interactive ? queuedRequests.length ? "mixed" : "interactive" : "parent" };
|
|
262
|
-
queuedRequests = []; interactive = false;
|
|
263
|
-
save({ state: "busy", activity: "思考" });
|
|
233
|
+
if (!execution.current) startLeaf = ctx.sessionManager.getLeafId();
|
|
234
|
+
execution.begin();
|
|
264
235
|
});
|
|
265
236
|
pi.on("tool_call", event => {
|
|
266
237
|
try {
|
|
267
238
|
ensure();
|
|
268
239
|
requireThat(event.toolName !== "pbot_agent" && event.toolName !== "pbot_team", "子会话禁止管理和委派。");
|
|
269
|
-
if (activeTeam && current?.source === "parent" && Date.now() >= activeTeam.run.deadlineAt) {
|
|
240
|
+
if (activeTeam && execution.current?.source === "parent" && Date.now() >= activeTeam.run.deadlineAt) {
|
|
270
241
|
ctx?.abort(); throw new Error("团队请求已到期限。");
|
|
271
242
|
}
|
|
272
243
|
save({ state: "busy", activity: event.toolName });
|
|
273
244
|
} catch (error) { return { block: true, reason: String(error) }; }
|
|
274
245
|
return;
|
|
275
246
|
});
|
|
276
|
-
pi.on("message_update", () => { if (current && Date.now() - lastActivity > 1000) { lastActivity = Date.now(); save({ state: "busy", activity: "回答" }); } });
|
|
247
|
+
pi.on("message_update", () => { if (execution.current && Date.now() - lastActivity > 1000) { lastActivity = Date.now(); save({ state: "busy", activity: "回答" }); } });
|
|
277
248
|
pi.on("agent_settled", (event, context) => { ctx = context; finish(event.aborted); });
|
|
278
249
|
pi.on("session_before_switch", () => ({ cancel: true }));
|
|
279
250
|
pi.on("session_before_fork", () => ({ cancel: true }));
|
|
@@ -285,8 +256,8 @@ export default function subagent(pi: ExtensionAPI) {
|
|
|
285
256
|
try {
|
|
286
257
|
teams.files.setReady(id, generation, false); ready = false;
|
|
287
258
|
pauseTeam("子会话关闭或重新载入");
|
|
288
|
-
if (current) finish(true);
|
|
289
|
-
if (event.reason !== "reload" &&
|
|
259
|
+
if (execution.current) finish(true);
|
|
260
|
+
if (event.reason !== "reload" && execution.state !== "error") save({ state: "stopped", activity: "已停止" });
|
|
290
261
|
} finally {
|
|
291
262
|
try { await python?.close(); } finally { restorePython?.(); teams.dispose(); ctx = undefined; }
|
|
292
263
|
}
|
|
@@ -3,58 +3,65 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-c
|
|
|
3
3
|
import { agentOwner } from "../integrations/pi/agent-context.js";
|
|
4
4
|
import type { WorkspaceInstance } from "./lifecycle.js";
|
|
5
5
|
import type { Confirmations } from "../ui/confirmation.js";
|
|
6
|
+
import { readPage } from "../ui/pi/reader.js";
|
|
7
|
+
import { choose } from "../ui/pi/menu.js";
|
|
8
|
+
import { stateLabel } from "../ui/presentation.js";
|
|
6
9
|
import { showAgents } from "./agents-command.js";
|
|
7
10
|
|
|
8
11
|
export async function showTeams(pi: ExtensionAPI, instance: WorkspaceInstance, ctx: ExtensionCommandContext, confirmations: Confirmations) {
|
|
9
12
|
instance.check();
|
|
10
13
|
const owner = () => agentOwner(ctx), views = () => instance.agents.list(owner()), service = instance.teams;
|
|
11
14
|
service.recover(owner());
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
if (confirmations.presenting) { ctx.ui.notify("请先处理主会话批准。", "info"); return; }
|
|
15
|
-
if (!values.length) { ctx.ui.notify("没有团队;先用 pbot_agent start 创建成员,再用 pbot_team create 组队。", "info"); return; }
|
|
16
|
-
const options = values.map(v => v.name + (v.owned ? " · " + (v.closed ? "closed" : v.run?.state ?? "open") + " · 在途 " + v.activeCount : " · 其他分支"));
|
|
17
|
-
const selected = await ctx.ui.select("pbot 团队", options), value = values[options.indexOf(selected ?? "")];
|
|
18
|
-
if (!value) return;
|
|
19
|
-
if (!value.owned) { ctx.ui.notify("请返回所属分支操作。", "info"); return; }
|
|
20
|
-
const action = await ctx.ui.select(value.name, ["状态", "公共记录", "发布信息", "新批次", "暂停", "关闭", "成员 TUI/报告"]);
|
|
21
|
-
instance.check();
|
|
22
|
-
if (action === "状态") { ctx.ui.notify(JSON.stringify(service.status(value.teamId, owner(), views()), null, 2), "info"); return; }
|
|
23
|
-
if (action === "成员 TUI/报告") { await showAgents(pi, instance, ctx, confirmations, value.members.map(m => m.agentId)); return; }
|
|
24
|
-
if (action === "暂停") { service.pause(value.teamId, owner()); return; }
|
|
25
|
-
if (action === "关闭") { service.close(value.teamId, owner(), views()); return; }
|
|
26
|
-
if (action === "发布信息") {
|
|
27
|
-
const text = await ctx.ui.editor("公开给全体成员(不会唤醒模型)");
|
|
28
|
-
instance.check();
|
|
29
|
-
if (text?.trim()) service.post(value.teamId, { kind: "user", owner: owner() }, randomUUID(), text);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (action === "新批次") {
|
|
33
|
-
const mode = await ctx.ui.select("协作策略", ["roundRobin", "parallel"]);
|
|
34
|
-
if (mode !== "roundRobin" && mode !== "parallel") return;
|
|
35
|
-
const instruction = await ctx.ui.editor("本批次要求;不要把新批次当作未知外部操作的重试");
|
|
36
|
-
if (!instruction?.trim()) return;
|
|
37
|
-
const rounds = await ctx.ui.input("轮数 1–10;期限默认 15 分钟", "2");
|
|
38
|
-
if (rounds === undefined) return;
|
|
39
|
-
instance.check();
|
|
40
|
-
service.run(value.teamId, owner(), { instruction, mode, rounds: rounds.trim() ? Number(rounds) : 2 }, views());
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (action !== "公共记录") return;
|
|
44
|
-
let page: { afterSeq?: number; seq?: number; offset?: number } = {};
|
|
15
|
+
if (ctx.mode !== "tui") { ctx.ui.notify(JSON.stringify({ teams: service.list(owner(), views()) }), "info"); return; }
|
|
16
|
+
if (confirmations.presenting) { ctx.ui.notify("请先处理待确认操作。", "info"); return; }
|
|
45
17
|
for (;;) {
|
|
46
|
-
instance.check();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
18
|
+
instance.check(); const values = service.list(owner(), views());
|
|
19
|
+
if (!values.length) { ctx.ui.notify("还没有团队。请主助手为已有专家组队。", "info"); return; }
|
|
20
|
+
const options = values.map(v => v.name + " · " + (v.owned ? stateLabel(v.closed ? "closed" : v.run?.state ?? "open") + " · 在途 " + v.activeCount : "其他分支"));
|
|
21
|
+
const selected = await choose(ctx, "团队", options), picked = values[options.indexOf(selected ?? "")]; if (!picked) return;
|
|
22
|
+
if (!picked.owned) { ctx.ui.notify("请返回所属分支操作。", "info"); continue; }
|
|
23
|
+
for (;;) {
|
|
24
|
+
instance.check(); const value = service.status(picked.teamId, owner(), views()); if (!value.owned) break;
|
|
25
|
+
const action = await choose(ctx, value.name + " · " + stateLabel(value.closed ? "closed" : value.run?.state ?? "open"),
|
|
26
|
+
["状态", "公共记录", "成员会话", ...(!value.closed ? ["发布消息", ...(value.run?.state === "running" ? ["暂停"] : ["新批次"]), "关闭团队"] : [])]);
|
|
27
|
+
if (!action) break;
|
|
28
|
+
instance.check();
|
|
29
|
+
try {
|
|
30
|
+
if (action === "状态") {
|
|
31
|
+
const run = value.run;
|
|
32
|
+
await readPage(ctx, value.name, [value.brief, "", ...value.members.map(member => `${member.name} · ${member.role} · ${stateLabel(member.state)}`), "",
|
|
33
|
+
...(run ? [`批次 ${stateLabel(run.state)} · ${run.mode === "parallel" ? "并行" : "轮流"} · ${run.rounds} 轮`,
|
|
34
|
+
`进展 已安排 ${run.scheduled} · 已交付 ${run.published} · 在途 ${value.activeCount}`, ...(run.reason ? [run.reason] : [])] : ["尚无批次"])].join("\n"));
|
|
35
|
+
} else if (action === "成员会话") await showAgents(pi, instance, ctx, confirmations, value.members.map(m => m.agentId));
|
|
36
|
+
else if (action === "暂停") { service.pause(value.teamId, owner()); ctx.ui.notify("已暂停新安排。", "info"); }
|
|
37
|
+
else if (action === "关闭团队") { service.close(value.teamId, owner(), views()); ctx.ui.notify("团队已关闭。", "info"); }
|
|
38
|
+
else if (action === "发布消息") {
|
|
39
|
+
const text = await ctx.ui.editor("公共消息"); instance.check();
|
|
40
|
+
if (text?.trim()) { service.post(value.teamId, { kind: "user", owner: owner() }, randomUUID(), text); ctx.ui.notify("消息已发布。", "info"); }
|
|
41
|
+
} else if (action === "新批次") {
|
|
42
|
+
const strategy = await ctx.ui.select("讨论模式", ["轮流讨论", "并行轮次"]); if (!strategy) continue;
|
|
43
|
+
const instruction = await ctx.ui.editor("任务与交付要求"); if (!instruction?.trim()) continue;
|
|
44
|
+
const rounds = await ctx.ui.input("轮数(1–10,默认 2)"); if (rounds === undefined) continue;
|
|
45
|
+
instance.check(); service.run(value.teamId, owner(), { instruction, mode: strategy === "并行轮次" ? "parallel" : "roundRobin", rounds: rounds.trim() ? Number(rounds) : 2 }, views());
|
|
46
|
+
ctx.ui.notify("新批次已开始。", "info");
|
|
47
|
+
} else if (action === "公共记录") await messages(value.teamId);
|
|
48
|
+
} catch (error) { ctx.ui.notify(error instanceof Error ? error.message : String(error), "error"); }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function messages(teamId: string) {
|
|
52
|
+
let page: { afterSeq?: number; seq?: number; offset?: number } = {};
|
|
53
|
+
for (;;) {
|
|
54
|
+
instance.check(); const data = service.read(teamId, { kind: "user", owner: owner() }, page);
|
|
55
|
+
await readPage(ctx, "公共记录", data.messages.map(message => `#${message.seq} ${message.author.name}${message.replyTo ? ` → #${message.replyTo}` : ""}\n${message.text}` +
|
|
56
|
+
(message.nextOffset === null ? "" : `\n[未完 · ${message.nextOffset}/${message.totalLength}]`)).join("\n\n") || "尚无消息。");
|
|
57
|
+
const last = data.messages.at(-1);
|
|
58
|
+
if (last?.nextOffset !== null && last?.nextOffset !== undefined) {
|
|
59
|
+
if (await ctx.ui.select("公共记录", ["继续本条", "返回"]) !== "继续本条") return;
|
|
60
|
+
page = { seq: last.seq, offset: last.nextOffset };
|
|
61
|
+
} else if (last && last.seq < data.lastSeq) {
|
|
62
|
+
if (await ctx.ui.select("公共记录", ["后续消息", "返回"]) !== "后续消息") return;
|
|
63
|
+
page = { afterSeq: last.seq };
|
|
64
|
+
} else return;
|
|
65
|
+
}
|
|
59
66
|
}
|
|
60
67
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { resourceRoot } from "
|
|
4
|
-
import { requireThat } from "
|
|
3
|
+
import { resourceRoot } from "../common/paths.js";
|
|
4
|
+
import { requireThat } from "../common/validation.js";
|
|
5
5
|
|
|
6
6
|
export async function agentText(agent: "coordinator" | "general-purpose", root: string) {
|
|
7
7
|
requireThat(agent === "coordinator" || agent === "general-purpose", "未知职责。");
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import type { Agent, AgentHandle, AgentSetup } from "@deepseek-ai/dsh-agent";
|
|
3
|
+
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
4
|
+
import type { AgentDescriptor, AgentSessionReference } from "../../model/agent.js";
|
|
5
|
+
import type { AgentHost } from "../../services/agents/agent-service.js";
|
|
6
|
+
import { requireThat } from "../../common/validation.js";
|
|
7
|
+
import { openDshAgent, readDshAgentSession, readDshReport } from "./session.js";
|
|
8
|
+
|
|
9
|
+
type NativeRun = {
|
|
10
|
+
generation: string;
|
|
11
|
+
controller: AbortController;
|
|
12
|
+
opening: Promise<AgentHandle>;
|
|
13
|
+
handle?: AgentHandle;
|
|
14
|
+
closing?: Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Native handles belong to the execution component, independently of browser observers. */
|
|
18
|
+
export class DshAgents implements AgentHost {
|
|
19
|
+
readonly kind = "dsh";
|
|
20
|
+
readonly #runs = new Map<string, NativeRun>();
|
|
21
|
+
#closed = false;
|
|
22
|
+
constructor(readonly ctx: Context, readonly parent: Agent,
|
|
23
|
+
readonly composition: {
|
|
24
|
+
setup(d: AgentDescriptor, directory: string, generation: string): AgentSetup;
|
|
25
|
+
ready(agent: Agent, generation: string): void;
|
|
26
|
+
stopping(agent: Agent, generation: string): Promise<void>;
|
|
27
|
+
}, readonly lifetime: AbortSignal) {}
|
|
28
|
+
available() {
|
|
29
|
+
this.lifetime.throwIfAborted();
|
|
30
|
+
requireThat(!this.#closed && this.ctx.agents.get(this.parent.id) === this.parent, "dsh 父会话或执行组件已关闭。");
|
|
31
|
+
}
|
|
32
|
+
alive(id: string) {
|
|
33
|
+
const run = this.#runs.get(id);
|
|
34
|
+
return Boolean(this.ctx.agents.get(SessionId(id))) || Boolean(run && (run.closing || !run.handle));
|
|
35
|
+
}
|
|
36
|
+
private reference(d: AgentDescriptor, reference: AgentSessionReference) {
|
|
37
|
+
requireThat(reference.host === "dsh" && reference.sessionId === d.agentId, "不能接管其他宿主或其他身份的原生会话。");
|
|
38
|
+
}
|
|
39
|
+
async validateSession(d: AgentDescriptor, reference: AgentSessionReference) {
|
|
40
|
+
this.available(); this.reference(d, reference);
|
|
41
|
+
requireThat(!this.alive(d.agentId), "原生专家仍被占用,不能恢复。");
|
|
42
|
+
await readDshAgentSession(this.ctx, d);
|
|
43
|
+
requireThat(!this.alive(d.agentId), "原生专家仍被占用,不能恢复。");
|
|
44
|
+
}
|
|
45
|
+
async launch(d: AgentDescriptor, directory: string, generation: string, reference: AgentSessionReference, resume: boolean, signal: AbortSignal) {
|
|
46
|
+
this.available(); this.reference(d, reference);
|
|
47
|
+
requireThat(!this.alive(d.agentId), "dsh 专家仍在运行或释放中。");
|
|
48
|
+
const previous = this.#runs.get(d.agentId);
|
|
49
|
+
if (previous) await this.release(d.agentId, previous.generation);
|
|
50
|
+
signal.throwIfAborted(); this.available();
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const combined = AbortSignal.any([signal, this.lifetime, controller.signal]);
|
|
53
|
+
const opening = Promise.resolve().then(() => openDshAgent(this.ctx, d, this.parent, resume,
|
|
54
|
+
this.composition.setup(d, directory, generation), combined));
|
|
55
|
+
const run: NativeRun = { generation, controller, opening };
|
|
56
|
+
this.#runs.set(d.agentId, run);
|
|
57
|
+
try {
|
|
58
|
+
run.handle = await opening;
|
|
59
|
+
if (combined.aborted) {
|
|
60
|
+
await this.release(d.agentId, generation);
|
|
61
|
+
combined.throwIfAborted();
|
|
62
|
+
}
|
|
63
|
+
this.composition.ready(run.handle.agent, generation);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (!run.handle && this.#runs.get(d.agentId) === run) this.#runs.delete(d.agentId);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
release(id: string, generation: string) {
|
|
70
|
+
const run = this.#runs.get(id);
|
|
71
|
+
if (!run) return this.ctx.agents.get(SessionId(id)) ? Promise.reject(new Error("原生专家仍由其他执行实例持有;当前组件不能释放。")) : Promise.resolve();
|
|
72
|
+
if (run.generation !== generation) return Promise.resolve();
|
|
73
|
+
if (run.closing) return run.closing;
|
|
74
|
+
run.controller.abort();
|
|
75
|
+
run.closing = (async () => {
|
|
76
|
+
const handle = run.handle ?? await run.opening.catch(() => undefined);
|
|
77
|
+
let failure: unknown;
|
|
78
|
+
if (handle) {
|
|
79
|
+
try { await this.composition.stopping(handle.agent, generation); } catch (error) { failure = error; }
|
|
80
|
+
await handle.dispose();
|
|
81
|
+
}
|
|
82
|
+
if (this.#runs.get(id) === run) this.#runs.delete(id);
|
|
83
|
+
if (failure) throw failure;
|
|
84
|
+
})();
|
|
85
|
+
return run.closing;
|
|
86
|
+
}
|
|
87
|
+
report(d: AgentDescriptor, reference: AgentSessionReference, position: string) {
|
|
88
|
+
this.reference(d, reference);
|
|
89
|
+
return readDshReport(this.ctx, d, position);
|
|
90
|
+
}
|
|
91
|
+
async close() {
|
|
92
|
+
this.#closed = true;
|
|
93
|
+
const results = await Promise.allSettled([...this.#runs].map(([id, run]) => this.release(id, run.generation)));
|
|
94
|
+
const errors = results.flatMap(r => r.status === "rejected" ? [r.reason] : []);
|
|
95
|
+
if (errors.length) throw new AggregateError(errors, "dsh 专家仍有未释放的执行资源。");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
4
|
+
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
5
|
+
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
6
|
+
import { BlockAssembler, type PreparedLlmCall } from "@deepseek-ai/dsh-llm";
|
|
7
|
+
import type { HistorySummarizer } from "../../model/history.js";
|
|
8
|
+
import { resourceRoot } from "../../common/paths.js";
|
|
9
|
+
import { requireThat } from "../../common/validation.js";
|
|
10
|
+
import { reduceHistorySummary } from "../history-summary.js";
|
|
11
|
+
|
|
12
|
+
declare module "@deepseek-ai/dsh-llm" {
|
|
13
|
+
interface LlmRequestPurposeMap { "pbot-history": true }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Independent text calls through the host's routing and authentication; no Agent or Session messages. */
|
|
17
|
+
export function dshHistorySummarizer(ctx: Context, selection: ReturnType<Agent["model"]["current"]>, sessionId: SessionId): HistorySummarizer {
|
|
18
|
+
return async (text, signal) => {
|
|
19
|
+
requireThat(selection, "请先选择 dsh 模型。"); signal.throwIfAborted();
|
|
20
|
+
const system = await readFile(join(resourceRoot, "prompts/history-summary.md"), { encoding: "utf8", signal });
|
|
21
|
+
const prepare = () => ctx.llm.prepareCall({ provider: selection.provider, model: selection.model, maxTokens: 2048 }, signal,
|
|
22
|
+
(controls, model) => ({ ...controls, ...(model.reasoning?.efforts[0] ? { reasoningEffort: model.reasoning.efforts[0].id } : {}) }));
|
|
23
|
+
let first: PreparedLlmCall | undefined = await prepare();
|
|
24
|
+
const budget = Math.max(2000, Math.min(24000, Math.floor((first.context?.contextWindow ?? 12000) / 2)));
|
|
25
|
+
const ask: HistorySummarizer = async (content, signal) => {
|
|
26
|
+
signal.throwIfAborted();
|
|
27
|
+
const prepared = first ?? await prepare(); first = undefined;
|
|
28
|
+
const assembler = new BlockAssembler(); let finished = false;
|
|
29
|
+
for await (const chunk of prepared.stream({ ...prepared.config, system, sessionId, purpose: "pbot-history", signal, tools: [],
|
|
30
|
+
messages: [{ role: "user", content: [{ type: "text", text: content }] }] })) {
|
|
31
|
+
signal.throwIfAborted(); assembler.push(chunk); if (chunk.type === "finish") finished = true;
|
|
32
|
+
}
|
|
33
|
+
signal.throwIfAborted();
|
|
34
|
+
const reason = assembler.finish;
|
|
35
|
+
requireThat(finished && reason.kind === "stop", reason.kind === "error" || reason.kind === "aborted" ? reason.failure.message : "历史摘要未正常结束:" + reason.kind);
|
|
36
|
+
const blocks = assembler.blocks();
|
|
37
|
+
requireThat(!blocks.some(block => block.type === "tool-call"), "历史摘要不能执行工具。");
|
|
38
|
+
const answer = blocks.filter(block => block.type === "text").map(block => block.text).join("\n").trim();
|
|
39
|
+
requireThat(answer, "历史摘要为空。"); return answer;
|
|
40
|
+
};
|
|
41
|
+
return reduceHistorySummary(budget, ask)(text, signal);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
import type { SessionEvent, SessionHeader } from "@deepseek-ai/dsh-session";
|
|
4
|
+
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
5
|
+
import type {} from "@deepseek-ai/dsh-session-query";
|
|
6
|
+
import type { HistoryExchange, HistoryLocation, HistorySession, HistorySource } from "../../model/history.js";
|
|
7
|
+
import { requireThat } from "../../common/validation.js";
|
|
8
|
+
import { finalDshAnswer } from "./messages.js";
|
|
9
|
+
|
|
10
|
+
type Question = Extract<SessionEvent, { type: "user/message" }>;
|
|
11
|
+
type Answer = Extract<SessionEvent, { type: "assistant/message" }>;
|
|
12
|
+
const text = (content: Question["data"]["content"]) => content.flatMap(block => block.type === "text" ? [block.text] :
|
|
13
|
+
block.type === "image" ? ["[图片 " + (block.attachment.name ?? block.attachment.attachmentId) + "]"] :
|
|
14
|
+
block.type === "file" ? ["[附件 " + (block.attachment.name ?? block.attachment.attachmentId) + "]"] : []).join("\n");
|
|
15
|
+
|
|
16
|
+
/** Archive questions and the final/partial answer; raw tool records stay in the native log. */
|
|
17
|
+
export function projectDshHistory(header: SessionHeader, events: readonly SessionEvent[], name = ""): HistorySession {
|
|
18
|
+
const exchanges: HistoryExchange[] = [];
|
|
19
|
+
let users: Question[] = [], answer: Answer | undefined;
|
|
20
|
+
const finish = (completed = false) => {
|
|
21
|
+
if (users.length) {
|
|
22
|
+
const entries = [...users, ...(answer ? [answer] : [])];
|
|
23
|
+
const body = answer ? answer.data.message.content.filter(block => block.type === "text").map(block => block.text).join("\n") : "";
|
|
24
|
+
exchanges.push({ entryIds: entries.map(event => "dsh:" + event.seq), from: new Date(users[0]!.time).toISOString(),
|
|
25
|
+
to: new Date(entries.at(-1)!.time).toISOString(),
|
|
26
|
+
completed: completed && Boolean(answer && finalDshAnswer(answer).trim()),
|
|
27
|
+
text: users.map(event => (event.data.source.kind === "user" ? "用户:\n" : "协调者要求:\n") + text(event.data.content)).join("\n\n") +
|
|
28
|
+
(body ? "\n\n助手:\n" + body : "") });
|
|
29
|
+
}
|
|
30
|
+
users = []; answer = undefined;
|
|
31
|
+
};
|
|
32
|
+
for (const event of events) {
|
|
33
|
+
if (event.type === "turn/start") finish();
|
|
34
|
+
else if (event.type === "user/message" && ["user", "pbot-input"].includes(event.data.source.kind)) users.push(event);
|
|
35
|
+
else if (event.type === "assistant/message" && users.length) answer = event;
|
|
36
|
+
else if (event.type === "turn/end") finish(event.data.reason.kind === "completed");
|
|
37
|
+
}
|
|
38
|
+
finish();
|
|
39
|
+
return { sessionId: header.id, name: name || exchanges[0]?.text.replace(/\s+/g, " ").slice(0, 80) || "",
|
|
40
|
+
branches: exchanges.length ? [exchanges] : [] };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class DshHistory implements HistorySource {
|
|
44
|
+
readonly host = "dsh";
|
|
45
|
+
constructor(private readonly ctx: Context, private readonly root: string, private readonly relatedSessions: () => string[] = () => []) {}
|
|
46
|
+
async scan(location: HistoryLocation, signal?: AbortSignal) {
|
|
47
|
+
requireThat(location.host === "dsh", "dsh 历史只读取 dsh 原生会话。");
|
|
48
|
+
signal?.throwIfAborted();
|
|
49
|
+
const records = await this.ctx.sessionQuery.listSessions(signal), selected = new Set<string>(), warnings: string[] = [];
|
|
50
|
+
try { for (const id of this.relatedSessions()) selected.add(id); } catch (error) { warnings.push("专家引用:" + String(error)); }
|
|
51
|
+
if (location.sessionId) selected.add(location.sessionId);
|
|
52
|
+
for (const record of records) {
|
|
53
|
+
signal?.throwIfAborted();
|
|
54
|
+
if ((record.header.origin !== "subagent" || !record.header.parentSession) && record.header.cwd &&
|
|
55
|
+
await realpath(record.header.cwd).catch(() => record.header.cwd) === this.root) selected.add(record.header.id);
|
|
56
|
+
}
|
|
57
|
+
// Native subagents may use a different cwd while still belonging to a workspace's parent.
|
|
58
|
+
for (let changed = true; changed;) {
|
|
59
|
+
changed = false;
|
|
60
|
+
for (const record of records) if (record.header.origin === "subagent" && record.header.parentSession && selected.has(record.header.parentSession) && !selected.has(record.header.id)) {
|
|
61
|
+
selected.add(record.header.id); changed = true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const sessions: HistorySession[] = [];
|
|
65
|
+
for (const id of selected) {
|
|
66
|
+
signal?.throwIfAborted();
|
|
67
|
+
try {
|
|
68
|
+
using observation = await this.ctx.sessionQuery.observeSession(SessionId(id), { ...(signal ? { signal } : {}), projectionMode: "none" });
|
|
69
|
+
const title = await this.ctx.sessionQuery.readTitle(SessionId(id), signal);
|
|
70
|
+
const session = projectDshHistory(observation.header, observation.events, title?.title);
|
|
71
|
+
if (session.branches.length) sessions.push(session);
|
|
72
|
+
} catch (error) { signal?.throwIfAborted(); warnings.push(id + ": " + String(error)); }
|
|
73
|
+
}
|
|
74
|
+
return { sessions, warnings };
|
|
75
|
+
}
|
|
76
|
+
}
|