@aexol/spectral 0.9.166 → 0.9.169
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/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +7 -0
- package/dist/cli.js +6 -0
- package/dist/commands/ports.d.ts +13 -0
- package/dist/commands/ports.d.ts.map +1 -0
- package/dist/commands/ports.js +110 -0
- package/dist/extensions/ports/index.d.ts +19 -0
- package/dist/extensions/ports/index.d.ts.map +1 -0
- package/dist/extensions/ports/index.js +154 -0
- package/dist/extensions/ports/platform.d.ts +39 -0
- package/dist/extensions/ports/platform.d.ts.map +1 -0
- package/dist/extensions/ports/platform.js +255 -0
- package/dist/memory/hooks/compaction-trigger.js +1 -1
- package/dist/sdk/agent-core/agent-loop.js +1 -1
- package/dist/sdk/coding-agent/core/agent-session.d.ts +21 -1
- package/dist/sdk/coding-agent/core/agent-session.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/agent-session.js +378 -76
- package/dist/sdk/coding-agent/core/compaction/llm-compaction.d.ts +42 -0
- package/dist/sdk/coding-agent/core/compaction/llm-compaction.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/llm-compaction.js +83 -0
- package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/native-extensions.js +11 -0
- package/dist/sdk/coding-agent/core/resource-loader.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/resource-loader.js +23 -1
- package/dist/sdk/coding-agent/core/session-manager.d.ts +50 -1
- package/dist/sdk/coding-agent/core/session-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/session-manager.js +222 -26
- package/dist/sdk/coding-agent/core/settings-manager.d.ts +15 -0
- package/dist/sdk/coding-agent/core/settings-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/settings-manager.js +11 -0
- package/dist/server/agent-bridge.d.ts +8 -6
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +28 -18
- package/dist/server/error-humanizer.d.ts.map +1 -1
- package/dist/server/error-humanizer.js +1 -1
- package/dist/server/wire.d.ts +2 -0
- package/dist/server/wire.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -18,18 +18,19 @@ import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels
|
|
|
18
18
|
import { stripFrontmatter } from "../utils/frontmatter.js";
|
|
19
19
|
import { resolvePath } from "../utils/paths.js";
|
|
20
20
|
import { sleep } from "../utils/sleep.js";
|
|
21
|
-
import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
|
|
21
|
+
import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage, } from "./auth-guidance.js";
|
|
22
22
|
import { executeBashWithOperations } from "./bash-executor.js";
|
|
23
23
|
import { calculateContextTokens, collectEntriesForBranchSummary, compactDcpLite, estimateContextTokens, estimateCompactionTokenMetrics, getManualCompactionSettings, normalizeCompactionPolicy, normalizeManualKeepRecentTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
|
|
24
|
+
import { enrichDcpCompaction, mergeCompactionObservations } from "./compaction/llm-compaction.js";
|
|
24
25
|
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
|
25
26
|
import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
|
|
26
27
|
import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
27
|
-
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
28
|
-
import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
|
|
28
|
+
import { expandPromptTemplate, } from "./prompt-templates.js";
|
|
29
|
+
import { CURRENT_SESSION_VERSION, getLatestCompactionEntry, } from "./session-manager.js";
|
|
29
30
|
import { createSyntheticSourceInfo } from "./source-info.js";
|
|
30
|
-
import { buildSystemPrompt } from "./system-prompt.js";
|
|
31
|
-
import { mutateSystemPrompt } from "./system-prompt-mutator.js";
|
|
32
|
-
import { createLocalBashOperations } from "./tools/bash.js";
|
|
31
|
+
import { buildSystemPrompt, } from "./system-prompt.js";
|
|
32
|
+
import { mutateSystemPrompt, } from "./system-prompt-mutator.js";
|
|
33
|
+
import { createLocalBashOperations, } from "./tools/bash.js";
|
|
33
34
|
import { createAllToolDefinitions } from "./tools/index.js";
|
|
34
35
|
import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
|
|
35
36
|
/**
|
|
@@ -73,7 +74,8 @@ function createVirtualPhaseBoundaryEntry(pathEntries) {
|
|
|
73
74
|
};
|
|
74
75
|
}
|
|
75
76
|
function isDcpLitePhaseBoundaryEntry(entry) {
|
|
76
|
-
return entry?.type === "custom_message" &&
|
|
77
|
+
return (entry?.type === "custom_message" &&
|
|
78
|
+
entry.customType === DCP_LITE_PHASE_BOUNDARY_CUSTOM_TYPE);
|
|
77
79
|
}
|
|
78
80
|
function isRecord(value) {
|
|
79
81
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -135,9 +137,9 @@ function getNonShrinkingSkipMessage(result) {
|
|
|
135
137
|
return "Compaction skipped: no eligible history to compact.";
|
|
136
138
|
}
|
|
137
139
|
if ((tokensRemoved ?? 0) <= 0 || summaryTokens >= tokensCompacted) {
|
|
138
|
-
return `Compaction skipped: generated summary would not shrink context (` +
|
|
140
|
+
return (`Compaction skipped: generated summary would not shrink context (` +
|
|
139
141
|
`compacted ~${tokensCompacted.toLocaleString()} tokens, ` +
|
|
140
|
-
`summary +~${summaryTokens.toLocaleString()} tokens)
|
|
142
|
+
`summary +~${summaryTokens.toLocaleString()} tokens).`);
|
|
141
143
|
}
|
|
142
144
|
return undefined;
|
|
143
145
|
}
|
|
@@ -153,7 +155,13 @@ function markCompactionSkipped(result, skipReason, message) {
|
|
|
153
155
|
// Constants
|
|
154
156
|
// ============================================================================
|
|
155
157
|
/** Standard thinking levels */
|
|
156
|
-
const THINKING_LEVELS = [
|
|
158
|
+
const THINKING_LEVELS = [
|
|
159
|
+
"off",
|
|
160
|
+
"minimal",
|
|
161
|
+
"low",
|
|
162
|
+
"medium",
|
|
163
|
+
"high",
|
|
164
|
+
];
|
|
157
165
|
const LENGTH_CONTINUATION_PROMPT = "Your previous assistant response ended because the provider hit its maximum output length. Continue exactly from where it stopped. Do not repeat, recap, or summarize earlier content. If the previous response ended mid-sentence, resume with the next missing word; otherwise continue with the next missing content.";
|
|
158
166
|
// ============================================================================
|
|
159
167
|
// AgentSession Class
|
|
@@ -176,6 +184,8 @@ export class AgentSession {
|
|
|
176
184
|
_compactionAbortController = undefined;
|
|
177
185
|
_autoCompactionAbortController = undefined;
|
|
178
186
|
_compactionInProgress = false;
|
|
187
|
+
_requestTimeJobs = new Map();
|
|
188
|
+
_requestTimeControllers = new Map();
|
|
179
189
|
_overflowRecoveryAttempted = false;
|
|
180
190
|
// Branch summarization state
|
|
181
191
|
_branchSummaryAbortController = undefined;
|
|
@@ -232,9 +242,14 @@ export class AgentSession {
|
|
|
232
242
|
this._modelRegistry = config.modelRegistry;
|
|
233
243
|
this._extensionRunnerRef = config.extensionRunnerRef;
|
|
234
244
|
this._initialActiveToolNames = config.initialActiveToolNames;
|
|
235
|
-
this._allowedToolNames = config.allowedToolNames
|
|
245
|
+
this._allowedToolNames = config.allowedToolNames
|
|
246
|
+
? new Set(config.allowedToolNames)
|
|
247
|
+
: undefined;
|
|
236
248
|
this._baseToolsOverride = config.baseToolsOverride;
|
|
237
|
-
this._sessionStartEvent = config.sessionStartEvent ?? {
|
|
249
|
+
this._sessionStartEvent = config.sessionStartEvent ?? {
|
|
250
|
+
type: "session_start",
|
|
251
|
+
reason: "startup",
|
|
252
|
+
};
|
|
238
253
|
// Always subscribe to agent events for internal handling
|
|
239
254
|
// (session persistence, extensions, auto-compaction, retry logic)
|
|
240
255
|
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
|
|
@@ -243,6 +258,14 @@ export class AgentSession {
|
|
|
243
258
|
activeToolNames: this._initialActiveToolNames,
|
|
244
259
|
includeAllExtensionTools: true,
|
|
245
260
|
});
|
|
261
|
+
// Recover journaled request-time workers after a process reload. The
|
|
262
|
+
// journal is authoritative; only pending/running jobs are resumed.
|
|
263
|
+
for (const job of this.sessionManager.getRequestTimeCompactionJobs()) {
|
|
264
|
+
this._requestTimeJobs.set(job.snapshot.compactionId, job);
|
|
265
|
+
const controller = new AbortController();
|
|
266
|
+
this._requestTimeControllers.set(job.snapshot.compactionId, controller);
|
|
267
|
+
void this._runRequestTimeCompaction(job, controller).catch(() => undefined);
|
|
268
|
+
}
|
|
246
269
|
}
|
|
247
270
|
/** Model registry for API key resolution and model discovery */
|
|
248
271
|
get modelRegistry() {
|
|
@@ -366,7 +389,9 @@ export class AgentSession {
|
|
|
366
389
|
// Emit to extensions first
|
|
367
390
|
await this._emitExtensionEvent(event);
|
|
368
391
|
// Notify all listeners
|
|
369
|
-
this._emit(event.type === "agent_end"
|
|
392
|
+
this._emit(event.type === "agent_end"
|
|
393
|
+
? { ...event, willRetry: this._willRetryAfterAgentEnd(event) }
|
|
394
|
+
: event);
|
|
370
395
|
// Handle session persistence
|
|
371
396
|
if (event.type === "message_end") {
|
|
372
397
|
this._persistMessageEnd(event.message);
|
|
@@ -397,7 +422,8 @@ export class AgentSession {
|
|
|
397
422
|
// Persist as CustomMessageEntry
|
|
398
423
|
this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
|
|
399
424
|
}
|
|
400
|
-
else if ((message.role === "user" &&
|
|
425
|
+
else if ((message.role === "user" &&
|
|
426
|
+
!this._shouldSkipMessagePersistence(message)) ||
|
|
401
427
|
message.role === "assistant" ||
|
|
402
428
|
message.role === "toolResult") {
|
|
403
429
|
// Regular LLM message - persist as SessionMessageEntry
|
|
@@ -405,7 +431,8 @@ export class AgentSession {
|
|
|
405
431
|
}
|
|
406
432
|
}
|
|
407
433
|
_handleUserMessageStart(messageText, message) {
|
|
408
|
-
const isInternalLengthContinuation = messageText === LENGTH_CONTINUATION_PROMPT &&
|
|
434
|
+
const isInternalLengthContinuation = messageText === LENGTH_CONTINUATION_PROMPT &&
|
|
435
|
+
this._pendingInternalLengthContinuationMessages > 0;
|
|
409
436
|
if (isInternalLengthContinuation) {
|
|
410
437
|
this._pendingInternalLengthContinuationMessages--;
|
|
411
438
|
this._internalLengthContinuationUserMessages.add(message);
|
|
@@ -483,7 +510,10 @@ export class AgentSession {
|
|
|
483
510
|
await this._extensionRunner.emit({ type: "agent_start" });
|
|
484
511
|
}
|
|
485
512
|
else if (event.type === "agent_end") {
|
|
486
|
-
await this._extensionRunner.emit({
|
|
513
|
+
await this._extensionRunner.emit({
|
|
514
|
+
type: "agent_end",
|
|
515
|
+
messages: event.messages,
|
|
516
|
+
});
|
|
487
517
|
}
|
|
488
518
|
else if (event.type === "turn_start") {
|
|
489
519
|
const extensionEvent = {
|
|
@@ -755,7 +785,9 @@ export class AgentSession {
|
|
|
755
785
|
}
|
|
756
786
|
const loaderSystemPrompt = this._resourceLoader.getSystemPrompt();
|
|
757
787
|
const loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();
|
|
758
|
-
const appendSystemPrompt = loaderAppendSystemPrompt.length > 0
|
|
788
|
+
const appendSystemPrompt = loaderAppendSystemPrompt.length > 0
|
|
789
|
+
? loaderAppendSystemPrompt.join("\n\n")
|
|
790
|
+
: undefined;
|
|
759
791
|
const loadedSkills = this._resourceLoader.getSkills().skills;
|
|
760
792
|
const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
|
|
761
793
|
this._baseSystemPromptOptions = {
|
|
@@ -843,9 +875,19 @@ export class AgentSession {
|
|
|
843
875
|
}
|
|
844
876
|
return await this._checkCompaction(msg);
|
|
845
877
|
}
|
|
878
|
+
_shouldRequestTimeCompact(message) {
|
|
879
|
+
const policy = this._getCompactionPolicy();
|
|
880
|
+
if (!this._isAutoOlderHistoryCompactionEnabled(policy) || message.stopReason === "error")
|
|
881
|
+
return false;
|
|
882
|
+
const contextWindow = this.model?.contextWindow ?? 0;
|
|
883
|
+
if (!contextWindow)
|
|
884
|
+
return false;
|
|
885
|
+
return shouldCompact(calculateContextTokens(message.usage), contextWindow, this._getAutoOlderHistoryCompactionSettings(policy));
|
|
886
|
+
}
|
|
846
887
|
_getCompactionPolicy() {
|
|
847
888
|
const manager = this.settingsManager;
|
|
848
|
-
return manager.getCompactionPolicy?.() ??
|
|
889
|
+
return (manager.getCompactionPolicy?.() ??
|
|
890
|
+
normalizeCompactionPolicy(manager.getCompactionSettings()));
|
|
849
891
|
}
|
|
850
892
|
_shouldAutoContinueLengthStop(message) {
|
|
851
893
|
if (message.stopReason !== "length")
|
|
@@ -878,7 +920,9 @@ export class AgentSession {
|
|
|
878
920
|
this._lastLengthContinuationReason = undefined;
|
|
879
921
|
return false;
|
|
880
922
|
}
|
|
881
|
-
this._lastLengthContinuationReason = truncatedToolCall
|
|
923
|
+
this._lastLengthContinuationReason = truncatedToolCall
|
|
924
|
+
? "truncated_tool_call"
|
|
925
|
+
: undefined;
|
|
882
926
|
return true;
|
|
883
927
|
}
|
|
884
928
|
_getAssistantOutputTokens(message) {
|
|
@@ -937,7 +981,9 @@ export class AgentSession {
|
|
|
937
981
|
// All values empty/blank => the arguments were only partially emitted.
|
|
938
982
|
return keys.every((key) => {
|
|
939
983
|
const value = args[key];
|
|
940
|
-
return value === undefined ||
|
|
984
|
+
return (value === undefined ||
|
|
985
|
+
value === null ||
|
|
986
|
+
(typeof value === "string" && value.trim() === ""));
|
|
941
987
|
});
|
|
942
988
|
});
|
|
943
989
|
}
|
|
@@ -994,7 +1040,9 @@ export class AgentSession {
|
|
|
994
1040
|
let expandedText = currentText;
|
|
995
1041
|
if (expandPromptTemplates) {
|
|
996
1042
|
expandedText = this._expandSkillCommand(expandedText);
|
|
997
|
-
expandedText = expandPromptTemplate(expandedText, [
|
|
1043
|
+
expandedText = expandPromptTemplate(expandedText, [
|
|
1044
|
+
...this.promptTemplates,
|
|
1045
|
+
]);
|
|
998
1046
|
}
|
|
999
1047
|
// If streaming, queue via steer() or followUp() based on option
|
|
1000
1048
|
if (this.isStreaming) {
|
|
@@ -1027,7 +1075,20 @@ export class AgentSession {
|
|
|
1027
1075
|
}
|
|
1028
1076
|
// Check if we need to compact before sending (catches aborted responses)
|
|
1029
1077
|
const lastAssistant = this._findLastAssistantMessage();
|
|
1030
|
-
|
|
1078
|
+
// This is the production request-time cut point: the previous agent run
|
|
1079
|
+
// has ended, but the next request has not started. Snapshotting here is
|
|
1080
|
+
// non-blocking and leaves overflow recovery on its existing path.
|
|
1081
|
+
const requestTimeScheduled = lastAssistant !== undefined &&
|
|
1082
|
+
lastAssistant.stopReason !== "aborted" &&
|
|
1083
|
+
this._shouldRequestTimeCompact(lastAssistant);
|
|
1084
|
+
if (requestTimeScheduled) {
|
|
1085
|
+
this.requestTimeCompact({
|
|
1086
|
+
targetTurnId: this.sessionManager.getLeafId() ?? undefined,
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
if (lastAssistant &&
|
|
1090
|
+
!requestTimeScheduled &&
|
|
1091
|
+
(await this._checkCompaction(lastAssistant, false))) {
|
|
1031
1092
|
try {
|
|
1032
1093
|
await this.agent.continue();
|
|
1033
1094
|
while (await this._handlePostAgentRun()) {
|
|
@@ -1041,7 +1102,9 @@ export class AgentSession {
|
|
|
1041
1102
|
// Build messages array (custom message if any, then user message)
|
|
1042
1103
|
messages = [];
|
|
1043
1104
|
// Add user message
|
|
1044
|
-
const userContent = [
|
|
1105
|
+
const userContent = [
|
|
1106
|
+
{ type: "text", text: expandedText },
|
|
1107
|
+
];
|
|
1045
1108
|
if (currentImages) {
|
|
1046
1109
|
userContent.push(...currentImages);
|
|
1047
1110
|
}
|
|
@@ -1127,7 +1190,9 @@ export class AgentSession {
|
|
|
1127
1190
|
const spaceIndex = text.indexOf(" ");
|
|
1128
1191
|
const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
|
|
1129
1192
|
const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
|
|
1130
|
-
const skill = this.resourceLoader
|
|
1193
|
+
const skill = this.resourceLoader
|
|
1194
|
+
.getSkills()
|
|
1195
|
+
.skills.find((s) => s.name === skillName);
|
|
1131
1196
|
if (!skill)
|
|
1132
1197
|
return text; // Unknown skill, pass through
|
|
1133
1198
|
try {
|
|
@@ -1161,7 +1226,9 @@ export class AgentSession {
|
|
|
1161
1226
|
}
|
|
1162
1227
|
// Expand skill commands and prompt templates
|
|
1163
1228
|
let expandedText = this._expandSkillCommand(text);
|
|
1164
|
-
expandedText = expandPromptTemplate(expandedText, [
|
|
1229
|
+
expandedText = expandPromptTemplate(expandedText, [
|
|
1230
|
+
...this.promptTemplates,
|
|
1231
|
+
]);
|
|
1165
1232
|
await this._queueSteer(expandedText, images);
|
|
1166
1233
|
}
|
|
1167
1234
|
/**
|
|
@@ -1178,7 +1245,9 @@ export class AgentSession {
|
|
|
1178
1245
|
}
|
|
1179
1246
|
// Expand skill commands and prompt templates
|
|
1180
1247
|
let expandedText = this._expandSkillCommand(text);
|
|
1181
|
-
expandedText = expandPromptTemplate(expandedText, [
|
|
1248
|
+
expandedText = expandPromptTemplate(expandedText, [
|
|
1249
|
+
...this.promptTemplates,
|
|
1250
|
+
]);
|
|
1182
1251
|
await this._queueFollowUp(expandedText, images);
|
|
1183
1252
|
}
|
|
1184
1253
|
/**
|
|
@@ -1392,7 +1461,9 @@ export class AgentSession {
|
|
|
1392
1461
|
if (currentIndex === -1)
|
|
1393
1462
|
currentIndex = 0;
|
|
1394
1463
|
const len = scopedModels.length;
|
|
1395
|
-
const nextIndex = direction === "forward"
|
|
1464
|
+
const nextIndex = direction === "forward"
|
|
1465
|
+
? (currentIndex + 1) % len
|
|
1466
|
+
: (currentIndex - 1 + len) % len;
|
|
1396
1467
|
const next = scopedModels[nextIndex];
|
|
1397
1468
|
const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
|
|
1398
1469
|
// Apply model
|
|
@@ -1405,7 +1476,11 @@ export class AgentSession {
|
|
|
1405
1476
|
// setThinkingLevel clamps to model capabilities.
|
|
1406
1477
|
this.setThinkingLevel(thinkingLevel);
|
|
1407
1478
|
await this._emitModelSelect(next.model, currentModel, "cycle");
|
|
1408
|
-
return {
|
|
1479
|
+
return {
|
|
1480
|
+
model: next.model,
|
|
1481
|
+
thinkingLevel: this.thinkingLevel,
|
|
1482
|
+
isScoped: true,
|
|
1483
|
+
};
|
|
1409
1484
|
}
|
|
1410
1485
|
async _cycleAvailableModel(direction) {
|
|
1411
1486
|
const availableModels = await this._modelRegistry.getAvailable();
|
|
@@ -1416,7 +1491,9 @@ export class AgentSession {
|
|
|
1416
1491
|
if (currentIndex === -1)
|
|
1417
1492
|
currentIndex = 0;
|
|
1418
1493
|
const len = availableModels.length;
|
|
1419
|
-
const nextIndex = direction === "forward"
|
|
1494
|
+
const nextIndex = direction === "forward"
|
|
1495
|
+
? (currentIndex + 1) % len
|
|
1496
|
+
: (currentIndex - 1 + len) % len;
|
|
1420
1497
|
const nextModel = availableModels[nextIndex];
|
|
1421
1498
|
const thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1422
1499
|
this.agent.state.model = nextModel;
|
|
@@ -1425,7 +1502,11 @@ export class AgentSession {
|
|
|
1425
1502
|
// Re-clamp thinking level for new model's capabilities
|
|
1426
1503
|
this.setThinkingLevel(thinkingLevel);
|
|
1427
1504
|
await this._emitModelSelect(nextModel, currentModel, "cycle");
|
|
1428
|
-
return {
|
|
1505
|
+
return {
|
|
1506
|
+
model: nextModel,
|
|
1507
|
+
thinkingLevel: this.thinkingLevel,
|
|
1508
|
+
isScoped: false,
|
|
1509
|
+
};
|
|
1429
1510
|
}
|
|
1430
1511
|
// =========================================================================
|
|
1431
1512
|
// Thinking Level Management
|
|
@@ -1437,7 +1518,9 @@ export class AgentSession {
|
|
|
1437
1518
|
*/
|
|
1438
1519
|
setThinkingLevel(level) {
|
|
1439
1520
|
const availableLevels = this.getAvailableThinkingLevels();
|
|
1440
|
-
const effectiveLevel = availableLevels.includes(level)
|
|
1521
|
+
const effectiveLevel = availableLevels.includes(level)
|
|
1522
|
+
? level
|
|
1523
|
+
: this._clampThinkingLevel(level, availableLevels);
|
|
1441
1524
|
// Only persist if actually changing
|
|
1442
1525
|
const previousLevel = this.agent.state.thinkingLevel;
|
|
1443
1526
|
const isChanging = effectiveLevel !== previousLevel;
|
|
@@ -1489,12 +1572,14 @@ export class AgentSession {
|
|
|
1489
1572
|
return explicitLevel;
|
|
1490
1573
|
}
|
|
1491
1574
|
if (!this.supportsThinking()) {
|
|
1492
|
-
return this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
|
|
1575
|
+
return (this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);
|
|
1493
1576
|
}
|
|
1494
1577
|
return this.thinkingLevel;
|
|
1495
1578
|
}
|
|
1496
1579
|
_clampThinkingLevel(level, _availableLevels) {
|
|
1497
|
-
return this.model
|
|
1580
|
+
return this.model
|
|
1581
|
+
? clampThinkingLevel(this.model, level)
|
|
1582
|
+
: "off";
|
|
1498
1583
|
}
|
|
1499
1584
|
// =========================================================================
|
|
1500
1585
|
// Queue Mode Management
|
|
@@ -1549,6 +1634,149 @@ export class AgentSession {
|
|
|
1549
1634
|
_buildDcpCompactionResult(preparation, extensionCompaction) {
|
|
1550
1635
|
return preserveDcpHookMemoryDetails(compactDcpLite(preparation), extensionCompaction);
|
|
1551
1636
|
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Schedule a durable request-time overlay. Snapshot and commit are short,
|
|
1639
|
+
* synchronous sections; summary generation never holds the normal compaction
|
|
1640
|
+
* lock and therefore cannot delay the next provider request.
|
|
1641
|
+
*/
|
|
1642
|
+
requestTimeCompact(options = {}) {
|
|
1643
|
+
const snapshot = this.sessionManager.createRequestTimeSnapshot(options.targetTurnId);
|
|
1644
|
+
const previous = this._requestTimeJobs.get(snapshot.compactionId);
|
|
1645
|
+
if (previous)
|
|
1646
|
+
return previous;
|
|
1647
|
+
const job = { snapshot, status: "pending" };
|
|
1648
|
+
this._requestTimeJobs.set(snapshot.compactionId, job);
|
|
1649
|
+
this.sessionManager.persistRequestTimeCompactionJob(job);
|
|
1650
|
+
const controller = new AbortController();
|
|
1651
|
+
this._requestTimeControllers.set(snapshot.compactionId, controller);
|
|
1652
|
+
if (options.signal) {
|
|
1653
|
+
if (options.signal.aborted)
|
|
1654
|
+
controller.abort();
|
|
1655
|
+
else
|
|
1656
|
+
options.signal.addEventListener("abort", () => controller.abort(), {
|
|
1657
|
+
once: true,
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
void this._runRequestTimeCompaction(job, controller).catch(() => undefined);
|
|
1661
|
+
return job;
|
|
1662
|
+
}
|
|
1663
|
+
async _runRequestTimeCompaction(job, controller) {
|
|
1664
|
+
job.status = "running";
|
|
1665
|
+
this.sessionManager.persistRequestTimeCompactionJob(job);
|
|
1666
|
+
try {
|
|
1667
|
+
if (controller.signal.aborted) {
|
|
1668
|
+
job.status = "aborted";
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
const settings = this._getAutoOlderHistoryCompactionSettings(this._getCompactionPolicy());
|
|
1672
|
+
const entries = job.snapshot.entries;
|
|
1673
|
+
const preparation = prepareCompaction(entries, settings, {
|
|
1674
|
+
phaseBoundary: false,
|
|
1675
|
+
});
|
|
1676
|
+
if (!preparation) {
|
|
1677
|
+
job.status = "failed";
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
// DCP-lite is deterministic and safe to execute away from the session
|
|
1681
|
+
// lock. Hook observations, when registered, are folded into details.
|
|
1682
|
+
let hook;
|
|
1683
|
+
let lastError;
|
|
1684
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1685
|
+
if (controller.signal.aborted) {
|
|
1686
|
+
job.status = "aborted";
|
|
1687
|
+
this.sessionManager.persistRequestTimeCompactionJob(job);
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
try {
|
|
1691
|
+
hook = await this._emitSessionBeforeCompact(preparation, entries, undefined, controller.signal, () => undefined);
|
|
1692
|
+
lastError = undefined;
|
|
1693
|
+
break;
|
|
1694
|
+
}
|
|
1695
|
+
catch (error) {
|
|
1696
|
+
lastError = error;
|
|
1697
|
+
if (!this._isRequestTimeRetryable(error) || attempt === 2)
|
|
1698
|
+
break;
|
|
1699
|
+
await sleep(50 * 2 ** attempt, controller.signal);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
if (lastError !== undefined)
|
|
1703
|
+
throw lastError;
|
|
1704
|
+
if (controller.signal.aborted) {
|
|
1705
|
+
job.status = "aborted";
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
const result = this._buildDcpCompactionResult(preparation, hook?.cancel ? undefined : hook?.compaction);
|
|
1709
|
+
if (this._markIfNonShrinking(result)) {
|
|
1710
|
+
job.status = "failed";
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
const llmSettings = this.settingsManager.getLlmCompactionSettings();
|
|
1714
|
+
const persistedObservations = entries
|
|
1715
|
+
.filter((entry) => entry.type === "compaction")
|
|
1716
|
+
.flatMap((entry) => this._extractCompactionObservations(entry.details));
|
|
1717
|
+
const durableObservations = mergeCompactionObservations(persistedObservations, this._extractCompactionObservations(hook?.compaction?.details));
|
|
1718
|
+
const finalResult = await enrichDcpCompaction(result, durableObservations, llmSettings, async () => {
|
|
1719
|
+
try {
|
|
1720
|
+
const configured = llmSettings.provider && llmSettings.modelId
|
|
1721
|
+
? this._modelRegistry.find(llmSettings.provider, llmSettings.modelId)
|
|
1722
|
+
: this.model;
|
|
1723
|
+
if (!configured)
|
|
1724
|
+
return undefined;
|
|
1725
|
+
const auth = await this._modelRegistry.getApiKeyAndHeaders(configured);
|
|
1726
|
+
return auth.ok && auth.apiKey ? { model: configured, apiKey: auth.apiKey, headers: auth.headers } : undefined;
|
|
1727
|
+
}
|
|
1728
|
+
catch {
|
|
1729
|
+
return undefined;
|
|
1730
|
+
}
|
|
1731
|
+
}, controller.signal);
|
|
1732
|
+
const committed = this.sessionManager.commitRequestTimeCompaction(job.snapshot, finalResult);
|
|
1733
|
+
job.status = committed.status === "stale" ? "stale" : "committed";
|
|
1734
|
+
this.sessionManager.persistRequestTimeCompactionJob(job);
|
|
1735
|
+
if (committed.status === "committed") {
|
|
1736
|
+
// Refresh provider state only after the durable entry exists. UI callers
|
|
1737
|
+
// continue to use getFullHistory(), which remains append-only.
|
|
1738
|
+
this.agent.state.messages =
|
|
1739
|
+
this.sessionManager.buildSessionContext().messages;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
catch (error) {
|
|
1743
|
+
job.status =
|
|
1744
|
+
controller.signal.aborted ||
|
|
1745
|
+
(error instanceof Error && error.name === "AbortError")
|
|
1746
|
+
? "aborted"
|
|
1747
|
+
: "failed";
|
|
1748
|
+
this.sessionManager.persistRequestTimeCompactionJob(job);
|
|
1749
|
+
}
|
|
1750
|
+
finally {
|
|
1751
|
+
this._requestTimeControllers.delete(job.snapshot.compactionId);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
_extractCompactionObservations(details) {
|
|
1755
|
+
if (!isRecord(details) || !Array.isArray(details.observations))
|
|
1756
|
+
return [];
|
|
1757
|
+
return details.observations.filter((item) => isRecord(item) && typeof item.content === "string").map((item) => ({
|
|
1758
|
+
id: typeof item.id === "string" ? item.id : undefined,
|
|
1759
|
+
content: item.content,
|
|
1760
|
+
relevance: typeof item.relevance === "string" ? item.relevance : undefined,
|
|
1761
|
+
sourceEntryIds: Array.isArray(item.sourceEntryIds) ? item.sourceEntryIds.filter((id) => typeof id === "string") : undefined,
|
|
1762
|
+
}));
|
|
1763
|
+
}
|
|
1764
|
+
_isRequestTimeRetryable(error) {
|
|
1765
|
+
if (error instanceof Error && error.name === "AbortError")
|
|
1766
|
+
return false;
|
|
1767
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
1768
|
+
return /timeout|temporar|network|rate.?limit|429|500|502|503|504|overload/i.test(text);
|
|
1769
|
+
}
|
|
1770
|
+
abortRequestTimeCompaction(compactionId) {
|
|
1771
|
+
const controller = this._requestTimeControllers.get(compactionId);
|
|
1772
|
+
if (!controller)
|
|
1773
|
+
return false;
|
|
1774
|
+
controller.abort();
|
|
1775
|
+
return true;
|
|
1776
|
+
}
|
|
1777
|
+
getRequestTimeCompaction(compactionId) {
|
|
1778
|
+
return this._requestTimeJobs.get(compactionId);
|
|
1779
|
+
}
|
|
1552
1780
|
_emitCompactionResultSummary(result) {
|
|
1553
1781
|
if (result.summary) {
|
|
1554
1782
|
this._emit({
|
|
@@ -1560,7 +1788,9 @@ export class AgentSession {
|
|
|
1560
1788
|
}
|
|
1561
1789
|
_markIfNonShrinking(result) {
|
|
1562
1790
|
const message = getNonShrinkingSkipMessage(result);
|
|
1563
|
-
return message
|
|
1791
|
+
return message
|
|
1792
|
+
? markCompactionSkipped(result, "non_shrinking", message)
|
|
1793
|
+
: undefined;
|
|
1564
1794
|
}
|
|
1565
1795
|
async _emitSessionBeforeCompact(preparation, branchEntries, customInstructions, signal, emitProgress) {
|
|
1566
1796
|
const runner = this._extensionRunner;
|
|
@@ -1579,7 +1809,7 @@ export class AgentSession {
|
|
|
1579
1809
|
* Execute a manual compaction under the compaction lock.
|
|
1580
1810
|
*/
|
|
1581
1811
|
async _executeCompaction(options) {
|
|
1582
|
-
const { customInstructions, phaseBoundary, keepRecentTokens, memoryHookMode } = normalizeManualCompactionOptions(options);
|
|
1812
|
+
const { customInstructions, phaseBoundary, keepRecentTokens, memoryHookMode, } = normalizeManualCompactionOptions(options);
|
|
1583
1813
|
this._disconnectFromAgent();
|
|
1584
1814
|
await this.abort();
|
|
1585
1815
|
this._compactionAbortController = new AbortController();
|
|
@@ -1588,14 +1818,23 @@ export class AgentSession {
|
|
|
1588
1818
|
let pathEntries = this.sessionManager.getBranch();
|
|
1589
1819
|
const policy = this._getCompactionPolicy();
|
|
1590
1820
|
let settings = getManualCompactionSettings(policy.conversation, policy.manual);
|
|
1591
|
-
if (typeof keepRecentTokens === "number" &&
|
|
1821
|
+
if (typeof keepRecentTokens === "number" &&
|
|
1822
|
+
Number.isFinite(keepRecentTokens) &&
|
|
1823
|
+
keepRecentTokens > 0) {
|
|
1592
1824
|
const normalizedKeepRecentTokens = normalizeManualKeepRecentTokens(keepRecentTokens, policy.manual);
|
|
1593
1825
|
if (normalizedKeepRecentTokens > 0) {
|
|
1594
|
-
settings = {
|
|
1826
|
+
settings = {
|
|
1827
|
+
...settings,
|
|
1828
|
+
keepRecentTokens: normalizedKeepRecentTokens,
|
|
1829
|
+
};
|
|
1595
1830
|
}
|
|
1596
1831
|
}
|
|
1597
|
-
if (phaseBoundary &&
|
|
1598
|
-
|
|
1832
|
+
if (phaseBoundary &&
|
|
1833
|
+
!isDcpLitePhaseBoundaryEntry(pathEntries[pathEntries.length - 1])) {
|
|
1834
|
+
const preflightEntries = [
|
|
1835
|
+
...pathEntries,
|
|
1836
|
+
createVirtualPhaseBoundaryEntry(pathEntries),
|
|
1837
|
+
];
|
|
1599
1838
|
const preflightPreparation = prepareCompaction(preflightEntries, settings, { phaseBoundary: true });
|
|
1600
1839
|
if (!preflightPreparation) {
|
|
1601
1840
|
const lastEntry = pathEntries[pathEntries.length - 1];
|
|
@@ -1626,7 +1865,9 @@ export class AgentSession {
|
|
|
1626
1865
|
});
|
|
1627
1866
|
pathEntries = this.sessionManager.getBranch();
|
|
1628
1867
|
}
|
|
1629
|
-
const preparation = prepareCompaction(pathEntries, settings, {
|
|
1868
|
+
const preparation = prepareCompaction(pathEntries, settings, {
|
|
1869
|
+
phaseBoundary,
|
|
1870
|
+
});
|
|
1630
1871
|
if (!preparation) {
|
|
1631
1872
|
// Check why we can't compact
|
|
1632
1873
|
const lastEntry = pathEntries[pathEntries.length - 1];
|
|
@@ -1641,7 +1882,8 @@ export class AgentSession {
|
|
|
1641
1882
|
const emitExtensionProgress = (delta, content) => {
|
|
1642
1883
|
if (!delta && !content)
|
|
1643
1884
|
return;
|
|
1644
|
-
extensionProgressContent =
|
|
1885
|
+
extensionProgressContent =
|
|
1886
|
+
content ?? `${extensionProgressContent}${delta}`;
|
|
1645
1887
|
this._emit({
|
|
1646
1888
|
type: "compaction_delta",
|
|
1647
1889
|
delta,
|
|
@@ -1698,7 +1940,8 @@ export class AgentSession {
|
|
|
1698
1940
|
}
|
|
1699
1941
|
catch (error) {
|
|
1700
1942
|
const message = error instanceof Error ? error.message : String(error);
|
|
1701
|
-
const aborted = message === "Compaction cancelled" ||
|
|
1943
|
+
const aborted = message === "Compaction cancelled" ||
|
|
1944
|
+
(error instanceof Error && error.name === "AbortError");
|
|
1702
1945
|
this._emit({
|
|
1703
1946
|
type: "compaction_end",
|
|
1704
1947
|
reason: "manual",
|
|
@@ -1750,12 +1993,16 @@ export class AgentSession {
|
|
|
1750
1993
|
// This handles the case where user switched from a smaller-context model (e.g. opus)
|
|
1751
1994
|
// to a larger-context model (e.g. codex) - the overflow error from the old model
|
|
1752
1995
|
// shouldn't trigger compaction for the new model.
|
|
1753
|
-
const sameModel = this.model &&
|
|
1996
|
+
const sameModel = this.model &&
|
|
1997
|
+
assistantMessage.provider === this.model.provider &&
|
|
1998
|
+
assistantMessage.model === this.model.id;
|
|
1754
1999
|
// Skip compaction checks if this assistant message is older than the latest
|
|
1755
2000
|
// compaction boundary. This prevents a stale pre-compaction usage/error
|
|
1756
2001
|
// from retriggering compaction on the first prompt after compaction.
|
|
1757
2002
|
const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
|
|
1758
|
-
const assistantIsFromBeforeCompaction = compactionEntry !== null &&
|
|
2003
|
+
const assistantIsFromBeforeCompaction = compactionEntry !== null &&
|
|
2004
|
+
assistantMessage.timestamp <=
|
|
2005
|
+
new Date(compactionEntry.timestamp).getTime();
|
|
1759
2006
|
if (assistantIsFromBeforeCompaction) {
|
|
1760
2007
|
return false;
|
|
1761
2008
|
}
|
|
@@ -1776,7 +2023,8 @@ export class AgentSession {
|
|
|
1776
2023
|
// Remove the error message from agent state (it IS saved to session for history,
|
|
1777
2024
|
// but we don't want it in context for the retry)
|
|
1778
2025
|
const messages = this.agent.state.messages;
|
|
1779
|
-
if (messages.length > 0 &&
|
|
2026
|
+
if (messages.length > 0 &&
|
|
2027
|
+
messages[messages.length - 1].role === "assistant") {
|
|
1780
2028
|
this.agent.state.messages = messages.slice(0, -1);
|
|
1781
2029
|
}
|
|
1782
2030
|
return await this._runAutoCompaction("overflow", true);
|
|
@@ -1796,7 +2044,8 @@ export class AgentSession {
|
|
|
1796
2044
|
const usageMsg = messages[estimate.lastUsageIndex];
|
|
1797
2045
|
if (compactionEntry &&
|
|
1798
2046
|
usageMsg.role === "assistant" &&
|
|
1799
|
-
usageMsg.timestamp <=
|
|
2047
|
+
usageMsg.timestamp <=
|
|
2048
|
+
new Date(compactionEntry.timestamp).getTime()) {
|
|
1800
2049
|
return false;
|
|
1801
2050
|
}
|
|
1802
2051
|
contextTokens = estimate.tokens;
|
|
@@ -1832,7 +2081,8 @@ export class AgentSession {
|
|
|
1832
2081
|
if (!this._isAutoOlderHistoryCompactionEnabled(policy)) {
|
|
1833
2082
|
return false;
|
|
1834
2083
|
}
|
|
1835
|
-
if (reason === "threshold" &&
|
|
2084
|
+
if (reason === "threshold" &&
|
|
2085
|
+
this._autoOlderHistoryCooldownTurnsRemaining > 0) {
|
|
1836
2086
|
return false;
|
|
1837
2087
|
}
|
|
1838
2088
|
if (this._compactionInProgress) {
|
|
@@ -1851,7 +2101,9 @@ export class AgentSession {
|
|
|
1851
2101
|
this._autoCompactionAbortController = new AbortController();
|
|
1852
2102
|
try {
|
|
1853
2103
|
const pathEntries = this.sessionManager.getBranch();
|
|
1854
|
-
const preparation = prepareCompaction(pathEntries, settings, {
|
|
2104
|
+
const preparation = prepareCompaction(pathEntries, settings, {
|
|
2105
|
+
phaseBoundary: false,
|
|
2106
|
+
});
|
|
1855
2107
|
if (!preparation) {
|
|
1856
2108
|
this._emit({
|
|
1857
2109
|
type: "compaction_end",
|
|
@@ -1864,8 +2116,10 @@ export class AgentSession {
|
|
|
1864
2116
|
}
|
|
1865
2117
|
const compactableTokens = estimateCompactionTokenMetrics(preparation, "").tokensCompacted ?? 0;
|
|
1866
2118
|
if (compactableTokens < policy.autoOlderHistory.minRawTokens) {
|
|
1867
|
-
this._lastAutoOlderHistorySkipSignature =
|
|
1868
|
-
|
|
2119
|
+
this._lastAutoOlderHistorySkipSignature =
|
|
2120
|
+
this._autoOlderHistorySkipSignature(pathEntries, compactableTokens);
|
|
2121
|
+
this._autoOlderHistoryCooldownTurnsRemaining =
|
|
2122
|
+
policy.autoOlderHistory.cooldownTurns;
|
|
1869
2123
|
this._emit({
|
|
1870
2124
|
type: "compaction_end",
|
|
1871
2125
|
reason,
|
|
@@ -1878,8 +2132,10 @@ export class AgentSession {
|
|
|
1878
2132
|
return false;
|
|
1879
2133
|
}
|
|
1880
2134
|
const skipSignature = this._autoOlderHistorySkipSignature(pathEntries, compactableTokens);
|
|
1881
|
-
if (reason === "threshold" &&
|
|
1882
|
-
this.
|
|
2135
|
+
if (reason === "threshold" &&
|
|
2136
|
+
this._lastAutoOlderHistorySkipSignature === skipSignature) {
|
|
2137
|
+
this._autoOlderHistoryCooldownTurnsRemaining =
|
|
2138
|
+
policy.autoOlderHistory.cooldownTurns;
|
|
1883
2139
|
this._emit({
|
|
1884
2140
|
type: "compaction_end",
|
|
1885
2141
|
reason,
|
|
@@ -1894,7 +2150,8 @@ export class AgentSession {
|
|
|
1894
2150
|
const emitExtensionProgress = (delta, content) => {
|
|
1895
2151
|
if (!delta && !content)
|
|
1896
2152
|
return;
|
|
1897
|
-
extensionProgressContent =
|
|
2153
|
+
extensionProgressContent =
|
|
2154
|
+
content ?? `${extensionProgressContent}${delta}`;
|
|
1898
2155
|
this._emit({
|
|
1899
2156
|
type: "compaction_delta",
|
|
1900
2157
|
delta,
|
|
@@ -1902,7 +2159,9 @@ export class AgentSession {
|
|
|
1902
2159
|
});
|
|
1903
2160
|
};
|
|
1904
2161
|
const beforeCompactResult = await this._emitSessionBeforeCompact(preparation, pathEntries, undefined, this._autoCompactionAbortController.signal, emitExtensionProgress);
|
|
1905
|
-
const extensionCompaction = beforeCompactResult?.cancel
|
|
2162
|
+
const extensionCompaction = beforeCompactResult?.cancel
|
|
2163
|
+
? undefined
|
|
2164
|
+
: beforeCompactResult?.compaction;
|
|
1906
2165
|
const compactResult = this._buildDcpCompactionResult(preparation, extensionCompaction);
|
|
1907
2166
|
if (this._autoCompactionAbortController.signal.aborted) {
|
|
1908
2167
|
this._emit({
|
|
@@ -1917,7 +2176,8 @@ export class AgentSession {
|
|
|
1917
2176
|
const skippedResult = this._markIfNonShrinking(compactResult);
|
|
1918
2177
|
if (skippedResult) {
|
|
1919
2178
|
this._lastAutoOlderHistorySkipSignature = skipSignature;
|
|
1920
|
-
this._autoOlderHistoryCooldownTurnsRemaining =
|
|
2179
|
+
this._autoOlderHistoryCooldownTurnsRemaining =
|
|
2180
|
+
policy.autoOlderHistory.cooldownTurns;
|
|
1921
2181
|
this._emit({
|
|
1922
2182
|
type: "compaction_end",
|
|
1923
2183
|
reason,
|
|
@@ -1931,7 +2191,8 @@ export class AgentSession {
|
|
|
1931
2191
|
return false;
|
|
1932
2192
|
}
|
|
1933
2193
|
this._lastAutoOlderHistorySkipSignature = undefined;
|
|
1934
|
-
this._autoOlderHistoryCooldownTurnsRemaining =
|
|
2194
|
+
this._autoOlderHistoryCooldownTurnsRemaining =
|
|
2195
|
+
policy.autoOlderHistory.cooldownTurns;
|
|
1935
2196
|
this._emitCompactionResultSummary(compactResult);
|
|
1936
2197
|
const { summary, firstKeptEntryId, tokensBefore, details } = compactResult;
|
|
1937
2198
|
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, false);
|
|
@@ -1948,11 +2209,18 @@ export class AgentSession {
|
|
|
1948
2209
|
});
|
|
1949
2210
|
}
|
|
1950
2211
|
const result = { ...compactResult, details };
|
|
1951
|
-
this._emit({
|
|
2212
|
+
this._emit({
|
|
2213
|
+
type: "compaction_end",
|
|
2214
|
+
reason,
|
|
2215
|
+
result,
|
|
2216
|
+
aborted: false,
|
|
2217
|
+
willRetry,
|
|
2218
|
+
});
|
|
1952
2219
|
if (willRetry) {
|
|
1953
2220
|
const messages = this.agent.state.messages;
|
|
1954
2221
|
const lastMsg = messages[messages.length - 1];
|
|
1955
|
-
if (lastMsg?.role === "assistant" &&
|
|
2222
|
+
if (lastMsg?.role === "assistant" &&
|
|
2223
|
+
lastMsg.stopReason === "error") {
|
|
1956
2224
|
this.agent.state.messages = messages.slice(0, -1);
|
|
1957
2225
|
}
|
|
1958
2226
|
return true;
|
|
@@ -2015,7 +2283,9 @@ export class AgentSession {
|
|
|
2015
2283
|
return;
|
|
2016
2284
|
}
|
|
2017
2285
|
const { skillPaths, promptPaths, themePaths } = await this._extensionRunner.emitResourcesDiscover(this._cwd, reason);
|
|
2018
|
-
if (skillPaths.length === 0 &&
|
|
2286
|
+
if (skillPaths.length === 0 &&
|
|
2287
|
+
promptPaths.length === 0 &&
|
|
2288
|
+
themePaths.length === 0) {
|
|
2019
2289
|
return;
|
|
2020
2290
|
}
|
|
2021
2291
|
const extensionPaths = {
|
|
@@ -2030,7 +2300,9 @@ export class AgentSession {
|
|
|
2030
2300
|
buildExtensionResourcePaths(entries) {
|
|
2031
2301
|
return entries.map((entry) => {
|
|
2032
2302
|
const source = this.getExtensionSourceLabel(entry.extensionPath);
|
|
2033
|
-
const baseDir = entry.extensionPath.startsWith("<")
|
|
2303
|
+
const baseDir = entry.extensionPath.startsWith("<")
|
|
2304
|
+
? undefined
|
|
2305
|
+
: dirname(entry.extensionPath);
|
|
2034
2306
|
return {
|
|
2035
2307
|
path: entry.path,
|
|
2036
2308
|
metadata: {
|
|
@@ -2071,7 +2343,9 @@ export class AgentSession {
|
|
|
2071
2343
|
}
|
|
2072
2344
|
_bindExtensionCore(runner) {
|
|
2073
2345
|
const getCommands = () => {
|
|
2074
|
-
const extensionCommands = runner
|
|
2346
|
+
const extensionCommands = runner
|
|
2347
|
+
.getRegisteredCommands()
|
|
2348
|
+
.map((command) => ({
|
|
2075
2349
|
name: command.invocationName,
|
|
2076
2350
|
description: command.description,
|
|
2077
2351
|
source: "extension",
|
|
@@ -2083,7 +2357,9 @@ export class AgentSession {
|
|
|
2083
2357
|
source: "prompt",
|
|
2084
2358
|
sourceInfo: template.sourceInfo,
|
|
2085
2359
|
}));
|
|
2086
|
-
const skills = this._resourceLoader
|
|
2360
|
+
const skills = this._resourceLoader
|
|
2361
|
+
.getSkills()
|
|
2362
|
+
.skills.map((skill) => ({
|
|
2087
2363
|
name: `skill:${skill.name}`,
|
|
2088
2364
|
description: skill.description,
|
|
2089
2365
|
source: "skill",
|
|
@@ -2192,7 +2468,9 @@ export class AgentSession {
|
|
|
2192
2468
|
...registeredTools,
|
|
2193
2469
|
...this._customTools.map((definition) => ({
|
|
2194
2470
|
definition,
|
|
2195
|
-
sourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, {
|
|
2471
|
+
sourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, {
|
|
2472
|
+
source: "sdk",
|
|
2473
|
+
}),
|
|
2196
2474
|
})),
|
|
2197
2475
|
].filter((tool) => isAllowedTool(tool.definition.name));
|
|
2198
2476
|
const definitionRegistry = new Map(Array.from(this._baseToolDefinitions.entries())
|
|
@@ -2201,7 +2479,9 @@ export class AgentSession {
|
|
|
2201
2479
|
name,
|
|
2202
2480
|
{
|
|
2203
2481
|
definition,
|
|
2204
|
-
sourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, {
|
|
2482
|
+
sourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, {
|
|
2483
|
+
source: "builtin",
|
|
2484
|
+
}),
|
|
2205
2485
|
},
|
|
2206
2486
|
]));
|
|
2207
2487
|
for (const tool of allCustomTools) {
|
|
@@ -2220,7 +2500,9 @@ export class AgentSession {
|
|
|
2220
2500
|
this._toolPromptGuidelines = new Map(Array.from(definitionRegistry.values())
|
|
2221
2501
|
.map(({ definition }) => {
|
|
2222
2502
|
const guidelines = this._normalizePromptGuidelines(definition.promptGuidelines);
|
|
2223
|
-
return guidelines.length > 0
|
|
2503
|
+
return guidelines.length > 0
|
|
2504
|
+
? [definition.name, guidelines]
|
|
2505
|
+
: undefined;
|
|
2224
2506
|
})
|
|
2225
2507
|
.filter((entry) => entry !== undefined));
|
|
2226
2508
|
const runner = this._extensionRunner;
|
|
@@ -2236,7 +2518,9 @@ export class AgentSession {
|
|
|
2236
2518
|
toolRegistry.set(tool.name, tool);
|
|
2237
2519
|
}
|
|
2238
2520
|
this._toolRegistry = toolRegistry;
|
|
2239
|
-
const nextActiveToolNames = (options?.activeToolNames
|
|
2521
|
+
const nextActiveToolNames = (options?.activeToolNames
|
|
2522
|
+
? [...options.activeToolNames]
|
|
2523
|
+
: [...previousActiveToolNames]).filter((name) => isAllowedTool(name));
|
|
2240
2524
|
if (allowedToolNames) {
|
|
2241
2525
|
for (const toolName of this._toolRegistry.keys()) {
|
|
2242
2526
|
if (allowedToolNames.has(toolName)) {
|
|
@@ -2271,7 +2555,10 @@ export class AgentSession {
|
|
|
2271
2555
|
read: { autoResizeImages },
|
|
2272
2556
|
bash: { commandPrefix: shellCommandPrefix, shellPath },
|
|
2273
2557
|
});
|
|
2274
|
-
this._baseToolDefinitions = new Map(Object.entries(baseToolDefinitions).map(([name, tool]) => [
|
|
2558
|
+
this._baseToolDefinitions = new Map(Object.entries(baseToolDefinitions).map(([name, tool]) => [
|
|
2559
|
+
name,
|
|
2560
|
+
tool,
|
|
2561
|
+
]));
|
|
2275
2562
|
const extensionsResult = this._resourceLoader.getExtensions();
|
|
2276
2563
|
if (options.flagValues) {
|
|
2277
2564
|
for (const [name, value] of options.flagValues) {
|
|
@@ -2295,7 +2582,10 @@ export class AgentSession {
|
|
|
2295
2582
|
}
|
|
2296
2583
|
async reload() {
|
|
2297
2584
|
const previousFlagValues = this._extensionRunner.getFlagValues();
|
|
2298
|
-
await emitSessionShutdownEvent(this._extensionRunner, {
|
|
2585
|
+
await emitSessionShutdownEvent(this._extensionRunner, {
|
|
2586
|
+
type: "session_shutdown",
|
|
2587
|
+
reason: "reload",
|
|
2588
|
+
});
|
|
2299
2589
|
await this.settingsManager.reload();
|
|
2300
2590
|
resetApiProviders();
|
|
2301
2591
|
await this._resourceLoader.reload();
|
|
@@ -2309,7 +2599,10 @@ export class AgentSession {
|
|
|
2309
2599
|
this._extensionShutdownHandler ||
|
|
2310
2600
|
this._extensionErrorListener;
|
|
2311
2601
|
if (hasBindings) {
|
|
2312
|
-
await this._extensionRunner.emit({
|
|
2602
|
+
await this._extensionRunner.emit({
|
|
2603
|
+
type: "session_start",
|
|
2604
|
+
reason: "reload",
|
|
2605
|
+
});
|
|
2313
2606
|
await this.extendResourcesFromExtensions("reload");
|
|
2314
2607
|
}
|
|
2315
2608
|
}
|
|
@@ -2329,7 +2622,7 @@ export class AgentSession {
|
|
|
2329
2622
|
return false;
|
|
2330
2623
|
const err = message.errorMessage;
|
|
2331
2624
|
// Match: overloaded_error, provider returned error, rate limit, 429, 500, 502, 503, 504, service unavailable, network/connection errors (including connection lost), WebSocket transport closes/errors, fetch failed, premature stream endings, HTTP/2 closed before response, terminated, retry delay exceeded
|
|
2332
|
-
return /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|stream ended before message_stop|http2 request did not get a response|timed? out|timeout|terminated|retry delay/i.test(err);
|
|
2625
|
+
return /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|connection.?closed|connection.?reset|websocket.?closed|websocket.?error|other side closed|socket connection was closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|stream ended before message_stop|http2 request did not get a response|timed? out|timeout|terminated|retry delay/i.test(err);
|
|
2333
2626
|
}
|
|
2334
2627
|
/**
|
|
2335
2628
|
* Prepare a retryable error for continuation with exponential backoff.
|
|
@@ -2356,7 +2649,8 @@ export class AgentSession {
|
|
|
2356
2649
|
});
|
|
2357
2650
|
// Remove error message from agent state (keep in session for history)
|
|
2358
2651
|
const messages = this.agent.state.messages;
|
|
2359
|
-
if (messages.length > 0 &&
|
|
2652
|
+
if (messages.length > 0 &&
|
|
2653
|
+
messages[messages.length - 1].role === "assistant") {
|
|
2360
2654
|
this.agent.state.messages = messages.slice(0, -1);
|
|
2361
2655
|
}
|
|
2362
2656
|
// Wait with exponential backoff (abortable)
|
|
@@ -2495,7 +2789,10 @@ export class AgentSession {
|
|
|
2495
2789
|
*/
|
|
2496
2790
|
setSessionName(name) {
|
|
2497
2791
|
this.sessionManager.appendSessionInfo(name);
|
|
2498
|
-
this._emit({
|
|
2792
|
+
this._emit({
|
|
2793
|
+
type: "session_info_changed",
|
|
2794
|
+
name: this.sessionManager.getSessionName(),
|
|
2795
|
+
});
|
|
2499
2796
|
}
|
|
2500
2797
|
// =========================================================================
|
|
2501
2798
|
// Tree Navigation
|
|
@@ -2574,7 +2871,9 @@ export class AgentSession {
|
|
|
2574
2871
|
// Run default summarizer if needed
|
|
2575
2872
|
let summaryText;
|
|
2576
2873
|
let summaryDetails;
|
|
2577
|
-
if (options.summarize &&
|
|
2874
|
+
if (options.summarize &&
|
|
2875
|
+
entriesToSummarize.length > 0 &&
|
|
2876
|
+
!extensionSummary) {
|
|
2578
2877
|
const model = this.model;
|
|
2579
2878
|
const { apiKey, headers } = await this._getRequiredRequestAuth(model);
|
|
2580
2879
|
const branchSummarySettings = this.settingsManager.getBranchSummarySettings();
|
|
@@ -2606,7 +2905,8 @@ export class AgentSession {
|
|
|
2606
2905
|
// Determine the new leaf position based on target type
|
|
2607
2906
|
let newLeafId;
|
|
2608
2907
|
let editorText;
|
|
2609
|
-
if (targetEntry.type === "message" &&
|
|
2908
|
+
if (targetEntry.type === "message" &&
|
|
2909
|
+
targetEntry.message.role === "user") {
|
|
2610
2910
|
// User message: leaf = parent (null if root), text goes to editor
|
|
2611
2911
|
newLeafId = targetEntry.parentId;
|
|
2612
2912
|
editorText = this._extractUserMessageText(targetEntry.message.content);
|
|
@@ -2761,7 +3061,8 @@ export class AgentSession {
|
|
|
2761
3061
|
const entry = branchEntries[i];
|
|
2762
3062
|
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
2763
3063
|
const assistant = entry.message;
|
|
2764
|
-
if (assistant.stopReason !== "aborted" &&
|
|
3064
|
+
if (assistant.stopReason !== "aborted" &&
|
|
3065
|
+
assistant.stopReason !== "error") {
|
|
2765
3066
|
const contextTokens = calculateContextTokens(assistant.usage);
|
|
2766
3067
|
if (contextTokens > 0) {
|
|
2767
3068
|
hasPostCompactionUsage = true;
|
|
@@ -2797,7 +3098,8 @@ export class AgentSession {
|
|
|
2797
3098
|
* @returns The resolved output file path.
|
|
2798
3099
|
*/
|
|
2799
3100
|
exportToJsonl(outputPath) {
|
|
2800
|
-
const filePath = resolvePath(outputPath ??
|
|
3101
|
+
const filePath = resolvePath(outputPath ??
|
|
3102
|
+
`session-${new Date().toISOString().replace(/[:.]/g, "-")}.jsonl`, process.cwd());
|
|
2801
3103
|
const dir = dirname(filePath);
|
|
2802
3104
|
if (!existsSync(dir)) {
|
|
2803
3105
|
mkdirSync(dir, { recursive: true });
|