@genesislcap/ai-assistant 15.61.0 → 15.62.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 (134) hide show
  1. package/dist/ai-assistant.api.json +18231 -11171
  2. package/dist/ai-assistant.d.ts +1235 -9
  3. package/dist/chat-driver.cjs +658 -50
  4. package/dist/chat-driver.cjs.map +4 -4
  5. package/dist/chat-driver.mjs +657 -50
  6. package/dist/chat-driver.mjs.map +4 -4
  7. package/dist/custom-elements.json +6169 -2127
  8. package/dist/dts/autopilot/autopilot-driver.d.ts +278 -0
  9. package/dist/dts/autopilot/autopilot-driver.d.ts.map +1 -0
  10. package/dist/dts/autopilot/autopilot-session.d.ts +163 -0
  11. package/dist/dts/autopilot/autopilot-session.d.ts.map +1 -0
  12. package/dist/dts/autopilot/autopilot-tools.d.ts +15 -0
  13. package/dist/dts/autopilot/autopilot-tools.d.ts.map +1 -0
  14. package/dist/dts/autopilot/decision-message.d.ts +62 -0
  15. package/dist/dts/autopilot/decision-message.d.ts.map +1 -0
  16. package/dist/dts/autopilot/hand-back/hand-back.controller.d.ts +69 -0
  17. package/dist/dts/autopilot/hand-back/hand-back.controller.d.ts.map +1 -0
  18. package/dist/dts/autopilot/hand-back/hand-back.d.ts +22 -0
  19. package/dist/dts/autopilot/hand-back/hand-back.d.ts.map +1 -0
  20. package/dist/dts/autopilot/index.d.ts +4 -0
  21. package/dist/dts/autopilot/index.d.ts.map +1 -0
  22. package/dist/dts/channel/ai-activity-bus.d.ts.map +1 -1
  23. package/dist/dts/channel/ai-activity-channel.d.ts +17 -0
  24. package/dist/dts/channel/ai-activity-channel.d.ts.map +1 -1
  25. package/dist/dts/channel/autopilot-needs-user.d.ts +8 -0
  26. package/dist/dts/channel/autopilot-needs-user.d.ts.map +1 -0
  27. package/dist/dts/components/agent-picker/agent-picker.constants.d.ts +10 -0
  28. package/dist/dts/components/agent-picker/agent-picker.constants.d.ts.map +1 -1
  29. package/dist/dts/components/agent-picker/agent-picker.d.ts +4 -0
  30. package/dist/dts/components/agent-picker/agent-picker.d.ts.map +1 -1
  31. package/dist/dts/components/agent-picker/agent-picker.template.d.ts.map +1 -1
  32. package/dist/dts/components/ai-driver/ai-driver.d.ts +34 -1
  33. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
  34. package/dist/dts/components/ai-driver/base-driver-decorator.d.ts +3 -1
  35. package/dist/dts/components/ai-driver/base-driver-decorator.d.ts.map +1 -1
  36. package/dist/dts/components/chat-bubble/chat-bubble.d.ts +21 -0
  37. package/dist/dts/components/chat-bubble/chat-bubble.d.ts.map +1 -1
  38. package/dist/dts/components/chat-bubble/chat-bubble.styles.d.ts.map +1 -1
  39. package/dist/dts/components/chat-bubble/chat-bubble.template.d.ts.map +1 -1
  40. package/dist/dts/components/chat-driver/chat-driver.d.ts +5 -1
  41. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  42. package/dist/dts/components/chat-driver/interaction-controller.d.ts +27 -3
  43. package/dist/dts/components/chat-driver/interaction-controller.d.ts.map +1 -1
  44. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts +10 -2
  45. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts.map +1 -1
  46. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +9 -1
  47. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  48. package/dist/dts/config/config.d.ts +183 -0
  49. package/dist/dts/config/config.d.ts.map +1 -1
  50. package/dist/dts/config/define-stateful-agent.d.ts +31 -1
  51. package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
  52. package/dist/dts/index.d.ts +7 -3
  53. package/dist/dts/index.d.ts.map +1 -1
  54. package/dist/dts/main/main.d.ts +128 -5
  55. package/dist/dts/main/main.d.ts.map +1 -1
  56. package/dist/dts/main/main.styles.d.ts.map +1 -1
  57. package/dist/dts/main/main.template.d.ts +2 -2
  58. package/dist/dts/main/main.template.d.ts.map +1 -1
  59. package/dist/dts/react.d.ts +10 -0
  60. package/dist/dts/state/debug-event-log.d.ts +1 -1
  61. package/dist/dts/state/debug-event-log.d.ts.map +1 -1
  62. package/dist/dts/state/decision-surface.d.ts +46 -0
  63. package/dist/dts/state/decision-surface.d.ts.map +1 -0
  64. package/dist/dts/state/interaction-context.d.ts +21 -1
  65. package/dist/dts/state/interaction-context.d.ts.map +1 -1
  66. package/dist/dts/state/interaction-registry.d.ts +63 -0
  67. package/dist/dts/state/interaction-registry.d.ts.map +1 -0
  68. package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
  69. package/dist/dts/state/persistence/session-snapshot.d.ts +21 -0
  70. package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
  71. package/dist/dts/state/run-steps.d.ts +66 -0
  72. package/dist/dts/state/run-steps.d.ts.map +1 -0
  73. package/dist/dts/types/ai-chat-widget.d.ts +7 -1
  74. package/dist/dts/types/ai-chat-widget.d.ts.map +1 -1
  75. package/dist/dts/types/interaction-context.d.ts +66 -1
  76. package/dist/dts/types/interaction-context.d.ts.map +1 -1
  77. package/dist/dts/types/interaction-interceptor.d.ts +60 -2
  78. package/dist/dts/types/interaction-interceptor.d.ts.map +1 -1
  79. package/dist/dts/types/interaction-surface.d.ts +195 -0
  80. package/dist/dts/types/interaction-surface.d.ts.map +1 -0
  81. package/dist/dts/utils/context-tokens.d.ts +6 -0
  82. package/dist/dts/utils/context-tokens.d.ts.map +1 -1
  83. package/dist/dts/utils/delegated-message.d.ts +13 -0
  84. package/dist/dts/utils/delegated-message.d.ts.map +1 -1
  85. package/dist/dts/utils/history-transform.d.ts.map +1 -1
  86. package/dist/dts/utils/preserved-user-messages.d.ts +21 -0
  87. package/dist/dts/utils/preserved-user-messages.d.ts.map +1 -0
  88. package/dist/esm/autopilot/autopilot-driver.js +906 -0
  89. package/dist/esm/autopilot/autopilot-session.js +449 -0
  90. package/dist/esm/autopilot/autopilot-tools.js +68 -0
  91. package/dist/esm/autopilot/decision-message.js +206 -0
  92. package/dist/esm/autopilot/hand-back/hand-back.controller.js +47 -0
  93. package/dist/esm/autopilot/hand-back/hand-back.js +129 -0
  94. package/dist/esm/autopilot/index.js +4 -0
  95. package/dist/esm/channel/ai-activity-bus.js +7 -0
  96. package/dist/esm/channel/autopilot-needs-user.js +26 -0
  97. package/dist/esm/components/agent-picker/agent-picker.constants.js +10 -0
  98. package/dist/esm/components/agent-picker/agent-picker.js +11 -1
  99. package/dist/esm/components/agent-picker/agent-picker.template.js +2 -4
  100. package/dist/esm/components/ai-driver/ai-driver.js +2 -0
  101. package/dist/esm/components/ai-driver/base-driver-decorator.js +8 -0
  102. package/dist/esm/components/chat-bubble/chat-bubble.js +45 -0
  103. package/dist/esm/components/chat-bubble/chat-bubble.styles.js +26 -0
  104. package/dist/esm/components/chat-bubble/chat-bubble.template.js +13 -0
  105. package/dist/esm/components/chat-driver/chat-driver.js +53 -18
  106. package/dist/esm/components/chat-driver/interaction-controller.js +297 -12
  107. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +16 -2
  108. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +28 -11
  109. package/dist/esm/config/define-stateful-agent.js +34 -29
  110. package/dist/esm/index.js +3 -0
  111. package/dist/esm/main/main.js +334 -15
  112. package/dist/esm/main/main.styles.js +126 -0
  113. package/dist/esm/main/main.template.js +116 -11
  114. package/dist/esm/state/debug-event-log.js +28 -1
  115. package/dist/esm/state/decision-surface.js +209 -0
  116. package/dist/esm/state/interaction-context.js +89 -1
  117. package/dist/esm/state/interaction-registry.js +33 -0
  118. package/dist/esm/state/persistence/session-persister.js +23 -11
  119. package/dist/esm/state/persistence/session-snapshot.js +1 -1
  120. package/dist/esm/state/run-steps.js +77 -0
  121. package/dist/esm/types/interaction-surface.js +1 -0
  122. package/dist/esm/utils/context-tokens.js +23 -9
  123. package/dist/esm/utils/delegated-message.js +15 -0
  124. package/dist/esm/utils/history-transform.js +18 -7
  125. package/dist/esm/utils/preserved-user-messages.js +31 -0
  126. package/dist/react.cjs +7 -0
  127. package/dist/react.mjs +6 -0
  128. package/docs/styling.md +1 -1
  129. package/package.json +17 -17
  130. package/sandbox/README.md +38 -2
  131. package/sandbox/autopilot.ts +358 -0
  132. package/sandbox/controls.ts +24 -1
  133. package/sandbox/fixtures.ts +2 -0
  134. package/sandbox/sandbox.ts +188 -1
@@ -0,0 +1,906 @@
1
+ import { __awaiter } from "tslib";
2
+ import { BaseDriverDecorator } from '../components/ai-driver/base-driver-decorator';
3
+ import { recordMetaEvent } from '../state/debug-event-log';
4
+ import { getInteractionRegistration, registerInteraction } from '../state/interaction-registry';
5
+ import { logger } from '../utils/logger';
6
+ import { AutopilotSession, positiveOr } from './autopilot-session';
7
+ import { AUTOPILOT_HAND_BACK, HandBackController, handBackAsText, } from './hand-back/hand-back.controller';
8
+ /**
9
+ * The holder id the autopilot claims interactions as.
10
+ *
11
+ * @beta
12
+ */
13
+ export const AUTOPILOT_CLAIMANT = 'autopilot';
14
+ /** How the working agent is told about a decision when the config gives no phrase. */
15
+ const DEFAULT_DECISION_FRAMING = "Decided on the user's behalf";
16
+ /** How a written answer sent on the user's behalf is framed, when the config gives no phrase. */
17
+ const DEFAULT_REPLY_FRAMING = "Answered on the user's behalf.";
18
+ /**
19
+ * Written answers the autopilot may send in a row, each answering a question the working agent
20
+ * ended its turn on, before the next question goes to the person instead: a loop between two agents
21
+ * talking to each other is a failure, not a conversation.
22
+ */
23
+ const MAX_CONSECUTIVE_REPLIES = 2;
24
+ /** The transient note that carries the working agent on, after the person said to. */
25
+ const CARRY_ON_PRIMER = '[The user asked you to carry on with the plan on their behalf.]';
26
+ /** Turn failures that are walls with their own UI, not something to offer to retry. */
27
+ const WALLS = new Set([
28
+ 'budget-exhausted',
29
+ 'provider-refused',
30
+ 'ai-service-error',
31
+ ]);
32
+ /** Written answers per step before `reply` is withdrawn, unless the config says otherwise. */
33
+ const DEFAULT_MAX_REFINEMENTS = 2;
34
+ /** How long a claimed interaction may go quiet before the watchdog hands it back. */
35
+ const DEFAULT_WATCHDOG_MS = 600000; // 10 minutes
36
+ /** How many times the watchdog looks within one window. */
37
+ const WATCHDOG_CHECKS_PER_WINDOW = 4;
38
+ const DEFAULT_COPY = {
39
+ running: 'Working on your behalf',
40
+ paused: 'Paused',
41
+ selfPaused: "Waiting for your answer: I'll carry on after this",
42
+ handBackLead: 'I need your input on this one:',
43
+ toggleLabel: 'Autopilot',
44
+ description: "Answers the assistant's questions on your behalf, so the work keeps going.",
45
+ pauseLabel: 'Pause autopilot',
46
+ resumeLabel: 'Resume autopilot',
47
+ resumeDecideLabel: 'Resume autopilot and force it to decide this one',
48
+ keepPausedLabel: 'Keep paused',
49
+ markLabel: 'Done in autopilot mode',
50
+ checkInMessage: "I've done a lot in one go, so I've stopped to check in. Shall I carry on?",
51
+ carryOnLabel: 'Carry on',
52
+ tooLongMessage: 'This conversation has grown too long for me to carry on as it is.',
53
+ compactLabel: 'Compact and carry on',
54
+ failedMessage: 'Something went wrong on that last step.',
55
+ tryAgainLabel: 'Try again',
56
+ sendLabel: 'Send',
57
+ budgetReason: 'This one is taking more working out than I expected, so over to you.',
58
+ errorReason: 'Something went wrong while I was deciding this one.',
59
+ noDecisionReason: "I couldn't settle this one.",
60
+ watchdogReason: 'This is taking longer than expected, so I need you to look at it.',
61
+ };
62
+ /**
63
+ * Autopilot (GENC-1660): a driver decorator that answers the questions a working agent asks the
64
+ * user, on their behalf, grounded in the agent's autopilot config (for bootstrap, the brief).
65
+ *
66
+ * Neither side calls the other. The working agent asks as it always does; this driver claims the
67
+ * interactions its policy allows, while it is running for that flow, and answers each in one short
68
+ * turn on the flow's `AutopilotSession`. It plugs in only through the generic extension points
69
+ * — an interaction interceptor, a handler capability and the flow lifecycle — so the core knows
70
+ * nothing about it, and it wraps any `AiDriver`.
71
+ *
72
+ * @beta
73
+ */
74
+ export class AutopilotDriver extends BaseDriverDecorator {
75
+ constructor(inner, options) {
76
+ var _a, _b, _c;
77
+ super(inner);
78
+ this.listeners = new Set();
79
+ this.sessions = new Map();
80
+ /** The flow the mode applies to: set when it is first switched, cleared when that flow ends. */
81
+ this.onFor = null;
82
+ this.mode = 'paused';
83
+ /** What sessions that have ended did, kept for the debug log. */
84
+ this.endedDiagnostics = [];
85
+ /** Written answers sent in a row; see `MAX_CONSECUTIVE_REPLIES`. */
86
+ this.consecutiveReplies = 0;
87
+ /**
88
+ * Bumped on every pause and resume, and when its sessions end. A written answer is sent only if
89
+ * none came between asking for it and its landing: one started before a pause is stale after it,
90
+ * even once resumed.
91
+ */
92
+ this.generation = 0;
93
+ /** Working turns this driver has started that have not ended yet; see `working`. */
94
+ this.turnsInFlight = 0;
95
+ /** Written questions being answered right now; see `working`. */
96
+ this.answersInFlight = 0;
97
+ this.agents = options.agents;
98
+ this.providerRegistry = options.providerRegistry;
99
+ this.sessionKey = (_a = options.sessionKey) !== null && _a !== void 0 ? _a : '';
100
+ // The turn-level hand-back is a widget like any other: registered, so it has a controller and a
101
+ // text form. Once per app, whichever autopilot gets there first.
102
+ if (!getInteractionRegistration(AUTOPILOT_HAND_BACK)) {
103
+ registerInteraction(AUTOPILOT_HAND_BACK, {
104
+ controller: (data, ctx) => new HandBackController(data, ctx),
105
+ asText: handBackAsText,
106
+ });
107
+ }
108
+ const framing = () => {
109
+ var _a;
110
+ const owner = this.flowOwner();
111
+ return (owner && ((_a = this.bindingFor(owner)) === null || _a === void 0 ? void 0 : _a.config.decisionFraming)) || DEFAULT_DECISION_FRAMING;
112
+ };
113
+ // Unset means the platform's own wording.
114
+ const editFraming = () => {
115
+ var _a;
116
+ const owner = this.flowOwner();
117
+ return (owner && ((_a = this.bindingFor(owner)) === null || _a === void 0 ? void 0 : _a.config.editFraming)) || undefined;
118
+ };
119
+ // Every hook on the inner driver is undone by `detach()`, so this decorator can be dropped
120
+ // while the driver it wrapped lives on.
121
+ const removeInterceptor = (_b = inner.addInteractionInterceptor) === null || _b === void 0 ? void 0 : _b.call(inner, {
122
+ id: AUTOPILOT_CLAIMANT,
123
+ // Read when a claimed interaction settles: the framing of the flow the autopilot serves.
124
+ get decisionFraming() {
125
+ return framing();
126
+ },
127
+ // Read when the person finishes an interaction the autopilot had changed first.
128
+ get editFraming() {
129
+ return editFraming();
130
+ },
131
+ onInteractionOpened: (opened) => this.onInteractionOpened(opened),
132
+ });
133
+ if (removeInterceptor)
134
+ this.onDetach(removeInterceptor);
135
+ const removeCapability = (_c = inner.provideHandlerCapability) === null || _c === void 0 ? void 0 : _c.call(inner, AUTOPILOT_CLAIMANT, (scope) => this.capabilityFor(scope));
136
+ if (removeCapability)
137
+ this.onDetach(removeCapability);
138
+ const onFlowOwnerChanged = (e) => this.onFlowOwnerChanged(e.detail);
139
+ // Availability can change with the agent's state (a brief being approved), which nothing
140
+ // announces; the transcript moving is the cheapest reliable nudge to re-read it.
141
+ const onHistoryUpdated = () => this.notify();
142
+ inner.addEventListener('flow-owner-changed', onFlowOwnerChanged);
143
+ inner.addEventListener('history-updated', onHistoryUpdated);
144
+ this.onDetach(() => {
145
+ this.disarmWatchdog();
146
+ inner.removeEventListener('flow-owner-changed', onFlowOwnerChanged);
147
+ inner.removeEventListener('history-updated', onHistoryUpdated);
148
+ this.releaseHeld();
149
+ this.endSessions();
150
+ });
151
+ }
152
+ // -----------------------------------------------------------------------------------------------
153
+ // AutopilotHost
154
+ // -----------------------------------------------------------------------------------------------
155
+ get state() {
156
+ var _a;
157
+ const owner = this.flowOwner();
158
+ const working = this.turnsInFlight > 0 || this.answersInFlight > 0 || this.inner.isBusy();
159
+ if (!owner || !this.isAvailable(owner)) {
160
+ return { mode: 'unavailable', status: '', needsUser: false, working };
161
+ }
162
+ const mode = this.onFor === owner ? this.mode : 'paused';
163
+ const copy = this.copyFor(owner);
164
+ const selfPaused = ((_a = this.pauseState) === null || _a === void 0 ? void 0 : _a.scope) === 'this-interaction';
165
+ return Object.assign(Object.assign({ mode }, (this.pauseState ? { pause: this.pauseState } : {})), { status: selfPaused ? copy.selfPaused : mode === 'running' ? copy.running : copy.paused, needsUser: selfPaused || !!this.turnHandBack, working });
166
+ }
167
+ get copy() {
168
+ return this.copyFor(this.flowOwner());
169
+ }
170
+ subscribe(listener) {
171
+ this.listeners.add(listener);
172
+ return () => {
173
+ this.listeners.delete(listener);
174
+ };
175
+ }
176
+ pause() {
177
+ var _a, _b;
178
+ const owner = this.flowOwner();
179
+ if (!owner || !this.isAvailable(owner))
180
+ return;
181
+ this.onFor = owner;
182
+ this.mode = 'paused';
183
+ this.pauseState = { scope: 'until-resumed' };
184
+ this.generation += 1;
185
+ const held = this.held;
186
+ if (held && held.opened.ctx.heldBy === AUTOPILOT_CLAIMANT) {
187
+ held.handedOver = 'pause';
188
+ (_a = this.sessions.get(held.agentName)) === null || _a === void 0 ? void 0 : _a.abort();
189
+ held.opened.hold.set('user');
190
+ }
191
+ // A written question it is answering is the person's now: its answer is stopped, and would be
192
+ // dropped if it landed anyway (see `generation`). They answer by typing.
193
+ if (this.answersInFlight > 0)
194
+ (_b = this.sessions.get(owner)) === null || _b === void 0 ? void 0 : _b.abort();
195
+ this.record('autopilot.paused', { scope: 'until-resumed', flow: owner });
196
+ this.notify();
197
+ }
198
+ resume() {
199
+ var _a;
200
+ const owner = this.flowOwner();
201
+ if (!owner || !this.isAvailable(owner))
202
+ return;
203
+ const wasSelfPaused = ((_a = this.pauseState) === null || _a === void 0 ? void 0 : _a.scope) === 'this-interaction';
204
+ this.onFor = owner;
205
+ this.mode = 'running';
206
+ this.pauseState = undefined;
207
+ this.generation += 1;
208
+ this.record('autopilot.resumed', { flow: owner, decideYourself: wasSelfPaused });
209
+ const held = this.held;
210
+ if (held === null || held === void 0 ? void 0 : held.handedOver) {
211
+ // Pausing handed it over, so take it back; a self-pause the person resumed means "decide it
212
+ // yourself", so take it back without the option to hand it back again.
213
+ held.handedOver = undefined;
214
+ if (held.opened.hold.set('claimant')) {
215
+ // Watched again, as when it was first claimed: the watchdog stops for good once it hands
216
+ // an interaction back, and a decision that stalled once may well stall again.
217
+ this.armWatchdog(held);
218
+ this.decide(held, { allowHandBack: !wasSelfPaused });
219
+ }
220
+ }
221
+ else if (!held && !this.turnHandBack && !this.inner.isBusy()) {
222
+ // Resumed with the working agent idle — its turn ended waiting for the person — so carry it
223
+ // on, or nothing would happen until they typed: answer the question it ended on, if it ended
224
+ // on one nobody answered, or tell it to carry on. Only on a live Resume, never on a restore.
225
+ const question = this.lastQuestion();
226
+ if (question) {
227
+ this.answerInBackground(owner, question);
228
+ }
229
+ else {
230
+ void this.continueFromHistory([{ role: 'user', content: CARRY_ON_PRIMER }]);
231
+ }
232
+ }
233
+ this.notify();
234
+ }
235
+ stop() {
236
+ var _a, _b;
237
+ (_b = (_a = this.inner).cancel) === null || _b === void 0 ? void 0 : _b.call(_a);
238
+ this.pause();
239
+ }
240
+ // -----------------------------------------------------------------------------------------------
241
+ // Persistence (§4.8): the mode is saved with the session and restored with it
242
+ // -----------------------------------------------------------------------------------------------
243
+ /** The mode to save with the session, while it is set for the current flow. */
244
+ getPersistedMode() {
245
+ const owner = this.flowOwner();
246
+ if (!owner || this.onFor !== owner)
247
+ return undefined;
248
+ return { onFor: owner, mode: this.mode };
249
+ }
250
+ /**
251
+ * Bring back a saved mode. It applies once its flow is the owner again — at once if it already
252
+ * is, otherwise when the restored flow re-activates. Restoring starts nothing: the autopilot acts
253
+ * only on live events, such as the working agent's own resume asking a question (§4.10, S3).
254
+ */
255
+ restorePersistedMode(saved) {
256
+ this.restored = saved;
257
+ const owner = this.flowOwner();
258
+ if (owner)
259
+ this.adoptRestored(owner);
260
+ }
261
+ adoptRestored(owner) {
262
+ const saved = this.restored;
263
+ if (!saved)
264
+ return;
265
+ this.restored = undefined;
266
+ // Another flow took over first, so the saved mode has nothing left to apply to.
267
+ if (saved.onFor !== owner)
268
+ return;
269
+ this.onFor = owner;
270
+ this.mode = saved.mode;
271
+ this.pauseState = undefined;
272
+ this.record(saved.mode === 'running' ? 'autopilot.resumed' : 'autopilot.paused', {
273
+ trigger: 'restored',
274
+ flow: owner,
275
+ });
276
+ this.notify();
277
+ }
278
+ keepPaused(interactionId) {
279
+ var _a;
280
+ if (((_a = this.pauseState) === null || _a === void 0 ? void 0 : _a.scope) !== 'this-interaction')
281
+ return;
282
+ if (this.pauseState.interactionId !== interactionId)
283
+ return;
284
+ this.generation += 1;
285
+ this.mode = 'paused';
286
+ this.pauseState = { scope: 'until-resumed' };
287
+ if (this.held)
288
+ this.held.handedOver = 'pause';
289
+ this.record('autopilot.paused', { scope: 'until-resumed', trigger: 'keep-paused' });
290
+ this.notify();
291
+ }
292
+ // -----------------------------------------------------------------------------------------------
293
+ // Turns: how each working turn ended decides what the autopilot does next (§4.2, §4.10)
294
+ // -----------------------------------------------------------------------------------------------
295
+ sendMessage(input, attachments, options) {
296
+ const _super = Object.create(null, {
297
+ sendMessage: { get: () => super.sendMessage }
298
+ });
299
+ return __awaiter(this, void 0, void 0, function* () {
300
+ // A message the person typed starts a fresh run of answers on their behalf.
301
+ if (!(options === null || options === void 0 ? void 0 : options.delegate))
302
+ this.consecutiveReplies = 0;
303
+ const result = yield this.whileWorking(() => _super.sendMessage.call(this, input, attachments, options));
304
+ this.afterTurn(result);
305
+ return result;
306
+ });
307
+ }
308
+ continueFromHistory(transientPrimer, options) {
309
+ const _super = Object.create(null, {
310
+ continueFromHistory: { get: () => super.continueFromHistory }
311
+ });
312
+ return __awaiter(this, void 0, void 0, function* () {
313
+ const result = yield this.whileWorking(() => _super.continueFromHistory.call(this, transientPrimer, options));
314
+ this.afterTurn(result);
315
+ return result;
316
+ });
317
+ }
318
+ resumeRestoredFlow(resumeNote, agent) {
319
+ const _super = Object.create(null, {
320
+ resumeRestoredFlow: { get: () => super.resumeRestoredFlow }
321
+ });
322
+ return __awaiter(this, void 0, void 0, function* () {
323
+ const result = yield this.whileWorking(() => _super.resumeRestoredFlow.call(this, resumeNote, agent));
324
+ if (result)
325
+ this.afterTurn(result);
326
+ else
327
+ this.notify();
328
+ return result;
329
+ });
330
+ }
331
+ /**
332
+ * Run a working turn, counted for `working` from the moment it is asked for: the wrapped driver
333
+ * may not say it is busy until some way in. The caller notifies once it has acted on the end.
334
+ */
335
+ whileWorking(run) {
336
+ return __awaiter(this, void 0, void 0, function* () {
337
+ this.turnsInFlight += 1;
338
+ this.notify();
339
+ let ended = false;
340
+ try {
341
+ const result = yield run();
342
+ ended = true;
343
+ return result;
344
+ }
345
+ finally {
346
+ this.turnsInFlight -= 1;
347
+ if (!ended)
348
+ this.notify();
349
+ }
350
+ });
351
+ }
352
+ /**
353
+ * Act on how a working turn ended, without holding up whoever awaited it, then notify: idle
354
+ * unless that started something (an answer, a hand-back), which it does before this returns.
355
+ */
356
+ afterTurn(result) {
357
+ void this.onTurnEnded(result).catch((e) => {
358
+ logger.error('AutopilotDriver: handling the end of a turn failed:', e);
359
+ this.record('autopilot.error', { message: e instanceof Error ? e.message : String(e) });
360
+ });
361
+ this.notify();
362
+ }
363
+ onTurnEnded(result) {
364
+ return __awaiter(this, void 0, void 0, function* () {
365
+ const owner = this.flowOwner();
366
+ // Nothing to do unless the autopilot is running for a flow that is still going.
367
+ if (!owner || this.onFor !== owner || this.mode !== 'running' || this.pauseState)
368
+ return;
369
+ if (this.held || this.turnHandBack || this.inner.isBusy())
370
+ return;
371
+ const failure = result.reason === 'done' ? result.failureReason : undefined;
372
+ if (failure) {
373
+ // A wall (the budget, a refused account) keeps its own UI and is not the person's to retry.
374
+ if (WALLS.has(failure))
375
+ return;
376
+ yield this.presentHandBack(failure === 'max-iterations'
377
+ ? 'max-iterations'
378
+ : failure === 'context-exhausted'
379
+ ? 'context-exhausted'
380
+ : 'failure');
381
+ return;
382
+ }
383
+ // Ended cleanly with the flow still running: the working agent is waiting for the person to type.
384
+ const question = this.lastQuestion();
385
+ if (!question)
386
+ return;
387
+ if (this.consecutiveReplies >= MAX_CONSECUTIVE_REPLIES) {
388
+ yield this.presentHandBack('question', { question });
389
+ return;
390
+ }
391
+ yield this.answer(owner, question);
392
+ });
393
+ }
394
+ /** {@link AutopilotDriver.answer}, from a caller that does not wait for it. */
395
+ answerInBackground(owner, question) {
396
+ void this.answer(owner, question).catch((e) => {
397
+ logger.error('AutopilotDriver: answering a question failed:', e);
398
+ this.record('autopilot.error', { message: e instanceof Error ? e.message : String(e) });
399
+ });
400
+ }
401
+ /**
402
+ * Answer a written question the working agent ended its turn on, and send the answer on the
403
+ * user's behalf, with what writing it cost booked on that message (§4.2).
404
+ *
405
+ * Sent only if the question still stands when the answer lands: no pause or resume since it was
406
+ * asked (Pause stops it, and the question is the person's to answer by typing), the flow still
407
+ * running, the working agent idle, and the question still the last thing it said. Otherwise the
408
+ * answer goes nowhere — not sent, not counted, not recorded as acted — and its cost is recorded as
409
+ * dropped, as it is whenever no reply is sent to carry it.
410
+ */
411
+ answer(owner, question) {
412
+ return __awaiter(this, void 0, void 0, function* () {
413
+ var _a, _b, _c;
414
+ const session = this.sessionFor(owner);
415
+ if (!session)
416
+ return;
417
+ const generation = this.generation;
418
+ const stands = () => this.generation === generation &&
419
+ this.flowOwner() === owner &&
420
+ this.onFor === owner &&
421
+ this.mode === 'running' &&
422
+ !this.pauseState &&
423
+ !this.inner.isBusy() &&
424
+ this.lastQuestion() === question;
425
+ this.answersInFlight += 1;
426
+ this.notify();
427
+ let outcome;
428
+ try {
429
+ outcome = yield session.answerQuestion(question, {
430
+ allowHandBack: true,
431
+ stillWanted: stands,
432
+ });
433
+ }
434
+ catch (e) {
435
+ this.answersInFlight -= 1;
436
+ this.notify();
437
+ throw e;
438
+ }
439
+ // Not notified yet: what follows (a reply, a hand-back, or nothing) notifies.
440
+ this.answersInFlight -= 1;
441
+ const cost = (_a = outcome.costUsd) !== null && _a !== void 0 ? _a : 0;
442
+ if (!stands()) {
443
+ this.dropCost(cost, 'stale');
444
+ this.notify();
445
+ return;
446
+ }
447
+ if (outcome.kind === 'replied') {
448
+ this.consecutiveReplies += 1;
449
+ this.record('autopilot.acted', { action: 'reply', question: true });
450
+ const framing = (_c = (_b = this.bindingFor(owner)) === null || _b === void 0 ? void 0 : _b.config.decisionFraming) !== null && _c !== void 0 ? _c : DEFAULT_REPLY_FRAMING;
451
+ yield this.sendMessage(outcome.text, undefined, {
452
+ delegate: AUTOPILOT_CLAIMANT,
453
+ delegateFraming: framing,
454
+ externalCostUsd: cost,
455
+ });
456
+ return;
457
+ }
458
+ this.dropCost(cost, outcome.kind);
459
+ if (outcome.kind === 'handed-back') {
460
+ yield this.presentHandBack('question', { question, reason: outcome.reason });
461
+ }
462
+ else {
463
+ this.notify();
464
+ }
465
+ });
466
+ }
467
+ /**
468
+ * Spend on a written answer that no reply carries into the session total — it was handed back,
469
+ * stopped, or went stale — recorded so it is never lost silently.
470
+ */
471
+ dropCost(costUsd, why) {
472
+ if (costUsd > 0)
473
+ this.record('autopilot.cost-dropped', { costUsd, why });
474
+ }
475
+ /** The working agent's last visible words, when its turn ended on them. */
476
+ lastQuestion() {
477
+ var _a, _b, _c, _d;
478
+ const history = (_c = (_b = (_a = this.inner).getRawHistory) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : [];
479
+ const last = [...history].reverse().find((m) => m.role !== 'system-event' && !m.thinking);
480
+ if (!last || last.role !== 'assistant' || last.interaction || last.delegate)
481
+ return undefined;
482
+ if (last.excludeFromProvider ||
483
+ last.category === 'reasoning' ||
484
+ last.category === 'narration') {
485
+ return undefined;
486
+ }
487
+ return ((_d = last.content) === null || _d === void 0 ? void 0 : _d.trim()) || undefined;
488
+ }
489
+ /**
490
+ * The turn-level hand-back (§4.10): a working turn ended needing the person and there is no
491
+ * widget to hand over, so the autopilot shows its own, `userOnly`, and acts on the choice.
492
+ */
493
+ presentHandBack(why_1) {
494
+ return __awaiter(this, arguments, void 0, function* (why, opts = {}) {
495
+ var _a, _b, _c, _d;
496
+ if (!this.inner.presentInteraction)
497
+ return;
498
+ const copy = this.copy;
499
+ const pauseOption = { id: 'pause', label: copy.pauseLabel };
500
+ const data = why === 'max-iterations'
501
+ ? {
502
+ message: copy.checkInMessage,
503
+ options: [{ id: 'carry-on', label: copy.carryOnLabel }, pauseOption],
504
+ }
505
+ : why === 'context-exhausted'
506
+ ? {
507
+ message: copy.tooLongMessage,
508
+ options: [{ id: 'compact', label: copy.compactLabel }, pauseOption],
509
+ }
510
+ : why === 'failure'
511
+ ? {
512
+ message: copy.failedMessage,
513
+ options: [{ id: 'try-again', label: copy.tryAgainLabel }, pauseOption],
514
+ }
515
+ : {
516
+ message: opts.reason ? `${copy.handBackLead} ${opts.reason}` : copy.handBackLead,
517
+ question: opts.question,
518
+ freeText: true,
519
+ sendLabel: copy.sendLabel,
520
+ options: [pauseOption],
521
+ };
522
+ this.turnHandBack = { why };
523
+ this.consecutiveReplies = 0;
524
+ this.notify();
525
+ let result;
526
+ try {
527
+ result = yield this.inner.presentInteraction(AUTOPILOT_HAND_BACK, data, {
528
+ userOnly: true,
529
+ });
530
+ }
531
+ finally {
532
+ this.turnHandBack = undefined;
533
+ this.notify();
534
+ }
535
+ const choice = (_a = result === null || result === void 0 ? void 0 : result.payload) === null || _a === void 0 ? void 0 : _a.choice;
536
+ this.record('autopilot.handback-presented', Object.assign({ why, options: data.options.map((o) => o.id) }, (choice ? { choice } : {})));
537
+ if (!choice)
538
+ return;
539
+ if (choice === 'pause') {
540
+ this.pause();
541
+ }
542
+ else if (choice === 'reply') {
543
+ // The person's own words: an ordinary message, not one sent on their behalf.
544
+ yield this.sendMessage((_b = result.payload.text) !== null && _b !== void 0 ? _b : '');
545
+ }
546
+ else {
547
+ if (choice === 'compact')
548
+ yield ((_d = (_c = this.inner).compact) === null || _d === void 0 ? void 0 : _d.call(_c));
549
+ yield this.continueFromHistory([{ role: 'user', content: CARRY_ON_PRIMER }]);
550
+ }
551
+ });
552
+ }
553
+ /**
554
+ * The inner driver's diagnostics, then everything the autopilot did (§4.8): diagnostics compose
555
+ * down the decorator stack, so they reach the downloaded and the persisted debug log through the
556
+ * host's existing external-diagnostics channel.
557
+ */
558
+ getExternalDiagnostics() {
559
+ return [
560
+ ...super.getExternalDiagnostics(),
561
+ ...this.endedDiagnostics,
562
+ ...[...this.sessions.values()].flatMap((session) => session.diagnostics()),
563
+ ];
564
+ }
565
+ /**
566
+ * Close every session, keeping what it did for the debug log. An answer one was still writing is
567
+ * stale from here: it is never sent, and its cost is recorded as dropped when it lands.
568
+ */
569
+ endSessions() {
570
+ this.generation += 1;
571
+ for (const session of this.sessions.values()) {
572
+ this.endedDiagnostics.push(...session.diagnostics());
573
+ session.dispose();
574
+ }
575
+ this.sessions.clear();
576
+ }
577
+ dispose() {
578
+ const _super = Object.create(null, {
579
+ dispose: { get: () => super.dispose }
580
+ });
581
+ return __awaiter(this, void 0, void 0, function* () {
582
+ this.endSessions();
583
+ yield _super.dispose.call(this);
584
+ });
585
+ }
586
+ // -----------------------------------------------------------------------------------------------
587
+ // Claiming and deciding
588
+ // -----------------------------------------------------------------------------------------------
589
+ onInteractionOpened(opened) {
590
+ var _a, _b;
591
+ const decline = (reason) => {
592
+ this.record('autopilot.declined', {
593
+ interactionId: opened.interactionId,
594
+ component: opened.componentName,
595
+ reason,
596
+ });
597
+ return undefined;
598
+ };
599
+ const owner = this.flowOwner();
600
+ if (!owner || this.onFor !== owner || this.mode !== 'running')
601
+ return decline('paused');
602
+ if (opened.ownerPath[0] !== owner)
603
+ return decline('other-flow');
604
+ // The innermost agent on the path that declares an autopilot answers for it: sub-agents
605
+ // inherit their flow's.
606
+ const agentName = [...opened.ownerPath].reverse().find((name) => this.bindingFor(name));
607
+ const binding = agentName ? this.bindingFor(agentName) : undefined;
608
+ if (!agentName || !binding)
609
+ return decline('no-autopilot');
610
+ if (!this.isAvailable(owner))
611
+ return decline('unavailable');
612
+ if (opened.userOnly)
613
+ return decline('userOnly');
614
+ if (((_b = (_a = binding.config.widgetPolicy) === null || _a === void 0 ? void 0 : _a[opened.componentName]) !== null && _b !== void 0 ? _b : 'human-first') !== 'allowed') {
615
+ return decline('policy');
616
+ }
617
+ if (opened.ctx.controller === undefined)
618
+ return decline('no-controller');
619
+ const held = { opened, agentName };
620
+ this.held = held;
621
+ opened.ctx.onDispose(() => this.onSettled(held));
622
+ this.record('autopilot.claimed', {
623
+ interactionId: opened.interactionId,
624
+ component: opened.componentName,
625
+ ownerPath: opened.ownerPath,
626
+ });
627
+ this.armWatchdog(held);
628
+ // The claim is granted only once this returns, so act on it after.
629
+ queueMicrotask(() => {
630
+ opened.ctx.onSurfaceReady(() => {
631
+ if (this.held === held && opened.ctx.heldBy === AUTOPILOT_CLAIMANT) {
632
+ this.decide(held, { allowHandBack: true });
633
+ }
634
+ });
635
+ });
636
+ return 'claim';
637
+ }
638
+ /** Run one decision on the flow's session, and act on how it ended. */
639
+ decide(held, options) {
640
+ const session = this.sessionFor(held.agentName);
641
+ if (!session)
642
+ return;
643
+ const key = this.stepKey(held);
644
+ session
645
+ .decide(held.opened, Object.assign(Object.assign({}, options), { allowReply: this.mayRefine(held, key), onReply: () => this.countRefinement(held, key) }))
646
+ .then((outcome) => this.onDecided(held, outcome))
647
+ .catch((e) => {
648
+ logger.error('AutopilotDriver: a decision failed:', e);
649
+ this.record('autopilot.error', {
650
+ interactionId: held.opened.interactionId,
651
+ message: e instanceof Error ? e.message : String(e),
652
+ });
653
+ this.handBack(held, this.copyFor(held.agentName).errorReason, 'error');
654
+ });
655
+ }
656
+ /** The step a decision belongs to, for the refinement limit (§4.4). */
657
+ stepKey(held) {
658
+ var _a, _b, _c;
659
+ const { stepKey } = (_b = (_a = this.bindingFor(held.agentName)) === null || _a === void 0 ? void 0 : _a.config) !== null && _b !== void 0 ? _b : {};
660
+ const state = (_c = this.bindingFor(held.agentName)) === null || _c === void 0 ? void 0 : _c.state();
661
+ try {
662
+ if (stepKey && state !== undefined)
663
+ return stepKey({ state });
664
+ }
665
+ catch (e) {
666
+ logger.warn('AutopilotDriver: stepKey threw; using the component name:', e);
667
+ }
668
+ return held.opened.componentName;
669
+ }
670
+ /** Whether a written answer is still allowed on this step. The count resets when the step changes. */
671
+ mayRefine(held, key) {
672
+ var _a;
673
+ if (((_a = this.refinements) === null || _a === void 0 ? void 0 : _a.key) !== key)
674
+ this.refinements = { key, count: 0 };
675
+ return this.refinements.count < this.refinementLimit(held);
676
+ }
677
+ countRefinement(held, key) {
678
+ var _a;
679
+ if (((_a = this.refinements) === null || _a === void 0 ? void 0 : _a.key) !== key)
680
+ this.refinements = { key, count: 0 };
681
+ this.refinements.count += 1;
682
+ const limit = this.refinementLimit(held);
683
+ this.record('autopilot.refinement', {
684
+ interactionId: held.opened.interactionId,
685
+ stepKey: key,
686
+ count: this.refinements.count,
687
+ limitReached: this.refinements.count >= limit,
688
+ });
689
+ }
690
+ /** Written answers allowed per step: the config's when it is a whole number `>= 0`. */
691
+ refinementLimit(held) {
692
+ var _a;
693
+ const limit = (_a = this.bindingFor(held.agentName)) === null || _a === void 0 ? void 0 : _a.config.maxRefinements;
694
+ return typeof limit === 'number' && Number.isFinite(limit) && limit >= 0
695
+ ? limit
696
+ : DEFAULT_MAX_REFINEMENTS;
697
+ }
698
+ /**
699
+ * The watchdog (§4.2, critique S5): a claimed interaction whose work has gone quiet for longer
700
+ * than `watchdogMs` is handed back rather than left to hang silently. Quiet is either no surface
701
+ * ready to act on and no heartbeat, or the autopilot's own decision still running with no model
702
+ * call landing and no heartbeat — a stalled model call, which nothing else times out. It checks a
703
+ * few times per window, and stops when the interaction settles.
704
+ */
705
+ armWatchdog(held) {
706
+ var _a;
707
+ this.disarmWatchdog();
708
+ const windowMs = positiveOr((_a = this.bindingFor(held.agentName)) === null || _a === void 0 ? void 0 : _a.config.watchdogMs, DEFAULT_WATCHDOG_MS);
709
+ const check = () => {
710
+ var _a, _b;
711
+ this.watchdog = undefined;
712
+ if (this.held !== held)
713
+ return;
714
+ const { ctx } = held.opened;
715
+ const deciding = (_a = this.sessions
716
+ .get(held.agentName)) === null || _a === void 0 ? void 0 : _a.decisionProgressAt(held.opened.interactionId);
717
+ const lastSign = Math.max(ctx.lastActivityAt, deciding !== null && deciding !== void 0 ? deciding : 0);
718
+ const quiet = Date.now() - lastSign > windowMs;
719
+ const waiting = deciding !== undefined || !((_b = ctx.surface) === null || _b === void 0 ? void 0 : _b.isReady());
720
+ if (ctx.heldBy === AUTOPILOT_CLAIMANT && waiting && quiet) {
721
+ this.handBack(held, this.copyFor(held.agentName).watchdogReason, 'watchdog');
722
+ return;
723
+ }
724
+ this.watchdog = setTimeout(check, Math.max(1, Math.floor(windowMs / WATCHDOG_CHECKS_PER_WINDOW)));
725
+ };
726
+ this.watchdog = setTimeout(check, Math.max(1, Math.floor(windowMs / WATCHDOG_CHECKS_PER_WINDOW)));
727
+ }
728
+ disarmWatchdog() {
729
+ if (this.watchdog !== undefined)
730
+ clearTimeout(this.watchdog);
731
+ this.watchdog = undefined;
732
+ }
733
+ onDecided(held, outcome) {
734
+ if (outcome.kind !== 'handed-back')
735
+ return;
736
+ this.handBack(held, outcome.reason, outcome.trigger);
737
+ }
738
+ /**
739
+ * The autopilot's self-pause, for one interaction (§4.10): the person gets it, with the reason as
740
+ * the banner, and the autopilot carries on by itself once they answer. Only while it is still
741
+ * the autopilot's to hand back — not settled, not already with the person — and its decision
742
+ * stops with it, so a decision still running spends nothing more on an interaction it no longer
743
+ * holds. Every caller (a decision's own hand-back, a failed decision, the watchdog) gets both.
744
+ */
745
+ handBack(held, reason, trigger) {
746
+ var _a;
747
+ if (this.held !== held || held.opened.ctx.heldBy !== AUTOPILOT_CLAIMANT)
748
+ return;
749
+ (_a = this.sessions.get(held.agentName)) === null || _a === void 0 ? void 0 : _a.abort();
750
+ held.handedOver = 'self';
751
+ held.opened.hold.set('user');
752
+ this.pauseState = {
753
+ scope: 'this-interaction',
754
+ interactionId: held.opened.interactionId,
755
+ reason,
756
+ };
757
+ this.record('autopilot.handed-back', {
758
+ interactionId: held.opened.interactionId,
759
+ reason,
760
+ trigger,
761
+ });
762
+ this.notify();
763
+ }
764
+ /**
765
+ * On detach: hand a claimed interaction the autopilot still holds to the person, stopping its
766
+ * decision, so the driver it wrapped lives on with nothing held by an autopilot that is gone.
767
+ */
768
+ releaseHeld() {
769
+ var _a;
770
+ const held = this.held;
771
+ this.held = undefined;
772
+ if (!held || held.opened.ctx.heldBy !== AUTOPILOT_CLAIMANT)
773
+ return;
774
+ (_a = this.sessions.get(held.agentName)) === null || _a === void 0 ? void 0 : _a.abort();
775
+ held.opened.hold.set('user');
776
+ this.record('autopilot.handed-back', {
777
+ interactionId: held.opened.interactionId,
778
+ trigger: 'detached',
779
+ });
780
+ }
781
+ /** A claimed interaction settled, whoever settled it. */
782
+ onSettled(held) {
783
+ var _a, _b;
784
+ if (this.held !== held)
785
+ return;
786
+ this.held = undefined;
787
+ this.disarmWatchdog();
788
+ (_a = this.sessions.get(held.agentName)) === null || _a === void 0 ? void 0 : _a.abort();
789
+ // The autopilot's own hand-back ends when the person answers: it carries on by itself.
790
+ if (((_b = this.pauseState) === null || _b === void 0 ? void 0 : _b.scope) === 'this-interaction' &&
791
+ this.pauseState.interactionId === held.opened.interactionId) {
792
+ this.pauseState = undefined;
793
+ this.record('autopilot.resumed', { trigger: 'answered', flow: this.onFor });
794
+ }
795
+ this.notify();
796
+ }
797
+ onFlowOwnerChanged(owner) {
798
+ if (owner === this.onFor)
799
+ return;
800
+ this.refinements = undefined;
801
+ // "On" is scoped to the flow it was set for: a new flow, or none, starts paused.
802
+ this.onFor = null;
803
+ this.mode = 'paused';
804
+ this.pauseState = undefined;
805
+ this.endSessions();
806
+ if (owner)
807
+ this.adoptRestored(owner);
808
+ this.notify();
809
+ }
810
+ capabilityFor(scope) {
811
+ const owner = scope.flowOwner;
812
+ if (!owner || !this.bindingFor(owner))
813
+ return undefined;
814
+ const currentMode = () => {
815
+ if (!this.isAvailable(owner))
816
+ return 'unavailable';
817
+ return this.onFor === owner ? this.mode : 'paused';
818
+ };
819
+ return {
820
+ get mode() {
821
+ return currentMode();
822
+ },
823
+ setMode: (mode) => {
824
+ if (this.flowOwner() !== owner)
825
+ return;
826
+ if (mode === 'running')
827
+ this.resume();
828
+ else
829
+ this.pause();
830
+ },
831
+ };
832
+ }
833
+ // -----------------------------------------------------------------------------------------------
834
+ // Helpers
835
+ // -----------------------------------------------------------------------------------------------
836
+ flowOwner() {
837
+ var _a, _b, _c;
838
+ return (_c = (_b = (_a = this.inner).getFlowOwner) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : null;
839
+ }
840
+ bindingFor(agentName) {
841
+ var _a;
842
+ return (_a = this.agents.find((a) => a.name === agentName)) === null || _a === void 0 ? void 0 : _a.autopilot;
843
+ }
844
+ isAvailable(owner) {
845
+ const binding = this.bindingFor(owner);
846
+ if (!binding)
847
+ return false;
848
+ const { availableWhen } = binding.config;
849
+ if (!availableWhen)
850
+ return true;
851
+ const state = binding.state();
852
+ if (state === undefined)
853
+ return false;
854
+ try {
855
+ return availableWhen({ state });
856
+ }
857
+ catch (e) {
858
+ logger.warn('AutopilotDriver: availableWhen threw; treating as unavailable:', e);
859
+ return false;
860
+ }
861
+ }
862
+ copyFor(owner) {
863
+ var _a;
864
+ const custom = owner ? (_a = this.bindingFor(owner)) === null || _a === void 0 ? void 0 : _a.config.copy : undefined;
865
+ return Object.assign(Object.assign({}, DEFAULT_COPY), custom);
866
+ }
867
+ sessionFor(agentName) {
868
+ const existing = this.sessions.get(agentName);
869
+ if (existing)
870
+ return existing;
871
+ const binding = this.bindingFor(agentName);
872
+ if (!binding)
873
+ return undefined;
874
+ const session = new AutopilotSession({
875
+ agentName,
876
+ binding,
877
+ providerRegistry: this.providerRegistry,
878
+ sessionKey: this.sessionKey,
879
+ working: this.inner,
880
+ claimant: AUTOPILOT_CLAIMANT,
881
+ copy: this.copyFor(agentName),
882
+ });
883
+ this.sessions.set(agentName, session);
884
+ return session;
885
+ }
886
+ record(type, detail) {
887
+ recordMetaEvent(this.sessionKey, type, detail);
888
+ }
889
+ notify() {
890
+ for (const listener of this.listeners) {
891
+ try {
892
+ listener();
893
+ }
894
+ catch (e) {
895
+ logger.error('AutopilotDriver: a subscriber threw', e);
896
+ }
897
+ }
898
+ }
899
+ }
900
+ /**
901
+ * The autopilot a host installed, if `driver` is one (GENC-1660). The host installs it outermost,
902
+ * so this is the driver it holds; nothing about the autopilot travels through `AiDriver`.
903
+ *
904
+ * @internal
905
+ */
906
+ export const autopilotOf = (driver) => driver instanceof AutopilotDriver ? driver : undefined;