@genesislcap/ai-assistant 15.24.2 → 15.26.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/dist/ai-assistant.api.json +1065 -33
- package/dist/ai-assistant.d.ts +288 -102
- package/dist/chat-driver.cjs +630 -378
- package/dist/chat-driver.cjs.map +4 -4
- package/dist/chat-driver.mjs +630 -378
- package/dist/chat-driver.mjs.map +4 -4
- package/dist/custom-elements.json +1198 -388
- package/dist/dts/components/agent-session/agent-session.d.ts +134 -0
- package/dist/dts/components/agent-session/agent-session.d.ts.map +1 -0
- package/dist/dts/components/agent-session/index.d.ts +2 -0
- package/dist/dts/components/agent-session/index.d.ts.map +1 -0
- package/dist/dts/components/ai-driver/ai-driver.d.ts +31 -4
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.constants.d.ts +52 -0
- package/dist/dts/components/chat-driver/chat-driver.constants.d.ts.map +1 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts +100 -82
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/fold-controller.d.ts +82 -0
- package/dist/dts/components/chat-driver/fold-controller.d.ts.map +1 -0
- package/dist/dts/components/chat-driver/turn-failures.d.ts +20 -1
- package/dist/dts/components/chat-driver/turn-failures.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +14 -24
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/index.d.ts +1 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +6 -16
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/suggestions/generate-suggestions.d.ts +35 -0
- package/dist/dts/suggestions/generate-suggestions.d.ts.map +1 -0
- package/dist/dts/utils/agents-key.d.ts +10 -0
- package/dist/dts/utils/agents-key.d.ts.map +1 -0
- package/dist/dts/utils/resolve-turn-input.d.ts +9 -0
- package/dist/dts/utils/resolve-turn-input.d.ts.map +1 -0
- package/dist/esm/components/agent-session/agent-session.js +208 -0
- package/dist/esm/components/agent-session/index.js +1 -0
- package/dist/esm/components/chat-driver/chat-driver.constants.js +72 -0
- package/dist/esm/components/chat-driver/chat-driver.js +190 -395
- package/dist/esm/components/chat-driver/fold-controller.js +184 -0
- package/dist/esm/components/chat-driver/turn-failures.js +14 -0
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +48 -131
- package/dist/esm/index.js +1 -0
- package/dist/esm/main/main.js +11 -38
- package/dist/esm/suggestions/generate-suggestions.js +123 -0
- package/dist/esm/utils/agents-key.js +24 -0
- package/dist/esm/utils/resolve-turn-input.js +16 -0
- package/docs/migration-GENC-1580.md +44 -36
- package/package.json +16 -16
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { __awaiter } from "tslib";
|
|
2
|
+
import { logger } from '../../utils/logger';
|
|
3
|
+
import { TOOL_FOLD_SYMBOL } from '../../utils/tool-fold';
|
|
4
|
+
/** Read `ToolFold` metadata off a handler, or `undefined` if it is not a fold facade. */
|
|
5
|
+
const foldOf = (handler) => handler
|
|
6
|
+
? handler[TOOL_FOLD_SYMBOL]
|
|
7
|
+
: undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Tool folds: grouping tools behind a facade so the model sees a small surface, and swapping the
|
|
10
|
+
* surface as it opens and closes them.
|
|
11
|
+
*
|
|
12
|
+
* Owns the fold stack and the no-progress counter. Everything else it needs is the driver's tool
|
|
13
|
+
* surface, reached through {@link FoldDeps}.
|
|
14
|
+
*/
|
|
15
|
+
export class FoldController {
|
|
16
|
+
constructor(deps) {
|
|
17
|
+
this.deps = deps;
|
|
18
|
+
this.stack = [];
|
|
19
|
+
/**
|
|
20
|
+
* Consecutive fold opens/closes with no tool call between them. A model that only opens and
|
|
21
|
+
* closes is making no progress, and the loop breaks out past a ceiling.
|
|
22
|
+
*/
|
|
23
|
+
this.consecutiveOps = 0;
|
|
24
|
+
}
|
|
25
|
+
/** Drop all fold state. Called on an agent swap — each agent starts outside every fold. */
|
|
26
|
+
reset() {
|
|
27
|
+
this.stack = [];
|
|
28
|
+
this.consecutiveOps = 0;
|
|
29
|
+
}
|
|
30
|
+
/** Names of the currently open folds, outermost first. */
|
|
31
|
+
activeNames() {
|
|
32
|
+
return this.stack.map((f) => f.foldName);
|
|
33
|
+
}
|
|
34
|
+
get depth() {
|
|
35
|
+
return this.stack.length;
|
|
36
|
+
}
|
|
37
|
+
/** The innermost open fold's name, or `undefined` when none is open. */
|
|
38
|
+
get topName() {
|
|
39
|
+
var _a;
|
|
40
|
+
return (_a = this.stack[this.stack.length - 1]) === null || _a === void 0 ? void 0 : _a.foldName;
|
|
41
|
+
}
|
|
42
|
+
/** Count one fold operation and return the new consecutive total. */
|
|
43
|
+
noteOp() {
|
|
44
|
+
this.consecutiveOps += 1;
|
|
45
|
+
return this.consecutiveOps;
|
|
46
|
+
}
|
|
47
|
+
/** Real work happened, so the no-progress streak restarts. */
|
|
48
|
+
clearOps() {
|
|
49
|
+
this.consecutiveOps = 0;
|
|
50
|
+
}
|
|
51
|
+
/** Fold metadata for a tool name, or `undefined` if it is not a fold facade. */
|
|
52
|
+
foldFor(toolName) {
|
|
53
|
+
return foldOf(this.deps.getHandlers()[toolName]);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Which fold to **open** to reach `toolName`, or `null` if no registered fold contains it.
|
|
57
|
+
*
|
|
58
|
+
* Returns the outermost *registered* ancestor, not the innermost fold holding the tool — the one
|
|
59
|
+
* consumer uses the answer to tell the model what to call next, and a nested fold cannot be
|
|
60
|
+
* opened before its parent. The recursion therefore only descends through closed folds: once a
|
|
61
|
+
* fold is open its children are top-level in the live handler map and match directly.
|
|
62
|
+
*/
|
|
63
|
+
findContaining(toolName, handlers) {
|
|
64
|
+
const source = handlers !== null && handlers !== void 0 ? handlers : this.deps.getHandlers();
|
|
65
|
+
for (const handler of Object.values(source)) {
|
|
66
|
+
const fold = foldOf(handler);
|
|
67
|
+
if (!fold)
|
|
68
|
+
continue;
|
|
69
|
+
if (fold.handlers[toolName])
|
|
70
|
+
return fold.name;
|
|
71
|
+
if (this.findContaining(toolName, fold.handlers))
|
|
72
|
+
return fold.name;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* If an open fold is hiding a previously-available tool, the fold to close to start getting it
|
|
78
|
+
* back, else `null`.
|
|
79
|
+
*
|
|
80
|
+
* Only exclusive folds hide tools — they replace the tool set on open rather than extending it —
|
|
81
|
+
* so a base tool that was visible beforehand sits in a frame's `previousHandlers` but not in the
|
|
82
|
+
* live map. Only the top fold's `close_` tool is active, so that is always the actionable step:
|
|
83
|
+
* closing repeatedly walks back to a tool further down the stack.
|
|
84
|
+
*/
|
|
85
|
+
hidingTool(toolName) {
|
|
86
|
+
if (this.stack.length === 0)
|
|
87
|
+
return null;
|
|
88
|
+
const hidden = this.stack.some((f) => f.previousHandlers[toolName]);
|
|
89
|
+
return hidden ? this.topName : null;
|
|
90
|
+
}
|
|
91
|
+
/** Open a fold: push a frame, swap the tool set, and return the response for the model. */
|
|
92
|
+
open(foldName, fold, args, activeToolCallId) {
|
|
93
|
+
// Shortcut dispatch: the model passed inner tool args directly, e.g.
|
|
94
|
+
// trading_tools({ search_trades: { side: "BUY" } })
|
|
95
|
+
for (const key of Object.keys(args)) {
|
|
96
|
+
const innerHandler = fold.handlers[key];
|
|
97
|
+
if (innerHandler) {
|
|
98
|
+
logger.debug(`ChatDriver: fold shortcut dispatch "${foldName}" → "${key}"`);
|
|
99
|
+
// Open first, so the tool set is correct for subsequent calls.
|
|
100
|
+
this.pushFrame(foldName);
|
|
101
|
+
this.applyToolSet(fold, foldName);
|
|
102
|
+
this.clearOps(); // shortcut dispatch counts as real work
|
|
103
|
+
const innerArgs = typeof args[key] === 'object' && args[key] !== null
|
|
104
|
+
? args[key]
|
|
105
|
+
: {};
|
|
106
|
+
return innerHandler(innerArgs, this.deps.handlerContext(activeToolCallId)).then((r) => typeof r === 'string' ? r : JSON.stringify(r));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.pushFrame(foldName);
|
|
110
|
+
this.applyToolSet(fold, foldName);
|
|
111
|
+
const closeToolName = `close_${foldName}`;
|
|
112
|
+
const innerToolNames = fold.tools.map((t) => t.name);
|
|
113
|
+
let message = `Fold opened: ${foldName}. Tools now available: ${[...innerToolNames, closeToolName].join(', ')}.`;
|
|
114
|
+
if (fold.usageNotes)
|
|
115
|
+
message += ` Notes: ${fold.usageNotes}`;
|
|
116
|
+
message += ` Call ${closeToolName} when done to return to the previous tools.`;
|
|
117
|
+
return Promise.resolve(message);
|
|
118
|
+
}
|
|
119
|
+
/** Close the top fold: pop the frame, restore the tool set it captured. */
|
|
120
|
+
close() {
|
|
121
|
+
const frame = this.stack.pop();
|
|
122
|
+
if (!frame)
|
|
123
|
+
return 'No fold is currently open.';
|
|
124
|
+
this.deps.setDefinitions(frame.previousDefinitions);
|
|
125
|
+
this.deps.setHandlers(frame.previousHandlers);
|
|
126
|
+
const toolNames = frame.previousDefinitions.map((t) => t.name);
|
|
127
|
+
return `Fold closed: ${frame.foldName}. Tools now available: ${toolNames.join(', ')}.`;
|
|
128
|
+
}
|
|
129
|
+
/** The fold-awareness suffix appended to the system prompt on each LLM call. */
|
|
130
|
+
systemPromptSuffix() {
|
|
131
|
+
const activeFolds = Object.values(this.deps.getHandlers())
|
|
132
|
+
.map(foldOf)
|
|
133
|
+
.filter((f) => !!f);
|
|
134
|
+
if (activeFolds.length === 0 && this.stack.length === 0)
|
|
135
|
+
return '';
|
|
136
|
+
const parts = [
|
|
137
|
+
'\n\n--- Tool Folds ---',
|
|
138
|
+
'Some tools are grouped into folds. You may see tool calls in the conversation history for tools that are not currently available — they are inside a fold. To access them, invoke the fold tool first.',
|
|
139
|
+
];
|
|
140
|
+
if (this.topName) {
|
|
141
|
+
parts.push(`You are currently inside the "${this.topName}" fold. Call close_${this.topName} when you are done with these tools.`);
|
|
142
|
+
}
|
|
143
|
+
for (const fold of activeFolds)
|
|
144
|
+
parts.push(`• ${fold.name}: ${fold.description}`);
|
|
145
|
+
return parts.join('\n');
|
|
146
|
+
}
|
|
147
|
+
pushFrame(foldName) {
|
|
148
|
+
this.stack.push({
|
|
149
|
+
foldName,
|
|
150
|
+
previousDefinitions: [...this.deps.getDefinitions()],
|
|
151
|
+
previousHandlers: Object.assign({}, this.deps.getHandlers()),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Install the fold's inner tool set, replacing it (exclusive) or extending it (non-exclusive),
|
|
156
|
+
* and inject the close tool. Does not touch the stack.
|
|
157
|
+
*/
|
|
158
|
+
applyToolSet(fold, foldName) {
|
|
159
|
+
const closeToolName = `close_${foldName}`;
|
|
160
|
+
const newDefs = [];
|
|
161
|
+
const newHandlers = {};
|
|
162
|
+
if (!fold.exclusive) {
|
|
163
|
+
// Keep the existing tools, minus the facade just opened.
|
|
164
|
+
for (const def of this.deps.getDefinitions()) {
|
|
165
|
+
if (def.name !== foldName)
|
|
166
|
+
newDefs.push(def);
|
|
167
|
+
}
|
|
168
|
+
for (const [name, handler] of Object.entries(this.deps.getHandlers())) {
|
|
169
|
+
if (name !== foldName)
|
|
170
|
+
newHandlers[name] = handler;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
newDefs.push(...fold.tools);
|
|
174
|
+
Object.assign(newHandlers, fold.handlers);
|
|
175
|
+
newDefs.push({
|
|
176
|
+
name: closeToolName,
|
|
177
|
+
description: `Close the ${foldName} fold and return to the previous set of tools.`,
|
|
178
|
+
parameters: { type: 'object', properties: {} },
|
|
179
|
+
});
|
|
180
|
+
newHandlers[closeToolName] = () => __awaiter(this, void 0, void 0, function* () { return this.close(); });
|
|
181
|
+
this.deps.setDefinitions(newDefs);
|
|
182
|
+
this.deps.setHandlers(newHandlers);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -82,3 +82,17 @@ export const TURN_FAILURE_EXITS = {
|
|
|
82
82
|
// `TurnFailureReason` and cost the literal types that let `turn-failures.test.ts` pin the
|
|
83
83
|
// two deliberate divergences at compile time instead of at runtime.
|
|
84
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Build the `done` loop result, carrying the typed failure reason when the turn bailed (PTC-0).
|
|
87
|
+
*
|
|
88
|
+
* The discriminant stays `'done'` either way, so consumers matching on `reason === 'done'` are
|
|
89
|
+
* unchanged. Both payloads are omitted rather than set to `undefined`, which keeps a happy-path
|
|
90
|
+
* result byte-identical to `{ reason: 'done' }` for a consumer that structurally compares it.
|
|
91
|
+
*
|
|
92
|
+
* @beta
|
|
93
|
+
*/
|
|
94
|
+
export function turnDone(failureReason, budget, providerRefused) {
|
|
95
|
+
if (!failureReason)
|
|
96
|
+
return { reason: 'done' };
|
|
97
|
+
return Object.assign(Object.assign({ reason: 'done', failureReason }, (budget ? { budget } : {})), (providerRefused ? { providerRefused } : {}));
|
|
98
|
+
}
|
|
@@ -149,16 +149,33 @@ function buildFallbackSystemPrompt(fallback, specialists) {
|
|
|
149
149
|
* @beta
|
|
150
150
|
*/
|
|
151
151
|
export class OrchestratingDriver extends EventTarget {
|
|
152
|
+
/**
|
|
153
|
+
* The stateful-agent contract (§7.1): active agent, user pin, flow lock, restored state, and
|
|
154
|
+
* the `onActivate` / `onDeactivate` hooks.
|
|
155
|
+
*
|
|
156
|
+
* **The inner driver's session, not one of our own.** One session is the invariant: two would
|
|
157
|
+
* let the outer and inner driver disagree about which agent is active, and the inner one would
|
|
158
|
+
* never be activated at all, because this driver installs a *decorated* config through
|
|
159
|
+
* `applyAgent` rather than going via `ChatDriver.activateAgent`. This driver keeps the routing
|
|
160
|
+
* decisions; the session owns the lifecycle.
|
|
161
|
+
*/
|
|
162
|
+
get agentSession() {
|
|
163
|
+
return this.chatDriver.agentSession;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The active agent, or `undefined` before the first activation and after a release.
|
|
167
|
+
*
|
|
168
|
+
* Kept as an accessor over `agentSession` rather than removed: it is a public member of an
|
|
169
|
+
* exported class, so nothing in this package reading it is not the same as nothing reading it.
|
|
170
|
+
*/
|
|
171
|
+
get activeAgent() {
|
|
172
|
+
return this.agentSession.getActive();
|
|
173
|
+
}
|
|
152
174
|
constructor(providerRegistry, agents, options = {}) {
|
|
153
175
|
var _a, _b, _c, _d;
|
|
154
176
|
super();
|
|
155
177
|
this.providerRegistry = providerRegistry;
|
|
156
178
|
this.agents = agents;
|
|
157
|
-
/**
|
|
158
|
-
* Aborted on driver disposal. Threaded into `AgentLifecycleContext.signal`
|
|
159
|
-
* so long-running `onActivate` work can bail if the session disconnects.
|
|
160
|
-
*/
|
|
161
|
-
this.lifecycleAbortController = new AbortController();
|
|
162
179
|
/**
|
|
163
180
|
* Set by `cancel()` for the duration of a `sendMessage` run. Breaks the
|
|
164
181
|
* handoff loop so we don't classify or start another agent turn after the
|
|
@@ -176,28 +193,6 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
176
193
|
* made it vanish. Reset at the top of each `runOrchestratedTurn`.
|
|
177
194
|
*/
|
|
178
195
|
this.userMessageAppended = false;
|
|
179
|
-
/**
|
|
180
|
-
* Sticky user pick from the picker (or the host's `setAgent` API). Only
|
|
181
|
-
* changes on explicit user action. Survives flow completion: when a stateful
|
|
182
|
-
* agent releases, the user-pin stays put and routing falls back to it on
|
|
183
|
-
* the next turn.
|
|
184
|
-
*/
|
|
185
|
-
this.pinnedAgentName = null;
|
|
186
|
-
/**
|
|
187
|
-
* Transient flow lock — set when a stateful agent's `onActivate` fires,
|
|
188
|
-
* cleared by `releaseActiveAgent`. While non-null, routing precedence goes
|
|
189
|
-
* to this agent and the picker is locked. Independent of
|
|
190
|
-
* `pinnedAgentName` so completing a flow doesn't wipe the user's sticky
|
|
191
|
-
* choice.
|
|
192
|
-
*/
|
|
193
|
-
this.flowOwnerAgentName = null;
|
|
194
|
-
/**
|
|
195
|
-
* Per-agent restored state for a rehydrated session, keyed by agent name.
|
|
196
|
-
* Primed by the host on restore; each entry is passed to that agent's
|
|
197
|
-
* `onActivate` as `ctx.restored` on its next activation, then deleted so a
|
|
198
|
-
* later re-activation starts fresh.
|
|
199
|
-
*/
|
|
200
|
-
this.restoredAgentStates = {};
|
|
201
196
|
this.sessionKey = (_a = options.sessionKey) !== null && _a !== void 0 ? _a : '';
|
|
202
197
|
this.maxHandoffs = (_b = options.maxHandoffs) !== null && _b !== void 0 ? _b : DEFAULT_MAX_HANDOFFS;
|
|
203
198
|
this.classifierHistoryLength =
|
|
@@ -224,6 +219,8 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
224
219
|
sessionKey: this.sessionKey,
|
|
225
220
|
activityBus: options.activityBus,
|
|
226
221
|
budgetExhaustedMessage: options.budgetExhaustedMessage,
|
|
222
|
+
// This driver releases after the turn so it can fire the host-facing events; see the option.
|
|
223
|
+
deferAgentRelease: true,
|
|
227
224
|
});
|
|
228
225
|
// Proxy events from the shared driver
|
|
229
226
|
this.chatDriver.addEventListener('history-updated', (e) => {
|
|
@@ -282,7 +279,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
282
279
|
* return to automatic routing.
|
|
283
280
|
*/
|
|
284
281
|
setPinnedAgent(name) {
|
|
285
|
-
this.
|
|
282
|
+
this.agentSession.setPinned(name);
|
|
286
283
|
}
|
|
287
284
|
/**
|
|
288
285
|
* Set the flow-owner lock — used to restore state from Redux on driver
|
|
@@ -291,7 +288,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
291
288
|
* for.
|
|
292
289
|
*/
|
|
293
290
|
setFlowOwner(name) {
|
|
294
|
-
this.
|
|
291
|
+
this.agentSession.setFlowOwner(name);
|
|
295
292
|
}
|
|
296
293
|
loadHistory(messages) {
|
|
297
294
|
this.chatDriver.loadHistory(messages);
|
|
@@ -535,7 +532,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
535
532
|
}
|
|
536
533
|
/** {@inheritDoc AiDriver.primeRestoredAgentState} */
|
|
537
534
|
primeRestoredAgentState(snapshots) {
|
|
538
|
-
this.
|
|
535
|
+
this.agentSession.primeRestoredStates(snapshots);
|
|
539
536
|
}
|
|
540
537
|
/**
|
|
541
538
|
* {@inheritDoc AiDriver.getContextReserve}
|
|
@@ -593,69 +590,16 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
593
590
|
}
|
|
594
591
|
/** {@inheritDoc AiDriver.getActiveAgent} */
|
|
595
592
|
getActiveAgent() {
|
|
596
|
-
return this.
|
|
593
|
+
return this.agentSession.getActive();
|
|
597
594
|
}
|
|
598
595
|
applyAgent(agent) {
|
|
599
596
|
return __awaiter(this, void 0, void 0, function* () {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
recordMetaEvent(this.sessionKey, 'agent.handoff', {
|
|
607
|
-
from: (_a = previousAgent === null || previousAgent === void 0 ? void 0 : previousAgent.name) !== null && _a !== void 0 ? _a : null,
|
|
608
|
-
to: agent.name,
|
|
609
|
-
// GENC-1351: was a restored snapshot waiting for this agent at activation?
|
|
610
|
-
restored: this.restoredAgentStates[agent.name] !== undefined,
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
// Fire lifecycle hooks around the swap — outgoing first, then incoming.
|
|
614
|
-
// Both are awaited so a heavy `onActivate` (e.g. machine restore) completes
|
|
615
|
-
// before the agent's first turn runs.
|
|
616
|
-
if (isSwitch && (previousAgent === null || previousAgent === void 0 ? void 0 : previousAgent.onDeactivate)) {
|
|
617
|
-
try {
|
|
618
|
-
yield previousAgent.onDeactivate({
|
|
619
|
-
agentName: previousAgent.name,
|
|
620
|
-
sessionKey: this.sessionKey,
|
|
621
|
-
signal: this.lifecycleAbortController.signal,
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
catch (e) {
|
|
625
|
-
logger.warn(`OrchestratingDriver: onDeactivate("${previousAgent.name}") threw:`, e);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
if (isSwitch && agent.onActivate) {
|
|
629
|
-
// On a session restore, hand this agent its persisted state exactly once
|
|
630
|
-
// (consumed here) so a stateful machine resumes at the right step; a later
|
|
631
|
-
// re-activation of the same agent then starts fresh.
|
|
632
|
-
const restored = this.restoredAgentStates[agent.name];
|
|
633
|
-
if (restored !== undefined)
|
|
634
|
-
delete this.restoredAgentStates[agent.name];
|
|
635
|
-
try {
|
|
636
|
-
yield agent.onActivate({
|
|
637
|
-
agentName: agent.name,
|
|
638
|
-
sessionKey: this.sessionKey,
|
|
639
|
-
previousAgentName: previousAgent === null || previousAgent === void 0 ? void 0 : previousAgent.name,
|
|
640
|
-
signal: this.lifecycleAbortController.signal,
|
|
641
|
-
restored,
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
catch (e) {
|
|
645
|
-
logger.warn(`OrchestratingDriver: onActivate("${agent.name}") threw:`, e);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
const hasLifecycleHooks = !!(agent.onActivate || agent.onDeactivate);
|
|
649
|
-
// Stateful agents auto-lock the flow on activation. The lock guarantees
|
|
650
|
-
// the machine survives subsequent turns (the classifier would otherwise be
|
|
651
|
-
// free to route away mid-flow, tearing the machine down). Release happens
|
|
652
|
-
// when the agent calls `releaseAgent` from a terminal-state tool handler
|
|
653
|
-
// — see the post-sendMessage check below. We write `flowOwnerAgentName`
|
|
654
|
-
// rather than `pinnedAgentName` so completing a flow doesn't wipe the
|
|
655
|
-
// user's sticky picker choice.
|
|
656
|
-
if (isSwitch && hasLifecycleHooks && this.flowOwnerAgentName !== agent.name) {
|
|
657
|
-
this.flowOwnerAgentName = agent.name;
|
|
658
|
-
this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: agent.name }));
|
|
597
|
+
// The transition — handoff record, lifecycle hooks, restored state, flow lock — belongs to
|
|
598
|
+
// the session. What is left below is this driver's own job: what the agent may route to, and
|
|
599
|
+
// what the transcript says happened.
|
|
600
|
+
const { previousAgent, flowOwnerChangedTo } = yield this.agentSession.activate(agent);
|
|
601
|
+
if (flowOwnerChangedTo !== undefined) {
|
|
602
|
+
this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: flowOwnerChangedTo }));
|
|
659
603
|
}
|
|
660
604
|
// Terminal agents do not get the cross-agent handoff tool. Four cases:
|
|
661
605
|
// • fallback — already a leaf; handoff would loop
|
|
@@ -667,9 +611,9 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
667
611
|
// classifier mid-machine. Capture the tool loop until the
|
|
668
612
|
// user (or the agent itself, via `releaseAgent`) releases.
|
|
669
613
|
const isTerminal = isFallback(agent) ||
|
|
670
|
-
this.
|
|
671
|
-
this.
|
|
672
|
-
|
|
614
|
+
this.agentSession.getPinned() !== null ||
|
|
615
|
+
this.agentSession.getFlowOwner() !== null ||
|
|
616
|
+
!!(agent.onActivate || agent.onDeactivate);
|
|
673
617
|
let agentToApply = agent;
|
|
674
618
|
if (!isTerminal) {
|
|
675
619
|
const declaredTools = agent.toolDefinitions;
|
|
@@ -691,7 +635,6 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
691
635
|
}
|
|
692
636
|
this.chatDriver.setProviderHistoryTransform((h) => transformHistoryForAgent(h, agent.name));
|
|
693
637
|
this.chatDriver.applyAgent(agentToApply);
|
|
694
|
-
this.activeAgent = agent;
|
|
695
638
|
this.dispatchEvent(new CustomEvent('orchestrating-stop'));
|
|
696
639
|
this.dispatchEvent(new CustomEvent('agent-changed', { detail: agent }));
|
|
697
640
|
});
|
|
@@ -709,27 +652,13 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
709
652
|
*/
|
|
710
653
|
releaseActiveAgent() {
|
|
711
654
|
return __awaiter(this, void 0, void 0, function* () {
|
|
712
|
-
const
|
|
713
|
-
if (!
|
|
655
|
+
const released = yield this.agentSession.release();
|
|
656
|
+
if (!released)
|
|
714
657
|
return;
|
|
715
|
-
if (
|
|
716
|
-
|
|
717
|
-
yield agent.onDeactivate({
|
|
718
|
-
agentName: agent.name,
|
|
719
|
-
sessionKey: this.sessionKey,
|
|
720
|
-
signal: this.lifecycleAbortController.signal,
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
catch (e) {
|
|
724
|
-
logger.warn(`OrchestratingDriver: release onDeactivate("${agent.name}") threw:`, e);
|
|
725
|
-
}
|
|
658
|
+
if (released.flowOwnerChangedTo !== undefined) {
|
|
659
|
+
this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: released.flowOwnerChangedTo }));
|
|
726
660
|
}
|
|
727
|
-
this.
|
|
728
|
-
if (this.flowOwnerAgentName !== null) {
|
|
729
|
-
this.flowOwnerAgentName = null;
|
|
730
|
-
this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: null }));
|
|
731
|
-
}
|
|
732
|
-
this.dispatchEvent(new CustomEvent('agent-released', { detail: agent }));
|
|
661
|
+
this.dispatchEvent(new CustomEvent('agent-released', { detail: released.previousAgent }));
|
|
733
662
|
this.dispatchEvent(new CustomEvent('agent-changed', { detail: undefined }));
|
|
734
663
|
});
|
|
735
664
|
}
|
|
@@ -749,23 +678,12 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
749
678
|
*/
|
|
750
679
|
dispose() {
|
|
751
680
|
return __awaiter(this, void 0, void 0, function* () {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
agentName: previousAgent.name,
|
|
757
|
-
sessionKey: this.sessionKey,
|
|
758
|
-
signal: this.lifecycleAbortController.signal,
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
catch (e) {
|
|
762
|
-
logger.warn(`OrchestratingDriver: dispose onDeactivate("${previousAgent.name}") threw:`, e);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
this.lifecycleAbortController.abort();
|
|
681
|
+
// Hooks first, then teardown — the order this method has always had, so an `onDeactivate`
|
|
682
|
+
// still runs against a live inner driver. The session lives on that driver, so disposing it
|
|
683
|
+
// below repeats this call and finds nothing left to do.
|
|
684
|
+
yield this.agentSession.dispose();
|
|
766
685
|
// Tear down the inner driver too, so any in-flight provider request aborts.
|
|
767
|
-
this.chatDriver.dispose();
|
|
768
|
-
this.activeAgent = undefined;
|
|
686
|
+
yield this.chatDriver.dispose();
|
|
769
687
|
});
|
|
770
688
|
}
|
|
771
689
|
/**
|
|
@@ -995,8 +913,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
995
913
|
* — protects against stale state after an agents-array reassignment.
|
|
996
914
|
*/
|
|
997
915
|
resolvePinnedAgent() {
|
|
998
|
-
|
|
999
|
-
const name = (_a = this.flowOwnerAgentName) !== null && _a !== void 0 ? _a : this.pinnedAgentName;
|
|
916
|
+
const name = this.agentSession.getLockedName();
|
|
1000
917
|
if (name === null)
|
|
1001
918
|
return undefined;
|
|
1002
919
|
const match = this.agents.find((a) => a.name === name);
|
package/dist/esm/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export * from './main/main';
|
|
|
2
2
|
export * from './main/main.types';
|
|
3
3
|
export * from './main/main.template';
|
|
4
4
|
export * from './components/agent-picker';
|
|
5
|
+
export * from './components/agent-session';
|
|
5
6
|
export * from './components/ai-driver';
|
|
6
7
|
export * from './components/chat-driver';
|
|
7
8
|
export * from './components/orchestrating-driver';
|
package/dist/esm/main/main.js
CHANGED
|
@@ -52,6 +52,7 @@ import { lastUserMessageIndex } from '../state/persistence/session-snapshot';
|
|
|
52
52
|
import { getSessionStore, hasSessionStore, deleteSessionStore, } from '../state/session-store';
|
|
53
53
|
import { pinColourAt } from '../styles/ai-colours';
|
|
54
54
|
import { ChatSuggestions } from '../suggestions/chat-suggestions';
|
|
55
|
+
import { agentsKey } from '../utils/agents-key';
|
|
55
56
|
import { resolveExclusiveLoadingStyle } from '../utils/animation-exclusivity';
|
|
56
57
|
import { deleteBankedBaseline } from '../utils/banked-usage-baselines';
|
|
57
58
|
import { estimateContextTokens, isContextMeasurable, } from '../utils/context-tokens';
|
|
@@ -555,7 +556,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
555
556
|
applyDeferredAgentsSwap() {
|
|
556
557
|
if (!this.driver)
|
|
557
558
|
return;
|
|
558
|
-
if (
|
|
559
|
+
if (agentsKey(this.agents) === this._driverAgentsKey)
|
|
559
560
|
return;
|
|
560
561
|
this.agentsChanged();
|
|
561
562
|
}
|
|
@@ -1153,8 +1154,8 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1153
1154
|
/**
|
|
1154
1155
|
* Aggregated USD cost across every chat turn in this session.
|
|
1155
1156
|
*
|
|
1156
|
-
* Read-only
|
|
1157
|
-
*
|
|
1157
|
+
* Read-only: a projection of `sessionUsage`, so an assigned figure would be overwritten by
|
|
1158
|
+
* the next transcript change.
|
|
1158
1159
|
*/
|
|
1159
1160
|
get sessionCostUsd() {
|
|
1160
1161
|
return this.sessionUsage.costUsd;
|
|
@@ -1414,14 +1415,9 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1414
1415
|
this.settings.costHistorySlotted = value;
|
|
1415
1416
|
}
|
|
1416
1417
|
/**
|
|
1417
|
-
* This element's driver as an orchestrator, or `undefined` when the session is
|
|
1418
|
-
*
|
|
1419
|
-
*
|
|
1420
|
-
* The five call sites that used to test the type themselves now read this
|
|
1421
|
-
* (§11.2 constraint 3 of `docs/code-health.md`): the agent pin, the flow-owner
|
|
1422
|
-
* lock, the wire-up in `wireDriver`, and the two `driverKind` diagnostics. The
|
|
1423
|
-
* type test itself lives in `asOrchestrator`, shared with the session
|
|
1424
|
-
* persister.
|
|
1418
|
+
* This element's driver as an orchestrator, or `undefined` when the session is running a bare
|
|
1419
|
+
* `ChatDriver`. The only place in this element that narrows the type; the test itself lives in
|
|
1420
|
+
* `asOrchestrator`, shared with the session persister.
|
|
1425
1421
|
*/
|
|
1426
1422
|
get orchestrator() {
|
|
1427
1423
|
return asOrchestrator(this.driver);
|
|
@@ -1544,7 +1540,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1544
1540
|
// This prevents spurious driver teardowns when the collapse-mode element
|
|
1545
1541
|
// connects and its parent re-assigns the same agents array, which would
|
|
1546
1542
|
// destroy pending interactions in the existing driver.
|
|
1547
|
-
const newKey =
|
|
1543
|
+
const newKey = agentsKey(this.agents);
|
|
1548
1544
|
if (newKey === this._driverAgentsKey)
|
|
1549
1545
|
return;
|
|
1550
1546
|
// GENC-1388: a sibling component sharing this session key (e.g. the popped-out
|
|
@@ -1597,26 +1593,6 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1597
1593
|
driverKind: this.orchestrator ? 'orchestrating' : 'chat',
|
|
1598
1594
|
});
|
|
1599
1595
|
}
|
|
1600
|
-
/** Returns a stable fingerprint for an agents array based on agent names and tool handler keys. */
|
|
1601
|
-
getAgentsKey(agents) {
|
|
1602
|
-
if (!(agents === null || agents === void 0 ? void 0 : agents.length))
|
|
1603
|
-
return '';
|
|
1604
|
-
return agents
|
|
1605
|
-
.map((a) => {
|
|
1606
|
-
var _a;
|
|
1607
|
-
// Function-form handlers are resolved per turn — their full key set
|
|
1608
|
-
// isn't knowable at fingerprint time. A constant marker still
|
|
1609
|
-
// distinguishes static-vs-dynamic shape changes; finer-grained
|
|
1610
|
-
// detection isn't load-bearing here.
|
|
1611
|
-
const toolNames = typeof a.toolHandlers === 'function'
|
|
1612
|
-
? '<fn>'
|
|
1613
|
-
: Object.keys((_a = a.toolHandlers) !== null && _a !== void 0 ? _a : {})
|
|
1614
|
-
.sort()
|
|
1615
|
-
.join('+');
|
|
1616
|
-
return `${a.name}[${toolNames}]`;
|
|
1617
|
-
})
|
|
1618
|
-
.join(',');
|
|
1619
|
-
}
|
|
1620
1596
|
/**
|
|
1621
1597
|
* Pure factory — creates a ChatDriver or OrchestratingDriver based on the
|
|
1622
1598
|
* current agent configuration. Does not wire event listeners or register in
|
|
@@ -1927,7 +1903,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1927
1903
|
* `clearSession`.
|
|
1928
1904
|
*/
|
|
1929
1905
|
rebuildDriver(key) {
|
|
1930
|
-
this._driverAgentsKey =
|
|
1906
|
+
this._driverAgentsKey = agentsKey(this.agents);
|
|
1931
1907
|
this.driver = getOrCreateDriver(key, this._driverAgentsKey, () => this.createDriver());
|
|
1932
1908
|
this.wireDriver();
|
|
1933
1909
|
}
|
|
@@ -1967,7 +1943,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1967
1943
|
}
|
|
1968
1944
|
}
|
|
1969
1945
|
const driverExisted = !!getDriver(key);
|
|
1970
|
-
this._driverAgentsKey =
|
|
1946
|
+
this._driverAgentsKey = agentsKey(this.agents);
|
|
1971
1947
|
this.driver = getOrCreateDriver(key, this._driverAgentsKey, () => this.createDriver());
|
|
1972
1948
|
this.wireDriver();
|
|
1973
1949
|
// Auto-restore only for a freshly created store (the page-reload case); an
|
|
@@ -2345,10 +2321,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
2345
2321
|
this.finalizeCostSession();
|
|
2346
2322
|
}
|
|
2347
2323
|
}
|
|
2348
|
-
/**
|
|
2349
|
-
* Runs side effects that were previously in `messagesChanged()`.
|
|
2350
|
-
* Called from the `messages` setter after dispatching to the store.
|
|
2351
|
-
*/
|
|
2324
|
+
/** Transcript side effects, called from the `messages` setter after it dispatches to the store. */
|
|
2352
2325
|
handleMessagesUpdate() {
|
|
2353
2326
|
var _a, _b;
|
|
2354
2327
|
// Reset the loading timer when an assistant message arrives mid-loop so each
|