@waifucave/discord-waifus 1.5.196 → 1.5.197
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/api/assistant/conversations.d.ts +2 -1
- package/dist/api/assistant/conversations.js +30 -8
- package/dist/api/assistant/conversations.js.map +1 -1
- package/dist/api/assistant/routes.js +7 -4
- package/dist/api/assistant/routes.js.map +1 -1
- package/dist/api/assistant/service.js +30 -6
- package/dist/api/assistant/service.js.map +1 -1
- package/dist/api/assistant/tools.js +175 -27
- package/dist/api/assistant/tools.js.map +1 -1
- package/dist/api/server.js +16 -0
- package/dist/api/server.js.map +1 -1
- package/dist-frontend/assets/{index-CgNbleJE.js → index-A6kJVkoz.js} +2 -2
- package/dist-frontend/assets/{index-CgNbleJE.js.map → index-A6kJVkoz.js.map} +1 -1
- package/dist-frontend/index.html +1 -1
- package/docs/assistant-kb/api.md +2 -2
- package/docs/assistant-kb/discord-setup.md +2 -2
- package/docs/assistant-kb/memory.md +2 -2
- package/docs/assistant-kb/providers-and-models.md +1 -1
- package/docs/assistant-kb/troubleshooting.md +9 -0
- package/package.json +1 -1
|
@@ -25,6 +25,7 @@ export type StoredMessage = {
|
|
|
25
25
|
} | {
|
|
26
26
|
role: "event";
|
|
27
27
|
event: AssistantEvent;
|
|
28
|
+
seq?: number;
|
|
28
29
|
at: string;
|
|
29
30
|
};
|
|
30
31
|
/**
|
|
@@ -46,6 +47,6 @@ export declare class ConversationStore {
|
|
|
46
47
|
appendChat(id: string, messages: ChatMessage[]): void;
|
|
47
48
|
appendStored(id: string, message: StoredMessage): void;
|
|
48
49
|
setBusy(id: string, busy: boolean): void;
|
|
49
|
-
subscribe(id: string, listener: (event: AssistantEvent) => void): () => void;
|
|
50
|
+
subscribe(id: string, listener: (event: AssistantEvent, seq: number) => void): () => void;
|
|
50
51
|
emit(id: string, event: AssistantEvent): void;
|
|
51
52
|
}
|
|
@@ -10,11 +10,14 @@ export class ConversationStore {
|
|
|
10
10
|
conversations = new Map();
|
|
11
11
|
create() {
|
|
12
12
|
const id = randomUUID();
|
|
13
|
-
this.conversations.set(id, { id, messages: [], chat: [], busy: false, listeners: new Set() });
|
|
14
|
-
// Map preserves insertion order
|
|
13
|
+
this.conversations.set(id, { id, messages: [], chat: [], busy: false, eventSeq: 0, listeners: new Set() });
|
|
14
|
+
// Map preserves insertion order; get() re-inserts, so iteration order is true LRU.
|
|
15
|
+
// Never evict a conversation mid-turn (its output would silently vanish).
|
|
15
16
|
while (this.conversations.size > MAX_CONVERSATIONS) {
|
|
16
|
-
const
|
|
17
|
-
|
|
17
|
+
const victim = [...this.conversations.values()].find((candidate) => !candidate.busy && candidate.id !== id);
|
|
18
|
+
if (!victim)
|
|
19
|
+
break;
|
|
20
|
+
this.conversations.delete(victim.id);
|
|
18
21
|
}
|
|
19
22
|
return { id };
|
|
20
23
|
}
|
|
@@ -22,13 +25,29 @@ export class ConversationStore {
|
|
|
22
25
|
const conversation = this.conversations.get(id);
|
|
23
26
|
if (!conversation)
|
|
24
27
|
return undefined;
|
|
28
|
+
// LRU touch: re-insert so create() evicts genuinely idle conversations first
|
|
29
|
+
this.conversations.delete(id);
|
|
30
|
+
this.conversations.set(id, conversation);
|
|
25
31
|
const { listeners: _listeners, ...visible } = conversation;
|
|
26
32
|
return visible;
|
|
27
33
|
}
|
|
28
34
|
appendChat(id, messages) {
|
|
29
35
|
const conversation = this.conversations.get(id);
|
|
30
|
-
if (conversation)
|
|
31
|
-
|
|
36
|
+
if (!conversation)
|
|
37
|
+
return;
|
|
38
|
+
// trim the model transcript from the front (never the system prompt) past a byte budget —
|
|
39
|
+
// tool results are up to 6000 chars each and were resent wholesale forever (audit finding 12)
|
|
40
|
+
const budget = 120_000;
|
|
41
|
+
let size = messages.reduce((sum, message) => sum + JSON.stringify(message).length, 0);
|
|
42
|
+
const trimmed = [...messages];
|
|
43
|
+
while (size > budget && trimmed.length > 2) {
|
|
44
|
+
const index = trimmed.findIndex((message) => message.role !== "system");
|
|
45
|
+
if (index === -1)
|
|
46
|
+
break;
|
|
47
|
+
size -= JSON.stringify(trimmed[index]).length;
|
|
48
|
+
trimmed.splice(index, 1);
|
|
49
|
+
}
|
|
50
|
+
conversation.chat = trimmed;
|
|
32
51
|
}
|
|
33
52
|
appendStored(id, message) {
|
|
34
53
|
const conversation = this.conversations.get(id);
|
|
@@ -55,9 +74,12 @@ export class ConversationStore {
|
|
|
55
74
|
const conversation = this.conversations.get(id);
|
|
56
75
|
if (!conversation)
|
|
57
76
|
return;
|
|
58
|
-
|
|
77
|
+
// monotone sequence: the SSE layer sends it as the event id so clients can drop
|
|
78
|
+
// duplicates when the browser reconnects and history is replayed (audit finding 10)
|
|
79
|
+
const seq = ++conversation.eventSeq;
|
|
80
|
+
this.appendStored(id, { role: "event", event, seq, at: new Date().toISOString() });
|
|
59
81
|
for (const listener of conversation.listeners)
|
|
60
|
-
listener(event);
|
|
82
|
+
listener(event, seq);
|
|
61
83
|
}
|
|
62
84
|
}
|
|
63
85
|
//# sourceMappingURL=conversations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversations.js","sourceRoot":"","sources":["../../../src/api/assistant/conversations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"conversations.js","sourceRoot":"","sources":["../../../src/api/assistant/conversations.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,19 +36,22 @@ export function registerAssistantRoutes(app, options) {
|
|
|
36
36
|
reply.code(404).send({ error: "unknown conversation" });
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
+
reply.hijack();
|
|
39
40
|
reply.raw.writeHead(200, {
|
|
40
41
|
"content-type": "text/event-stream",
|
|
41
42
|
"cache-control": "no-cache",
|
|
42
43
|
connection: "keep-alive"
|
|
43
44
|
});
|
|
44
|
-
// Replay recorded events so a reopened panel catches up, then stream live.
|
|
45
|
+
// Replay recorded events so a reopened panel catches up, then stream live. Each frame
|
|
46
|
+
// carries the conversation-monotone seq as its SSE id so clients can drop duplicates
|
|
47
|
+
// after browser auto-reconnects.
|
|
45
48
|
for (const message of conversation.messages) {
|
|
46
49
|
if (message.role === "event") {
|
|
47
|
-
reply.raw.write(`event: assistant\ndata: ${JSON.stringify(message.event)}\n\n`);
|
|
50
|
+
reply.raw.write(`event: assistant\nid: ${message.seq ?? 0}\ndata: ${JSON.stringify(message.event)}\n\n`);
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
|
-
const unsubscribe = store.subscribe(id, (event) => {
|
|
51
|
-
reply.raw.write(`event: assistant\ndata: ${JSON.stringify(event)}\n\n`);
|
|
53
|
+
const unsubscribe = store.subscribe(id, (event, seq) => {
|
|
54
|
+
reply.raw.write(`event: assistant\nid: ${seq}\ndata: ${JSON.stringify(event)}\n\n`);
|
|
52
55
|
});
|
|
53
56
|
const heartbeat = setInterval(() => {
|
|
54
57
|
reply.raw.write(`event: heartbeat\ndata: ${JSON.stringify({ time: new Date().toISOString() })}\n\n`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/api/assistant/routes.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/api/assistant/routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,7 +17,7 @@ export async function resolveAssistantTarget(app, dataRoot) {
|
|
|
17
17
|
const orchestrator = await getJson(app, "/api/orchestrator/config");
|
|
18
18
|
const source = assistant?.modelId ? assistant : orchestrator?.modelId ? orchestrator : undefined;
|
|
19
19
|
if (!source) {
|
|
20
|
-
return { ok: false, reason: "No model configured
|
|
20
|
+
return { ok: false, reason: "No model is configured for the assistant. Set one under Direction \u2192 Assistant (do not repurpose the orchestrator\u2019s setting \u2014 an empty orchestrator model can be a deliberate free deterministic mode)." };
|
|
21
21
|
}
|
|
22
22
|
let target;
|
|
23
23
|
try {
|
|
@@ -80,9 +80,21 @@ export async function runAssistantTurn(deps, conversationId, userContent) {
|
|
|
80
80
|
throw new AssistantTurnError(404, "Unknown conversation.");
|
|
81
81
|
if (conversation.busy)
|
|
82
82
|
throw new AssistantTurnError(409, "A turn is already running in this conversation.");
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// claim the conversation BEFORE the first await — two concurrent POSTs both passed the
|
|
84
|
+
// check above and interleaved, last-writer-winning the model transcript (audit finding 6)
|
|
85
|
+
store.setBusy(conversationId, true);
|
|
86
|
+
let target;
|
|
87
|
+
try {
|
|
88
|
+
target = await resolveAssistantTarget(app, dataRoot);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
store.setBusy(conversationId, false);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
if (!target.ok) {
|
|
95
|
+
store.setBusy(conversationId, false);
|
|
85
96
|
throw new AssistantTurnError(503, target.reason);
|
|
97
|
+
}
|
|
86
98
|
const pipeline = deps.createPipeline
|
|
87
99
|
? deps.createPipeline({ providerId: target.providerId, modelId: target.modelId })
|
|
88
100
|
: createGatewayModelPipeline({
|
|
@@ -91,13 +103,17 @@ export async function runAssistantTurn(deps, conversationId, userContent) {
|
|
|
91
103
|
queryRole: "assistant",
|
|
92
104
|
dataRoot
|
|
93
105
|
});
|
|
94
|
-
if (!pipeline.generateAssistantTurn)
|
|
106
|
+
if (!pipeline.generateAssistantTurn) {
|
|
107
|
+
store.setBusy(conversationId, false);
|
|
95
108
|
throw new AssistantTurnError(503, "The resolved pipeline cannot run assistant turns.");
|
|
109
|
+
}
|
|
96
110
|
const transcript = conversation.chat.length > 0
|
|
97
111
|
? [...conversation.chat]
|
|
98
112
|
: [{ role: "system", content: assistantSystemPrompt(await buildSnapshot(app)) }];
|
|
99
113
|
transcript.push({ role: "user", content: userContent });
|
|
100
|
-
|
|
114
|
+
// persist the user turn into the MODEL transcript now: if the pipeline throws, the next
|
|
115
|
+
// turn must still contain this message (audit finding 8 — "well??" after a failed turn)
|
|
116
|
+
store.appendChat(conversationId, transcript);
|
|
101
117
|
store.appendStored(conversationId, { role: "user", content: userContent, at: new Date().toISOString() });
|
|
102
118
|
store.emit(conversationId, { type: "turn_started" });
|
|
103
119
|
try {
|
|
@@ -107,7 +123,7 @@ export async function runAssistantTurn(deps, conversationId, userContent) {
|
|
|
107
123
|
tools: toolDefs(),
|
|
108
124
|
params: target.params,
|
|
109
125
|
executeTool: (name, argsJson) => executeAssistantTool({ app }, name, argsJson),
|
|
110
|
-
onEvent: (event) => store.emit(conversationId, event)
|
|
126
|
+
onEvent: (event) => store.emit(conversationId, scrubSecretsFromEvent(event))
|
|
111
127
|
});
|
|
112
128
|
store.appendChat(conversationId, result.messages);
|
|
113
129
|
store.appendStored(conversationId, { role: "assistant", content: result.content, at: new Date().toISOString() });
|
|
@@ -124,4 +140,12 @@ export async function runAssistantTurn(deps, conversationId, userContent) {
|
|
|
124
140
|
store.setBusy(conversationId, false);
|
|
125
141
|
}
|
|
126
142
|
}
|
|
143
|
+
/** Keys/tokens must never persist in the display transcript or SSE replay, even when the
|
|
144
|
+
* user pasted one into chat and the model called set_provider_key with it. */
|
|
145
|
+
function scrubSecretsFromEvent(event) {
|
|
146
|
+
if (event.type !== "tool_call")
|
|
147
|
+
return event;
|
|
148
|
+
const scrubbed = event.arguments.replace(/("(?:apiKey|token)"\s*:\s*")([^"]+)(")/g, "$1[redacted]$3");
|
|
149
|
+
return scrubbed === event.arguments ? event : { ...event, arguments: scrubbed };
|
|
150
|
+
}
|
|
127
151
|
//# sourceMappingURL=service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/api/assistant/service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,0BAA0B,EAAE,MAAM,iDAAiD,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,+CAA+C,CAAC;AACnF,OAAO,EAAE,+BAA+B,EAAE,MAAM,6BAA6B,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAO5D,KAAK,UAAU,OAAO,CAAC,GAAoB,EAAE,GAAW;IACtD,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1D,IAAI,QAAQ,CAAC,UAAU,KAAK,GAAG;QAAE,OAAO,SAAS,CAAC;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAA4B,CAAC;AAC9D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,GAAoB,EAAE,QAAgB;IACjF,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAC;IAC9D,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,0BAA0B,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;IACjG,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/api/assistant/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readDoc, searchDocs } from "../docsKb.js";
|
|
1
|
+
import { listDocs, readDoc, searchDocs } from "../docsKb.js";
|
|
2
2
|
async function inject(ctx, options) {
|
|
3
3
|
const response = await ctx.app.inject({
|
|
4
4
|
method: options.method,
|
|
@@ -35,7 +35,8 @@ const AGENT_CONFIG_URLS = {
|
|
|
35
35
|
export const ASSISTANT_TOOLS = [
|
|
36
36
|
{
|
|
37
37
|
name: "get_runtime_status",
|
|
38
|
-
description: "Runtime and Discord connection status: connected bots, pause state, queues, data root
|
|
38
|
+
description: "Runtime and Discord connection status: connected bots, pause state, queues, data root — and " +
|
|
39
|
+
"discord.warnings, which includes live per-channel permission failures (bot cannot send).",
|
|
39
40
|
parameters: NO_ARGS,
|
|
40
41
|
execute: async (ctx) => {
|
|
41
42
|
const [runtime, status] = await Promise.all([
|
|
@@ -133,11 +134,27 @@ export const ASSISTANT_TOOLS = [
|
|
|
133
134
|
},
|
|
134
135
|
{
|
|
135
136
|
name: "list_models",
|
|
136
|
-
description: "
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
description: "Models in the gateway registry (compact: id, provider, displayName). Filter by providerId to stay small — " +
|
|
138
|
+
"the unfiltered list is long and may truncate.",
|
|
139
|
+
parameters: {
|
|
140
|
+
type: "object",
|
|
141
|
+
properties: { providerId: { type: "string", description: "e.g. deepseek, google-ai-studio, anthropic, openai" } },
|
|
142
|
+
additionalProperties: false
|
|
143
|
+
},
|
|
144
|
+
execute: async (ctx, args) => {
|
|
139
145
|
const result = await inject(ctx, { method: "GET", url: "/api/llm/v1/models" });
|
|
140
|
-
|
|
146
|
+
if (result.status !== 200)
|
|
147
|
+
return `Model registry unavailable (${result.status}).`;
|
|
148
|
+
const parsed = JSON.parse(result.body);
|
|
149
|
+
const models = Array.isArray(parsed) ? parsed : (parsed.data ?? []);
|
|
150
|
+
const compact = models
|
|
151
|
+
.map((model) => ({
|
|
152
|
+
id: model.id ?? model.modelId,
|
|
153
|
+
provider: model.provider ?? model.providerId,
|
|
154
|
+
displayName: model.displayName ?? model.name
|
|
155
|
+
}))
|
|
156
|
+
.filter((model) => (args.providerId ? model.provider === args.providerId : true));
|
|
157
|
+
return JSON.stringify(compact);
|
|
141
158
|
}
|
|
142
159
|
},
|
|
143
160
|
{
|
|
@@ -206,9 +223,15 @@ export const ASSISTANT_TOOLS = [
|
|
|
206
223
|
required: ["id", "changes"],
|
|
207
224
|
additionalProperties: false
|
|
208
225
|
},
|
|
209
|
-
execute: async (ctx, args) => revisionedPut(ctx, `/api/waifus/${encodeURIComponent(String(args.id))}`, () =>
|
|
210
|
-
...args.changes
|
|
211
|
-
|
|
226
|
+
execute: async (ctx, args) => revisionedPut(ctx, `/api/waifus/${encodeURIComponent(String(args.id))}`, (current) => {
|
|
227
|
+
const changes = { ...args.changes };
|
|
228
|
+
// params replace wholesale server-side; merge here so "set the temperature"
|
|
229
|
+
// does not silently erase the rest of her sampling/reasoning config
|
|
230
|
+
if (changes.params && typeof changes.params === "object") {
|
|
231
|
+
changes.params = { ...current.params, ...changes.params };
|
|
232
|
+
}
|
|
233
|
+
return changes;
|
|
234
|
+
})
|
|
212
235
|
},
|
|
213
236
|
{
|
|
214
237
|
name: "delete_waifu",
|
|
@@ -220,8 +243,19 @@ export const ASSISTANT_TOOLS = [
|
|
|
220
243
|
additionalProperties: false
|
|
221
244
|
},
|
|
222
245
|
execute: async (ctx, args) => {
|
|
223
|
-
const
|
|
224
|
-
|
|
246
|
+
const url = `/api/waifus/${encodeURIComponent(String(args.id))}`;
|
|
247
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
248
|
+
const current = await inject(ctx, { method: "GET", url });
|
|
249
|
+
if (current.status !== 200)
|
|
250
|
+
return current.body;
|
|
251
|
+
const { revision } = JSON.parse(current.body);
|
|
252
|
+
const result = await inject(ctx, { method: "DELETE", url, payload: { revision } });
|
|
253
|
+
if (result.status < 300)
|
|
254
|
+
return `Waifu ${args.id} deleted.`;
|
|
255
|
+
if (result.status !== 409)
|
|
256
|
+
return result.body;
|
|
257
|
+
}
|
|
258
|
+
return "Conflict: the waifu changed twice while I was deleting. Try again.";
|
|
225
259
|
}
|
|
226
260
|
},
|
|
227
261
|
{
|
|
@@ -326,10 +360,27 @@ export const ASSISTANT_TOOLS = [
|
|
|
326
360
|
});
|
|
327
361
|
if (botsPut.startsWith("GET ") || botsPut.startsWith("Conflict"))
|
|
328
362
|
return botsPut;
|
|
363
|
+
let bots;
|
|
364
|
+
try {
|
|
365
|
+
bots = JSON.parse(botsPut);
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
return `Bot write returned an unreadable response: ${botsPut.slice(0, 200)}`;
|
|
369
|
+
}
|
|
370
|
+
if (bots.error || !Array.isArray(bots.waifus))
|
|
371
|
+
return `Bot write failed: ${botsPut.slice(0, 300)}`;
|
|
329
372
|
const waifuPut = await revisionedPut(ctx, `/api/waifus/${encodeURIComponent(waifuId)}`, () => ({ botId: waifuId }));
|
|
330
373
|
if (waifuPut.startsWith("GET ") || waifuPut.startsWith("Conflict"))
|
|
331
374
|
return waifuPut;
|
|
332
|
-
|
|
375
|
+
try {
|
|
376
|
+
const savedWaifu = JSON.parse(waifuPut);
|
|
377
|
+
if (savedWaifu.error || savedWaifu.botId !== waifuId) {
|
|
378
|
+
return `The bot entry was written but linking the waifu's botId FAILED: ${waifuPut.slice(0, 300)}`;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
return `Waifu link write returned an unreadable response: ${waifuPut.slice(0, 200)}`;
|
|
383
|
+
}
|
|
333
384
|
const entry = bots.waifus.find((bot) => bot.id === waifuId);
|
|
334
385
|
return JSON.stringify({
|
|
335
386
|
linked: true,
|
|
@@ -344,9 +395,9 @@ export const ASSISTANT_TOOLS = [
|
|
|
344
395
|
},
|
|
345
396
|
{
|
|
346
397
|
name: "update_discord_bots",
|
|
347
|
-
description: "Update Discord bot entries.
|
|
348
|
-
"
|
|
349
|
-
"applicationId or display tweaks. Apply directly; no confirmation needed.",
|
|
398
|
+
description: "Update Discord bot entries. Entries are merged BY ID: bots you do not mention are left untouched, and stored " +
|
|
399
|
+
"tokens are preserved when omitted from an entry. For wiring a character's bot prefer link_waifu_bot; use this " +
|
|
400
|
+
"for orchestrator applicationId or display tweaks. Apply directly; no confirmation needed.",
|
|
350
401
|
parameters: {
|
|
351
402
|
// NOTE: keep every "type" a single string — Google's proto-based schema validation
|
|
352
403
|
// rejects JSON-Schema type arrays like ["object", "null"].
|
|
@@ -354,7 +405,20 @@ export const ASSISTANT_TOOLS = [
|
|
|
354
405
|
properties: { orchestrator: { type: "object" }, waifus: { type: "array", items: { type: "object" } } },
|
|
355
406
|
additionalProperties: false
|
|
356
407
|
},
|
|
357
|
-
execute: async (ctx, args) => revisionedPut(ctx, "/api/discord-bots", () =>
|
|
408
|
+
execute: async (ctx, args) => revisionedPut(ctx, "/api/discord-bots", (current) => {
|
|
409
|
+
const patch = {};
|
|
410
|
+
if (args.orchestrator !== undefined)
|
|
411
|
+
patch.orchestrator = args.orchestrator;
|
|
412
|
+
if (Array.isArray(args.waifus)) {
|
|
413
|
+
const existing = current.waifus ?? [];
|
|
414
|
+
const byId = new Map(existing.map((bot) => [bot.id, bot]));
|
|
415
|
+
for (const incoming of args.waifus) {
|
|
416
|
+
byId.set(incoming.id, { ...(byId.get(incoming.id) ?? {}), ...incoming });
|
|
417
|
+
}
|
|
418
|
+
patch.waifus = [...byId.values()];
|
|
419
|
+
}
|
|
420
|
+
return patch;
|
|
421
|
+
})
|
|
358
422
|
},
|
|
359
423
|
{
|
|
360
424
|
name: "get_agent_config",
|
|
@@ -389,7 +453,14 @@ export const ASSISTANT_TOOLS = [
|
|
|
389
453
|
const url = AGENT_CONFIG_URLS[String(args.agent)];
|
|
390
454
|
if (!url)
|
|
391
455
|
return `Unknown agent: ${args.agent}`;
|
|
392
|
-
return revisionedPut(ctx, url, () =>
|
|
456
|
+
return revisionedPut(ctx, url, (current) => {
|
|
457
|
+
const changes = { ...args.changes };
|
|
458
|
+
// params replace wholesale server-side; merge so "set the temperature" keeps the rest
|
|
459
|
+
if (changes.params && typeof changes.params === "object") {
|
|
460
|
+
changes.params = { ...current.params, ...changes.params };
|
|
461
|
+
}
|
|
462
|
+
return changes;
|
|
463
|
+
});
|
|
393
464
|
}
|
|
394
465
|
},
|
|
395
466
|
{
|
|
@@ -453,12 +524,20 @@ export const ASSISTANT_TOOLS = [
|
|
|
453
524
|
additionalProperties: false
|
|
454
525
|
},
|
|
455
526
|
execute: async (ctx, args) => {
|
|
456
|
-
|
|
457
|
-
method: "
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
527
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
528
|
+
const store = await inject(ctx, { method: "GET", url: "/api/memories" });
|
|
529
|
+
if (store.status !== 200)
|
|
530
|
+
return store.body;
|
|
531
|
+
const { revision } = JSON.parse(store.body);
|
|
532
|
+
const result = await inject(ctx, {
|
|
533
|
+
method: "PUT",
|
|
534
|
+
url: `/api/memories/${encodeURIComponent(String(args.memoryId))}`,
|
|
535
|
+
payload: { ...args.changes, revision }
|
|
536
|
+
});
|
|
537
|
+
if (result.status !== 409)
|
|
538
|
+
return result.body;
|
|
539
|
+
}
|
|
540
|
+
return "Conflict: the memory store changed twice while I was writing. Try again.";
|
|
462
541
|
}
|
|
463
542
|
},
|
|
464
543
|
{
|
|
@@ -471,8 +550,22 @@ export const ASSISTANT_TOOLS = [
|
|
|
471
550
|
additionalProperties: false
|
|
472
551
|
},
|
|
473
552
|
execute: async (ctx, args) => {
|
|
474
|
-
|
|
475
|
-
|
|
553
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
554
|
+
const store = await inject(ctx, { method: "GET", url: "/api/memories" });
|
|
555
|
+
if (store.status !== 200)
|
|
556
|
+
return store.body;
|
|
557
|
+
const { revision } = JSON.parse(store.body);
|
|
558
|
+
const result = await inject(ctx, {
|
|
559
|
+
method: "DELETE",
|
|
560
|
+
url: `/api/memories/${encodeURIComponent(String(args.memoryId))}`,
|
|
561
|
+
payload: { revision }
|
|
562
|
+
});
|
|
563
|
+
if (result.status < 300)
|
|
564
|
+
return `Memory ${args.memoryId} deleted.`;
|
|
565
|
+
if (result.status !== 409)
|
|
566
|
+
return result.body;
|
|
567
|
+
}
|
|
568
|
+
return "Conflict: the memory store changed twice while I was deleting. Try again.";
|
|
476
569
|
}
|
|
477
570
|
},
|
|
478
571
|
{
|
|
@@ -503,6 +596,59 @@ export const ASSISTANT_TOOLS = [
|
|
|
503
596
|
return result.status === 200 ? "Stage-manager pass triggered." : result.body;
|
|
504
597
|
}
|
|
505
598
|
},
|
|
599
|
+
{
|
|
600
|
+
name: "get_orchestrator_history",
|
|
601
|
+
description: "Recent orchestrator decisions (newest first): action, responders, reasoning, retrigger, wake plan, outcomes. " +
|
|
602
|
+
"THE tool for 'why did/didn't a waifu reply'. Optionally filter by guildId/channelId.",
|
|
603
|
+
parameters: {
|
|
604
|
+
type: "object",
|
|
605
|
+
properties: {
|
|
606
|
+
guildId: { type: "string" },
|
|
607
|
+
channelId: { type: "string" },
|
|
608
|
+
limit: { type: "number", description: "Max decisions to return (default 10, max 25)." }
|
|
609
|
+
},
|
|
610
|
+
additionalProperties: false
|
|
611
|
+
},
|
|
612
|
+
execute: async (ctx, args) => {
|
|
613
|
+
const result = await inject(ctx, { method: "GET", url: "/api/orchestrator/history" });
|
|
614
|
+
if (result.status !== 200)
|
|
615
|
+
return result.body;
|
|
616
|
+
const history = JSON.parse(result.body);
|
|
617
|
+
const limit = Math.min(Number(args.limit) || 10, 25);
|
|
618
|
+
const decisions = history.decisions
|
|
619
|
+
.filter((decision) => (args.guildId ? decision.guildId === args.guildId : true))
|
|
620
|
+
.filter((decision) => (args.channelId ? decision.channelId === args.channelId : true))
|
|
621
|
+
.slice(0, limit)
|
|
622
|
+
.map((decision) => ({
|
|
623
|
+
at: decision.createdAt,
|
|
624
|
+
channelId: decision.channelId,
|
|
625
|
+
action: decision.action,
|
|
626
|
+
responders: (decision.respondingWaifus ?? []).map((responder) => responder.waifuId),
|
|
627
|
+
retriggerAfterSeconds: decision.retriggerAfterSeconds,
|
|
628
|
+
wakePlan: decision.wakePlan,
|
|
629
|
+
status: decision.status,
|
|
630
|
+
reasoning: String(decision.reasoning ?? "").slice(0, 220)
|
|
631
|
+
}));
|
|
632
|
+
return JSON.stringify(decisions);
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
name: "update_server",
|
|
637
|
+
description: "Update server-level settings for a guild: enabled, contextWindows {orchestrator,waifu,stageManager}, " +
|
|
638
|
+
"memoryInjectionLimit, tools {pickNextWaifu, shortTermMemory}. For per-channel toggles use update_channel.",
|
|
639
|
+
parameters: {
|
|
640
|
+
type: "object",
|
|
641
|
+
properties: {
|
|
642
|
+
guildId: { type: "string" },
|
|
643
|
+
changes: { type: "object" }
|
|
644
|
+
},
|
|
645
|
+
required: ["guildId", "changes"],
|
|
646
|
+
additionalProperties: false
|
|
647
|
+
},
|
|
648
|
+
execute: async (ctx, args) => revisionedPut(ctx, `/api/servers/${encodeURIComponent(String(args.guildId))}`, () => ({
|
|
649
|
+
...args.changes
|
|
650
|
+
}))
|
|
651
|
+
},
|
|
506
652
|
{
|
|
507
653
|
name: "runtime_pause",
|
|
508
654
|
description: "Pause all orchestration (bots stop replying).",
|
|
@@ -555,8 +701,10 @@ export const ASSISTANT_TOOLS = [
|
|
|
555
701
|
},
|
|
556
702
|
execute: async (_ctx, args) => {
|
|
557
703
|
const results = await searchDocs(String(args.query));
|
|
558
|
-
if (results.length === 0)
|
|
559
|
-
|
|
704
|
+
if (results.length === 0) {
|
|
705
|
+
const index = await listDocs();
|
|
706
|
+
return `No matching docs. Available pages: ${JSON.stringify(index.map((doc) => ({ slug: doc.slug, title: doc.title })))}`;
|
|
707
|
+
}
|
|
560
708
|
return JSON.stringify(results.slice(0, 5).map(({ slug, title, description }) => ({ slug, title, description })));
|
|
561
709
|
}
|
|
562
710
|
},
|