@downcity/agent 1.1.401 → 1.1.408
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/README.md +4 -3
- package/bin/agent/WorkspaceEntry.js +1 -1
- package/bin/agent/WorkspaceEntry.js.map +1 -1
- package/bin/city/types/PluginHostContext.d.ts +3 -0
- package/bin/city/types/PluginHostContext.d.ts.map +1 -1
- package/bin/executor/core-engine/CoreEngineError.js +8 -2
- package/bin/executor/core-engine/CoreEngineError.js.map +1 -1
- package/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -1
- package/bin/executor/core-engine/CoreEngineRunner.js +36 -0
- package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
- package/bin/executor/model/ModelGenerate.d.ts.map +1 -1
- package/bin/executor/model/ModelGenerate.js +2 -1
- package/bin/executor/model/ModelGenerate.js.map +1 -1
- package/bin/executor/model/ModelStepRunner.d.ts +2 -0
- package/bin/executor/model/ModelStepRunner.d.ts.map +1 -1
- package/bin/executor/model/ModelStepRunner.js +33 -7
- package/bin/executor/model/ModelStepRunner.js.map +1 -1
- package/bin/executor/model/ModelStreamFailure.d.ts +22 -0
- package/bin/executor/model/ModelStreamFailure.d.ts.map +1 -0
- package/bin/executor/model/ModelStreamFailure.js +35 -0
- package/bin/executor/model/ModelStreamFailure.js.map +1 -0
- package/bin/group/GroupDispatchSession.d.ts +36 -0
- package/bin/group/GroupDispatchSession.d.ts.map +1 -0
- package/bin/group/GroupDispatchSession.js +154 -0
- package/bin/group/GroupDispatchSession.js.map +1 -0
- package/bin/group/GroupSession.d.ts +3 -1
- package/bin/group/GroupSession.d.ts.map +1 -1
- package/bin/group/GroupSession.js +65 -28
- package/bin/group/GroupSession.js.map +1 -1
- package/bin/index.d.ts +2 -0
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js.map +1 -1
- package/bin/session/messages/SessionAssistantMessageWriter.d.ts.map +1 -1
- package/bin/session/messages/SessionAssistantMessageWriter.js +10 -6
- package/bin/session/messages/SessionAssistantMessageWriter.js.map +1 -1
- package/bin/types/group/DispatchStrategy.d.ts +3 -0
- package/bin/types/group/DispatchStrategy.d.ts.map +1 -1
- package/bin/types/group/DispatchStrategy.js +66 -18
- package/bin/types/group/DispatchStrategy.js.map +1 -1
- package/bin/types/group/GroupDispatchSession.d.ts +65 -0
- package/bin/types/group/GroupDispatchSession.d.ts.map +1 -0
- package/bin/types/group/GroupDispatchSession.js +8 -0
- package/bin/types/group/GroupDispatchSession.js.map +1 -0
- package/bin/types/group/GroupSession.d.ts +2 -0
- package/bin/types/group/GroupSession.d.ts.map +1 -1
- package/bin/types/group/GroupSessionStore.d.ts +6 -1
- package/bin/types/group/GroupSessionStore.d.ts.map +1 -1
- package/bin/types/plugin/PluginNotification.d.ts +31 -0
- package/bin/types/plugin/PluginNotification.d.ts.map +1 -0
- package/bin/types/plugin/PluginNotification.js +8 -0
- package/bin/types/plugin/PluginNotification.js.map +1 -0
- package/bin/workspace/store/LocalGroupDispatchSessionDataStore.d.ts +28 -0
- package/bin/workspace/store/LocalGroupDispatchSessionDataStore.d.ts.map +1 -0
- package/bin/workspace/store/LocalGroupDispatchSessionDataStore.js +102 -0
- package/bin/workspace/store/LocalGroupDispatchSessionDataStore.js.map +1 -0
- package/bin/workspace/store/LocalGroupSessionDataStore.d.ts +2 -0
- package/bin/workspace/store/LocalGroupSessionDataStore.d.ts.map +1 -1
- package/bin/workspace/store/LocalGroupSessionDataStore.js +25 -8
- package/bin/workspace/store/LocalGroupSessionDataStore.js.map +1 -1
- package/city-test/group.test.mjs +252 -1
- package/package.json +4 -4
- package/scripts/agent-multi-workspace.test.mjs +67 -0
- package/scripts/core-engine-context-compaction.test.mjs +49 -0
- package/scripts/core-engine-model-stream.test.mjs +69 -0
- package/src/agent/WorkspaceEntry.ts +1 -1
- package/src/city/types/PluginHostContext.ts +4 -0
- package/src/executor/core-engine/CoreEngineError.ts +8 -2
- package/src/executor/core-engine/CoreEngineRunner.ts +44 -0
- package/src/executor/model/ModelGenerate.ts +3 -1
- package/src/executor/model/ModelStepRunner.ts +36 -5
- package/src/executor/model/ModelStreamFailure.ts +44 -0
- package/src/group/GroupDispatchSession.ts +175 -0
- package/src/group/GroupSession.ts +83 -28
- package/src/index.ts +10 -0
- package/src/session/messages/SessionAssistantMessageWriter.ts +9 -5
- package/src/types/group/DispatchStrategy.ts +79 -19
- package/src/types/group/GroupDispatchSession.ts +80 -0
- package/src/types/group/GroupSession.ts +2 -0
- package/src/types/group/GroupSessionStore.ts +6 -1
- package/src/types/plugin/PluginNotification.ts +38 -0
- package/src/workspace/store/LocalGroupDispatchSessionDataStore.ts +118 -0
- package/src/workspace/store/LocalGroupSessionDataStore.ts +35 -9
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -17,6 +17,11 @@ import type { DispatchDecision, DispatchNode, DispatchStrategy } from "@/types/g
|
|
|
17
17
|
import type { WorkspaceBase } from "@downcity/workspace";
|
|
18
18
|
import type { GroupSessionDataStore } from "@/types/group/GroupSessionStore.js";
|
|
19
19
|
import type { RespondSessionInteractionInput } from "@/types/session/SessionInteraction.js";
|
|
20
|
+
import {
|
|
21
|
+
GroupDispatchSession,
|
|
22
|
+
GroupDispatchStoppedError,
|
|
23
|
+
} from "@/group/GroupDispatchSession.js";
|
|
24
|
+
import type { GroupDispatchSessionResult } from "@/types/group/GroupDispatchSession.js";
|
|
20
25
|
|
|
21
26
|
const max_auto_dispatch_count = 32;
|
|
22
27
|
|
|
@@ -47,7 +52,7 @@ export class GroupSession implements GroupSessionContract {
|
|
|
47
52
|
private readonly group_name: string;
|
|
48
53
|
private readonly instruction?: string;
|
|
49
54
|
private readonly members: readonly Agent[];
|
|
50
|
-
private readonly
|
|
55
|
+
private readonly dispatch_session: GroupDispatchSession;
|
|
51
56
|
private readonly workspace?: WorkspaceBase;
|
|
52
57
|
private readonly messages_by_id: GroupMessage[] = [];
|
|
53
58
|
private readonly subscribers = new Set<GroupEventSubscriber>();
|
|
@@ -77,7 +82,9 @@ export class GroupSession implements GroupSessionContract {
|
|
|
77
82
|
this.group_name = options.group_name;
|
|
78
83
|
this.instruction = options.instruction;
|
|
79
84
|
this.members = options.members;
|
|
80
|
-
this.
|
|
85
|
+
this.dispatch_session = new GroupDispatchSession({
|
|
86
|
+
dispatch_strategy: options.dispatch_strategy,
|
|
87
|
+
});
|
|
81
88
|
this.workspace = options.workspace;
|
|
82
89
|
this.workspace_id = options.workspace?.id;
|
|
83
90
|
}
|
|
@@ -90,6 +97,7 @@ export class GroupSession implements GroupSessionContract {
|
|
|
90
97
|
if (this.store === store) return this;
|
|
91
98
|
this.store = store;
|
|
92
99
|
await store.initialize();
|
|
100
|
+
await this.dispatch_session.initialize(store.dispatch_session);
|
|
93
101
|
const metadata = await store.read_metadata();
|
|
94
102
|
if (metadata.group_id !== this.group_id) {
|
|
95
103
|
throw new Error(`GroupSession "${this.id}" belongs to another Group`);
|
|
@@ -112,7 +120,8 @@ export class GroupSession implements GroupSessionContract {
|
|
|
112
120
|
turn_id: checkpoint.turn_id,
|
|
113
121
|
root_message_id: checkpoint.root_message_id,
|
|
114
122
|
context_message_ids: checkpoint.context_message_ids,
|
|
115
|
-
|
|
123
|
+
dispatch_stage: checkpoint.dispatch_stage,
|
|
124
|
+
auto_pending: checkpoint.dispatch_stage === "auto",
|
|
116
125
|
stopped: false,
|
|
117
126
|
recovered: true,
|
|
118
127
|
});
|
|
@@ -151,7 +160,12 @@ export class GroupSession implements GroupSessionContract {
|
|
|
151
160
|
if (Object.keys(valid_member_session_ids).length !== Object.keys(member_session_ids).length) {
|
|
152
161
|
await store.update_metadata({ member_session_ids: valid_member_session_ids });
|
|
153
162
|
}
|
|
154
|
-
|
|
163
|
+
for (const group_turn of this.group_turns_by_id.values()) {
|
|
164
|
+
if (group_turn.dispatch_stage !== "user") continue;
|
|
165
|
+
const message = this.messages_by_id.find((item) => item.id === group_turn.root_message_id);
|
|
166
|
+
if (message) this.start_user_dispatch(message, group_turn);
|
|
167
|
+
}
|
|
168
|
+
if ([...this.group_turns_by_id.values()].some((turn) => turn.auto_pending) || this.auto_frontier_messages.length > 0) {
|
|
155
169
|
this.request_auto_dispatch();
|
|
156
170
|
}
|
|
157
171
|
return this;
|
|
@@ -169,19 +183,14 @@ export class GroupSession implements GroupSessionContract {
|
|
|
169
183
|
turn_id,
|
|
170
184
|
root_message_id: message.id,
|
|
171
185
|
context_message_ids: [...context_message_ids, message.id],
|
|
186
|
+
dispatch_stage: "user",
|
|
172
187
|
auto_pending: false,
|
|
173
188
|
stopped: false,
|
|
174
189
|
};
|
|
175
190
|
this.group_turns_by_id.set(turn_id, group_turn);
|
|
176
191
|
await this.persist_dispatch_checkpoint();
|
|
177
192
|
this.publish_status(turn_id, "dispatching", { message_id: message.id });
|
|
178
|
-
|
|
179
|
-
group_turn.user_dispatch = dispatch;
|
|
180
|
-
this.pending_deliveries.add(dispatch);
|
|
181
|
-
void dispatch.finally(() => {
|
|
182
|
-
this.pending_deliveries.delete(dispatch);
|
|
183
|
-
if (group_turn.auto_pending) this.request_auto_dispatch();
|
|
184
|
-
});
|
|
193
|
+
this.start_user_dispatch(message, group_turn);
|
|
185
194
|
return { turn_id, success: true, message_count: this.messages_by_id.length };
|
|
186
195
|
}
|
|
187
196
|
|
|
@@ -217,7 +226,10 @@ export class GroupSession implements GroupSessionContract {
|
|
|
217
226
|
if (this.disposed) return;
|
|
218
227
|
this.stop_requested = true;
|
|
219
228
|
this.publish_status(undefined, "stopped");
|
|
220
|
-
await Promise.allSettled([
|
|
229
|
+
await Promise.allSettled([
|
|
230
|
+
this.dispatch_session.stop(),
|
|
231
|
+
...[...this.member_sessions.values()].map((session) => session.stop()),
|
|
232
|
+
]);
|
|
221
233
|
await Promise.allSettled([...this.pending_deliveries]);
|
|
222
234
|
if (this.auto_dispatch_promise) await this.auto_dispatch_promise;
|
|
223
235
|
this.auto_dispatch_requested = false;
|
|
@@ -232,6 +244,7 @@ export class GroupSession implements GroupSessionContract {
|
|
|
232
244
|
async dispose(): Promise<void> {
|
|
233
245
|
if (this.disposed) return;
|
|
234
246
|
await this.stop();
|
|
247
|
+
await this.dispatch_session.dispose();
|
|
235
248
|
this.disposed = true;
|
|
236
249
|
this.subscribers.clear();
|
|
237
250
|
for (const unsubscribe of this.member_session_unsubscribes.values()) unsubscribe();
|
|
@@ -250,28 +263,53 @@ export class GroupSession implements GroupSessionContract {
|
|
|
250
263
|
const group_turn = this.group_turns_by_id.get(turn_id);
|
|
251
264
|
if (!group_turn) return;
|
|
252
265
|
try {
|
|
253
|
-
const
|
|
266
|
+
const dispatch = await this.decide_dispatch("user", message, [message]);
|
|
254
267
|
const context_messages = group_turn.context_message_ids
|
|
255
268
|
.map((message_id) => this.messages_by_id.find((item) => item.id === message_id))
|
|
256
269
|
.filter((item): item is GroupMessage => Boolean(item));
|
|
257
|
-
await this.run_dispatch_plan(
|
|
270
|
+
await this.run_dispatch_plan(
|
|
271
|
+
dispatch.decision,
|
|
272
|
+
message,
|
|
273
|
+
context_messages,
|
|
274
|
+
turn_id,
|
|
275
|
+
dispatch.dispatch_id,
|
|
276
|
+
);
|
|
258
277
|
if (this.stop_requested || this.disposed) {
|
|
259
278
|
group_turn.stopped = true;
|
|
260
279
|
this.group_turns_by_id.delete(turn_id);
|
|
261
280
|
await this.persist_dispatch_checkpoint();
|
|
262
281
|
return;
|
|
263
282
|
}
|
|
264
|
-
|
|
283
|
+
if (dispatch.decision.terminal) {
|
|
284
|
+
this.group_turns_by_id.delete(turn_id);
|
|
285
|
+
} else {
|
|
286
|
+
group_turn.dispatch_stage = "auto";
|
|
287
|
+
group_turn.auto_pending = true;
|
|
288
|
+
}
|
|
265
289
|
await this.persist_dispatch_checkpoint();
|
|
290
|
+
if (dispatch.decision.terminal) this.publish_status(turn_id, "idle");
|
|
266
291
|
} catch (error) {
|
|
267
292
|
group_turn.stopped = this.stop_requested;
|
|
268
|
-
|
|
293
|
+
if (!(error instanceof GroupDispatchStoppedError)) {
|
|
294
|
+
await this.append_dispatch_failure(message, error);
|
|
295
|
+
}
|
|
269
296
|
this.group_turns_by_id.delete(turn_id);
|
|
270
297
|
await this.persist_dispatch_checkpoint();
|
|
271
298
|
this.publish_status(turn_id, this.stop_requested ? "stopped" : "failed");
|
|
272
299
|
}
|
|
273
300
|
}
|
|
274
301
|
|
|
302
|
+
/** 登记一次用户调度运行;新建和恢复必须经过同一个生命周期入口。 */
|
|
303
|
+
private start_user_dispatch(message: GroupMessage, group_turn: GroupTurnRuntime): void {
|
|
304
|
+
const dispatch = this.run_user_dispatch(message, group_turn.turn_id);
|
|
305
|
+
group_turn.user_dispatch = dispatch;
|
|
306
|
+
this.pending_deliveries.add(dispatch);
|
|
307
|
+
void dispatch.finally(() => {
|
|
308
|
+
this.pending_deliveries.delete(dispatch);
|
|
309
|
+
if (group_turn.auto_pending) this.request_auto_dispatch();
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
275
313
|
/** GroupSession 唯一的自动调度循环,统一消费已完成成员产生的新消息。 */
|
|
276
314
|
private request_auto_dispatch(): void {
|
|
277
315
|
if (this.stop_requested || this.disposed) return;
|
|
@@ -299,8 +337,8 @@ export class GroupSession implements GroupSessionContract {
|
|
|
299
337
|
const message = frontier_messages[frontier_messages.length - 1];
|
|
300
338
|
this.publish_status(undefined, "dispatching", { message_id: message.id });
|
|
301
339
|
try {
|
|
302
|
-
const
|
|
303
|
-
const dispatch_key = JSON.stringify(decision);
|
|
340
|
+
const dispatch = await this.decide_dispatch("auto", message, frontier_messages);
|
|
341
|
+
const dispatch_key = JSON.stringify(dispatch.decision);
|
|
304
342
|
if (this.auto_dispatch_path_keys.has(dispatch_key)) {
|
|
305
343
|
await this.append_message({ sender_type: "system", sender_id: "system", text: "Group auto dispatch detected a repeated path.", reply_to: message.id });
|
|
306
344
|
frontier_messages = [];
|
|
@@ -311,8 +349,14 @@ export class GroupSession implements GroupSessionContract {
|
|
|
311
349
|
continue;
|
|
312
350
|
}
|
|
313
351
|
this.auto_dispatch_path_keys.add(dispatch_key);
|
|
314
|
-
frontier_messages = await this.run_dispatch_plan(
|
|
315
|
-
|
|
352
|
+
frontier_messages = await this.run_dispatch_plan(
|
|
353
|
+
dispatch.decision,
|
|
354
|
+
message,
|
|
355
|
+
this.messages_by_id,
|
|
356
|
+
undefined,
|
|
357
|
+
dispatch.dispatch_id,
|
|
358
|
+
);
|
|
359
|
+
if (dispatch.decision.terminal || frontier_messages.length === 0) {
|
|
316
360
|
frontier_messages = [];
|
|
317
361
|
this.auto_frontier_messages = [];
|
|
318
362
|
this.auto_dispatch_path_keys.clear();
|
|
@@ -324,6 +368,7 @@ export class GroupSession implements GroupSessionContract {
|
|
|
324
368
|
this.auto_dispatch_requested = true;
|
|
325
369
|
}
|
|
326
370
|
} catch (error) {
|
|
371
|
+
if (error instanceof GroupDispatchStoppedError) return;
|
|
327
372
|
await this.append_dispatch_failure(message, error);
|
|
328
373
|
this.publish_status(undefined, "failed");
|
|
329
374
|
frontier_messages = [];
|
|
@@ -361,16 +406,21 @@ export class GroupSession implements GroupSessionContract {
|
|
|
361
406
|
batch_turns.forEach((group_turn) => { group_turn.auto_pending = false; });
|
|
362
407
|
for (const message of candidate_messages) this.consumed_auto_message_ids.add(message.id);
|
|
363
408
|
await this.persist_dispatch_checkpoint();
|
|
364
|
-
const dispatch_id = `group-dispatch-${nanoid(12)}`;
|
|
365
409
|
dispatch_count = 0;
|
|
366
410
|
const message = candidate_messages[candidate_messages.length - 1];
|
|
367
411
|
this.publish_status(undefined, "dispatching", { message_id: message.id });
|
|
368
412
|
try {
|
|
369
|
-
const
|
|
413
|
+
const dispatch = await this.decide_dispatch("auto", message, pending_messages);
|
|
370
414
|
const context_messages = this.messages_by_id.filter((item) => item.turn_id && batch_turn_ids.has(item.turn_id));
|
|
371
|
-
const outputs = await this.run_dispatch_plan(
|
|
415
|
+
const outputs = await this.run_dispatch_plan(
|
|
416
|
+
dispatch.decision,
|
|
417
|
+
message,
|
|
418
|
+
context_messages,
|
|
419
|
+
undefined,
|
|
420
|
+
dispatch.dispatch_id,
|
|
421
|
+
);
|
|
372
422
|
for (const turn_id of batch_turn_ids) this.group_turns_by_id.delete(turn_id);
|
|
373
|
-
if (decision.terminal || outputs.length === 0) {
|
|
423
|
+
if (dispatch.decision.terminal || outputs.length === 0) {
|
|
374
424
|
this.auto_frontier_messages = [];
|
|
375
425
|
await this.persist_dispatch_checkpoint();
|
|
376
426
|
this.publish_status(undefined, "idle");
|
|
@@ -381,6 +431,7 @@ export class GroupSession implements GroupSessionContract {
|
|
|
381
431
|
this.auto_dispatch_requested = true;
|
|
382
432
|
}
|
|
383
433
|
} catch (error) {
|
|
434
|
+
if (error instanceof GroupDispatchStoppedError) return;
|
|
384
435
|
await this.append_dispatch_failure(message, error);
|
|
385
436
|
this.publish_status(undefined, "failed");
|
|
386
437
|
for (const turn_id of batch_turn_ids) this.group_turns_by_id.delete(turn_id);
|
|
@@ -394,8 +445,8 @@ export class GroupSession implements GroupSessionContract {
|
|
|
394
445
|
trigger: "user" | "auto",
|
|
395
446
|
message: GroupMessage,
|
|
396
447
|
pending_messages: readonly GroupMessage[],
|
|
397
|
-
): Promise<
|
|
398
|
-
return await this.
|
|
448
|
+
): Promise<GroupDispatchSessionResult> {
|
|
449
|
+
return await this.dispatch_session.decide({
|
|
399
450
|
trigger,
|
|
400
451
|
message,
|
|
401
452
|
pending_messages,
|
|
@@ -537,7 +588,10 @@ export class GroupSession implements GroupSessionContract {
|
|
|
537
588
|
await this.append_member_failure(agent, message, result.error || "未知错误");
|
|
538
589
|
return null;
|
|
539
590
|
}
|
|
540
|
-
if (!result.text?.trim())
|
|
591
|
+
if (!result.text?.trim()) {
|
|
592
|
+
await this.append_member_failure(agent, message, "未生成有效回复");
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
541
595
|
const reply = await this.append_message({
|
|
542
596
|
sender_type: "agent",
|
|
543
597
|
sender_id: agent.id,
|
|
@@ -653,7 +707,7 @@ export class GroupSession implements GroupSessionContract {
|
|
|
653
707
|
`Current message from ${message.sender_type === "agent" ? message.sender_id : "user"}: ${message.text}`,
|
|
654
708
|
dispatch_instruction,
|
|
655
709
|
`你只能代表成员 ${agent.id} 自己发言。不得代替、指挥、裁定、总结或转述其他成员;不得假装自己是其他成员;不得创建队长、裁判或协调者。`,
|
|
656
|
-
"
|
|
710
|
+
"你已被 Group 调度选中,必须直接回复当前消息。",
|
|
657
711
|
].filter(Boolean).join("\n");
|
|
658
712
|
}
|
|
659
713
|
|
|
@@ -695,6 +749,7 @@ export class GroupSession implements GroupSessionContract {
|
|
|
695
749
|
turn_id: group_turn.turn_id,
|
|
696
750
|
root_message_id: group_turn.root_message_id,
|
|
697
751
|
context_message_ids: [...group_turn.context_message_ids],
|
|
752
|
+
dispatch_stage: group_turn.dispatch_stage,
|
|
698
753
|
})),
|
|
699
754
|
auto_frontier_message_ids: this.auto_frontier_messages.map((message) => message.id),
|
|
700
755
|
});
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,11 @@ export type {
|
|
|
26
26
|
GroupSessionHistoryMeta,
|
|
27
27
|
GroupSessionStore,
|
|
28
28
|
} from "./types/group/GroupSessionStore.js";
|
|
29
|
+
export type {
|
|
30
|
+
GroupDispatchSessionDataStore,
|
|
31
|
+
GroupDispatchTurnRecord,
|
|
32
|
+
GroupDispatchTurnStatus,
|
|
33
|
+
} from "./types/group/GroupDispatchSession.js";
|
|
29
34
|
export { GroupSession, GroupSessions } from "./group/index.js";
|
|
30
35
|
export type {
|
|
31
36
|
GroupMemberRuntime,
|
|
@@ -238,6 +243,11 @@ export type {
|
|
|
238
243
|
} from "./types/sdk/AgentSessionCompact.js";
|
|
239
244
|
export type { PluginContext } from "./types/plugin/PluginContext.js";
|
|
240
245
|
export type { AgentPluginContext } from "./types/plugin/AgentPluginContext.js";
|
|
246
|
+
export type {
|
|
247
|
+
PluginNotificationInput,
|
|
248
|
+
PluginNotificationPublisher,
|
|
249
|
+
PluginNotificationTopicInput,
|
|
250
|
+
} from "./types/plugin/PluginNotification.js";
|
|
241
251
|
export type {
|
|
242
252
|
PluginWebServices,
|
|
243
253
|
} from "./types/plugin/PluginServices.js";
|
|
@@ -215,7 +215,8 @@ export class SessionAssistantMessageWriter {
|
|
|
215
215
|
/** 在单写队列中应用标准模型事件。 */
|
|
216
216
|
private async apply_model_event_serialized(event: ModelStreamEvent): Promise<void> {
|
|
217
217
|
if (this.closed) throw new Error("Assistant Message writer is closed");
|
|
218
|
-
|
|
218
|
+
// model_error 由 Executor 的恢复策略消费;Writer 保留已经写入的草稿事实。
|
|
219
|
+
if (event.type === "model_error") return;
|
|
219
220
|
if (event.type === "text_start" || event.type === "reasoning_start") {
|
|
220
221
|
const type = event.type === "text_start" ? "text" : "reasoning";
|
|
221
222
|
const part_id = `${type}:${generate_id()}`;
|
|
@@ -283,8 +284,9 @@ export class SessionAssistantMessageWriter {
|
|
|
283
284
|
const tool = this.require_tool(tool_call_id);
|
|
284
285
|
await this.upsert_tool(tool_call_id, {
|
|
285
286
|
tool_name: tool.tool_name,
|
|
286
|
-
state: "ready",
|
|
287
|
+
state: event.input_error ? "failed" : "ready",
|
|
287
288
|
input: event.input,
|
|
289
|
+
...(event.input_error ? { error: event.input_error } : {}),
|
|
288
290
|
});
|
|
289
291
|
}
|
|
290
292
|
}
|
|
@@ -435,9 +437,11 @@ export class SessionAssistantMessageWriter {
|
|
|
435
437
|
|
|
436
438
|
/** 从 Tool 失败输出中提取稳定错误文本。 */
|
|
437
439
|
function read_tool_error(output: unknown): string {
|
|
438
|
-
if (output && typeof output === "object"
|
|
439
|
-
const
|
|
440
|
-
|
|
440
|
+
if (output && typeof output === "object") {
|
|
441
|
+
const result = output as { error?: unknown; message?: unknown; output?: unknown };
|
|
442
|
+
for (const candidate of [result.error, result.message, result.output]) {
|
|
443
|
+
if (typeof candidate === "string" && candidate.trim()) return candidate;
|
|
444
|
+
}
|
|
441
445
|
}
|
|
442
446
|
return "Tool execution failed";
|
|
443
447
|
}
|
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
import type { GroupMessage } from "@/types/group/Group.js";
|
|
4
4
|
import type { Agent } from "@/agent/Agent.js";
|
|
5
5
|
import type { AgentModel } from "@/agent/AgentModel.js";
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ModelClient,
|
|
8
|
+
ModelContent,
|
|
9
|
+
ModelJsonValue,
|
|
10
|
+
ModelMessage,
|
|
11
|
+
} from "@downcity/type";
|
|
7
12
|
import { generate_model } from "@executor/model/ModelGenerate.js";
|
|
8
13
|
import { z } from "zod";
|
|
9
14
|
|
|
@@ -49,6 +54,8 @@ export interface DispatchStrategy {
|
|
|
49
54
|
readonly messages: readonly GroupMessage[];
|
|
50
55
|
/** 当前 Group 成员快照。 */
|
|
51
56
|
readonly members: readonly Agent[];
|
|
57
|
+
/** 当前 Dispatch Session Turn 的取消信号。 */
|
|
58
|
+
readonly abort_signal: AbortSignal;
|
|
52
59
|
}): Promise<DispatchDecision> | DispatchDecision;
|
|
53
60
|
}
|
|
54
61
|
|
|
@@ -65,6 +72,8 @@ const dispatch_group_input_schema = z.object({
|
|
|
65
72
|
next: z.enum(["stop", "continue"]),
|
|
66
73
|
});
|
|
67
74
|
|
|
75
|
+
const max_dispatch_model_steps = 3;
|
|
76
|
+
|
|
68
77
|
/** 使用 Group.model 理解群聊意图并通过内部 tool call 生成成员投递决定。 */
|
|
69
78
|
export class AiDispatchStrategy implements DispatchStrategy {
|
|
70
79
|
private readonly model?: ModelClient;
|
|
@@ -79,29 +88,45 @@ export class AiDispatchStrategy implements DispatchStrategy {
|
|
|
79
88
|
readonly pending_messages: readonly GroupMessage[];
|
|
80
89
|
readonly messages: readonly GroupMessage[];
|
|
81
90
|
readonly members: readonly Agent[];
|
|
91
|
+
readonly abort_signal: AbortSignal;
|
|
82
92
|
}): Promise<DispatchDecision> {
|
|
83
93
|
if (!this.model) {
|
|
84
94
|
throw new Error("Group requires a configured model for dispatch");
|
|
85
95
|
}
|
|
86
96
|
try {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
const messages: ModelMessage[] = [
|
|
98
|
+
{ role: "system", content: [{ type: "text", text: "你是群聊消息调度器。你只能调用 dispatch_group,不回答用户问题。普通任务默认只选择一个最合适的成员;只有用户明确要求多人分别回答,或确实存在并行的独立工作时,才把多个成员放在同一阶段。外层 steps 阶段按顺序执行,后一个阶段等待前一个阶段完成。只有真实存在的成员才能被选择。" }] },
|
|
99
|
+
{ role: "user", content: [{ type: "text", text: build_dispatch_prompt(input) }] },
|
|
100
|
+
];
|
|
101
|
+
let protocol_error = "Group dispatch model did not call dispatch_group";
|
|
102
|
+
for (let step_index = 0; step_index < max_dispatch_model_steps; step_index += 1) {
|
|
103
|
+
const result = await generate_model(this.model, {
|
|
104
|
+
messages,
|
|
105
|
+
tools: [{
|
|
106
|
+
name: "dispatch_group",
|
|
107
|
+
description: "提交当前 Group 的成员投递路径。只调用一次;不要输出普通文本。没有成员需要回复时使用空 steps 和 next=stop。",
|
|
108
|
+
input_schema: z.toJSONSchema(dispatch_group_input_schema) as Record<string, ModelJsonValue>,
|
|
109
|
+
}],
|
|
110
|
+
tool_choice: { type: "tool", tool_name: "dispatch_group" },
|
|
111
|
+
max_output_tokens: 600,
|
|
112
|
+
reasoning: { enabled: false },
|
|
113
|
+
}, input.abort_signal);
|
|
114
|
+
const dispatch_call = result.tool_calls.length === 1 && result.tool_calls[0]?.tool_name === "dispatch_group"
|
|
115
|
+
? result.tool_calls[0]
|
|
116
|
+
: undefined;
|
|
117
|
+
if (dispatch_call) {
|
|
118
|
+
try {
|
|
119
|
+
return normalize_dispatch_tool_input(dispatch_call.input, input.members);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
protocol_error = error instanceof Error ? error.message : String(error);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
protocol_error = "Group dispatch model did not call dispatch_group exactly once";
|
|
125
|
+
}
|
|
126
|
+
if (step_index + 1 >= max_dispatch_model_steps) break;
|
|
127
|
+
append_dispatch_retry_messages(messages, result.text, result.tool_calls, protocol_error);
|
|
103
128
|
}
|
|
104
|
-
|
|
129
|
+
throw new Error(`${protocol_error} after ${max_dispatch_model_steps} attempts`);
|
|
105
130
|
} catch (error) {
|
|
106
131
|
const detail = error instanceof Error ? error.message : String(error);
|
|
107
132
|
throw new Error(`Group dispatch model failed: ${detail}`, { cause: error });
|
|
@@ -109,6 +134,41 @@ export class AiDispatchStrategy implements DispatchStrategy {
|
|
|
109
134
|
}
|
|
110
135
|
}
|
|
111
136
|
|
|
137
|
+
/** 把协议错误作为下一模型 step 的显式上下文,允许模型在同一调度 Turn 内纠正。 */
|
|
138
|
+
function append_dispatch_retry_messages(
|
|
139
|
+
messages: ModelMessage[],
|
|
140
|
+
text: string,
|
|
141
|
+
tool_calls: readonly { type: "tool_call"; tool_call_id: string; tool_name: string; input: ModelJsonValue }[],
|
|
142
|
+
error: string,
|
|
143
|
+
): void {
|
|
144
|
+
const assistant_content: ModelContent[] = [
|
|
145
|
+
...(text.trim() ? [{ type: "text" as const, text }] : []),
|
|
146
|
+
...tool_calls,
|
|
147
|
+
];
|
|
148
|
+
if (assistant_content.length > 0) {
|
|
149
|
+
messages.push({ role: "assistant", content: assistant_content });
|
|
150
|
+
}
|
|
151
|
+
if (tool_calls.length > 0) {
|
|
152
|
+
messages.push({
|
|
153
|
+
role: "tool",
|
|
154
|
+
content: tool_calls.map((tool_call) => ({
|
|
155
|
+
type: "tool_result" as const,
|
|
156
|
+
tool_call_id: tool_call.tool_call_id,
|
|
157
|
+
tool_name: tool_call.tool_name,
|
|
158
|
+
outcome: "failed" as const,
|
|
159
|
+
content: [{ type: "text" as const, text: error }],
|
|
160
|
+
})),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
messages.push({
|
|
164
|
+
role: "user",
|
|
165
|
+
content: [{
|
|
166
|
+
type: "text",
|
|
167
|
+
text: `上一响应不符合调度协议:${error}。请只调用一次 dispatch_group。`,
|
|
168
|
+
}],
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
112
172
|
/** 为 AI 调度器构造稳定、有限长度的群聊上下文。 */
|
|
113
173
|
function build_dispatch_prompt(input: {
|
|
114
174
|
readonly trigger: DispatchTrigger;
|
|
@@ -152,7 +212,7 @@ function normalize_dispatch_tool_input(value: unknown, members: readonly Agent[]
|
|
|
152
212
|
member_ids: unique_member_ids,
|
|
153
213
|
response_mode: unique_member_ids.length > 1 ? "parallel" as const : "single" as const,
|
|
154
214
|
depends_on_node_ids: index === 0 ? [] : [`step-${index - 1}`],
|
|
155
|
-
instruction: "
|
|
215
|
+
instruction: "你已被 Group 调度选中,请直接针对当前消息给出回复,并且只代表自己发言。",
|
|
156
216
|
};
|
|
157
217
|
});
|
|
158
218
|
if (nodes.length === 0 && parsed.next === "continue") {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group Dispatch Session 领域类型。
|
|
3
|
+
*
|
|
4
|
+
* Dispatch Session 只记录群聊调度 Turn,不复制 GroupMessage 正文;消息正文仍由
|
|
5
|
+
* GroupSession 的消息日志作为唯一事实源,调度记录通过消息 ID 建立稳定引用。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Agent } from "@/agent/Agent.js";
|
|
9
|
+
import type { GroupMessage } from "@/types/group/Group.js";
|
|
10
|
+
import type {
|
|
11
|
+
DispatchDecision,
|
|
12
|
+
DispatchStrategy,
|
|
13
|
+
DispatchTrigger,
|
|
14
|
+
} from "@/types/group/DispatchStrategy.js";
|
|
15
|
+
|
|
16
|
+
/** Dispatch Session 中一次调度 Turn 的持久化状态。 */
|
|
17
|
+
export type GroupDispatchTurnStatus =
|
|
18
|
+
| "queued"
|
|
19
|
+
| "running"
|
|
20
|
+
| "completed"
|
|
21
|
+
| "failed"
|
|
22
|
+
| "stopped";
|
|
23
|
+
|
|
24
|
+
/** Dispatch Session 中一次调度 Turn 的完整持久化快照。 */
|
|
25
|
+
export interface GroupDispatchTurnRecord {
|
|
26
|
+
/** 当前调度 Turn 的稳定标识。 */
|
|
27
|
+
readonly dispatch_id: string;
|
|
28
|
+
/** 本次调度由用户消息还是自动传播触发。 */
|
|
29
|
+
readonly trigger: DispatchTrigger;
|
|
30
|
+
/** 当前调度所围绕的 GroupMessage 标识。 */
|
|
31
|
+
readonly message_id: string;
|
|
32
|
+
/** 当前调度需要一起判断的新增 GroupMessage 标识。 */
|
|
33
|
+
readonly pending_message_ids: readonly string[];
|
|
34
|
+
/** 当前调度 Turn 的生命周期状态。 */
|
|
35
|
+
readonly status: GroupDispatchTurnStatus;
|
|
36
|
+
/** 调度成功后已经校验并持久化的成员投递决定。 */
|
|
37
|
+
readonly decision?: DispatchDecision;
|
|
38
|
+
/** 调度失败或停止时用于诊断的稳定错误文本。 */
|
|
39
|
+
readonly error?: string;
|
|
40
|
+
/** 当前调度 Turn 首次入队的 Unix 毫秒时间戳。 */
|
|
41
|
+
readonly created_at: number;
|
|
42
|
+
/** 当前状态最近一次提交的 Unix 毫秒时间戳。 */
|
|
43
|
+
readonly updated_at: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Dispatch Session 持久化所需的最小数据能力。 */
|
|
47
|
+
export interface GroupDispatchSessionDataStore {
|
|
48
|
+
/** 读取每个 dispatch_id 的最新调度 Turn 快照。 */
|
|
49
|
+
list_turns(): Promise<GroupDispatchTurnRecord[]>;
|
|
50
|
+
/** 以追加日志方式提交一个调度 Turn 的最新完整快照。 */
|
|
51
|
+
append_turn(turn: GroupDispatchTurnRecord): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Dispatch Session 成功完成一次调度后的运行结果。 */
|
|
55
|
+
export interface GroupDispatchSessionResult {
|
|
56
|
+
/** 当前调度 Turn 的稳定标识。 */
|
|
57
|
+
readonly dispatch_id: string;
|
|
58
|
+
/** 已经校验并持久化的成员投递决定。 */
|
|
59
|
+
readonly decision: DispatchDecision;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 创建 Group Dispatch Session 的运行依赖。 */
|
|
63
|
+
export interface GroupDispatchSessionOptions {
|
|
64
|
+
/** 当前 GroupSession 使用的消息调度策略。 */
|
|
65
|
+
readonly dispatch_strategy: DispatchStrategy;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 向 Group Dispatch Session 提交一次调度判断的输入快照。 */
|
|
69
|
+
export interface GroupDispatchSessionInput {
|
|
70
|
+
/** 本次调度由用户消息还是自动传播触发。 */
|
|
71
|
+
readonly trigger: DispatchTrigger;
|
|
72
|
+
/** 当前调度所围绕的 GroupMessage。 */
|
|
73
|
+
readonly message: GroupMessage;
|
|
74
|
+
/** 当前调度需要一起判断的新增 GroupMessage。 */
|
|
75
|
+
readonly pending_messages: readonly GroupMessage[];
|
|
76
|
+
/** 当前 GroupSession 的完整共享消息快照。 */
|
|
77
|
+
readonly messages: readonly GroupMessage[];
|
|
78
|
+
/** 当前 Group 的成员快照。 */
|
|
79
|
+
readonly members: readonly Agent[];
|
|
80
|
+
}
|
|
@@ -143,6 +143,8 @@ export interface GroupTurnRuntime {
|
|
|
143
143
|
readonly root_message_id: string;
|
|
144
144
|
/** 用户消息写入时冻结的上下文消息标识。 */
|
|
145
145
|
readonly context_message_ids: readonly string[];
|
|
146
|
+
/** 当前 Turn 位于初始用户调度还是自动传播阶段。 */
|
|
147
|
+
dispatch_stage: "user" | "auto";
|
|
146
148
|
/** 当前 GroupTurn 的异步 user dispatch。 */
|
|
147
149
|
user_dispatch?: Promise<void>;
|
|
148
150
|
/** 当前 GroupTurn 是否等待 GroupSession 的 auto dispatch。 */
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/** GroupSession 持久化协议:只描述 GroupSession 所需的底层数据能力。 */
|
|
2
2
|
|
|
3
3
|
import type { GroupMessage } from "@/types/group/Group.js";
|
|
4
|
+
import type { GroupDispatchSessionDataStore } from "@/types/group/GroupDispatchSession.js";
|
|
4
5
|
import type { FileSystem } from "@downcity/workspace";
|
|
5
6
|
|
|
6
7
|
/** GroupSession 的持久化元数据。 */
|
|
7
8
|
export interface GroupSessionHistoryMeta {
|
|
8
9
|
/** metadata schema 版本。 */
|
|
9
|
-
readonly v:
|
|
10
|
+
readonly v: 2;
|
|
10
11
|
/** 当前 GroupSession 标识。 */
|
|
11
12
|
readonly session_id: string;
|
|
12
13
|
/** 所属 Group 标识。 */
|
|
@@ -37,12 +38,16 @@ export interface GroupSessionTurnCheckpoint {
|
|
|
37
38
|
readonly root_message_id: string;
|
|
38
39
|
/** 用户消息写入时冻结的上下文消息标识。 */
|
|
39
40
|
readonly context_message_ids: readonly string[];
|
|
41
|
+
/** 恢复后应重新执行初始用户调度,还是继续等待自动传播收口。 */
|
|
42
|
+
readonly dispatch_stage: "user" | "auto";
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
/** 单个 GroupSession 的持久化数据视图。 */
|
|
43
46
|
export interface GroupSessionDataStore {
|
|
44
47
|
/** 当前 GroupSession 标识。 */
|
|
45
48
|
readonly session_id: string;
|
|
49
|
+
/** 当前 GroupSession 独享的 Dispatch Session 持久化视图。 */
|
|
50
|
+
readonly dispatch_session: GroupDispatchSessionDataStore;
|
|
46
51
|
/** 初始化当前 GroupSession 的存储布局。 */
|
|
47
52
|
initialize(): Promise<void>;
|
|
48
53
|
/** 读取完整共享消息历史。 */
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Plugin 向宿主发布用户注意事项的通知协议。
|
|
3
|
+
*
|
|
4
|
+
* Plugin 只描述通知内容与自身工作区路由,不感知 Desktop、Dock、持久化或已读策略。
|
|
5
|
+
* 宿主负责绑定 Plugin 身份、聚合通知并决定具体呈现方式。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { JsonObject } from "@/types/common/Json.js";
|
|
9
|
+
|
|
10
|
+
/** Agent Plugin 发布的一条宿主通知。 */
|
|
11
|
+
export interface PluginNotificationInput {
|
|
12
|
+
/** Plugin 内稳定的聚合键;同一键的新通知替换旧的未读通知。 */
|
|
13
|
+
readonly topic_key: string;
|
|
14
|
+
|
|
15
|
+
/** 简短的用户可见标题。 */
|
|
16
|
+
readonly title: string;
|
|
17
|
+
|
|
18
|
+
/** 可选的用户可见补充说明。 */
|
|
19
|
+
readonly body?: string;
|
|
20
|
+
|
|
21
|
+
/** 打开通知时使用的当前 Plugin 工作区 JSON 路由。 */
|
|
22
|
+
readonly route?: JsonObject;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Plugin 要清除的一项本地通知主题。 */
|
|
26
|
+
export interface PluginNotificationTopicInput {
|
|
27
|
+
/** Plugin 内稳定的聚合键。 */
|
|
28
|
+
readonly topic_key: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 宿主注入给 Agent Plugin factory 的通知发布能力。 */
|
|
32
|
+
export interface PluginNotificationPublisher {
|
|
33
|
+
/** 发布一条由当前 Plugin 拥有的通知。 */
|
|
34
|
+
publish(input: PluginNotificationInput): Promise<void>;
|
|
35
|
+
|
|
36
|
+
/** 清除当前 Plugin 命名空间内一个主题的未读通知。 */
|
|
37
|
+
dismiss(input: PluginNotificationTopicInput): Promise<void>;
|
|
38
|
+
}
|