Commit Graph

28271 Commits

Author SHA1 Message Date
Christopher 5cfa2b2f0f fix(gateway): surface actionable message for local model server connection errors 2026-08-16 01:50:44 -07:00
Christopher ae7b5c8eab fix(batch_runner): propagate fatal and validation errors as non-zero exit codes
Python Fire serializes the return value of a wrapped function but does not
use that value as the process exit code. Error paths in main() that used
 or  therefore caused the process to exit 0, swallowing
fatal errors and argument-validation failures.

Raise SystemExit(1) on every error path so batch_runner returns a non-zero
exit code when it cannot run. Success paths (e.g. --list_distributions) are
left unchanged.

Closes NousResearch/hermes-agent#86524.
2026-08-16 01:50:20 -07:00
fangliquanflq faaf2ae093 fix(gateway): read routed profile model config 2026-08-16 01:49:53 -07:00
Jasmine Naderi 9ac1e65b0a fix(cron): do not treat directories as unsafe lifecycle scripts (#86753)
Docker Desktop writes fpath=(~/.docker/completions ...) into .zshrc.
The referenced-script walk then opened that directory, saw a non-regular
file, and fail-closed — blocking source ~/.zshrc on every terminal
command. Directories are not scripts; devices stay fail-closed.
2026-08-16 01:49:25 -07:00
ygd58 08baf96537 fix(runtime): exempt loopback custom-provider pool credentials from the usable-secret floor
Fixes #86864.

Legacy custom_providers configs commonly used short/placeholder
api_keys ('123', 'm') for local no-auth services like Ollama --
harmless for the endpoint itself, since Ollama accepts any key or no
key. A stricter has_usable_secret(value, min_length=4) gate added
later now rejects these, but only the credential-POOL resolution path
lacked the same "no-key-required" exemption every OTHER resolution
path in this file already has for exactly this scenario:

- The config-based custom_providers fallback (non-pool path) already
  ends with `api_key or "no-key-required"`.
- The "actual" provider's local-offline path already injects
  ACTUAL_LOCAL_NOAUTH_PLACEHOLDER before the usable-secret gate for a
  loopback base_url.
- _try_resolve_from_custom_pool() was the one gap: it returned the raw
  short pool credential unchanged, which then failed the downstream
  has_usable_secret() gate with a generic "No usable credentials found
  for custom" error that contradicts setup.status ("configured
  credentials" vs "runtime failed"), sending users hunting in the
  wrong direction.

Fixed by substituting the same "no-key-required" placeholder when the
pool's stored credential fails has_usable_secret() AND the base_url
resolves to a loopback hostname (using the existing _loopback_hostname
helper, matching the exemption scope the issue itself requested:
localhost/127.0.0.1/::1 only, not arbitrary remote endpoints with a
genuinely-too-short key).

Added 4 regression tests extending the existing
test_runtime_provider_resolution.py file, following its established
credential-pool mocking pattern: the exact reported 3-char repro
('123'), a 1-char case, a non-loopback sanity check confirming the
exemption stays scoped (a short key for a remote endpoint is NOT
silently exempted), and a sanity check that a genuinely usable
loopback key passes through unmodified. Verified as a genuine
regression by reverting the fix and confirming 2 tests fail with the
exact raw short key leaking through unchanged.

59/59 pass in the extended test file; 14/14 across two more related
custom-provider test files (no regression).
2026-08-16 01:48:59 -07:00
RelaxJonh 3beea05438 fix: persist computer_use provider selection so Desktop picker survives refresh
The `computer_use` toolset (cua-driver) had no persistence branch in
_write_provider_config or _is_provider_active. When the Desktop GUI
sent PUT /api/tools/toolsets/computer_use/provider, the config write
was a no-op and _is_provider_active always returned False -- so the
"Use this backend" CTA reappeared on every refresh.

Add a computer_use_backend marker to the cua-driver provider entry
and handle it in the same pattern as web_backend / browser_backend:
- _write_provider_config now sets computer_use.backend = "cua"
- _is_provider_active now checks computer_use.backend
- _reconfigure_provider (interactive CLI) also persists the key

Fixes #86962
2026-08-16 01:48:34 -07:00
liuhao1024 f13f3401a1 fix(webhook): authenticate Linear deliveries via linear-signature HMAC (#87348) 2026-08-16 01:45:37 -07:00
RelaxJonh eac1f65340 fix(install): validate --commit SHA and fail hard on fetch/checkout errors (#87268)
Three problems with install.sh --commit:

1. No validation: non-hex or too-short arguments passed through to git,
   producing misleading errors.

2. Fetch failure swallowed by || true: abbreviated SHAs are refused by
   GitHub's server ("couldn't find remote ref"), but the error was
   silently ignored.

3. Checkout failure not checked: git checkout --detach with a missing
   object produces a misleading "does not take a path argument" error
   and the install continues unpinned, exiting 0.

Fix:
- Validate --commit is a 7-40 hex string up front
- Remove || true from fetch; fail with actionable message directing
  users to full 40-char SHAs
- Check git checkout --detach result and fail hard on error

Fixes #87268
2026-08-16 01:45:10 -07:00
RelaxJonh f887819421 fix(install): capture npm output on failure for diagnosable errors (#87340)
Both install-blocking npm install call sites (browser tools and TUI) ran
with --silent and no output capture, so failures printed only a generic
error message with no npm diagnostics.

Apply the same pattern used by the camofox install path: redirect npm
output to a temp file and replay it on failure, so users can see the
actual error (EBADENGINE, ETARGET, network timeout, registry 5xx, etc.).

Fixes #87340
2026-08-16 01:44:45 -07:00
fangliquanflq 9219cd3944 fix(gateway): isolate post-turn loop failures 2026-08-16 01:39:14 -07:00
fangliquanflq 2f6bbfbcbc fix(gateway): release loop ticks after empty responses 2026-08-16 01:39:14 -07:00
Teknium a15de34545 feat(desktop-sdk): host.deleteProfile — teardown-routed profile delete for plugins
Plugins deleting profiles via `cli.exec ['profile','delete',…]` bypass the
Electron-side DELETE /api/profiles interception (prepareProfileDeleteRequest),
so a live pool backend — e.g. one the roster's hover pre-warm just woke —
holds the profile dir open and the renderer's reconnect respawns it
mid-delete, recreating the directory (#52279). Bot Mode's right-click Delete
hits this every time because right-click hovers the row first.

Add host.deleteProfile(name) to the plugin SDK: routes through the same
teardown-routed REST path core's DeleteProfileDialog uses (backend teardown
first, next request routed away), rejects on 'default', and re-homes the app
to the default profile when the deleted profile was the live gateway's —
mirroring the core dialog's ordering.

Reported by @BkashJosi (Bot Mode: deleting a bot errors while its session
is awake).
2026-08-16 01:28:47 -07:00
Teknium 69f7c655b4 docs(tui): document defer_history vs omit_messages precedence
Follow-up to the #62799 salvage: Desktop sends both defer_history and
omit_messages on a cold resume. Make explicit in the deferred branch that
defer_history supersedes omit_messages — the single history read happens in
the background hydration worker and the synchronous omit_messages read on
the cold-resume default path is skipped entirely, so the transcript is
never loaded twice for one resume.
2026-08-16 01:28:26 -07:00
embwl0x a2fd20f896 test(tui): isolate deferred hydration worker 2026-08-16 01:28:26 -07:00
embwl0x 7a1b2cd0d1 test(desktop): preserve bounded deferred resume 2026-08-16 01:28:26 -07:00
embwl0x bd1a44fade test(tui): wait for deferred profile DB close 2026-08-16 01:28:26 -07:00
embwl0x 60be8ef26d perf(desktop): make session resume incremental 2026-08-16 01:28:26 -07:00
kshitij b2369172ad Merge pull request #87516 from kshitijk4poor/chore/author-map-justinbowes
chore: map justin@bowes.org to @justinbowes
2026-08-16 13:33:16 +05:30
Teknium 37445d6dc2 feat(desktop): unify MCP Servers and Catalog into one coherent list
The MCP tab's left column previously split the configured fleet and the
Nous-approved catalog behind a Servers/Catalog tab toggle. Installed
entries appeared in both views and the install button lived a tab flip
away from the list it fed.

Now one scrolling column: configured servers (live status, toggles,
probes) on top, a Catalog section below offering only entries not yet
installed. Installing moves the entry up into the fleet list; the
zero-servers empty state keeps the catalog visible beneath it instead
of hiding it behind a full-page invitation.

Bot Mode's Advanced view embeds this same McpTab via the plugin SDK, so
the unification mirrors there automatically.

- removed leftView state + TextTab toggle; section headers reuse the
  existing tabServers/tabCatalog strings (no i18n changes)
- availableCatalog memo filters installed/name-clashing entries
- catalog memoized to satisfy react-hooks/exhaustive-deps
2026-08-16 00:58:50 -07:00
kshitij 2cd5393af7 chore: map justin@bowes.org to @justinbowes
Attribution mapping for the PR #84982 salvage. The commit email is not
linked to a public GitHub account, so contributor_audit --strict fails
without it; login confirmed from the PR author field.
2026-08-16 13:10:40 +05:30
kshitij 2be183142c refactor: extract _install_paired helper, fix misleading comment, isolate test fixture
Apply findings from /simplify-code 3-agent review:

1. Extract _install_paired() inner helper — the Ctrl, Alt, and Shift
   sections all repeated the same mok+csiu sequence generation pattern
   (~30 lines of duplication). Now each section builds a dict and
   delegates to _install_paired(modifier, mapping).

2. Replace 10 hardcoded Ctrl+digit lines with a loop matching the
   Ctrl+letter pattern above it.

3. Fix misleading comment: claimed 'Ctrl+0 doesn't produce a control
   byte' but chr(ord('0') & 0x1F) = 0x10 = ControlP. The code was
   correct (maps directly to Keys.Control0..9); only the comment was
   wrong.

4. Add comment explaining why Shift+letter maps both lowercase and
   uppercase codepoints (some terminals send the already-shifted
   codepoint with modifier=2).

5. Test fixture: snapshot/restore ANSI_SEQUENCES in teardown so 294
   mappings don't leak into sibling test files (global mutable state).
2026-08-16 13:02:27 +05:30
kshitij b353ac39a2 fix(cli): map all Ctrl/Alt/Shift+key combos under modifyOtherKeys level 2
Commit 4c34eeb416 stopped pushing the Kitty keyboard protocol (CSI >1u)
because Ctrl+C arrived as ESC[99;5u instead of \x03, breaking SIGINT.
But modifyOtherKeys level 2 (CSI >4;2m) was kept so Shift+Enter stays
distinguishable from Enter.

Under modifyOtherKeys=2, terminals re-encode EVERY Ctrl+key combo as
ESC[27;5;<codepoint>~ instead of the raw control byte. prompt_toolkit
3.x only maps ESC[27;5;13~ (Ctrl+Enter = Ctrl+M); all other Ctrl+letter
combos are unmapped and leak as literal text or get swallowed — breaking
Ctrl+A, Ctrl+C, Ctrl+D, Ctrl+E, Ctrl+K, Ctrl+R, Ctrl+U, Ctrl+W, Ctrl+Z,
etc. Shift+letter combos (ESC[27;2;<codepoint>~) have the same problem,
causing the 'caps locked sessions' symptom where typed text appears
corrupted or stuck.

Fix: add install_modify_other_keys_aliases() to pt_input_extras.py that
populates prompt_toolkit's ANSI_SEQUENCES dict with 294 mappings covering:
- Ctrl+letter (a-z): ESC[27;5;<code>~ and ESC[<code>;5u -> Keys.ControlA..Z
- Ctrl+digit (0-9): same formats -> Keys.Control0..9
- Ctrl+symbol ([ \ ] ^ _ @ Space): same formats -> matching Keys.Control*
- Alt+letter (a-z, A-Z): both formats -> (Escape, <letter>) tuple
- Shift+letter (a-z, A-Z): both formats -> uppercase character

Uses setdefault semantics — never clobbers existing mappings from
install_shift_enter_alias or install_ctrl_enter_alias. The Ink TUI
(Node.js) already handles this via a regex parser; prompt_toolkit 3.x
uses dict lookup only, so we populate the dict.

Refs #56684, #87711.
2026-08-16 13:02:27 +05:30
fangliquanflq c6dfdcbf8d fix(agent): reject masked verification results 2026-08-16 00:14:58 -07:00
hermes-seaeye[bot] 1f8f79b320 fmt(js): npm run fix on merge (#87505)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-16 07:11:41 +00:00
Teknium 6bb8f60f57 fix(desktop): defer credential-warning onboarding to the first chat attempt
Switching to a profile with no provider configured popped the blocking
onboarding overlay (Nous Portal / provider picker, "gateway isn't
ready") the moment the profile's runtime info arrived — punishing the
user for merely looking at an unconfigured bot/profile.

The passive credential_warning (session create/activate/resume info,
stream heartbeats) is now stashed instead of opening the overlay.
The submit path consumes it when the user actually tries to chat and
opens onboarding then, before the doomed send; the draft stays in the
composer. A warning-free session event clears the stash, so healed or
switched-away profiles never fire stale onboarding. Turn-error paths
(a real failed send) still open onboarding immediately, unchanged.
2026-08-16 00:06:07 -07:00
Teknium 8ad055414b fix(terminal): warn when exit_code 0 masks a piped build/test failure
`cargo build 2>&1 | tail -20` exits with tail's 0 even when the build
failed — bash without pipefail reports the last pipeline command's
status, and `cmd || echo failed` swallows the status the same way. The
model reads exit_code: 0 as a strong success signal and can conclude a
build passed while the visible output says it failed (community report,
Windows Rust builds; not platform-specific).

Two-part fix, mirroring OpenCode's prompt-side approach plus a
result-side backstop they don't have:

- Tool description now forbids piping builds/tests through
  tail/head/cat (output is already auto-truncated + spilled to a file)
  and warns that pipes/|| fallbacks mask exit codes.
- New annotate_masked_success() in tools/terminal_hints.py: when
  exit_code == 0, the command shape can mask an upstream status
  (top-level pipe into a passthrough consumer, or || echo/true), AND
  the output carries strong tool-specific failure shapes (rustc,
  cargo, pytest, gcc, npm, make, ninja), attach an advisory 'hint'
  telling the model to treat the run as failed and re-run bare.
  exit_code itself is never modified. Search/content heads
  (grep/rg/echo/printf/...) are excluded to avoid false positives on
  pipelines whose output legitimately contains error text.

E2E-verified through the real terminal tool path: hint fires on masked
cargo-style failures, silent on bare commands, clean pipes, and
grep/printf pipelines. 42 targeted tests pass.
2026-08-15 23:48:15 -07:00
Teknium 406c5daf04 fix(compression): bound the rotation tail clone below the rotator's own flush
CI caught two rotation-path regressions from the unbounded clone: the #47202
pre-publish flush writes the rotator's OWN input transcript to the parent
(above the start-watermark), and the clone was duplicating it into the child
alongside the handoff. publish_compression_child gains watermark_ceiling —
the MAX(id) captured immediately BEFORE that flush — so only rows in
(watermark, ceiling] (genuinely foreign concurrent appends) clone across.
Ceiling capture failure falls back to no tail preservation (historical
behavior) rather than risking duplication. Ceiling-exclusion test added.
2026-08-15 23:37:22 -07:00
Teknium 652f5c2ebb fix(compression): rotation path clones the concurrent tail into the child
CI caught the sibling site the in-place fix missed: legacy (non-in-place)
compression rotates via publish_compression_child, where a mid-summary
append previously stranded in the closed parent. Same watermark + pure-SQL
column clone as archive_and_compact, with session_id rewritten to the child.
Lineage-guard test flipped to pin the appends-flow-freely contract; rotation
watermark tests added (tail follows the child; None = historical behavior).
2026-08-15 23:37:22 -07:00
Teknium 21d3e63702 fix(compression): watermark commit — appends flow freely, concurrent tail survives compaction
Redesign of the #75316 class (supersedes the approach in PR #87307).

Root cause family: the compression lock fenced ORDINARY transcript appends
for the whole slow provider-summary call. Turns died as
session_persistence_failed whenever a message overlapped a compression
(#74568, #77386, #75083), stale dead-PID locks blocked writes for the full
TTL, and the busy-wait mitigation (#75264) was an order of magnitude shorter
than real summaries. Separately, the commit archived from a pre-call
snapshot, so rows appended mid-compression were swept into the archive.

Design: the commit transaction is already exclusive — no lock phases needed.

1. Appends never check compression_locks. The lock's only job is stopping
   two compressions colliding; it keeps that job. The whole stale-lock /
   busy-wait symptom family dies as a class.
2. Watermark captured in the DB at compression start
   (get_active_message_watermark = MAX(id) of active rows) — not from
   in-memory message dicts, which carry no row ids in production.
3. archive_and_compact(watermark=, lock_holder=): one transaction verifies
   the holder still owns an unexpired lease (a reclaimed lease cannot
   publish a stale compaction), archives the snapshot, inserts the compacted
   set, and re-sequences the concurrent tail (id > watermark) via a
   pure-SQL column clone — every column except id survives byte-exact
   (api_content, platform_message_id, reasoning sidecars, token counts),
   FTS triggers index the clones naturally, originals stay archived and
   recoverable. watermark=None preserves the historical behavior.

Removed: the append-side compression fence in _check_transcript_write_guards
(with rationale note), making the _COMPRESSION_BUSY_WAIT_S retry lane
unreachable from append paths (kept for other callers).

Tests: 12 new (watermark contract, column-exact clone, commit fence incl.
lease-lost/expired/rollback failure injection, append-vs-commit race);
busy-retry suite flipped to pin the new contract; sabotage-verified (5 fail
with the watermark disabled, 12 pass restored); E2E through the real
compress_context seam with a mid-summary append landing and surviving.
2026-08-15 23:37:22 -07:00
Teknium 128fb74d98 chore: map contributor email for 5Hyeons 2026-08-15 23:37:15 -07:00
5Hyeons 9975180a59 fix(mcp): handle DCR clients with secrets across all OAuth paths
Some MCP OAuth providers (notably Supabase) return a client_secret from
dynamic client registration but omit token_endpoint_auth_method. The MCP
SDK defaults the missing method to "none", so the token exchange omits
client_secret and the server rejects it (HTTP 422 "Required parameter:
client_secret"), looping the browser consent page.

This resolves the whole class, not just one provider:

- Storage layer (HermesTokenStorage): coerce secret-bearing client info
  with missing/none auth method to client_secret_post on both read and
  write, persisting the corrected shape.
- Both live provider paths (tools/mcp_oauth.py HermesOAuthClientProvider
  and tools/mcp_oauth_manager.py HermesMCPOAuthProvider): coerce
  in-memory client info immediately before token exchange and refresh.
- Accept the full 2xx range on token and refresh responses (Supabase
  returns 201 Created), instead of the SDK's exact-200 check.
- Redact token response bodies from error messages and logs on
  malformed responses.

The Figma-specific request-time default (apply_oauth_provider_defaults)
remains; this generalizes the same bug class for every DCR provider.

Fixes #29680. Supersedes #34274 and #35700 (201-only variants).
2026-08-15 23:37:15 -07:00
Teknium 411903b6fa chore: contributor mapping for NikolaRHristov 2026-08-15 23:03:52 -07:00
Teknium 20fcc11a0c refactor(hooks): share fail-closed approve logic; update sibling tests
Follow-up to the #28953 salvage:

- Extract _resolve_block_from_details() so resolve_pre_tool_block and
  _dispatch_pre_tool_call_hooks share ONE fail-closed approval-gate
  implementation. This also gives the new dispatcher the observability
  context wrapping around request_tool_approval that the original PR's
  inlined copy lacked.
- Update sibling tests that patched resolve_pre_tool_block at the three
  migrated dispatch sites to patch _dispatch_pre_tool_call_hooks with the
  (block_message, modified_args) tuple contract.

Verified: 448 targeted tests green; E2E with a real shell hook in an
isolated HERMES_HOME rewrote a live write_file call (path + content)
through handle_function_call, with block and negative paths intact.
2026-08-15 23:03:52 -07:00
Nikola Hristov d083b85591 feat(hooks): pre_tool_call content transformation via modify directive
Adds a `modify` response type to pre_tool_call hooks so a hook can
transform tool arguments before the tool executes, instead of repairing
results afterwards via post_tool_call.

- hermes_cli/plugins.py: _dispatch_pre_tool_call_hooks() fires hooks once
  and returns (block_message, modified_args); modify directives
  shallow-merge into an accumulated dict built from the original args.
- agent/shell_hooks.py: _parse_response() accepts both the canonical
  {"action": "modify", "args": {...}} and Claude Code-compatible
  {"decision": "modify", "tool_input": {...}} wire formats.
- model_tools.py, agent/tool_executor.py, agent/agent_runtime_helpers.py:
  dispatch sites migrated; modified args applied before execution.
- Docs + 10 new tests (merge semantics, precedence, block interplay).

Salvaged from PR #28953. Best fix for #18988.
2026-08-15 23:03:52 -07:00
Teknium f3bf718a62 feat(desktop): drag-resizable panes on the Capabilities Skills tab
The Skills tab's three panes are now all drag-resizable:

- List/detail column seam: MasterDetail grows an optional resizeId that
  turns the seam between the rail and the detail pane into a vertical
  drag sash (same visual language as DetailPane's top-edge sash). The
  rail width persists in the shared pane store under that id;
  double-click resets to the default 0.75fr track. Skills and Tools
  tabs share one id so the split stays consistent across tabs.
- Skills Hub section: the embedded hub picker's top edge is now a drag
  sash — pull the hub pane up to grow it (the skills list above
  absorbs the change). Height persists through the same pane store,
  double-click resets, and the cross-origin iframe gets
  pointer-events:none during the gesture so it can't swallow the drag.
  Replaces the old native CSS corner-resize handle.
- The skill editor bottom pane already resized via DetailPane's sash.

MASTER_DETAIL_WIDE_COLS keeps its exported shape (MCP tab reads it) —
the --md-split var falls back to the declared track when unset, so
grids without a sash render exactly as before.
2026-08-15 22:58:30 -07:00
Teknium 9b9dd6eeab fix(computer-use): zero-rect AX bounds serialize as unknown, not a position
KDE/Qt apps report [0,0,0,0] bounds for elements that are perfectly
clickable by index (live QA: all 50 of kcalc's zero-rect elements,
including every radio button). Serializing that as a plausible rect
invites a model to derive coordinate=[0,0] and click the screen corner.

- _element_to_dict: zero rect -> bounds: null
- _format_elements: '@ bounds-unknown (click by element index)' instead
  of the fake rect in the summary line
- malformed bounds fail open (unchanged serialization)

Live-proven on real kcalc (cua-driver 0.20.0): 50 elements now null, 0
zero-rect leftovers, summary annotated, real rects preserved, and a
null-bounds radio button still clicks fine by index.
2026-08-15 22:36:52 -07:00
Teknium 1d5fc2bc0b feat(config): wire compression.tail_mode + docs (en/zh)
#87326 shipped the lean-compaction capability on the compressor; this adds
the config.yaml surface (compression.tail_mode: legacy|lean, default
legacy), DEFAULT_CONFIG entry, and docs on both the dev-guide compression
page and the user-guide configuration page, with zh-Hans parity.
2026-08-15 22:36:31 -07:00
Teknium 4a9411cf7f Merge pull request #87326 from NousResearch/feat/compaction-eval-and-tail-policy
feat(compression): lean tail mode + compaction recall eval harness
2026-08-15 22:22:39 -07:00
Teknium b7f6280259 fix(computer-use): refuse wrong-window input on app= mismatch; hint near-miss actions
Live complex-action QA on a real KDE desktop (kcalc + kate multi-app
flows) found two dispatch gaps:

1. Wrong-window input reported as success. Input actions deliver to the
   backend's sticky target (last capture/focus_app); the app= argument
   models routinely pass on the input call itself was silently dropped.
   Proven live: with kcalc sticky, type(text='777', app='kate') returned
   ok:true and typed 777 INTO KCALC. New guard: provable mismatch
   (both names known, neither substring of the other — list_windows
   names are localized/variant) refuses with input_target_mismatch and
   a one-call fix instruction. Unknown current target fails open so
   legacy no-app flows are untouched.

2. Near-miss unknown actions were dead ends. A model emitting 'hotkey'
   got a bare unknown-action error. Suggestion map now names the real
   action ('did you mean key?') without aliasing — we never repair bad
   model output, we just point at the schema.

Also documents the verified-lost-keystroke rung in the computer-use
skill: KTextEditor (Kate/KWrite) discards synthetic X keystrokes at the
toolkit level — foreground type reports ok but AX shows nothing arrived,
and a raw XTest control fails identically outside our stack. Guidance:
after one verified-lost round trip, switch to file/DBus I/O instead of
looping the ladder.

Live proof on the fixed build: mismatch refused, kcalc display clean,
same call after capture(app=kate) succeeds, 'hotkey' suggests 'key'.
11 new tests; 158 sibling tests green.
2026-08-15 22:18:09 -07:00
Teknium b7790d91a6 merge: refresh onto current main (take main's superior sequential-timeout de-flake) 2026-08-15 19:11:57 -07:00
Teknium 0ee5ae61e2 docs(evals): real Codex CLI head-to-head arm + results
scripts/codex_arm.py drives OpenAI Codex CLI end-to-end on the same
transcripts: chunk-file reads until its REAL auto-compaction fires (verified
via compacted events in the rollout jsonl; peak 455-483K vs its 258K
window), then quizzes post-compaction with the identical question banks and
judge. Results (results/codex-arm-2026-08-15/): codex 36.7% avg vs lean
closed-book 40.0% vs lean+recovery 68.3%. Codex has no runtime re-access
over its rollout history — the session_search differentiator, measured.
2026-08-15 18:41:24 -07:00
Teknium 460d345642 fix(computer-use): flag macOS zero-display capture in doctor + discovery reason
A headless Mac or asleep built-in panel leaves ScreenCaptureKit with 0
shareable displays while TCC grants pass — health_report stays ok and
every capture silently returns 0x0 (#67165). Guard at the report seam
(_apply_display_count_guard, both real and fallback paths): flips the
screen_capture_capability check to fail with recovery actions (wake
display / HDMI dummy / virtual display) and downgrades ok -> degraded.
The empty-discovery reason ladder gains the matching darwin rung.

Composed from #52949 (sujeet111) and #67259 (webtecnica); both PRs
predate the doctor rewrite and the envelope normalization on main, so
this reimplements their shared intent at the current seams.

Co-authored-by: Sujeet <64351924+sujeet111@users.noreply.github.com>
Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>
2026-08-15 18:20:55 -07:00
0xGr1mm d303e18d09 fix(computer_use): ignore placeholder pid/window_id ids
capture() read any non-None pid/window_id as a request for exact-window
targeting. Several providers emit every declared schema property on every
tool call, zero-filling unused optional integers, so those calls arrive as
pid=0, window_id=0. The exact-target branch was then entered, the caller's
app= was discarded, _positive_int(0) returned None for both ids, and the
capture failed with a message pointing at pid/window_id. For that class of
model capture(app=...) and frontmost capture never worked at all.

Normalize non-positive ids to None before the branch decision so dispatch
falls through to app/frontmost discovery. Malformed non-numeric ids are
deliberately not treated as placeholders: they still reach the existing
validation error instead of being silently ignored.

Fixes #81333
2026-08-15 18:20:55 -07:00
Teknium 61f2738205 test(tool-executor): close the second race — deadline vs middleware preamble
The first hardening (deterministic worker start) still went red on its
own PR's CI: the 0.1s deadline can ALSO expire between worker start and
handle_function_call — argument parsing, hooks, and cold imports run in
that window, and the timeout interrupt then returns the middleware
without dispatching (first_started unset; observed 0.15s tool error vs
0.1s budget in the slice-4 log).

Deadline raised to a 1.0s floor: still fast, ~7x the worst observed
preamble, and the submit() sync from the first commit keeps the
countdown anchored to worker start. The clarify human-wait test's sleep
rises to 1.3s so it still outlasts the deadline (the relationship the
test exists to pin). Sabotage v2 injects BOTH races (0.3s thread start
+ 0.5s preamble): hardened passes, un-hardened reproduces the exact CI
failure.
2026-08-15 18:15:57 -07:00
Teknium d24d294421 test(tool-executor): deterministic worker start kills sequential-timeout flake
test_sequential_tool_timeout_emits_result_and_continues failed twice in
two days on unrelated computer_use PRs (slice 4, xdist): assert
first_started.is_set() -> False. Root cause: the sequential timeout path
computes deadline = now + timeout_s right after executor.submit(); with
the test's 0.1s deadline, a loaded CI worker can take longer than the
whole deadline just to START the pool thread, so the future is cancelled
before the tool ever dispatches.

Fix: an autouse fixture subclasses DaemonThreadPoolExecutor so submit()
blocks (bounded 10s) until the worker callable has begun — the deadline
now races the tool, not the thread scheduler, which is what these tests
mean to pin. Timeouts stay tight (0.05s), so the suite stays fast.

Sabotage-proven: a 0.3s injected thread-start delay reproduces the exact
CI failure without the fixture and passes with it.
2026-08-15 18:15:57 -07:00
Teknium 933ef69470 feat: session picker lifecycle status + delete 2026-08-15 18:15:22 -07:00
Teknium 99500aca11 fix: guard breadcrumb writes for bare test fakes; fold session category into general
- cli.py breadcrumb call sites use getattr so object.__new__/SimpleNamespace
  test fakes without the mixin method don't AttributeError (pitfall 17)
- web_server: fold one-field 'session' category into 'general' per the
  single-field-category test contract
2026-08-15 18:15:18 -07:00
Teknium d6f02e3496 feat: per-terminal --continue via terminal breadcrumbs 2026-08-15 18:15:18 -07:00
Teknium 04c61f2949 feat: import and resume Claude Code / Codex CLI sessions 2026-08-15 18:15:13 -07:00
Teknium e2990428e7 docs(evals): ship transcript-building scripts + full eval detail in-repo
- scripts/reconstruct_lineage.py: rebuild full uncompacted lineage
  transcripts from a state.db COPY (descendant-tree walk, content-hash
  dedupe, synthetic-artifact strip, system_prompts hash resolution)
- scripts/replay_lineage.py + scripts/build_html_report.py: replay a 500K
  prefix through any checkout's compressor and render before/after
  side-by-side with compaction artifacts color-coded
- README: transcript-building workflow, scoping-tripwire section
- results/SCORECARD-2026-08-15.md: full per-transcript scorecards, all 4
  exam question banks, survival analysis, methodology + caveats
2026-08-15 17:40:43 -07:00