@zwbigi/ink-xy 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +2 -2
- package/.next/build-manifest.json +2 -2
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/inkos/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/install/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/search/route.js.nft.json +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +1 -1
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app-paths-manifest.json +2 -2
- package/.next/server/chunks/162.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/trace +4 -4
- package/.next/trace-build +1 -1
- package/bin/pi-web.js +4 -1
- package/inkos/.env.example +20 -0
- package/inkos/.node-version +1 -0
- package/inkos/.nvmrc +1 -0
- package/inkos/CHANGELOG.md +787 -0
- package/inkos/CONTRIBUTING.md +89 -0
- package/inkos/LICENSE +661 -0
- package/inkos/README.en.md +483 -0
- package/inkos/README.ja.md +461 -0
- package/inkos/README.md +272 -0
- package/inkos/assets/15qun.jpg +0 -0
- package/inkos/assets/41777702961_.pic.jpg +0 -0
- package/inkos/assets/inkos-short-demo-cover.png +0 -0
- package/inkos/assets/inkos-text.svg +40 -0
- package/inkos/assets/logo.svg +47 -0
- package/inkos/assets/screenshot-chapters.png +0 -0
- package/inkos/assets/screenshot-pipeline.png +0 -0
- package/inkos/assets/screenshot-state.png +0 -0
- package/inkos/assets/screenshot-terminal.png +0 -0
- package/inkos/assets/wechat-group-v8.jpg +0 -0
- package/inkos/package.json +42 -0
- package/inkos/packages/cli/package.json +74 -0
- package/inkos/packages/cli/src/__tests__/analytics.test.ts +154 -0
- package/inkos/packages/cli/src/__tests__/cli-integration.test.ts +1031 -0
- package/inkos/packages/cli/src/__tests__/daemon.test.ts +93 -0
- package/inkos/packages/cli/src/__tests__/doctor.test.ts +36 -0
- package/inkos/packages/cli/src/__tests__/interact-command.test.ts +142 -0
- package/inkos/packages/cli/src/__tests__/interaction-tools.test.ts +107 -0
- package/inkos/packages/cli/src/__tests__/llm-overrides.test.ts +25 -0
- package/inkos/packages/cli/src/__tests__/localization.test.ts +121 -0
- package/inkos/packages/cli/src/__tests__/progress-text.test.ts +92 -0
- package/inkos/packages/cli/src/__tests__/project-bootstrap.test.ts +71 -0
- package/inkos/packages/cli/src/__tests__/publish-package.test.ts +272 -0
- package/inkos/packages/cli/src/__tests__/revision-command.test.ts +82 -0
- package/inkos/packages/cli/src/__tests__/runtime-requirements.test.ts +89 -0
- package/inkos/packages/cli/src/__tests__/short-fiction-command.test.ts +48 -0
- package/inkos/packages/cli/src/__tests__/studio-runtime.test.ts +142 -0
- package/inkos/packages/cli/src/__tests__/studio.test.ts +87 -0
- package/inkos/packages/cli/src/__tests__/tui-activity-state.test.ts +20 -0
- package/inkos/packages/cli/src/__tests__/tui-agent-session.test.ts +213 -0
- package/inkos/packages/cli/src/__tests__/tui-chat-depth.test.ts +27 -0
- package/inkos/packages/cli/src/__tests__/tui-chat-draft.test.ts +44 -0
- package/inkos/packages/cli/src/__tests__/tui-command.test.ts +86 -0
- package/inkos/packages/cli/src/__tests__/tui-composer-caret.test.ts +46 -0
- package/inkos/packages/cli/src/__tests__/tui-composer-display.test.ts +40 -0
- package/inkos/packages/cli/src/__tests__/tui-dashboard.test.tsx +219 -0
- package/inkos/packages/cli/src/__tests__/tui-effects-i18n.test.ts +29 -0
- package/inkos/packages/cli/src/__tests__/tui-i18n.test.ts +22 -0
- package/inkos/packages/cli/src/__tests__/tui-input-chrome.test.ts +10 -0
- package/inkos/packages/cli/src/__tests__/tui-input-history.test.ts +40 -0
- package/inkos/packages/cli/src/__tests__/tui-layout.test.ts +55 -0
- package/inkos/packages/cli/src/__tests__/tui-local-commands.test.ts +47 -0
- package/inkos/packages/cli/src/__tests__/tui-session-store.test.ts +81 -0
- package/inkos/packages/cli/src/__tests__/tui-setup-i18n.test.ts +31 -0
- package/inkos/packages/cli/src/__tests__/tui-slash-autocomplete.test.ts +33 -0
- package/inkos/packages/cli/src/commands/agent.ts +65 -0
- package/inkos/packages/cli/src/commands/analytics.ts +77 -0
- package/inkos/packages/cli/src/commands/audit.ts +52 -0
- package/inkos/packages/cli/src/commands/book.ts +260 -0
- package/inkos/packages/cli/src/commands/compose.ts +50 -0
- package/inkos/packages/cli/src/commands/config.ts +328 -0
- package/inkos/packages/cli/src/commands/consolidate.ts +50 -0
- package/inkos/packages/cli/src/commands/daemon.ts +121 -0
- package/inkos/packages/cli/src/commands/detect.ts +125 -0
- package/inkos/packages/cli/src/commands/doctor.ts +391 -0
- package/inkos/packages/cli/src/commands/draft.ts +43 -0
- package/inkos/packages/cli/src/commands/eval.ts +217 -0
- package/inkos/packages/cli/src/commands/export.ts +45 -0
- package/inkos/packages/cli/src/commands/fanfic.ts +183 -0
- package/inkos/packages/cli/src/commands/genre.ts +160 -0
- package/inkos/packages/cli/src/commands/import.ts +158 -0
- package/inkos/packages/cli/src/commands/init.ts +47 -0
- package/inkos/packages/cli/src/commands/interact.ts +109 -0
- package/inkos/packages/cli/src/commands/plan.ts +54 -0
- package/inkos/packages/cli/src/commands/radar.ts +60 -0
- package/inkos/packages/cli/src/commands/review.ts +253 -0
- package/inkos/packages/cli/src/commands/revise.ts +58 -0
- package/inkos/packages/cli/src/commands/short-fiction.ts +294 -0
- package/inkos/packages/cli/src/commands/status.ts +138 -0
- package/inkos/packages/cli/src/commands/studio.ts +194 -0
- package/inkos/packages/cli/src/commands/style.ts +99 -0
- package/inkos/packages/cli/src/commands/tui.ts +18 -0
- package/inkos/packages/cli/src/commands/update.ts +45 -0
- package/inkos/packages/cli/src/commands/write.ts +324 -0
- package/inkos/packages/cli/src/index.ts +5 -0
- package/inkos/packages/cli/src/interaction/tools.ts +49 -0
- package/inkos/packages/cli/src/localization.ts +215 -0
- package/inkos/packages/cli/src/program.ts +106 -0
- package/inkos/packages/cli/src/progress-text.ts +85 -0
- package/inkos/packages/cli/src/project-bootstrap.ts +175 -0
- package/inkos/packages/cli/src/runtime-requirements.ts +135 -0
- package/inkos/packages/cli/src/tui/__tests__/markdown.test.ts +64 -0
- package/inkos/packages/cli/src/tui/activity-state.ts +41 -0
- package/inkos/packages/cli/src/tui/agent-input.ts +264 -0
- package/inkos/packages/cli/src/tui/ansi.ts +72 -0
- package/inkos/packages/cli/src/tui/app.ts +130 -0
- package/inkos/packages/cli/src/tui/chat-depth.ts +22 -0
- package/inkos/packages/cli/src/tui/chat-draft.ts +42 -0
- package/inkos/packages/cli/src/tui/composer-caret.ts +22 -0
- package/inkos/packages/cli/src/tui/composer-display.ts +26 -0
- package/inkos/packages/cli/src/tui/dashboard-model.ts +164 -0
- package/inkos/packages/cli/src/tui/dashboard.tsx +544 -0
- package/inkos/packages/cli/src/tui/effects.ts +542 -0
- package/inkos/packages/cli/src/tui/i18n.ts +278 -0
- package/inkos/packages/cli/src/tui/input-history.ts +69 -0
- package/inkos/packages/cli/src/tui/local-commands.ts +55 -0
- package/inkos/packages/cli/src/tui/markdown.ts +64 -0
- package/inkos/packages/cli/src/tui/session-store.ts +6 -0
- package/inkos/packages/cli/src/tui/setup.ts +397 -0
- package/inkos/packages/cli/src/tui/slash-autocomplete.ts +62 -0
- package/inkos/packages/cli/src/tui/theme.ts +17 -0
- package/inkos/packages/cli/src/utils.ts +222 -0
- package/inkos/packages/cli/tsconfig.json +9 -0
- package/inkos/packages/core/genres/cozy.md +43 -0
- package/inkos/packages/core/genres/cultivation.md +42 -0
- package/inkos/packages/core/genres/dungeon-core.md +40 -0
- package/inkos/packages/core/genres/horror.md +51 -0
- package/inkos/packages/core/genres/isekai.md +43 -0
- package/inkos/packages/core/genres/litrpg.md +43 -0
- package/inkos/packages/core/genres/other.md +24 -0
- package/inkos/packages/core/genres/progression.md +41 -0
- package/inkos/packages/core/genres/romantasy.md +45 -0
- package/inkos/packages/core/genres/sci-fi.md +42 -0
- package/inkos/packages/core/genres/system-apocalypse.md +40 -0
- package/inkos/packages/core/genres/tower-climber.md +41 -0
- package/inkos/packages/core/genres/urban.md +53 -0
- package/inkos/packages/core/genres/xianxia.md +46 -0
- package/inkos/packages/core/genres/xuanhuan.md +64 -0
- package/inkos/packages/core/package.json +61 -0
- package/inkos/packages/core/src/__tests__/agent-max-tokens-policy.test.ts +29 -0
- package/inkos/packages/core/src/__tests__/agent-session.test.ts +866 -0
- package/inkos/packages/core/src/__tests__/agent-system-prompt.test.ts +167 -0
- package/inkos/packages/core/src/__tests__/agent-tools-params.test.ts +197 -0
- package/inkos/packages/core/src/__tests__/agent-tools.test.ts +421 -0
- package/inkos/packages/core/src/__tests__/ai-tells.test.ts +90 -0
- package/inkos/packages/core/src/__tests__/architect-phase5-consolidated.test.ts +445 -0
- package/inkos/packages/core/src/__tests__/architect-phase5.test.ts +455 -0
- package/inkos/packages/core/src/__tests__/architect-phase7.test.ts +210 -0
- package/inkos/packages/core/src/__tests__/architect.test.ts +859 -0
- package/inkos/packages/core/src/__tests__/audit-parse.test.ts +78 -0
- package/inkos/packages/core/src/__tests__/book-id.test.ts +26 -0
- package/inkos/packages/core/src/__tests__/book-session-store.test.ts +447 -0
- package/inkos/packages/core/src/__tests__/book-session.test.ts +113 -0
- package/inkos/packages/core/src/__tests__/chapter-analyzer.test.ts +574 -0
- package/inkos/packages/core/src/__tests__/chapter-memo-parser.test.ts +247 -0
- package/inkos/packages/core/src/__tests__/chapter-persistence.test.ts +198 -0
- package/inkos/packages/core/src/__tests__/chapter-review-cycle.test.ts +294 -0
- package/inkos/packages/core/src/__tests__/chapter-splitter.test.ts +156 -0
- package/inkos/packages/core/src/__tests__/chapter-state-recovery.test.ts +235 -0
- package/inkos/packages/core/src/__tests__/chapter-truth-validation.test.ts +253 -0
- package/inkos/packages/core/src/__tests__/composer.test.ts +627 -0
- package/inkos/packages/core/src/__tests__/config-loader.test.ts +325 -0
- package/inkos/packages/core/src/__tests__/config-migration.test.ts +102 -0
- package/inkos/packages/core/src/__tests__/consolidator.test.ts +32 -0
- package/inkos/packages/core/src/__tests__/context-filter.test.ts +60 -0
- package/inkos/packages/core/src/__tests__/context-transform.test.ts +108 -0
- package/inkos/packages/core/src/__tests__/continuity.test.ts +391 -0
- package/inkos/packages/core/src/__tests__/detection-insights.test.ts +59 -0
- package/inkos/packages/core/src/__tests__/detector.test.ts +86 -0
- package/inkos/packages/core/src/__tests__/draft-directive-parser.test.ts +386 -0
- package/inkos/packages/core/src/__tests__/edit-controller.test.ts +190 -0
- package/inkos/packages/core/src/__tests__/effective-llm-config.test.ts +486 -0
- package/inkos/packages/core/src/__tests__/fanfic-dimensions.test.ts +58 -0
- package/inkos/packages/core/src/__tests__/fanfic-models.test.ts +69 -0
- package/inkos/packages/core/src/__tests__/governed-working-set.test.ts +155 -0
- package/inkos/packages/core/src/__tests__/hook-arbiter.test.ts +124 -0
- package/inkos/packages/core/src/__tests__/hook-governance.test.ts +228 -0
- package/inkos/packages/core/src/__tests__/hook-health.test.ts +166 -0
- package/inkos/packages/core/src/__tests__/hook-ledger-validator.test.ts +236 -0
- package/inkos/packages/core/src/__tests__/hook-promotion.test.ts +192 -0
- package/inkos/packages/core/src/__tests__/hook-stale-detection.test.ts +136 -0
- package/inkos/packages/core/src/__tests__/index-notify-lazy.test.ts +20 -0
- package/inkos/packages/core/src/__tests__/interaction-chat-tokens.test.ts +170 -0
- package/inkos/packages/core/src/__tests__/interaction-models.test.ts +155 -0
- package/inkos/packages/core/src/__tests__/interaction-nl-router.test.ts +223 -0
- package/inkos/packages/core/src/__tests__/interaction-runtime.test.ts +633 -0
- package/inkos/packages/core/src/__tests__/interaction-tools.test.ts +343 -0
- package/inkos/packages/core/src/__tests__/length-metrics.test.ts +82 -0
- package/inkos/packages/core/src/__tests__/length-normalizer.test.ts +331 -0
- package/inkos/packages/core/src/__tests__/list-models.test.ts +109 -0
- package/inkos/packages/core/src/__tests__/llm-env.test.ts +31 -0
- package/inkos/packages/core/src/__tests__/logger.test.ts +175 -0
- package/inkos/packages/core/src/__tests__/long-span-fatigue.test.ts +160 -0
- package/inkos/packages/core/src/__tests__/memory-retrieval.test.ts +1303 -0
- package/inkos/packages/core/src/__tests__/models.test.ts +918 -0
- package/inkos/packages/core/src/__tests__/outline-paths.test.ts +97 -0
- package/inkos/packages/core/src/__tests__/path-safety.test.ts +22 -0
- package/inkos/packages/core/src/__tests__/persisted-governed-plan.test.ts +134 -0
- package/inkos/packages/core/src/__tests__/phase5-cleanup.test.ts +393 -0
- package/inkos/packages/core/src/__tests__/phase5-hotfix.test.ts +288 -0
- package/inkos/packages/core/src/__tests__/phase7-hotfix.test.ts +614 -0
- package/inkos/packages/core/src/__tests__/pipeline-agent.test.ts +354 -0
- package/inkos/packages/core/src/__tests__/pipeline-runner-memory-sync.test.ts +317 -0
- package/inkos/packages/core/src/__tests__/pipeline-runner.test.ts +5200 -0
- package/inkos/packages/core/src/__tests__/planner-context.test.ts +137 -0
- package/inkos/packages/core/src/__tests__/planner-prompts-ratio.test.ts +11 -0
- package/inkos/packages/core/src/__tests__/planner-prompts.test.ts +171 -0
- package/inkos/packages/core/src/__tests__/planner.test.ts +362 -0
- package/inkos/packages/core/src/__tests__/planning-materials.test.ts +90 -0
- package/inkos/packages/core/src/__tests__/polisher.test.ts +189 -0
- package/inkos/packages/core/src/__tests__/post-write-validator.test.ts +291 -0
- package/inkos/packages/core/src/__tests__/probe.test.ts +77 -0
- package/inkos/packages/core/src/__tests__/project-interaction.test.ts +241 -0
- package/inkos/packages/core/src/__tests__/provider.test.ts +953 -0
- package/inkos/packages/core/src/__tests__/providers-group.test.ts +34 -0
- package/inkos/packages/core/src/__tests__/providers-lookup.test.ts +81 -0
- package/inkos/packages/core/src/__tests__/providers-schema.test.ts +158 -0
- package/inkos/packages/core/src/__tests__/proxy-fetch.test.ts +75 -0
- package/inkos/packages/core/src/__tests__/revise-foundation.test.ts +514 -0
- package/inkos/packages/core/src/__tests__/reviser.test.ts +859 -0
- package/inkos/packages/core/src/__tests__/runtime-state-store.test.ts +388 -0
- package/inkos/packages/core/src/__tests__/scheduler.test.ts +123 -0
- package/inkos/packages/core/src/__tests__/secrets-migration.test.ts +71 -0
- package/inkos/packages/core/src/__tests__/secrets.test.ts +95 -0
- package/inkos/packages/core/src/__tests__/sensitive-words.test.ts +88 -0
- package/inkos/packages/core/src/__tests__/service-presets-regression.test.ts +73 -0
- package/inkos/packages/core/src/__tests__/service-resolver-regression.test.ts +75 -0
- package/inkos/packages/core/src/__tests__/service-resolver.test.ts +228 -0
- package/inkos/packages/core/src/__tests__/session-transcript-restore.test.ts +1311 -0
- package/inkos/packages/core/src/__tests__/session-transcript.test.ts +195 -0
- package/inkos/packages/core/src/__tests__/settler-delta-parser.test.ts +133 -0
- package/inkos/packages/core/src/__tests__/short-fiction-public.test.ts +241 -0
- package/inkos/packages/core/src/__tests__/spot-fix-patches.test.ts +104 -0
- package/inkos/packages/core/src/__tests__/state-manager.test.ts +1298 -0
- package/inkos/packages/core/src/__tests__/state-projections.test.ts +130 -0
- package/inkos/packages/core/src/__tests__/state-reducer.test.ts +372 -0
- package/inkos/packages/core/src/__tests__/state-validator-agent.test.ts +165 -0
- package/inkos/packages/core/src/__tests__/state-validator.test.ts +122 -0
- package/inkos/packages/core/src/__tests__/style-analyzer.test.ts +61 -0
- package/inkos/packages/core/src/__tests__/temperature-constraints.test.ts +57 -0
- package/inkos/packages/core/src/__tests__/v13-hotfix-round4.test.ts +343 -0
- package/inkos/packages/core/src/__tests__/verify-service.test.ts +77 -0
- package/inkos/packages/core/src/__tests__/webhook.test.ts +91 -0
- package/inkos/packages/core/src/__tests__/writer-parser.test.ts +348 -0
- package/inkos/packages/core/src/__tests__/writer-prompts.test.ts +269 -0
- package/inkos/packages/core/src/__tests__/writer.test.ts +1360 -0
- package/inkos/packages/core/src/agent/agent-session.ts +737 -0
- package/inkos/packages/core/src/agent/agent-system-prompt.ts +199 -0
- package/inkos/packages/core/src/agent/agent-tools.ts +835 -0
- package/inkos/packages/core/src/agent/context-transform.ts +85 -0
- package/inkos/packages/core/src/agent/index.ts +14 -0
- package/inkos/packages/core/src/agents/ai-tells.ts +161 -0
- package/inkos/packages/core/src/agents/architect.ts +1291 -0
- package/inkos/packages/core/src/agents/base.ts +100 -0
- package/inkos/packages/core/src/agents/chapter-analyzer.ts +634 -0
- package/inkos/packages/core/src/agents/composer.ts +469 -0
- package/inkos/packages/core/src/agents/consolidator.ts +218 -0
- package/inkos/packages/core/src/agents/continuity.ts +824 -0
- package/inkos/packages/core/src/agents/detection-insights.ts +72 -0
- package/inkos/packages/core/src/agents/detector.ts +224 -0
- package/inkos/packages/core/src/agents/en-prompt-sections.ts +129 -0
- package/inkos/packages/core/src/agents/fanfic-canon-importer.ts +146 -0
- package/inkos/packages/core/src/agents/fanfic-dimensions.ts +87 -0
- package/inkos/packages/core/src/agents/fanfic-prompt-sections.ts +109 -0
- package/inkos/packages/core/src/agents/foundation-reviewer.ts +204 -0
- package/inkos/packages/core/src/agents/length-normalizer.ts +218 -0
- package/inkos/packages/core/src/agents/observer-prompts.ts +127 -0
- package/inkos/packages/core/src/agents/planner-context.ts +297 -0
- package/inkos/packages/core/src/agents/planner-prompts.ts +404 -0
- package/inkos/packages/core/src/agents/planner.ts +783 -0
- package/inkos/packages/core/src/agents/polisher.ts +153 -0
- package/inkos/packages/core/src/agents/post-write-validator.ts +873 -0
- package/inkos/packages/core/src/agents/radar-source.ts +123 -0
- package/inkos/packages/core/src/agents/radar.ts +120 -0
- package/inkos/packages/core/src/agents/reviser.ts +701 -0
- package/inkos/packages/core/src/agents/rules-reader.ts +155 -0
- package/inkos/packages/core/src/agents/sensitive-words.ts +142 -0
- package/inkos/packages/core/src/agents/settler-delta-parser.ts +53 -0
- package/inkos/packages/core/src/agents/settler-parser.ts +38 -0
- package/inkos/packages/core/src/agents/settler-prompts.ts +230 -0
- package/inkos/packages/core/src/agents/short-fiction.ts +429 -0
- package/inkos/packages/core/src/agents/state-validator.ts +322 -0
- package/inkos/packages/core/src/agents/style-analyzer.ts +93 -0
- package/inkos/packages/core/src/agents/writer-parser.ts +178 -0
- package/inkos/packages/core/src/agents/writer-prompts.ts +899 -0
- package/inkos/packages/core/src/agents/writer.ts +1450 -0
- package/inkos/packages/core/src/index.ts +392 -0
- package/inkos/packages/core/src/interaction/book-session-store.ts +226 -0
- package/inkos/packages/core/src/interaction/draft-directive-parser.ts +266 -0
- package/inkos/packages/core/src/interaction/edit-controller.ts +270 -0
- package/inkos/packages/core/src/interaction/events.ts +41 -0
- package/inkos/packages/core/src/interaction/export-artifact.ts +151 -0
- package/inkos/packages/core/src/interaction/intents.ts +63 -0
- package/inkos/packages/core/src/interaction/modes.ts +13 -0
- package/inkos/packages/core/src/interaction/nl-router.ts +258 -0
- package/inkos/packages/core/src/interaction/project-control.ts +150 -0
- package/inkos/packages/core/src/interaction/project-session-store.ts +81 -0
- package/inkos/packages/core/src/interaction/project-tools.ts +704 -0
- package/inkos/packages/core/src/interaction/request-router.ts +5 -0
- package/inkos/packages/core/src/interaction/runtime.ts +1167 -0
- package/inkos/packages/core/src/interaction/session-transcript-legacy.ts +113 -0
- package/inkos/packages/core/src/interaction/session-transcript-restore.ts +607 -0
- package/inkos/packages/core/src/interaction/session-transcript-schema.ts +76 -0
- package/inkos/packages/core/src/interaction/session-transcript.ts +189 -0
- package/inkos/packages/core/src/interaction/session.ts +226 -0
- package/inkos/packages/core/src/interaction/truth-authority.ts +45 -0
- package/inkos/packages/core/src/llm/config-migration.ts +58 -0
- package/inkos/packages/core/src/llm/cover-providers.ts +45 -0
- package/inkos/packages/core/src/llm/provider.ts +1331 -0
- package/inkos/packages/core/src/llm/providers/endpoints/ai360.ts +42 -0
- package/inkos/packages/core/src/llm/providers/endpoints/anthropic.ts +82 -0
- package/inkos/packages/core/src/llm/providers/endpoints/astronCodingPlan.ts +30 -0
- package/inkos/packages/core/src/llm/providers/endpoints/baichuan.ts +28 -0
- package/inkos/packages/core/src/llm/providers/endpoints/bailian.ts +65 -0
- package/inkos/packages/core/src/llm/providers/endpoints/bailianCodingPlan.ts +30 -0
- package/inkos/packages/core/src/llm/providers/endpoints/custom.ts +22 -0
- package/inkos/packages/core/src/llm/providers/endpoints/deepseek.ts +35 -0
- package/inkos/packages/core/src/llm/providers/endpoints/giteeai.ts +41 -0
- package/inkos/packages/core/src/llm/providers/endpoints/githubCopilot.ts +43 -0
- package/inkos/packages/core/src/llm/providers/endpoints/glmCodingPlan.ts +28 -0
- package/inkos/packages/core/src/llm/providers/endpoints/google.ts +51 -0
- package/inkos/packages/core/src/llm/providers/endpoints/hunyuan.ts +42 -0
- package/inkos/packages/core/src/llm/providers/endpoints/infiniai.ts +72 -0
- package/inkos/packages/core/src/llm/providers/endpoints/internlm.ts +28 -0
- package/inkos/packages/core/src/llm/providers/endpoints/kimiCode.ts +23 -0
- package/inkos/packages/core/src/llm/providers/endpoints/kimiCodingPlan.ts +24 -0
- package/inkos/packages/core/src/llm/providers/endpoints/kkaiapi.ts +56 -0
- package/inkos/packages/core/src/llm/providers/endpoints/longcat.ts +25 -0
- package/inkos/packages/core/src/llm/providers/endpoints/minimax.ts +39 -0
- package/inkos/packages/core/src/llm/providers/endpoints/minimaxCodingPlan.ts +28 -0
- package/inkos/packages/core/src/llm/providers/endpoints/mistral.ts +40 -0
- package/inkos/packages/core/src/llm/providers/endpoints/modelscope.ts +30 -0
- package/inkos/packages/core/src/llm/providers/endpoints/moonshot.ts +39 -0
- package/inkos/packages/core/src/llm/providers/endpoints/newapi.ts +21 -0
- package/inkos/packages/core/src/llm/providers/endpoints/ollama.ts +73 -0
- package/inkos/packages/core/src/llm/providers/endpoints/openai.ts +77 -0
- package/inkos/packages/core/src/llm/providers/endpoints/opencodeCodingPlan.ts +30 -0
- package/inkos/packages/core/src/llm/providers/endpoints/openrouter.ts +87 -0
- package/inkos/packages/core/src/llm/providers/endpoints/ppio.ts +86 -0
- package/inkos/packages/core/src/llm/providers/endpoints/qiniu.ts +32 -0
- package/inkos/packages/core/src/llm/providers/endpoints/sensenova.ts +45 -0
- package/inkos/packages/core/src/llm/providers/endpoints/siliconcloud.ts +126 -0
- package/inkos/packages/core/src/llm/providers/endpoints/spark.ts +33 -0
- package/inkos/packages/core/src/llm/providers/endpoints/stepfun.ts +35 -0
- package/inkos/packages/core/src/llm/providers/endpoints/tencentcloud.ts +25 -0
- package/inkos/packages/core/src/llm/providers/endpoints/volcengine.ts +52 -0
- package/inkos/packages/core/src/llm/providers/endpoints/volcengineCodingPlan.ts +42 -0
- package/inkos/packages/core/src/llm/providers/endpoints/wenxin.ts +106 -0
- package/inkos/packages/core/src/llm/providers/endpoints/xai.ts +34 -0
- package/inkos/packages/core/src/llm/providers/endpoints/xiaomimimo.ts +26 -0
- package/inkos/packages/core/src/llm/providers/endpoints/zeroone.ts +34 -0
- package/inkos/packages/core/src/llm/providers/endpoints/zhipu.ts +61 -0
- package/inkos/packages/core/src/llm/providers/index.ts +71 -0
- package/inkos/packages/core/src/llm/providers/lookup.ts +70 -0
- package/inkos/packages/core/src/llm/providers/probe.ts +35 -0
- package/inkos/packages/core/src/llm/providers/types.ts +89 -0
- package/inkos/packages/core/src/llm/providers/verify.ts +104 -0
- package/inkos/packages/core/src/llm/secrets.ts +77 -0
- package/inkos/packages/core/src/llm/service-presets.ts +215 -0
- package/inkos/packages/core/src/llm/service-resolver.ts +91 -0
- package/inkos/packages/core/src/models/book-rules.ts +126 -0
- package/inkos/packages/core/src/models/book.ts +70 -0
- package/inkos/packages/core/src/models/chapter.ts +42 -0
- package/inkos/packages/core/src/models/detection.ts +25 -0
- package/inkos/packages/core/src/models/genre-profile.ts +36 -0
- package/inkos/packages/core/src/models/input-governance.ts +99 -0
- package/inkos/packages/core/src/models/length-governance.ts +46 -0
- package/inkos/packages/core/src/models/project.ts +161 -0
- package/inkos/packages/core/src/models/runtime-state.ts +144 -0
- package/inkos/packages/core/src/models/state.ts +52 -0
- package/inkos/packages/core/src/models/style-profile.ts +15 -0
- package/inkos/packages/core/src/notify/dispatcher.ts +96 -0
- package/inkos/packages/core/src/notify/feishu.ts +34 -0
- package/inkos/packages/core/src/notify/telegram.ts +25 -0
- package/inkos/packages/core/src/notify/webhook.ts +58 -0
- package/inkos/packages/core/src/notify/wechat-work.ts +22 -0
- package/inkos/packages/core/src/pipeline/agent.ts +691 -0
- package/inkos/packages/core/src/pipeline/chapter-persistence.ts +79 -0
- package/inkos/packages/core/src/pipeline/chapter-review-cycle.ts +324 -0
- package/inkos/packages/core/src/pipeline/chapter-state-recovery.ts +236 -0
- package/inkos/packages/core/src/pipeline/chapter-truth-validation.ts +145 -0
- package/inkos/packages/core/src/pipeline/detection-runner.ts +164 -0
- package/inkos/packages/core/src/pipeline/persisted-governed-plan.ts +216 -0
- package/inkos/packages/core/src/pipeline/runner.ts +3438 -0
- package/inkos/packages/core/src/pipeline/scheduler.ts +411 -0
- package/inkos/packages/core/src/pipeline/short-fiction-runner.ts +801 -0
- package/inkos/packages/core/src/prompts/index.ts +1 -0
- package/inkos/packages/core/src/prompts/short-fiction.ts +273 -0
- package/inkos/packages/core/src/state/manager.ts +560 -0
- package/inkos/packages/core/src/state/memory-db.ts +359 -0
- package/inkos/packages/core/src/state/runtime-state-store.ts +164 -0
- package/inkos/packages/core/src/state/state-bootstrap.ts +657 -0
- package/inkos/packages/core/src/state/state-projections.ts +255 -0
- package/inkos/packages/core/src/state/state-reducer.ts +260 -0
- package/inkos/packages/core/src/state/state-validator.ts +117 -0
- package/inkos/packages/core/src/utils/analytics.ts +92 -0
- package/inkos/packages/core/src/utils/book-id.ts +31 -0
- package/inkos/packages/core/src/utils/cadence-policy.ts +46 -0
- package/inkos/packages/core/src/utils/chapter-cadence.ts +211 -0
- package/inkos/packages/core/src/utils/chapter-memo-parser.ts +157 -0
- package/inkos/packages/core/src/utils/chapter-splitter.ts +80 -0
- package/inkos/packages/core/src/utils/config-loader.ts +29 -0
- package/inkos/packages/core/src/utils/context-assembly.ts +98 -0
- package/inkos/packages/core/src/utils/context-filter.ts +190 -0
- package/inkos/packages/core/src/utils/effective-llm-config.ts +529 -0
- package/inkos/packages/core/src/utils/governed-context.ts +101 -0
- package/inkos/packages/core/src/utils/governed-working-set.ts +395 -0
- package/inkos/packages/core/src/utils/hook-arbiter.ts +332 -0
- package/inkos/packages/core/src/utils/hook-governance.ts +199 -0
- package/inkos/packages/core/src/utils/hook-health.ts +189 -0
- package/inkos/packages/core/src/utils/hook-ledger-validator.ts +277 -0
- package/inkos/packages/core/src/utils/hook-lifecycle.ts +224 -0
- package/inkos/packages/core/src/utils/hook-policy.ts +115 -0
- package/inkos/packages/core/src/utils/hook-promotion.ts +313 -0
- package/inkos/packages/core/src/utils/hook-stale-detection.ts +168 -0
- package/inkos/packages/core/src/utils/length-metrics.ts +123 -0
- package/inkos/packages/core/src/utils/llm-endpoint-auth.ts +40 -0
- package/inkos/packages/core/src/utils/llm-env.ts +74 -0
- package/inkos/packages/core/src/utils/logger.ts +123 -0
- package/inkos/packages/core/src/utils/long-span-fatigue.ts +545 -0
- package/inkos/packages/core/src/utils/memory-retrieval.ts +527 -0
- package/inkos/packages/core/src/utils/narrative-control.ts +177 -0
- package/inkos/packages/core/src/utils/outline-paths.ts +275 -0
- package/inkos/packages/core/src/utils/path-safety.ts +11 -0
- package/inkos/packages/core/src/utils/planning-materials.ts +185 -0
- package/inkos/packages/core/src/utils/pov-filter.ts +149 -0
- package/inkos/packages/core/src/utils/proxy-fetch.ts +44 -0
- package/inkos/packages/core/src/utils/runtime-writer.ts +41 -0
- package/inkos/packages/core/src/utils/spot-fix-patches.ts +189 -0
- package/inkos/packages/core/src/utils/story-markdown.ts +346 -0
- package/inkos/packages/core/src/utils/web-search.ts +82 -0
- package/inkos/packages/core/src/utils/writing-methodology.ts +164 -0
- package/inkos/packages/core/tsconfig.json +8 -0
- package/inkos/packages/core/vitest.config.ts +7 -0
- package/inkos/packages/studio/components.json +25 -0
- package/inkos/packages/studio/index.html +13 -0
- package/inkos/packages/studio/package.json +72 -0
- package/inkos/packages/studio/postcss.config.js +3 -0
- package/inkos/packages/studio/src/App.test.ts +25 -0
- package/inkos/packages/studio/src/App.tsx +280 -0
- package/inkos/packages/studio/src/api/__tests__/normalize-base-url.test.ts +40 -0
- package/inkos/packages/studio/src/api/book-create.test.ts +104 -0
- package/inkos/packages/studio/src/api/book-create.ts +94 -0
- package/inkos/packages/studio/src/api/errors.ts +17 -0
- package/inkos/packages/studio/src/api/index.ts +30 -0
- package/inkos/packages/studio/src/api/lib/run-store.ts +177 -0
- package/inkos/packages/studio/src/api/lib/sse.ts +50 -0
- package/inkos/packages/studio/src/api/phase5-hotfix.test.ts +335 -0
- package/inkos/packages/studio/src/api/safety.ts +6 -0
- package/inkos/packages/studio/src/api/server.test.ts +3162 -0
- package/inkos/packages/studio/src/api/server.ts +3666 -0
- package/inkos/packages/studio/src/api/v13-hotfix-round4.test.ts +226 -0
- package/inkos/packages/studio/src/app-state.test.ts +8 -0
- package/inkos/packages/studio/src/app-state.ts +1 -0
- package/inkos/packages/studio/src/components/ConfirmDialog.tsx +95 -0
- package/inkos/packages/studio/src/components/ServiceConfigSourceCard.tsx +139 -0
- package/inkos/packages/studio/src/components/ServiceQuickLinks.tsx +65 -0
- package/inkos/packages/studio/src/components/Sidebar.tsx +652 -0
- package/inkos/packages/studio/src/components/ai-elements/code-block.tsx +562 -0
- package/inkos/packages/studio/src/components/ai-elements/confirmation.tsx +174 -0
- package/inkos/packages/studio/src/components/ai-elements/message.tsx +360 -0
- package/inkos/packages/studio/src/components/ai-elements/prompt-input.tsx +1457 -0
- package/inkos/packages/studio/src/components/ai-elements/reasoning.tsx +226 -0
- package/inkos/packages/studio/src/components/ai-elements/shimmer.tsx +77 -0
- package/inkos/packages/studio/src/components/ai-elements/tool.tsx +173 -0
- package/inkos/packages/studio/src/components/chat/BookSidebar.tsx +291 -0
- package/inkos/packages/studio/src/components/chat/ChatMessage.tsx +39 -0
- package/inkos/packages/studio/src/components/chat/QuickActions.tsx +73 -0
- package/inkos/packages/studio/src/components/chat/ToolExecutionSteps.tsx +320 -0
- package/inkos/packages/studio/src/components/chat/__tests__/ToolExecutionSteps.test.ts +114 -0
- package/inkos/packages/studio/src/components/chat-utils.ts +56 -0
- package/inkos/packages/studio/src/components/chatbar-state.test.ts +69 -0
- package/inkos/packages/studio/src/components/sidebar/ChaptersSection.tsx +66 -0
- package/inkos/packages/studio/src/components/sidebar/CharacterSection.tsx +129 -0
- package/inkos/packages/studio/src/components/sidebar/FoundationSection.tsx +61 -0
- package/inkos/packages/studio/src/components/sidebar/ProgressSection.tsx +124 -0
- package/inkos/packages/studio/src/components/sidebar/SidebarCard.tsx +30 -0
- package/inkos/packages/studio/src/components/sidebar/SummarySection.tsx +89 -0
- package/inkos/packages/studio/src/components/ui/alert.tsx +76 -0
- package/inkos/packages/studio/src/components/ui/badge.tsx +52 -0
- package/inkos/packages/studio/src/components/ui/button-group.tsx +87 -0
- package/inkos/packages/studio/src/components/ui/button.tsx +58 -0
- package/inkos/packages/studio/src/components/ui/collapsible.tsx +19 -0
- package/inkos/packages/studio/src/components/ui/command.tsx +194 -0
- package/inkos/packages/studio/src/components/ui/dialog.tsx +158 -0
- package/inkos/packages/studio/src/components/ui/dropdown-menu.tsx +266 -0
- package/inkos/packages/studio/src/components/ui/hover-card.tsx +51 -0
- package/inkos/packages/studio/src/components/ui/input-group.tsx +158 -0
- package/inkos/packages/studio/src/components/ui/input.tsx +20 -0
- package/inkos/packages/studio/src/components/ui/select.tsx +199 -0
- package/inkos/packages/studio/src/components/ui/separator.tsx +23 -0
- package/inkos/packages/studio/src/components/ui/spinner.tsx +10 -0
- package/inkos/packages/studio/src/components/ui/textarea.tsx +18 -0
- package/inkos/packages/studio/src/components/ui/tooltip.tsx +66 -0
- package/inkos/packages/studio/src/constants/service-groups.ts +29 -0
- package/inkos/packages/studio/src/hooks/use-api.test.ts +93 -0
- package/inkos/packages/studio/src/hooks/use-api.ts +189 -0
- package/inkos/packages/studio/src/hooks/use-book-activity.test.ts +129 -0
- package/inkos/packages/studio/src/hooks/use-book-activity.ts +180 -0
- package/inkos/packages/studio/src/hooks/use-colors.ts +27 -0
- package/inkos/packages/studio/src/hooks/use-hash-route.test.ts +101 -0
- package/inkos/packages/studio/src/hooks/use-hash-route.ts +89 -0
- package/inkos/packages/studio/src/hooks/use-i18n.ts +289 -0
- package/inkos/packages/studio/src/hooks/use-session-events.ts +64 -0
- package/inkos/packages/studio/src/hooks/use-sse.test.ts +52 -0
- package/inkos/packages/studio/src/hooks/use-sse.ts +92 -0
- package/inkos/packages/studio/src/hooks/use-theme.test.ts +31 -0
- package/inkos/packages/studio/src/hooks/use-theme.ts +75 -0
- package/inkos/packages/studio/src/index.css +323 -0
- package/inkos/packages/studio/src/lib/error-copy.test.ts +34 -0
- package/inkos/packages/studio/src/lib/error-copy.ts +37 -0
- package/inkos/packages/studio/src/lib/utils.ts +6 -0
- package/inkos/packages/studio/src/main.tsx +10 -0
- package/inkos/packages/studio/src/pages/Analytics.tsx +80 -0
- package/inkos/packages/studio/src/pages/BookCreate.tsx +895 -0
- package/inkos/packages/studio/src/pages/BookDetail.tsx +652 -0
- package/inkos/packages/studio/src/pages/ChapterReader.tsx +266 -0
- package/inkos/packages/studio/src/pages/ChatPage.tsx +521 -0
- package/inkos/packages/studio/src/pages/DaemonControl.tsx +116 -0
- package/inkos/packages/studio/src/pages/Dashboard.tsx +379 -0
- package/inkos/packages/studio/src/pages/DoctorView.tsx +82 -0
- package/inkos/packages/studio/src/pages/GenreManager.tsx +464 -0
- package/inkos/packages/studio/src/pages/ImportManager.tsx +216 -0
- package/inkos/packages/studio/src/pages/LanguageSelector.tsx +74 -0
- package/inkos/packages/studio/src/pages/LogViewer.tsx +82 -0
- package/inkos/packages/studio/src/pages/RadarView.tsx +157 -0
- package/inkos/packages/studio/src/pages/ServiceDetailPage.tsx +393 -0
- package/inkos/packages/studio/src/pages/ServiceListPage.tsx +463 -0
- package/inkos/packages/studio/src/pages/StyleManager.tsx +225 -0
- package/inkos/packages/studio/src/pages/TruthFiles.tsx +194 -0
- package/inkos/packages/studio/src/pages/chat-page-state.test.ts +206 -0
- package/inkos/packages/studio/src/pages/chat-page-state.ts +112 -0
- package/inkos/packages/studio/src/pages/page-state.test.ts +258 -0
- package/inkos/packages/studio/src/pages/service-detail-state.test.ts +294 -0
- package/inkos/packages/studio/src/pages/service-detail-state.ts +234 -0
- package/inkos/packages/studio/src/pages/style-manager-state.test.ts +22 -0
- package/inkos/packages/studio/src/pages/truth-files-state.test.ts +61 -0
- package/inkos/packages/studio/src/shared/contracts.ts +143 -0
- package/inkos/packages/studio/src/store/chat/__tests__/message-parts.test.ts +172 -0
- package/inkos/packages/studio/src/store/chat/index.ts +3 -0
- package/inkos/packages/studio/src/store/chat/initialState.ts +8 -0
- package/inkos/packages/studio/src/store/chat/message-policy.test.ts +16 -0
- package/inkos/packages/studio/src/store/chat/message-policy.ts +5 -0
- package/inkos/packages/studio/src/store/chat/parts-builder.ts +187 -0
- package/inkos/packages/studio/src/store/chat/selectors.ts +13 -0
- package/inkos/packages/studio/src/store/chat/slices/create/action.ts +10 -0
- package/inkos/packages/studio/src/store/chat/slices/create/initialState.ts +9 -0
- package/inkos/packages/studio/src/store/chat/slices/message/action.ts +417 -0
- package/inkos/packages/studio/src/store/chat/slices/message/initialState.ts +10 -0
- package/inkos/packages/studio/src/store/chat/slices/message/runtime.test.ts +21 -0
- package/inkos/packages/studio/src/store/chat/slices/message/runtime.ts +233 -0
- package/inkos/packages/studio/src/store/chat/slices/message/stream-events.ts +272 -0
- package/inkos/packages/studio/src/store/chat/store.ts +11 -0
- package/inkos/packages/studio/src/store/chat/types.ts +169 -0
- package/inkos/packages/studio/src/store/service/index.ts +2 -0
- package/inkos/packages/studio/src/store/service/store.ts +123 -0
- package/inkos/packages/studio/src/store/service/types.ts +50 -0
- package/inkos/packages/studio/tsconfig.json +24 -0
- package/inkos/packages/studio/tsconfig.server.json +11 -0
- package/inkos/packages/studio/vite.config.ts +34 -0
- package/inkos/packages/studio/vitest.config.ts +14 -0
- package/inkos/pnpm-lock.yaml +9569 -0
- package/inkos/pnpm-workspace.yaml +2 -0
- package/inkos/scripts/prepare-package-for-publish.mjs +135 -0
- package/inkos/scripts/restore-package-json.mjs +31 -0
- package/inkos/scripts/set-package-versions.mjs +74 -0
- package/inkos/scripts/verify-no-workspace-protocol.mjs +140 -0
- package/inkos/skills/SKILL.md +654 -0
- package/inkos/tsconfig.json +19 -0
- package/package.json +4 -3
- /package/.next/static/{F2hMZMf1IyCVAWpkbtRz7 → -3vIrBZXdQ0rp7Wa3Kz40}/_buildManifest.js +0 -0
- /package/.next/static/{F2hMZMf1IyCVAWpkbtRz7 → -3vIrBZXdQ0rp7Wa3Kz40}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
composeCurrentArcProse,
|
|
5
|
+
extractCollaboratorRows,
|
|
6
|
+
extractOpponentRows,
|
|
7
|
+
extractProtagonistRow,
|
|
8
|
+
extractRelevantThreads,
|
|
9
|
+
} from "../agents/planner-context.js";
|
|
10
|
+
|
|
11
|
+
// Real column layouts match the production truth-file schemas under story/.
|
|
12
|
+
// Keeping these literal samples in tests guards against the kind of
|
|
13
|
+
// off-by-one column bug that slipped past Phase 3 review.
|
|
14
|
+
const EMOTIONAL_ARCS_SAMPLE = `
|
|
15
|
+
| 角色 | 章节 | 情绪状态 | 触发事件 | 强度(1-10) | 弧线方向 |
|
|
16
|
+
|------|------|----------|----------|-------------|----------|
|
|
17
|
+
| 周谨川 | 36 | 紧绷 | 门前对峙 | 8 | 升级 |
|
|
18
|
+
| 周谨川 | 37 | 克制发热 | 逼对方亮身份 | 9 | 升级 |
|
|
19
|
+
| 周谨川 | 38 | 骤亮后阴冷 | 看见顾明诚收 | 10 | 顶点 |
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const CHARACTER_MATRIX_SAMPLE = `
|
|
23
|
+
| 角色 | 核心标签 | 反差细节 | 说话风格 | 性格底色 | 与主角关系 | 核心动机 | 当前目标 |
|
|
24
|
+
|------|----------|----------|----------|----------|------------|----------|----------|
|
|
25
|
+
| 周谨川 | 带伤守证者 | 起不来身却硬顶 | 短、硬 | 克制耐痛 | 主角本人 | 不让资料被收走 | 守现场 |
|
|
26
|
+
| 草帽男 | 止损执行节点 | 明对手却急坏 | 装熟翻脸 | 控场欲 | 敌对监视者 | 护住七号门 | 顶住门边 |
|
|
27
|
+
| 修锁老师傅 | 一线手艺证人 | 不抢话却硬护场 | 平、稳 | 老练 | 临时协力 | 看锁说锁 | 守住现场事实 |
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const SUBPLOT_BOARD_SAMPLE = `
|
|
31
|
+
| S001 | 三担保排查 | 主角 | ch1 | ch38 | 0 | 推进 | 核心旧账线 | 1章 |
|
|
32
|
+
| S007 | 货款线 | 主角 | ch3 | ch4 | 34 | 暂挂 | 冻结 | 4-6章 |
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
describe("composeCurrentArcProse reads chapter from emotional_arcs column 1", () => {
|
|
36
|
+
it("filters rows by chapter from column index 1 (章节), not column 0 (角色)", () => {
|
|
37
|
+
// Previously the function filtered where row[0] matched /^\d+$/.
|
|
38
|
+
// Since row[0] is "周谨川" here, the old predicate produced zero matches
|
|
39
|
+
// and "近期情感线" fell out of the composed prose entirely.
|
|
40
|
+
const prose = composeCurrentArcProse(
|
|
41
|
+
SUBPLOT_BOARD_SAMPLE,
|
|
42
|
+
EMOTIONAL_ARCS_SAMPLE,
|
|
43
|
+
39,
|
|
44
|
+
);
|
|
45
|
+
expect(prose).toContain("近期情感线");
|
|
46
|
+
expect(prose).toContain("紧绷");
|
|
47
|
+
expect(prose).toContain("克制发热");
|
|
48
|
+
expect(prose).toContain("骤亮后阴冷");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("excludes rows at or beyond the current chapter", () => {
|
|
52
|
+
const prose = composeCurrentArcProse(
|
|
53
|
+
SUBPLOT_BOARD_SAMPLE,
|
|
54
|
+
EMOTIONAL_ARCS_SAMPLE,
|
|
55
|
+
37,
|
|
56
|
+
);
|
|
57
|
+
expect(prose).toContain("紧绷");
|
|
58
|
+
expect(prose).not.toContain("克制发热");
|
|
59
|
+
expect(prose).not.toContain("骤亮后阴冷");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("still composes active subplots when emotional arcs are empty", () => {
|
|
63
|
+
const prose = composeCurrentArcProse(SUBPLOT_BOARD_SAMPLE, "", 39);
|
|
64
|
+
expect(prose).toContain("活跃支线");
|
|
65
|
+
expect(prose).toContain("S001");
|
|
66
|
+
expect(prose).not.toContain("S007");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("returns the empty-state sentinel only when nothing is extractable", () => {
|
|
70
|
+
const prose = composeCurrentArcProse("", "", 1);
|
|
71
|
+
expect(prose).toContain("暂无 arc 数据");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("extractProtagonistRow", () => {
|
|
76
|
+
it("matches the real convention '主角本人', not only the exact token '主角'", () => {
|
|
77
|
+
const row = extractProtagonistRow(CHARACTER_MATRIX_SAMPLE);
|
|
78
|
+
expect(row).toContain("周谨川");
|
|
79
|
+
expect(row).toContain("主角本人");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("falls back to the first data row when no protagonist marker is found", () => {
|
|
83
|
+
const noMarker = `
|
|
84
|
+
| 角色 | 核心标签 | 与主角关系 |
|
|
85
|
+
|------|----------|------------|
|
|
86
|
+
| 李沉 | 重生者 | — |
|
|
87
|
+
| 王一 | 对手 | 敌对 |
|
|
88
|
+
`;
|
|
89
|
+
const row = extractProtagonistRow(noMarker);
|
|
90
|
+
expect(row).toContain("李沉");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("returns the sentinel only when the matrix has zero data rows", () => {
|
|
94
|
+
const empty = `
|
|
95
|
+
| 角色 | 核心标签 |
|
|
96
|
+
|------|----------|
|
|
97
|
+
`;
|
|
98
|
+
const row = extractProtagonistRow(empty);
|
|
99
|
+
expect(row).toContain("未找到主角行");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("extractOpponentRows / extractCollaboratorRows", () => {
|
|
104
|
+
it("picks opponents by 与主角关系 semantic keywords", () => {
|
|
105
|
+
const rows = extractOpponentRows(CHARACTER_MATRIX_SAMPLE, 3);
|
|
106
|
+
expect(rows).toContain("草帽男");
|
|
107
|
+
expect(rows).not.toContain("周谨川");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("picks collaborators by 与主角关系 semantic keywords", () => {
|
|
111
|
+
const rows = extractCollaboratorRows(CHARACTER_MATRIX_SAMPLE, 3);
|
|
112
|
+
expect(rows).toContain("修锁老师傅");
|
|
113
|
+
expect(rows).not.toContain("草帽男");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("extractRelevantThreads", () => {
|
|
118
|
+
it("selects active hooks and subplots, filtering dormant/stale", () => {
|
|
119
|
+
const hooks = `
|
|
120
|
+
| hook_id | 状态 | 最近推进 |
|
|
121
|
+
|---------|------|----------|
|
|
122
|
+
| H001 | activating | ch38 |
|
|
123
|
+
| H002 | dormant | ch20 |
|
|
124
|
+
| H003 | partial_payoff | ch37 |
|
|
125
|
+
`;
|
|
126
|
+
const subplots = `
|
|
127
|
+
| S001 | 主线追查 | 推进 |
|
|
128
|
+
| S007 | 旁线 | 暂挂 |
|
|
129
|
+
`;
|
|
130
|
+
const threads = extractRelevantThreads(hooks, subplots);
|
|
131
|
+
expect(threads).toContain("H001");
|
|
132
|
+
expect(threads).toContain("H003");
|
|
133
|
+
expect(threads).not.toContain("H002");
|
|
134
|
+
expect(threads).toContain("S001");
|
|
135
|
+
expect(threads).not.toContain("S007");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { PLANNER_MEMO_SYSTEM_PROMPT, PLANNER_MEMO_SYSTEM_PROMPT_EN } from "../agents/planner-prompts.js";
|
|
3
|
+
|
|
4
|
+
describe("planner prompt line-ratio handling", () => {
|
|
5
|
+
it("requires user-specified plot proportions to become visible chapter beats", () => {
|
|
6
|
+
expect(PLANNER_MEMO_SYSTEM_PROMPT).toContain("用户设定的内容比例必须落成场面");
|
|
7
|
+
expect(PLANNER_MEMO_SYSTEM_PROMPT).toContain("权谋/感情各半");
|
|
8
|
+
expect(PLANNER_MEMO_SYSTEM_PROMPT_EN).toContain("User-specified content proportions must become scenes");
|
|
9
|
+
expect(PLANNER_MEMO_SYSTEM_PROMPT_EN).toContain("politics 50% / romance 50%");
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
PLANNER_MEMO_SYSTEM_PROMPT,
|
|
4
|
+
PLANNER_MEMO_USER_TEMPLATE,
|
|
5
|
+
buildPlannerUserMessage,
|
|
6
|
+
buildGoldenOpeningGuidance,
|
|
7
|
+
} from "../agents/planner-prompts.js";
|
|
8
|
+
|
|
9
|
+
describe("PLANNER_MEMO_SYSTEM_PROMPT", () => {
|
|
10
|
+
it("contains key methodology phrases from new.txt", () => {
|
|
11
|
+
expect(PLANNER_MEMO_SYSTEM_PROMPT).toContain("1 主线 + 1 支线");
|
|
12
|
+
expect(PLANNER_MEMO_SYSTEM_PROMPT).toContain("三连问");
|
|
13
|
+
expect(PLANNER_MEMO_SYSTEM_PROMPT).toContain("YAML frontmatter");
|
|
14
|
+
expect(PLANNER_MEMO_SYSTEM_PROMPT).toContain("goal 字段不超过 50 字");
|
|
15
|
+
expect(PLANNER_MEMO_SYSTEM_PROMPT).toContain("## 当前任务");
|
|
16
|
+
expect(PLANNER_MEMO_SYSTEM_PROMPT).toContain("## 不要做");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("is not accidentally empty", () => {
|
|
20
|
+
expect(PLANNER_MEMO_SYSTEM_PROMPT.length).toBeGreaterThan(500);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("PLANNER_MEMO_USER_TEMPLATE", () => {
|
|
25
|
+
it("contains all placeholders", () => {
|
|
26
|
+
const placeholders = [
|
|
27
|
+
"{{chapterNumber}}",
|
|
28
|
+
"{{previous_chapter_ending_excerpt}}",
|
|
29
|
+
"{{recent_summaries}}",
|
|
30
|
+
"{{current_arc_prose}}",
|
|
31
|
+
"{{protagonist_matrix_row}}",
|
|
32
|
+
"{{opponent_rows}}",
|
|
33
|
+
"{{collaborator_rows}}",
|
|
34
|
+
"{{relevant_threads}}",
|
|
35
|
+
"{{recyclable_hooks}}",
|
|
36
|
+
"{{isGoldenOpening}}",
|
|
37
|
+
"{{book_rules_relevant}}",
|
|
38
|
+
];
|
|
39
|
+
for (const ph of placeholders) {
|
|
40
|
+
expect(PLANNER_MEMO_USER_TEMPLATE).toContain(ph);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("buildPlannerUserMessage", () => {
|
|
46
|
+
it("fills placeholders in order", () => {
|
|
47
|
+
const out = buildPlannerUserMessage({
|
|
48
|
+
chapterNumber: 12,
|
|
49
|
+
previousChapterEndingExcerpt: "上一屏结尾原文",
|
|
50
|
+
recentSummaries: "| ch9 | ... |",
|
|
51
|
+
currentArcProse: "主线推进七号门",
|
|
52
|
+
protagonistMatrixRow: "| 阿泽 | 主角 | ... |",
|
|
53
|
+
opponentRows: "| 老李 | 对手 | ... |",
|
|
54
|
+
collaboratorRows: "| 小白 | 盟友 | ... |",
|
|
55
|
+
relevantThreads: "- H03: 未解码信\n- S004: 七号门异常",
|
|
56
|
+
recyclableHooks: "(暂无陈旧 hook——账本干净)",
|
|
57
|
+
isGoldenOpening: false,
|
|
58
|
+
bookRulesRelevant: "- 禁止主角降智",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(out).toContain("# 第 12 章 memo 请求");
|
|
62
|
+
expect(out).toContain("上一屏结尾原文");
|
|
63
|
+
expect(out).toContain("| ch9 | ... |");
|
|
64
|
+
expect(out).toContain("主线推进七号门");
|
|
65
|
+
expect(out).toContain("| 阿泽 | 主角 | ... |");
|
|
66
|
+
expect(out).toContain("| 老李 | 对手 | ... |");
|
|
67
|
+
expect(out).toContain("| 小白 | 盟友 | ... |");
|
|
68
|
+
expect(out).toContain("- H03: 未解码信");
|
|
69
|
+
expect(out).toContain("是否黄金三章:否");
|
|
70
|
+
expect(out).toContain("- 禁止主角降智");
|
|
71
|
+
expect(out).not.toContain("{{");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("translates isGoldenOpening true to 是", () => {
|
|
75
|
+
const out = buildPlannerUserMessage({
|
|
76
|
+
chapterNumber: 1,
|
|
77
|
+
previousChapterEndingExcerpt: "",
|
|
78
|
+
recentSummaries: "",
|
|
79
|
+
currentArcProse: "",
|
|
80
|
+
protagonistMatrixRow: "",
|
|
81
|
+
opponentRows: "",
|
|
82
|
+
collaboratorRows: "",
|
|
83
|
+
relevantThreads: "",
|
|
84
|
+
recyclableHooks: "",
|
|
85
|
+
isGoldenOpening: true,
|
|
86
|
+
bookRulesRelevant: "",
|
|
87
|
+
});
|
|
88
|
+
expect(out).toContain("是否黄金三章:是");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Phase 6.5 — Golden Opening Guidance prose
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
describe("buildGoldenOpeningGuidance", () => {
|
|
97
|
+
it("emits zh slot prose for chapter 1 (confront core conflict)", () => {
|
|
98
|
+
const out = buildGoldenOpeningGuidance(1, "zh");
|
|
99
|
+
expect(out).toContain("黄金三章规划指引");
|
|
100
|
+
expect(out).toContain("第 1 章");
|
|
101
|
+
// Ch1 slot: throw protagonist into core conflict
|
|
102
|
+
expect(out).toContain("核心冲突");
|
|
103
|
+
expect(out).toContain("主角出场即面对主线矛盾");
|
|
104
|
+
// Opening economy
|
|
105
|
+
expect(out).toContain("场景 ≤ 3");
|
|
106
|
+
expect(out).toContain("人物 ≤ 3");
|
|
107
|
+
// Information layering
|
|
108
|
+
expect(out).toContain("信息分层");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("emits zh slot prose for chapter 2 (demonstrate the edge)", () => {
|
|
112
|
+
const out = buildGoldenOpeningGuidance(2, "zh");
|
|
113
|
+
expect(out).toContain("第 2 章");
|
|
114
|
+
expect(out).toContain("金手指");
|
|
115
|
+
// Must demand a concrete event, not narration
|
|
116
|
+
expect(out).toContain("一次具体事件");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("emits zh slot prose for chapter 3 (lock the short-term goal)", () => {
|
|
120
|
+
const out = buildGoldenOpeningGuidance(3, "zh");
|
|
121
|
+
expect(out).toContain("第 3 章");
|
|
122
|
+
expect(out).toContain("短期目标");
|
|
123
|
+
expect(out).toContain("3-10 章");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("emits en slot prose for chapter 1 with all three slot descriptions", () => {
|
|
127
|
+
const out = buildGoldenOpeningGuidance(1, "en");
|
|
128
|
+
expect(out).toContain("Golden Opening Guidance");
|
|
129
|
+
expect(out).toContain("Chapter 1");
|
|
130
|
+
expect(out).toContain("core conflict");
|
|
131
|
+
expect(out).toContain("concrete event");
|
|
132
|
+
expect(out).toContain("short-term goal");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("returns empty string for ch>=4 in both languages", () => {
|
|
136
|
+
expect(buildGoldenOpeningGuidance(4, "zh")).toBe("");
|
|
137
|
+
expect(buildGoldenOpeningGuidance(5, "zh")).toBe("");
|
|
138
|
+
expect(buildGoldenOpeningGuidance(4, "en")).toBe("");
|
|
139
|
+
expect(buildGoldenOpeningGuidance(99, "en")).toBe("");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("renders as cohesive prose, not a numbered or bulleted checklist", () => {
|
|
143
|
+
const zh = buildGoldenOpeningGuidance(1, "zh");
|
|
144
|
+
// Heading is allowed; body must not contain enumerated lines.
|
|
145
|
+
expect(zh).not.toMatch(/^\s*1\.\s/m);
|
|
146
|
+
expect(zh).not.toMatch(/^\s*-\s/m);
|
|
147
|
+
expect(zh).not.toMatch(/^\s*\*\s/m);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("buildPlannerUserMessage appends guidance for ch<=3 and omits it for ch>=4", () => {
|
|
151
|
+
const base = {
|
|
152
|
+
previousChapterEndingExcerpt: "",
|
|
153
|
+
recentSummaries: "",
|
|
154
|
+
currentArcProse: "",
|
|
155
|
+
protagonistMatrixRow: "",
|
|
156
|
+
opponentRows: "",
|
|
157
|
+
collaboratorRows: "",
|
|
158
|
+
relevantThreads: "",
|
|
159
|
+
recyclableHooks: "",
|
|
160
|
+
isGoldenOpening: false,
|
|
161
|
+
bookRulesRelevant: "",
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const ch2 = buildPlannerUserMessage({ ...base, chapterNumber: 2 });
|
|
165
|
+
expect(ch2).toContain("黄金三章规划指引");
|
|
166
|
+
expect(ch2).toContain("第 2 章");
|
|
167
|
+
|
|
168
|
+
const ch4 = buildPlannerUserMessage({ ...base, chapterNumber: 4 });
|
|
169
|
+
expect(ch4).not.toContain("黄金三章规划指引");
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { PlannerAgent } from "../agents/planner.js";
|
|
6
|
+
import * as llmProvider from "../llm/provider.js";
|
|
7
|
+
import type { LLMClient } from "../llm/provider.js";
|
|
8
|
+
import type { BookConfig } from "../models/book.js";
|
|
9
|
+
import { PlannerParseError } from "../utils/chapter-memo-parser.js";
|
|
10
|
+
|
|
11
|
+
const VALID_BODY = `
|
|
12
|
+
## 当前任务
|
|
13
|
+
主角进入七号门现场,比对锁芯刮痕与监控时间线,把"被动过手脚"从猜测钉成实证。
|
|
14
|
+
|
|
15
|
+
## 读者此刻在等什么
|
|
16
|
+
1) 读者在等七号门是否有异常实锤
|
|
17
|
+
2) 本章完全兑现,钉成现场实证
|
|
18
|
+
|
|
19
|
+
## 该兑现的 / 暂不掀的
|
|
20
|
+
- 该兑现:七号门异常 → 钉成现场实证
|
|
21
|
+
- 暂不掀:幕后主使 → 压到第 20 章
|
|
22
|
+
|
|
23
|
+
## 日常/过渡承担什么任务
|
|
24
|
+
不适用 - 本章为高压实证章,无日常过渡段。
|
|
25
|
+
|
|
26
|
+
## 关键抉择过三连问
|
|
27
|
+
- 主角本章最关键的一次选择:
|
|
28
|
+
- 为什么这么做?线索只剩这一条
|
|
29
|
+
- 符合当前利益吗?符合
|
|
30
|
+
- 符合他的人设吗?符合
|
|
31
|
+
- 对手/配角本章最关键的一次选择:
|
|
32
|
+
- 为什么这么做?掩盖踪迹
|
|
33
|
+
- 符合当前利益吗?符合
|
|
34
|
+
- 符合他的人设吗?符合
|
|
35
|
+
|
|
36
|
+
## 章尾必须发生的改变
|
|
37
|
+
- 信息改变:主角掌握实证,可以面对幕后主使前先压住对手的退路
|
|
38
|
+
|
|
39
|
+
## 本章 hook 账
|
|
40
|
+
advance:
|
|
41
|
+
- H03 "七号门异常" → 从 pressured → near_payoff(本章钉成实证)
|
|
42
|
+
resolve:
|
|
43
|
+
- S004 "锁芯刮痕" → 核验完毕,本章结清
|
|
44
|
+
defer:
|
|
45
|
+
- H07 "幕后主使" → 第 20 章再动
|
|
46
|
+
|
|
47
|
+
## 不要做
|
|
48
|
+
- 不要让对手突然降智
|
|
49
|
+
- 不要直接点破幕后主使
|
|
50
|
+
`.trim();
|
|
51
|
+
|
|
52
|
+
function validMemoRaw(chapter: number): string {
|
|
53
|
+
return `---\nchapter: ${chapter}\ngoal: 把七号门被动过手脚钉成现场实证\nisGoldenOpening: false\nthreadRefs:\n - H03\n - S004\n---\n${VALID_BODY}\n`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ZERO_USAGE = {
|
|
57
|
+
promptTokens: 0,
|
|
58
|
+
completionTokens: 0,
|
|
59
|
+
totalTokens: 0,
|
|
60
|
+
} as const;
|
|
61
|
+
|
|
62
|
+
const STUB_CLIENT: LLMClient = {
|
|
63
|
+
provider: "openai",
|
|
64
|
+
apiFormat: "chat",
|
|
65
|
+
stream: false,
|
|
66
|
+
defaults: { temperature: 0.7, maxTokens: 2048, thinkingBudget: 0, maxTokensCap: null, extra: {} },
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function makeBook(): BookConfig {
|
|
70
|
+
return {
|
|
71
|
+
id: "book-plan-1",
|
|
72
|
+
title: "Test Book",
|
|
73
|
+
genre: "urban",
|
|
74
|
+
platform: "qidian",
|
|
75
|
+
status: "active",
|
|
76
|
+
language: "zh",
|
|
77
|
+
targetChapters: 120,
|
|
78
|
+
chapterWordCount: 3000,
|
|
79
|
+
createdAt: "2026-04-01T00:00:00.000Z",
|
|
80
|
+
updatedAt: "2026-04-01T00:00:00.000Z",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function seedStoryFiles(bookDir: string): Promise<void> {
|
|
85
|
+
const storyDir = join(bookDir, "story");
|
|
86
|
+
await mkdir(storyDir, { recursive: true });
|
|
87
|
+
await Promise.all([
|
|
88
|
+
writeFile(join(storyDir, "author_intent.md"), "# Intent\n- Tell a taut mystery.", "utf-8"),
|
|
89
|
+
writeFile(join(storyDir, "current_focus.md"), "# Focus\n- Keep pressure on the seventh gate.", "utf-8"),
|
|
90
|
+
writeFile(join(storyDir, "story_bible.md"), "# Bible\n- Protagonist: 阿泽", "utf-8"),
|
|
91
|
+
writeFile(join(storyDir, "volume_outline.md"), "# Outline\n- 第 1 章:开场", "utf-8"),
|
|
92
|
+
writeFile(join(storyDir, "chapter_summaries.md"), "# Summaries\n", "utf-8"),
|
|
93
|
+
writeFile(join(storyDir, "book_rules.md"), "# Rules\n- 禁止反派降智", "utf-8"),
|
|
94
|
+
writeFile(join(storyDir, "current_state.md"), "# State\n- 主角在七号门附近", "utf-8"),
|
|
95
|
+
writeFile(join(storyDir, "pending_hooks.md"), "# Hooks\n", "utf-8"),
|
|
96
|
+
writeFile(join(storyDir, "subplot_board.md"), "# Subplot\n", "utf-8"),
|
|
97
|
+
writeFile(join(storyDir, "emotional_arcs.md"), "# Arcs\n", "utf-8"),
|
|
98
|
+
writeFile(join(storyDir, "character_matrix.md"), "# Matrix\n", "utf-8"),
|
|
99
|
+
]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
describe("PlannerAgent.planChapter memo generation", () => {
|
|
103
|
+
let root: string;
|
|
104
|
+
let bookDir: string;
|
|
105
|
+
|
|
106
|
+
beforeEach(async () => {
|
|
107
|
+
root = await mkdtemp(join(tmpdir(), "planner-memo-"));
|
|
108
|
+
bookDir = join(root, "book");
|
|
109
|
+
await seedStoryFiles(bookDir);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
afterEach(async () => {
|
|
113
|
+
vi.restoreAllMocks();
|
|
114
|
+
await rm(root, { recursive: true, force: true });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
function makePlanner(): PlannerAgent {
|
|
118
|
+
return new PlannerAgent({
|
|
119
|
+
client: STUB_CLIENT,
|
|
120
|
+
model: "test-model",
|
|
121
|
+
projectRoot: root,
|
|
122
|
+
bookId: "book-plan-1",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
it("produces a valid ChapterMemo when the LLM returns well-formed output", async () => {
|
|
127
|
+
const chatSpy = vi.spyOn(llmProvider, "chatCompletion").mockResolvedValue({
|
|
128
|
+
content: validMemoRaw(1),
|
|
129
|
+
usage: ZERO_USAGE,
|
|
130
|
+
} as unknown as Awaited<ReturnType<typeof llmProvider.chatCompletion>>);
|
|
131
|
+
|
|
132
|
+
const result = await makePlanner().planChapter({
|
|
133
|
+
book: makeBook(),
|
|
134
|
+
bookDir,
|
|
135
|
+
chapterNumber: 1,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(chatSpy).toHaveBeenCalledTimes(1);
|
|
139
|
+
expect(result.memo.chapter).toBe(1);
|
|
140
|
+
expect(result.memo.isGoldenOpening).toBe(true); // ch1 zh → golden opening, authoritative over LLM
|
|
141
|
+
expect(result.memo.goal).toBe("把七号门被动过手脚钉成现场实证");
|
|
142
|
+
expect(result.memo.threadRefs).toEqual(["H03", "S004"]);
|
|
143
|
+
expect(result.memo.body).toContain("## 当前任务");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("does not hard-cap memo generation below the configured model output budget", async () => {
|
|
147
|
+
const chatSpy = vi.spyOn(llmProvider, "chatCompletion").mockResolvedValue({
|
|
148
|
+
content: validMemoRaw(1),
|
|
149
|
+
usage: ZERO_USAGE,
|
|
150
|
+
} as unknown as Awaited<ReturnType<typeof llmProvider.chatCompletion>>);
|
|
151
|
+
|
|
152
|
+
await makePlanner().planChapter({
|
|
153
|
+
book: makeBook(),
|
|
154
|
+
bookDir,
|
|
155
|
+
chapterNumber: 1,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const callArgs = chatSpy.mock.calls[0]!;
|
|
159
|
+
const options = callArgs[3] as { temperature?: number; maxTokens?: number } | undefined;
|
|
160
|
+
expect(options).toEqual(expect.objectContaining({ temperature: 0.7 }));
|
|
161
|
+
expect(options).not.toHaveProperty("maxTokens");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("passes per-chapter user context into the memo prompt as a high-priority instruction", async () => {
|
|
165
|
+
const chatSpy = vi.spyOn(llmProvider, "chatCompletion").mockResolvedValue({
|
|
166
|
+
content: validMemoRaw(1),
|
|
167
|
+
usage: ZERO_USAGE,
|
|
168
|
+
} as unknown as Awaited<ReturnType<typeof llmProvider.chatCompletion>>);
|
|
169
|
+
|
|
170
|
+
await makePlanner().planChapter({
|
|
171
|
+
book: makeBook(),
|
|
172
|
+
bookDir,
|
|
173
|
+
chapterNumber: 1,
|
|
174
|
+
externalContext: "本章标题:雨夜账本\n必须围绕账本失窃后的当面对质展开。",
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const callArgs = chatSpy.mock.calls[0]!;
|
|
178
|
+
const messages = callArgs[2] as ReadonlyArray<{ role: string; content: string }>;
|
|
179
|
+
const userMsg = messages.find((m) => m.role === "user");
|
|
180
|
+
expect(userMsg?.content).toContain("本章用户指令");
|
|
181
|
+
expect(userMsg?.content).toContain("本章标题:雨夜账本");
|
|
182
|
+
expect(userMsg?.content).toContain("当面对质");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("retries when the first response is malformed and succeeds on retry", async () => {
|
|
186
|
+
const chatSpy = vi.spyOn(llmProvider, "chatCompletion")
|
|
187
|
+
.mockResolvedValueOnce({
|
|
188
|
+
content: "no frontmatter here",
|
|
189
|
+
usage: ZERO_USAGE,
|
|
190
|
+
} as unknown as Awaited<ReturnType<typeof llmProvider.chatCompletion>>)
|
|
191
|
+
.mockResolvedValueOnce({
|
|
192
|
+
content: "still no frontmatter",
|
|
193
|
+
usage: ZERO_USAGE,
|
|
194
|
+
} as unknown as Awaited<ReturnType<typeof llmProvider.chatCompletion>>)
|
|
195
|
+
.mockResolvedValueOnce({
|
|
196
|
+
content: validMemoRaw(4),
|
|
197
|
+
usage: ZERO_USAGE,
|
|
198
|
+
} as unknown as Awaited<ReturnType<typeof llmProvider.chatCompletion>>);
|
|
199
|
+
|
|
200
|
+
const result = await makePlanner().planChapter({
|
|
201
|
+
book: makeBook(),
|
|
202
|
+
bookDir,
|
|
203
|
+
chapterNumber: 4,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(chatSpy).toHaveBeenCalledTimes(3);
|
|
207
|
+
expect(result.memo.chapter).toBe(4);
|
|
208
|
+
expect(result.memo.isGoldenOpening).toBe(false);
|
|
209
|
+
|
|
210
|
+
// Retry prompts must include the failure feedback
|
|
211
|
+
const secondCallArgs = chatSpy.mock.calls[1]!;
|
|
212
|
+
const secondMessages = secondCallArgs[2] as ReadonlyArray<{ role: string; content: string }>;
|
|
213
|
+
const userMsg = secondMessages.find((m) => m.role === "user");
|
|
214
|
+
expect(userMsg?.content).toContain("上次输出的错误");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Phase hotfix 4: English books must receive English system + user prompts
|
|
218
|
+
// and English golden-opening guidance for chapters ≤ 3.
|
|
219
|
+
it("uses English prompts end-to-end when book.language is en", async () => {
|
|
220
|
+
const VALID_EN_BODY = `
|
|
221
|
+
## Current task
|
|
222
|
+
Pin the Door 7 tampering from suspicion to live evidence.
|
|
223
|
+
|
|
224
|
+
## What the reader is waiting for right now
|
|
225
|
+
1) Reader expects to learn whether Door 7 is really compromised.
|
|
226
|
+
2) This chapter pays it off in full — live evidence on stage.
|
|
227
|
+
|
|
228
|
+
## To pay off / to keep buried
|
|
229
|
+
- Pay off: Door 7 anomaly → live evidence
|
|
230
|
+
- Keep buried: the mastermind → push to chapter 20
|
|
231
|
+
|
|
232
|
+
## What the slow / transitional beats carry
|
|
233
|
+
n/a — pressure chapter, no transitional beats.
|
|
234
|
+
|
|
235
|
+
## Three-question check on the key choice
|
|
236
|
+
- Protagonist's most important choice this chapter:
|
|
237
|
+
- Why this choice? It is the only remaining lead.
|
|
238
|
+
- Does it match current interest? Yes.
|
|
239
|
+
- Does it match their persona? Yes.
|
|
240
|
+
- Antagonist / supporting cast's most important choice this chapter:
|
|
241
|
+
- Why this choice? To cover their tracks.
|
|
242
|
+
- Does it match current interest? Yes.
|
|
243
|
+
- Does it match their persona? Yes.
|
|
244
|
+
|
|
245
|
+
## Required end-of-chapter change
|
|
246
|
+
- Information change: protagonist holds live evidence.
|
|
247
|
+
|
|
248
|
+
## Hook ledger for this chapter
|
|
249
|
+
advance:
|
|
250
|
+
- H03 "Door 7 anomaly" → pressured → near_payoff (pinned as live evidence this chapter)
|
|
251
|
+
defer:
|
|
252
|
+
- H07 "the mastermind" → hold until chapter 20
|
|
253
|
+
|
|
254
|
+
## Do not
|
|
255
|
+
- Do not let the antagonist suddenly turn dumb.
|
|
256
|
+
- Do not directly name the mastermind.
|
|
257
|
+
`.trim();
|
|
258
|
+
|
|
259
|
+
const validEnRaw = `---\nchapter: 1\ngoal: Pin Door 7 tampering as live evidence\nisGoldenOpening: false\nthreadRefs:\n - H03\n---\n${VALID_EN_BODY}\n`;
|
|
260
|
+
|
|
261
|
+
const chatSpy = vi.spyOn(llmProvider, "chatCompletion").mockResolvedValue({
|
|
262
|
+
content: validEnRaw,
|
|
263
|
+
usage: ZERO_USAGE,
|
|
264
|
+
} as unknown as Awaited<ReturnType<typeof llmProvider.chatCompletion>>);
|
|
265
|
+
|
|
266
|
+
const enBook = { ...makeBook(), language: "en" as const };
|
|
267
|
+
const result = await makePlanner().planChapter({
|
|
268
|
+
book: enBook,
|
|
269
|
+
bookDir,
|
|
270
|
+
chapterNumber: 1,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
expect(chatSpy).toHaveBeenCalledTimes(1);
|
|
274
|
+
expect(result.memo.chapter).toBe(1);
|
|
275
|
+
expect(result.memo.isGoldenOpening).toBe(true); // ch1 en → also golden (≤5)
|
|
276
|
+
|
|
277
|
+
// System prompt must be the English variant
|
|
278
|
+
const callArgs = chatSpy.mock.calls[0]!;
|
|
279
|
+
const messages = callArgs[2] as ReadonlyArray<{ role: string; content: string }>;
|
|
280
|
+
const systemMsg = messages.find((m) => m.role === "system");
|
|
281
|
+
const userMsg = messages.find((m) => m.role === "user");
|
|
282
|
+
|
|
283
|
+
// English system prompt markers
|
|
284
|
+
expect(systemMsg?.content).toContain("editor-in-chief");
|
|
285
|
+
expect(systemMsg?.content).toContain("Output format (strict)");
|
|
286
|
+
expect(systemMsg?.content).not.toContain("你是这本小说的创作总编");
|
|
287
|
+
|
|
288
|
+
// English user template markers
|
|
289
|
+
expect(userMsg?.content).toContain("# Chapter 1 memo request");
|
|
290
|
+
expect(userMsg?.content).toContain("Last screen of previous chapter");
|
|
291
|
+
expect(userMsg?.content).toContain("Golden opening chapter: yes");
|
|
292
|
+
expect(userMsg?.content).not.toContain("# 第 1 章 memo 请求");
|
|
293
|
+
|
|
294
|
+
// English golden-opening guidance appended for ch ≤ 3
|
|
295
|
+
expect(userMsg?.content).toContain("Golden Opening Guidance");
|
|
296
|
+
expect(userMsg?.content).toContain("Chapter 1");
|
|
297
|
+
expect(userMsg?.content).not.toContain("黄金三章规划指引");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("throws PlannerParseError when all 3 attempts fail", async () => {
|
|
301
|
+
vi.spyOn(llmProvider, "chatCompletion").mockResolvedValue({
|
|
302
|
+
content: "permanently broken",
|
|
303
|
+
usage: ZERO_USAGE,
|
|
304
|
+
} as unknown as Awaited<ReturnType<typeof llmProvider.chatCompletion>>);
|
|
305
|
+
|
|
306
|
+
await expect(
|
|
307
|
+
makePlanner().planChapter({
|
|
308
|
+
book: makeBook(),
|
|
309
|
+
bookDir,
|
|
310
|
+
chapterNumber: 2,
|
|
311
|
+
}),
|
|
312
|
+
).rejects.toBeInstanceOf(PlannerParseError);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Phase hotfix 5: planner.intent.mustAvoid must come from the Phase 5
|
|
316
|
+
// authoritative loader (story_frame frontmatter), not from raw
|
|
317
|
+
// book_rules.md — for new-layout books the legacy file is just a shim.
|
|
318
|
+
it("derives intent.mustAvoid from outline/story_frame.md frontmatter (new layout)", async () => {
|
|
319
|
+
// Replace book_rules.md with a Phase 5 compat shim (no YAML, just pointer)
|
|
320
|
+
// and put the authoritative YAML on outline/story_frame.md.
|
|
321
|
+
const storyDir = join(bookDir, "story");
|
|
322
|
+
await mkdir(join(storyDir, "outline"), { recursive: true });
|
|
323
|
+
await writeFile(
|
|
324
|
+
join(storyDir, "outline/story_frame.md"),
|
|
325
|
+
[
|
|
326
|
+
"---",
|
|
327
|
+
"version: \"1.0\"",
|
|
328
|
+
"protagonist:",
|
|
329
|
+
" name: 阿泽",
|
|
330
|
+
" personalityLock: []",
|
|
331
|
+
" behavioralConstraints: []",
|
|
332
|
+
"prohibitions:",
|
|
333
|
+
" - 禁止主角降智",
|
|
334
|
+
" - 禁止神化反派",
|
|
335
|
+
"---",
|
|
336
|
+
"",
|
|
337
|
+
"## 主题与基调",
|
|
338
|
+
"调查与压制。",
|
|
339
|
+
].join("\n"),
|
|
340
|
+
"utf-8",
|
|
341
|
+
);
|
|
342
|
+
await writeFile(
|
|
343
|
+
join(storyDir, "book_rules.md"),
|
|
344
|
+
"# 本书规则(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。",
|
|
345
|
+
"utf-8",
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
vi.spyOn(llmProvider, "chatCompletion").mockResolvedValue({
|
|
349
|
+
content: validMemoRaw(2),
|
|
350
|
+
usage: ZERO_USAGE,
|
|
351
|
+
} as unknown as Awaited<ReturnType<typeof llmProvider.chatCompletion>>);
|
|
352
|
+
|
|
353
|
+
const result = await makePlanner().planChapter({
|
|
354
|
+
book: makeBook(),
|
|
355
|
+
bookDir,
|
|
356
|
+
chapterNumber: 2,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
expect(result.intent.mustAvoid).toContain("禁止主角降智");
|
|
360
|
+
expect(result.intent.mustAvoid).toContain("禁止神化反派");
|
|
361
|
+
});
|
|
362
|
+
});
|