@animalabs/agent-framework 0.30.0 → 0.32.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/README.md +71 -8
- package/dist/src/agent.d.ts +29 -9
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +82 -17
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts.map +1 -1
- package/dist/src/api/mcp-server-core.js +10 -3
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/code-execution/isolate/render.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/render.js +7 -6
- package/dist/src/code-execution/isolate/render.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +5 -4
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/framework.d.ts +138 -102
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +1133 -752
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +24 -17
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +198 -144
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/recipe-reconcile.d.ts.map +1 -1
- package/dist/src/gate/recipe-reconcile.js +6 -5
- package/dist/src/gate/recipe-reconcile.js.map +1 -1
- package/dist/src/host-presentation.d.ts.map +1 -1
- package/dist/src/host-presentation.js +5 -7
- package/dist/src/host-presentation.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/inference-bounds.d.ts +37 -0
- package/dist/src/inference-bounds.d.ts.map +1 -0
- package/dist/src/inference-bounds.js +84 -0
- package/dist/src/inference-bounds.js.map +1 -0
- package/dist/src/line-reader.d.ts +7 -1
- package/dist/src/line-reader.d.ts.map +1 -1
- package/dist/src/line-reader.js +17 -6
- package/dist/src/line-reader.js.map +1 -1
- package/dist/src/mcpl/errors.d.ts +1 -0
- package/dist/src/mcpl/errors.d.ts.map +1 -1
- package/dist/src/mcpl/errors.js +3 -0
- package/dist/src/mcpl/errors.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +2 -2
- package/dist/src/mcpl/inbound-source.js +2 -2
- package/dist/src/mcpl/inference-router.d.ts +11 -1
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js +65 -9
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts +10 -7
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +85 -74
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +1 -1
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +45 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +2 -7
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +1 -2
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +3 -3
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +14 -0
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +26 -22
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts +1 -0
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +16 -11
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +44 -21
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +283 -64
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +17 -1
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/module-registry.d.ts +32 -4
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +189 -25
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
- package/dist/src/modules/discord/discord-js-client.js +24 -16
- package/dist/src/modules/discord/discord-js-client.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +5 -4
- package/dist/src/modules/sender-name.d.ts.map +1 -1
- package/dist/src/modules/sender-name.js +7 -5
- package/dist/src/modules/sender-name.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +16 -8
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +33 -12
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +6 -3
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +61 -15
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +9 -1
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +50 -10
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +11 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +19 -8
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +8 -0
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/modules/workspace/watcher.d.ts +7 -0
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js +7 -5
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.js +1 -9
- package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
- package/dist/src/supplied-text.d.ts +2 -0
- package/dist/src/supplied-text.d.ts.map +1 -1
- package/dist/src/supplied-text.js +16 -7
- package/dist/src/supplied-text.js.map +1 -1
- package/dist/src/tool-result-guard.js +1 -1
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +2 -20
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +175 -70
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +14 -2
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/types/agent.d.ts +6 -12
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +65 -24
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +42 -3
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +4 -3
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agent.ts +90 -25
- package/src/api/mcp-server-core.ts +10 -3
- package/src/code-execution/isolate/render.ts +7 -6
- package/src/code-execution/isolate/scripts.ts +5 -4
- package/src/framework.ts +1163 -770
- package/src/gate/event-gate.ts +220 -145
- package/src/gate/recipe-reconcile.ts +7 -5
- package/src/host-presentation.ts +4 -7
- package/src/index.ts +1 -0
- package/src/inference-bounds.ts +111 -0
- package/src/line-reader.ts +22 -5
- package/src/mcpl/errors.ts +4 -0
- package/src/mcpl/inbound-source.ts +2 -2
- package/src/mcpl/inference-router.ts +67 -10
- package/src/mcpl/modern-connection.ts +83 -68
- package/src/mcpl/protocol-family.ts +37 -4
- package/src/mcpl/push-coalescer.ts +2 -7
- package/src/mcpl/references.ts +3 -4
- package/src/mcpl/server-connection.ts +30 -21
- package/src/mcpl/tool-signature.ts +18 -11
- package/src/mcpl/transport.ts +261 -60
- package/src/mcpl/types.ts +18 -1
- package/src/module-registry.ts +178 -25
- package/src/modules/discord/discord-js-client.ts +33 -29
- package/src/modules/sender-name.ts +8 -5
- package/src/modules/workspace/disk-agreement.ts +39 -15
- package/src/modules/workspace/index.ts +65 -14
- package/src/modules/workspace/observe.ts +51 -10
- package/src/modules/workspace/reconcile.ts +22 -8
- package/src/modules/workspace/types.ts +5 -0
- package/src/modules/workspace/watcher.ts +8 -6
- package/src/recovery/discord-awareness-outbox.ts +1 -11
- package/src/supplied-text.ts +17 -6
- package/src/tool-result-guard.ts +1 -1
- package/src/tune-out/coordinator.ts +187 -85
- package/src/tune-out/tools.ts +14 -2
- package/src/types/agent.ts +6 -12
- package/src/types/framework.ts +66 -24
- package/src/types/module.ts +42 -3
- package/src/types/trace.ts +4 -3
- package/dist/src/reasoning-loss-notice.d.ts +0 -23
- package/dist/src/reasoning-loss-notice.d.ts.map +0 -1
- package/dist/src/reasoning-loss-notice.js +0 -52
- package/dist/src/reasoning-loss-notice.js.map +0 -1
- package/dist/src/reasoning-loss.d.ts +0 -24
- package/dist/src/reasoning-loss.d.ts.map +0 -1
- package/dist/src/reasoning-loss.js +0 -102
- package/dist/src/reasoning-loss.js.map +0 -1
- package/src/reasoning-loss-notice.ts +0 -64
- package/src/reasoning-loss.ts +0 -120
package/src/module-registry.ts
CHANGED
|
@@ -19,12 +19,14 @@ import type {
|
|
|
19
19
|
SpeechContext,
|
|
20
20
|
SpeechHandlerOptions,
|
|
21
21
|
ProcessEvent,
|
|
22
|
+
ToolResultEvent,
|
|
22
23
|
TraceEventListener,
|
|
23
24
|
ModuleMessage,
|
|
24
25
|
ModuleContextInjection,
|
|
25
26
|
MessageQueryResult,
|
|
26
27
|
} from './types/index.js';
|
|
27
28
|
import { heldWriteId } from './held-writes.js';
|
|
29
|
+
import { boundedErrorMessage } from './error-message.js';
|
|
28
30
|
import type { Agent } from './agent.js';
|
|
29
31
|
import { DEFAULT_SHUTDOWN_TIMEOUT_MS, validateShutdownTimeout, waitForShutdown } from './shutdown.js';
|
|
30
32
|
import { logValue } from './mcpl/log-value.js';
|
|
@@ -38,6 +40,19 @@ export interface ModuleQueue {
|
|
|
38
40
|
|
|
39
41
|
const MODULE_STATE_PREFIX = 'modules/';
|
|
40
42
|
|
|
43
|
+
export function droppedToolResultLine(event: ToolResultEvent, when: string): string {
|
|
44
|
+
const outcome = event.result.success && !event.result.isError
|
|
45
|
+
? 'succeeded'
|
|
46
|
+
: `failed${event.result.error ? `: ${boundedErrorMessage(event.result.error)}` : ''}`;
|
|
47
|
+
return `[framework] ${event.agentName}: result of tool call ${event.callId} (${event.toolName ? `${event.toolName}, ` : ''}${event.moduleName}) arrived ${when} — dropped; the tool ${outcome}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function retiredPushLine(event: ProcessEvent, moduleName: string): string {
|
|
51
|
+
return event.type === 'tool-result'
|
|
52
|
+
? droppedToolResultLine(event, `after module ${moduleName} stopped`)
|
|
53
|
+
: `[framework] a '${event.type}' event from module ${moduleName} arrived after that module stopped — dropped`;
|
|
54
|
+
}
|
|
55
|
+
|
|
41
56
|
/**
|
|
42
57
|
* True when a registerState failure means "this state id already exists" —
|
|
43
58
|
* the one failure that is safe to swallow on an idempotent re-registration.
|
|
@@ -104,6 +119,7 @@ export class ModuleRegistry {
|
|
|
104
119
|
|
|
105
120
|
private modules: Map<string, Module> = new Map();
|
|
106
121
|
private stopping = new Map<Module, Promise<void>>();
|
|
122
|
+
private starting = new Map<Module, Promise<void>>();
|
|
107
123
|
/** Module and namespace pairs already told that a system injection held a block that isn't text. */
|
|
108
124
|
private notShownWarned = new Set<string>();
|
|
109
125
|
private moduleContexts: Map<string, ModuleContextImpl> = new Map();
|
|
@@ -120,6 +136,11 @@ export class ModuleRegistry {
|
|
|
120
136
|
private pushEventFn: (event: ProcessEvent, moduleName?: string) => void;
|
|
121
137
|
private onTraceFn: (listener: TraceEventListener) => () => void;
|
|
122
138
|
private callToolFn: (call: ToolCall) => Promise<ToolResult>;
|
|
139
|
+
/**
|
|
140
|
+
* The constructor's `dropRetiredPush` option.
|
|
141
|
+
* @internal For the framework's drop lines; not a host API.
|
|
142
|
+
*/
|
|
143
|
+
readonly dropRetiredPushFn?: (event: ProcessEvent, moduleName: string) => void;
|
|
123
144
|
/** Framework ops-alert channel, surfaced to modules via
|
|
124
145
|
* ModuleContext.notifyOps. Readonly-public so ModuleContextImpl can
|
|
125
146
|
* delegate without another positional constructor parameter. */
|
|
@@ -142,6 +163,10 @@ export class ModuleRegistry {
|
|
|
142
163
|
queryMessages: (filter: MessageQuery, opts?: { forAgent?: string }) => MessageQueryResult;
|
|
143
164
|
/** Called with the pushing module's name. */
|
|
144
165
|
pushEvent: (event: ProcessEvent, moduleName?: string) => void;
|
|
166
|
+
/** Logs a push a module made after its stop() completed, which is
|
|
167
|
+
* dropped, not queued. Without it, the registry logs a line of its own.
|
|
168
|
+
* @internal For the framework's drop lines; not a host API. */
|
|
169
|
+
dropRetiredPush?: (event: ProcessEvent, moduleName: string) => void;
|
|
145
170
|
onTrace: (listener: TraceEventListener) => () => void;
|
|
146
171
|
callTool: (call: ToolCall) => Promise<ToolResult>;
|
|
147
172
|
notifyOps?: (kind: string, agentName: string, message: string, data?: Record<string, unknown>) => void;
|
|
@@ -159,6 +184,7 @@ export class ModuleRegistry {
|
|
|
159
184
|
this.onTraceFn = options.onTrace;
|
|
160
185
|
this.callToolFn = options.callTool;
|
|
161
186
|
this.notifyOpsFn = options.notifyOps;
|
|
187
|
+
this.dropRetiredPushFn = options.dropRetiredPush;
|
|
162
188
|
}
|
|
163
189
|
|
|
164
190
|
/**
|
|
@@ -200,7 +226,21 @@ export class ModuleRegistry {
|
|
|
200
226
|
this.moduleContexts.set(module.name, context);
|
|
201
227
|
|
|
202
228
|
// Start the module
|
|
203
|
-
|
|
229
|
+
let release!: (start: Promise<void>) => void;
|
|
230
|
+
const settled = new Promise<void>((resolve) => { release = resolve; }).then(() => undefined, () => undefined);
|
|
231
|
+
this.starting.set(module, settled);
|
|
232
|
+
let started: Promise<void>;
|
|
233
|
+
try {
|
|
234
|
+
started = Promise.resolve(module.start(context));
|
|
235
|
+
} catch (error) {
|
|
236
|
+
started = Promise.reject(error);
|
|
237
|
+
}
|
|
238
|
+
release(started);
|
|
239
|
+
try {
|
|
240
|
+
await started;
|
|
241
|
+
} finally {
|
|
242
|
+
if (this.starting.get(module) === settled) this.starting.delete(module);
|
|
243
|
+
}
|
|
204
244
|
}
|
|
205
245
|
|
|
206
246
|
/**
|
|
@@ -229,6 +269,17 @@ export class ModuleRegistry {
|
|
|
229
269
|
return Array.from(this.modules.values());
|
|
230
270
|
}
|
|
231
271
|
|
|
272
|
+
/**
|
|
273
|
+
* The current registration of the module of this name: a value that
|
|
274
|
+
* changes each time a module of that name is added, the same module object
|
|
275
|
+
* included, and that stays the same until its stop() completes;
|
|
276
|
+
* `undefined` while none is registered.
|
|
277
|
+
* @internal For the framework's registration checks; not a host API.
|
|
278
|
+
*/
|
|
279
|
+
registrationOf(moduleName: string): object | undefined {
|
|
280
|
+
return this.moduleContexts.get(moduleName);
|
|
281
|
+
}
|
|
282
|
+
|
|
232
283
|
/**
|
|
233
284
|
* Set state for a module by name.
|
|
234
285
|
* Used by the framework to apply stateUpdate from EventResponse after its
|
|
@@ -385,7 +436,9 @@ export class ModuleRegistry {
|
|
|
385
436
|
|
|
386
437
|
/**
|
|
387
438
|
* Stop all modules. A caller may bound its wait without cancelling cleanup.
|
|
388
|
-
* The default waits for each module's own shutdown contract.
|
|
439
|
+
* The default waits for each module's own shutdown contract. A module whose
|
|
440
|
+
* start() is still running is stopped once its start() has settled, within
|
|
441
|
+
* the same wait.
|
|
389
442
|
*/
|
|
390
443
|
async stopAll(timeoutMs = DEFAULT_SHUTDOWN_TIMEOUT_MS): Promise<void> {
|
|
391
444
|
validateShutdownTimeout(timeoutMs);
|
|
@@ -397,11 +450,16 @@ export class ModuleRegistry {
|
|
|
397
450
|
private stopModule(module: Module): Promise<void> {
|
|
398
451
|
const existing = this.stopping.get(module);
|
|
399
452
|
if (existing) return existing;
|
|
400
|
-
const attempt = Promise.resolve().then(() => module.stop()).then(() => {
|
|
453
|
+
const attempt = (this.starting.get(module) ?? Promise.resolve()).then(() => module.stop()).then(() => {
|
|
401
454
|
if (this.modules.get(module.name) === module) {
|
|
455
|
+
const context = this.moduleContexts.get(module.name);
|
|
402
456
|
this.modules.delete(module.name);
|
|
403
457
|
this.moduleContexts.delete(module.name);
|
|
404
458
|
this.speechHandlers = this.speechHandlers.filter(handler => handler.moduleName !== module.name);
|
|
459
|
+
this.batchHooksRunning.delete(module.name);
|
|
460
|
+
this.stopping.delete(module);
|
|
461
|
+
context?.retire();
|
|
462
|
+
return;
|
|
405
463
|
}
|
|
406
464
|
this.stopping.delete(module);
|
|
407
465
|
}, error => {
|
|
@@ -453,7 +511,12 @@ export class ModuleRegistry {
|
|
|
453
511
|
* something that isn't an array of objects, or with a `system` entry whose
|
|
454
512
|
* content isn't an array of objects, every block of which the agent's
|
|
455
513
|
* buildSystemPrompt reads, and one that answers after its timeout adds
|
|
456
|
-
* nothing.
|
|
514
|
+
* nothing. Once every answer is in, the answers are merged, and an answer
|
|
515
|
+
* from a module whose registration has ended by then (its stop()
|
|
516
|
+
* completed, whether or not a module of its name has been added since) is
|
|
517
|
+
* left out, with a line if it held injections; a stop that completes after
|
|
518
|
+
* the merge doesn't change what this returned. An array returned without a
|
|
519
|
+
* promise is taken as the module's
|
|
457
520
|
* answer. An entry's other fields are the module's to get right. Each
|
|
458
521
|
* module's budget starts once its call returns, after its own synchronous
|
|
459
522
|
* work and every earlier module's; a later module's synchronous work still
|
|
@@ -473,6 +536,7 @@ export class ModuleRegistry {
|
|
|
473
536
|
// that answers after its timeout adds nothing (it's skipped, fail-open).
|
|
474
537
|
const results: ModuleContextInjection[][] = [];
|
|
475
538
|
const promises: Promise<void>[] = [];
|
|
539
|
+
const asked: { slot: number; moduleName: string; registration: object | undefined }[] = [];
|
|
476
540
|
|
|
477
541
|
for (const module of this.modules.values()) {
|
|
478
542
|
const gather = module.gatherContext;
|
|
@@ -491,6 +555,7 @@ export class ModuleRegistry {
|
|
|
491
555
|
let timedOut = false;
|
|
492
556
|
const slot = results.length;
|
|
493
557
|
results.push([]);
|
|
558
|
+
asked.push({ slot, moduleName: module.name, registration: this.moduleContexts.get(module.name) });
|
|
494
559
|
// Called here, before its timer is set, so its budget starts after its
|
|
495
560
|
// synchronous work; a call that throws is skipped as a rejection is.
|
|
496
561
|
let call: Promise<ModuleContextInjection[]>;
|
|
@@ -516,7 +581,6 @@ export class ModuleRegistry {
|
|
|
516
581
|
if (unreadable) {
|
|
517
582
|
throw new TypeError(`gatherContext answered with a system injection in namespace ${logValue(unreadable.namespace)} whose content isn't an array of objects`);
|
|
518
583
|
}
|
|
519
|
-
this.noteBlocksNotShown(module.name, r);
|
|
520
584
|
results[slot] = [...r];
|
|
521
585
|
}),
|
|
522
586
|
new Promise<void>((_, rej) => {
|
|
@@ -536,6 +600,19 @@ export class ModuleRegistry {
|
|
|
536
600
|
}
|
|
537
601
|
|
|
538
602
|
await Promise.all(promises);
|
|
603
|
+
for (const { slot, moduleName, registration } of asked) {
|
|
604
|
+
if (this.moduleContexts.get(moduleName) === registration) {
|
|
605
|
+
this.noteBlocksNotShown(moduleName, results[slot]);
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
if (results[slot].length > 0) {
|
|
609
|
+
console.error(
|
|
610
|
+
`[framework] module ${moduleName} stopped before its gatherContext answer for ${agentName} was used, ` +
|
|
611
|
+
`so its ${results[slot].length} injection(s) were left out`,
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
results[slot] = [];
|
|
615
|
+
}
|
|
539
616
|
return results.flat();
|
|
540
617
|
}
|
|
541
618
|
|
|
@@ -565,7 +642,8 @@ export class ModuleRegistry {
|
|
|
565
642
|
* Tell modules an agent's tool batch completed (see
|
|
566
643
|
* Module.onToolBatchComplete). Hooks run in parallel, each awaited up to
|
|
567
644
|
* `timeoutMs`; a hook that throws or times out is logged and skipped, so a
|
|
568
|
-
* wedged module can't hold the round.
|
|
645
|
+
* wedged module can't hold the round. A module whose `stop()` completes
|
|
646
|
+
* before its hook is called isn't called. Returns the modules that didn't
|
|
569
647
|
* finish, for the caller's trace.
|
|
570
648
|
*/
|
|
571
649
|
async notifyToolBatchComplete(
|
|
@@ -583,10 +661,14 @@ export class ModuleRegistry {
|
|
|
583
661
|
failures.push({ module: module.name, error: 'still running from an earlier batch' });
|
|
584
662
|
continue;
|
|
585
663
|
}
|
|
664
|
+
const registration = this.moduleContexts.get(module.name);
|
|
586
665
|
// Called from a promise, so a hook that throws synchronously, before
|
|
587
666
|
// returning its promise, is caught below like any other failure
|
|
588
667
|
// instead of escaping the round (the result would never be provided).
|
|
589
|
-
const call = Promise.resolve().then(() =>
|
|
668
|
+
const call = Promise.resolve().then(() => {
|
|
669
|
+
if (this.moduleContexts.get(module.name) !== registration) return;
|
|
670
|
+
return module.onToolBatchComplete!(agentName);
|
|
671
|
+
});
|
|
590
672
|
this.batchHooksRunning.set(module.name, call);
|
|
591
673
|
const settled = (): void => {
|
|
592
674
|
if (this.batchHooksRunning.get(module.name) === call) this.batchHooksRunning.delete(module.name);
|
|
@@ -623,10 +705,11 @@ export class ModuleRegistry {
|
|
|
623
705
|
// Find handlers that match this agent
|
|
624
706
|
const handlers = this.speechHandlers.filter(
|
|
625
707
|
(h) => h.agents === '*' || h.agents.includes(agentName)
|
|
626
|
-
);
|
|
708
|
+
).map((handler) => ({ handler, registration: this.moduleContexts.get(handler.moduleName) }));
|
|
627
709
|
|
|
628
710
|
// Call each handler
|
|
629
|
-
for (const handler of handlers) {
|
|
711
|
+
for (const { handler, registration } of handlers) {
|
|
712
|
+
if (this.moduleContexts.get(handler.moduleName) !== registration) continue;
|
|
630
713
|
const module = this.modules.get(handler.moduleName);
|
|
631
714
|
if (module?.onAgentSpeech) {
|
|
632
715
|
try {
|
|
@@ -669,6 +752,10 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
669
752
|
// External ID mapping stored in module state
|
|
670
753
|
private externalIdMap: Map<string, MessageId> = new Map();
|
|
671
754
|
|
|
755
|
+
private retired = false;
|
|
756
|
+
private readonly saidRetired = new Set<string>();
|
|
757
|
+
private readonly traceSubscriptions = new Set<() => void>();
|
|
758
|
+
|
|
672
759
|
constructor(
|
|
673
760
|
moduleName: string,
|
|
674
761
|
store: JsStore,
|
|
@@ -690,7 +777,12 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
690
777
|
this.store = store;
|
|
691
778
|
this.stateId = stateId;
|
|
692
779
|
// The deprecated handle: a push names this module, as pushEvent() does.
|
|
693
|
-
this.queue = {
|
|
780
|
+
this.queue = {
|
|
781
|
+
push: (event) => {
|
|
782
|
+
if (this.retired) this.dropPush(event);
|
|
783
|
+
else queue.push(event, moduleName);
|
|
784
|
+
},
|
|
785
|
+
};
|
|
694
786
|
this.registry = registry;
|
|
695
787
|
this.isRestart = isRestart;
|
|
696
788
|
this.getAgentsFn = getAgents;
|
|
@@ -710,12 +802,38 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
710
802
|
}
|
|
711
803
|
}
|
|
712
804
|
|
|
805
|
+
retire(): void {
|
|
806
|
+
this.retired = true;
|
|
807
|
+
const subscriptions = [...this.traceSubscriptions];
|
|
808
|
+
this.traceSubscriptions.clear();
|
|
809
|
+
for (const unsubscribe of subscriptions) {
|
|
810
|
+
try {
|
|
811
|
+
unsubscribe();
|
|
812
|
+
} catch (error) {
|
|
813
|
+
console.error(`[framework] module ${JSON.stringify(this.moduleName)} has stopped, and ending one of its trace subscriptions threw: ${boundedErrorMessage(error)}`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
private refused(method: string): boolean {
|
|
819
|
+
if (!this.retired) return false;
|
|
820
|
+
if (!this.saidRetired.has(method)) {
|
|
821
|
+
this.saidRetired.add(method);
|
|
822
|
+
console.error(
|
|
823
|
+
`[framework] module ${JSON.stringify(this.moduleName)} has stopped, so its ${method}() call had no effect; ` +
|
|
824
|
+
`later ${method}() calls from it aren't logged`,
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
|
|
713
830
|
getState<T>(): T | null {
|
|
714
831
|
const state = this.store.getStateJson(this.stateId);
|
|
715
832
|
return state as T | null;
|
|
716
833
|
}
|
|
717
834
|
|
|
718
835
|
setState<T>(state: T): void {
|
|
836
|
+
if (this.refused('setState')) return;
|
|
719
837
|
// Merge external ID map into state
|
|
720
838
|
const fullState = {
|
|
721
839
|
...(state as object),
|
|
@@ -758,6 +876,7 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
758
876
|
name: string,
|
|
759
877
|
opts?: { deltaSnapshotEvery?: number; fullSnapshotEvery?: number }
|
|
760
878
|
): void {
|
|
879
|
+
if (this.refused('registerLogState')) return;
|
|
761
880
|
ensureStateRegistered(this.store, {
|
|
762
881
|
id: this.logStateId(name),
|
|
763
882
|
strategy: 'append_log',
|
|
@@ -768,10 +887,12 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
768
887
|
}
|
|
769
888
|
|
|
770
889
|
appendToLog<T>(name: string, item: T): void {
|
|
890
|
+
if (this.refused('appendToLog')) return;
|
|
771
891
|
this.store.appendToStateJson(this.registeredLogStateId(name), item);
|
|
772
892
|
}
|
|
773
893
|
|
|
774
894
|
editLogItem<T>(name: string, index: number, item: T): void {
|
|
895
|
+
if (this.refused('editLogItem')) return;
|
|
775
896
|
this.store.editStateItem(this.registeredLogStateId(name), index, Buffer.from(JSON.stringify(item)));
|
|
776
897
|
}
|
|
777
898
|
|
|
@@ -785,18 +906,34 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
785
906
|
}
|
|
786
907
|
|
|
787
908
|
getModule<T extends Module>(name: string): T | null {
|
|
909
|
+
if (this.refused('getModule')) return null;
|
|
788
910
|
return this.registry.getModule<T>(name);
|
|
789
911
|
}
|
|
790
912
|
|
|
791
913
|
onTrace(listener: TraceEventListener): () => void {
|
|
792
|
-
|
|
914
|
+
if (this.refused('onTrace')) return () => {};
|
|
915
|
+
const unsubscribe = this.onTraceFn((event) => {
|
|
916
|
+
if (!this.retired) listener(event);
|
|
917
|
+
});
|
|
918
|
+
this.traceSubscriptions.add(unsubscribe);
|
|
919
|
+
return () => {
|
|
920
|
+
if (this.traceSubscriptions.delete(unsubscribe)) unsubscribe();
|
|
921
|
+
};
|
|
793
922
|
}
|
|
794
923
|
|
|
795
924
|
callTool(call: ToolCall): Promise<ToolResult> {
|
|
925
|
+
if (this.refused('callTool')) {
|
|
926
|
+
return Promise.resolve({
|
|
927
|
+
success: false,
|
|
928
|
+
isError: true,
|
|
929
|
+
error: `module ${JSON.stringify(this.moduleName)} has stopped, so its call of ${call.name} wasn't made`,
|
|
930
|
+
});
|
|
931
|
+
}
|
|
796
932
|
return this.callToolFn(call);
|
|
797
933
|
}
|
|
798
934
|
|
|
799
935
|
notifyOps(kind: string, agentName: string, message: string, data?: Record<string, unknown>): void {
|
|
936
|
+
if (this.refused('notifyOps')) return;
|
|
800
937
|
this.registry.notifyOpsFn?.(kind, agentName, message, data);
|
|
801
938
|
}
|
|
802
939
|
|
|
@@ -806,6 +943,7 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
806
943
|
metadata?: MessageMetadata & { external?: ExternalIdRef },
|
|
807
944
|
opts?: { forAgent?: string },
|
|
808
945
|
): MessageId {
|
|
946
|
+
if (this.refused('addMessage')) return '';
|
|
809
947
|
// The external id map is read in the primary's store
|
|
810
948
|
// (findMessageByExternalId), so a mapping into another window would
|
|
811
949
|
// answer wrong.
|
|
@@ -830,10 +968,12 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
830
968
|
}
|
|
831
969
|
|
|
832
970
|
editMessage(id: MessageId, content: ContentBlock[]): void {
|
|
971
|
+
if (this.refused('editMessage')) return;
|
|
833
972
|
this.editMessageFn(id, content);
|
|
834
973
|
}
|
|
835
974
|
|
|
836
975
|
removeMessage(id: MessageId): void {
|
|
976
|
+
if (this.refused('removeMessage')) return;
|
|
837
977
|
this.removeMessageFn(id);
|
|
838
978
|
// Clean up external ID mapping
|
|
839
979
|
for (const [key, msgId] of this.externalIdMap) {
|
|
@@ -850,26 +990,31 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
850
990
|
const entry = this.externalIdMap.get(key);
|
|
851
991
|
if (entry === undefined) return null;
|
|
852
992
|
// A stored id answers while the store holds its row, and once the row is
|
|
853
|
-
// gone the entry goes, as removeMessage drops it
|
|
854
|
-
//
|
|
855
|
-
//
|
|
856
|
-
//
|
|
857
|
-
//
|
|
858
|
-
//
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
//
|
|
993
|
+
// gone the entry goes, as removeMessage drops it, except from a context
|
|
994
|
+
// whose module has stopped, which keeps its mapping as it was. The '' a held
|
|
995
|
+
// write keeps here (addMessage) names no row, so the message is looked up in
|
|
996
|
+
// the store by its external id, held rows included, and the first row found
|
|
997
|
+
// is taken, as context-manager's findByExternalId takes it: a held row
|
|
998
|
+
// answers by its handle and leaves the entry as it is, and a stored row's id
|
|
999
|
+
// becomes the entry. With nothing found, '' stays, as earlier releases kept
|
|
1000
|
+
// it: in a module's start(), the writes the last process held don't show
|
|
1001
|
+
// yet, since create() restores them after the modules start, and a write
|
|
1002
|
+
// that shows later finds its entry.
|
|
862
1003
|
if (entry !== '') {
|
|
863
1004
|
if (this.getMessageFn(entry)) return entry;
|
|
864
|
-
this.
|
|
865
|
-
|
|
1005
|
+
if (!this.retired) {
|
|
1006
|
+
this.externalIdMap.delete(key);
|
|
1007
|
+
this.persistExternalIdMap();
|
|
1008
|
+
}
|
|
866
1009
|
return null;
|
|
867
1010
|
}
|
|
868
1011
|
const row = this.queryMessagesFn({ source, externalIds: [externalId] }).messages[0];
|
|
869
1012
|
if (row?.held) return row.id;
|
|
870
1013
|
if (!row) return null;
|
|
871
|
-
this.
|
|
872
|
-
|
|
1014
|
+
if (!this.retired) {
|
|
1015
|
+
this.externalIdMap.set(key, row.id);
|
|
1016
|
+
this.persistExternalIdMap();
|
|
1017
|
+
}
|
|
873
1018
|
return row.id;
|
|
874
1019
|
}
|
|
875
1020
|
|
|
@@ -893,15 +1038,23 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
893
1038
|
agents: '*' | string[],
|
|
894
1039
|
options?: SpeechHandlerOptions
|
|
895
1040
|
): void {
|
|
1041
|
+
if (this.refused('registerSpeechHandler')) return;
|
|
896
1042
|
this.registry.registerSpeechHandler(this.moduleName, agents, options);
|
|
897
1043
|
}
|
|
898
1044
|
|
|
899
1045
|
unregisterSpeechHandler(): void {
|
|
1046
|
+
if (this.refused('unregisterSpeechHandler')) return;
|
|
900
1047
|
this.registry.unregisterSpeechHandler(this.moduleName);
|
|
901
1048
|
}
|
|
902
1049
|
|
|
903
1050
|
pushEvent(event: ProcessEvent): void {
|
|
904
|
-
this.
|
|
1051
|
+
if (this.retired) this.dropPush(event);
|
|
1052
|
+
else this.pushEventFn(event, this.moduleName);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
private dropPush(event: ProcessEvent): void {
|
|
1056
|
+
if (this.registry.dropRetiredPushFn) this.registry.dropRetiredPushFn(event, this.moduleName);
|
|
1057
|
+
else console.error(retiredPushLine(event, this.moduleName));
|
|
905
1058
|
}
|
|
906
1059
|
|
|
907
1060
|
private persistExternalIdMap(): void {
|
|
@@ -2,24 +2,18 @@
|
|
|
2
2
|
* Discord.js client adapter implementing DiscordClientInterface
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import type {
|
|
7
7
|
Client,
|
|
8
8
|
DiscordAPIError,
|
|
9
|
-
DiscordjsError,
|
|
10
|
-
DiscordjsErrorCodes,
|
|
11
|
-
DiscordjsTypeError,
|
|
12
|
-
GatewayIntentBits,
|
|
13
|
-
Partials,
|
|
14
|
-
RESTJSONErrorCodes,
|
|
15
9
|
TextChannel,
|
|
16
10
|
DMChannel,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
Channel,
|
|
12
|
+
Message,
|
|
13
|
+
Attachment,
|
|
14
|
+
User,
|
|
15
|
+
Guild,
|
|
16
|
+
GuildBasedChannel,
|
|
23
17
|
} from 'discord.js';
|
|
24
18
|
import type {
|
|
25
19
|
DiscordClientInterface,
|
|
@@ -31,6 +25,12 @@ import type {
|
|
|
31
25
|
} from './types.js';
|
|
32
26
|
import { DiscordRefusal } from './refusal.js';
|
|
33
27
|
|
|
28
|
+
const requireHere = createRequire(import.meta.url);
|
|
29
|
+
|
|
30
|
+
function discordJs(): typeof import('discord.js') {
|
|
31
|
+
return requireHere('discord.js') as typeof import('discord.js');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
34
|
/** The ids a tool's call named, for the words of Discord's refusal. */
|
|
35
35
|
interface Named {
|
|
36
36
|
channelId?: string;
|
|
@@ -55,6 +55,7 @@ function target(named: Named): string {
|
|
|
55
55
|
* `overrides` words a code as one tool means it.
|
|
56
56
|
*/
|
|
57
57
|
function refusalReason(error: DiscordAPIError, named: Named, overrides: Partial<Record<number, string>> = {}): string {
|
|
58
|
+
const { RESTJSONErrorCodes } = discordJs();
|
|
58
59
|
const override = typeof error.code === 'number' ? overrides[error.code] : undefined;
|
|
59
60
|
if (override !== undefined) return override;
|
|
60
61
|
switch (error.code) {
|
|
@@ -103,6 +104,7 @@ function unknownEmoji(named: Named): string {
|
|
|
103
104
|
* emoji it can't read. Any other error has no words here.
|
|
104
105
|
*/
|
|
105
106
|
function localRefusalReason(error: unknown, named: Named): string | undefined {
|
|
107
|
+
const { DiscordjsError, DiscordjsTypeError, DiscordjsErrorCodes } = discordJs();
|
|
106
108
|
if (!(error instanceof DiscordjsError) && !(error instanceof DiscordjsTypeError)) return undefined;
|
|
107
109
|
switch ((error as { code?: unknown }).code) {
|
|
108
110
|
case DiscordjsErrorCodes.MessageThreadParent:
|
|
@@ -149,15 +151,16 @@ export class DiscordJsClient implements DiscordClientInterface {
|
|
|
149
151
|
this.guildIds = config.guildIds;
|
|
150
152
|
this.channelIds = config.channelIds;
|
|
151
153
|
|
|
152
|
-
|
|
154
|
+
const djs = discordJs();
|
|
155
|
+
this.client = new djs.Client({
|
|
153
156
|
intents: [
|
|
154
|
-
GatewayIntentBits.Guilds,
|
|
155
|
-
GatewayIntentBits.GuildMessages,
|
|
156
|
-
GatewayIntentBits.MessageContent,
|
|
157
|
-
GatewayIntentBits.DirectMessages,
|
|
158
|
-
GatewayIntentBits.GuildMessageReactions,
|
|
157
|
+
djs.GatewayIntentBits.Guilds,
|
|
158
|
+
djs.GatewayIntentBits.GuildMessages,
|
|
159
|
+
djs.GatewayIntentBits.MessageContent,
|
|
160
|
+
djs.GatewayIntentBits.DirectMessages,
|
|
161
|
+
djs.GatewayIntentBits.GuildMessageReactions,
|
|
159
162
|
],
|
|
160
|
-
partials: [Partials.Channel, Partials.Message],
|
|
163
|
+
partials: [djs.Partials.Channel, djs.Partials.Message],
|
|
161
164
|
});
|
|
162
165
|
|
|
163
166
|
this.setupEventHandlers();
|
|
@@ -301,17 +304,18 @@ export class DiscordJsClient implements DiscordClientInterface {
|
|
|
301
304
|
await message.startThread({ name });
|
|
302
305
|
return undefined;
|
|
303
306
|
} catch (error) {
|
|
304
|
-
|
|
307
|
+
const djs = discordJs();
|
|
308
|
+
if (error instanceof djs.DiscordAPIError) {
|
|
305
309
|
// Discord answers a retried start that it had already carried out as
|
|
306
310
|
// a thread that exists: say it's there.
|
|
307
|
-
if (error.code === RESTJSONErrorCodes.ThreadAlreadyCreatedForMessage) return `${sent}, and its thread exists already, with the message's own id, ${message.id}.`;
|
|
311
|
+
if (error.code === djs.RESTJSONErrorCodes.ThreadAlreadyCreatedForMessage) return `${sent}, and its thread exists already, with the message's own id, ${message.id}.`;
|
|
308
312
|
// The message is out, and `named` holds it, so no sentence points
|
|
309
313
|
// elsewhere, where posting it again would post it twice.
|
|
310
314
|
// The bot has just posted in the channel, so Missing Access is about
|
|
311
315
|
// its threads, not about seeing it.
|
|
312
316
|
const permission = `this bot isn't allowed to start threads in channel ${channelId} (Discord answered "Missing Permissions"), and calling again won't change that. Ask whoever runs this host to grant the permission.`;
|
|
313
317
|
const access = `this bot can't start threads in channel ${channelId} (Discord answered "Missing Access"), and calling again won't change that. Ask whoever runs this host to give it access.`;
|
|
314
|
-
return `${sent}, but no thread was started: ${refusalReason(error, named, { [RESTJSONErrorCodes.MissingPermissions]: permission, [RESTJSONErrorCodes.MissingAccess]: access })} Once that's settled, ${startOne} starts one.`;
|
|
318
|
+
return `${sent}, but no thread was started: ${refusalReason(error, named, { [djs.RESTJSONErrorCodes.MissingPermissions]: permission, [djs.RESTJSONErrorCodes.MissingAccess]: access })} Once that's settled, ${startOne} starts one.`;
|
|
315
319
|
}
|
|
316
320
|
// The thread refusals localRefusalReason words, a message outside a
|
|
317
321
|
// text or announcement channel and one that has a thread, can't come
|
|
@@ -359,7 +363,7 @@ export class DiscordJsClient implements DiscordClientInterface {
|
|
|
359
363
|
} catch (error) {
|
|
360
364
|
// A message that isn't there may be one an earlier call deleted, or a
|
|
361
365
|
// retried request of this one, so its sentence has no lead.
|
|
362
|
-
if (error instanceof DiscordRefusal && error.cause instanceof DiscordAPIError && error.cause.code === RESTJSONErrorCodes.UnknownMessage) {
|
|
366
|
+
if (error instanceof DiscordRefusal && error.cause instanceof discordJs().DiscordAPIError && error.cause.code === discordJs().RESTJSONErrorCodes.UnknownMessage) {
|
|
363
367
|
throw new DiscordRefusal(`Channel ${channelId} has no message ${messageId} now: it's deleted, or the id is wrong. discord--fetch_history with that channelId shows its recent messages and their ids.`, { cause: error.cause });
|
|
364
368
|
}
|
|
365
369
|
throw error;
|
|
@@ -393,7 +397,7 @@ export class DiscordJsClient implements DiscordClientInterface {
|
|
|
393
397
|
} catch (error) {
|
|
394
398
|
// Discord's answer for a message that has a thread, whether an earlier
|
|
395
399
|
// call started it or a retried request of this one did.
|
|
396
|
-
if (error instanceof DiscordRefusal && error.cause instanceof DiscordAPIError && error.cause.code === RESTJSONErrorCodes.ThreadAlreadyCreatedForMessage) {
|
|
400
|
+
if (error instanceof DiscordRefusal && error.cause instanceof discordJs().DiscordAPIError && error.cause.code === discordJs().RESTJSONErrorCodes.ThreadAlreadyCreatedForMessage) {
|
|
397
401
|
throw new DiscordRefusal(`Message ${messageId} has a thread already, whose id is the message's own: discord--send with channelId ${messageId} posts in it.`, { cause: error.cause });
|
|
398
402
|
}
|
|
399
403
|
throw error;
|
|
@@ -427,7 +431,7 @@ export class DiscordJsClient implements DiscordClientInterface {
|
|
|
427
431
|
try {
|
|
428
432
|
return await run();
|
|
429
433
|
} catch (error) {
|
|
430
|
-
if (error instanceof DiscordAPIError || error instanceof DiscordRefusal) throw error;
|
|
434
|
+
if (error instanceof discordJs().DiscordAPIError || error instanceof DiscordRefusal) throw error;
|
|
431
435
|
throw new DiscordRefusal(`${lead}: looking up ${what} stopped on an unexpected error: ${error instanceof Error ? error.message : String(error)}. Calling it again is safe.`, { cause: error });
|
|
432
436
|
}
|
|
433
437
|
}
|
|
@@ -443,7 +447,7 @@ export class DiscordJsClient implements DiscordClientInterface {
|
|
|
443
447
|
try {
|
|
444
448
|
return await run();
|
|
445
449
|
} catch (error) {
|
|
446
|
-
if (error instanceof DiscordAPIError) throw new DiscordRefusal(`${lead}: ${refusalReason(error, named)}`, { cause: error });
|
|
450
|
+
if (error instanceof discordJs().DiscordAPIError) throw new DiscordRefusal(`${lead}: ${refusalReason(error, named)}`, { cause: error });
|
|
447
451
|
const local = localRefusalReason(error, named);
|
|
448
452
|
if (local !== undefined) throw new DiscordRefusal(`${lead}: ${local}`, { cause: error });
|
|
449
453
|
throw error;
|
|
@@ -471,7 +475,7 @@ export class DiscordJsClient implements DiscordClientInterface {
|
|
|
471
475
|
const inPosts = (kind: string) => `${kind}, whose messages are in its posts, each a thread with its own id, which this module has no tool to list`;
|
|
472
476
|
const is = type === 'category' ? 'a category, which holds channels, not messages'
|
|
473
477
|
: type === 'forum' ? inPosts('a forum')
|
|
474
|
-
: channel.type === ChannelType.GuildMedia ? inPosts('a media channel')
|
|
478
|
+
: channel.type === discordJs().ChannelType.GuildMedia ? inPosts('a media channel')
|
|
475
479
|
: type === 'unknown' ? 'a kind of channel that holds no messages'
|
|
476
480
|
: `a ${type} channel, which holds no messages`;
|
|
477
481
|
return `channel ${channelId} is ${is}. discord--list_channels with a guildId shows the server's channels and their ids.`;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { markedText } from '../supplied-text.js';
|
|
2
|
+
|
|
1
3
|
/** The participant a module stores for a sender's name from outside the
|
|
2
|
-
* framework: on one line,
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* framework: on one line, with each listed notice opening marked as in
|
|
5
|
+
* supplied text, and marked with its source, so it isn't an agent's name
|
|
6
|
+
* unless a host names an agent in that form. The generic `user` stays as
|
|
7
|
+
* given, since context-manager places injections by the last message whose
|
|
8
|
+
* participant is `user`, in any capitalization. */
|
|
6
9
|
export function outsideSenderName(name: string, source: string): string {
|
|
7
|
-
const line = name.replace(/[\p{Cc}\u2028\u2029]+/gu, ' ');
|
|
10
|
+
const line = markedText(name.replace(/[\p{Cc}\u2028\u2029]+/gu, ' '));
|
|
8
11
|
return line.toLowerCase() === 'user' ? line : `${line} (${source})`;
|
|
9
12
|
}
|