v0.0.16 (WIP): capability-driven context budgets, cost estimation, legacy cleanup, docs sync#23
Conversation
…lity, add cost estimation - Compactor context window now resolves live from the narrative slot's model capability (llm.toml hot-reload aware); COVEL_COMPACTOR_CONTEXT_WINDOW becomes an optional explicit override (fallback 32768). - New CJK-aware estimateTokens replaces the chars/4 heuristic that undercounted Chinese text ~3x. - Activate the applyBudget hard prune as last line of defense: estimator + getter-based contextBudget injected into turn-executor deps. - Debug cost panel: per-model USD estimation via model-db pricing; model recovered by pairing llm.responded with the preceding llm.calling. - ModelCapabilityInfo gains the flat per-M-token price fields actually returned by /api/model-db/lookup. Riders on shared files: memorySystem module-setter typing cleanup (actions.ts), withSessionLock ghost-comment reword (env.d.ts), dead searchModelDb removal (llm.ts), orphaned lorebook i18n keys removal (locales), doc-sync lines in CLAUDE.md / env-registry.md.
Deletions verified zero-reference (.ts and .js cross-checked; plugins are plain JS): - memory: unused compactor chain (production compaction lives in @covel/context maybeCompact) incl. CompactionConfig/Result types - ai-provider: never-wired Langfuse trace hook + LANGFUSE_* env entries - store: 18 superseded row-mapper re-exports (both SQL backends), mediaObjectKey, dead ALL_TABLE_NAMES re-export - shared: orphan plugin type modules (character-presence, branch-reply, living-world-rules) - runtime: HookPipelineRun, backward-compat re-export block in index.ts, collectEventsFrom export demoted to private - create: createPlugin chain (CLI scaffold is an independent script); createWorld path kept - plugin-test-utils: createTestHarness retired (documented rejection in scene-stage integration test); unused deps dropped, lockfile synced - web: lorebook client (backend routes stay), schema-block-renderer, searchModelDb, fetchPluginDocs, getPluginData, syncSessionDimensions, legacy fetchTraceEvents, browser media-store, LEGACY_EVENTS fallback, LEGACY_SLOTS renamed to DEFAULT_FALLBACK_SLOTS - server: pathExists, ui-spec-schema dead exports demoted, loadWorldPackages export demoted, withSessionLock ghost comments fixed - root configs: stale apps/desktop-tauri entries - docs: plugin-testing guide rewritten around the patterns actually in use; flow/authoring guides and create-plugin skill references synced; CHANGELOG CI wording matched to the real workflow
- Add the supply-chain .npmrc (minimum-release-age) to all 20 bundled plugins, matching the plugin-authoring appendix; scaffold already copies it from templates via dotfile-inclusive copyDir. - Add scene-prompts PLUGIN.en.md so English sessions get an English agent prompt (was the only CJK-bodied schedulable agent without one). - Migrate haruka-academy world.yaml from deprecated top-level required/recommended/excludedPlugins to pluginPolicy (fold shim kept for third-party worlds).
…ry, set compat-layer sunset - Remove the blockSchemas dead chain end to end: the registry was written at boot but never read after message rendering moved to json-render. Drops block-renderer.tsx, boot-time fetch/set, fetchBlockSchemas client, GET /api/block-schemas route, BlockSchemaDeclaration/Meta shared types, and the api.md row. - Remove the never-read COVEL_ALLOWED_LLM_HOSTS registry entry; the SSRF guard is open by design (CLAUDE.md / env-registry wording updated). - Give the deprecated PLUGIN.md registration fields (tools.local / hooks / rpc / wires) an explicit sunset: planned removal in v0.0.17 (deprecation note, boot warning, plugins.md).
Cluster-by-cluster verification of docs/ against the codebase (every fix backed by a file:line check): - flow.md: model-db count 2967, createGateway factory names, full 11-type proposal enum, compactor + applyBudget window resolution, plugin-test-utils surface, slot examples aligned with slots.md - api.md: drop deleted plugin-data-contract route; add missing DELETE /api/worlds/:id, /api/plugin-flows, media-token, install endpoints; rewrite event-type tables around the CovelEvent union - protocol.md: fix stale file/line anchors, add proposalType to hook trace payloads - plugins.md/tools.md: fix world-init ui spec name, branch-reply slot column, chat-mode-narrator requires, player-init runtime shape (agent + guard, not function), approval policy path; mojibake repair - ui-panels/ui-components: drop fixed Lorebook tab claim, add missing character-presence and scene-cast panels, component count 48 - CLAUDE.md: 27 store tables (with real pointer), app.ts size, ~48 component catalog - glossary.md: split pluginType (core-plugin/plugin) from trust tier (builtin/official/community) - two independent axes - README/guides: verified, minor sync only Historical docs (CHANGELOG, explorations, superpowers, audits) deliberately untouched.
Explore/verify workflow audit (24 confirmed findings); this lands all small, safe fixes: - dead code: unused @covel/context re-exports in runtime barrel, cloneVectorRows, 8 unused ui-* CSS classes (incl. paper theme ref) - stale docs: remove CLAUDE.md section for the deleted LLM replay cache, correct TRUSTED_PROXY_IPS status in env-registry, retag shipped S4-T4.c TTL sweep from TODO to spec, fix stale comments - logging: settings REST backend now surfaces failed PUTs (res.ok), keep stacks in turn-executor warn paths, add [shutdown] prefixes, log Data Explorer catch paths and model-db refresh failures - consistency: shared readJsonBody helper in api-error.ts; 13 routes now return 400 invalid_json_body instead of 500 on malformed JSON - perf: plugin-data-store snapshots scoped per (pluginId, namespace) so unrelated plugin-data writes no longer re-render every panel
… from registry
Two verified high-impact items from the six-dimension audit:
1. Bounded per-turn message reads. loadTurnSessionState no longer
full-reads listTurnMessages every turn (the log only ever grows —
compaction tags rows but never deletes). New DataStore surface:
- listUncompactedTurnMessages(sessionId): the raw suffix the
compactor and prompt builder actually operate on (the compacted
prefix is represented by session summaries)
- getTurnMessageStats(sessionId): grouped-count aggregate for
turnNumber / per-runtime trigger counts (GROUP BY on SQL backends
via a new SelectOpts.groupBy; shared JS fallback for memory/idb)
buildMessageHistoryWithSummaries now emits summaries not referenced
by any history row up front (chronological), so uncompacted-suffix
histories keep their compacted context; maybeCompact counts ALL
session summaries for its token estimate for the same reason.
Full-history callers are byte-identical in both cases.
2. IDB deleteSession cascade is now derived from SESSION_SCOPED_TABLES
(new explicit idbStore field), matching the SQL/Memory backends —
a newly registered session table can no longer silently leak rows
on the browser backend. Index-vs-filter deletion is auto-detected
from the store's own indexNames; the IDB-only
state_snapshot_metadata sidecar stays as one explicit residual.
Validation: store contract suite extended for both new methods and run
against memory/sqlite/idb plus a real PostgreSQL container (152/152 —
covers PG's string count(*) normalisation); runtime read-dedup test
updated to pin the new two-read pattern; full turbo lint 21/21.
…al, settings probe, chat window cap)
Four verified backlog items, per owner triage:
- perf(memory): recall ingestion no longer full-reads + sorts the whole
turn-message log every turn. New DataStore.listTurnMessagesAfter
(forward keyset on (createdAt, id), SQL tuple predicate + shared JS
fallback, contract-tested incl. same-createdAt tie-break) reads just
the batch window past the persisted cursor. Also fixes a latent
stall: a run of empty-content rows now advances the cursor instead
of pinning it forever, and cursor ordering now matches the store's
byte-order keyset exactly.
- refactor(runtime,server,context): remove two zero-caller extension
seams. findToolClient (structurally broken for any non-InMemory
client) is gone from ToolExecutorConfig; tool resolution is the
plain findTool path. The getConfigFn / ctx.config / {{ config.* }}
chain (always {} in production, undocumented) is deleted end to end,
plus stale comment references ({{ config.worldEntries }} et al).
- fix(web): settings backend selection now honours the REST-desktop
tier. probeDesktopMode() runs BEFORE initSettings() and the backend
picks via isDesktopApp(), so self-host setups without the Electron
IPC bridge persist settings to ~/.covel/settings.json instead of
silently falling back to localStorage.
- feat(web): live chat messages are capped in memory (configurable
ui.chatMessageWindow setting, default 2000, min 200). The cap drops
oldest rows on live append only and advances olderMessagesCursor to
the new window edge so scroll-up re-fetches exactly the dropped
range; streaming placeholders are never dropped and explicit
history loads never cap.
Validation: full workspace tests 40/40 tasks, turbo lint 21/21,
prettier + oxlint clean; new contract tests ran on memory/sqlite/idb.
|
Appended the 2026-07-17 six-dimension audit series (3 commits):
Validation: full workspace tests 40/40 turbo tasks, lint 21/21, prettier/oxlint/i18n clean. |
…l discovery)
Runtimes with large tool whitelists can now declare tools.defer
(true = whole whitelist, string[] = listed names). Deferred tools stay
registered and authorized but are withheld from the initial LLM tool
list; the framework injects a search-tools definition instead, whose
description advertises the pool size and owner. When the LLM calls it,
the agent loop intercepts the call (suspend/runtime-done sentinel
pattern — never dispatched to the ToolExecutor), ranks the deferred
pool with BM25, and grows the working tool surface so matches are
directly callable from the next step for the rest of the turn.
Design follows openai/codex's tool_search (ToolExposure::Deferred +
BM25 over name/description/parameter-schema text, default limit 8):
- zero-dep BM25 (k1=1.2, b=0.75) in @covel/tools; CJK text tokenizes
into character bigrams so Chinese queries match non-adjacent words
('解锁图鉴' → '解锁…图鉴条目'), with substring fallback
- corpus per tool = name + description + parameter-schema field
names/descriptions (parameterSchemaSearchText)
- search-tools is framework-injected only: not registered in the tool
map, not declarable in tools.builtin; searchable pool is exactly the
runtime's own declared whitelist resolved through getToolInfo, so
the builtin/local access boundary is inherited unchanged
- defer entries outside the whitelist are ignored — deferring can
never grant access to an undeclared tool
- searches are traced as normal toolCalls records
No-defer runtimes are byte-identical to before (regression-pinned).
Docs: tools.md (registry row + search-tools contract), plugins.md
(tools.defer field), plugin-authoring-advanced.md (usage guidance).
Validation: 14 new tests (BM25 ranking/CJK/limits ×9, agent-loop
integration ×5); full workspace tests 40/40 tasks, lint 21/21,
prettier + oxlint clean.
…er path Provider connectivity test (and every real LLM call) failed with "SSRF policy rejected ...198.18.x.x" on machines running a TUN proxy: Clash/mihomo/sing-box/Surge fake-IP mode maps every external hostname into 198.18.0.0/15 and routes by SNI, and the DNS-pinning guard rejected those answers as non-public. LAN endpoints (Ollama at 192.168.x.x) hit the same wall. The self tier is already single-user local play (loopback-bound, owner/operator tokens no-op), so the core provider path (the user's own configured baseUrl via postJson/getJson) now accepts any resolver answer for a hostname; the socket stays pinned to it, preserving the anti-rebinding guarantee. The exemption is deliberately narrow: - plugin ctx.http (third-party code) stays strict — no local-network probing even on desktop - IP-literal URLs get no exemption (url-safety blocks private literals) - hosted tiers (demo/commercial) keep the strict public-only policy Verified end-to-end against api.deepseek.com through the real connect-pinned dispatcher (reaches the host, 401 on the dummy key). Tests: request-dns-safety split into hosted-tier-strict and self-tier-core-accept + ctx.http-still-strict cases; ai-provider 239 pass; turbo lint 21/21.
What changed
Long-context stability + cost visibility (
d6a8977d)COVEL_COMPACTOR_CONTEXT_WINDOWbecomes an optional override (fallback 32768).estimateTokensreplaces thechars/4heuristic that undercounted Chinese text ~3x.applyBudgethard prune activated as the last line of defense (estimator + getter-based budget injected into turn-executor deps).llm.respondedwith the precedingllm.calling).Dead-code / legacy cleanup (
f275a3d1,7bcfd960, −2600 lines)createPluginprogrammatic API, 18 superseded store row-mappers, 3 orphan shared type modules, web lorebook client + orphan API functions,createTestHarness, write-only blockSchemas chain (incl.GET /api/block-schemas), tauri config residue, LEGACY_EVENTS fallback, never-readCOVEL_ALLOWED_LLM_HOSTS.tools.local/hooks/rpc/wires) given an explicit sunset: removal planned for v0.0.17.Plugin contract fixes (
391d0526).npmrcsupply-chain guard added to all 20 bundled plugins;scene-promptsgainsPLUGIN.en.md; haruka-academy migrated topluginPolicy.Docs synced to current architecture (
7452f693)Why
Compaction previously triggered against a fixed 32k window regardless of the actual model, Chinese sessions blew past real limits unnoticed, and users had no cost visibility. An 8-way audit then confirmed substantial dead/legacy code and doc drift accumulated across recent iterations.
How it was validated
pnpm lint(21/21),pnpm test(40/40 packages),format:check,deps:check,check:i18nall green.*.tsand*.js(plugins are plain JS), plus an adversarial review pass over the diff.Known risks / follow-ups
defaultslot capability, not per-sessionruntime_model_overrides(noted in code).