@genesislcap/ai-assistant 15.61.0 → 15.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +4759 -717
- 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/genesis/write-commit.d.ts +17 -7
- package/dist/dts/genesis/write-commit.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/genesis/write-outcome.js +2 -2
- 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
package/dist/chat-driver.mjs
CHANGED
|
@@ -3879,6 +3879,7 @@ var AgenticActivityBus = class {
|
|
|
3879
3879
|
const inBrowser = typeof window !== "undefined" && typeof BroadcastChannel !== "undefined";
|
|
3880
3880
|
if (this.crossTabEvents.size === 0 || !this.channelNameFactory || !inBrowser) return;
|
|
3881
3881
|
this.channel = new BroadcastChannel(this.channelNameFactory());
|
|
3882
|
+
this.channel.unref?.();
|
|
3882
3883
|
this.channel.onmessage = (ev) => {
|
|
3883
3884
|
const { type, detail } = ev.data ?? {};
|
|
3884
3885
|
if (type) this.notifyLocal(type, detail);
|
|
@@ -3955,10 +3956,10 @@ var agenticActivityBus = new AgenticActivityBus({
|
|
|
3955
3956
|
});
|
|
3956
3957
|
|
|
3957
3958
|
// src/config/validate-providers.ts
|
|
3958
|
-
function resolveChatProvider(
|
|
3959
|
-
const provider =
|
|
3959
|
+
function resolveChatProvider(registry3, providerName, agentName) {
|
|
3960
|
+
const provider = registry3.get(providerName);
|
|
3960
3961
|
if (!provider) {
|
|
3961
|
-
const available =
|
|
3962
|
+
const available = registry3.names().join(", ") || "(none)";
|
|
3962
3963
|
throw new Error(
|
|
3963
3964
|
`Agent "${agentName}" references unknown provider "${providerName}". Registered: ${available}`
|
|
3964
3965
|
);
|
|
@@ -3970,13 +3971,13 @@ function resolveChatProvider(registry2, providerName, agentName) {
|
|
|
3970
3971
|
}
|
|
3971
3972
|
return provider;
|
|
3972
3973
|
}
|
|
3973
|
-
function validateStaticAgentProviders(agents,
|
|
3974
|
+
function validateStaticAgentProviders(agents, registry3) {
|
|
3974
3975
|
const visited = /* @__PURE__ */ new Set();
|
|
3975
3976
|
const walk = (agent) => {
|
|
3976
3977
|
if (visited.has(agent)) return;
|
|
3977
3978
|
visited.add(agent);
|
|
3978
3979
|
if (typeof agent.provider === "string") {
|
|
3979
|
-
resolveChatProvider(
|
|
3980
|
+
resolveChatProvider(registry3, agent.provider, agent.name);
|
|
3980
3981
|
}
|
|
3981
3982
|
for (const sub of agent.subAgents ?? []) walk(sub);
|
|
3982
3983
|
};
|
|
@@ -4027,10 +4028,36 @@ var META_EVENT_IMPORTANCE = {
|
|
|
4027
4028
|
"interaction.outcome": "normal",
|
|
4028
4029
|
// An outcome that could not be recorded: the widget goes on showing only the answer.
|
|
4029
4030
|
"interaction.outcome-dropped": "high",
|
|
4031
|
+
// A long action's step starting, finishing, or skipped on resume because it was already done.
|
|
4032
|
+
"interaction.step": "low",
|
|
4033
|
+
// A cut checkpoint taken over after a reload, refused (by sameWork, or as malformed), or dropped
|
|
4034
|
+
// at flow end.
|
|
4035
|
+
"interaction.checkpoint": "normal",
|
|
4030
4036
|
// A claimant handing a pending interaction to the person, or taking it back.
|
|
4031
4037
|
"interaction.held": "normal",
|
|
4032
4038
|
// A claim on a `userOnly` interaction, refused. Expected when an interceptor claims broadly.
|
|
4033
4039
|
"interaction.claim-refused": "low",
|
|
4040
|
+
// A claimant ran one of the surface's actions: what it did, and whether that settled it.
|
|
4041
|
+
"interaction.invoked": "normal",
|
|
4042
|
+
"autopilot.claimed": "normal",
|
|
4043
|
+
// One per interaction the autopilot does not take, so frequent and mostly expected.
|
|
4044
|
+
"autopilot.declined": "low",
|
|
4045
|
+
"autopilot.decision-sent": "low",
|
|
4046
|
+
"autopilot.acted": "normal",
|
|
4047
|
+
"autopilot.handed-back": "normal",
|
|
4048
|
+
"autopilot.paused": "normal",
|
|
4049
|
+
"autopilot.resumed": "normal",
|
|
4050
|
+
// The wrapper itself failed: the journey stops being automatic without anyone choosing that.
|
|
4051
|
+
"autopilot.error": "high",
|
|
4052
|
+
// A written answer counted against the step's limit; reaching it withdraws `reply`.
|
|
4053
|
+
"autopilot.refinement": "normal",
|
|
4054
|
+
// The autopilot showed its own widget because a working turn ended needing the person.
|
|
4055
|
+
"autopilot.handback-presented": "normal",
|
|
4056
|
+
// The autopilot was given the whole visible transcript: its first decision, a rebuild, or after
|
|
4057
|
+
// a compaction removed the last message it saw.
|
|
4058
|
+
"autopilot.catch-up": "normal",
|
|
4059
|
+
// Autopilot spend that reached no message, so it is missing from the session total.
|
|
4060
|
+
"autopilot.cost-dropped": "high",
|
|
4034
4061
|
"external-diagnostics.folded": "normal",
|
|
4035
4062
|
"session.cleared": "normal",
|
|
4036
4063
|
"session.switched": "low",
|
|
@@ -4109,6 +4136,45 @@ function clearSession(key) {
|
|
|
4109
4136
|
registry.delete(key);
|
|
4110
4137
|
}
|
|
4111
4138
|
|
|
4139
|
+
// src/utils/delegated-message.ts
|
|
4140
|
+
var DEFAULT_DELEGATE_FRAMING = "Sent on the user's behalf.";
|
|
4141
|
+
function delegatedFields(options) {
|
|
4142
|
+
if (!options?.delegate) return {};
|
|
4143
|
+
const cost = options.externalCostUsd;
|
|
4144
|
+
return {
|
|
4145
|
+
delegate: options.delegate,
|
|
4146
|
+
delegateFraming: options.delegateFraming ?? DEFAULT_DELEGATE_FRAMING,
|
|
4147
|
+
...typeof cost === "number" && Number.isFinite(cost) && cost > 0 ? { externalCostUsd: cost } : {}
|
|
4148
|
+
};
|
|
4149
|
+
}
|
|
4150
|
+
function frameDelegatedContent(content, framing) {
|
|
4151
|
+
return `(${framing ?? DEFAULT_DELEGATE_FRAMING})
|
|
4152
|
+
|
|
4153
|
+
${content}`;
|
|
4154
|
+
}
|
|
4155
|
+
|
|
4156
|
+
// src/utils/preserved-user-messages.ts
|
|
4157
|
+
function freshUserMessages(toCompact) {
|
|
4158
|
+
return toCompact.flatMap(
|
|
4159
|
+
(m) => m.role === "user" && m.content?.trim() ? [
|
|
4160
|
+
{
|
|
4161
|
+
content: m.content,
|
|
4162
|
+
...m.timestamp ? { timestamp: m.timestamp } : {},
|
|
4163
|
+
...m.delegate ? { delegate: m.delegate, delegateFraming: m.delegateFraming } : {}
|
|
4164
|
+
}
|
|
4165
|
+
] : []
|
|
4166
|
+
);
|
|
4167
|
+
}
|
|
4168
|
+
var preservedUserLine = (u) => `User: ${u.delegate ? frameDelegatedContent(u.content, u.delegateFraming) : u.content}`;
|
|
4169
|
+
function verbatimUserMessagesBlock(preserved) {
|
|
4170
|
+
if (!preserved?.length) return "";
|
|
4171
|
+
return `
|
|
4172
|
+
|
|
4173
|
+
<user_messages_verbatim>
|
|
4174
|
+
${preserved.map(preservedUserLine).join("\n")}
|
|
4175
|
+
</user_messages_verbatim>`;
|
|
4176
|
+
}
|
|
4177
|
+
|
|
4112
4178
|
// src/utils/context-tokens.ts
|
|
4113
4179
|
var APPROX_CHARS_PER_TOKEN = 4;
|
|
4114
4180
|
var IMAGE_TOKEN_ESTIMATE = 1600;
|
|
@@ -4142,7 +4208,7 @@ function imageCount(m) {
|
|
|
4142
4208
|
}
|
|
4143
4209
|
var TEXT_ATTACHMENT_FRAMING_CHARS = 9;
|
|
4144
4210
|
function wireChars(m) {
|
|
4145
|
-
let chars = m.content?.length ?? 0;
|
|
4211
|
+
let chars = (m.content?.length ?? 0) + verbatimUserMessagesBlock(m.compaction?.preservedUserMessages).length;
|
|
4146
4212
|
for (const attachment of m.attachments ?? []) {
|
|
4147
4213
|
if (attachment.kind === "image") continue;
|
|
4148
4214
|
chars += TEXT_ATTACHMENT_FRAMING_CHARS + attachment.name.length + (attachment.content?.length ?? 0);
|
|
@@ -4231,8 +4297,11 @@ function projectCompaction(history, cut, summaryTokens = COMPACTION_SUMMARY_TOKE
|
|
|
4231
4297
|
const { estimates, overhead, total } = calibrate(history, preambleTokens);
|
|
4232
4298
|
let tail = 0;
|
|
4233
4299
|
for (let i = cut; i < estimates.length; i += 1) tail += estimates[i];
|
|
4300
|
+
const kept = Math.ceil(
|
|
4301
|
+
verbatimUserMessagesBlock(freshUserMessages(history.slice(0, cut))).length / APPROX_CHARS_PER_TOKEN
|
|
4302
|
+
);
|
|
4234
4303
|
const tokensBefore = total;
|
|
4235
|
-
const tokensAfter = overhead + summaryTokens + tail;
|
|
4304
|
+
const tokensAfter = overhead + summaryTokens + kept + tail;
|
|
4236
4305
|
return {
|
|
4237
4306
|
cut,
|
|
4238
4307
|
compactedCount: cut,
|
|
@@ -4242,14 +4311,6 @@ function projectCompaction(history, cut, summaryTokens = COMPACTION_SUMMARY_TOKE
|
|
|
4242
4311
|
};
|
|
4243
4312
|
}
|
|
4244
4313
|
|
|
4245
|
-
// src/utils/delegated-message.ts
|
|
4246
|
-
var DEFAULT_DELEGATE_FRAMING = "Sent on the user's behalf.";
|
|
4247
|
-
function frameDelegatedContent(content, framing) {
|
|
4248
|
-
return `(${framing ?? DEFAULT_DELEGATE_FRAMING})
|
|
4249
|
-
|
|
4250
|
-
${content}`;
|
|
4251
|
-
}
|
|
4252
|
-
|
|
4253
4314
|
// src/utils/logger.ts
|
|
4254
4315
|
var logger2 = createLogger("ai-assistant");
|
|
4255
4316
|
|
|
@@ -4691,7 +4752,12 @@ function planCompaction(history, options = {}) {
|
|
|
4691
4752
|
function renderMessageForSummary(m) {
|
|
4692
4753
|
switch (m.role) {
|
|
4693
4754
|
case "compacted-summary":
|
|
4694
|
-
return
|
|
4755
|
+
return [
|
|
4756
|
+
`[Earlier summary] ${m.content}`,
|
|
4757
|
+
...(m.compaction?.preservedUserMessages ?? []).map(
|
|
4758
|
+
(u) => `[Kept word for word with it] ${preservedUserLine(u)}`
|
|
4759
|
+
)
|
|
4760
|
+
].join("\n");
|
|
4695
4761
|
case "user":
|
|
4696
4762
|
if (!m.content) return "";
|
|
4697
4763
|
return `User: ${m.delegate ? frameDelegatedContent(m.content, m.delegateFraming) : m.content}`;
|
|
@@ -4730,14 +4796,16 @@ function normalizeForProvider(history) {
|
|
|
4730
4796
|
return { ...m, content: frameDelegatedContent(m.content ?? "", m.delegateFraming) };
|
|
4731
4797
|
}
|
|
4732
4798
|
if (m.role !== "compacted-summary") return m;
|
|
4799
|
+
const preserved = m.compaction?.preservedUserMessages ?? [];
|
|
4800
|
+
const verbatim = verbatimUserMessagesBlock(preserved);
|
|
4733
4801
|
return {
|
|
4734
4802
|
...m,
|
|
4735
4803
|
role: "user",
|
|
4736
4804
|
content: `<conversation_summary>
|
|
4737
4805
|
${m.content}
|
|
4738
|
-
</conversation_summary
|
|
4806
|
+
</conversation_summary>${verbatim}
|
|
4739
4807
|
|
|
4740
|
-
The above summarizes earlier parts of this conversation that were compacted. Continue naturally from it
|
|
4808
|
+
The above summarizes earlier parts of this conversation that were compacted` + (preserved.length ? ", with the user's own messages from them kept word for word" : "") + ". Continue naturally from it.",
|
|
4741
4809
|
compaction: void 0
|
|
4742
4810
|
};
|
|
4743
4811
|
});
|
|
@@ -5198,19 +5266,222 @@ var FoldController = class {
|
|
|
5198
5266
|
}
|
|
5199
5267
|
};
|
|
5200
5268
|
|
|
5269
|
+
// src/state/decision-surface.ts
|
|
5270
|
+
var SELECT_ACTION = "select";
|
|
5271
|
+
var REPLY_ACTION = "reply";
|
|
5272
|
+
var normalise = (option) => typeof option === "string" ? { id: option, label: option } : { id: option.id ?? option.label, label: option.label, enabledWhen: option.enabledWhen };
|
|
5273
|
+
var isEnabled = (option) => {
|
|
5274
|
+
try {
|
|
5275
|
+
return option.enabledWhen?.() ?? true;
|
|
5276
|
+
} catch {
|
|
5277
|
+
return false;
|
|
5278
|
+
}
|
|
5279
|
+
};
|
|
5280
|
+
function buildDecisionSurface(spec) {
|
|
5281
|
+
const options = spec.options.map(normalise);
|
|
5282
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5283
|
+
for (const { id } of options) {
|
|
5284
|
+
if (ids.has(id)) throw new Error(`exposeDecision: two options share the id "${id}".`);
|
|
5285
|
+
if (spec.multi && id === SELECT_ACTION || spec.freeText && id === REPLY_ACTION) {
|
|
5286
|
+
throw new Error(`exposeDecision: an option cannot use the reserved id "${id}".`);
|
|
5287
|
+
}
|
|
5288
|
+
ids.add(id);
|
|
5289
|
+
}
|
|
5290
|
+
const byIdOrLabel = (key) => typeof key === "string" ? options.find((o) => o.id === key || o.label === key) : void 0;
|
|
5291
|
+
const actions = () => {
|
|
5292
|
+
const enabled = options.filter(isEnabled);
|
|
5293
|
+
return [
|
|
5294
|
+
...options.map((o) => ({
|
|
5295
|
+
name: o.id,
|
|
5296
|
+
label: o.label,
|
|
5297
|
+
finishes: true,
|
|
5298
|
+
enabled: isEnabled(o)
|
|
5299
|
+
})),
|
|
5300
|
+
...spec.multi ? [
|
|
5301
|
+
{
|
|
5302
|
+
name: SELECT_ACTION,
|
|
5303
|
+
finishes: true,
|
|
5304
|
+
enabled: enabled.length > 0,
|
|
5305
|
+
args: "options"
|
|
5306
|
+
}
|
|
5307
|
+
] : [],
|
|
5308
|
+
...spec.freeText ? [{ name: REPLY_ACTION, finishes: true, enabled: true, args: "text" }] : []
|
|
5309
|
+
];
|
|
5310
|
+
};
|
|
5311
|
+
const answer = (a) => () => spec.choose(a);
|
|
5312
|
+
return {
|
|
5313
|
+
prompt: spec.prompt,
|
|
5314
|
+
facts: () => spec.facts?.() ?? {},
|
|
5315
|
+
actions,
|
|
5316
|
+
isReady: () => actions().some((a) => a.finishes && a.enabled),
|
|
5317
|
+
prepare(name, args) {
|
|
5318
|
+
if (spec.multi && name === SELECT_ACTION) {
|
|
5319
|
+
const picked = args?.options;
|
|
5320
|
+
if (!Array.isArray(picked) || picked.length === 0) {
|
|
5321
|
+
return {
|
|
5322
|
+
error: "invalid-args",
|
|
5323
|
+
message: "`select` takes { options: string[] }, not empty."
|
|
5324
|
+
};
|
|
5325
|
+
}
|
|
5326
|
+
const chosen = picked.map(byIdOrLabel);
|
|
5327
|
+
const unknown = picked.filter((_, i) => !chosen[i]);
|
|
5328
|
+
if (unknown.length) {
|
|
5329
|
+
return { error: "invalid-args", message: `Not options here: ${unknown.join(", ")}.` };
|
|
5330
|
+
}
|
|
5331
|
+
const disabled = chosen.filter((o) => !isEnabled(o));
|
|
5332
|
+
if (disabled.length) {
|
|
5333
|
+
return {
|
|
5334
|
+
error: "disabled",
|
|
5335
|
+
message: `Not available right now: ${disabled.map((o) => o.label).join(", ")}.`
|
|
5336
|
+
};
|
|
5337
|
+
}
|
|
5338
|
+
const picks = chosen;
|
|
5339
|
+
return {
|
|
5340
|
+
choice: picks.map((o) => o.label).join(", "),
|
|
5341
|
+
finishes: true,
|
|
5342
|
+
run: answer({ kind: "options", options: picks.map((o) => o.id) })
|
|
5343
|
+
};
|
|
5344
|
+
}
|
|
5345
|
+
if (spec.freeText && name === REPLY_ACTION) {
|
|
5346
|
+
const text = args?.text;
|
|
5347
|
+
if (typeof text !== "string" || !text.trim()) {
|
|
5348
|
+
return { error: "invalid-args", message: "`reply` takes { text: string }, not empty." };
|
|
5349
|
+
}
|
|
5350
|
+
return { choice: text, finishes: true, run: answer({ kind: "text", text }) };
|
|
5351
|
+
}
|
|
5352
|
+
const option = options.find((o) => o.id === name);
|
|
5353
|
+
if (!option) {
|
|
5354
|
+
return { error: "unknown-action", message: `No action "${name}" here.` };
|
|
5355
|
+
}
|
|
5356
|
+
if (!isEnabled(option)) {
|
|
5357
|
+
return { error: "disabled", message: `"${option.label}" is not available right now.` };
|
|
5358
|
+
}
|
|
5359
|
+
return {
|
|
5360
|
+
choice: option.label,
|
|
5361
|
+
finishes: true,
|
|
5362
|
+
run: answer({ kind: "option", option: option.id })
|
|
5363
|
+
};
|
|
5364
|
+
}
|
|
5365
|
+
};
|
|
5366
|
+
}
|
|
5367
|
+
var CHOICE_CHARS = 300;
|
|
5368
|
+
var matchesType = (value, type) => {
|
|
5369
|
+
switch (type) {
|
|
5370
|
+
case "string":
|
|
5371
|
+
return typeof value === "string";
|
|
5372
|
+
case "number":
|
|
5373
|
+
case "integer":
|
|
5374
|
+
return typeof value === "number" && (type === "number" || Number.isInteger(value));
|
|
5375
|
+
case "boolean":
|
|
5376
|
+
return typeof value === "boolean";
|
|
5377
|
+
case "array":
|
|
5378
|
+
return Array.isArray(value);
|
|
5379
|
+
case "object":
|
|
5380
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5381
|
+
default:
|
|
5382
|
+
return true;
|
|
5383
|
+
}
|
|
5384
|
+
};
|
|
5385
|
+
function checkArgs(args, schema) {
|
|
5386
|
+
if (!schema) return void 0;
|
|
5387
|
+
if (!matchesType(args, "object")) return "its arguments must be an object.";
|
|
5388
|
+
const record = args;
|
|
5389
|
+
const required = schema.required ?? [];
|
|
5390
|
+
const missing = required.filter((key) => record[key] === void 0);
|
|
5391
|
+
if (missing.length) return `missing: ${missing.join(", ")}.`;
|
|
5392
|
+
const properties = schema.properties ?? {};
|
|
5393
|
+
const wrong = Object.entries(properties).filter(([key, prop]) => record[key] !== void 0 && !matchesType(record[key], prop?.type)).map(([key, prop]) => `${key} should be ${String(prop?.type)}`);
|
|
5394
|
+
return wrong.length ? `${wrong.join("; ")}.` : void 0;
|
|
5395
|
+
}
|
|
5396
|
+
function buildFullSurface(spec) {
|
|
5397
|
+
const guard = (enabledWhen) => {
|
|
5398
|
+
try {
|
|
5399
|
+
return enabledWhen?.() ?? true;
|
|
5400
|
+
} catch {
|
|
5401
|
+
return false;
|
|
5402
|
+
}
|
|
5403
|
+
};
|
|
5404
|
+
const actions = () => Object.entries(spec.actions).map(([name, a]) => ({
|
|
5405
|
+
name,
|
|
5406
|
+
finishes: !!a.finishes,
|
|
5407
|
+
enabled: guard(a.enabledWhen),
|
|
5408
|
+
description: a.description,
|
|
5409
|
+
...a.argsSchema ? { args: "fields", argsSchema: a.argsSchema } : {}
|
|
5410
|
+
}));
|
|
5411
|
+
return {
|
|
5412
|
+
prompt: spec.prompt,
|
|
5413
|
+
facts: () => ({}),
|
|
5414
|
+
describe: () => spec.describe(),
|
|
5415
|
+
views: spec.views,
|
|
5416
|
+
read: (view) => spec.read(view),
|
|
5417
|
+
actions,
|
|
5418
|
+
isReady: () => actions().some((a) => a.enabled),
|
|
5419
|
+
prepare(name, args) {
|
|
5420
|
+
const action = Object.prototype.hasOwnProperty.call(spec.actions, name) ? spec.actions[name] : void 0;
|
|
5421
|
+
if (!action) return { error: "unknown-action", message: `No action "${name}" here.` };
|
|
5422
|
+
if (!guard(action.enabledWhen)) {
|
|
5423
|
+
return { error: "disabled", message: `"${name}" is not available right now.` };
|
|
5424
|
+
}
|
|
5425
|
+
const problem = checkArgs(args ?? {}, action.argsSchema);
|
|
5426
|
+
if (problem) return { error: "invalid-args", message: `"${name}": ${problem}` };
|
|
5427
|
+
const shown = args && Object.keys(args).length ? ` ${JSON.stringify(args)}` : "";
|
|
5428
|
+
return {
|
|
5429
|
+
choice: `${name}${shown}`.slice(0, CHOICE_CHARS),
|
|
5430
|
+
finishes: !!action.finishes,
|
|
5431
|
+
run: () => action.run(args ?? {})
|
|
5432
|
+
};
|
|
5433
|
+
}
|
|
5434
|
+
};
|
|
5435
|
+
}
|
|
5436
|
+
|
|
5201
5437
|
// src/state/interaction-context.ts
|
|
5202
|
-
function createInteractionContext(interactionId) {
|
|
5438
|
+
function createInteractionContext(interactionId, deps = {}) {
|
|
5203
5439
|
const state = {};
|
|
5204
5440
|
const onceCache = /* @__PURE__ */ new Map();
|
|
5205
5441
|
const listeners = /* @__PURE__ */ new Set();
|
|
5206
5442
|
const disposers = /* @__PURE__ */ new Set();
|
|
5443
|
+
const readyListeners = /* @__PURE__ */ new Set();
|
|
5207
5444
|
let disposed = false;
|
|
5445
|
+
let settled = false;
|
|
5208
5446
|
let heldBy = "user";
|
|
5447
|
+
let controller;
|
|
5448
|
+
let surface;
|
|
5449
|
+
let lastActivityAt = Date.now();
|
|
5450
|
+
let checkpoint = deps.checkpoint;
|
|
5451
|
+
const checkReady = () => {
|
|
5452
|
+
if (disposed || !surface || readyListeners.size === 0 || !surface.isReady()) return;
|
|
5453
|
+
const waiting = [...readyListeners];
|
|
5454
|
+
readyListeners.clear();
|
|
5455
|
+
for (const fn of waiting) {
|
|
5456
|
+
try {
|
|
5457
|
+
fn();
|
|
5458
|
+
} catch (e) {
|
|
5459
|
+
logger2.error("InteractionContext onSurfaceReady callback threw", e);
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
5462
|
+
};
|
|
5209
5463
|
const context = {
|
|
5210
5464
|
interactionId,
|
|
5211
5465
|
get heldBy() {
|
|
5212
5466
|
return heldBy;
|
|
5213
5467
|
},
|
|
5468
|
+
get controller() {
|
|
5469
|
+
return controller;
|
|
5470
|
+
},
|
|
5471
|
+
get surface() {
|
|
5472
|
+
return surface;
|
|
5473
|
+
},
|
|
5474
|
+
get lastActivityAt() {
|
|
5475
|
+
return lastActivityAt;
|
|
5476
|
+
},
|
|
5477
|
+
get checkpoint() {
|
|
5478
|
+
return checkpoint;
|
|
5479
|
+
},
|
|
5480
|
+
saveCheckpoint(next) {
|
|
5481
|
+
if (disposed) return;
|
|
5482
|
+
checkpoint = next;
|
|
5483
|
+
deps.saveCheckpoint?.(next);
|
|
5484
|
+
},
|
|
5214
5485
|
state,
|
|
5215
5486
|
once(key, factory) {
|
|
5216
5487
|
if (onceCache.has(key)) return onceCache.get(key);
|
|
@@ -5225,6 +5496,7 @@ function createInteractionContext(interactionId) {
|
|
|
5225
5496
|
return value;
|
|
5226
5497
|
},
|
|
5227
5498
|
notify() {
|
|
5499
|
+
lastActivityAt = Date.now();
|
|
5228
5500
|
for (const listener of listeners) {
|
|
5229
5501
|
try {
|
|
5230
5502
|
listener();
|
|
@@ -5232,6 +5504,7 @@ function createInteractionContext(interactionId) {
|
|
|
5232
5504
|
logger2.error("InteractionContext subscriber threw during notify()", e);
|
|
5233
5505
|
}
|
|
5234
5506
|
}
|
|
5507
|
+
checkReady();
|
|
5235
5508
|
},
|
|
5236
5509
|
subscribe(listener) {
|
|
5237
5510
|
listeners.add(listener);
|
|
@@ -5245,6 +5518,36 @@ function createInteractionContext(interactionId) {
|
|
|
5245
5518
|
return;
|
|
5246
5519
|
}
|
|
5247
5520
|
disposers.add(fn);
|
|
5521
|
+
},
|
|
5522
|
+
resolve(result) {
|
|
5523
|
+
if (settled || disposed) return;
|
|
5524
|
+
settled = true;
|
|
5525
|
+
deps.resolve?.(result);
|
|
5526
|
+
},
|
|
5527
|
+
exposeDecision(decision) {
|
|
5528
|
+
if (disposed) return;
|
|
5529
|
+
surface = buildDecisionSurface(decision);
|
|
5530
|
+
context.notify();
|
|
5531
|
+
},
|
|
5532
|
+
exposeSurface(spec) {
|
|
5533
|
+
if (disposed) return;
|
|
5534
|
+
surface = buildFullSurface(spec);
|
|
5535
|
+
context.notify();
|
|
5536
|
+
},
|
|
5537
|
+
onSurfaceReady(fn) {
|
|
5538
|
+
if (disposed) return () => void 0;
|
|
5539
|
+
if (surface?.isReady()) {
|
|
5540
|
+
fn();
|
|
5541
|
+
return () => void 0;
|
|
5542
|
+
}
|
|
5543
|
+
readyListeners.add(fn);
|
|
5544
|
+
return () => {
|
|
5545
|
+
readyListeners.delete(fn);
|
|
5546
|
+
};
|
|
5547
|
+
},
|
|
5548
|
+
claimantOwns(action) {
|
|
5549
|
+
if (heldBy === "user" || !surface) return false;
|
|
5550
|
+
return surface.actions().some((a) => a.name === action && a.enabled);
|
|
5248
5551
|
}
|
|
5249
5552
|
};
|
|
5250
5553
|
return {
|
|
@@ -5261,6 +5564,7 @@ function createInteractionContext(interactionId) {
|
|
|
5261
5564
|
}
|
|
5262
5565
|
disposers.clear();
|
|
5263
5566
|
listeners.clear();
|
|
5567
|
+
readyListeners.clear();
|
|
5264
5568
|
onceCache.clear();
|
|
5265
5569
|
},
|
|
5266
5570
|
setHeldBy(holder) {
|
|
@@ -5268,12 +5572,70 @@ function createInteractionContext(interactionId) {
|
|
|
5268
5572
|
heldBy = holder;
|
|
5269
5573
|
context.notify();
|
|
5270
5574
|
return true;
|
|
5575
|
+
},
|
|
5576
|
+
setController(value) {
|
|
5577
|
+
controller = value;
|
|
5578
|
+
},
|
|
5579
|
+
get surface() {
|
|
5580
|
+
return surface;
|
|
5271
5581
|
}
|
|
5272
5582
|
};
|
|
5273
5583
|
}
|
|
5274
5584
|
|
|
5585
|
+
// src/state/interaction-registry.ts
|
|
5586
|
+
var registry2 = /* @__PURE__ */ new Map();
|
|
5587
|
+
function getInteractionRegistration(name) {
|
|
5588
|
+
return registry2.get(name);
|
|
5589
|
+
}
|
|
5590
|
+
|
|
5591
|
+
// src/state/run-steps.ts
|
|
5592
|
+
var recorders = /* @__PURE__ */ new WeakMap();
|
|
5593
|
+
function bindStepRecorder(ctx, record, refused) {
|
|
5594
|
+
recorders.set(ctx, { step: record, refused });
|
|
5595
|
+
}
|
|
5596
|
+
|
|
5275
5597
|
// src/components/chat-driver/interaction-controller.ts
|
|
5276
5598
|
var DEFAULT_DECISION_FRAMING = "Decided on the user's behalf";
|
|
5599
|
+
var DEFAULT_EDIT_FRAMING = "Changed on the user's behalf before the user answered; the user then finished it, confirming the changes";
|
|
5600
|
+
var BUSY = {
|
|
5601
|
+
error: "busy",
|
|
5602
|
+
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."
|
|
5603
|
+
};
|
|
5604
|
+
var isThenable = (value) => value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
5605
|
+
function editOf(by, action, choice, args) {
|
|
5606
|
+
const copy = jsonCopy(args);
|
|
5607
|
+
return { by, action, choice, ...copy !== void 0 && { args: copy } };
|
|
5608
|
+
}
|
|
5609
|
+
function jsonCopy(args) {
|
|
5610
|
+
if (args === void 0 || args === null) return void 0;
|
|
5611
|
+
try {
|
|
5612
|
+
const copy = JSON.parse(JSON.stringify(args));
|
|
5613
|
+
const empty = copy === "" || Array.isArray(copy) && copy.length === 0 || copy !== null && typeof copy === "object" && Object.keys(copy).length === 0;
|
|
5614
|
+
return empty ? void 0 : copy;
|
|
5615
|
+
} catch {
|
|
5616
|
+
return void 0;
|
|
5617
|
+
}
|
|
5618
|
+
}
|
|
5619
|
+
function doneOnUsersBehalf(interaction, answeredBy) {
|
|
5620
|
+
const edits = interaction.edits?.length ? interaction.edits.map((edit) => edit.choice) : void 0;
|
|
5621
|
+
if (answeredBy && answeredBy !== "user") {
|
|
5622
|
+
const recorded = interaction.decision;
|
|
5623
|
+
return {
|
|
5624
|
+
decision: {
|
|
5625
|
+
framing: interaction.claimant?.decisionFraming ?? DEFAULT_DECISION_FRAMING,
|
|
5626
|
+
...edits && { edits },
|
|
5627
|
+
...recorded?.choice !== void 0 && { choice: recorded.choice },
|
|
5628
|
+
...recorded?.shownToUser !== void 0 && { shownToUser: recorded.shownToUser }
|
|
5629
|
+
}
|
|
5630
|
+
};
|
|
5631
|
+
}
|
|
5632
|
+
if (answeredBy === "user" && edits) {
|
|
5633
|
+
return {
|
|
5634
|
+
edited: { framing: interaction.claimant?.editFraming ?? DEFAULT_EDIT_FRAMING, edits }
|
|
5635
|
+
};
|
|
5636
|
+
}
|
|
5637
|
+
return {};
|
|
5638
|
+
}
|
|
5277
5639
|
var STOPPED = {
|
|
5278
5640
|
status: "timeout",
|
|
5279
5641
|
message: "The turn was stopped before this was answered."
|
|
@@ -5297,6 +5659,72 @@ var InteractionController = class {
|
|
|
5297
5659
|
*/
|
|
5298
5660
|
this.interceptors = [];
|
|
5299
5661
|
}
|
|
5662
|
+
/** See `ChatDriver.getInteractionCheckpoint`. */
|
|
5663
|
+
getCheckpoint() {
|
|
5664
|
+
const cp = this.checkpoint;
|
|
5665
|
+
return cp ? { componentName: cp.componentName, value: cp.value } : void 0;
|
|
5666
|
+
}
|
|
5667
|
+
/** See `ChatDriver.restoreInteractionCheckpoint`. Restored means cut: its interaction is gone. */
|
|
5668
|
+
restoreCheckpoint(saved) {
|
|
5669
|
+
this.checkpoint = saved ? { componentName: saved.componentName, value: saved.value, cut: true } : void 0;
|
|
5670
|
+
}
|
|
5671
|
+
/** Drop the checkpoint, used or not, when its flow ends. */
|
|
5672
|
+
clearCheckpoint(why) {
|
|
5673
|
+
const cp = this.checkpoint;
|
|
5674
|
+
if (!cp) return;
|
|
5675
|
+
this.checkpoint = void 0;
|
|
5676
|
+
recordMetaEvent(this.deps.sessionKey, "interaction.checkpoint", {
|
|
5677
|
+
action: "cleared",
|
|
5678
|
+
component: cp.componentName,
|
|
5679
|
+
why,
|
|
5680
|
+
...cp.interactionId ? { interactionId: cp.interactionId } : {}
|
|
5681
|
+
});
|
|
5682
|
+
}
|
|
5683
|
+
/**
|
|
5684
|
+
* Hand a cut checkpoint to a new request of the same widget type, if its registration's
|
|
5685
|
+
* `sameWork` agrees. Only the first such request is asked: a refused checkpoint is dropped, and
|
|
5686
|
+
* the work starts fresh as it would have without one.
|
|
5687
|
+
*/
|
|
5688
|
+
takeOverCheckpoint(componentName, data, interactionId) {
|
|
5689
|
+
const cut = this.checkpoint;
|
|
5690
|
+
if (!cut?.cut || cut.componentName !== componentName) return void 0;
|
|
5691
|
+
this.checkpoint = void 0;
|
|
5692
|
+
const sameWork = getInteractionRegistration(componentName)?.sameWork;
|
|
5693
|
+
let agrees = true;
|
|
5694
|
+
if (sameWork) {
|
|
5695
|
+
try {
|
|
5696
|
+
agrees = sameWork(cut.value, data);
|
|
5697
|
+
} catch (e) {
|
|
5698
|
+
logger2.warn(`InteractionController: "${componentName}" sameWork threw; starting fresh:`, e);
|
|
5699
|
+
agrees = false;
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
recordMetaEvent(this.deps.sessionKey, "interaction.checkpoint", {
|
|
5703
|
+
action: agrees ? "resumed" : "refused",
|
|
5704
|
+
component: componentName,
|
|
5705
|
+
interactionId,
|
|
5706
|
+
...agrees ? {} : { saved: cut.value }
|
|
5707
|
+
});
|
|
5708
|
+
if (!agrees) return void 0;
|
|
5709
|
+
this.checkpoint = { componentName, value: cut.value, interactionId };
|
|
5710
|
+
return cut.value;
|
|
5711
|
+
}
|
|
5712
|
+
/** A controller's `ctx.saveCheckpoint`: kept as JSON, the form it is persisted in. */
|
|
5713
|
+
saveCheckpoint(interactionId, componentName, value) {
|
|
5714
|
+
if (!this.pending.has(interactionId)) return;
|
|
5715
|
+
let copy;
|
|
5716
|
+
try {
|
|
5717
|
+
copy = JSON.parse(JSON.stringify(value) ?? "null");
|
|
5718
|
+
} catch (e) {
|
|
5719
|
+
logger2.warn(
|
|
5720
|
+
`InteractionController: a "${componentName}" checkpoint is not storable as JSON:`,
|
|
5721
|
+
e
|
|
5722
|
+
);
|
|
5723
|
+
return;
|
|
5724
|
+
}
|
|
5725
|
+
this.checkpoint = { componentName, value: copy, interactionId };
|
|
5726
|
+
this.deps.emit(new CustomEvent("checkpoint-saved", { detail: { interactionId } }));
|
|
5727
|
+
}
|
|
5300
5728
|
/** See `ChatDriver.addInteractionInterceptor`. Returns a remover. */
|
|
5301
5729
|
addInterceptor(interceptor) {
|
|
5302
5730
|
if (!interceptor.id || interceptor.id === "user") {
|
|
@@ -5356,6 +5784,10 @@ var InteractionController = class {
|
|
|
5356
5784
|
if (!isClaimant()) return false;
|
|
5357
5785
|
const next = holder === "user" ? "user" : interceptor.id;
|
|
5358
5786
|
if (!handle.setHeldBy(next)) return false;
|
|
5787
|
+
if (next === "user") {
|
|
5788
|
+
const pending = this.pending.get(handle.context.interactionId);
|
|
5789
|
+
if (pending && !pending.running?.finishes) pending.settledBy = void 0;
|
|
5790
|
+
}
|
|
5359
5791
|
recordMetaEvent(this.deps.sessionKey, "interaction.held", {
|
|
5360
5792
|
interactionId: handle.context.interactionId,
|
|
5361
5793
|
heldBy: next,
|
|
@@ -5377,9 +5809,87 @@ var InteractionController = class {
|
|
|
5377
5809
|
if (!pending) return false;
|
|
5378
5810
|
pending.decision = { choice: decision.choice, shownToUser: decision.shownToUser };
|
|
5379
5811
|
return true;
|
|
5812
|
+
},
|
|
5813
|
+
invoke: async (action, args, options) => {
|
|
5814
|
+
const { interactionId } = handle.context;
|
|
5815
|
+
const pending = this.pending.get(interactionId);
|
|
5816
|
+
if (!isClaimant() || !pending || handle.context.heldBy !== interceptor.id) {
|
|
5817
|
+
return {
|
|
5818
|
+
ok: false,
|
|
5819
|
+
error: "not-held",
|
|
5820
|
+
message: pending ? "This interaction is with the person now, so it cannot be acted on." : "This interaction has already ended."
|
|
5821
|
+
};
|
|
5822
|
+
}
|
|
5823
|
+
const { surface } = handle;
|
|
5824
|
+
if (!surface) {
|
|
5825
|
+
return { ok: false, error: "no-surface", message: "Nothing has been exposed here yet." };
|
|
5826
|
+
}
|
|
5827
|
+
if (pending.running) {
|
|
5828
|
+
return { ok: false, ...BUSY, actions: surface.actions() };
|
|
5829
|
+
}
|
|
5830
|
+
const prepared = surface.prepare(action, args);
|
|
5831
|
+
if ("error" in prepared) {
|
|
5832
|
+
return { ok: false, ...prepared, actions: surface.actions() };
|
|
5833
|
+
}
|
|
5834
|
+
const previous = { decision: pending.decision, settledBy: pending.settledBy };
|
|
5835
|
+
const restore = () => {
|
|
5836
|
+
if (!prepared.finishes) return;
|
|
5837
|
+
pending.decision = previous.decision;
|
|
5838
|
+
pending.settledBy = handle.context.heldBy === interceptor.id ? previous.settledBy : void 0;
|
|
5839
|
+
};
|
|
5840
|
+
if (prepared.finishes) {
|
|
5841
|
+
pending.decision = { choice: prepared.choice, shownToUser: options?.note };
|
|
5842
|
+
pending.settledBy = interceptor.id;
|
|
5843
|
+
}
|
|
5844
|
+
const content = options?.note?.trim();
|
|
5845
|
+
const note = content ? { content, delegate: interceptor.id } : void 0;
|
|
5846
|
+
const token = {};
|
|
5847
|
+
pending.running = { token, finishes: prepared.finishes, note };
|
|
5848
|
+
let output;
|
|
5849
|
+
try {
|
|
5850
|
+
const ran = prepared.run();
|
|
5851
|
+
output = isThenable(ran) ? await ran : ran;
|
|
5852
|
+
} catch (e) {
|
|
5853
|
+
logger2.warn(`InteractionController: action "${action}" threw:`, e);
|
|
5854
|
+
if (pending.settledInRun !== token) restore();
|
|
5855
|
+
return {
|
|
5856
|
+
ok: false,
|
|
5857
|
+
error: "threw",
|
|
5858
|
+
message: e instanceof Error ? e.message : String(e)
|
|
5859
|
+
};
|
|
5860
|
+
} finally {
|
|
5861
|
+
if (pending.running?.token === token) pending.running = void 0;
|
|
5862
|
+
}
|
|
5863
|
+
const finished = pending.settledInRun === token;
|
|
5864
|
+
if (!finished) {
|
|
5865
|
+
restore();
|
|
5866
|
+
if (note && this.pending.has(interactionId)) this.appendNote(note);
|
|
5867
|
+
(pending.edits ??= []).push(editOf(interceptor.id, action, prepared.choice, args));
|
|
5868
|
+
}
|
|
5869
|
+
recordMetaEvent(this.deps.sessionKey, "interaction.invoked", {
|
|
5870
|
+
interactionId,
|
|
5871
|
+
claimant: interceptor.id,
|
|
5872
|
+
action,
|
|
5873
|
+
finished
|
|
5874
|
+
});
|
|
5875
|
+
return {
|
|
5876
|
+
ok: true,
|
|
5877
|
+
finished,
|
|
5878
|
+
actions: finished ? [] : surface.actions(),
|
|
5879
|
+
...output !== void 0 && !finished ? { output } : {}
|
|
5880
|
+
};
|
|
5380
5881
|
}
|
|
5381
5882
|
};
|
|
5382
5883
|
}
|
|
5884
|
+
/** A claimant's note about its choice: a delegated, display-only message in the assistant's voice. */
|
|
5885
|
+
appendNote(note) {
|
|
5886
|
+
this.deps.appendToHistory({
|
|
5887
|
+
role: "assistant",
|
|
5888
|
+
content: note.content,
|
|
5889
|
+
delegate: note.delegate,
|
|
5890
|
+
excludeFromProvider: true
|
|
5891
|
+
});
|
|
5892
|
+
}
|
|
5383
5893
|
/** See `ChatDriver.setHostInteractionRequester`. */
|
|
5384
5894
|
setHostRequester(fn, recordOutcome) {
|
|
5385
5895
|
this.hostRequester = fn;
|
|
@@ -5424,8 +5934,36 @@ var InteractionController = class {
|
|
|
5424
5934
|
});
|
|
5425
5935
|
}, timeoutMs) : void 0
|
|
5426
5936
|
});
|
|
5427
|
-
const handle = createInteractionContext(interactionId
|
|
5937
|
+
const handle = createInteractionContext(interactionId, {
|
|
5938
|
+
resolve: (result) => this.resolve(interactionId, result),
|
|
5939
|
+
checkpoint: this.takeOverCheckpoint(componentName, data, interactionId),
|
|
5940
|
+
saveCheckpoint: (value) => this.saveCheckpoint(interactionId, componentName, value)
|
|
5941
|
+
});
|
|
5428
5942
|
this.contexts.set(interactionId, handle);
|
|
5943
|
+
bindStepRecorder(
|
|
5944
|
+
handle.context,
|
|
5945
|
+
(step, outcome) => recordMetaEvent(this.deps.sessionKey, "interaction.step", {
|
|
5946
|
+
interactionId,
|
|
5947
|
+
component: componentName,
|
|
5948
|
+
step,
|
|
5949
|
+
outcome
|
|
5950
|
+
}),
|
|
5951
|
+
(reason, saved) => recordMetaEvent(this.deps.sessionKey, "interaction.checkpoint", {
|
|
5952
|
+
action: "refused",
|
|
5953
|
+
component: componentName,
|
|
5954
|
+
interactionId,
|
|
5955
|
+
reason,
|
|
5956
|
+
saved
|
|
5957
|
+
})
|
|
5958
|
+
);
|
|
5959
|
+
const registration = getInteractionRegistration(componentName);
|
|
5960
|
+
if (registration?.controller) {
|
|
5961
|
+
try {
|
|
5962
|
+
handle.setController(registration.controller(data, handle.context));
|
|
5963
|
+
} catch (e) {
|
|
5964
|
+
logger2.error(`InteractionController: the "${componentName}" controller threw:`, e);
|
|
5965
|
+
}
|
|
5966
|
+
}
|
|
5429
5967
|
const heldBy = this.offer(handle, {
|
|
5430
5968
|
interactionId,
|
|
5431
5969
|
componentName,
|
|
@@ -5502,21 +6040,20 @@ var InteractionController = class {
|
|
|
5502
6040
|
const costUsd = typeof result?.costUsd === "number" && Number.isFinite(result.costUsd) && result.costUsd > 0 ? result.costUsd : void 0;
|
|
5503
6041
|
const unanswered = result?.status === "timeout";
|
|
5504
6042
|
const answeredBy = unanswered ? void 0 : interaction.settledBy ?? "user";
|
|
5505
|
-
const
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
}
|
|
5513
|
-
} : void 0;
|
|
6043
|
+
const run = answeredBy && answeredBy !== "user" ? interaction.running : void 0;
|
|
6044
|
+
if (run) {
|
|
6045
|
+
interaction.settledInRun = run.token;
|
|
6046
|
+
if (run.note) this.appendNote(run.note);
|
|
6047
|
+
}
|
|
6048
|
+
const edits = interaction.edits?.length ? interaction.edits : void 0;
|
|
6049
|
+
const { decision, edited } = doneOnUsersBehalf(interaction, answeredBy);
|
|
5514
6050
|
const patched = this.deps.patchInteractionMessage(interactionId, (message) => ({
|
|
5515
6051
|
...message,
|
|
5516
6052
|
interaction: {
|
|
5517
6053
|
...message.interaction,
|
|
5518
6054
|
resolved: result,
|
|
5519
|
-
...answeredBy ? { answeredBy } : {}
|
|
6055
|
+
...answeredBy ? { answeredBy } : {},
|
|
6056
|
+
...edits ? { edits } : {}
|
|
5520
6057
|
},
|
|
5521
6058
|
...costUsd != null ? { externalCostUsd: (message.externalCostUsd ?? 0) + costUsd } : {}
|
|
5522
6059
|
}));
|
|
@@ -5536,16 +6073,23 @@ var InteractionController = class {
|
|
|
5536
6073
|
}
|
|
5537
6074
|
recordMetaEvent(this.deps.sessionKey, "interaction.resolved", {
|
|
5538
6075
|
interactionId,
|
|
5539
|
-
...answeredBy ? { answeredBy } : {}
|
|
6076
|
+
...answeredBy ? { answeredBy } : {},
|
|
6077
|
+
...edits ? { edits: edits.length } : {}
|
|
5540
6078
|
});
|
|
5541
6079
|
this.deps.activityBus.publish("interaction-resolved", void 0);
|
|
5542
6080
|
try {
|
|
5543
|
-
interaction.onSettled?.({ answeredBy, decision });
|
|
6081
|
+
interaction.onSettled?.({ answeredBy, decision, ...edited ? { edited } : {} });
|
|
5544
6082
|
} catch (e) {
|
|
5545
6083
|
logger2.warn("InteractionController: onSettled threw:", e);
|
|
5546
6084
|
}
|
|
6085
|
+
this.deps.emit(
|
|
6086
|
+
new CustomEvent("interaction-settled", {
|
|
6087
|
+
detail: { interactionId, ...answeredBy ? { answeredBy } : {} }
|
|
6088
|
+
})
|
|
6089
|
+
);
|
|
5547
6090
|
interaction.resolve(result);
|
|
5548
6091
|
this.pending.delete(interactionId);
|
|
6092
|
+
if (this.checkpoint?.interactionId === interactionId) this.checkpoint = void 0;
|
|
5549
6093
|
this.contexts.get(interactionId)?.dispose();
|
|
5550
6094
|
this.contexts.delete(interactionId);
|
|
5551
6095
|
}
|
|
@@ -5942,17 +6486,30 @@ var budgetDetailOf = (e) => {
|
|
|
5942
6486
|
...e.otherVendorAvailable != null ? { otherVendorAvailable: e.otherVendorAvailable } : {}
|
|
5943
6487
|
};
|
|
5944
6488
|
};
|
|
5945
|
-
function withDelegatedDecisions(result, content,
|
|
5946
|
-
|
|
6489
|
+
function withDelegatedDecisions(result, content, done, hasImages) {
|
|
6490
|
+
const { decided, edited } = done;
|
|
6491
|
+
if (decided.length === 0 && edited.length === 0) return content;
|
|
5947
6492
|
if (!hasImages && result !== null && typeof result === "object" && !Array.isArray(result)) {
|
|
5948
|
-
return JSON.stringify({
|
|
6493
|
+
return JSON.stringify({
|
|
6494
|
+
...result,
|
|
6495
|
+
...decided.length ? { decidedOnUsersBehalf: decided } : {},
|
|
6496
|
+
...edited.length ? { editedOnUsersBehalf: edited } : {}
|
|
6497
|
+
});
|
|
5949
6498
|
}
|
|
5950
|
-
return [
|
|
6499
|
+
return [
|
|
6500
|
+
content,
|
|
6501
|
+
...decided.map(describeDelegatedDecision),
|
|
6502
|
+
...edited.map(describeDelegatedEdits)
|
|
6503
|
+
].join("\n\n");
|
|
6504
|
+
}
|
|
6505
|
+
function describeDelegatedEdits(edited) {
|
|
6506
|
+
return `${edited.framing}: ${edited.edits.map((edit) => `"${edit}"`).join(", ")}.`;
|
|
5951
6507
|
}
|
|
5952
6508
|
function describeDelegatedDecision(decision) {
|
|
5953
6509
|
const choice = decision.choice !== void 0 ? `: "${decision.choice}"` : "";
|
|
5954
6510
|
const shown = decision.shownToUser ? ` What the user was shown, as your words: "${decision.shownToUser}"` : " Nothing was shown to the user about it.";
|
|
5955
|
-
|
|
6511
|
+
const edited = decision.edits?.length ? ` Before deciding it changed the widget: ${decision.edits.map((edit) => `"${edit}"`).join(", ")}.` : "";
|
|
6512
|
+
return `${decision.framing}${choice}.${shown}${edited}`;
|
|
5956
6513
|
}
|
|
5957
6514
|
function inFlightToolCallsAt(history) {
|
|
5958
6515
|
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
@@ -6699,6 +7256,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
6699
7256
|
* release, then the active agent.
|
|
6700
7257
|
*/
|
|
6701
7258
|
dispatchAgentTransition(t) {
|
|
7259
|
+
if (t.flowOwnerChangedTo === null) this.interactions.clearCheckpoint("flow-ended");
|
|
6702
7260
|
if (t.flowOwnerChangedTo !== void 0) {
|
|
6703
7261
|
this.dispatchEvent(new CustomEvent("flow-owner-changed", { detail: t.flowOwnerChangedTo }));
|
|
6704
7262
|
}
|
|
@@ -7283,7 +7841,11 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7283
7841
|
// below, taking its per-message cost/token fields with it — so a host total
|
|
7284
7842
|
// re-derived from the transcript would otherwise fall by exactly this much
|
|
7285
7843
|
// on every compaction. Summarising a turn does not refund what it cost.
|
|
7286
|
-
rolledUpUsage: sumUsage(toCompact)
|
|
7844
|
+
rolledUpUsage: sumUsage(toCompact),
|
|
7845
|
+
// The user's own words survive word for word, beside the paraphrase (GENC-1660, C2): this
|
|
7846
|
+
// range's own only. An earlier summary's kept messages are not carried forward; the
|
|
7847
|
+
// summariser read them with it and folded them into this paraphrase.
|
|
7848
|
+
preservedUserMessages: freshUserMessages(toCompact)
|
|
7287
7849
|
}
|
|
7288
7850
|
};
|
|
7289
7851
|
this.loadHistory([summaryMessage, ...tail]);
|
|
@@ -7455,6 +8017,14 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7455
8017
|
getInteractionContext(interactionId) {
|
|
7456
8018
|
return this.interactions.context(interactionId);
|
|
7457
8019
|
}
|
|
8020
|
+
/** {@inheritDoc AiDriver.getInteractionCheckpoint} */
|
|
8021
|
+
getInteractionCheckpoint() {
|
|
8022
|
+
return this.interactions.getCheckpoint();
|
|
8023
|
+
}
|
|
8024
|
+
/** {@inheritDoc AiDriver.restoreInteractionCheckpoint} */
|
|
8025
|
+
restoreInteractionCheckpoint(saved) {
|
|
8026
|
+
this.interactions.restoreCheckpoint(saved);
|
|
8027
|
+
}
|
|
7458
8028
|
/**
|
|
7459
8029
|
* Replace the history message owning `interactionId`. `false` when it is no longer in history.
|
|
7460
8030
|
* Does not announce the change — the interaction paths differ on whether they should.
|
|
@@ -7497,7 +8067,8 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7497
8067
|
role: "user",
|
|
7498
8068
|
content: userInput,
|
|
7499
8069
|
attachments,
|
|
7500
|
-
|
|
8070
|
+
// Who sent it, and what writing it cost when that was not the working agent's own spend.
|
|
8071
|
+
...delegatedFields(options)
|
|
7501
8072
|
});
|
|
7502
8073
|
this.turnStartedAt = Date.now();
|
|
7503
8074
|
recordMetaEvent(this.sessionKey, "turn.start", {
|
|
@@ -7563,7 +8134,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7563
8134
|
* kept on a seven-way fan-out, reporting ~2.3x under the real cost, with the
|
|
7564
8135
|
* error growing as the fan-out widens.
|
|
7565
8136
|
*/
|
|
7566
|
-
buildHandlerContext(activeToolCallId, traceCapture,
|
|
8137
|
+
buildHandlerContext(activeToolCallId, traceCapture, done) {
|
|
7567
8138
|
let opened;
|
|
7568
8139
|
let recorded = false;
|
|
7569
8140
|
const capabilities2 = this.buildCapabilities();
|
|
@@ -7577,9 +8148,11 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7577
8148
|
opened = interactionId;
|
|
7578
8149
|
},
|
|
7579
8150
|
void 0,
|
|
7580
|
-
//
|
|
8151
|
+
// What an interceptor did to an interaction is reported in THIS call's result
|
|
8152
|
+
// (GENC-1660): its answer, or the edits it made before the person answered.
|
|
7581
8153
|
(settled) => {
|
|
7582
|
-
if (settled.decision)
|
|
8154
|
+
if (settled.decision) done?.decided.push(settled.decision);
|
|
8155
|
+
if (settled.edited) done?.edited.push(settled.edited);
|
|
7583
8156
|
}
|
|
7584
8157
|
),
|
|
7585
8158
|
recordInteractionOutcome: (outcome) => {
|
|
@@ -8416,17 +8989,17 @@ ${tailBody}
|
|
|
8416
8989
|
}
|
|
8417
8990
|
const traceCapture = { traces: [] };
|
|
8418
8991
|
const capturedTrace = () => traceCapture.traces.length ? traceCapture.traces.flat() : void 0;
|
|
8419
|
-
const
|
|
8992
|
+
const done = { decided: [], edited: [] };
|
|
8420
8993
|
try {
|
|
8421
8994
|
const result = await handler(
|
|
8422
8995
|
tc.args,
|
|
8423
|
-
this.buildHandlerContext(tc.id, traceCapture,
|
|
8996
|
+
this.buildHandlerContext(tc.id, traceCapture, done)
|
|
8424
8997
|
);
|
|
8425
8998
|
const resultImages = toolResultImages(result);
|
|
8426
8999
|
const content = withDelegatedDecisions(
|
|
8427
9000
|
result,
|
|
8428
9001
|
resultImages ? typeof result.content === "string" ? result.content : JSON.stringify({ ...result, attachments: void 0 }) : typeof result === "string" ? result : JSON.stringify(result),
|
|
8429
|
-
|
|
9002
|
+
done,
|
|
8430
9003
|
!!resultImages
|
|
8431
9004
|
);
|
|
8432
9005
|
executedById.set(tc.id, {
|
|
@@ -8636,8 +9209,10 @@ var AI_DRIVER_EVENT_TYPES = Object.keys({
|
|
|
8636
9209
|
"sub-agent-stop": true,
|
|
8637
9210
|
"interaction-start": true,
|
|
8638
9211
|
"interaction-stop": true,
|
|
9212
|
+
"interaction-settled": true,
|
|
8639
9213
|
"provider-changed": true,
|
|
8640
9214
|
"external-cost-recorded": true,
|
|
9215
|
+
"checkpoint-saved": true,
|
|
8641
9216
|
"agent-changed": true,
|
|
8642
9217
|
"agent-released": true,
|
|
8643
9218
|
"flow-owner-changed": true,
|
|
@@ -8733,6 +9308,12 @@ var BaseDriverDecorator = class _BaseDriverDecorator extends EventTarget {
|
|
|
8733
9308
|
getFlowOwner() {
|
|
8734
9309
|
return this.inner.getFlowOwner?.() ?? null;
|
|
8735
9310
|
}
|
|
9311
|
+
getInteractionCheckpoint() {
|
|
9312
|
+
return this.inner.getInteractionCheckpoint?.();
|
|
9313
|
+
}
|
|
9314
|
+
restoreInteractionCheckpoint(saved) {
|
|
9315
|
+
this.inner.restoreInteractionCheckpoint?.(saved);
|
|
9316
|
+
}
|
|
8736
9317
|
/**
|
|
8737
9318
|
* Forwarded, so an interceptor installed on any layer lands on the driver that opens the
|
|
8738
9319
|
* interactions. A decorator that answers interactions installs its own on `this.inner`.
|
|
@@ -8967,10 +9548,7 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
8967
9548
|
return this.chatDriver.getSuggestions(history, prompt2, count, agentInfo);
|
|
8968
9549
|
}
|
|
8969
9550
|
async sendMessage(input, attachments, options) {
|
|
8970
|
-
const delegated = options
|
|
8971
|
-
delegate: options.delegate,
|
|
8972
|
-
delegateFraming: options.delegateFraming ?? DEFAULT_DELEGATE_FRAMING
|
|
8973
|
-
} : {};
|
|
9551
|
+
const delegated = delegatedFields(options);
|
|
8974
9552
|
try {
|
|
8975
9553
|
return await this.runOrchestratedTurn(input, attachments, options, delegated);
|
|
8976
9554
|
} catch (e) {
|
|
@@ -9103,7 +9681,25 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
9103
9681
|
this.cancelled = false;
|
|
9104
9682
|
await this.installAgent(agent);
|
|
9105
9683
|
if (!resumeNote || agent.resumeOnRestore === false || this.cancelled) return void 0;
|
|
9106
|
-
|
|
9684
|
+
const result = await this.chatDriver.continueFromHistory(void 0, { tailNote: resumeNote });
|
|
9685
|
+
await this.releaseIfRequested();
|
|
9686
|
+
return result;
|
|
9687
|
+
}
|
|
9688
|
+
/**
|
|
9689
|
+
* A continuation outside a routed turn — a decorator carrying the agent on, say — can finish the
|
|
9690
|
+
* flow as surely as a turn can, so a release it requests is acted on here too. Before
|
|
9691
|
+
* (GENC-1660), only `sendMessage` checked, and a flow finished by a continuation stayed locked.
|
|
9692
|
+
*/
|
|
9693
|
+
async continueFromHistory(transientPrimer, options) {
|
|
9694
|
+
const result = await this.chatDriver.continueFromHistory(transientPrimer, options);
|
|
9695
|
+
await this.releaseIfRequested();
|
|
9696
|
+
return result;
|
|
9697
|
+
}
|
|
9698
|
+
/** Release the flow if the turn that just ended asked to, and an agent is still active to release. */
|
|
9699
|
+
async releaseIfRequested() {
|
|
9700
|
+
if (this.chatDriver.getAgentReleaseRequested() && this.agentSession.getActive()) {
|
|
9701
|
+
await this.releaseActiveAgent();
|
|
9702
|
+
}
|
|
9107
9703
|
}
|
|
9108
9704
|
/**
|
|
9109
9705
|
* {@inheritDoc AiDriver.getContextReserve}
|
|
@@ -9566,6 +10162,13 @@ function defineStatefulAgent(opts) {
|
|
|
9566
10162
|
notResumableMessage: opts.notResumableMessage,
|
|
9567
10163
|
resumeOnRestore: opts.resumeOnRestore,
|
|
9568
10164
|
contextReserve: opts.contextReserve,
|
|
10165
|
+
// Bound to the live state, so the autopilot's resolvers see what this agent's do (GENC-1660).
|
|
10166
|
+
...opts.autopilot && {
|
|
10167
|
+
autopilot: {
|
|
10168
|
+
config: opts.autopilot,
|
|
10169
|
+
state: () => state
|
|
10170
|
+
}
|
|
10171
|
+
},
|
|
9569
10172
|
onActivate: async (ctx) => {
|
|
9570
10173
|
state = await opts.init(ctx);
|
|
9571
10174
|
if (opts.toolHandlers) {
|
|
@@ -9606,6 +10209,9 @@ function defineStatefulAgent(opts) {
|
|
|
9606
10209
|
excludeFromClassifier: opts.excludeFromClassifier
|
|
9607
10210
|
};
|
|
9608
10211
|
}
|
|
10212
|
+
function defineAutopilot(config) {
|
|
10213
|
+
return config;
|
|
10214
|
+
}
|
|
9609
10215
|
|
|
9610
10216
|
// src/config/fallback-agents.ts
|
|
9611
10217
|
var friendlyFallbackAgent = {
|
|
@@ -9822,6 +10428,7 @@ export {
|
|
|
9822
10428
|
buildTimelineEntries,
|
|
9823
10429
|
clearSession,
|
|
9824
10430
|
defineAgent,
|
|
10431
|
+
defineAutopilot,
|
|
9825
10432
|
defineStatefulAgent,
|
|
9826
10433
|
emptyUsage,
|
|
9827
10434
|
friendlyFallbackAgent,
|