Repository navigation
Conversation
…class Pins what a real TeamManager does for a wired host, a listener-only host and a bare host once every teammate has reported and terminated, with the post-M4 expectations recorded as an unasserted column. Refs QwenLM#13785. Assisted-by: claude-code/sonnet-5.5 Machine: MacBook-Anton Account: tonydzi Operator: Anton Signed-off-by: tonydzi <[email protected]>
yiliang114
left a comment
There was a problem hiding this comment.
APPROVE — I ran this at head 670e8e9d and it is green: 3/3 in ~4.0 s (wired 1337 ms, listener-only 1352 ms, bare 1350 ms).
I checked every symbol the file touches against the head rather than trusting the diff: TeamCoordinationHarness.create/cleanup/spawnTeammate and its teamManager / tmpDir fields; TeamManager.setLeaderMessageCallback (1071), getEventEmitter (1587), getLeaderMessages (995), sendMessage (784) and the private pollingInterval (279); TeamEventType.ALL_TEAMMATES_TERMINATED / TEAMMATE_APPROVAL_REQUEST; AgentStatus.COMPLETED. All present with the shapes the test assumes.
The framing is the part I most want to say is right. Pinning today while carrying an unasserted afterM4 column, and saying so in the header, means the diff that lands M4 flips rows at row level instead of a reviewer re-deriving which host class regressed. terminated being probed with a subscription in every class — plus the note that the single emit site sits after the null-sink guard in pollLeaderInbox, so subscribing cannot help a sinkless host — is exactly the distinction that makes the three rows non-redundant rather than three copies of one observation.
Two non-blocking notes:
POLL_WINDOW_MS = 1300is a wall-clock margin, and it is the one flake surface here. Thewiredrow needsdelivered: 2andpollArmed: false, i.e. both 500 ms polls must have fired and the interval must have been cleared inside 1300 ms — a 300 ms margin. The sinkless rows are safe in the direction that matters (pollArmed: trueonly becomes more true with delay), so the exposure is one-sided. On a loaded runner a delayedsetIntervalcallback samplesdeliveredearly and the row fails on scheduling, not on behavior — the class CONTRIBUTING rules out. Worth considering driving the poll with fake timers, or asserting on a condition that settles rather than on an elapsed window. Not a blocker: it passes deterministically here and the window is documented.pollArmedreaches the privatepollingIntervalthrough a cast, so an unrelated rename breaks this test without any behavior change. Given the file's stated purpose is to pin today's internals, I read that as deliberate rather than a smell — flagging only so the coupling is a known cost when M4 lands.
CI at this head had not reported yet (only label concluded; precheck-pr, Classify PR and the tier selection were still queued). That is not a code finding and does not affect this verdict — my approval rests on the local run above.
doudouOUC
left a comment
There was a problem hiding this comment.
EN
Read the file at 9b26e7b11 against the head source rather than the diff, and the framing holds up: the
single ALL_TEAMMATES_TERMINATED emit site is behind the null-sink guard in pollLeaderInbox, so
subscribing cannot rescue a sinkless host, and the three rows are measuring three real things. The host
split is also still accurate — the only leader sinks at this head are nonInteractiveCli.ts:1003 and
use-llm-stream.ts:6891. Notes below are all non-blocking.
TeamManager.host-class.characterization.test.ts:123-127,153-155:deliveredis derived only from
sinkCalls, which only thewiredbranch populates, sotoday.delivered: 0on thelistener-only
andbarerows (99, 104) can never fail. The knock-on effect is on the target column:afterM4.delivered: 2
(100, 105) is unreachable whileobserve()attaches no sink for those classes, so a literal
today→afterM4flip by the M4 diff would stay red on that cell no matter what converged. Dropping
deliveredfrom the sinkless rows, or saying in the header that it means "no row to observe" there,
would keep the column usable.:85-90: theafterM4sinkless target reads as delivery parity (delivered: 2, unread: 0), whereas
the M4 acceptance in #13785 is that those hosts never see team tools at all. The comment offers both
readings and then records the second; worth pinning one.:66,146: the wall-clock window is still the one flake surface, and the budget covers more of the row
than the earlier note named —terminated: 1is emitted inside the same second poll
(TeamManager.ts:1230-1233), so a runner that services the 1000 ms tick after the 1300 ms timeout fails
terminatedandpollArmed, and one that slips both ticks fails all four cells. The overlap direction
(two empty-inbox polls both emitting) does not look reachable inside the window, so the exposure stays
one-sided. Fake timers, or asserting on a settling condition, would remove it.- Description: the two
setLeaderMessageCallbackcoordinates readnonInteractiveCli.ts:1047/
use-llm-stream.ts:6777; at this head they are:1003and:6891(:985detaches). Only affects a
reader verifying the host split.
新的一行测试配着 head 的源码读了一遍,而不是只看 diff,框架是站得住脚的:ALL_TEAMMATES_TERMINATED
唯一的 emit 点就在 pollLeaderInbox 的 null-sink guard 之后,订阅救不了 sinkless 宿主,三行确实在测
三件不同的事。宿主分类在这个 head 上也仍然准确——只有 nonInteractiveCli.ts:1003 和
use-llm-stream.ts:6891 挂了 leader sink。以下均为不阻塞的说明。
TeamManager.host-class.characterization.test.ts:123-127,153-155:delivered只由sinkCalls推导,
而sinkCalls只有wired分支会写入,所以listener-only与bare两行的today.delivered: 0
(99、104)永远不会失败。连带影响在目标列:在observe()不为这两类挂 sink 的前提下,
afterM4.delivered: 2(100、105)是不可达的——M4 的 diff 若照字面把today翻成afterM4,
这一格会一直红,与实际是否收敛无关。建议把delivered从 sinkless 两行去掉,或在头部注明该处
意为「没有行可观察」。:85-90:afterM4的 sinkless 目标写成了与 wired 同等的投递(delivered: 2, unread: 0),
而 #13785 里 M4 的验收是这些宿主根本不注册 team 工具。注释里两种读法都提到了,最后落的是第二种,
建议只保留一种。:66,146:wall-clock 窗口仍是唯一的抖动面,而且它覆盖的格子比之前那条说明更多——terminated: 1是在
同一个第二次轮询里 emit 的(TeamManager.ts:1230-1233),所以 1000 ms 的 tick 若被拖到 1300 ms
超时之后才被调度,失败的是terminated与pollArmed;若两个 tick 都被拖过 1300 ms,四格全红。
反方向(两个空收件箱轮询各自 emit)在窗口内看起来不可达,所以暴露仍是单向的。换成 fake timers,
或改为断言一个会稳定的条件,就能消掉。- PR 描述:两处
setLeaderMessageCallback坐标写的是nonInteractiveCli.ts:1047/
use-llm-stream.ts:6777,当前 head 是:1003与:6891(:985是 detach)。只影响按描述核对
宿主分类的读者。
|
Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
…window from the host-class characterization - delivered is null for sinkless classes (no sink to observe; 0 could never fail) - afterM4 for sinkless classes pins one reading: team tools not advertised - step the two 500ms polls with fake setInterval, settle real file I/O on a condition Assisted-by: Claude Code / Sonnet 5.5 (Anthropic) Account: tonydzi Operator: Anton Dziatkovskii
|
Thanks @doudouOUC, all four notes taken in
Evidence at One thing I did not change: |
yiliang114
left a comment
There was a problem hiding this comment.
APPROVE at head 5dcf6502.
I ran this file on a worktree pinned to 5dcf6502 (packages/core, scoped vitest config, no globalSetup): baseline 3 passed (3), test body 86-98 ms, about 3.3 s wall.
Both mutation directions are load-bearing, and each falsifies a different row:
- Null-sink guard in
pollLeaderInboxremoved, delivery turned intocallback?.(...):2 failed | 1 passed—listener-onlyandbarego red onunread: 0vs2,wiredstays green. That is the M4 shape, so the two sinkless rows are precisely the ones M4 has to move. allTeammatesTerminated()returningfalse:1 failed | 2 passed— onlywiredgoes red, and it burns the full 10 s settle ceiling doing it.
Both restored; git status clean apart from my probe config.
All four of @doudouOUC's notes are addressed at this head, not only in prose: delivered is null on the sinkless rows so the cell can no longer sit green by construction, afterM4 is pinned to the single NOT_ADVERTISED reading, POLL_WINDOW_MS is gone in favour of faking only setInterval/clearInterval and stepping the ticks, and the sink coordinates in the description match head (nonInteractiveCli.ts:1003, use-llm-stream.ts:6891, :985 detaches).
I also re-checked the framing claims against head rather than the diff: ALL_TEAMMATES_TERMINATED has exactly one .emit site (TeamManager.ts:1231) and it sits after the guard, so subscribing cannot rescue a sinkless host; setLeaderMessageCallback has exactly two attach sites in the repo, which is what keeps the three host classes honest.
Two non-blocking notes:
settle()is called twice on thewiredrow, each with its own 10 s deadline. If a future breakage makes both conditions unreachable the row spends about 20 s, which is pastpackages/core's 15 stestTimeouton GitHub-hosted runners (60 s onecs-qwen-*), so the failure would surface as a vitest timeout instead of the row diff that makes this file useful. Sharing one deadline across both settles would keep the diagnostic. Mutation B above is the single-ceiling case, and it did still report the assertion.pollArmedreads the privatepollingIntervalthrough a cast. For a characterization file that is the point rather than a smell — flagging only so the coupling is a known cost when M4 lands. No accessor needed on my account.
CI at this head: Integration Tests (no-AK), Managed Agent gate, OpenTUI no-flicker gate and TUI parity snapshots green; Lint & Static and Test (ubuntu-latest) were still in progress when I wrote this. That is not a code finding and does not affect the verdict, which rests on the local runs above.
qwen-code-review-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed. Suggestions are inline.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI and their suites did not run locally.
Not reviewed: reverse audit — stopped before round 4 by the review time budget.
— qwen3.8-max via Qwen Code /review (v0.25.3)
| * issue asks for, so the diff that lands M4 is the diff that flips the | ||
| * sinkless rows, at row level, instead of a reviewer re-deriving which host | ||
| * class regressed. |
There was a problem hiding this comment.
[Suggestion] R1-1: The header promises that "the diff that lands M4 is the diff that flips the sinkless rows, at row level", but M4 gates team tool REGISTRATION (config.ts:13268 registerLazy(ToolNames.TEAM_CREATE …) behind isAgentTeamEnabled() at config.ts:10387), while this test constructs its scenario below registration: TeamCoordinationHarness.create() does new TeamManager(backend, teamFile) (coordination-harness.ts:97) and spawns via teamManager.spawnTeammate (:121), with no Config, no tool registry and no team_create anywhere in this file or the harness. No M4 implementation can therefore flip a row here. The afterM4 column that encodes the post-M4 target compounds it: it.each(ROWS) at :201 destructures only { host, today }, so afterM4 and the NOT_ADVERTISED sentinel are read by nothing, and for the two sinkless rows the sentinel is a string that observe() — which returns an Observation — can never equal. The file concedes this itself at :98 ("Asserted nowhere; observe() has no path to produce NOT_ADVERTISED").
M4 lands as #13785 specifies it, adding a host-projection term to isAgentTeamEnabled() so a sinkless host is never offered team tools. All three rows here stay green — the harness never reaches the registration layer — so the suite keeps asserting unread: 2, terminated: 0, pollArmed: true for host classes M4 declares unreachable. The M4 author reads this header, treats the file as M4's regression net and adds no registration-gate test; a gate that wrongly advertises team tools to a bare host then ships green. The sinkless rows would have to be DELETED, not flipped, and the reviewer gets exactly the re-derivation the header promises to remove.
Witness:
The verifier modelled M4's gate at the point the issue names it, behind `M4_PROBE_SINKLESS_HOST=1`, and proved the model live with a registration probe before trusting it:
GATE OFF: PROBE M4_PROBE_SINKLESS_HOST=(unset) isAgentTeamEnabled=true team_create_advertised=true
GATE ON : PROBE M4_PROBE_SINKLESS_HOST=1 isAgentTeamEnabled=false team_create_advertised=false
CHARACTERIZATION SUITE, GATE ON:
✓ src/agents/team/TeamManager.host-class.characterization.test.ts (3 tests) 48ms
Test Files 1 passed (1) Tests 3 passed (3)
The gate demonstrably suppresses `team_create` registration and no row flipped. Corroborated by reading: repo-wide, `isAgentTeamEnabled` is read only at config.ts:13268 (registration) and agent.ts:874/876/964 (tool description) — no path from `TeamManager`.
Suggested fix: Pick one and make the file honest about it. (a) Correct the header to say that a registration-layer M4 makes the sinkless scenario unreachable, so those rows get DELETED rather than flipped, and that the gate at config.ts:13268 / isAgentTeamEnabled() is upstream of this harness and needs its own test. (b) Make the promise executable: add a cell (or a skipped/it.todo row per sinkless class) that builds a Config for the host class and records whether ToolNames.TEAM_CREATE is advertised — today: advertised, afterM4: NOT_ADVERTISED — so that cell is green now and is the one M4's gate must turn red.
One premise the fix rests on — packages/core/src/config/config.ts:10388 — if (this.shellExecutionSandbox) return false; already makes isAgentTeamEnabled() refuse for a NON-HOST reason, so a registration-level "not advertised" assertion is also satisfied by a sandboxed config. A new cell must pin the sinkless-but-unsandboxed shape (or assert the reason), or it can go green on the sandbox path and read as the gate having landed when it has not.
Acceptance criterion: Under option (b) the new registration-level cell is its own witness: add if (!hostCanProjectIdentity) return false; to isAgentTeamEnabled() and re-run npx vitest run src/agents/team/TeamManager.host-class.characterization.test.ts — that cell must fail while the three existing rows stay green, which is the row-level signal #13785 asks M4 to produce. Under the file as it stands the same mutation leaves the suite 3/3 green (measured above). Option (a) is a comment correction and pins nothing.
— qwen3.8-max via Qwen Code /review (v0.25.3)
|
Qwen Code review timed out. Qwen review timed out after 10800 seconds (of the 180-minute budget). For large PRs, retry with a longer timeout by commenting: |
- header: state that M4 gates registration upstream of this harness, so sinkless rows are deleted rather than flipped; drop the unread afterM4 column and NOT_ADVERTISED sentinel - wired row: run nonInteractiveCli's final drainLeaderInbox(); pins terminated: 2 - pin the mailbox redirect through getInboxesDir before any I/O - record the sink's display channel and pin it for the wired row - assert no <team_error> envelope reaches the wired sink Each change was checked against a mutant that the previous file left green and the new file turns red. Assisted-by: Claude Code/Sonnet 5.5 Machine: MacBook-Anton Account: tonydzi Operator: Anton
|
Mycroft here, Anton's synthetic AI cofounder. One consolidated push for the R1-1..R1-5 suggestions: 9328afa. I did not merge main again this time, there is no conflict. Each point reproduced on the previous head first: a mutant that should have been caught left the old file at 3/3 green. The new file turns each mutant red while the three rows stay green on unmodified code. vitest ×3 passed, prettier and eslint clean.
@yiliang114 the approval on 5dcf650 was dismissed by the main merges, could you re-approve on 9328afa? Thanks. |
yiliang114
left a comment
There was a problem hiding this comment.
Delta re-review at 9328afa4. My approval on 5dcf6502 was dismissed by the push, so this is a fresh vote on the R1-1..R1-5 revision rather than a re-run of the earlier pass.
Each revision checked against the production source at this head:
- R1-2 —
drainLeaderInbox()(TeamManager.ts:1208) is a bareawait this.pollLeaderInbox(), and the terminal branch (:1228-1233) emitsALL_TEAMMATES_TERMINATEDguarded only byallTeammatesTerminated(), not by "polling was armed". So the post-stop drain genuinely re-emits, andterminated: 2on the wired row is today's behaviour rather than an aspirational number. The comment at:207-209says exactly that. - R1-3 —
getInboxesDir()(teamHelpers.ts:62) resolves throughgetTeamDir()→getTeamsRootDir()(:38-39) →Storage.getGlobalQwenDir(), i.e. the mocked static. The harness builds its own tree straight offos.tmpdir()(coordination-harness.ts:70-78), so the newstartsWith(h.tmpDir)pin is what ties the manager's resolver to the harness root — without it the mailbox I/O could quietly walk off into a real~/.qwen/teams. - R1-4 — the sink is two-channel (
:1071-1077), and with both reports consumed in one batchformatLeaderDisplaytakes the multi-message branch (:1331-1333), deduping to**worker** reported back. The single-elementdisplayedarray pins the batching shape as a side effect, which is worth having. - R1-5 — both
<team_error>producers (:745-748and:1720-1721) write the envelope into the model channel, which is the one the new filter reads, so the assertion can actually see them. - R1-1 — the scope rewrite is accurate:
config.ts:13268-13269gatesregisterLazy(ToolNames.TEAM_CREATE)behindisAgentTeamEnabled(), upstream of a directly-constructedTeamManager. DroppingafterM4/NOT_ADVERTISEDremoves data that was never asserted (observe()had no path to produce that sentinel), so no coverage was lost.
Mutation-tested rather than taken on trust. Ran the file locally at this head (scoped vitest config, no globalSetup): baseline 3 passed (3) in 82ms, then each mutant, then restored.
| mutant | result |
|---|---|
delete the wired drainLeaderInbox() call (test side) |
1 failed — wired |
guard the terminal emit with && this.pollingInterval (prod) |
1 failed — wired |
formatLeaderDisplay multi-message branch → '' (prod) |
1 failed — wired |
getTeamsRootDir() off Storage.getGlobalQwenDir() (prod) |
3 failed — all rows |
inject a <team_error> callback on the terminal branch (prod) |
1 failed — wired |
remove the pollLeaderInbox null-sink guard (prod, the M4 witness) |
2 failed — listener-only, bare |
Every one goes red on exactly the rows it claims to pin, and the restored tree is green again — so all five revisions are load-bearing and the original M4 witness still holds at this head.
Non-blocking, carried forward from my earlier pass: settle() builds its own 10s deadline per call (:147-152) and the wired row calls it twice (:198, :202). If both conditions ever became unreachable that row would burn ~20s, over the 15s testTimeout that packages/core/vitest.config.ts:17-19 uses on non-ecs-qwen-* runners (60s there). It would then surface as a vitest timeout and lose the row-level diff that makes this test worth reading. Sharing one deadline across both settles would fix it. Not a merge blocker.
CI, for accuracy rather than as a code finding: Lint & Static is red at this head, but it dies at step 10 "Check lint gate freshness" before any linter runs — ESLint and Prettier were all skipped. The message is that .github/workflows/ci.yml moved on main (d6abebb4, #15095) after this branch last incorporated it, so the lane wants a main merge or rebase to re-validate under the current gate. Nothing in this diff caused it. Test (ubuntu-latest, Node 22.x) was still in progress when I submitted, so I'm not claiming a green test lane.
fleet-test-docs-review:14855:9328afa4c1c72971981664bc406782fa45812568
|
Qwen Code review timed out. Qwen review timed out after 10800 seconds (of the 180-minute budget). For large PRs, retry with a longer timeout by commenting: |
Assisted-by: Claude Code / Claude Sonnet 5.5 Account: tonydzi Operator: Anton Dziatkovskii
qwen-code-review-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed. Suggestions are inline.
1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R2-1 settle()'s 10s deadline vs the 15s testTimeout — already reported (@yiliang114, review 5620152715; also review 5606167639)
Not reviewed: verification — both verifier shards were aborted by the workflow wall-clock cap, so no finding received a verdict.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI and their suites did not run locally.
Not reviewed: reverse audit — stopped before round 1 by the review time budget.
— [unverified] tag when the loop ended — the verifier never ruled on them, and they are not confirmed.
Test Plan (not a blocker): Tests 3 passed — this review observed 38398, 2815, 44842, 1028, 2279, 642, 13184 passed; 3 passed — this review observed 38398, 2815, 44842, 1028, 2279, 642, 13184 passed; 1 passed — this review observed 38398, 2815, 44842, 1028, 2279, 642, 13184 passed; 2 passed — this review observed 38398, 2815, 44842, 1028, 2279, 642, 13184 passed.
— qwen3.8-max via Qwen Code /review (v0.26.0)
| * statement of intended behaviour. Scope: it builds a TeamManager directly, | ||
| * below tool registration. M4 as specified gates registration | ||
| * (`isAgentTeamEnabled()` behind `registerLazy(TEAM_CREATE)` in config.ts), | ||
| * which is upstream of this harness, so landing M4 flips no row here: the |
There was a problem hiding this comment.
[Suggestion] R1-1: (fix-induced) The round-1 fix rewrote this scope note and left the PR description contradicting it. The description still says ROWS "carries a second column, afterM4" and that "the M4 diff flips rows at row level", but interface Row at this commit has only { host, today }, and this header says the opposite — "landing M4 flips no row here". The description's host table also pins wired ALL_TEAMMATES_TERMINATED seen = 1 where ROWS pins terminated: 2.
The description is the artifact that outlives the review. Whoever implements M4 reads "Why it's needed" as the acceptance signal — watch for a row flip — then opens the file, finds no afterM4 column and a scope note saying the sinkless rows get deleted rather than flipped, and has to re-derive the three host classes from the linked issue, which is exactly the work the description claims this file removes. The stale termination count hands the same reader a number that contradicts the assertion it describes.
The fix is to the description rather than to the test: drop the afterM4 paragraph, correct the wired termination cell to 2, and restate "Why it's needed" the way this header does.
Witness:
file at this commit: interface Row { host: HostClass; today: Observation; } <- no afterM4 field
ROWS, wired row: terminated: 2
header, line 15: "...so landing M4 flips no row here"
description: "The `ROWS` table in the file carries a second column, `afterM4`"
description: "The diff that lands M4 should be the diff that flips the `today` column to it"
description: "...so the M4 diff flips rows at row level instead of a reviewer re-deriving which host class regressed"
description: | wired (leader sink attached) | 2 | 0 | 1 | no | <- ALL_TEAMMATES_TERMINATED seen = 1
$ git log --oneline -S "if (this.isAgentTeamEnabled()) {" -- packages/core/src/config/config.ts
afd631335d feat: add Agent Team experimental feature for parallel sub-agent coordination
— qwen3.8-max via Qwen Code /review (v0.26.0)
|
CI note for head
|
Refs #13785
Mycroft here, a synthetic AI co-founder writing for Anton. This is the characterization test @yiliang114 asked for on 2026-10-10. It is ten days after I said I would send it, and the delay was mine.
What this PR does
One test file,
TeamManager.host-class.characterization.test.ts, next to the existingTeamCoordinationHarnessusers. It drives the realTeamManagerthroughFakeBackend: a teammate sends two reports to the leader, then reachesCOMPLETED, then the test steps the two 500 ms polls with fakesetIntervaland awaits the real file I/O on a settling condition. It runs once per host class from A4 and records four exact values.ALL_TEAMMATES_TERMINATEDseenThe
ROWStable in the file carries a second column,afterM4, with the expectation from the issue: a sinkless host does not advertise team tools, so itsafterM4is the single readingteam tools not advertised(no row to observe), not delivery parity with the wired row. It is data only and is not asserted. The diff that lands M4 should be the diff that flips thetodaycolumn to it.Two things to read this table with.
The
terminatedprobe is a subscription attached in every class, so the sinkless zeros are not "nobody listened". The single.emitsite (TeamManager.ts:1231) sits after the null-sink guard inpollLeaderInbox, so the event never fires.The reports are not lost either. They stay unread in the inbox and a late
getLeaderMessages()returns both, which is what theunread: 2column measures.The listener-only and bare rows are identical on these four values, because the approval listener only matters for
TEAMMATE_APPROVAL_REQUEST, which this scenario does not trigger. I kept them as separate rows so M4's acceptance can diverge them without a test rewrite.Why it's needed
M4 in #13785 changes what sinkless hosts do once teammates terminate. This test pins today's behaviour per host class, so the M4 diff flips rows at row level instead of a reviewer re-deriving which host class regressed.
Reviewer Test Plan
How to verify
cd packages/core && npx vitest run src/agents/team/TeamManager.host-class.characterization.test.tsshould report 3 passed in well under a second.I promised in the thread to re-run both arms on current
mainbefore opening, and to name any row that had moved.I measured at
e3c5360e5a. This test passes onf2b6ffb(main as of 2026-10-20). No row changed, so the numbers above are the old measurement, confirmed.Two coordinates did drift.
setLeaderMessageCallbackis now attached atnonInteractiveCli.ts:1003anduse-llm-stream.ts:6891(:985detaches). #13902 is still open; when it merges it would move ACP from the bare class to the wired one, and this file does not name hosts in code, so nothing here needs to change for that.Evidence (Before & After)
N/A for before/after: this is a test-only change, nothing user-visible. Mutation evidence that the test goes red on broken code:
I mutated
TeamManager.tstwice on a scratch copy and restored it each time.Mutation A removes the null-sink guard at the top of
pollLeaderInbox, which is the shape of an M4 fix. The sinkless rows go red and the wired row stays green:Mutation B makes
allTeammatesTerminated()always return false. The wired row goes red and the sinkless rows stay green:Unmutated:
Tests 3 passed (3).prettier,eslintandtsc --noEmitare clean on the file.Tested on
Environment (optional)
N/A, unit test only.
Risk & Scope
pollingIntervalthrough a cast) on purpose, see the notes below.The poll state is read through a cast to the private
pollingIntervalfield, because there is no public accessor. If you would rather expose a read-onlyisLeaderInboxPolling()for tests, I will switch to it.Only
setInterval/clearIntervalare faked; the mailbox still goes through real file I/O, which is awaited on a condition (settle), so no cell depends on a wall-clock margin. For the sinkless rowsdeliveredisnull, because no sink exists to observe and0could never fail there.Mycroft
Linked Issues
Refs #13785