Releases: ackness/covel
Release list
Covel v0.0.17
[0.0.17] - 2026-07-21
Trust-boundary and commit-integrity release, plus a smoother story-mode opening. A consolidated full-repository audit (2026-07-20) drove three remediation batches: plugin identity and community-code approval are now enforced end to end, a turn's commit outcome is authoritative everywhere, and single-connection stores serialize writes against open transactions. The GalGame stage now pre-warms its art during pre-game and appears as soon as the player submits the opening form. Toolchain moved to pnpm 11 / TypeScript 7 / Vite 8 / Electron 43, and the runtime baseline is Node 26.
Added
- Stage media preload — known scene backdrops and character sprites/avatars are warmed into the browser media cache as soon as a session opens (world-package media is already in the MediaStore at creation), so the opening turn paints them straight from cache instead of downloading after the pre-game turn ends.
- Early stage entry — the visual stage mounts the moment the player submits the opening (character-creation) form, showing the world hero backdrop and a thinking indicator until the narrator's first
scene.setswaps in the real scene art. Previously the player stared at the chat stream until pre-game fully completed. turn_results.commit_status+origin— every execution artifact records whether its proposals actually committed (pending→committed/failed; a lingeringpendingis a crash signature) and which path produced it (player/manual/follower/recursivewithparent_turn_id), sosession.turnCountcounts committed player turns only.proposal.failedSSE/trace event — a proposal commit failure surfaces to the client and withholds the completion barrier instead of reporting turn success.
Security
- Canonical plugin identity — install derives one canonical ID from the manifest root name; the scoped-npm impersonation bypass (e.g.
@covel/plugin-narratorclaiming the builtinnarrator) returns 409, and boot discovery hard-fails a dir/frontmatter identity mismatch. - Two-phase community approval enforced exactly — grants are exact-action only (a prefix match previously let any grant unlock everything); community runtime dispatch, declarative RPC handlers, and entry imports all walk
covel:plugin-server-codefirst, then the specific grant. - Executor-side tool authorization — the agent loop passes the runtime's exact authorized tool set and the executor rejects out-of-set names (checked on the final name, after overrides and PreToolUse replacement); local tools fail closed without context.
- Framework-owned identity is frozen — tool-carried proposals are rebound to the executing runtime's session/turn/source before commit;
recursiveCalltakes a delta (no session/turn/origin override) and returns a result that cannot fireturn.completed; PostRuntime replacement keeps manifest identity; PreStateCommit replacement is payload-only; PreSchedule replacement is filter-only. - Function-runtime capability revocation — a timed-out or aborted handler loses every capability (store, pluginData, media, images, speech, gateway, logger, assetProgress, recursiveCall) the moment the race settles, so a detached handler cannot write after the session lock releases.
- Plugin-data trust gaps closed — the reserved
_namespace (_jobs,_logs) and core-plugin namespaces are rejected on every plugin-controlled write path (REST, commit handlers, function-runtime writer);world-initno longer seeds a new session by copying another session's player-writable plugin-data. - Prompt-injection surface reduction — player input never rides the system prompt (
{{ player.message }}interpolation removed from narrator bodies); inject blocks are interpolated exactly once so escaped{{ … }}in player/model data cannot expand; core-memory blocks are XML-escaped with validated labels; compacted history summaries are demoted from system messages to escaped data envelopes.
Changed
- Toolchain baseline — pnpm 11.9, TypeScript 7.0, Vite 8 (rolldown), Vitest 4.1, Electron 43, Hono 4.12, Undici 8.6; Radix umbrella replaced with per-package imports. Node runtime pins moved 22 → 26 (engines
>=26, mise, CI, Docker base image digest-pinned to26.5.0-alpine3.24). - Dialogue-mode parity for tracking runtimes — character-tracker, npc-graph extractor, and codex discover the narrative engine by capability instead of naming
narrator, so character state and the relationship graph update in chat mode too. start_sessionwith an empty plugin set returns 400 instead of silently activating every registered plugin (including community and mutually-exclusive ones) for the life of the session.- Comment/doc hygiene — audit/spec/PR tracer IDs swept out of code comments and test names; docs corrected where they had drifted from the implementation (transactions boundary, turnCount semantics, postHistory scoping, emit-event dedup).
Fixed
- Commit outcome is authoritative — synchronous plugin-rpc returns 500
turn-commit-failedon an uncommitted turn; background jobs settle failed and schedule no follower onto rolled-back state; nestedrecursiveCallproposals bubble to the top-level barrier instead of being dropped; a failed best-effort auto-snapshot no longer fails an already-committed turn. - Turn accounting — a scoped
retry_runtimeno longer double-advances the turn counter; a fork's inherited count is preserved; the counter advances only after commit settles. - SSE isolation and ordering — the event-bus subscription is established inside the session lock (a queued action can no longer receive the previous execution's events under its own envelope); post-commit fan-out is buffered until the enclosing transaction commits; stream writes go through one serial queue.
- Single-connection store writes serialize against open transactions — on SQLite/Memory backends an unrelated write issued while a transaction was open used to join it (and vanish on rollback); a per-connection write gate now queues outside writes, covering the vector and mirror-media stores sharing the connection.
- World-data write atomicity —
sync-dimensionsruns under one session lock + transaction;sync-datare-checks content hashes inside the transaction (409 on drift) and defers media deletion to after commit so an abort cannot leave committed references to deleted files; media materialization compensates partial failures. - NPC graph runs on a real turn clock — relationship edges are versioned (changed relations close the open version and open a new one with authoritative
validAt/invalidAt),firstSeen/lastSeenno longer derive from row counts, an unknown turn can no longer rewindlastSeenTurn, and superseded/duplicate open edges are healed and filtered from retrieval. - Event-runtime throttling restored — fan-out now feeds real per-runtime trigger history, so
maxTriggerCount/cooldownTurnsbind again; fan-out also respects the realpreGameCompletedset so a finished Pre-Game runtime is not resurrected. - Prompt budget enforcement for tool-using agents — pruning is tool-pair-aware (no orphaned
toolmessages), which lifts the exclusion that had exempted every main agent from the hard budget; working memory is capped at render time and its entry quota enforced on both write paths. - Read-your-own-write for plugin data —
get/listoverlay the calling plugin's own uncommitted proposals from the current tool loop. - npc-graph / retry / snapshot / localization follow-ups — adjacency index prunes closed edge ids; schema-declared runtimes get a correct completion preamble instead of being told to call a tool they don't have; the framework locale rule is scoped to natural-language content so machine values (enums, ids, topics) stay verbatim; localized manifests are verified field-by-field against the canonical manifest.
What's Changed
- Release v0.0.17 — trust-boundary & commit-integrity hardening, story-mode opening, Node 26 by @ackness in #24
Full Changelog: v0.0.16...v0.0.17
Covel v0.0.16
[0.0.16] - 2026-07-17
Performance, correctness, and reliability release. Context budgets are now derived from real model capabilities, per-turn history reads are bounded so long sessions stay fast, and a repository-wide six-dimension audit closed a batch of dead code, stale-doc, logging, and cross-backend-consistency gaps. New capability: deferred tool loading (tools.defer) lets runtimes with large tool whitelists keep the prompt small and let the model search tools on demand. Reliability fixes harden the shutdown drain, background-job sweep, event tail-frame delivery, and hosted-tier owner-token enforcement.
Added
- Deferred tool loading (
tools.defer) — a runtime may defer part or all of its tool whitelist; deferred tools stay registered and authorized but are withheld from the initial LLM tool list, and the framework injects asearch-toolstool that ranks the deferred pool with a zero-dependency BM25 (CJK-aware) and activates matches for the rest of the turn. Modeled on openai/codex'stool_search. See docs/reference/tools.md. - Capability-driven context budgets — compaction thresholds and prompt budgets are derived per model from detected token limits, plus per-model USD cost estimation on the debug Cost view.
- Configurable chat window cap (
ui.chatMessageWindow, default 2000) — live message appends are bounded in memory; dropped rows remain re-fetchable via the existing scroll-up path.
Changed
- Bounded per-turn history reads —
loadTurnSessionStateno longer full-reads the wholeturn_messageslog every turn. NewDataStoremethodslistUncompactedTurnMessages,listTurnMessagesAfter(forward keyset), andgetTurnMessageStats(grouped aggregate) keep reads bounded as a session grows; the semantic-memory recall ingestion walks the log by cursor instead of re-reading and re-sorting it. - IndexedDB
deleteSessioncascade now derives fromSESSION_SCOPED_TABLES, matching the SQL/Memory backends — a newly registered session table can no longer silently leak rows on the browser backend. - Removed two zero-caller extension seams — the
findToolClienttool-client resolver and the per-runtimegetConfigFn/ctx.config/{{ config.* }}chain (always empty in production) were deleted end to end. - Framework/plugin isolation and docs — removed a write-only
blockSchemaschain, retired an unread env-registry entry, closed authoring-contract drift, and synced all reference/guide docs to the current architecture.
Fixed
- SSRF self-tier core-provider path — the DNS-pinning guard rejected provider hosts on machines running a TUN proxy (Clash/mihomo/sing-box/Surge fake-IP maps every domain into a private/benchmark range) or a LAN Ollama endpoint. The
selftier now accepts any resolver answer on the user's own configuredbaseUrl(socket still pinned); pluginctx.http, IP-literal URLs, and hosted tiers stay strict. - Settings persistence on the REST-desktop tier — self-host setups without the Electron IPC bridge now persist settings to
~/.covel/settings.jsoninstead of silently falling back tolocalStorage; the settings REST backend surfaces failed writes instead of swallowing them. - Plugin-panel re-render storm — plugin-data store snapshots are scoped per
(pluginId, namespace), so an unrelated plugin's data write no longer re-renders every active panel. - Malformed JSON bodies now return
400 invalid_json_bodyinstead of a generic 500 across ~13 API routes (sharedreadJsonBodyhelper). - Shutdown drain flushes in-flight turns before closing the store; background-job sweep periodically reclaims orphaned pending jobs; event delivery detects lost tail frames via a transport heartbeat.
- Hosted-tier auth hardening — owner-token hash stripped from responses, event injection gated, owner guard enforced on the resume
DELETEsuspension route; desktop IPC sender-origin checks extended to the remaining channels. - Observability & logging — turn-executor and shutdown paths keep error stacks instead of
err.messageonly, the Data Explorer and model-db refresh catch paths log, and[component]prefixes are consistent.
What's Changed
- Post-v0.0.15 audit remediation (2 High, 2 Medium, Lows) by @ackness in #22
- v0.0.16 (WIP): capability-driven context budgets, cost estimation, legacy cleanup, docs sync by @ackness in #23
Full Changelog: v0.0.15...v0.0.16
Covel v0.0.15
[0.0.15] - 2026-07-16
Security and reliability hardening release. A full-repository audit and two follow-up remediation rounds closed the credential, trust-boundary, transaction-consistency, and event-delivery gaps that made the previous build unsafe to expose on a public, multi-user, or multi-instance deployment. Local single-user self/desktop play is unchanged by default; the new hosted-tier controls are opt-in via DEPLOYMENT_TIER.
Security
- Provider keys stay bound to their trusted origin. Server/platform API keys now flow to the gateway as
envApiKeys, separate from request-suppliedX-Provider-Keys; an env key attaches only when the resolvedbaseUrlorigin matches trusted config (llm.toml/ registered provider defaults). A request-scoped custom preset that redirects a provider to another origin receives no env key and no trusted default headers, closing the request-level key-exfiltration chain. - Per-request slot overlays are isolated. Custom presets register under a request-derived scoped id, so two concurrent requests using the same provider name with different base URLs can no longer share a registration — a victim's browser key can never be sent to an origin another in-flight request registered.
- Core provider HTTP resolves DNS through a pinning dispatcher.
postJson/getJson/postFormData(not just the pluginctx.httphelper) validate every A/AAAA answer at connect time and reject private/link-local/metadata addresses, closing the string-check-to-connect DNS-rebinding gap. - Hosted deployments gate every session and global route. A per-session owner token (minted at create, hash-persisted, returned once) guards all session-scoped routes; hosted (
demo/commercial) tiers additionally require an operator credential to create/list sessions and reach global admin/model/world routes. The server binds127.0.0.1by default (COVEL_BIND_HOSTopts into a public interface). A startup posture check fails closed on a hosted tier missing its media secret, CORS origin, or operator token.self/desktop/dev tiers remain a strict no-op. - Community plugin code is import-gated behind two-phase approval. A community (third-party) plugin's server code (
entry/ handler / hook / wire / runtime JS) is never imported until an explicitcovel:plugin-server-codegrant is approved for the session; the specific action then requires its own approval. Legacyhooks:handlers defer their import behind the same gate, the community entry store is default-deny (writes flow through proposals), and a timed-out agent guard has its write capabilities revoked so it cannot mutate a later turn. - Desktop hardening. Electron blocks main-frame navigation off the sidecar origin and validates the sender frame on secret-returning IPC; ZIP imports enforce entry-count / total-size / per-entry / ratio limits and no longer accept renderer-supplied arbitrary paths.
- Supply chain and logging. Pinned
only-allowand Docker image digests; Postgres binds host loopback; media and session tokens are redacted from error logs; GitHub Actions pinned to commit SHAs.
Added
- Operator Access settings pane to save / clear / show / hide the hosted operator token, reloading session and world data on change.
- Pull-request / push CI running install → lint →
lint:ci(typecheck + dependency hygiene) → tests → build → i18n/plugin checks;DEPLOYMENT_TIERis parsed against an explicit enum and fails closed to the most restrictive tier on an unknown value.
Changed
- Community plugins seeded by a world now require explicit approval on every tier. A third-party (
community-trust) plugin listed in a world manifest is no longer auto-activated and auto-loaded on first schedule — it is dropped from a session's active set at creation and must be explicitly enabled and approved (two phases: load the plugin's server code, then the specific action) before any of its code executes. This now applies on all deployment tiers, including localself/desktop, matching the documented community trust model (deferredimport()until the user approves). Bundled sample worlds are unaffected — their plugins arebuiltin/officialand still auto-load at boot. Migration: if you author a world that references a third-party community plugin, players must enable and approve it in-session; a previously-working self-tier world that relied on such a plugin auto-loading will start with it inactive until approved. - Turn side effects are strictly post-commit. Externally-visible "committed" SSE/trace broadcasts,
PostStateCommithooks,turn.completed, and post-turn memory/vector ingestion now run only after the commit transaction resolves, so a rolled-back batch emits no ghost events and never writes memory for a failed turn; a singletraceIdthreads the whole turn. - Snapshots are checkpoint-throttled and paginated (
COVEL_SNAPSHOT_INTERVAL_TURNS, default 5), with a store-level metadata projection + keyset pagination that never deserializes payloads. - Streaming text is decoupled from message history into an external store, so per-delta updates are O(1) and the chat grouping memo no longer rebuilds per token; the IDB backend and debug route move out of the main web chunk.
Fixed
- EventBus delivery is bounded and gap-aware. A fixed-capacity ring buffer with LRU/idle eviction (active subscribers pinned),
${epoch}:${seq}non-reusable ids, replay-gap detection that drives asystem.reset→ client re-hydrate, a per-session ordered cross-pod outbox over PGLISTEN/NOTIFY, and leak-free SSE cleanup with a per-session connection cap and bounded write backpressure. - The compactor cascades past the first compaction — the window starts after the last boundary and tokens are estimated from the effective prompt view, so long sessions keep compacting instead of eventually overflowing the model context.
- Graceful shutdown drains watchers, event bus, store, and PG lock pool; background jobs get a startup recovery sweep and a concurrency cap; the PG advisory-lock deadline now covers pool checkout.
- Turn hot-path reads reduced (working memory deduped 3→1 per turn; touched-only transaction snapshots), IDB keyset pagination with an atomic v11→v12 metadata migration, and session-owner coverage extended to
approvals/media/ world-sync /ui-specs.
What's Changed
Full Changelog: v0.0.14...v0.0.15
Covel v0.0.14
[0.0.14] - 2026-07-13
The agent-core release. Server-side plugin registration converges into a single entry factory (covel.registerTool / on / registerRpc / registerWires), players can steer or abort a turn while the LLM is still streaming, and a full-codebase audit hardened the turn/snapshot/fork consistency boundaries: proposal commits are now atomic under the session lock, auto snapshots capture post-commit state, forks restore session lifecycle from the snapshot itself, and outbound plugin fetches pin DNS resolution against rebinding.
Added
- Unified PluginAPI (
entry). The four scattered server-side registration surfaces (local tools, hooks, RPC actions, media wires) converge into one factory declared via the newentryPLUGIN.md field —export default function (covel) { ... }withcovel.registerTool / on / registerRpc / registerWiresandcovel.toolkit. Agent runtimes declare LLM tool visibility with the newtools.pluginname list. All 8 bundled plugins with server-side registrations migrated; legacy fields keep working for one release cycle with a per-plugin deprecation warning, and community entries still defer to activation (trust gating unchanged). - Mid-turn player steering + abort.
POST /api/sessions/:id/steermerges queued player interjections into story runtimes' next LLM step (persisted to history);POST /api/sessions/:id/abortcuts the in-flight LLM stream immediately, is non-retriable, bypasses the streaming salvage path (no partial narrative is ever committed), and surfaces asabortReason: "aborted-by-player"on the existingexecution.completedevent. Both return 409 when no turn is active. The web composer stays usable during a turn — submit steers, a stop button aborts. - Snapshot payload schemaVersion 2. Snapshots now capture session lifecycle and runtime configuration (
status,turnCount,preGameCompleted,locale,activePlugins,presetId,runtimeModelOverrides), so forking an old snapshot restores the session as it was at capture time instead of inheriting the parent's current scheduling state. V1 snapshots remain readable and fork with the previous parent-fallback behaviour.
Changed
- Agent loop layering. All how-to-run derivation (tool defs, schema gate, model override chain, streaming gate, step/retry budgets,
requireToolUse,acceptsSteering) converges intobuildAgentLoopPolicy(); the loop's single delta outlet is extracted intocreateDeltaForwarder. The loop body is control flow only and independently instantiable — pinned by a direct loop-core test suite (trace/delta sequences, runtime-done stripping, maxSteps, streaming gates, steer/abort/salvage-bypass).
Fixed
- Turn commits are atomic under the session lock. Player-input persistence, turn execution, proposal commit,
turnCountsync and the auto snapshot now run inside a singlesessionLock.withLockscope, so an overlapping same-session action can no longer read pre-commit state (stale-read / lost-update). A bounded PG advisory-lock acquire timeout surfaces as a coded, retryable 503 instead of a generic 500. - Auto snapshots capture post-commit state. Snapshot capture moved out of the turn finalizer (which ran before proposals committed) into the server pipeline after commit — forked sessions no longer inherit a turn's dialogue while missing that same turn's state, character and plugin-data writes.
- Manual snapshots and forks share the session lock, so payload reads can no longer observe a mixed point-in-time while a turn is committing.
- Plugin uninstall honours the install privilege boundary.
DELETE /api/plugins/:id(which recursively deletes the plugin directory) now sits behind the same production opt-in / desktop bearer-token guard as install, instead of being callable by anyone who can reach the API. - Outbound plugin fetches pin DNS resolution. SSRF validation previously only checked the literal hostname; a public name resolving to a private/metadata address passed.
fetchWithRetrynow resolves and policy-checks addresses per attempt and pins the connection to the validated address via an undici dispatcher (DNS-rebinding defence). - PR #18 review hardening. Plugin tool-access allowlists only grant ownership after successful registration (tool-name collisions are rejected instead of leaving a dangling grant); the active turn registers inside the session lock so a queued action can no longer steal the steer/abort target; the web client clears its delta buffer and streaming placeholder on player abort (no ghost message).
What's Changed
- refactor(core): agent-core & plugin registration improvement roadmap by @ackness in #18
- fix: resolve 2026-07-11 full-codebase audit findings (A-01..A-06) by @ackness in #19
- Release v0.0.14 by @ackness in #20
Full Changelog: v0.0.13...v0.0.14
Covel v0.0.13
[0.0.13] - 2026-07-10
The Windows release. The dev environment (spawn shims, plugin loading, supervised pnpm dev) now works on Windows, and CI builds Windows installers alongside the macOS bundles on every tag. Public web-only (browser IndexedDB) deployments are hardened: the shared session listing is hidden and the Render blueprint pins the memory backend explicitly.
Added
- Windows desktop CI.
release.ymlgains abuild-electron-winjob (windows-latest): full desktop build with Electron-ABI native rebuild, staged-server smoke under Electron's Node mode,electron-builder --win(NSIS + portable, x64, unsigned until a cert is configured), and.exeartefacts attached to GitHub Releases alongside the macOS bundles. Windows targets are x64-only: the staged server ships a better-sqlite3 rebuilt for the build host's arch, so an arm64 installer from an x64 runner would carry an addon the arm64 sidecar cannot load — Windows-on-ARM runs the x64 installer via emulation.
Fixed
- Windows dev environment. Dev scripts spawn through
cross-spawn(resolvespnpm.cmd-style shims with correct cmd.exe quoting); plugin/runtime dynamic imports of absolute paths go throughpathToFileURLso plugin loading works on Windows;pnpm devlaunches web + server directly with supervised teardown;--env-file-if-existstolerates a missing.env(raises the minimum Node to 22.9). - Desktop staging smoke now exercises the Electron ABI. The native rebuild runs before the smoke test and the staged server boots under
ELECTRON_RUN_AS_NODE; a missing Electron binary fails the build instead of silently downgrading the check (COVEL_SMOKE_HOST_NODE=1opts into the weaker host-Node smoke). - Electron runtime binary is materialised explicitly. electron@42 removed its postinstall hook, so
pnpm installno longer downloads the binary intonode_modules/electron/dist(whitelisting inonlyBuiltDependencieshas nothing to run). The desktop build and dev shell now runensure-electron.mjsfirst, which invokes electron'sinstall.jswhen the binary is missing — without this, every desktop CI build died at the staging smoke on both macOS and Windows. - Public web-only hosting hardening. On memory-backend production deploys without debug routes,
GET /api/sessionreturns an empty list — server-side sessions there are transient sync copies of browser-IndexedDB data, and the only callers of a shared listing would be other players.render.yamlpinsSTORE_BACKEND=memoryexplicitly (the sqlite default would pool every player's data in one shared DB), adds a health check, and disables the debug page; the Docker image regains the workspace manifests (settings,plugin-handlers-utils, and four plugins) missing from its dependency-install stage.
What's Changed
- feat: web-only (IndexedDB) public hosting hardening + Render blueprint fixes by @ackness in #13
- [codex] Fix Windows dev environment by @ackness in #14
- Release v0.0.13 by @ackness in #15
- fix: v0.0.13 CI — Electron binary download (electron@42 dropped postinstall), actions/cache v5 by @ackness in #16
- fix: Windows portable exe overwrote the NSIS installer (artifact name collision) by @ackness in #17
Full Changelog: v0.0.12...v0.0.13
Covel v0.0.12
[0.0.12] - 2026-07-06
The media-pipeline release. Speech joins images as a first-class framework primitive: ctx.speech gives function runtimes a unified TTS/STT surface with the same wire-selection, dedupe, and MediaStore-persistence guarantees as ctx.images, and every media modality (image / speech / transcription) now routes through pluggable per-modality wire registries. Plugins can ship vendor wires declaratively via the new PLUGIN.md wires field — supporting a new provider no longer requires touching bundled code.
Added
ctx.speech— unified TTS/STT for function runtimes.generate()synthesizes speech, dedupes onsha256(presetId, text, voice, format), and persists through MediaStore;transcribe()accepts aMediaRefor raw bytes and returns plain text. Assembled under the same conditions asctx.images; the plugin gateway and trace layer gainsynthesizeSpeech/transcribeAudiopassthroughs.- Plugin-declared media wires (PLUGIN.md
wiresfield). Plugins register image / speech / transcription wires declaratively; ids are namespaced<pluginId>/<wireId>and loading is trust-gated. Slots select wires viaproviderRequestMetadata.imageWire|speechWire|transcriptionWireinllm.toml. - Manifest diagnostics at bootstrap. A declared capability tag sitting one edit away from a framework-known one now warns (a misspelled tag was previously just silently never discovered), as does a plugin whose frontmatter name root differs from its directory name (a mismatch silently denies the plugin its own local tools).
Changed
- Speech/transcription move from provider adapters to pluggable wires (builtin
openai-speech/openai-transcription), completing the per-modality wire registry started with images in 0.0.11. - Plugin handlers read plugin data exclusively through
ctx.pluginData— no more store-shape sniffing; trusted and community runtimes go through the identical scoped path. Emptyrelations: {}frontmatter dropped across all bundled plugins; runtime imports moved from devDependencies to dependencies where misfiled; world-init tests relocated totests/, guide + pregame gain handler tests. - Web session view slimmed.
game-viewreads session state from the session store instead of ~20 pass-through props; a shareduseSettingsDialoghook replaces duplicated dialog state; UI primitives trimmed to the variants actually used.
Fixed
video_generationmodels derive a video output modality instead of being misclassified as text.- Commit handlers reject
state.patch/event.emitproposals with an empty table/field/topic instead of silently landing writes underdefault/unknown(JS plugins bypass the type layer, so the gate must live at commit time). POST /api/media(10 req/min) and sessionplugin-rpc(30 req/min) are rate-limited per IP — runtime-mode RPC dispatch runs a full LLM turn, the same cost class asPOST /api/actions.
What's Changed
Full Changelog: v0.0.11...v0.0.12
Covel v0.0.11
[0.0.11] - 2026-07-04
The visual-novel release. A full-screen GalGame stage mode — scene backdrops, character sprites, typewriter dialog, choice overlays — built on three new framework layers: a unified event-emission layer (events contracts + emit-event), a first-class image-generation pipeline (ctx.images + pluggable wires), and the scene-stage plugin that resolves narrative locations into stage art and generates missing backdrops on demand, mid-session. Haruka Academy ships the full asset set and plays as a visual novel out of the box.
Added
- Stage mode (
viewMode: "stage"). A full-screen visual-novel view for Playing turns: crossfading scene backdrop, bottom-anchored character sprites with active-speaker highlight, a delta-driven typewriter dialog with paragraph pauses, a classic centered choice overlay (interaction choices + scene-prompts phrases + free input), a HUD (scene badge, history drawer, auto-play, immersive toggle), and a player-clean history drawer reusingChatMessages. Worlds pick their default via the newworld.yamldefaultViewMode: stage | parsed; players can switch any time. Sprite stationing is sticky — salience decides who is on stage and who is highlighted, never where anyone stands — and sprites render inside equal-width lanes, so occlusion is impossible by construction. - Unified event layer. Plugin runtimes declare event contracts in frontmatter (
events: [{topic, schema, description, advertise}]); emitting agents declareadvertiseEvents: trueand call the new builtinemit-eventtool, whose payloads are schema-validated against the declaring plugin's contract with per-turn topic dedupe. A per-session event directory aggregates active plugins' contracts into the prompt (<available-events>), and deferred event followers are scheduled once per turn on the main loop.narrator/chat-mode-narratoremitscene.setas the reference implementation. - Framework image pipeline (
ctx.images/gateway.generateImage). One primitive for plugin image generation: wire selection (builtinopenai-imagesanddashscope-wansubmit+poll, extensible viaregisterImageWire()), MediaStore persistence, and promptHash dedupe all handled by the framework — handlers never touch bytes or provider credentials. scene-stageplugin. Resolvesscene.set(location + day/night) against the world scene registry — exact, then fuzzy, then session-generated matches — writesstage/currentfor the stage backdrop, and queues background generation for unmatched locations behind player-tunable gates (autoGenerateScenes,maxGeneratedScenes). Night variants lazy-generate on first use and reuse the day image's visual hint.- Haruka Academy visual-novel asset set. Ten scene backdrops (5 locations × day/night) plus regenerated transparent-background portraits, imported via new
worldDatamedia sources (to: media+indexTo+key: filename) and thescenes.registry.json/presence.jsonregistries;scripts/generate-scenes.mjs+scripts/emit-scenes.mjsregenerate them fromscenes.jsonspecs. requireToolUsemanifest gate. An agent runtime whose whole job is a tool call gets one corrective retry (locale-aware message) when it drifts into prose without calling anything;scene-promptsuses it.
Changed
- Media store simplified. The unused S3 backend and its SQLite/PG metadata adapters are gone; media stores gain a shared
listByMetadatafilter. The imperativebeginTx/commitTx/rollbackTxAPI and the emptyplugin_configstable are removed (all writes go throughwithTransaction; existing databases are unaffected). - ai-provider slimmed. Dead config loader, token estimator, and protocol-registry indirection removed; model capability resolution unchanged.
- Desktop auto-updater removed — releases install via the dmg; update checks return in a later cycle.
- README rewritten around stage mode with a fresh 6×-speed demo gif and play-mode stills; the
create-world/create-pluginskills now document the VN feature set (defaultViewMode, asset pipeline, events contracts,ctx.images).
Fixed
plugin-data.changedSSE now fires after transaction commit (and never on rollback), so panels re-render exactly when data is durable.- worldData sources targeting a plugin the player deselected are skipped with a warning instead of failing session creation with a 500;
indexTotargets skip only the index writes. - Stage polish batch: sprites no longer drift when the speaker or scene changes (sticky stations + lane geometry), stale
getSessionresponses can't corrupt state after a session switch, the typewriter survives same-text turns and stream-end races, artless speakers keep a name-card on stage, execution errors surface on stage with retry, and choice overlays no longer cover sprites. - A stuck "generating…" backdrop retries when the scene re-emits after a failed generation; DashScope image tasks that are CANCELED/UNKNOWN fail fast instead of polling to timeout.
- Duplicate deferred event followers are deduped per turn; enum values render in the event catalog; image cache hits stamp per-call metadata.
What's Changed
Full Changelog: v0.0.10...v0.0.11
Covel v0.0.10
[0.0.10] - 2026-06-29
A plugin-configuration pass: worlds can now preset any plugin's player-tunable settings and declare genre-specific memory dimensions, the player's per-plugin settings finally reach the scheduled turn loop, and the plugin config layer is consolidated onto a single declaration with server-enforced constraints. Behavior-unchanged for worlds and plugins that don't adopt the new fields.
Added
- World-preset plugin settings (
pluginSettings). Aworld.yamlcan now declare per-plugin defaults for any plugin'suserSettings, keyedpluginId → settingKey → value. They form the middle layer of a three-tier resolution chain — player override → world default → manifest default — merged at the turn boundary intoTurnInput.userSettingsand consumed by agent{{ userSettings.* }}templates, guards, and hooks. A dialogue world can ship "70% dialogue, short replies" as its default while players keep the freedom to override. Seedocs/reference/world-data.md. - World-declared core-memory blocks (
memoryBlocks). A world can add genre-specific memory dimensions (a detective world'sclues/suspects, a business sim'sdeals/rivals) without forking a plugin — same shape as a plugin'smemoryBlocks. The memory system resolves the block schema per session, merging a session's world blocks onto the global plugin blocks (base wins on label collision; the world only adds new labels), so the extra dimensions render and extract only for the worlds that declare them. Closes the long-documented "(or by a world package)" gap. integer/slideruser-setting types, and a consolidated design-principles page (docs/architecture/design-principles.md) that roots the framework's "kernel provides primitives, plugins carry gameplay" contract, the agent / function / composition writing styles, and the plug-vs-appliance test.- Character portraits + presence wiring for the flagship worlds. Both worlds ship a curated portrait set (mistport fog-noir, haruka GalGame-style), generated via
scripts/generate-portraits.mjs+scripts/emit-presence.mjsand wired to thecharacter-presenceplugin throughmedia+presenceworld-data sources (content-addressed by sha256) — portraits render in the right-panel character card and as dialogue sprites. The two worlds also gained tuned plugin defaults (haruka:activeSpeakerCount,npc-graph; mistport: acost-gatebudget guardrail). Prompt spec inworlds/PORTRAITS.md; shipping contract indocs/reference/world-data.md. - Hot-reload
llm.tomlwithout restarting. Settings → LLM gains a Reload config button (POST /api/llm-config/reload) that re-readsllm.tomland applies it to the live gateway in place — the provider / preset / slot registries are reconfigured rather than rebuilt, so the gateway and every adapter pick up added/removed slots without a restart. A malformedllm.toml(e.g. a key with=and no value) no longer fails silently: the file still falls back to the built-in default, but the parse error is now surfaced via the newerrorfield onGET /api/llm-configand a red banner in Settings — instead of every plugin's slot just showing as "missing" with no explanation. On desktop the reload endpoint is gated by the same one-time REST token as other config writes. Seedocs/guide/desktop-config.md. - Character portraits show as avatar badges in the character list. Each row in the char-creator character panel now leads with the character's portrait as a small avatar badge, via a new generic
CharacterAvatarjson-render component. It reads presence records from a source plugin/namespace named in the spec (char-creator wires it tocharacter-presence/presence) — keeping the framework component free of any hard-coded plugin id, mirroring howImageGallerytakes itspluginIdby prop. Matching tolerates the<sessionId>-<characterId>ids the character list uses against the barecharacterIdthe presence records key on. Characters without a portrait — and entire worlds without presence data — render exactly as before: the badge simply doesn't appear. - Worlds can declare character attributes with i18n labels (
characterAttributes).AttributeDefinition.name/descriptionnow accept anI18nText(plain string or{ "zh-CN": …, "en-US": … }record), and aworld.yamlcan ship its owncharacterAttributesarray.world-init's guard writes it verbatim as the session'scharacter-attributesschema — and does so authoritatively, ahead of cross-session reuse, so editing the declaration takes effect on new sessions instead of inheriting an older session's stale/derived schema. The right-panel character card resolves each label to the UI locale (resolveI18nText), and the prompt-injected<world-schema>is deep-resolved to one language too (resolveI18nDeep), so a custom field like Haruka'saffectionshows as 好感度 / Affection instead of a rawaffectionkey under "其他". Both flagship worlds now declare their cast fields (mistport: faction / role / tideSense / fogRot / …; haruka: club / affection / trust / …) with bilingual labels. Worlds that declare nothing are unchanged — the guard still derives generic attributes from dimensions, then falls back to the LLM. Seedocs/reference/world-data.mdanddocs/reference/plugins.md. - Click-to-enlarge for portraits, avatars, and generated images. The image-generation gallery's preview was extracted into a generic
MediaPreviewDialog. The json-renderImagecomponent gains azoomprop, the character-portrait gallery and the character-list avatar badges opt into it, and the image gallery now delegates to the same dialog — one enlarge + download surface everywhere. The avatar badge stops its click from also toggling the surrounding collapsible card. TheImagecomponent also gains aframedprop (rounded bordered card + hover) so the portrait gallery's thumbnails match the image-generation gallery's cards — the two read as one family. The enlarge view is a tight lightbox that hugs the image (w-auto) rather than a wide dialog with the image floating in empty space. branch-replyreply-swipe revived as a real feature. The GalGame "swipe between AI replies" plugin shipped active in both default worlds but was a permanently-invisible dead feature — a bootstrap deadlock where the only candidate-writer lived behind a button inside a block that never rendered. It is now a working auto-seeded swipe: after each story turn it captures the narrator's reply as the original (engine-agnostic — discovered via the narrative-output contract, no hardcodednarrator/chat-mode-narratorid), and a Regenerate control produces genuine LLM-generated alternative phrasings (fast slot, session locale) the player can compare and Accept to rewrite prompt history. The original is never re-rendered as a card, so a reply never appears twice; the old English deterministic filler is gone.
Changed
- One plugin-config declaration.
userSettingsis now the single source for player-tunable plugin settings; the dead, never-consumedconfigmanifest field is removed. A stragglerPLUGIN.mdthat still declaresconfig:is stripped with a deprecation warning rather than failing to load. ThePluginUserSettingSpectype is single-sourced in@covel/shared(two divergent copies removed), and its declared constraints (min/max/options/ type) are now enforced — client-side in the SettingsStore and server-side inresolveUserSettings, so an out-of-range world or header value degrades to the manifest default instead of reaching a guard or hook. - Plugin selection consolidated into
pluginPolicy. The deprecated top-levelrequiredPlugins/recommendedPlugins/excludedPluginsnow fold (de-duplicated) intopluginPolicyat world-load time, soWorldRecord.metadatacarries plugin selection in one place. The top-level fields remain valid inworld.yamlfor back-compat. Per-turn world reads are served from a short-TTL per-worldIdcache. - Sample worlds curated to two deeply-built flagships. The bundled set is now Mistport (traditional-story / dark-fantasy mystery) and Haruka Academy (dialogue / GalGame-style), each greatly expanded and doubling as the canonical example of the new fields. Mistport gains a seven-character seed cast, a fourth faction, bilingual lore, and
clues/relics/tidesmemory blocks; Haruka grows to eight characters across five routes withrelationships/promises/rumors/festivalmemory. The redundantcloudmereandneonridgestory worlds move toworlds/_archive/(kept for reference, not loaded).
Fixed
- Player per-plugin settings now actually reach the main turn loop.
POST /api/actions(and the resume / plugin-rpc paths) never decodedX-Plugin-User-Settings, so in the scheduled loop every runtime'suserSettingscollapsed to manifest defaults — a player's UI-tuned values (chat-mode-narrator'sdialogueRatio, cost-gate's token caps) were silently ignored while only the manual plugin-rpc path honored them. The main route now decodes the header and merges it with the world defaults. - The settings header no longer masks world defaults.
X-Plugin-User-Settingswas built from every registered plugin setting (store.get()returns the manifest default for untouched keys), so it always carried a full bucket of defaults that, sent as "player overrides," overrode the new worldpluginSettingslayer. It now carries only the keys a player explicitly set (store.has()). - Archived bundled worlds no longer linger in existing users' databases.
seedWorldsonly ever upserts, so a sample world removed from...
Covel v0.0.9
[0.0.9] - 2026-06-28
A playability-loop pass — function runtimes become visible in the trace timeline, stale suspensions expire, and player-input narrative localizes by session locale — plus a follow-up engineering batch: multi-node S3 media metadata on Postgres, plugin-utils provider-call tracing, a /debug cost panel, and community plugin uninstall/revoke. The default world and bundled plugins are behavior-unchanged.
Added
- Function-runtime trace coverage (A2-P1-5). Function runtimes were near-invisible in
/debug— nothing betweenruntime.started/completed, and zero rows forctx.gatewayprovider calls. They now emitfunction.executing/function.completed(handler boundary) and, via awithGatewayTracewrapper applied at execution time,gateway.calling/gateway.responded/gateway.failedforgenerateText/generateObject— all persisted totrace_eventsand broadcast, so a function runtime's LLM usage is as visible as an agent runtime's. The five events join the single-sourceCovelEventunion (compile-time exhaustive overCOVEL_EVENT_META+ the frontend switch). A missing handler now emits a terminalruntime.failedinstead of leaving a hangingruntime.started. - Plugin-utils provider-call trace. Closes the A2-P1-5 follow-up: image plugins (and any plugin owning its wire) call providers via
ctx.utils.fetchWithRetry, which awithUtilsTracewrapper now traces asutils.fetch.calling/responded/failed(trace-only,forwardToActionStream:false) at both the function-runtime and agent-guard injection sites. PII-safe — payloads carry only host / method / status / durationMs, never the full URL, query, or API key. - Multi-node S3 media metadata on Postgres.
createPgS3MetadataAdapter(+…FromClient) implements theS3MediaMetadataAdapterinterface over the sharedmedia_assets/media_refsPG tables, so S3-backed media survives restarts and is shared across nodes — the SQLite adapter only covered a single node. Mirrors the SQLite adapter 1:1 and passes the same media-store contract suite against a real Postgres. /debugtoken cost panel. A new Cost view aggregatesusagefromllm.responded/gateway.respondedtrace events by runtime, by turn, and session-total (zero-dependency CSS bars), aggregating generically by event type + runtime id. USD cost is a pending follow-up —llm.respondedpayloads don't yet carry the model id needed forusage × pricing.- Community plugin uninstall + approval revoke.
DELETE /api/plugins/:idremoves a third-party plugin from the user plugins dir (rejects builtin ids, returnsrestartRequired:true);DELETE /api/sessions/:id/approvals[?pluginId=]revokes cached approval grants via the newgate.revoke. The Settings → Packages pane lists installed third-party plugins with an uninstall button. Closes the only missing stage of the community discover→approve→import→active lifecycle (the import stage was already live; docs were stale).
Changed
submit-formis now locale-aware. Theconfirmation{{confirmed}}value (确认/取消) and the fallback-narrative prefixes ([玩家输入]/[玩家选择]/[玩家确认]/[玩家取消]) were hardcoded Chinese; they now resolve by session locale (threaded in via a newRpcHandlerContext.locale, sourced fromsession.localein the plugin-rpc dispatch — no executor change).en-USyieldsConfirm/Cancel+[Player input]/…; unknown locales fall back to zh-CN, byte-for-byte identical to the previous output.submit-form'sSubmission.typenow references the single-sourceInteractionTypeunion.- Dependencies bumped to latest stable. All workspace dependencies updated to their latest stable under the existing
minimumReleaseAge: 10080(1-week) gate — including the major bumps@hono/node-server1→2,electron41→42,@types/node25→26,@json-render/*0.18→0.19, andzod4.3→4.4. One breaking change handled: zod 4.4 treats a barez.unknown()inside a.strict()object as a required key, so the plugin user-settingdefaultfield gained an explicit.optional(). Verified across the full workspace lint + test (incl. real Postgres), server boot, API e2e, and the desktop (electron 42) typecheck. - Dependency-hygiene gate + scaffold alignment. Added
knipas a CIdeps:checkgate (catches unused/missing workspace deps; understands JSDoc type imports + test files, so type-only/test-only deps aren't false-flagged), cleaned 12 stale plugin devDeps, and aligned the@covel/createscaffold to emit correctly-layered minimal deps per template. The authoring guide gains a dependency-layering + extraction-threshold section.
Fixed
- Function runtimes no longer leave a hanging
runtime.startedwhen the handler is missing or throws — a terminalruntime.failed(andfunction.completed{status:failed}) is emitted on every exit path. /api/ui-specsno longer 500s when one plugin's runtime fails to load. A single bad runtime (corrupt UI spec, missing handler dep) used to take down the whole response on every world open; it is now logged and skipped while healthy plugins still resolve.app.onErroradditionally logs request context (method + full URL) for every 500.- Desktop packaging now stages plugin-only workspace deps. 5 bundled function plugins depend on
@covel/plugin-handlers-utils(used by plugins, never by@covel/server), whichpnpm deploy --filter @covel/serverleft out — so every packaged build shipped them broken withERR_MODULE_NOT_FOUND. The desktop build now scans each plugin's@covel/*runtime deps and stages any the server deploy missed; the post-staging smoke test fails on plugin-load errors instead of swallowing them.
中文(备份翻译)
一次可玩性闭环整理(function runtime 在 trace 时间线可见、陈旧挂起项过期、玩家输入叙事按会话 locale 本地化),外加一批工程收尾:Postgres 上的多节点 S3 媒体元数据、plugin-utils provider 调用 trace、/debug 成本面板、社区插件卸载/撤销。默认世界与内置插件行为不变。
Added
- Function-runtime trace 覆盖(A2-P1-5):function runtime 此前在
/debug几乎不可见——runtime.started/completed之间空白,ctx.gatewayprovider 调用零记录。现在发射function.executing/function.completed(handler 边界),并经执行期withGatewayTrace包裹对generateText/generateObject发gateway.calling/gateway.responded/gateway.failed——全部持久化到trace_events并广播,使 function runtime 的 LLM 用量与 agent runtime 同等可见。五个事件纳入单一真相CovelEventunion(对COVEL_EVENT_META与前端 switch 编译期穷尽)。缺失 handler 现在发终结runtime.failed,不再留悬空runtime.started。 - Plugin-utils provider 调用 trace:收尾 A2-P1-5 follow-up。图像插件(及任何自带 wire 的插件)经
ctx.utils.fetchWithRetry调 provider,现由withUtilsTrace包裹器在 function-runtime 与 agent-guard 注入处 trace 为utils.fetch.calling/responded/failed(trace-only,forwardToActionStream:false)。PII 安全——负载仅含 host / method / status / durationMs,绝不含完整 URL、query、api key。 - Postgres 上的多节点 S3 媒体元数据:
createPgS3MetadataAdapter(+…FromClient)在共享media_assets/media_refsPG 表上实现S3MediaMetadataAdapter接口,使 S3 媒体跨重启存活、跨节点共享——此前 SQLite 适配器只覆盖单节点。1:1 镜像 SQLite 版,并对真实 Postgres 通过同一 media-store 契约套件。 /debugtoken 成本面板:新增 Cost 视图,按 runtime / turn / 会话总计聚合llm.responded/gateway.responded的usage(零依赖 CSS 条形),按事件类型 + runtime id 通用聚合。美元成本为待办 follow-up——llm.responded负载尚未携带usage × pricing所需的 model id。- 社区插件卸载 + 审批撤销:
DELETE /api/plugins/:id从用户插件目录删除第三方插件(拒绝内置 id,返回restartRequired:true);DELETE /api/sessions/:id/approvals[?pluginId=]经新增gate.revoke撤销缓存授权。Settings → Packages 面板列出已安装第三方插件并提供卸载按钮。收尾社区 discover→approve→import→active 生命周期唯一缺失的阶段(import 阶段早已实现,文档此前陈旧)。
Changed
submit-form现按 locale 本地化:confirmation的{{confirmed}}取值(确认/取消)与回退叙事前缀([玩家输入]/[玩家选择]/[玩家确认]/[玩家取消])此前写死中文;现按会话 locale 解析(经新增的RpcHandlerContext.locale注入,来源是 plugin-rpc dispatch 的session.locale,无需改 executor)。en-US产出Confirm/Cancel+[Player input]/…;未知 locale 回落 zh-CN,与改前输出逐字一致。submit-form的Submission.type现引用单一真相InteractionTypeunion。- 依赖升级到最新 stable:全部 workspace 依赖在既有
minimumReleaseAge: 10080(1 周)门控下升到最新 stable——含跨 major 的@hono/node-server1→2、electron41→42、@types/node25→26、@json-render/*0.18→0.19、zod4.3→4.4。处理了一处 breaking:zod 4.4 把.strict()object 内裸z.unknown()当作必填 key,故插件 user-setting 的default字段显式加.optional()。已通过全 workspace lint + test(含真实 Postgres)、server 启动、API e2e 与 desktop(electron 42)typecheck 验证。 - 依赖卫生 gate + 脚手架对齐:新增
knip作为 CIdeps:check关卡(拦截 unused/missing workspace 依赖;识别 JSDoc 类型引用与测试文件,不误杀 type-only/test-only 依赖),清理 12 个 stale 插件 devDep,并让@covel/create脚手架按 template 生成正确分层的最小依赖。插件指南新增"依赖分层与复用规范"章节。
Fixed
- function runtime 在 handler 缺失或抛错时不再留悬空
runtime.started——每条退出路径都发终结runtime.failed(及function.completed{status:failed})。 /api/ui-specs不再因单个插件 runtime 加载失败而 500:一个坏 runtime(损坏的 UI spec、缺失的 handler 依赖)此前会在每次打开世界时拖垮整个响应;现在记录并跳过,健康插件照常返回。app.onError还会为每个 500 记录请求上下文(method + 完整 URL)。- 桌面打包现在 stage 插件专属的 workspace 依赖:5 个内置 function 插件依赖
@covel/plugin-handlers-utils(只被插件用、不被@covel/server用),pnpm deploy --filter @covel/server把它漏掉了——导致每个打包版本都带着ERR_MODULE_NOT_FOUND的坏插件。桌面构建现在扫描每个插件的@covel/*运行时依赖并补 stage server deploy 漏掉的;打包后的 smoke test 现在会因插件加载失败而失败,不再静默吞掉。
What's Changed
Full Changelog: v0.0.8...v0.0.9
Covel v0.0.8
[0.0.8] - 2026-06-28
Eighth public release. Finishes the v0.0.7 architecture pass — clears the remaining audit debt, makes the schema and transaction layers single-source-of-truth, and ships semantic (vector) memory recall, all verified end-to-end against a real pgvector Postgres. New game genres can declare their own memory blocks, and a session can switch storage backends without any behaviour change. The default world and bundled plugins are behavior-unchanged.
Added
- Semantic (vector) memory recall. The memory tier now embeds turn messages (recall) and lorebook + character records (archival) on write — a post-turn, best-effort sweep that never blocks the turn — and serves KNN recall over them, falling back to keyword search per-session when no embedding model is locked. Embed-on-write ingestion is incremental (a persisted cursor + content hashes) and self-heals deleted records; backfill of existing sessions runs in the same path. Wired through a single injected
embedseam so@covel/memorystill depends only onshared+store. Verified end-to-end on real pgvector Postgres. - A real-pgvector
vector-storecontract branch (PgStore) — the production vector path (upsertVector/searchVectors/deleteVectorsvia thevectortype + pgvector operators) now has automated coverage it previously lacked, plus a memory-vector × PgStore integration test. @covel/settingspackage — the unifiedSettingsStore+ its platform backends (browser localStorage, Electron-IPC json-file) split out of@covel/shared, so pure-types consumers no longer pull in browser/Electron code.
Changed
- Store schema is now single-source-of-truth. The boot DDL is derived at module load from the Drizzle schema (
buildCreateTablesSql), so a column or index is declared once and the executed DDL can never drift from it; only the bits Drizzle can't model (triggers, idempotent column migrations) stay hand-written. Identifiers are quoted uniformly. - Transactions are scoped to a single commit. Production turn-commit / session-create / world-data-sync / snapshot-fork callers moved from the global imperative
beginTx/commitTxshim towithTransaction(fn)(real pooled-Postgres transactions; nested calls on serial backends are rejected, not deadlocked), removing the all-database serialization window. runtime/src's 56 flat files are organized into 13 sub-domain directories (trigger / schedule / agent-loop / commit / session / trace / snapshot / rpc / resume / llm / retry / function-runtime); the public barrel is byte-identical.- Cleared remaining v0.0.7 audit debt: priority-band literals collapse to
isPreGamePriority()/NARRATOR_PRIORITY; the two frontend SSE channels share one event reducer;CompactorLLMAdapter+MemoryLLMAdapterconverge into a sharedSimpleCompletionAdapter; the Anthropic cache-breakpoint cap is one shared constant. - Removed the duplicate turn entrypoint.
POST /api/sessions/:id/turnwas a mounted-but-frontend-unreachable second turn pipeline;/api/actionsis now the single turn-execution route. Its tests migrated to/api/actions. - Bumped all monorepo package versions
0.0.7→0.0.8.
Fixed
- Cross-backend vector parity. A real-PG vector contract immediately surfaced two PgStore-only bugs the Memory/SQLite backends hid: a
freshSchemastore kept stale rows because the dynamicvec_mem_*tables were not dropped, and the upsert/delete paths were untested. A fresh store now starts empty on every backend — the same data, the same API, switch backend freely. - memory recall data-loss / staleness. The recall cursor no longer advances past a message that got an empty embedding (which would drop it from recall forever); short vector results during backfill are topped up with keyword hits so the most recent messages aren't missed; deleted lorebook/character vectors are purged instead of returned as stale hits.
- Snapshot-fork orphaned media refs.
mediaStore.addRef(a cross-store write the DataStore transaction can't roll back) moved to run after the fork commits, so a rolled-back fork (e.g. the cursor-missing 409 path) can no longer leave an orphan ref. - Postgres contract suite no longer races the system catalog under parallel runs (per-file database isolation + single-connection
freshSchemaDDL).
中文(备份翻译)
第八个公开版本。收尾 v0.0.7 的架构整理——清掉剩余审计债,让 schema 与事务层成为单一真相源,并交付向量语义记忆召回,全部用真实 pgvector Postgres 端到端验证。新游戏类型可声明自己的记忆块;一个会话可在不改变任何行为的前提下切换存储后端。默认世界与内置插件行为不变。
Added
- 向量语义记忆召回:记忆层在写入时把 turn 消息(recall)与 lorebook + 角色记录(archival)embedding(回合后 best-effort sweep,绝不阻塞回合),并对其做 KNN 召回;未锁定 embedding 模型时按会话回退关键词检索。写入时 ingestion 增量(持久游标 + 内容哈希)、自愈已删记录、历史会话回填走同一路径。经单一注入的
embedseam 接入,@covel/memory仍只依赖shared+store。真实 pgvector Postgres 端到端验证。 vector-store契约新增 PgStore(真 pgvector)分支——生产向量路径此前零自动化覆盖,现已补上,外加 memory 向量 × PgStore 集成测试。@covel/settings包——统一SettingsStore及平台后端(浏览器 localStorage、Electron-IPC json-file)从@covel/shared拆出,纯类型消费方不再被迫拖入浏览器/Electron 代码。
Changed
- 存储 schema 单一真相源:boot DDL 在模块加载时由 Drizzle schema 派生(
buildCreateTablesSql),列/索引只声明一次、执行的 DDL 不可能漂移;只有 Drizzle 无法建模的部分(触发器、幂等列迁移)保留手写。标识符统一加引号。 - 事务作用域绑定到单次提交:生产的回合提交/建会话/world-data 同步/快照 fork 调用方从全局命令式
beginTx/commitTx垫片迁到withTransaction(fn)(真实 Postgres 池化事务;串行后端的嵌套被拒绝而非死锁),消除全库串行化窗口。 runtime/src的 56 个扁平文件整理进 13 个子领域目录;公开 barrel 逐字节不变。- 清掉剩余 v0.0.7 审计债:priority band 字面量收成
isPreGamePriority()/NARRATOR_PRIORITY;前端两条 SSE 通道共用一个事件 reducer;CompactorLLMAdapter+MemoryLLMAdapter合并为共享SimpleCompletionAdapter;Anthropic 缓存断点上限收成单一常量。 - 移除重复的回合入口:
POST /api/sessions/:id/turn是已挂载但前端不可达的第二条回合管线;/api/actions现为唯一回合执行路由,其测试已迁移。 - 所有 monorepo 包版本
0.0.7→0.0.8。
Fixed
- 跨后端向量一致性:真 PG 向量契约立刻暴露两个 Memory/SQLite 后端掩盖的 PgStore 专属 bug——
freshSchema的 store 因动态vec_mem_*表未被 drop 而残留旧数据,且 upsert/delete 路径未测。现在 fresh store 在每个后端都从空开始——同样的数据、同样的 API、自由切换后端。 - memory 召回数据丢失/陈旧:召回游标不再越过 embedding 为空的消息(否则永久丢失);回填期向量结果不足时用关键词补足,不漏最近消息;已删 lorebook/角色向量被清除而非作为陈旧命中返回。
- 快照 fork 孤儿 media ref:
mediaStore.addRef(DataStore 事务回滚不了的跨 store 写)挪到 fork 提交后执行,回滚的 fork(如 cursor-missing 409)不再留下孤儿 ref。 - Postgres 契约套件在并行下不再竞争系统目录(每文件独立库 + 单连接
freshSchemaDDL)。
What's Changed
- chore(release): v0.0.8 — semantic vector memory + single-source schema/tx (real pgvector verified) by @ackness in #8
Full Changelog: v0.0.7...v0.0.8