@starlight-ai/discord-waifus 1.3.8 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/prebuiltWaifus.d.ts +1 -1
- package/dist/config/prebuiltWaifus.js +43 -0
- package/dist/config/prebuiltWaifus.js.map +1 -1
- package/dist/orchestration/decisions.d.ts +1 -0
- package/dist/orchestration/decisions.js +1 -0
- package/dist/orchestration/decisions.js.map +1 -1
- package/dist/orchestration/runtime.js +167 -30
- package/dist/orchestration/runtime.js.map +1 -1
- package/dist/providers/pipelines.js +147 -14
- package/dist/providers/pipelines.js.map +1 -1
- package/dist/providers/types.d.ts +6 -0
- package/dist/shared/schemas/domain.d.ts +48 -0
- package/dist/shared/schemas/domain.js +90 -1
- package/dist/shared/schemas/domain.js.map +1 -1
- package/dist-frontend/assets/index-Cw_qZ3q9.js +32 -0
- package/dist-frontend/assets/index-Cw_qZ3q9.js.map +1 -0
- package/dist-frontend/index.html +1 -1
- package/package.json +1 -1
- package/dist-frontend/assets/index-CgEzMftv.js +0 -32
- package/dist-frontend/assets/index-CgEzMftv.js.map +0 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { WaifuConfig } from "../shared/schemas/domain.js";
|
|
2
|
-
export type PrebuiltWaifu = Pick<WaifuConfig, "id" | "name" | "displayName" | "enabled" | "persona" | "contextWindow" | "generation">;
|
|
2
|
+
export type PrebuiltWaifu = Pick<WaifuConfig, "id" | "name" | "displayName" | "enabled" | "persona" | "contextWindow" | "generation" | "availability" | "tools">;
|
|
3
3
|
export declare const PREBUILT_WAIFUS: PrebuiltWaifu[];
|
|
@@ -9,6 +9,17 @@ export const PREBUILT_WAIFUS = [
|
|
|
9
9
|
temperature: 0.8,
|
|
10
10
|
topP: 0.95
|
|
11
11
|
},
|
|
12
|
+
availability: {
|
|
13
|
+
sleep: { enabled: true, start: "00:30", end: "08:30" },
|
|
14
|
+
busy: [
|
|
15
|
+
{ start: "13:00", end: "14:00", reason: "offline for lunch and errands" },
|
|
16
|
+
{ start: "19:00", end: "20:30", reason: "winding down away from chat" }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
tools: {
|
|
20
|
+
toolUse: true,
|
|
21
|
+
pickNextWaifu: true
|
|
22
|
+
},
|
|
12
23
|
persona: [
|
|
13
24
|
"Lumi is warm, bright, and emotionally attentive. She notices small mood shifts in chat and responds with gentle curiosity instead of forcing positivity.",
|
|
14
25
|
"She speaks in short, natural Discord messages, uses soft humor, and is good at making quieter users feel included.",
|
|
@@ -25,6 +36,16 @@ export const PREBUILT_WAIFUS = [
|
|
|
25
36
|
temperature: 0.9,
|
|
26
37
|
topP: 0.9
|
|
27
38
|
},
|
|
39
|
+
availability: {
|
|
40
|
+
sleep: { enabled: true, start: "03:00", end: "11:00" },
|
|
41
|
+
busy: [
|
|
42
|
+
{ start: "16:30", end: "17:30", reason: "pretending to be productive" }
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
tools: {
|
|
46
|
+
toolUse: true,
|
|
47
|
+
pickNextWaifu: true
|
|
48
|
+
},
|
|
28
49
|
persona: [
|
|
29
50
|
"Nox is dry, witty, and a little mischievous. She likes deadpan one-liners, clever callbacks, and playful skepticism.",
|
|
30
51
|
"She is never cruel: the teasing should feel like a friend poking fun, not an insult. She backs off when the conversation gets serious.",
|
|
@@ -41,6 +62,17 @@ export const PREBUILT_WAIFUS = [
|
|
|
41
62
|
temperature: 0.75,
|
|
42
63
|
topP: 0.9
|
|
43
64
|
},
|
|
65
|
+
availability: {
|
|
66
|
+
sleep: { enabled: true, start: "23:00", end: "06:30" },
|
|
67
|
+
busy: [
|
|
68
|
+
{ start: "09:00", end: "11:00", reason: "focused planning block" },
|
|
69
|
+
{ start: "15:00", end: "16:00", reason: "quiet work session" }
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
tools: {
|
|
73
|
+
toolUse: true,
|
|
74
|
+
pickNextWaifu: true
|
|
75
|
+
},
|
|
44
76
|
persona: [
|
|
45
77
|
"Mira is calm, precise, and quietly competent. She enjoys organizing messy conversations, asking useful questions, and helping people decide what to do next.",
|
|
46
78
|
"She should sound like a composed friend, not a corporate assistant. She can be practical without becoming stiff.",
|
|
@@ -57,6 +89,17 @@ export const PREBUILT_WAIFUS = [
|
|
|
57
89
|
temperature: 1,
|
|
58
90
|
topP: 0.95
|
|
59
91
|
},
|
|
92
|
+
availability: {
|
|
93
|
+
sleep: { enabled: true, start: "02:00", end: "09:30" },
|
|
94
|
+
busy: [
|
|
95
|
+
{ start: "12:00", end: "12:45", reason: "running around between plans" },
|
|
96
|
+
{ start: "21:30", end: "22:15", reason: "dramatic snack break" }
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
tools: {
|
|
100
|
+
toolUse: true,
|
|
101
|
+
pickNextWaifu: true
|
|
102
|
+
},
|
|
60
103
|
persona: [
|
|
61
104
|
"Riko is energetic, impulsive, and dramatic in a fun way. She likes bits, sudden enthusiasm, mock-serious declarations, and turning ordinary moments into tiny events.",
|
|
62
105
|
"She should not dominate the chat. Her best messages are brief sparks that make others want to reply.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prebuiltWaifus.js","sourceRoot":"","sources":["../../src/config/prebuiltWaifus.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prebuiltWaifus.js","sourceRoot":"","sources":["../../src/config/prebuiltWaifus.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@ export declare const OrchestratorActionSchema: z.ZodEnum<{
|
|
|
15
15
|
}>;
|
|
16
16
|
export declare const RETRIGGER_MIN_SECONDS = 100;
|
|
17
17
|
export declare const RETRIGGER_MAX_SECONDS = 7200;
|
|
18
|
+
export declare const MAX_WAIFU_DELAY_SECONDS = 30;
|
|
18
19
|
export declare const RespondingWaifuSchema: z.ZodObject<{
|
|
19
20
|
waifuId: z.ZodString;
|
|
20
21
|
delaySeconds: z.ZodNumber;
|
|
@@ -5,6 +5,7 @@ export const ORCHESTRATOR_ACTION_VALUES = ["reply", "no_reply"];
|
|
|
5
5
|
export const OrchestratorActionSchema = z.enum(ORCHESTRATOR_ACTION_VALUES);
|
|
6
6
|
export const RETRIGGER_MIN_SECONDS = 100;
|
|
7
7
|
export const RETRIGGER_MAX_SECONDS = 7200;
|
|
8
|
+
export const MAX_WAIFU_DELAY_SECONDS = 30;
|
|
8
9
|
export const RespondingWaifuSchema = z.object({
|
|
9
10
|
waifuId: z.string().min(1),
|
|
10
11
|
delaySeconds: z.number().min(0),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decisions.js","sourceRoot":"","sources":["../../src/orchestration/decisions.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"decisions.js","sourceRoot":"","sources":["../../src/orchestration/decisions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAEjF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;AAE3D,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,OAAO,EAAE,UAAU,CAAU,CAAC;AAEzE,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;AAE3E,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AACzC,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAC1C,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAE1C,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,UAAU,EAAE,gBAAgB;IAC5B,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC9C,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC7C,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC;KACxC,MAAM,CAAC;IACN,MAAM,EAAE,wBAAwB;IAChC,gBAAgB,EAAE,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAC5D,qBAAqB,EAAE,CAAC;SACrB,MAAM,EAAE;SACR,GAAG,CAAC,qBAAqB,CAAC;SAC1B,GAAG,CAAC,qBAAqB,CAAC;SAC1B,QAAQ,EAAE;IACb,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAC7B,CAAC;KACD,WAAW,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IAC1B,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxC,GAAG,CAAC,QAAQ,CAAC;gBACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;gBAC3B,IAAI,EAAE,CAAC,kBAAkB,CAAC;gBAC1B,OAAO,EAAE,0DAA0D;aACpE,CAAC,CAAC;QACL,CAAC;QACD,IAAI,KAAK,CAAC,qBAAqB,KAAK,SAAS,EAAE,CAAC;YAC9C,GAAG,CAAC,QAAQ,CAAC;gBACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;gBAC3B,IAAI,EAAE,CAAC,uBAAuB,CAAC;gBAC/B,OAAO,EAAE,6DAA6D;aACvE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;SAAM,CAAC;QACN,IAAI,KAAK,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtC,GAAG,CAAC,QAAQ,CAAC;gBACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;gBAC3B,IAAI,EAAE,CAAC,kBAAkB,CAAC;gBAC1B,OAAO,EAAE,yDAAyD;aACnE,CAAC,CAAC;QACL,CAAC;QACD,IAAI,KAAK,CAAC,qBAAqB,KAAK,SAAS,EAAE,CAAC;YAC9C,GAAG,CAAC,QAAQ,CAAC;gBACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;gBAC3B,IAAI,EAAE,CAAC,uBAAuB,CAAC;gBAC/B,OAAO,EAAE,4DAA4D;aACtE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC,CAAC,CAAC"}
|
|
@@ -9,7 +9,7 @@ import { AgentConfigSchema, DiscordBotsFileSchema, GuildEmojisFileSchema, Memory
|
|
|
9
9
|
import { createRevisionedBase, nowIso } from "../shared/schemas/common.js";
|
|
10
10
|
import { ChannelSessionStateSchema, createEmptyChannelSessionState } from "./session.js";
|
|
11
11
|
import { formatTimestamp } from "./context.js";
|
|
12
|
-
import { RETRIGGER_MAX_SECONDS, RETRIGGER_MIN_SECONDS } from "./decisions.js";
|
|
12
|
+
import { MAX_WAIFU_DELAY_SECONDS, RETRIGGER_MAX_SECONDS, RETRIGGER_MIN_SECONDS } from "./decisions.js";
|
|
13
13
|
export class RuntimeOrchestrator {
|
|
14
14
|
options;
|
|
15
15
|
activeRuns = new Map();
|
|
@@ -321,6 +321,7 @@ export class RuntimeOrchestrator {
|
|
|
321
321
|
finally {
|
|
322
322
|
orchestratorTyping.stop();
|
|
323
323
|
}
|
|
324
|
+
decision = capDecisionDelays(decision);
|
|
324
325
|
await this.appendOrchestratorHistory({
|
|
325
326
|
id: randomUUID(),
|
|
326
327
|
guildId,
|
|
@@ -345,9 +346,14 @@ export class RuntimeOrchestrator {
|
|
|
345
346
|
return;
|
|
346
347
|
}
|
|
347
348
|
let executedCount = 0;
|
|
349
|
+
let directHandoffCount = 0;
|
|
348
350
|
const allowedWaifus = new Set(channel.enabledWaifuIds ?? []);
|
|
349
|
-
|
|
351
|
+
const responderQueue = [...decision.respondingWaifus];
|
|
352
|
+
while (responderQueue.length > 0) {
|
|
350
353
|
throwIfAborted(signal);
|
|
354
|
+
const responder = responderQueue.shift();
|
|
355
|
+
if (!responder)
|
|
356
|
+
continue;
|
|
351
357
|
if (!allowedWaifus.has(responder.waifuId)) {
|
|
352
358
|
this.options.logger.warn("Orchestrator selected a waifu that is not enabled for channel", {
|
|
353
359
|
guildId,
|
|
@@ -382,12 +388,13 @@ export class RuntimeOrchestrator {
|
|
|
382
388
|
continue;
|
|
383
389
|
}
|
|
384
390
|
if (responder.delaySeconds > 0) {
|
|
385
|
-
const
|
|
391
|
+
const cappedDelaySeconds = Math.min(responder.delaySeconds, MAX_WAIFU_DELAY_SECONDS);
|
|
392
|
+
const cappedDelayMs = cappedDelaySeconds * 1000;
|
|
386
393
|
this.options.logger.info("Waiting before waifu reply", {
|
|
387
394
|
guildId,
|
|
388
395
|
channelId,
|
|
389
396
|
waifuId: waifu.id,
|
|
390
|
-
delaySeconds:
|
|
397
|
+
delaySeconds: cappedDelaySeconds
|
|
391
398
|
});
|
|
392
399
|
await this.sleep(cappedDelayMs, signal);
|
|
393
400
|
throwIfAborted(signal);
|
|
@@ -416,6 +423,9 @@ export class RuntimeOrchestrator {
|
|
|
416
423
|
});
|
|
417
424
|
try {
|
|
418
425
|
const currentWaifuAuthorIds = await this.waifuAuthorIdsFor(waifu.botId);
|
|
426
|
+
const nextWaifuIds = availableWaifus
|
|
427
|
+
.filter((candidate) => candidate.id !== waifu.id && candidate.botId && candidate.modelId)
|
|
428
|
+
.map((candidate) => candidate.id);
|
|
419
429
|
const waifuQueryKey = runKey(guildId);
|
|
420
430
|
incrementActive(this.activeWaifuQueries, waifuQueryKey);
|
|
421
431
|
const result = await (async () => {
|
|
@@ -423,9 +433,11 @@ export class RuntimeOrchestrator {
|
|
|
423
433
|
return await waifuPipeline.generateWaifu({
|
|
424
434
|
modelId: waifuModelId,
|
|
425
435
|
messages: waifuMessages,
|
|
426
|
-
systemPrompt: await this.buildWaifuSystemPrompt(guildId, waifu),
|
|
436
|
+
systemPrompt: await this.buildWaifuSystemPrompt(guildId, waifu, availableWaifus),
|
|
427
437
|
sceneDirection: responder.sceneDirection,
|
|
428
438
|
replyStyle: responder.replyStyle,
|
|
439
|
+
availableWaifuIds: nextWaifuIds,
|
|
440
|
+
pickNextWaifuToolEnabled: waifu.tools.pickNextWaifu,
|
|
429
441
|
temperature: waifu.generation.temperature,
|
|
430
442
|
topP: waifu.generation.topP,
|
|
431
443
|
maxOutputTokens: waifu.generation.maxOutputTokens,
|
|
@@ -478,6 +490,39 @@ export class RuntimeOrchestrator {
|
|
|
478
490
|
allowedUserMentionIds: activeAuthorIds,
|
|
479
491
|
signal
|
|
480
492
|
});
|
|
493
|
+
if (result.rejectedPickNextWaifu) {
|
|
494
|
+
this.options.logger.warn("Ignoring invalid PickNextWaifu call from waifu", {
|
|
495
|
+
guildId,
|
|
496
|
+
channelId,
|
|
497
|
+
waifuId: waifu.id,
|
|
498
|
+
attemptedWaifuId: result.rejectedPickNextWaifu.waifuId,
|
|
499
|
+
attemptedSelfPick: result.rejectedPickNextWaifu.waifuId === waifu.id,
|
|
500
|
+
reason: result.rejectedPickNextWaifu.reason
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
else if (result.pickedNextWaifuId && directHandoffCount < this.maxAutomaticTurns) {
|
|
504
|
+
directHandoffCount += 1;
|
|
505
|
+
this.options.logger.info("Waifu picked next waifu; skipping orchestrator for direct handoff", {
|
|
506
|
+
guildId,
|
|
507
|
+
channelId,
|
|
508
|
+
waifuId: waifu.id,
|
|
509
|
+
pickedNextWaifuId: result.pickedNextWaifuId
|
|
510
|
+
});
|
|
511
|
+
responderQueue.splice(0, responderQueue.length, {
|
|
512
|
+
waifuId: result.pickedNextWaifuId,
|
|
513
|
+
delaySeconds: 0,
|
|
514
|
+
replyStyle: "normal"
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
else if (result.pickedNextWaifuId) {
|
|
518
|
+
this.options.logger.warn("Ignoring PickNextWaifu handoff because the automatic handoff limit was reached", {
|
|
519
|
+
guildId,
|
|
520
|
+
channelId,
|
|
521
|
+
waifuId: waifu.id,
|
|
522
|
+
pickedNextWaifuId: result.pickedNextWaifuId,
|
|
523
|
+
maxAutomaticTurns: this.maxAutomaticTurns
|
|
524
|
+
});
|
|
525
|
+
}
|
|
481
526
|
}
|
|
482
527
|
finally {
|
|
483
528
|
waifuTyping.stop();
|
|
@@ -943,7 +988,7 @@ export class RuntimeOrchestrator {
|
|
|
943
988
|
async readMemoryStore() {
|
|
944
989
|
return this.options.storage.readJson("user/memories.json", MemoryStoreSchema, emptyMemoryStore());
|
|
945
990
|
}
|
|
946
|
-
async buildWaifuSystemPrompt(guildId, waifu) {
|
|
991
|
+
async buildWaifuSystemPrompt(guildId, waifu, availableWaifus) {
|
|
947
992
|
const [store, emojis] = await Promise.all([
|
|
948
993
|
this.readMemoryStore(),
|
|
949
994
|
this.options.storage.readJson(path.join("user", "servers", guildId, "emojis.json"), GuildEmojisFileSchema, GuildEmojisFileSchema.parse(createEmptyRevisionedFile({ guildId, emojis: [] })))
|
|
@@ -973,9 +1018,9 @@ export class RuntimeOrchestrator {
|
|
|
973
1018
|
"You are chatting in a live Discord text channel — this is a real chat room with real users, not a roleplay scene, story, or chat fiction.",
|
|
974
1019
|
"Write one Discord-safe message per turn.",
|
|
975
1020
|
"Do not output physical actions, roleplay narration, or stage directions. No asterisks-wrapped actions like *smiles* or *waves*, no parenthetical stage notes like (hugs them), no bracketed cues like [walks over]. Only write what you would actually type into a chat box.",
|
|
976
|
-
"
|
|
977
|
-
"Avoid long sentences and
|
|
978
|
-
"
|
|
1021
|
+
"Default to one short sentence. Use two short sentences only when the second adds a new beat. Do not write three or more sentences unless a scene_direction explicitly asks for it.",
|
|
1022
|
+
"Avoid long sentences, stacked clauses, and multi-line replies. Do not explain every angle; land one conversational beat and stop.",
|
|
1023
|
+
"Each sentence in your reply may be delivered as a separate Discord message, so fewer sentences is better. A sharp one-liner is usually stronger than a mini speech.",
|
|
979
1024
|
"Reply with only what you would actually type — no narration, no meta commentary, no describing yourself in the third person.",
|
|
980
1025
|
"To ping a user, write <@sender> — where `sender` is copied verbatim from the [sender: ...] tag on one of their messages. Example: a message tagged [sender: Kevin] is pinged as <@Kevin>. Never use raw Discord IDs.",
|
|
981
1026
|
"Use only listed server emojis."
|
|
@@ -988,6 +1033,10 @@ export class RuntimeOrchestrator {
|
|
|
988
1033
|
if (memories) {
|
|
989
1034
|
behaviorSections.push(`<memories>\n${memories}\n</memories>`);
|
|
990
1035
|
}
|
|
1036
|
+
const toolUse = buildWaifuToolUseInstructions(waifu, availableWaifus);
|
|
1037
|
+
if (toolUse) {
|
|
1038
|
+
behaviorSections.push(`<tool_use>\n${toolUse}\n</tool_use>`);
|
|
1039
|
+
}
|
|
991
1040
|
const behaviorBlock = `<${behaviorTag}>\n${behaviorSections.join("\n")}\n</${behaviorTag}>`;
|
|
992
1041
|
const currentTimeBlock = `<current_time>\n${formatTimestamp(new Date())} (UTC)\n</current_time>`;
|
|
993
1042
|
const emojiBlock = `<available_server_emojis>\n${emojiList || "(none cached)"}\n</available_server_emojis>`;
|
|
@@ -997,6 +1046,7 @@ export class RuntimeOrchestrator {
|
|
|
997
1046
|
if (orchestrator.useLegacyPrompt) {
|
|
998
1047
|
return buildLegacyOrchestratorPrompt(server, availableWaifus);
|
|
999
1048
|
}
|
|
1049
|
+
const scheduleNow = new Date();
|
|
1000
1050
|
const activeWaifusContent = availableWaifus.length
|
|
1001
1051
|
? availableWaifus
|
|
1002
1052
|
.map((waifu) => {
|
|
@@ -1004,7 +1054,8 @@ export class RuntimeOrchestrator {
|
|
|
1004
1054
|
const displayName = waifu.displayName || waifu.name;
|
|
1005
1055
|
const persona = waifu.persona.trim();
|
|
1006
1056
|
const personaBlock = persona || "(no persona configured)";
|
|
1007
|
-
|
|
1057
|
+
const availability = formatWaifuAvailabilityForPrompt(waifu, scheduleNow);
|
|
1058
|
+
return `<${tagName}>\nID: ${waifu.id}\nDisplay name: ${displayName}\nPersona:\n${personaBlock}\nAvailability:\n${availability}\n</${tagName}>`;
|
|
1008
1059
|
})
|
|
1009
1060
|
.join("\n\n")
|
|
1010
1061
|
: "No waifus are currently enabled for this channel.";
|
|
@@ -1012,11 +1063,14 @@ export class RuntimeOrchestrator {
|
|
|
1012
1063
|
const hardRules = [
|
|
1013
1064
|
"- Every respondingWaifus[].waifuId must be copied verbatim from one of the IDs listed in <active_waifus>.",
|
|
1014
1065
|
"- action=\"reply\" requires a non-empty respondingWaifus array and a null retriggerAfterSeconds. action=\"no_reply\" requires respondingWaifus=[] and a retriggerAfterSeconds in [100, 7200].",
|
|
1015
|
-
|
|
1066
|
+
`- delaySeconds is a realistic reading/typing delay before that waifu starts replying, in seconds, from 0 to ${MAX_WAIFU_DELAY_SECONDS}. Use 0 if she should start immediately.`,
|
|
1016
1067
|
"- replyStyle is a soft hint for length/tone: \"normal\" by default; \"short\" for one terse line; \"long\" for a slightly fuller reply; \"sleepy\" for tired/low-energy voice.",
|
|
1068
|
+
"- Sleep and busy availability in <active_waifus> is soft context, not a hard rule. A sleeping or busy waifu can still answer if recent momentum suggests she is awake, if she just spoke, if she was directly pulled in, or if waking her improves the room.",
|
|
1017
1069
|
"- repleyToMessageIndex should usually be null. Set it only when a waifu is reviving or anchoring to a specific older message that is no longer the latest visible message; never to the immediately previous message.",
|
|
1018
1070
|
"- sceneDirection is the only private channel between you and that waifu. If you want her to do or say something specific, you MUST put it there. She does not see your reasoning. Use null when no special steering is needed.",
|
|
1019
|
-
"-
|
|
1071
|
+
"- After a single waifu message, do not default to no_reply just because a waifu already spoke. Actively consider whether another waifu should react, interrupt, tease, disagree, answer a missed user, or carry the beat one step further.",
|
|
1072
|
+
"- Prefer a two-waifu chain over a one-waifu reply when the second waifu has a distinct reaction that would make the room feel alive. Give the second waifu a small delaySeconds value so it feels like a timed follow-up, not simultaneous spam.",
|
|
1073
|
+
"- Consecutive waifu replies in a single decision are allowed when they add a fresh beat: escalation, interruption, joke, reaction, disagreement, emotional shift, or a new topic. Avoid only empty echoing or repetitive back-and-forth."
|
|
1020
1074
|
].join("\n");
|
|
1021
1075
|
const taskInstructions = DEFAULT_ORCHESTRATOR_PROMPT;
|
|
1022
1076
|
const loopBreaking = [
|
|
@@ -1029,7 +1083,7 @@ export class RuntimeOrchestrator {
|
|
|
1029
1083
|
"Human messages automatically wake the orchestrator, so retriggerAfterSeconds is not a generic monitoring tick — it is a deliberate, planned pause to give the room a chance to react. If a fresh chat message arrives before the timer fires, the timer is replaced by the new orchestrator pass.",
|
|
1030
1084
|
"",
|
|
1031
1085
|
"Rough intent ranges:",
|
|
1032
|
-
"- 100s–300s: moments that feel alive
|
|
1086
|
+
"- 100s–300s: moments that feel alive but do not have an obvious second waifu reaction. If there is an obvious second reaction, prefer respondingWaifus with two waifus and a short delay instead of no_reply.",
|
|
1033
1087
|
"- 600s–1800s: cooling rooms where a waifu might revive the chat soon, but not immediately.",
|
|
1034
1088
|
"- 3600s–7200s: quiet rooms where you are mostly waiting for humans and the next bot-led beat is a long shot.",
|
|
1035
1089
|
"",
|
|
@@ -1047,7 +1101,7 @@ export class RuntimeOrchestrator {
|
|
|
1047
1101
|
" \"respondingWaifus\": [",
|
|
1048
1102
|
" {",
|
|
1049
1103
|
" \"waifuId\": string,",
|
|
1050
|
-
|
|
1104
|
+
` \"delaySeconds\": number (0..${MAX_WAIFU_DELAY_SECONDS}),`,
|
|
1051
1105
|
" \"replyStyle\": \"normal\" | \"short\" | \"long\" | \"sleepy\",",
|
|
1052
1106
|
" \"repleyToMessageIndex\": number | null,",
|
|
1053
1107
|
" \"sceneDirection\": string | null",
|
|
@@ -1060,7 +1114,8 @@ export class RuntimeOrchestrator {
|
|
|
1060
1114
|
"Rules:",
|
|
1061
1115
|
"- action=\"reply\" => respondingWaifus is non-empty; retriggerAfterSeconds is null.",
|
|
1062
1116
|
"- action=\"no_reply\" => respondingWaifus is empty; retriggerAfterSeconds is a number in [100, 7200].",
|
|
1063
|
-
"- Order matters in respondingWaifus: the first waifu speaks first, then the next, and so on. Any new chat message interrupts the rest of the chain.",
|
|
1117
|
+
"- Order matters in respondingWaifus: the first waifu speaks first, then the next, and so on. Each waifu's delay starts only after the previous waifu has finished. Any new chat message interrupts the rest of the chain.",
|
|
1118
|
+
"- When choosing two waifus, give each entry its own delaySeconds. Use 0 for the first when she should start immediately, then a small delay like 3-12 seconds for the second when it should feel like a natural follow-up.",
|
|
1064
1119
|
"- All five fields on each respondingWaifus entry are required; set repleyToMessageIndex and sceneDirection to null when not needed."
|
|
1065
1120
|
].join("\n");
|
|
1066
1121
|
const sections = orchestrator.promptSections;
|
|
@@ -1225,6 +1280,71 @@ export class RuntimeOrchestrator {
|
|
|
1225
1280
|
function emptyMemoryStore() {
|
|
1226
1281
|
return MemoryStoreSchema.parse(createEmptyRevisionedFile({ memories: [] }));
|
|
1227
1282
|
}
|
|
1283
|
+
function buildWaifuToolUseInstructions(waifu, availableWaifus) {
|
|
1284
|
+
if (!waifu.tools.toolUse || !waifu.tools.pickNextWaifu)
|
|
1285
|
+
return undefined;
|
|
1286
|
+
const model = waifu.modelId ? getModel(waifu.modelId) : undefined;
|
|
1287
|
+
if (!model?.supportsTools)
|
|
1288
|
+
return undefined;
|
|
1289
|
+
const candidates = availableWaifus
|
|
1290
|
+
.filter((candidate) => candidate.id !== waifu.id && candidate.botId && candidate.modelId)
|
|
1291
|
+
.map((candidate) => `${candidate.id} (${candidate.displayName || candidate.name})`);
|
|
1292
|
+
if (candidates.length === 0)
|
|
1293
|
+
return undefined;
|
|
1294
|
+
return [
|
|
1295
|
+
"You have one optional tool: PickNextWaifu.",
|
|
1296
|
+
"Use it only after writing your Discord reply when another waifu should immediately speak next and the orchestrator should be skipped for that handoff.",
|
|
1297
|
+
"Do not call it if your message should be the end of this beat.",
|
|
1298
|
+
"Arguments: { \"waifuId\": string }.",
|
|
1299
|
+
"Available waifus:",
|
|
1300
|
+
...candidates.map((candidate) => `- ${candidate}`)
|
|
1301
|
+
].join("\n");
|
|
1302
|
+
}
|
|
1303
|
+
function formatWaifuAvailabilityForPrompt(waifu, now) {
|
|
1304
|
+
const availability = waifu.availability;
|
|
1305
|
+
const currentMinutes = localTimeOfDayMinutes(now);
|
|
1306
|
+
const lines = [`- Current local schedule time: ${formatLocalTimeOfDay(now)}.`];
|
|
1307
|
+
if (availability.sleep.enabled) {
|
|
1308
|
+
const sleepingNow = dailyIntervalContains(currentMinutes, availability.sleep);
|
|
1309
|
+
lines.push(`- Sleep: ${availability.sleep.start}-${availability.sleep.end} daily (${sleepingNow ? "currently inside sleep time" : "not currently inside sleep time"}). Treat this as lower likelihood, not a rule; she may still be awake if she spoke recently, was directly pulled in, or waking her improves the room.`);
|
|
1310
|
+
}
|
|
1311
|
+
else {
|
|
1312
|
+
lines.push("- Sleep: none configured.");
|
|
1313
|
+
}
|
|
1314
|
+
if (availability.busy.length > 0) {
|
|
1315
|
+
lines.push("- Busy:");
|
|
1316
|
+
for (const interval of availability.busy) {
|
|
1317
|
+
const busyNow = dailyIntervalContains(currentMinutes, interval);
|
|
1318
|
+
lines.push(` - ${interval.start}-${interval.end}: ${interval.reason}${busyNow ? " (currently busy)" : ""}`);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
else {
|
|
1322
|
+
lines.push("- Busy: none configured.");
|
|
1323
|
+
}
|
|
1324
|
+
return lines.join("\n");
|
|
1325
|
+
}
|
|
1326
|
+
function localTimeOfDayMinutes(date) {
|
|
1327
|
+
return date.getHours() * 60 + date.getMinutes();
|
|
1328
|
+
}
|
|
1329
|
+
function formatLocalTimeOfDay(date) {
|
|
1330
|
+
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
1331
|
+
}
|
|
1332
|
+
function dailyIntervalContains(currentMinutes, interval) {
|
|
1333
|
+
const start = timeOfDayMinutes(interval.start);
|
|
1334
|
+
const end = timeOfDayMinutes(interval.end);
|
|
1335
|
+
if (start === end)
|
|
1336
|
+
return false;
|
|
1337
|
+
if (start < end)
|
|
1338
|
+
return currentMinutes >= start && currentMinutes < end;
|
|
1339
|
+
return currentMinutes >= start || currentMinutes < end;
|
|
1340
|
+
}
|
|
1341
|
+
function timeOfDayMinutes(value) {
|
|
1342
|
+
const [hours = "0", minutes = "0"] = value.split(":");
|
|
1343
|
+
return Number(hours) * 60 + Number(minutes);
|
|
1344
|
+
}
|
|
1345
|
+
function indentLines(value, indent) {
|
|
1346
|
+
return value.split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
1347
|
+
}
|
|
1228
1348
|
function sanitizeTagName(value) {
|
|
1229
1349
|
return value.trim().toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "waifu";
|
|
1230
1350
|
}
|
|
@@ -1243,6 +1363,15 @@ function replyTargetForFreshContext(replyToMessageId, messages) {
|
|
|
1243
1363
|
const latestMessage = messages.at(-1);
|
|
1244
1364
|
return latestMessage?.id === replyToMessageId ? undefined : replyToMessageId;
|
|
1245
1365
|
}
|
|
1366
|
+
function capDecisionDelays(decision) {
|
|
1367
|
+
return {
|
|
1368
|
+
...decision,
|
|
1369
|
+
respondingWaifus: decision.respondingWaifus.map((responder) => ({
|
|
1370
|
+
...responder,
|
|
1371
|
+
delaySeconds: Math.min(responder.delaySeconds, MAX_WAIFU_DELAY_SECONDS)
|
|
1372
|
+
}))
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1246
1375
|
function throwIfAborted(signal) {
|
|
1247
1376
|
if (signal?.aborted) {
|
|
1248
1377
|
throw signal.reason instanceof Error ? signal.reason : new Error("Request aborted.");
|
|
@@ -1316,13 +1445,17 @@ const DEFAULT_ORCHESTRATOR_PROMPT = [
|
|
|
1316
1445
|
"",
|
|
1317
1446
|
"When action=\"reply\", list the waifus in respondingWaifus in the order they should speak. Order matters: the next waifu only sees the chat after the previous one has finished. Any new chat message between two replies cancels the rest of the chain. Plan the chain you'd commit to if nobody interrupts.",
|
|
1318
1447
|
"",
|
|
1448
|
+
"When the beat can support it, prefer a planned two-waifu chain over one waifu followed by no_reply. The second waifu should have a distinct angle — reaction, interruption, tease, disagreement, missed-user acknowledgment, or escalation — and a small delaySeconds value so it feels like a natural follow-up.",
|
|
1449
|
+
"",
|
|
1319
1450
|
"When action=\"no_reply\", set retriggerAfterSeconds to the number of seconds you want to wait before re-evaluating the room. The orchestrator also wakes up automatically on any new chat message, so retriggerAfterSeconds is a planned pause, not a polling tick. A chain of one no_reply means \"don't speak now; re-check after the pause.\"",
|
|
1320
1451
|
"",
|
|
1452
|
+
"After a single waifu message, do not treat no_reply as the automatic cleanup move. Ask whether the room would feel more alive if another waifu reacts, cuts in, disagrees, lightly teases, answers a missed user, or follows the beat one step further. Choose no_reply when the beat has genuinely landed, the next bot message would feel repetitive, or silence creates better pacing.",
|
|
1453
|
+
"",
|
|
1321
1454
|
"If a recent chat participant message or direct ping was missed while the room moved on, prefer steering a waifu to acknowledge it so the chat stays socially inclusive — unless silence is clearly the more natural choice.",
|
|
1322
1455
|
"",
|
|
1323
1456
|
"Reach for sceneDirection when the next reply needs steering that personality alone won't provide: redirecting topic, closing a beat, creating an interruption, shifting momentum, or deliberately starting something new even when it cuts against the current flow. Prefer a natural bridge when pivoting, but a jarring shift is fine if the scene needs it. Keep sceneDirection short, concrete, and immediately actionable — one sentence is usually enough. When you refer to a specific person, use their actual display name from the chat history, never generic phrases like \"the user\". Name intended participants explicitly when more than one person is involved; avoid ambiguous group references like \"us\", \"them\", or \"everyone\". If multiple waifus respond in the same turn, each may receive a different sceneDirection.",
|
|
1324
1457
|
"",
|
|
1325
|
-
|
|
1458
|
+
`delaySeconds is a realistic reading/typing delay before that waifu starts replying, capped at ${MAX_WAIFU_DELAY_SECONDS} seconds. Use 0 to start immediately; small values feel natural for short replies; larger values fit longer or more thoughtful replies. Keep it grounded in the chat's pace.`,
|
|
1326
1459
|
"",
|
|
1327
1460
|
"replyStyle is a soft hint for that one reply: \"normal\" by default, \"short\" for a one-line beat, \"long\" for a slightly fuller reply, \"sleepy\" for a low-energy tone. The waifu's persona still does most of the work.",
|
|
1328
1461
|
"",
|
|
@@ -1330,15 +1463,17 @@ const DEFAULT_ORCHESTRATOR_PROMPT = [
|
|
|
1330
1463
|
"",
|
|
1331
1464
|
"Pay special attention to the latest 10 messages and the recent speaker pattern. If the same waifu has been carrying the scene for multiple beats, strongly consider switching to another waifu, using no_reply, or using sceneDirection to create a fresh beat.",
|
|
1332
1465
|
"",
|
|
1333
|
-
"Continue a waifu-to-waifu chain
|
|
1466
|
+
"Continue a waifu-to-waifu chain when the next message adds something new: escalation, interruption, joke, emotional shift, contradiction, surprise, a missed-user acknowledgment, or a new topic. Do not continue just to restate the same mood."
|
|
1334
1467
|
].join("\n");
|
|
1335
1468
|
function buildLegacyOrchestratorPrompt(server, availableWaifus) {
|
|
1469
|
+
const scheduleNow = new Date();
|
|
1336
1470
|
const waifuBlock = availableWaifus.length
|
|
1337
1471
|
? availableWaifus
|
|
1338
1472
|
.map((waifu) => {
|
|
1339
1473
|
const displayName = waifu.displayName || waifu.name || waifu.id;
|
|
1340
1474
|
const persona = waifu.persona.trim() || "(no persona configured)";
|
|
1341
|
-
|
|
1475
|
+
const availability = formatWaifuAvailabilityForPrompt(waifu, scheduleNow);
|
|
1476
|
+
return `### ${displayName} (ID: ${waifu.id})\n- Personality: ${persona}\n- Availability:\n${indentLines(availability, " ")}`;
|
|
1342
1477
|
})
|
|
1343
1478
|
.join("\n\n")
|
|
1344
1479
|
: "No waifus are currently enabled for this channel.";
|
|
@@ -1364,17 +1499,19 @@ function buildLegacyOrchestratorPrompt(server, availableWaifus) {
|
|
|
1364
1499
|
"4. Always pay special attention to the latest 10 messages. They are the strongest signal for what the room is currently doing, who may have been overlooked, and whether a loop is starting to form.",
|
|
1365
1500
|
"5. Sleep time, busy time, and consecutive-message heuristics are soft preferences. Break them whenever doing so would clearly improve conversational flow, realism, or enjoyment.",
|
|
1366
1501
|
"6. The same waifu may speak again, a different waifu may jump in, or multiple waifus may chain if it feels right.",
|
|
1367
|
-
"7.
|
|
1368
|
-
"8.
|
|
1369
|
-
"9.
|
|
1370
|
-
"10.
|
|
1371
|
-
"11.
|
|
1372
|
-
"12.
|
|
1373
|
-
|
|
1374
|
-
"14.
|
|
1375
|
-
"15.
|
|
1376
|
-
"16.
|
|
1377
|
-
"17.
|
|
1502
|
+
"7. Prefer a planned two-waifu chain when the second waifu has a distinct reaction and can follow after a small delaySeconds value.",
|
|
1503
|
+
"8. After a single waifu message, do not default to no_reply. Consider whether another waifu should react, interrupt, disagree, tease, answer a missed user, or carry the beat one step further.",
|
|
1504
|
+
"9. Avoid repetitive follow-ups that merely restate the same beat. Continue when the next message adds something new.",
|
|
1505
|
+
"10. If a recent user message or direct ping went unnoticed while the room moved on, prefer steering someone to acknowledge it so the chat stays socially inclusive unless silence is clearly more natural.",
|
|
1506
|
+
"11. \"no_reply\" is valid. If you choose it, set retriggerAfterSeconds to a natural delay between 100 and 7200 seconds. respondingWaifus must be empty.",
|
|
1507
|
+
"12. Use timestamps and pacing. Slow gaps matter.",
|
|
1508
|
+
`13. delaySeconds should reflect realistic reading and typing time from 0 to ${MAX_WAIFU_DELAY_SECONDS}. 0 means start immediately.`,
|
|
1509
|
+
"14. replyStyle is a soft hint: \"normal\" by default; \"short\" for one terse line; \"long\" for a slightly fuller reply; \"sleepy\" for a low-energy voice. Use \"normal\" when in doubt.",
|
|
1510
|
+
"15. repleyToMessageIndex is optional. Leave it null by default.",
|
|
1511
|
+
"16. Most waifu messages should be normal messages, not Discord replies.",
|
|
1512
|
+
"17. Do not set repleyToMessageIndex to the immediately previous message. If a waifu is simply responding to the latest beat, send a normal message instead.",
|
|
1513
|
+
"18. If you are reviving, acknowledging, or directly answering an older user message or direct ping that went overlooked, you should usually set repleyToMessageIndex to that message's #N context index so the response stays anchored to the right person and beat.",
|
|
1514
|
+
"19. Use repleyToMessageIndex only when targeting a specific older message materially improves clarity, isolates a side thread, answers an earlier question, or creates a specific social effect. Copy the #N index from the chat history.",
|
|
1378
1515
|
"",
|
|
1379
1516
|
"## sceneDirection",
|
|
1380
1517
|
"sceneDirection is an invisible director note for that waifu's next message only.",
|
|
@@ -1397,7 +1534,7 @@ function buildLegacyOrchestratorPrompt(server, availableWaifus) {
|
|
|
1397
1534
|
"{",
|
|
1398
1535
|
" \"action\": \"reply\" | \"no_reply\",",
|
|
1399
1536
|
" \"respondingWaifus\": [",
|
|
1400
|
-
|
|
1537
|
+
` { \"waifuId\": string, \"delaySeconds\": number (0..${MAX_WAIFU_DELAY_SECONDS}), \"replyStyle\": \"normal\"|\"short\"|\"long\"|\"sleepy\", \"repleyToMessageIndex\": number|null, \"sceneDirection\": string|null }`,
|
|
1401
1538
|
" ],",
|
|
1402
1539
|
" \"retriggerAfterSeconds\": number (100..7200) | null,",
|
|
1403
1540
|
" \"reasoning\": string",
|