@genesislcap/ai-assistant 14.443.1 → 14.444.1

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.
@@ -4805,6 +4805,36 @@
4805
4805
  "isProtected": false,
4806
4806
  "isAbstract": false
4807
4807
  },
4808
+ {
4809
+ "kind": "Property",
4810
+ "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#flowOwnerAgentName:member",
4811
+ "docComment": "/**\n * Name of the agent that currently owns a guided flow. Set by the orchestrator when a stateful agent's `onActivate` fires, cleared when the agent calls `releaseAgent` from a terminal-state tool handler. While non-null, routing precedence goes to this agent and the picker is locked.\n *\n * Independent of `pinnedAgentName`: a flow can complete and release without disturbing the user's sticky picker choice.\n */\n",
4812
+ "excerptTokens": [
4813
+ {
4814
+ "kind": "Content",
4815
+ "text": "get flowOwnerAgentName(): "
4816
+ },
4817
+ {
4818
+ "kind": "Content",
4819
+ "text": "string | null"
4820
+ },
4821
+ {
4822
+ "kind": "Content",
4823
+ "text": ";\n\nset flowOwnerAgentName(value: string | null);"
4824
+ }
4825
+ ],
4826
+ "isReadonly": false,
4827
+ "isOptional": false,
4828
+ "releaseTag": "Beta",
4829
+ "name": "flowOwnerAgentName",
4830
+ "propertyTypeTokenRange": {
4831
+ "startIndex": 1,
4832
+ "endIndex": 2
4833
+ },
4834
+ "isStatic": false,
4835
+ "isProtected": false,
4836
+ "isAbstract": false
4837
+ },
4808
4838
  {
4809
4839
  "kind": "Method",
4810
4840
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#getDebugLog:member(1)",
@@ -5587,7 +5617,7 @@
5587
5617
  {
5588
5618
  "kind": "Property",
5589
5619
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#pinLocked:member",
5590
- "docComment": "/**\n * The pin is locked when a stateful agent (one with lifecycle hooks) is *actively running* — i.e. its `onActivate` has fired and it owns live state. Until the user sends their first message, a freshly pinned stateful agent is not yet active and the picker should remain free; the user might change their mind and unpin without anything to clean up.\n *\n * We derive from `activeAgent` (set by the orchestrator after `onActivate` completes) rather than `pinnedAgentName` (set immediately on picker click). The serialized `activeAgent` strips lifecycle hooks, so we look up the live config from `this.agents` to check for them.\n */\n",
5620
+ "docComment": "/**\n * The pin is locked when a stateful flow is mid-stream — i.e. some agent's `onActivate` has fired and it owns live state, and it hasn't yet called `releaseAgent`. Derives directly from `flowOwnerAgentName`, the single source of truth the orchestrator writes on activation and clears on release. Until the user sends their first message, a freshly pinned stateful agent is not yet active and the picker should remain free; the user might change their mind and unpin without anything to clean up.\n */\n",
5591
5621
  "excerptTokens": [
5592
5622
  {
5593
5623
  "kind": "Content",
@@ -5677,7 +5707,7 @@
5677
5707
  {
5678
5708
  "kind": "Property",
5679
5709
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#pinnedAgentName:member",
5680
- "docComment": "/**\n * Name of the agent the user has pinned via the agent picker. `null` means automatic routing (Auto). Persisted on the session store, so it survives pop-in/pop-out but resets on page refresh.\n */\n",
5710
+ "docComment": "/**\n * Name of the agent the user has pinned via the agent picker. `null` means automatic routing (Auto). Persisted on the session store, so it survives pop-in/pop-out but resets on page refresh.\n *\n * Only changes on explicit user action (picker click or host's `setAgent`). The orchestrator's auto-pin on stateful activation writes `flowOwnerAgentName` instead, so finishing a guided flow doesn't wipe the user's sticky pick.\n */\n",
5681
5711
  "excerptTokens": [
5682
5712
  {
5683
5713
  "kind": "Content",
@@ -7756,6 +7786,54 @@
7756
7786
  "isAbstract": false,
7757
7787
  "name": "sendMessage"
7758
7788
  },
7789
+ {
7790
+ "kind": "Method",
7791
+ "canonicalReference": "@genesislcap/ai-assistant!OrchestratingDriver#setFlowOwner:member(1)",
7792
+ "docComment": "/**\n * Set the flow-owner lock — used to restore state from Redux on driver reconstruction (agents-array changes, pop-in/pop-out). Not intended for external callers to manipulate routing; that's what `setPinnedAgent` is for.\n */\n",
7793
+ "excerptTokens": [
7794
+ {
7795
+ "kind": "Content",
7796
+ "text": "setFlowOwner(name: "
7797
+ },
7798
+ {
7799
+ "kind": "Content",
7800
+ "text": "string | null"
7801
+ },
7802
+ {
7803
+ "kind": "Content",
7804
+ "text": "): "
7805
+ },
7806
+ {
7807
+ "kind": "Content",
7808
+ "text": "void"
7809
+ },
7810
+ {
7811
+ "kind": "Content",
7812
+ "text": ";"
7813
+ }
7814
+ ],
7815
+ "isStatic": false,
7816
+ "returnTypeTokenRange": {
7817
+ "startIndex": 3,
7818
+ "endIndex": 4
7819
+ },
7820
+ "releaseTag": "Beta",
7821
+ "isProtected": false,
7822
+ "overloadIndex": 1,
7823
+ "parameters": [
7824
+ {
7825
+ "parameterName": "name",
7826
+ "parameterTypeTokenRange": {
7827
+ "startIndex": 1,
7828
+ "endIndex": 2
7829
+ },
7830
+ "isOptional": false
7831
+ }
7832
+ ],
7833
+ "isOptional": false,
7834
+ "isAbstract": false,
7835
+ "name": "setFlowOwner"
7836
+ },
7759
7837
  {
7760
7838
  "kind": "Method",
7761
7839
  "canonicalReference": "@genesislcap/ai-assistant!OrchestratingDriver#setPinnedAgent:member(1)",
@@ -986,9 +986,25 @@ export declare class FoundationAiAssistant extends GenesisElement {
986
986
  * Name of the agent the user has pinned via the agent picker. `null` means
987
987
  * automatic routing (Auto). Persisted on the session store, so it survives
988
988
  * pop-in/pop-out but resets on page refresh.
989
+ *
990
+ * Only changes on explicit user action (picker click or host's `setAgent`).
991
+ * The orchestrator's auto-pin on stateful activation writes
992
+ * `flowOwnerAgentName` instead, so finishing a guided flow doesn't wipe
993
+ * the user's sticky pick.
989
994
  */
990
995
  get pinnedAgentName(): string | null;
991
996
  set pinnedAgentName(value: string | null);
997
+ /**
998
+ * Name of the agent that currently owns a guided flow. Set by the
999
+ * orchestrator when a stateful agent's `onActivate` fires, cleared when the
1000
+ * agent calls `releaseAgent` from a terminal-state tool handler. While
1001
+ * non-null, routing precedence goes to this agent and the picker is locked.
1002
+ *
1003
+ * Independent of `pinnedAgentName`: a flow can complete and release
1004
+ * without disturbing the user's sticky picker choice.
1005
+ */
1006
+ get flowOwnerAgentName(): string | null;
1007
+ set flowOwnerAgentName(value: string | null);
992
1008
  get liveSubAgentTrace(): ChatMessage[];
993
1009
  set liveSubAgentTrace(value: ChatMessage[]);
994
1010
  get liveSubAgentName(): string | null;
@@ -1160,16 +1176,13 @@ export declare class FoundationAiAssistant extends GenesisElement {
1160
1176
  /** Hint text for the currently pinned agent, if any. Used in the toggle button tooltip. */
1161
1177
  get pinnedAgentHint(): string | undefined;
1162
1178
  /**
1163
- * The pin is locked when a stateful agent (one with lifecycle hooks) is
1164
- * *actively running* — i.e. its `onActivate` has fired and it owns live
1165
- * state. Until the user sends their first message, a freshly pinned stateful
1166
- * agent is not yet active and the picker should remain free; the user might
1167
- * change their mind and unpin without anything to clean up.
1168
- *
1169
- * We derive from `activeAgent` (set by the orchestrator after `onActivate`
1170
- * completes) rather than `pinnedAgentName` (set immediately on picker
1171
- * click). The serialized `activeAgent` strips lifecycle hooks, so we look
1172
- * up the live config from `this.agents` to check for them.
1179
+ * The pin is locked when a stateful flow is mid-stream i.e. some agent's
1180
+ * `onActivate` has fired and it owns live state, and it hasn't yet called
1181
+ * `releaseAgent`. Derives directly from `flowOwnerAgentName`, the
1182
+ * single source of truth the orchestrator writes on activation and clears
1183
+ * on release. Until the user sends their first message, a freshly pinned
1184
+ * stateful agent is not yet active and the picker should remain free; the
1185
+ * user might change their mind and unpin without anything to clean up.
1173
1186
  */
1174
1187
  get pinLocked(): boolean;
1175
1188
  /** Tooltip shown on the picker toggle button. */
@@ -1408,7 +1421,21 @@ export declare class OrchestratingDriver extends EventTarget implements AiDriver
1408
1421
  * so long-running `onActivate` work can bail if the session disconnects.
1409
1422
  */
1410
1423
  private readonly lifecycleAbortController;
1424
+ /**
1425
+ * Sticky user pick from the picker (or the host's `setAgent` API). Only
1426
+ * changes on explicit user action. Survives flow completion: when a stateful
1427
+ * agent releases, the user-pin stays put and routing falls back to it on
1428
+ * the next turn.
1429
+ */
1411
1430
  private pinnedAgentName;
1431
+ /**
1432
+ * Transient flow lock — set when a stateful agent's `onActivate` fires,
1433
+ * cleared by `releaseActiveAgent`. While non-null, routing precedence goes
1434
+ * to this agent and the picker is locked. Independent of
1435
+ * `pinnedAgentName` so completing a flow doesn't wipe the user's sticky
1436
+ * choice.
1437
+ */
1438
+ private flowOwnerAgentName;
1412
1439
  activeAgent?: AgentConfig;
1413
1440
  constructor(aiProvider: AIProvider, agents: AgentConfig[], options?: {
1414
1441
  sessionKey?: string;
@@ -1428,6 +1455,13 @@ export declare class OrchestratingDriver extends EventTarget implements AiDriver
1428
1455
  * return to automatic routing.
1429
1456
  */
1430
1457
  setPinnedAgent(name: string | null): void;
1458
+ /**
1459
+ * Set the flow-owner lock — used to restore state from Redux on driver
1460
+ * reconstruction (agents-array changes, pop-in/pop-out). Not intended for
1461
+ * external callers to manipulate routing; that's what `setPinnedAgent` is
1462
+ * for.
1463
+ */
1464
+ setFlowOwner(name: string | null): void;
1431
1465
  loadHistory(messages: ChatMessage[]): void;
1432
1466
  getRawHistory(): readonly ChatMessage[];
1433
1467
  /** Delegates to the inner {@link ChatDriver} — turns are captured there. */
@@ -1437,9 +1471,15 @@ export declare class OrchestratingDriver extends EventTarget implements AiDriver
1437
1471
  continueFromHistory(transientPrimer?: ChatMessage[]): Promise<ChatDriverResult>;
1438
1472
  private applyAgent;
1439
1473
  /**
1440
- * Release the current stateful agent: fire `onDeactivate`, clear the pin,
1441
- * dispatch events so the host (and Redux) reflect the unpinned state. Called
1442
- * automatically when a tool handler invokes `context.releaseAgent`.
1474
+ * Release the current stateful agent: fire `onDeactivate`, clear the
1475
+ * flow-owner lock, dispatch events so the host (and Redux) reflect the
1476
+ * unlocked state. Called automatically when a tool handler invokes
1477
+ * `context.releaseAgent`.
1478
+ *
1479
+ * The user-pin (`pinnedAgentName`) is intentionally *not* cleared — a user
1480
+ * who explicitly picked this agent should stay on it after the flow ends,
1481
+ * so the next message starts a fresh flow on the same agent rather than
1482
+ * routing through the classifier.
1443
1483
  */
1444
1484
  private releaseActiveAgent;
1445
1485
  /**
@@ -1450,9 +1490,12 @@ export declare class OrchestratingDriver extends EventTarget implements AiDriver
1450
1490
  dispose(): Promise<void>;
1451
1491
  private classify;
1452
1492
  /**
1453
- * Returns the pinned agent if `pinnedAgentName` matches a known specialist or
1454
- * fallback. Logs and returns `undefined` if pinned to a name that no longer
1455
- * exists in the agents array — caller falls back to the classifier.
1493
+ * Returns the agent that the current turn is locked to, if any. Flow-owner
1494
+ * (transient, set by stateful-agent activation) wins over user-pin (sticky,
1495
+ * set by picker click). `undefined` falls back to the classifier.
1496
+ *
1497
+ * Logs and returns `undefined` if a name no longer matches the agents array
1498
+ * — protects against stale state after an agents-array reassignment.
1456
1499
  */
1457
1500
  private resolvePinnedAgent;
1458
1501
  private appendInlineMessage;
@@ -24,7 +24,21 @@ export declare class OrchestratingDriver extends EventTarget implements AiDriver
24
24
  * so long-running `onActivate` work can bail if the session disconnects.
25
25
  */
26
26
  private readonly lifecycleAbortController;
27
+ /**
28
+ * Sticky user pick from the picker (or the host's `setAgent` API). Only
29
+ * changes on explicit user action. Survives flow completion: when a stateful
30
+ * agent releases, the user-pin stays put and routing falls back to it on
31
+ * the next turn.
32
+ */
27
33
  private pinnedAgentName;
34
+ /**
35
+ * Transient flow lock — set when a stateful agent's `onActivate` fires,
36
+ * cleared by `releaseActiveAgent`. While non-null, routing precedence goes
37
+ * to this agent and the picker is locked. Independent of
38
+ * `pinnedAgentName` so completing a flow doesn't wipe the user's sticky
39
+ * choice.
40
+ */
41
+ private flowOwnerAgentName;
28
42
  activeAgent?: AgentConfig;
29
43
  constructor(aiProvider: AIProvider, agents: AgentConfig[], options?: {
30
44
  sessionKey?: string;
@@ -44,6 +58,13 @@ export declare class OrchestratingDriver extends EventTarget implements AiDriver
44
58
  * return to automatic routing.
45
59
  */
46
60
  setPinnedAgent(name: string | null): void;
61
+ /**
62
+ * Set the flow-owner lock — used to restore state from Redux on driver
63
+ * reconstruction (agents-array changes, pop-in/pop-out). Not intended for
64
+ * external callers to manipulate routing; that's what `setPinnedAgent` is
65
+ * for.
66
+ */
67
+ setFlowOwner(name: string | null): void;
47
68
  loadHistory(messages: ChatMessage[]): void;
48
69
  getRawHistory(): readonly ChatMessage[];
49
70
  /** Delegates to the inner {@link ChatDriver} — turns are captured there. */
@@ -53,9 +74,15 @@ export declare class OrchestratingDriver extends EventTarget implements AiDriver
53
74
  continueFromHistory(transientPrimer?: ChatMessage[]): Promise<ChatDriverResult>;
54
75
  private applyAgent;
55
76
  /**
56
- * Release the current stateful agent: fire `onDeactivate`, clear the pin,
57
- * dispatch events so the host (and Redux) reflect the unpinned state. Called
58
- * automatically when a tool handler invokes `context.releaseAgent`.
77
+ * Release the current stateful agent: fire `onDeactivate`, clear the
78
+ * flow-owner lock, dispatch events so the host (and Redux) reflect the
79
+ * unlocked state. Called automatically when a tool handler invokes
80
+ * `context.releaseAgent`.
81
+ *
82
+ * The user-pin (`pinnedAgentName`) is intentionally *not* cleared — a user
83
+ * who explicitly picked this agent should stay on it after the flow ends,
84
+ * so the next message starts a fresh flow on the same agent rather than
85
+ * routing through the classifier.
59
86
  */
60
87
  private releaseActiveAgent;
61
88
  /**
@@ -66,9 +93,12 @@ export declare class OrchestratingDriver extends EventTarget implements AiDriver
66
93
  dispose(): Promise<void>;
67
94
  private classify;
68
95
  /**
69
- * Returns the pinned agent if `pinnedAgentName` matches a known specialist or
70
- * fallback. Logs and returns `undefined` if pinned to a name that no longer
71
- * exists in the agents array — caller falls back to the classifier.
96
+ * Returns the agent that the current turn is locked to, if any. Flow-owner
97
+ * (transient, set by stateful-agent activation) wins over user-pin (sticky,
98
+ * set by picker click). `undefined` falls back to the classifier.
99
+ *
100
+ * Logs and returns `undefined` if a name no longer matches the agents array
101
+ * — protects against stale state after an agents-array reassignment.
72
102
  */
73
103
  private resolvePinnedAgent;
74
104
  private appendInlineMessage;
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrating-driver.d.ts","sourceRoot":"","sources":["../../../../src/components/orchestrating-driver/orchestrating-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,WAAW,EAEZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EACV,WAAW,EAKZ,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,4BAA4B,CAAC;AAoDpC;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,WAAY,YAAW,QAAQ;IAkBpE,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAlBzB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0B;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAsB;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAyB;IAClE,OAAO,CAAC,eAAe,CAAuB;IAE9C,WAAW,CAAC,EAAE,WAAW,CAAC;gBAGP,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,WAAW,EAAE,EACtC,OAAO,GAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,uBAAuB,CAAC,EAAE,MAAM,CAAC;QACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;KACtB;IA8DR,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAIhE,MAAM,IAAI,OAAO;IAIjB;;;;;OAKG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIzC,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI;IAI1C,aAAa,IAAI,SAAS,WAAW,EAAE;IAIvC,4EAA4E;IAC5E,gBAAgB,IAAI,aAAa,CAAC,YAAY,CAAC;IAIzC,cAAc,CAClB,OAAO,EAAE,WAAW,EAAE,EACtB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,eAAe,EAAE,GAC/B,OAAO,CAAC,MAAM,EAAE,CAAC;IAgBd,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwErF,mBAAmB,CAAC,eAAe,CAAC,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAIvE,UAAU;IAgFxB;;;;OAIG;YACW,kBAAkB;IAuBhC;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAiBhB,QAAQ;IA+FtB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,mBAAmB;CAO5B"}
1
+ {"version":3,"file":"orchestrating-driver.d.ts","sourceRoot":"","sources":["../../../../src/components/orchestrating-driver/orchestrating-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,WAAW,EAEZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EACV,WAAW,EAKZ,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,4BAA4B,CAAC;AAoDpC;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,WAAY,YAAW,QAAQ;IAgCpE,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAhCzB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0B;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAsB;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAyB;IAClE;;;;;OAKG;IACH,OAAO,CAAC,eAAe,CAAuB;IAC9C;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB,CAAuB;IAEjD,WAAW,CAAC,EAAE,WAAW,CAAC;gBAGP,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,WAAW,EAAE,EACtC,OAAO,GAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,uBAAuB,CAAC,EAAE,MAAM,CAAC;QACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;KACtB;IA8DR,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAIhE,MAAM,IAAI,OAAO;IAIjB;;;;;OAKG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIzC;;;;;OAKG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIvC,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI;IAI1C,aAAa,IAAI,SAAS,WAAW,EAAE;IAIvC,4EAA4E;IAC5E,gBAAgB,IAAI,aAAa,CAAC,YAAY,CAAC;IAIzC,cAAc,CAClB,OAAO,EAAE,WAAW,EAAE,EACtB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,eAAe,EAAE,GAC/B,OAAO,CAAC,MAAM,EAAE,CAAC;IAgBd,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwErF,mBAAmB,CAAC,eAAe,CAAC,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAIvE,UAAU;IAuFxB;;;;;;;;;;OAUG;YACW,kBAAkB;IAuBhC;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAiBhB,QAAQ;IA+FtB;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,mBAAmB;CAO5B"}
@@ -100,9 +100,25 @@ export declare class FoundationAiAssistant extends GenesisElement {
100
100
  * Name of the agent the user has pinned via the agent picker. `null` means
101
101
  * automatic routing (Auto). Persisted on the session store, so it survives
102
102
  * pop-in/pop-out but resets on page refresh.
103
+ *
104
+ * Only changes on explicit user action (picker click or host's `setAgent`).
105
+ * The orchestrator's auto-pin on stateful activation writes
106
+ * `flowOwnerAgentName` instead, so finishing a guided flow doesn't wipe
107
+ * the user's sticky pick.
103
108
  */
104
109
  get pinnedAgentName(): string | null;
105
110
  set pinnedAgentName(value: string | null);
111
+ /**
112
+ * Name of the agent that currently owns a guided flow. Set by the
113
+ * orchestrator when a stateful agent's `onActivate` fires, cleared when the
114
+ * agent calls `releaseAgent` from a terminal-state tool handler. While
115
+ * non-null, routing precedence goes to this agent and the picker is locked.
116
+ *
117
+ * Independent of `pinnedAgentName`: a flow can complete and release
118
+ * without disturbing the user's sticky picker choice.
119
+ */
120
+ get flowOwnerAgentName(): string | null;
121
+ set flowOwnerAgentName(value: string | null);
106
122
  get liveSubAgentTrace(): ChatMessage[];
107
123
  set liveSubAgentTrace(value: ChatMessage[]);
108
124
  get liveSubAgentName(): string | null;
@@ -274,16 +290,13 @@ export declare class FoundationAiAssistant extends GenesisElement {
274
290
  /** Hint text for the currently pinned agent, if any. Used in the toggle button tooltip. */
275
291
  get pinnedAgentHint(): string | undefined;
276
292
  /**
277
- * The pin is locked when a stateful agent (one with lifecycle hooks) is
278
- * *actively running* — i.e. its `onActivate` has fired and it owns live
279
- * state. Until the user sends their first message, a freshly pinned stateful
280
- * agent is not yet active and the picker should remain free; the user might
281
- * change their mind and unpin without anything to clean up.
282
- *
283
- * We derive from `activeAgent` (set by the orchestrator after `onActivate`
284
- * completes) rather than `pinnedAgentName` (set immediately on picker
285
- * click). The serialized `activeAgent` strips lifecycle hooks, so we look
286
- * up the live config from `this.agents` to check for them.
293
+ * The pin is locked when a stateful flow is mid-stream i.e. some agent's
294
+ * `onActivate` has fired and it owns live state, and it hasn't yet called
295
+ * `releaseAgent`. Derives directly from `flowOwnerAgentName`, the
296
+ * single source of truth the orchestrator writes on activation and clears
297
+ * on release. Until the user sends their first message, a freshly pinned
298
+ * stateful agent is not yet active and the picker should remain free; the
299
+ * user might change their mind and unpin without anything to clean up.
287
300
  */
288
301
  get pinLocked(): boolean;
289
302
  /** Tooltip shown on the picker toggle button. */
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/main/main.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/main/main.ts"],"names":[],"mappings":"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"}
@@ -40,9 +40,20 @@ export interface AiAssistantSessionState {
40
40
  /**
41
41
  * Name of the agent the user has pinned via the picker. `null` means the
42
42
  * orchestrator chooses each turn (Auto). Survives lifecycle events but
43
- * resets on page refresh.
43
+ * resets on page refresh. Only changed by explicit user picker action (or
44
+ * the host's `setAgent` API) — never by the orchestrator's auto-pin on
45
+ * stateful activation. See {@link flowOwnerAgentName} for that.
44
46
  */
45
47
  pinnedAgentName: string | null;
48
+ /**
49
+ * Name of the agent that currently owns a guided flow — set by the
50
+ * orchestrator when a stateful agent's `onActivate` fires, cleared by
51
+ * `releaseAgent` or session teardown. While non-null, routing precedence
52
+ * goes to this agent and the picker is locked. Independent of
53
+ * {@link pinnedAgentName}: a flow can wrap up and release without
54
+ * disturbing the user's sticky picker choice.
55
+ */
56
+ flowOwnerAgentName: string | null;
46
57
  /**
47
58
  * Whether the agent picker slide-out panel is currently open. Lives in the
48
59
  * slice so the bubble-dialog and popout-panel instances stay in sync as the
@@ -78,6 +89,7 @@ export declare const aiAssistantSlice: import("@reduxjs/toolkit").Slice<AiAssist
78
89
  setActiveModel(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | undefined>): void;
79
90
  setActiveAgent(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<Omit<AgentConfig, "toolHandlers"> | undefined>): void;
80
91
  setPinnedAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | null>): void;
92
+ setFlowOwnerAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | null>): void;
81
93
  setAgentPickerOpen(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<boolean>): void;
82
94
  setInputValue(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string>): void;
83
95
  setLiveSubAgentTrace(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<ChatMessage[]>): void;
@@ -1 +1 @@
1
- {"version":3,"file":"ai-assistant-slice.d.ts","sourceRoot":"","sources":["../../../src/state/ai-assistant-slice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC5F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAEnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEnG;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,IAAI,EAAE,4BAA4B,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,wBAAwB,EAAE,OAAO,CAAC;IAClC,iBAAiB,EAAE,oBAAoB,EAAE,CAAC;IAC1C,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,kEAAkE;IAClE,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,SAAS,CAAC;IAC3D;;;;OAIG;IACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,iBAAiB,EAAE,WAAW,EAAE,CAAC;IACjC,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;;;OAKG;IACH,cAAc,EAAE,aAAa,EAAE,CAAC;CACjC;AAED,eAAO,MAAM,mBAAmB,EAAE,uBAmBjC,CAAC;AAEF,eAAO,MAAM,gBAAgB;uFAIE,aAAa,CAAC,WAAW,EAAE,CAAC;oFAG/B,aAAa,CAAC,gBAAgB,CAAC;4FAGvB,aAAa,CAAC,OAAO,CAAC;gGAGlB,aAAa,CAAC,OAAO,CAAC;uGAGf,aAAa,CAAC,OAAO,CAAC;gGAG7B,aAAa,CAAC,oBAAoB,EAAE,CAAC;+FAGtC,aAAa,CAAC,gBAAgB,CAAC;4FAGlC,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;2FAGlC,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;6FAG/B,aAAa,CAAC,MAAM,CAAC;0FAGxB,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;0FAGjC,aAAa,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,SAAS,CAAC;8FAGxD,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;8FAG5B,aAAa,CAAC,OAAO,CAAC;yFAG3B,aAAa,CAAC,MAAM,CAAC;gGAGd,aAAa,CAAC,WAAW,EAAE,CAAC;+FAG7B,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;4FAG/B,aAAa,CAAC,aAAa,CAAC;+FAGzB,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;oCAKlE,CAAC"}
1
+ {"version":3,"file":"ai-assistant-slice.d.ts","sourceRoot":"","sources":["../../../src/state/ai-assistant-slice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC5F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAEnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEnG;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,IAAI,EAAE,4BAA4B,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,wBAAwB,EAAE,OAAO,CAAC;IAClC,iBAAiB,EAAE,oBAAoB,EAAE,CAAC;IAC1C,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,kEAAkE;IAClE,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,SAAS,CAAC;IAC3D;;;;;;OAMG;IACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;OAOG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,iBAAiB,EAAE,WAAW,EAAE,CAAC;IACjC,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;;;OAKG;IACH,cAAc,EAAE,aAAa,EAAE,CAAC;CACjC;AAED,eAAO,MAAM,mBAAmB,EAAE,uBAoBjC,CAAC;AAEF,eAAO,MAAM,gBAAgB;uFAIE,aAAa,CAAC,WAAW,EAAE,CAAC;oFAG/B,aAAa,CAAC,gBAAgB,CAAC;4FAGvB,aAAa,CAAC,OAAO,CAAC;gGAGlB,aAAa,CAAC,OAAO,CAAC;uGAGf,aAAa,CAAC,OAAO,CAAC;gGAG7B,aAAa,CAAC,oBAAoB,EAAE,CAAC;+FAGtC,aAAa,CAAC,gBAAgB,CAAC;4FAGlC,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;2FAGlC,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;6FAG/B,aAAa,CAAC,MAAM,CAAC;0FAGxB,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;0FAGjC,aAAa,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,SAAS,CAAC;8FAGxD,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;iGAGzB,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;8FAG/B,aAAa,CAAC,OAAO,CAAC;yFAG3B,aAAa,CAAC,MAAM,CAAC;gGAGd,aAAa,CAAC,WAAW,EAAE,CAAC;+FAG7B,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;4FAG/B,aAAa,CAAC,aAAa,CAAC;+FAGzB,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;oCAKlE,CAAC"}
@@ -14,6 +14,7 @@ declare function makeStore(devTools: boolean | undefined): import("@genesislcap/
14
14
  setActiveModel(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | undefined>): void;
15
15
  setActiveAgent(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<Omit<import("..").AgentConfig, "toolHandlers"> | undefined>): void;
16
16
  setPinnedAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | null>): void;
17
+ setFlowOwnerAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | null>): void;
17
18
  setAgentPickerOpen(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<boolean>): void;
18
19
  setInputValue(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string>): void;
19
20
  setLiveSubAgentTrace(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<import("@genesislcap/foundation-ai").ChatMessage[]>): void;
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../src/state/session-store.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAIvD,iBAAS,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;wCAM/C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAMnF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAC"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../src/state/session-store.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAIvD,iBAAS,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;wCAM/C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAMnF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAC"}
@@ -60,7 +60,21 @@ export class OrchestratingDriver extends EventTarget {
60
60
  * so long-running `onActivate` work can bail if the session disconnects.
61
61
  */
62
62
  this.lifecycleAbortController = new AbortController();
63
+ /**
64
+ * Sticky user pick from the picker (or the host's `setAgent` API). Only
65
+ * changes on explicit user action. Survives flow completion: when a stateful
66
+ * agent releases, the user-pin stays put and routing falls back to it on
67
+ * the next turn.
68
+ */
63
69
  this.pinnedAgentName = null;
70
+ /**
71
+ * Transient flow lock — set when a stateful agent's `onActivate` fires,
72
+ * cleared by `releaseActiveAgent`. While non-null, routing precedence goes
73
+ * to this agent and the picker is locked. Independent of
74
+ * `pinnedAgentName` so completing a flow doesn't wipe the user's sticky
75
+ * choice.
76
+ */
77
+ this.flowOwnerAgentName = null;
64
78
  this.sessionKey = (_a = options.sessionKey) !== null && _a !== void 0 ? _a : '';
65
79
  this.maxHandoffs = (_b = options.maxHandoffs) !== null && _b !== void 0 ? _b : DEFAULT_MAX_HANDOFFS;
66
80
  this.classifierHistoryLength =
@@ -112,6 +126,15 @@ export class OrchestratingDriver extends EventTarget {
112
126
  setPinnedAgent(name) {
113
127
  this.pinnedAgentName = name;
114
128
  }
129
+ /**
130
+ * Set the flow-owner lock — used to restore state from Redux on driver
131
+ * reconstruction (agents-array changes, pop-in/pop-out). Not intended for
132
+ * external callers to manipulate routing; that's what `setPinnedAgent` is
133
+ * for.
134
+ */
135
+ setFlowOwner(name) {
136
+ this.flowOwnerAgentName = name;
137
+ }
115
138
  loadHistory(messages) {
116
139
  this.chatDriver.loadHistory(messages);
117
140
  }
@@ -238,24 +261,30 @@ export class OrchestratingDriver extends EventTarget {
238
261
  }
239
262
  }
240
263
  const hasLifecycleHooks = !!(agent.onActivate || agent.onDeactivate);
241
- // Stateful agents auto-pin on activation. The pin guarantees the machine
242
- // survives subsequent turns (the classifier would otherwise be free to
243
- // route away mid-flow, tearing the machine down). Release happens when the
244
- // agent calls `releaseAgent` from a terminal-state tool handler — see the
245
- // post-sendMessage check below.
246
- if (isSwitch && hasLifecycleHooks && this.pinnedAgentName !== agent.name) {
247
- this.pinnedAgentName = agent.name;
248
- this.dispatchEvent(new CustomEvent('pinned-changed', { detail: agent.name }));
264
+ // Stateful agents auto-lock the flow on activation. The lock guarantees
265
+ // the machine survives subsequent turns (the classifier would otherwise be
266
+ // free to route away mid-flow, tearing the machine down). Release happens
267
+ // when the agent calls `releaseAgent` from a terminal-state tool handler
268
+ // — see the post-sendMessage check below. We write `flowOwnerAgentName`
269
+ // rather than `pinnedAgentName` so completing a flow doesn't wipe the
270
+ // user's sticky picker choice.
271
+ if (isSwitch && hasLifecycleHooks && this.flowOwnerAgentName !== agent.name) {
272
+ this.flowOwnerAgentName = agent.name;
273
+ this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: agent.name }));
249
274
  }
250
- // Terminal agents do not get the cross-agent handoff tool. Three cases:
251
- // • fallback — already a leaf; handoff would loop
252
- // • pinned — user explicitly selected this agent; do not auto-route away
253
- // • stateful agents with lifecycle hooks own state for the duration of
254
- // their flow. Initiating a handoff mid-flow would abandon
255
- // that state with no clean exit and dump the user into the
256
- // classifier mid-machine. Capture the tool loop until the
257
- // user (or the agent itself, via `releaseAgent`) releases.
258
- const isTerminal = isFallback(agent) || this.pinnedAgentName !== null || hasLifecycleHooks;
275
+ // Terminal agents do not get the cross-agent handoff tool. Four cases:
276
+ // • fallback — already a leaf; handoff would loop
277
+ // • user-pinned — user explicitly selected this agent; do not auto-route away
278
+ // • flow-owner a stateful flow is mid-stream; do not auto-route away
279
+ // stateful — agents with lifecycle hooks own state for the duration of
280
+ // their flow. Initiating a handoff mid-flow would abandon
281
+ // that state with no clean exit and dump the user into the
282
+ // classifier mid-machine. Capture the tool loop until the
283
+ // user (or the agent itself, via `releaseAgent`) releases.
284
+ const isTerminal = isFallback(agent) ||
285
+ this.pinnedAgentName !== null ||
286
+ this.flowOwnerAgentName !== null ||
287
+ hasLifecycleHooks;
259
288
  let agentToApply = agent;
260
289
  if (!isTerminal) {
261
290
  const declaredTools = agent.toolDefinitions;
@@ -280,9 +309,15 @@ export class OrchestratingDriver extends EventTarget {
280
309
  });
281
310
  }
282
311
  /**
283
- * Release the current stateful agent: fire `onDeactivate`, clear the pin,
284
- * dispatch events so the host (and Redux) reflect the unpinned state. Called
285
- * automatically when a tool handler invokes `context.releaseAgent`.
312
+ * Release the current stateful agent: fire `onDeactivate`, clear the
313
+ * flow-owner lock, dispatch events so the host (and Redux) reflect the
314
+ * unlocked state. Called automatically when a tool handler invokes
315
+ * `context.releaseAgent`.
316
+ *
317
+ * The user-pin (`pinnedAgentName`) is intentionally *not* cleared — a user
318
+ * who explicitly picked this agent should stay on it after the flow ends,
319
+ * so the next message starts a fresh flow on the same agent rather than
320
+ * routing through the classifier.
286
321
  */
287
322
  releaseActiveAgent() {
288
323
  return __awaiter(this, void 0, void 0, function* () {
@@ -302,9 +337,9 @@ export class OrchestratingDriver extends EventTarget {
302
337
  }
303
338
  }
304
339
  this.activeAgent = undefined;
305
- if (this.pinnedAgentName !== null) {
306
- this.pinnedAgentName = null;
307
- this.dispatchEvent(new CustomEvent('pinned-changed', { detail: null }));
340
+ if (this.flowOwnerAgentName !== null) {
341
+ this.flowOwnerAgentName = null;
342
+ this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: null }));
308
343
  }
309
344
  this.dispatchEvent(new CustomEvent('agent-released', { detail: agent }));
310
345
  this.dispatchEvent(new CustomEvent('agent-changed', { detail: undefined }));
@@ -418,16 +453,21 @@ export class OrchestratingDriver extends EventTarget {
418
453
  });
419
454
  }
420
455
  /**
421
- * Returns the pinned agent if `pinnedAgentName` matches a known specialist or
422
- * fallback. Logs and returns `undefined` if pinned to a name that no longer
423
- * exists in the agents array — caller falls back to the classifier.
456
+ * Returns the agent that the current turn is locked to, if any. Flow-owner
457
+ * (transient, set by stateful-agent activation) wins over user-pin (sticky,
458
+ * set by picker click). `undefined` falls back to the classifier.
459
+ *
460
+ * Logs and returns `undefined` if a name no longer matches the agents array
461
+ * — protects against stale state after an agents-array reassignment.
424
462
  */
425
463
  resolvePinnedAgent() {
426
- if (this.pinnedAgentName === null)
464
+ var _a;
465
+ const name = (_a = this.flowOwnerAgentName) !== null && _a !== void 0 ? _a : this.pinnedAgentName;
466
+ if (name === null)
427
467
  return undefined;
428
- const match = this.agents.find((a) => a.name === this.pinnedAgentName);
468
+ const match = this.agents.find((a) => a.name === name);
429
469
  if (!match) {
430
- logger.warn(`OrchestratingDriver: pinned agent "${this.pinnedAgentName}" not found in agents array — falling back to classifier.`);
470
+ logger.warn(`OrchestratingDriver: pinned agent "${name}" not found in agents array — falling back to classifier.`);
431
471
  return undefined;
432
472
  }
433
473
  return match;
@@ -303,6 +303,11 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
303
303
  * Name of the agent the user has pinned via the agent picker. `null` means
304
304
  * automatic routing (Auto). Persisted on the session store, so it survives
305
305
  * pop-in/pop-out but resets on page refresh.
306
+ *
307
+ * Only changes on explicit user action (picker click or host's `setAgent`).
308
+ * The orchestrator's auto-pin on stateful activation writes
309
+ * `flowOwnerAgentName` instead, so finishing a guided flow doesn't wipe
310
+ * the user's sticky pick.
306
311
  */
307
312
  get pinnedAgentName() {
308
313
  var _a, _b;
@@ -322,6 +327,26 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
322
327
  this.fetchSuggestions();
323
328
  }
324
329
  }
330
+ /**
331
+ * Name of the agent that currently owns a guided flow. Set by the
332
+ * orchestrator when a stateful agent's `onActivate` fires, cleared when the
333
+ * agent calls `releaseAgent` from a terminal-state tool handler. While
334
+ * non-null, routing precedence goes to this agent and the picker is locked.
335
+ *
336
+ * Independent of `pinnedAgentName`: a flow can complete and release
337
+ * without disturbing the user's sticky picker choice.
338
+ */
339
+ get flowOwnerAgentName() {
340
+ var _a, _b;
341
+ return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.flowOwnerAgentName) !== null && _b !== void 0 ? _b : null;
342
+ }
343
+ set flowOwnerAgentName(value) {
344
+ var _a;
345
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setFlowOwnerAgentName(value);
346
+ if (this.driver instanceof OrchestratingDriver) {
347
+ this.driver.setFlowOwner(value);
348
+ }
349
+ }
325
350
  get liveSubAgentTrace() {
326
351
  var _a, _b;
327
352
  return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.liveSubAgentTrace) !== null && _b !== void 0 ? _b : [];
@@ -517,6 +542,13 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
517
542
  if (oldDriver && 'dispose' in oldDriver && typeof oldDriver.dispose === 'function') {
518
543
  void oldDriver.dispose();
519
544
  }
545
+ // Clear any in-flight flow-owner lock from Redux too. The old machine has
546
+ // been disposed; we don't want the new driver re-locking to a flow that
547
+ // no longer has state behind it. User-pin survives — it's the user's
548
+ // sticky picker choice, not flow state.
549
+ if (this.flowOwnerAgentName !== null) {
550
+ this.flowOwnerAgentName = null;
551
+ }
520
552
  this.driver = getOrCreateDriver(key, () => this.createDriver());
521
553
  this.wireDriver();
522
554
  if (history.length)
@@ -679,9 +711,11 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
679
711
  () => driver.removeEventListener('interaction-stop', onInteractionStop),
680
712
  ];
681
713
  if (driver instanceof OrchestratingDriver) {
682
- // Restore any pinned agent from the session store onto the freshly built
683
- // driver, so pop-in/pop-out and agents-array reassignment preserve the pin.
714
+ // Restore the user pin and flow-owner lock from the session store onto
715
+ // the freshly built driver, so pop-in/pop-out and agents-array
716
+ // reassignment preserve both.
684
717
  driver.setPinnedAgent(this.pinnedAgentName);
718
+ driver.setFlowOwner(this.flowOwnerAgentName);
685
719
  const onOrchStart = () => {
686
720
  this.showHalo = 'orchestrating';
687
721
  };
@@ -692,10 +726,12 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
692
726
  const onAgentChanged = (e) => {
693
727
  this.activeAgent = e.detail;
694
728
  };
695
- // Orchestrator-initiated pin changes (auto-pin on stateful activation,
696
- // auto-clear on release). User-initiated pins flow the other direction
697
- // via the `pinnedAgentName` setter, which already calls setPinnedAgent
698
- // back into the driver guard against the redundant round-trip.
729
+ // User-initiated pin changes flow into the driver via the
730
+ // `pinnedAgentName` setter, which already calls setPinnedAgent no
731
+ // event-driven mirror needed in this direction. The driver does still
732
+ // emit `'pinned-changed'` if other code ever calls `setPinnedAgent`
733
+ // directly, so keep the mirror for safety; guard against the redundant
734
+ // round-trip caused by our own setter call.
699
735
  const onPinnedChanged = (e) => {
700
736
  var _a, _b;
701
737
  const name = e.detail;
@@ -703,11 +739,22 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
703
739
  (_b = this._sessionRef) === null || _b === void 0 ? void 0 : _b.actions.aiAssistant.setPinnedAgentName(name);
704
740
  }
705
741
  };
742
+ // Orchestrator-initiated flow-owner changes (auto-lock on stateful
743
+ // activation, auto-clear on release). Mirror to Redux so the UI freeze
744
+ // state and `pinLocked` survive component remount.
745
+ const onFlowOwnerChanged = (e) => {
746
+ var _a, _b;
747
+ const name = e.detail;
748
+ if (((_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.flowOwnerAgentName) !== name) {
749
+ (_b = this._sessionRef) === null || _b === void 0 ? void 0 : _b.actions.aiAssistant.setFlowOwnerAgentName(name);
750
+ }
751
+ };
706
752
  driver.addEventListener('orchestrating-start', onOrchStart);
707
753
  driver.addEventListener('orchestrating-stop', onOrchStop);
708
754
  driver.addEventListener('agent-changed', onAgentChanged);
709
755
  driver.addEventListener('pinned-changed', onPinnedChanged);
710
- cleanups.push(() => driver.removeEventListener('orchestrating-start', onOrchStart), () => driver.removeEventListener('orchestrating-stop', onOrchStop), () => driver.removeEventListener('agent-changed', onAgentChanged), () => driver.removeEventListener('pinned-changed', onPinnedChanged));
756
+ driver.addEventListener('flow-owner-changed', onFlowOwnerChanged);
757
+ cleanups.push(() => driver.removeEventListener('orchestrating-start', onOrchStart), () => driver.removeEventListener('orchestrating-stop', onOrchStop), () => driver.removeEventListener('agent-changed', onAgentChanged), () => driver.removeEventListener('pinned-changed', onPinnedChanged), () => driver.removeEventListener('flow-owner-changed', onFlowOwnerChanged));
711
758
  }
712
759
  this.driverCleanup = () => {
713
760
  for (const fn of cleanups)
@@ -1024,25 +1071,16 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
1024
1071
  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;
1025
1072
  }
1026
1073
  /**
1027
- * The pin is locked when a stateful agent (one with lifecycle hooks) is
1028
- * *actively running* — i.e. its `onActivate` has fired and it owns live
1029
- * state. Until the user sends their first message, a freshly pinned stateful
1030
- * agent is not yet active and the picker should remain free; the user might
1031
- * change their mind and unpin without anything to clean up.
1032
- *
1033
- * We derive from `activeAgent` (set by the orchestrator after `onActivate`
1034
- * completes) rather than `pinnedAgentName` (set immediately on picker
1035
- * click). The serialized `activeAgent` strips lifecycle hooks, so we look
1036
- * up the live config from `this.agents` to check for them.
1074
+ * The pin is locked when a stateful flow is mid-stream i.e. some agent's
1075
+ * `onActivate` has fired and it owns live state, and it hasn't yet called
1076
+ * `releaseAgent`. Derives directly from `flowOwnerAgentName`, the
1077
+ * single source of truth the orchestrator writes on activation and clears
1078
+ * on release. Until the user sends their first message, a freshly pinned
1079
+ * stateful agent is not yet active and the picker should remain free; the
1080
+ * user might change their mind and unpin without anything to clean up.
1037
1081
  */
1038
1082
  get pinLocked() {
1039
- var _a;
1040
- if (!this.activeAgent)
1041
- return false;
1042
- const live = (_a = this.agents) === null || _a === void 0 ? void 0 : _a.find((a) => a.name === this.activeAgent.name);
1043
- if (!live)
1044
- return false;
1045
- return !!(live.onActivate || live.onDeactivate);
1083
+ return this.flowOwnerAgentName !== null;
1046
1084
  }
1047
1085
  /** Tooltip shown on the picker toggle button. */
1048
1086
  get agentToggleTitle() {
@@ -13,6 +13,7 @@ export const defaultSessionState = {
13
13
  activeModel: undefined,
14
14
  activeAgent: undefined,
15
15
  pinnedAgentName: null,
16
+ flowOwnerAgentName: null,
16
17
  agentPickerOpen: false,
17
18
  inputValue: '',
18
19
  liveSubAgentTrace: [],
@@ -62,6 +63,9 @@ export const aiAssistantSlice = createSlice({
62
63
  setPinnedAgentName(state, action) {
63
64
  state.pinnedAgentName = action.payload;
64
65
  },
66
+ setFlowOwnerAgentName(state, action) {
67
+ state.flowOwnerAgentName = action.payload;
68
+ },
65
69
  setAgentPickerOpen(state, action) {
66
70
  state.agentPickerOpen = action.payload;
67
71
  },
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.443.1",
4
+ "version": "14.444.1",
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.443.1",
68
- "@genesislcap/genx": "14.443.1",
69
- "@genesislcap/rollup-builder": "14.443.1",
70
- "@genesislcap/ts-builder": "14.443.1",
71
- "@genesislcap/uvu-playwright-builder": "14.443.1",
72
- "@genesislcap/vite-builder": "14.443.1",
73
- "@genesislcap/webpack-builder": "14.443.1",
67
+ "@genesislcap/foundation-testing": "14.444.1",
68
+ "@genesislcap/genx": "14.444.1",
69
+ "@genesislcap/rollup-builder": "14.444.1",
70
+ "@genesislcap/ts-builder": "14.444.1",
71
+ "@genesislcap/uvu-playwright-builder": "14.444.1",
72
+ "@genesislcap/vite-builder": "14.444.1",
73
+ "@genesislcap/webpack-builder": "14.444.1",
74
74
  "@types/dompurify": "^3.0.5",
75
75
  "@types/marked": "^5.0.2"
76
76
  },
77
77
  "dependencies": {
78
- "@genesislcap/foundation-ai": "14.443.1",
79
- "@genesislcap/foundation-logger": "14.443.1",
80
- "@genesislcap/foundation-redux": "14.443.1",
81
- "@genesislcap/foundation-ui": "14.443.1",
82
- "@genesislcap/foundation-utils": "14.443.1",
83
- "@genesislcap/rapid-design-system": "14.443.1",
84
- "@genesislcap/web-core": "14.443.1",
78
+ "@genesislcap/foundation-ai": "14.444.1",
79
+ "@genesislcap/foundation-logger": "14.444.1",
80
+ "@genesislcap/foundation-redux": "14.444.1",
81
+ "@genesislcap/foundation-ui": "14.444.1",
82
+ "@genesislcap/foundation-utils": "14.444.1",
83
+ "@genesislcap/rapid-design-system": "14.444.1",
84
+ "@genesislcap/web-core": "14.444.1",
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": "a10ca31c4ba209c31a50b507dc473ecd5791fc21"
96
+ "gitHead": "837befe80b4972686cb48fe7d86334c5d819ced6"
97
97
  }
@@ -91,7 +91,21 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
91
91
  * so long-running `onActivate` work can bail if the session disconnects.
92
92
  */
93
93
  private readonly lifecycleAbortController = new AbortController();
94
+ /**
95
+ * Sticky user pick from the picker (or the host's `setAgent` API). Only
96
+ * changes on explicit user action. Survives flow completion: when a stateful
97
+ * agent releases, the user-pin stays put and routing falls back to it on
98
+ * the next turn.
99
+ */
94
100
  private pinnedAgentName: string | null = null;
101
+ /**
102
+ * Transient flow lock — set when a stateful agent's `onActivate` fires,
103
+ * cleared by `releaseActiveAgent`. While non-null, routing precedence goes
104
+ * to this agent and the picker is locked. Independent of
105
+ * `pinnedAgentName` so completing a flow doesn't wipe the user's sticky
106
+ * choice.
107
+ */
108
+ private flowOwnerAgentName: string | null = null;
95
109
 
96
110
  activeAgent?: AgentConfig;
97
111
 
@@ -186,6 +200,16 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
186
200
  this.pinnedAgentName = name;
187
201
  }
188
202
 
203
+ /**
204
+ * Set the flow-owner lock — used to restore state from Redux on driver
205
+ * reconstruction (agents-array changes, pop-in/pop-out). Not intended for
206
+ * external callers to manipulate routing; that's what `setPinnedAgent` is
207
+ * for.
208
+ */
209
+ setFlowOwner(name: string | null): void {
210
+ this.flowOwnerAgentName = name;
211
+ }
212
+
189
213
  loadHistory(messages: ChatMessage[]): void {
190
214
  this.chatDriver.loadHistory(messages);
191
215
  }
@@ -329,25 +353,32 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
329
353
 
330
354
  const hasLifecycleHooks = !!(agent.onActivate || agent.onDeactivate);
331
355
 
332
- // Stateful agents auto-pin on activation. The pin guarantees the machine
333
- // survives subsequent turns (the classifier would otherwise be free to
334
- // route away mid-flow, tearing the machine down). Release happens when the
335
- // agent calls `releaseAgent` from a terminal-state tool handler — see the
336
- // post-sendMessage check below.
337
- if (isSwitch && hasLifecycleHooks && this.pinnedAgentName !== agent.name) {
338
- this.pinnedAgentName = agent.name;
339
- this.dispatchEvent(new CustomEvent('pinned-changed', { detail: agent.name }));
356
+ // Stateful agents auto-lock the flow on activation. The lock guarantees
357
+ // the machine survives subsequent turns (the classifier would otherwise be
358
+ // free to route away mid-flow, tearing the machine down). Release happens
359
+ // when the agent calls `releaseAgent` from a terminal-state tool handler
360
+ // — see the post-sendMessage check below. We write `flowOwnerAgentName`
361
+ // rather than `pinnedAgentName` so completing a flow doesn't wipe the
362
+ // user's sticky picker choice.
363
+ if (isSwitch && hasLifecycleHooks && this.flowOwnerAgentName !== agent.name) {
364
+ this.flowOwnerAgentName = agent.name;
365
+ this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: agent.name }));
340
366
  }
341
367
 
342
- // Terminal agents do not get the cross-agent handoff tool. Three cases:
343
- // • fallback — already a leaf; handoff would loop
344
- // • pinned — user explicitly selected this agent; do not auto-route away
345
- // • stateful agents with lifecycle hooks own state for the duration of
346
- // their flow. Initiating a handoff mid-flow would abandon
347
- // that state with no clean exit and dump the user into the
348
- // classifier mid-machine. Capture the tool loop until the
349
- // user (or the agent itself, via `releaseAgent`) releases.
350
- const isTerminal = isFallback(agent) || this.pinnedAgentName !== null || hasLifecycleHooks;
368
+ // Terminal agents do not get the cross-agent handoff tool. Four cases:
369
+ // • fallback — already a leaf; handoff would loop
370
+ // • user-pinned — user explicitly selected this agent; do not auto-route away
371
+ // • flow-owner a stateful flow is mid-stream; do not auto-route away
372
+ // stateful — agents with lifecycle hooks own state for the duration of
373
+ // their flow. Initiating a handoff mid-flow would abandon
374
+ // that state with no clean exit and dump the user into the
375
+ // classifier mid-machine. Capture the tool loop until the
376
+ // user (or the agent itself, via `releaseAgent`) releases.
377
+ const isTerminal =
378
+ isFallback(agent) ||
379
+ this.pinnedAgentName !== null ||
380
+ this.flowOwnerAgentName !== null ||
381
+ hasLifecycleHooks;
351
382
 
352
383
  let agentToApply: AgentConfig = agent;
353
384
  if (!isTerminal) {
@@ -377,9 +408,15 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
377
408
  }
378
409
 
379
410
  /**
380
- * Release the current stateful agent: fire `onDeactivate`, clear the pin,
381
- * dispatch events so the host (and Redux) reflect the unpinned state. Called
382
- * automatically when a tool handler invokes `context.releaseAgent`.
411
+ * Release the current stateful agent: fire `onDeactivate`, clear the
412
+ * flow-owner lock, dispatch events so the host (and Redux) reflect the
413
+ * unlocked state. Called automatically when a tool handler invokes
414
+ * `context.releaseAgent`.
415
+ *
416
+ * The user-pin (`pinnedAgentName`) is intentionally *not* cleared — a user
417
+ * who explicitly picked this agent should stay on it after the flow ends,
418
+ * so the next message starts a fresh flow on the same agent rather than
419
+ * routing through the classifier.
383
420
  */
384
421
  private async releaseActiveAgent(): Promise<void> {
385
422
  const agent = this.activeAgent;
@@ -396,9 +433,9 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
396
433
  }
397
434
  }
398
435
  this.activeAgent = undefined;
399
- if (this.pinnedAgentName !== null) {
400
- this.pinnedAgentName = null;
401
- this.dispatchEvent(new CustomEvent('pinned-changed', { detail: null }));
436
+ if (this.flowOwnerAgentName !== null) {
437
+ this.flowOwnerAgentName = null;
438
+ this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: null }));
402
439
  }
403
440
  this.dispatchEvent(new CustomEvent('agent-released', { detail: agent }));
404
441
  this.dispatchEvent(new CustomEvent('agent-changed', { detail: undefined }));
@@ -522,16 +559,20 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
522
559
  }
523
560
 
524
561
  /**
525
- * Returns the pinned agent if `pinnedAgentName` matches a known specialist or
526
- * fallback. Logs and returns `undefined` if pinned to a name that no longer
527
- * exists in the agents array — caller falls back to the classifier.
562
+ * Returns the agent that the current turn is locked to, if any. Flow-owner
563
+ * (transient, set by stateful-agent activation) wins over user-pin (sticky,
564
+ * set by picker click). `undefined` falls back to the classifier.
565
+ *
566
+ * Logs and returns `undefined` if a name no longer matches the agents array
567
+ * — protects against stale state after an agents-array reassignment.
528
568
  */
529
569
  private resolvePinnedAgent(): AgentConfig | undefined {
530
- if (this.pinnedAgentName === null) return undefined;
531
- const match = this.agents.find((a) => a.name === this.pinnedAgentName);
570
+ const name = this.flowOwnerAgentName ?? this.pinnedAgentName;
571
+ if (name === null) return undefined;
572
+ const match = this.agents.find((a) => a.name === name);
532
573
  if (!match) {
533
574
  logger.warn(
534
- `OrchestratingDriver: pinned agent "${this.pinnedAgentName}" not found in agents array — falling back to classifier.`,
575
+ `OrchestratingDriver: pinned agent "${name}" not found in agents array — falling back to classifier.`,
535
576
  );
536
577
  return undefined;
537
578
  }
package/src/main/main.ts CHANGED
@@ -329,6 +329,11 @@ export class FoundationAiAssistant extends GenesisElement {
329
329
  * Name of the agent the user has pinned via the agent picker. `null` means
330
330
  * automatic routing (Auto). Persisted on the session store, so it survives
331
331
  * pop-in/pop-out but resets on page refresh.
332
+ *
333
+ * Only changes on explicit user action (picker click or host's `setAgent`).
334
+ * The orchestrator's auto-pin on stateful activation writes
335
+ * `flowOwnerAgentName` instead, so finishing a guided flow doesn't wipe
336
+ * the user's sticky pick.
332
337
  */
333
338
  get pinnedAgentName(): string | null {
334
339
  return this._sessionRef?.store.aiAssistant.pinnedAgentName ?? null;
@@ -347,6 +352,25 @@ export class FoundationAiAssistant extends GenesisElement {
347
352
  }
348
353
  }
349
354
 
355
+ /**
356
+ * Name of the agent that currently owns a guided flow. Set by the
357
+ * orchestrator when a stateful agent's `onActivate` fires, cleared when the
358
+ * agent calls `releaseAgent` from a terminal-state tool handler. While
359
+ * non-null, routing precedence goes to this agent and the picker is locked.
360
+ *
361
+ * Independent of `pinnedAgentName`: a flow can complete and release
362
+ * without disturbing the user's sticky picker choice.
363
+ */
364
+ get flowOwnerAgentName(): string | null {
365
+ return this._sessionRef?.store.aiAssistant.flowOwnerAgentName ?? null;
366
+ }
367
+ set flowOwnerAgentName(value: string | null) {
368
+ this._sessionRef?.actions.aiAssistant.setFlowOwnerAgentName(value);
369
+ if (this.driver instanceof OrchestratingDriver) {
370
+ this.driver.setFlowOwner(value);
371
+ }
372
+ }
373
+
350
374
  get liveSubAgentTrace(): ChatMessage[] {
351
375
  return this._sessionRef?.store.aiAssistant.liveSubAgentTrace ?? [];
352
376
  }
@@ -590,6 +614,13 @@ export class FoundationAiAssistant extends GenesisElement {
590
614
  if (oldDriver && 'dispose' in oldDriver && typeof oldDriver.dispose === 'function') {
591
615
  void oldDriver.dispose();
592
616
  }
617
+ // Clear any in-flight flow-owner lock from Redux too. The old machine has
618
+ // been disposed; we don't want the new driver re-locking to a flow that
619
+ // no longer has state behind it. User-pin survives — it's the user's
620
+ // sticky picker choice, not flow state.
621
+ if (this.flowOwnerAgentName !== null) {
622
+ this.flowOwnerAgentName = null;
623
+ }
593
624
  this.driver = getOrCreateDriver(key, () => this.createDriver());
594
625
  this.wireDriver();
595
626
  if (history.length) this.driver.loadHistory([...history]);
@@ -766,9 +797,11 @@ export class FoundationAiAssistant extends GenesisElement {
766
797
  ];
767
798
 
768
799
  if (driver instanceof OrchestratingDriver) {
769
- // Restore any pinned agent from the session store onto the freshly built
770
- // driver, so pop-in/pop-out and agents-array reassignment preserve the pin.
800
+ // Restore the user pin and flow-owner lock from the session store onto
801
+ // the freshly built driver, so pop-in/pop-out and agents-array
802
+ // reassignment preserve both.
771
803
  driver.setPinnedAgent(this.pinnedAgentName);
804
+ driver.setFlowOwner(this.flowOwnerAgentName);
772
805
  const onOrchStart = () => {
773
806
  this.showHalo = 'orchestrating';
774
807
  };
@@ -778,25 +811,38 @@ export class FoundationAiAssistant extends GenesisElement {
778
811
  const onAgentChanged = (e: Event) => {
779
812
  this.activeAgent = (e as CustomEvent<AgentConfig>).detail;
780
813
  };
781
- // Orchestrator-initiated pin changes (auto-pin on stateful activation,
782
- // auto-clear on release). User-initiated pins flow the other direction
783
- // via the `pinnedAgentName` setter, which already calls setPinnedAgent
784
- // back into the driver guard against the redundant round-trip.
814
+ // User-initiated pin changes flow into the driver via the
815
+ // `pinnedAgentName` setter, which already calls setPinnedAgent no
816
+ // event-driven mirror needed in this direction. The driver does still
817
+ // emit `'pinned-changed'` if other code ever calls `setPinnedAgent`
818
+ // directly, so keep the mirror for safety; guard against the redundant
819
+ // round-trip caused by our own setter call.
785
820
  const onPinnedChanged = (e: Event) => {
786
821
  const name = (e as CustomEvent<string | null>).detail;
787
822
  if (this._sessionRef?.store.aiAssistant.pinnedAgentName !== name) {
788
823
  this._sessionRef?.actions.aiAssistant.setPinnedAgentName(name);
789
824
  }
790
825
  };
826
+ // Orchestrator-initiated flow-owner changes (auto-lock on stateful
827
+ // activation, auto-clear on release). Mirror to Redux so the UI freeze
828
+ // state and `pinLocked` survive component remount.
829
+ const onFlowOwnerChanged = (e: Event) => {
830
+ const name = (e as CustomEvent<string | null>).detail;
831
+ if (this._sessionRef?.store.aiAssistant.flowOwnerAgentName !== name) {
832
+ this._sessionRef?.actions.aiAssistant.setFlowOwnerAgentName(name);
833
+ }
834
+ };
791
835
  driver.addEventListener('orchestrating-start', onOrchStart);
792
836
  driver.addEventListener('orchestrating-stop', onOrchStop);
793
837
  driver.addEventListener('agent-changed', onAgentChanged);
794
838
  driver.addEventListener('pinned-changed', onPinnedChanged);
839
+ driver.addEventListener('flow-owner-changed', onFlowOwnerChanged);
795
840
  cleanups.push(
796
841
  () => driver.removeEventListener('orchestrating-start', onOrchStart),
797
842
  () => driver.removeEventListener('orchestrating-stop', onOrchStop),
798
843
  () => driver.removeEventListener('agent-changed', onAgentChanged),
799
844
  () => driver.removeEventListener('pinned-changed', onPinnedChanged),
845
+ () => driver.removeEventListener('flow-owner-changed', onFlowOwnerChanged),
800
846
  );
801
847
  }
802
848
 
@@ -1144,23 +1190,17 @@ export class FoundationAiAssistant extends GenesisElement {
1144
1190
  }
1145
1191
 
1146
1192
  /**
1147
- * The pin is locked when a stateful agent (one with lifecycle hooks) is
1148
- * *actively running* — i.e. its `onActivate` has fired and it owns live
1149
- * state. Until the user sends their first message, a freshly pinned stateful
1150
- * agent is not yet active and the picker should remain free; the user might
1151
- * change their mind and unpin without anything to clean up.
1152
- *
1153
- * We derive from `activeAgent` (set by the orchestrator after `onActivate`
1154
- * completes) rather than `pinnedAgentName` (set immediately on picker
1155
- * click). The serialized `activeAgent` strips lifecycle hooks, so we look
1156
- * up the live config from `this.agents` to check for them.
1193
+ * The pin is locked when a stateful flow is mid-stream i.e. some agent's
1194
+ * `onActivate` has fired and it owns live state, and it hasn't yet called
1195
+ * `releaseAgent`. Derives directly from `flowOwnerAgentName`, the
1196
+ * single source of truth the orchestrator writes on activation and clears
1197
+ * on release. Until the user sends their first message, a freshly pinned
1198
+ * stateful agent is not yet active and the picker should remain free; the
1199
+ * user might change their mind and unpin without anything to clean up.
1157
1200
  */
1158
1201
  @volatile
1159
1202
  get pinLocked(): boolean {
1160
- if (!this.activeAgent) return false;
1161
- const live = this.agents?.find((a) => a.name === this.activeAgent!.name);
1162
- if (!live) return false;
1163
- return !!(live.onActivate || live.onDeactivate);
1203
+ return this.flowOwnerAgentName !== null;
1164
1204
  }
1165
1205
 
1166
1206
  /** Tooltip shown on the picker toggle button. */
@@ -43,9 +43,20 @@ export interface AiAssistantSessionState {
43
43
  /**
44
44
  * Name of the agent the user has pinned via the picker. `null` means the
45
45
  * orchestrator chooses each turn (Auto). Survives lifecycle events but
46
- * resets on page refresh.
46
+ * resets on page refresh. Only changed by explicit user picker action (or
47
+ * the host's `setAgent` API) — never by the orchestrator's auto-pin on
48
+ * stateful activation. See {@link flowOwnerAgentName} for that.
47
49
  */
48
50
  pinnedAgentName: string | null;
51
+ /**
52
+ * Name of the agent that currently owns a guided flow — set by the
53
+ * orchestrator when a stateful agent's `onActivate` fires, cleared by
54
+ * `releaseAgent` or session teardown. While non-null, routing precedence
55
+ * goes to this agent and the picker is locked. Independent of
56
+ * {@link pinnedAgentName}: a flow can wrap up and release without
57
+ * disturbing the user's sticky picker choice.
58
+ */
59
+ flowOwnerAgentName: string | null;
49
60
  /**
50
61
  * Whether the agent picker slide-out panel is currently open. Lives in the
51
62
  * slice so the bubble-dialog and popout-panel instances stay in sync as the
@@ -81,6 +92,7 @@ export const defaultSessionState: AiAssistantSessionState = {
81
92
  activeModel: undefined,
82
93
  activeAgent: undefined,
83
94
  pinnedAgentName: null,
95
+ flowOwnerAgentName: null,
84
96
  agentPickerOpen: false,
85
97
  inputValue: '',
86
98
  liveSubAgentTrace: [],
@@ -131,6 +143,9 @@ export const aiAssistantSlice = createSlice({
131
143
  setPinnedAgentName(state, action: PayloadAction<string | null>) {
132
144
  state.pinnedAgentName = action.payload;
133
145
  },
146
+ setFlowOwnerAgentName(state, action: PayloadAction<string | null>) {
147
+ state.flowOwnerAgentName = action.payload;
148
+ },
134
149
  setAgentPickerOpen(state, action: PayloadAction<boolean>) {
135
150
  state.agentPickerOpen = action.payload;
136
151
  },