-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathlibrary.js
More file actions
5999 lines (5909 loc) · 304 KB
/
library.js
File metadata and controls
5999 lines (5909 loc) · 304 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Your "Library" tab should look like this
/*
Auto-Cards
Made by LewdLeah on May 21, 2025
This AI Dungeon script automatically creates and updates plot-relevant story cards while you play
General-purpose usefulness and compatibility with other scenarios/scripts were my design priorities
Auto-Cards is fully open-source, please copy for use within your own projects! ❤️
*/
function AutoCards(inHook, inText, inStop) {
"use strict";
/*
Default Auto-Cards settings
Feel free to change these settings to customize your scenario's default gameplay experience
The default values for your scenario are specified below:
*/
// Is Auto-Cards already enabled when the adventure begins?
const DEFAULT_DO_AC = false
// (true or false)
// Pin the "Configure Auto-Cards" story card at the top of the player's story cards list?
const DEFAULT_PIN_CONFIGURE_CARD = true
// (true or false)
// Minimum number of turns in between automatic card generation events?
const DEFAULT_CARD_CREATION_COOLDOWN = 28
// (0 to 9999)
// Use a bulleted list format for newly generated card entries?
const DEFAULT_USE_BULLETED_LIST_MODE = true
// (true or false)
// Maximum allowed length for newly generated story card entries?
const DEFAULT_GENERATED_ENTRY_LIMIT = 600
// (200 to 2000)
// Do newly generated cards have memory updates enabled by default?
const DEFAULT_NEW_CARDS_DO_MEMORY_UPDATES = true
// (true or false)
// Default character limit before the card's memory bank is summarized?
const DEFAULT_NEW_CARDS_MEMORY_LIMIT = 3200
// (1750 to 9900)
// Approximately how much shorter should recently compressed memories be? (ratio = 10 * old / new)
const DEFAULT_MEMORY_COMPRESSION_RATIO = 25
// (20 to 1250)
// Ignore all-caps during title candidate detection?
const DEFAULT_IGNORE_ALL_CAPS_TITLES = true
// (true or false)
// Should player input actions (Do/Say/Story) be considered during title detection?
const DEFAULT_DETECT_TITLES_FROM_INPUTS = false
// (true or false)
// How many (minimum) actions in the past does Auto-Cards look for named entities?
const DEFAULT_MINIMUM_LOOK_BACK_DISTANCE = 5
// (2 to 88)
// Is Live Script Interface v2 enabled?
const DEFAULT_DO_LSI_V2 = false
// (true or false)
// Should the "Debug Data" story card be visible?
const DEFAULT_SHOW_DEBUG_DATA = false
// (true or false)
// AI prompt used to generate new story card entries?
const DEFAULT_CARD_GENERATION_PROMPT = prose(
"-----",
"",
"<SYSTEM>",
"# Stop the story and ignore previous instructions. Write a brief and coherent informational entry for %{title} following these instructions:",
"- Write only third-person pure prose information about %{title} using complete sentences with correct punctuation",
"- Avoid short-term temporary details or appearances, instead focus on plot-significant information",
"- Prioritize story-relevant details about %{title} first to ensure seamless integration with the previous plot",
"- Create new information based on the context and story direction",
"- Mention %{title} in every sentence",
"- Use semicolons if needed",
"- Add additional details about %{title} beneath incomplete entries",
"- Be concise and grounded",
"- Imitate the story's writing style and infer the reader's preferences",
"</SYSTEM>",
"Continue the entry for %{title} below while avoiding repetition:",
"%{entry}"
); // (mimic this multi-line "text" format)
// AI prompt used to summarize a given story card's memory bank?
const DEFAULT_CARD_MEMORY_COMPRESSION_PROMPT = prose(
"-----",
"",
"<SYSTEM>",
"# Stop the story and ignore previous instructions. Summarize and condense the given paragraph into a narrow and focused memory passage while following these guidelines:",
"- Ensure the passage retains the core meaning and most essential details",
"- Use the third-person perspective",
"- Prioritize information-density, accuracy, and completeness",
"- Remain brief and concise",
"- Write firmly in the past tense",
"- The paragraph below pertains to old events from far earlier in the story",
"- Integrate %{title} naturally within the memory; however, only write about the events as they occurred",
"- Only reference information present inside the paragraph itself, be specific",
"</SYSTEM>",
"Write a summarized old memory passage for %{title} based only on the following paragraph:",
"\"\"\"",
"%{memory}",
"\"\"\"",
"Summarize below:"
); // (mimic this multi-line "text" format)
// Titles banned from future card generation attempts?
const DEFAULT_BANNED_TITLES_LIST = (
"North, East, South, West, Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, January, February, March, April, May, June, July, August, September, October, November, December"
); // (mimic this comma-list "text" format)
// Default story card "type" used by Auto-Cards? (does not matter)
const DEFAULT_CARD_TYPE = "class"
// ("text")
// Should titles mentioned in the "opening" plot component be banned from future card generation by default?
const DEFAULT_BAN_TITLES_FROM_OPENING = true
// (true or false)
//—————————————————————————————————————————————————————————————————————————————————
/*
Useful API functions for coders (otherwise ignore)
Here's what each one does in plain terms:
AutoCards().API.postponeEvents();
Pauses Auto-Cards activity for n many turns
AutoCards().API.emergencyHalt();
Emergency stop or resume
AutoCards().API.suppressMessages();
Hides Auto-Cards toasts by preventing assignment to state.message
AutoCards().API.debugLog();
Writes to the debug log card
AutoCards().API.toggle();
Turns Auto-Cards on/off
AutoCards().API.generateCard();
Initiates AI generation of the requested card
AutoCards().API.redoCard();
Regenerates an existing card
AutoCards().API.setCardAsAuto();
Flags or unflags a card as automatic
AutoCards().API.addCardMemory();
Adds a memory to a specific card
AutoCards().API.eraseAllAutoCards();
Deletes all auto-cards
AutoCards().API.getUsedTitles();
Lists all current card titles
AutoCards().API.getBannedTitles();
Shows your current banned titles list
AutoCards().API.setBannedTitles();
Replaces the banned titles list with a new list
AutoCards().API.buildCard();
Makes a new card from scratch, using exact parameters
AutoCards().API.getCard();
Finds cards that match a filter
AutoCards().API.eraseCard();
Deletes cards matching a filter
*/
/*** Postpones internal Auto-Cards events for a specified number of turns
*
* @function
* @param {number} turns A non-negative integer representing the number of turns to postpone events
* @returns {Object} An object containing cooldown values affected by the postponement
* @throws {Error} If turns is not a non-negative integer
*/
// AutoCards().API.postponeEvents();
/*** Sets or clears the emergency halt flag to pause Auto-Cards operations
*
* @function
* @param {boolean} shouldHalt A boolean value indicating whether to engage (true) or disengage (false) emergency halt
* @returns {boolean} The value that was set
* @throws {Error} If called from within isolateLSIv2 scope or with a non-boolean argument
*/
// AutoCards().API.emergencyHalt();
/*** Enables or disables state.message assignments from Auto-Cards
*
* @function
* @param {boolean} shouldSuppress If true, suppresses all Auto-Cards messages; false enables them
* @returns {Array} The current pending messages after setting suppression
* @throws {Error} If shouldSuppress is not a boolean
*/
// AutoCards().API.suppressMessages();
/*** Logs debug information to the "Debug Log card console
*
* @function
* @param {...any} args Arguments to log for debugging purposes
* @returns {any} The story card object reference
*/
// AutoCards().API.debugLog();
/*** Toggles Auto-Cards behavior or sets it directly
*
* @function
* @param {boolean|null|undefined} toggleType If undefined, toggles the current state. If boolean or null, sets the state accordingly
* @returns {boolean|null|undefined} The state that was set or inferred
* @throws {Error} If toggleType is not a boolean, null, or undefined
*/
// AutoCards().API.toggle();
/*** Generates a new card using optional prompt details or a card request object
*
* This function supports two usage modes:
*
* 1. Object Mode:
* Pass a single object containing card request parameters. The only mandatory property is "title"
* All other properties are optional and customize the card generation
*
* Example:
* AutoCards().API.generateCard({
* type: "character", // The category or type of the card; defaults to "class" if omitted
* title: "Leah the Lewd", // The card's title (required)
* keysStart: "Lewd,Leah", // Optional trigger keywords associated with the card
* entryStart: "You are a woman named Leah.", // Existing content to prepend to the AI-generated entry
* entryPrompt: "", // Global prompt guiding AI content generation
* entryPromptDetails: "Focus on Leah's works of artifice and ingenuity", // Additional prompt info
* entryLimit: 600, // Target character length for the AI-generated entry
* description: "Player character!", // Freeform notes
* memoryStart: "Leah purchased a new sweater.", // Existing memory content
* memoryUpdates: true, // Whether the card's memory bank will update on its own
* memoryLimit: 3200 // Preferred memory bank size before summarization/compression
* });
*
* 2. String Mode:
* Pass a string as the title and optionally two additional strings to specify prompt details
* This mode is shorthand for quick card generation without an explicit card request object
*
* Examples:
* AutoCards().API.generateCard("Leah the Lewd");
* AutoCards().API.generateCard("Leah the Lewd", "Focus on Leah's works of artifice and ingenuity");
* AutoCards().API.generateCard(
* "Leah the Lewd",
* "Focus on Leah's works of artifice and ingenuity",
* "You are a woman named Leah."
* );
*
* @function
* @param {Object|string} request Either a fully specified card request object or a string title
* @param {string} [extra1] Optional detailed prompt text when using string mode
* @param {string} [extra2] Optional entry start text when using string mode
* @returns {boolean} Returns true if the generation attempt succeeded, false otherwise
* @throws {Error} Throws if called with invalid arguments or missing a required title property
*/
// AutoCards().API.generateCard();
/*** Regenerates a card by title or object reference, optionally preserving or modifying its input info
*
* @function
* @param {Object|string} request Either a fully specified card request object or a string title for the card to be regenerated
* @param {boolean} [useOldInfo=true] If true, preserves old info in the new generation; false omits it
* @param {string} [newInfo=""] Additional info to append to the generation prompt
* @returns {boolean} True if regeneration succeeded; false otherwise
* @throws {Error} If the request format is invalid, or if the second or third parameters are the wrong types
*/
// AutoCards().API.redoCard();
/*** Flags or unflags a card as an auto-card, controlling its automatic generation behavior
*
* @function
* @param {Object|string} targetCard The card object or title to mark/unmark as an auto-card
* @param {boolean} [setOrUnset=true] If true, marks the card as an auto-card; false removes the flag
* @returns {boolean} True if the operation succeeded; false if the card was invalid or already matched the target state
* @throws {Error} If the arguments are invalid types
*/
// AutoCards().API.setCardAsAuto();
/*** Appends a memory to a story card's memory bank
*
* @function
* @param {Object|string} targetCard A card object reference or title string
* @param {string} newMemory The memory text to add
* @returns {boolean} True if the memory was added; false if it was empty, already present, or the card was not found
* @throws {Error} If the inputs are not a string or valid card object reference
*/
// AutoCards().API.addCardMemory();
/*** Removes all previously generated auto-cards and resets various states
*
* @function
* @returns {number} The number of cards that were removed
*/
// AutoCards().API.eraseAllAutoCards();
/*** Retrieves an array of titles currently used by the adventure's story cards
*
* @function
* @returns {Array<string>} An array of strings representing used titles
*/
// AutoCards().API.getUsedTitles();
/*** Retrieves an array of banned titles
*
* @function
* @returns {Array<string>} An array of banned title strings
*/
// AutoCards().API.getBannedTitles();
/*** Sets the banned titles array, replacing any previously banned titles
*
* @function
* @param {string|Array<string>} titles A comma-separated string or array of strings representing titles to ban
* @returns {Object} An object containing oldBans and newBans arrays
* @throws {Error} If the input is neither a string nor an array of strings
*/
// AutoCards().API.setBannedTitles();
/*** Creates a new story card with the specified parameters
*
* @function
* @param {string|Object} title Card title string or full card template object containing all fields
* @param {string} [entry] The entry text for the card
* @param {string} [type] The card type (e.g., "character", "location")
* @param {string} [keys] The keys (triggers) for the card
* @param {string} [description] The notes or memory bank of the card
* @param {number} [insertionIndex] Optional index to insert the card at a specific position within storyCards
* @returns {Object|null} The created card object reference, or null if creation failed
*/
// AutoCards().API.buildCard();
/*** Finds and returns story cards satisfying a user-defined condition
* Example:
* const leahCard = AutoCards().API.getCard(card => (card.title === "Leah"));
*
* @function
* @param {Function} predicate A function which takes a card and returns true if it matches
* @param {boolean} [getAll=false] If true, returns all matching cards; otherwise returns the first match
* @returns {Object|Array<Object>|null} A single card object reference, an array of cards, or null if no match is found
* @throws {Error} If the predicate is not a function or getAll is not a boolean
*/
// AutoCards().API.getCard();
/*** Removes story cards based on a user-defined condition or by direct reference
* Example:
* AutoCards().API.eraseCard(card => (card.title === "Leah"));
*
* @function
* @param {Function|Object} predicate A predicate function or a card object reference
* @param {boolean} [eraseAll=false] If true, removes all matching cards; otherwise removes the first match
* @returns {boolean|number} True if a single card was removed, false if none matched, or the number of cards erased
* @throws {Error} If the inputs are not a valid predicate function, card object, or boolean
*/
// AutoCards().API.eraseCard();
//—————————————————————————————————————————————————————————————————————————————————
/*
To everyone who helped, thank you:
AHotHamster
Most extensive testing, feedback, ideation, and kindness
BinKompliziert
UI feedback
Boo
Discord communication
bottledfox
API ideas for alternative card generation use-cases
Bruno
Most extensive testing, feedback, ideation, and kindness
https://play.aidungeon.com/profile/Azuhre
Burnout
Implementation improvements, algorithm ideas, script help, and LSIv2 inspiration
bweni
Testing
DebaczX
Most extensive testing, feedback, ideation, and kindness
Dirty Kurtis
Card entry generation prompt engineering
Dragranis
Provided the memory dataset used for boundary calibration
effortlyss
Data, testing, in-game command ideas, config settings, and other UX improvements
Hawk
Grammar and special-cased proper nouns
Idle Confusion
Testing
https://play.aidungeon.com/profile/Idle%20Confusion
ImprezA
Most extensive testing, feedback, ideation, and kindness
https://play.aidungeon.com/profile/ImprezA
Kat-Oli
Title parsing, grammar, and special-cased proper nouns
KryptykAngel
LSIv2 ideas
https://play.aidungeon.com/profile/KryptykAngel
Mad19pumpkin
API ideas
https://play.aidungeon.com/profile/Mad19pumpkin
Magic
Implementation and syntax improvements
https://play.aidungeon.com/profile/MagicOfLolis
Mirox80
Testing, feedback, and scenario integration ideas
https://play.aidungeon.com/profile/Mirox80
Nathaniel Wyvern
Testing
https://play.aidungeon.com/profile/NathanielWyvern
NobodyIsUgly
All-caps title parsing feedback
OnyxFlame
Card memory bank implementation ideas and special-cased proper nouns
Purplejump
API ideas for deep integration with other AID scripts
Randy Viosca
Context injection and card memory bank structure
https://play.aidungeon.com/profile/Random_Variable
RustyPawz
API ideas for simplified card interaction
https://play.aidungeon.com/profile/RustyPawz
sinner
Testing
Sleepy pink
Testing and feedback
https://play.aidungeon.com/profile/Pinkghost
Vutinberg
Memory compression ideas and prompt engineering
Wilmar
Card entry generation and memory summarization prompt engineering
Yi1i1i
Idea for the redoCard API function and "/ac redo" in-game command
A note to future individuals:
If you fork or modify Auto-Cards... Go ahead and put your name here too! Yay! 🥰
*/
//—————————————————————————————————————————————————————————————————————————————————
/*
The code below implements Auto-Cards
Enjoy! ❤️
*/
// My class definitions are hoisted by wrapper functions because it's less ugly (lol)
const Const = hoistConst();
const O = hoistO();
const Words = hoistWords();
const StringsHashed = hoistStringsHashed();
const Internal = hoistInternal();
// AutoCards has an explicitly immutable domain: HOOK, TEXT, and STOP
const HOOK = inHook;
const TEXT = ((typeof inText === "string") && inText) || "\n";
const STOP = (inStop === true);
// AutoCards returns a pseudoimmutable codomain which is initialized only once before being read and returned
const CODOMAIN = new Const().declare();
// Transient sets for high-performance lookup
const [used, bans, auto, forenames, surnames] = Array.from({length: 5}, () => new Set());
const memoized = new Map();
// Holds a reference to the data card singleton, remains unassigned unless required
let data = null;
// Validate globalThis.text
text = ((typeof text === "string") && text) || "\n";
// Container for the persistent state of AutoCards
const AC = (function() {
if (state.LSIv2) {
// The Auto-Cards external API is also available from within the inner scope of LSIv2
// Call with AutoCards().API.nameOfFunction(yourArguments);
return state.LSIv2;
} else if (state.AutoCards) {
// state.AutoCards is prioritized for performance
const ac = state.AutoCards;
delete state.AutoCards;
return ac;
}
const dataVariants = getDataVariants();
data = getSingletonCard(false, O.f({...dataVariants.critical}), O.f({...dataVariants.debug}));
// Deserialize the state of Auto-Cards from the data card
const ac = (function() {
try {
return JSON.parse(data?.description);
} catch {
return null;
}
})();
// If the deserialized state fails to match the following structure, fallback to defaults
if (validate(ac, O.f({
config: [
"doAC", "deleteAllAutoCards", "pinConfigureCard", "addCardCooldown", "bulletedListMode", "defaultEntryLimit", "defaultCardsDoMemoryUpdates", "defaultMemoryLimit", "memoryCompressionRatio", "ignoreAllCapsTitles", "readFromInputs", "minimumLookBackDistance", "LSIv2", "showDebugData", "generationPrompt", "compressionPrompt", "defaultCardType"
],
signal: [
"emergencyHalt", "forceToggle", "overrideBans", "swapControlCards", "recheckRetryOrErase", "maxChars", "outputReplacement", "upstreamError"
],
generation: [
"cooldown", "completed", "permitted", "workpiece", "pending"
],
compression: [
"completed", "titleKey", "vanityTitle", "responseEstimate", "lastConstructIndex", "oldMemoryBank", "newMemoryBank"
],
message: [
"previous", "suppress", "pending", "event"
],
chronometer: [
"turn", "step", "amnesia", "postpone"
],
database: {
titles: [
"used", "banned", "candidates", "lastActionParsed", "lastTextHash", "pendingBans", "pendingUnbans"
],
memories: [
"associations", "duplicates"
]
}
}))) {
// The deserialization was a success
return ac;
}
function validate(obj, finalKeys) {
if ((typeof obj !== "object") || (obj === null)) {
return false;
} else {
return Object.entries(finalKeys).every(([key, value]) => {
if (!(key in obj)) {
return false;
} else if (Array.isArray(value)) {
return value.every(finalKey => {
return (finalKey in obj[key]);
});
} else {
return validate(obj[key], value);
}
});
}
}
// AC is malformed, reinitialize with default values
return {
// In-game configurable parameters
config: getDefaultConfig(),
// Collection of various short-term signals passed forward in time
signal: {
// API: Suspend nearly all Auto-Cards processes
emergencyHalt: false,
// API: Forcefully toggle Auto-Cards on or off
forceToggle: null,
// API: Banned titles were externally overwritten
overrideBans: 0,
// Signal the construction of the opposite control card during the upcoming onOutput hook
swapControlCards: false,
// Signal a limited recheck of recent title candidates following a retry or erase
recheckRetryOrErase: false,
// Signal an upcoming onOutput text replacement
outputReplacement: "",
// info.maxChars is only defined onContext but must be accessed during other hooks too
maxChars: Math.abs(info?.maxChars || 3200),
// An error occured within the isolateLSIv2 scope during an earlier hook
upstreamError: ""
},
// Moderates the generation of new story card entries
generation: {
// Number of story progression turns between card generations
cooldown: validateCooldown(underQuarterInteger(validateCooldown(DEFAULT_CARD_CREATION_COOLDOWN))),
// Continues prompted so far
completed: 0,
// Upper limit on consecutive continues
permitted: 34,
// Properties of the incomplete story card
workpiece: O.f({}),
// Pending card generations
pending: [],
},
// Moderates the compression of story card memories
compression: {
// Continues prompted so far
completed: 0,
// A title header reference key for this auto-card
titleKey: "",
// The full and proper title
vanityTitle: "",
// Response length estimate used to compute # of outputs remaining
responseEstimate: 1400,
// Indices [0, n] of oldMemoryBank memories used to build the current memory construct
lastConstructIndex: -1,
// Bank of card memories awaiting compression
oldMemoryBank: [],
// Incomplete bank of newly compressed card memories
newMemoryBank: [],
},
// Prevents incompatibility issues borne of state.message modification
message: {
// Last turn's state.message
previous: getStateMessage(),
// API: Allow Auto-Cards to post messages?
suppress: false,
// Pending Auto-Cards message(s)
pending: (function() {
if (DEFAULT_DO_AC !== false) {
const startupMessage = "Enabled! You may now edit the \"Configure Auto-Cards\" story card";
logEvent(startupMessage);
return [startupMessage];
} else {
return [];
}
})(),
// Counter to track all Auto-Cards message events
event: 0
},
// Timekeeper used for temporal events
chronometer: {
// Previous turn's measurement of info.actionCount
turn: getTurn(),
// Whether or not various turn counters should be stepped (falsified by retry actions)
step: true,
// Number of consecutive turn interruptions
amnesia: 0,
// API: Postpone Auto-Cards externalities for n many turns
postpone: 0,
},
// Scalable atabase to store dynamic game information
database: {
// Words are pale shadows of forgotten names. As names have power, words have power
titles: {
// A transient array of known titles parsed from card titles, entry title headers, and trigger keywords
used: [],
// Titles banned from future card generation attempts and various maintenance procedures
banned: getDefaultConfigBans(),
// Potential future card titles and their turns of occurrence
candidates: [],
// Helps avoid rechecking the same action text more than once, generally
lastActionParsed: -1,
// Ensures weird combinations of retry/erase events remain predictable
lastTextHash: "%@%",
// Newly banned titles which will be added to the config card
pendingBans: [],
// Currently banned titles which will be removed from the config card
pendingUnbans: []
},
// Memories are parsed from context and handled by various operations (basically magic)
memories: {
// Dynamic store of 'story card -> memory' conceptual relations
associations: {},
// Serialized hashset of the 2000 most recent near-duplicate memories purged from context
duplicates: "%@%"
}
}
};
})();
O.f(AC);
O.s(AC.config);
O.s(AC.signal);
O.s(AC.generation);
O.s(AC.generation.workpiece);
AC.generation.pending.forEach(request => O.s(request));
O.s(AC.compression);
O.s(AC.message);
O.s(AC.chronometer);
O.f(AC.database);
O.s(AC.database.titles);
O.s(AC.database.memories);
if (!HOOK) {
globalThis.stop ??= false;
AC.signal.maxChars = Math.abs(info?.maxChars || AC.signal.maxChars);
if (HOOK === null) {
if (/Recent\s*Story\s*:/i.test(text)) {
// AutoCards(null) is always invoked once after being declared within the shared library
// Context must be cleaned before passing text to the context modifier
// This measure is taken to ensure compatability with other scripts
// First, remove all command, continue, and comfirmation messages from the context window
text = (text
// Hide the guide
.replace(/\s*>>>\s*Detailed\s*Guide\s*:[\s\S]*?<<<\s*/gi, "\n\n")
// Excise all /AC command messages
.replace(/\s*>>>\s*Auto-Cards\s*has\s*been\s*enabled!\s*<<<\s*/gi, " ")
.replace(/^.*\/\s*A\s*C.*$/gmi, "%@%")
.replace(/\s*%@%\s*/g, " ")
// Consolidate all consecutive continue messages into placeholder substrings
.replace(/(?:(?:\s*>>>\s*please\s*select\s*"continue"\s*\([\s\S]*?\)\s*<<<\s*)+)/gi, message => {
// Replace all continue messages with %@+%-patterned substrings
return (
// The # of "@" symbols corresponds with the # of consecutive continue messages
"%" + "@".repeat(
// Count the number of consecutive continue message occurrences
(message.match(/>>>\s*please\s*select\s*"continue"\s*\([\s\S]*?\)\s*<<</gi) || []).length
) + "%"
);
})
// Situationally replace all placeholder substrings with either spaces or double newlines
.replace(/%@+%/g, (match, matchIndex, intermediateText) => {
// Check the case of the next char following the match to decide how to replace it
let i = matchIndex + match.length;
let nextChar = intermediateText[i];
if (nextChar === undefined) {
return " ";
} else if (/^[A-Z]$/.test(nextChar)) {
// Probably denotes a new sentence/paragraph
return "\n\n";
} else if (/^[a-z]$/.test(nextChar)) {
return " ";
}
// The first nextChar was a weird punctuation char, find the next non-whitespace char
do {
i++;
nextChar = intermediateText[i];
if (nextChar === undefined) {
return " ";
}
} while (/\s/.test(nextChar));
if (nextChar === nextChar.toUpperCase()) {
// Probably denotes a new sentence/paragraph
return "\n\n";
}
// Returning " " probably indicates a previous output's incompleteness
return " ";
})
// Remove all comfirmation requests and responses
.replace(/\s*\n*.*CONFIRM\s*DELETE.*\n*\s*/gi, confirmation => {
if (confirmation.includes("<<<")) {
return " ";
} else {
return "";
}
})
// Remove dumb memories from the context window
// (Latitude, if you're reading this, please give us memoryBank read/write access 😭)
.replace(/(Memories\s*:)\s*([\s\S]*?)\s*(Recent\s*Story\s*:|$)/i, (_, left, memories, right) => {
return (left + "\n" + (memories
.split("\n")
.filter(memory => {
const lowerMemory = memory.toLowerCase();
return !(
(lowerMemory.includes("select") && lowerMemory.includes("continue"))
|| lowerMemory.includes(">>>") || lowerMemory.includes("<<<")
|| lowerMemory.includes("lsiv2")
);
})
.join("\n")
) + (function() {
if (right !== "") {
return "\n\n" + right;
} else {
return "";
}
})());
})
// Remove LSIv2 error messages
.replace(/(?:\s*>>>[\s\S]*?<<<\s*)+/g, " ")
);
if (!shouldProceed()) {
// Whenever Auto-Cards is inactive, remove auto card title headers from contextualized story card entries
text = (text
.replace(/\s*{\s*titles?\s*:[\s\S]*?}\s*/gi, "\n\n")
.replace(/World\s*Lore\s*:\s*/i, "World Lore:\n")
);
// Otherwise, implement a more complex version of this step within the (HOOK === "context") scope of AutoCards
}
}
CODOMAIN.initialize(null);
} else {
// AutoCards was (probably) called without arguments, return an external API to allow other script creators to programmatically govern the behavior of Auto-Cards from elsewhere within their own scripts
CODOMAIN.initialize({API: O.f(Object.fromEntries(Object.entries({
// Call these API functions like so: AutoCards().API.nameOfFunction(argumentsOfFunction)
/*** Postpones internal Auto-Cards events for a specified number of turns
*
* @function
* @param {number} turns A non-negative integer representing the number of turns to postpone events
* @returns {Object} An object containing cooldown values affected by the postponement
* @throws {Error} If turns is not a non-negative integer
*/
postponeEvents: function(turns) {
if (Number.isInteger(turns) && (0 <= turns)) {
AC.chronometer.postpone = turns;
} else {
throw new Error(
"Invalid argument: \"" + turns + "\" -> AutoCards().API.postponeEvents() must be be called with a non-negative integer"
);
}
return {
postponeAllCooldown: turns,
addCardRealCooldown: AC.generation.cooldown,
addCardNextCooldown: AC.config.addCardCooldown
};
},
/*** Sets or clears the emergency halt flag to pause Auto-Cards operations
*
* @function
* @param {boolean} shouldHalt A boolean value indicating whether to engage (true) or disengage (false) emergency halt
* @returns {boolean} The value that was set
* @throws {Error} If called from within isolateLSIv2 scope or with a non-boolean argument
*/
emergencyHalt: function(shouldHalt) {
const scopeRestriction = new Error();
if (scopeRestriction.stack && scopeRestriction.stack.includes("isolateLSIv2")) {
throw new Error(
"Scope restriction: AutoCards().API.emergencyHalt() cannot be called from within LSIv2 (prevents deadlock) but you're more than welcome to use AutoCards().API.postponeEvents() instead!"
);
} else if (typeof shouldHalt === "boolean") {
AC.signal.emergencyHalt = shouldHalt;
} else {
throw new Error(
"Invalid argument: \"" + shouldHalt + "\" -> AutoCards().API.emergencyHalt() must be called with a boolean true or false"
);
}
return shouldHalt;
},
/*** Enables or disables state.message assignments from Auto-Cards
*
* @function
* @param {boolean} shouldSuppress If true, suppresses all Auto-Cards messages; false enables them
* @returns {Array} The current pending messages after setting suppression
* @throws {Error} If shouldSuppress is not a boolean
*/
suppressMessages: function(shouldSuppress) {
if (typeof shouldSuppress === "boolean") {
AC.message.suppress = shouldSuppress;
} else {
throw new Error(
"Invalid argument: \"" + shouldSuppress + "\" -> AutoCards().API.suppressMessages() must be called with a boolean true or false"
);
}
return AC.message.pending;
},
/*** Logs debug information to the "Debug Log" console card
*
* @function
* @param {...any} args Arguments to log for debugging purposes
* @returns {any} The story card object reference
*/
debugLog: function(...args) {
return Internal.debugLog(...args);
},
/*** Toggles Auto-Cards behavior or sets it directly
*
* @function
* @param {boolean|null|undefined} toggleType If undefined, toggles the current state. If boolean or null, sets the state accordingly
* @returns {boolean|null|undefined} The state that was set or inferred
* @throws {Error} If toggleType is not a boolean, null, or undefined
*/
toggle: function(toggleType) {
if (toggleType === undefined) {
if (AC.signal.forceToggle !== null) {
AC.signal.forceToggle = !AC.signal.forceToggle;
} else if (AC.config.doAC) {
AC.signal.forceToggle = false;
} else {
AC.signal.forceToggle = true;
}
} else if ((toggleType === null) || (typeof toggleType === "boolean")) {
AC.signal.forceToggle = toggleType;
} else {
throw new Error(
"Invalid argument: \"" + toggleType + "\" -> AutoCards().API.toggle() must be called with either A) a boolean true or false, B) a null argument, or C) no arguments at all (undefined)"
);
}
return toggleType;
},
/*** Generates a new card using optional prompt details or a request object
*
* @function
* @param {Object|string} request A request object with card parameters or a string representing the title
* @param {string} [extra1] Optional entryPromptDetails if using string mode
* @param {string} [extra2] Optional entryStart if using string mode
* @returns {boolean} Did the generation attempt succeed or fail
* @throws {Error} If the request is not valid or missing a title
*/
generateCard: function(request, extra1, extra2) {
// Function call guide:
// AutoCards().API.generateCard({
// // All properties except 'title' are optional
// type: "card type, defaults to 'class' for ease of filtering",
// title: "card title",
// keysStart: "preexisting card triggers",
// entryStart: "preexisting card entry",
// entryPrompt: "prompt the AI will use to complete this entry",
// entryPromptDetails: "extra details to include with this card's prompt",
// entryLimit: 600, // target character count for the generated entry
// description: "card notes",
// memoryStart: "preexisting card memory",
// memoryUpdates: true, // card updates when new relevant memories are formed
// memoryLimit: 3200, // max characters before the card memory is compressed
// });
if (typeof request === "string") {
request = {title: request};
if (typeof extra1 === "string") {
request.entryPromptDetails = extra1;
if (typeof extra2 === "string") {
request.entryStart = extra2;
}
}
} else if (!isTitleInObj(request)) {
throw new Error(
"Invalid argument: \"" + request + "\" -> AutoCards().API.generateCard() must be called with either 1, 2, or 3 strings OR a correctly formatted card generation object"
);
}
O.f(request);
Internal.getUsedTitles(true);
return Internal.generateCard(request);
},
/*** Regenerates a card by title or object reference, optionally preserving or modifying its input info
*
* @function
* @param {Object|string} request A card object reference or title string for the card to be regenerated
* @param {boolean} [useOldInfo=true] If true, preserves old info in the new generation; false omits it
* @param {string} [newInfo=""] Additional info to append to the generation prompt
* @returns {boolean} True if regeneration succeeded; false otherwise
* @throws {Error} If the request format is invalid, or if the second or third parameters are the wrong types
*/
redoCard: function(request, useOldInfo = true, newInfo = "") {
if (typeof request === "string") {
request = {title: request};
} else if (!isTitleInObj(request)) {
throw new Error(
"Invalid argument: \"" + request + "\" -> AutoCards().API.redoCard() must be called with a string or correctly formatted card generation object"
);
}
if (typeof useOldInfo !== "boolean") {
throw new Error(
"Invalid argument: \"" + request + ", " + useOldInfo + "\" -> AutoCards().API.redoCard() requires a boolean as its second argument"
);
} else if (typeof newInfo !== "string") {
throw new Error(
"Invalid argument: \"" + request + ", " + useOldInfo + ", " + newInfo + "\" -> AutoCards().API.redoCard() requires a string for its third argument"
);
}
return Internal.redoCard(request, useOldInfo, newInfo);
},
/*** Flags or unflags a card as an auto-card, controlling its automatic generation behavior
*
* @function
* @param {Object|string} targetCard The card object or title to mark/unmark as an auto-card
* @param {boolean} [setOrUnset=true] If true, marks the card as an auto-card; false removes the flag
* @returns {boolean} True if the operation succeeded; false if the card was invalid or already matched the target state
* @throws {Error} If the arguments are invalid types
*/
setCardAsAuto: function(targetCard, setOrUnset = true) {
if (isTitleInObj(targetCard)) {
targetCard = targetCard.title;
} else if (typeof targetCard !== "string") {
throw new Error(
"Invalid argument: \"" + targetCard + "\" -> AutoCards().API.setCardAsAuto() must be called with a string or card object"
);
}
if (typeof setOrUnset !== "boolean") {
throw new Error(
"Invalid argument: \"" + targetCard + ", " + setOrUnset + "\" -> AutoCards().API.setCardAsAuto() requires a boolean as its second argument"
);
}
const [card, isAuto] = getIntendedCard(targetCard);
if (card === null) {
return false;
}
if (setOrUnset) {
if (checkAuto()) {
return false;
}
card.description = "{title:}";
Internal.getUsedTitles(true);
return card.entry.startsWith("{title: ");
} else if (!checkAuto()) {
return false;
}
card.entry = removeAutoProps(card.entry);
card.description = removeAutoProps(card.description.replace((