@genesislcap/ai-assistant 15.25.1 → 15.27.0-agent-log-submit.3
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 +1014 -110
- package/dist/ai-assistant.d.ts +236 -99
- package/dist/chat-driver.cjs +460 -373
- package/dist/chat-driver.cjs.map +4 -4
- package/dist/chat-driver.mjs +460 -373
- package/dist/chat-driver.mjs.map +4 -4
- package/dist/custom-elements.json +1171 -248
- package/dist/dts/components/chat-driver/chat-driver.d.ts +28 -200
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/interaction-controller.d.ts +99 -0
- package/dist/dts/components/chat-driver/interaction-controller.d.ts.map +1 -0
- package/dist/dts/components/chat-driver/turn-recorder.d.ts +191 -0
- package/dist/dts/components/chat-driver/turn-recorder.d.ts.map +1 -0
- package/dist/dts/components/settings-modal/settings-modal.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.template.d.ts +28 -0
- package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
- package/dist/dts/index.d.ts +1 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +180 -1
- 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 +19 -0
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/state/ai-assistant-slice.d.ts +7 -1
- package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-persistence-provider.d.ts +5 -0
- package/dist/dts/state/persistence/session-persistence-provider.d.ts.map +1 -1
- package/dist/dts/state/session-store.d.ts +1 -0
- package/dist/dts/state/session-store.d.ts.map +1 -1
- package/dist/dts/utils/format-message-timestamp.d.ts +77 -0
- package/dist/dts/utils/format-message-timestamp.d.ts.map +1 -0
- package/dist/dts/utils/resolve-debug-log-note.d.ts +25 -0
- package/dist/dts/utils/resolve-debug-log-note.d.ts.map +1 -0
- package/dist/dts/utils/resolve-preference-baseline.d.ts +18 -1
- package/dist/dts/utils/resolve-preference-baseline.d.ts.map +1 -1
- package/dist/esm/components/chat-driver/chat-driver.js +68 -404
- package/dist/esm/components/chat-driver/interaction-controller.js +235 -0
- package/dist/esm/components/chat-driver/turn-recorder.js +206 -0
- package/dist/esm/components/settings-modal/settings-modal.js +1 -0
- package/dist/esm/components/settings-modal/settings-modal.styles.js +85 -5
- package/dist/esm/components/settings-modal/settings-modal.template.js +220 -25
- package/dist/esm/main/main.js +510 -23
- package/dist/esm/main/main.styles.js +75 -0
- package/dist/esm/main/main.template.js +43 -2
- package/dist/esm/state/ai-assistant-slice.js +5 -0
- package/dist/esm/utils/format-message-timestamp.js +126 -0
- package/dist/esm/utils/resolve-debug-log-note.js +37 -0
- package/dist/esm/utils/resolve-preference-baseline.js +54 -2
- package/package.json +16 -16
- package/sandbox/README.md +45 -2
- package/sandbox/sandbox.ts +95 -3
|
@@ -3,7 +3,6 @@ import { BudgetExhaustedError, ContextOverflowError, ProviderRefusedError, isObs
|
|
|
3
3
|
import { NOOP_ACTIVITY_BUS } from '../../channel/ai-activity-bus';
|
|
4
4
|
import { resolveChatProvider } from '../../config/validate-providers';
|
|
5
5
|
import { clearSession, getMetaEvents, mergeMetaEvents, recordMetaEvent, recordTurnError, recordTurnRetry, } from '../../state/debug-event-log';
|
|
6
|
-
import { createInteractionContext, } from '../../state/interaction-context';
|
|
7
6
|
import { generateSuggestions } from '../../suggestions/generate-suggestions';
|
|
8
7
|
import { applyCondensation, condenseForPersistence, } from '../../utils/condense-history';
|
|
9
8
|
import { estimatePreambleTokens, estimateRequestTokens, } from '../../utils/context-tokens';
|
|
@@ -14,7 +13,9 @@ import { messageUsage, sumUsage } from '../../utils/sum-usage';
|
|
|
14
13
|
import { AgentSession } from '../agent-session/agent-session';
|
|
15
14
|
import { DEFAULT_MAX_FOLD_OPERATIONS, DEFAULT_MAX_TOOL_ITERATIONS, DEFAULT_MAX_TURN_SNAPSHOTS, DEFAULT_MAX_UNKNOWN_TOOL_CALLS, EMPTY_RESPONSE_NUDGE, HANDOFF_TOOL_RESULT_PLACEHOLDER, HARVESTED_SUBAGENT_EVENTS, MALFORMED_CALL_NUDGE, MAX_EMPTY_RESPONSE_RETRIES, MAX_MALFORMED_RETRIES, MAX_SETUP_TRANSPORT_RETRIES, MAX_STALE_TOOL_CALLS, REQUEST_CONTINUATION_TOOL, } from './chat-driver.constants';
|
|
16
15
|
import { FoldController } from './fold-controller';
|
|
16
|
+
import { InteractionController } from './interaction-controller';
|
|
17
17
|
import { TURN_FAILURE_EXITS, turnDone } from './turn-failures';
|
|
18
|
+
import { TurnRecorder } from './turn-recorder';
|
|
18
19
|
/** Re-exported so `from './chat-driver'` keeps resolving these. */
|
|
19
20
|
export { EMPTY_RESPONSE_NUDGE, MALFORMED_CALL_NUDGE, REQUEST_CONTINUATION_TOOL, } from './chat-driver.constants';
|
|
20
21
|
/**
|
|
@@ -85,17 +86,6 @@ export class ChatDriver extends EventTarget {
|
|
|
85
86
|
this.busy = false;
|
|
86
87
|
/** Epoch ms when the current turn loop began — drives the `turn.end` duration. */
|
|
87
88
|
this.turnStartedAt = 0;
|
|
88
|
-
this.pendingInteractions = new Map();
|
|
89
|
-
/**
|
|
90
|
-
* Live per-interaction contexts (GENC-1390), keyed by interactionId. Created in
|
|
91
|
-
* `requestInteraction` alongside the pending entry, disposed + removed in
|
|
92
|
-
* `resolveInteraction`. Non-serialisable (sockets, timers, subscriber sets), so
|
|
93
|
-
* they live here on the driver — not on the DOM, not in the persisted store — and
|
|
94
|
-
* therefore survive the element remounts (layout reflow/drag, transcript
|
|
95
|
-
* re-render) that this feature exists to make harmless. At most one is live at a
|
|
96
|
-
* time (see the one-interaction-at-a-time guard in `requestInteraction`).
|
|
97
|
-
*/
|
|
98
|
-
this.interactionContexts = new Map();
|
|
99
89
|
/**
|
|
100
90
|
* Tool-declared condensation policies, keyed by tool-call id. Populated by
|
|
101
91
|
* `condenseWhen` (first-wins per call); read by `applyCondensation` before each
|
|
@@ -194,31 +184,6 @@ export class ChatDriver extends EventTarget {
|
|
|
194
184
|
* doesn't leak forward.
|
|
195
185
|
*/
|
|
196
186
|
this.agentReleaseRequested = false;
|
|
197
|
-
/**
|
|
198
|
-
* Ring buffer of per-LLM-call snapshots. Cap is configurable via
|
|
199
|
-
* `chatConfig.agent.maxTurnSnapshots`; older entries drop off as new ones
|
|
200
|
-
* arrive. See {@link TurnSnapshot} for the captured shape.
|
|
201
|
-
*/
|
|
202
|
-
this.turnSnapshots = [];
|
|
203
|
-
/**
|
|
204
|
-
* `turn` + `message` diagnostic-log entries harvested from an out-of-band source (a resolved
|
|
205
|
-
* interaction whose widget returned `InteractionResult.diagnostics` — e.g. a server-side
|
|
206
|
-
* ChatDriver's debug log). `event` entries are NOT kept here — they're folded into the session's
|
|
207
|
-
* meta-event registry via `mergeMetaEvents` (see `resolveInteraction`) so they inherit fresh host
|
|
208
|
-
* indices, and reach the log through `getMetaEvents`. Append-only; the host folds these into its
|
|
209
|
-
* debug log alongside this driver's own timeline. Not cleared here — it lives with the driver
|
|
210
|
-
* instance, like `turnSnapshots`, so a new chat (fresh driver) starts empty while a compaction
|
|
211
|
-
* (same instance) keeps it. (GENC-1461.)
|
|
212
|
-
*/
|
|
213
|
-
this.externalDiagnostics = [];
|
|
214
|
-
/**
|
|
215
|
-
* Monotonic count of external-diagnostics batches harvested this driver-lifetime. Namespaces each
|
|
216
|
-
* batch's re-keyed `turn` entries so distinct server generations in one chat session can't collide
|
|
217
|
-
* on `turnIndex` in the forward-capture persister (which dedups turns by that key). (GENC-1461.)
|
|
218
|
-
*/
|
|
219
|
-
this.externalDiagnosticsBatches = 0;
|
|
220
|
-
/** Monotonic counter that survives agent swaps — useful for cross-referencing with history. */
|
|
221
|
-
this.globalTurnIndex = 0;
|
|
222
187
|
/**
|
|
223
188
|
* Aborted by `dispose()` on driver teardown (e.g. an agent-config swap).
|
|
224
189
|
* Threaded into every provider call as `ChatRequestOptions.signal`, so a
|
|
@@ -316,7 +281,17 @@ export class ChatDriver extends EventTarget {
|
|
|
316
281
|
this.systemPrompt = systemPrompt;
|
|
317
282
|
this.primerHistory = primerHistory;
|
|
318
283
|
this.maxFoldOperations = maxFoldOperations;
|
|
319
|
-
this.
|
|
284
|
+
this.turnRecorder = new TurnRecorder(sessionKey, maxTurnSnapshots);
|
|
285
|
+
this.interactions = new InteractionController({
|
|
286
|
+
sessionKey,
|
|
287
|
+
activityBus,
|
|
288
|
+
activeAgentName: () => this.activeAgentName,
|
|
289
|
+
appendToHistory: (message) => this.appendToHistory(message),
|
|
290
|
+
patchInteractionMessage: (interactionId, patch) => this.patchInteractionMessage(interactionId, patch),
|
|
291
|
+
notifyHistoryUpdated: () => this.notifyHistoryUpdated(),
|
|
292
|
+
emit: (event) => this.dispatchEvent(event),
|
|
293
|
+
foldDiagnostics: (interactionId, harvested) => this.turnRecorder.foldDiagnostics(interactionId, harvested),
|
|
294
|
+
});
|
|
320
295
|
// Runtime provider switching: when the host registered an observable
|
|
321
296
|
// registry, drop our memoised provider lookups whenever its mapping/default
|
|
322
297
|
// changes so the next turn re-resolves against the new providers. Resetting
|
|
@@ -343,7 +318,7 @@ export class ChatDriver extends EventTarget {
|
|
|
343
318
|
* on to completion or the transport timeout. Called by the host on driver
|
|
344
319
|
* swap and by `OrchestratingDriver.dispose()`. Idempotent.
|
|
345
320
|
*
|
|
346
|
-
* Deliberately does NOT settle
|
|
321
|
+
* Deliberately does NOT settle pending interactions here: dispose is never
|
|
347
322
|
* reached while one is pending. The only host-level dispose is the agent-swap
|
|
348
323
|
* rebuild, which is guarded by `isBusy()` — a pending interaction keeps the
|
|
349
324
|
* tool loop suspended (`busy === true`), so the swap is deferred until the
|
|
@@ -361,13 +336,8 @@ export class ChatDriver extends EventTarget {
|
|
|
361
336
|
// fires this and forgets (`main.ts`, `void old.dispose()`). Awaiting anything first opened a
|
|
362
337
|
// window in which a disposed driver was still subscribed to the provider registry.
|
|
363
338
|
this.lifecycleController.abort(new DOMException('AI assistant driver disposed', 'AbortError'));
|
|
364
|
-
// Drop any live interaction contexts (GENC-1390).
|
|
365
|
-
|
|
366
|
-
// is not reached mid-interaction); disposed defensively so a stray context can
|
|
367
|
-
// never leak an open socket/timer.
|
|
368
|
-
for (const handle of this.interactionContexts.values())
|
|
369
|
-
handle.dispose();
|
|
370
|
-
this.interactionContexts.clear();
|
|
339
|
+
// Drop any live interaction contexts (GENC-1390).
|
|
340
|
+
this.interactions.disposeContexts();
|
|
371
341
|
// Detach from the provider registry so the long-lived registry doesn't pin
|
|
372
342
|
// this driver (and its closure) after teardown. Guard-cleared so a second
|
|
373
343
|
// dispose is a no-op, matching this method's idempotent contract.
|
|
@@ -687,7 +657,7 @@ export class ChatDriver extends EventTarget {
|
|
|
687
657
|
// canonical name when displayName is unset.
|
|
688
658
|
this.activeAgentLabel =
|
|
689
659
|
typeof config.displayName === 'string' ? config.displayName : config.name;
|
|
690
|
-
this.
|
|
660
|
+
this.turnRecorder.setSnapshotter(config.getDebugSnapshot);
|
|
691
661
|
this.subAgentsMap = new Map(((_c = config.subAgents) !== null && _c !== void 0 ? _c : []).map((s) => [s.name, s]));
|
|
692
662
|
this.activeProviderInput = config.provider;
|
|
693
663
|
this.activeTemperatureInput = config.temperature;
|
|
@@ -911,104 +881,21 @@ export class ChatDriver extends EventTarget {
|
|
|
911
881
|
* log exporter to show what the LLM saw on each turn — system prompt, tool
|
|
912
882
|
* surface, and agent-supplied state (e.g. a machine snapshot).
|
|
913
883
|
*
|
|
914
|
-
* Ring-buffered at `
|
|
884
|
+
* Ring-buffered at `maxTurnSnapshots`; older entries are dropped.
|
|
915
885
|
*/
|
|
916
886
|
getTurnSnapshots() {
|
|
917
|
-
return this.
|
|
887
|
+
return this.turnRecorder.snapshots();
|
|
918
888
|
}
|
|
919
889
|
/**
|
|
920
890
|
* The `turn` + `message` diagnostic-log entries harvested from resolved interactions'
|
|
921
891
|
* `InteractionResult.diagnostics` (e.g. a server-side ChatDriver's collated debug log). The host
|
|
922
892
|
* concatenates these into its own debug log so `downloadDebugLog`/the persisted diagnostics stream
|
|
923
893
|
* include external-engine logs; `event` entries are excluded here because they're merged into the
|
|
924
|
-
* session's meta-event registry instead
|
|
925
|
-
*
|
|
894
|
+
* session's meta-event registry instead and reach the log via `getMetaEvents`. Opaque,
|
|
895
|
+
* chronologically sorted by the host at assemble time. (GENC-1461.)
|
|
926
896
|
*/
|
|
927
897
|
getExternalDiagnostics() {
|
|
928
|
-
return this.externalDiagnostics;
|
|
929
|
-
}
|
|
930
|
-
/**
|
|
931
|
-
* Merge a sub-agent's turn snapshots into this driver's buffer so they surface
|
|
932
|
-
* as `kind:'turn'` entries in the exported debug log. The child runs as a
|
|
933
|
-
* separate, discarded driver, so its snapshots would otherwise be lost. Each is
|
|
934
|
-
* re-labelled under the parent turn that activated the sub-agent: the child's
|
|
935
|
-
* own (numeric) turns become `"<parentTurn>-1"`, `"-2"`, … (1-based, in order);
|
|
936
|
-
* any already-forwarded grand-child labels (strings) have their leading segment
|
|
937
|
-
* remapped the same way, so nesting composes (`"5-2"` → `"5-2-1"`).
|
|
938
|
-
*
|
|
939
|
-
* Note: two sub-agents invoked in the *same* parent turn share the prefix, so
|
|
940
|
-
* their labels can repeat — `agentName` on each snapshot disambiguates them.
|
|
941
|
-
*/
|
|
942
|
-
forwardSubAgentSnapshots(childSnapshots) {
|
|
943
|
-
var _a;
|
|
944
|
-
if (childSnapshots.length === 0)
|
|
945
|
-
return;
|
|
946
|
-
// The activating parent turn = the most recent snapshot this driver recorded
|
|
947
|
-
// before entering the tool handler that invoked the sub-agent.
|
|
948
|
-
const parentTurn = Math.max(0, this.globalTurnIndex - 1);
|
|
949
|
-
const ownTurnLabel = new Map();
|
|
950
|
-
let ownPos = 0;
|
|
951
|
-
for (const snap of childSnapshots) {
|
|
952
|
-
let turnIndex;
|
|
953
|
-
if (!snap.turnIndex.includes('-')) {
|
|
954
|
-
// The child's own turn (a bare counter) → number it under the parent turn.
|
|
955
|
-
ownPos += 1;
|
|
956
|
-
turnIndex = `${parentTurn}-${ownPos}`;
|
|
957
|
-
ownTurnLabel.set(snap.turnIndex, turnIndex);
|
|
958
|
-
}
|
|
959
|
-
else {
|
|
960
|
-
// An already-forwarded grand-child label — remap its leading segment.
|
|
961
|
-
const [lead, ...rest] = snap.turnIndex.split('-');
|
|
962
|
-
const leadLabel = (_a = ownTurnLabel.get(lead)) !== null && _a !== void 0 ? _a : `${parentTurn}-${lead}`;
|
|
963
|
-
turnIndex = [leadLabel, ...rest].join('-');
|
|
964
|
-
}
|
|
965
|
-
this.turnSnapshots.push(Object.assign(Object.assign({}, snap), { turnIndex }));
|
|
966
|
-
}
|
|
967
|
-
while (this.turnSnapshots.length > this.maxTurnSnapshots) {
|
|
968
|
-
this.turnSnapshots.shift();
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
/**
|
|
972
|
-
* Push one snapshot to the ring buffer. Called inside `runToolLoop` just
|
|
973
|
-
* before each LLM call — that's the latest point where the prompt, tool
|
|
974
|
-
* surface, and agent state line up with what the model is about to see.
|
|
975
|
-
*
|
|
976
|
-
* Returns the pushed object so the caller can back-fill what only the response
|
|
977
|
-
* knows (`usage`). Mutating it after the fact is safe whether or not the ring
|
|
978
|
-
* buffer has since evicted it — an evicted snapshot is simply no longer exported.
|
|
979
|
-
*/
|
|
980
|
-
recordTurnSnapshot(resolvedSystemPrompt, temperature, toolChoice, tailContext) {
|
|
981
|
-
let agentSnapshot;
|
|
982
|
-
if (this.debugSnapshotter) {
|
|
983
|
-
try {
|
|
984
|
-
agentSnapshot = this.debugSnapshotter();
|
|
985
|
-
}
|
|
986
|
-
catch (e) {
|
|
987
|
-
// A snapshotter throwing must not derail the LLM call — capture the
|
|
988
|
-
// error string in place of the snapshot so the export still shows
|
|
989
|
-
// *something* happened.
|
|
990
|
-
agentSnapshot = `<getDebugSnapshot threw: ${e instanceof Error ? e.message : String(e)}>`;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
const turnIndex = String(this.globalTurnIndex);
|
|
994
|
-
this.globalTurnIndex += 1;
|
|
995
|
-
const snapshot = {
|
|
996
|
-
turnIndex,
|
|
997
|
-
timestamp: new Date().toISOString(),
|
|
998
|
-
agentName: this.activeAgentName,
|
|
999
|
-
agentLabel: this.activeAgentLabel,
|
|
1000
|
-
systemPrompt: resolvedSystemPrompt,
|
|
1001
|
-
tailContext,
|
|
1002
|
-
toolNames: this.toolDefinitions.map((t) => t.name),
|
|
1003
|
-
temperature,
|
|
1004
|
-
toolChoice,
|
|
1005
|
-
agentSnapshot,
|
|
1006
|
-
};
|
|
1007
|
-
this.turnSnapshots.push(snapshot);
|
|
1008
|
-
if (this.turnSnapshots.length > this.maxTurnSnapshots) {
|
|
1009
|
-
this.turnSnapshots.shift();
|
|
1010
|
-
}
|
|
1011
|
-
return snapshot;
|
|
898
|
+
return this.turnRecorder.externalDiagnostics();
|
|
1012
899
|
}
|
|
1013
900
|
/**
|
|
1014
901
|
* Set the mid-loop context guard. Its margin is deliberately far smaller than
|
|
@@ -1074,6 +961,10 @@ export class ChatDriver extends EventTarget {
|
|
|
1074
961
|
logger.error(`ChatDriver: ending the turn — the request would fill the context window (~${estimated} of ${limit})`);
|
|
1075
962
|
return { estimated, threshold };
|
|
1076
963
|
}
|
|
964
|
+
/**
|
|
965
|
+
* Optional transform applied to conversation history immediately before each LLM request.
|
|
966
|
+
* Cleared when `undefined`. Does not alter stored history.
|
|
967
|
+
*/
|
|
1077
968
|
setProviderHistoryTransform(transform) {
|
|
1078
969
|
this.providerHistoryTransform = transform;
|
|
1079
970
|
}
|
|
@@ -1237,7 +1128,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1237
1128
|
* naturally: a grandchild → child → root.
|
|
1238
1129
|
*/
|
|
1239
1130
|
setHostInteractionRequester(fn) {
|
|
1240
|
-
this.
|
|
1131
|
+
this.interactions.setHostRequester(fn);
|
|
1241
1132
|
}
|
|
1242
1133
|
/**
|
|
1243
1134
|
* Request a custom UI interaction. Emits a new message with the interaction.
|
|
@@ -1260,80 +1151,16 @@ export class ChatDriver extends EventTarget {
|
|
|
1260
1151
|
*/
|
|
1261
1152
|
requestInteraction(componentName, data, options) {
|
|
1262
1153
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1263
|
-
|
|
1264
|
-
return this.hostInteractionRequester(componentName, data, options);
|
|
1265
|
-
}
|
|
1266
|
-
if (this.pendingInteractions.size > 0) {
|
|
1267
|
-
throw new Error('requestInteraction: another user interaction is already in flight. ' +
|
|
1268
|
-
'Only one interaction may be active at a time — sequence them in a single tool handler ' +
|
|
1269
|
-
'rather than spawning widgets from parallel sub-agents or parallel tool calls.');
|
|
1270
|
-
}
|
|
1271
|
-
const interactionId = crypto.randomUUID();
|
|
1272
|
-
const chatInputDuringExecution = options === null || options === void 0 ? void 0 : options.chatInputDuringExecution;
|
|
1273
|
-
const timeoutMs = options === null || options === void 0 ? void 0 : options.timeoutMs;
|
|
1274
|
-
const presentation = options === null || options === void 0 ? void 0 : options.presentation;
|
|
1275
|
-
return new Promise((resolve, reject) => {
|
|
1276
|
-
this.pendingInteractions.set(interactionId, {
|
|
1277
|
-
resolve,
|
|
1278
|
-
reject,
|
|
1279
|
-
overrideId: chatInputDuringExecution ? interactionId : undefined,
|
|
1280
|
-
// Resolve the interaction with a timeout result if the user does not
|
|
1281
|
-
// respond in time. `resolveInteraction` runs the same teardown a normal
|
|
1282
|
-
// resolution does (marks the widget read-only, reverts any input
|
|
1283
|
-
// override) and clears any still-pending handle.
|
|
1284
|
-
timeoutHandle: timeoutMs != null
|
|
1285
|
-
? setTimeout(() => {
|
|
1286
|
-
this.resolveInteraction(interactionId, {
|
|
1287
|
-
status: 'timeout',
|
|
1288
|
-
message: `Interaction timed out after ${timeoutMs}ms.`,
|
|
1289
|
-
});
|
|
1290
|
-
}, timeoutMs)
|
|
1291
|
-
: undefined,
|
|
1292
|
-
});
|
|
1293
|
-
recordMetaEvent(this.sessionKey, 'interaction.requested', {
|
|
1294
|
-
interactionId,
|
|
1295
|
-
component: componentName,
|
|
1296
|
-
agent: this.activeAgentName,
|
|
1297
|
-
});
|
|
1298
|
-
// Signal the park boundary on the activity bus so turn-aware UI can tell
|
|
1299
|
-
// "actively computing" from "parked awaiting the user" — the latter is a
|
|
1300
|
-
// safe window for actions disallowed mid-request (e.g. switching provider
|
|
1301
|
-
// during a long journey step). Paired with `interaction-resolved`.
|
|
1302
|
-
this.activityBus.publish('interaction-requested', undefined);
|
|
1303
|
-
if (chatInputDuringExecution) {
|
|
1304
|
-
this.dispatchEvent(new CustomEvent('interaction-start', {
|
|
1305
|
-
detail: { interactionId, chatInputDuringExecution },
|
|
1306
|
-
}));
|
|
1307
|
-
}
|
|
1308
|
-
// Create the live context (GENC-1390) BEFORE the widget can mount, so the
|
|
1309
|
-
// wrapper finds it when it builds the pending `interaction` descriptor.
|
|
1310
|
-
this.interactionContexts.set(interactionId, createInteractionContext(interactionId));
|
|
1311
|
-
this.appendToHistory({
|
|
1312
|
-
role: 'assistant',
|
|
1313
|
-
content: '',
|
|
1314
|
-
interaction: Object.assign({ interactionId,
|
|
1315
|
-
componentName,
|
|
1316
|
-
data }, (presentation ? { presentation } : {})),
|
|
1317
|
-
});
|
|
1318
|
-
});
|
|
1154
|
+
return this.interactions.request(componentName, data, options);
|
|
1319
1155
|
});
|
|
1320
1156
|
}
|
|
1321
1157
|
/**
|
|
1322
|
-
* Add external (non-LLM) spend to an interaction's message AFTER it has resolved
|
|
1158
|
+
* Add external (non-LLM) spend to an interaction's message AFTER it has resolved — for a widget
|
|
1159
|
+
* that resolves early so the assistant stops waiting, then keeps doing paid work on the user's
|
|
1160
|
+
* behalf. `InteractionController.recordExternalCost` explains why the resolution path cannot
|
|
1161
|
+
* carry this.
|
|
1323
1162
|
*
|
|
1324
|
-
*
|
|
1325
|
-
* total only through `InteractionResult.costUsd`, which is read exactly once — `resolveInteraction`
|
|
1326
|
-
* does its whole job inside `if (interaction)` and then deletes the entry from
|
|
1327
|
-
* `pendingInteractions`. That is correct for the resolution itself, but it assumes a widget stops
|
|
1328
|
-
* costing money when it resolves, and several deliberately do not: a widget that resolves EARLY so
|
|
1329
|
-
* the assistant stops waiting (options shown, a preview rendered) may stay interactive for minutes
|
|
1330
|
-
* afterwards and do more paid work on the user's behalf. Re-emitting `interaction-completed` does
|
|
1331
|
-
* not help — with the interaction gone from the map the call is a silent no-op — so that spend had
|
|
1332
|
-
* nowhere to go and was simply missing from the figure shown to the user.
|
|
1333
|
-
*
|
|
1334
|
-
* Additive by design: the caller reports a DELTA, not a running total, so repeated calls sum. It
|
|
1335
|
-
* touches only `externalCostUsd`; the recorded `interaction.resolved` payload is left exactly as
|
|
1336
|
-
* the resolution wrote it, because this is not a second resolution and must not read like one.
|
|
1163
|
+
* Additive by design: the caller reports a DELTA, not a running total, so repeated calls sum.
|
|
1337
1164
|
*
|
|
1338
1165
|
* Deliberately does NOT require the interaction to be pending, does not resurrect it, and has no
|
|
1339
1166
|
* loading/turn side effects — this is bookkeeping about a turn that already happened.
|
|
@@ -1343,187 +1170,19 @@ export class ChatDriver extends EventTarget {
|
|
|
1343
1170
|
* @returns `true` when the spend was recorded; `false` when it was not — either the value was
|
|
1344
1171
|
* not a positive finite amount, or the message is no longer in history (trimmed or condensed
|
|
1345
1172
|
* away). A real outcome a caller may want to log rather than a failure to throw on; every
|
|
1346
|
-
* `false` also records an `interaction.external-cost-dropped` meta event
|
|
1347
|
-
*
|
|
1173
|
+
* `false` also records an `interaction.external-cost-dropped` meta event, so money can never
|
|
1174
|
+
* disappear without a trace.
|
|
1348
1175
|
* @beta
|
|
1349
1176
|
*/
|
|
1350
1177
|
recordExternalCost(interactionId, costUsd) {
|
|
1351
|
-
|
|
1352
|
-
const drop = (reason) => {
|
|
1353
|
-
recordMetaEvent(this.sessionKey, 'interaction.external-cost-dropped', {
|
|
1354
|
-
interactionId,
|
|
1355
|
-
costUsd,
|
|
1356
|
-
reason,
|
|
1357
|
-
});
|
|
1358
|
-
return false;
|
|
1359
|
-
};
|
|
1360
|
-
if (!interactionId)
|
|
1361
|
-
return drop('no interactionId');
|
|
1362
|
-
if (typeof costUsd !== 'number' || !Number.isFinite(costUsd) || costUsd <= 0) {
|
|
1363
|
-
return drop('not a positive finite amount');
|
|
1364
|
-
}
|
|
1365
|
-
const idx = this.history.findIndex((m) => { var _a; return ((_a = m.interaction) === null || _a === void 0 ? void 0 : _a.interactionId) === interactionId; });
|
|
1366
|
-
// The message is gone (trimmed or condensed): the spend is real and already billed, so it is
|
|
1367
|
-
// recorded as dropped rather than silently discarded — an invisible gap in the session total
|
|
1368
|
-
// is exactly what this whole channel exists to close.
|
|
1369
|
-
if (idx === -1)
|
|
1370
|
-
return drop('interaction message no longer in history');
|
|
1371
|
-
this.history[idx] = Object.assign(Object.assign({}, this.history[idx]), { externalCostUsd: ((_a = this.history[idx].externalCostUsd) !== null && _a !== void 0 ? _a : 0) + costUsd });
|
|
1372
|
-
recordMetaEvent(this.sessionKey, 'interaction.external-cost', { interactionId, costUsd });
|
|
1373
|
-
// A NARROW event, deliberately not `history-updated`.
|
|
1374
|
-
//
|
|
1375
|
-
// The host answers `history-updated` by reassigning `messages`, which re-evaluates
|
|
1376
|
-
// `activeInteractionRow` — and the trailing interaction slot renders through a
|
|
1377
|
-
// `repeat(..., { recycle: false })`, so every view in it is disposed and rebuilt regardless of
|
|
1378
|
-
// item identity. That is correct for a PENDING interaction (GENC-1429: a recycled wrapper
|
|
1379
|
-
// carries the previous interaction's truthy `resolved` and swallows the next completion), and
|
|
1380
|
-
// survivable because `buildInteraction` hands a remounted pending widget the same driver-owned
|
|
1381
|
-
// `interactionContext`.
|
|
1382
|
-
//
|
|
1383
|
-
// A RESOLVED one has no such context — `resolveInteraction` disposed and deleted it — so
|
|
1384
|
-
// announcing this on `history-updated` would tear down and blank the very widget that just
|
|
1385
|
-
// reported, in exactly the flow this channel exists for (a user tweaking an option after the
|
|
1386
|
-
// interaction resolved). Bookkeeping about a finished turn must stay invisible, so the host
|
|
1387
|
-
// recomputes its totals from this driver's history and saves, without touching what is
|
|
1388
|
-
// rendered. Reviewer catch on #2474 — confirmed against the layout widget, whose tweak result
|
|
1389
|
-
// went off screen because the rebuild raced the session write that would have restored it.
|
|
1390
|
-
this.dispatchEvent(new CustomEvent('external-cost-recorded', {
|
|
1391
|
-
detail: { interactionId, costUsd },
|
|
1392
|
-
}));
|
|
1393
|
-
return true;
|
|
1178
|
+
return this.interactions.recordExternalCost(interactionId, costUsd);
|
|
1394
1179
|
}
|
|
1395
1180
|
/**
|
|
1396
1181
|
* Resolve a pending interaction. The wrapper component calls this on completion.
|
|
1397
1182
|
* Marks the interaction message as resolved so it renders read-only on re-render.
|
|
1398
1183
|
*/
|
|
1399
1184
|
resolveInteraction(interactionId, result) {
|
|
1400
|
-
|
|
1401
|
-
const interaction = this.pendingInteractions.get(interactionId);
|
|
1402
|
-
if (interaction) {
|
|
1403
|
-
// Clear the timeout so a user resolution doesn't later fire a stale
|
|
1404
|
-
// timeout (and a timeout firing here clears its own already-fired handle).
|
|
1405
|
-
if (interaction.timeoutHandle)
|
|
1406
|
-
clearTimeout(interaction.timeoutHandle);
|
|
1407
|
-
// Harvest any out-of-band diagnostic entries the widget collated (e.g. from a server-side
|
|
1408
|
-
// ChatDriver) so the host's debug log + persisted diagnostics include them. Independent of the
|
|
1409
|
-
// message `idx` below — they describe the widget's own work, not this message.
|
|
1410
|
-
//
|
|
1411
|
-
// The harvested entries carry the SOURCE engine's own index space (0-based turns/events), which
|
|
1412
|
-
// would collide with this host's cursors in the forward-capture persister — turns dedup by
|
|
1413
|
-
// `turnIndex`, events by an `index` high-water mark — so foreign entries get silently dropped
|
|
1414
|
-
// from the persisted stream. Route each kind through the host's own collision-safe machinery,
|
|
1415
|
-
// mirroring how a sub-agent's log is folded in (see `invokeSubAgent`):
|
|
1416
|
-
// • `event` → `mergeMetaEvents`, which re-indexes them onto this session's monotonic event
|
|
1417
|
-
// counter (clearing the high-water) while preserving their timestamps; they then
|
|
1418
|
-
// reach the log via `getMetaEvents` in `buildTimelineEntries`, not this buffer.
|
|
1419
|
-
// • `turn` → re-key `turnIndex` to the turn's local position within a per-batch namespace,
|
|
1420
|
-
// so turns stay unique even when one interaction folds in several independent
|
|
1421
|
-
// server runs that each number their own turns from 0 (a consolidator carries a
|
|
1422
|
-
// consolidator-code run AND a table-code run), and never collide with the host's
|
|
1423
|
-
// own bare-integer turn keys.
|
|
1424
|
-
// • `message` → kept as-is; the persister already keys messages by content, so they survive.
|
|
1425
|
-
// TODO(GENC-1461, option 2 / streaming): expose an imperative `appendDiagnostics(entries)` on
|
|
1426
|
-
// the handler context (see buildHandlerContext) that runs this same decomposition as SSE ticks
|
|
1427
|
-
// arrive, so a streaming widget can surface its external engine's log live rather than only at
|
|
1428
|
-
// resolve time. Batch-at-resolve (here) is the current, non-streaming path.
|
|
1429
|
-
const harvestedDiagnostics = result === null || result === void 0 ? void 0 : result.diagnostics;
|
|
1430
|
-
if (Array.isArray(harvestedDiagnostics) && harvestedDiagnostics.length) {
|
|
1431
|
-
const batch = (this.externalDiagnosticsBatches += 1);
|
|
1432
|
-
const externalEvents = [];
|
|
1433
|
-
let turnCount = 0;
|
|
1434
|
-
let messageCount = 0;
|
|
1435
|
-
for (const raw of harvestedDiagnostics) {
|
|
1436
|
-
// The harvest is external and typed `unknown[]`, so an element could be a primitive, null,
|
|
1437
|
-
// or an object without a string `kind`. Guard before reading `.kind` so a malformed entry
|
|
1438
|
-
// can't throw here (and gets skipped rather than derailing the whole fold).
|
|
1439
|
-
if (!raw ||
|
|
1440
|
-
typeof raw !== 'object' ||
|
|
1441
|
-
typeof raw.kind !== 'string') {
|
|
1442
|
-
continue;
|
|
1443
|
-
}
|
|
1444
|
-
const entry = raw;
|
|
1445
|
-
if (entry.kind === 'event') {
|
|
1446
|
-
const { kind: _kind } = entry, event = __rest(entry, ["kind"]);
|
|
1447
|
-
externalEvents.push(event);
|
|
1448
|
-
}
|
|
1449
|
-
else if (entry.kind === 'turn') {
|
|
1450
|
-
// Local position within THIS harvest, not the source engine's own `turnIndex`: one
|
|
1451
|
-
// interaction can carry several independent server runs (a consolidator folds in its
|
|
1452
|
-
// consolidator-code run and its table-code run), each numbering its turns from 0, so
|
|
1453
|
-
// keying by the source index would collide them. The local position keeps every turn key
|
|
1454
|
-
// distinct within the batch, so the persister's per-`turnIndex` dedup keeps them all.
|
|
1455
|
-
this.externalDiagnostics.push(Object.assign(Object.assign({}, entry), { turnIndex: `server-generation.${batch}:${turnCount}` }));
|
|
1456
|
-
turnCount += 1;
|
|
1457
|
-
}
|
|
1458
|
-
else if (entry.kind === 'message') {
|
|
1459
|
-
messageCount += 1;
|
|
1460
|
-
this.externalDiagnostics.push(entry);
|
|
1461
|
-
}
|
|
1462
|
-
// Any other kind is intentionally dropped, not folded. A sibling's `buildTimelineEntries`
|
|
1463
|
-
// only ever emits message/turn/event; `meta-snapshot` is host/DOM-owned (latest-wins on
|
|
1464
|
-
// reassembly, plus its own persister dedup), so folding a foreign one would pollute this
|
|
1465
|
-
// host's timeline and meta cursor. Restricting to the three real kinds also keeps the
|
|
1466
|
-
// manifest counts accurate (nothing miscounted as a `message`).
|
|
1467
|
-
}
|
|
1468
|
-
if (externalEvents.length)
|
|
1469
|
-
mergeMetaEvents(this.sessionKey, externalEvents);
|
|
1470
|
-
// Manifest: one provenance marker for the fold itself — records that an out-of-band engine's
|
|
1471
|
-
// log crossed into this session and how much of each kind, so a regression that silently
|
|
1472
|
-
// drops a generation's diagnostics (e.g. a new path that forgets to thread them across the
|
|
1473
|
-
// boundary) surfaces as a zero/absent manifest rather than only by diffing a download. Also
|
|
1474
|
-
// the natural seed for the streaming PR's open/close bracket. Stamped at resolve, so it
|
|
1475
|
-
// closes the block on the sorted timeline. (GENC-1461.)
|
|
1476
|
-
recordMetaEvent(this.sessionKey, 'external-diagnostics.folded', {
|
|
1477
|
-
interactionId,
|
|
1478
|
-
batch,
|
|
1479
|
-
counts: { turn: turnCount, event: externalEvents.length, message: messageCount },
|
|
1480
|
-
});
|
|
1481
|
-
}
|
|
1482
|
-
const idx = this.history.findIndex((m) => { var _a; return ((_a = m.interaction) === null || _a === void 0 ? void 0 : _a.interactionId) === interactionId; });
|
|
1483
|
-
if (idx !== -1) {
|
|
1484
|
-
// Fold any widget-reported external (non-LLM) cost onto the message, so
|
|
1485
|
-
// the host's `sumCosts` recompute includes it in the session total.
|
|
1486
|
-
// Accumulates defensively in case a widget reports more than once.
|
|
1487
|
-
const costUsd = typeof (result === null || result === void 0 ? void 0 : result.costUsd) === 'number' &&
|
|
1488
|
-
Number.isFinite(result.costUsd) &&
|
|
1489
|
-
result.costUsd > 0
|
|
1490
|
-
? result.costUsd
|
|
1491
|
-
: undefined;
|
|
1492
|
-
this.history[idx] = Object.assign(Object.assign(Object.assign({}, this.history[idx]), { interaction: Object.assign(Object.assign({}, this.history[idx].interaction), { resolved: result }) }), (costUsd != null
|
|
1493
|
-
? { externalCostUsd: ((_a = this.history[idx].externalCostUsd) !== null && _a !== void 0 ? _a : 0) + costUsd }
|
|
1494
|
-
: {}));
|
|
1495
|
-
this.dispatchEvent(new CustomEvent('history-updated', {
|
|
1496
|
-
detail: this.history,
|
|
1497
|
-
}));
|
|
1498
|
-
}
|
|
1499
|
-
// A widget may opt into echoing its outcome on the user's side of the
|
|
1500
|
-
// chat by returning `userMessage`. Appended as a display-only
|
|
1501
|
-
// `'synthetic-user'` message: it is tracked in history (and the debug log)
|
|
1502
|
-
// and rendered like a user message, but transports never serialize it —
|
|
1503
|
-
// the model already learns the outcome from this handler's return value.
|
|
1504
|
-
// Appended before `interaction.resolve` so it precedes the tool result and
|
|
1505
|
-
// any follow-up turn the resumed handler produces.
|
|
1506
|
-
if (typeof (result === null || result === void 0 ? void 0 : result.userMessage) === 'string' && result.userMessage.trim()) {
|
|
1507
|
-
this.appendToHistory({ role: 'synthetic-user', content: result.userMessage });
|
|
1508
|
-
}
|
|
1509
|
-
if (interaction.overrideId) {
|
|
1510
|
-
this.dispatchEvent(new CustomEvent('interaction-stop', { detail: { interactionId } }));
|
|
1511
|
-
}
|
|
1512
|
-
recordMetaEvent(this.sessionKey, 'interaction.resolved', { interactionId });
|
|
1513
|
-
// The park is ending and the loop is about to resume computing — paired
|
|
1514
|
-
// with `interaction-requested`. Fires for every resolution path (user
|
|
1515
|
-
// completion, timeout, cancellation), since all route through here.
|
|
1516
|
-
this.activityBus.publish('interaction-resolved', undefined);
|
|
1517
|
-
interaction.resolve(result);
|
|
1518
|
-
this.pendingInteractions.delete(interactionId);
|
|
1519
|
-
// Tear down the live context on RESOLVE (not on element unmount) — this closes
|
|
1520
|
-
// any sockets/timers the widget parked on it via `onDispose`. (GENC-1390)
|
|
1521
|
-
(_b = this.interactionContexts.get(interactionId)) === null || _b === void 0 ? void 0 : _b.dispose();
|
|
1522
|
-
this.interactionContexts.delete(interactionId);
|
|
1523
|
-
}
|
|
1524
|
-
else {
|
|
1525
|
-
logger.warn(`Interaction with ID ${interactionId} not found.`);
|
|
1526
|
-
}
|
|
1185
|
+
this.interactions.resolve(interactionId, result);
|
|
1527
1186
|
}
|
|
1528
1187
|
/**
|
|
1529
1188
|
* The live {@link InteractionContext} for a pending interaction (GENC-1390), or
|
|
@@ -1531,24 +1190,18 @@ export class ChatDriver extends EventTarget {
|
|
|
1531
1190
|
* host) to build the widget's pending `interaction` descriptor.
|
|
1532
1191
|
*/
|
|
1533
1192
|
getInteractionContext(interactionId) {
|
|
1534
|
-
|
|
1535
|
-
return (_a = this.interactionContexts.get(interactionId)) === null || _a === void 0 ? void 0 : _a.context;
|
|
1193
|
+
return this.interactions.context(interactionId);
|
|
1536
1194
|
}
|
|
1537
1195
|
/**
|
|
1538
|
-
*
|
|
1539
|
-
*
|
|
1540
|
-
* the widget is parked in *this* driver's map (interactions resolve on the
|
|
1541
|
-
* host, not the child), so cancelling it here unblocks the sub-agent handler
|
|
1542
|
-
* awaiting it. The "one interaction at a time" invariant means there is at
|
|
1543
|
-
* most one.
|
|
1196
|
+
* Replace the history message owning `interactionId`. `false` when it is no longer in history.
|
|
1197
|
+
* Does not announce the change — the interaction paths differ on whether they should.
|
|
1544
1198
|
*/
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
}
|
|
1199
|
+
patchInteractionMessage(interactionId, patch) {
|
|
1200
|
+
const idx = this.history.findIndex((m) => { var _a; return ((_a = m.interaction) === null || _a === void 0 ? void 0 : _a.interactionId) === interactionId; });
|
|
1201
|
+
if (idx === -1)
|
|
1202
|
+
return false;
|
|
1203
|
+
this.history[idx] = patch(this.history[idx]);
|
|
1204
|
+
return true;
|
|
1552
1205
|
}
|
|
1553
1206
|
/**
|
|
1554
1207
|
* Seeds the driver with a prior conversation history so a new component instance
|
|
@@ -1556,9 +1209,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1556
1209
|
*/
|
|
1557
1210
|
loadHistory(messages) {
|
|
1558
1211
|
this.history = [...messages];
|
|
1559
|
-
this.
|
|
1560
|
-
detail: this.history,
|
|
1561
|
-
}));
|
|
1212
|
+
this.notifyHistoryUpdated();
|
|
1562
1213
|
}
|
|
1563
1214
|
sendMessage(userInput, attachments) {
|
|
1564
1215
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1831,7 +1482,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1831
1482
|
// interaction to close the widget and unblock the child. Without this,
|
|
1832
1483
|
// `await runPromise` below would hang forever and the timeout would
|
|
1833
1484
|
// never complete (or record `subagent.failed`).
|
|
1834
|
-
this.
|
|
1485
|
+
this.interactions.cancelAll(`Sub-agent "${name}" timed out after ${timeoutMs}ms.`);
|
|
1835
1486
|
yield runPromise.catch(() => undefined);
|
|
1836
1487
|
}
|
|
1837
1488
|
}
|
|
@@ -1860,7 +1511,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1860
1511
|
// the registry, and a crashed sub-agent still leaves its turns/events behind.
|
|
1861
1512
|
// Forward the child's per-LLM-call snapshots so they show as `kind:'turn'`
|
|
1862
1513
|
// entries in the exported debug log, re-numbered under the activating parent turn.
|
|
1863
|
-
this.
|
|
1514
|
+
this.turnRecorder.forwardSubAgent(child.getTurnSnapshots());
|
|
1864
1515
|
// Fold the sub-agent's high-value meta events (retries/errors/tool failures —
|
|
1865
1516
|
// see HARVESTED_SUBAGENT_EVENTS) into THIS session, preserving their original
|
|
1866
1517
|
// timestamps so they interleave within the subagent.started→completed/failed
|
|
@@ -2003,7 +1654,10 @@ export class ChatDriver extends EventTarget {
|
|
|
2003
1654
|
// ---------------------------------------------------------------------------
|
|
2004
1655
|
// Tool loop
|
|
2005
1656
|
// ---------------------------------------------------------------------------
|
|
2006
|
-
//
|
|
1657
|
+
// Over both ceilings deliberately: the per-iteration phases share a dozen accumulators, so
|
|
1658
|
+
// splitting threads that state through signatures rather than removing it. `turn-setup.ts`
|
|
1659
|
+
// (docs/code-health.md §11.3) is the intended carve-up.
|
|
1660
|
+
// oxlint-disable-next-line complexity, max-lines-per-function
|
|
2007
1661
|
runToolLoop(userInput, attachments, transientPrimer) {
|
|
2008
1662
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2009
1663
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
@@ -2251,7 +1905,15 @@ export class ChatDriver extends EventTarget {
|
|
|
2251
1905
|
// on a free-text answer; top-level agents stay 'auto'. (Transports no-op a
|
|
2252
1906
|
// force when no tools are advertised.)
|
|
2253
1907
|
const effectiveToolChoice = resolvedToolChoice !== null && resolvedToolChoice !== void 0 ? resolvedToolChoice : (this.isSubAgent ? 'required' : undefined);
|
|
2254
|
-
const turnSnapshot = this.
|
|
1908
|
+
const turnSnapshot = this.turnRecorder.record({
|
|
1909
|
+
agentName: this.activeAgentName,
|
|
1910
|
+
agentLabel: this.activeAgentLabel,
|
|
1911
|
+
systemPrompt,
|
|
1912
|
+
tailContext,
|
|
1913
|
+
toolNames: this.toolDefinitions.map((t) => t.name),
|
|
1914
|
+
temperature: resolvedTemperature,
|
|
1915
|
+
toolChoice: effectiveToolChoice,
|
|
1916
|
+
});
|
|
2255
1917
|
// Capture the pending user input, then clear the slots BEFORE the chat
|
|
2256
1918
|
// call. `sendMessage` already appended the user message to `this.history`,
|
|
2257
1919
|
// so on retries (empty / malformed) we must rely on history alone —
|
|
@@ -2307,7 +1969,7 @@ export class ChatDriver extends EventTarget {
|
|
|
2307
1969
|
// oxlint-disable-next-line no-await-in-loop
|
|
2308
1970
|
const activeProvider = yield this.resolveProviderForTurn(promptCtx);
|
|
2309
1971
|
// Attribute the turn to the tier/model it resolved. Stamped HERE, not inside
|
|
2310
|
-
// `
|
|
1972
|
+
// `TurnRecorder.record`: the snapshot is taken before this line runs, so reading
|
|
2311
1973
|
// `lastResolved*` there yields the PREVIOUS call's model — wrong on precisely the
|
|
2312
1974
|
// turn where an agent's per-state `provider` selector switches tier, which is the
|
|
2313
1975
|
// turn a reader is looking for. `model` is refined to the serving model once the
|
|
@@ -2909,9 +2571,7 @@ export class ChatDriver extends EventTarget {
|
|
|
2909
2571
|
stale: staleToolIds.has(tc.id) || undefined, subAgentTrace: (_e = executedById.get(tc.id)) === null || _e === void 0 ? void 0 : _e.subAgentTrace });
|
|
2910
2572
|
});
|
|
2911
2573
|
this.history[tcMsgIdx] = Object.assign(Object.assign({}, tcMsg), { toolCalls: annotatedCalls });
|
|
2912
|
-
this.
|
|
2913
|
-
detail: this.history,
|
|
2914
|
-
}));
|
|
2574
|
+
this.notifyHistoryUpdated();
|
|
2915
2575
|
}
|
|
2916
2576
|
if (hitUnknownToolLimit) {
|
|
2917
2577
|
logger.error(`ChatDriver: unknown-tool limit (${DEFAULT_MAX_UNKNOWN_TOOL_CALLS}) reached — stopping`);
|
|
@@ -3005,6 +2665,10 @@ export class ChatDriver extends EventTarget {
|
|
|
3005
2665
|
}
|
|
3006
2666
|
: {}));
|
|
3007
2667
|
this.history = [...this.history, tagged];
|
|
2668
|
+
this.notifyHistoryUpdated();
|
|
2669
|
+
}
|
|
2670
|
+
/** Announce the current history to the host. */
|
|
2671
|
+
notifyHistoryUpdated() {
|
|
3008
2672
|
this.dispatchEvent(new CustomEvent('history-updated', {
|
|
3009
2673
|
detail: this.history,
|
|
3010
2674
|
}));
|