Skip to content

持续重构集成#690

Closed
loning wants to merge 206 commits into
devfrom
auto-refact-dev
Closed

持续重构集成#690
loning wants to merge 206 commits into
devfrom
auto-refact-dev

Conversation

@loning
Copy link
Copy Markdown
Contributor

@loning loning commented May 19, 2026

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 | 集群清单

Cluster Title PR Net LOC Key result
cluster-024 ChatStreamAsync as the only AI executor #687 (a461989) various LLM 主链路全流式;ToolCallLoop middleware 持 IsStreaming=true;新 ChatStreamContentAggregator helper 用于 5 处非流式聚合场景
cluster-025 Voice host session state moves to actor #706 (b862578) -268 host-side _gate/_state/AttachmentState 删除;remote audio chunk envelope 路径禁用直到 raw transport;setup/control envelopes 保留
cluster-026 Voice provider response epoch moves into actor turn #707 (3a8a323) mixed OpenAI/MiniCPM _responseEpochs 字典从 provider 后台线程移走;actor turn 持 provider-response-id 映射 + cancel 抑制;新 typed provider_response_id proto 字段
cluster-027 Streaming reply moves from sink-owned timer to actor turn #708 (b012a07) -840 TurnStreamingReplySink/SkillRunnerStreamingReplySink 被动化;throttle/dedupe/final-flush 移入 AgentRunGAgent + SkillRunnerGAgent run-owned 状态
cluster-028 Workflow artifact uses typed RoleId #688 (d099c55) additive WorkflowArtifactFactBuilder 用 typed RoleId 字段,不再用 actor id 前缀
cluster-029 Workflow scope/channel use typed proto fields #689 (16bcf04) -190 workflow scope/channel 用 typed proto field,删除 metadata bag fallback

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 的架构愿景:

External I/O → Stream → Actor 持 stream → remote LLM → LLM 决定 function call → 完全异步生成 EventEnvelope 调用其他 actor → reply EventEnvelope 塞回 Stream → 告诉 LLM

硬约束:一 actor 持一 streamchunks 永不走 EventEnvelopefunction calls 走 EventEnvelopevoice 和 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 改进

  • Phase 8 fix-retry loop(any reject → fix codex,不再直接 escalate)
  • Phase 8 GitHub traceability(每个 review/fix/consensus 动作 PR comment 可溯)
  • Phase 9 multi-solver design consensus(3 固定角色 + meta-judge + 3/3 unanimous)
  • Phase 9 maintainer-reply-resets-the-round(无 cap,每次回评触发 fresh round)
  • Phase 7 Monitor 自动发现(label-based,不 hardcode issue 号)
  • spawn-codex.sh prompt+log 双 file + --prompt-text 自动 mktemp + SPAWN/DONE banner
  • Cluster PR 永远 target auto-refact-dev(只有 rollup PR target dev)
  • Bilingual EN+ZH 完整对照(每个 user-facing artifact)
  • mermaid 在 GitHub issue/PR 评论中禁用 bilingual quoted labels(ASCII 优先)
  • Team-member 安全门(只回复 collaborator/org-member/whitelist)

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 | 审计追踪

  • Audit: .refactor-loop/runs/audit-iter-15.md
  • Per-cluster implement summaries: .refactor-loop/runs/implement-cluster-*.md
  • Phase 8 reviewer outputs: .refactor-loop/runs/v{1..5}-review/pr{687,706,707,708}/
  • Phase 9 solver+judge outputs: .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)

@codecov
Copy link
Copy Markdown

codecov Bot commented May 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.11%. Comparing base (9ad361d) to head (822791e).

@@            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     
Flag Coverage Δ
ci 83.11% <ø> (+0.59%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...Abstractions/Agents/RoleConfigurationNormalizer.cs 96.07% <ø> (-0.22%) ⬇️
....AI.Abstractions/LLMProviders/LLMControlContext.cs 97.43% <ø> (ø)
...stractions/LLMProviders/LLMControlContextMapper.cs 100.00% <ø> (ø)
...Aevatar.AI.Abstractions/LLMProviders/LLMRequest.cs 77.61% <ø> (+0.33%) ⬆️
...s/ToolProviders/AgentToolExecutionContextMapper.cs 98.40% <ø> (-0.67%) ⬇️
src/Aevatar.AI.Core/AIGAgentBase.cs 80.75% <ø> (-3.22%) ⬇️
src/Aevatar.AI.Core/Chat/ChatRuntime.cs 90.03% <ø> (-0.43%) ⬇️
...rc/Aevatar.AI.Core/Chat/ChatRuntimeStepExecutor.cs 92.74% <ø> (ø)
...atar.AI.Core/LLMProviders/OwnerLlmConfigApplier.cs 75.75% <ø> (-6.39%) ⬇️
src/Aevatar.AI.Core/RoleGAgent.cs 82.95% <ø> (+1.14%) ⬆️
... and 11 more

... and 371 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@loning loning changed the title Auto refact dev iter15 rollup: 6 architecture-cleanup clusters (024/025/026/027 + 028/029 from dev) May 19, 2026
@loning loning force-pushed the auto-refact-dev branch from 328114e to 8b1754e Compare May 19, 2026 18:22
@loning loning changed the title iter15 rollup: 6 architecture-cleanup clusters (024/025/026/027 + 028/029 from dev) 持续重构集成 May 19, 2026
@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 19, 2026

📊 iter17 进展 — 4/4 cluster merged 🎉

Cluster PR Phase 9 共识 Round 数
cluster-034(projection watermark readmodel) #726 r3 structural r1→r2 fix
cluster-036(detached command background completion) #724 (audit cluster) r1→r2 fix + test-add
cluster-038(nyx-relay callback_jti admission) #727 r4 structural r1→r2→r3→r4 fix(全 approve unanimous!)
cluster-035(projection live sink process registry) #725 (audit cluster) r1→r2→r3→r4 fix + 2× test-add + rebase resolve + admin merge codecov override

iter17 净改动:多 actor / 多 projection / proto schema 演化 + 删 process-local 权威源。

🤖 controller status banner

@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 19, 2026

📊 iter18 audit 派出

iter17 4/4 cluster merged → 自动派 iter18 audit codex(per SKILL '## INFINITE refactor loop')。

维度
阶段 🔍 audit-iter-18 in-flight
Audit codex timeout 60 min
预期 产出 audit-iter-18.md + 候选 NDJSON(≥25 candidates,6 analyzer pack 命令)

剩 open design issue:#720 #722(escalated 等 maintainer)— 不阻 iter18。

🤖 controller status banner

@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 19, 2026

📊 iter18 bootstrap — 7 cluster 进入工作链

Cluster Severity requires_design 入口
cluster-001-ai-nonstreaming-provider-contract high design issue #728
cluster-002-projection-session-lifecycle-in-process high design issue #729
cluster-003-event-store-watermark-query-replay high 直接 implement(worktree ready)
cluster-004-process-local-idempotency-guards medium design issue #730
cluster-005-detached-taskrun-completion-monitor high design issue #731
cluster-006-command-path-projection-lifecycle medium design issue #732
cluster-007-telegram-bridge-polling-in-actor-turn medium design issue #733

Phase 9 multi-solver 已为 6 design issue 排队,controller 下次 wakeup 触发派出 r1 三 solver。

🤖 controller status banner

@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 19, 2026

📊 iter18 Phase 9 r1 派出(6 design issue × 3 solver = 18 child codex)

Issue Cluster Solvers
#728 cluster-001 ai-nonstreaming-provider-contract minimal+structural+delete
#729 cluster-002 projection-session-lifecycle-in-process minimal+structural+delete
#730 cluster-004 process-local-idempotency-guards minimal+structural+delete
#731 cluster-005 detached-taskrun-completion-monitor minimal+structural+delete
#732 cluster-006 command-path-projection-lifecycle minimal+structural+delete
#733 cluster-007 telegram-bridge-polling-in-actor-turn minimal+structural+delete

每 solver 3600s 上限。等齐 3 solver per issue → controller 派 meta-judge。

🤖 controller status banner

@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 19, 2026

📊 iter18 Phase 9 r1 全 18 codex 派出 ✅

Issue Cluster Solvers spawned
#728 cluster-001 ai-nonstreaming-provider-contract 3/3 ✅
#729 cluster-002 projection-session-lifecycle-in-process 3/3 ✅
#730 cluster-004 process-local-idempotency-guards 3/3 ✅
#731 cluster-005 detached-taskrun-completion-monitor 3/3 ✅
#732 cluster-006 command-path-projection-lifecycle 3/3 ✅
#733 cluster-007 telegram-bridge-polling-in-actor-turn 3/3 ✅
  • cluster-003 implement(audit-only,无 design phase)在 /Users/auric/aevatar-wt-iter18-cluster-003 跑。

后台 in-flight = 19 codex(18 Phase 9 r1 solver + 1 cluster-003 implement)。

🤖 controller status banner

@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 19, 2026

implement: cluster-003

已完成 cluster-003-event-store-watermark-query-replay

  • 替换 query-time event-store replay:水位查询现在读取 ProjectionScopeWatermarkReadModel
  • scope actor 在状态变化后物化 projection-owned watermark readmodel;event replay 保留为 actor recovery/repair 语义。
  • 注册各 host/provider 的 watermark readmodel document store。
  • 验证:dotnet build aevatar.slnx --nologo、focused tests、test_stability_guards.shquery_projection_priming_guard.sharchitecture_guards.sh 均通过。

摘要:.refactor-loop/runs/implement-cluster-003.md

@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 19, 2026

📊 iter18 Phase 9 r1 结果

Issue Cluster Meta-judge 下一步
#728 cluster-001 ai-nonstreaming-provider-contract converge r2 r2 三 solver 派出
#729 cluster-002 projection-session-lifecycle-in-process 🆘 escalate philosophy 等 maintainer 决策
#730 cluster-004 process-local-idempotency-guards converge r2 r2 三 solver 派出
#731 cluster-005 detached-taskrun-completion-monitor converge r2 r2 三 solver 派出
#732 cluster-006 command-path-projection-lifecycle 🆘 escalate philosophy 等 maintainer 决策
#733 cluster-007 telegram-bridge-polling-in-actor-turn converge r2 r2 三 solver 派出
(audit) cluster-003 eventstore-watermark-replay (no design) PR #734 rebase resolve 中(behind 19 commits)

后台 in-flight = 13 codex(12 r2 solver + 1 rebase resolve)。

🤖 controller status banner

@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 20, 2026

📊 iter18 进展更新

Cluster Status PR
cluster-001 ai-nonstreaming-provider-contract ✅ merged(4 fix round → r4 共识) #735
cluster-002 projection-session-lifecycle-in-process 🆘 escalate(philosophy) issue #729
cluster-003 eventstore-watermark-replay ✅ merged(1 fix round → r2 共识) #734
cluster-004 process-local-idempotency-guards 🆘 escalate(stalled 3 round) issue #730
cluster-005 detached-taskrun-completion-monitor 🆘 escalate(stalled 5 round) issue #731
cluster-006 command-path-projection-lifecycle 🆘 escalate(philosophy) issue #732
cluster-007 telegram-bridge-polling-in-actor-turn 🆘 escalate(stalled 4 round) issue #733

iter18 净:2/7 cluster merged;5 escalated 等 maintainer 决策。

Auto-loop 推进无可派任务(全 escalation 路径 + 0 in-flight codex)。下次 wakeup 进入 idle heartbeat 模式。

🤖 controller status banner

@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 20, 2026

📊 iter19 audit 派出

iter18 已 merge 2/7(余 5 escalated 等 maintainer);loop 在 idle 后按 INFINITE 规则派 iter19 audit 拉新工作(独立于 escalation 路径)。

🤖 controller status banner

@loning loning added auto-loop Created by codex-refactor-loop skill 🚀 phase:pr-open 🤖 human:auto-推进 labels May 20, 2026
@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 20, 2026

📊 状态 — 已收到 maintainer 评论(daemon 识别)

维度
触发评论 id=4493040980 author=loning
评论摘要 ## implement: cluster-003
daemon 反应 👀 eyes react 已加
下一步 controller 下次 wakeup(≤25 min)读 daemon log → 派 fresh codex round(maintainer-reply-resets-the-round)→ 更新本卡片
是否需要人介入 ❌ 否(自动响应中)

🤖 comment-monitor.sh daemon

⟦AI:AUTO-LOOP⟧

@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 20, 2026

📊 状态卡片 — solver 派出

维度
阶段 派出 codex(role=solver)
codex log audit-iter-20.log
工作目录 /Users/auric/aevatar
timeout 3600s(~60 min 上限)
上下文 iter20 audit — iter18 完成后启动新 iter
下一步自动会做 1. 三 solver SOLVER_DONE:... 2. controller 派 meta-judge r 3. consensus → implement / converge → fresh round
是否需要人介入 ❌ 否(自动推进)

🤖 controller status banner

⟦AI:AUTO-LOOP⟧

@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 20, 2026

📊 状态卡片 — solver 派出

维度
阶段 派出 codex(role=solver)
codex log iter20-bootstrap.log
工作目录 /Users/auric/aevatar
timeout 3600s(~60 min 上限)
上下文 iter20 bootstrap: open 4 cluster 的 design issues + worktrees
下一步自动会做 1. 三 solver SOLVER_DONE:... 2. controller 派 meta-judge r 3. consensus → implement / converge → fresh round
是否需要人介入 ❌ 否(自动推进)

🤖 controller status banner

⟦AI:AUTO-LOOP⟧

@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 20, 2026

⟦AI:AUTO-LOOP⟧ iter20 bootstrap complete

OPENED:design-issue:cluster-001-studio-host-authoring-orchestration:747
OPENED:design-issue:cluster-002-request-path-projection-session-priming:748
OPENED:design-issue:cluster-003-durable-inbox-worker-state:749
OPENED:design-issue:cluster-004-conversation-volatile-streaming-state:750
ITER20_BOOTSTRAP_DONE:4:0

Notes:

  • Source: .refactor-loop/runs/audit-iter-20.md
  • All iter20 clusters have requires_design=true; no detached implement worktrees were created.
  • Labels applied to each issue: refactor-design-needed, auto-loop, phase9-auto-solve, 🔍 phase:design-solving, 🤖 human:auto-推进.

@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 20, 2026

📊 codex 进展 iter20-bootstrap (⏳ 进行中; 已跑 6 min)

ITER20_BOOTSTRAP_DONE:4:0

Notes:
- Source: "'`.refactor-loop/runs/audit-iter-20.md`
- All iter20 clusters have `requires_design=true`; no detached implement worktrees were created.
- Labels applied to each issue: `refactor-design-needed`, `auto-loop`, `phase9-auto-solve`, `🔍 phase:design-solving`, `🤖 human:auto-推进`.
EOF
gh pr comment 690 --repo aevatarAI/aevatar --body-file /tmp/iter20-bootstrap-banner.md' in /Users/auric/aevatar
 succeeded in 1551ms:
https://github.com/aevatarAI/aevatar/pull/690#issuecomment-4500716106

codex
OPENED:design-issue:cluster-001-studio-host-authoring-orchestration:747
OPENED:design-issue:cluster-002-request-path-projection-session-priming:748
OPENED:design-issue:cluster-003-durable-inbox-worker-state:749
OPENED:design-issue:cluster-004-conversation-volatile-streaming-state:750
ITER20_BOOTSTRAP_DONE:4:0

PR #690 banner 已发:`https://github.com/aevatarAI/aevatar/pull/690#issuecomment-4500716106`
tokens used
78,589
OPENED:design-issue:cluster-001-studio-host-authoring-orchestration:747
OPENED:design-issue:cluster-002-request-path-projection-session-priming:748
OPENED:design-issue:cluster-003-durable-inbox-worker-state:749
OPENED:design-issue:cluster-004-conversation-volatile-streaming-state:750

自动更新每 10 分钟;edit-in-place 不堆评论;codex 完成后此 comment 自动删除(per Auric "完成后删掉就好了 否则太占空间")。
🤖 controller progress reporter

eanzhao added a commit that referenced this pull request May 21, 2026
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.
loning added a commit that referenced this pull request May 21, 2026
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⟧
loning added 21 commits May 26, 2026 11:52
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⟧
loning and others added 6 commits May 27, 2026 18:27
…') (#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>
@loning
Copy link
Copy Markdown
Contributor Author

loning commented May 27, 2026

Superseded by daemon-managed rollup(等 daemon next tick 创新 PR)。⟦AI:AUTO-LOOP⟧

@loning loning closed this May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants