@msm-core/mini 0.8.0 → 0.14.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/CHANGELOG.md +279 -0
- package/dist/adapters/index.d.ts +12 -2
- package/dist/adapters/index.js +10 -0
- package/dist/adapters/memory-control.d.ts +43 -0
- package/dist/adapters/memory-control.js +56 -0
- package/dist/adapters/memory-dedup.d.ts +36 -0
- package/dist/adapters/memory-dedup.js +59 -0
- package/dist/adapters/memory-lock.d.ts +45 -0
- package/dist/adapters/memory-lock.js +89 -0
- package/dist/adapters/redis-control.d.ts +2 -1
- package/dist/adapters/redis-lock.d.ts +9 -5
- package/dist/brain/anthropic.js +8 -1
- package/dist/brain/gemini.js +15 -2
- package/dist/brain/ollama.js +8 -2
- package/dist/brain/openai.js +9 -2
- package/dist/brain/retry.d.ts +98 -2
- package/dist/brain/retry.js +132 -2
- package/dist/brain/streaming.d.ts +42 -0
- package/dist/brain/streaming.js +44 -1
- package/dist/bridge/pipeline.js +11 -8
- package/dist/core/hooks.d.ts +14 -2
- package/dist/core/hooks.js +20 -3
- package/dist/core/loop.js +148 -23
- package/dist/core/types.d.ts +387 -5
- package/dist/core/types.js +130 -1
- package/dist/definition/skills.d.ts +74 -0
- package/dist/definition/skills.js +119 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +30 -0
- package/dist/tools/dedup.d.ts +22 -1
- package/dist/tools/dedup.js +28 -0
- package/dist/tools/delegate.d.ts +134 -0
- package/dist/tools/delegate.js +223 -0
- package/dist/tools/executor.d.ts +49 -5
- package/dist/tools/executor.js +38 -5
- package/package.json +4 -3
package/dist/bridge/pipeline.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* The pipeline is duck-typed — no direct import of @msm-core/pipeline.
|
|
16
16
|
*/
|
|
17
|
+
import { BRAIN_ACTIONS } from "../core/types.js";
|
|
17
18
|
// ─── Adapter ──────────────────────────────────────────────────
|
|
18
19
|
/**
|
|
19
20
|
* Wrap an @msm-core/pipeline as a Brain for @msm-core/mini.
|
|
@@ -57,14 +58,16 @@ export function wrapPipeline(pipeline, name = "pipeline") {
|
|
|
57
58
|
: {}),
|
|
58
59
|
});
|
|
59
60
|
const brain = result.payload.brain;
|
|
60
|
-
// Normalise action — pipeline may return
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
// Normalise action — a duck-typed pipeline may return any string, and
|
|
62
|
+
// mini dispatches on four. The set is built FROM `BRAIN_ACTIONS` and is
|
|
63
|
+
// no longer a second hand-written copy of it (ر٢): a copy is how the
|
|
64
|
+
// union and this list came to disagree about a fifth action that the loop
|
|
65
|
+
// never dispatched on, and how that disagreement went unnoticed.
|
|
66
|
+
//
|
|
67
|
+
// The fallback is unchanged: an unrecognised string becomes `respond`,
|
|
68
|
+
// which is also what the loop itself does with one that arrives past this
|
|
69
|
+
// point. Two layers, one rule.
|
|
70
|
+
const VALID_ACTIONS = new Set(BRAIN_ACTIONS);
|
|
68
71
|
const rawAction = brain?.action ?? "respond";
|
|
69
72
|
const action = VALID_ACTIONS.has(rawAction)
|
|
70
73
|
? rawAction
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -10,13 +10,25 @@ export declare function fireSectionComplete(hooks: AgentHooks | undefined, sessi
|
|
|
10
10
|
export declare function fireToolCall(hooks: AgentHooks | undefined, sessionId: string, result: ToolResult, iteration: number, cached: boolean, durationMs: number): void;
|
|
11
11
|
export declare function fireGuard(hooks: AgentHooks | undefined, sessionId: string, signal: GuardSignal, iteration: number): void;
|
|
12
12
|
/**
|
|
13
|
-
* One streamed piece of model text (ب١)
|
|
13
|
+
* One streamed piece of model text (ب١), carrying `reset` when there is one
|
|
14
|
+
* and a consumer who asked for it (ص٣/٢).
|
|
14
15
|
*
|
|
15
16
|
* Same shape as its siblings, and the same contract: `safely` swallows a
|
|
16
17
|
* throwing hook. That matters more here than anywhere else — this one fires
|
|
17
18
|
* dozens of times per step, in the middle of reading a provider stream, and a
|
|
18
19
|
* consumer whose SSE socket closed mid-answer must not take the run down with
|
|
19
20
|
* it. A failed display is a failed display, not a failed run.
|
|
21
|
+
*
|
|
22
|
+
* **`reset` is spread, not assigned.** `{ sessionId, iteration, text }` is what
|
|
23
|
+
* an undeclared consumer received before this parameter existed and it is
|
|
24
|
+
* exactly what it receives now — the key is ABSENT, not present-and-undefined,
|
|
25
|
+
* so `Object.keys`, `toEqual`, `JSON.stringify` and a `for…in` all see what
|
|
26
|
+
* they saw yesterday. That equality is the mandatory control of ص٣/٢, and
|
|
27
|
+
* writing `reset` unconditionally would have broken it while every existing
|
|
28
|
+
* assertion stayed green.
|
|
29
|
+
*
|
|
30
|
+
* The parameter is optional and last, so the pre-ص٣ call shape still compiles
|
|
31
|
+
* and still means what it meant.
|
|
20
32
|
*/
|
|
21
|
-
export declare function fireChunk(hooks: AgentHooks | undefined, sessionId: string, iteration: number, text: string): void;
|
|
33
|
+
export declare function fireChunk(hooks: AgentHooks | undefined, sessionId: string, iteration: number, text: string, reset?: true): void;
|
|
22
34
|
export declare function fireFatalError(hooks: AgentHooks | undefined, error: Error, sessionId: string): void;
|
package/dist/core/hooks.js
CHANGED
|
@@ -54,18 +54,35 @@ export function fireGuard(hooks, sessionId, signal, iteration) {
|
|
|
54
54
|
safely(() => hooks.onGuard({ sessionId, signal, iteration }));
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
|
-
* One streamed piece of model text (ب١)
|
|
57
|
+
* One streamed piece of model text (ب١), carrying `reset` when there is one
|
|
58
|
+
* and a consumer who asked for it (ص٣/٢).
|
|
58
59
|
*
|
|
59
60
|
* Same shape as its siblings, and the same contract: `safely` swallows a
|
|
60
61
|
* throwing hook. That matters more here than anywhere else — this one fires
|
|
61
62
|
* dozens of times per step, in the middle of reading a provider stream, and a
|
|
62
63
|
* consumer whose SSE socket closed mid-answer must not take the run down with
|
|
63
64
|
* it. A failed display is a failed display, not a failed run.
|
|
65
|
+
*
|
|
66
|
+
* **`reset` is spread, not assigned.** `{ sessionId, iteration, text }` is what
|
|
67
|
+
* an undeclared consumer received before this parameter existed and it is
|
|
68
|
+
* exactly what it receives now — the key is ABSENT, not present-and-undefined,
|
|
69
|
+
* so `Object.keys`, `toEqual`, `JSON.stringify` and a `for…in` all see what
|
|
70
|
+
* they saw yesterday. That equality is the mandatory control of ص٣/٢, and
|
|
71
|
+
* writing `reset` unconditionally would have broken it while every existing
|
|
72
|
+
* assertion stayed green.
|
|
73
|
+
*
|
|
74
|
+
* The parameter is optional and last, so the pre-ص٣ call shape still compiles
|
|
75
|
+
* and still means what it meant.
|
|
64
76
|
*/
|
|
65
|
-
export function fireChunk(hooks, sessionId, iteration, text) {
|
|
77
|
+
export function fireChunk(hooks, sessionId, iteration, text, reset) {
|
|
66
78
|
if (!hooks?.onChunk)
|
|
67
79
|
return;
|
|
68
|
-
safely(() => hooks.onChunk({
|
|
80
|
+
safely(() => hooks.onChunk({
|
|
81
|
+
sessionId,
|
|
82
|
+
iteration,
|
|
83
|
+
text,
|
|
84
|
+
...(reset === true ? { reset } : {}),
|
|
85
|
+
}));
|
|
69
86
|
}
|
|
70
87
|
export function fireFatalError(hooks, error, sessionId) {
|
|
71
88
|
if (!hooks?.onFatalError)
|
package/dist/core/loop.js
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { randomBytes } from "crypto";
|
|
11
11
|
import { compactionAnchor, compactionBoundary, deriveMessages, } from "@msm-core/session";
|
|
12
|
+
// The declaration thread of ص٣/٢, and the only values this file takes from
|
|
13
|
+
// `types.js`: one predicate to read the hook's opt-in, one wrapper to pass it on.
|
|
14
|
+
import { acceptResets, isResetAwareHook } from "./types.js";
|
|
12
15
|
import { toWireMessage } from "../brain/tool-context.js";
|
|
13
16
|
import { resolveGuards, checkGuards, hasHardBlock, hardAction, } from "./guards.js";
|
|
14
17
|
import { runGates } from "./gates.js";
|
|
@@ -18,6 +21,7 @@ import { RedisMemory, connectRedis } from "../adapters/redis-memory.js";
|
|
|
18
21
|
import { RedisControlBus } from "../adapters/redis-control.js";
|
|
19
22
|
import { RedisDistributedLock } from "../adapters/redis-lock.js";
|
|
20
23
|
import { executeTool, toToolDefinitions } from "../tools/executor.js";
|
|
24
|
+
import { RedisToolDedup } from "../tools/dedup.js";
|
|
21
25
|
import { parseDefinition } from "../definition/parser.js";
|
|
22
26
|
function makeTaskId() {
|
|
23
27
|
return randomBytes(8).toString("hex");
|
|
@@ -305,11 +309,62 @@ function assertRequestReconstructible(args) {
|
|
|
305
309
|
throw invariantError("the tool results", turnId, stepId, expectedResults, sentResults);
|
|
306
310
|
}
|
|
307
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* The four ports Redis can back, each paired with what a reader of the error
|
|
314
|
+
* will recognise it as. In the order a turn resolves them, so an error listing
|
|
315
|
+
* several reads in the order the run would have needed them.
|
|
316
|
+
*/
|
|
317
|
+
const REDIS_BACKED_PORTS = [
|
|
318
|
+
["memory", "session memory — history, metadata, document state"],
|
|
319
|
+
["controlBus", "the control bus — kill / pause / disable-tool"],
|
|
320
|
+
["lock", "the session run-lock"],
|
|
321
|
+
["dedup", "tool-call dedup"],
|
|
322
|
+
];
|
|
323
|
+
/**
|
|
324
|
+
* `redis` is optional (ص٤) under exactly one rule: **whatever it would have
|
|
325
|
+
* backed must be injected instead.** This is where that rule is enforced, and
|
|
326
|
+
* where it is enforced is half the point.
|
|
327
|
+
*
|
|
328
|
+
* A port is resolved lazily, deep inside a turn, on a path a given event may
|
|
329
|
+
* not even take. Left to itself, a composition that dropped `redis` while
|
|
330
|
+
* forgetting `dedup` would run fine until the first tool call — possibly in
|
|
331
|
+
* production, hours after the process started — and then fail with a message
|
|
332
|
+
* about a Redis config nobody wrote. A composition mistake belongs at
|
|
333
|
+
* composition time, named by the exact field that is missing, before a single
|
|
334
|
+
* event is handled and before a single token is paid for.
|
|
335
|
+
*
|
|
336
|
+
* Absence is read from the VALUE, not from the key: `exactOptionalPropertyTypes`
|
|
337
|
+
* stops a TypeScript caller writing `redis: undefined`, but a JavaScript host
|
|
338
|
+
* spreading an options bag can hand one over, and that caller deserves this
|
|
339
|
+
* error rather than a `TypeError` from inside `getRedis()`.
|
|
340
|
+
*
|
|
341
|
+
* With `redis` present this returns immediately and nothing about any existing
|
|
342
|
+
* composition changes.
|
|
343
|
+
*/
|
|
344
|
+
function assertRedisOrPorts(config) {
|
|
345
|
+
if (config.redis !== undefined)
|
|
346
|
+
return;
|
|
347
|
+
const missing = REDIS_BACKED_PORTS.filter(([field]) => config[field] === undefined);
|
|
348
|
+
if (missing.length === 0)
|
|
349
|
+
return;
|
|
350
|
+
const named = missing
|
|
351
|
+
.map(([field, what]) => `'${field}' (${what})`)
|
|
352
|
+
.join(", ");
|
|
353
|
+
const one = missing.length === 1;
|
|
354
|
+
throw new Error(`msm-mini: AgentConfig has no 'redis', so every port Redis would have ` +
|
|
355
|
+
`backed must be injected — missing ${one ? "port" : "ports"}: ${named}. ` +
|
|
356
|
+
`Inject ${one ? "it" : "them"} (InMemorySessionStore / InMemoryControlBus / ` +
|
|
357
|
+
`InMemoryLock / InMemoryToolDedup ship from '@msm-core/mini/adapters'), ` +
|
|
358
|
+
`or supply 'redis'.`);
|
|
359
|
+
}
|
|
308
360
|
/**
|
|
309
361
|
* Create a stateful agent from config.
|
|
310
362
|
* Returns an Agent object — call .handle() on every incoming event.
|
|
311
363
|
*/
|
|
312
364
|
export function createAgent(config) {
|
|
365
|
+
// Before anything else: a redis-less config with an unbacked port is a
|
|
366
|
+
// composition error, and it is raised here rather than mid-turn (ص٤).
|
|
367
|
+
assertRedisOrPorts(config);
|
|
313
368
|
const def = typeof config.definition === "string"
|
|
314
369
|
? parseDefinition(config.definition)
|
|
315
370
|
: config.definition;
|
|
@@ -318,16 +373,24 @@ export function createAgent(config) {
|
|
|
318
373
|
// never applied — the documented limits silently no-op'd (M1). resolveGuards
|
|
319
374
|
// then rejects any NaN/garbage and clamps to sane bounds (H10).
|
|
320
375
|
const guards = resolveGuards({ ...def.limits, ...config.guards });
|
|
321
|
-
const prefix = config.redis
|
|
322
|
-
const ttl = config.redis
|
|
323
|
-
// Lazy-initialized shared Redis connection
|
|
376
|
+
const prefix = config.redis?.prefix ?? "msm";
|
|
377
|
+
const ttl = config.redis?.ttl ?? {};
|
|
378
|
+
// Lazy-initialized shared Redis connection.
|
|
379
|
+
//
|
|
380
|
+
// Reachable only when `config.redis` is present: `assertRedisOrPorts` has
|
|
381
|
+
// already refused a redis-less config that left any of the four ports
|
|
382
|
+
// unbacked, and a redis-less config with all four injected never calls a
|
|
383
|
+
// resolver's fallback arm. The `?.`s below are therefore about a config
|
|
384
|
+
// object that EXISTS but is empty — `redis: {}` keeps throwing the same
|
|
385
|
+
// message it always has, which is what makes it a usable "explode if opened"
|
|
386
|
+
// test config (ports.test.ts leans on exactly that).
|
|
324
387
|
let _redis = null;
|
|
325
388
|
async function getRedis() {
|
|
326
389
|
if (!_redis) {
|
|
327
|
-
if (config.redis
|
|
390
|
+
if (config.redis?.client) {
|
|
328
391
|
_redis = config.redis.client;
|
|
329
392
|
}
|
|
330
|
-
else if (config.redis
|
|
393
|
+
else if (config.redis?.url) {
|
|
331
394
|
_redis = (await connectRedis(config.redis.url));
|
|
332
395
|
}
|
|
333
396
|
else {
|
|
@@ -395,6 +458,49 @@ export function createAgent(config) {
|
|
|
395
458
|
...(ttl.document !== undefined ? { documentTtl: ttl.document } : {}),
|
|
396
459
|
});
|
|
397
460
|
}
|
|
461
|
+
// ── The other three ports (س٦) ─────────────────────────────
|
|
462
|
+
//
|
|
463
|
+
// Same shape as `resolveStore`, three times: an injected port wins and no
|
|
464
|
+
// Redis object is built for it — and, critically, `getRedis()` is not called
|
|
465
|
+
// on its account, so a run with all four ports injected never opens a
|
|
466
|
+
// connection at all. Omit one and the loop builds the bundled Redis adapter
|
|
467
|
+
// exactly as it did before, with the tenant-scoped prefix it computed.
|
|
468
|
+
//
|
|
469
|
+
// They take the prefix rather than the event because `kill/pause/resume` have
|
|
470
|
+
// no event — they are handed a `companyId`/`agentType` pair instead. That is
|
|
471
|
+
// also why these are one function each and not four `new RedisControlBus`
|
|
472
|
+
// calls: the four sites that used to construct a bus by hand (handleCore plus
|
|
473
|
+
// the three public commands) had already begun to differ in how they built
|
|
474
|
+
// their prefix, and two sites that do the same thing separately are two sites
|
|
475
|
+
// that will one day do it differently (س١).
|
|
476
|
+
async function resolveControlBus(tenantPrefix) {
|
|
477
|
+
const injected = config.controlBus;
|
|
478
|
+
if (injected)
|
|
479
|
+
return injected;
|
|
480
|
+
return new RedisControlBus(await getRedis(), tenantPrefix);
|
|
481
|
+
}
|
|
482
|
+
async function resolveLock(tenantPrefix) {
|
|
483
|
+
const injected = config.lock;
|
|
484
|
+
if (injected)
|
|
485
|
+
return injected;
|
|
486
|
+
return new RedisDistributedLock(await getRedis(), tenantPrefix);
|
|
487
|
+
}
|
|
488
|
+
async function resolveDedup(tenantPrefix) {
|
|
489
|
+
const injected = config.dedup;
|
|
490
|
+
if (injected)
|
|
491
|
+
return injected;
|
|
492
|
+
return new RedisToolDedup(await getRedis(), tenantPrefix);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* The prefix the three public commands scope their bus with — the one
|
|
496
|
+
* expression that used to be copy-pasted into `kill`, `pause` and `resume`.
|
|
497
|
+
* Note it is NOT `tenantPrefixFor`: those take the two ids separately and
|
|
498
|
+
* fall back to the base prefix when either is missing, which is the behaviour
|
|
499
|
+
* being preserved, not tidied.
|
|
500
|
+
*/
|
|
501
|
+
function commandPrefix(companyId, agentType) {
|
|
502
|
+
return companyId && agentType ? `${prefix}:${companyId}:${agentType}` : prefix;
|
|
503
|
+
}
|
|
398
504
|
/**
|
|
399
505
|
* Public entry point — runs the loop, then persists final run metadata
|
|
400
506
|
* (status / iterations / cost) for ops dashboards. Metadata is best-effort:
|
|
@@ -434,11 +540,13 @@ export function createAgent(config) {
|
|
|
434
540
|
const effectiveGuards = event.guardsOverride
|
|
435
541
|
? resolveGuards({ ...guards, ...event.guardsOverride })
|
|
436
542
|
: guards;
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const
|
|
543
|
+
// The three run-control ports, resolved where the Redis trio used to be
|
|
544
|
+
// constructed — injected if the composition provided them, Redis-backed
|
|
545
|
+
// from `config.redis` if not. Session memory arrives resolved from
|
|
546
|
+
// handle(). With all four injected, nothing below touches Redis.
|
|
547
|
+
const controlBus = await resolveControlBus(tenantPrefix);
|
|
548
|
+
const lock = await resolveLock(tenantPrefix);
|
|
549
|
+
const dedup = await resolveDedup(tenantPrefix);
|
|
442
550
|
// ── Gates (zero-LLM filters) ─────────────────────────────
|
|
443
551
|
const gated = runGates(sessionId, taskId, event.message, config.gates);
|
|
444
552
|
if (gated)
|
|
@@ -470,10 +578,24 @@ export function createAgent(config) {
|
|
|
470
578
|
* `state.iteration` is read when a chunk fires, not when this closure is
|
|
471
579
|
* made, so each chunk carries the step it actually belongs to — including
|
|
472
580
|
* the finalize call, which streams under the iteration it aborted on.
|
|
581
|
+
*
|
|
582
|
+
* **And it declares for resets only when the hook did (ص٣/٢).** ص٢ built
|
|
583
|
+
* the cure in `brain/retry.ts` and could not deliver it here, because a
|
|
584
|
+
* declared sink whose `fireChunk` had no reset channel would take the flag
|
|
585
|
+
* and drop it — showing the hook consumer the retried answer glued onto
|
|
586
|
+
* the truncated one, which is ب١/٢ over again. `fireChunk` now carries the
|
|
587
|
+
* flag, so the loop can ask the one question worth asking: did the person
|
|
588
|
+
* on the far end of `onChunk` say they would act on it? Declared, the sink
|
|
589
|
+
* is wrapped and `withRetry` clears-and-restarts; undeclared, `isResetAware`
|
|
590
|
+
* is false in `retry.ts`, the ص١ mute path runs, and not a byte of this
|
|
591
|
+
* run differs from yesterday. The default did not move — it was extended.
|
|
473
592
|
*/
|
|
474
|
-
const
|
|
475
|
-
? (chunk) => fireChunk(config.hooks, sessionId, state.iteration, chunk.text)
|
|
593
|
+
const rawChunkSink = config.hooks?.onChunk
|
|
594
|
+
? (chunk) => fireChunk(config.hooks, sessionId, state.iteration, chunk.text, chunk.reset)
|
|
476
595
|
: undefined;
|
|
596
|
+
const chunkSink = rawChunkSink !== undefined && isResetAwareHook(config.hooks?.onChunk)
|
|
597
|
+
? acceptResets(rawChunkSink)
|
|
598
|
+
: rawChunkSink;
|
|
477
599
|
const toolDefs = toToolDefinitions(config.tools);
|
|
478
600
|
const toolMap = new Map(config.tools.map((t) => [t.name, t]));
|
|
479
601
|
const toolResults = [];
|
|
@@ -640,6 +762,15 @@ export function createAgent(config) {
|
|
|
640
762
|
* numbered `stepId.c1 … stepId.cN`. The log shape was built for this from
|
|
641
763
|
* the start (س٢ §4: "a step carries as many tool calls as the model asked
|
|
642
764
|
* for") — this is the writer finally filling it.
|
|
765
|
+
*
|
|
766
|
+
* **And `model` is who answered, not who was asked** (س٦/٤). The request
|
|
767
|
+
* event carries `def.brain.model` — the configured name, which is the
|
|
768
|
+
* honest answer to "what did this agent ask for". It is NOT the honest
|
|
769
|
+
* answer to "what replied", and the two come apart the moment a host
|
|
770
|
+
* swaps the brain under a definition. So the responder travels on the
|
|
771
|
+
* payload, from the provider's own reply, and is written here beside the
|
|
772
|
+
* text it produced. A brain that does not report one writes no `model`
|
|
773
|
+
* key at all — an absent field, never an inferred one.
|
|
643
774
|
*/
|
|
644
775
|
const logResponse = async (payload, stepId) => {
|
|
645
776
|
if (!turnLog)
|
|
@@ -649,6 +780,7 @@ export function createAgent(config) {
|
|
|
649
780
|
const text = payload.generation?.response_text ?? payload.final_output?.text;
|
|
650
781
|
const calls = orch?.action === "use_tool" ? stepToolCalls(orch) : [];
|
|
651
782
|
await writer.writeStep("model_response", stepId, {
|
|
783
|
+
...(payload.model !== undefined ? { model: payload.model } : {}),
|
|
652
784
|
...(text !== undefined ? { text } : {}),
|
|
653
785
|
...(calls.length > 0
|
|
654
786
|
? {
|
|
@@ -889,8 +1021,7 @@ export function createAgent(config) {
|
|
|
889
1021
|
agentName: def.name,
|
|
890
1022
|
...(event.tenantContext ? { tenantContext: event.tenantContext } : {}),
|
|
891
1023
|
}, {
|
|
892
|
-
|
|
893
|
-
redisPrefix: tenantPrefix,
|
|
1024
|
+
dedup,
|
|
894
1025
|
dedupTtlSeconds: ttl.toolDedup ?? 300,
|
|
895
1026
|
...(config.hooks?.onBeforeTool
|
|
896
1027
|
? { hooks: { onBeforeTool: config.hooks.onBeforeTool } }
|
|
@@ -978,21 +1109,15 @@ export function createAgent(config) {
|
|
|
978
1109
|
}
|
|
979
1110
|
}
|
|
980
1111
|
async function kill(sessionId, companyId, agentType) {
|
|
981
|
-
const
|
|
982
|
-
const p = companyId && agentType ? `${prefix}:${companyId}:${agentType}` : prefix;
|
|
983
|
-
const bus = new RedisControlBus(redis, p);
|
|
1112
|
+
const bus = await resolveControlBus(commandPrefix(companyId, agentType));
|
|
984
1113
|
await bus.kill(sessionId);
|
|
985
1114
|
}
|
|
986
1115
|
async function pause(sessionId, companyId, agentType) {
|
|
987
|
-
const
|
|
988
|
-
const p = companyId && agentType ? `${prefix}:${companyId}:${agentType}` : prefix;
|
|
989
|
-
const bus = new RedisControlBus(redis, p);
|
|
1116
|
+
const bus = await resolveControlBus(commandPrefix(companyId, agentType));
|
|
990
1117
|
await bus.pause(sessionId);
|
|
991
1118
|
}
|
|
992
1119
|
async function resume(sessionId, companyId, agentType) {
|
|
993
|
-
const
|
|
994
|
-
const p = companyId && agentType ? `${prefix}:${companyId}:${agentType}` : prefix;
|
|
995
|
-
const bus = new RedisControlBus(redis, p);
|
|
1120
|
+
const bus = await resolveControlBus(commandPrefix(companyId, agentType));
|
|
996
1121
|
await bus.resume(sessionId);
|
|
997
1122
|
}
|
|
998
1123
|
return { handle, kill, pause, resume };
|