inferParamBFromIdOrName used a consuming trailing boundary `b(?:[^a-z0-9]|$)`,
so when two `<num>b` parameter tokens are separated by a single delimiter
("8b 70b", "8b-70b"), the first match ate the shared delimiter and the second
token's required leading boundary had nothing to match, silently skipping it —
returning the first (often smaller) size instead of the largest. Make the
trailing boundary a non-consuming lookahead.
Co-authored-by: ly-wang19 <ly-wang19@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(llm): collapse cumulative openai-responses message snapshots instead of concatenating
Some openai-responses providers (observed: Bedrock Mantle with GPT-5.x
reasoning enabled, confirmed server-side via raw curl) re-emit the
assistant message as many cumulative snapshot items — each a
prefix-superset of the previous one — instead of a single final message
item. Both stream consumers appended one text block per item, so the
final visible reply, transcript, and replay context repeated the answer
once per snapshot (observed 49-80x).
Treat a same-phase message item whose text extends the immediately
preceding text block as a replacement: the prior block takes the longer
text, the duplicate block is dropped, and the first item's signature is
kept so replay and stream-item identity stay stable. Shrinking or
identical adjacent snapshots are dropped. Any non-message output item
(reasoning, tool call) is a real boundary that resets the collapse, so
distinct post-tool messages and reasoning replay pairing are untouched,
as are different-phase (commentary/final_answer) items. Applies to the
agent transport stream, the shared LLM consumer, and completed-response
backfill.
Fixes#91959. Reported by @phoenixyy with server-side evidence from
@DaiMingNJ.
* test(llm): drop redundant stream drains from responses snapshot tests
* fix(llm): collapse only strict snapshot extensions and keep newest item signature
Address ClawSweeper P1 review findings on #92399: text-prefix relation
alone was broader than the observed corruption. Equal or shrinking
adjacent same-phase message items are now always kept as distinct blocks
(the Responses protocol allows multiple message items per response —
verified against the sibling Codex parser, codex-rs/codex-api/src/sse/
responses.rs, which emits every output_item.done message as an
independent item). With extension-only collapse a false positive can
only merge rendering of two messages; it can never remove text.
The merged block now carries the newest item's signature instead of the
first one's, so replay associates the final content with the item that
actually produced it.
* fix(llm): defer snapshot-candidate message blocks to keep the event lifecycle balanced
Address the remaining ClawSweeper P1 on #92399: collapsing a snapshot
used to pop a block whose text_start had already been emitted, leaving
per-index stream subscribers tracking a phantom block.
A message item that follows a finalized text block now defers its public
block: no text_start is emitted and deltas are withheld until the item
either diverges from the prior text (then the block opens and the
withheld prefix replays as one delta) or completes. A collapsed snapshot
therefore never starts a block — it only re-ends the prior index with
grown content, the documented resend shape — and a distinct deferred
item opens and closes its own block normally. No block is ever removed,
so every text_start has exactly one matching text_end at a live index.
Tests now assert the complete ordered event sequence for the collapse,
distinct-item, and divergence cases in both consumers.
* fix(llm): treat any non-message item as a collapse boundary in completed-response backfill
The streaming consumer resets the snapshot-collapse anchor on every
non-message output item ("any other item is a real boundary"), but the
transport's completed-response backfill only dispatched message and
function_call items, so a reasoning item between two strict-prefix
message items did not reset the anchor and the later message could
collapse across it — an asymmetry with the streaming path's documented
invariant. Reset lastTextBlock for every non-message item in the backfill
loop (one canonical place; the per-tool-call reset is now redundant and
removed). Covered by a backfill reasoning-boundary regression test.
normalizeRoleList in src/shared/device-pairing-access.ts called .trim() on every roles[] entry and the singular role without a typeof === "string" guard, so a malformed/legacy on-disk pairing record (roles/role loaded via blind-cast JSON in coercePairingStateRecord) threw "TypeError: role.trim is not a function" and crashed resolvePendingDeviceApprovalState -- and thus `openclaw devices list`, which calls it per pending request with no try/catch.
Route each item through the shared non-string-safe normalizer normalizeUniqueSingleOrTrimmedStringList, mirroring the #90654/#92178 fix that already guarded the sibling mergeRoles/mergeScopes (src/infra/device-pairing.ts) and the in-file scopes path (normalizeDeviceAuthScopes). Non-string entries are dropped; valid roles are still trimmed, deduped, and sorted. Net -10 LOC.
Co-authored-by: ly-wang19 <ly-wang19@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(reasoning-tags): strip MiniMax `mm:` namespaced reasoning tags
MiniMax M3 (e.g. via Fireworks) emits its chain-of-thought inline in the
content stream wrapped in `<mm:think>…</mm:think>` rather than in a separate
`reasoning_content` field. The reasoning-tag stripper only recognized the
`antml:` namespace, so `mm:`-namespaced tags slipped through QUICK_TAG_RE and
leaked the model's hidden reasoning into visible chat output.
Accept the `mm:` prefix alongside `antml:` in the shared sanitizer
(reasoning-tags.ts) and in the Telegram reasoning-lane coordinator's tag regex
and prefix list. Adds unit tests covering mm: think/thinking/thought blocks,
truncated-open orphan close recovery, and code-fence preservation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(reasoning): handle MiniMax tags in streams
---------
Co-authored-by: DrHack1 <DrHack1@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
Adds broad inline comments and JSDoc for CLI, cron, outbound/channel, plugin SDK, ACP, shared helpers, net policy, and related utility contracts. Proof: git diff --check on latest exact head plus focused cron tests passed; CI had no failing checks observed before merge attempt.
Roll both subagent token usage formatters over to the million unit when rounded thousands reach the next unit.
The original fix covers `formatTokenShort`, which feeds the subagent list usage line. The maintainer follow-up applies the same unit-boundary rule to compact subagent announcement stats, preserving that formatter's one-decimal style while preventing `1000.0k` output.
Verification:
- focused runtime probe for list and compact announce stats at 999,999 tokens
- `oxfmt --check` on touched formatter/test files
- `git diff --check origin/main..HEAD`
- `node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test-pr88209.tsbuildinfo`
- autoreview local closeout clean
- exact-head CI passed for Real behavior proof, check-test-types, check-prod-types, check-guards, security-fast, and preflight
Known unrelated current-main reds at merge: `check-lint`, `checks-node-agentic-gateway-methods`, and `checks-node-agentic-control-plane-agent-chat`.
Co-authored-by: coder999999999 <coder999999999@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Adds optional `gateway.tailscale.serviceName` support for Tailscale Serve so the Gateway Control UI can be exposed through a named Tailscale Service while existing hostname-based Serve and Funnel behavior stays unchanged.
The implementation validates `svc:<dns-label>`, passes the Service name to `tailscale serve`, clears named Service config with `tailscale serve clear <service>` when resetOnExit runs, and uses the derived Service hostname in startup logs, status output, and pairing URLs.
Verification:
- node scripts/run-vitest.mjs src/infra/tailscale.test.ts src/gateway/server-tailscale.test.ts src/config/config.gateway-tailscale-bind.test.ts src/gateway/startup-auth.test.ts src/commands/status.scan.shared.test.ts src/pairing/setup-code.test.ts
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main --parallel-tests "node scripts/run-vitest.mjs src/infra/tailscale.test.ts src/gateway/server-tailscale.test.ts src/config/config.gateway-tailscale-bind.test.ts src/gateway/startup-auth.test.ts src/commands/status.scan.shared.test.ts src/pairing/setup-code.test.ts"
- git diff --check
- git merge-tree --write-tree origin/main origin/pr/88691
Closes#88629.
Co-authored-by: Charles OpenClaw <charles-openclaw@9bcfae.inboxapi.ai>