@genesislcap/ai-assistant 14.437.0 → 14.437.2
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/ai-assistant.api.json +242 -19
- package/dist/ai-assistant.d.ts +104 -20
- package/dist/dts/components/chat-driver/chat-driver.d.ts +31 -3
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +30 -2
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/config/define-stateful-agent.d.ts +38 -14
- package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +4 -2
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/esm/components/chat-driver/chat-driver.js +45 -3
- package/dist/esm/config/define-stateful-agent.js +32 -45
- package/dist/esm/main/main.js +20 -9
- package/dist/esm/main/main.template.js +8 -5
- package/package.json +16 -16
- package/src/components/chat-driver/chat-driver.ts +79 -4
- package/src/config/config.ts +33 -2
- package/src/config/define-stateful-agent.ts +72 -57
- package/src/main/main.template.ts +1 -1
- package/src/main/main.ts +21 -7
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
SystemPromptContext,
|
|
17
17
|
SystemPromptInput,
|
|
18
18
|
ToolDefinitionsInput,
|
|
19
|
+
ToolHandlersInput,
|
|
19
20
|
} from '../../config/config';
|
|
20
21
|
import { applyHistoryCap } from '../../utils/history-transform';
|
|
21
22
|
import { logger } from '../../utils/logger';
|
|
@@ -65,6 +66,12 @@ export interface TurnSnapshot {
|
|
|
65
66
|
systemPrompt?: string;
|
|
66
67
|
/** Tool names sent to the LLM, in order — definitions are static per name so names alone suffice. */
|
|
67
68
|
toolNames: string[];
|
|
69
|
+
/**
|
|
70
|
+
* Per-turn display label resolved from the agent's `displayName`, e.g.
|
|
71
|
+
* "Guided Booking (Counterparties)". `agentName` stays as the canonical
|
|
72
|
+
* identity used for routing/filtering.
|
|
73
|
+
*/
|
|
74
|
+
agentLabel?: string;
|
|
68
75
|
/** Agent-supplied snapshot — machine state/context for stateful agents, undefined otherwise. */
|
|
69
76
|
agentSnapshot?: unknown;
|
|
70
77
|
}
|
|
@@ -114,9 +121,33 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
114
121
|
private toolDefinitionsFactory?: (
|
|
115
122
|
ctx: SystemPromptContext,
|
|
116
123
|
) => ChatToolDefinition[] | Promise<ChatToolDefinition[]>;
|
|
124
|
+
/**
|
|
125
|
+
* Resolved tool handler map used for dispatch. When `toolHandlersFactory` is
|
|
126
|
+
* set, this is overwritten each tool-loop iteration with the factory's output
|
|
127
|
+
* — keeping it in lockstep with `toolDefinitions` so handlers don't have to
|
|
128
|
+
* defend themselves against being dispatched in states where their tool
|
|
129
|
+
* isn't advertised. Folds mutate this in place; `defineStatefulAgent`
|
|
130
|
+
* forbids folds when a factory is set, so the fold-mutation path is
|
|
131
|
+
* unreachable in that case.
|
|
132
|
+
*/
|
|
117
133
|
private toolHandlers: ChatToolHandlers;
|
|
134
|
+
/**
|
|
135
|
+
* Optional per-turn handler-map source. Mirrors `toolDefinitionsFactory` so
|
|
136
|
+
* the LLM-visible tools and the dispatchable handlers can be narrowed in
|
|
137
|
+
* lockstep. Resolved each tool-loop iteration before the LLM call.
|
|
138
|
+
*/
|
|
139
|
+
private toolHandlersFactory?: (
|
|
140
|
+
ctx: SystemPromptContext,
|
|
141
|
+
) => ChatToolHandlers | Promise<ChatToolHandlers>;
|
|
118
142
|
private primerHistory?: ChatMessage[];
|
|
119
143
|
private activeAgentName?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Per-turn display label resolved from the agent's `displayName`. Stamped
|
|
146
|
+
* onto outgoing messages and turn snapshots for UX; `activeAgentName` stays
|
|
147
|
+
* stable for routing/history-transform identity matching.
|
|
148
|
+
*/
|
|
149
|
+
private activeAgentLabel?: string;
|
|
150
|
+
private displayName?: SystemPromptInput;
|
|
120
151
|
/**
|
|
121
152
|
* When set, `requestInteraction` delegates to this callback instead of using
|
|
122
153
|
* this driver's own pending map. Wired by `invokeSubAgent` so a sub-agent's
|
|
@@ -175,7 +206,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
175
206
|
|
|
176
207
|
constructor(
|
|
177
208
|
private readonly aiProvider: AIProvider,
|
|
178
|
-
toolHandlers:
|
|
209
|
+
toolHandlers: ToolHandlersInput = {},
|
|
179
210
|
toolDefinitions: ToolDefinitionsInput = [],
|
|
180
211
|
systemPrompt?: SystemPromptInput,
|
|
181
212
|
primerHistory?: ChatMessage[],
|
|
@@ -184,7 +215,13 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
184
215
|
maxTurnSnapshots: number = DEFAULT_MAX_TURN_SNAPSHOTS,
|
|
185
216
|
) {
|
|
186
217
|
super();
|
|
187
|
-
|
|
218
|
+
if (typeof toolHandlers === 'function') {
|
|
219
|
+
this.toolHandlersFactory = toolHandlers;
|
|
220
|
+
this.toolHandlers = {};
|
|
221
|
+
} else {
|
|
222
|
+
this.toolHandlersFactory = undefined;
|
|
223
|
+
this.toolHandlers = toolHandlers;
|
|
224
|
+
}
|
|
188
225
|
if (typeof toolDefinitions === 'function') {
|
|
189
226
|
this.toolDefinitionsFactory = toolDefinitions;
|
|
190
227
|
this.toolDefinitions = [];
|
|
@@ -213,9 +250,23 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
213
250
|
this.toolDefinitionsFactory = undefined;
|
|
214
251
|
this.toolDefinitions = config.toolDefinitions ?? [];
|
|
215
252
|
}
|
|
216
|
-
|
|
253
|
+
if (typeof config.toolHandlers === 'function') {
|
|
254
|
+
this.toolHandlersFactory = config.toolHandlers;
|
|
255
|
+
// Cleared each turn by the factory in runToolLoop; empty is safe in the
|
|
256
|
+
// meantime (no LLM call happens before resolution).
|
|
257
|
+
this.toolHandlers = {};
|
|
258
|
+
} else {
|
|
259
|
+
this.toolHandlersFactory = undefined;
|
|
260
|
+
this.toolHandlers = config.toolHandlers ?? {};
|
|
261
|
+
}
|
|
217
262
|
this.primerHistory = config.primerHistory;
|
|
218
263
|
this.activeAgentName = config.name;
|
|
264
|
+
this.displayName = config.displayName;
|
|
265
|
+
// Static string form resolves to a stable label up-front; the function
|
|
266
|
+
// form gets re-resolved each tool-loop iteration. Falls back to the
|
|
267
|
+
// canonical name when displayName is unset.
|
|
268
|
+
this.activeAgentLabel =
|
|
269
|
+
typeof config.displayName === 'string' ? config.displayName : config.name;
|
|
219
270
|
this.debugSnapshotter = config.getDebugSnapshot;
|
|
220
271
|
this.subAgentsMap = new Map((config.subAgents ?? []).map((s) => [s.name, s]));
|
|
221
272
|
// Reset fold state when agent changes — each specialist starts fresh
|
|
@@ -273,6 +324,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
273
324
|
turnIndex,
|
|
274
325
|
timestamp: new Date().toISOString(),
|
|
275
326
|
agentName: this.activeAgentName,
|
|
327
|
+
agentLabel: this.activeAgentLabel,
|
|
276
328
|
systemPrompt: resolvedSystemPrompt,
|
|
277
329
|
toolNames: this.toolDefinitions.map((t) => t.name),
|
|
278
330
|
agentSnapshot,
|
|
@@ -835,6 +887,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
835
887
|
// Tool loop
|
|
836
888
|
// ---------------------------------------------------------------------------
|
|
837
889
|
|
|
890
|
+
// eslint-disable-next-line complexity
|
|
838
891
|
private async runToolLoop(
|
|
839
892
|
userInput: string,
|
|
840
893
|
attachments?: ChatAttachment[],
|
|
@@ -877,6 +930,14 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
877
930
|
// eslint-disable-next-line no-await-in-loop
|
|
878
931
|
this.toolDefinitions = await this.toolDefinitionsFactory(promptCtx);
|
|
879
932
|
}
|
|
933
|
+
// Same story for the handler-map factory: re-resolve so dispatch sees
|
|
934
|
+
// only the handlers valid for the current state, in lockstep with the
|
|
935
|
+
// tool definitions exposed above. Folds are forbidden when this is set,
|
|
936
|
+
// so the fold-mutation paths on `this.toolHandlers` are unreachable.
|
|
937
|
+
if (this.toolHandlersFactory) {
|
|
938
|
+
// eslint-disable-next-line no-await-in-loop
|
|
939
|
+
this.toolHandlers = await this.toolHandlersFactory(promptCtx);
|
|
940
|
+
}
|
|
880
941
|
|
|
881
942
|
const resolvedSystemPrompt =
|
|
882
943
|
typeof this.systemPrompt === 'function'
|
|
@@ -884,6 +945,15 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
884
945
|
await this.systemPrompt(promptCtx)
|
|
885
946
|
: this.systemPrompt;
|
|
886
947
|
|
|
948
|
+
// Re-resolve the per-turn display label. Falls back to the canonical
|
|
949
|
+
// agent name when displayName is unset. Stamped onto outgoing messages
|
|
950
|
+
// and turn snapshots for UX only; routing/history-transform continues
|
|
951
|
+
// to read `activeAgentName`.
|
|
952
|
+
if (typeof this.displayName === 'function') {
|
|
953
|
+
// eslint-disable-next-line no-await-in-loop
|
|
954
|
+
this.activeAgentLabel = await this.displayName(promptCtx);
|
|
955
|
+
}
|
|
956
|
+
|
|
887
957
|
const foldSuffix = this.buildFoldSystemPromptSuffix();
|
|
888
958
|
const baseSystemPrompt = resolvedSystemPrompt
|
|
889
959
|
? `${resolvedSystemPrompt}${foldSuffix}`
|
|
@@ -1198,7 +1268,12 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1198
1268
|
|
|
1199
1269
|
private appendToHistory(message: ChatMessage): void {
|
|
1200
1270
|
const tagged: ChatMessage = this.activeAgentName
|
|
1201
|
-
? {
|
|
1271
|
+
? {
|
|
1272
|
+
...message,
|
|
1273
|
+
agentName: this.activeAgentName,
|
|
1274
|
+
// Display-only — falls back to agentName in renderers when unset.
|
|
1275
|
+
agentLabel: this.activeAgentLabel,
|
|
1276
|
+
}
|
|
1202
1277
|
: message;
|
|
1203
1278
|
this.history = [...this.history, tagged];
|
|
1204
1279
|
this.dispatchEvent(
|
package/src/config/config.ts
CHANGED
|
@@ -61,6 +61,20 @@ export type ToolDefinitionsInput =
|
|
|
61
61
|
| ChatToolDefinition[]
|
|
62
62
|
| ((ctx: SystemPromptContext) => ChatToolDefinition[] | Promise<ChatToolDefinition[]>);
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Tool handlers for an agent. Either a static map (the conventional shape) or a
|
|
66
|
+
* function resolved each tool-loop iteration. The function form lets the agent
|
|
67
|
+
* narrow the dispatchable handler set per turn — pair it with the function form
|
|
68
|
+
* of `toolDefinitions` so the LLM-visible tools and the dispatchable handlers
|
|
69
|
+
* stay in lockstep, and handlers don't have to defend themselves against being
|
|
70
|
+
* dispatched in states where their tool isn't advertised.
|
|
71
|
+
*
|
|
72
|
+
* @beta
|
|
73
|
+
*/
|
|
74
|
+
export type ToolHandlersInput =
|
|
75
|
+
| ChatToolHandlers
|
|
76
|
+
| ((ctx: SystemPromptContext) => ChatToolHandlers | Promise<ChatToolHandlers>);
|
|
77
|
+
|
|
64
78
|
/**
|
|
65
79
|
* Opts an agent in to manual selection from the assistant's agent picker.
|
|
66
80
|
*
|
|
@@ -83,9 +97,21 @@ export interface ManualSelectionConfig {
|
|
|
83
97
|
|
|
84
98
|
interface BaseAgentConfig {
|
|
85
99
|
/**
|
|
86
|
-
*
|
|
100
|
+
* Stable identity for this agent. Used for classifier routing, manual
|
|
101
|
+
* pinning, and history filtering — must not vary per turn. For a per-turn
|
|
102
|
+
* display label (e.g. "Guided Booking (Counterparties)"), supply
|
|
103
|
+
* {@link BaseAgentConfig.displayName}.
|
|
87
104
|
*/
|
|
88
105
|
name: string;
|
|
106
|
+
/**
|
|
107
|
+
* Optional per-turn display label. Resolved each tool-loop iteration and
|
|
108
|
+
* stamped onto outgoing messages (`agentLabel`) and the debug-log timeline.
|
|
109
|
+
* Renderers fall back to `name` when this is unset. Use the function form
|
|
110
|
+
* to vary the label by current state (e.g. a state machine's step).
|
|
111
|
+
*
|
|
112
|
+
* Identity stays on `name` — this is for UX only.
|
|
113
|
+
*/
|
|
114
|
+
displayName?: SystemPromptInput;
|
|
89
115
|
/**
|
|
90
116
|
* System prompt injected into every conversation turn for this agent.
|
|
91
117
|
*
|
|
@@ -105,8 +131,13 @@ interface BaseAgentConfig {
|
|
|
105
131
|
toolDefinitions?: ToolDefinitionsInput;
|
|
106
132
|
/**
|
|
107
133
|
* Tool handler implementations for this agent.
|
|
134
|
+
*
|
|
135
|
+
* Either a static map or a function resolved each tool-loop iteration —
|
|
136
|
+
* pick the function form to narrow the dispatchable handler set per turn,
|
|
137
|
+
* matching the function form of `toolDefinitions`.
|
|
138
|
+
* See {@link ToolHandlersInput}.
|
|
108
139
|
*/
|
|
109
|
-
toolHandlers?:
|
|
140
|
+
toolHandlers?: ToolHandlersInput;
|
|
110
141
|
/**
|
|
111
142
|
* Optional primer history prepended to every call (not visible to the user).
|
|
112
143
|
* Used to establish agent identity and behavioural rules.
|
|
@@ -6,9 +6,23 @@ import type {
|
|
|
6
6
|
ChatInputDuringExecutionMode,
|
|
7
7
|
ManualSelectionConfig,
|
|
8
8
|
SystemPromptContext,
|
|
9
|
+
SystemPromptInput,
|
|
9
10
|
ToolDefinitionsInput,
|
|
11
|
+
ToolHandlersInput,
|
|
10
12
|
} from './config';
|
|
11
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Context passed to per-turn resolvers on a stateful agent — the standard
|
|
16
|
+
* {@link SystemPromptContext} plus the live `state` value. Used by
|
|
17
|
+
* `systemPrompt`, `displayName`, and the function form of `toolDefinitions`.
|
|
18
|
+
*
|
|
19
|
+
* Exported so consumers can lift resolvers into separate files without
|
|
20
|
+
* re-deriving the shape.
|
|
21
|
+
*
|
|
22
|
+
* @beta
|
|
23
|
+
*/
|
|
24
|
+
export type StatefulAgentContext<S> = SystemPromptContext & { state: S };
|
|
25
|
+
|
|
12
26
|
/**
|
|
13
27
|
* Init options for {@link defineStatefulAgent}. Generic over the state shape `S`
|
|
14
28
|
* the agent owns (a state machine, an observable controller, anything).
|
|
@@ -57,7 +71,15 @@ export interface StatefulAgentInit<S> {
|
|
|
57
71
|
* iteration. Use this to feed the LLM whatever the current state implies
|
|
58
72
|
* (e.g. a state machine's `meta.systemPrompt` plus captured context).
|
|
59
73
|
*/
|
|
60
|
-
systemPrompt?: (ctx:
|
|
74
|
+
systemPrompt?: (ctx: StatefulAgentContext<S>) => string | Promise<string>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Per-turn display label, e.g. "Guided Booking (Counterparties)". Resolved
|
|
78
|
+
* each tool-loop iteration and stamped onto outgoing messages and the
|
|
79
|
+
* debug-log timeline — display only. The agent's `name` stays as the
|
|
80
|
+
* canonical identity used for routing/history filtering.
|
|
81
|
+
*/
|
|
82
|
+
displayName?: (ctx: StatefulAgentContext<S>) => string | Promise<string>;
|
|
61
83
|
|
|
62
84
|
/**
|
|
63
85
|
* Tool definitions the LLM sees. Either a static array (resolved once) or a
|
|
@@ -72,21 +94,27 @@ export interface StatefulAgentInit<S> {
|
|
|
72
94
|
*/
|
|
73
95
|
toolDefinitions?:
|
|
74
96
|
| ChatToolDefinition[]
|
|
75
|
-
| ((
|
|
76
|
-
ctx: SystemPromptContext & { state: S },
|
|
77
|
-
) => ChatToolDefinition[] | Promise<ChatToolDefinition[]>);
|
|
97
|
+
| ((ctx: StatefulAgentContext<S>) => ChatToolDefinition[] | Promise<ChatToolDefinition[]>);
|
|
78
98
|
|
|
79
99
|
/**
|
|
80
|
-
* Factory returning the
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
100
|
+
* Factory returning the handler map for the **current state**. Called each
|
|
101
|
+
* tool-loop iteration with the live `state` value, so the handler set the
|
|
102
|
+
* driver dispatches against matches what `toolDefinitions` exposes to the
|
|
103
|
+
* LLM that turn. Return only the handlers valid right now — no need to
|
|
104
|
+
* advertise every handler the agent might ever expose, and no defensive
|
|
105
|
+
* `if (!machine.matches(...))` guards inside each handler.
|
|
106
|
+
*
|
|
107
|
+
* Pair with the function form of `toolDefinitions` so the visible tools and
|
|
108
|
+
* the dispatchable handlers stay in lockstep.
|
|
84
109
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
110
|
+
* **Constraint:** resolved handlers must not include fold facades. Folds and
|
|
111
|
+
* state-machine-driven tool filtering both try to control the LLM's tool
|
|
112
|
+
* view — pick one. Helper samples once on activation (init state) and
|
|
113
|
+
* throws if a fold-tagged handler is detected; subsequent resolves also
|
|
114
|
+
* validate, so misuse on a non-init state surfaces when that state is
|
|
115
|
+
* reached.
|
|
88
116
|
*/
|
|
89
|
-
toolHandlers?: (state: S) => ChatToolHandlers
|
|
117
|
+
toolHandlers?: (state: S) => ChatToolHandlers | Promise<ChatToolHandlers>;
|
|
90
118
|
|
|
91
119
|
/**
|
|
92
120
|
* Optional getter for the debug-log snapshot. Defaults to auto-snapshotting
|
|
@@ -163,7 +191,6 @@ function defaultStatefulDebugSnapshot(state: unknown): unknown {
|
|
|
163
191
|
*/
|
|
164
192
|
export function defineStatefulAgent<S>(opts: StatefulAgentInit<S>): AgentConfig {
|
|
165
193
|
let state: S | undefined;
|
|
166
|
-
let cachedHandlers: ChatToolHandlers | undefined;
|
|
167
194
|
|
|
168
195
|
const assertNoFolds = (handlers: ChatToolHandlers): void => {
|
|
169
196
|
for (const [name, handler] of Object.entries(handlers)) {
|
|
@@ -194,62 +221,51 @@ export function defineStatefulAgent<S>(opts: StatefulAgentInit<S>): AgentConfig
|
|
|
194
221
|
return td;
|
|
195
222
|
})();
|
|
196
223
|
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (!state || !cachedHandlers) {
|
|
207
|
-
throw new Error(`Stateful agent "${opts.name}" handler called before init`);
|
|
208
|
-
}
|
|
209
|
-
const handler = cachedHandlers[name];
|
|
210
|
-
if (!handler) {
|
|
211
|
-
throw new Error(`Tool "${name}" has no handler on stateful agent "${opts.name}"`);
|
|
224
|
+
// Wrap the handler factory into the ctx-shaped form the driver expects, and
|
|
225
|
+
// validate folds on each resolve. The driver re-resolves this per tool-loop
|
|
226
|
+
// iteration, so the handler set the driver dispatches against always
|
|
227
|
+
// matches the LLM-visible tools for the current state — handlers don't need
|
|
228
|
+
// defensive `if (!machine.matches(...))` guards.
|
|
229
|
+
const wrappedHandlers: ToolHandlersInput | undefined = opts.toolHandlers
|
|
230
|
+
? async () => {
|
|
231
|
+
if (!state) {
|
|
232
|
+
throw new Error(`Stateful agent "${opts.name}" handlers called before init`);
|
|
212
233
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// Static-tools case: handler names are knowable from the definitions array.
|
|
220
|
-
// Dynamic-tools case: we discover names from a sample of `toolHandlers(state)`
|
|
221
|
-
// taken inside onActivate. Until then, the handler dict is empty — fine
|
|
222
|
-
// because no tool call can happen before activation completes.
|
|
223
|
-
const staticHandlerProxy =
|
|
224
|
-
Array.isArray(opts.toolDefinitions) && opts.toolHandlers
|
|
225
|
-
? buildHandlerProxy(opts.toolDefinitions.map((d) => d.name))
|
|
226
|
-
: undefined;
|
|
234
|
+
const handlers = await opts.toolHandlers!(state);
|
|
235
|
+
assertNoFolds(handlers);
|
|
236
|
+
return handlers;
|
|
237
|
+
}
|
|
238
|
+
: undefined;
|
|
227
239
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
240
|
+
const wrappedDisplayName: SystemPromptInput | undefined = opts.displayName
|
|
241
|
+
? async (ctx: SystemPromptContext) => {
|
|
242
|
+
if (!state) {
|
|
243
|
+
throw new Error(`Stateful agent "${opts.name}" displayName called before init`);
|
|
244
|
+
}
|
|
245
|
+
return opts.displayName!({ ...ctx, state });
|
|
246
|
+
}
|
|
247
|
+
: undefined;
|
|
231
248
|
|
|
232
249
|
const base = {
|
|
233
250
|
name: opts.name,
|
|
251
|
+
displayName: wrappedDisplayName,
|
|
234
252
|
primerHistory: opts.primerHistory,
|
|
235
253
|
subAgents: opts.subAgents,
|
|
236
254
|
manualSelection: opts.manualSelection,
|
|
237
255
|
chatInputDuringExecution: opts.chatInputDuringExecution,
|
|
238
256
|
toolDefinitions: wrappedTools,
|
|
239
|
-
toolHandlers:
|
|
257
|
+
toolHandlers: wrappedHandlers,
|
|
240
258
|
|
|
241
259
|
onActivate: async (ctx: AgentLifecycleContext) => {
|
|
242
260
|
state = await opts.init(ctx);
|
|
243
|
-
// Sample
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
for (const key of Object.keys(resolvedHandlers)) delete resolvedHandlers[key];
|
|
252
|
-
Object.assign(resolvedHandlers, dynamicProxy);
|
|
261
|
+
// Sample once with the init state to fail loud on fold misuse at the
|
|
262
|
+
// agent-switch boundary instead of waiting for the first tool call.
|
|
263
|
+
// The driver re-resolves the factory each iteration, so misuse on
|
|
264
|
+
// handlers gated behind a non-init state surfaces when that state is
|
|
265
|
+
// reached — same loud failure mode, narrower coverage.
|
|
266
|
+
if (opts.toolHandlers) {
|
|
267
|
+
const sampled = await opts.toolHandlers(state);
|
|
268
|
+
assertNoFolds(sampled);
|
|
253
269
|
}
|
|
254
270
|
},
|
|
255
271
|
|
|
@@ -261,7 +277,6 @@ export function defineStatefulAgent<S>(opts: StatefulAgentInit<S>): AgentConfig
|
|
|
261
277
|
// cannot interleave the read-then-write of `state`.
|
|
262
278
|
// eslint-disable-next-line require-atomic-updates
|
|
263
279
|
state = undefined;
|
|
264
|
-
cachedHandlers = undefined;
|
|
265
280
|
},
|
|
266
281
|
|
|
267
282
|
systemPrompt: opts.systemPrompt
|
|
@@ -233,7 +233,7 @@ ${(tc) => (tc.foldPath?.length ? `${tc.foldPath.join(' › ')} › ` : '')}<stro
|
|
|
233
233
|
messageType(m) === 'ai-function' &&
|
|
234
234
|
m.agentName &&
|
|
235
235
|
(c.parent as FoundationAiAssistant).showAgentSwitchIndicator
|
|
236
|
-
? `Tool Call · ${m.agentName}`
|
|
236
|
+
? `Tool Call · ${m.agentLabel ?? m.agentName}`
|
|
237
237
|
: senderLabel[messageType(m)]}
|
|
238
238
|
</div>
|
|
239
239
|
<div class="content">
|
package/src/main/main.ts
CHANGED
|
@@ -113,9 +113,10 @@ avoidTreeShaking(
|
|
|
113
113
|
* Recursively strips non-serializable fields from an agent before storing in Redux:
|
|
114
114
|
* - `toolHandlers` (functions),
|
|
115
115
|
* - `onActivate` / `onDeactivate` (lifecycle hooks, functions),
|
|
116
|
-
* -
|
|
117
|
-
*
|
|
118
|
-
*
|
|
116
|
+
* - `getDebugSnapshot` (function),
|
|
117
|
+
* - function-form `systemPrompt` / `toolDefinitions` / `displayName` (downgraded
|
|
118
|
+
* to `undefined` in the snapshot — the live config on the driver is still
|
|
119
|
+
* the source of truth; the slice only stores a serializable projection).
|
|
119
120
|
*/
|
|
120
121
|
function stripHandlers(agent: AgentConfig): Omit<AgentConfig, 'toolHandlers'> {
|
|
121
122
|
const {
|
|
@@ -126,12 +127,14 @@ function stripHandlers(agent: AgentConfig): Omit<AgentConfig, 'toolHandlers'> {
|
|
|
126
127
|
subAgents,
|
|
127
128
|
systemPrompt,
|
|
128
129
|
toolDefinitions,
|
|
130
|
+
displayName,
|
|
129
131
|
...rest
|
|
130
132
|
} = agent;
|
|
131
133
|
const stripped = {
|
|
132
134
|
...rest,
|
|
133
135
|
systemPrompt: typeof systemPrompt === 'function' ? undefined : systemPrompt,
|
|
134
136
|
toolDefinitions: typeof toolDefinitions === 'function' ? undefined : toolDefinitions,
|
|
137
|
+
displayName: typeof displayName === 'function' ? undefined : displayName,
|
|
135
138
|
};
|
|
136
139
|
return subAgents?.length
|
|
137
140
|
? { ...stripped, subAgents: subAgents.map(stripHandlers) as AgentConfig[] }
|
|
@@ -544,9 +547,16 @@ export class FoundationAiAssistant extends GenesisElement {
|
|
|
544
547
|
if (!agents?.length) return '';
|
|
545
548
|
return agents
|
|
546
549
|
.map((a) => {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
+
// Function-form handlers are resolved per turn — their full key set
|
|
551
|
+
// isn't knowable at fingerprint time. A constant marker still
|
|
552
|
+
// distinguishes static-vs-dynamic shape changes; finer-grained
|
|
553
|
+
// detection isn't load-bearing here.
|
|
554
|
+
const toolNames =
|
|
555
|
+
typeof a.toolHandlers === 'function'
|
|
556
|
+
? '<fn>'
|
|
557
|
+
: Object.keys(a.toolHandlers ?? {})
|
|
558
|
+
.sort()
|
|
559
|
+
.join('+');
|
|
550
560
|
return `${a.name}[${toolNames}]`;
|
|
551
561
|
})
|
|
552
562
|
.join(',');
|
|
@@ -1170,7 +1180,11 @@ export class FoundationAiAssistant extends GenesisElement {
|
|
|
1170
1180
|
agentSummary: this.agents?.map((a) => ({
|
|
1171
1181
|
...a,
|
|
1172
1182
|
toolDefinitions: Array.isArray(a.toolDefinitions)
|
|
1173
|
-
?
|
|
1183
|
+
? typeof a.toolHandlers === 'function'
|
|
1184
|
+
? // Static defs + dynamic handlers — can't walk fold tree
|
|
1185
|
+
// because the handler map isn't materialized at log time.
|
|
1186
|
+
a.toolDefinitions
|
|
1187
|
+
: expandToolTree(a.toolDefinitions, a.toolHandlers ?? {})
|
|
1174
1188
|
: typeof a.toolDefinitions === 'function'
|
|
1175
1189
|
? '<dynamic — resolved per turn>'
|
|
1176
1190
|
: [],
|