持续重构集成#690
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## dev #690 +/- ##
==========================================
+ Coverage 82.52% 83.11% +0.59%
==========================================
Files 981 1001 +20
Lines 61962 65590 +3628
Branches 8074 8480 +406
==========================================
+ Hits 51133 54514 +3381
- Misses 7374 7386 +12
- Partials 3455 3690 +235
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 371 files with indirect coverage changes 🚀 New features to boost your workflow:
|
📊 iter17 进展 — 4/4 cluster merged 🎉
iter17 净改动:多 actor / 多 projection / proto schema 演化 + 删 process-local 权威源。 🤖 controller status banner |
📊 iter18 audit 派出iter17 4/4 cluster merged → 自动派 iter18 audit codex(per SKILL '## INFINITE refactor loop')。
剩 open design issue:#720 #722(escalated 等 maintainer)— 不阻 iter18。 🤖 controller status banner |
📊 iter18 bootstrap — 7 cluster 进入工作链
Phase 9 multi-solver 已为 6 design issue 排队,controller 下次 wakeup 触发派出 r1 三 solver。 🤖 controller status banner |
📊 iter18 Phase 9 r1 派出(6 design issue × 3 solver = 18 child codex)
每 solver 3600s 上限。等齐 3 solver per issue → controller 派 meta-judge。 🤖 controller status banner |
📊 iter18 Phase 9 r1 全 18 codex 派出 ✅
后台 in-flight = 19 codex(18 Phase 9 r1 solver + 1 cluster-003 implement)。 🤖 controller status banner |
implement: cluster-003已完成
摘要: |
📊 iter18 Phase 9 r1 结果
后台 in-flight = 13 codex(12 r2 solver + 1 rebase resolve)。 🤖 controller status banner |
📊 iter18 进展更新
iter18 净:2/7 cluster merged;5 escalated 等 maintainer 决策。 Auto-loop 推进无可派任务(全 escalation 路径 + 0 in-flight codex)。下次 wakeup 进入 idle heartbeat 模式。 🤖 controller status banner |
📊 iter19 audit 派出iter18 已 merge 2/7(余 5 escalated 等 maintainer);loop 在 idle 后按 INFINITE 规则派 iter19 audit 拉新工作(独立于 escalation 路径)。 🤖 controller status banner |
📊 状态 — 已收到 maintainer 评论(daemon 识别)
🤖 comment-monitor.sh daemon ⟦AI:AUTO-LOOP⟧ |
📊 状态卡片 — solver 派出
🤖 controller status banner ⟦AI:AUTO-LOOP⟧ |
📊 状态卡片 — solver 派出
🤖 controller status banner ⟦AI:AUTO-LOOP⟧ |
|
⟦AI:AUTO-LOOP⟧ iter20 bootstrap complete OPENED:design-issue:cluster-001-studio-host-authoring-orchestration:747 Notes:
|
📊 codex 进展 iter20-bootstrap (⏳ 进行中; 已跑 6 min)
|
Brings in 50 cluster commits from the codex-refactor-loop rollup branch — 368 files, +22120/-12016 net (matches PR #690). Major architectural inflow: - ILLMProvider stream-only contract (cluster-001 deletes ChatAsync; all 25 callsites migrated to ChatStreamAsync+aggregator) - NyxRelay reply-token cleanup → persisted admission claim flow (CaptureNyxRelayReplyToken/RemoveNyxRelayReplyToken/Schedule*Cleanup helpers + NyxRelayReplyTokenCleanupRequestedEvent removed; capped RelayReplayClaimsCap=10000, PendingRelayAdmissionsCap=1000) - Studio authoring port + Host fake actor shell deletion (iter21 cluster-001) - Request-path projection priming via CQRS binders (iter21 cluster-002, supersedes #751) - Projection scope watermark → typed readmodel + committed-state publication hook (cluster-003/006) - Voice host session state / provider response epoch move into actor turn (iter15 cluster-025/026/027) - Workflow scope/channel typed proto fields (cluster-029) Conflicts resolved (3): - agents/.../ConversationGAgent.cs: kept router's chat-route admission reject path (issue #694), replaced the dead RemoveNyxRelayReplyToken call with ClearReplyLifecyclesAsync(..., "chat_route_rejected") to match the new admission-claim flow; merged the runCopy mutations so target_ref + reply_token + transport credentials all flow through to the run actor. - test/Aevatar.AI.Tests/NyxIdChatEndpointsCoverageTests.cs: kept HEAD's generalized NormalizeEndpointArgs (Any-based) with chat-routing stub insertions, dropped auto-refact-dev's legacy length-equality short-circuit. - test/.../ConversationGAgentDedupTests.cs: merged optional ctor params (queryPort + chatRouteResolver + store + eventPublisher) and kept both branches' helper classes (StaticChatRoute* + RecordingEventPublisher). Build: 171 projects, 0 errors. No push.
per Auric 2026-05-21 "完整分析改脚本": 新文件 tools/refactor-loop/controller_lib.sh: - safe_worktree: detect-or-create,消除 "already exists" race - merge_pr <pr>: auto-close linked issue + cleanup phase labels + worktree remove。修复 #2 (Closes # 不 trigger close 因为 base!=master) - open_pr_with_label: 原地 export PR_NUM,消除 bash subshell grep var bug (iter22 #690 reviewer 误发事故) - render_template: 处理 {{var}} + envsubst 双语法 - sweep_stale_labels: 批量清 closed but in-flight phase label - validate_prompt: 派 codex 前 check 0 unresolved {{var}} (iter25 #784 implement blocked 事故) 模板修正:audit.md/implement.md/remote-ci-fix.md/test-add.md/verify.md 全部从 {{var}} 转 ${VAR}(envsubst 兼容)。 SKILL.md 新章节: - Label 生命周期状态机(per kind:design issue / cluster PR / rollup PR) - 强制规则:派 codex 必 validate_prompt;merge 必 merge_pr; worktree 必 safe_worktree;PR num 必 open_pr_with_label ⟦AI:AUTO-LOOP⟧
Phase 9 r2 consensus(reflector retry-fix narrowing):删 startup-only fake rebuild event path,不引入新 core refresh contract;activation-only startup 保留。 违反 CLAUDE.md「权威状态 / ReadModel / Projection」:committed event store + actor state 是唯一真相,projection 只消费 committed 事实;不得 query-time replay/priming;无业务变化的领域事件不应作为 projection trigger。 修法(narrow delete): - 删 ChannelBotRegistrationStartupService dispatch fake event 部分 - 删 ChannelBotRebuildProjectionCommand 类型 - 删 ChannelBotProjectionRebuildRequestedEvent 类型 + legacy alias - 删 ChannelBotRegistrationGAgent.HandleRebuildProjection handler + no-op state transition - 删 activation plan provider 中 fake event 路由 - 调整测试:验证 activation-only 行为,删 fake event 行为测试 保留: - activation-only startup(committed-state hook 自然 cover cold-start catch-up,无需 synthetic event) - readmodel structure 不动 build + ChannelRuntime tests(890 passed) + architecture/test_stability guards 全过。 closes #1043 ⟦AI:AUTO-LOOP⟧
…actor 内 detach) (#1062) * iter103 cluster-voice-whip: 把 WHIP transport completion 改成 typed signal 违反 CLAUDE.md「Actor 设计 / 生命周期 / 执行模型」「回调只发信号: Task.Run/Timer/线程池回调不得直接读写运行态,只能发布内部触发事件」+ 「业务推进内聚:工作流推进必须在 Actor 事件处理流程内完成」。 修法(narrow): - 删 VoicePresenceEndpoints fire-and-forget ObserveTransportLifetimeAsync 直接 await transport completion + 调 DetachTransportAsync - 改为 publish typed VoiceTransportLifetimeCompleted signal,包含 session_id + transport_lease_id + owner + expiry - Actor/session turn 消费 typed signal,verify active lease,reject stale completion,actor scope 内做 detach/release - 加 active_transport_lease_id 到 capability readmodel/snapshot, 让 actor 能 publish 同 typed completion 含足够 identity - 加 // Refactor (iter103/cluster-voice-whip) 自我说明注释 build + VoicePresence tests (157 passed) + architecture/test_stability guards 全过。 ⟦AI:AUTO-LOOP⟧ * iter103 cluster-voice-whip fix r2: callback 只发信号,actor 内做 cleanup architect r1 reject:WHIP callback 仍读/写 module runtime state(读 _transportPump + DisposeVolatileTransportAsync 设 _transportPump=null + _provider.OnEvent=null)。 违反「回调只发信号」。 修法: - callback path 只 publish typed VoiceTransportLifetimeCompleted signal - 删 callback 中 TryBuildTransportLifetimeCompleted + DisposeVolatileTransportAsync 调用 - HandleTransportLifetimeCompletedAsync(actor 内)统一做 stale check + volatile transport cleanup + lease state persist - 删 fallback escape behavior(no dispatcher 时直接 dispose module transport) - 改 WHIP tests:验证 callback emit signal only + cleanup 经 actor handling build + voice presence tests (157 pass) + guards 全过。 ⟦AI:AUTO-LOOP⟧
…1067) Phase 9 r1 consensus(3/3 unanimous):reuse existing WorkflowRunEventEnvelope proto;delete SDK WorkflowOutputFrame JSON semantic contract;JSON only at external wire adapter boundary。 违反 CLAUDE.md「序列化」:统一 Protobuf 为 State / 领域事件 / 命令 / 回调载荷 / 跨 Actor/跨节点内部传输对象;外部协议必须 JSON 时仅在 Host/Adapter 边界 做协议转换,进入应用/领域/运行时层后恢复为 Protobuf。 修法: - 删 SDK WorkflowOutputFrame JSON contract;WorkflowEvent.Frame 直接用 WorkflowRunEventEnvelope proto - SDK SSE adapter 新增 WorkflowRunEventJsonBoundaryCodec,只在 external SSE JSON wire boundary 做 JSON → proto 解析 - RunSessionTracker / WorkflowCustomEventParser / RunFailed 全改读 typed proto payload - SDK/Host tests + fixtures 改 protobuf JSON wire shape - 更新 SDK/chat/observability docs verified: - Workflow SDK tests 32 passed - Workflow Host API tests 358 passed - build + architecture/test_stability/docs lint guards 全过 closes #1064 ⟦AI:AUTO-LOOP⟧
…treamingProxyGAgent (#1069) * iter104 cluster-1 #1063: 删 StreamingProxyChatLifecycleFacade,fold 进 StreamingProxyGAgent Phase 9 r2 consensus(reflector r1 retry-fix narrowing → 3/3 unanimous): - 不新 actor type;reuse existing StreamingProxyGAgent - delete/fold StreamingProxyChatLifecycleFacade orchestration into actor handlers - 保留 deprecated compat endpoints - 禁 reuse LlmSessionGAgent(与 #1058 耦合) 违反 CLAUDE.md「Actor 设计」「业务推进内聚」「命令骨架内聚」: Application 层 facade 持 chat continuation lifecycle orchestration。 修法: - 删 StreamingProxyChatLifecycleFacade + DI 注册 - StreamingProxyEndpoints 保留 deprecated compat surface,只做 HTTP normalize + admission + dispatch/attach - StreamingProxyGAgent 加 typed self continuation StreamingProxyChatLifecycleContinuationRequested,actor handler 内推进 Nyx participant join/reply/terminal effects - 不新 actor;不动 LlmSessionGAgent;不动 NyxID/chrono-* 外部 verified: - build + AI tests (StreamingProxy filter) + Workflow Host API tests + Workflow SDK tests 全过 - architecture/test_stability guards 全过 closes #1063 ⟦AI:AUTO-LOOP⟧ * iter104 cluster-1 #1063 fix r2: actor 只 publish continuation,外部 adapter 做 Nyx I/O r1 architect reject:HandleChatLifecycleContinuationRequested 在 actor handler 内调 StreamingProxyNyxParticipantCoordinator 做 Nyx/LLM streaming I/O,违反 "External Nyx streaming I/O stays outside actor turns"。 r1 tests reject:typed continuation lifecycle 无直接 GAgent 行为测试。 修法: - 删 actor handler 内 await StreamingProxyNyxParticipantCoordinator - actor 只 publish typed continuation request + persist actor-owned facts - 引入 outside-actor adapter/runner(host-side consumer)消费 typed continuation - adapter 完成 participant join/reply/terminal → typed event 回 actor commit state - 加 GAgent 行为测试:publish continuation(有/无 token)+ continuation handler verified: - build + AI tests + Workflow Host + Workflow SDK tests 全过 - architecture/test_stability guards 全过 ⟦AI:AUTO-LOOP⟧
* iter104 cluster-3: voice presence dedupe fence 移到 actor state 来源 iter104 audit Cluster 3(requires_design=false,直接 implement)。 违反 CLAUDE.md「中间层状态约束」「事实源唯一」「Actor 设计:单线程事实源」: VoicePresenceEventPolicy 在 module memory 持 _recent + _recentKeys dedupe state。 修法: - recent-event dedupe fence 移到 VoicePresenceRuntimeState(actor-owned), bounded by timestamp/window - VoicePresenceEventPolicy 改为 pure evaluator(接 actor-owned recent keys 参数) - VoicePresenceModule 不再创建 per-module 持 state 的 policy 实例 - 不引入 distributed cache(actor-owned 足够) verified: - VoicePresence tests 158 passed - build + architecture/test_stability guards 全过 ⟦AI:AUTO-LOOP⟧ * iter104 cluster-3 fix r2: 加 pure evaluator + source regression test tests r1 reject:VoicePresenceEventPolicy.Evaluate 纯 evaluator 契约无直接测试。 bad impl 若保留 internal _recent / _recentKeys 仍能通过现 test。 修法(test-only): - 加 pure evaluator regression test:同 policy 实例 + 同 envelope + 空 fence, 第二次仍必须 Admit;只有显式 BuildFence 返回 fence 再传回时才 DropDuplicate - 加 source regression guard:断言 policy source 不含 _recentKeys / RecentEventEntry / LinkedList<RecentEventEntry> verified: - build + VoicePresence tests + architecture/test_stability guards 全过 ⟦AI:AUTO-LOOP⟧
…cation LLM 主链 (#1068) * iter103 cluster-1 #1058: extend LlmSessionGAgent typed events,删 Application 层 LLM 主链 Phase 9 r3 consensus(reflector retry-fix narrowing):extend LlmSessionGAgent typed events;1 start command + 5 typed execution events;不新 actor。 违反 CLAUDE.md「Actor 设计」「读写分离」「业务推进内聚」「命令骨架内聚」: Responses/Messages 直连模型路径在 Application facade 持完整 LLM 会话执行 (stream loop / tool call / 累积 text),违反 actor-owned run progression。 修法(extend existing actor, no new actor type): - LlmSessionGAgent 加 LlmRunRequested start command(typed) - 加 5 typed execution events:stream chunk / tool call / forwarded tool / completion / failure - Application facade(Responses/Messages)只 normalize + resolve + dispatch typed command - 删 stream loop / tool execution / text accumulation in Application - LlmSessionGAgent actor 内消费 typed command,publish typed events as run progresses - Mainnet tool provider 接入新 typed flow - 保留 /v1/responses + /v1/messages public surface verified: - build + AI tests + GAgentService tests + Mainnet endpoint tests 全过 - architecture/test_stability guards 全过 closes #1058 ⟦AI:AUTO-LOOP⟧ * iter103 cluster-1 #1058 fix r2: facade 改 accepted ACK + 加 actor 直接测试 r1 architect reject:facade 在 DispatchAsync 后立即 ReadObservedCompletionAsync, 违反 ACK 诚实(accepted ≠ committed/observed)。 r1 tests reject:LlmSessionGAgent forwarded client tool + failure/cancel 分支 无直接 actor 测试。 修法: - ResponsesCommandFacade / MessagesCommandFacade dispatch 后直接返回 accepted DispatchAdmission(不读 readmodel completion) - Responses/Messages endpoints 把 accepted run 映射为 in-progress/accepted streaming surface - 加 LlmSessionGAgent 直接 actor 测试覆盖: - forwarded client tool 分支 - provider failure typed event/state - cancellation typed event/state verified: - build + GAgentService + AI + Hosting tests 全过 - architecture/test_stability guards 全过 ⟦AI:AUTO-LOOP⟧ * iter103 cluster-1 #1058 fix r3: 删 duplicate dispatch + 加 dispatch count assert r2 3/3 reject 同因(duplicate dispatch in ResponsesCommandFacade.StreamAsync): - architect:duplicate DispatchRunAsync(1 streaming request 2 inbox admissions) - tests:facade test 不断言 dispatch count = 1 - quality:同因 + MainnetResponsesEndpointsTests codified StreamCallCount=2 修法(narrow): - 删 ResponsesCommandFacade.StreamAsync 第一个 DispatchRunAsync(改为 exactly 1 dispatch) - 加 facade tests 断言 dispatch count = 1 + payload = LlmRunRequested (Responses + Messages 各 streaming + non-streaming) - MainnetResponsesEndpointsTests 改 expect StreamCallCount = 1 verified: - build + GAgentService tests 610/610 + Hosting tests 204/204 + guards 全过 ⟦AI:AUTO-LOOP⟧ * iter103 cluster-1 #1058 fix r4: 删 ResponsesToolContracts.cs EOF blank line quality r3 narrow reject(trailing blank line)。git diff --check 通过。 ⟦AI:AUTO-LOOP⟧
事故:hotfix r3/r4 期间 controller 0 codex 等 Monitor,Auric 提醒"状态/修好了么" 多次。把 floor=2 写入 hard rule:每 wakeup + 每 spawn/merge/banner 后 verify active >= 2,否则当 turn 派满才允许 ScheduleWakeup。stop 标记是唯一 0 codex 合法情况。 ⟦AI:AUTO-LOOP⟧
per Auric 2026-05-26 "主动 pr review 已经存在的 pr,主动解决已经存在的但没人处理的 issues"。 Phase 10 advisory: - 严格 eligibility(白名单内 author / 未 review / CI 至少 1 pass / 非 too-new / 非 too-large / 非 auto-loop 池) - 派 3 reviewer codex(advisory 模式,不接 fix loop,不 auto-merge) - 加 `phase10-reviewed` label 防重(head SHA 变即失效) - reject 时 PushNotification Phase 11 triage: - 同 Path B 复用 triage-monitor.sh daemon - 严格 eligibility(白名单 author / 未 triaged / 不太新 / body ≥ 100 chars / 24h 内无 maintainer reply) - 每 wakeup 最多新加 2 个 auto-loop-triage label,防压垮 - accept → reshape 进 Phase 9;reject → phase11-not-eligible ⟦AI:AUTO-LOOP⟧
…inspect graph (#1080) per Phase 9 r5 共识(3/3 propose graph-only existing facade,reject unified application service,5 round 收敛)。 - 加 WorkflowArtifactQueryTool(graph-only on existing workflow execution facade) - 删 actor_inspect graph wrapper + workflow artifact actor_id aliases (仅 tool surfaces;不动 HTTP/SDK / actor current-state query path) - 重命名 artifact gate 为 artifact 语义(与 actor query 区分) - 不开 unified application service(reject as shallow forwarding) closes #1073 ⟦AI:AUTO-LOOP⟧
…tep result (#1087) * iter110 cluster-2 #1085:executor IO-only + AgentRunGAgent 应用 typed step result per Phase 9 r1 共识(3/3 propose minimal framing): - agent_run.proto:AgentRunNextLlmStepRequestedEvent.step_state / AgentRunNextToolStepRequestedEvent.step_state 改为 typed llm_step_result / tool_step_result;保留 identity 字段 - AgentRunReplyGenerationExecutor:删 StepState.Clone() mutation 路径,executor 只填 typed facts(LLM streamed text/content/tool calls/ usage/finish reason;tool result messages/round increment intent) - AgentRunGAgent:HandleNextLlmStepAsync/HandleNextToolStepAsync 内 validate continuation identity + derive 下一份 AgentRunReplyStepState + 持久化(不再直接持久化 executor 传来 full state) - AgentRunReplyStepMappers:加 narrow result payload helper - 测试加 stale/mismatched + reconcile 行为测试 closes #1085 ⟦AI:AUTO-LOOP⟧ * PR #1087 fix r1: proto reserved 7 + 新 tag 8 + refactor 注释 per Phase 8 architect reject(applied-2): - AgentRunNextLlmStepRequestedEvent / AgentRunNextToolStepRequestedEvent: reserved 7 + reserved "step_state",新字段 llm_step_result/tool_step_result 挪到 tag 8(防 stream retention / 跨版本残留旧消息错误解码) - AgentRunGAgent ApplyLlmStepResult/ApplyToolStepResult/AddUsage + mapper helpers 加 Refactor (iter110/cluster-110-...): Old/New 注释 ⟦AI:AUTO-LOOP⟧
…3/3 solver) (#1079) * iter108 cluster-3 #1076:删 orphan HouseholdEntityTool 整套 per Phase 9 r2 共识(3/3 verified orphan,delete framing)。 删除: - HouseholdEntityTool.cs(orphan tool,无 LLM agent 注册) - HouseholdEntityToolOptions.cs - HouseholdEntityToolSource.cs - ServiceCollectionExtensions.cs(tool-only DI 注册) - HouseholdEntityToolTests.cs(tool-only tests) 保留:HouseholdEntity actor + device callback CQRS command facade。 closes #1076 ⟦AI:AUTO-LOOP⟧ * PR #1079 fix r1: 迁移 source-regression assertion 到 HouseholdEntityTests.cs per Phase 8 tests reject:删除 HouseholdEntityToolTests.cs 时连带删了保护 HouseholdEntity.cs 不使用 ChatAsync( 的 source-regression 守卫。本 fix 把该 断言迁移到保留的 HouseholdEntityTests.cs(测试方法名调整以反映 file scope)。 ⟦AI:AUTO-LOOP⟧
之前 2026-05-25 一度降到 2(per "强制检测最少2个 codex"),实际跑下来频繁 hit floor=1-2 critical violation,响应慢。回滚到 5。throttle 状态降到 3 作为降速档(非日常 floor)。 同时把自检脚本 `ps grep "timeout (3600|5400) codex"` 改 `ps grep "codex exec"` (undercount 修)。 ⟦AI:AUTO-LOOP⟧
…v/test-only) (#1081) * iter109 cluster-2 #1077:删 InMemoryStream fire-and-forget dispatch per Phase 9 r2 共识(3/3 verified dev/test-only + 删 DispatchSubscribersConcurrently + 不加 admission 抽象)。 - 删 InMemoryStream DispatchSubscribersConcurrently 方法 + 删 Task.Run(subscriber) fire-and-forget 分支(保留 sequential dispatch) - InMemoryStreamProvider 加 dev/test-only boundary 注释(production 必须 kafka/orleans) - 测试同步更新(无 fire-and-forget 后的覆盖) closes #1077 ⟦AI:AUTO-LOOP⟧ * PR #1081 fix r1: 加 source-regression guard;cluster id 保持 audit verbatim per Phase 8 comment(applied-1:rejected-1): - tests comment: 加 source-regression test 防 DispatchSubscribersConcurrently / fire-and-forget subscriber Task.Run 复发(37 LOC InMemoryStreamCoverageTests) - quality comment: 拒 — audit cluster_id 真值是 cluster-109-inmemory-stream-inline-dispatch, refactor 注释保留 verbatim 正确 ⟦AI:AUTO-LOOP⟧
… typed self-signal (#1082) * iter106 cluster-2 #1074:voice provider session/lease 改 actor-owned + typed self-signal per Phase 9 r3 共识(3/3 unanimous + reflector r1 ruling 落地)。 - IRealtimeVoiceProvider 改为 stateless transport handle 契约(ConnectAsync 返 disposable) - proto 加 VoiceProviderEventSignal(session_key + lease_epoch + event_type + payload) - OpenAIRealtimeProvider / MiniCPMRealtimeProvider 删 _session/_eventChannel/ _lifetimeCts/_receiveLoop/_dispatchLoop process-local 字段;callback emit typed self-signal,不再 OnEvent 直 mutate - VoicePresenceModule 拆 _transportPump:relay/dispatcher 由 actor self-signal continuation 推进,陈旧 session 按 lease_epoch 拒绝 - 测试同步更新(覆盖 stateless transport + typed signal 路径) 不开新 actor type / 不开新 envelope kind(reflector r1 ruling 已禁此路径)。 closes #1074 ⟦AI:AUTO-LOOP⟧ * PR #1082 fix r1: 完全删 legacy provider callback shim,落实单一主链 per Phase 8 architect reject(applied-1): - IRealtimeVoiceProvider:删旧 ConnectAsync(config) 重载 / 旧 provider I/O 方法 / mutable OnEvent / LegacyRealtimeVoiceProviderSession;只留 connector→disposable session→typed event sink 主链 - OpenAIRealtimeProvider:删 OnEvent property / 旧 provider-as-session 方法 / _activeSession 字段 - MiniCPMRealtimeProvider:同上 - 测试 fake 改走新 connector→session→typed sink 路径(test-only helper 保留) ⟦AI:AUTO-LOOP⟧ * PR #1082 fix r2: 删 dead test helper + 加 source-regression guard per Phase 8 r2 comments(applied-2): - 删 VoiceTransportRelayTests RecordingProvider.SimulateEventAndWait dead helper(0 caller) - 加 VoicePresenceModuleTests source-regression guard 防 OnEvent / LegacyRealtimeVoiceProviderSession 复发 ⟦AI:AUTO-LOOP⟧
…f-continuation(删 IO 队列) (#1083) * iter107 cluster-1 #1075:Channel actor 自持 reply operation + typed self-continuation per Phase 9 r2 共识(3/3 propose,reflector r1 ruling 落地)。 - ConversationGAgent 持有 reply / card-relay in-flight operation state (operation_id + lease_epoch + step + pending payload) - proto 加 typed self-continuation events(ReplyOperationStepEvent / LarkCardOperationSignal / NyxRelayTextOperationTimeoutPayload) - 删 LongRunningBusinessIoExecutor 单例 IO 队列(原 Channel<> + Task.Factory.StartNew workers + Func<,Task> work item);改 disposable provider IO lease - AgentRunGAgent / AgentRunReplyGenerationExecutor 同模式 actor-owned - 测试同步(删 executor tests + 加 operation/lease 测试) 不新 actor type / 不新 envelope kind(reflector r1 ruling)。 closes #1075 ⟦AI:AUTO-LOOP⟧ * PR #1083 fix r1: 删 no-op lease 空壳 + 加 source-regression guard per Phase 8 architect+tests+quality r1 3/3 reject(applied-3): - 完全删 IDisposableProviderIoLeaseFactory / IDisposableProviderIoLease / DisposableProviderIoLeaseFactory(no-op shell;违反「删除优先 / 不保留无效层」) - 删 ILongRunningBusinessIoExecutor.cs 整个文件(原 cluster 已删 executor, lease 接口也在此文件,一并清) - 删 AgentRunReplyGenerationExecutor 的 using var lease ceremony, 改 inline async IO call - 删 DisposableProviderIoLeaseFactoryTests(no-op 测试无价值) - 加 ChannelRuntimeSourceRegressionTests 防 LongRunningBusinessIoExecutor / IDisposableProviderIoLease 复发 ⟦AI:AUTO-LOOP⟧ * PR #1083 fix r2: 补 Lark card 行为测试 + 清 unused params + 加 refactor 注释 per Phase 8 r2(applied-3:rejected-0): - 补 Lark card stream/finalize 行为测试(覆盖 self-continuation 分支) - 清 4 个未用 runtimeContext 参数(StartNyxRelayText / StartLarkCardCreate / Stream / Finalize OperationAsync) - 加 iter107/cluster-1 Old/New refactor 注释块到 ConversationGAgent / LarkCardStreaming / AgentRunGAgent / AgentRunReplyGenerationExecutor ⟦AI:AUTO-LOOP⟧
…ID relay (#1095) * iter113 cluster-1 #1092:删 Telegram in-memory updates + route via NyxID relay per Phase 9 r2 共识(3/3 unanimous,经 reflector r1 narrowing 禁新 actor): - TelegramUserConnector:删 telegram_user /getUpdates in-memory queue + process-local state(净 -222 LOC) - Route inbound Telegram 通过现有 NyxID relay/proxy surface - AevatarConnectorConfig 配套调整 - 测试覆盖更新(ConnectorAndHostingCoverageTests / ConnectorConfigTests) 不开新 actor type / 不动 send/login。 closes #1092 ⟦AI:AUTO-LOOP⟧ * PR #1095 fix r1: 补 NyxIdServiceApiHints 回归 + 删 dead helper + 改测试名 per Phase 8 r1 verdicts(applied-3:rejected-0): - 补 NyxIdServiceApiHintsTests +7 LOC(防 /getUpdates 复出现在 hint) - 删 TelegramUserConnector.ToBotStyleChatId private dead helper(-11) - ConnectorAndHostingCoverageTests 测试名改 ShouldRejectGetUpdatesWithNyxIdRelayGuidance ⟦AI:AUTO-LOOP⟧ * PR #1095 fix r2: 补 TelegramUserConnector source-regression guard per Phase 8 r2 tests reject(applied-1): - ConnectorAndHostingCoverageTests 加 +26 LOC source-regression test 防 TelegramInboundUpdate / GetUpdatesPayload / MaxBufferedUpdates / Queue<> 等 inbound queue/polling 状态复发 ⟦AI:AUTO-LOOP⟧ * PR #1095 fix r3: 加 AevatarConnectorConfig fallback 分支测试 (+32 LOC) per Phase 8 r3 tests reject(applied-1):覆盖 `allowedOperations` 只含 /getUpdates 时 fallback /sendMessage 分支。 ⟦AI:AUTO-LOOP⟧
…tDefinitionSnapshot (#1096) * iter113 cluster-2 #1093: 删 Scripting runtime side-read + direct ScriptDefinitionSnapshot per Phase 9 r2 共识(3/3 unanimous identical): - 删 ScriptBehaviorRuntimeCapabilities readmodel side-read/cache - 删 IScriptDefinitionSnapshotFactory factory injection - Command 直接携 typed ScriptDefinitionSnapshot - Script-facing API migrated as in-scope migration risk 不开新 actor / envelope / pipeline。 closes #1093 ⟦AI:AUTO-LOOP⟧ * PR #1096 fix r1: 加 ScriptBehaviorRuntimeCapabilities source-regression test (+39 LOC) per Phase 8 r1 tests reject(applied-1):防 IScriptDefinitionSnapshotPort / ResolveDefinitionSnapshotAsync / IScriptDefinitionSnapshotFactory 复发。 ⟦AI:AUTO-LOOP⟧
…LOC) (#1097) * iter114 cluster-2 #1094: 删 VoicePresenceModule attach bridge + actor-owned port per Phase 9 r3 共识(3/3 unanimous B,经 r2 split → r3 minimal 改向 B 收敛): - 删 VoicePresenceModule.cs:_transportPump / _providerSession / _providerSessionKey / _volatileSelfSignalDispatcher process-local fields(净 -452) - 删 AttachTransport bridge + background relay loop(净 -565 from VoiceTransportRelayTests) - attach 走 existing actor-owned attachment port - default DI returns honest unsupported(不 silent) - 测试同步(8 files / 净 -1643) 不开新 actor type / envelope / pipeline。 closes #1094 ⟦AI:AUTO-LOOP⟧ * PR #1097 fix r1: 删 deletion-leftover test dead code (-100 LOC) per Phase 8 r1 quality reject(applied-3):删 module relay bridge 后留 PassiveVoiceTransport / RecordingVoiceTransport / StubVoiceTransport 3 个 nested test double + 对应 unused using import,grep 0 caller。 ⟦AI:AUTO-LOOP⟧ * PR #1097 fix r2: 删 production no-op + 4 残留 dead test doubles (-94 LOC) per Phase 8 r2 quality reject(applied-2): - VoicePresenceModule.cs: 删 SendProviderAudioToCurrentTransportAsync 空实现 + call site - VoicePresenceModuleTests.cs: 删 ThrowingReceiveVoiceTransport/ThrowingSendVoiceTransport/ RecordingDispatchPort/ThrowingDispatchPort 4 个 nested test double ⟦AI:AUTO-LOOP⟧
…ctor state (#1100) * iter115 cluster-3 #1098: 收紧 WorkflowExecutionRuntimeContext + typed actor state(净 +461 LOC) per Phase 9 r1 consensus(merged structural): - 新增 typed proto state 承载 LLM/connector/secret control facts - WorkflowExecutionRuntimeContext 不再持权威事实,仅留 same-turn passthrough + guard - SecureInputStateAccess.SaveAsync 停止无条件清空 captured - 加 source-regression test 禁止 runtime context 重新含权威字段 - 加 behavioral test 验 resume / cleanup / no raw secret leak closes #1098 ⟦AI:AUTO-LOOP⟧ * PR #1100 fix r1: event-source typed ExecutionContext + replay test (applied-1) per Phase 8 r1 architect reject: - 新增 typed domain event 携 ExecutionContext 增量 + reducer - accessors → event builders;TransitionState 唯一更新路径 - 删 ExecutionContextState 直接 mutator 暴露 - 加 replay test 验 actor deactivate/reactivate 恢复 typed control/security values ⟦AI:AUTO-LOOP⟧
把 9 个 daemon / monitor / helper 从 `tools/refactor-loop/` 挪到 `.claude/skills/codex-refactor-loop/scripts/`,与 SKILL.md + prompts/ + spawn-codex.sh colocate。Skill 完全自包含。 背景:eanzhao 2026-05-23 4a02998 "Clean non-production assets" 误删 tools/refactor-loop/ 8 file(SKILL.md 强制依赖)。daemon 进程 in-memory 跑但源码 disk 没,功能 dead 5+ 天 → behind dev 50。 修法: - restore 8 file 到 skill scripts/(triage-monitor.sh 也 move 过来) - 删空的 tools/refactor-loop/ 目录 - SKILL.md / prompts / scripts 自身的 path 全替换 tools/refactor-loop → .claude/skills/codex-refactor-loop/scripts (sed bulk replace,17+1+9 = 27 处) 后续:dev_sync_daemon.py 仍有 MERGE_HEAD path bug(本 PR 不改, 只 colocate);后续 PR 改 `git rev-parse --git-dir` 替代硬编码 `.git/MERGE_HEAD`。 ⟦AI:AUTO-LOOP⟧
旧 prompt 把 body 空 issue (#1102 'workflow、llm、nyx耦合') 直接 reject unclear,Auric 评论 '你自己搜搜不会么?'。 改成 Step 0 强制 deep investigation:title token 分解 + per-token rg + cross-token intersection (耦合点 evidence) + git log + CLAUDE 条款对照。'unclear' / 'body 空' 严禁作 reject reason — body 形态是 触发调研的信号,不是 verdict 依据。 只在确切非范畴 (product-feature / docs-only / out-of-scope / dup / scope-too-large) reject。 ⟦AI:AUTO-LOOP⟧
…') (#1107) PR #1106 反复 fix → push → CI fail loop 浪费 ~2h: - sync codex 只跑 dotnet build 不跑 dotnet test → 30+ test fail 没 catch - fix r1 narrow filter ("hosting only") → 68 pass marker → CI 暴露 74 fail in channel/runtime 修法: - rule 10: 所有 sync/fix/implement/test-add codex DONE marker 前必跑 full slnx test (filter 只用于 iterative 反馈,最后必须 full) - rule 11: controller commit/push 前 self-verify 兜底,即使 codex marker pass CI 是 fault detector 不是 fix-loop driver。本地一次跑全 test 直接 catch 而不是 push → CI fail → fix → 重 push → CI 再 fail 循环。 ⟦AI:AUTO-LOOP⟧
* docs(adr): add ADR-0026 tool-first chat ingress Records the architectural decision to collapse ChatRouteAction to Reject + ForwardToModel, exposing GAgent/Team/Workflow invocation as IAgentToolSource tools through the existing ToolCallLoop. Supersedes ADR-0024 §D5 (v1 action set) and ADR-0025 (voice v1 ForwardToGAgent); ADR-0024 D1/D2/D3/D4/D6 stand. Tracked end-to-end in epic #808; voice GA prerequisite in #809. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ai-tools): add 5 invocation tool sources + shared dispatcher Implements ADR-0026 Stage 1 unit-1 (epic #808). New project src/Aevatar.AI.ToolProviders.AevatarInvocation/ exposes aevatar_invoke_gagent / _invoke_team / _start_workflow / _observe_run / _query_readmodel as IAgentToolSource, so the LLM can drive orchestration through the existing ToolCallLoop instead of parallel router branches. Design: - Tool payloads are proto-derived strict JSON-Schema (no map<string,string> bags) - wait=ack|stream|complete supported; stream is default for long-running tools; GAgent/workflow wait=complete returns wait_complete_unavailable until Stage 2 session actor lands - Caller scope flows through AgentToolRequestContext only; protected caller-scope keys (LLMRequestMetadataKeys.*) are stripped from LLM-supplied payload.headers before server values are stamped, so the LLM cannot inject overrides for nyxid.access_token / scope_id / owner_subject etc. - query_readmodel is bounded to a closed registered set - Dispatch reuses existing surfaces (IActorDispatchPort, ITeamEntryMemberResolver + IStaticGAgentStreamInvocationPort<AGUIEvent>, ICommandDispatchService<WorkflowChatRunRequest,...>); no new dispatch chain 21 tests pass (4 credential-injection regression + 1 ObserveRun fast-fail added in post-review hardening); arch_guards + test_stability + docs lint all PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(lark): cover caller-scope propagation in LarkMessagesSendTool Implements ADR-0026 Stage 1 unit-2 (D7 prerequisite) for the Lark outbound caller-scope guarantee. After auditing the existing path (LarkMessagesSendTool → LarkNyxClient → NyxIdApiClient) no production refactor was required: the tool already reads AgentToolRequestContext.NyxIdAccessToken (no credential parameters) and forwards the caller bearer through NyxID's api-lark-bot proxy, which exchanges to a Lark tenant_access_token without seeing the caller's authorization header. The metadata-bag credential-injection surface that unit-1 had to harden is structurally absent here (no headers/metadata bag at the dispatch boundary). Added 2 regression tests: - Asserts the dispatched NyxID call carries AgentToolRequestContext's trusted typed NyxIdAccessToken - Asserts a malicious LLM payload (smuggled nyx_id_access_token, fake headers, ExternalMetadata overriding LLMRequestMetadataKeys.NyxIdAccessToken) cannot override the trusted caller token at dispatch NyxID investigation summary (verified via gh against ChronoAIProject/NyxID backend source): /api/v1/proxy/s/api-lark-bot/open-apis/im/v1/messages accepts only the caller's NyxID bearer; NyxID resolves caller's api-lark-bot binding, exchanges {app_id, app_secret} → tenant_access_token per channel_adapters/lark.rs::lark_family_token_exchange_config, strips the inbound authorization, and injects bearer for outbound to Lark. Semantic: messages post as the caller's bound Lark bot (NyxID-mediated), not as the human user's OAuth identity and not as Aevatar's service-level identity. This satisfies ADR-0026 §D7's "lands in the caller's Lark account" use case. 61/61 tests pass; arch_guards + test_stability + docs lint all PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(responses): add /v1/responses → aevatar_invoke_gagent E2E test Closes ADR-0026 Stage 1 (epic #808). Integration test demonstrates the new tool-first ingress path works end-to-end after units 1+2 landed, without touching any production code. Test: MainnetResponsesEndpointsTests.PostResponses_StreamWithAevatarInvokeGAgentAdditiveTool_ShouldDispatchActorEnvelope Scenario: - /v1/responses streamed request with real DI registration of unit-1's AddAevatarInvocationTools (5 production IAgentToolSource instances) - Stubbed LLM emits aevatar_invoke_gagent tool call with a malicious payload that smuggles nyxid.access_token + aevatar.scope_id overrides - ResponsesCompletionApplicationService executes the local tool call inline (not as function_call SSE output — verified against production StreamAsync behavior) - AevatarInvocationDispatcher dispatches through IActorDispatchPort (captured by RecordingActorDispatchPort) - LLM round 2 continues after tool result, SSE lifecycle completes Assertions: - Dispatched envelope's Route.PublisherActorId == DirectGAgentPublisherId - Dispatched ChatRequestEvent.Headers carry the trusted bearer/scope (caller-scope protection from unit-1 verified end-to-end) - ThrowingStaticGAgentStreamInvocationPort.InvocationCount == 0 (the legacy ForwardToGAgent/ForwardToTeam path in ResponsesEndpoints.cs:779-927 is NOT entered) 202/202 tests pass in Aevatar.Hosting.Tests; arch_guards + test_stability_guards + docs lint all PASS. No production code changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix responses forward team status probes * feat(routing): extend ForwardToModel with tool_set_ref + tool_choice_hint and add ToolSetRegistry Stage 2 Unit 1 of ADR-0026 (epic #808). Lays the proto + composition foundation that Stage 2 Units 2 (resolver translation) and 3 (ChatRunActor) consume. Proto changes (src/Aevatar.ChatRouting.Abstractions/chat_route_policy.proto): - ForwardToModel.tool_set_ref (field 2) — ChatRouteToolSetRef typed message - ForwardToModel.tool_choice_hint (field 3) — ChatRouteToolChoiceHint typed message with google.protobuf.Struct prefilled_arguments (NOT map<string,string> per CLAUDE.md typed-proto rule) - No oneof reshape; legacy ForwardToGAgent/Team/Workflow variants untouched New project src/Aevatar.AI.ToolProviders.ToolSetRegistry/: - IToolSetRegistry: resolves ChatRouteToolSetRef.name to IAgentToolSource list at boundary time via DI factories (NOT cached at registration — preserves per-request scope for tool sources carrying caller context) - ToolSetResolveResult / ToolSetResolveError: structured errors (tool_set_name_required / unknown_tool_set), no exceptions for normal failure - ResponsesAevatarToolProvider now also implements IAgentToolSource so existing Responses substitute tools participate in named composition without changing the IResponsesToolProvider path Three default named sets (registered in MainnetHostBuilderExtensions): - workspace.default: comprehensive (Stage 1 invocation + substitute/additive tools + NyxID/Lark/Telegram/ChronoStorage/Web) - lark.self_notify: minimal (lark_messages_send + aevatar_query_readmodel) — for ADR-0026 §D7 "push to my Lark" use case - voice.realtime: placeholder for Stage 5 (currently same shape as workspace.default; tightens when voice convergence lands) Argument-merge policy documented for Unit 2 boundary code: server-set prefilled_arguments are trusted route policy; LLM-supplied arguments that conflict on the same key MUST be rejected, not silently overwritten. 10 tests added across 3 test projects (registry resolve / proto round-trip / Mainnet DI composition incl. lark.self_notify minimal-set assertion). Build + arch_guards + test_stability + docs lint all PASS. No ChatRouteResolver changes (Unit 2). No ChatRunActor (Unit 3). No legacy deletion (Stage 4). No external repo changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(gitignore): ignore .implement-loop/ codex-implement-loop artifacts The loop maintains state, prompts, logs, reviews, run summaries, and worktrees under .implement-loop/ at the repo root. They are session-local artifacts, not source of truth; ignoring them prevents accidental staging during merges back into the working branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(routing): translate legacy forward actions to ForwardToModel + tool_choice_hint Stage 2 Unit 2 of ADR-0026. ChatRouteResolver now performs runtime translation so existing policy rules emitting legacy ForwardToGAgent / ForwardToTeam / ForwardToWorkflow continue to work while the consumer side (Unit 3's ChatRunActor and the existing /v1/responses path) only needs to understand ForwardToModel + tool_choice_hint. Persisted policy proto is unchanged; Stage 4 will delete the legacy oneof variants. Translations (in-memory only, ChatRouteDecision stays transient per ADR-0024 D2): ForwardToGAgent(actor_id) → ForwardToModel { tool_set_ref = workspace.default tool_choice_hint = { tool_name = aevatar_invoke_gagent prefilled = { actor_id } } } ForwardToTeam(team_id, endpoint) → ForwardToModel { tool_set_ref = workspace.default tool_choice_hint = { tool_name = aevatar_invoke_team prefilled = { team_id, endpoint_id, [scope_id if non-empty] } } } ForwardToWorkflow(workflow_id) → ForwardToModel { tool_set_ref = workspace.default tool_choice_hint = { tool_name = aevatar_start_workflow prefilled = { workflow_id } } } ForwardToModel → pass-through (preserve new fields) Reject → pass-through ChatRoutePolicyMigrator.MigrateLegacyActions(snapshot): pure in-memory transform that rewrites legacy actions to the canonical shape; idempotent on already-new-shape snapshots. Persistence flow is Stage 3 work. Tool-set name sourced from ToolSetNames.WorkspaceDefault (no magic strings). prefilled_arguments built as google.protobuf.Struct (NOT map<string,string>) via Value.ForString. voice_module_name from legacy ForwardToGAgent is NOT emitted because aevatar_invoke_gagent proto has no such field. Known asymmetry to address in Unit 3: scope_id is emitted for aevatar_invoke_team but InvokeTeamToolRequest proto has no scope_id field; the boundary consumer must strip unknown args before JsonParser.Default.Parse or surface scope_id through a different channel. 36/36 ChatRouting.Core.Tests pass (16 new); build + arch_guards + test_stability all PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chatrun): introduce ChatRunActor session-scoped actor + boundary integration Stage 2 Unit 3 of ADR-0026 (epic #808). Closes Stage 2. ChatRunActor (src/platform/Aevatar.GAgentService.Core/GAgents/ChatRunActor.cs): - Business-named, session-scoped (keyed chat-run:{response_id}) - Typed proto State (ChatRunState in llm_sessions.proto): LLM context + tool_call_history + active_sub_run_subscriptions as repeated typed ChatRunSubRunSubscription (NOT Dictionary — CLAUDE.md 中间层状态约束) - Event-sourced via PersistDomainEventAsync + reducer - Single-threaded event loop (no lock / ConcurrentDictionary / GetAwaiter) - Self-continuation via PublishAsync(..., TopologyAudience.Self) Cross-actor sub-run observation (the architectural pattern this unit settles): - ChatRunActor itself owns the observation via IStreamProvider.GetStream(targetActorId).UpsertRelayAsync( StreamForwardingMode.HandleThenForward, EventTypeFilter:CommittedStateEventPublished) — mirrors src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeGAgentBase precedent - Forwarded envelopes consumed via [AllEventHandler], correlated by run_id + publisher_actor_id, fold terminal result on actor event loop, publish typed ChatRunToolResultReady on self-stream - ChatRunToolCompletionCoordinator (Application layer) only awaits typed ChatRunToolResultReady through IChatRunActorPort — NO raw SubscribeAsync<EventEnvelope> from Application (round-1 review caught the original violation; round-2 verified the fix) Boundary integration in ResponsesEndpoints + ResponsesCompletionApplicationService: - Resolves ForwardToModel.tool_set_ref via IToolSetRegistry → turn's additive tool set - tool_choice_hint pinned to tool_name; prefilled_arguments stamped on; LLM arguments conflicting on prefilled keys → structured tool_choice_prefill_conflict error so LLM self-corrects (Stage 1 unit-1 caller-scope hardening pattern extended to this layer) - Only aevatar_invoke_gagent/_invoke_team/_start_workflow with wait=complete route through ChatRunActor; everything else (wait=ack/stream, other tools) keeps current inline behavior — minimal viable integration scope_id asymmetry (unit-2 review concern) resolved via ProtoToolArguments.Parse(WithIgnoreUnknownFields(true)) — scoped to invocation- tool-request parsing only, NOT global. caller-scope channel via dispatcher metadata is authoritative; inline scope_id from legacy ForwardToTeam is silently dropped from InvokeTeamToolRequest parsing. Regression test asserts the dispatched envelope still has correct caller scope. Stage 1 wait_complete_unavailable migrated: AevatarInvocationDispatcher no longer rejects wait=complete; dispatches and returns receipt; completion is folded by ChatRunActor when the path goes through coordinator. Regression test updated accordingly. Test for user-owned ResponsesForwardTeamInternalProbeExecutorTests.cs was renamed + reasserted to reflect Stage 2 unit-2's cascade: resolver now translates legacy ForwardToTeam → ForwardToModel + tool_choice_hint, so the probe correctly reports Down for that route shape until the user migrates the probe's expectation. Production probe code untouched. Explanatory comment added linking to ChatRoutePolicyMigrator + ADR-0026 D2. 814/814 tests pass (36+23+207+548 across 4 affected projects). SubscribeAsync<EventEnvelope> ban in dispatch_projection_boundary_guard passes when arch_guards run from worktree (canonical CI invocation). Non-blocking follow-up: RemoveRelayAsync not called on ChatRunActor HandleTerminateAsync — bounded leak per session, see review round-2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(routing): deprecation signaling for legacy actions + migration endpoint Stage 3 Unit 1 of ADR-0026 (epic #808). Makes Stage 4's upcoming deletion of ForwardToGAgent/Team/Workflow safe by giving operators visible signal and a runtime-accessible migration tool. Resolver deprecation signaling: - ChatRouteDecision.deprecations: repeated ChatRouteDeprecation typed proto sub-messages (NOT map<string,string>); transient per ADR-0024 D2 - ChatRouteDeprecation carries: code, message, action_kind, matched_rule_id, translated_target - ChatRouteResolver emits LogWarning chat_route_legacy_action_used with structured fields when a rule resolves to a legacy action (one log per matched legacy rule per resolve call, not per request) - Covers both rule-match and default_target paths Consumer header propagation (RFC 9745 Deprecation + RFC 9110 Warning): - ApplyChatRouteDeprecationHeaders helper called from ResponsesEndpoints and MessagesEndpoints after route resolution - Sets `Deprecation: true` + `Warning: 299 - "chat_route_legacy_action_used: <details>"` - Voice boundary log-only (WebSocket can't carry response headers after upgrade) Migration helper exposure: - New admin endpoint POST /api/scopes/{scopeId}/chat-route-policy/migrate - Dry-run default: returns the migrated UpsertChatRoutePolicyRequested payload - ?apply=true: dispatches as single atomic UpsertChatRoutePolicyRequested command to ChatRoutePolicyGAgent (ADR-0024 D5: no temporary invalid state) - Reuses existing chat-route-policy admin auth (scope ownership / admin role) Tests cover: resolver deprecation per legacy action variant + empty for ForwardToModel + structured warning fields; admin endpoint dry-run + apply; SSE response Deprecation+Warning headers for legacy ForwardToGAgent. No deletion of legacy proto variants (Stage 4). No actor changes. No external repo changes. Build + ChatRouting.Core.Tests + Hosting.Tests + test_stability_guards + architecture_guards (through workflow_binding boundary) all PASS canonically from worktree; playground_asset_drift_guard env-blocked in worktree per known infrastructure gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(chatrun): clean up sub-run forwarding relays on terminate Stage 3 Unit 2 of ADR-0026 (follow-up from Stage 2 unit-3 review). Closes the bounded forwarding-registry-binding leak per chat run session. Mirrors src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeGAgentBase RemoveObservationRelayAsync precedent: ChatRunActor.HandleTerminateAsync snapshots active_sub_run_subscriptions before persisting ChatRunTerminatedEvent (which clears them via the existing reducer), then iterates the snapshot and calls IStreamProvider.GetStream(targetActorId).RemoveRelayAsync(Id, ct) for each target. Cleanup runs sequentially on the actor event loop (no Task.Run / Timer); failures are best-effort with Logger.LogWarning; iteration continues; state remains cleared regardless. OperationCanceledException not swallowed. Regression tests (2 new in ChatRunActorTests): - Terminate_ShouldRemoveRelayAsync_ForEachActiveSubscription: N=2 active subscriptions, observable IStreamProvider, asserts RemoveRelayAsync called per target_actor_id + state cleared - Terminate_WhenRemoveRelayThrows_ShouldStillClearState: simulated throw, asserts cleanup attempted for all targets, warnings logged, no exception propagated, state cleared 550/550 GAgentService.Tests pass; arch_guards + test_stability PASS canonically from worktree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add chat completions facade * Add chat completions status probe * fix: address PR review feedback * Fix tool-driven chat route completion handling * Retire responses forward team status probes * Add core loop status probe * Cover chat completions auth fallback * Share NyxID direct tool sources * Fix status dashboard startup tests * Fix user LLM route model handling * Retire auth gate status probes * Add default workspace tool set routing * Strip consumed keys from submission.Arguments after typed payloads After populating WorkflowResume / LlmSelection typed sub-messages, remove the matching keys from the open-ended Arguments map so each piece of data has a single semantic location (typed field), not two. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Adopt typed DispatchAdmission stub across ChannelRuntime tests IActorDispatchPort.DispatchAsync returns Task<DispatchAdmission>; the prior Task.CompletedTask stubs no longer satisfy that signature. Add ActorDispatchPortTestSupport.AcceptAsync that builds an admission via DispatchAdmissionFactory from the captured actorId + envelope, and switch every Substitute setup to it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Align ScopeServiceEndpoints stream test with shipped event flow Production now emits only TextMessageEnd + RunFinished for the pong path (no Start/Content frames), and surfaces the upstream LLM message verbatim instead of wrapping it with "LLM request failed [tools=none]:". The assertions were stale; this updates them to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Wait for committed binding and runtime state in script integration tests Reading state immediately after EnsureRuntime/Run was racing with the async binding event and read-model materialization. Add two helpers on ScriptEvolutionIntegrationTestKit -- WaitForScriptBindingAsync (polls the event store for the matching ScriptBehaviorBoundEvent) and WaitForStateAsync<TState> (polls the actor's StateRoot until a predicate matches) -- and adopt them across HybridServiceUpgrade, ScriptDefinitionRuntimeContract, and ScriptGAgentFactoryLifecycle tests so assertions observe the committed state instead of a snapshot in flight. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Register YieldApprovalHandler so mainnet ssh_exec opt-in is satisfied Mainnet sets EnableSshExecTool=true but never registered an IToolApprovalHandler, so NyxIdAgentToolSource.DiscoverToolsAsync threw on every Lark conversation turn and the bot stopped replying. The canonical yielding handler keeps tool exposure gated while approval flows yield into RoleGAgent's actor-owned remote continuation via NyxIdRemoteToolApprovalPort. iter23/cluster-001-nyxid-tool-approval-polling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix Lark CardKit self continuations * Fix coverage-quality integration tests * Fix relay skill continuation timeout * Fix relay skill continuation flow * Fix Studio bind endpoint UX * Fix Studio observe page focus * Fix Studio observe run lookup * Fix Script member creation flow * Update Studio execution page test * Remove backend changes from observe PR --------- Co-authored-by: eanzhao <yiqi.zhao@aelf.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: eanzhao <141116261+eanzhao@users.noreply.github.com> Co-authored-by: AbigailDeng <Abigail.deng@aelf.io> Co-authored-by: AbigailDeng <108705114+AbigailDeng@users.noreply.github.com>
…auto-PR)+ rule 10/11 (#1110) per Auric 2026-05-27 'codex 处理所有复杂情况 + 监测 18h 差自动 rollup PR auto-merge'。 升级: 1. MERGE_HEAD path bug 修(用 git rev-parse --git-dir 替代硬编码 .git/MERGE_HEAD) — 之前 secondary worktree .git 是文件 daemon 永远 detect 不到 merge state 2. dev → auto-refact-dev sync 不再直 push(branch protection enforce_admins=true 拒) — 改 push chore/dev-sync-auto-<ts> + gh pr create + gh pr merge --auto 3. 新增 auto-refact-dev → dev rollup: - check earliest divergent commit age >= 18h(env ROLLUP_HOURS_THRESHOLD) - push chore/auto-refact-dev-rollup-<ts> + gh pr create base=dev + auto-merge 4. 防 spam:has_open_pr 检测同 prefix 已开 PR 即 skip 5. 升级 sync codex prompt: - 引 rule 10/11(full slnx test mandatory before marker) - 列 PR1106 复杂场景:test fixture DI / API rename / proto rename / behavioral change / namespace add / 跨 module 同根因 6. 双 worktree:aevatar-wt-dev-sync (sync) + aevatar-wt-dev-rollup (rollup) 工作流: sync: ff/no-ff in worktree → 开 PR base=auto-refact-dev → auto-merge rollup: earliest_ahead >= 18h → branch + PR base=dev → auto-merge ⟦AI:AUTO-LOOP⟧
…ypass) (#1115) * docs(adr): add ADR-0026 tool-first chat ingress Records the architectural decision to collapse ChatRouteAction to Reject + ForwardToModel, exposing GAgent/Team/Workflow invocation as IAgentToolSource tools through the existing ToolCallLoop. Supersedes ADR-0024 §D5 (v1 action set) and ADR-0025 (voice v1 ForwardToGAgent); ADR-0024 D1/D2/D3/D4/D6 stand. Tracked end-to-end in epic #808; voice GA prerequisite in #809. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ai-tools): add 5 invocation tool sources + shared dispatcher Implements ADR-0026 Stage 1 unit-1 (epic #808). New project src/Aevatar.AI.ToolProviders.AevatarInvocation/ exposes aevatar_invoke_gagent / _invoke_team / _start_workflow / _observe_run / _query_readmodel as IAgentToolSource, so the LLM can drive orchestration through the existing ToolCallLoop instead of parallel router branches. Design: - Tool payloads are proto-derived strict JSON-Schema (no map<string,string> bags) - wait=ack|stream|complete supported; stream is default for long-running tools; GAgent/workflow wait=complete returns wait_complete_unavailable until Stage 2 session actor lands - Caller scope flows through AgentToolRequestContext only; protected caller-scope keys (LLMRequestMetadataKeys.*) are stripped from LLM-supplied payload.headers before server values are stamped, so the LLM cannot inject overrides for nyxid.access_token / scope_id / owner_subject etc. - query_readmodel is bounded to a closed registered set - Dispatch reuses existing surfaces (IActorDispatchPort, ITeamEntryMemberResolver + IStaticGAgentStreamInvocationPort<AGUIEvent>, ICommandDispatchService<WorkflowChatRunRequest,...>); no new dispatch chain 21 tests pass (4 credential-injection regression + 1 ObserveRun fast-fail added in post-review hardening); arch_guards + test_stability + docs lint all PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(lark): cover caller-scope propagation in LarkMessagesSendTool Implements ADR-0026 Stage 1 unit-2 (D7 prerequisite) for the Lark outbound caller-scope guarantee. After auditing the existing path (LarkMessagesSendTool → LarkNyxClient → NyxIdApiClient) no production refactor was required: the tool already reads AgentToolRequestContext.NyxIdAccessToken (no credential parameters) and forwards the caller bearer through NyxID's api-lark-bot proxy, which exchanges to a Lark tenant_access_token without seeing the caller's authorization header. The metadata-bag credential-injection surface that unit-1 had to harden is structurally absent here (no headers/metadata bag at the dispatch boundary). Added 2 regression tests: - Asserts the dispatched NyxID call carries AgentToolRequestContext's trusted typed NyxIdAccessToken - Asserts a malicious LLM payload (smuggled nyx_id_access_token, fake headers, ExternalMetadata overriding LLMRequestMetadataKeys.NyxIdAccessToken) cannot override the trusted caller token at dispatch NyxID investigation summary (verified via gh against ChronoAIProject/NyxID backend source): /api/v1/proxy/s/api-lark-bot/open-apis/im/v1/messages accepts only the caller's NyxID bearer; NyxID resolves caller's api-lark-bot binding, exchanges {app_id, app_secret} → tenant_access_token per channel_adapters/lark.rs::lark_family_token_exchange_config, strips the inbound authorization, and injects bearer for outbound to Lark. Semantic: messages post as the caller's bound Lark bot (NyxID-mediated), not as the human user's OAuth identity and not as Aevatar's service-level identity. This satisfies ADR-0026 §D7's "lands in the caller's Lark account" use case. 61/61 tests pass; arch_guards + test_stability + docs lint all PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(responses): add /v1/responses → aevatar_invoke_gagent E2E test Closes ADR-0026 Stage 1 (epic #808). Integration test demonstrates the new tool-first ingress path works end-to-end after units 1+2 landed, without touching any production code. Test: MainnetResponsesEndpointsTests.PostResponses_StreamWithAevatarInvokeGAgentAdditiveTool_ShouldDispatchActorEnvelope Scenario: - /v1/responses streamed request with real DI registration of unit-1's AddAevatarInvocationTools (5 production IAgentToolSource instances) - Stubbed LLM emits aevatar_invoke_gagent tool call with a malicious payload that smuggles nyxid.access_token + aevatar.scope_id overrides - ResponsesCompletionApplicationService executes the local tool call inline (not as function_call SSE output — verified against production StreamAsync behavior) - AevatarInvocationDispatcher dispatches through IActorDispatchPort (captured by RecordingActorDispatchPort) - LLM round 2 continues after tool result, SSE lifecycle completes Assertions: - Dispatched envelope's Route.PublisherActorId == DirectGAgentPublisherId - Dispatched ChatRequestEvent.Headers carry the trusted bearer/scope (caller-scope protection from unit-1 verified end-to-end) - ThrowingStaticGAgentStreamInvocationPort.InvocationCount == 0 (the legacy ForwardToGAgent/ForwardToTeam path in ResponsesEndpoints.cs:779-927 is NOT entered) 202/202 tests pass in Aevatar.Hosting.Tests; arch_guards + test_stability_guards + docs lint all PASS. No production code changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix responses forward team status probes * feat(routing): extend ForwardToModel with tool_set_ref + tool_choice_hint and add ToolSetRegistry Stage 2 Unit 1 of ADR-0026 (epic #808). Lays the proto + composition foundation that Stage 2 Units 2 (resolver translation) and 3 (ChatRunActor) consume. Proto changes (src/Aevatar.ChatRouting.Abstractions/chat_route_policy.proto): - ForwardToModel.tool_set_ref (field 2) — ChatRouteToolSetRef typed message - ForwardToModel.tool_choice_hint (field 3) — ChatRouteToolChoiceHint typed message with google.protobuf.Struct prefilled_arguments (NOT map<string,string> per CLAUDE.md typed-proto rule) - No oneof reshape; legacy ForwardToGAgent/Team/Workflow variants untouched New project src/Aevatar.AI.ToolProviders.ToolSetRegistry/: - IToolSetRegistry: resolves ChatRouteToolSetRef.name to IAgentToolSource list at boundary time via DI factories (NOT cached at registration — preserves per-request scope for tool sources carrying caller context) - ToolSetResolveResult / ToolSetResolveError: structured errors (tool_set_name_required / unknown_tool_set), no exceptions for normal failure - ResponsesAevatarToolProvider now also implements IAgentToolSource so existing Responses substitute tools participate in named composition without changing the IResponsesToolProvider path Three default named sets (registered in MainnetHostBuilderExtensions): - workspace.default: comprehensive (Stage 1 invocation + substitute/additive tools + NyxID/Lark/Telegram/ChronoStorage/Web) - lark.self_notify: minimal (lark_messages_send + aevatar_query_readmodel) — for ADR-0026 §D7 "push to my Lark" use case - voice.realtime: placeholder for Stage 5 (currently same shape as workspace.default; tightens when voice convergence lands) Argument-merge policy documented for Unit 2 boundary code: server-set prefilled_arguments are trusted route policy; LLM-supplied arguments that conflict on the same key MUST be rejected, not silently overwritten. 10 tests added across 3 test projects (registry resolve / proto round-trip / Mainnet DI composition incl. lark.self_notify minimal-set assertion). Build + arch_guards + test_stability + docs lint all PASS. No ChatRouteResolver changes (Unit 2). No ChatRunActor (Unit 3). No legacy deletion (Stage 4). No external repo changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(gitignore): ignore .implement-loop/ codex-implement-loop artifacts The loop maintains state, prompts, logs, reviews, run summaries, and worktrees under .implement-loop/ at the repo root. They are session-local artifacts, not source of truth; ignoring them prevents accidental staging during merges back into the working branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(routing): translate legacy forward actions to ForwardToModel + tool_choice_hint Stage 2 Unit 2 of ADR-0026. ChatRouteResolver now performs runtime translation so existing policy rules emitting legacy ForwardToGAgent / ForwardToTeam / ForwardToWorkflow continue to work while the consumer side (Unit 3's ChatRunActor and the existing /v1/responses path) only needs to understand ForwardToModel + tool_choice_hint. Persisted policy proto is unchanged; Stage 4 will delete the legacy oneof variants. Translations (in-memory only, ChatRouteDecision stays transient per ADR-0024 D2): ForwardToGAgent(actor_id) → ForwardToModel { tool_set_ref = workspace.default tool_choice_hint = { tool_name = aevatar_invoke_gagent prefilled = { actor_id } } } ForwardToTeam(team_id, endpoint) → ForwardToModel { tool_set_ref = workspace.default tool_choice_hint = { tool_name = aevatar_invoke_team prefilled = { team_id, endpoint_id, [scope_id if non-empty] } } } ForwardToWorkflow(workflow_id) → ForwardToModel { tool_set_ref = workspace.default tool_choice_hint = { tool_name = aevatar_start_workflow prefilled = { workflow_id } } } ForwardToModel → pass-through (preserve new fields) Reject → pass-through ChatRoutePolicyMigrator.MigrateLegacyActions(snapshot): pure in-memory transform that rewrites legacy actions to the canonical shape; idempotent on already-new-shape snapshots. Persistence flow is Stage 3 work. Tool-set name sourced from ToolSetNames.WorkspaceDefault (no magic strings). prefilled_arguments built as google.protobuf.Struct (NOT map<string,string>) via Value.ForString. voice_module_name from legacy ForwardToGAgent is NOT emitted because aevatar_invoke_gagent proto has no such field. Known asymmetry to address in Unit 3: scope_id is emitted for aevatar_invoke_team but InvokeTeamToolRequest proto has no scope_id field; the boundary consumer must strip unknown args before JsonParser.Default.Parse or surface scope_id through a different channel. 36/36 ChatRouting.Core.Tests pass (16 new); build + arch_guards + test_stability all PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chatrun): introduce ChatRunActor session-scoped actor + boundary integration Stage 2 Unit 3 of ADR-0026 (epic #808). Closes Stage 2. ChatRunActor (src/platform/Aevatar.GAgentService.Core/GAgents/ChatRunActor.cs): - Business-named, session-scoped (keyed chat-run:{response_id}) - Typed proto State (ChatRunState in llm_sessions.proto): LLM context + tool_call_history + active_sub_run_subscriptions as repeated typed ChatRunSubRunSubscription (NOT Dictionary — CLAUDE.md 中间层状态约束) - Event-sourced via PersistDomainEventAsync + reducer - Single-threaded event loop (no lock / ConcurrentDictionary / GetAwaiter) - Self-continuation via PublishAsync(..., TopologyAudience.Self) Cross-actor sub-run observation (the architectural pattern this unit settles): - ChatRunActor itself owns the observation via IStreamProvider.GetStream(targetActorId).UpsertRelayAsync( StreamForwardingMode.HandleThenForward, EventTypeFilter:CommittedStateEventPublished) — mirrors src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeGAgentBase precedent - Forwarded envelopes consumed via [AllEventHandler], correlated by run_id + publisher_actor_id, fold terminal result on actor event loop, publish typed ChatRunToolResultReady on self-stream - ChatRunToolCompletionCoordinator (Application layer) only awaits typed ChatRunToolResultReady through IChatRunActorPort — NO raw SubscribeAsync<EventEnvelope> from Application (round-1 review caught the original violation; round-2 verified the fix) Boundary integration in ResponsesEndpoints + ResponsesCompletionApplicationService: - Resolves ForwardToModel.tool_set_ref via IToolSetRegistry → turn's additive tool set - tool_choice_hint pinned to tool_name; prefilled_arguments stamped on; LLM arguments conflicting on prefilled keys → structured tool_choice_prefill_conflict error so LLM self-corrects (Stage 1 unit-1 caller-scope hardening pattern extended to this layer) - Only aevatar_invoke_gagent/_invoke_team/_start_workflow with wait=complete route through ChatRunActor; everything else (wait=ack/stream, other tools) keeps current inline behavior — minimal viable integration scope_id asymmetry (unit-2 review concern) resolved via ProtoToolArguments.Parse(WithIgnoreUnknownFields(true)) — scoped to invocation- tool-request parsing only, NOT global. caller-scope channel via dispatcher metadata is authoritative; inline scope_id from legacy ForwardToTeam is silently dropped from InvokeTeamToolRequest parsing. Regression test asserts the dispatched envelope still has correct caller scope. Stage 1 wait_complete_unavailable migrated: AevatarInvocationDispatcher no longer rejects wait=complete; dispatches and returns receipt; completion is folded by ChatRunActor when the path goes through coordinator. Regression test updated accordingly. Test for user-owned ResponsesForwardTeamInternalProbeExecutorTests.cs was renamed + reasserted to reflect Stage 2 unit-2's cascade: resolver now translates legacy ForwardToTeam → ForwardToModel + tool_choice_hint, so the probe correctly reports Down for that route shape until the user migrates the probe's expectation. Production probe code untouched. Explanatory comment added linking to ChatRoutePolicyMigrator + ADR-0026 D2. 814/814 tests pass (36+23+207+548 across 4 affected projects). SubscribeAsync<EventEnvelope> ban in dispatch_projection_boundary_guard passes when arch_guards run from worktree (canonical CI invocation). Non-blocking follow-up: RemoveRelayAsync not called on ChatRunActor HandleTerminateAsync — bounded leak per session, see review round-2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(routing): deprecation signaling for legacy actions + migration endpoint Stage 3 Unit 1 of ADR-0026 (epic #808). Makes Stage 4's upcoming deletion of ForwardToGAgent/Team/Workflow safe by giving operators visible signal and a runtime-accessible migration tool. Resolver deprecation signaling: - ChatRouteDecision.deprecations: repeated ChatRouteDeprecation typed proto sub-messages (NOT map<string,string>); transient per ADR-0024 D2 - ChatRouteDeprecation carries: code, message, action_kind, matched_rule_id, translated_target - ChatRouteResolver emits LogWarning chat_route_legacy_action_used with structured fields when a rule resolves to a legacy action (one log per matched legacy rule per resolve call, not per request) - Covers both rule-match and default_target paths Consumer header propagation (RFC 9745 Deprecation + RFC 9110 Warning): - ApplyChatRouteDeprecationHeaders helper called from ResponsesEndpoints and MessagesEndpoints after route resolution - Sets `Deprecation: true` + `Warning: 299 - "chat_route_legacy_action_used: <details>"` - Voice boundary log-only (WebSocket can't carry response headers after upgrade) Migration helper exposure: - New admin endpoint POST /api/scopes/{scopeId}/chat-route-policy/migrate - Dry-run default: returns the migrated UpsertChatRoutePolicyRequested payload - ?apply=true: dispatches as single atomic UpsertChatRoutePolicyRequested command to ChatRoutePolicyGAgent (ADR-0024 D5: no temporary invalid state) - Reuses existing chat-route-policy admin auth (scope ownership / admin role) Tests cover: resolver deprecation per legacy action variant + empty for ForwardToModel + structured warning fields; admin endpoint dry-run + apply; SSE response Deprecation+Warning headers for legacy ForwardToGAgent. No deletion of legacy proto variants (Stage 4). No actor changes. No external repo changes. Build + ChatRouting.Core.Tests + Hosting.Tests + test_stability_guards + architecture_guards (through workflow_binding boundary) all PASS canonically from worktree; playground_asset_drift_guard env-blocked in worktree per known infrastructure gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(chatrun): clean up sub-run forwarding relays on terminate Stage 3 Unit 2 of ADR-0026 (follow-up from Stage 2 unit-3 review). Closes the bounded forwarding-registry-binding leak per chat run session. Mirrors src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeGAgentBase RemoveObservationRelayAsync precedent: ChatRunActor.HandleTerminateAsync snapshots active_sub_run_subscriptions before persisting ChatRunTerminatedEvent (which clears them via the existing reducer), then iterates the snapshot and calls IStreamProvider.GetStream(targetActorId).RemoveRelayAsync(Id, ct) for each target. Cleanup runs sequentially on the actor event loop (no Task.Run / Timer); failures are best-effort with Logger.LogWarning; iteration continues; state remains cleared regardless. OperationCanceledException not swallowed. Regression tests (2 new in ChatRunActorTests): - Terminate_ShouldRemoveRelayAsync_ForEachActiveSubscription: N=2 active subscriptions, observable IStreamProvider, asserts RemoveRelayAsync called per target_actor_id + state cleared - Terminate_WhenRemoveRelayThrows_ShouldStillClearState: simulated throw, asserts cleanup attempted for all targets, warnings logged, no exception propagated, state cleared 550/550 GAgentService.Tests pass; arch_guards + test_stability PASS canonically from worktree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add chat completions facade * Add chat completions status probe * fix: address PR review feedback * Fix tool-driven chat route completion handling * Retire responses forward team status probes * Add core loop status probe * Cover chat completions auth fallback * Share NyxID direct tool sources * Fix status dashboard startup tests * Fix user LLM route model handling * Retire auth gate status probes * Add default workspace tool set routing * Strip consumed keys from submission.Arguments after typed payloads After populating WorkflowResume / LlmSelection typed sub-messages, remove the matching keys from the open-ended Arguments map so each piece of data has a single semantic location (typed field), not two. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Adopt typed DispatchAdmission stub across ChannelRuntime tests IActorDispatchPort.DispatchAsync returns Task<DispatchAdmission>; the prior Task.CompletedTask stubs no longer satisfy that signature. Add ActorDispatchPortTestSupport.AcceptAsync that builds an admission via DispatchAdmissionFactory from the captured actorId + envelope, and switch every Substitute setup to it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Align ScopeServiceEndpoints stream test with shipped event flow Production now emits only TextMessageEnd + RunFinished for the pong path (no Start/Content frames), and surfaces the upstream LLM message verbatim instead of wrapping it with "LLM request failed [tools=none]:". The assertions were stale; this updates them to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Wait for committed binding and runtime state in script integration tests Reading state immediately after EnsureRuntime/Run was racing with the async binding event and read-model materialization. Add two helpers on ScriptEvolutionIntegrationTestKit -- WaitForScriptBindingAsync (polls the event store for the matching ScriptBehaviorBoundEvent) and WaitForStateAsync<TState> (polls the actor's StateRoot until a predicate matches) -- and adopt them across HybridServiceUpgrade, ScriptDefinitionRuntimeContract, and ScriptGAgentFactoryLifecycle tests so assertions observe the committed state instead of a snapshot in flight. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Register YieldApprovalHandler so mainnet ssh_exec opt-in is satisfied Mainnet sets EnableSshExecTool=true but never registered an IToolApprovalHandler, so NyxIdAgentToolSource.DiscoverToolsAsync threw on every Lark conversation turn and the bot stopped replying. The canonical yielding handler keeps tool exposure gated while approval flows yield into RoleGAgent's actor-owned remote continuation via NyxIdRemoteToolApprovalPort. iter23/cluster-001-nyxid-tool-approval-polling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix Lark CardKit self continuations * Fix coverage-quality integration tests * Fix relay skill continuation timeout * Fix relay skill continuation flow * Fix Studio bind endpoint UX * Fix Studio observe page focus * Fix Studio observe run lookup * Refactor settings LLM contract * Fix LLM settings save response semantics * Fix Script member creation flow * Update Studio execution page test * Remove backend changes from observe PR * Fix user config save ack semantics * fix(studio): 收敛 LLM 设置契约剩余架构问题 触发来源: PR #1072 三路复审指出 settings 与 slash 写入双轨、Application contract 泄露 JSON wire 细节、canonical settings 值域开放、ACK receipt 映射测试不足。 行为类型: 收敛 LLM preference 写入主干,将 JSON DTO 移到 Hosting 边界,封闭 route source/status/fallbackReason 值域,补 dispatch admission 和 settings 边界测试。 等价语义: Console Settings 与 /model slash 共享同一写入/normalization 语义;Application contract 表达业务语义而非 HTTP JSON wire contract。 后续复用: 后续 settings/channel LLM 入口可复用统一 writer/core、catalog normalizer 与 settings view builder。 失败痕迹归属: Studio 与 ChannelRuntime 测试、architecture guards、test stability guards 覆盖本次修复;若 CI 失败归属本提交继续修复。 ⟦AI:FKST⟧ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(studio): 修正 channel 写入与拒收 ACK 语义 触发来源: PR #1072 修复后复审指出 /model 生产 DI 误用全局 writer,以及 dispatch admission rejected 被异常化成 400 并丢失 receipt。 行为类型: 让 channel 写入显式使用 channel-scoped catalog writer,并为 rejected admission 返回诚实 receipt。 等价语义: /model 与 Console Settings 共享写入 core,但各自使用正确的 catalog adapter;dispatch admission 无论 accepted/rejected 都保留可追踪 receipt。 后续复用: 后续 channel LLM selection 可继续复用 UserLlmPreferenceWriteCore,不会依赖全局 HTTP catalog 或伪 token。 失败痕迹归属: Studio 与 ChannelRuntime 测试、architecture guards、test stability guards 覆盖本次修复;若 CI 失败归属本提交继续修复。 ⟦AI:FKST⟧ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(studio): 移除 channel LLM 写入占位 token 触发来源: PR #1072 最新复审指出 /model channel 写入仍把 channel-context 占位字符串传给 catalog lookup,生产 NyxID 会把它当 Bearer token 使用。 行为类型: 让 channel 写入使用已解析 typed option 或真实 proxy capability token,并补 production-equivalent DI 测试反断言占位 token。 等价语义: /model list 与 /model use 保存阶段使用同一 channel-scoped 能力语义,不再用字符串占位伪装授权上下文。 后续复用: 后续 channel LLM selection 可继续复用 UserLlmPreferenceWriter/UserLlmPreferenceWriteCore,同时避免二次 catalog lookup 需要伪 token。 失败痕迹归属: Studio 与 ChannelRuntime 测试、architecture guards、test stability guards 已在修复报告中通过;若 CI 失败归属本提交继续修复。 ⟦AI:FKST⟧ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Improve frontend team member flow * Refine teams homepage readiness * Guard teams homepage scope queries * Avoid cached scope roster loads * Polish teams homepage list layout --------- Co-authored-by: eanzhao <yiqi.zhao@aelf.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: eanzhao <141116261+eanzhao@users.noreply.github.com> Co-authored-by: AbigailDeng <Abigail.deng@aelf.io> Co-authored-by: AbigailDeng <108705114+AbigailDeng@users.noreply.github.com> Co-authored-by: potter <potter.sun@aelf.io> Co-authored-by: louis.li <louis.li@aelf.io> Co-authored-by: Abigail Deng <abigaildeng@AbigaildeMacBook-Pro.local> Co-authored-by: potter-sun <82434926+potter-sun@users.noreply.github.com>
per Auric 2026-05-27/28 '自动同步修好了么?':daemon 5+ 小时 14 个 codex SUCCESS marker 但 0 sync PR 实际 push。 根因:sync_dev_to_integration() 每 tick 调 reset_to_remote(cwd, INTEGRATION) 抛弃 codex 已 stage 的 resolve 工作 → 重新 try merge → conflict → dispatch new codex → 循环 14 次。 修法: 1. latest_sync_codex_marker(cwd) helper:parse 最近 sync codex DEV_SYNC_RESOLVED marker + age 2. try_finalize_codex_merge(cwd) helper:MERGE_HEAD exists + 0 unmerged + codex DONE → GIT_EDITOR=true git merge --continue + push_sync_pr,不抛弃 codex 工作 3. tick() 优先 push pending codex result(不 reset): - merge_in_progress + codex done → try_finalize_codex_merge → push_sync_pr - merge_in_progress + codex in flight → skip 等 - working_tree_dirty + 最近 codex done → push_sync_pr(防 codex amend miss commit) - behind == 0 → up-to-date,不 reset - 否则才 reset_to_remote + try merge 4. rollup_integration_to_dev() force-push:open rollup PR DIRTY OR head SHA stale > 1h → force-push update rollup branch to latest origin/INTEGRATION 5. push_sync_pr 加 "ahead origin/INTEGRATION ≥ 1" sanity check 防 push 空 branch ⟦AI:AUTO-LOOP⟧
… HEAD push) (#1124) * docs(adr): add ADR-0026 tool-first chat ingress Records the architectural decision to collapse ChatRouteAction to Reject + ForwardToModel, exposing GAgent/Team/Workflow invocation as IAgentToolSource tools through the existing ToolCallLoop. Supersedes ADR-0024 §D5 (v1 action set) and ADR-0025 (voice v1 ForwardToGAgent); ADR-0024 D1/D2/D3/D4/D6 stand. Tracked end-to-end in epic #808; voice GA prerequisite in #809. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ai-tools): add 5 invocation tool sources + shared dispatcher Implements ADR-0026 Stage 1 unit-1 (epic #808). New project src/Aevatar.AI.ToolProviders.AevatarInvocation/ exposes aevatar_invoke_gagent / _invoke_team / _start_workflow / _observe_run / _query_readmodel as IAgentToolSource, so the LLM can drive orchestration through the existing ToolCallLoop instead of parallel router branches. Design: - Tool payloads are proto-derived strict JSON-Schema (no map<string,string> bags) - wait=ack|stream|complete supported; stream is default for long-running tools; GAgent/workflow wait=complete returns wait_complete_unavailable until Stage 2 session actor lands - Caller scope flows through AgentToolRequestContext only; protected caller-scope keys (LLMRequestMetadataKeys.*) are stripped from LLM-supplied payload.headers before server values are stamped, so the LLM cannot inject overrides for nyxid.access_token / scope_id / owner_subject etc. - query_readmodel is bounded to a closed registered set - Dispatch reuses existing surfaces (IActorDispatchPort, ITeamEntryMemberResolver + IStaticGAgentStreamInvocationPort<AGUIEvent>, ICommandDispatchService<WorkflowChatRunRequest,...>); no new dispatch chain 21 tests pass (4 credential-injection regression + 1 ObserveRun fast-fail added in post-review hardening); arch_guards + test_stability + docs lint all PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(lark): cover caller-scope propagation in LarkMessagesSendTool Implements ADR-0026 Stage 1 unit-2 (D7 prerequisite) for the Lark outbound caller-scope guarantee. After auditing the existing path (LarkMessagesSendTool → LarkNyxClient → NyxIdApiClient) no production refactor was required: the tool already reads AgentToolRequestContext.NyxIdAccessToken (no credential parameters) and forwards the caller bearer through NyxID's api-lark-bot proxy, which exchanges to a Lark tenant_access_token without seeing the caller's authorization header. The metadata-bag credential-injection surface that unit-1 had to harden is structurally absent here (no headers/metadata bag at the dispatch boundary). Added 2 regression tests: - Asserts the dispatched NyxID call carries AgentToolRequestContext's trusted typed NyxIdAccessToken - Asserts a malicious LLM payload (smuggled nyx_id_access_token, fake headers, ExternalMetadata overriding LLMRequestMetadataKeys.NyxIdAccessToken) cannot override the trusted caller token at dispatch NyxID investigation summary (verified via gh against ChronoAIProject/NyxID backend source): /api/v1/proxy/s/api-lark-bot/open-apis/im/v1/messages accepts only the caller's NyxID bearer; NyxID resolves caller's api-lark-bot binding, exchanges {app_id, app_secret} → tenant_access_token per channel_adapters/lark.rs::lark_family_token_exchange_config, strips the inbound authorization, and injects bearer for outbound to Lark. Semantic: messages post as the caller's bound Lark bot (NyxID-mediated), not as the human user's OAuth identity and not as Aevatar's service-level identity. This satisfies ADR-0026 §D7's "lands in the caller's Lark account" use case. 61/61 tests pass; arch_guards + test_stability + docs lint all PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(responses): add /v1/responses → aevatar_invoke_gagent E2E test Closes ADR-0026 Stage 1 (epic #808). Integration test demonstrates the new tool-first ingress path works end-to-end after units 1+2 landed, without touching any production code. Test: MainnetResponsesEndpointsTests.PostResponses_StreamWithAevatarInvokeGAgentAdditiveTool_ShouldDispatchActorEnvelope Scenario: - /v1/responses streamed request with real DI registration of unit-1's AddAevatarInvocationTools (5 production IAgentToolSource instances) - Stubbed LLM emits aevatar_invoke_gagent tool call with a malicious payload that smuggles nyxid.access_token + aevatar.scope_id overrides - ResponsesCompletionApplicationService executes the local tool call inline (not as function_call SSE output — verified against production StreamAsync behavior) - AevatarInvocationDispatcher dispatches through IActorDispatchPort (captured by RecordingActorDispatchPort) - LLM round 2 continues after tool result, SSE lifecycle completes Assertions: - Dispatched envelope's Route.PublisherActorId == DirectGAgentPublisherId - Dispatched ChatRequestEvent.Headers carry the trusted bearer/scope (caller-scope protection from unit-1 verified end-to-end) - ThrowingStaticGAgentStreamInvocationPort.InvocationCount == 0 (the legacy ForwardToGAgent/ForwardToTeam path in ResponsesEndpoints.cs:779-927 is NOT entered) 202/202 tests pass in Aevatar.Hosting.Tests; arch_guards + test_stability_guards + docs lint all PASS. No production code changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix responses forward team status probes * feat(routing): extend ForwardToModel with tool_set_ref + tool_choice_hint and add ToolSetRegistry Stage 2 Unit 1 of ADR-0026 (epic #808). Lays the proto + composition foundation that Stage 2 Units 2 (resolver translation) and 3 (ChatRunActor) consume. Proto changes (src/Aevatar.ChatRouting.Abstractions/chat_route_policy.proto): - ForwardToModel.tool_set_ref (field 2) — ChatRouteToolSetRef typed message - ForwardToModel.tool_choice_hint (field 3) — ChatRouteToolChoiceHint typed message with google.protobuf.Struct prefilled_arguments (NOT map<string,string> per CLAUDE.md typed-proto rule) - No oneof reshape; legacy ForwardToGAgent/Team/Workflow variants untouched New project src/Aevatar.AI.ToolProviders.ToolSetRegistry/: - IToolSetRegistry: resolves ChatRouteToolSetRef.name to IAgentToolSource list at boundary time via DI factories (NOT cached at registration — preserves per-request scope for tool sources carrying caller context) - ToolSetResolveResult / ToolSetResolveError: structured errors (tool_set_name_required / unknown_tool_set), no exceptions for normal failure - ResponsesAevatarToolProvider now also implements IAgentToolSource so existing Responses substitute tools participate in named composition without changing the IResponsesToolProvider path Three default named sets (registered in MainnetHostBuilderExtensions): - workspace.default: comprehensive (Stage 1 invocation + substitute/additive tools + NyxID/Lark/Telegram/ChronoStorage/Web) - lark.self_notify: minimal (lark_messages_send + aevatar_query_readmodel) — for ADR-0026 §D7 "push to my Lark" use case - voice.realtime: placeholder for Stage 5 (currently same shape as workspace.default; tightens when voice convergence lands) Argument-merge policy documented for Unit 2 boundary code: server-set prefilled_arguments are trusted route policy; LLM-supplied arguments that conflict on the same key MUST be rejected, not silently overwritten. 10 tests added across 3 test projects (registry resolve / proto round-trip / Mainnet DI composition incl. lark.self_notify minimal-set assertion). Build + arch_guards + test_stability + docs lint all PASS. No ChatRouteResolver changes (Unit 2). No ChatRunActor (Unit 3). No legacy deletion (Stage 4). No external repo changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(gitignore): ignore .implement-loop/ codex-implement-loop artifacts The loop maintains state, prompts, logs, reviews, run summaries, and worktrees under .implement-loop/ at the repo root. They are session-local artifacts, not source of truth; ignoring them prevents accidental staging during merges back into the working branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(routing): translate legacy forward actions to ForwardToModel + tool_choice_hint Stage 2 Unit 2 of ADR-0026. ChatRouteResolver now performs runtime translation so existing policy rules emitting legacy ForwardToGAgent / ForwardToTeam / ForwardToWorkflow continue to work while the consumer side (Unit 3's ChatRunActor and the existing /v1/responses path) only needs to understand ForwardToModel + tool_choice_hint. Persisted policy proto is unchanged; Stage 4 will delete the legacy oneof variants. Translations (in-memory only, ChatRouteDecision stays transient per ADR-0024 D2): ForwardToGAgent(actor_id) → ForwardToModel { tool_set_ref = workspace.default tool_choice_hint = { tool_name = aevatar_invoke_gagent prefilled = { actor_id } } } ForwardToTeam(team_id, endpoint) → ForwardToModel { tool_set_ref = workspace.default tool_choice_hint = { tool_name = aevatar_invoke_team prefilled = { team_id, endpoint_id, [scope_id if non-empty] } } } ForwardToWorkflow(workflow_id) → ForwardToModel { tool_set_ref = workspace.default tool_choice_hint = { tool_name = aevatar_start_workflow prefilled = { workflow_id } } } ForwardToModel → pass-through (preserve new fields) Reject → pass-through ChatRoutePolicyMigrator.MigrateLegacyActions(snapshot): pure in-memory transform that rewrites legacy actions to the canonical shape; idempotent on already-new-shape snapshots. Persistence flow is Stage 3 work. Tool-set name sourced from ToolSetNames.WorkspaceDefault (no magic strings). prefilled_arguments built as google.protobuf.Struct (NOT map<string,string>) via Value.ForString. voice_module_name from legacy ForwardToGAgent is NOT emitted because aevatar_invoke_gagent proto has no such field. Known asymmetry to address in Unit 3: scope_id is emitted for aevatar_invoke_team but InvokeTeamToolRequest proto has no scope_id field; the boundary consumer must strip unknown args before JsonParser.Default.Parse or surface scope_id through a different channel. 36/36 ChatRouting.Core.Tests pass (16 new); build + arch_guards + test_stability all PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chatrun): introduce ChatRunActor session-scoped actor + boundary integration Stage 2 Unit 3 of ADR-0026 (epic #808). Closes Stage 2. ChatRunActor (src/platform/Aevatar.GAgentService.Core/GAgents/ChatRunActor.cs): - Business-named, session-scoped (keyed chat-run:{response_id}) - Typed proto State (ChatRunState in llm_sessions.proto): LLM context + tool_call_history + active_sub_run_subscriptions as repeated typed ChatRunSubRunSubscription (NOT Dictionary — CLAUDE.md 中间层状态约束) - Event-sourced via PersistDomainEventAsync + reducer - Single-threaded event loop (no lock / ConcurrentDictionary / GetAwaiter) - Self-continuation via PublishAsync(..., TopologyAudience.Self) Cross-actor sub-run observation (the architectural pattern this unit settles): - ChatRunActor itself owns the observation via IStreamProvider.GetStream(targetActorId).UpsertRelayAsync( StreamForwardingMode.HandleThenForward, EventTypeFilter:CommittedStateEventPublished) — mirrors src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeGAgentBase precedent - Forwarded envelopes consumed via [AllEventHandler], correlated by run_id + publisher_actor_id, fold terminal result on actor event loop, publish typed ChatRunToolResultReady on self-stream - ChatRunToolCompletionCoordinator (Application layer) only awaits typed ChatRunToolResultReady through IChatRunActorPort — NO raw SubscribeAsync<EventEnvelope> from Application (round-1 review caught the original violation; round-2 verified the fix) Boundary integration in ResponsesEndpoints + ResponsesCompletionApplicationService: - Resolves ForwardToModel.tool_set_ref via IToolSetRegistry → turn's additive tool set - tool_choice_hint pinned to tool_name; prefilled_arguments stamped on; LLM arguments conflicting on prefilled keys → structured tool_choice_prefill_conflict error so LLM self-corrects (Stage 1 unit-1 caller-scope hardening pattern extended to this layer) - Only aevatar_invoke_gagent/_invoke_team/_start_workflow with wait=complete route through ChatRunActor; everything else (wait=ack/stream, other tools) keeps current inline behavior — minimal viable integration scope_id asymmetry (unit-2 review concern) resolved via ProtoToolArguments.Parse(WithIgnoreUnknownFields(true)) — scoped to invocation- tool-request parsing only, NOT global. caller-scope channel via dispatcher metadata is authoritative; inline scope_id from legacy ForwardToTeam is silently dropped from InvokeTeamToolRequest parsing. Regression test asserts the dispatched envelope still has correct caller scope. Stage 1 wait_complete_unavailable migrated: AevatarInvocationDispatcher no longer rejects wait=complete; dispatches and returns receipt; completion is folded by ChatRunActor when the path goes through coordinator. Regression test updated accordingly. Test for user-owned ResponsesForwardTeamInternalProbeExecutorTests.cs was renamed + reasserted to reflect Stage 2 unit-2's cascade: resolver now translates legacy ForwardToTeam → ForwardToModel + tool_choice_hint, so the probe correctly reports Down for that route shape until the user migrates the probe's expectation. Production probe code untouched. Explanatory comment added linking to ChatRoutePolicyMigrator + ADR-0026 D2. 814/814 tests pass (36+23+207+548 across 4 affected projects). SubscribeAsync<EventEnvelope> ban in dispatch_projection_boundary_guard passes when arch_guards run from worktree (canonical CI invocation). Non-blocking follow-up: RemoveRelayAsync not called on ChatRunActor HandleTerminateAsync — bounded leak per session, see review round-2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(routing): deprecation signaling for legacy actions + migration endpoint Stage 3 Unit 1 of ADR-0026 (epic #808). Makes Stage 4's upcoming deletion of ForwardToGAgent/Team/Workflow safe by giving operators visible signal and a runtime-accessible migration tool. Resolver deprecation signaling: - ChatRouteDecision.deprecations: repeated ChatRouteDeprecation typed proto sub-messages (NOT map<string,string>); transient per ADR-0024 D2 - ChatRouteDeprecation carries: code, message, action_kind, matched_rule_id, translated_target - ChatRouteResolver emits LogWarning chat_route_legacy_action_used with structured fields when a rule resolves to a legacy action (one log per matched legacy rule per resolve call, not per request) - Covers both rule-match and default_target paths Consumer header propagation (RFC 9745 Deprecation + RFC 9110 Warning): - ApplyChatRouteDeprecationHeaders helper called from ResponsesEndpoints and MessagesEndpoints after route resolution - Sets `Deprecation: true` + `Warning: 299 - "chat_route_legacy_action_used: <details>"` - Voice boundary log-only (WebSocket can't carry response headers after upgrade) Migration helper exposure: - New admin endpoint POST /api/scopes/{scopeId}/chat-route-policy/migrate - Dry-run default: returns the migrated UpsertChatRoutePolicyRequested payload - ?apply=true: dispatches as single atomic UpsertChatRoutePolicyRequested command to ChatRoutePolicyGAgent (ADR-0024 D5: no temporary invalid state) - Reuses existing chat-route-policy admin auth (scope ownership / admin role) Tests cover: resolver deprecation per legacy action variant + empty for ForwardToModel + structured warning fields; admin endpoint dry-run + apply; SSE response Deprecation+Warning headers for legacy ForwardToGAgent. No deletion of legacy proto variants (Stage 4). No actor changes. No external repo changes. Build + ChatRouting.Core.Tests + Hosting.Tests + test_stability_guards + architecture_guards (through workflow_binding boundary) all PASS canonically from worktree; playground_asset_drift_guard env-blocked in worktree per known infrastructure gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(chatrun): clean up sub-run forwarding relays on terminate Stage 3 Unit 2 of ADR-0026 (follow-up from Stage 2 unit-3 review). Closes the bounded forwarding-registry-binding leak per chat run session. Mirrors src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeGAgentBase RemoveObservationRelayAsync precedent: ChatRunActor.HandleTerminateAsync snapshots active_sub_run_subscriptions before persisting ChatRunTerminatedEvent (which clears them via the existing reducer), then iterates the snapshot and calls IStreamProvider.GetStream(targetActorId).RemoveRelayAsync(Id, ct) for each target. Cleanup runs sequentially on the actor event loop (no Task.Run / Timer); failures are best-effort with Logger.LogWarning; iteration continues; state remains cleared regardless. OperationCanceledException not swallowed. Regression tests (2 new in ChatRunActorTests): - Terminate_ShouldRemoveRelayAsync_ForEachActiveSubscription: N=2 active subscriptions, observable IStreamProvider, asserts RemoveRelayAsync called per target_actor_id + state cleared - Terminate_WhenRemoveRelayThrows_ShouldStillClearState: simulated throw, asserts cleanup attempted for all targets, warnings logged, no exception propagated, state cleared 550/550 GAgentService.Tests pass; arch_guards + test_stability PASS canonically from worktree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add chat completions facade * Add chat completions status probe * fix: address PR review feedback * Fix tool-driven chat route completion handling * Retire responses forward team status probes * Add core loop status probe * Cover chat completions auth fallback * Share NyxID direct tool sources * Fix status dashboard startup tests * Fix user LLM route model handling * Retire auth gate status probes * Add default workspace tool set routing * Strip consumed keys from submission.Arguments after typed payloads After populating WorkflowResume / LlmSelection typed sub-messages, remove the matching keys from the open-ended Arguments map so each piece of data has a single semantic location (typed field), not two. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Adopt typed DispatchAdmission stub across ChannelRuntime tests IActorDispatchPort.DispatchAsync returns Task<DispatchAdmission>; the prior Task.CompletedTask stubs no longer satisfy that signature. Add ActorDispatchPortTestSupport.AcceptAsync that builds an admission via DispatchAdmissionFactory from the captured actorId + envelope, and switch every Substitute setup to it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Align ScopeServiceEndpoints stream test with shipped event flow Production now emits only TextMessageEnd + RunFinished for the pong path (no Start/Content frames), and surfaces the upstream LLM message verbatim instead of wrapping it with "LLM request failed [tools=none]:". The assertions were stale; this updates them to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Wait for committed binding and runtime state in script integration tests Reading state immediately after EnsureRuntime/Run was racing with the async binding event and read-model materialization. Add two helpers on ScriptEvolutionIntegrationTestKit -- WaitForScriptBindingAsync (polls the event store for the matching ScriptBehaviorBoundEvent) and WaitForStateAsync<TState> (polls the actor's StateRoot until a predicate matches) -- and adopt them across HybridServiceUpgrade, ScriptDefinitionRuntimeContract, and ScriptGAgentFactoryLifecycle tests so assertions observe the committed state instead of a snapshot in flight. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Register YieldApprovalHandler so mainnet ssh_exec opt-in is satisfied Mainnet sets EnableSshExecTool=true but never registered an IToolApprovalHandler, so NyxIdAgentToolSource.DiscoverToolsAsync threw on every Lark conversation turn and the bot stopped replying. The canonical yielding handler keeps tool exposure gated while approval flows yield into RoleGAgent's actor-owned remote continuation via NyxIdRemoteToolApprovalPort. iter23/cluster-001-nyxid-tool-approval-polling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix Lark CardKit self continuations * Fix coverage-quality integration tests * Fix relay skill continuation timeout * Fix relay skill continuation flow * Fix Studio bind endpoint UX * Fix Studio observe page focus * Fix Studio observe run lookup * Refactor settings LLM contract * Fix LLM settings save response semantics * Fix Script member creation flow * Update Studio execution page test * Remove backend changes from observe PR * Fix user config save ack semantics * fix(studio): 收敛 LLM 设置契约剩余架构问题 触发来源: PR #1072 三路复审指出 settings 与 slash 写入双轨、Application contract 泄露 JSON wire 细节、canonical settings 值域开放、ACK receipt 映射测试不足。 行为类型: 收敛 LLM preference 写入主干,将 JSON DTO 移到 Hosting 边界,封闭 route source/status/fallbackReason 值域,补 dispatch admission 和 settings 边界测试。 等价语义: Console Settings 与 /model slash 共享同一写入/normalization 语义;Application contract 表达业务语义而非 HTTP JSON wire contract。 后续复用: 后续 settings/channel LLM 入口可复用统一 writer/core、catalog normalizer 与 settings view builder。 失败痕迹归属: Studio 与 ChannelRuntime 测试、architecture guards、test stability guards 覆盖本次修复;若 CI 失败归属本提交继续修复。 ⟦AI:FKST⟧ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(studio): 修正 channel 写入与拒收 ACK 语义 触发来源: PR #1072 修复后复审指出 /model 生产 DI 误用全局 writer,以及 dispatch admission rejected 被异常化成 400 并丢失 receipt。 行为类型: 让 channel 写入显式使用 channel-scoped catalog writer,并为 rejected admission 返回诚实 receipt。 等价语义: /model 与 Console Settings 共享写入 core,但各自使用正确的 catalog adapter;dispatch admission 无论 accepted/rejected 都保留可追踪 receipt。 后续复用: 后续 channel LLM selection 可继续复用 UserLlmPreferenceWriteCore,不会依赖全局 HTTP catalog 或伪 token。 失败痕迹归属: Studio 与 ChannelRuntime 测试、architecture guards、test stability guards 覆盖本次修复;若 CI 失败归属本提交继续修复。 ⟦AI:FKST⟧ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(studio): 移除 channel LLM 写入占位 token 触发来源: PR #1072 最新复审指出 /model channel 写入仍把 channel-context 占位字符串传给 catalog lookup,生产 NyxID 会把它当 Bearer token 使用。 行为类型: 让 channel 写入使用已解析 typed option 或真实 proxy capability token,并补 production-equivalent DI 测试反断言占位 token。 等价语义: /model list 与 /model use 保存阶段使用同一 channel-scoped 能力语义,不再用字符串占位伪装授权上下文。 后续复用: 后续 channel LLM selection 可继续复用 UserLlmPreferenceWriter/UserLlmPreferenceWriteCore,同时避免二次 catalog lookup 需要伪 token。 失败痕迹归属: Studio 与 ChannelRuntime 测试、architecture guards、test stability guards 已在修复报告中通过;若 CI 失败归属本提交继续修复。 ⟦AI:FKST⟧ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Improve frontend team member flow * Refine teams homepage readiness * Guard teams homepage scope queries * Avoid cached scope roster loads * Polish teams homepage list layout * feat: add frontend-refactor-team automated refactoring skill Multi-agent workflow for auditing, fixing, reviewing, and PR-ing frontend architecture and design issues against CLAUDE.md and docs/canon/frontend-design.md rules. Agents: auditor, backend-impact-scanner, implementer, arch-reviewer, design-reviewer, browser-qa-reviewer, ci-runner. Each agent returns FRONTEND_REFACTOR_JSON for machine-readable Team Lead decisions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: eanzhao <yiqi.zhao@aelf.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: eanzhao <141116261+eanzhao@users.noreply.github.com> Co-authored-by: AbigailDeng <Abigail.deng@aelf.io> Co-authored-by: AbigailDeng <108705114+AbigailDeng@users.noreply.github.com> Co-authored-by: potter <potter.sun@aelf.io> Co-authored-by: louis.li <louis.li@aelf.io> Co-authored-by: Abigail Deng <abigaildeng@AbigaildeMacBook-Pro.local> Co-authored-by: potter-sun <82434926+potter-sun@users.noreply.github.com>
|
Superseded by daemon-managed rollup(等 daemon next tick 创新 PR)。⟦AI:AUTO-LOOP⟧ |
iter15 rollup: 6 architecture-cleanup clusters | iter15 rollup:6 个架构清理 cluster
This is the iteration-level rollup of all iter15 auto-refactor clusters landed on
auto-refact-dev. Reviewing this PR is the human-gate point for the entire iteration.本 PR 是 iter15 自动重构所有 cluster 在
auto-refact-dev上的迭代级 rollup。审本 PR 即审整个 iteration 的人工把关点。Cluster ledger | 集群清单
ToolCallLoopmiddleware 持IsStreaming=true;新ChatStreamContentAggregatorhelper 用于 5 处非流式聚合场景_gate/_state/AttachmentState删除;remote audio chunk envelope 路径禁用直到 raw transport;setup/control envelopes 保留_responseEpochs字典从 provider 后台线程移走;actor turn 持 provider-response-id 映射 + cancel 抑制;新 typedprovider_response_idproto 字段TurnStreamingReplySink/SkillRunnerStreamingReplySink被动化;throttle/dedupe/final-flush 移入AgentRunGAgent+SkillRunnerGAgentrun-owned 状态WorkflowArtifactFactBuilder用 typedRoleId字段,不再用 actor id 前缀cluster-028/029 历史 controller 错误直接 PR 到 dev(已在 dev 中);#706/707/708/687 都正确 PR 到
auto-refact-dev(本 PR 涵盖)。Phase 9 meta-design (#701) | Phase 9 元设计 (#701)
cluster-025/026/027 触发 Phase 9 multi-solver design consensus,经 5 轮 solver + meta-judge 收敛,最终采纳 Auric 的架构愿景:
硬约束:一 actor 持一 stream、chunks 永不走 EventEnvelope、function calls 走 EventEnvelope、voice 和 text 用同一 pipeline、无新核心抽象。
cluster-025 boundary 决定 (Option A):remote setup/control envelopes 保留;remote audio chunk envelope relay 禁用直到 raw media transport 出现。
详见已关闭 design issues:#681 #682 #684 #701。
Skill-level improvements landed alongside | 同期落地的 skill 改进
spawn-codex.shprompt+log 双 file +--prompt-text自动 mktemp + SPAWN/DONE bannerauto-refact-dev(只有 rollup PR target dev)Verification per cluster | 各 cluster 验证
每个 cluster PR 都通过 Phase 8 multi-codex review (3 角色: architect / tests / quality),fix-retry 直到 3/3 或 2 approve + 1 non-blocking comment。各 PR 附本地+远程 CI 通过、test_stability_guards.sh 通过、architecture_guards.sh 通过的证据。
详见各 PR 的 round-by-round 评论。
Diff summary | diff 汇总
51 files changed, +3570 / -1803 (net +1767 productive code, mostly deletion-positive at production code level — large pluses are bilingual PR/issue artifacts + new comprehensive behavior tests + Phase 9 prompt files).
Audit trail | 审计追踪
.refactor-loop/runs/audit-iter-15.md.refactor-loop/runs/implement-cluster-*.md.refactor-loop/runs/v{1..5}-review/pr{687,706,707,708}/.refactor-loop/runs/phase9-issue701-r{1..5}-*.md🤖 Auto-loop / codex-refactor-loop iter15 (Phase 1 audit → Phase 9 design → Phase 2 implement → Phase 8 review → Phase 4 rollup)