Skip to content

Releases: ackness/covel

Covel v0.0.17

Choose a tag to compare

@github-actions github-actions released this 21 Jul 15:29
668b89f

[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.set swaps 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 (pendingcommitted/failed; a lingering pending is a crash signature) and which path produced it (player/manual/follower/recursive with parent_turn_id), so session.turnCount counts committed player turns only.
  • proposal.failed SSE/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-narrator claiming the builtin narrator) 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-code first, 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; recursiveCall takes a delta (no session/turn/origin override) and returns a result that cannot fire turn.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-init no 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 to 26.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_session with 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-failed on an uncommitted turn; background jobs settle failed and schedule no follower onto rolled-back state; nested recursiveCall proposals 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_runtime no 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 atomicitysync-dimensions runs under one session lock + transaction; sync-data re-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/lastSeen no longer derive from row counts, an unknown turn can no longer rewind lastSeenTurn, 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/cooldownTurns bind again; fan-out also respects the real preGameCompleted set so a finished Pre-Game runtime is not resurrected.
  • Prompt budget enforcement for tool-using agents — pruning is tool-pair-aware (no orphaned tool messages), 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 dataget/list overlay 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

Choose a tag to compare

@github-actions github-actions released this 17 Jul 08:40

[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 a search-tools tool 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's tool_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 readsloadTurnSessionState no longer full-reads the whole turn_messages log every turn. New DataStore methods listUncompactedTurnMessages, listTurnMessagesAfter (forward keyset), and getTurnMessageStats (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 deleteSession cascade now derives from SESSION_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 findToolClient tool-client resolver and the per-runtime getConfigFn / ctx.config / {{ config.* }} chain (always empty in production) were deleted end to end.
  • Framework/plugin isolation and docs — removed a write-only blockSchemas chain, 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 self tier now accepts any resolver answer on the user's own configured baseUrl (socket still pinned); plugin ctx.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.json instead of silently falling back to localStorage; 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_body instead of a generic 500 across ~13 API routes (shared readJsonBody helper).
  • 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 DELETE suspension route; desktop IPC sender-origin checks extended to the remaining channels.
  • Observability & logging — turn-executor and shutdown paths keep error stacks instead of err.message only, 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

Choose a tag to compare

@github-actions github-actions released this 16 Jul 02:56
c63263b

[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-supplied X-Provider-Keys; an env key attaches only when the resolved baseUrl origin 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 plugin ctx.http helper) 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 binds 127.0.0.1 by default (COVEL_BIND_HOST opts 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 explicit covel:plugin-server-code grant is approved for the session; the specific action then requires its own approval. Legacy hooks: 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-allow and 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_TIER is 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 local self/desktop, matching the documented community trust model (deferred import() until the user approves). Bundled sample worlds are unaffected — their plugins are builtin/official and 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, PostStateCommit hooks, 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 single traceId threads 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 a system.reset → client re-hydrate, a per-session ordered cross-pod outbox over PG LISTEN/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

  • Release v0.0.15 — security & reliability hardening by @ackness in #21

Full Changelog: v0.0.14...v0.0.15

Covel v0.0.14

Choose a tag to compare

@github-actions github-actions released this 13 Jul 03:21
605bfce

[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 new entry PLUGIN.md field — export default function (covel) { ... } with covel.registerTool / on / registerRpc / registerWires and covel.toolkit. Agent runtimes declare LLM tool visibility with the new tools.plugin name 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/steer merges queued player interjections into story runtimes' next LLM step (persisted to history); POST /api/sessions/:id/abort cuts the in-flight LLM stream immediately, is non-retriable, bypasses the streaming salvage path (no partial narrative is ever committed), and surfaces as abortReason: "aborted-by-player" on the existing execution.completed event. 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 into buildAgentLoopPolicy(); the loop's single delta outlet is extracted into createDeltaForwarder. 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, turnCount sync and the auto snapshot now run inside a single sessionLock.withLock scope, 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. fetchWithRetry now 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

Choose a tag to compare

@github-actions github-actions released this 10 Jul 02:48
bbf3a40

[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.yml gains a build-electron-win job (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 .exe artefacts 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 (resolves pnpm.cmd-style shims with correct cmd.exe quoting); plugin/runtime dynamic imports of absolute paths go through pathToFileURL so plugin loading works on Windows; pnpm dev launches web + server directly with supervised teardown; --env-file-if-exists tolerates 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=1 opts into the weaker host-Node smoke).
  • Electron runtime binary is materialised explicitly. electron@42 removed its postinstall hook, so pnpm install no longer downloads the binary into node_modules/electron/dist (whitelisting in onlyBuiltDependencies has nothing to run). The desktop build and dev shell now run ensure-electron.mjs first, which invokes electron's install.js when 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/session returns 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.yaml pins STORE_BACKEND=memory explicitly (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

Choose a tag to compare

@github-actions github-actions released this 06 Jul 03:00
73605e3

[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 on sha256(presetId, text, voice, format), and persists through MediaStore; transcribe() accepts a MediaRef or raw bytes and returns plain text. Assembled under the same conditions as ctx.images; the plugin gateway and trace layer gain synthesizeSpeech / transcribeAudio passthroughs.
  • Plugin-declared media wires (PLUGIN.md wires field). Plugins register image / speech / transcription wires declaratively; ids are namespaced <pluginId>/<wireId> and loading is trust-gated. Slots select wires via providerRequestMetadata.imageWire|speechWire|transcriptionWire in llm.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. Empty relations: {} frontmatter dropped across all bundled plugins; runtime imports moved from devDependencies to dependencies where misfiled; world-init tests relocated to tests/, guide + pregame gain handler tests.
  • Web session view slimmed. game-view reads session state from the session store instead of ~20 pass-through props; a shared useSettingsDialog hook replaces duplicated dialog state; UI primitives trimmed to the variants actually used.

Fixed

  • video_generation models derive a video output modality instead of being misclassified as text.
  • Commit handlers reject state.patch / event.emit proposals with an empty table/field/topic instead of silently landing writes under default/unknown (JS plugins bypass the type layer, so the gate must live at commit time).
  • POST /api/media (10 req/min) and session plugin-rpc (30 req/min) are rate-limited per IP — runtime-mode RPC dispatch runs a full LLM turn, the same cost class as POST /api/actions.

What's Changed

Full Changelog: v0.0.11...v0.0.12

Covel v0.0.11

Choose a tag to compare

@github-actions github-actions released this 04 Jul 03:51
16fd3bd

[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 reusing ChatMessages. Worlds pick their default via the new world.yaml defaultViewMode: 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 declare advertiseEvents: true and call the new builtin emit-event tool, 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-narrator emit scene.set as the reference implementation.
  • Framework image pipeline (ctx.images / gateway.generateImage). One primitive for plugin image generation: wire selection (builtin openai-images and dashscope-wan submit+poll, extensible via registerImageWire()), MediaStore persistence, and promptHash dedupe all handled by the framework — handlers never touch bytes or provider credentials.
  • scene-stage plugin. Resolves scene.set (location + day/night) against the world scene registry — exact, then fuzzy, then session-generated matches — writes stage/current for 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 worldData media sources (to: media + indexTo + key: filename) and the scenes.registry.json / presence.json registries; scripts/generate-scenes.mjs + scripts/emit-scenes.mjs regenerate them from scenes.json specs.
  • requireToolUse manifest 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-prompts uses it.

Changed

  • Media store simplified. The unused S3 backend and its SQLite/PG metadata adapters are gone; media stores gain a shared listByMetadata filter. The imperative beginTx/commitTx/rollbackTx API and the empty plugin_configs table are removed (all writes go through withTransaction; 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-plugin skills now document the VN feature set (defaultViewMode, asset pipeline, events contracts, ctx.images).

Fixed

  • plugin-data.changed SSE 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; indexTo targets skip only the index writes.
  • Stage polish batch: sprites no longer drift when the speaker or scene changes (sticky stations + lane geometry), stale getSession responses 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

Choose a tag to compare

@github-actions github-actions released this 01 Jul 14:49

[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). A world.yaml can now declare per-plugin defaults for any plugin's userSettings, keyed pluginId → settingKey → value. They form the middle layer of a three-tier resolution chain — player override → world default → manifest default — merged at the turn boundary into TurnInput.userSettings and 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. See docs/reference/world-data.md.
  • World-declared core-memory blocks (memoryBlocks). A world can add genre-specific memory dimensions (a detective world's clues / suspects, a business sim's deals / rivals) without forking a plugin — same shape as a plugin's memoryBlocks. 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 / slider user-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.mjs and wired to the character-presence plugin through media + presence world-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: a cost-gate budget guardrail). Prompt spec in worlds/PORTRAITS.md; shipping contract in docs/reference/world-data.md.
  • Hot-reload llm.toml without restarting. Settings → LLM gains a Reload config button (POST /api/llm-config/reload) that re-reads llm.toml and 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 malformed llm.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 new error field on GET /api/llm-config and 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. See docs/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 CharacterAvatar json-render component. It reads presence records from a source plugin/namespace named in the spec (char-creator wires it to character-presence / presence) — keeping the framework component free of any hard-coded plugin id, mirroring how ImageGallery takes its pluginId by prop. Matching tolerates the <sessionId>-<characterId> ids the character list uses against the bare characterId the 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 / description now accept an I18nText (plain string or { "zh-CN": …, "en-US": … } record), and a world.yaml can ship its own characterAttributes array. world-init's guard writes it verbatim as the session's character-attributes schema — 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's affection shows as 好感度 / Affection instead of a raw affection key 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. See docs/reference/world-data.md and docs/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-render Image component gains a zoom prop, 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. The Image component also gains a framed prop (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-reply reply-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 hardcoded narrator / chat-mode-narrator id), 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. userSettings is now the single source for player-tunable plugin settings; the dead, never-consumed config manifest field is removed. A straggler PLUGIN.md that still declares config: is stripped with a deprecation warning rather than failing to load. The PluginUserSettingSpec type 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 in resolveUserSettings, 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-level requiredPlugins / recommendedPlugins / excludedPlugins now fold (de-duplicated) into pluginPolicy at world-load time, so WorldRecord.metadata carries plugin selection in one place. The top-level fields remain valid in world.yaml for back-compat. Per-turn world reads are served from a short-TTL per-worldId cache.
  • 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 / tides memory blocks; Haruka grows to eight characters across five routes with relationships / promises / rumors / festival memory. The redundant cloudmere and neonridge story worlds move to worlds/_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 decoded X-Plugin-User-Settings, so in the scheduled loop every runtime's userSettings collapsed to manifest defaults — a player's UI-tuned values (chat-mode-narrator's dialogueRatio, 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-Settings was 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 world pluginSettings layer. It now carries only the keys a player explicitly set (store.has()).
  • Archived bundled worlds no longer linger in existing users' databases. seedWorlds only ever upserts, so a sample world removed from...
Read more

Covel v0.0.9

Choose a tag to compare

@github-actions github-actions released this 28 Jun 16:53
615a84d

[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 between runtime.started/completed, and zero rows for ctx.gateway provider calls. They now emit function.executing / function.completed (handler boundary) and, via a withGatewayTrace wrapper applied at execution time, gateway.calling / gateway.responded / gateway.failed for generateText/generateObject — all persisted to trace_events and broadcast, so a function runtime's LLM usage is as visible as an agent runtime's. The five events join the single-source CovelEvent union (compile-time exhaustive over COVEL_EVENT_META + the frontend switch). A missing handler now emits a terminal runtime.failed instead of leaving a hanging runtime.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 a withUtilsTrace wrapper now traces as utils.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 the S3MediaMetadataAdapter interface over the shared media_assets / media_refs PG 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.
  • /debug token cost panel. A new Cost view aggregates usage from llm.responded / gateway.responded trace 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.responded payloads don't yet carry the model id needed for usage × pricing.
  • Community plugin uninstall + approval revoke. DELETE /api/plugins/:id removes a third-party plugin from the user plugins dir (rejects builtin ids, returns restartRequired:true); DELETE /api/sessions/:id/approvals[?pluginId=] revokes cached approval grants via the new gate.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-form is now locale-aware. The confirmation {{confirmed}} value (确认/取消) and the fallback-narrative prefixes ([玩家输入] / [玩家选择] / [玩家确认] / [玩家取消]) were hardcoded Chinese; they now resolve by session locale (threaded in via a new RpcHandlerContext.locale, sourced from session.locale in the plugin-rpc dispatch — no executor change). en-US yields Confirm/Cancel + [Player input]/…; unknown locales fall back to zh-CN, byte-for-byte identical to the previous output. submit-form's Submission.type now references the single-source InteractionType union.
  • 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-server 1→2, electron 41→42, @types/node 25→26, @json-render/* 0.18→0.19, and zod 4.3→4.4. One breaking change handled: zod 4.4 treats a bare z.unknown() inside a .strict() object as a required key, so the plugin user-setting default field 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 knip as a CI deps:check gate (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/create scaffold 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.started when the handler is missing or throws — a terminal runtime.failed (and function.completed{status:failed}) is emitted on every exit path.
  • /api/ui-specs no 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.onError additionally 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), which pnpm deploy --filter @covel/server left out — so every packaged build shipped them broken with ERR_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.gateway provider 调用零记录。现在发射 function.executing / function.completed(handler 边界),并经执行期 withGatewayTrace 包裹对 generateText/generateObjectgateway.calling / gateway.responded / gateway.failed——全部持久化到 trace_events 并广播,使 function runtime 的 LLM 用量与 agent runtime 同等可见。五个事件纳入单一真相 CovelEvent union(对 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_refs PG 表上实现 S3MediaMetadataAdapter 接口,使 S3 媒体跨重启存活、跨节点共享——此前 SQLite 适配器只覆盖单节点。1:1 镜像 SQLite 版,并对真实 Postgres 通过同一 media-store 契约套件。
  • /debug token 成本面板:新增 Cost 视图,按 runtime / turn / 会话总计聚合 llm.responded / gateway.respondedusage(零依赖 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-formSubmission.type 现引用单一真相 InteractionType union。
  • 依赖升级到最新 stable:全部 workspace 依赖在既有 minimumReleaseAge: 10080(1 周)门控下升到最新 stable——含跨 major 的 @hono/node-server 1→2、electron 41→42、@types/node 25→26、@json-render/* 0.18→0.19、zod 4.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 作为 CI deps: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

  • v0.0.9 — playability loop, dependency governance, desktop packaging fixes by @ackness in #9

Full Changelog: v0.0.8...v0.0.9

Covel v0.0.8

Choose a tag to compare

@github-actions github-actions released this 28 Jun 03:41
7655427

[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 embed seam so @covel/memory still depends only on shared + store. Verified end-to-end on real pgvector Postgres.
  • A real-pgvector vector-store contract branch (PgStore) — the production vector path (upsertVector / searchVectors / deleteVectors via the vector type + pgvector operators) now has automated coverage it previously lacked, plus a memory-vector × PgStore integration test.
  • @covel/settings package — the unified SettingsStore + 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/commitTx shim to withTransaction(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 + MemoryLLMAdapter converge into a shared SimpleCompletionAdapter; the Anthropic cache-breakpoint cap is one shared constant.
  • Removed the duplicate turn entrypoint. POST /api/sessions/:id/turn was a mounted-but-frontend-unreachable second turn pipeline; /api/actions is now the single turn-execution route. Its tests migrated to /api/actions.
  • Bumped all monorepo package versions 0.0.70.0.8.

Fixed

  • Cross-backend vector parity. A real-PG vector contract immediately surfaced two PgStore-only bugs the Memory/SQLite backends hid: a freshSchema store kept stale rows because the dynamic vec_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 freshSchema DDL).
中文(备份翻译)

第八个公开版本。收尾 v0.0.7 的架构整理——清掉剩余审计债,让 schema 与事务层成为单一真相源,并交付向量语义记忆召回,全部用真实 pgvector Postgres 端到端验证。新游戏类型可声明自己的记忆块;一个会话可在不改变任何行为的前提下切换存储后端。默认世界与内置插件行为不变。

Added

  • 向量语义记忆召回:记忆层在写入时把 turn 消息(recall)与 lorebook + 角色记录(archival)embedding(回合后 best-effort sweep,绝不阻塞回合),并对其做 KNN 召回;未锁定 embedding 模型时按会话回退关键词检索。写入时 ingestion 增量(持久游标 + 内容哈希)、自愈已删记录、历史会话回填走同一路径。经单一注入的 embed seam 接入,@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.70.0.8

Fixed

  • 跨后端向量一致性:真 PG 向量契约立刻暴露两个 Memory/SQLite 后端掩盖的 PgStore 专属 bug——freshSchema 的 store 因动态 vec_mem_* 表未被 drop 而残留旧数据,且 upsert/delete 路径未测。现在 fresh store 在每个后端都从空开始——同样的数据、同样的 API、自由切换后端。
  • memory 召回数据丢失/陈旧:召回游标不再越过 embedding 为空的消息(否则永久丢失);回填期向量结果不足时用关键词补足,不漏最近消息;已删 lorebook/角色向量被清除而非作为陈旧命中返回。
  • 快照 fork 孤儿 media refmediaStore.addRef(DataStore 事务回滚不了的跨 store 写)挪到 fork 提交后执行,回滚的 fork(如 cursor-missing 409)不再留下孤儿 ref。
  • Postgres 契约套件在并行下不再竞争系统目录(每文件独立库 + 单连接 freshSchema DDL)。

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