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,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runProcess = runProcess;
|
|
4
|
+
exports.stopChild = stopChild;
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
function runProcess(command, args, options) {
|
|
7
|
+
return new Promise(resolve => {
|
|
8
|
+
const stdio = options.outputPile ? ['ignore', 'pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'];
|
|
9
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
10
|
+
cwd: options.cwd,
|
|
11
|
+
env: options.env ?? process.env,
|
|
12
|
+
stdio: stdio
|
|
13
|
+
});
|
|
14
|
+
let stdout = '';
|
|
15
|
+
let stderr = '';
|
|
16
|
+
let spawnError;
|
|
17
|
+
let outputPileError;
|
|
18
|
+
let resolved = false;
|
|
19
|
+
const resolveOnce = (result) => {
|
|
20
|
+
if (resolved)
|
|
21
|
+
return;
|
|
22
|
+
resolved = true;
|
|
23
|
+
resolve(result);
|
|
24
|
+
};
|
|
25
|
+
child.stdout?.on('data', chunk => {
|
|
26
|
+
const text = chunk.toString();
|
|
27
|
+
stdout += text;
|
|
28
|
+
if (!options.quiet)
|
|
29
|
+
process.stdout.write(text);
|
|
30
|
+
});
|
|
31
|
+
child.stderr?.on('data', chunk => {
|
|
32
|
+
const text = chunk.toString();
|
|
33
|
+
stderr += text;
|
|
34
|
+
if (!options.quiet)
|
|
35
|
+
process.stderr.write(text);
|
|
36
|
+
});
|
|
37
|
+
if (options.outputPile) {
|
|
38
|
+
const outputStream = child.stdio[options.outputPile.fd];
|
|
39
|
+
if (!outputStream || typeof outputStream.on !== 'function') {
|
|
40
|
+
outputPileError = new Error('promptpile output fd ' + options.outputPile.fd + ' was not created');
|
|
41
|
+
child.kill();
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
outputStream.setEncoding('utf8');
|
|
45
|
+
outputStream.on('data', chunk => {
|
|
46
|
+
try {
|
|
47
|
+
options.outputPile?.onData(String(chunk));
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
outputPileError = e instanceof Error ? e : new Error(String(e));
|
|
51
|
+
child.kill();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
outputStream.on('error', error => {
|
|
55
|
+
outputPileError = new Error('promptpile output fd ' + options.outputPile?.fd + ' error: ' + error.message);
|
|
56
|
+
child.kill();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
child.on('error', error => {
|
|
61
|
+
spawnError = error;
|
|
62
|
+
});
|
|
63
|
+
child.on('close', status => {
|
|
64
|
+
resolveOnce({ status, stdout, stderr, error: outputPileError ?? spawnError });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function stopChild(child) {
|
|
69
|
+
return new Promise(resolve => {
|
|
70
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
71
|
+
return resolve();
|
|
72
|
+
const timer = setTimeout(() => { if (child.exitCode === null)
|
|
73
|
+
child.kill('SIGKILL'); }, 3000);
|
|
74
|
+
child.once('close', () => { clearTimeout(timer); resolve(); });
|
|
75
|
+
child.kill('SIGTERM');
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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.projectRevisePayload = projectRevisePayload;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function projectRevisePayload(payload, worldRoot) {
|
|
10
|
+
const changes = new Map();
|
|
11
|
+
const characterIds = new Set(readIndexIds(worldRoot, 'characters'));
|
|
12
|
+
const sceneIds = new Set(readIndexIds(worldRoot, 'scenes'));
|
|
13
|
+
const put = (relativePath, content) => { changes.set(relativePath, { relativePath, content }); };
|
|
14
|
+
const putIfMissing = (relativePath, content) => {
|
|
15
|
+
if (!worldRoot || !fs_1.default.existsSync(path_1.default.join(worldRoot, relativePath)))
|
|
16
|
+
put(relativePath, content);
|
|
17
|
+
};
|
|
18
|
+
for (const operation of payload.operations) {
|
|
19
|
+
if (operation.op === 'replace_canon') {
|
|
20
|
+
put(`canon/${operation.section}.md`, operation.content);
|
|
21
|
+
}
|
|
22
|
+
else if (operation.op === 'upsert_character') {
|
|
23
|
+
characterIds.add(operation.id);
|
|
24
|
+
const dir = `characters/${operation.id}`;
|
|
25
|
+
put(`${dir}/profile.md`, operation.profileMd);
|
|
26
|
+
if (operation.relationshipsMd !== undefined)
|
|
27
|
+
put(`${dir}/relationships.md`, operation.relationshipsMd);
|
|
28
|
+
else
|
|
29
|
+
putIfMissing(`${dir}/relationships.md`, '# Relationships\n\n');
|
|
30
|
+
put(`${dir}/meta.yaml`, entityMetaYaml(operation.id, 'character', operation.meta));
|
|
31
|
+
putIfMissing(`${dir}/memory.md`, '');
|
|
32
|
+
putIfMissing(`${dir}/timeline.md`, '');
|
|
33
|
+
}
|
|
34
|
+
else if (operation.op === 'upsert_scene') {
|
|
35
|
+
sceneIds.add(operation.id);
|
|
36
|
+
const dir = `scenes/${operation.id}`;
|
|
37
|
+
put(`${dir}/profile.md`, operation.profileMd);
|
|
38
|
+
put(`${dir}/meta.yaml`, entityMetaYaml(operation.id, 'scene', operation.meta));
|
|
39
|
+
putIfMissing(`${dir}/memory.md`, '');
|
|
40
|
+
putIfMissing(`${dir}/triggers.yaml`, 'triggers: []\n');
|
|
41
|
+
putIfMissing(`${dir}/timeline.md`, '');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if ([...payload.operations].some(op => op.op === 'upsert_character'))
|
|
45
|
+
put('characters/index.yaml', yamlIdList('characters', [...characterIds]));
|
|
46
|
+
if ([...payload.operations].some(op => op.op === 'upsert_scene'))
|
|
47
|
+
put('scenes/index.yaml', yamlIdList('scenes', [...sceneIds]));
|
|
48
|
+
return [...changes.values()];
|
|
49
|
+
}
|
|
50
|
+
function readIndexIds(worldRoot, key) {
|
|
51
|
+
if (!worldRoot)
|
|
52
|
+
return [];
|
|
53
|
+
const filePath = path_1.default.join(worldRoot, key, 'index.yaml');
|
|
54
|
+
if (!fs_1.default.existsSync(filePath))
|
|
55
|
+
return [];
|
|
56
|
+
return fs_1.default.readFileSync(filePath, 'utf8').split(/\r?\n/).map(line => line.match(/^\s*-\s+(.+?)\s*$/)?.[1]).filter((id) => Boolean(id)).map(id => id.replace(/^['"]|['"]$/g, ''));
|
|
57
|
+
}
|
|
58
|
+
function yamlIdList(key, ids) { return `${key}:\n${ids.map(id => ` - ${JSON.stringify(id)}`).join('\n')}\n`; }
|
|
59
|
+
function entityMetaYaml(id, type, meta) {
|
|
60
|
+
const tags = meta?.tags ?? [];
|
|
61
|
+
return [`id: ${JSON.stringify(id)}`, `type: ${type}`, `status: ${JSON.stringify(meta?.status ?? 'active')}`, tags.length ? `tags:\n${tags.map(tag => ` - ${JSON.stringify(tag)}`).join('\n')}` : 'tags: []', ''].join('\n');
|
|
62
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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.runPromptpileUntilText = runPromptpileUntilText;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const bin_resolve_1 = require("./bin-resolve");
|
|
9
|
+
const mcp_tools_1 = require("./mcp-tools");
|
|
10
|
+
const session_1 = require("./session");
|
|
11
|
+
const run_promptpile_with_stream_1 = require("../shared/run-promptpile-with-stream");
|
|
12
|
+
async function runPromptpileUntilText(session, baseUrl, token, maxToolRounds, onDelta = () => undefined) {
|
|
13
|
+
for (let round = 0; round <= maxToolRounds; round += 1) {
|
|
14
|
+
const spawnConfig = (0, bin_resolve_1.getPromptpileSpawnConfig)();
|
|
15
|
+
const result = await (0, run_promptpile_with_stream_1.runPromptpileWithStream)({
|
|
16
|
+
command: spawnConfig.command,
|
|
17
|
+
args: [
|
|
18
|
+
...spawnConfig.argvPrefix,
|
|
19
|
+
'--config', path_1.default.basename(session.promptpileConfig),
|
|
20
|
+
'-d', 'messages',
|
|
21
|
+
'--tools-file', path_1.default.basename(session.toolsFile),
|
|
22
|
+
'--continue',
|
|
23
|
+
'--quiet'
|
|
24
|
+
],
|
|
25
|
+
cwd: session.root,
|
|
26
|
+
quiet: true,
|
|
27
|
+
onDelta
|
|
28
|
+
});
|
|
29
|
+
if (result.error)
|
|
30
|
+
throw new Error('Failed to run ' + spawnConfig.displayName + ': ' + result.error.message);
|
|
31
|
+
if (result.status !== 0)
|
|
32
|
+
throw new Error('promptpile exited with code ' + result.status + ': ' + result.stderr.trim().slice(-500));
|
|
33
|
+
const callsFile = (0, session_1.getLatestCallsFile)(session.messagesDir);
|
|
34
|
+
if (!callsFile)
|
|
35
|
+
return (0, session_1.getLatestAssistantText)(session.messagesDir);
|
|
36
|
+
if (round === maxToolRounds)
|
|
37
|
+
throw new Error('AI exceeded the tool-call limit (' + maxToolRounds + ') for this turn');
|
|
38
|
+
await (0, mcp_tools_1.executeReadonlyCalls)(baseUrl, token, callsFile);
|
|
39
|
+
}
|
|
40
|
+
throw new Error('Unexpected promptpile loop exit');
|
|
41
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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.loadRevisePrompt = loadRevisePrompt;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const PROMPTS_DIR = path_1.default.resolve(__dirname, '../../prompts');
|
|
10
|
+
function loadRevisePrompt(name) {
|
|
11
|
+
const filePath = path_1.default.join(PROMPTS_DIR, `${name}.system.md`);
|
|
12
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
13
|
+
throw new Error(`Prompt not found: ${filePath}`);
|
|
14
|
+
}
|
|
15
|
+
return fs_1.default.readFileSync(filePath, 'utf8');
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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.readReviseUserInput = readReviseUserInput;
|
|
7
|
+
exports.askYesNo = askYesNo;
|
|
8
|
+
const readline_1 = __importDefault(require("readline"));
|
|
9
|
+
async function readReviseUserInput() {
|
|
10
|
+
while (true) {
|
|
11
|
+
const text = (await readMultilineInput()).trim();
|
|
12
|
+
if (text)
|
|
13
|
+
return text;
|
|
14
|
+
if (await askYesNo('Empty input. Save draft and exit revise session? (Y/N): '))
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function askYesNo(question) {
|
|
19
|
+
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
20
|
+
return new Promise(resolve => {
|
|
21
|
+
rl.question(question, answer => {
|
|
22
|
+
rl.close();
|
|
23
|
+
resolve(/^y(es)?$/i.test(answer.trim()));
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async function readMultilineInput() {
|
|
28
|
+
process.stdout.write('\nEnter your message. Finish with Ctrl+Z then Enter (Windows), or Ctrl+D (macOS/Linux).\n');
|
|
29
|
+
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
30
|
+
const lines = [];
|
|
31
|
+
for await (const line of rl)
|
|
32
|
+
lines.push(line);
|
|
33
|
+
rl.close();
|
|
34
|
+
return lines.join('\n');
|
|
35
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
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.createReviseSession = createReviseSession;
|
|
7
|
+
exports.createFinalizeSession = createFinalizeSession;
|
|
8
|
+
exports.scanMessageIndices = scanMessageIndices;
|
|
9
|
+
exports.appendUserMessage = appendUserMessage;
|
|
10
|
+
exports.getLatestAssistantText = getLatestAssistantText;
|
|
11
|
+
exports.getLatestCallsFile = getLatestCallsFile;
|
|
12
|
+
exports.buildTranscript = buildTranscript;
|
|
13
|
+
exports.readDraft = readDraft;
|
|
14
|
+
exports.writeDraft = writeDraft;
|
|
15
|
+
exports.cleanupSession = cleanupSession;
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const os_1 = __importDefault(require("os"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const constants_1 = require("./constants");
|
|
20
|
+
const prompts_1 = require("./prompts");
|
|
21
|
+
const MESSAGE_PATTERN = /^\[(\d+)\](.+)\.(md|json)$/i;
|
|
22
|
+
function promptpileToml() {
|
|
23
|
+
return `[[llm_api]]
|
|
24
|
+
name = "deepseek"
|
|
25
|
+
model = "deepseek-chat"
|
|
26
|
+
base_url = "https://api.deepseek.com/v1"
|
|
27
|
+
api_key_env = "DEEPSEEK_API_KEY"
|
|
28
|
+
|
|
29
|
+
[promptpile]
|
|
30
|
+
llm_api = "deepseek"
|
|
31
|
+
dir = "./messages"
|
|
32
|
+
tools_file = "./readonly.tools.toml"
|
|
33
|
+
quiet = true
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
function createReviseSession() {
|
|
37
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'dayloom-revise-'));
|
|
38
|
+
const messagesDir = path_1.default.join(root, 'messages');
|
|
39
|
+
const toolsFile = path_1.default.join(root, 'readonly.tools.toml');
|
|
40
|
+
const promptpileConfig = path_1.default.join(root, 'promptpile.toml');
|
|
41
|
+
const draftFile = path_1.default.join(root, 'draft.json');
|
|
42
|
+
fs_1.default.mkdirSync(messagesDir, { recursive: true });
|
|
43
|
+
fs_1.default.writeFileSync(promptpileConfig, promptpileToml(), 'utf8');
|
|
44
|
+
fs_1.default.writeFileSync(path_1.default.join(messagesDir, '[0]system.md'), (0, prompts_1.loadRevisePrompt)('revise-dialogue'), 'utf8');
|
|
45
|
+
fs_1.default.writeFileSync(path_1.default.join(messagesDir, '[1]assistant.md'), constants_1.OPENING_ASSISTANT, 'utf8');
|
|
46
|
+
writeDraft({ root, messagesDir, toolsFile, promptpileConfig, draftFile }, { pending_changes: [] });
|
|
47
|
+
return { root, messagesDir, toolsFile, promptpileConfig, draftFile };
|
|
48
|
+
}
|
|
49
|
+
function createFinalizeSession(transcript, draft) {
|
|
50
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'dayloom-revise-finalize-'));
|
|
51
|
+
const messagesDir = path_1.default.join(root, 'messages');
|
|
52
|
+
const toolsFile = path_1.default.join(root, 'readonly.tools.toml');
|
|
53
|
+
const promptpileConfig = path_1.default.join(root, 'promptpile.toml');
|
|
54
|
+
const draftFile = path_1.default.join(root, 'draft.json');
|
|
55
|
+
fs_1.default.mkdirSync(messagesDir, { recursive: true });
|
|
56
|
+
fs_1.default.writeFileSync(promptpileConfig, promptpileToml(), 'utf8');
|
|
57
|
+
fs_1.default.writeFileSync(path_1.default.join(messagesDir, '[0]system.md'), (0, prompts_1.loadRevisePrompt)('revise-finalize'), 'utf8');
|
|
58
|
+
fs_1.default.writeFileSync(path_1.default.join(messagesDir, '[1]user.md'), `# Transcript\n\n${transcript}\n\n# Pending changes\n\n${JSON.stringify(draft, null, 2)}\n`, 'utf8');
|
|
59
|
+
fs_1.default.writeFileSync(path_1.default.join(messagesDir, '[2]user.md'), '请生成最终 revise-payload。', 'utf8');
|
|
60
|
+
writeDraft({ root, messagesDir, toolsFile, promptpileConfig, draftFile }, draft);
|
|
61
|
+
return { root, messagesDir, toolsFile, promptpileConfig, draftFile };
|
|
62
|
+
}
|
|
63
|
+
function scanMessageIndices(messagesDir) {
|
|
64
|
+
const indices = new Set();
|
|
65
|
+
for (const name of fs_1.default.readdirSync(messagesDir)) {
|
|
66
|
+
const m = name.match(MESSAGE_PATTERN);
|
|
67
|
+
if (m)
|
|
68
|
+
indices.add(parseInt(m[1], 10));
|
|
69
|
+
}
|
|
70
|
+
return [...indices].sort((a, b) => a - b);
|
|
71
|
+
}
|
|
72
|
+
function appendUserMessage(messagesDir, content) {
|
|
73
|
+
const indices = scanMessageIndices(messagesDir);
|
|
74
|
+
const next = indices.length ? Math.max(...indices) + 1 : 0;
|
|
75
|
+
const filePath = path_1.default.join(messagesDir, `[${next}]user.md`);
|
|
76
|
+
fs_1.default.writeFileSync(filePath, content, 'utf8');
|
|
77
|
+
return filePath;
|
|
78
|
+
}
|
|
79
|
+
function getLatestAssistantText(messagesDir) {
|
|
80
|
+
const files = fs_1.default.readdirSync(messagesDir)
|
|
81
|
+
.filter(name => /^\[\d+\]assistant\.md$/i.test(name))
|
|
82
|
+
.sort((a, b) => Number(a.match(/^\[(\d+)\]/)[1]) - Number(b.match(/^\[(\d+)\]/)[1]));
|
|
83
|
+
if (!files.length)
|
|
84
|
+
throw new Error('No assistant message found in revise session');
|
|
85
|
+
return fs_1.default.readFileSync(path_1.default.join(messagesDir, files[files.length - 1]), 'utf8');
|
|
86
|
+
}
|
|
87
|
+
function getLatestCallsFile(messagesDir) {
|
|
88
|
+
const files = fs_1.default.readdirSync(messagesDir)
|
|
89
|
+
.filter(name => /^\[\d+\]assistant\.calls\.jsonl$/i.test(name))
|
|
90
|
+
.sort((a, b) => Number(a.match(/^\[(\d+)\]/)[1]) - Number(b.match(/^\[(\d+)\]/)[1]));
|
|
91
|
+
if (!files.length)
|
|
92
|
+
return undefined;
|
|
93
|
+
const latest = path_1.default.join(messagesDir, files[files.length - 1]);
|
|
94
|
+
const result = latest.replace(/\.calls\.jsonl$/i, '.result.jsonl');
|
|
95
|
+
return fs_1.default.existsSync(result) ? undefined : latest;
|
|
96
|
+
}
|
|
97
|
+
function buildTranscript(messagesDir) {
|
|
98
|
+
return fs_1.default.readdirSync(messagesDir)
|
|
99
|
+
.map(name => {
|
|
100
|
+
const m = name.match(MESSAGE_PATTERN);
|
|
101
|
+
if (!m)
|
|
102
|
+
return null;
|
|
103
|
+
return { idx: Number(m[1]), role: m[2], content: fs_1.default.readFileSync(path_1.default.join(messagesDir, name), 'utf8') };
|
|
104
|
+
})
|
|
105
|
+
.filter((x) => x !== null)
|
|
106
|
+
.sort((a, b) => a.idx - b.idx || a.role.localeCompare(b.role))
|
|
107
|
+
.map(x => `## [${x.idx}] ${x.role}\n\n${x.content}`)
|
|
108
|
+
.join('\n\n');
|
|
109
|
+
}
|
|
110
|
+
function readDraft(session) {
|
|
111
|
+
return JSON.parse(fs_1.default.readFileSync(session.draftFile, 'utf8'));
|
|
112
|
+
}
|
|
113
|
+
function writeDraft(session, draft) {
|
|
114
|
+
fs_1.default.writeFileSync(session.draftFile, `${JSON.stringify(draft, null, 2)}\n`, 'utf8');
|
|
115
|
+
}
|
|
116
|
+
function cleanupSession(session) {
|
|
117
|
+
if (fs_1.default.existsSync(session.root))
|
|
118
|
+
fs_1.default.rmSync(session.root, { recursive: true, force: true });
|
|
119
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateRevisePayload = validateRevisePayload;
|
|
4
|
+
const CANON_SECTIONS = new Set(['premise', 'rules', 'style', 'user_role']);
|
|
5
|
+
const SNAKE_CASE = /^[a-z][a-z0-9_]*$/;
|
|
6
|
+
function validateRevisePayload(payload) {
|
|
7
|
+
if (!payload || typeof payload !== 'object')
|
|
8
|
+
throw new Error('Revise payload must be an object');
|
|
9
|
+
if (typeof payload.summary !== 'string' || !payload.summary.trim())
|
|
10
|
+
throw new Error('Revise payload summary must be a non-empty string');
|
|
11
|
+
if (!Array.isArray(payload.operations) || payload.operations.length === 0)
|
|
12
|
+
throw new Error('Revise payload operations must be a non-empty array');
|
|
13
|
+
payload.operations.forEach((operation, index) => {
|
|
14
|
+
if (!operation || typeof operation !== 'object')
|
|
15
|
+
throw new Error(`Revise operation ${index} must be an object`);
|
|
16
|
+
if (operation.op === 'replace_canon') {
|
|
17
|
+
if (!CANON_SECTIONS.has(operation.section))
|
|
18
|
+
throw new Error(`Unsupported canon section in operation ${index}: ${String(operation.section)}`);
|
|
19
|
+
assertNonEmpty(operation.content, `Revise operation ${index} content`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (operation.op === 'upsert_character' || operation.op === 'upsert_scene') {
|
|
23
|
+
if (!SNAKE_CASE.test(operation.id))
|
|
24
|
+
throw new Error(`Invalid entity id in operation ${index}: ${String(operation.id)}`);
|
|
25
|
+
assertNonEmpty(operation.profileMd, `Revise operation ${index} profileMd`);
|
|
26
|
+
if (operation.op === 'upsert_character' && operation.relationshipsMd !== undefined)
|
|
27
|
+
assertNonEmpty(operation.relationshipsMd, `Revise operation ${index} relationshipsMd`);
|
|
28
|
+
validateMeta(operation.meta, index);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`Unsupported revise operation ${index}: ${String(operation.op)}`);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function assertNonEmpty(value, label) {
|
|
35
|
+
if (typeof value !== 'string' || !value.trim())
|
|
36
|
+
throw new Error(`${label} must be non-empty`);
|
|
37
|
+
}
|
|
38
|
+
function validateMeta(meta, index) {
|
|
39
|
+
if (meta === undefined)
|
|
40
|
+
return;
|
|
41
|
+
if (!meta || typeof meta !== 'object' || Array.isArray(meta))
|
|
42
|
+
throw new Error(`Revise operation ${index} meta must be an object`);
|
|
43
|
+
if (meta.status !== undefined)
|
|
44
|
+
assertNonEmpty(meta.status, `Revise operation ${index} meta.status`);
|
|
45
|
+
if (meta.tags !== undefined && (!Array.isArray(meta.tags) || meta.tags.some(tag => typeof tag !== 'string')))
|
|
46
|
+
throw new Error(`Revise operation ${index} meta.tags must be strings`);
|
|
47
|
+
}
|
|
@@ -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.callSettlementAi = callSettlementAi;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const promptpile_loop_1 = require("../daily/promptpile-loop");
|
|
10
|
+
const session_1 = require("./session");
|
|
11
|
+
async function callSettlementAi(userContent, toolsFile, baseUrl, token, maxToolRounds, keepSession = false) {
|
|
12
|
+
const prompt = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '..', '..', 'prompts', 'settle.system.md'), 'utf8');
|
|
13
|
+
const session = (0, session_1.createSettlementSession)(prompt, userContent, toolsFile);
|
|
14
|
+
try {
|
|
15
|
+
return await (0, promptpile_loop_1.runPromptpileUntilText)(session, baseUrl, token, maxToolRounds);
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
if (keepSession)
|
|
19
|
+
process.stderr.write(`Settlement AI session preserved at: ${session.root}\n`);
|
|
20
|
+
else
|
|
21
|
+
(0, session_1.cleanupSettlementSession)(session);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -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.applySettlement = applySettlement;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function applySettlement(worldRoot, proposal, changes, nextDay) {
|
|
10
|
+
const workRoot = path_1.default.join(worldRoot, '.loom', 'settle-transaction', proposal.day);
|
|
11
|
+
const stagingRoot = path_1.default.join(workRoot, 'staged');
|
|
12
|
+
const backupRoot = path_1.default.join(workRoot, 'before');
|
|
13
|
+
const createdFiles = [];
|
|
14
|
+
const committedFiles = [];
|
|
15
|
+
fs_1.default.rmSync(workRoot, { recursive: true, force: true });
|
|
16
|
+
try {
|
|
17
|
+
for (const change of changes) {
|
|
18
|
+
const target = path_1.default.join(worldRoot, change.relativePath);
|
|
19
|
+
const staged = path_1.default.join(stagingRoot, change.relativePath);
|
|
20
|
+
fs_1.default.mkdirSync(path_1.default.dirname(staged), { recursive: true });
|
|
21
|
+
fs_1.default.writeFileSync(staged, change.content, 'utf8');
|
|
22
|
+
if (fs_1.default.existsSync(target)) {
|
|
23
|
+
const backup = path_1.default.join(backupRoot, change.relativePath);
|
|
24
|
+
fs_1.default.mkdirSync(path_1.default.dirname(backup), { recursive: true });
|
|
25
|
+
fs_1.default.copyFileSync(target, backup);
|
|
26
|
+
}
|
|
27
|
+
else
|
|
28
|
+
createdFiles.push(change.relativePath);
|
|
29
|
+
}
|
|
30
|
+
const ordered = [...changes.filter(change => change.relativePath !== 'current.yaml'), ...changes.filter(change => change.relativePath === 'current.yaml')];
|
|
31
|
+
for (const change of ordered) {
|
|
32
|
+
commitFile(stagingRoot, worldRoot, change.relativePath);
|
|
33
|
+
committedFiles.push(change.relativePath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
rollback(worldRoot, backupRoot, committedFiles, new Set(createdFiles));
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
fs_1.default.rmSync(workRoot, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function commitFile(stagingRoot, worldRoot, relativePath) {
|
|
45
|
+
const staged = path_1.default.join(stagingRoot, relativePath);
|
|
46
|
+
const target = path_1.default.join(worldRoot, relativePath);
|
|
47
|
+
fs_1.default.mkdirSync(path_1.default.dirname(target), { recursive: true });
|
|
48
|
+
fs_1.default.renameSync(staged, target);
|
|
49
|
+
}
|
|
50
|
+
function rollback(worldRoot, backupRoot, committedFiles, createdFiles) {
|
|
51
|
+
for (const relativePath of committedFiles.reverse()) {
|
|
52
|
+
const target = path_1.default.join(worldRoot, relativePath);
|
|
53
|
+
if (createdFiles.has(relativePath))
|
|
54
|
+
fs_1.default.rmSync(target, { force: true });
|
|
55
|
+
else
|
|
56
|
+
fs_1.default.copyFileSync(path_1.default.join(backupRoot, relativePath), target);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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.buildSettlementPlayerContext = buildSettlementPlayerContext;
|
|
7
|
+
exports.buildSettlementPromptInput = buildSettlementPromptInput;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const player_context_1 = require("../daily/player-context");
|
|
11
|
+
const TRANSCRIPT_TAIL = 6000;
|
|
12
|
+
function buildSettlementPlayerContext(worldRoot, day, outputRoot) {
|
|
13
|
+
(0, player_context_1.buildPlayerContext)(worldRoot, outputRoot);
|
|
14
|
+
const today = path_1.default.join(outputRoot, 'today');
|
|
15
|
+
fs_1.default.mkdirSync(today, { recursive: true });
|
|
16
|
+
for (const name of ['plan.user.md', 'plan.initial.json', 'plan.current.json', 'play.state.json', 'runtime.state.json']) {
|
|
17
|
+
copyIfExists(path_1.default.join(worldRoot, 'days', day, name), path_1.default.join(today, name));
|
|
18
|
+
}
|
|
19
|
+
const eventsSource = path_1.default.join(worldRoot, 'days', day, 'events');
|
|
20
|
+
const eventsTarget = path_1.default.join(today, 'events');
|
|
21
|
+
if (!fs_1.default.existsSync(eventsSource))
|
|
22
|
+
return;
|
|
23
|
+
for (const eventId of fs_1.default.readdirSync(eventsSource).sort()) {
|
|
24
|
+
for (const name of ['event.json', 'status.json', 'result.json']) {
|
|
25
|
+
copyIfExists(path_1.default.join(eventsSource, eventId, name), path_1.default.join(eventsTarget, eventId, name));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function buildSettlementPromptInput(worldRoot, day) {
|
|
30
|
+
const dayRoot = path_1.default.join(worldRoot, 'days', day);
|
|
31
|
+
const sections = [
|
|
32
|
+
section('Current day', day),
|
|
33
|
+
fileSection('User plan', path_1.default.join(dayRoot, 'plan.user.md')),
|
|
34
|
+
fileSection('Final plan state', path_1.default.join(dayRoot, 'plan.current.json')),
|
|
35
|
+
fileSection('Runtime state', path_1.default.join(dayRoot, 'runtime.state.json')),
|
|
36
|
+
fileSection('Existing short-term memory', path_1.default.join(worldRoot, 'memory', 'short_term.md')),
|
|
37
|
+
fileSection('Existing unresolved threads', path_1.default.join(worldRoot, 'memory', 'unresolved_threads.yaml')),
|
|
38
|
+
];
|
|
39
|
+
const eventsRoot = path_1.default.join(dayRoot, 'events');
|
|
40
|
+
if (fs_1.default.existsSync(eventsRoot)) {
|
|
41
|
+
for (const eventId of fs_1.default.readdirSync(eventsRoot).sort()) {
|
|
42
|
+
const eventRoot = path_1.default.join(eventsRoot, eventId);
|
|
43
|
+
const eventParts = [
|
|
44
|
+
fileSection('Event definition', path_1.default.join(eventRoot, 'event.json')),
|
|
45
|
+
fileSection('Final status', path_1.default.join(eventRoot, 'status.json')),
|
|
46
|
+
fileSection('Event result', path_1.default.join(eventRoot, 'result.json')),
|
|
47
|
+
];
|
|
48
|
+
const transcriptPath = path_1.default.join(eventRoot, 'transcript.md');
|
|
49
|
+
if (fs_1.default.existsSync(transcriptPath)) {
|
|
50
|
+
const transcript = fs_1.default.readFileSync(transcriptPath, 'utf8');
|
|
51
|
+
eventParts.push(section('Transcript tail', transcript.slice(-TRANSCRIPT_TAIL)));
|
|
52
|
+
}
|
|
53
|
+
sections.push(section(`Event ${eventId}`, eventParts.filter(Boolean).join('\n\n')));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return sections.filter(Boolean).join('\n\n');
|
|
57
|
+
}
|
|
58
|
+
function copyIfExists(source, target) {
|
|
59
|
+
if (!fs_1.default.existsSync(source))
|
|
60
|
+
return;
|
|
61
|
+
fs_1.default.mkdirSync(path_1.default.dirname(target), { recursive: true });
|
|
62
|
+
fs_1.default.copyFileSync(source, target);
|
|
63
|
+
}
|
|
64
|
+
function fileSection(title, filePath) {
|
|
65
|
+
return fs_1.default.existsSync(filePath) ? section(title, fs_1.default.readFileSync(filePath, 'utf8')) : '';
|
|
66
|
+
}
|
|
67
|
+
function section(title, content) {
|
|
68
|
+
return `# ${title}\n\n${content.trim()}`;
|
|
69
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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.buildProgramSettlementProposal = buildProgramSettlementProposal;
|
|
7
|
+
exports.deriveStatePatch = deriveStatePatch;
|
|
8
|
+
exports.readUnresolvedThreads = readUnresolvedThreads;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
function buildProgramSettlementProposal(worldRoot, day, narrative) {
|
|
12
|
+
return {
|
|
13
|
+
version: 1,
|
|
14
|
+
day,
|
|
15
|
+
summary: narrative.summary.trim(),
|
|
16
|
+
diary: narrative.diary.trim(),
|
|
17
|
+
state_patch: deriveStatePatch(worldRoot, day, narrative.summary),
|
|
18
|
+
next_day_seed: {
|
|
19
|
+
summary: narrative.next_day_seed.summary.trim(),
|
|
20
|
+
suggested_intents: narrative.next_day_seed.suggested_intents.map(value => value.trim()),
|
|
21
|
+
unresolved_threads: readUnresolvedThreads(worldRoot),
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function deriveStatePatch(worldRoot, day, summary) {
|
|
26
|
+
const shortTerm = path_1.default.join(worldRoot, 'memory', 'short_term.md');
|
|
27
|
+
if (!fs_1.default.existsSync(shortTerm))
|
|
28
|
+
return [];
|
|
29
|
+
return [{ op: 'append', path: 'memory/short_term.md', content: `## ${day}\n\n${summary.trim()}` }];
|
|
30
|
+
}
|
|
31
|
+
function readUnresolvedThreads(worldRoot) {
|
|
32
|
+
const filePath = path_1.default.join(worldRoot, 'memory', 'unresolved_threads.yaml');
|
|
33
|
+
if (!fs_1.default.existsSync(filePath))
|
|
34
|
+
return [];
|
|
35
|
+
const text = fs_1.default.readFileSync(filePath, 'utf8');
|
|
36
|
+
if (/^threads:\s*\[\s*\]\s*$/m.test(text))
|
|
37
|
+
return [];
|
|
38
|
+
const lines = text.split(/\r?\n/);
|
|
39
|
+
const values = [];
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
const id = line.match(/^\s*-\s+id:\s*(.+?)\s*$/)?.[1]?.trim();
|
|
42
|
+
if (id) {
|
|
43
|
+
values.push(unquote(id));
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const scalar = line.match(/^\s*-\s+([^:#][^:#]*)\s*$/)?.[1]?.trim();
|
|
47
|
+
if (scalar)
|
|
48
|
+
values.push(unquote(scalar));
|
|
49
|
+
}
|
|
50
|
+
return [...new Set(values)];
|
|
51
|
+
}
|
|
52
|
+
function unquote(value) {
|
|
53
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
|
|
54
|
+
return value.slice(1, -1);
|
|
55
|
+
return value;
|
|
56
|
+
}
|