Commit Graph

437 Commits

Author SHA1 Message Date
Teknium 70c6cf8e7e feat: add new FAL video families and image models
Video (plugins/video_gen/fal): Seedance 2.5, MiniMax H3, Seedance 2.0
Mini, FLUX 3, Grok Imagine 1.5, Gemini Omni Flash (i2v-only). New
family capability flags:
- duration_int: endpoints that take duration as a JSON integer
- resolution_aliases: maps 720p/1080p-style values onto non-standard
  enums (H3's 768P/2K/4K)
- image_drop_keys: strips keys the family's i2v endpoint rejects
  (aspect_ratio on Seedance 2.5 / H3 / Grok 1.5)

Image (tools/image_generation_tool): Seedream 5.0 Pro (+edit) and
Lite, Ideogram V4 instant + fast, Qwen Image 3 (+edit), MAI Image 2.5
Pro, Nano Banana 2 Lite (+edit), Recraft V4.1.

Every new endpoint live-tested against fal.run through the real
payload builders + submit path: 18/18 pass (t2v, i2v, t2i, and edit
probes). Note: several new endpoints return HTTP 409 from the Nous
Portal FAL proxy allowlist until it is updated portal-side; BYOK
FAL_KEY works today and the existing 4xx guidance message covers it.
2026-08-08 04:31:55 -07:00
rob-maron b3aa561faf
add Hermes headers to Fireworks provider (#81321) 2026-08-07 20:56:29 +00:00
Gille f346458f29 fix(cron): surface initial scheduler registration failures 2026-08-07 17:45:06 +05:30
rob-maron 226b095a59
Fireworks user agent (#80422) 2026-08-07 01:49:57 +00:00
Jeffrey Quesnelle 5943bab1ec
Merge branch 'main' into feat/hermes-relay-model-metrics 2026-08-04 12:07:51 -04:00
ehz0ah a49a9e5e37 fix(openviking): verify servers before sending credentials 2026-08-03 20:35:47 +05:30
ehz0ah e443d32718 test(retaindb): guard scoped secret config resolution 2026-08-03 20:35:47 +05:30
ehz0ah e43bc0b7aa fix(openviking): integrate reliability and configuration hardening 2026-08-03 20:35:47 +05:30
PRATHAMESH75 5396dd8f02 fix(memory): read non-secret provider config from config.yaml for OpenViking and RetainDB
OpenViking is_available() only consulted env vars and use_ovcli_config, so an
endpoint saved to config.yaml (e.g. by the Dashboard) reported needs_config;
_resolve_connection_settings() likewise never folded config.yaml's non-secret
fields into its chain. RetainDB initialize() read base_url/project from the
environment only, ignoring the values the Dashboard writes to config.yaml.

Both now resolve non-secret fields as env -> (ovcli ->) config.yaml -> default;
secrets still come from the environment. Adds regression tests for both.

Fixes #68209

(cherry picked from commit dca57915b9)
2026-08-03 20:35:47 +05:30
Jeff Mettel f94914f773 test(openviking): cover the compression lifecycle, not a hand-set latch
Review feedback: the previous test called _mark_session_committed
directly, so it verified the guard's behavior but not the wiring that
sets it — a future break in the commit_memory_session -> same-id
compression-boundary path would not be caught.

Add a lifecycle regression that drives the real sequence: on_session_end
commits through the actual path, on_session_switch(same id,
reason="compression") crosses the boundary, sync_turn records a genuinely
new turn, and a second on_session_end must produce a second commit POST.

Without the fix it fails showing exactly one commit call, which is the
reported data loss: every turn after the first compression is dropped.
The rotation and /undo tests stay as scope guards.

(cherry picked from commit 0ca5a33063)
2026-08-03 20:35:47 +05:30
Jeff Mettel f0cb219e5e fix(openviking): re-arm the commit guard after in-place compression
`_committed_session_ids` is a permanent per-sid latch, and
`_session_needs_commit` checks it before the turn counter by design — a
racing sync_turn can re-increment `_turn_count` after commit+reset, so
the guard must win to stop a double-commit.

That is correct for a session being left behind. It is wrong for one
that keeps its id. `compress_context()` commits before rewriting the
transcript in both modes, and with `compression.in_place: true` (the
default) `on_session_switch` receives the same id and does not rotate.
The latch then rejects every later commit for a still-live session — the
next compression, /new, normal session end, startup recovery — so every
post-compression turn is silently never extracted.

Rotation mode is unaffected because a fresh child id is minted and
starts clean, which is what confirms the latch's intent was only ever to
dedupe the departing id.

Clear the latch when compression completes without rotation. Turns
arriving after that point are genuinely new, and this is a defined
moment rather than a race. The rotation path is untouched, so the old
id stays latched and its _finalize_session_async still dedupes against
the compression commit.

Fixes #74695

(cherry picked from commit d1e5c3dc33)
2026-08-03 20:35:47 +05:30
ddy4633 9014aa0263 fix(openviking): drop stale "disabled for this Hermes run" warnings
The provider used to disable OpenViking permanently when the server was
unreachable. That was fixed: `_ensure_client()` now reconnects lazily,
with a 30s cooldown gate in `_ensure_client_locked`.

Only one of the seven user-facing warnings was updated to match. The
other six still told the user memory was "disabled for this Hermes run",
which is no longer true — every one of those paths is retried on the next
access. A user who reads the old message has no reason to retry, which is
very likely how #5721 ("never recovers") came to be filed against
behaviour that already recovers.

All six sites were traced to confirm none is terminal for the run: the
`initialize()`-time and waiter-thread failures never arm `_failed_refresh`
(only line 2439 does), so they retry on the very next access with no
cooldown at all.

The replacement wording deliberately omits the "(after cooldown)"
parenthetical used at the already-correct site — that detail is only
accurate where `_failed_refresh` was just armed. The neutral phrasing is
true at all six.

Also promotes two clause separators to periods to avoid "…; …disabled;"
collisions.

(cherry picked from commit 8346403a4b)
2026-08-03 20:35:47 +05:30
Jeff Mettel a3f6953f1a fix(openviking): don't spawn a second server onto a live port
`_start_local_openviking_server()` spawned `openviking-server`
unconditionally. Both callers — `initialize()` and the runtime
unreachable handler — reach it from a health probe, and that probe can
time out client-side while the server is up and serving. The spawned
process then loses the data-directory lock and exits immediately with
`DataDirectoryLocked`; because the probe keeps timing out, the cycle
repeats every cooldown window (~5 min observed).

The existing 30s `_failed_refresh` cooldown paces the loop but cannot
stop it, since it expires while the underlying condition persists.

Probe the target host:port before spawning and treat an occupied port as
already-started. This guards both call sites at their single convergence
point. The probe deliberately tests only that a listener owns the port —
enough to know a second server would lose the lock — and says nothing
about that listener's health.

The parse/probe now precedes the PATH lookup, so a reachable server is
reported as running even when `openviking-server` is not on PATH.

Fixes #74846

(cherry picked from commit b49427d85f)
2026-08-03 20:35:47 +05:30
峯岸 亮 c7fd21add3 fix(security): reject always-blocked OpenViking endpoints
## Summary
- Normalize OpenViking endpoints through `is_always_blocked_url` and fall back to the default local endpoint when poisoned.
- Keep intentional loopback / LAN self-host working.
- Add focused unit tests.

## Salvage / credit
Memory-provider endpoint floor sibling of RetainDB/Supermemory always-blocked hardening (avoids over-broad #4984-style private-IP bans).

(cherry picked from commit 8fa607d0ae)
2026-08-03 20:35:47 +05:30
Alex Fournier a97abcd55a Merge upstream main into model metrics
Signed-off-by: Alex Fournier <afournier@nvidia.com>

# Conflicts:
#	tests/agent/test_auxiliary_relay.py
2026-08-02 20:10:47 -07:00
Ben Kamholtz 5a8102d71c fix(a2a): JSON-RPC conformance for a2a-sdk 1.1.0 compatibility
Two bugs reported by gfdsa (PR #41711 comment, Jul 12) that break the
official a2a-sdk 1.1.0 Python client:

1. Task objects serialized non-spec createdAt/lastModified fields.
   The A2A v1.0 Task proto (lf.a2a.v1.Task) only has id, contextId,
   status, artifacts, history, metadata. Strict ProtoJSON parsers
   reject unknown fields with ParseError. Removed both fields from
   build_task(); created_at param kept for call-site compatibility.

2. SSE streaming frames were not JSON-RPC wrapped. A2A v1.0 §9.4
   requires data: {"jsonrpc":"2.0","id":...,"result":{StreamResponse}}.
   sse_data() now accepts req_id and wraps in JSON-RPC envelope.
   sse_done() changed from 'data: {}' to SSE comment ': done' so
   SDK doesn't try to parse an empty JSON-RPC response.

All call sites in adapter.py (_emit_terminal, _rpc_message_stream,
_rpc_tasks_subscribe) updated to thread req_id through.

Tests updated: 153 pass (151 unit + 17 integration, including 2 new
tests for JSON-RPC envelope wrapping and fallback behavior).

Refs: gfdsa/a2a-hermes reproduction repo
2026-08-02 15:10:15 -07:00
Kevin (OpenClaw Bot) b1819ceb7d fix(a2a): align multiplexer with v1 protocol and tenant isolation 2026-08-02 15:10:15 -07:00
Kevin (OpenClaw Bot) fe1aca5770 feat(a2a): file/data Parts + push config full CRUD
## File/data Parts (v1.0 unified Part)
- file_part(url=, raw=, filename=, media_type=) builds v1.0 file Parts
- data_part(data, media_type=) builds v1.0 data Parts
- message_with_parts(role, parts, context_id=) builds Messages with mixed Part types
- extract_text now renders file/data Parts into the text stream:
  - File with URL: '[file: name] https://url (mediaType)'
  - File with raw: '[file: name] N bytes base64-encoded (mediaType)'
  - Data: '[data (mediaType)]\n{json}'
  - v0.3 file (file.fileWithUri) and data (kind=data) still accepted
- Outbound replies stay text-only (agent produces text)

## Push notification config full CRUD
- get_push_config(task_id, config_id) — retrieve by task, optionally by configId
- list_push_configs(task_id) — list all configs for a task (max 1 per task)
- delete_push_config(task_id, config_id) — remove a config
- New JSON-RPC methods: tasks/pushNotificationConfig/get, /list, /delete
- New adapter handlers: _rpc_push_config_get, _list, _delete
- All return spec-shaped PushNotificationConfig with configId + createdAt

## Tests
- 6 new unit tests for Part builders + extract_text with file/data
- 13 new unit tests for push config get/list/delete (happy + error paths)
- 2 new integration tests over real HTTP:
  - test_mixed_parts_delivered_to_agent: file URL + data JSON reach agent
  - test_push_config_crud_over_http: full create→get→list→delete cycle
- Old test_extract_text_skips_non_text_parts replaced (now renders, not skips)

Total: 151 tests (134 unit + 17 integration), 0 failed.
DESIGN.md updated: file/data Parts and push config CRUD removed from
out-of-scope list.
2026-08-02 15:10:15 -07:00
Kevin (OpenClaw Bot) 41c406e1ab feat(a2a): v1.0 upgrade + full code review fixes
Fable 5 pass: 40 turns, $13.56, 109k output tokens.

## A2A v1.0 upgrade
- SCREAMING_SNAKE task states (TASK_STATE_COMPLETED etc)
- ROLE_USER/ROLE_AGENT message roles
- Unified Parts (no kind field, member-presence discrimination)
- Agent Card: supportedInterfaces[], provider, capabilities.extendedAgentCard
- SSE: member-discriminated statusUpdate/artifactUpdate, closure=terminal
- contextId inside Message (not top-level params)
- ISO 8601 millisecond timestamps, createdAt/lastModified on Task
- New operations: tasks/list, tasks/subscribe
- input-required state reachable via [INPUT_REQUIRED] hint

## Security & correctness (all must-fix from review)
- Slash-command bypass removed — remote peers can't invoke operator commands
- Per-peer token auth (A2A_PEER_TOKENS) replaces self-asserted params.peer
- _pending_replies keyed by task_id with per-context FIFO (no cross-talk)
- Timeout returns TASK_STATE_FAILED, not completed
- reset_turns uses task's context from store (was silent no-op)
- Error codes: spec codes only for spec semantics, custom -32050..-32052
- Real latency metric (was fake 0.0)

## Dead features wired
- Push notifications: inline configuration.taskPushNotificationConfig in
  message/send + tasks/pushNotificationConfig/create. HMAC-signed e2e.
- Dynamic Agent Cards: skills from live tools.registry, A2A_ADVERTISED_TOOLSETS
- Persistence: new a2a_history(context_id) tool recalls conversations
- Dead helpers cut: rate_limit_status, is_open_mode, verify_push_signature,
  turn_count, check_bearer

## Architecture
- TurnTracker/RateLimiter/TaskStore on adapter instance (was module-global)
- Handler class at module level (was untestable closure)
- on_processing_complete for failure/cancel paths
- SSE hang fix: keepalive header no longer prevents socket closure

## a2a_orchestrate kept per user instruction
- best mode: only successful replies considered (long error can't win)
- all-error case: explicit 'All peers failed' listing
- Client paths deduped into _send_task helper

## Tests
- inspect.getsource() tests replaced with behavioral coverage
- 133 total: 118 unit + 15 integration
- v1.0 spec compliance, peer-token auth, FIFO replies, timeout→FAILED,
  tasks/get-after-complete, streaming SSE parse, subscribe replay,
  anti-loop rejection, 429s, push e2e, input-required e2e, orchestrate

## Docs
- DESIGN.md out-of-scope synced with reality
- README and plugin.yaml updated

Still TODO (in DESIGN.md): file/data Parts, push-config get/list/delete,
tenant, gRPC/HTTP+JSON bindings, true mid-turn task abort.
2026-08-02 15:10:15 -07:00
Kevin (OpenClaw Bot) 37481dccf4 fix(a2a): security hardening from code review
Critical fixes:
- SSRF protection: validate push notification callback URLs (block
  internal/private/loopback/metadata, enforce http/https only)
- Request body size limit: 1MB max (prevents memory exhaustion DoS)
- Thread safety: module-level locks for turn tracking, rate limiting,
  and pending task registry (was lazily initialized, racy)
- Peer identity: fall back to client IP when 'peer' field absent
  (prevents rate limiting collapse to single 'unknown' bucket)

Minor fixes:
- Watchdog survives reconnect: clear _watchdog_stop in connect()
- Redact error messages before sending to peers
- Remove dead _streaming_queues state
- Fix duplicate tags key in Agent Card skills
- Always send contextId in a2a_call (fixes client/server mismatch)
- Clear push_callbacks on disconnect
- SSE streaming cleanup via try/finally

16 new tests covering SSRF, body size, thread safety, watchdog
reconnect, error redaction, contextId consistency.
Tests: 97 passed, 3 deselected, 0 failed.
2026-08-02 15:10:15 -07:00
Kevin (OpenClaw Bot) c6b0e3a80e feat(a2a): Phase 2+3 — SSE streaming, push notifications, anti-loop, orchestrate
Phase 2 (production features):
- SSE streaming: message/stream endpoint with proper event formatting
  (submitted → working → completed → done), keepalive pings
- Push notifications: HMAC-SHA256 signed webhooks via
  tasks/pushNotification/set, auto-fired on task completion
- Rate limiting: token-bucket per peer (A2A_RATE_LIMIT, default 60/min)
- Metrics: /metrics endpoint with counters, latency tracking, uptime
- Orphaned task watchdog: background thread cleans stale tasks (>300s)

Phase 3 (OpenClaw patterns):
- Anti-loop ping-pong: per-context turn counter with configurable
  max (A2A_MAX_PINGPONG_TURNS, default 5, max 20)
- Async durable messaging: pending task registry with register/
  complete/orphaned/clear lifecycle
- Capability-based routing: a2a_orchestrate tool with fan-out modes
  (all/first/best), matches peers by capabilities in config
- Dynamic Agent Cards: skills_from_real_toolsets() builds skill cards
  from actual toolset registry, not just names
- Trusted-peer approval (#56434): A2A_TRUSTED_PEERS env/config,
  is_trusted_peer() gate in inbound handler
- Task completion notifications (#56435): build_task includes
  status.message + artifacts for completed/failed states

Agent Card version bumped to 0.2.0, capabilities now advertise
streaming=True and pushNotifications=True.

Tests: 81 passed (45 existing + 36 new), 0 failed.
2026-08-02 15:10:15 -07:00
Kevin (OpenClaw Bot) 436e5a9cb5 fix(a2a): integrate all follow-up fixes for #41711
Consolidates 5 follow-up PRs onto the a2a-work branch:

1. Reply-capture fix (#56437): adapter.send() now only resolves the
   blocked RPC Future when metadata['notify'] is True (the gateway's
   final-reply marker). Interim sends no longer short-circuit the
   response. Also accepts **kwargs in connect() for reconnect compat.

2. Slash command passthrough (#53743): wrap_inbound() passes /-prefixed
   text through unwrapped so the gateway command processor sees it.
   Fixes /sethome deadlock during A2A onboarding. Documented security
   trade-off (bearer auth at network layer compensates).

3. Routable URL in Agent Card (#53736): _build_card() now derives URL
   from A2A_PUBLIC_URL env > X-Forwarded-Host/Host header > bind host.
   Fixes k8s bug where Agent Card advertised 0.0.0.0.

4. contextId multi-turn memory (#53756): _handle_inbound_task() now
   checks top-level params.contextId first (A2A spec), falls back to
   params.message.contextId (legacy). Outbound a2a_call also sends
   contextId at both top-level and inside message.

5. Type checker fixes (#53759): TypedDict for _SCHEMAS, _FunctionSchema,
   _ToolSchema. Removes str() band-aid casts.

All 45 tests pass including new tests for each fix.
Zero core files modified — only plugins/platforms/a2a/ and tests/.

Credits: @davidrobertson (#56437), @knoal (#53736, #53743, #53756,
#53759), @kuangmi-bit (slash command bug report), @gfdsa (k8s URL bug
report), @shivasymbl (#45996 userContext OBO).
2026-08-02 15:10:15 -07:00
David Robertson 38318cec1e fix(a2a): wait for final replies before resolving RPCs 2026-08-02 15:10:15 -07:00
teknium1 7d57422936 fix(a2a): client tools take args-as-dict positional; accept agent_name alias
Live Tier-3 testing (CLI agent -> a2a tools -> live peer gateway -> model)
surfaced two bugs the kwarg-style unit tests masked:

1. registry.dispatch calls handlers as handler(args, **kwargs) — args is the
   whole dict positional. The handlers used keyword params (url=, agent=), so
   the dict bound to the first param and .strip() raised
   'dict object has no attribute strip'. Rewrote all three handlers to take
   args: dict (matching the spotify/google_meet convention). Added a
   registry-dispatch regression test that exercises the real call path the
   direct-kwarg tests never hit.

2. The model repeatedly reached for agent_name= instead of agent= (6 retries
   before success). Accept agent_name/name and message/text/task aliases so a
   reasonable guess succeeds first try.

Verified live: client agent discovers the peer's Agent Card, calls it, and
gets the reply back (PONG round-trip confirmed on both client audit log and
peer conversation log). 39 plugin tests pass.
2026-08-02 15:10:15 -07:00
teknium1 837003b1ed feat(a2a): consolidated Agent-to-Agent protocol plugin (closes #514)
Single platform-adapter plugin under plugins/platforms/a2a/ — zero core
edits — that supersedes the entire A2A PR/issue cluster. Built on the
ctx.register_platform + ctx.register_tool surface the codebase now exposes.

Outbound (a2a toolset): a2a_discover / a2a_call / a2a_list let the agent
call any A2A-compliant peer over JSON-RPC message/send. Inbound (platform
adapter): a stdlib http.server serves an Agent Card at
/.well-known/agent.json and routes incoming tasks into the agent's LIVE
gateway session (the #11025 insight) — same agent, full memory — returning
the reply over A2A.

Security on by default: no bearer token => 127.0.0.1-only bind; constant-
time bearer auth; inbound prompt-injection filtering + untrusted-peer
framing; outbound credential redaction; append-only audit log; per-context
conversation persistence outside the compaction pipeline.

Stdlib only (no a2a-sdk). 37 tests incl. a live HTTP round-trip
(card + message/send + reply) and a bearer-auth 401 path.
2026-08-02 15:10:15 -07:00
kshitij 9bbd956b73 fix(memory/hindsight): evict timed-out retain ops + coarser status polls
Review follow-up on the #62871 salvage (simplify pass, HIGH):

1. Ops unresolved at the wait deadline were RETAINED in the pending set.
   A permanently failing status endpoint (auth error, endless 500s, or a
   server that loses ops without 404) would grow the set forever and make
   EVERY later prefetch burn the full 10s budget re-polling it — and
   prefetch()'s bounded 3s join sits on the reply path, so that money-quote
   'adds no response latency' claim breaks. Timed-out ops are now dropped
   (identical degradation to prefetch_waits_for_retain=False: possibly
   stale recall) with a WARNING so persistent server trouble is visible.
   Guard test mutation-checked (fails with eviction disabled).

2. Status polls now spaced 0.5s (was 0.05s shared with the local drain
   poll): a wedged op cost up to ~200 get_operation_status round trips
   per prefetch; now ~20 max over the default 10s budget.
2026-08-02 22:33:43 +05:30
And 1be353bf9c fix(memory/hindsight): gate prefetch on server-side retain completion, not just queue drain
Address PR #62871 review: with the default retain_async=True, aretain_batch
returns when the write is accepted, not when it's durable/recall-visible, so
draining the local writer queue (task_done) is not a read-after-write signal.
The next-turn prefetch could still recall before the just-completed turn was
observable on the server.

- Track the async operation_id/operation_ids returned by aretain_batch
- _wait_for_retains_drained now applies two ordered, budget-bounded barriers:
  (1) local writer queue drains, then (2) tracked server-side async ops report
  completion via operations.get_operation_status (an explicit read-after-write
  condition). NotFound (completed+evicted) counts as done; transient errors
  keep waiting until the deadline
- Completed ops are removed from the pending set so later prefetches don't
  re-poll them; the whole wait stays off the reply path
- Add TestPrefetchServerRetainVisibility: op-id tracking (single/multiple),
  no-op tracking when retain_async=False, prefetch waiting for server
  completion before recall, timeout fallback on a wedged op, and NotFound /
  transient-error status handling
2026-08-02 22:33:43 +05:30
And 94b10eccf5 feat(memory/hindsight): order background prefetch after pending retains
Async retain already keeps the memory WRITE off the reply path (writes drain
on the single writer thread while the user gets their response immediately).
This closes the remaining retain/prefetch race: the next turn's warm prefetch
runs on its own thread and could recall BEFORE the just-enqueued retain write
lands, silently dropping the latest turn from recall.

- The background prefetch now waits (bounded) for pending retains to drain
  before recalling, so warmed context includes the just-completed turn.
- The wait runs only on the background prefetch thread, never the reply path,
  so it adds zero latency to the user's response and loses no writes.
- Bounded by prefetch_retain_drain_timeout (default 10s) and polls
  unfinished_tasks so a wedged write can't hang the prefetch.
- New config keys: prefetch_waits_for_retain (default true),
  prefetch_retain_drain_timeout (default 10.0).
2026-08-02 22:33:43 +05:30
JabberELF 7a450ca5ce fix(memory): resolve dim=1 float32/float64 blob ambiguity
When hrr_dim=1 the prefixed float32 blob (4+4=8 bytes) collides in
size with a raw float64 blob (1×8=8 bytes), making the format
discriminator in bytes_to_phases ambiguous — a legacy blob starting
with HRR1 would be misread as a prefixed float32 vector.

- phases_to_bytes now accepts an optional dim and falls back to
  writing raw float64 when the two blob sizes are equal.
- bytes_to_phases prefers the legacy float64 interpretation when
  sizes collide and dim is provided, since phases_to_bytes never
  writes a prefixed float32 blob at dim=1.
- Three regression tests cover dim=1 write, round-trip, and the
  legacy-prefix collision case.

Addresses hermes-sweeper review on PR #30499.
2026-08-02 22:33:13 +05:30
JabberELF 958ffd1085 perf(memory): store holographic vectors as float32 2026-08-02 22:33:13 +05:30
kshitij a2f95e4c0e test(memory): hoisted-retriever fixture uses a real tmp db, not :memory:
Review follow-up on the #76142 salvage: MemoryStore path-resolves and
shares one process-wide connection per file, so MemoryStore(":memory:")
creates a literal ./:memory: FILE whose state leaks across test runs —
the second run of the file failed all three spy tests because the
NULL-vector test had permanently wiped hrr_vector in the leaked db.
tmp_path isolates each run; verified two consecutive runs green + full
tests/plugins/memory/ green.
2026-08-02 21:16:34 +05:30
spfcraze 89f74d58f6 perf(memory): hoist loop-invariant HRR encodes out of retrieval loops
FactRetriever.search() re-encoded the query vector once per candidate,
related() re-encoded both role atoms once per fact row, and probe()
re-encoded the role-content atom once per row. All three encoders are
deterministic (SHA-256 counter blocks), so the hoisted vectors are
bit-identical to the per-iteration values they replace.

Measured (300-fact store, dim=1024, median of 30 calls): search()
11.62 -> 1.46 ms/call (8.0x; encode_text 30 -> 1 per call), related()
63.08 -> 16.17 ms/call (3.9x; encode_atom 601 -> 3 per call), probe()
431.93 -> 389.36 ms/call (1.1x; dominated by per-fact content encoding,
which is inherent to the algorithm and unchanged).

Tests: call-count regression tests for each hoist plus a bit-exact
parity test of search() against the pre-fix per-candidate loop.
2026-08-02 21:16:34 +05:30
brooklyn! 97971643ab
Merge pull request #76417 from NousResearch/bb/kanban-model-picker
Pick a kanban task's model and thinking depth from the board
2026-08-01 16:55:01 -05:00
Brooklyn Nicholson f0ed0aebbc feat(kanban): expose the per-task reasoning effort over REST
Carries the new column through create, PATCH, and bulk. Clearing is an
explicit clear_reasoning_effort flag rather than a null, because a null in a
PATCH body means "field not sent", not "set to NULL" — the same shape the
model override already uses, and the reason "none" can stay a real value.

Tests cover normalization, the depth-survives-a-model-clear invariant, both
spawn-argv branches, and the REST round-trip. One asserts the worker CLI
actually accepts the --reasoning flag the dispatcher emits: a spawn arg no
parser accepts would fail every dispatch while every unit test stayed green.
2026-08-01 16:10:03 -05:00
Teknium c05f0bb81d test: importorskip discord.py in the slash-gate isolation test
CI's plugin-test slice runs without the discord optional extra; the raw
import failed with ModuleNotFoundError while every other test in the
file uses injected mock modules.
2026-08-01 10:51:42 -07:00
Teknium 81c0691e17 fix(gateway): per-profile Discord/Telegram allow-deny gates under multiplex_profiles
Under gateway.multiplex_profiles, Discord and Telegram authorization gates
(allowed/ignored channels, allowed users/roles, allow-all flags) were read
from process-global os.environ, populated first-writer-wins by the YAML->env
bridge in each adapter's _apply_yaml_config. The first profile to initialize
pinned its allow/deny lists — and its ALLOW_ALL flags — for every other
profile in the process (issue #72348, incl. the Telegram mirror reported in
the thread).

Fix (per-adapter-instance gate reads, whole class):

- gateway/authz_mixin.py: new _platform_gate_env — scope-authoritative gate
  read: under an installed profile secret scope with multiplex active, a
  missing key returns the default instead of falling through to os.environ
  (which may hold another profile's value). Single-profile behavior is
  byte-identical to os.getenv.
- Discord adapter:
  - connect() snapshots all gate env vars (_GATE_ENV_KEYS) inside the owning
    profile's runtime scope into a per-adapter dict; new accessors
    (_get_allowed_channels/_get_ignored_channels/_get_allowed_users/
    _get_allowed_roles/_get_no_thread_channels/_discord_allow_all_users/
    _gateway_allow_all_users/_get_allow_bots) resolve snapshot -> config.extra
    -> scope-aware env, replacing every raw os.getenv gate read: on_message
    channel gates, _is_allowed_user allow-all flags, slash authorization,
    fail-closed diagnostics, missed-message backfill, bot-message gating,
    and _component_check_auth (component buttons).
  - _apply_yaml_config always seeds gate values into PlatformConfig.extra
    (incl. new allowed_roles / allow_all_users keys) and SKIPS the
    process-global env writes when loading a profile-scoped config under
    multiplex; the legacy first-writer env bridge is preserved verbatim for
    single-profile deployments.
  - _resolve_allowed_usernames no longer unconditionally rewrites
    os.environ[DISCORD_ALLOWED_USERS] — under multiplex the resolved IDs stay
    adapter-local (snapshot refresh); single-profile keeps the env rewrite.
- Telegram adapter (mirror of the same class): intake prefilter and
  callback-auth fallbacks, _telegram_auth_env_configured, and the
  allowed/ignored chats-topics-threads getters now read via the scoped gate
  reader; _apply_yaml_config skips authorization env writes for
  profile-scoped loads and seeds free_response_chats/ignored_threads extras.

Regression tests (tests/plugins/platforms/test_discord_gate_isolation.py):
two adapter instances with different allow-lists enforce their OWN lists
order-independently across message, slash, and component gates; negative
allow-all case proves profile A's open-access flag cannot authorize profile
B; username-resolution env-clobber; YAML-bridge seeding/skip matrix; and the
Telegram scoped-reader matrix. Sabotage-verified: reverting either the
Discord snapshot accessors or the Telegram scoped reader fails 12/2 tests
respectively.

Credit: builds on the per-adapter accessor direction of PR #72427
(@JonthanaHanh) and the scope-aware-reader approach validated live on v0.19.0
by @yournetworkplug-ctrl for the Telegram mirror; scope corrections from
jackjin1997's and cal88's analysis in the issue thread (allow-all flags,
unguarded username-resolution env write, per-site channel reads).

Fixes #72348
2026-08-01 10:51:42 -07:00
Teknium fae0c4f5f4 fix(hindsight): create embedded profile env file owner-only (0600)
The embedded Hindsight daemon's profile env file carries the plaintext
HINDSIGHT_API_LLM_API_KEY but was written via bare write_text(), leaving
it with umask-derived (typically world-readable) permissions.

- Create/truncate the file via os.open(..., 0o600); chmod a pre-existing
  file to 0600 BEFORE writing new secret bytes.
- Post-write validation on POSIX: verify 0600, retry chmod, and raise if
  the file still isn't owner-only.
- If validation fails, unlink the secret file so a plaintext key is never
  left behind with unverified permissions.
- Regression tests under tests/plugins/ for fresh-write mode, tightening a
  pre-existing 0644 file, and cleanup on validation failure.

Narrowed reimplementation of #74236 confined to plugins/memory/hindsight/;
the core utils.py atomic-replace opt-out from the PR was dropped.

Co-authored-by: carrion256 <carrion256@proton.me>
2026-07-31 22:39:34 -07:00
brooklyn! 0324849fe4
Merge pull request #61173 from NousResearch/bb/desktop-kanban
feat(desktop): Kanban — the founding plugin on the desktop SDK
2026-07-31 13:00:10 -05:00
Alex Fournier 0de9b65c23 Merge upstream main into model route metrics
Signed-off-by: Alex Fournier <afournier@nvidia.com>
2026-07-31 07:53:12 -07:00
Austin Pickett 74fdc578cc fix(cron): set headers for chronos JWKS requests
The chronos cron-fire verifier constructed PyJWKClient without explicit
headers, so its JWKS fetch to the NAS portal hit the same WAF 403 the
dashboard-auth providers already guard against. It reaches the same
portal issuer, so it's the same bug class — mirror the fix here and add
a constructor-contract regression test.

Co-authored-by: James Hodgkinson <james@terminaloutcomes.com>
2026-07-31 10:17:30 -04:00
James Hodgkinson eaa9582e38 fix(dashboard): set headers for Nous JWKS requests
The Nous PyJWKClient was constructed without explicit headers, while the
self_hosted provider already sends Accept + User-Agent. Without them the
Portal WAF can block the JWKS fetch, so the same failure mode remained for
the Nous dashboard-auth route. Mirror the self_hosted fix and add a
constructor-contract regression test.
2026-07-31 10:17:30 -04:00
James Hodgkinson 83cee29ff7 fix(dashboard): set headers for JWKS requests 2026-07-31 10:17:30 -04:00
Alex Fournier c0369f0891 Merge remote-tracking branch 'origin/main' into feat/hermes-relay-model-metrics
Signed-off-by: Alex Fournier <afournier@nvidia.com>
2026-07-30 11:12:51 -07:00
Brooklyn Nicholson 346149c4f8 feat(kanban): task effort estimate via the auxiliary model
An "Estimate" action asks the auto-routed auxiliary model for a rough token
count + complexity band (S/M/L) with a one-line rationale — tokens, not
dollars, since providers don't report cost reliably. POST /estimate (typed
title/body, for the create dialog) and POST /tasks/{id}/estimate (existing
cards) share one core. Desktop renders it inline ("~15k tok · Medium") with a
"makes a model call" disclaimer; SDK exports compactNumber.
2026-07-30 07:18:08 -05:00
Brooklyn Nicholson 027ef381a4 feat(kanban): scope boards to a project
Boards gain an optional project_id. When set, the board's default_workdir
mirrors the project's primary repo and every new task inherits the project —
a deterministic worktree + branch per task — unless it names its own. New
GET /projects; board create/patch/list carry project_id + resolved name; the
create dialog defaults its workspace to the board's and allows a per-task
path override. Desktop: "Board settings…" gains a project picker.
2026-07-30 07:18:08 -05:00
Kyzcreig 9c28600e77 test: prove concurrency with barriers/witnesses instead of wall-clock bounds
Replace OS-scheduler-dependent elapsed-time ceilings with deterministic
concurrency proofs in three tests:

- test_context_refs_concurrent: asyncio.Barrier rendezvous — all 3 URL
  fetches must be in flight simultaneously before any returns.
- test_memory_boundary_commit: positive non-blocking witness — the
  provider call list must still be empty when the async commit returns.
- test_mem0_v3 slow-prefetch: threading.Event park/release — prefetch
  must return while the backend search is still parked.

The tests/tools/test_mcp_tool.py hunk from the original PR is dropped:
main no longer carries the 'elapsed < 2.5' assertion it targeted
(superseded by a delay-relative bound).

Salvaged from #71913.
2026-07-29 21:30:53 -07:00
webtecnica 66c4c9c0b1 fix(tests): forward Windows location vars through the hermetic runner; patch Path.home() in hindsight _clean_env
Combines the Windows-hermeticity cluster (#67512 by @webtecnica, earliest;
#71112 by @Sanjays2402; #67196 by @anatolijlaptev1991-ctrl) into one fix:

- scripts/run_tests.sh: env -i forwarded only HOME, but native Windows
  CPython resolves Path.home() from USERPROFILE (or HOMEDRIVE+HOMEPATH),
  stdlib paths from LOCALAPPDATA/APPDATA, ssl/sockets need SYSTEMROOT,
  tempfile needs TEMP/TMP — the strip broke collection tree-wide on
  native Windows (issues #67385, #70813). Location vars (never
  credentials) are now forwarded, each only when actually set, so
  POSIX runs are byte-for-byte unchanged (probe-verified both ways).
  PYTHONUTF8=1 added for legacy-codepage consoles printing the
  runner's glyphs.
- tests/plugins/memory/test_hindsight_provider.py: _clean_env patched
  HOME only; on Windows Path.home() ignores HOME. Now patches
  Path.home directly into tmp_path (from #67196).

Not ported: #71112's guard test — it regex-reads run_tests.sh source,
which the test policy bans (never read source code in tests).

Fixes #67385. Fixes #70813.
2026-07-29 18:55:10 -07:00
Alex Fournier dfb8c1bd4d fix(observability): preserve shared metrics compatibility
Signed-off-by: Alex Fournier <afournier@nvidia.com>
2026-07-29 14:09:47 -07:00
Teknium 6b81590c55
test: prune low-value tests suite-wide (wave 1) — 46,820 → 28,106 test functions
Systematic prune per AGENTS.md test policy, one pass over every major
test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli,
cron, tui_gateway, honcho/openviking, root-level):

- DELETE: source-reading tests (read_text/getsource on prod files),
  change-detector tests (exact catalog counts, model-name snapshots,
  config version literals), mock-echo tests (assert a mock returns what
  it was told), assertion-free/trivial tests, near-duplicate
  parametrizations (boundaries + one representative kept), async/sync
  twin duplicates, cosmetic within-file variations.
- KEEP (mandatory): security/redaction/approval guards, message-role
  alternation invariants, prompt-caching/deterministic-call-id
  invariants, issue-number regression tests (deduped), E2E tests.
- 6 test files deleted outright (script-style/no-assert or fully
  redundant); conftest.py, fakes/, fixtures/ untouched.
- tests/acp/conftest.py added: autouse fixture stubs the live
  models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server
  tests performed on every session create — test_server.py 147s → 3.4s,
  and the tests are now genuinely hermetic.
- Sleep-based slowness shrunk where safe (codex_ttfb_watchdog,
  compression_concurrent_fork, etc.); no wall-clock assertion tightened.

Verification: full hermetic suite via scripts/run_tests.sh —
2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall
(baseline: 583s wall, 13,564s subprocess CPU).
2026-07-29 13:10:23 -07:00
teknium1 2164e548b1 test: retarget nemo-relay telemetry stub at read_raw_config_readonly
Same sibling-mock class as the relay-metrics runtime file — this test
stubs the telemetry gate's config read, which now goes through
read_raw_config_readonly(). Swept the whole test tree for remaining
read_raw_config stubs: all others target consumers that still use the
mutable reader (browser config, url_safety, inventory) and carry no
telemetry keys.
2026-07-29 11:33:41 -07:00