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.
Files changed (215) hide show
  1. package/CONTEXT.md +9 -7
  2. package/README.md +15 -8
  3. package/README.zh-CN.md +15 -8
  4. package/docs/README.md +3 -1
  5. package/docs/agent-collaboration.md +27 -13
  6. package/docs/architecture-guidelines.md +2 -2
  7. package/docs/capability-design.md +6 -4
  8. package/docs/dsh-implementation-plan.md +158 -0
  9. package/docs/dsh-support-proposal.md +314 -0
  10. package/docs/dsh.md +103 -0
  11. package/docs/file-storage.md +14 -9
  12. package/docs/history.md +29 -18
  13. package/docs/installation-design.md +3 -1
  14. package/docs/interaction-design.md +13 -0
  15. package/docs/release.md +3 -3
  16. package/docs/subagent-upgrade-proposal.md +3 -3
  17. package/docs/technical-design.md +29 -15
  18. package/docs/validation.md +49 -3
  19. package/lib/client.js +1071 -0
  20. package/lib/common/files.d.ts +2 -0
  21. package/lib/common/files.js +44 -0
  22. package/lib/common/json.d.ts +6 -0
  23. package/lib/common/json.js +16 -0
  24. package/lib/common/markdown.d.ts +5 -0
  25. package/lib/common/markdown.js +26 -0
  26. package/lib/common/paths.d.ts +1 -0
  27. package/lib/common/paths.js +2 -0
  28. package/lib/common/validation.d.ts +3 -0
  29. package/lib/common/validation.js +14 -0
  30. package/lib/common/version.d.ts +3 -0
  31. package/lib/common/version.js +13 -0
  32. package/lib/dsh.d.ts +4 -0
  33. package/lib/dsh.js +22 -0
  34. package/lib/extension/dsh/agent-events.d.ts +24 -0
  35. package/lib/extension/dsh/agent-events.js +242 -0
  36. package/lib/extension/dsh/agent-runtime.d.ts +49 -0
  37. package/lib/extension/dsh/agent-runtime.js +348 -0
  38. package/lib/extension/dsh/command.d.ts +4 -0
  39. package/lib/extension/dsh/command.js +83 -0
  40. package/lib/extension/dsh/files.d.ts +5 -0
  41. package/lib/extension/dsh/files.js +67 -0
  42. package/lib/extension/dsh/history.d.ts +15 -0
  43. package/lib/extension/dsh/history.js +88 -0
  44. package/lib/extension/dsh/management.d.ts +21 -0
  45. package/lib/extension/dsh/management.js +217 -0
  46. package/lib/extension/dsh/workspace.d.ts +48 -0
  47. package/lib/extension/dsh/workspace.js +290 -0
  48. package/lib/integrations/agent-resources.d.ts +1 -0
  49. package/lib/integrations/agent-resources.js +18 -0
  50. package/lib/integrations/dsh/dsh-agents.d.ts +30 -0
  51. package/lib/integrations/dsh/dsh-agents.js +104 -0
  52. package/lib/integrations/dsh/history-summary.d.ts +11 -0
  53. package/lib/integrations/dsh/history-summary.js +40 -0
  54. package/lib/integrations/dsh/history.d.ts +16 -0
  55. package/lib/integrations/dsh/history.js +156 -0
  56. package/lib/integrations/dsh/input.d.ts +21 -0
  57. package/lib/integrations/dsh/input.js +62 -0
  58. package/lib/integrations/dsh/messages.d.ts +36 -0
  59. package/lib/integrations/dsh/messages.js +11 -0
  60. package/lib/integrations/dsh/notifications.d.ts +8 -0
  61. package/lib/integrations/dsh/notifications.js +55 -0
  62. package/lib/integrations/dsh/resources.d.ts +10 -0
  63. package/lib/integrations/dsh/resources.js +41 -0
  64. package/lib/integrations/dsh/schema.d.ts +7 -0
  65. package/lib/integrations/dsh/schema.js +28 -0
  66. package/lib/integrations/dsh/session.d.ts +14 -0
  67. package/lib/integrations/dsh/session.js +59 -0
  68. package/lib/integrations/history-summary.d.ts +2 -0
  69. package/lib/integrations/history-summary.js +21 -0
  70. package/lib/integrations/process.d.ts +14 -0
  71. package/lib/integrations/process.js +48 -0
  72. package/lib/integrations/python-activation.d.ts +2 -0
  73. package/lib/integrations/python-activation.js +29 -0
  74. package/lib/integrations/team-context.d.ts +18 -0
  75. package/lib/integrations/team-context.js +57 -0
  76. package/lib/model/agent.d.ts +92 -0
  77. package/lib/model/agent.js +1 -0
  78. package/lib/model/history.d.ts +43 -0
  79. package/lib/model/history.js +1 -0
  80. package/lib/model/team.d.ts +85 -0
  81. package/lib/model/team.js +1 -0
  82. package/lib/services/agents/agent-notices.d.ts +13 -0
  83. package/lib/services/agents/agent-notices.js +25 -0
  84. package/lib/services/agents/agent-run.d.ts +39 -0
  85. package/lib/services/agents/agent-run.js +138 -0
  86. package/lib/services/agents/agent-service.d.ts +166 -0
  87. package/lib/services/agents/agent-service.js +325 -0
  88. package/lib/services/agents/team-service.d.ts +168 -0
  89. package/lib/services/agents/team-service.js +343 -0
  90. package/lib/services/history/history-service.d.ts +103 -0
  91. package/lib/services/history/history-service.js +269 -0
  92. package/lib/services/workspace/python-environment-service.d.ts +15 -0
  93. package/lib/services/workspace/python-environment-service.js +50 -0
  94. package/lib/services/workspace/workspace-files.d.ts +2 -0
  95. package/lib/services/workspace/workspace-files.js +17 -0
  96. package/lib/services/workspace/workspace-preparation.d.ts +2 -0
  97. package/lib/services/workspace/workspace-preparation.js +18 -0
  98. package/lib/storage/agent-files.d.ts +30 -0
  99. package/lib/storage/agent-files.js +166 -0
  100. package/lib/storage/atomic-json.d.ts +11 -0
  101. package/lib/storage/atomic-json.js +101 -0
  102. package/lib/storage/file-access.d.ts +2 -0
  103. package/lib/storage/file-access.js +26 -0
  104. package/lib/storage/history-files.d.ts +10 -0
  105. package/lib/storage/history-files.js +63 -0
  106. package/lib/storage/team-files.d.ts +16 -0
  107. package/lib/storage/team-files.js +111 -0
  108. package/lib/tools/agents/actions.d.ts +52 -0
  109. package/lib/tools/agents/actions.js +49 -0
  110. package/lib/tools/agents/contract.d.ts +163 -0
  111. package/lib/tools/agents/contract.js +56 -0
  112. package/lib/tools/agents/dsh-contract.d.ts +52 -0
  113. package/lib/tools/agents/dsh-contract.js +15 -0
  114. package/lib/tools/agents/dsh-tool.d.ts +5 -0
  115. package/lib/tools/agents/dsh-tool.js +38 -0
  116. package/lib/tools/history/actions.d.ts +70 -0
  117. package/lib/tools/history/actions.js +7 -0
  118. package/lib/tools/history/contract.d.ts +81 -0
  119. package/lib/tools/history/contract.js +32 -0
  120. package/lib/tools/history/dsh-tool.d.ts +4 -0
  121. package/lib/tools/history/dsh-tool.js +22 -0
  122. package/lib/tools/result.d.ts +18 -0
  123. package/lib/tools/result.js +10 -0
  124. package/lib/tools/teams/actions.d.ts +24 -0
  125. package/lib/tools/teams/actions.js +38 -0
  126. package/lib/tools/teams/contract.d.ts +232 -0
  127. package/lib/tools/teams/contract.js +67 -0
  128. package/lib/tools/teams/dsh-tool.d.ts +8 -0
  129. package/lib/tools/teams/dsh-tool.js +49 -0
  130. package/lib/ui/dsh/attachments.d.ts +5 -0
  131. package/lib/ui/dsh/attachments.js +39 -0
  132. package/lib/ui/dsh/elements.d.ts +17 -0
  133. package/lib/ui/dsh/elements.js +79 -0
  134. package/lib/ui/dsh/index.d.ts +4 -0
  135. package/lib/ui/dsh/index.js +13 -0
  136. package/lib/ui/dsh/management.d.ts +4 -0
  137. package/lib/ui/dsh/management.js +711 -0
  138. package/lib/ui/dsh/remote.d.ts +15 -0
  139. package/lib/ui/dsh/remote.js +11 -0
  140. package/lib/ui/dsh/styles.d.ts +1 -0
  141. package/lib/ui/dsh/styles.js +132 -0
  142. package/lib/ui/presentation.d.ts +3 -0
  143. package/lib/ui/presentation.js +12 -0
  144. package/npm-shrinkwrap.json +11123 -793
  145. package/package.json +94 -11
  146. package/python/README.md +6 -6
  147. package/resources/agents/coordinator.md +1 -1
  148. package/resources/dsh/cordis.patch.yml +3 -0
  149. package/resources/prompts/workspace.md +1 -1
  150. package/resources/references/compute.md +1 -1
  151. package/resources/references/task-conductor.md +6 -4
  152. package/resources/skills/provider-connections/SKILL.md +10 -0
  153. package/scripts/README.md +9 -1
  154. package/src/dsh.ts +22 -0
  155. package/src/extension/agent-events.ts +51 -46
  156. package/src/extension/agents-command.ts +48 -34
  157. package/src/extension/command.ts +42 -23
  158. package/src/extension/dsh/agent-events.ts +148 -0
  159. package/src/extension/dsh/agent-runtime.ts +251 -0
  160. package/src/extension/dsh/command.ts +78 -0
  161. package/src/extension/dsh/files.ts +61 -0
  162. package/src/extension/dsh/history.ts +69 -0
  163. package/src/extension/dsh/management.ts +108 -0
  164. package/src/extension/dsh/workspace.ts +206 -0
  165. package/src/extension/events.ts +6 -4
  166. package/src/extension/history.ts +15 -12
  167. package/src/extension/subagent-entry.ts +34 -63
  168. package/src/extension/teams-command.ts +53 -46
  169. package/src/integrations/{pi/agent-resources.ts → agent-resources.ts} +2 -2
  170. package/src/integrations/dsh/dsh-agents.ts +97 -0
  171. package/src/integrations/dsh/history-summary.ts +43 -0
  172. package/src/integrations/dsh/history.ts +76 -0
  173. package/src/integrations/dsh/input.ts +66 -0
  174. package/src/integrations/dsh/messages.ts +22 -0
  175. package/src/integrations/dsh/notifications.ts +46 -0
  176. package/src/integrations/dsh/resources.ts +44 -0
  177. package/src/integrations/dsh/schema.ts +26 -0
  178. package/src/integrations/dsh/session.ts +67 -0
  179. package/src/integrations/history-summary.ts +20 -0
  180. package/src/integrations/pi/history.ts +4 -14
  181. package/src/integrations/pi/pi-agents.ts +17 -6
  182. package/src/integrations/pi/team-context.ts +6 -58
  183. package/src/integrations/team-context.ts +60 -0
  184. package/src/model/agent.ts +2 -0
  185. package/src/model/history.ts +3 -1
  186. package/src/pbot.ts +1 -1
  187. package/src/services/agents/agent-notices.ts +28 -0
  188. package/src/services/agents/agent-run.ts +114 -0
  189. package/src/services/agents/agent-service.ts +127 -64
  190. package/src/services/history/history-service.ts +120 -59
  191. package/src/storage/agent-files.ts +17 -4
  192. package/src/storage/history-files.ts +30 -5
  193. package/src/tools/agents/actions.ts +47 -0
  194. package/src/tools/agents/contract.ts +8 -5
  195. package/src/tools/agents/dsh-contract.ts +16 -0
  196. package/src/tools/agents/dsh-tool.ts +41 -0
  197. package/src/tools/agents/tool.ts +7 -43
  198. package/src/tools/history/actions.ts +9 -0
  199. package/src/tools/history/contract.ts +34 -0
  200. package/src/tools/history/dsh-tool.ts +22 -0
  201. package/src/tools/history/tool.ts +7 -30
  202. package/src/tools/teams/actions.ts +38 -0
  203. package/src/tools/teams/contract.ts +7 -3
  204. package/src/tools/teams/dsh-tool.ts +55 -0
  205. package/src/tools/teams/tool.ts +6 -31
  206. package/src/ui/agent-display.ts +7 -4
  207. package/src/ui/dsh/attachments.ts +42 -0
  208. package/src/ui/dsh/elements.ts +50 -0
  209. package/src/ui/dsh/index.ts +18 -0
  210. package/src/ui/dsh/management.ts +412 -0
  211. package/src/ui/dsh/remote.ts +20 -0
  212. package/src/ui/dsh/styles.ts +132 -0
  213. package/src/ui/pi/menu.ts +37 -0
  214. package/src/ui/pi/reader.ts +46 -0
  215. package/src/ui/presentation.ts +12 -0
@@ -0,0 +1,168 @@
1
+ import { AgentFiles } from "../../storage/agent-files.js";
2
+ import { TeamFiles } from "../../storage/team-files.js";
3
+ import type { AgentCompletion, AgentOwner } from "../../model/agent.js";
4
+ import type { TeamActor, TeamAgent, TeamInput, TeamLink, TeamMessage, TeamRecord, TeamRunOptions } from "../../model/team.js";
5
+ /** Team decisions only. The parent dispatches returned inputs through AgentService. */
6
+ export declare class TeamService {
7
+ #private;
8
+ readonly root: string;
9
+ readonly agents: AgentFiles;
10
+ readonly files: TeamFiles;
11
+ constructor(root: string, agents: AgentFiles, files: TeamFiles);
12
+ check(): void;
13
+ fail(error: unknown): void;
14
+ dispose(): void;
15
+ private owned;
16
+ private records;
17
+ private get;
18
+ private member;
19
+ private requests;
20
+ private active;
21
+ private available;
22
+ create(owner: AgentOwner, input: Pick<TeamRecord, "name" | "brief" | "members">, views: TeamAgent[]): TeamRecord;
23
+ run(id: string, owner: AgentOwner, options: TeamRunOptions, views: TeamAgent[]): {
24
+ runId: `${string}-${string}-${string}-${string}-${string}`;
25
+ instruction: string;
26
+ mode: "roundRobin" | "parallel";
27
+ rounds: number;
28
+ maxConcurrent: number;
29
+ deadlineAt: number;
30
+ generations: {
31
+ [k: string]: string;
32
+ };
33
+ state: "running";
34
+ issued: never[];
35
+ };
36
+ pause(id: string, owner: AgentOwner, reason?: string): void;
37
+ private pauseRecord;
38
+ pauseAll(owner: AgentOwner, reason: string): void;
39
+ recover(owner: AgentOwner): void;
40
+ pauseMember(agentId: string, reason: string): void;
41
+ close(id: string, owner: AgentOwner, views: TeamAgent[]): void;
42
+ post(id: string, actor: TeamActor, operationId: string, text: string, replyTo?: number): TeamMessage;
43
+ read(id: string, actor: TeamActor, page?: {
44
+ afterSeq?: number;
45
+ seq?: number;
46
+ offset?: number;
47
+ limit?: number;
48
+ }): {
49
+ teamId: string;
50
+ messages: {
51
+ text: string;
52
+ offset: number;
53
+ totalLength: number;
54
+ nextOffset: number | null;
55
+ seq: number;
56
+ operationId: string;
57
+ author: {
58
+ kind: "user" | "coordinator" | "member";
59
+ agentId?: string;
60
+ name: string;
61
+ };
62
+ replyTo?: number;
63
+ runId?: string;
64
+ source?: {
65
+ requestId: string;
66
+ cycleId: string;
67
+ };
68
+ }[];
69
+ lastSeq: number;
70
+ nextAfterSeq: number | null;
71
+ };
72
+ status(id: string, owner: AgentOwner, views: TeamAgent[]): {
73
+ owned: false;
74
+ teamId: string;
75
+ name: string;
76
+ closed: boolean;
77
+ } | {
78
+ run?: {
79
+ reason?: string;
80
+ runId: string;
81
+ instruction: string;
82
+ mode: "roundRobin" | "parallel";
83
+ state: "running" | "paused" | "finished";
84
+ rounds: number;
85
+ maxConcurrent: number;
86
+ deadlineAt: number;
87
+ scheduled: number;
88
+ published: number;
89
+ };
90
+ brief: string;
91
+ owned: true;
92
+ members: {
93
+ name: string;
94
+ state: "starting" | "busy" | "idle" | "stopping" | "stopped" | "error";
95
+ agentId: string;
96
+ role: string;
97
+ }[];
98
+ lastSeq: number;
99
+ activeCount: number;
100
+ unresolved: {
101
+ cycleId?: string;
102
+ requestId: string;
103
+ agentId: string;
104
+ runId: string;
105
+ round: number;
106
+ active: boolean;
107
+ state: string;
108
+ }[];
109
+ teamId: string;
110
+ name: string;
111
+ closed: boolean;
112
+ };
113
+ list(owner: AgentOwner, views: TeamAgent[]): ({
114
+ owned: false;
115
+ teamId: string;
116
+ name: string;
117
+ closed: boolean;
118
+ } | {
119
+ run?: {
120
+ reason?: string;
121
+ runId: string;
122
+ instruction: string;
123
+ mode: "roundRobin" | "parallel";
124
+ state: "running" | "paused" | "finished";
125
+ rounds: number;
126
+ maxConcurrent: number;
127
+ deadlineAt: number;
128
+ scheduled: number;
129
+ published: number;
130
+ };
131
+ brief: string;
132
+ owned: true;
133
+ members: {
134
+ name: string;
135
+ state: "starting" | "busy" | "idle" | "stopping" | "stopped" | "error";
136
+ agentId: string;
137
+ role: string;
138
+ }[];
139
+ lastSeq: number;
140
+ activeCount: number;
141
+ unresolved: {
142
+ cycleId?: string;
143
+ requestId: string;
144
+ agentId: string;
145
+ runId: string;
146
+ round: number;
147
+ active: boolean;
148
+ state: string;
149
+ }[];
150
+ teamId: string;
151
+ name: string;
152
+ closed: boolean;
153
+ })[];
154
+ private summary;
155
+ links(): Map<string, TeamLink>;
156
+ /** The callback persists the existing child receipt while the shared control lock is held. */
157
+ claim(agentId: string, generation: string, requestId: string, commit: () => void): TeamLink | undefined;
158
+ advance(owner: AgentOwner, views: TeamAgent[], completions: AgentCompletion[]): TeamInput[];
159
+ notifications(owner: AgentOwner, views: TeamAgent[]): {
160
+ reason?: string;
161
+ key: string;
162
+ teamId: string;
163
+ name: string;
164
+ runId: string;
165
+ state: "running" | "paused" | "finished";
166
+ activeCount: number;
167
+ }[];
168
+ }
@@ -0,0 +1,343 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { requireThat } from "../../common/validation.js";
3
+ import { packageVersion, releaseLine } from "../../common/version.js";
4
+ import { AgentFiles, descriptor, launch, status, message } from "../../storage/agent-files.js";
5
+ import { TeamFiles } from "../../storage/team-files.js";
6
+ /** Team decisions only. The parent dispatches returned inputs through AgentService. */
7
+ export class TeamService {
8
+ root;
9
+ agents;
10
+ files;
11
+ #failure;
12
+ #disposed = false;
13
+ #recovered = new Set();
14
+ constructor(root, agents, files) {
15
+ this.root = root;
16
+ this.agents = agents;
17
+ this.files = files;
18
+ }
19
+ check() { requireThat(!this.#disposed, "团队服务已关闭。"); this.files.check(); if (this.#failure)
20
+ throw this.#failure; }
21
+ fail(error) { this.#failure = error; }
22
+ dispose() { this.#disposed = true; }
23
+ owned(team, owner) {
24
+ return team.workspace === this.root && team.parentSessionId === owner.sessionId && (team.parentLeaf === null || owner.branch.includes(team.parentLeaf));
25
+ }
26
+ records(sessionId) {
27
+ this.check();
28
+ return this.files.list().filter(t => t.workspace === this.root && releaseLine(t.release) === releaseLine(packageVersion) &&
29
+ (!sessionId || t.parentSessionId === sessionId));
30
+ }
31
+ get(id, actor) {
32
+ this.check();
33
+ const team = this.files.read(id);
34
+ requireThat(team.workspace === this.root && releaseLine(team.release) === releaseLine(packageVersion), "团队工作区或版本不匹配。");
35
+ if (actor.kind === "member") {
36
+ const d = this.agents.read(actor.agentId, "descriptor.json", descriptor), run = this.agents.read(actor.agentId, "launch.json", launch);
37
+ requireThat(d.workspace === this.root && d.parentSessionId === team.parentSessionId && team.members.some(m => m.agentId === actor.agentId) &&
38
+ run.generation === actor.generation && !run.stop, "团队成员身份已失效。");
39
+ }
40
+ else
41
+ requireThat(this.owned(team, actor.owner), "团队不属于当前会话分支。");
42
+ return team;
43
+ }
44
+ member(id, owner, views) {
45
+ const v = views.find(v => v.agentId === id), d = this.agents.read(id, "descriptor.json", descriptor);
46
+ requireThat(v && v.owned && d.workspace === this.root && d.parentSessionId === owner.sessionId &&
47
+ (d.parentLeaf === null || owner.branch.includes(d.parentLeaf)), "成员不属于当前分支:" + id);
48
+ requireThat(releaseLine(d.release) === releaseLine(packageVersion), "成员版本不兼容:" + id);
49
+ return v;
50
+ }
51
+ requests(team, views) {
52
+ const states = new Map(team.members.map(m => [m.agentId, { view: views.find(v => v.agentId === m.agentId),
53
+ saved: this.agents.optional(m.agentId, "status.json", status),
54
+ cycles: new Map(this.agents.completions(m.agentId).flatMap(c => c.requestIds.map(id => [c.generation + ":" + id, c]))) }]));
55
+ return team.runs.flatMap(run => run.issued.map(q => {
56
+ const s = states.get(q.agentId), ended = s.cycles.get(q.generation + ":" + q.requestId);
57
+ const accepted = s.saved?.generation === q.generation && !!s.saved.accepted?.includes(q.requestId);
58
+ const active = !ended && accepted && !!s.view?.alive && s.view.generation === q.generation;
59
+ let outcome = ended?.state;
60
+ if (ended && ended.source !== "parent")
61
+ outcome = ended.source;
62
+ else if (outcome === "completed" && !team.messages.some(m => m.source?.requestId === q.requestId))
63
+ outcome = "unpublished";
64
+ return { requestId: q.requestId, agentId: q.agentId, runId: run.runId, round: q.round, active,
65
+ state: outcome ?? (active ? "accepted" : accepted ? "uncertain" : run.state === "running" ? "queued" : "cancelled"),
66
+ ...(ended ? { cycleId: ended.cycleId } : {}) };
67
+ }));
68
+ }
69
+ active(team, views) { return this.requests(team, views).filter(q => q.active); }
70
+ available(id, owner, views, links) {
71
+ const v = this.member(id, owner, views), run = this.agents.read(id, "launch.json", launch);
72
+ const saved = this.agents.optional(id, "status.json", status), ready = this.files.ready(id);
73
+ const available = (condition, message) => {
74
+ if (!condition)
75
+ throw Object.assign(new Error(message), { code: "TEAM_NOT_READY" });
76
+ };
77
+ available(v.alive && v.state === "idle" && !run.stop && v.generation === run.generation && saved?.generation === run.generation && saved.state === "idle", "成员尚未空闲:" + id);
78
+ available(ready?.ready && ready.generation === run.generation, "成员需加载新版子入口:" + id);
79
+ available(!this.agents.messages(id).some(q => q.generation === run.generation && !saved?.accepted?.includes(q.requestId) && !links.has(q.requestId)), "成员有待处理私人输入:" + id);
80
+ return v;
81
+ }
82
+ create(owner, input, views) {
83
+ this.check();
84
+ return this.agents.control(() => {
85
+ const records = this.records();
86
+ for (const m of input.members) {
87
+ this.member(m.agentId, owner, views);
88
+ requireThat(!records.some(t => !t.closed && t.members.some(v => v.agentId === m.agentId)), "成员已属于未关闭团队:" + m.agentId);
89
+ }
90
+ const team = { ...input, members: structuredClone(input.members), teamId: randomUUID(), release: packageVersion,
91
+ workspace: this.root, parentSessionId: owner.sessionId, parentLeaf: owner.leaf, closed: false, runs: [], messages: [] };
92
+ this.files.save(team, true);
93
+ return team;
94
+ });
95
+ }
96
+ run(id, owner, options, views) {
97
+ this.check();
98
+ return this.agents.control(() => {
99
+ const team = this.get(id, { kind: "coordinator", owner }), links = this.files.links(this.records());
100
+ requireThat(!team.closed && !team.runs.some(r => r.state === "running") && !this.active(team, views).length, "团队仍在工作或已关闭。");
101
+ const members = team.members.map(m => this.available(m.agentId, owner, views, links));
102
+ requireThat(options.mode !== "roundRobin" || options.maxConcurrent === undefined || options.maxConcurrent === 1, "轮流讨论并发须为 1。");
103
+ const rounds = options.rounds ?? 2, maxConcurrent = options.mode === "roundRobin" ? 1 : options.maxConcurrent ?? Math.min(2, members.length);
104
+ requireThat(Number.isInteger(rounds) && rounds >= 1 && rounds <= 10 && Number.isInteger(maxConcurrent) && maxConcurrent >= 1 && maxConcurrent <= members.length, "轮次或并发无效。");
105
+ const seconds = options.timeoutSeconds ?? 900;
106
+ requireThat(Number.isInteger(seconds) && seconds >= 1 && seconds <= 86400, "期限须为 1–86400 秒。");
107
+ requireThat(options.instruction.trim().length > 0, "新批次需要新的明确要求。");
108
+ const run = { runId: randomUUID(), instruction: options.instruction, mode: options.mode, rounds, maxConcurrent,
109
+ deadlineAt: Date.now() + seconds * 1000, generations: Object.fromEntries(members.map(v => [v.agentId, v.generation])),
110
+ state: "running", issued: [] };
111
+ team.runs.push(run);
112
+ this.files.save(team);
113
+ return run;
114
+ });
115
+ }
116
+ pause(id, owner, reason = "主会话暂停") {
117
+ this.check();
118
+ this.agents.control(() => { const team = this.get(id, { kind: "coordinator", owner }); this.pauseRecord(team, reason); });
119
+ }
120
+ pauseRecord(team, reason) {
121
+ let changed = false;
122
+ for (const run of team.runs)
123
+ if (run.state === "running") {
124
+ run.state = "paused";
125
+ run.reason = reason;
126
+ changed = true;
127
+ }
128
+ if (changed)
129
+ this.files.save(team);
130
+ }
131
+ pauseAll(owner, reason) {
132
+ this.check();
133
+ this.agents.control(() => { for (const team of this.records(owner.sessionId))
134
+ this.pauseRecord(team, reason); });
135
+ }
136
+ recover(owner) {
137
+ if (this.#recovered.has(owner.sessionId))
138
+ return;
139
+ this.pauseAll(owner, "主会话恢复:未自动续排旧批次");
140
+ this.#recovered.add(owner.sessionId);
141
+ }
142
+ pauseMember(agentId, reason) {
143
+ this.check();
144
+ this.agents.control(() => { for (const team of this.records())
145
+ if (team.members.some(m => m.agentId === agentId))
146
+ this.pauseRecord(team, reason); });
147
+ }
148
+ close(id, owner, views) {
149
+ this.check();
150
+ this.agents.control(() => {
151
+ const team = this.get(id, { kind: "coordinator", owner });
152
+ requireThat(!team.runs.some(r => r.state === "running") && !this.active(team, views).length, "先暂停团队并等待在途工作结束。");
153
+ if (!team.closed) {
154
+ team.closed = true;
155
+ this.files.save(team);
156
+ }
157
+ });
158
+ }
159
+ post(id, actor, operationId, text, replyTo) {
160
+ this.check();
161
+ requireThat(text.trim().length > 0 && text.length <= 24000 && operationId.length > 0, "消息须为 1–24000 字符。");
162
+ operationId = actor.kind === "member" ? "post:" + actor.agentId + ":" + actor.generation + ":" + operationId : "post:" + actor.kind + ":" + actor.owner.sessionId + ":" + operationId;
163
+ return this.agents.control(() => {
164
+ const team = this.get(id, actor);
165
+ const prior = team.messages.find(m => m.operationId === operationId);
166
+ if (prior) {
167
+ requireThat(prior.text === text && prior.replyTo === replyTo && prior.author.kind === actor.kind &&
168
+ (actor.kind !== "member" || prior.author.agentId === actor.agentId), "发布操作 ID 冲突。");
169
+ return prior;
170
+ }
171
+ requireThat(!team.closed, "团队已关闭。");
172
+ requireThat(replyTo === undefined || team.messages.some(m => m.seq === replyTo), "回复目标不存在。");
173
+ let runId;
174
+ if (actor.kind === "member") {
175
+ const run = team.runs.find(r => r.issued.some(q => q.requestId === actor.requestId && q.agentId === actor.agentId && q.generation === actor.generation));
176
+ const saved = this.agents.optional(actor.agentId, "status.json", status);
177
+ requireThat(run && saved?.generation === actor.generation && saved.cycle?.state === "running" && saved.cycle.source === "parent" &&
178
+ !!actor.requestId && saved.cycle.requestIds.includes(actor.requestId), "只能在自己的团队工作中发言。");
179
+ runId = run.runId;
180
+ }
181
+ const value = { seq: team.messages.length + 1, operationId, text,
182
+ author: actor.kind === "member" ? { kind: "member", agentId: actor.agentId, name: this.agents.read(actor.agentId, "launch.json", launch).name }
183
+ : { kind: actor.kind, name: actor.kind === "user" ? "用户" : "主会话" },
184
+ ...(replyTo === undefined ? {} : { replyTo }), ...(runId ? { runId } : {}) };
185
+ team.messages.push(value);
186
+ this.files.save(team);
187
+ return value;
188
+ });
189
+ }
190
+ read(id, actor, page = {}) {
191
+ const team = this.get(id, actor), limit = page.limit ?? 16000, after = page.afterSeq ?? 0, offset = page.offset ?? 0;
192
+ requireThat(Number.isInteger(limit) && limit >= 1 && limit <= 16000 && Number.isInteger(after) && after >= 0 && after <= team.messages.length &&
193
+ Number.isInteger(offset) && offset >= 0 && (page.seq !== undefined || offset === 0), "分页参数无效。");
194
+ const values = page.seq === undefined ? team.messages.filter(m => m.seq > after) : team.messages.filter(m => m.seq === page.seq);
195
+ requireThat(page.seq === undefined || values.length === 1, "消息不存在。");
196
+ let left = limit;
197
+ const messages = [];
198
+ for (const m of values) {
199
+ const at = page.seq === undefined ? 0 : offset;
200
+ requireThat(at <= m.text.length, "消息分页位置超出范围。");
201
+ const text = m.text.slice(at, at + left);
202
+ left -= Math.max(1, text.length);
203
+ messages.push({ ...m, text, offset: at, totalLength: m.text.length, nextOffset: at + text.length < m.text.length ? at + text.length : null });
204
+ if (left <= 0)
205
+ break;
206
+ }
207
+ const last = messages.at(-1)?.seq ?? after;
208
+ return { teamId: id, messages, lastSeq: team.messages.length, nextAfterSeq: page.seq === undefined && last < team.messages.length ? last : null };
209
+ }
210
+ status(id, owner, views) {
211
+ return this.summary(this.get(id, { kind: "coordinator", owner }), owner, views);
212
+ }
213
+ list(owner, views) { return this.records(owner.sessionId).map(t => this.summary(t, owner, views)); }
214
+ summary(team, owner, views) {
215
+ const identity = { teamId: team.teamId, name: team.name, closed: team.closed };
216
+ if (!this.owned(team, owner))
217
+ return { ...identity, owned: false };
218
+ const run = team.runs.at(-1), requests = this.requests(team, views), activeCount = requests.filter(q => q.active).length;
219
+ return { ...identity, brief: team.brief, owned: true,
220
+ members: team.members.map(m => { const v = views.find(v => v.agentId === m.agentId); return { ...m, name: v?.name ?? m.agentId, state: v?.state ?? "stopped" }; }),
221
+ lastSeq: team.messages.length, activeCount, unresolved: requests.filter(q => q.state !== "completed"),
222
+ ...(run ? { run: { runId: run.runId, instruction: run.instruction, mode: run.mode, state: run.state, rounds: run.rounds,
223
+ maxConcurrent: run.maxConcurrent, deadlineAt: run.deadlineAt, scheduled: run.issued.length,
224
+ published: team.messages.filter(m => m.runId === run.runId && m.source).length, ...(run.reason ? { reason: run.reason } : {}) } } : {}) };
225
+ }
226
+ links() { return this.files.links(this.records()); }
227
+ /** The callback persists the existing child receipt while the shared control lock is held. */
228
+ claim(agentId, generation, requestId, commit) {
229
+ this.check();
230
+ return this.agents.control(() => {
231
+ const link = this.links().get(requestId), run = this.agents.read(agentId, "launch.json", launch), ready = this.files.ready(agentId);
232
+ const saved = this.agents.optional(agentId, "status.json", status), input = this.agents.optional(agentId, "inbox/" + requestId + ".json", message);
233
+ if (!link || link.request.agentId !== agentId || link.request.generation !== generation || run.generation !== generation || run.stop ||
234
+ !ready?.ready || ready.generation !== generation || saved?.generation !== generation || saved.state !== "idle" ||
235
+ saved.accepted?.includes(requestId) || input?.generation !== generation || link.team.closed || link.run.state !== "running" || link.run.deadlineAt <= Date.now())
236
+ return;
237
+ for (const member of link.team.members) {
238
+ const memberRun = this.agents.read(member.agentId, "launch.json", launch), memberReady = this.files.ready(member.agentId);
239
+ if (memberRun.stop || memberRun.generation !== link.run.generations[member.agentId] || !memberReady?.ready || memberReady.generation !== memberRun.generation)
240
+ return;
241
+ }
242
+ commit();
243
+ return link;
244
+ });
245
+ }
246
+ advance(owner, views, completions) {
247
+ this.check();
248
+ return this.agents.control(() => {
249
+ const inputs = [], records = this.records(owner.sessionId), links = this.files.links(records);
250
+ for (const team of records) {
251
+ let changed = false;
252
+ for (const run of team.runs) {
253
+ for (const q of run.issued) {
254
+ const done = completions.find(v => v.agentId === q.agentId && v.cycle.generation === q.generation && v.cycle.requestIds.includes(q.requestId));
255
+ if (!done)
256
+ continue;
257
+ if (done.cycle.state !== "completed" || done.cycle.source !== "parent" || done.cycle.requestIds.length !== 1 || !done.report.trim()) {
258
+ if (run.state === "running") {
259
+ run.state = "paused";
260
+ run.reason = "成员失败或人工接管:" + q.agentId;
261
+ changed = true;
262
+ }
263
+ }
264
+ else if (!team.messages.some(m => m.source?.requestId === q.requestId)) {
265
+ team.messages.push({ seq: team.messages.length + 1, operationId: "result:" + q.requestId,
266
+ author: { kind: "member", agentId: q.agentId, name: done.name }, text: done.report, runId: run.runId,
267
+ source: { requestId: q.requestId, cycleId: done.cycle.cycleId } });
268
+ changed = true;
269
+ }
270
+ }
271
+ if (run.state !== "running")
272
+ continue;
273
+ const unavailable = team.members.find(m => {
274
+ const v = views.find(v => v.agentId === m.agentId), ready = this.files.ready(m.agentId);
275
+ return !v?.alive || !v.owned || v.generation !== run.generations[m.agentId] || !ready?.ready || ready.generation !== v.generation ||
276
+ ["stopping", "stopped", "error"].includes(v.state) || (v.cycle?.state === "running" && v.cycle.source !== "parent");
277
+ });
278
+ if (!this.owned(team, owner) || unavailable || Date.now() >= run.deadlineAt) {
279
+ run.state = "paused";
280
+ run.reason = !this.owned(team, owner) ? "当前分支无团队归属" : unavailable ? "成员不可用:" + unavailable.agentId : "已到期限";
281
+ changed = true;
282
+ continue;
283
+ }
284
+ const finished = new Set(team.messages.filter(m => m.runId === run.runId && m.source).map(m => m.source.requestId));
285
+ const outstanding = run.issued.filter(q => !finished.has(q.requestId));
286
+ const round = Math.max(1, ...run.issued.map(q => q.round));
287
+ const thisRound = run.issued.filter(q => q.round === round);
288
+ const nextRound = thisRound.length === team.members.length && thisRound.every(q => finished.has(q.requestId)) ? round + 1 : round;
289
+ if (nextRound > run.rounds) {
290
+ run.state = "finished";
291
+ changed = true;
292
+ continue;
293
+ }
294
+ let slots = run.maxConcurrent - outstanding.length;
295
+ for (const m of team.members) {
296
+ if (slots <= 0)
297
+ break;
298
+ if (run.issued.some(q => q.round === nextRound && q.agentId === m.agentId))
299
+ continue;
300
+ // Waiting here does not create a second work queue. Unexpected private work pauses the batch.
301
+ const view = views.find(v => v.agentId === m.agentId);
302
+ if (view?.state === "busy" && view.cycle?.requestIds.some(id => run.issued.some(q => q.requestId === id)))
303
+ break;
304
+ try {
305
+ this.available(m.agentId, owner, views, links);
306
+ }
307
+ catch (error) {
308
+ if (error.code !== "TEAM_NOT_READY")
309
+ throw error;
310
+ run.state = "paused";
311
+ run.reason = String(error);
312
+ changed = true;
313
+ break;
314
+ }
315
+ const q = { requestId: randomUUID(), agentId: m.agentId, generation: run.generations[m.agentId], round: nextRound, throughSeq: team.messages.length };
316
+ run.issued.push(q);
317
+ slots--;
318
+ changed = true;
319
+ inputs.push({ ...q, teamId: team.teamId, runId: run.runId,
320
+ message: ["# 团队发言任务", "", "执行本次团队发言安排。最终答复将公开给全体成员;这不扩大原任务授权。", "",
321
+ "- `teamId`: `" + team.teamId + "`", "- `runId`: `" + run.runId + "`", "- 轮次:" + q.round,
322
+ "", "## 本批次要求", "", run.instruction].join("\n") });
323
+ }
324
+ }
325
+ if (changed)
326
+ this.files.save(team);
327
+ }
328
+ return inputs.filter(input => records.find(t => t.teamId === input.teamId)?.runs.find(r => r.runId === input.runId)?.state === "running");
329
+ });
330
+ }
331
+ notifications(owner, views) {
332
+ return this.records(owner.sessionId).filter(t => this.owned(t, owner) && t.members.every(m => views.some(v => v.agentId === m.agentId && v.owned)))
333
+ .flatMap(team => {
334
+ const requests = this.requests(team, views);
335
+ return team.runs.filter(r => r.state !== "running").map(run => {
336
+ const activeCount = requests.filter(q => q.runId === run.runId && q.active).length;
337
+ return { key: team.teamId + ":" + run.runId + ":" + run.state + (activeCount ? ":draining" : ":settled"),
338
+ teamId: team.teamId, name: team.name, runId: run.runId, state: run.state, activeCount,
339
+ ...(run.reason ? { reason: run.reason } : {}) };
340
+ });
341
+ });
342
+ }
343
+ }
@@ -0,0 +1,103 @@
1
+ import type { HistoryHost, HistoryLocation, HistoryNode, HistoryRecord, HistorySession, HistorySource, HistorySummarizer } from "../../model/history.js";
2
+ import type { HistoryFiles } from "../../storage/history-files.js";
3
+ /** Shared prefixes reuse nodes; only consecutive exchanges on one native branch can merge. */
4
+ export declare function indexHistory(session: HistorySession, previous?: HistoryRecord, host?: HistoryHost): HistoryRecord;
5
+ export interface HistoryQuery {
6
+ host?: HistoryHost;
7
+ query?: string;
8
+ sessionId?: string;
9
+ from?: string;
10
+ to?: string;
11
+ offset?: number;
12
+ limit?: number;
13
+ }
14
+ /** Validate the exact inputs again under the source lock; another writer's ready summary wins. */
15
+ export declare function completeHistorySummary(record: HistoryRecord, id: string, children: HistoryNode[], text: string): boolean;
16
+ export declare class HistoryService {
17
+ #private;
18
+ readonly root: string;
19
+ private readonly files;
20
+ private readonly source;
21
+ private previousFiles?;
22
+ private readonly otherFiles?;
23
+ constructor(root: string, files: HistoryFiles, source: HistorySource, previousFiles?: HistoryFiles | undefined, otherFiles?: HistoryFiles | undefined);
24
+ check(): void;
25
+ get warnings(): string[];
26
+ get status(): {
27
+ loaded: boolean;
28
+ organizing: boolean;
29
+ pending: number | null;
30
+ warnings: string[];
31
+ };
32
+ refresh(location: HistoryLocation, signal?: AbortSignal): Promise<void>;
33
+ private load;
34
+ search({ host, query, sessionId, from, to, offset, limit }?: HistoryQuery): {
35
+ matches: {
36
+ parents: string[];
37
+ snippet: string;
38
+ host: HistoryHost;
39
+ id: string;
40
+ kind: "session" | "exchange" | "summary";
41
+ sessionId: string;
42
+ name: string;
43
+ preview: string;
44
+ from: string;
45
+ to: string;
46
+ ready: boolean;
47
+ completed: boolean;
48
+ }[];
49
+ total: number;
50
+ nextOffset: number | null;
51
+ warnings: string[];
52
+ };
53
+ private find;
54
+ read(id: string, offset?: number, limit?: number, childrenOffset?: number, host?: HistoryHost): {
55
+ text: string;
56
+ totalLength: number;
57
+ nextOffset: number | null;
58
+ parents: {
59
+ host: HistoryHost;
60
+ id: string;
61
+ kind: "session" | "exchange" | "summary";
62
+ sessionId: string;
63
+ name: string;
64
+ preview: string;
65
+ from: string;
66
+ to: string;
67
+ ready: boolean;
68
+ completed: boolean;
69
+ }[];
70
+ children: {
71
+ host: HistoryHost;
72
+ id: string;
73
+ kind: "session" | "exchange" | "summary";
74
+ sessionId: string;
75
+ name: string;
76
+ preview: string;
77
+ from: string;
78
+ to: string;
79
+ ready: boolean;
80
+ completed: boolean;
81
+ }[];
82
+ nextChildrenOffset: number | null;
83
+ source: {
84
+ host: HistoryHost;
85
+ sessionId: string;
86
+ entryIds: string[];
87
+ };
88
+ warnings: string[];
89
+ host: HistoryHost;
90
+ id: string;
91
+ kind: "session" | "exchange" | "summary";
92
+ sessionId: string;
93
+ name: string;
94
+ preview: string;
95
+ from: string;
96
+ to: string;
97
+ ready: boolean;
98
+ completed: boolean;
99
+ };
100
+ /** Only this source is organized. Model calls run outside the write lock. */
101
+ organize(summarize: HistorySummarizer, signal: AbortSignal): Promise<void>;
102
+ close(): Promise<void>;
103
+ }