@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/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
- if (value["type"] === "beat") return {
2237
- type: "beat",
2238
- text: words
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
- return [...source.events.perceivedBy(characterId).map((seen) => {
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
- relevance: peopleCue(seen.event.involves) + overlap(cues, `${seen.perception.content} ${seen.event.text}`, seen.event.tags) + (locationId !== void 0 && seen.event.locationId === locationId ? PLACE_CUE : 0),
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
- relevance: peopleCue(belief.about) + overlap(cues, belief.text, []),
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
- if (policy.flaky) clarity = clarityAt(retention({
5409
- ageDays: candidate.ageDays,
5410
- importance: candidate.importance,
5411
- recalls: candidate.recalls,
5412
- seed: candidate.seed,
5413
- durability: candidate.durability,
5414
- cue: relevance >= CUE_RELEVANCE
5415
- }), candidate.importance);
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: candidates.map((candidate) => ({
5451
- candidate,
5452
- fused: fused.get(candidate.key) ?? 0
5453
- })).flatMap((entry) => {
5454
- const standing = judged(entry.candidate, entry.fused, policy);
5455
- return standing === void 0 ? [] : [{
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
- return pool.entries.flatMap(({ candidate, fused }) => {
5480
- const standing = judged(candidate, rerank === void 0 ? fused : RERANK_BLEND * Math.min(1, Math.max(0, rerank.get(candidate.key) ?? 0)) + .30000000000000004 * fused, policy);
5481
- return standing === void 0 ? [] : [{
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
- ...standing
5852
+ relevance,
5853
+ ...judgement,
5854
+ why
5484
5855
  }];
5485
- }).sort((a, b) => b.score - a.score || b.candidate.recalled.turn - a.candidate.recalled.turn).slice(0, policy.recall).sort((a, b) => a.candidate.recalled.turn - b.candidate.recalled.turn).map(({ candidate, clarity, text }) => ({
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: { about: {
11196
- type: "string",
11197
- required: true,
11198
- description: "What to remember: a name, an object, a topic or a short description of the moment."
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: args.about,
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, args.about, exec.signal))).map((entry) => ({
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} (${entry.key})`).join("\n")
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