@animalabs/agent-framework 0.6.7 → 0.6.10
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/src/agent.d.ts +5 -34
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +62 -137
- package/dist/src/agent.js.map +1 -1
- package/dist/src/framework.d.ts +14 -56
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +192 -1742
- package/dist/src/framework.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +10 -0
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +48 -2
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +2 -0
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/modules/workspace/watcher.d.ts +17 -0
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js +143 -7
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/types/agent.d.ts +15 -12
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/test/helpers/mock-membrane.js +1 -1
- package/dist/test/helpers/mock-membrane.js.map +1 -1
- package/dist/test/mcpl-awareness-barrier.test.js +1 -228
- package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
- package/dist/test/refusal-single-call.test.d.ts +2 -0
- package/dist/test/refusal-single-call.test.d.ts.map +1 -0
- package/dist/test/refusal-single-call.test.js +177 -0
- package/dist/test/refusal-single-call.test.js.map +1 -0
- package/dist/test/trunk-channel-routing.test.js +5 -1
- package/dist/test/trunk-channel-routing.test.js.map +1 -1
- package/dist/test/workspace-watcher.test.js +161 -9
- package/dist/test/workspace-watcher.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/agent.ts +67 -226
- package/src/framework.ts +226 -2394
- package/src/modules/workspace/index.ts +47 -2
- package/src/modules/workspace/types.ts +2 -0
- package/src/modules/workspace/watcher.ts +160 -7
- package/src/types/agent.ts +17 -12
- package/dist/src/canonical-json.d.ts +0 -2
- package/dist/src/canonical-json.d.ts.map +0 -1
- package/dist/src/canonical-json.js +0 -23
- package/dist/src/canonical-json.js.map +0 -1
- package/dist/test/primary-summary-refusal-fallback-integration.test.d.ts +0 -2
- package/dist/test/primary-summary-refusal-fallback-integration.test.d.ts.map +0 -1
- package/dist/test/primary-summary-refusal-fallback-integration.test.js +0 -1955
- package/dist/test/primary-summary-refusal-fallback-integration.test.js.map +0 -1
- package/dist/test/primary-summary-refusal-fallback.test.d.ts +0 -2
- package/dist/test/primary-summary-refusal-fallback.test.d.ts.map +0 -1
- package/dist/test/primary-summary-refusal-fallback.test.js +0 -753
- package/dist/test/primary-summary-refusal-fallback.test.js.map +0 -1
- package/src/canonical-json.ts +0 -22
package/src/framework.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
3
|
import { JsStore } from '@animalabs/chronicle';
|
|
5
|
-
import type { Membrane, ContentBlock, NormalizedRequest,
|
|
4
|
+
import type { Membrane, ContentBlock, NormalizedRequest, YieldingStream, ToolResult as MembraneToolResult, ToolResultContentBlock } from '@animalabs/membrane';
|
|
6
5
|
import { MembraneError } from '@animalabs/membrane';
|
|
7
6
|
import { ContextManager, PassthroughStrategy } from '@animalabs/context-manager';
|
|
8
7
|
import type {
|
|
9
|
-
BranchGenerationInfo,
|
|
10
8
|
MessageId,
|
|
11
9
|
MessageMetadata,
|
|
12
10
|
MessageQuery,
|
|
13
11
|
MessageQueryResult,
|
|
14
|
-
PrimarySummaryContract,
|
|
15
|
-
PrimarySummaryIdentity,
|
|
16
|
-
PrimarySummaryProjection,
|
|
17
12
|
StoredMessage,
|
|
18
13
|
} from '@animalabs/context-manager';
|
|
19
14
|
import type {
|
|
@@ -56,7 +51,7 @@ import type {
|
|
|
56
51
|
SameRoundThinkTextPolicy,
|
|
57
52
|
} from './types/index.js';
|
|
58
53
|
import { ProcessQueueImpl } from './queue.js';
|
|
59
|
-
import { Agent
|
|
54
|
+
import { Agent } from './agent.js';
|
|
60
55
|
import { ModuleRegistry, isStateExistsError } from './module-registry.js';
|
|
61
56
|
import { McplServerRegistry } from './mcpl/server-registry.js';
|
|
62
57
|
import { FeatureSetManager } from './mcpl/feature-set-manager.js';
|
|
@@ -70,7 +65,29 @@ import { safeSlice } from './safe-slice.js';
|
|
|
70
65
|
import type { WorkspaceModule } from './modules/workspace/index.js';
|
|
71
66
|
import { toolResultDataToHistoryString } from './tool-result-history.js';
|
|
72
67
|
import { splitProseSegments } from './prose-segments.js';
|
|
73
|
-
|
|
68
|
+
|
|
69
|
+
/** Detect a supported image media type from magic bytes (the model API
|
|
70
|
+
* rejects mislabeled media types, so trust bytes over extensions).
|
|
71
|
+
* Returns undefined for non-image content. */
|
|
72
|
+
function sniffImageMediaType(data: Buffer): string | undefined {
|
|
73
|
+
if (data.length >= 4 && data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4e && data[3] === 0x47) {
|
|
74
|
+
return 'image/png';
|
|
75
|
+
}
|
|
76
|
+
if (data.length >= 3 && data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff) {
|
|
77
|
+
return 'image/jpeg';
|
|
78
|
+
}
|
|
79
|
+
if (data.length >= 3 && data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46) {
|
|
80
|
+
return 'image/gif';
|
|
81
|
+
}
|
|
82
|
+
if (
|
|
83
|
+
data.length >= 12 &&
|
|
84
|
+
data[0] === 0x52 && data[1] === 0x49 && data[2] === 0x46 && data[3] === 0x46 &&
|
|
85
|
+
data[8] === 0x57 && data[9] === 0x45 && data[10] === 0x42 && data[11] === 0x50
|
|
86
|
+
) {
|
|
87
|
+
return 'image/webp';
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
74
91
|
|
|
75
92
|
/**
|
|
76
93
|
* Tools whose presence suppresses auto-routing of the surrounding prose,
|
|
@@ -127,12 +144,6 @@ const FRAMEWORK_STATE_ID = 'framework/state';
|
|
|
127
144
|
const CONVERSATION_ROUTER_STATE_ID = 'framework/conversation-router';
|
|
128
145
|
const INFERENCE_LOG_ID = 'framework/inference-log';
|
|
129
146
|
const PROCESS_LOG_ID = 'framework/process-log';
|
|
130
|
-
const PRIMARY_SUMMARY_FALLBACK_STATE_ID = 'framework/primary-summary-fallback';
|
|
131
|
-
const PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY = 'primarySummarySettlement';
|
|
132
|
-
const PRIMARY_SUMMARY_SETTLEMENT_VERSION = 1;
|
|
133
|
-
const PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH = 1024;
|
|
134
|
-
const PRIMARY_SUMMARY_SETTLEMENT_MAX_NAME_LENGTH = 256;
|
|
135
|
-
const PRIMARY_SUMMARY_SETTLEMENT_MAX_REASON_LENGTH = 256;
|
|
136
147
|
const TURN_CHECKPOINTS_ID = 'framework/turn-checkpoints'; // legacy single-map layout, read-only fallback
|
|
137
148
|
const TURN_CHECKPOINTS_TREE_ID = 'framework/turn-checkpoints/tree';
|
|
138
149
|
|
|
@@ -187,128 +198,6 @@ interface InferenceToolSnapshot {
|
|
|
187
198
|
sameRoundThinkTextPolicy: SameRoundThinkTextPolicy;
|
|
188
199
|
}
|
|
189
200
|
|
|
190
|
-
type PrimarySummaryFallbackStatus =
|
|
191
|
-
| 'success'
|
|
192
|
-
| 'refusal'
|
|
193
|
-
| 'held'
|
|
194
|
-
| 'pending'
|
|
195
|
-
| 'error'
|
|
196
|
-
| 'timeout_unknown';
|
|
197
|
-
|
|
198
|
-
interface PrimarySummaryFallbackRetryDispatch {
|
|
199
|
-
originalRequestId: string;
|
|
200
|
-
baselineRequestId: string;
|
|
201
|
-
intentId: string;
|
|
202
|
-
branch: { id: string; generation: number };
|
|
203
|
-
candidateSummaries: PrimarySummaryIdentity[];
|
|
204
|
-
originalArtifacts: ActivationCompileArtifacts;
|
|
205
|
-
originalProjectionKeys: string[];
|
|
206
|
-
expectedRetryRequestHash: string;
|
|
207
|
-
requestBudgetTokens: number;
|
|
208
|
-
originalRequestInputBoundTokens: number;
|
|
209
|
-
originalProviderInputTokens?: number;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
interface PrimarySummaryFallbackIntentRecord {
|
|
213
|
-
intentId: string;
|
|
214
|
-
createdAt: number;
|
|
215
|
-
baselineRequestId: string;
|
|
216
|
-
candidateSummaries: PrimarySummaryIdentity[];
|
|
217
|
-
retryRequestHash: string;
|
|
218
|
-
retryCompleteBoundTokens: number;
|
|
219
|
-
requestBudgetTokens: number;
|
|
220
|
-
retryRequestId?: string;
|
|
221
|
-
dispatchedAt?: number;
|
|
222
|
-
quarantinePersistedAt?: number;
|
|
223
|
-
outputs?: PrimarySummaryFallbackOutputRecord[];
|
|
224
|
-
toolDispatches?: PrimarySummaryFallbackToolDispatchRecord[];
|
|
225
|
-
finalizedAt?: number;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
interface PrimarySummaryFallbackOutputRecord {
|
|
229
|
-
phase: string;
|
|
230
|
-
contentHash: string;
|
|
231
|
-
messageId: string;
|
|
232
|
-
blockTypes: string[];
|
|
233
|
-
persistedAt: number;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
type PrimarySummarySettlementOutcome = 'success' | 'held';
|
|
237
|
-
type PrimarySummarySettlementRole = 'assistant' | 'user';
|
|
238
|
-
type PrimarySummarySettlementKind = 'assistant_output' | 'generated_tool_result';
|
|
239
|
-
|
|
240
|
-
interface PrimarySummarySettlementMetadata {
|
|
241
|
-
version: number;
|
|
242
|
-
requestId: string;
|
|
243
|
-
settlementId: string;
|
|
244
|
-
agentName: string;
|
|
245
|
-
dispatchKind: PrimarySummaryRequestRecord['dispatchKind'];
|
|
246
|
-
outcome: PrimarySummarySettlementOutcome;
|
|
247
|
-
stopReason?: string;
|
|
248
|
-
holdReason?: string;
|
|
249
|
-
providerInputTokens?: number;
|
|
250
|
-
visibleAssistantOutput: boolean;
|
|
251
|
-
executedToolCalls: number;
|
|
252
|
-
branch: { id: string; name: string; generation: number };
|
|
253
|
-
role: PrimarySummarySettlementRole;
|
|
254
|
-
kind: PrimarySummarySettlementKind;
|
|
255
|
-
entryIndex: number;
|
|
256
|
-
entryCount: number;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
interface PrimarySummaryLogicalSettlementEntry {
|
|
260
|
-
messages: StoredMessage[];
|
|
261
|
-
settlement: PrimarySummarySettlementMetadata;
|
|
262
|
-
participant: string;
|
|
263
|
-
content: ContentBlock[];
|
|
264
|
-
bodyGroupId?: string;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
interface PrimarySummaryFallbackToolDispatchRecord {
|
|
268
|
-
callId: string;
|
|
269
|
-
inputHash: string;
|
|
270
|
-
recordedAt: number;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
interface PrimarySummaryRequestRecord {
|
|
274
|
-
requestId: string;
|
|
275
|
-
agentName: string;
|
|
276
|
-
namespace: string;
|
|
277
|
-
timestamp: number;
|
|
278
|
-
dispatchKind: 'primary' | 'primary_summary_fallback_retry';
|
|
279
|
-
branch: { id: string; name: string; generation: number };
|
|
280
|
-
projection: PrimarySummaryProjection | null;
|
|
281
|
-
requestInputBoundTokens: number;
|
|
282
|
-
requestCompleteBoundTokens: number;
|
|
283
|
-
providerInputTokens?: number;
|
|
284
|
-
systemHash: string;
|
|
285
|
-
modelConfigHash: string;
|
|
286
|
-
toolContractHash: string;
|
|
287
|
-
stopReason?: string;
|
|
288
|
-
visibleAssistantOutput: boolean;
|
|
289
|
-
executedToolCalls: number;
|
|
290
|
-
finalStatus: PrimarySummaryFallbackStatus;
|
|
291
|
-
fallbackIntent?: PrimarySummaryFallbackIntentRecord;
|
|
292
|
-
fallbackStatus?: PrimarySummaryFallbackStatus;
|
|
293
|
-
fallbackHeldReason?: string;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
interface PrimarySummaryFallbackState {
|
|
297
|
-
requests: PrimarySummaryRequestRecord[];
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
interface ContextBudgetRestartDispatch {
|
|
301
|
-
requestId: string;
|
|
302
|
-
dispatchKind: 'primary' | 'primary_summary_fallback_retry';
|
|
303
|
-
originalRequestId?: string;
|
|
304
|
-
intentId?: string;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
interface PendingInferenceRequest extends InferenceRequest {
|
|
308
|
-
primarySummaryFallbackRetry?: PrimarySummaryFallbackRetryDispatch;
|
|
309
|
-
contextBudgetRestart?: ContextBudgetRestartDispatch;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
201
|
/**
|
|
313
202
|
* Default inference policy - infer if any request exists for the agent.
|
|
314
203
|
*/
|
|
@@ -456,13 +345,6 @@ interface EphemeralRun {
|
|
|
456
345
|
toolCallsCount: number;
|
|
457
346
|
}
|
|
458
347
|
|
|
459
|
-
interface ActiveStreamContext {
|
|
460
|
-
requestId: string;
|
|
461
|
-
trigger?: PendingInferenceRequest;
|
|
462
|
-
compiledRequest?: NormalizedRequest;
|
|
463
|
-
artifacts?: ActivationCompileArtifacts;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
348
|
/** Cap an API error message for inline use in a rewind marker: keep enough to
|
|
467
349
|
* identify the failure class without pasting a wall of provider JSON into the
|
|
468
350
|
* agent's context. */
|
|
@@ -479,7 +361,7 @@ export class AgentFramework {
|
|
|
479
361
|
private moduleRegistry: ModuleRegistry;
|
|
480
362
|
private inferencePolicy: InferencePolicy;
|
|
481
363
|
private errorPolicy: ErrorPolicy;
|
|
482
|
-
private pendingRequests:
|
|
364
|
+
private pendingRequests: InferenceRequest[] = [];
|
|
483
365
|
/**
|
|
484
366
|
* Per-agent channel that triggered the agent's CURRENT inference turn, if any
|
|
485
367
|
* (item-3 redux). Read by the ChannelRegistry's `activeChannelResolver` to
|
|
@@ -508,8 +390,6 @@ export class AgentFramework {
|
|
|
508
390
|
private processLoggingPersist: boolean;
|
|
509
391
|
private processLoggingBroadcast: boolean;
|
|
510
392
|
private activeStreams: Map<string, Promise<void>> = new Map();
|
|
511
|
-
private activeStreamContexts: Map<string, ActiveStreamContext> = new Map();
|
|
512
|
-
private primarySummaryStateLock: Promise<void> = Promise.resolve();
|
|
513
393
|
|
|
514
394
|
/** Per-agent output locus pinned for the CURRENT logical turn (see
|
|
515
395
|
* resolveTurnLocus in driveStream). Lives here — not in driveStream
|
|
@@ -765,12 +645,6 @@ export class AgentFramework {
|
|
|
765
645
|
}
|
|
766
646
|
}
|
|
767
647
|
|
|
768
|
-
try {
|
|
769
|
-
store.registerState({ id: PRIMARY_SUMMARY_FALLBACK_STATE_ID, strategy: 'snapshot' });
|
|
770
|
-
} catch {
|
|
771
|
-
// Already registered
|
|
772
|
-
}
|
|
773
|
-
|
|
774
648
|
const discordAwarenessOutboxPath = config.discordAwarenessOutboxPath
|
|
775
649
|
?? (config.storePath ? defaultDiscordAwarenessOutboxPath(config.storePath) : undefined);
|
|
776
650
|
const discordAwarenessOutbox = discordAwarenessOutboxPath
|
|
@@ -815,15 +689,14 @@ export class AgentFramework {
|
|
|
815
689
|
}
|
|
816
690
|
}
|
|
817
691
|
|
|
692
|
+
// Restore persisted usage data (if any) from prior session
|
|
693
|
+
framework.restoreUsageState();
|
|
694
|
+
|
|
818
695
|
// Create agents
|
|
819
696
|
for (const agentConfig of config.agents) {
|
|
820
697
|
await framework.createAgent(agentConfig);
|
|
821
698
|
}
|
|
822
699
|
|
|
823
|
-
// Restore persisted usage data (if any) from prior session
|
|
824
|
-
framework.restoreUsageState();
|
|
825
|
-
await framework.reconcilePrimarySummaryFallbackStateOnStartup();
|
|
826
|
-
|
|
827
700
|
// Finish any branch-local suppression interrupted after Chronicle switched
|
|
828
701
|
// branches. This runs before modules, MCPL connections, or inbound traffic.
|
|
829
702
|
await framework.resumePreparedDiscordSuppressions();
|
|
@@ -1080,7 +953,7 @@ export class AgentFramework {
|
|
|
1080
953
|
const queued = [...this.agents.values()].flatMap((agent) => {
|
|
1081
954
|
const cm = agent.getContextManager();
|
|
1082
955
|
const tools = this.getToolsForAgent(agent.name).filter((tool) => agent.canUseTool(tool.name));
|
|
1083
|
-
cm.setToolDefinitions
|
|
956
|
+
cm.setToolDefinitions(tools);
|
|
1084
957
|
if (cm.isReady()) return [];
|
|
1085
958
|
const pending = cm.getPendingWork()?.description;
|
|
1086
959
|
const progress = this.contextProgress(cm);
|
|
@@ -1284,7 +1157,9 @@ export class AgentFramework {
|
|
|
1284
1157
|
...channelTools,
|
|
1285
1158
|
...gateTools,
|
|
1286
1159
|
this.buildAgentSettingsTool(),
|
|
1287
|
-
...(this.getWorkspaceModule()
|
|
1160
|
+
...(this.getWorkspaceModule()
|
|
1161
|
+
? [AgentFramework.SAVE_IMAGE_TOOL, AgentFramework.READ_IMAGE_TOOL]
|
|
1162
|
+
: []),
|
|
1288
1163
|
];
|
|
1289
1164
|
}
|
|
1290
1165
|
|
|
@@ -1741,1322 +1616,6 @@ export class AgentFramework {
|
|
|
1741
1616
|
return { promise, resolve, reject };
|
|
1742
1617
|
}
|
|
1743
1618
|
|
|
1744
|
-
private hashJson(value: unknown): string {
|
|
1745
|
-
return createHash('sha256').update(canonicalJsonStringify(value), 'utf8').digest('hex');
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
|
-
private estimateStringTokens(text: string, overhead = 0): number {
|
|
1749
|
-
return Math.ceil(Buffer.byteLength(text, 'utf8') / 4) + overhead;
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
private estimateUnknownTokens(value: unknown, overhead = 0): number {
|
|
1753
|
-
return this.estimateStringTokens(JSON.stringify(value) ?? 'null', overhead);
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
private estimateRequestInputBoundTokens(request: NormalizedRequest): number {
|
|
1757
|
-
let total = 96;
|
|
1758
|
-
total += this.estimateStringTokens(request.system ?? '', 48);
|
|
1759
|
-
for (const message of request.messages) {
|
|
1760
|
-
total += 24;
|
|
1761
|
-
total += this.estimateStringTokens(message.participant, 8);
|
|
1762
|
-
if ((message as { cacheBreakpoint?: boolean }).cacheBreakpoint) total += 8;
|
|
1763
|
-
for (const block of message.content) {
|
|
1764
|
-
switch (block.type) {
|
|
1765
|
-
case 'text':
|
|
1766
|
-
total += this.estimateStringTokens(block.text, 24);
|
|
1767
|
-
break;
|
|
1768
|
-
case 'thinking':
|
|
1769
|
-
total += this.estimateStringTokens(block.thinking, 32);
|
|
1770
|
-
total += block.signature ? this.estimateStringTokens(block.signature, 8) : 0;
|
|
1771
|
-
break;
|
|
1772
|
-
case 'redacted_thinking':
|
|
1773
|
-
total += this.estimateUnknownTokens(block, 32);
|
|
1774
|
-
break;
|
|
1775
|
-
default:
|
|
1776
|
-
total += this.estimateUnknownTokens(block, 32);
|
|
1777
|
-
break;
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
if (request.tools && request.tools.length > 0) {
|
|
1782
|
-
total += 32;
|
|
1783
|
-
for (const tool of request.tools) {
|
|
1784
|
-
total += this.estimateUnknownTokens({
|
|
1785
|
-
name: tool.name,
|
|
1786
|
-
description: tool.description,
|
|
1787
|
-
inputSchema: tool.inputSchema,
|
|
1788
|
-
}, 48);
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
total += this.estimateUnknownTokens(request.config, 48);
|
|
1792
|
-
if (request.providerParams !== undefined) {
|
|
1793
|
-
total += this.estimateUnknownTokens(request.providerParams, 32);
|
|
1794
|
-
}
|
|
1795
|
-
total += request.promptCaching ? 16 : 0;
|
|
1796
|
-
total += request.cacheTtl ? this.estimateStringTokens(request.cacheTtl, 8) : 0;
|
|
1797
|
-
total += request.assistantParticipant
|
|
1798
|
-
? this.estimateStringTokens(request.assistantParticipant, 8)
|
|
1799
|
-
: 0;
|
|
1800
|
-
return total;
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
private estimateRequestCompleteBoundTokens(request: NormalizedRequest): number {
|
|
1804
|
-
const reserve = typeof request.config.maxTokens === 'number' && Number.isFinite(request.config.maxTokens)
|
|
1805
|
-
? request.config.maxTokens
|
|
1806
|
-
: 0;
|
|
1807
|
-
return this.estimateRequestInputBoundTokens(request) + reserve + 96;
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
private primarySummaryAdmissionBoundTokens(
|
|
1811
|
-
originalRequestInputTokens: number,
|
|
1812
|
-
expandedRequest: NormalizedRequest,
|
|
1813
|
-
originalProviderInputTokens: number | undefined,
|
|
1814
|
-
): number {
|
|
1815
|
-
const expandedInput = this.estimateRequestInputBoundTokens(expandedRequest);
|
|
1816
|
-
const expandedComplete = this.estimateRequestCompleteBoundTokens(expandedRequest);
|
|
1817
|
-
if (originalProviderInputTokens === undefined) return expandedComplete;
|
|
1818
|
-
const reserve = typeof expandedRequest.config.maxTokens === 'number' && Number.isFinite(expandedRequest.config.maxTokens)
|
|
1819
|
-
? expandedRequest.config.maxTokens
|
|
1820
|
-
: 0;
|
|
1821
|
-
const positiveDelta = Math.max(0, expandedInput - originalRequestInputTokens);
|
|
1822
|
-
return Math.max(
|
|
1823
|
-
expandedComplete,
|
|
1824
|
-
originalProviderInputTokens + positiveDelta + reserve + 96,
|
|
1825
|
-
);
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
private readPrimarySummaryFallbackState(): PrimarySummaryFallbackState {
|
|
1829
|
-
const raw = (this.store as { getStateJson?: (id: string) => unknown } | undefined)
|
|
1830
|
-
?.getStateJson?.(PRIMARY_SUMMARY_FALLBACK_STATE_ID);
|
|
1831
|
-
if (!raw || typeof raw !== 'object') return { requests: [] };
|
|
1832
|
-
const requests = Array.isArray((raw as { requests?: unknown[] }).requests)
|
|
1833
|
-
? ((raw as { requests: PrimarySummaryRequestRecord[] }).requests)
|
|
1834
|
-
: [];
|
|
1835
|
-
return { requests };
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
private writePrimarySummaryFallbackState(state: PrimarySummaryFallbackState): void {
|
|
1839
|
-
this.store.setStateJson(PRIMARY_SUMMARY_FALLBACK_STATE_ID, state);
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
private async withPrimarySummaryStateLock<T>(work: () => Promise<T> | T): Promise<T> {
|
|
1843
|
-
const previous = this.primarySummaryStateLock;
|
|
1844
|
-
let release!: () => void;
|
|
1845
|
-
this.primarySummaryStateLock = new Promise<void>((resolve) => { release = resolve; });
|
|
1846
|
-
await previous;
|
|
1847
|
-
try {
|
|
1848
|
-
return await work();
|
|
1849
|
-
} finally {
|
|
1850
|
-
release();
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
private async reconcilePrimarySummaryFallbackStateOnStartup(): Promise<void> {
|
|
1855
|
-
const alerts: Array<{ agentName: string; requestId: string; reason: string }> = [];
|
|
1856
|
-
const unsettledRequests = (await this.withPrimarySummaryStateLock(() =>
|
|
1857
|
-
this.readPrimarySummaryFallbackState().requests
|
|
1858
|
-
.filter((record) =>
|
|
1859
|
-
record.finalStatus === 'pending'
|
|
1860
|
-
|| record.finalStatus === 'refusal'
|
|
1861
|
-
|| record.fallbackStatus === 'pending',
|
|
1862
|
-
)
|
|
1863
|
-
.map((record) => structuredClone(record)),
|
|
1864
|
-
)) as PrimarySummaryRequestRecord[];
|
|
1865
|
-
|
|
1866
|
-
const resolvedRequestIds = new Set<string>();
|
|
1867
|
-
for (const record of unsettledRequests) {
|
|
1868
|
-
const result = await this.finalizePrimarySummarySettlementOnStartup(record);
|
|
1869
|
-
if (result === 'finalized') {
|
|
1870
|
-
resolvedRequestIds.add(record.requestId);
|
|
1871
|
-
continue;
|
|
1872
|
-
}
|
|
1873
|
-
if (result && typeof result === 'object' && 'invalidReason' in result) {
|
|
1874
|
-
resolvedRequestIds.add(record.requestId);
|
|
1875
|
-
alerts.push({ agentName: record.agentName, requestId: record.requestId, reason: result.invalidReason });
|
|
1876
|
-
await this.markPrimarySummaryRequestRestartFailure(record, result.invalidReason);
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
|
-
await this.withPrimarySummaryStateLock(() => {
|
|
1881
|
-
const state = this.readPrimarySummaryFallbackState();
|
|
1882
|
-
let changed = false;
|
|
1883
|
-
state.requests = state.requests.map((record) => {
|
|
1884
|
-
if (resolvedRequestIds.has(record.requestId)) return record;
|
|
1885
|
-
if (record.dispatchKind === 'primary_summary_fallback_retry' && (
|
|
1886
|
-
record.finalStatus === 'pending' || record.finalStatus === 'refusal'
|
|
1887
|
-
)) {
|
|
1888
|
-
changed = true;
|
|
1889
|
-
const reason = 'primary_summary_fallback_unresolved_on_restart';
|
|
1890
|
-
alerts.push({ agentName: record.agentName, requestId: record.requestId, reason });
|
|
1891
|
-
return {
|
|
1892
|
-
...record,
|
|
1893
|
-
finalStatus: 'held',
|
|
1894
|
-
fallbackHeldReason: reason,
|
|
1895
|
-
};
|
|
1896
|
-
}
|
|
1897
|
-
if (record.fallbackIntent && record.fallbackStatus === 'pending') {
|
|
1898
|
-
changed = true;
|
|
1899
|
-
const reason = 'primary_summary_fallback_unresolved_on_restart';
|
|
1900
|
-
alerts.push({ agentName: record.agentName, requestId: record.requestId, reason });
|
|
1901
|
-
return {
|
|
1902
|
-
...record,
|
|
1903
|
-
fallbackStatus: 'held',
|
|
1904
|
-
fallbackHeldReason: reason,
|
|
1905
|
-
};
|
|
1906
|
-
}
|
|
1907
|
-
if (record.finalStatus === 'refusal') {
|
|
1908
|
-
changed = true;
|
|
1909
|
-
const reason = 'primary_summary_request_unresolved_on_restart';
|
|
1910
|
-
alerts.push({ agentName: record.agentName, requestId: record.requestId, reason });
|
|
1911
|
-
return {
|
|
1912
|
-
...record,
|
|
1913
|
-
finalStatus: 'held',
|
|
1914
|
-
fallbackStatus: 'held',
|
|
1915
|
-
fallbackHeldReason: reason,
|
|
1916
|
-
};
|
|
1917
|
-
}
|
|
1918
|
-
if (record.finalStatus === 'pending') {
|
|
1919
|
-
changed = true;
|
|
1920
|
-
const reason = 'primary_summary_request_unresolved_on_restart';
|
|
1921
|
-
alerts.push({ agentName: record.agentName, requestId: record.requestId, reason });
|
|
1922
|
-
return {
|
|
1923
|
-
...record,
|
|
1924
|
-
finalStatus: 'timeout_unknown',
|
|
1925
|
-
fallbackHeldReason: reason,
|
|
1926
|
-
};
|
|
1927
|
-
}
|
|
1928
|
-
return record;
|
|
1929
|
-
});
|
|
1930
|
-
if (changed) this.writePrimarySummaryFallbackState({ requests: state.requests.slice(-256) });
|
|
1931
|
-
});
|
|
1932
|
-
for (const alert of alerts) {
|
|
1933
|
-
this.opsAlert('refusal-held', alert.agentName, alert.reason, {
|
|
1934
|
-
data: { requestId: alert.requestId },
|
|
1935
|
-
});
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
private primarySummaryIdentityKey(identity: PrimarySummaryIdentity): string {
|
|
1940
|
-
return [
|
|
1941
|
-
identity.id,
|
|
1942
|
-
identity.contentHash,
|
|
1943
|
-
identity.carrierHash,
|
|
1944
|
-
identity.sourceLeafHash,
|
|
1945
|
-
].join('\u0000');
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
private primarySummaryProjectionKeys(projection: PrimarySummaryProjection | null): string[] {
|
|
1949
|
-
return projection?.selectedSummaries.map((selection) =>
|
|
1950
|
-
this.primarySummaryIdentityKey(selection.identity),
|
|
1951
|
-
) ?? [];
|
|
1952
|
-
}
|
|
1953
|
-
|
|
1954
|
-
private primarySummaryFallbackConfig(agent: Agent): {
|
|
1955
|
-
enabled: boolean;
|
|
1956
|
-
maxNewSummaries: number;
|
|
1957
|
-
requestBudgetTokens: number;
|
|
1958
|
-
} {
|
|
1959
|
-
const raw = (agent.refusalHandling as {
|
|
1960
|
-
primarySummaryFallback?: {
|
|
1961
|
-
enabled?: boolean;
|
|
1962
|
-
maxNewSummaries?: number;
|
|
1963
|
-
requestBudgetTokens?: number;
|
|
1964
|
-
};
|
|
1965
|
-
} | undefined)?.primarySummaryFallback;
|
|
1966
|
-
const fallbackBudgetBase = agent.contextBudgetTokens ?? 100_000;
|
|
1967
|
-
return {
|
|
1968
|
-
enabled: raw?.enabled === true,
|
|
1969
|
-
maxNewSummaries:
|
|
1970
|
-
typeof raw?.maxNewSummaries === 'number' && Number.isSafeInteger(raw.maxNewSummaries)
|
|
1971
|
-
? Math.max(0, raw.maxNewSummaries)
|
|
1972
|
-
: 4,
|
|
1973
|
-
requestBudgetTokens:
|
|
1974
|
-
typeof raw?.requestBudgetTokens === 'number' && Number.isFinite(raw.requestBudgetTokens) && raw.requestBudgetTokens > 0
|
|
1975
|
-
? raw.requestBudgetTokens
|
|
1976
|
-
: fallbackBudgetBase + agent.maxTokens,
|
|
1977
|
-
};
|
|
1978
|
-
}
|
|
1979
|
-
|
|
1980
|
-
private latestCompatibleHealthyPrimaryRequest(
|
|
1981
|
-
current: PrimarySummaryRequestRecord,
|
|
1982
|
-
state: PrimarySummaryFallbackState,
|
|
1983
|
-
): PrimarySummaryRequestRecord | null {
|
|
1984
|
-
for (let i = state.requests.length - 1; i >= 0; i--) {
|
|
1985
|
-
const candidate = state.requests[i]!;
|
|
1986
|
-
if (candidate.requestId === current.requestId) continue;
|
|
1987
|
-
if (candidate.agentName !== current.agentName) continue;
|
|
1988
|
-
if (candidate.namespace !== current.namespace) continue;
|
|
1989
|
-
if (candidate.branch.id !== current.branch.id) continue;
|
|
1990
|
-
if (candidate.branch.generation !== current.branch.generation) continue;
|
|
1991
|
-
if (candidate.systemHash !== current.systemHash) continue;
|
|
1992
|
-
if (candidate.modelConfigHash !== current.modelConfigHash) continue;
|
|
1993
|
-
if (candidate.toolContractHash !== current.toolContractHash) continue;
|
|
1994
|
-
if (candidate.dispatchKind !== 'primary') continue;
|
|
1995
|
-
if (candidate.fallbackIntent || candidate.fallbackStatus) continue;
|
|
1996
|
-
if (candidate.finalStatus !== 'success') continue;
|
|
1997
|
-
return candidate;
|
|
1998
|
-
}
|
|
1999
|
-
return null;
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
private async persistPrimarySummaryRequestRecord(record: PrimarySummaryRequestRecord): Promise<void> {
|
|
2003
|
-
await this.withPrimarySummaryStateLock(() => {
|
|
2004
|
-
const state = this.readPrimarySummaryFallbackState();
|
|
2005
|
-
const requests = state.requests.filter((entry) => entry.requestId !== record.requestId);
|
|
2006
|
-
requests.push(record);
|
|
2007
|
-
this.writePrimarySummaryFallbackState({ requests: requests.slice(-256) });
|
|
2008
|
-
});
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
private async updatePrimarySummaryRequestRecord(
|
|
2012
|
-
requestId: string,
|
|
2013
|
-
update: (record: PrimarySummaryRequestRecord) => PrimarySummaryRequestRecord,
|
|
2014
|
-
): Promise<PrimarySummaryRequestRecord | null> {
|
|
2015
|
-
return this.withPrimarySummaryStateLock(() => {
|
|
2016
|
-
const state = this.readPrimarySummaryFallbackState();
|
|
2017
|
-
const index = state.requests.findIndex((entry) => entry.requestId === requestId);
|
|
2018
|
-
if (index < 0) return null;
|
|
2019
|
-
state.requests[index] = update(state.requests[index]!);
|
|
2020
|
-
this.writePrimarySummaryFallbackState({ requests: state.requests.slice(-256) });
|
|
2021
|
-
return state.requests[index]!;
|
|
2022
|
-
});
|
|
2023
|
-
}
|
|
2024
|
-
|
|
2025
|
-
private async getPrimarySummaryRequestRecord(requestId: string): Promise<PrimarySummaryRequestRecord | null> {
|
|
2026
|
-
return this.withPrimarySummaryStateLock(() => {
|
|
2027
|
-
const state = this.readPrimarySummaryFallbackState();
|
|
2028
|
-
return state.requests.find((entry) => entry.requestId === requestId) ?? null;
|
|
2029
|
-
});
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
private primarySummaryContractsMatch(
|
|
2033
|
-
left: PrimarySummaryContract & { systemHash: string },
|
|
2034
|
-
right: PrimarySummaryContract & { systemHash: string },
|
|
2035
|
-
): boolean {
|
|
2036
|
-
return left.systemHash === right.systemHash
|
|
2037
|
-
&& left.modelConfigHash === right.modelConfigHash
|
|
2038
|
-
&& left.toolContractHash === right.toolContractHash;
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
private primarySummaryFallbackOutputPhaseHash(blocks: ReadonlyArray<ContentBlock>): string {
|
|
2042
|
-
return this.hashJson(blocks);
|
|
2043
|
-
}
|
|
2044
|
-
|
|
2045
|
-
private primarySummaryFallbackBlockTypes(blocks: ReadonlyArray<ContentBlock>): string[] {
|
|
2046
|
-
return blocks.map((block) => block.type);
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
private static readonly NON_EXECUTED_TOOL_RESULT_TEXT = '[tool not executed]';
|
|
2050
|
-
|
|
2051
|
-
private buildNonExecutedToolResults(
|
|
2052
|
-
blocks: ReadonlyArray<ContentBlock>,
|
|
2053
|
-
): ContentBlock[] {
|
|
2054
|
-
const seenToolUseIds = new Set<string>();
|
|
2055
|
-
return blocks
|
|
2056
|
-
.filter((block): block is ContentBlock & { type: 'tool_use'; id: string } => block.type === 'tool_use')
|
|
2057
|
-
.filter((block) => {
|
|
2058
|
-
if (seenToolUseIds.has(block.id)) return false;
|
|
2059
|
-
seenToolUseIds.add(block.id);
|
|
2060
|
-
return true;
|
|
2061
|
-
})
|
|
2062
|
-
.map((block) => ({
|
|
2063
|
-
type: 'tool_result' as const,
|
|
2064
|
-
toolUseId: block.id,
|
|
2065
|
-
content: AgentFramework.NON_EXECUTED_TOOL_RESULT_TEXT,
|
|
2066
|
-
isError: true,
|
|
2067
|
-
}));
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
private buildPrimarySummarySettlementId(
|
|
2071
|
-
record: Pick<PrimarySummaryRequestRecord, 'dispatchKind'>,
|
|
2072
|
-
outcome: PrimarySummarySettlementOutcome,
|
|
2073
|
-
): string {
|
|
2074
|
-
return `${record.dispatchKind}:${outcome}:terminal:v${PRIMARY_SUMMARY_SETTLEMENT_VERSION}`;
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
private buildPrimarySummarySettlementMetadata(
|
|
2078
|
-
record: PrimarySummaryRequestRecord,
|
|
2079
|
-
options: {
|
|
2080
|
-
outcome: PrimarySummarySettlementOutcome;
|
|
2081
|
-
settlementId?: string;
|
|
2082
|
-
stopReason?: string;
|
|
2083
|
-
holdReason?: string;
|
|
2084
|
-
providerInputTokens?: number;
|
|
2085
|
-
visibleAssistantOutput: boolean;
|
|
2086
|
-
executedToolCalls: number;
|
|
2087
|
-
entryIndex: number;
|
|
2088
|
-
entryCount: number;
|
|
2089
|
-
role: PrimarySummarySettlementRole;
|
|
2090
|
-
kind: PrimarySummarySettlementKind;
|
|
2091
|
-
},
|
|
2092
|
-
): MessageMetadata {
|
|
2093
|
-
const settlementId = options.settlementId ?? this.buildPrimarySummarySettlementId(record, options.outcome);
|
|
2094
|
-
return {
|
|
2095
|
-
[PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY]: {
|
|
2096
|
-
version: PRIMARY_SUMMARY_SETTLEMENT_VERSION,
|
|
2097
|
-
requestId: record.requestId,
|
|
2098
|
-
settlementId,
|
|
2099
|
-
agentName: record.agentName,
|
|
2100
|
-
dispatchKind: record.dispatchKind,
|
|
2101
|
-
outcome: options.outcome,
|
|
2102
|
-
...(options.stopReason ? { stopReason: options.stopReason } : {}),
|
|
2103
|
-
...(options.holdReason ? { holdReason: options.holdReason } : {}),
|
|
2104
|
-
...(options.providerInputTokens !== undefined
|
|
2105
|
-
? { providerInputTokens: options.providerInputTokens }
|
|
2106
|
-
: {}),
|
|
2107
|
-
visibleAssistantOutput: options.visibleAssistantOutput,
|
|
2108
|
-
executedToolCalls: options.executedToolCalls,
|
|
2109
|
-
branch: {
|
|
2110
|
-
id: record.branch.id,
|
|
2111
|
-
name: record.branch.name,
|
|
2112
|
-
generation: record.branch.generation,
|
|
2113
|
-
},
|
|
2114
|
-
role: options.role,
|
|
2115
|
-
kind: options.kind,
|
|
2116
|
-
entryIndex: options.entryIndex,
|
|
2117
|
-
entryCount: options.entryCount,
|
|
2118
|
-
} satisfies PrimarySummarySettlementMetadata,
|
|
2119
|
-
};
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
private isBoundedNonEmptyString(value: unknown, maxLength: number): value is string {
|
|
2123
|
-
return typeof value === 'string' && value.length > 0 && value.length <= maxLength;
|
|
2124
|
-
}
|
|
2125
|
-
|
|
2126
|
-
private isSafeNonNegativeInteger(value: unknown): value is number {
|
|
2127
|
-
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
private primarySummarySettlementRequestId(message: StoredMessage): string | null {
|
|
2131
|
-
const raw = message.metadata?.[PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY];
|
|
2132
|
-
if (!raw || typeof raw !== 'object') return null;
|
|
2133
|
-
const requestId = (raw as { requestId?: unknown }).requestId;
|
|
2134
|
-
return typeof requestId === 'string' ? requestId : null;
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
|
-
private parsePrimarySummarySettlementMetadata(
|
|
2138
|
-
message: StoredMessage,
|
|
2139
|
-
): PrimarySummarySettlementMetadata | { invalidReason: string } | null {
|
|
2140
|
-
const raw = message.metadata?.[PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY];
|
|
2141
|
-
if (!raw || typeof raw !== 'object') return null;
|
|
2142
|
-
const candidate = raw as Partial<PrimarySummarySettlementMetadata>;
|
|
2143
|
-
if (
|
|
2144
|
-
candidate.version !== PRIMARY_SUMMARY_SETTLEMENT_VERSION ||
|
|
2145
|
-
!this.isBoundedNonEmptyString(candidate.requestId, PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH) ||
|
|
2146
|
-
!this.isBoundedNonEmptyString(candidate.settlementId, PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH) ||
|
|
2147
|
-
!this.isBoundedNonEmptyString(candidate.agentName, PRIMARY_SUMMARY_SETTLEMENT_MAX_NAME_LENGTH) ||
|
|
2148
|
-
(candidate.dispatchKind !== 'primary' && candidate.dispatchKind !== 'primary_summary_fallback_retry') ||
|
|
2149
|
-
(candidate.outcome !== 'success' && candidate.outcome !== 'held') ||
|
|
2150
|
-
typeof candidate.visibleAssistantOutput !== 'boolean' ||
|
|
2151
|
-
!this.isSafeNonNegativeInteger(candidate.executedToolCalls) ||
|
|
2152
|
-
!this.isSafeNonNegativeInteger(candidate.entryIndex) ||
|
|
2153
|
-
!this.isSafeNonNegativeInteger(candidate.entryCount) ||
|
|
2154
|
-
(candidate.role !== 'assistant' && candidate.role !== 'user') ||
|
|
2155
|
-
(candidate.kind !== 'assistant_output' && candidate.kind !== 'generated_tool_result') ||
|
|
2156
|
-
!candidate.branch ||
|
|
2157
|
-
!this.isBoundedNonEmptyString(candidate.branch.id, PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH) ||
|
|
2158
|
-
!this.isBoundedNonEmptyString(candidate.branch.name, PRIMARY_SUMMARY_SETTLEMENT_MAX_NAME_LENGTH) ||
|
|
2159
|
-
!this.isSafeNonNegativeInteger(candidate.branch.generation) ||
|
|
2160
|
-
candidate.entryCount < 1 ||
|
|
2161
|
-
candidate.entryIndex >= candidate.entryCount ||
|
|
2162
|
-
(candidate.stopReason !== undefined
|
|
2163
|
-
&& !this.isBoundedNonEmptyString(candidate.stopReason, PRIMARY_SUMMARY_SETTLEMENT_MAX_REASON_LENGTH)) ||
|
|
2164
|
-
(candidate.holdReason !== undefined
|
|
2165
|
-
&& !this.isBoundedNonEmptyString(candidate.holdReason, PRIMARY_SUMMARY_SETTLEMENT_MAX_REASON_LENGTH)) ||
|
|
2166
|
-
(candidate.providerInputTokens !== undefined
|
|
2167
|
-
&& !this.isSafeNonNegativeInteger(candidate.providerInputTokens)) ||
|
|
2168
|
-
(candidate.outcome === 'held' && candidate.holdReason === undefined) ||
|
|
2169
|
-
(candidate.outcome === 'success' && candidate.holdReason !== undefined)
|
|
2170
|
-
) {
|
|
2171
|
-
return { invalidReason: 'primary_summary_settlement_metadata_invalid_on_restart' };
|
|
2172
|
-
}
|
|
2173
|
-
return candidate as PrimarySummarySettlementMetadata;
|
|
2174
|
-
}
|
|
2175
|
-
|
|
2176
|
-
private primarySummarySettlementMetadataConsistent(
|
|
2177
|
-
left: PrimarySummarySettlementMetadata,
|
|
2178
|
-
right: PrimarySummarySettlementMetadata,
|
|
2179
|
-
): boolean {
|
|
2180
|
-
return left.version === right.version
|
|
2181
|
-
&& left.requestId === right.requestId
|
|
2182
|
-
&& left.settlementId === right.settlementId
|
|
2183
|
-
&& left.agentName === right.agentName
|
|
2184
|
-
&& left.dispatchKind === right.dispatchKind
|
|
2185
|
-
&& left.outcome === right.outcome
|
|
2186
|
-
&& left.stopReason === right.stopReason
|
|
2187
|
-
&& left.holdReason === right.holdReason
|
|
2188
|
-
&& left.providerInputTokens === right.providerInputTokens
|
|
2189
|
-
&& left.visibleAssistantOutput === right.visibleAssistantOutput
|
|
2190
|
-
&& left.executedToolCalls === right.executedToolCalls
|
|
2191
|
-
&& left.branch.id === right.branch.id
|
|
2192
|
-
&& left.branch.name === right.branch.name
|
|
2193
|
-
&& left.branch.generation === right.branch.generation
|
|
2194
|
-
&& left.entryCount === right.entryCount;
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
private primarySummaryShardEnvelopeConsistent(
|
|
2198
|
-
participant: string,
|
|
2199
|
-
left: PrimarySummarySettlementMetadata,
|
|
2200
|
-
candidateParticipant: string,
|
|
2201
|
-
right: PrimarySummarySettlementMetadata,
|
|
2202
|
-
): boolean {
|
|
2203
|
-
return participant === candidateParticipant
|
|
2204
|
-
&& this.primarySummarySettlementMetadataConsistent(left, right)
|
|
2205
|
-
&& left.role === right.role
|
|
2206
|
-
&& left.kind === right.kind
|
|
2207
|
-
&& left.entryIndex === right.entryIndex;
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
private primarySummaryLogicalSettlementEntries(
|
|
2211
|
-
agent: Agent,
|
|
2212
|
-
requestId: string,
|
|
2213
|
-
): PrimarySummaryLogicalSettlementEntry[] | { invalidReason: string } {
|
|
2214
|
-
const allMessages = agent.getContextManager().getAllMessages()
|
|
2215
|
-
.slice()
|
|
2216
|
-
.sort((left, right) => left.sequence - right.sequence);
|
|
2217
|
-
const entries: PrimarySummaryLogicalSettlementEntry[] = [];
|
|
2218
|
-
for (let index = 0; index < allMessages.length;) {
|
|
2219
|
-
const message = allMessages[index]!;
|
|
2220
|
-
if (this.primarySummarySettlementRequestId(message) !== requestId) {
|
|
2221
|
-
index++;
|
|
2222
|
-
continue;
|
|
2223
|
-
}
|
|
2224
|
-
const parsed = this.parsePrimarySummarySettlementMetadata(message);
|
|
2225
|
-
if (!parsed) {
|
|
2226
|
-
index++;
|
|
2227
|
-
continue;
|
|
2228
|
-
}
|
|
2229
|
-
if ('invalidReason' in parsed) return parsed;
|
|
2230
|
-
|
|
2231
|
-
if (message.bodyGroupId === undefined) {
|
|
2232
|
-
if (message.shardIndex !== undefined) {
|
|
2233
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
2234
|
-
}
|
|
2235
|
-
entries.push({
|
|
2236
|
-
messages: [message],
|
|
2237
|
-
settlement: parsed,
|
|
2238
|
-
participant: message.participant,
|
|
2239
|
-
content: message.content,
|
|
2240
|
-
});
|
|
2241
|
-
index++;
|
|
2242
|
-
continue;
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
|
-
const groupId = message.bodyGroupId;
|
|
2246
|
-
if (!this.isBoundedNonEmptyString(groupId, PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH)) {
|
|
2247
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
2248
|
-
}
|
|
2249
|
-
if (index > 0 && allMessages[index - 1]?.bodyGroupId === groupId) {
|
|
2250
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
const groupMessages: StoredMessage[] = [];
|
|
2254
|
-
while (index < allMessages.length && allMessages[index]!.bodyGroupId === groupId) {
|
|
2255
|
-
groupMessages.push(allMessages[index]!);
|
|
2256
|
-
index++;
|
|
2257
|
-
}
|
|
2258
|
-
if (allMessages.slice(index).some((candidate) => candidate.bodyGroupId === groupId)) {
|
|
2259
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
const orderedGroupMessages: StoredMessage[] = [];
|
|
2263
|
-
let expectedShardIndex = 0;
|
|
2264
|
-
for (const groupMessage of groupMessages) {
|
|
2265
|
-
if (this.primarySummarySettlementRequestId(groupMessage) !== requestId) {
|
|
2266
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
2267
|
-
}
|
|
2268
|
-
const groupParsed = this.parsePrimarySummarySettlementMetadata(groupMessage);
|
|
2269
|
-
if (!groupParsed) {
|
|
2270
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
2271
|
-
}
|
|
2272
|
-
if ('invalidReason' in groupParsed) return groupParsed;
|
|
2273
|
-
if (!this.primarySummaryShardEnvelopeConsistent(
|
|
2274
|
-
message.participant,
|
|
2275
|
-
parsed,
|
|
2276
|
-
groupMessage.participant,
|
|
2277
|
-
groupParsed,
|
|
2278
|
-
)) {
|
|
2279
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
2280
|
-
}
|
|
2281
|
-
if (groupMessage.shardIndex !== expectedShardIndex) {
|
|
2282
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
2283
|
-
}
|
|
2284
|
-
orderedGroupMessages.push(groupMessage);
|
|
2285
|
-
expectedShardIndex++;
|
|
2286
|
-
}
|
|
2287
|
-
|
|
2288
|
-
entries.push({
|
|
2289
|
-
messages: orderedGroupMessages,
|
|
2290
|
-
settlement: parsed,
|
|
2291
|
-
participant: message.participant,
|
|
2292
|
-
content: orderedGroupMessages.flatMap((groupMessage) => groupMessage.content),
|
|
2293
|
-
bodyGroupId: groupId,
|
|
2294
|
-
});
|
|
2295
|
-
}
|
|
2296
|
-
return entries;
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
private primarySummaryContentMatches(
|
|
2300
|
-
left: ReadonlyArray<ContentBlock>,
|
|
2301
|
-
right: ReadonlyArray<ContentBlock>,
|
|
2302
|
-
): boolean {
|
|
2303
|
-
return this.primarySummaryFallbackOutputPhaseHash(left) === this.primarySummaryFallbackOutputPhaseHash(right);
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
private async persistPrimarySummaryChronicleEntries(
|
|
2307
|
-
agent: Agent,
|
|
2308
|
-
entries: Array<{ participant: string; content: ContentBlock[]; metadata?: MessageMetadata }>,
|
|
2309
|
-
): Promise<string[]> {
|
|
2310
|
-
const messageIds: string[] = [];
|
|
2311
|
-
for (const entry of entries) {
|
|
2312
|
-
if (entry.content.length === 0) continue;
|
|
2313
|
-
messageIds.push(agent.getContextManager().addMessage(entry.participant, entry.content, entry.metadata));
|
|
2314
|
-
}
|
|
2315
|
-
return messageIds;
|
|
2316
|
-
}
|
|
2317
|
-
|
|
2318
|
-
private async markPrimarySummaryRequestRestartFailure(
|
|
2319
|
-
record: PrimarySummaryRequestRecord,
|
|
2320
|
-
reason: string,
|
|
2321
|
-
): Promise<void> {
|
|
2322
|
-
if (record.dispatchKind === 'primary_summary_fallback_retry') {
|
|
2323
|
-
const parent = await this.findPrimarySummaryRetryParent(record.requestId);
|
|
2324
|
-
await this.updatePrimarySummaryRequestRecord(record.requestId, (entry) => ({
|
|
2325
|
-
...entry,
|
|
2326
|
-
finalStatus: 'held',
|
|
2327
|
-
fallbackHeldReason: reason,
|
|
2328
|
-
}));
|
|
2329
|
-
if (parent) {
|
|
2330
|
-
await this.updatePrimarySummaryRequestRecord(parent.originalRequestId, (entry) => ({
|
|
2331
|
-
...entry,
|
|
2332
|
-
finalStatus: 'held',
|
|
2333
|
-
fallbackStatus: 'held',
|
|
2334
|
-
fallbackHeldReason: reason,
|
|
2335
|
-
}));
|
|
2336
|
-
}
|
|
2337
|
-
return;
|
|
2338
|
-
}
|
|
2339
|
-
await this.updatePrimarySummaryRequestRecord(record.requestId, (current) => ({
|
|
2340
|
-
...current,
|
|
2341
|
-
finalStatus: 'held',
|
|
2342
|
-
fallbackStatus: 'held',
|
|
2343
|
-
fallbackHeldReason: reason,
|
|
2344
|
-
}));
|
|
2345
|
-
}
|
|
2346
|
-
|
|
2347
|
-
private async persistPrimarySummaryHeldOutput(
|
|
2348
|
-
agent: Agent,
|
|
2349
|
-
requestId: string,
|
|
2350
|
-
holdReason: string,
|
|
2351
|
-
options: {
|
|
2352
|
-
stopReason?: string;
|
|
2353
|
-
providerInputTokens?: number;
|
|
2354
|
-
visibleAssistantOutput: boolean;
|
|
2355
|
-
executedToolCalls: number;
|
|
2356
|
-
assistantBlocks: ContentBlock[];
|
|
2357
|
-
retryDispatch?: PrimarySummaryFallbackRetryDispatch;
|
|
2358
|
-
retryPhase?: string;
|
|
2359
|
-
holdData?: Record<string, unknown>;
|
|
2360
|
-
},
|
|
2361
|
-
): Promise<void> {
|
|
2362
|
-
const record = await this.getPrimarySummaryRequestRecord(requestId);
|
|
2363
|
-
if (!record) {
|
|
2364
|
-
throw new Error(`Primary summary request '${requestId}' disappeared before held-output persistence`);
|
|
2365
|
-
}
|
|
2366
|
-
const placeholderBlocks = this.buildNonExecutedToolResults(options.assistantBlocks);
|
|
2367
|
-
const entryCount = placeholderBlocks.length > 0 ? 2 : 1;
|
|
2368
|
-
const assistantMetadata = this.buildPrimarySummarySettlementMetadata(record, {
|
|
2369
|
-
outcome: 'held',
|
|
2370
|
-
stopReason: options.stopReason,
|
|
2371
|
-
holdReason,
|
|
2372
|
-
providerInputTokens: options.providerInputTokens,
|
|
2373
|
-
visibleAssistantOutput: options.visibleAssistantOutput,
|
|
2374
|
-
executedToolCalls: options.executedToolCalls,
|
|
2375
|
-
entryIndex: 0,
|
|
2376
|
-
entryCount,
|
|
2377
|
-
role: 'assistant',
|
|
2378
|
-
kind: 'assistant_output',
|
|
2379
|
-
});
|
|
2380
|
-
const settlementId =
|
|
2381
|
-
(assistantMetadata[PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY] as PrimarySummarySettlementMetadata).settlementId;
|
|
2382
|
-
const placeholderMetadata = placeholderBlocks.length > 0
|
|
2383
|
-
? this.buildPrimarySummarySettlementMetadata(record, {
|
|
2384
|
-
outcome: 'held',
|
|
2385
|
-
settlementId,
|
|
2386
|
-
stopReason: options.stopReason,
|
|
2387
|
-
holdReason,
|
|
2388
|
-
providerInputTokens: options.providerInputTokens,
|
|
2389
|
-
visibleAssistantOutput: options.visibleAssistantOutput,
|
|
2390
|
-
executedToolCalls: options.executedToolCalls,
|
|
2391
|
-
entryIndex: 1,
|
|
2392
|
-
entryCount,
|
|
2393
|
-
role: 'user',
|
|
2394
|
-
kind: 'generated_tool_result',
|
|
2395
|
-
})
|
|
2396
|
-
: undefined;
|
|
2397
|
-
const messageIds = await this.persistPrimarySummaryChronicleEntries(agent, [
|
|
2398
|
-
{ participant: agent.name, content: options.assistantBlocks, metadata: assistantMetadata },
|
|
2399
|
-
...(placeholderBlocks.length > 0 && placeholderMetadata
|
|
2400
|
-
? [{ participant: 'user', content: placeholderBlocks, metadata: placeholderMetadata }]
|
|
2401
|
-
: []),
|
|
2402
|
-
]);
|
|
2403
|
-
if (options.retryDispatch && options.retryPhase && options.assistantBlocks.length > 0) {
|
|
2404
|
-
await this.recordPrimarySummaryRetryOutput(
|
|
2405
|
-
options.retryDispatch.originalRequestId,
|
|
2406
|
-
options.retryDispatch.intentId,
|
|
2407
|
-
{
|
|
2408
|
-
phase: options.retryPhase,
|
|
2409
|
-
contentHash: this.primarySummaryFallbackOutputPhaseHash(options.assistantBlocks),
|
|
2410
|
-
messageId: messageIds[0] ?? '',
|
|
2411
|
-
blockTypes: this.primarySummaryFallbackBlockTypes(options.assistantBlocks),
|
|
2412
|
-
persistedAt: Date.now(),
|
|
2413
|
-
},
|
|
2414
|
-
);
|
|
2415
|
-
}
|
|
2416
|
-
if (options.retryDispatch) {
|
|
2417
|
-
await this.holdPrimarySummaryRetryFamily(
|
|
2418
|
-
agent,
|
|
2419
|
-
requestId,
|
|
2420
|
-
options.retryDispatch,
|
|
2421
|
-
holdReason,
|
|
2422
|
-
options.holdData,
|
|
2423
|
-
);
|
|
2424
|
-
return;
|
|
2425
|
-
}
|
|
2426
|
-
await this.holdPrimarySummaryRequest(agent, requestId, holdReason, options.holdData);
|
|
2427
|
-
}
|
|
2428
|
-
|
|
2429
|
-
private async findPrimarySummaryRetryParent(
|
|
2430
|
-
retryRequestId: string,
|
|
2431
|
-
): Promise<{ originalRequestId: string; intentId: string } | null> {
|
|
2432
|
-
return this.withPrimarySummaryStateLock(() => {
|
|
2433
|
-
const state = this.readPrimarySummaryFallbackState();
|
|
2434
|
-
for (const record of state.requests) {
|
|
2435
|
-
const intent = record.fallbackIntent;
|
|
2436
|
-
if (intent?.retryRequestId === retryRequestId) {
|
|
2437
|
-
return { originalRequestId: record.requestId, intentId: intent.intentId };
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
return null;
|
|
2441
|
-
});
|
|
2442
|
-
}
|
|
2443
|
-
|
|
2444
|
-
private async finalizePrimarySummarySettlementOnStartup(
|
|
2445
|
-
record: PrimarySummaryRequestRecord,
|
|
2446
|
-
): Promise<'finalized' | { invalidReason: string } | false> {
|
|
2447
|
-
const agent = this.agents.get(record.agentName);
|
|
2448
|
-
if (!agent) return false;
|
|
2449
|
-
const settlementEntries = this.primarySummaryLogicalSettlementEntries(agent, record.requestId);
|
|
2450
|
-
if ('invalidReason' in settlementEntries) return settlementEntries;
|
|
2451
|
-
if (settlementEntries.length === 0) {
|
|
2452
|
-
return false;
|
|
2453
|
-
}
|
|
2454
|
-
const currentBranch = agent.getCurrentBranchGeneration();
|
|
2455
|
-
const assistantEntry = settlementEntries[0]!;
|
|
2456
|
-
const settlement = assistantEntry.settlement;
|
|
2457
|
-
if (
|
|
2458
|
-
assistantEntry.participant !== record.agentName ||
|
|
2459
|
-
settlement.agentName !== record.agentName ||
|
|
2460
|
-
settlement.dispatchKind !== record.dispatchKind ||
|
|
2461
|
-
settlement.entryIndex !== 0 ||
|
|
2462
|
-
settlement.role !== 'assistant' ||
|
|
2463
|
-
settlement.kind !== 'assistant_output'
|
|
2464
|
-
) {
|
|
2465
|
-
return { invalidReason: 'primary_summary_settlement_invalid_assistant_marker_on_restart' };
|
|
2466
|
-
}
|
|
2467
|
-
if (!currentBranch) {
|
|
2468
|
-
return { invalidReason: 'primary_summary_settlement_branch_unknown_on_restart' };
|
|
2469
|
-
}
|
|
2470
|
-
if (
|
|
2471
|
-
settlement.branch.id !== record.branch.id ||
|
|
2472
|
-
settlement.branch.name !== record.branch.name ||
|
|
2473
|
-
settlement.branch.generation !== record.branch.generation ||
|
|
2474
|
-
settlement.branch.id !== currentBranch.id ||
|
|
2475
|
-
settlement.branch.name !== currentBranch.name ||
|
|
2476
|
-
settlement.branch.generation !== currentBranch.generation
|
|
2477
|
-
) {
|
|
2478
|
-
return { invalidReason: 'primary_summary_settlement_branch_mismatch_on_restart' };
|
|
2479
|
-
}
|
|
2480
|
-
if (settlement.entryCount < 1 || settlement.entryCount > 2) {
|
|
2481
|
-
return { invalidReason: 'primary_summary_settlement_entry_count_invalid_on_restart' };
|
|
2482
|
-
}
|
|
2483
|
-
const entriesByIndex = new Map<number, PrimarySummaryLogicalSettlementEntry>();
|
|
2484
|
-
for (const entry of settlementEntries) {
|
|
2485
|
-
if (!this.primarySummarySettlementMetadataConsistent(settlement, entry.settlement)) {
|
|
2486
|
-
return { invalidReason: 'primary_summary_settlement_marker_conflict_on_restart' };
|
|
2487
|
-
}
|
|
2488
|
-
if (entriesByIndex.has(entry.settlement.entryIndex)) {
|
|
2489
|
-
return { invalidReason: 'primary_summary_settlement_duplicate_entry_on_restart' };
|
|
2490
|
-
}
|
|
2491
|
-
entriesByIndex.set(entry.settlement.entryIndex, entry);
|
|
2492
|
-
}
|
|
2493
|
-
const placeholderEntry = entriesByIndex.get(1);
|
|
2494
|
-
if (
|
|
2495
|
-
placeholderEntry
|
|
2496
|
-
&& (
|
|
2497
|
-
placeholderEntry.participant !== 'user'
|
|
2498
|
-
|| placeholderEntry.settlement.role !== 'user'
|
|
2499
|
-
|| placeholderEntry.settlement.kind !== 'generated_tool_result'
|
|
2500
|
-
)
|
|
2501
|
-
) {
|
|
2502
|
-
return { invalidReason: 'primary_summary_settlement_placeholder_marker_invalid_on_restart' };
|
|
2503
|
-
}
|
|
2504
|
-
if ([...entriesByIndex.keys()].some((index) => index !== 0 && index !== 1)) {
|
|
2505
|
-
return { invalidReason: 'primary_summary_settlement_out_of_order_entries_on_restart' };
|
|
2506
|
-
}
|
|
2507
|
-
const expectedPlaceholder = settlement.outcome === 'held'
|
|
2508
|
-
? this.buildNonExecutedToolResults(assistantEntry.content)
|
|
2509
|
-
: [];
|
|
2510
|
-
if (settlement.outcome === 'success') {
|
|
2511
|
-
if (settlement.entryCount !== 1 || placeholderEntry) {
|
|
2512
|
-
return { invalidReason: 'primary_summary_success_settlement_shape_invalid_on_restart' };
|
|
2513
|
-
}
|
|
2514
|
-
} else {
|
|
2515
|
-
const expectedEntryCount = expectedPlaceholder.length > 0 ? 2 : 1;
|
|
2516
|
-
if (settlement.entryCount !== expectedEntryCount) {
|
|
2517
|
-
return { invalidReason: 'primary_summary_held_settlement_shape_invalid_on_restart' };
|
|
2518
|
-
}
|
|
2519
|
-
if (placeholderEntry) {
|
|
2520
|
-
if (!this.primarySummaryContentMatches(placeholderEntry.content, expectedPlaceholder)) {
|
|
2521
|
-
return { invalidReason: 'primary_summary_settlement_placeholder_content_mismatch_on_restart' };
|
|
2522
|
-
}
|
|
2523
|
-
} else if (expectedPlaceholder.length > 0) {
|
|
2524
|
-
const placeholderMetadata = this.buildPrimarySummarySettlementMetadata(record, {
|
|
2525
|
-
outcome: 'held',
|
|
2526
|
-
settlementId: settlement.settlementId,
|
|
2527
|
-
stopReason: settlement.stopReason,
|
|
2528
|
-
holdReason: settlement.holdReason,
|
|
2529
|
-
providerInputTokens: settlement.providerInputTokens,
|
|
2530
|
-
visibleAssistantOutput: settlement.visibleAssistantOutput,
|
|
2531
|
-
executedToolCalls: settlement.executedToolCalls,
|
|
2532
|
-
entryIndex: 1,
|
|
2533
|
-
entryCount: settlement.entryCount,
|
|
2534
|
-
role: 'user',
|
|
2535
|
-
kind: 'generated_tool_result',
|
|
2536
|
-
});
|
|
2537
|
-
await this.persistPrimarySummaryChronicleEntries(agent, [
|
|
2538
|
-
{ participant: 'user', content: expectedPlaceholder, metadata: placeholderMetadata },
|
|
2539
|
-
]);
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
|
|
2543
|
-
if (record.dispatchKind === 'primary_summary_fallback_retry') {
|
|
2544
|
-
const parent = await this.findPrimarySummaryRetryParent(record.requestId);
|
|
2545
|
-
if (!parent) return { invalidReason: 'primary_summary_fallback_parent_missing_on_restart' };
|
|
2546
|
-
if (settlement.outcome === 'success') {
|
|
2547
|
-
await this.updatePrimarySummaryRequestRecord(record.requestId, (entry) => ({
|
|
2548
|
-
...entry,
|
|
2549
|
-
stopReason: settlement.stopReason ?? entry.stopReason,
|
|
2550
|
-
providerInputTokens: settlement.providerInputTokens ?? entry.providerInputTokens,
|
|
2551
|
-
visibleAssistantOutput: settlement.visibleAssistantOutput,
|
|
2552
|
-
executedToolCalls: settlement.executedToolCalls,
|
|
2553
|
-
finalStatus: 'success',
|
|
2554
|
-
fallbackHeldReason: undefined,
|
|
2555
|
-
}));
|
|
2556
|
-
await this.updatePrimarySummaryRequestRecord(parent.originalRequestId, (entry) => {
|
|
2557
|
-
const intent = entry.fallbackIntent;
|
|
2558
|
-
return {
|
|
2559
|
-
...entry,
|
|
2560
|
-
finalStatus: 'success',
|
|
2561
|
-
fallbackStatus: 'success',
|
|
2562
|
-
fallbackHeldReason: undefined,
|
|
2563
|
-
fallbackIntent: intent && intent.intentId === parent.intentId
|
|
2564
|
-
? { ...intent, finalizedAt: intent.finalizedAt ?? Date.now() }
|
|
2565
|
-
: intent,
|
|
2566
|
-
};
|
|
2567
|
-
});
|
|
2568
|
-
return 'finalized';
|
|
2569
|
-
}
|
|
2570
|
-
await this.updatePrimarySummaryRequestRecord(record.requestId, (entry) => ({
|
|
2571
|
-
...entry,
|
|
2572
|
-
stopReason: settlement.stopReason ?? entry.stopReason,
|
|
2573
|
-
providerInputTokens: settlement.providerInputTokens ?? entry.providerInputTokens,
|
|
2574
|
-
visibleAssistantOutput: settlement.visibleAssistantOutput,
|
|
2575
|
-
executedToolCalls: settlement.executedToolCalls,
|
|
2576
|
-
finalStatus: 'held',
|
|
2577
|
-
fallbackHeldReason: settlement.holdReason,
|
|
2578
|
-
}));
|
|
2579
|
-
await this.updatePrimarySummaryRequestRecord(parent.originalRequestId, (entry) => ({
|
|
2580
|
-
...entry,
|
|
2581
|
-
finalStatus: 'held',
|
|
2582
|
-
fallbackStatus: 'held',
|
|
2583
|
-
fallbackHeldReason: settlement.holdReason,
|
|
2584
|
-
}));
|
|
2585
|
-
return 'finalized';
|
|
2586
|
-
}
|
|
2587
|
-
|
|
2588
|
-
await this.updatePrimarySummaryRequestRecord(record.requestId, (entry) => ({
|
|
2589
|
-
...entry,
|
|
2590
|
-
stopReason: settlement.stopReason ?? entry.stopReason,
|
|
2591
|
-
providerInputTokens: settlement.providerInputTokens ?? entry.providerInputTokens,
|
|
2592
|
-
visibleAssistantOutput: settlement.visibleAssistantOutput,
|
|
2593
|
-
executedToolCalls: settlement.executedToolCalls,
|
|
2594
|
-
finalStatus: settlement.outcome === 'success' ? 'success' : 'held',
|
|
2595
|
-
fallbackStatus: settlement.outcome === 'success' ? entry.fallbackStatus : 'held',
|
|
2596
|
-
fallbackHeldReason: settlement.outcome === 'held' ? settlement.holdReason : undefined,
|
|
2597
|
-
}));
|
|
2598
|
-
return 'finalized';
|
|
2599
|
-
}
|
|
2600
|
-
|
|
2601
|
-
private summarizePrimarySummaryRetryRequest(request: NormalizedRequest): Record<string, unknown> {
|
|
2602
|
-
const blockCounts = new Map<string, number>();
|
|
2603
|
-
for (const message of request.messages) {
|
|
2604
|
-
for (const block of message.content) {
|
|
2605
|
-
blockCounts.set(block.type, (blockCounts.get(block.type) ?? 0) + 1);
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
return {
|
|
2609
|
-
kind: 'primary_summary_fallback_retry',
|
|
2610
|
-
requestHash: this.hashJson(request),
|
|
2611
|
-
systemHash: this.hashJson(request.system ?? ''),
|
|
2612
|
-
messageCount: request.messages.length,
|
|
2613
|
-
toolCount: request.tools?.length ?? 0,
|
|
2614
|
-
participantCount: [...new Set(request.messages.map((message) => message.participant))].length,
|
|
2615
|
-
blockCounts: Object.fromEntries([...blockCounts.entries()].sort(([left], [right]) => left.localeCompare(right))),
|
|
2616
|
-
promptCaching: request.promptCaching === true,
|
|
2617
|
-
cacheTtl: request.cacheTtl,
|
|
2618
|
-
assistantParticipant: request.assistantParticipant,
|
|
2619
|
-
requestCompleteBoundTokens: this.estimateRequestCompleteBoundTokens(request),
|
|
2620
|
-
};
|
|
2621
|
-
}
|
|
2622
|
-
|
|
2623
|
-
private summarizePrimarySummaryRetryResponse(response: NormalizedResponse): Record<string, unknown> {
|
|
2624
|
-
const blockCounts = new Map<string, number>();
|
|
2625
|
-
for (const block of response.content) {
|
|
2626
|
-
blockCounts.set(block.type, (blockCounts.get(block.type) ?? 0) + 1);
|
|
2627
|
-
}
|
|
2628
|
-
return {
|
|
2629
|
-
kind: 'primary_summary_fallback_retry',
|
|
2630
|
-
stopReason: response.stopReason,
|
|
2631
|
-
rawAssistantTextHash: response.rawAssistantText
|
|
2632
|
-
? this.hashJson(response.rawAssistantText)
|
|
2633
|
-
: undefined,
|
|
2634
|
-
blockCounts: Object.fromEntries([...blockCounts.entries()].sort(([left], [right]) => left.localeCompare(right))),
|
|
2635
|
-
toolCallIds: response.toolCalls.map((call: { id: string }) => call.id),
|
|
2636
|
-
usage: response.usage
|
|
2637
|
-
? {
|
|
2638
|
-
inputTokens: response.usage.inputTokens,
|
|
2639
|
-
outputTokens: response.usage.outputTokens,
|
|
2640
|
-
}
|
|
2641
|
-
: undefined,
|
|
2642
|
-
};
|
|
2643
|
-
}
|
|
2644
|
-
|
|
2645
|
-
private async claimPrimarySummaryRetryIntent(
|
|
2646
|
-
originalRequestId: string,
|
|
2647
|
-
intentId: string,
|
|
2648
|
-
retryRequestId: string,
|
|
2649
|
-
): Promise<'claimed' | 'duplicate' | 'missing'> {
|
|
2650
|
-
let outcome: 'claimed' | 'duplicate' | 'missing' = 'missing';
|
|
2651
|
-
await this.updatePrimarySummaryRequestRecord(originalRequestId, (record) => {
|
|
2652
|
-
const intent = record.fallbackIntent;
|
|
2653
|
-
if (!intent || intent.intentId !== intentId || record.fallbackStatus !== 'pending') {
|
|
2654
|
-
return record;
|
|
2655
|
-
}
|
|
2656
|
-
if (intent.retryRequestId && intent.retryRequestId !== retryRequestId) {
|
|
2657
|
-
outcome = 'duplicate';
|
|
2658
|
-
return record;
|
|
2659
|
-
}
|
|
2660
|
-
outcome = 'claimed';
|
|
2661
|
-
return {
|
|
2662
|
-
...record,
|
|
2663
|
-
fallbackIntent: {
|
|
2664
|
-
...intent,
|
|
2665
|
-
retryRequestId,
|
|
2666
|
-
dispatchedAt: intent.dispatchedAt ?? Date.now(),
|
|
2667
|
-
},
|
|
2668
|
-
};
|
|
2669
|
-
});
|
|
2670
|
-
return outcome;
|
|
2671
|
-
}
|
|
2672
|
-
|
|
2673
|
-
private async validatePrimarySummaryRetryMutation(
|
|
2674
|
-
agent: Agent,
|
|
2675
|
-
requestId: string,
|
|
2676
|
-
retryDispatch: PrimarySummaryFallbackRetryDispatch,
|
|
2677
|
-
compiledRequest: NormalizedRequest,
|
|
2678
|
-
artifacts: ActivationCompileArtifacts,
|
|
2679
|
-
phase: string,
|
|
2680
|
-
): Promise<{ ok: true } | { ok: false; reason: string; data: Record<string, unknown> }> {
|
|
2681
|
-
const branch = agent.getCurrentBranchGeneration();
|
|
2682
|
-
if (!branch || branch.id !== retryDispatch.branch.id || branch.generation !== retryDispatch.branch.generation) {
|
|
2683
|
-
return {
|
|
2684
|
-
ok: false,
|
|
2685
|
-
reason: `primary_summary_fallback_branch_changed_${phase}`,
|
|
2686
|
-
data: {
|
|
2687
|
-
expectedBranchId: retryDispatch.branch.id,
|
|
2688
|
-
expectedGeneration: retryDispatch.branch.generation,
|
|
2689
|
-
actualBranchId: branch?.id,
|
|
2690
|
-
actualGeneration: branch?.generation,
|
|
2691
|
-
},
|
|
2692
|
-
};
|
|
2693
|
-
}
|
|
2694
|
-
const requestHash = this.hashJson(compiledRequest);
|
|
2695
|
-
if (requestHash !== retryDispatch.expectedRetryRequestHash) {
|
|
2696
|
-
return {
|
|
2697
|
-
ok: false,
|
|
2698
|
-
reason: `primary_summary_fallback_request_changed_${phase}`,
|
|
2699
|
-
data: {
|
|
2700
|
-
expectedRetryRequestHash: retryDispatch.expectedRetryRequestHash,
|
|
2701
|
-
actualRetryRequestHash: requestHash,
|
|
2702
|
-
},
|
|
2703
|
-
};
|
|
2704
|
-
}
|
|
2705
|
-
if (
|
|
2706
|
-
this.primarySummaryProjectionKeys(artifacts.compileResult.primarySummaryProjection ?? null).join('\u0000') !==
|
|
2707
|
-
retryDispatch.originalProjectionKeys.join('\u0000')
|
|
2708
|
-
) {
|
|
2709
|
-
return {
|
|
2710
|
-
ok: false,
|
|
2711
|
-
reason: `primary_summary_fallback_projection_changed_${phase}`,
|
|
2712
|
-
data: {
|
|
2713
|
-
expectedProjectionKeys: retryDispatch.originalProjectionKeys,
|
|
2714
|
-
actualProjectionKeys: this.primarySummaryProjectionKeys(
|
|
2715
|
-
artifacts.compileResult.primarySummaryProjection ?? null,
|
|
2716
|
-
),
|
|
2717
|
-
},
|
|
2718
|
-
};
|
|
2719
|
-
}
|
|
2720
|
-
if (!this.primarySummaryContractsMatch(artifacts.contract, retryDispatch.originalArtifacts.contract)) {
|
|
2721
|
-
return {
|
|
2722
|
-
ok: false,
|
|
2723
|
-
reason: `primary_summary_fallback_contract_changed_${phase}`,
|
|
2724
|
-
data: {
|
|
2725
|
-
expectedContract: retryDispatch.originalArtifacts.contract,
|
|
2726
|
-
actualContract: artifacts.contract,
|
|
2727
|
-
},
|
|
2728
|
-
};
|
|
2729
|
-
}
|
|
2730
|
-
const original = await this.getPrimarySummaryRequestRecord(retryDispatch.originalRequestId);
|
|
2731
|
-
if (
|
|
2732
|
-
!original?.fallbackIntent ||
|
|
2733
|
-
original.fallbackIntent.intentId !== retryDispatch.intentId ||
|
|
2734
|
-
original.fallbackIntent.retryRequestId !== requestId ||
|
|
2735
|
-
original.fallbackStatus !== 'pending'
|
|
2736
|
-
) {
|
|
2737
|
-
return {
|
|
2738
|
-
ok: false,
|
|
2739
|
-
reason: `primary_summary_fallback_intent_changed_${phase}`,
|
|
2740
|
-
data: {
|
|
2741
|
-
expectedIntentId: retryDispatch.intentId,
|
|
2742
|
-
actualIntentId: original?.fallbackIntent?.intentId,
|
|
2743
|
-
retryRequestId: original?.fallbackIntent?.retryRequestId,
|
|
2744
|
-
fallbackStatus: original?.fallbackStatus,
|
|
2745
|
-
},
|
|
2746
|
-
};
|
|
2747
|
-
}
|
|
2748
|
-
return { ok: true };
|
|
2749
|
-
}
|
|
2750
|
-
|
|
2751
|
-
private async recordPrimarySummaryRetryQuarantine(
|
|
2752
|
-
originalRequestId: string,
|
|
2753
|
-
intentId: string,
|
|
2754
|
-
): Promise<void> {
|
|
2755
|
-
await this.updatePrimarySummaryRequestRecord(originalRequestId, (record) => {
|
|
2756
|
-
const intent = record.fallbackIntent;
|
|
2757
|
-
if (!intent || intent.intentId !== intentId || intent.quarantinePersistedAt) return record;
|
|
2758
|
-
return {
|
|
2759
|
-
...record,
|
|
2760
|
-
fallbackIntent: {
|
|
2761
|
-
...intent,
|
|
2762
|
-
quarantinePersistedAt: Date.now(),
|
|
2763
|
-
},
|
|
2764
|
-
};
|
|
2765
|
-
});
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
private async recordPrimarySummaryRetryOutput(
|
|
2769
|
-
originalRequestId: string,
|
|
2770
|
-
intentId: string,
|
|
2771
|
-
output: PrimarySummaryFallbackOutputRecord,
|
|
2772
|
-
): Promise<void> {
|
|
2773
|
-
await this.updatePrimarySummaryRequestRecord(originalRequestId, (record) => {
|
|
2774
|
-
const intent = record.fallbackIntent;
|
|
2775
|
-
if (!intent || intent.intentId !== intentId) return record;
|
|
2776
|
-
const existing = intent.outputs?.find((entry) => entry.phase === output.phase);
|
|
2777
|
-
if (
|
|
2778
|
-
existing &&
|
|
2779
|
-
existing.contentHash === output.contentHash &&
|
|
2780
|
-
existing.messageId === output.messageId
|
|
2781
|
-
) {
|
|
2782
|
-
return record;
|
|
2783
|
-
}
|
|
2784
|
-
if (existing) {
|
|
2785
|
-
throw new Error(`Primary summary fallback output phase '${output.phase}' changed after persistence`);
|
|
2786
|
-
}
|
|
2787
|
-
return {
|
|
2788
|
-
...record,
|
|
2789
|
-
fallbackIntent: {
|
|
2790
|
-
...intent,
|
|
2791
|
-
outputs: [...(intent.outputs ?? []), output],
|
|
2792
|
-
},
|
|
2793
|
-
};
|
|
2794
|
-
});
|
|
2795
|
-
}
|
|
2796
|
-
|
|
2797
|
-
private async recordPrimarySummaryRetryToolDispatch(
|
|
2798
|
-
originalRequestId: string,
|
|
2799
|
-
intentId: string,
|
|
2800
|
-
call: ToolCall,
|
|
2801
|
-
): Promise<void> {
|
|
2802
|
-
await this.updatePrimarySummaryRequestRecord(originalRequestId, (record) => {
|
|
2803
|
-
const intent = record.fallbackIntent;
|
|
2804
|
-
if (!intent || intent.intentId !== intentId) return record;
|
|
2805
|
-
const inputHash = this.hashJson(call.input);
|
|
2806
|
-
const existing = intent.toolDispatches?.find((entry) => entry.callId === call.id);
|
|
2807
|
-
if (existing?.inputHash === inputHash) return record;
|
|
2808
|
-
if (existing) {
|
|
2809
|
-
throw new Error(`Primary summary fallback tool dispatch '${call.id}' changed after persistence`);
|
|
2810
|
-
}
|
|
2811
|
-
return {
|
|
2812
|
-
...record,
|
|
2813
|
-
fallbackIntent: {
|
|
2814
|
-
...intent,
|
|
2815
|
-
toolDispatches: [
|
|
2816
|
-
...(intent.toolDispatches ?? []),
|
|
2817
|
-
{
|
|
2818
|
-
callId: call.id,
|
|
2819
|
-
inputHash,
|
|
2820
|
-
recordedAt: Date.now(),
|
|
2821
|
-
},
|
|
2822
|
-
],
|
|
2823
|
-
},
|
|
2824
|
-
};
|
|
2825
|
-
});
|
|
2826
|
-
}
|
|
2827
|
-
|
|
2828
|
-
private async resolveContextBudgetRestartDispatch(
|
|
2829
|
-
requestId: string,
|
|
2830
|
-
retryDispatch?: PrimarySummaryFallbackRetryDispatch,
|
|
2831
|
-
): Promise<ContextBudgetRestartDispatch> {
|
|
2832
|
-
const retryIdentity = retryDispatch
|
|
2833
|
-
? {
|
|
2834
|
-
requestId,
|
|
2835
|
-
dispatchKind: 'primary_summary_fallback_retry' as const,
|
|
2836
|
-
originalRequestId: retryDispatch.originalRequestId,
|
|
2837
|
-
intentId: retryDispatch.intentId,
|
|
2838
|
-
}
|
|
2839
|
-
: null;
|
|
2840
|
-
const record = await this.getPrimarySummaryRequestRecord(requestId);
|
|
2841
|
-
if (record?.dispatchKind === 'primary_summary_fallback_retry' && retryIdentity) {
|
|
2842
|
-
return retryIdentity;
|
|
2843
|
-
}
|
|
2844
|
-
if (retryIdentity) {
|
|
2845
|
-
const original = await this.getPrimarySummaryRequestRecord(retryIdentity.originalRequestId);
|
|
2846
|
-
if (
|
|
2847
|
-
original?.fallbackIntent?.intentId === retryIdentity.intentId
|
|
2848
|
-
&& original.fallbackIntent.retryRequestId === requestId
|
|
2849
|
-
) {
|
|
2850
|
-
return retryIdentity;
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
return {
|
|
2854
|
-
requestId,
|
|
2855
|
-
dispatchKind: 'primary',
|
|
2856
|
-
};
|
|
2857
|
-
}
|
|
2858
|
-
|
|
2859
|
-
private contextBudgetRestartMatchesIdentity(
|
|
2860
|
-
request: PendingInferenceRequest,
|
|
2861
|
-
identity: ContextBudgetRestartDispatch,
|
|
2862
|
-
): boolean {
|
|
2863
|
-
if (request.reason !== 'context_budget_restart') return false;
|
|
2864
|
-
const restart = request.contextBudgetRestart;
|
|
2865
|
-
if (!restart) return false;
|
|
2866
|
-
if (restart.dispatchKind !== identity.dispatchKind) return false;
|
|
2867
|
-
if (restart.requestId !== identity.requestId) return false;
|
|
2868
|
-
if (identity.dispatchKind !== 'primary_summary_fallback_retry') return true;
|
|
2869
|
-
return restart.originalRequestId === identity.originalRequestId
|
|
2870
|
-
&& restart.intentId === identity.intentId;
|
|
2871
|
-
}
|
|
2872
|
-
|
|
2873
|
-
private clearQueuedContextBudgetRestart(identity: ContextBudgetRestartDispatch): number {
|
|
2874
|
-
const before = this.pendingRequests.length;
|
|
2875
|
-
this.pendingRequests = this.pendingRequests.filter(
|
|
2876
|
-
(request) => !this.contextBudgetRestartMatchesIdentity(request, identity),
|
|
2877
|
-
);
|
|
2878
|
-
return before - this.pendingRequests.length;
|
|
2879
|
-
}
|
|
2880
|
-
|
|
2881
|
-
private async markPrimarySummaryRetrySuccess(
|
|
2882
|
-
requestId: string,
|
|
2883
|
-
retryDispatch: PrimarySummaryFallbackRetryDispatch,
|
|
2884
|
-
stopReason: string | undefined,
|
|
2885
|
-
providerInputTokens: number | undefined,
|
|
2886
|
-
visibleAssistantOutput: boolean,
|
|
2887
|
-
executedToolCalls: number,
|
|
2888
|
-
): Promise<void> {
|
|
2889
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
2890
|
-
...record,
|
|
2891
|
-
stopReason,
|
|
2892
|
-
providerInputTokens,
|
|
2893
|
-
visibleAssistantOutput,
|
|
2894
|
-
executedToolCalls,
|
|
2895
|
-
finalStatus: 'success',
|
|
2896
|
-
fallbackHeldReason: undefined,
|
|
2897
|
-
}));
|
|
2898
|
-
await this.updatePrimarySummaryRequestRecord(retryDispatch.originalRequestId, (record) => {
|
|
2899
|
-
const intent = record.fallbackIntent;
|
|
2900
|
-
return {
|
|
2901
|
-
...record,
|
|
2902
|
-
finalStatus: 'success',
|
|
2903
|
-
fallbackStatus: 'success',
|
|
2904
|
-
fallbackHeldReason: undefined,
|
|
2905
|
-
fallbackIntent: intent && intent.intentId === retryDispatch.intentId
|
|
2906
|
-
? { ...intent, finalizedAt: Date.now() }
|
|
2907
|
-
: intent,
|
|
2908
|
-
};
|
|
2909
|
-
});
|
|
2910
|
-
}
|
|
2911
|
-
|
|
2912
|
-
private async holdPrimarySummaryRetryFamily(
|
|
2913
|
-
agent: Agent,
|
|
2914
|
-
requestId: string,
|
|
2915
|
-
retryDispatch: PrimarySummaryFallbackRetryDispatch,
|
|
2916
|
-
reason: string,
|
|
2917
|
-
data?: Record<string, unknown>,
|
|
2918
|
-
options?: { emitChronicleMarker?: boolean; stopReason?: string },
|
|
2919
|
-
): Promise<void> {
|
|
2920
|
-
const clearedQueuedContextBudgetRestarts = this.clearQueuedContextBudgetRestart({
|
|
2921
|
-
requestId,
|
|
2922
|
-
dispatchKind: 'primary_summary_fallback_retry',
|
|
2923
|
-
originalRequestId: retryDispatch.originalRequestId,
|
|
2924
|
-
intentId: retryDispatch.intentId,
|
|
2925
|
-
});
|
|
2926
|
-
const holdData = clearedQueuedContextBudgetRestarts > 0
|
|
2927
|
-
? { ...(data ?? {}), clearedQueuedContextBudgetRestarts }
|
|
2928
|
-
: data;
|
|
2929
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
2930
|
-
...record,
|
|
2931
|
-
...(options?.stopReason ? { stopReason: options.stopReason } : {}),
|
|
2932
|
-
finalStatus: 'held',
|
|
2933
|
-
fallbackHeldReason: reason,
|
|
2934
|
-
}));
|
|
2935
|
-
await this.updatePrimarySummaryRequestRecord(retryDispatch.originalRequestId, (record) => ({
|
|
2936
|
-
...record,
|
|
2937
|
-
finalStatus: 'held',
|
|
2938
|
-
fallbackStatus: 'held',
|
|
2939
|
-
fallbackHeldReason: reason,
|
|
2940
|
-
}));
|
|
2941
|
-
this.opsAlert('refusal-held', agent.name, reason, { data: holdData });
|
|
2942
|
-
if (options?.emitChronicleMarker === false) return;
|
|
2943
|
-
try {
|
|
2944
|
-
agent.getContextManager().addMessage(
|
|
2945
|
-
'user',
|
|
2946
|
-
[{
|
|
2947
|
-
type: 'text',
|
|
2948
|
-
text:
|
|
2949
|
-
`[refusal-held] Your previous turn was refused by the provider and ` +
|
|
2950
|
-
`is being held for operator review rather than silently retried. ` +
|
|
2951
|
-
`Automatic raw-expansion recovery was either unsafe or unsuccessful.`,
|
|
2952
|
-
}],
|
|
2953
|
-
{ system: true, kind: 'refusal-held', reason },
|
|
2954
|
-
);
|
|
2955
|
-
} catch (error) {
|
|
2956
|
-
console.error('[refusal-held] could not record chronicle marker:', error);
|
|
2957
|
-
}
|
|
2958
|
-
}
|
|
2959
|
-
|
|
2960
|
-
private async primarySummaryRetryAlreadySucceeded(
|
|
2961
|
-
requestId: string,
|
|
2962
|
-
retryDispatch: PrimarySummaryFallbackRetryDispatch,
|
|
2963
|
-
): Promise<boolean> {
|
|
2964
|
-
const [retryRecord, originalRecord] = await Promise.all([
|
|
2965
|
-
this.getPrimarySummaryRequestRecord(requestId),
|
|
2966
|
-
this.getPrimarySummaryRequestRecord(retryDispatch.originalRequestId),
|
|
2967
|
-
]);
|
|
2968
|
-
return retryRecord?.finalStatus === 'success'
|
|
2969
|
-
|| originalRecord?.fallbackStatus === 'success'
|
|
2970
|
-
|| originalRecord?.finalStatus === 'success';
|
|
2971
|
-
}
|
|
2972
|
-
|
|
2973
|
-
private async primarySummaryRetryFamilyAlreadySettled(
|
|
2974
|
-
requestId: string,
|
|
2975
|
-
retryDispatch: PrimarySummaryFallbackRetryDispatch,
|
|
2976
|
-
): Promise<boolean> {
|
|
2977
|
-
const [retryRecord, originalRecord] = await Promise.all([
|
|
2978
|
-
this.getPrimarySummaryRequestRecord(requestId),
|
|
2979
|
-
this.getPrimarySummaryRequestRecord(retryDispatch.originalRequestId),
|
|
2980
|
-
]);
|
|
2981
|
-
return retryRecord?.finalStatus === 'held'
|
|
2982
|
-
|| retryRecord?.finalStatus === 'success'
|
|
2983
|
-
|| originalRecord?.fallbackStatus === 'held'
|
|
2984
|
-
|| originalRecord?.fallbackStatus === 'success'
|
|
2985
|
-
|| originalRecord?.finalStatus === 'held'
|
|
2986
|
-
|| originalRecord?.finalStatus === 'success';
|
|
2987
|
-
}
|
|
2988
|
-
|
|
2989
|
-
private async holdPrimarySummaryRequest(
|
|
2990
|
-
agent: Agent,
|
|
2991
|
-
requestId: string,
|
|
2992
|
-
reason: string,
|
|
2993
|
-
data?: Record<string, unknown>,
|
|
2994
|
-
): Promise<void> {
|
|
2995
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
2996
|
-
...record,
|
|
2997
|
-
finalStatus: 'held',
|
|
2998
|
-
fallbackStatus: 'held',
|
|
2999
|
-
fallbackHeldReason: reason,
|
|
3000
|
-
}));
|
|
3001
|
-
this.opsAlert('refusal-held', agent.name, reason, { data });
|
|
3002
|
-
try {
|
|
3003
|
-
agent.getContextManager().addMessage(
|
|
3004
|
-
'user',
|
|
3005
|
-
[{
|
|
3006
|
-
type: 'text',
|
|
3007
|
-
text:
|
|
3008
|
-
`[refusal-held] Your previous turn was refused by the provider and ` +
|
|
3009
|
-
`is being held for operator review rather than silently retried. ` +
|
|
3010
|
-
`Automatic raw-expansion recovery was either unsafe or unsuccessful.`,
|
|
3011
|
-
}],
|
|
3012
|
-
{ system: true, kind: 'refusal-held', reason },
|
|
3013
|
-
);
|
|
3014
|
-
} catch (error) {
|
|
3015
|
-
console.error('[refusal-held] could not record chronicle marker:', error);
|
|
3016
|
-
}
|
|
3017
|
-
}
|
|
3018
|
-
|
|
3019
|
-
private hasVisibleAssistantOutput(blocks: ReadonlyArray<ContentBlock>): boolean {
|
|
3020
|
-
return blocks.some((block) => block.type !== 'thinking' && block.type !== 'redacted_thinking');
|
|
3021
|
-
}
|
|
3022
|
-
|
|
3023
|
-
private buildPrimarySummaryRequestRecord(
|
|
3024
|
-
agent: Agent,
|
|
3025
|
-
requestId: string,
|
|
3026
|
-
dispatchKind: PrimarySummaryRequestRecord['dispatchKind'],
|
|
3027
|
-
request: NormalizedRequest,
|
|
3028
|
-
artifacts: ActivationCompileArtifacts | undefined,
|
|
3029
|
-
branch: BranchGenerationInfo | null,
|
|
3030
|
-
stopReason: string | undefined,
|
|
3031
|
-
visibleAssistantOutput: boolean,
|
|
3032
|
-
executedToolCalls: number,
|
|
3033
|
-
providerInputTokens: number | undefined,
|
|
3034
|
-
finalStatus: PrimarySummaryFallbackStatus,
|
|
3035
|
-
): PrimarySummaryRequestRecord {
|
|
3036
|
-
const projection = artifacts?.compileResult.primarySummaryProjection ?? null;
|
|
3037
|
-
return {
|
|
3038
|
-
requestId,
|
|
3039
|
-
agentName: agent.name,
|
|
3040
|
-
namespace: projection?.namespace ?? `agents/${agent.name}`,
|
|
3041
|
-
timestamp: Date.now(),
|
|
3042
|
-
dispatchKind,
|
|
3043
|
-
branch: branch
|
|
3044
|
-
? { id: branch.id, name: branch.name, generation: branch.generation }
|
|
3045
|
-
: { id: 'unknown', name: 'unknown', generation: -1 },
|
|
3046
|
-
projection,
|
|
3047
|
-
requestInputBoundTokens: this.estimateRequestInputBoundTokens(request),
|
|
3048
|
-
requestCompleteBoundTokens: this.estimateRequestCompleteBoundTokens(request),
|
|
3049
|
-
providerInputTokens,
|
|
3050
|
-
systemHash: artifacts?.contract.systemHash ?? this.hashJson(request.system ?? ''),
|
|
3051
|
-
modelConfigHash: artifacts?.contract.modelConfigHash ?? this.hashJson({ request: request.config }),
|
|
3052
|
-
toolContractHash: artifacts?.contract.toolContractHash ?? this.hashJson(request.tools ?? []),
|
|
3053
|
-
stopReason,
|
|
3054
|
-
visibleAssistantOutput,
|
|
3055
|
-
executedToolCalls,
|
|
3056
|
-
finalStatus,
|
|
3057
|
-
};
|
|
3058
|
-
}
|
|
3059
|
-
|
|
3060
1619
|
/** Liveness ping for an active ephemeral run (no-op otherwise). */
|
|
3061
1620
|
private touchEphemeralRun(agentName: string, inferenceStarted = false): void {
|
|
3062
1621
|
const run = this.ephemeralRuns.get(agentName);
|
|
@@ -3381,6 +1940,29 @@ export class AgentFramework {
|
|
|
3381
1940
|
},
|
|
3382
1941
|
};
|
|
3383
1942
|
|
|
1943
|
+
/** Synthesized read_image tool — present when a workspace module is
|
|
1944
|
+
* registered. Returns the image as a native image block in the tool
|
|
1945
|
+
* result, so the agent SEES it in the live turn (history keeps a compact
|
|
1946
|
+
* placeholder via the standard tool-result serializer). */
|
|
1947
|
+
private static readonly READ_IMAGE_TOOL: import('./types/index.js').ToolDefinition = {
|
|
1948
|
+
name: 'read_image',
|
|
1949
|
+
description:
|
|
1950
|
+
'View an image file from a workspace mount. The image is returned into ' +
|
|
1951
|
+
'your context so you can actually see it (e.g. after save_recent_image, ' +
|
|
1952
|
+
'or for images placed in the workspace by other means). Path is ' +
|
|
1953
|
+
'mount-prefixed, e.g. "project/photos/cat.png". Supports png/jpeg/gif/webp.',
|
|
1954
|
+
inputSchema: {
|
|
1955
|
+
type: 'object',
|
|
1956
|
+
properties: {
|
|
1957
|
+
path: {
|
|
1958
|
+
type: 'string',
|
|
1959
|
+
description: 'Mount-prefixed image path, e.g. "project/photos/name.png".',
|
|
1960
|
+
},
|
|
1961
|
+
},
|
|
1962
|
+
required: ['path'],
|
|
1963
|
+
},
|
|
1964
|
+
};
|
|
1965
|
+
|
|
3384
1966
|
private static readonly AGENT_SETTINGS_TOOL: import('./types/index.js').ToolDefinition = {
|
|
3385
1967
|
name: 'agent_settings',
|
|
3386
1968
|
description:
|
|
@@ -4603,7 +3185,6 @@ export class AgentFramework {
|
|
|
4603
3185
|
// Cast to AgentState to bypass TypeScript's control flow narrowing
|
|
4604
3186
|
const currentState = agent.state as AgentState;
|
|
4605
3187
|
if (currentState.status === 'ready') {
|
|
4606
|
-
const activeStreamContext = this.activeStreamContexts.get(agent.name);
|
|
4607
3188
|
// Flush pending assistant blocks (tool_use + preamble text) to context
|
|
4608
3189
|
const pendingBlocks = this.pendingAssistantBlocks.get(agent.name);
|
|
4609
3190
|
if (pendingBlocks) {
|
|
@@ -4725,59 +3306,6 @@ export class AgentFramework {
|
|
|
4725
3306
|
// a terminal failure — rejecting an ephemeral's promise mid-run
|
|
4726
3307
|
// and emitting a spurious inference:exhausted (same race shape as
|
|
4727
3308
|
// endTurn above).
|
|
4728
|
-
const requestId = activeStreamContext?.requestId;
|
|
4729
|
-
const primarySummaryRetry = activeStreamContext?.trigger?.primarySummaryFallbackRetry;
|
|
4730
|
-
const compiledRequest = activeStreamContext?.compiledRequest;
|
|
4731
|
-
const artifacts = activeStreamContext?.artifacts;
|
|
4732
|
-
const restartDispatch = await this.resolveContextBudgetRestartDispatch(
|
|
4733
|
-
requestId ?? `${agent.name}:context_budget_restart`,
|
|
4734
|
-
primarySummaryRetry,
|
|
4735
|
-
);
|
|
4736
|
-
if (
|
|
4737
|
-
restartDispatch.dispatchKind === 'primary_summary_fallback_retry'
|
|
4738
|
-
&& requestId
|
|
4739
|
-
&& primarySummaryRetry
|
|
4740
|
-
&& compiledRequest
|
|
4741
|
-
&& artifacts
|
|
4742
|
-
) {
|
|
4743
|
-
const validation = await this.validatePrimarySummaryRetryMutation(
|
|
4744
|
-
agent,
|
|
4745
|
-
requestId,
|
|
4746
|
-
primarySummaryRetry,
|
|
4747
|
-
compiledRequest,
|
|
4748
|
-
artifacts,
|
|
4749
|
-
'before_context_budget_restart_hold',
|
|
4750
|
-
);
|
|
4751
|
-
if (!validation.ok) {
|
|
4752
|
-
await this.holdPrimarySummaryRetryFamily(
|
|
4753
|
-
agent,
|
|
4754
|
-
requestId,
|
|
4755
|
-
primarySummaryRetry,
|
|
4756
|
-
validation.reason,
|
|
4757
|
-
validation.data,
|
|
4758
|
-
{ emitChronicleMarker: false, stopReason: 'abort' },
|
|
4759
|
-
);
|
|
4760
|
-
} else {
|
|
4761
|
-
await this.holdPrimarySummaryRetryFamily(
|
|
4762
|
-
agent,
|
|
4763
|
-
requestId,
|
|
4764
|
-
primarySummaryRetry,
|
|
4765
|
-
'primary_summary_fallback_context_budget_restart_required',
|
|
4766
|
-
{
|
|
4767
|
-
inputTokens: agent.lastStreamInputTokens,
|
|
4768
|
-
budget: agent.maxStreamTokens,
|
|
4769
|
-
originalRequestId: primarySummaryRetry.originalRequestId,
|
|
4770
|
-
intentId: primarySummaryRetry.intentId,
|
|
4771
|
-
},
|
|
4772
|
-
{ emitChronicleMarker: false, stopReason: 'abort' },
|
|
4773
|
-
);
|
|
4774
|
-
}
|
|
4775
|
-
this.frameworkCancelledStreams.set(`${agent.name}:${agent.streamId}`, 'budget_restart');
|
|
4776
|
-
currentState.stream?.cancel();
|
|
4777
|
-
agent.reset();
|
|
4778
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
4779
|
-
return;
|
|
4780
|
-
}
|
|
4781
3309
|
if (currentState.stream) {
|
|
4782
3310
|
this.frameworkCancelledStreams.set(`${agent.name}:${agent.streamId}`, 'budget_restart');
|
|
4783
3311
|
}
|
|
@@ -4794,8 +3322,6 @@ export class AgentFramework {
|
|
|
4794
3322
|
reason: 'context_budget_restart',
|
|
4795
3323
|
source: 'framework',
|
|
4796
3324
|
timestamp: Date.now(),
|
|
4797
|
-
channelId: activeStreamContext?.trigger?.channelId,
|
|
4798
|
-
contextBudgetRestart: restartDispatch,
|
|
4799
3325
|
});
|
|
4800
3326
|
} else if (currentState.stream) {
|
|
4801
3327
|
// Streaming path: convert results and resume the stream.
|
|
@@ -5296,18 +3822,30 @@ export class AgentFramework {
|
|
|
5296
3822
|
? event.origin.messageId
|
|
5297
3823
|
: event.eventId;
|
|
5298
3824
|
const maxBackscroll = descriptor?.capabilities?.history?.maxMessages ?? 0;
|
|
3825
|
+
// Resolve human-readable names from the event origin wherever the
|
|
3826
|
+
// surface provided them — a mind reading this should see WHO addressed
|
|
3827
|
+
// it WHERE, not a wall of snowflake ids. Raw ids remain only inside the
|
|
3828
|
+
// tool-argument instructions, where they are the literal values to pass.
|
|
3829
|
+
const origin = (event.origin ?? {}) as Record<string, unknown>;
|
|
3830
|
+
const authorName = typeof origin.authorName === 'string' && origin.authorName ? origin.authorName : 'Someone';
|
|
3831
|
+
const channelLabel = triggerChannel.label ? `#${triggerChannel.label}` : `"${triggerChannel.channelId}"`;
|
|
3832
|
+
const place = typeof origin.guildName === 'string' && origin.guildName
|
|
3833
|
+
? `${channelLabel} in "${origin.guildName}"`
|
|
3834
|
+
: channelLabel;
|
|
5299
3835
|
content.push({
|
|
5300
3836
|
type: 'text',
|
|
5301
3837
|
text:
|
|
5302
|
-
`\n[Channel invitation]
|
|
5303
|
-
`
|
|
5304
|
-
`
|
|
3838
|
+
`\n[Channel invitation] ${authorName} addressed you in ${place} — ` +
|
|
3839
|
+
`a channel you haven't joined. You're seeing this one message; you won't receive more from it unless you join. ` +
|
|
3840
|
+
`Your options:\n` +
|
|
3841
|
+
`1. Reply without joining — simply write your reply as normal text this turn; it will be delivered to ${channelLabel}.\n` +
|
|
3842
|
+
`2. Join the channel — call channel_open with channelId "${triggerChannel.channelId}" and serverId "${event.serverId}"` +
|
|
5305
3843
|
(maxBackscroll > 0
|
|
5306
|
-
?
|
|
5307
|
-
: `
|
|
5308
|
-
`
|
|
5309
|
-
`
|
|
5310
|
-
`
|
|
3844
|
+
? `; to also read recent history, add backscroll (a number up to ${maxBackscroll}) and beforeMessageId "${messageId}".\n`
|
|
3845
|
+
: `.\n`) +
|
|
3846
|
+
`3. Stay out — call channel_decline with channelId "${triggerChannel.channelId}", serverId "${event.serverId}", ` +
|
|
3847
|
+
`and messageId "${messageId}" (optionally set acknowledge to an emoji like 👀 so ${authorName} isn't left hanging).\n` +
|
|
3848
|
+
`Doing nothing is also fine.`,
|
|
5311
3849
|
});
|
|
5312
3850
|
metadata.channelInvitation = true;
|
|
5313
3851
|
metadata.channelOpen = false;
|
|
@@ -5382,7 +3920,7 @@ export class AgentFramework {
|
|
|
5382
3920
|
const state = this.createFrameworkState();
|
|
5383
3921
|
|
|
5384
3922
|
// Group requests by agent
|
|
5385
|
-
const requestsByAgent = new Map<string,
|
|
3923
|
+
const requestsByAgent = new Map<string, InferenceRequest[]>();
|
|
5386
3924
|
for (const req of this.pendingRequests) {
|
|
5387
3925
|
const existing = requestsByAgent.get(req.agentName) ?? [];
|
|
5388
3926
|
existing.push(req);
|
|
@@ -5393,8 +3931,7 @@ export class AgentFramework {
|
|
|
5393
3931
|
this.pendingRequests = [];
|
|
5394
3932
|
|
|
5395
3933
|
// Check each agent
|
|
5396
|
-
for (const [agentName,
|
|
5397
|
-
let requests = queuedRequests;
|
|
3934
|
+
for (const [agentName, requests] of requestsByAgent) {
|
|
5398
3935
|
const agent = this.agents.get(agentName);
|
|
5399
3936
|
if (!agent) {
|
|
5400
3937
|
// Agent not found — request is orphaned. Emit warning and drop.
|
|
@@ -5439,19 +3976,6 @@ export class AgentFramework {
|
|
|
5439
3976
|
continue;
|
|
5440
3977
|
}
|
|
5441
3978
|
|
|
5442
|
-
if (this.discordAwarenessBarrier) {
|
|
5443
|
-
this.pendingRequests.push(...requests);
|
|
5444
|
-
continue;
|
|
5445
|
-
}
|
|
5446
|
-
|
|
5447
|
-
requests = requests.filter((request) =>
|
|
5448
|
-
request.reason !== 'context_budget_restart'
|
|
5449
|
-
|| request.contextBudgetRestart?.dispatchKind !== 'primary_summary_fallback_retry'
|
|
5450
|
-
);
|
|
5451
|
-
if (requests.length === 0) {
|
|
5452
|
-
continue;
|
|
5453
|
-
}
|
|
5454
|
-
|
|
5455
3979
|
// Check policy
|
|
5456
3980
|
if (!this.inferencePolicy.shouldInfer(agentName, requests, state)) {
|
|
5457
3981
|
// Loud drop: a queued request that dies here is otherwise invisible —
|
|
@@ -5465,12 +3989,7 @@ export class AgentFramework {
|
|
|
5465
3989
|
}
|
|
5466
3990
|
|
|
5467
3991
|
// Start streaming inference (non-blocking — driveStream runs in background)
|
|
5468
|
-
const trigger = requests
|
|
5469
|
-
if (trigger.primarySummaryFallbackRetry) {
|
|
5470
|
-
for (const request of requests) {
|
|
5471
|
-
if (request !== trigger) this.pendingRequests.push(request);
|
|
5472
|
-
}
|
|
5473
|
-
}
|
|
3992
|
+
const trigger = requests[0];
|
|
5474
3993
|
// Route this turn's auto-published speech to the channel that triggered
|
|
5475
3994
|
// it (item-3 redux). A batched wake may carry several triggering channels
|
|
5476
3995
|
// (messages arrived in >1 channel while the agent was busy/idle) — reply
|
|
@@ -5487,9 +4006,9 @@ export class AgentFramework {
|
|
|
5487
4006
|
}
|
|
5488
4007
|
}
|
|
5489
4008
|
|
|
5490
|
-
private async startAgentStream(agent: Agent, trigger?:
|
|
4009
|
+
private async startAgentStream(agent: Agent, trigger?: InferenceRequest, attempt = 0): Promise<void> {
|
|
5491
4010
|
// Record turn checkpoint before inference (only on first attempt, not retries)
|
|
5492
|
-
if (attempt === 0
|
|
4011
|
+
if (attempt === 0) {
|
|
5493
4012
|
this.recordTurnCheckpoint(agent.name);
|
|
5494
4013
|
this.redoStacks.delete(agent.name); // new work invalidates redo
|
|
5495
4014
|
}
|
|
@@ -5503,283 +4022,69 @@ export class AgentFramework {
|
|
|
5503
4022
|
if (trigger?.channelId) {
|
|
5504
4023
|
this.activeTriggerChannels.set(agent.name, trigger.channelId);
|
|
5505
4024
|
} else {
|
|
5506
|
-
this.activeTriggerChannels.delete(agent.name);
|
|
5507
|
-
}
|
|
5508
|
-
|
|
5509
|
-
this.touchEphemeralRun(agent.name, true);
|
|
5510
|
-
this.emitTrace({ type: 'inference:started', agentName: agent.name });
|
|
5511
|
-
this.eventGate?.onInferenceStarted(agent.name);
|
|
5512
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), startedAt: Date.now() });
|
|
5513
|
-
const requestId = `${agent.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5514
|
-
|
|
5515
|
-
try {
|
|
5516
|
-
const requestSnapshot = this.captureInferenceToolSnapshot(agent);
|
|
5517
|
-
const allTools = this.getToolsForAgent(agent.name, requestSnapshot);
|
|
5518
|
-
const tools = allTools.filter((t) => agent.canUseTool(t.name));
|
|
5519
|
-
const fallbackConfig = this.primarySummaryFallbackConfig(agent);
|
|
5520
|
-
const retryDispatch = trigger?.primarySummaryFallbackRetry;
|
|
5521
|
-
let compiledRequest: NormalizedRequest;
|
|
5522
|
-
let artifacts: ActivationCompileArtifacts;
|
|
5523
|
-
let branch = agent.getCurrentBranchGeneration();
|
|
5524
|
-
|
|
5525
|
-
if (retryDispatch) {
|
|
5526
|
-
if (!fallbackConfig.enabled) {
|
|
5527
|
-
await this.holdPrimarySummaryRequest(
|
|
5528
|
-
agent,
|
|
5529
|
-
retryDispatch.originalRequestId,
|
|
5530
|
-
'primary_summary_fallback_disabled_before_retry',
|
|
5531
|
-
);
|
|
5532
|
-
agent.reset();
|
|
5533
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
5534
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
5535
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
5536
|
-
return;
|
|
5537
|
-
}
|
|
5538
|
-
|
|
5539
|
-
const claim = await this.claimPrimarySummaryRetryIntent(
|
|
5540
|
-
retryDispatch.originalRequestId,
|
|
5541
|
-
retryDispatch.intentId,
|
|
5542
|
-
requestId,
|
|
5543
|
-
);
|
|
5544
|
-
if (claim !== 'claimed') {
|
|
5545
|
-
await this.holdPrimarySummaryRequest(
|
|
5546
|
-
agent,
|
|
5547
|
-
retryDispatch.originalRequestId,
|
|
5548
|
-
claim === 'duplicate'
|
|
5549
|
-
? 'primary_summary_fallback_duplicate_retry_blocked'
|
|
5550
|
-
: 'primary_summary_fallback_retry_intent_missing',
|
|
5551
|
-
{ intentId: retryDispatch.intentId, retryRequestId: requestId },
|
|
5552
|
-
);
|
|
5553
|
-
agent.reset();
|
|
5554
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
5555
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
5556
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
5557
|
-
return;
|
|
5558
|
-
}
|
|
5559
|
-
|
|
5560
|
-
if (
|
|
5561
|
-
!branch ||
|
|
5562
|
-
branch.id !== retryDispatch.branch.id ||
|
|
5563
|
-
branch.generation !== retryDispatch.branch.generation
|
|
5564
|
-
) {
|
|
5565
|
-
await this.holdPrimarySummaryRequest(
|
|
5566
|
-
agent,
|
|
5567
|
-
retryDispatch.originalRequestId,
|
|
5568
|
-
'primary_summary_fallback_branch_changed_before_retry',
|
|
5569
|
-
{
|
|
5570
|
-
expectedBranchId: retryDispatch.branch.id,
|
|
5571
|
-
expectedGeneration: retryDispatch.branch.generation,
|
|
5572
|
-
actualBranchId: branch?.id,
|
|
5573
|
-
actualGeneration: branch?.generation,
|
|
5574
|
-
},
|
|
5575
|
-
);
|
|
5576
|
-
agent.reset();
|
|
5577
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
5578
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
5579
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
5580
|
-
return;
|
|
5581
|
-
}
|
|
4025
|
+
this.activeTriggerChannels.delete(agent.name);
|
|
4026
|
+
}
|
|
5582
4027
|
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
);
|
|
5588
|
-
compiledRequest = rebuilt.request;
|
|
5589
|
-
artifacts = rebuilt.artifacts;
|
|
4028
|
+
this.touchEphemeralRun(agent.name, true);
|
|
4029
|
+
this.emitTrace({ type: 'inference:started', agentName: agent.name });
|
|
4030
|
+
this.eventGate?.onInferenceStarted(agent.name);
|
|
4031
|
+
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), startedAt: Date.now() });
|
|
5590
4032
|
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
retryDispatch.originalProjectionKeys.join('\u0000')
|
|
5596
|
-
) {
|
|
5597
|
-
await this.holdPrimarySummaryRequest(
|
|
5598
|
-
agent,
|
|
5599
|
-
retryDispatch.originalRequestId,
|
|
5600
|
-
'primary_summary_fallback_revalidation_failed_before_retry',
|
|
5601
|
-
{
|
|
5602
|
-
expectedRetryRequestHash: retryDispatch.expectedRetryRequestHash,
|
|
5603
|
-
actualRetryRequestHash: rebuiltHash,
|
|
5604
|
-
},
|
|
5605
|
-
);
|
|
5606
|
-
agent.reset();
|
|
5607
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
5608
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
5609
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
5610
|
-
return;
|
|
5611
|
-
}
|
|
4033
|
+
try {
|
|
4034
|
+
const requestSnapshot = this.captureInferenceToolSnapshot(agent);
|
|
4035
|
+
const allTools = this.getToolsForAgent(agent.name, requestSnapshot);
|
|
4036
|
+
const tools = allTools.filter((t) => agent.canUseTool(t.name));
|
|
5612
4037
|
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
'primary_summary_fallback_retry',
|
|
5617
|
-
compiledRequest,
|
|
5618
|
-
artifacts,
|
|
5619
|
-
branch,
|
|
5620
|
-
undefined,
|
|
5621
|
-
false,
|
|
5622
|
-
0,
|
|
5623
|
-
undefined,
|
|
5624
|
-
'pending',
|
|
5625
|
-
);
|
|
5626
|
-
await this.persistPrimarySummaryRequestRecord(retryRecord);
|
|
4038
|
+
// Gather context from modules (pull-based) and MCPL hooks (push-based)
|
|
4039
|
+
// Both produce ContextInjection[] that get merged before inference.
|
|
4040
|
+
let injections: ContextInjection[] | undefined;
|
|
5627
4041
|
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
4042
|
+
// Module gatherContext (fail-open, per-module timeout — the module's
|
|
4043
|
+
// contextTimeoutMs, else the registry default). Injections are
|
|
4044
|
+
// channel-scoped via scopeInjectionsForAgent, matching the MCPL hook
|
|
4045
|
+
// injections below, so conversation forks don't see cross-channel
|
|
4046
|
+
// content.
|
|
4047
|
+
try {
|
|
4048
|
+
const moduleInjections = this.scopeInjectionsForAgent(
|
|
4049
|
+
agent.name,
|
|
4050
|
+
await this.moduleRegistry.gatherContext(agent.name),
|
|
5632
4051
|
);
|
|
5633
|
-
if (
|
|
5634
|
-
|
|
5635
|
-
agent,
|
|
5636
|
-
requestId,
|
|
5637
|
-
'primary_summary_fallback_retry_over_budget',
|
|
5638
|
-
{
|
|
5639
|
-
admittedTokens,
|
|
5640
|
-
requestBudgetTokens: retryDispatch.requestBudgetTokens,
|
|
5641
|
-
retryRequestHash: rebuiltHash,
|
|
5642
|
-
},
|
|
5643
|
-
);
|
|
5644
|
-
await this.updatePrimarySummaryRequestRecord(retryDispatch.originalRequestId, (record) => ({
|
|
5645
|
-
...record,
|
|
5646
|
-
fallbackStatus: 'held',
|
|
5647
|
-
fallbackHeldReason: 'primary_summary_fallback_retry_over_budget',
|
|
5648
|
-
}));
|
|
5649
|
-
agent.reset();
|
|
5650
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
5651
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
5652
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
5653
|
-
return;
|
|
4052
|
+
if (moduleInjections.length > 0) {
|
|
4053
|
+
injections = moduleInjections;
|
|
5654
4054
|
}
|
|
5655
|
-
}
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
// contextTimeoutMs, else the registry default). Injections are
|
|
5662
|
-
// channel-scoped via scopeInjectionsForAgent, matching the MCPL hook
|
|
5663
|
-
// injections below, so conversation forks don't see cross-channel
|
|
5664
|
-
// content.
|
|
4055
|
+
} catch (error) {
|
|
4056
|
+
console.error('Module gatherContext error:', error);
|
|
4057
|
+
}
|
|
4058
|
+
|
|
4059
|
+
// MCPL beforeInference hooks (fail-open)
|
|
4060
|
+
if (this.hookOrchestrator) {
|
|
5665
4061
|
try {
|
|
5666
|
-
const
|
|
4062
|
+
const hookParams = this.buildBeforeInferenceParams(agent, trigger);
|
|
4063
|
+
const hookInjections = this.scopeInjectionsForAgent(
|
|
5667
4064
|
agent.name,
|
|
5668
|
-
await this.
|
|
4065
|
+
await this.hookOrchestrator.beforeInference(hookParams),
|
|
5669
4066
|
);
|
|
5670
|
-
if (
|
|
5671
|
-
injections =
|
|
4067
|
+
if (hookInjections.length > 0) {
|
|
4068
|
+
injections = injections ? [...injections, ...hookInjections] : hookInjections;
|
|
5672
4069
|
}
|
|
5673
4070
|
} catch (error) {
|
|
5674
|
-
console.error('
|
|
5675
|
-
}
|
|
5676
|
-
|
|
5677
|
-
// MCPL beforeInference hooks (fail-open)
|
|
5678
|
-
if (this.hookOrchestrator) {
|
|
5679
|
-
try {
|
|
5680
|
-
const hookParams = this.buildBeforeInferenceParams(agent, trigger);
|
|
5681
|
-
const hookInjections = this.scopeInjectionsForAgent(
|
|
5682
|
-
agent.name,
|
|
5683
|
-
await this.hookOrchestrator.beforeInference(hookParams),
|
|
5684
|
-
);
|
|
5685
|
-
if (hookInjections.length > 0) {
|
|
5686
|
-
injections = injections ? [...injections, ...hookInjections] : hookInjections;
|
|
5687
|
-
}
|
|
5688
|
-
} catch (error) {
|
|
5689
|
-
console.error('beforeInference hook error:', error);
|
|
5690
|
-
}
|
|
5691
|
-
}
|
|
5692
|
-
|
|
5693
|
-
const prepared = await agent.prepareActivationRequest(tools, injections);
|
|
5694
|
-
compiledRequest = prepared.request;
|
|
5695
|
-
artifacts = prepared.artifacts;
|
|
5696
|
-
branch = agent.getCurrentBranchGeneration();
|
|
5697
|
-
|
|
5698
|
-
if (fallbackConfig.enabled) {
|
|
5699
|
-
const record = this.buildPrimarySummaryRequestRecord(
|
|
5700
|
-
agent,
|
|
5701
|
-
requestId,
|
|
5702
|
-
'primary',
|
|
5703
|
-
compiledRequest,
|
|
5704
|
-
artifacts,
|
|
5705
|
-
branch,
|
|
5706
|
-
undefined,
|
|
5707
|
-
false,
|
|
5708
|
-
0,
|
|
5709
|
-
undefined,
|
|
5710
|
-
'pending',
|
|
5711
|
-
);
|
|
5712
|
-
await this.persistPrimarySummaryRequestRecord(record);
|
|
5713
|
-
const expandedByQuarantine =
|
|
5714
|
-
artifacts.compileResult.primarySummaryProjection?.selectedSummaries.some(
|
|
5715
|
-
(selection) => selection.renderedAs === 'raw_expansion',
|
|
5716
|
-
) ?? false;
|
|
5717
|
-
if (expandedByQuarantine) {
|
|
5718
|
-
const admittedTokens = this.estimateRequestCompleteBoundTokens(compiledRequest);
|
|
5719
|
-
if (admittedTokens > fallbackConfig.requestBudgetTokens) {
|
|
5720
|
-
await this.holdPrimarySummaryRequest(
|
|
5721
|
-
agent,
|
|
5722
|
-
requestId,
|
|
5723
|
-
'primary_summary_quarantine_over_budget',
|
|
5724
|
-
{
|
|
5725
|
-
admittedTokens,
|
|
5726
|
-
requestBudgetTokens: fallbackConfig.requestBudgetTokens,
|
|
5727
|
-
},
|
|
5728
|
-
);
|
|
5729
|
-
agent.reset();
|
|
5730
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
5731
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
5732
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
5733
|
-
return;
|
|
5734
|
-
}
|
|
5735
|
-
}
|
|
4071
|
+
console.error('beforeInference hook error:', error);
|
|
5736
4072
|
}
|
|
5737
4073
|
}
|
|
5738
4074
|
|
|
5739
|
-
const { stream } = agent.
|
|
5740
|
-
|
|
5741
|
-
requestId,
|
|
5742
|
-
trigger,
|
|
5743
|
-
compiledRequest,
|
|
5744
|
-
artifacts,
|
|
5745
|
-
});
|
|
4075
|
+
const { stream, request: compiledRequest } = await agent.startStreamWithInjections(tools, injections);
|
|
4076
|
+
|
|
5746
4077
|
const handle = this.driveStream(
|
|
5747
4078
|
agent,
|
|
5748
4079
|
stream,
|
|
5749
4080
|
requestSnapshot,
|
|
5750
|
-
requestId,
|
|
5751
4081
|
trigger,
|
|
5752
4082
|
attempt,
|
|
5753
4083
|
compiledRequest,
|
|
5754
|
-
artifacts,
|
|
5755
4084
|
);
|
|
5756
4085
|
this.activeStreams.set(agent.name, handle);
|
|
5757
4086
|
} catch (error) {
|
|
5758
4087
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
5759
|
-
if (trigger?.primarySummaryFallbackRetry) {
|
|
5760
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
5761
|
-
...record,
|
|
5762
|
-
finalStatus: 'held',
|
|
5763
|
-
fallbackHeldReason: err.message,
|
|
5764
|
-
}));
|
|
5765
|
-
await this.updatePrimarySummaryRequestRecord(trigger.primarySummaryFallbackRetry.originalRequestId, (record) => ({
|
|
5766
|
-
...record,
|
|
5767
|
-
finalStatus: 'held',
|
|
5768
|
-
fallbackStatus: 'held',
|
|
5769
|
-
fallbackHeldReason: err.message,
|
|
5770
|
-
}));
|
|
5771
|
-
this.opsAlert('refusal-held', agent.name, err.message, {
|
|
5772
|
-
data: { requestId, originalRequestId: trigger.primarySummaryFallbackRetry.originalRequestId },
|
|
5773
|
-
});
|
|
5774
|
-
agent.reset();
|
|
5775
|
-
this.settleAgent(agent.name, {
|
|
5776
|
-
stopReason: 'completed',
|
|
5777
|
-
speech: '',
|
|
5778
|
-
});
|
|
5779
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
5780
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
5781
|
-
return;
|
|
5782
|
-
}
|
|
5783
4088
|
this.emitTrace({
|
|
5784
4089
|
type: 'inference:failed',
|
|
5785
4090
|
agentName: agent.name,
|
|
@@ -5823,23 +4128,14 @@ export class AgentFramework {
|
|
|
5823
4128
|
agent: Agent,
|
|
5824
4129
|
stream: YieldingStream,
|
|
5825
4130
|
requestSnapshot: InferenceToolSnapshot,
|
|
5826
|
-
|
|
5827
|
-
trigger?: PendingInferenceRequest,
|
|
4131
|
+
trigger?: InferenceRequest,
|
|
5828
4132
|
attempt = 0,
|
|
5829
|
-
compiledRequest?: NormalizedRequest
|
|
5830
|
-
artifacts?: ActivationCompileArtifacts,
|
|
4133
|
+
compiledRequest?: NormalizedRequest
|
|
5831
4134
|
): Promise<void> {
|
|
5832
4135
|
const startTime = Date.now();
|
|
4136
|
+
const requestId = `${agent.name}-${startTime}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5833
4137
|
const myStreamId = agent.streamId;
|
|
5834
4138
|
let hadToolCalls = false;
|
|
5835
|
-
let executedToolCalls = 0;
|
|
5836
|
-
const primarySummaryConfig = this.primarySummaryFallbackConfig(agent);
|
|
5837
|
-
const primarySummaryRetry = trigger?.primarySummaryFallbackRetry;
|
|
5838
|
-
let primarySummaryQuarantinePersisted = false;
|
|
5839
|
-
const fallbackRetryRequestForLog =
|
|
5840
|
-
primarySummaryRetry && compiledRequest
|
|
5841
|
-
? this.summarizePrimarySummaryRetryRequest(compiledRequest)
|
|
5842
|
-
: compiledRequest ?? { note: 'streaming request' };
|
|
5843
4139
|
|
|
5844
4140
|
// ---- Present-while-acting turn state ---------------------------------
|
|
5845
4141
|
// Output locus for the current CONVERSATIONAL ROUND: resolved lazily and
|
|
@@ -5848,7 +4144,7 @@ export class AgentFramework {
|
|
|
5848
4144
|
// single provider inference can now contain several conversations. The
|
|
5849
4145
|
// pin lives in `turnLocusPins` so it survives a context-budget restart. A
|
|
5850
4146
|
// null resolution is not pinned.
|
|
5851
|
-
if (trigger?.reason !== 'context_budget_restart'
|
|
4147
|
+
if (trigger?.reason !== 'context_budget_restart') {
|
|
5852
4148
|
this.turnLocusPins.delete(agent.name);
|
|
5853
4149
|
this.midTurnRoutingResets.delete(agent.name);
|
|
5854
4150
|
}
|
|
@@ -5920,104 +4216,6 @@ export class AgentFramework {
|
|
|
5920
4216
|
}
|
|
5921
4217
|
};
|
|
5922
4218
|
|
|
5923
|
-
const persistPrimarySummaryRetryQuarantine = async (): Promise<boolean> => {
|
|
5924
|
-
if (!primarySummaryRetry || primarySummaryQuarantinePersisted || !artifacts) return true;
|
|
5925
|
-
const validation = await this.validatePrimarySummaryRetryMutation(
|
|
5926
|
-
agent,
|
|
5927
|
-
requestId,
|
|
5928
|
-
primarySummaryRetry,
|
|
5929
|
-
compiledRequest!,
|
|
5930
|
-
artifacts,
|
|
5931
|
-
'before_quarantine',
|
|
5932
|
-
);
|
|
5933
|
-
if (!validation.ok) {
|
|
5934
|
-
await this.holdPrimarySummaryRetryFamily(
|
|
5935
|
-
agent,
|
|
5936
|
-
requestId,
|
|
5937
|
-
primarySummaryRetry,
|
|
5938
|
-
validation.reason,
|
|
5939
|
-
validation.data,
|
|
5940
|
-
{ emitChronicleMarker: false },
|
|
5941
|
-
);
|
|
5942
|
-
stream.cancel();
|
|
5943
|
-
agent.reset();
|
|
5944
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
5945
|
-
return false;
|
|
5946
|
-
}
|
|
5947
|
-
try {
|
|
5948
|
-
const quarantinePrimarySummaryForPrimaryLane =
|
|
5949
|
-
agent.getContextManager().quarantinePrimarySummaryForPrimaryLane;
|
|
5950
|
-
if (typeof quarantinePrimarySummaryForPrimaryLane !== 'function') {
|
|
5951
|
-
throw new Error('Primary summary quarantine is unavailable in the active context strategy');
|
|
5952
|
-
}
|
|
5953
|
-
await quarantinePrimarySummaryForPrimaryLane.call(
|
|
5954
|
-
agent.getContextManager(),
|
|
5955
|
-
artifacts.contract,
|
|
5956
|
-
primarySummaryRetry.candidateSummaries,
|
|
5957
|
-
);
|
|
5958
|
-
primarySummaryQuarantinePersisted = true;
|
|
5959
|
-
await this.recordPrimarySummaryRetryQuarantine(
|
|
5960
|
-
primarySummaryRetry.originalRequestId,
|
|
5961
|
-
primarySummaryRetry.intentId,
|
|
5962
|
-
);
|
|
5963
|
-
return true;
|
|
5964
|
-
} catch (error) {
|
|
5965
|
-
const reason = 'primary_summary_fallback_quarantine_failed';
|
|
5966
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, reason, {
|
|
5967
|
-
error: error instanceof Error ? error.message : String(error),
|
|
5968
|
-
});
|
|
5969
|
-
stream.cancel();
|
|
5970
|
-
agent.reset();
|
|
5971
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
5972
|
-
return false;
|
|
5973
|
-
}
|
|
5974
|
-
};
|
|
5975
|
-
|
|
5976
|
-
const persistPrimarySummaryRetryOutput = async (
|
|
5977
|
-
phase: string,
|
|
5978
|
-
blocks: ContentBlock[],
|
|
5979
|
-
participant = agent.name,
|
|
5980
|
-
metadata?: MessageMetadata,
|
|
5981
|
-
): Promise<boolean> => {
|
|
5982
|
-
if (!primarySummaryRetry || !artifacts) return true;
|
|
5983
|
-
if (blocks.length === 0) return true;
|
|
5984
|
-
const validation = await this.validatePrimarySummaryRetryMutation(
|
|
5985
|
-
agent,
|
|
5986
|
-
requestId,
|
|
5987
|
-
primarySummaryRetry,
|
|
5988
|
-
compiledRequest!,
|
|
5989
|
-
artifacts,
|
|
5990
|
-
`before_${phase}`,
|
|
5991
|
-
);
|
|
5992
|
-
if (!validation.ok) {
|
|
5993
|
-
await this.holdPrimarySummaryRetryFamily(
|
|
5994
|
-
agent,
|
|
5995
|
-
requestId,
|
|
5996
|
-
primarySummaryRetry,
|
|
5997
|
-
validation.reason,
|
|
5998
|
-
validation.data,
|
|
5999
|
-
{ emitChronicleMarker: false },
|
|
6000
|
-
);
|
|
6001
|
-
stream.cancel();
|
|
6002
|
-
agent.reset();
|
|
6003
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
6004
|
-
return false;
|
|
6005
|
-
}
|
|
6006
|
-
const messageId = agent.getContextManager().addMessage(participant, blocks, metadata);
|
|
6007
|
-
await this.recordPrimarySummaryRetryOutput(
|
|
6008
|
-
primarySummaryRetry.originalRequestId,
|
|
6009
|
-
primarySummaryRetry.intentId,
|
|
6010
|
-
{
|
|
6011
|
-
phase,
|
|
6012
|
-
contentHash: this.primarySummaryFallbackOutputPhaseHash(blocks),
|
|
6013
|
-
messageId,
|
|
6014
|
-
blockTypes: this.primarySummaryFallbackBlockTypes(blocks),
|
|
6015
|
-
persistedAt: Date.now(),
|
|
6016
|
-
},
|
|
6017
|
-
);
|
|
6018
|
-
return true;
|
|
6019
|
-
};
|
|
6020
|
-
|
|
6021
4219
|
try {
|
|
6022
4220
|
for await (const event of stream) {
|
|
6023
4221
|
this.touchEphemeralRun(agent.name, true);
|
|
@@ -6047,6 +4245,7 @@ export class AgentFramework {
|
|
|
6047
4245
|
case 'tool-calls': {
|
|
6048
4246
|
adoptInjectedRound();
|
|
6049
4247
|
hadToolCalls = true;
|
|
4248
|
+
this.recordEphemeralToolCalls(agent.name, event.calls.length);
|
|
6050
4249
|
this.emitTrace({
|
|
6051
4250
|
type: 'inference:tool_calls_yielded',
|
|
6052
4251
|
agentName: agent.name,
|
|
@@ -6085,51 +4284,7 @@ export class AgentFramework {
|
|
|
6085
4284
|
|
|
6086
4285
|
agent.enterWaitingForTools(event.calls, stream);
|
|
6087
4286
|
|
|
6088
|
-
if (!(await persistPrimarySummaryRetryQuarantine())) {
|
|
6089
|
-
return;
|
|
6090
|
-
}
|
|
6091
|
-
|
|
6092
|
-
if (primarySummaryRetry) {
|
|
6093
|
-
const phase = `tool_round_${event.context.depth ?? 0}`;
|
|
6094
|
-
if (!(await persistPrimarySummaryRetryOutput(phase, assistantBlocks))) {
|
|
6095
|
-
return;
|
|
6096
|
-
}
|
|
6097
|
-
this.pendingAssistantBlocks.delete(agent.name);
|
|
6098
|
-
}
|
|
6099
|
-
|
|
6100
|
-
executedToolCalls += event.calls.length;
|
|
6101
|
-
this.recordEphemeralToolCalls(agent.name, event.calls.length);
|
|
6102
|
-
|
|
6103
4287
|
for (const call of event.calls) {
|
|
6104
|
-
if (primarySummaryRetry) {
|
|
6105
|
-
const validation = await this.validatePrimarySummaryRetryMutation(
|
|
6106
|
-
agent,
|
|
6107
|
-
requestId,
|
|
6108
|
-
primarySummaryRetry,
|
|
6109
|
-
compiledRequest!,
|
|
6110
|
-
artifacts!,
|
|
6111
|
-
`before_tool_dispatch_${call.id}`,
|
|
6112
|
-
);
|
|
6113
|
-
if (!validation.ok) {
|
|
6114
|
-
await this.holdPrimarySummaryRetryFamily(
|
|
6115
|
-
agent,
|
|
6116
|
-
requestId,
|
|
6117
|
-
primarySummaryRetry,
|
|
6118
|
-
validation.reason,
|
|
6119
|
-
validation.data,
|
|
6120
|
-
{ emitChronicleMarker: false },
|
|
6121
|
-
);
|
|
6122
|
-
stream.cancel();
|
|
6123
|
-
agent.reset();
|
|
6124
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
6125
|
-
return;
|
|
6126
|
-
}
|
|
6127
|
-
await this.recordPrimarySummaryRetryToolDispatch(
|
|
6128
|
-
primarySummaryRetry.originalRequestId,
|
|
6129
|
-
primarySummaryRetry.intentId,
|
|
6130
|
-
call,
|
|
6131
|
-
);
|
|
6132
|
-
}
|
|
6133
4288
|
this.dispatchToolCall(agent.name, call);
|
|
6134
4289
|
}
|
|
6135
4290
|
|
|
@@ -6222,213 +4377,6 @@ export class AgentFramework {
|
|
|
6222
4377
|
}
|
|
6223
4378
|
}
|
|
6224
4379
|
|
|
6225
|
-
const lastToolIdx = response.content.reduce(
|
|
6226
|
-
(last: number, b: ContentBlock, i: number) =>
|
|
6227
|
-
b.type === 'tool_use' || b.type === 'tool_result' ? i : last,
|
|
6228
|
-
-1
|
|
6229
|
-
);
|
|
6230
|
-
const terminalContent = lastToolIdx >= 0
|
|
6231
|
-
? response.content.slice(lastToolIdx + 1)
|
|
6232
|
-
: response.content;
|
|
6233
|
-
const heldAssistantContent = response.content;
|
|
6234
|
-
const visibleAssistantOutput = this.hasVisibleAssistantOutput(response.content);
|
|
6235
|
-
const providerInputTokens =
|
|
6236
|
-
response.usage?.inputTokens ??
|
|
6237
|
-
response.details?.usage?.inputTokens;
|
|
6238
|
-
|
|
6239
|
-
if (primarySummaryConfig.enabled && compiledRequest && artifacts && response.stopReason === 'refusal') {
|
|
6240
|
-
const stopDetails = (response.raw?.response as {
|
|
6241
|
-
stop_details?: { category?: string; explanation?: string };
|
|
6242
|
-
} | undefined)?.stop_details;
|
|
6243
|
-
const category = stopDetails?.category ?? 'unknown';
|
|
6244
|
-
console.error(
|
|
6245
|
-
`[inference-refusal] agent=${agent.name} category=${category}` +
|
|
6246
|
-
(stopDetails?.explanation ? ` explanation=${stopDetails.explanation}` : ''),
|
|
6247
|
-
);
|
|
6248
|
-
this.noteRefusal(agent.name, category, response.details?.usage
|
|
6249
|
-
? {
|
|
6250
|
-
input: response.details.usage.inputTokens,
|
|
6251
|
-
output: response.details.usage.outputTokens,
|
|
6252
|
-
}
|
|
6253
|
-
: undefined);
|
|
6254
|
-
|
|
6255
|
-
const currentRecord = await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
6256
|
-
...record,
|
|
6257
|
-
stopReason: response.stopReason,
|
|
6258
|
-
providerInputTokens,
|
|
6259
|
-
visibleAssistantOutput,
|
|
6260
|
-
executedToolCalls,
|
|
6261
|
-
finalStatus: 'refusal',
|
|
6262
|
-
}));
|
|
6263
|
-
|
|
6264
|
-
const holdReasonData = {
|
|
6265
|
-
category,
|
|
6266
|
-
visibleAssistantOutput,
|
|
6267
|
-
executedToolCalls,
|
|
6268
|
-
candidateLimit: primarySummaryConfig.maxNewSummaries,
|
|
6269
|
-
};
|
|
6270
|
-
|
|
6271
|
-
if (primarySummaryRetry) {
|
|
6272
|
-
const reason = visibleAssistantOutput
|
|
6273
|
-
? 'primary_summary_fallback_retry_partial_output'
|
|
6274
|
-
: executedToolCalls > 0
|
|
6275
|
-
? 'primary_summary_fallback_retry_tool_already_executed'
|
|
6276
|
-
: 'primary_summary_fallback_retry_refusal';
|
|
6277
|
-
if (visibleAssistantOutput || executedToolCalls > 0) {
|
|
6278
|
-
await this.persistPrimarySummaryHeldOutput(agent, requestId, reason, {
|
|
6279
|
-
stopReason: response.stopReason,
|
|
6280
|
-
providerInputTokens,
|
|
6281
|
-
visibleAssistantOutput,
|
|
6282
|
-
executedToolCalls,
|
|
6283
|
-
assistantBlocks: heldAssistantContent,
|
|
6284
|
-
retryDispatch: primarySummaryRetry,
|
|
6285
|
-
retryPhase: 'refusal_visible_output',
|
|
6286
|
-
holdData: holdReasonData,
|
|
6287
|
-
});
|
|
6288
|
-
} else {
|
|
6289
|
-
await this.holdPrimarySummaryRetryFamily(
|
|
6290
|
-
agent,
|
|
6291
|
-
requestId,
|
|
6292
|
-
primarySummaryRetry,
|
|
6293
|
-
reason,
|
|
6294
|
-
holdReasonData,
|
|
6295
|
-
);
|
|
6296
|
-
}
|
|
6297
|
-
} else if (visibleAssistantOutput || executedToolCalls > 0 || !currentRecord?.projection) {
|
|
6298
|
-
const reason = visibleAssistantOutput
|
|
6299
|
-
? 'primary_summary_refusal_partial_output'
|
|
6300
|
-
: executedToolCalls > 0
|
|
6301
|
-
? 'primary_summary_refusal_tool_already_executed'
|
|
6302
|
-
: 'primary_summary_refusal_no_projection';
|
|
6303
|
-
if (visibleAssistantOutput || executedToolCalls > 0) {
|
|
6304
|
-
await this.persistPrimarySummaryHeldOutput(agent, requestId, reason, {
|
|
6305
|
-
stopReason: response.stopReason,
|
|
6306
|
-
providerInputTokens,
|
|
6307
|
-
visibleAssistantOutput,
|
|
6308
|
-
executedToolCalls,
|
|
6309
|
-
assistantBlocks: heldAssistantContent,
|
|
6310
|
-
holdData: holdReasonData,
|
|
6311
|
-
});
|
|
6312
|
-
} else {
|
|
6313
|
-
await this.holdPrimarySummaryRequest(agent, requestId, reason, holdReasonData);
|
|
6314
|
-
}
|
|
6315
|
-
} else {
|
|
6316
|
-
const state = this.readPrimarySummaryFallbackState();
|
|
6317
|
-
const baseline = this.latestCompatibleHealthyPrimaryRequest(currentRecord, state);
|
|
6318
|
-
if (!baseline?.projection) {
|
|
6319
|
-
await this.holdPrimarySummaryRequest(
|
|
6320
|
-
agent,
|
|
6321
|
-
requestId,
|
|
6322
|
-
'primary_summary_refusal_no_compatible_healthy_baseline',
|
|
6323
|
-
holdReasonData,
|
|
6324
|
-
);
|
|
6325
|
-
} else {
|
|
6326
|
-
const baselineKeys = new Set(this.primarySummaryProjectionKeys(baseline.projection));
|
|
6327
|
-
const candidates = currentRecord.projection.selectedSummaries
|
|
6328
|
-
.filter((selection) => !baselineKeys.has(this.primarySummaryIdentityKey(selection.identity)))
|
|
6329
|
-
.map((selection) => selection.identity);
|
|
6330
|
-
if (candidates.length === 0) {
|
|
6331
|
-
await this.holdPrimarySummaryRequest(
|
|
6332
|
-
agent,
|
|
6333
|
-
requestId,
|
|
6334
|
-
'primary_summary_refusal_no_newly_admitted_candidates',
|
|
6335
|
-
holdReasonData,
|
|
6336
|
-
);
|
|
6337
|
-
} else if (candidates.length > primarySummaryConfig.maxNewSummaries) {
|
|
6338
|
-
await this.holdPrimarySummaryRequest(
|
|
6339
|
-
agent,
|
|
6340
|
-
requestId,
|
|
6341
|
-
'primary_summary_refusal_too_many_candidates',
|
|
6342
|
-
{ ...holdReasonData, candidateCount: candidates.length },
|
|
6343
|
-
);
|
|
6344
|
-
} else {
|
|
6345
|
-
try {
|
|
6346
|
-
const retry = agent.buildPrimarySummaryRawExpansionRequest(
|
|
6347
|
-
artifacts,
|
|
6348
|
-
candidates,
|
|
6349
|
-
compiledRequest.tools ?? [],
|
|
6350
|
-
);
|
|
6351
|
-
const retryRequestHash = this.hashJson(retry.request);
|
|
6352
|
-
const admittedTokens = this.primarySummaryAdmissionBoundTokens(
|
|
6353
|
-
currentRecord.requestInputBoundTokens,
|
|
6354
|
-
retry.request,
|
|
6355
|
-
providerInputTokens,
|
|
6356
|
-
);
|
|
6357
|
-
if (admittedTokens > primarySummaryConfig.requestBudgetTokens) {
|
|
6358
|
-
await this.holdPrimarySummaryRequest(
|
|
6359
|
-
agent,
|
|
6360
|
-
requestId,
|
|
6361
|
-
'primary_summary_refusal_retry_over_budget',
|
|
6362
|
-
{
|
|
6363
|
-
...holdReasonData,
|
|
6364
|
-
admittedTokens,
|
|
6365
|
-
requestBudgetTokens: primarySummaryConfig.requestBudgetTokens,
|
|
6366
|
-
},
|
|
6367
|
-
);
|
|
6368
|
-
} else {
|
|
6369
|
-
const intentId = `${requestId}:fallback:${Math.random().toString(36).slice(2, 8)}`;
|
|
6370
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
6371
|
-
...record,
|
|
6372
|
-
fallbackIntent: {
|
|
6373
|
-
intentId,
|
|
6374
|
-
createdAt: Date.now(),
|
|
6375
|
-
baselineRequestId: baseline.requestId,
|
|
6376
|
-
candidateSummaries: candidates,
|
|
6377
|
-
retryRequestHash,
|
|
6378
|
-
retryCompleteBoundTokens: admittedTokens,
|
|
6379
|
-
requestBudgetTokens: primarySummaryConfig.requestBudgetTokens,
|
|
6380
|
-
},
|
|
6381
|
-
fallbackStatus: 'pending',
|
|
6382
|
-
}));
|
|
6383
|
-
this.pendingRequests.push({
|
|
6384
|
-
agentName: agent.name,
|
|
6385
|
-
reason: 'primary-summary-fallback-retry',
|
|
6386
|
-
source: 'framework',
|
|
6387
|
-
timestamp: Date.now(),
|
|
6388
|
-
channelId: trigger?.channelId,
|
|
6389
|
-
primarySummaryFallbackRetry: {
|
|
6390
|
-
originalRequestId: requestId,
|
|
6391
|
-
baselineRequestId: baseline.requestId,
|
|
6392
|
-
intentId,
|
|
6393
|
-
branch: {
|
|
6394
|
-
id: currentRecord.branch.id,
|
|
6395
|
-
generation: currentRecord.branch.generation,
|
|
6396
|
-
},
|
|
6397
|
-
candidateSummaries: candidates,
|
|
6398
|
-
originalArtifacts: artifacts,
|
|
6399
|
-
originalProjectionKeys: this.primarySummaryProjectionKeys(currentRecord.projection),
|
|
6400
|
-
expectedRetryRequestHash: retryRequestHash,
|
|
6401
|
-
requestBudgetTokens: primarySummaryConfig.requestBudgetTokens,
|
|
6402
|
-
originalRequestInputBoundTokens: currentRecord.requestInputBoundTokens,
|
|
6403
|
-
originalProviderInputTokens: providerInputTokens,
|
|
6404
|
-
},
|
|
6405
|
-
});
|
|
6406
|
-
}
|
|
6407
|
-
} catch (error) {
|
|
6408
|
-
await this.holdPrimarySummaryRequest(
|
|
6409
|
-
agent,
|
|
6410
|
-
requestId,
|
|
6411
|
-
'primary_summary_refusal_retry_build_failed',
|
|
6412
|
-
{
|
|
6413
|
-
...holdReasonData,
|
|
6414
|
-
error: error instanceof Error ? error.message : String(error),
|
|
6415
|
-
},
|
|
6416
|
-
);
|
|
6417
|
-
}
|
|
6418
|
-
}
|
|
6419
|
-
}
|
|
6420
|
-
}
|
|
6421
|
-
|
|
6422
|
-
agent.reset();
|
|
6423
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
6424
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
6425
|
-
continue;
|
|
6426
|
-
}
|
|
6427
|
-
|
|
6428
|
-
if (!(await persistPrimarySummaryRetryQuarantine())) {
|
|
6429
|
-
return;
|
|
6430
|
-
}
|
|
6431
|
-
|
|
6432
4380
|
// Add assistant response to context.
|
|
6433
4381
|
// If we had tool calls, each round's blocks (thinking + text +
|
|
6434
4382
|
// tool_use) were already stored as pendingAssistantBlocks and
|
|
@@ -6437,52 +4385,20 @@ export class AgentFramework {
|
|
|
6437
4385
|
// double-store earlier rounds' text/thinking AND detach signed
|
|
6438
4386
|
// thinking blocks from their tool_use turn (the API requires
|
|
6439
4387
|
// thinking to precede tool_use in the same assistant turn).
|
|
4388
|
+
const lastToolIdx = response.content.reduce(
|
|
4389
|
+
(last: number, b: ContentBlock, i: number) =>
|
|
4390
|
+
b.type === 'tool_use' || b.type === 'tool_result' ? i : last,
|
|
4391
|
+
-1
|
|
4392
|
+
);
|
|
4393
|
+
const terminalContent = lastToolIdx >= 0
|
|
4394
|
+
? response.content.slice(lastToolIdx + 1)
|
|
4395
|
+
: response.content;
|
|
6440
4396
|
if (lastToolIdx >= 0) {
|
|
6441
4397
|
if (terminalContent.length > 0) {
|
|
6442
|
-
if (primarySummaryRetry) {
|
|
6443
|
-
const retryRecord = await this.getPrimarySummaryRequestRecord(requestId);
|
|
6444
|
-
const terminalMetadata = retryRecord
|
|
6445
|
-
? this.buildPrimarySummarySettlementMetadata(retryRecord, {
|
|
6446
|
-
outcome: 'success',
|
|
6447
|
-
stopReason: response.stopReason,
|
|
6448
|
-
providerInputTokens,
|
|
6449
|
-
visibleAssistantOutput,
|
|
6450
|
-
executedToolCalls,
|
|
6451
|
-
entryIndex: 0,
|
|
6452
|
-
entryCount: 1,
|
|
6453
|
-
role: 'assistant',
|
|
6454
|
-
kind: 'assistant_output',
|
|
6455
|
-
})
|
|
6456
|
-
: undefined;
|
|
6457
|
-
if (!(await persistPrimarySummaryRetryOutput('terminal', terminalContent, agent.name, terminalMetadata))) {
|
|
6458
|
-
return;
|
|
6459
|
-
}
|
|
6460
|
-
} else {
|
|
6461
|
-
agent.addAssistantResponse(terminalContent);
|
|
6462
|
-
}
|
|
6463
|
-
}
|
|
6464
|
-
} else {
|
|
6465
|
-
if (primarySummaryRetry) {
|
|
6466
|
-
const retryRecord = await this.getPrimarySummaryRequestRecord(requestId);
|
|
6467
|
-
const terminalMetadata = retryRecord && terminalContent.length > 0
|
|
6468
|
-
? this.buildPrimarySummarySettlementMetadata(retryRecord, {
|
|
6469
|
-
outcome: 'success',
|
|
6470
|
-
stopReason: response.stopReason,
|
|
6471
|
-
providerInputTokens,
|
|
6472
|
-
visibleAssistantOutput,
|
|
6473
|
-
executedToolCalls,
|
|
6474
|
-
entryIndex: 0,
|
|
6475
|
-
entryCount: 1,
|
|
6476
|
-
role: 'assistant',
|
|
6477
|
-
kind: 'assistant_output',
|
|
6478
|
-
})
|
|
6479
|
-
: undefined;
|
|
6480
|
-
if (!(await persistPrimarySummaryRetryOutput('terminal', terminalContent, agent.name, terminalMetadata))) {
|
|
6481
|
-
return;
|
|
6482
|
-
}
|
|
6483
|
-
} else {
|
|
6484
4398
|
agent.addAssistantResponse(terminalContent);
|
|
6485
4399
|
}
|
|
4400
|
+
} else {
|
|
4401
|
+
agent.addAssistantResponse(terminalContent);
|
|
6486
4402
|
}
|
|
6487
4403
|
|
|
6488
4404
|
// Run afterInference hooks (no-op if no MCPL servers)
|
|
@@ -6544,50 +4460,6 @@ export class AgentFramework {
|
|
|
6544
4460
|
}
|
|
6545
4461
|
: undefined;
|
|
6546
4462
|
|
|
6547
|
-
if (primarySummaryRetry && artifacts) {
|
|
6548
|
-
const validation = await this.validatePrimarySummaryRetryMutation(
|
|
6549
|
-
agent,
|
|
6550
|
-
requestId,
|
|
6551
|
-
primarySummaryRetry,
|
|
6552
|
-
compiledRequest!,
|
|
6553
|
-
artifacts,
|
|
6554
|
-
'before_success_markers',
|
|
6555
|
-
);
|
|
6556
|
-
if (!validation.ok) {
|
|
6557
|
-
await this.holdPrimarySummaryRetryFamily(
|
|
6558
|
-
agent,
|
|
6559
|
-
requestId,
|
|
6560
|
-
primarySummaryRetry,
|
|
6561
|
-
validation.reason,
|
|
6562
|
-
validation.data,
|
|
6563
|
-
{ emitChronicleMarker: false },
|
|
6564
|
-
);
|
|
6565
|
-
return;
|
|
6566
|
-
}
|
|
6567
|
-
}
|
|
6568
|
-
|
|
6569
|
-
if (primarySummaryConfig.enabled) {
|
|
6570
|
-
if (primarySummaryRetry) {
|
|
6571
|
-
await this.markPrimarySummaryRetrySuccess(
|
|
6572
|
-
requestId,
|
|
6573
|
-
primarySummaryRetry,
|
|
6574
|
-
response.stopReason,
|
|
6575
|
-
providerInputTokens,
|
|
6576
|
-
visibleAssistantOutput,
|
|
6577
|
-
executedToolCalls,
|
|
6578
|
-
);
|
|
6579
|
-
} else {
|
|
6580
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
6581
|
-
...record,
|
|
6582
|
-
stopReason: response.stopReason,
|
|
6583
|
-
providerInputTokens,
|
|
6584
|
-
visibleAssistantOutput,
|
|
6585
|
-
executedToolCalls,
|
|
6586
|
-
finalStatus: 'success',
|
|
6587
|
-
}));
|
|
6588
|
-
}
|
|
6589
|
-
}
|
|
6590
|
-
|
|
6591
4463
|
// Reset agent state before emitting inference:completed. Traces are
|
|
6592
4464
|
// observability-only, but external synchronous listeners should
|
|
6593
4465
|
// still see the terminal state at the terminal trace boundary.
|
|
@@ -6626,10 +4498,8 @@ export class AgentFramework {
|
|
|
6626
4498
|
agentName: agent.name,
|
|
6627
4499
|
requestId,
|
|
6628
4500
|
success: true,
|
|
6629
|
-
request:
|
|
6630
|
-
response:
|
|
6631
|
-
? this.summarizePrimarySummaryRetryResponse(response)
|
|
6632
|
-
: response.raw ?? { note: 'streaming response' },
|
|
4501
|
+
request: compiledRequest ?? { note: 'streaming request' },
|
|
4502
|
+
response: response.raw ?? { note: 'streaming response' },
|
|
6633
4503
|
durationMs,
|
|
6634
4504
|
tokenUsage,
|
|
6635
4505
|
stopReason: response.stopReason,
|
|
@@ -6851,30 +4721,13 @@ export class AgentFramework {
|
|
|
6851
4721
|
requestId,
|
|
6852
4722
|
success: false,
|
|
6853
4723
|
error: err.message,
|
|
6854
|
-
request:
|
|
4724
|
+
request: compiledRequest ?? { note: 'streaming request failed' },
|
|
6855
4725
|
durationMs,
|
|
6856
4726
|
});
|
|
6857
4727
|
|
|
6858
4728
|
// Only reset + retry if this is still the active stream
|
|
6859
4729
|
if (agent.streamId !== myStreamId) break;
|
|
6860
4730
|
|
|
6861
|
-
if (primarySummaryRetry) {
|
|
6862
|
-
await this.holdPrimarySummaryRetryFamily(
|
|
6863
|
-
agent,
|
|
6864
|
-
requestId,
|
|
6865
|
-
primarySummaryRetry,
|
|
6866
|
-
'primary_summary_fallback_retry_stream_error',
|
|
6867
|
-
{ error: err.message },
|
|
6868
|
-
);
|
|
6869
|
-
agent.reset();
|
|
6870
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
6871
|
-
this.settleAgent(agent.name, {
|
|
6872
|
-
stopReason: 'completed',
|
|
6873
|
-
speech: '',
|
|
6874
|
-
});
|
|
6875
|
-
return;
|
|
6876
|
-
}
|
|
6877
|
-
|
|
6878
4731
|
agent.reset();
|
|
6879
4732
|
|
|
6880
4733
|
const action = this.errorPolicy.onInferenceError(err, agent.name, attempt);
|
|
@@ -6914,17 +4767,6 @@ export class AgentFramework {
|
|
|
6914
4767
|
// reject an ephemeral's promise mid-run and bump the failure
|
|
6915
4768
|
// streak). Gate release + stream teardown happen in `finally`.
|
|
6916
4769
|
if (this.frameworkCancelledStreams.delete(`${agent.name}:${myStreamId}`)) {
|
|
6917
|
-
if (primarySummaryRetry) {
|
|
6918
|
-
if (!(await this.primarySummaryRetryFamilyAlreadySettled(requestId, primarySummaryRetry))) {
|
|
6919
|
-
await this.holdPrimarySummaryRetryFamily(
|
|
6920
|
-
agent,
|
|
6921
|
-
requestId,
|
|
6922
|
-
primarySummaryRetry,
|
|
6923
|
-
'primary_summary_fallback_retry_cancelled_before_success',
|
|
6924
|
-
{ frameworkCancelled: true },
|
|
6925
|
-
);
|
|
6926
|
-
}
|
|
6927
|
-
}
|
|
6928
4770
|
this.eventGate?.onInferenceEnded(agent.name);
|
|
6929
4771
|
return;
|
|
6930
4772
|
}
|
|
@@ -6933,40 +4775,6 @@ export class AgentFramework {
|
|
|
6933
4775
|
// may have already started a new stream, bumping streamId)
|
|
6934
4776
|
if (agent.streamId === myStreamId) {
|
|
6935
4777
|
const durationMs = Date.now() - startTime;
|
|
6936
|
-
if (primarySummaryRetry) {
|
|
6937
|
-
await this.holdPrimarySummaryRetryFamily(
|
|
6938
|
-
agent,
|
|
6939
|
-
requestId,
|
|
6940
|
-
primarySummaryRetry,
|
|
6941
|
-
`stream_aborted:${reason}`,
|
|
6942
|
-
{ reason },
|
|
6943
|
-
{ stopReason: 'abort' },
|
|
6944
|
-
);
|
|
6945
|
-
agent.reset();
|
|
6946
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
6947
|
-
this.logInference({
|
|
6948
|
-
timestamp: startTime,
|
|
6949
|
-
agentName: agent.name,
|
|
6950
|
-
requestId,
|
|
6951
|
-
success: false,
|
|
6952
|
-
error: `Stream aborted: ${reason}`,
|
|
6953
|
-
request: fallbackRetryRequestForLog,
|
|
6954
|
-
durationMs,
|
|
6955
|
-
});
|
|
6956
|
-
this.settleAgent(agent.name, {
|
|
6957
|
-
stopReason: 'completed',
|
|
6958
|
-
speech: '',
|
|
6959
|
-
});
|
|
6960
|
-
return;
|
|
6961
|
-
}
|
|
6962
|
-
if (primarySummaryConfig.enabled) {
|
|
6963
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
6964
|
-
...record,
|
|
6965
|
-
stopReason: 'abort',
|
|
6966
|
-
finalStatus: 'error',
|
|
6967
|
-
fallbackHeldReason: `stream_aborted:${reason}`,
|
|
6968
|
-
}));
|
|
6969
|
-
}
|
|
6970
4778
|
agent.reset();
|
|
6971
4779
|
this.settleAgent(agent.name, {
|
|
6972
4780
|
stopReason: 'exhausted',
|
|
@@ -6990,7 +4798,7 @@ export class AgentFramework {
|
|
|
6990
4798
|
requestId,
|
|
6991
4799
|
success: false,
|
|
6992
4800
|
error: `Stream aborted: ${reason}`,
|
|
6993
|
-
request:
|
|
4801
|
+
request: compiledRequest ?? { note: 'streaming request aborted' },
|
|
6994
4802
|
durationMs,
|
|
6995
4803
|
});
|
|
6996
4804
|
this.eventGate?.onInferenceEnded(agent.name);
|
|
@@ -7042,63 +4850,12 @@ export class AgentFramework {
|
|
|
7042
4850
|
// inference:exhausted so ephemeral agent promises can settle.
|
|
7043
4851
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
7044
4852
|
const durationMs = Date.now() - startTime;
|
|
7045
|
-
if (primarySummaryRetry) {
|
|
7046
|
-
if (await this.primarySummaryRetryAlreadySucceeded(requestId, primarySummaryRetry)) {
|
|
7047
|
-
this.logInference({
|
|
7048
|
-
timestamp: startTime,
|
|
7049
|
-
agentName: agent.name,
|
|
7050
|
-
requestId,
|
|
7051
|
-
success: true,
|
|
7052
|
-
request: fallbackRetryRequestForLog,
|
|
7053
|
-
response: { kind: 'primary_summary_fallback_retry', postSuccessError: err.message },
|
|
7054
|
-
durationMs,
|
|
7055
|
-
});
|
|
7056
|
-
agent.reset();
|
|
7057
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
7058
|
-
this.settleAgent(agent.name, {
|
|
7059
|
-
stopReason: 'completed',
|
|
7060
|
-
speech: '',
|
|
7061
|
-
});
|
|
7062
|
-
return;
|
|
7063
|
-
}
|
|
7064
|
-
await this.holdPrimarySummaryRetryFamily(
|
|
7065
|
-
agent,
|
|
7066
|
-
requestId,
|
|
7067
|
-
primarySummaryRetry,
|
|
7068
|
-
'primary_summary_fallback_retry_stream_threw',
|
|
7069
|
-
{ error: err.message },
|
|
7070
|
-
);
|
|
7071
|
-
} else if (primarySummaryConfig.enabled) {
|
|
7072
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
7073
|
-
...record,
|
|
7074
|
-
finalStatus: 'error',
|
|
7075
|
-
fallbackHeldReason: err.message,
|
|
7076
|
-
}));
|
|
7077
|
-
}
|
|
7078
4853
|
this.emitTrace({
|
|
7079
4854
|
type: 'inference:failed',
|
|
7080
4855
|
agentName: agent.name,
|
|
7081
4856
|
error: err.message,
|
|
7082
4857
|
stack: err.stack,
|
|
7083
4858
|
});
|
|
7084
|
-
if (primarySummaryRetry) {
|
|
7085
|
-
this.logInference({
|
|
7086
|
-
timestamp: startTime,
|
|
7087
|
-
agentName: agent.name,
|
|
7088
|
-
requestId,
|
|
7089
|
-
success: false,
|
|
7090
|
-
error: `Stream threw: ${err.message}`,
|
|
7091
|
-
request: fallbackRetryRequestForLog,
|
|
7092
|
-
durationMs,
|
|
7093
|
-
});
|
|
7094
|
-
agent.reset();
|
|
7095
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
7096
|
-
this.settleAgent(agent.name, {
|
|
7097
|
-
stopReason: 'completed',
|
|
7098
|
-
speech: '',
|
|
7099
|
-
});
|
|
7100
|
-
return;
|
|
7101
|
-
}
|
|
7102
4859
|
this.settleAgent(agent.name, {
|
|
7103
4860
|
stopReason: 'exhausted',
|
|
7104
4861
|
speech: '',
|
|
@@ -7119,7 +4876,7 @@ export class AgentFramework {
|
|
|
7119
4876
|
requestId,
|
|
7120
4877
|
success: false,
|
|
7121
4878
|
error: `Stream threw: ${err.message}`,
|
|
7122
|
-
request:
|
|
4879
|
+
request: compiledRequest ?? { note: 'streaming request threw' },
|
|
7123
4880
|
durationMs,
|
|
7124
4881
|
});
|
|
7125
4882
|
agent.reset();
|
|
@@ -7140,7 +4897,6 @@ export class AgentFramework {
|
|
|
7140
4897
|
this.channelRegistry?.stopTyping();
|
|
7141
4898
|
this.frameworkCancelledStreams.delete(`${agent.name}:${myStreamId}`);
|
|
7142
4899
|
this.activeStreams.delete(agent.name);
|
|
7143
|
-
this.activeStreamContexts.delete(agent.name);
|
|
7144
4900
|
this.pendingAssistantBlocks.delete(agent.name);
|
|
7145
4901
|
|
|
7146
4902
|
// A conversation fork whose TTL closure turn just finished is done for
|
|
@@ -7415,6 +5171,11 @@ export class AgentFramework {
|
|
|
7415
5171
|
return;
|
|
7416
5172
|
}
|
|
7417
5173
|
|
|
5174
|
+
if (enrichedCall.name === 'read_image') {
|
|
5175
|
+
this.dispatchReadImageToolCall(agentName, enrichedCall);
|
|
5176
|
+
return;
|
|
5177
|
+
}
|
|
5178
|
+
|
|
7418
5179
|
// Route gate_status tool
|
|
7419
5180
|
if (enrichedCall.name === 'gate_status' && this.eventGate) {
|
|
7420
5181
|
this.dispatchGateToolCall(agentName, enrichedCall);
|
|
@@ -7678,23 +5439,12 @@ export class AgentFramework {
|
|
|
7678
5439
|
* work, per-agent status + last inference activity. Cheap and read-only.
|
|
7679
5440
|
*/
|
|
7680
5441
|
healthSnapshot(): Record<string, unknown> {
|
|
7681
|
-
const primarySummary = this.readPrimarySummaryFallbackState();
|
|
7682
5442
|
return {
|
|
7683
5443
|
at: new Date().toISOString(),
|
|
7684
5444
|
uptimeSec: Math.round(process.uptime()),
|
|
7685
5445
|
gate: this.eventGate?.inferenceDiagnostics() ?? null,
|
|
7686
5446
|
pendingRequests: this.pendingRequests.length,
|
|
7687
5447
|
activeStreams: [...this.activeStreams.keys()],
|
|
7688
|
-
primarySummaryFallback: {
|
|
7689
|
-
requests: primarySummary.requests.length,
|
|
7690
|
-
pendingDispatches: primarySummary.requests.filter((record) => record.finalStatus === 'pending').length,
|
|
7691
|
-
unresolvedIntents: primarySummary.requests.filter((record) => record.fallbackStatus === 'pending').length,
|
|
7692
|
-
held: primarySummary.requests.filter((record) =>
|
|
7693
|
-
record.finalStatus === 'held' ||
|
|
7694
|
-
record.finalStatus === 'timeout_unknown' ||
|
|
7695
|
-
record.fallbackStatus === 'held' ||
|
|
7696
|
-
record.fallbackStatus === 'timeout_unknown').length,
|
|
7697
|
-
},
|
|
7698
5448
|
agents: [...this.agents.entries()].map(([name, agent]) => ({
|
|
7699
5449
|
name,
|
|
7700
5450
|
status: agent.state.status,
|
|
@@ -9249,6 +6999,19 @@ export class AgentFramework {
|
|
|
9249
6999
|
.then((result) => {
|
|
9250
7000
|
const durationMs = Date.now() - startTime;
|
|
9251
7001
|
this.emitTrace({ type: 'tool:completed', module: 'channels', tool: call.name, callId: call.id, durationMs });
|
|
7002
|
+
// A successful channel_open plants the agent's feet in the opened
|
|
7003
|
+
// channel: pin it as this turn's reply locus so plain-text speech
|
|
7004
|
+
// written right after opening routes THERE. Found live 2026-07-21
|
|
7005
|
+
// (Aria bring-up): open → plain-text reply → "no locus (no
|
|
7006
|
+
// home/active channel, defaultPublishChannel null)" on a turn with
|
|
7007
|
+
// no channel trigger (post-restart continuation). Next turn start
|
|
7008
|
+
// re-resolves the locus as usual (startAgentStream set/delete).
|
|
7009
|
+
if (call.name === 'channel_open' && result?.success) {
|
|
7010
|
+
const opened =
|
|
7011
|
+
(result.data as { channelId?: string } | undefined)?.channelId
|
|
7012
|
+
?? (call.input as { channelId?: string } | undefined)?.channelId;
|
|
7013
|
+
if (opened) this.activeTriggerChannels.set(agentName, opened);
|
|
7014
|
+
}
|
|
9252
7015
|
this.pushEvent({
|
|
9253
7016
|
type: 'tool-result',
|
|
9254
7017
|
callId: call.id,
|
|
@@ -9411,6 +7174,75 @@ export class AgentFramework {
|
|
|
9411
7174
|
});
|
|
9412
7175
|
}
|
|
9413
7176
|
|
|
7177
|
+
/**
|
|
7178
|
+
* Handle the synthesized `read_image` tool: read image bytes from a
|
|
7179
|
+
* workspace mount and return them as a native MCP-shaped content array
|
|
7180
|
+
* (`[{type:'text'…},{type:'image', data, mimeType}]`). The live-turn
|
|
7181
|
+
* converter (tryNativeToolResultContent → membrane tool_result image
|
|
7182
|
+
* blocks) delivers the actual image to the model; history storage keeps
|
|
7183
|
+
* the standard compact `[image: …]` placeholder.
|
|
7184
|
+
*/
|
|
7185
|
+
private dispatchReadImageToolCall(agentName: string, call: ToolCall): void {
|
|
7186
|
+
this.emitTrace({ type: 'tool:started', module: 'workspace', tool: call.name, callId: call.id, input: call.input });
|
|
7187
|
+
const finish = (result: ToolResult): void => {
|
|
7188
|
+
this.emitTrace({
|
|
7189
|
+
type: result.isError ? 'tool:failed' : 'tool:completed',
|
|
7190
|
+
module: 'workspace',
|
|
7191
|
+
tool: call.name,
|
|
7192
|
+
callId: call.id,
|
|
7193
|
+
durationMs: 0,
|
|
7194
|
+
...(result.isError ? { error: result.error } : {}),
|
|
7195
|
+
});
|
|
7196
|
+
this.pushEvent({ type: 'tool-result', callId: call.id, agentName, moduleName: 'workspace', result });
|
|
7197
|
+
};
|
|
7198
|
+
void (async (): Promise<void> => {
|
|
7199
|
+
try {
|
|
7200
|
+
const workspace = this.getWorkspaceModule();
|
|
7201
|
+
if (!workspace || typeof workspace.readBinary !== 'function') {
|
|
7202
|
+
throw new Error('read_image requires a workspace module');
|
|
7203
|
+
}
|
|
7204
|
+
const input = (call.input ?? {}) as { path?: unknown };
|
|
7205
|
+
if (typeof input.path !== 'string' || input.path.length === 0) {
|
|
7206
|
+
throw new Error('read_image: `path` (mount-prefixed) is required');
|
|
7207
|
+
}
|
|
7208
|
+
const read = await workspace.readBinary(input.path);
|
|
7209
|
+
if ('error' in read) throw new Error(read.error);
|
|
7210
|
+
const mediaType = sniffImageMediaType(read.data);
|
|
7211
|
+
if (!mediaType) {
|
|
7212
|
+
throw new Error(
|
|
7213
|
+
`read_image: "${input.path}" does not look like a supported image ` +
|
|
7214
|
+
'(png/jpeg/gif/webp — checked by magic bytes, not extension)',
|
|
7215
|
+
);
|
|
7216
|
+
}
|
|
7217
|
+
// Anthropic rejects images >5MB (and >8000px); guard the hard byte
|
|
7218
|
+
// limit here — resizing is out of scope without an image library.
|
|
7219
|
+
const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
7220
|
+
if (read.data.byteLength > MAX_IMAGE_BYTES) {
|
|
7221
|
+
throw new Error(
|
|
7222
|
+
`read_image: "${input.path}" is ${read.data.byteLength} bytes — ` +
|
|
7223
|
+
`over the ${MAX_IMAGE_BYTES}-byte model limit`,
|
|
7224
|
+
);
|
|
7225
|
+
}
|
|
7226
|
+
finish({
|
|
7227
|
+
success: true,
|
|
7228
|
+
data: [
|
|
7229
|
+
{
|
|
7230
|
+
type: 'text',
|
|
7231
|
+
text: `${input.path} (${mediaType}, ${Math.ceil(read.data.byteLength / 1024)} KB):`,
|
|
7232
|
+
},
|
|
7233
|
+
{ type: 'image', data: read.data.toString('base64'), mimeType: mediaType },
|
|
7234
|
+
],
|
|
7235
|
+
});
|
|
7236
|
+
} catch (error) {
|
|
7237
|
+
finish({
|
|
7238
|
+
success: false,
|
|
7239
|
+
error: error instanceof Error ? error.message : String(error),
|
|
7240
|
+
isError: true,
|
|
7241
|
+
});
|
|
7242
|
+
}
|
|
7243
|
+
})();
|
|
7244
|
+
}
|
|
7245
|
+
|
|
9414
7246
|
/**
|
|
9415
7247
|
* Handle the synthesized `save_recent_image` tool: locate the requested
|
|
9416
7248
|
* image blocks in the calling agent's context (blobs re-inlined, counted
|