feat(orchestration,gateway): 子代理结果登 WS 帧 subagent_result(裁决 02.4.3)
- 编排层改为在 subagent.result 上发布带父会话归属的 SubagentResultEvent,后台批次结果不再消失在进程内总线 - gateway 中继:新帧常量 wsSubagentResult="subagent_result",eventBridge/bridgePatterns 登记该 topic,normalizeEvent 专用成形(帧壳 sessionId = 派生该批次的父会话) - 角色门 fail-closed:未登记进 frameReachesAll,owner/admin 可达、viewer 不可达,真 JWT 三角色 WS 断言钉住 - 前景(2s 内跑完)批次仍只回工具结果,不发此帧
This commit is contained in:
1 parent
0ce498754a
commit
8c6466cb00
4 files changed
+295
-22
No files matched your search
@@ -42,11 +42,11 @@ const (
|
||||
minSpawnTimeoutSeconds = 1
|
||||
maxSpawnTimeoutSeconds = 24 * 60 * 60
|
||||
|
||||
// SubagentResultSubject is the bus subject a finished background batch is
|
||||
// announced on, carrying the *MergedResult. It is the design/05 §9 结果回填 hook
|
||||
// for whichever component owns the session message queue (design/15); the
|
||||
// WS-visible per-agent status already travels on the scheduler's own progress
|
||||
// events, so this subject has no consumer until that queue lands.
|
||||
// SubagentResultSubject 是后台批次结算后广播其结果的主题,载荷是
|
||||
// *SubagentResultEvent(父会话 + 批次结果)。它是 design/05 §9「结果回填主
|
||||
// 会话并提示用户」的载体,消费者是 gateway 的中继:该主题被登记为 WS 下行帧
|
||||
// subagent_result(Docs/api-client.md §3.10),帧按载荷里的父会话归属,因此结果
|
||||
// 落回父会话的实时流而非悬空 hook;逐代理进行中状态仍走调度器进度事件。
|
||||
SubagentResultSubject = "subagent.result"
|
||||
)
|
||||
|
||||
@@ -191,7 +191,7 @@ func (t *SpawnTool) Execute(ctx context.Context, args any) (any, error) {
|
||||
"persona", specs[0].PersonaID, "mode", specs[0].Mode, "session", specs[0].ParentSession)
|
||||
|
||||
if boolArg(m, "background") {
|
||||
go t.await(ids)
|
||||
go t.await(ids, specs[0].ParentSession)
|
||||
return runningSpawnResult(ids, specs), nil
|
||||
}
|
||||
return t.foreground(ids, specs), nil
|
||||
@@ -206,7 +206,7 @@ func (t *SpawnTool) foreground(ids []string, specs []AgentSpec) any {
|
||||
res, err := t.sched.Wait(waitCtx, ids)
|
||||
if err != nil {
|
||||
t.log.Info("sub-agent batch moved to background", "count", len(ids), "waited", t.wait.String())
|
||||
go t.await(ids)
|
||||
go t.await(ids, specs[0].ParentSession)
|
||||
return runningSpawnResult(ids, specs)
|
||||
}
|
||||
return doneSpawnResult(res)
|
||||
@@ -215,7 +215,9 @@ func (t *SpawnTool) foreground(ids []string, specs []AgentSpec) any {
|
||||
// await blocks until the batch settles and broadcasts the merged outcome as the
|
||||
// design/05 §2 "结束后写执行总结 → 返回主会话" hand-off. It runs on a detached
|
||||
// goroutine; a nil bus or an unknown id ends it quietly.
|
||||
func (t *SpawnTool) await(ids []string) {
|
||||
// parentSession 是该批次所属的父会话,随载荷一起广播,使中继能把回填结果
|
||||
// 归属到父会话的实时流,而不是发一张无归属的帧。
|
||||
func (t *SpawnTool) await(ids []string, parentSession string) {
|
||||
res, err := t.sched.Wait(context.Background(), ids)
|
||||
if err != nil {
|
||||
t.log.Warn("sub-agent batch wait failed", "count", len(ids), "error", err)
|
||||
@@ -227,7 +229,8 @@ func (t *SpawnTool) await(ids []string) {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
if perr := b.Publish(context.Background(), SubagentResultSubject, res); perr != nil {
|
||||
payload := &SubagentResultEvent{SessionID: parentSession, SpawnResult: doneSpawnResult(res)}
|
||||
if perr := b.Publish(context.Background(), SubagentResultSubject, payload); perr != nil {
|
||||
t.log.Warn("publish sub-agent results", "error", perr)
|
||||
}
|
||||
}
|
||||
@@ -265,6 +268,13 @@ type SpawnAgentResult struct {
|
||||
Steps int `json:"steps,omitempty"`
|
||||
}
|
||||
|
||||
// SubagentResultEvent 是后台批次结算后发布的回填载荷(design/05 §9;契约见
|
||||
// Docs/api-client.md §3.10):SessionID 为父会话,其余字段即派发工具返回给模型的结果。
|
||||
type SubagentResultEvent struct {
|
||||
SessionID string `json:"sessionId,omitempty"`
|
||||
*SpawnResult
|
||||
}
|
||||
|
||||
// doneSpawnResult maps a settled batch onto the tool result.
|
||||
func doneSpawnResult(res *MergedResult) *SpawnResult {
|
||||
out := &SpawnResult{Status: "done", Agents: make([]SpawnAgentResult, 0)}
|
||||
|
||||
Reference in new issue
Block a user