@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.
Files changed (33) hide show
  1. package/dist/ai-assistant.api.json +1513 -70
  2. package/dist/ai-assistant.d.ts +367 -7
  3. package/dist/dts/components/ai-driver/ai-driver.d.ts +8 -0
  4. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
  5. package/dist/dts/components/chat-driver/chat-driver.d.ts +79 -3
  6. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  7. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +23 -0
  8. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  9. package/dist/dts/config/config.d.ts +106 -2
  10. package/dist/dts/config/config.d.ts.map +1 -1
  11. package/dist/dts/config/define-stateful-agent.d.ts +115 -0
  12. package/dist/dts/config/define-stateful-agent.d.ts.map +1 -0
  13. package/dist/dts/index.d.ts +1 -0
  14. package/dist/dts/index.d.ts.map +1 -1
  15. package/dist/dts/main/main.d.ts +36 -4
  16. package/dist/dts/main/main.d.ts.map +1 -1
  17. package/dist/dts/main/main.template.d.ts.map +1 -1
  18. package/dist/esm/components/chat-driver/chat-driver.js +126 -11
  19. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +192 -33
  20. package/dist/esm/config/define-stateful-agent.js +174 -0
  21. package/dist/esm/index.js +1 -0
  22. package/dist/esm/main/main.js +164 -21
  23. package/dist/esm/main/main.template.js +2 -11
  24. package/dist/tsconfig.tsbuildinfo +1 -1
  25. package/package.json +16 -16
  26. package/src/components/ai-driver/ai-driver.ts +9 -0
  27. package/src/components/chat-driver/chat-driver.ts +178 -8
  28. package/src/components/orchestrating-driver/orchestrating-driver.ts +191 -17
  29. package/src/config/config.ts +112 -2
  30. package/src/config/define-stateful-agent.ts +293 -0
  31. package/src/index.ts +1 -0
  32. package/src/main/main.template.ts +2 -9
  33. package/src/main/main.ts +167 -14
@@ -0,0 +1,174 @@
1
+ import { __awaiter, __rest } from "tslib";
2
+ import { TOOL_FOLD_SYMBOL } from '../utils/tool-fold';
3
+ /**
4
+ * Walk a state object and snapshot any machine-shaped values found on it.
5
+ * Recognises foundation-state-machine instances by structural shape rather
6
+ * than `instanceof`, so the helper stays free of a runtime dep on
7
+ * foundation-state-machine.
8
+ *
9
+ * Strips the `errors`/`error` framework noise from each machine's context —
10
+ * these are foundation-state-machine internals (an ErrorMap instance and its
11
+ * last-error pointer) that bloat the debug payload without aiding debugging.
12
+ */
13
+ function defaultStatefulDebugSnapshot(state) {
14
+ var _a;
15
+ if (!state || typeof state !== 'object')
16
+ return state;
17
+ const result = {};
18
+ for (const [key, val] of Object.entries(state)) {
19
+ if (val && typeof val === 'object' && 'state' in val && 'context' in val && 'complete' in val) {
20
+ const m = val;
21
+ const ctx = (_a = m.context) !== null && _a !== void 0 ? _a : {};
22
+ const { errors: _e, error: _err } = ctx, userContext = __rest(ctx, ["errors", "error"]);
23
+ result[key] = {
24
+ state: m.state,
25
+ context: userContext,
26
+ complete: m.complete,
27
+ output: m.output,
28
+ };
29
+ }
30
+ }
31
+ return Object.keys(result).length
32
+ ? result
33
+ : '<no auto-snapshot — state has no machine-shaped properties; provide getDebugSnapshot>';
34
+ }
35
+ /**
36
+ * Build an `AgentConfig` whose `systemPrompt`, `toolDefinitions`, and tool
37
+ * handlers all close over a long-lived state object created on activation.
38
+ *
39
+ * The framework wires the lifecycle: `init` on `onActivate`, `dispose` on
40
+ * `onDeactivate`. State is held inside the helper's closure — never exposed on
41
+ * the resulting `AgentConfig` — so the redux serializer doesn't see it.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const guidedBooking = defineStatefulAgent<{ machine: GuidedBookingMachine }>({
46
+ * name: 'Guided Booking',
47
+ * description: 'Books a trade via a guided wizard.',
48
+ * excludeFromClassifier: true,
49
+ * manualSelection: { enabled: true, hint: 'Step-by-step trade booking' },
50
+ * init: () => ({ machine: new GuidedBookingMachine() }),
51
+ * dispose: ({ state }) => state.machine.stop(),
52
+ * systemPrompt: ({ state }) => composeFromMachine(state.machine),
53
+ * toolDefinitions: ({ state }) => toolsForState(state.machine.state),
54
+ * toolHandlers: ({ machine }) => ({ ... }),
55
+ * });
56
+ * ```
57
+ *
58
+ * @beta
59
+ */
60
+ export function defineStatefulAgent(opts) {
61
+ let state;
62
+ let cachedHandlers;
63
+ const assertNoFolds = (handlers) => {
64
+ for (const [name, handler] of Object.entries(handlers)) {
65
+ if (handler[TOOL_FOLD_SYMBOL]) {
66
+ throw new Error(`Stateful agent "${opts.name}" tool "${name}" carries fold metadata. ` +
67
+ `Folds and state-machine-driven tool filtering both try to control the ` +
68
+ `LLM's tool view — pick one. Remove the fold or migrate this agent to ` +
69
+ `a non-stateful AgentConfig.`);
70
+ }
71
+ }
72
+ };
73
+ // Wrap the optional dynamic-tools factory so it threads `state` in and asserts
74
+ // we never accidentally resolved a fold-tagged tool. Static arrays are passed
75
+ // through unchanged.
76
+ const wrappedTools = (() => {
77
+ const td = opts.toolDefinitions;
78
+ if (typeof td === 'function') {
79
+ return (ctx) => __awaiter(this, void 0, void 0, function* () {
80
+ if (!state) {
81
+ throw new Error(`Stateful agent "${opts.name}" tools called before init`);
82
+ }
83
+ return td(Object.assign(Object.assign({}, ctx), { state }));
84
+ });
85
+ }
86
+ return td;
87
+ })();
88
+ // Each proxy entry calls into `cachedHandlers`, which is populated eagerly
89
+ // inside `onActivate` (see below). Eager population means the no-folds
90
+ // assertion fires at activation time — not on first tool call — so a
91
+ // misconfigured agent fails loud and immediately instead of silently
92
+ // appearing to work until the LLM happens to invoke a tool.
93
+ const buildHandlerProxy = (names) => {
94
+ const out = {};
95
+ for (const name of names) {
96
+ out[name] = (args, ctx) => __awaiter(this, void 0, void 0, function* () {
97
+ if (!state || !cachedHandlers) {
98
+ throw new Error(`Stateful agent "${opts.name}" handler called before init`);
99
+ }
100
+ const handler = cachedHandlers[name];
101
+ if (!handler) {
102
+ throw new Error(`Tool "${name}" has no handler on stateful agent "${opts.name}"`);
103
+ }
104
+ return handler(args, ctx);
105
+ });
106
+ }
107
+ return out;
108
+ };
109
+ // Static-tools case: handler names are knowable from the definitions array.
110
+ // Dynamic-tools case: we discover names from a sample of `toolHandlers(state)`
111
+ // taken inside onActivate. Until then, the handler dict is empty — fine
112
+ // because no tool call can happen before activation completes.
113
+ const staticHandlerProxy = Array.isArray(opts.toolDefinitions) && opts.toolHandlers
114
+ ? buildHandlerProxy(opts.toolDefinitions.map((d) => d.name))
115
+ : undefined;
116
+ // For the dynamic case we patch this object in place on activation. The
117
+ // driver reads `toolHandlers` by reference, so mutating in place is enough.
118
+ const resolvedHandlers = staticHandlerProxy !== null && staticHandlerProxy !== void 0 ? staticHandlerProxy : {};
119
+ const base = {
120
+ name: opts.name,
121
+ primerHistory: opts.primerHistory,
122
+ subAgents: opts.subAgents,
123
+ manualSelection: opts.manualSelection,
124
+ chatInputDuringExecution: opts.chatInputDuringExecution,
125
+ toolDefinitions: wrappedTools,
126
+ toolHandlers: opts.toolHandlers ? resolvedHandlers : undefined,
127
+ onActivate: (ctx) => __awaiter(this, void 0, void 0, function* () {
128
+ var _a, _b;
129
+ state = yield opts.init(ctx);
130
+ // Sample handlers eagerly and validate up-front. Throws here are visible
131
+ // at the agent-switch boundary instead of buried inside a future tool
132
+ // dispatch.
133
+ cachedHandlers = (_b = (_a = opts.toolHandlers) === null || _a === void 0 ? void 0 : _a.call(opts, state)) !== null && _b !== void 0 ? _b : {};
134
+ assertNoFolds(cachedHandlers);
135
+ // Dynamic-tools path: handler names are discovered from the sample.
136
+ if (!staticHandlerProxy && opts.toolHandlers) {
137
+ const dynamicProxy = buildHandlerProxy(Object.keys(cachedHandlers));
138
+ for (const key of Object.keys(resolvedHandlers))
139
+ delete resolvedHandlers[key];
140
+ Object.assign(resolvedHandlers, dynamicProxy);
141
+ }
142
+ }),
143
+ onDeactivate: (ctx) => __awaiter(this, void 0, void 0, function* () {
144
+ if (state !== undefined && opts.dispose) {
145
+ yield opts.dispose(Object.assign(Object.assign({}, ctx), { state }));
146
+ }
147
+ // Orchestrator serializes onActivate/onDeactivate; concurrent calls
148
+ // cannot interleave the read-then-write of `state`.
149
+ // eslint-disable-next-line require-atomic-updates
150
+ state = undefined;
151
+ cachedHandlers = undefined;
152
+ }),
153
+ systemPrompt: opts.systemPrompt
154
+ ? (ctx) => __awaiter(this, void 0, void 0, function* () {
155
+ if (!state) {
156
+ throw new Error(`Stateful agent "${opts.name}" systemPrompt called before init`);
157
+ }
158
+ return opts.systemPrompt(Object.assign(Object.assign({}, ctx), { state }));
159
+ })
160
+ : undefined,
161
+ // Called per LLM call by ChatDriver, and once at debug-log export time.
162
+ // Returns `undefined` before activation (state hasn't been built yet) and
163
+ // after deactivation (state was disposed) — the snapshotter is best-effort
164
+ // and the driver tolerates undefined.
165
+ getDebugSnapshot: () => {
166
+ if (!state)
167
+ return undefined;
168
+ return opts.getDebugSnapshot
169
+ ? opts.getDebugSnapshot(state)
170
+ : defaultStatefulDebugSnapshot(state);
171
+ },
172
+ };
173
+ return Object.assign(Object.assign({}, base), { description: opts.description, excludeFromClassifier: opts.excludeFromClassifier });
174
+ }
package/dist/esm/index.js CHANGED
@@ -9,6 +9,7 @@ export * from './components/popout-manager';
9
9
  export * from './channel/ai-activity-channel';
10
10
  export * from './channel/ai-activity-bus';
11
11
  export * from './config/config';
12
+ export * from './config/define-stateful-agent';
12
13
  export * from './config/fallback-agents';
13
14
  export * from './utils/tool-fold';
14
15
  export { AiChatMarkdown } from './components/chat-markdown/chat-markdown';
@@ -69,11 +69,19 @@ const MODEL_CONTEXT_LIMITS = {
69
69
  };
70
70
  // Register supporting components when the main component module is imported.
71
71
  avoidTreeShaking(AiChatMarkdown, AiChatInteractionWrapper, AiHaloOverlay, AiChatBubble, AiActivityHalo, ChatSuggestions, AgentPicker);
72
- /** Recursively strips `toolHandlers` from an agent and all its sub-agents. */
72
+ /**
73
+ * Recursively strips non-serializable fields from an agent before storing in Redux:
74
+ * - `toolHandlers` (functions),
75
+ * - `onActivate` / `onDeactivate` (lifecycle hooks, functions),
76
+ * - function-form `systemPrompt` / `toolDefinitions` (downgraded to `undefined`
77
+ * in the snapshot — the live config on the driver is still the source of
78
+ * truth; the slice only stores a serializable projection).
79
+ */
73
80
  function stripHandlers(agent) {
74
- const { toolHandlers: _, subAgents } = agent, rest = __rest(agent, ["toolHandlers", "subAgents"]);
81
+ const { toolHandlers: _h, onActivate: _on, onDeactivate: _off, getDebugSnapshot: _g, subAgents, systemPrompt, toolDefinitions } = agent, rest = __rest(agent, ["toolHandlers", "onActivate", "onDeactivate", "getDebugSnapshot", "subAgents", "systemPrompt", "toolDefinitions"]);
82
+ const stripped = Object.assign(Object.assign({}, rest), { systemPrompt: typeof systemPrompt === 'function' ? undefined : systemPrompt, toolDefinitions: typeof toolDefinitions === 'function' ? undefined : toolDefinitions });
75
83
  return (subAgents === null || subAgents === void 0 ? void 0 : subAgents.length)
76
- ? Object.assign(Object.assign({}, rest), { subAgents: subAgents.map(stripHandlers) }) : rest;
84
+ ? Object.assign(Object.assign({}, stripped), { subAgents: subAgents.map(stripHandlers) }) : stripped;
77
85
  }
78
86
  /**
79
87
  * Foundation AI Assistant component.
@@ -443,8 +451,15 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
443
451
  if (this.driver.isBusy())
444
452
  return;
445
453
  const history = (_c = (_b = (_a = this.driver).getRawHistory) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : [];
454
+ const oldDriver = this.driver;
446
455
  this.unwireDriver();
447
456
  deleteDriver(key);
457
+ // Fire the active agent's `onDeactivate` so machines / per-agent state get
458
+ // torn down before the replacement driver instantiates them again. Fire
459
+ // and forget — the old driver is about to be GC'd, errors are non-fatal.
460
+ if (oldDriver && 'dispose' in oldDriver && typeof oldDriver.dispose === 'function') {
461
+ void oldDriver.dispose();
462
+ }
448
463
  this.driver = getOrCreateDriver(key, () => this.createDriver());
449
464
  this.wireDriver();
450
465
  if (history.length)
@@ -469,22 +484,64 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
469
484
  * current agent configuration. Does not wire event listeners or register in
470
485
  * the driver registry.
471
486
  */
487
+ /**
488
+ * Warn at config time if any stateful agent (one with lifecycle hooks) is
489
+ * configured in a way that makes it unreachable: `excludeFromClassifier`
490
+ * removes the classifier path, so the only entry left is manual pinning —
491
+ * which requires both `manualSelection.enabled` on the agent and the picker
492
+ * itself being enabled at the assistant level.
493
+ */
494
+ warnUnreachableStatefulAgents() {
495
+ var _a, _b;
496
+ const pickerDisabled = this.agentPicker === 'disabled';
497
+ for (const a of (_a = this.agents) !== null && _a !== void 0 ? _a : []) {
498
+ const isStateful = !!(a.onActivate || a.onDeactivate);
499
+ if (!isStateful)
500
+ continue;
501
+ // Specialists only — defineStatefulAgent rejects fallback.
502
+ const excluded = 'excludeFromClassifier' in a && a.excludeFromClassifier === true;
503
+ if (!excluded)
504
+ continue;
505
+ const manual = ((_b = a.manualSelection) === null || _b === void 0 ? void 0 : _b.enabled) === true;
506
+ if (manual && !pickerDisabled)
507
+ continue;
508
+ const reason = pickerDisabled
509
+ ? 'the picker is disabled at chatConfig.picker.mode'
510
+ : 'manualSelection.enabled is not set on the agent';
511
+ logger.warn(`Stateful agent "${a.name}" is unreachable: excludeFromClassifier is true and ${reason}. ` +
512
+ 'Either remove excludeFromClassifier, enable manualSelection on the agent, or enable the picker.');
513
+ }
514
+ }
472
515
  createDriver() {
473
- var _a, _b, _c;
516
+ var _a, _b;
517
+ this.warnUnreachableStatefulAgents();
474
518
  const agent = (_a = this.chatConfig.agent) !== null && _a !== void 0 ? _a : {};
475
519
  const { agents } = this;
476
- if (agents && agents.length > 1) {
520
+ // Always route through OrchestratingDriver when agents are configured
521
+ // even with a single agent — so lifecycle hooks (`onActivate`/
522
+ // `onDeactivate`), auto-pin, and release semantics fire uniformly. The
523
+ // classifier short-circuits when there's only one viable candidate, so
524
+ // single-agent setups pay no LLM cost for routing. With no agents
525
+ // configured at all, fall back to a bare ChatDriver — there's nothing to
526
+ // orchestrate and no hooks to fire.
527
+ if (agents && agents.length > 0) {
528
+ if (agents.length === 1 && !agents[0].fallback) {
529
+ // Preset for getters that read activeAgent before the first turn
530
+ // (e.g. chatInputDuringExecution). The orchestrator will overwrite
531
+ // this via `agent-changed` once applyAgent fires on first sendMessage.
532
+ this.activeAgent = agents[0];
533
+ }
477
534
  return new OrchestratingDriver(this.aiProvider, agents, {
535
+ sessionKey: (_b = this.getStateKey()) !== null && _b !== void 0 ? _b : '',
478
536
  maxHandoffs: agent.maxHandoffs,
479
537
  classifierHistoryLength: agent.classifierHistoryLength,
480
538
  classifierRetries: agent.classifierRetries,
481
539
  maxToolIterations: agent.maxToolIterations,
482
540
  maxFoldOperations: agent.maxFoldOperations,
541
+ maxTurnSnapshots: agent.maxTurnSnapshots,
483
542
  });
484
543
  }
485
- const singleAgent = agents === null || agents === void 0 ? void 0 : agents[0];
486
- this.activeAgent = singleAgent;
487
- return new ChatDriver(this.aiProvider, (_b = singleAgent === null || singleAgent === void 0 ? void 0 : singleAgent.toolHandlers) !== null && _b !== void 0 ? _b : {}, (_c = singleAgent === null || singleAgent === void 0 ? void 0 : singleAgent.toolDefinitions) !== null && _c !== void 0 ? _c : [], singleAgent === null || singleAgent === void 0 ? void 0 : singleAgent.systemPrompt, singleAgent === null || singleAgent === void 0 ? void 0 : singleAgent.primerHistory, agent.maxToolIterations, agent.maxFoldOperations);
544
+ return new ChatDriver(this.aiProvider, {}, [], undefined, undefined, agent.maxToolIterations, agent.maxFoldOperations, agent.maxTurnSnapshots);
488
545
  }
489
546
  /**
490
547
  * Attaches event listeners to the current driver. Stores a cleanup function
@@ -550,10 +607,22 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
550
607
  const onAgentChanged = (e) => {
551
608
  this.activeAgent = e.detail;
552
609
  };
610
+ // Orchestrator-initiated pin changes (auto-pin on stateful activation,
611
+ // auto-clear on release). User-initiated pins flow the other direction
612
+ // via the `pinnedAgentName` setter, which already calls setPinnedAgent
613
+ // back into the driver — guard against the redundant round-trip.
614
+ const onPinnedChanged = (e) => {
615
+ var _a, _b;
616
+ const name = e.detail;
617
+ if (((_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.pinnedAgentName) !== name) {
618
+ (_b = this._sessionRef) === null || _b === void 0 ? void 0 : _b.actions.aiAssistant.setPinnedAgentName(name);
619
+ }
620
+ };
553
621
  driver.addEventListener('orchestrating-start', onOrchStart);
554
622
  driver.addEventListener('orchestrating-stop', onOrchStop);
555
623
  driver.addEventListener('agent-changed', onAgentChanged);
556
- cleanups.push(() => driver.removeEventListener('orchestrating-start', onOrchStart), () => driver.removeEventListener('orchestrating-stop', onOrchStop), () => driver.removeEventListener('agent-changed', onAgentChanged));
624
+ driver.addEventListener('pinned-changed', onPinnedChanged);
625
+ cleanups.push(() => driver.removeEventListener('orchestrating-start', onOrchStart), () => driver.removeEventListener('orchestrating-stop', onOrchStop), () => driver.removeEventListener('agent-changed', onAgentChanged), () => driver.removeEventListener('pinned-changed', onPinnedChanged));
557
626
  }
558
627
  this.driverCleanup = () => {
559
628
  for (const fn of cleanups)
@@ -570,7 +639,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
570
639
  this.driverCleanup = undefined;
571
640
  }
572
641
  connectedCallback() {
573
- var _a, _b, _c, _d, _e, _f, _g;
642
+ var _a, _b, _c, _d, _e, _f, _j;
574
643
  // Initialise the store reference BEFORE super.connectedCallback() so that
575
644
  // the first FAST render has access to the store. The store Proxy calls
576
645
  // Observable.track(observableStore, sliceName) whenever a slice is read,
@@ -621,7 +690,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
621
690
  // Subscribe once so that when the originating send() completes (possibly on a
622
691
  // different element instance that has since disconnected), this element cleans up
623
692
  // its own timer, syncs the halo, and triggers the post-response suggestion fetch.
624
- this._executionCompletionUnsub = (_g = this._sessionRef) === null || _g === void 0 ? void 0 : _g.subscribeKey((s) => s.aiAssistant.state, () => {
693
+ this._executionCompletionUnsub = (_j = this._sessionRef) === null || _j === void 0 ? void 0 : _j.subscribeKey((s) => s.aiAssistant.state, () => {
625
694
  var _a, _b;
626
695
  if (((_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.state) === 'idle') {
627
696
  this.stopLoadingTimer();
@@ -856,6 +925,44 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
856
925
  return undefined;
857
926
  return (_c = (_b = (_a = this.agents) === null || _a === void 0 ? void 0 : _a.find((a) => a.name === this.pinnedAgentName)) === null || _b === void 0 ? void 0 : _b.manualSelection) === null || _c === void 0 ? void 0 : _c.hint;
858
927
  }
928
+ /**
929
+ * The pin is locked when a stateful agent (one with lifecycle hooks) is
930
+ * *actively running* — i.e. its `onActivate` has fired and it owns live
931
+ * state. Until the user sends their first message, a freshly pinned stateful
932
+ * agent is not yet active and the picker should remain free; the user might
933
+ * change their mind and unpin without anything to clean up.
934
+ *
935
+ * We derive from `activeAgent` (set by the orchestrator after `onActivate`
936
+ * completes) rather than `pinnedAgentName` (set immediately on picker
937
+ * click). The serialized `activeAgent` strips lifecycle hooks, so we look
938
+ * up the live config from `this.agents` to check for them.
939
+ */
940
+ get pinLocked() {
941
+ var _a;
942
+ if (!this.activeAgent)
943
+ return false;
944
+ const live = (_a = this.agents) === null || _a === void 0 ? void 0 : _a.find((a) => a.name === this.activeAgent.name);
945
+ if (!live)
946
+ return false;
947
+ return !!(live.onActivate || live.onDeactivate);
948
+ }
949
+ /** Tooltip shown on the picker toggle button. */
950
+ get agentToggleTitle() {
951
+ var _a;
952
+ if (this.pinLocked) {
953
+ const name = (_a = this.pinnedAgentName) !== null && _a !== void 0 ? _a : 'This agent';
954
+ return `${name} is in the middle of a guided flow. Agent switching is disabled until the flow completes.`;
955
+ }
956
+ if (this.agentPickerOpen) {
957
+ return 'Close agent picker';
958
+ }
959
+ if (this.pinnedAgentName !== null) {
960
+ return this.pinnedAgentHint
961
+ ? `${this.pinnedAgentName} — ${this.pinnedAgentHint}`
962
+ : this.pinnedAgentName;
963
+ }
964
+ return 'Attempts to route messages to the correct available agent. Click to manually pin an agent.';
965
+ }
859
966
  /**
860
967
  * Tint applied to the pin icon when an agent is pinned. Picked from the
861
968
  * brand palette by agent position (modulo the palette length), so each agent
@@ -897,21 +1004,46 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
897
1004
  this.enabledAnimations = animations;
898
1005
  }
899
1006
  getDebugLog() {
900
- var _a, _b, _c, _d, _e, _f, _g;
1007
+ var _a, _b, _c, _d, _e, _f, _j, _k, _l, _m, _o, _p, _q;
901
1008
  const timestamp = new Date().toISOString().replace(/:/g, '-');
1009
+ // Live agent reference — `this.activeAgent` is the redux-stripped projection,
1010
+ // so getDebugSnapshot is not on it. Look up the live config from the agents
1011
+ // array to get the function back.
1012
+ const liveActiveAgent = this.activeAgent
1013
+ ? (_a = this.agents) === null || _a === void 0 ? void 0 : _a.find((a) => a.name === this.activeAgent.name)
1014
+ : undefined;
1015
+ let activeDebugSnapshot;
1016
+ try {
1017
+ activeDebugSnapshot = (_b = liveActiveAgent === null || liveActiveAgent === void 0 ? void 0 : liveActiveAgent.getDebugSnapshot) === null || _b === void 0 ? void 0 : _b.call(liveActiveAgent);
1018
+ }
1019
+ catch (e) {
1020
+ activeDebugSnapshot = `<getDebugSnapshot threw: ${e instanceof Error ? e.message : String(e)}>`;
1021
+ }
902
1022
  return {
903
- messages: (_c = (_b = (_a = this.driver) === null || _a === void 0 ? void 0 : _a.getRawHistory) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : this.messages,
1023
+ messages: (_e = (_d = (_c = this.driver) === null || _c === void 0 ? void 0 : _c.getRawHistory) === null || _d === void 0 ? void 0 : _d.call(_c)) !== null && _e !== void 0 ? _e : this.messages,
904
1024
  meta: {
905
1025
  timestamp,
906
1026
  host: window.location.host,
907
- agentSummary: (_d = this.agents) === null || _d === void 0 ? void 0 : _d.map((a) => {
908
- var _a, _b;
909
- return (Object.assign(Object.assign({}, a), { toolDefinitions: expandToolTree((_a = a.toolDefinitions) !== null && _a !== void 0 ? _a : [], (_b = a.toolHandlers) !== null && _b !== void 0 ? _b : {}), toolHandlers: undefined }));
1027
+ agentSummary: (_f = this.agents) === null || _f === void 0 ? void 0 : _f.map((a) => {
1028
+ var _a;
1029
+ return (Object.assign(Object.assign({}, a), { toolDefinitions: Array.isArray(a.toolDefinitions)
1030
+ ? expandToolTree(a.toolDefinitions, (_a = a.toolHandlers) !== null && _a !== void 0 ? _a : {})
1031
+ : typeof a.toolDefinitions === 'function'
1032
+ ? '<dynamic — resolved per turn>'
1033
+ : [], toolHandlers: undefined, onActivate: undefined, onDeactivate: undefined, getDebugSnapshot: undefined }));
910
1034
  }),
911
- activeSystemPrompt: (_e = this.activeAgent) === null || _e === void 0 ? void 0 : _e.systemPrompt,
912
- activePrimerHistory: (_f = this.activeAgent) === null || _f === void 0 ? void 0 : _f.primerHistory,
1035
+ activeSystemPrompt: typeof ((_j = this.activeAgent) === null || _j === void 0 ? void 0 : _j.systemPrompt) === 'function'
1036
+ ? '<dynamic resolved per turn>'
1037
+ : (_k = this.activeAgent) === null || _k === void 0 ? void 0 : _k.systemPrompt,
1038
+ activePrimerHistory: (_l = this.activeAgent) === null || _l === void 0 ? void 0 : _l.primerHistory,
913
1039
  activeFoldStack: this.driver instanceof ChatDriver ? this.driver.getActiveFoldNames() : undefined,
914
- debug: (_g = this.debugStateFactory) === null || _g === void 0 ? void 0 : _g.call(this),
1040
+ // Snapshot captured fresh at log-export time reflects state NOW, which
1041
+ // may have transitioned since the last LLM call.
1042
+ activeDebugSnapshot,
1043
+ // Per-LLM-call timeline — pairs each turn with the prompt + tool surface
1044
+ // + agent state that drove it. Capped to the most recent N entries.
1045
+ turnSnapshots: (_p = (_o = (_m = this.driver) === null || _m === void 0 ? void 0 : _m.getTurnSnapshots) === null || _o === void 0 ? void 0 : _o.call(_m)) !== null && _p !== void 0 ? _p : [],
1046
+ debug: (_q = this.debugStateFactory) === null || _q === void 0 ? void 0 : _q.call(this),
915
1047
  },
916
1048
  };
917
1049
  }
@@ -1011,7 +1143,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
1011
1143
  }
1012
1144
  fetchSuggestions() {
1013
1145
  return __awaiter(this, void 0, void 0, function* () {
1014
- var _a, _b;
1146
+ var _a;
1015
1147
  const suggestionsConfig = this.chatConfig.suggestions;
1016
1148
  if (!this.driver || !suggestionsConfig || suggestionsConfig.behavior === 'never') {
1017
1149
  return;
@@ -1041,7 +1173,12 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
1041
1173
  {
1042
1174
  name: this.activeAgent.name,
1043
1175
  description: 'description' in this.activeAgent ? this.activeAgent.description : '',
1044
- tools: (_b = this.activeAgent.toolDefinitions) !== null && _b !== void 0 ? _b : [],
1176
+ // Dynamic-tools agents have no resolvable tool list outside a
1177
+ // per-turn context — suggestions just get an empty hint, matching
1178
+ // what `OrchestratingDriver.getSuggestions` does for them.
1179
+ tools: Array.isArray(this.activeAgent.toolDefinitions)
1180
+ ? this.activeAgent.toolDefinitions
1181
+ : [],
1045
1182
  },
1046
1183
  ]
1047
1184
  : undefined;
@@ -1192,6 +1329,12 @@ __decorate([
1192
1329
  __decorate([
1193
1330
  volatile
1194
1331
  ], FoundationAiAssistant.prototype, "pinnedAgentHint", null);
1332
+ __decorate([
1333
+ volatile
1334
+ ], FoundationAiAssistant.prototype, "pinLocked", null);
1335
+ __decorate([
1336
+ volatile
1337
+ ], FoundationAiAssistant.prototype, "agentToggleTitle", null);
1195
1338
  __decorate([
1196
1339
  volatile
1197
1340
  ], FoundationAiAssistant.prototype, "pinnedAgentColour", null);
@@ -196,17 +196,8 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
196
196
  class="agent-toggle-button"
197
197
  part="agent-toggle-button"
198
198
  appearance="stealth"
199
- title=${(x) => {
200
- var _a;
201
- return x.agentPickerOpen
202
- ? 'Close agent picker'
203
- : x.pinnedAgentName !== null
204
- ? x.pinnedAgentHint
205
- ? `${x.pinnedAgentName} — ${x.pinnedAgentHint}`
206
- : ((_a = x.pinnedAgentName) !== null && _a !== void 0 ? _a : '')
207
- : 'Attempts to route messages to the correct available agent. Click to manually pin an agent.';
208
- }}
209
- ?disabled=${(x) => x.state === 'loading'}
199
+ title=${(x) => x.agentToggleTitle}
200
+ ?disabled=${(x) => x.state === 'loading' || x.pinLocked}
210
201
  @click=${(x) => x.toggleAgentPicker()}
211
202
  >
212
203
  ${when((x) => x.agentPickerOpen, html `<${iconTag} name="chevron-down"></${iconTag}>`)}
@@ -1 +1 @@
1
- {"root":["../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/halo-overlay.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/config/config.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/state/ai-assistant-slice.ts","../src/state/driver-registry.ts","../src/state/session-store.ts","../src/styles/ai-colours.ts","../src/styles/index.ts","../src/styles/styles.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/utils/animated-panel-toggle.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/tool-fold.ts"],"version":"5.9.2"}
1
+ {"root":["../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/halo-overlay.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/config/config.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/state/ai-assistant-slice.ts","../src/state/driver-registry.ts","../src/state/session-store.ts","../src/styles/ai-colours.ts","../src/styles/index.ts","../src/styles/styles.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/utils/animated-panel-toggle.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/tool-fold.ts"],"version":"5.9.2"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/ai-assistant",
3
3
  "description": "Genesis AI Assistant micro-frontend",
4
- "version": "14.434.0",
4
+ "version": "14.436.0",
5
5
  "license": "SEE LICENSE IN license.txt",
6
6
  "main": "dist/esm/index.js",
7
7
  "types": "dist/ai-assistant.d.ts",
@@ -64,24 +64,24 @@
64
64
  }
65
65
  },
66
66
  "devDependencies": {
67
- "@genesislcap/foundation-testing": "14.434.0",
68
- "@genesislcap/genx": "14.434.0",
69
- "@genesislcap/rollup-builder": "14.434.0",
70
- "@genesislcap/ts-builder": "14.434.0",
71
- "@genesislcap/uvu-playwright-builder": "14.434.0",
72
- "@genesislcap/vite-builder": "14.434.0",
73
- "@genesislcap/webpack-builder": "14.434.0",
67
+ "@genesislcap/foundation-testing": "14.436.0",
68
+ "@genesislcap/genx": "14.436.0",
69
+ "@genesislcap/rollup-builder": "14.436.0",
70
+ "@genesislcap/ts-builder": "14.436.0",
71
+ "@genesislcap/uvu-playwright-builder": "14.436.0",
72
+ "@genesislcap/vite-builder": "14.436.0",
73
+ "@genesislcap/webpack-builder": "14.436.0",
74
74
  "@types/dompurify": "^3.0.5",
75
75
  "@types/marked": "^5.0.2"
76
76
  },
77
77
  "dependencies": {
78
- "@genesislcap/foundation-ai": "14.434.0",
79
- "@genesislcap/foundation-logger": "14.434.0",
80
- "@genesislcap/foundation-redux": "14.434.0",
81
- "@genesislcap/foundation-ui": "14.434.0",
82
- "@genesislcap/foundation-utils": "14.434.0",
83
- "@genesislcap/rapid-design-system": "14.434.0",
84
- "@genesislcap/web-core": "14.434.0",
78
+ "@genesislcap/foundation-ai": "14.436.0",
79
+ "@genesislcap/foundation-logger": "14.436.0",
80
+ "@genesislcap/foundation-redux": "14.436.0",
81
+ "@genesislcap/foundation-ui": "14.436.0",
82
+ "@genesislcap/foundation-utils": "14.436.0",
83
+ "@genesislcap/rapid-design-system": "14.436.0",
84
+ "@genesislcap/web-core": "14.436.0",
85
85
  "dompurify": "^3.3.1",
86
86
  "marked": "^17.0.3"
87
87
  },
@@ -93,5 +93,5 @@
93
93
  "publishConfig": {
94
94
  "access": "public"
95
95
  },
96
- "gitHead": "ba7b16060050e905ef549b8eed08d67fe4e732d2"
96
+ "gitHead": "87ef9b53cc5f3030651cbc3126bd26f07ffd6819"
97
97
  }
@@ -4,6 +4,7 @@ import type {
4
4
  ChatMessage,
5
5
  ChatToolDefinition,
6
6
  } from '@genesislcap/foundation-ai';
7
+ import type { TurnSnapshot } from '../chat-driver/chat-driver';
7
8
 
8
9
  /** @internal */
9
10
  export interface AllAgentSummary {
@@ -66,4 +67,12 @@ export interface AiDriver extends EventTarget {
66
67
  count: number,
67
68
  allAgentInfo?: AllAgentSummary[],
68
69
  ): Promise<string[]>;
70
+
71
+ /**
72
+ * Per-LLM-call snapshots — what the model saw each turn (prompt, tools,
73
+ * agent state). Used by the host's debug-log exporter. Optional because the
74
+ * interface is implemented by both leaf and orchestrating drivers; the
75
+ * orchestrator just delegates to its inner `ChatDriver`.
76
+ */
77
+ getTurnSnapshots?(): ReadonlyArray<TurnSnapshot>;
69
78
  }