test#33
Merged
Merged
Conversation
…rs (#1979) Two unrelated try/catch blocks were eating errors with no observable signal, both reachable from production paths: 1) `src/pipeline/template.ts:215` `sanitizeContext` (the JSON round-trip that severs prototype chains before handing pipeline context to the VM sandbox) caught any `JSON.stringify` failure and returned `{}`. The most common cause is a BigInt anywhere in `data` / `args` / `item` / `root` (e.g. GraphQL 64-bit IDs). After collapse, every template expression referencing that branch resolved to `undefined`, producing silent column-drops downstream with no warning. Fix: - Add a JSON.stringify replacer that coerces BigInt to string, so the common BigInt-in-context case survives the sandbox copy. - For everything else (circular references, Symbol, etc.), the fallback is still `{}` but now log.warn so the failure shows up in `~/.opencli` logs and doctor output instead of silently producing blank rows. 2) `src/daemon.ts:445` the WS message handler from the extension caught `JSON.parse` failures and ran the `// Ignore malformed messages` comment. A malformed message presents downstream as a generic command timeout (`pending` never resolves), so the actual protocol drift / version skew between daemon and extension never surfaced in the log. Fix: log.warn the parse error plus the first 200 chars of the offending frame so the root cause is visible during triage. Both changes are observability-only: no successful path changes behavior; only previously-silent failure paths get a log line, plus BigInt now serializes to a string instead of nuking its containing branch. Tests: - `src/pipeline/template.test.ts`: two new cases covering the BigInt preservation path (forces the VM sandbox via `String(args.id)`, not the resolvePath fast path) and the circular-ref no-crash invariant. - daemon WS handler change is log-only; existing daemon tests cover the message dispatch path.
* Add Mercury reimbursement helpers * fix(mercury): fail closed reimbursement drafts * fix(mercury): harden reimbursement draft safety checks --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
…1980) reconcileTargetLeaseRegistry computed each lease's remaining lifetime (stored.idleDeadlineAt - now) but used it only to decide expire-vs-keep; the keep branch called resetWindowIdleTimer(leaseKey), which always schedules a fresh FULL idle timeout, discarding the remaining time. Under MV3 service-worker churn (the SW is evicted/restarted routinely), a lease's idle deadline was refreshed to the full timeout on every restart, so an owned adapter tab/placeholder that should auto-release could linger far past its idle timeout — effectively indefinitely. Add an optional remainingMs override to resetWindowIdleTimer and pass the computed remaining from reconcile, clamped to [0, timeout]. Adds a regression test (5s-remaining lease must schedule a ~5s alarm, not 30s); reverse-validated.
…ach (#1978) A forced detach inside ensureAttached's re-attach loop fires chrome.debugger.onDetach, whose handler deletes the tab's armed networkCaptures state; the detach also disables the CDP Network domain, and re-attach only re-issued Runtime.enable. So any non-navigate command that triggered a re-attach (a stale-attach health-check failure during SPA navigation, or third-party debugger interference) left network-capture-read returning [] even though requests fired — the recorded "0 captures" symptom. Snapshot the capture before the re-attach and, on success, re-enable the Network domain and restore the accumulated state (restored last so it wins over the onDetach handler's delete). Adds a regression test that fails without the restore.
* feat(gemini): add model and thinking selection Co-authored-by: multica-agent <github@multica.ai> * fix(gemini): harden model selection contracts --------- Co-authored-by: coder-SOTA-hm <coder-SOTA-hm@users.noreply.github.com> Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: jackwener <jakevingoo@gmail.com>
Three independent CDP-layer correctness fixes:
1. Redirect wiped the captured POST body. On an HTTP 30x, CDP re-fires
Network.requestWillBeSent with the SAME requestId (the prior hop in
`redirectResponse`) for the redirect target — usually a GET with no
postData. The handler overwrote the entry's request-body fields
unconditionally, destroying the original POST body. Now the body is
only populated on the initial send (guarded on `!redirectResponse`).
2. responseReceived created orphan entries. If readNetworkCapture()
drained the entries (clearing requestToIndex) while a request was in
flight, the later Network.responseReceived ran getOrCreate and made a
new half-entry with a defaulted method ('GET') and no request data.
Now it is lookup-only, mirroring loadingFinished.
3. evaluateInFrame had no retry on a stale cached context. A navigated/
reloaded frame invalidates its cached execution-context id, but the
executionContextDestroyed event may not be processed yet, so
Runtime.evaluate rejects with "Cannot find context with specified id".
Now that rejection drops the stale id and falls through to the
frame-target path (mirrors evaluate()'s re-resolution); genuine page
errors still propagate.
Tests: redirect-body preservation, orphan-entry prevention, and
stale-context fallback — all reverse-validated. cdp suite 15/15, tsc
clean, extension/dist rebuilt.
* Fix ChatGPT intelligence level selection * Document ChatGPT model adapter usage in browser skill * fix(chatgpt): verify model config selection * fix(chatgpt): classify model preference api drift --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
* Add ChatGPT Deep Research result extraction * fix(chatgpt): bind deep research results to requested conversation * fix(chatgpt): preserve deep research payload failures --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
…DP timeouts (#2067) * fix(browser): end-to-end command deadlines, safe transport retries, CDP timeouts Three connectivity/stability fixes that share one root cause: the timeout and retry contracts between CLI, daemon, and extension were disconnected. 1. Plumb one command deadline through all three layers. The client HTTP request was hardcoded to 30s while the daemon default was 120s and no caller ever set body.timeout — every command slower than 30s died with an opaque client-side AbortError while still running in the browser. Now the transport computes an effective timeout (user --timeout via setDaemonCommandTimeoutSeconds, or timeoutMs + margin for extension-side waits like wait-download), sends it as body.timeout, and aborts the HTTP request only after the daemon's structured 408 should have arrived. The daemon timer now rejects with the command_result_unknown contract instead of a bare Error the client cannot classify. 2. Stop replaying possibly-dispatched commands on fetch TypeError. Any `TypeError: fetch failed` used to trigger ensure + resend with a fresh id, bypassing the daemon's duplicate-id guard — a daemon crash mid-click could double-submit a form. Only pre-connect failures (ECONNREFUSED and friends, checked via err.cause) are retried now; post-connect drops surface as command_result_unknown per the existing contract. 3. Give chrome.debugger commands a real deadline. The extension's CDP calls had none (sendCommandInFrameTarget declared _timeoutMs and never used it), so a page-blocking native dialog (alert/confirm/beforeunload) hung Runtime.evaluate forever and wedged every later command on the tab. All sendCommand calls now race a timer; exec/cdp commands derive their deadline from the transport's body.timeout, undercut by 5s so the more specific extension error beats the daemon's generic timer. * fix(browser): swallow post-timeout CDP rejections; short deadline for doctor probe Two issues found in self-review of the deadline work: - sendDebuggerCommand raced the command promise against a timer but left the losing command promise unobserved — if it rejected later (debugger detach on tab close long after the timeout fired) it surfaced as an unhandled rejection in the service worker. Swallow it on a side branch. - doctor's checkConnectivity probe inherited the default 120s transport deadline, so a daemon that accepts requests but never answers made doctor hang for 2 minutes before reporting FAIL. A health probe wants the opposite: shrink the per-command deadline to the probe budget (8s) and restore it afterwards. * fix(extension): honor derived CDP deadline in evaluateInFrame warm-up; pin deadline tests From adversarial review of the deadline work: - evaluateInFrame's Runtime.enable warm-up on the frame-target fallback path dropped the caller's derived deadline and fell back to the 60s default — a blocked iframe could burn the whole daemon budget in the warm-up alone, so the daemon's generic 408 always beat the extension's specific error on the cross-frame path. - Two untested links in the deadline chain are now pinned by regression tests: the client HTTP abort fires exactly at timeout*1000 + 10s (not before the daemon's structured 408 can arrive), and handleExec derives 115s from a 120s transport timeout (10s floor for tiny timeouts).
…s, absolute deadlines (#2070) * refactor(transport): exactly-once command transport — journal, waiters, absolute deadlines Rebuilds the CLI→daemon→extension command transport around one principle: exactly-once = at-least-once retry + an idempotent executor. This replaces the accumulated per-layer compensation (three client retry flags, cause-code walking, duplicate-id 409s, an inner extension retry loop, a phased reconnect state machine) with three small primitives: 1. Command journal (extension/src/journal.ts, chrome.storage.session). Every command id executes exactly once: duplicates attach to the in-flight promise, completed ids replay the recorded result, and ids whose worker died mid-execution report `command_lost` honestly. storage.session survives service-worker restarts and clears on browser exit — precisely the lifetime a retry cares about. 2. Stable ids + daemon waiters. Transport retries keep the SAME id; the daemon attaches duplicate ids to the pending command instead of 409ing. With the executor idempotent, every transport failure becomes safely retryable (gated on extension >= 1.0.22; legacy extensions keep the old conservative pre-connect-only retry). Semantic retries (attach_failed / tab_gone — failures BEFORE any page code ran) are the only place a new id is minted, once. 3. Absolute deadline (`deadlineAt`, epoch ms) instead of per-hop durations. Same machine, one clock: every layer computes remaining = deadlineAt - now, so daemon queueing and service-worker wake latency no longer silently shrink the innermost budget or invert the layering. Error classification now happens once, at the failure site: the extension tags results with machine-readable codes (attach_failed, tab_gone, target_navigated, detached_mid_command, cdp_timeout) and errors.ts prefers codes over the legacy message-pattern tables (kept only for old extensions). detached_mid_command / cdp_timeout are now correctly non-retryable — they die mid-execution, so a blind re-run could double-apply a write. Deletions and stability fixes riding the same contract: - extension evaluate()'s inner retry loop (the client owns semantic retries now); the fast/slow reconnect window + notifyDaemonReachable rescheduling (plain exponential backoff with jitter, reset on success); the three parallel session-override Maps (one record per lease). - WS application-level keepalive ({type:'ping'} every 20s): Chrome 116+ only extends the service worker's lifetime on WS *activity*, so an idle socket lived on a knife-edge between the 30s idle kill and the 30s keepalive alarm. - idle-lease release is deferred while a command is executing on the lease (refcount) — a 30s idle timer can no longer tear the tab down mid-command; completion re-arms the timer. - daemon shutdown flushes structured 503s to waiting clients before exiting instead of process.exit() killing the queued responses. - results are delivered on the freshest open socket after a reconnect instead of being dropped when the executing socket was superseded. * fix(transport): gate daemon_shutting_down resend on journal capability; bound ensure by deadline From adversarial review of the transport-v2 work: - daemon_shutting_down was resent with the same id regardless of the extension's journal capability. The daemon fires it for DISPATCHED commands too, so on a pre-journal extension the resend re-executes a write. The daemon now returns the pre-dispatch contract for commands that never reached the extension (safe to resend anywhere) and daemon_shutting_down only for dispatched ones; the client resends those only when the extension journals ids, else surfaces command_result_unknown. - ensureBridge's connect wait was a fixed 45s regardless of the command's remaining budget — repeated daemon failures could stretch a 30s --timeout command past two minutes. The wait is now clamped to the remaining deadline.
…layer (#2017) The "Create a file like clis/<site>/<command>.ts" example predates #928, which converted the entire adapter layer from TypeScript to JavaScript. The repo now ships 0 .ts and 1259 .js built-in adapters, so a contributor following the doc creates a file in the wrong format. Update the example to JavaScript (keeping a pointer to the still-supported TypeScript path), and fix the adapter test command, which pointed at src/ rather than the adapter's own clis/ test file. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(hltv): add HLTV adapters * fix(hltv): harden row identity contracts --------- Co-authored-by: Adong <jhdong8855@gmail.com> Co-authored-by: jackwener <jakevingoo@gmail.com>
#2074) OpenCLIApp injects OPENCLI_DAEMON_PORT=19825 into the environment of every CLI it manages. The CLI hard-rejected the variable regardless of its value, so fresh OpenCLIApp installs failed on every command — including --version and doctor — with EX_CONFIG, and the daemon never started (#2068, #2072). A value equal to the default port carries no configuration at all; only a NON-default value is a genuine misconfiguration worth failing on. All three rejection points (main.ts entry, daemon startup, transport assert) now share isIgnorableDaemonPortEnv(). Also fixes the README multi-profile example that was missing the required browser <session> positional (#1893).
…t on signal death (#2075) npm-installed CLIs on Windows are .cmd shims: `where` finds them (so the installed-check passes), but Node refuses to spawn them directly since the CVE-2024-27980 hardening — spawnSync fails with EINVAL/ENOENT and every CLI-hub passthrough to an npm-installed tool breaks (#1958). On that specific failure the passthrough now retries through the shell with each token quoted for cmd.exe. Also: a child killed by a signal left status null and opencli exited 0, reporting success to the calling shell/agent; signal death now maps to a non-zero exit code.
…2073) * fix(browser): stale default profile must not veto live connections A persisted default profile (browser-profiles.json defaultContextId) has a lifetime that routinely exceeds the extension instance it names — reinstalling the extension or resetting Chrome regenerates the contextId. Since #1235 that stale preference was folded together with --profile/OPENCLI_PROFILE into one hard contextId on every command, so the daemon refused to serve it (profile_disconnected) even when exactly one live profile was connected, breaking the documented promise "with only one connected profile, OpenCLI uses it automatically" — and making doctor hang waiting for a dead profile. First-principles fix: distinguish REQUIREMENT from PREFERENCE end to end and let the component that knows live state arbitrate. - profile.ts resolves a ProfileSelection tagged 'explicit' (--profile arg, OPENCLI_PROFILE env — fail loud when offline) or 'preferred' (config default); profileRouteParams() maps it to the wire fields. - New wire field preferredContextId (both protocol copies); contextId keeps its strict semantics. Old daemons ignore the new field, which degrades to the no-contextId single-profile auto-use — exactly the documented behavior. - The daemon arbitrates via a pure, tested resolveProfileRoute(): requested → strict; preferred → use when connected, fall back to the only connected profile when not (logged once per stale id), ask with a stale-default hint when multiple are connected. - bridge/ensure only pin readiness to a profile for explicit requirements — a stale preference no longer makes connect()/doctor wait for a dead profile. - doctor surfaces the stale default with the fallback status and the recovery command (opencli profile use). * fix(cli): key saved-tab scope by the selected profile in getPageScope From adversarial review: getBrowserPage computed the target scope from the profile SELECTION (explicit or preferred), but getPageScope read only the explicit Page.contextId — so with a config-default profile the remembered tab was saved under "<session>" and looked up under "<contextId>:<session>", silently forgetting the selection on every command. Both sites now key the scope by the selected profile.
Use stable Chrome for Testing for headed E2E and keep e2e child-process timeouts below Vitest's framework timeout.
…2047) (#2055) * fix(weibo): resolve uid before the full auth probe to avoid HTTP 400 (#2047) `auth status --site weibo --full` failed with `HTTP 400 from /ajax/profile/info` even on a logged-in session: verifyWeiboIdentity fetched the bare /ajax/profile/info, which the current Weibo web app rejects without a uid. `weibo me` already works because it resolves the current uid first. Mirror that path: call getSelfUid(page) (which throws AuthRequiredError when no logged-in uid resolves), then probe /ajax/profile/info?uid=<uid>. Extract the probe into buildWeiboIdentityProbe(uid) and add clis/weibo/auth.test.js. * fix(weibo): unwrap auth identity probes --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
Pin headed E2E macOS coverage to macos-15 while macos-latest migrates to macOS 26.
… article (#2001) (#2026) * fix(twitter): match localized delete menu and poll for late-hydrating article (#2001) twitter delete failed on a Simplified-Chinese X detail page: (1) the More caret was matched by aria-label === 'More', which X localizes (zh-Hans 更多), and (2) findTargetArticle() ran before the article's self-referential /status/<id> link hydrated on slow networks. Inside buildDeleteScript: - Prefer the language-agnostic [data-testid="caret"] (scoped to the matched article), falling back to a multilingual /^(More|更多)/ aria-label match. - Poll findTargetArticle() for ~5s (20 x 250ms) before giving up. - Broaden the Delete menu item to Delete/删除 and exclude the Lists item in both languages (List/列表). * fix(twitter): harden delete menu result handling * fix(twitter): scope delete menu items to opened menu --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
Keep Linux AX smoke release-blocking while allowing hosted macOS to skip when command-line unpacked extension startup is unavailable.
Bump @jackwener/opencli to 1.8.6.
…rt contract E2E (#2081) The AX real-Chrome smoke's contract is "the extension bridge works in a real Chrome", not "a window appears" — Linux already admitted that by faking a display with xvfb. Hosted macOS runners fail headed Chrome at the OS level (child processes lose the Mach port rendezvous with the browser process because CI jobs run outside a regular Aqua session), and PR #2079 papered over that by skipping the platform. Running the smoke with --headless=new removes the display/GUI-session dependency entirely: new headless is a full browser (MV3 service worker, chrome.debugger, --load-extension), verified locally against the same Chrome for Testing build CI uses. The smoke is release-blocking on every OS again; headed mode stays available locally via OPENCLI_E2E_HEADED=1. New daemon-transport contract E2E: the real dist/src/daemon.js process with a scripted fake extension, pinning the cross-layer contracts end to end — duplicate ids attach to the pending command without re-dispatch, deadlines produce a structured 408 command_result_unknown, extension death after dispatch yields command_result_unknown, a stale preferredContextId falls back to the only connected profile while an explicit contextId fails loud, and graceful shutdown flushes structured daemon_shutting_down 503s with exit code 0. No browser required; runs in the fixed-port project on every OS.
#2082) #2081 tried to make the real-browser AX smoke run everywhere via --headless=new. That fixed headed macOS's Mach-port crash but exposed a second environment property: hosted runners don't reliably start the MV3 extension service worker in headless, so main went red on macOS anyway. Chasing headless reliability across runner images is the wrong axis. First principles: gate each check on the environment that can run it deterministically, and make sure every OS has a real blocking gate. - Real-browser extension smoke (AX tree + cross-frame CDP): Linux under xvfb is the one hosted environment where a real Chrome reliably starts an MV3 extension. It runs there, headed, release-blocking. It is not scheduled on macOS/Windows because neither can run it deterministically (headed macOS crashes on Mach port rendezvous outside an Aqua session; headless connects no SW). - Daemon transport contracts: no browser, deterministic, so they run blocking on all three OSes including Windows — macOS/Windows now have a real gate over the exact layer our recent bugs lived in (#2067/#2070/ #2073), not a skipped test that proves nothing. - Windows joins the matrix for the first time (transport gate); the setup-chrome action is skipped there since it hangs on the MSI path and Windows needs no browser. Local Chrome launch stays headed by default; OPENCLI_E2E_HEADLESS=1 opts into headless for display-less local runs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Related issue:
Type of Change
Checklist
Documentation (if adding/modifying an adapter)
docs/adapters/(if new adapter)docs/adapters/index.mdtable (if new adapter)docs/.vitepress/config.mts(if new adapter)README.md/README.zh-CN.mdwhen command discoverability changedCliErrorsubclasses instead of rawErrorScreenshots / Output