@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,322 @@
|
|
|
1
|
+
import { BaseAgent } from "./base.js";
|
|
2
|
+
|
|
3
|
+
export interface ValidationWarning {
|
|
4
|
+
readonly category: string;
|
|
5
|
+
readonly description: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ValidationResult {
|
|
9
|
+
readonly warnings: ReadonlyArray<ValidationWarning>;
|
|
10
|
+
readonly passed: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface StateValidationAuthorityContext {
|
|
14
|
+
readonly storyFrame?: string;
|
|
15
|
+
readonly bookRules?: string;
|
|
16
|
+
readonly chapterSummaries?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validates Settler output by comparing old and new truth files via LLM.
|
|
21
|
+
* Catches contradictions, missing state changes, and temporal inconsistencies.
|
|
22
|
+
*
|
|
23
|
+
* Uses a minimal verdict protocol instead of requiring structured JSON:
|
|
24
|
+
* Line 1: PASS or FAIL
|
|
25
|
+
* Remaining lines: free-form warnings (one per line, optional category prefix)
|
|
26
|
+
*/
|
|
27
|
+
export class StateValidatorAgent extends BaseAgent {
|
|
28
|
+
get name(): string {
|
|
29
|
+
return "state-validator";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async validate(
|
|
33
|
+
chapterContent: string,
|
|
34
|
+
chapterNumber: number,
|
|
35
|
+
oldState: string,
|
|
36
|
+
newState: string,
|
|
37
|
+
oldHooks: string,
|
|
38
|
+
newHooks: string,
|
|
39
|
+
language: "zh" | "en" = "zh",
|
|
40
|
+
authorityContext?: StateValidationAuthorityContext,
|
|
41
|
+
): Promise<ValidationResult> {
|
|
42
|
+
const stateDiff = this.computeDiff(oldState, newState, "State Card");
|
|
43
|
+
const hooksDiff = this.computeDiff(oldHooks, newHooks, "Hooks Pool");
|
|
44
|
+
|
|
45
|
+
// Skip validation if nothing changed
|
|
46
|
+
if (!stateDiff && !hooksDiff) {
|
|
47
|
+
return { warnings: [], passed: true };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const langInstruction = language === "en"
|
|
51
|
+
? "Respond in English."
|
|
52
|
+
: "用中文回答。";
|
|
53
|
+
|
|
54
|
+
const systemPrompt = `You are a continuity validator for a novel writing system. ${langInstruction}
|
|
55
|
+
|
|
56
|
+
Given the chapter text and the CHANGES made to truth files (state card + hooks pool), check for contradictions:
|
|
57
|
+
|
|
58
|
+
1. State change without narrative support — truth file says something changed but the chapter text doesn't describe it
|
|
59
|
+
2. Missing state change — chapter text describes something happening but the truth file didn't capture it
|
|
60
|
+
3. Temporal impossibility — character moves locations without transition, injury heals without time passing
|
|
61
|
+
4. Hook anomaly — a hook disappeared without being marked resolved, or a new hook has no basis in the chapter
|
|
62
|
+
5. Retroactive edit — truth file change implies something happened in a PREVIOUS chapter, not the current one
|
|
63
|
+
6. Cross-truth key-setting conflict — numbered rules, named laws, ranks, identities, locations, or relationship labels in the new truth files contradict the chapter text or the authority context
|
|
64
|
+
|
|
65
|
+
Output format (simple, NOT JSON):
|
|
66
|
+
- First line: exactly PASS or FAIL (nothing else on this line)
|
|
67
|
+
- Following lines: one warning per line, optionally prefixed with [category]
|
|
68
|
+
- If no issues at all, just output: PASS
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
PASS
|
|
72
|
+
[unsupported_change] State card says character moved to the forest, but text only shows intent
|
|
73
|
+
[minor] Hook H03 advanced but text mention is brief
|
|
74
|
+
|
|
75
|
+
Or if there are hard contradictions:
|
|
76
|
+
FAIL
|
|
77
|
+
[contradiction] State says character is dead but chapter text shows them speaking
|
|
78
|
+
[unsupported_change] New location not mentioned anywhere in chapter text
|
|
79
|
+
|
|
80
|
+
IMPORTANT: Output FAIL ONLY for hard contradictions — facts that directly conflict with the chapter text. Do NOT fail for:
|
|
81
|
+
- Slightly ahead-of-text inferences
|
|
82
|
+
- Missing details that the state card didn't capture
|
|
83
|
+
- Reasonable extrapolations from text
|
|
84
|
+
- Hook management differences that don't contradict text
|
|
85
|
+
These should be warnings with PASS, not FAIL.`;
|
|
86
|
+
|
|
87
|
+
const authorityBlock = this.buildAuthorityContextBlock(authorityContext);
|
|
88
|
+
|
|
89
|
+
const userPrompt = `Chapter ${chapterNumber} validation:
|
|
90
|
+
|
|
91
|
+
${authorityBlock}
|
|
92
|
+
|
|
93
|
+
## State Card Changes
|
|
94
|
+
${stateDiff || "(no changes)"}
|
|
95
|
+
|
|
96
|
+
## Hooks Pool Changes
|
|
97
|
+
${hooksDiff || "(no changes)"}
|
|
98
|
+
|
|
99
|
+
## Chapter Text (for reference)
|
|
100
|
+
${chapterContent.slice(0, 6000)}`;
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const response = await this.chat(
|
|
104
|
+
[
|
|
105
|
+
{ role: "system", content: systemPrompt },
|
|
106
|
+
{ role: "user", content: userPrompt },
|
|
107
|
+
],
|
|
108
|
+
{ temperature: 0.1 },
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
return this.parseResult(response.content);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
this.log?.warn(`State validation failed: ${error}`);
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private computeDiff(oldText: string, newText: string, label: string): string | null {
|
|
119
|
+
if (oldText === newText) return null;
|
|
120
|
+
|
|
121
|
+
const oldLines = oldText.split("\n").filter((l) => l.trim());
|
|
122
|
+
const newLines = newText.split("\n").filter((l) => l.trim());
|
|
123
|
+
|
|
124
|
+
const added = newLines.filter((l) => !oldLines.includes(l));
|
|
125
|
+
const removed = oldLines.filter((l) => !newLines.includes(l));
|
|
126
|
+
|
|
127
|
+
if (added.length === 0 && removed.length === 0) return null;
|
|
128
|
+
|
|
129
|
+
const parts = [`### ${label}`];
|
|
130
|
+
if (removed.length > 0) parts.push("Removed:\n" + removed.map((l) => `- ${l}`).join("\n"));
|
|
131
|
+
if (added.length > 0) parts.push("Added:\n" + added.map((l) => `+ ${l}`).join("\n"));
|
|
132
|
+
return parts.join("\n");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private buildAuthorityContextBlock(authorityContext?: StateValidationAuthorityContext): string {
|
|
136
|
+
if (!authorityContext) return "## Authority / Cross-Truth Context\n(no authority context provided)";
|
|
137
|
+
|
|
138
|
+
const storyFrame = this.truncateHead(authorityContext.storyFrame ?? "", 3500);
|
|
139
|
+
const bookRules = this.truncateHead(authorityContext.bookRules ?? "", 2000);
|
|
140
|
+
const chapterSummaries = this.truncateTail(authorityContext.chapterSummaries ?? "", 3500);
|
|
141
|
+
|
|
142
|
+
return [
|
|
143
|
+
"## Authority / Cross-Truth Context",
|
|
144
|
+
"Authority priority: current chapter text > runtime truth files/current summaries > story_frame/book_rules > legacy story_bible intro or marketing-style prose. If the current chapter establishes a numbered/name mapping, new truth files must follow that mapping instead of preserving an older intro-only version.",
|
|
145
|
+
"",
|
|
146
|
+
"### story_frame / legacy story_bible excerpt",
|
|
147
|
+
storyFrame || "(empty)",
|
|
148
|
+
"",
|
|
149
|
+
"### book_rules excerpt",
|
|
150
|
+
bookRules || "(empty)",
|
|
151
|
+
"",
|
|
152
|
+
"### recent chapter_summaries excerpt",
|
|
153
|
+
chapterSummaries || "(empty)",
|
|
154
|
+
].join("\n");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private truncateHead(text: string, maxChars: number): string {
|
|
158
|
+
const trimmed = text.trim();
|
|
159
|
+
if (trimmed.length <= maxChars) return trimmed;
|
|
160
|
+
return `${trimmed.slice(0, maxChars).trimEnd()}\n\n[...truncated...]`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private truncateTail(text: string, maxChars: number): string {
|
|
164
|
+
const trimmed = text.trim();
|
|
165
|
+
if (trimmed.length <= maxChars) return trimmed;
|
|
166
|
+
return `[...truncated...]\n\n${trimmed.slice(-maxChars).trimStart()}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private parseResult(content: string): ValidationResult {
|
|
170
|
+
const trimmed = content.trim();
|
|
171
|
+
if (!trimmed) {
|
|
172
|
+
throw new Error("LLM returned empty response");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const jsonResult = this.tryParseJsonResult(trimmed);
|
|
176
|
+
if (jsonResult) {
|
|
177
|
+
return jsonResult;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const lines = trimmed.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
181
|
+
if (lines.length === 0) {
|
|
182
|
+
throw new Error("LLM returned empty response");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const verdictLine = lines[0]!;
|
|
186
|
+
if (!/^(PASS|FAIL)$/i.test(verdictLine)) {
|
|
187
|
+
throw new Error("State validator returned invalid response");
|
|
188
|
+
}
|
|
189
|
+
const passed = /^PASS$/i.test(verdictLine);
|
|
190
|
+
|
|
191
|
+
const warnings: ValidationWarning[] = [];
|
|
192
|
+
for (let i = 1; i < lines.length; i++) {
|
|
193
|
+
const line = lines[i]!;
|
|
194
|
+
if (/^(PASS|FAIL)$/i.test(line)) continue;
|
|
195
|
+
|
|
196
|
+
const categoryMatch = line.match(/^\[([^\]]+)\]\s*(.+)$/);
|
|
197
|
+
if (categoryMatch) {
|
|
198
|
+
warnings.push({
|
|
199
|
+
category: categoryMatch[1]!.trim(),
|
|
200
|
+
description: categoryMatch[2]!.trim(),
|
|
201
|
+
});
|
|
202
|
+
} else if (line.startsWith("- ") || line.startsWith("* ")) {
|
|
203
|
+
warnings.push({
|
|
204
|
+
category: "general",
|
|
205
|
+
description: line.slice(2).trim(),
|
|
206
|
+
});
|
|
207
|
+
} else if (line.length > 5) {
|
|
208
|
+
warnings.push({
|
|
209
|
+
category: "general",
|
|
210
|
+
description: line,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { warnings, passed };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private tryParseJsonResult(text: string): ValidationResult | null {
|
|
219
|
+
const direct = this.tryParseExactJsonResult(text);
|
|
220
|
+
if (direct) {
|
|
221
|
+
return direct;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const candidate = extractBalancedJsonObject(text);
|
|
225
|
+
if (!candidate) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return this.tryParseExactJsonResult(candidate);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private tryParseExactJsonResult(text: string): ValidationResult | null {
|
|
232
|
+
try {
|
|
233
|
+
const parsed = JSON.parse(text) as {
|
|
234
|
+
warnings?: Array<{ category?: string; description?: string }>;
|
|
235
|
+
passed?: boolean;
|
|
236
|
+
};
|
|
237
|
+
if (typeof parsed.passed !== "boolean") return null;
|
|
238
|
+
return {
|
|
239
|
+
warnings: (parsed.warnings ?? []).map((w) => ({
|
|
240
|
+
category: w.category ?? "unknown",
|
|
241
|
+
description: w.description ?? "",
|
|
242
|
+
})),
|
|
243
|
+
passed: parsed.passed,
|
|
244
|
+
};
|
|
245
|
+
} catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function extractBalancedJsonObject(text: string): string | null {
|
|
252
|
+
const start = text.indexOf("{");
|
|
253
|
+
if (start < 0) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let depth = 0;
|
|
258
|
+
let inString = false;
|
|
259
|
+
let escaped = false;
|
|
260
|
+
let endIndex = -1;
|
|
261
|
+
|
|
262
|
+
for (let index = start; index < text.length; index += 1) {
|
|
263
|
+
const char = text[index]!;
|
|
264
|
+
|
|
265
|
+
if (inString) {
|
|
266
|
+
if (escaped) {
|
|
267
|
+
escaped = false;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (char === "\\") {
|
|
271
|
+
escaped = true;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (char === "\"") {
|
|
275
|
+
inString = false;
|
|
276
|
+
}
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (char === "\"") {
|
|
281
|
+
inString = true;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (char === "{") {
|
|
286
|
+
depth += 1;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (char === "}") {
|
|
291
|
+
depth -= 1;
|
|
292
|
+
if (depth === 0) {
|
|
293
|
+
endIndex = index;
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
if (depth < 0) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (endIndex < 0) return null;
|
|
303
|
+
|
|
304
|
+
// Only accept the candidate if what follows the closing brace is
|
|
305
|
+
// nothing, whitespace, or a structural JSON terminator.
|
|
306
|
+
// This rejects trailing content like "{...} more text here"
|
|
307
|
+
const followingChar = text[endIndex + 1];
|
|
308
|
+
if (
|
|
309
|
+
followingChar !== undefined &&
|
|
310
|
+
followingChar !== "\n" &&
|
|
311
|
+
followingChar !== "\r" &&
|
|
312
|
+
followingChar !== "\t" &&
|
|
313
|
+
followingChar !== " " &&
|
|
314
|
+
followingChar !== "," &&
|
|
315
|
+
followingChar !== "]" &&
|
|
316
|
+
followingChar !== "}"
|
|
317
|
+
) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return text.slice(start, endIndex + 1);
|
|
322
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Style fingerprint analysis — pure text analysis (no LLM).
|
|
3
|
+
* Extracts statistical features from reference text to build a StyleProfile.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { StyleProfile } from "../models/style-profile.js";
|
|
7
|
+
|
|
8
|
+
// Common rhetorical patterns in Chinese fiction
|
|
9
|
+
const RHETORICAL_PATTERNS: ReadonlyArray<{ readonly name: string; readonly regex: RegExp }> = [
|
|
10
|
+
{ name: "比喻(像/如/仿佛)", regex: /[像如仿佛似](?:是|同|一般|一样)/g },
|
|
11
|
+
{ name: "排比", regex: /[,。;]([^,。;]{2,6})[,。;]\1/g },
|
|
12
|
+
{ name: "反问", regex: /难道|怎么可能|岂不是|何尝不/g },
|
|
13
|
+
{ name: "夸张", regex: /天崩地裂|惊天动地|翻天覆地|震耳欲聋/g },
|
|
14
|
+
{ name: "拟人", regex: /[风雨雪月花树草石](?:在|像|仿佛).*?(?:笑|哭|叹|呻|吟|怒|舞)/g },
|
|
15
|
+
{ name: "短句节奏", regex: /[。!?][^。!?]{1,8}[。!?]/g },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Analyze a reference text and extract its style profile.
|
|
20
|
+
* The returned profile can be serialized to style_profile.json.
|
|
21
|
+
*/
|
|
22
|
+
export function analyzeStyle(text: string, sourceName?: string): StyleProfile {
|
|
23
|
+
const sentences = text
|
|
24
|
+
.split(/[。!?\n]/)
|
|
25
|
+
.map((s) => s.trim())
|
|
26
|
+
.filter((s) => s.length > 0);
|
|
27
|
+
|
|
28
|
+
const paragraphs = text
|
|
29
|
+
.split(/\n\s*\n/)
|
|
30
|
+
.map((p) => p.trim())
|
|
31
|
+
.filter((p) => p.length > 0);
|
|
32
|
+
|
|
33
|
+
// Sentence length stats
|
|
34
|
+
const sentenceLengths = sentences.map((s) => s.length);
|
|
35
|
+
const avgSentenceLength = sentenceLengths.length > 0
|
|
36
|
+
? sentenceLengths.reduce((a, b) => a + b, 0) / sentenceLengths.length
|
|
37
|
+
: 0;
|
|
38
|
+
const sentenceLengthStdDev = sentenceLengths.length > 1
|
|
39
|
+
? Math.sqrt(
|
|
40
|
+
sentenceLengths.reduce((sum, l) => sum + (l - avgSentenceLength) ** 2, 0) /
|
|
41
|
+
sentenceLengths.length,
|
|
42
|
+
)
|
|
43
|
+
: 0;
|
|
44
|
+
|
|
45
|
+
// Paragraph length stats
|
|
46
|
+
const paragraphLengths = paragraphs.map((p) => p.length);
|
|
47
|
+
const avgParagraphLength = paragraphLengths.length > 0
|
|
48
|
+
? paragraphLengths.reduce((a, b) => a + b, 0) / paragraphLengths.length
|
|
49
|
+
: 0;
|
|
50
|
+
const minParagraph = paragraphLengths.length > 0 ? Math.min(...paragraphLengths) : 0;
|
|
51
|
+
const maxParagraph = paragraphLengths.length > 0 ? Math.max(...paragraphLengths) : 0;
|
|
52
|
+
|
|
53
|
+
// Vocabulary diversity (TTR — Type-Token Ratio)
|
|
54
|
+
// Use character-level for Chinese (each char is roughly a "token")
|
|
55
|
+
const chars = text.replace(/[\s\n\r,。!?、:;""''()【】《》\d]/g, "");
|
|
56
|
+
const uniqueChars = new Set(chars);
|
|
57
|
+
const vocabularyDiversity = chars.length > 0 ? uniqueChars.size / chars.length : 0;
|
|
58
|
+
|
|
59
|
+
// Top sentence opening patterns (first 2 chars)
|
|
60
|
+
const openingCounts: Record<string, number> = {};
|
|
61
|
+
for (const s of sentences) {
|
|
62
|
+
if (s.length >= 2) {
|
|
63
|
+
const opening = s.slice(0, 2);
|
|
64
|
+
openingCounts[opening] = (openingCounts[opening] ?? 0) + 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const topPatterns = Object.entries(openingCounts)
|
|
68
|
+
.sort((a, b) => b[1] - a[1])
|
|
69
|
+
.slice(0, 5)
|
|
70
|
+
.filter(([, count]) => count >= 3)
|
|
71
|
+
.map(([pattern, count]) => `${pattern}...(${count}次)`);
|
|
72
|
+
|
|
73
|
+
// Rhetorical features
|
|
74
|
+
const rhetoricalFeatures: string[] = [];
|
|
75
|
+
for (const { name, regex } of RHETORICAL_PATTERNS) {
|
|
76
|
+
const matches = text.match(regex);
|
|
77
|
+
if (matches && matches.length >= 2) {
|
|
78
|
+
rhetoricalFeatures.push(`${name}(${matches.length}处)`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
avgSentenceLength: Math.round(avgSentenceLength * 10) / 10,
|
|
84
|
+
sentenceLengthStdDev: Math.round(sentenceLengthStdDev * 10) / 10,
|
|
85
|
+
avgParagraphLength: Math.round(avgParagraphLength),
|
|
86
|
+
paragraphLengthRange: { min: minParagraph, max: maxParagraph },
|
|
87
|
+
vocabularyDiversity: Math.round(vocabularyDiversity * 1000) / 1000,
|
|
88
|
+
topPatterns,
|
|
89
|
+
rhetoricalFeatures,
|
|
90
|
+
sourceName,
|
|
91
|
+
analyzedAt: new Date().toISOString(),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { GenreProfile } from "../models/genre-profile.js";
|
|
2
|
+
import type { LengthCountingMode } from "../models/length-governance.js";
|
|
3
|
+
import type { WriteChapterOutput } from "./writer.js";
|
|
4
|
+
import { countChapterLength } from "../utils/length-metrics.js";
|
|
5
|
+
|
|
6
|
+
export interface CreativeOutput {
|
|
7
|
+
readonly title: string;
|
|
8
|
+
readonly content: string;
|
|
9
|
+
readonly wordCount: number;
|
|
10
|
+
readonly preWriteCheck: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function parseCreativeOutput(
|
|
14
|
+
chapterNumber: number,
|
|
15
|
+
content: string,
|
|
16
|
+
countingMode: LengthCountingMode = "zh_chars",
|
|
17
|
+
): CreativeOutput {
|
|
18
|
+
const extract = (tag: string): string => {
|
|
19
|
+
const regex = new RegExp(
|
|
20
|
+
`=== ${tag} ===\\s*([\\s\\S]*?)(?==== [A-Z_]+ ===|$)`,
|
|
21
|
+
);
|
|
22
|
+
const match = content.match(regex);
|
|
23
|
+
return match?.[1]?.trim() ?? "";
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let chapterContent = extract("CHAPTER_CONTENT");
|
|
27
|
+
|
|
28
|
+
// Fallback: if === TAG === parsing fails (common with local/small models),
|
|
29
|
+
// try to extract usable content from the raw output
|
|
30
|
+
if (!chapterContent) {
|
|
31
|
+
chapterContent = fallbackExtractContent(content, countingMode);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let title = extract("CHAPTER_TITLE");
|
|
35
|
+
if (!title) {
|
|
36
|
+
title = fallbackExtractTitle(content, chapterNumber, countingMode);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
title,
|
|
41
|
+
content: chapterContent,
|
|
42
|
+
wordCount: countChapterLength(chapterContent, countingMode),
|
|
43
|
+
preWriteCheck: extract("PRE_WRITE_CHECK"),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Fallback content extraction when === CHAPTER_CONTENT === tag is missing.
|
|
49
|
+
* Tries common patterns from local/small models, then falls back to
|
|
50
|
+
* stripping metadata and returning the longest prose block.
|
|
51
|
+
*/
|
|
52
|
+
function fallbackExtractContent(raw: string, countingMode: LengthCountingMode): string {
|
|
53
|
+
// Try markdown heading: # 第N章 ... followed by content
|
|
54
|
+
const headingMatch = raw.match(/^#\s*第\d+章[^\n]*\n+([\s\S]+)/m);
|
|
55
|
+
if (headingMatch) {
|
|
56
|
+
return headingMatch[1]!.trim();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (countingMode === "en_words") {
|
|
60
|
+
const englishHeadingMatch = raw.match(/^#\s*Chapter\s+\d+(?::|\s+)([^\n]*)\n+([\s\S]+)/im);
|
|
61
|
+
if (englishHeadingMatch) {
|
|
62
|
+
return englishHeadingMatch[2]!.trim();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Try "正文" or "内容" labeled section
|
|
67
|
+
const labelMatch = raw.match(/(?:正文|内容|章节内容)[::]\s*\n+([\s\S]+)/);
|
|
68
|
+
if (labelMatch) {
|
|
69
|
+
return labelMatch[1]!.trim();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (countingMode === "en_words") {
|
|
73
|
+
const englishLabelMatch = raw.match(/(?:content|chapter content)[::]\s*\n+([\s\S]+)/i);
|
|
74
|
+
if (englishLabelMatch) {
|
|
75
|
+
return englishLabelMatch[1]!.trim();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Last resort: strip lines that look like metadata/tags, keep the rest
|
|
80
|
+
const lines = raw.split("\n");
|
|
81
|
+
const proseLines = lines.filter((line) => {
|
|
82
|
+
const trimmed = line.trim();
|
|
83
|
+
// Skip tag-like lines, empty lines at boundaries, and short key-value lines
|
|
84
|
+
if (/^===\s*[A-Z_]+\s*===/.test(trimmed)) return false;
|
|
85
|
+
if (/^(PRE_WRITE_CHECK|CHAPTER_TITLE|章节标题|写作自检)[::]/.test(trimmed)) return false;
|
|
86
|
+
return true;
|
|
87
|
+
});
|
|
88
|
+
const result = proseLines.join("\n").trim();
|
|
89
|
+
// Only use fallback if we got meaningful content (>100 chars)
|
|
90
|
+
return result.length > 100 ? result : "";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Fallback title extraction when === CHAPTER_TITLE === tag is missing.
|
|
95
|
+
*/
|
|
96
|
+
function fallbackExtractTitle(
|
|
97
|
+
raw: string,
|
|
98
|
+
chapterNumber: number,
|
|
99
|
+
countingMode: LengthCountingMode,
|
|
100
|
+
): string {
|
|
101
|
+
// Try: # 第N章 Title
|
|
102
|
+
const headingMatch = raw.match(/^#\s*第\d+章\s*(.+)/m);
|
|
103
|
+
if (headingMatch) {
|
|
104
|
+
return headingMatch[1]!.trim();
|
|
105
|
+
}
|
|
106
|
+
if (countingMode === "en_words") {
|
|
107
|
+
const englishHeadingMatch = raw.match(/^#\s*Chapter\s+\d+(?::|\s+)\s*(.+)/im);
|
|
108
|
+
if (englishHeadingMatch) {
|
|
109
|
+
return englishHeadingMatch[1]!.trim();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Try: 章节标题:Title or CHAPTER_TITLE: Title (without === delimiters)
|
|
113
|
+
const labelMatch = raw.match(/(?:章节标题|CHAPTER_TITLE)[::]\s*(.+)/);
|
|
114
|
+
if (labelMatch) {
|
|
115
|
+
return labelMatch[1]!.trim();
|
|
116
|
+
}
|
|
117
|
+
return defaultChapterTitle(chapterNumber, countingMode);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type ParsedWriterOutput = Omit<WriteChapterOutput, "postWriteErrors" | "postWriteWarnings">;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Parse LLM output that uses === TAG === delimiters into structured chapter data.
|
|
124
|
+
* Shared by WriterAgent (writing new chapters) and ChapterAnalyzerAgent (analyzing existing chapters).
|
|
125
|
+
*/
|
|
126
|
+
export function parseWriterOutput(
|
|
127
|
+
chapterNumber: number,
|
|
128
|
+
content: string,
|
|
129
|
+
genreProfile: GenreProfile,
|
|
130
|
+
countingMode: LengthCountingMode = "zh_chars",
|
|
131
|
+
): ParsedWriterOutput {
|
|
132
|
+
const extract = (tag: string): string => {
|
|
133
|
+
const regex = new RegExp(
|
|
134
|
+
`=== ${tag} ===\\s*([\\s\\S]*?)(?==== [A-Z_]+ ===|$)`,
|
|
135
|
+
);
|
|
136
|
+
const match = content.match(regex);
|
|
137
|
+
return match?.[1]?.trim() ?? "";
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const chapterContent = extract("CHAPTER_CONTENT");
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
chapterNumber,
|
|
144
|
+
title: extract("CHAPTER_TITLE") || defaultChapterTitle(chapterNumber, countingMode),
|
|
145
|
+
content: chapterContent,
|
|
146
|
+
wordCount: countChapterLength(chapterContent, countingMode),
|
|
147
|
+
preWriteCheck: extract("PRE_WRITE_CHECK"),
|
|
148
|
+
postSettlement: extract("POST_SETTLEMENT"),
|
|
149
|
+
updatedState: extract("UPDATED_STATE") || defaultStatePlaceholder(countingMode),
|
|
150
|
+
updatedLedger: genreProfile.numericalSystem
|
|
151
|
+
? (extract("UPDATED_LEDGER") || defaultLedgerPlaceholder(countingMode))
|
|
152
|
+
: "",
|
|
153
|
+
updatedHooks: extract("UPDATED_HOOKS") || defaultHooksPlaceholder(countingMode),
|
|
154
|
+
chapterSummary: extract("CHAPTER_SUMMARY"),
|
|
155
|
+
updatedSubplots: extract("UPDATED_SUBPLOTS"),
|
|
156
|
+
updatedEmotionalArcs: extract("UPDATED_EMOTIONAL_ARCS"),
|
|
157
|
+
updatedCharacterMatrix: extract("UPDATED_CHARACTER_MATRIX"),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function defaultChapterTitle(
|
|
162
|
+
chapterNumber: number,
|
|
163
|
+
countingMode: LengthCountingMode,
|
|
164
|
+
): string {
|
|
165
|
+
return countingMode === "en_words" ? `Chapter ${chapterNumber}` : `第${chapterNumber}章`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function defaultStatePlaceholder(countingMode: LengthCountingMode): string {
|
|
169
|
+
return countingMode === "en_words" ? "(state card not updated)" : "(状态卡未更新)";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function defaultLedgerPlaceholder(countingMode: LengthCountingMode): string {
|
|
173
|
+
return countingMode === "en_words" ? "(ledger not updated)" : "(账本未更新)";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function defaultHooksPlaceholder(countingMode: LengthCountingMode): string {
|
|
177
|
+
return countingMode === "en_words" ? "(hooks pool not updated)" : "(伏笔池未更新)";
|
|
178
|
+
}
|