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.
- package/README.md +1146 -0
- package/dist/cli/daily.js +41 -0
- package/dist/cli/index.js +33 -0
- package/dist/cli/init.js +40 -0
- package/dist/cli/next.js +60 -0
- package/dist/cli/play.js +39 -0
- package/dist/cli/revise.js +41 -0
- package/dist/cli/settle.js +45 -0
- package/dist/daily/apply-plan.js +25 -0
- package/dist/daily/constants.js +16 -0
- package/dist/daily/dialogue-loop.js +147 -0
- package/dist/daily/finalize.js +23 -0
- package/dist/daily/guard.js +54 -0
- package/dist/daily/index.js +27 -0
- package/dist/daily/intent-router.js +65 -0
- package/dist/daily/mcp-gateway.js +5 -0
- package/dist/daily/mcp-tools.js +8 -0
- package/dist/daily/parse-assistant.js +38 -0
- package/dist/daily/parse-payload.js +10 -0
- package/dist/daily/player-context.js +85 -0
- package/dist/daily/project-plan.js +15 -0
- package/dist/daily/promptpile-loop.js +41 -0
- package/dist/daily/prompts.js +11 -0
- package/dist/daily/read-user-input.js +6 -0
- package/dist/daily/session.js +119 -0
- package/dist/daily/types.js +2 -0
- package/dist/daily/validate-plan.js +46 -0
- package/dist/i18n/detect.js +23 -0
- package/dist/i18n/index.js +22 -0
- package/dist/i18n/messages.js +149 -0
- package/dist/index.js +27 -0
- package/dist/init/apply-payload.js +18 -0
- package/dist/init/archive-transcript.js +28 -0
- package/dist/init/checklist.js +74 -0
- package/dist/init/cleanup.js +12 -0
- package/dist/init/constants.js +21 -0
- package/dist/init/errors.js +11 -0
- package/dist/init/finalize.js +35 -0
- package/dist/init/guard.js +31 -0
- package/dist/init/index.js +59 -0
- package/dist/init/interview-loop.js +103 -0
- package/dist/init/parse-assistant.js +50 -0
- package/dist/init/project-payload.js +78 -0
- package/dist/init/promptpile-run.js +80 -0
- package/dist/init/prompts.js +16 -0
- package/dist/init/read-user-input.js +44 -0
- package/dist/init/scaffold.js +66 -0
- package/dist/init/session.js +98 -0
- package/dist/init/types.js +2 -0
- package/dist/next/index.js +79 -0
- package/dist/next/inspect.js +90 -0
- package/dist/play/ai.js +11 -0
- package/dist/play/event-loop.js +244 -0
- package/dist/play/guard.js +14 -0
- package/dist/play/index.js +21 -0
- package/dist/play/parse-assistant.js +39 -0
- package/dist/play/player-context.js +20 -0
- package/dist/play/prompts.js +9 -0
- package/dist/play/session.js +14 -0
- package/dist/play/state.js +117 -0
- package/dist/play/types.js +2 -0
- package/dist/play/validate.js +156 -0
- package/dist/revise/apply-payload.js +58 -0
- package/dist/revise/bin-resolve.js +38 -0
- package/dist/revise/constants.js +17 -0
- package/dist/revise/dialogue-loop.js +116 -0
- package/dist/revise/diff.js +24 -0
- package/dist/revise/file-hash.js +27 -0
- package/dist/revise/finalize.js +23 -0
- package/dist/revise/guard.js +17 -0
- package/dist/revise/index.js +24 -0
- package/dist/revise/mcp-gateway.js +74 -0
- package/dist/revise/mcp-tools.js +91 -0
- package/dist/revise/parse-assistant.js +41 -0
- package/dist/revise/parse-payload.js +22 -0
- package/dist/revise/process-run.js +77 -0
- package/dist/revise/project-payload.js +62 -0
- package/dist/revise/promptpile-loop.js +41 -0
- package/dist/revise/prompts.js +16 -0
- package/dist/revise/read-user-input.js +35 -0
- package/dist/revise/session.js +119 -0
- package/dist/revise/types.js +2 -0
- package/dist/revise/validate-payload.js +47 -0
- package/dist/settle/ai.js +23 -0
- package/dist/settle/apply.js +58 -0
- package/dist/settle/context.js +69 -0
- package/dist/settle/derive.js +56 -0
- package/dist/settle/guard.js +71 -0
- package/dist/settle/index.js +105 -0
- package/dist/settle/parse-assistant.js +14 -0
- package/dist/settle/parse-payload.js +19 -0
- package/dist/settle/project.js +58 -0
- package/dist/settle/session.js +45 -0
- package/dist/settle/types.js +2 -0
- package/dist/settle/validate.js +100 -0
- package/dist/shared/filtered-stream-output.js +41 -0
- package/dist/shared/promptpile-stream.js +59 -0
- package/dist/shared/run-promptpile-with-stream.js +34 -0
- package/dist/utils/loading.js +54 -0
- package/package.json +39 -0
- package/prompts/README.md +39 -0
- package/prompts/choice.system.md +0 -0
- package/prompts/daily-dialogue.system.md +37 -0
- package/prompts/daily-finalize-plan.system.md +34 -0
- package/prompts/daily-intent-router.system.md +34 -0
- package/prompts/day-planner.system.md +0 -0
- package/prompts/day-summarizer.system.md +0 -0
- package/prompts/dialogue.system.md +0 -0
- package/prompts/diary-writer.system.md +0 -0
- package/prompts/event-runner.system.md +0 -0
- package/prompts/init-finalize.system.md +59 -0
- package/prompts/init-interviewer.system.md +37 -0
- package/prompts/memory-updater.system.md +0 -0
- package/prompts/next-day-seeder.system.md +0 -0
- package/prompts/play-event-dialogue.system.md +20 -0
- package/prompts/play-event-generator.system.md +19 -0
- package/prompts/play-event-resolver.system.md +26 -0
- package/prompts/play-replanner.system.md +21 -0
- package/prompts/revise-dialogue.system.md +22 -0
- package/prompts/revise-finalize.system.md +40 -0
- package/prompts/settle.system.md +28 -0
- package/prompts/spec.md +320 -0
- package/prompts/state-resolver.system.md +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
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.describeChanges = describeChanges;
|
|
7
|
+
exports.applyChanges = applyChanges;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
function describeChanges(worldRoot, changes) {
|
|
11
|
+
return changes.map(change => {
|
|
12
|
+
const action = fs_1.default.existsSync(path_1.default.join(worldRoot, change.relativePath)) ? 'update' : 'create';
|
|
13
|
+
return `${action} ${change.relativePath}`;
|
|
14
|
+
}).join('\n');
|
|
15
|
+
}
|
|
16
|
+
function applyChanges(worldRoot, payload, changes, now = new Date(), artifacts = {}) {
|
|
17
|
+
const revisionId = `revision_${formatRevisionTimestamp(now)}`;
|
|
18
|
+
const revisionRoot = path_1.default.join(worldRoot, '.loom', 'revisions', revisionId);
|
|
19
|
+
const beforeRoot = path_1.default.join(revisionRoot, 'before');
|
|
20
|
+
const createdFiles = [];
|
|
21
|
+
fs_1.default.mkdirSync(beforeRoot, { recursive: true });
|
|
22
|
+
for (const change of changes) {
|
|
23
|
+
const filePath = path_1.default.join(worldRoot, change.relativePath);
|
|
24
|
+
if (fs_1.default.existsSync(filePath)) {
|
|
25
|
+
const beforePath = path_1.default.join(beforeRoot, change.relativePath);
|
|
26
|
+
fs_1.default.mkdirSync(path_1.default.dirname(beforePath), { recursive: true });
|
|
27
|
+
fs_1.default.copyFileSync(filePath, beforePath);
|
|
28
|
+
}
|
|
29
|
+
else
|
|
30
|
+
createdFiles.push(change.relativePath);
|
|
31
|
+
}
|
|
32
|
+
fs_1.default.writeFileSync(path_1.default.join(revisionRoot, 'payload.json'), `${JSON.stringify({ ...payload, created_files: createdFiles }, null, 2)}\n`, 'utf8');
|
|
33
|
+
fs_1.default.writeFileSync(path_1.default.join(revisionRoot, 'changes.txt'), `${describeChanges(worldRoot, changes)}\n`, 'utf8');
|
|
34
|
+
if (artifacts.diff !== undefined)
|
|
35
|
+
fs_1.default.writeFileSync(path_1.default.join(revisionRoot, 'diff.patch'), artifacts.diff, 'utf8');
|
|
36
|
+
if (artifacts.draft !== undefined)
|
|
37
|
+
fs_1.default.writeFileSync(path_1.default.join(revisionRoot, 'draft.json'), `${JSON.stringify(artifacts.draft, null, 2)}\n`, 'utf8');
|
|
38
|
+
if (artifacts.transcript !== undefined) {
|
|
39
|
+
const transcriptDir = path_1.default.join(revisionRoot, 'transcript');
|
|
40
|
+
fs_1.default.mkdirSync(transcriptDir, { recursive: true });
|
|
41
|
+
fs_1.default.writeFileSync(path_1.default.join(transcriptDir, 'dialogue.md'), artifacts.transcript, 'utf8');
|
|
42
|
+
}
|
|
43
|
+
for (const change of changes) {
|
|
44
|
+
const filePath = path_1.default.join(worldRoot, change.relativePath);
|
|
45
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
46
|
+
fs_1.default.writeFileSync(filePath, change.content, 'utf8');
|
|
47
|
+
}
|
|
48
|
+
appendStateChange(worldRoot, { type: 'world_revision', revision: revisionId, summary: payload.summary, changed_files: changes.map(change => change.relativePath) });
|
|
49
|
+
return revisionId;
|
|
50
|
+
}
|
|
51
|
+
function appendStateChange(worldRoot, entry) {
|
|
52
|
+
const logPath = path_1.default.join(worldRoot, 'logs', 'state_changes.jsonl');
|
|
53
|
+
fs_1.default.mkdirSync(path_1.default.dirname(logPath), { recursive: true });
|
|
54
|
+
fs_1.default.appendFileSync(logPath, `${JSON.stringify(entry)}\n`, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
function formatRevisionTimestamp(date) {
|
|
57
|
+
return date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
58
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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.getPromptpileSpawnConfig = getPromptpileSpawnConfig;
|
|
7
|
+
exports.getPromptpileMcpSpawnConfig = getPromptpileMcpSpawnConfig;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
function nodeScript(script) {
|
|
11
|
+
return { command: process.execPath, argvPrefix: [script], displayName: `node "${script}"` };
|
|
12
|
+
}
|
|
13
|
+
function getPromptpileSpawnConfig() {
|
|
14
|
+
const override = process.env.PROMPTPILE_BIN?.trim();
|
|
15
|
+
if (override)
|
|
16
|
+
return { command: override, argvPrefix: [], displayName: override };
|
|
17
|
+
try {
|
|
18
|
+
const pkg = require.resolve('promptpile/package.json');
|
|
19
|
+
const script = path_1.default.join(path_1.default.dirname(pkg), 'dist', 'index.js');
|
|
20
|
+
if (fs_1.default.existsSync(script))
|
|
21
|
+
return nodeScript(script);
|
|
22
|
+
}
|
|
23
|
+
catch { /* fall through */ }
|
|
24
|
+
return { command: 'promptpile', argvPrefix: [], displayName: 'promptpile' };
|
|
25
|
+
}
|
|
26
|
+
function getPromptpileMcpSpawnConfig() {
|
|
27
|
+
const override = process.env.PROMPTPILE_MCP_BIN?.trim();
|
|
28
|
+
if (override)
|
|
29
|
+
return { command: override, argvPrefix: [], displayName: override };
|
|
30
|
+
try {
|
|
31
|
+
const pkg = require.resolve('promptpile-mcp/package.json');
|
|
32
|
+
const script = path_1.default.join(path_1.default.dirname(pkg), 'dist', 'src', 'index.js');
|
|
33
|
+
if (fs_1.default.existsSync(script))
|
|
34
|
+
return nodeScript(script);
|
|
35
|
+
}
|
|
36
|
+
catch { /* fall through */ }
|
|
37
|
+
return { command: 'promptpile-mcp', argvPrefix: [], displayName: 'promptpile-mcp' };
|
|
38
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OPENING_ASSISTANT = exports.READONLY_MCP_TOOL_NAMES = exports.DEFAULT_GATEWAY_TIMEOUT_MS = exports.DEFAULT_MAX_TOOL_ROUNDS = void 0;
|
|
4
|
+
exports.DEFAULT_MAX_TOOL_ROUNDS = 8;
|
|
5
|
+
exports.DEFAULT_GATEWAY_TIMEOUT_MS = 60000;
|
|
6
|
+
exports.READONLY_MCP_TOOL_NAMES = new Set([
|
|
7
|
+
'mcp__world__list_allowed_directories',
|
|
8
|
+
'mcp__world__list_directory',
|
|
9
|
+
'mcp__world__directory_tree',
|
|
10
|
+
'mcp__world__search_files',
|
|
11
|
+
'mcp__world__read_text_file',
|
|
12
|
+
'mcp__world__read_multiple_files',
|
|
13
|
+
'mcp__world__get_file_info',
|
|
14
|
+
]);
|
|
15
|
+
exports.OPENING_ASSISTANT = `你好,我是 dayloom 的 World 设定维护助手。
|
|
16
|
+
|
|
17
|
+
你可以询问现有角色、场景和世界设定,也可以告诉我需要怎样修改。每轮可输入多行内容,结束时按 Ctrl+D(macOS/Linux)或 Ctrl+Z 后 Enter(Windows)提交。输入 /pending 查看待修改事项,输入 /apply 生成修改提案,输入 /cancel 放弃退出。`;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reviseWorldInteractive = reviseWorldInteractive;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
const apply_payload_1 = require("./apply-payload");
|
|
6
|
+
const diff_1 = require("./diff");
|
|
7
|
+
const file_hash_1 = require("./file-hash");
|
|
8
|
+
const finalize_1 = require("./finalize");
|
|
9
|
+
const guard_1 = require("./guard");
|
|
10
|
+
const mcp_gateway_1 = require("./mcp-gateway");
|
|
11
|
+
const mcp_tools_1 = require("./mcp-tools");
|
|
12
|
+
const parse_assistant_1 = require("./parse-assistant");
|
|
13
|
+
const promptpile_loop_1 = require("./promptpile-loop");
|
|
14
|
+
const read_user_input_1 = require("./read-user-input");
|
|
15
|
+
const session_1 = require("./session");
|
|
16
|
+
const project_payload_1 = require("./project-payload");
|
|
17
|
+
const validate_payload_1 = require("./validate-payload");
|
|
18
|
+
const filtered_stream_output_1 = require("../shared/filtered-stream-output");
|
|
19
|
+
const loading_1 = require("../utils/loading");
|
|
20
|
+
async function reviseWorldInteractive(dir, options = {}) {
|
|
21
|
+
if (!process.env.DEEPSEEK_API_KEY?.trim())
|
|
22
|
+
throw new Error('DEEPSEEK_API_KEY is not set. Interactive revise requires an API key.');
|
|
23
|
+
const worldRoot = (0, guard_1.resolveWorldRoot)(dir);
|
|
24
|
+
(0, guard_1.assertInitializedWorld)(worldRoot);
|
|
25
|
+
const session = (0, session_1.createReviseSession)();
|
|
26
|
+
let preserveSession = options.keepSession ?? false;
|
|
27
|
+
let gateway;
|
|
28
|
+
const maxToolRounds = options.maxToolRounds ?? constants_1.DEFAULT_MAX_TOOL_ROUNDS;
|
|
29
|
+
try {
|
|
30
|
+
await (0, loading_1.withLoading)('正在准备修订会话...', async (loading) => {
|
|
31
|
+
gateway = await (0, mcp_gateway_1.connectOrStartGateway)(session.root, worldRoot, options.mcpBaseUrl, options.mcpToken);
|
|
32
|
+
loading.update('正在准备只读工具...');
|
|
33
|
+
await (0, mcp_tools_1.exportReadonlyTools)(gateway.baseUrl, gateway.token, session.toolsFile);
|
|
34
|
+
await (0, mcp_tools_1.assertAllowedWorldRoot)(gateway.baseUrl, gateway.token, worldRoot, session.root);
|
|
35
|
+
});
|
|
36
|
+
if (!gateway)
|
|
37
|
+
throw new Error('Failed to initialize readonly gateway');
|
|
38
|
+
process.stdout.write(`\n--- World revision session ---\n\n${constants_1.OPENING_ASSISTANT}\n`);
|
|
39
|
+
while (true) {
|
|
40
|
+
const input = await (0, read_user_input_1.readReviseUserInput)();
|
|
41
|
+
if (input === undefined) {
|
|
42
|
+
preserveSession = true;
|
|
43
|
+
process.stdout.write(`Revision draft saved in session: ${session.root}\n`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (input === '/help') {
|
|
47
|
+
process.stdout.write('/pending /apply /cancel /exit\n');
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (input === '/pending') {
|
|
51
|
+
process.stdout.write(`${JSON.stringify((0, session_1.readDraft)(session), null, 2)}\n`);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (input === '/cancel') {
|
|
55
|
+
process.stdout.write('Revision cancelled.\n');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (input === '/exit') {
|
|
59
|
+
preserveSession = true;
|
|
60
|
+
process.stdout.write(`Revision draft saved in session: ${session.root}\n`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (input === '/apply') {
|
|
64
|
+
const draft = (0, session_1.readDraft)(session);
|
|
65
|
+
if (!draft.pending_changes.length) {
|
|
66
|
+
process.stdout.write('No pending changes.\n');
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const payload = await (0, loading_1.withLoading)('正在生成修订方案...', () => (0, finalize_1.finalizeRevision)((0, session_1.buildTranscript)(session.messagesDir), draft, session.toolsFile, gateway.baseUrl, gateway.token, maxToolRounds, options.keepSession));
|
|
70
|
+
(0, validate_payload_1.validateRevisePayload)(payload);
|
|
71
|
+
const changes = (0, project_payload_1.projectRevisePayload)(payload, worldRoot);
|
|
72
|
+
const diff = (0, diff_1.buildUnifiedDiff)(worldRoot, changes);
|
|
73
|
+
if (!diff) {
|
|
74
|
+
process.stdout.write('No file changes produced.\n');
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
process.stdout.write(`\n${diff}\n`);
|
|
78
|
+
if (options.dryRun) {
|
|
79
|
+
process.stdout.write('Dry run only. No files changed.\n');
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const snapshots = (0, file_hash_1.snapshotChanges)(worldRoot, changes);
|
|
83
|
+
if (!options.yes && !await (0, read_user_input_1.askYesNo)('Apply this revision? (Y/N): ')) {
|
|
84
|
+
process.stdout.write('Revision not applied.\n');
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
(0, file_hash_1.assertSnapshotsUnchanged)(worldRoot, snapshots);
|
|
88
|
+
const revisionId = (0, apply_payload_1.applyChanges)(worldRoot, payload, changes, new Date(), { diff, draft, transcript: (0, session_1.buildTranscript)(session.messagesDir) });
|
|
89
|
+
process.stdout.write(`Applied World revision: ${revisionId}\n`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
(0, session_1.appendUserMessage)(session.messagesDir, input);
|
|
93
|
+
process.stdout.write('\nAI> ');
|
|
94
|
+
const stream = (0, filtered_stream_output_1.createFilteredStreamOutput)({ hiddenBlocks: ['revise-status'] });
|
|
95
|
+
const reply = await (0, promptpile_loop_1.runPromptpileUntilText)(session, gateway.baseUrl, gateway.token, maxToolRounds, text => stream.push(text));
|
|
96
|
+
stream.flush();
|
|
97
|
+
try {
|
|
98
|
+
const status = (0, parse_assistant_1.parseReviseStatus)(reply);
|
|
99
|
+
if (status)
|
|
100
|
+
(0, session_1.writeDraft)(session, status);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
process.stderr.write(`Warning: ${err instanceof Error ? err.message : err}\n`);
|
|
104
|
+
}
|
|
105
|
+
process.stdout.write('\n');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
if (gateway)
|
|
110
|
+
await gateway.stop();
|
|
111
|
+
if (preserveSession)
|
|
112
|
+
process.stderr.write(`Revise session preserved at: ${session.root}\n`);
|
|
113
|
+
else
|
|
114
|
+
(0, session_1.cleanupSession)(session);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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.buildUnifiedDiff = buildUnifiedDiff;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function buildUnifiedDiff(worldRoot, changes) {
|
|
10
|
+
return changes.map(change => {
|
|
11
|
+
const filePath = path_1.default.join(worldRoot, change.relativePath);
|
|
12
|
+
const before = fs_1.default.existsSync(filePath) ? fs_1.default.readFileSync(filePath, 'utf8') : '';
|
|
13
|
+
if (before === change.content)
|
|
14
|
+
return '';
|
|
15
|
+
return [
|
|
16
|
+
`--- a/${change.relativePath}`,
|
|
17
|
+
`+++ b/${change.relativePath}`,
|
|
18
|
+
'@@',
|
|
19
|
+
...before.split('\n').filter((_, i, arr) => i < arr.length - 1 || arr[i] !== '').map(line => `-${line}`),
|
|
20
|
+
...change.content.split('\n').filter((_, i, arr) => i < arr.length - 1 || arr[i] !== '').map(line => `+${line}`),
|
|
21
|
+
'',
|
|
22
|
+
].join('\n');
|
|
23
|
+
}).filter(Boolean).join('\n');
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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.snapshotChanges = snapshotChanges;
|
|
7
|
+
exports.assertSnapshotsUnchanged = assertSnapshotsUnchanged;
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
function snapshotChanges(worldRoot, changes) {
|
|
12
|
+
return changes.map(change => snapshotFile(worldRoot, change.relativePath));
|
|
13
|
+
}
|
|
14
|
+
function assertSnapshotsUnchanged(worldRoot, snapshots) {
|
|
15
|
+
for (const expected of snapshots) {
|
|
16
|
+
const current = snapshotFile(worldRoot, expected.relativePath);
|
|
17
|
+
if (current.exists !== expected.exists || current.sha256 !== expected.sha256) {
|
|
18
|
+
throw new Error(`Refusing to apply revision: file changed during review: ${expected.relativePath}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function snapshotFile(worldRoot, relativePath) {
|
|
23
|
+
const filePath = path_1.default.join(worldRoot, relativePath);
|
|
24
|
+
if (!fs_1.default.existsSync(filePath))
|
|
25
|
+
return { relativePath, exists: false };
|
|
26
|
+
return { relativePath, exists: true, sha256: crypto_1.default.createHash('sha256').update(fs_1.default.readFileSync(filePath)).digest('hex') };
|
|
27
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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.finalizeRevision = finalizeRevision;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const parse_assistant_1 = require("./parse-assistant");
|
|
9
|
+
const promptpile_loop_1 = require("./promptpile-loop");
|
|
10
|
+
const session_1 = require("./session");
|
|
11
|
+
async function finalizeRevision(transcript, draft, toolsFile, baseUrl, token, maxToolRounds, keepSession = false) {
|
|
12
|
+
const session = (0, session_1.createFinalizeSession)(transcript, draft);
|
|
13
|
+
fs_1.default.copyFileSync(toolsFile, session.toolsFile);
|
|
14
|
+
try {
|
|
15
|
+
return (0, parse_assistant_1.parseRevisePayload)(await (0, promptpile_loop_1.runPromptpileUntilText)(session, baseUrl, token, maxToolRounds));
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
if (keepSession)
|
|
19
|
+
process.stderr.write(`Finalize session preserved at: ${session.root}\n`);
|
|
20
|
+
else
|
|
21
|
+
(0, session_1.cleanupSession)(session);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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.assertInitializedWorld = assertInitializedWorld;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
function resolveWorldRoot(dir) {
|
|
11
|
+
return path_1.default.resolve(dir);
|
|
12
|
+
}
|
|
13
|
+
function assertInitializedWorld(worldRoot) {
|
|
14
|
+
if (!fs_1.default.existsSync(path_1.default.join(worldRoot, 'manifest.yaml'))) {
|
|
15
|
+
throw new Error(`World save is not initialized: ${worldRoot}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reviseWorldInteractive = void 0;
|
|
4
|
+
exports.reviseWorldFromProposal = reviseWorldFromProposal;
|
|
5
|
+
const apply_payload_1 = require("./apply-payload");
|
|
6
|
+
const dialogue_loop_1 = require("./dialogue-loop");
|
|
7
|
+
Object.defineProperty(exports, "reviseWorldInteractive", { enumerable: true, get: function () { return dialogue_loop_1.reviseWorldInteractive; } });
|
|
8
|
+
const guard_1 = require("./guard");
|
|
9
|
+
const parse_payload_1 = require("./parse-payload");
|
|
10
|
+
const project_payload_1 = require("./project-payload");
|
|
11
|
+
const validate_payload_1 = require("./validate-payload");
|
|
12
|
+
function reviseWorldFromProposal(dir, proposalPath, options = {}) {
|
|
13
|
+
const worldRoot = (0, guard_1.resolveWorldRoot)(dir);
|
|
14
|
+
(0, guard_1.assertInitializedWorld)(worldRoot);
|
|
15
|
+
const payload = (0, parse_payload_1.readRevisePayload)(proposalPath);
|
|
16
|
+
(0, validate_payload_1.validateRevisePayload)(payload);
|
|
17
|
+
const changes = (0, project_payload_1.projectRevisePayload)(payload, worldRoot);
|
|
18
|
+
const description = (0, apply_payload_1.describeChanges)(worldRoot, changes);
|
|
19
|
+
if (options.dryRun)
|
|
20
|
+
return { worldRoot, description };
|
|
21
|
+
if (!options.yes)
|
|
22
|
+
throw new Error('Applying a revision requires --yes. Use --dry-run to inspect changes.');
|
|
23
|
+
return { worldRoot, description, revisionId: (0, apply_payload_1.applyChanges)(worldRoot, payload, changes) };
|
|
24
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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.connectOrStartGateway = connectOrStartGateway;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const net_1 = __importDefault(require("net"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const constants_1 = require("./constants");
|
|
13
|
+
const bin_resolve_1 = require("./bin-resolve");
|
|
14
|
+
const process_run_1 = require("./process-run");
|
|
15
|
+
async function connectOrStartGateway(sessionRoot, worldRoot, externalBaseUrl, externalToken) {
|
|
16
|
+
if (externalBaseUrl)
|
|
17
|
+
return { baseUrl: externalBaseUrl.replace(/\/$/, ''), token: externalToken, async stop() { } };
|
|
18
|
+
const port = await pickFreePort();
|
|
19
|
+
const token = crypto_1.default.randomBytes(24).toString('hex');
|
|
20
|
+
const config = path_1.default.join(sessionRoot, 'mcp.toml');
|
|
21
|
+
const filesystemServer = getFilesystemServerSpawnConfig(worldRoot);
|
|
22
|
+
fs_1.default.writeFileSync(config, `version = 1\n\n[gateway]\nport = ${port}\ntoken = "${token}"\n\n[behavior]\nfailure_policy = "strict"\nflat_names = false\n\n[servers.world]\ncommand = ${JSON.stringify(filesystemServer.command)}\nargs = ${JSON.stringify(filesystemServer.args)}\n`, 'utf8');
|
|
23
|
+
const spawnConfig = (0, bin_resolve_1.getPromptpileMcpSpawnConfig)();
|
|
24
|
+
const child = (0, child_process_1.spawn)(spawnConfig.command, [...spawnConfig.argvPrefix, 'launch', '--config', config], { cwd: sessionRoot, env: process.env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
25
|
+
let stderr = '';
|
|
26
|
+
let spawnError;
|
|
27
|
+
child.stderr?.on('data', chunk => { stderr += chunk.toString(); });
|
|
28
|
+
child.on('error', error => { spawnError = error; });
|
|
29
|
+
const baseUrl = `http://127.0.0.1:${port}`;
|
|
30
|
+
try {
|
|
31
|
+
await waitForHealth(baseUrl, token, child, () => stderr, () => spawnError);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
await (0, process_run_1.stopChild)(child);
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
return { baseUrl, token, stop: () => (0, process_run_1.stopChild)(child) };
|
|
38
|
+
}
|
|
39
|
+
async function pickFreePort() {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const server = net_1.default.createServer();
|
|
42
|
+
server.once('error', reject);
|
|
43
|
+
server.listen(0, '127.0.0.1', () => {
|
|
44
|
+
const address = server.address();
|
|
45
|
+
if (!address || typeof address === 'string')
|
|
46
|
+
return reject(new Error('Failed to choose MCP gateway port'));
|
|
47
|
+
server.close(err => err ? reject(err) : resolve(address.port));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async function waitForHealth(baseUrl, token, child, getStderr, getSpawnError) {
|
|
52
|
+
const deadline = Date.now() + constants_1.DEFAULT_GATEWAY_TIMEOUT_MS;
|
|
53
|
+
while (Date.now() < deadline) {
|
|
54
|
+
const spawnError = getSpawnError();
|
|
55
|
+
if (spawnError)
|
|
56
|
+
throw new Error(`Failed to start promptpile-mcp: ${spawnError.message}`);
|
|
57
|
+
if (child.exitCode !== null)
|
|
58
|
+
throw new Error(`promptpile-mcp exited before health check: ${getStderr().trim().slice(-500)}`);
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(`${baseUrl}/health`, { headers: { Authorization: `Bearer ${token}` } });
|
|
61
|
+
if (res.ok)
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
catch { /* retry */ }
|
|
65
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`Timed out waiting for promptpile-mcp gateway. ${getStderr().trim().slice(-500)}`);
|
|
68
|
+
}
|
|
69
|
+
function getFilesystemServerSpawnConfig(worldRoot) {
|
|
70
|
+
const override = process.env.DAY_LOOM_FILESYSTEM_MCP_BIN?.trim();
|
|
71
|
+
if (override)
|
|
72
|
+
return { command: process.execPath, args: [override, worldRoot] };
|
|
73
|
+
return { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', worldRoot] };
|
|
74
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
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.exportReadonlyTools = exportReadonlyTools;
|
|
7
|
+
exports.parseAndAssertReadonlyCalls = parseAndAssertReadonlyCalls;
|
|
8
|
+
exports.executeReadonlyCalls = executeReadonlyCalls;
|
|
9
|
+
exports.assertAllowedWorldRoot = assertAllowedWorldRoot;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const constants_1 = require("./constants");
|
|
13
|
+
function headers(token) {
|
|
14
|
+
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
15
|
+
}
|
|
16
|
+
async function exportReadonlyTools(baseUrl, token, outputFile) {
|
|
17
|
+
const res = await fetch(`${baseUrl}/v1/tools/export`, { headers: { Accept: 'application/json', ...headers(token) } });
|
|
18
|
+
if (!res.ok)
|
|
19
|
+
throw new Error(`MCP export-tools failed: HTTP ${res.status}`);
|
|
20
|
+
const body = await res.json();
|
|
21
|
+
if (!Array.isArray(body.tools))
|
|
22
|
+
throw new Error('MCP export-tools response missing tools array');
|
|
23
|
+
const tools = body.tools.filter(tool => constants_1.READONLY_MCP_TOOL_NAMES.has(tool.function.name));
|
|
24
|
+
for (const required of constants_1.READONLY_MCP_TOOL_NAMES) {
|
|
25
|
+
if (!tools.some(tool => tool.function.name === required))
|
|
26
|
+
throw new Error(`MCP readonly tool missing: ${required}`);
|
|
27
|
+
}
|
|
28
|
+
fs_1.default.writeFileSync(outputFile, toolsToml(tools), 'utf8');
|
|
29
|
+
}
|
|
30
|
+
function toolsToml(tools) {
|
|
31
|
+
return tools.map(tool => [
|
|
32
|
+
'[[tools]]',
|
|
33
|
+
`name = ${JSON.stringify(tool.function.name)}`,
|
|
34
|
+
...(tool.function.description ? [`description = ${JSON.stringify(tool.function.description)}`] : []),
|
|
35
|
+
`parameters = ${JSON.stringify(JSON.stringify(tool.function.parameters ?? { type: 'object', properties: {} }))}`,
|
|
36
|
+
'',
|
|
37
|
+
].join('\n')).join('\n');
|
|
38
|
+
}
|
|
39
|
+
function parseAndAssertReadonlyCalls(callFile) {
|
|
40
|
+
const calls = [];
|
|
41
|
+
for (const [index, line] of fs_1.default.readFileSync(callFile, 'utf8').split(/\r?\n/).entries()) {
|
|
42
|
+
if (!line.trim())
|
|
43
|
+
continue;
|
|
44
|
+
const call = JSON.parse(line);
|
|
45
|
+
if (!call || call.type !== 'function' || typeof call.id !== 'string' || typeof call.function?.name !== 'string' || typeof call.function.arguments !== 'string') {
|
|
46
|
+
throw new Error(`Invalid tool call on line ${index + 1}: ${path_1.default.basename(callFile)}`);
|
|
47
|
+
}
|
|
48
|
+
if (!constants_1.READONLY_MCP_TOOL_NAMES.has(call.function.name))
|
|
49
|
+
throw new Error(`Refusing non-readonly MCP tool call: ${call.function.name}`);
|
|
50
|
+
calls.push(call);
|
|
51
|
+
}
|
|
52
|
+
return calls;
|
|
53
|
+
}
|
|
54
|
+
async function executeReadonlyCalls(baseUrl, token, callFile) {
|
|
55
|
+
const calls = parseAndAssertReadonlyCalls(callFile);
|
|
56
|
+
if (!calls.length)
|
|
57
|
+
throw new Error(`No MCP calls found in ${callFile}`);
|
|
58
|
+
const res = await fetch(`${baseUrl}/v1/calls/exec`, {
|
|
59
|
+
method: 'POST', headers: { 'Content-Type': 'application/json', ...headers(token) }, body: JSON.stringify({ calls }),
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok)
|
|
62
|
+
throw new Error(`MCP exec-calls failed: HTTP ${res.status}`);
|
|
63
|
+
const body = await res.json();
|
|
64
|
+
if (!Array.isArray(body.results))
|
|
65
|
+
throw new Error('MCP exec-calls response missing results array');
|
|
66
|
+
const byId = new Map(body.results.map(result => [result.toolCallId, result]));
|
|
67
|
+
const resultFile = callFile.replace(/\.calls\.jsonl$/i, '.result.jsonl');
|
|
68
|
+
const lines = calls.map(call => {
|
|
69
|
+
const result = byId.get(call.id);
|
|
70
|
+
const content = result?.ok ? (typeof result.content === 'string' ? result.content : JSON.stringify(result.content ?? '')) : result?.error ?? 'MCP tool failed';
|
|
71
|
+
return JSON.stringify({ tool_call_id: call.id, name: call.function.name, content });
|
|
72
|
+
});
|
|
73
|
+
fs_1.default.writeFileSync(resultFile, `${lines.join('\n')}\n`, 'utf8');
|
|
74
|
+
return resultFile;
|
|
75
|
+
}
|
|
76
|
+
async function assertAllowedWorldRoot(baseUrl, token, worldRoot, scratchDir) {
|
|
77
|
+
const callFile = path_1.default.join(scratchDir, `.dayloom-allowed-${process.pid}.calls.jsonl`);
|
|
78
|
+
const id = `allowed-${process.pid}`;
|
|
79
|
+
fs_1.default.writeFileSync(callFile, `${JSON.stringify({ id, type: 'function', function: { name: 'mcp__world__list_allowed_directories', arguments: '{}' } })}\n`, 'utf8');
|
|
80
|
+
try {
|
|
81
|
+
const resultFile = await executeReadonlyCalls(baseUrl, token, callFile);
|
|
82
|
+
const row = JSON.parse(fs_1.default.readFileSync(resultFile, 'utf8').trim());
|
|
83
|
+
const allowedText = row.content;
|
|
84
|
+
if (!allowedText.includes(worldRoot))
|
|
85
|
+
throw new Error(`MCP allowed directories do not include World root: ${worldRoot}`);
|
|
86
|
+
fs_1.default.rmSync(resultFile, { force: true });
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
fs_1.default.rmSync(callFile, { force: true });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseReviseStatus = parseReviseStatus;
|
|
4
|
+
exports.parseRevisePayload = parseRevisePayload;
|
|
5
|
+
exports.stripReviseStatus = stripReviseStatus;
|
|
6
|
+
function extractBlock(text, label) {
|
|
7
|
+
const m = text.match(new RegExp('```(?:json\\s+)?' + label + '\\s*\\n([\\s\\S]*?)```', 'i'));
|
|
8
|
+
return m ? m[1].trim() : null;
|
|
9
|
+
}
|
|
10
|
+
function parseReviseStatus(text) {
|
|
11
|
+
const raw = extractBlock(text, 'revise-status');
|
|
12
|
+
if (!raw)
|
|
13
|
+
return undefined;
|
|
14
|
+
const parsed = JSON.parse(raw);
|
|
15
|
+
if (!parsed || !Array.isArray(parsed.pending_changes)) {
|
|
16
|
+
throw new Error('revise-status pending_changes must be an array');
|
|
17
|
+
}
|
|
18
|
+
for (const [index, item] of parsed.pending_changes.entries()) {
|
|
19
|
+
if (!item || typeof item !== 'object' || typeof item.instruction !== 'string' || !item.instruction.trim()) {
|
|
20
|
+
throw new Error(`revise-status pending_changes[${index}] is invalid`);
|
|
21
|
+
}
|
|
22
|
+
if (!item.target || typeof item.target !== 'object' || Array.isArray(item.target)) {
|
|
23
|
+
throw new Error(`revise-status pending_changes[${index}].target is invalid`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
28
|
+
function parseRevisePayload(text) {
|
|
29
|
+
const raw = extractBlock(text, 'revise-payload');
|
|
30
|
+
if (!raw)
|
|
31
|
+
throw new Error('Assistant response missing revise-payload JSON block');
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(raw);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
throw new Error(`Failed to parse revise-payload JSON: ${err instanceof Error ? err.message : err}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function stripReviseStatus(text) {
|
|
40
|
+
return text.replace(/```(?:json\s+)?revise-status\s*\n[\s\S]*?```/gi, '').trim();
|
|
41
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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.readRevisePayload = readRevisePayload;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
function readRevisePayload(filePath) {
|
|
9
|
+
let raw;
|
|
10
|
+
try {
|
|
11
|
+
raw = fs_1.default.readFileSync(filePath, 'utf8');
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
throw new Error(`Failed to read revise proposal: ${err instanceof Error ? err.message : err}`);
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
throw new Error(`Failed to parse revise proposal JSON: ${err instanceof Error ? err.message : err}`);
|
|
21
|
+
}
|
|
22
|
+
}
|