Persist qualified QQBot group and guild routes so session announcements return to the originating conversation without letting direct-message turns overwrite the shared route.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: lzyyzznl <lzyyzznl@users.noreply.github.com>
* fix(browser): retire durable tab rows whose browser never returns
Durable cleanup defers whenever ownership cannot be proven, so a browser
that never comes back at the same cdpUrl leaves its rows behind forever:
each sweep re-claims them, fails the identity lookup, warns, and defers
again. Nothing in the subsystem removes a row by age.
The `browser.session-tabs` namespace is opened with a 5000-row cap and
`reject-new`, so once those rows accumulate to the cap, tracking a new tab
throws PLUGIN_STATE_LIMIT_EXCEEDED. That propagates into the compensation
path in browser-tool-session-tabs.ts, which closes the tab the user just
opened and rethrows -- every `browser open` on that profile then opens a
tab, closes it again, and errors, with no self-healing path.
Bound the retry: when a close attempt reports the target unavailable and
the tab has been unused for longer than the retire window, drop the row
instead of deferring again. A browser returning after that long almost
always carries a fresh instance fingerprint, which retires the row through
the ownership-mismatch path anyway.
closeTrackedBrowserTabsForSessions now accepts `now` like the sweep does,
so lifecycle cleanup can be exercised on a coherent clock.
* refactor(browser): split session tab cleanup claim and test harness
check-lint failed on max-lines: session-tab-registry.ts was at 699 of its
700-line budget and the durable registry test at 982 of 1000, so the retire
branch and its regression test pushed both over.
Extract the cleanup claim bookkeeping (claim, ownership match, delete) into
session-tab-cleanup-claim.ts, and the durable registry test shapes into
session-tab-registry.sqlite.test-helpers.ts, matching the existing
*.test-helpers.ts convention in this directory. No behavior change.
* test(browser): protect unreachable retirement races
Co-authored-by: Yigtwxx <yigiterdogan023@gmail.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(qa-lab): reject hex/exponent Telegram SUT uid env values
* test(qa-lab): add direct unit tests for parseSutId strict int parsing
Export parseSutId and add 15 direct unit tests covering hex/exponent/fraction/
empty/whitespace/zero/negative rejection and valid decimal acceptance across
UID, GID, and CLEANUP_TIMEOUT_MS keys. The 3 existing integration tests
already prove the full CLI chain rejects malformed UIDs before gateway startup.
Mutation check: reverting to permissive Number() parsing causes 6 tests to
fail (4 unit + 2 integration).
Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>
* test(qa-lab): cover GID and cleanup-timeout CLI reject-before-gateway
* fix(qa-lab): drop stale live-scenario mocks from SUT uid tests
* style(qa-lab): oxfmt import order in telegram cli.runtime
* test(qa-lab): streamline strict SUT integer coverage
---------
Co-authored-by: nebulacoder-v8.0 <noreply@zte.com.cn>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Preserve existing lower-level high effort while making xhigh/max available before runtime activation.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(anthropic): accept "cli" entrypoint in Claude session catalog discovery
Claude Code v2.x writes entrypoint: "cli" in its JSONL session files.
Previously only "sdk-cli" was recognized, causing all v2.x sessions to
be silently skipped.
Extract CLI_ENTRYPOINTS set and isCliEntrypoint() helper, then apply
the same fix to both TypeScript catalog and macOS paired-node native
catalog. Add matching regression coverage on both platforms.
Closes#105164
* fix(anthropic): refresh Claude CLI session discovery
Apply the reviewed cli/sdk-cli allowlist, mirrored negative coverage, and fallback-scope documentation to current main.
Co-authored-by: 黄攀0668000858 <huang.pan@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(cua-computer): add experimental Windows/Linux computer-use fulfiller
Bundled plugin that fulfills the capability-based computer.act + screen.snapshot
node contract on Windows and Linux by supervising a pinned cua-driver 0.10.x
daemon over MCP stdio. macOS keeps the Peekaboo fulfiller; this plugin is
disabled by default and never available on darwin.
Grounded in cua-driver 0.10.0 source (tool schemas, refusal codes, coordinate
spaces, session/daemon lifecycle). Notable safety and correctness properties:
- Deny-by-default env allowlist so OpenClaw secrets (provider/channel tokens,
CUA_API_KEY) never reach the separately installed daemon; telemetry and
update checks forced off.
- Version-gated handshake (exact-minor pin + capability/schema version),
time-bounded so a corrected driver recovers without a node restart.
- Robust daemon supervision: full readiness-budget polling, startup-race
tolerance, signal-death and spawn-error recovery, shared-daemon lifecycle
(never killed on dispose).
- Frame authorization preserved within upstream limits (generation + full live
geometry; capture refused when screen and screenshot geometry diverge).
- Action mapping refuses inputs cua-driver cannot faithfully deliver:
layout-shifted keys, modifier-held drag/scroll, Linux modifier clicks,
hold_key/mouse down-up, non-positive scroll; drag duration clamped.
* fix(cua-computer): satisfy lint, test-types, dead-code, and docs-map gates
* refactor(media): ingress and preflight consumers ride ordered media facts
Media-facts program PR 2: channel context retains ordered MediaFact[]
through finalization; inbound gates, hook mapping, current-turn
normalization, and all five channel families' audio preflights consume
facts. Four review cycles hardened the canonical/legacy merge: canonical
facts are authoritative (legacy fills gaps only), one shared staging
predicate covers MediaStaged and MediaWorkspaceDir, kind inference runs
after fallback MIME fills, legacy counts use max array cardinality, and
alignment-only empty slots no longer count as media presence — all
pinned by a 54-cell canonical×legacy×type merge-matrix test.
* fix(media): hide internal fact helper
* fix(ollama): bound DNS preflight with guard-owned request timeouts
Five ollama call sites passed their deadline as init.signal, which
fetchWithSsrFGuard only forwards to the final fetch. The DNS/proxy
preflight and dispatcher connect timeout read the top-level timeoutMs,
so a stalled lookup hung past the intended 3s/5s/15s deadline.
Move each deadline to the guard-owned timeoutMs. Values unchanged.
* test(ollama): assert preflight lookup ran and deadline bound both ways
Use plain counters instead of vi.fn for the injected lookup: vi.fn is not
assignable to the overloaded LookupFn type, which passed vitest but failed
the extension test-types lane.
Preserve the shipped default identity while hashing custom header identity without retaining raw values.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(approvals): handle stale plugin waits
Recognize the gateway stale-id message through the shared classifier and map stale exec, Codex app-server, and native PermissionRequest waits to their existing fail-closed or defer contracts without swallowing aborts.
Closes#88111
Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
* style(approvals): type stale wait catch values
Satisfy the catch-callback unknown lint without changing approval behavior.
Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(feishu): normalize media upload multipart data
Convert Feishu SDK multipart Buffer upload parts into explicit FormData
before they reach the wrapped HTTP transport. The SDK upload helpers
pass multipart data as a plain object with Buffer media parts; relying on
implicit serialization in the timeout/proxy-aware HTTP wrapper was fragile
and caused file/image delivery failures (400 volc-dcdn / write ECONNRESET)
even though the same credentials succeeded via a standalone SDK upload.
Adds normalizeMultipartUploadData, applied on the request path of the
shared Feishu HTTP instance, plus a regression test covering the
multipart-to-FormData normalization.
Rebased onto current origin/main, which replaced the old synchronous
injectTimeout path with the async proxy-aware injectRequestOptions flow;
the multipart normalization is now applied before that flow.
* fix(feishu): scope multipart media normalization
* test(feishu): mark multipart auth fixture synthetic
* test(feishu): use field-shaped secret fixtures
* fix(feishu): validate multipart upload endpoints
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor(ai): invert plugin coupling behind the transport host port
* fix(ai): queue custom transport registrations until the host is configured
* refactor(ai): remove relocated transport sources from src/agents
* fix(ai): source core stream types from canonical packages and fix tarball fixtures
* fix(ai): invert plugin transport host wiring
* fix(ai): harden managed transport projection
* test(ai): register synchronous stream in transport mock
* fix(ai): lazily install transport runtime host
* fix(ai): preserve completion compat detection
* fix(anthropic): complete transcript reverse-scan windows across short reads
readLocalClaudeTranscriptPage filled each reverse-scan window with a single
positional read and threw "Claude transcript changed while it was being read"
whenever bytesRead !== size. A positional read may return fewer bytes than
requested inside an unchanged file, so a benign short read failed the transcript
page load with a message implying the file changed. The forward metadata scan in
the same file already loops short reads; the reverse scan did not.
Fill each window across short reads, advancing both the buffer offset and the
file position. A zero-byte read before the window is filled is premature EOF and
still throws the same error.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(anthropic): tighten transcript short-read coverage
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(extensions/anthropic): write migrated model entries as own properties
* test(extensions/anthropic): guard optional models value in Object.hasOwn assertions
---------
Co-authored-by: chengzhichao-xydt <chengzhichao-xydt@users.noreply.github.com>
* fix(feishu): fall back media replies
* test(feishu): polish media fallback coverage
* test(feishu): cover media fallback policy wiring
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(github-copilot): strip encrypted_content from reasoning replay items
* refactor(github-copilot): name replay sanitizer accurately
Use one provider-boundary sanitizer name for both connection-bound IDs and session-bound encrypted reasoning, and assert the final stream payload drops ciphertext.\n\nCo-authored-by: openperf <16864032@qq.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor(media): consolidate parallel media-kind unions onto canonical MediaKind
One canonical MediaKind union (media-core constants) replaces ~40
duplicate/parallel kind declarations across core and channel plugins;
channel-specific narrower contracts derive via Extract/Exclude. Also
fixes a review-caught fallback bug where a stored "unknown" reply-chain
kind preempted MIME inference and relabeled images as documents.
* refactor(ui): derive attachment kinds from MediaKind
* fix(telegram): drop type-dead unknown guard in reply-context kind fallback
* style(telegram): format media kind fallback