Skip to content

Verify real models and packaged desktop, fix what they found, close follow-ups - #477

Merged
ackness merged 37 commits into
mainfrom
feat/verification-and-followups
Nov 1, 2026
Merged

ackness merged 37 commits into
mainfrom
feat/verification-and-followups

Conversation

@ackness

@ackness ackness commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Summary / 摘要

This PR closes the follow-ups left after the plugin extensibility work, and fixes a desktop crash found by the first packaged-app check on Windows. Each topic arrived as its own merge commit, followed by merges of fixes from a review of the whole set. Review them one merge at a time.

Packaged desktop app crashed at launch

  • Problem: every packaged desktop format (Windows ZIP, portable EXE, installer; the same main bundle ships on Linux and macOS) failed before opening a window with "Cannot find module '../data/patch.json'". The desktop main process imported the shared package's root entry, which loads the theme CSS parser; esbuild bundled the parser's code but not the JSON file it requires at runtime. No release is affected: the last release predates the theme packs.
  • Fix: the main process imports narrow shared entries (locales, provider keys, the proxy channel) and never loads theme validation. The build, staging and release checks now reject a main or preload bundle that still contains a literal runtime require (including createRequire aliases), so this class of failure stops the release instead of the app.

Full browser suite in CI

  • Problem: CI ran only the smoke suite and the community acceptance suite. Full Playwright regressions surfaced only in local runs, and one timing race showed only on CI runners.
  • Fix: every source PR, main push and release runs the full browser suite in four shards, one worker each, pinned to two CPUs, with failure artifacts. The required result fails unless every shard passes. A nightly and a manual run add repeated timing-sensitive acceptance specs. Docs-only changes keep the short path.

Operation failure notices on stories

  • Problem: when an attached operation (a write service call such as a purchase) failed at commit, the player saw only a passing toast.
  • Fix: the story message that carried it keeps a localized footer with the number of operations that did not apply. The count is written atomically with the story, survives recovery, resume, paging, forks and checkpoints, reaches other open tabs, and exposes no operation details. The footer is host markup outside theme scopes, so a theme pack cannot restyle it away.

Parallel delegation

  • Problem: the ensemble narrator asked its character actors one at a time even when the turn policy suggested they could speak together.
  • Fix: a generic delegate-batch tool dispatches several sub-agent calls at once within the existing budget, depth and concurrency limits. It refuses calls beyond the limit instead of queueing them, returns results in request order, isolates failures so one actor failing costs only its line, propagates cancellation, keeps private targets out of traces and attributes usage per call. The bundled ensemble narrator adopts it.

Fixes from real-model and packaged-app runs

  • Narrative Only could not begin bundled adventures: world data inferred a setup receiver even when the composition had no setup step, so the opening waited forever for an excluded feature. Inferred receivers no longer add setup, and an unfinished setup now names the waiting plugin and how to continue or disable it.
  • Plugin asset URLs fell through to the app: an unmatched /plugin-assets/... path served the SPA index. It now returns a JSON 404, including after dot-segment normalization.
  • Kimi/Moonshot rejected event tools: strict endpoints refuse enum schemas without a type. Model-facing copies of event schemas now carry inferred types; topics that cannot be described safely are omitted with a server warning. Author schemas still validate payloads.
  • DeepSeek thinking continuations failed with missing reasoning_content after a text-only answer was repaired or steered. Every assistant step in the current user turn now carries the reasoning field for model families that need it.
  • Adding the first provider left Story unbound when the built-in default had no server credentials. Story is now bound too, existing choices are kept, and Settings reports the assigned roles.
  • Delegation tools were offered with no reachable target, so models called tools that could only fail. Both delegation tools now list only eligible targets, and Dialogue Narrator falls back to narrator-only play.
  • The GPT-6 Chat reasoning rule applied only to the provider named openai: relays, OpenRouter and other providers serving the same model sent a reasoning field the model rejects with tools. The rule now follows the model and protocol.
  • Replacements derived from injected own data were unguarded: an agent reading its own data through io.selfData and writing it back could overwrite a newer value. Injected keys now record read baselines from their full stored values, so a stale replacement fails with a conflict while the story still commits.

Smaller fixes

  • pnpm validate:plugin prints the collected warnings even when a package has errors, keeping the first error and exit code 1.
  • A standalone SillyTavern lorebook imported with exactly one character card resolves {{char}} to that card's name, in either upload order.
  • The narrator's and world extractor's longest instruction sentences are split, in both languages, keeping every rule (both plugins bump to 0.0.42).
  • Changelog fragments keep working links after release assembly; links already broken in released entries are repaired.

Type of change / 变更类型

  • New feature / 新功能 (feat)
  • Bug fix / Bug 修复 (fix)
  • Refactor / 重构 (refactor)
  • Documentation / 文档 (docs)
  • Tests / 测试 (test)
  • Tooling, CI, release / 工具链、CI、发布 (chore / ci)
  • Performance / 性能 (perf)
  • Breaking change / 破坏性变更 (BREAKING CHANGE)

Breaking changes / 破坏性变更

  • operation.failed event. It now requires messages: { messageId, count }[] naming the story messages that kept a notice. Update server and web clients together.

No database schema change or migration is needed. Existing stories stay unannotated.

Verification / 验证方式

Final run on the merged branch (static gate, every source package, PostgreSQL, full browser suite pinned to two CPUs as in CI):

  • pnpm install --frozen-lockfile, pnpm check, pnpm lint, pnpm schemas:generate (no diff), pnpm validate:plugin for every bundled plugin
  • Package tests: shared 1187, ai-provider 1159, runtime 3250 (+9 todo), server 3417 (+88 skipped), web 3043, store 1041, plugin-loader 673, tools 254, create 173, settings 136, test-runtime 126, events 78, plugin-handlers-utils 264, context 182, memory 58, approval 49, plugin-test-utils 44, site 47, desktop node --test
  • pnpm test:pg against a live PostgreSQL: 539 + 113 passed
  • pnpm e2e (full, 2 CPUs): 190 passed; community acceptance: 11 passed; pnpm test:docs, pnpm test:docs:packages, docs site build
  • Windows 11 packaged app (ZIP, portable, NSIS installer): launch, first adventure, restart, lifecycle, data relocation, CSP, first-run wizard and uninstall all pass. Linux --dir build boots. Not checked: macOS.
  • Real models, scripted pnpm e2e:verify sessions (purchase, presentation cues, ensemble with auto-advance and Stop, notebook and bridge, sub-agent world reads, stale and fresh replacements). After the fixes in this PR: OpenAI (gpt-6-luna), an OpenAI-compatible relay and the same model under a custom provider name pass every scenario with no request errors; adding the first provider binds Story. DeepSeek (deepseek-flash) passes purchase, presentation, ensemble (21 committed turns, no error responses) and the stale-replacement check; its remaining failure is invalid narrator output from the model, which commits nothing. Kimi turns commit with no schema errors; its slow responses hit harness deadlines, so the run was stopped early
  • Replay suite re-recorded on the OpenAI lane: 48/48 hits

Gaps: macOS packaged app untested. Real-model runs are samples, not a soak.

Related issue / context / 关联

Follows #476.

Docs sync / 文档同步

  • docs/reference/ updated (api, protocol, plugins, tools, extension-points, plugin-extensions, transactions, world-model, ui-panels, theme-packages, world-installation, plugin-installation)
  • Guides (plugins/advanced, prompt-style, testing; worlds/import-sillytavern), builtin pages and internals (commands, e2e-testing, desktop-packaging, flow, packages, security); Chinese pages mirrored under docs/en/
  • CONTRIBUTING.md, CONTRIBUTING.en.md, .github/PULL_REQUEST_TEMPLATE.md and AGENTS.md updated for the CI and desktop changes
  • Changelog fragments: story-operation-failure-notices, parallel-character-contributions, plugin-validation-warnings, lorebook-character-macros, bundled-prompt-clarity, changelog-links, setup-blocked-opening, plugin-asset-routing, moonshot-enum-schema, deepseek-reasoning-passback, first-provider-story-slot, delegate-offer, reasoning-rule-by-model; proposal-read-baselines extended for selfData; the changelog README documents the link convention and released entries' broken links are repaired

🤖 Generated with Claude Code

ackness and others added 30 commits November 1, 2026 05:15
A standalone SillyTavern lorebook imported together with one character
card now resolves {{char}} and its aliases to that card's name, whichever
file is uploaded first. Embedded books keep their own card, and imports
with no card or several cards keep reporting the removed macros.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`pnpm validate:plugin` printed warnings only when a package passed, so an
author fixing the first error never saw the warnings collected alongside
it. The CLI now prints every available warning once, keeps the first
error on stderr and still exits 1; warning-only packages exit 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Split the narrator's and the world extractor's longest instruction
sentences so both prompt variants pass the style limit, keeping every
gameplay, attribution and extraction rule. Both plugins bump to 0.0.42.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI ran only the smoke suite and the community acceptance suite, so full
Playwright regressions surfaced only in local runs. Every source PR, main
push and release now runs the full browser suite in four shards, one
worker each, pinned to two CPUs, and uploads failure artifacts. The
required result fails unless every shard passes. A nightly and a manual
run add repeated timing-sensitive acceptance specs. Docs-only changes
keep the short path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When an attached operation (a write service call such as a purchase)
failed at commit, the player saw only a passing toast. The story message
that carried it now keeps a localized footer with the number of
operations that did not apply. The count is written atomically with the
story messages, survives recovery, paging, forks and checkpoints, and
exposes no operation details. Older stories stay unannotated.

BREAKING CHANGE: the operation.failed event now requires a messages list
of { messageId, count } naming the story messages that kept a notice.
Update server and web clients together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ensemble narrator asked its character actors one at a time even when
the turn policy suggested they could speak together. A generic
delegate-batch tool now dispatches several sub-agent calls at once within
the existing budget, depth and concurrency limits, refuses calls beyond
the limit instead of queueing them, returns results in request order, and
isolates failures so one actor failing costs only its line. Cancellation
reaches every child, private targets stay out of traces, and usage is
attributed per call. The bundled ensemble narrator adopts it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/runtime/tests/subagent-world-view.test.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The packaged app failed at launch with "Cannot find module
'../data/patch.json'". The desktop main process imported the shared
package's root entry, which loads the theme CSS parser; esbuild bundled
the parser's code but not the JSON it requires at runtime. The main
process now imports narrow shared entries for locales, provider keys and
the proxy channel, and never loads theme validation.

The build, staging and release checks now reject a main or preload bundle
that still contains a literal runtime require (including createRequire
aliases), so this class of failure stops the release instead of the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fragments link relative to docs/changelog.d, but releasing copied them
verbatim into docs/CHANGELOG.md, one directory up, so their targets
broke. Assembly now rebases relative Markdown links and images to the
changelog's directory, the fragment check rejects links to missing local
files, and the released entries' broken links are repaired.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A resume response that arrived after recovery replaced the recovered
story row and erased its operation failure notice. Completing a message
that is already present now keeps its validated notice. An observing tab
also never loaded a story committed while another runtime in the same
execution stayed suspended; a committed turn.suspended now refreshes
messages through the existing coalescing and ownership guards.

The notice footer stays in host markup outside theme scopes, so a theme
pack cannot restyle it into its background, and the theme pack browser
test targets the toast and the footer separately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plugin asset URL whose dot segments normalized outside its package,
such as /plugin-assets/<id>/<digest>/%2e%2e/%2e%2e/PLUGIN.md, fell through
to the web app and returned its index page with status 200. Nothing was
disclosed, but the plugin asset namespace now stays reserved after
normalization and every unmatched path returns a JSON 404.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moonshot rejected whole requests when an event topic's payload schema
contained an enum without a type, because emit-event copied author
schemas verbatim into its tool parameters. Tool parameter schemas are now
normalized for the model while the original schemas still validate
payloads; topics whose schema cannot be described are omitted with a
diagnostic, and emit-event is hidden when none remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DeepSeek in thinking mode returned HTTP 400 on the second step of a tool
loop, because the OpenAI Chat serializer dropped empty or absent
reasoning from text-only assistant steps. Reasoning is now included on
every assistant step of the current user turn for model families that
require it; requests to other models are byte-for-byte unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adding the first custom provider in Settings assigned only the plugin
slot, because the built-in story default looked like an explicit server
assignment, so a new player's game had no story model. Built-in defaults
now carry a marker; the first provider replaces unkeyed defaults while
operator, local and keyed assignments stay, and Settings reports which
roles were assigned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agents were offered delegate and delegate-batch even when no provider
could serve any target, and real models spent calls on not-found
results. Both tools are now hidden when no target resolves and list only
reachable targets otherwise; a provider that disappears mid-execution
still yields not-found. The bundled ensemble narrator mentions
delegation only when the tools are present (0.0.44).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ackness and others added 7 commits November 1, 2026 07:55
Choosing a composition without setup, such as Narrative Only on Lantern
Barrow, could still start a session that waited forever: automatic data
receiver selection added a plugin with a character creation step that
nothing completed, and the setup notice stayed blank. Inference no longer
introduces setup, directly or through dependencies, into a composition
without it; that data is reported as omitted like any other missing
receiver. A setup notice with no message now names the plugins it waits on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GPT-6 reasoning rule only applied to the provider named openai, so a
custom provider, the default provider or a gateway serving the same model
with tools sent a request the API rejected with HTTP 400. Reasoning rules
now follow the model and protocol, as documented, wherever the model is
reached. No other rule depended on the provider name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A runtime that received its own data through prompt injection and
rewrote it recorded no read baseline, so a value another writer committed
while the model was answering was silently overwritten. Injected keys the
runtime can write now count as reads: their delivered values become
baselines, and a replacement derived from them is rejected as a conflict
when the key changed before commit. Story text still commits; read-only
injections record nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PostgreSQL jsonb returns object keys in its own order, so the shared
selfData baseline suite's fake model missed the injected score on the
live PostgreSQL run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ackness
ackness temporarily deployed to feat/verification-and-followups - covel-demo PR #477 November 1, 2026 07:48 — with Render Destroyed
@ackness
ackness merged commit a315382 into main Nov 1, 2026
19 checks passed
@ackness
ackness deleted the feat/verification-and-followups branch November 1, 2026 09:30

This branch was successfully deployed

No deployments
feat/verification-and-followups - covel-demo PR #477 — beb3e966 Deployed Nov 1, 2026 by ackness
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant