@averyyy/pi-coding-agent 0.85.1-piclient.1 → 0.85.1-piclient.3
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/CHANGELOG.md +3 -0
- package/dist/bundle/chunks/{chunk-ZNXRLOMG.js → chunk-LQDML2ZO.js} +61 -54
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +21 -3
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +26 -7
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +212 -114
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/pi-server-client.d.ts +4 -0
- package/dist/core/pi-server-client.d.ts.map +1 -1
- package/dist/core/pi-server-client.js +162 -46
- package/dist/core/pi-server-client.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +17 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +236 -44
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/package.json +4 -4
|
@@ -30,7 +30,7 @@ import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
|
|
|
30
30
|
import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
|
|
31
31
|
import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
32
32
|
import { ModelRegistry } from "./model-registry.js";
|
|
33
|
-
import { compactPiServer } from "./pi-server-client.js";
|
|
33
|
+
import { cancelPiServerOperations, compactPiServer, } from "./pi-server-client.js";
|
|
34
34
|
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
35
35
|
import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
|
|
36
36
|
import { createSyntheticSourceInfo } from "./source-info.js";
|
|
@@ -74,9 +74,6 @@ function estimateMessagesTokens(messages) {
|
|
|
74
74
|
// ============================================================================
|
|
75
75
|
const INTRA_TURN_COMPACTION_MARKER_TYPE = "pi:intra-turn-compaction";
|
|
76
76
|
const INTRA_TURN_COMPACTION_MARKER_TEXT = "Continue from the compaction summary above. The previous tool loop was compacted before the next provider request; use the summary's Operational State, file lists, failures, last command, and next steps.";
|
|
77
|
-
const VALIDATION_HINT_MESSAGE_TYPE = "pi:validation-hint";
|
|
78
|
-
const VALIDATION_HINT_FILE_LIMIT = 12;
|
|
79
|
-
const VALIDATION_HINT_FAILURE_CHAR_LIMIT = 1200;
|
|
80
77
|
const AUTO_SESSION_NAME_INPUT_CHAR_LIMIT = 4000;
|
|
81
78
|
const AUTO_SESSION_NAME_MAX_CHARS = 80;
|
|
82
79
|
const AUTO_SESSION_NAME_PROMPT = "Name this coding session from the transcript below. Return only a concise title, in the user's language when clear. No quotes, no markdown, no explanation. Do not call tools.";
|
|
@@ -86,12 +83,6 @@ function isPiServerMode() {
|
|
|
86
83
|
function toProviderReasoning(thinkingLevel) {
|
|
87
84
|
return thinkingLevel === "off" ? undefined : thinkingLevel;
|
|
88
85
|
}
|
|
89
|
-
function getStringField(input, key) {
|
|
90
|
-
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
91
|
-
return undefined;
|
|
92
|
-
const value = input[key];
|
|
93
|
-
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
94
|
-
}
|
|
95
86
|
function getPiServerFailurePhase(message) {
|
|
96
87
|
for (const diagnostic of message.diagnostics ?? []) {
|
|
97
88
|
if (diagnostic.type !== "pi_server_failure")
|
|
@@ -112,19 +103,6 @@ function getPiServerFailureRetryable(message) {
|
|
|
112
103
|
}
|
|
113
104
|
return undefined;
|
|
114
105
|
}
|
|
115
|
-
function toolTextContent(content) {
|
|
116
|
-
return content
|
|
117
|
-
.filter((part) => part.type === "text")
|
|
118
|
-
.map((part) => part.text)
|
|
119
|
-
.join("\n")
|
|
120
|
-
.trim();
|
|
121
|
-
}
|
|
122
|
-
function compactValidationSummary(text) {
|
|
123
|
-
const trimmed = text.trim();
|
|
124
|
-
if (trimmed.length <= VALIDATION_HINT_FAILURE_CHAR_LIMIT)
|
|
125
|
-
return trimmed;
|
|
126
|
-
return `...${trimmed.slice(trimmed.length - VALIDATION_HINT_FAILURE_CHAR_LIMIT + 3)}`;
|
|
127
|
-
}
|
|
128
106
|
function messageTextContent(message) {
|
|
129
107
|
if (!("content" in message))
|
|
130
108
|
return "";
|
|
@@ -174,6 +152,10 @@ export class AgentSession {
|
|
|
174
152
|
_isAgentRunActive = false;
|
|
175
153
|
_idleWaitPromise;
|
|
176
154
|
_resolveIdleWait;
|
|
155
|
+
_abortPromise;
|
|
156
|
+
_isAborting = false;
|
|
157
|
+
_abortError;
|
|
158
|
+
_continueAfterAbortRequested = false;
|
|
177
159
|
/** Tracks pending steering messages for UI display. Removed when delivered. */
|
|
178
160
|
_steeringMessages = [];
|
|
179
161
|
/** Tracks pending follow-up messages for UI display. Removed when delivered. */
|
|
@@ -185,18 +167,23 @@ export class AgentSession {
|
|
|
185
167
|
// Compaction state
|
|
186
168
|
_compactionAbortController = undefined;
|
|
187
169
|
_autoCompactionAbortController = undefined;
|
|
170
|
+
_compactionCompletionPromise = undefined;
|
|
171
|
+
_prePromptAbortController = undefined;
|
|
172
|
+
_prePromptCompletionPromise = undefined;
|
|
188
173
|
_overflowRecoveryAttempted = false;
|
|
189
174
|
_lastServerCompactionTimestamp = undefined;
|
|
190
175
|
// Branch summarization state
|
|
191
176
|
_branchSummaryAbortController = undefined;
|
|
177
|
+
_branchSummaryCompletionPromise = undefined;
|
|
192
178
|
// Retry state
|
|
193
179
|
_retryAbortController = undefined;
|
|
194
180
|
_retryAttempt = 0;
|
|
181
|
+
// Post-turn session naming is a provider request too, so cancellation must reach it.
|
|
182
|
+
_sessionNameAbortController = undefined;
|
|
195
183
|
// Bash execution state
|
|
196
184
|
_bashAbortControllers = new Set();
|
|
185
|
+
_bashCompletionPromises = new Set();
|
|
197
186
|
_pendingBashMessages = [];
|
|
198
|
-
_validationModifiedFiles = new Set();
|
|
199
|
-
_validationFailure = undefined;
|
|
200
187
|
// Extension system
|
|
201
188
|
_extensionRunner;
|
|
202
189
|
_turnIndex = 0;
|
|
@@ -320,71 +307,6 @@ export class AgentSession {
|
|
|
320
307
|
return { model };
|
|
321
308
|
}
|
|
322
309
|
}
|
|
323
|
-
_resetValidationHintState() {
|
|
324
|
-
this._validationModifiedFiles.clear();
|
|
325
|
-
this._validationFailure = undefined;
|
|
326
|
-
}
|
|
327
|
-
_recordValidationToolResult(toolName, input, content, isError) {
|
|
328
|
-
if ((toolName === "edit" || toolName === "write") && !isError) {
|
|
329
|
-
const path = getStringField(input, "path") ?? getStringField(input, "file_path");
|
|
330
|
-
if (path) {
|
|
331
|
-
this._validationModifiedFiles.add(path);
|
|
332
|
-
}
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
if (toolName !== "bash") {
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
const command = getStringField(input, "command");
|
|
339
|
-
if (!command) {
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
if (isError) {
|
|
343
|
-
this._validationFailure = {
|
|
344
|
-
command,
|
|
345
|
-
summary: compactValidationSummary(toolTextContent(content)),
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
else if (this._validationFailure?.command === command) {
|
|
349
|
-
this._resetValidationHintState();
|
|
350
|
-
}
|
|
351
|
-
else if (!this._validationFailure) {
|
|
352
|
-
this._validationModifiedFiles.clear();
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
_createValidationHintMessage() {
|
|
356
|
-
const modifiedFiles = Array.from(this._validationModifiedFiles).slice(-VALIDATION_HINT_FILE_LIMIT);
|
|
357
|
-
if (modifiedFiles.length === 0 && !this._validationFailure) {
|
|
358
|
-
return undefined;
|
|
359
|
-
}
|
|
360
|
-
const lines = ["<validation-hint>"];
|
|
361
|
-
if (modifiedFiles.length > 0) {
|
|
362
|
-
lines.push("Recent modified files:", ...modifiedFiles.map((file) => `- ${file}`));
|
|
363
|
-
}
|
|
364
|
-
if (this._validationFailure) {
|
|
365
|
-
lines.push("Recent failed command:", this._validationFailure.command, "Failure summary:", this._validationFailure.summary || "(no output)", "Suggested next validation command:", this._validationFailure.command);
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
lines.push("Suggested next step:", "Run the smallest project validation that covers the modified files.");
|
|
369
|
-
}
|
|
370
|
-
lines.push("Inspect failures, fix the root cause, then rerun validation before finalizing.", "</validation-hint>");
|
|
371
|
-
return {
|
|
372
|
-
role: "custom",
|
|
373
|
-
customType: VALIDATION_HINT_MESSAGE_TYPE,
|
|
374
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
375
|
-
display: false,
|
|
376
|
-
details: { modifiedFiles, failedCommand: this._validationFailure?.command },
|
|
377
|
-
timestamp: Date.now(),
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
_withValidationHint(context) {
|
|
381
|
-
const messages = context.messages.filter((message) => !(message.role === "custom" && message.customType === VALIDATION_HINT_MESSAGE_TYPE));
|
|
382
|
-
const hint = this._createValidationHintMessage();
|
|
383
|
-
return {
|
|
384
|
-
...context,
|
|
385
|
-
messages: hint ? [...messages, hint] : messages,
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
310
|
/**
|
|
389
311
|
* Install tool hooks once on the Agent instance.
|
|
390
312
|
*
|
|
@@ -433,8 +355,6 @@ export class AgentSession {
|
|
|
433
355
|
const normalizedContent = await normalizeToolResultImages(content, {
|
|
434
356
|
autoResizeImages: this.settingsManager.getImageAutoResize(),
|
|
435
357
|
});
|
|
436
|
-
const finalIsError = hookResult?.isError ?? isError;
|
|
437
|
-
this._recordValidationToolResult(toolCall.name, args, normalizedContent, finalIsError);
|
|
438
358
|
if (!hookResult && normalizedContent === content) {
|
|
439
359
|
return undefined;
|
|
440
360
|
}
|
|
@@ -458,11 +378,11 @@ export class AgentSession {
|
|
|
458
378
|
const previousContext = previousSnapshot?.context ?? nextTurn.context;
|
|
459
379
|
return {
|
|
460
380
|
...previousSnapshot,
|
|
461
|
-
context:
|
|
381
|
+
context: {
|
|
462
382
|
...previousContext,
|
|
463
383
|
systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
|
|
464
384
|
tools: this.agent.state.tools.slice(),
|
|
465
|
-
}
|
|
385
|
+
},
|
|
466
386
|
model: this.agent.state.model,
|
|
467
387
|
thinkingLevel: this.agent.state.thinkingLevel,
|
|
468
388
|
};
|
|
@@ -834,6 +754,14 @@ export class AgentSession {
|
|
|
834
754
|
get isStreaming() {
|
|
835
755
|
return this._isAgentRunActive;
|
|
836
756
|
}
|
|
757
|
+
/** Whether the current run is being cancelled. New queued intent is drained after cancellation. */
|
|
758
|
+
get isAborting() {
|
|
759
|
+
return this._isAborting;
|
|
760
|
+
}
|
|
761
|
+
/** Error from the most recent cancellation attempt, if it failed. */
|
|
762
|
+
get abortError() {
|
|
763
|
+
return this._abortError;
|
|
764
|
+
}
|
|
837
765
|
/** Whether the session has no active agent run, compaction, branch summary, retry, or queued continuation. */
|
|
838
766
|
get isIdle() {
|
|
839
767
|
return !this._isAgentRunActive && !this.isCompacting;
|
|
@@ -987,12 +915,16 @@ export class AgentSession {
|
|
|
987
915
|
// =========================================================================
|
|
988
916
|
// Prompting
|
|
989
917
|
// =========================================================================
|
|
990
|
-
async
|
|
991
|
-
this._resetValidationHintState();
|
|
918
|
+
async _runAgentOperation(start) {
|
|
992
919
|
this._isAgentRunActive = true;
|
|
993
920
|
try {
|
|
994
|
-
await
|
|
921
|
+
await start();
|
|
995
922
|
while (await this._handlePostAgentRun()) {
|
|
923
|
+
// Esc cancels the current agent run. Keep messages queued during that
|
|
924
|
+
// cancellation for the single deferred continuation in abort().
|
|
925
|
+
if (this._isAborting) {
|
|
926
|
+
break;
|
|
927
|
+
}
|
|
996
928
|
try {
|
|
997
929
|
await this.agent.continue();
|
|
998
930
|
}
|
|
@@ -1010,7 +942,9 @@ export class AgentSession {
|
|
|
1010
942
|
throw error;
|
|
1011
943
|
}
|
|
1012
944
|
}
|
|
1013
|
-
|
|
945
|
+
if (!this._isAborting) {
|
|
946
|
+
await this._maybeGenerateSessionName();
|
|
947
|
+
}
|
|
1014
948
|
}
|
|
1015
949
|
finally {
|
|
1016
950
|
this._systemPromptOverride = undefined;
|
|
@@ -1019,6 +953,9 @@ export class AgentSession {
|
|
|
1019
953
|
await this._emitAgentSettled();
|
|
1020
954
|
}
|
|
1021
955
|
}
|
|
956
|
+
async _runAgentPrompt(messages) {
|
|
957
|
+
await this._runAgentOperation(() => this.agent.prompt(messages));
|
|
958
|
+
}
|
|
1022
959
|
_sessionHasInfoEntry() {
|
|
1023
960
|
return this.sessionManager.getEntries().some((entry) => entry.type === "session_info");
|
|
1024
961
|
}
|
|
@@ -1052,6 +989,7 @@ export class AgentSession {
|
|
|
1052
989
|
const transcript = this._buildSessionNameTranscript();
|
|
1053
990
|
if (!model || !transcript)
|
|
1054
991
|
return;
|
|
992
|
+
this._sessionNameAbortController = new AbortController();
|
|
1055
993
|
try {
|
|
1056
994
|
const stream = await this.agent.streamFunction(model, {
|
|
1057
995
|
systemPrompt: this.systemPrompt,
|
|
@@ -1070,6 +1008,7 @@ export class AgentSession {
|
|
|
1070
1008
|
transport: this.agent.transport,
|
|
1071
1009
|
thinkingBudgets: this.agent.thinkingBudgets,
|
|
1072
1010
|
maxRetryDelayMs: this.agent.maxRetryDelayMs,
|
|
1011
|
+
signal: this._sessionNameAbortController.signal,
|
|
1073
1012
|
});
|
|
1074
1013
|
for await (const _event of stream) {
|
|
1075
1014
|
// Drain the title stream without surfacing it as a chat message.
|
|
@@ -1084,6 +1023,9 @@ export class AgentSession {
|
|
|
1084
1023
|
catch {
|
|
1085
1024
|
// Session naming is a post-turn convenience; the completed assistant turn stays authoritative.
|
|
1086
1025
|
}
|
|
1026
|
+
finally {
|
|
1027
|
+
this._sessionNameAbortController = undefined;
|
|
1028
|
+
}
|
|
1087
1029
|
}
|
|
1088
1030
|
async _handlePostAgentRun() {
|
|
1089
1031
|
const msg = this._lastAssistantMessage;
|
|
@@ -1091,9 +1033,18 @@ export class AgentSession {
|
|
|
1091
1033
|
if (!msg) {
|
|
1092
1034
|
return false;
|
|
1093
1035
|
}
|
|
1036
|
+
if (this._isAborting) {
|
|
1037
|
+
if (isPiServerMode() && this._isTerminalAssistantFailure(msg)) {
|
|
1038
|
+
this._detachPiServerTerminalAssistantFailure();
|
|
1039
|
+
}
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1094
1042
|
if (this._isRetryableError(msg) && (await this._prepareRetry(msg))) {
|
|
1095
1043
|
return true;
|
|
1096
1044
|
}
|
|
1045
|
+
if (this._isAborting) {
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1097
1048
|
if (msg.stopReason === "error" && this._retryAttempt > 0) {
|
|
1098
1049
|
this._emit({
|
|
1099
1050
|
type: "auto_retry_end",
|
|
@@ -1137,7 +1088,7 @@ export class AgentSession {
|
|
|
1137
1088
|
return;
|
|
1138
1089
|
}
|
|
1139
1090
|
}
|
|
1140
|
-
if (this._compactionAbortController !== undefined) {
|
|
1091
|
+
if (this._compactionAbortController !== undefined && !this._isAborting) {
|
|
1141
1092
|
throw new Error("Cannot submit a prompt while compaction is in progress. Wait for compaction to finish and retry.");
|
|
1142
1093
|
}
|
|
1143
1094
|
// Emit input event for extension interception (before skill/template expansion)
|
|
@@ -1161,7 +1112,7 @@ export class AgentSession {
|
|
|
1161
1112
|
expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
|
|
1162
1113
|
}
|
|
1163
1114
|
// If streaming, queue via steer() or followUp() based on option
|
|
1164
|
-
if (this.isStreaming) {
|
|
1115
|
+
if (this.isStreaming || this._isAborting) {
|
|
1165
1116
|
if (!options?.streamingBehavior) {
|
|
1166
1117
|
throw new Error("Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.");
|
|
1167
1118
|
}
|
|
@@ -1194,9 +1145,35 @@ export class AgentSession {
|
|
|
1194
1145
|
}
|
|
1195
1146
|
// Check if we need to compact before sending (catches aborted responses).
|
|
1196
1147
|
// The user's new prompt is sent below, so do not call agent.continue() here.
|
|
1148
|
+
if (this._isAborting) {
|
|
1149
|
+
preflightResult?.(false);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1197
1152
|
const lastAssistant = this._findLastAssistantMessage();
|
|
1198
1153
|
if (lastAssistant) {
|
|
1199
|
-
|
|
1154
|
+
const prePromptAbortController = new AbortController();
|
|
1155
|
+
let resolvePrePromptCompletion;
|
|
1156
|
+
const prePromptCompletion = new Promise((resolve) => {
|
|
1157
|
+
resolvePrePromptCompletion = resolve;
|
|
1158
|
+
});
|
|
1159
|
+
this._prePromptAbortController = prePromptAbortController;
|
|
1160
|
+
this._prePromptCompletionPromise = prePromptCompletion;
|
|
1161
|
+
try {
|
|
1162
|
+
await this._checkCompaction(lastAssistant, false, prePromptAbortController.signal);
|
|
1163
|
+
}
|
|
1164
|
+
finally {
|
|
1165
|
+
if (this._prePromptAbortController === prePromptAbortController) {
|
|
1166
|
+
this._prePromptAbortController = undefined;
|
|
1167
|
+
}
|
|
1168
|
+
if (this._prePromptCompletionPromise === prePromptCompletion) {
|
|
1169
|
+
this._prePromptCompletionPromise = undefined;
|
|
1170
|
+
}
|
|
1171
|
+
resolvePrePromptCompletion?.();
|
|
1172
|
+
}
|
|
1173
|
+
if (prePromptAbortController.signal.aborted || this._isAborting) {
|
|
1174
|
+
preflightResult?.(false);
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1200
1177
|
}
|
|
1201
1178
|
if (isPiServerMode()) {
|
|
1202
1179
|
this._detachPiServerTerminalAssistantFailure();
|
|
@@ -1362,6 +1339,9 @@ export class AgentSession {
|
|
|
1362
1339
|
content,
|
|
1363
1340
|
timestamp: Date.now(),
|
|
1364
1341
|
});
|
|
1342
|
+
if (this._isAborting) {
|
|
1343
|
+
this._continueAfterAbortRequested = true;
|
|
1344
|
+
}
|
|
1365
1345
|
}
|
|
1366
1346
|
/**
|
|
1367
1347
|
* Internal: Queue a follow-up message (already expanded, no extension command check).
|
|
@@ -1378,6 +1358,9 @@ export class AgentSession {
|
|
|
1378
1358
|
content,
|
|
1379
1359
|
timestamp: Date.now(),
|
|
1380
1360
|
});
|
|
1361
|
+
if (this._isAborting) {
|
|
1362
|
+
this._continueAfterAbortRequested = true;
|
|
1363
|
+
}
|
|
1381
1364
|
}
|
|
1382
1365
|
/**
|
|
1383
1366
|
* Throw an error if the text is an extension command.
|
|
@@ -1416,12 +1399,18 @@ export class AgentSession {
|
|
|
1416
1399
|
if (options?.deliverAs === "nextTurn") {
|
|
1417
1400
|
this._pendingNextTurnMessages.push(appMessage);
|
|
1418
1401
|
}
|
|
1419
|
-
else if (this.isStreaming && options?.triggerTurn !== false) {
|
|
1402
|
+
else if ((this.isStreaming || this._isAborting) && options?.triggerTurn !== false) {
|
|
1420
1403
|
if (options?.deliverAs === "followUp") {
|
|
1421
1404
|
this.agent.followUp(appMessage);
|
|
1405
|
+
if (this._isAborting) {
|
|
1406
|
+
this._continueAfterAbortRequested = true;
|
|
1407
|
+
}
|
|
1422
1408
|
}
|
|
1423
1409
|
else {
|
|
1424
1410
|
this.agent.steer(appMessage);
|
|
1411
|
+
if (this._isAborting) {
|
|
1412
|
+
this._continueAfterAbortRequested = true;
|
|
1413
|
+
}
|
|
1425
1414
|
}
|
|
1426
1415
|
}
|
|
1427
1416
|
else if (options?.triggerTurn) {
|
|
@@ -1523,15 +1512,79 @@ export class AgentSession {
|
|
|
1523
1512
|
get resourceLoader() {
|
|
1524
1513
|
return this._resourceLoader;
|
|
1525
1514
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1515
|
+
_startQueuedAgentRunAfterAbort() {
|
|
1516
|
+
if (this._isAgentRunActive || this._isAborting || !this.agent.hasQueuedMessages()) {
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
void this._runAgentOperation(() => this.agent.continue()).catch((error) => {
|
|
1520
|
+
this._extensionRunner.emitError({
|
|
1521
|
+
extensionPath: "<runtime>",
|
|
1522
|
+
event: "abort_continuation",
|
|
1523
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1524
|
+
});
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
async _performAbort() {
|
|
1530
1528
|
this.abortRetry();
|
|
1531
1529
|
this.abortCompaction();
|
|
1532
1530
|
this.abortBranchSummary();
|
|
1531
|
+
this.abortBash();
|
|
1532
|
+
this._prePromptAbortController?.abort();
|
|
1533
|
+
this._sessionNameAbortController?.abort();
|
|
1533
1534
|
this.agent.abort();
|
|
1534
|
-
|
|
1535
|
+
// _runAgentOperation owns the settled event and the idle waiter. Do not
|
|
1536
|
+
// start a queued continuation until that cleanup has completed.
|
|
1537
|
+
try {
|
|
1538
|
+
const cleanupPromises = [
|
|
1539
|
+
this.waitForIdle(),
|
|
1540
|
+
...(this._compactionCompletionPromise ? [this._compactionCompletionPromise] : []),
|
|
1541
|
+
...(this._prePromptCompletionPromise ? [this._prePromptCompletionPromise] : []),
|
|
1542
|
+
...(this._branchSummaryCompletionPromise ? [this._branchSummaryCompletionPromise] : []),
|
|
1543
|
+
...this._bashCompletionPromises,
|
|
1544
|
+
];
|
|
1545
|
+
if (isPiServerMode()) {
|
|
1546
|
+
cleanupPromises.push(cancelPiServerOperations(this.sessionId));
|
|
1547
|
+
}
|
|
1548
|
+
const cleanupResults = await Promise.allSettled(cleanupPromises);
|
|
1549
|
+
const cleanupFailure = cleanupResults.find((result) => result.status === "rejected");
|
|
1550
|
+
if (cleanupFailure?.status === "rejected") {
|
|
1551
|
+
throw cleanupFailure.reason;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
catch (error) {
|
|
1555
|
+
this._abortError = error instanceof Error ? error.message : String(error);
|
|
1556
|
+
this._abortPromise = undefined;
|
|
1557
|
+
this._emit({ type: "abort_error", errorMessage: this._abortError });
|
|
1558
|
+
throw error;
|
|
1559
|
+
}
|
|
1560
|
+
this._isAborting = false;
|
|
1561
|
+
const continueAfterAbort = this._continueAfterAbortRequested && this.agent.hasQueuedMessages();
|
|
1562
|
+
this._continueAfterAbortRequested = false;
|
|
1563
|
+
this._abortError = undefined;
|
|
1564
|
+
this._abortPromise = undefined;
|
|
1565
|
+
this._emit({ type: "abort_end" });
|
|
1566
|
+
if (continueAfterAbort) {
|
|
1567
|
+
this._startQueuedAgentRunAfterAbort();
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Abort the current operation and wait for cancellation cleanup to finish.
|
|
1572
|
+
* Messages queued while cancellation is in progress are sent in one deferred continuation.
|
|
1573
|
+
*/
|
|
1574
|
+
abort() {
|
|
1575
|
+
if (this._abortPromise) {
|
|
1576
|
+
return this._abortPromise;
|
|
1577
|
+
}
|
|
1578
|
+
if (this._isAborting) {
|
|
1579
|
+
this._abortError = undefined;
|
|
1580
|
+
}
|
|
1581
|
+
else {
|
|
1582
|
+
this._isAborting = true;
|
|
1583
|
+
}
|
|
1584
|
+
const abortPromise = this._performAbort();
|
|
1585
|
+
this._abortPromise = abortPromise;
|
|
1586
|
+
this._emit({ type: "abort_start" });
|
|
1587
|
+
return abortPromise;
|
|
1535
1588
|
}
|
|
1536
1589
|
async waitForIdle() {
|
|
1537
1590
|
if (this.isIdle) {
|
|
@@ -1798,8 +1851,13 @@ export class AgentSession {
|
|
|
1798
1851
|
async compact(customInstructions) {
|
|
1799
1852
|
await this.abort();
|
|
1800
1853
|
this._compactionAbortController = new AbortController();
|
|
1801
|
-
|
|
1854
|
+
let resolveCompactionCompletion;
|
|
1855
|
+
const compactionCompletion = new Promise((resolve) => {
|
|
1856
|
+
resolveCompactionCompletion = resolve;
|
|
1857
|
+
});
|
|
1858
|
+
this._compactionCompletionPromise = compactionCompletion;
|
|
1802
1859
|
try {
|
|
1860
|
+
this._emit({ type: "compaction_start", reason: "manual" });
|
|
1803
1861
|
if (!this.model) {
|
|
1804
1862
|
throw new Error(formatNoModelSelectedMessage());
|
|
1805
1863
|
}
|
|
@@ -1941,6 +1999,11 @@ export class AgentSession {
|
|
|
1941
1999
|
}
|
|
1942
2000
|
finally {
|
|
1943
2001
|
this._clearManualCompactionState();
|
|
2002
|
+
this._compactionAbortController = undefined;
|
|
2003
|
+
if (this._compactionCompletionPromise === compactionCompletion) {
|
|
2004
|
+
this._compactionCompletionPromise = undefined;
|
|
2005
|
+
}
|
|
2006
|
+
resolveCompactionCompletion?.();
|
|
1944
2007
|
}
|
|
1945
2008
|
}
|
|
1946
2009
|
/**
|
|
@@ -1968,7 +2031,9 @@ export class AgentSession {
|
|
|
1968
2031
|
* @param assistantMessage The assistant message to check
|
|
1969
2032
|
* @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true
|
|
1970
2033
|
*/
|
|
1971
|
-
async _checkCompaction(assistantMessage, skipAbortedCheck = true) {
|
|
2034
|
+
async _checkCompaction(assistantMessage, skipAbortedCheck = true, externalSignal) {
|
|
2035
|
+
if (externalSignal?.aborted)
|
|
2036
|
+
return false;
|
|
1972
2037
|
const settings = this.settingsManager.getCompactionSettings();
|
|
1973
2038
|
if (!settings.enabled)
|
|
1974
2039
|
return false;
|
|
@@ -2001,7 +2066,7 @@ export class AgentSession {
|
|
|
2001
2066
|
if (contextOverflow || recoverableLength) {
|
|
2002
2067
|
const willRetry = assistantMessage.stopReason !== "stop";
|
|
2003
2068
|
if (!willRetry) {
|
|
2004
|
-
return await this._runAutoCompaction("overflow", false);
|
|
2069
|
+
return await this._runAutoCompaction("overflow", false, undefined, undefined, externalSignal);
|
|
2005
2070
|
}
|
|
2006
2071
|
if (this._overflowRecoveryAttempted) {
|
|
2007
2072
|
const errorMessage = contextOverflow
|
|
@@ -2032,7 +2097,7 @@ export class AgentSession {
|
|
|
2032
2097
|
this.agent.state.messages = messages.slice(0, -1);
|
|
2033
2098
|
}
|
|
2034
2099
|
}
|
|
2035
|
-
return await this._runAutoCompaction("overflow", willRetry, terminalFailurePreparation);
|
|
2100
|
+
return await this._runAutoCompaction("overflow", willRetry, terminalFailurePreparation, undefined, externalSignal);
|
|
2036
2101
|
}
|
|
2037
2102
|
// Case 2: Threshold - context is getting large
|
|
2038
2103
|
// For error messages or all-zero usage messages, estimate from the last valid response.
|
|
@@ -2060,25 +2125,33 @@ export class AgentSession {
|
|
|
2060
2125
|
contextTokens = directContextTokens;
|
|
2061
2126
|
}
|
|
2062
2127
|
if (shouldCompact(contextTokens, contextWindow, settings)) {
|
|
2063
|
-
return await this._runAutoCompaction("threshold", false);
|
|
2128
|
+
return await this._runAutoCompaction("threshold", false, undefined, undefined, externalSignal);
|
|
2064
2129
|
}
|
|
2065
2130
|
return false;
|
|
2066
2131
|
}
|
|
2067
2132
|
/**
|
|
2068
2133
|
* Internal: Run auto-compaction with events.
|
|
2069
2134
|
*/
|
|
2070
|
-
async _runAutoCompaction(reason, willRetry, preparationOverride, preparationOptions) {
|
|
2135
|
+
async _runAutoCompaction(reason, willRetry, preparationOverride, preparationOptions, externalSignal) {
|
|
2071
2136
|
const settings = this.settingsManager.getCompactionSettings();
|
|
2072
2137
|
let started = false;
|
|
2073
2138
|
let fromExtension = false;
|
|
2139
|
+
if (externalSignal?.aborted)
|
|
2140
|
+
return false;
|
|
2141
|
+
const abortExternal = () => this._autoCompactionAbortController?.abort();
|
|
2142
|
+
externalSignal?.addEventListener("abort", abortExternal, { once: true });
|
|
2074
2143
|
try {
|
|
2075
2144
|
if (!this.model) {
|
|
2076
2145
|
return false;
|
|
2077
2146
|
}
|
|
2078
2147
|
const { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model);
|
|
2148
|
+
if (externalSignal?.aborted)
|
|
2149
|
+
return false;
|
|
2079
2150
|
if (isPiServerMode()) {
|
|
2080
2151
|
this._emit({ type: "compaction_start", reason });
|
|
2081
2152
|
this._autoCompactionAbortController = new AbortController();
|
|
2153
|
+
if (externalSignal?.aborted)
|
|
2154
|
+
this._autoCompactionAbortController.abort();
|
|
2082
2155
|
started = true;
|
|
2083
2156
|
const signal = this._autoCompactionAbortController.signal;
|
|
2084
2157
|
const piServerResult = await compactPiServer(requestModel, {
|
|
@@ -2096,18 +2169,26 @@ export class AgentSession {
|
|
|
2096
2169
|
reasoning: toProviderReasoning(this.thinkingLevel),
|
|
2097
2170
|
onHistoryReconciled: (snapshot) => this.reconcilePiServerHistory(snapshot),
|
|
2098
2171
|
});
|
|
2172
|
+
if (externalSignal?.aborted || signal.aborted) {
|
|
2173
|
+
this._emit({ type: "compaction_end", reason, result: undefined, aborted: true, willRetry: false });
|
|
2174
|
+
return false;
|
|
2175
|
+
}
|
|
2099
2176
|
const result = await this._applyPiServerCompactionResult(piServerResult, reason, willRetry);
|
|
2100
2177
|
this._lastServerCompactionTimestamp = Date.now();
|
|
2101
2178
|
this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
|
|
2102
2179
|
return willRetry ? true : this.agent.hasQueuedMessages();
|
|
2103
2180
|
}
|
|
2104
2181
|
const pathEntries = this.sessionManager.getBranch();
|
|
2182
|
+
if (externalSignal?.aborted)
|
|
2183
|
+
return false;
|
|
2105
2184
|
const preparation = preparationOverride ?? prepareCompaction(pathEntries, settings);
|
|
2106
2185
|
if (!preparation) {
|
|
2107
2186
|
return false;
|
|
2108
2187
|
}
|
|
2109
2188
|
this._emit({ type: "compaction_start", reason });
|
|
2110
2189
|
this._autoCompactionAbortController = new AbortController();
|
|
2190
|
+
if (externalSignal?.aborted)
|
|
2191
|
+
this._autoCompactionAbortController.abort();
|
|
2111
2192
|
started = true;
|
|
2112
2193
|
let extensionCompaction;
|
|
2113
2194
|
if (this._extensionRunner.hasHandlers("session_before_compact")) {
|
|
@@ -2163,7 +2244,7 @@ export class AgentSession {
|
|
|
2163
2244
|
usage = compactResult.usage;
|
|
2164
2245
|
details = compactResult.details;
|
|
2165
2246
|
}
|
|
2166
|
-
if (this._autoCompactionAbortController.signal.aborted) {
|
|
2247
|
+
if (externalSignal?.aborted || this._autoCompactionAbortController.signal.aborted) {
|
|
2167
2248
|
this._emit({
|
|
2168
2249
|
type: "compaction_end",
|
|
2169
2250
|
reason,
|
|
@@ -2246,6 +2327,7 @@ export class AgentSession {
|
|
|
2246
2327
|
return false;
|
|
2247
2328
|
}
|
|
2248
2329
|
finally {
|
|
2330
|
+
externalSignal?.removeEventListener("abort", abortExternal);
|
|
2249
2331
|
this._autoCompactionAbortController = undefined;
|
|
2250
2332
|
this._resolveIdleWaitIfIdle();
|
|
2251
2333
|
}
|
|
@@ -2806,6 +2888,11 @@ export class AgentSession {
|
|
|
2806
2888
|
async executeBash(command, onChunk, options) {
|
|
2807
2889
|
const abortController = new AbortController();
|
|
2808
2890
|
this._bashAbortControllers.add(abortController);
|
|
2891
|
+
let resolveBashCompletion;
|
|
2892
|
+
const bashCompletion = new Promise((resolve) => {
|
|
2893
|
+
resolveBashCompletion = resolve;
|
|
2894
|
+
});
|
|
2895
|
+
this._bashCompletionPromises.add(bashCompletion);
|
|
2809
2896
|
// Apply command prefix if configured (e.g., "shopt -s expand_aliases" for alias support)
|
|
2810
2897
|
const prefix = this.settingsManager.getShellCommandPrefix();
|
|
2811
2898
|
const shellPath = this.settingsManager.getShellPath();
|
|
@@ -2823,6 +2910,8 @@ export class AgentSession {
|
|
|
2823
2910
|
}
|
|
2824
2911
|
finally {
|
|
2825
2912
|
this._bashAbortControllers.delete(abortController);
|
|
2913
|
+
this._bashCompletionPromises.delete(bashCompletion);
|
|
2914
|
+
resolveBashCompletion?.();
|
|
2826
2915
|
}
|
|
2827
2916
|
}
|
|
2828
2917
|
/**
|
|
@@ -2945,6 +3034,11 @@ export class AgentSession {
|
|
|
2945
3034
|
};
|
|
2946
3035
|
// Set up abort controller for summarization
|
|
2947
3036
|
this._branchSummaryAbortController = new AbortController();
|
|
3037
|
+
let resolveBranchSummaryCompletion;
|
|
3038
|
+
const branchSummaryCompletion = new Promise((resolve) => {
|
|
3039
|
+
resolveBranchSummaryCompletion = resolve;
|
|
3040
|
+
});
|
|
3041
|
+
this._branchSummaryCompletionPromise = branchSummaryCompletion;
|
|
2948
3042
|
try {
|
|
2949
3043
|
let extensionSummary;
|
|
2950
3044
|
let fromExtension = false;
|
|
@@ -3069,6 +3163,10 @@ export class AgentSession {
|
|
|
3069
3163
|
}
|
|
3070
3164
|
finally {
|
|
3071
3165
|
this._branchSummaryAbortController = undefined;
|
|
3166
|
+
if (this._branchSummaryCompletionPromise === branchSummaryCompletion) {
|
|
3167
|
+
this._branchSummaryCompletionPromise = undefined;
|
|
3168
|
+
}
|
|
3169
|
+
resolveBranchSummaryCompletion?.();
|
|
3072
3170
|
this._resolveIdleWaitIfIdle();
|
|
3073
3171
|
}
|
|
3074
3172
|
}
|