dayloom 0.1.0-beta.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.
Files changed (123) hide show
  1. package/README.md +1146 -0
  2. package/dist/cli/daily.js +41 -0
  3. package/dist/cli/index.js +33 -0
  4. package/dist/cli/init.js +40 -0
  5. package/dist/cli/next.js +60 -0
  6. package/dist/cli/play.js +39 -0
  7. package/dist/cli/revise.js +41 -0
  8. package/dist/cli/settle.js +45 -0
  9. package/dist/daily/apply-plan.js +25 -0
  10. package/dist/daily/constants.js +16 -0
  11. package/dist/daily/dialogue-loop.js +147 -0
  12. package/dist/daily/finalize.js +23 -0
  13. package/dist/daily/guard.js +54 -0
  14. package/dist/daily/index.js +27 -0
  15. package/dist/daily/intent-router.js +65 -0
  16. package/dist/daily/mcp-gateway.js +5 -0
  17. package/dist/daily/mcp-tools.js +8 -0
  18. package/dist/daily/parse-assistant.js +38 -0
  19. package/dist/daily/parse-payload.js +10 -0
  20. package/dist/daily/player-context.js +85 -0
  21. package/dist/daily/project-plan.js +15 -0
  22. package/dist/daily/promptpile-loop.js +41 -0
  23. package/dist/daily/prompts.js +11 -0
  24. package/dist/daily/read-user-input.js +6 -0
  25. package/dist/daily/session.js +119 -0
  26. package/dist/daily/types.js +2 -0
  27. package/dist/daily/validate-plan.js +46 -0
  28. package/dist/i18n/detect.js +23 -0
  29. package/dist/i18n/index.js +22 -0
  30. package/dist/i18n/messages.js +149 -0
  31. package/dist/index.js +27 -0
  32. package/dist/init/apply-payload.js +18 -0
  33. package/dist/init/archive-transcript.js +28 -0
  34. package/dist/init/checklist.js +74 -0
  35. package/dist/init/cleanup.js +12 -0
  36. package/dist/init/constants.js +21 -0
  37. package/dist/init/errors.js +11 -0
  38. package/dist/init/finalize.js +35 -0
  39. package/dist/init/guard.js +31 -0
  40. package/dist/init/index.js +59 -0
  41. package/dist/init/interview-loop.js +103 -0
  42. package/dist/init/parse-assistant.js +50 -0
  43. package/dist/init/project-payload.js +78 -0
  44. package/dist/init/promptpile-run.js +80 -0
  45. package/dist/init/prompts.js +16 -0
  46. package/dist/init/read-user-input.js +44 -0
  47. package/dist/init/scaffold.js +66 -0
  48. package/dist/init/session.js +98 -0
  49. package/dist/init/types.js +2 -0
  50. package/dist/next/index.js +79 -0
  51. package/dist/next/inspect.js +90 -0
  52. package/dist/play/ai.js +11 -0
  53. package/dist/play/event-loop.js +244 -0
  54. package/dist/play/guard.js +14 -0
  55. package/dist/play/index.js +21 -0
  56. package/dist/play/parse-assistant.js +39 -0
  57. package/dist/play/player-context.js +20 -0
  58. package/dist/play/prompts.js +9 -0
  59. package/dist/play/session.js +14 -0
  60. package/dist/play/state.js +117 -0
  61. package/dist/play/types.js +2 -0
  62. package/dist/play/validate.js +156 -0
  63. package/dist/revise/apply-payload.js +58 -0
  64. package/dist/revise/bin-resolve.js +38 -0
  65. package/dist/revise/constants.js +17 -0
  66. package/dist/revise/dialogue-loop.js +116 -0
  67. package/dist/revise/diff.js +24 -0
  68. package/dist/revise/file-hash.js +27 -0
  69. package/dist/revise/finalize.js +23 -0
  70. package/dist/revise/guard.js +17 -0
  71. package/dist/revise/index.js +24 -0
  72. package/dist/revise/mcp-gateway.js +74 -0
  73. package/dist/revise/mcp-tools.js +91 -0
  74. package/dist/revise/parse-assistant.js +41 -0
  75. package/dist/revise/parse-payload.js +22 -0
  76. package/dist/revise/process-run.js +77 -0
  77. package/dist/revise/project-payload.js +62 -0
  78. package/dist/revise/promptpile-loop.js +41 -0
  79. package/dist/revise/prompts.js +16 -0
  80. package/dist/revise/read-user-input.js +35 -0
  81. package/dist/revise/session.js +119 -0
  82. package/dist/revise/types.js +2 -0
  83. package/dist/revise/validate-payload.js +47 -0
  84. package/dist/settle/ai.js +23 -0
  85. package/dist/settle/apply.js +58 -0
  86. package/dist/settle/context.js +69 -0
  87. package/dist/settle/derive.js +56 -0
  88. package/dist/settle/guard.js +71 -0
  89. package/dist/settle/index.js +105 -0
  90. package/dist/settle/parse-assistant.js +14 -0
  91. package/dist/settle/parse-payload.js +19 -0
  92. package/dist/settle/project.js +58 -0
  93. package/dist/settle/session.js +45 -0
  94. package/dist/settle/types.js +2 -0
  95. package/dist/settle/validate.js +100 -0
  96. package/dist/shared/filtered-stream-output.js +41 -0
  97. package/dist/shared/promptpile-stream.js +59 -0
  98. package/dist/shared/run-promptpile-with-stream.js +34 -0
  99. package/dist/utils/loading.js +54 -0
  100. package/package.json +39 -0
  101. package/prompts/README.md +39 -0
  102. package/prompts/choice.system.md +0 -0
  103. package/prompts/daily-dialogue.system.md +37 -0
  104. package/prompts/daily-finalize-plan.system.md +34 -0
  105. package/prompts/daily-intent-router.system.md +34 -0
  106. package/prompts/day-planner.system.md +0 -0
  107. package/prompts/day-summarizer.system.md +0 -0
  108. package/prompts/dialogue.system.md +0 -0
  109. package/prompts/diary-writer.system.md +0 -0
  110. package/prompts/event-runner.system.md +0 -0
  111. package/prompts/init-finalize.system.md +59 -0
  112. package/prompts/init-interviewer.system.md +37 -0
  113. package/prompts/memory-updater.system.md +0 -0
  114. package/prompts/next-day-seeder.system.md +0 -0
  115. package/prompts/play-event-dialogue.system.md +20 -0
  116. package/prompts/play-event-generator.system.md +19 -0
  117. package/prompts/play-event-resolver.system.md +26 -0
  118. package/prompts/play-replanner.system.md +21 -0
  119. package/prompts/revise-dialogue.system.md +22 -0
  120. package/prompts/revise-finalize.system.md +40 -0
  121. package/prompts/settle.system.md +28 -0
  122. package/prompts/spec.md +320 -0
  123. package/prompts/state-resolver.system.md +0 -0
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.messages = void 0;
4
+ exports.messages = {
5
+ en: {
6
+ 'cli.description': 'dayloom: file-based AI life simulation by day',
7
+ 'cli.help': 'Show help',
8
+ 'cli.helpCommand': 'Display help for command',
9
+ 'cli.lang': 'Interface language: en or zh',
10
+ 'cli.version': 'Output the version number',
11
+ 'cli.error': 'Error:',
12
+ 'cli.positiveInteger': 'Expected a positive integer',
13
+ 'cli.init.description': 'Initialize a new World save directory (see prompts/spec.md)',
14
+ 'cli.init.quick': 'Empty scaffold only, no AI interview',
15
+ 'cli.init.id': 'World id (default: directory basename or payload id)',
16
+ 'cli.init.title': 'World title',
17
+ 'cli.init.maxRounds': 'Maximum interview rounds',
18
+ 'cli.init.keepSession': 'On failure, preserve temp interview session path',
19
+ 'cli.next.description': 'Inspect a World save and run the next appropriate dayloom phase',
20
+ 'cli.next.status': 'Only show current state and recommended next command',
21
+ 'cli.next.confirm': 'Ask before running the next action',
22
+ 'cli.next.quick': 'When uninitialized, scaffold an empty World without AI interview',
23
+ 'cli.next.id': 'World id for init',
24
+ 'cli.next.title': 'World title for init',
25
+ 'cli.next.maxRounds': 'Maximum init interview rounds',
26
+ 'cli.next.dryRun': 'Pass dry-run mode to daily or settle',
27
+ 'cli.next.yes': 'Apply generated daily or settlement changes without prompting where supported',
28
+ 'cli.next.keepSession': 'Preserve temporary AI and MCP sessions where supported',
29
+ 'cli.next.maxToolRounds': 'Maximum MCP tool rounds per AI call',
30
+ 'cli.next.maxEventRounds': 'Maximum user turns in one play event',
31
+ 'cli.daily.description': 'Plan the current day from the protagonist perspective',
32
+ 'cli.daily.proposal': 'Apply a daily plan JSON file instead of starting AI chat',
33
+ 'cli.daily.dryRun': 'Show projected file changes without writing',
34
+ 'cli.daily.yes': 'Apply the validated daily plan without prompting',
35
+ 'cli.daily.keepSession': 'Preserve temporary AI daily sessions',
36
+ 'cli.daily.maxToolRounds': 'Maximum MCP tool rounds per user message',
37
+ 'cli.daily.applied': 'Applied daily plan.',
38
+ 'cli.play.description': 'Execute the planned day one event at a time',
39
+ 'cli.play.keepSession': 'Preserve temporary MCP service session',
40
+ 'cli.play.maxToolRounds': 'Maximum MCP tool rounds per AI call',
41
+ 'cli.play.maxEventRounds': 'Maximum user turns in one event',
42
+ 'cli.settle.description': 'Settle the completed day and advance to the next idle day',
43
+ 'cli.settle.proposal': 'Apply an existing settlement proposal JSON file',
44
+ 'cli.settle.dryRun': 'Show projected file changes without committing the settlement',
45
+ 'cli.settle.yes': 'Apply the validated settlement without stopping at a generated draft',
46
+ 'cli.settle.keepSession': 'Preserve temporary AI and MCP sessions',
47
+ 'cli.settle.maxToolRounds': 'Maximum MCP tool rounds for the AI call',
48
+ 'cli.settle.settled': 'Settled {day}; advanced to {nextDay}.',
49
+ 'cli.settle.generatedProposal': 'Generated settlement proposal: {proposalPath}',
50
+ 'cli.settle.reviewProposal': 'Review it, then rerun with --proposal and --yes.',
51
+ 'cli.revise.description': 'Discuss and apply controlled revisions to an initialized World save',
52
+ 'cli.revise.proposal': 'Apply a revision proposal JSON file instead of starting AI chat',
53
+ 'cli.revise.dryRun': 'Show projected file changes without writing',
54
+ 'cli.revise.yes': 'Apply the validated revision without prompting',
55
+ 'cli.revise.keepSession': 'Preserve temporary AI revise sessions',
56
+ 'cli.revise.maxToolRounds': 'Maximum MCP tool rounds per user message',
57
+ 'cli.revise.applied': 'Applied World revision: {revisionId}',
58
+ 'cli.common.dir': 'World save root directory',
59
+ 'cli.common.mcpBaseUrl': 'Use an existing promptpile-mcp gateway',
60
+ 'cli.common.mcpToken': 'Bearer token for an existing promptpile-mcp gateway',
61
+ 'cli.common.dryRunOnly': 'Dry run only. No files changed.',
62
+ 'next.world': 'World: {worldRoot}',
63
+ 'next.currentUninitialized': 'Current: uninitialized',
64
+ 'next.currentPhase': 'Current: {day} / phase={phase}',
65
+ 'next.lastCommittedDay': 'Last committed day: {lastCommittedDay}',
66
+ 'next.nextAction': 'Next action: {action}',
67
+ 'next.recommendedCommand': 'Recommended command:',
68
+ 'next.actionInit': 'This will create a new World save.',
69
+ 'next.actionDaily': "This will start today's planning session.",
70
+ 'next.actionPlayStart': "This will start today's play session.",
71
+ 'next.actionPlayContinue': 'This will continue the current play session.',
72
+ 'next.actionSettle': 'This will settle the completed day and advance to the next idle day.',
73
+ 'next.proceed': 'Proceed with {action}? (Y/N): ',
74
+ 'next.cancelled': 'Cancelled.',
75
+ 'next.initialized': 'Initialized World save: {worldRoot}',
76
+ },
77
+ zh: {
78
+ 'cli.description': 'dayloom:按天推进的文件式 AI 生活模拟引擎',
79
+ 'cli.help': '显示帮助',
80
+ 'cli.helpCommand': '显示指定命令的帮助',
81
+ 'cli.lang': '界面语言:en 或 zh',
82
+ 'cli.version': '输出版本号',
83
+ 'cli.error': '错误:',
84
+ 'cli.positiveInteger': '需要正整数',
85
+ 'cli.init.description': '初始化新的 World 存档目录(见 prompts/spec.md)',
86
+ 'cli.init.quick': '只创建空存档骨架,不进行 AI 访谈',
87
+ 'cli.init.id': 'World id(默认使用目录名或 payload id)',
88
+ 'cli.init.title': 'World 标题',
89
+ 'cli.init.maxRounds': '最大访谈轮数',
90
+ 'cli.init.keepSession': '失败时保留临时访谈会话路径',
91
+ 'cli.next.description': '检查 World 存档状态,并执行合适的下一个 dayloom 阶段',
92
+ 'cli.next.status': '只显示当前状态和推荐的下一条命令',
93
+ 'cli.next.confirm': '执行下一步前先询问确认',
94
+ 'cli.next.quick': '未初始化时只创建空 World,不进行 AI 访谈',
95
+ 'cli.next.id': '初始化时使用的 World id',
96
+ 'cli.next.title': '初始化时使用的 World 标题',
97
+ 'cli.next.maxRounds': '初始化访谈最大轮数',
98
+ 'cli.next.dryRun': '将 dry-run 模式传给 daily 或 settle',
99
+ 'cli.next.yes': '在支持的阶段中跳过确认并应用生成结果',
100
+ 'cli.next.keepSession': '在支持的阶段中保留临时 AI 和 MCP 会话',
101
+ 'cli.next.maxToolRounds': '每次 AI 调用的最大 MCP 工具轮数',
102
+ 'cli.next.maxEventRounds': '单个 play 事件中的最大用户轮数',
103
+ 'cli.daily.description': '从主角视角规划当前这一天',
104
+ 'cli.daily.proposal': '应用 daily plan JSON 文件,而不是启动 AI 对话',
105
+ 'cli.daily.dryRun': '只显示预计文件改动,不写入',
106
+ 'cli.daily.yes': '跳过确认并应用验证后的 daily plan',
107
+ 'cli.daily.keepSession': '保留临时 AI daily 会话',
108
+ 'cli.daily.maxToolRounds': '每条用户消息的最大 MCP 工具轮数',
109
+ 'cli.daily.applied': '已应用当天计划。',
110
+ 'cli.play.description': '按事件逐个执行已规划的一天',
111
+ 'cli.play.keepSession': '保留临时 MCP 服务会话',
112
+ 'cli.play.maxToolRounds': '每次 AI 调用的最大 MCP 工具轮数',
113
+ 'cli.play.maxEventRounds': '单个事件中的最大用户轮数',
114
+ 'cli.settle.description': '结算已完成的一天,并推进到下一天的 idle 状态',
115
+ 'cli.settle.proposal': '应用已有 settlement proposal JSON 文件',
116
+ 'cli.settle.dryRun': '只显示预计结算改动,不提交结算',
117
+ 'cli.settle.yes': '跳过生成草稿后的停顿并应用验证后的结算',
118
+ 'cli.settle.keepSession': '保留临时 AI 和 MCP 会话',
119
+ 'cli.settle.maxToolRounds': 'AI 调用的最大 MCP 工具轮数',
120
+ 'cli.settle.settled': '已结算 {day};已推进到 {nextDay}。',
121
+ 'cli.settle.generatedProposal': '已生成结算提案:{proposalPath}',
122
+ 'cli.settle.reviewProposal': '请检查后使用 --proposal 和 --yes 重新运行。',
123
+ 'cli.revise.description': '通过对话检查并应用受控的 World 设定修订',
124
+ 'cli.revise.proposal': '应用 revision proposal JSON 文件,而不是启动 AI 对话',
125
+ 'cli.revise.dryRun': '只显示预计文件改动,不写入',
126
+ 'cli.revise.yes': '跳过确认并应用验证后的 revision',
127
+ 'cli.revise.keepSession': '保留临时 AI revise 会话',
128
+ 'cli.revise.maxToolRounds': '每条用户消息的最大 MCP 工具轮数',
129
+ 'cli.revise.applied': '已应用 World 修订:{revisionId}',
130
+ 'cli.common.dir': 'World 存档根目录',
131
+ 'cli.common.mcpBaseUrl': '使用已有 promptpile-mcp gateway',
132
+ 'cli.common.mcpToken': '已有 promptpile-mcp gateway 的 Bearer token',
133
+ 'cli.common.dryRunOnly': '仅 dry run,未改动文件。',
134
+ 'next.world': 'World:{worldRoot}',
135
+ 'next.currentUninitialized': '当前状态:未初始化',
136
+ 'next.currentPhase': '当前状态:{day} / phase={phase}',
137
+ 'next.lastCommittedDay': '上次提交日:{lastCommittedDay}',
138
+ 'next.nextAction': '下一步操作:{action}',
139
+ 'next.recommendedCommand': '推荐命令:',
140
+ 'next.actionInit': '这将创建一个新的 World 存档。',
141
+ 'next.actionDaily': '这将开始当天计划会话。',
142
+ 'next.actionPlayStart': '这将开始当天 play 会话。',
143
+ 'next.actionPlayContinue': '这将继续当前 play 会话。',
144
+ 'next.actionSettle': '这将结算已完成的一天,并推进到下一天的 idle 状态。',
145
+ 'next.proceed': '是否执行 {action}?(Y/N):',
146
+ 'next.cancelled': '已取消。',
147
+ 'next.initialized': '已初始化 World 存档:{worldRoot}',
148
+ },
149
+ };
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ const cli_1 = require("./cli");
19
+ try {
20
+ (0, cli_1.parseCli)();
21
+ }
22
+ catch (e) {
23
+ console.error('Error:', e instanceof Error ? e.message : e);
24
+ process.exitCode = 1;
25
+ }
26
+ __exportStar(require("./play"), exports);
27
+ __exportStar(require("./settle"), exports);
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.applyPayload = applyPayload;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const project_payload_1 = require("./project-payload");
10
+ function writeFileForce(filePath, content) {
11
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
12
+ fs_1.default.writeFileSync(filePath, content, 'utf8');
13
+ }
14
+ function applyPayload(worldRoot, payload) {
15
+ for (const file of (0, project_payload_1.projectPayload)(payload)) {
16
+ writeFileForce(path_1.default.join(worldRoot, file.relativePath), file.content);
17
+ }
18
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.archiveTranscript = archiveTranscript;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function copyDir(src, dest) {
10
+ fs_1.default.mkdirSync(dest, { recursive: true });
11
+ for (const entry of fs_1.default.readdirSync(src, { withFileTypes: true })) {
12
+ const srcPath = path_1.default.join(src, entry.name);
13
+ const destPath = path_1.default.join(dest, entry.name);
14
+ if (entry.isDirectory()) {
15
+ copyDir(srcPath, destPath);
16
+ }
17
+ else {
18
+ fs_1.default.copyFileSync(srcPath, destPath);
19
+ }
20
+ }
21
+ }
22
+ function archiveTranscript(session, worldRoot) {
23
+ const dest = path_1.default.join(worldRoot, '.loom', 'init-transcript');
24
+ if (fs_1.default.existsSync(dest)) {
25
+ fs_1.default.rmSync(dest, { recursive: true, force: true });
26
+ }
27
+ copyDir(session.root, dest);
28
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getInterviewMissingFromTranscript = getInterviewMissingFromTranscript;
4
+ exports.isPayloadComplete = isPayloadComplete;
5
+ exports.isInterviewReady = isInterviewReady;
6
+ const SNAKE_CASE = /^[a-z][a-z0-9_]*$/;
7
+ function nonEmpty(s) {
8
+ return typeof s === 'string' && s.trim().length > 0;
9
+ }
10
+ function getInterviewMissingFromTranscript(transcript) {
11
+ const missing = [];
12
+ const lower = transcript.toLowerCase();
13
+ if (transcript.trim().length < 80) {
14
+ missing.push('premise');
15
+ }
16
+ if (!/规则|边界|constraint|rule/i.test(transcript)) {
17
+ missing.push('rules');
18
+ }
19
+ if (!/风格|文风|style|tone/i.test(transcript)) {
20
+ missing.push('style');
21
+ }
22
+ if (!/主角|你扮演|user|protagonist|我/i.test(transcript)) {
23
+ missing.push('user_role');
24
+ }
25
+ if (!/npc|人物|角色|同伴|朋友|同事|char_/i.test(lower)) {
26
+ missing.push('npc');
27
+ }
28
+ return missing;
29
+ }
30
+ function isPayloadComplete(payload) {
31
+ const missing = [];
32
+ if (!nonEmpty(payload.manifest?.id)) {
33
+ missing.push('manifest.id');
34
+ }
35
+ if (!nonEmpty(payload.manifest?.title)) {
36
+ missing.push('manifest.title');
37
+ }
38
+ if (!nonEmpty(payload.canon?.['premise.md'])) {
39
+ missing.push('canon.premise');
40
+ }
41
+ if (!nonEmpty(payload.canon?.['rules.md'])) {
42
+ missing.push('canon.rules');
43
+ }
44
+ if (!nonEmpty(payload.canon?.['style.md'])) {
45
+ missing.push('canon.style');
46
+ }
47
+ if (!nonEmpty(payload.canon?.['user_role.md'])) {
48
+ missing.push('canon.user_role');
49
+ }
50
+ if (!nonEmpty(payload.state?.['world.yaml'])) {
51
+ missing.push('state.world');
52
+ }
53
+ if (!payload.characters?.length) {
54
+ missing.push('characters.min_one');
55
+ }
56
+ else {
57
+ for (const c of payload.characters) {
58
+ if (!SNAKE_CASE.test(c.id)) {
59
+ missing.push(`characters.invalid_id:${c.id}`);
60
+ }
61
+ if (!nonEmpty(c.profileMd)) {
62
+ missing.push(`characters.profile:${c.id}`);
63
+ }
64
+ }
65
+ }
66
+ return missing;
67
+ }
68
+ function isInterviewReady(status, transcript) {
69
+ if (status.status !== 'ready') {
70
+ return false;
71
+ }
72
+ const transcriptMissing = getInterviewMissingFromTranscript(transcript);
73
+ return status.missing.length === 0 && transcriptMissing.length === 0;
74
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.cleanupSession = cleanupSession;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ function cleanupSession(session) {
9
+ if (fs_1.default.existsSync(session.root)) {
10
+ fs_1.default.rmSync(session.root, { recursive: true, force: true });
11
+ }
12
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROMPTPILE_TOML = exports.FINALIZE_USER_PROMPT = exports.OPENING_ASSISTANT = exports.DEFAULT_MAX_INTERVIEW_ROUNDS = exports.PROTOCOL_VERSION = void 0;
4
+ exports.PROTOCOL_VERSION = '0.0.0';
5
+ exports.DEFAULT_MAX_INTERVIEW_ROUNDS = 12;
6
+ exports.OPENING_ASSISTANT = `你好,我是 dayloom 的世界构建助手。接下来我会通过几轮简短提问,帮你整理一份可玩的 World 存档设定。
7
+
8
+ 先从整体开始:你想体验什么样的世界?(例如题材、时代背景、基调)以及你希望扮演什么样的主角?`;
9
+ exports.FINALIZE_USER_PROMPT = '请根据以上完整对话,生成 init-payload JSON(严格遵守 init-finalize 系统提示中的 schema)。';
10
+ exports.PROMPTPILE_TOML = `[[llm_api]]
11
+ name = "deepseek"
12
+ model = "deepseek-chat"
13
+ base_url = "https://api.deepseek.com/v1"
14
+ api_key_env = "DEEPSEEK_API_KEY"
15
+
16
+ [promptpile]
17
+ llm_api = "deepseek"
18
+ dir = "./messages"
19
+ disable_tool = true
20
+ quiet = false
21
+ `;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InitCancelledError = void 0;
4
+ class InitCancelledError extends Error {
5
+ constructor(message = 'Init cancelled by user.', session) {
6
+ super(message);
7
+ this.name = 'InitCancelledError';
8
+ this.session = session;
9
+ }
10
+ }
11
+ exports.InitCancelledError = InitCancelledError;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.finalizeWorld = finalizeWorld;
4
+ const checklist_1 = require("./checklist");
5
+ const parse_assistant_1 = require("./parse-assistant");
6
+ const promptpile_run_1 = require("./promptpile-run");
7
+ const session_1 = require("./session");
8
+ const cleanup_1 = require("./cleanup");
9
+ const constants_1 = require("./constants");
10
+ const loading_1 = require("../utils/loading");
11
+ async function finalizeWorld(transcript) {
12
+ const session = (0, session_1.createFinalizeSession)(transcript);
13
+ (0, session_1.appendUserMessage)(session.messagesDir, constants_1.FINALIZE_USER_PROMPT);
14
+ try {
15
+ const result = await (0, loading_1.withLoading)('正在生成世界文件...', () => (0, promptpile_run_1.runPromptpile)(session, [
16
+ '--config',
17
+ 'promptpile.toml',
18
+ '-d',
19
+ 'messages',
20
+ '--continue',
21
+ '--disable-tool',
22
+ ]));
23
+ (0, promptpile_run_1.assertPromptpileOk)(result, 'Finalize');
24
+ const assistantText = result.stdout.trim() || (0, session_1.getLatestAssistantText)(session.messagesDir);
25
+ const payload = (0, parse_assistant_1.parseInitPayload)(assistantText);
26
+ const missing = (0, checklist_1.isPayloadComplete)(payload);
27
+ if (missing.length > 0) {
28
+ throw new Error(`Init payload incomplete: ${missing.join(', ')}. Re-run init or edit prompts.`);
29
+ }
30
+ return payload;
31
+ }
32
+ finally {
33
+ (0, cleanup_1.cleanupSession)(session);
34
+ }
35
+ }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveWorldRoot = resolveWorldRoot;
7
+ exports.isInitialized = isInitialized;
8
+ exports.assertNotInitialized = assertNotInitialized;
9
+ exports.assertApiKey = assertApiKey;
10
+ exports.ensureWorldRootParent = ensureWorldRootParent;
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const path_1 = __importDefault(require("path"));
13
+ function resolveWorldRoot(dir) {
14
+ return path_1.default.resolve(dir);
15
+ }
16
+ function isInitialized(worldRoot) {
17
+ return fs_1.default.existsSync(path_1.default.join(worldRoot, 'manifest.yaml'));
18
+ }
19
+ function assertNotInitialized(worldRoot) {
20
+ if (isInitialized(worldRoot)) {
21
+ throw new Error(`World save already initialized: ${worldRoot} (manifest.yaml exists)`);
22
+ }
23
+ }
24
+ function assertApiKey() {
25
+ if (!process.env.DEEPSEEK_API_KEY?.trim()) {
26
+ throw new Error('DEEPSEEK_API_KEY is not set. Interactive init requires an API key.');
27
+ }
28
+ }
29
+ function ensureWorldRootParent(worldRoot) {
30
+ fs_1.default.mkdirSync(path_1.default.dirname(worldRoot), { recursive: true });
31
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.initWorldQuick = initWorldQuick;
7
+ exports.initWorldInteractive = initWorldInteractive;
8
+ const path_1 = __importDefault(require("path"));
9
+ const archive_transcript_1 = require("./archive-transcript");
10
+ const apply_payload_1 = require("./apply-payload");
11
+ const cleanup_1 = require("./cleanup");
12
+ const constants_1 = require("./constants");
13
+ const errors_1 = require("./errors");
14
+ const finalize_1 = require("./finalize");
15
+ const guard_1 = require("./guard");
16
+ const interview_loop_1 = require("./interview-loop");
17
+ const scaffold_1 = require("./scaffold");
18
+ function initWorldQuick(dir, options = {}) {
19
+ const worldRoot = (0, guard_1.resolveWorldRoot)(dir);
20
+ (0, guard_1.assertNotInitialized)(worldRoot);
21
+ (0, guard_1.ensureWorldRootParent)(worldRoot);
22
+ const id = options.id ?? path_1.default.basename(worldRoot);
23
+ const title = options.title ?? id;
24
+ (0, scaffold_1.scaffoldEmptyWorld)(worldRoot, { id, title });
25
+ return worldRoot;
26
+ }
27
+ async function initWorldInteractive(dir, options = {}) {
28
+ (0, guard_1.assertApiKey)();
29
+ const worldRoot = (0, guard_1.resolveWorldRoot)(dir);
30
+ (0, guard_1.assertNotInitialized)(worldRoot);
31
+ (0, guard_1.ensureWorldRootParent)(worldRoot);
32
+ const maxRounds = options.maxRounds ?? constants_1.DEFAULT_MAX_INTERVIEW_ROUNDS;
33
+ let interviewSession;
34
+ try {
35
+ const interview = await (0, interview_loop_1.runInterviewLoop)(maxRounds);
36
+ interviewSession = interview.session;
37
+ const payload = await (0, finalize_1.finalizeWorld)(interview.transcript);
38
+ const id = options.id ?? payload.manifest.id ?? path_1.default.basename(worldRoot);
39
+ const title = options.title ?? payload.manifest.title ?? id;
40
+ payload.manifest.id = id;
41
+ payload.manifest.title = title;
42
+ (0, guard_1.assertNotInitialized)(worldRoot);
43
+ (0, scaffold_1.scaffoldEmptyWorld)(worldRoot, { id, title });
44
+ (0, apply_payload_1.applyPayload)(worldRoot, payload);
45
+ (0, archive_transcript_1.archiveTranscript)(interview.session, worldRoot);
46
+ (0, cleanup_1.cleanupSession)(interview.session);
47
+ return worldRoot;
48
+ }
49
+ catch (err) {
50
+ const cancelledSession = err instanceof errors_1.InitCancelledError ? err.session : interviewSession;
51
+ if (cancelledSession && options.keepSessionOnError) {
52
+ process.stderr.write(`Init session preserved at: ${cancelledSession.root}\n`);
53
+ }
54
+ else if (cancelledSession) {
55
+ (0, cleanup_1.cleanupSession)(cancelledSession);
56
+ }
57
+ throw err;
58
+ }
59
+ }
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runInterviewLoop = runInterviewLoop;
4
+ const constants_1 = require("./constants");
5
+ const checklist_1 = require("./checklist");
6
+ const errors_1 = require("./errors");
7
+ const parse_assistant_1 = require("./parse-assistant");
8
+ const promptpile_run_1 = require("./promptpile-run");
9
+ const read_user_input_1 = require("./read-user-input");
10
+ const session_1 = require("./session");
11
+ async function runInterviewRound(session, onDelta) {
12
+ let userText;
13
+ try {
14
+ userText = await (0, read_user_input_1.readUserInput)();
15
+ }
16
+ catch (err) {
17
+ if (err instanceof errors_1.InitCancelledError) {
18
+ throw new errors_1.InitCancelledError(err.message, session);
19
+ }
20
+ throw err;
21
+ }
22
+ (0, session_1.appendUserMessage)(session.messagesDir, userText);
23
+ const result = await (0, promptpile_run_1.runPromptpile)(session, [
24
+ '--config',
25
+ 'promptpile.toml',
26
+ '-d',
27
+ 'messages',
28
+ '--continue',
29
+ '--disable-tool',
30
+ ], { onDelta });
31
+ (0, promptpile_run_1.assertPromptpileOk)(result, 'Interview round');
32
+ return (0, session_1.getLatestAssistantText)(session.messagesDir);
33
+ }
34
+ async function runInterviewLoop(maxRounds = constants_1.DEFAULT_MAX_INTERVIEW_ROUNDS) {
35
+ const session = (0, session_1.createSession)();
36
+ (0, session_1.writeOpeningAssistant)(session.messagesDir, constants_1.OPENING_ASSISTANT);
37
+ process.stdout.write('\n--- World building interview ---\n\n');
38
+ process.stdout.write(stripDisplay(constants_1.OPENING_ASSISTANT));
39
+ process.stdout.write('\n');
40
+ for (let round = 1; round <= maxRounds; round += 1) {
41
+ session.round = round;
42
+ process.stdout.write('\n--- Assistant ---\n\n');
43
+ const displayStream = createInitDisplayStream();
44
+ const assistantText = await runInterviewRound(session, text => displayStream.push(text));
45
+ displayStream.flush();
46
+ process.stdout.write('\n');
47
+ const status = (0, parse_assistant_1.parseInterviewStatus)(assistantText);
48
+ const transcript = (0, session_1.buildTranscript)(session.messagesDir);
49
+ if ((0, checklist_1.isInterviewReady)(status, transcript)) {
50
+ process.stdout.write('\nInterview complete. Finalizing world save...\n');
51
+ return { session, transcript };
52
+ }
53
+ if (status.status === 'ready') {
54
+ const gaps = [
55
+ ...status.missing,
56
+ ...(0, checklist_1.getInterviewMissingFromTranscript)(transcript),
57
+ ];
58
+ process.stdout.write(`\nNote: model marked ready but checklist incomplete (${[...new Set(gaps)].join(', ')}). Continuing...\n`);
59
+ }
60
+ }
61
+ throw new Error(`Interview did not complete within ${maxRounds} rounds. Re-run init or increase --max-rounds.`);
62
+ }
63
+ function stripDisplay(text) {
64
+ return text.replace(/```(?:json\s+)?init-status\s*\n[\s\S]*?```/gi, '').trim();
65
+ }
66
+ function createInitDisplayStream() {
67
+ let buffer = '';
68
+ let suppressBlock = false;
69
+ const handleLine = (line, hasNewline) => {
70
+ const trimmed = line.trim();
71
+ if (suppressBlock) {
72
+ if (trimmed.startsWith('```')) {
73
+ suppressBlock = false;
74
+ }
75
+ return;
76
+ }
77
+ if (/^```.*(?:init-status|init-payload)/i.test(trimmed)) {
78
+ suppressBlock = true;
79
+ return;
80
+ }
81
+ process.stdout.write(line);
82
+ if (hasNewline) {
83
+ process.stdout.write('\n');
84
+ }
85
+ };
86
+ return {
87
+ push(text) {
88
+ buffer += text;
89
+ const lines = buffer.split('\n');
90
+ buffer = lines.pop() ?? '';
91
+ for (const line of lines) {
92
+ handleLine(line, true);
93
+ }
94
+ },
95
+ flush() {
96
+ if (buffer !== '') {
97
+ const line = buffer;
98
+ buffer = '';
99
+ handleLine(line, false);
100
+ }
101
+ }
102
+ };
103
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseInterviewStatus = parseInterviewStatus;
4
+ exports.parseInitPayload = parseInitPayload;
5
+ exports.stripInitStatusBlock = stripInitStatusBlock;
6
+ function extractFencedBlock(text, label) {
7
+ const re = new RegExp('```(?:json\\s+)?' + label + '\\s*\\n([\\s\\S]*?)```', 'i');
8
+ const m = text.match(re);
9
+ if (m) {
10
+ return m[1].trim();
11
+ }
12
+ const generic = text.match(/```json\s*\n([\s\S]*?)```/i);
13
+ return generic ? generic[1].trim() : null;
14
+ }
15
+ function parseInterviewStatus(assistantText) {
16
+ const raw = extractFencedBlock(assistantText, 'init-status') ??
17
+ extractFencedBlock(assistantText, '');
18
+ if (!raw) {
19
+ return { status: 'continue', missing: ['init-status block'] };
20
+ }
21
+ try {
22
+ const parsed = JSON.parse(raw);
23
+ const status = parsed.status === 'ready' ? 'ready' : 'continue';
24
+ const missing = Array.isArray(parsed.missing)
25
+ ? parsed.missing.map(String)
26
+ : [];
27
+ return { status, missing };
28
+ }
29
+ catch {
30
+ return { status: 'continue', missing: ['invalid init-status JSON'] };
31
+ }
32
+ }
33
+ function parseInitPayload(assistantText) {
34
+ const raw = extractFencedBlock(assistantText, 'init-payload') ??
35
+ extractFencedBlock(assistantText, '');
36
+ if (!raw) {
37
+ throw new Error('Assistant response missing init-payload JSON block');
38
+ }
39
+ try {
40
+ return JSON.parse(raw);
41
+ }
42
+ catch (e) {
43
+ throw new Error(`Failed to parse init-payload JSON: ${e instanceof Error ? e.message : e}`);
44
+ }
45
+ }
46
+ function stripInitStatusBlock(text) {
47
+ return text
48
+ .replace(/```(?:json\s+)?init-status\s*\n[\s\S]*?```/gi, '')
49
+ .trim();
50
+ }