@genesislcap/ai-assistant 15.61.0 → 15.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-assistant.api.json +18231 -11171
- package/dist/ai-assistant.d.ts +1235 -9
- package/dist/chat-driver.cjs +658 -50
- package/dist/chat-driver.cjs.map +4 -4
- package/dist/chat-driver.mjs +657 -50
- package/dist/chat-driver.mjs.map +4 -4
- package/dist/custom-elements.json +6169 -2127
- package/dist/dts/autopilot/autopilot-driver.d.ts +278 -0
- package/dist/dts/autopilot/autopilot-driver.d.ts.map +1 -0
- package/dist/dts/autopilot/autopilot-session.d.ts +163 -0
- package/dist/dts/autopilot/autopilot-session.d.ts.map +1 -0
- package/dist/dts/autopilot/autopilot-tools.d.ts +15 -0
- package/dist/dts/autopilot/autopilot-tools.d.ts.map +1 -0
- package/dist/dts/autopilot/decision-message.d.ts +62 -0
- package/dist/dts/autopilot/decision-message.d.ts.map +1 -0
- package/dist/dts/autopilot/hand-back/hand-back.controller.d.ts +69 -0
- package/dist/dts/autopilot/hand-back/hand-back.controller.d.ts.map +1 -0
- package/dist/dts/autopilot/hand-back/hand-back.d.ts +22 -0
- package/dist/dts/autopilot/hand-back/hand-back.d.ts.map +1 -0
- package/dist/dts/autopilot/index.d.ts +4 -0
- package/dist/dts/autopilot/index.d.ts.map +1 -0
- package/dist/dts/channel/ai-activity-bus.d.ts.map +1 -1
- package/dist/dts/channel/ai-activity-channel.d.ts +17 -0
- package/dist/dts/channel/ai-activity-channel.d.ts.map +1 -1
- package/dist/dts/channel/autopilot-needs-user.d.ts +8 -0
- package/dist/dts/channel/autopilot-needs-user.d.ts.map +1 -0
- package/dist/dts/components/agent-picker/agent-picker.constants.d.ts +10 -0
- package/dist/dts/components/agent-picker/agent-picker.constants.d.ts.map +1 -1
- package/dist/dts/components/agent-picker/agent-picker.d.ts +4 -0
- package/dist/dts/components/agent-picker/agent-picker.d.ts.map +1 -1
- package/dist/dts/components/agent-picker/agent-picker.template.d.ts.map +1 -1
- package/dist/dts/components/ai-driver/ai-driver.d.ts +34 -1
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/ai-driver/base-driver-decorator.d.ts +3 -1
- package/dist/dts/components/ai-driver/base-driver-decorator.d.ts.map +1 -1
- package/dist/dts/components/chat-bubble/chat-bubble.d.ts +21 -0
- package/dist/dts/components/chat-bubble/chat-bubble.d.ts.map +1 -1
- package/dist/dts/components/chat-bubble/chat-bubble.styles.d.ts.map +1 -1
- package/dist/dts/components/chat-bubble/chat-bubble.template.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +5 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/interaction-controller.d.ts +27 -3
- package/dist/dts/components/chat-driver/interaction-controller.d.ts.map +1 -1
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts +10 -2
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +9 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +183 -0
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/config/define-stateful-agent.d.ts +31 -1
- package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
- package/dist/dts/index.d.ts +7 -3
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +128 -5
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.styles.d.ts.map +1 -1
- package/dist/dts/main/main.template.d.ts +2 -2
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/react.d.ts +10 -0
- package/dist/dts/state/debug-event-log.d.ts +1 -1
- package/dist/dts/state/debug-event-log.d.ts.map +1 -1
- package/dist/dts/state/decision-surface.d.ts +46 -0
- package/dist/dts/state/decision-surface.d.ts.map +1 -0
- package/dist/dts/state/interaction-context.d.ts +21 -1
- package/dist/dts/state/interaction-context.d.ts.map +1 -1
- package/dist/dts/state/interaction-registry.d.ts +63 -0
- package/dist/dts/state/interaction-registry.d.ts.map +1 -0
- package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-snapshot.d.ts +21 -0
- package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
- package/dist/dts/state/run-steps.d.ts +66 -0
- package/dist/dts/state/run-steps.d.ts.map +1 -0
- package/dist/dts/types/ai-chat-widget.d.ts +7 -1
- package/dist/dts/types/ai-chat-widget.d.ts.map +1 -1
- package/dist/dts/types/interaction-context.d.ts +66 -1
- package/dist/dts/types/interaction-context.d.ts.map +1 -1
- package/dist/dts/types/interaction-interceptor.d.ts +60 -2
- package/dist/dts/types/interaction-interceptor.d.ts.map +1 -1
- package/dist/dts/types/interaction-surface.d.ts +195 -0
- package/dist/dts/types/interaction-surface.d.ts.map +1 -0
- package/dist/dts/utils/context-tokens.d.ts +6 -0
- package/dist/dts/utils/context-tokens.d.ts.map +1 -1
- package/dist/dts/utils/delegated-message.d.ts +13 -0
- package/dist/dts/utils/delegated-message.d.ts.map +1 -1
- package/dist/dts/utils/history-transform.d.ts.map +1 -1
- package/dist/dts/utils/preserved-user-messages.d.ts +21 -0
- package/dist/dts/utils/preserved-user-messages.d.ts.map +1 -0
- package/dist/esm/autopilot/autopilot-driver.js +906 -0
- package/dist/esm/autopilot/autopilot-session.js +449 -0
- package/dist/esm/autopilot/autopilot-tools.js +68 -0
- package/dist/esm/autopilot/decision-message.js +206 -0
- package/dist/esm/autopilot/hand-back/hand-back.controller.js +47 -0
- package/dist/esm/autopilot/hand-back/hand-back.js +129 -0
- package/dist/esm/autopilot/index.js +4 -0
- package/dist/esm/channel/ai-activity-bus.js +7 -0
- package/dist/esm/channel/autopilot-needs-user.js +26 -0
- package/dist/esm/components/agent-picker/agent-picker.constants.js +10 -0
- package/dist/esm/components/agent-picker/agent-picker.js +11 -1
- package/dist/esm/components/agent-picker/agent-picker.template.js +2 -4
- package/dist/esm/components/ai-driver/ai-driver.js +2 -0
- package/dist/esm/components/ai-driver/base-driver-decorator.js +8 -0
- package/dist/esm/components/chat-bubble/chat-bubble.js +45 -0
- package/dist/esm/components/chat-bubble/chat-bubble.styles.js +26 -0
- package/dist/esm/components/chat-bubble/chat-bubble.template.js +13 -0
- package/dist/esm/components/chat-driver/chat-driver.js +53 -18
- package/dist/esm/components/chat-driver/interaction-controller.js +297 -12
- package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +16 -2
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +28 -11
- package/dist/esm/config/define-stateful-agent.js +34 -29
- package/dist/esm/index.js +3 -0
- package/dist/esm/main/main.js +334 -15
- package/dist/esm/main/main.styles.js +126 -0
- package/dist/esm/main/main.template.js +116 -11
- package/dist/esm/state/debug-event-log.js +28 -1
- package/dist/esm/state/decision-surface.js +209 -0
- package/dist/esm/state/interaction-context.js +89 -1
- package/dist/esm/state/interaction-registry.js +33 -0
- package/dist/esm/state/persistence/session-persister.js +23 -11
- package/dist/esm/state/persistence/session-snapshot.js +1 -1
- package/dist/esm/state/run-steps.js +77 -0
- package/dist/esm/types/interaction-surface.js +1 -0
- package/dist/esm/utils/context-tokens.js +23 -9
- package/dist/esm/utils/delegated-message.js +15 -0
- package/dist/esm/utils/history-transform.js +18 -7
- package/dist/esm/utils/preserved-user-messages.js +31 -0
- package/dist/react.cjs +7 -0
- package/dist/react.mjs +6 -0
- package/docs/styling.md +1 -1
- package/package.json +17 -17
- package/sandbox/README.md +38 -2
- package/sandbox/autopilot.ts +358 -0
- package/sandbox/controls.ts +24 -1
- package/sandbox/fixtures.ts +2 -0
- 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: (
|
|
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, (
|
|
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: (
|
|
508
|
-
message: (
|
|
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
|
-
(
|
|
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 = (
|
|
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((
|
|
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((
|
|
705
|
-
driver === null || driver === void 0 ? void 0 : driver.setFlowOwner((
|
|
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 {};
|