@kienmatu/inkos-core 0.1.0
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/LICENSE +661 -0
- package/dist/agent/agent-session.d.ts +100 -0
- package/dist/agent/agent-session.d.ts.map +1 -0
- package/dist/agent/agent-session.js +1102 -0
- package/dist/agent/agent-session.js.map +1 -0
- package/dist/agent/agent-system-prompt.d.ts +12 -0
- package/dist/agent/agent-system-prompt.d.ts.map +1 -0
- package/dist/agent/agent-system-prompt.js +596 -0
- package/dist/agent/agent-system-prompt.js.map +1 -0
- package/dist/agent/agent-tools.d.ts +508 -0
- package/dist/agent/agent-tools.d.ts.map +1 -0
- package/dist/agent/agent-tools.js +3083 -0
- package/dist/agent/agent-tools.js.map +1 -0
- package/dist/agent/chapter-import-source.d.ts +16 -0
- package/dist/agent/chapter-import-source.d.ts.map +1 -0
- package/dist/agent/chapter-import-source.js +48 -0
- package/dist/agent/chapter-import-source.js.map +1 -0
- package/dist/agent/context-transform.d.ts +13 -0
- package/dist/agent/context-transform.d.ts.map +1 -0
- package/dist/agent/context-transform.js +183 -0
- package/dist/agent/context-transform.js.map +1 -0
- package/dist/agent/film-authoring-tools.d.ts +98 -0
- package/dist/agent/film-authoring-tools.d.ts.map +1 -0
- package/dist/agent/film-authoring-tools.js +405 -0
- package/dist/agent/film-authoring-tools.js.map +1 -0
- package/dist/agent/forecast-tools.d.ts +22 -0
- package/dist/agent/forecast-tools.d.ts.map +1 -0
- package/dist/agent/forecast-tools.js +153 -0
- package/dist/agent/forecast-tools.js.map +1 -0
- package/dist/agent/index.d.ts +8 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +8 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/llm-stub.d.ts +19 -0
- package/dist/agent/llm-stub.d.ts.map +1 -0
- package/dist/agent/llm-stub.js +134 -0
- package/dist/agent/llm-stub.js.map +1 -0
- package/dist/agent/pi-stream.d.ts +8 -0
- package/dist/agent/pi-stream.d.ts.map +1 -0
- package/dist/agent/pi-stream.js +31 -0
- package/dist/agent/pi-stream.js.map +1 -0
- package/dist/agent/skill-tool.d.ts +42 -0
- package/dist/agent/skill-tool.d.ts.map +1 -0
- package/dist/agent/skill-tool.js +279 -0
- package/dist/agent/skill-tool.js.map +1 -0
- package/dist/agent/worker-agent.d.ts +24 -0
- package/dist/agent/worker-agent.d.ts.map +1 -0
- package/dist/agent/worker-agent.js +288 -0
- package/dist/agent/worker-agent.js.map +1 -0
- package/dist/agents/ai-tells.d.ts +26 -0
- package/dist/agents/ai-tells.d.ts.map +1 -0
- package/dist/agents/ai-tells.js +140 -0
- package/dist/agents/ai-tells.js.map +1 -0
- package/dist/agents/architect.d.ts +78 -0
- package/dist/agents/architect.d.ts.map +1 -0
- package/dist/agents/architect.js +1184 -0
- package/dist/agents/architect.js.map +1 -0
- package/dist/agents/base.d.ts +42 -0
- package/dist/agents/base.d.ts.map +1 -0
- package/dist/agents/base.js +123 -0
- package/dist/agents/base.js.map +1 -0
- package/dist/agents/chapter-analyzer.d.ts +30 -0
- package/dist/agents/chapter-analyzer.d.ts.map +1 -0
- package/dist/agents/chapter-analyzer.js +509 -0
- package/dist/agents/chapter-analyzer.js.map +1 -0
- package/dist/agents/composer.d.ts +67 -0
- package/dist/agents/composer.d.ts.map +1 -0
- package/dist/agents/composer.js +936 -0
- package/dist/agents/composer.js.map +1 -0
- package/dist/agents/consolidator.d.ts +39 -0
- package/dist/agents/consolidator.d.ts.map +1 -0
- package/dist/agents/consolidator.js +179 -0
- package/dist/agents/consolidator.js.map +1 -0
- package/dist/agents/continuity.d.ts +45 -0
- package/dist/agents/continuity.d.ts.map +1 -0
- package/dist/agents/continuity.js +722 -0
- package/dist/agents/continuity.js.map +1 -0
- package/dist/agents/detection-insights.d.ts +9 -0
- package/dist/agents/detection-insights.d.ts.map +1 -0
- package/dist/agents/detection-insights.js +54 -0
- package/dist/agents/detection-insights.js.map +1 -0
- package/dist/agents/detector.d.ts +17 -0
- package/dist/agents/detector.d.ts.map +1 -0
- package/dist/agents/detector.js +77 -0
- package/dist/agents/detector.js.map +1 -0
- package/dist/agents/en-prompt-sections.d.ts +4 -0
- package/dist/agents/en-prompt-sections.d.ts.map +1 -0
- package/dist/agents/en-prompt-sections.js +9 -0
- package/dist/agents/en-prompt-sections.js.map +1 -0
- package/dist/agents/fanfic-canon-importer.d.ts +16 -0
- package/dist/agents/fanfic-canon-importer.d.ts.map +1 -0
- package/dist/agents/fanfic-canon-importer.js +164 -0
- package/dist/agents/fanfic-canon-importer.js.map +1 -0
- package/dist/agents/fanfic-dimensions.d.ts +14 -0
- package/dist/agents/fanfic-dimensions.d.ts.map +1 -0
- package/dist/agents/fanfic-dimensions.js +63 -0
- package/dist/agents/fanfic-dimensions.js.map +1 -0
- package/dist/agents/fanfic-prompt-sections.d.ts +5 -0
- package/dist/agents/fanfic-prompt-sections.d.ts.map +1 -0
- package/dist/agents/fanfic-prompt-sections.js +85 -0
- package/dist/agents/fanfic-prompt-sections.js.map +1 -0
- package/dist/agents/foundation-reviewer.d.ts +34 -0
- package/dist/agents/foundation-reviewer.d.ts.map +1 -0
- package/dist/agents/foundation-reviewer.js +174 -0
- package/dist/agents/foundation-reviewer.js.map +1 -0
- package/dist/agents/observer-prompts.d.ts +10 -0
- package/dist/agents/observer-prompts.d.ts.map +1 -0
- package/dist/agents/observer-prompts.js +113 -0
- package/dist/agents/observer-prompts.js.map +1 -0
- package/dist/agents/planner-context.d.ts +53 -0
- package/dist/agents/planner-context.d.ts.map +1 -0
- package/dist/agents/planner-context.js +238 -0
- package/dist/agents/planner-context.js.map +1 -0
- package/dist/agents/planner-prompts.d.ts +45 -0
- package/dist/agents/planner-prompts.d.ts.map +1 -0
- package/dist/agents/planner-prompts.js +391 -0
- package/dist/agents/planner-prompts.js.map +1 -0
- package/dist/agents/planner.d.ts +90 -0
- package/dist/agents/planner.d.ts.map +1 -0
- package/dist/agents/planner.js +715 -0
- package/dist/agents/planner.js.map +1 -0
- package/dist/agents/polisher.d.ts +33 -0
- package/dist/agents/polisher.d.ts.map +1 -0
- package/dist/agents/polisher.js +122 -0
- package/dist/agents/polisher.js.map +1 -0
- package/dist/agents/post-write-validator.d.ts +35 -0
- package/dist/agents/post-write-validator.d.ts.map +1 -0
- package/dist/agents/post-write-validator.js +758 -0
- package/dist/agents/post-write-validator.js.map +1 -0
- package/dist/agents/radar-source.d.ts +38 -0
- package/dist/agents/radar-source.d.ts.map +1 -0
- package/dist/agents/radar-source.js +92 -0
- package/dist/agents/radar-source.js.map +1 -0
- package/dist/agents/radar.d.ts +24 -0
- package/dist/agents/radar.d.ts.map +1 -0
- package/dist/agents/radar.js +85 -0
- package/dist/agents/radar.js.map +1 -0
- package/dist/agents/researcher.d.ts +38 -0
- package/dist/agents/researcher.d.ts.map +1 -0
- package/dist/agents/researcher.js +164 -0
- package/dist/agents/researcher.js.map +1 -0
- package/dist/agents/reviser.d.ts +35 -0
- package/dist/agents/reviser.d.ts.map +1 -0
- package/dist/agents/reviser.js +496 -0
- package/dist/agents/reviser.js.map +1 -0
- package/dist/agents/rules-reader.d.ts +32 -0
- package/dist/agents/rules-reader.d.ts.map +1 -0
- package/dist/agents/rules-reader.js +129 -0
- package/dist/agents/rules-reader.js.map +1 -0
- package/dist/agents/script-storyboard.d.ts +66 -0
- package/dist/agents/script-storyboard.d.ts.map +1 -0
- package/dist/agents/script-storyboard.js +657 -0
- package/dist/agents/script-storyboard.js.map +1 -0
- package/dist/agents/sensitive-words.d.ts +24 -0
- package/dist/agents/sensitive-words.d.ts.map +1 -0
- package/dist/agents/sensitive-words.js +103 -0
- package/dist/agents/sensitive-words.js.map +1 -0
- package/dist/agents/settler-delta-parser.d.ts +7 -0
- package/dist/agents/settler-delta-parser.d.ts.map +1 -0
- package/dist/agents/settler-delta-parser.js +40 -0
- package/dist/agents/settler-delta-parser.js.map +1 -0
- package/dist/agents/settler-parser.d.ts +13 -0
- package/dist/agents/settler-parser.d.ts.map +1 -0
- package/dist/agents/settler-parser.js +20 -0
- package/dist/agents/settler-parser.js.map +1 -0
- package/dist/agents/settler-prompts.d.ts +22 -0
- package/dist/agents/settler-prompts.d.ts.map +1 -0
- package/dist/agents/settler-prompts.js +195 -0
- package/dist/agents/settler-prompts.js.map +1 -0
- package/dist/agents/short-fiction.d.ts +120 -0
- package/dist/agents/short-fiction.d.ts.map +1 -0
- package/dist/agents/short-fiction.js +353 -0
- package/dist/agents/short-fiction.js.map +1 -0
- package/dist/agents/state-validator.d.ts +33 -0
- package/dist/agents/state-validator.d.ts.map +1 -0
- package/dist/agents/state-validator.js +262 -0
- package/dist/agents/state-validator.js.map +1 -0
- package/dist/agents/style-analyzer.d.ts +11 -0
- package/dist/agents/style-analyzer.d.ts.map +1 -0
- package/dist/agents/style-analyzer.js +98 -0
- package/dist/agents/style-analyzer.js.map +1 -0
- package/dist/agents/writer-parser.d.ts +17 -0
- package/dist/agents/writer-parser.d.ts.map +1 -0
- package/dist/agents/writer-parser.js +131 -0
- package/dist/agents/writer-parser.js.map +1 -0
- package/dist/agents/writer-prompts.d.ts +12 -0
- package/dist/agents/writer-prompts.d.ts.map +1 -0
- package/dist/agents/writer-prompts.js +495 -0
- package/dist/agents/writer-prompts.js.map +1 -0
- package/dist/agents/writer.d.ts +102 -0
- package/dist/agents/writer.d.ts.map +1 -0
- package/dist/agents/writer.js +882 -0
- package/dist/agents/writer.js.map +1 -0
- package/dist/forecast/agent.d.ts +22 -0
- package/dist/forecast/agent.d.ts.map +1 -0
- package/dist/forecast/agent.js +48 -0
- package/dist/forecast/agent.js.map +1 -0
- package/dist/forecast/context-builder.d.ts +35 -0
- package/dist/forecast/context-builder.d.ts.map +1 -0
- package/dist/forecast/context-builder.js +214 -0
- package/dist/forecast/context-builder.js.map +1 -0
- package/dist/forecast/prompts.d.ts +12 -0
- package/dist/forecast/prompts.d.ts.map +1 -0
- package/dist/forecast/prompts.js +114 -0
- package/dist/forecast/prompts.js.map +1 -0
- package/dist/forecast/render.d.ts +9 -0
- package/dist/forecast/render.d.ts.map +1 -0
- package/dist/forecast/render.js +154 -0
- package/dist/forecast/render.js.map +1 -0
- package/dist/forecast/runner.d.ts +51 -0
- package/dist/forecast/runner.d.ts.map +1 -0
- package/dist/forecast/runner.js +115 -0
- package/dist/forecast/runner.js.map +1 -0
- package/dist/forecast/schema.d.ts +767 -0
- package/dist/forecast/schema.d.ts.map +1 -0
- package/dist/forecast/schema.js +115 -0
- package/dist/forecast/schema.js.map +1 -0
- package/dist/forecast/store.d.ts +32 -0
- package/dist/forecast/store.d.ts.map +1 -0
- package/dist/forecast/store.js +129 -0
- package/dist/forecast/store.js.map +1 -0
- package/dist/index.d.ts +160 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +172 -0
- package/dist/index.js.map +1 -0
- package/dist/interaction/action-envelope.d.ts +1493 -0
- package/dist/interaction/action-envelope.d.ts.map +1 -0
- package/dist/interaction/action-envelope.js +257 -0
- package/dist/interaction/action-envelope.js.map +1 -0
- package/dist/interaction/book-session-store.d.ts +29 -0
- package/dist/interaction/book-session-store.d.ts.map +1 -0
- package/dist/interaction/book-session-store.js +185 -0
- package/dist/interaction/book-session-store.js.map +1 -0
- package/dist/interaction/draft-directive-parser.d.ts +38 -0
- package/dist/interaction/draft-directive-parser.d.ts.map +1 -0
- package/dist/interaction/draft-directive-parser.js +208 -0
- package/dist/interaction/draft-directive-parser.js.map +1 -0
- package/dist/interaction/edit-controller.d.ts +62 -0
- package/dist/interaction/edit-controller.d.ts.map +1 -0
- package/dist/interaction/edit-controller.js +381 -0
- package/dist/interaction/edit-controller.js.map +1 -0
- package/dist/interaction/events.d.ts +45 -0
- package/dist/interaction/events.d.ts.map +1 -0
- package/dist/interaction/events.js +32 -0
- package/dist/interaction/events.js.map +1 -0
- package/dist/interaction/export-artifact.d.ts +32 -0
- package/dist/interaction/export-artifact.d.ts.map +1 -0
- package/dist/interaction/export-artifact.js +103 -0
- package/dist/interaction/export-artifact.js.map +1 -0
- package/dist/interaction/intents.d.ts +99 -0
- package/dist/interaction/intents.d.ts.map +1 -0
- package/dist/interaction/intents.js +59 -0
- package/dist/interaction/intents.js.map +1 -0
- package/dist/interaction/modes.d.ts +5 -0
- package/dist/interaction/modes.d.ts.map +1 -0
- package/dist/interaction/modes.js +7 -0
- package/dist/interaction/modes.js.map +1 -0
- package/dist/interaction/project-control.d.ts +45 -0
- package/dist/interaction/project-control.d.ts.map +1 -0
- package/dist/interaction/project-control.js +71 -0
- package/dist/interaction/project-control.js.map +1 -0
- package/dist/interaction/project-session-store.d.ts +9 -0
- package/dist/interaction/project-session-store.d.ts.map +1 -0
- package/dist/interaction/project-session-store.js +64 -0
- package/dist/interaction/project-session-store.js.map +1 -0
- package/dist/interaction/project-tools.d.ts +23 -0
- package/dist/interaction/project-tools.d.ts.map +1 -0
- package/dist/interaction/project-tools.js +408 -0
- package/dist/interaction/project-tools.js.map +1 -0
- package/dist/interaction/request-router.d.ts +3 -0
- package/dist/interaction/request-router.d.ts.map +1 -0
- package/dist/interaction/request-router.js +5 -0
- package/dist/interaction/request-router.js.map +1 -0
- package/dist/interaction/runtime.d.ts +54 -0
- package/dist/interaction/runtime.d.ts.map +1 -0
- package/dist/interaction/runtime.js +919 -0
- package/dist/interaction/runtime.js.map +1 -0
- package/dist/interaction/session-transcript-legacy.d.ts +4 -0
- package/dist/interaction/session-transcript-legacy.d.ts.map +1 -0
- package/dist/interaction/session-transcript-legacy.js +102 -0
- package/dist/interaction/session-transcript-legacy.js.map +1 -0
- package/dist/interaction/session-transcript-restore.d.ts +18 -0
- package/dist/interaction/session-transcript-restore.d.ts.map +1 -0
- package/dist/interaction/session-transcript-restore.js +746 -0
- package/dist/interaction/session-transcript-restore.js.map +1 -0
- package/dist/interaction/session-transcript-schema.d.ts +435 -0
- package/dist/interaction/session-transcript-schema.d.ts.map +1 -0
- package/dist/interaction/session-transcript-schema.js +65 -0
- package/dist/interaction/session-transcript-schema.js.map +1 -0
- package/dist/interaction/session-transcript.d.ts +21 -0
- package/dist/interaction/session-transcript.d.ts.map +1 -0
- package/dist/interaction/session-transcript.js +164 -0
- package/dist/interaction/session-transcript.js.map +1 -0
- package/dist/interaction/session.d.ts +1194 -0
- package/dist/interaction/session.d.ts.map +1 -0
- package/dist/interaction/session.js +194 -0
- package/dist/interaction/session.js.map +1 -0
- package/dist/interaction/truth-authority.d.ts +4 -0
- package/dist/interaction/truth-authority.d.ts.map +1 -0
- package/dist/interaction/truth-authority.js +37 -0
- package/dist/interaction/truth-authority.js.map +1 -0
- package/dist/interactive-film/authoring-store.d.ts +35 -0
- package/dist/interactive-film/authoring-store.d.ts.map +1 -0
- package/dist/interactive-film/authoring-store.js +114 -0
- package/dist/interactive-film/authoring-store.js.map +1 -0
- package/dist/interactive-film/authoring-tools.d.ts +9 -0
- package/dist/interactive-film/authoring-tools.d.ts.map +1 -0
- package/dist/interactive-film/authoring-tools.js +19 -0
- package/dist/interactive-film/authoring-tools.js.map +1 -0
- package/dist/interactive-film/delta.d.ts +591 -0
- package/dist/interactive-film/delta.d.ts.map +1 -0
- package/dist/interactive-film/delta.js +54 -0
- package/dist/interactive-film/delta.js.map +1 -0
- package/dist/interactive-film/emotion.d.ts +20 -0
- package/dist/interactive-film/emotion.d.ts.map +1 -0
- package/dist/interactive-film/emotion.js +57 -0
- package/dist/interactive-film/emotion.js.map +1 -0
- package/dist/interactive-film/evaluator.d.ts +7 -0
- package/dist/interactive-film/evaluator.d.ts.map +1 -0
- package/dist/interactive-film/evaluator.js +40 -0
- package/dist/interactive-film/evaluator.js.map +1 -0
- package/dist/interactive-film/export-html.d.ts +6 -0
- package/dist/interactive-film/export-html.d.ts.map +1 -0
- package/dist/interactive-film/export-html.js +84 -0
- package/dist/interactive-film/export-html.js.map +1 -0
- package/dist/interactive-film/export-ink.d.ts +3 -0
- package/dist/interactive-film/export-ink.d.ts.map +1 -0
- package/dist/interactive-film/export-ink.js +51 -0
- package/dist/interactive-film/export-ink.js.map +1 -0
- package/dist/interactive-film/film-context.d.ts +4 -0
- package/dist/interactive-film/film-context.d.ts.map +1 -0
- package/dist/interactive-film/film-context.js +30 -0
- package/dist/interactive-film/film-context.js.map +1 -0
- package/dist/interactive-film/generate.d.ts +15 -0
- package/dist/interactive-film/generate.d.ts.map +1 -0
- package/dist/interactive-film/generate.js +51 -0
- package/dist/interactive-film/generate.js.map +1 -0
- package/dist/interactive-film/graph-schema.d.ts +819 -0
- package/dist/interactive-film/graph-schema.d.ts.map +1 -0
- package/dist/interactive-film/graph-schema.js +84 -0
- package/dist/interactive-film/graph-schema.js.map +1 -0
- package/dist/interactive-film/graph-store.d.ts +5 -0
- package/dist/interactive-film/graph-store.d.ts.map +1 -0
- package/dist/interactive-film/graph-store.js +24 -0
- package/dist/interactive-film/graph-store.js.map +1 -0
- package/dist/interactive-film/memory-link.d.ts +9 -0
- package/dist/interactive-film/memory-link.d.ts.map +1 -0
- package/dist/interactive-film/memory-link.js +15 -0
- package/dist/interactive-film/memory-link.js.map +1 -0
- package/dist/interactive-film/node-image.d.ts +29 -0
- package/dist/interactive-film/node-image.d.ts.map +1 -0
- package/dist/interactive-film/node-image.js +52 -0
- package/dist/interactive-film/node-image.js.map +1 -0
- package/dist/interactive-film/paths.d.ts +14 -0
- package/dist/interactive-film/paths.d.ts.map +1 -0
- package/dist/interactive-film/paths.js +60 -0
- package/dist/interactive-film/paths.js.map +1 -0
- package/dist/interactive-film/tool-schemas.d.ts +182 -0
- package/dist/interactive-film/tool-schemas.d.ts.map +1 -0
- package/dist/interactive-film/tool-schemas.js +125 -0
- package/dist/interactive-film/tool-schemas.js.map +1 -0
- package/dist/interactive-film/validation.d.ts +14 -0
- package/dist/interactive-film/validation.d.ts.map +1 -0
- package/dist/interactive-film/validation.js +292 -0
- package/dist/interactive-film/validation.js.map +1 -0
- package/dist/llm/agent-trajectory.d.ts +27 -0
- package/dist/llm/agent-trajectory.d.ts.map +1 -0
- package/dist/llm/agent-trajectory.js +74 -0
- package/dist/llm/agent-trajectory.js.map +1 -0
- package/dist/llm/config-migration.d.ts +5 -0
- package/dist/llm/config-migration.d.ts.map +1 -0
- package/dist/llm/config-migration.js +51 -0
- package/dist/llm/config-migration.js.map +1 -0
- package/dist/llm/cover-providers.d.ts +14 -0
- package/dist/llm/cover-providers.d.ts.map +1 -0
- package/dist/llm/cover-providers.js +51 -0
- package/dist/llm/cover-providers.js.map +1 -0
- package/dist/llm/long-form-completion.d.ts +16 -0
- package/dist/llm/long-form-completion.d.ts.map +1 -0
- package/dist/llm/long-form-completion.js +62 -0
- package/dist/llm/long-form-completion.js.map +1 -0
- package/dist/llm/provider.d.ts +110 -0
- package/dist/llm/provider.d.ts.map +1 -0
- package/dist/llm/provider.js +1405 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/llm/providers/endpoints/ai360.d.ts +10 -0
- package/dist/llm/providers/endpoints/ai360.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/ai360.js +34 -0
- package/dist/llm/providers/endpoints/ai360.js.map +1 -0
- package/dist/llm/providers/endpoints/anthropic.d.ts +12 -0
- package/dist/llm/providers/endpoints/anthropic.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/anthropic.js +72 -0
- package/dist/llm/providers/endpoints/anthropic.js.map +1 -0
- package/dist/llm/providers/endpoints/astronCodingPlan.d.ts +16 -0
- package/dist/llm/providers/endpoints/astronCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/astronCodingPlan.js +16 -0
- package/dist/llm/providers/endpoints/astronCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/baichuan.d.ts +10 -0
- package/dist/llm/providers/endpoints/baichuan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/baichuan.js +20 -0
- package/dist/llm/providers/endpoints/baichuan.js.map +1 -0
- package/dist/llm/providers/endpoints/bailian.d.ts +25 -0
- package/dist/llm/providers/endpoints/bailian.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/bailian.js +42 -0
- package/dist/llm/providers/endpoints/bailian.js.map +1 -0
- package/dist/llm/providers/endpoints/bailianCodingPlan.d.ts +10 -0
- package/dist/llm/providers/endpoints/bailianCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/bailianCodingPlan.js +22 -0
- package/dist/llm/providers/endpoints/bailianCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/custom.d.ts +16 -0
- package/dist/llm/providers/endpoints/custom.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/custom.js +15 -0
- package/dist/llm/providers/endpoints/custom.js.map +1 -0
- package/dist/llm/providers/endpoints/deepseek.d.ts +17 -0
- package/dist/llm/providers/endpoints/deepseek.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/deepseek.js +20 -0
- package/dist/llm/providers/endpoints/deepseek.js.map +1 -0
- package/dist/llm/providers/endpoints/giteeai.d.ts +10 -0
- package/dist/llm/providers/endpoints/giteeai.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/giteeai.js +33 -0
- package/dist/llm/providers/endpoints/giteeai.js.map +1 -0
- package/dist/llm/providers/endpoints/githubCopilot.d.ts +10 -0
- package/dist/llm/providers/endpoints/githubCopilot.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/githubCopilot.js +35 -0
- package/dist/llm/providers/endpoints/githubCopilot.js.map +1 -0
- package/dist/llm/providers/endpoints/glmCodingPlan.d.ts +9 -0
- package/dist/llm/providers/endpoints/glmCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/glmCodingPlan.js +21 -0
- package/dist/llm/providers/endpoints/glmCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/google.d.ts +12 -0
- package/dist/llm/providers/endpoints/google.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/google.js +41 -0
- package/dist/llm/providers/endpoints/google.js.map +1 -0
- package/dist/llm/providers/endpoints/hunyuan.d.ts +10 -0
- package/dist/llm/providers/endpoints/hunyuan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/hunyuan.js +34 -0
- package/dist/llm/providers/endpoints/hunyuan.js.map +1 -0
- package/dist/llm/providers/endpoints/infiniai.d.ts +10 -0
- package/dist/llm/providers/endpoints/infiniai.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/infiniai.js +64 -0
- package/dist/llm/providers/endpoints/infiniai.js.map +1 -0
- package/dist/llm/providers/endpoints/internlm.d.ts +10 -0
- package/dist/llm/providers/endpoints/internlm.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/internlm.js +20 -0
- package/dist/llm/providers/endpoints/internlm.js.map +1 -0
- package/dist/llm/providers/endpoints/kimiCode.d.ts +9 -0
- package/dist/llm/providers/endpoints/kimiCode.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/kimiCode.js +16 -0
- package/dist/llm/providers/endpoints/kimiCode.js.map +1 -0
- package/dist/llm/providers/endpoints/kimiCodingPlan.d.ts +9 -0
- package/dist/llm/providers/endpoints/kimiCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/kimiCodingPlan.js +17 -0
- package/dist/llm/providers/endpoints/kimiCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/kkaiapi.d.ts +14 -0
- package/dist/llm/providers/endpoints/kkaiapi.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/kkaiapi.js +44 -0
- package/dist/llm/providers/endpoints/kkaiapi.js.map +1 -0
- package/dist/llm/providers/endpoints/lmstudio.d.ts +10 -0
- package/dist/llm/providers/endpoints/lmstudio.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/lmstudio.js +15 -0
- package/dist/llm/providers/endpoints/lmstudio.js.map +1 -0
- package/dist/llm/providers/endpoints/longcat.d.ts +9 -0
- package/dist/llm/providers/endpoints/longcat.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/longcat.js +18 -0
- package/dist/llm/providers/endpoints/longcat.js.map +1 -0
- package/dist/llm/providers/endpoints/minimax.d.ts +15 -0
- package/dist/llm/providers/endpoints/minimax.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/minimax.js +27 -0
- package/dist/llm/providers/endpoints/minimax.js.map +1 -0
- package/dist/llm/providers/endpoints/minimaxCodingPlan.d.ts +9 -0
- package/dist/llm/providers/endpoints/minimaxCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/minimaxCodingPlan.js +21 -0
- package/dist/llm/providers/endpoints/minimaxCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/mistral.d.ts +11 -0
- package/dist/llm/providers/endpoints/mistral.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/mistral.js +31 -0
- package/dist/llm/providers/endpoints/mistral.js.map +1 -0
- package/dist/llm/providers/endpoints/modelscope.d.ts +10 -0
- package/dist/llm/providers/endpoints/modelscope.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/modelscope.js +22 -0
- package/dist/llm/providers/endpoints/modelscope.js.map +1 -0
- package/dist/llm/providers/endpoints/moonshot.d.ts +12 -0
- package/dist/llm/providers/endpoints/moonshot.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/moonshot.js +29 -0
- package/dist/llm/providers/endpoints/moonshot.js.map +1 -0
- package/dist/llm/providers/endpoints/newapi.d.ts +14 -0
- package/dist/llm/providers/endpoints/newapi.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/newapi.js +14 -0
- package/dist/llm/providers/endpoints/newapi.js.map +1 -0
- package/dist/llm/providers/endpoints/ollama.d.ts +12 -0
- package/dist/llm/providers/endpoints/ollama.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/ollama.js +63 -0
- package/dist/llm/providers/endpoints/ollama.js.map +1 -0
- package/dist/llm/providers/endpoints/openai.d.ts +12 -0
- package/dist/llm/providers/endpoints/openai.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/openai.js +67 -0
- package/dist/llm/providers/endpoints/openai.js.map +1 -0
- package/dist/llm/providers/endpoints/opencodeCodingPlan.d.ts +9 -0
- package/dist/llm/providers/endpoints/opencodeCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/opencodeCodingPlan.js +23 -0
- package/dist/llm/providers/endpoints/opencodeCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/openrouter.d.ts +15 -0
- package/dist/llm/providers/endpoints/openrouter.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/openrouter.js +75 -0
- package/dist/llm/providers/endpoints/openrouter.js.map +1 -0
- package/dist/llm/providers/endpoints/ppio.d.ts +15 -0
- package/dist/llm/providers/endpoints/ppio.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/ppio.js +73 -0
- package/dist/llm/providers/endpoints/ppio.js.map +1 -0
- package/dist/llm/providers/endpoints/qiniu.d.ts +10 -0
- package/dist/llm/providers/endpoints/qiniu.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/qiniu.js +24 -0
- package/dist/llm/providers/endpoints/qiniu.js.map +1 -0
- package/dist/llm/providers/endpoints/sensenova.d.ts +10 -0
- package/dist/llm/providers/endpoints/sensenova.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/sensenova.js +37 -0
- package/dist/llm/providers/endpoints/sensenova.js.map +1 -0
- package/dist/llm/providers/endpoints/siliconcloud.d.ts +14 -0
- package/dist/llm/providers/endpoints/siliconcloud.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/siliconcloud.js +114 -0
- package/dist/llm/providers/endpoints/siliconcloud.js.map +1 -0
- package/dist/llm/providers/endpoints/spark.d.ts +14 -0
- package/dist/llm/providers/endpoints/spark.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/spark.js +21 -0
- package/dist/llm/providers/endpoints/spark.js.map +1 -0
- package/dist/llm/providers/endpoints/stepfun.d.ts +10 -0
- package/dist/llm/providers/endpoints/stepfun.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/stepfun.js +27 -0
- package/dist/llm/providers/endpoints/stepfun.js.map +1 -0
- package/dist/llm/providers/endpoints/tencentcloud.d.ts +10 -0
- package/dist/llm/providers/endpoints/tencentcloud.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/tencentcloud.js +17 -0
- package/dist/llm/providers/endpoints/tencentcloud.js.map +1 -0
- package/dist/llm/providers/endpoints/volcengine.d.ts +10 -0
- package/dist/llm/providers/endpoints/volcengine.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/volcengine.js +44 -0
- package/dist/llm/providers/endpoints/volcengine.js.map +1 -0
- package/dist/llm/providers/endpoints/volcengineCodingPlan.d.ts +19 -0
- package/dist/llm/providers/endpoints/volcengineCodingPlan.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/volcengineCodingPlan.js +25 -0
- package/dist/llm/providers/endpoints/volcengineCodingPlan.js.map +1 -0
- package/dist/llm/providers/endpoints/wenxin.d.ts +10 -0
- package/dist/llm/providers/endpoints/wenxin.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/wenxin.js +98 -0
- package/dist/llm/providers/endpoints/wenxin.js.map +1 -0
- package/dist/llm/providers/endpoints/xai.d.ts +11 -0
- package/dist/llm/providers/endpoints/xai.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/xai.js +25 -0
- package/dist/llm/providers/endpoints/xai.js.map +1 -0
- package/dist/llm/providers/endpoints/xiaomimimo.d.ts +12 -0
- package/dist/llm/providers/endpoints/xiaomimimo.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/xiaomimimo.js +16 -0
- package/dist/llm/providers/endpoints/xiaomimimo.js.map +1 -0
- package/dist/llm/providers/endpoints/zeroone.d.ts +10 -0
- package/dist/llm/providers/endpoints/zeroone.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/zeroone.js +26 -0
- package/dist/llm/providers/endpoints/zeroone.js.map +1 -0
- package/dist/llm/providers/endpoints/zhipu.d.ts +12 -0
- package/dist/llm/providers/endpoints/zhipu.d.ts.map +1 -0
- package/dist/llm/providers/endpoints/zhipu.js +51 -0
- package/dist/llm/providers/endpoints/zhipu.js.map +1 -0
- package/dist/llm/providers/index.d.ts +5 -0
- package/dist/llm/providers/index.d.ts.map +1 -0
- package/dist/llm/providers/index.js +64 -0
- package/dist/llm/providers/index.js.map +1 -0
- package/dist/llm/providers/lookup.d.ts +16 -0
- package/dist/llm/providers/lookup.d.ts.map +1 -0
- package/dist/llm/providers/lookup.js +68 -0
- package/dist/llm/providers/lookup.js.map +1 -0
- package/dist/llm/providers/probe.d.ts +11 -0
- package/dist/llm/providers/probe.d.ts.map +1 -0
- package/dist/llm/providers/probe.js +24 -0
- package/dist/llm/providers/probe.js.map +1 -0
- package/dist/llm/providers/types.d.ts +71 -0
- package/dist/llm/providers/types.d.ts.map +1 -0
- package/dist/llm/providers/types.js +9 -0
- package/dist/llm/providers/types.js.map +1 -0
- package/dist/llm/providers/verify.d.ts +27 -0
- package/dist/llm/providers/verify.d.ts.map +1 -0
- package/dist/llm/providers/verify.js +77 -0
- package/dist/llm/providers/verify.js.map +1 -0
- package/dist/llm/secrets.d.ts +9 -0
- package/dist/llm/secrets.d.ts.map +1 -0
- package/dist/llm/secrets.js +56 -0
- package/dist/llm/secrets.js.map +1 -0
- package/dist/llm/service-presets.d.ts +45 -0
- package/dist/llm/service-presets.d.ts.map +1 -0
- package/dist/llm/service-presets.js +180 -0
- package/dist/llm/service-presets.js.map +1 -0
- package/dist/llm/service-resolver.d.ts +10 -0
- package/dist/llm/service-resolver.d.ts.map +1 -0
- package/dist/llm/service-resolver.js +62 -0
- package/dist/llm/service-resolver.js.map +1 -0
- package/dist/llm/think-tag-stripper.d.ts +15 -0
- package/dist/llm/think-tag-stripper.d.ts.map +1 -0
- package/dist/llm/think-tag-stripper.js +60 -0
- package/dist/llm/think-tag-stripper.js.map +1 -0
- package/dist/materials/ingest.d.ts +31 -0
- package/dist/materials/ingest.d.ts.map +1 -0
- package/dist/materials/ingest.js +224 -0
- package/dist/materials/ingest.js.map +1 -0
- package/dist/materials/retrieve.d.ts +20 -0
- package/dist/materials/retrieve.d.ts.map +1 -0
- package/dist/materials/retrieve.js +109 -0
- package/dist/materials/retrieve.js.map +1 -0
- package/dist/models/book-rules.d.ts +142 -0
- package/dist/models/book-rules.d.ts.map +1 -0
- package/dist/models/book-rules.js +286 -0
- package/dist/models/book-rules.js.map +1 -0
- package/dist/models/book.d.ts +94 -0
- package/dist/models/book.d.ts.map +1 -0
- package/dist/models/book.js +79 -0
- package/dist/models/book.js.map +1 -0
- package/dist/models/chapter.d.ts +131 -0
- package/dist/models/chapter.d.ts.map +1 -0
- package/dist/models/chapter.js +38 -0
- package/dist/models/chapter.js.map +1 -0
- package/dist/models/context-compression.d.ts +13 -0
- package/dist/models/context-compression.d.ts.map +1 -0
- package/dist/models/context-compression.js +2 -0
- package/dist/models/context-compression.js.map +1 -0
- package/dist/models/detection.d.ts +25 -0
- package/dist/models/detection.d.ts.map +1 -0
- package/dist/models/detection.js +2 -0
- package/dist/models/detection.js.map +1 -0
- package/dist/models/genre-profile.d.ts +45 -0
- package/dist/models/genre-profile.d.ts.map +1 -0
- package/dist/models/genre-profile.js +26 -0
- package/dist/models/genre-profile.js.map +1 -0
- package/dist/models/input-governance.d.ts +437 -0
- package/dist/models/input-governance.d.ts.map +1 -0
- package/dist/models/input-governance.js +104 -0
- package/dist/models/input-governance.js.map +1 -0
- package/dist/models/length-governance.d.ts +85 -0
- package/dist/models/length-governance.d.ts.map +1 -0
- package/dist/models/length-governance.js +31 -0
- package/dist/models/length-governance.js.map +1 -0
- package/dist/models/play.d.ts +530 -0
- package/dist/models/play.d.ts.map +1 -0
- package/dist/models/play.js +318 -0
- package/dist/models/play.js.map +1 -0
- package/dist/models/project.d.ts +797 -0
- package/dist/models/project.d.ts.map +1 -0
- package/dist/models/project.js +152 -0
- package/dist/models/project.js.map +1 -0
- package/dist/models/runtime-state.d.ts +708 -0
- package/dist/models/runtime-state.d.ts.map +1 -0
- package/dist/models/runtime-state.js +105 -0
- package/dist/models/runtime-state.js.map +1 -0
- package/dist/models/state.d.ts +48 -0
- package/dist/models/state.d.ts.map +1 -0
- package/dist/models/state.js +6 -0
- package/dist/models/state.js.map +1 -0
- package/dist/models/style-profile.d.ts +16 -0
- package/dist/models/style-profile.d.ts.map +1 -0
- package/dist/models/style-profile.js +2 -0
- package/dist/models/style-profile.js.map +1 -0
- package/dist/notify/dispatcher.d.ts +10 -0
- package/dist/notify/dispatcher.d.ts.map +1 -0
- package/dist/notify/dispatcher.js +67 -0
- package/dist/notify/dispatcher.js.map +1 -0
- package/dist/notify/feishu.d.ts +6 -0
- package/dist/notify/feishu.d.ts.map +1 -0
- package/dist/notify/feishu.js +32 -0
- package/dist/notify/feishu.js.map +1 -0
- package/dist/notify/format.d.ts +12 -0
- package/dist/notify/format.d.ts.map +1 -0
- package/dist/notify/format.js +11 -0
- package/dist/notify/format.js.map +1 -0
- package/dist/notify/telegram.d.ts +7 -0
- package/dist/notify/telegram.d.ts.map +1 -0
- package/dist/notify/telegram.js +18 -0
- package/dist/notify/telegram.js.map +1 -0
- package/dist/notify/webhook.d.ts +15 -0
- package/dist/notify/webhook.d.ts.map +1 -0
- package/dist/notify/webhook.js +28 -0
- package/dist/notify/webhook.js.map +1 -0
- package/dist/notify/wechat-work.d.ts +6 -0
- package/dist/notify/wechat-work.d.ts.map +1 -0
- package/dist/notify/wechat-work.js +15 -0
- package/dist/notify/wechat-work.js.map +1 -0
- package/dist/pipeline/chapter-persistence.d.ts +33 -0
- package/dist/pipeline/chapter-persistence.d.ts.map +1 -0
- package/dist/pipeline/chapter-persistence.js +39 -0
- package/dist/pipeline/chapter-persistence.js.map +1 -0
- package/dist/pipeline/chapter-review-cycle.d.ts +81 -0
- package/dist/pipeline/chapter-review-cycle.d.ts.map +1 -0
- package/dist/pipeline/chapter-review-cycle.js +190 -0
- package/dist/pipeline/chapter-review-cycle.js.map +1 -0
- package/dist/pipeline/chapter-state-recovery.d.ts +61 -0
- package/dist/pipeline/chapter-state-recovery.d.ts.map +1 -0
- package/dist/pipeline/chapter-state-recovery.js +135 -0
- package/dist/pipeline/chapter-state-recovery.js.map +1 -0
- package/dist/pipeline/chapter-truth-validation.d.ts +42 -0
- package/dist/pipeline/chapter-truth-validation.d.ts.map +1 -0
- package/dist/pipeline/chapter-truth-validation.js +93 -0
- package/dist/pipeline/chapter-truth-validation.js.map +1 -0
- package/dist/pipeline/detection-runner.d.ts +31 -0
- package/dist/pipeline/detection-runner.d.ts.map +1 -0
- package/dist/pipeline/detection-runner.js +109 -0
- package/dist/pipeline/detection-runner.js.map +1 -0
- package/dist/pipeline/persisted-governed-plan.d.ts +5 -0
- package/dist/pipeline/persisted-governed-plan.d.ts.map +1 -0
- package/dist/pipeline/persisted-governed-plan.js +249 -0
- package/dist/pipeline/persisted-governed-plan.js.map +1 -0
- package/dist/pipeline/runner.d.ts +334 -0
- package/dist/pipeline/runner.d.ts.map +1 -0
- package/dist/pipeline/runner.js +2974 -0
- package/dist/pipeline/runner.js.map +1 -0
- package/dist/pipeline/scheduler.d.ts +58 -0
- package/dist/pipeline/scheduler.d.ts.map +1 -0
- package/dist/pipeline/scheduler.js +322 -0
- package/dist/pipeline/scheduler.js.map +1 -0
- package/dist/pipeline/script-storyboard-runner.d.ts +130 -0
- package/dist/pipeline/script-storyboard-runner.d.ts.map +1 -0
- package/dist/pipeline/script-storyboard-runner.js +532 -0
- package/dist/pipeline/script-storyboard-runner.js.map +1 -0
- package/dist/pipeline/short-fiction-runner.d.ts +110 -0
- package/dist/pipeline/short-fiction-runner.d.ts.map +1 -0
- package/dist/pipeline/short-fiction-runner.js +864 -0
- package/dist/pipeline/short-fiction-runner.js.map +1 -0
- package/dist/play/play-agents.d.ts +72 -0
- package/dist/play/play-agents.d.ts.map +1 -0
- package/dist/play/play-agents.js +580 -0
- package/dist/play/play-agents.js.map +1 -0
- package/dist/play/play-db-factory.d.ts +9 -0
- package/dist/play/play-db-factory.d.ts.map +1 -0
- package/dist/play/play-db-factory.js +18 -0
- package/dist/play/play-db-factory.js.map +1 -0
- package/dist/play/play-db.d.ts +22 -0
- package/dist/play/play-db.d.ts.map +1 -0
- package/dist/play/play-db.js +248 -0
- package/dist/play/play-db.js.map +1 -0
- package/dist/play/play-file-db.d.ts +32 -0
- package/dist/play/play-file-db.d.ts.map +1 -0
- package/dist/play/play-file-db.js +156 -0
- package/dist/play/play-file-db.js.map +1 -0
- package/dist/play/play-image.d.ts +58 -0
- package/dist/play/play-image.d.ts.map +1 -0
- package/dist/play/play-image.js +142 -0
- package/dist/play/play-image.js.map +1 -0
- package/dist/play/play-reducer.d.ts +31 -0
- package/dist/play/play-reducer.d.ts.map +1 -0
- package/dist/play/play-reducer.js +261 -0
- package/dist/play/play-reducer.js.map +1 -0
- package/dist/play/play-runner.d.ts +116 -0
- package/dist/play/play-runner.d.ts.map +1 -0
- package/dist/play/play-runner.js +582 -0
- package/dist/play/play-runner.js.map +1 -0
- package/dist/play/play-store.d.ts +113 -0
- package/dist/play/play-store.d.ts.map +1 -0
- package/dist/play/play-store.js +314 -0
- package/dist/play/play-store.js.map +1 -0
- package/dist/production/harness.d.ts +57 -0
- package/dist/production/harness.d.ts.map +1 -0
- package/dist/production/harness.js +51 -0
- package/dist/production/harness.js.map +1 -0
- package/dist/prompts/builtin-prompts.d.ts +10 -0
- package/dist/prompts/builtin-prompts.d.ts.map +1 -0
- package/dist/prompts/builtin-prompts.js +157 -0
- package/dist/prompts/builtin-prompts.js.map +1 -0
- package/dist/prompts/index.d.ts +5 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +5 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/prompt-pack.d.ts +33 -0
- package/dist/prompts/prompt-pack.d.ts.map +1 -0
- package/dist/prompts/prompt-pack.js +82 -0
- package/dist/prompts/prompt-pack.js.map +1 -0
- package/dist/prompts/short-fiction.d.ts +58 -0
- package/dist/prompts/short-fiction.d.ts.map +1 -0
- package/dist/prompts/short-fiction.js +480 -0
- package/dist/prompts/short-fiction.js.map +1 -0
- package/dist/prompts/types.d.ts +22 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/prompts/types.js +9 -0
- package/dist/prompts/types.js.map +1 -0
- package/dist/references/book-references.d.ts +40 -0
- package/dist/references/book-references.d.ts.map +1 -0
- package/dist/references/book-references.js +207 -0
- package/dist/references/book-references.js.map +1 -0
- package/dist/references/reference-context.d.ts +26 -0
- package/dist/references/reference-context.d.ts.map +1 -0
- package/dist/references/reference-context.js +120 -0
- package/dist/references/reference-context.js.map +1 -0
- package/dist/retrieval/local-search.d.ts +38 -0
- package/dist/retrieval/local-search.d.ts.map +1 -0
- package/dist/retrieval/local-search.js +232 -0
- package/dist/retrieval/local-search.js.map +1 -0
- package/dist/skills/builtin-loader.d.ts +9 -0
- package/dist/skills/builtin-loader.d.ts.map +1 -0
- package/dist/skills/builtin-loader.js +24 -0
- package/dist/skills/builtin-loader.js.map +1 -0
- package/dist/skills/external-loader.d.ts +26 -0
- package/dist/skills/external-loader.d.ts.map +1 -0
- package/dist/skills/external-loader.js +186 -0
- package/dist/skills/external-loader.js.map +1 -0
- package/dist/skills/index.d.ts +6 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +6 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/production-bindings.d.ts +18 -0
- package/dist/skills/production-bindings.d.ts.map +1 -0
- package/dist/skills/production-bindings.js +37 -0
- package/dist/skills/production-bindings.js.map +1 -0
- package/dist/skills/registry.d.ts +6 -0
- package/dist/skills/registry.d.ts.map +1 -0
- package/dist/skills/registry.js +68 -0
- package/dist/skills/registry.js.map +1 -0
- package/dist/skills/types.d.ts +42 -0
- package/dist/skills/types.d.ts.map +1 -0
- package/dist/skills/types.js +10 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/state/chapter-delete.d.ts +30 -0
- package/dist/state/chapter-delete.d.ts.map +1 -0
- package/dist/state/chapter-delete.js +78 -0
- package/dist/state/chapter-delete.js.map +1 -0
- package/dist/state/chapter-word-sync.d.ts +30 -0
- package/dist/state/chapter-word-sync.d.ts.map +1 -0
- package/dist/state/chapter-word-sync.js +62 -0
- package/dist/state/chapter-word-sync.js.map +1 -0
- package/dist/state/chapter-workspace.d.ts +15 -0
- package/dist/state/chapter-workspace.d.ts.map +1 -0
- package/dist/state/chapter-workspace.js +118 -0
- package/dist/state/chapter-workspace.js.map +1 -0
- package/dist/state/manager.d.ts +70 -0
- package/dist/state/manager.d.ts.map +1 -0
- package/dist/state/manager.js +727 -0
- package/dist/state/manager.js.map +1 -0
- package/dist/state/memory-db.d.ts +83 -0
- package/dist/state/memory-db.d.ts.map +1 -0
- package/dist/state/memory-db.js +249 -0
- package/dist/state/memory-db.js.map +1 -0
- package/dist/state/runtime-state-store.d.ts +38 -0
- package/dist/state/runtime-state-store.d.ts.map +1 -0
- package/dist/state/runtime-state-store.js +164 -0
- package/dist/state/runtime-state-store.js.map +1 -0
- package/dist/state/state-bootstrap.d.ts +21 -0
- package/dist/state/state-bootstrap.d.ts.map +1 -0
- package/dist/state/state-bootstrap.js +476 -0
- package/dist/state/state-bootstrap.js.map +1 -0
- package/dist/state/state-projections.d.ts +7 -0
- package/dist/state/state-projections.d.ts.map +1 -0
- package/dist/state/state-projections.js +209 -0
- package/dist/state/state-projections.js.map +1 -0
- package/dist/state/state-reducer.d.ts +13 -0
- package/dist/state/state-reducer.d.ts.map +1 -0
- package/dist/state/state-reducer.js +193 -0
- package/dist/state/state-reducer.js.map +1 -0
- package/dist/state/state-validator.d.ts +12 -0
- package/dist/state/state-validator.d.ts.map +1 -0
- package/dist/state/state-validator.js +67 -0
- package/dist/state/state-validator.js.map +1 -0
- package/dist/translation/epub.d.ts +10 -0
- package/dist/translation/epub.d.ts.map +1 -0
- package/dist/translation/epub.js +86 -0
- package/dist/translation/epub.js.map +1 -0
- package/dist/translation/export.d.ts +6 -0
- package/dist/translation/export.d.ts.map +1 -0
- package/dist/translation/export.js +68 -0
- package/dist/translation/export.js.map +1 -0
- package/dist/translation/index.d.ts +9 -0
- package/dist/translation/index.d.ts.map +1 -0
- package/dist/translation/index.js +9 -0
- package/dist/translation/index.js.map +1 -0
- package/dist/translation/llm-model.d.ts +11 -0
- package/dist/translation/llm-model.d.ts.map +1 -0
- package/dist/translation/llm-model.js +135 -0
- package/dist/translation/llm-model.js.map +1 -0
- package/dist/translation/project.d.ts +3 -0
- package/dist/translation/project.d.ts.map +1 -0
- package/dist/translation/project.js +76 -0
- package/dist/translation/project.js.map +1 -0
- package/dist/translation/run-store.d.ts +12 -0
- package/dist/translation/run-store.d.ts.map +1 -0
- package/dist/translation/run-store.js +69 -0
- package/dist/translation/run-store.js.map +1 -0
- package/dist/translation/runner.d.ts +6 -0
- package/dist/translation/runner.d.ts.map +1 -0
- package/dist/translation/runner.js +155 -0
- package/dist/translation/runner.js.map +1 -0
- package/dist/translation/source.d.ts +12 -0
- package/dist/translation/source.d.ts.map +1 -0
- package/dist/translation/source.js +66 -0
- package/dist/translation/source.js.map +1 -0
- package/dist/translation/text.d.ts +10 -0
- package/dist/translation/text.d.ts.map +1 -0
- package/dist/translation/text.js +110 -0
- package/dist/translation/text.js.map +1 -0
- package/dist/translation/types.d.ts +96 -0
- package/dist/translation/types.d.ts.map +1 -0
- package/dist/translation/types.js +2 -0
- package/dist/translation/types.js.map +1 -0
- package/dist/utils/analytics.d.ts +39 -0
- package/dist/utils/analytics.d.ts.map +1 -0
- package/dist/utils/analytics.js +50 -0
- package/dist/utils/analytics.js.map +1 -0
- package/dist/utils/atomic-file-set.d.ts +12 -0
- package/dist/utils/atomic-file-set.d.ts.map +1 -0
- package/dist/utils/atomic-file-set.js +93 -0
- package/dist/utils/atomic-file-set.js.map +1 -0
- package/dist/utils/book-eval.d.ts +35 -0
- package/dist/utils/book-eval.d.ts.map +1 -0
- package/dist/utils/book-eval.js +116 -0
- package/dist/utils/book-eval.js.map +1 -0
- package/dist/utils/book-id.d.ts +4 -0
- package/dist/utils/book-id.d.ts.map +1 -0
- package/dist/utils/book-id.js +27 -0
- package/dist/utils/book-id.js.map +1 -0
- package/dist/utils/cadence-policy.d.ts +36 -0
- package/dist/utils/cadence-policy.d.ts.map +1 -0
- package/dist/utils/cadence-policy.js +38 -0
- package/dist/utils/cadence-policy.js.map +1 -0
- package/dist/utils/chapter-cadence.d.ts +34 -0
- package/dist/utils/chapter-cadence.d.ts.map +1 -0
- package/dist/utils/chapter-cadence.js +143 -0
- package/dist/utils/chapter-cadence.js.map +1 -0
- package/dist/utils/chapter-memo-parser.d.ts +22 -0
- package/dist/utils/chapter-memo-parser.d.ts.map +1 -0
- package/dist/utils/chapter-memo-parser.js +158 -0
- package/dist/utils/chapter-memo-parser.js.map +1 -0
- package/dist/utils/chapter-splitter.d.ts +18 -0
- package/dist/utils/chapter-splitter.d.ts.map +1 -0
- package/dist/utils/chapter-splitter.js +60 -0
- package/dist/utils/chapter-splitter.js.map +1 -0
- package/dist/utils/config-loader.d.ts +11 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +16 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/dist/utils/context-assembly.d.ts +29 -0
- package/dist/utils/context-assembly.d.ts.map +1 -0
- package/dist/utils/context-assembly.js +117 -0
- package/dist/utils/context-assembly.js.map +1 -0
- package/dist/utils/context-filter.d.ts +31 -0
- package/dist/utils/context-filter.d.ts.map +1 -0
- package/dist/utils/context-filter.js +163 -0
- package/dist/utils/context-filter.js.map +1 -0
- package/dist/utils/effective-llm-config.d.ts +34 -0
- package/dist/utils/effective-llm-config.d.ts.map +1 -0
- package/dist/utils/effective-llm-config.js +463 -0
- package/dist/utils/effective-llm-config.js.map +1 -0
- package/dist/utils/governed-context.d.ts +11 -0
- package/dist/utils/governed-context.d.ts.map +1 -0
- package/dist/utils/governed-context.js +42 -0
- package/dist/utils/governed-context.js.map +1 -0
- package/dist/utils/governed-working-set.d.ts +18 -0
- package/dist/utils/governed-working-set.d.ts.map +1 -0
- package/dist/utils/governed-working-set.js +296 -0
- package/dist/utils/governed-working-set.js.map +1 -0
- package/dist/utils/hook-arbiter.d.ts +16 -0
- package/dist/utils/hook-arbiter.d.ts.map +1 -0
- package/dist/utils/hook-arbiter.js +161 -0
- package/dist/utils/hook-arbiter.js.map +1 -0
- package/dist/utils/hook-governance.d.ts +26 -0
- package/dist/utils/hook-governance.d.ts.map +1 -0
- package/dist/utils/hook-governance.js +65 -0
- package/dist/utils/hook-governance.js.map +1 -0
- package/dist/utils/hook-health.d.ts +15 -0
- package/dist/utils/hook-health.d.ts.map +1 -0
- package/dist/utils/hook-health.js +131 -0
- package/dist/utils/hook-health.js.map +1 -0
- package/dist/utils/hook-ledger-validator.d.ts +82 -0
- package/dist/utils/hook-ledger-validator.d.ts.map +1 -0
- package/dist/utils/hook-ledger-validator.js +225 -0
- package/dist/utils/hook-ledger-validator.js.map +1 -0
- package/dist/utils/hook-lifecycle.d.ts +41 -0
- package/dist/utils/hook-lifecycle.d.ts.map +1 -0
- package/dist/utils/hook-lifecycle.js +183 -0
- package/dist/utils/hook-lifecycle.js.map +1 -0
- package/dist/utils/hook-policy.d.ts +57 -0
- package/dist/utils/hook-policy.d.ts.map +1 -0
- package/dist/utils/hook-policy.js +96 -0
- package/dist/utils/hook-policy.js.map +1 -0
- package/dist/utils/hook-promotion.d.ts +83 -0
- package/dist/utils/hook-promotion.d.ts.map +1 -0
- package/dist/utils/hook-promotion.js +241 -0
- package/dist/utils/hook-promotion.js.map +1 -0
- package/dist/utils/hook-stale-detection.d.ts +51 -0
- package/dist/utils/hook-stale-detection.d.ts.map +1 -0
- package/dist/utils/hook-stale-detection.js +125 -0
- package/dist/utils/hook-stale-detection.js.map +1 -0
- package/dist/utils/language.d.ts +19 -0
- package/dist/utils/language.d.ts.map +1 -0
- package/dist/utils/language.js +29 -0
- package/dist/utils/language.js.map +1 -0
- package/dist/utils/length-metrics.d.ts +12 -0
- package/dist/utils/length-metrics.d.ts.map +1 -0
- package/dist/utils/length-metrics.js +85 -0
- package/dist/utils/length-metrics.js.map +1 -0
- package/dist/utils/llm-endpoint-auth.d.ts +5 -0
- package/dist/utils/llm-endpoint-auth.d.ts.map +1 -0
- package/dist/utils/llm-endpoint-auth.js +36 -0
- package/dist/utils/llm-endpoint-auth.js.map +1 -0
- package/dist/utils/llm-env.d.ts +14 -0
- package/dist/utils/llm-env.d.ts.map +1 -0
- package/dist/utils/llm-env.js +53 -0
- package/dist/utils/llm-env.js.map +1 -0
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +79 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/long-span-fatigue.d.ts +28 -0
- package/dist/utils/long-span-fatigue.d.ts.map +1 -0
- package/dist/utils/long-span-fatigue.js +406 -0
- package/dist/utils/long-span-fatigue.js.map +1 -0
- package/dist/utils/memory-retrieval.d.ts +71 -0
- package/dist/utils/memory-retrieval.d.ts.map +1 -0
- package/dist/utils/memory-retrieval.js +360 -0
- package/dist/utils/memory-retrieval.js.map +1 -0
- package/dist/utils/narrative-control.d.ts +16 -0
- package/dist/utils/narrative-control.d.ts.map +1 -0
- package/dist/utils/narrative-control.js +131 -0
- package/dist/utils/narrative-control.js.map +1 -0
- package/dist/utils/outline-paths.d.ts +63 -0
- package/dist/utils/outline-paths.d.ts.map +1 -0
- package/dist/utils/outline-paths.js +311 -0
- package/dist/utils/outline-paths.js.map +1 -0
- package/dist/utils/path-safety.d.ts +2 -0
- package/dist/utils/path-safety.d.ts.map +1 -0
- package/dist/utils/path-safety.js +11 -0
- package/dist/utils/path-safety.js.map +1 -0
- package/dist/utils/planning-materials.d.ts +35 -0
- package/dist/utils/planning-materials.d.ts.map +1 -0
- package/dist/utils/planning-materials.js +123 -0
- package/dist/utils/planning-materials.js.map +1 -0
- package/dist/utils/posix-path.d.ts +2 -0
- package/dist/utils/posix-path.d.ts.map +1 -0
- package/dist/utils/posix-path.js +7 -0
- package/dist/utils/posix-path.js.map +1 -0
- package/dist/utils/pov-filter.d.ts +30 -0
- package/dist/utils/pov-filter.d.ts.map +1 -0
- package/dist/utils/pov-filter.js +129 -0
- package/dist/utils/pov-filter.js.map +1 -0
- package/dist/utils/proxy-fetch.d.ts +9 -0
- package/dist/utils/proxy-fetch.d.ts.map +1 -0
- package/dist/utils/proxy-fetch.js +31 -0
- package/dist/utils/proxy-fetch.js.map +1 -0
- package/dist/utils/runtime-writer.d.ts +14 -0
- package/dist/utils/runtime-writer.d.ts.map +1 -0
- package/dist/utils/runtime-writer.js +21 -0
- package/dist/utils/runtime-writer.js.map +1 -0
- package/dist/utils/spot-fix-patches.d.ts +21 -0
- package/dist/utils/spot-fix-patches.d.ts.map +1 -0
- package/dist/utils/spot-fix-patches.js +148 -0
- package/dist/utils/spot-fix-patches.js.map +1 -0
- package/dist/utils/story-markdown.d.ts +13 -0
- package/dist/utils/story-markdown.d.ts.map +1 -0
- package/dist/utils/story-markdown.js +320 -0
- package/dist/utils/story-markdown.js.map +1 -0
- package/dist/utils/web-search.d.ts +28 -0
- package/dist/utils/web-search.d.ts.map +1 -0
- package/dist/utils/web-search.js +72 -0
- package/dist/utils/web-search.js.map +1 -0
- package/dist/utils/writing-methodology.d.ts +10 -0
- package/dist/utils/writing-methodology.d.ts.map +1 -0
- package/dist/utils/writing-methodology.js +163 -0
- package/dist/utils/writing-methodology.js.map +1 -0
- package/genres/cozy.md +43 -0
- package/genres/cultivation.md +42 -0
- package/genres/dungeon-core.md +40 -0
- package/genres/horror.md +51 -0
- package/genres/isekai.md +43 -0
- package/genres/litrpg.md +43 -0
- package/genres/other.md +24 -0
- package/genres/progression.md +41 -0
- package/genres/romantasy.md +45 -0
- package/genres/sci-fi.md +42 -0
- package/genres/system-apocalypse.md +40 -0
- package/genres/tower-climber.md +41 -0
- package/genres/urban.md +53 -0
- package/genres/xianxia.md +46 -0
- package/genres/xuanhuan.md +64 -0
- package/package.json +76 -0
- package/skills/inkos-interactive-film/SKILL.md +16 -0
- package/skills/inkos-interactive-film/references/branch-state-craft.md +13 -0
- package/skills/inkos-long-market-research/SKILL.md +16 -0
- package/skills/inkos-long-market-research/references/research-rubric.md +19 -0
- package/skills/inkos-long-story-analysis/SKILL.md +17 -0
- package/skills/inkos-long-story-analysis/references/analysis-lens.md +16 -0
- package/skills/inkos-long-writing/SKILL.md +17 -0
- package/skills/inkos-long-writing/references/reader-contract-and-progression.md +13 -0
- package/skills/inkos-long-writing/references/scene-dialogue-and-length-budget.md +13 -0
- package/skills/inkos-play-world/SKILL.md +17 -0
- package/skills/inkos-play-world/references/world-turn-continuity.md +13 -0
- package/skills/inkos-script-writing/SKILL.md +15 -0
- package/skills/inkos-script-writing/references/episode-scene-craft.md +13 -0
- package/skills/inkos-short-market-research/SKILL.md +17 -0
- package/skills/inkos-short-market-research/references/short-market-rubric.md +16 -0
- package/skills/inkos-short-story-analysis/SKILL.md +16 -0
- package/skills/inkos-short-story-analysis/references/deconstruction-template.md +14 -0
- package/skills/inkos-short-writing/SKILL.md +18 -0
- package/skills/inkos-short-writing/references/production-checklist.md +19 -0
- package/skills/inkos-story-cover/SKILL.md +17 -0
- package/skills/inkos-story-cover/references/cover-brief.md +16 -0
- package/skills/inkos-story-deslop/SKILL.md +16 -0
- package/skills/inkos-story-deslop/references/semantic-cleanup.md +13 -0
- package/skills/inkos-story-import/SKILL.md +19 -0
- package/skills/inkos-story-import/references/reconstruction-rubric.md +13 -0
- package/skills/inkos-story-review/SKILL.md +16 -0
- package/skills/inkos-story-review/references/review-matrix.md +21 -0
- package/skills/inkos-storyboard/SKILL.md +15 -0
- package/skills/inkos-storyboard/references/shot-continuity.md +13 -0
- package/skills/inkos-translation/SKILL.md +14 -0
- package/skills/inkos-translation/references/long-form-consistency.md +13 -0
|
@@ -0,0 +1,3083 @@
|
|
|
1
|
+
import { Type } from "@mariozechner/pi-ai";
|
|
2
|
+
import { ArchitectIncompleteFoundationError } from "../agents/architect.js";
|
|
3
|
+
import { defaultChapterLength } from "../utils/length-metrics.js";
|
|
4
|
+
import { inferLanguage } from "../utils/language.js";
|
|
5
|
+
import { mkdir, readFile, writeFile, readdir, stat } from "node:fs/promises";
|
|
6
|
+
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
7
|
+
import { StateManager } from "../state/manager.js";
|
|
8
|
+
import { deleteLatestChapter } from "../state/chapter-delete.js";
|
|
9
|
+
import { assertSafeTruthFileName, createInteractionToolsFromDeps } from "../interaction/project-tools.js";
|
|
10
|
+
import { writeExportArtifact } from "../interaction/export-artifact.js";
|
|
11
|
+
import { assertSafeBookId, deriveBookIdFromTitle } from "../utils/book-id.js";
|
|
12
|
+
import { safeChildPath } from "../utils/path-safety.js";
|
|
13
|
+
import { normalizePlatformId, normalizePlatformOrOther, } from "../models/book.js";
|
|
14
|
+
import { generateShortFictionCover, runShortFictionProduction } from "../pipeline/short-fiction-runner.js";
|
|
15
|
+
import { runInteractiveFilmCreation, runScriptCreation, runStoryboardCreation } from "../pipeline/script-storyboard-runner.js";
|
|
16
|
+
import { createTranslationProjectFromFile } from "../translation/index.js";
|
|
17
|
+
import { runResearchReport } from "../agents/researcher.js";
|
|
18
|
+
import { ingestMaterial } from "../materials/ingest.js";
|
|
19
|
+
import { retrieveMaterials } from "../materials/retrieve.js";
|
|
20
|
+
import { bindBookReference, listBookReferences, unbindBookReference, } from "../references/book-references.js";
|
|
21
|
+
import { loadChaptersFromPath } from "./chapter-import-source.js";
|
|
22
|
+
import { createPlayDB } from "../play/play-db-factory.js";
|
|
23
|
+
import { PlayRunner } from "../play/play-runner.js";
|
|
24
|
+
import { PlayStore } from "../play/play-store.js";
|
|
25
|
+
import { ActionPayloadSchema, shortRunCharsPerChapterError, shortRunCharsPerChapterRange, } from "../interaction/action-envelope.js";
|
|
26
|
+
import { ResearchSearchConfigSchema } from "../models/project.js";
|
|
27
|
+
import { searchWeb } from "../utils/web-search.js";
|
|
28
|
+
import { runAsWorkflowTrajectory, runWithAgentTrajectoryRole, } from "../llm/agent-trajectory.js";
|
|
29
|
+
import { activatedSkillIds, mergeActivatedSkillGuidance, } from "../skills/production-bindings.js";
|
|
30
|
+
function textResult(text, details) {
|
|
31
|
+
return { content: [{ type: "text", text }], details: details };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a user-supplied relative path against the books root and guard
|
|
35
|
+
* against path-traversal (../ etc.).
|
|
36
|
+
*/
|
|
37
|
+
function safeBooksPath(booksRoot, relativePath) {
|
|
38
|
+
return safeChildPath(booksRoot, relativePath);
|
|
39
|
+
}
|
|
40
|
+
function resolveToolBookId(toolName, paramsBookId, activeBookId) {
|
|
41
|
+
const resolvedBookId = paramsBookId ?? activeBookId ?? undefined;
|
|
42
|
+
if (!resolvedBookId) {
|
|
43
|
+
throw new Error(`${toolName} requires bookId when there is no active book.`);
|
|
44
|
+
}
|
|
45
|
+
const safeBookId = assertSafeBookId(resolvedBookId, `${toolName}.bookId`);
|
|
46
|
+
if (paramsBookId && activeBookId && safeBookId !== activeBookId) {
|
|
47
|
+
throw new Error(`${toolName}.bookId must match the active book.`);
|
|
48
|
+
}
|
|
49
|
+
return safeBookId;
|
|
50
|
+
}
|
|
51
|
+
function buildAgentBookConfig(input, defaults = {}) {
|
|
52
|
+
const now = new Date().toISOString();
|
|
53
|
+
const id = deriveBookIdFromTitle(input.title);
|
|
54
|
+
if (!id)
|
|
55
|
+
throw new Error(`Could not derive a valid book id from title: ${JSON.stringify(input.title)}`);
|
|
56
|
+
return {
|
|
57
|
+
id,
|
|
58
|
+
title: input.title.trim(),
|
|
59
|
+
platform: normalizePlatformOrOther(input.platform),
|
|
60
|
+
genre: input.genre?.trim() || "other",
|
|
61
|
+
status: "outlining",
|
|
62
|
+
targetChapters: input.targetChapters ?? defaults.targetChapters ?? 200,
|
|
63
|
+
chapterWordCount: input.chapterWordCount
|
|
64
|
+
?? defaults.chapterWordCount
|
|
65
|
+
?? defaultChapterLength(input.language === "en" ? "en" : "zh"),
|
|
66
|
+
...(input.language ? { language: input.language } : {}),
|
|
67
|
+
...(input.parentBookId ? { parentBookId: input.parentBookId } : {}),
|
|
68
|
+
...(input.fanficMode ? { fanficMode: input.fanficMode } : {}),
|
|
69
|
+
createdAt: now,
|
|
70
|
+
updatedAt: now,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async function assertBookDoesNotExist(projectRoot, bookId) {
|
|
74
|
+
try {
|
|
75
|
+
await stat(new StateManager(projectRoot).bookDir(bookId));
|
|
76
|
+
throw new Error(`Book "${bookId}" already exists.`);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error.code === "ENOENT")
|
|
80
|
+
return;
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function loadCreationSource(input) {
|
|
85
|
+
if (input.sourceText?.trim()) {
|
|
86
|
+
return {
|
|
87
|
+
text: input.sourceText.trim(),
|
|
88
|
+
name: input.sourceName?.trim() || "source",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (!input.sourcePath?.trim()) {
|
|
92
|
+
throw new Error("A sourceText or sourcePath is required.");
|
|
93
|
+
}
|
|
94
|
+
if (isAbsolute(input.sourcePath)) {
|
|
95
|
+
throw new Error("Creation sourcePath must be project-relative. Upload or ingest the file first.");
|
|
96
|
+
}
|
|
97
|
+
const sourcePath = safeChildPath(input.projectRoot, input.sourcePath);
|
|
98
|
+
const material = await ingestMaterial(input.projectRoot, {
|
|
99
|
+
sourceKind: "file",
|
|
100
|
+
filePath: sourcePath,
|
|
101
|
+
filename: basename(sourcePath),
|
|
102
|
+
title: input.sourceName?.trim() || basename(sourcePath).replace(/\.[^.]+$/u, ""),
|
|
103
|
+
purpose: input.purpose,
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
text: await readFile(join(input.projectRoot, material.markdownPath), "utf-8"),
|
|
107
|
+
name: input.sourceName?.trim() || material.title,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function createDeterministicInteractionTools(pipeline, projectRoot) {
|
|
111
|
+
const state = new StateManager(projectRoot);
|
|
112
|
+
return createInteractionToolsFromDeps(pipeline, state);
|
|
113
|
+
}
|
|
114
|
+
function closePlayDB(db) {
|
|
115
|
+
db.close?.();
|
|
116
|
+
}
|
|
117
|
+
// runnerFactory 注入的测试替身没有 close 方法,可选调用兜底。
|
|
118
|
+
function closePlayRunner(runner) {
|
|
119
|
+
runner?.close?.();
|
|
120
|
+
}
|
|
121
|
+
function safePlayId(value, fallback) {
|
|
122
|
+
const raw = (value?.trim() || fallback).slice(0, 80);
|
|
123
|
+
if (!raw || raw === "." || raw === ".." || raw.includes("/") || raw.includes("\\") || raw.includes("\0")) {
|
|
124
|
+
throw new Error(`Invalid play id: ${JSON.stringify(value)}`);
|
|
125
|
+
}
|
|
126
|
+
return raw;
|
|
127
|
+
}
|
|
128
|
+
const SuggestedActionParam = Type.Union([
|
|
129
|
+
Type.String({ description: "A short clickable player action." }),
|
|
130
|
+
Type.Object({
|
|
131
|
+
label: Type.Optional(Type.String({ description: "Short clickable player action." })),
|
|
132
|
+
action: Type.Optional(Type.String({ description: "Concrete action text." })),
|
|
133
|
+
text: Type.Optional(Type.String({ description: "Concrete action text." })),
|
|
134
|
+
title: Type.Optional(Type.String({ description: "Short action title." })),
|
|
135
|
+
description: Type.Optional(Type.String({ description: "Optional action description." })),
|
|
136
|
+
}, { description: "A model may describe an action as an object; InkOS will normalize it to one short action string." }),
|
|
137
|
+
], { description: "Suggested action as a string or small action object." });
|
|
138
|
+
function normalizeSuggestedActions(value) {
|
|
139
|
+
if (!Array.isArray(value))
|
|
140
|
+
return [];
|
|
141
|
+
const out = [];
|
|
142
|
+
for (const raw of value) {
|
|
143
|
+
const text = typeof raw === "string"
|
|
144
|
+
? raw
|
|
145
|
+
: raw.action ?? raw.label ?? raw.text ?? raw.title ?? raw.description ?? "";
|
|
146
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
147
|
+
if (normalized)
|
|
148
|
+
out.push(normalized);
|
|
149
|
+
if (out.length >= 4)
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// 1. Proposed Action Tool (propose_action)
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
const ProposeActionParams = Type.Object({
|
|
158
|
+
action: Type.Union([
|
|
159
|
+
Type.Literal("create_book"),
|
|
160
|
+
Type.Literal("short_run"),
|
|
161
|
+
Type.Literal("play_start"),
|
|
162
|
+
Type.Literal("generate_cover"),
|
|
163
|
+
Type.Literal("fanfic_init"),
|
|
164
|
+
Type.Literal("continuation_import"),
|
|
165
|
+
Type.Literal("spinoff_create"),
|
|
166
|
+
Type.Literal("style_imitation"),
|
|
167
|
+
Type.Literal("script_create"),
|
|
168
|
+
Type.Literal("storyboard_create"),
|
|
169
|
+
Type.Literal("interactive_film_create"),
|
|
170
|
+
Type.Literal("translation_create"),
|
|
171
|
+
Type.Literal("draft_structure"),
|
|
172
|
+
Type.Literal("connect_choice"),
|
|
173
|
+
Type.Literal("remove_node"),
|
|
174
|
+
], {
|
|
175
|
+
description: "The production or assisted Studio workflow the user appears to want, but which needs explicit confirmation from general chat.",
|
|
176
|
+
}),
|
|
177
|
+
instruction: Type.String({
|
|
178
|
+
description: "The exact production instruction to run after the user confirms. It must be self-contained: include title, story direction, active target, output directory, cover visual direction, or any referenced context that would otherwise be lost when switching sessions.",
|
|
179
|
+
}),
|
|
180
|
+
title: Type.Optional(Type.String({
|
|
181
|
+
description: "Short user-facing title for the confirmation card.",
|
|
182
|
+
})),
|
|
183
|
+
summary: Type.Optional(Type.String({
|
|
184
|
+
description: "One or two sentences explaining what will happen if the user confirms.",
|
|
185
|
+
})),
|
|
186
|
+
createBook: Type.Optional(Type.Object({
|
|
187
|
+
title: Type.String({
|
|
188
|
+
description: "Confirmed long-form book title.",
|
|
189
|
+
}),
|
|
190
|
+
genre: Type.Optional(Type.String({
|
|
191
|
+
description: "Confirmed book genre/category.",
|
|
192
|
+
})),
|
|
193
|
+
platform: Type.Optional(Type.Union([
|
|
194
|
+
Type.Literal("tomato"),
|
|
195
|
+
Type.Literal("qidian"),
|
|
196
|
+
Type.Literal("feilu"),
|
|
197
|
+
Type.Literal("other"),
|
|
198
|
+
], { description: "Confirmed target platform, e.g. tomato for 番茄." })),
|
|
199
|
+
language: Type.Optional(Type.Union([
|
|
200
|
+
Type.Literal("zh"),
|
|
201
|
+
Type.Literal("en"),
|
|
202
|
+
], { description: "Confirmed writing language." })),
|
|
203
|
+
targetChapters: Type.Optional(Type.Number({
|
|
204
|
+
description: "Confirmed total chapter count.",
|
|
205
|
+
})),
|
|
206
|
+
chapterWordCount: Type.Optional(Type.Number({
|
|
207
|
+
description: "Confirmed per-chapter length in the book's native unit.",
|
|
208
|
+
})),
|
|
209
|
+
}, { description: "Structured execution args for action=create_book. Put platform/length here; do not leave them only in instruction text." })),
|
|
210
|
+
shortRun: Type.Optional(Type.Object({
|
|
211
|
+
title: Type.String({
|
|
212
|
+
description: "Confirmed standalone short title or working title. The host uses it as the stable project identity.",
|
|
213
|
+
}),
|
|
214
|
+
direction: Type.String({
|
|
215
|
+
description: "Confirmed standalone short direction.",
|
|
216
|
+
}),
|
|
217
|
+
reference: Type.Optional(Type.String({
|
|
218
|
+
description: "Optional confirmed reference notes or constraints.",
|
|
219
|
+
})),
|
|
220
|
+
storyId: Type.Optional(Type.String({
|
|
221
|
+
description: "Optional confirmed output id under shorts/.",
|
|
222
|
+
})),
|
|
223
|
+
language: Type.Optional(Type.Union([
|
|
224
|
+
Type.Literal("zh"),
|
|
225
|
+
Type.Literal("en"),
|
|
226
|
+
], { description: "Output language of the short fiction. Fill the language the user asked the story to be written in; it may differ from the conversation language (e.g. a Chinese chat asking for an English short => en). When the user does not name one, it defaults to the conversation language." })),
|
|
227
|
+
chapters: Type.Optional(Type.Number({
|
|
228
|
+
description: "Confirmed complete short chapter count, 12-18.",
|
|
229
|
+
})),
|
|
230
|
+
charsPerChapter: Type.Optional(Type.Number({
|
|
231
|
+
minimum: 600,
|
|
232
|
+
maximum: 1200,
|
|
233
|
+
description: "Confirmed per-chapter length in the story language's native unit. zh shorts only accept 900-1200 Chinese characters; en shorts only accept 600-800 English words. Values outside the selected language's range are rejected before the task starts. Do not put total story length here.",
|
|
234
|
+
})),
|
|
235
|
+
cover: Type.Optional(Type.Boolean({
|
|
236
|
+
description: "Whether to attempt cover generation.",
|
|
237
|
+
})),
|
|
238
|
+
}, { description: "Structured execution args for action=short_run." })),
|
|
239
|
+
playStart: Type.Optional(Type.Object({
|
|
240
|
+
title: Type.String({ description: "Confirmed interactive world title." }),
|
|
241
|
+
premise: Type.String({ description: "Confirmed playable premise." }),
|
|
242
|
+
worldContract: Type.Optional(Type.String({
|
|
243
|
+
description: "Confirmed durable world contract in natural language: time semantics, role autonomy, object/clue/relationship rules, taboos, or other long-lived rules the user explicitly asked for. Do not invent RPG/level systems.",
|
|
244
|
+
})),
|
|
245
|
+
visualContract: Type.Optional(Type.String({
|
|
246
|
+
description: "Confirmed visual contract for Play illustrations in natural language. Only include user-defined visual semantics; do not invent game frames, colored tiers, UI, or stats.",
|
|
247
|
+
})),
|
|
248
|
+
mode: Type.Optional(Type.Union([
|
|
249
|
+
Type.Literal("open"),
|
|
250
|
+
Type.Literal("guided"),
|
|
251
|
+
], { description: "Confirmed play mode: open for free actions, guided for suggested choices." })),
|
|
252
|
+
initialScene: Type.String({
|
|
253
|
+
description: "Confirmed opening scene shown to the player after confirmation. It must be pure narrative prose, not a title/setup/rules summary, not a question prompt, and not an action/options list.",
|
|
254
|
+
}),
|
|
255
|
+
suggestedActions: Type.Optional(Type.Array(SuggestedActionParam, {
|
|
256
|
+
description: "Optional action springboards shown as separate UI chips. Do not include these in initialScene.",
|
|
257
|
+
})),
|
|
258
|
+
}, { description: "Structured execution args for action=play_start." })),
|
|
259
|
+
generateCover: Type.Optional(Type.Object({
|
|
260
|
+
title: Type.String({ description: "Confirmed cover title." }),
|
|
261
|
+
intro: Type.Optional(Type.String({ description: "Confirmed synopsis/hook for the cover." })),
|
|
262
|
+
sellingPoints: Type.Optional(Type.String({ description: "Confirmed selling points for the cover." })),
|
|
263
|
+
coverPrompt: Type.Optional(Type.String({ description: "Confirmed visual direction." })),
|
|
264
|
+
outputDir: Type.Optional(Type.String({ description: "Confirmed output directory." })),
|
|
265
|
+
}, { description: "Structured execution args for action=generate_cover." })),
|
|
266
|
+
scriptCreate: Type.Optional(Type.Object({
|
|
267
|
+
title: Type.String({ description: "Confirmed script project title." }),
|
|
268
|
+
sourceKind: Type.Optional(Type.String({ description: "Source type, e.g. novel excerpt, original idea, outline, existing script." })),
|
|
269
|
+
targetFormat: Type.Optional(Type.Union([
|
|
270
|
+
Type.Literal("vertical_short_drama"),
|
|
271
|
+
Type.Literal("screenplay"),
|
|
272
|
+
Type.Literal("audio_drama"),
|
|
273
|
+
Type.Literal("interactive_script"),
|
|
274
|
+
Type.Literal("general_script"),
|
|
275
|
+
], { description: "Confirmed script output format." })),
|
|
276
|
+
sourceText: Type.Optional(Type.String({ description: "User-provided source text. For long sources, prefer sourcePath instead of summarizing." })),
|
|
277
|
+
sourcePath: Type.Optional(Type.String({ description: "Optional project-relative source file path." })),
|
|
278
|
+
requirements: Type.Optional(Type.String({ description: "Confirmed script format, production constraints, tone, episode structure, or user preferences." })),
|
|
279
|
+
episodeCount: Type.Optional(Type.Number({ description: "Optional target episode/segment count." })),
|
|
280
|
+
episodeDuration: Type.Optional(Type.String({ description: "Optional per-episode/per-segment duration." })),
|
|
281
|
+
projectId: Type.Optional(Type.String({ description: "Optional output id under dramas/." })),
|
|
282
|
+
outDir: Type.Optional(Type.String({ description: "Optional project-relative output directory. Default dramas/." })),
|
|
283
|
+
}, { description: "Structured execution args for action=script_create." })),
|
|
284
|
+
storyboardCreate: Type.Optional(Type.Object({
|
|
285
|
+
title: Type.String({ description: "Confirmed storyboard project title." }),
|
|
286
|
+
sourceKind: Type.Optional(Type.String({ description: "Source type, e.g. script, novel excerpt, idea, scene list." })),
|
|
287
|
+
sourceText: Type.Optional(Type.String({ description: "User-provided source text. For long sources, prefer sourcePath instead of summarizing." })),
|
|
288
|
+
sourcePath: Type.Optional(Type.String({ description: "Optional project-relative source file path." })),
|
|
289
|
+
requirements: Type.Optional(Type.String({ description: "Confirmed shot/storyboard requirements." })),
|
|
290
|
+
visualStyle: Type.Optional(Type.String({ description: "Confirmed visual style, if the user specified one." })),
|
|
291
|
+
aspectRatio: Type.Optional(Type.String({ description: "Confirmed aspect ratio, e.g. 9:16, 16:9, 1:1." })),
|
|
292
|
+
granularity: Type.Optional(Type.String({ description: "Confirmed storyboard granularity." })),
|
|
293
|
+
maxShots: Type.Optional(Type.Number({ description: "Optional max shot count." })),
|
|
294
|
+
projectId: Type.Optional(Type.String({ description: "Optional output id under storyboards/." })),
|
|
295
|
+
outDir: Type.Optional(Type.String({ description: "Optional project-relative output directory. Default storyboards/." })),
|
|
296
|
+
}, { description: "Structured execution args for action=storyboard_create." })),
|
|
297
|
+
interactiveFilmCreate: Type.Optional(Type.Object({
|
|
298
|
+
title: Type.String({ description: "Confirmed interactive-film project title." }),
|
|
299
|
+
sourceKind: Type.Optional(Type.String({ description: "Source type, e.g. novel excerpt, script, outline, original idea." })),
|
|
300
|
+
sourceText: Type.Optional(Type.String({ description: "User-provided source text. For long sources, prefer sourcePath instead of summarizing." })),
|
|
301
|
+
sourcePath: Type.Optional(Type.String({ description: "Optional project-relative source file path." })),
|
|
302
|
+
requirements: Type.Optional(Type.String({ description: "Confirmed branching, variable/flag, ending, production, visual, or market requirements." })),
|
|
303
|
+
targetAudience: Type.Optional(Type.String({ description: "Confirmed target audience or market." })),
|
|
304
|
+
episodeCount: Type.Optional(Type.Number({ description: "Optional target episode/segment count." })),
|
|
305
|
+
episodeDuration: Type.Optional(Type.String({ description: "Optional per-episode/per-segment duration." })),
|
|
306
|
+
budget: Type.Optional(Type.String({ description: "Optional budget or production constraints." })),
|
|
307
|
+
referenceMode: Type.Optional(Type.String({ description: "Optional reference mode, e.g. 盛世天下-style multi-ending interactive drama." })),
|
|
308
|
+
projectId: Type.Optional(Type.String({ description: "Optional output id under interactive-films/." })),
|
|
309
|
+
outDir: Type.Optional(Type.String({ description: "Optional project-relative output directory. Default interactive-films/." })),
|
|
310
|
+
}, { description: "Structured execution args for action=interactive_film_create." })),
|
|
311
|
+
translationCreate: Type.Optional(Type.Object({
|
|
312
|
+
filePath: Type.String({ description: "Project-relative EPUB/PDF/TXT/Markdown source file path to translate." }),
|
|
313
|
+
sourceLanguage: Type.String({ description: "Source language as a human-readable name, e.g. Auto detect, Japanese, English, Chinese (Simplified), 繁体中文(台湾). Do not require ISO abbreviations." }),
|
|
314
|
+
targetLanguage: Type.String({ description: "Target language as a human-readable name, e.g. Chinese (Simplified), English, Japanese, Korean, Brazilian Portuguese. Do not require ISO abbreviations." }),
|
|
315
|
+
title: Type.Optional(Type.String({ description: "Optional translation project title." })),
|
|
316
|
+
segmentMaxChars: Type.Optional(Type.Number({ description: "Optional long-paragraph split threshold." })),
|
|
317
|
+
}, { description: "Structured execution args for action=translation_create." })),
|
|
318
|
+
fanficCreate: Type.Optional(Type.Object({
|
|
319
|
+
title: Type.String({ description: "Confirmed fanfiction book title." }),
|
|
320
|
+
sourceText: Type.Optional(Type.String({ description: "Provided canon/source text. Prefer sourcePath for uploaded or long files." })),
|
|
321
|
+
sourcePath: Type.Optional(Type.String({ description: "Project-relative uploaded canon/source file path." })),
|
|
322
|
+
sourceName: Type.Optional(Type.String({ description: "Human-readable source work name." })),
|
|
323
|
+
mode: Type.Optional(Type.Union([
|
|
324
|
+
Type.Literal("canon"),
|
|
325
|
+
Type.Literal("au"),
|
|
326
|
+
Type.Literal("ooc"),
|
|
327
|
+
Type.Literal("cp"),
|
|
328
|
+
], { description: "Confirmed fanfiction mode." })),
|
|
329
|
+
genre: Type.Optional(Type.String({ description: "Confirmed genre." })),
|
|
330
|
+
platform: Type.Optional(Type.Union([
|
|
331
|
+
Type.Literal("tomato"), Type.Literal("qidian"), Type.Literal("feilu"), Type.Literal("other"),
|
|
332
|
+
])),
|
|
333
|
+
language: Type.Optional(Type.Union([Type.Literal("zh"), Type.Literal("en")])),
|
|
334
|
+
targetChapters: Type.Optional(Type.Number({ description: "Confirmed total chapter count." })),
|
|
335
|
+
chapterWordCount: Type.Optional(Type.Number({ description: "Confirmed per-chapter length." })),
|
|
336
|
+
}, { description: "Structured execution args for action=fanfic_init. This creates the book directly after confirmation." })),
|
|
337
|
+
continuationImport: Type.Optional(Type.Object({
|
|
338
|
+
bookId: Type.Optional(Type.String({ description: "Existing target book id. Omit when creating a new continuation book." })),
|
|
339
|
+
title: Type.Optional(Type.String({ description: "New continuation book title when bookId is omitted." })),
|
|
340
|
+
sourcePath: Type.String({ description: "Project-relative uploaded novel file or chapter directory." }),
|
|
341
|
+
splitPattern: Type.Optional(Type.String({ description: "Optional custom chapter-heading regex source." })),
|
|
342
|
+
resumeFrom: Type.Optional(Type.Number({ description: "Resume interrupted replay from this 1-based chapter number." })),
|
|
343
|
+
genre: Type.Optional(Type.String({ description: "Genre for a newly created continuation book." })),
|
|
344
|
+
platform: Type.Optional(Type.Union([
|
|
345
|
+
Type.Literal("tomato"), Type.Literal("qidian"), Type.Literal("feilu"), Type.Literal("other"),
|
|
346
|
+
])),
|
|
347
|
+
language: Type.Optional(Type.Union([Type.Literal("zh"), Type.Literal("en")])),
|
|
348
|
+
targetChapters: Type.Optional(Type.Number({ description: "Target total chapters for a new book." })),
|
|
349
|
+
chapterWordCount: Type.Optional(Type.Number({ description: "Per-chapter length for a new book." })),
|
|
350
|
+
}, { description: "Structured execution args for action=continuation_import. This imports and rebuilds state directly after confirmation." })),
|
|
351
|
+
spinoffCreate: Type.Optional(Type.Object({
|
|
352
|
+
title: Type.String({ description: "Confirmed side-story title." }),
|
|
353
|
+
parentBookId: Type.String({ description: "Existing InkOS parent book id whose canon is inherited." }),
|
|
354
|
+
direction: Type.Optional(Type.String({ description: "Confirmed standalone side-story direction." })),
|
|
355
|
+
genre: Type.Optional(Type.String({ description: "Optional genre override; defaults to the parent book." })),
|
|
356
|
+
platform: Type.Optional(Type.Union([
|
|
357
|
+
Type.Literal("tomato"), Type.Literal("qidian"), Type.Literal("feilu"), Type.Literal("other"),
|
|
358
|
+
])),
|
|
359
|
+
language: Type.Optional(Type.Union([Type.Literal("zh"), Type.Literal("en")])),
|
|
360
|
+
targetChapters: Type.Optional(Type.Number({ description: "Optional chapter count; defaults to the parent book." })),
|
|
361
|
+
chapterWordCount: Type.Optional(Type.Number({ description: "Optional chapter length; defaults to the parent book." })),
|
|
362
|
+
}, { description: "Structured execution args for action=spinoff_create. This creates the side-story directly after confirmation." })),
|
|
363
|
+
imitationCreate: Type.Optional(Type.Object({
|
|
364
|
+
title: Type.String({ description: "Confirmed original imitation-project title." }),
|
|
365
|
+
referenceText: Type.Optional(Type.String({ description: "Reference prose. Prefer referencePath for uploaded or long files." })),
|
|
366
|
+
referencePath: Type.Optional(Type.String({ description: "Project-relative uploaded reference-work path." })),
|
|
367
|
+
storyIdea: Type.String({ description: "Confirmed original story idea; do not copy the reference plot." }),
|
|
368
|
+
sourceName: Type.Optional(Type.String({ description: "Human-readable reference work name." })),
|
|
369
|
+
genre: Type.Optional(Type.String({ description: "Confirmed genre." })),
|
|
370
|
+
platform: Type.Optional(Type.Union([
|
|
371
|
+
Type.Literal("tomato"), Type.Literal("qidian"), Type.Literal("feilu"), Type.Literal("other"),
|
|
372
|
+
])),
|
|
373
|
+
language: Type.Optional(Type.Union([Type.Literal("zh"), Type.Literal("en")])),
|
|
374
|
+
targetChapters: Type.Optional(Type.Number({ description: "Confirmed total chapter count." })),
|
|
375
|
+
chapterWordCount: Type.Optional(Type.Number({ description: "Confirmed per-chapter length." })),
|
|
376
|
+
}, { description: "Structured execution args for action=style_imitation. This creates an original book and style guide directly after confirmation." })),
|
|
377
|
+
});
|
|
378
|
+
function proposedActionSessionKind(action) {
|
|
379
|
+
if (action === "create_book")
|
|
380
|
+
return "book-create";
|
|
381
|
+
if (action === "play_start")
|
|
382
|
+
return "play";
|
|
383
|
+
if (action === "script_create")
|
|
384
|
+
return "script";
|
|
385
|
+
if (action === "storyboard_create")
|
|
386
|
+
return "storyboard";
|
|
387
|
+
if (action === "interactive_film_create")
|
|
388
|
+
return "interactive-film";
|
|
389
|
+
if (action === "translation_create")
|
|
390
|
+
return "chat";
|
|
391
|
+
if (action === "draft_structure" || action === "connect_choice" || action === "remove_node")
|
|
392
|
+
return "interactive-film-authoring";
|
|
393
|
+
if (action === "fanfic_init" || action === "continuation_import" || action === "spinoff_create" || action === "style_imitation")
|
|
394
|
+
return "chat";
|
|
395
|
+
return "short";
|
|
396
|
+
}
|
|
397
|
+
function proposedActionFallbackTitle(action, isZh) {
|
|
398
|
+
switch (action) {
|
|
399
|
+
case "create_book":
|
|
400
|
+
return isZh ? "创建长篇书籍" : "Create a long-form book";
|
|
401
|
+
case "short_run":
|
|
402
|
+
return isZh ? "生成 InkOS Short" : "Generate InkOS Short";
|
|
403
|
+
case "play_start":
|
|
404
|
+
return isZh ? "启动 InkOS Play" : "Start InkOS Play";
|
|
405
|
+
case "generate_cover":
|
|
406
|
+
return isZh ? "生成封面" : "Generate cover";
|
|
407
|
+
case "fanfic_init":
|
|
408
|
+
return isZh ? "创建同人作品" : "Create fanfiction";
|
|
409
|
+
case "continuation_import":
|
|
410
|
+
return isZh ? "导入并续写作品" : "Import and continue a work";
|
|
411
|
+
case "spinoff_create":
|
|
412
|
+
return isZh ? "创建番外作品" : "Create a side story";
|
|
413
|
+
case "style_imitation":
|
|
414
|
+
return isZh ? "创建仿写作品" : "Create a style-imitation work";
|
|
415
|
+
case "script_create":
|
|
416
|
+
return isZh ? "创建剧本" : "Create script";
|
|
417
|
+
case "storyboard_create":
|
|
418
|
+
return isZh ? "创建分镜" : "Create storyboard";
|
|
419
|
+
case "interactive_film_create":
|
|
420
|
+
return isZh ? "创建互动影游" : "Create interactive film";
|
|
421
|
+
case "translation_create":
|
|
422
|
+
return isZh ? "创建翻译项目" : "Create translation project";
|
|
423
|
+
case "draft_structure":
|
|
424
|
+
return isZh ? "生成故事结构" : "Draft story structure";
|
|
425
|
+
case "connect_choice":
|
|
426
|
+
return isZh ? "连接选项" : "Connect choice";
|
|
427
|
+
case "remove_node":
|
|
428
|
+
return isZh ? "删除节点" : "Remove node";
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function proposedActionFallbackSummary(action, isZh) {
|
|
432
|
+
return isZh
|
|
433
|
+
? "确认后将直接执行这条需求;不会要求你再去另一个表单重复填写。"
|
|
434
|
+
: "After confirmation, InkOS will run this request directly without asking you to repeat it in another form.";
|
|
435
|
+
}
|
|
436
|
+
function compactObject(value) {
|
|
437
|
+
if (!value || typeof value !== "object")
|
|
438
|
+
return undefined;
|
|
439
|
+
const out = {};
|
|
440
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
441
|
+
if (typeof raw === "string") {
|
|
442
|
+
const text = raw.trim();
|
|
443
|
+
if (text)
|
|
444
|
+
out[key] = text;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (Array.isArray(raw)) {
|
|
448
|
+
const items = raw.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
449
|
+
.map((item) => item.trim());
|
|
450
|
+
if (items.length > 0)
|
|
451
|
+
out[key] = items;
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (typeof raw === "number") {
|
|
455
|
+
if (Number.isFinite(raw) && raw > 0)
|
|
456
|
+
out[key] = raw;
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (raw !== undefined && raw !== null) {
|
|
460
|
+
out[key] = raw;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
464
|
+
}
|
|
465
|
+
function compactPlayStartPayload(value) {
|
|
466
|
+
if (!value || typeof value !== "object")
|
|
467
|
+
return undefined;
|
|
468
|
+
const out = {};
|
|
469
|
+
const title = value.title?.trim();
|
|
470
|
+
if (title)
|
|
471
|
+
out.title = title;
|
|
472
|
+
const premise = value.premise?.trim();
|
|
473
|
+
if (premise)
|
|
474
|
+
out.premise = premise;
|
|
475
|
+
const worldContract = value.worldContract?.trim();
|
|
476
|
+
if (worldContract)
|
|
477
|
+
out.worldContract = worldContract;
|
|
478
|
+
const visualContract = value.visualContract?.trim();
|
|
479
|
+
if (visualContract)
|
|
480
|
+
out.visualContract = visualContract;
|
|
481
|
+
if (value.mode)
|
|
482
|
+
out.mode = value.mode;
|
|
483
|
+
const initialScene = value.initialScene?.trim();
|
|
484
|
+
if (initialScene)
|
|
485
|
+
out.initialScene = initialScene;
|
|
486
|
+
const suggestedActions = normalizeSuggestedActions(value.suggestedActions);
|
|
487
|
+
if (suggestedActions.length > 0)
|
|
488
|
+
out.suggestedActions = suggestedActions;
|
|
489
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
490
|
+
}
|
|
491
|
+
function proposedActionPayload(params, language) {
|
|
492
|
+
const payload = {};
|
|
493
|
+
if (params.action === "create_book") {
|
|
494
|
+
const createBook = compactObject(params.createBook);
|
|
495
|
+
if (createBook)
|
|
496
|
+
payload.createBook = createBook;
|
|
497
|
+
}
|
|
498
|
+
if (params.action === "short_run") {
|
|
499
|
+
const shortRun = compactObject(params.shortRun);
|
|
500
|
+
if (shortRun)
|
|
501
|
+
payload.shortRun = { language, ...shortRun };
|
|
502
|
+
}
|
|
503
|
+
if (params.action === "play_start") {
|
|
504
|
+
const playStart = compactPlayStartPayload(params.playStart);
|
|
505
|
+
if (playStart)
|
|
506
|
+
payload.playStart = playStart;
|
|
507
|
+
}
|
|
508
|
+
if (params.action === "generate_cover") {
|
|
509
|
+
const generateCover = compactObject(params.generateCover);
|
|
510
|
+
if (generateCover)
|
|
511
|
+
payload.generateCover = generateCover;
|
|
512
|
+
}
|
|
513
|
+
if (params.action === "script_create") {
|
|
514
|
+
const scriptCreate = compactObject(params.scriptCreate);
|
|
515
|
+
if (scriptCreate)
|
|
516
|
+
payload.scriptCreate = scriptCreate;
|
|
517
|
+
}
|
|
518
|
+
if (params.action === "storyboard_create") {
|
|
519
|
+
const storyboardCreate = compactObject(params.storyboardCreate);
|
|
520
|
+
if (storyboardCreate)
|
|
521
|
+
payload.storyboardCreate = storyboardCreate;
|
|
522
|
+
}
|
|
523
|
+
if (params.action === "interactive_film_create") {
|
|
524
|
+
const interactiveFilmCreate = compactObject(params.interactiveFilmCreate);
|
|
525
|
+
if (interactiveFilmCreate)
|
|
526
|
+
payload.interactiveFilmCreate = interactiveFilmCreate;
|
|
527
|
+
}
|
|
528
|
+
if (params.action === "translation_create") {
|
|
529
|
+
const translationCreate = compactObject(params.translationCreate);
|
|
530
|
+
if (translationCreate)
|
|
531
|
+
payload.translationCreate = translationCreate;
|
|
532
|
+
}
|
|
533
|
+
if (params.action === "fanfic_init") {
|
|
534
|
+
const fanficCreate = compactObject(params.fanficCreate);
|
|
535
|
+
if (fanficCreate)
|
|
536
|
+
payload.fanficCreate = fanficCreate;
|
|
537
|
+
}
|
|
538
|
+
if (params.action === "continuation_import") {
|
|
539
|
+
const continuationImport = compactObject(params.continuationImport);
|
|
540
|
+
if (continuationImport)
|
|
541
|
+
payload.continuationImport = continuationImport;
|
|
542
|
+
}
|
|
543
|
+
if (params.action === "spinoff_create") {
|
|
544
|
+
const spinoffCreate = compactObject(params.spinoffCreate);
|
|
545
|
+
if (spinoffCreate)
|
|
546
|
+
payload.spinoffCreate = spinoffCreate;
|
|
547
|
+
}
|
|
548
|
+
if (params.action === "style_imitation") {
|
|
549
|
+
const imitationCreate = compactObject(params.imitationCreate);
|
|
550
|
+
if (imitationCreate)
|
|
551
|
+
payload.imitationCreate = imitationCreate;
|
|
552
|
+
}
|
|
553
|
+
return Object.keys(payload).length > 0 ? payload : undefined;
|
|
554
|
+
}
|
|
555
|
+
function validateProposedActionPayload(payload) {
|
|
556
|
+
if (!payload)
|
|
557
|
+
return {};
|
|
558
|
+
const parsed = ActionPayloadSchema.safeParse(payload);
|
|
559
|
+
if (parsed.success)
|
|
560
|
+
return { payload: parsed.data };
|
|
561
|
+
return { error: parsed.error.issues.map((issue) => issue.message).join("; ") };
|
|
562
|
+
}
|
|
563
|
+
function withSingleAttachmentFallback(params, payload, attachmentPaths) {
|
|
564
|
+
const paths = [...new Set(attachmentPaths.map((path) => path.trim()).filter(Boolean))];
|
|
565
|
+
if (!payload || paths.length !== 1)
|
|
566
|
+
return payload;
|
|
567
|
+
const [path] = paths;
|
|
568
|
+
const useHostAttachment = (candidate) => {
|
|
569
|
+
const value = candidate?.trim();
|
|
570
|
+
return !value || (value.startsWith(".inkos/uploads/") && value !== path);
|
|
571
|
+
};
|
|
572
|
+
if (params.action === "translation_create" && payload.translationCreate && useHostAttachment(payload.translationCreate.filePath)) {
|
|
573
|
+
return { ...payload, translationCreate: { ...payload.translationCreate, filePath: path } };
|
|
574
|
+
}
|
|
575
|
+
if (params.action === "fanfic_init"
|
|
576
|
+
&& payload.fanficCreate
|
|
577
|
+
&& !payload.fanficCreate.sourceText?.trim()
|
|
578
|
+
&& useHostAttachment(payload.fanficCreate.sourcePath)) {
|
|
579
|
+
return { ...payload, fanficCreate: { ...payload.fanficCreate, sourcePath: path } };
|
|
580
|
+
}
|
|
581
|
+
if (params.action === "continuation_import"
|
|
582
|
+
&& payload.continuationImport
|
|
583
|
+
&& useHostAttachment(payload.continuationImport.sourcePath)) {
|
|
584
|
+
return { ...payload, continuationImport: { ...payload.continuationImport, sourcePath: path } };
|
|
585
|
+
}
|
|
586
|
+
if (params.action === "style_imitation"
|
|
587
|
+
&& payload.imitationCreate
|
|
588
|
+
&& !payload.imitationCreate.referenceText?.trim()
|
|
589
|
+
&& useHostAttachment(payload.imitationCreate.referencePath)) {
|
|
590
|
+
return { ...payload, imitationCreate: { ...payload.imitationCreate, referencePath: path } };
|
|
591
|
+
}
|
|
592
|
+
return payload;
|
|
593
|
+
}
|
|
594
|
+
function requireProposedText(value, label) {
|
|
595
|
+
if (typeof value === "string" && value.trim().length > 0)
|
|
596
|
+
return;
|
|
597
|
+
throw new Error(`propose_action is missing ${label}; retry with that field in the structured payload, not only in summary or instruction.`);
|
|
598
|
+
}
|
|
599
|
+
function assertExecutableProposedAction(params, payload) {
|
|
600
|
+
if (params.action === "create_book") {
|
|
601
|
+
requireProposedText(payload?.createBook?.title, "createBook.title");
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (params.action === "play_start") {
|
|
605
|
+
requireProposedText(payload?.playStart?.title, "playStart.title");
|
|
606
|
+
requireProposedText(payload?.playStart?.premise, "playStart.premise");
|
|
607
|
+
requireProposedText(payload?.playStart?.initialScene, "playStart.initialScene");
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (params.action === "short_run") {
|
|
611
|
+
requireProposedText(payload?.shortRun?.title, "shortRun.title");
|
|
612
|
+
requireProposedText(payload?.shortRun?.direction, "shortRun.direction");
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (params.action === "generate_cover") {
|
|
616
|
+
requireProposedText(payload?.generateCover?.title, "generateCover.title");
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (params.action === "script_create") {
|
|
620
|
+
requireProposedText(payload?.scriptCreate?.title, "scriptCreate.title");
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (params.action === "storyboard_create") {
|
|
624
|
+
requireProposedText(payload?.storyboardCreate?.title, "storyboardCreate.title");
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (params.action === "interactive_film_create") {
|
|
628
|
+
requireProposedText(payload?.interactiveFilmCreate?.title, "interactiveFilmCreate.title");
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
if (params.action === "translation_create") {
|
|
632
|
+
requireProposedText(payload?.translationCreate?.filePath, "translationCreate.filePath");
|
|
633
|
+
requireProposedText(payload?.translationCreate?.sourceLanguage, "translationCreate.sourceLanguage");
|
|
634
|
+
requireProposedText(payload?.translationCreate?.targetLanguage, "translationCreate.targetLanguage");
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (params.action === "fanfic_init") {
|
|
638
|
+
requireProposedText(payload?.fanficCreate?.title, "fanficCreate.title");
|
|
639
|
+
if (!payload?.fanficCreate?.sourceText?.trim() && !payload?.fanficCreate?.sourcePath?.trim()) {
|
|
640
|
+
throw new Error("propose_action is missing fanficCreate.sourceText/sourcePath; ask for or use the attached source before proposing production.");
|
|
641
|
+
}
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (params.action === "continuation_import") {
|
|
645
|
+
requireProposedText(payload?.continuationImport?.sourcePath, "continuationImport.sourcePath");
|
|
646
|
+
if (!payload?.continuationImport?.bookId?.trim() && !payload?.continuationImport?.title?.trim()) {
|
|
647
|
+
throw new Error("propose_action requires continuationImport.bookId or continuationImport.title.");
|
|
648
|
+
}
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (params.action === "spinoff_create") {
|
|
652
|
+
requireProposedText(payload?.spinoffCreate?.title, "spinoffCreate.title");
|
|
653
|
+
requireProposedText(payload?.spinoffCreate?.parentBookId, "spinoffCreate.parentBookId");
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
if (params.action === "style_imitation") {
|
|
657
|
+
requireProposedText(payload?.imitationCreate?.title, "imitationCreate.title");
|
|
658
|
+
requireProposedText(payload?.imitationCreate?.storyIdea, "imitationCreate.storyIdea");
|
|
659
|
+
if (!payload?.imitationCreate?.referenceText?.trim() && !payload?.imitationCreate?.referencePath?.trim()) {
|
|
660
|
+
throw new Error("propose_action is missing imitationCreate.referenceText/referencePath; ask for or use the attached reference before proposing production.");
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
export function createProposeActionTool(language = "zh", options = {}) {
|
|
665
|
+
return {
|
|
666
|
+
name: "propose_action",
|
|
667
|
+
description: "Ask the user to confirm a production action from general chat. " +
|
|
668
|
+
"Use this before creating books, generating shorts/covers, or starting play worlds when the user has not clicked a confirmation.",
|
|
669
|
+
label: "Confirm Action",
|
|
670
|
+
parameters: ProposeActionParams,
|
|
671
|
+
async execute(_toolCallId, params) {
|
|
672
|
+
const targetSessionKind = proposedActionSessionKind(params.action);
|
|
673
|
+
const isZh = language === "zh";
|
|
674
|
+
const title = params.title?.trim() || proposedActionFallbackTitle(params.action, isZh);
|
|
675
|
+
const summary = params.summary?.trim() || proposedActionFallbackSummary(params.action, isZh);
|
|
676
|
+
const proposedPayload = validateProposedActionPayload(withSingleAttachmentFallback(params, proposedActionPayload(params, language), options.attachmentPaths?.() ?? []));
|
|
677
|
+
if (proposedPayload.error) {
|
|
678
|
+
throw new Error(`Invalid proposed action payload: ${proposedPayload.error}`);
|
|
679
|
+
}
|
|
680
|
+
const actionPayload = proposedPayload.payload;
|
|
681
|
+
assertExecutableProposedAction(params, actionPayload);
|
|
682
|
+
const requestedSkills = normalizeProposedSkillIds(options.requestedSkillIds?.());
|
|
683
|
+
return textResult([
|
|
684
|
+
title,
|
|
685
|
+
summary,
|
|
686
|
+
"",
|
|
687
|
+
`Instruction: ${params.instruction}`,
|
|
688
|
+
].join("\n"), {
|
|
689
|
+
kind: "proposed_action",
|
|
690
|
+
action: params.action,
|
|
691
|
+
targetSessionKind,
|
|
692
|
+
sameSession: options.sameSession === true,
|
|
693
|
+
title,
|
|
694
|
+
summary,
|
|
695
|
+
instruction: params.instruction,
|
|
696
|
+
...(requestedSkills.length > 0 ? { requestedSkills } : {}),
|
|
697
|
+
...(actionPayload ? { actionPayload } : {}),
|
|
698
|
+
});
|
|
699
|
+
},
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function normalizeProposedSkillIds(values) {
|
|
703
|
+
const out = [];
|
|
704
|
+
const seen = new Set();
|
|
705
|
+
for (const value of values ?? []) {
|
|
706
|
+
const id = value.trim().toLowerCase();
|
|
707
|
+
if (!id || seen.has(id))
|
|
708
|
+
continue;
|
|
709
|
+
seen.add(id);
|
|
710
|
+
out.push(id);
|
|
711
|
+
}
|
|
712
|
+
return out;
|
|
713
|
+
}
|
|
714
|
+
// ---------------------------------------------------------------------------
|
|
715
|
+
// 2. SubAgentTool (sub_agent)
|
|
716
|
+
// ---------------------------------------------------------------------------
|
|
717
|
+
const SubAgentParams = Type.Object({
|
|
718
|
+
agent: Type.Union([
|
|
719
|
+
Type.Literal("architect"),
|
|
720
|
+
Type.Literal("writer"),
|
|
721
|
+
Type.Literal("auditor"),
|
|
722
|
+
Type.Literal("reviser"),
|
|
723
|
+
Type.Literal("exporter"),
|
|
724
|
+
]),
|
|
725
|
+
instruction: Type.String({ description: "Natural language instruction for the sub-agent. For reviser, this is passed as the one-off revision brief." }),
|
|
726
|
+
bookId: Type.Optional(Type.String({
|
|
727
|
+
description: "Optional book ID. In active-book sessions, omit it to use the current active book; if provided, it must match the current active book. For architect creation, this optionally sets the new book ID.",
|
|
728
|
+
})),
|
|
729
|
+
chapterNumber: Type.Optional(Type.Number({ description: "auditor/reviser: target chapter number. Omit to use the latest chapter." })),
|
|
730
|
+
chapterCount: Type.Optional(Type.Integer({
|
|
731
|
+
minimum: 1,
|
|
732
|
+
maximum: 20,
|
|
733
|
+
description: "writer only: number of consecutive new chapters to write in this operation. Default: 1. InkOS writes them sequentially under one book lock.",
|
|
734
|
+
})),
|
|
735
|
+
// -- architect params --
|
|
736
|
+
title: Type.Optional(Type.String({ description: "architect only: explicit book title. Required when creating a book." })),
|
|
737
|
+
genre: Type.Optional(Type.String({ description: "architect only: genre (xuanhuan, urban, mystery, romance, scifi, fantasy, wuxia, general, etc.)" })),
|
|
738
|
+
platform: Type.Optional(Type.Union([
|
|
739
|
+
Type.Literal("tomato"),
|
|
740
|
+
Type.Literal("qidian"),
|
|
741
|
+
Type.Literal("feilu"),
|
|
742
|
+
Type.Literal("other"),
|
|
743
|
+
], { description: "architect only: target platform. Default: other" })),
|
|
744
|
+
language: Type.Optional(Type.Union([
|
|
745
|
+
Type.Literal("zh"),
|
|
746
|
+
Type.Literal("en"),
|
|
747
|
+
], { description: "architect only: writing language. Default: zh" })),
|
|
748
|
+
targetChapters: Type.Optional(Type.Number({ description: "architect only: total chapter count. Default: 200" })),
|
|
749
|
+
chapterWordCount: Type.Optional(Type.Number({ description: "architect/writer: per-chapter length in the book's native unit (zh characters / en words). Default: 3000 zh, 2000 en" })),
|
|
750
|
+
revise: Type.Optional(Type.Boolean({
|
|
751
|
+
description: "architect only: true 表示在当前 active book 上重新生成架构稿,而不是新建书籍。no-book creation sessions cannot revise an existing book.",
|
|
752
|
+
})),
|
|
753
|
+
feedback: Type.Optional(Type.String({
|
|
754
|
+
description: "architect only: revise 模式下的调整要求。举例:把架构稿从条目式升级成段落式架构稿、某个角色设定需要重新设计、主线冲突表达太弱需要加强等。如果是架构稿评审未通过要求重写的场景,把评审意见的 overallFeedback 原样传入即可",
|
|
755
|
+
})),
|
|
756
|
+
// -- reviser params --
|
|
757
|
+
mode: Type.Optional(Type.Union([
|
|
758
|
+
Type.Literal("spot-fix"),
|
|
759
|
+
Type.Literal("polish"),
|
|
760
|
+
Type.Literal("rewrite"),
|
|
761
|
+
Type.Literal("rework"),
|
|
762
|
+
Type.Literal("anti-detect"),
|
|
763
|
+
], { description: "reviser only: revision mode. Default: spot-fix" })),
|
|
764
|
+
// -- exporter params --
|
|
765
|
+
format: Type.Optional(Type.Union([
|
|
766
|
+
Type.Literal("txt"),
|
|
767
|
+
Type.Literal("md"),
|
|
768
|
+
Type.Literal("epub"),
|
|
769
|
+
], { description: "exporter only: export format. Default: txt" })),
|
|
770
|
+
approvedOnly: Type.Optional(Type.Boolean({ description: "exporter only: export only approved chapters. Default: false" })),
|
|
771
|
+
});
|
|
772
|
+
const ArchitectCreateSubAgentParams = Type.Object({
|
|
773
|
+
agent: Type.Literal("architect"),
|
|
774
|
+
instruction: Type.String({ description: "Confirmed self-contained book-creation instruction for the architect." }),
|
|
775
|
+
bookId: Type.Optional(Type.String({
|
|
776
|
+
description: "Optional new book ID. Usually omit it and let InkOS derive the ID from title.",
|
|
777
|
+
})),
|
|
778
|
+
title: Type.Optional(Type.String({ description: "Confirmed book title. Required when creating a book." })),
|
|
779
|
+
genre: Type.Optional(Type.String({ description: "Confirmed book genre." })),
|
|
780
|
+
platform: Type.Optional(Type.Union([
|
|
781
|
+
Type.Literal("tomato"),
|
|
782
|
+
Type.Literal("qidian"),
|
|
783
|
+
Type.Literal("feilu"),
|
|
784
|
+
Type.Literal("other"),
|
|
785
|
+
], { description: "Confirmed target platform. Default: other" })),
|
|
786
|
+
language: Type.Optional(Type.Union([
|
|
787
|
+
Type.Literal("zh"),
|
|
788
|
+
Type.Literal("en"),
|
|
789
|
+
], { description: "Confirmed writing language. Default: zh" })),
|
|
790
|
+
targetChapters: Type.Optional(Type.Number({ description: "Confirmed total chapter count. Default: 200" })),
|
|
791
|
+
chapterWordCount: Type.Optional(Type.Number({ description: "Confirmed per-chapter length in the book's native unit. Default: 3000 zh, 2000 en" })),
|
|
792
|
+
});
|
|
793
|
+
function prepareSubAgentArguments(args) {
|
|
794
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) {
|
|
795
|
+
return args;
|
|
796
|
+
}
|
|
797
|
+
const prepared = { ...args };
|
|
798
|
+
if ("platform" in prepared) {
|
|
799
|
+
const platform = normalizePlatformId(prepared.platform);
|
|
800
|
+
if (platform) {
|
|
801
|
+
prepared.platform = platform;
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
delete prepared.platform;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return prepared;
|
|
808
|
+
}
|
|
809
|
+
function runPipelineWithAgentContext(pipeline, signal, activatedSkills, task) {
|
|
810
|
+
return runAsWorkflowTrajectory(() => (pipeline.runWithAgentContext({ signal, activatedSkills }, task)));
|
|
811
|
+
}
|
|
812
|
+
function runPipelineWithAbortSignal(pipeline, signal, task) {
|
|
813
|
+
return runPipelineWithAgentContext(pipeline, signal, [], task);
|
|
814
|
+
}
|
|
815
|
+
function resolveProductionToolSkills(options) {
|
|
816
|
+
return mergeActivatedSkillGuidance(options.defaultSkills ?? [], options.activeSkills?.() ?? []);
|
|
817
|
+
}
|
|
818
|
+
export function createSubAgentTool(pipeline, activeBookId, projectRoot, options = {}) {
|
|
819
|
+
const sessionIsZh = (options.language ?? "zh") !== "en";
|
|
820
|
+
return {
|
|
821
|
+
name: "sub_agent",
|
|
822
|
+
description: options.architectCreateOnly
|
|
823
|
+
? "Create a new long-form InkOS book foundation. This confirmation turn can only call agent='architect'; writing chapters happens after the session is bound to the created book."
|
|
824
|
+
: "Delegate a heavy operation to a specialised sub-agent. " +
|
|
825
|
+
"Use agent='architect' to initialise a new book, 'writer' to write the next chapter, " +
|
|
826
|
+
"'auditor' to audit quality, 'reviser' to revise a chapter, 'exporter' to export.",
|
|
827
|
+
label: "Sub-Agent",
|
|
828
|
+
parameters: options.architectCreateOnly ? ArchitectCreateSubAgentParams : SubAgentParams,
|
|
829
|
+
prepareArguments: prepareSubAgentArguments,
|
|
830
|
+
async execute(toolCallId, params, _signal, onUpdate) {
|
|
831
|
+
return runWithAgentTrajectoryRole("subagent", async () => {
|
|
832
|
+
const { agent, instruction, bookId, title, chapterNumber, chapterCount, genre, platform, language, targetChapters, chapterWordCount, revise, feedback, mode, format, approvedOnly } = params;
|
|
833
|
+
const activatedSkills = mergeActivatedSkillGuidance(options.workerSkills?.(agent) ?? [], options.activeSkills?.() ?? []);
|
|
834
|
+
const skillIds = activatedSkills.map((activation) => activation.skill.id);
|
|
835
|
+
const progress = (msg) => {
|
|
836
|
+
onUpdate?.(textResult(msg));
|
|
837
|
+
};
|
|
838
|
+
try {
|
|
839
|
+
if (options.architectCreateOnly && agent !== "architect") {
|
|
840
|
+
throw new Error("This confirmed book-creation turn can only run the architect. Open the created book or use the book session to write chapters.");
|
|
841
|
+
}
|
|
842
|
+
if (!activeBookId && agent !== "architect") {
|
|
843
|
+
return textResult("No active book. Only the architect agent can create a book from this session.");
|
|
844
|
+
}
|
|
845
|
+
if (activeBookId && agent === "architect" && !revise) {
|
|
846
|
+
return textResult(sessionIsZh
|
|
847
|
+
? "当前已有书籍,不需要建书。如果你想创建新书,请先回到首页。"
|
|
848
|
+
: "This session already has a book, so no new book is needed. To create a new book, go back to the home page first.");
|
|
849
|
+
}
|
|
850
|
+
switch (agent) {
|
|
851
|
+
case "architect": {
|
|
852
|
+
const createBookPayload = options.actionPayload?.createBook;
|
|
853
|
+
if (revise) {
|
|
854
|
+
if (!activeBookId) {
|
|
855
|
+
return textResult("Open the book first before revising its foundation.");
|
|
856
|
+
}
|
|
857
|
+
const targetBookId = resolveToolBookId("architect", bookId, activeBookId);
|
|
858
|
+
progress(`Revising foundation for "${targetBookId}"...`);
|
|
859
|
+
await runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => pipeline.reviseFoundation(targetBookId, feedback ?? instruction));
|
|
860
|
+
progress(`Foundation revised for "${targetBookId}".`);
|
|
861
|
+
return textResult(sessionIsZh
|
|
862
|
+
? `Book "${targetBookId}" 架构稿已按要求重写。原书的条目式架构稿已备份到 story/.backup-phase4-<时间戳>/。`
|
|
863
|
+
: `Book "${targetBookId}" foundation has been rewritten as requested. The previous itemized foundation was backed up to story/.backup-phase4-<timestamp>/.`);
|
|
864
|
+
}
|
|
865
|
+
const confirmedTitle = createBookPayload?.title?.trim();
|
|
866
|
+
const resolvedTitle = confirmedTitle || title?.trim();
|
|
867
|
+
if (!resolvedTitle) {
|
|
868
|
+
return textResult('Error: title is required for the architect agent.');
|
|
869
|
+
}
|
|
870
|
+
const id = confirmedTitle
|
|
871
|
+
? deriveBookIdFromTitle(confirmedTitle) || `book-${Date.now().toString(36)}`
|
|
872
|
+
: bookId
|
|
873
|
+
? assertSafeBookId(bookId, "architect.bookId")
|
|
874
|
+
: deriveBookIdFromTitle(resolvedTitle) || `book-${Date.now().toString(36)}`;
|
|
875
|
+
const now = new Date().toISOString();
|
|
876
|
+
const resolvedLanguage = createBookPayload?.language ?? language ?? inferLanguage(instruction);
|
|
877
|
+
progress(`Starting architect for book "${id}"...`);
|
|
878
|
+
await runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => pipeline.initBook({
|
|
879
|
+
id,
|
|
880
|
+
title: resolvedTitle,
|
|
881
|
+
genre: createBookPayload?.genre ?? genre ?? "general",
|
|
882
|
+
platform: normalizePlatformOrOther(createBookPayload?.platform ?? platform),
|
|
883
|
+
language: resolvedLanguage,
|
|
884
|
+
status: "outlining",
|
|
885
|
+
targetChapters: createBookPayload?.targetChapters ?? targetChapters ?? 200,
|
|
886
|
+
chapterWordCount: createBookPayload?.chapterWordCount ?? chapterWordCount ?? defaultChapterLength(resolvedLanguage),
|
|
887
|
+
createdAt: now,
|
|
888
|
+
updatedAt: now,
|
|
889
|
+
}, { externalContext: instruction }));
|
|
890
|
+
progress(`Architect finished — book "${id}" foundation created.`);
|
|
891
|
+
return textResult(`Book "${resolvedTitle}" (${id}) initialised successfully. Foundation files are ready.`, { kind: "book_created", bookId: id, title: resolvedTitle, skillIds });
|
|
892
|
+
}
|
|
893
|
+
case "writer": {
|
|
894
|
+
const targetBookId = resolveToolBookId("writer", bookId, activeBookId);
|
|
895
|
+
const requestedCount = chapterCount ?? 1;
|
|
896
|
+
if (requestedCount > 1) {
|
|
897
|
+
progress(`Writing ${requestedCount} consecutive chapters for "${targetBookId}"...`);
|
|
898
|
+
const results = await runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => pipeline.writeChapters(targetBookId, requestedCount, {
|
|
899
|
+
wordCount: chapterWordCount,
|
|
900
|
+
externalContext: instruction,
|
|
901
|
+
onChapterComplete(result, completedCount, totalCount) {
|
|
902
|
+
progress(`Writer finished chapter ${result.chapterNumber} (${completedCount}/${totalCount}) for "${targetBookId}".`);
|
|
903
|
+
},
|
|
904
|
+
}));
|
|
905
|
+
const last = results.at(-1);
|
|
906
|
+
const stoppedStatus = last?.status !== "ready-for-review" ? last?.status : undefined;
|
|
907
|
+
const output = textResult(stoppedStatus
|
|
908
|
+
? sessionIsZh
|
|
909
|
+
? `已完成 ${results.length}/${requestedCount} 章;第 ${last?.chapterNumber} 章状态为 ${stoppedStatus},批量写作已停止,请复核后再继续。`
|
|
910
|
+
: `Writer completed ${results.length} of ${requestedCount} requested chapters for "${targetBookId}" and stopped because chapter ${last?.chapterNumber} ended with status "${stoppedStatus}".`
|
|
911
|
+
: sessionIsZh
|
|
912
|
+
? `已连续完成 ${results.length} 章(第 ${results[0]?.chapterNumber} 章至第 ${last?.chapterNumber} 章)。`
|
|
913
|
+
: `Writer completed ${results.length} consecutive chapters for "${targetBookId}".`, {
|
|
914
|
+
kind: "chapters_written",
|
|
915
|
+
bookId: targetBookId,
|
|
916
|
+
requestedCount,
|
|
917
|
+
completedCount: results.length,
|
|
918
|
+
skillIds,
|
|
919
|
+
chapters: results.map((result) => ({
|
|
920
|
+
chapterNumber: result.chapterNumber,
|
|
921
|
+
title: result.title,
|
|
922
|
+
wordCount: result.wordCount,
|
|
923
|
+
status: result.status,
|
|
924
|
+
...(result.contextTrace ? { contextTrace: result.contextTrace } : {}),
|
|
925
|
+
})),
|
|
926
|
+
...(stoppedStatus ? { stoppedStatus } : {}),
|
|
927
|
+
});
|
|
928
|
+
return stoppedStatus ? { ...output, isError: true } : output;
|
|
929
|
+
}
|
|
930
|
+
progress(`Writing next chapter for "${targetBookId}"...`);
|
|
931
|
+
const result = await runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => pipeline.writeNextChapter(targetBookId, chapterWordCount, undefined, instruction));
|
|
932
|
+
progress(`Writer finished chapter for "${targetBookId}".`);
|
|
933
|
+
const resultStatus = result.status;
|
|
934
|
+
const wordCount = result.wordCount ?? "unknown";
|
|
935
|
+
const chapterNumberResult = result.chapterNumber;
|
|
936
|
+
const titleResult = result.title;
|
|
937
|
+
const needsReview = Boolean(resultStatus && resultStatus !== "ready-for-review" && resultStatus !== "active");
|
|
938
|
+
const chapterRef = chapterNumberResult
|
|
939
|
+
? sessionIsZh
|
|
940
|
+
? `第 ${chapterNumberResult} 章${titleResult ? `《${titleResult}》` : ""}`
|
|
941
|
+
: `chapter ${chapterNumberResult}${titleResult ? ` "${titleResult}"` : ""}`
|
|
942
|
+
: sessionIsZh ? "下一章" : "the next chapter";
|
|
943
|
+
const message = needsReview
|
|
944
|
+
? sessionIsZh
|
|
945
|
+
? `已为 ${targetBookId} 写出${chapterRef},字数 ${wordCount},但审稿未通过,状态 ${resultStatus},需要复核后再继续。`
|
|
946
|
+
: `Wrote ${chapterRef} for ${targetBookId}: ${wordCount} words, but review did not pass (status: ${resultStatus}). Manual review is required before continuing.`
|
|
947
|
+
: sessionIsZh
|
|
948
|
+
? `已为 ${targetBookId} 完成${chapterRef},字数 ${wordCount},状态 ${resultStatus ?? "ready-for-review"}。`
|
|
949
|
+
: `Completed ${chapterRef} for ${targetBookId}: ${wordCount} words, status ${resultStatus ?? "ready-for-review"}.`;
|
|
950
|
+
const output = textResult(message, {
|
|
951
|
+
kind: "chapter_written",
|
|
952
|
+
bookId: targetBookId,
|
|
953
|
+
chapterNumber: chapterNumberResult,
|
|
954
|
+
title: titleResult,
|
|
955
|
+
wordCount,
|
|
956
|
+
status: resultStatus,
|
|
957
|
+
skillIds,
|
|
958
|
+
...(result.contextTrace ? { contextTrace: result.contextTrace } : {}),
|
|
959
|
+
});
|
|
960
|
+
return needsReview ? { ...output, isError: true } : output;
|
|
961
|
+
}
|
|
962
|
+
case "auditor": {
|
|
963
|
+
const targetBookId = resolveToolBookId("auditor", bookId, activeBookId);
|
|
964
|
+
progress(`Auditing chapter ${chapterNumber ?? "latest"} for "${targetBookId}"...`);
|
|
965
|
+
const audit = await runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => pipeline.auditDraft(targetBookId, chapterNumber));
|
|
966
|
+
progress(`Audit complete for "${targetBookId}".`);
|
|
967
|
+
const issueLines = (audit.issues ?? [])
|
|
968
|
+
.map((i) => `[${i.severity}] ${i.description}`)
|
|
969
|
+
.join("\n");
|
|
970
|
+
return textResult(`Audit chapter ${audit.chapterNumber}: ${audit.passed ? "PASSED" : "FAILED"}, ${(audit.issues ?? []).length} issue(s).` +
|
|
971
|
+
(issueLines ? `\n${issueLines}` : ""), {
|
|
972
|
+
kind: "chapter_audit",
|
|
973
|
+
bookId: targetBookId,
|
|
974
|
+
chapterNumber: audit.chapterNumber,
|
|
975
|
+
passed: audit.passed,
|
|
976
|
+
issueCount: (audit.issues ?? []).length,
|
|
977
|
+
skillIds,
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
case "reviser": {
|
|
981
|
+
const targetBookId = resolveToolBookId("reviser", bookId, activeBookId);
|
|
982
|
+
const resolvedMode = mode ?? "spot-fix";
|
|
983
|
+
progress(`Revising "${targetBookId}" chapter ${chapterNumber ?? "latest"} in ${resolvedMode} mode...`);
|
|
984
|
+
const result = await runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => pipeline.reviseDraft(targetBookId, chapterNumber, resolvedMode, instruction));
|
|
985
|
+
const applied = result.applied !== false;
|
|
986
|
+
const resultChapter = result.chapterNumber ?? chapterNumber;
|
|
987
|
+
const details = {
|
|
988
|
+
kind: "chapter_revision",
|
|
989
|
+
bookId: targetBookId,
|
|
990
|
+
chapterNumber: resultChapter,
|
|
991
|
+
mode: resolvedMode,
|
|
992
|
+
applied,
|
|
993
|
+
status: result.status,
|
|
994
|
+
wordCount: result.wordCount,
|
|
995
|
+
fixedIssues: result.fixedIssues,
|
|
996
|
+
skippedReason: result.skippedReason,
|
|
997
|
+
auditPassed: result.auditPassed,
|
|
998
|
+
auditIssues: result.auditIssues,
|
|
999
|
+
revisionDiagnostics: result.revisionDiagnostics,
|
|
1000
|
+
skillIds,
|
|
1001
|
+
};
|
|
1002
|
+
if (!applied) {
|
|
1003
|
+
progress(`Revision not applied for "${targetBookId}".`);
|
|
1004
|
+
const diagnostics = result.revisionDiagnostics;
|
|
1005
|
+
const diagnosticText = diagnostics
|
|
1006
|
+
? [
|
|
1007
|
+
"",
|
|
1008
|
+
"Revision gate:",
|
|
1009
|
+
`- Standard: ${diagnostics.standard}`,
|
|
1010
|
+
`- Before: blocking=${diagnostics.before.blockingCount}, critical=${diagnostics.before.criticalCount}, aiTell=${diagnostics.before.aiTellCount}`,
|
|
1011
|
+
`- After: blocking=${diagnostics.after.blockingCount}, critical=${diagnostics.after.criticalCount}, aiTell=${diagnostics.after.aiTellCount}`,
|
|
1012
|
+
...(diagnostics.remainingIssues.length > 0
|
|
1013
|
+
? [
|
|
1014
|
+
"- Remaining issues:",
|
|
1015
|
+
...diagnostics.remainingIssues.map((issue) => ` - [${issue.severity}] ${issue.category}: ${issue.description}${issue.suggestion ? ` (${issue.suggestion})` : ""}`),
|
|
1016
|
+
]
|
|
1017
|
+
: []),
|
|
1018
|
+
].join("\n")
|
|
1019
|
+
: "";
|
|
1020
|
+
return textResult(`Revision not applied for "${targetBookId}" chapter ${resultChapter ?? "latest"}: ${result.skippedReason ?? result.status ?? "pipeline kept the original chapter"}.${diagnosticText}`, details);
|
|
1021
|
+
}
|
|
1022
|
+
progress(`Revision complete for "${targetBookId}".`);
|
|
1023
|
+
const auditText = result.auditPassed === undefined
|
|
1024
|
+
? ""
|
|
1025
|
+
: result.auditPassed
|
|
1026
|
+
? " Audit passed."
|
|
1027
|
+
: ` Audit still has ${(result.auditIssues ?? []).length} blocking issue(s).`;
|
|
1028
|
+
return textResult(`Revision (${resolvedMode}) complete for "${targetBookId}" chapter ${resultChapter ?? "latest"}.${auditText}`, details);
|
|
1029
|
+
}
|
|
1030
|
+
case "exporter": {
|
|
1031
|
+
const targetBookId = resolveToolBookId("exporter", bookId, activeBookId);
|
|
1032
|
+
if (!projectRoot)
|
|
1033
|
+
return textResult("Error: exporter requires projectRoot.");
|
|
1034
|
+
const state = new StateManager(projectRoot);
|
|
1035
|
+
const result = await writeExportArtifact(state, targetBookId, {
|
|
1036
|
+
format: format ?? "txt",
|
|
1037
|
+
approvedOnly: approvedOnly ?? false,
|
|
1038
|
+
});
|
|
1039
|
+
return textResult(`Exported "${targetBookId}": ${result.chaptersExported} chapters, ${result.totalWords} words → ${result.outputPath}`);
|
|
1040
|
+
}
|
|
1041
|
+
default:
|
|
1042
|
+
return textResult(`Unknown agent: ${agent}`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
catch (err) {
|
|
1046
|
+
if (agent === "architect" && err instanceof ArchitectIncompleteFoundationError) {
|
|
1047
|
+
const missing = err.missing.join(", ");
|
|
1048
|
+
return textResult([
|
|
1049
|
+
err.message,
|
|
1050
|
+
"",
|
|
1051
|
+
`缺失 section: ${missing}`,
|
|
1052
|
+
"我会把已生成的部分保留下来,并继续补齐缺失 section;不要重新发明一本书。",
|
|
1053
|
+
].join("\n"), {
|
|
1054
|
+
kind: "architect_incomplete",
|
|
1055
|
+
missing: [...err.missing],
|
|
1056
|
+
partialContent: err.partialContent,
|
|
1057
|
+
retryInstruction: `Continue repairing the architect foundation. Preserve the partial content and fill missing sections: ${missing}.`,
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
console.error(`[sub_agent] "${agent}" failed:`, err);
|
|
1061
|
+
throw err;
|
|
1062
|
+
}
|
|
1063
|
+
}, toolCallId);
|
|
1064
|
+
},
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
// ---------------------------------------------------------------------------
|
|
1068
|
+
// 2. Research Tool (research_web)
|
|
1069
|
+
// ---------------------------------------------------------------------------
|
|
1070
|
+
const ResearchWebParams = Type.Object({
|
|
1071
|
+
topic: Type.String({
|
|
1072
|
+
description: "Research question or topic, e.g. 1990s county cold-storage accounting workflow or Tang dynasty courier stations.",
|
|
1073
|
+
}),
|
|
1074
|
+
purpose: Type.Union([
|
|
1075
|
+
Type.Literal("worldbuilding"),
|
|
1076
|
+
Type.Literal("era"),
|
|
1077
|
+
Type.Literal("profession"),
|
|
1078
|
+
Type.Literal("market"),
|
|
1079
|
+
Type.Literal("fact-check"),
|
|
1080
|
+
Type.Literal("general"),
|
|
1081
|
+
], {
|
|
1082
|
+
description: "Why this research is needed. Research reports are references only and must not directly mutate story state.",
|
|
1083
|
+
}),
|
|
1084
|
+
depth: Type.Optional(Type.Union([
|
|
1085
|
+
Type.Literal("quick"),
|
|
1086
|
+
Type.Literal("standard"),
|
|
1087
|
+
Type.Literal("deep"),
|
|
1088
|
+
], {
|
|
1089
|
+
description: "Research depth. Default standard.",
|
|
1090
|
+
})),
|
|
1091
|
+
});
|
|
1092
|
+
export function createResearchWebTool(projectRoot) {
|
|
1093
|
+
return {
|
|
1094
|
+
name: "research_web",
|
|
1095
|
+
description: "Collect traceable web research for worldbuilding, era, profession, market, or fact-check questions. " +
|
|
1096
|
+
"Saves a Markdown report under .inkos/research/. It is reference material only; it must not modify books, chapters, or truth files.",
|
|
1097
|
+
label: "Research Web",
|
|
1098
|
+
parameters: ResearchWebParams,
|
|
1099
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
1100
|
+
onUpdate?.(textResult(`Researching: ${params.topic}`));
|
|
1101
|
+
const searchConfig = await readResearchSearchConfig(projectRoot);
|
|
1102
|
+
const searchOptions = searchConfig.enabled
|
|
1103
|
+
? {
|
|
1104
|
+
apiKey: searchConfig.apiKey,
|
|
1105
|
+
apiKeyEnv: searchConfig.apiKeyEnv,
|
|
1106
|
+
baseUrl: searchConfig.baseUrl,
|
|
1107
|
+
}
|
|
1108
|
+
: {};
|
|
1109
|
+
const report = await runResearchReport({
|
|
1110
|
+
topic: params.topic,
|
|
1111
|
+
purpose: params.purpose,
|
|
1112
|
+
depth: params.depth ?? "standard",
|
|
1113
|
+
}, {
|
|
1114
|
+
search: (query, maxResults) => searchWeb(query, maxResults, searchOptions),
|
|
1115
|
+
});
|
|
1116
|
+
const reportDir = join(projectRoot, ".inkos", "research");
|
|
1117
|
+
await mkdir(reportDir, { recursive: true });
|
|
1118
|
+
const fileName = `${new Date().toISOString().replace(/[:.]/g, "-")}-${slugResearchTopic(params.topic)}.md`;
|
|
1119
|
+
const reportPath = join(reportDir, fileName);
|
|
1120
|
+
await writeFile(reportPath, report.markdown, "utf-8");
|
|
1121
|
+
return textResult([
|
|
1122
|
+
`Research report saved: ${reportPath}`,
|
|
1123
|
+
`Sources: ${report.sources.length}; confidence: ${report.confidence}.`,
|
|
1124
|
+
report.partialFailures.length > 0 ? `Partial failures: ${report.partialFailures.length}.` : "Partial failures: none.",
|
|
1125
|
+
].join("\n"), {
|
|
1126
|
+
kind: "research_report",
|
|
1127
|
+
reportPath,
|
|
1128
|
+
topic: params.topic,
|
|
1129
|
+
purpose: params.purpose,
|
|
1130
|
+
depth: params.depth ?? "standard",
|
|
1131
|
+
sources: report.sources,
|
|
1132
|
+
claims: report.claims,
|
|
1133
|
+
confidence: report.confidence,
|
|
1134
|
+
partialFailures: report.partialFailures,
|
|
1135
|
+
});
|
|
1136
|
+
},
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
// ---------------------------------------------------------------------------
|
|
1140
|
+
// 3. Material Ingestion Tool (ingest_material)
|
|
1141
|
+
// ---------------------------------------------------------------------------
|
|
1142
|
+
const IngestMaterialParams = Type.Object({
|
|
1143
|
+
sourceKind: Type.Union([
|
|
1144
|
+
Type.Literal("url"),
|
|
1145
|
+
Type.Literal("file"),
|
|
1146
|
+
], {
|
|
1147
|
+
description: "Use url for an external URL; use file for a user-uploaded file path shown in the Uploaded Files block.",
|
|
1148
|
+
}),
|
|
1149
|
+
url: Type.Optional(Type.String({
|
|
1150
|
+
description: "HTTP/HTTPS URL to fetch and extract. Supports HTML/text/JSON/PDF.",
|
|
1151
|
+
})),
|
|
1152
|
+
filePath: Type.Optional(Type.String({
|
|
1153
|
+
description: "Project-relative stored_path from the Uploaded Files block, e.g. .inkos/uploads/session/file.pdf.",
|
|
1154
|
+
})),
|
|
1155
|
+
filename: Type.Optional(Type.String({
|
|
1156
|
+
description: "Original filename when known.",
|
|
1157
|
+
})),
|
|
1158
|
+
mimeType: Type.Optional(Type.String({
|
|
1159
|
+
description: "MIME type when known, e.g. application/pdf or text/markdown.",
|
|
1160
|
+
})),
|
|
1161
|
+
title: Type.Optional(Type.String({
|
|
1162
|
+
description: "Human-readable material title.",
|
|
1163
|
+
})),
|
|
1164
|
+
purpose: Type.Optional(Type.Union([
|
|
1165
|
+
Type.Literal("reference"),
|
|
1166
|
+
Type.Literal("worldbuilding"),
|
|
1167
|
+
Type.Literal("script"),
|
|
1168
|
+
Type.Literal("storyboard"),
|
|
1169
|
+
Type.Literal("research"),
|
|
1170
|
+
Type.Literal("general"),
|
|
1171
|
+
], {
|
|
1172
|
+
description: "Why this material is being ingested. It remains reference material unless the user explicitly promotes it.",
|
|
1173
|
+
})),
|
|
1174
|
+
});
|
|
1175
|
+
export function createIngestMaterialTool(projectRoot) {
|
|
1176
|
+
return {
|
|
1177
|
+
name: "ingest_material",
|
|
1178
|
+
description: "Extract and archive a user-provided URL or uploaded file into .inkos/materials as traceable Markdown. " +
|
|
1179
|
+
"Supports HTML/text/JSON/Markdown/PDF. This creates reference material only; it must not mutate canon, chapters, scripts, or play state.",
|
|
1180
|
+
label: "Ingest Material",
|
|
1181
|
+
parameters: IngestMaterialParams,
|
|
1182
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
1183
|
+
onUpdate?.(textResult(params.sourceKind === "url"
|
|
1184
|
+
? `Extracting URL: ${params.url ?? "(missing)"}`
|
|
1185
|
+
: `Extracting file: ${params.filePath ?? params.filename ?? "(missing)"}`));
|
|
1186
|
+
const asset = await ingestMaterial(projectRoot, {
|
|
1187
|
+
sourceKind: params.sourceKind,
|
|
1188
|
+
url: params.url,
|
|
1189
|
+
filePath: params.filePath,
|
|
1190
|
+
filename: params.filename,
|
|
1191
|
+
mimeType: params.mimeType,
|
|
1192
|
+
title: params.title,
|
|
1193
|
+
purpose: params.purpose ?? "reference",
|
|
1194
|
+
});
|
|
1195
|
+
return textResult([
|
|
1196
|
+
`Material ingested: ${asset.markdownPath}`,
|
|
1197
|
+
`Material ID: ${asset.id}`,
|
|
1198
|
+
`Kind: ${asset.kind}; chars: ${asset.charCount}; source: ${asset.source}`,
|
|
1199
|
+
asset.totalPages !== undefined ? `PDF pages: ${asset.totalPages}` : "",
|
|
1200
|
+
"",
|
|
1201
|
+
"Excerpt:",
|
|
1202
|
+
asset.excerpt,
|
|
1203
|
+
].filter(Boolean).join("\n"), {
|
|
1204
|
+
kind: "material_ingested",
|
|
1205
|
+
asset,
|
|
1206
|
+
});
|
|
1207
|
+
},
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
// ---------------------------------------------------------------------------
|
|
1211
|
+
// 4. Material Retrieval Tool (retrieve_material)
|
|
1212
|
+
// ---------------------------------------------------------------------------
|
|
1213
|
+
const RetrieveMaterialParams = Type.Object({
|
|
1214
|
+
query: Type.String({
|
|
1215
|
+
description: "Natural-language query written by the agent from the user's current task, e.g. 冷库赔偿款 0607 账页 or storyboard shot requirements.",
|
|
1216
|
+
}),
|
|
1217
|
+
purpose: Type.Optional(Type.Union([
|
|
1218
|
+
Type.Literal("reference"),
|
|
1219
|
+
Type.Literal("worldbuilding"),
|
|
1220
|
+
Type.Literal("script"),
|
|
1221
|
+
Type.Literal("storyboard"),
|
|
1222
|
+
Type.Literal("research"),
|
|
1223
|
+
Type.Literal("general"),
|
|
1224
|
+
], {
|
|
1225
|
+
description: "Optional material purpose filter.",
|
|
1226
|
+
})),
|
|
1227
|
+
limit: Type.Optional(Type.Number({
|
|
1228
|
+
description: "Maximum number of material snippets to return. Default 5.",
|
|
1229
|
+
})),
|
|
1230
|
+
});
|
|
1231
|
+
export function createRetrieveMaterialTool(projectRoot) {
|
|
1232
|
+
return {
|
|
1233
|
+
name: "retrieve_material",
|
|
1234
|
+
description: "Retrieve traceable snippets from previously ingested .inkos/materials reference cards. " +
|
|
1235
|
+
"The agent supplies the semantic query; InkOS returns evidence pointers. This must not mutate canon, chapters, scripts, or play state.",
|
|
1236
|
+
label: "Retrieve Material",
|
|
1237
|
+
parameters: RetrieveMaterialParams,
|
|
1238
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
1239
|
+
onUpdate?.(textResult(`Retrieving materials: ${params.query}`));
|
|
1240
|
+
const results = await retrieveMaterials(projectRoot, {
|
|
1241
|
+
query: params.query,
|
|
1242
|
+
purpose: params.purpose,
|
|
1243
|
+
limit: params.limit,
|
|
1244
|
+
});
|
|
1245
|
+
if (results.length === 0) {
|
|
1246
|
+
return textResult("No matching archived materials were found. Ask the user to upload or ingest relevant material if needed.", {
|
|
1247
|
+
kind: "material_retrieval",
|
|
1248
|
+
query: params.query,
|
|
1249
|
+
purpose: params.purpose,
|
|
1250
|
+
results: [],
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
return textResult([
|
|
1254
|
+
`Retrieved ${results.length} material snippet${results.length === 1 ? "" : "s"}.`,
|
|
1255
|
+
"",
|
|
1256
|
+
...results.flatMap((result, index) => [
|
|
1257
|
+
`## ${index + 1}. ${result.title}`,
|
|
1258
|
+
`- material_id: ${result.id}`,
|
|
1259
|
+
`- source: ${result.source}`,
|
|
1260
|
+
`- path: ${result.markdownPath}:${result.charStart}-${result.charEnd}`,
|
|
1261
|
+
`- purpose: ${result.purpose}`,
|
|
1262
|
+
`- score: ${result.score.toFixed(2)}`,
|
|
1263
|
+
"",
|
|
1264
|
+
result.excerpt,
|
|
1265
|
+
"",
|
|
1266
|
+
]),
|
|
1267
|
+
].join("\n"), {
|
|
1268
|
+
kind: "material_retrieval",
|
|
1269
|
+
query: params.query,
|
|
1270
|
+
purpose: params.purpose,
|
|
1271
|
+
results,
|
|
1272
|
+
});
|
|
1273
|
+
},
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
// ---------------------------------------------------------------------------
|
|
1277
|
+
// 5. Book Reference Binding Tool (manage_book_reference)
|
|
1278
|
+
// ---------------------------------------------------------------------------
|
|
1279
|
+
const ManageBookReferenceParams = Type.Object({
|
|
1280
|
+
action: Type.Union([
|
|
1281
|
+
Type.Literal("list"),
|
|
1282
|
+
Type.Literal("bind"),
|
|
1283
|
+
Type.Literal("unbind"),
|
|
1284
|
+
], {
|
|
1285
|
+
description: "list = inspect bindings; bind = attach an ingested material to this book; unbind = remove that attachment without deleting the project asset.",
|
|
1286
|
+
}),
|
|
1287
|
+
materialId: Type.Optional(Type.String({
|
|
1288
|
+
description: "Exact material asset id returned by ingest_material. Required for bind and unbind.",
|
|
1289
|
+
})),
|
|
1290
|
+
uses: Type.Optional(Type.Array(Type.String(), {
|
|
1291
|
+
description: "bind only: user-defined natural-language purposes, e.g. 开篇机制, 人物关系, 调查节奏. Preserve the user's words instead of mapping them to a fixed taxonomy.",
|
|
1292
|
+
})),
|
|
1293
|
+
note: Type.Optional(Type.String({
|
|
1294
|
+
description: "bind only: optional user instruction that limits how this reference may be used.",
|
|
1295
|
+
})),
|
|
1296
|
+
});
|
|
1297
|
+
export function createManageBookReferenceTool(projectRoot, activeBookId) {
|
|
1298
|
+
const bookId = assertSafeBookId(activeBookId, "manage_book_reference.bookId");
|
|
1299
|
+
return {
|
|
1300
|
+
name: "manage_book_reference",
|
|
1301
|
+
description: "Bind already-ingested project materials to the active book with user-defined purposes, list current bindings, or unbind them. " +
|
|
1302
|
+
"The material remains stored once under .inkos/materials. Binding never copies prose into the book and never changes canon by itself.",
|
|
1303
|
+
label: "Manage Book Reference",
|
|
1304
|
+
parameters: ManageBookReferenceParams,
|
|
1305
|
+
async execute(_toolCallId, params, signal, onUpdate) {
|
|
1306
|
+
signal?.throwIfAborted();
|
|
1307
|
+
if (params.action === "list") {
|
|
1308
|
+
const listed = await listBookReferences(projectRoot, bookId);
|
|
1309
|
+
const references = listed.references.map((reference) => ({
|
|
1310
|
+
materialId: reference.materialId,
|
|
1311
|
+
title: reference.title,
|
|
1312
|
+
uses: reference.uses,
|
|
1313
|
+
note: reference.note,
|
|
1314
|
+
available: reference.available,
|
|
1315
|
+
error: reference.error,
|
|
1316
|
+
}));
|
|
1317
|
+
return textResult(references.length === 0
|
|
1318
|
+
? `No reference materials are bound to book "${bookId}".`
|
|
1319
|
+
: [
|
|
1320
|
+
`Bound references for "${bookId}":`,
|
|
1321
|
+
...references.map((reference) => [
|
|
1322
|
+
`- ${reference.title ?? reference.materialId} (${reference.materialId})`,
|
|
1323
|
+
` uses: ${reference.uses.join("; ")}`,
|
|
1324
|
+
reference.note ? ` note: ${reference.note}` : undefined,
|
|
1325
|
+
reference.available ? undefined : ` unavailable: ${reference.error ?? "material missing"}`,
|
|
1326
|
+
].filter(Boolean).join("\n")),
|
|
1327
|
+
].join("\n"), { kind: "book_reference_list", bookId, references });
|
|
1328
|
+
}
|
|
1329
|
+
const materialId = params.materialId?.trim();
|
|
1330
|
+
if (!materialId)
|
|
1331
|
+
throw new Error(`manage_book_reference.${params.action} requires materialId.`);
|
|
1332
|
+
if (params.action === "unbind") {
|
|
1333
|
+
onUpdate?.(textResult(`Unbinding reference ${materialId} from ${bookId}...`));
|
|
1334
|
+
const result = await unbindBookReference(projectRoot, bookId, materialId);
|
|
1335
|
+
return textResult(result.removed
|
|
1336
|
+
? `Reference ${materialId} was unbound from "${bookId}". The project asset was kept.`
|
|
1337
|
+
: `Reference ${materialId} was not bound to "${bookId}".`, { kind: "book_reference_unbound", bookId, materialId, removed: result.removed });
|
|
1338
|
+
}
|
|
1339
|
+
const uses = params.uses ?? [];
|
|
1340
|
+
onUpdate?.(textResult(`Binding reference ${materialId} to ${bookId}...`));
|
|
1341
|
+
const manifest = await bindBookReference(projectRoot, bookId, {
|
|
1342
|
+
materialId,
|
|
1343
|
+
uses,
|
|
1344
|
+
note: params.note,
|
|
1345
|
+
});
|
|
1346
|
+
const binding = manifest.bindings.find((entry) => entry.materialId === materialId);
|
|
1347
|
+
return textResult([
|
|
1348
|
+
`Reference ${materialId} was bound to "${bookId}".`,
|
|
1349
|
+
`Uses: ${binding.uses.join("; ")}`,
|
|
1350
|
+
binding.note ? `Note: ${binding.note}` : undefined,
|
|
1351
|
+
"Future chapter composition may select relevant sections; the reference does not override author intent or canon.",
|
|
1352
|
+
].filter(Boolean).join("\n"), {
|
|
1353
|
+
kind: "book_reference_bound",
|
|
1354
|
+
bookId,
|
|
1355
|
+
materialId,
|
|
1356
|
+
uses: binding.uses,
|
|
1357
|
+
note: binding.note,
|
|
1358
|
+
});
|
|
1359
|
+
},
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
// ---------------------------------------------------------------------------
|
|
1363
|
+
// 5. Chapter Import Tool (import_chapters)
|
|
1364
|
+
// ---------------------------------------------------------------------------
|
|
1365
|
+
const ImportChaptersParams = Type.Object({
|
|
1366
|
+
bookId: Type.Optional(Type.String({
|
|
1367
|
+
description: "Target book ID to import into. In active-book sessions, omit it to use the current active book; if provided, it must match the active book. In general chat there is no active book, so it is required and must be an existing book.",
|
|
1368
|
+
})),
|
|
1369
|
+
sourcePath: Type.String({
|
|
1370
|
+
description: "Local path of the chapter source: either the stored_path from the Uploaded Files block (project-relative, e.g. .inkos/uploads/<session>/novel.txt) or an absolute path on this machine that the user provided. A directory imports each .md/.txt file as one chapter in filename order; a single file is split into chapters automatically by heading lines.",
|
|
1371
|
+
}),
|
|
1372
|
+
splitPattern: Type.Optional(Type.String({
|
|
1373
|
+
description: "Single-file mode only: custom JavaScript regex source matching chapter heading lines. Omit to use the default pattern, which matches \"第X章/第X回\" and \"Chapter N\" headings.",
|
|
1374
|
+
})),
|
|
1375
|
+
resumeFrom: Type.Optional(Type.Number({
|
|
1376
|
+
description: "Resume an interrupted import from chapter N (1-based). Required when the book already has chapters: replay starts at chapter N and earlier chapters are kept. Omit for a fresh import into an empty book.",
|
|
1377
|
+
})),
|
|
1378
|
+
importMode: Type.Optional(Type.Union([
|
|
1379
|
+
Type.Literal("continuation"),
|
|
1380
|
+
Type.Literal("series"),
|
|
1381
|
+
], {
|
|
1382
|
+
description: "continuation (default): the book picks up exactly where the imported text left off, no new spacetime. series: shared universe but an independent new story, so a new spacetime is generated.",
|
|
1383
|
+
})),
|
|
1384
|
+
});
|
|
1385
|
+
export function createImportChaptersTool(pipeline, activeBookId, projectRoot) {
|
|
1386
|
+
return {
|
|
1387
|
+
name: "import_chapters",
|
|
1388
|
+
description: "Import an existing novel's chapters from a local file or directory into an InkOS book as real chapters (not reference material). " +
|
|
1389
|
+
"InkOS reverse-engineers foundation/truth files from the imported text and replays every chapter to rebuild story state, so the book can be continued afterwards. " +
|
|
1390
|
+
"Use ingest_material instead when the user only wants to archive reference material without touching book chapters.",
|
|
1391
|
+
label: "Import Chapters",
|
|
1392
|
+
parameters: ImportChaptersParams,
|
|
1393
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
1394
|
+
const targetBookId = resolveToolBookId("import_chapters", params.bookId, activeBookId);
|
|
1395
|
+
const state = new StateManager(projectRoot);
|
|
1396
|
+
const existingChapterCount = (await state.getNextChapterNumber(targetBookId)) - 1;
|
|
1397
|
+
if (existingChapterCount > 0 && params.resumeFrom === undefined) {
|
|
1398
|
+
throw new Error(`Book "${targetBookId}" already has ${existingChapterCount} chapter(s). ` +
|
|
1399
|
+
`Pass resumeFrom=<n> to resume/append from chapter n, or ask the user to clear the existing chapters first.`);
|
|
1400
|
+
}
|
|
1401
|
+
const resolvedSourcePath = isAbsolute(params.sourcePath)
|
|
1402
|
+
? params.sourcePath
|
|
1403
|
+
: resolve(projectRoot, params.sourcePath);
|
|
1404
|
+
onUpdate?.(textResult(`Reading chapters from ${resolvedSourcePath}...`));
|
|
1405
|
+
const chapters = await loadChaptersFromPath(resolvedSourcePath, params.splitPattern);
|
|
1406
|
+
onUpdate?.(textResult(`Found ${chapters.length} chapter(s); importing into "${targetBookId}"...`));
|
|
1407
|
+
const result = await runPipelineWithAbortSignal(pipeline, _signal, () => pipeline.importChapters({
|
|
1408
|
+
bookId: targetBookId,
|
|
1409
|
+
chapters,
|
|
1410
|
+
resumeFrom: params.resumeFrom,
|
|
1411
|
+
importMode: params.importMode,
|
|
1412
|
+
}));
|
|
1413
|
+
const regeneratedFoundation = (params.resumeFrom ?? 1) === 1;
|
|
1414
|
+
return textResult([
|
|
1415
|
+
`Imported ${result.importedCount} chapter(s) into book "${result.bookId}".`,
|
|
1416
|
+
`Total imported length: ${result.totalWords}. Next chapter to write: ${result.nextChapter}.`,
|
|
1417
|
+
regeneratedFoundation
|
|
1418
|
+
? "Foundation and truth files were reverse-engineered from the imported text; chapter files and the chapter index were rebuilt by sequential replay."
|
|
1419
|
+
: `Resumed replay from chapter ${params.resumeFrom}; earlier chapters and the existing foundation were kept.`,
|
|
1420
|
+
`The book can now be continued with sub_agent(agent="writer") in the book session.`,
|
|
1421
|
+
].join("\n"), {
|
|
1422
|
+
kind: "chapters_imported",
|
|
1423
|
+
bookId: result.bookId,
|
|
1424
|
+
importedCount: result.importedCount,
|
|
1425
|
+
totalWords: result.totalWords,
|
|
1426
|
+
nextChapter: result.nextChapter,
|
|
1427
|
+
importMode: params.importMode ?? "continuation",
|
|
1428
|
+
});
|
|
1429
|
+
},
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
const FanficCreateParams = Type.Object({
|
|
1433
|
+
title: Type.String({ description: "Fanfiction book title." }),
|
|
1434
|
+
sourceText: Type.Optional(Type.String({ description: "Canon/source text. Prefer sourcePath for long material." })),
|
|
1435
|
+
sourcePath: Type.Optional(Type.String({ description: "Project-relative uploaded canon/source path." })),
|
|
1436
|
+
sourceName: Type.Optional(Type.String({ description: "Human-readable source work name." })),
|
|
1437
|
+
mode: Type.Optional(Type.Union([
|
|
1438
|
+
Type.Literal("canon"), Type.Literal("au"), Type.Literal("ooc"), Type.Literal("cp"),
|
|
1439
|
+
])),
|
|
1440
|
+
genre: Type.Optional(Type.String()),
|
|
1441
|
+
platform: Type.Optional(Type.Union([
|
|
1442
|
+
Type.Literal("tomato"), Type.Literal("qidian"), Type.Literal("feilu"), Type.Literal("other"),
|
|
1443
|
+
])),
|
|
1444
|
+
language: Type.Optional(Type.Union([Type.Literal("zh"), Type.Literal("en")])),
|
|
1445
|
+
targetChapters: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
1446
|
+
chapterWordCount: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
1447
|
+
});
|
|
1448
|
+
export function createFanficBookTool(pipeline, projectRoot, options = {}) {
|
|
1449
|
+
return {
|
|
1450
|
+
name: "fanfic_create",
|
|
1451
|
+
description: "Create an InkOS fanfiction book directly from supplied canon/source material after user confirmation.",
|
|
1452
|
+
label: "Create Fanfiction",
|
|
1453
|
+
parameters: FanficCreateParams,
|
|
1454
|
+
async execute(_toolCallId, params, signal, onUpdate) {
|
|
1455
|
+
const source = await loadCreationSource({
|
|
1456
|
+
projectRoot,
|
|
1457
|
+
sourceText: params.sourceText,
|
|
1458
|
+
sourcePath: params.sourcePath,
|
|
1459
|
+
sourceName: params.sourceName,
|
|
1460
|
+
purpose: "reference",
|
|
1461
|
+
});
|
|
1462
|
+
const mode = params.mode ?? "canon";
|
|
1463
|
+
const book = buildAgentBookConfig({
|
|
1464
|
+
...params,
|
|
1465
|
+
fanficMode: mode,
|
|
1466
|
+
}, { targetChapters: 100 });
|
|
1467
|
+
await assertBookDoesNotExist(projectRoot, book.id);
|
|
1468
|
+
const activatedSkills = resolveProductionToolSkills(options);
|
|
1469
|
+
onUpdate?.(textResult(`Creating fanfiction book "${book.title}" from ${source.name}...`));
|
|
1470
|
+
await runPipelineWithAgentContext(pipeline, signal, activatedSkills, () => (pipeline.initFanficBook(book, source.text, source.name, mode)));
|
|
1471
|
+
return textResult(`Created fanfiction book "${book.title}" (${book.id}) in ${mode} mode.`, {
|
|
1472
|
+
kind: "book_created",
|
|
1473
|
+
creationKind: "fanfic",
|
|
1474
|
+
bookId: book.id,
|
|
1475
|
+
title: book.title,
|
|
1476
|
+
fanficMode: mode,
|
|
1477
|
+
sourceName: source.name,
|
|
1478
|
+
skillIds: activatedSkillIds(activatedSkills),
|
|
1479
|
+
});
|
|
1480
|
+
},
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
const SpinoffCreateParams = Type.Object({
|
|
1484
|
+
title: Type.String({ description: "Standalone side-story title." }),
|
|
1485
|
+
parentBookId: Type.String({ description: "Existing InkOS parent book id." }),
|
|
1486
|
+
direction: Type.Optional(Type.String({ description: "Side-story direction that must not advance the parent mainline." })),
|
|
1487
|
+
genre: Type.Optional(Type.String()),
|
|
1488
|
+
platform: Type.Optional(Type.Union([
|
|
1489
|
+
Type.Literal("tomato"), Type.Literal("qidian"), Type.Literal("feilu"), Type.Literal("other"),
|
|
1490
|
+
])),
|
|
1491
|
+
language: Type.Optional(Type.Union([Type.Literal("zh"), Type.Literal("en")])),
|
|
1492
|
+
targetChapters: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
1493
|
+
chapterWordCount: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
1494
|
+
});
|
|
1495
|
+
export function createSpinoffBookTool(pipeline, projectRoot, options = {}) {
|
|
1496
|
+
return {
|
|
1497
|
+
name: "spinoff_create",
|
|
1498
|
+
description: "Create a standalone side story that inherits canon from an existing InkOS parent book.",
|
|
1499
|
+
label: "Create Side Story",
|
|
1500
|
+
parameters: SpinoffCreateParams,
|
|
1501
|
+
async execute(_toolCallId, params, signal, onUpdate) {
|
|
1502
|
+
const parentBookId = assertSafeBookId(params.parentBookId, "spinoff_create.parentBookId");
|
|
1503
|
+
const state = new StateManager(projectRoot);
|
|
1504
|
+
const parent = await state.loadBookConfig(parentBookId);
|
|
1505
|
+
const book = buildAgentBookConfig({
|
|
1506
|
+
...params,
|
|
1507
|
+
parentBookId,
|
|
1508
|
+
genre: params.genre ?? parent.genre,
|
|
1509
|
+
platform: params.platform ?? parent.platform,
|
|
1510
|
+
language: params.language ?? parent.language,
|
|
1511
|
+
targetChapters: params.targetChapters ?? parent.targetChapters,
|
|
1512
|
+
chapterWordCount: params.chapterWordCount ?? parent.chapterWordCount,
|
|
1513
|
+
});
|
|
1514
|
+
await assertBookDoesNotExist(projectRoot, book.id);
|
|
1515
|
+
const activatedSkills = resolveProductionToolSkills(options);
|
|
1516
|
+
onUpdate?.(textResult(`Creating side story "${book.title}" from parent book "${parent.title}"...`));
|
|
1517
|
+
await runPipelineWithAgentContext(pipeline, signal, activatedSkills, () => (pipeline.initSpinoffBook(book, parentBookId, params.direction)));
|
|
1518
|
+
return textResult(`Created side-story book "${book.title}" (${book.id}) from "${parent.title}".`, {
|
|
1519
|
+
kind: "book_created",
|
|
1520
|
+
creationKind: "spinoff",
|
|
1521
|
+
bookId: book.id,
|
|
1522
|
+
title: book.title,
|
|
1523
|
+
parentBookId,
|
|
1524
|
+
skillIds: activatedSkillIds(activatedSkills),
|
|
1525
|
+
});
|
|
1526
|
+
},
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
const ImitationCreateParams = Type.Object({
|
|
1530
|
+
title: Type.String({ description: "Original imitation-project title." }),
|
|
1531
|
+
referenceText: Type.Optional(Type.String({ description: "Reference prose. Prefer referencePath for long material." })),
|
|
1532
|
+
referencePath: Type.Optional(Type.String({ description: "Project-relative uploaded reference-work path." })),
|
|
1533
|
+
storyIdea: Type.String({ description: "Original story idea. The reference contributes prose style, not plot or characters." }),
|
|
1534
|
+
sourceName: Type.Optional(Type.String({ description: "Human-readable reference work name." })),
|
|
1535
|
+
genre: Type.Optional(Type.String()),
|
|
1536
|
+
platform: Type.Optional(Type.Union([
|
|
1537
|
+
Type.Literal("tomato"), Type.Literal("qidian"), Type.Literal("feilu"), Type.Literal("other"),
|
|
1538
|
+
])),
|
|
1539
|
+
language: Type.Optional(Type.Union([Type.Literal("zh"), Type.Literal("en")])),
|
|
1540
|
+
targetChapters: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
1541
|
+
chapterWordCount: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
1542
|
+
});
|
|
1543
|
+
export function createImitationBookTool(pipeline, projectRoot, options = {}) {
|
|
1544
|
+
return {
|
|
1545
|
+
name: "imitation_create",
|
|
1546
|
+
description: "Create an original InkOS book and derive its prose style guide from supplied reference writing.",
|
|
1547
|
+
label: "Create Style Imitation",
|
|
1548
|
+
parameters: ImitationCreateParams,
|
|
1549
|
+
async execute(_toolCallId, params, signal, onUpdate) {
|
|
1550
|
+
const reference = await loadCreationSource({
|
|
1551
|
+
projectRoot,
|
|
1552
|
+
sourceText: params.referenceText,
|
|
1553
|
+
sourcePath: params.referencePath,
|
|
1554
|
+
sourceName: params.sourceName,
|
|
1555
|
+
purpose: "reference",
|
|
1556
|
+
});
|
|
1557
|
+
const book = buildAgentBookConfig(params);
|
|
1558
|
+
await assertBookDoesNotExist(projectRoot, book.id);
|
|
1559
|
+
const activatedSkills = resolveProductionToolSkills(options);
|
|
1560
|
+
onUpdate?.(textResult(`Creating original book "${book.title}" with style reference ${reference.name}...`));
|
|
1561
|
+
await runPipelineWithAgentContext(pipeline, signal, activatedSkills, () => (pipeline.initImitationBook(book, reference.text, params.storyIdea, reference.name)));
|
|
1562
|
+
return textResult(`Created imitation book "${book.title}" (${book.id}) with a persisted style guide.`, {
|
|
1563
|
+
kind: "book_created",
|
|
1564
|
+
creationKind: "imitation",
|
|
1565
|
+
bookId: book.id,
|
|
1566
|
+
title: book.title,
|
|
1567
|
+
sourceName: reference.name,
|
|
1568
|
+
skillIds: activatedSkillIds(activatedSkills),
|
|
1569
|
+
});
|
|
1570
|
+
},
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
const ContinuationImportParams = Type.Object({
|
|
1574
|
+
bookId: Type.Optional(Type.String({ description: "Existing target book id. Omit to create a new continuation book." })),
|
|
1575
|
+
title: Type.Optional(Type.String({ description: "New book title when bookId is omitted." })),
|
|
1576
|
+
sourcePath: Type.String({ description: "Project-relative uploaded novel file or chapter directory." }),
|
|
1577
|
+
splitPattern: Type.Optional(Type.String({ description: "Optional custom chapter-heading regex source." })),
|
|
1578
|
+
resumeFrom: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
1579
|
+
genre: Type.Optional(Type.String()),
|
|
1580
|
+
platform: Type.Optional(Type.Union([
|
|
1581
|
+
Type.Literal("tomato"), Type.Literal("qidian"), Type.Literal("feilu"), Type.Literal("other"),
|
|
1582
|
+
])),
|
|
1583
|
+
language: Type.Optional(Type.Union([Type.Literal("zh"), Type.Literal("en")])),
|
|
1584
|
+
targetChapters: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
1585
|
+
chapterWordCount: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
1586
|
+
});
|
|
1587
|
+
export function createContinuationImportTool(pipeline, activeBookId, projectRoot, options = {}) {
|
|
1588
|
+
return {
|
|
1589
|
+
name: "continuation_import",
|
|
1590
|
+
description: "Import an uploaded novel into an existing or newly created InkOS book, rebuild story state, and prepare it for continuation.",
|
|
1591
|
+
label: "Import for Continuation",
|
|
1592
|
+
parameters: ContinuationImportParams,
|
|
1593
|
+
async execute(_toolCallId, params, signal, onUpdate) {
|
|
1594
|
+
if (isAbsolute(params.sourcePath)) {
|
|
1595
|
+
throw new Error("continuation_import.sourcePath must be project-relative. Upload the source first.");
|
|
1596
|
+
}
|
|
1597
|
+
const sourcePath = safeChildPath(projectRoot, params.sourcePath);
|
|
1598
|
+
const state = new StateManager(projectRoot);
|
|
1599
|
+
const requestedBookId = params.bookId ?? activeBookId ?? undefined;
|
|
1600
|
+
let bookId;
|
|
1601
|
+
let created = false;
|
|
1602
|
+
if (requestedBookId) {
|
|
1603
|
+
bookId = resolveToolBookId("continuation_import", requestedBookId, activeBookId);
|
|
1604
|
+
await state.loadBookConfig(bookId);
|
|
1605
|
+
}
|
|
1606
|
+
else {
|
|
1607
|
+
if (!params.title?.trim()) {
|
|
1608
|
+
throw new Error("continuation_import requires title when no existing bookId is selected.");
|
|
1609
|
+
}
|
|
1610
|
+
const book = buildAgentBookConfig({ ...params, title: params.title.trim() });
|
|
1611
|
+
await assertBookDoesNotExist(projectRoot, book.id);
|
|
1612
|
+
await state.saveBookConfig(book.id, book);
|
|
1613
|
+
bookId = book.id;
|
|
1614
|
+
created = true;
|
|
1615
|
+
}
|
|
1616
|
+
const existingChapterCount = (await state.getNextChapterNumber(bookId)) - 1;
|
|
1617
|
+
if (existingChapterCount > 0 && params.resumeFrom === undefined) {
|
|
1618
|
+
throw new Error(`Book "${bookId}" already has ${existingChapterCount} chapter(s); resumeFrom is required.`);
|
|
1619
|
+
}
|
|
1620
|
+
const chapters = await loadChaptersFromPath(sourcePath, params.splitPattern);
|
|
1621
|
+
const activatedSkills = resolveProductionToolSkills(options);
|
|
1622
|
+
onUpdate?.(textResult(`Importing ${chapters.length} chapter(s) into "${bookId}" and rebuilding story state...`));
|
|
1623
|
+
const result = await runPipelineWithAgentContext(pipeline, signal, activatedSkills, () => (pipeline.importChapters({
|
|
1624
|
+
bookId,
|
|
1625
|
+
chapters,
|
|
1626
|
+
resumeFrom: params.resumeFrom,
|
|
1627
|
+
importMode: "continuation",
|
|
1628
|
+
})));
|
|
1629
|
+
return textResult(`Imported ${result.importedCount} chapter(s) into "${bookId}". Next chapter: ${result.nextChapter}.`, {
|
|
1630
|
+
kind: created ? "book_created" : "chapters_imported",
|
|
1631
|
+
creationKind: "continuation",
|
|
1632
|
+
bookId,
|
|
1633
|
+
importedCount: result.importedCount,
|
|
1634
|
+
totalWords: result.totalWords,
|
|
1635
|
+
nextChapter: result.nextChapter,
|
|
1636
|
+
skillIds: activatedSkillIds(activatedSkills),
|
|
1637
|
+
});
|
|
1638
|
+
},
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
function slugResearchTopic(topic) {
|
|
1642
|
+
const slug = topic
|
|
1643
|
+
.normalize("NFKC")
|
|
1644
|
+
.toLowerCase()
|
|
1645
|
+
.replace(/[^a-z0-9\u4e00-\u9fff]+/g, "-")
|
|
1646
|
+
.replace(/^-+|-+$/g, "")
|
|
1647
|
+
.slice(0, 60);
|
|
1648
|
+
return slug || "research";
|
|
1649
|
+
}
|
|
1650
|
+
async function readResearchSearchConfig(projectRoot) {
|
|
1651
|
+
try {
|
|
1652
|
+
const raw = JSON.parse(await readFile(join(projectRoot, "inkos.json"), "utf-8"));
|
|
1653
|
+
return ResearchSearchConfigSchema.parse(raw.researchSearch ?? {});
|
|
1654
|
+
}
|
|
1655
|
+
catch {
|
|
1656
|
+
return ResearchSearchConfigSchema.parse({});
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
// ---------------------------------------------------------------------------
|
|
1660
|
+
// 3. Standalone Short Fiction Tool
|
|
1661
|
+
// ---------------------------------------------------------------------------
|
|
1662
|
+
const ShortFictionRunParams = Type.Object({
|
|
1663
|
+
title: Type.Optional(Type.String({
|
|
1664
|
+
description: "Confirmed title or working title. When present, the host uses it as the stable project identity instead of guessing from generated outline prose.",
|
|
1665
|
+
})),
|
|
1666
|
+
direction: Type.String({
|
|
1667
|
+
description: "Required short fiction direction, e.g. 女频短篇 婚姻背叛 证据反杀. Include genre, protagonist pressure, conflict, and desired payoff when known.",
|
|
1668
|
+
}),
|
|
1669
|
+
reference: Type.Optional(Type.String({
|
|
1670
|
+
description: "Optional user-provided reference notes or constraints. Do not paste copyrighted source text unless the user explicitly provided it.",
|
|
1671
|
+
})),
|
|
1672
|
+
storyId: Type.Optional(Type.String({
|
|
1673
|
+
description: "Optional output id under shorts/. Leave empty to derive from the generated title.",
|
|
1674
|
+
})),
|
|
1675
|
+
chapters: Type.Optional(Type.Number({
|
|
1676
|
+
description: "Target complete short chapter count, 12-18. Default 12.",
|
|
1677
|
+
})),
|
|
1678
|
+
charsPerChapter: Type.Optional(Type.Number({
|
|
1679
|
+
description: "Per-chapter length in the story language's native unit: 900-1200 Chinese characters (default 1000) for zh, or 600-800 English words (default 650) for en. Values outside the story language's range are rejected before the pipeline starts. Do not use total story length here.",
|
|
1680
|
+
})),
|
|
1681
|
+
cover: Type.Optional(Type.Boolean({
|
|
1682
|
+
description: "Whether to attempt cover image generation after synopsis and cover prompt. Default true; use false if the user only wants text assets.",
|
|
1683
|
+
})),
|
|
1684
|
+
coverBaseUrl: Type.Optional(Type.String({
|
|
1685
|
+
description: "Optional OpenAI-compatible Responses API base URL for cover generation.",
|
|
1686
|
+
})),
|
|
1687
|
+
coverEndpoint: Type.Optional(Type.String({
|
|
1688
|
+
description: "Optional exact Responses endpoint for cover generation. Overrides coverBaseUrl.",
|
|
1689
|
+
})),
|
|
1690
|
+
coverModel: Type.Optional(Type.String({
|
|
1691
|
+
description: "Optional image-capable Responses model. Default gpt-image-2.",
|
|
1692
|
+
})),
|
|
1693
|
+
coverSize: Type.Optional(Type.String({
|
|
1694
|
+
description: "Optional image size, default 1024x1360.",
|
|
1695
|
+
})),
|
|
1696
|
+
coverApiKeyEnv: Type.Optional(Type.String({
|
|
1697
|
+
description: "Optional env var containing the cover API key. Default INKOS_COVER_API_KEY.",
|
|
1698
|
+
})),
|
|
1699
|
+
});
|
|
1700
|
+
// 启动 pipeline 之前校验 charsPerChapter 是否落在最终语言的合法区间:
|
|
1701
|
+
// 确认卡 payload 在 language 缺省时只能做 600-1200 并集校验,这里能拿到最终
|
|
1702
|
+
// 语言(payload.language ?? 会话语言 ?? zh,与 runner 的默认一致),越界立即
|
|
1703
|
+
// 抛出带合法范围的双语错误,不让任务开跑后才在 runner 中途失败。
|
|
1704
|
+
function assertShortRunCharsPerChapter(value, language) {
|
|
1705
|
+
if (value === undefined)
|
|
1706
|
+
return;
|
|
1707
|
+
const { min, max } = shortRunCharsPerChapterRange(language);
|
|
1708
|
+
if (Number.isInteger(value) && value >= min && value <= max)
|
|
1709
|
+
return;
|
|
1710
|
+
throw new Error(shortRunCharsPerChapterError(value, language));
|
|
1711
|
+
}
|
|
1712
|
+
export function createShortFictionRunTool(pipeline, projectRoot, options = {}) {
|
|
1713
|
+
return {
|
|
1714
|
+
name: "short_fiction_run",
|
|
1715
|
+
description: "Create a standalone short fiction project from a direction. " +
|
|
1716
|
+
"Runs outline -> outline review/revision -> full draft -> draft review/revision -> synopsis/selling points/cover prompt -> optional cover image. " +
|
|
1717
|
+
"Uses the user's direction and optional reference notes as input.",
|
|
1718
|
+
label: "Short Fiction",
|
|
1719
|
+
parameters: ShortFictionRunParams,
|
|
1720
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
1721
|
+
const progress = (message) => onUpdate?.(textResult(message));
|
|
1722
|
+
const shortPayload = options.actionPayload?.shortRun;
|
|
1723
|
+
const language = shortPayload?.language ?? options.language;
|
|
1724
|
+
const charsPerChapter = shortPayload?.charsPerChapter ?? params.charsPerChapter;
|
|
1725
|
+
const activatedSkills = resolveProductionToolSkills(options);
|
|
1726
|
+
assertShortRunCharsPerChapter(charsPerChapter, language ?? "zh");
|
|
1727
|
+
const result = await runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => runShortFictionProduction({
|
|
1728
|
+
projectRoot,
|
|
1729
|
+
title: shortPayload?.title ?? params.title,
|
|
1730
|
+
direction: shortPayload?.direction ?? params.direction,
|
|
1731
|
+
runtimes: {
|
|
1732
|
+
planner: pipeline.createAgentContext("short-outline"),
|
|
1733
|
+
outlineReview: pipeline.createAgentContext("short-outline-review"),
|
|
1734
|
+
writer: pipeline.createAgentContext("short-writer"),
|
|
1735
|
+
draftReview: pipeline.createAgentContext("short-draft-review"),
|
|
1736
|
+
revise: pipeline.createAgentContext("short-revise"),
|
|
1737
|
+
package: pipeline.createAgentContext("short-package"),
|
|
1738
|
+
},
|
|
1739
|
+
...((shortPayload?.reference ?? params.reference) ? { reference: { text: shortPayload?.reference ?? params.reference } } : {}),
|
|
1740
|
+
storyId: shortPayload?.storyId ?? params.storyId,
|
|
1741
|
+
chapterCount: shortPayload?.chapters ?? params.chapters,
|
|
1742
|
+
charsPerChapter,
|
|
1743
|
+
language,
|
|
1744
|
+
cover: shortPayload?.cover ?? params.cover,
|
|
1745
|
+
coverBaseUrl: params.coverBaseUrl,
|
|
1746
|
+
coverEndpoint: params.coverEndpoint,
|
|
1747
|
+
coverModel: params.coverModel,
|
|
1748
|
+
coverSize: params.coverSize,
|
|
1749
|
+
coverApiKeyEnv: params.coverApiKeyEnv,
|
|
1750
|
+
signal: _signal,
|
|
1751
|
+
onProgress: progress,
|
|
1752
|
+
}));
|
|
1753
|
+
return textResult([
|
|
1754
|
+
`Short fiction "${result.storyId}" completed.`,
|
|
1755
|
+
`Final: ${result.finalMarkdownPath}`,
|
|
1756
|
+
`Sales package: ${result.salesPackagePath}`,
|
|
1757
|
+
`Cover prompt: ${result.coverPromptPath}`,
|
|
1758
|
+
result.coverImagePath
|
|
1759
|
+
? `Cover image: ${result.coverImagePath}`
|
|
1760
|
+
: [
|
|
1761
|
+
"Cover image: not generated.",
|
|
1762
|
+
`Cover image reason: ${summarizeCoverGenerationError(result.coverError)}`,
|
|
1763
|
+
"The short fiction draft, synopsis, selling points, and cover prompt were still written successfully.",
|
|
1764
|
+
].join("\n"),
|
|
1765
|
+
].join("\n"), { kind: "short_fiction_created", ...result, skillIds: activatedSkillIds(activatedSkills) });
|
|
1766
|
+
},
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
function summarizeCoverGenerationError(error) {
|
|
1770
|
+
const text = (error ?? "not generated").trim();
|
|
1771
|
+
if (text.includes("HTTP 503")) {
|
|
1772
|
+
return "cover provider returned HTTP 503; retry later or switch the Studio cover provider/model.";
|
|
1773
|
+
}
|
|
1774
|
+
if (text.includes("HTTP 502")) {
|
|
1775
|
+
return "cover provider returned HTTP 502; retry later or switch the Studio cover provider/model.";
|
|
1776
|
+
}
|
|
1777
|
+
if (/API key is required|api key/i.test(text)) {
|
|
1778
|
+
return "cover API key is missing; configure it in Studio service settings.";
|
|
1779
|
+
}
|
|
1780
|
+
return text.slice(0, 300);
|
|
1781
|
+
}
|
|
1782
|
+
// ---------------------------------------------------------------------------
|
|
1783
|
+
// 3. Translation tool
|
|
1784
|
+
// ---------------------------------------------------------------------------
|
|
1785
|
+
const TranslationCreateParams = Type.Object({
|
|
1786
|
+
filePath: Type.String({
|
|
1787
|
+
description: "Project-relative EPUB/PDF/TXT/Markdown source file path to translate.",
|
|
1788
|
+
}),
|
|
1789
|
+
sourceLanguage: Type.String({
|
|
1790
|
+
description: "Source language as a human-readable name, e.g. Auto detect, Japanese, English, Chinese (Simplified), 繁体中文(台湾). Do not require ISO abbreviations.",
|
|
1791
|
+
}),
|
|
1792
|
+
targetLanguage: Type.String({
|
|
1793
|
+
description: "Target language as a human-readable name, e.g. Chinese (Simplified), English, Japanese, Korean, Brazilian Portuguese. Do not require ISO abbreviations.",
|
|
1794
|
+
}),
|
|
1795
|
+
title: Type.Optional(Type.String({
|
|
1796
|
+
description: "Optional translation project title.",
|
|
1797
|
+
})),
|
|
1798
|
+
segmentMaxChars: Type.Optional(Type.Number({
|
|
1799
|
+
description: "Optional max chars per segment before splitting long paragraphs.",
|
|
1800
|
+
})),
|
|
1801
|
+
});
|
|
1802
|
+
export function createTranslationCreateTool(projectRoot, options = {}) {
|
|
1803
|
+
return {
|
|
1804
|
+
name: "translation_create",
|
|
1805
|
+
description: "Create an InkOS translation project from an EPUB/PDF/TXT/Markdown file. " +
|
|
1806
|
+
"This only ingests and segments the source; running the actual translation is a separate long task.",
|
|
1807
|
+
label: "Translation",
|
|
1808
|
+
parameters: TranslationCreateParams,
|
|
1809
|
+
async execute(_toolCallId, params) {
|
|
1810
|
+
const payload = options.actionPayload?.translationCreate;
|
|
1811
|
+
const result = await createTranslationProjectFromFile(projectRoot, {
|
|
1812
|
+
filePath: payload?.filePath ?? params.filePath,
|
|
1813
|
+
sourceLanguage: payload?.sourceLanguage ?? params.sourceLanguage,
|
|
1814
|
+
targetLanguage: payload?.targetLanguage ?? params.targetLanguage,
|
|
1815
|
+
title: payload?.title ?? params.title,
|
|
1816
|
+
segmentMaxChars: payload?.segmentMaxChars ?? params.segmentMaxChars,
|
|
1817
|
+
});
|
|
1818
|
+
return textResult([
|
|
1819
|
+
`Translation project "${result.manifest.title}" created.`,
|
|
1820
|
+
`ID: ${result.manifest.id}`,
|
|
1821
|
+
`Source: ${result.manifest.source.kind} ${result.manifest.sourceLanguage} -> ${result.manifest.targetLanguage}`,
|
|
1822
|
+
`Chapters: ${result.manifest.chapters.length}`,
|
|
1823
|
+
`Manifest: ${result.manifestPath}`,
|
|
1824
|
+
].join("\n"), { kind: "translation_project_created", ...result });
|
|
1825
|
+
},
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
// ---------------------------------------------------------------------------
|
|
1829
|
+
// 4. Script and Storyboard tools
|
|
1830
|
+
// ---------------------------------------------------------------------------
|
|
1831
|
+
const ScriptCreateParams = Type.Object({
|
|
1832
|
+
title: Type.String({
|
|
1833
|
+
description: "Required script project title.",
|
|
1834
|
+
}),
|
|
1835
|
+
instruction: Type.String({
|
|
1836
|
+
description: "Confirmed script creation instruction, including format, source, and user preferences.",
|
|
1837
|
+
}),
|
|
1838
|
+
sourceKind: Type.Optional(Type.String({
|
|
1839
|
+
description: "Source type, e.g. novel excerpt, original idea, outline, existing script.",
|
|
1840
|
+
})),
|
|
1841
|
+
targetFormat: Type.Optional(Type.Union([
|
|
1842
|
+
Type.Literal("vertical_short_drama"),
|
|
1843
|
+
Type.Literal("screenplay"),
|
|
1844
|
+
Type.Literal("audio_drama"),
|
|
1845
|
+
Type.Literal("interactive_script"),
|
|
1846
|
+
Type.Literal("general_script"),
|
|
1847
|
+
], { description: "Confirmed script output format." })),
|
|
1848
|
+
sourceText: Type.Optional(Type.String({
|
|
1849
|
+
description: "User-provided source text. For long sources, prefer sourcePath instead of summarizing.",
|
|
1850
|
+
})),
|
|
1851
|
+
sourcePath: Type.Optional(Type.String({
|
|
1852
|
+
description: "Optional project-relative source file path.",
|
|
1853
|
+
})),
|
|
1854
|
+
requirements: Type.Optional(Type.String({
|
|
1855
|
+
description: "Confirmed script format, production constraints, tone, episode structure, or user preferences.",
|
|
1856
|
+
})),
|
|
1857
|
+
episodeCount: Type.Optional(Type.Number({
|
|
1858
|
+
description: "Optional target episode/segment count.",
|
|
1859
|
+
})),
|
|
1860
|
+
episodeDuration: Type.Optional(Type.String({
|
|
1861
|
+
description: "Optional per-episode/per-segment duration.",
|
|
1862
|
+
})),
|
|
1863
|
+
projectId: Type.Optional(Type.String({
|
|
1864
|
+
description: "Optional output id under dramas/.",
|
|
1865
|
+
})),
|
|
1866
|
+
outDir: Type.Optional(Type.String({
|
|
1867
|
+
description: "Optional project-relative output directory. Default dramas/.",
|
|
1868
|
+
})),
|
|
1869
|
+
});
|
|
1870
|
+
export function createScriptCreationTool(pipeline, projectRoot, options = {}) {
|
|
1871
|
+
return {
|
|
1872
|
+
name: "script_create",
|
|
1873
|
+
description: "Create a script project from a novel excerpt, idea, outline, or existing script. " +
|
|
1874
|
+
"Writes human-readable Markdown spec and script files under dramas/.",
|
|
1875
|
+
label: "Script Creation",
|
|
1876
|
+
parameters: ScriptCreateParams,
|
|
1877
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
1878
|
+
const progress = (message) => onUpdate?.(textResult(message));
|
|
1879
|
+
const payload = options.actionPayload?.scriptCreate;
|
|
1880
|
+
const activatedSkills = resolveProductionToolSkills(options);
|
|
1881
|
+
const result = await runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => runScriptCreation({
|
|
1882
|
+
projectRoot,
|
|
1883
|
+
runtime: pipeline.createAgentContext("script-creation"),
|
|
1884
|
+
title: payload?.title ?? params.title,
|
|
1885
|
+
instruction: params.instruction,
|
|
1886
|
+
sourceKind: payload?.sourceKind ?? params.sourceKind,
|
|
1887
|
+
targetFormat: (payload?.targetFormat ?? params.targetFormat),
|
|
1888
|
+
sourceText: payload?.sourceText ?? params.sourceText,
|
|
1889
|
+
sourcePath: payload?.sourcePath ?? params.sourcePath,
|
|
1890
|
+
requirements: payload?.requirements ?? params.requirements,
|
|
1891
|
+
episodeCount: payload?.episodeCount ?? params.episodeCount,
|
|
1892
|
+
episodeDuration: payload?.episodeDuration ?? params.episodeDuration,
|
|
1893
|
+
language: options.language,
|
|
1894
|
+
projectId: payload?.projectId ?? params.projectId,
|
|
1895
|
+
outDir: payload?.outDir ?? params.outDir,
|
|
1896
|
+
onProgress: progress,
|
|
1897
|
+
}));
|
|
1898
|
+
return textResult([
|
|
1899
|
+
`Script "${result.projectId}" completed.`,
|
|
1900
|
+
`Spec: ${result.specPath}`,
|
|
1901
|
+
`Script: ${result.scriptPath}`,
|
|
1902
|
+
].join("\n"), { kind: "script_created", ...result, skillIds: activatedSkillIds(activatedSkills) });
|
|
1903
|
+
},
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
const StoryboardCreateParams = Type.Object({
|
|
1907
|
+
title: Type.String({
|
|
1908
|
+
description: "Required storyboard project title.",
|
|
1909
|
+
}),
|
|
1910
|
+
instruction: Type.String({
|
|
1911
|
+
description: "Confirmed storyboard creation instruction, including source, style, aspect ratio, and shot granularity.",
|
|
1912
|
+
}),
|
|
1913
|
+
sourceKind: Type.Optional(Type.String({
|
|
1914
|
+
description: "Source type, e.g. script, novel excerpt, idea, scene list.",
|
|
1915
|
+
})),
|
|
1916
|
+
sourceText: Type.Optional(Type.String({
|
|
1917
|
+
description: "User-provided source text. For long sources, prefer sourcePath instead of summarizing.",
|
|
1918
|
+
})),
|
|
1919
|
+
sourcePath: Type.Optional(Type.String({
|
|
1920
|
+
description: "Optional project-relative source file path.",
|
|
1921
|
+
})),
|
|
1922
|
+
requirements: Type.Optional(Type.String({
|
|
1923
|
+
description: "Confirmed shot/storyboard requirements.",
|
|
1924
|
+
})),
|
|
1925
|
+
visualStyle: Type.Optional(Type.String({
|
|
1926
|
+
description: "Confirmed visual style, if the user specified one.",
|
|
1927
|
+
})),
|
|
1928
|
+
aspectRatio: Type.Optional(Type.String({
|
|
1929
|
+
description: "Confirmed aspect ratio, e.g. 9:16, 16:9, 1:1.",
|
|
1930
|
+
})),
|
|
1931
|
+
granularity: Type.Optional(Type.String({
|
|
1932
|
+
description: "Confirmed storyboard granularity.",
|
|
1933
|
+
})),
|
|
1934
|
+
maxShots: Type.Optional(Type.Number({
|
|
1935
|
+
description: "Optional max shot count.",
|
|
1936
|
+
})),
|
|
1937
|
+
projectId: Type.Optional(Type.String({
|
|
1938
|
+
description: "Optional output id under storyboards/.",
|
|
1939
|
+
})),
|
|
1940
|
+
outDir: Type.Optional(Type.String({
|
|
1941
|
+
description: "Optional project-relative output directory. Default storyboards/.",
|
|
1942
|
+
})),
|
|
1943
|
+
});
|
|
1944
|
+
export function createStoryboardCreationTool(pipeline, projectRoot, options = {}) {
|
|
1945
|
+
return {
|
|
1946
|
+
name: "storyboard_create",
|
|
1947
|
+
description: "Create a storyboard project and image prompts from a script, novel excerpt, idea, or scene list. " +
|
|
1948
|
+
"Writes human-readable Markdown spec, storyboard, and image prompt files under storyboards/.",
|
|
1949
|
+
label: "Storyboard Creation",
|
|
1950
|
+
parameters: StoryboardCreateParams,
|
|
1951
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
1952
|
+
const progress = (message) => onUpdate?.(textResult(message));
|
|
1953
|
+
const payload = options.actionPayload?.storyboardCreate;
|
|
1954
|
+
const activatedSkills = resolveProductionToolSkills(options);
|
|
1955
|
+
const result = await runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => runStoryboardCreation({
|
|
1956
|
+
projectRoot,
|
|
1957
|
+
runtime: pipeline.createAgentContext("storyboard-creation"),
|
|
1958
|
+
title: payload?.title ?? params.title,
|
|
1959
|
+
instruction: params.instruction,
|
|
1960
|
+
sourceKind: payload?.sourceKind ?? params.sourceKind,
|
|
1961
|
+
sourceText: payload?.sourceText ?? params.sourceText,
|
|
1962
|
+
sourcePath: payload?.sourcePath ?? params.sourcePath,
|
|
1963
|
+
requirements: payload?.requirements ?? params.requirements,
|
|
1964
|
+
visualStyle: payload?.visualStyle ?? params.visualStyle,
|
|
1965
|
+
aspectRatio: payload?.aspectRatio ?? params.aspectRatio,
|
|
1966
|
+
granularity: payload?.granularity ?? params.granularity,
|
|
1967
|
+
maxShots: payload?.maxShots ?? params.maxShots,
|
|
1968
|
+
language: options.language,
|
|
1969
|
+
projectId: payload?.projectId ?? params.projectId,
|
|
1970
|
+
outDir: payload?.outDir ?? params.outDir,
|
|
1971
|
+
onProgress: progress,
|
|
1972
|
+
}));
|
|
1973
|
+
return textResult([
|
|
1974
|
+
`Storyboard "${result.projectId}" completed.`,
|
|
1975
|
+
`Spec: ${result.specPath}`,
|
|
1976
|
+
`Storyboard: ${result.storyboardPath}`,
|
|
1977
|
+
`Image prompts: ${result.imagePromptsPath}`,
|
|
1978
|
+
`Image assets: ${result.assetsManifestPath}`,
|
|
1979
|
+
].join("\n"), { kind: "storyboard_created", ...result, skillIds: activatedSkillIds(activatedSkills) });
|
|
1980
|
+
},
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
const InteractiveFilmCreateParams = Type.Object({
|
|
1984
|
+
title: Type.String({
|
|
1985
|
+
description: "Required interactive-film project title.",
|
|
1986
|
+
}),
|
|
1987
|
+
instruction: Type.String({
|
|
1988
|
+
description: "Confirmed interactive-film creation instruction, including branching, variables/flags, endings, source, and user preferences.",
|
|
1989
|
+
}),
|
|
1990
|
+
sourceKind: Type.Optional(Type.String({
|
|
1991
|
+
description: "Source type, e.g. novel excerpt, script, outline, original idea.",
|
|
1992
|
+
})),
|
|
1993
|
+
sourceText: Type.Optional(Type.String({
|
|
1994
|
+
description: "User-provided source text. For long sources, prefer sourcePath instead of summarizing.",
|
|
1995
|
+
})),
|
|
1996
|
+
sourcePath: Type.Optional(Type.String({
|
|
1997
|
+
description: "Optional project-relative source file path.",
|
|
1998
|
+
})),
|
|
1999
|
+
requirements: Type.Optional(Type.String({
|
|
2000
|
+
description: "Confirmed branching, variable/flag, ending, production, visual, or market requirements.",
|
|
2001
|
+
})),
|
|
2002
|
+
targetAudience: Type.Optional(Type.String({
|
|
2003
|
+
description: "Optional confirmed target audience or market.",
|
|
2004
|
+
})),
|
|
2005
|
+
episodeCount: Type.Optional(Type.Number({
|
|
2006
|
+
description: "Optional target episode/segment count.",
|
|
2007
|
+
})),
|
|
2008
|
+
episodeDuration: Type.Optional(Type.String({
|
|
2009
|
+
description: "Optional per-episode/per-segment duration.",
|
|
2010
|
+
})),
|
|
2011
|
+
budget: Type.Optional(Type.String({
|
|
2012
|
+
description: "Optional budget or production constraints.",
|
|
2013
|
+
})),
|
|
2014
|
+
referenceMode: Type.Optional(Type.String({
|
|
2015
|
+
description: "Optional reference mode, e.g. 盛世天下-style multi-ending interactive drama.",
|
|
2016
|
+
})),
|
|
2017
|
+
projectId: Type.Optional(Type.String({
|
|
2018
|
+
description: "Optional output id under interactive-films/.",
|
|
2019
|
+
})),
|
|
2020
|
+
outDir: Type.Optional(Type.String({
|
|
2021
|
+
description: "Optional project-relative output directory. Default interactive-films/.",
|
|
2022
|
+
})),
|
|
2023
|
+
});
|
|
2024
|
+
export function createInteractiveFilmCreationTool(pipeline, projectRoot, options = {}) {
|
|
2025
|
+
return {
|
|
2026
|
+
name: "interactive_film_create",
|
|
2027
|
+
description: "Create an interactive film/game script package with story tree, variables/flags, endings, script, storyboard, and image prompts. " +
|
|
2028
|
+
"Writes human-readable Markdown files under interactive-films/.",
|
|
2029
|
+
label: "Interactive Film Creation",
|
|
2030
|
+
parameters: InteractiveFilmCreateParams,
|
|
2031
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
2032
|
+
const progress = (message) => onUpdate?.(textResult(message));
|
|
2033
|
+
const payload = options.actionPayload?.interactiveFilmCreate;
|
|
2034
|
+
const activatedSkills = resolveProductionToolSkills(options);
|
|
2035
|
+
const result = await runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => runInteractiveFilmCreation({
|
|
2036
|
+
projectRoot,
|
|
2037
|
+
runtime: pipeline.createAgentContext("interactive-film-creation"),
|
|
2038
|
+
title: payload?.title ?? params.title,
|
|
2039
|
+
instruction: params.instruction,
|
|
2040
|
+
sourceKind: payload?.sourceKind ?? params.sourceKind,
|
|
2041
|
+
sourceText: payload?.sourceText ?? params.sourceText,
|
|
2042
|
+
sourcePath: payload?.sourcePath ?? params.sourcePath,
|
|
2043
|
+
requirements: payload?.requirements ?? params.requirements,
|
|
2044
|
+
targetAudience: payload?.targetAudience ?? params.targetAudience,
|
|
2045
|
+
episodeCount: payload?.episodeCount ?? params.episodeCount,
|
|
2046
|
+
episodeDuration: payload?.episodeDuration ?? params.episodeDuration,
|
|
2047
|
+
budget: payload?.budget ?? params.budget,
|
|
2048
|
+
referenceMode: payload?.referenceMode ?? params.referenceMode,
|
|
2049
|
+
language: options.language,
|
|
2050
|
+
projectId: payload?.projectId ?? params.projectId,
|
|
2051
|
+
outDir: payload?.outDir ?? params.outDir,
|
|
2052
|
+
onProgress: progress,
|
|
2053
|
+
}));
|
|
2054
|
+
return textResult([
|
|
2055
|
+
`Interactive film "${result.projectId}" completed.`,
|
|
2056
|
+
`Spec: ${result.specPath}`,
|
|
2057
|
+
`Story graph: ${result.storyGraphPath}`,
|
|
2058
|
+
`Story tree: ${result.storyTreePath}`,
|
|
2059
|
+
`Flags: ${result.flagsPath}`,
|
|
2060
|
+
`Script: ${result.scriptPath}`,
|
|
2061
|
+
`Storyboard: ${result.storyboardPath}`,
|
|
2062
|
+
`Image prompts: ${result.imagePromptsPath}`,
|
|
2063
|
+
`Image assets: ${result.assetsManifestPath}`,
|
|
2064
|
+
].join("\n"), { kind: "interactive_film_created", ...result, skillIds: activatedSkillIds(activatedSkills) });
|
|
2065
|
+
},
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
// ---------------------------------------------------------------------------
|
|
2069
|
+
// 4. Standalone Cover Tool
|
|
2070
|
+
// ---------------------------------------------------------------------------
|
|
2071
|
+
const GenerateCoverParams = Type.Object({
|
|
2072
|
+
title: Type.String({
|
|
2073
|
+
description: "Required book or short-fiction title. Use the real story title when regenerating an existing cover.",
|
|
2074
|
+
}),
|
|
2075
|
+
intro: Type.Optional(Type.String({
|
|
2076
|
+
description: "Optional synopsis or one-paragraph story hook to guide the cover.",
|
|
2077
|
+
})),
|
|
2078
|
+
sellingPoints: Type.Optional(Type.String({
|
|
2079
|
+
description: "Optional selling points separated by semicolons or new lines, e.g. 婚姻背叛;证据反杀;女主冷笑.",
|
|
2080
|
+
})),
|
|
2081
|
+
coverPrompt: Type.Optional(Type.String({
|
|
2082
|
+
description: "Optional concrete or revised visual direction. Use this when the user changes the cover prompt through chat. Keep it short and commercial; do not paste the whole story.",
|
|
2083
|
+
})),
|
|
2084
|
+
outputDir: Type.Optional(Type.String({
|
|
2085
|
+
description: "Optional project-relative directory for cover-prompt.md and cover.png. For an existing short or cover prompt revision, use its existing final/cover directory to overwrite that cover.",
|
|
2086
|
+
})),
|
|
2087
|
+
coverBaseUrl: Type.Optional(Type.String({
|
|
2088
|
+
description: "Optional image API base URL. Usually omit and use Studio cover config.",
|
|
2089
|
+
})),
|
|
2090
|
+
coverEndpoint: Type.Optional(Type.String({
|
|
2091
|
+
description: "Optional exact image endpoint. Overrides coverBaseUrl.",
|
|
2092
|
+
})),
|
|
2093
|
+
coverModel: Type.Optional(Type.String({
|
|
2094
|
+
description: "Optional image model. Usually omit and use Studio cover config.",
|
|
2095
|
+
})),
|
|
2096
|
+
coverSize: Type.Optional(Type.String({
|
|
2097
|
+
description: "Optional image size, default 1024x1360.",
|
|
2098
|
+
})),
|
|
2099
|
+
coverApiKeyEnv: Type.Optional(Type.String({
|
|
2100
|
+
description: "Optional env var containing the cover API key. Usually omit and use Studio cover config.",
|
|
2101
|
+
})),
|
|
2102
|
+
});
|
|
2103
|
+
export function createGenerateCoverTool(projectRoot, options = {}) {
|
|
2104
|
+
return {
|
|
2105
|
+
name: "generate_cover",
|
|
2106
|
+
description: "Generate only a cover image and cover prompt from a title/synopsis/visual direction. " +
|
|
2107
|
+
"Use this when the user asks to create/regenerate a cover or revise the cover prompt through chat, without rerunning story generation.",
|
|
2108
|
+
label: "Generate Cover",
|
|
2109
|
+
parameters: GenerateCoverParams,
|
|
2110
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
2111
|
+
onUpdate?.(textResult("Generating cover image..."));
|
|
2112
|
+
const coverPayload = options.actionPayload?.generateCover;
|
|
2113
|
+
const result = await generateShortFictionCover({
|
|
2114
|
+
projectRoot,
|
|
2115
|
+
title: coverPayload?.title ?? params.title,
|
|
2116
|
+
intro: coverPayload?.intro ?? params.intro,
|
|
2117
|
+
sellingPoints: coverPayload?.sellingPoints ?? params.sellingPoints,
|
|
2118
|
+
coverPrompt: coverPayload?.coverPrompt ?? params.coverPrompt,
|
|
2119
|
+
outputDir: coverPayload?.outputDir ?? params.outputDir,
|
|
2120
|
+
coverBaseUrl: params.coverBaseUrl,
|
|
2121
|
+
coverEndpoint: params.coverEndpoint,
|
|
2122
|
+
coverModel: params.coverModel,
|
|
2123
|
+
coverSize: params.coverSize,
|
|
2124
|
+
coverApiKeyEnv: params.coverApiKeyEnv,
|
|
2125
|
+
signal: _signal,
|
|
2126
|
+
});
|
|
2127
|
+
return textResult([
|
|
2128
|
+
`Cover generated for "${result.title}".`,
|
|
2129
|
+
`Cover prompt: ${result.coverPromptPath}`,
|
|
2130
|
+
`Cover image: ${result.coverImagePath}`,
|
|
2131
|
+
].join("\n"), { kind: "cover_generated", ...result });
|
|
2132
|
+
},
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
// ---------------------------------------------------------------------------
|
|
2136
|
+
// 4. Interactive Play tools
|
|
2137
|
+
// ---------------------------------------------------------------------------
|
|
2138
|
+
const PlayStartParams = Type.Object({
|
|
2139
|
+
title: Type.String({
|
|
2140
|
+
description: "Interactive world title. Use the user's natural direction as a short playable world title.",
|
|
2141
|
+
}),
|
|
2142
|
+
premise: Type.Optional(Type.String({
|
|
2143
|
+
description: "Playable premise: player role, location, pressure, and core conflict. Keep it concise.",
|
|
2144
|
+
})),
|
|
2145
|
+
worldContract: Type.Optional(Type.String({
|
|
2146
|
+
description: "Durable world contract in natural language. Preserve only user-defined long-lived rules: semantic time, role autonomy, object/clue/relationship systems, taboos, or setting laws. Leave empty when the user did not define rules; do not invent RPG/level systems.",
|
|
2147
|
+
})),
|
|
2148
|
+
visualContract: Type.Optional(Type.String({
|
|
2149
|
+
description: "Visual contract for Play illustrations. Preserve only user-defined visual rules; leave empty when unspecified. Do not invent game frames, colored tiers, UI, or stats.",
|
|
2150
|
+
})),
|
|
2151
|
+
mode: Type.Optional(Type.Union([
|
|
2152
|
+
Type.Literal("open"),
|
|
2153
|
+
Type.Literal("guided"),
|
|
2154
|
+
], { description: "open = free actions; guided = emphasize suggested actions. Default open." })),
|
|
2155
|
+
initialScene: Type.Optional(Type.String({
|
|
2156
|
+
description: "Opening scene shown to the player. Write pure narrative prose for the first playable moment, not a config summary, not a question prompt, and not an action/options list.",
|
|
2157
|
+
})),
|
|
2158
|
+
suggestedActions: Type.Optional(Type.Array(SuggestedActionParam)),
|
|
2159
|
+
});
|
|
2160
|
+
export function createPlayStartTool(pipeline, projectRoot, sessionId, playMode, options = {}) {
|
|
2161
|
+
return {
|
|
2162
|
+
name: "play_start",
|
|
2163
|
+
description: "Start an interactive InkOS Play world directly from chat. " +
|
|
2164
|
+
"Use when the user asks to play, roleplay, run an open-world interactive story, or start a Tavern-like scene.",
|
|
2165
|
+
label: "Start Play",
|
|
2166
|
+
parameters: PlayStartParams,
|
|
2167
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
2168
|
+
_signal?.throwIfAborted();
|
|
2169
|
+
onUpdate?.(textResult("Starting interactive world..."));
|
|
2170
|
+
if (!pipeline) {
|
|
2171
|
+
throw new Error("play_start requires an initialized InkOS pipeline to create authoritative world state.");
|
|
2172
|
+
}
|
|
2173
|
+
const playPayload = options.actionPayload?.playStart;
|
|
2174
|
+
const activatedSkills = resolveProductionToolSkills(options);
|
|
2175
|
+
const store = new PlayStore(projectRoot);
|
|
2176
|
+
// The play world is bound 1:1 to the chat session: worldId IS the
|
|
2177
|
+
// sessionId. This removes any "which world?" ambiguity, so two play
|
|
2178
|
+
// sessions never advance each other's world.
|
|
2179
|
+
const worldId = safePlayId(sessionId, sessionId);
|
|
2180
|
+
const runId = "main";
|
|
2181
|
+
const title = playPayload?.title ?? params.title;
|
|
2182
|
+
const premise = playPayload?.premise ?? params.premise;
|
|
2183
|
+
const worldContract = playPayload?.worldContract ?? params.worldContract;
|
|
2184
|
+
const visualContract = playPayload?.visualContract ?? params.visualContract;
|
|
2185
|
+
const initialScene = playPayload?.initialScene?.trim() || params.initialScene;
|
|
2186
|
+
const playLanguage = inferLanguage([title, premise, worldContract, visualContract, initialScene].filter(Boolean).join("\n"));
|
|
2187
|
+
const existingWorld = await store.loadWorld(worldId);
|
|
2188
|
+
const world = await store.createWorld({
|
|
2189
|
+
id: worldId,
|
|
2190
|
+
title: title.trim(),
|
|
2191
|
+
premise: premise?.trim() ?? "",
|
|
2192
|
+
worldContract: worldContract?.trim() ?? "",
|
|
2193
|
+
visualContract: visualContract?.trim() ?? "",
|
|
2194
|
+
mode: playMode ?? params.mode ?? "open",
|
|
2195
|
+
language: playLanguage,
|
|
2196
|
+
});
|
|
2197
|
+
await store.ensureRun(world.id, runId);
|
|
2198
|
+
const existingTranscript = await store.readTranscript(world.id, runId);
|
|
2199
|
+
const sceneText = (initialScene?.trim() || (world.language === "en"
|
|
2200
|
+
? [`You enter "${world.title}".`, world.premise || "The scene is set. Make your first move."].join("\n")
|
|
2201
|
+
: [`你进入「${world.title}」。`, world.premise || "场景已经就位,等待你的第一个动作。"].join("\n"))).trim();
|
|
2202
|
+
const suggestedActions = normalizeSuggestedActions(playPayload?.suggestedActions ?? params.suggestedActions);
|
|
2203
|
+
let seed = null;
|
|
2204
|
+
let graph;
|
|
2205
|
+
try {
|
|
2206
|
+
const db = createPlayDB(store.runDir(world.id, runId));
|
|
2207
|
+
try {
|
|
2208
|
+
seed = await runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => {
|
|
2209
|
+
const ctx = pipeline.createAgentContext("play");
|
|
2210
|
+
const runner = options.runnerFactory?.({
|
|
2211
|
+
projectRoot,
|
|
2212
|
+
worldId: world.id,
|
|
2213
|
+
runId,
|
|
2214
|
+
ctx,
|
|
2215
|
+
db,
|
|
2216
|
+
}) ?? new PlayRunner({
|
|
2217
|
+
projectRoot,
|
|
2218
|
+
worldId: world.id,
|
|
2219
|
+
runId,
|
|
2220
|
+
ctx,
|
|
2221
|
+
db,
|
|
2222
|
+
});
|
|
2223
|
+
return runner.seedOpening({ sceneText, suggestedActions });
|
|
2224
|
+
});
|
|
2225
|
+
_signal?.throwIfAborted();
|
|
2226
|
+
graph = db.snapshot();
|
|
2227
|
+
}
|
|
2228
|
+
finally {
|
|
2229
|
+
closePlayDB(db);
|
|
2230
|
+
}
|
|
2231
|
+
if (!graph?.entities?.some((entity) => entity.id === "actor_player")
|
|
2232
|
+
|| !graph.entities.some((entity) => entity.id !== "actor_player")) {
|
|
2233
|
+
throw new Error(world.language === "en"
|
|
2234
|
+
? "Play opening state is incomplete: no usable player/world graph was created."
|
|
2235
|
+
: "互动世界开场状态不完整:没有生成可用的玩家与世界图谱。");
|
|
2236
|
+
}
|
|
2237
|
+
if (existingTranscript.length === 0) {
|
|
2238
|
+
await store.writeProjection(world.id, runId, "projections/scene.md", `${sceneText}\n`);
|
|
2239
|
+
await store.saveCurrentState(world.id, runId, {
|
|
2240
|
+
turn: 0,
|
|
2241
|
+
worldId: world.id,
|
|
2242
|
+
runId,
|
|
2243
|
+
mode: world.mode,
|
|
2244
|
+
premise: world.premise,
|
|
2245
|
+
worldContract: world.worldContract,
|
|
2246
|
+
visualContract: world.visualContract,
|
|
2247
|
+
});
|
|
2248
|
+
await store.appendTranscriptTurn(world.id, runId, {
|
|
2249
|
+
role: "assistant",
|
|
2250
|
+
content: sceneText,
|
|
2251
|
+
timestamp: Date.now(),
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
catch (error) {
|
|
2256
|
+
_signal?.throwIfAborted();
|
|
2257
|
+
if (!existingWorld)
|
|
2258
|
+
await store.removeWorld(world.id);
|
|
2259
|
+
throw error;
|
|
2260
|
+
}
|
|
2261
|
+
return textResult(sceneText, {
|
|
2262
|
+
kind: "play_world_started",
|
|
2263
|
+
worldId: world.id,
|
|
2264
|
+
runId,
|
|
2265
|
+
title: world.title,
|
|
2266
|
+
mode: world.mode,
|
|
2267
|
+
premise: world.premise,
|
|
2268
|
+
worldContract: world.worldContract,
|
|
2269
|
+
visualContract: world.visualContract,
|
|
2270
|
+
sceneText,
|
|
2271
|
+
suggestedActions,
|
|
2272
|
+
skillIds: activatedSkillIds(activatedSkills),
|
|
2273
|
+
...(seed ? { seedMutation: seed.mutation } : {}),
|
|
2274
|
+
...(graph ? { graph } : {}),
|
|
2275
|
+
});
|
|
2276
|
+
},
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
const PlayStepParams = Type.Object({
|
|
2280
|
+
input: Type.String({
|
|
2281
|
+
description: "The player's next free-form action or chosen option.",
|
|
2282
|
+
}),
|
|
2283
|
+
});
|
|
2284
|
+
const PlayReviseParams = Type.Object({
|
|
2285
|
+
action: Type.Union([
|
|
2286
|
+
Type.Literal("regenerate_last"),
|
|
2287
|
+
Type.Literal("edit_last_input"),
|
|
2288
|
+
Type.Literal("restore_variant"),
|
|
2289
|
+
], {
|
|
2290
|
+
description: "How to revise the latest play turn: regenerate the same player input, edit the previous player input, or restore a saved variant.",
|
|
2291
|
+
}),
|
|
2292
|
+
input: Type.Optional(Type.String({
|
|
2293
|
+
description: "Replacement player input when action=edit_last_input.",
|
|
2294
|
+
})),
|
|
2295
|
+
turn: Type.Optional(Type.Number({
|
|
2296
|
+
description: "Turn number when restoring a saved variant.",
|
|
2297
|
+
})),
|
|
2298
|
+
variantId: Type.Optional(Type.String({
|
|
2299
|
+
description: "Saved variant id when action=restore_variant.",
|
|
2300
|
+
})),
|
|
2301
|
+
});
|
|
2302
|
+
const PlayEntityUpdateParam = Type.Object({
|
|
2303
|
+
id: Type.Optional(Type.String({
|
|
2304
|
+
description: "Existing entity id to update. Use actor_player for the player persona.",
|
|
2305
|
+
})),
|
|
2306
|
+
label: Type.Optional(Type.String({
|
|
2307
|
+
description: "Existing entity label to update when id is unknown.",
|
|
2308
|
+
})),
|
|
2309
|
+
type: Type.Optional(Type.Union([
|
|
2310
|
+
Type.Literal("actor"),
|
|
2311
|
+
Type.Literal("location"),
|
|
2312
|
+
Type.Literal("item"),
|
|
2313
|
+
Type.Literal("evidence"),
|
|
2314
|
+
Type.Literal("clue"),
|
|
2315
|
+
Type.Literal("claim"),
|
|
2316
|
+
Type.Literal("proof_chain"),
|
|
2317
|
+
Type.Literal("organization"),
|
|
2318
|
+
Type.Literal("rule"),
|
|
2319
|
+
Type.Literal("scene"),
|
|
2320
|
+
Type.Literal("event"),
|
|
2321
|
+
], { description: "Entity type when creating a missing entity. Usually actor for character/persona edits." })),
|
|
2322
|
+
summary: Type.Optional(Type.String({
|
|
2323
|
+
description: "Replacement or enriched entity summary, including goals/motives/persona when relevant.",
|
|
2324
|
+
})),
|
|
2325
|
+
status: Type.Optional(Type.String({
|
|
2326
|
+
description: "Natural-language current status. Do not invent numeric meters unless the user asked for them.",
|
|
2327
|
+
})),
|
|
2328
|
+
});
|
|
2329
|
+
const PlayContractReplacementParam = Type.Object({
|
|
2330
|
+
from: Type.String({
|
|
2331
|
+
description: "Exact old wording to replace in the existing contract.",
|
|
2332
|
+
}),
|
|
2333
|
+
to: Type.String({
|
|
2334
|
+
description: "New wording that should replace the old wording.",
|
|
2335
|
+
}),
|
|
2336
|
+
});
|
|
2337
|
+
const PlayEditParams = Type.Object({
|
|
2338
|
+
worldContract: Type.Optional(Type.String({
|
|
2339
|
+
description: "Full updated world contract after applying the user's requested rule change. Use when the user edits world rules, time semantics, item semantics, role autonomy, taboos, or costs.",
|
|
2340
|
+
})),
|
|
2341
|
+
worldContractReplacements: Type.Optional(Type.Array(PlayContractReplacementParam, {
|
|
2342
|
+
description: "Exact replacements for existing world-contract wording. Use when the user says to change/replace X into Y; do not append the new rule while leaving the old wording in place.",
|
|
2343
|
+
})),
|
|
2344
|
+
worldContractAppend: Type.Optional(Type.String({
|
|
2345
|
+
description: "A narrow new world-contract addition. Do not use this for replacements such as 'change X to Y'; use worldContractReplacements or full worldContract instead.",
|
|
2346
|
+
})),
|
|
2347
|
+
visualContract: Type.Optional(Type.String({
|
|
2348
|
+
description: "Full updated visual contract after applying the user's requested image/visual-rule change.",
|
|
2349
|
+
})),
|
|
2350
|
+
visualContractReplacements: Type.Optional(Type.Array(PlayContractReplacementParam, {
|
|
2351
|
+
description: "Exact replacements for existing visual-contract wording. Use when the user says to change/replace one visual rule into another.",
|
|
2352
|
+
})),
|
|
2353
|
+
visualContractAppend: Type.Optional(Type.String({
|
|
2354
|
+
description: "A narrow new visual-contract addition. Do not use this for replacements such as 'change X to Y'; use visualContractReplacements or full visualContract instead.",
|
|
2355
|
+
})),
|
|
2356
|
+
premise: Type.Optional(Type.String({
|
|
2357
|
+
description: "Updated world premise only when the user explicitly changes premise/backstory. Do not rewrite premise for ordinary turns.",
|
|
2358
|
+
})),
|
|
2359
|
+
playerPersona: Type.Optional(Type.String({
|
|
2360
|
+
description: "Updated player persona/identity/goals. This updates the reserved actor_player entity.",
|
|
2361
|
+
})),
|
|
2362
|
+
entityUpdates: Type.Optional(Type.Array(PlayEntityUpdateParam, {
|
|
2363
|
+
description: "Character, object, place, or rule-card updates requested by the user. Use for role goals, status, motives, taboos, or known facts.",
|
|
2364
|
+
})),
|
|
2365
|
+
note: Type.Optional(Type.String({
|
|
2366
|
+
description: "Short human-readable note summarizing what changed.",
|
|
2367
|
+
})),
|
|
2368
|
+
});
|
|
2369
|
+
export function createPlayEditTool(projectRoot, sessionId, language = "zh") {
|
|
2370
|
+
return {
|
|
2371
|
+
name: "play_edit",
|
|
2372
|
+
description: "Persistently edit the active InkOS Play world card, visual contract, player persona, or entity/role cards without advancing time or narrating a turn. " +
|
|
2373
|
+
"Use when the user says to change world rules, visual rules, character goals/persona/status, or long-lived play contracts.",
|
|
2374
|
+
label: "Edit Play World",
|
|
2375
|
+
parameters: PlayEditParams,
|
|
2376
|
+
async execute(_toolCallId, params) {
|
|
2377
|
+
const store = new PlayStore(projectRoot);
|
|
2378
|
+
const worldId = safePlayId(sessionId, sessionId);
|
|
2379
|
+
const runId = "main";
|
|
2380
|
+
const world = await store.loadWorld(worldId);
|
|
2381
|
+
if (!world) {
|
|
2382
|
+
return textResult(language === "en"
|
|
2383
|
+
? "There is no interactive world to edit yet. Start one with play_start first."
|
|
2384
|
+
: "还没有可编辑的互动世界。先用 play_start 开一局。");
|
|
2385
|
+
}
|
|
2386
|
+
const isZh = (world.language ?? "zh") !== "en";
|
|
2387
|
+
const patch = {};
|
|
2388
|
+
const nextWorldContract = mergeContract(world.worldContract, params.worldContract, params.worldContractReplacements, params.worldContractAppend);
|
|
2389
|
+
const nextVisualContract = mergeContract(world.visualContract, params.visualContract, params.visualContractReplacements, params.visualContractAppend);
|
|
2390
|
+
if (nextWorldContract !== world.worldContract)
|
|
2391
|
+
patch.worldContract = nextWorldContract;
|
|
2392
|
+
if (nextVisualContract !== world.visualContract)
|
|
2393
|
+
patch.visualContract = nextVisualContract;
|
|
2394
|
+
const premise = params.premise?.trim();
|
|
2395
|
+
if (premise && premise !== world.premise)
|
|
2396
|
+
patch.premise = premise;
|
|
2397
|
+
const updatedWorld = Object.keys(patch).length > 0
|
|
2398
|
+
? await store.updateWorld(worldId, patch)
|
|
2399
|
+
: world;
|
|
2400
|
+
await store.ensureRun(worldId, runId);
|
|
2401
|
+
const db = createPlayDB(store.runDir(worldId, runId));
|
|
2402
|
+
let updatedEntities = 0;
|
|
2403
|
+
try {
|
|
2404
|
+
const playerPersona = params.playerPersona?.trim();
|
|
2405
|
+
if (playerPersona) {
|
|
2406
|
+
const existingPlayer = db.getEntity("actor_player");
|
|
2407
|
+
upsertPlayEditEntity(db, {
|
|
2408
|
+
id: "actor_player",
|
|
2409
|
+
type: "actor",
|
|
2410
|
+
label: existingPlayer?.label ?? (isZh ? "玩家" : "Player"),
|
|
2411
|
+
summary: playerPersona,
|
|
2412
|
+
status: isZh ? "已更新" : "Updated",
|
|
2413
|
+
});
|
|
2414
|
+
updatedEntities += 1;
|
|
2415
|
+
}
|
|
2416
|
+
for (const update of params.entityUpdates ?? []) {
|
|
2417
|
+
if (upsertPlayEditEntity(db, update))
|
|
2418
|
+
updatedEntities += 1;
|
|
2419
|
+
}
|
|
2420
|
+
const graph = db.snapshot();
|
|
2421
|
+
const currentState = await store.loadCurrentState(worldId, runId).catch(() => ({}));
|
|
2422
|
+
await store.saveCurrentState(worldId, runId, {
|
|
2423
|
+
...(currentState && typeof currentState === "object" ? currentState : {}),
|
|
2424
|
+
worldContract: updatedWorld.worldContract,
|
|
2425
|
+
visualContract: updatedWorld.visualContract,
|
|
2426
|
+
premise: updatedWorld.premise,
|
|
2427
|
+
graphEditedAt: new Date().toISOString(),
|
|
2428
|
+
});
|
|
2429
|
+
return textResult(params.note?.trim() || (isZh ? "互动世界设定已更新。" : "Interactive world settings updated."), {
|
|
2430
|
+
kind: "play_world_updated",
|
|
2431
|
+
worldId,
|
|
2432
|
+
runId,
|
|
2433
|
+
world: updatedWorld,
|
|
2434
|
+
updatedWorldContract: nextWorldContract !== world.worldContract,
|
|
2435
|
+
updatedVisualContract: nextVisualContract !== world.visualContract,
|
|
2436
|
+
updatedPremise: Boolean(patch.premise),
|
|
2437
|
+
updatedEntities,
|
|
2438
|
+
graph,
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
finally {
|
|
2442
|
+
closePlayDB(db);
|
|
2443
|
+
}
|
|
2444
|
+
},
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
export function createPlayStepTool(pipeline, projectRoot, sessionId, options = {}) {
|
|
2448
|
+
return {
|
|
2449
|
+
name: "play_step",
|
|
2450
|
+
description: "Advance the current InkOS Play world by one player action. " +
|
|
2451
|
+
"Use after play_start when the user keeps acting in the interactive scene.",
|
|
2452
|
+
label: "Play Step",
|
|
2453
|
+
parameters: PlayStepParams,
|
|
2454
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
2455
|
+
const input = params.input.trim();
|
|
2456
|
+
if (!input)
|
|
2457
|
+
return textResult("Play input is empty.");
|
|
2458
|
+
const store = new PlayStore(projectRoot);
|
|
2459
|
+
// The play world is bound to this chat session (worldId === sessionId).
|
|
2460
|
+
const worldId = safePlayId(sessionId, sessionId);
|
|
2461
|
+
const runId = "main";
|
|
2462
|
+
const world = await store.loadWorld(worldId);
|
|
2463
|
+
if (!world) {
|
|
2464
|
+
return textResult(options.language === "en"
|
|
2465
|
+
? "There is no interactive world to advance yet. Start one with play_start first."
|
|
2466
|
+
: "还没有可推进的互动世界。先用 play_start 开一局。");
|
|
2467
|
+
}
|
|
2468
|
+
const target = { worldId, runId, world };
|
|
2469
|
+
const activatedSkills = resolveProductionToolSkills(options);
|
|
2470
|
+
onUpdate?.(textResult(`Advancing "${target.worldId}" / "${target.runId}"...`));
|
|
2471
|
+
let runner;
|
|
2472
|
+
let step;
|
|
2473
|
+
try {
|
|
2474
|
+
step = await runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => {
|
|
2475
|
+
const ctx = pipeline.createAgentContext("play");
|
|
2476
|
+
const activeRunner = options.runnerFactory?.({
|
|
2477
|
+
projectRoot,
|
|
2478
|
+
worldId: target.worldId,
|
|
2479
|
+
runId: target.runId,
|
|
2480
|
+
ctx,
|
|
2481
|
+
}) ?? new PlayRunner({
|
|
2482
|
+
projectRoot,
|
|
2483
|
+
worldId: target.worldId,
|
|
2484
|
+
runId: target.runId,
|
|
2485
|
+
ctx,
|
|
2486
|
+
});
|
|
2487
|
+
runner = activeRunner;
|
|
2488
|
+
return activeRunner.step(input);
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2491
|
+
catch (err) {
|
|
2492
|
+
// Never hand a raw tool error to the outer agent — it improvises a fake
|
|
2493
|
+
// "service unavailable / reload your save" message. Return a fixed, graceful
|
|
2494
|
+
// structured failure so the turn fails honestly and recoverably instead.
|
|
2495
|
+
const isZh = (target.world?.language ?? "zh") !== "en";
|
|
2496
|
+
return textResult(isZh
|
|
2497
|
+
? "(系统刚才卡了一下,这一步没能展开。把你刚才想做的再说一遍,我就接着推进。)"
|
|
2498
|
+
: "(The system hiccuped and this step didn't resolve. Say what you just did again and I'll continue.)", {
|
|
2499
|
+
kind: "play_step_failed",
|
|
2500
|
+
worldId: target.worldId,
|
|
2501
|
+
runId: target.runId,
|
|
2502
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2503
|
+
skillIds: activatedSkillIds(activatedSkills),
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
finally {
|
|
2507
|
+
closePlayRunner(runner);
|
|
2508
|
+
}
|
|
2509
|
+
const db = createPlayDB(store.runDir(target.worldId, target.runId));
|
|
2510
|
+
let graph;
|
|
2511
|
+
try {
|
|
2512
|
+
graph = db.snapshot();
|
|
2513
|
+
}
|
|
2514
|
+
finally {
|
|
2515
|
+
closePlayDB(db);
|
|
2516
|
+
}
|
|
2517
|
+
const currentState = await store.loadCurrentState(target.worldId, target.runId).catch(() => null);
|
|
2518
|
+
return textResult(step.sceneText, {
|
|
2519
|
+
kind: "play_turn_advanced",
|
|
2520
|
+
worldId: target.worldId,
|
|
2521
|
+
runId: target.runId,
|
|
2522
|
+
title: target.world?.title,
|
|
2523
|
+
sceneText: step.sceneText,
|
|
2524
|
+
suggestedActions: step.suggestedActions,
|
|
2525
|
+
action: step.action,
|
|
2526
|
+
mutation: step.mutation,
|
|
2527
|
+
currentState,
|
|
2528
|
+
graph,
|
|
2529
|
+
skillIds: activatedSkillIds(activatedSkills),
|
|
2530
|
+
});
|
|
2531
|
+
},
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
export function createPlayReviseTool(pipeline, projectRoot, sessionId, options = {}) {
|
|
2535
|
+
return {
|
|
2536
|
+
name: "play_revise",
|
|
2537
|
+
description: "Regenerate, edit, or restore the latest InkOS Play turn using saved turn checkpoints. " +
|
|
2538
|
+
"Use when the user says to redo the previous turn, try another version, swipe, or replace their last player input.",
|
|
2539
|
+
label: "Revise Play Turn",
|
|
2540
|
+
parameters: PlayReviseParams,
|
|
2541
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
2542
|
+
const store = new PlayStore(projectRoot);
|
|
2543
|
+
const worldId = safePlayId(sessionId, sessionId);
|
|
2544
|
+
const runId = "main";
|
|
2545
|
+
const world = await store.loadWorld(worldId);
|
|
2546
|
+
if (!world) {
|
|
2547
|
+
return textResult(options.language === "en"
|
|
2548
|
+
? "There is no interactive world to redo yet. Start one with play_start first."
|
|
2549
|
+
: "还没有可重做的互动世界。先用 play_start 开一局。");
|
|
2550
|
+
}
|
|
2551
|
+
const isZh = (world.language ?? "zh") !== "en";
|
|
2552
|
+
const activatedSkills = resolveProductionToolSkills(options);
|
|
2553
|
+
let runner;
|
|
2554
|
+
const runWithPlayRunner = (task) => runPipelineWithAgentContext(pipeline, _signal, activatedSkills, () => {
|
|
2555
|
+
const ctx = pipeline.createAgentContext("play");
|
|
2556
|
+
const activeRunner = options.runnerFactory?.({ projectRoot, worldId, runId, ctx }) ?? new PlayRunner({
|
|
2557
|
+
projectRoot,
|
|
2558
|
+
worldId,
|
|
2559
|
+
runId,
|
|
2560
|
+
ctx,
|
|
2561
|
+
});
|
|
2562
|
+
runner = activeRunner;
|
|
2563
|
+
return task(activeRunner);
|
|
2564
|
+
});
|
|
2565
|
+
let replay;
|
|
2566
|
+
// finally 关闭 runner 自建的 play.db 连接:句柄不关闭时 Windows 上无法删除数据库文件。
|
|
2567
|
+
try {
|
|
2568
|
+
if (params.action === "restore_variant") {
|
|
2569
|
+
const turn = params.turn;
|
|
2570
|
+
const variantId = params.variantId?.trim();
|
|
2571
|
+
if (typeof turn !== "number" || !Number.isFinite(turn) || !variantId) {
|
|
2572
|
+
return textResult(isZh
|
|
2573
|
+
? "恢复版本需要 turn 和 variantId。"
|
|
2574
|
+
: "Restoring a variant requires both turn and variantId.");
|
|
2575
|
+
}
|
|
2576
|
+
onUpdate?.(textResult(`Restoring play variant "${variantId}"...`));
|
|
2577
|
+
const restored = await runWithPlayRunner((activeRunner) => activeRunner.restoreVariant({
|
|
2578
|
+
turn: Math.trunc(turn),
|
|
2579
|
+
variantId,
|
|
2580
|
+
}));
|
|
2581
|
+
return textResult(restored.sceneText || (isZh ? "已切换到指定互动回合版本。" : "Switched to the requested play turn variant."), {
|
|
2582
|
+
kind: "play_variant_restored",
|
|
2583
|
+
worldId,
|
|
2584
|
+
runId,
|
|
2585
|
+
title: world.title,
|
|
2586
|
+
turn: restored.turn,
|
|
2587
|
+
variantId: restored.variantId,
|
|
2588
|
+
sceneText: restored.sceneText,
|
|
2589
|
+
skillIds: activatedSkillIds(activatedSkills),
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
const replacement = params.action === "edit_last_input" ? params.input?.trim() : undefined;
|
|
2593
|
+
if (params.action === "edit_last_input" && !replacement) {
|
|
2594
|
+
return textResult(isZh
|
|
2595
|
+
? "编辑上一条玩家动作需要提供新的 input。"
|
|
2596
|
+
: "Editing the previous player action requires a new input.");
|
|
2597
|
+
}
|
|
2598
|
+
onUpdate?.(textResult(params.action === "edit_last_input" ? "Replaying edited play turn..." : "Regenerating last play turn..."));
|
|
2599
|
+
try {
|
|
2600
|
+
replay = await runWithPlayRunner((activeRunner) => activeRunner.regenerateLastTurn(replacement));
|
|
2601
|
+
}
|
|
2602
|
+
catch (err) {
|
|
2603
|
+
return textResult(isZh
|
|
2604
|
+
? "(上一回合暂时不能安全重做。继续输入新的动作,我会从当前状态推进。)"
|
|
2605
|
+
: "(The previous turn cannot be safely regenerated yet. Enter a new action and I will continue from the current state.)", {
|
|
2606
|
+
kind: "play_revise_failed",
|
|
2607
|
+
worldId,
|
|
2608
|
+
runId,
|
|
2609
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2610
|
+
skillIds: activatedSkillIds(activatedSkills),
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
finally {
|
|
2615
|
+
closePlayRunner(runner);
|
|
2616
|
+
}
|
|
2617
|
+
const db = createPlayDB(store.runDir(worldId, runId));
|
|
2618
|
+
let graph;
|
|
2619
|
+
try {
|
|
2620
|
+
graph = db.snapshot();
|
|
2621
|
+
}
|
|
2622
|
+
finally {
|
|
2623
|
+
closePlayDB(db);
|
|
2624
|
+
}
|
|
2625
|
+
const currentState = await store.loadCurrentState(worldId, runId).catch(() => null);
|
|
2626
|
+
return textResult(replay.sceneText, {
|
|
2627
|
+
kind: "play_turn_revised",
|
|
2628
|
+
worldId,
|
|
2629
|
+
runId,
|
|
2630
|
+
title: world.title,
|
|
2631
|
+
sceneText: replay.sceneText,
|
|
2632
|
+
suggestedActions: replay.suggestedActions,
|
|
2633
|
+
action: replay.action,
|
|
2634
|
+
mutation: replay.mutation,
|
|
2635
|
+
replayedInput: replay.replayedInput,
|
|
2636
|
+
previousVariantId: replay.previousVariantId,
|
|
2637
|
+
variantId: replay.variantId,
|
|
2638
|
+
currentState,
|
|
2639
|
+
graph,
|
|
2640
|
+
skillIds: activatedSkillIds(activatedSkills),
|
|
2641
|
+
});
|
|
2642
|
+
},
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
function mergeContract(existing, replacement, replacements, addition) {
|
|
2646
|
+
const next = replacement?.trim();
|
|
2647
|
+
if (next)
|
|
2648
|
+
return next;
|
|
2649
|
+
let current = existing;
|
|
2650
|
+
for (const patch of replacements ?? []) {
|
|
2651
|
+
const from = patch.from.trim();
|
|
2652
|
+
const to = patch.to.trim();
|
|
2653
|
+
if (!from || !to || !current.includes(from))
|
|
2654
|
+
continue;
|
|
2655
|
+
current = current.split(from).join(to);
|
|
2656
|
+
}
|
|
2657
|
+
const add = addition?.trim();
|
|
2658
|
+
if (!add)
|
|
2659
|
+
return current;
|
|
2660
|
+
if (current.includes(add))
|
|
2661
|
+
return current;
|
|
2662
|
+
return current.trim() ? `${current.trim()}\n- ${add}` : add;
|
|
2663
|
+
}
|
|
2664
|
+
function upsertPlayEditEntity(db, update) {
|
|
2665
|
+
const summary = update.summary?.trim();
|
|
2666
|
+
const status = update.status?.trim();
|
|
2667
|
+
const label = update.label?.trim();
|
|
2668
|
+
const entityId = resolvePlayEditEntityId(db, update);
|
|
2669
|
+
if (!entityId && !label)
|
|
2670
|
+
return false;
|
|
2671
|
+
const existing = entityId ? db.getEntity(entityId) : null;
|
|
2672
|
+
const id = entityId || playEditEntityId(update.type ?? "actor", label);
|
|
2673
|
+
db.upsertEntity({
|
|
2674
|
+
id,
|
|
2675
|
+
type: update.type ?? existing?.type ?? "actor",
|
|
2676
|
+
label: label || existing?.label || id,
|
|
2677
|
+
summary: summary ?? existing?.summary ?? "",
|
|
2678
|
+
status: status ?? existing?.status ?? "",
|
|
2679
|
+
createdEventId: existing?.createdEventId ?? "manual-edit",
|
|
2680
|
+
updatedEventId: "manual-edit",
|
|
2681
|
+
});
|
|
2682
|
+
return true;
|
|
2683
|
+
}
|
|
2684
|
+
function resolvePlayEditEntityId(db, update) {
|
|
2685
|
+
const id = update.id?.trim();
|
|
2686
|
+
if (id)
|
|
2687
|
+
return id;
|
|
2688
|
+
const label = update.label?.trim();
|
|
2689
|
+
if (!label)
|
|
2690
|
+
return undefined;
|
|
2691
|
+
const snapshot = db.snapshot();
|
|
2692
|
+
const match = snapshot.entities.find((entity) => entity.label === label || entity.id === label);
|
|
2693
|
+
return match?.id;
|
|
2694
|
+
}
|
|
2695
|
+
function playEditEntityId(type, label) {
|
|
2696
|
+
const ascii = label
|
|
2697
|
+
.trim()
|
|
2698
|
+
.toLowerCase()
|
|
2699
|
+
.replace(/[^a-z0-9\u4e00-\u9fff]+/gu, "_")
|
|
2700
|
+
.replace(/^_+|_+$/g, "")
|
|
2701
|
+
.slice(0, 48);
|
|
2702
|
+
return `${type}_${ascii || Date.now().toString(36)}`;
|
|
2703
|
+
}
|
|
2704
|
+
// ---------------------------------------------------------------------------
|
|
2705
|
+
// 5. Deterministic writing tools
|
|
2706
|
+
// ---------------------------------------------------------------------------
|
|
2707
|
+
const WriteTruthFileParams = Type.Object({
|
|
2708
|
+
bookId: Type.Optional(Type.String({ description: "Book ID. Omit to use the active book." })),
|
|
2709
|
+
fileName: Type.String({ description: "Truth file path under story/. Prefer outline/story_frame.md, outline/volume_map.md, roles/major/<name>.md, roles/minor/<name>.md; flat files such as current_focus.md and author_intent.md are also supported." }),
|
|
2710
|
+
content: Type.String({ description: "Full replacement content for the truth file." }),
|
|
2711
|
+
});
|
|
2712
|
+
export function createWriteTruthFileTool(pipeline, projectRoot, activeBookId) {
|
|
2713
|
+
const tools = createDeterministicInteractionTools(pipeline, projectRoot);
|
|
2714
|
+
return {
|
|
2715
|
+
name: "write_truth_file",
|
|
2716
|
+
description: "Replace a truth/control file under story/ using deterministic project tools.",
|
|
2717
|
+
label: "Write Truth File",
|
|
2718
|
+
parameters: WriteTruthFileParams,
|
|
2719
|
+
async execute(_toolCallId, params) {
|
|
2720
|
+
try {
|
|
2721
|
+
const bookId = resolveToolBookId("write_truth_file", params.bookId, activeBookId);
|
|
2722
|
+
const fileName = assertSafeTruthFileName(params.fileName);
|
|
2723
|
+
await tools.writeTruthFile(bookId, fileName, params.content);
|
|
2724
|
+
return textResult(`Updated "${fileName}" for "${bookId}".`);
|
|
2725
|
+
}
|
|
2726
|
+
catch (err) {
|
|
2727
|
+
return textResult(`write_truth_file failed: ${err?.message ?? String(err)}`);
|
|
2728
|
+
}
|
|
2729
|
+
},
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
const RenameEntityParams = Type.Object({
|
|
2733
|
+
bookId: Type.Optional(Type.String({ description: "Book ID. Omit to use the active book." })),
|
|
2734
|
+
oldValue: Type.String({ description: "Current entity name." }),
|
|
2735
|
+
newValue: Type.String({ description: "New entity name." }),
|
|
2736
|
+
});
|
|
2737
|
+
export function createRenameEntityTool(pipeline, projectRoot, activeBookId) {
|
|
2738
|
+
const tools = createDeterministicInteractionTools(pipeline, projectRoot);
|
|
2739
|
+
return {
|
|
2740
|
+
name: "rename_entity",
|
|
2741
|
+
description: "Rename an entity across truth files and chapters using deterministic edit control.",
|
|
2742
|
+
label: "Rename Entity",
|
|
2743
|
+
parameters: RenameEntityParams,
|
|
2744
|
+
async execute(_toolCallId, params) {
|
|
2745
|
+
const bookId = resolveToolBookId("rename_entity", params.bookId, activeBookId);
|
|
2746
|
+
const result = await tools.renameEntity(bookId, params.oldValue, params.newValue);
|
|
2747
|
+
const summary = result.__interaction?.responseText ?? `Renamed "${params.oldValue}" to "${params.newValue}" in "${bookId}".`;
|
|
2748
|
+
return textResult(summary);
|
|
2749
|
+
},
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
const PatchChapterTextParams = Type.Object({
|
|
2753
|
+
bookId: Type.Optional(Type.String({ description: "Book ID. Omit to use the active book." })),
|
|
2754
|
+
chapterNumber: Type.Number({ description: "Chapter number to patch." }),
|
|
2755
|
+
targetText: Type.String({ description: "Exact text to replace." }),
|
|
2756
|
+
replacementText: Type.String({ description: "Replacement text." }),
|
|
2757
|
+
});
|
|
2758
|
+
const DeleteLatestChapterParams = Type.Object({
|
|
2759
|
+
bookId: Type.Optional(Type.String({ description: "Book ID. Omit to use the active book." })),
|
|
2760
|
+
chapterNumber: Type.Optional(Type.Number({
|
|
2761
|
+
description: "Latest chapter number expected by the user. The tool rejects middle-chapter deletion.",
|
|
2762
|
+
})),
|
|
2763
|
+
});
|
|
2764
|
+
export function createDeleteLatestChapterTool(projectRoot, activeBookId) {
|
|
2765
|
+
return {
|
|
2766
|
+
name: "delete_latest_chapter",
|
|
2767
|
+
description: "Safely delete only the latest chapter, preserve its manuscript under chapters/.trash, " +
|
|
2768
|
+
"and roll story state back to the previous chapter snapshot. Never deletes a middle chapter.",
|
|
2769
|
+
label: "Delete Latest Chapter",
|
|
2770
|
+
parameters: DeleteLatestChapterParams,
|
|
2771
|
+
async execute(_toolCallId, params) {
|
|
2772
|
+
const bookId = resolveToolBookId("delete_latest_chapter", params.bookId, activeBookId);
|
|
2773
|
+
const state = new StateManager(projectRoot);
|
|
2774
|
+
const releaseLock = await state.acquireBookLock(bookId);
|
|
2775
|
+
try {
|
|
2776
|
+
const result = await deleteLatestChapter(state, bookId, {
|
|
2777
|
+
chapterNumber: params.chapterNumber,
|
|
2778
|
+
});
|
|
2779
|
+
return textResult(`Deleted latest chapter ${result.deletedChapter} from "${bookId}", preserved it in trash, and rolled story state back to chapter ${result.rolledBackTo}.`, {
|
|
2780
|
+
kind: "chapter_deleted",
|
|
2781
|
+
...result,
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
finally {
|
|
2785
|
+
await releaseLock();
|
|
2786
|
+
}
|
|
2787
|
+
},
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
export function createPatchChapterTextTool(pipeline, projectRoot, activeBookId) {
|
|
2791
|
+
const tools = createDeterministicInteractionTools(pipeline, projectRoot);
|
|
2792
|
+
return {
|
|
2793
|
+
name: "patch_chapter_text",
|
|
2794
|
+
description: "Apply a deterministic local text patch to a chapter and mark it for review.",
|
|
2795
|
+
label: "Patch Chapter",
|
|
2796
|
+
parameters: PatchChapterTextParams,
|
|
2797
|
+
async execute(_toolCallId, params) {
|
|
2798
|
+
const bookId = resolveToolBookId("patch_chapter_text", params.bookId, activeBookId);
|
|
2799
|
+
const result = await tools.patchChapterText(bookId, params.chapterNumber, params.targetText, params.replacementText);
|
|
2800
|
+
const summary = result.__interaction?.responseText ?? `Patched chapter ${params.chapterNumber} for "${bookId}".`;
|
|
2801
|
+
return textResult(summary);
|
|
2802
|
+
},
|
|
2803
|
+
};
|
|
2804
|
+
}
|
|
2805
|
+
const ReplaceChapterTextParams = Type.Object({
|
|
2806
|
+
bookId: Type.Optional(Type.String({ description: "Book ID. Omit to use the active book." })),
|
|
2807
|
+
chapterNumber: Type.Number({ description: "Chapter number to replace." }),
|
|
2808
|
+
fullText: Type.String({ description: "The complete replacement chapter markdown/text supplied by the user." }),
|
|
2809
|
+
});
|
|
2810
|
+
export function createReplaceChapterTextTool(pipeline, projectRoot, activeBookId) {
|
|
2811
|
+
const tools = createDeterministicInteractionTools(pipeline, projectRoot);
|
|
2812
|
+
return {
|
|
2813
|
+
name: "replace_chapter_text",
|
|
2814
|
+
description: "Replace a whole existing chapter with user-supplied full chapter text and mark it for review. " +
|
|
2815
|
+
"Use only when the user provides the complete replacement chapter; for model-generated rewrites use sub_agent reviser.",
|
|
2816
|
+
label: "Replace Chapter",
|
|
2817
|
+
parameters: ReplaceChapterTextParams,
|
|
2818
|
+
async execute(_toolCallId, params) {
|
|
2819
|
+
const bookId = resolveToolBookId("replace_chapter_text", params.bookId, activeBookId);
|
|
2820
|
+
const result = await tools.replaceChapterText(bookId, params.chapterNumber, params.fullText);
|
|
2821
|
+
const summary = result.__interaction?.responseText ?? `Replaced chapter ${params.chapterNumber} for "${bookId}".`;
|
|
2822
|
+
return textResult(summary);
|
|
2823
|
+
},
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
2826
|
+
const ResyncChapterStateParams = Type.Object({
|
|
2827
|
+
bookId: Type.Optional(Type.String({ description: "Book ID. Omit to use the active book." })),
|
|
2828
|
+
chapterNumber: Type.Optional(Type.Number({ description: "Latest chapter number to rebuild from its persisted body. Omit to use the latest chapter." })),
|
|
2829
|
+
allowNewHooks: Type.Optional(Type.Boolean({
|
|
2830
|
+
description: "Whether settlement may create brand-new hook IDs. Set false when the user asks to preserve stable hook IDs, avoid replacement hooks, or only repair existing truth state.",
|
|
2831
|
+
})),
|
|
2832
|
+
});
|
|
2833
|
+
export function createResyncChapterStateTool(pipeline, activeBookId, options = {}) {
|
|
2834
|
+
return {
|
|
2835
|
+
name: "resync_chapter_state",
|
|
2836
|
+
description: "Keep the persisted chapter body unchanged, rebuild its derived story state, summaries, and hooks from the previous chapter snapshot, then run a fresh audit. " +
|
|
2837
|
+
"Use after an explicit chapter edit or when the user asks to repair/synchronize truth state without rewriting prose. Only the latest chapter is supported.",
|
|
2838
|
+
label: "Resync Chapter State",
|
|
2839
|
+
parameters: ResyncChapterStateParams,
|
|
2840
|
+
async execute(_toolCallId, params, signal) {
|
|
2841
|
+
const bookId = resolveToolBookId("resync_chapter_state", params.bookId, activeBookId);
|
|
2842
|
+
const activatedSkills = resolveProductionToolSkills(options);
|
|
2843
|
+
const result = await runPipelineWithAgentContext(pipeline, signal, activatedSkills, () => pipeline.resyncChapterStateAndAudit(bookId, params.chapterNumber, {
|
|
2844
|
+
allowNewHooks: params.allowNewHooks,
|
|
2845
|
+
}));
|
|
2846
|
+
const issues = result.audit.issues;
|
|
2847
|
+
const zh = options.language !== "en";
|
|
2848
|
+
const summary = result.audit.passed
|
|
2849
|
+
? (zh
|
|
2850
|
+
? `第 ${result.chapter.chapterNumber} 章正文未改动;状态、摘要与伏笔已从上一章快照重建,重新审稿通过。`
|
|
2851
|
+
: `Chapter ${result.chapter.chapterNumber} prose was unchanged; state, summaries, and hooks were rebuilt from the previous snapshot, and the fresh audit passed.`)
|
|
2852
|
+
: [
|
|
2853
|
+
zh
|
|
2854
|
+
? `第 ${result.chapter.chapterNumber} 章正文未改动;状态、摘要与伏笔已重建,但重新审稿仍有 ${issues.length} 个问题:`
|
|
2855
|
+
: `Chapter ${result.chapter.chapterNumber} prose was unchanged; state, summaries, and hooks were rebuilt, but the fresh audit still found ${issues.length} issue(s):`,
|
|
2856
|
+
...issues.map((issue) => `- [${issue.severity}] ${issue.description}${issue.suggestion ? ` (${issue.suggestion})` : ""}`),
|
|
2857
|
+
].join("\n");
|
|
2858
|
+
return textResult(summary, {
|
|
2859
|
+
kind: "chapter_state_resynced",
|
|
2860
|
+
bookId,
|
|
2861
|
+
chapterNumber: result.chapter.chapterNumber,
|
|
2862
|
+
status: result.audit.passed ? "ready-for-review" : "audit-failed",
|
|
2863
|
+
auditPassed: result.audit.passed,
|
|
2864
|
+
auditIssues: issues,
|
|
2865
|
+
summary: result.audit.summary,
|
|
2866
|
+
skillIds: activatedSkillIds(activatedSkills),
|
|
2867
|
+
});
|
|
2868
|
+
},
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2871
|
+
// ---------------------------------------------------------------------------
|
|
2872
|
+
// 3. Read Tool
|
|
2873
|
+
// ---------------------------------------------------------------------------
|
|
2874
|
+
const ReadParams = Type.Object({
|
|
2875
|
+
path: Type.String({ description: "File path relative to the tool's permitted read root, or an absolute path when system path reading is enabled." }),
|
|
2876
|
+
});
|
|
2877
|
+
function resolveReadPath(readRoot, requestedPath, options) {
|
|
2878
|
+
if (options.allowSystemPaths && isAbsolute(requestedPath)) {
|
|
2879
|
+
return resolve(requestedPath);
|
|
2880
|
+
}
|
|
2881
|
+
return safeChildPath(readRoot, requestedPath);
|
|
2882
|
+
}
|
|
2883
|
+
export function createReadTool(projectRoot, options = {}) {
|
|
2884
|
+
const readRoot = options.scope === "project" ? projectRoot : join(projectRoot, "books");
|
|
2885
|
+
const description = options.allowSystemPaths
|
|
2886
|
+
? "Read a file. Relative paths resolve under books/; absolute paths read from the system filesystem."
|
|
2887
|
+
: options.scope === "project"
|
|
2888
|
+
? "Read a UTF-8 file inside the current InkOS project. Path is relative to the project root."
|
|
2889
|
+
: "Read a file from the book directory. Path is relative to books/.";
|
|
2890
|
+
return {
|
|
2891
|
+
name: "read",
|
|
2892
|
+
description,
|
|
2893
|
+
label: "Read File",
|
|
2894
|
+
parameters: ReadParams,
|
|
2895
|
+
async execute(_toolCallId, params) {
|
|
2896
|
+
try {
|
|
2897
|
+
const filePath = resolveReadPath(readRoot, params.path, options);
|
|
2898
|
+
const content = await readFile(filePath, "utf-8");
|
|
2899
|
+
return textResult(content);
|
|
2900
|
+
}
|
|
2901
|
+
catch (err) {
|
|
2902
|
+
return textResult(`Failed to read "${params.path}": ${err?.message ?? String(err)}`);
|
|
2903
|
+
}
|
|
2904
|
+
},
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
// ---------------------------------------------------------------------------
|
|
2908
|
+
// 3. Edit Tool
|
|
2909
|
+
// ---------------------------------------------------------------------------
|
|
2910
|
+
const EditParams = Type.Object({
|
|
2911
|
+
path: Type.String({ description: "File path relative to books/" }),
|
|
2912
|
+
old_string: Type.String({ description: "Exact string to find in the file" }),
|
|
2913
|
+
new_string: Type.String({ description: "Replacement string" }),
|
|
2914
|
+
});
|
|
2915
|
+
export function createEditTool(projectRoot) {
|
|
2916
|
+
const booksRoot = join(projectRoot, "books");
|
|
2917
|
+
return {
|
|
2918
|
+
name: "edit",
|
|
2919
|
+
description: "Edit a file under books/ via exact string replacement. " +
|
|
2920
|
+
"old_string must appear exactly once in the file. " +
|
|
2921
|
+
"For chapter text use patch_chapter_text; for canonical truth files (outline/story_frame.md, outline/volume_map.md, roles/**/*.md, current_focus.md, author_intent.md) prefer write_truth_file; " +
|
|
2922
|
+
"to rewrite or polish a whole chapter call sub_agent with agent=\"reviser\".",
|
|
2923
|
+
label: "Edit File",
|
|
2924
|
+
parameters: EditParams,
|
|
2925
|
+
async execute(_toolCallId, params) {
|
|
2926
|
+
try {
|
|
2927
|
+
const filePath = safeBooksPath(booksRoot, params.path);
|
|
2928
|
+
const content = await readFile(filePath, "utf-8");
|
|
2929
|
+
const idx = content.indexOf(params.old_string);
|
|
2930
|
+
if (idx === -1) {
|
|
2931
|
+
return textResult(`old_string not found in "${params.path}".`);
|
|
2932
|
+
}
|
|
2933
|
+
if (content.indexOf(params.old_string, idx + 1) !== -1) {
|
|
2934
|
+
return textResult(`old_string appears more than once in "${params.path}". Provide a more specific match.`);
|
|
2935
|
+
}
|
|
2936
|
+
const updated = content.slice(0, idx) + params.new_string + content.slice(idx + params.old_string.length);
|
|
2937
|
+
await writeFile(filePath, updated, "utf-8");
|
|
2938
|
+
return textResult(`File "${params.path}" updated successfully.`);
|
|
2939
|
+
}
|
|
2940
|
+
catch (err) {
|
|
2941
|
+
return textResult(`Failed to edit "${params.path}": ${err?.message ?? String(err)}`);
|
|
2942
|
+
}
|
|
2943
|
+
},
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
// ---------------------------------------------------------------------------
|
|
2947
|
+
// 4. Write Tool
|
|
2948
|
+
// ---------------------------------------------------------------------------
|
|
2949
|
+
const WriteFileParams = Type.Object({
|
|
2950
|
+
path: Type.String({ description: "File path relative to books/" }),
|
|
2951
|
+
content: Type.String({ description: "Full file content to write" }),
|
|
2952
|
+
});
|
|
2953
|
+
export function createWriteFileTool(projectRoot) {
|
|
2954
|
+
const booksRoot = join(projectRoot, "books");
|
|
2955
|
+
return {
|
|
2956
|
+
name: "write",
|
|
2957
|
+
description: "Create a new file, or fully replace an existing file's content under books/. " +
|
|
2958
|
+
"Parent directories are created automatically. Existing content is overwritten silently — " +
|
|
2959
|
+
"for canonical truth files prefer write_truth_file; " +
|
|
2960
|
+
"for whole-chapter rewrites/polishing call sub_agent with agent=\"reviser\".",
|
|
2961
|
+
label: "Write File",
|
|
2962
|
+
parameters: WriteFileParams,
|
|
2963
|
+
async execute(_toolCallId, params) {
|
|
2964
|
+
try {
|
|
2965
|
+
const filePath = safeBooksPath(booksRoot, params.path);
|
|
2966
|
+
const parentDir = resolve(filePath, "..");
|
|
2967
|
+
const { mkdir } = await import("node:fs/promises");
|
|
2968
|
+
await mkdir(parentDir, { recursive: true });
|
|
2969
|
+
await writeFile(filePath, params.content, "utf-8");
|
|
2970
|
+
return textResult(`File "${params.path}" written successfully.`);
|
|
2971
|
+
}
|
|
2972
|
+
catch (err) {
|
|
2973
|
+
return textResult(`Failed to write "${params.path}": ${err?.message ?? String(err)}`);
|
|
2974
|
+
}
|
|
2975
|
+
},
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
// ---------------------------------------------------------------------------
|
|
2979
|
+
// 5. Grep Tool
|
|
2980
|
+
// ---------------------------------------------------------------------------
|
|
2981
|
+
const GrepParams = Type.Object({
|
|
2982
|
+
bookId: Type.String({ description: "Book ID to search within" }),
|
|
2983
|
+
pattern: Type.String({ description: "Search pattern (plain text or regex)" }),
|
|
2984
|
+
});
|
|
2985
|
+
export function createGrepTool(projectRoot) {
|
|
2986
|
+
const booksRoot = join(projectRoot, "books");
|
|
2987
|
+
return {
|
|
2988
|
+
name: "grep",
|
|
2989
|
+
description: "Search for a text pattern across a book's story/ and chapters/ directories. Returns matching lines.",
|
|
2990
|
+
label: "Search",
|
|
2991
|
+
parameters: GrepParams,
|
|
2992
|
+
async execute(_toolCallId, params) {
|
|
2993
|
+
try {
|
|
2994
|
+
const bookDir = safeBooksPath(booksRoot, params.bookId);
|
|
2995
|
+
const regex = new RegExp(params.pattern, "gi");
|
|
2996
|
+
const results = [];
|
|
2997
|
+
async function searchDir(dir, prefix) {
|
|
2998
|
+
let entries;
|
|
2999
|
+
try {
|
|
3000
|
+
entries = await readdir(dir);
|
|
3001
|
+
}
|
|
3002
|
+
catch {
|
|
3003
|
+
return; // directory doesn't exist
|
|
3004
|
+
}
|
|
3005
|
+
for (const entry of entries) {
|
|
3006
|
+
const fullPath = join(dir, entry);
|
|
3007
|
+
const entryStat = await stat(fullPath);
|
|
3008
|
+
if (entryStat.isDirectory()) {
|
|
3009
|
+
await searchDir(fullPath, `${prefix}${entry}/`);
|
|
3010
|
+
}
|
|
3011
|
+
else if (entry.endsWith(".md") || entry.endsWith(".txt") || entry.endsWith(".json")) {
|
|
3012
|
+
const content = await readFile(fullPath, "utf-8");
|
|
3013
|
+
const lines = content.split("\n");
|
|
3014
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3015
|
+
if (regex.test(lines[i])) {
|
|
3016
|
+
results.push(`${prefix}${entry}:${i + 1}: ${lines[i]}`);
|
|
3017
|
+
regex.lastIndex = 0; // reset for next test
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
await Promise.all([
|
|
3024
|
+
searchDir(join(bookDir, "story"), "story/"),
|
|
3025
|
+
searchDir(join(bookDir, "chapters"), "chapters/"),
|
|
3026
|
+
]);
|
|
3027
|
+
if (results.length === 0) {
|
|
3028
|
+
return textResult(`No matches for "${params.pattern}" in book "${params.bookId}".`);
|
|
3029
|
+
}
|
|
3030
|
+
const truncated = results.length > 100
|
|
3031
|
+
? results.slice(0, 100).join("\n") + `\n\n... [${results.length - 100} more matches]`
|
|
3032
|
+
: results.join("\n");
|
|
3033
|
+
return textResult(truncated);
|
|
3034
|
+
}
|
|
3035
|
+
catch (err) {
|
|
3036
|
+
return textResult(`Grep failed: ${err?.message ?? String(err)}`);
|
|
3037
|
+
}
|
|
3038
|
+
},
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
// ---------------------------------------------------------------------------
|
|
3042
|
+
// 5. Ls Tool
|
|
3043
|
+
// ---------------------------------------------------------------------------
|
|
3044
|
+
const LsParams = Type.Object({
|
|
3045
|
+
bookId: Type.String({ description: "Book ID" }),
|
|
3046
|
+
subdir: Type.Optional(Type.String({ description: "Subdirectory within the book, e.g. 'story', 'chapters', 'story/runtime'" })),
|
|
3047
|
+
});
|
|
3048
|
+
export function createLsTool(projectRoot) {
|
|
3049
|
+
const booksRoot = join(projectRoot, "books");
|
|
3050
|
+
return {
|
|
3051
|
+
name: "ls",
|
|
3052
|
+
description: "List files in a book directory. Optionally specify a subdirectory like 'story' or 'chapters'.",
|
|
3053
|
+
label: "List Files",
|
|
3054
|
+
parameters: LsParams,
|
|
3055
|
+
async execute(_toolCallId, params) {
|
|
3056
|
+
try {
|
|
3057
|
+
const base = safeBooksPath(booksRoot, params.bookId);
|
|
3058
|
+
const target = params.subdir ? safeBooksPath(base, params.subdir) : base;
|
|
3059
|
+
const entries = await readdir(target);
|
|
3060
|
+
const details = [];
|
|
3061
|
+
for (const entry of entries) {
|
|
3062
|
+
const fullPath = join(target, entry);
|
|
3063
|
+
try {
|
|
3064
|
+
const entryStat = await stat(fullPath);
|
|
3065
|
+
const suffix = entryStat.isDirectory() ? "/" : ` (${entryStat.size} bytes)`;
|
|
3066
|
+
details.push(`${entry}${suffix}`);
|
|
3067
|
+
}
|
|
3068
|
+
catch {
|
|
3069
|
+
details.push(entry);
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
if (details.length === 0) {
|
|
3073
|
+
return textResult(`Directory is empty: ${params.bookId}/${params.subdir ?? ""}`);
|
|
3074
|
+
}
|
|
3075
|
+
return textResult(details.join("\n"));
|
|
3076
|
+
}
|
|
3077
|
+
catch (err) {
|
|
3078
|
+
return textResult(`Failed to list "${params.bookId}/${params.subdir ?? ""}": ${err?.message ?? String(err)}`);
|
|
3079
|
+
}
|
|
3080
|
+
},
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
//# sourceMappingURL=agent-tools.js.map
|