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
@@ -176,10 +176,10 @@ func TestSpawnToolMovesSlowBatchToBackground(t *testing.T) {
|
||||
s.runner = &mockRunner{gate: gate, result: &loop.Result{Text: "late", Steps: 2}}
|
||||
sp := NewSpawnTool(s, WithSpawnWait(40*time.Millisecond))
|
||||
|
||||
results := make(chan *MergedResult, 1)
|
||||
results := make(chan *SubagentResultEvent, 1)
|
||||
unsub, err := s.Bus().Subscribe(context.Background(), SubagentResultSubject,
|
||||
func(_ context.Context, e bus.Event) error {
|
||||
if res, ok := e.Payload.(*MergedResult); ok {
|
||||
if res, ok := e.Payload.(*SubagentResultEvent); ok {
|
||||
select {
|
||||
case results <- res:
|
||||
default:
|
||||
@@ -192,8 +192,11 @@ func TestSpawnToolMovesSlowBatchToBackground(t *testing.T) {
|
||||
}
|
||||
defer unsub()
|
||||
|
||||
// 派发回合的会话(loop 在 Run 顶部把它播进 ctx):回填载荷必须带上它,
|
||||
// gateway 的中继才能把结果归属到父会话的实时流(design/05 §9)。
|
||||
ctx := loop.WithFlowBranch(context.Background(), parentSession)
|
||||
start := time.Now()
|
||||
raw, err := sp.Execute(context.Background(), map[string]any{"goal": "slow"})
|
||||
raw, err := sp.Execute(ctx, map[string]any{"goal": "slow"})
|
||||
elapsed := time.Since(start)
|
||||
if err != nil {
|
||||
t.Fatalf("Execute: %v", err)
|
||||
@@ -212,14 +215,32 @@ func TestSpawnToolMovesSlowBatchToBackground(t *testing.T) {
|
||||
close(gate)
|
||||
select {
|
||||
case res := <-results:
|
||||
if res.Total != 1 || res.Succeeded != 1 {
|
||||
t.Fatalf("background merge = %d total / %d ok, want 1/1", res.Total, res.Succeeded)
|
||||
if res.SessionID != parentSession {
|
||||
t.Fatalf("result sessionId = %q, want the dispatching session %q", res.SessionID, parentSession)
|
||||
}
|
||||
if res.Agents[0].Text != "late" {
|
||||
t.Fatalf("background result text = %q, want late", res.Agents[0].Text)
|
||||
if res.Status != "done" || len(res.Agents) != 1 {
|
||||
t.Fatalf("result = %+v, want one settled agent", res)
|
||||
}
|
||||
if a := res.Agents[0]; a.Text != "late" || a.Steps != 2 || a.Goal != "slow" || a.Status != string(StatusDone) {
|
||||
t.Fatalf("result agent = %+v, want the late/2-steps/done projection", a)
|
||||
}
|
||||
// 载荷形状是 api-client §3.10 登记的 WS 帧 data:键集锁定,避免
|
||||
// 生产者悄悄改键而文档与实现分叉。
|
||||
wire, merr := json.Marshal(res)
|
||||
if merr != nil {
|
||||
t.Fatalf("marshal result payload: %v", merr)
|
||||
}
|
||||
var keys map[string]json.RawMessage
|
||||
if uerr := json.Unmarshal(wire, &keys); uerr != nil {
|
||||
t.Fatalf("unmarshal result payload: %v", uerr)
|
||||
}
|
||||
for _, want := range []string{"sessionId", "status", "summary", "agents"} {
|
||||
if _, ok := keys[want]; !ok {
|
||||
t.Fatalf("result payload %s lacks key %q", wire, want)
|
||||
}
|
||||
}
|
||||
case <-time.After(waitTimeout):
|
||||
t.Fatal("a background batch never published its merged result")
|
||||
t.Fatal("a background batch never published its result payload")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user