Commit graph

19056 commits

Author SHA1 Message Date
Nick Edson
201be3e546 fix(gateway): reserve retrying Photon listener ownership 2026-07-28 21:45:52 -07:00
Nick Edson
c608a6937a fix(gateway): release failed Photon listener claims 2026-07-28 21:45:52 -07:00
Nick Edson
a908c62d28 fix(gateway): guard Photon sidecar listener collisions 2026-07-28 21:45:52 -07:00
Nick Edson
cf19ac8ff3 fix(gateway): prevent duplicate Photon sidecar storms 2026-07-28 21:45:52 -07:00
Teknium
88ff722f94 docs(api-server): document profile-bound HTTP auth from #72285
The multiplexed listener now rejects the default API_SERVER_KEY on
/p/<profile>/ prefixes (fail-closed per-profile keys). Add the
multi-profile routing section with an explicit breaking-change callout
for the next release notes.
2026-07-28 21:45:44 -07:00
Teknium
41233e19c6 fix(gateway): forward failure_reason through the empty-response return path
Sibling of #64686: _run_agent's empty-final_response early return dropped
failure_reason (and #64686 only fixed the non-empty path), so downstream
consumers (TUI billing surface, transient-failure persistence) lost the
structured reason exactly when a failed run produced no text.

Also hardens the two BasePlatformAdapter identity checks (edit_message /
delete_message) with getattr so duck-typed adapters without the attribute
mean 'capability absent', not AttributeError — this was crashing the
send_progress_messages path for minimal adapters and test fakes.
2026-07-28 21:45:44 -07:00
Teknium
d4ff566232 fix(model-set): persist base_url/api_key on auxiliary slot assignments
Sibling of #65254 (main-slot endpoint preservation): the auxiliary scope of
POST /api/model/set dropped the request's base_url/api_key on the floor, so
an aux slot pinned to a custom/local endpoint silently depended on
model.base_url — and broke the moment the main slot switched away and
cleared it. The aux resolver already reads auxiliary.<task>.base_url/api_key
(_resolve_task_provider_model); this persists them.

Desktop side: setAuxiliaryToMain / applyAuxiliaryDraft now carry the
user-defined provider's api_url as base_url, mirroring applyMainModel.
2026-07-28 21:45:44 -07:00
Teknium
07e931fcb4 feat(buzz): WebSocket inbound transport — NIP-42 auth, live DM discovery, poll fallback
Consolidates the native-transport half of PR #73636 by @ScaleLeanChris
onto the merged adapter: persistent NIP-42-authenticated Nostr WebSocket
subscription as the default inbound path (transport=auto|websocket|poll),
kind-44100 membership events for live DM discovery, since-timestamp
resume on reconnect with bounded exponential backoff, and automatic
fallback to CLI polling when the WS can't be established. Events route
through the same _handle_event() pipeline as the poll loop, so de-dupe,
mention gating, p-tag DM latching, and allow-lists behave identically on
both transports. Outbound stays on the CLI (one-shot sends never race a
WS auth handshake — his design).

E2E verified against a real in-process websockets relay: NIP-42
challenge -> signed kind-22242 AUTH (event id re-derived server-side) ->
REQ subscription -> EVENT dispatch -> clean disconnect.

Co-authored-by: ScaleLeanChris <chris@scalelean.com>
2026-07-28 21:45:34 -07:00
ScaleLeanChris
21c7b806a3 feat(buzz): dependency-free Nostr signing module (NIP-42 auth, BIP-340)
Extracted verbatim from PR #73636 by @ScaleLeanChris — nsec/hex key
decoding, secp256k1 point math, BIP-340 Schnorr signing, and NIP-42
AUTH event construction with optional NIP-OA owner attestation tag.
2026-07-28 21:45:34 -07:00
Teknium
ceaa7880ee fix(photon): un-shadow the U+FFFC deferred-wait handler; fix stale sidecar-deps fixture
Two independent cross-PR collisions red on main (slice 8/8):

1. fd4f756492 (salvaged from stale #54514) added an early 'drop U+FFFC
   placeholder' return at the top of _dispatch_inbound — written before
   the deferred-wait handler (6b91b50c6e/afab7ed46e) existed further
   down the same function. The early return shadowed it: _pending_fffc
   never populated, no attachment-timeout tracking, 4 tests red. Remove
   the duplicate block; the deferred handler already drops the
   placeholder AND tracks/cancels/warns.

2. 9cf2046081 tightened sidecar_deps_installed() to require
   node_modules/spectrum-ts, but test_runtime_record's _patch_spawn
   fixture still created only bare node_modules/ — 2 tests red. Mirror
   a real completed install.

tests/plugins/platforms/photon: 164/164 after; 158/164 before.
2026-07-28 21:28:25 -07:00
Teknium
0f64557c06 fix(wake): coerce dead onnx->tflite on macOS ARM64; clear stale voice turn-timeout
Two follow-ups from the voice PR (#70509).

1. macOS ARM64 onnx migration. Existing users who pinned
   openwakeword.inference_framework=onnx before the tflite fix landed kept a
   wake word that arms but never fires (ONNX's embedding model is broken on
   Apple Silicon, upstream #336). New resolve_inference_framework() honors an
   explicit framework everywhere ONNX actually works, but coerces the one
   provably-dead combination (explicit onnx + macOS ARM64) to tflite with a
   one-time warning. No config mutation; empty still falls back to the platform
   default. Both read sites (engine init + requirements check) route through
   the shared resolver.

2. Voice turn-timeout leak. Each listen cycle reassigned turnTimeoutRef
   without clearing the prior 60s timer, so a stale timer from an earlier
   cycle could fire handleTurn() mid-way through a later listen — after enough
   idle re-listens this wedged the loop into a non-re-arming state (the
   'voice chat deactivates after ~a minute' report). Clear before re-arm.

Tests: 64 wake tests (added onnx-coercion / intel-kept / tflite-kept /
empty-default cases; updated the stale 'explicit onnx kept on ARM64' test
that encoded the old broken behavior), 39 desktop voice/wake vitest, tsc +
eslint clean.
2026-07-28 19:40:25 -07:00
brooklyn!
533d633ab9
Merge pull request #73774 from NousResearch/bb/desktop-messaging-icon-churn
perf(desktop): memoize PlatformAvatar + StatusDot (messaging icon churn)
2026-07-28 21:16:19 -05:00
hermes-seaeye[bot]
8a342a0002
fmt(js): npm run fix on merge (#73780)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-29 02:10:16 +00:00
brooklyn!
40a53ca031
Merge pull request #73698 from NousResearch/bb/desktop-overlay-perf
perf(desktop): kill sidebar + overlay render churn from hot store subscriptions
2026-07-28 21:01:26 -05:00
Brooklyn Nicholson
d76c7f5409 perf(desktop): memoize PlatformAvatar + StatusDot to kill messaging churn
The sidebar's messaging section renders one PlatformAvatar (labelIcon) and
StatusDot per platform group. The sidebar re-renders on every streaming tick
($sessions/$workingSessionIds/$messagingSessions churn), and both leaves were
unmemoized — so every platform's avatar + dot re-rendered on each delta even
though their props (platformId/tone/className) never changed.

- PlatformAvatar: memo(forwardRef(...)) — pure fn of platformId/name/class/style
- StatusDot: memo() — pure fn of tone/class

Measured (Messaging open, settled, 2 sessions streaming, 3s window):
  before: 96 wasted (PlatformAvatar 32, StatusDot 32, + brand icons)
  after:  0 wasted

Both are shared primitives; the memo also helps every other consumer
(session rows, gateway menu, session tiles) that renders them under a hot
parent.
2026-07-28 20:51:04 -05:00
hermes-seaeye[bot]
ada389004f
fmt(js): npm run fix on merge (#73770)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-29 01:49:23 +00:00
brooklyn!
f088fa5070
Merge pull request #73764 from NousResearch/bb/message-age-fallback
fix(desktop): stop message age falling back to the 1970 epoch ("20663d ago")
2026-07-28 20:40:18 -05:00
Brooklyn Nicholson
50d0de0358 fix(desktop): satisfy lint — import order, unused import, exhaustive-deps
- command-center/command-palette/skills: import ordering + drop unused
  HermesGateway type
- settings: wrap openSubView/openProviderView/openKeysView in useCallback so
  the navGroups memo deps are honest and stable
- command-center: add setSection to navGroups memo deps
2026-07-28 20:38:04 -05:00
Brooklyn Nicholson
64bdbd7f3c fix(desktop): stop message age falling back to the 1970 epoch ("20663d ago") 2026-07-28 20:33:25 -05:00
Brooklyn Nicholson
4acc162712 chore: overlay churn measurement probes (sweep + median A/B) 2026-07-28 20:26:58 -05:00
Brooklyn Nicholson
6fc01f8ab8 perf(desktop): memoize sidebar rows + status dots + artifact cells
The sidebar stays mounted beneath every overlay/page, and it subscribes to
$sessions + $workingSessionIds — both tick on every streaming token. An
unmemoized SidebarSessionRow re-rendered the whole list (Codicon, labels,
status dots) on each delta, and that churn bled into every overlay opened on
top: Cron, Profiles, Agents, Starmap, Webhooks, Command Center, Settings.

- SidebarSessionRow: memo() with a custom comparator that ignores the pure
  id-forwarding callbacks (fresh closures by design) and compares only the
  data that changes what the row paints. Rows bail out while siblings stream.
- SessionStatusDot: the 5 $...SessionIds arrays now read via useStoreSelector
  returning this session's boolean, so a dot repaints only when ITS OWN
  membership flips, not on every array tick.
- Artifacts: stable cellCtx (useMemo) + memoized Primary/Location/Session
  cells so a link-title fetch on one row stops re-rendering the whole table.

Measured before/after (2s idle, sessions streaming), sidebar-fed overlays:
  Cron 407->~30 wasted, Profiles 732->~80, Agents 132->9, Starmap 188->56,
  Webhooks 154->22.
2026-07-28 20:23:10 -05:00
Teknium
76173ba8cd lint: suppress windows-footgun false positive on POSIX-gated os.kill probe 2026-07-28 18:21:01 -07:00
Teknium
f041c95b7f fix(send_message): pass photon DM chat GUIDs through as explicit targets
'photon:any;-;+1555...' targets matched no parser pattern, so
_handle_send bounced them off the channel directory and failed
resolution even though the adapter accepts the GUID verbatim (the
react handler already passed them through). Recognize the DM chat
GUID shape (mirrors the adapter's _DM_CHAT_GUID_RE) in
_parse_target_ref for photon only.
2026-07-28 18:21:01 -07:00
Teknium
e79d316a04 fix(photon): persist sidecar runtime record so cron/standalone sends work
The sidecar auth token is generated at spawn (secrets.token_hex) and
existed only in the gateway process memory + sidecar child env, so
_standalone_send from cron subprocesses, hermes send, or the dashboard
structurally could not authenticate (#69960).

The adapter now writes <hermes-home>/runtime/photon-sidecar.json
({port, token, pid}, 0600, atomic tempfile+os.replace) once the sidecar
passes its /healthz readiness check, and deletes it in _stop_sidecar,
on every startup-failure path, and at disconnect so a stale record
never outlives a dead sidecar. _standalone_send falls back to the
record when PHOTON_SIDECAR_TOKEN is unset, validating the recorded pid
is alive first; a stale record yields a clear 'gateway appears to be
down' error. Docs note the gateway-must-be-running requirement and the
Photon-side shared-line initiation policy (#51897).
2026-07-28 18:21:01 -07:00
Teknium
4f65f56279 chore: contributor mappings for DI404N and JoaoMarcos44 2026-07-28 18:20:06 -07:00
Teknium
893c99bca1 test(photon): stub token validation in setup tests to avoid network
Maintainer follow-up: _cmd_setup now validates existing tokens (#72763
salvage); the pre-existing setup tests monkeypatch a stored token, so
without stubbing check_photon_token_valid they'd hit the real dashboard
API and hang.
2026-07-28 18:20:06 -07:00
Teknium
1887c4e820 test(photon): allow auth.lock sentinel in temp-file leak assertions
Maintainer follow-up: #60427's leak tests predate #64902's cross-process
lock, whose auth.lock sentinel legitimately persists next to auth.json.
2026-07-28 18:20:06 -07:00
DI404N
6294703dec fix: disable httpx proxy for Photon sidecar localhost connections
All Photon sidecar HTTP requests target 127.0.0.1 — they should
never be routed through a system HTTP proxy. When trust_env=True
(the default), httpx picks up macOS system proxy settings and
routes localhost requests through the proxy. If the proxy returns
a spurious response (e.g. 502), _reap_stale_sidecar() interprets
it as 'port in use by a non-sidecar process' and refuses to start,
yielding: 'pids: unknown, not a Photon sidecar'.

Set trust_env=False on all five httpx.AsyncClient call sites in
the Photon adapter so localhost sidecar communication bypasses
the system proxy entirely.
2026-07-28 18:20:06 -07:00
joaomarcos
9cf2046081 fix(photon): unify sidecar-deps check, harden log unlink and truncation
Addresses teknium1 review on #50983:

- cli.py's `hermes photon status` and adapter.py's _start_sidecar() still
  used the old node_modules/-existence check while check_requirements()
  had moved to a spectrum-ts content check. Extracted the check into a
  shared sidecar_deps_installed(), used by all three, so an
  empty/partial node_modules/ (aborted npm install) is rejected
  consistently instead of only in check_requirements().
- _install_sidecar()'s success-path _NPM_ERROR_LOG.unlink() only caught
  FileNotFoundError, so a PermissionError/OSError on a locked file would
  propagate. Broadened to OSError.
- npm stderr was truncated only when read back in check_requirements();
  an unbounded stderr was written to disk on every failed install. Now
  truncated to _NPM_ERROR_LOG_MAX_CHARS before write_text().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 18:20:06 -07:00
joaomarcos
de5c39c033 fix(photon): surface npm install failures in check_requirements() diagnostic chain
Problema
--------
Quando o npm install do sidecar Photon falhava, o Hermes descartava toda
evidencia e continuava normalmente - deixando o adapter de iMessage
silenciosamente ausente, sem nenhuma mensagem de erro acionavel.

Tres falhas independentes formavam o caminho de falha silenciosa:

1. check_requirements() sem logging
   Cada branch de return False retornava sem emitir nenhum log. O core em
   platform_registry.py so consome o bool de check_fn() e loga uma mensagem
   generica com o install_hint - sem acesso ao motivo real da falha.

     if not HTTPX_AVAILABLE:     return False  # sem log
     if not shutil.which(node):  return False  # sem log
     if not node_modules.exists: return False  # sem log

2. node_modules/ parcialmente criado passava o guard (Risk 2)
   npm cria node_modules/ antes de abortar em ENOSPC, timeout de rede ou
   EACCES. O diretorio existia, check_requirements() retornava True (falso
   positivo), o adapter era registrado, e o crash acontecia em runtime com
   um erro de modulo ausente aparentemente nao relacionado ao setup.

3. stderr do npm descartado (Risk 3)
   subprocess.run sem stderr=PIPE. O output de erro aparecia no terminal
   durante o setup e sumia depois - diagnostico impossivel em CI/CD, Docker,
   VPS headless, e qualquer reinstalacao posterior.

Correcoes
---------
adapter.py - check_requirements() agora loga por branch:
  - httpx ausente       -> logger.warning com nome do pacote
  - node nao no PATH    -> logger.warning com nome do binario e env var
  - spectrum-ts ausente -> logger.debug com path do sidecar + ultimo erro npm
    (DEBUG nao WARNING: estado normal pre-setup; check_fn() e chamado de
    5 hot paths do core incluindo polling do /api/status)

adapter.py - content check em vez de existence check (Risk 2):
  antes:  if not (_SIDECAR_DIR / node_modules).exists()
  depois: if not (_SIDECAR_DIR / node_modules / spectrum-ts).exists()
  spectrum-ts e a unica dependencia do package.json. Checar sua presenca
  garante que instalacao parcial/abortada e detectada no boot do gateway,
  nao na primeira mensagem recebida via gRPC.

cli.py - stderr capturado e persistido (Risk 3):
  subprocess.run passa agora stderr=subprocess.PIPE, text=True em ambas as
  chamadas (npm ci e npm install fallback). O stderr capturado e:
    - impresso em sys.stderr imediatamente (output visivel no terminal)
    - persistido em _NPM_ERROR_LOG = sidecar/.photon-npm-error.log se
      returncode != 0, limitado a 300 chars
    - apagado de _NPM_ERROR_LOG se returncode == 0 (evita erro stale)
  check_requirements() le _NPM_ERROR_LOG quando spectrum-ts esta ausente e
  inclui o conteudo no DEBUG log - o erro do npm sobrevive ao terminal, ao
  restart do gateway e a reinicializacao da maquina.

sidecar/.gitignore - adicionado node_modules/ e .photon-npm-error.log.

Isolamento - sem impacto no core:
  - check_fn() continua retornando apenas bool; core nao e modificado
  - Logging usa namespace plugins.platforms.photon.adapter, isolado de
    gateway.* e hermes_cli.*
  - Cada plugin tem seu proprio check_requirements() independente
  - OSError no write/read de _NPM_ERROR_LOG e silenciado - nunca propaga

Testes - 24/24 passando:
  test_check_requirements_risks.py (7 testes):
    WARNING emitido quando httpx ausente
    WARNING emitido quando node nao no PATH
    DEBUG emitido (nao WARNING) quando spectrum-ts ausente, com path
    node_modules/ vazio agora retorna False (Risk 2 resolvido)
    _NPM_ERROR_LOG escrito no stderr do npm em falha
    _NPM_ERROR_LOG apagado apos npm bem-sucedido
    erro npm aparece no DEBUG log quando node_modules ausente

  test_npm_error_log_regression.py (9 testes - vetores de falha da solucao):
    return code contrato intacto (0 em sucesso, nao-zero em falha)
    OSError no write do log silenciado, exit code ainda propagado
    OSError no read do log silenciado, check_requirements() retorna False
    stderr vazio nao cria arquivo de log
    proc.stderr=None nao lanca AttributeError
    log stale apagado apos reinstall bem-sucedido
    DEBUG emitido mesmo sem log de erro (setup pela primeira vez)

Closes #50981

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 18:20:06 -07:00
liuhao1024
703de9bacb fix(photon): skip secret regeneration when existing credentials are valid
hermes photon setup unconditionally called regenerate_project_secret()
on every re-run, invalidating the credential held by a running sidecar
and causing all outbound sends to fail with AuthenticationError.

Now validates existing credentials via a lightweight list_users call
before deciding to regenerate. Only rotates when no credentials exist
or the existing ones are invalid, and warns the user to restart the
gateway when rotation occurs.

Fixes #50755
2026-07-28 18:20:06 -07:00
Teknium
eccf39dded refactor(photon): route setup token check through validate_photon_token
Maintainer follow-up to the #72763 salvage: check_photon_token_valid()
now delegates to the existing validate_photon_token() (session lookup +
/api/projects/) instead of a bespoke get-session-only probe, since the
device flow can mint tokens that pass the session check but fail the
project APIs that setup actually uses. Semantics preserved: definitive
auth rejection = stale, transient errors = probably-valid.
2026-07-28 18:20:06 -07:00
webtecnica
cd158466d1 fix(plugins/photon): clear stale token and re-enable channel after setup (#72763)
Two related bugs in hermes photon setup / gateway_setup:

Bug 1 -- stale token reused (401)
----------------------------------
_cmd_setup reused an existing dashboard token without validation.
The device token has a short TTL (~3-4 days observed); reusing a
stale token caused every management API call (find_project_by_name,
regenerate_project_secret, etc.) to fail with 401.  The operator
saw confusing "spectrum provisioning failed: 401" errors.

Fix: check GET /api/auth/get-session before using the stored token.
On 401/403, clear the stale token with clear_photon_token() and
fall back to a fresh device-login flow automatically.

Bug 2 -- channel left disabled after successful setup
-----------------------------------------------------
After all five provisioning steps completed, config.yaml still had
photon.enabled: false, so the gateway never loaded the Photon
adapter.  Every inbound iMessage hit Photon's offline auto-responder
without the operator being notified.

Fix: call write_platform_config_field('photon', 'enabled', True,
raw=True) as a final setup step so the gateway picks up the freshly
configured channel on its next start.

New public API in auth.py:
  - clear_photon_token()   -- discard stored token from auth.json
  - check_photon_token_valid(token) -- lightweight session-check test

References: #72763
2026-07-28 18:20:06 -07:00
pierrenode
b378bc6fab fix(photon): serialize auth.json writes with the shared cross-process lock
store_photon_token/store_project_credentials/store_user_numbers read-modify-
wrote auth.json without hermes_cli/auth.py's _auth_store_lock(), the
cross-process flock every other writer of that file (credential_pool
refresh, model_switch, fallback_cmd, nous_portal adapter, etc.) already
holds. A concurrent write from either side during the unlocked
load-mutate-save window silently drops the other side's update.
2026-07-28 18:20:06 -07:00
solyanviktor-star
c3531cfac2 fix(photon): close the raw fd when os.fdopen fails in _save_auth
Review follow-up: if os.fdopen() raised before taking ownership of the
descriptor returned by os.open(), the cleanup handler unlinked the temp
file but leaked the fd. Close it explicitly on that path, mirroring the
credential-writer cleanup from #62837.

Strengthen the tests so the old writer could not pass them: an os.open
spy asserts O_CREAT | O_EXCL and an explicit 0o600 mode (the final-mode
check alone was also satisfied by the post-write chmod), and a forced
fdopen-failure test asserts the raw fd is closed and no temp file is
left behind.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 18:20:06 -07:00
solyanviktor-star
2b30744c34 security(photon): create auth.json temp file with 0o600 atomically
_save_auth() wrote the bearer token with tmp.open('w') — created at
process umask (typically 0o644) — and only chmod'ed to 0o600 after the
write, leaving a window where the token sat world-readable. The temp
name was also fixed and predictable (auth.json.tmp), so it could be
pre-planted (symlink attack).

Create the temp file with os.open(O_WRONLY|O_CREAT|O_EXCL, 0o600) and a
per-process random suffix, fsync before the atomic replace, and clean
the temp file up on failure. Mirrors hermes_cli/auth.py:_save_auth_store
(#19673, #21148), which hardened the same pattern in the core writer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 18:20:06 -07:00
Teknium
9044c4b836 style: eslint perfectionist import order in session-source test 2026-07-28 18:19:12 -07:00
Teknium
f65cde9fd5 chore: map contributor email for Bounty13 2026-07-28 18:19:12 -07:00
Bounty13
f2954945be test(desktop): add regression test for Photon messaging source (#46761)
Assert isMessagingSource('photon') is true and that Photon/iMessage search aliases resolve, so a silent removal of the photon entry from MESSAGING_SESSION_SOURCE_IDS fails CI instead of regressing the sidebar section added in #47395.
2026-07-28 18:19:12 -07:00
Bounty13
560be7dbf2 feat(desktop): add Photon iMessage section to sidebar
Register Photon as a messaging platform in the Desktop sidebar:
- Add Photon brand icon (three-bar mark) to PlatformAvatar catalog
- Add photon to SOURCE_LABELS, SOURCE_ALIASES, and MESSAGING_SESSION_SOURCE_IDS

Closes #46761
2026-07-28 18:19:12 -07:00
Teknium
df841d342c fix(state): complete the bounded-merge protocol — usermerge floor, progress-bounded continuation, tolerate mid-rebuild missing index
Follow-up on top of #65554 (@the3asic):

- Lower FTS5 'usermerge' to its minimum of 2 (persisted in the config
  shadow table, applied once per SessionDB instance). Without this a
  positive-rank 'merge' skips any level holding fewer than 4 segments
  (SQLite FTS5 §6.8), so the fragmented-index case the cadence targets
  never converges.
- Run up to _FTS_MERGE_COMMANDS_PER_PASS (4) bounded merge commands per
  index per cadence, stopping early on the documented no-progress
  signal (total_changes delta < 2). Each command is its own implicit
  transaction, so the write lock is released between commands.
- Skip a missing messages_fts instead of raising: the chunked
  optimize-storage rebuild legitimately drops + backfills FTS tables
  while writers keep running; warning every 1000 writes for the whole
  backfill window would be noise, and optimize_fts() has always
  treated missing tables as skippable.
- Replace traced-SQL shape assertions with behavioral tests: real
  fragmented-index convergence (automerge suppressed, 60 segments) and
  a 3-segment below-default-usermerge compaction test that fails
  against a bare positive-rank merge (sabotage-verified).

Validation on a sqlite3.backup() copy of a real 10.7 GB production
state.db (1.49M messages, 1.3 GB + 2.9 GB FTS shadow tables):
worst per-command write-lock hold 41.8 ms (was 9.2 s / 18.1 s per
index with 'optimize'), search results byte-identical, fts5
integrity-check and PRAGMA integrity_check clean, steady-state pass
0.0 ms.
2026-07-28 18:18:17 -07:00
3ASiC
db16c5ce51 fix(state): bound routine FTS merge work 2026-07-28 18:18:17 -07:00
Teknium
52b9cf1e0e test: de-pin aux default literals in provider-parity tests
Sibling of the test_auxiliary_client.py cleanup — these two assertions
pinned the OpenRouter/Nous aux default as a frozen literal and broke on
the 3.6-flash bump (CI slice 5). Reference _OPENROUTER_MODEL/_NOUS_MODEL
instead so the next default rotation can't break them.
2026-07-28 18:17:56 -07:00
Teknium
63fc810b95 fix(gemini): sweep hardcoded Gemini default models to gemini-3.6-flash (#32360)
gemini-2.5-flash shuts down Oct 16 2026 (Google deprecation schedule)
and gemini-3-flash-preview is superseded. Update every hardcoded
default to the current GA flash model:

- gemini_native_adapter: probe_gemini_tier + _create_chat_completion
  default params; free-tier guidance de-pinned from a specific model's
  RPD number so it doesn't stale again
- auxiliary_client: gemini/kilocode fallback aux models,
  _OPENROUTER_MODEL, _NOUS_MODEL -> google/gemini-3.6-flash
  (verified live on both OpenRouter and Nous portal /models)
- provider plugins: kilocode + vertex default_aux_model
- hindsight memory plugin: gemini provider default
- setup wizard gemini list: 3-flash-preview -> 3.6-flash (matches the
  curated picker catalog)
- tests: aux-client assertions that pinned the old default literal now
  reference the _NOUS_MODEL constant, so the next default bump can't
  break them (change-detector cleanup)

Fixes #32360.
2026-07-28 18:17:56 -07:00
MattMaximo
482d1ab0f6 fix(photon): unwrap dashboard project responses 2026-07-28 18:17:52 -07:00
liuhao1024
21264c4343 fix(photon): mark adapter as not supporting message editing to suppress streaming cursor
Photon (iMessage) has no real edit API for already-sent messages. When
streaming completes, the gateway attempts to edit the message to remove
the streaming cursor (▉). Without edit support, this cursor gets stuck
in the final message, corrupting Unicode characters.

This change sets SUPPORTS_MESSAGE_EDITING=False on PhotonAdapter, which
causes the gateway to suppress the streaming cursor entirely for this
platform (via _effective_cursor in gateway/run.py). This prevents the
stale tofu square (▉) from appearing in streamed iMessage responses.

Fixes #49253
2026-07-28 18:17:52 -07:00
kelsia14
fd4f756492 fix(photon): ignore iMessage media placeholders
Cherry-picked from PR #54514; dropped frozen AUTHOR_MAP hunk in scripts/release.py, contributor mapping added instead.
2026-07-28 18:17:52 -07:00
tachyon-r
be05741349 test: isolate delegated and Photon environment state 2026-07-28 18:17:52 -07:00
Teknium
e807b7106c test: set returncode on fake Popen proc (main's player loop reads returncode, not wait()) 2026-07-28 18:12:26 -07:00
Teknium
050461ec83 fix: hoist STT credential read guard to the public transcribe_audio entry point
The rebase onto #73510's prepare/dispatch split left the guard inside
_transcribe_prepared_audio, where source validation ran first and a
blocked .env surfaced a format error instead of the read-block message.
Guard now fires before any validation/preprocessing.
2026-07-28 18:12:26 -07:00