Commit Graph

18 Commits

Author SHA1 Message Date
Yichen Jiang f376ee23d1 fix(llm): size unknown models and refuse a section that cannot be served
Three defects surfaced while driving the Models page.

A hand-declared model needed an explicit contextWindow and maxTokens,
but a provider listing usually returns ids and nothing else — so the
page happily wrote a profile the adapter then rejected, which took the
whole namespace down silently. Capacities now fall back to the route's
`defaultContextWindow` (262,144) and `defaultMaxTokens` (32,768). Both
are guesses by construction, which is why they are route fields a
deployment corrects once rather than constants buried in the adapter;
the fallback sizes the model and never becomes a per-request cap.

That silent failure was the second defect. A schema-valid profile the
adapter could not serve was stored and only rejected later, disabling
every route in the namespace with nothing said. `dsh-settings` gains an
optional `validate` on registration — a check for what a schema cannot
express — and `llm-pi-ai` refuses an unserviceable section at the write
that produced it. A stored section that fails keeps the namespace's last
good value, as a schema failure already did, so an externally edited
document still cannot strand the owner. The plugin's own last-good
fallback goes with it: nothing reaching it can fail any more.

Third, a model with no reasoning metadata advertised the single level
`off`, which pi-ai translates to *omitting* the reasoning option — the
same request naming no effort produces. Selecting it disabled nothing,
so a provider whose default is to think kept thinking with `off` shown
as selected. Such a model now reports no reasoning capability at all,
which is the seam's way of saying the control is unavailable, and the
per-model `reasoning` flag is gone: without a thinkingLevelMap to spell
levels it could only invent them.

The protocol table narrows to the three a hand-declared route reaches
today, most-reached first so a surface offering a choice defaults to the
one gateways actually speak.
2026-08-05 18:54:23 +08:00
Yichen Jiang 4c80cab108 fix(llm): capture an immutable snapshot per pi-ai operation
Review found four defects in the declared-provider work.

`PiAiAdapter` reused one `Models` collection and mutated it whenever the
configuration changed. `Models.streamSimple()` resolves its provider
lazily — when the stream is first consumed, which is after the adapter
awaits the route's credential — so a configuration change landing in
that window let an in-flight request finish under a configuration it
never resolved against, or fail on a provider that no longer existed.
Each resolution now produces an immutable snapshot and every operation
captures one before its first await, which is what makes the seam's
per-step freeze (`llm.prepareCall()`) hold end to end: switching models
mid-reply takes effect on the next step, never inside the one in flight.

`defaultMaxTokens` was materialized from the catalog's `Model.maxTokens`.
The two answer different questions: pi-ai requires that field as the
model's output capability, while the seam's is a cap the deployment
chose to send on requests naming none, so every request had started
carrying a number nobody picked. Only an explicitly configured cap
reaches the seam now.

The configurable-provider directory was refreshed by disposing its
registration and making a new one. A candidate set the registry refuses
— a profile keyed `deepseek-official`, which llm-deepseek declares —
left the whole directory withdrawn and the Models page empty, silently,
because the settings callback contains the failure. The seam's
registration handle now carries `replace()` with the same
validate-first atomicity `registerAdapter` has.

The protocol table offered every pi-ai streaming API, including four
whose authentication a profile cannot express: Bedrock signs with SigV4
over AWS credentials and a region, Vertex needs a project, a location,
and ADC, Azure needs provider environment plus an api-version, and Codex
uses OAuth. Offering them handed back routes that cannot authenticate.
Catalog routes still reach them through their own provider.
2026-08-05 18:54:23 +08:00
Yichen Jiang d6126c25f2 feat(llm): declare pi-ai providers instead of looking them up
A pi-ai route had to name an installed catalog provider, served that
catalog's models verbatim, and could override only the endpoint. An
OpenAI-compatible gateway, a self-hosted server, or a model newer than
the pinned pi-ai release was therefore unreachable, and a stale context
window could not be corrected without upgrading the package.

A route is now a declaration whose defaults come from the installed
catalog. `catalog.ts` merges that catalog under the profile's own model
entries, `provider.ts` builds the pi-ai Provider (reusing the catalog
provider when the route keeps its protocol, so implementations this
package cannot reconstruct keep working), and the adapter serves every
operation from one `createModels()` collection. That also retires the
`@earendil-works/pi-ai/compat` import, which pi-ai documents as a
temporary entry point it deletes with its ModelManager migration.

Credentials stay on the harness seam: the resolved key rides the request
as pi-ai's highest-priority auth override, so `Models` holds no
credential store and a named-but-missing reference still fails loud
instead of falling back to an unrelated ambient key.

A model's configured maxTokens now reaches the seam as defaultMaxTokens.
2026-08-05 18:54:23 +08:00
Turtle c991a7cc79 docs: finish hierarchy rescan after rebase 2026-08-05 16:18:58 +08:00
xjt 2db712eec7 docs(i18n): proofread active Chinese documentation 2026-08-04 17:36:14 +08:00
Yichen Jiang e6483f0afc feat(settings): detect stale writers with a revision, and announce raw changes
The remaining P1 from the #939 review, plus the P2 it shares a mechanism with.

Nothing carried a version, so two tabs editing one namespace silently
overwrote each other — reproduced as tab B's `reasoning` lost to tab A's
older draft. The seam's per-namespace write queue orders writes; it cannot
tell a fresh writer from one replaying a snapshot a predecessor superseded.

Each namespace now carries a monotonic `revision` over its RAW section. A
write may send `expectedRevision`, checked at the FRONT of the queue (not at
call time, which would race the very predecessor it guards against); a
mismatch rejects with `SettingsConflictError` → `settings-conflict` on the
wire, carrying both revisions. The editor captures the revision it opened at
and, on conflict, asks the user to reopen rather than replaying its snapshot.

The same counter fixes the missing broadcast. `settings/updated` is gated on
the resolved value — correct for consumers, wrong for configuration surfaces:
storing an override equal to the composition base leaves the resolved value
alone while changing what the document says (the field is now overridden, not
inherited) and moving every open editor's revision. `settings/document-updated
(ns, revision)` fires on any raw-section change, in-process or external, and
`host/settings-changed` now rides it.

That event also closes the stale model picker: editing a provider's `models`
changes no route, so `llm/adapters-updated` never fired and an open picker
kept serving the old catalog. A change to an exposed provider namespace now
emits `host/models-changed` too — that namespace holds the catalog.

Docs: both sides of the five touched README pairs, a type-equiv block for
`SettingsPathOp`, and an Agent Note recording what the plane exposes and who
may overwrite what. The deferred wire-redaction gaps (secrets behind
union/intersection/transform, `.default(...)` in the served envelope, schema
text in rejection messages, `new Function` rehydration, pi-ai's `headers`) are
recorded as TODO(settings-wire-redaction) and in Known Limitations rather than
half-fixed.
2026-07-30 19:24:21 +08:00
Yichen Jiang 0a263941ef Merge branch 'worktree-llm-dynamic-config' into worktree-llm-web-config
# Conflicts:
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.i18n.yaml
#	docs/event-producer-consumer.md
#	examples/headless-agent/tests/headless.snapshot.ts
#	examples/headless-agent/tests/snapshots/missing-credential/stream-json.expected.jsonl
#	packages/llm/llm-deepseek/README.i18n.yaml
#	packages/llm/llm-deepseek/src/index.ts
#	packages/llm/llm-pi-ai/README.i18n.yaml
#	packages/llm/llm-pi-ai/src/index.ts
#	packages/llm/llm/README.i18n.yaml
#	packages/llm/llm/src/index.ts
2026-07-30 17:22:44 +08:00
Yichen Jiang 9626c15c6b test(sandbox): carry the resolved read denials through consumer policy assertions
The policy home's resolve() now stamps readDenyPaths, so every consumer
that pins the resolved shape (bash-sandbox hand-off, tool-fs stamps)
carries it, and three uncovered branches gained real tests: landlock
reporting partial enforcement for a denial it cannot express, the
policy's default under programmatic construction, and both ambient
credential paths in llm-deepseek without a mounted seam.
2026-07-30 16:11:21 +08:00
Yichen Jiang 483199d47a Merge branch 'worktree-llm-dynamic-config' into worktree-llm-web-config
# Conflicts:
#	apps/cli/cordis.yml
#	apps/web/tests/snapshots/code-mode-round/session.jsonl
#	apps/web/tests/snapshots/cordis-tool-round/session.jsonl
#	apps/web/tests/snapshots/fresh-round-trip/session.jsonl
#	apps/web/tests/snapshots/lifecycle-chrome/session.jsonl
#	apps/web/tests/snapshots/live-interactions/session.jsonl
#	apps/web/tests/snapshots/navigation-panes/seed.jsonl
#	apps/web/tests/snapshots/question-composer/session.jsonl
#	apps/web/tests/snapshots/seeded-history/seed.jsonl
#	apps/web/tests/snapshots/steering/session.jsonl
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.i18n.yaml
#	docs/core-data-structures/settings.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	examples/acp-agent/tests/snapshots/workspace-context/session.jsonl
#	packages/client/connection/README.i18n.yaml
#	packages/client/connection/src/index.ts
#	packages/client/connection/tests/node-half.spec.ts
#	packages/client/runtime/README.i18n.yaml
#	packages/client/runtime/README.md
#	packages/client/runtime/README.zh.md
#	packages/client/runtime/src/client/index.ts
#	packages/client/runtime/tests/fake-api.ts
#	packages/client/ui-models/README.i18n.yaml
#	packages/examples/tui-demo/README.i18n.yaml
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/package.json
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/rpc.schema.ts
#	packages/host/apiproxy/src/api/rpc.ts
#	packages/llm/llm-deepseek/README.i18n.yaml
#	packages/llm/llm-deepseek/README.zh.md
#	packages/llm/llm-pi-ai/README.i18n.yaml
#	packages/llm/llm/README.i18n.yaml
#	packages/llm/llm/README.zh.md
#	packages/sdk/sdk-client/README.i18n.yaml
#	packages/settings/settings/README.i18n.yaml
#	packages/settings/settings/README.md
#	packages/settings/settings/README.zh.md
#	packages/subagent/subagent-dsh-sdk/README.i18n.yaml
#	packages/subagent/subagent-dsh-sdk/README.zh.md
#	packages/support/llm-replay/README.i18n.yaml
#	packages/ui/jsonrpc/README.i18n.yaml
#	packages/ui/jsonrpc/README.zh.md
#	packages/ui/tui/tests/snapshots/model-selector.expected.txt
#	packages/ui/tui/tests/snapshots/model-switching.expected.txt
#	packages/ui/tui/tests/snapshots/resume-sessions.expected.txt
#	packages/ui/tui/tests/snapshots/status-diagnostics-narrow.expected.txt
#	packages/ui/tui/tests/snapshots/status-diagnostics.expected.txt
#	packages/ui/tui/tests/tui.snapshot.ts
#	pnpm-lock.yaml
#	python/sdk/README.i18n.yaml
#	scripts/snapshots/translation-prompt-v4/request-response.expected.json
2026-07-30 15:18:26 +08:00
Yichen Jiang 8ff6b23214 Merge branch 'worktree-config-settings-seam' into worktree-llm-dynamic-config
# Conflicts:
#	docs/capability-seams.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.i18n.yaml
#	docs/module-graph.md
#	examples/tui-agent/cordis.yml
#	packages/README.i18n.yaml
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/llm/llm-deepseek/README.i18n.yaml
#	packages/llm/llm-deepseek/README.zh.md
#	packages/llm/llm-pi-ai/README.i18n.yaml
#	packages/llm/llm-pi-ai/README.zh.md
#	packages/settings/settings-local/src/index.ts
#	packages/util/README.i18n.yaml
#	packages/util/README.md
#	packages/util/README.zh.md
#	scripts/doc-budgets.manifest.json
2026-07-30 14:33:36 +08:00
Yichen Jiang 51415debe5 docs: bilingual config-plane documentation, regenerated catalogs, and the web-config-plane Agent Note 2026-07-30 10:53:39 +08:00
j-xiang 5bd49b1f0e docs(i18n): address final line audit 2026-07-29 16:53:53 +08:00
j-xiang 42840394c4 docs(i18n): proofread README translations 81-100 2026-07-29 15:29:38 +08:00
Yichen Jiang 4e9916b3e5 feat(llm-pi-ai): dormant bare mount — routes live entirely in the settings plane
An empty or omitted providers dict is now the valid dormant posture: the
adapter mounts with zero routes and no catalog entries, registers routes
the moment the llm-pi-ai settings section supplies profiles, and drops
them when it empties. The TUI demo mounts the adapter bare, so adding an
openai/anthropic provider is purely a settings.yaml (or, next PR, web
form) operation with per-request apiKeyEnv credential resolution.
2026-07-29 14:56:09 +08:00
Yichen Jiang b0a2011d95 docs: bilingual credentials/settings-consumer documentation, catalogs, and gates
New credentials data-structure page (type-equiv manifested), group README,
rewritten llm-deepseek/llm-pi-ai READMEs (dynamic configuration, dict
profiles, credential chain), capability-seams/service-role registration,
Agent Note (bilingual), demo compositions mounting settings-local +
credentials-local with no inline key plumbing, installSettingsSection
consumer helper on the settings seam (deduplicating both adapters' wiring),
jscpd symmetry markers for the provider twins, runtime-closure additions for
python/sdk-runtime, and doc-budget ceilings AGENTS.md 1750→1755 /
packages/README.md 850→865 for the structural one-line group rows.
2026-07-29 14:20:06 +08:00
Tianyi Cui 8a952ab433 Merge remote-tracking branch 'origin/master' into worktree/pr628-merge-20260727
# Conflicts:
#	.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md
#	docs/architecture.i18n.yaml
#	docs/config-catalog.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/llm-streaming.i18n.yaml
#	docs/core-data-structures/llm-streaming.md
#	docs/core-data-structures/llm-streaming.zh.md
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	examples/headless-agent/tests/headless.snapshot.ts
#	packages/compact/compact-basic/tests/compact-loop-repro.spec.ts
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/core/agent-loop/README.md
#	packages/core/agent-loop/src/loop.ts
#	packages/examples/agent-spine-demo/README.md
#	packages/llm/README.md
#	packages/llm/llm-deepseek/src/adapter.ts
#	packages/llm/llm-pi-ai/src/adapter.ts
#	packages/llm/llm-retry/README.md
#	packages/llm/llm/README.md
#	packages/llm/llm/src/index.ts
#	packages/llm/llm/tests/service.spec.ts
#	packages/support/llm-replay/src/index.ts
#	packages/support/llm-replay/tests/llm-replay.spec.ts
2026-07-27 22:44:43 +08:00
Yichen Jiang 21aeff3029 Merge remote-tracking branch 'origin/master' into worktree/llm-reasoning-effort
# Conflicts:
#	.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml
#	docs/config-catalog.md
#	docs/core-data-structures/session.i18n.yaml
#	scripts/type-equiv.manifest.json
2026-07-27 13:35:10 +08:00
Tianyi Cui 226dc7a249 docs: translate remaining READMEs 2026-07-26 05:06:39 +08:00