Commit Graph

48958 Commits

Author SHA1 Message Date
Yuan Li
87bfb9426e fix(pm): isolate per-package pin failures instead of aborting the run
_apply_pins let any exception from _pin_artifacts propagate: one broken
pin - a package's resolution bug (gh's 3-part target split) or one
target's upstream pool skew (Termux rolling pool 404 while nodejs.org
is ahead) - aborted the entire run, blocked every unrelated package,
and left lockfile.save() unreached, so any failure meant zero progress.

Catch per package, pin everything that succeeds, install the pinned
set, print a 'pinned X, failed Y' summary, and exit non-zero when
anything failed. A run where every pin fails still leaves the lockfile
untouched.

Fixes #125386
2026-10-05 15:34:40 -07:00
Teknium
4b6884aaa7 chore: map osadchiis contributor email 2026-10-05 15:28:17 -07:00
Teknium
ca398ea39e fix(mcp): trim the read-only hint reader; the e2e read-only cell passes
mcp_field(None, ...) already yields None, so the extra None guard is dead.
Drop the #121042 KNOWN entry from the stdio conformance suite: on a
trust: untrusted server the readOnlyHint tool now runs without approval
against the real mcp 2.x fixture server.

Co-authored-by: pierrenode <298902573+pierrenode@users.noreply.github.com>
2026-10-05 15:28:17 -07:00
Sergii Osadchyi
35332931bf fix(mcp): read readOnlyHint from mcp 2.x snake_case annotations
mcp 2.0 renamed ToolAnnotations.readOnlyHint to read_only_hint; camelCase
survives only as a pydantic serialization alias, which is not applied to
attribute access. _annotation_read_only_hint() read the camelCase attribute
off the SDK object, so on mcp 2.x the hint was always None and every tool --
including honestly annotated read-only ones -- fell into the
"unknown = write-capable" branch.

With trust: untrusted that means the approval gate fires before every read
call (get_*, search_*, list_*), which is exactly the failure mode that
trains a user to click through the prompt without reading it, weakening the
gate on the writes it exists for.

Use mcp_field() for the object path (the helper already used across the MCP
client for this 1.x -> 2.x rename) and accept snake_case in the cache dict
too, for caches written by another client. Unknown still means
write-capable; only the field lookup changes.

Regression test covers the SDK 2.x model, both snake_case shapes, the 1.x
camelCase attribute and a snake_case cache dict.
2026-10-05 15:28:17 -07:00
Brooklyn Nicholson
2d66d6e2f8 fix(pm): wait out Windows scanner holds in the archive hoist and entry swaps
flatten_single_dir renames a tree extracted seconds earlier, while
Defender or another scanner still holds it, and ffmpeg/agent-browser
installs died on the package/bin rename with [WinError 5] (#131884).
The entry displace/restore renames in install.py had the same bare call.

pm.filesystem.retry_held retries EACCES/EPERM/EBUSY for about 3s and
raises anything else immediately; the hoist, merge_tree and the entry
swaps use it.

Co-authored-by: bighamx <10262931+bighamx@users.noreply.github.com>
Co-authored-by: lzmuhioin <1814826+lzmuhioin@users.noreply.github.com>
2026-10-05 17:08:32 -05:00
Brooklyn Nicholson
f0b9649032 fix(build): wait out Windows scanner holds when publishing products
publishDirectory moved a finished product into place with one renameSync.
On Windows an antivirus scanner opens every freshly written file, so the
rename onto build/native-deps (right after hud-modifier-monitor.exe is
compiled) or hermes_cli/web_dist failed EPERM and threw away a build that
took minutes.

retryHeld retries EPERM/EACCES/EBUSY for about 3s and raises every other
code at once. All three publication renames use it, as does the HUD
helper's staging rename. The previous product's backup now sits beside
the output instead of inside withProduct's scratch tree, so a rollback
that also fails no longer has its only copy deleted by scratch cleanup;
the error names where it is.

Co-authored-by: dskwe <6286305+dskwe@users.noreply.github.com>
Co-authored-by: juliorobertoalvarez <39749815+juliorobertoalvarez@users.noreply.github.com>
Co-authored-by: bighamx <10262931+bighamx@users.noreply.github.com>
Co-authored-by: lzmuhioin <1814826+lzmuhioin@users.noreply.github.com>
2026-10-05 17:08:32 -05:00
Brooklyn Nicholson
f102b16c1f fix(profiles): reuse the canonical _path_key for cross-profile folder merge
Review on #132008: os.path.normcase/normpath derive case folding and
separator handling from the host, so a POSIX backend serving Windows
paths never folds C:\Users\me\Project with C:/Users/me/project, and
accented folders regress on the #65014 class. tui_gateway.project_tree
already defines the shape-derived identity the per-profile trees use;
reuse it so the merge agrees by construction with its inputs. The
casing test no longer monkeypatches normcase — it pins the real
normalization on POSIX, plus forward-slash and NFC/NFD fold tests
(red on the normcase key, green with _path_key).
2026-10-05 16:55:53 -05:00
David Metcalfe
798d5c3c68 fix: chain normpath before normcase, add merge-key test
Address sweeper review feedback on #92637:

1. Chain os.path.normpath before os.path.normcase to also handle
   trailing-slash and '..' duplicate shapes, not just casing.
2. Add unit tests for _merge_profile_tree covering case-insensitive
   merge, trailing-slash dedup, id fallback, and declared-vs-auto
   identity precedence.
2026-10-05 16:55:53 -05:00
David Metcalfe
426895d9e8 fix: deduplicate project folders in sidebar across profiles
The all-profiles project tree merge used raw path strings as keys.
On Windows (case-insensitive fs), the same folder with different
casings across profiles (e.g. C:\Users\me\Project vs
C:\Users\me\project) produced duplicate top-level entries in the
sidebar Projects section.

Backend: normalize the merge key with os.path.normcase() (no-op on
POSIX, lowercases on Windows).

Frontend: deduplicate projectModel by id before rendering — a safety
net that catches any merge edge cases the backend fix doesn't cover.

Closes #92588
2026-10-05 16:55:53 -05:00
Brooklyn Nicholson
57d82f87ae desktop: replay gateway-health statusbar keys into split locale modules
Main split ar.ts/zh-hant.ts into per-section modules, so the three new
statusbar keys (backend, messagingStopped, messagingDegraded) now live
in ar_chrome.ts/zh-hant_chrome.ts next to the statusbar section that
owns them, and the duplicate gateway-health-pill import left by the
cherry-pick is collapsed. Regenerated locales/_keys.desktop.json.

Co-authored-by: Noa <astraltrekkin@users.noreply.github.com>
2026-10-05 16:52:06 -05:00
Cursor Agent
aa43debbe0 desktop: sort gateway-health-pill import for perfectionist lint
CI failed on eslint perfectionist/sort-imports because the new helper
was imported after @/lib/icons. Also restore the padding-line blanks
eslint --fix wants around the derived messaging-down locals.

Co-authored-by: Noa <astraltrekkin@users.noreply.github.com>
2026-10-05 16:52:06 -05:00
Cursor Agent
2801f72c25 desktop: distinguish backend connection from messaging-gateway health
The status-bar pill labeled the serve-backend socket "Gateway ready",
which users read as Discord/Telegram being up. Relabel the pill to
Backend and fold StatusResponse messaging liveness into the detail
when the messaging process is stopped or a platform is unhealthy.

Co-authored-by: Noa <astraltrekkin@users.noreply.github.com>
2026-10-05 16:52:06 -05:00
Brooklyn Nicholson
3bf1032ffa test(desktop): pin the localized notification fallback; reword Studio comment
Review follow-up:

- The fallback-parameter assertions all passed the hardcoded English
  constant, so deleting the fallback param kept the suite green
  (verified: mutant survived 10/10). Pin the real per-locale values
  from the i18n files (zh, zh-hant, ja, ar) so a broken fallback path
  or an emptied locale string now flips a test; also pin the
  localized-fallback 140-char cap.
- Reword the 'old Studio builder' comments: that chain lived in the
  released 0.20.0 dist/client bundle quoted by #88488, not in this
  repo's source. This app's own chain was text.slice(0, 140) || i18n
  fallback and never read the title.
2026-10-05 16:51:34 -05:00
Brooklyn Nicholson
530651698d style(desktop): blank line before final return in turn-done helper (eslint --fix)
Co-authored-by: keepConcentration <phm543@gmail.com>
2026-10-05 16:51:34 -05:00
keepConcentration
6086a87cc9 fix(desktop): keep completion notification body off the session title
When the assistant reply is missing, fall back to a generic phrase instead of an empty body or the session title. Extract a tested helper so that title cannot sneak back into the chain.
2026-10-05 16:51:34 -05:00
Brooklyn Nicholson
1205863164 fix(desktop): sync the live composer draft on press so a stalled flush can't disable Send (#52950)
The Send button's disabled gate derives from AUI composer state, which
lags the contentEditable by the coalesced per-frame flush — the same
seam Enter guards against by reading the live DOM (#39630). A disabled
button swallows the whole press in Chromium (pointer-events-none drops
mousedown/click/form-submit with no feedback), so while the flush is
stalled the button reads dead even though Enter still sends (#52950).

Sync the live editor into composer state at the top of the press
(form pointerdown-capture, before the press's hit-test resolves), so
the gate is open when the click hit-tests. Skip mid-IME-composition:
compositionend owns that flush. Preserve the HUD drag wiring.
2026-10-05 16:51:16 -05:00
686f6c61
e9f5bdc178 fix(desktop): keep the chat scrollbar off the right-sidebar sash
The vertical sash grab band sat 1px into the leading pane and stole
the Windows overlay-scrollbar hit target. Keep the 8px band entirely
on the trailing side of the seam.
2026-10-05 16:50:58 -05:00
Brooklyn Nicholson
7abc06df13 fix(desktop): validate remembered owner hints against the row, not the foreground socket
Review finding on the resumeSession half of #97809: the staleness test
compared the remembered hint against activeGatewayConnectionId() — the
CURRENT foreground socket — but resumeSession is the function that moves
the foreground (ensureGatewayAgent below), and hints are minted from the
AMBIENT connection at create/open time (sdk openSession), which in the
all-profiles view is not the foreground. So 'names a connection that
isn't active' was true for exactly the correct hints, and a pathname-
driven resume with no capturedOwner deleted them.

Use the row as the authority instead — the same predicate as the click
path (openStoredSession) and the boot auto-restore
(repairOwnerHintsForRestore): a connection-tagged cached row pins the
hint's route; an untagged or absent row leaves no basis to trust an
explicit persisted hint, so it is dropped rather than dialed. Explicit
captures stay authoritative as given.

Tests: the two hint tests now pin the row-based contract (untagged row
drops a legacy local hint; a tagged row keeps an agreeing hint even when
the window is not looking at that connection; a disagreeing hint is
dropped), and the resets clear the persisted storage copy like the
integrations suite does.
2026-10-05 16:50:49 -05:00
Brooklyn Nicholson
430ada18ca fix(desktop): drop stale persisted session owner hints on resume
Older desktop builds persisted a `local` owner hint for sessions whose
rows carry no connection tag (the legacy primary-SSH path). Sidebar
clicks repair it (openStoredSession drops the hint for untagged rows),
but every pathname-driven resume (boot auto-restore, reconnect
re-resume, stranded-view self-heal) trusted the hint verbatim — dialing
the Mac backend for a remote session and dying with "session not
found".

Two seams now apply the same hygiene:

- resumeSession (use-session-actions/index.ts): a remembered hint is
  only honored when the connection it names is this window's live
  primary; a stale one is repaired (deleted), not just ignored, so it
  cannot re-poison the next resume or the session-scoped RPC ladder.
  An explicitly captured owner remains authoritative as given.
- useDesktopIntegrations boot auto-restore: the row ladder that
  resolves a remembered/route session now repairs the owner hint from
  the row (tagged row pins its exact route, untagged row drops the
  stale hint), matching what openStoredSession does for clicks.

Fixes #97809
2026-10-05 16:50:49 -05:00
Brooklyn Nicholson
78315ed0b2 fix(desktop): grapheme-safe truncation for cron error summaries and job text
Review follow-up on the surrogate-pair truncation fix:

- lastErrorSummary caps astral error text through the shared
  truncateText helper, so a surrogate pair can no longer be split at
  the 200-unit cap (slice()'s UTF-16 limit).
- truncateText now segments on grapheme clusters (Intl.Segmenter, the
  same idiom as profile-short-label.ts) instead of code points, so
  ZWJ sequences, flags, skin tones, and combining marks stay intact
  in job names/prompts.
- Tests port the grapheme shapes from profile-glyph.test.tsx and pin
  the astral error-text cap.
2026-10-05 16:50:33 -05:00
luyifan
3117383043 fix(desktop): truncate cron text on Unicode boundaries 2026-10-05 16:50:33 -05:00
Brooklyn Nicholson
2953ead658 fix(desktop): stamp optimistic pin rows so value copies can't confirm the guard
The WeakSet keyed optimistic-row tracking on object identity, but SessionInfo
rows are re-spread by value on ordinary user paths (touchSessionActivity on
send, applySessionTitle on rename). A copy carried the painted `pinned` while
losing WeakSet membership, so the next reconcile read it as a page agreeing
with our write and released the guard while the PATCH was still in flight —
re-opening the revert #128198 closes. Stamp the row with an enumerable symbol
instead: identity no longer matters, and only a row a real list page delivered
(a fresh object without the stamp) can confirm the guard.

Also covers the unpin half in the failed-write comment.

Found by @Enough1122 review on #128198.
2026-10-05 16:49:53 -05:00
Barclay McFadden
6891c65702 fix(desktop): keep sidebar pin toggles from reverting after the write guard
A local pin toggle left cached rows carrying the pre-toggle pinned flag; once the 10s write guard lapsed, the next reconcile (e.g. another shift+click) re-adopted the stale row and PATCHed it back. Paint the written value onto loaded rows at write time and never let those optimistic rows confirm the guard.

Fixes #128196
2026-10-05 16:49:53 -05:00
nikkoxgonzales
ffe73c37f0 fix(mcp): make sampling.tools capability opt-in via expose_client_tools 2026-10-05 16:47:32 -05:00
Brooklyn Nicholson
e92da2f906 test(child-env): fixture isolates the process-wide skill passthrough set
The child_env fixture still reset the allowlist via the removed
_allowed_env_vars_var ContextVar token, so every test using the fixture
(AttributeError at setup) failed across all Python/OS lanes once #90004's
process-wide set landed. Save/restore the set contents instead.
2026-10-05 16:46:53 -05:00
liuhao1024
3c4a46def3 fix(skills): make env_passthrough registrations visible across tool context snapshots
skill_view registers a skill's declared env vars (required_environment_
variables and the legacy prerequisites.env_vars) into the sandbox
passthrough allowlist. The allowlist was backed by a ContextVar, and
tool dispatch fans each tool call onto a worker whose context is a
copy_context() snapshot taken at submit time
(tools.thread_context.propagate_context_to_thread). The registration
therefore lived and died inside the skill_view worker's snapshot: the
submitting thread's context never saw it, and the next tool's snapshot
(execute_code, terminal) always started from an empty allowlist — so a
skill's env vars were never passed through, on any turn, in any session
(#90004). The config-based allowlist (terminal.env_passthrough) is a
module-level global and worked, which is why manually adding the var
there was the only workaround.

Store the skill-registered names in a process-wide set, matching the
config path's visibility. Cross-session exposure is limited to the
names — values still resolve per profile through
resolve_passthrough_value's secret_scope, so a name registered by one
session cannot read another profile's secret. clear_env_passthrough
keeps its reset semantics; a later skill_view re-registers on demand.
2026-10-05 16:46:53 -05:00
Brooklyn Nicholson
7f2271dee3 fix(desktop): harden every Windows SSH stdout parser against CLIXML pollution
Review on #132013 (Enough1122): $ProgressPreference and the CLIXML strip
only covered probeWindowsRemote, but the same SSH connection runs four
more PowerShell builders that parse stdout the same way.

- windowsUpdateMarkerProbeCommand: fail-closed gate in
  assertWindowsRemoteInstallUpdateClear reads .pop() of the last line —
  a progress record serialized after the final Write-Output used to win
  the pop and refuse SSH startup as kind='update-in-progress' (misleading
  safety verdict). Add-Type compiles C# in this script, well outside the
  probe's Get-Item/Get-Command progress suppression.
- helperCommand / atomicWindowsSpawnCommand: import the remote
  hermes_cli module (auto-load progress) and JSON-parse the last line.

Extract stripPowerShellNoise (BOM strip + CLIXML block removal + the
last-meaningful-line convention) and share it across all four parsers;
prepend $ProgressPreference to the three parsed builders. The fourth
builder, buildWindowsInteractiveCommand, drives a -tt pty the user reads
directly — nothing parses its output, so it keeps no strip.

On the stderr premise: the issue's error string is the _fail() default
('SSH error connecting to …'), i.e. CLIXML in stderr on a non-zero exit —
exec() throws before any parse in that case, so the stdout strip is
defense-in-depth for the stdout-channel race the issue's grep loop
shows; $ProgressPreference suppresses both channels at the source.

Tests: the marker gate stays CLEAR with CLIXML before/after the verdict
line (red on base via .pop()), LIVE still pauses startup; all three
parsed builders pin $ProgressPreference before $ErrorActionPreference;
helper tolerates CLIXML around its JSON.
2026-10-05 16:44:32 -05:00
Brooklyn Nicholson
2399ea7d32 test(desktop): satisfy the probe's arch check in the try/catch-adjacency fixture
The CLIXML-tolerant probe now requires parsed.arch; the script-shape test's
stub only returned os. Same assertion either way — the fixture feeds
probeWindowsRemote only to capture the emitted script.
2026-10-05 16:44:32 -05:00
Konstantin Khlopkov
dd479b3776 fix(desktop): keep SSH Windows probe stable against CLIXML progress-stream pollution 2026-10-05 16:44:32 -05:00
Brooklyn Nicholson
33687b0725 fix(desktop): advance archive pagination by page size, dedupe back-filled pins
Answers the #132009 review (Enough1122): listEveryArchivedSession used
sessions.length as the next offset, but pages carry back-filled pinned rows
past the window (the pin back-fill ignores the archived filter), so the
cursor over-advanced and skipped archived rows, and comparing that inflated
count against the archived-only total ended pagination early. The offset
now advances by the page size only, rows dedupe by id, and the loop ends
once the cursor reaches page.total (which pins can't inflate) or a page
comes back empty. Regression tests pin the page-size offset, the pin dedupe
and the 1000-archived/300-pin case; the existing pagination tests now use
full 200-row pages so they exercise the real cursor contract.
2026-10-05 16:44:14 -05:00
Brooklyn Nicholson
f6daab0eb7 test(desktop): mock the paginating archive loader in the unarchive fixture
The settings page now loads its archived list through
listEveryArchivedSession() (store module), so the '@/hermes' barrel
mock of listAllProfileSessions never intercepted the call and the
unarchive button never rendered in CI. Mock the store seam instead.
2026-10-05 16:44:14 -05:00
Brooklyn Nicholson
a0771ba046 test(desktop): merge archived-pagination tests with main's refresh-failure coverage
Main gained its own sidebar-archive.test.ts (refresh-failure coverage,
dbda53b8be) while #88504 was open; the cherry-picked pagination tests
landed on top of it. Merge both describes in one file and mock
'@/api/sessions' directly — the store now imports from the API module,
so the '@/hermes' barrel mock never intercepted the call.
2026-10-05 16:44:14 -05:00
Shawn Wang
b7a5f04327 fix(desktop): wire archive pagination through sessions API
Signed-off-by: Shawn Wang <32839114+enwaiax@users.noreply.github.com>
2026-10-05 16:44:14 -05:00
Shawn Wang
4da9edd79f fix(sessions): page aggregate lists past 500 rows
Signed-off-by: Shawn Wang <32839114+enwaiax@users.noreply.github.com>
2026-10-05 16:44:14 -05:00
Shawn Wang
2cc27f99e0 fix(desktop): paginate archived sessions
Signed-off-by: Shawn Wang <32839114+enwaiax@users.noreply.github.com>
2026-10-05 16:44:14 -05:00
Adolanium
8eeb6ec4c6 fix(zai): clamp GLM-5.3 thinking-off to enabled plus low
GLM-5.3 rejects thinking.type=disabled with HTTP 400 code 1210
(issue #85890, #96373). An explicit off preference now sends
enabled plus reasoning_effort=low. 4.5/4.6/5.2 still send disabled.
2026-10-05 16:43:54 -05:00
686f6c61
7bc350cfbb fix(verify): share only a full passing hermes verify across sessions
A targeted or failed CLI run must not clear another session's stale
flag or wipe its last_edit_at. Gate fan-out and the workspace fallback
on status=passed and scope=full.
2026-10-05 16:43:19 -05:00
686f6c61
5fe3a06729 fix(verify): clear workspace stale after hermes verify on another session
Standalone hermes verify records under session_id=default. The stop
guard keyed on the editing session never saw that pass. Share verify
events across sessions for the same root.
2026-10-05 16:43:19 -05:00
Brooklyn Nicholson
53ee917406 style(desktop): sort imports after conflict resolution 2026-10-05 16:43:11 -05:00
rainbowgits
518575750b fix(desktop): refresh readiness on OAuth success so a stale credential failure clears while unfocused
The status snapshot hook skips its poll while the window is unfocused, so a credential-readiness failure could remain on screen after an OAuth sign-in completed if the user stayed in the browser. Subscribe to the onboarding store and force one focus-independent readiness refresh on the auth-success transition, restoring the normal focus-gated cadence afterwards. Adds a regression test plus a guard that non-success transitions do not force a background poll.
2026-10-05 16:43:11 -05:00
Brooklyn Nicholson
293574bf14 fix(pty-bridge): sweep helpers of a non-leader child on close
The group-leadership guard nulls a non-leader pgid so the shared group is
never killpg'd, but that also skipped the helpers-outlive-leader sweep
(#76759): a SIGHUP-ignoring helper of a shared-group child kept the PTY
slave open past close(). Snapshot the child's descendants with psutil
BEFORE the parent is signalled (they reparent and become undiscoverable
once it exits), then send SIGHUP with the helper grace and SIGKILL the
survivors individually — mirroring browser_tool_lifecycle's non-leader
branch and the group sweep's leader-dead cadence.
2026-10-05 16:42:57 -05:00
Brooklyn Nicholson
1190fabf8c test(browser-tree): bypass the live-system guard in the reparented-descendant kill test
The grandchild in test_real_shared_group_child_descendants_are_killed
reparents to init the moment proc.kill() lands, and psutil's kill routes
through os.kill, which the guard only authorizes for the test process's
subtree — a walk that races with the reparenting. The blocked kill was
suppressed by the sweep's contextlib.suppress, so the grandchild survived
and the test flaked ~1.5% on the macOS CI lane (and 3/200 locally).
Signalling a reparented descendant is the exact contract under test, the
established pattern for live-signal tests; the flake is gone over 400
local full-file runs.
2026-10-05 16:42:57 -05:00
beardthelion
2f41514cae Address PR review feedback (#119150)
- process_registry._reap_untracked: delegate to _terminate_host_pid with the
  session's recorded host_start_time instead of a raw killpg -- pgid == pid
  only proves group leadership, and a reaped child recycled onto an unrelated
  group leader would still have been signalled. The helper revalidates the
  kernel start time and refuses on mismatch, and settles descendants from a
  psutil snapshot so no group signal is needed at all.
- browser_tool_lifecycle._legacy_kill_process_tree: in the non-leader path,
  snapshot descendants via psutil before proc.kill() and kill each
  individually -- a bare proc.kill() left grandchildren holding the capture
  pipe's write end, reintroducing the communicate() hang the helper exists
  to prevent.
2026-10-05 16:42:57 -05:00
beardthelion
956cd8dd3e fix(processes): signal a process group only when the child leads it
_kill_process_tree fallback and three latent killpg callers resolved
os.getpgid(child) and signalled the result unconditionally. A child that
shares the caller's group resolves to that group, so TERM+KILL took down
the whole Hermes tree; a reaped-and-recycled pid resolves to a foreign
leader's group. Signal the group only when pgid == pid, the rule
deadline.kill_process_tree and _subprocess_compat already use, and fall
back to signalling the direct child otherwise.
2026-10-05 16:42:57 -05:00
Brooklyn Nicholson
00067849ce test: keep the OPENAI .env fallback key OpenRouter-shaped
The sk-or- shape guard on unbound OPENAI_API_KEY (openrouter.ai context)
is deliberate: a real OpenAI key must never reach a third-party host.
The new fallback test used a neutral string, so the guard correctly
refused it. Make the fixture key sk-or- shaped so the test exercises the
fallback it means to.
2026-10-05 16:42:43 -05:00
liuhao1024
0742b460a3 fix(runtime): let a raw op:// reference in .env yield to the resolved scoped key
Lift the credential pool's op:// carve-out into
hermes_cli.config.get_env_value_prefer_dotenv so the OpenRouter/OPENAI
fallback rungs added here cannot send an unresolved op:// reference as
the API key when the 1Password source already resolved the key into the
active scope. Mirrors the pool's precedence test on the resolver path
and covers the OPENAI_API_KEY .env rung.
2026-10-05 16:42:43 -05:00
liuhao1024
b6e8e657cf fix(runtime): read the OpenRouter fallback keys from .env after an exhausted pool entry 2026-10-05 16:42:43 -05:00
liuhao1024
5b671e29a4 fix(tts): make tts.provider the authoritative backend selector
The per-call provider argument on text_to_speech (added via #73513,
closing #47459) let the model reroute speech to any configured backend,
contradicting the tool's own schema header ("Voice and provider are
user-configured ... not model-selected") and leaving compact channels
unable to keep a house voice without prompt-layer appendices.

- drop provider from the model-facing TTS_SCHEMA
- stop forwarding args["provider"] in the registry handler
- keep the function argument for internal/test callers, but a value
  disagreeing with the configured tts.provider is now ignored with a
  warning (both text_to_speech_tool and _text_to_speech_single)

Fixes #90109
2026-10-05 16:42:22 -05:00
SmokeDev
0e73d1d3b6 fix(discord): track live voice credentials across re-keys instead of snapshotting at start()
VoiceReceiver.start() captured conn.secret_key (by value) and
conn.dave_session (by reference) exactly once. Both go stale in normal
operation:

- op 4 SESSION_DESCRIPTION rotates the transport secret_key on every
  voice (re)connect — the snapshot then fails NaCl decrypt forever.
- voice_state.reinit_dave_session() REPLACES conn.dave_session with a
  NEW DaveSession when none existed yet, so a receiver that starts
  before DAVE finishes negotiating keeps None forever and treats E2EE
  as off — every frame reaches opus as ciphertext for the whole
  session.

The failure is silent by construction: decrypt failures warn for the
first 10 packets then go dark, and shredded opus output raises nothing.
The operator-visible symptom is "the bot ignores me."

Fix — resolve, don't copy:

- Credentials live in ONE tuple (secret_key, dave_session,
  dave_protocol_version, dave_downgraded), re-resolved from the live
  connection and swapped in a single reference store; the receive
  thread reads one consistent generation per packet, so a refresh can
  never pair a new key with an old session.
- Refresh triggers: the voice-ws hook on op 4 and op 24 (the hook runs
  AFTER received_message dispatches the op, so it reads the new
  values); membership changes in the bot's channel (DAVE epoch bump)
  via the existing on_voice_state_update handler; and a
  25-consecutive-NaCl-failure streak as the catch-all, retried every
  streak window while the failure persists.
- Unmapped-SSRC hole closed without recreating it elsewhere: with E2EE
  actively on (protocol_version > 0 and outside a passthrough window),
  frames for an SSRC with no user mapping are dropped (after the
  existing sole-member inference) instead of being fed to opus as
  ciphertext.  A non-null session alone is NOT the predicate — the
  session object survives protocol downgrades to 0 and passthrough
  transitions, where plaintext is legitimate.  Passthrough windows
  mirror discord.py's own set_passthrough_mode calls: op 21
  pending-downgrade opens 120s; op 22 opens a 10s grace ONLY on the
  dave_downgraded True -> False edge (upgrade-after-downgrade), with
  replace-semantics so the short grace supersedes a residual long
  window; a receiver starting mid-pending-downgrade seeds the window
  from conn.dave_pending_transitions.
- Silence made visible: decrypt failures keep emitting one warning per
  250 after the first 10, and stop() logs decode-health counters
  (ok / decrypt_failed / dave_unmapped_dropped).

Tests: tests/gateway/test_discord_voice_rekey.py (24) — credential
resolution/rotation, the late-DAVE-session upgrade, failure-streak
refresh + reset, op 4/24/5 hook behavior, original-hook passthrough,
the DAVE-state drop predicate (downgraded-plaintext decodes, op-21
window decodes, expired window drops, upgrade edge replaces the long
window, same-version and duplicate op 22 open nothing,
mid-pending-downgrade startup seeds), unmapped-drop vs mapped flow,
sole-member rescue, tuple-generation consistency, teardown health
line.  Adjacent voice_command / voice_mixer / send / opus suites green
under the diff (111 passed).

Fixes #77968
2026-10-05 16:42:08 -05:00
Brooklyn Nicholson
3a009f97c1 fix(agent): detect pronounless action acknowledgements (#72692)
Port of #72710 by @foma-agent onto current main.

With provider: copilot-acp the model sometimes announces its next action
and ends the turn without emitting a tool call, in terse log style with
no first-person lead-in: "Brief written. Creating the session now."
The intent-ack detector only matched first-person future acks
(_ACK_FUTURE_RE), so the task stalled until the user sent another
message.

looks_like_codex_intermediate_ack now also accepts a narrowly bounded
pronounless action clause: a present-participle action verb that begins
the response or follows a sentence/status/newline boundary, plus an
explicit transition cue ("now", "on Copilot via acpx", "the actual log",
"with corrected arguments", "whether ... healthy", ...). Questions,
completed reports ("Testing complete."), bare status counters, retry
counters and numbered/option lists stay final.

The 16-commit PR branch predates main's agent_runtime_helpers refactor
(dead-predicate prune eb67765c58, module-level _ACK_* constants), so the
final state is re-applied by hand: main's constants are kept, the
pronounless detector folds into the guardrail chain, and the PR's test
corpus (21 announcements that continue, 70 finals/questions/status
lists that do not) is restored minus the test for the pruned
intent_ack_continuation_enabled helper.

Co-authored-by: foma <foma@bokonon.ai>
Fixes #72692
2026-10-05 16:41:57 -05:00