@genesislcap/ai-assistant 15.55.0 → 15.57.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 (42) hide show
  1. package/dist/ai-assistant.api.json +438 -8
  2. package/dist/ai-assistant.d.ts +120 -4
  3. package/dist/chat-driver.cjs +60 -9
  4. package/dist/chat-driver.cjs.map +2 -2
  5. package/dist/chat-driver.mjs +60 -9
  6. package/dist/chat-driver.mjs.map +2 -2
  7. package/dist/custom-elements.json +238 -3
  8. package/dist/dts/components/agent-session/agent-session.d.ts +9 -0
  9. package/dist/dts/components/agent-session/agent-session.d.ts.map +1 -1
  10. package/dist/dts/components/ai-driver/ai-driver.d.ts +31 -2
  11. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
  12. package/dist/dts/components/chat-driver/chat-driver.d.ts +18 -2
  13. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  14. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +15 -2
  15. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  16. package/dist/dts/config/config.d.ts +21 -0
  17. package/dist/dts/config/config.d.ts.map +1 -1
  18. package/dist/dts/config/define-stateful-agent.d.ts +6 -0
  19. package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
  20. package/dist/dts/main/main.d.ts +21 -0
  21. package/dist/dts/main/main.d.ts.map +1 -1
  22. package/dist/dts/state/debug-event-log.d.ts +1 -1
  23. package/dist/dts/state/debug-event-log.d.ts.map +1 -1
  24. package/dist/dts/state/persistence/resume-verdict.d.ts +32 -0
  25. package/dist/dts/state/persistence/resume-verdict.d.ts.map +1 -0
  26. package/dist/dts/state/persistence/session-persister.d.ts +34 -1
  27. package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
  28. package/dist/dts/state/persistence/session-snapshot.d.ts +19 -0
  29. package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
  30. package/dist/dts/utils/message-partition.d.ts +9 -1
  31. package/dist/dts/utils/message-partition.d.ts.map +1 -1
  32. package/dist/esm/components/agent-session/agent-session.js +14 -0
  33. package/dist/esm/components/chat-driver/chat-driver.js +12 -5
  34. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +34 -2
  35. package/dist/esm/config/define-stateful-agent.js +1 -0
  36. package/dist/esm/main/main.js +101 -22
  37. package/dist/esm/state/debug-event-log.js +4 -0
  38. package/dist/esm/state/persistence/resume-verdict.js +20 -0
  39. package/dist/esm/state/persistence/session-persister.js +55 -15
  40. package/dist/esm/state/persistence/session-snapshot.js +67 -0
  41. package/dist/esm/utils/message-partition.js +11 -2
  42. package/package.json +17 -17
@@ -449,6 +449,8 @@ export declare class AgentSession {
449
449
  * activation, then deleted so a later re-activation starts fresh.
450
450
  */
451
451
  private restoredStates;
452
+ /** The tail of the activation queue — see {@link AgentSession.activate}. */
453
+ private activation;
452
454
  constructor(deps: AgentSessionDeps);
453
455
  /** The active agent, or `undefined` before the first activation and after a release. */
454
456
  getActive(): AgentConfig | undefined;
@@ -481,8 +483,15 @@ export declare class AgentSession {
481
483
  *
482
484
  * Re-activating the same agent by name is not a switch: no hooks fire, no lock moves, and no
483
485
  * handoff is recorded. That guard matters because a per-turn re-resolve calls through here.
486
+ *
487
+ * Single-flight (GENC-1662): calls run one after another, never interleaved. `active` is only
488
+ * set once the hooks have finished, so two overlapping calls for the same agent would otherwise
489
+ * both see a switch and run `onActivate` twice — and a restored `init` would be overwritten by a
490
+ * fresh one that finished later. That overlap became possible when a restore started activating
491
+ * the flow owner outside a turn, where a send can land mid-activation.
484
492
  */
485
493
  activate(agent: AgentConfig): Promise<AgentTransition>;
494
+ private activateNow;
486
495
  /**
487
496
  * Release the active agent: fire `onDeactivate` and drop the flow lock.
488
497
  *
@@ -767,7 +776,7 @@ export declare interface AiDriver extends EventTarget {
767
776
  * `turnSignal`. A continuation after the user stopped that message is not
768
777
  * part of it, and gets a fresh one.
769
778
  */
770
- continueFromHistory(transientPrimer?: ChatMessage[]): Promise<ChatDriverResult>;
779
+ continueFromHistory(transientPrimer?: ChatMessage[], options?: ContinueFromHistoryOptions): Promise<ChatDriverResult>;
771
780
  /**
772
781
  * Resolve a pending blocking interaction with the given result.
773
782
  */
@@ -803,6 +812,35 @@ export declare interface AiDriver extends EventTarget {
803
812
  * shipped drivers implement it and a host no longer has to feature-detect before restoring.
804
813
  */
805
814
  primeRestoredAgentState(snapshots: Record<string, unknown>): void;
815
+ /**
816
+ * Whether a restore can bring back the flow `agentName` owns (GENC-1662): the agent is configured
817
+ * here. Pure — no activation. A restore re-activates only an owner this answers `true` for.
818
+ *
819
+ * Optional: a driver without it never re-activates or resumes.
820
+ *
821
+ * @beta
822
+ */
823
+ canRestoreFlowOwner?(agentName: string): boolean;
824
+ /**
825
+ * Whether a restored flow owned by `agentName` should also RESUME — run a turn on its own — when
826
+ * the reload interrupted one (GENC-1662): the agent is configured here and has not opted out with
827
+ * `resumeOnRestore: false`. Pure. Lets the restore pick its divider wording.
828
+ *
829
+ * @beta
830
+ */
831
+ canResumeRestoredFlow?(agentName: string): boolean;
832
+ /**
833
+ * Bring a restored session's flow back to life (GENC-1662). Activates the flow owner — a
834
+ * stateful agent, the only kind that holds a flow — so its `init({ restored })` runs now rather
835
+ * than on the next message, then, when `resumeNote` is given and the agent has not opted out,
836
+ * runs one turn from the restored history with the note in the tail of its first model call.
837
+ *
838
+ * Returns the turn's result when one ran, `undefined` otherwise (no flow owner, the owner is not
839
+ * configured, or no note). Never throws for those cases.
840
+ *
841
+ * @beta
842
+ */
843
+ resumeRestoredFlow?(resumeNote?: string): Promise<ChatDriverResult | undefined>;
806
844
  /**
807
845
  * Return the full, unredacted conversation history.
808
846
  */
@@ -1434,6 +1472,27 @@ declare interface BaseAgentConfig {
1434
1472
  * @beta
1435
1473
  */
1436
1474
  notResumableMessage?: string;
1475
+ /**
1476
+ * Whether a restored session whose flow this agent owns continues on its own after a page reload
1477
+ * (GENC-1662). Defaults to `true`; set `false` to keep the old behaviour of waiting for the user.
1478
+ *
1479
+ * Only meaningful on a STATEFUL agent (one with `onActivate`/`onDeactivate`, e.g. built with
1480
+ * `defineStatefulAgent`) — only those own a flow, so on any other agent this is a no-op.
1481
+ *
1482
+ * Applies when the reload interrupted a turn: an unanswered tool call (often one waiting on a
1483
+ * widget, which is a promise inside a running handler and cannot be persisted, so the restore
1484
+ * trims it), tool results the model had not yet answered, or a user message with no reply yet.
1485
+ * With this on, the restore runs one turn from the restored history, with a note in the tail of
1486
+ * its first model call saying what was cut. That turn is a fresh model decision — it usually asks
1487
+ * the same question again, but may word it differently or pick another valid action.
1488
+ *
1489
+ * A restored flow owner is re-activated either way, so its `init({ restored })` runs straight
1490
+ * after the reload rather than on the next message. Never consulted for a session restored as
1491
+ * interrupted ({@link BaseAgentConfig.resumable} returned `false`).
1492
+ *
1493
+ * @beta
1494
+ */
1495
+ resumeOnRestore?: boolean;
1437
1496
  /**
1438
1497
  * Tokens this agent wants kept free for one of its turns (GENC-1567).
1439
1498
  * Overrides `chatConfig.context.reserveTokens` while this agent is active;
@@ -2559,9 +2618,13 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
2559
2618
  private invokeSubAgent;
2560
2619
  /**
2561
2620
  * Continue the tool loop from current history without appending a new user message.
2562
- * Used by OrchestratingDriver after an agent handoff.
2621
+ * Used by OrchestratingDriver after an agent handoff, and to resume a restored flow (GENC-1662).
2622
+ *
2623
+ * `options.tailNote` rides the framed tail of the FIRST model call only — after the history, where
2624
+ * a note about the conversation belongs — and is dropped once the model has answered, so it
2625
+ * cannot go stale over the rest of the loop. Contrast `transientPrimer`, which leads the request.
2563
2626
  */
2564
- continueFromHistory(transientPrimer?: ChatMessage[]): Promise<ChatDriverResult>;
2627
+ continueFromHistory(transientPrimer?: ChatMessage[], options?: ContinueFromHistoryOptions): Promise<ChatDriverResult>;
2565
2628
  private runToolLoop;
2566
2629
  /**
2567
2630
  * Append prose the DRIVER wrote rather than the model — a failure notice, a limit explanation,
@@ -2879,6 +2942,19 @@ declare interface ContextReserveResolution {
2879
2942
  basis: 'locked' | 'reachable-max';
2880
2943
  }
2881
2944
 
2945
+ /**
2946
+ * Options for `continueFromHistory` (GENC-1662).
2947
+ *
2948
+ * @beta
2949
+ */
2950
+ export declare interface ContinueFromHistoryOptions {
2951
+ /**
2952
+ * A note for the model, placed in the framed tail of the first model call only — after the
2953
+ * history it describes — and dropped once the model has answered.
2954
+ */
2955
+ tailNote?: string;
2956
+ }
2957
+
2882
2958
  /** A model used during a recorded cost session. */
2883
2959
  export declare interface CostSessionModelEntry {
2884
2960
  model: string;
@@ -4945,6 +5021,22 @@ export declare class FoundationAiAssistant extends GenesisElement {
4945
5021
  * persistence is (now) on.
4946
5022
  */
4947
5023
  private initPersistenceForNewStore;
5024
+ /**
5025
+ * Whether the session restored for `key` is no longer the one on screen (GENC-1662): switched in
5026
+ * place, or the element disconnected, while the load was in flight.
5027
+ */
5028
+ private isRestoredSessionStale;
5029
+ /**
5030
+ * Re-activate a restored flow owner (GENC-1662), so a stateful agent's `init({ restored })` — and
5031
+ * anything the host hangs off it, like a progress stepper — runs straight after the reload
5032
+ * instead of on the next message.
5033
+ */
5034
+ private reactivateRestoredFlow;
5035
+ /**
5036
+ * Run the turn a reload interrupted (GENC-1662), shown as busy exactly as a send is — or, when it
5037
+ * cannot run, withdraw the resume the restore divider promised.
5038
+ */
5039
+ private resumeInterruptedTurn;
4948
5040
  triggerFileInput(): void;
4949
5041
  handleFileSelect(e: Event): void;
4950
5042
  removeAttachment(attachment: ChatAttachment): void;
@@ -4991,6 +5083,11 @@ export declare class FoundationAiAssistant extends GenesisElement {
4991
5083
  */
4992
5084
  private _pendingSendPrompt?;
4993
5085
  private send;
5086
+ /**
5087
+ * The post-turn settle shared by `send()` and a resumed restore turn: back to idle through
5088
+ * the session ref captured at turn start, then the cleanup the state setter would have done.
5089
+ */
5090
+ private settleAfterTurn;
4994
5091
  onChatHeaderMouseDown(e: MouseEvent): void;
4995
5092
  handleInteractionCompleted(e: Event): void;
4996
5093
  /**
@@ -5490,9 +5587,22 @@ export declare class OrchestratingDriver extends EventTarget implements AiDriver
5490
5587
  getSuggestions(history: ChatMessage[], prompt: string, count: number, allAgentInfo?: AllAgentSummary[]): Promise<string[]>;
5491
5588
  sendMessage(input: string, attachments?: ChatAttachment[]): Promise<ChatDriverResult>;
5492
5589
  private runOrchestratedTurn;
5493
- continueFromHistory(transientPrimer?: ChatMessage[]): Promise<ChatDriverResult>;
5590
+ continueFromHistory(transientPrimer?: ChatMessage[], options?: ContinueFromHistoryOptions): Promise<ChatDriverResult>;
5494
5591
  /** {@inheritDoc AiDriver.primeRestoredAgentState} */
5495
5592
  primeRestoredAgentState(snapshots: Record<string, unknown>): void;
5593
+ /** {@inheritDoc AiDriver.canRestoreFlowOwner} */
5594
+ canRestoreFlowOwner(agentName: string): boolean;
5595
+ /** {@inheritDoc AiDriver.canResumeRestoredFlow} */
5596
+ canResumeRestoredFlow(agentName: string): boolean;
5597
+ /**
5598
+ * {@inheritDoc AiDriver.resumeRestoredFlow}
5599
+ *
5600
+ * Installs the flow owner directly rather than through `handleTurn`: there is no user message
5601
+ * to classify, and a restored lock already says which agent owns the flow. `continueFromHistory`
5602
+ * on its own would not do — it neither routes nor installs, so it would run on whatever agent
5603
+ * happened to be installed (none, straight after a reload).
5604
+ */
5605
+ resumeRestoredFlow(resumeNote?: string): Promise<ChatDriverResult | undefined>;
5496
5606
  /**
5497
5607
  * {@inheritDoc AiDriver.getContextReserve}
5498
5608
  *
@@ -6400,6 +6510,12 @@ export declare interface StatefulAgentInit<S> {
6400
6510
  * `false`. Falls back to a built-in default when omitted.
6401
6511
  */
6402
6512
  notResumableMessage?: string;
6513
+ /**
6514
+ * Whether this agent's flow continues on its own when a page reload interrupted one of its turns
6515
+ * — see `AgentConfig.resumeOnRestore`, which this is passed straight through to. Defaults to
6516
+ * `true`. A stateful agent is the only kind this applies to, since only it owns a flow.
6517
+ */
6518
+ resumeOnRestore?: boolean;
6403
6519
  }
6404
6520
 
6405
6521
  /**
@@ -4114,6 +4114,10 @@ var META_EVENT_IMPORTANCE = {
4114
4114
  "session.switched": "low",
4115
4115
  "session.switch-deferred": "low",
4116
4116
  "persistence.restored": "normal",
4117
+ // Whether a restored flow will be re-activated and resumed (GENC-1662), and how much the trim cut.
4118
+ "persistence.resume-planned": "normal",
4119
+ // A planned resume that did not run, and why (GENC-1662) — the divider was put back to plain.
4120
+ "persistence.resume-withdrawn": "normal",
4117
4121
  "persistence.suppressed": "normal",
4118
4122
  "persistence.not-resumable": "normal",
4119
4123
  "driver.wired": "low",
@@ -4907,6 +4911,8 @@ var AgentSession = class {
4907
4911
  * activation, then deleted so a later re-activation starts fresh.
4908
4912
  */
4909
4913
  this.restoredStates = {};
4914
+ /** The tail of the activation queue — see {@link AgentSession.activate}. */
4915
+ this.activation = Promise.resolve();
4910
4916
  }
4911
4917
  /** The active agent, or `undefined` before the first activation and after a release. */
4912
4918
  getActive() {
@@ -4955,8 +4961,19 @@ var AgentSession = class {
4955
4961
  *
4956
4962
  * Re-activating the same agent by name is not a switch: no hooks fire, no lock moves, and no
4957
4963
  * handoff is recorded. That guard matters because a per-turn re-resolve calls through here.
4958
- */
4959
- async activate(agent) {
4964
+ *
4965
+ * Single-flight (GENC-1662): calls run one after another, never interleaved. `active` is only
4966
+ * set once the hooks have finished, so two overlapping calls for the same agent would otherwise
4967
+ * both see a switch and run `onActivate` twice — and a restored `init` would be overwritten by a
4968
+ * fresh one that finished later. That overlap became possible when a restore started activating
4969
+ * the flow owner outside a turn, where a send can land mid-activation.
4970
+ */
4971
+ activate(agent) {
4972
+ const run = this.activation.then(() => this.activateNow(agent));
4973
+ this.activation = run.catch(() => void 0);
4974
+ return run;
4975
+ }
4976
+ async activateNow(agent) {
4960
4977
  const previousAgent = this.active;
4961
4978
  const switched = !previousAgent || previousAgent.name !== agent.name;
4962
4979
  const transition = { previousAgent, switched };
@@ -7517,9 +7534,13 @@ var ChatDriver = class _ChatDriver extends EventTarget {
7517
7534
  }
7518
7535
  /**
7519
7536
  * Continue the tool loop from current history without appending a new user message.
7520
- * Used by OrchestratingDriver after an agent handoff.
7537
+ * Used by OrchestratingDriver after an agent handoff, and to resume a restored flow (GENC-1662).
7538
+ *
7539
+ * `options.tailNote` rides the framed tail of the FIRST model call only — after the history, where
7540
+ * a note about the conversation belongs — and is dropped once the model has answered, so it
7541
+ * cannot go stale over the rest of the loop. Contrast `transientPrimer`, which leads the request.
7521
7542
  */
7522
- async continueFromHistory(transientPrimer) {
7543
+ async continueFromHistory(transientPrimer, options) {
7523
7544
  if (this.busy) return { reason: "done" };
7524
7545
  this.busy = true;
7525
7546
  if (this.messageController.signal.aborted) this.beginMessage();
@@ -7541,7 +7562,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
7541
7562
  this.activityBus.publish("tool-loop-start", void 0);
7542
7563
  let result = { reason: "done" };
7543
7564
  try {
7544
- result = await this.runToolLoop("", void 0, transientPrimer);
7565
+ result = await this.runToolLoop("", void 0, transientPrimer, options?.tailNote);
7545
7566
  return result;
7546
7567
  } catch (e) {
7547
7568
  logger2.error("ChatDriver error:", e);
@@ -7578,7 +7599,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
7578
7599
  // splitting threads that state through signatures rather than removing it. `turn-setup.ts`
7579
7600
  // (docs/code-health.md §11.3) is the intended carve-up.
7580
7601
  // oxlint-disable-next-line complexity, max-lines-per-function
7581
- async runToolLoop(userInput, attachments, transientPrimer) {
7602
+ async runToolLoop(userInput, attachments, transientPrimer, tailNote) {
7582
7603
  if (!this.systemPrompt) {
7583
7604
  logger2.warn(
7584
7605
  "ChatDriver: no systemPrompt set. The assistant will have no instructions \u2014 provide a systemPrompt via agents config or the foundation-ai-assistant property."
@@ -7639,7 +7660,10 @@ var ChatDriver = class _ChatDriver extends EventTarget {
7639
7660
  ${MALFORMED_CALL_NUDGE}` : emptyResponseAttempts > 0 ? `
7640
7661
 
7641
7662
  ${EMPTY_RESPONSE_NUDGE}` : "";
7642
- const frameworkSystemSuffix = `${foldSuffix}${retryNudge}`;
7663
+ const firstCallNote = tailNote && iterations === 1 ? `
7664
+
7665
+ ${tailNote}` : "";
7666
+ const frameworkSystemSuffix = `${foldSuffix}${firstCallNote}${retryNudge}`;
7643
7667
  const primer = normalizeForProvider([
7644
7668
  ...this.primerHistory ?? [],
7645
7669
  ...transientPrimer ?? []
@@ -8698,13 +8722,39 @@ var OrchestratingDriver = class extends EventTarget {
8698
8722
  }
8699
8723
  return lastResult?.reason === "done" ? lastResult : { reason: "done" };
8700
8724
  }
8701
- async continueFromHistory(transientPrimer) {
8702
- return this.chatDriver.continueFromHistory(transientPrimer);
8725
+ async continueFromHistory(transientPrimer, options) {
8726
+ return this.chatDriver.continueFromHistory(transientPrimer, options);
8703
8727
  }
8704
8728
  /** {@inheritDoc AiDriver.primeRestoredAgentState} */
8705
8729
  primeRestoredAgentState(snapshots) {
8706
8730
  this.agentSession.primeRestoredStates(snapshots);
8707
8731
  }
8732
+ /** {@inheritDoc AiDriver.canRestoreFlowOwner} */
8733
+ canRestoreFlowOwner(agentName) {
8734
+ return this.agents.some((a) => a.name === agentName);
8735
+ }
8736
+ /** {@inheritDoc AiDriver.canResumeRestoredFlow} */
8737
+ canResumeRestoredFlow(agentName) {
8738
+ const agent = this.agents.find((a) => a.name === agentName);
8739
+ return !!agent && agent.resumeOnRestore !== false;
8740
+ }
8741
+ /**
8742
+ * {@inheritDoc AiDriver.resumeRestoredFlow}
8743
+ *
8744
+ * Installs the flow owner directly rather than through `handleTurn`: there is no user message
8745
+ * to classify, and a restored lock already says which agent owns the flow. `continueFromHistory`
8746
+ * on its own would not do — it neither routes nor installs, so it would run on whatever agent
8747
+ * happened to be installed (none, straight after a reload).
8748
+ */
8749
+ async resumeRestoredFlow(resumeNote) {
8750
+ const owner = this.agentSession.getFlowOwner();
8751
+ const agent = owner ? this.agents.find((a) => a.name === owner) : void 0;
8752
+ if (!agent) return void 0;
8753
+ this.cancelled = false;
8754
+ await this.installAgent(agent);
8755
+ if (!resumeNote || agent.resumeOnRestore === false || this.cancelled) return void 0;
8756
+ return this.chatDriver.continueFromHistory(void 0, { tailNote: resumeNote });
8757
+ }
8708
8758
  /**
8709
8759
  * {@inheritDoc AiDriver.getContextReserve}
8710
8760
  *
@@ -9179,6 +9229,7 @@ function defineStatefulAgent(opts) {
9179
9229
  onUnresolvedTool: wrappedOnUnresolvedTool,
9180
9230
  resumable: wrappedResumable,
9181
9231
  notResumableMessage: opts.notResumableMessage,
9232
+ resumeOnRestore: opts.resumeOnRestore,
9182
9233
  contextReserve: opts.contextReserve,
9183
9234
  onActivate: async (ctx) => {
9184
9235
  state = await opts.init(ctx);