@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,206 @@
1
+ import { getInteractionRegistration } from '../state/interaction-registry';
2
+ import { filterVisibleMessages } from '../utils/message-partition';
3
+ /** How long a fallback dump of a widget's data may run, in characters. */
4
+ const FALLBACK_DATA_CHARS = 2000;
5
+ /**
6
+ * A widget as text: what the person saw (GENC-1660 §4.7). From its registration's `asText` when it
7
+ * has one; otherwise its name and a size-limited dump of its data — ugly, never wrong, and a nudge
8
+ * to register one.
9
+ */
10
+ export function widgetAsText(componentName, data, result) {
11
+ var _a, _b;
12
+ const asText = (_a = getInteractionRegistration(componentName)) === null || _a === void 0 ? void 0 : _a.asText;
13
+ if (asText) {
14
+ try {
15
+ return asText(data, result);
16
+ }
17
+ catch (_c) {
18
+ // Fall through to the dump: a broken text form must not hide the decision.
19
+ }
20
+ }
21
+ const dump = (_b = JSON.stringify(data)) !== null && _b !== void 0 ? _b : '';
22
+ return `[${componentName}] ${dump.length > FALLBACK_DATA_CHARS ? `${dump.slice(0, FALLBACK_DATA_CHARS)}…` : dump}`;
23
+ }
24
+ /**
25
+ * List a surface's actions for the model (GENC-1660 §4.2). Options are numbered from 1, so they are
26
+ * chosen by number rather than by retyping a label; platform-owned actions keep their names. An
27
+ * unavailable action is listed, and marked, so the model knows it exists but cannot take it.
28
+ */
29
+ export function renderActions(actions, options = {}) {
30
+ var _a;
31
+ const numbering = [];
32
+ const lines = [];
33
+ for (const action of actions) {
34
+ // Past the refinement limit, `reply` is not offered at all: not listed, not runnable.
35
+ if (options.withholdReply && action.args === 'text')
36
+ continue;
37
+ const unavailable = action.enabled ? '' : ' (not available right now)';
38
+ if (action.description !== undefined) {
39
+ // A full-form action: named, described, and finishing or not.
40
+ const call = action.args === 'fields' ? `${action.name}({fields})` : `${action.name}()`;
41
+ const settles = action.finishes ? 'settles it' : 'does not settle it';
42
+ lines.push(` ${call} — ${action.description} (${settles})${unavailable}`);
43
+ if (action.argsSchema)
44
+ lines.push(` fields schema: ${JSON.stringify(action.argsSchema)}`);
45
+ }
46
+ else if (action.args === 'options') {
47
+ lines.push(` select({options}) — choose several, by number or label${unavailable}`);
48
+ }
49
+ else if (action.args === 'text') {
50
+ lines.push(` reply({text}) — answer in your own words${unavailable}`);
51
+ }
52
+ else {
53
+ numbering.push(action.name);
54
+ lines.push(` [${numbering.length}] "${(_a = action.label) !== null && _a !== void 0 ? _a : action.name}"${unavailable}`);
55
+ }
56
+ }
57
+ const allSettle = actions.every((a) => a.description === undefined);
58
+ return {
59
+ text: `${allSettle ? 'Actions (each settles it)' : 'Actions'}:\n${lines.join('\n')}`,
60
+ numbering,
61
+ };
62
+ }
63
+ /** The current decision as one message for the autopilot (GENC-1660 §4.2). */
64
+ export function renderDecision(opts) {
65
+ var _a, _b;
66
+ const { surface } = opts;
67
+ const facts = surface.facts();
68
+ const actions = renderActions(surface.actions(), { withholdReply: opts.withholdReply });
69
+ // A full-form surface describes itself, lists its views, and shows its default view up front, to
70
+ // save a round trip.
71
+ let full = [];
72
+ if (surface.describe) {
73
+ let preview = '';
74
+ try {
75
+ preview = JSON.stringify((_a = surface.read) === null || _a === void 0 ? void 0 : _a.call(surface));
76
+ }
77
+ catch (_c) {
78
+ preview = '';
79
+ }
80
+ full = [
81
+ surface.describe(),
82
+ ...(((_b = surface.views) === null || _b === void 0 ? void 0 : _b.length) ? [`Views (widget_read): ${surface.views.join(', ')}`] : []),
83
+ ...(preview ? [`Now: ${preview}`] : []),
84
+ ];
85
+ }
86
+ const parts = [
87
+ `<interaction id="${opts.interactionId}" widget="${opts.componentName}">`,
88
+ widgetAsText(opts.componentName, opts.data),
89
+ ...full,
90
+ ...(Object.keys(facts).length ? [`Facts: ${JSON.stringify(facts)}`] : []),
91
+ ...(surface.prompt ? [`Guidance: ${surface.prompt}`] : []),
92
+ actions.text,
93
+ ...(opts.decideYourself
94
+ ? [
95
+ 'The user handed this one back to you and asked you to use your best judgement: decide it yourself. hand_back is not available.',
96
+ ]
97
+ : []),
98
+ '</interaction>',
99
+ ];
100
+ return { text: parts.join('\n'), numbering: actions.numbering };
101
+ }
102
+ /**
103
+ * A written question the working agent ended its turn on, as one message for the autopilot
104
+ * (GENC-1660 §4.2): answer it in the user's place, or hand it back.
105
+ */
106
+ export function renderQuestion(question, opts = {}) {
107
+ return [
108
+ '<question>',
109
+ 'The assistant asked the user this, and is waiting for their answer:',
110
+ question,
111
+ "Actions:\n reply({text}) — answer it in the user's place, with widget_act",
112
+ ...(opts.decideYourself
113
+ ? ['The user asked you to use your best judgement: hand_back is not available.']
114
+ : []),
115
+ '</question>',
116
+ ].join('\n');
117
+ }
118
+ /**
119
+ * What the person sees with the chat's default settings: no reasoning, no narration, no tool calls.
120
+ * The autopilot sees the same, whatever toggles this user has switched on, so runs are repeatable
121
+ * (§4.7, §4.11).
122
+ */
123
+ const DEFAULT_VISIBILITY = {
124
+ showToolCalls: false,
125
+ showThinkingSteps: false,
126
+ showNarration: false,
127
+ showAgentSwitchIndicator: false,
128
+ showRestoreNotices: false,
129
+ };
130
+ /**
131
+ * Who answered a resolved widget, as the autopilot is told. Nobody, only when it timed out: the
132
+ * record of who answered is newer than some saved sessions, so a widget without one that did not
133
+ * time out was answered by the person. `claimant` is this autopilot; any other claimant answered on
134
+ * the user's behalf.
135
+ */
136
+ const answeredPhrase = (resolved, answeredBy, claimant) => {
137
+ if ((resolved === null || resolved === void 0 ? void 0 : resolved.status) === 'timeout') {
138
+ return 'answered by nobody';
139
+ }
140
+ if (answeredBy === claimant)
141
+ return 'answered by you';
142
+ if (answeredBy === undefined || answeredBy === 'user')
143
+ return 'answered by the user';
144
+ return "answered on the user's behalf";
145
+ };
146
+ /** Recent entries a short-form transcript keeps verbatim. */
147
+ const SHORT_FORM_RECENT = 20;
148
+ /**
149
+ * What the person could see in `messages`, as lines, for the autopilot's `<since-last>` block
150
+ * (GENC-1660 §4.7). Built with the renderer's own visibility function at the default settings, so
151
+ * what the autopilot is told and what the person sees cannot drift: verbatim user messages and
152
+ * visible assistant output (driver notices included — hidden from the model is not hidden from the
153
+ * person); interactions by their text form and who answered. Never the working agent's hidden prose,
154
+ * tool calls or results: the autopilot checks proposals against its grounding, not against the
155
+ * argument that produced them.
156
+ *
157
+ * `claimant` is this autopilot's id. Its own messages — what it said to the person, and what it
158
+ * sent on the user's behalf — are left out while its own history holds them; `includeOwn` puts them
159
+ * in, labelled as its own, for when it does not (a first decision, a reload, a rebuild, or a
160
+ * compaction that removed what it last saw). `short` keeps the last entries verbatim and reduces
161
+ * older answered widgets to their first line — the form a catch-up after a long flow uses.
162
+ */
163
+ export function visibleTranscript(messages, opts) {
164
+ const visible = filterVisibleMessages([...messages], DEFAULT_VISIBILITY);
165
+ const olderThan = opts.short ? visible.length - SHORT_FORM_RECENT : -1;
166
+ const lines = [];
167
+ visible.forEach((m, i) => {
168
+ var _a, _b, _c, _d, _e, _f;
169
+ if (m.interaction) {
170
+ const { interactionId, componentName, data, resolved, answeredBy } = m.interaction;
171
+ if (interactionId === opts.skipInteractionId || !resolved)
172
+ return;
173
+ const answered = answeredPhrase(resolved, answeredBy, opts.claimant);
174
+ const text = widgetAsText(componentName, data, resolved);
175
+ lines.push(`[${componentName}, ${answered}] ${i < olderThan ? text.split('\n')[0] : text}`);
176
+ return;
177
+ }
178
+ if (m.role === 'compacted-summary') {
179
+ lines.push(`[Earlier, summarised] ${(_a = m.content) !== null && _a !== void 0 ? _a : ''}`);
180
+ // The user's own words from the summarised part, which a summary must not paraphrase.
181
+ for (const u of (_c = (_b = m.compaction) === null || _b === void 0 ? void 0 : _b.preservedUserMessages) !== null && _c !== void 0 ? _c : []) {
182
+ lines.push(u.delegate === opts.claimant && opts.includeOwn
183
+ ? `You (on the user's behalf, earlier): ${u.content}`
184
+ : u.delegate
185
+ ? `User (on their behalf, earlier): ${u.content}`
186
+ : `User (earlier): ${u.content}`);
187
+ }
188
+ return;
189
+ }
190
+ if (m.delegate === opts.claimant) {
191
+ if (!opts.includeOwn || !((_d = m.content) === null || _d === void 0 ? void 0 : _d.trim()))
192
+ return;
193
+ lines.push(m.role === 'assistant'
194
+ ? `You (to the user): ${m.content}`
195
+ : `You (on the user's behalf): ${m.content}`);
196
+ return;
197
+ }
198
+ if ((m.role === 'user' || m.role === 'synthetic-user') && ((_e = m.content) === null || _e === void 0 ? void 0 : _e.trim())) {
199
+ lines.push(m.delegate ? `User (on their behalf): ${m.content}` : `User: ${m.content}`);
200
+ return;
201
+ }
202
+ if (m.role === 'assistant' && ((_f = m.content) === null || _f === void 0 ? void 0 : _f.trim()))
203
+ lines.push(`Assistant: ${m.content}`);
204
+ });
205
+ return lines;
206
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The tag of the turn-level hand-back widget (GENC-1660 §4.10): what the autopilot shows when a
3
+ * working turn ends needing the person, and there is no widget to hand over.
4
+ *
5
+ * @beta
6
+ */
7
+ export const AUTOPILOT_HAND_BACK = 'ai-autopilot-hand-back';
8
+ /**
9
+ * The hand-back's controller. DOM-free: it exposes the choices as a decision (so the visible
10
+ * transcript and the debug log describe it like any widget) and resolves through `ctx.resolve`. The
11
+ * hand-back is always `userOnly`, so nothing but the person ever answers it.
12
+ */
13
+ export class HandBackController {
14
+ constructor(data, ctx) {
15
+ this.data = data;
16
+ this.ctx = ctx;
17
+ ctx.exposeDecision({
18
+ options: data.options.map((o) => ({ id: o.id, label: o.label })),
19
+ freeText: data.freeText,
20
+ choose: (answer) => this.choose(answer),
21
+ });
22
+ }
23
+ /** The person's choice, from the view's buttons or its text box. */
24
+ choose(answer) {
25
+ const payload = answer.kind === 'text'
26
+ ? { choice: 'reply', text: answer.text }
27
+ : {
28
+ choice: (answer.kind === 'option'
29
+ ? answer.option
30
+ : answer.options[0]),
31
+ };
32
+ this.ctx.resolve({ status: 'approved', payload });
33
+ }
34
+ }
35
+ /** The hand-back as text: what the person saw, and what they chose. */
36
+ export function handBackAsText(data, result) {
37
+ var _a, _b;
38
+ const d = data;
39
+ const lines = [d.message, ...(d.question ? [`Question: ${d.question}`] : [])];
40
+ lines.push(`Options: ${d.options.map((o) => o.label).join(' | ')}`);
41
+ const payload = result === null || result === void 0 ? void 0 : result.payload;
42
+ if (payload) {
43
+ const label = (_b = (_a = d.options.find((o) => o.id === payload.choice)) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : payload.choice;
44
+ lines.push(`Answer: ${payload.choice === 'reply' ? payload.text : label}`);
45
+ }
46
+ return lines.join('\n');
47
+ }
@@ -0,0 +1,129 @@
1
+ import { __decorate } from "tslib";
2
+ import { css, customElement, GenesisElement, html, observable, repeat, when, } from '@genesislcap/web-core';
3
+ import { AUTOPILOT_HAND_BACK, } from './hand-back.controller';
4
+ const template = html `
5
+ <p class="message">${(x) => { var _a; return (_a = x.data) === null || _a === void 0 ? void 0 : _a.message; }}</p>
6
+ ${when((x) => { var _a; return !!((_a = x.data) === null || _a === void 0 ? void 0 : _a.question); }, html `
7
+ <p class="question">${(x) => { var _a; return (_a = x.data) === null || _a === void 0 ? void 0 : _a.question; }}</p>
8
+ `)}
9
+ ${when((x) => x.answer !== undefined, html `
10
+ <p class="answer">✓ ${(x) => x.answer}</p>
11
+ `)}
12
+ ${when((x) => x.answer === undefined, html `
13
+ ${when((x) => { var _a; return !!((_a = x.data) === null || _a === void 0 ? void 0 : _a.freeText); }, html `
14
+ <textarea
15
+ class="reply"
16
+ rows="2"
17
+ :value="${(x) => x.draft}"
18
+ @input="${(x, c) => {
19
+ x.draft = c.event.target.value;
20
+ return true;
21
+ }}"
22
+ ></textarea>
23
+ <rapid-button
24
+ appearance="accent"
25
+ ?disabled="${(x) => !x.draft.trim()}"
26
+ @click="${(x) => {
27
+ x.reply();
28
+ return true;
29
+ }}"
30
+ >
31
+ ${(x) => { var _a; return (_a = x.data) === null || _a === void 0 ? void 0 : _a.sendLabel; }}
32
+ </rapid-button>
33
+ `)}
34
+ <div class="options">
35
+ ${repeat((x) => { var _a, _b; return (_b = (_a = x.data) === null || _a === void 0 ? void 0 : _a.options) !== null && _b !== void 0 ? _b : []; }, html `
36
+ <rapid-button
37
+ @click="${(o, c) => {
38
+ c.parent.choose(o.id);
39
+ return true;
40
+ }}"
41
+ >
42
+ ${(o) => o.label}
43
+ </rapid-button>
44
+ `)}
45
+ </div>
46
+ `)}
47
+ `;
48
+ const styles = css `
49
+ :host {
50
+ display: block;
51
+ font: inherit;
52
+ }
53
+ .message,
54
+ .question,
55
+ .answer {
56
+ margin: 0 0 calc(var(--design-unit) * 2px);
57
+ }
58
+ .question {
59
+ font-style: italic;
60
+ }
61
+ .reply {
62
+ width: 100%;
63
+ box-sizing: border-box;
64
+ font: inherit;
65
+ margin-bottom: calc(var(--design-unit) * 2px);
66
+ }
67
+ .options {
68
+ display: flex;
69
+ flex-wrap: wrap;
70
+ gap: calc(var(--design-unit) * 2px);
71
+ margin-top: calc(var(--design-unit) * 2px);
72
+ }
73
+ `;
74
+ /**
75
+ * The turn-level hand-back's view (GENC-1660 §4.10): the message, the working agent's question if
76
+ * there was one, and the choices. It only renders: choosing calls the controller, which resolves.
77
+ * Always the person's to answer (`userOnly`), so it never hides itself.
78
+ *
79
+ * @beta
80
+ */
81
+ let AutopilotHandBack = class AutopilotHandBack extends GenesisElement {
82
+ constructor() {
83
+ super(...arguments);
84
+ this.interactionId = '';
85
+ this.draft = '';
86
+ }
87
+ get data() {
88
+ var _a;
89
+ return (_a = this.interaction) === null || _a === void 0 ? void 0 : _a.data;
90
+ }
91
+ /** What was chosen, once resolved. */
92
+ get answer() {
93
+ var _a, _b, _c, _d, _e;
94
+ if (((_a = this.interaction) === null || _a === void 0 ? void 0 : _a.status) !== 'resolved')
95
+ return undefined;
96
+ const payload = this.interaction.result.payload;
97
+ if (!payload)
98
+ return '';
99
+ if (payload.choice === 'reply')
100
+ return (_b = payload.text) !== null && _b !== void 0 ? _b : '';
101
+ return (_e = (_d = (_c = this.data) === null || _c === void 0 ? void 0 : _c.options.find((o) => o.id === payload.choice)) === null || _d === void 0 ? void 0 : _d.label) !== null && _e !== void 0 ? _e : payload.choice;
102
+ }
103
+ get controller() {
104
+ var _a;
105
+ return ((_a = this.interaction) === null || _a === void 0 ? void 0 : _a.status) === 'pending'
106
+ ? this.interaction.interactionContext.controller
107
+ : undefined;
108
+ }
109
+ choose(option) {
110
+ var _a;
111
+ (_a = this.controller) === null || _a === void 0 ? void 0 : _a.choose({ kind: 'option', option });
112
+ }
113
+ reply() {
114
+ var _a;
115
+ const text = this.draft.trim();
116
+ if (text)
117
+ (_a = this.controller) === null || _a === void 0 ? void 0 : _a.choose({ kind: 'text', text });
118
+ }
119
+ };
120
+ __decorate([
121
+ observable
122
+ ], AutopilotHandBack.prototype, "interaction", void 0);
123
+ __decorate([
124
+ observable
125
+ ], AutopilotHandBack.prototype, "draft", void 0);
126
+ AutopilotHandBack = __decorate([
127
+ customElement({ name: AUTOPILOT_HAND_BACK, template, styles })
128
+ ], AutopilotHandBack);
129
+ export { AutopilotHandBack };
@@ -0,0 +1,4 @@
1
+ export { AUTOPILOT_CLAIMANT, AutopilotDriver, } from './autopilot-driver';
2
+ export { AUTOPILOT_HAND_BACK, } from './hand-back/hand-back.controller';
3
+ // The view: importing the package defines the element, so the host can render the hand-back.
4
+ export { AutopilotHandBack } from './hand-back/hand-back';
@@ -41,6 +41,7 @@ export class AgenticActivityBus {
41
41
  * browser environment; elsewhere the bus stays purely in-process.
42
42
  */
43
43
  ensureChannel() {
44
+ var _a, _b;
44
45
  if (this.channelInitialised)
45
46
  return;
46
47
  this.channelInitialised = true;
@@ -48,6 +49,12 @@ export class AgenticActivityBus {
48
49
  if (this.crossTabEvents.size === 0 || !this.channelNameFactory || !inBrowser)
49
50
  return;
50
51
  this.channel = new BroadcastChannel(this.channelNameFactory());
52
+ // Node's BroadcastChannel (which jsdom test runs get, since they also have a `window`) is an
53
+ // open handle that keeps the process alive, and tests that touched the bus hung after their last
54
+ // assertion, again and again, unless they remembered to `close()` it. Unref it so a channel can
55
+ // never hold the process open: it still delivers while anything else keeps the process running.
56
+ // Browsers have no `unref` (a tab never "exits"), so there this does nothing.
57
+ (_b = (_a = this.channel).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
51
58
  this.channel.onmessage = (ev) => {
52
59
  var _a;
53
60
  const { type, detail } = ((_a = ev.data) !== null && _a !== void 0 ? _a : {});
@@ -0,0 +1,26 @@
1
+ import { agenticActivityBus } from './ai-activity-bus';
2
+ /**
3
+ * Which sessions have something waiting on the person while their autopilot is available
4
+ * (GENC-1660 §4.10), kept as well as announced: the collapsed assistant can mount after the change
5
+ * was published — a popin, or a host adding the bubble late — and an event alone would leave its
6
+ * badge wrong until the next change. Keyed by session, so two assistants on one page each badge
7
+ * only their own.
8
+ */
9
+ const waiting = new Set();
10
+ /** Set it for one session, announcing `autopilot-needs-user` when it changes. */
11
+ export function setAutopilotNeedsUser(sessionKey, needsUser) {
12
+ if (waiting.has(sessionKey) === needsUser)
13
+ return;
14
+ if (needsUser)
15
+ waiting.add(sessionKey);
16
+ else
17
+ waiting.delete(sessionKey);
18
+ agenticActivityBus.publish('autopilot-needs-user', { sessionKey, needsUser });
19
+ }
20
+ /**
21
+ * Whether a session has something waiting on the person — or, without a key (a bubble that cannot
22
+ * tell which assistant it holds), whether any session has.
23
+ */
24
+ export function autopilotNeedsUser(sessionKey) {
25
+ return sessionKey === undefined ? waiting.size > 0 : waiting.has(sessionKey);
26
+ }
@@ -1,2 +1,12 @@
1
1
  /** Sentinel value representing "Auto" where a menu row needs a string value. */
2
2
  export const AGENT_PICKER_AUTO_VALUE = '__auto__';
3
+ /**
4
+ * The picker's default name for orchestration: the assistant routes each message to the agent it
5
+ * suits, as opposed to a pinned agent. Not "Auto", which other tools use for picking a model tier;
6
+ * "Orchestration" says that several agents stand behind it.
7
+ */
8
+ export const DEFAULT_ORCHESTRATION_LABEL = 'Orchestration';
9
+ /** The same choice as the picker menu's first row. The same word by default; separate so a host can say more there. */
10
+ export const DEFAULT_ORCHESTRATION_MENU_LABEL = 'Orchestration';
11
+ /** What orchestration does, as the menu row's hint and the pill's tooltip. */
12
+ export const DEFAULT_ORCHESTRATION_DESCRIPTION = 'Each message goes to the specialist it suits.';
@@ -1,7 +1,7 @@
1
1
  import { __decorate } from "tslib";
2
2
  import { customElement, GenesisElement, observable, volatile } from '@genesislcap/web-core';
3
3
  import { pinColourAt } from '../../styles/ai-colours';
4
- import { AGENT_PICKER_AUTO_VALUE } from './agent-picker.constants';
4
+ import { AGENT_PICKER_AUTO_VALUE, DEFAULT_ORCHESTRATION_DESCRIPTION, DEFAULT_ORCHESTRATION_MENU_LABEL, } from './agent-picker.constants';
5
5
  import { styles } from './agent-picker.styles';
6
6
  import { AgentPickerTemplate } from './agent-picker.template';
7
7
  export { AGENT_PICKER_AUTO_VALUE };
@@ -39,6 +39,10 @@ let AgentPicker = class AgentPicker extends GenesisElement {
39
39
  this.pinnedAgentName = null;
40
40
  /** When true, rows show a neutral dot instead of the per-agent brand tint. */
41
41
  this.disablePinColours = false;
42
+ /** The orchestration row's name, from `chatConfig.picker.orchestrationMenuLabel` (resolved by the host). */
43
+ this.orchestrationLabel = DEFAULT_ORCHESTRATION_MENU_LABEL;
44
+ /** The orchestration row's hint, from `chatConfig.picker.orchestrationDescription` (resolved by the host). */
45
+ this.orchestrationDescription = DEFAULT_ORCHESTRATION_DESCRIPTION;
42
46
  }
43
47
  /** Top-level agents that opted in to manual selection. */
44
48
  get selectableAgents() {
@@ -103,6 +107,12 @@ __decorate([
103
107
  __decorate([
104
108
  observable
105
109
  ], AgentPicker.prototype, "disablePinColours", void 0);
110
+ __decorate([
111
+ observable
112
+ ], AgentPicker.prototype, "orchestrationLabel", void 0);
113
+ __decorate([
114
+ observable
115
+ ], AgentPicker.prototype, "orchestrationDescription", void 0);
106
116
  __decorate([
107
117
  volatile
108
118
  ], AgentPicker.prototype, "selectableAgents", null);
@@ -1,6 +1,4 @@
1
1
  import { html, repeat } from '@genesislcap/web-core';
2
- const AUTO_LABEL = 'Auto';
3
- const AUTO_HINT = 'Let the assistant route each message to the right agent.';
4
2
  /**
5
3
  * One selectable row. `menuitemradio` rather than `menuitem` because the choice
6
4
  * is single-select and sticky — a screen reader should announce which agent is
@@ -41,8 +39,8 @@ export const AgentPickerTemplate = () => html `
41
39
  >
42
40
  <span class="menu-item-mark" aria-hidden="true"></span>
43
41
  <span class="menu-item-body">
44
- <span class="menu-item-name">${() => AUTO_LABEL}</span>
45
- <span class="menu-item-hint">${() => AUTO_HINT}</span>
42
+ <span class="menu-item-name">${(x) => x.orchestrationLabel}</span>
43
+ <span class="menu-item-hint">${(x) => x.orchestrationDescription}</span>
46
44
  </span>
47
45
  </button>
48
46
  <div class="menu-separator" role="separator"></div>
@@ -11,8 +11,10 @@ export const AI_DRIVER_EVENT_TYPES = Object.keys({
11
11
  'sub-agent-stop': true,
12
12
  'interaction-start': true,
13
13
  'interaction-stop': true,
14
+ 'interaction-settled': true,
14
15
  'provider-changed': true,
15
16
  'external-cost-recorded': true,
17
+ 'checkpoint-saved': true,
16
18
  'agent-changed': true,
17
19
  'agent-released': true,
18
20
  'flow-owner-changed': true,
@@ -119,6 +119,14 @@ export class BaseDriverDecorator extends EventTarget {
119
119
  var _a, _b, _c;
120
120
  return (_c = (_b = (_a = this.inner).getFlowOwner) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : null;
121
121
  }
122
+ getInteractionCheckpoint() {
123
+ var _a, _b;
124
+ return (_b = (_a = this.inner).getInteractionCheckpoint) === null || _b === void 0 ? void 0 : _b.call(_a);
125
+ }
126
+ restoreInteractionCheckpoint(saved) {
127
+ var _a, _b;
128
+ (_b = (_a = this.inner).restoreInteractionCheckpoint) === null || _b === void 0 ? void 0 : _b.call(_a, saved);
129
+ }
122
130
  /**
123
131
  * Forwarded, so an interceptor installed on any layer lands on the driver that opens the
124
132
  * interactions. A decorator that answers interactions installs its own on `this.inner`.
@@ -1,6 +1,7 @@
1
1
  import { __decorate } from "tslib";
2
2
  import { attr, customElement, GenesisElement, html, observable } from '@genesislcap/web-core';
3
3
  import { agenticActivityBus } from '../../channel/ai-activity-bus';
4
+ import { autopilotNeedsUser } from '../../channel/autopilot-needs-user';
4
5
  import { styles } from './chat-bubble.styles';
5
6
  import { AiChatBubbleTemplate } from './chat-bubble.template';
6
7
  /**
@@ -60,6 +61,12 @@ let AiChatBubble = class AiChatBubble extends GenesisElement {
60
61
  super(...arguments);
61
62
  this.title = 'AI Assistant';
62
63
  this.designSystemPrefix = 'rapid';
64
+ /**
65
+ * What the "needs you" badge on the orb says — its tooltip, and what a screen reader announces
66
+ * when it appears (GENC-1660 §4.10). The bubble cannot read the autopilot's own wording, so a host
67
+ * that renames or localises the autopilot sets this too.
68
+ */
69
+ this.needsInputLabel = 'The assistant needs your input';
63
70
  /** @internal */
64
71
  this.dialogContent = [];
65
72
  /** @internal */
@@ -80,6 +87,11 @@ let AiChatBubble = class AiChatBubble extends GenesisElement {
80
87
  this.dialogOpen = false;
81
88
  /** Whether the bubble orb is currently hovered. @internal */
82
89
  this.hovered = false;
90
+ /**
91
+ * Whether the autopilot waits on the person (GENC-1660 §4.10): a hand-back the closed chat would
92
+ * otherwise hide. Shown as a badge on the orb; opening the chat shows what it waits on. @internal
93
+ */
94
+ this.needsUser = false;
83
95
  this.dragging = false;
84
96
  this.dragTarget = null;
85
97
  this.startMouseX = 0;
@@ -153,6 +165,7 @@ let AiChatBubble = class AiChatBubble extends GenesisElement {
153
165
  this.dialogLeft = Math.max(VIEWPORT_EDGE_MARGIN, this.bubbleLeft - DIALOG_BUBBLE_HORIZONTAL_OFFSET);
154
166
  this.dialogTop = Math.max(VIEWPORT_EDGE_MARGIN, this.bubbleTop - DIALOG_OPEN_TOP_OFFSET);
155
167
  this.dialogOpen = true;
168
+ this.showWaitingIfBadged();
156
169
  }
157
170
  }
158
171
  }
@@ -182,9 +195,12 @@ let AiChatBubble = class AiChatBubble extends GenesisElement {
182
195
  this.hidden = false;
183
196
  this.dialogOpen = true;
184
197
  });
198
+ this.refreshNeedsUser();
199
+ const unsubNeedsUser = agenticActivityBus.subscribe('autopilot-needs-user', () => this.refreshNeedsUser());
185
200
  this.unsubBus = () => {
186
201
  unsubPopout();
187
202
  unsubPopin();
203
+ unsubNeedsUser();
188
204
  };
189
205
  // Measure tooltip width once after first render. The bubble is visible on connect
190
206
  // (dialogOpen starts false), so the element is in the DOM by the time rAF fires.
@@ -266,7 +282,30 @@ let AiChatBubble = class AiChatBubble extends GenesisElement {
266
282
  this.resizeObserver = undefined;
267
283
  }
268
284
  }
285
+ /**
286
+ * The session of the assistant this bubble holds, when it can tell: the key the assistant
287
+ * publishes under, read from the assistant in the bubble's light DOM — whether or not the dialog
288
+ * (and with it the slot) is rendered, since the badge shows while it is not.
289
+ */
290
+ assistantSessionKey() {
291
+ var _a;
292
+ const assistant = this.findAssistant([...this.children]);
293
+ return (_a = assistant === null || assistant === void 0 ? void 0 : assistant.stateKey) !== null && _a !== void 0 ? _a : undefined;
294
+ }
295
+ /** Opened from the badge: take the person to what is waiting on them (GENC-1660). */
296
+ showWaitingIfBadged() {
297
+ if (!this.needsUser)
298
+ return;
299
+ agenticActivityBus.publish('autopilot-show-waiting', {
300
+ sessionKey: this.assistantSessionKey(),
301
+ });
302
+ }
303
+ /** Badge only for this bubble's own assistant (GENC-1660). */
304
+ refreshNeedsUser() {
305
+ this.needsUser = autopilotNeedsUser(this.assistantSessionKey());
306
+ }
269
307
  dialogContentChanged(_, next) {
308
+ this.refreshNeedsUser();
270
309
  const assistant = this.findAssistant(next);
271
310
  if (!assistant) {
272
311
  this.removeCloseButton();
@@ -415,6 +454,9 @@ __decorate([
415
454
  __decorate([
416
455
  attr({ attribute: 'design-system-prefix' })
417
456
  ], AiChatBubble.prototype, "designSystemPrefix", void 0);
457
+ __decorate([
458
+ attr({ attribute: 'needs-input-label' })
459
+ ], AiChatBubble.prototype, "needsInputLabel", void 0);
418
460
  __decorate([
419
461
  observable
420
462
  ], AiChatBubble.prototype, "dialogContent", void 0);
@@ -445,6 +487,9 @@ __decorate([
445
487
  __decorate([
446
488
  observable
447
489
  ], AiChatBubble.prototype, "hovered", void 0);
490
+ __decorate([
491
+ observable
492
+ ], AiChatBubble.prototype, "needsUser", void 0);
448
493
  AiChatBubble = __decorate([
449
494
  customElement({
450
495
  name: 'foundation-ai-chat-bubble',
@@ -99,6 +99,32 @@ export const styles = css `
99
99
  translate: 0 0;
100
100
  }
101
101
 
102
+ /* The autopilot waits on the person (GENC-1660): a dot on the orb, which opening the chat answers. */
103
+ .needs-you-badge {
104
+ position: absolute;
105
+ top: 2px;
106
+ right: 2px;
107
+ width: 14px;
108
+ height: 14px;
109
+ border: 2px solid var(--neutral-layer-1, #fff);
110
+ border-radius: 50%;
111
+ background: var(--accent-fill-rest, rgb(0 120 255));
112
+ pointer-events: none;
113
+ }
114
+
115
+ /* The badge's live region: out of paint and flow, kept in the accessibility tree. */
116
+ .visually-hidden {
117
+ position: absolute;
118
+ width: 1px;
119
+ height: 1px;
120
+ margin: -1px;
121
+ padding: 0;
122
+ overflow: hidden;
123
+ clip-path: inset(50%);
124
+ white-space: nowrap;
125
+ border: 0;
126
+ }
127
+
102
128
  .pulse-ring {
103
129
  position: absolute;
104
130
  width: 62px;