The strict knip workspaces landed in #108547 flagged seven files. Two are
convention-resolved and become explicit entries (discord configured-state via
the package-state probes, qa-lab cli via the SDK facade basename resolver);
the other five were dead barrels/wrappers superseded by canonical modules and
are deleted (browser bridge/cdp barrels, discord timeouts wrapper, openai
register.runtime, qa-lab model-selection wrapper).
The deleted text provider's usage hook was the only source for the /status
Codex subscription usage line. Harnesses can now contribute an optional
usage snapshot (provider hooks keep priority; only distinct synthetic hook
owners fall through), and the codex harness reports app-server rate limits
via account/rateLimits/read with the same conversion and account identity
as before. No text provider resurrected; deadcode and SDK surface gates
clean.
- null-vs-undefined runtime record type at the provider-move helper
- gateway models.list fixtures expect the new agentRuntime intent field
(codex/implicit for policy-backed OpenAI routes, openclaw/implicit for
synthetic routes without provider policy)
- split doctor cron legacy-repair into its own module to satisfy the
max-lines gate without suppressions; callers import directly
The live codex text provider was a redundant projection of the openai
catalog (exclusive provider ownership; the openai plugin's ChatGPT OAuth
discovery already serves gpt-5.6-* route-aware). Folding it:
- extensions/codex no longer registers a text provider, catalog entry, or
synthetic text auth; provider.ts/provider-catalog.ts/provider-discovery.ts
and the route-blind model-name heuristics are deleted; the narrow
post-harness reasoning fallback moves to an app-server-owned module
- openai thinking policy keys on explicit selected-route provenance
(api === openai-chatgpt-responses) instead of value-shape inference
- models.list gains an optional additive agentRuntime field (configured
intent); session agentHarnessId remains the execution proof
- doctor --fix migrates the shipped codex/* config shape end to end:
every model slot, provider-config merge with blocker-aware conflict
handling, sessions, cron payloads (two-phase: runtime policy persists
before cron refs rewrite), transcripts; migrated refs carry model-scoped
agentRuntime.id=codex preserving the shipped wizard semantics; auto
runtime policies normalize to codex with sibling fields preserved;
blocked provider conflicts retain the whole legacy namespace fail-closed
with an actionable warning
- the stale openai:default profile cleanup (#91352) was deliberately
deferred to a follow-up after review showed it needs per-agent identity
proofs; doctor keeps warning about unusable profiles
Fixes#105561Fixes#84637Fixes#90420
* fix(line): honor channelData.line.mediaKind on the reply-token path
The reply-token delivery built every media message with createImageMessage,
ignoring channelData.line.mediaKind (and previewImageUrl/durationMs/trackingId),
so a video/audio reply was silently downgraded to a broken image. The push path
already honored mediaKind via resolveLineOutboundMedia + buildLineMediaMessageObject.
Route reply-token media through those same helpers (relocated to outbound-media.ts
and reused by both paths) via an injected buildMediaMessage dep wired in monitor.ts,
preserving the delivery file's dependency-injection boundary. Generic media without
LINE-specific options keeps the image route; a media that cannot be built surfaces
as a visible partial delivery so the text still reaches the user.
* refactor(line): unify reply media delivery
* fix(line): normalize media delivery failures
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(line): use precise control-command check for group mention bypass
Group requireMention bypass is gated on `command.hasControlCommand`, but LINE
fed it the broad `shouldComputeCommandAuthorized` detector, which is true for
any inline "/x"/"!x" token. An allowlisted member's plain message like
"cd /home" would satisfy the bypass and reach the agent even though the bot
was never mentioned.
Groups now use the precise `hasControlCommand` (message starts with a real
command); DMs keep the broad detector since they have no mention gate. This
mirrors googlechat's monitor-access wiring. The group/DM split lives in a
small `resolveLineControlCommand` helper in group-policy.ts, next to
resolveLineGroupRequireMention, so bot-handlers.ts stays within its size cap.
* test(line): assert group mention-bypass with a registered control command
The positive regression case sent `!status`, which the mocked
command-auth module treated as a control command via a naive
`startsWith("!")`. Production `hasControlCommand` matches the message
body against registered command aliases (e.g. `/status`), so `!status`
is only an inline token, not a real control command. The test could
therefore pass without proving that a genuine control command still
bypasses requireMention.
Use the registered `/status` alias for the bypass cases and make the
mock reflect the real split: `hasControlCommand` = starts with a
registered alias (precise); `shouldComputeCommandAuthorized` =
`hasInlineCommandTokens` regex over `/x`/`!x` (broad). The two
detectors are now distinguishable, so the group precise-detector path
is actually exercised.
* refactor(line): centralize command detection
* test(line): complete direct message fixture
* docs(changelog): credit LINE command fix
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor(qa): route live channel selectors through suite host
* fix(qa): repair live selector CI
* fix(qa): remove retired runner exports
* fix(qa): remove obsolete live evidence
* fix(slack): mark connected status degraded without bot identity
When auth.test fails or botToken is a user token (user_id without bot_id),
Socket Mode can still come up. Keep connected=true for transport liveness
but publish healthState=degraded with lastError instead of advertising healthy.
Rebased onto current main; credentials revoked by operator.
* fix(slack): report degraded socket identity health
Co-authored-by: zw-xysk <zhao.wang1@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(synology-chat): settle user_list overflow without hanging
Cap reads at 1 MiB with Buffer concat. On overflow, finish the promise
before destroy() — bare destroy often skips end/error and hung the test.
* fix(synology-chat): share bounded user-list reader
Co-authored-by: zw-xysk <zhao.wang1@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(qa-channel): add timeout to qa-bus state guarded fetch
* test(qa-channel): add executable negative control for bus-state hang
* test(qa-channel): oxfmt negative-control bus-client test
* fix(qa-channel): sync SDK facade type with getQaBusState options parameter
Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>
* test(qa-channel): add standalone production-path proof for bus-state timeout
Proof exercises the real getQaBusState code path against loopback
TCP peers that accept but never return HTTP headers:
- Negative control: fetchWithSsrFGuard without timeoutMs stays pending
- Positive control: getQaBusState with timeout rejects with TimeoutError
- Valid response: normal server still resolves within the timeout floor
Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>
* test(qa-channel): log node version and head SHA in bus-state timeout proof
Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>
* fix(qa-channel): drop test timeout from public Plugin SDK facade type
Keep the `timeoutMs` option internal to `getQaBusState` for test-only
short floors. The public facade callers do not need it and the SDK
surface should not expose test-only parameters.
Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>
* chore(qa-channel): remove standalone proof script per review
The negative, timeout, and responsive cases are already durably covered in
bus-client.test.ts. The executed live output remains in the PR body as
evidence.
Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>
* chore: trigger CI after rebase onto main
* chore: trigger CI after rebase onto main
* test(qa-channel): keep state timeout internal
---------
Co-authored-by: nebulacoder-v8.0 <noreply@zte.com.cn>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>