@oh-my-pi/pi-coding-agent 3.37.1 → 4.0.1
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 +103 -0
- package/README.md +44 -3
- package/docs/extensions.md +29 -4
- package/docs/sdk.md +3 -3
- package/package.json +5 -5
- package/src/cli/args.ts +8 -0
- package/src/config.ts +5 -15
- package/src/core/agent-session.ts +217 -51
- package/src/core/auth-storage.ts +456 -47
- package/src/core/bash-executor.ts +79 -14
- package/src/core/custom-commands/types.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/export-html/index.ts +33 -1
- package/src/core/export-html/template.css +99 -0
- package/src/core/export-html/template.generated.ts +1 -1
- package/src/core/export-html/template.js +133 -8
- package/src/core/extensions/index.ts +22 -4
- package/src/core/extensions/loader.ts +152 -214
- package/src/core/extensions/runner.ts +139 -79
- package/src/core/extensions/types.ts +143 -19
- package/src/core/extensions/wrapper.ts +5 -8
- package/src/core/hooks/types.ts +1 -1
- package/src/core/index.ts +2 -1
- package/src/core/keybindings.ts +4 -1
- package/src/core/model-registry.ts +4 -4
- package/src/core/model-resolver.ts +35 -26
- package/src/core/sdk.ts +96 -76
- package/src/core/settings-manager.ts +45 -14
- package/src/core/system-prompt.ts +5 -15
- package/src/core/tools/bash.ts +115 -54
- package/src/core/tools/find.ts +86 -7
- package/src/core/tools/grep.ts +27 -6
- package/src/core/tools/index.ts +15 -6
- package/src/core/tools/ls.ts +49 -18
- package/src/core/tools/render-utils.ts +2 -1
- package/src/core/tools/task/worker.ts +35 -12
- package/src/core/tools/web-search/auth.ts +37 -32
- package/src/core/tools/web-search/providers/anthropic.ts +35 -22
- package/src/index.ts +101 -9
- package/src/main.ts +60 -20
- package/src/migrations.ts +47 -2
- package/src/modes/index.ts +2 -2
- package/src/modes/interactive/components/assistant-message.ts +25 -7
- package/src/modes/interactive/components/bash-execution.ts +5 -0
- package/src/modes/interactive/components/branch-summary-message.ts +5 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +5 -0
- package/src/modes/interactive/components/countdown-timer.ts +38 -0
- package/src/modes/interactive/components/custom-editor.ts +8 -0
- package/src/modes/interactive/components/custom-message.ts +5 -0
- package/src/modes/interactive/components/footer.ts +2 -5
- package/src/modes/interactive/components/hook-input.ts +29 -20
- package/src/modes/interactive/components/hook-selector.ts +52 -38
- package/src/modes/interactive/components/index.ts +39 -0
- package/src/modes/interactive/components/login-dialog.ts +160 -0
- package/src/modes/interactive/components/model-selector.ts +10 -2
- package/src/modes/interactive/components/session-selector.ts +5 -1
- package/src/modes/interactive/components/settings-defs.ts +9 -0
- package/src/modes/interactive/components/status-line/segments.ts +3 -3
- package/src/modes/interactive/components/tool-execution.ts +9 -16
- package/src/modes/interactive/components/tree-selector.ts +1 -6
- package/src/modes/interactive/interactive-mode.ts +466 -215
- package/src/modes/interactive/theme/theme.ts +50 -2
- package/src/modes/print-mode.ts +78 -31
- package/src/modes/rpc/rpc-mode.ts +186 -78
- package/src/modes/rpc/rpc-types.ts +10 -3
- package/src/prompts/system-prompt.md +36 -28
- package/src/utils/clipboard.ts +90 -50
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +1 -1
- package/src/utils/tools-manager.ts +2 -2
|
@@ -19,7 +19,7 @@ import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-a
|
|
|
19
19
|
import type { Rule } from "../capability/rule";
|
|
20
20
|
import { getAuthPath } from "../config";
|
|
21
21
|
import { theme } from "../modes/interactive/theme/theme";
|
|
22
|
-
import { type BashResult, executeBash as executeBashCommand } from "./bash-executor";
|
|
22
|
+
import { type BashResult, executeBash as executeBashCommand, executeBashWithOperations } from "./bash-executor";
|
|
23
23
|
import {
|
|
24
24
|
type CompactionResult,
|
|
25
25
|
calculateContextTokens,
|
|
@@ -52,9 +52,11 @@ import { parseModelString } from "./model-resolver";
|
|
|
52
52
|
import { expandPromptTemplate, type PromptTemplate, parseCommandArgs } from "./prompt-templates";
|
|
53
53
|
import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions, SessionManager } from "./session-manager";
|
|
54
54
|
import type { SettingsManager, SkillsSettings } from "./settings-manager";
|
|
55
|
+
import type { Skill, SkillWarning } from "./skills";
|
|
55
56
|
import { expandSlashCommand, type FileSlashCommand } from "./slash-commands";
|
|
56
57
|
import { closeAllConnections } from "./ssh/connection-manager";
|
|
57
58
|
import { unmountAll } from "./ssh/sshfs-mount";
|
|
59
|
+
import type { BashOperations } from "./tools/bash";
|
|
58
60
|
import type { TtsrManager } from "./ttsr";
|
|
59
61
|
|
|
60
62
|
/** Session-specific events that extend the core AgentEvent */
|
|
@@ -85,6 +87,10 @@ export interface AgentSessionConfig {
|
|
|
85
87
|
slashCommands?: FileSlashCommand[];
|
|
86
88
|
/** Extension runner (created in main.ts with wrapped tools) */
|
|
87
89
|
extensionRunner?: ExtensionRunner;
|
|
90
|
+
/** Loaded skills (already discovered by SDK) */
|
|
91
|
+
skills?: Skill[];
|
|
92
|
+
/** Skill loading warnings (already captured by SDK) */
|
|
93
|
+
skillWarnings?: SkillWarning[];
|
|
88
94
|
/** Custom commands (TypeScript slash commands) */
|
|
89
95
|
customCommands?: LoadedCustomCommand[];
|
|
90
96
|
skillsSettings?: Required<SkillsSettings>;
|
|
@@ -154,9 +160,9 @@ const THINKING_LEVELS: ThinkingLevel[] = ["off", "minimal", "low", "medium", "hi
|
|
|
154
160
|
const THINKING_LEVELS_WITH_XHIGH: ThinkingLevel[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
155
161
|
|
|
156
162
|
const noOpUIContext: ExtensionUIContext = {
|
|
157
|
-
select: async () => undefined,
|
|
158
|
-
confirm: async () => false,
|
|
159
|
-
input: async () => undefined,
|
|
163
|
+
select: async (_title, _options, _dialogOptions) => undefined,
|
|
164
|
+
confirm: async (_title, _message, _dialogOptions) => false,
|
|
165
|
+
input: async (_title, _placeholder, _dialogOptions) => undefined,
|
|
160
166
|
notify: () => {},
|
|
161
167
|
setStatus: () => {},
|
|
162
168
|
setWidget: () => {},
|
|
@@ -168,6 +174,12 @@ const noOpUIContext: ExtensionUIContext = {
|
|
|
168
174
|
get theme() {
|
|
169
175
|
return theme;
|
|
170
176
|
},
|
|
177
|
+
getAllThemes: () => [],
|
|
178
|
+
getTheme: () => undefined,
|
|
179
|
+
setTheme: (_theme) => ({ success: false, error: "UI not available" }),
|
|
180
|
+
setFooter: () => {},
|
|
181
|
+
setHeader: () => {},
|
|
182
|
+
setEditorComponent: () => {},
|
|
171
183
|
};
|
|
172
184
|
|
|
173
185
|
async function cleanupSshResources(): Promise<void> {
|
|
@@ -224,6 +236,9 @@ export class AgentSession {
|
|
|
224
236
|
private _extensionRunner: ExtensionRunner | undefined = undefined;
|
|
225
237
|
private _turnIndex = 0;
|
|
226
238
|
|
|
239
|
+
private _skills: Skill[];
|
|
240
|
+
private _skillWarnings: SkillWarning[];
|
|
241
|
+
|
|
227
242
|
// Custom commands (TypeScript slash commands)
|
|
228
243
|
private _customCommands: LoadedCustomCommand[] = [];
|
|
229
244
|
|
|
@@ -250,6 +265,8 @@ export class AgentSession {
|
|
|
250
265
|
this._promptTemplates = config.promptTemplates ?? [];
|
|
251
266
|
this._slashCommands = config.slashCommands ?? [];
|
|
252
267
|
this._extensionRunner = config.extensionRunner;
|
|
268
|
+
this._skills = config.skills ?? [];
|
|
269
|
+
this._skillWarnings = config.skillWarnings ?? [];
|
|
253
270
|
this._customCommands = config.customCommands ?? [];
|
|
254
271
|
this._skillsSettings = config.skillsSettings;
|
|
255
272
|
this._modelRegistry = config.modelRegistry;
|
|
@@ -578,6 +595,11 @@ export class AgentSession {
|
|
|
578
595
|
return this.agent.state.isStreaming;
|
|
579
596
|
}
|
|
580
597
|
|
|
598
|
+
/** Current retry attempt (0 if not retrying) */
|
|
599
|
+
get retryAttempt(): number {
|
|
600
|
+
return this._retryAttempt;
|
|
601
|
+
}
|
|
602
|
+
|
|
581
603
|
/**
|
|
582
604
|
* Get the names of currently active tools.
|
|
583
605
|
* Returns the names of tools currently set on the agent.
|
|
@@ -788,7 +810,11 @@ export class AgentSession {
|
|
|
788
810
|
|
|
789
811
|
// Emit before_agent_start extension event
|
|
790
812
|
if (this._extensionRunner) {
|
|
791
|
-
const result = await this._extensionRunner.emitBeforeAgentStart(
|
|
813
|
+
const result = await this._extensionRunner.emitBeforeAgentStart(
|
|
814
|
+
expandedText,
|
|
815
|
+
options?.images,
|
|
816
|
+
this._baseSystemPrompt,
|
|
817
|
+
);
|
|
792
818
|
if (result?.messages) {
|
|
793
819
|
for (const msg of result.messages) {
|
|
794
820
|
messages.push({
|
|
@@ -802,8 +828,8 @@ export class AgentSession {
|
|
|
802
828
|
}
|
|
803
829
|
}
|
|
804
830
|
|
|
805
|
-
if (result?.
|
|
806
|
-
this.agent.setSystemPrompt(
|
|
831
|
+
if (result?.systemPrompt !== undefined) {
|
|
832
|
+
this.agent.setSystemPrompt(result.systemPrompt);
|
|
807
833
|
} else {
|
|
808
834
|
this.agent.setSystemPrompt(this._baseSystemPrompt);
|
|
809
835
|
}
|
|
@@ -861,6 +887,10 @@ export class AgentSession {
|
|
|
861
887
|
void this.abort();
|
|
862
888
|
},
|
|
863
889
|
hasPendingMessages: () => this.queuedMessageCount > 0,
|
|
890
|
+
shutdown: () => {
|
|
891
|
+
void this.dispose();
|
|
892
|
+
process.exit(0);
|
|
893
|
+
},
|
|
864
894
|
hasQueuedMessages: () => this.queuedMessageCount > 0,
|
|
865
895
|
waitForIdle: () => this.agent.waitForIdle(),
|
|
866
896
|
newSession: async (options) => {
|
|
@@ -905,7 +935,7 @@ export class AgentSession {
|
|
|
905
935
|
const ctx = {
|
|
906
936
|
...baseCtx,
|
|
907
937
|
hasQueuedMessages: baseCtx.hasPendingMessages,
|
|
908
|
-
} as HookCommandContext;
|
|
938
|
+
} as unknown as HookCommandContext;
|
|
909
939
|
|
|
910
940
|
try {
|
|
911
941
|
const args = parseCommandArgs(argsString);
|
|
@@ -1052,6 +1082,45 @@ export class AgentSession {
|
|
|
1052
1082
|
);
|
|
1053
1083
|
}
|
|
1054
1084
|
|
|
1085
|
+
/**
|
|
1086
|
+
* Send a user message to the agent. Always triggers a turn.
|
|
1087
|
+
* When the agent is streaming, use deliverAs to specify how to queue the message.
|
|
1088
|
+
*
|
|
1089
|
+
* @param content User message content (string or content array)
|
|
1090
|
+
* @param options.deliverAs Delivery mode when streaming: "steer" or "followUp"
|
|
1091
|
+
*/
|
|
1092
|
+
async sendUserMessage(
|
|
1093
|
+
content: string | (TextContent | ImageContent)[],
|
|
1094
|
+
options?: { deliverAs?: "steer" | "followUp" },
|
|
1095
|
+
): Promise<void> {
|
|
1096
|
+
// Normalize content to text string + optional images
|
|
1097
|
+
let text: string;
|
|
1098
|
+
let images: ImageContent[] | undefined;
|
|
1099
|
+
|
|
1100
|
+
if (typeof content === "string") {
|
|
1101
|
+
text = content;
|
|
1102
|
+
} else {
|
|
1103
|
+
const textParts: string[] = [];
|
|
1104
|
+
images = [];
|
|
1105
|
+
for (const part of content) {
|
|
1106
|
+
if (part.type === "text") {
|
|
1107
|
+
textParts.push(part.text);
|
|
1108
|
+
} else {
|
|
1109
|
+
images.push(part);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
text = textParts.join("\n");
|
|
1113
|
+
if (images.length === 0) images = undefined;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion
|
|
1117
|
+
await this.prompt(text, {
|
|
1118
|
+
expandPromptTemplates: false,
|
|
1119
|
+
streamingBehavior: options?.deliverAs,
|
|
1120
|
+
images,
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1055
1124
|
/**
|
|
1056
1125
|
* Clear queued messages and return them.
|
|
1057
1126
|
* Useful for restoring to editor when user aborts.
|
|
@@ -1075,10 +1144,40 @@ export class AgentSession {
|
|
|
1075
1144
|
return { steering: this._steeringMessages, followUp: this._followUpMessages };
|
|
1076
1145
|
}
|
|
1077
1146
|
|
|
1147
|
+
/**
|
|
1148
|
+
* Pop the last queued message (steering first, then follow-up).
|
|
1149
|
+
* Used by dequeue keybinding to restore messages to editor one at a time.
|
|
1150
|
+
*/
|
|
1151
|
+
popLastQueuedMessage(): string | undefined {
|
|
1152
|
+
// Pop from steering first (LIFO)
|
|
1153
|
+
if (this._steeringMessages.length > 0) {
|
|
1154
|
+
const message = this._steeringMessages.pop();
|
|
1155
|
+
this.agent.popLastSteer();
|
|
1156
|
+
return message;
|
|
1157
|
+
}
|
|
1158
|
+
// Then from follow-up
|
|
1159
|
+
if (this._followUpMessages.length > 0) {
|
|
1160
|
+
const message = this._followUpMessages.pop();
|
|
1161
|
+
this.agent.popLastFollowUp();
|
|
1162
|
+
return message;
|
|
1163
|
+
}
|
|
1164
|
+
return undefined;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1078
1167
|
get skillsSettings(): Required<SkillsSettings> | undefined {
|
|
1079
1168
|
return this._skillsSettings;
|
|
1080
1169
|
}
|
|
1081
1170
|
|
|
1171
|
+
/** Skills loaded by SDK (empty if --no-skills or skills: [] was passed) */
|
|
1172
|
+
get skills(): readonly Skill[] {
|
|
1173
|
+
return this._skills;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/** Skill loading warnings captured by SDK */
|
|
1177
|
+
get skillWarnings(): readonly SkillWarning[] {
|
|
1178
|
+
return this._skillWarnings;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1082
1181
|
/**
|
|
1083
1182
|
* Abort current operation and wait for agent to become idle.
|
|
1084
1183
|
*/
|
|
@@ -1115,6 +1214,7 @@ export class AgentSession {
|
|
|
1115
1214
|
this.agent.reset();
|
|
1116
1215
|
await this.sessionManager.flush();
|
|
1117
1216
|
this.sessionManager.newSession(options);
|
|
1217
|
+
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
1118
1218
|
this._steeringMessages = [];
|
|
1119
1219
|
this._followUpMessages = [];
|
|
1120
1220
|
this._pendingNextTurnMessages = [];
|
|
@@ -1311,16 +1411,12 @@ export class AgentSession {
|
|
|
1311
1411
|
|
|
1312
1412
|
/**
|
|
1313
1413
|
* Set thinking level.
|
|
1314
|
-
* Clamps to model capabilities
|
|
1414
|
+
* Clamps to model capabilities based on available thinking levels.
|
|
1315
1415
|
* Saves to session and settings.
|
|
1316
1416
|
*/
|
|
1317
1417
|
setThinkingLevel(level: ThinkingLevel): void {
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
effectiveLevel = "off";
|
|
1321
|
-
} else if (level === "xhigh" && !this.supportsXhighThinking()) {
|
|
1322
|
-
effectiveLevel = "high";
|
|
1323
|
-
}
|
|
1418
|
+
const availableLevels = this.getAvailableThinkingLevels();
|
|
1419
|
+
const effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels);
|
|
1324
1420
|
this.agent.setThinkingLevel(effectiveLevel);
|
|
1325
1421
|
this.sessionManager.appendThinkingLevelChange(effectiveLevel);
|
|
1326
1422
|
this.settingsManager.setDefaultThinkingLevel(effectiveLevel);
|
|
@@ -1344,8 +1440,10 @@ export class AgentSession {
|
|
|
1344
1440
|
|
|
1345
1441
|
/**
|
|
1346
1442
|
* Get available thinking levels for current model.
|
|
1443
|
+
* The provider will clamp to what the specific model supports internally.
|
|
1347
1444
|
*/
|
|
1348
1445
|
getAvailableThinkingLevels(): ThinkingLevel[] {
|
|
1446
|
+
if (!this.supportsThinking()) return ["off"];
|
|
1349
1447
|
return this.supportsXhighThinking() ? THINKING_LEVELS_WITH_XHIGH : THINKING_LEVELS;
|
|
1350
1448
|
}
|
|
1351
1449
|
|
|
@@ -1363,6 +1461,24 @@ export class AgentSession {
|
|
|
1363
1461
|
return !!this.model?.reasoning;
|
|
1364
1462
|
}
|
|
1365
1463
|
|
|
1464
|
+
private _clampThinkingLevel(level: ThinkingLevel, availableLevels: ThinkingLevel[]): ThinkingLevel {
|
|
1465
|
+
const ordered = THINKING_LEVELS_WITH_XHIGH;
|
|
1466
|
+
const available = new Set(availableLevels);
|
|
1467
|
+
const requestedIndex = ordered.indexOf(level);
|
|
1468
|
+
if (requestedIndex === -1) {
|
|
1469
|
+
return availableLevels[0] ?? "off";
|
|
1470
|
+
}
|
|
1471
|
+
for (let i = requestedIndex; i < ordered.length; i++) {
|
|
1472
|
+
const candidate = ordered[i];
|
|
1473
|
+
if (available.has(candidate)) return candidate;
|
|
1474
|
+
}
|
|
1475
|
+
for (let i = requestedIndex - 1; i >= 0; i--) {
|
|
1476
|
+
const candidate = ordered[i];
|
|
1477
|
+
if (available.has(candidate)) return candidate;
|
|
1478
|
+
}
|
|
1479
|
+
return availableLevels[0] ?? "off";
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1366
1482
|
// =========================================================================
|
|
1367
1483
|
// Message Queue Mode Management
|
|
1368
1484
|
// =========================================================================
|
|
@@ -1548,8 +1664,24 @@ export class AgentSession {
|
|
|
1548
1664
|
|
|
1549
1665
|
const contextWindow = this.model?.contextWindow ?? 0;
|
|
1550
1666
|
|
|
1667
|
+
// Skip overflow check if the message came from a different model.
|
|
1668
|
+
// This handles the case where user switched from a smaller-context model (e.g. opus)
|
|
1669
|
+
// to a larger-context model (e.g. codex) - the overflow error from the old model
|
|
1670
|
+
// shouldn't trigger compaction for the new model.
|
|
1671
|
+
const sameModel =
|
|
1672
|
+
this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;
|
|
1673
|
+
|
|
1674
|
+
// Skip overflow check if the error is from before a compaction in the current path.
|
|
1675
|
+
// This handles the case where an error was kept after compaction (in the "kept" region).
|
|
1676
|
+
// The error shouldn't trigger another compaction since we already compacted.
|
|
1677
|
+
// Example: opus fails → switch to codex → compact → switch back to opus → opus error
|
|
1678
|
+
// is still in context but shouldn't trigger compaction again.
|
|
1679
|
+
const compactionEntry = this.sessionManager.getBranch().find((e) => e.type === "compaction");
|
|
1680
|
+
const errorIsFromBeforeCompaction =
|
|
1681
|
+
compactionEntry && assistantMessage.timestamp < new Date(compactionEntry.timestamp).getTime();
|
|
1682
|
+
|
|
1551
1683
|
// Case 1: Overflow - LLM returned context overflow error
|
|
1552
|
-
if (isContextOverflow(assistantMessage, contextWindow)) {
|
|
1684
|
+
if (sameModel && !errorIsFromBeforeCompaction && isContextOverflow(assistantMessage, contextWindow)) {
|
|
1553
1685
|
// Remove the error message from agent state (it IS saved to session for history,
|
|
1554
1686
|
// but we don't want it in context for the retry)
|
|
1555
1687
|
const messages = this.agent.state.messages;
|
|
@@ -1849,12 +1981,16 @@ export class AgentSession {
|
|
|
1849
1981
|
}
|
|
1850
1982
|
|
|
1851
1983
|
private _isRetryableErrorMessage(errorMessage: string): boolean {
|
|
1852
|
-
// Match: overloaded_error, rate limit, 429, 500, 502, 503, 504, service unavailable, connection error
|
|
1853
|
-
return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error/i.test(
|
|
1984
|
+
// Match: overloaded_error, rate limit, usage limit, 429, 500, 502, 503, 504, service unavailable, connection error
|
|
1985
|
+
return /overloaded|rate.?limit|usage.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error/i.test(
|
|
1854
1986
|
errorMessage,
|
|
1855
1987
|
);
|
|
1856
1988
|
}
|
|
1857
1989
|
|
|
1990
|
+
private _isUsageLimitErrorMessage(errorMessage: string): boolean {
|
|
1991
|
+
return /usage.?limit|usage_limit_reached|limit_reached/i.test(errorMessage);
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1858
1994
|
private _parseRetryAfterMsFromError(errorMessage: string): number | undefined {
|
|
1859
1995
|
const now = Date.now();
|
|
1860
1996
|
const retryAfterMsMatch = /retry-after-ms\s*[:=]\s*(\d+)/i.exec(errorMessage);
|
|
@@ -1931,14 +2067,30 @@ export class AgentSession {
|
|
|
1931
2067
|
return false;
|
|
1932
2068
|
}
|
|
1933
2069
|
|
|
1934
|
-
const
|
|
2070
|
+
const errorMessage = message.errorMessage || "Unknown error";
|
|
2071
|
+
let delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);
|
|
2072
|
+
|
|
2073
|
+
if (this.model && this._isUsageLimitErrorMessage(errorMessage)) {
|
|
2074
|
+
const retryAfterMs = this._parseRetryAfterMsFromError(errorMessage);
|
|
2075
|
+
const switched = await this._modelRegistry.authStorage.markUsageLimitReached(
|
|
2076
|
+
this.model.provider,
|
|
2077
|
+
this.sessionId,
|
|
2078
|
+
{
|
|
2079
|
+
retryAfterMs,
|
|
2080
|
+
baseUrl: this.model.baseUrl,
|
|
2081
|
+
},
|
|
2082
|
+
);
|
|
2083
|
+
if (switched) {
|
|
2084
|
+
delayMs = 0;
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
1935
2087
|
|
|
1936
2088
|
this._emit({
|
|
1937
2089
|
type: "auto_retry_start",
|
|
1938
2090
|
attempt: this._retryAttempt,
|
|
1939
2091
|
maxAttempts: settings.maxRetries,
|
|
1940
2092
|
delayMs,
|
|
1941
|
-
errorMessage
|
|
2093
|
+
errorMessage,
|
|
1942
2094
|
});
|
|
1943
2095
|
|
|
1944
2096
|
// Remove error message from agent state (keep in session for history)
|
|
@@ -2005,7 +2157,7 @@ export class AgentSession {
|
|
|
2005
2157
|
*/
|
|
2006
2158
|
abortRetry(): void {
|
|
2007
2159
|
this._retryAbortController?.abort();
|
|
2008
|
-
|
|
2160
|
+
// Note: _retryAttempt is reset in the catch block of _autoRetry
|
|
2009
2161
|
this._resolveRetry();
|
|
2010
2162
|
}
|
|
2011
2163
|
|
|
@@ -2046,51 +2198,63 @@ export class AgentSession {
|
|
|
2046
2198
|
* @param command The bash command to execute
|
|
2047
2199
|
* @param onChunk Optional streaming callback for output
|
|
2048
2200
|
* @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)
|
|
2201
|
+
* @param options.operations Custom BashOperations for remote execution
|
|
2049
2202
|
*/
|
|
2050
2203
|
async executeBash(
|
|
2051
2204
|
command: string,
|
|
2052
2205
|
onChunk?: (chunk: string) => void,
|
|
2053
|
-
options?: { excludeFromContext?: boolean },
|
|
2206
|
+
options?: { excludeFromContext?: boolean; operations?: BashOperations },
|
|
2054
2207
|
): Promise<BashResult> {
|
|
2055
2208
|
this._bashAbortController = new AbortController();
|
|
2056
2209
|
|
|
2057
2210
|
try {
|
|
2058
|
-
const result =
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
output: result.output,
|
|
2068
|
-
exitCode: result.exitCode,
|
|
2069
|
-
cancelled: result.cancelled,
|
|
2070
|
-
truncated: result.truncated,
|
|
2071
|
-
fullOutputPath: result.fullOutputPath,
|
|
2072
|
-
timestamp: Date.now(),
|
|
2073
|
-
excludeFromContext: options?.excludeFromContext,
|
|
2074
|
-
};
|
|
2075
|
-
|
|
2076
|
-
// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering
|
|
2077
|
-
if (this.isStreaming) {
|
|
2078
|
-
// Queue for later - will be flushed on agent_end
|
|
2079
|
-
this._pendingBashMessages.push(bashMessage);
|
|
2080
|
-
} else {
|
|
2081
|
-
// Add to agent state immediately
|
|
2082
|
-
this.agent.appendMessage(bashMessage);
|
|
2083
|
-
|
|
2084
|
-
// Save to session
|
|
2085
|
-
this.sessionManager.appendMessage(bashMessage);
|
|
2086
|
-
}
|
|
2211
|
+
const result = options?.operations
|
|
2212
|
+
? await executeBashWithOperations(command, process.cwd(), options.operations, {
|
|
2213
|
+
onChunk,
|
|
2214
|
+
signal: this._bashAbortController.signal,
|
|
2215
|
+
})
|
|
2216
|
+
: await executeBashCommand(command, {
|
|
2217
|
+
onChunk,
|
|
2218
|
+
signal: this._bashAbortController.signal,
|
|
2219
|
+
});
|
|
2087
2220
|
|
|
2221
|
+
this.recordBashResult(command, result, options);
|
|
2088
2222
|
return result;
|
|
2089
2223
|
} finally {
|
|
2090
2224
|
this._bashAbortController = undefined;
|
|
2091
2225
|
}
|
|
2092
2226
|
}
|
|
2093
2227
|
|
|
2228
|
+
/**
|
|
2229
|
+
* Record a bash execution result in session history.
|
|
2230
|
+
* Used by executeBash and by extensions that handle bash execution themselves.
|
|
2231
|
+
*/
|
|
2232
|
+
recordBashResult(command: string, result: BashResult, options?: { excludeFromContext?: boolean }): void {
|
|
2233
|
+
const bashMessage: BashExecutionMessage = {
|
|
2234
|
+
role: "bashExecution",
|
|
2235
|
+
command,
|
|
2236
|
+
output: result.output,
|
|
2237
|
+
exitCode: result.exitCode,
|
|
2238
|
+
cancelled: result.cancelled,
|
|
2239
|
+
truncated: result.truncated,
|
|
2240
|
+
fullOutputPath: result.fullOutputPath,
|
|
2241
|
+
timestamp: Date.now(),
|
|
2242
|
+
excludeFromContext: options?.excludeFromContext,
|
|
2243
|
+
};
|
|
2244
|
+
|
|
2245
|
+
// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering
|
|
2246
|
+
if (this.isStreaming) {
|
|
2247
|
+
// Queue for later - will be flushed on agent_end
|
|
2248
|
+
this._pendingBashMessages.push(bashMessage);
|
|
2249
|
+
} else {
|
|
2250
|
+
// Add to agent state immediately
|
|
2251
|
+
this.agent.appendMessage(bashMessage);
|
|
2252
|
+
|
|
2253
|
+
// Save to session
|
|
2254
|
+
this.sessionManager.appendMessage(bashMessage);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2094
2258
|
/**
|
|
2095
2259
|
* Cancel running bash command.
|
|
2096
2260
|
*/
|
|
@@ -2163,6 +2327,7 @@ export class AgentSession {
|
|
|
2163
2327
|
|
|
2164
2328
|
// Set new session
|
|
2165
2329
|
await this.sessionManager.setSessionFile(sessionPath);
|
|
2330
|
+
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
2166
2331
|
|
|
2167
2332
|
// Reload messages
|
|
2168
2333
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
@@ -2247,6 +2412,7 @@ export class AgentSession {
|
|
|
2247
2412
|
} else {
|
|
2248
2413
|
this.sessionManager.createBranchedSession(selectedEntry.parentId);
|
|
2249
2414
|
}
|
|
2415
|
+
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
2250
2416
|
|
|
2251
2417
|
// Reload messages from entries (works for both file and in-memory mode)
|
|
2252
2418
|
const sessionContext = this.sessionManager.buildSessionContext();
|