@genesislcap/ai-assistant 15.61.0 → 15.63.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 (137) 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 +4759 -717
  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/genesis/write-commit.d.ts +17 -7
  53. package/dist/dts/genesis/write-commit.d.ts.map +1 -1
  54. package/dist/dts/index.d.ts +7 -3
  55. package/dist/dts/index.d.ts.map +1 -1
  56. package/dist/dts/main/main.d.ts +128 -5
  57. package/dist/dts/main/main.d.ts.map +1 -1
  58. package/dist/dts/main/main.styles.d.ts.map +1 -1
  59. package/dist/dts/main/main.template.d.ts +2 -2
  60. package/dist/dts/main/main.template.d.ts.map +1 -1
  61. package/dist/dts/react.d.ts +10 -0
  62. package/dist/dts/state/debug-event-log.d.ts +1 -1
  63. package/dist/dts/state/debug-event-log.d.ts.map +1 -1
  64. package/dist/dts/state/decision-surface.d.ts +46 -0
  65. package/dist/dts/state/decision-surface.d.ts.map +1 -0
  66. package/dist/dts/state/interaction-context.d.ts +21 -1
  67. package/dist/dts/state/interaction-context.d.ts.map +1 -1
  68. package/dist/dts/state/interaction-registry.d.ts +63 -0
  69. package/dist/dts/state/interaction-registry.d.ts.map +1 -0
  70. package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
  71. package/dist/dts/state/persistence/session-snapshot.d.ts +21 -0
  72. package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
  73. package/dist/dts/state/run-steps.d.ts +66 -0
  74. package/dist/dts/state/run-steps.d.ts.map +1 -0
  75. package/dist/dts/types/ai-chat-widget.d.ts +7 -1
  76. package/dist/dts/types/ai-chat-widget.d.ts.map +1 -1
  77. package/dist/dts/types/interaction-context.d.ts +66 -1
  78. package/dist/dts/types/interaction-context.d.ts.map +1 -1
  79. package/dist/dts/types/interaction-interceptor.d.ts +60 -2
  80. package/dist/dts/types/interaction-interceptor.d.ts.map +1 -1
  81. package/dist/dts/types/interaction-surface.d.ts +195 -0
  82. package/dist/dts/types/interaction-surface.d.ts.map +1 -0
  83. package/dist/dts/utils/context-tokens.d.ts +6 -0
  84. package/dist/dts/utils/context-tokens.d.ts.map +1 -1
  85. package/dist/dts/utils/delegated-message.d.ts +13 -0
  86. package/dist/dts/utils/delegated-message.d.ts.map +1 -1
  87. package/dist/dts/utils/history-transform.d.ts.map +1 -1
  88. package/dist/dts/utils/preserved-user-messages.d.ts +21 -0
  89. package/dist/dts/utils/preserved-user-messages.d.ts.map +1 -0
  90. package/dist/esm/autopilot/autopilot-driver.js +906 -0
  91. package/dist/esm/autopilot/autopilot-session.js +449 -0
  92. package/dist/esm/autopilot/autopilot-tools.js +68 -0
  93. package/dist/esm/autopilot/decision-message.js +206 -0
  94. package/dist/esm/autopilot/hand-back/hand-back.controller.js +47 -0
  95. package/dist/esm/autopilot/hand-back/hand-back.js +129 -0
  96. package/dist/esm/autopilot/index.js +4 -0
  97. package/dist/esm/channel/ai-activity-bus.js +7 -0
  98. package/dist/esm/channel/autopilot-needs-user.js +26 -0
  99. package/dist/esm/components/agent-picker/agent-picker.constants.js +10 -0
  100. package/dist/esm/components/agent-picker/agent-picker.js +11 -1
  101. package/dist/esm/components/agent-picker/agent-picker.template.js +2 -4
  102. package/dist/esm/components/ai-driver/ai-driver.js +2 -0
  103. package/dist/esm/components/ai-driver/base-driver-decorator.js +8 -0
  104. package/dist/esm/components/chat-bubble/chat-bubble.js +45 -0
  105. package/dist/esm/components/chat-bubble/chat-bubble.styles.js +26 -0
  106. package/dist/esm/components/chat-bubble/chat-bubble.template.js +13 -0
  107. package/dist/esm/components/chat-driver/chat-driver.js +53 -18
  108. package/dist/esm/components/chat-driver/interaction-controller.js +297 -12
  109. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +16 -2
  110. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +28 -11
  111. package/dist/esm/config/define-stateful-agent.js +34 -29
  112. package/dist/esm/genesis/write-outcome.js +2 -2
  113. package/dist/esm/index.js +3 -0
  114. package/dist/esm/main/main.js +334 -15
  115. package/dist/esm/main/main.styles.js +126 -0
  116. package/dist/esm/main/main.template.js +116 -11
  117. package/dist/esm/state/debug-event-log.js +28 -1
  118. package/dist/esm/state/decision-surface.js +209 -0
  119. package/dist/esm/state/interaction-context.js +89 -1
  120. package/dist/esm/state/interaction-registry.js +33 -0
  121. package/dist/esm/state/persistence/session-persister.js +23 -11
  122. package/dist/esm/state/persistence/session-snapshot.js +1 -1
  123. package/dist/esm/state/run-steps.js +77 -0
  124. package/dist/esm/types/interaction-surface.js +1 -0
  125. package/dist/esm/utils/context-tokens.js +23 -9
  126. package/dist/esm/utils/delegated-message.js +15 -0
  127. package/dist/esm/utils/history-transform.js +18 -7
  128. package/dist/esm/utils/preserved-user-messages.js +31 -0
  129. package/dist/react.cjs +7 -0
  130. package/dist/react.mjs +6 -0
  131. package/docs/styling.md +1 -1
  132. package/package.json +17 -17
  133. package/sandbox/README.md +38 -2
  134. package/sandbox/autopilot.ts +358 -0
  135. package/sandbox/controls.ts +24 -1
  136. package/sandbox/fixtures.ts +2 -0
  137. package/sandbox/sandbox.ts +188 -1
@@ -0,0 +1,449 @@
1
+ import { __awaiter } from "tslib";
2
+ import { NOOP_ACTIVITY_BUS } from '../channel/ai-activity-bus';
3
+ import { ChatDriver } from '../components/chat-driver/chat-driver';
4
+ import { clearSession, getMetaEvents, recordMetaEvent } from '../state/debug-event-log';
5
+ import { buildTimelineEntries } from '../state/persistence/build-timeline-entries';
6
+ import { logger } from '../utils/logger';
7
+ import { sumUsage } from '../utils/sum-usage';
8
+ import { AUTOPILOT_TOOL_DEFINITIONS, HAND_BACK, WIDGET_ACT, WIDGET_READ } from './autopilot-tools';
9
+ import { renderActions, renderDecision, renderQuestion, visibleTranscript, } from './decision-message';
10
+ /** Model calls a decision may take before it is handed back, unless the config says otherwise. */
11
+ const DEFAULT_MAX_ITERATIONS_PER_DECISION = 6;
12
+ /** The autopilot's own history, in characters, past which it is rebuilt short (§4.7, N3). */
13
+ const DEFAULT_HISTORY_BUDGET_CHARS = 120000;
14
+ /**
15
+ * The share of the history budget a rebuilt digest may fill, so the rebuilt history leaves room for
16
+ * the decisions that follow and does not pass the budget again straight away.
17
+ */
18
+ const DIGEST_SHARE_OF_BUDGET = 0.5;
19
+ /** Sessions created so far: each one's debug-log key is its own (see `ownSessionKey`). */
20
+ let sessionCount = 0;
21
+ /**
22
+ * A numeric guard from an autopilot's config: the value when it is a positive, finite number, the
23
+ * fallback otherwise — so `0`, a negative or `NaN` cannot turn a guard into a hair trigger.
24
+ */
25
+ export const positiveOr = (value, fallback) => typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
26
+ /**
27
+ * The newest `lines` that fit in `maxChars`, after one line saying how many earlier ones were left
28
+ * out: a transcript cut to size from the old end, since what the autopilot decides next turns on the
29
+ * recent past.
30
+ */
31
+ export function newestThatFit(lines, maxChars) {
32
+ let used = 0;
33
+ let first = lines.length;
34
+ while (first > 0 && used + lines[first - 1].length + 1 <= maxChars) {
35
+ first -= 1;
36
+ used += lines[first].length + 1;
37
+ }
38
+ return first > 0 ? [`[${first} earlier entries omitted]`, ...lines.slice(first)] : [...lines];
39
+ }
40
+ /** Characters in a history: content and tool arguments, which is what grows. */
41
+ const historyChars = (messages) => messages.reduce((n, m) => {
42
+ var _a, _b, _c, _d;
43
+ return n +
44
+ ((_b = (_a = m.content) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) +
45
+ ((_d = (_c = m.toolResult) === null || _c === void 0 ? void 0 : _c.content.length) !== null && _d !== void 0 ? _d : 0) +
46
+ (m.toolCalls ? JSON.stringify(m.toolCalls.map((c) => c.args)).length : 0);
47
+ }, 0);
48
+ /** When the head compaction summary of `history` was made, if it has one. */
49
+ const compactedAtOf = (history) => { var _a, _b; return (_b = (_a = history.find((m) => m.role === 'compacted-summary')) === null || _a === void 0 ? void 0 : _a.compaction) === null || _b === void 0 ? void 0 : _b.createdAt; };
50
+ /** The last message of `history`, as the next decision finds it again. */
51
+ function seenThrough(history) {
52
+ var _a;
53
+ const index = history.length - 1;
54
+ if (index < 0)
55
+ return undefined;
56
+ const last = history[index];
57
+ return Object.assign(Object.assign({}, (last.timestamp && last.role !== 'compacted-summary' ? { timestamp: last.timestamp } : {})), { role: last.role, content: last.content, interactionId: (_a = last.interaction) === null || _a === void 0 ? void 0 : _a.interactionId, index, compactedAt: compactedAtOf(history) });
58
+ }
59
+ /**
60
+ * Where to read `history` from: just after the message last seen, or `undefined` when there is none
61
+ * or it is gone — a compaction removed it — and the autopilot has to catch up.
62
+ */
63
+ function readFrom(history, seen) {
64
+ var _a;
65
+ if (!seen)
66
+ return undefined;
67
+ if (seen.timestamp === undefined) {
68
+ // Saved before messages were stamped: the index holds only while nothing was compacted since.
69
+ const unmoved = compactedAtOf(history) === seen.compactedAt && seen.index < history.length;
70
+ return unmoved ? seen.index + 1 : undefined;
71
+ }
72
+ // From the end: several messages can share a millisecond, and the one seen was the last then.
73
+ for (let i = history.length - 1; i >= 0; i -= 1) {
74
+ const m = history[i];
75
+ if (m.role !== 'compacted-summary' &&
76
+ m.timestamp === seen.timestamp &&
77
+ m.role === seen.role &&
78
+ m.content === seen.content &&
79
+ ((_a = m.interaction) === null || _a === void 0 ? void 0 : _a.interactionId) === seen.interactionId) {
80
+ return i + 1;
81
+ }
82
+ }
83
+ return undefined;
84
+ }
85
+ /** Resolve a static-or-function config field against the working agent's live state. */
86
+ const resolveWith = (input, ctx, state) => __awaiter(void 0, void 0, void 0, function* () {
87
+ return typeof input === 'function'
88
+ ? input(Object.assign(Object.assign({}, ctx), { state }))
89
+ : input;
90
+ });
91
+ /**
92
+ * One autopilot for one flow (GENC-1660): its own `ChatDriver`, its own history, and the last
93
+ * message of what the person has seen that it was told about. Each decision is one short turn on
94
+ * that driver, which ends when the autopilot acts or hands back.
95
+ *
96
+ * Its history holds only its own conversation — the decisions it was sent, its tool calls and
97
+ * their results — never the working agent's. What happened without it (answers the person gave,
98
+ * messages they typed, what the assistant showed) arrives at the top of the next decision as a
99
+ * `<since-last>` block, built from the working history by code. Its own messages in the working
100
+ * history (what it sent on the user's behalf) are left out of that block while its history holds
101
+ * them, and labelled as its own whenever it does not: on its first decision (after a reload, too),
102
+ * after a size rebuild, and when a compaction removed the last message it saw.
103
+ */
104
+ export class AutopilotSession {
105
+ constructor(deps) {
106
+ this.deps = deps;
107
+ /** The tail of the decision queue: decisions run one after another, never interleaved. */
108
+ this.queue = Promise.resolve();
109
+ /** Its own messages a size-bounded rebuild dropped, kept for the debug log. */
110
+ this.archived = [];
111
+ const { config } = deps.binding;
112
+ sessionCount += 1;
113
+ this.ownKey = `${deps.sessionKey}::autopilot::${deps.agentName}::${sessionCount}`;
114
+ this.driver = new ChatDriver(deps.providerRegistry, {
115
+ sessionKey: this.ownSessionKey,
116
+ // Its own, inert activity channel: the host's loading state, halo and budget wall follow the
117
+ // working driver only.
118
+ activityBus: NOOP_ACTIVITY_BUS,
119
+ maxToolIterations: positiveOr(config.maxIterationsPerDecision, DEFAULT_MAX_ITERATIONS_PER_DECISION),
120
+ });
121
+ // Forced tool use, and the `completeSubAgent` early exit that ends a turn on an act or a
122
+ // hand-back.
123
+ this.driver.markAsSubAgent();
124
+ this.driver.installAgent(this.agentConfig());
125
+ }
126
+ /**
127
+ * Decide one interaction this autopilot holds. Resolves when the autopilot's turn ends, however it
128
+ * ended; the interaction itself is settled (or handed back) by then, unless the turn was aborted.
129
+ *
130
+ * Queued behind any decision still unwinding — an aborted one finishes its in-flight call before
131
+ * it can stop — so a Resume straight after a Pause is not lost. Checked again when its turn
132
+ * comes: if the autopilot no longer holds the interaction, nothing is sent.
133
+ */
134
+ decide(opened, options) {
135
+ return this.enqueue(() => __awaiter(this, void 0, void 0, function* () {
136
+ const surface = opened.ctx.surface;
137
+ if (!surface || opened.ctx.heldBy !== this.deps.claimant)
138
+ return { kind: 'aborted' };
139
+ const decision = renderDecision({
140
+ interactionId: opened.interactionId,
141
+ componentName: opened.componentName,
142
+ data: opened.data,
143
+ surface,
144
+ decideYourself: !options.allowHandBack,
145
+ withholdReply: !options.allowReply,
146
+ });
147
+ return this.runTurn(Object.assign(Object.assign({}, options), { opened, numbering: decision.numbering }), decision.text, opened.interactionId);
148
+ }));
149
+ }
150
+ /**
151
+ * Answer a written question the working agent ended its turn on (GENC-1660 §4.2): `replied` with
152
+ * the text to send on the user's behalf, or `handed-back`. Either way the outcome carries what it
153
+ * cost (see `DecisionOutcome`).
154
+ *
155
+ * Queued like `decide`, and checked again when its turn comes: if `stillWanted` says no — the
156
+ * person paused since it was asked — nothing is sent and it ends `aborted`.
157
+ */
158
+ answerQuestion(question, options) {
159
+ return this.enqueue(() => __awaiter(this, void 0, void 0, function* () {
160
+ if (options.stillWanted && !options.stillWanted())
161
+ return { kind: 'aborted' };
162
+ return this.runTurn({ allowHandBack: options.allowHandBack, allowReply: true, question, numbering: [] }, renderQuestion(question, { decideYourself: !options.allowHandBack }));
163
+ }));
164
+ }
165
+ /**
166
+ * Everything the autopilot did, as debug-log entries (§4.8): its turns (prompt, tools, model,
167
+ * usage), its messages (the decisions it was sent, its calls and their results) and its own
168
+ * events, tagged `delegate` and breadcrumbed with the agent it serves, so a replay can follow one
169
+ * decision from what it was shown to what it did and what it cost.
170
+ */
171
+ diagnostics() {
172
+ const label = `${this.deps.agentName} › Autopilot`;
173
+ return buildTimelineEntries({
174
+ turnSnapshots: this.driver.getTurnSnapshots(),
175
+ messages: [...this.archived, ...this.driver.getRawHistory()],
176
+ metaEvents: getMetaEvents(this.ownSessionKey),
177
+ }).map((entry) => (Object.assign(Object.assign({}, entry), { delegate: this.deps.claimant, agentName: label })));
178
+ }
179
+ /**
180
+ * The autopilot's own debug-log key: under the working session's, named for the agent it serves,
181
+ * and unique to this session, so two agents' autopilots never relabel each other's events and a
182
+ * later flow's session never reads an ended one's again. Cleared when the session is disposed,
183
+ * after the driver has harvested it.
184
+ */
185
+ get ownSessionKey() {
186
+ return this.ownKey;
187
+ }
188
+ /**
189
+ * When the decision on `interactionId` last showed progress (it started, or a model call or tool
190
+ * result landed); `undefined` when no decision on it is running. Read by the watchdog.
191
+ */
192
+ decisionProgressAt(interactionId) {
193
+ var _a, _b;
194
+ return ((_b = (_a = this.current) === null || _a === void 0 ? void 0 : _a.opened) === null || _b === void 0 ? void 0 : _b.interactionId) === interactionId ? this.progressAt : undefined;
195
+ }
196
+ /** Stop the decision in progress: the person took it back, or it ended another way. */
197
+ abort() {
198
+ var _a;
199
+ var _b;
200
+ if (!this.current)
201
+ return;
202
+ (_a = (_b = this.current).outcome) !== null && _a !== void 0 ? _a : (_b.outcome = { kind: 'aborted' });
203
+ this.driver.cancel();
204
+ }
205
+ /**
206
+ * Stop, and drop its debug-log bucket. The owner harvests `diagnostics()` first. Cleared again once
207
+ * the aborted turn has unwound, so nothing it records on the way out is left behind.
208
+ */
209
+ dispose() {
210
+ this.abort();
211
+ const key = this.ownSessionKey;
212
+ clearSession(key);
213
+ void Promise.allSettled([this.driver.dispose(), this.queue]).then(() => clearSession(key));
214
+ }
215
+ enqueue(run) {
216
+ const next = this.queue.then(run);
217
+ this.queue = next.catch(() => undefined);
218
+ return next;
219
+ }
220
+ /**
221
+ * One turn on the autopilot's driver: the `<since-last>` block, then the decision. Ends when the
222
+ * autopilot acts, replies or hands back, when it passes its spend cap, or when it runs out of
223
+ * calls. Books what it cost to `interactionId`; for a written question, which has none, returns
224
+ * it on the outcome for the driver to book on its reply.
225
+ */
226
+ runTurn(current, decisionText, interactionId) {
227
+ return __awaiter(this, void 0, void 0, function* () {
228
+ var _a, _b, _c, _d, _e, _f, _g;
229
+ // Past its budget, the autopilot's own history is rebuilt: dropped, and replaced by a short-form
230
+ // digest of everything the person saw. Its first decision in a flow — including the first after a
231
+ // reload — is a catch-up too: the whole visible transcript, since it has seen none of it. So is a
232
+ // decision after a compaction removed the last message it saw: what came between is gone, and
233
+ // only the summary says what it was.
234
+ const budget = positiveOr(this.deps.binding.config.historyBudgetChars, DEFAULT_HISTORY_BUDGET_CHARS);
235
+ const ownChars = historyChars(this.driver.getRawHistory());
236
+ const rebuild = ownChars > budget;
237
+ if (rebuild) {
238
+ this.archived.push(...this.driver.getRawHistory());
239
+ this.driver.loadHistory([]);
240
+ this.seen = undefined;
241
+ }
242
+ const history = (_c = (_b = (_a = this.deps.working).getRawHistory) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : [];
243
+ const from = readFrom(history, this.seen);
244
+ const catchUp = from === undefined;
245
+ const why = rebuild ? 'size' : this.seen ? 'compacted' : 'first';
246
+ if (catchUp && history.length) {
247
+ recordMetaEvent(this.deps.sessionKey, 'autopilot.catch-up', Object.assign(Object.assign({ why }, (rebuild ? { ownChars } : {})), { messages: history.length }));
248
+ }
249
+ // A rebuild and a compaction both catch up in short form: there is a long flow behind them.
250
+ const short = catchUp && why !== 'first';
251
+ const transcript = visibleTranscript(history.slice(from !== null && from !== void 0 ? from : 0), {
252
+ claimant: this.deps.claimant,
253
+ skipInteractionId: interactionId,
254
+ short,
255
+ // On a catch-up its own history does not hold what it sent on the user's behalf.
256
+ includeOwn: catchUp,
257
+ });
258
+ // Short form shortens only older widgets, so on a long flow the digest alone could pass the
259
+ // budget, and every decision after would rebuild and resend it. Cut to a share of the budget.
260
+ const sinceLast = short
261
+ ? newestThatFit(transcript, Math.floor(budget * DIGEST_SHARE_OF_BUDGET))
262
+ : transcript;
263
+ this.seen = seenThrough(history);
264
+ const message = [
265
+ ...(sinceLast.length ? [`<since-last>\n${sinceLast.join('\n')}\n</since-last>`] : []),
266
+ decisionText,
267
+ ].join('\n');
268
+ this.current = current;
269
+ recordMetaEvent(this.deps.sessionKey, 'autopilot.decision-sent', Object.assign(Object.assign({}, (interactionId ? { interactionId } : { question: true })), { agent: this.deps.agentName, chars: message.length }));
270
+ const before = this.driver.getRawHistory().length;
271
+ // The per-decision spend cap, checked as each model call lands: past it, the decision is handed
272
+ // back rather than finished at any price.
273
+ const cap = positiveOr(this.deps.binding.config.maxDecisionCostUsd, undefined);
274
+ const { copy } = this.deps;
275
+ // Each landing is progress, for the watchdog; then the spend cap is checked.
276
+ const onHistory = () => {
277
+ this.progressAt = Date.now();
278
+ if (cap === undefined || !this.current || this.current.outcome)
279
+ return;
280
+ const spent = sumUsage(this.driver.getRawHistory().slice(before)).costUsd;
281
+ if (spent <= cap)
282
+ return;
283
+ this.current.outcome = { kind: 'handed-back', reason: copy.budgetReason, trigger: 'budget' };
284
+ this.driver.cancel();
285
+ };
286
+ this.progressAt = Date.now();
287
+ this.driver.addEventListener('history-updated', onHistory);
288
+ try {
289
+ yield this.driver.sendMessage(message);
290
+ }
291
+ catch (e) {
292
+ logger.error('AutopilotSession: the decision turn threw:', e);
293
+ (_d = current.outcome) !== null && _d !== void 0 ? _d : (current.outcome = { kind: 'handed-back', reason: copy.errorReason, trigger: 'error' });
294
+ }
295
+ this.driver.removeEventListener('history-updated', onHistory);
296
+ const outcome = (_e = current.outcome) !== null && _e !== void 0 ? _e : {
297
+ kind: 'handed-back',
298
+ reason: copy.noDecisionReason,
299
+ trigger: 'no-decision',
300
+ };
301
+ this.current = undefined;
302
+ this.progressAt = undefined;
303
+ // What this decision cost, so it reaches the session total: booked to the interaction it
304
+ // answered, or, for a written question, returned for the driver to book on its reply.
305
+ const cost = sumUsage(this.driver.getRawHistory().slice(before)).costUsd;
306
+ if (!interactionId)
307
+ return cost > 0 ? Object.assign(Object.assign({}, outcome), { costUsd: cost }) : outcome;
308
+ // A refused booking records its own dropped-cost event; a driver with no way to book records
309
+ // nothing, so it is recorded here.
310
+ if (cost > 0 && ((_g = (_f = this.deps.working).recordExternalCost) === null || _g === void 0 ? void 0 : _g.call(_f, interactionId, cost)) === undefined) {
311
+ recordMetaEvent(this.deps.sessionKey, 'autopilot.cost-dropped', {
312
+ costUsd: cost,
313
+ interactionId,
314
+ why: 'no-recorder',
315
+ });
316
+ }
317
+ return outcome;
318
+ });
319
+ }
320
+ /** The autopilot as an agent: its config's resolvers bound to the working agent's state. */
321
+ agentConfig() {
322
+ var _a, _b, _c;
323
+ const { binding } = this.deps;
324
+ const { config } = binding;
325
+ return {
326
+ name: `${this.deps.agentName} autopilot`,
327
+ description: `Answers ${this.deps.agentName}'s questions on the user's behalf.`,
328
+ systemPrompt: (ctx) => __awaiter(this, void 0, void 0, function* () { var _a; return (_a = (yield resolveWith(config.systemPrompt, ctx, binding.state()))) !== null && _a !== void 0 ? _a : ''; }),
329
+ tailContext: config.tailContext
330
+ ? (ctx) => __awaiter(this, void 0, void 0, function* () { var _a; return (_a = (yield resolveWith(config.tailContext, ctx, binding.state()))) !== null && _a !== void 0 ? _a : ''; })
331
+ : undefined,
332
+ provider: typeof config.provider === 'function'
333
+ ? (ctx) => __awaiter(this, void 0, void 0, function* () {
334
+ var _a;
335
+ return (_a = (yield resolveWith(config.provider, ctx, binding.state()))) !== null && _a !== void 0 ? _a : this.deps.providerRegistry.defaultName();
336
+ })
337
+ : config.provider,
338
+ // The prefix (tools, system, stored history) is stable by design, so cache all of it.
339
+ cachePolicy: { scope: 'history' },
340
+ toolDefinitions: [...AUTOPILOT_TOOL_DEFINITIONS, ...((_b = (_a = config.tools) === null || _a === void 0 ? void 0 : _a.definitions) !== null && _b !== void 0 ? _b : [])],
341
+ toolHandlers: Object.assign(Object.assign({}, (_c = config.tools) === null || _c === void 0 ? void 0 : _c.handlers), this.fixedHandlers()),
342
+ };
343
+ }
344
+ /** `widget_read`, `widget_act` and `hand_back`, acting on the decision in progress. */
345
+ fixedHandlers() {
346
+ const relist = () => {
347
+ var _a;
348
+ const current = this.current;
349
+ if ((current === null || current === void 0 ? void 0 : current.question) !== undefined) {
350
+ return "Actions:\n reply({text}) — answer the question in the user's place";
351
+ }
352
+ const surface = (_a = current === null || current === void 0 ? void 0 : current.opened) === null || _a === void 0 ? void 0 : _a.ctx.surface;
353
+ if (!current || !surface)
354
+ return 'There is nothing to decide now.';
355
+ const actions = renderActions(surface.actions(), { withholdReply: !current.allowReply });
356
+ current.numbering = actions.numbering;
357
+ return actions.text;
358
+ };
359
+ return {
360
+ [WIDGET_READ]: (args, ctx) => __awaiter(this, void 0, void 0, function* () {
361
+ var _a, _b;
362
+ const current = this.current;
363
+ if ((current === null || current === void 0 ? void 0 : current.question) !== undefined)
364
+ return `Question: ${current.question}\n${relist()}`;
365
+ const surface = (_a = current === null || current === void 0 ? void 0 : current.opened) === null || _a === void 0 ? void 0 : _a.ctx.surface;
366
+ if (!surface)
367
+ return 'There is nothing to decide now.';
368
+ const view = args === null || args === void 0 ? void 0 : args.view;
369
+ // A full-form surface is read by view; a short-form decision has only its facts.
370
+ const state = surface.read ? surface.read(view) : surface.facts();
371
+ const result = `${JSON.stringify(state)}\n${relist()}`;
372
+ // A read serves the decision it was made for. Once that turn ends it is stale — the widget
373
+ // has moved on — so it leaves the autopilot's history as a one-line note rather than riding
374
+ // every later decision at full size. Not a re-read pointer: reading again would read the
375
+ // next decision, not this one.
376
+ (_b = ctx.condenseWhen) === null || _b === void 0 ? void 0 : _b.call(ctx, {
377
+ on: { kind: 'turnEnd' },
378
+ response: {
379
+ replaceWith: `[An earlier read${view ? ` of the "${view}" view` : ''}, ${result.length} characters, for a decision that has since been made.]`,
380
+ },
381
+ });
382
+ return result;
383
+ }),
384
+ [WIDGET_ACT]: (args, ctx) => __awaiter(this, void 0, void 0, function* () {
385
+ var _a, _b, _c, _d;
386
+ const current = this.current;
387
+ if (!current)
388
+ return 'There is nothing to decide now.';
389
+ const { action, options, text, note, fields } = args;
390
+ // A written question has one action: the answer, sent on the user's behalf by the driver.
391
+ if (current.question !== undefined) {
392
+ const answer = (text !== null && text !== void 0 ? text : '').trim();
393
+ if (String(action !== null && action !== void 0 ? action : '').trim() !== 'reply' || !answer) {
394
+ return `Not done: answer with action "reply" and your text.\n${relist()}`;
395
+ }
396
+ current.outcome = { kind: 'replied', text: answer };
397
+ (_a = ctx.completeSubAgent) === null || _a === void 0 ? void 0 : _a.call(ctx, { replied: true });
398
+ return 'Sent.';
399
+ }
400
+ const opened = current.opened;
401
+ // A number names an option in the list last sent; a name is a platform-owned action.
402
+ const byNumber = (value) => /^\d+$/.test(value.trim()) ? current.numbering[Number(value.trim()) - 1] : undefined;
403
+ const name = (_b = byNumber(String(action !== null && action !== void 0 ? action : ''))) !== null && _b !== void 0 ? _b : String(action !== null && action !== void 0 ? action : '').trim();
404
+ if (name === 'reply' && !current.allowReply) {
405
+ return `Not done: a written answer is not available for this one; choose an option or hand it back.\n${relist()}`;
406
+ }
407
+ // A full-form action takes its arguments as `fields`, a JSON object sent as text.
408
+ let parsedFields;
409
+ if (fields !== undefined && fields !== '') {
410
+ try {
411
+ parsedFields = JSON.parse(fields);
412
+ }
413
+ catch (_e) {
414
+ return `Not done: fields is not valid JSON.\n${relist()}`;
415
+ }
416
+ }
417
+ const result = yield opened.hold.invoke(name, parsedFields !== undefined
418
+ ? parsedFields
419
+ : Object.assign(Object.assign({}, (options ? { options: options.map((o) => { var _a; return (_a = byNumber(o)) !== null && _a !== void 0 ? _a : o; }) } : {})), (text !== undefined ? { text } : {})), { note });
420
+ if (!result.ok) {
421
+ return `Not done: ${result.message}\n${relist()}`;
422
+ }
423
+ if (name === 'reply')
424
+ (_c = current.onReply) === null || _c === void 0 ? void 0 : _c.call(current);
425
+ recordMetaEvent(this.deps.sessionKey, 'autopilot.acted', Object.assign({ interactionId: opened.interactionId, action: name, finished: result.finished }, (note ? { note } : {})));
426
+ if (result.finished) {
427
+ current.outcome = { kind: 'acted', action: name };
428
+ (_d = ctx.completeSubAgent) === null || _d === void 0 ? void 0 : _d.call(ctx, { acted: name });
429
+ return 'Done.';
430
+ }
431
+ const changed = result.output !== undefined ? `\nResult: ${JSON.stringify(result.output)}` : '';
432
+ return `Done, and it is still open.${changed}\n${relist()}`;
433
+ }),
434
+ [HAND_BACK]: (args, ctx) => __awaiter(this, void 0, void 0, function* () {
435
+ var _a, _b;
436
+ const current = this.current;
437
+ if (!current)
438
+ return 'There is nothing to decide now.';
439
+ if (!current.allowHandBack) {
440
+ return 'hand_back is not available: the user asked you to decide this one yourself.';
441
+ }
442
+ const reason = String((_a = args.reason) !== null && _a !== void 0 ? _a : '').trim();
443
+ current.outcome = { kind: 'handed-back', reason, trigger: 'model' };
444
+ (_b = ctx.completeSubAgent) === null || _b === void 0 ? void 0 : _b.call(ctx, { handedBack: reason });
445
+ return 'Handed to the user.';
446
+ }),
447
+ };
448
+ }
449
+ }
@@ -0,0 +1,68 @@
1
+ /** Read the widget as it stands now. */
2
+ export const WIDGET_READ = 'widget_read';
3
+ /** Run one of the widget's actions. */
4
+ export const WIDGET_ACT = 'widget_act';
5
+ /** Hand this one decision to the person. */
6
+ export const HAND_BACK = 'hand_back';
7
+ /**
8
+ * The autopilot's three fixed tools (GENC-1660 §4.2, §4.6). Fixed for every decision, so the cached
9
+ * prefix holds: what varies — the widget, its actions, their arguments — travels in the decision
10
+ * message, not here. Plain string and string-array fields only, which every provider accepts
11
+ * (critique round 2, N5: Gemini rejects a type-array union).
12
+ */
13
+ export const AUTOPILOT_TOOL_DEFINITIONS = [
14
+ {
15
+ name: WIDGET_READ,
16
+ description: 'Read the current decision as it stands now: its state (one of its views, if it lists views), and its actions with their numbers and whether each is available. Use it when the decision message may be out of date, or to read a view it lists.',
17
+ parameters: {
18
+ type: 'object',
19
+ properties: {
20
+ view: { type: 'string', description: 'One of the views the decision lists, if any.' },
21
+ },
22
+ },
23
+ },
24
+ {
25
+ name: WIDGET_ACT,
26
+ description: 'Act on the current decision, as the user would by clicking. `action` is the number of an option from the decision\'s list (e.g. "1"), or a named action from that list: "select" with `options` to choose several, or "reply" with `text` to answer in your own words. ' +
27
+ 'If it settles the decision, your turn ends. ' +
28
+ '`note` is shown to the user as the assistant speaking, before the outcome: one short sentence in the first person saying what you chose and why, e.g. "Your brief has no external feeds, so I\'ll skip Data Feeds." Never mention an autopilot or a second agent.',
29
+ parameters: {
30
+ type: 'object',
31
+ required: ['action'],
32
+ properties: {
33
+ action: {
34
+ type: 'string',
35
+ description: 'An option number from the list, starting at 1, or a named action ("select", "reply").',
36
+ },
37
+ options: {
38
+ type: 'array',
39
+ items: { type: 'string' },
40
+ description: 'For "select": the options to choose, by number or label.',
41
+ },
42
+ text: { type: 'string', description: 'For "reply": your answer, in the user\'s place.' },
43
+ fields: {
44
+ type: 'string',
45
+ description: 'For a named action that takes arguments: a JSON object, as text, matching the args schema the decision lists for it.',
46
+ },
47
+ note: {
48
+ type: 'string',
49
+ description: 'Shown to the user as the assistant speaking. One sentence, first person.',
50
+ },
51
+ },
52
+ },
53
+ },
54
+ {
55
+ name: HAND_BACK,
56
+ description: 'Give this one decision to the user, when it rests on something only they can know and neither their brief nor their own answers settle it. Your turn ends and the user sees the question. `reason` is shown to them as the assistant speaking, after "I need your input on this one:" — say plainly what you need from them. Never mention an autopilot or a second agent.',
57
+ parameters: {
58
+ type: 'object',
59
+ required: ['reason'],
60
+ properties: {
61
+ reason: {
62
+ type: 'string',
63
+ description: 'What the user needs to decide, and why it is theirs.',
64
+ },
65
+ },
66
+ },
67
+ },
68
+ ];