Commit Graph

169 Commits

Author SHA1 Message Date
renovate[bot] 14df09a107 chore(deps): update vendored js (#974)
* chore(deps): update vendored js

* chore: download vendored JS files

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-05 12:58:15 -07:00
Patrick Buckley 166b46cda4 chore: bump version to 1.8.0a5 2026-07-29 22:49:25 -07:00
Patrick Buckley 96834496c4 feat(anthropic): onboard claude-opus-5
The capabilities row is a copy of claude-opus-4-8 — 1M context, 128K output,
adaptive thinking, the full low..max effort ladder, mid-conversation system
messages. Two of the model's documented breaking changes are unreachable from
this lane and stay that way only while thinking_mode is "adaptive": thinking is
on by default when the param is omitted, and disabling it is a 400 at effort
xhigh or max. We never omit it and never emit "disabled", so both are recorded
at the row rather than defended against.

The third needed work. A safety classifier can decline with
stop_reason="refusal" on a successful HTTP 200, with content either empty or
partial, and an unmapped value fell through _normalize_finish_reason as a
literal string. The drain gate only raises on an ABSENT finish reason, so a
declined turn landed as a complete result with nothing to notice it: the
interactive lane matched neither of its two warn arms and stayed silent, and a
sub-agent handed the declined partial up to its parent as though it were
finished synthesis. Normalizing onto content_filter routes the decline into the
arms both lanes already have for that state — the operator gets the warning,
and the sub-agent stops rather than passing the fragment on.

The raw stop reason is logged where it is still in hand: normalization is lossy
and a classifier decline is otherwise indistinguishable from an ordinary
content filter. The gate is on the RAW value rather than
(normalized != raw), which is true for end_turn and tool_use as well and would
fire on every turn in every lane.

The anthropic floor moves to 0.117 to track the release current at onboarding.
The model needs no new SDK surface — ids are opaque strings and "refusal" has
been in the StopReason literal since ~0.95 — so this is hygiene; raise it again
when adopting fast mode, server-side fallbacks, advisor, or mid-conversation
tool changes, which do need newer typed params.
2026-07-25 01:12:04 -07:00
Patrick Buckley 51bb525b27 chore: bump version to 1.8.0a4 2026-07-21 15:30:35 -07:00
Patrick Buckley 431ef7c2fe ci: give the e2e_recovery suite its own lane exclusion, drop the live co-mark
The recovery e2e tests run a scripted provider — no LLM backend — so the
live co-mark was a lie told to keep the existing CI expression skipping
them. Both CI lanes now deselect explicitly via
-m "not live and not e2e_recovery", and the tests carry only their
honest marker. Select with -m e2e_recovery.
2026-07-20 22:38:32 -07:00
Patrick Buckley 43561c9b08 test(sse): end-to-end recovery harness (server-contract + browser livepass)
Tier 1 (tests/test_sse_recovery_e2e.py, opt-in e2e_recovery marker): six
scenarios against a real interactive server with a scripted provider and
ephemeral DBs — storm batching without loss, slow-consumer overflow with
lossless ring replay, mid-run truncation with cursor-adoption rebuild,
restart truncated-honesty (exact lost_count; no-loss variant replay_ok),
failed-resync retry via the truncation record, and sub-agent storm
attribution. BrowserlikeSSEClient (tests/_sse_recovery_helpers.py)
implements the browser cursor contract; RecoveryServer
(tests/_sse_recovery_server.py) boots the real app per test.

Tier 2 (scripts/recovery_e2e.py): the livepass idiom against a REAL node
— boots the real InteractivePane over real EventSource/authFetch, with a
dependency-free CDP runner driving the storm and hide-restart-show
scenarios; document.title stamps verdicts so a broken state cannot pass
silently.

Events are produced by the real session engine through the provider
boundary — no synthetic frames; teardown leaves no leaked threads; the
default suite keeps these deselected.
2026-07-20 22:38:32 -07:00
Patrick Buckley a16e6d66d8 chore: bump version to 1.8.0a3 2026-07-20 07:38:27 -07:00
renovate[bot] c3306be442 chore(deps): update dependency katex to v0.18.1 2026-07-20 07:21:23 -07:00
Patrick Buckley 0e6a99e0f1 chore: bump version to 1.8.0a2 2026-07-14 11:43:44 -07:00
Patrick Buckley 6f991d6aff chore: bump version to 1.8.0a1 2026-07-13 22:41:36 -07:00
Patrick Buckley b450b9ad20 fix(providers): align GPT-5.6 with the GA API surface
- every 5.6 tier accepts effort "max" and reasoning.mode
  "standard"/"pro" (GA docs: pro is a request mode on any GPT-5.6
  model) -- drop the Sol-only gating
- GPT-5.6 deprecates prompt_cache_retention; send
  prompt_cache_options={"ttl": "30m"} (its only supported lifetime)
  and keep the 24h retention policy for pre-5.6 models
- never inject commercial cache params into local lanes: dropped from
  the Chat Completions lane (which serves only openai-compatible and
  google) and gated off the compat-pinned Responses lane -- a gpt-5*
  served-model name is not an OpenAI account
- account cache writes: usage *_tokens_details.cache_write_tokens
  flows into cache_creation_tokens (5.6 bills writes at 1.25x the
  uncached input rate)
- drop non-string verbosity/reasoning_mode overrides with a warning
  instead of raising on unhashable capability-JSON values
- keep ModelCapabilities' public positional prefix stable by appending
  the verbosity/pro fields at the tail; pin it with a constructor test
- openai floor 2.44 -> 2.45, the first release with the typed
  prompt_cache_options kwarg
2026-07-10 15:59:40 -07:00
Patrick Buckley 47f908c9e0 feat(providers): add OpenAI GPT-5.6 (Sol/Terra/Luna) support
Onboard the GPT-5.6 family (GA 2026-07-09) to the OpenAI Responses lane.

- Capability rows for gpt-5.6 (= Sol alias/catch-all), gpt-5.6-terra, and
  gpt-5.6-luna: 1.05M context, 128K output, tool_search/vision/pdf/reasoning
  replay, default effort medium, temperature only at effort=none.
- "max" reasoning effort, Sol-only; Terra/Luna cap at xhigh (the knob's "max"
  snaps to the xhigh ceiling). First commercial OpenAI use of "max" — the
  ordinal knob already ranked it, so no effort-ladder change was needed.
- Verbosity and pro mode as operator-declared capability fields
  (supports_verbosity/verbosity, supports_pro_mode/reasoning_mode), merged
  from the model-definition capabilities JSON and emitted on the Responses
  wire as text.verbosity and reasoning.mode. Both are gated by a supports
  flag plus an enum guard that drops unknown values with a warning. Pro mode
  is Sol-only. There is no gpt-5.6-pro model — "pro" is the reasoning.mode
  param, not a separate model id.
- Raise the openai floor to >=2.44 for the 5.6 Responses params.

Unit and wire-golden tests cover the rows, max->xhigh snapping, the two
levers, and the enum guards. Validated live against the OpenAI API: gpt-5.6
accepts the model id, effort "max", text.verbosity, and reasoning.mode="pro".
2026-07-09 18:48:51 -07:00
Patrick Buckley 801d5dfb59 chore: bump version to 1.7.0rc1 2026-07-05 02:03:18 -07:00
Patrick Buckley a352b20786 chore: bump version to 1.7.0a7 2026-07-05 02:02:16 -07:00
Patrick Buckley 7053439e84 refactor(eval): split measurement core from prompt optimizer
turnstone-eval was misnamed: it was a prompt optimizer, not a measurement
harness. Split the 3252-line turnstone/eval.py into a strictly one-way
dependency (optimizer -> eval-core; core never imports the optimizer):

- turnstone/eval/core.py  measurement substrate — everything up to and
  including _run_iteration: provider detection, NullUI, HeadlessSession,
  the test runner, score_run, aggregation, and neutral reporting.
- turnstone/eval/cli.py   new measure-only `turnstone-eval` — the old
  --no-optimize path promoted to the whole job (one _run_iteration call,
  then print the summary table).
- turnstone/optimizer.py  the UCB self-modify loop and its multi-agent
  pipeline (analyst/optimizer/observer/diversifier/tool optimizer), now
  `turnstone-optimizer`; imports from eval.core only.
- turnstone/eval/__init__.py re-exports the core public API for
  back-compat (score_run, _match_action, _run_iteration, HeadlessSession).

_apply_tool_overrides lives in core (HeadlessSession needs it) rather than
alongside the other tree helpers, so the dependency stays one-way.

Breaking change: `turnstone-eval` now measures; use `turnstone-optimizer`
to optimize. Both code paths are behaviour-preserving — the moved function
bodies are byte-identical.
2026-07-03 16:39:31 -07:00
Patrick Buckley c71cc749d9 chore: bump version to 1.7.0a6 2026-07-01 21:07:52 -07:00
Patrick Buckley bbadd00ac0 chore: bump version to 1.7.0a5 2026-06-28 04:12:06 -07:00
Patrick Buckley 2b0b1cf73e chore: bump version to 1.7.0a4 2026-06-27 17:20:21 -07:00
Patrick Buckley 0e1972e6aa chore: bump version to 1.7.0a3 2026-06-27 01:22:33 -07:00
Patrick Buckley c0be383f99 refactor(doctor): replace turnstone-bootstrap with turnstone-doctor (#718)
* refactor(doctor): replace turnstone-bootstrap with turnstone-doctor

turnstone-bootstrap was an LLM setup wizard for Day-0; run.sh now owns install.
Repurpose its LLM/conversation plumbing into turnstone-doctor — a diagnose-only
tool for a running cluster.

- Preflight detects the install kind (docker-compose/systemd/pip/source) from
  config.toml + TURNSTONE_* env, with secret redaction.
- Self-configuring brain resolves the cluster's own model from config/env/storage
  read-only (no migrations, no create_all), falling back to interactive
  selection; the attempt itself is the LLM-backend health check.
- Deterministic version check: installed version, cluster drift via the console's
  authoritative /health, and latest upstream stable/experimental (offline-safe).
- Read-only diagnostic tools (read_file, compose/systemd/journal, http_health,
  check_llm_backend, node_health, finish) behind one secret-scrubbing chokepoint;
  no generic shell, so read-only is structural.
- node_health reaches a node the right way for the detected install kind
  (exec-into-container for compose, direct HTTP otherwise), overridable per node
  for mixed clusters.
- mTLS-aware: forwards [database] SSL params and reports node-mesh mTLS instead of
  mislabelling healthy nodes "unreachable".

init_storage gains a backward-compatible create_tables override for read-only
opens. Entry point turnstone-bootstrap -> turnstone-doctor; README/QUICKSTART/
architecture/docker docs, the bundled compose header, run.sh, and the CI smoke
updated. CHANGELOG deferred.

* fix(doctor): address Copilot + CodeQL review findings on #718

Validated all seven review findings (none false positives) and fixed:

- check_llm_backend now applies the same scheme / metadata-host guard as
  http_health (extracted to _assert_safe_http_url), so a model-supplied
  base_url can't be steered at the cloud metadata endpoint or a file:// URL.
- node_health no longer double-appends the default port when the operator
  passes host:port (regression: 10.0.0.5:8081 -> http://10.0.0.5:8081:8080).
- node_health install_type enum uses "git-source" to match the label the
  rest of the module and the prompt/report show the model (a schema-strict
  provider would otherwise reject the value the model is told to use).
- _read_api_creds takes base_url + api_key as a unit from the first config
  source that defines either field, then env-fills, instead of splicing the
  two across different config files into a pair that exists in no real config.
- _mask_secrets masks assignment-shaped content inside comment lines, so a
  commented-out real secret can't leak through read_file / the report; prose
  comments (no KEY=value shape) still pass through untouched.
- drop the mixed import styles CodeQL flagged in doctor.py and test_doctor.py.

Adds 5 tests; ruff + mypy clean; full doctor suite passes (129).
2026-06-26 04:57:20 -07:00
renovate[bot] 8e362986cc chore(deps): update dependency mermaid to v11.16.0 2026-06-25 19:42:08 -07:00
Patrick Buckley 2b3b212971 Add altair + vl-convert-python viz stack (#685)
* feat(deps): add altair + vl-convert-python viz stack

The standard, ui://-ready visualization stack: one Vega-Lite spec renders to
static SVG via vl-convert (a bundled Rust renderer — no browser, GDAL, or
chromium) and drops into vega-embed for interactive ui:// panels. The first
consumer is the civic-records choropleth map; future ui:// surfaces build on
the same stack.

The dependency closure is fully permissive (BSD-3 + the OFL font + MIT/ISC JS) —
clean for Apache-2.0 and commercial use. Adds a mypy override for the untyped
vl_convert wheel.

* fix(deps): bump pydantic-settings to 2.14.2 (GHSA-4xgf-cpjx-pc3j)

Clears the pip-audit --strict advisory on the transitive pydantic-settings
2.14.1. Pinned as an explicit security floor in [project.dependencies]
(matching the starlette/cryptography CVE-floor convention) even though it is
transitive-only, so the floor is documented and survives re-resolution.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* chore: regenerate uv.lock to pass lock check

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-21 15:39:41 -07:00
Patrick Buckley a7d8895287 test: eliminate leaked-thread test pollution + guard against it
Background daemons, event loops, and test servers that outlived their test
bled into later tests' captured output — an intermittent "I/O operation on
closed file" heisenbug, and the same class behind a past multi-day CI-hang
investigation.

- conftest: a fail-on-leak autouse guard (`_no_leaked_threads`) snapshots
  threads at setup and fails any test that leaves one running past teardown,
  with an `allow_thread_leak` opt-out — so the next leak is caught in minutes,
  not days. Plus `logging.raiseExceptions = False` to mute the benign
  logging-vs-capture-teardown race, and shared loop/server teardown helpers
  (`stop_loop_thread`, `serve_until_exit`).

- collector (PRODUCT FIX): the node-discovery loop slept uninterruptibly, so
  `ClusterCollector.stop()` couldn't join the `console-discovery` thread until
  the full interval elapsed — a real shutdown hang in production (up to
  `discovery_interval`). It now sleeps on an interruptible Event that `stop()`
  sets and `start()` clears.

- test fixtures: docker_healthcheck's HTTP servers, the MCP background event
  loops (shutdown_default_executor + close), and the FastMCP uvicorn upstreams
  (timeout_graceful_shutdown=0 + force_exit) now tear down cleanly instead of
  leaking.

Full non-live suite: 7456 passed, 0 closed-file errors, 0 leaked threads, and
~1.5 min faster (the leaks were dragging it).
2026-06-17 18:07:45 -07:00
Patrick Buckley fd5b710437 chore: bump version to 1.7.0a2 2026-06-16 04:39:26 -07:00
Patrick Buckley e562d04e8b fix(deps): enforce cryptography + starlette security floors
main's lockfile was already on the patched versions (cryptography 49.0.0,
starlette 1.3.1) via renovate, but the pyproject floors (>=42, >=1.0.1) still
permitted a regression to vulnerable versions. Raise the floors to match
stable/1.6's v1.6.7 security fix:
- cryptography >=48.0.1 (GHSA-537c-gmf6-5ccf — bundled OpenSSL vulnerable <48.0.1)
- starlette >=1.3.1 (CVE-2026-54282 host spoof + CVE-2026-54283 url-encoded form DoS)
2026-06-16 04:39:26 -07:00
Patrick Buckley 471d94b27f feat(attachments): rasterize PDF to page images for vision models without native PDF
A vision-capable model that can't ingest PDF natively now gets the PDF
rendered to one image per page instead of extracted text; falls back to
text extraction when rendering yields nothing.

- core/pdf.py: rasterize_pdf via pypdfium2 render + Pillow PNG (page-capped
  at 10, never raises)
- session._wire_content_part: pdf + !supports_pdf + supports_vision ->
  rasterized image parts; else text extraction
- trajectory.resolve_attachment_parts: a placeholder can now expand to a
  list of parts (1->N); the resolve_attachments callback return type widened
  to dict[str, Any] across the provider protocol + 4 providers
- pyproject: pillow dependency
- tests: rasterize_pdf, vision-rasterize gate path, 1->N materialization
2026-06-16 00:48:14 -07:00
Patrick Buckley addb8d0be8 feat(attachments): capability-gated client-side fallback (pdf->text, audio->transcript)
When the active model can't ingest a kind natively, the wire resolver
converts it client-side instead of sending a part the model can't read.
Per-kind ownership, no shared machinery: PDF text-extraction is a
pure-local PDF concern; audio transcription is an STT concern memoized
in the audio domain.

- core/pdf.py: extract_pdf_text via pypdfium2 (pure-local, no network, no
  cache — re-run per build; page-capped)
- core/audio.py: transcribe_cached — non-raising, memoized by
  (alias, content-hash); backend failures not cached
- session._wire_content_part: per-kind dispatch — native where the model
  supports the kind (supports_pdf / supports_audio_input), else fallback;
  display/export resolve natively so no conversion fires on a render
- image left ungated (pre-existing behavior unchanged)
- pyproject: pypdfium2 dependency + mypy untyped-import override
- tests: pdf extraction, transcript memoization, per-kind gate dispatch
2026-06-16 00:48:14 -07:00
Patrick Buckley 3e5f2c3870 test: zero out the suite's warning noise
121 warnings -> 0. Two upstream deprecations get narrowly-scoped
filterwarnings entries (the mcp streamablehttp_client rename — adoption
deliberately rides the v2 migration since the new entry point's call
shape changes again there; the starlette httpx TestClient notice). The
one real RuntimeWarning is fixed at the source: tests that mock
asyncio.run_coroutine_threadsafe handed real coroutines to a stub that
never awaited them, GC-firing 'coroutine was never awaited' inside
whatever unrelated test ran later (the same cross-test bleed mechanism
as the CI closed-stream spew — per-test filterwarnings markers cannot
catch it, which is why two such markers existed and still leaked). A
shared _dispatch_stub now closes real coroutines before returning the
canned future; the obsolete markers are removed.
2026-06-11 20:42:34 -07:00
Patrick Buckley 1497c392e4 chore: cap mcp <2 ahead of the v2 breaking rewrite
mcp 2.0.0a1 shipped 2026-06-11 (stable targeted ~2026-07-27). v2 removes
streamablehttp_client, changes the transport tuple arity, and renames
mcp.types fields to snake_case — all of which our client imports. The
maintainers' release note asks downstream packages to add an upper
bound now (their worked example is this exact constraint). Floor stays
at 1.27: nothing newer adds anything our surface needs, and the #2147
shutdown busy-loop we wrap remains unfixed at every released version.
Resolution is unchanged (1.27.2); lockfile re-pinned metadata only.
2026-06-11 20:42:34 -07:00
Patrick Buckley a137bffa25 chore: bump version to 1.7.0a1 2026-06-10 22:21:43 -07:00
Patrick Buckley 23007e3ac5 chore: bump version to 1.6.0 2026-06-10 22:21:03 -07:00
Patrick Buckley 81a3eaecce chore: relicense BUSL-1.1 → Apache 2.0 for 1.6.0 (#651)
* chore: relicense BUSL-1.1 -> Apache 2.0 for 1.6.0

Flips every license artifact in the tree; 1.5.x and earlier remain
BUSL-1.1 per their release-time LICENSE files. Contributor consent
record: #548 (rationale: #546).

- LICENSE: canonical Apache 2.0 text
- NOTICE: new; copyright line + pointer to THIRD-PARTY-NOTICES
- pyproject.toml: SPDX expression + explicit license-files trio
- Dockerfile: COPY the license trio (hatchling needs them at build)
- THIRD-PARTY-NOTICES: BUSL line reworded; bundled-version drift
  fixed (KaTeX 0.17.0, Mermaid 11.15.0, hls.js 1.6.16)
- README badge + License section, CONTRIBUTING inbound-license line,
  TS SDK package(+lock), example pyproject
- docs/pgbouncer.md: drop stray ':' introduced in #353

* docs: add CONTRIBUTORS.md

* chore: drop LICENSE leading blank line

The apache.org LICENSE-2.0.txt begins with a newline; the SPDX
canonical text and GitHub license templates do not. Use the
conventional form — detection is whitespace-normalized either way.
2026-06-10 21:03:16 -07:00
Patrick Buckley 160062235a chore: bump version to 1.6.0rc2 2026-06-10 13:35:47 -07:00
Patrick Buckley 319b73d046 chore: bump version to 1.6.0rc1 2026-06-09 13:39:19 -07:00
Patrick Buckley 114ada791b feat(providers): add Claude Fable 5 to the Anthropic provider
- claude-fable-5 capability entry: 1M context / 128K output, adaptive
  thinking (summarized display), effort low..max incl. xhigh, no
  sampling params, web + tool search, vision, reasoning replay, native
  mid-conversation system messages
- document the Fable 5 wire quirk at the capability table: an explicit
  thinking={"type": "disabled"} is a 400 on this model; the adaptive
  branch never emits "disabled", so adaptive-or-omitted is preserved
- widen the native mid-conversation-system comments from opus-4-8-only
  to opus-4-8 + fable-5 (protocol, provider, tool_advisory, prompts,
  session)
- raise the anthropic SDK floor 0.39 -> 0.108: 0.39 predates every
  named kwarg the provider sends (output_config 0.77, top-level
  cache_control 0.83, mid-conversation system blocks 0.105); 0.108
  adds claude-fable-5
- tests: capability assertions for claude-fable-5 + dated-variant
  prefix match
2026-06-09 10:50:58 -07:00
Patrick Buckley a5bc37058e chore: bump version to 1.6.0a12 2026-06-08 10:12:29 -07:00
Patrick Buckley 87d1ea8530 chore: bump version to 1.6.0a11 2026-06-04 18:40:02 -07:00
Patrick Buckley b3c1b9c9e0 build: promote anthropic, postgres, console, tls to core dependencies
The Anthropic SDK provider was the lone first-class provider gated behind
an optional extra, while OpenAI ships in core and Google rides the
OpenAI-compatible path. Fold anthropic, psycopg (postgres), croniter
(console), and lacme (tls) into the base dependency set so a default
`pip install turnstone` yields a complete single- or multi-node
deployment; only the Discord/Slack channel gateways stay optional.

- pyproject: four extras → base deps; `all` is now discord+slack; drop the
  redundant croniter from the `test` extra; regenerate uv.lock.
- ci: the postgres test job installs `.[test]` (psycopg is base now).
- providers: `_ensure_anthropic` becomes a thin SDK accessor for
  `create_client`; drop the now-redundant eager import-guard calls from
  the streaming/completion hot path (anthropic is always present).
- bootstrap: import anthropic directly.
- tests/docs: drop the anthropic importorskips and stale extra-install hints.
2026-06-04 11:03:13 -07:00
Patrick Buckley 87c51b43ca chore: bump version to 1.6.0a10 2026-06-01 21:36:36 -07:00
Patrick Buckley 40b49c1d6c chore: bump version to 1.6.0a9 2026-05-31 20:45:06 -07:00
Patrick Buckley 110d44b07e refactor(tools): remove man, math, and plan_agent built-in tools
`man` and `math` duplicated capabilities already reachable through
`bash`; `plan_agent` is better expressed as a `task_agent` running a
planning skill, and carried a large amount of special-case machinery
(plan-review gate, refinement loop, per-kind model routing). Removing
all three shrinks the tool surface and cuts per-call token cost.

Also removed, as dead-once-the-tools-are-gone:
- the `math` sandbox executor (`turnstone.core.sandbox`) and its
  `[sandbox]` extra; the eval analyst now runs bash-only
- the read-only `AGENT_TOOLS` sub-agent tool set and the `agent`
  tool-metadata key (`task_agent`/`TASK_AGENT_TOOLS` retained)
- the plan-review protocol end to end: the `on_plan_review` UI hook,
  `resolve_plan`, `POST /v1/api/plan` + `POST /v1/api/route/plan`,
  the `plan_review`/`plan_resolved` SSE events, and their Python SDK /
  TypeScript SDK / OpenAPI / frontend / Discord+Slack bindings
- the `model.plan_alias` / `model.plan_effort` settings and the
  registry `plan_model` / `plan_effort` routing fields

TOOLS 31->28, TASK_AGENT_TOOLS 13->11; COORDINATOR_TOOLS unchanged.

BREAKING CHANGE: removes the `man`, `math`, `plan_agent` tools, the
plan-review SSE/HTTP/SDK surface, and the plan_* model-routing settings
from the experimental 1.6 line.
2026-05-31 19:54:43 -07:00
Patrick Buckley 1728a4c0af feat(web-search): replace Tavily/DuckDuckGo backends with self-hosted SearxNG
Drop the Tavily and DuckDuckGo (ddgs) web_search backends for a single
self-hosted SearxNG service bundled into the docker-compose stacks.

Core:
- New SearXNGClient + _format_searxng; rewrite resolve_web_search_client to
  (backend, searxng_url, searxng_engines, ...). MCP backend + oauth_user guard
  unchanged. _resolve_search_client follows storage -> toml -> env -> default
  precedence (explicit "" disables, via ConfigStore.stored_keys()).
- Drop the Tavily-era topic=finance (no SearxNG category); topic is now
  general/news.

Settings/config:
- Remove tools.tavily_api_key, get_tavily_key, $TAVILY_API_KEY, [api].tavily_key.
- Add tools.searxng_url (default http://searxng:8080) + tools.searxng_engines,
  with get_searxng_url/get_searxng_engines.

Compose + bundled config:
- Internal-only searxng service (no published API port, :ro config, /healthz
  healthcheck, persistent searxng-cache volume) in both stacks; bundle
  turnstone/deploy/searxng/settings.yml (JSON output on, limiter off).
- Caddy serves the SearxNG web UI on :8444 (dev: localhost-only; prod: opt-in).
- bootstrap extractor + wheel packaging updated.

Deps: drop the ddg extra + ddgs mypy override (regenerates uv.lock, removing the
lxml/h2/brotli transitives).

Docs: tools/docker/architecture/openshell + diagrams + config example + CHANGELOG;
docs/docker.md carries the AGPL-3.0 §13 operator note.

BREAKING: tools.web_search_backend no longer accepts "tavily"/"ddg";
tools.tavily_api_key and the ddg extra are removed. Run the bundled SearxNG (ships
in the compose stacks) or set TURNSTONE_SEARXNG_URL to an external instance.

Closes #545
2026-05-31 19:03:16 -07:00
Patrick Buckley 631f1b0021 feat(compose): cluster-by-default Caddy-fronted stack with bare-metal join
`docker compose up` from a clone builds one image and brings up the whole stack
— PostgreSQL, console, Caddy, channel, and 10 server nodes — sharing one
Postgres so the console discovers every node. The dashboard is reachable only
through Caddy (HTTP/2 avoids the browser's 6-connection cap on the dashboard's
SSE streams); the console's plain-HTTP port is no longer published. Postgres
binds 127.0.0.1 so a bare-metal turnstone-server can join the cluster — the
bare-metal overlay is folded in and removed. Insecure dev defaults keep it
zero-config; the bundled production stack mirrors the shape but pulls ghcr
images and requires real secrets.

Move the Caddyfile under turnstone/deploy so it ships in the wheel; update docs,
QUICKSTART, and .env.example to match.
2026-05-31 16:05:21 -07:00
Patrick Buckley e322b69c8b chore: bump version to 1.6.0a8 2026-05-31 00:58:44 -07:00
Patrick Buckley 14369cecfa chore: bump version to 1.6.0a7 2026-05-29 18:44:56 -07:00
Patrick Buckley 4ee043d949 chore: bump version to 1.6.0a6 2026-05-28 21:30:37 -07:00
Patrick Buckley 76faf81bf2 chore: bump version to 1.6.0a5 2026-05-24 18:37:20 -07:00
Patrick Buckley aa9812f2a6 chore: bump version to 1.6.0a4 2026-05-23 18:11:24 -07:00
renovate[bot] 747ee99220 chore(deps): update dependency katex to v0.17.0 2026-05-23 11:33:36 -07:00
Patrick Buckley cdba6dea7a chore: bump version to 1.6.0a3 2026-05-22 20:05:29 -07:00