@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.
- 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 +4419 -377
- 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
|
@@ -14,6 +14,11 @@ const genesisIconTemplate = html `
|
|
|
14
14
|
`;
|
|
15
15
|
export const AiChatBubbleTemplate = () => {
|
|
16
16
|
return html `
|
|
17
|
+
<!-- Mounted once, before it has anything to say, and only its text changes: a status region
|
|
18
|
+
inserted already holding its text is widely missed by screen readers. It announces the badge. -->
|
|
19
|
+
<span class="visually-hidden" role="status">
|
|
20
|
+
${(x) => (x.needsUser && !x.dialogOpen ? x.needsInputLabel : '')}
|
|
21
|
+
</span>
|
|
17
22
|
${when((x) => !x.dialogOpen, html `
|
|
18
23
|
<div
|
|
19
24
|
class="bubble ${(x) => (x.hovered ? 'hovered' : '')}"
|
|
@@ -29,6 +34,14 @@ export const AiChatBubbleTemplate = () => {
|
|
|
29
34
|
`)}
|
|
30
35
|
${when((x) => !x.imageSrc, genesisIconTemplate)}
|
|
31
36
|
<div class="pulse-ring" part="pulse-ring"></div>
|
|
37
|
+
${when((x) => x.needsUser, html `
|
|
38
|
+
<span
|
|
39
|
+
class="needs-you-badge"
|
|
40
|
+
part="needs-you-badge"
|
|
41
|
+
aria-hidden="true"
|
|
42
|
+
title="${(x) => x.needsInputLabel}"
|
|
43
|
+
></span>
|
|
44
|
+
`)}
|
|
32
45
|
<div class="tooltip" part="tooltip" style="${(x) => x.tooltipStyle}" ${ref('tooltipEl')}>
|
|
33
46
|
${(x) => x.title}
|
|
34
47
|
</div>
|
|
@@ -6,9 +6,10 @@ import { clearSession, getMetaEvents, mergeMetaEvents, recordMetaEvent, recordTu
|
|
|
6
6
|
import { generateSuggestions } from '../../suggestions/generate-suggestions';
|
|
7
7
|
import { applyCondensation, condenseForPersistence, isValidCondenseTrigger, parsePersistedCondensePolicy, rebaseRestoredCondensePolicy, snapshotCondensePolicies, toolCallIdsIn, } from '../../utils/condense-history';
|
|
8
8
|
import { estimatePreambleTokens, estimateRequestTokens, } from '../../utils/context-tokens';
|
|
9
|
-
import { DEFAULT_DELEGATE_FRAMING, frameDelegatedContent, } from '../../utils/delegated-message';
|
|
9
|
+
import { DEFAULT_DELEGATE_FRAMING, delegatedFields, frameDelegatedContent, } from '../../utils/delegated-message';
|
|
10
10
|
import { applyHistoryCap, buildCompactionSummaryPrompt, planCompaction, normalizeForProvider, } from '../../utils/history-transform';
|
|
11
11
|
import { logger } from '../../utils/logger';
|
|
12
|
+
import { freshUserMessages } from '../../utils/preserved-user-messages';
|
|
12
13
|
import { resolveTurnInput } from '../../utils/resolve-turn-input';
|
|
13
14
|
import { messageUsage, sumUsage } from '../../utils/sum-usage';
|
|
14
15
|
import { AgentSession } from '../agent-session/agent-session';
|
|
@@ -103,26 +104,42 @@ const budgetDetailOf = (e) => {
|
|
|
103
104
|
return Object.assign(Object.assign({ budgetUsd: e.budgetUsd, spentUsd: e.spentUsd, vendorLabel: e.vendorLabel }, (vendor ? { vendor } : {})), (e.otherVendorAvailable != null ? { otherVendorAvailable: e.otherVendorAvailable } : {}));
|
|
104
105
|
};
|
|
105
106
|
/**
|
|
106
|
-
* Add
|
|
107
|
-
*
|
|
108
|
-
* anything else a sentence per
|
|
109
|
-
* the
|
|
107
|
+
* Add what an interceptor did on the user's behalf to a tool result (GENC-1660), so the working
|
|
108
|
+
* agent is told the truth about who chose: an object result gains `decidedOnUsersBehalf` and
|
|
109
|
+
* `editedOnUsersBehalf`, anything else a sentence per record. Each decision is `{ framing, choice?,
|
|
110
|
+
* shownToUser?, edits? }`; `edits` lists what the claimant changed in the widget before deciding,
|
|
111
|
+
* and is omitted when it changed nothing. Each edited record is `{ framing, edits }`: the person
|
|
112
|
+
* answered, but only after the claimant had changed the widget. Unchanged when there are none,
|
|
113
|
+
* which is every call the person answered on a widget nobody else touched.
|
|
110
114
|
*/
|
|
111
|
-
function withDelegatedDecisions(result, content,
|
|
112
|
-
|
|
115
|
+
function withDelegatedDecisions(result, content, done, hasImages) {
|
|
116
|
+
const { decided, edited } = done;
|
|
117
|
+
if (decided.length === 0 && edited.length === 0)
|
|
113
118
|
return content;
|
|
114
119
|
if (!hasImages && result !== null && typeof result === 'object' && !Array.isArray(result)) {
|
|
115
|
-
return JSON.stringify(Object.assign(Object.assign({}, result), { decidedOnUsersBehalf:
|
|
120
|
+
return JSON.stringify(Object.assign(Object.assign(Object.assign({}, result), (decided.length ? { decidedOnUsersBehalf: decided } : {})), (edited.length ? { editedOnUsersBehalf: edited } : {})));
|
|
116
121
|
}
|
|
117
|
-
return [
|
|
122
|
+
return [
|
|
123
|
+
content,
|
|
124
|
+
...decided.map(describeDelegatedDecision),
|
|
125
|
+
...edited.map(describeDelegatedEdits),
|
|
126
|
+
].join('\n\n');
|
|
127
|
+
}
|
|
128
|
+
/** Edits the person then confirmed, as a sentence for a text tool result. */
|
|
129
|
+
function describeDelegatedEdits(edited) {
|
|
130
|
+
return `${edited.framing}: ${edited.edits.map((edit) => `"${edit}"`).join(', ')}.`;
|
|
118
131
|
}
|
|
119
132
|
/** One decision as a sentence for a text tool result, e.g. `Decided on the user's behalf: "Skip".` */
|
|
120
133
|
function describeDelegatedDecision(decision) {
|
|
134
|
+
var _a;
|
|
121
135
|
const choice = decision.choice !== undefined ? `: "${decision.choice}"` : '';
|
|
122
136
|
const shown = decision.shownToUser
|
|
123
137
|
? ` What the user was shown, as your words: "${decision.shownToUser}"`
|
|
124
138
|
: ' Nothing was shown to the user about it.';
|
|
125
|
-
|
|
139
|
+
const edited = ((_a = decision.edits) === null || _a === void 0 ? void 0 : _a.length)
|
|
140
|
+
? ` Before deciding it changed the widget: ${decision.edits.map((edit) => `"${edit}"`).join(', ')}.`
|
|
141
|
+
: '';
|
|
142
|
+
return `${decision.framing}${choice}.${shown}${edited}`;
|
|
126
143
|
}
|
|
127
144
|
/**
|
|
128
145
|
* Where the tool calls still waiting for their results start: the last assistant message with a
|
|
@@ -975,6 +992,9 @@ export class ChatDriver extends EventTarget {
|
|
|
975
992
|
* release, then the active agent.
|
|
976
993
|
*/
|
|
977
994
|
dispatchAgentTransition(t) {
|
|
995
|
+
// The flow ended, so a checkpoint nothing took over has nothing left to resume (§4.12).
|
|
996
|
+
if (t.flowOwnerChangedTo === null)
|
|
997
|
+
this.interactions.clearCheckpoint('flow-ended');
|
|
978
998
|
if (t.flowOwnerChangedTo !== undefined) {
|
|
979
999
|
this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: t.flowOwnerChangedTo }));
|
|
980
1000
|
}
|
|
@@ -1635,6 +1655,10 @@ export class ChatDriver extends EventTarget {
|
|
|
1635
1655
|
// re-derived from the transcript would otherwise fall by exactly this much
|
|
1636
1656
|
// on every compaction. Summarising a turn does not refund what it cost.
|
|
1637
1657
|
rolledUpUsage: sumUsage(toCompact),
|
|
1658
|
+
// The user's own words survive word for word, beside the paraphrase (GENC-1660, C2): this
|
|
1659
|
+
// range's own only. An earlier summary's kept messages are not carried forward; the
|
|
1660
|
+
// summariser read them with it and folded them into this paraphrase.
|
|
1661
|
+
preservedUserMessages: freshUserMessages(toCompact),
|
|
1638
1662
|
},
|
|
1639
1663
|
};
|
|
1640
1664
|
this.loadHistory([summaryMessage, ...tail]);
|
|
@@ -1806,6 +1830,14 @@ export class ChatDriver extends EventTarget {
|
|
|
1806
1830
|
getInteractionContext(interactionId) {
|
|
1807
1831
|
return this.interactions.context(interactionId);
|
|
1808
1832
|
}
|
|
1833
|
+
/** {@inheritDoc AiDriver.getInteractionCheckpoint} */
|
|
1834
|
+
getInteractionCheckpoint() {
|
|
1835
|
+
return this.interactions.getCheckpoint();
|
|
1836
|
+
}
|
|
1837
|
+
/** {@inheritDoc AiDriver.restoreInteractionCheckpoint} */
|
|
1838
|
+
restoreInteractionCheckpoint(saved) {
|
|
1839
|
+
this.interactions.restoreCheckpoint(saved);
|
|
1840
|
+
}
|
|
1809
1841
|
/**
|
|
1810
1842
|
* Replace the history message owning `interactionId`. `false` when it is no longer in history.
|
|
1811
1843
|
* Does not announce the change — the interaction paths differ on whether they should.
|
|
@@ -1856,7 +1888,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1856
1888
|
this.aiServiceWall = undefined;
|
|
1857
1889
|
this.aiServiceNoticesShown.clear();
|
|
1858
1890
|
this.aiServiceNoticesQueued = [];
|
|
1859
|
-
this.appendToHistory(Object.assign({ role: 'user', content: userInput, attachments }, (
|
|
1891
|
+
this.appendToHistory(Object.assign({ role: 'user', content: userInput, attachments }, delegatedFields(options)));
|
|
1860
1892
|
this.turnStartedAt = Date.now();
|
|
1861
1893
|
recordMetaEvent(this.sessionKey, 'turn.start', {
|
|
1862
1894
|
phase: 'sendMessage',
|
|
@@ -1923,7 +1955,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1923
1955
|
* kept on a seven-way fan-out, reporting ~2.3x under the real cost, with the
|
|
1924
1956
|
* error growing as the fan-out widens.
|
|
1925
1957
|
*/
|
|
1926
|
-
buildHandlerContext(activeToolCallId, traceCapture,
|
|
1958
|
+
buildHandlerContext(activeToolCallId, traceCapture, done) {
|
|
1927
1959
|
var _a;
|
|
1928
1960
|
// The interaction THIS call opened, if any: the only one its `recordInteractionOutcome` can
|
|
1929
1961
|
// reach, so a tool can never write an outcome onto a widget another tool opened.
|
|
@@ -1933,10 +1965,13 @@ export class ChatDriver extends EventTarget {
|
|
|
1933
1965
|
return Object.assign(Object.assign(Object.assign(Object.assign({}, (capabilities && { capabilities })), { requestInteraction: (componentName, data, options) => this.requestInteraction(componentName, data, options, (interactionId) => {
|
|
1934
1966
|
opened = interactionId;
|
|
1935
1967
|
}, undefined,
|
|
1936
|
-
//
|
|
1968
|
+
// What an interceptor did to an interaction is reported in THIS call's result
|
|
1969
|
+
// (GENC-1660): its answer, or the edits it made before the person answered.
|
|
1937
1970
|
(settled) => {
|
|
1938
1971
|
if (settled.decision)
|
|
1939
|
-
|
|
1972
|
+
done === null || done === void 0 ? void 0 : done.decided.push(settled.decision);
|
|
1973
|
+
if (settled.edited)
|
|
1974
|
+
done === null || done === void 0 ? void 0 : done.edited.push(settled.edited);
|
|
1940
1975
|
}), recordInteractionOutcome: (outcome) => {
|
|
1941
1976
|
var _a;
|
|
1942
1977
|
if (!opened)
|
|
@@ -3123,10 +3158,10 @@ export class ChatDriver extends EventTarget {
|
|
|
3123
3158
|
// `sumUsage` and `usageRows` would still agree with each other.
|
|
3124
3159
|
const traceCapture = { traces: [] };
|
|
3125
3160
|
const capturedTrace = () => traceCapture.traces.length ? traceCapture.traces.flat() : undefined;
|
|
3126
|
-
//
|
|
3127
|
-
const
|
|
3161
|
+
// What an interceptor did to the interactions this call awaited (GENC-1660).
|
|
3162
|
+
const done = { decided: [], edited: [] };
|
|
3128
3163
|
try {
|
|
3129
|
-
const result = yield handler(tc.args, this.buildHandlerContext(tc.id, traceCapture,
|
|
3164
|
+
const result = yield handler(tc.args, this.buildHandlerContext(tc.id, traceCapture, done));
|
|
3130
3165
|
// A handler that returns images gets them carried as real attachments instead of
|
|
3131
3166
|
// JSON-stringified into the text — the difference between a model reading
|
|
3132
3167
|
// `{"kind":"image","data":"iVBOR…"}` as prose and actually SEEING the render.
|
|
@@ -3140,7 +3175,7 @@ export class ChatDriver extends EventTarget {
|
|
|
3140
3175
|
: JSON.stringify(Object.assign(Object.assign({}, result), { attachments: undefined }))
|
|
3141
3176
|
: typeof result === 'string'
|
|
3142
3177
|
? result
|
|
3143
|
-
: JSON.stringify(result),
|
|
3178
|
+
: JSON.stringify(result), done, !!resultImages);
|
|
3144
3179
|
executedById.set(tc.id, Object.assign(Object.assign({ toolCallId: tc.id, content }, (resultImages ? { attachments: resultImages } : {})), {
|
|
3145
3180
|
// Concatenated when a handler invoked several children, so none is lost.
|
|
3146
3181
|
// Stays `undefined` when nothing was captured — readers key off presence,
|
|
@@ -1,8 +1,69 @@
|
|
|
1
|
+
import { __awaiter } from "tslib";
|
|
1
2
|
import { recordMetaEvent } from '../../state/debug-event-log';
|
|
2
3
|
import { createInteractionContext, } from '../../state/interaction-context';
|
|
4
|
+
import { getInteractionRegistration } from '../../state/interaction-registry';
|
|
5
|
+
import { bindStepRecorder } from '../../state/run-steps';
|
|
3
6
|
import { logger } from '../../utils/logger';
|
|
4
7
|
/** The framing of a {@link DelegatedDecision} whose claimant declares none. */
|
|
5
8
|
const DEFAULT_DECISION_FRAMING = "Decided on the user's behalf";
|
|
9
|
+
/** The framing of {@link DelegatedEdits} whose claimant declares none. */
|
|
10
|
+
const DEFAULT_EDIT_FRAMING = "Changed on the user's behalf before the user answered; the user then finished it, confirming the changes";
|
|
11
|
+
/** Why a `hold.invoke` was refused while another of the claimant's actions was still running. */
|
|
12
|
+
const BUSY = {
|
|
13
|
+
error: 'busy',
|
|
14
|
+
message: 'Another action on this interaction is still running, so this one was not run. Once that one has returned, try this again if it still applies.',
|
|
15
|
+
};
|
|
16
|
+
/** Whether `value` is a promise, or anything else awaiting it would wait on. */
|
|
17
|
+
const isThenable = (value) => value !== null &&
|
|
18
|
+
(typeof value === 'object' || typeof value === 'function') &&
|
|
19
|
+
typeof value.then === 'function';
|
|
20
|
+
/** One editing action a claimant ran, as it is recorded on the interaction (GENC-1660). */
|
|
21
|
+
function editOf(by, action, choice, args) {
|
|
22
|
+
const copy = jsonCopy(args);
|
|
23
|
+
return Object.assign({ by, action, choice }, (copy !== undefined && { args: copy }));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A JSON copy of an action's arguments, so the record cannot change under a widget that mutates
|
|
27
|
+
* what it was given. `undefined` when there are none (nothing, an empty object, list or string),
|
|
28
|
+
* or when they cannot be copied.
|
|
29
|
+
*/
|
|
30
|
+
function jsonCopy(args) {
|
|
31
|
+
if (args === undefined || args === null)
|
|
32
|
+
return undefined;
|
|
33
|
+
try {
|
|
34
|
+
const copy = JSON.parse(JSON.stringify(args));
|
|
35
|
+
const empty = copy === '' ||
|
|
36
|
+
(Array.isArray(copy) && copy.length === 0) ||
|
|
37
|
+
(copy !== null && typeof copy === 'object' && Object.keys(copy).length === 0);
|
|
38
|
+
return empty ? undefined : copy;
|
|
39
|
+
}
|
|
40
|
+
catch (_a) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* What the requester is told a claimant did (GENC-1660): its decision, when it answered; or, when
|
|
46
|
+
* the person answered one it had changed first, those edits — the answer is theirs, but the agent
|
|
47
|
+
* still learns the widget was changed on their behalf before they answered.
|
|
48
|
+
*/
|
|
49
|
+
function doneOnUsersBehalf(interaction, answeredBy) {
|
|
50
|
+
var _a, _b, _c, _d, _e;
|
|
51
|
+
const edits = ((_a = interaction.edits) === null || _a === void 0 ? void 0 : _a.length)
|
|
52
|
+
? interaction.edits.map((edit) => edit.choice)
|
|
53
|
+
: undefined;
|
|
54
|
+
if (answeredBy && answeredBy !== 'user') {
|
|
55
|
+
const recorded = interaction.decision;
|
|
56
|
+
return {
|
|
57
|
+
decision: Object.assign(Object.assign(Object.assign({ framing: (_c = (_b = interaction.claimant) === null || _b === void 0 ? void 0 : _b.decisionFraming) !== null && _c !== void 0 ? _c : DEFAULT_DECISION_FRAMING }, (edits && { edits })), ((recorded === null || recorded === void 0 ? void 0 : recorded.choice) !== undefined && { choice: recorded.choice })), ((recorded === null || recorded === void 0 ? void 0 : recorded.shownToUser) !== undefined && { shownToUser: recorded.shownToUser })),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (answeredBy === 'user' && edits) {
|
|
61
|
+
return {
|
|
62
|
+
edited: { framing: (_e = (_d = interaction.claimant) === null || _d === void 0 ? void 0 : _d.editFraming) !== null && _e !== void 0 ? _e : DEFAULT_EDIT_FRAMING, edits },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
6
67
|
/** What an interaction closed by its `signal` resolves with: unanswered, like a timeout. */
|
|
7
68
|
const STOPPED = {
|
|
8
69
|
status: 'timeout',
|
|
@@ -34,6 +95,70 @@ export class InteractionController {
|
|
|
34
95
|
*/
|
|
35
96
|
this.interceptors = [];
|
|
36
97
|
}
|
|
98
|
+
/** See `ChatDriver.getInteractionCheckpoint`. */
|
|
99
|
+
getCheckpoint() {
|
|
100
|
+
const cp = this.checkpoint;
|
|
101
|
+
return cp ? { componentName: cp.componentName, value: cp.value } : undefined;
|
|
102
|
+
}
|
|
103
|
+
/** See `ChatDriver.restoreInteractionCheckpoint`. Restored means cut: its interaction is gone. */
|
|
104
|
+
restoreCheckpoint(saved) {
|
|
105
|
+
this.checkpoint = saved
|
|
106
|
+
? { componentName: saved.componentName, value: saved.value, cut: true }
|
|
107
|
+
: undefined;
|
|
108
|
+
}
|
|
109
|
+
/** Drop the checkpoint, used or not, when its flow ends. */
|
|
110
|
+
clearCheckpoint(why) {
|
|
111
|
+
const cp = this.checkpoint;
|
|
112
|
+
if (!cp)
|
|
113
|
+
return;
|
|
114
|
+
this.checkpoint = undefined;
|
|
115
|
+
recordMetaEvent(this.deps.sessionKey, 'interaction.checkpoint', Object.assign({ action: 'cleared', component: cp.componentName, why }, (cp.interactionId ? { interactionId: cp.interactionId } : {})));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Hand a cut checkpoint to a new request of the same widget type, if its registration's
|
|
119
|
+
* `sameWork` agrees. Only the first such request is asked: a refused checkpoint is dropped, and
|
|
120
|
+
* the work starts fresh as it would have without one.
|
|
121
|
+
*/
|
|
122
|
+
takeOverCheckpoint(componentName, data, interactionId) {
|
|
123
|
+
var _a;
|
|
124
|
+
const cut = this.checkpoint;
|
|
125
|
+
if (!(cut === null || cut === void 0 ? void 0 : cut.cut) || cut.componentName !== componentName)
|
|
126
|
+
return undefined;
|
|
127
|
+
this.checkpoint = undefined;
|
|
128
|
+
const sameWork = (_a = getInteractionRegistration(componentName)) === null || _a === void 0 ? void 0 : _a.sameWork;
|
|
129
|
+
let agrees = true;
|
|
130
|
+
if (sameWork) {
|
|
131
|
+
try {
|
|
132
|
+
agrees = sameWork(cut.value, data);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
logger.warn(`InteractionController: "${componentName}" sameWork threw; starting fresh:`, e);
|
|
136
|
+
agrees = false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
recordMetaEvent(this.deps.sessionKey, 'interaction.checkpoint', Object.assign({ action: agrees ? 'resumed' : 'refused', component: componentName, interactionId }, (agrees ? {} : { saved: cut.value })));
|
|
140
|
+
if (!agrees)
|
|
141
|
+
return undefined;
|
|
142
|
+
this.checkpoint = { componentName, value: cut.value, interactionId };
|
|
143
|
+
return cut.value;
|
|
144
|
+
}
|
|
145
|
+
/** A controller's `ctx.saveCheckpoint`: kept as JSON, the form it is persisted in. */
|
|
146
|
+
saveCheckpoint(interactionId, componentName, value) {
|
|
147
|
+
var _a;
|
|
148
|
+
if (!this.pending.has(interactionId))
|
|
149
|
+
return;
|
|
150
|
+
let copy;
|
|
151
|
+
try {
|
|
152
|
+
copy = JSON.parse((_a = JSON.stringify(value)) !== null && _a !== void 0 ? _a : 'null');
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
logger.warn(`InteractionController: a "${componentName}" checkpoint is not storable as JSON:`, e);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
this.checkpoint = { componentName, value: copy, interactionId };
|
|
159
|
+
// A finished step changes no message, so autosave would not see it: ask for a save.
|
|
160
|
+
this.deps.emit(new CustomEvent('checkpoint-saved', { detail: { interactionId } }));
|
|
161
|
+
}
|
|
37
162
|
/** See `ChatDriver.addInteractionInterceptor`. Returns a remover. */
|
|
38
163
|
addInterceptor(interceptor) {
|
|
39
164
|
if (!interceptor.id || interceptor.id === 'user') {
|
|
@@ -92,11 +217,21 @@ export class InteractionController {
|
|
|
92
217
|
holdFor(handle, interceptor, isClaimant) {
|
|
93
218
|
return {
|
|
94
219
|
set: (holder) => {
|
|
220
|
+
var _a;
|
|
95
221
|
if (!isClaimant())
|
|
96
222
|
return false;
|
|
97
223
|
const next = holder === 'user' ? 'user' : interceptor.id;
|
|
98
224
|
if (!handle.setHeldBy(next))
|
|
99
225
|
return false;
|
|
226
|
+
// With the person now, so whatever settles it next is their answer — even if an editing
|
|
227
|
+
// action the claimant started is still unwinding. A finishing action still in flight keeps
|
|
228
|
+
// its credit: it is the claimant's choice, already made, and a pause must not pass it off as
|
|
229
|
+
// the person's. If that run does not settle it after all, its own restore clears the credit.
|
|
230
|
+
if (next === 'user') {
|
|
231
|
+
const pending = this.pending.get(handle.context.interactionId);
|
|
232
|
+
if (pending && !((_a = pending.running) === null || _a === void 0 ? void 0 : _a.finishes))
|
|
233
|
+
pending.settledBy = undefined;
|
|
234
|
+
}
|
|
100
235
|
recordMetaEvent(this.deps.sessionKey, 'interaction.held', {
|
|
101
236
|
interactionId: handle.context.interactionId,
|
|
102
237
|
heldBy: next,
|
|
@@ -122,8 +257,114 @@ export class InteractionController {
|
|
|
122
257
|
pending.decision = { choice: decision.choice, shownToUser: decision.shownToUser };
|
|
123
258
|
return true;
|
|
124
259
|
},
|
|
260
|
+
invoke: (action, args, options) => __awaiter(this, void 0, void 0, function* () {
|
|
261
|
+
var _a, _b, _c;
|
|
262
|
+
const { interactionId } = handle.context;
|
|
263
|
+
const pending = this.pending.get(interactionId);
|
|
264
|
+
if (!isClaimant() || !pending || handle.context.heldBy !== interceptor.id) {
|
|
265
|
+
return {
|
|
266
|
+
ok: false,
|
|
267
|
+
error: 'not-held',
|
|
268
|
+
message: pending
|
|
269
|
+
? 'This interaction is with the person now, so it cannot be acted on.'
|
|
270
|
+
: 'This interaction has already ended.',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const { surface } = handle;
|
|
274
|
+
if (!surface) {
|
|
275
|
+
return { ok: false, error: 'no-surface', message: 'Nothing has been exposed here yet.' };
|
|
276
|
+
}
|
|
277
|
+
// One action at a time: the claimant's calls may run in parallel, and a second run would
|
|
278
|
+
// overwrite the first's record and attribution while it is still in flight. Refused, with
|
|
279
|
+
// nothing run, so the caller can try again once the first has returned.
|
|
280
|
+
if (pending.running) {
|
|
281
|
+
return Object.assign(Object.assign({ ok: false }, BUSY), { actions: surface.actions() });
|
|
282
|
+
}
|
|
283
|
+
const prepared = surface.prepare(action, args);
|
|
284
|
+
if ('error' in prepared) {
|
|
285
|
+
return Object.assign(Object.assign({ ok: false }, prepared), { actions: surface.actions() });
|
|
286
|
+
}
|
|
287
|
+
// What a finishing run records before it runs, put back unless the run settles it: one that
|
|
288
|
+
// throws, or one that leaves it open, must not leave the next settle credited to it. An
|
|
289
|
+
// editing action records neither, so it has nothing to put back.
|
|
290
|
+
const previous = { decision: pending.decision, settledBy: pending.settledBy };
|
|
291
|
+
const restore = () => {
|
|
292
|
+
if (!prepared.finishes)
|
|
293
|
+
return;
|
|
294
|
+
pending.decision = previous.decision;
|
|
295
|
+
// Handed to the person meanwhile: whatever settles it next is theirs (see `set`).
|
|
296
|
+
pending.settledBy =
|
|
297
|
+
handle.context.heldBy === interceptor.id ? previous.settledBy : undefined;
|
|
298
|
+
};
|
|
299
|
+
// Recorded BEFORE running: a finishing action resolves inside `run`, and the record and the
|
|
300
|
+
// attribution have to be in place when it does. A finishing action is the claimant's
|
|
301
|
+
// answer, as `hold.resolve` is; handing the interaction back clears it. An editing action
|
|
302
|
+
// is never an answer, so a settle while one is in flight is not credited to it.
|
|
303
|
+
if (prepared.finishes) {
|
|
304
|
+
pending.decision = { choice: prepared.choice, shownToUser: options === null || options === void 0 ? void 0 : options.note };
|
|
305
|
+
pending.settledBy = interceptor.id;
|
|
306
|
+
}
|
|
307
|
+
// The note is what the person is shown about the choice, in the assistant's voice: a
|
|
308
|
+
// delegated, display-only message. The platform stamps `delegate`, so it cannot be claimed
|
|
309
|
+
// falsely. Shown only once the action has run: when it settles, `resolve` appends it so it
|
|
310
|
+
// precedes the outcome; otherwise it is appended below.
|
|
311
|
+
const content = (_a = options === null || options === void 0 ? void 0 : options.note) === null || _a === void 0 ? void 0 : _a.trim();
|
|
312
|
+
const note = content ? { content, delegate: interceptor.id } : undefined;
|
|
313
|
+
const token = {};
|
|
314
|
+
pending.running = { token, finishes: prepared.finishes, note };
|
|
315
|
+
let output;
|
|
316
|
+
try {
|
|
317
|
+
// A run that returns at once is over at once: the claimant's parallel calls start in the
|
|
318
|
+
// same tick, so awaiting it anyway would refuse the next one as busy for nothing.
|
|
319
|
+
const ran = prepared.run();
|
|
320
|
+
output = isThenable(ran) ? yield ran : ran;
|
|
321
|
+
}
|
|
322
|
+
catch (e) {
|
|
323
|
+
logger.warn(`InteractionController: action "${action}" threw:`, e);
|
|
324
|
+
if (pending.settledInRun !== token)
|
|
325
|
+
restore();
|
|
326
|
+
return {
|
|
327
|
+
ok: false,
|
|
328
|
+
error: 'threw',
|
|
329
|
+
message: e instanceof Error ? e.message : String(e),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
if (((_b = pending.running) === null || _b === void 0 ? void 0 : _b.token) === token)
|
|
334
|
+
pending.running = undefined;
|
|
335
|
+
}
|
|
336
|
+
// This run settled it — not merely "it is no longer pending", which a settle by someone
|
|
337
|
+
// else while the action was in flight would also satisfy.
|
|
338
|
+
const finished = pending.settledInRun === token;
|
|
339
|
+
// An editing action is not an answer: whoever finishes it later is credited with that. It
|
|
340
|
+
// is remembered as an edit instead, so the resolved view can mark it, and the working agent
|
|
341
|
+
// is told of it whoever finishes: in the claimant's decision when the claimant finishes it,
|
|
342
|
+
// or as edits made on the user's behalf that the person then confirmed.
|
|
343
|
+
if (!finished) {
|
|
344
|
+
restore();
|
|
345
|
+
if (note && this.pending.has(interactionId))
|
|
346
|
+
this.appendNote(note);
|
|
347
|
+
((_c = pending.edits) !== null && _c !== void 0 ? _c : (pending.edits = [])).push(editOf(interceptor.id, action, prepared.choice, args));
|
|
348
|
+
}
|
|
349
|
+
recordMetaEvent(this.deps.sessionKey, 'interaction.invoked', {
|
|
350
|
+
interactionId,
|
|
351
|
+
claimant: interceptor.id,
|
|
352
|
+
action,
|
|
353
|
+
finished,
|
|
354
|
+
});
|
|
355
|
+
return Object.assign({ ok: true, finished, actions: finished ? [] : surface.actions() }, (output !== undefined && !finished ? { output } : {}));
|
|
356
|
+
}),
|
|
125
357
|
};
|
|
126
358
|
}
|
|
359
|
+
/** A claimant's note about its choice: a delegated, display-only message in the assistant's voice. */
|
|
360
|
+
appendNote(note) {
|
|
361
|
+
this.deps.appendToHistory({
|
|
362
|
+
role: 'assistant',
|
|
363
|
+
content: note.content,
|
|
364
|
+
delegate: note.delegate,
|
|
365
|
+
excludeFromProvider: true,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
127
368
|
/** See `ChatDriver.setHostInteractionRequester`. */
|
|
128
369
|
setHostRequester(fn, recordOutcome) {
|
|
129
370
|
this.hostRequester = fn;
|
|
@@ -175,8 +416,39 @@ export class InteractionController {
|
|
|
175
416
|
// wrapper finds it when it builds the pending `interaction` descriptor, and
|
|
176
417
|
// offer it to the interceptors (GENC-1660) before it is appended, so the
|
|
177
418
|
// widget's first render already knows who holds it.
|
|
178
|
-
|
|
419
|
+
// `ctx.resolve` settles through the same path a widget's `interaction-completed` takes —
|
|
420
|
+
// including a controller resolving in its constructor, before its message is appended,
|
|
421
|
+
// which `resolve` holds back until there is a message to patch.
|
|
422
|
+
const handle = createInteractionContext(interactionId, {
|
|
423
|
+
resolve: (result) => this.resolve(interactionId, result),
|
|
424
|
+
checkpoint: this.takeOverCheckpoint(componentName, data, interactionId),
|
|
425
|
+
saveCheckpoint: (value) => this.saveCheckpoint(interactionId, componentName, value),
|
|
426
|
+
});
|
|
179
427
|
this.contexts.set(interactionId, handle);
|
|
428
|
+
bindStepRecorder(handle.context, (step, outcome) => recordMetaEvent(this.deps.sessionKey, 'interaction.step', {
|
|
429
|
+
interactionId,
|
|
430
|
+
component: componentName,
|
|
431
|
+
step,
|
|
432
|
+
outcome,
|
|
433
|
+
}), (reason, saved) => recordMetaEvent(this.deps.sessionKey, 'interaction.checkpoint', {
|
|
434
|
+
action: 'refused',
|
|
435
|
+
component: componentName,
|
|
436
|
+
interactionId,
|
|
437
|
+
reason,
|
|
438
|
+
saved,
|
|
439
|
+
}));
|
|
440
|
+
// Build the widget's controller from its registration (GENC-1660) BEFORE the interceptors are
|
|
441
|
+
// asked, so a pure question's surface is ready at the handoff.
|
|
442
|
+
const registration = getInteractionRegistration(componentName);
|
|
443
|
+
if (registration === null || registration === void 0 ? void 0 : registration.controller) {
|
|
444
|
+
try {
|
|
445
|
+
handle.setController(registration.controller(data, handle.context));
|
|
446
|
+
}
|
|
447
|
+
catch (e) {
|
|
448
|
+
// The widget still renders and the person can still answer it; it just has no surface.
|
|
449
|
+
logger.error(`InteractionController: the "${componentName}" controller threw:`, e);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
180
452
|
const heldBy = this.offer(handle, {
|
|
181
453
|
interactionId,
|
|
182
454
|
componentName,
|
|
@@ -235,7 +507,7 @@ export class InteractionController {
|
|
|
235
507
|
* report twice, and the second report has nothing left to settle.
|
|
236
508
|
*/
|
|
237
509
|
resolve(interactionId, result) {
|
|
238
|
-
var _a, _b, _c, _d, _e, _f, _g
|
|
510
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
239
511
|
const interaction = this.pending.get(interactionId);
|
|
240
512
|
if (!interaction) {
|
|
241
513
|
logger.warn(`Interaction with ID ${interactionId} not found.`);
|
|
@@ -268,15 +540,19 @@ export class InteractionController {
|
|
|
268
540
|
// claimant holds is still the person's answer. A timeout or a stop is nobody's.
|
|
269
541
|
const unanswered = (result === null || result === void 0 ? void 0 : result.status) === 'timeout';
|
|
270
542
|
const answeredBy = unanswered ? undefined : ((_c = interaction.settledBy) !== null && _c !== void 0 ? _c : 'user');
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
543
|
+
// Settled under a claimant's `hold.invoke` run, credited to it: that run's note goes first, so
|
|
544
|
+
// it precedes the outcome, and the run learns that it was the one that finished it.
|
|
545
|
+
const run = answeredBy && answeredBy !== 'user' ? interaction.running : undefined;
|
|
546
|
+
if (run) {
|
|
547
|
+
interaction.settledInRun = run.token;
|
|
548
|
+
if (run.note)
|
|
549
|
+
this.appendNote(run.note);
|
|
550
|
+
}
|
|
551
|
+
const edits = ((_d = interaction.edits) === null || _d === void 0 ? void 0 : _d.length) ? interaction.edits : undefined;
|
|
552
|
+
const { decision, edited } = doneOnUsersBehalf(interaction, answeredBy);
|
|
277
553
|
const patched = this.deps.patchInteractionMessage(interactionId, (message) => {
|
|
278
554
|
var _a;
|
|
279
|
-
return (Object.assign(Object.assign(Object.assign({}, message), { interaction: Object.assign(Object.assign(Object.assign({}, message.interaction), { resolved: result }), (answeredBy ? { answeredBy } : {})) }), (costUsd != null ? { externalCostUsd: ((_a = message.externalCostUsd) !== null && _a !== void 0 ? _a : 0) + costUsd } : {})));
|
|
555
|
+
return (Object.assign(Object.assign(Object.assign({}, message), { interaction: Object.assign(Object.assign(Object.assign(Object.assign({}, message.interaction), { resolved: result }), (answeredBy ? { answeredBy } : {})), (edits ? { edits } : {})) }), (costUsd != null ? { externalCostUsd: ((_a = message.externalCostUsd) !== null && _a !== void 0 ? _a : 0) + costUsd } : {})));
|
|
280
556
|
});
|
|
281
557
|
if (patched)
|
|
282
558
|
this.deps.notifyHistoryUpdated();
|
|
@@ -304,23 +580,32 @@ export class InteractionController {
|
|
|
304
580
|
if (interaction.overrideId) {
|
|
305
581
|
this.deps.emit(new CustomEvent('interaction-stop', { detail: { interactionId } }));
|
|
306
582
|
}
|
|
307
|
-
recordMetaEvent(this.deps.sessionKey, 'interaction.resolved', Object.assign({ interactionId }, (answeredBy ? { answeredBy } : {})));
|
|
583
|
+
recordMetaEvent(this.deps.sessionKey, 'interaction.resolved', Object.assign(Object.assign({ interactionId }, (answeredBy ? { answeredBy } : {})), (edits ? { edits: edits.length } : {})));
|
|
308
584
|
// The park is ending and the loop is about to resume computing — paired
|
|
309
585
|
// with `interaction-requested`. Fires for every resolution path (user
|
|
310
586
|
// completion, timeout, cancellation), since all route through here.
|
|
311
587
|
this.deps.activityBus.publish('interaction-resolved', undefined);
|
|
312
588
|
// Before the promise settles, so the record is in place when the handler carries on.
|
|
313
589
|
try {
|
|
314
|
-
(
|
|
590
|
+
(_e = interaction.onSettled) === null || _e === void 0 ? void 0 : _e.call(interaction, Object.assign({ answeredBy, decision }, (edited ? { edited } : {})));
|
|
315
591
|
}
|
|
316
592
|
catch (e) {
|
|
317
593
|
logger.warn('InteractionController: onSettled threw:', e);
|
|
318
594
|
}
|
|
595
|
+
// For the host, whoever settled it (GENC-1660): a controller resolving through `ctx.resolve`
|
|
596
|
+
// never passes through the host's `interaction-completed` handler, which is where it started
|
|
597
|
+
// its loading indicator.
|
|
598
|
+
this.deps.emit(new CustomEvent('interaction-settled', {
|
|
599
|
+
detail: Object.assign({ interactionId }, (answeredBy ? { answeredBy } : {})),
|
|
600
|
+
}));
|
|
319
601
|
interaction.resolve(result);
|
|
320
602
|
this.pending.delete(interactionId);
|
|
603
|
+
// Settled, so its work is finished with: nothing is left to resume.
|
|
604
|
+
if (((_f = this.checkpoint) === null || _f === void 0 ? void 0 : _f.interactionId) === interactionId)
|
|
605
|
+
this.checkpoint = undefined;
|
|
321
606
|
// Tear down the live context on RESOLVE (not on element unmount) — this closes
|
|
322
607
|
// any sockets/timers the widget parked on it via `onDispose`. (GENC-1390)
|
|
323
|
-
(
|
|
608
|
+
(_g = this.contexts.get(interactionId)) === null || _g === void 0 ? void 0 : _g.dispose();
|
|
324
609
|
this.contexts.delete(interactionId);
|
|
325
610
|
}
|
|
326
611
|
/**
|
|
@@ -22,6 +22,12 @@ let AiChatInteractionWrapper = class AiChatInteractionWrapper extends GenesisEle
|
|
|
22
22
|
this.resolved = undefined;
|
|
23
23
|
/** See `AiChatInteraction`'s resolved `outcome`: recorded after the answer, by the tool. */
|
|
24
24
|
this.outcome = undefined;
|
|
25
|
+
/**
|
|
26
|
+
* See `AiChatInteraction`'s resolved `edits`: what a claimant changed before it was settled. The
|
|
27
|
+
* template binds the message's `edits` straight over this, so `undefined` arrives whenever there
|
|
28
|
+
* were none: it means "no edits", never an error.
|
|
29
|
+
*/
|
|
30
|
+
this.edits = undefined;
|
|
25
31
|
}
|
|
26
32
|
connectedCallback() {
|
|
27
33
|
super.connectedCallback();
|
|
@@ -67,9 +73,14 @@ let AiChatInteractionWrapper = class AiChatInteractionWrapper extends GenesisEle
|
|
|
67
73
|
outcomeChanged() {
|
|
68
74
|
this.resolvedChanged();
|
|
69
75
|
}
|
|
70
|
-
/**
|
|
76
|
+
/** Edits a claimant made reach a widget already showing the answer, the same way. */
|
|
77
|
+
editsChanged() {
|
|
78
|
+
this.resolvedChanged();
|
|
79
|
+
}
|
|
80
|
+
/** The resolved descriptor, with the outcome and the claimant's edits when there are any. */
|
|
71
81
|
resolvedInteraction() {
|
|
72
|
-
|
|
82
|
+
var _a;
|
|
83
|
+
return Object.assign(Object.assign({ status: 'resolved', data: this.data, result: this.resolved }, (this.outcome !== undefined ? { outcome: this.outcome } : {})), (((_a = this.edits) === null || _a === void 0 ? void 0 : _a.length) ? { edits: this.edits } : {}));
|
|
73
84
|
}
|
|
74
85
|
/**
|
|
75
86
|
* Watch the rendered widget element for box-size changes. When the widget
|
|
@@ -213,6 +224,9 @@ __decorate([
|
|
|
213
224
|
__decorate([
|
|
214
225
|
observable
|
|
215
226
|
], AiChatInteractionWrapper.prototype, "outcome", void 0);
|
|
227
|
+
__decorate([
|
|
228
|
+
observable
|
|
229
|
+
], AiChatInteractionWrapper.prototype, "edits", void 0);
|
|
216
230
|
AiChatInteractionWrapper = __decorate([
|
|
217
231
|
customElement({
|
|
218
232
|
name: 'ai-chat-interaction-wrapper',
|