@genesislcap/ai-assistant 15.61.0 → 15.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-assistant.api.json +18231 -11171
- package/dist/ai-assistant.d.ts +1235 -9
- package/dist/chat-driver.cjs +658 -50
- package/dist/chat-driver.cjs.map +4 -4
- package/dist/chat-driver.mjs +657 -50
- package/dist/chat-driver.mjs.map +4 -4
- package/dist/custom-elements.json +6169 -2127
- package/dist/dts/autopilot/autopilot-driver.d.ts +278 -0
- package/dist/dts/autopilot/autopilot-driver.d.ts.map +1 -0
- package/dist/dts/autopilot/autopilot-session.d.ts +163 -0
- package/dist/dts/autopilot/autopilot-session.d.ts.map +1 -0
- package/dist/dts/autopilot/autopilot-tools.d.ts +15 -0
- package/dist/dts/autopilot/autopilot-tools.d.ts.map +1 -0
- package/dist/dts/autopilot/decision-message.d.ts +62 -0
- package/dist/dts/autopilot/decision-message.d.ts.map +1 -0
- package/dist/dts/autopilot/hand-back/hand-back.controller.d.ts +69 -0
- package/dist/dts/autopilot/hand-back/hand-back.controller.d.ts.map +1 -0
- package/dist/dts/autopilot/hand-back/hand-back.d.ts +22 -0
- package/dist/dts/autopilot/hand-back/hand-back.d.ts.map +1 -0
- package/dist/dts/autopilot/index.d.ts +4 -0
- package/dist/dts/autopilot/index.d.ts.map +1 -0
- package/dist/dts/channel/ai-activity-bus.d.ts.map +1 -1
- package/dist/dts/channel/ai-activity-channel.d.ts +17 -0
- package/dist/dts/channel/ai-activity-channel.d.ts.map +1 -1
- package/dist/dts/channel/autopilot-needs-user.d.ts +8 -0
- package/dist/dts/channel/autopilot-needs-user.d.ts.map +1 -0
- package/dist/dts/components/agent-picker/agent-picker.constants.d.ts +10 -0
- package/dist/dts/components/agent-picker/agent-picker.constants.d.ts.map +1 -1
- package/dist/dts/components/agent-picker/agent-picker.d.ts +4 -0
- package/dist/dts/components/agent-picker/agent-picker.d.ts.map +1 -1
- package/dist/dts/components/agent-picker/agent-picker.template.d.ts.map +1 -1
- package/dist/dts/components/ai-driver/ai-driver.d.ts +34 -1
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/ai-driver/base-driver-decorator.d.ts +3 -1
- package/dist/dts/components/ai-driver/base-driver-decorator.d.ts.map +1 -1
- package/dist/dts/components/chat-bubble/chat-bubble.d.ts +21 -0
- package/dist/dts/components/chat-bubble/chat-bubble.d.ts.map +1 -1
- package/dist/dts/components/chat-bubble/chat-bubble.styles.d.ts.map +1 -1
- package/dist/dts/components/chat-bubble/chat-bubble.template.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +5 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/interaction-controller.d.ts +27 -3
- package/dist/dts/components/chat-driver/interaction-controller.d.ts.map +1 -1
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts +10 -2
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +9 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +183 -0
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/config/define-stateful-agent.d.ts +31 -1
- package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
- package/dist/dts/index.d.ts +7 -3
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +128 -5
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.styles.d.ts.map +1 -1
- package/dist/dts/main/main.template.d.ts +2 -2
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/react.d.ts +10 -0
- package/dist/dts/state/debug-event-log.d.ts +1 -1
- package/dist/dts/state/debug-event-log.d.ts.map +1 -1
- package/dist/dts/state/decision-surface.d.ts +46 -0
- package/dist/dts/state/decision-surface.d.ts.map +1 -0
- package/dist/dts/state/interaction-context.d.ts +21 -1
- package/dist/dts/state/interaction-context.d.ts.map +1 -1
- package/dist/dts/state/interaction-registry.d.ts +63 -0
- package/dist/dts/state/interaction-registry.d.ts.map +1 -0
- package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-snapshot.d.ts +21 -0
- package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
- package/dist/dts/state/run-steps.d.ts +66 -0
- package/dist/dts/state/run-steps.d.ts.map +1 -0
- package/dist/dts/types/ai-chat-widget.d.ts +7 -1
- package/dist/dts/types/ai-chat-widget.d.ts.map +1 -1
- package/dist/dts/types/interaction-context.d.ts +66 -1
- package/dist/dts/types/interaction-context.d.ts.map +1 -1
- package/dist/dts/types/interaction-interceptor.d.ts +60 -2
- package/dist/dts/types/interaction-interceptor.d.ts.map +1 -1
- package/dist/dts/types/interaction-surface.d.ts +195 -0
- package/dist/dts/types/interaction-surface.d.ts.map +1 -0
- package/dist/dts/utils/context-tokens.d.ts +6 -0
- package/dist/dts/utils/context-tokens.d.ts.map +1 -1
- package/dist/dts/utils/delegated-message.d.ts +13 -0
- package/dist/dts/utils/delegated-message.d.ts.map +1 -1
- package/dist/dts/utils/history-transform.d.ts.map +1 -1
- package/dist/dts/utils/preserved-user-messages.d.ts +21 -0
- package/dist/dts/utils/preserved-user-messages.d.ts.map +1 -0
- package/dist/esm/autopilot/autopilot-driver.js +906 -0
- package/dist/esm/autopilot/autopilot-session.js +449 -0
- package/dist/esm/autopilot/autopilot-tools.js +68 -0
- package/dist/esm/autopilot/decision-message.js +206 -0
- package/dist/esm/autopilot/hand-back/hand-back.controller.js +47 -0
- package/dist/esm/autopilot/hand-back/hand-back.js +129 -0
- package/dist/esm/autopilot/index.js +4 -0
- package/dist/esm/channel/ai-activity-bus.js +7 -0
- package/dist/esm/channel/autopilot-needs-user.js +26 -0
- package/dist/esm/components/agent-picker/agent-picker.constants.js +10 -0
- package/dist/esm/components/agent-picker/agent-picker.js +11 -1
- package/dist/esm/components/agent-picker/agent-picker.template.js +2 -4
- package/dist/esm/components/ai-driver/ai-driver.js +2 -0
- package/dist/esm/components/ai-driver/base-driver-decorator.js +8 -0
- package/dist/esm/components/chat-bubble/chat-bubble.js +45 -0
- package/dist/esm/components/chat-bubble/chat-bubble.styles.js +26 -0
- package/dist/esm/components/chat-bubble/chat-bubble.template.js +13 -0
- package/dist/esm/components/chat-driver/chat-driver.js +53 -18
- package/dist/esm/components/chat-driver/interaction-controller.js +297 -12
- package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +16 -2
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +28 -11
- package/dist/esm/config/define-stateful-agent.js +34 -29
- package/dist/esm/index.js +3 -0
- package/dist/esm/main/main.js +334 -15
- package/dist/esm/main/main.styles.js +126 -0
- package/dist/esm/main/main.template.js +116 -11
- package/dist/esm/state/debug-event-log.js +28 -1
- package/dist/esm/state/decision-surface.js +209 -0
- package/dist/esm/state/interaction-context.js +89 -1
- package/dist/esm/state/interaction-registry.js +33 -0
- package/dist/esm/state/persistence/session-persister.js +23 -11
- package/dist/esm/state/persistence/session-snapshot.js +1 -1
- package/dist/esm/state/run-steps.js +77 -0
- package/dist/esm/types/interaction-surface.js +1 -0
- package/dist/esm/utils/context-tokens.js +23 -9
- package/dist/esm/utils/delegated-message.js +15 -0
- package/dist/esm/utils/history-transform.js +18 -7
- package/dist/esm/utils/preserved-user-messages.js +31 -0
- package/dist/react.cjs +7 -0
- package/dist/react.mjs +6 -0
- package/docs/styling.md +1 -1
- package/package.json +17 -17
- package/sandbox/README.md +38 -2
- package/sandbox/autopilot.ts +358 -0
- package/sandbox/controls.ts +24 -1
- package/sandbox/fixtures.ts +2 -0
- package/sandbox/sandbox.ts +188 -1
package/dist/chat-driver.cjs
CHANGED
|
@@ -60,6 +60,7 @@ __export(chat_driver_node_exports, {
|
|
|
60
60
|
buildTimelineEntries: () => buildTimelineEntries,
|
|
61
61
|
clearSession: () => clearSession,
|
|
62
62
|
defineAgent: () => defineAgent,
|
|
63
|
+
defineAutopilot: () => defineAutopilot,
|
|
63
64
|
defineStatefulAgent: () => defineStatefulAgent,
|
|
64
65
|
emptyUsage: () => emptyUsage,
|
|
65
66
|
friendlyFallbackAgent: () => friendlyFallbackAgent,
|
|
@@ -3961,6 +3962,7 @@ var AgenticActivityBus = class {
|
|
|
3961
3962
|
const inBrowser = typeof window !== "undefined" && typeof BroadcastChannel !== "undefined";
|
|
3962
3963
|
if (this.crossTabEvents.size === 0 || !this.channelNameFactory || !inBrowser) return;
|
|
3963
3964
|
this.channel = new BroadcastChannel(this.channelNameFactory());
|
|
3965
|
+
this.channel.unref?.();
|
|
3964
3966
|
this.channel.onmessage = (ev) => {
|
|
3965
3967
|
const { type, detail } = ev.data ?? {};
|
|
3966
3968
|
if (type) this.notifyLocal(type, detail);
|
|
@@ -4037,10 +4039,10 @@ var agenticActivityBus = new AgenticActivityBus({
|
|
|
4037
4039
|
});
|
|
4038
4040
|
|
|
4039
4041
|
// src/config/validate-providers.ts
|
|
4040
|
-
function resolveChatProvider(
|
|
4041
|
-
const provider =
|
|
4042
|
+
function resolveChatProvider(registry3, providerName, agentName) {
|
|
4043
|
+
const provider = registry3.get(providerName);
|
|
4042
4044
|
if (!provider) {
|
|
4043
|
-
const available =
|
|
4045
|
+
const available = registry3.names().join(", ") || "(none)";
|
|
4044
4046
|
throw new Error(
|
|
4045
4047
|
`Agent "${agentName}" references unknown provider "${providerName}". Registered: ${available}`
|
|
4046
4048
|
);
|
|
@@ -4052,13 +4054,13 @@ function resolveChatProvider(registry2, providerName, agentName) {
|
|
|
4052
4054
|
}
|
|
4053
4055
|
return provider;
|
|
4054
4056
|
}
|
|
4055
|
-
function validateStaticAgentProviders(agents,
|
|
4057
|
+
function validateStaticAgentProviders(agents, registry3) {
|
|
4056
4058
|
const visited = /* @__PURE__ */ new Set();
|
|
4057
4059
|
const walk = (agent) => {
|
|
4058
4060
|
if (visited.has(agent)) return;
|
|
4059
4061
|
visited.add(agent);
|
|
4060
4062
|
if (typeof agent.provider === "string") {
|
|
4061
|
-
resolveChatProvider(
|
|
4063
|
+
resolveChatProvider(registry3, agent.provider, agent.name);
|
|
4062
4064
|
}
|
|
4063
4065
|
for (const sub of agent.subAgents ?? []) walk(sub);
|
|
4064
4066
|
};
|
|
@@ -4109,10 +4111,36 @@ var META_EVENT_IMPORTANCE = {
|
|
|
4109
4111
|
"interaction.outcome": "normal",
|
|
4110
4112
|
// An outcome that could not be recorded: the widget goes on showing only the answer.
|
|
4111
4113
|
"interaction.outcome-dropped": "high",
|
|
4114
|
+
// A long action's step starting, finishing, or skipped on resume because it was already done.
|
|
4115
|
+
"interaction.step": "low",
|
|
4116
|
+
// A cut checkpoint taken over after a reload, refused (by sameWork, or as malformed), or dropped
|
|
4117
|
+
// at flow end.
|
|
4118
|
+
"interaction.checkpoint": "normal",
|
|
4112
4119
|
// A claimant handing a pending interaction to the person, or taking it back.
|
|
4113
4120
|
"interaction.held": "normal",
|
|
4114
4121
|
// A claim on a `userOnly` interaction, refused. Expected when an interceptor claims broadly.
|
|
4115
4122
|
"interaction.claim-refused": "low",
|
|
4123
|
+
// A claimant ran one of the surface's actions: what it did, and whether that settled it.
|
|
4124
|
+
"interaction.invoked": "normal",
|
|
4125
|
+
"autopilot.claimed": "normal",
|
|
4126
|
+
// One per interaction the autopilot does not take, so frequent and mostly expected.
|
|
4127
|
+
"autopilot.declined": "low",
|
|
4128
|
+
"autopilot.decision-sent": "low",
|
|
4129
|
+
"autopilot.acted": "normal",
|
|
4130
|
+
"autopilot.handed-back": "normal",
|
|
4131
|
+
"autopilot.paused": "normal",
|
|
4132
|
+
"autopilot.resumed": "normal",
|
|
4133
|
+
// The wrapper itself failed: the journey stops being automatic without anyone choosing that.
|
|
4134
|
+
"autopilot.error": "high",
|
|
4135
|
+
// A written answer counted against the step's limit; reaching it withdraws `reply`.
|
|
4136
|
+
"autopilot.refinement": "normal",
|
|
4137
|
+
// The autopilot showed its own widget because a working turn ended needing the person.
|
|
4138
|
+
"autopilot.handback-presented": "normal",
|
|
4139
|
+
// The autopilot was given the whole visible transcript: its first decision, a rebuild, or after
|
|
4140
|
+
// a compaction removed the last message it saw.
|
|
4141
|
+
"autopilot.catch-up": "normal",
|
|
4142
|
+
// Autopilot spend that reached no message, so it is missing from the session total.
|
|
4143
|
+
"autopilot.cost-dropped": "high",
|
|
4116
4144
|
"external-diagnostics.folded": "normal",
|
|
4117
4145
|
"session.cleared": "normal",
|
|
4118
4146
|
"session.switched": "low",
|
|
@@ -4191,6 +4219,45 @@ function clearSession(key) {
|
|
|
4191
4219
|
registry.delete(key);
|
|
4192
4220
|
}
|
|
4193
4221
|
|
|
4222
|
+
// src/utils/delegated-message.ts
|
|
4223
|
+
var DEFAULT_DELEGATE_FRAMING = "Sent on the user's behalf.";
|
|
4224
|
+
function delegatedFields(options) {
|
|
4225
|
+
if (!options?.delegate) return {};
|
|
4226
|
+
const cost = options.externalCostUsd;
|
|
4227
|
+
return {
|
|
4228
|
+
delegate: options.delegate,
|
|
4229
|
+
delegateFraming: options.delegateFraming ?? DEFAULT_DELEGATE_FRAMING,
|
|
4230
|
+
...typeof cost === "number" && Number.isFinite(cost) && cost > 0 ? { externalCostUsd: cost } : {}
|
|
4231
|
+
};
|
|
4232
|
+
}
|
|
4233
|
+
function frameDelegatedContent(content, framing) {
|
|
4234
|
+
return `(${framing ?? DEFAULT_DELEGATE_FRAMING})
|
|
4235
|
+
|
|
4236
|
+
${content}`;
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
// src/utils/preserved-user-messages.ts
|
|
4240
|
+
function freshUserMessages(toCompact) {
|
|
4241
|
+
return toCompact.flatMap(
|
|
4242
|
+
(m) => m.role === "user" && m.content?.trim() ? [
|
|
4243
|
+
{
|
|
4244
|
+
content: m.content,
|
|
4245
|
+
...m.timestamp ? { timestamp: m.timestamp } : {},
|
|
4246
|
+
...m.delegate ? { delegate: m.delegate, delegateFraming: m.delegateFraming } : {}
|
|
4247
|
+
}
|
|
4248
|
+
] : []
|
|
4249
|
+
);
|
|
4250
|
+
}
|
|
4251
|
+
var preservedUserLine = (u) => `User: ${u.delegate ? frameDelegatedContent(u.content, u.delegateFraming) : u.content}`;
|
|
4252
|
+
function verbatimUserMessagesBlock(preserved) {
|
|
4253
|
+
if (!preserved?.length) return "";
|
|
4254
|
+
return `
|
|
4255
|
+
|
|
4256
|
+
<user_messages_verbatim>
|
|
4257
|
+
${preserved.map(preservedUserLine).join("\n")}
|
|
4258
|
+
</user_messages_verbatim>`;
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4194
4261
|
// src/utils/context-tokens.ts
|
|
4195
4262
|
var APPROX_CHARS_PER_TOKEN = 4;
|
|
4196
4263
|
var IMAGE_TOKEN_ESTIMATE = 1600;
|
|
@@ -4224,7 +4291,7 @@ function imageCount(m) {
|
|
|
4224
4291
|
}
|
|
4225
4292
|
var TEXT_ATTACHMENT_FRAMING_CHARS = 9;
|
|
4226
4293
|
function wireChars(m) {
|
|
4227
|
-
let chars = m.content?.length ?? 0;
|
|
4294
|
+
let chars = (m.content?.length ?? 0) + verbatimUserMessagesBlock(m.compaction?.preservedUserMessages).length;
|
|
4228
4295
|
for (const attachment of m.attachments ?? []) {
|
|
4229
4296
|
if (attachment.kind === "image") continue;
|
|
4230
4297
|
chars += TEXT_ATTACHMENT_FRAMING_CHARS + attachment.name.length + (attachment.content?.length ?? 0);
|
|
@@ -4313,8 +4380,11 @@ function projectCompaction(history, cut, summaryTokens = COMPACTION_SUMMARY_TOKE
|
|
|
4313
4380
|
const { estimates, overhead, total } = calibrate(history, preambleTokens);
|
|
4314
4381
|
let tail = 0;
|
|
4315
4382
|
for (let i = cut; i < estimates.length; i += 1) tail += estimates[i];
|
|
4383
|
+
const kept = Math.ceil(
|
|
4384
|
+
verbatimUserMessagesBlock(freshUserMessages(history.slice(0, cut))).length / APPROX_CHARS_PER_TOKEN
|
|
4385
|
+
);
|
|
4316
4386
|
const tokensBefore = total;
|
|
4317
|
-
const tokensAfter = overhead + summaryTokens + tail;
|
|
4387
|
+
const tokensAfter = overhead + summaryTokens + kept + tail;
|
|
4318
4388
|
return {
|
|
4319
4389
|
cut,
|
|
4320
4390
|
compactedCount: cut,
|
|
@@ -4324,14 +4394,6 @@ function projectCompaction(history, cut, summaryTokens = COMPACTION_SUMMARY_TOKE
|
|
|
4324
4394
|
};
|
|
4325
4395
|
}
|
|
4326
4396
|
|
|
4327
|
-
// src/utils/delegated-message.ts
|
|
4328
|
-
var DEFAULT_DELEGATE_FRAMING = "Sent on the user's behalf.";
|
|
4329
|
-
function frameDelegatedContent(content, framing) {
|
|
4330
|
-
return `(${framing ?? DEFAULT_DELEGATE_FRAMING})
|
|
4331
|
-
|
|
4332
|
-
${content}`;
|
|
4333
|
-
}
|
|
4334
|
-
|
|
4335
4397
|
// src/utils/logger.ts
|
|
4336
4398
|
var logger2 = createLogger("ai-assistant");
|
|
4337
4399
|
|
|
@@ -4773,7 +4835,12 @@ function planCompaction(history, options = {}) {
|
|
|
4773
4835
|
function renderMessageForSummary(m) {
|
|
4774
4836
|
switch (m.role) {
|
|
4775
4837
|
case "compacted-summary":
|
|
4776
|
-
return
|
|
4838
|
+
return [
|
|
4839
|
+
`[Earlier summary] ${m.content}`,
|
|
4840
|
+
...(m.compaction?.preservedUserMessages ?? []).map(
|
|
4841
|
+
(u) => `[Kept word for word with it] ${preservedUserLine(u)}`
|
|
4842
|
+
)
|
|
4843
|
+
].join("\n");
|
|
4777
4844
|
case "user":
|
|
4778
4845
|
if (!m.content) return "";
|
|
4779
4846
|
return `User: ${m.delegate ? frameDelegatedContent(m.content, m.delegateFraming) : m.content}`;
|
|
@@ -4812,14 +4879,16 @@ function normalizeForProvider(history) {
|
|
|
4812
4879
|
return { ...m, content: frameDelegatedContent(m.content ?? "", m.delegateFraming) };
|
|
4813
4880
|
}
|
|
4814
4881
|
if (m.role !== "compacted-summary") return m;
|
|
4882
|
+
const preserved = m.compaction?.preservedUserMessages ?? [];
|
|
4883
|
+
const verbatim = verbatimUserMessagesBlock(preserved);
|
|
4815
4884
|
return {
|
|
4816
4885
|
...m,
|
|
4817
4886
|
role: "user",
|
|
4818
4887
|
content: `<conversation_summary>
|
|
4819
4888
|
${m.content}
|
|
4820
|
-
</conversation_summary
|
|
4889
|
+
</conversation_summary>${verbatim}
|
|
4821
4890
|
|
|
4822
|
-
The above summarizes earlier parts of this conversation that were compacted. Continue naturally from it
|
|
4891
|
+
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.",
|
|
4823
4892
|
compaction: void 0
|
|
4824
4893
|
};
|
|
4825
4894
|
});
|
|
@@ -5280,19 +5349,222 @@ var FoldController = class {
|
|
|
5280
5349
|
}
|
|
5281
5350
|
};
|
|
5282
5351
|
|
|
5352
|
+
// src/state/decision-surface.ts
|
|
5353
|
+
var SELECT_ACTION = "select";
|
|
5354
|
+
var REPLY_ACTION = "reply";
|
|
5355
|
+
var normalise = (option) => typeof option === "string" ? { id: option, label: option } : { id: option.id ?? option.label, label: option.label, enabledWhen: option.enabledWhen };
|
|
5356
|
+
var isEnabled = (option) => {
|
|
5357
|
+
try {
|
|
5358
|
+
return option.enabledWhen?.() ?? true;
|
|
5359
|
+
} catch {
|
|
5360
|
+
return false;
|
|
5361
|
+
}
|
|
5362
|
+
};
|
|
5363
|
+
function buildDecisionSurface(spec) {
|
|
5364
|
+
const options = spec.options.map(normalise);
|
|
5365
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5366
|
+
for (const { id } of options) {
|
|
5367
|
+
if (ids.has(id)) throw new Error(`exposeDecision: two options share the id "${id}".`);
|
|
5368
|
+
if (spec.multi && id === SELECT_ACTION || spec.freeText && id === REPLY_ACTION) {
|
|
5369
|
+
throw new Error(`exposeDecision: an option cannot use the reserved id "${id}".`);
|
|
5370
|
+
}
|
|
5371
|
+
ids.add(id);
|
|
5372
|
+
}
|
|
5373
|
+
const byIdOrLabel = (key) => typeof key === "string" ? options.find((o) => o.id === key || o.label === key) : void 0;
|
|
5374
|
+
const actions = () => {
|
|
5375
|
+
const enabled = options.filter(isEnabled);
|
|
5376
|
+
return [
|
|
5377
|
+
...options.map((o) => ({
|
|
5378
|
+
name: o.id,
|
|
5379
|
+
label: o.label,
|
|
5380
|
+
finishes: true,
|
|
5381
|
+
enabled: isEnabled(o)
|
|
5382
|
+
})),
|
|
5383
|
+
...spec.multi ? [
|
|
5384
|
+
{
|
|
5385
|
+
name: SELECT_ACTION,
|
|
5386
|
+
finishes: true,
|
|
5387
|
+
enabled: enabled.length > 0,
|
|
5388
|
+
args: "options"
|
|
5389
|
+
}
|
|
5390
|
+
] : [],
|
|
5391
|
+
...spec.freeText ? [{ name: REPLY_ACTION, finishes: true, enabled: true, args: "text" }] : []
|
|
5392
|
+
];
|
|
5393
|
+
};
|
|
5394
|
+
const answer = (a) => () => spec.choose(a);
|
|
5395
|
+
return {
|
|
5396
|
+
prompt: spec.prompt,
|
|
5397
|
+
facts: () => spec.facts?.() ?? {},
|
|
5398
|
+
actions,
|
|
5399
|
+
isReady: () => actions().some((a) => a.finishes && a.enabled),
|
|
5400
|
+
prepare(name, args) {
|
|
5401
|
+
if (spec.multi && name === SELECT_ACTION) {
|
|
5402
|
+
const picked = args?.options;
|
|
5403
|
+
if (!Array.isArray(picked) || picked.length === 0) {
|
|
5404
|
+
return {
|
|
5405
|
+
error: "invalid-args",
|
|
5406
|
+
message: "`select` takes { options: string[] }, not empty."
|
|
5407
|
+
};
|
|
5408
|
+
}
|
|
5409
|
+
const chosen = picked.map(byIdOrLabel);
|
|
5410
|
+
const unknown = picked.filter((_, i) => !chosen[i]);
|
|
5411
|
+
if (unknown.length) {
|
|
5412
|
+
return { error: "invalid-args", message: `Not options here: ${unknown.join(", ")}.` };
|
|
5413
|
+
}
|
|
5414
|
+
const disabled = chosen.filter((o) => !isEnabled(o));
|
|
5415
|
+
if (disabled.length) {
|
|
5416
|
+
return {
|
|
5417
|
+
error: "disabled",
|
|
5418
|
+
message: `Not available right now: ${disabled.map((o) => o.label).join(", ")}.`
|
|
5419
|
+
};
|
|
5420
|
+
}
|
|
5421
|
+
const picks = chosen;
|
|
5422
|
+
return {
|
|
5423
|
+
choice: picks.map((o) => o.label).join(", "),
|
|
5424
|
+
finishes: true,
|
|
5425
|
+
run: answer({ kind: "options", options: picks.map((o) => o.id) })
|
|
5426
|
+
};
|
|
5427
|
+
}
|
|
5428
|
+
if (spec.freeText && name === REPLY_ACTION) {
|
|
5429
|
+
const text = args?.text;
|
|
5430
|
+
if (typeof text !== "string" || !text.trim()) {
|
|
5431
|
+
return { error: "invalid-args", message: "`reply` takes { text: string }, not empty." };
|
|
5432
|
+
}
|
|
5433
|
+
return { choice: text, finishes: true, run: answer({ kind: "text", text }) };
|
|
5434
|
+
}
|
|
5435
|
+
const option = options.find((o) => o.id === name);
|
|
5436
|
+
if (!option) {
|
|
5437
|
+
return { error: "unknown-action", message: `No action "${name}" here.` };
|
|
5438
|
+
}
|
|
5439
|
+
if (!isEnabled(option)) {
|
|
5440
|
+
return { error: "disabled", message: `"${option.label}" is not available right now.` };
|
|
5441
|
+
}
|
|
5442
|
+
return {
|
|
5443
|
+
choice: option.label,
|
|
5444
|
+
finishes: true,
|
|
5445
|
+
run: answer({ kind: "option", option: option.id })
|
|
5446
|
+
};
|
|
5447
|
+
}
|
|
5448
|
+
};
|
|
5449
|
+
}
|
|
5450
|
+
var CHOICE_CHARS = 300;
|
|
5451
|
+
var matchesType = (value, type) => {
|
|
5452
|
+
switch (type) {
|
|
5453
|
+
case "string":
|
|
5454
|
+
return typeof value === "string";
|
|
5455
|
+
case "number":
|
|
5456
|
+
case "integer":
|
|
5457
|
+
return typeof value === "number" && (type === "number" || Number.isInteger(value));
|
|
5458
|
+
case "boolean":
|
|
5459
|
+
return typeof value === "boolean";
|
|
5460
|
+
case "array":
|
|
5461
|
+
return Array.isArray(value);
|
|
5462
|
+
case "object":
|
|
5463
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5464
|
+
default:
|
|
5465
|
+
return true;
|
|
5466
|
+
}
|
|
5467
|
+
};
|
|
5468
|
+
function checkArgs(args, schema) {
|
|
5469
|
+
if (!schema) return void 0;
|
|
5470
|
+
if (!matchesType(args, "object")) return "its arguments must be an object.";
|
|
5471
|
+
const record = args;
|
|
5472
|
+
const required = schema.required ?? [];
|
|
5473
|
+
const missing = required.filter((key) => record[key] === void 0);
|
|
5474
|
+
if (missing.length) return `missing: ${missing.join(", ")}.`;
|
|
5475
|
+
const properties = schema.properties ?? {};
|
|
5476
|
+
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)}`);
|
|
5477
|
+
return wrong.length ? `${wrong.join("; ")}.` : void 0;
|
|
5478
|
+
}
|
|
5479
|
+
function buildFullSurface(spec) {
|
|
5480
|
+
const guard = (enabledWhen) => {
|
|
5481
|
+
try {
|
|
5482
|
+
return enabledWhen?.() ?? true;
|
|
5483
|
+
} catch {
|
|
5484
|
+
return false;
|
|
5485
|
+
}
|
|
5486
|
+
};
|
|
5487
|
+
const actions = () => Object.entries(spec.actions).map(([name, a]) => ({
|
|
5488
|
+
name,
|
|
5489
|
+
finishes: !!a.finishes,
|
|
5490
|
+
enabled: guard(a.enabledWhen),
|
|
5491
|
+
description: a.description,
|
|
5492
|
+
...a.argsSchema ? { args: "fields", argsSchema: a.argsSchema } : {}
|
|
5493
|
+
}));
|
|
5494
|
+
return {
|
|
5495
|
+
prompt: spec.prompt,
|
|
5496
|
+
facts: () => ({}),
|
|
5497
|
+
describe: () => spec.describe(),
|
|
5498
|
+
views: spec.views,
|
|
5499
|
+
read: (view) => spec.read(view),
|
|
5500
|
+
actions,
|
|
5501
|
+
isReady: () => actions().some((a) => a.enabled),
|
|
5502
|
+
prepare(name, args) {
|
|
5503
|
+
const action = Object.prototype.hasOwnProperty.call(spec.actions, name) ? spec.actions[name] : void 0;
|
|
5504
|
+
if (!action) return { error: "unknown-action", message: `No action "${name}" here.` };
|
|
5505
|
+
if (!guard(action.enabledWhen)) {
|
|
5506
|
+
return { error: "disabled", message: `"${name}" is not available right now.` };
|
|
5507
|
+
}
|
|
5508
|
+
const problem = checkArgs(args ?? {}, action.argsSchema);
|
|
5509
|
+
if (problem) return { error: "invalid-args", message: `"${name}": ${problem}` };
|
|
5510
|
+
const shown = args && Object.keys(args).length ? ` ${JSON.stringify(args)}` : "";
|
|
5511
|
+
return {
|
|
5512
|
+
choice: `${name}${shown}`.slice(0, CHOICE_CHARS),
|
|
5513
|
+
finishes: !!action.finishes,
|
|
5514
|
+
run: () => action.run(args ?? {})
|
|
5515
|
+
};
|
|
5516
|
+
}
|
|
5517
|
+
};
|
|
5518
|
+
}
|
|
5519
|
+
|
|
5283
5520
|
// src/state/interaction-context.ts
|
|
5284
|
-
function createInteractionContext(interactionId) {
|
|
5521
|
+
function createInteractionContext(interactionId, deps = {}) {
|
|
5285
5522
|
const state = {};
|
|
5286
5523
|
const onceCache = /* @__PURE__ */ new Map();
|
|
5287
5524
|
const listeners = /* @__PURE__ */ new Set();
|
|
5288
5525
|
const disposers = /* @__PURE__ */ new Set();
|
|
5526
|
+
const readyListeners = /* @__PURE__ */ new Set();
|
|
5289
5527
|
let disposed = false;
|
|
5528
|
+
let settled = false;
|
|
5290
5529
|
let heldBy = "user";
|
|
5530
|
+
let controller;
|
|
5531
|
+
let surface;
|
|
5532
|
+
let lastActivityAt = Date.now();
|
|
5533
|
+
let checkpoint = deps.checkpoint;
|
|
5534
|
+
const checkReady = () => {
|
|
5535
|
+
if (disposed || !surface || readyListeners.size === 0 || !surface.isReady()) return;
|
|
5536
|
+
const waiting = [...readyListeners];
|
|
5537
|
+
readyListeners.clear();
|
|
5538
|
+
for (const fn of waiting) {
|
|
5539
|
+
try {
|
|
5540
|
+
fn();
|
|
5541
|
+
} catch (e) {
|
|
5542
|
+
logger2.error("InteractionContext onSurfaceReady callback threw", e);
|
|
5543
|
+
}
|
|
5544
|
+
}
|
|
5545
|
+
};
|
|
5291
5546
|
const context = {
|
|
5292
5547
|
interactionId,
|
|
5293
5548
|
get heldBy() {
|
|
5294
5549
|
return heldBy;
|
|
5295
5550
|
},
|
|
5551
|
+
get controller() {
|
|
5552
|
+
return controller;
|
|
5553
|
+
},
|
|
5554
|
+
get surface() {
|
|
5555
|
+
return surface;
|
|
5556
|
+
},
|
|
5557
|
+
get lastActivityAt() {
|
|
5558
|
+
return lastActivityAt;
|
|
5559
|
+
},
|
|
5560
|
+
get checkpoint() {
|
|
5561
|
+
return checkpoint;
|
|
5562
|
+
},
|
|
5563
|
+
saveCheckpoint(next) {
|
|
5564
|
+
if (disposed) return;
|
|
5565
|
+
checkpoint = next;
|
|
5566
|
+
deps.saveCheckpoint?.(next);
|
|
5567
|
+
},
|
|
5296
5568
|
state,
|
|
5297
5569
|
once(key, factory) {
|
|
5298
5570
|
if (onceCache.has(key)) return onceCache.get(key);
|
|
@@ -5307,6 +5579,7 @@ function createInteractionContext(interactionId) {
|
|
|
5307
5579
|
return value;
|
|
5308
5580
|
},
|
|
5309
5581
|
notify() {
|
|
5582
|
+
lastActivityAt = Date.now();
|
|
5310
5583
|
for (const listener of listeners) {
|
|
5311
5584
|
try {
|
|
5312
5585
|
listener();
|
|
@@ -5314,6 +5587,7 @@ function createInteractionContext(interactionId) {
|
|
|
5314
5587
|
logger2.error("InteractionContext subscriber threw during notify()", e);
|
|
5315
5588
|
}
|
|
5316
5589
|
}
|
|
5590
|
+
checkReady();
|
|
5317
5591
|
},
|
|
5318
5592
|
subscribe(listener) {
|
|
5319
5593
|
listeners.add(listener);
|
|
@@ -5327,6 +5601,36 @@ function createInteractionContext(interactionId) {
|
|
|
5327
5601
|
return;
|
|
5328
5602
|
}
|
|
5329
5603
|
disposers.add(fn);
|
|
5604
|
+
},
|
|
5605
|
+
resolve(result) {
|
|
5606
|
+
if (settled || disposed) return;
|
|
5607
|
+
settled = true;
|
|
5608
|
+
deps.resolve?.(result);
|
|
5609
|
+
},
|
|
5610
|
+
exposeDecision(decision) {
|
|
5611
|
+
if (disposed) return;
|
|
5612
|
+
surface = buildDecisionSurface(decision);
|
|
5613
|
+
context.notify();
|
|
5614
|
+
},
|
|
5615
|
+
exposeSurface(spec) {
|
|
5616
|
+
if (disposed) return;
|
|
5617
|
+
surface = buildFullSurface(spec);
|
|
5618
|
+
context.notify();
|
|
5619
|
+
},
|
|
5620
|
+
onSurfaceReady(fn) {
|
|
5621
|
+
if (disposed) return () => void 0;
|
|
5622
|
+
if (surface?.isReady()) {
|
|
5623
|
+
fn();
|
|
5624
|
+
return () => void 0;
|
|
5625
|
+
}
|
|
5626
|
+
readyListeners.add(fn);
|
|
5627
|
+
return () => {
|
|
5628
|
+
readyListeners.delete(fn);
|
|
5629
|
+
};
|
|
5630
|
+
},
|
|
5631
|
+
claimantOwns(action) {
|
|
5632
|
+
if (heldBy === "user" || !surface) return false;
|
|
5633
|
+
return surface.actions().some((a) => a.name === action && a.enabled);
|
|
5330
5634
|
}
|
|
5331
5635
|
};
|
|
5332
5636
|
return {
|
|
@@ -5343,6 +5647,7 @@ function createInteractionContext(interactionId) {
|
|
|
5343
5647
|
}
|
|
5344
5648
|
disposers.clear();
|
|
5345
5649
|
listeners.clear();
|
|
5650
|
+
readyListeners.clear();
|
|
5346
5651
|
onceCache.clear();
|
|
5347
5652
|
},
|
|
5348
5653
|
setHeldBy(holder) {
|
|
@@ -5350,12 +5655,70 @@ function createInteractionContext(interactionId) {
|
|
|
5350
5655
|
heldBy = holder;
|
|
5351
5656
|
context.notify();
|
|
5352
5657
|
return true;
|
|
5658
|
+
},
|
|
5659
|
+
setController(value) {
|
|
5660
|
+
controller = value;
|
|
5661
|
+
},
|
|
5662
|
+
get surface() {
|
|
5663
|
+
return surface;
|
|
5353
5664
|
}
|
|
5354
5665
|
};
|
|
5355
5666
|
}
|
|
5356
5667
|
|
|
5668
|
+
// src/state/interaction-registry.ts
|
|
5669
|
+
var registry2 = /* @__PURE__ */ new Map();
|
|
5670
|
+
function getInteractionRegistration(name) {
|
|
5671
|
+
return registry2.get(name);
|
|
5672
|
+
}
|
|
5673
|
+
|
|
5674
|
+
// src/state/run-steps.ts
|
|
5675
|
+
var recorders = /* @__PURE__ */ new WeakMap();
|
|
5676
|
+
function bindStepRecorder(ctx, record, refused) {
|
|
5677
|
+
recorders.set(ctx, { step: record, refused });
|
|
5678
|
+
}
|
|
5679
|
+
|
|
5357
5680
|
// src/components/chat-driver/interaction-controller.ts
|
|
5358
5681
|
var DEFAULT_DECISION_FRAMING = "Decided on the user's behalf";
|
|
5682
|
+
var DEFAULT_EDIT_FRAMING = "Changed on the user's behalf before the user answered; the user then finished it, confirming the changes";
|
|
5683
|
+
var BUSY = {
|
|
5684
|
+
error: "busy",
|
|
5685
|
+
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."
|
|
5686
|
+
};
|
|
5687
|
+
var isThenable = (value) => value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
5688
|
+
function editOf(by, action, choice, args) {
|
|
5689
|
+
const copy = jsonCopy(args);
|
|
5690
|
+
return { by, action, choice, ...copy !== void 0 && { args: copy } };
|
|
5691
|
+
}
|
|
5692
|
+
function jsonCopy(args) {
|
|
5693
|
+
if (args === void 0 || args === null) return void 0;
|
|
5694
|
+
try {
|
|
5695
|
+
const copy = JSON.parse(JSON.stringify(args));
|
|
5696
|
+
const empty = copy === "" || Array.isArray(copy) && copy.length === 0 || copy !== null && typeof copy === "object" && Object.keys(copy).length === 0;
|
|
5697
|
+
return empty ? void 0 : copy;
|
|
5698
|
+
} catch {
|
|
5699
|
+
return void 0;
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
function doneOnUsersBehalf(interaction, answeredBy) {
|
|
5703
|
+
const edits = interaction.edits?.length ? interaction.edits.map((edit) => edit.choice) : void 0;
|
|
5704
|
+
if (answeredBy && answeredBy !== "user") {
|
|
5705
|
+
const recorded = interaction.decision;
|
|
5706
|
+
return {
|
|
5707
|
+
decision: {
|
|
5708
|
+
framing: interaction.claimant?.decisionFraming ?? DEFAULT_DECISION_FRAMING,
|
|
5709
|
+
...edits && { edits },
|
|
5710
|
+
...recorded?.choice !== void 0 && { choice: recorded.choice },
|
|
5711
|
+
...recorded?.shownToUser !== void 0 && { shownToUser: recorded.shownToUser }
|
|
5712
|
+
}
|
|
5713
|
+
};
|
|
5714
|
+
}
|
|
5715
|
+
if (answeredBy === "user" && edits) {
|
|
5716
|
+
return {
|
|
5717
|
+
edited: { framing: interaction.claimant?.editFraming ?? DEFAULT_EDIT_FRAMING, edits }
|
|
5718
|
+
};
|
|
5719
|
+
}
|
|
5720
|
+
return {};
|
|
5721
|
+
}
|
|
5359
5722
|
var STOPPED = {
|
|
5360
5723
|
status: "timeout",
|
|
5361
5724
|
message: "The turn was stopped before this was answered."
|
|
@@ -5379,6 +5742,72 @@ var InteractionController = class {
|
|
|
5379
5742
|
*/
|
|
5380
5743
|
this.interceptors = [];
|
|
5381
5744
|
}
|
|
5745
|
+
/** See `ChatDriver.getInteractionCheckpoint`. */
|
|
5746
|
+
getCheckpoint() {
|
|
5747
|
+
const cp = this.checkpoint;
|
|
5748
|
+
return cp ? { componentName: cp.componentName, value: cp.value } : void 0;
|
|
5749
|
+
}
|
|
5750
|
+
/** See `ChatDriver.restoreInteractionCheckpoint`. Restored means cut: its interaction is gone. */
|
|
5751
|
+
restoreCheckpoint(saved) {
|
|
5752
|
+
this.checkpoint = saved ? { componentName: saved.componentName, value: saved.value, cut: true } : void 0;
|
|
5753
|
+
}
|
|
5754
|
+
/** Drop the checkpoint, used or not, when its flow ends. */
|
|
5755
|
+
clearCheckpoint(why) {
|
|
5756
|
+
const cp = this.checkpoint;
|
|
5757
|
+
if (!cp) return;
|
|
5758
|
+
this.checkpoint = void 0;
|
|
5759
|
+
recordMetaEvent(this.deps.sessionKey, "interaction.checkpoint", {
|
|
5760
|
+
action: "cleared",
|
|
5761
|
+
component: cp.componentName,
|
|
5762
|
+
why,
|
|
5763
|
+
...cp.interactionId ? { interactionId: cp.interactionId } : {}
|
|
5764
|
+
});
|
|
5765
|
+
}
|
|
5766
|
+
/**
|
|
5767
|
+
* Hand a cut checkpoint to a new request of the same widget type, if its registration's
|
|
5768
|
+
* `sameWork` agrees. Only the first such request is asked: a refused checkpoint is dropped, and
|
|
5769
|
+
* the work starts fresh as it would have without one.
|
|
5770
|
+
*/
|
|
5771
|
+
takeOverCheckpoint(componentName, data, interactionId) {
|
|
5772
|
+
const cut = this.checkpoint;
|
|
5773
|
+
if (!cut?.cut || cut.componentName !== componentName) return void 0;
|
|
5774
|
+
this.checkpoint = void 0;
|
|
5775
|
+
const sameWork = getInteractionRegistration(componentName)?.sameWork;
|
|
5776
|
+
let agrees = true;
|
|
5777
|
+
if (sameWork) {
|
|
5778
|
+
try {
|
|
5779
|
+
agrees = sameWork(cut.value, data);
|
|
5780
|
+
} catch (e) {
|
|
5781
|
+
logger2.warn(`InteractionController: "${componentName}" sameWork threw; starting fresh:`, e);
|
|
5782
|
+
agrees = false;
|
|
5783
|
+
}
|
|
5784
|
+
}
|
|
5785
|
+
recordMetaEvent(this.deps.sessionKey, "interaction.checkpoint", {
|
|
5786
|
+
action: agrees ? "resumed" : "refused",
|
|
5787
|
+
component: componentName,
|
|
5788
|
+
interactionId,
|
|
5789
|
+
...agrees ? {} : { saved: cut.value }
|
|
5790
|
+
});
|
|
5791
|
+
if (!agrees) return void 0;
|
|
5792
|
+
this.checkpoint = { componentName, value: cut.value, interactionId };
|
|
5793
|
+
return cut.value;
|
|
5794
|
+
}
|
|
5795
|
+
/** A controller's `ctx.saveCheckpoint`: kept as JSON, the form it is persisted in. */
|
|
5796
|
+
saveCheckpoint(interactionId, componentName, value) {
|
|
5797
|
+
if (!this.pending.has(interactionId)) return;
|
|
5798
|
+
let copy;
|
|
5799
|
+
try {
|
|
5800
|
+
copy = JSON.parse(JSON.stringify(value) ?? "null");
|
|
5801
|
+
} catch (e) {
|
|
5802
|
+
logger2.warn(
|
|
5803
|
+
`InteractionController: a "${componentName}" checkpoint is not storable as JSON:`,
|
|
5804
|
+
e
|
|
5805
|
+
);
|
|
5806
|
+
return;
|
|
5807
|
+
}
|
|
5808
|
+
this.checkpoint = { componentName, value: copy, interactionId };
|
|
5809
|
+
this.deps.emit(new CustomEvent("checkpoint-saved", { detail: { interactionId } }));
|
|
5810
|
+
}
|
|
5382
5811
|
/** See `ChatDriver.addInteractionInterceptor`. Returns a remover. */
|
|
5383
5812
|
addInterceptor(interceptor) {
|
|
5384
5813
|
if (!interceptor.id || interceptor.id === "user") {
|
|
@@ -5438,6 +5867,10 @@ var InteractionController = class {
|
|
|
5438
5867
|
if (!isClaimant()) return false;
|
|
5439
5868
|
const next = holder === "user" ? "user" : interceptor.id;
|
|
5440
5869
|
if (!handle.setHeldBy(next)) return false;
|
|
5870
|
+
if (next === "user") {
|
|
5871
|
+
const pending = this.pending.get(handle.context.interactionId);
|
|
5872
|
+
if (pending && !pending.running?.finishes) pending.settledBy = void 0;
|
|
5873
|
+
}
|
|
5441
5874
|
recordMetaEvent(this.deps.sessionKey, "interaction.held", {
|
|
5442
5875
|
interactionId: handle.context.interactionId,
|
|
5443
5876
|
heldBy: next,
|
|
@@ -5459,9 +5892,87 @@ var InteractionController = class {
|
|
|
5459
5892
|
if (!pending) return false;
|
|
5460
5893
|
pending.decision = { choice: decision.choice, shownToUser: decision.shownToUser };
|
|
5461
5894
|
return true;
|
|
5895
|
+
},
|
|
5896
|
+
invoke: async (action, args, options) => {
|
|
5897
|
+
const { interactionId } = handle.context;
|
|
5898
|
+
const pending = this.pending.get(interactionId);
|
|
5899
|
+
if (!isClaimant() || !pending || handle.context.heldBy !== interceptor.id) {
|
|
5900
|
+
return {
|
|
5901
|
+
ok: false,
|
|
5902
|
+
error: "not-held",
|
|
5903
|
+
message: pending ? "This interaction is with the person now, so it cannot be acted on." : "This interaction has already ended."
|
|
5904
|
+
};
|
|
5905
|
+
}
|
|
5906
|
+
const { surface } = handle;
|
|
5907
|
+
if (!surface) {
|
|
5908
|
+
return { ok: false, error: "no-surface", message: "Nothing has been exposed here yet." };
|
|
5909
|
+
}
|
|
5910
|
+
if (pending.running) {
|
|
5911
|
+
return { ok: false, ...BUSY, actions: surface.actions() };
|
|
5912
|
+
}
|
|
5913
|
+
const prepared = surface.prepare(action, args);
|
|
5914
|
+
if ("error" in prepared) {
|
|
5915
|
+
return { ok: false, ...prepared, actions: surface.actions() };
|
|
5916
|
+
}
|
|
5917
|
+
const previous = { decision: pending.decision, settledBy: pending.settledBy };
|
|
5918
|
+
const restore = () => {
|
|
5919
|
+
if (!prepared.finishes) return;
|
|
5920
|
+
pending.decision = previous.decision;
|
|
5921
|
+
pending.settledBy = handle.context.heldBy === interceptor.id ? previous.settledBy : void 0;
|
|
5922
|
+
};
|
|
5923
|
+
if (prepared.finishes) {
|
|
5924
|
+
pending.decision = { choice: prepared.choice, shownToUser: options?.note };
|
|
5925
|
+
pending.settledBy = interceptor.id;
|
|
5926
|
+
}
|
|
5927
|
+
const content = options?.note?.trim();
|
|
5928
|
+
const note = content ? { content, delegate: interceptor.id } : void 0;
|
|
5929
|
+
const token = {};
|
|
5930
|
+
pending.running = { token, finishes: prepared.finishes, note };
|
|
5931
|
+
let output;
|
|
5932
|
+
try {
|
|
5933
|
+
const ran = prepared.run();
|
|
5934
|
+
output = isThenable(ran) ? await ran : ran;
|
|
5935
|
+
} catch (e) {
|
|
5936
|
+
logger2.warn(`InteractionController: action "${action}" threw:`, e);
|
|
5937
|
+
if (pending.settledInRun !== token) restore();
|
|
5938
|
+
return {
|
|
5939
|
+
ok: false,
|
|
5940
|
+
error: "threw",
|
|
5941
|
+
message: e instanceof Error ? e.message : String(e)
|
|
5942
|
+
};
|
|
5943
|
+
} finally {
|
|
5944
|
+
if (pending.running?.token === token) pending.running = void 0;
|
|
5945
|
+
}
|
|
5946
|
+
const finished = pending.settledInRun === token;
|
|
5947
|
+
if (!finished) {
|
|
5948
|
+
restore();
|
|
5949
|
+
if (note && this.pending.has(interactionId)) this.appendNote(note);
|
|
5950
|
+
(pending.edits ??= []).push(editOf(interceptor.id, action, prepared.choice, args));
|
|
5951
|
+
}
|
|
5952
|
+
recordMetaEvent(this.deps.sessionKey, "interaction.invoked", {
|
|
5953
|
+
interactionId,
|
|
5954
|
+
claimant: interceptor.id,
|
|
5955
|
+
action,
|
|
5956
|
+
finished
|
|
5957
|
+
});
|
|
5958
|
+
return {
|
|
5959
|
+
ok: true,
|
|
5960
|
+
finished,
|
|
5961
|
+
actions: finished ? [] : surface.actions(),
|
|
5962
|
+
...output !== void 0 && !finished ? { output } : {}
|
|
5963
|
+
};
|
|
5462
5964
|
}
|
|
5463
5965
|
};
|
|
5464
5966
|
}
|
|
5967
|
+
/** A claimant's note about its choice: a delegated, display-only message in the assistant's voice. */
|
|
5968
|
+
appendNote(note) {
|
|
5969
|
+
this.deps.appendToHistory({
|
|
5970
|
+
role: "assistant",
|
|
5971
|
+
content: note.content,
|
|
5972
|
+
delegate: note.delegate,
|
|
5973
|
+
excludeFromProvider: true
|
|
5974
|
+
});
|
|
5975
|
+
}
|
|
5465
5976
|
/** See `ChatDriver.setHostInteractionRequester`. */
|
|
5466
5977
|
setHostRequester(fn, recordOutcome) {
|
|
5467
5978
|
this.hostRequester = fn;
|
|
@@ -5506,8 +6017,36 @@ var InteractionController = class {
|
|
|
5506
6017
|
});
|
|
5507
6018
|
}, timeoutMs) : void 0
|
|
5508
6019
|
});
|
|
5509
|
-
const handle = createInteractionContext(interactionId
|
|
6020
|
+
const handle = createInteractionContext(interactionId, {
|
|
6021
|
+
resolve: (result) => this.resolve(interactionId, result),
|
|
6022
|
+
checkpoint: this.takeOverCheckpoint(componentName, data, interactionId),
|
|
6023
|
+
saveCheckpoint: (value) => this.saveCheckpoint(interactionId, componentName, value)
|
|
6024
|
+
});
|
|
5510
6025
|
this.contexts.set(interactionId, handle);
|
|
6026
|
+
bindStepRecorder(
|
|
6027
|
+
handle.context,
|
|
6028
|
+
(step, outcome) => recordMetaEvent(this.deps.sessionKey, "interaction.step", {
|
|
6029
|
+
interactionId,
|
|
6030
|
+
component: componentName,
|
|
6031
|
+
step,
|
|
6032
|
+
outcome
|
|
6033
|
+
}),
|
|
6034
|
+
(reason, saved) => recordMetaEvent(this.deps.sessionKey, "interaction.checkpoint", {
|
|
6035
|
+
action: "refused",
|
|
6036
|
+
component: componentName,
|
|
6037
|
+
interactionId,
|
|
6038
|
+
reason,
|
|
6039
|
+
saved
|
|
6040
|
+
})
|
|
6041
|
+
);
|
|
6042
|
+
const registration = getInteractionRegistration(componentName);
|
|
6043
|
+
if (registration?.controller) {
|
|
6044
|
+
try {
|
|
6045
|
+
handle.setController(registration.controller(data, handle.context));
|
|
6046
|
+
} catch (e) {
|
|
6047
|
+
logger2.error(`InteractionController: the "${componentName}" controller threw:`, e);
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
5511
6050
|
const heldBy = this.offer(handle, {
|
|
5512
6051
|
interactionId,
|
|
5513
6052
|
componentName,
|
|
@@ -5584,21 +6123,20 @@ var InteractionController = class {
|
|
|
5584
6123
|
const costUsd = typeof result?.costUsd === "number" && Number.isFinite(result.costUsd) && result.costUsd > 0 ? result.costUsd : void 0;
|
|
5585
6124
|
const unanswered = result?.status === "timeout";
|
|
5586
6125
|
const answeredBy = unanswered ? void 0 : interaction.settledBy ?? "user";
|
|
5587
|
-
const
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
}
|
|
5595
|
-
} : void 0;
|
|
6126
|
+
const run = answeredBy && answeredBy !== "user" ? interaction.running : void 0;
|
|
6127
|
+
if (run) {
|
|
6128
|
+
interaction.settledInRun = run.token;
|
|
6129
|
+
if (run.note) this.appendNote(run.note);
|
|
6130
|
+
}
|
|
6131
|
+
const edits = interaction.edits?.length ? interaction.edits : void 0;
|
|
6132
|
+
const { decision, edited } = doneOnUsersBehalf(interaction, answeredBy);
|
|
5596
6133
|
const patched = this.deps.patchInteractionMessage(interactionId, (message) => ({
|
|
5597
6134
|
...message,
|
|
5598
6135
|
interaction: {
|
|
5599
6136
|
...message.interaction,
|
|
5600
6137
|
resolved: result,
|
|
5601
|
-
...answeredBy ? { answeredBy } : {}
|
|
6138
|
+
...answeredBy ? { answeredBy } : {},
|
|
6139
|
+
...edits ? { edits } : {}
|
|
5602
6140
|
},
|
|
5603
6141
|
...costUsd != null ? { externalCostUsd: (message.externalCostUsd ?? 0) + costUsd } : {}
|
|
5604
6142
|
}));
|
|
@@ -5618,16 +6156,23 @@ var InteractionController = class {
|
|
|
5618
6156
|
}
|
|
5619
6157
|
recordMetaEvent(this.deps.sessionKey, "interaction.resolved", {
|
|
5620
6158
|
interactionId,
|
|
5621
|
-
...answeredBy ? { answeredBy } : {}
|
|
6159
|
+
...answeredBy ? { answeredBy } : {},
|
|
6160
|
+
...edits ? { edits: edits.length } : {}
|
|
5622
6161
|
});
|
|
5623
6162
|
this.deps.activityBus.publish("interaction-resolved", void 0);
|
|
5624
6163
|
try {
|
|
5625
|
-
interaction.onSettled?.({ answeredBy, decision });
|
|
6164
|
+
interaction.onSettled?.({ answeredBy, decision, ...edited ? { edited } : {} });
|
|
5626
6165
|
} catch (e) {
|
|
5627
6166
|
logger2.warn("InteractionController: onSettled threw:", e);
|
|
5628
6167
|
}
|
|
6168
|
+
this.deps.emit(
|
|
6169
|
+
new CustomEvent("interaction-settled", {
|
|
6170
|
+
detail: { interactionId, ...answeredBy ? { answeredBy } : {} }
|
|
6171
|
+
})
|
|
6172
|
+
);
|
|
5629
6173
|
interaction.resolve(result);
|
|
5630
6174
|
this.pending.delete(interactionId);
|
|
6175
|
+
if (this.checkpoint?.interactionId === interactionId) this.checkpoint = void 0;
|
|
5631
6176
|
this.contexts.get(interactionId)?.dispose();
|
|
5632
6177
|
this.contexts.delete(interactionId);
|
|
5633
6178
|
}
|
|
@@ -6024,17 +6569,30 @@ var budgetDetailOf = (e) => {
|
|
|
6024
6569
|
...e.otherVendorAvailable != null ? { otherVendorAvailable: e.otherVendorAvailable } : {}
|
|
6025
6570
|
};
|
|
6026
6571
|
};
|
|
6027
|
-
function withDelegatedDecisions(result, content,
|
|
6028
|
-
|
|
6572
|
+
function withDelegatedDecisions(result, content, done, hasImages) {
|
|
6573
|
+
const { decided, edited } = done;
|
|
6574
|
+
if (decided.length === 0 && edited.length === 0) return content;
|
|
6029
6575
|
if (!hasImages && result !== null && typeof result === "object" && !Array.isArray(result)) {
|
|
6030
|
-
return JSON.stringify({
|
|
6576
|
+
return JSON.stringify({
|
|
6577
|
+
...result,
|
|
6578
|
+
...decided.length ? { decidedOnUsersBehalf: decided } : {},
|
|
6579
|
+
...edited.length ? { editedOnUsersBehalf: edited } : {}
|
|
6580
|
+
});
|
|
6031
6581
|
}
|
|
6032
|
-
return [
|
|
6582
|
+
return [
|
|
6583
|
+
content,
|
|
6584
|
+
...decided.map(describeDelegatedDecision),
|
|
6585
|
+
...edited.map(describeDelegatedEdits)
|
|
6586
|
+
].join("\n\n");
|
|
6587
|
+
}
|
|
6588
|
+
function describeDelegatedEdits(edited) {
|
|
6589
|
+
return `${edited.framing}: ${edited.edits.map((edit) => `"${edit}"`).join(", ")}.`;
|
|
6033
6590
|
}
|
|
6034
6591
|
function describeDelegatedDecision(decision) {
|
|
6035
6592
|
const choice = decision.choice !== void 0 ? `: "${decision.choice}"` : "";
|
|
6036
6593
|
const shown = decision.shownToUser ? ` What the user was shown, as your words: "${decision.shownToUser}"` : " Nothing was shown to the user about it.";
|
|
6037
|
-
|
|
6594
|
+
const edited = decision.edits?.length ? ` Before deciding it changed the widget: ${decision.edits.map((edit) => `"${edit}"`).join(", ")}.` : "";
|
|
6595
|
+
return `${decision.framing}${choice}.${shown}${edited}`;
|
|
6038
6596
|
}
|
|
6039
6597
|
function inFlightToolCallsAt(history) {
|
|
6040
6598
|
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
@@ -6781,6 +7339,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
6781
7339
|
* release, then the active agent.
|
|
6782
7340
|
*/
|
|
6783
7341
|
dispatchAgentTransition(t) {
|
|
7342
|
+
if (t.flowOwnerChangedTo === null) this.interactions.clearCheckpoint("flow-ended");
|
|
6784
7343
|
if (t.flowOwnerChangedTo !== void 0) {
|
|
6785
7344
|
this.dispatchEvent(new CustomEvent("flow-owner-changed", { detail: t.flowOwnerChangedTo }));
|
|
6786
7345
|
}
|
|
@@ -7365,7 +7924,11 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7365
7924
|
// below, taking its per-message cost/token fields with it — so a host total
|
|
7366
7925
|
// re-derived from the transcript would otherwise fall by exactly this much
|
|
7367
7926
|
// on every compaction. Summarising a turn does not refund what it cost.
|
|
7368
|
-
rolledUpUsage: sumUsage(toCompact)
|
|
7927
|
+
rolledUpUsage: sumUsage(toCompact),
|
|
7928
|
+
// The user's own words survive word for word, beside the paraphrase (GENC-1660, C2): this
|
|
7929
|
+
// range's own only. An earlier summary's kept messages are not carried forward; the
|
|
7930
|
+
// summariser read them with it and folded them into this paraphrase.
|
|
7931
|
+
preservedUserMessages: freshUserMessages(toCompact)
|
|
7369
7932
|
}
|
|
7370
7933
|
};
|
|
7371
7934
|
this.loadHistory([summaryMessage, ...tail]);
|
|
@@ -7537,6 +8100,14 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7537
8100
|
getInteractionContext(interactionId) {
|
|
7538
8101
|
return this.interactions.context(interactionId);
|
|
7539
8102
|
}
|
|
8103
|
+
/** {@inheritDoc AiDriver.getInteractionCheckpoint} */
|
|
8104
|
+
getInteractionCheckpoint() {
|
|
8105
|
+
return this.interactions.getCheckpoint();
|
|
8106
|
+
}
|
|
8107
|
+
/** {@inheritDoc AiDriver.restoreInteractionCheckpoint} */
|
|
8108
|
+
restoreInteractionCheckpoint(saved) {
|
|
8109
|
+
this.interactions.restoreCheckpoint(saved);
|
|
8110
|
+
}
|
|
7540
8111
|
/**
|
|
7541
8112
|
* Replace the history message owning `interactionId`. `false` when it is no longer in history.
|
|
7542
8113
|
* Does not announce the change — the interaction paths differ on whether they should.
|
|
@@ -7579,7 +8150,8 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7579
8150
|
role: "user",
|
|
7580
8151
|
content: userInput,
|
|
7581
8152
|
attachments,
|
|
7582
|
-
|
|
8153
|
+
// Who sent it, and what writing it cost when that was not the working agent's own spend.
|
|
8154
|
+
...delegatedFields(options)
|
|
7583
8155
|
});
|
|
7584
8156
|
this.turnStartedAt = Date.now();
|
|
7585
8157
|
recordMetaEvent(this.sessionKey, "turn.start", {
|
|
@@ -7645,7 +8217,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7645
8217
|
* kept on a seven-way fan-out, reporting ~2.3x under the real cost, with the
|
|
7646
8218
|
* error growing as the fan-out widens.
|
|
7647
8219
|
*/
|
|
7648
|
-
buildHandlerContext(activeToolCallId, traceCapture,
|
|
8220
|
+
buildHandlerContext(activeToolCallId, traceCapture, done) {
|
|
7649
8221
|
let opened;
|
|
7650
8222
|
let recorded = false;
|
|
7651
8223
|
const capabilities2 = this.buildCapabilities();
|
|
@@ -7659,9 +8231,11 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7659
8231
|
opened = interactionId;
|
|
7660
8232
|
},
|
|
7661
8233
|
void 0,
|
|
7662
|
-
//
|
|
8234
|
+
// What an interceptor did to an interaction is reported in THIS call's result
|
|
8235
|
+
// (GENC-1660): its answer, or the edits it made before the person answered.
|
|
7663
8236
|
(settled) => {
|
|
7664
|
-
if (settled.decision)
|
|
8237
|
+
if (settled.decision) done?.decided.push(settled.decision);
|
|
8238
|
+
if (settled.edited) done?.edited.push(settled.edited);
|
|
7665
8239
|
}
|
|
7666
8240
|
),
|
|
7667
8241
|
recordInteractionOutcome: (outcome) => {
|
|
@@ -8498,17 +9072,17 @@ ${tailBody}
|
|
|
8498
9072
|
}
|
|
8499
9073
|
const traceCapture = { traces: [] };
|
|
8500
9074
|
const capturedTrace = () => traceCapture.traces.length ? traceCapture.traces.flat() : void 0;
|
|
8501
|
-
const
|
|
9075
|
+
const done = { decided: [], edited: [] };
|
|
8502
9076
|
try {
|
|
8503
9077
|
const result = await handler(
|
|
8504
9078
|
tc.args,
|
|
8505
|
-
this.buildHandlerContext(tc.id, traceCapture,
|
|
9079
|
+
this.buildHandlerContext(tc.id, traceCapture, done)
|
|
8506
9080
|
);
|
|
8507
9081
|
const resultImages = toolResultImages(result);
|
|
8508
9082
|
const content = withDelegatedDecisions(
|
|
8509
9083
|
result,
|
|
8510
9084
|
resultImages ? typeof result.content === "string" ? result.content : JSON.stringify({ ...result, attachments: void 0 }) : typeof result === "string" ? result : JSON.stringify(result),
|
|
8511
|
-
|
|
9085
|
+
done,
|
|
8512
9086
|
!!resultImages
|
|
8513
9087
|
);
|
|
8514
9088
|
executedById.set(tc.id, {
|
|
@@ -8718,8 +9292,10 @@ var AI_DRIVER_EVENT_TYPES = Object.keys({
|
|
|
8718
9292
|
"sub-agent-stop": true,
|
|
8719
9293
|
"interaction-start": true,
|
|
8720
9294
|
"interaction-stop": true,
|
|
9295
|
+
"interaction-settled": true,
|
|
8721
9296
|
"provider-changed": true,
|
|
8722
9297
|
"external-cost-recorded": true,
|
|
9298
|
+
"checkpoint-saved": true,
|
|
8723
9299
|
"agent-changed": true,
|
|
8724
9300
|
"agent-released": true,
|
|
8725
9301
|
"flow-owner-changed": true,
|
|
@@ -8815,6 +9391,12 @@ var BaseDriverDecorator = class _BaseDriverDecorator extends EventTarget {
|
|
|
8815
9391
|
getFlowOwner() {
|
|
8816
9392
|
return this.inner.getFlowOwner?.() ?? null;
|
|
8817
9393
|
}
|
|
9394
|
+
getInteractionCheckpoint() {
|
|
9395
|
+
return this.inner.getInteractionCheckpoint?.();
|
|
9396
|
+
}
|
|
9397
|
+
restoreInteractionCheckpoint(saved) {
|
|
9398
|
+
this.inner.restoreInteractionCheckpoint?.(saved);
|
|
9399
|
+
}
|
|
8818
9400
|
/**
|
|
8819
9401
|
* Forwarded, so an interceptor installed on any layer lands on the driver that opens the
|
|
8820
9402
|
* interactions. A decorator that answers interactions installs its own on `this.inner`.
|
|
@@ -9049,10 +9631,7 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
9049
9631
|
return this.chatDriver.getSuggestions(history, prompt2, count, agentInfo);
|
|
9050
9632
|
}
|
|
9051
9633
|
async sendMessage(input, attachments, options) {
|
|
9052
|
-
const delegated = options
|
|
9053
|
-
delegate: options.delegate,
|
|
9054
|
-
delegateFraming: options.delegateFraming ?? DEFAULT_DELEGATE_FRAMING
|
|
9055
|
-
} : {};
|
|
9634
|
+
const delegated = delegatedFields(options);
|
|
9056
9635
|
try {
|
|
9057
9636
|
return await this.runOrchestratedTurn(input, attachments, options, delegated);
|
|
9058
9637
|
} catch (e) {
|
|
@@ -9185,7 +9764,25 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
9185
9764
|
this.cancelled = false;
|
|
9186
9765
|
await this.installAgent(agent);
|
|
9187
9766
|
if (!resumeNote || agent.resumeOnRestore === false || this.cancelled) return void 0;
|
|
9188
|
-
|
|
9767
|
+
const result = await this.chatDriver.continueFromHistory(void 0, { tailNote: resumeNote });
|
|
9768
|
+
await this.releaseIfRequested();
|
|
9769
|
+
return result;
|
|
9770
|
+
}
|
|
9771
|
+
/**
|
|
9772
|
+
* A continuation outside a routed turn — a decorator carrying the agent on, say — can finish the
|
|
9773
|
+
* flow as surely as a turn can, so a release it requests is acted on here too. Before
|
|
9774
|
+
* (GENC-1660), only `sendMessage` checked, and a flow finished by a continuation stayed locked.
|
|
9775
|
+
*/
|
|
9776
|
+
async continueFromHistory(transientPrimer, options) {
|
|
9777
|
+
const result = await this.chatDriver.continueFromHistory(transientPrimer, options);
|
|
9778
|
+
await this.releaseIfRequested();
|
|
9779
|
+
return result;
|
|
9780
|
+
}
|
|
9781
|
+
/** Release the flow if the turn that just ended asked to, and an agent is still active to release. */
|
|
9782
|
+
async releaseIfRequested() {
|
|
9783
|
+
if (this.chatDriver.getAgentReleaseRequested() && this.agentSession.getActive()) {
|
|
9784
|
+
await this.releaseActiveAgent();
|
|
9785
|
+
}
|
|
9189
9786
|
}
|
|
9190
9787
|
/**
|
|
9191
9788
|
* {@inheritDoc AiDriver.getContextReserve}
|
|
@@ -9648,6 +10245,13 @@ function defineStatefulAgent(opts) {
|
|
|
9648
10245
|
notResumableMessage: opts.notResumableMessage,
|
|
9649
10246
|
resumeOnRestore: opts.resumeOnRestore,
|
|
9650
10247
|
contextReserve: opts.contextReserve,
|
|
10248
|
+
// Bound to the live state, so the autopilot's resolvers see what this agent's do (GENC-1660).
|
|
10249
|
+
...opts.autopilot && {
|
|
10250
|
+
autopilot: {
|
|
10251
|
+
config: opts.autopilot,
|
|
10252
|
+
state: () => state
|
|
10253
|
+
}
|
|
10254
|
+
},
|
|
9651
10255
|
onActivate: async (ctx) => {
|
|
9652
10256
|
state = await opts.init(ctx);
|
|
9653
10257
|
if (opts.toolHandlers) {
|
|
@@ -9688,6 +10292,9 @@ function defineStatefulAgent(opts) {
|
|
|
9688
10292
|
excludeFromClassifier: opts.excludeFromClassifier
|
|
9689
10293
|
};
|
|
9690
10294
|
}
|
|
10295
|
+
function defineAutopilot(config) {
|
|
10296
|
+
return config;
|
|
10297
|
+
}
|
|
9691
10298
|
|
|
9692
10299
|
// src/config/fallback-agents.ts
|
|
9693
10300
|
var friendlyFallbackAgent = {
|
|
@@ -9905,6 +10512,7 @@ function withFreshMetaSnapshot(stored, fresh) {
|
|
|
9905
10512
|
buildTimelineEntries,
|
|
9906
10513
|
clearSession,
|
|
9907
10514
|
defineAgent,
|
|
10515
|
+
defineAutopilot,
|
|
9908
10516
|
defineStatefulAgent,
|
|
9909
10517
|
emptyUsage,
|
|
9910
10518
|
friendlyFallbackAgent,
|