Skip to content

Reduce subagent read polling and suppress transcript chatter - #7

Merged
ianwalter merged 15 commits into
mainfrom
fix/subagents-read-summary
Aug 17, 2026
Merged

Reduce subagent read polling and suppress transcript chatter#7
ianwalter merged 15 commits into
mainfrom
fix/subagents-read-summary

Merge remote-tracking branch 'origin/fix/subagents-read-summary' into…

808f0ce
Select commit
Loading
Failed to load commit list.
Informant fa2915a4 / Informant / test succeeded Aug 17, 2026 in 1m 14s

test passed

Ran on blackbird.local.

Details

b/aarch64-linux-gnu/libtirpc.so.3.usr-is-merged by libtirpc3t64'
#9 2.751 Adding 'diversion of /lib/aarch64-linux-gnu/libtirpc.so.3.0.0 to /lib/aarch64-linux-gnu/libtirpc.so.3.0.0.usr-is-merged by libtirpc3t64'
#9 2.752 Unpacking libtirpc3t64:arm64 (1.3.6+ds-1) ...
#9 2.759 Selecting previously unselected package lsof.
#9 2.759 Preparing to unpack .../10-lsof_4.99.4+dfsg-2_arm64.deb ...
#9 2.759 Unpacking lsof (4.99.4+dfsg-2) ...
#9 2.773 Selecting previously unselected package perl-modules-5.40.
#9 2.774 Preparing to unpack .../11-perl-modules-5.40_5.40.1-6_all.deb ...
#9 2.774 Unpacking perl-modules-5.40 (5.40.1-6) ...
#9 2.914 Selecting previously unselected package libgdbm6t64:arm64.
#9 2.914 Preparing to unpack .../12-libgdbm6t64_1.24-2_arm64.deb ...
#9 2.915 Unpacking libgdbm6t64:arm64 (1.24-2) ...
#9 2.921 Selecting previously unselected package libgdbm-compat4t64:arm64.
#9 2.922 Preparing to unpack .../13-libgdbm-compat4t64_1.24-2_arm64.deb ...
#9 2.922 Unpacking libgdbm-compat4t64:arm64 (1.24-2) ...
#9 2.928 Selecting previously unselected package libperl5.40:arm64.
#9 2.929 Preparing to unpack .../14-libperl5.40_5.40.1-6_arm64.deb ...
#9 2.929 Unpacking libperl5.40:arm64 (5.40.1-6) ...
#9 3.094 Selecting previously unselected package perl.
#9 3.095 Preparing to unpack .../15-perl_5.40.1-6_arm64.deb ...
#9 3.095 Unpacking perl (5.40.1-6) ...
#9 3.109 Selecting previously unselected package libbrotli1:arm64.
#9 3.109 Preparing to unpack .../16-libbrotli1_1.1.0-2+b7_arm64.deb ...
#9 3.110 Unpacking libbrotli1:arm64 (1.1.0-2+b7) ...
#9 3.126 Selecting previously unselected package libunistring5:arm64.
#9 3.126 Preparing to unpack .../17-libunistring5_1.3-2_arm64.deb ...
#9 3.127 Unpacking libunistring5:arm64 (1.3-2) ...
#9 3.151 Selecting previously unselected package libidn2-0:arm64.
#9 3.151 Preparing to unpack .../18-libidn2-0_2.3.8-2_arm64.deb ...
#9 3.151 Unpacking libidn2-0:arm64 (2.3.8-2) ...
#9 3.172 Selecting previously unselected package libffi8:arm64.
#9 3.172 Preparing to unpack .../19-libffi8_3.4.8-2_arm64.deb ...
#9 3.173 Unpacking libffi8:arm64 (3.4.8-2) ...
#9 3.178 Selecting previously unselected package libp11-kit0:arm64.
#9 3.178 Preparing to unpack .../20-libp11-kit0_0.25.5-3_arm64.deb ...
#9 3.178 Unpacking libp11-kit0:arm64 (0.25.5-3) ...
#9 3.196 Selecting previously unselected package libtasn1-6:arm64.
#9 3.197 Preparing to unpack .../21-libtasn1-6_4.20.0-2+deb13u1_arm64.deb ...
#9 3.197 Unpacking libtasn1-6:arm64 (4.20.0-2+deb13u1) ...
#9 3.204 Selecting previously unselected package libgnutls30t64:arm64.
#9 3.204 Preparing to unpack .../22-libgnutls30t64_3.8.9-3+deb13u4_arm64.deb ...
#9 3.204 Unpacking libgnutls30t64:arm64 (3.8.9-3+deb13u4) ...
#9 3.244 Selecting previously unselected package libsasl2-modules-db:arm64.
#9 3.244 Preparing to unpack .../23-libsasl2-modules-db_2.1.28+dfsg1-9_arm64.deb ...
#9 3.244 Unpacking libsasl2-modules-db:arm64 (2.1.28+dfsg1-9) ...
#9 3.250 Selecting previously unselected package libsasl2-2:arm64.
#9 3.250 Preparing to unpack .../24-libsasl2-2_2.1.28+dfsg1-9_arm64.deb ...
#9 3.251 Unpacking libsasl2-2:arm64 (2.1.28+dfsg1-9) ...
#9 3.257 Selecting previously unselected package libldap2:arm64.
#9 3.258 Preparing to unpack .../25-libldap2_2.6.10+dfsg-1_arm64.deb ...
#9 3.258 Unpacking libldap2:arm64 (2.6.10+dfsg-1) ...
#9 3.269 Selecting previously unselected package libnghttp2-14:arm64.
#9 3.270 Preparing to unpack .../26-libnghttp2-14_1.64.0-1.1+deb13u1_arm64.deb ...
#9 3.270 Unpacking libnghttp2-14:arm64 (1.64.0-1.1+deb13u1) ...
#9 3.277 Selecting previously unselected package libnghttp3-9:arm64.
#9 3.278 Preparing to unpack .../27-libnghttp3-9_1.8.0-1_arm64.deb ...
#9 3.278 Unpacking libnghttp3-9:arm64 (1.8.0-1) ...
#9 3.285 Selecting previously unselected package libngtcp2-16:arm64.
#9 3.285 Preparing to unpack .../28-libngtcp2-16_1.11.0-1+deb13u1_arm64.deb ...
#9 3.285 Unpacking libngtcp2-16:arm64 (1.11.0-1+deb13u1) ...
#9 3.294 Selecting previously unselected package libngtcp2-crypto-gnutls8:arm64.
#9 3.294 Preparing to unpack .../29-libngtcp2-crypto-gnutls8_1.11.0-1+deb13u1_arm64.deb ...
#9 3.294 Unpacking libngtcp2-crypto-gnutls8:arm64 (1.11.0-1+deb13u1) ...
#9 3.300 Selecting previously unselected package libpsl5t64:arm64.
#9 3.300 Preparing to unpack .../30-libpsl5t64_0.21.2-1.1+b1_arm64.deb ...
#9 3.301 Unpacking libpsl5t64:arm64 (0.21.2-1.1+b1) ...
#9 3.308 Selecting previously unselected package librtmp1:arm64.
#9 3.308 Preparing to unpack .../31-librtmp1_2.4+20151223.gitfa8646d.1-2+b5_arm64.deb ...
#9 3.309 Unpacking librtmp1:arm64 (2.4+20151223.gitfa8646d.1-2+b5) ...
#9 3.315 Selecting previously unselected package libssh2-1t64:arm64.
#9 3.315 Preparing to unpack .../32-libssh2-1t64_1.11.1-1+deb13u1_arm64.deb ...
#9 3.316 Unpacking libssh2-1t64:arm64 (1.11.1-1+deb13u1) ...
#9 3.326 Selecting previously unselected package libcurl3t64-gnutls:arm64.
#9 3.327 Preparing to unpack .../33-libcurl3t64-gnutls_8.14.1-2+deb13u4_arm64.deb ...
#9 3.327 Unpacking libcurl3t64-gnutls:arm64 (8.14.1-2+deb13u4) ...
#9 3.344 Selecting previously unselected package liberror-perl.
#9 3.344 Preparing to unpack .../34-liberror-perl_0.17030-1_all.deb ...
#9 3.345 Unpacking liberror-perl (0.17030-1) ...
#9 3.350 Selecting previously unselected package git-man.
#9 3.351 Preparing to unpack .../35-git-man_1%3a2.47.3-0+deb13u1_all.deb ...
#9 3.351 Unpacking git-man (1:2.47.3-0+deb13u1) ...
#9 3.399 Selecting previously unselected package git.
#9 3.400 Preparing to unpack .../36-git_1%3a2.47.3-0+deb13u1_arm64.deb ...
#9 3.401 Unpacking git (1:2.47.3-0+deb13u1) ...
#9 3.643 Setting up libexpat1:arm64 (2.8.2-1~deb13u1) ...
#9 3.643 Setting up libkeyutils1:arm64 (1.6.3-6) ...
#9 3.644 Setting up libgdbm6t64:arm64 (1.24-2) ...
#9 3.645 Setting up libgdbm-compat4t64:arm64 (1.24-2) ...
#9 3.646 Setting up liblsof0 (4.99.4+dfsg-2) ...
#9 3.647 Setting up libtirpc-common (1.3.6+ds-1) ...
#9 3.648 Setting up libbrotli1:arm64 (1.1.0-2+b7) ...
#9 3.649 Setting up libnghttp2-14:arm64 (1.64.0-1.1+deb13u1) ...
#9 3.650 Setting up libcom-err2:arm64 (1.47.2-3+b11) ...
#9 3.651 Setting up libkrb5support0:arm64 (1.21.3-5+deb13u1) ...
#9 3.652 Setting up libsasl2-modules-db:arm64 (2.1.28+dfsg1-9) ...
#9 3.652 Setting up libunistring5:arm64 (1.3-2) ...
#9 3.653 Setting up libk5crypto3:arm64 (1.21.3-5+deb13u1) ...
#9 3.654 Setting up libsasl2-2:arm64 (2.1.28+dfsg1-9) ...
#9 3.655 Setting up libnghttp3-9:arm64 (1.8.0-1) ...
#9 3.656 Setting up libffi8:arm64 (3.4.8-2) ...
#9 3.657 Setting up perl-modules-5.40 (5.40.1-6) ...
#9 3.658 Setting up libtasn1-6:arm64 (4.20.0-2+deb13u1) ...
#9 3.660 Setting up git-man (1:2.47.3-0+deb13u1) ...
#9 3.666 Setting up libngtcp2-16:arm64 (1.11.0-1+deb13u1) ...
#9 3.666 Setting up libkrb5-3:arm64 (1.21.3-5+deb13u1) ...
#9 3.667 Setting up libssh2-1t64:arm64 (1.11.1-1+deb13u1) ...
#9 3.668 Setting up libldap2:arm64 (2.6.10+dfsg-1) ...
#9 3.669 Setting up libidn2-0:arm64 (2.3.8-2) ...
#9 3.670 Setting up libperl5.40:arm64 (5.40.1-6) ...
#9 3.671 Setting up perl (5.40.1-6) ...
#9 3.673 Setting up libp11-kit0:arm64 (0.25.5-3) ...
#9 3.673 Setting up libgssapi-krb5-2:arm64 (1.21.3-5+deb13u1) ...
#9 3.674 Setting up libgnutls30t64:arm64 (3.8.9-3+deb13u4) ...
#9 3.675 Setting up libtirpc3t64:arm64 (1.3.6+ds-1) ...
#9 3.676 Setting up libpsl5t64:arm64 (0.21.2-1.1+b1) ...
#9 3.677 Setting up liberror-perl (0.17030-1) ...
#9 3.678 Setting up librtmp1:arm64 (2.4+20151223.gitfa8646d.1-2+b5) ...
#9 3.679 Setting up lsof (4.99.4+dfsg-2) ...
#9 3.679 Setting up libngtcp2-crypto-gnutls8:arm64 (1.11.0-1+deb13u1) ...
#9 3.680 Setting up libcurl3t64-gnutls:arm64 (8.14.1-2+deb13u4) ...
#9 3.681 Setting up git (1:2.47.3-0+deb13u1) ...
#9 3.684 Processing triggers for libc-bin (2.41-12+deb13u1) ...
#9 3.692 bun install v1.3.10 (30e609e0)
#9 6.062 
#9 6.062 + @biomejs/biome@2.3.7
#9 6.062 + @earendil-works/pi-ai@0.84.1
#9 6.062 + @earendil-works/pi-coding-agent@0.84.1
#9 6.062 + @earendil-works/pi-tui@0.84.1
#9 6.062 + @tailwindcss/vite@4.1.18
#9 6.062 + @types/bun@1.3.10
#9 6.062 + @types/node@24.12.0
#9 6.062 + @types/react@19.2.14
#9 6.062 + @types/react-dom@19.2.3
#9 6.062 + @vitejs/plugin-react@5.1.4
#9 6.062 + class-variance-authority@0.7.1
#9 6.062 + clsx@2.1.1
#9 6.062 + lucide-react@0.563.0
#9 6.062 + react@19.2.4
#9 6.062 + react-dom@19.2.4
#9 6.062 + react-markdown@10.1.0
#9 6.062 + remark-gfm@4.0.1
#9 6.062 + tailwind-merge@3.4.0
#9 6.062 + tailwindcss@4.1.18
#9 6.062 + typebox@1.3.7
#9 6.062 + typescript@5.9.3
#9 6.062 + vite@7.3.6
#9 6.062 + @dnd-kit/core@6.3.1
#9 6.062 + @dnd-kit/sortable@10.0.0
#9 6.062 + @dnd-kit/utilities@3.2.2
#9 6.062 + @radix-ui/react-tooltip@1.2.8
#9 6.062 + diff@8.0.3
#9 6.062 + highlight.js@11.11.1
#9 6.062 + proper-lockfile@4.1.2
#9 6.062 
#9 6.062 347 packages installed [2.37s]
#9 DONE 6.5s

#10 exporting to oci image format
#10 exporting layers
#10 exporting layers 7.3s done
#10 exporting manifest sha256:51f07c4dd499b392afb9b156ad1e204e1d92e7b93d46ec4341f9c756b763bb57 done
#10 exporting config sha256:1f6e8da66d597ef233a074cb0e8cab33b536d58bbf2ba90aa46c4281724f1a3e done
#10 exporting manifest list sha256:96711cb331e190a6299fb69465ea60da3a4ce19c9500fbd24c8e9c642759e76a done
#10 sending tarball
#10 sending tarball 1.0s done
#10 DONE 8.3s
informant-prepared-container:0dc88d77990c3a2e
[2026-08-17T13:46:26.415Z] [test] started

[test] $ set -eu
bun run test
[0/6] [0s]
[1/6] Fetching image [0s]
[2/6] Unpacking image [0s]
[3/6] Fetching kernel [0s]
[4/6] Fetching init image [0s]
[5/6] Unpacking init image [0s]
[6/6] Starting container [0s]
[6/6] Starting container [0s]
$ bun test
bun test v1.3.10 (30e609e0)

tests/auto-router-classify.test.ts:
✓ classifyTurnComplexity parses a bare "minimal" reply [3.01ms]
✓ classifyTurnComplexity parses a bare "low" reply [0.11ms]
✓ classifyTurnComplexity parses a bare "medium" reply [0.06ms]
✓ classifyTurnComplexity parses a bare "high" reply [0.03ms]
✓ classifyTurnComplexity parses a bare "xhigh" reply [0.02ms]
✓ classifyTurnComplexity parses a bare "max" reply [0.03ms]
✓ classifyTurnComplexity parses the reply case-insensitively with surrounding text [0.07ms]
✓ classifyTurnComplexity picks the level word the model actually led with, not whichever word sorts earliest in the level list [0.04ms]
✓ classifyTurnComplexity picks the level word the model actually led with, even when a later caveat mentions an earlier-sorting level [0.07ms]
✓ classifyTurnComplexity falls back to medium on an unparseable reply [0.05ms]
✓ classifyTurnComplexity falls back to medium when the provider call throws, and records why in reply [0.22ms]
✓ classifyTurnComplexity surfaces the raw reply text alongside the parsed level, for diagnosing mismatches later [0.05ms]
✓ classifyTurnComplexity surfaces usage from the response when present [0.07ms]
✓ classifyTurnComplexity notes attached images in the classification prompt [0.07ms]

tests/auto-router-extension.test.ts:
✓ escapeTableCell neutralizes both pipes and line breaks, so one bad reply can't break the rest of the table [1.31ms]
✓ selecting Auto marks it active without eagerly routing, showing a neutral footer badge [2.45ms]
✓ selecting a pinned Auto (<tier>) entry shows that tier in the footer immediately, before any turn runs [1.08ms]
✓ a pinned Auto (<tier>) entry routes directly within that tier, skipping classification entirely [1.12ms]
✓ after a turn settles, /model reverts to the same pinned entry that was selected, not the adaptive one [0.61ms]
✓ session_start restores a pinned tier from a persisted entry and reverts to that specific placeholder [0.44ms]
✓ before_agent_start self-heals into the correct pinned tier when ctx.model is already that pinned placeholder [0.32ms]
✓ before_agent_start routes to the classified tier, and the picker shows Auto again once the turn settles [0.64ms]
✓ a model's `effort` override sets its own thinking level, independent of the tier that routed to it [1.11ms]
✓ a routed turn's classification is logged and shows up in /usage, so a routing decision can be checked against what the classifier actually said [0.55ms]
✓ routing escalates to a higher tier when the classified tier is entirely unhealthy [0.70ms]
✓ routing falls back to the classified tier's model as a last resort when nothing anywhere is healthy, and warns [0.53ms]
✓ the last-resort fallback labels the model with the tier it actually belongs to, not the (unconfigured) resolved tier [0.63ms]
✓ a recorded failure fails over to the next configured model in the same tier [0.48ms]
✓ a message-level provider error (no distinct HTTP failure status) still fails over on the next turn [0.56ms]
✓ an aborted (user-cancelled) message does not count as a provider failure [0.56ms]
✓ router-observed usage is tracked for a manually-selected configured model, not just ones Auto routed to [0.58ms]
✓ router-observed failures are tracked for a manually-selected configured model too [1.63ms]
✓ usage from a model that isn't configured anywhere in autoRouter is not tracked [0.59ms]
✓ manually picking a real model while Auto is active turns Auto off [0.52ms]
✓ deactivating Auto removes its footer badge [0.48ms]
✓ /usage with no configured models notifies instead of throwing [0.34ms]
✓ session_start on a cleanly-idle Auto session leaves the placeholder selected [0.43ms]
✓ a brand-new session whose defaultModel is auto/auto routes on the first turn with no prior /model pick or session entries [0.54ms]
✓ before_agent_start routes for real even if autoActive's own bookkeeping never saw the placeholder become active [0.41ms]
✓ session_start restored mid-turn (e.g. after a crash) reverts back to the Auto placeholder [0.71ms]

tests/auto-router-health.test.ts:
✓ modelKey joins provider and id [0.16ms]
✓ parseRetryAfterMs reads a numeric seconds header [0.13ms]
✓ parseRetryAfterMs reads an HTTP-date header [0.13ms]
✓ parseRetryAfterMs finds the header regardless of casing [0.11ms]
✓ parseRetryAfterMs returns undefined when the header is missing or unparseable [0.11ms]
✓ applySuccess resets failure state and accumulates usage totals [0.13ms]
✓ applyFailure on 429 honors retry-after when present [0.11ms]
✓ applyFailure on 429 backs off exponentially without retry-after, capped [0.13ms]
✓ applyFailure on 401/403 sets a fixed auth cooldown immediately [0.10ms]
✓ applyFailure on 5xx only cools down after crossing the consecutive-failure threshold [0.15ms]
✓ applyFailure on other 4xx codes eventually applies a generic cooldown [0.11ms]
✓ applyQuotaResult sets a cooldown to the real reset time even without a local 429 [0.11ms]
✓ applyQuotaResult without a reset time falls back to a short default cooldown [0.09ms]
✓ applyQuotaResult with confirmed headroom clears a stale local cooldown outright [0.11ms]
✓ isHealthy/pickHealthy skip models in cooldown and pick the first healthy candidate [0.12ms]
✓ pickHealthy returns undefined once every candidate is in cooldown [0.10ms]
✓ a cooldown clears once its expiry has passed [0.10ms]
✓ AutoRouterHealthStore round-trips model health and classification log through flush/load, without ever persisting prompt content [0.70ms]
✓ AutoRouterHealthStore.load reads a pre-classification-log file (flat model-keyed record) as model health with an empty log [0.21ms]
✓ AutoRouterHealthStore.recordClassification truncates long text and caps the log at the most recent 20 entries [0.22ms]

tests/auto-router-quota.test.ts:
✓ reconcileProviderQuota returns undefined for providers without a known fetcher [0.06ms]
✓ reconcileProviderQuota returns undefined without credentials, never calling fetch [0.14ms]
✓ reconcileProviderQuota(anthropic) skips a raw API key without calling fetch [0.04ms]
✓ reconcileProviderQuota(anthropic) reports exhaustion from an OAuth token's usage window [0.22ms]
✓ reconcileProviderQuota(anthropic) reports headroom, with the most-used window as detail [0.05ms]
✓ reconcileProviderQuota(openai-codex) returns undefined without a discoverable account id [0.21ms]
✓ reconcileProviderQuota(openai-codex) reports exhaustion when the spend cap is reached [0.06ms]
✓ reconcileProviderQuota(openai-codex) applies the account-wide rate_limit.limit_reached flag only to models without their own additional_rate_limits entry [0.12ms]
✓ reconcileProviderQuota(openai-codex) reports exhaustion when a rate-limit window is depleted (percent_left) [0.04ms]
✓ reconcileProviderQuota(openai-codex) reports exhaustion when a rate-limit window is depleted (used_percent) [0.03ms]
✓ reconcileProviderQuota(openai-codex) reports headroom when used_percent is low [0.03ms]
✓ reconcileProviderQuota(openai-codex) can report a model exhausted independently of a healthy account [0.05ms]
✓ reconcileProviderQuota(zai) reports exhaustion from a TOKENS_LIMIT entry [0.13ms]
✓ reconcileProviderQuota(zai) also reports usage from a CREDIT_LIMIT entry (a different plan tier's shape) [0.05ms]
✓ reconcileProviderQuota(kimi-coding) reports exhaustion when used reaches the weekly limit [0.09ms]
✓ reconcileProviderQuota(minimax) labels the short window from its real start/end times (observed 5h), not a vague 'interval' [0.15ms]
✓ reconcileProviderQuota(minimax) still derives the right window duration when start/end times are epoch seconds, not milliseconds [0.04ms]
✓ reconcileProviderQuota(minimax) falls back to 'interval' when start/end times aren't present [0.04ms]
✓ reconcileProviderQuota(minimax) reports exhaustion when the interval bucket is depleted [0.04ms]
✓ reconcileProviderQuota(minimax) reports exhaustion when only the weekly bucket is depleted [0.03ms]
✓ reconcileProviderQuota(minimax) degrades gracefully when the CLI is missing or not logged in [0.03ms]
✓ reconcileProviderQuota(minimax) degrades gracefully on unparseable CLI output [0.06ms]
✓ reconcileProviderQuota(opencode-go) reports every window's usage together, not just whichever has the highest percentage [0.05ms]
✓ reconcileProviderQuota(opencode-go) reports exhaustion when a window's percent crosses the threshold [0.03ms]
✓ reconcileProviderQuota(opencode-go) reports exhaustion from a non-ok status even at low percent [0.04ms]
✓ reconcileProviderQuota(opencode-go) uses the blocked window's own resetsAt even when a healthy window has a higher percentage, while still showing both in detail [0.04ms]
✓ reconcileProviderQuota(opencode-go) returns undefined without credentials, never calling fetch [0.02ms]
✓ reconcileProviderQuota degrades gracefully on network/HTTP failure [0.05ms]
✓ reconcileProviderQuota degrades gracefully when the fetcher throws [0.07ms]

tests/auto-router-settings.test.ts:
✓ parseAutoRouterSettings accepts a well-formed config [0.06ms]
✓ parseAutoRouterSettings drops unknown effort levels [0.03ms]
✓ parseAutoRouterSettings drops malformed model refs and empty tiers [0.04ms]
✓ parseAutoRouterSettings accepts a per-model effort override, independent of the tier it's listed under [0.02ms]
✓ parseAutoRouterSettings drops an invalid effort override rather than the whole model entry [0.01ms]
✓ parseAutoRouterSettings tolerates garbage input [0.01ms]
✓ resolveEffortTier returns the level itself when configured [0.01ms]
✓ resolveEffortTier falls back toward medium when the classified level is unconfigured [0.01ms]
✓ resolveEffortTier never overshoots past medium to a configured higher tier
✓ escalationTiers walks strictly upward through configured tiers only [0.01ms]
✓ escalationTiers skips tiers below fromTier even if configured [0.03ms]
✓ allConfiguredModels lists every model in tier order, deduplicated [0.03ms]
✓ auto router settings write preserves unrelated Pi and extension keys [1.42ms]
✓ concurrent auto router settings updates remain valid and retain unrelated keys [22.64ms]
✓ malformed settings reject without leaking the cross-process lock [0.98ms]
✓ ensureAutoModelScoped adds Auto's pattern when scoping is configured [0.58ms]
✓ ensureAutoModelScoped is a no-op when Auto's pattern is already present [0.46ms]
✓ ensureAutoModelScoped is a no-op when no scoping is configured [0.36ms]
✓ ensureAutoModelScoped is a no-op when enabledModels is an empty array [0.40ms]
✓ ensureAutoModelScoped preserves unrelated keys [0.53ms]

tests/delete-session-extension.test.ts:
✓ delete-session rechecks managed worktree ownership after confirmation [1.14ms]

tests/subagents.test.ts:
✓ subagent entrypoint preserves its tool, command, and lifecycle registrations [0.22ms]
✓ compact formatting handles non-JSON values and preserves Unicode code points [0.11ms]
✓ subagent tool output truncates at a valid UTF-8 byte boundary [0.62ms]
✓ subagent footer editor preserves key-release preferences [0.04ms]
✓ subagent detail rendering caches wrapped transcript lines [36.28ms]
✓ creating agents reserve capacity before their session exists [0.05ms]
✓ aborting the main run aborts every running subagent and leaves completed agents alone [0.22ms]
✓ terminal provider errors are classified as failures [0.02ms]
✓ terminal cleanup classification preserves live subagents and archives unread output [0.07ms]
✓ available subagent models honor a configured scope [0.08ms]
✓ inherited subagents reuse headers-only temporary host authentication [5.25ms]
✓ subagent model guidance exposes exact choices and inheritance [0.11ms]
✓ subagent read returns a concise completion summary and auto-releases terminal agents [0.45ms]
✓ subagent read includes transcript only when requested [0.18ms]
✓ persisted usage checkpoints reject malformed data [0.07ms]

tests/tailscale.test.ts:
✓ Tailscale web publishing > is opt-in and defaults to HTTPS 8443 [0.10ms]
✓ Tailscale web publishing > publishes the localhost server with background Tailscale Serve [0.28ms]
✓ Tailscale web publishing > removes the configured Pi route immediately when disabled [0.10ms]
✓ Tailscale web publishing > publishes a named service at its service MagicDNS name [0.06ms]
✓ Tailscale web publishing > replaces a previous Serve identity only after the new route is reachable [0.14ms]
✓ Tailscale web publishing > removes an unreachable replacement without removing the previous route [0.14ms]
✓ Tailscale web publishing > does not claim publication until the HTTPS route is reachable [0.05ms]
✓ Tailscale web publishing > reports disconnected Tailscale without preventing localhost startup [0.04ms]

tests/terminal-output.test.ts:
✓ terminal redraws retain only the final visible progress frame [2.02ms]
✓ terminal output handles carriage returns, erasure, and ordinary ANSI colors [0.23ms]
✓ oversized vertical cursor parameters are bounded [62.54ms]
✓ oversized horizontal cursor parameters and aggregate materialization are bounded [412.46ms]
✓ ordinary command output is unchanged [0.10ms]

tests/web-anchored-position.test.ts:
✓ send menu opens above a bottom mobile anchor inside the visual viewport [0.72ms]
✓ portaled menus clamp to shifted mobile visual viewports [0.02ms]

tests/web-app-viewport.test.ts:
✓ standalone non-keyboard states do not pin to the visual viewport [0.54ms]
✓ standalone viewport pins to the scrolled visual rect while the keyboard is open [0.03ms]
✓ ordinary browsers and pinch zoom follow the visual rect [0.04ms]

tests/web-assistant-message.test.ts:
✓ aborted assistant turns produce a visible stopped notice [0.61ms]
✓ failed agent ends are distinguishable from successful idle settlement [0.06ms]

tests/web-client-api.test.ts:
✓ one-shot commands fall back across an older daemon protocol [0.09ms]
✓ clone and fork outlive the server's 30-second operation bound [0.05ms]
✓ worktree capability is refreshed for each create request [0.39ms]
✓ command capability is refreshed and a close during connect rejects immediately [0.63ms]

tests/web-file-presence.test.ts:
✓ missing-path reconciliation requires an explicit ENOENT [0.70ms]
✓ path probing distinguishes present and absent files [0.41ms]

tests/web-history.test.ts:
✓ web history drops entries before the latest compaction boundary [0.40ms]
✓ web history reserves space for the compaction summary [0.07ms]
✓ web history remains byte bounded and truncates oversized content [1.51ms]
✓ oversized history images become explicit omission markers [0.90ms]
✓ aggregate image size cannot drop an otherwise valid prompt [0.09ms]
✓ managed context messages become bounded semantic history [0.13ms]

tests/web-http-utils.test.ts:
✓ JSON responses preserve every HeadersInit representation [0.86ms]
✓ static assets reject malformed paths and never serve directories [0.79ms]

tests/web-image-payload.test.ts:
✓ complete prompt frames keep text and multiple images below the websocket limit [136.69ms]
✓ prompt frame accounting uses exact encoded JSON bytes without serializing the frame [56.24ms]

tests/web-local-command.test.ts:
✓ local compact commands survive authoritative history replacement in timestamp order [0.82ms]
✓ local command reconciliation does not duplicate an incoming command [0.02ms]

tests/web-managed-session-store.test.ts:
✓ managed web session ownership survives restart and follows replacement files [0.80ms]
✓ malformed managed session ownership fails closed [0.27ms]

tests/web-paths.test.ts:
✓ web cwd resolution expands shell-style home shortcuts [0.06ms]
✓ web cwd resolution keeps absolute paths and resolves relative paths from the server root [0.02ms]
✓ web cwd resolution rejects unsupported named-user shortcuts [0.03ms]

tests/web-prompts.test.ts:
✓ native web prompts expand Pi prompt templates with quoted arguments and defaults [4.24ms]
✓ prompt arguments preserve empty and escaped quoted values [0.35ms]
✓ web reload routing only accepts the exact built-in command [0.03ms]
✓ web compact routing accepts optional instructions without matching prose [0.04ms]
✓ the web slash menu exposes control commands across stale native metadata [0.06ms]
✓ web skill commands stay intact with their arguments [0.18ms]

tests/web-queue-delivery.test.ts:
✓ queued delivery failures back off and reach a bounded discard disposition [0.06ms]
✓ pre-delivery persistence publishes bounded retries and gates exactly-once delivery [0.20ms]
✓ pre-delivery persistence retry is bounded [0.11ms]
✓ dirty snapshot worker keeps one retry loop during permanent failure [0.24ms]
✓ dirty snapshot worker coalesces multiple accepted removals without losing later state [0.09ms]
✓ dirty snapshot worker restarts when markDirty interleaves with finalization [0.06ms]
✓ dirty snapshot worker performs one bounded final shutdown snapshot [1.26ms]
✓ dirty snapshot worker cancellation drains an in-flight old-identity write [0.10ms]
✓ dirty snapshot worker cancellation interrupts a custom delay [0.09ms]

tests/web-queue-mutation.test.ts:
✓ session queue mutations are deterministic and serialized [0.17ms]
✓ queue mutation quiescing drains admitted work and rejects late intake [0.38ms]
✓ armed queue retry is cancelled during migration and resumed only after intake reopens [0.06ms]
✓ unarmed queue retry is not spuriously scheduled after migration [0.02ms]
✓ exhausted discard persistence failure restores the uncertain snapshot [0.15ms]
✓ failed persistence rolls back only its serialized mutation [0.11ms]
✓ background accepted-removal retry does not retain the mutation tail [0.07ms]

tests/web-queue-store.test.ts:
✓ web follow-up queues survive daemon restarts with attachments [0.72ms]
✓ daemon restart preserves an in-flight item as blocked instead of redelivering or erasing it [0.30ms]
✓ a missing queue store starts empty, but malformed JSON fails closed [0.27ms]
✓ queue store corruption is propagated instead of dropping malformed entries [0.16ms]
✓ shared queue-map mutations roll back globally before a later session snapshots [3.20ms]
✓ failed atomic queue-store renames clean up temporary files [2.13ms]
✓ coalesced queue writes durably preserve the newest snapshot [2.90ms]

tests/web-recent-repositories.test.ts:
✓ recent repositories deduplicate worktrees and use the primary checkout path [0.14ms]

tests/web-refresh-policy.test.ts:
✓ foreground managed refresh propagates migration failures [0.15ms]
✓ complete managed refreshes serialize per record across foreground and background callers [0.13ms]
✓ failed foreground refresh does not poison serialized background refresh [0.08ms]
✓ background managed refresh suppresses failures only when explicitly requested [0.08ms]

tests/web-scroll-follow.test.ts:
✓ layout growth while following stays pinned without showing the down arrow [0.03ms]
✓ an explicit user scroll away from the bottom remains unfollowed
✓ an unpinned reader near the bottom remains frozen during layout changes
✓ passive layout changes cannot resume while the down arrow is visible
✓ explicit downward input at the actual bottom resumes following
✓ anchor correction offsets only visual movement caused above the viewport [0.03ms]

tests/web-semantic-history.test.ts:
✓ reconnect history ignores regenerated wrapper metadata [0.15ms]

tests/web-serialized-writer.test.ts:
✓ prompts and compaction remain uncertain after transport acknowledgement loss [0.05ms]
✓ serialized writes acknowledge completion and preserve order [0.19ms]
✓ serialized writes skip queued work that expires before delivery [0.08ms]
✓ serialized writes report a delivery failure and recover for later writes [0.07ms]

tests/web-server.test.ts:
✓ session ordering defaults to creation time and accepts persistent manual moves [0.22ms]
✓ queued follow-ups can move into any insertion slot without losing attachments [0.12ms]
✓ linked Git worktrees resolve to the same sidebar project [19.05ms]
✓ Git projects expose checkout roots for submodules and bare repositories [27.62ms]
✓ Bun web server keeps tokenless clients inside localhost and same-origin trust boundaries [2492.44ms]
✓ TUI metadata changes update every connected web catalog [2177.49ms]
✓ managed sessions are published only after their runtime identity is final [2438.85ms]
✓ Stop acknowledges delivery without waiting for agent teardown [2055.81ms]
✓ an idle native session flushes its restored web follow-up queue on hello [2048.58ms]
✓ a visible client can resynchronize a durable queue after a missed reconnect update [2062.48ms]
✓ restored uncertain delivery is never automatic and requires explicit reconciliation [2160.37ms]
✓ resuming a selected saved session keeps its existing client subscription [2185.90ms]
✓ managed RPC requests fail within the configured bound when Pi wedges [2089.19ms]
✓ sessions deleted from the TUI are removed from the live web catalog [3039.46ms]
✓ transient session-file access errors do not reconcile as deletion [0.43ms]
✓ saved-session metadata refresh clears hydrated ownership and skips malformed markers [2010.95ms]
✓ failed durable queue deletion leaves the session file and record retryable [2057.73ms]
✓ deleting a saved managed-worktree session removes its checkout and branch [2023.72ms]
✓ worktree cleanup failure does not turn a completed session deletion into an error [2083.86ms]
✓ daemon restart completes a staged managed worktree source deletion [2026.59ms]
✓ managed startup failure retains an initialized worktree and setup output [2052.29ms]
✓ native sessions route the web /compact command with optional instructions [2057.21ms]
✓ web reload survives a native bridge reconnect [2056.93ms]
✓ queued web reload waits for active subagents and executes as a control command [2104.62ms]
✓ native replacement recovery redirects a queue-free source session [2081.25ms]
✓ native worktree switches survive the replacement bridge reconnect [3255.42ms]
✓ native sessions expose queued-delivery ordering and context compaction lifecycle [2904.71ms]
✓ browser sessions stay managed and idle across daemon restarts [4991.29ms]
✓ native Pi sessions expose semantic history without replacing their physical TUI [3486.84ms]

tests/web-session-file-catalog.test.ts:
✓ metadata cache results have fresh arrays and current ownership [1.46ms]
✓ deletion reads worktree ownership from a bounded session prefix [0.71ms]

tests/web-session-lifecycle.test.ts:
✓ stale working updates preserve terminal failures until real activity starts [0.68ms]

tests/web-session-queue-coordinator.test.ts:
✓ settle fallback advances queues when agent activity is initially unknown [156.36ms]
✓ uncertain deliveries block steering and all are reported on subscribe [2.46ms]
✓ reordering an uncertain delivery behind an ordinary item still blocks flushing [3.35ms]
✓ queued control commands cannot be converted into steering prompts [0.42ms]
✓ settled failures advance queues without erasing the visible error [1.70ms]
✓ resubmit retry is cancellable and cannot flush a stale record [26.13ms]

tests/web-session-status.test.ts:
✓ offline sessions are labeled inactive regardless of their origin [3.59ms]
✓ active subagents keep an idle main session working and stoppable [0.33ms]

tests/web-session-telemetry.test.ts:
✓ session telemetry survives partial and transient zero-valued snapshots [4.75ms]
✓ an explicit empty subagent snapshot clears stale managed-runtime telemetry [0.25ms]
✓ new authoritative telemetry replaces the preserved snapshot per session [0.36ms]
✓ subagent transcript and streaming deltas merge without retransmitting prior content [0.71ms]

tests/web-sessions-extension.test.ts:
✓ web Stop aborts the main session and waits for subagent propagation [0.43ms]
✓ web Stop reports a synchronous main-session abort failure before acknowledgement [0.11ms]
✓ web Stop still aborts when an optional subagent listener fails [0.12ms]
✓ the bridge retains an authoritative subagent snapshot for reconnect hello [0.32ms]
✓ web worktree forwarding preserves spaces in quoted arguments [0.26ms]
✓ web model selection honors the session model scope [0.14ms]
✓ Tailscale setting persistence failure rolls the live route back [0.49ms]
✓ Tailscale setting transaction reports a failed route rollback [0.24ms]

tests/web-settings.test.ts:
✓ web settings writes preserve unrelated Pi and extension keys [2.44ms]
✓ concurrent web settings updates remain valid and retain unrelated keys [25.83ms]
✓ malformed settings reject without leaking the cross-process lock [3.11ms]
✓ write and rename failures clean temporary files and always release the lock [1.25ms]

tests/web-shutdown-policy.test.ts:
✓ graceful shutdown waits without a deadline until managed work settles [1.43ms]
✓ shutdown draining keeps Stop available while rejecting new work [0.09ms]
✓ graceful daemon shutdown waits for managed main-agent work [0.05ms]
✓ graceful daemon shutdown waits for active managed subagents [0.05ms]
✓ idle, stopped, and external sessions do not delay daemon shutdown [0.02ms]

tests/web-slash-command-service.test.ts:
✓ slash command discovery deduplicates concurrent runtime starts [2.97ms]
✓ invalidation isolates overlapping slash command discoveries [0.40ms]
✓ slash command discovery is bounded and shutdown cannot mask the timeout [13.02ms]
✓ slash command projection reuses the compact fallback [0.27ms]

tests/web-suggestions.test.ts:
✓ directory suggestions list home directories in ~ shorthand [7.84ms]
✓ directory suggestions stop inside a Git repository [6.54ms]
✓ directory suggestions keep absolute form outside home [2.94ms]
✓ repository branches list local and remote refs [11.57ms]
✓ branch listing fails outside a Git repository [0.71ms]
✓ web server serves directory and branch suggestions [2057.41ms]

tests/web-tool-expansion.test.ts:
✓ Write and Edit cards auto-expand from their rendered arguments without tool output [0.08ms]

tests/web-usage.test.ts:
✓ subagent header usage equals the cumulative usage displayed by its rows [0.08ms]

tests/web-worktrees.test.ts:
✓ checkout-producing Git commands use the long-running operation bound [0.05ms]
✓ web worktrees are created under the primary repository .pi directory [35.65ms]
Deleted branch tembo/cancel-builds (was f1e4d62).
✓ managed directory, local branch, remote start point, and upstream are independent [31.86ms]
✓ an unused existing local branch is reused and preserved by rollback [17.93ms]
✓ managed creation rejects branches checked out in another worktree [11.17ms]
✓ invalid branches and missing start points fail before creating resources [7.79ms]
✓ existing registered worktrees are resolved without modifying their branch [8.69ms]
✓ existing detached worktrees retain their exact HEAD [8.20ms]
✓ existing targets reject missing paths, unregistered checkouts, and other repositories [9.25ms]
✓ executable worktree setup scripts run through their shebang [8.84ms]
✓ failed setup retains the new checkout and branch for inspection [9.11ms]
✓ worktree setup is terminated after its bounded timeout [52.08ms]
✓ worktree setup force-kills descendants after a timed-out leader exits [2119.49ms]
✓ async managed cleanup yields before spawning Git verification [2049.05ms]
✓ managed worktree metadata is parsed and cleanup removes its checkout and branch [36.63ms]
✓ reused sessions inherit branch ownership when the original owner is deleted first [27.78ms]
✓ worktree cleanup waits until the final saved session is deleted [3.89ms]
✓ worktree rollback removes the checkout and its branch [15.08ms]
✓ rollback removes operation-owned resources after the checkout branch changes [14.25ms]
✓ rollback preserves commits made after a managed branch was created [14.64ms]
✓ rollback preserves a changed checkout with uncommitted files [11.06ms]
✓ worktree handling requires Git 2.36 or newer [0.09ms]
✓ web worktree names reject traversal and nested paths [1.25ms]

tests/worktree-extension.test.ts:
✓ the queued internal worktree message dispatches an extension command on Pi 0.84 [13.02ms]
✓ worktree tool queues a correlated create command and terminates the old run [0.74ms]
✓ worktree tool queues the existing-worktree form without shell interpolation [0.06ms]
✓ worktree tool correlation is session-bound, concurrent, and cleaned on failure [0.13ms]
✓ worktree replacement is single-flight per source session [0.15ms]
✓ worktree command parses safe quoted repository arguments [0.17ms]
✓ quoted relative and root-relative Windows paths preserve separators [0.11ms]
✓ double-quoted JSON control escapes decode after spaces [0.03ms]
✓ formatted branch and start-point values round-trip without command interpolation [0.06ms]
✓ tool-quoted worktree paths preserve JSON control escapes [0.04ms]
✓ worktree command parses existing checkout paths exclusively [0.78ms]
✓ branched replacements are self-contained before the source is deleted [3.39ms]
✓ existing worktree migration activates the replacement and rollback restores the source [3.27ms]
✓ verified replacement deletes its source session and records a durable tombstone [22.32ms]
✓ an independently open source session rolls back and preserves the original [7.41ms]
✓ failed replacement-file cleanup retains the managed worktree CWD [0.93ms]
✓ existing selection preserves ownership only for the current managed worktree [0.48ms]
✓ worktree invocation only matches the extension command [0.13ms]

 327 pass
 0 fail
 836 expect() calls
Ran 327 tests across 44 files. [65.97s]

[2026-08-17T13:47:40.181Z] [test] finished (success, exit 0)