@actalk/inkos-core 1.6.3 → 1.7.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/dist/agent/agent-session.d.ts.map +1 -1
- package/dist/agent/agent-session.js +21 -11
- package/dist/agent/agent-session.js.map +1 -1
- package/dist/agent/agent-system-prompt.d.ts.map +1 -1
- package/dist/agent/agent-system-prompt.js +14 -8
- package/dist/agent/agent-system-prompt.js.map +1 -1
- package/dist/agent/agent-tools.d.ts +34 -2
- package/dist/agent/agent-tools.d.ts.map +1 -1
- package/dist/agent/agent-tools.js +236 -54
- package/dist/agent/agent-tools.js.map +1 -1
- package/dist/agent/chapter-import-source.d.ts +15 -0
- package/dist/agent/chapter-import-source.d.ts.map +1 -0
- package/dist/agent/chapter-import-source.js +41 -0
- package/dist/agent/chapter-import-source.js.map +1 -0
- package/dist/agent/film-authoring-tools.d.ts +5 -3
- package/dist/agent/film-authoring-tools.d.ts.map +1 -1
- package/dist/agent/film-authoring-tools.js +34 -19
- package/dist/agent/film-authoring-tools.js.map +1 -1
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agents/base.d.ts +1 -0
- package/dist/agents/base.d.ts.map +1 -1
- package/dist/agents/base.js +2 -0
- package/dist/agents/base.js.map +1 -1
- package/dist/agents/script-storyboard.d.ts +3 -0
- package/dist/agents/script-storyboard.d.ts.map +1 -1
- package/dist/agents/script-storyboard.js +221 -14
- package/dist/agents/script-storyboard.js.map +1 -1
- package/dist/agents/short-fiction.d.ts +13 -2
- package/dist/agents/short-fiction.d.ts.map +1 -1
- package/dist/agents/short-fiction.js +77 -43
- package/dist/agents/short-fiction.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -8
- package/dist/index.js.map +1 -1
- package/dist/interaction/action-envelope.d.ts +53 -1
- package/dist/interaction/action-envelope.d.ts.map +1 -1
- package/dist/interaction/action-envelope.js +9 -0
- package/dist/interaction/action-envelope.js.map +1 -1
- package/dist/interaction/intents.d.ts +2 -2
- package/dist/interaction/project-control.d.ts +1 -1
- package/dist/interactive-film/generate.d.ts +1 -0
- package/dist/interactive-film/generate.d.ts.map +1 -1
- package/dist/interactive-film/generate.js +15 -5
- package/dist/interactive-film/generate.js.map +1 -1
- package/dist/llm/provider.d.ts +1 -0
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/llm/provider.js +64 -18
- package/dist/llm/provider.js.map +1 -1
- package/dist/llm/providers/endpoints/openrouter.d.ts.map +1 -1
- package/dist/llm/providers/endpoints/openrouter.js +3 -2
- package/dist/llm/providers/endpoints/openrouter.js.map +1 -1
- 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.map +1 -1
- package/dist/materials/ingest.js +4 -3
- package/dist/materials/ingest.js.map +1 -1
- package/dist/materials/retrieve.d.ts.map +1 -1
- package/dist/materials/retrieve.js +3 -1
- package/dist/materials/retrieve.js.map +1 -1
- package/dist/models/book.d.ts +28 -0
- package/dist/models/book.d.ts.map +1 -1
- package/dist/models/book.js +22 -0
- package/dist/models/book.js.map +1 -1
- package/dist/models/project.d.ts +40 -0
- package/dist/models/project.d.ts.map +1 -1
- package/dist/models/project.js +5 -0
- package/dist/models/project.js.map +1 -1
- package/dist/notify/dispatcher.d.ts.map +1 -1
- package/dist/notify/dispatcher.js +8 -5
- package/dist/notify/dispatcher.js.map +1 -1
- package/dist/notify/feishu.d.ts +2 -1
- package/dist/notify/feishu.d.ts.map +1 -1
- package/dist/notify/feishu.js +12 -6
- package/dist/notify/feishu.js.map +1 -1
- 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 +2 -1
- package/dist/notify/telegram.d.ts.map +1 -1
- package/dist/notify/telegram.js +3 -2
- package/dist/notify/telegram.js.map +1 -1
- package/dist/notify/wechat-work.d.ts +2 -1
- package/dist/notify/wechat-work.d.ts.map +1 -1
- package/dist/notify/wechat-work.js +5 -5
- package/dist/notify/wechat-work.js.map +1 -1
- package/dist/pipeline/runner.d.ts +13 -1
- package/dist/pipeline/runner.d.ts.map +1 -1
- package/dist/pipeline/runner.js +41 -7
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/pipeline/script-storyboard-runner.d.ts +3 -0
- package/dist/pipeline/script-storyboard-runner.d.ts.map +1 -1
- package/dist/pipeline/script-storyboard-runner.js +86 -43
- package/dist/pipeline/script-storyboard-runner.js.map +1 -1
- package/dist/pipeline/short-fiction-runner.d.ts +3 -1
- package/dist/pipeline/short-fiction-runner.d.ts.map +1 -1
- package/dist/pipeline/short-fiction-runner.js +80 -33
- package/dist/pipeline/short-fiction-runner.js.map +1 -1
- package/dist/play/play-runner.d.ts +8 -0
- package/dist/play/play-runner.d.ts.map +1 -1
- package/dist/play/play-runner.js +14 -0
- package/dist/play/play-runner.js.map +1 -1
- package/dist/prompts/short-fiction.d.ts +14 -13
- package/dist/prompts/short-fiction.d.ts.map +1 -1
- package/dist/prompts/short-fiction.js +253 -14
- package/dist/prompts/short-fiction.js.map +1 -1
- package/dist/state/manager.d.ts +16 -2
- package/dist/state/manager.d.ts.map +1 -1
- package/dist/state/manager.js +218 -31
- package/dist/state/manager.js.map +1 -1
- 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 +8 -0
- package/dist/translation/llm-model.d.ts.map +1 -0
- package/dist/translation/llm-model.js +134 -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 +11 -0
- package/dist/translation/run-store.d.ts.map +1 -0
- package/dist/translation/run-store.js +53 -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 +88 -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 +77 -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/effective-llm-config.js +19 -1
- package/dist/utils/effective-llm-config.js.map +1 -1
- 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/package.json +4 -3
|
@@ -12,9 +12,11 @@ import { safeChildPath } from "../utils/path-safety.js";
|
|
|
12
12
|
import { normalizePlatformId, normalizePlatformOrOther } from "../models/book.js";
|
|
13
13
|
import { generateShortFictionCover, runShortFictionProduction } from "../pipeline/short-fiction-runner.js";
|
|
14
14
|
import { runInteractiveFilmCreation, runScriptCreation, runStoryboardCreation } from "../pipeline/script-storyboard-runner.js";
|
|
15
|
+
import { createTranslationProjectFromFile } from "../translation/index.js";
|
|
15
16
|
import { runResearchReport } from "../agents/researcher.js";
|
|
16
17
|
import { ingestMaterial } from "../materials/ingest.js";
|
|
17
18
|
import { retrieveMaterials } from "../materials/retrieve.js";
|
|
19
|
+
import { loadChaptersFromPath } from "./chapter-import-source.js";
|
|
18
20
|
import { createPlayDB } from "../play/play-db-factory.js";
|
|
19
21
|
import { PlayRunner } from "../play/play-runner.js";
|
|
20
22
|
import { PlayStore } from "../play/play-store.js";
|
|
@@ -49,6 +51,10 @@ function createDeterministicInteractionTools(pipeline, projectRoot) {
|
|
|
49
51
|
function closePlayDB(db) {
|
|
50
52
|
db.close?.();
|
|
51
53
|
}
|
|
54
|
+
// runnerFactory 注入的测试替身没有 close 方法,可选调用兜底。
|
|
55
|
+
function closePlayRunner(runner) {
|
|
56
|
+
runner.close?.();
|
|
57
|
+
}
|
|
52
58
|
function safePlayId(value, fallback) {
|
|
53
59
|
const raw = (value?.trim() || fallback).slice(0, 80);
|
|
54
60
|
if (!raw || raw === "." || raw === ".." || raw.includes("/") || raw.includes("\\") || raw.includes("\0")) {
|
|
@@ -98,6 +104,7 @@ const ProposeActionParams = Type.Object({
|
|
|
98
104
|
Type.Literal("script_create"),
|
|
99
105
|
Type.Literal("storyboard_create"),
|
|
100
106
|
Type.Literal("interactive_film_create"),
|
|
107
|
+
Type.Literal("translation_create"),
|
|
101
108
|
Type.Literal("draft_structure"),
|
|
102
109
|
Type.Literal("connect_choice"),
|
|
103
110
|
Type.Literal("remove_node"),
|
|
@@ -229,6 +236,13 @@ const ProposeActionParams = Type.Object({
|
|
|
229
236
|
projectId: Type.Optional(Type.String({ description: "Optional output id under interactive-films/." })),
|
|
230
237
|
outDir: Type.Optional(Type.String({ description: "Optional project-relative output directory. Default interactive-films/." })),
|
|
231
238
|
}, { description: "Structured execution args for action=interactive_film_create." })),
|
|
239
|
+
translationCreate: Type.Optional(Type.Object({
|
|
240
|
+
filePath: Type.Optional(Type.String({ description: "Project-relative EPUB/PDF/TXT/Markdown source file path to translate." })),
|
|
241
|
+
sourceLanguage: Type.Optional(Type.String({ description: "Source language as a human-readable name, e.g. Auto detect, Japanese, English, Chinese (Simplified), 繁体中文(台湾). Do not require ISO abbreviations." })),
|
|
242
|
+
targetLanguage: Type.Optional(Type.String({ description: "Target language as a human-readable name, e.g. Chinese (Simplified), English, Japanese, Korean, Brazilian Portuguese. Do not require ISO abbreviations." })),
|
|
243
|
+
title: Type.Optional(Type.String({ description: "Optional translation project title." })),
|
|
244
|
+
segmentMaxChars: Type.Optional(Type.Number({ description: "Optional long-paragraph split threshold." })),
|
|
245
|
+
}, { description: "Structured execution args for action=translation_create." })),
|
|
232
246
|
});
|
|
233
247
|
function proposedActionSessionKind(action) {
|
|
234
248
|
if (action === "create_book")
|
|
@@ -241,6 +255,8 @@ function proposedActionSessionKind(action) {
|
|
|
241
255
|
return "storyboard";
|
|
242
256
|
if (action === "interactive_film_create")
|
|
243
257
|
return "interactive-film";
|
|
258
|
+
if (action === "translation_create")
|
|
259
|
+
return "chat";
|
|
244
260
|
if (action === "draft_structure" || action === "connect_choice" || action === "remove_node")
|
|
245
261
|
return "interactive-film-authoring";
|
|
246
262
|
if (action === "fanfic_init" || action === "continuation_import" || action === "spinoff_create" || action === "style_imitation")
|
|
@@ -282,6 +298,8 @@ function proposedActionFallbackTitle(action, isZh) {
|
|
|
282
298
|
return isZh ? "创建分镜" : "Create storyboard";
|
|
283
299
|
case "interactive_film_create":
|
|
284
300
|
return isZh ? "创建互动影游" : "Create interactive film";
|
|
301
|
+
case "translation_create":
|
|
302
|
+
return isZh ? "创建翻译项目" : "Create translation project";
|
|
285
303
|
case "draft_structure":
|
|
286
304
|
return isZh ? "生成故事结构" : "Draft story structure";
|
|
287
305
|
case "connect_choice":
|
|
@@ -392,6 +410,11 @@ function proposedActionPayload(params) {
|
|
|
392
410
|
if (interactiveFilmCreate)
|
|
393
411
|
payload.interactiveFilmCreate = interactiveFilmCreate;
|
|
394
412
|
}
|
|
413
|
+
if (params.action === "translation_create") {
|
|
414
|
+
const translationCreate = compactObject(params.translationCreate);
|
|
415
|
+
if (translationCreate)
|
|
416
|
+
payload.translationCreate = translationCreate;
|
|
417
|
+
}
|
|
395
418
|
return Object.keys(payload).length > 0 ? payload : undefined;
|
|
396
419
|
}
|
|
397
420
|
function validateProposedActionPayload(payload) {
|
|
@@ -432,6 +455,12 @@ function assertExecutableProposedAction(params, payload) {
|
|
|
432
455
|
}
|
|
433
456
|
if (params.action === "interactive_film_create") {
|
|
434
457
|
requireProposedText(payload?.interactiveFilmCreate?.title, "interactiveFilmCreate.title");
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (params.action === "translation_create") {
|
|
461
|
+
requireProposedText(payload?.translationCreate?.filePath, "translationCreate.filePath");
|
|
462
|
+
requireProposedText(payload?.translationCreate?.sourceLanguage, "translationCreate.sourceLanguage");
|
|
463
|
+
requireProposedText(payload?.translationCreate?.targetLanguage, "translationCreate.targetLanguage");
|
|
435
464
|
}
|
|
436
465
|
}
|
|
437
466
|
export function createProposeActionTool(language = "zh", options = {}) {
|
|
@@ -562,7 +591,16 @@ function prepareSubAgentArguments(args) {
|
|
|
562
591
|
}
|
|
563
592
|
return prepared;
|
|
564
593
|
}
|
|
594
|
+
function runPipelineWithAbortSignal(pipeline, signal, task) {
|
|
595
|
+
// PipelineRunner owns the async cancellation scope. The fallback keeps
|
|
596
|
+
// lightweight embedders/test doubles source-compatible.
|
|
597
|
+
const runner = pipeline;
|
|
598
|
+
return runner.runWithAbortSignal
|
|
599
|
+
? runner.runWithAbortSignal(signal, task)
|
|
600
|
+
: task();
|
|
601
|
+
}
|
|
565
602
|
export function createSubAgentTool(pipeline, activeBookId, projectRoot, options = {}) {
|
|
603
|
+
const sessionIsZh = (options.language ?? "zh") !== "en";
|
|
566
604
|
return {
|
|
567
605
|
name: "sub_agent",
|
|
568
606
|
description: options.architectCreateOnly
|
|
@@ -586,7 +624,9 @@ export function createSubAgentTool(pipeline, activeBookId, projectRoot, options
|
|
|
586
624
|
return textResult("No active book. Only the architect agent can create a book from this session.");
|
|
587
625
|
}
|
|
588
626
|
if (activeBookId && agent === "architect" && !revise) {
|
|
589
|
-
return textResult(
|
|
627
|
+
return textResult(sessionIsZh
|
|
628
|
+
? "当前已有书籍,不需要建书。如果你想创建新书,请先回到首页。"
|
|
629
|
+
: "This session already has a book, so no new book is needed. To create a new book, go back to the home page first.");
|
|
590
630
|
}
|
|
591
631
|
switch (agent) {
|
|
592
632
|
case "architect": {
|
|
@@ -597,9 +637,11 @@ export function createSubAgentTool(pipeline, activeBookId, projectRoot, options
|
|
|
597
637
|
}
|
|
598
638
|
const targetBookId = resolveToolBookId("architect", bookId, activeBookId);
|
|
599
639
|
progress(`Revising foundation for "${targetBookId}"...`);
|
|
600
|
-
await pipeline.reviseFoundation(targetBookId, feedback ?? instruction);
|
|
640
|
+
await runPipelineWithAbortSignal(pipeline, _signal, () => pipeline.reviseFoundation(targetBookId, feedback ?? instruction));
|
|
601
641
|
progress(`Foundation revised for "${targetBookId}".`);
|
|
602
|
-
return textResult(
|
|
642
|
+
return textResult(sessionIsZh
|
|
643
|
+
? `Book "${targetBookId}" 架构稿已按要求重写。原书的条目式架构稿已备份到 story/.backup-phase4-<时间戳>/。`
|
|
644
|
+
: `Book "${targetBookId}" foundation has been rewritten as requested. The previous itemized foundation was backed up to story/.backup-phase4-<timestamp>/.`);
|
|
603
645
|
}
|
|
604
646
|
const confirmedTitle = createBookPayload?.title?.trim();
|
|
605
647
|
const resolvedTitle = confirmedTitle || title?.trim();
|
|
@@ -614,7 +656,7 @@ export function createSubAgentTool(pipeline, activeBookId, projectRoot, options
|
|
|
614
656
|
const now = new Date().toISOString();
|
|
615
657
|
const resolvedLanguage = createBookPayload?.language ?? language ?? inferLanguage(instruction);
|
|
616
658
|
progress(`Starting architect for book "${id}"...`);
|
|
617
|
-
await pipeline.initBook({
|
|
659
|
+
await runPipelineWithAbortSignal(pipeline, _signal, () => pipeline.initBook({
|
|
618
660
|
id,
|
|
619
661
|
title: resolvedTitle,
|
|
620
662
|
genre: createBookPayload?.genre ?? genre ?? "general",
|
|
@@ -625,14 +667,14 @@ export function createSubAgentTool(pipeline, activeBookId, projectRoot, options
|
|
|
625
667
|
chapterWordCount: createBookPayload?.chapterWordCount ?? chapterWordCount ?? defaultChapterLength(resolvedLanguage),
|
|
626
668
|
createdAt: now,
|
|
627
669
|
updatedAt: now,
|
|
628
|
-
}, { externalContext: instruction });
|
|
670
|
+
}, { externalContext: instruction }));
|
|
629
671
|
progress(`Architect finished — book "${id}" foundation created.`);
|
|
630
672
|
return textResult(`Book "${resolvedTitle}" (${id}) initialised successfully. Foundation files are ready.`, { kind: "book_created", bookId: id, title: resolvedTitle });
|
|
631
673
|
}
|
|
632
674
|
case "writer": {
|
|
633
675
|
const targetBookId = resolveToolBookId("writer", bookId, activeBookId);
|
|
634
676
|
progress(`Writing next chapter for "${targetBookId}"...`);
|
|
635
|
-
const result = await pipeline.writeNextChapter(targetBookId, chapterWordCount);
|
|
677
|
+
const result = await runPipelineWithAbortSignal(pipeline, _signal, () => pipeline.writeNextChapter(targetBookId, chapterWordCount));
|
|
636
678
|
progress(`Writer finished chapter for "${targetBookId}".`);
|
|
637
679
|
const resultStatus = result.status;
|
|
638
680
|
const wordCount = result.wordCount ?? "unknown";
|
|
@@ -653,7 +695,7 @@ export function createSubAgentTool(pipeline, activeBookId, projectRoot, options
|
|
|
653
695
|
case "auditor": {
|
|
654
696
|
const targetBookId = resolveToolBookId("auditor", bookId, activeBookId);
|
|
655
697
|
progress(`Auditing chapter ${chapterNumber ?? "latest"} for "${targetBookId}"...`);
|
|
656
|
-
const audit = await pipeline.auditDraft(targetBookId, chapterNumber);
|
|
698
|
+
const audit = await runPipelineWithAbortSignal(pipeline, _signal, () => pipeline.auditDraft(targetBookId, chapterNumber));
|
|
657
699
|
progress(`Audit complete for "${targetBookId}".`);
|
|
658
700
|
const issueLines = (audit.issues ?? [])
|
|
659
701
|
.map((i) => `[${i.severity}] ${i.description}`)
|
|
@@ -665,7 +707,7 @@ export function createSubAgentTool(pipeline, activeBookId, projectRoot, options
|
|
|
665
707
|
const targetBookId = resolveToolBookId("reviser", bookId, activeBookId);
|
|
666
708
|
const resolvedMode = mode ?? "spot-fix";
|
|
667
709
|
progress(`Revising "${targetBookId}" chapter ${chapterNumber ?? "latest"} in ${resolvedMode} mode...`);
|
|
668
|
-
const result = await pipeline.reviseDraft(targetBookId, chapterNumber, resolvedMode, instruction);
|
|
710
|
+
const result = await runPipelineWithAbortSignal(pipeline, _signal, () => pipeline.reviseDraft(targetBookId, chapterNumber, resolvedMode, instruction));
|
|
669
711
|
const applied = result.applied !== false;
|
|
670
712
|
const resultChapter = result.chapterNumber ?? chapterNumber;
|
|
671
713
|
const details = {
|
|
@@ -952,6 +994,76 @@ export function createRetrieveMaterialTool(projectRoot) {
|
|
|
952
994
|
},
|
|
953
995
|
};
|
|
954
996
|
}
|
|
997
|
+
// ---------------------------------------------------------------------------
|
|
998
|
+
// 5. Chapter Import Tool (import_chapters)
|
|
999
|
+
// ---------------------------------------------------------------------------
|
|
1000
|
+
const ImportChaptersParams = Type.Object({
|
|
1001
|
+
bookId: Type.Optional(Type.String({
|
|
1002
|
+
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.",
|
|
1003
|
+
})),
|
|
1004
|
+
sourcePath: Type.String({
|
|
1005
|
+
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.",
|
|
1006
|
+
}),
|
|
1007
|
+
splitPattern: Type.Optional(Type.String({
|
|
1008
|
+
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.",
|
|
1009
|
+
})),
|
|
1010
|
+
resumeFrom: Type.Optional(Type.Number({
|
|
1011
|
+
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.",
|
|
1012
|
+
})),
|
|
1013
|
+
importMode: Type.Optional(Type.Union([
|
|
1014
|
+
Type.Literal("continuation"),
|
|
1015
|
+
Type.Literal("series"),
|
|
1016
|
+
], {
|
|
1017
|
+
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.",
|
|
1018
|
+
})),
|
|
1019
|
+
});
|
|
1020
|
+
export function createImportChaptersTool(pipeline, activeBookId, projectRoot) {
|
|
1021
|
+
return {
|
|
1022
|
+
name: "import_chapters",
|
|
1023
|
+
description: "Import an existing novel's chapters from a local file or directory into an InkOS book as real chapters (not reference material). " +
|
|
1024
|
+
"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. " +
|
|
1025
|
+
"Use ingest_material instead when the user only wants to archive reference material without touching book chapters.",
|
|
1026
|
+
label: "Import Chapters",
|
|
1027
|
+
parameters: ImportChaptersParams,
|
|
1028
|
+
async execute(_toolCallId, params, _signal, onUpdate) {
|
|
1029
|
+
const targetBookId = resolveToolBookId("import_chapters", params.bookId, activeBookId);
|
|
1030
|
+
const state = new StateManager(projectRoot);
|
|
1031
|
+
const existingChapterCount = (await state.getNextChapterNumber(targetBookId)) - 1;
|
|
1032
|
+
if (existingChapterCount > 0 && params.resumeFrom === undefined) {
|
|
1033
|
+
throw new Error(`Book "${targetBookId}" already has ${existingChapterCount} chapter(s). ` +
|
|
1034
|
+
`Pass resumeFrom=<n> to resume/append from chapter n, or ask the user to clear the existing chapters first.`);
|
|
1035
|
+
}
|
|
1036
|
+
const resolvedSourcePath = isAbsolute(params.sourcePath)
|
|
1037
|
+
? params.sourcePath
|
|
1038
|
+
: resolve(projectRoot, params.sourcePath);
|
|
1039
|
+
onUpdate?.(textResult(`Reading chapters from ${resolvedSourcePath}...`));
|
|
1040
|
+
const chapters = await loadChaptersFromPath(resolvedSourcePath, params.splitPattern);
|
|
1041
|
+
onUpdate?.(textResult(`Found ${chapters.length} chapter(s); importing into "${targetBookId}"...`));
|
|
1042
|
+
const result = await runPipelineWithAbortSignal(pipeline, _signal, () => pipeline.importChapters({
|
|
1043
|
+
bookId: targetBookId,
|
|
1044
|
+
chapters,
|
|
1045
|
+
resumeFrom: params.resumeFrom,
|
|
1046
|
+
importMode: params.importMode,
|
|
1047
|
+
}));
|
|
1048
|
+
const regeneratedFoundation = (params.resumeFrom ?? 1) === 1;
|
|
1049
|
+
return textResult([
|
|
1050
|
+
`Imported ${result.importedCount} chapter(s) into book "${result.bookId}".`,
|
|
1051
|
+
`Total imported length: ${result.totalWords}. Next chapter to write: ${result.nextChapter}.`,
|
|
1052
|
+
regeneratedFoundation
|
|
1053
|
+
? "Foundation and truth files were reverse-engineered from the imported text; chapter files and the chapter index were rebuilt by sequential replay."
|
|
1054
|
+
: `Resumed replay from chapter ${params.resumeFrom}; earlier chapters and the existing foundation were kept.`,
|
|
1055
|
+
`The book can now be continued with sub_agent(agent="writer") in the book session.`,
|
|
1056
|
+
].join("\n"), {
|
|
1057
|
+
kind: "chapters_imported",
|
|
1058
|
+
bookId: result.bookId,
|
|
1059
|
+
importedCount: result.importedCount,
|
|
1060
|
+
totalWords: result.totalWords,
|
|
1061
|
+
nextChapter: result.nextChapter,
|
|
1062
|
+
importMode: params.importMode ?? "continuation",
|
|
1063
|
+
});
|
|
1064
|
+
},
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
955
1067
|
function slugResearchTopic(topic) {
|
|
956
1068
|
const slug = topic
|
|
957
1069
|
.normalize("NFKC")
|
|
@@ -987,7 +1099,7 @@ const ShortFictionRunParams = Type.Object({
|
|
|
987
1099
|
description: "Target complete short chapter count, 12-18. Default 12.",
|
|
988
1100
|
})),
|
|
989
1101
|
charsPerChapter: Type.Optional(Type.Number({
|
|
990
|
-
description: "
|
|
1102
|
+
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. Do not use total story length here.",
|
|
991
1103
|
})),
|
|
992
1104
|
cover: Type.Optional(Type.Boolean({
|
|
993
1105
|
description: "Whether to attempt cover image generation after synopsis and cover prompt. Default true; use false if the user only wants text assets.",
|
|
@@ -1034,6 +1146,7 @@ export function createShortFictionRunTool(pipeline, projectRoot, options = {}) {
|
|
|
1034
1146
|
storyId: shortPayload?.storyId ?? params.storyId,
|
|
1035
1147
|
chapterCount: shortPayload?.chapters ?? params.chapters,
|
|
1036
1148
|
charsPerChapter: shortPayload?.charsPerChapter ?? params.charsPerChapter,
|
|
1149
|
+
language: options.language,
|
|
1037
1150
|
cover: shortPayload?.cover ?? params.cover,
|
|
1038
1151
|
coverBaseUrl: params.coverBaseUrl,
|
|
1039
1152
|
coverEndpoint: params.coverEndpoint,
|
|
@@ -1072,7 +1185,53 @@ function summarizeCoverGenerationError(error) {
|
|
|
1072
1185
|
return text.slice(0, 300);
|
|
1073
1186
|
}
|
|
1074
1187
|
// ---------------------------------------------------------------------------
|
|
1075
|
-
// 3.
|
|
1188
|
+
// 3. Translation tool
|
|
1189
|
+
// ---------------------------------------------------------------------------
|
|
1190
|
+
const TranslationCreateParams = Type.Object({
|
|
1191
|
+
filePath: Type.String({
|
|
1192
|
+
description: "Project-relative EPUB/PDF/TXT/Markdown source file path to translate.",
|
|
1193
|
+
}),
|
|
1194
|
+
sourceLanguage: Type.String({
|
|
1195
|
+
description: "Source language as a human-readable name, e.g. Auto detect, Japanese, English, Chinese (Simplified), 繁体中文(台湾). Do not require ISO abbreviations.",
|
|
1196
|
+
}),
|
|
1197
|
+
targetLanguage: Type.String({
|
|
1198
|
+
description: "Target language as a human-readable name, e.g. Chinese (Simplified), English, Japanese, Korean, Brazilian Portuguese. Do not require ISO abbreviations.",
|
|
1199
|
+
}),
|
|
1200
|
+
title: Type.Optional(Type.String({
|
|
1201
|
+
description: "Optional translation project title.",
|
|
1202
|
+
})),
|
|
1203
|
+
segmentMaxChars: Type.Optional(Type.Number({
|
|
1204
|
+
description: "Optional max chars per segment before splitting long paragraphs.",
|
|
1205
|
+
})),
|
|
1206
|
+
});
|
|
1207
|
+
export function createTranslationCreateTool(projectRoot, options = {}) {
|
|
1208
|
+
return {
|
|
1209
|
+
name: "translation_create",
|
|
1210
|
+
description: "Create an InkOS translation project from an EPUB/PDF/TXT/Markdown file. " +
|
|
1211
|
+
"This only ingests and segments the source; running the actual translation is a separate long task.",
|
|
1212
|
+
label: "Translation",
|
|
1213
|
+
parameters: TranslationCreateParams,
|
|
1214
|
+
async execute(_toolCallId, params) {
|
|
1215
|
+
const payload = options.actionPayload?.translationCreate;
|
|
1216
|
+
const result = await createTranslationProjectFromFile(projectRoot, {
|
|
1217
|
+
filePath: payload?.filePath ?? params.filePath,
|
|
1218
|
+
sourceLanguage: payload?.sourceLanguage ?? params.sourceLanguage,
|
|
1219
|
+
targetLanguage: payload?.targetLanguage ?? params.targetLanguage,
|
|
1220
|
+
title: payload?.title ?? params.title,
|
|
1221
|
+
segmentMaxChars: payload?.segmentMaxChars ?? params.segmentMaxChars,
|
|
1222
|
+
});
|
|
1223
|
+
return textResult([
|
|
1224
|
+
`Translation project "${result.manifest.title}" created.`,
|
|
1225
|
+
`ID: ${result.manifest.id}`,
|
|
1226
|
+
`Source: ${result.manifest.source.kind} ${result.manifest.sourceLanguage} -> ${result.manifest.targetLanguage}`,
|
|
1227
|
+
`Chapters: ${result.manifest.chapters.length}`,
|
|
1228
|
+
`Manifest: ${result.manifestPath}`,
|
|
1229
|
+
].join("\n"), { kind: "translation_project_created", ...result });
|
|
1230
|
+
},
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
// ---------------------------------------------------------------------------
|
|
1234
|
+
// 4. Script and Storyboard tools
|
|
1076
1235
|
// ---------------------------------------------------------------------------
|
|
1077
1236
|
const ScriptCreateParams = Type.Object({
|
|
1078
1237
|
title: Type.String({
|
|
@@ -1135,6 +1294,7 @@ export function createScriptCreationTool(pipeline, projectRoot, options = {}) {
|
|
|
1135
1294
|
requirements: payload?.requirements ?? params.requirements,
|
|
1136
1295
|
episodeCount: payload?.episodeCount ?? params.episodeCount,
|
|
1137
1296
|
episodeDuration: payload?.episodeDuration ?? params.episodeDuration,
|
|
1297
|
+
language: options.language,
|
|
1138
1298
|
projectId: payload?.projectId ?? params.projectId,
|
|
1139
1299
|
outDir: payload?.outDir ?? params.outDir,
|
|
1140
1300
|
onProgress: progress,
|
|
@@ -1208,6 +1368,7 @@ export function createStoryboardCreationTool(pipeline, projectRoot, options = {}
|
|
|
1208
1368
|
aspectRatio: payload?.aspectRatio ?? params.aspectRatio,
|
|
1209
1369
|
granularity: payload?.granularity ?? params.granularity,
|
|
1210
1370
|
maxShots: payload?.maxShots ?? params.maxShots,
|
|
1371
|
+
language: options.language,
|
|
1211
1372
|
projectId: payload?.projectId ?? params.projectId,
|
|
1212
1373
|
outDir: payload?.outDir ?? params.outDir,
|
|
1213
1374
|
onProgress: progress,
|
|
@@ -1287,6 +1448,7 @@ export function createInteractiveFilmCreationTool(pipeline, projectRoot, options
|
|
|
1287
1448
|
episodeDuration: payload?.episodeDuration ?? params.episodeDuration,
|
|
1288
1449
|
budget: payload?.budget ?? params.budget,
|
|
1289
1450
|
referenceMode: payload?.referenceMode ?? params.referenceMode,
|
|
1451
|
+
language: options.language,
|
|
1290
1452
|
projectId: payload?.projectId ?? params.projectId,
|
|
1291
1453
|
outDir: payload?.outDir ?? params.outDir,
|
|
1292
1454
|
onProgress: progress,
|
|
@@ -1588,7 +1750,7 @@ const PlayEditParams = Type.Object({
|
|
|
1588
1750
|
description: "Short human-readable note summarizing what changed.",
|
|
1589
1751
|
})),
|
|
1590
1752
|
});
|
|
1591
|
-
export function createPlayEditTool(projectRoot, sessionId) {
|
|
1753
|
+
export function createPlayEditTool(projectRoot, sessionId, language = "zh") {
|
|
1592
1754
|
return {
|
|
1593
1755
|
name: "play_edit",
|
|
1594
1756
|
description: "Persistently edit the active InkOS Play world card, visual contract, player persona, or entity/role cards without advancing time or narrating a turn. " +
|
|
@@ -1601,8 +1763,11 @@ export function createPlayEditTool(projectRoot, sessionId) {
|
|
|
1601
1763
|
const runId = "main";
|
|
1602
1764
|
const world = await store.loadWorld(worldId);
|
|
1603
1765
|
if (!world) {
|
|
1604
|
-
return textResult(
|
|
1766
|
+
return textResult(language === "en"
|
|
1767
|
+
? "There is no interactive world to edit yet. Start one with play_start first."
|
|
1768
|
+
: "还没有可编辑的互动世界。先用 play_start 开一局。");
|
|
1605
1769
|
}
|
|
1770
|
+
const isZh = (world.language ?? "zh") !== "en";
|
|
1606
1771
|
const patch = {};
|
|
1607
1772
|
const nextWorldContract = mergeContract(world.worldContract, params.worldContract, params.worldContractReplacements, params.worldContractAppend);
|
|
1608
1773
|
const nextVisualContract = mergeContract(world.visualContract, params.visualContract, params.visualContractReplacements, params.visualContractAppend);
|
|
@@ -1626,9 +1791,9 @@ export function createPlayEditTool(projectRoot, sessionId) {
|
|
|
1626
1791
|
upsertPlayEditEntity(db, {
|
|
1627
1792
|
id: "actor_player",
|
|
1628
1793
|
type: "actor",
|
|
1629
|
-
label: existingPlayer?.label ?? "玩家",
|
|
1794
|
+
label: existingPlayer?.label ?? (isZh ? "玩家" : "Player"),
|
|
1630
1795
|
summary: playerPersona,
|
|
1631
|
-
status: "已更新",
|
|
1796
|
+
status: isZh ? "已更新" : "Updated",
|
|
1632
1797
|
});
|
|
1633
1798
|
updatedEntities += 1;
|
|
1634
1799
|
}
|
|
@@ -1645,7 +1810,7 @@ export function createPlayEditTool(projectRoot, sessionId) {
|
|
|
1645
1810
|
premise: updatedWorld.premise,
|
|
1646
1811
|
graphEditedAt: new Date().toISOString(),
|
|
1647
1812
|
});
|
|
1648
|
-
return textResult(params.note?.trim() || "互动世界设定已更新。", {
|
|
1813
|
+
return textResult(params.note?.trim() || (isZh ? "互动世界设定已更新。" : "Interactive world settings updated."), {
|
|
1649
1814
|
kind: "play_world_updated",
|
|
1650
1815
|
worldId,
|
|
1651
1816
|
runId,
|
|
@@ -1680,7 +1845,9 @@ export function createPlayStepTool(pipeline, projectRoot, sessionId, options = {
|
|
|
1680
1845
|
const runId = "main";
|
|
1681
1846
|
const world = await store.loadWorld(worldId);
|
|
1682
1847
|
if (!world) {
|
|
1683
|
-
return textResult(
|
|
1848
|
+
return textResult(options.language === "en"
|
|
1849
|
+
? "There is no interactive world to advance yet. Start one with play_start first."
|
|
1850
|
+
: "还没有可推进的互动世界。先用 play_start 开一局。");
|
|
1684
1851
|
}
|
|
1685
1852
|
const target = { worldId, runId, world };
|
|
1686
1853
|
onUpdate?.(textResult(`Advancing "${target.worldId}" / "${target.runId}"...`));
|
|
@@ -1714,6 +1881,9 @@ export function createPlayStepTool(pipeline, projectRoot, sessionId, options = {
|
|
|
1714
1881
|
error: err instanceof Error ? err.message : String(err),
|
|
1715
1882
|
});
|
|
1716
1883
|
}
|
|
1884
|
+
finally {
|
|
1885
|
+
closePlayRunner(runner);
|
|
1886
|
+
}
|
|
1717
1887
|
const db = createPlayDB(store.runDir(target.worldId, target.runId));
|
|
1718
1888
|
let graph;
|
|
1719
1889
|
try {
|
|
@@ -1751,8 +1921,11 @@ export function createPlayReviseTool(pipeline, projectRoot, sessionId, options =
|
|
|
1751
1921
|
const runId = "main";
|
|
1752
1922
|
const world = await store.loadWorld(worldId);
|
|
1753
1923
|
if (!world) {
|
|
1754
|
-
return textResult(
|
|
1924
|
+
return textResult(options.language === "en"
|
|
1925
|
+
? "There is no interactive world to redo yet. Start one with play_start first."
|
|
1926
|
+
: "还没有可重做的互动世界。先用 play_start 开一局。");
|
|
1755
1927
|
}
|
|
1928
|
+
const isZh = (world.language ?? "zh") !== "en";
|
|
1756
1929
|
const ctx = pipeline.createAgentContext("play");
|
|
1757
1930
|
const runner = options.runnerFactory?.({ projectRoot, worldId, runId, ctx }) ?? new PlayRunner({
|
|
1758
1931
|
projectRoot,
|
|
@@ -1760,46 +1933,55 @@ export function createPlayReviseTool(pipeline, projectRoot, sessionId, options =
|
|
|
1760
1933
|
runId,
|
|
1761
1934
|
ctx,
|
|
1762
1935
|
});
|
|
1763
|
-
if (params.action === "restore_variant") {
|
|
1764
|
-
const turn = params.turn;
|
|
1765
|
-
const variantId = params.variantId?.trim();
|
|
1766
|
-
if (typeof turn !== "number" || !Number.isFinite(turn) || !variantId) {
|
|
1767
|
-
return textResult("恢复版本需要 turn 和 variantId。");
|
|
1768
|
-
}
|
|
1769
|
-
onUpdate?.(textResult(`Restoring play variant "${variantId}"...`));
|
|
1770
|
-
const restored = await runner.restoreVariant({
|
|
1771
|
-
turn: Math.trunc(turn),
|
|
1772
|
-
variantId,
|
|
1773
|
-
});
|
|
1774
|
-
return textResult(restored.sceneText || "已切换到指定互动回合版本。", {
|
|
1775
|
-
kind: "play_variant_restored",
|
|
1776
|
-
worldId,
|
|
1777
|
-
runId,
|
|
1778
|
-
title: world.title,
|
|
1779
|
-
turn: restored.turn,
|
|
1780
|
-
variantId: restored.variantId,
|
|
1781
|
-
sceneText: restored.sceneText,
|
|
1782
|
-
});
|
|
1783
|
-
}
|
|
1784
|
-
const replacement = params.action === "edit_last_input" ? params.input?.trim() : undefined;
|
|
1785
|
-
if (params.action === "edit_last_input" && !replacement) {
|
|
1786
|
-
return textResult("编辑上一条玩家动作需要提供新的 input。");
|
|
1787
|
-
}
|
|
1788
|
-
onUpdate?.(textResult(params.action === "edit_last_input" ? "Replaying edited play turn..." : "Regenerating last play turn..."));
|
|
1789
1936
|
let replay;
|
|
1937
|
+
// finally 关闭 runner 自建的 play.db 连接:句柄不关闭时 Windows 上无法删除数据库文件。
|
|
1790
1938
|
try {
|
|
1791
|
-
|
|
1939
|
+
if (params.action === "restore_variant") {
|
|
1940
|
+
const turn = params.turn;
|
|
1941
|
+
const variantId = params.variantId?.trim();
|
|
1942
|
+
if (typeof turn !== "number" || !Number.isFinite(turn) || !variantId) {
|
|
1943
|
+
return textResult(isZh
|
|
1944
|
+
? "恢复版本需要 turn 和 variantId。"
|
|
1945
|
+
: "Restoring a variant requires both turn and variantId.");
|
|
1946
|
+
}
|
|
1947
|
+
onUpdate?.(textResult(`Restoring play variant "${variantId}"...`));
|
|
1948
|
+
const restored = await runner.restoreVariant({
|
|
1949
|
+
turn: Math.trunc(turn),
|
|
1950
|
+
variantId,
|
|
1951
|
+
});
|
|
1952
|
+
return textResult(restored.sceneText || (isZh ? "已切换到指定互动回合版本。" : "Switched to the requested play turn variant."), {
|
|
1953
|
+
kind: "play_variant_restored",
|
|
1954
|
+
worldId,
|
|
1955
|
+
runId,
|
|
1956
|
+
title: world.title,
|
|
1957
|
+
turn: restored.turn,
|
|
1958
|
+
variantId: restored.variantId,
|
|
1959
|
+
sceneText: restored.sceneText,
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
const replacement = params.action === "edit_last_input" ? params.input?.trim() : undefined;
|
|
1963
|
+
if (params.action === "edit_last_input" && !replacement) {
|
|
1964
|
+
return textResult(isZh
|
|
1965
|
+
? "编辑上一条玩家动作需要提供新的 input。"
|
|
1966
|
+
: "Editing the previous player action requires a new input.");
|
|
1967
|
+
}
|
|
1968
|
+
onUpdate?.(textResult(params.action === "edit_last_input" ? "Replaying edited play turn..." : "Regenerating last play turn..."));
|
|
1969
|
+
try {
|
|
1970
|
+
replay = await runner.regenerateLastTurn(replacement);
|
|
1971
|
+
}
|
|
1972
|
+
catch (err) {
|
|
1973
|
+
return textResult(isZh
|
|
1974
|
+
? "(上一回合暂时不能安全重做。继续输入新的动作,我会从当前状态推进。)"
|
|
1975
|
+
: "(The previous turn cannot be safely regenerated yet. Enter a new action and I will continue from the current state.)", {
|
|
1976
|
+
kind: "play_revise_failed",
|
|
1977
|
+
worldId,
|
|
1978
|
+
runId,
|
|
1979
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1792
1982
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
return textResult(isZh
|
|
1796
|
-
? "(上一回合暂时不能安全重做。继续输入新的动作,我会从当前状态推进。)"
|
|
1797
|
-
: "(The previous turn cannot be safely regenerated yet. Enter a new action and I will continue from the current state.)", {
|
|
1798
|
-
kind: "play_revise_failed",
|
|
1799
|
-
worldId,
|
|
1800
|
-
runId,
|
|
1801
|
-
error: err instanceof Error ? err.message : String(err),
|
|
1802
|
-
});
|
|
1983
|
+
finally {
|
|
1984
|
+
closePlayRunner(runner);
|
|
1803
1985
|
}
|
|
1804
1986
|
const db = createPlayDB(store.runDir(worldId, runId));
|
|
1805
1987
|
let graph;
|