@dsh-agentic-roleplay/state 0.1.1 → 0.3.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 +7 -1
- package/lib/index.d.ts +162 -9
- package/lib/index.js +509 -66
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { DatabaseSync } from "node:sqlite";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { Service } from "@deepseek-ai/cordis";
|
|
5
5
|
import z from "@deepseek-ai/schemastery";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
@@ -346,6 +346,8 @@ const COMPARISONS = [
|
|
|
346
346
|
"gt",
|
|
347
347
|
"ge"
|
|
348
348
|
];
|
|
349
|
+
/** In a cue, the key of the character it is said to: in a `while` condition and in the words of the cue. */
|
|
350
|
+
const SELF = "@self";
|
|
349
351
|
const TRIGGER_STATUSES = [
|
|
350
352
|
"dormant",
|
|
351
353
|
"fired",
|
|
@@ -835,14 +837,14 @@ function parseInstant(text, zone) {
|
|
|
835
837
|
/** `Tuesday 4 October 1912, 21:40`. */
|
|
836
838
|
function describeInstant(instant, zone) {
|
|
837
839
|
const local = localTime(instant, zone);
|
|
838
|
-
return `${WEEKDAYS[local.weekday]} ${local.day} ${MONTHS[local.month - 1]} ${local.year}, ${pad(local.hour)}:${pad(local.minute)}`;
|
|
840
|
+
return `${WEEKDAYS[local.weekday]} ${local.day} ${MONTHS[local.month - 1]} ${local.year}, ${pad$1(local.hour)}:${pad$1(local.minute)}`;
|
|
839
841
|
}
|
|
840
842
|
/** `1912-10-04T21:40:00` as the wall clock of a zone reads, the form a file or a person can write back. */
|
|
841
843
|
function localIso(instant, zone) {
|
|
842
844
|
const local = localTime(instant, zone);
|
|
843
|
-
return `${String(local.year).padStart(4, "0")}-${pad(local.month)}-${pad(local.day)}T${pad(local.hour)}:${pad(local.minute)}:${pad(local.second)}`;
|
|
845
|
+
return `${String(local.year).padStart(4, "0")}-${pad$1(local.month)}-${pad$1(local.day)}T${pad$1(local.hour)}:${pad$1(local.minute)}:${pad$1(local.second)}`;
|
|
844
846
|
}
|
|
845
|
-
const pad = (value) => String(value).padStart(2, "0");
|
|
847
|
+
const pad$1 = (value) => String(value).padStart(2, "0");
|
|
846
848
|
const DAY_PARTS = [
|
|
847
849
|
"night",
|
|
848
850
|
"morning",
|
|
@@ -946,7 +948,7 @@ function catchUpPlan(cursor, now, limits = {}) {
|
|
|
946
948
|
/** `21:40`: the clock of a zone at an instant. */
|
|
947
949
|
function clockTime(instant, zone) {
|
|
948
950
|
const local = localTime(instant, zone);
|
|
949
|
-
return `${pad(local.hour)}:${pad(local.minute)}`;
|
|
951
|
+
return `${pad$1(local.hour)}:${pad$1(local.minute)}`;
|
|
950
952
|
}
|
|
951
953
|
/** The local calendar day `days` after the one the instant falls on, as year, month and day. */
|
|
952
954
|
function addLocalDays(instant, zone, days) {
|
|
@@ -964,7 +966,7 @@ function addLocalDays(instant, zone, days) {
|
|
|
964
966
|
};
|
|
965
967
|
}
|
|
966
968
|
/** `07:30` for a minute of the day. */
|
|
967
|
-
const minuteText = (minute) => `${pad(Math.floor(minute / 60))}:${pad(minute % 60)}`;
|
|
969
|
+
const minuteText = (minute) => `${pad$1(Math.floor(minute / 60))}:${pad$1(minute % 60)}`;
|
|
968
970
|
//#endregion
|
|
969
971
|
//#region src/records/agenda-edit.ts
|
|
970
972
|
const MAX_PLAN_STEPS = 8;
|
|
@@ -1664,6 +1666,78 @@ function moveCharacter(context, characterId, move) {
|
|
|
1664
1666
|
return result.plan;
|
|
1665
1667
|
}
|
|
1666
1668
|
//#endregion
|
|
1669
|
+
//#region src/records/template.ts
|
|
1670
|
+
/**
|
|
1671
|
+
* What the words of a cue or a beat may name by `{kind:key}`, filled in from the story when they are told. The key is how the world's files
|
|
1672
|
+
* name the thing (`@self` in a cue is the character it is said to), and a name follows the story: a renamed character is renamed in the words.
|
|
1673
|
+
*/
|
|
1674
|
+
const TEMPLATE_KINDS = {
|
|
1675
|
+
/** The name of a character. */
|
|
1676
|
+
character: {
|
|
1677
|
+
args: 1,
|
|
1678
|
+
about: "a character"
|
|
1679
|
+
},
|
|
1680
|
+
/** The name of a place. */
|
|
1681
|
+
place: {
|
|
1682
|
+
args: 1,
|
|
1683
|
+
about: "a place"
|
|
1684
|
+
},
|
|
1685
|
+
/** The name of an item. */
|
|
1686
|
+
item: {
|
|
1687
|
+
args: 1,
|
|
1688
|
+
about: "an item"
|
|
1689
|
+
},
|
|
1690
|
+
/** The name of the place a character is in now. */
|
|
1691
|
+
where: {
|
|
1692
|
+
args: 1,
|
|
1693
|
+
about: "a character"
|
|
1694
|
+
},
|
|
1695
|
+
/** Who holds an item now, or the place it lies in. */
|
|
1696
|
+
holder: {
|
|
1697
|
+
args: 1,
|
|
1698
|
+
about: "an item"
|
|
1699
|
+
},
|
|
1700
|
+
/** What a variable is now, in words: `{value:key}` for a world variable, `{value:key:subject}` for the character, item or place it is about. */
|
|
1701
|
+
value: {
|
|
1702
|
+
args: 2,
|
|
1703
|
+
about: "a variable"
|
|
1704
|
+
},
|
|
1705
|
+
/** The time of day in the story, as `19:30`. */
|
|
1706
|
+
time: {
|
|
1707
|
+
args: 0,
|
|
1708
|
+
about: ""
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
const PATTERN = /\{([a-z]+)(?::([^{}]*))?\}/g;
|
|
1712
|
+
/** The placeholders in some words, and what is wrong with any that is not one. Anything that does not look like `{word}` or `{word:key}` is plain text. */
|
|
1713
|
+
function placeholdersOf(text) {
|
|
1714
|
+
const found = [];
|
|
1715
|
+
const problems = [];
|
|
1716
|
+
for (const match of text.matchAll(PATTERN)) {
|
|
1717
|
+
const [raw, word, rest] = match;
|
|
1718
|
+
if (!(word in TEMPLATE_KINDS)) {
|
|
1719
|
+
problems.push(`${raw}: there is no "${word}"; the words may name ${Object.keys(TEMPLATE_KINDS).join(", ")}`);
|
|
1720
|
+
continue;
|
|
1721
|
+
}
|
|
1722
|
+
const kind = word;
|
|
1723
|
+
const args = rest === void 0 ? [] : rest.split(":").map((part) => part.trim());
|
|
1724
|
+
const { args: wanted } = TEMPLATE_KINDS[kind];
|
|
1725
|
+
if (!(kind === "value" ? args.length >= 1 && args.length <= 2 : args.length === wanted) || args.some((part) => part === "")) {
|
|
1726
|
+
problems.push(`${raw}: ${kind === "time" ? "{time} takes nothing" : kind === "value" ? "{value:variable} or {value:variable:subject}" : `{${kind}:key} takes the key of ${TEMPLATE_KINDS[kind].about}`}`);
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
found.push({
|
|
1730
|
+
raw,
|
|
1731
|
+
kind,
|
|
1732
|
+
args
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
return {
|
|
1736
|
+
found,
|
|
1737
|
+
problems
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
//#endregion
|
|
1667
1741
|
//#region src/sqlite/variable-repository.ts
|
|
1668
1742
|
const MAX_BANDS = 8;
|
|
1669
1743
|
const MAX_OPTIONS = 12;
|
|
@@ -1922,9 +1996,15 @@ function describeValue(def, value) {
|
|
|
1922
1996
|
const band = def.bands.find((candidate) => value <= candidate.upTo) ?? def.bands.at(-1);
|
|
1923
1997
|
return band === void 0 ? shortNumber(value) : `${band.label} (${shortNumber(value)})`;
|
|
1924
1998
|
}
|
|
1999
|
+
/** A variable written by its name or in other letter case ("Valence" for `valence`), when exactly one variable has that name or key. */
|
|
2000
|
+
function namedVariable(variables, written) {
|
|
2001
|
+
const wanted = written.trim().normalize("NFKC").toLowerCase();
|
|
2002
|
+
const found = wanted === "" ? [] : variables.defs().filter((def) => def.key.normalize("NFKC").toLowerCase() === wanted || def.name.normalize("NFKC").toLowerCase() === wanted);
|
|
2003
|
+
return found.length === 1 ? found[0] : void 0;
|
|
2004
|
+
}
|
|
1925
2005
|
/** Whether a change can be made, and what it would come to. Nothing is changed. */
|
|
1926
2006
|
function planVariableChange(context, spec) {
|
|
1927
|
-
const def = context.variables.def(spec.variable);
|
|
2007
|
+
const def = context.variables.def(spec.variable) ?? namedVariable(context.variables, spec.variable);
|
|
1928
2008
|
if (def === void 0) return {
|
|
1929
2009
|
ok: false,
|
|
1930
2010
|
problems: [`there is no variable "${spec.variable}" (list_variables shows them)`]
|
|
@@ -2200,6 +2280,21 @@ function parseAnnouncement(value, path, problems) {
|
|
|
2200
2280
|
...at === void 0 ? {} : { at }
|
|
2201
2281
|
};
|
|
2202
2282
|
}
|
|
2283
|
+
/** Whether words or a condition name the character a cue is said to. */
|
|
2284
|
+
function usesSelf(words, condition) {
|
|
2285
|
+
return placeholdersOf(words).found.some((placeholder) => placeholder.args.includes("@self")) || condition !== void 0 && mentionsSelf(condition);
|
|
2286
|
+
}
|
|
2287
|
+
function mentionsSelf(condition) {
|
|
2288
|
+
switch (condition.type) {
|
|
2289
|
+
case "place": return condition.character === SELF;
|
|
2290
|
+
case "variable": return condition.subject === SELF;
|
|
2291
|
+
case "item": return condition.item === "@self" || condition.heldBy === "@self";
|
|
2292
|
+
case "all":
|
|
2293
|
+
case "any": return condition.conditions.some(mentionsSelf);
|
|
2294
|
+
case "not": return mentionsSelf(condition.condition);
|
|
2295
|
+
default: return false;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2203
2298
|
function parseEffect(value, path, problems, checkEdit) {
|
|
2204
2299
|
if (!isObject$2(value)) {
|
|
2205
2300
|
problems.push(`${path}: an effect is an object`);
|
|
@@ -2219,6 +2314,10 @@ function parseEffect(value, path, problems, checkEdit) {
|
|
|
2219
2314
|
problems.push(`${path}.edit: ${error instanceof InvalidEditError ? error.problems.join("; ") : String(error)}`);
|
|
2220
2315
|
return;
|
|
2221
2316
|
}
|
|
2317
|
+
if (value["while"] !== void 0) {
|
|
2318
|
+
problems.push(`${path}.while: only a beat or a cue can wait on a condition when it is told`);
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2222
2321
|
return {
|
|
2223
2322
|
type: "edit",
|
|
2224
2323
|
edit: value["edit"]
|
|
@@ -2233,16 +2332,26 @@ function parseEffect(value, path, problems, checkEdit) {
|
|
|
2233
2332
|
problems.push(`${path}.text: required text of at most ${MAX_TEXT$1} characters`);
|
|
2234
2333
|
return;
|
|
2235
2334
|
}
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2335
|
+
const template = placeholdersOf(words);
|
|
2336
|
+
for (const problem of template.problems) problems.push(`${path}.text: ${problem}`);
|
|
2337
|
+
const waiting = value["while"] === void 0 ? void 0 : parseCondition(value["while"], `${path}.while`, problems, { nodes: 0 });
|
|
2338
|
+
if (value["while"] !== void 0 && waiting === void 0) return void 0;
|
|
2339
|
+
if (waiting !== void 0 && judgedStatements(waiting).length > 0) problems.push(`${path}.while: a statement a model judges (judged) cannot be waited on when something is told; use what the story records`);
|
|
2340
|
+
if (value["type"] === "beat") {
|
|
2341
|
+
if (usesSelf(words, waiting)) problems.push(`${path}: only a cue is said to someone, so only a cue can name ${SELF}`);
|
|
2342
|
+
return template.problems.length > 0 ? void 0 : {
|
|
2343
|
+
type: "beat",
|
|
2344
|
+
text: words,
|
|
2345
|
+
...waiting === void 0 ? {} : { while: waiting }
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2240
2348
|
if (value["type"] === "cue") {
|
|
2241
2349
|
const audience = value["audience"] !== void 0 ? parseAudience(value["audience"], `${path}.audience`, problems) : parseAudience({ characters: value["characters"] }, `${path}.characters`, problems);
|
|
2242
|
-
return audience === void 0 ? void 0 : {
|
|
2350
|
+
return audience === void 0 || template.problems.length > 0 ? void 0 : {
|
|
2243
2351
|
type: "cue",
|
|
2244
2352
|
audience,
|
|
2245
|
-
text: words
|
|
2353
|
+
text: words,
|
|
2354
|
+
...waiting === void 0 ? {} : { while: waiting }
|
|
2246
2355
|
};
|
|
2247
2356
|
}
|
|
2248
2357
|
problems.push(`${path}.type: one of edit, beat, cue`);
|
|
@@ -2294,8 +2403,8 @@ function parseTriggerDefinition(value, path, checkEdit) {
|
|
|
2294
2403
|
...announce === void 0 ? {} : { announce }
|
|
2295
2404
|
} };
|
|
2296
2405
|
}
|
|
2297
|
-
function conditionProblems(target, condition, path, known) {
|
|
2298
|
-
const character = (key) => target.characters.list().some((entry) => characterKey(entry) === key) ? [] : [`${path}: there is no character "${key}"`];
|
|
2406
|
+
function conditionProblems(target, condition, path, known, self = false) {
|
|
2407
|
+
const character = (key) => self && key === "@self" || target.characters.list().some((entry) => characterKey(entry) === key) ? [] : [`${path}: there is no character "${key}"`];
|
|
2299
2408
|
const place = (key) => target.locations.list().some((entry) => locationKey(entry) === key) ? [] : [`${path}: there is no place "${key}"`];
|
|
2300
2409
|
switch (condition.type) {
|
|
2301
2410
|
case "time": return [];
|
|
@@ -2304,11 +2413,11 @@ function conditionProblems(target, condition, path, known) {
|
|
|
2304
2413
|
const def = target.variables.def(condition.variable);
|
|
2305
2414
|
if (def === void 0) return [`${path}: there is no variable "${condition.variable}"`];
|
|
2306
2415
|
if (def.subject === "world" ? condition.subject !== void 0 : condition.subject === void 0) return [`${path}: ${def.subject === "world" ? "a world variable has no subject" : `give the ${def.subject} it is about as subject`}`];
|
|
2307
|
-
const subject = condition.subject === void 0 ? void 0 : subjectOf(target, def.subject, condition.subject);
|
|
2308
|
-
return [...condition.subject !== void 0 && subject === void 0 ? [`${path}: there is no ${def.subject} "${condition.subject}"`] : [], ...def.kind !== "number" && condition.op !== "eq" && condition.op !== "ne" ? [`${path}: ${def.kind} variables compare with eq or ne only`] : []];
|
|
2416
|
+
const subject = condition.subject === void 0 || self && condition.subject === "@self" ? void 0 : subjectOf(target, def.subject, condition.subject);
|
|
2417
|
+
return [...condition.subject !== void 0 && subject === void 0 && !(self && condition.subject === "@self") ? [`${path}: there is no ${def.subject} "${condition.subject}"`] : [], ...def.kind !== "number" && condition.op !== "eq" && condition.op !== "ne" ? [`${path}: ${def.kind} variables compare with eq or ne only`] : []];
|
|
2309
2418
|
}
|
|
2310
2419
|
case "item": return [
|
|
2311
|
-
...target.items.list().some((entry) => itemKey(entry) === condition.item) ? [] : [`${path}: there is no item "${condition.item}"`],
|
|
2420
|
+
...self && condition.item === "@self" || target.items.list().some((entry) => itemKey(entry) === condition.item) ? [] : [`${path}: there is no item "${condition.item}"`],
|
|
2312
2421
|
...condition.heldBy === void 0 ? [] : character(condition.heldBy),
|
|
2313
2422
|
...condition.at === void 0 ? [] : place(condition.at)
|
|
2314
2423
|
];
|
|
@@ -2316,9 +2425,33 @@ function conditionProblems(target, condition, path, known) {
|
|
|
2316
2425
|
case "fired": return known.has(condition.trigger) ? [] : [`${path}: there is no trigger "${condition.trigger}"`];
|
|
2317
2426
|
case "judged": return [];
|
|
2318
2427
|
case "all":
|
|
2319
|
-
case "any": return condition.conditions.flatMap((part, index) => conditionProblems(target, part, `${path}.conditions[${index}]`, known));
|
|
2320
|
-
case "not": return conditionProblems(target, condition.condition, `${path}.condition`, known);
|
|
2321
|
-
}
|
|
2428
|
+
case "any": return condition.conditions.flatMap((part, index) => conditionProblems(target, part, `${path}.conditions[${index}]`, known, self));
|
|
2429
|
+
case "not": return conditionProblems(target, condition.condition, `${path}.condition`, known, self);
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
/** What the words of a cue or beat name by `{kind:key}` that the story does not have. */
|
|
2433
|
+
function wordsProblems(target, words, path, self) {
|
|
2434
|
+
return placeholdersOf(words).found.flatMap((placeholder) => {
|
|
2435
|
+
const key = placeholder.args[0];
|
|
2436
|
+
const selfKey = self && key === "@self";
|
|
2437
|
+
const missing = (what) => [`${path}: ${placeholder.raw}: there is no ${what} "${key}"`];
|
|
2438
|
+
switch (placeholder.kind) {
|
|
2439
|
+
case "character":
|
|
2440
|
+
case "where": return selfKey || target.characters.list().some((entry) => characterKey(entry) === key) ? [] : missing("character");
|
|
2441
|
+
case "place": return target.locations.list().some((entry) => locationKey(entry) === key) ? [] : missing("place");
|
|
2442
|
+
case "item":
|
|
2443
|
+
case "holder": return target.items.list().some((entry) => itemKey(entry) === key) ? [] : missing("item");
|
|
2444
|
+
case "value": {
|
|
2445
|
+
const def = target.variables.def(key);
|
|
2446
|
+
if (def === void 0) return missing("variable");
|
|
2447
|
+
const subject = placeholder.args[1];
|
|
2448
|
+
if (def.subject === "world") return subject === void 0 ? [] : [`${path}: ${placeholder.raw}: a world variable has no subject`];
|
|
2449
|
+
if (subject === void 0) return [`${path}: ${placeholder.raw}: give the ${def.subject} it is about`];
|
|
2450
|
+
return self && subject === "@self" || subjectOf(target, def.subject, subject) !== void 0 ? [] : [`${path}: ${placeholder.raw}: there is no ${def.subject} "${subject}"`];
|
|
2451
|
+
}
|
|
2452
|
+
case "time": return [];
|
|
2453
|
+
}
|
|
2454
|
+
});
|
|
2322
2455
|
}
|
|
2323
2456
|
/**
|
|
2324
2457
|
* What in a trigger names something the story does not have: a character, place, item or variable in a condition, the characters of a cue,
|
|
@@ -2332,10 +2465,12 @@ function triggerReferenceProblems(target, definition, otherKeys = []) {
|
|
|
2332
2465
|
return who.characters.flatMap((key) => target.characters.list().some((entry) => characterKey(entry) === key) ? [] : [`${path}: there is no character "${key}"`]);
|
|
2333
2466
|
};
|
|
2334
2467
|
const cues = definition.effects.flatMap((effect, index) => effect.type !== "cue" ? [] : audience(effect.audience, `effects[${index}]`));
|
|
2468
|
+
const said = definition.effects.flatMap((effect, index) => effect.type === "edit" ? [] : [...effect.while === void 0 ? [] : conditionProblems(target, effect.while, `effects[${index}].while`, known, effect.type === "cue"), ...wordsProblems(target, effect.text, `effects[${index}].text`, effect.type === "cue")]);
|
|
2335
2469
|
const announced = definition.announce === void 0 ? [] : audience(definition.announce.audience, "announce");
|
|
2336
2470
|
return [
|
|
2337
2471
|
...conditionProblems(target, definition.condition, "when", known),
|
|
2338
2472
|
...cues,
|
|
2473
|
+
...said,
|
|
2339
2474
|
...announced
|
|
2340
2475
|
].map((problem) => `${definition.key}: ${problem}`);
|
|
2341
2476
|
}
|
|
@@ -3677,7 +3812,7 @@ function recordsEditSchema(options = {}) {
|
|
|
3677
3812
|
},
|
|
3678
3813
|
effects: {
|
|
3679
3814
|
type: "array",
|
|
3680
|
-
description: "What happens, one to six: {type:\"edit\", edit:{...}} a change of the records in the same format as edit_records (events with a location are seen by those there, variable_changes, exit_changes, item_changes, moves, intents_added, engagements); {type:\"beat\", text} something the game master is told to weave into the story; {type:\"cue\", characters:[keys] (or audience: \"everyone\" | {place:key}), text} something those characters notice; in the events of an edit, perceived_by may be \"everyone\".",
|
|
3815
|
+
description: "What happens, one to six: {type:\"edit\", edit:{...}} a change of the records in the same format as edit_records (events with a location are seen by those there, variable_changes, exit_changes, item_changes, moves, intents_added, engagements); {type:\"beat\", text, while?} something the game master is told to weave into the story; {type:\"cue\", characters:[keys] (or audience: \"everyone\" | {place:key}), text, while?} something those characters notice; a beat or cue is written when it is told, which may be turns after the trigger fired: its text may name things as {character:key}, {place:key}, {item:key}, {where:character} (the place a character is in), {holder:item} (who has it or where it lies), {value:variable} or {value:variable:subject}, {time}, and is told as the story stands then, and `while` is a condition (as above, nothing judged) that must still hold then or it is withdrawn; in a cue \"@self\" stands for the character it is said to (for example while:{type:\"place\", character:\"@self\", location:\"cellar\"}); in the events of an edit, perceived_by may be \"everyone\".",
|
|
3681
3816
|
items: {
|
|
3682
3817
|
type: "object",
|
|
3683
3818
|
properties: { type: {
|
|
@@ -5205,12 +5340,147 @@ function lapseNotes(target, turn, kept) {
|
|
|
5205
5340
|
return lapsed;
|
|
5206
5341
|
}
|
|
5207
5342
|
//#endregion
|
|
5343
|
+
//#region src/records/deferred.ts
|
|
5344
|
+
/** The words cannot be written as the story stands: something they name is not there. */
|
|
5345
|
+
var UnwritableError = class extends Error {};
|
|
5346
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
5347
|
+
function resolve$1(target, placeholder, clock, self) {
|
|
5348
|
+
const key = (index) => placeholder.args[index] === "@self" ? self ?? "@self" : placeholder.args[index];
|
|
5349
|
+
const character = (wanted) => {
|
|
5350
|
+
const found = target.characters.list().find((entry) => characterKey(entry) === wanted);
|
|
5351
|
+
if (found === void 0) throw new UnwritableError(`${placeholder.raw}: there is no character "${wanted}"`);
|
|
5352
|
+
return found;
|
|
5353
|
+
};
|
|
5354
|
+
const place = (wanted) => {
|
|
5355
|
+
const found = target.locations.list().find((entry) => locationKey(entry) === wanted);
|
|
5356
|
+
if (found === void 0) throw new UnwritableError(`${placeholder.raw}: there is no place "${wanted}"`);
|
|
5357
|
+
return found;
|
|
5358
|
+
};
|
|
5359
|
+
const item = (wanted) => {
|
|
5360
|
+
const found = target.items.list().find((entry) => itemKey(entry) === wanted);
|
|
5361
|
+
if (found === void 0) throw new UnwritableError(`${placeholder.raw}: there is no item "${wanted}"`);
|
|
5362
|
+
return found;
|
|
5363
|
+
};
|
|
5364
|
+
switch (placeholder.kind) {
|
|
5365
|
+
case "character": return character(key(0)).name;
|
|
5366
|
+
case "place": return place(key(0)).name;
|
|
5367
|
+
case "item": return item(key(0)).name;
|
|
5368
|
+
case "where": {
|
|
5369
|
+
const who = character(key(0));
|
|
5370
|
+
const at = target.placements.get("character", who.id);
|
|
5371
|
+
const found = at === void 0 ? void 0 : target.locations.get(at.locationId);
|
|
5372
|
+
if (found === void 0) throw new UnwritableError(`${placeholder.raw}: ${who.name} is nowhere in particular`);
|
|
5373
|
+
return found.name;
|
|
5374
|
+
}
|
|
5375
|
+
case "holder": {
|
|
5376
|
+
const thing = item(key(0));
|
|
5377
|
+
const where = target.items.where(thing.id);
|
|
5378
|
+
const holder = where.characterId !== void 0 ? target.characters.get(where.characterId)?.name : where.locationId !== void 0 ? target.locations.get(where.locationId)?.name : void 0;
|
|
5379
|
+
if (holder === void 0) throw new UnwritableError(`${placeholder.raw}: ${thing.name} is nowhere that is known`);
|
|
5380
|
+
return holder;
|
|
5381
|
+
}
|
|
5382
|
+
case "value": {
|
|
5383
|
+
const def = target.variables.def(placeholder.args[0]);
|
|
5384
|
+
if (def === void 0) throw new UnwritableError(`${placeholder.raw}: there is no variable "${placeholder.args[0]}"`);
|
|
5385
|
+
const subject = def.subject === "world" ? { id: 0 } : placeholder.args[1] === void 0 ? void 0 : subjectOf(target, def.subject, key(1));
|
|
5386
|
+
if (subject === void 0) throw new UnwritableError(`${placeholder.raw}: ${def.subject === "world" ? "a world variable has no subject" : `give the ${def.subject} it is about`}`);
|
|
5387
|
+
return describeValue(def, target.variables.value(def.key, def.subject, subject.id));
|
|
5388
|
+
}
|
|
5389
|
+
case "time": {
|
|
5390
|
+
const local = localTime(clock.now, clock.zone);
|
|
5391
|
+
return `${pad(local.hour)}:${pad(local.minute)}`;
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
}
|
|
5395
|
+
/** Words as the story stands now. @throws {UnwritableError} when something they name is not there. */
|
|
5396
|
+
function renderWords(target, text, clock, self) {
|
|
5397
|
+
const { found } = placeholdersOf(text);
|
|
5398
|
+
let at = 0;
|
|
5399
|
+
let out = "";
|
|
5400
|
+
for (const placeholder of found) {
|
|
5401
|
+
const start = text.indexOf(placeholder.raw, at);
|
|
5402
|
+
out += text.slice(at, start) + resolve$1(target, placeholder, clock, self);
|
|
5403
|
+
at = start + placeholder.raw.length;
|
|
5404
|
+
}
|
|
5405
|
+
return out + text.slice(at);
|
|
5406
|
+
}
|
|
5407
|
+
/** A condition with `@self` taken as a character. */
|
|
5408
|
+
function bindSelf(condition, self) {
|
|
5409
|
+
const bound = (value) => value === "@self" ? self : value;
|
|
5410
|
+
switch (condition.type) {
|
|
5411
|
+
case "place": return {
|
|
5412
|
+
...condition,
|
|
5413
|
+
character: bound(condition.character)
|
|
5414
|
+
};
|
|
5415
|
+
case "variable": return condition.subject === void 0 ? condition : {
|
|
5416
|
+
...condition,
|
|
5417
|
+
subject: bound(condition.subject)
|
|
5418
|
+
};
|
|
5419
|
+
case "item": return {
|
|
5420
|
+
...condition,
|
|
5421
|
+
item: bound(condition.item),
|
|
5422
|
+
...condition.heldBy === void 0 ? {} : { heldBy: bound(condition.heldBy) }
|
|
5423
|
+
};
|
|
5424
|
+
case "all":
|
|
5425
|
+
case "any": return {
|
|
5426
|
+
...condition,
|
|
5427
|
+
conditions: condition.conditions.map((part) => bindSelf(part, self))
|
|
5428
|
+
};
|
|
5429
|
+
case "not": return {
|
|
5430
|
+
...condition,
|
|
5431
|
+
condition: bindSelf(condition.condition, self)
|
|
5432
|
+
};
|
|
5433
|
+
default: return condition;
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
/** The effect of a trigger that a note was made from: the beat or cue whose words it carries. */
|
|
5437
|
+
function noteEffect(trigger, note) {
|
|
5438
|
+
return trigger?.effects.find((effect) => effect.type === note.kind && effect.text === note.text);
|
|
5439
|
+
}
|
|
5440
|
+
/**
|
|
5441
|
+
* Decides, at the moment notes are about to be told, what is still to be told and in what words. A note whose effect says something must
|
|
5442
|
+
* still hold and does not hold any more is withdrawn; so is one whose words name something that is not there. Nothing is changed.
|
|
5443
|
+
*/
|
|
5444
|
+
function deliverNotes(target, notes, clock) {
|
|
5445
|
+
const delivery = {
|
|
5446
|
+
shown: [],
|
|
5447
|
+
withdrawn: []
|
|
5448
|
+
};
|
|
5449
|
+
for (const note of notes) {
|
|
5450
|
+
const effect = noteEffect(target.triggers.get(note.triggerId), note);
|
|
5451
|
+
const self = note.kind === "cue" && note.characterIds[0] !== void 0 ? target.characters.list().find((entry) => entry.id === note.characterIds[0]) : void 0;
|
|
5452
|
+
const who = self === void 0 ? void 0 : characterKey(self);
|
|
5453
|
+
if (effect?.while !== void 0 && !conditionHolds(target, who === void 0 ? effect.while : bindSelf(effect.while, who), clock)) {
|
|
5454
|
+
delivery.withdrawn.push({
|
|
5455
|
+
note,
|
|
5456
|
+
reason: "what it was waiting on no longer holds"
|
|
5457
|
+
});
|
|
5458
|
+
continue;
|
|
5459
|
+
}
|
|
5460
|
+
try {
|
|
5461
|
+
delivery.shown.push({
|
|
5462
|
+
note,
|
|
5463
|
+
text: renderWords(target, note.text, clock, who)
|
|
5464
|
+
});
|
|
5465
|
+
} catch (error) {
|
|
5466
|
+
if (!(error instanceof UnwritableError)) throw error;
|
|
5467
|
+
delivery.withdrawn.push({
|
|
5468
|
+
note,
|
|
5469
|
+
reason: error.message
|
|
5470
|
+
});
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
return delivery;
|
|
5474
|
+
}
|
|
5475
|
+
//#endregion
|
|
5208
5476
|
//#region src/memory/recall.ts
|
|
5209
5477
|
const DAY$1 = 864e5;
|
|
5210
5478
|
const DEFAULT_MEMORY_POLICY = {
|
|
5211
5479
|
flaky: true,
|
|
5212
5480
|
recall: 12,
|
|
5213
|
-
overRetrieve: 4
|
|
5481
|
+
overRetrieve: 4,
|
|
5482
|
+
associate: 2,
|
|
5483
|
+
diversity: .3
|
|
5214
5484
|
};
|
|
5215
5485
|
/** Story days for the memory of an unimportant event to halve; each step of importance doubles it. */
|
|
5216
5486
|
const HALF_LIFE_DAYS = 6;
|
|
@@ -5341,7 +5611,7 @@ const DENSE_RANGE = .5;
|
|
|
5341
5611
|
const denseScore = (similarity) => Math.min(1, Math.max(0, (similarity - DENSE_FLOOR) / DENSE_RANGE));
|
|
5342
5612
|
/** Being where something happened calls it to mind, about as much as sharing two words with it. */
|
|
5343
5613
|
const PLACE_CUE = 2;
|
|
5344
|
-
function gather(source, { characterId, characters, situation, locationId }) {
|
|
5614
|
+
function gather(source, { characterId, characters, situation, locationId, about }) {
|
|
5345
5615
|
const names = new Map(characters.map((character) => [character.id, character.name]));
|
|
5346
5616
|
const peers = mentioned(situation, characters);
|
|
5347
5617
|
peers.delete(characterId);
|
|
@@ -5359,24 +5629,46 @@ function gather(source, { characterId, characters, situation, locationId }) {
|
|
|
5359
5629
|
}
|
|
5360
5630
|
const atTurn = (eventTurn) => turns.get(eventTurn) ?? start;
|
|
5361
5631
|
const ageDays = (at) => now === void 0 || at === void 0 ? 0 : Math.max(0, now - at) / DAY$1;
|
|
5362
|
-
|
|
5632
|
+
const concerns = (ids) => about === void 0 || about.length === 0 || ids.some((id) => about.includes(id));
|
|
5633
|
+
return [...source.events.perceivedBy(characterId).filter((seen) => concerns(seen.event.involves)).map((seen) => {
|
|
5363
5634
|
const at = rounds.get(`${seen.event.turn}:${seen.event.round}`) ?? atTurn(seen.event.turn);
|
|
5635
|
+
const cue = {
|
|
5636
|
+
people: peopleCue(seen.event.involves),
|
|
5637
|
+
words: overlap(cues, `${seen.perception.content} ${seen.event.text}`, seen.event.tags),
|
|
5638
|
+
place: locationId !== void 0 && seen.event.locationId === locationId ? PLACE_CUE : 0
|
|
5639
|
+
};
|
|
5364
5640
|
return {
|
|
5365
5641
|
key: `p:${seen.perception.id}`,
|
|
5642
|
+
links: {
|
|
5643
|
+
people: seen.event.involves.filter((id) => id !== characterId),
|
|
5644
|
+
tags: seen.event.tags.map((tag) => tag.toLowerCase()),
|
|
5645
|
+
...seen.event.locationId === void 0 ? {} : { place: seen.event.locationId }
|
|
5646
|
+
},
|
|
5366
5647
|
recalled: describeEvent(seen, names, at),
|
|
5367
|
-
|
|
5648
|
+
cues: cue,
|
|
5649
|
+
relevance: cue.people + cue.words + cue.place,
|
|
5368
5650
|
importance: seen.event.importance,
|
|
5369
5651
|
ageDays: ageDays(at),
|
|
5370
5652
|
recalls: seen.perception.recalls,
|
|
5371
5653
|
seed: seen.perception.id,
|
|
5372
5654
|
durability: 1
|
|
5373
5655
|
};
|
|
5374
|
-
}), ...source.memories.list(characterId).map((belief) => {
|
|
5656
|
+
}), ...source.memories.list(characterId).filter((belief) => concerns(belief.about)).map((belief) => {
|
|
5375
5657
|
const at = atTurn(belief.turn);
|
|
5658
|
+
const cue = {
|
|
5659
|
+
people: peopleCue(belief.about),
|
|
5660
|
+
words: overlap(cues, belief.text, []),
|
|
5661
|
+
place: 0
|
|
5662
|
+
};
|
|
5376
5663
|
return {
|
|
5377
5664
|
key: `m:${belief.id}`,
|
|
5665
|
+
links: {
|
|
5666
|
+
people: belief.about.filter((id) => id !== characterId),
|
|
5667
|
+
tags: []
|
|
5668
|
+
},
|
|
5378
5669
|
recalled: describeBelief(belief, at),
|
|
5379
|
-
|
|
5670
|
+
cues: cue,
|
|
5671
|
+
relevance: cue.people + cue.words,
|
|
5380
5672
|
importance: belief.importance,
|
|
5381
5673
|
ageDays: ageDays(at),
|
|
5382
5674
|
recalls: belief.recalls,
|
|
@@ -5405,20 +5697,25 @@ function fuseScores(searches) {
|
|
|
5405
5697
|
/** How well a candidate stands once its relevance is known: what the moment calls to mind, how much it matters and how recent it is. */
|
|
5406
5698
|
function judged(candidate, relevance, policy) {
|
|
5407
5699
|
let clarity = "clear";
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5700
|
+
let kept;
|
|
5701
|
+
if (policy.flaky) {
|
|
5702
|
+
kept = retention({
|
|
5703
|
+
ageDays: candidate.ageDays,
|
|
5704
|
+
importance: candidate.importance,
|
|
5705
|
+
recalls: candidate.recalls,
|
|
5706
|
+
seed: candidate.seed,
|
|
5707
|
+
durability: candidate.durability,
|
|
5708
|
+
cue: relevance >= CUE_RELEVANCE
|
|
5709
|
+
});
|
|
5710
|
+
clarity = clarityAt(kept, candidate.importance);
|
|
5711
|
+
}
|
|
5416
5712
|
if (clarity === void 0) return void 0;
|
|
5417
5713
|
const score = RELEVANCE_SCALE * relevance + .6 * candidate.importance + 2 / (1 + Math.max(0, candidate.ageDays)) + (clarity === "vague" ? -1 : 0);
|
|
5418
5714
|
return {
|
|
5419
5715
|
clarity,
|
|
5420
5716
|
score,
|
|
5421
|
-
text: candidate.recalled[clarity === "clear" ? "clear" : clarity === "faded" ? "faded" : "vague"]
|
|
5717
|
+
text: candidate.recalled[clarity === "clear" ? "clear" : clarity === "faded" ? "faded" : "vague"],
|
|
5718
|
+
...kept === void 0 ? {} : { retention: kept }
|
|
5422
5719
|
};
|
|
5423
5720
|
}
|
|
5424
5721
|
/**
|
|
@@ -5429,7 +5726,7 @@ function judged(candidate, relevance, policy) {
|
|
|
5429
5726
|
* is, and not what has faded from memory altogether). A reranker may then judge that pool.
|
|
5430
5727
|
*/
|
|
5431
5728
|
function recallPool(source, query, evidence = {}) {
|
|
5432
|
-
const { policy } = query;
|
|
5729
|
+
const { policy, explain } = query;
|
|
5433
5730
|
const candidates = gather(source, query);
|
|
5434
5731
|
const fused = fuseScores([
|
|
5435
5732
|
{
|
|
@@ -5446,18 +5743,28 @@ function recallPool(source, query, evidence = {}) {
|
|
|
5446
5743
|
}
|
|
5447
5744
|
]);
|
|
5448
5745
|
const width = Math.min(MAX_POOL, Math.max(policy.recall, Math.ceil(policy.recall * Math.max(1, policy.overRetrieve ?? 1))));
|
|
5746
|
+
const entries = candidates.map((candidate) => ({
|
|
5747
|
+
candidate,
|
|
5748
|
+
fused: fused.get(candidate.key) ?? 0,
|
|
5749
|
+
found: {
|
|
5750
|
+
...evidence.lexical?.get(candidate.key) === void 0 ? {} : { lexical: evidence.lexical.get(candidate.key) },
|
|
5751
|
+
...evidence.dense?.get(candidate.key) === void 0 ? {} : { dense: evidence.dense.get(candidate.key) }
|
|
5752
|
+
}
|
|
5753
|
+
})).flatMap((entry) => {
|
|
5754
|
+
const standing = judged(entry.candidate, entry.fused, policy);
|
|
5755
|
+
return standing === void 0 ? [] : [{
|
|
5756
|
+
entry,
|
|
5757
|
+
score: standing.score
|
|
5758
|
+
}];
|
|
5759
|
+
}).sort((a, b) => b.score - a.score || b.entry.candidate.recalled.turn - a.entry.candidate.recalled.turn).map(({ entry }) => entry).slice(0, width);
|
|
5760
|
+
const kept = new Set(entries.map((entry) => entry.candidate.key));
|
|
5449
5761
|
return {
|
|
5450
|
-
entries
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
entry,
|
|
5457
|
-
score: standing.score
|
|
5458
|
-
}];
|
|
5459
|
-
}).sort((a, b) => b.score - a.score || b.entry.candidate.recalled.turn - a.entry.candidate.recalled.turn).slice(0, width).map(({ entry }) => entry),
|
|
5460
|
-
query: { policy }
|
|
5762
|
+
entries,
|
|
5763
|
+
others: candidates.filter((candidate) => !kept.has(candidate.key)),
|
|
5764
|
+
query: {
|
|
5765
|
+
policy,
|
|
5766
|
+
...explain === true ? { explain } : {}
|
|
5767
|
+
}
|
|
5461
5768
|
};
|
|
5462
5769
|
}
|
|
5463
5770
|
/** What a reranker is asked to judge: every candidate of the pool, with the text it would be recalled by. */
|
|
@@ -5469,26 +5776,129 @@ function poolCandidates(pool) {
|
|
|
5469
5776
|
importance: candidate.importance
|
|
5470
5777
|
}));
|
|
5471
5778
|
}
|
|
5779
|
+
/** Two things are tied by the same people, the same tags and the same place; this is how strongly (people count most, tags and place add a little). */
|
|
5780
|
+
function tie(a, b) {
|
|
5781
|
+
const people = a.people.filter((id) => b.people.includes(id)).length;
|
|
5782
|
+
const tags = a.tags.filter((tag) => b.tags.includes(tag)).length;
|
|
5783
|
+
const place = a.place !== void 0 && a.place === b.place;
|
|
5784
|
+
return {
|
|
5785
|
+
strength: 2 * Math.min(people, 2) + Math.min(tags, 2) + (place ? 1 : 0),
|
|
5786
|
+
people,
|
|
5787
|
+
tags,
|
|
5788
|
+
place
|
|
5789
|
+
};
|
|
5790
|
+
}
|
|
5791
|
+
/** The least a tie has to be for one thing to bring the other to mind. */
|
|
5792
|
+
const STRONG_TIE = 3;
|
|
5793
|
+
/** What does not matter much is not brought back by association. */
|
|
5794
|
+
const ASSOCIABLE_IMPORTANCE = 3;
|
|
5795
|
+
/** How much two texts say the same, from 0 to 1: the share of their words (or pairs of characters) they have in common. */
|
|
5796
|
+
function resemblance(a, b) {
|
|
5797
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
5798
|
+
let shared = 0;
|
|
5799
|
+
for (const word of a) if (b.has(word)) shared++;
|
|
5800
|
+
return shared / (a.size + b.size - shared);
|
|
5801
|
+
}
|
|
5802
|
+
/**
|
|
5803
|
+
* Choose `count` of the ranked entries by maximal marginal relevance: each pick is the one with the best score
|
|
5804
|
+
* discounted by how much it resembles what is already chosen, so that three memories of the same moment do not
|
|
5805
|
+
* crowd out one of another. `diversity` 0 keeps the ranking as it is.
|
|
5806
|
+
*/
|
|
5807
|
+
function diversified(ranked, count, diversity) {
|
|
5808
|
+
const weight = Math.min(1, Math.max(0, diversity));
|
|
5809
|
+
if (weight === 0 || ranked.length <= 1) return ranked.slice(0, count);
|
|
5810
|
+
const top = Math.max(...ranked.map((entry) => entry.score), Number.EPSILON);
|
|
5811
|
+
const words = new Map(ranked.map((entry) => [entry, tokens(entry.candidate.recalled.clear)]));
|
|
5812
|
+
const left = [...ranked];
|
|
5813
|
+
const chosen = [];
|
|
5814
|
+
while (chosen.length < count && left.length > 0) {
|
|
5815
|
+
let best = 0;
|
|
5816
|
+
let bestValue = -Infinity;
|
|
5817
|
+
left.forEach((entry, index) => {
|
|
5818
|
+
const repeat = Math.max(0, ...chosen.map((picked) => resemblance(words.get(entry), words.get(picked))));
|
|
5819
|
+
const value = (1 - weight) * Math.max(0, entry.score) / top - weight * repeat;
|
|
5820
|
+
if (value > bestValue) {
|
|
5821
|
+
best = index;
|
|
5822
|
+
bestValue = value;
|
|
5823
|
+
}
|
|
5824
|
+
});
|
|
5825
|
+
chosen.push(left.splice(best, 1)[0]);
|
|
5826
|
+
}
|
|
5827
|
+
return chosen;
|
|
5828
|
+
}
|
|
5472
5829
|
/**
|
|
5473
|
-
* The last stage: choose the `policy.recall` best of the pool. With a reranker's judgement each
|
|
5830
|
+
* The last stage: choose the `policy.recall` best of the pool, spread by `policy.diversity`. With a reranker's judgement each
|
|
5474
5831
|
* candidate's relevance is mostly that (a candidate it left out counts as 0), and the rest is what
|
|
5475
5832
|
* retrieval found; without, it is what retrieval found.
|
|
5476
5833
|
*/
|
|
5477
5834
|
function finishRecall(pool, rerank) {
|
|
5478
5835
|
const { policy } = pool.query;
|
|
5479
|
-
|
|
5480
|
-
const
|
|
5481
|
-
|
|
5836
|
+
const standing = pool.entries.flatMap(({ candidate, fused, found }) => {
|
|
5837
|
+
const relevance = rerank === void 0 ? fused : RERANK_BLEND * Math.min(1, Math.max(0, rerank.get(candidate.key) ?? 0)) + .30000000000000004 * fused;
|
|
5838
|
+
const judgement = judged(candidate, relevance, policy);
|
|
5839
|
+
if (judgement === void 0) return [];
|
|
5840
|
+
const why = pool.query.explain === true ? {
|
|
5841
|
+
cues: candidate.cues,
|
|
5842
|
+
...found,
|
|
5843
|
+
...rerank === void 0 ? {} : { rerank: rerank.get(candidate.key) ?? 0 },
|
|
5844
|
+
relevance,
|
|
5845
|
+
importance: candidate.importance,
|
|
5846
|
+
ageDays: candidate.ageDays,
|
|
5847
|
+
...judgement.retention === void 0 ? {} : { retention: judgement.retention },
|
|
5848
|
+
score: judgement.score
|
|
5849
|
+
} : void 0;
|
|
5850
|
+
return [{
|
|
5482
5851
|
candidate,
|
|
5483
|
-
|
|
5852
|
+
relevance,
|
|
5853
|
+
...judgement,
|
|
5854
|
+
why
|
|
5484
5855
|
}];
|
|
5485
|
-
}).sort((a, b) => b.score - a.score || b.candidate.recalled.turn - a.candidate.recalled.turn)
|
|
5856
|
+
}).sort((a, b) => b.score - a.score || b.candidate.recalled.turn - a.candidate.recalled.turn);
|
|
5857
|
+
const chosen = diversified(standing, policy.recall, policy.diversity ?? 0);
|
|
5858
|
+
const result = chosen.map(({ candidate, clarity, text, why }) => ({
|
|
5486
5859
|
clarity,
|
|
5487
5860
|
text,
|
|
5488
5861
|
turn: candidate.recalled.turn,
|
|
5489
5862
|
...candidate.recalled.at === void 0 ? {} : { at: candidate.recalled.at },
|
|
5490
|
-
source: candidate.recalled.source
|
|
5863
|
+
source: candidate.recalled.source,
|
|
5864
|
+
...why === void 0 ? {} : { why }
|
|
5491
5865
|
}));
|
|
5866
|
+
const anchors = chosen.filter((entry) => entry.relevance >= CUE_RELEVANCE);
|
|
5867
|
+
const wanted = anchors.length === 0 ? 0 : Math.max(0, policy.associate ?? 0);
|
|
5868
|
+
if (wanted > 0) {
|
|
5869
|
+
const taken = new Set(chosen.map((entry) => entry.candidate.key));
|
|
5870
|
+
const tied = [...standing.filter((entry) => !chosen.includes(entry)).map((entry) => entry.candidate), ...pool.others].filter((candidate) => !taken.has(candidate.key) && candidate.importance >= ASSOCIABLE_IMPORTANCE).flatMap((candidate) => {
|
|
5871
|
+
const best = anchors.map((anchor) => tie(anchor.candidate.links, candidate.links)).sort((a, b) => b.strength - a.strength)[0];
|
|
5872
|
+
const judgement = best === void 0 || best.strength < STRONG_TIE ? void 0 : judged(candidate, 0, policy);
|
|
5873
|
+
return best === void 0 || judgement === void 0 || judgement.clarity === "vague" ? [] : [{
|
|
5874
|
+
candidate,
|
|
5875
|
+
best,
|
|
5876
|
+
judgement
|
|
5877
|
+
}];
|
|
5878
|
+
}).sort((a, b) => b.best.strength - a.best.strength || b.candidate.importance - a.candidate.importance || b.candidate.recalled.turn - a.candidate.recalled.turn).slice(0, wanted);
|
|
5879
|
+
for (const { candidate, best, judgement } of tied) result.push({
|
|
5880
|
+
clarity: judgement.clarity,
|
|
5881
|
+
text: judgement.text,
|
|
5882
|
+
turn: candidate.recalled.turn,
|
|
5883
|
+
...candidate.recalled.at === void 0 ? {} : { at: candidate.recalled.at },
|
|
5884
|
+
source: candidate.recalled.source,
|
|
5885
|
+
linked: true,
|
|
5886
|
+
...pool.query.explain === true ? { why: {
|
|
5887
|
+
cues: candidate.cues,
|
|
5888
|
+
relevance: 0,
|
|
5889
|
+
importance: candidate.importance,
|
|
5890
|
+
ageDays: candidate.ageDays,
|
|
5891
|
+
...judgement.retention === void 0 ? {} : { retention: judgement.retention },
|
|
5892
|
+
score: judgement.score,
|
|
5893
|
+
linked: {
|
|
5894
|
+
people: best.people,
|
|
5895
|
+
tags: best.tags,
|
|
5896
|
+
place: best.place
|
|
5897
|
+
}
|
|
5898
|
+
} } : {}
|
|
5899
|
+
});
|
|
5900
|
+
}
|
|
5901
|
+
return result.sort((a, b) => a.turn - b.turn);
|
|
5492
5902
|
}
|
|
5493
5903
|
/** Remind a character of what the moment calls to mind, in one call: {@link recallPool}, then {@link finishRecall} with the `rerank` evidence if there is any. */
|
|
5494
5904
|
function recall(source, query, evidence = {}) {
|
|
@@ -9091,6 +9501,9 @@ var SqliteSessionStore = class {
|
|
|
9091
9501
|
runTriggers(options) {
|
|
9092
9502
|
return runTriggers(this, options);
|
|
9093
9503
|
}
|
|
9504
|
+
deliverNotes(notes, clock) {
|
|
9505
|
+
return deliverNotes(this, notes, clock);
|
|
9506
|
+
}
|
|
9094
9507
|
lapseNotes(turn, kept) {
|
|
9095
9508
|
return this.transaction(() => lapseNotes(this, turn, kept));
|
|
9096
9509
|
}
|
|
@@ -9169,6 +9582,7 @@ function openSessionStore(location, options = {}) {
|
|
|
9169
9582
|
try {
|
|
9170
9583
|
const state = inspect(db);
|
|
9171
9584
|
if (state.version === 0 || state.applicationId !== 1146245971) throw new SchemaError(`${location} is not a session database`);
|
|
9585
|
+
if (state.version < LATEST_VERSION && location !== ":memory:") backUpBeforeMigrating(db, location, state.version);
|
|
9172
9586
|
migrate(db);
|
|
9173
9587
|
return new SqliteSessionStore(db, options.clock ?? systemClock, vectors);
|
|
9174
9588
|
} catch (error) {
|
|
@@ -9176,6 +9590,24 @@ function openSessionStore(location, options = {}) {
|
|
|
9176
9590
|
throw error;
|
|
9177
9591
|
}
|
|
9178
9592
|
}
|
|
9593
|
+
/** Folder, beside the database, holding the copies made before a schema upgrade. */
|
|
9594
|
+
const MIGRATION_BACKUP_FOLDER = "migration-backups";
|
|
9595
|
+
/** Where the copy of a database as it was at schema `version` is kept. */
|
|
9596
|
+
function migrationBackupPath(location, version) {
|
|
9597
|
+
return join(dirname(location), MIGRATION_BACKUP_FOLDER, `schema-${version}.sqlite`);
|
|
9598
|
+
}
|
|
9599
|
+
/**
|
|
9600
|
+
* A released session cannot be rebuilt, so an upgrade starts from a copy that passed the integrity check; one copy per old version is kept.
|
|
9601
|
+
* @throws {CorruptDataError} when the database is damaged, before anything is changed.
|
|
9602
|
+
*/
|
|
9603
|
+
function backUpBeforeMigrating(db, location, version) {
|
|
9604
|
+
const check = db.get("PRAGMA quick_check")?.quick_check;
|
|
9605
|
+
if (check !== "ok") throw new CorruptDataError(`${location} failed its integrity check, so it was not upgraded: ${check ?? "no result"}`);
|
|
9606
|
+
const target = migrationBackupPath(location, version);
|
|
9607
|
+
if (existsSync(target)) return;
|
|
9608
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
9609
|
+
db.run("VACUUM INTO ?", [target]);
|
|
9610
|
+
}
|
|
9179
9611
|
//#endregion
|
|
9180
9612
|
//#region src/sessions.ts
|
|
9181
9613
|
const SESSION_DATABASE_FILE = "session.sqlite";
|
|
@@ -11157,6 +11589,7 @@ var RoleplayState = class extends Service {
|
|
|
11157
11589
|
edit: (edit, options) => store.edit(edit, options),
|
|
11158
11590
|
runTriggers: (options) => store.runTriggers(options),
|
|
11159
11591
|
lapseNotes: (turn, kept) => store.lapseNotes(turn, kept),
|
|
11592
|
+
deliverNotes: (notes, clock) => store.deliverNotes(notes, clock),
|
|
11160
11593
|
records: (scope) => store.records(scope)
|
|
11161
11594
|
};
|
|
11162
11595
|
this.sessions.set(key, {
|
|
@@ -11192,11 +11625,17 @@ function createCharacterTools(link, characterId) {
|
|
|
11192
11625
|
defineTool({
|
|
11193
11626
|
name: "recall_my_memories",
|
|
11194
11627
|
description: "Ask yourself what you remember about something: a person, an object, a place or an earlier moment. You get your own version of it, what you took in yourself and what you believe, and what has faded comes back as hazy or not at all. Use it only when you need a memory that the situation does not already give you.",
|
|
11195
|
-
parameters: {
|
|
11196
|
-
|
|
11197
|
-
|
|
11198
|
-
|
|
11199
|
-
|
|
11628
|
+
parameters: {
|
|
11629
|
+
about: {
|
|
11630
|
+
type: "string",
|
|
11631
|
+
required: true,
|
|
11632
|
+
description: "What to remember: a name, an object, a topic or a short description of the moment."
|
|
11633
|
+
},
|
|
11634
|
+
person: {
|
|
11635
|
+
type: "string",
|
|
11636
|
+
description: "Only what concerns this person (a name or alias you know): everything you remember of them."
|
|
11637
|
+
}
|
|
11638
|
+
},
|
|
11200
11639
|
output: {
|
|
11201
11640
|
schema: {
|
|
11202
11641
|
type: "array",
|
|
@@ -11221,20 +11660,24 @@ function createCharacterTools(link, characterId) {
|
|
|
11221
11660
|
},
|
|
11222
11661
|
render: (_args, value) => [{
|
|
11223
11662
|
type: "text",
|
|
11224
|
-
text: value.length === 0 ? "You do not remember anything about that." : value.map((entry) => `- (${entry.clarity === "clear" ? "" : entry.clarity === "faded" ? "the details are fuzzy" : "you barely remember"}) ${entry.text}`.replace("- () ", "- ")).join("\n")
|
|
11663
|
+
text: value.length === 0 ? "You do not remember anything about that, and there is nothing more to find: go on with what you know." : value.map((entry) => `- (${entry.clarity === "clear" ? "" : entry.clarity === "faded" ? "the details are fuzzy" : "you barely remember"}) ${entry.text}`.replace("- () ", "- ")).join("\n")
|
|
11225
11664
|
}]
|
|
11226
11665
|
},
|
|
11227
11666
|
isConcurrencySafe: () => false,
|
|
11228
11667
|
async execute(args, exec) {
|
|
11229
11668
|
const agent = link();
|
|
11230
11669
|
const { session, turn, policy } = agent;
|
|
11670
|
+
const who = args.person === void 0 || args.person.trim() === "" ? [] : [...mentioned(args.person, session.characters.list())].filter((id) => id !== characterId);
|
|
11671
|
+
if (args.person !== void 0 && args.person.trim() !== "" && who.length === 0) return [];
|
|
11672
|
+
const situation = who.length === 0 ? args.about : `${args.about} ${args.person}`;
|
|
11231
11673
|
return (await session.recallRanked({
|
|
11232
11674
|
characterId,
|
|
11233
|
-
situation
|
|
11675
|
+
situation,
|
|
11234
11676
|
turn,
|
|
11235
11677
|
policy,
|
|
11678
|
+
...who.length === 0 ? {} : { about: who },
|
|
11236
11679
|
...placeOf(session, characterId)
|
|
11237
|
-
}, exec.signal, await vectorOf(agent,
|
|
11680
|
+
}, exec.signal, await vectorOf(agent, situation, exec.signal))).map((entry) => ({
|
|
11238
11681
|
clarity: entry.clarity,
|
|
11239
11682
|
turn: entry.turn,
|
|
11240
11683
|
text: entry.text
|
|
@@ -11272,7 +11715,7 @@ function createCharacterTools(link, characterId) {
|
|
|
11272
11715
|
},
|
|
11273
11716
|
render: (_args, value) => [{
|
|
11274
11717
|
type: "text",
|
|
11275
|
-
text: value.length === 0 ? "You know of nothing matching that." : value.map((entry) => `- ${entry.kind}: ${entry.name}
|
|
11718
|
+
text: value.length === 0 ? "You know of nothing matching that." : value.map((entry) => `- ${entry.kind}: ${entry.name}`).join("\n")
|
|
11276
11719
|
}]
|
|
11277
11720
|
},
|
|
11278
11721
|
isConcurrencySafe: () => true,
|
|
@@ -11383,6 +11826,6 @@ function createCharacterTools(link, characterId) {
|
|
|
11383
11826
|
];
|
|
11384
11827
|
}
|
|
11385
11828
|
//#endregion
|
|
11386
|
-
export { ACCURACIES, CHARACTER_CONTROLS, CHARACTER_KINDS, CHARACTER_ROLES, CHARACTER_TIERS, CLOCK_MODES, COMPARISONS, CONSOLIDATION_KINDS, CONTAINER_STATES, Config, ConflictError, CorruptDataError, DAY, DAY_PARTS, DEFAULT_MEMORY_POLICY, DEFAULT_SLOTS, EMOTION_VARIABLES, ENGAGEMENT_GRACE, ENGAGEMENT_STATUSES, EXIT_STATES, HOUR, IMPORTANCE_MAX, IMPORTANCE_MIN, INTENT_HORIZONS, INTENT_STATUSES, INTENT_TYPES, ITEM_ACTIONS, InvalidEditError, InvalidMoveError, InvalidValueError, KNOWLEDGE_KINDS, LORE_KINDS, LORE_SUBJECT_KINDS, MAX_LOCATION_FEATURES, MAX_ROUTINE_WINDOWS, MAX_SHOWN, MAX_TRIGGERS, MAX_VARIABLES, MEMBERSHIP_STATUSES, MEMORY_KINDS, MINUTE, NotFoundError, PERCEPTION_MODES, PLACEABLE_KINDS, PLOT_NUDGE_MODES, PLOT_STATUSES, RECALL_ITEM_KINDS, RoleplayState, LATEST_VERSION as SCHEMA_VERSION, SECOND, SESSION_DATABASE_FILE, SLOT_ID, SUMMARY_LEVELS, SchemaError, StateError, THREAD_STATUSES, TIME_CHANGE_KINDS, TRIGGER_NOTE_KINDS, TRIGGER_STATUSES, VARIABLE_KEY, VARIABLE_KINDS, VARIABLE_SUBJECTS, VARIABLE_VISIBILITIES, VISUAL_SUBJECT_KINDS, WEEKDAY_NAMES, addLocalDays, agendaAt, announcements, apply, applyItemPlan, applyRecordsEdit, audienceIds, catchUpPlan, changeItem, changeVariable, characterKey, clarityAt, clockTime, conditionHolds, createCharacterTools, createSession, createSessionStore, createTools, dayPart, deleteSession, denseScore, describeDuration, describeHolder, describeInstant, describeValue, finishRecall, fuseScores, inAudience, inWindow, isSessionId, isTimeZone, itemKey, jitter, judgedStatements, lapseNotes, lintWorld, listSessions, localIso, localTime, locationKey, mentioned, moveCharacter, name, nextOccurrence, nextTimeOfDay, nodeSqliteDriver, occurrenceAt, openSession, openSessionStore, overlap, parseInstant, parseRecordsEdit, parseTimeOfDay, parseTriggerDefinition, planItemChange, planKnownMove, planMove, planVariableChange, poolCandidates, rankGoals, readRecords, readTriggerDefinition, recall, recallCandidates, recallPool, recordsEditSchema, retention, runTriggers, seededRoll, sessionDatabase, sessionFolder, shownVariables, subjectOf, systemClock, systemTimeZone, tokens, triggerReferenceProblems, valueIssue, zonedInstant };
|
|
11829
|
+
export { ACCURACIES, CHARACTER_CONTROLS, CHARACTER_KINDS, CHARACTER_ROLES, CHARACTER_TIERS, CLOCK_MODES, COMPARISONS, CONSOLIDATION_KINDS, CONTAINER_STATES, Config, ConflictError, CorruptDataError, DAY, DAY_PARTS, DEFAULT_MEMORY_POLICY, DEFAULT_SLOTS, EMOTION_VARIABLES, ENGAGEMENT_GRACE, ENGAGEMENT_STATUSES, EXIT_STATES, HOUR, IMPORTANCE_MAX, IMPORTANCE_MIN, INTENT_HORIZONS, INTENT_STATUSES, INTENT_TYPES, ITEM_ACTIONS, InvalidEditError, InvalidMoveError, InvalidValueError, KNOWLEDGE_KINDS, LORE_KINDS, LORE_SUBJECT_KINDS, MAX_LOCATION_FEATURES, MAX_ROUTINE_WINDOWS, MAX_SHOWN, MAX_TRIGGERS, MAX_VARIABLES, MEMBERSHIP_STATUSES, MEMORY_KINDS, MIGRATION_BACKUP_FOLDER, MINUTE, NotFoundError, PERCEPTION_MODES, PLACEABLE_KINDS, PLOT_NUDGE_MODES, PLOT_STATUSES, RECALL_ITEM_KINDS, RoleplayState, LATEST_VERSION as SCHEMA_VERSION, SECOND, SELF, SESSION_DATABASE_FILE, SLOT_ID, SUMMARY_LEVELS, SchemaError, StateError, TEMPLATE_KINDS, THREAD_STATUSES, TIME_CHANGE_KINDS, TRIGGER_NOTE_KINDS, TRIGGER_STATUSES, UnwritableError, VARIABLE_KEY, VARIABLE_KINDS, VARIABLE_SUBJECTS, VARIABLE_VISIBILITIES, VISUAL_SUBJECT_KINDS, WEEKDAY_NAMES, addLocalDays, agendaAt, announcements, apply, applyItemPlan, applyRecordsEdit, audienceIds, catchUpPlan, changeItem, changeVariable, characterKey, clarityAt, clockTime, conditionHolds, createCharacterTools, createSession, createSessionStore, createTools, dayPart, deleteSession, deliverNotes, denseScore, describeDuration, describeHolder, describeInstant, describeValue, finishRecall, fuseScores, inAudience, inWindow, isSessionId, isTimeZone, itemKey, jitter, judgedStatements, lapseNotes, lintWorld, listSessions, localIso, localTime, locationKey, mentioned, migrationBackupPath, moveCharacter, name, nextOccurrence, nextTimeOfDay, nodeSqliteDriver, occurrenceAt, openSession, openSessionStore, overlap, parseInstant, parseRecordsEdit, parseTimeOfDay, parseTriggerDefinition, placeholdersOf, planItemChange, planKnownMove, planMove, planVariableChange, poolCandidates, rankGoals, readRecords, readTriggerDefinition, recall, recallCandidates, recallPool, recordsEditSchema, renderWords, retention, runTriggers, seededRoll, sessionDatabase, sessionFolder, shownVariables, subjectOf, systemClock, systemTimeZone, tokens, triggerReferenceProblems, valueIssue, zonedInstant };
|
|
11387
11830
|
|
|
11388
11831
|
//# sourceMappingURL=index.js.map
|