agentworkshop 0.7.37 → 0.7.38

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 (59) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/builds/latest.json +1 -1
  3. package/.output/public/_nuxt/builds/meta/44b66249-54d0-4c8a-9057-9755a32329e2.json +1 -0
  4. package/.output/server/chunks/_/nitro.mjs +798 -799
  5. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  6. package/.output/server/package.json +1 -1
  7. package/README-zh.md +2 -2
  8. package/README.md +2 -2
  9. package/docs/cli.en.md +2 -2
  10. package/docs/cli.md +2 -2
  11. package/docs/full-test-plan.md +1 -1
  12. package/docs/plugins.en.md +1 -1
  13. package/docs/plugins.md +1 -1
  14. package/docs/sdk.en.md +1 -1
  15. package/docs/sdk.md +1 -1
  16. package/package.json +1 -1
  17. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +1 -0
  18. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +1 -0
  19. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +153 -0
  20. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +153 -0
  21. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +431 -0
  22. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +180 -0
  23. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +112 -0
  24. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +227 -0
  25. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +131 -0
  26. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +160 -0
  27. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +157 -0
  28. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +348 -0
  29. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +296 -0
  30. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +285 -0
  31. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +217 -0
  32. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +301 -0
  33. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +151 -0
  34. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +135 -0
  35. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +156 -0
  36. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +72 -0
  37. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +469 -0
  38. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +259 -0
  39. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +97 -0
  40. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +277 -0
  41. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +171 -0
  42. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +425 -0
  43. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +99 -0
  44. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +325 -0
  45. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +332 -0
  46. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +43 -0
  47. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +154 -0
  48. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +531 -0
  49. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +439 -0
  50. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +323 -0
  51. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +433 -0
  52. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +280 -0
  53. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +117 -0
  54. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +160 -0
  55. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +101 -0
  56. package/scripts/test-plugin-hardening.mjs +32 -0
  57. package/server/plugins/dev-stability-guard.ts +40 -63
  58. package/server/utils/stability-severity.ts +66 -0
  59. package/.output/public/_nuxt/builds/meta/a66fdef7-dcfd-453b-b1f6-543c7dfd98d5.json +0 -1
@@ -0,0 +1,439 @@
1
+ /**
2
+ * 系统全面验证测试 — 验证所有子系统协同
3
+ *
4
+ * 测试矩阵:
5
+ * A. 任务队列顺序执行(mock):给同一 worker 连续下发 3 个任务,验证 FIFO 顺序执行
6
+ * B. 实时通讯(immediate):worker 忙碌时注入 steer 消息
7
+ * C. lead dispatch + worker 执行(mock):验证 lead 分发 → worker 接取 → 完成 → lead 汇总
8
+ * D. worker ↔ worker 通信(mock):worker-A 给 worker-B 发消息,B 入队后消费
9
+ * E. goal 模式(omp):lead 判断目标完成
10
+ * F. loop 模式(mock):循环重放
11
+ * G. pipeline 模式(mock):流水线阶段
12
+ *
13
+ * 运行: npx tsx scripts/test-full-system.ts
14
+ */
15
+ import { openWorkshopDb } from '../server/services/workshop/db/database'
16
+ import { createChannelRepo } from '../server/services/workshop/db/channel.repo'
17
+ import { createAgentRepo } from '../server/services/workshop/db/agent.repo'
18
+ import { createChannelAgentRepo } from '../server/services/workshop/db/channel-agent.repo'
19
+ import { createTaskRepo } from '../server/services/workshop/db/task.repo'
20
+ import { createMemoryRepo } from '../server/services/workshop/db/memory.repo'
21
+ import { createChannelEventRepo } from '../server/services/workshop/db/channel-event.repo'
22
+ import { createTeamRepo } from '../server/services/workshop/db/team.repo'
23
+ import { createTeamMemberRepo } from '../server/services/workshop/db/team-member.repo'
24
+ import { createMessageRepo } from '../server/services/workshop/db/message.repo'
25
+ import { createSubscriptionRepo } from '../server/services/workshop/db/subscription.repo'
26
+ import { createAgentChannelManager } from '../server/services/workshop/runtime/manager'
27
+ import type { AgentChannelManager } from '../server/services/workshop/runtime/manager'
28
+ import { createAgentImpl } from '../server/services/workshop/agents/factory'
29
+ import type { WorkspaceTask } from '../server/services/workshop/types/task'
30
+
31
+ let failures = 0
32
+ let testCount = 0
33
+ function check(name: string, ok: boolean, detail = ''): void {
34
+ testCount += 1
35
+ console.log(` ${ok ? '✅' : '❌'} ${name}${detail ? ` — ${detail}` : ''}`)
36
+ if (!ok) failures += 1
37
+ }
38
+
39
+ const sleep = (ms: number) => new Promise<void>(r => setTimeout(r, ms))
40
+
41
+ async function waitUntil(cond: () => boolean | Promise<boolean>, timeoutMs: number): Promise<boolean> {
42
+ const deadline = Date.now() + timeoutMs
43
+ while (Date.now() < deadline) {
44
+ if (await cond()) return true
45
+ await sleep(50)
46
+ }
47
+ return false
48
+ }
49
+
50
+ interface SetupResult {
51
+ manager: AgentChannelManager
52
+ db: ReturnType<typeof openWorkshopDb>
53
+ repos: {
54
+ messages: ReturnType<typeof createMessageRepo>
55
+ }
56
+ }
57
+
58
+ function setup(): SetupResult {
59
+ const db = openWorkshopDb(':memory:')
60
+ const repos = {
61
+ channels: createChannelRepo(db),
62
+ agents: createAgentRepo(db),
63
+ channelAgents: createChannelAgentRepo(db),
64
+ messages: createMessageRepo(db),
65
+ subscriptions: createSubscriptionRepo(db),
66
+ tasks: createTaskRepo(db),
67
+ memories: createMemoryRepo(db),
68
+
69
+ channelEvents: createChannelEventRepo(db),
70
+
71
+ teams: createTeamRepo(db),
72
+
73
+ teamMembers: createTeamMemberRepo(db),
74
+ }
75
+ const manager = createAgentChannelManager({ repos, implFactory: createAgentImpl, db })
76
+ return { manager, db, repos: { messages: repos.messages } }
77
+ }
78
+
79
+ function getEngine(manager: AgentChannelManager) {
80
+ return (manager as unknown as {
81
+ getTaskEngine(): {
82
+ get(id: string): WorkspaceTask | undefined
83
+ list(channelId: string): WorkspaceTask[]
84
+ }
85
+ }).getTaskEngine()
86
+ }
87
+
88
+ function attachScheduler(manager: AgentChannelManager, channelId: string, tickMs = 200, stallMs = 60_000): void {
89
+ // 懒加载设计:scheduler 由 manager 内部装配与管理
90
+ manager.ensureChannelActive(channelId, { tickMs, stallMs })
91
+ }
92
+
93
+ async function cleanup(manager: AgentChannelManager, db: ReturnType<typeof openWorkshopDb>): Promise<void> {
94
+ await manager.shutdown()
95
+ db.close()
96
+ }
97
+
98
+ // ===== 测试 A: 任务队列顺序执行 =====
99
+ async function testTaskQueueOrder(): Promise<void> {
100
+ console.log('\n━━━ 测试 A: 任务队列 FIFO 顺序执行(mock)━━━')
101
+
102
+ const { manager, db } = setup()
103
+
104
+ try {
105
+ const ch = await manager.createChannel({
106
+ name: '队列测试',
107
+ leadAgent: { name: 'lead', harness: 'mock', config: { delayMs: 100 } },
108
+ })
109
+ const workerTpl = await manager.createAgent({ name: 'worker', harness: 'mock', config: { delayMs: 100 } })
110
+ const worker = await manager.addAgentToChannel({ channelId: ch.channelId, agentId: workerTpl.id, role: 'worker' })
111
+ attachScheduler(manager, ch.channelId)
112
+
113
+ const engine = getEngine(manager)
114
+
115
+ // 连续提交 3 个任务
116
+ const t1 = await manager.submitChannelTask({ channelId: ch.channelId, title: '任务1' })
117
+ const t2 = await manager.submitChannelTask({ channelId: ch.channelId, title: '任务2' })
118
+ const t3 = await manager.submitChannelTask({ channelId: ch.channelId, title: '任务3' })
119
+
120
+ // 等待全部完成
121
+ await waitUntil(() => {
122
+ return engine.get(t1.id)?.state === 'COMPLETED'
123
+ && engine.get(t2.id)?.state === 'COMPLETED'
124
+ && engine.get(t3.id)?.state === 'COMPLETED'
125
+ }, 15_000)
126
+
127
+ // 验证全部 COMPLETED
128
+ check('任务1 COMPLETED', engine.get(t1.id)?.state === 'COMPLETED')
129
+ check('任务2 COMPLETED', engine.get(t2.id)?.state === 'COMPLETED')
130
+ check('任务3 COMPLETED', engine.get(t3.id)?.state === 'COMPLETED')
131
+
132
+ // 验证 FIFO: 任务1 的 updatedAt <= 任务2 的 updatedAt <= 任务3 的 updatedAt
133
+ const r1 = engine.get(t1.id)
134
+ const r2 = engine.get(t2.id)
135
+ const r3 = engine.get(t3.id)
136
+ const order = r1!.updatedAt <= r2!.updatedAt && r2!.updatedAt <= r3!.updatedAt
137
+ check('FIFO 顺序执行(任务1先完成)', order, `${r1!.updatedAt.slice(11, 19)} ≤ ${r2!.updatedAt.slice(11, 19)} ≤ ${r3!.updatedAt.slice(11, 19)}`)
138
+
139
+ // 验证每个任务有 artifact
140
+ check('每个任务有 artifact', (r1!.artifacts.length > 0) && (r2!.artifacts.length > 0) && (r3!.artifacts.length > 0))
141
+
142
+ void worker
143
+ }
144
+ finally {
145
+ await cleanup(manager, db)
146
+ }
147
+ }
148
+
149
+ // ===== 测试 B: 实时通讯 immediate =====
150
+ async function testImmediateMessage(): Promise<void> {
151
+ console.log('\n━━━ 测试 B: 实时通讯(immediate priority)━━━')
152
+
153
+ const { manager, db, repos } = setup()
154
+
155
+ try {
156
+ const ch = await manager.createChannel({
157
+ name: '实时通讯测试',
158
+ leadAgent: { name: 'lead', harness: 'mock', config: { delayMs: 200 } },
159
+ })
160
+ const w1Tpl = await manager.createAgent({ name: 'w1', harness: 'mock', config: { delayMs: 200 } })
161
+ const w1 = await manager.addAgentToChannel({ channelId: ch.channelId, agentId: w1Tpl.id, role: 'worker' })
162
+ const w2Tpl = await manager.createAgent({ name: 'w2', harness: 'mock', config: { delayMs: 200 } })
163
+ const w2 = await manager.addAgentToChannel({ channelId: ch.channelId, agentId: w2Tpl.id, role: 'worker' })
164
+ attachScheduler(manager, ch.channelId)
165
+
166
+ // 提交任务让 w1 忙碌
167
+ const task = await manager.submitChannelTask({ channelId: ch.channelId, title: 'busy task' })
168
+
169
+ // 等 w1 开始工作(busy)
170
+ await waitUntil(() => {
171
+ const cr = (manager as unknown as { channels: Map<string, { getAgents(): Array<{ getState: () => string, agentId: string }> }> }).channels.get(ch.channelId)!
172
+ const w1rt = cr.getAgents().find(a => a.agentId === w1.id)
173
+ return w1rt?.getState() === 'busy'
174
+ }, 5_000)
175
+
176
+ // w1 busy 时发送 immediate 消息
177
+ const msg = await manager.sendImmediateMessage({
178
+ channelId: ch.channelId,
179
+ fromAgentId: w2.id,
180
+ toAgentId: w1.id,
181
+ parts: [{ text: '紧急消息!' }],
182
+ })
183
+ check('immediate 消息发送成功', !!msg)
184
+
185
+ // 验证消息有 immediate priority metadata
186
+ check('消息 priority=immediate', msg.metadata?.['x-aw-msg-priority'] === 'immediate')
187
+
188
+ // 等 w1 完成任务
189
+ const engine = getEngine(manager)
190
+ await waitUntil(() => engine.get(task.id)?.state === 'COMPLETED', 5_000)
191
+
192
+ // 发送 task priority 消息给 idle 的 w1
193
+ const msg2 = await manager.sendA2A(ch.channelId, w2.id, {
194
+ toAgentId: w1.id,
195
+ parts: [{ text: '普通消息' }],
196
+ metadata: { 'x-aw-msg-priority': 'task' },
197
+ })
198
+ check('task 消息发送成功', !!msg2)
199
+
200
+ // 消息自动被 consumeLoop 消费;验证 DB 中有消息记录(consumed 状态)
201
+ const recentMsgs = repos.messages.listRecentByChannel(ch.channelId, 10)
202
+ const w1Msgs = recentMsgs.filter(m => m.toAgentId === w1.id)
203
+ check('w1 收到了消息(DB 记录)', w1Msgs.length >= 1, `消息数=${w1Msgs.length}`)
204
+ check('消息被自动消费(consumed)', w1Msgs.some(m => m.state === 'consumed'), `states=[${w1Msgs.map(m => m.state).join(',')}]`)
205
+ }
206
+ finally {
207
+ await cleanup(manager, db)
208
+ }
209
+ }
210
+
211
+ // ===== 测试 C: lead dispatch + worker 执行 + 汇总 =====
212
+ async function testLeadDispatchCycle(): Promise<void> {
213
+ console.log('\n━━━ 测试 C: lead dispatch → worker 执行 → lead 汇总(mock)━━━')
214
+
215
+ const { manager, db } = setup()
216
+
217
+ try {
218
+ const ch = await manager.createChannel({
219
+ name: 'dispatch 测试',
220
+ leadAgent: { name: 'lead', harness: 'mock', config: { delayMs: 50 } },
221
+ })
222
+ await manager.addAgentToChannel({ channelId: ch.channelId, agentId: (await manager.createAgent({ name: 'w1', harness: 'mock', config: { delayMs: 50 } })).id, role: 'worker' })
223
+ await manager.addAgentToChannel({ channelId: ch.channelId, agentId: (await manager.createAgent({ name: 'w2', harness: 'mock', config: { delayMs: 50 } })).id, role: 'worker' })
224
+ attachScheduler(manager, ch.channelId)
225
+
226
+ const engine = getEngine(manager)
227
+ const main = await manager.submitChannelTask({ channelId: ch.channelId, title: '主任务' })
228
+
229
+ await waitUntil(() => engine.get(main.id)?.state === 'COMPLETED', 10_000)
230
+
231
+ const allTasks = engine.list(ch.channelId)
232
+ const children = allTasks.filter(t => t.parentId === main.id)
233
+
234
+ check('主任务 COMPLETED', engine.get(main.id)?.state === 'COMPLETED')
235
+ check('lead dispatch 了子任务', children.length >= 1, `子任务数=${children.length}`)
236
+ check('子任务全部 COMPLETED', children.every(c => c.state === 'COMPLETED'), `${children.filter(c => c.state === 'COMPLETED').length}/${children.length}`)
237
+ check('主任务有汇总 artifact', (engine.get(main.id)?.artifacts.length ?? 0) > 0)
238
+ }
239
+ finally {
240
+ await cleanup(manager, db)
241
+ }
242
+ }
243
+
244
+ // ===== 测试 D: worker ↔ worker 通信 =====
245
+ async function testWorkerToWorkerComm(): Promise<void> {
246
+ console.log('\n━━━ 测试 D: worker ↔ worker 通信(mock)━━━')
247
+
248
+ const { manager, db, repos } = setup()
249
+
250
+ try {
251
+ const ch = await manager.createChannel({
252
+ name: '通信测试',
253
+ leadAgent: { name: 'lead', harness: 'mock', config: { delayMs: 50 } },
254
+ })
255
+ const w1 = await manager.addAgentToChannel({ channelId: ch.channelId, agentId: (await manager.createAgent({ name: 'w1', harness: 'mock', config: { delayMs: 50 } })).id, role: 'worker' })
256
+ const w2 = await manager.addAgentToChannel({ channelId: ch.channelId, agentId: (await manager.createAgent({ name: 'w2', harness: 'mock', config: { delayMs: 50 } })).id, role: 'worker' })
257
+
258
+ // w1 → w2 发消息(task priority → 入队)
259
+ const msg = await manager.sendA2A(ch.channelId, w1.id, {
260
+ toAgentId: w2.id,
261
+ parts: [{ text: 'hello from w1' }],
262
+ })
263
+ check('w1 → w2 消息发送', !!msg)
264
+ check('消息有 target metadata', msg.metadata?.['x-aw-target-agent'] === w2.id)
265
+ check('消息有 from metadata', msg.metadata?.['x-aw-from-agent'] === w1.id)
266
+
267
+ // 等 consumeLoop 处理(mock worker 消费消息很快)
268
+ await sleep(200)
269
+
270
+ // 验证 DB 中有 w2 的消息记录(已被消费)
271
+ const recentMsgs = repos.messages.listRecentByChannel(ch.channelId, 20)
272
+ const w2Msgs = recentMsgs.filter(m => m.toAgentId === w2.id)
273
+ check('w2 收到了 w1 的消息(DB 记录)', w2Msgs.length >= 1, `消息数=${w2Msgs.length}`)
274
+
275
+ // w1 没收到自己的消息(点对点不广播)
276
+ const w1SelfMsgs = recentMsgs.filter(m => m.toAgentId === w1.id && m.fromAgentId === w1.id)
277
+ check('w1 没收到自己的消息(点对点)', w1SelfMsgs.length === 0)
278
+
279
+ // lead → w1 发消息
280
+ const leadInfo = (await manager.listChannelAgents(ch.channelId)).find(a => a.role === 'lead')!
281
+ await manager.sendA2A(ch.channelId, leadInfo.id, {
282
+ toAgentId: w1.id,
283
+ parts: [{ text: 'lead to w1' }],
284
+ })
285
+ await sleep(200)
286
+ const recentAfter = repos.messages.listRecentByChannel(ch.channelId, 20)
287
+ const leadToW1 = recentAfter.filter(m => m.toAgentId === w1.id && m.fromAgentId === leadInfo.id)
288
+ check('lead → w1 消息到达(DB 记录)', leadToW1.length >= 1, `消息数=${leadToW1.length}`)
289
+ }
290
+ finally {
291
+ await cleanup(manager, db)
292
+ }
293
+ }
294
+
295
+ // ===== 测试 E: goal 模式(mock 快速验证调度逻辑) =====
296
+ async function testGoalMode(): Promise<void> {
297
+ console.log('\n━━━ 测试 E: goal 模式(mock)━━━')
298
+
299
+ const { manager, db } = setup()
300
+
301
+ try {
302
+ const ch = await manager.createChannel({
303
+ name: 'goal 测试',
304
+ leadAgent: { name: 'lead', harness: 'mock', config: { delayMs: 30 } },
305
+ })
306
+ await manager.addAgentToChannel({ channelId: ch.channelId, agentId: (await manager.createAgent({ name: 'w1', harness: 'mock', config: { delayMs: 30 } })).id, role: 'worker' })
307
+ attachScheduler(manager, ch.channelId)
308
+
309
+ const engine = getEngine(manager)
310
+ const task = await manager.submitChannelTask({
311
+ channelId: ch.channelId,
312
+ title: 'goal 任务',
313
+ description: '需要完成的目标',
314
+ mode: 'goal',
315
+ modeConfig: { goalCriteria: '任务完成' },
316
+ })
317
+
318
+ await waitUntil(() => {
319
+ const t = engine.get(task.id)
320
+ return t?.state === 'COMPLETED' || t?.state === 'CANCELED'
321
+ }, 10_000)
322
+
323
+ check('goal 任务 COMPLETED', engine.get(task.id)?.state === 'COMPLETED')
324
+
325
+ // 验证 description 编码了模式
326
+ const final = engine.get(task.id)
327
+ check('goal description 有 [mode:goal] 前缀', final?.description?.includes('[mode:goal]') ?? false, final?.description?.slice(0, 40))
328
+ }
329
+ finally {
330
+ await cleanup(manager, db)
331
+ }
332
+ }
333
+
334
+ // ===== 测试 F: loop 模式 =====
335
+ async function testLoopMode(): Promise<void> {
336
+ console.log('\n━━━ 测试 F: loop 模式(mock)━━━')
337
+
338
+ const { manager, db } = setup()
339
+
340
+ try {
341
+ const ch = await manager.createChannel({
342
+ name: 'loop 测试',
343
+ leadAgent: { name: 'lead', harness: 'mock', config: { delayMs: 20 } },
344
+ })
345
+ await manager.addAgentToChannel({ channelId: ch.channelId, agentId: (await manager.createAgent({ name: 'w1', harness: 'mock', config: { delayMs: 20 } })).id, role: 'worker' })
346
+ attachScheduler(manager, ch.channelId, 100)
347
+
348
+ const engine = getEngine(manager)
349
+ const task = await manager.submitChannelTask({
350
+ channelId: ch.channelId,
351
+ title: '循环任务',
352
+ description: 'loop test',
353
+ mode: 'loop',
354
+ modeConfig: { intervalMs: 1000, maxIterations: 2 },
355
+ })
356
+
357
+ // 等第一次完成
358
+ await waitUntil(() => engine.get(task.id)?.state === 'COMPLETED', 10_000)
359
+ check('loop 第一次执行完成', engine.get(task.id)?.state === 'COMPLETED')
360
+
361
+ // 等循环重放
362
+ await sleep(2500)
363
+ const allTasks = engine.list(ch.channelId).filter(t => t.title === '循环任务')
364
+ check('loop 循环重放生成新任务', allTasks.length >= 2, `任务数=${allTasks.length}`)
365
+ }
366
+ finally {
367
+ await cleanup(manager, db)
368
+ }
369
+ }
370
+
371
+ // ===== 测试 G: pipeline 模式 =====
372
+ async function testPipelineMode(): Promise<void> {
373
+ console.log('\n━━━ 测试 G: pipeline 模式(mock)━━━')
374
+
375
+ const { manager, db } = setup()
376
+
377
+ try {
378
+ const ch = await manager.createChannel({
379
+ name: 'pipeline 测试',
380
+ leadAgent: { name: 'lead', harness: 'mock', config: { delayMs: 30 } },
381
+ })
382
+ await manager.addAgentToChannel({ channelId: ch.channelId, agentId: (await manager.createAgent({ name: 'w1', harness: 'mock', config: { delayMs: 30 } })).id, role: 'worker' })
383
+ attachScheduler(manager, ch.channelId)
384
+
385
+ const engine = getEngine(manager)
386
+ const task = await manager.submitChannelTask({
387
+ channelId: ch.channelId,
388
+ title: '流水线任务',
389
+ description: 'pipeline test',
390
+ mode: 'pipeline',
391
+ modeConfig: {
392
+ stages: [
393
+ { name: '阶段1', description: '第一步' },
394
+ { name: '阶段2', description: '第二步' },
395
+ ],
396
+ },
397
+ })
398
+
399
+ await waitUntil(() => {
400
+ const t = engine.get(task.id)
401
+ return t?.state === 'COMPLETED' || t?.state === 'CANCELED'
402
+ }, 10_000)
403
+
404
+ const allTasks = engine.list(ch.channelId)
405
+ const children = allTasks.filter(t => t.parentId === task.id)
406
+
407
+ check('pipeline 主任务完成', ['COMPLETED', 'CANCELED'].includes(engine.get(task.id)?.state ?? ''), `state=${engine.get(task.id)?.state}`)
408
+ check('pipeline 生成了子任务', children.length >= 1, `子任务数=${children.length}`)
409
+ check('pipeline description 有 [mode:pipeline]', engine.get(task.id)?.description?.includes('[mode:pipeline]') ?? false)
410
+ }
411
+ finally {
412
+ await cleanup(manager, db)
413
+ }
414
+ }
415
+
416
+ // ===== main =====
417
+ async function main(): Promise<void> {
418
+ console.log('╔════════════════════════════════════════════════════════════╗')
419
+ console.log('║ 系统全面验证测试 — 所有子系统 ║')
420
+ console.log('╚════════════════════════════════════════════════════════════╝')
421
+
422
+ await testTaskQueueOrder()
423
+ await testImmediateMessage()
424
+ await testLeadDispatchCycle()
425
+ await testWorkerToWorkerComm()
426
+ await testGoalMode()
427
+ await testLoopMode()
428
+ await testPipelineMode()
429
+
430
+ console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
431
+ console.log(` ${failures === 0 ? `🎉 全部通过(${testCount} 项检查)` : `❌ ${failures}/${testCount} 项失败`}`)
432
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
433
+ process.exit(failures === 0 ? 0 : 1)
434
+ }
435
+
436
+ main().catch((e) => {
437
+ console.error('测试异常:', e)
438
+ process.exit(1)
439
+ })