@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.
@@ -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 evict the oldest past the cap.
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 oldest = this.conversations.keys().next().value;
17
- this.conversations.delete(oldest);
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
- conversation.chat = messages;
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
- this.appendStored(id, { role: "event", event, at: new Date().toISOString() });
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":"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"}
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 set one on the assistant or the orchestrator." };
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
- const target = await resolveAssistantTarget(app, dataRoot);
84
- if (!target.ok)
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
- store.setBusy(conversationId, true);
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":"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"}
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: "All models in the gateway registry with provider and key capabilities.",
137
- parameters: NO_ARGS,
138
- execute: async (ctx) => {
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
- return result.status === 200 ? result.body : `Model registry unavailable (${result.status}).`;
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 result = await inject(ctx, { method: "DELETE", url: `/api/waifus/${encodeURIComponent(String(args.id))}` });
224
- return result.status < 300 ? `Waifu ${args.id} deleted.` : result.body;
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
- const bots = JSON.parse(botsPut);
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. The server MERGES per entry and preserves stored tokens when omitted nothing is " +
348
- "wiped by leaving fields out. For wiring a character's bot prefer link_waifu_bot; use this for orchestrator " +
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", () => args)
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, () => ({ ...args.changes }));
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
- const result = await inject(ctx, {
457
- method: "PUT",
458
- url: `/api/memories/${encodeURIComponent(String(args.memoryId))}`,
459
- payload: args.changes
460
- });
461
- return result.body;
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
- const result = await inject(ctx, { method: "DELETE", url: `/api/memories/${encodeURIComponent(String(args.memoryId))}` });
475
- return result.status < 300 ? `Memory ${args.memoryId} deleted.` : result.body;
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
- return "No matching docs.";
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
  },