@genesislcap/ai-assistant 14.434.0 → 14.436.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 +1513 -70
- package/dist/ai-assistant.d.ts +367 -7
- package/dist/dts/components/ai-driver/ai-driver.d.ts +8 -0
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +79 -3
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +23 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +106 -2
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/config/define-stateful-agent.d.ts +115 -0
- package/dist/dts/config/define-stateful-agent.d.ts.map +1 -0
- package/dist/dts/index.d.ts +1 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +36 -4
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/esm/components/chat-driver/chat-driver.js +126 -11
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +192 -33
- package/dist/esm/config/define-stateful-agent.js +174 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/main/main.js +164 -21
- package/dist/esm/main/main.template.js +2 -11
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/components/ai-driver/ai-driver.ts +9 -0
- package/src/components/chat-driver/chat-driver.ts +178 -8
- package/src/components/orchestrating-driver/orchestrating-driver.ts +191 -17
- package/src/config/config.ts +112 -2
- package/src/config/define-stateful-agent.ts +293 -0
- package/src/index.ts +1 -0
- package/src/main/main.template.ts +2 -9
- package/src/main/main.ts +167 -14
package/src/main/main.ts
CHANGED
|
@@ -109,12 +109,33 @@ avoidTreeShaking(
|
|
|
109
109
|
AgentPicker,
|
|
110
110
|
);
|
|
111
111
|
|
|
112
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* Recursively strips non-serializable fields from an agent before storing in Redux:
|
|
114
|
+
* - `toolHandlers` (functions),
|
|
115
|
+
* - `onActivate` / `onDeactivate` (lifecycle hooks, functions),
|
|
116
|
+
* - function-form `systemPrompt` / `toolDefinitions` (downgraded to `undefined`
|
|
117
|
+
* in the snapshot — the live config on the driver is still the source of
|
|
118
|
+
* truth; the slice only stores a serializable projection).
|
|
119
|
+
*/
|
|
113
120
|
function stripHandlers(agent: AgentConfig): Omit<AgentConfig, 'toolHandlers'> {
|
|
114
|
-
const {
|
|
121
|
+
const {
|
|
122
|
+
toolHandlers: _h,
|
|
123
|
+
onActivate: _on,
|
|
124
|
+
onDeactivate: _off,
|
|
125
|
+
getDebugSnapshot: _g,
|
|
126
|
+
subAgents,
|
|
127
|
+
systemPrompt,
|
|
128
|
+
toolDefinitions,
|
|
129
|
+
...rest
|
|
130
|
+
} = agent;
|
|
131
|
+
const stripped = {
|
|
132
|
+
...rest,
|
|
133
|
+
systemPrompt: typeof systemPrompt === 'function' ? undefined : systemPrompt,
|
|
134
|
+
toolDefinitions: typeof toolDefinitions === 'function' ? undefined : toolDefinitions,
|
|
135
|
+
};
|
|
115
136
|
return subAgents?.length
|
|
116
|
-
? { ...
|
|
117
|
-
:
|
|
137
|
+
? { ...stripped, subAgents: subAgents.map(stripHandlers) as AgentConfig[] }
|
|
138
|
+
: stripped;
|
|
118
139
|
}
|
|
119
140
|
|
|
120
141
|
/**
|
|
@@ -504,8 +525,15 @@ export class FoundationAiAssistant extends GenesisElement {
|
|
|
504
525
|
// down a mid-flight driver and creating a fresh idle one.
|
|
505
526
|
if (this.driver.isBusy()) return;
|
|
506
527
|
const history = this.driver.getRawHistory?.() ?? [];
|
|
528
|
+
const oldDriver = this.driver;
|
|
507
529
|
this.unwireDriver();
|
|
508
530
|
deleteDriver(key);
|
|
531
|
+
// Fire the active agent's `onDeactivate` so machines / per-agent state get
|
|
532
|
+
// torn down before the replacement driver instantiates them again. Fire
|
|
533
|
+
// and forget — the old driver is about to be GC'd, errors are non-fatal.
|
|
534
|
+
if (oldDriver && 'dispose' in oldDriver && typeof oldDriver.dispose === 'function') {
|
|
535
|
+
void oldDriver.dispose();
|
|
536
|
+
}
|
|
509
537
|
this.driver = getOrCreateDriver(key, () => this.createDriver());
|
|
510
538
|
this.wireDriver();
|
|
511
539
|
if (history.length) this.driver.loadHistory([...history]);
|
|
@@ -529,30 +557,72 @@ export class FoundationAiAssistant extends GenesisElement {
|
|
|
529
557
|
* current agent configuration. Does not wire event listeners or register in
|
|
530
558
|
* the driver registry.
|
|
531
559
|
*/
|
|
560
|
+
/**
|
|
561
|
+
* Warn at config time if any stateful agent (one with lifecycle hooks) is
|
|
562
|
+
* configured in a way that makes it unreachable: `excludeFromClassifier`
|
|
563
|
+
* removes the classifier path, so the only entry left is manual pinning —
|
|
564
|
+
* which requires both `manualSelection.enabled` on the agent and the picker
|
|
565
|
+
* itself being enabled at the assistant level.
|
|
566
|
+
*/
|
|
567
|
+
private warnUnreachableStatefulAgents(): void {
|
|
568
|
+
const pickerDisabled = this.agentPicker === 'disabled';
|
|
569
|
+
for (const a of this.agents ?? []) {
|
|
570
|
+
const isStateful = !!(a.onActivate || a.onDeactivate);
|
|
571
|
+
if (!isStateful) continue;
|
|
572
|
+
// Specialists only — defineStatefulAgent rejects fallback.
|
|
573
|
+
const excluded = 'excludeFromClassifier' in a && a.excludeFromClassifier === true;
|
|
574
|
+
if (!excluded) continue;
|
|
575
|
+
const manual = a.manualSelection?.enabled === true;
|
|
576
|
+
if (manual && !pickerDisabled) continue;
|
|
577
|
+
const reason = pickerDisabled
|
|
578
|
+
? 'the picker is disabled at chatConfig.picker.mode'
|
|
579
|
+
: 'manualSelection.enabled is not set on the agent';
|
|
580
|
+
logger.warn(
|
|
581
|
+
`Stateful agent "${a.name}" is unreachable: excludeFromClassifier is true and ${reason}. ` +
|
|
582
|
+
'Either remove excludeFromClassifier, enable manualSelection on the agent, or enable the picker.',
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
532
587
|
private createDriver(): AiDriver {
|
|
588
|
+
this.warnUnreachableStatefulAgents();
|
|
533
589
|
const agent = this.chatConfig.agent ?? {};
|
|
534
590
|
const { agents } = this;
|
|
535
591
|
|
|
536
|
-
|
|
592
|
+
// Always route through OrchestratingDriver when agents are configured —
|
|
593
|
+
// even with a single agent — so lifecycle hooks (`onActivate`/
|
|
594
|
+
// `onDeactivate`), auto-pin, and release semantics fire uniformly. The
|
|
595
|
+
// classifier short-circuits when there's only one viable candidate, so
|
|
596
|
+
// single-agent setups pay no LLM cost for routing. With no agents
|
|
597
|
+
// configured at all, fall back to a bare ChatDriver — there's nothing to
|
|
598
|
+
// orchestrate and no hooks to fire.
|
|
599
|
+
if (agents && agents.length > 0) {
|
|
600
|
+
if (agents.length === 1 && !agents[0].fallback) {
|
|
601
|
+
// Preset for getters that read activeAgent before the first turn
|
|
602
|
+
// (e.g. chatInputDuringExecution). The orchestrator will overwrite
|
|
603
|
+
// this via `agent-changed` once applyAgent fires on first sendMessage.
|
|
604
|
+
this.activeAgent = agents[0];
|
|
605
|
+
}
|
|
537
606
|
return new OrchestratingDriver(this.aiProvider, agents, {
|
|
607
|
+
sessionKey: this.getStateKey() ?? '',
|
|
538
608
|
maxHandoffs: agent.maxHandoffs,
|
|
539
609
|
classifierHistoryLength: agent.classifierHistoryLength,
|
|
540
610
|
classifierRetries: agent.classifierRetries,
|
|
541
611
|
maxToolIterations: agent.maxToolIterations,
|
|
542
612
|
maxFoldOperations: agent.maxFoldOperations,
|
|
613
|
+
maxTurnSnapshots: agent.maxTurnSnapshots,
|
|
543
614
|
});
|
|
544
615
|
}
|
|
545
616
|
|
|
546
|
-
const singleAgent = agents?.[0];
|
|
547
|
-
this.activeAgent = singleAgent;
|
|
548
617
|
return new ChatDriver(
|
|
549
618
|
this.aiProvider,
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
619
|
+
{},
|
|
620
|
+
[],
|
|
621
|
+
undefined,
|
|
622
|
+
undefined,
|
|
554
623
|
agent.maxToolIterations,
|
|
555
624
|
agent.maxFoldOperations,
|
|
625
|
+
agent.maxTurnSnapshots,
|
|
556
626
|
);
|
|
557
627
|
}
|
|
558
628
|
|
|
@@ -623,13 +693,25 @@ export class FoundationAiAssistant extends GenesisElement {
|
|
|
623
693
|
const onAgentChanged = (e: Event) => {
|
|
624
694
|
this.activeAgent = (e as CustomEvent<AgentConfig>).detail;
|
|
625
695
|
};
|
|
696
|
+
// Orchestrator-initiated pin changes (auto-pin on stateful activation,
|
|
697
|
+
// auto-clear on release). User-initiated pins flow the other direction
|
|
698
|
+
// via the `pinnedAgentName` setter, which already calls setPinnedAgent
|
|
699
|
+
// back into the driver — guard against the redundant round-trip.
|
|
700
|
+
const onPinnedChanged = (e: Event) => {
|
|
701
|
+
const name = (e as CustomEvent<string | null>).detail;
|
|
702
|
+
if (this._sessionRef?.store.aiAssistant.pinnedAgentName !== name) {
|
|
703
|
+
this._sessionRef?.actions.aiAssistant.setPinnedAgentName(name);
|
|
704
|
+
}
|
|
705
|
+
};
|
|
626
706
|
driver.addEventListener('orchestrating-start', onOrchStart);
|
|
627
707
|
driver.addEventListener('orchestrating-stop', onOrchStop);
|
|
628
708
|
driver.addEventListener('agent-changed', onAgentChanged);
|
|
709
|
+
driver.addEventListener('pinned-changed', onPinnedChanged);
|
|
629
710
|
cleanups.push(
|
|
630
711
|
() => driver.removeEventListener('orchestrating-start', onOrchStart),
|
|
631
712
|
() => driver.removeEventListener('orchestrating-stop', onOrchStop),
|
|
632
713
|
() => driver.removeEventListener('agent-changed', onAgentChanged),
|
|
714
|
+
() => driver.removeEventListener('pinned-changed', onPinnedChanged),
|
|
633
715
|
);
|
|
634
716
|
}
|
|
635
717
|
|
|
@@ -964,6 +1046,44 @@ export class FoundationAiAssistant extends GenesisElement {
|
|
|
964
1046
|
return this.agents?.find((a) => a.name === this.pinnedAgentName)?.manualSelection?.hint;
|
|
965
1047
|
}
|
|
966
1048
|
|
|
1049
|
+
/**
|
|
1050
|
+
* The pin is locked when a stateful agent (one with lifecycle hooks) is
|
|
1051
|
+
* *actively running* — i.e. its `onActivate` has fired and it owns live
|
|
1052
|
+
* state. Until the user sends their first message, a freshly pinned stateful
|
|
1053
|
+
* agent is not yet active and the picker should remain free; the user might
|
|
1054
|
+
* change their mind and unpin without anything to clean up.
|
|
1055
|
+
*
|
|
1056
|
+
* We derive from `activeAgent` (set by the orchestrator after `onActivate`
|
|
1057
|
+
* completes) rather than `pinnedAgentName` (set immediately on picker
|
|
1058
|
+
* click). The serialized `activeAgent` strips lifecycle hooks, so we look
|
|
1059
|
+
* up the live config from `this.agents` to check for them.
|
|
1060
|
+
*/
|
|
1061
|
+
@volatile
|
|
1062
|
+
get pinLocked(): boolean {
|
|
1063
|
+
if (!this.activeAgent) return false;
|
|
1064
|
+
const live = this.agents?.find((a) => a.name === this.activeAgent!.name);
|
|
1065
|
+
if (!live) return false;
|
|
1066
|
+
return !!(live.onActivate || live.onDeactivate);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/** Tooltip shown on the picker toggle button. */
|
|
1070
|
+
@volatile
|
|
1071
|
+
get agentToggleTitle(): string {
|
|
1072
|
+
if (this.pinLocked) {
|
|
1073
|
+
const name = this.pinnedAgentName ?? 'This agent';
|
|
1074
|
+
return `${name} is in the middle of a guided flow. Agent switching is disabled until the flow completes.`;
|
|
1075
|
+
}
|
|
1076
|
+
if (this.agentPickerOpen) {
|
|
1077
|
+
return 'Close agent picker';
|
|
1078
|
+
}
|
|
1079
|
+
if (this.pinnedAgentName !== null) {
|
|
1080
|
+
return this.pinnedAgentHint
|
|
1081
|
+
? `${this.pinnedAgentName} — ${this.pinnedAgentHint}`
|
|
1082
|
+
: this.pinnedAgentName;
|
|
1083
|
+
}
|
|
1084
|
+
return 'Attempts to route messages to the correct available agent. Click to manually pin an agent.';
|
|
1085
|
+
}
|
|
1086
|
+
|
|
967
1087
|
/**
|
|
968
1088
|
* Tint applied to the pin icon when an agent is pinned. Picked from the
|
|
969
1089
|
* brand palette by agent position (modulo the palette length), so each agent
|
|
@@ -1009,6 +1129,18 @@ export class FoundationAiAssistant extends GenesisElement {
|
|
|
1009
1129
|
|
|
1010
1130
|
getDebugLog() {
|
|
1011
1131
|
const timestamp = new Date().toISOString().replace(/:/g, '-');
|
|
1132
|
+
// Live agent reference — `this.activeAgent` is the redux-stripped projection,
|
|
1133
|
+
// so getDebugSnapshot is not on it. Look up the live config from the agents
|
|
1134
|
+
// array to get the function back.
|
|
1135
|
+
const liveActiveAgent = this.activeAgent
|
|
1136
|
+
? this.agents?.find((a) => a.name === this.activeAgent!.name)
|
|
1137
|
+
: undefined;
|
|
1138
|
+
let activeDebugSnapshot: unknown;
|
|
1139
|
+
try {
|
|
1140
|
+
activeDebugSnapshot = liveActiveAgent?.getDebugSnapshot?.();
|
|
1141
|
+
} catch (e) {
|
|
1142
|
+
activeDebugSnapshot = `<getDebugSnapshot threw: ${e instanceof Error ? e.message : String(e)}>`;
|
|
1143
|
+
}
|
|
1012
1144
|
return {
|
|
1013
1145
|
messages: this.driver?.getRawHistory?.() ?? this.messages,
|
|
1014
1146
|
meta: {
|
|
@@ -1016,13 +1148,29 @@ export class FoundationAiAssistant extends GenesisElement {
|
|
|
1016
1148
|
host: window.location.host,
|
|
1017
1149
|
agentSummary: this.agents?.map((a) => ({
|
|
1018
1150
|
...a,
|
|
1019
|
-
toolDefinitions:
|
|
1151
|
+
toolDefinitions: Array.isArray(a.toolDefinitions)
|
|
1152
|
+
? expandToolTree(a.toolDefinitions, a.toolHandlers ?? {})
|
|
1153
|
+
: typeof a.toolDefinitions === 'function'
|
|
1154
|
+
? '<dynamic — resolved per turn>'
|
|
1155
|
+
: [],
|
|
1020
1156
|
toolHandlers: undefined,
|
|
1157
|
+
onActivate: undefined,
|
|
1158
|
+
onDeactivate: undefined,
|
|
1159
|
+
getDebugSnapshot: undefined,
|
|
1021
1160
|
})),
|
|
1022
|
-
activeSystemPrompt:
|
|
1161
|
+
activeSystemPrompt:
|
|
1162
|
+
typeof this.activeAgent?.systemPrompt === 'function'
|
|
1163
|
+
? '<dynamic — resolved per turn>'
|
|
1164
|
+
: this.activeAgent?.systemPrompt,
|
|
1023
1165
|
activePrimerHistory: this.activeAgent?.primerHistory,
|
|
1024
1166
|
activeFoldStack:
|
|
1025
1167
|
this.driver instanceof ChatDriver ? this.driver.getActiveFoldNames() : undefined,
|
|
1168
|
+
// Snapshot captured fresh at log-export time — reflects state NOW, which
|
|
1169
|
+
// may have transitioned since the last LLM call.
|
|
1170
|
+
activeDebugSnapshot,
|
|
1171
|
+
// Per-LLM-call timeline — pairs each turn with the prompt + tool surface
|
|
1172
|
+
// + agent state that drove it. Capped to the most recent N entries.
|
|
1173
|
+
turnSnapshots: this.driver?.getTurnSnapshots?.() ?? [],
|
|
1026
1174
|
debug: this.debugStateFactory?.(),
|
|
1027
1175
|
},
|
|
1028
1176
|
};
|
|
@@ -1167,7 +1315,12 @@ export class FoundationAiAssistant extends GenesisElement {
|
|
|
1167
1315
|
{
|
|
1168
1316
|
name: this.activeAgent.name,
|
|
1169
1317
|
description: 'description' in this.activeAgent ? this.activeAgent.description : '',
|
|
1170
|
-
tools
|
|
1318
|
+
// Dynamic-tools agents have no resolvable tool list outside a
|
|
1319
|
+
// per-turn context — suggestions just get an empty hint, matching
|
|
1320
|
+
// what `OrchestratingDriver.getSuggestions` does for them.
|
|
1321
|
+
tools: Array.isArray(this.activeAgent.toolDefinitions)
|
|
1322
|
+
? this.activeAgent.toolDefinitions
|
|
1323
|
+
: [],
|
|
1171
1324
|
},
|
|
1172
1325
|
]
|
|
1173
1326
|
: undefined;
|