@librechat/agents 3.7.7 → 3.7.9
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/cjs/agents/AgentContext.cjs +26 -5
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/constants.cjs +12 -0
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +56 -11
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +8 -0
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +2 -0
- package/dist/cjs/hooks/index.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +1 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +43 -3
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseTraceShaping.cjs +75 -0
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/toolCache.cjs +1 -1
- package/dist/cjs/llm/fake.cjs +9 -5
- package/dist/cjs/llm/fake.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +3 -7
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/toolCache.cjs +1 -1
- package/dist/cjs/llm/prepareProviderRequest.cjs +2 -2
- package/dist/cjs/llm/providers.cjs +1 -1
- package/dist/cjs/llm/request.cjs +6 -0
- package/dist/cjs/llm/request.cjs.map +1 -1
- package/dist/cjs/llm/truncation.cjs +1 -0
- package/dist/cjs/main.cjs +22 -13
- package/dist/cjs/messages/format.cjs +375 -3
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +1 -1
- package/dist/cjs/messages/prune.cjs +1 -1
- package/dist/cjs/run.cjs +217 -52
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +1 -1
- package/dist/cjs/session/JsonlSessionStore.cjs +1 -1
- package/dist/cjs/stream.cjs +1 -1
- package/dist/cjs/summarization/index.cjs +1 -0
- package/dist/cjs/summarization/index.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +58 -85
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/summarization/semanticIndex.cjs +366 -0
- package/dist/cjs/summarization/semanticIndex.cjs.map +1 -0
- package/dist/cjs/summarization/shared.cjs +83 -0
- package/dist/cjs/summarization/shared.cjs.map +1 -0
- package/dist/cjs/tools/ToolNode.cjs +5 -5
- package/dist/cjs/tools/local/CompileCheckTool.cjs +1 -1
- package/dist/cjs/tools/local/LocalCodingTools.cjs +1 -1
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +1 -1
- package/dist/cjs/tools/runStepResume.cjs +1 -1
- package/dist/cjs/tools/runStepResume.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +8 -12
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs +2 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/cjs/utils/tokens.cjs +8 -1
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +26 -5
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/constants.mjs +11 -1
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +56 -11
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +8 -1
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +2 -1
- package/dist/esm/hooks/index.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +1 -0
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +43 -3
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseTraceShaping.mjs +75 -0
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
- package/dist/esm/llm/bedrock/toolCache.mjs +1 -1
- package/dist/esm/llm/fake.mjs +9 -5
- package/dist/esm/llm/fake.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +3 -7
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openrouter/toolCache.mjs +1 -1
- package/dist/esm/llm/prepareProviderRequest.mjs +2 -2
- package/dist/esm/llm/providers.mjs +1 -1
- package/dist/esm/llm/request.mjs +6 -1
- package/dist/esm/llm/request.mjs.map +1 -1
- package/dist/esm/llm/truncation.mjs +1 -1
- package/dist/esm/main.mjs +15 -14
- package/dist/esm/messages/format.mjs +375 -3
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +1 -1
- package/dist/esm/messages/prune.mjs +1 -1
- package/dist/esm/run.mjs +217 -52
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +1 -1
- package/dist/esm/session/JsonlSessionStore.mjs +1 -1
- package/dist/esm/stream.mjs +1 -1
- package/dist/esm/summarization/index.mjs +1 -0
- package/dist/esm/summarization/index.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +57 -84
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/summarization/semanticIndex.mjs +363 -0
- package/dist/esm/summarization/semanticIndex.mjs.map +1 -0
- package/dist/esm/summarization/shared.mjs +79 -0
- package/dist/esm/summarization/shared.mjs.map +1 -0
- package/dist/esm/tools/ToolNode.mjs +5 -5
- package/dist/esm/tools/local/CompileCheckTool.mjs +1 -1
- package/dist/esm/tools/local/LocalCodingTools.mjs +1 -1
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +1 -1
- package/dist/esm/tools/runStepResume.mjs +1 -1
- package/dist/esm/tools/runStepResume.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +8 -12
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs +2 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +1 -1
- package/dist/esm/utils/tokens.mjs +8 -2
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +27 -1
- package/dist/types/common/constants.d.ts +17 -0
- package/dist/types/graphs/Graph.d.ts +24 -0
- package/dist/types/hooks/executeHooks.d.ts +5 -2
- package/dist/types/hooks/index.d.ts +7 -2
- package/dist/types/hooks/types.d.ts +45 -7
- package/dist/types/langfuse.d.ts +3 -1
- package/dist/types/llm/fake.d.ts +12 -2
- package/dist/types/llm/request.d.ts +10 -0
- package/dist/types/messages/format.d.ts +17 -2
- package/dist/types/run.d.ts +19 -6
- package/dist/types/summarization/index.d.ts +6 -0
- package/dist/types/summarization/node.d.ts +0 -4
- package/dist/types/summarization/semanticIndex.d.ts +21 -0
- package/dist/types/summarization/shared.d.ts +25 -0
- package/dist/types/types/graph.d.ts +9 -1
- package/dist/types/types/run.d.ts +8 -0
- package/dist/types/types/stream.d.ts +6 -0
- package/dist/types/types/summarize.d.ts +44 -0
- package/dist/types/utils/tokens.d.ts +9 -0
- package/package.json +2 -1
- package/src/agents/AgentContext.ts +63 -6
- package/src/common/constants.ts +19 -0
- package/src/graphs/Graph.ts +86 -8
- package/src/hooks/HookRegistry.ts +3 -1
- package/src/hooks/executeHooks.ts +20 -2
- package/src/hooks/index.ts +12 -1
- package/src/hooks/types.ts +47 -4
- package/src/langfuse.ts +70 -0
- package/src/langfuseTraceShaping.ts +94 -0
- package/src/llm/fake.ts +35 -4
- package/src/llm/invoke.ts +13 -29
- package/src/llm/request.ts +24 -0
- package/src/messages/format.ts +839 -5
- package/src/run.ts +393 -187
- package/src/summarization/index.ts +11 -0
- package/src/summarization/node.ts +202 -158
- package/src/summarization/semanticIndex.ts +662 -0
- package/src/summarization/shared.ts +130 -0
- package/src/tools/runStepResume.ts +9 -0
- package/src/tools/subagent/SubagentExecutor.ts +4 -27
- package/src/tools/subagent/childGraphConfig.ts +3 -0
- package/src/types/graph.ts +9 -0
- package/src/types/run.ts +8 -0
- package/src/types/stream.ts +6 -0
- package/src/types/summarize.ts +57 -0
- package/src/utils/tokens.ts +31 -8
|
@@ -50,7 +50,9 @@ import {
|
|
|
50
50
|
Providers,
|
|
51
51
|
} from '@/common';
|
|
52
52
|
import { isTokenCounterCacheCompatible } from '@/llm/tokenCounterCacheCompatibility';
|
|
53
|
+
import { snapshotCompactionSemanticIndex } from '@/summarization/semanticIndex';
|
|
53
54
|
import { createExactTokenCountCache } from '@/llm/contextPressureMeter';
|
|
55
|
+
import { buildSummaryCarrierText } from '@/summarization/shared';
|
|
54
56
|
import { createSchemaOnlyTools } from '@/tools/schema';
|
|
55
57
|
import { apportionTokenCounts } from '@/utils/tokens';
|
|
56
58
|
import { isThinkingEnabled } from '@/llm/request';
|
|
@@ -74,6 +76,14 @@ type ProgrammaticToolInstructionTarget = {
|
|
|
74
76
|
executesDirectly: boolean;
|
|
75
77
|
};
|
|
76
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Consecutive summarization attempts that may return no usable summary before
|
|
81
|
+
* the run stops asking. Every such attempt spends a full model call over the
|
|
82
|
+
* whole history and leaves the message set exactly as it was, so the cap
|
|
83
|
+
* bounds spend as much as it bounds the compaction loop.
|
|
84
|
+
*/
|
|
85
|
+
const MAX_SUMMARIZATION_FAILURES = 3;
|
|
86
|
+
|
|
77
87
|
/**
|
|
78
88
|
* Encapsulates agent-specific state that can vary between agents in a multi-agent system
|
|
79
89
|
*/
|
|
@@ -108,6 +118,7 @@ export class AgentContext {
|
|
|
108
118
|
discoveredTools,
|
|
109
119
|
summarizationEnabled,
|
|
110
120
|
summarizationConfig,
|
|
121
|
+
compactionSemanticIndex,
|
|
111
122
|
initialSummary,
|
|
112
123
|
contextPruningConfig,
|
|
113
124
|
maxToolResultChars,
|
|
@@ -141,11 +152,18 @@ export class AgentContext {
|
|
|
141
152
|
discoveredTools,
|
|
142
153
|
summarizationEnabled,
|
|
143
154
|
summarizationConfig,
|
|
155
|
+
compactionSemanticIndex,
|
|
144
156
|
contextPruningConfig,
|
|
145
157
|
maxToolResultChars,
|
|
146
158
|
});
|
|
147
159
|
|
|
148
|
-
agentContext._sourceInputs =
|
|
160
|
+
agentContext._sourceInputs =
|
|
161
|
+
compactionSemanticIndex == null
|
|
162
|
+
? agentConfig
|
|
163
|
+
: {
|
|
164
|
+
...agentConfig,
|
|
165
|
+
compactionSemanticIndex: agentContext.compactionSemanticIndex,
|
|
166
|
+
};
|
|
149
167
|
agentContext.subagentConfigs = subagentConfigs;
|
|
150
168
|
agentContext.maxSubagentDepth = maxSubagentDepth;
|
|
151
169
|
/**
|
|
@@ -346,6 +364,8 @@ export class AgentContext {
|
|
|
346
364
|
summarizationEnabled?: boolean;
|
|
347
365
|
/** Summarization runtime settings used by graph pruning hooks */
|
|
348
366
|
summarizationConfig?: t.SummarizationConfig;
|
|
367
|
+
/** Host-supplied advisory guidance consumed only when compaction runs. */
|
|
368
|
+
compactionSemanticIndex?: t.CompactionSemanticIndex;
|
|
349
369
|
/** Current summary text produced by the summarize node, integrated into system message */
|
|
350
370
|
private summaryText?: string;
|
|
351
371
|
/** Token count of the current summary (tracked for token accounting) */
|
|
@@ -376,6 +396,13 @@ export class AgentContext {
|
|
|
376
396
|
* Summarization is allowed to fire again only when new messages appear.
|
|
377
397
|
*/
|
|
378
398
|
private _lastSummarizationMsgCount: number = 0;
|
|
399
|
+
/**
|
|
400
|
+
* Consecutive summarization attempts that produced no usable summary.
|
|
401
|
+
* An empty or failed summary leaves the message set exactly as it was, so
|
|
402
|
+
* the next prune cycle would ask again on identical state. Cleared by
|
|
403
|
+
* `setSummary` and by `reset()`.
|
|
404
|
+
*/
|
|
405
|
+
private _summarizationFailures: number = 0;
|
|
379
406
|
/**
|
|
380
407
|
* Forced compactions performed after a provider rejected a prompt as too
|
|
381
408
|
* large. Bounds the recovery loop so a model that keeps refusing cannot
|
|
@@ -430,6 +457,7 @@ export class AgentContext {
|
|
|
430
457
|
discoveredTools,
|
|
431
458
|
summarizationEnabled,
|
|
432
459
|
summarizationConfig,
|
|
460
|
+
compactionSemanticIndex,
|
|
433
461
|
contextPruningConfig,
|
|
434
462
|
maxToolResultChars,
|
|
435
463
|
}: {
|
|
@@ -456,6 +484,7 @@ export class AgentContext {
|
|
|
456
484
|
discoveredTools?: string[];
|
|
457
485
|
summarizationEnabled?: boolean;
|
|
458
486
|
summarizationConfig?: t.SummarizationConfig;
|
|
487
|
+
compactionSemanticIndex?: t.CompactionSemanticIndex;
|
|
459
488
|
contextPruningConfig?: t.ContextPruningConfig;
|
|
460
489
|
maxToolResultChars?: number;
|
|
461
490
|
}) {
|
|
@@ -495,6 +524,11 @@ export class AgentContext {
|
|
|
495
524
|
this.useLegacyContent = useLegacyContent ?? false;
|
|
496
525
|
this.summarizationEnabled = summarizationEnabled;
|
|
497
526
|
this.summarizationConfig = summarizationConfig;
|
|
527
|
+
if (compactionSemanticIndex != null) {
|
|
528
|
+
this.compactionSemanticIndex = snapshotCompactionSemanticIndex(
|
|
529
|
+
compactionSemanticIndex
|
|
530
|
+
);
|
|
531
|
+
}
|
|
498
532
|
this.contextPruningConfig = contextPruningConfig;
|
|
499
533
|
this.maxToolResultChars = maxToolResultChars;
|
|
500
534
|
|
|
@@ -934,11 +968,7 @@ export class AgentContext {
|
|
|
934
968
|
private buildSummaryHumanMessage(
|
|
935
969
|
promptCacheProvider: PromptCacheProvider | undefined
|
|
936
970
|
): HumanMessage {
|
|
937
|
-
const wrappedSummary =
|
|
938
|
-
'<summary>\n' +
|
|
939
|
-
(this.summaryText as string) +
|
|
940
|
-
'\n</summary>\n\n' +
|
|
941
|
-
'This is your own checkpoint: you wrote it to preserve context after compaction. Pick up where you left off based on the summary above. Do not repeat prior tasks, information or acknowledge this checkpoint message directly.';
|
|
971
|
+
const wrappedSummary = buildSummaryCarrierText(this.summaryText as string);
|
|
942
972
|
|
|
943
973
|
if (promptCacheProvider !== Providers.ANTHROPIC) {
|
|
944
974
|
return new HumanMessage(wrappedSummary);
|
|
@@ -1209,6 +1239,7 @@ export class AgentContext {
|
|
|
1209
1239
|
this.summaryTokenCount = this._durableSummaryTokenCount;
|
|
1210
1240
|
this.summaryPrecedesMessages = this.durableSummaryPrecedesMessages;
|
|
1211
1241
|
this._lastSummarizationMsgCount = 0;
|
|
1242
|
+
this._summarizationFailures = 0;
|
|
1212
1243
|
this.lastCallUsage = undefined;
|
|
1213
1244
|
this.totalTokensFresh = false;
|
|
1214
1245
|
this.restoreContextBudgetAfterOverflow();
|
|
@@ -1478,6 +1509,7 @@ export class AgentContext {
|
|
|
1478
1509
|
this._durableSummaryTokenCount = tokenCount;
|
|
1479
1510
|
this.durableSummaryPrecedesMessages = this.summaryPrecedesMessages;
|
|
1480
1511
|
this._summaryVersion += 1;
|
|
1512
|
+
this._summarizationFailures = 0;
|
|
1481
1513
|
this.systemRunnableStale = true;
|
|
1482
1514
|
this.pruneMessages = undefined;
|
|
1483
1515
|
}
|
|
@@ -1547,6 +1579,31 @@ export class AgentContext {
|
|
|
1547
1579
|
this._lastSummarizationMsgCount = msgCount;
|
|
1548
1580
|
}
|
|
1549
1581
|
|
|
1582
|
+
/**
|
|
1583
|
+
* Records a summarization attempt that produced no usable summary — an
|
|
1584
|
+
* empty model response, or a provider failure the run declined to paper
|
|
1585
|
+
* over with a metadata stub. Cleared by the next successful summary.
|
|
1586
|
+
*/
|
|
1587
|
+
recordSummarizationFailure(): void {
|
|
1588
|
+
this._summarizationFailures += 1;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
get summarizationFailures(): number {
|
|
1592
|
+
return this._summarizationFailures;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
/**
|
|
1596
|
+
* True once consecutive no-progress attempts reach {@link MAX_SUMMARIZATION_FAILURES}.
|
|
1597
|
+
* A summarizer that has returned nothing this many times in a row will keep
|
|
1598
|
+
* returning nothing: each empty result leaves the history unchanged, so the
|
|
1599
|
+
* next prune cycle re-triggers on the same state and the run burns its
|
|
1600
|
+
* recursion budget on empty summary steps. Summarization stays off for the
|
|
1601
|
+
* remainder of the run; `reset()` restores it for the next one.
|
|
1602
|
+
*/
|
|
1603
|
+
get summarizationExhausted(): boolean {
|
|
1604
|
+
return this._summarizationFailures >= MAX_SUMMARIZATION_FAILURES;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1550
1607
|
get overflowRecoveryAttempts(): number {
|
|
1551
1608
|
return this._overflowRecoveryAttempts;
|
|
1552
1609
|
}
|
package/src/common/constants.ts
CHANGED
|
@@ -20,6 +20,14 @@ export const DEFAULT_TOOL_TOKEN_MULTIPLIER = 1.4;
|
|
|
20
20
|
*/
|
|
21
21
|
export const DEFAULT_MAX_SEALS = 8;
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Default ceiling on Stop-hook continuations within one Run. A blocking Stop
|
|
25
|
+
* hook can keep a naturally terminal run warm by injecting another user turn;
|
|
26
|
+
* the ceiling prevents a faulty hook or continuously arriving input from
|
|
27
|
+
* keeping one processStream call alive forever.
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULT_MAX_STOP_CONTINUATIONS = 8;
|
|
30
|
+
|
|
23
31
|
/**
|
|
24
32
|
* Per-hook timeout for `PreemptBoundary`, deliberately far above
|
|
25
33
|
* `DEFAULT_HOOK_TIMEOUT_MS`. A host drain has already popped its queue and
|
|
@@ -42,3 +50,14 @@ export const ACTIVITY_LABEL_RUN_NAME = 'StepLabel';
|
|
|
42
50
|
export const REASONING_LABEL_RUN_NAME = 'ReasoningLabel';
|
|
43
51
|
export const ACTIVITY_PHASE_RUN_NAME = 'MultiStepLabel';
|
|
44
52
|
export const ACTIVITY_PHASE_LABEL_RUN_NAME = 'MultiStepLabelGeneration';
|
|
53
|
+
|
|
54
|
+
/** Shared admission and rendering bounds for compaction navigation hints. */
|
|
55
|
+
export const COMPACTION_SEMANTIC_INDEX_LIMITS = Object.freeze({
|
|
56
|
+
maxInputEntries: 256,
|
|
57
|
+
maxEntries: 64,
|
|
58
|
+
maxEntryChars: 512,
|
|
59
|
+
maxTotalChars: 4_096,
|
|
60
|
+
maxInputTextChars: 4_096,
|
|
61
|
+
maxIdentityChars: 512,
|
|
62
|
+
maxSourceContentIndex: 4_095,
|
|
63
|
+
} as const);
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -153,10 +153,10 @@ import { createLocalCodingToolBundle } from '@/tools/local/LocalCodingTools';
|
|
|
153
153
|
import { SUBAGENT_REPLAY_CONTROLLER } from '@/tools/subagent/SubagentReplay';
|
|
154
154
|
import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
|
|
155
155
|
import { createContextPressureMeter } from '@/llm/contextPressureMeter';
|
|
156
|
-
import { prepareToolsForPromptCache } from '@/llm/promptCacheTools';
|
|
157
156
|
import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
|
|
158
157
|
import { prepareProviderRequest } from '@/llm/prepareProviderRequest';
|
|
159
158
|
import { createCloudflareCodingToolBundle } from '@/tools/cloudflare';
|
|
159
|
+
import { prepareToolsForPromptCache } from '@/llm/promptCacheTools';
|
|
160
160
|
import { providerRequiresStrictAlternation } from '@/llm/providers';
|
|
161
161
|
import { buildSubagentToolParams } from '@/tools/SubagentTool';
|
|
162
162
|
import { initializeLangfuseTracing } from '@/instrumentation';
|
|
@@ -164,6 +164,7 @@ import { shouldTriggerSummarization } from '@/summarization';
|
|
|
164
164
|
import { isRunStepResumeState } from '@/tools/runStepResume';
|
|
165
165
|
import { resolveLocalToolsForBinding } from '@/tools/local';
|
|
166
166
|
import { createSummarizeNode } from '@/summarization/node';
|
|
167
|
+
import { getTruncationStopReason } from '@/llm/truncation';
|
|
167
168
|
import { messagesStateReducer } from '@/messages/reducer';
|
|
168
169
|
import { createSchemaOnlyTools } from '@/tools/schema';
|
|
169
170
|
import { AgentContext } from '@/agents/AgentContext';
|
|
@@ -770,6 +771,9 @@ export abstract class Graph<
|
|
|
770
771
|
contentData: t.RunStep[] = [];
|
|
771
772
|
protected nextContentIndex = 0;
|
|
772
773
|
protected runStepStateRevision = 0;
|
|
774
|
+
protected stopContinuationCount = 0;
|
|
775
|
+
protected stopContinuationExecutionId = '';
|
|
776
|
+
protected streamSegment = 0;
|
|
773
777
|
stepKeyIds: Map<string, string[]> = new Map<string, string[]>();
|
|
774
778
|
contentIndexMap: Map<string, number> = new Map();
|
|
775
779
|
toolCallStepIds: Map<string, string> = new Map();
|
|
@@ -899,6 +903,9 @@ export abstract class Graph<
|
|
|
899
903
|
this.contentData = [];
|
|
900
904
|
this.nextContentIndex = 0;
|
|
901
905
|
this.runStepStateRevision = 0;
|
|
906
|
+
this.stopContinuationCount = 0;
|
|
907
|
+
this.stopContinuationExecutionId = '';
|
|
908
|
+
this.streamSegment = 0;
|
|
902
909
|
this.contentIndexMap = new Map();
|
|
903
910
|
this.stepKeyIds = new Map();
|
|
904
911
|
this.toolCallStepIds.clear();
|
|
@@ -1425,6 +1432,21 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1425
1432
|
* hosts persist it as unfinished rather than complete.
|
|
1426
1433
|
*/
|
|
1427
1434
|
preemptIncomplete = false;
|
|
1435
|
+
/**
|
|
1436
|
+
* True when `routeMessage` sent a turn to `END` because the last AI
|
|
1437
|
+
* message carries no tool call, AND the provider reports it stopped for
|
|
1438
|
+
* hitting the output token ceiling (`getTruncationStopReason`). Plain-text
|
|
1439
|
+
* and reasoning turns cut off this way carry no tool call for
|
|
1440
|
+
* `assertNotTruncatedToolCall` to catch, so `toolsCondition` reads them as
|
|
1441
|
+
* an ordinary finished turn otherwise — hosts read this flag to persist
|
|
1442
|
+
* the turn as unfinished instead of a silently truncated "complete" answer.
|
|
1443
|
+
*
|
|
1444
|
+
* Deliberately separate from `preemptIncomplete`/`preemptHaltReason`: this
|
|
1445
|
+
* has no interaction with the preempt/seal machinery (in particular the
|
|
1446
|
+
* `preemptHaltReason` check at each model node's entry), so setting it
|
|
1447
|
+
* cannot suppress an unrelated agent's turn in a multi-agent graph.
|
|
1448
|
+
*/
|
|
1449
|
+
outputTruncatedIncomplete = false;
|
|
1428
1450
|
/**
|
|
1429
1451
|
* `stopReason` from a `PreemptBoundary` hook that halted the turn.
|
|
1430
1452
|
*
|
|
@@ -1521,6 +1543,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1521
1543
|
this.cachedRunMessages = undefined;
|
|
1522
1544
|
this.cachedDiscoveredTools = undefined;
|
|
1523
1545
|
this.config = resetIfNotEmpty(this.config, undefined);
|
|
1546
|
+
this.stopContinuationCount = 0;
|
|
1547
|
+
this.stopContinuationExecutionId = '';
|
|
1548
|
+
this.streamSegment = 0;
|
|
1524
1549
|
if (keepContent !== true) {
|
|
1525
1550
|
this.contentData = resetIfNotEmpty(this.contentData, []);
|
|
1526
1551
|
this.nextContentIndex = 0;
|
|
@@ -1702,6 +1727,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1702
1727
|
this.preemptEmptyBoundaries = 0;
|
|
1703
1728
|
this.preemptIncomplete = false;
|
|
1704
1729
|
this.preemptHaltReason = undefined;
|
|
1730
|
+
this.outputTruncatedIncomplete = false;
|
|
1705
1731
|
}
|
|
1706
1732
|
|
|
1707
1733
|
/**
|
|
@@ -1818,6 +1844,13 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1818
1844
|
version: 1,
|
|
1819
1845
|
revision: this.runStepStateRevision,
|
|
1820
1846
|
nextIndex: this.nextContentIndex,
|
|
1847
|
+
stopContinuationCount: this.stopContinuationCount,
|
|
1848
|
+
...(this.stopContinuationExecutionId === ''
|
|
1849
|
+
? {}
|
|
1850
|
+
: {
|
|
1851
|
+
stopContinuationExecutionId: this.stopContinuationExecutionId,
|
|
1852
|
+
}),
|
|
1853
|
+
streamSegment: this.streamSegment,
|
|
1821
1854
|
toolCallSteps: [...this.toolCallStepIds].map(([toolCallId, stepId]) => ({
|
|
1822
1855
|
toolCallId,
|
|
1823
1856
|
stepId,
|
|
@@ -1837,6 +1870,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1837
1870
|
|
|
1838
1871
|
this.nextContentIndex = state.nextIndex;
|
|
1839
1872
|
this.runStepStateRevision = state.revision;
|
|
1873
|
+
this.stopContinuationCount = state.stopContinuationCount ?? 0;
|
|
1874
|
+
this.stopContinuationExecutionId =
|
|
1875
|
+
state.stopContinuationExecutionId ?? '';
|
|
1876
|
+
this.streamSegment = state.streamSegment ?? 0;
|
|
1840
1877
|
for (const { toolCallId, stepId } of state.toolCallSteps) {
|
|
1841
1878
|
this.toolCallStepIds.set(toolCallId, stepId);
|
|
1842
1879
|
}
|
|
@@ -1871,6 +1908,30 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1871
1908
|
return undefined;
|
|
1872
1909
|
}
|
|
1873
1910
|
|
|
1911
|
+
getStopContinuationCount(): number {
|
|
1912
|
+
return this.stopContinuationCount;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
setStopContinuationCount(count: number): void {
|
|
1916
|
+
this.stopContinuationCount = count;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
getStopContinuationExecutionId(): string {
|
|
1920
|
+
return this.stopContinuationExecutionId;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
startStopContinuationExecution(executionId: string): void {
|
|
1924
|
+
this.stopContinuationExecutionId = executionId;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
getStreamSegment(): number {
|
|
1928
|
+
return this.streamSegment;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
advanceStreamSegment(): void {
|
|
1932
|
+
this.streamSegment += 1;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1874
1935
|
/**
|
|
1875
1936
|
* Derives the same lane key `dispatchRunStep` stamps as `runStep.agentId`.
|
|
1876
1937
|
* The multi-agent check gates the lookup because `getAgentContext` signals
|
|
@@ -2319,6 +2380,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2319
2380
|
metadata.langgraph_node as string,
|
|
2320
2381
|
metadata.langgraph_step as number,
|
|
2321
2382
|
checkpointNs,
|
|
2383
|
+
this.streamSegment,
|
|
2322
2384
|
];
|
|
2323
2385
|
|
|
2324
2386
|
return keyList;
|
|
@@ -2801,9 +2863,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2801
2863
|
provider,
|
|
2802
2864
|
clientOptions,
|
|
2803
2865
|
tools,
|
|
2804
|
-
isDeferred: makeIsDeferred(
|
|
2805
|
-
agentContext.getEffectiveToolDefinitions()
|
|
2806
|
-
),
|
|
2866
|
+
isDeferred: makeIsDeferred(agentContext.getEffectiveToolDefinitions()),
|
|
2807
2867
|
});
|
|
2808
2868
|
}
|
|
2809
2869
|
|
|
@@ -3030,9 +3090,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3030
3090
|
messagesToRefine.length > 0;
|
|
3031
3091
|
|
|
3032
3092
|
if (hasPrunedMessages) {
|
|
3033
|
-
const shouldSkip =
|
|
3034
|
-
|
|
3035
|
-
|
|
3093
|
+
const shouldSkip =
|
|
3094
|
+
agentContext.summarizationExhausted ||
|
|
3095
|
+
agentContext.shouldSkipSummarization(messages.length);
|
|
3036
3096
|
const triggerResult =
|
|
3037
3097
|
!shouldSkip &&
|
|
3038
3098
|
shouldTriggerSummarization({
|
|
@@ -3089,6 +3149,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3089
3149
|
messageCount: messages.length,
|
|
3090
3150
|
messagesToRefineCount: messagesToRefine.length,
|
|
3091
3151
|
contextLength: context.length,
|
|
3152
|
+
summarizationFailures: agentContext.summarizationFailures,
|
|
3153
|
+
summarizationExhausted: agentContext.summarizationExhausted,
|
|
3092
3154
|
},
|
|
3093
3155
|
{ runId: this.runId, agentId }
|
|
3094
3156
|
);
|
|
@@ -4818,11 +4880,27 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4818
4880
|
if (state.summarizationRequest != null) {
|
|
4819
4881
|
return summarizeNode;
|
|
4820
4882
|
}
|
|
4821
|
-
|
|
4883
|
+
const decision = toolsCondition(
|
|
4822
4884
|
state as t.BaseGraphState,
|
|
4823
4885
|
toolNode,
|
|
4824
4886
|
this.invokedToolIds
|
|
4825
4887
|
);
|
|
4888
|
+
/**
|
|
4889
|
+
* `toolsCondition` only looks at `tool_calls` — a plain-text/reasoning
|
|
4890
|
+
* turn cut off by the output token ceiling has none, so it reads as an
|
|
4891
|
+
* ordinary finished turn and routes here to END. Flag it so hosts can
|
|
4892
|
+
* tell a genuinely finished answer from one the model never got to
|
|
4893
|
+
* complete. See `outputTruncatedIncomplete` for why this stays clear
|
|
4894
|
+
* of the preempt/seal halt fields.
|
|
4895
|
+
*/
|
|
4896
|
+
if (decision === END) {
|
|
4897
|
+
const { messages } = state as t.BaseGraphState;
|
|
4898
|
+
const lastMessage = messages[messages.length - 1];
|
|
4899
|
+
if (getTruncationStopReason(lastMessage) != null) {
|
|
4900
|
+
this.outputTruncatedIncomplete = true;
|
|
4901
|
+
}
|
|
4902
|
+
}
|
|
4903
|
+
return decision;
|
|
4826
4904
|
};
|
|
4827
4905
|
|
|
4828
4906
|
const StateAnnotation = Annotation.Root({
|
|
@@ -43,7 +43,9 @@ type MatcherBucket = Partial<Record<HookEvent, HookMatcher<HookEvent>[]>>;
|
|
|
43
43
|
/**
|
|
44
44
|
* Events whose hooks can change a tool call's input or output. Presence of
|
|
45
45
|
* any of these disables eager tool execution and early completion emission;
|
|
46
|
-
*
|
|
46
|
+
* hooks that cannot rewrite a tool result (`PostToolBatch`, `Stop`, telemetry
|
|
47
|
+
* hooks) do not. A Stop hook may continue the Run, but only after every tool
|
|
48
|
+
* result in the terminal graph segment is already authoritative.
|
|
47
49
|
*/
|
|
48
50
|
const RESULT_ALTERING_HOOK_EVENTS = [
|
|
49
51
|
'PreToolUse',
|
|
@@ -313,6 +313,9 @@ function applyAggregatedResult(
|
|
|
313
313
|
target.additionalContexts.push(...source.additionalContexts);
|
|
314
314
|
target.injectedMessages.push(...source.injectedMessages);
|
|
315
315
|
target.errors.push(...source.errors);
|
|
316
|
+
if (source.hasHookFailures === true) {
|
|
317
|
+
target.hasHookFailures = true;
|
|
318
|
+
}
|
|
316
319
|
if (source.decision != null) {
|
|
317
320
|
applyToolDecision(target, source.decision, source.reason);
|
|
318
321
|
}
|
|
@@ -347,6 +350,19 @@ function combineAggregatedResults(
|
|
|
347
350
|
return combined;
|
|
348
351
|
}
|
|
349
352
|
|
|
353
|
+
/** Deterministically fold results from serialized hook phases. */
|
|
354
|
+
export function mergeAggregatedHookResults(
|
|
355
|
+
...results: Array<AggregatedHookResult | undefined>
|
|
356
|
+
): AggregatedHookResult | undefined {
|
|
357
|
+
let combined: AggregatedHookResult | undefined;
|
|
358
|
+
for (const result of results) {
|
|
359
|
+
if (result != null) {
|
|
360
|
+
combined = combineAggregatedResults(combined, result);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return combined;
|
|
364
|
+
}
|
|
365
|
+
|
|
350
366
|
function createPendingApprovalReplay(
|
|
351
367
|
approvalResult: AggregatedHookResult,
|
|
352
368
|
pendingApproval: AggregatedHookResult | undefined,
|
|
@@ -377,6 +393,7 @@ function fold(outcomes: readonly HookOutcome[]): {
|
|
|
377
393
|
for (const outcome of outcomes) {
|
|
378
394
|
const isOnceMatcher = outcome.matcher.once === true;
|
|
379
395
|
if (outcome.error !== null) {
|
|
396
|
+
aggregated.hasHookFailures = true;
|
|
380
397
|
if (outcome.matcher.internal !== true) {
|
|
381
398
|
aggregated.errors.push(outcome.error);
|
|
382
399
|
}
|
|
@@ -455,8 +472,9 @@ function fold(outcomes: readonly HookOutcome[]): {
|
|
|
455
472
|
* ## Internal matchers
|
|
456
473
|
*
|
|
457
474
|
* A matcher with `internal: true` is excluded from both the `errors` array
|
|
458
|
-
* and the logger output.
|
|
459
|
-
*
|
|
475
|
+
* and the logger output. `hasHookFailures` still records that the call was
|
|
476
|
+
* not fully successful, so fail-closed infrastructure boundaries can reject
|
|
477
|
+
* without exposing the suppressed diagnostic.
|
|
460
478
|
*
|
|
461
479
|
* ## Once semantics — atomic at-most-once
|
|
462
480
|
*
|
package/src/hooks/index.ts
CHANGED
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
// `StandardGraph.createCallModel` (PreemptBoundary).
|
|
10
10
|
export { HookRegistry } from './HookRegistry';
|
|
11
11
|
export type { HookHaltSignal } from './HookRegistry';
|
|
12
|
-
export {
|
|
12
|
+
export {
|
|
13
|
+
executeHooks,
|
|
14
|
+
mergeAggregatedHookResults,
|
|
15
|
+
DEFAULT_HOOK_TIMEOUT_MS,
|
|
16
|
+
} from './executeHooks';
|
|
13
17
|
/**
|
|
14
18
|
* Feature probe for hosts: hook outputs support `injectedMessages`
|
|
15
19
|
* (per-message graph-state injection at the `PostToolBatch` boundary).
|
|
@@ -28,6 +32,11 @@ export const HOOK_INJECTED_MESSAGES_CAPABLE = true;
|
|
|
28
32
|
* the user as a dead button rather than as an unsupported feature.
|
|
29
33
|
*/
|
|
30
34
|
export const HOOK_PREEMPT_BOUNDARY_CAPABLE = true;
|
|
35
|
+
/**
|
|
36
|
+
* Feature probe for hosts: a blocking `Stop` hook can inject queued messages
|
|
37
|
+
* and continue within the same `Run.processStream` lifecycle.
|
|
38
|
+
*/
|
|
39
|
+
export const HOOK_STOP_CONTINUATION_CAPABLE = true;
|
|
31
40
|
export {
|
|
32
41
|
matchesQuery,
|
|
33
42
|
hasNestedQuantifier,
|
|
@@ -71,6 +80,7 @@ export type {
|
|
|
71
80
|
SubagentStartHookInput,
|
|
72
81
|
SubagentStopHookInput,
|
|
73
82
|
StopHookInput,
|
|
83
|
+
StopFinalizeHookInput,
|
|
74
84
|
StopFailureHookInput,
|
|
75
85
|
PreCompactHookInput,
|
|
76
86
|
PostCompactHookInput,
|
|
@@ -85,6 +95,7 @@ export type {
|
|
|
85
95
|
SubagentStartHookOutput,
|
|
86
96
|
SubagentStopHookOutput,
|
|
87
97
|
StopHookOutput,
|
|
98
|
+
StopFinalizeHookOutput,
|
|
88
99
|
StopFailureHookOutput,
|
|
89
100
|
PreCompactHookOutput,
|
|
90
101
|
PostCompactHookOutput,
|
package/src/hooks/types.ts
CHANGED
|
@@ -22,6 +22,7 @@ export const HOOK_EVENTS = [
|
|
|
22
22
|
'SubagentStart',
|
|
23
23
|
'SubagentStop',
|
|
24
24
|
'Stop',
|
|
25
|
+
'StopFinalize',
|
|
25
26
|
'StopFailure',
|
|
26
27
|
'PreCompact',
|
|
27
28
|
'PostCompact',
|
|
@@ -209,7 +210,34 @@ export interface StopHookInput extends BaseHookInput {
|
|
|
209
210
|
hook_event_name: 'Stop';
|
|
210
211
|
messages: BaseMessage[];
|
|
211
212
|
stopReason?: string;
|
|
213
|
+
/** True after this hook has already kept the current Run warm once. */
|
|
212
214
|
stopHookActive: boolean;
|
|
215
|
+
/** Number of terminal continuations already admitted in this Run. */
|
|
216
|
+
continuationCount: number;
|
|
217
|
+
/**
|
|
218
|
+
* Remaining continuations the SDK can honor. A host that durably claims
|
|
219
|
+
* queued work MUST seal admission instead when this reaches zero.
|
|
220
|
+
*/
|
|
221
|
+
continuationBudgetRemaining: number;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Serialized terminal admission phase. Unlike ordinary `Stop` hooks (which
|
|
226
|
+
* execute in parallel), this fires after their outputs have been folded so a
|
|
227
|
+
* durable host can make one final claim-or-seal decision with full knowledge
|
|
228
|
+
* of whether another hook already kept the Run warm.
|
|
229
|
+
*/
|
|
230
|
+
export interface StopFinalizeHookInput extends BaseHookInput {
|
|
231
|
+
hook_event_name: 'StopFinalize';
|
|
232
|
+
messages: BaseMessage[];
|
|
233
|
+
stopReason?: string;
|
|
234
|
+
stopHookActive: boolean;
|
|
235
|
+
continuationCount: number;
|
|
236
|
+
continuationBudgetRemaining: number;
|
|
237
|
+
/** A regular Stop hook already supplied a valid warm continuation. */
|
|
238
|
+
continuationPlanned: boolean;
|
|
239
|
+
/** A halt reason or Stop output forbids another graph segment. */
|
|
240
|
+
continuationPrevented: boolean;
|
|
213
241
|
}
|
|
214
242
|
|
|
215
243
|
export interface StopFailureHookInput extends BaseHookInput {
|
|
@@ -259,6 +287,7 @@ export type HookInput =
|
|
|
259
287
|
| SubagentStartHookInput
|
|
260
288
|
| SubagentStopHookInput
|
|
261
289
|
| StopHookInput
|
|
290
|
+
| StopFinalizeHookInput
|
|
262
291
|
| StopFailureHookInput
|
|
263
292
|
| PreCompactHookInput
|
|
264
293
|
| PostCompactHookInput;
|
|
@@ -276,6 +305,7 @@ export type HookInputByEvent = {
|
|
|
276
305
|
SubagentStart: SubagentStartHookInput;
|
|
277
306
|
SubagentStop: SubagentStopHookInput;
|
|
278
307
|
Stop: StopHookInput;
|
|
308
|
+
StopFinalize: StopFinalizeHookInput;
|
|
279
309
|
StopFailure: StopFailureHookInput;
|
|
280
310
|
PreCompact: PreCompactHookInput;
|
|
281
311
|
PostCompact: PostCompactHookInput;
|
|
@@ -298,10 +328,11 @@ export interface BaseHookOutput {
|
|
|
298
328
|
* mid-run steering message). Accumulated across hooks in registration
|
|
299
329
|
* order.
|
|
300
330
|
*
|
|
301
|
-
* Consumed at
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
* Every other event ignores the
|
|
331
|
+
* Consumed at three dispatch sites, all of which run the same converter so
|
|
332
|
+
* the emitted shapes cannot drift: `PostToolBatch` (the tool boundary),
|
|
333
|
+
* `PreemptBoundary` (a cooperative mid-generation seal), and a blocking
|
|
334
|
+
* `Stop` hook (terminal Run continuation). Every other event ignores the
|
|
335
|
+
* field.
|
|
305
336
|
*/
|
|
306
337
|
injectedMessages?: InjectedMessage[];
|
|
307
338
|
/** True to prevent the next model turn. Any hook can set this. */
|
|
@@ -435,10 +466,18 @@ export interface SubagentStartHookOutput extends BaseHookOutput {
|
|
|
435
466
|
export type SubagentStopHookOutput = BaseHookOutput;
|
|
436
467
|
|
|
437
468
|
export interface StopHookOutput extends BaseHookOutput {
|
|
469
|
+
/**
|
|
470
|
+
* `block` keeps a naturally terminal Run warm only when the output also
|
|
471
|
+
* supplies non-empty `injectedMessages` or `additionalContext`. The SDK
|
|
472
|
+
* starts another graph segment inside the same `processStream` call; no
|
|
473
|
+
* RunStart/UserPromptSubmit hooks or terminal cleanup occur between them.
|
|
474
|
+
*/
|
|
438
475
|
decision?: StopDecision;
|
|
439
476
|
reason?: string;
|
|
440
477
|
}
|
|
441
478
|
|
|
479
|
+
export type StopFinalizeHookOutput = StopHookOutput;
|
|
480
|
+
|
|
442
481
|
export type StopFailureHookOutput = BaseHookOutput;
|
|
443
482
|
|
|
444
483
|
export type PreCompactHookOutput = BaseHookOutput;
|
|
@@ -458,6 +497,7 @@ export type HookOutputByEvent = {
|
|
|
458
497
|
SubagentStart: SubagentStartHookOutput;
|
|
459
498
|
SubagentStop: SubagentStopHookOutput;
|
|
460
499
|
Stop: StopHookOutput;
|
|
500
|
+
StopFinalize: StopFinalizeHookOutput;
|
|
461
501
|
StopFailure: StopFailureHookOutput;
|
|
462
502
|
PreCompact: PreCompactHookOutput;
|
|
463
503
|
PostCompact: PostCompactHookOutput;
|
|
@@ -476,6 +516,7 @@ export type HookOutput =
|
|
|
476
516
|
| SubagentStartHookOutput
|
|
477
517
|
| SubagentStopHookOutput
|
|
478
518
|
| StopHookOutput
|
|
519
|
+
| StopFinalizeHookOutput
|
|
479
520
|
| StopFailureHookOutput
|
|
480
521
|
| PreCompactHookOutput
|
|
481
522
|
| PostCompactHookOutput;
|
|
@@ -593,6 +634,8 @@ export interface AggregatedHookResult {
|
|
|
593
634
|
* `preventContinuation` do not overwrite the reason.
|
|
594
635
|
*/
|
|
595
636
|
stopReason?: string;
|
|
637
|
+
/** True when any hook failed, including internal hooks with hidden diagnostics. */
|
|
638
|
+
hasHookFailures?: true;
|
|
596
639
|
/** Error messages from hooks that threw; always present (possibly empty). */
|
|
597
640
|
errors: string[];
|
|
598
641
|
}
|