@genesislcap/ai-assistant 14.467.1 → 14.467.2-GENC-1369.2
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 +39 -53
- package/dist/ai-assistant.d.ts +59 -26
- package/dist/dts/components/chat-driver/chat-driver.d.ts +38 -0
- package/dist/dts/components/chat-driver/chat-driver.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 +1 -20
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/state/debug-event-log.d.ts +17 -1
- package/dist/dts/state/debug-event-log.d.ts.map +1 -1
- package/dist/dts/state/debug-event-log.test.d.ts +2 -0
- package/dist/dts/state/debug-event-log.test.d.ts.map +1 -0
- package/dist/dts/utils/condense-history.d.ts +115 -0
- package/dist/dts/utils/condense-history.d.ts.map +1 -0
- package/dist/dts/utils/condense-history.test.d.ts +2 -0
- package/dist/dts/utils/condense-history.test.d.ts.map +1 -0
- package/dist/dts/utils/flatten-sub-agent-messages.d.ts +51 -0
- package/dist/dts/utils/flatten-sub-agent-messages.d.ts.map +1 -0
- package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts +2 -0
- package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts.map +1 -0
- package/dist/dts/utils/strip-agent-handlers.d.ts +29 -0
- package/dist/dts/utils/strip-agent-handlers.d.ts.map +1 -0
- package/dist/dts/utils/strip-agent-handlers.test.d.ts +2 -0
- package/dist/dts/utils/strip-agent-handlers.test.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +156 -19
- package/dist/esm/components/chat-driver/chat-driver.test.js +225 -1
- package/dist/esm/main/main.js +14 -38
- package/dist/esm/state/debug-event-log.js +50 -2
- package/dist/esm/state/debug-event-log.test.js +67 -0
- package/dist/esm/utils/condense-history.js +218 -0
- package/dist/esm/utils/condense-history.test.js +297 -0
- package/dist/esm/utils/flatten-sub-agent-messages.js +49 -0
- package/dist/esm/utils/flatten-sub-agent-messages.test.js +139 -0
- package/dist/esm/utils/strip-agent-handlers.js +51 -0
- package/dist/esm/utils/strip-agent-handlers.test.js +81 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/components/chat-driver/chat-driver.test.ts +276 -1
- package/src/components/chat-driver/chat-driver.ts +184 -16
- package/src/index.ts +1 -0
- package/src/main/main.ts +16 -37
- package/src/state/debug-event-log.test.ts +89 -0
- package/src/state/debug-event-log.ts +52 -2
- package/src/utils/condense-history.test.ts +373 -0
- package/src/utils/condense-history.ts +306 -0
- package/src/utils/flatten-sub-agent-messages.test.ts +163 -0
- package/src/utils/flatten-sub-agent-messages.ts +88 -0
- package/src/utils/strip-agent-handlers.test.ts +99 -0
- package/src/utils/strip-agent-handlers.ts +52 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
+
/**
|
|
3
|
+
* A chat message lifted into the debug-log timeline as a `kind: 'message'` entry.
|
|
4
|
+
*
|
|
5
|
+
* @beta
|
|
6
|
+
*/
|
|
7
|
+
export type TimelineMessage = ChatMessage & {
|
|
8
|
+
kind: 'message';
|
|
9
|
+
/**
|
|
10
|
+
* Set only on entries hoisted out of a `subAgentTrace`: the sub-agent's own
|
|
11
|
+
* (un-breadcrumbed) `agentName`, kept for filtering since the displayed
|
|
12
|
+
* `agentName` is rewritten to a `"<parent> › <sub-agent>"` breadcrumb.
|
|
13
|
+
*/
|
|
14
|
+
subAgentName?: string;
|
|
15
|
+
/** Delegation depth — absent/0 for top-level, 1 for a sub-agent, 2 for a sub-agent's sub-agent, … */
|
|
16
|
+
subAgentDepth?: number;
|
|
17
|
+
/** Id of the parent tool call that spawned this run — correlates a hoisted message back to its delegation. */
|
|
18
|
+
subAgentOf?: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Flatten sub-agent conversations into a single top-level message stream for the
|
|
22
|
+
* debug-log timeline.
|
|
23
|
+
*
|
|
24
|
+
* A tool call that delegates to a sub-agent carries the sub-agent's entire
|
|
25
|
+
* conversation on `toolCall.subAgentTrace`. The live UI reads that nested shape,
|
|
26
|
+
* but a top-to-bottom timeline reads far better with those messages inline next to
|
|
27
|
+
* the turn that produced them. This walks the list and, for every tool call with a
|
|
28
|
+
* `subAgentTrace`, hoists the sub-agent's messages up as their own timeline
|
|
29
|
+
* entries while **removing** the nested copy from the emitted parent tool call — a
|
|
30
|
+
* move, not a copy. The data is relocated, never duplicated: the export stays the
|
|
31
|
+
* same size and a naive `sum(timeline[].cost)` does not double-count (the
|
|
32
|
+
* canonical `sumCosts` total still reads the un-flattened history).
|
|
33
|
+
*
|
|
34
|
+
* Hoisted entries are:
|
|
35
|
+
* - **breadcrumbed**: `agentName` becomes `"<parent> › <sub-agent>"`, composing for
|
|
36
|
+
* nested sub-agents (`"<parent> › <sub> › <subsub>"`); the raw sub-agent name is
|
|
37
|
+
* preserved on `subAgentName`.
|
|
38
|
+
* - **depth-tagged** (`subAgentDepth`) and **correlated** to the spawning tool call
|
|
39
|
+
* (`subAgentOf`), so the delegation tree is reconstructable even when two
|
|
40
|
+
* sub-agents run within the same parent turn.
|
|
41
|
+
*
|
|
42
|
+
* Top-level messages (depth 0) are emitted unchanged apart from the trace strip.
|
|
43
|
+
* Order is preserved within each list; the caller sorts the whole timeline by
|
|
44
|
+
* timestamp afterwards, and every sub-agent message carries its own timestamp, so
|
|
45
|
+
* hoisted entries interleave chronologically between the parent's tool-call and
|
|
46
|
+
* tool-result messages.
|
|
47
|
+
*
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export declare function flattenSubAgentMessages(messages: readonly ChatMessage[], prefix?: string, depth?: number, subAgentOf?: string): TimelineMessage[];
|
|
51
|
+
//# sourceMappingURL=flatten-sub-agent-messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flatten-sub-agent-messages.d.ts","sourceRoot":"","sources":["../../../src/utils/flatten-sub-agent-messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C,IAAI,EAAE,SAAS,CAAC;IAChB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qGAAqG;IACrG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8GAA8G;IAC9G,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,MAAM,SAAK,EACX,KAAK,SAAI,EACT,UAAU,CAAC,EAAE,MAAM,GAClB,eAAe,EAAE,CA+BnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flatten-sub-agent-messages.test.d.ts","sourceRoot":"","sources":["../../../src/utils/flatten-sub-agent-messages.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AgentConfig } from '../config/config';
|
|
2
|
+
/**
|
|
3
|
+
* Project an `AgentConfig` down to a JSON-serializable shape for the redux
|
|
4
|
+
* session store. Drops two kinds of field:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Directly function-valued** — the lifecycle/dispatch hooks (`onActivate`,
|
|
7
|
+
* `onDeactivate`, `getDebugSnapshot`, `onUnresolvedTool`) and the function
|
|
8
|
+
* form of the per-turn resolvers (`systemPrompt`, `toolDefinitions`,
|
|
9
|
+
* `displayName`, `provider`, `temperature`, `toolChoice`, `toolHandlers`).
|
|
10
|
+
* 2. **Object "handler bags" whose *values* are functions** — `toolHandlers` in
|
|
11
|
+
* its object form is `{ name: handler }`, so `typeof` is `'object'`, not
|
|
12
|
+
* `'function'`. A by-value check on the field alone misses it, leaking a live
|
|
13
|
+
* handler into store state and tripping redux's serializability check (the
|
|
14
|
+
* regression this guards against — top-level agents tend to use the factory
|
|
15
|
+
* function form, but sub-agents commonly declare an object literal).
|
|
16
|
+
*
|
|
17
|
+
* `subAgents` are projected recursively. Static forms (string / number / array /
|
|
18
|
+
* plain data object) pass through unchanged.
|
|
19
|
+
*
|
|
20
|
+
* Filtering by value rather than an explicit field list means a new
|
|
21
|
+
* function-valued field on `AgentConfig` is handled automatically — no denylist
|
|
22
|
+
* to maintain. The live config on the driver stays the source of truth; the
|
|
23
|
+
* slice only holds this serializable projection and functions are never read
|
|
24
|
+
* back from it.
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export declare function stripAgentHandlers(agent: AgentConfig): Omit<AgentConfig, 'toolHandlers'>;
|
|
29
|
+
//# sourceMappingURL=strip-agent-handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strip-agent-handlers.d.ts","sourceRoot":"","sources":["../../../src/utils/strip-agent-handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAuBxF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strip-agent-handlers.test.d.ts","sourceRoot":"","sources":["../../../src/utils/strip-agent-handlers.test.ts"],"names":[],"mappings":""}
|
|
@@ -2,7 +2,8 @@ import { __awaiter } from "tslib";
|
|
|
2
2
|
import { isObservableAIProviderRegistry, MalformedFunctionCallError, } from '@genesislcap/foundation-ai';
|
|
3
3
|
import { agenticActivityBus } from '../../channel/ai-activity-bus';
|
|
4
4
|
import { resolveChatProvider } from '../../config/validate-providers';
|
|
5
|
-
import { recordMetaEvent, recordTurnError, recordTurnRetry } from '../../state/debug-event-log';
|
|
5
|
+
import { clearSession, getMetaEvents, mergeMetaEvents, recordMetaEvent, recordTurnError, recordTurnRetry, } from '../../state/debug-event-log';
|
|
6
|
+
import { applyCondensation } from '../../utils/condense-history';
|
|
6
7
|
import { applyHistoryCap } from '../../utils/history-transform';
|
|
7
8
|
import { logger } from '../../utils/logger';
|
|
8
9
|
import { TOOL_FOLD_SYMBOL } from '../../utils/tool-fold';
|
|
@@ -32,6 +33,22 @@ const MAX_EMPTY_RESPONSE_RETRIES = 3;
|
|
|
32
33
|
// SAME iteration up to this many times before propagating, rather than tearing down the turn.
|
|
33
34
|
const MAX_SETUP_TRANSPORT_RETRIES = 3;
|
|
34
35
|
const SUGGESTIONS_HISTORY_WINDOW = 8;
|
|
36
|
+
/**
|
|
37
|
+
* Sub-agent meta events worth folding into the parent's debug timeline: the
|
|
38
|
+
* per-attempt and per-failure signals that do NOT otherwise surface in the
|
|
39
|
+
* sub-agent's (now hoisted) messages — a retried-away malformed/empty attempt
|
|
40
|
+
* produces no message, and the stale-vs-hallucinated/streak diagnostics live only
|
|
41
|
+
* on the event. High-volume, message-derivable events (turn.start/turn.end,
|
|
42
|
+
* provider.selected, context.updated) are intentionally excluded: read the
|
|
43
|
+
* sub-agent's hoisted messages for model/tokens/cost and turn-by-turn activity.
|
|
44
|
+
* See `ChatDriver.invokeSubAgent`.
|
|
45
|
+
*/
|
|
46
|
+
const HARVESTED_SUBAGENT_EVENTS = new Set([
|
|
47
|
+
'turn.retry',
|
|
48
|
+
'turn.error',
|
|
49
|
+
'tool.failed',
|
|
50
|
+
'tool.unresolved',
|
|
51
|
+
]);
|
|
35
52
|
/** Name reserved for the cross-agent handoff tool — injected by OrchestratingDriver. */
|
|
36
53
|
export const REQUEST_CONTINUATION_TOOL = 'request_continuation';
|
|
37
54
|
/** Paired in history for each `request_continuation` so tool_calls stay balanced for the provider. */
|
|
@@ -59,6 +76,40 @@ export class ChatDriver extends EventTarget {
|
|
|
59
76
|
/** Epoch ms when the current turn loop began — drives the `turn.end` duration. */
|
|
60
77
|
this.turnStartedAt = 0;
|
|
61
78
|
this.pendingInteractions = new Map();
|
|
79
|
+
/**
|
|
80
|
+
* Tool-declared condensation policies, keyed by tool-call id. Populated by
|
|
81
|
+
* `condenseWhen` (first-wins per call); read by `applyCondensation` before each
|
|
82
|
+
* provider call to collapse stale payloads from the model-bound history only.
|
|
83
|
+
* Accumulates across agents on a shared driver (a superseded read collapses no
|
|
84
|
+
* matter which agent made it) and is never cleared — it dies with the driver.
|
|
85
|
+
*/
|
|
86
|
+
this.condensePolicies = new Map();
|
|
87
|
+
/**
|
|
88
|
+
* Monotonic model-call counter for the driver's whole lifetime — the age clock
|
|
89
|
+
* for `condenseWhen({ on: { kind: 'age' } })`. The per-`sendMessage` `iterations`
|
|
90
|
+
* loop counter resets to 0 every turn, so it can only measure age WITHIN a
|
|
91
|
+
* single turn; this never resets, so `age` counts model-calls since the result
|
|
92
|
+
* appeared across turn boundaries (each short turn still advances it ≥ 1).
|
|
93
|
+
* Bumped once per tool-loop iteration (provider call).
|
|
94
|
+
*/
|
|
95
|
+
this.modelCallSeq = 0;
|
|
96
|
+
/**
|
|
97
|
+
* Monotonic turn counter — the `turnEnd` clock. Bumped once per `sendMessage`
|
|
98
|
+
* (a user turn; NOT per handoff continuation, which is the same request), never
|
|
99
|
+
* reset. `turnEnd` collapses a payload once `turnSeq` exceeds the turn it was
|
|
100
|
+
* created in.
|
|
101
|
+
*/
|
|
102
|
+
this.turnSeq = 0;
|
|
103
|
+
/**
|
|
104
|
+
* Monotonic agent-activation counter — the `agentEnd` clock. Advances when the
|
|
105
|
+
* active flow ends: a swap to a different-named agent (`applyAgent`) OR an
|
|
106
|
+
* explicit `releaseAgent` / `completeSubAgent`. A stateful agent re-resolving
|
|
107
|
+
* the same name across turns keeps one activation. `agentEnd` fires for a
|
|
108
|
+
* payload once a LATER activation is current (`callActivation < currentActivation`)
|
|
109
|
+
* — so a re-run of a released agent gets a fresh activation and is NOT
|
|
110
|
+
* collapsed until IT ends.
|
|
111
|
+
*/
|
|
112
|
+
this.currentActivation = 0;
|
|
62
113
|
/** Stack of fold frames — grows when a fold opens, shrinks when it closes. */
|
|
63
114
|
this.foldStack = [];
|
|
64
115
|
/** Consecutive fold open/close ops without a real tool call. Reset on real tool execution. */
|
|
@@ -264,6 +315,12 @@ export class ChatDriver extends EventTarget {
|
|
|
264
315
|
*/
|
|
265
316
|
applyAgent(config) {
|
|
266
317
|
var _a, _b, _c;
|
|
318
|
+
// A real swap to a different agent begins a NEW activation — the prior agent's
|
|
319
|
+
// flow is over, so its `agentEnd`-tagged payloads become collapsible. Guarded
|
|
320
|
+
// on a name change so re-applying the same agent (e.g. a per-turn re-resolve)
|
|
321
|
+
// does not spuriously advance the clock and prematurely drop its payloads.
|
|
322
|
+
if (config.name !== this.activeAgentName)
|
|
323
|
+
this.currentActivation += 1;
|
|
267
324
|
this.systemPrompt = config.systemPrompt;
|
|
268
325
|
if (typeof config.toolDefinitions === 'function') {
|
|
269
326
|
this.toolDefinitionsFactory = config.toolDefinitions;
|
|
@@ -857,6 +914,9 @@ export class ChatDriver extends EventTarget {
|
|
|
857
914
|
return { reason: 'done' };
|
|
858
915
|
this.busy = true;
|
|
859
916
|
this.beginTurn();
|
|
917
|
+
// A new user turn — advances the `turnEnd` clock (continuations after a
|
|
918
|
+
// handoff stay on the same turn; they go through `continueFromHistory`).
|
|
919
|
+
this.turnSeq += 1;
|
|
860
920
|
this.subAgentCompletion = undefined;
|
|
861
921
|
this.subAgentFailure = undefined;
|
|
862
922
|
this.agentReleaseRequested = false;
|
|
@@ -902,11 +962,15 @@ export class ChatDriver extends EventTarget {
|
|
|
902
962
|
* Centralised here so fold shortcut dispatch and the main tool loop use the
|
|
903
963
|
* same context without duplication.
|
|
904
964
|
*
|
|
965
|
+
* @param activeToolCallId - The id of the tool call this context belongs to, so
|
|
966
|
+
* `condenseWhen` can register against the right call (it stamps the clocks
|
|
967
|
+
* straight off the driver). Absent for dispatch paths with no addressable tool
|
|
968
|
+
* call (e.g. the fold-close handler), where `condenseWhen` is a no-op.
|
|
905
969
|
* @param traceCapture - Optional per-invocation slot. When provided, the trace
|
|
906
970
|
* from any sub-agent call is written here rather than to shared instance state,
|
|
907
971
|
* so parallel tool calls each capture their own trace independently.
|
|
908
972
|
*/
|
|
909
|
-
buildHandlerContext(traceCapture) {
|
|
973
|
+
buildHandlerContext(activeToolCallId, traceCapture) {
|
|
910
974
|
return Object.assign(Object.assign({ requestInteraction: (componentName, data, options) => this.requestInteraction(componentName, data, options) }, (this.subAgentsMap.size > 0 && {
|
|
911
975
|
requestSubAgent: (name, options) => this.invokeSubAgent(name, options).then(({ outcome, trace }) => {
|
|
912
976
|
if (traceCapture)
|
|
@@ -920,6 +984,9 @@ export class ChatDriver extends EventTarget {
|
|
|
920
984
|
return;
|
|
921
985
|
}
|
|
922
986
|
this.subAgentCompletion = { result };
|
|
987
|
+
// This activation's flow has ended — advance the clock so its
|
|
988
|
+
// `agentEnd` payloads collapse and a re-run starts a fresh activation.
|
|
989
|
+
this.currentActivation += 1;
|
|
923
990
|
}, releaseAgent: () => {
|
|
924
991
|
var _a;
|
|
925
992
|
if (this.agentReleaseRequested) {
|
|
@@ -927,6 +994,42 @@ export class ChatDriver extends EventTarget {
|
|
|
927
994
|
return;
|
|
928
995
|
}
|
|
929
996
|
this.agentReleaseRequested = true;
|
|
997
|
+
// The stateful flow has wrapped up — advance the clock so its `agentEnd`
|
|
998
|
+
// payloads collapse and a re-run starts a fresh activation.
|
|
999
|
+
this.currentActivation += 1;
|
|
1000
|
+
}, condenseWhen: (policy) => {
|
|
1001
|
+
var _a, _b, _c;
|
|
1002
|
+
// No addressable tool call (e.g. fold-close handler) — nothing to attach to.
|
|
1003
|
+
if (!activeToolCallId)
|
|
1004
|
+
return;
|
|
1005
|
+
if (!(policy === null || policy === void 0 ? void 0 : policy.args) && !(policy === null || policy === void 0 ? void 0 : policy.response)) {
|
|
1006
|
+
logger.warn(`ChatDriver(${(_a = this.activeAgentName) !== null && _a !== void 0 ? _a : 'unknown'}): condenseWhen called with neither args nor response — ignoring`);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
const triggers = Array.isArray(policy.on) ? policy.on : [policy.on];
|
|
1010
|
+
const validTrigger = (t) => (t === null || t === void 0 ? void 0 : t.kind) === 'superseded'
|
|
1011
|
+
? typeof t.by === 'string' && t.by.length > 0
|
|
1012
|
+
: (t === null || t === void 0 ? void 0 : t.kind) === 'age'
|
|
1013
|
+
? typeof t.turns === 'number' && t.turns >= 1
|
|
1014
|
+
: (t === null || t === void 0 ? void 0 : t.kind) === 'turnEnd' || (t === null || t === void 0 ? void 0 : t.kind) === 'agentEnd';
|
|
1015
|
+
if (triggers.length === 0 || !triggers.every(validTrigger)) {
|
|
1016
|
+
logger.warn(`ChatDriver(${(_b = this.activeAgentName) !== null && _b !== void 0 ? _b : 'unknown'}): condenseWhen called with an invalid trigger — ignoring`);
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
// First-wins: a handler declares its policy once per call. A second
|
|
1020
|
+
// declaration for the same tool call is a handler bug, not a refinement.
|
|
1021
|
+
if (this.condensePolicies.has(activeToolCallId)) {
|
|
1022
|
+
logger.error(`ChatDriver(${(_c = this.activeAgentName) !== null && _c !== void 0 ? _c : 'unknown'}): condenseWhen called more than once for the same tool call — keeping the first policy, ignoring this one`);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
// Stamp the three clocks NOW (read straight off the driver — the handler
|
|
1026
|
+
// runs during the current model-call's tool batch).
|
|
1027
|
+
this.condensePolicies.set(activeToolCallId, {
|
|
1028
|
+
policy,
|
|
1029
|
+
iteration: this.modelCallSeq,
|
|
1030
|
+
turn: this.turnSeq,
|
|
1031
|
+
activation: this.currentActivation,
|
|
1032
|
+
});
|
|
930
1033
|
} });
|
|
931
1034
|
}
|
|
932
1035
|
/**
|
|
@@ -937,7 +1040,7 @@ export class ChatDriver extends EventTarget {
|
|
|
937
1040
|
*/
|
|
938
1041
|
invokeSubAgent(name, options) {
|
|
939
1042
|
return __awaiter(this, void 0, void 0, function* () {
|
|
940
|
-
var _a, _b, _c, _d;
|
|
1043
|
+
var _a, _b, _c, _d, _e;
|
|
941
1044
|
const subConfig = this.subAgentsMap.get(name);
|
|
942
1045
|
if (!subConfig) {
|
|
943
1046
|
const available = [...this.subAgentsMap.keys()].join(', ') || '(none)';
|
|
@@ -960,7 +1063,13 @@ export class ChatDriver extends EventTarget {
|
|
|
960
1063
|
...contextMessages,
|
|
961
1064
|
...((_b = subConfig.primerHistory) !== null && _b !== void 0 ? _b : []),
|
|
962
1065
|
];
|
|
963
|
-
|
|
1066
|
+
// Unique per-invocation id — reused for the lifecycle event bracket below —
|
|
1067
|
+
// and a child session key derived from it. The child files its meta events
|
|
1068
|
+
// under this own bucket (rather than the shared empty-key sink), so they can
|
|
1069
|
+
// be harvested into THIS session on completion and then discarded.
|
|
1070
|
+
const invocationId = crypto.randomUUID();
|
|
1071
|
+
const childSessionKey = `${this.sessionKey}::sub:${invocationId}`;
|
|
1072
|
+
const child = new ChatDriver(this.providerRegistry, {}, [], undefined, undefined, undefined, undefined, undefined, childSessionKey);
|
|
964
1073
|
// Mark before the first turn so the child forces tool use and reports a
|
|
965
1074
|
// typed failure (rather than user-facing text) if it never completes.
|
|
966
1075
|
child.markAsSubAgent();
|
|
@@ -994,9 +1103,6 @@ export class ChatDriver extends EventTarget {
|
|
|
994
1103
|
};
|
|
995
1104
|
child.addEventListener('history-updated', forwardTrace);
|
|
996
1105
|
child.addEventListener('provider-changed', forwardProviderChanged);
|
|
997
|
-
// Unique per-invocation id so listeners can pair start/stop reliably even
|
|
998
|
-
// when the same sub-agent runs multiple times in parallel.
|
|
999
|
-
const invocationId = crypto.randomUUID();
|
|
1000
1106
|
const chatInputDuringExecution = options === null || options === void 0 ? void 0 : options.chatInputDuringExecution;
|
|
1001
1107
|
const lifecycleDetail = { name, invocationId, chatInputDuringExecution };
|
|
1002
1108
|
this.dispatchEvent(new CustomEvent('sub-agent-start', { detail: lifecycleDetail }));
|
|
@@ -1056,13 +1162,30 @@ export class ChatDriver extends EventTarget {
|
|
|
1056
1162
|
// settled) lifecycle, so the snapshot/completion reads below still work.
|
|
1057
1163
|
child.dispose();
|
|
1058
1164
|
this.dispatchEvent(new CustomEvent('sub-agent-stop', { detail: lifecycleDetail }));
|
|
1165
|
+
// Capture the child's diagnostics into THIS session, then ALWAYS discard its
|
|
1166
|
+
// transient bucket — done in the `finally` so an unexpected `sendMessage`
|
|
1167
|
+
// rejection (which propagates out of this method) can't orphan the bucket in
|
|
1168
|
+
// the registry, and a crashed sub-agent still leaves its turns/events behind.
|
|
1169
|
+
// Forward the child's per-LLM-call snapshots so they show as `kind:'turn'`
|
|
1170
|
+
// entries in the exported debug log, re-numbered under the activating parent turn.
|
|
1171
|
+
this.forwardSubAgentSnapshots(child.getTurnSnapshots());
|
|
1172
|
+
// Fold the sub-agent's high-value meta events (retries/errors/tool failures —
|
|
1173
|
+
// see HARVESTED_SUBAGENT_EVENTS) into THIS session, preserving their original
|
|
1174
|
+
// timestamps so they interleave within the subagent.started→completed/failed
|
|
1175
|
+
// bracket. Each is breadcrumbed `"<parent> › <sub-agent>"`, composing for a
|
|
1176
|
+
// nested sub-agent whose own breadcrumb the child already merged.
|
|
1177
|
+
const parentName = (_c = this.activeAgentName) !== null && _c !== void 0 ? _c : '?';
|
|
1178
|
+
const harvested = getMetaEvents(childSessionKey)
|
|
1179
|
+
.filter((e) => HARVESTED_SUBAGENT_EVENTS.has(e.type))
|
|
1180
|
+
.map((e) => {
|
|
1181
|
+
var _a;
|
|
1182
|
+
const existing = (_a = e.detail) === null || _a === void 0 ? void 0 : _a.subAgent;
|
|
1183
|
+
return Object.assign(Object.assign({}, e), { detail: Object.assign(Object.assign({}, e.detail), { subAgent: `${parentName} › ${existing !== null && existing !== void 0 ? existing : name}` }) });
|
|
1184
|
+
});
|
|
1185
|
+
mergeMetaEvents(this.sessionKey, harvested);
|
|
1186
|
+
clearSession(childSessionKey);
|
|
1059
1187
|
}
|
|
1060
1188
|
const trace = child.getHistory();
|
|
1061
|
-
// Forward the child's per-LLM-call snapshots onto this (parent) driver's
|
|
1062
|
-
// buffer so they show as `kind:'turn'` entries in the exported debug log,
|
|
1063
|
-
// re-numbered under the activating parent turn. Runs for both success and
|
|
1064
|
-
// failure so the sub-agent's turns are always visible.
|
|
1065
|
-
this.forwardSubAgentSnapshots(child.getTurnSnapshots());
|
|
1066
1189
|
if (timedOut) {
|
|
1067
1190
|
// Same failure shape as any other non-completion — the parent handler
|
|
1068
1191
|
// recovers on its existing `{ ok: false }` branch. Recorded under THIS
|
|
@@ -1081,7 +1204,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1081
1204
|
// provider ignored forced tool use and returned text). The previous
|
|
1082
1205
|
// final-text fallback is intentionally gone — sub-agents return a
|
|
1083
1206
|
// structured outcome only, and the parent handler decides how to recover.
|
|
1084
|
-
const reason = (
|
|
1207
|
+
const reason = (_e = (_d = child.getSubAgentFailure()) === null || _d === void 0 ? void 0 : _d.reason) !== null && _e !== void 0 ? _e : 'max_iterations';
|
|
1085
1208
|
// Record under THIS (parent) driver's session so the failure lands on the
|
|
1086
1209
|
// user-visible debug-log timeline — the child ran under its own session key.
|
|
1087
1210
|
// This is also the only telemetry for the defensive default above, where the
|
|
@@ -1215,7 +1338,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1215
1338
|
this.toolHandlers = newHandlers;
|
|
1216
1339
|
}
|
|
1217
1340
|
/** Open a fold: push a stack frame, swap the tool set, return the response message. */
|
|
1218
|
-
openFold(foldName, fold, args) {
|
|
1341
|
+
openFold(foldName, fold, args, activeToolCallId) {
|
|
1219
1342
|
// Shortcut dispatch: model passed inner tool args directly, e.g.
|
|
1220
1343
|
// trading_tools({ search_trades: { side: "BUY" } })
|
|
1221
1344
|
for (const key of Object.keys(args)) {
|
|
@@ -1229,7 +1352,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1229
1352
|
const innerArgs = typeof args[key] === 'object' && args[key] !== null
|
|
1230
1353
|
? args[key]
|
|
1231
1354
|
: {};
|
|
1232
|
-
return innerHandler(innerArgs, this.buildHandlerContext()).then((r) => typeof r === 'string' ? r : JSON.stringify(r));
|
|
1355
|
+
return innerHandler(innerArgs, this.buildHandlerContext(activeToolCallId)).then((r) => typeof r === 'string' ? r : JSON.stringify(r));
|
|
1233
1356
|
}
|
|
1234
1357
|
}
|
|
1235
1358
|
// Normal two-step open
|
|
@@ -1308,6 +1431,10 @@ export class ChatDriver extends EventTarget {
|
|
|
1308
1431
|
let firstLlmCall = !!currentInput;
|
|
1309
1432
|
while (iterations < this.maxToolIterations) {
|
|
1310
1433
|
iterations += 1;
|
|
1434
|
+
// Monotonic across the driver's life — the age clock. Unlike `iterations`,
|
|
1435
|
+
// it is NOT reset per turn and NOT decremented for folds, so `age` keeps
|
|
1436
|
+
// counting model-calls once a turn ends. (See the `modelCallSeq` field.)
|
|
1437
|
+
this.modelCallSeq += 1;
|
|
1311
1438
|
// A cancel (or dispose) that landed while the previous iteration's tool
|
|
1312
1439
|
// batch was running takes effect here — before issuing another LLM call.
|
|
1313
1440
|
// An abort during the call itself is handled by the catch below.
|
|
@@ -1376,9 +1503,19 @@ export class ChatDriver extends EventTarget {
|
|
|
1376
1503
|
const primer = [...((_b = this.primerHistory) !== null && _b !== void 0 ? _b : []), ...(transientPrimer !== null && transientPrimer !== void 0 ? transientPrimer : [])];
|
|
1377
1504
|
const baseHistory = firstLlmCall ? this.history.slice(0, -1) : this.history;
|
|
1378
1505
|
firstLlmCall = false;
|
|
1506
|
+
// Collapse stale tool payloads this agent declared via `condenseWhen`, then
|
|
1507
|
+
// pipe through any externally-set transform (e.g. multi-agent masking).
|
|
1508
|
+
// Both run on a copy — stored history is untouched.
|
|
1509
|
+
const condensedHistory = applyCondensation([...baseHistory], this.condensePolicies, {
|
|
1510
|
+
modelCall: this.modelCallSeq,
|
|
1511
|
+
turn: this.turnSeq,
|
|
1512
|
+
// A call's activation has ended once a later activation is current — a
|
|
1513
|
+
// swap or release/complete both advance the counter.
|
|
1514
|
+
activationEnded: (activation) => activation < this.currentActivation,
|
|
1515
|
+
}, (detail) => recordMetaEvent(this.sessionKey, 'context.condensed', detail));
|
|
1379
1516
|
const historyForProvider = this.providerHistoryTransform
|
|
1380
|
-
? this.providerHistoryTransform(
|
|
1381
|
-
:
|
|
1517
|
+
? this.providerHistoryTransform(condensedHistory)
|
|
1518
|
+
: condensedHistory;
|
|
1382
1519
|
const historyForCall = [...primer, ...historyForProvider];
|
|
1383
1520
|
const systemPrompt = malformedAttempts > 0
|
|
1384
1521
|
? `${baseSystemPrompt !== null && baseSystemPrompt !== void 0 ? baseSystemPrompt : ''}\n\nIMPORTANT: Use only the structured function-call API to invoke tools. Do not write Python code or use Python-style syntax to call tools.`
|
|
@@ -1589,7 +1726,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1589
1726
|
});
|
|
1590
1727
|
return;
|
|
1591
1728
|
}
|
|
1592
|
-
const content = yield this.openFold(tc.name, fold, tc.args);
|
|
1729
|
+
const content = yield this.openFold(tc.name, fold, tc.args, tc.id);
|
|
1593
1730
|
executedById.set(tc.id, { toolCallId: tc.id, content });
|
|
1594
1731
|
// Fold open/close does NOT count as a real iteration — decrement to compensate
|
|
1595
1732
|
iterations -= 1;
|
|
@@ -1700,7 +1837,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1700
1837
|
// Real tool execution
|
|
1701
1838
|
try {
|
|
1702
1839
|
const traceCapture = {};
|
|
1703
|
-
const result = yield handler(tc.args, this.buildHandlerContext(traceCapture));
|
|
1840
|
+
const result = yield handler(tc.args, this.buildHandlerContext(tc.id, traceCapture));
|
|
1704
1841
|
const content = typeof result === 'string' ? result : JSON.stringify(result);
|
|
1705
1842
|
executedById.set(tc.id, {
|
|
1706
1843
|
toolCallId: tc.id,
|