@dsh-agentic-roleplay/state 0.3.0 → 0.3.1
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 +1 -1
- package/lib/index.d.ts +190 -12
- package/lib/index.js +684 -49
- package/lib/index.js.map +1 -1
- package/package.json +3 -3
package/lib/index.js
CHANGED
|
@@ -141,6 +141,15 @@ const MEMORY_KINDS = [
|
|
|
141
141
|
"relationship",
|
|
142
142
|
"knowledge"
|
|
143
143
|
];
|
|
144
|
+
/** How sure a character is of what it believes, from least to most. */
|
|
145
|
+
const MEMORY_CONFIDENCES = [
|
|
146
|
+
"doubtful",
|
|
147
|
+
"likely",
|
|
148
|
+
"sure"
|
|
149
|
+
];
|
|
150
|
+
const DEFAULT_CONFIDENCE = "likely";
|
|
151
|
+
/** Whether a perception speaks for a belief or against it. */
|
|
152
|
+
const EVIDENCE_RELATIONS = ["supports", "contradicts"];
|
|
144
153
|
const THREAD_STATUSES = ["open", "resolved"];
|
|
145
154
|
/** A digest covers a stretch of recent turns closely; a chapter covers older turns briefly. */
|
|
146
155
|
const SUMMARY_LEVELS = ["digest", "chapter"];
|
|
@@ -403,6 +412,213 @@ const PLOT_NUDGE_MODES = [
|
|
|
403
412
|
"breathing_room"
|
|
404
413
|
];
|
|
405
414
|
//#endregion
|
|
415
|
+
//#region ../dsh-roleplay-model/src/messages-zh.ts
|
|
416
|
+
/** The Chinese of the texts built in code, by the English that stands in the code. A `{name}` is filled in and must stand in both. */
|
|
417
|
+
const ZH = {
|
|
418
|
+
", ": ",",
|
|
419
|
+
"; ": ";",
|
|
420
|
+
": ": ":",
|
|
421
|
+
".": "。",
|
|
422
|
+
" and ": "和",
|
|
423
|
+
"doubtful": "存疑",
|
|
424
|
+
"likely": "较有把握",
|
|
425
|
+
"sure": "确信",
|
|
426
|
+
"open": "畅通",
|
|
427
|
+
"locked": "已上锁",
|
|
428
|
+
"blocked": "被堵住",
|
|
429
|
+
"night": "夜里",
|
|
430
|
+
"morning": "上午",
|
|
431
|
+
"afternoon": "下午",
|
|
432
|
+
"evening": "傍晚",
|
|
433
|
+
"spring": "春季",
|
|
434
|
+
"summer": "夏季",
|
|
435
|
+
"autumn": "秋季",
|
|
436
|
+
"winter": "冬季",
|
|
437
|
+
"today is {holidays}": "今天是{holidays}",
|
|
438
|
+
"Summary": "概述",
|
|
439
|
+
"Personality": "性格",
|
|
440
|
+
"Background": "背景",
|
|
441
|
+
"Appearance": "外貌",
|
|
442
|
+
"How you speak": "说话方式",
|
|
443
|
+
"You are {name}.": "你是{name}。",
|
|
444
|
+
"You are also called {names}.": "你也被称作{names}。",
|
|
445
|
+
"age: {value}": "年龄:{value}",
|
|
446
|
+
"gender: {value}": "性别:{value}",
|
|
447
|
+
"pronouns: {value}": "代词:{value}",
|
|
448
|
+
"Situation:": "当前情形:",
|
|
449
|
+
"Now: {time}.": "现在是:{time}。",
|
|
450
|
+
"Where you are:": "你所在之处:",
|
|
451
|
+
"Present:": "在场的人:",
|
|
452
|
+
" (you)": "(你)",
|
|
453
|
+
"What you know is coming:": "你知道即将发生的事:",
|
|
454
|
+
"Just now you notice:": "此刻你注意到:",
|
|
455
|
+
"How you are (what you can tell of yourself):": "你的状态(你自己能感觉到的):",
|
|
456
|
+
"What you can tell of the others here:": "你能看出在场其他人的情况:",
|
|
457
|
+
"What you have on you:": "你身上带着:",
|
|
458
|
+
"Things here: ": "这里有:",
|
|
459
|
+
"Ways out: ": "出路:",
|
|
460
|
+
"Lying about: ": "散落在周围的:",
|
|
461
|
+
"hidden": "隐蔽",
|
|
462
|
+
" holding {things}": ",里面有{things}",
|
|
463
|
+
"; and {count} more": ";另有{count}项",
|
|
464
|
+
"at {feature}": "在{feature}",
|
|
465
|
+
"; and {count} more you can recall if needed": ";另有{count}项,需要时你可以回想起来",
|
|
466
|
+
"What you know exists (this does not tell you where it is now or what state it is in):": "你知道存在的事物(这并不说明它现在在哪里、处于什么状态):",
|
|
467
|
+
"People: ": "人物:",
|
|
468
|
+
"Factions: ": "组织:",
|
|
469
|
+
"Places: ": "地点:",
|
|
470
|
+
"Routes: ": "路径:",
|
|
471
|
+
"Things: ": "物品:",
|
|
472
|
+
"Your day and what you are after (plans, not orders: keep them, put them off or drop them as the moment and your character suggest):": "你的日程与你想达成的事(只是计划,不是命令:依当下情形和你的性格,可以照做、推迟或放弃):",
|
|
473
|
+
"Your day, now: {activity}{place}, until {until}{note}.": "你此刻的日程:{activity}{place},持续到{until}{note}。",
|
|
474
|
+
"Your day, next: {activity}{place}, from {from}.": "你接下来的日程:{activity}{place},{from}开始。",
|
|
475
|
+
" at {place}": ",在{place}",
|
|
476
|
+
" (not something you would drop lightly)": "(你不会轻易放下)",
|
|
477
|
+
", with {names}": ",与{names}一起",
|
|
478
|
+
"You were to: {title}{place}{with}, at {at}, which has passed and is still open.": "你本应:{title}{place}{with},时间是{at},已经过了,事情仍未了结。",
|
|
479
|
+
"You are due to: {title}{place}{with}, at {at}.": "你约定要:{title}{place}{with},时间是{at}。",
|
|
480
|
+
"What you are after:": "你想达成的事:",
|
|
481
|
+
", by {deadline}, which has passed": ",限期{deadline},已过",
|
|
482
|
+
", by {deadline}": ",限期{deadline}",
|
|
483
|
+
" Next: {plan}.": " 下一步:{plan}。",
|
|
484
|
+
"How you stand with people who bear on this moment (your own view, not objective fact):": "你与和此刻相关的人的关系(是你自己的看法,不是客观事实):",
|
|
485
|
+
" (here)": "(在场)",
|
|
486
|
+
" (not here)": "(不在场)",
|
|
487
|
+
"knows them well": "很了解对方",
|
|
488
|
+
"barely knows them": "几乎不了解对方",
|
|
489
|
+
"trusts them": "信任对方",
|
|
490
|
+
"distrusts them": "不信任对方",
|
|
491
|
+
"is fond of them": "喜欢对方",
|
|
492
|
+
"dislikes them": "不喜欢对方",
|
|
493
|
+
"things are strained between them": "彼此关系紧张",
|
|
494
|
+
"Your affiliations (their goals are pressures and commitments, not commands):": "你的所属组织(其目标是压力和承诺,不是命令):",
|
|
495
|
+
"member": "成员",
|
|
496
|
+
" Goals: {goals}.": " 目标:{goals}。",
|
|
497
|
+
"- {name}: {position}; {status}; commitment {commitment}.": "- {name}:{position};{status};投入程度 {commitment}。",
|
|
498
|
+
" Your view: {attitude}.": " 你的看法:{attitude}。",
|
|
499
|
+
"What you remember that bears on this (only what you took in yourself; do not claim to remember more, and be unsure of what is fuzzy):": "你记得的、与此相关的事(只限你自己亲身经历的;不要声称记得更多,模糊的地方要保持不确定):",
|
|
500
|
+
"What you remember that bears on this: nothing comes to mind.": "你记得的、与此相关的事:想不起任何事。",
|
|
501
|
+
"some time ago": "一段时间以前",
|
|
502
|
+
"just now": "刚才",
|
|
503
|
+
"1 day ago": "1天前",
|
|
504
|
+
"{amount} days ago": "{amount}天前",
|
|
505
|
+
"1 hour ago": "1小时前",
|
|
506
|
+
"{amount} hours ago": "{amount}小时前",
|
|
507
|
+
"1 minute ago": "1分钟前",
|
|
508
|
+
"{amount} minutes ago": "{amount}分钟前",
|
|
509
|
+
"the details are fuzzy": "细节已经模糊",
|
|
510
|
+
"you barely remember": "你几乎记不清了",
|
|
511
|
+
"you are sure of it": "你对此确信无疑",
|
|
512
|
+
"you doubt it": "你对此存疑",
|
|
513
|
+
"something you have seen since speaks against it": "此后你所见所闻有与之相悖的地方",
|
|
514
|
+
"it comes to mind with the rest": "它和其余的事一起浮现在脑海",
|
|
515
|
+
"Earlier in this scene, before this moment:": "这一幕此刻之前发生的事:",
|
|
516
|
+
"What just happened, and what you are reacting to:": "刚才发生的事,以及你在回应的是什么:",
|
|
517
|
+
"- {name} intended: {intent} ({result}) {summary}": "- {name}打算:{intent}({result}){summary}",
|
|
518
|
+
"- {name} said \"{utterance}\" and intended: {intent} ({result}) {summary}": "- {name}说了“{utterance}”,并打算:{intent}({result}){summary}",
|
|
519
|
+
"Your earlier intent did not work out: {intent}\nYou are trying again, knowing the above. Decide again what you do now.": "你先前的打算没有成功:{intent}\n你带着上面这些再试一次。重新决定你现在做什么。",
|
|
520
|
+
"Character: {name}{summary}": "角色:{name}{summary}",
|
|
521
|
+
"What they believe now:": "他们现在的看法:",
|
|
522
|
+
"(nothing yet)": "(暂时没有)",
|
|
523
|
+
"What they have taken in since:": "他们此后经历的事:",
|
|
524
|
+
"Sort their memory.": "请整理他们的记忆。",
|
|
525
|
+
"not acceptable: {problems}": "不可接受:{problems}",
|
|
526
|
+
"{kind}, importance {importance}, {confidence}{about}{dispute}": "{kind},重要性 {importance},{confidence}{about}{dispute}",
|
|
527
|
+
", about {names}": ",关于{names}",
|
|
528
|
+
", something taken in since speaks against it": ",此后经历的事与之相悖",
|
|
529
|
+
"add: at most {max} new beliefs in one pass; merge or choose": "add:一次最多 {max} 条新看法;请合并或取舍",
|
|
530
|
+
"at most {max} changes in one pass": "一次最多 {max} 处修改",
|
|
531
|
+
"{where}: there is no thing it took in numbered {id}": "{where}:没有编号为 {id} 的经历",
|
|
532
|
+
"{where}: confidence is one of {options}": "{where}:confidence 须为 {options} 之一",
|
|
533
|
+
"{where}: unknown kind \"{kind}\"": "{where}:未知的 kind “{kind}”",
|
|
534
|
+
"{where}: say what it believes": "{where}:请写出它相信什么",
|
|
535
|
+
"{where}: at most {max} characters; say it shorter": "{where}:最多 {max} 个字符;请写得更短",
|
|
536
|
+
"{where}: importance runs from {min} to {max}": "{where}:importance 取值为 {min} 到 {max}",
|
|
537
|
+
"{where}: there is no belief numbered {id}": "{where}:没有编号为 {id} 的看法",
|
|
538
|
+
"{where}: one passing moment is not yet a belief; rest it on things from at least two different turns, or on one event that turned the story": "{where}:一个转瞬即逝的瞬间还不是看法;请以至少两个不同回合的经历为依据,或以一件扭转了故事的事件为依据",
|
|
539
|
+
"{where}: to be sure of something it must rest on things from at least two different turns": "{where}:要对某事确信,必须以至少两个不同回合的经历为依据",
|
|
540
|
+
"{where}: \"{name}\" is not a character; name them as they are named": "{where}:“{name}”不是角色;请使用他们的名字",
|
|
541
|
+
"{where}: belief {id} is already changed in this pass": "{where}:看法 {id} 在这一轮中已经修改过",
|
|
542
|
+
"{where}: say what it took in since that bears on it": "{where}:请写出此后与之相关的经历",
|
|
543
|
+
"{where}: thing {id} came before the belief was formed; cite what came after": "{where}:经历 {id} 发生在看法形成之前;请引用之后发生的事",
|
|
544
|
+
"{where}: say what changes": "{where}:请写出有何改变",
|
|
545
|
+
"{where}: the text must be there and at most {max} characters": "{where}:必须有文字,且最多 {max} 个字符",
|
|
546
|
+
"{where}: a belief that something contradicts must be doubted: lower its confidence": "{where}:被事实相悖的看法必须存疑:请降低其 confidence",
|
|
547
|
+
"{where}: to doubt a belief, name what contradicts it in contradicted_by": "{where}:要对看法存疑,请在 contradicted_by 中写明与之相悖的经历",
|
|
548
|
+
"{where}: merge into a belief that stays": "{where}:请合并到保留的看法中",
|
|
549
|
+
"Time: {time}.": "时间:{time}。",
|
|
550
|
+
"The story's time moves only as you say: each round takes what you give it (elapsed_seconds in resolve_round, or the default), and advance_time skips ahead.": "故事的时间只按你说的推进:每一轮耗时由你给定(resolve_round 的 elapsed_seconds,或取默认值),advance_time 可以跳过一段时间。",
|
|
551
|
+
"The player was away for {duration} of real time and the story went on meanwhile: characters have been about their own business, so do not assume anyone is where or as the player left them unless the records say so.": "玩家离开了{duration}的真实时间,其间故事仍在继续:角色们各忙各的事,除非记录另有说明,不要假定任何人还在玩家离开时的位置或状态。",
|
|
552
|
+
"The story's time follows the real clock: it cannot be skipped, and each round takes as long as it really took.": "故事时间跟随真实时钟:无法跳过,每一轮耗时就是它实际花费的时间。",
|
|
553
|
+
"By their routines since you last looked: {moves}.": "按各自的日程,自你上次查看以来:{moves}。",
|
|
554
|
+
"{name} went to {place} ({activity})": "{name}去了{place}({activity})",
|
|
555
|
+
"With the player's character, and their days: {days}. The characters decide for themselves whether to keep to their day; the moment may keep them.": "与玩家角色在一起的人及其日程:{days}。是否照日程行事由角色自己决定;当下的情形也可能拖住他们。",
|
|
556
|
+
", at {place}": ",在{place}",
|
|
557
|
+
"\"{title}\" ({with}{place}) was due {when} and is still open": "“{title}”({with}{place})原定{when},仍未了结",
|
|
558
|
+
"\"{title}\" ({with}{place}) is due {when}": "“{title}”({with}{place})定于{when}",
|
|
559
|
+
"Commitments due soon or overdue: {entries}.": "即将到期或已逾期的约定:{entries}。",
|
|
560
|
+
"named in the move": "被这一步行动点到",
|
|
561
|
+
"in the scene": "在场",
|
|
562
|
+
"own goal: {goal}": "自身目标:{goal}",
|
|
563
|
+
"faction pressure: {goal}": "组织压力:{goal}",
|
|
564
|
+
"high tension toward the player": "对玩家高度紧张",
|
|
565
|
+
"distrusts the player": "不信任玩家",
|
|
566
|
+
"cares about the player": "在意玩家",
|
|
567
|
+
"dislikes the player": "不喜欢玩家",
|
|
568
|
+
"Likely characters to activate, in priority order (advice only; activate only whoever the moment actually reaches):": "可能需要启动的角色,按优先级排列(仅供参考;只启动当下真正牵涉到的人):",
|
|
569
|
+
"socially implicated": "社交上被牵涉",
|
|
570
|
+
"The player's character is in {place}": "玩家角色在{place}",
|
|
571
|
+
"Characters here:": "在这里的角色:",
|
|
572
|
+
"Characters elsewhere (they perceive nothing here unless the moment reaches them):": "在别处的角色(除非当下波及他们,否则感知不到这里的事):",
|
|
573
|
+
"{member} - in {place} ({key})": "{member} - 在{place}({key})",
|
|
574
|
+
"Characters with no place (not part of this scene; they cannot be asked to act):": "没有所在地点的角色(不属于这一幕;不能让他们行动):",
|
|
575
|
+
"the player's character, \"{key}\"": "玩家角色,“{key}”",
|
|
576
|
+
"acts through \"{key}\"": "通过“{key}”行动",
|
|
577
|
+
"Currently: {state}": "目前:{state}",
|
|
578
|
+
"Scene before turn {turn}:": "第 {turn} 回合之前的场景:",
|
|
579
|
+
"What characters have on them:": "角色们身上带着:",
|
|
580
|
+
"The world acted since the last turn, by itself:": "自上一回合以来,世界自行发生的事:",
|
|
581
|
+
"Known beforehand, still to come (who knows of it; it will happen by itself when its time comes):": "事先已知、尚未发生的事(谁知道;时候到了会自行发生):",
|
|
582
|
+
"[known to {who}]": "[知情者:{who}]",
|
|
583
|
+
"Weave these into the story when it fits, then say you did with addressed in narrate (each stays for a few turns and is then let go):": "合适时把这些织入故事,然后在 narrate 的 addressed 中说明已处理(每条会保留几个回合,之后放弃):",
|
|
584
|
+
"What the story keeps track of (change it with variable_changes in an outcome when what happened calls for it):": "故事追踪的内容(发生的事需要时,在结果中用 variable_changes 修改):",
|
|
585
|
+
"Still open in the story:": "故事中仍未了结的事:",
|
|
586
|
+
"Earlier events that bear on what the player just did (from the records of the story):": "与玩家刚才的行动相关的早先事件(来自故事记录):",
|
|
587
|
+
"turn {turn}": "第 {turn} 回合",
|
|
588
|
+
"You were to {title}, at {when}": "你本应{title},时间是{when}",
|
|
589
|
+
"You were to {title} at {place}, at {when}": "你本应在{place}:{title},时间是{when}",
|
|
590
|
+
" ({note})": "({note})",
|
|
591
|
+
"It is {now} now and the time has passed.": "现在是{now},时间已经过了。",
|
|
592
|
+
"It is {now} now.": "现在是{now}。",
|
|
593
|
+
"It was with: {who}.": "同你一起的有:{who}。",
|
|
594
|
+
"You are not with the player's character, and nothing of this happens in front of anyone: decide what you do about it.": "你不和玩家的角色在一起,这件事也不会发生在任何人面前:决定你要怎么处理。",
|
|
595
|
+
"The time you had for \"{goal}\" has passed ({when}; it is {now} now)": "你为“{goal}”预留的时间已过({when};现在是{now})",
|
|
596
|
+
"You are not with the player's character and nothing of this happens in front of anyone: decide what you do about it.": "你不和玩家的角色在一起,这件事也不会发生在任何人面前:决定你要怎么处理。",
|
|
597
|
+
"{names} noticed: {text}": "{names}注意到:{text}",
|
|
598
|
+
"{name} went to {place}.": "{name}去了{place}。"
|
|
599
|
+
};
|
|
600
|
+
//#endregion
|
|
601
|
+
//#region ../dsh-roleplay-model/src/messages.ts
|
|
602
|
+
/** The languages the texts a model reads are written in. English is the source: a text is its own key. */
|
|
603
|
+
const LANGUAGES = ["en", "zh"];
|
|
604
|
+
/** The language a setting such as `zh-CN` or `en` stands for; English when it is none that is known. */
|
|
605
|
+
function languageOf(code) {
|
|
606
|
+
return (code ?? "").trim().toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
607
|
+
}
|
|
608
|
+
const fill = (template, values) => values === void 0 ? template : template.replace(/\{(\w+)\}/g, (whole, name) => name in values ? String(values[name]) : whole);
|
|
609
|
+
const CATALOGUES = { zh: ZH };
|
|
610
|
+
/**
|
|
611
|
+
* The texts the engine builds for a model (briefs, reports, refusals) in a language. The text written in the code is the English one and the key of
|
|
612
|
+
* the others; a text a language lacks is told in English, and a test keeps that from happening.
|
|
613
|
+
*/
|
|
614
|
+
function translator(language) {
|
|
615
|
+
const catalogue = language === "en" ? void 0 : CATALOGUES[language];
|
|
616
|
+
return (text, values) => fill(catalogue?.[text] ?? text, values);
|
|
617
|
+
}
|
|
618
|
+
/** The texts with a translation, for the test that every text has one. */
|
|
619
|
+
const translated = (language) => Object.keys(CATALOGUES[language]);
|
|
620
|
+
const translation = (language, text) => CATALOGUES[language][text];
|
|
621
|
+
//#endregion
|
|
406
622
|
//#region src/clock.ts
|
|
407
623
|
const systemClock = () => /* @__PURE__ */ new Date();
|
|
408
624
|
//#endregion
|
|
@@ -562,6 +778,7 @@ const toWindow = (row) => ({
|
|
|
562
778
|
id: row.id,
|
|
563
779
|
characterId: row.character_id,
|
|
564
780
|
days: parseNumberList(row.days, "routine_windows.days"),
|
|
781
|
+
on: parseStringList(row.filters, "routine_windows.filters"),
|
|
565
782
|
start: row.start_minute,
|
|
566
783
|
end: row.end_minute,
|
|
567
784
|
activity: row.activity,
|
|
@@ -595,10 +812,11 @@ var SqliteRoutineRepository = class {
|
|
|
595
812
|
if (this.db.get("SELECT 1 FROM characters WHERE id = ?", [characterId]) === void 0) throw new NotFoundError(`character ${characterId} does not exist`);
|
|
596
813
|
this.db.run("DELETE FROM routine_windows WHERE character_id = ?", [characterId]);
|
|
597
814
|
windows.forEach((window, index) => {
|
|
598
|
-
this.db.run("INSERT INTO routine_windows (character_id, ordinal, days, start_minute, end_minute, activity, location_id, feature_key, position, interruptible) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
815
|
+
this.db.run("INSERT INTO routine_windows (character_id, ordinal, days, filters, start_minute, end_minute, activity, location_id, feature_key, position, interruptible) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
599
816
|
characterId,
|
|
600
817
|
index,
|
|
601
818
|
toJson([...new Set(window.days)].sort()),
|
|
819
|
+
toJson([...new Set(window.on ?? [])]),
|
|
602
820
|
window.start,
|
|
603
821
|
window.end,
|
|
604
822
|
window.activity.trim(),
|
|
@@ -707,6 +925,226 @@ var SqliteEngagementRepository = class {
|
|
|
707
925
|
}
|
|
708
926
|
};
|
|
709
927
|
//#endregion
|
|
928
|
+
//#region src/records/calendar.ts
|
|
929
|
+
/**
|
|
930
|
+
* Which days a thing applies on, beyond the days of the week: whole months, a date every year or once, a stretch of dates, the day of the month,
|
|
931
|
+
* the first or last Monday of a month, a season, and the holidays a world names. A day is told by tokens (`dec`, `12-24`, `1912-09-21..1912-09-22`,
|
|
932
|
+
* `dom:1`, `nth:mon:1`, `season:winter`, `holiday:founders-day`, `fri`); a token with `!` before it is a day it does not apply on. A list holds on a
|
|
933
|
+
* day when it names no day to apply on or one of them is today, and none of the days to leave out is.
|
|
934
|
+
*/
|
|
935
|
+
const MONTH_NAMES = [
|
|
936
|
+
"jan",
|
|
937
|
+
"feb",
|
|
938
|
+
"mar",
|
|
939
|
+
"apr",
|
|
940
|
+
"may",
|
|
941
|
+
"jun",
|
|
942
|
+
"jul",
|
|
943
|
+
"aug",
|
|
944
|
+
"sep",
|
|
945
|
+
"oct",
|
|
946
|
+
"nov",
|
|
947
|
+
"dec"
|
|
948
|
+
];
|
|
949
|
+
const SEASONS = [
|
|
950
|
+
"spring",
|
|
951
|
+
"summer",
|
|
952
|
+
"autumn",
|
|
953
|
+
"winter"
|
|
954
|
+
];
|
|
955
|
+
const NO_CALENDAR = {
|
|
956
|
+
hemisphere: "north",
|
|
957
|
+
holidays: []
|
|
958
|
+
};
|
|
959
|
+
const MAX_TOKEN = 60;
|
|
960
|
+
const daysIn = (year, month) => new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
961
|
+
const DATE = /^(?:(\d{4})-)?(\d{2})-(\d{2})$/;
|
|
962
|
+
function dateOf(text) {
|
|
963
|
+
const found = DATE.exec(text);
|
|
964
|
+
if (found === null) return void 0;
|
|
965
|
+
const [, year, month, day] = found;
|
|
966
|
+
const m = Number(month);
|
|
967
|
+
const d = Number(day);
|
|
968
|
+
if (m < 1 || m > 12 || d < 1 || d > daysIn(year === void 0 ? 2024 : Number(year), m)) return void 0;
|
|
969
|
+
return {
|
|
970
|
+
...year === void 0 ? {} : { year: Number(year) },
|
|
971
|
+
month: m,
|
|
972
|
+
day: d
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
/** What a token says, or why it says nothing. */
|
|
976
|
+
function parseDayToken(raw) {
|
|
977
|
+
const negated = raw.startsWith("!");
|
|
978
|
+
const text = (negated ? raw.slice(1) : raw).trim().toLowerCase();
|
|
979
|
+
const fail = (problem) => ({ problem: `"${raw}": ${problem}` });
|
|
980
|
+
if (text === "" || text.length > MAX_TOKEN) return fail("a day is told in a few words");
|
|
981
|
+
const weekday = WEEKDAY_NAMES.indexOf(text);
|
|
982
|
+
if (weekday >= 0) return {
|
|
983
|
+
token: {
|
|
984
|
+
kind: "weekday",
|
|
985
|
+
weekday
|
|
986
|
+
},
|
|
987
|
+
negated
|
|
988
|
+
};
|
|
989
|
+
const month = MONTH_NAMES.indexOf(text);
|
|
990
|
+
if (month >= 0) return {
|
|
991
|
+
token: {
|
|
992
|
+
kind: "month",
|
|
993
|
+
month: month + 1
|
|
994
|
+
},
|
|
995
|
+
negated
|
|
996
|
+
};
|
|
997
|
+
const one = dateOf(text);
|
|
998
|
+
if (one !== void 0) return {
|
|
999
|
+
token: {
|
|
1000
|
+
kind: "date",
|
|
1001
|
+
...one
|
|
1002
|
+
},
|
|
1003
|
+
negated
|
|
1004
|
+
};
|
|
1005
|
+
if (text.includes("..")) {
|
|
1006
|
+
const [from, to, ...rest] = text.split("..");
|
|
1007
|
+
const start = from === void 0 ? void 0 : dateOf(from);
|
|
1008
|
+
const end = to === void 0 ? void 0 : dateOf(to);
|
|
1009
|
+
if (rest.length > 0 || start === void 0 || end === void 0 || start.year === void 0 !== (end.year === void 0)) return fail("a stretch is two dates of the same kind: `12-20..01-05` every year, or `1912-09-21..1912-09-22` once");
|
|
1010
|
+
return {
|
|
1011
|
+
token: {
|
|
1012
|
+
kind: "span",
|
|
1013
|
+
from: start,
|
|
1014
|
+
to: end
|
|
1015
|
+
},
|
|
1016
|
+
negated
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
const [head, a, b] = text.split(":");
|
|
1020
|
+
if (head === "dom" && a !== void 0 && b === void 0) {
|
|
1021
|
+
if (a === "last") return {
|
|
1022
|
+
token: {
|
|
1023
|
+
kind: "dom",
|
|
1024
|
+
day: "last"
|
|
1025
|
+
},
|
|
1026
|
+
negated
|
|
1027
|
+
};
|
|
1028
|
+
const day = Number(a);
|
|
1029
|
+
return Number.isInteger(day) && day >= 1 && day <= 31 ? {
|
|
1030
|
+
token: {
|
|
1031
|
+
kind: "dom",
|
|
1032
|
+
day
|
|
1033
|
+
},
|
|
1034
|
+
negated
|
|
1035
|
+
} : fail("the day of the month is 1 to 31, or `dom:last`");
|
|
1036
|
+
}
|
|
1037
|
+
if (head === "nth" && a !== void 0 && b !== void 0) {
|
|
1038
|
+
const day = WEEKDAY_NAMES.indexOf(a);
|
|
1039
|
+
const n = b === "last" ? "last" : Number(b);
|
|
1040
|
+
return day >= 0 && (n === "last" || Number.isInteger(n) && n >= 1 && n <= 5) ? {
|
|
1041
|
+
token: {
|
|
1042
|
+
kind: "nth",
|
|
1043
|
+
weekday: day,
|
|
1044
|
+
n
|
|
1045
|
+
},
|
|
1046
|
+
negated
|
|
1047
|
+
} : fail("`nth:mon:1` is the first Monday of a month, `nth:fri:last` the last Friday");
|
|
1048
|
+
}
|
|
1049
|
+
if (head === "season" && a !== void 0 && b === void 0) {
|
|
1050
|
+
const season = SEASONS.find((candidate) => candidate === a);
|
|
1051
|
+
return season === void 0 ? fail(`a season is one of ${SEASONS.join(", ")}`) : {
|
|
1052
|
+
token: {
|
|
1053
|
+
kind: "season",
|
|
1054
|
+
season
|
|
1055
|
+
},
|
|
1056
|
+
negated
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
if (head === "holiday" && a !== void 0 && b === void 0 && a !== "") return {
|
|
1060
|
+
token: {
|
|
1061
|
+
kind: "holiday",
|
|
1062
|
+
id: a
|
|
1063
|
+
},
|
|
1064
|
+
negated
|
|
1065
|
+
};
|
|
1066
|
+
return fail("not a day: use a weekday (`mon`), a month (`dec`), a date (`12-24`, `1912-09-21`), a stretch (`12-20..01-05`), `dom:1`, `nth:mon:1`, `season:winter` or `holiday:<id>`");
|
|
1067
|
+
}
|
|
1068
|
+
/** What is wrong with a list of day tokens, each with where it is. `holidays` are those the world names, when `holiday:` may be used. */
|
|
1069
|
+
function dayTokenProblems(tokens, path, holidays) {
|
|
1070
|
+
return tokens.flatMap((raw) => {
|
|
1071
|
+
const parsed = parseDayToken(raw);
|
|
1072
|
+
if ("problem" in parsed) return [`${path}: ${parsed.problem}`];
|
|
1073
|
+
if (parsed.token.kind === "holiday") {
|
|
1074
|
+
if (holidays === void 0) return [`${path}: "${raw}": a holiday cannot be named here`];
|
|
1075
|
+
if (holidays !== "any" && !holidays.has(parsed.token.id)) return [`${path}: "${raw}": the world names no holiday "${parsed.token.id}"`];
|
|
1076
|
+
}
|
|
1077
|
+
return [];
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
/** The season a day is in, by the months of its hemisphere (winter is December to February in the north, June to August in the south). */
|
|
1081
|
+
function seasonOf(day, hemisphere = "north") {
|
|
1082
|
+
const north = day.month === 12 || day.month <= 2 ? "winter" : day.month <= 5 ? "spring" : day.month <= 8 ? "summer" : "autumn";
|
|
1083
|
+
if (hemisphere === "north") return north;
|
|
1084
|
+
return north === "winter" ? "summer" : north === "summer" ? "winter" : north === "spring" ? "autumn" : "spring";
|
|
1085
|
+
}
|
|
1086
|
+
const key = (month, day) => month * 100 + day;
|
|
1087
|
+
function within(day, from, to) {
|
|
1088
|
+
if (from.year !== void 0 && to.year !== void 0) {
|
|
1089
|
+
const at = day.year * 1e4 + key(day.month, day.day);
|
|
1090
|
+
return at >= from.year * 1e4 + key(from.month, from.day) && at <= to.year * 1e4 + key(to.month, to.day);
|
|
1091
|
+
}
|
|
1092
|
+
const at = key(day.month, day.day);
|
|
1093
|
+
const start = key(from.month, from.day);
|
|
1094
|
+
const end = key(to.month, to.day);
|
|
1095
|
+
return start <= end ? at >= start && at <= end : at >= start || at <= end;
|
|
1096
|
+
}
|
|
1097
|
+
function matches(token, day, calendar) {
|
|
1098
|
+
switch (token.kind) {
|
|
1099
|
+
case "weekday": return day.weekday === token.weekday;
|
|
1100
|
+
case "month": return day.month === token.month;
|
|
1101
|
+
case "date": return day.month === token.month && day.day === token.day && (token.year === void 0 || day.year === token.year);
|
|
1102
|
+
case "span": return within(day, token.from, token.to);
|
|
1103
|
+
case "dom": return token.day === "last" ? day.day === daysIn(day.year, day.month) : day.day === token.day;
|
|
1104
|
+
case "nth": return day.weekday === token.weekday && (token.n === "last" ? day.day + 7 > daysIn(day.year, day.month) : Math.ceil(day.day / 7) === token.n);
|
|
1105
|
+
case "season": return seasonOf(day, calendar.hemisphere) === token.season;
|
|
1106
|
+
case "holiday": return holidaysOn(calendar, day).some((holiday) => holiday.id === token.id);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
/** Whether a list of day tokens holds on a day; tokens that say nothing sensible count as not matching. An empty list holds on every day. */
|
|
1110
|
+
function dayMatches(tokens, day, calendar = NO_CALENDAR) {
|
|
1111
|
+
const parsed = tokens.flatMap((raw) => {
|
|
1112
|
+
const found = parseDayToken(raw);
|
|
1113
|
+
return "problem" in found ? [] : [found];
|
|
1114
|
+
});
|
|
1115
|
+
const wanted = parsed.filter((entry) => !entry.negated);
|
|
1116
|
+
const unwanted = parsed.filter((entry) => entry.negated);
|
|
1117
|
+
return (wanted.length === 0 || wanted.some((entry) => matches(entry.token, day, calendar))) && !unwanted.some((entry) => matches(entry.token, day, calendar));
|
|
1118
|
+
}
|
|
1119
|
+
/** The holidays that fall on a day. A holiday is told by days that are no holidays themselves, so that none can lead back to itself. */
|
|
1120
|
+
function holidaysOn(calendar, day) {
|
|
1121
|
+
return calendar.holidays.filter((holiday) => holiday.on.length > 0 && dayMatches(holiday.on, day, {
|
|
1122
|
+
...calendar,
|
|
1123
|
+
holidays: []
|
|
1124
|
+
}));
|
|
1125
|
+
}
|
|
1126
|
+
const CALENDAR_KEY = "calendar";
|
|
1127
|
+
/** The calendar a story was started with, from its metadata; none when the world has none. */
|
|
1128
|
+
function readCalendar(metadata) {
|
|
1129
|
+
const stored = metadata.getValue(CALENDAR_KEY);
|
|
1130
|
+
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) return NO_CALENDAR;
|
|
1131
|
+
const { hemisphere, holidays } = stored;
|
|
1132
|
+
return {
|
|
1133
|
+
hemisphere: hemisphere === "south" ? "south" : "north",
|
|
1134
|
+
holidays: Array.isArray(holidays) ? holidays.flatMap((entry) => {
|
|
1135
|
+
const { id, name, on } = entry ?? {};
|
|
1136
|
+
return typeof id === "string" && typeof name === "string" && Array.isArray(on) ? [{
|
|
1137
|
+
id,
|
|
1138
|
+
name,
|
|
1139
|
+
on: on.filter((token) => typeof token === "string")
|
|
1140
|
+
}] : [];
|
|
1141
|
+
}) : []
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
const writeCalendar = (metadata, calendar) => {
|
|
1145
|
+
metadata.setValue(CALENDAR_KEY, calendar);
|
|
1146
|
+
};
|
|
1147
|
+
//#endregion
|
|
710
1148
|
//#region src/records/time.ts
|
|
711
1149
|
/** Instants are UTC milliseconds; a time zone only decides how they read. */
|
|
712
1150
|
const SECOND = 1e3;
|
|
@@ -834,9 +1272,19 @@ function parseInstant(text, zone) {
|
|
|
834
1272
|
}
|
|
835
1273
|
throw new RangeError(`"${text}" is not a date and time: write it as 1912-10-04T19:30 (or with an offset, 1912-10-04T19:30+01:00)`);
|
|
836
1274
|
}
|
|
837
|
-
|
|
838
|
-
|
|
1275
|
+
const WEEKDAYS_ZH = [
|
|
1276
|
+
"星期日",
|
|
1277
|
+
"星期一",
|
|
1278
|
+
"星期二",
|
|
1279
|
+
"星期三",
|
|
1280
|
+
"星期四",
|
|
1281
|
+
"星期五",
|
|
1282
|
+
"星期六"
|
|
1283
|
+
];
|
|
1284
|
+
/** `Tuesday 4 October 1912, 21:40`, or `1912年10月4日 星期二 21:40`. */
|
|
1285
|
+
function describeInstant(instant, zone, language = "en") {
|
|
839
1286
|
const local = localTime(instant, zone);
|
|
1287
|
+
if (language === "zh") return `${local.year}年${local.month}月${local.day}日 ${WEEKDAYS_ZH[local.weekday]} ${pad$1(local.hour)}:${pad$1(local.minute)}`;
|
|
840
1288
|
return `${WEEKDAYS[local.weekday]} ${local.day} ${MONTHS[local.month - 1]} ${local.year}, ${pad$1(local.hour)}:${pad$1(local.minute)}`;
|
|
841
1289
|
}
|
|
842
1290
|
/** `1912-10-04T21:40:00` as the wall clock of a zone reads, the form a file or a person can write back. */
|
|
@@ -856,9 +1304,25 @@ function dayPart(minuteOfDay) {
|
|
|
856
1304
|
const hour = Math.floor(minuteOfDay / 60);
|
|
857
1305
|
return hour < 5 || hour >= 22 ? "night" : hour < 12 ? "morning" : hour < 17 ? "afternoon" : "evening";
|
|
858
1306
|
}
|
|
859
|
-
/** In words and rounded to what matters: `45 seconds`, `3 hours 12 minutes`, `2 days 3 hours
|
|
860
|
-
function describeDuration(milliseconds) {
|
|
1307
|
+
/** In words and rounded to what matters: `45 seconds`, `3 hours 12 minutes`, `2 days 3 hours`; or `45秒`, `3小时12分钟`, `2天3小时`. */
|
|
1308
|
+
function describeDuration(milliseconds, language = "en") {
|
|
861
1309
|
const total = Math.max(0, Math.round(milliseconds / SECOND));
|
|
1310
|
+
if (language === "zh") {
|
|
1311
|
+
if (total < 60) return `${total}秒`;
|
|
1312
|
+
const parts = [];
|
|
1313
|
+
let left = total;
|
|
1314
|
+
for (const [name, size] of [
|
|
1315
|
+
["天", 86400],
|
|
1316
|
+
["小时", 3600],
|
|
1317
|
+
["分钟", 60]
|
|
1318
|
+
]) {
|
|
1319
|
+
const count = Math.floor(left / size);
|
|
1320
|
+
left -= count * size;
|
|
1321
|
+
if (count > 0) parts.push(`${count}${name}`);
|
|
1322
|
+
if (parts.length === 2) break;
|
|
1323
|
+
}
|
|
1324
|
+
return parts.join("");
|
|
1325
|
+
}
|
|
862
1326
|
if (total < 60) return `${total} second${total === 1 ? "" : "s"}`;
|
|
863
1327
|
const units = [
|
|
864
1328
|
["day", 86400],
|
|
@@ -1114,6 +1578,8 @@ function prepareAgenda(target, edit, context) {
|
|
|
1114
1578
|
if (found < 0) problems.push(`${here}.days: "${day}" is not a day (${WEEKDAY_NAMES.join(", ")})`);
|
|
1115
1579
|
return found < 0 ? [] : [found];
|
|
1116
1580
|
});
|
|
1581
|
+
const holidays = new Set(readCalendar(target.metadata).holidays.map((holiday) => holiday.id));
|
|
1582
|
+
problems.push(...dayTokenProblems(list$1(window.on), `${here}.on`, holidays));
|
|
1117
1583
|
if (window.location !== void 0) {
|
|
1118
1584
|
placeKnown(window.location, `${here}.location`);
|
|
1119
1585
|
const place = context.places.get(window.location);
|
|
@@ -1122,6 +1588,7 @@ function prepareAgenda(target, edit, context) {
|
|
|
1122
1588
|
if (start === void 0 || end === void 0) return [];
|
|
1123
1589
|
return [{
|
|
1124
1590
|
days,
|
|
1591
|
+
on: [...list$1(window.on)],
|
|
1125
1592
|
start,
|
|
1126
1593
|
end,
|
|
1127
1594
|
activity: window.activity.trim(),
|
|
@@ -2143,8 +2610,11 @@ function parseCondition(value, path, problems, count, depth = 0) {
|
|
|
2143
2610
|
to: text(value["window"], "to") ?? ""
|
|
2144
2611
|
} : void 0;
|
|
2145
2612
|
const days = Array.isArray(value["days"]) ? value["days"].filter((day) => typeof day === "string") : void 0;
|
|
2146
|
-
|
|
2147
|
-
|
|
2613
|
+
const on = Array.isArray(value["on"]) ? value["on"].filter((day) => typeof day === "string") : void 0;
|
|
2614
|
+
if (value["on"] !== void 0 && on?.length !== value["on"]?.length) problems.push(`${path}.on: a list of days (months, dates, seasons, holidays)`);
|
|
2615
|
+
if (on !== void 0) problems.push(...dayTokenProblems(on, `${path}.on`, "any"));
|
|
2616
|
+
if (from === void 0 && to === void 0 && window === void 0 && days === void 0 && on === void 0) {
|
|
2617
|
+
problems.push(`${path}: a time condition needs from, to, window, days or on`);
|
|
2148
2618
|
return;
|
|
2149
2619
|
}
|
|
2150
2620
|
if (window !== void 0 && (parseTimeOfDay(window.from) === void 0 || parseTimeOfDay(window.to) === void 0 || window.from === window.to)) problems.push(`${path}.window: from and to are different times of day, HH:MM`);
|
|
@@ -2155,7 +2625,8 @@ function parseCondition(value, path, problems, count, depth = 0) {
|
|
|
2155
2625
|
...from === void 0 ? {} : { from },
|
|
2156
2626
|
...to === void 0 ? {} : { to },
|
|
2157
2627
|
...window === void 0 ? {} : { window },
|
|
2158
|
-
...days === void 0 ? {} : { days }
|
|
2628
|
+
...days === void 0 ? {} : { days },
|
|
2629
|
+
...on === void 0 ? {} : { on }
|
|
2159
2630
|
};
|
|
2160
2631
|
}
|
|
2161
2632
|
case "place": {
|
|
@@ -2407,7 +2878,7 @@ function conditionProblems(target, condition, path, known, self = false) {
|
|
|
2407
2878
|
const character = (key) => self && key === "@self" || target.characters.list().some((entry) => characterKey(entry) === key) ? [] : [`${path}: there is no character "${key}"`];
|
|
2408
2879
|
const place = (key) => target.locations.list().some((entry) => locationKey(entry) === key) ? [] : [`${path}: there is no place "${key}"`];
|
|
2409
2880
|
switch (condition.type) {
|
|
2410
|
-
case "time": return [];
|
|
2881
|
+
case "time": return dayTokenProblems(condition.on ?? [], `${path}.on`, new Set(readCalendar(target.metadata).holidays.map((holiday) => holiday.id)));
|
|
2411
2882
|
case "place": return [...character(condition.character), ...place(condition.location)];
|
|
2412
2883
|
case "variable": {
|
|
2413
2884
|
const def = target.variables.def(condition.variable);
|
|
@@ -2503,14 +2974,20 @@ function holds(target, condition, clock) {
|
|
|
2503
2974
|
if (condition.from !== void 0 && now < parseInstant(condition.from, zone)) return false;
|
|
2504
2975
|
if (condition.to !== void 0 && now >= parseInstant(condition.to, zone)) return false;
|
|
2505
2976
|
const local = localTime(now, zone);
|
|
2977
|
+
const calendar = readCalendar(target.metadata);
|
|
2978
|
+
/** The day a time belongs to: the morning part of a window past midnight belongs to the day before. */
|
|
2979
|
+
const dayAt = (shift) => addLocalDays(now, zone, shift);
|
|
2980
|
+
const onDay = (shift) => {
|
|
2981
|
+
const day = dayAt(shift);
|
|
2982
|
+
return (condition.days === void 0 || condition.days.length === 0 || condition.days.includes(WEEKDAY_NAMES[day.weekday] ?? "")) && (condition.on === void 0 || dayMatches(condition.on, day, calendar));
|
|
2983
|
+
};
|
|
2506
2984
|
if (condition.window !== void 0) {
|
|
2507
2985
|
const start = parseTimeOfDay(condition.window.from) ?? 0;
|
|
2508
2986
|
const end = parseTimeOfDay(condition.window.to) ?? 0;
|
|
2509
2987
|
if (!inWindow(local.minuteOfDay, start, end)) return false;
|
|
2510
|
-
|
|
2511
|
-
return condition.days === void 0 || condition.days.length === 0 || condition.days.includes(WEEKDAY_NAMES[weekday] ?? "");
|
|
2988
|
+
return onDay(start > end && local.minuteOfDay < end ? -1 : 0);
|
|
2512
2989
|
}
|
|
2513
|
-
return
|
|
2990
|
+
return onDay(0);
|
|
2514
2991
|
}
|
|
2515
2992
|
case "place": {
|
|
2516
2993
|
const who = target.characters.list().find((entry) => characterKey(entry) === condition.character);
|
|
@@ -2632,6 +3109,15 @@ function parseRecordsEdit(value) {
|
|
|
2632
3109
|
}
|
|
2633
3110
|
return found;
|
|
2634
3111
|
};
|
|
3112
|
+
const numbersOf = (entry, key) => {
|
|
3113
|
+
const found = entry[key];
|
|
3114
|
+
if (found === void 0 || found === null) return void 0;
|
|
3115
|
+
if (!Array.isArray(found) || found.some((item) => typeof item !== "number")) {
|
|
3116
|
+
problems.push(`${key} must be a list of numbers`);
|
|
3117
|
+
return;
|
|
3118
|
+
}
|
|
3119
|
+
return found;
|
|
3120
|
+
};
|
|
2635
3121
|
const text = (entry, key) => typeof entry[key] === "string" ? entry[key] : void 0;
|
|
2636
3122
|
/** A key, slot or position the model may leave blank instead of leaving out: blank is nothing given, not a name that does not exist. */
|
|
2637
3123
|
const named = (entry, key) => {
|
|
@@ -2924,13 +3410,19 @@ function parseRecordsEdit(value) {
|
|
|
2924
3410
|
kind: text(entry, "kind") ?? "",
|
|
2925
3411
|
text: text(entry, "text") ?? "",
|
|
2926
3412
|
about: strings(entry, "about"),
|
|
2927
|
-
importance: num(entry, "importance") ?? 0
|
|
3413
|
+
importance: num(entry, "importance") ?? 0,
|
|
3414
|
+
confidence: text(entry, "confidence"),
|
|
3415
|
+
evidenceFor: numbersOf(entry, "evidence_for"),
|
|
3416
|
+
evidenceAgainst: numbersOf(entry, "evidence_against")
|
|
2928
3417
|
})),
|
|
2929
3418
|
beliefEdits: array("belief_edits").map((entry) => defined({
|
|
2930
3419
|
id: num(entry, "id") ?? 0,
|
|
2931
3420
|
text: text(entry, "text"),
|
|
2932
3421
|
about: strings(entry, "about"),
|
|
2933
|
-
importance: num(entry, "importance")
|
|
3422
|
+
importance: num(entry, "importance"),
|
|
3423
|
+
confidence: text(entry, "confidence"),
|
|
3424
|
+
evidenceFor: numbersOf(entry, "evidence_for"),
|
|
3425
|
+
evidenceAgainst: numbersOf(entry, "evidence_against")
|
|
2934
3426
|
})),
|
|
2935
3427
|
beliefRetractions: numbers("belief_retractions"),
|
|
2936
3428
|
threadsOpened: Array.isArray(value["threads_opened"]) ? value["threads_opened"].filter((entry) => typeof entry === "string") : [],
|
|
@@ -3015,6 +3507,7 @@ function parseRecordsEdit(value) {
|
|
|
3015
3507
|
character: text(entry, "character") ?? "",
|
|
3016
3508
|
windows: Array.isArray(entry["windows"]) ? entry["windows"].filter(isObject$1).map((window) => defined({
|
|
3017
3509
|
days: strings(window, "days"),
|
|
3510
|
+
on: strings(window, "on"),
|
|
3018
3511
|
from: text(window, "from") ?? "",
|
|
3019
3512
|
to: text(window, "to") ?? "",
|
|
3020
3513
|
activity: text(window, "activity") ?? "",
|
|
@@ -3036,6 +3529,21 @@ const scalarValue = {
|
|
|
3036
3529
|
],
|
|
3037
3530
|
description: "A number, true or false, or one of the words of a choice."
|
|
3038
3531
|
};
|
|
3532
|
+
const CONFIDENCE = {
|
|
3533
|
+
type: "string",
|
|
3534
|
+
enum: [...MEMORY_CONFIDENCES],
|
|
3535
|
+
description: "How sure the character is: doubtful, likely (the default) or sure. Sure only for what it has seen more than once or cannot doubt."
|
|
3536
|
+
};
|
|
3537
|
+
const EVIDENCE_FOR = {
|
|
3538
|
+
type: "array",
|
|
3539
|
+
items: { type: "integer" },
|
|
3540
|
+
description: "The numbers of perceptions of the character (from read_records) that this rests on."
|
|
3541
|
+
};
|
|
3542
|
+
const EVIDENCE_AGAINST = {
|
|
3543
|
+
type: "array",
|
|
3544
|
+
items: { type: "integer" },
|
|
3545
|
+
description: "The numbers of perceptions of the character that speak against this; lower the confidence then."
|
|
3546
|
+
};
|
|
3039
3547
|
const IMPORTANCE = {
|
|
3040
3548
|
type: "integer",
|
|
3041
3549
|
enum: [
|
|
@@ -3196,7 +3704,10 @@ function recordsEditSchema(options = {}) {
|
|
|
3196
3704
|
},
|
|
3197
3705
|
text: { type: "string" },
|
|
3198
3706
|
about: keys,
|
|
3199
|
-
importance: IMPORTANCE
|
|
3707
|
+
importance: IMPORTANCE,
|
|
3708
|
+
confidence: CONFIDENCE,
|
|
3709
|
+
evidence_for: EVIDENCE_FOR,
|
|
3710
|
+
evidence_against: EVIDENCE_AGAINST
|
|
3200
3711
|
},
|
|
3201
3712
|
required: [
|
|
3202
3713
|
"character",
|
|
@@ -3893,6 +4404,11 @@ function recordsEditSchema(options = {}) {
|
|
|
3893
4404
|
},
|
|
3894
4405
|
description: "Leave out for every day."
|
|
3895
4406
|
},
|
|
4407
|
+
on: {
|
|
4408
|
+
type: "array",
|
|
4409
|
+
items: { type: "string" },
|
|
4410
|
+
description: "Other days it applies on, as well as days: months (dec), dates (12-24, 1912-09-21), stretches (12-20..01-05), dom:1, nth:mon:1 (first Monday), season:winter, holiday:<id>; a ! before one is a day it does not apply on (!holiday:festival). Leave out for every day."
|
|
4411
|
+
},
|
|
3896
4412
|
from: {
|
|
3897
4413
|
type: "string",
|
|
3898
4414
|
description: "HH:MM, 24-hour."
|
|
@@ -4258,7 +4774,10 @@ function recordsEditSchema(options = {}) {
|
|
|
4258
4774
|
id: { type: "integer" },
|
|
4259
4775
|
text: { type: "string" },
|
|
4260
4776
|
about: keys,
|
|
4261
|
-
importance: IMPORTANCE
|
|
4777
|
+
importance: IMPORTANCE,
|
|
4778
|
+
confidence: CONFIDENCE,
|
|
4779
|
+
evidence_for: EVIDENCE_FOR,
|
|
4780
|
+
evidence_against: EVIDENCE_AGAINST
|
|
4262
4781
|
},
|
|
4263
4782
|
required: ["id"],
|
|
4264
4783
|
additionalProperties: false
|
|
@@ -4604,26 +5123,49 @@ function applyChanges(target, edit) {
|
|
|
4604
5123
|
perception: one
|
|
4605
5124
|
}];
|
|
4606
5125
|
});
|
|
5126
|
+
const confidence = (value, where) => {
|
|
5127
|
+
if (value !== void 0 && !MEMORY_CONFIDENCES.includes(value)) problems.push(`${where}: confidence is one of ${MEMORY_CONFIDENCES.join(", ")}`);
|
|
5128
|
+
};
|
|
5129
|
+
/** Perceptions a belief of one character is said to rest on, or to be contradicted by: they must be the character's own. */
|
|
5130
|
+
const evidence = (value, characterId, where) => (value ?? []).flatMap((id) => {
|
|
5131
|
+
const seen = target.events.getPerception(id);
|
|
5132
|
+
if (seen === void 0) {
|
|
5133
|
+
problems.push(`${where}: there is no perception ${id}`);
|
|
5134
|
+
return [];
|
|
5135
|
+
}
|
|
5136
|
+
if (characterId !== void 0 && seen.characterId !== characterId) {
|
|
5137
|
+
problems.push(`${where}: perception ${id} is not one the character took in`);
|
|
5138
|
+
return [];
|
|
5139
|
+
}
|
|
5140
|
+
return [id];
|
|
5141
|
+
});
|
|
4607
5142
|
const beliefs = list(edit.beliefs).flatMap((belief, index) => {
|
|
4608
5143
|
const where = `beliefs[${index}]`;
|
|
4609
5144
|
const character = idOf(belief.character, where);
|
|
4610
5145
|
if (!MEMORY_KINDS.includes(belief.kind)) problems.push(`${where}: unknown kind "${String(belief.kind)}"`);
|
|
4611
5146
|
if (!present(belief.text)) problems.push(`${where}: say what the character believes or knows`);
|
|
4612
5147
|
importance(belief.importance, where);
|
|
5148
|
+
confidence(belief.confidence, where);
|
|
4613
5149
|
return character === void 0 ? [] : [{
|
|
4614
5150
|
belief,
|
|
4615
5151
|
character,
|
|
4616
|
-
about: ids(belief.about, `${where}.about`)
|
|
5152
|
+
about: ids(belief.about, `${where}.about`),
|
|
5153
|
+
evidenceFor: evidence(belief.evidenceFor, character, `${where}.evidence_for`),
|
|
5154
|
+
evidenceAgainst: evidence(belief.evidenceAgainst, character, `${where}.evidence_against`)
|
|
4617
5155
|
}];
|
|
4618
5156
|
});
|
|
4619
5157
|
const beliefEdits = list(edit.beliefEdits).map((change, index) => {
|
|
4620
5158
|
const where = `belief_edits[${index}]`;
|
|
4621
|
-
|
|
5159
|
+
const current = target.memories.get(change.id);
|
|
5160
|
+
if (current === void 0) problems.push(`${where}: there is no belief ${change.id}`);
|
|
4622
5161
|
if (change.text !== void 0 && !present(change.text)) problems.push(`${where}: say what the character believes or knows`);
|
|
4623
5162
|
importance(change.importance, where);
|
|
5163
|
+
confidence(change.confidence, where);
|
|
4624
5164
|
return {
|
|
4625
5165
|
change,
|
|
4626
|
-
about: change.about === void 0 ? void 0 : ids(change.about, `${where}.about`)
|
|
5166
|
+
about: change.about === void 0 ? void 0 : ids(change.about, `${where}.about`),
|
|
5167
|
+
evidenceFor: evidence(change.evidenceFor, current?.characterId, `${where}.evidence_for`),
|
|
5168
|
+
evidenceAgainst: evidence(change.evidenceAgainst, current?.characterId, `${where}.evidence_against`)
|
|
4627
5169
|
};
|
|
4628
5170
|
});
|
|
4629
5171
|
for (const id of list(edit.beliefRetractions)) if (target.memories.get(id) === void 0) problems.push(`belief_retractions: there is no belief ${id}`);
|
|
@@ -4898,23 +5440,30 @@ function applyChanges(target, edit) {
|
|
|
4898
5440
|
learnEventEntities(target.events.get(eventId), perception.characterId);
|
|
4899
5441
|
result.writes += 1;
|
|
4900
5442
|
}
|
|
4901
|
-
for (const { belief, character, about } of beliefs) {
|
|
4902
|
-
|
|
5443
|
+
for (const { belief, character, about, evidenceFor, evidenceAgainst } of beliefs) {
|
|
5444
|
+
const added = target.memories.add({
|
|
4903
5445
|
characterId: character,
|
|
4904
5446
|
kind: belief.kind,
|
|
4905
5447
|
text: belief.text.trim(),
|
|
4906
5448
|
about,
|
|
4907
5449
|
importance: belief.importance,
|
|
5450
|
+
...belief.confidence === void 0 ? {} : { confidence: belief.confidence },
|
|
4908
5451
|
turn: edit.turn
|
|
4909
|
-
})
|
|
5452
|
+
});
|
|
5453
|
+
result.beliefIds.push(added.id);
|
|
5454
|
+
for (const id of evidenceFor) target.memories.addEvidence(added.id, id, "supports", edit.turn);
|
|
5455
|
+
for (const id of evidenceAgainst) target.memories.addEvidence(added.id, id, "contradicts", edit.turn);
|
|
4910
5456
|
result.writes += 1;
|
|
4911
5457
|
}
|
|
4912
|
-
for (const { change, about } of beliefEdits) {
|
|
5458
|
+
for (const { change, about, evidenceFor, evidenceAgainst } of beliefEdits) {
|
|
4913
5459
|
target.memories.update(change.id, {
|
|
4914
5460
|
...change.text === void 0 ? {} : { text: change.text.trim() },
|
|
4915
5461
|
...about === void 0 ? {} : { about },
|
|
4916
|
-
...change.importance === void 0 ? {} : { importance: change.importance }
|
|
5462
|
+
...change.importance === void 0 ? {} : { importance: change.importance },
|
|
5463
|
+
...change.confidence === void 0 ? {} : { confidence: change.confidence }
|
|
4917
5464
|
});
|
|
5465
|
+
for (const id of evidenceFor) target.memories.addEvidence(change.id, id, "supports", edit.turn);
|
|
5466
|
+
for (const id of evidenceAgainst) target.memories.addEvidence(change.id, id, "contradicts", edit.turn);
|
|
4918
5467
|
result.writes += 1;
|
|
4919
5468
|
}
|
|
4920
5469
|
for (const id of list(edit.beliefRetractions)) {
|
|
@@ -5480,7 +6029,8 @@ const DEFAULT_MEMORY_POLICY = {
|
|
|
5480
6029
|
recall: 12,
|
|
5481
6030
|
overRetrieve: 4,
|
|
5482
6031
|
associate: 2,
|
|
5483
|
-
diversity: .3
|
|
6032
|
+
diversity: .3,
|
|
6033
|
+
foldedFade: .5
|
|
5484
6034
|
};
|
|
5485
6035
|
/** Story days for the memory of an unimportant event to halve; each step of importance doubles it. */
|
|
5486
6036
|
const HALF_LIFE_DAYS = 6;
|
|
@@ -5569,7 +6119,7 @@ function describeEvent({ perception, event }, names, at) {
|
|
|
5569
6119
|
vague: about === "" ? "something happened, but you cannot say what" : `something to do with ${about}`
|
|
5570
6120
|
};
|
|
5571
6121
|
}
|
|
5572
|
-
function describeBelief(memory, at) {
|
|
6122
|
+
function describeBelief(memory, at, disputed) {
|
|
5573
6123
|
return {
|
|
5574
6124
|
turn: memory.turn,
|
|
5575
6125
|
...at === void 0 ? {} : { at },
|
|
@@ -5577,6 +6127,8 @@ function describeBelief(memory, at) {
|
|
|
5577
6127
|
kind: "memory",
|
|
5578
6128
|
id: memory.id
|
|
5579
6129
|
},
|
|
6130
|
+
confidence: memory.confidence,
|
|
6131
|
+
...disputed ? { disputed: true } : {},
|
|
5580
6132
|
clear: memory.text,
|
|
5581
6133
|
faded: first(memory.text),
|
|
5582
6134
|
vague: first(memory.text)
|
|
@@ -5611,7 +6163,9 @@ const DENSE_RANGE = .5;
|
|
|
5611
6163
|
const denseScore = (similarity) => Math.min(1, Math.max(0, (similarity - DENSE_FLOOR) / DENSE_RANGE));
|
|
5612
6164
|
/** Being where something happened calls it to mind, about as much as sharing two words with it. */
|
|
5613
6165
|
const PLACE_CUE = 2;
|
|
5614
|
-
function gather(source, { characterId, characters, situation, locationId, about }) {
|
|
6166
|
+
function gather(source, { characterId, characters, situation, locationId, about, policy }) {
|
|
6167
|
+
const evidence = source.memories.evidenceIndex?.(characterId);
|
|
6168
|
+
const folding = Math.min(1, Math.max(.05, policy?.foldedFade ?? 1));
|
|
5615
6169
|
const names = new Map(characters.map((character) => [character.id, character.name]));
|
|
5616
6170
|
const peers = mentioned(situation, characters);
|
|
5617
6171
|
peers.delete(characterId);
|
|
@@ -5651,7 +6205,8 @@ function gather(source, { characterId, characters, situation, locationId, about
|
|
|
5651
6205
|
ageDays: ageDays(at),
|
|
5652
6206
|
recalls: seen.perception.recalls,
|
|
5653
6207
|
seed: seen.perception.id,
|
|
5654
|
-
durability: 1
|
|
6208
|
+
durability: evidence?.folded.has(seen.perception.id) === true ? folding : 1,
|
|
6209
|
+
...evidence?.folded.has(seen.perception.id) === true ? { covered: evidence.folded.get(seen.perception.id) } : {}
|
|
5655
6210
|
};
|
|
5656
6211
|
}), ...source.memories.list(characterId).filter((belief) => concerns(belief.about)).map((belief) => {
|
|
5657
6212
|
const at = atTurn(belief.turn);
|
|
@@ -5666,7 +6221,7 @@ function gather(source, { characterId, characters, situation, locationId, about
|
|
|
5666
6221
|
people: belief.about.filter((id) => id !== characterId),
|
|
5667
6222
|
tags: []
|
|
5668
6223
|
},
|
|
5669
|
-
recalled: describeBelief(belief, at),
|
|
6224
|
+
recalled: describeBelief(belief, at, evidence?.disputed.has(belief.id) === true),
|
|
5670
6225
|
cues: cue,
|
|
5671
6226
|
relevance: cue.people + cue.words,
|
|
5672
6227
|
importance: belief.importance,
|
|
@@ -5826,6 +6381,28 @@ function diversified(ranked, count, diversity) {
|
|
|
5826
6381
|
}
|
|
5827
6382
|
return chosen;
|
|
5828
6383
|
}
|
|
6384
|
+
/** How sure the character is of a belief, and whether something since speaks against it. */
|
|
6385
|
+
function beliefOf(candidate) {
|
|
6386
|
+
const { confidence, disputed } = candidate.recalled;
|
|
6387
|
+
return {
|
|
6388
|
+
...confidence === void 0 ? {} : { confidence },
|
|
6389
|
+
...disputed === void 0 ? {} : { disputed }
|
|
6390
|
+
};
|
|
6391
|
+
}
|
|
6392
|
+
/**
|
|
6393
|
+
* Chooses as {@link diversified} does, but does not tell what the character took in when a belief resting on it is told as well: the belief says it,
|
|
6394
|
+
* and the place goes to the next best.
|
|
6395
|
+
*/
|
|
6396
|
+
function chooseUncovered(standing, count, diversity) {
|
|
6397
|
+
let pool = standing;
|
|
6398
|
+
for (;;) {
|
|
6399
|
+
const chosen = diversified(pool, count, diversity);
|
|
6400
|
+
const told = new Set(chosen.filter((entry) => entry.candidate.recalled.source.kind === "memory").map((entry) => entry.candidate.recalled.source.id));
|
|
6401
|
+
const repeated = chosen.filter((entry) => entry.candidate.covered?.some((id) => told.has(id)) === true);
|
|
6402
|
+
if (repeated.length === 0) return chosen;
|
|
6403
|
+
pool = pool.filter((entry) => !repeated.includes(entry));
|
|
6404
|
+
}
|
|
6405
|
+
}
|
|
5829
6406
|
/**
|
|
5830
6407
|
* The last stage: choose the `policy.recall` best of the pool, spread by `policy.diversity`. With a reranker's judgement each
|
|
5831
6408
|
* candidate's relevance is mostly that (a candidate it left out counts as 0), and the rest is what
|
|
@@ -5854,13 +6431,14 @@ function finishRecall(pool, rerank) {
|
|
|
5854
6431
|
why
|
|
5855
6432
|
}];
|
|
5856
6433
|
}).sort((a, b) => b.score - a.score || b.candidate.recalled.turn - a.candidate.recalled.turn);
|
|
5857
|
-
const chosen =
|
|
6434
|
+
const chosen = chooseUncovered(standing, policy.recall, policy.diversity ?? 0);
|
|
5858
6435
|
const result = chosen.map(({ candidate, clarity, text, why }) => ({
|
|
5859
6436
|
clarity,
|
|
5860
6437
|
text,
|
|
5861
6438
|
turn: candidate.recalled.turn,
|
|
5862
6439
|
...candidate.recalled.at === void 0 ? {} : { at: candidate.recalled.at },
|
|
5863
6440
|
source: candidate.recalled.source,
|
|
6441
|
+
...beliefOf(candidate),
|
|
5864
6442
|
...why === void 0 ? {} : { why }
|
|
5865
6443
|
}));
|
|
5866
6444
|
const anchors = chosen.filter((entry) => entry.relevance >= CUE_RELEVANCE);
|
|
@@ -5882,6 +6460,7 @@ function finishRecall(pool, rerank) {
|
|
|
5882
6460
|
turn: candidate.recalled.turn,
|
|
5883
6461
|
...candidate.recalled.at === void 0 ? {} : { at: candidate.recalled.at },
|
|
5884
6462
|
source: candidate.recalled.source,
|
|
6463
|
+
...beliefOf(candidate),
|
|
5885
6464
|
linked: true,
|
|
5886
6465
|
...pool.query.explain === true ? { why: {
|
|
5887
6466
|
cues: candidate.cues,
|
|
@@ -6031,15 +6610,21 @@ function readRecords(target, scope = {}) {
|
|
|
6031
6610
|
gist: entry.gist
|
|
6032
6611
|
}))
|
|
6033
6612
|
})),
|
|
6034
|
-
beliefs: characters.flatMap((character) => target.memories.list(character.id).filter((belief) => inScope(belief.turn)).map((belief) =>
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6613
|
+
beliefs: characters.flatMap((character) => target.memories.list(character.id).filter((belief) => inScope(belief.turn)).map((belief) => {
|
|
6614
|
+
const evidence = target.memories.evidenceOf(belief.id);
|
|
6615
|
+
return {
|
|
6616
|
+
id: belief.id,
|
|
6617
|
+
character: characterKey(character),
|
|
6618
|
+
kind: belief.kind,
|
|
6619
|
+
text: belief.text,
|
|
6620
|
+
about: belief.about.map(keyOf),
|
|
6621
|
+
importance: belief.importance,
|
|
6622
|
+
confidence: belief.confidence,
|
|
6623
|
+
evidence_for: evidence.filter((entry) => entry.relation === "supports").map((entry) => entry.perceptionId),
|
|
6624
|
+
evidence_against: evidence.filter((entry) => entry.relation === "contradicts").map((entry) => entry.perceptionId),
|
|
6625
|
+
turn: belief.turn
|
|
6626
|
+
};
|
|
6627
|
+
})),
|
|
6043
6628
|
open_threads: target.threads.list("open").map((thread) => ({
|
|
6044
6629
|
id: thread.id,
|
|
6045
6630
|
text: thread.text,
|
|
@@ -6400,6 +6985,7 @@ const toMemory = (row) => ({
|
|
|
6400
6985
|
text: row.text,
|
|
6401
6986
|
about: parseNumberList(row.about, "memories.about"),
|
|
6402
6987
|
importance: row.importance,
|
|
6988
|
+
confidence: row.confidence,
|
|
6403
6989
|
turn: row.turn,
|
|
6404
6990
|
recalls: row.recalls,
|
|
6405
6991
|
...row.last_recalled_turn === null ? {} : { lastRecalledTurn: row.last_recalled_turn },
|
|
@@ -6416,12 +7002,13 @@ var SqliteMemoryRepository = class {
|
|
|
6416
7002
|
}
|
|
6417
7003
|
add(memory) {
|
|
6418
7004
|
return guard(() => {
|
|
6419
|
-
const { lastInsertRowid } = this.db.run("INSERT INTO memories (character_id, kind, text, about, importance, turn, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [
|
|
7005
|
+
const { lastInsertRowid } = this.db.run("INSERT INTO memories (character_id, kind, text, about, importance, confidence, turn, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
6420
7006
|
memory.characterId,
|
|
6421
7007
|
memory.kind,
|
|
6422
7008
|
memory.text,
|
|
6423
7009
|
toJson(memory.about),
|
|
6424
7010
|
memory.importance,
|
|
7011
|
+
memory.confidence ?? "likely",
|
|
6425
7012
|
memory.turn,
|
|
6426
7013
|
this.clock().toISOString()
|
|
6427
7014
|
]);
|
|
@@ -6445,10 +7032,11 @@ var SqliteMemoryRepository = class {
|
|
|
6445
7032
|
update(id, patch) {
|
|
6446
7033
|
return guard(() => {
|
|
6447
7034
|
const current = this.require(id);
|
|
6448
|
-
this.db.run("UPDATE memories SET text = ?, about = ?, importance = ? WHERE id = ?", [
|
|
7035
|
+
this.db.run("UPDATE memories SET text = ?, about = ?, importance = ?, confidence = ? WHERE id = ?", [
|
|
6449
7036
|
patch.text ?? current.text,
|
|
6450
7037
|
toJson(patch.about ?? current.about),
|
|
6451
7038
|
patch.importance ?? current.importance,
|
|
7039
|
+
patch.confidence ?? current.confidence,
|
|
6452
7040
|
id
|
|
6453
7041
|
]);
|
|
6454
7042
|
const changed = this.require(id);
|
|
@@ -6470,6 +7058,36 @@ var SqliteMemoryRepository = class {
|
|
|
6470
7058
|
this.index.remove("memory", id);
|
|
6471
7059
|
return this.db.run("DELETE FROM memories WHERE id = ?", [id]).changes > 0;
|
|
6472
7060
|
}
|
|
7061
|
+
addEvidence(memoryId, perceptionId, relation, turn) {
|
|
7062
|
+
this.require(memoryId);
|
|
7063
|
+
guard(() => this.db.run("INSERT INTO memory_evidence (memory_id, perception_id, relation, turn) VALUES (?, ?, ?, ?) ON CONFLICT (memory_id, perception_id) DO UPDATE SET relation = excluded.relation, turn = excluded.turn", [
|
|
7064
|
+
memoryId,
|
|
7065
|
+
perceptionId,
|
|
7066
|
+
relation,
|
|
7067
|
+
turn
|
|
7068
|
+
]));
|
|
7069
|
+
}
|
|
7070
|
+
evidenceOf(memoryId) {
|
|
7071
|
+
return this.db.all("SELECT * FROM memory_evidence WHERE memory_id = ? ORDER BY perception_id", [memoryId]).map((row) => ({
|
|
7072
|
+
memoryId: row.memory_id,
|
|
7073
|
+
perceptionId: row.perception_id,
|
|
7074
|
+
relation: row.relation,
|
|
7075
|
+
turn: row.turn
|
|
7076
|
+
}));
|
|
7077
|
+
}
|
|
7078
|
+
evidenceIndex(characterId) {
|
|
7079
|
+
const rows = this.db.all("SELECT e.memory_id, e.perception_id, e.relation FROM memory_evidence e JOIN memories m ON m.id = e.memory_id WHERE m.character_id = ? ORDER BY e.memory_id", [characterId]);
|
|
7080
|
+
const folded = /* @__PURE__ */ new Map();
|
|
7081
|
+
const disputed = /* @__PURE__ */ new Set();
|
|
7082
|
+
for (const row of rows) {
|
|
7083
|
+
folded.set(row.perception_id, [...folded.get(row.perception_id) ?? [], row.memory_id]);
|
|
7084
|
+
if (row.relation === "contradicts") disputed.add(row.memory_id);
|
|
7085
|
+
}
|
|
7086
|
+
return {
|
|
7087
|
+
folded,
|
|
7088
|
+
disputed
|
|
7089
|
+
};
|
|
7090
|
+
}
|
|
6473
7091
|
require(id) {
|
|
6474
7092
|
const memory = this.get(id);
|
|
6475
7093
|
if (memory === void 0) throw new NotFoundError(`memory ${id} does not exist`);
|
|
@@ -8627,6 +9245,21 @@ const MIGRATIONS = [
|
|
|
8627
9245
|
INSERT INTO entity_knowledge SELECT * FROM old_entity_knowledge;
|
|
8628
9246
|
DROP TABLE old_entity_knowledge;
|
|
8629
9247
|
CREATE INDEX entity_knowledge_subject ON entity_knowledge (subject_kind, subject_id);
|
|
9248
|
+
`
|
|
9249
|
+
},
|
|
9250
|
+
{
|
|
9251
|
+
version: 7,
|
|
9252
|
+
sql: `
|
|
9253
|
+
ALTER TABLE memories ADD COLUMN confidence TEXT NOT NULL DEFAULT 'likely' CHECK (confidence IN ('doubtful', 'likely', 'sure'));
|
|
9254
|
+
CREATE TABLE memory_evidence (
|
|
9255
|
+
memory_id INTEGER NOT NULL REFERENCES memories (id) ON DELETE CASCADE,
|
|
9256
|
+
perception_id INTEGER NOT NULL REFERENCES perceptions (id) ON DELETE CASCADE,
|
|
9257
|
+
relation TEXT NOT NULL CHECK (relation IN ('supports', 'contradicts')),
|
|
9258
|
+
turn INTEGER NOT NULL,
|
|
9259
|
+
PRIMARY KEY (memory_id, perception_id)
|
|
9260
|
+
) STRICT;
|
|
9261
|
+
CREATE INDEX memory_evidence_perception ON memory_evidence (perception_id);
|
|
9262
|
+
ALTER TABLE routine_windows ADD COLUMN filters TEXT NOT NULL DEFAULT '[]';
|
|
8630
9263
|
`
|
|
8631
9264
|
}
|
|
8632
9265
|
];
|
|
@@ -10004,8 +10637,9 @@ function lintWorld(target) {
|
|
|
10004
10637
|
//#region src/records/agenda.ts
|
|
10005
10638
|
/** An engagement still pending this long after it was due (or over) is overdue. */
|
|
10006
10639
|
const ENGAGEMENT_GRACE = 30 * MINUTE;
|
|
10007
|
-
function occurrenceOn(window, day, zone) {
|
|
10640
|
+
function occurrenceOn(window, day, zone, calendar) {
|
|
10008
10641
|
if (window.days.length > 0 && !window.days.includes(day.weekday)) return void 0;
|
|
10642
|
+
if (window.on.length > 0 && !dayMatches(window.on, day, calendar)) return void 0;
|
|
10009
10643
|
const startsAt = zonedInstant({
|
|
10010
10644
|
...day,
|
|
10011
10645
|
hour: Math.floor(window.start / 60),
|
|
@@ -10022,15 +10656,15 @@ function occurrenceOn(window, day, zone) {
|
|
|
10022
10656
|
};
|
|
10023
10657
|
}
|
|
10024
10658
|
/** The window that holds at an instant (the first of the list that does), with the stretch of this occurrence of it. */
|
|
10025
|
-
function occurrenceAt(windows, now, zone) {
|
|
10659
|
+
function occurrenceAt(windows, now, zone, calendar = NO_CALENDAR) {
|
|
10026
10660
|
const days = [addLocalDays(now, zone, 0), addLocalDays(now, zone, -1)];
|
|
10027
10661
|
for (const window of windows) for (const day of days) {
|
|
10028
|
-
const occurrence = occurrenceOn(window, day, zone);
|
|
10662
|
+
const occurrence = occurrenceOn(window, day, zone, calendar);
|
|
10029
10663
|
if (occurrence !== void 0 && occurrence.startsAt <= now && now < occurrence.endsAt) return occurrence;
|
|
10030
10664
|
}
|
|
10031
10665
|
}
|
|
10032
10666
|
/** The window that starts next after an instant, within `horizon`. */
|
|
10033
|
-
function nextOccurrence(windows, now, zone, horizon) {
|
|
10667
|
+
function nextOccurrence(windows, now, zone, horizon, calendar = NO_CALENDAR) {
|
|
10034
10668
|
let best;
|
|
10035
10669
|
for (const offset of [
|
|
10036
10670
|
0,
|
|
@@ -10039,7 +10673,7 @@ function nextOccurrence(windows, now, zone, horizon) {
|
|
|
10039
10673
|
]) {
|
|
10040
10674
|
const day = addLocalDays(now, zone, offset);
|
|
10041
10675
|
for (const window of windows) {
|
|
10042
|
-
const occurrence = occurrenceOn(window, day, zone);
|
|
10676
|
+
const occurrence = occurrenceOn(window, day, zone, calendar);
|
|
10043
10677
|
if (occurrence === void 0 || occurrence.startsAt <= now || occurrence.startsAt > now + horizon) continue;
|
|
10044
10678
|
if (best === void 0 || occurrence.startsAt < best.startsAt) best = occurrence;
|
|
10045
10679
|
}
|
|
@@ -10072,8 +10706,9 @@ const DEFAULTS = {
|
|
|
10072
10706
|
function agendaAt(source, characterId, now, zone, options = {}) {
|
|
10073
10707
|
const horizon = options.horizon ?? DEFAULTS.horizon;
|
|
10074
10708
|
const windows = source.routines.list(characterId);
|
|
10075
|
-
const
|
|
10076
|
-
const
|
|
10709
|
+
const calendar = source.metadata === void 0 ? NO_CALENDAR : readCalendar(source.metadata);
|
|
10710
|
+
const current = occurrenceAt(windows, now, zone, calendar);
|
|
10711
|
+
const next = nextOccurrence(windows, now, zone, horizon, calendar);
|
|
10077
10712
|
const engagements = source.engagements.list({
|
|
10078
10713
|
status: "pending",
|
|
10079
10714
|
participant: characterId,
|
|
@@ -11826,6 +12461,6 @@ function createCharacterTools(link, characterId) {
|
|
|
11826
12461
|
];
|
|
11827
12462
|
}
|
|
11828
12463
|
//#endregion
|
|
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 };
|
|
12464
|
+
export { ACCURACIES, CHARACTER_CONTROLS, CHARACTER_KINDS, CHARACTER_ROLES, CHARACTER_TIERS, CLOCK_MODES, COMPARISONS, CONSOLIDATION_KINDS, CONTAINER_STATES, Config, ConflictError, CorruptDataError, DAY, DAY_PARTS, DEFAULT_CONFIDENCE, DEFAULT_MEMORY_POLICY, DEFAULT_SLOTS, EMOTION_VARIABLES, ENGAGEMENT_GRACE, ENGAGEMENT_STATUSES, EVIDENCE_RELATIONS, EXIT_STATES, HOUR, IMPORTANCE_MAX, IMPORTANCE_MIN, INTENT_HORIZONS, INTENT_STATUSES, INTENT_TYPES, ITEM_ACTIONS, InvalidEditError, InvalidMoveError, InvalidValueError, KNOWLEDGE_KINDS, LANGUAGES, LORE_KINDS, LORE_SUBJECT_KINDS, MAX_LOCATION_FEATURES, MAX_ROUTINE_WINDOWS, MAX_SHOWN, MAX_TRIGGERS, MAX_VARIABLES, MEMBERSHIP_STATUSES, MEMORY_CONFIDENCES, MEMORY_KINDS, MIGRATION_BACKUP_FOLDER, MINUTE, MONTH_NAMES, NO_CALENDAR, NotFoundError, PERCEPTION_MODES, PLACEABLE_KINDS, PLOT_NUDGE_MODES, PLOT_STATUSES, RECALL_ITEM_KINDS, RoleplayState, LATEST_VERSION as SCHEMA_VERSION, SEASONS, 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, dayMatches, dayPart, dayTokenProblems, deleteSession, deliverNotes, denseScore, describeDuration, describeHolder, describeInstant, describeValue, finishRecall, fuseScores, holidaysOn, inAudience, inWindow, isSessionId, isTimeZone, itemKey, jitter, judgedStatements, languageOf, lapseNotes, lintWorld, listSessions, localIso, localTime, locationKey, mentioned, migrationBackupPath, moveCharacter, name, nextOccurrence, nextTimeOfDay, nodeSqliteDriver, occurrenceAt, openSession, openSessionStore, overlap, parseDayToken, parseInstant, parseRecordsEdit, parseTimeOfDay, parseTriggerDefinition, placeholdersOf, planItemChange, planKnownMove, planMove, planVariableChange, poolCandidates, rankGoals, readCalendar, readRecords, readTriggerDefinition, recall, recallCandidates, recallPool, recordsEditSchema, renderWords, retention, runTriggers, seasonOf, seededRoll, sessionDatabase, sessionFolder, shownVariables, subjectOf, systemClock, systemTimeZone, tokens, translated, translation, translator, triggerReferenceProblems, valueIssue, writeCalendar, zonedInstant };
|
|
11830
12465
|
|
|
11831
12466
|
//# sourceMappingURL=index.js.map
|