@animalabs/agent-framework 0.6.7 → 0.6.8
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 +1 -34
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +55 -137
- package/dist/src/agent.js.map +1 -1
- package/dist/src/framework.d.ts +0 -56
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +45 -1733
- package/dist/src/framework.js.map +1 -1
- package/dist/src/types/agent.d.ts +0 -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/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/agent.ts +60 -226
- package/src/framework.ts +72 -2387
- package/src/types/agent.ts +0 -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,6 @@ 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
|
-
import { canonicalJsonStringify } from './canonical-json.js';
|
|
74
68
|
|
|
75
69
|
/**
|
|
76
70
|
* Tools whose presence suppresses auto-routing of the surrounding prose,
|
|
@@ -127,12 +121,6 @@ const FRAMEWORK_STATE_ID = 'framework/state';
|
|
|
127
121
|
const CONVERSATION_ROUTER_STATE_ID = 'framework/conversation-router';
|
|
128
122
|
const INFERENCE_LOG_ID = 'framework/inference-log';
|
|
129
123
|
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
124
|
const TURN_CHECKPOINTS_ID = 'framework/turn-checkpoints'; // legacy single-map layout, read-only fallback
|
|
137
125
|
const TURN_CHECKPOINTS_TREE_ID = 'framework/turn-checkpoints/tree';
|
|
138
126
|
|
|
@@ -187,128 +175,6 @@ interface InferenceToolSnapshot {
|
|
|
187
175
|
sameRoundThinkTextPolicy: SameRoundThinkTextPolicy;
|
|
188
176
|
}
|
|
189
177
|
|
|
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
178
|
/**
|
|
313
179
|
* Default inference policy - infer if any request exists for the agent.
|
|
314
180
|
*/
|
|
@@ -456,13 +322,6 @@ interface EphemeralRun {
|
|
|
456
322
|
toolCallsCount: number;
|
|
457
323
|
}
|
|
458
324
|
|
|
459
|
-
interface ActiveStreamContext {
|
|
460
|
-
requestId: string;
|
|
461
|
-
trigger?: PendingInferenceRequest;
|
|
462
|
-
compiledRequest?: NormalizedRequest;
|
|
463
|
-
artifacts?: ActivationCompileArtifacts;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
325
|
/** Cap an API error message for inline use in a rewind marker: keep enough to
|
|
467
326
|
* identify the failure class without pasting a wall of provider JSON into the
|
|
468
327
|
* agent's context. */
|
|
@@ -479,7 +338,7 @@ export class AgentFramework {
|
|
|
479
338
|
private moduleRegistry: ModuleRegistry;
|
|
480
339
|
private inferencePolicy: InferencePolicy;
|
|
481
340
|
private errorPolicy: ErrorPolicy;
|
|
482
|
-
private pendingRequests:
|
|
341
|
+
private pendingRequests: InferenceRequest[] = [];
|
|
483
342
|
/**
|
|
484
343
|
* Per-agent channel that triggered the agent's CURRENT inference turn, if any
|
|
485
344
|
* (item-3 redux). Read by the ChannelRegistry's `activeChannelResolver` to
|
|
@@ -508,8 +367,6 @@ export class AgentFramework {
|
|
|
508
367
|
private processLoggingPersist: boolean;
|
|
509
368
|
private processLoggingBroadcast: boolean;
|
|
510
369
|
private activeStreams: Map<string, Promise<void>> = new Map();
|
|
511
|
-
private activeStreamContexts: Map<string, ActiveStreamContext> = new Map();
|
|
512
|
-
private primarySummaryStateLock: Promise<void> = Promise.resolve();
|
|
513
370
|
|
|
514
371
|
/** Per-agent output locus pinned for the CURRENT logical turn (see
|
|
515
372
|
* resolveTurnLocus in driveStream). Lives here — not in driveStream
|
|
@@ -765,12 +622,6 @@ export class AgentFramework {
|
|
|
765
622
|
}
|
|
766
623
|
}
|
|
767
624
|
|
|
768
|
-
try {
|
|
769
|
-
store.registerState({ id: PRIMARY_SUMMARY_FALLBACK_STATE_ID, strategy: 'snapshot' });
|
|
770
|
-
} catch {
|
|
771
|
-
// Already registered
|
|
772
|
-
}
|
|
773
|
-
|
|
774
625
|
const discordAwarenessOutboxPath = config.discordAwarenessOutboxPath
|
|
775
626
|
?? (config.storePath ? defaultDiscordAwarenessOutboxPath(config.storePath) : undefined);
|
|
776
627
|
const discordAwarenessOutbox = discordAwarenessOutboxPath
|
|
@@ -815,15 +666,14 @@ export class AgentFramework {
|
|
|
815
666
|
}
|
|
816
667
|
}
|
|
817
668
|
|
|
669
|
+
// Restore persisted usage data (if any) from prior session
|
|
670
|
+
framework.restoreUsageState();
|
|
671
|
+
|
|
818
672
|
// Create agents
|
|
819
673
|
for (const agentConfig of config.agents) {
|
|
820
674
|
await framework.createAgent(agentConfig);
|
|
821
675
|
}
|
|
822
676
|
|
|
823
|
-
// Restore persisted usage data (if any) from prior session
|
|
824
|
-
framework.restoreUsageState();
|
|
825
|
-
await framework.reconcilePrimarySummaryFallbackStateOnStartup();
|
|
826
|
-
|
|
827
677
|
// Finish any branch-local suppression interrupted after Chronicle switched
|
|
828
678
|
// branches. This runs before modules, MCPL connections, or inbound traffic.
|
|
829
679
|
await framework.resumePreparedDiscordSuppressions();
|
|
@@ -1080,7 +930,7 @@ export class AgentFramework {
|
|
|
1080
930
|
const queued = [...this.agents.values()].flatMap((agent) => {
|
|
1081
931
|
const cm = agent.getContextManager();
|
|
1082
932
|
const tools = this.getToolsForAgent(agent.name).filter((tool) => agent.canUseTool(tool.name));
|
|
1083
|
-
cm.setToolDefinitions
|
|
933
|
+
cm.setToolDefinitions(tools);
|
|
1084
934
|
if (cm.isReady()) return [];
|
|
1085
935
|
const pending = cm.getPendingWork()?.description;
|
|
1086
936
|
const progress = this.contextProgress(cm);
|
|
@@ -1723,1338 +1573,22 @@ export class AgentFramework {
|
|
|
1723
1573
|
|
|
1724
1574
|
const agent = new Agent(config, contextManager, this.membrane);
|
|
1725
1575
|
|
|
1726
|
-
const cleanup = () => {
|
|
1727
|
-
// Don't close the store — it's shared. Just release the CM.
|
|
1728
|
-
// Data persists in the store under the namespace for investigation.
|
|
1729
|
-
};
|
|
1730
|
-
|
|
1731
|
-
return { agent, contextManager, cleanup };
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
private createDeferred<T>(): Deferred<T> {
|
|
1735
|
-
let resolve!: (value: T) => void;
|
|
1736
|
-
let reject!: (reason?: unknown) => void;
|
|
1737
|
-
const promise = new Promise<T>((res, rej) => {
|
|
1738
|
-
resolve = res;
|
|
1739
|
-
reject = rej;
|
|
1740
|
-
});
|
|
1741
|
-
return { promise, resolve, reject };
|
|
1742
|
-
}
|
|
1743
|
-
|
|
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
|
-
}
|
|
1576
|
+
const cleanup = () => {
|
|
1577
|
+
// Don't close the store — it's shared. Just release the CM.
|
|
1578
|
+
// Data persists in the store under the namespace for investigation.
|
|
1579
|
+
};
|
|
3018
1580
|
|
|
3019
|
-
|
|
3020
|
-
return blocks.some((block) => block.type !== 'thinking' && block.type !== 'redacted_thinking');
|
|
1581
|
+
return { agent, contextManager, cleanup };
|
|
3021
1582
|
}
|
|
3022
1583
|
|
|
3023
|
-
private
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
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
|
-
};
|
|
1584
|
+
private createDeferred<T>(): Deferred<T> {
|
|
1585
|
+
let resolve!: (value: T) => void;
|
|
1586
|
+
let reject!: (reason?: unknown) => void;
|
|
1587
|
+
const promise = new Promise<T>((res, rej) => {
|
|
1588
|
+
resolve = res;
|
|
1589
|
+
reject = rej;
|
|
1590
|
+
});
|
|
1591
|
+
return { promise, resolve, reject };
|
|
3058
1592
|
}
|
|
3059
1593
|
|
|
3060
1594
|
/** Liveness ping for an active ephemeral run (no-op otherwise). */
|
|
@@ -4603,7 +3137,6 @@ export class AgentFramework {
|
|
|
4603
3137
|
// Cast to AgentState to bypass TypeScript's control flow narrowing
|
|
4604
3138
|
const currentState = agent.state as AgentState;
|
|
4605
3139
|
if (currentState.status === 'ready') {
|
|
4606
|
-
const activeStreamContext = this.activeStreamContexts.get(agent.name);
|
|
4607
3140
|
// Flush pending assistant blocks (tool_use + preamble text) to context
|
|
4608
3141
|
const pendingBlocks = this.pendingAssistantBlocks.get(agent.name);
|
|
4609
3142
|
if (pendingBlocks) {
|
|
@@ -4725,59 +3258,6 @@ export class AgentFramework {
|
|
|
4725
3258
|
// a terminal failure — rejecting an ephemeral's promise mid-run
|
|
4726
3259
|
// and emitting a spurious inference:exhausted (same race shape as
|
|
4727
3260
|
// 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
3261
|
if (currentState.stream) {
|
|
4782
3262
|
this.frameworkCancelledStreams.set(`${agent.name}:${agent.streamId}`, 'budget_restart');
|
|
4783
3263
|
}
|
|
@@ -4794,8 +3274,6 @@ export class AgentFramework {
|
|
|
4794
3274
|
reason: 'context_budget_restart',
|
|
4795
3275
|
source: 'framework',
|
|
4796
3276
|
timestamp: Date.now(),
|
|
4797
|
-
channelId: activeStreamContext?.trigger?.channelId,
|
|
4798
|
-
contextBudgetRestart: restartDispatch,
|
|
4799
3277
|
});
|
|
4800
3278
|
} else if (currentState.stream) {
|
|
4801
3279
|
// Streaming path: convert results and resume the stream.
|
|
@@ -5382,7 +3860,7 @@ export class AgentFramework {
|
|
|
5382
3860
|
const state = this.createFrameworkState();
|
|
5383
3861
|
|
|
5384
3862
|
// Group requests by agent
|
|
5385
|
-
const requestsByAgent = new Map<string,
|
|
3863
|
+
const requestsByAgent = new Map<string, InferenceRequest[]>();
|
|
5386
3864
|
for (const req of this.pendingRequests) {
|
|
5387
3865
|
const existing = requestsByAgent.get(req.agentName) ?? [];
|
|
5388
3866
|
existing.push(req);
|
|
@@ -5393,8 +3871,7 @@ export class AgentFramework {
|
|
|
5393
3871
|
this.pendingRequests = [];
|
|
5394
3872
|
|
|
5395
3873
|
// Check each agent
|
|
5396
|
-
for (const [agentName,
|
|
5397
|
-
let requests = queuedRequests;
|
|
3874
|
+
for (const [agentName, requests] of requestsByAgent) {
|
|
5398
3875
|
const agent = this.agents.get(agentName);
|
|
5399
3876
|
if (!agent) {
|
|
5400
3877
|
// Agent not found — request is orphaned. Emit warning and drop.
|
|
@@ -5439,19 +3916,6 @@ export class AgentFramework {
|
|
|
5439
3916
|
continue;
|
|
5440
3917
|
}
|
|
5441
3918
|
|
|
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
3919
|
// Check policy
|
|
5456
3920
|
if (!this.inferencePolicy.shouldInfer(agentName, requests, state)) {
|
|
5457
3921
|
// Loud drop: a queued request that dies here is otherwise invisible —
|
|
@@ -5465,12 +3929,7 @@ export class AgentFramework {
|
|
|
5465
3929
|
}
|
|
5466
3930
|
|
|
5467
3931
|
// 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
|
-
}
|
|
3932
|
+
const trigger = requests[0];
|
|
5474
3933
|
// Route this turn's auto-published speech to the channel that triggered
|
|
5475
3934
|
// it (item-3 redux). A batched wake may carry several triggering channels
|
|
5476
3935
|
// (messages arrived in >1 channel while the agent was busy/idle) — reply
|
|
@@ -5487,9 +3946,9 @@ export class AgentFramework {
|
|
|
5487
3946
|
}
|
|
5488
3947
|
}
|
|
5489
3948
|
|
|
5490
|
-
private async startAgentStream(agent: Agent, trigger?:
|
|
3949
|
+
private async startAgentStream(agent: Agent, trigger?: InferenceRequest, attempt = 0): Promise<void> {
|
|
5491
3950
|
// Record turn checkpoint before inference (only on first attempt, not retries)
|
|
5492
|
-
if (attempt === 0
|
|
3951
|
+
if (attempt === 0) {
|
|
5493
3952
|
this.recordTurnCheckpoint(agent.name);
|
|
5494
3953
|
this.redoStacks.delete(agent.name); // new work invalidates redo
|
|
5495
3954
|
}
|
|
@@ -5510,276 +3969,62 @@ export class AgentFramework {
|
|
|
5510
3969
|
this.emitTrace({ type: 'inference:started', agentName: agent.name });
|
|
5511
3970
|
this.eventGate?.onInferenceStarted(agent.name);
|
|
5512
3971
|
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
3972
|
|
|
5515
3973
|
try {
|
|
5516
3974
|
const requestSnapshot = this.captureInferenceToolSnapshot(agent);
|
|
5517
3975
|
const allTools = this.getToolsForAgent(agent.name, requestSnapshot);
|
|
5518
3976
|
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
3977
|
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
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
|
-
}
|
|
3978
|
+
// Gather context from modules (pull-based) and MCPL hooks (push-based)
|
|
3979
|
+
// Both produce ContextInjection[] that get merged before inference.
|
|
3980
|
+
let injections: ContextInjection[] | undefined;
|
|
5582
3981
|
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
3982
|
+
// Module gatherContext (fail-open, per-module timeout — the module's
|
|
3983
|
+
// contextTimeoutMs, else the registry default). Injections are
|
|
3984
|
+
// channel-scoped via scopeInjectionsForAgent, matching the MCPL hook
|
|
3985
|
+
// injections below, so conversation forks don't see cross-channel
|
|
3986
|
+
// content.
|
|
3987
|
+
try {
|
|
3988
|
+
const moduleInjections = this.scopeInjectionsForAgent(
|
|
3989
|
+
agent.name,
|
|
3990
|
+
await this.moduleRegistry.gatherContext(agent.name),
|
|
5587
3991
|
);
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
const rebuiltHash = this.hashJson(compiledRequest);
|
|
5592
|
-
if (
|
|
5593
|
-
rebuiltHash !== retryDispatch.expectedRetryRequestHash ||
|
|
5594
|
-
this.primarySummaryProjectionKeys(artifacts.compileResult.primarySummaryProjection ?? null).join('\u0000') !==
|
|
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;
|
|
3992
|
+
if (moduleInjections.length > 0) {
|
|
3993
|
+
injections = moduleInjections;
|
|
5611
3994
|
}
|
|
3995
|
+
} catch (error) {
|
|
3996
|
+
console.error('Module gatherContext error:', error);
|
|
3997
|
+
}
|
|
5612
3998
|
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
requestId,
|
|
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);
|
|
5627
|
-
|
|
5628
|
-
const admittedTokens = this.primarySummaryAdmissionBoundTokens(
|
|
5629
|
-
retryDispatch.originalRequestInputBoundTokens,
|
|
5630
|
-
compiledRequest,
|
|
5631
|
-
retryDispatch.originalProviderInputTokens,
|
|
5632
|
-
);
|
|
5633
|
-
if (admittedTokens > retryDispatch.requestBudgetTokens) {
|
|
5634
|
-
await this.holdPrimarySummaryRequest(
|
|
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;
|
|
5654
|
-
}
|
|
5655
|
-
} else {
|
|
5656
|
-
// Gather context from modules (pull-based) and MCPL hooks (push-based)
|
|
5657
|
-
// Both produce ContextInjection[] that get merged before inference.
|
|
5658
|
-
let injections: ContextInjection[] | undefined;
|
|
5659
|
-
|
|
5660
|
-
// Module gatherContext (fail-open, per-module timeout — the module's
|
|
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.
|
|
3999
|
+
// MCPL beforeInference hooks (fail-open)
|
|
4000
|
+
if (this.hookOrchestrator) {
|
|
5665
4001
|
try {
|
|
5666
|
-
const
|
|
4002
|
+
const hookParams = this.buildBeforeInferenceParams(agent, trigger);
|
|
4003
|
+
const hookInjections = this.scopeInjectionsForAgent(
|
|
5667
4004
|
agent.name,
|
|
5668
|
-
await this.
|
|
4005
|
+
await this.hookOrchestrator.beforeInference(hookParams),
|
|
5669
4006
|
);
|
|
5670
|
-
if (
|
|
5671
|
-
injections =
|
|
4007
|
+
if (hookInjections.length > 0) {
|
|
4008
|
+
injections = injections ? [...injections, ...hookInjections] : hookInjections;
|
|
5672
4009
|
}
|
|
5673
4010
|
} 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
|
-
}
|
|
4011
|
+
console.error('beforeInference hook error:', error);
|
|
5736
4012
|
}
|
|
5737
4013
|
}
|
|
5738
4014
|
|
|
5739
|
-
const { stream } = agent.
|
|
5740
|
-
|
|
5741
|
-
requestId,
|
|
5742
|
-
trigger,
|
|
5743
|
-
compiledRequest,
|
|
5744
|
-
artifacts,
|
|
5745
|
-
});
|
|
4015
|
+
const { stream, request: compiledRequest } = await agent.startStreamWithInjections(tools, injections);
|
|
4016
|
+
|
|
5746
4017
|
const handle = this.driveStream(
|
|
5747
4018
|
agent,
|
|
5748
4019
|
stream,
|
|
5749
4020
|
requestSnapshot,
|
|
5750
|
-
requestId,
|
|
5751
4021
|
trigger,
|
|
5752
4022
|
attempt,
|
|
5753
4023
|
compiledRequest,
|
|
5754
|
-
artifacts,
|
|
5755
4024
|
);
|
|
5756
4025
|
this.activeStreams.set(agent.name, handle);
|
|
5757
4026
|
} catch (error) {
|
|
5758
4027
|
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
4028
|
this.emitTrace({
|
|
5784
4029
|
type: 'inference:failed',
|
|
5785
4030
|
agentName: agent.name,
|
|
@@ -5823,23 +4068,14 @@ export class AgentFramework {
|
|
|
5823
4068
|
agent: Agent,
|
|
5824
4069
|
stream: YieldingStream,
|
|
5825
4070
|
requestSnapshot: InferenceToolSnapshot,
|
|
5826
|
-
|
|
5827
|
-
trigger?: PendingInferenceRequest,
|
|
4071
|
+
trigger?: InferenceRequest,
|
|
5828
4072
|
attempt = 0,
|
|
5829
|
-
compiledRequest?: NormalizedRequest
|
|
5830
|
-
artifacts?: ActivationCompileArtifacts,
|
|
4073
|
+
compiledRequest?: NormalizedRequest
|
|
5831
4074
|
): Promise<void> {
|
|
5832
4075
|
const startTime = Date.now();
|
|
4076
|
+
const requestId = `${agent.name}-${startTime}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5833
4077
|
const myStreamId = agent.streamId;
|
|
5834
4078
|
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
4079
|
|
|
5844
4080
|
// ---- Present-while-acting turn state ---------------------------------
|
|
5845
4081
|
// Output locus for the current CONVERSATIONAL ROUND: resolved lazily and
|
|
@@ -5848,7 +4084,7 @@ export class AgentFramework {
|
|
|
5848
4084
|
// single provider inference can now contain several conversations. The
|
|
5849
4085
|
// pin lives in `turnLocusPins` so it survives a context-budget restart. A
|
|
5850
4086
|
// null resolution is not pinned.
|
|
5851
|
-
if (trigger?.reason !== 'context_budget_restart'
|
|
4087
|
+
if (trigger?.reason !== 'context_budget_restart') {
|
|
5852
4088
|
this.turnLocusPins.delete(agent.name);
|
|
5853
4089
|
this.midTurnRoutingResets.delete(agent.name);
|
|
5854
4090
|
}
|
|
@@ -5920,104 +4156,6 @@ export class AgentFramework {
|
|
|
5920
4156
|
}
|
|
5921
4157
|
};
|
|
5922
4158
|
|
|
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
4159
|
try {
|
|
6022
4160
|
for await (const event of stream) {
|
|
6023
4161
|
this.touchEphemeralRun(agent.name, true);
|
|
@@ -6047,6 +4185,7 @@ export class AgentFramework {
|
|
|
6047
4185
|
case 'tool-calls': {
|
|
6048
4186
|
adoptInjectedRound();
|
|
6049
4187
|
hadToolCalls = true;
|
|
4188
|
+
this.recordEphemeralToolCalls(agent.name, event.calls.length);
|
|
6050
4189
|
this.emitTrace({
|
|
6051
4190
|
type: 'inference:tool_calls_yielded',
|
|
6052
4191
|
agentName: agent.name,
|
|
@@ -6085,51 +4224,7 @@ export class AgentFramework {
|
|
|
6085
4224
|
|
|
6086
4225
|
agent.enterWaitingForTools(event.calls, stream);
|
|
6087
4226
|
|
|
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
4227
|
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
4228
|
this.dispatchToolCall(agent.name, call);
|
|
6134
4229
|
}
|
|
6135
4230
|
|
|
@@ -6222,213 +4317,6 @@ export class AgentFramework {
|
|
|
6222
4317
|
}
|
|
6223
4318
|
}
|
|
6224
4319
|
|
|
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
4320
|
// Add assistant response to context.
|
|
6433
4321
|
// If we had tool calls, each round's blocks (thinking + text +
|
|
6434
4322
|
// tool_use) were already stored as pendingAssistantBlocks and
|
|
@@ -6437,52 +4325,20 @@ export class AgentFramework {
|
|
|
6437
4325
|
// double-store earlier rounds' text/thinking AND detach signed
|
|
6438
4326
|
// thinking blocks from their tool_use turn (the API requires
|
|
6439
4327
|
// thinking to precede tool_use in the same assistant turn).
|
|
4328
|
+
const lastToolIdx = response.content.reduce(
|
|
4329
|
+
(last: number, b: ContentBlock, i: number) =>
|
|
4330
|
+
b.type === 'tool_use' || b.type === 'tool_result' ? i : last,
|
|
4331
|
+
-1
|
|
4332
|
+
);
|
|
4333
|
+
const terminalContent = lastToolIdx >= 0
|
|
4334
|
+
? response.content.slice(lastToolIdx + 1)
|
|
4335
|
+
: response.content;
|
|
6440
4336
|
if (lastToolIdx >= 0) {
|
|
6441
4337
|
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
4338
|
agent.addAssistantResponse(terminalContent);
|
|
6485
4339
|
}
|
|
4340
|
+
} else {
|
|
4341
|
+
agent.addAssistantResponse(terminalContent);
|
|
6486
4342
|
}
|
|
6487
4343
|
|
|
6488
4344
|
// Run afterInference hooks (no-op if no MCPL servers)
|
|
@@ -6544,50 +4400,6 @@ export class AgentFramework {
|
|
|
6544
4400
|
}
|
|
6545
4401
|
: undefined;
|
|
6546
4402
|
|
|
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
4403
|
// Reset agent state before emitting inference:completed. Traces are
|
|
6592
4404
|
// observability-only, but external synchronous listeners should
|
|
6593
4405
|
// still see the terminal state at the terminal trace boundary.
|
|
@@ -6626,10 +4438,8 @@ export class AgentFramework {
|
|
|
6626
4438
|
agentName: agent.name,
|
|
6627
4439
|
requestId,
|
|
6628
4440
|
success: true,
|
|
6629
|
-
request:
|
|
6630
|
-
response:
|
|
6631
|
-
? this.summarizePrimarySummaryRetryResponse(response)
|
|
6632
|
-
: response.raw ?? { note: 'streaming response' },
|
|
4441
|
+
request: compiledRequest ?? { note: 'streaming request' },
|
|
4442
|
+
response: response.raw ?? { note: 'streaming response' },
|
|
6633
4443
|
durationMs,
|
|
6634
4444
|
tokenUsage,
|
|
6635
4445
|
stopReason: response.stopReason,
|
|
@@ -6851,30 +4661,13 @@ export class AgentFramework {
|
|
|
6851
4661
|
requestId,
|
|
6852
4662
|
success: false,
|
|
6853
4663
|
error: err.message,
|
|
6854
|
-
request:
|
|
4664
|
+
request: compiledRequest ?? { note: 'streaming request failed' },
|
|
6855
4665
|
durationMs,
|
|
6856
4666
|
});
|
|
6857
4667
|
|
|
6858
4668
|
// Only reset + retry if this is still the active stream
|
|
6859
4669
|
if (agent.streamId !== myStreamId) break;
|
|
6860
4670
|
|
|
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
4671
|
agent.reset();
|
|
6879
4672
|
|
|
6880
4673
|
const action = this.errorPolicy.onInferenceError(err, agent.name, attempt);
|
|
@@ -6914,17 +4707,6 @@ export class AgentFramework {
|
|
|
6914
4707
|
// reject an ephemeral's promise mid-run and bump the failure
|
|
6915
4708
|
// streak). Gate release + stream teardown happen in `finally`.
|
|
6916
4709
|
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
4710
|
this.eventGate?.onInferenceEnded(agent.name);
|
|
6929
4711
|
return;
|
|
6930
4712
|
}
|
|
@@ -6933,40 +4715,6 @@ export class AgentFramework {
|
|
|
6933
4715
|
// may have already started a new stream, bumping streamId)
|
|
6934
4716
|
if (agent.streamId === myStreamId) {
|
|
6935
4717
|
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
4718
|
agent.reset();
|
|
6971
4719
|
this.settleAgent(agent.name, {
|
|
6972
4720
|
stopReason: 'exhausted',
|
|
@@ -6990,7 +4738,7 @@ export class AgentFramework {
|
|
|
6990
4738
|
requestId,
|
|
6991
4739
|
success: false,
|
|
6992
4740
|
error: `Stream aborted: ${reason}`,
|
|
6993
|
-
request:
|
|
4741
|
+
request: compiledRequest ?? { note: 'streaming request aborted' },
|
|
6994
4742
|
durationMs,
|
|
6995
4743
|
});
|
|
6996
4744
|
this.eventGate?.onInferenceEnded(agent.name);
|
|
@@ -7042,63 +4790,12 @@ export class AgentFramework {
|
|
|
7042
4790
|
// inference:exhausted so ephemeral agent promises can settle.
|
|
7043
4791
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
7044
4792
|
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
4793
|
this.emitTrace({
|
|
7079
4794
|
type: 'inference:failed',
|
|
7080
4795
|
agentName: agent.name,
|
|
7081
4796
|
error: err.message,
|
|
7082
4797
|
stack: err.stack,
|
|
7083
4798
|
});
|
|
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
4799
|
this.settleAgent(agent.name, {
|
|
7103
4800
|
stopReason: 'exhausted',
|
|
7104
4801
|
speech: '',
|
|
@@ -7119,7 +4816,7 @@ export class AgentFramework {
|
|
|
7119
4816
|
requestId,
|
|
7120
4817
|
success: false,
|
|
7121
4818
|
error: `Stream threw: ${err.message}`,
|
|
7122
|
-
request:
|
|
4819
|
+
request: compiledRequest ?? { note: 'streaming request threw' },
|
|
7123
4820
|
durationMs,
|
|
7124
4821
|
});
|
|
7125
4822
|
agent.reset();
|
|
@@ -7140,7 +4837,6 @@ export class AgentFramework {
|
|
|
7140
4837
|
this.channelRegistry?.stopTyping();
|
|
7141
4838
|
this.frameworkCancelledStreams.delete(`${agent.name}:${myStreamId}`);
|
|
7142
4839
|
this.activeStreams.delete(agent.name);
|
|
7143
|
-
this.activeStreamContexts.delete(agent.name);
|
|
7144
4840
|
this.pendingAssistantBlocks.delete(agent.name);
|
|
7145
4841
|
|
|
7146
4842
|
// A conversation fork whose TTL closure turn just finished is done for
|
|
@@ -7678,23 +5374,12 @@ export class AgentFramework {
|
|
|
7678
5374
|
* work, per-agent status + last inference activity. Cheap and read-only.
|
|
7679
5375
|
*/
|
|
7680
5376
|
healthSnapshot(): Record<string, unknown> {
|
|
7681
|
-
const primarySummary = this.readPrimarySummaryFallbackState();
|
|
7682
5377
|
return {
|
|
7683
5378
|
at: new Date().toISOString(),
|
|
7684
5379
|
uptimeSec: Math.round(process.uptime()),
|
|
7685
5380
|
gate: this.eventGate?.inferenceDiagnostics() ?? null,
|
|
7686
5381
|
pendingRequests: this.pendingRequests.length,
|
|
7687
5382
|
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
5383
|
agents: [...this.agents.entries()].map(([name, agent]) => ({
|
|
7699
5384
|
name,
|
|
7700
5385
|
status: agent.state.status,
|