@genesislcap/ai-assistant 15.84.2 → 15.85.0-FUI-2666.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 +2175 -138
- package/dist/ai-assistant.d.ts +420 -43
- package/dist/chat-driver.cjs +780 -210
- package/dist/chat-driver.cjs.map +4 -4
- package/dist/chat-driver.mjs +780 -209
- package/dist/chat-driver.mjs.map +4 -4
- package/dist/custom-elements.json +2000 -910
- package/dist/dts/components/agent-session/agent-session.d.ts +50 -2
- package/dist/dts/components/agent-session/agent-session.d.ts.map +1 -1
- package/dist/dts/components/ai-driver/ai-driver.d.ts +59 -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 +13 -1
- package/dist/dts/components/ai-driver/base-driver-decorator.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.constants.d.ts +0 -8
- package/dist/dts/components/chat-driver/chat-driver.constants.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +74 -15
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/hand-off.d.ts +20 -0
- package/dist/dts/components/orchestrating-driver/hand-off.d.ts.map +1 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +121 -22
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +66 -0
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +3 -0
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.types.d.ts +6 -0
- package/dist/dts/main/main.types.d.ts.map +1 -1
- package/dist/dts/react.d.ts +4 -1
- package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-snapshot.d.ts +33 -1
- package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
- package/dist/dts/utils/condense-history.d.ts +10 -3
- package/dist/dts/utils/condense-history.d.ts.map +1 -1
- package/dist/esm/components/agent-session/agent-session.js +128 -7
- package/dist/esm/components/ai-driver/ai-driver.js +3 -0
- package/dist/esm/components/ai-driver/base-driver-decorator.js +24 -0
- package/dist/esm/components/chat-driver/chat-driver.constants.js +0 -8
- package/dist/esm/components/chat-driver/chat-driver.js +247 -74
- package/dist/esm/components/orchestrating-driver/hand-off.js +59 -0
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +480 -299
- package/dist/esm/main/main.js +32 -6
- package/dist/esm/main/main.types.js +6 -0
- package/dist/esm/state/debug-event-log.js +1 -1
- package/dist/esm/state/persistence/session-persister.js +55 -8
- package/dist/esm/state/persistence/session-snapshot.js +12 -6
- package/dist/esm/utils/condense-history.js +13 -4
- package/dist/react.cjs +16 -1
- package/dist/react.mjs +16 -1
- package/docs/sub_agent.md +1 -1
- package/package.json +17 -17
package/dist/chat-driver.mjs
CHANGED
|
@@ -4410,7 +4410,7 @@ function toolCallIdsIn(history) {
|
|
|
4410
4410
|
}
|
|
4411
4411
|
return ids;
|
|
4412
4412
|
}
|
|
4413
|
-
function snapshotCondensePolicies(policies, clocks, history) {
|
|
4413
|
+
function snapshotCondensePolicies(policies, clocks, history, suspended = /* @__PURE__ */ new Map()) {
|
|
4414
4414
|
if (policies.size === 0) return [];
|
|
4415
4415
|
const present = toolCallIdsIn(history);
|
|
4416
4416
|
const out = [];
|
|
@@ -4425,8 +4425,11 @@ function snapshotCondensePolicies(policies, clocks, history) {
|
|
|
4425
4425
|
// the clamp only stops a malformed entry from being written out as "the future".
|
|
4426
4426
|
modelCalls: Math.max(0, clocks.modelCall - entry.iteration),
|
|
4427
4427
|
turns: Math.max(0, clocks.turn - entry.turn),
|
|
4428
|
-
activations: Math.max(0, clocks.activation - entry.activation),
|
|
4429
|
-
phases: Math.max(
|
|
4428
|
+
activations: suspended.has(entry.activation) ? 0 : Math.max(0, clocks.activation - entry.activation),
|
|
4429
|
+
phases: Math.max(
|
|
4430
|
+
0,
|
|
4431
|
+
(suspended.get(entry.activation) ?? clocks.phaseEpoch) - entry.phaseEpoch
|
|
4432
|
+
)
|
|
4430
4433
|
}
|
|
4431
4434
|
});
|
|
4432
4435
|
}
|
|
@@ -4546,7 +4549,7 @@ function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
4546
4549
|
// backstop, when the whole activation has ended, so a phase boundary that
|
|
4547
4550
|
// is never declared still collapses at `agentEnd` rather than never.
|
|
4548
4551
|
case "phaseEnd":
|
|
4549
|
-
return ctx.phaseEnded(entry.phaseEpoch) || ctx.activationEnded(entry.activation);
|
|
4552
|
+
return ctx.phaseEnded(entry.phaseEpoch, entry.activation) || ctx.activationEnded(entry.activation);
|
|
4550
4553
|
default:
|
|
4551
4554
|
return latestByKey.get(trig.by) !== toolCallId;
|
|
4552
4555
|
}
|
|
@@ -4921,6 +4924,17 @@ var AgentSession = class {
|
|
|
4921
4924
|
this.restoredStates = {};
|
|
4922
4925
|
/** The tail of the activation queue — see {@link AgentSession.activate}. */
|
|
4923
4926
|
this.activation = Promise.resolve();
|
|
4927
|
+
/**
|
|
4928
|
+
* Callers waiting in a hand-off (GENC-1737), oldest first: still live, so never deactivated by
|
|
4929
|
+
* the hand-off, and resumed without `onActivate`. Empty after a reload, when a caller has no live
|
|
4930
|
+
* state and is activated again from its saved state instead.
|
|
4931
|
+
*/
|
|
4932
|
+
this.suspended = [];
|
|
4933
|
+
/**
|
|
4934
|
+
* The `data` of the hand-off that activated each agent (GENC-1737), for its tool handlers: kept
|
|
4935
|
+
* while it is active or waits as a caller, and dropped when its activation ends.
|
|
4936
|
+
*/
|
|
4937
|
+
this.handOffData = /* @__PURE__ */ new Map();
|
|
4924
4938
|
}
|
|
4925
4939
|
/** The active agent, or `undefined` before the first activation and after a release. */
|
|
4926
4940
|
getActive() {
|
|
@@ -4962,6 +4976,84 @@ var AgentSession = class {
|
|
|
4962
4976
|
hasRestoredState(agentName) {
|
|
4963
4977
|
return this.restoredStates[agentName] !== void 0;
|
|
4964
4978
|
}
|
|
4979
|
+
/** The `data` of the hand-off that activated `agentName`, if one did and carried any. */
|
|
4980
|
+
getHandOffData(agentName) {
|
|
4981
|
+
return this.handOffData.get(agentName);
|
|
4982
|
+
}
|
|
4983
|
+
/** Record the `data` a hand-off activated `agentName` with, or restore it; `undefined` drops it. */
|
|
4984
|
+
setHandOffData(agentName, data) {
|
|
4985
|
+
if (data === void 0) this.handOffData.delete(agentName);
|
|
4986
|
+
else this.handOffData.set(agentName, data);
|
|
4987
|
+
}
|
|
4988
|
+
/**
|
|
4989
|
+
* Drop the flow lock and leave the active agent active (GENC-1737): a plain agent holding it with
|
|
4990
|
+
* no hand-off behind it is done once its turn ends.
|
|
4991
|
+
*/
|
|
4992
|
+
dropFlowLock() {
|
|
4993
|
+
if (this.flowOwner === null) return;
|
|
4994
|
+
this.flowOwner = null;
|
|
4995
|
+
this.reportTransition({
|
|
4996
|
+
previousAgent: this.active,
|
|
4997
|
+
switched: false,
|
|
4998
|
+
flowOwnerChangedTo: null
|
|
4999
|
+
});
|
|
5000
|
+
}
|
|
5001
|
+
/** The callers waiting in a hand-off whose state is live, oldest first. */
|
|
5002
|
+
getSuspended() {
|
|
5003
|
+
return this.suspended;
|
|
5004
|
+
}
|
|
5005
|
+
/**
|
|
5006
|
+
* Make `target` active for a hand-off (GENC-1737), holding the flow lock whatever kind of agent
|
|
5007
|
+
* it is, since a waiting caller depends on it. With `suspendCaller` the active agent waits:
|
|
5008
|
+
* it is not deactivated and keeps its state. Without it the active agent is deactivated as on
|
|
5009
|
+
* any swap: a one-way hand-off from a target, whose waiting caller then waits for the new
|
|
5010
|
+
* target. Queued with {@link AgentSession.activate}.
|
|
5011
|
+
*/
|
|
5012
|
+
handOff(target, options) {
|
|
5013
|
+
const run = this.activation.then(
|
|
5014
|
+
() => this.activateNow(target, { lock: true, suspendPrevious: options.suspendCaller })
|
|
5015
|
+
);
|
|
5016
|
+
this.activation = run.catch(() => void 0);
|
|
5017
|
+
return run;
|
|
5018
|
+
}
|
|
5019
|
+
/**
|
|
5020
|
+
* Make a waiting caller active again when its hand-off ends (GENC-1737). One whose state is live
|
|
5021
|
+
* resumes without `onActivate`, since it was never deactivated; after a reload it is activated
|
|
5022
|
+
* from its saved state like any other agent. `lock` keeps the flow lock on it: it held it before,
|
|
5023
|
+
* or another caller is still waiting further down.
|
|
5024
|
+
*/
|
|
5025
|
+
resume(caller, lock) {
|
|
5026
|
+
const run = this.activation.then(async () => {
|
|
5027
|
+
const index = this.suspended.findIndex((a) => a.name === caller.name);
|
|
5028
|
+
if (index === -1) return this.activateNow(caller, { lock });
|
|
5029
|
+
const [live2] = this.suspended.splice(index, 1);
|
|
5030
|
+
const transition = { previousAgent: this.active, switched: true };
|
|
5031
|
+
recordMetaEvent(this.deps.sessionKey(), "agent.handoff", {
|
|
5032
|
+
from: this.active?.name ?? null,
|
|
5033
|
+
to: live2.name,
|
|
5034
|
+
restored: false
|
|
5035
|
+
});
|
|
5036
|
+
this.active = live2;
|
|
5037
|
+
if (lock && this.flowOwner !== caller.name) {
|
|
5038
|
+
this.flowOwner = caller.name;
|
|
5039
|
+
transition.flowOwnerChangedTo = caller.name;
|
|
5040
|
+
}
|
|
5041
|
+
this.reportTransition(transition);
|
|
5042
|
+
return transition;
|
|
5043
|
+
});
|
|
5044
|
+
this.activation = run.catch(() => void 0);
|
|
5045
|
+
return run;
|
|
5046
|
+
}
|
|
5047
|
+
/**
|
|
5048
|
+
* Deactivate the callers left waiting by hand-offs that ended without a hand-back, newest first,
|
|
5049
|
+
* when no teardown follows to do it (GENC-1737).
|
|
5050
|
+
*/
|
|
5051
|
+
async endSuspended() {
|
|
5052
|
+
for (const caller of this.suspended.splice(0).reverse()) {
|
|
5053
|
+
this.handOffData.delete(caller.name);
|
|
5054
|
+
await this.fireDeactivate(caller, "release");
|
|
5055
|
+
}
|
|
5056
|
+
}
|
|
4965
5057
|
/**
|
|
4966
5058
|
* Make `agent` active, firing the lifecycle hooks around the swap: outgoing `onDeactivate`
|
|
4967
5059
|
* first, then incoming `onActivate`. Both are awaited so heavy activation work (a machine
|
|
@@ -4982,7 +5074,11 @@ var AgentSession = class {
|
|
|
4982
5074
|
this.activation = run.catch(() => void 0);
|
|
4983
5075
|
return run;
|
|
4984
5076
|
}
|
|
4985
|
-
|
|
5077
|
+
/**
|
|
5078
|
+
* `lock` takes the flow lock whatever kind of agent it is, and `suspendPrevious` leaves the
|
|
5079
|
+
* outgoing agent waiting instead of deactivating it, as a hand-off does (GENC-1737).
|
|
5080
|
+
*/
|
|
5081
|
+
async activateNow(agent, { lock = false, suspendPrevious = false } = {}) {
|
|
4986
5082
|
const previousAgent = this.active;
|
|
4987
5083
|
const switched = !previousAgent || previousAgent.name !== agent.name;
|
|
4988
5084
|
const transition = { previousAgent, switched };
|
|
@@ -4994,7 +5090,14 @@ var AgentSession = class {
|
|
|
4994
5090
|
restored: this.hasRestoredState(agent.name)
|
|
4995
5091
|
});
|
|
4996
5092
|
}
|
|
4997
|
-
if (switched && previousAgent)
|
|
5093
|
+
if (switched && previousAgent) {
|
|
5094
|
+
if (suspendPrevious) {
|
|
5095
|
+
this.suspended.push(previousAgent);
|
|
5096
|
+
} else {
|
|
5097
|
+
this.handOffData.delete(previousAgent.name);
|
|
5098
|
+
await this.fireDeactivate(previousAgent, "switch");
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
4998
5101
|
if (switched && agent.onActivate) {
|
|
4999
5102
|
const restored = this.restoredStates[agent.name];
|
|
5000
5103
|
if (restored !== void 0) delete this.restoredStates[agent.name];
|
|
@@ -5011,9 +5114,12 @@ var AgentSession = class {
|
|
|
5011
5114
|
}
|
|
5012
5115
|
}
|
|
5013
5116
|
const hasLifecycleHooks = !!(agent.onActivate || agent.onDeactivate);
|
|
5014
|
-
if (switched && hasLifecycleHooks && this.flowOwner !== agent.name) {
|
|
5117
|
+
if (switched && (hasLifecycleHooks || lock) && this.flowOwner !== agent.name) {
|
|
5015
5118
|
this.flowOwner = agent.name;
|
|
5016
5119
|
transition.flowOwnerChangedTo = agent.name;
|
|
5120
|
+
} else if (switched && !suspendPrevious && this.flowOwner === previousAgent?.name) {
|
|
5121
|
+
this.flowOwner = null;
|
|
5122
|
+
transition.flowOwnerChangedTo = null;
|
|
5017
5123
|
}
|
|
5018
5124
|
this.active = agent;
|
|
5019
5125
|
if (switched) this.reportTransition(transition);
|
|
@@ -5031,6 +5137,7 @@ var AgentSession = class {
|
|
|
5031
5137
|
async release() {
|
|
5032
5138
|
const agent = this.active;
|
|
5033
5139
|
if (!agent) return void 0;
|
|
5140
|
+
this.handOffData.delete(agent.name);
|
|
5034
5141
|
await this.fireDeactivate(agent, "release");
|
|
5035
5142
|
this.active = void 0;
|
|
5036
5143
|
const transition = { previousAgent: agent, switched: true };
|
|
@@ -5068,6 +5175,11 @@ var AgentSession = class {
|
|
|
5068
5175
|
async dispose() {
|
|
5069
5176
|
const agent = this.active;
|
|
5070
5177
|
if (agent) await this.fireDeactivate(agent, "dispose");
|
|
5178
|
+
for (const caller of [...this.suspended].reverse()) {
|
|
5179
|
+
await this.fireDeactivate(caller, "dispose");
|
|
5180
|
+
}
|
|
5181
|
+
this.suspended = [];
|
|
5182
|
+
this.handOffData.clear();
|
|
5071
5183
|
this.lifecycleAbortController.abort();
|
|
5072
5184
|
this.active = void 0;
|
|
5073
5185
|
}
|
|
@@ -5107,8 +5219,6 @@ var HARVESTED_SUBAGENT_EVENTS = /* @__PURE__ */ new Set([
|
|
|
5107
5219
|
"tool.failed",
|
|
5108
5220
|
"tool.unresolved"
|
|
5109
5221
|
]);
|
|
5110
|
-
var REQUEST_CONTINUATION_TOOL = "request_continuation";
|
|
5111
|
-
var HANDOFF_TOOL_RESULT_PLACEHOLDER = "Handoff to another specialist \u2014 routing continues on the next turn.";
|
|
5112
5222
|
|
|
5113
5223
|
// src/utils/tool-fold.ts
|
|
5114
5224
|
var TOOL_FOLD_SYMBOL = Symbol("toolFold");
|
|
@@ -6736,6 +6846,16 @@ var SERVICE_WIDE_REFUSALS = /* @__PURE__ */ new Set([
|
|
|
6736
6846
|
"ACCESS_DENIED",
|
|
6737
6847
|
"VENDOR_UNREACHABLE"
|
|
6738
6848
|
]);
|
|
6849
|
+
var handOffClosingNote = (to) => `[Hand-off]: You handed this request to ${to}, which takes it from here. Tell the user so in one short line. Call no tools, and do not answer the rest of the request.`;
|
|
6850
|
+
var HAND_BACK_CLOSING_NOTE = "[Hand-back]: You have finished this request and handed it back. Tell the user so in one short line. Call no tools, and start nothing new.";
|
|
6851
|
+
var jsonCopy2 = (value) => {
|
|
6852
|
+
try {
|
|
6853
|
+
const text = JSON.stringify(value);
|
|
6854
|
+
return text === void 0 ? void 0 : JSON.parse(text);
|
|
6855
|
+
} catch {
|
|
6856
|
+
return void 0;
|
|
6857
|
+
}
|
|
6858
|
+
};
|
|
6739
6859
|
var toolResultImages = (result) => {
|
|
6740
6860
|
if (!result || typeof result !== "object") return void 0;
|
|
6741
6861
|
const candidate = result.attachments;
|
|
@@ -6925,6 +7045,17 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
6925
7045
|
* doesn't leak forward.
|
|
6926
7046
|
*/
|
|
6927
7047
|
this.agentReleaseRequested = false;
|
|
7048
|
+
/**
|
|
7049
|
+
* The `agentEnd` activation of each caller waiting in a hand-off, by agent name, with the phase
|
|
7050
|
+
* epoch it was suspended at (GENC-1737). A waiting caller is suspended, not ended, so neither its
|
|
7051
|
+
* `agentEnd` payloads nor its `phaseEnd` ones (to another agent's `endPhase()`) collapse while the
|
|
7052
|
+
* target runs; when it is installed again its entries move to the new activation and, while
|
|
7053
|
+
* their phase had not ended, to the current epoch. A caller restored from a saved session gets
|
|
7054
|
+
* an activation placeholder below zero, which no live activation uses.
|
|
7055
|
+
*/
|
|
7056
|
+
this.suspendedActivations = /* @__PURE__ */ new Map();
|
|
7057
|
+
/** The last placeholder handed out for a restored caller; see `suspendedActivations`. */
|
|
7058
|
+
this.restoredSuspensionSeq = 0;
|
|
6928
7059
|
/**
|
|
6929
7060
|
* Aborted by `dispose()` on driver teardown (e.g. an agent-config swap).
|
|
6930
7061
|
* Threaded into every provider call as `ChatRequestOptions.signal`, so a
|
|
@@ -6973,8 +7104,8 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
6973
7104
|
* The stateful-agent contract for this driver: active agent, user pin, flow lock, restored
|
|
6974
7105
|
* state, and the `onActivate` / `onDeactivate` hooks (§7.1).
|
|
6975
7106
|
*
|
|
6976
|
-
* **Public because `OrchestratingDriver` reaches through to it.** The orchestrator
|
|
6977
|
-
* config before installing it (the
|
|
7107
|
+
* **Public because `OrchestratingDriver` reaches through to it.** The orchestrator builds a
|
|
7108
|
+
* config before installing it (the fallback's prompt), so it cannot route through
|
|
6978
7109
|
* {@link ChatDriver.activateAgent} and must drive the session itself — and one shared session is
|
|
6979
7110
|
* the point: two would let `getActiveAgent()` disagree between the outer and inner driver.
|
|
6980
7111
|
* `@internal` rather than private so that reach is legal without six delegating one-liners.
|
|
@@ -7381,19 +7512,15 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7381
7512
|
this.aiServiceNoticesShown.add(code);
|
|
7382
7513
|
return false;
|
|
7383
7514
|
}
|
|
7384
|
-
/**
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
* meets the wall and says so.
|
|
7388
|
-
*/
|
|
7389
|
-
subAgentWallThisTurn(handingOff = false) {
|
|
7390
|
-
return this.providerRefusedThisTurn || this.budgetExhaustedThisTurn || this.aiServiceErrorThisTurn && !handingOff;
|
|
7515
|
+
/** Whether a sub-agent hit a wall this turn that stops this driver too. */
|
|
7516
|
+
subAgentWallThisTurn() {
|
|
7517
|
+
return this.providerRefusedThisTurn || this.budgetExhaustedThisTurn || this.aiServiceErrorThisTurn;
|
|
7391
7518
|
}
|
|
7392
7519
|
/**
|
|
7393
7520
|
* Ends the turn when a sub-agent hit a wall this turn that stops this driver too: the provider
|
|
7394
7521
|
* refusing the account, the AI service (GENC-1611), or the budget. Checked before each model call,
|
|
7395
|
-
* and again right after a tool batch, before
|
|
7396
|
-
*
|
|
7522
|
+
* and again right after a tool batch, before the iteration cap can report the turn as something
|
|
7523
|
+
* else.
|
|
7397
7524
|
*/
|
|
7398
7525
|
endTurnOnSubAgentWall() {
|
|
7399
7526
|
if (!this.subAgentWallThisTurn()) return void 0;
|
|
@@ -7601,6 +7728,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7601
7728
|
installAgent(config) {
|
|
7602
7729
|
if (config.name !== this.activeAgentName) {
|
|
7603
7730
|
this.currentActivation += 1;
|
|
7731
|
+
this.resumeSuspendedActivation(config.name);
|
|
7604
7732
|
this.resumeRestoredCondensePolicies(config.name);
|
|
7605
7733
|
}
|
|
7606
7734
|
this.systemPrompt = config.systemPrompt;
|
|
@@ -7802,6 +7930,109 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7802
7930
|
getAgentReleaseRequested() {
|
|
7803
7931
|
return this.agentReleaseRequested;
|
|
7804
7932
|
}
|
|
7933
|
+
/**
|
|
7934
|
+
* Offer `handOff` and `completeHandOff` to tool handlers, checking each `handOff` with `check`
|
|
7935
|
+
* (GENC-1737). Set by `OrchestratingDriver`, which knows the agents and the hand-off stack and acts
|
|
7936
|
+
* on the request after the call; a bare `ChatDriver` has no other agent to hand to.
|
|
7937
|
+
*
|
|
7938
|
+
* @internal
|
|
7939
|
+
*/
|
|
7940
|
+
setHandOffCheck(check) {
|
|
7941
|
+
this.checkHandOff = check;
|
|
7942
|
+
}
|
|
7943
|
+
/** The `handOff` the most recent call accepted, if any (GENC-1737). */
|
|
7944
|
+
getHandOffRequest() {
|
|
7945
|
+
return this.handOffRequested;
|
|
7946
|
+
}
|
|
7947
|
+
/** What `completeHandOff` handed back in the most recent call, if it was called. */
|
|
7948
|
+
getHandOffCompletion() {
|
|
7949
|
+
return this.handOffCompletion;
|
|
7950
|
+
}
|
|
7951
|
+
/**
|
|
7952
|
+
* A handler's `handOff`: refused once this call has handed off or released, or when `data` is
|
|
7953
|
+
* not JSON, and otherwise checked by the orchestrator. `data` is kept as a JSON copy, since it is
|
|
7954
|
+
* saved with the session and the handler may change its own object afterwards.
|
|
7955
|
+
*/
|
|
7956
|
+
requestHandOff(request) {
|
|
7957
|
+
if (this.handOffRequested) return { ok: false, reason: "already-requested" };
|
|
7958
|
+
if (this.agentReleaseRequested) return { ok: false, reason: "released" };
|
|
7959
|
+
let data;
|
|
7960
|
+
if (request?.data !== void 0) {
|
|
7961
|
+
data = jsonCopy2(request.data);
|
|
7962
|
+
if (data === void 0) return { ok: false, reason: "not-json" };
|
|
7963
|
+
}
|
|
7964
|
+
const accepted = {
|
|
7965
|
+
to: request.to,
|
|
7966
|
+
message: String(request.message ?? ""),
|
|
7967
|
+
...data !== void 0 && { data },
|
|
7968
|
+
...request.returnTo === false && { returnTo: false }
|
|
7969
|
+
};
|
|
7970
|
+
const result = this.checkHandOff(accepted, this.activeAgentName);
|
|
7971
|
+
if (result.ok) this.handOffRequested = accepted;
|
|
7972
|
+
return result;
|
|
7973
|
+
}
|
|
7974
|
+
/**
|
|
7975
|
+
* The note of the closing call a hand-off asks for (GENC-1737), or `undefined` when none: once a
|
|
7976
|
+
* handler's `handOff` is accepted, or `completeHandOff` is called, the rest of that batch runs,
|
|
7977
|
+
* then one model call with no tools, so the agent can say so but not carry on, which a model told
|
|
7978
|
+
* so in a tool result does anyway. Then the turn ends.
|
|
7979
|
+
*/
|
|
7980
|
+
closingCallNote() {
|
|
7981
|
+
if (this.handOffRequested) return handOffClosingNote(this.handOffRequested.to);
|
|
7982
|
+
return this.handOffCompletion ? HAND_BACK_CLOSING_NOTE : void 0;
|
|
7983
|
+
}
|
|
7984
|
+
/**
|
|
7985
|
+
* Suspend the active agent's activation for a hand-off it made (GENC-1737): installing the target
|
|
7986
|
+
* advances the `agentEnd` clock as any swap does, but this caller's payloads stay live.
|
|
7987
|
+
*/
|
|
7988
|
+
suspendActivation(agentName) {
|
|
7989
|
+
if (agentName === this.activeAgentName) {
|
|
7990
|
+
this.suspendedActivations.set(agentName, {
|
|
7991
|
+
activation: this.currentActivation,
|
|
7992
|
+
phaseEpoch: this.currentPhaseEpoch
|
|
7993
|
+
});
|
|
7994
|
+
}
|
|
7995
|
+
}
|
|
7996
|
+
/**
|
|
7997
|
+
* Suspend restored callers before their condense policies are restored, so entries still live
|
|
7998
|
+
* at save time stay live instead of waiting for "the next activation", which is the target's.
|
|
7999
|
+
*/
|
|
8000
|
+
suspendRestoredActivations(agentNames) {
|
|
8001
|
+
for (const name of agentNames) {
|
|
8002
|
+
this.restoredSuspensionSeq -= 1;
|
|
8003
|
+
this.suspendedActivations.set(name, {
|
|
8004
|
+
activation: this.restoredSuspensionSeq,
|
|
8005
|
+
phaseEpoch: this.currentPhaseEpoch
|
|
8006
|
+
});
|
|
8007
|
+
}
|
|
8008
|
+
}
|
|
8009
|
+
/** End every suspension: the hand-offs were unwound, so their callers' activations are over. */
|
|
8010
|
+
endSuspendedActivations() {
|
|
8011
|
+
this.suspendedActivations.clear();
|
|
8012
|
+
}
|
|
8013
|
+
/**
|
|
8014
|
+
* A caller installed again takes its suspended entries into the activation it resumes in, and
|
|
8015
|
+
* the ones whose phase had not ended into the current phase epoch.
|
|
8016
|
+
*/
|
|
8017
|
+
resumeSuspendedActivation(agentName) {
|
|
8018
|
+
const suspended = this.suspendedActivations.get(agentName);
|
|
8019
|
+
if (suspended === void 0) return;
|
|
8020
|
+
this.suspendedActivations.delete(agentName);
|
|
8021
|
+
for (const entry of this.condensePolicies.values()) {
|
|
8022
|
+
if (entry.activation !== suspended.activation) continue;
|
|
8023
|
+
entry.activation = this.currentActivation;
|
|
8024
|
+
if (entry.phaseEpoch >= suspended.phaseEpoch) entry.phaseEpoch = this.currentPhaseEpoch;
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
8027
|
+
/**
|
|
8028
|
+
* The suspended activations, each with the phase epoch it was suspended at, for the `agentEnd`
|
|
8029
|
+
* and `phaseEnd` checks and for persistence.
|
|
8030
|
+
*/
|
|
8031
|
+
suspendedClocks() {
|
|
8032
|
+
return new Map(
|
|
8033
|
+
[...this.suspendedActivations.values()].map((s) => [s.activation, s.phaseEpoch])
|
|
8034
|
+
);
|
|
8035
|
+
}
|
|
7805
8036
|
/**
|
|
7806
8037
|
* Act on a `releaseAgent` call from a tool handler: fire `onDeactivate` and drop the flow lock,
|
|
7807
8038
|
* so the next turn starts clean and the persister does not serialize a flow that is over.
|
|
@@ -7940,7 +8171,12 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7940
8171
|
* @beta
|
|
7941
8172
|
*/
|
|
7942
8173
|
getCondensePoliciesForPersistence(messages) {
|
|
7943
|
-
return snapshotCondensePolicies(
|
|
8174
|
+
return snapshotCondensePolicies(
|
|
8175
|
+
this.condensePolicies,
|
|
8176
|
+
this.condenseClocks(),
|
|
8177
|
+
messages,
|
|
8178
|
+
this.suspendedClocks()
|
|
8179
|
+
);
|
|
7944
8180
|
}
|
|
7945
8181
|
/**
|
|
7946
8182
|
* Re-register policies saved by {@link ChatDriver.getCondensePoliciesForPersistence} against
|
|
@@ -7989,9 +8225,13 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
7989
8225
|
const entry = parsePersistedCondensePolicy(raw);
|
|
7990
8226
|
if (!entry || !present.has(entry.toolCallId)) continue;
|
|
7991
8227
|
if (this.condensePolicies.has(entry.toolCallId)) continue;
|
|
7992
|
-
|
|
8228
|
+
const registered = rebaseRestoredCondensePolicy(entry, clocks);
|
|
8229
|
+
this.condensePolicies.set(entry.toolCallId, registered);
|
|
7993
8230
|
restored += 1;
|
|
7994
|
-
|
|
8231
|
+
const suspended = entry.elapsed.activations === 0 && entry.agent !== void 0 ? this.suspendedActivations.get(entry.agent) : void 0;
|
|
8232
|
+
if (suspended !== void 0) {
|
|
8233
|
+
registered.activation = suspended.activation;
|
|
8234
|
+
} else if (entry.elapsed.activations === 0 && entry.agent !== this.activeAgentName) {
|
|
7995
8235
|
this.condenseAwaitingReactivation.add(entry.toolCallId);
|
|
7996
8236
|
awaitingReactivation += 1;
|
|
7997
8237
|
}
|
|
@@ -8348,6 +8588,8 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
8348
8588
|
this.subAgentCompletion = void 0;
|
|
8349
8589
|
this.subAgentFailure = void 0;
|
|
8350
8590
|
this.agentReleaseRequested = false;
|
|
8591
|
+
this.handOffRequested = void 0;
|
|
8592
|
+
this.handOffCompletion = void 0;
|
|
8351
8593
|
this.budgetExhaustedThisTurn = false;
|
|
8352
8594
|
this.budgetWallDetail = void 0;
|
|
8353
8595
|
this.budgetWallViaSubAgent = false;
|
|
@@ -8481,6 +8723,12 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
8481
8723
|
this.currentActivation += 1;
|
|
8482
8724
|
},
|
|
8483
8725
|
releaseAgent: () => {
|
|
8726
|
+
if (this.handOffRequested) {
|
|
8727
|
+
logger2.warn(
|
|
8728
|
+
`ChatDriver(${this.activeAgentName ?? "unknown"}): releaseAgent called after handOff \u2014 ignoring; the hand-off ends this agent's turn`
|
|
8729
|
+
);
|
|
8730
|
+
return;
|
|
8731
|
+
}
|
|
8484
8732
|
if (this.agentReleaseRequested) {
|
|
8485
8733
|
logger2.warn(
|
|
8486
8734
|
`ChatDriver(${this.activeAgentName ?? "unknown"}): releaseAgent called more than once \u2014 ignoring`
|
|
@@ -8490,6 +8738,34 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
8490
8738
|
this.agentReleaseRequested = true;
|
|
8491
8739
|
this.currentActivation += 1;
|
|
8492
8740
|
},
|
|
8741
|
+
...this.checkHandOff && {
|
|
8742
|
+
handOff: (request) => this.requestHandOff(request),
|
|
8743
|
+
// A copy, so a handler cannot change what is saved.
|
|
8744
|
+
handOffData: () => jsonCopy2(this.agentSession.getHandOffData(this.activeAgentName ?? "")),
|
|
8745
|
+
completeHandOff: (completion) => {
|
|
8746
|
+
if (this.agentReleaseRequested || this.handOffRequested) {
|
|
8747
|
+
logger2.warn(
|
|
8748
|
+
`ChatDriver(${this.activeAgentName ?? "unknown"}): completeHandOff called after a release or a hand-off \u2014 ignoring`
|
|
8749
|
+
);
|
|
8750
|
+
return;
|
|
8751
|
+
}
|
|
8752
|
+
let data;
|
|
8753
|
+
if (completion?.data !== void 0) {
|
|
8754
|
+
data = jsonCopy2(completion.data);
|
|
8755
|
+
if (data === void 0) {
|
|
8756
|
+
logger2.warn(
|
|
8757
|
+
`ChatDriver(${this.activeAgentName ?? "unknown"}): completeHandOff data cannot be saved as JSON \u2014 handing back without it`
|
|
8758
|
+
);
|
|
8759
|
+
}
|
|
8760
|
+
}
|
|
8761
|
+
this.handOffCompletion = {
|
|
8762
|
+
message: String(completion?.message ?? ""),
|
|
8763
|
+
...data !== void 0 && { data }
|
|
8764
|
+
};
|
|
8765
|
+
this.agentReleaseRequested = true;
|
|
8766
|
+
this.currentActivation += 1;
|
|
8767
|
+
}
|
|
8768
|
+
},
|
|
8493
8769
|
endPhase: () => {
|
|
8494
8770
|
this.currentPhaseEpoch += 1;
|
|
8495
8771
|
},
|
|
@@ -8692,6 +8968,9 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
8692
8968
|
this.beginTurn();
|
|
8693
8969
|
this.subAgentCompletion = void 0;
|
|
8694
8970
|
this.subAgentFailure = void 0;
|
|
8971
|
+
this.agentReleaseRequested = false;
|
|
8972
|
+
this.handOffRequested = void 0;
|
|
8973
|
+
this.handOffCompletion = void 0;
|
|
8695
8974
|
this.budgetExhaustedThisTurn = false;
|
|
8696
8975
|
this.budgetWallDetail = void 0;
|
|
8697
8976
|
this.budgetWallViaSubAgent = false;
|
|
@@ -8759,7 +9038,8 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
8759
9038
|
let firstLlmCall = !!currentInput;
|
|
8760
9039
|
let pinnedThinkingPolicy;
|
|
8761
9040
|
let thinkingPolicyPinned = false;
|
|
8762
|
-
|
|
9041
|
+
let closingCallMade = false;
|
|
9042
|
+
while (iterations < this.maxToolIterations || this.closingCallNote() && !closingCallMade) {
|
|
8763
9043
|
iterations += 1;
|
|
8764
9044
|
this.modelCallSeq += 1;
|
|
8765
9045
|
if (this.turnController.signal.aborted) {
|
|
@@ -8767,6 +9047,8 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
8767
9047
|
}
|
|
8768
9048
|
const wall = this.endTurnOnSubAgentWall();
|
|
8769
9049
|
if (wall) return wall;
|
|
9050
|
+
if (closingCallMade) return turnDone();
|
|
9051
|
+
const closing = this.closingCallNote();
|
|
8770
9052
|
const promptCtx = {
|
|
8771
9053
|
agentName: this.activeAgentName ?? "",
|
|
8772
9054
|
history: this.history,
|
|
@@ -8808,13 +9090,17 @@ ${EMPTY_RESPONSE_NUDGE}` : "";
|
|
|
8808
9090
|
const firstCallNote = tailNote && iterations === 1 ? `
|
|
8809
9091
|
|
|
8810
9092
|
${tailNote}` : "";
|
|
8811
|
-
const
|
|
9093
|
+
const closingNote = closing ? `
|
|
9094
|
+
|
|
9095
|
+
${closing}` : "";
|
|
9096
|
+
const frameworkSystemSuffix = `${foldSuffix}${firstCallNote}${closingNote}${retryNudge}`;
|
|
8812
9097
|
const primer = normalizeForProvider([
|
|
8813
9098
|
...this.primerHistory ?? [],
|
|
8814
9099
|
...transientPrimer ?? []
|
|
8815
9100
|
]);
|
|
8816
9101
|
const baseHistory = firstLlmCall ? this.history.slice(0, -1) : this.history;
|
|
8817
9102
|
firstLlmCall = false;
|
|
9103
|
+
const suspended = this.suspendedClocks();
|
|
8818
9104
|
const condensedHistory = applyCondensation(
|
|
8819
9105
|
[...baseHistory],
|
|
8820
9106
|
this.condensePolicies,
|
|
@@ -8823,10 +9109,11 @@ ${tailNote}` : "";
|
|
|
8823
9109
|
turn: this.turnSeq,
|
|
8824
9110
|
// A call's activation has ended once a later activation is current — a
|
|
8825
9111
|
// swap or release/complete both advance the counter.
|
|
8826
|
-
|
|
9112
|
+
// A caller waiting in a hand-off is suspended, not ended (GENC-1737).
|
|
9113
|
+
activationEnded: (activation) => activation < this.currentActivation && !suspended.has(activation),
|
|
8827
9114
|
// A call's phase has ended once the app declared a later phase epoch
|
|
8828
|
-
// current via `endPhase()
|
|
8829
|
-
phaseEnded: (phaseEpoch) => phaseEpoch < this.currentPhaseEpoch,
|
|
9115
|
+
// current via `endPhase()`; a waiting caller's, only by one before it waited.
|
|
9116
|
+
phaseEnded: (phaseEpoch, activation) => phaseEpoch < (suspended.get(activation) ?? this.currentPhaseEpoch),
|
|
8830
9117
|
batchCalls: this.condenseBatchCalls
|
|
8831
9118
|
},
|
|
8832
9119
|
(detail) => recordMetaEvent(this.sessionKey, "context.condensed", detail)
|
|
@@ -8866,7 +9153,7 @@ ${tailNote}` : "";
|
|
|
8866
9153
|
const tailContext = tailBody ? `<system-reminder>
|
|
8867
9154
|
${tailBody}
|
|
8868
9155
|
</system-reminder>` : void 0;
|
|
8869
|
-
const effectiveToolChoice = resolvedToolChoice ?? (this.isSubAgent ? "required" : void 0);
|
|
9156
|
+
const effectiveToolChoice = closing ? "none" : resolvedToolChoice ?? (this.isSubAgent ? "required" : void 0);
|
|
8870
9157
|
const turnSnapshot = this.turnRecorder.record({
|
|
8871
9158
|
agentName: this.activeAgentName,
|
|
8872
9159
|
agentLabel: this.activeAgentLabel,
|
|
@@ -8938,6 +9225,7 @@ ${tailBody}
|
|
|
8938
9225
|
});
|
|
8939
9226
|
return this.endTurnWithFailure(TURN_FAILURE_EXITS.contextGuard);
|
|
8940
9227
|
}
|
|
9228
|
+
if (closing) closingCallMade = true;
|
|
8941
9229
|
let response;
|
|
8942
9230
|
try {
|
|
8943
9231
|
response = await activeProvider.chat(historyForCall, userInputForCall, options);
|
|
@@ -9071,6 +9359,10 @@ ${tailBody}
|
|
|
9071
9359
|
}
|
|
9072
9360
|
turnSnapshot.usage = messageUsage(response);
|
|
9073
9361
|
if (response.model !== void 0) turnSnapshot.model = response.model;
|
|
9362
|
+
if (closing) {
|
|
9363
|
+
if (response.toolCalls?.length) response = { ...response, toolCalls: void 0 };
|
|
9364
|
+
if (!response.content?.trim()) return turnDone();
|
|
9365
|
+
}
|
|
9074
9366
|
const isThinkingStep = response.content && response.toolCalls?.length;
|
|
9075
9367
|
const isEmptyResponse = !response.content?.trim() && !response.toolCalls?.length;
|
|
9076
9368
|
const isRefusal = response.responseMeta?.finishReason === "refusal";
|
|
@@ -9148,17 +9440,11 @@ ${tailBody}
|
|
|
9148
9440
|
emptyResponseAttempts = 0;
|
|
9149
9441
|
malformedAttempts = 0;
|
|
9150
9442
|
setupTransportAttempts = 0;
|
|
9443
|
+
if (closing) return turnDone();
|
|
9151
9444
|
if (!response.toolCalls?.length) {
|
|
9152
9445
|
break;
|
|
9153
9446
|
}
|
|
9154
|
-
const
|
|
9155
|
-
(acc, tc) => {
|
|
9156
|
-
if (tc.name === REQUEST_CONTINUATION_TOOL) acc[1].push(tc);
|
|
9157
|
-
else acc[0].push(tc);
|
|
9158
|
-
return acc;
|
|
9159
|
-
},
|
|
9160
|
-
[[], []]
|
|
9161
|
-
);
|
|
9447
|
+
const toolCalls = response.toolCalls;
|
|
9162
9448
|
const executedById = /* @__PURE__ */ new Map();
|
|
9163
9449
|
const unknownToolIds = /* @__PURE__ */ new Set();
|
|
9164
9450
|
const staleToolIds = /* @__PURE__ */ new Set();
|
|
@@ -9336,28 +9622,20 @@ RECOVERY: this tool failed once \u2014 you may retry it, or take a different val
|
|
|
9336
9622
|
}
|
|
9337
9623
|
const foldPath = this.folds.activeNames();
|
|
9338
9624
|
for (const tc of response.toolCalls) {
|
|
9339
|
-
|
|
9625
|
+
const r = executedById.get(tc.id);
|
|
9626
|
+
if (r) {
|
|
9340
9627
|
this.appendToHistory({
|
|
9341
9628
|
role: "tool",
|
|
9342
9629
|
content: "",
|
|
9343
|
-
toolResult: {
|
|
9630
|
+
toolResult: {
|
|
9631
|
+
toolCallId: r.toolCallId,
|
|
9632
|
+
content: r.content,
|
|
9633
|
+
// Images DO persist, unlike the sub-agent trace above: they are the payload
|
|
9634
|
+
// the model must see on every replay of this turn, not a one-off UI artefact.
|
|
9635
|
+
// `condenseWhen` is what sheds them once they are spent — see condense-history.
|
|
9636
|
+
...r.attachments ? { attachments: r.attachments } : {}
|
|
9637
|
+
}
|
|
9344
9638
|
});
|
|
9345
|
-
} else {
|
|
9346
|
-
const r = executedById.get(tc.id);
|
|
9347
|
-
if (r) {
|
|
9348
|
-
this.appendToHistory({
|
|
9349
|
-
role: "tool",
|
|
9350
|
-
content: "",
|
|
9351
|
-
toolResult: {
|
|
9352
|
-
toolCallId: r.toolCallId,
|
|
9353
|
-
content: r.content,
|
|
9354
|
-
// Images DO persist, unlike the sub-agent trace above: they are the payload
|
|
9355
|
-
// the model must see on every replay of this turn, not a one-off UI artefact.
|
|
9356
|
-
// `condenseWhen` is what sheds them once they are spent — see condense-history.
|
|
9357
|
-
...r.attachments ? { attachments: r.attachments } : {}
|
|
9358
|
-
}
|
|
9359
|
-
});
|
|
9360
|
-
}
|
|
9361
9639
|
}
|
|
9362
9640
|
}
|
|
9363
9641
|
let tcMsgIdx = -1;
|
|
@@ -9421,8 +9699,7 @@ RECOVERY: this tool failed once \u2014 you may retry it, or take a different val
|
|
|
9421
9699
|
});
|
|
9422
9700
|
return this.endTurnWithFailure(TURN_FAILURE_EXITS.unknownToolLimit);
|
|
9423
9701
|
}
|
|
9424
|
-
|
|
9425
|
-
if (!this.subAgentCompletion && this.subAgentWallThisTurn(!!firstContinuation)) {
|
|
9702
|
+
if (!this.subAgentCompletion && this.subAgentWallThisTurn()) {
|
|
9426
9703
|
if (this.turnController.signal.aborted) return this.completeAbortedTurn();
|
|
9427
9704
|
const wallAfterBatch = this.endTurnOnSubAgentWall();
|
|
9428
9705
|
if (wallAfterBatch) return wallAfterBatch;
|
|
@@ -9431,10 +9708,6 @@ RECOVERY: this tool failed once \u2014 you may retry it, or take a different val
|
|
|
9431
9708
|
this.noticeAIServiceRefusal(this.aiServiceWall.code);
|
|
9432
9709
|
this.flushAIServiceNotices();
|
|
9433
9710
|
}
|
|
9434
|
-
if (firstContinuation) {
|
|
9435
|
-
const { summary, remaining_task: remainingTask } = firstContinuation.args;
|
|
9436
|
-
return { reason: "agent-handoff", summary, remainingTask };
|
|
9437
|
-
}
|
|
9438
9711
|
if (this.subAgentCompletion) {
|
|
9439
9712
|
return turnDone();
|
|
9440
9713
|
}
|
|
@@ -9510,6 +9783,9 @@ var AI_DRIVER_EVENT_TYPES = Object.keys({
|
|
|
9510
9783
|
"agent-changed": true,
|
|
9511
9784
|
"agent-released": true,
|
|
9512
9785
|
"flow-owner-changed": true,
|
|
9786
|
+
"handoff-started": true,
|
|
9787
|
+
"handoff-returned": true,
|
|
9788
|
+
"handoff-unwound": true,
|
|
9513
9789
|
"orchestrating-start": true,
|
|
9514
9790
|
"orchestrating-stop": true
|
|
9515
9791
|
});
|
|
@@ -9590,6 +9866,24 @@ var BaseDriverDecorator = class _BaseDriverDecorator extends EventTarget {
|
|
|
9590
9866
|
restoreCondensePolicies(entries) {
|
|
9591
9867
|
return this.inner.restoreCondensePolicies?.(entries) ?? { restored: 0, awaitingReactivation: 0 };
|
|
9592
9868
|
}
|
|
9869
|
+
getHandOffStack() {
|
|
9870
|
+
return this.inner.getHandOffStack?.() ?? [];
|
|
9871
|
+
}
|
|
9872
|
+
restoreHandOffStack(saved, savedStates, interrupted) {
|
|
9873
|
+
return this.inner.restoreHandOffStack?.(saved, savedStates, interrupted) ?? {};
|
|
9874
|
+
}
|
|
9875
|
+
unwindHandOffs(kind) {
|
|
9876
|
+
return this.inner.unwindHandOffs?.(kind) ?? Promise.resolve();
|
|
9877
|
+
}
|
|
9878
|
+
serializeWaitingCallers() {
|
|
9879
|
+
return this.inner.serializeWaitingCallers?.() ?? {};
|
|
9880
|
+
}
|
|
9881
|
+
getHandOffData() {
|
|
9882
|
+
return this.inner.getHandOffData?.() ?? {};
|
|
9883
|
+
}
|
|
9884
|
+
restoreHandOffData(saved, interrupted) {
|
|
9885
|
+
this.inner.restoreHandOffData?.(saved, interrupted);
|
|
9886
|
+
}
|
|
9593
9887
|
getActiveAgent() {
|
|
9594
9888
|
return this.inner.getActiveAgent();
|
|
9595
9889
|
}
|
|
@@ -9698,36 +9992,64 @@ var BaseDriverDecorator = class _BaseDriverDecorator extends EventTarget {
|
|
|
9698
9992
|
}
|
|
9699
9993
|
};
|
|
9700
9994
|
|
|
9995
|
+
// src/components/orchestrating-driver/hand-off.ts
|
|
9996
|
+
var OUTCOME_KINDS = [
|
|
9997
|
+
"completed",
|
|
9998
|
+
"released",
|
|
9999
|
+
"cleared",
|
|
10000
|
+
"agent-removed",
|
|
10001
|
+
"state-lost"
|
|
10002
|
+
];
|
|
10003
|
+
function handOffNote(caller) {
|
|
10004
|
+
return `[Hand-off]: ${caller} handed this request to you. Its brief is the message above. Work on it as you would on a request from the user.`;
|
|
10005
|
+
}
|
|
10006
|
+
function handBackNote(frame, outcome) {
|
|
10007
|
+
const how = {
|
|
10008
|
+
completed: `${frame.target} has finished the request you handed it.`,
|
|
10009
|
+
released: `${frame.target} has finished the request you handed it, without a summary. Its last reply is above.`,
|
|
10010
|
+
cleared: "The hand-off was cleared.",
|
|
10011
|
+
"agent-removed": `${frame.target} is no longer available, so the request you handed it was not finished.`,
|
|
10012
|
+
"state-lost": `The hand-off to ${frame.target} could not be resumed.`
|
|
10013
|
+
};
|
|
10014
|
+
const summary = outcome.kind === "completed" && outcome.message ? " Its summary is the message above." : "";
|
|
10015
|
+
return `[Hand-back]: ${how[outcome.kind]}${summary} Carry on with the user from here.`;
|
|
10016
|
+
}
|
|
10017
|
+
var frameCopy = (frame) => structuredClone(frame);
|
|
10018
|
+
var isOutcome = (value) => {
|
|
10019
|
+
const o = value;
|
|
10020
|
+
return !!o && typeof o === "object" && OUTCOME_KINDS.includes(o.kind) && (o.message === void 0 || typeof o.message === "string");
|
|
10021
|
+
};
|
|
10022
|
+
function parseHandOffFrames(saved) {
|
|
10023
|
+
if (!Array.isArray(saved)) return [];
|
|
10024
|
+
return saved.filter((value) => {
|
|
10025
|
+
const f = value;
|
|
10026
|
+
return !!f && typeof f === "object" && typeof f.id === "string" && typeof f.caller === "string" && typeof f.target === "string" && typeof f.returnTo === "boolean" && typeof f.message === "string" && typeof f.startedAt === "string" && (f.outcome === void 0 || isOutcome(f.outcome));
|
|
10027
|
+
});
|
|
10028
|
+
}
|
|
10029
|
+
|
|
9701
10030
|
// src/components/orchestrating-driver/orchestrating-driver.ts
|
|
9702
10031
|
var DEFAULT_MAX_HANDOFFS = 3;
|
|
10032
|
+
var DEFAULT_MAX_HAND_OFF_DEPTH = 1;
|
|
9703
10033
|
var DEFAULT_CLASSIFIER_HISTORY_LENGTH = 4;
|
|
9704
10034
|
var DEFAULT_CLASSIFIER_RETRIES = 2;
|
|
9705
10035
|
var CLASSIFIER_MESSAGE_CHARS = 600;
|
|
9706
10036
|
var CLASSIFIER_TRANSCRIPT_CHARS = 3e3;
|
|
9707
|
-
var REQUEST_CONTINUATION_DEFINITION = {
|
|
9708
|
-
name: REQUEST_CONTINUATION_TOOL,
|
|
9709
|
-
description: "Call this when you have completed your part of the task but fulfilling the user's full request requires capabilities outside your domain. Do not call this if you can complete the request yourself. Pass a plain-language description of what still needs to be done \u2014 another specialist will be selected automatically.",
|
|
9710
|
-
parameters: {
|
|
9711
|
-
type: "object",
|
|
9712
|
-
required: ["summary", "remaining_task"],
|
|
9713
|
-
properties: {
|
|
9714
|
-
summary: {
|
|
9715
|
-
type: "string",
|
|
9716
|
-
description: "What you found or did \u2014 passed as context to the next specialist."
|
|
9717
|
-
},
|
|
9718
|
-
remaining_task: {
|
|
9719
|
-
type: "string",
|
|
9720
|
-
description: "What still needs to be done, in plain language. Used to route to the right specialist."
|
|
9721
|
-
}
|
|
9722
|
-
}
|
|
9723
|
-
}
|
|
9724
|
-
};
|
|
9725
10037
|
function isSpecialist(agent) {
|
|
9726
10038
|
return !agent.fallback;
|
|
9727
10039
|
}
|
|
9728
10040
|
function isFallback(agent) {
|
|
9729
10041
|
return agent.fallback === true;
|
|
9730
10042
|
}
|
|
10043
|
+
function isStateful(agent) {
|
|
10044
|
+
return !!(agent.onActivate || agent.onDeactivate);
|
|
10045
|
+
}
|
|
10046
|
+
async function callHandOffHook(agent, hook, run) {
|
|
10047
|
+
try {
|
|
10048
|
+
await run();
|
|
10049
|
+
} catch (e) {
|
|
10050
|
+
logger2.warn(`OrchestratingDriver: ${hook}("${agent}") threw:`, e);
|
|
10051
|
+
}
|
|
10052
|
+
}
|
|
9731
10053
|
function normalizeContextReserve(input) {
|
|
9732
10054
|
if (input == null) return void 0;
|
|
9733
10055
|
const positive = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
@@ -9760,37 +10082,40 @@ Politely let the user know what you can help with and invite them to rephrase th
|
|
|
9760
10082
|
|
|
9761
10083
|
${UNROUTABLE_INSTRUCTION}`;
|
|
9762
10084
|
}
|
|
9763
|
-
function handoffTailNote(agent, remainingTask) {
|
|
9764
|
-
if (isFallback(agent)) {
|
|
9765
|
-
return "[Hand-off]: no specialist here can take on what remains. Reply to the user now, as the routing note says.";
|
|
9766
|
-
}
|
|
9767
|
-
const work = remainingTask?.trim();
|
|
9768
|
-
return work ? `[Hand-off]: the previous agent passed the rest of this request to you. What remains: ${work}` : "[Hand-off]: the previous agent passed the rest of this request to you without saying what remains. Carry on with the user's request above.";
|
|
9769
|
-
}
|
|
9770
10085
|
var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator {
|
|
9771
10086
|
constructor(providerRegistry, agents, options = {}) {
|
|
9772
10087
|
super(_OrchestratingDriver.createInner(providerRegistry, agents, options));
|
|
9773
10088
|
this.providerRegistry = providerRegistry;
|
|
9774
10089
|
this.agents = agents;
|
|
9775
10090
|
/**
|
|
9776
|
-
* Set by `cancel()` for the duration of a
|
|
9777
|
-
*
|
|
9778
|
-
* user stops. Reset at the start of each
|
|
10091
|
+
* Set by `cancel()` for the duration of a run. Stops it before the next agent
|
|
10092
|
+
* runs — a hand-off's target or a hand-back's caller — so nothing more is asked
|
|
10093
|
+
* of a provider after the user stops. Reset at the start of each run.
|
|
9779
10094
|
*/
|
|
9780
10095
|
this.cancelled = false;
|
|
9781
10096
|
/**
|
|
9782
10097
|
* Whether the CURRENT turn's user message has reached the inner driver's
|
|
9783
10098
|
* history. False through the pre-first-turn classify (where the only echo of
|
|
9784
10099
|
* the message is an optimistic `history-updated` dispatch), true from the
|
|
9785
|
-
* moment `chatDriver.sendMessage` is entered
|
|
9786
|
-
*
|
|
10100
|
+
* moment `chatDriver.sendMessage` is entered. Read by the budget-wall catch in
|
|
10101
|
+
* `sendMessage` to decide
|
|
9787
10102
|
* whether `reportBudgetExhausted` must append the message itself: appending
|
|
9788
10103
|
* it when already appended duplicated it; not appending it when unappended
|
|
9789
10104
|
* made it vanish. Reset at the top of each `runOrchestratedTurn`.
|
|
9790
10105
|
*/
|
|
9791
10106
|
this.userMessageAppended = false;
|
|
10107
|
+
/**
|
|
10108
|
+
* The hand-offs in progress, oldest first (GENC-1737). The top frame's target is the active agent
|
|
10109
|
+
* and holds the flow lock; each caller waits, suspended, for the target above it. Saved with the
|
|
10110
|
+
* session through {@link OrchestratingDriver.getHandOffStack}.
|
|
10111
|
+
*/
|
|
10112
|
+
this.handOffStack = [];
|
|
10113
|
+
/** Hand-offs and hand-backs this user turn, against `maxHandoffs`. */
|
|
10114
|
+
this.handOffsThisTurn = 0;
|
|
9792
10115
|
this.sessionKey = options.sessionKey ?? "";
|
|
9793
10116
|
this.maxHandoffs = options.maxHandoffs ?? DEFAULT_MAX_HANDOFFS;
|
|
10117
|
+
this.maxHandOffDepth = options.maxHandOffDepth ?? DEFAULT_MAX_HAND_OFF_DEPTH;
|
|
10118
|
+
this.chatDriver.setHandOffCheck((request, caller) => this.checkHandOff(request, caller));
|
|
9794
10119
|
this.classifierHistoryLength = options.classifierHistoryLength ?? DEFAULT_CLASSIFIER_HISTORY_LENGTH;
|
|
9795
10120
|
this.classifierRetries = options.classifierRetries ?? DEFAULT_CLASSIFIER_RETRIES;
|
|
9796
10121
|
this.specialists = agents.filter(isSpecialist).filter((a) => !a.excludeFromClassifier);
|
|
@@ -9843,7 +10168,7 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
9843
10168
|
activityBus: options.activityBus,
|
|
9844
10169
|
budgetExhaustedMessage: options.budgetExhaustedMessage,
|
|
9845
10170
|
aiServiceErrorMessages: options.aiServiceErrorMessages,
|
|
9846
|
-
// A release
|
|
10171
|
+
// A release can hand back to a waiting caller, so this driver acts on the flag itself.
|
|
9847
10172
|
deferAgentRelease: true
|
|
9848
10173
|
});
|
|
9849
10174
|
}
|
|
@@ -9883,6 +10208,8 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
9883
10208
|
async runOrchestratedTurn(input, attachments, options, delegated) {
|
|
9884
10209
|
this.cancelled = false;
|
|
9885
10210
|
this.userMessageAppended = false;
|
|
10211
|
+
this.handOffsThisTurn = 0;
|
|
10212
|
+
if (this.pendingHandBack?.resumed) this.pendingHandBack = void 0;
|
|
9886
10213
|
const history = this.chatDriver.getHistory();
|
|
9887
10214
|
this.dispatchEvent(
|
|
9888
10215
|
new CustomEvent("history-updated", {
|
|
@@ -9900,66 +10227,16 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
9900
10227
|
} else {
|
|
9901
10228
|
routed = await this.classify(routingInput, history, this.activeAgent);
|
|
9902
10229
|
}
|
|
9903
|
-
let currentAgent = routed.agent;
|
|
9904
|
-
let isHandoff = false;
|
|
9905
|
-
let handoffs = 0;
|
|
9906
|
-
let handoffSummary = "";
|
|
9907
|
-
let remainingTask = "";
|
|
9908
|
-
const attempted = [];
|
|
9909
10230
|
let lastResult;
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
await this.installAgent(
|
|
9913
|
-
if (this.cancelled)
|
|
9914
|
-
let result;
|
|
9915
|
-
if (isHandoff) {
|
|
9916
|
-
const contextPrimer = handoffSummary ? [{ role: "user", content: `[Context from previous agent]: ${handoffSummary}` }] : [];
|
|
9917
|
-
if (isFallback(currentAgent)) {
|
|
9918
|
-
const work = remainingTask?.trim() || handoffSummary?.trim();
|
|
9919
|
-
contextPrimer.push({
|
|
9920
|
-
role: "user",
|
|
9921
|
-
content: (work ? `[Routing note]: no specialist is available for the remaining work: ${work}
|
|
9922
|
-
` : "[Routing note]: no specialist is available for the work that remains.\n") + "Tell the user plainly what was established, that this part falls outside what any specialist here can do, and what they could do next. Do not claim you will do it yourself, and do not return an empty response."
|
|
9923
|
-
});
|
|
9924
|
-
}
|
|
9925
|
-
result = await this.chatDriver.continueFromHistory(contextPrimer, {
|
|
9926
|
-
tailNote: handoffTailNote(currentAgent, remainingTask)
|
|
9927
|
-
});
|
|
9928
|
-
} else {
|
|
10231
|
+
if (!this.cancelled) {
|
|
10232
|
+
await this.endOrphanedHandOffs(routed.agent);
|
|
10233
|
+
await this.installAgent(routed.agent);
|
|
10234
|
+
if (!this.cancelled) {
|
|
9929
10235
|
this.userMessageAppended = true;
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
lastResult = result;
|
|
9933
|
-
if (this.chatDriver.getAgentReleaseRequested()) {
|
|
9934
|
-
await this.releaseActiveAgent();
|
|
9935
|
-
break;
|
|
9936
|
-
}
|
|
9937
|
-
if (result.reason !== "agent-handoff" || isFallback(currentAgent) || pinned) {
|
|
9938
|
-
break;
|
|
9939
|
-
}
|
|
9940
|
-
if (this.cancelled) break;
|
|
9941
|
-
handoffs += 1;
|
|
9942
|
-
if (handoffs > this.maxHandoffs) {
|
|
9943
|
-
this.appendInlineMessage(
|
|
9944
|
-
`I wasn't able to fully complete your request \u2014 the task required more hand-offs between specialists than allowed. Please try breaking your request into smaller steps.`
|
|
10236
|
+
lastResult = await this.carryOn(
|
|
10237
|
+
await this.chatDriver.sendMessage(input, attachments, options)
|
|
9945
10238
|
);
|
|
9946
|
-
break;
|
|
9947
10239
|
}
|
|
9948
|
-
handoffSummary = result.summary;
|
|
9949
|
-
remainingTask = result.remainingTask;
|
|
9950
|
-
isHandoff = true;
|
|
9951
|
-
if (isSpecialist(currentAgent) && !attempted.includes(currentAgent.name)) {
|
|
9952
|
-
attempted.push(currentAgent.name);
|
|
9953
|
-
}
|
|
9954
|
-
const updatedHistory = this.chatDriver.getHistory();
|
|
9955
|
-
this.dispatchEvent(new CustomEvent("orchestrating-start"));
|
|
9956
|
-
routed = await this.classify(
|
|
9957
|
-
remainingTask?.trim() || routingInput,
|
|
9958
|
-
updatedHistory,
|
|
9959
|
-
void 0,
|
|
9960
|
-
attempted
|
|
9961
|
-
);
|
|
9962
|
-
currentAgent = routed.agent;
|
|
9963
10240
|
}
|
|
9964
10241
|
if (this.cancelled && !this.userMessageAppended) {
|
|
9965
10242
|
this.chatDriver.reportStoppedBeforeTurn({
|
|
@@ -9969,7 +10246,74 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
9969
10246
|
...delegated
|
|
9970
10247
|
});
|
|
9971
10248
|
}
|
|
9972
|
-
return lastResult
|
|
10249
|
+
return lastResult ?? { reason: "done" };
|
|
10250
|
+
}
|
|
10251
|
+
/**
|
|
10252
|
+
* Act on what the agent call that just ended asked for, and run the next agent in the same user
|
|
10253
|
+
* turn while there is one (GENC-1737): a `handOff` runs its target with the brief, and a target
|
|
10254
|
+
* that releases hands back to its caller. Returns the last call's result.
|
|
10255
|
+
*
|
|
10256
|
+
* A failed call ends the turn there, as a Stop does: what it asked for still happens, and the
|
|
10257
|
+
* next agent runs on the next message. Running it now would meet the same wall (a budget, a
|
|
10258
|
+
* refused account, a full context window), or build on a reply that never came.
|
|
10259
|
+
*/
|
|
10260
|
+
async carryOn(result) {
|
|
10261
|
+
let last = result;
|
|
10262
|
+
let tailNote = await this.afterAgentCall();
|
|
10263
|
+
while (tailNote !== void 0 && !this.cancelled && !last.failureReason) {
|
|
10264
|
+
last = await this.chatDriver.continueFromHistory(void 0, { tailNote });
|
|
10265
|
+
tailNote = await this.afterAgentCall();
|
|
10266
|
+
}
|
|
10267
|
+
return last;
|
|
10268
|
+
}
|
|
10269
|
+
/**
|
|
10270
|
+
* After one agent call: start the hand-off it asked for, or release it and, when it was a
|
|
10271
|
+
* hand-off's target, hand back. Returns the tail note for the agent to run next, or `undefined`
|
|
10272
|
+
* when the turn ends here.
|
|
10273
|
+
*
|
|
10274
|
+
* A plain target hands back when its call ends, released or not, as a plain agent is done once
|
|
10275
|
+
* it has answered. A stateful one holds the hand-off until it releases, as it holds its flow.
|
|
10276
|
+
*/
|
|
10277
|
+
async afterAgentCall() {
|
|
10278
|
+
const request = this.chatDriver.getHandOffRequest();
|
|
10279
|
+
if (request) return this.startHandOff(request);
|
|
10280
|
+
const active = this.agentSession.getActive();
|
|
10281
|
+
if (!active) return void 0;
|
|
10282
|
+
const frame = this.topFrameFor(active.name);
|
|
10283
|
+
const released = this.chatDriver.getAgentReleaseRequested();
|
|
10284
|
+
if (!released && !(frame && !isStateful(active))) {
|
|
10285
|
+
if (!isStateful(active)) this.endPlainTurn(active);
|
|
10286
|
+
return void 0;
|
|
10287
|
+
}
|
|
10288
|
+
if (!frame) {
|
|
10289
|
+
await this.releaseActiveAgent();
|
|
10290
|
+
return void 0;
|
|
10291
|
+
}
|
|
10292
|
+
frame.outcome = this.outcomeOf(frame);
|
|
10293
|
+
await this.releaseActiveAgent();
|
|
10294
|
+
return this.handBack(frame, frame.outcome);
|
|
10295
|
+
}
|
|
10296
|
+
/** How the target of `frame` finished: with `completeHandOff`, or a release and its last reply. */
|
|
10297
|
+
outcomeOf(frame) {
|
|
10298
|
+
const completion = this.chatDriver.getHandOffCompletion();
|
|
10299
|
+
if (completion) {
|
|
10300
|
+
return {
|
|
10301
|
+
kind: "completed",
|
|
10302
|
+
message: completion.message,
|
|
10303
|
+
...completion.data !== void 0 && { data: completion.data }
|
|
10304
|
+
};
|
|
10305
|
+
}
|
|
10306
|
+
const finalReply = this.lastReply(frame);
|
|
10307
|
+
return { kind: "released", ...finalReply ? { message: finalReply } : {} };
|
|
10308
|
+
}
|
|
10309
|
+
/**
|
|
10310
|
+
* A plain agent is done once it has answered (GENC-1737): the data a one-way hand-off gave it ends
|
|
10311
|
+
* with its turn, and so does a flow lock no hand-off holds, such as one a restore left on a
|
|
10312
|
+
* target whose hand-off it could not resume.
|
|
10313
|
+
*/
|
|
10314
|
+
endPlainTurn(agent) {
|
|
10315
|
+
this.agentSession.setHandOffData(agent.name, void 0);
|
|
10316
|
+
if (this.agentSession.getFlowOwner() === agent.name) this.agentSession.dropFlowLock();
|
|
9973
10317
|
}
|
|
9974
10318
|
/** {@inheritDoc AiDriver.canRestoreFlowOwner} */
|
|
9975
10319
|
canRestoreFlowOwner(agentName) {
|
|
@@ -9993,15 +10337,33 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
9993
10337
|
* undecorated.
|
|
9994
10338
|
*/
|
|
9995
10339
|
async resumeRestoredFlow(resumeNote) {
|
|
10340
|
+
if (this.pendingHandBack) return this.resumeHandBack(resumeNote);
|
|
9996
10341
|
const owner = this.agentSession.getFlowOwner();
|
|
9997
10342
|
const agent = owner ? this.agents.find((a) => a.name === owner) : void 0;
|
|
9998
10343
|
if (!agent) return void 0;
|
|
9999
10344
|
this.cancelled = false;
|
|
10345
|
+
this.handOffsThisTurn = 0;
|
|
10000
10346
|
await this.installAgent(agent);
|
|
10001
10347
|
if (!resumeNote || agent.resumeOnRestore === false || this.cancelled) return void 0;
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10348
|
+
return this.carryOn(
|
|
10349
|
+
await this.chatDriver.continueFromHistory(void 0, { tailNote: resumeNote })
|
|
10350
|
+
);
|
|
10351
|
+
}
|
|
10352
|
+
/**
|
|
10353
|
+
* The hand-back a restore found due (GENC-1737). The first call, inside the host's restore gate,
|
|
10354
|
+
* makes the caller active again and tells it the outcome; the call with the note runs its turn.
|
|
10355
|
+
*/
|
|
10356
|
+
async resumeHandBack(note) {
|
|
10357
|
+
const pending = this.pendingHandBack;
|
|
10358
|
+
this.cancelled = false;
|
|
10359
|
+
if (!pending.resumed) {
|
|
10360
|
+
pending.resumed = true;
|
|
10361
|
+
await this.handBack(pending.frame, pending.frame.outcome);
|
|
10362
|
+
}
|
|
10363
|
+
if (!note || this.cancelled) return void 0;
|
|
10364
|
+
this.pendingHandBack = void 0;
|
|
10365
|
+
this.handOffsThisTurn = 0;
|
|
10366
|
+
return this.carryOn(await this.chatDriver.continueFromHistory(void 0, { tailNote: note }));
|
|
10005
10367
|
}
|
|
10006
10368
|
/**
|
|
10007
10369
|
* A continuation outside a routed turn — a decorator carrying the agent on, say — can finish the
|
|
@@ -10009,15 +10371,9 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
10009
10371
|
* (GENC-1660), only `sendMessage` checked, and a flow finished by a continuation stayed locked.
|
|
10010
10372
|
*/
|
|
10011
10373
|
async continueFromHistory(transientPrimer, options) {
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
return
|
|
10015
|
-
}
|
|
10016
|
-
/** Release the flow if the turn that just ended asked to, and an agent is still active to release. */
|
|
10017
|
-
async releaseIfRequested() {
|
|
10018
|
-
if (this.chatDriver.getAgentReleaseRequested() && this.agentSession.getActive()) {
|
|
10019
|
-
await this.releaseActiveAgent();
|
|
10020
|
-
}
|
|
10374
|
+
this.cancelled = false;
|
|
10375
|
+
this.handOffsThisTurn = 0;
|
|
10376
|
+
return this.carryOn(await this.chatDriver.continueFromHistory(transientPrimer, options));
|
|
10021
10377
|
}
|
|
10022
10378
|
/**
|
|
10023
10379
|
* {@inheritDoc AiDriver.getContextReserve}
|
|
@@ -10045,25 +10401,18 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
10045
10401
|
}
|
|
10046
10402
|
return winner ? { ...winner, agentName, basis: "reachable-max" } : void 0;
|
|
10047
10403
|
}
|
|
10404
|
+
/** Make `agent` active through the session, firing its hooks, and install it. */
|
|
10405
|
+
async installAgent(agent) {
|
|
10406
|
+
const { previousAgent } = await this.agentSession.activate(agent);
|
|
10407
|
+
this.applyAgent(agent, previousAgent?.name);
|
|
10408
|
+
}
|
|
10048
10409
|
/**
|
|
10049
|
-
*
|
|
10050
|
-
*
|
|
10410
|
+
* Install an agent the session has already made active on the inner driver: the transcript's
|
|
10411
|
+
* divider when another agent spoke before it, and its own view of the history. What is left here
|
|
10412
|
+
* is this driver's own job, the transcript; the session owns the lifecycle.
|
|
10051
10413
|
*/
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
const isTerminal = isFallback(agent) || (pin === void 0 ? this.agentSession.getPinned() : pin) !== null || this.agentSession.getFlowOwner() !== null || !!(agent.onActivate || agent.onDeactivate);
|
|
10055
|
-
let agentToApply = agent;
|
|
10056
|
-
if (!isTerminal) {
|
|
10057
|
-
const declaredTools = agent.toolDefinitions;
|
|
10058
|
-
agentToApply = {
|
|
10059
|
-
...agent,
|
|
10060
|
-
toolDefinitions: typeof declaredTools === "function" ? async (ctx) => [
|
|
10061
|
-
...await declaredTools(ctx),
|
|
10062
|
-
REQUEST_CONTINUATION_DEFINITION
|
|
10063
|
-
] : [...declaredTools ?? [], REQUEST_CONTINUATION_DEFINITION]
|
|
10064
|
-
};
|
|
10065
|
-
}
|
|
10066
|
-
if (previousAgent && previousAgent.name !== agent.name) {
|
|
10414
|
+
applyAgent(agent, previousName) {
|
|
10415
|
+
if (previousName && previousName !== agent.name) {
|
|
10067
10416
|
const rawHistory = this.chatDriver.getHistory();
|
|
10068
10417
|
this.chatDriver.loadHistory([
|
|
10069
10418
|
...rawHistory,
|
|
@@ -10071,7 +10420,7 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
10071
10420
|
]);
|
|
10072
10421
|
}
|
|
10073
10422
|
this.chatDriver.setProviderHistoryTransform((h) => transformHistoryForAgent(h, agent.name));
|
|
10074
|
-
this.chatDriver.installAgent(
|
|
10423
|
+
this.chatDriver.installAgent(agent);
|
|
10075
10424
|
this.dispatchEvent(new CustomEvent("orchestrating-stop"));
|
|
10076
10425
|
}
|
|
10077
10426
|
/**
|
|
@@ -10088,10 +10437,250 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
10088
10437
|
async releaseActiveAgent() {
|
|
10089
10438
|
await this.agentSession.release();
|
|
10090
10439
|
}
|
|
10440
|
+
/** The agent named `name` as this driver installs it: the fallback with its prompt built. */
|
|
10441
|
+
agentNamed(name) {
|
|
10442
|
+
if (this.fallback?.name === name) return this.fallback;
|
|
10443
|
+
return this.agents.find((a) => a.name === name);
|
|
10444
|
+
}
|
|
10445
|
+
/**
|
|
10446
|
+
* End the hand-offs when a turn runs an agent other than the top target (GENC-1737): the lock
|
|
10447
|
+
* that kept turns on the target was taken away, by a host clearing `flowOwnerAgentName` say,
|
|
10448
|
+
* and its caller would otherwise wait for good.
|
|
10449
|
+
*/
|
|
10450
|
+
async endOrphanedHandOffs(agent) {
|
|
10451
|
+
const top = this.handOffStack[this.handOffStack.length - 1];
|
|
10452
|
+
if (!top || top.outcome || top.target === agent.name) return;
|
|
10453
|
+
await this.unwindHandOffs("cleared");
|
|
10454
|
+
await this.agentSession.endSuspended();
|
|
10455
|
+
}
|
|
10456
|
+
/**
|
|
10457
|
+
* Check a handler's `handOff` when it is made (GENC-1737), so a refusal reaches its model. Checked
|
|
10458
|
+
* against the agents and the stack as they are now; the hand-off itself starts after the call.
|
|
10459
|
+
*/
|
|
10460
|
+
checkHandOff(request, caller) {
|
|
10461
|
+
const target = this.agentNamed(request.to);
|
|
10462
|
+
if (!target) return { ok: false, reason: "unknown-agent" };
|
|
10463
|
+
const allowed = this.agents.find((a) => a.name === caller)?.handOffTargets;
|
|
10464
|
+
if (allowed && !allowed.includes(target.name)) return { ok: false, reason: "not-allowed" };
|
|
10465
|
+
if (target.name === caller || this.handOffStack.some((f) => f.caller === target.name || f.target === target.name)) {
|
|
10466
|
+
return { ok: false, reason: "re-entry" };
|
|
10467
|
+
}
|
|
10468
|
+
if (request.returnTo !== false && this.handOffStack.length >= this.maxHandOffDepth) {
|
|
10469
|
+
return { ok: false, reason: "depth" };
|
|
10470
|
+
}
|
|
10471
|
+
if (this.handOffsThisTurn >= this.maxHandoffs) return { ok: false, reason: "limit" };
|
|
10472
|
+
return { ok: true };
|
|
10473
|
+
}
|
|
10474
|
+
/**
|
|
10475
|
+
* Start the hand-off the agent call just made, and return the target's tail note (GENC-1737).
|
|
10476
|
+
*
|
|
10477
|
+
* - `returnTo` (the default): push a frame. The caller waits without being deactivated, its
|
|
10478
|
+
* `agentEnd` payloads suspended, and the target takes the flow lock.
|
|
10479
|
+
* - One-way from a hand-off's target: that frame's caller now waits for the new target instead.
|
|
10480
|
+
* - One-way with nobody waiting: a plain switch, the caller deactivated as on any swap.
|
|
10481
|
+
*
|
|
10482
|
+
* Either way the brief is recorded as a message the caller sent on the user's behalf.
|
|
10483
|
+
*/
|
|
10484
|
+
async startHandOff(request) {
|
|
10485
|
+
const caller = this.agentSession.getActive();
|
|
10486
|
+
const target = this.agentNamed(request.to);
|
|
10487
|
+
if (!caller || !target) return void 0;
|
|
10488
|
+
this.handOffsThisTurn += 1;
|
|
10489
|
+
const frame = {
|
|
10490
|
+
id: crypto.randomUUID(),
|
|
10491
|
+
caller: caller.name,
|
|
10492
|
+
target: target.name,
|
|
10493
|
+
returnTo: request.returnTo !== false,
|
|
10494
|
+
message: request.message,
|
|
10495
|
+
...request.data !== void 0 && { data: request.data },
|
|
10496
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10497
|
+
};
|
|
10498
|
+
const top = this.handOffStack[this.handOffStack.length - 1];
|
|
10499
|
+
if (frame.returnTo) {
|
|
10500
|
+
this.handOffStack.push(frame);
|
|
10501
|
+
await callHandOffHook(
|
|
10502
|
+
caller.name,
|
|
10503
|
+
"onHandOffStart",
|
|
10504
|
+
() => caller.onHandOffStart?.(frameCopy(frame))
|
|
10505
|
+
);
|
|
10506
|
+
this.chatDriver.suspendActivation(caller.name);
|
|
10507
|
+
await this.agentSession.handOff(target, { suspendCaller: true });
|
|
10508
|
+
} else if (top?.target === caller.name) {
|
|
10509
|
+
top.target = target.name;
|
|
10510
|
+
await this.agentSession.handOff(target, { suspendCaller: false });
|
|
10511
|
+
} else {
|
|
10512
|
+
await this.agentSession.activate(target);
|
|
10513
|
+
}
|
|
10514
|
+
this.applyAgent(this.agentSession.getActive() ?? target, caller.name);
|
|
10515
|
+
this.agentSession.setHandOffData(target.name, frameCopy(frame).data);
|
|
10516
|
+
this.recordDelegated(caller.name, frame.message, `Handed over by ${caller.name}`);
|
|
10517
|
+
this.dispatchEvent(new CustomEvent("handoff-started", { detail: frameCopy(frame) }));
|
|
10518
|
+
return handOffNote(caller.name);
|
|
10519
|
+
}
|
|
10520
|
+
/**
|
|
10521
|
+
* Hand back to `frame`'s caller with `outcome`, and return its tail note (GENC-1737). The outcome
|
|
10522
|
+
* is kept on the frame until the caller is active again and its `onHandOffReturn` has run, so a
|
|
10523
|
+
* reload in between still hands back; the frame then goes, before the caller's model call.
|
|
10524
|
+
*/
|
|
10525
|
+
async handBack(frame, outcome) {
|
|
10526
|
+
frame.outcome = outcome;
|
|
10527
|
+
this.handOffsThisTurn += 1;
|
|
10528
|
+
const remaining = this.handOffStack.filter((f) => f !== frame);
|
|
10529
|
+
const caller = this.agentNamed(frame.caller);
|
|
10530
|
+
if (!caller) {
|
|
10531
|
+
this.handOffStack = remaining;
|
|
10532
|
+
this.unwound(frame, "agent-removed");
|
|
10533
|
+
return void 0;
|
|
10534
|
+
}
|
|
10535
|
+
await this.agentSession.resume(caller, remaining.length > 0 || isStateful(caller));
|
|
10536
|
+
const live2 = this.agentSession.getActive() ?? caller;
|
|
10537
|
+
this.applyAgent(live2, frame.target);
|
|
10538
|
+
if (outcome.kind === "completed" && outcome.message) {
|
|
10539
|
+
this.recordDelegated(frame.target, outcome.message, `Handed back by ${frame.target}`);
|
|
10540
|
+
}
|
|
10541
|
+
await callHandOffHook(
|
|
10542
|
+
live2.name,
|
|
10543
|
+
"onHandOffReturn",
|
|
10544
|
+
() => live2.onHandOffReturn?.(structuredClone(outcome), frameCopy(frame))
|
|
10545
|
+
);
|
|
10546
|
+
this.handOffStack = remaining;
|
|
10547
|
+
this.dispatchEvent(new CustomEvent("handoff-returned", { detail: frameCopy(frame) }));
|
|
10548
|
+
return handBackNote(frame, outcome);
|
|
10549
|
+
}
|
|
10550
|
+
/** The top frame, when `agent` is its target. */
|
|
10551
|
+
topFrameFor(agent) {
|
|
10552
|
+
const top = this.handOffStack[this.handOffStack.length - 1];
|
|
10553
|
+
return top?.target === agent ? top : void 0;
|
|
10554
|
+
}
|
|
10555
|
+
/** The target's last reply since its brief, for a target that released without a summary. */
|
|
10556
|
+
lastReply(frame) {
|
|
10557
|
+
const history = this.chatDriver.getHistory();
|
|
10558
|
+
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
10559
|
+
const m = history[i];
|
|
10560
|
+
if (m.role === "user" && m.delegate === frame.caller) return void 0;
|
|
10561
|
+
if (m.role === "assistant" && m.agentName === frame.target && reachesProvider(m) && m.content?.trim()) {
|
|
10562
|
+
return m.content;
|
|
10563
|
+
}
|
|
10564
|
+
}
|
|
10565
|
+
return void 0;
|
|
10566
|
+
}
|
|
10567
|
+
/** The brief, in the transcript as a message the caller sent on the user's behalf. */
|
|
10568
|
+
recordDelegated(from, message, framing) {
|
|
10569
|
+
const history = this.chatDriver.getHistory();
|
|
10570
|
+
this.chatDriver.loadHistory([
|
|
10571
|
+
...history,
|
|
10572
|
+
{
|
|
10573
|
+
role: "user",
|
|
10574
|
+
content: message,
|
|
10575
|
+
delegate: from,
|
|
10576
|
+
delegateFraming: framing,
|
|
10577
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10578
|
+
}
|
|
10579
|
+
]);
|
|
10580
|
+
this.dispatchEvent(
|
|
10581
|
+
new CustomEvent("history-updated", { detail: this.chatDriver.getHistory() })
|
|
10582
|
+
);
|
|
10583
|
+
}
|
|
10584
|
+
/** End `frame` with `kind` and no hand-back, for the host. */
|
|
10585
|
+
unwound(frame, kind) {
|
|
10586
|
+
frame.outcome = { kind };
|
|
10587
|
+
this.dispatchEvent(new CustomEvent("handoff-unwound", { detail: frameCopy(frame) }));
|
|
10588
|
+
}
|
|
10589
|
+
/** {@inheritDoc AiDriver.getHandOffStack} */
|
|
10590
|
+
getHandOffStack() {
|
|
10591
|
+
return this.handOffStack.map(frameCopy);
|
|
10592
|
+
}
|
|
10593
|
+
/** {@inheritDoc AiDriver.restoreHandOffStack} */
|
|
10594
|
+
restoreHandOffStack(saved, savedStates, interrupted) {
|
|
10595
|
+
this.handOffStack = [];
|
|
10596
|
+
this.pendingHandBack = void 0;
|
|
10597
|
+
const kept = [];
|
|
10598
|
+
const unwoundTargets = /* @__PURE__ */ new Set();
|
|
10599
|
+
for (const frame of parseHandOffFrames(saved)) {
|
|
10600
|
+
const caller = this.agents.find((a) => a.name === frame.caller);
|
|
10601
|
+
const outcome = frame.outcome;
|
|
10602
|
+
if (!caller) {
|
|
10603
|
+
this.unwound(frame, "agent-removed");
|
|
10604
|
+
} else if (interrupted || caller.serialize && savedStates?.[caller.name] === void 0) {
|
|
10605
|
+
this.unwound(frame, "state-lost");
|
|
10606
|
+
} else {
|
|
10607
|
+
kept.push(frame);
|
|
10608
|
+
continue;
|
|
10609
|
+
}
|
|
10610
|
+
this.agentSession.setHandOffData(frame.caller, void 0);
|
|
10611
|
+
unwoundTargets.add(frame.target);
|
|
10612
|
+
const below = kept[kept.length - 1];
|
|
10613
|
+
if (below?.target === frame.caller) {
|
|
10614
|
+
below.target = frame.target;
|
|
10615
|
+
if (outcome) below.outcome = outcome;
|
|
10616
|
+
}
|
|
10617
|
+
}
|
|
10618
|
+
const top = kept[kept.length - 1];
|
|
10619
|
+
if (top && !top.outcome && !this.agents.some((a) => a.name === top.target)) {
|
|
10620
|
+
top.outcome = { kind: "agent-removed" };
|
|
10621
|
+
}
|
|
10622
|
+
this.handOffStack = kept;
|
|
10623
|
+
this.chatDriver.suspendRestoredActivations(kept.map((f) => f.caller));
|
|
10624
|
+
const droppedLocks = [...unwoundTargets].filter((name) => {
|
|
10625
|
+
const target = this.agents.find((a) => a.name === name);
|
|
10626
|
+
return !!target && !isStateful(target) && !kept.some((f) => f.target === name);
|
|
10627
|
+
});
|
|
10628
|
+
const dropped = droppedLocks.length ? { droppedLocks } : {};
|
|
10629
|
+
if (!top?.outcome) return dropped;
|
|
10630
|
+
this.pendingHandBack = { frame: top, resumed: false };
|
|
10631
|
+
return { ...dropped, resume: { agent: top.caller, note: handBackNote(top, top.outcome) } };
|
|
10632
|
+
}
|
|
10633
|
+
/** {@inheritDoc AiDriver.unwindHandOffs} */
|
|
10634
|
+
async unwindHandOffs(kind) {
|
|
10635
|
+
const frames = [...this.handOffStack].reverse();
|
|
10636
|
+
this.handOffStack = [];
|
|
10637
|
+
this.pendingHandBack = void 0;
|
|
10638
|
+
this.chatDriver.endSuspendedActivations();
|
|
10639
|
+
for (const frame of frames) {
|
|
10640
|
+
this.unwound(frame, kind);
|
|
10641
|
+
const live2 = this.agentSession.getSuspended().find((a) => a.name === frame.caller);
|
|
10642
|
+
if (kind === "cleared" && live2) {
|
|
10643
|
+
await callHandOffHook(
|
|
10644
|
+
live2.name,
|
|
10645
|
+
"onHandOffReturn",
|
|
10646
|
+
() => live2.onHandOffReturn?.(structuredClone(frame.outcome), frameCopy(frame))
|
|
10647
|
+
);
|
|
10648
|
+
}
|
|
10649
|
+
}
|
|
10650
|
+
}
|
|
10651
|
+
/** {@inheritDoc AiDriver.getHandOffData} */
|
|
10652
|
+
getHandOffData() {
|
|
10653
|
+
const live2 = [this.agentSession.getFlowOwner(), ...this.handOffStack.map((f) => f.caller)];
|
|
10654
|
+
const saved = {};
|
|
10655
|
+
for (const name of live2) {
|
|
10656
|
+
const data = name ? this.agentSession.getHandOffData(name) : void 0;
|
|
10657
|
+
if (name && data !== void 0) saved[name] = data;
|
|
10658
|
+
}
|
|
10659
|
+
return saved;
|
|
10660
|
+
}
|
|
10661
|
+
/** {@inheritDoc AiDriver.restoreHandOffData} */
|
|
10662
|
+
restoreHandOffData(saved, interrupted) {
|
|
10663
|
+
if (interrupted || !saved || typeof saved !== "object" || Array.isArray(saved)) return;
|
|
10664
|
+
for (const [name, data] of Object.entries(saved)) this.agentSession.setHandOffData(name, data);
|
|
10665
|
+
}
|
|
10666
|
+
/** {@inheritDoc AiDriver.serializeWaitingCallers} */
|
|
10667
|
+
serializeWaitingCallers() {
|
|
10668
|
+
const states = {};
|
|
10669
|
+
for (const caller of this.agentSession.getSuspended()) {
|
|
10670
|
+
if (!caller.serialize) continue;
|
|
10671
|
+
try {
|
|
10672
|
+
const state = caller.serialize();
|
|
10673
|
+
if (state !== void 0) states[caller.name] = state;
|
|
10674
|
+
} catch (e) {
|
|
10675
|
+
logger2.error(`OrchestratingDriver: serialize("${caller.name}") threw:`, e);
|
|
10676
|
+
}
|
|
10677
|
+
}
|
|
10678
|
+
return states;
|
|
10679
|
+
}
|
|
10091
10680
|
/**
|
|
10092
10681
|
* Stop the current turn (user "stop" button). Cancels the inner chat driver's
|
|
10093
|
-
* in-flight request and
|
|
10094
|
-
*
|
|
10682
|
+
* in-flight request, and no further agent runs this turn, not even a hand-off's
|
|
10683
|
+
* target or a hand-back. The driver stays usable for the next message.
|
|
10095
10684
|
*/
|
|
10096
10685
|
cancel() {
|
|
10097
10686
|
this.cancelled = true;
|
|
@@ -10128,12 +10717,8 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
10128
10717
|
decision,
|
|
10129
10718
|
to: agent.name,
|
|
10130
10719
|
fallback: isFallback(agent),
|
|
10131
|
-
// The
|
|
10132
|
-
//
|
|
10133
|
-
// `candidates[index]` resolve to the wrong agent whenever `excluded` was non-empty — and the
|
|
10134
|
-
// README sentence invited exactly that join. `this.specialists` stays recoverable as
|
|
10135
|
-
// `candidates ∪ excluded`. Absent where nothing was put to a vote, which is more truthful
|
|
10136
|
-
// than a full list. Reviewer catch on #2496.
|
|
10720
|
+
// The ballot, so `candidates[index]` is the agent chosen. Absent where nothing was put to a
|
|
10721
|
+
// vote, which is more truthful than a full list.
|
|
10137
10722
|
...ballot ? { candidates: ballot } : {},
|
|
10138
10723
|
...rest,
|
|
10139
10724
|
...usage ? { usage } : {},
|
|
@@ -10156,7 +10741,7 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
10156
10741
|
}
|
|
10157
10742
|
return lines.join("\n");
|
|
10158
10743
|
}
|
|
10159
|
-
async classify(input, history, contextAgent
|
|
10744
|
+
async classify(input, history, contextAgent) {
|
|
10160
10745
|
const classifyStartedAt = Date.now();
|
|
10161
10746
|
if (this.specialists.length === 1 && !this.fallback) {
|
|
10162
10747
|
const only = this.specialists[0];
|
|
@@ -10179,27 +10764,16 @@ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator
|
|
|
10179
10764
|
this.recordRouting("no-specialists", leaf, { durationMs: Date.now() - classifyStartedAt });
|
|
10180
10765
|
return { agent: leaf, decision: "no-specialists" };
|
|
10181
10766
|
}
|
|
10182
|
-
const ballot =
|
|
10183
|
-
if (ballot.length === 0) {
|
|
10184
|
-
const leaf = this.fallback ?? this.specialists[0];
|
|
10185
|
-
this.recordRouting("ballot-exhausted", leaf, {
|
|
10186
|
-
excluded: excludeAgents,
|
|
10187
|
-
durationMs: Date.now() - classifyStartedAt
|
|
10188
|
-
});
|
|
10189
|
-
return { agent: leaf, decision: "ballot-exhausted" };
|
|
10190
|
-
}
|
|
10767
|
+
const ballot = this.specialists;
|
|
10191
10768
|
const agentList = ballot.map((a, i) => `${i}: ${a.name} \u2014 ${a.description}`).join("\n");
|
|
10192
10769
|
const recentMessages = this.classifierTranscript(history);
|
|
10193
|
-
const continuationHint = contextAgent && isSpecialist(contextAgent)
|
|
10770
|
+
const continuationHint = contextAgent && isSpecialist(contextAgent) ? `
|
|
10194
10771
|
|
|
10195
10772
|
The previous turn was handled by "${contextAgent.name}". If the new message is a refinement, correction, or continuation of that work, prefer "${contextAgent.name}" \u2014 unless the topic has clearly shifted to another specialist's domain.` : "";
|
|
10196
|
-
const
|
|
10197
|
-
|
|
10198
|
-
Already attempted this turn and handed the work on, so not listed above: ${excludeAgents.join(", ")}. Do not expect another agent to repeat their part.` : "";
|
|
10199
|
-
const classifierPrompt = `You are a routing classifier. Call select_agent with the index of the best matching agent for the user message. Use -1 only if the request is entirely unrelated to every agent listed \u2014 prefer the closest match when in doubt. If the request spans multiple agents, pick the one that should act first; I will re-classify and route again once that agent is done.
|
|
10773
|
+
const classifierPrompt = `You are a routing classifier. Call select_agent with the index of the best matching agent for the user message. Use -1 only if the request is entirely unrelated to every agent listed \u2014 prefer the closest match when in doubt. If the request spans multiple agents, pick the one that should act first.
|
|
10200
10774
|
|
|
10201
10775
|
Agents:
|
|
10202
|
-
${agentList}${continuationHint}${
|
|
10776
|
+
${agentList}${continuationHint}${recentMessages ? `
|
|
10203
10777
|
|
|
10204
10778
|
Recent conversation:
|
|
10205
10779
|
${recentMessages}` : ""}`;
|
|
@@ -10247,8 +10821,7 @@ ${recentMessages}` : ""}`;
|
|
|
10247
10821
|
attempts,
|
|
10248
10822
|
durationMs: Date.now() - classifyStartedAt,
|
|
10249
10823
|
response,
|
|
10250
|
-
ballot: ballot.map((a) => a.name)
|
|
10251
|
-
...excludeAgents.length ? { excluded: excludeAgents } : {}
|
|
10824
|
+
ballot: ballot.map((a) => a.name)
|
|
10252
10825
|
});
|
|
10253
10826
|
return { agent: chosen, decision: "classified" };
|
|
10254
10827
|
}
|
|
@@ -10269,8 +10842,7 @@ ${recentMessages}` : ""}`;
|
|
|
10269
10842
|
durationMs: Date.now() - classifyStartedAt,
|
|
10270
10843
|
ballot: ballot.map((a) => a.name),
|
|
10271
10844
|
...lastError ? { error: lastError } : {},
|
|
10272
|
-
...lastResponse ? { response: lastResponse } : {}
|
|
10273
|
-
...excludeAgents.length ? { excluded: excludeAgents } : {}
|
|
10845
|
+
...lastResponse ? { response: lastResponse } : {}
|
|
10274
10846
|
};
|
|
10275
10847
|
if (this.fallback) {
|
|
10276
10848
|
this.recordRouting(decision, this.fallback, routingDetail);
|
|
@@ -10308,7 +10880,7 @@ ${recentMessages}` : ""}`;
|
|
|
10308
10880
|
return match;
|
|
10309
10881
|
}
|
|
10310
10882
|
/**
|
|
10311
|
-
* Append prose the ORCHESTRATOR wrote — the
|
|
10883
|
+
* Append prose the ORCHESTRATOR wrote — the no-specialist apology. Flagged
|
|
10312
10884
|
* `excludeFromProvider` for the same reason the driver's own notices are: it is not something a
|
|
10313
10885
|
* model said, and feeding it back invites the model to echo it. See
|
|
10314
10886
|
* `ChatMessage.excludeFromProvider`.
|
|
@@ -10816,7 +11388,6 @@ export {
|
|
|
10816
11388
|
OrchestratingDriver,
|
|
10817
11389
|
PROVIDER_REFUSED_CODE,
|
|
10818
11390
|
ProviderRefusedError,
|
|
10819
|
-
REQUEST_CONTINUATION_TOOL,
|
|
10820
11391
|
SUPPORTED_ANTHROPIC_MODEL_IDS,
|
|
10821
11392
|
SUPPORTED_GEMINI_MODEL_IDS,
|
|
10822
11393
|
UNROUTABLE_INSTRUCTION,
|