@genesislcap/ai-assistant 15.60.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 +4419 -377
  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,209 @@
1
+ /** The `select` action a multi-select adds. Platform-owned, so never an option's id. */
2
+ export const SELECT_ACTION = 'select';
3
+ /** The `reply` action `freeText` adds. Platform-owned, so never an option's id. */
4
+ export const REPLY_ACTION = 'reply';
5
+ const normalise = (option) => {
6
+ var _a;
7
+ return typeof option === 'string'
8
+ ? { id: option, label: option }
9
+ : { id: (_a = option.id) !== null && _a !== void 0 ? _a : option.label, label: option.label, enabledWhen: option.enabledWhen };
10
+ };
11
+ const isEnabled = (option) => {
12
+ var _a, _b;
13
+ try {
14
+ return (_b = (_a = option.enabledWhen) === null || _a === void 0 ? void 0 : _a.call(option)) !== null && _b !== void 0 ? _b : true;
15
+ }
16
+ catch (_c) {
17
+ // A guard that throws cannot vouch for its button.
18
+ return false;
19
+ }
20
+ };
21
+ /**
22
+ * Build the surface for a short-form decision (GENC-1660). Its actions are its options (by id),
23
+ * plus `select` for a multi-select and `reply` for a written answer; all of them finish. Every
24
+ * read — facts, actions, enabled-ness — is taken fresh, so the surface always describes the
25
+ * widget as it is now.
26
+ *
27
+ * Throws if an option's id collides with another or with a platform-owned name, which is a bug in
28
+ * the widget rather than something to recover from.
29
+ */
30
+ export function buildDecisionSurface(spec) {
31
+ const options = spec.options.map(normalise);
32
+ const ids = new Set();
33
+ for (const { id } of options) {
34
+ if (ids.has(id))
35
+ throw new Error(`exposeDecision: two options share the id "${id}".`);
36
+ if ((spec.multi && id === SELECT_ACTION) || (spec.freeText && id === REPLY_ACTION)) {
37
+ throw new Error(`exposeDecision: an option cannot use the reserved id "${id}".`);
38
+ }
39
+ ids.add(id);
40
+ }
41
+ const byIdOrLabel = (key) => typeof key === 'string' ? options.find((o) => o.id === key || o.label === key) : undefined;
42
+ const actions = () => {
43
+ const enabled = options.filter(isEnabled);
44
+ return [
45
+ ...options.map((o) => ({
46
+ name: o.id,
47
+ label: o.label,
48
+ finishes: true,
49
+ enabled: isEnabled(o),
50
+ })),
51
+ ...(spec.multi
52
+ ? [
53
+ {
54
+ name: SELECT_ACTION,
55
+ finishes: true,
56
+ enabled: enabled.length > 0,
57
+ args: 'options',
58
+ },
59
+ ]
60
+ : []),
61
+ ...(spec.freeText
62
+ ? [{ name: REPLY_ACTION, finishes: true, enabled: true, args: 'text' }]
63
+ : []),
64
+ ];
65
+ };
66
+ const answer = (a) => () => spec.choose(a);
67
+ return {
68
+ prompt: spec.prompt,
69
+ facts: () => { var _a, _b; return (_b = (_a = spec.facts) === null || _a === void 0 ? void 0 : _a.call(spec)) !== null && _b !== void 0 ? _b : {}; },
70
+ actions,
71
+ isReady: () => actions().some((a) => a.finishes && a.enabled),
72
+ prepare(name, args) {
73
+ if (spec.multi && name === SELECT_ACTION) {
74
+ const picked = args === null || args === void 0 ? void 0 : args.options;
75
+ if (!Array.isArray(picked) || picked.length === 0) {
76
+ return {
77
+ error: 'invalid-args',
78
+ message: '`select` takes { options: string[] }, not empty.',
79
+ };
80
+ }
81
+ const chosen = picked.map(byIdOrLabel);
82
+ const unknown = picked.filter((_, i) => !chosen[i]);
83
+ if (unknown.length) {
84
+ return { error: 'invalid-args', message: `Not options here: ${unknown.join(', ')}.` };
85
+ }
86
+ const disabled = chosen.filter((o) => !isEnabled(o));
87
+ if (disabled.length) {
88
+ return {
89
+ error: 'disabled',
90
+ message: `Not available right now: ${disabled.map((o) => o.label).join(', ')}.`,
91
+ };
92
+ }
93
+ const picks = chosen;
94
+ return {
95
+ choice: picks.map((o) => o.label).join(', '),
96
+ finishes: true,
97
+ run: answer({ kind: 'options', options: picks.map((o) => o.id) }),
98
+ };
99
+ }
100
+ if (spec.freeText && name === REPLY_ACTION) {
101
+ const text = args === null || args === void 0 ? void 0 : args.text;
102
+ if (typeof text !== 'string' || !text.trim()) {
103
+ return { error: 'invalid-args', message: '`reply` takes { text: string }, not empty.' };
104
+ }
105
+ return { choice: text, finishes: true, run: answer({ kind: 'text', text }) };
106
+ }
107
+ const option = options.find((o) => o.id === name);
108
+ if (!option) {
109
+ return { error: 'unknown-action', message: `No action "${name}" here.` };
110
+ }
111
+ if (!isEnabled(option)) {
112
+ return { error: 'disabled', message: `"${option.label}" is not available right now.` };
113
+ }
114
+ return {
115
+ choice: option.label,
116
+ finishes: true,
117
+ run: answer({ kind: 'option', option: option.id }),
118
+ };
119
+ },
120
+ };
121
+ }
122
+ /** How long a choice's text may run in a `decidedOnUsersBehalf` record, in characters. */
123
+ const CHOICE_CHARS = 300;
124
+ /** The JSON types a schema's `type` names, as `typeof` reads them. */
125
+ const matchesType = (value, type) => {
126
+ switch (type) {
127
+ case 'string':
128
+ return typeof value === 'string';
129
+ case 'number':
130
+ case 'integer':
131
+ return typeof value === 'number' && (type === 'number' || Number.isInteger(value));
132
+ case 'boolean':
133
+ return typeof value === 'boolean';
134
+ case 'array':
135
+ return Array.isArray(value);
136
+ case 'object':
137
+ return !!value && typeof value === 'object' && !Array.isArray(value);
138
+ default:
139
+ return true;
140
+ }
141
+ };
142
+ /**
143
+ * Check `args` against an action's schema: an object, with its `required` properties present and
144
+ * each listed property of its declared top-level `type`. Deliberately shallow — enough to refuse a
145
+ * malformed call with a reason the model can act on, without a schema library in the browser.
146
+ */
147
+ export function checkArgs(args, schema) {
148
+ var _a, _b;
149
+ if (!schema)
150
+ return undefined;
151
+ if (!matchesType(args, 'object'))
152
+ return 'its arguments must be an object.';
153
+ const record = args;
154
+ const required = (_a = schema.required) !== null && _a !== void 0 ? _a : [];
155
+ const missing = required.filter((key) => record[key] === undefined);
156
+ if (missing.length)
157
+ return `missing: ${missing.join(', ')}.`;
158
+ const properties = ((_b = schema.properties) !== null && _b !== void 0 ? _b : {});
159
+ const wrong = Object.entries(properties)
160
+ .filter(([key, prop]) => record[key] !== undefined && !matchesType(record[key], prop === null || prop === void 0 ? void 0 : prop.type))
161
+ .map(([key, prop]) => `${key} should be ${String(prop === null || prop === void 0 ? void 0 : prop.type)}`);
162
+ return wrong.length ? `${wrong.join('; ')}.` : undefined;
163
+ }
164
+ /**
165
+ * Build the surface for a full-form spec (GENC-1660 §4.2): named actions, some editing and some
166
+ * finishing, each guarded and schema-checked; a description; views to read. Every read is fresh.
167
+ */
168
+ export function buildFullSurface(spec) {
169
+ const guard = (enabledWhen) => {
170
+ var _a;
171
+ try {
172
+ return (_a = enabledWhen === null || enabledWhen === void 0 ? void 0 : enabledWhen()) !== null && _a !== void 0 ? _a : true;
173
+ }
174
+ catch (_b) {
175
+ return false;
176
+ }
177
+ };
178
+ const actions = () => Object.entries(spec.actions).map(([name, a]) => (Object.assign({ name, finishes: !!a.finishes, enabled: guard(a.enabledWhen), description: a.description }, (a.argsSchema ? { args: 'fields', argsSchema: a.argsSchema } : {}))));
179
+ return {
180
+ prompt: spec.prompt,
181
+ facts: () => ({}),
182
+ describe: () => spec.describe(),
183
+ views: spec.views,
184
+ read: (view) => spec.read(view),
185
+ actions,
186
+ isReady: () => actions().some((a) => a.enabled),
187
+ prepare(name, args) {
188
+ // Own actions only: a name from the model such as `constructor` or `toString` must not reach
189
+ // the object's prototype.
190
+ const action = Object.prototype.hasOwnProperty.call(spec.actions, name)
191
+ ? spec.actions[name]
192
+ : undefined;
193
+ if (!action)
194
+ return { error: 'unknown-action', message: `No action "${name}" here.` };
195
+ if (!guard(action.enabledWhen)) {
196
+ return { error: 'disabled', message: `"${name}" is not available right now.` };
197
+ }
198
+ const problem = checkArgs(args !== null && args !== void 0 ? args : {}, action.argsSchema);
199
+ if (problem)
200
+ return { error: 'invalid-args', message: `"${name}": ${problem}` };
201
+ const shown = args && Object.keys(args).length ? ` ${JSON.stringify(args)}` : '';
202
+ return {
203
+ choice: `${name}${shown}`.slice(0, CHOICE_CHARS),
204
+ finishes: !!action.finishes,
205
+ run: () => action.run(args !== null && args !== void 0 ? args : {}),
206
+ };
207
+ },
208
+ };
209
+ }
@@ -1,4 +1,5 @@
1
1
  import { logger } from '../utils/logger';
2
+ import { buildDecisionSurface, buildFullSurface } from './decision-surface';
2
3
  /**
3
4
  * Create a live, per-interaction context (GENC-1390). Framework-agnostic: the
4
5
  * `subscribe`/`notify` pair is a plain observer set, not FAST reactivity. See
@@ -6,18 +7,58 @@ import { logger } from '../utils/logger';
6
7
  *
7
8
  * @internal
8
9
  */
9
- export function createInteractionContext(interactionId) {
10
+ export function createInteractionContext(interactionId, deps = {}) {
10
11
  const state = {};
11
12
  const onceCache = new Map();
12
13
  const listeners = new Set();
13
14
  const disposers = new Set();
15
+ const readyListeners = new Set();
14
16
  let disposed = false;
17
+ let settled = false;
15
18
  let heldBy = 'user';
19
+ let controller;
20
+ let surface;
21
+ let lastActivityAt = Date.now();
22
+ let checkpoint = deps.checkpoint;
23
+ /** Fire every waiting `onSurfaceReady` once the surface has something a claimant could do. */
24
+ const checkReady = () => {
25
+ if (disposed || !surface || readyListeners.size === 0 || !surface.isReady())
26
+ return;
27
+ const waiting = [...readyListeners];
28
+ readyListeners.clear();
29
+ for (const fn of waiting) {
30
+ try {
31
+ fn();
32
+ }
33
+ catch (e) {
34
+ logger.error('InteractionContext onSurfaceReady callback threw', e);
35
+ }
36
+ }
37
+ };
16
38
  const context = {
17
39
  interactionId,
18
40
  get heldBy() {
19
41
  return heldBy;
20
42
  },
43
+ get controller() {
44
+ return controller;
45
+ },
46
+ get surface() {
47
+ return surface;
48
+ },
49
+ get lastActivityAt() {
50
+ return lastActivityAt;
51
+ },
52
+ get checkpoint() {
53
+ return checkpoint;
54
+ },
55
+ saveCheckpoint(next) {
56
+ var _a;
57
+ if (disposed)
58
+ return;
59
+ checkpoint = next;
60
+ (_a = deps.saveCheckpoint) === null || _a === void 0 ? void 0 : _a.call(deps, next);
61
+ },
21
62
  state,
22
63
  once(key, factory) {
23
64
  if (onceCache.has(key))
@@ -35,6 +76,8 @@ export function createInteractionContext(interactionId) {
35
76
  return value;
36
77
  },
37
78
  notify() {
79
+ // Every notification is a heartbeat: the controller is still doing something (GENC-1660).
80
+ lastActivityAt = Date.now();
38
81
  // A Set tolerates a listener unsubscribing mid-walk (matches
39
82
  // interaction-panel-session's forEach); a throwing subscriber must not stop the rest.
40
83
  for (const listener of listeners) {
@@ -45,6 +88,8 @@ export function createInteractionContext(interactionId) {
45
88
  logger.error('InteractionContext subscriber threw during notify()', e);
46
89
  }
47
90
  }
91
+ // A guard that turned true is a gate that just opened.
92
+ checkReady();
48
93
  },
49
94
  subscribe(listener) {
50
95
  listeners.add(listener);
@@ -61,6 +106,42 @@ export function createInteractionContext(interactionId) {
61
106
  }
62
107
  disposers.add(fn);
63
108
  },
109
+ resolve(result) {
110
+ var _a;
111
+ if (settled || disposed)
112
+ return;
113
+ settled = true;
114
+ (_a = deps.resolve) === null || _a === void 0 ? void 0 : _a.call(deps, result);
115
+ },
116
+ exposeDecision(decision) {
117
+ if (disposed)
118
+ return;
119
+ surface = buildDecisionSurface(decision);
120
+ context.notify();
121
+ },
122
+ exposeSurface(spec) {
123
+ if (disposed)
124
+ return;
125
+ surface = buildFullSurface(spec);
126
+ context.notify();
127
+ },
128
+ onSurfaceReady(fn) {
129
+ if (disposed)
130
+ return () => undefined;
131
+ if (surface === null || surface === void 0 ? void 0 : surface.isReady()) {
132
+ fn();
133
+ return () => undefined;
134
+ }
135
+ readyListeners.add(fn);
136
+ return () => {
137
+ readyListeners.delete(fn);
138
+ };
139
+ },
140
+ claimantOwns(action) {
141
+ if (heldBy === 'user' || !surface)
142
+ return false;
143
+ return surface.actions().some((a) => a.name === action && a.enabled);
144
+ },
64
145
  };
65
146
  return {
66
147
  context,
@@ -78,6 +159,7 @@ export function createInteractionContext(interactionId) {
78
159
  }
79
160
  disposers.clear();
80
161
  listeners.clear();
162
+ readyListeners.clear();
81
163
  // Release memoised `once` values (sockets, timers, service handles) for GC — the resources
82
164
  // themselves are closed via their `onDispose`, but drop the references too.
83
165
  onceCache.clear();
@@ -89,5 +171,11 @@ export function createInteractionContext(interactionId) {
89
171
  context.notify();
90
172
  return true;
91
173
  },
174
+ setController(value) {
175
+ controller = value;
176
+ },
177
+ get surface() {
178
+ return surface;
179
+ },
92
180
  };
93
181
  }
@@ -0,0 +1,33 @@
1
+ import { logger } from '../utils/logger';
2
+ const registry = new Map();
3
+ /**
4
+ * Register a widget with the platform (GENC-1660): its controller, the element that renders it, how
5
+ * it behaves while a claimant holds it, and its text form. Once per name, at start-up; a second
6
+ * registration under the same name replaces the first, with a warning.
7
+ *
8
+ * Registering is optional and additive: an unregistered widget works exactly as before.
9
+ *
10
+ * @beta
11
+ */
12
+ export function registerInteraction(name, registration) {
13
+ if (registry.has(name)) {
14
+ logger.warn(`registerInteraction: "${name}" was already registered; replacing it.`);
15
+ }
16
+ registry.set(name, registration);
17
+ }
18
+ /**
19
+ * The registration for a component name, or `undefined` when it has none.
20
+ *
21
+ * @beta
22
+ */
23
+ export function getInteractionRegistration(name) {
24
+ return registry.get(name);
25
+ }
26
+ /**
27
+ * Forget every registration. For tests.
28
+ *
29
+ * @internal
30
+ */
31
+ export function clearInteractionRegistry() {
32
+ registry.clear();
33
+ }
@@ -7,6 +7,9 @@ import { recordMetaEvent } from '../debug-event-log';
7
7
  import { diagnosticsMessageKey } from './diagnostics';
8
8
  import { getDiagnosticsCursors } from './diagnostics-cursors';
9
9
  import { buildResumeNote, createPersistedSession, isRestorableSession, keepThroughLastUserMessage, reconcileAgentSnapshots, resolveNonResumableNotice, toLoadSessionPayload, trimToRestorableBoundary, unknownAgentSnapshots, } from './session-snapshot';
10
+ const autopilotOf = (driver) => driver && typeof driver.getPersistedMode === 'function'
11
+ ? driver
12
+ : undefined;
10
13
  /**
11
14
  * Backstop for a slow/hung persistence provider on startup restore (GENC-1351
12
15
  * §6). Deliberately long: silently losing a restorable session is a far worse
@@ -293,7 +296,7 @@ export class SessionPersister {
293
296
  */
294
297
  persistSession() {
295
298
  return __awaiter(this, void 0, void 0, function* () {
296
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
299
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
297
300
  const { provider, enabled } = this.deps.getPersistence();
298
301
  if (!enabled || !provider)
299
302
  return;
@@ -453,6 +456,8 @@ export class SessionPersister {
453
456
  activeProviderName: s.activeProviderName,
454
457
  agentSnapshots,
455
458
  condensePolicies: this.condensePoliciesToPersist(driver, messages),
459
+ autopilot: (_h = autopilotOf(driver)) === null || _h === void 0 ? void 0 : _h.getPersistedMode(),
460
+ checkpoint: (_j = driver === null || driver === void 0 ? void 0 : driver.getInteractionCheckpoint) === null || _j === void 0 ? void 0 : _j.call(driver),
456
461
  });
457
462
  // Observability (GENC-1351): record what each autosave captured for stateful
458
463
  // agents — `agents: []` here means no machine snapshot was taken (the bug to
@@ -468,7 +473,7 @@ export class SessionPersister {
468
473
  const captured = {
469
474
  messages: messages.length,
470
475
  agents: Object.keys(agentSnapshots),
471
- active: (_h = liveActive === null || liveActive === void 0 ? void 0 : liveActive.name) !== null && _h !== void 0 ? _h : null,
476
+ active: (_k = liveActive === null || liveActive === void 0 ? void 0 : liveActive.name) !== null && _k !== void 0 ? _k : null,
472
477
  hasSerialize: !!(liveActive === null || liveActive === void 0 ? void 0 : liveActive.serialize),
473
478
  agentSnapshots,
474
479
  };
@@ -487,7 +492,7 @@ export class SessionPersister {
487
492
  // means the body never reached the wire at all, because a provider builds its
488
493
  // request body inside the `fetch` init, so a throw while serialising creates no
489
494
  // request and leaves no server-side trace.
490
- this.recordSaveOutcome(false, (_j = e === null || e === void 0 ? void 0 : e.message) !== null && _j !== void 0 ? _j : String(e));
495
+ this.recordSaveOutcome(false, (_l = e === null || e === void 0 ? void 0 : e.message) !== null && _l !== void 0 ? _l : String(e));
491
496
  // Report straight away rather than waiting for the next attempt: we are on the resumable
492
497
  // path, so a reload is safe NOW, and a one-cycle delay is a second of extra exposure for
493
498
  // no benefit.
@@ -504,8 +509,8 @@ export class SessionPersister {
504
509
  agents: captured.agents,
505
510
  active: captured.active,
506
511
  hasSerialize: captured.hasSerialize,
507
- error: (_k = e === null || e === void 0 ? void 0 : e.name) !== null && _k !== void 0 ? _k : typeof e,
508
- message: (_l = e === null || e === void 0 ? void 0 : e.message) !== null && _l !== void 0 ? _l : String(e),
512
+ error: (_m = e === null || e === void 0 ? void 0 : e.name) !== null && _m !== void 0 ? _m : typeof e,
513
+ message: (_o = e === null || e === void 0 ? void 0 : e.message) !== null && _o !== void 0 ? _o : String(e),
509
514
  });
510
515
  logger.error('Session save failed:', e);
511
516
  }
@@ -524,7 +529,7 @@ export class SessionPersister {
524
529
  */
525
530
  restore() {
526
531
  return __awaiter(this, void 0, void 0, function* () {
527
- var _a, _b, _c, _d, _e, _f, _g, _h;
532
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
528
533
  const { provider } = this.deps.getPersistence();
529
534
  if (!provider)
530
535
  return undefined;
@@ -582,6 +587,13 @@ export class SessionPersister {
582
587
  // matter when the agent re-activates. The flow-owner lock self-heals on
583
588
  // activation (OrchestratingDriver.installAgent re-sets it).
584
589
  driver === null || driver === void 0 ? void 0 : driver.primeRestoredAgentState(snapshot.agentSnapshots);
590
+ // The autopilot's mode waits for its flow to re-activate; restoring it starts nothing.
591
+ (_d = autopilotOf(driver)) === null || _d === void 0 ? void 0 : _d.restorePersistedMode(snapshot.autopilot);
592
+ // A long action the reload cut waits for the next request of its widget type (§4.12) — but
593
+ // only when its flow is re-activated above. A flow left behind (interrupted, or an owner this
594
+ // driver does not know) never ends, so nothing would clear its checkpoint, and the next request
595
+ // of that widget type would take over the abandoned run's work.
596
+ (_e = driver === null || driver === void 0 ? void 0 : driver.restoreInteractionCheckpoint) === null || _e === void 0 ? void 0 : _e.call(driver, flowOwner ? snapshot.checkpoint : undefined);
585
597
  // Seed the carry-forward cache so the immediate post-restore autosave (fired
586
598
  // by the history change below, before any agent re-activates) preserves the
587
599
  // machine snapshot instead of overwriting it with an empty one (GENC-1351).
@@ -653,14 +665,14 @@ export class SessionPersister {
653
665
  }
654
666
  const messages = [...trimmed, ...trailers];
655
667
  ref.actions.aiAssistant.loadSession(toLoadSessionPayload(Object.assign(Object.assign({}, snapshot), { messages })));
656
- (_d = driver === null || driver === void 0 ? void 0 : driver.loadHistory) === null || _d === void 0 ? void 0 : _d.call(driver, [...messages]);
668
+ (_f = driver === null || driver === void 0 ? void 0 : driver.loadHistory) === null || _f === void 0 ? void 0 : _f.call(driver, [...messages]);
657
669
  // Re-arm the condense policies that had not fired at save time (GENC-1632) — AFTER
658
670
  // `loadHistory`, because the driver keeps only entries whose tool call is in the history it
659
671
  // now holds, which is how an entry for a call `trimToRestorableBoundary` cut away is dropped
660
672
  // rather than resurrected. A snapshot from before the field existed has none, so this is a
661
673
  // no-op and the restore is exactly what it was. Timing against the agent's re-activation is
662
674
  // the driver's problem, not ours: it defers the activation stamp to the next `installAgent`.
663
- const condenseRestore = (_e = driver === null || driver === void 0 ? void 0 : driver.restoreCondensePolicies) === null || _e === void 0 ? void 0 : _e.call(driver, snapshot.condensePolicies);
675
+ const condenseRestore = (_g = driver === null || driver === void 0 ? void 0 : driver.restoreCondensePolicies) === null || _g === void 0 ? void 0 : _g.call(driver, snapshot.condensePolicies);
664
676
  // Observability (GENC-1351, counts refined for GENC-1632 review): record exactly what the
665
677
  // restore primed, so a post-reload debug-log export shows whether a stateful agent's snapshot
666
678
  // actually came back (meta events reset on reload, so this is the only window onto the
@@ -672,7 +684,7 @@ export class SessionPersister {
672
684
  // are left OFF the object (not zeroed) when there is no driver or it predates this method, so
673
685
  // that case can't be misread as "re-armed none" either.
674
686
  this.logMeta('persistence.restored', {
675
- agents: Object.keys((_f = snapshot.agentSnapshots) !== null && _f !== void 0 ? _f : {}),
687
+ agents: Object.keys((_h = snapshot.agentSnapshots) !== null && _h !== void 0 ? _h : {}),
676
688
  condensePolicies: Object.assign({ stored: Array.isArray(snapshot.condensePolicies) ? snapshot.condensePolicies.length : 0 }, condenseRestore),
677
689
  agentSnapshots: snapshot.agentSnapshots,
678
690
  pinnedAgentName: snapshot.pinnedAgentName,
@@ -701,8 +713,8 @@ export class SessionPersister {
701
713
  //
702
714
  // On the driver itself, not a narrowed orchestrator (GENC-1660): both members are on
703
715
  // `AiDriver` and every decorator forwards them, so this reaches the routing whatever wraps it.
704
- driver === null || driver === void 0 ? void 0 : driver.setPinnedAgent((_g = snapshot.pinnedAgentName) !== null && _g !== void 0 ? _g : null);
705
- driver === null || driver === void 0 ? void 0 : driver.setFlowOwner((_h = snapshot.flowOwnerAgentName) !== null && _h !== void 0 ? _h : null);
716
+ driver === null || driver === void 0 ? void 0 : driver.setPinnedAgent((_j = snapshot.pinnedAgentName) !== null && _j !== void 0 ? _j : null);
717
+ driver === null || driver === void 0 ? void 0 : driver.setFlowOwner((_k = snapshot.flowOwnerAgentName) !== null && _k !== void 0 ? _k : null);
706
718
  if (flowOwner) {
707
719
  this.logMeta('persistence.resume-planned', {
708
720
  flowOwner,
@@ -14,7 +14,7 @@ export const PERSISTED_SESSION_VERSION = 1;
14
14
  */
15
15
  export function createPersistedSession(input) {
16
16
  var _a, _b;
17
- return Object.assign({ version: PERSISTED_SESSION_VERSION, savedAt: new Date().toISOString(), host: input.host, sessionKey: input.sessionKey, messages: input.messages, activeAgentName: input.activeAgentName, pinnedAgentName: input.pinnedAgentName, flowOwnerAgentName: input.flowOwnerAgentName, sessionCostUsd: input.sessionCostUsd, contextTokens: input.contextTokens, contextLimit: input.contextLimit, activeModel: input.activeModel, activeProviderName: input.activeProviderName, agentSnapshots: (_a = input.agentSnapshots) !== null && _a !== void 0 ? _a : {} }, (((_b = input.condensePolicies) === null || _b === void 0 ? void 0 : _b.length) ? { condensePolicies: input.condensePolicies } : {}));
17
+ return Object.assign(Object.assign(Object.assign({ version: PERSISTED_SESSION_VERSION, savedAt: new Date().toISOString(), host: input.host, sessionKey: input.sessionKey, messages: input.messages, activeAgentName: input.activeAgentName, pinnedAgentName: input.pinnedAgentName, flowOwnerAgentName: input.flowOwnerAgentName, sessionCostUsd: input.sessionCostUsd, contextTokens: input.contextTokens, contextLimit: input.contextLimit, activeModel: input.activeModel, activeProviderName: input.activeProviderName, agentSnapshots: (_a = input.agentSnapshots) !== null && _a !== void 0 ? _a : {} }, (((_b = input.condensePolicies) === null || _b === void 0 ? void 0 : _b.length) ? { condensePolicies: input.condensePolicies } : {})), (input.autopilot ? { autopilot: input.autopilot } : {})), (input.checkpoint ? { checkpoint: input.checkpoint } : {}));
18
18
  }
19
19
  /**
20
20
  * Resolve the notice text + pre-fill prompt for an interrupted (non-resumable) flow
@@ -0,0 +1,77 @@
1
+ import { __awaiter } from "tslib";
2
+ const recorders = new WeakMap();
3
+ /**
4
+ * Bind the debug-log recorders for a context's steps: each step's outcome, and a handed-over
5
+ * checkpoint refused as malformed. Called by the platform when it creates the context, so
6
+ * `runSteps` records under the right session without the context carrying it.
7
+ *
8
+ * @internal
9
+ */
10
+ export function bindStepRecorder(ctx, record, refused) {
11
+ recorders.set(ctx, { step: record, refused });
12
+ }
13
+ /**
14
+ * The checkpoint to start from: the one handed over when it is shaped like one, otherwise a fresh
15
+ * one from `initial`. A handed-over checkpoint is persisted data — saved by an older version of the
16
+ * widget, say, or read from a damaged store — so it is checked rather than trusted: a malformed one
17
+ * would otherwise throw on the first step and leave the interaction parked with nothing to settle it.
18
+ */
19
+ function startingPoint(handed, initial, refused) {
20
+ const fresh = () => (Object.assign(Object.assign({}, initial), { done: [] }));
21
+ if (handed === undefined)
22
+ return fresh();
23
+ const started = handed === null || handed === void 0 ? void 0 : handed.started;
24
+ if (!Array.isArray(handed === null || handed === void 0 ? void 0 : handed.done) || (started !== undefined && !Array.isArray(started))) {
25
+ refused === null || refused === void 0 ? void 0 : refused('malformed: no list of done steps', handed);
26
+ return fresh();
27
+ }
28
+ return handed;
29
+ }
30
+ /**
31
+ * Run a long action as named steps, checkpointing after each one (GENC-1660 §4.12), so a reload
32
+ * skips what is already done instead of starting again.
33
+ *
34
+ * Starts from the checkpoint the platform handed over, if any — it has already decided, by widget
35
+ * type and the registration's `sameWork`, that the checkpoint is this work's — or from `initial`, the
36
+ * identity `sameWork` compares, kept in the checkpoint because the request that cut it is gone.
37
+ * Each step that is already done is skipped; the rest run in order. `ctx.state.stage` names the step
38
+ * running, and every step notifies, which is also the watchdog's heartbeat.
39
+ *
40
+ * Exported beside the context rather than added to it, so the context's own API stays small.
41
+ * Throws if `signal` aborts, before or between steps.
42
+ *
43
+ * @beta
44
+ */
45
+ export function runSteps(ctx, initial, steps, signal) {
46
+ return __awaiter(this, void 0, void 0, function* () {
47
+ var _a, _b, _c;
48
+ const { step: record, refused } = (_a = recorders.get(ctx)) !== null && _a !== void 0 ? _a : {};
49
+ let cp = startingPoint(ctx.checkpoint, initial, refused);
50
+ const save = (next) => {
51
+ cp = next;
52
+ ctx.saveCheckpoint(cp);
53
+ };
54
+ for (const step of steps) {
55
+ signal === null || signal === void 0 ? void 0 : signal.throwIfAborted();
56
+ if (cp.done.includes(step.name)) {
57
+ record === null || record === void 0 ? void 0 : record(step.name, 'skipped');
58
+ continue;
59
+ }
60
+ // Started before the reload but never finished: it runs again, with whatever it saved part-way.
61
+ const resumed = !!((_b = cp.started) === null || _b === void 0 ? void 0 : _b.includes(step.name));
62
+ record === null || record === void 0 ? void 0 : record(step.name, resumed ? 'resumed' : 'started');
63
+ if (!resumed)
64
+ save(Object.assign(Object.assign({}, cp), { started: [...((_c = cp.started) !== null && _c !== void 0 ? _c : []), step.name] }));
65
+ ctx.state.stage = step.name;
66
+ ctx.notify();
67
+ // oxlint-disable-next-line no-await-in-loop -- steps run in order: each may need the last's output
68
+ const out = yield step.run(cp, {
69
+ signal,
70
+ save: (partial) => save(Object.assign(Object.assign({}, cp), partial)),
71
+ });
72
+ save(Object.assign(Object.assign(Object.assign({}, cp), out), { done: [...cp.done, step.name] }));
73
+ record === null || record === void 0 ? void 0 : record(step.name, 'done');
74
+ }
75
+ return cp;
76
+ });
77
+ }
@@ -0,0 +1 @@
1
+ export {};