privateer-agent 0.12.20 → 0.12.21
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "privateer-agent",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.21",
|
|
4
4
|
"description": "Privacy-first terminal coding agent — bring your own model across 20 providers (Anthropic, OpenAI, OpenRouter, Google, local Ollama…). Safe-by-default permissions, MCP, sub-agents, workflows, and verifiable TEE inference. Built on the Pi toolkit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -68,7 +68,7 @@ index 4600b23..075ecae 100644
|
|
|
68
68
|
export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
|
|
69
69
|
export const ENV_SESSION_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_SESSION_DIR`;
|
|
70
70
|
diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js
|
|
71
|
-
index ce8a9a2..
|
|
71
|
+
index ce8a9a2..b7b339e 100644
|
|
72
72
|
--- a/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js
|
|
73
73
|
+++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js
|
|
74
74
|
@@ -38,6 +38,70 @@ import { createLocalBashOperations } from "./tools/bash.js";
|
|
@@ -174,7 +174,7 @@ index ce8a9a2..c52ea80 100644
|
|
|
174
174
|
// Emit to extensions first
|
|
175
175
|
await this._emitExtensionEvent(event);
|
|
176
176
|
// Notify all listeners
|
|
177
|
-
@@ -772,6 +854,
|
|
177
|
+
@@ -772,6 +854,25 @@ export class AgentSession {
|
|
178
178
|
finalError: msg.errorMessage,
|
|
179
179
|
});
|
|
180
180
|
this._retryAttempt = 0;
|
|
@@ -186,11 +186,21 @@ index ce8a9a2..c52ea80 100644
|
|
|
186
186
|
+ // That re-entry is what produced the endless "retrying 1/3…2/3…3/3" loop
|
|
187
187
|
+ // on a persistently-failing provider/tool. Context-overflow errors never
|
|
188
188
|
+ // reach this branch (_isRetryableError → false), so compaction is untouched.
|
|
189
|
+
+ return false;
|
|
190
|
+
+ }
|
|
191
|
+
+ // Privateer patch: a first-attempt hard 4xx (a WAF 403, a 401, a 404) is also
|
|
192
|
+
+ // terminal. Stock Pi still falls through to compaction here because _retryAttempt
|
|
193
|
+
+ // is 0, and _checkCompaction will summarise a session that already has usage.
|
|
194
|
+
+ // That summary is another LLM call to the SAME blocked endpoint; retryAssistantCall
|
|
195
|
+
+ // classifies the raw HTML body as transient (it contains "500"/"502"), so the
|
|
196
|
+
+ // agent looks hung — retrying a compaction that can never succeed — until the
|
|
197
|
+
+ // summarizer budget burns. Context overflow never matches isHardHttpFailure.
|
|
198
|
+
+ if (msg.stopReason === "error" && isHardHttpFailure(msg.errorMessage)) {
|
|
189
199
|
+ return false;
|
|
190
200
|
}
|
|
191
201
|
if (await this._checkCompaction(msg)) {
|
|
192
202
|
return true;
|
|
193
|
-
@@ -852,6 +
|
|
203
|
+
@@ -852,6 +953,14 @@ export class AgentSession {
|
|
194
204
|
if (!hasConfiguredAuth) {
|
|
195
205
|
const isOAuth = this._modelRuntime.isUsingOAuth(this.model.provider);
|
|
196
206
|
if (isOAuth) {
|
|
@@ -205,7 +215,7 @@ index ce8a9a2..c52ea80 100644
|
|
|
205
215
|
throw new Error(`Authentication failed for "${this.model.provider}". ` +
|
|
206
216
|
`Credentials may have expired or network is unavailable. ` +
|
|
207
217
|
`Run '/login ${this.model.provider}' to re-authenticate.`);
|
|
208
|
-
@@ -2084,6 +
|
|
218
|
+
@@ -2084,6 +2193,27 @@ export class AgentSession {
|
|
209
219
|
// Context overflow is handled by compaction, not retry.
|
|
210
220
|
if (isContextOverflow(message, this.model?.contextWindow ?? 0))
|
|
211
221
|
return false;
|
|
@@ -1052,17 +1062,19 @@ index 6f7ed6a..b0ed0ae 100644
|
|
|
1052
1062
|
getResourcePattern(item) {
|
|
1053
1063
|
const scope = item.metadata.scope;
|
|
1054
1064
|
diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
|
|
1055
|
-
index 1d9f046..
|
|
1065
|
+
index 1d9f046..c0326e3 100644
|
|
1056
1066
|
--- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
|
|
1057
1067
|
+++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
|
|
1058
|
-
@@ -1,
|
|
1068
|
+
@@ -1,7 +1,6 @@
|
|
1059
1069
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
1060
1070
|
-import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
1061
1071
|
+import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
1062
1072
|
import { areExperimentalFeaturesEnabled } from "../../../core/experimental.js";
|
|
1063
|
-
|
|
1073
|
+
-import { addUsageToTotals, createUsageTotals } from "../../../core/usage-totals.js";
|
|
1064
1074
|
import { theme } from "../theme/theme.js";
|
|
1065
|
-
|
|
1075
|
+
/**
|
|
1076
|
+
* Sanitize text for display in a single-line status.
|
|
1077
|
+
@@ -14,6 +13,43 @@ function sanitizeStatusText(text) {
|
|
1066
1078
|
.replace(/ +/g, " ")
|
|
1067
1079
|
.trim();
|
|
1068
1080
|
}
|
|
@@ -1106,7 +1118,182 @@ index 1d9f046..2a1fba8 100644
|
|
|
1106
1118
|
/**
|
|
1107
1119
|
* Format token counts for compact footer display.
|
|
1108
1120
|
*/
|
|
1109
|
-
@@ -
|
|
1121
|
+
@@ -41,8 +77,11 @@ export function formatCwdForFooter(cwd, home) {
|
|
1122
|
+
return relativeToHome === "" ? "~" : `~${sep}${relativeToHome}`;
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
- * Footer component that shows pwd, token stats, and context usage.
|
|
1126
|
+
- * Computes token/context stats from session, gets git branch and extension statuses from provider.
|
|
1127
|
+
+ * Footer component that shows pwd (with git branch) and the active model.
|
|
1128
|
+
+ *
|
|
1129
|
+
+ * Privateer trimmed this: upstream also drew a second row of cumulative token
|
|
1130
|
+
+ * counters, cache-hit rate, cost and a context gauge. Gets git branch and
|
|
1131
|
+
+ * extension statuses from provider.
|
|
1132
|
+
*/
|
|
1133
|
+
export class FooterComponent {
|
|
1134
|
+
autoCompactEnabled = true;
|
|
1135
|
+
@@ -74,29 +113,9 @@ export class FooterComponent {
|
|
1136
|
+
}
|
|
1137
|
+
render(width) {
|
|
1138
|
+
const state = this.session.state;
|
|
1139
|
+
- // Calculate cumulative usage from ALL session entries (not just post-compaction messages)
|
|
1140
|
+
- const usageTotals = createUsageTotals();
|
|
1141
|
+
- let latestCacheHitRate;
|
|
1142
|
+
- for (const entry of this.session.sessionManager.getEntries()) {
|
|
1143
|
+
- if (entry.type === "message" && entry.message.role === "assistant") {
|
|
1144
|
+
- addUsageToTotals(usageTotals, entry.message.usage);
|
|
1145
|
+
- const latestPromptTokens = entry.message.usage.input + entry.message.usage.cacheRead + entry.message.usage.cacheWrite;
|
|
1146
|
+
- latestCacheHitRate =
|
|
1147
|
+
- latestPromptTokens > 0 ? (entry.message.usage.cacheRead / latestPromptTokens) * 100 : undefined;
|
|
1148
|
+
- }
|
|
1149
|
+
- else if (entry.type === "message" && entry.message.role === "toolResult" && entry.message.usage) {
|
|
1150
|
+
- addUsageToTotals(usageTotals, entry.message.usage);
|
|
1151
|
+
- }
|
|
1152
|
+
- else if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) {
|
|
1153
|
+
- addUsageToTotals(usageTotals, entry.usage);
|
|
1154
|
+
- }
|
|
1155
|
+
- }
|
|
1156
|
+
- // Calculate context usage from session (handles compaction correctly).
|
|
1157
|
+
- // After compaction, tokens are unknown until the next LLM response.
|
|
1158
|
+
- const contextUsage = this.session.getContextUsage();
|
|
1159
|
+
- const contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;
|
|
1160
|
+
- const contextPercentValue = contextUsage?.percent ?? 0;
|
|
1161
|
+
- const contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : "?";
|
|
1162
|
+
+ // Privateer: no token counters, cache-hit rate, cost or context gauge in the
|
|
1163
|
+
+ // footer — so the accumulation that fed them is gone too. Upstream walked EVERY
|
|
1164
|
+
+ // session entry on EVERY render to build numbers we no longer draw.
|
|
1165
|
+
// Replace home directory with ~
|
|
1166
|
+
let pwd = formatCwdForFooter(this.session.sessionManager.getCwd(), process.env.HOME || process.env.USERPROFILE);
|
|
1167
|
+
// Add git branch if available
|
|
1168
|
+
@@ -109,56 +128,24 @@ export class FooterComponent {
|
|
1169
|
+
if (sessionName) {
|
|
1170
|
+
pwd = `${pwd} • ${sessionName}`;
|
|
1171
|
+
}
|
|
1172
|
+
- // Build stats line
|
|
1173
|
+
- const statsParts = [];
|
|
1174
|
+
- if (usageTotals.input)
|
|
1175
|
+
- statsParts.push(`↑${formatTokens(usageTotals.input)}`);
|
|
1176
|
+
- if (usageTotals.output)
|
|
1177
|
+
- statsParts.push(`↓${formatTokens(usageTotals.output)}`);
|
|
1178
|
+
- if (usageTotals.cacheRead)
|
|
1179
|
+
- statsParts.push(`R${formatTokens(usageTotals.cacheRead)}`);
|
|
1180
|
+
- if (usageTotals.cacheWrite)
|
|
1181
|
+
- statsParts.push(`W${formatTokens(usageTotals.cacheWrite)}`);
|
|
1182
|
+
- if ((usageTotals.cacheRead > 0 || usageTotals.cacheWrite > 0) && latestCacheHitRate !== undefined) {
|
|
1183
|
+
- statsParts.push(`CH${latestCacheHitRate.toFixed(1)}%`);
|
|
1184
|
+
- }
|
|
1185
|
+
- // Kimi Coding is subscription-backed despite using API-key authentication.
|
|
1186
|
+
- const usingSubscription = state.model
|
|
1187
|
+
- ? state.model.provider === "kimi-coding" || this.session.modelRuntime.isUsingSubscription(state.model.provider)
|
|
1188
|
+
- : false;
|
|
1189
|
+
- if (usageTotals.cost || usingSubscription) {
|
|
1190
|
+
- const costStr = `$${usageTotals.cost.toFixed(3)}${usingSubscription ? " (sub)" : ""}`;
|
|
1191
|
+
- statsParts.push(costStr);
|
|
1192
|
+
- }
|
|
1193
|
+
- // Colorize context percentage based on usage
|
|
1194
|
+
- let contextPercentStr;
|
|
1195
|
+
- const autoIndicator = this.autoCompactEnabled ? " (auto)" : "";
|
|
1196
|
+
- const contextPercentDisplay = contextPercent === "?"
|
|
1197
|
+
- ? `?/${formatTokens(contextWindow)}${autoIndicator}`
|
|
1198
|
+
- : `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;
|
|
1199
|
+
- if (contextPercentValue > 90) {
|
|
1200
|
+
- contextPercentStr = theme.fg("error", contextPercentDisplay);
|
|
1201
|
+
- }
|
|
1202
|
+
- else if (contextPercentValue > 70) {
|
|
1203
|
+
- contextPercentStr = theme.fg("warning", contextPercentDisplay);
|
|
1204
|
+
- }
|
|
1205
|
+
- else {
|
|
1206
|
+
- contextPercentStr = contextPercentDisplay;
|
|
1207
|
+
- }
|
|
1208
|
+
- statsParts.push(contextPercentStr);
|
|
1209
|
+
- if (areExperimentalFeaturesEnabled()) {
|
|
1210
|
+
- statsParts.push(`${theme.fg("dim", "•")} ${theme.bold(theme.fg("warning", "xp"))}`);
|
|
1211
|
+
- }
|
|
1212
|
+
- let statsLeft = statsParts.join(" ");
|
|
1213
|
+
+ // Privateer: ONE footer row — the working directory on the left, the model on
|
|
1214
|
+
+ // the right. Upstream spent a second row on cumulative token counters, cache-hit
|
|
1215
|
+
+ // rate and cost; those are session trivia that never change what you do next, and
|
|
1216
|
+
+ // they made the busiest part of the screen the least useful. The `xp` flag stays:
|
|
1217
|
+
+ // it warns that experimental features are live, which is not trivia.
|
|
1218
|
+
+ const experimentalFlag = areExperimentalFeaturesEnabled()
|
|
1219
|
+
+ ? ` ${theme.fg("dim", "•")} ${theme.bold(theme.fg("warning", "xp"))}`
|
|
1220
|
+
+ : "";
|
|
1221
|
+
+ let left = pwd + experimentalFlag;
|
|
1222
|
+
// Add model name on the right side, plus thinking level if model supports it
|
|
1223
|
+
const modelName = state.model?.id || "no-model";
|
|
1224
|
+
- let statsLeftWidth = visibleWidth(statsLeft);
|
|
1225
|
+
- // If statsLeft is too wide, truncate it
|
|
1226
|
+
- if (statsLeftWidth > width) {
|
|
1227
|
+
- statsLeft = truncateToWidth(statsLeft, width, "...");
|
|
1228
|
+
- statsLeftWidth = visibleWidth(statsLeft);
|
|
1229
|
+
+ let leftWidth = visibleWidth(left);
|
|
1230
|
+
+ // If the left side is too wide, truncate it
|
|
1231
|
+
+ if (leftWidth > width) {
|
|
1232
|
+
+ left = truncateToWidth(left, width, "...");
|
|
1233
|
+
+ leftWidth = visibleWidth(left);
|
|
1234
|
+
}
|
|
1235
|
+
- // Calculate available space for padding (minimum 2 spaces between stats and model)
|
|
1236
|
+
+ // Calculate available space for padding (minimum 2 spaces between cwd and model)
|
|
1237
|
+
const minPadding = 2;
|
|
1238
|
+
// Add thinking level indicator if model supports reasoning
|
|
1239
|
+
let rightSideWithoutProvider = modelName;
|
|
1240
|
+
@@ -171,50 +158,48 @@ export class FooterComponent {
|
|
1241
|
+
let rightSide = rightSideWithoutProvider;
|
|
1242
|
+
if (this.footerData.getAvailableProviderCount() > 1 && state.model) {
|
|
1243
|
+
rightSide = `(${state.model.provider}) ${rightSideWithoutProvider}`;
|
|
1244
|
+
- if (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {
|
|
1245
|
+
+ if (leftWidth + minPadding + visibleWidth(rightSide) > width) {
|
|
1246
|
+
// Too wide, fall back
|
|
1247
|
+
rightSide = rightSideWithoutProvider;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
const rightSideWidth = visibleWidth(rightSide);
|
|
1251
|
+
- const totalNeeded = statsLeftWidth + minPadding + rightSideWidth;
|
|
1252
|
+
- let statsLine;
|
|
1253
|
+
+ const totalNeeded = leftWidth + minPadding + rightSideWidth;
|
|
1254
|
+
+ let footerLine;
|
|
1255
|
+
if (totalNeeded <= width) {
|
|
1256
|
+
// Both fit - add padding to right-align model
|
|
1257
|
+
- const padding = " ".repeat(width - statsLeftWidth - rightSideWidth);
|
|
1258
|
+
- statsLine = statsLeft + padding + rightSide;
|
|
1259
|
+
+ const padding = " ".repeat(width - leftWidth - rightSideWidth);
|
|
1260
|
+
+ footerLine = left + padding + rightSide;
|
|
1261
|
+
}
|
|
1262
|
+
else {
|
|
1263
|
+
// Need to truncate right side
|
|
1264
|
+
- const availableForRight = width - statsLeftWidth - minPadding;
|
|
1265
|
+
+ const availableForRight = width - leftWidth - minPadding;
|
|
1266
|
+
if (availableForRight > 0) {
|
|
1267
|
+
const truncatedRight = truncateToWidth(rightSide, availableForRight, "");
|
|
1268
|
+
const truncatedRightWidth = visibleWidth(truncatedRight);
|
|
1269
|
+
- const padding = " ".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));
|
|
1270
|
+
- statsLine = statsLeft + padding + truncatedRight;
|
|
1271
|
+
+ const padding = " ".repeat(Math.max(0, width - leftWidth - truncatedRightWidth));
|
|
1272
|
+
+ footerLine = left + padding + truncatedRight;
|
|
1273
|
+
}
|
|
1274
|
+
else {
|
|
1275
|
+
// Not enough space for right side at all
|
|
1276
|
+
- statsLine = statsLeft;
|
|
1277
|
+
+ footerLine = left;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
- // Apply dim to each part separately. statsLeft may contain color codes (for context %)
|
|
1281
|
+
- // that end with a reset, which would clear an outer dim wrapper. So we dim the parts
|
|
1282
|
+
- // before and after the colored section independently.
|
|
1283
|
+
- const dimStatsLeft = theme.fg("dim", statsLeft);
|
|
1284
|
+
- const remainder = statsLine.slice(statsLeft.length); // padding + rightSide
|
|
1285
|
+
- const dimRemainder = theme.fg("dim", remainder);
|
|
1286
|
+
- const pwdLine = truncateToWidth(theme.fg("dim", pwd), width, theme.fg("dim", "..."));
|
|
1287
|
+
- const lines = [pwdLine, dimStatsLeft + dimRemainder];
|
|
1288
|
+
+ // Apply dim to each part separately. `left` may carry the bold/warning-coloured
|
|
1289
|
+
+ // xp flag, whose reset would clear an outer dim wrapper. So dim the parts before
|
|
1290
|
+
+ // and after the coloured section independently.
|
|
1291
|
+
+ const dimLeft = theme.fg("dim", left);
|
|
1292
|
+
+ const remainder = footerLine.slice(left.length); // padding + rightSide
|
|
1293
|
+
+ const lines = [dimLeft + theme.fg("dim", remainder)];
|
|
1294
|
+
// Add extension statuses on a single line, sorted by key alphabetically
|
|
1295
|
+
const extensionStatuses = this.footerData.getExtensionStatuses();
|
|
1296
|
+
if (extensionStatuses.size > 0) {
|
|
1110
1297
|
const sortedStatuses = Array.from(extensionStatuses.entries())
|
|
1111
1298
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
1112
1299
|
.map(([, text]) => sanitizeStatusText(text));
|
|
@@ -1119,6 +1306,97 @@ index 1d9f046..2a1fba8 100644
|
|
|
1119
1306
|
}
|
|
1120
1307
|
return lines;
|
|
1121
1308
|
}
|
|
1309
|
+
diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js
|
|
1310
|
+
index 3f93cc6..bbe2c10 100644
|
|
1311
|
+
--- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js
|
|
1312
|
+
+++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js
|
|
1313
|
+
@@ -1,8 +1,35 @@
|
|
1314
|
+
-import { Box, Container, getCapabilities, Image, Spacer, Text } from "@earendil-works/pi-tui";
|
|
1315
|
+
+import { Box, Container, getCapabilities, Image, Spacer, visibleWidth, Text } from "@earendil-works/pi-tui";
|
|
1316
|
+
import { createAllToolDefinitions } from "../../../core/tools/index.js";
|
|
1317
|
+
import { getTextOutput as getRenderedTextOutput } from "../../../core/tools/render-utils.js";
|
|
1318
|
+
import { convertToPng } from "../../../utils/image-convert.js";
|
|
1319
|
+
import { theme } from "../theme/theme.js";
|
|
1320
|
+
+/**
|
|
1321
|
+
+ * Privateer: mark a failed tool call inline on its title line.
|
|
1322
|
+
+ *
|
|
1323
|
+
+ * Upstream's ONLY failure signal is the full-width red `toolErrorBg` wash behind
|
|
1324
|
+
+ * the whole block. We flatten those washes (see dark.json / light.json) because
|
|
1325
|
+
+ * large blocks of colour read as noise — but flattening the error one alone would
|
|
1326
|
+
+ * make a failure indistinguishable from a success, which is a real regression.
|
|
1327
|
+
+ *
|
|
1328
|
+
+ * So we put the signal back where it costs nothing: a red glyph prefixed to the
|
|
1329
|
+
+ * first line of whatever the tool's own renderCall produced. Wrapping the CALL
|
|
1330
|
+
+ * component (not the Box) means Box still owns padding and width, and the marker
|
|
1331
|
+
+ * works for every tool without touching seven per-tool renderers. Subsequent
|
|
1332
|
+
+ * lines get a blank hanging indent so the title stays visually aligned.
|
|
1333
|
+
+ */
|
|
1334
|
+
+function markCallErrored(component, marker) {
|
|
1335
|
+
+ const markerWidth = visibleWidth(marker);
|
|
1336
|
+
+ const indent = " ".repeat(markerWidth);
|
|
1337
|
+
+ return {
|
|
1338
|
+
+ render(width) {
|
|
1339
|
+
+ const lines = component.render(Math.max(1, width - markerWidth));
|
|
1340
|
+
+ return lines.map((line, i) => (i === 0 ? marker + line : indent + line));
|
|
1341
|
+
+ },
|
|
1342
|
+
+ invalidate() {
|
|
1343
|
+
+ component.invalidate?.();
|
|
1344
|
+
+ },
|
|
1345
|
+
+ };
|
|
1346
|
+
+}
|
|
1347
|
+
export class ToolExecutionComponent extends Container {
|
|
1348
|
+
contentBox;
|
|
1349
|
+
contentText;
|
|
1350
|
+
@@ -106,6 +133,13 @@ export class ToolExecutionComponent extends Container {
|
|
1351
|
+
createCallFallback() {
|
|
1352
|
+
return new Text(theme.fg("toolTitle", theme.bold(this.toolName)), 0, 0);
|
|
1353
|
+
}
|
|
1354
|
+
+ /** Privateer: red-flag a failed call, pass a pending/succeeded one straight through. */
|
|
1355
|
+
+ decorateCall(component) {
|
|
1356
|
+
+ if (!this.result?.isError || this.isPartial) {
|
|
1357
|
+
+ return component;
|
|
1358
|
+
+ }
|
|
1359
|
+
+ return markCallErrored(component, theme.fg("error", "\u2717 "));
|
|
1360
|
+
+ }
|
|
1361
|
+
createResultFallback() {
|
|
1362
|
+
const output = this.getTextOutput();
|
|
1363
|
+
if (!output) {
|
|
1364
|
+
@@ -218,19 +252,22 @@ export class ToolExecutionComponent extends Container {
|
|
1365
|
+
renderContainer.clear();
|
|
1366
|
+
const callRenderer = this.getCallRenderer();
|
|
1367
|
+
if (!callRenderer) {
|
|
1368
|
+
- renderContainer.addChild(this.createCallFallback());
|
|
1369
|
+
+ renderContainer.addChild(this.decorateCall(this.createCallFallback()));
|
|
1370
|
+
hasContent = true;
|
|
1371
|
+
}
|
|
1372
|
+
else {
|
|
1373
|
+
try {
|
|
1374
|
+
const component = callRenderer(this.args, theme, this.getRenderContext(this.callRendererComponent));
|
|
1375
|
+
+ // Store the RAW component: it comes back as `lastComponent` on the
|
|
1376
|
+
+ // next render and the tool's renderer calls its own methods on it,
|
|
1377
|
+
+ // so handing it a wrapper would break every stateful renderer.
|
|
1378
|
+
this.callRendererComponent = component;
|
|
1379
|
+
- renderContainer.addChild(component);
|
|
1380
|
+
+ renderContainer.addChild(this.decorateCall(component));
|
|
1381
|
+
hasContent = true;
|
|
1382
|
+
}
|
|
1383
|
+
catch {
|
|
1384
|
+
this.callRendererComponent = undefined;
|
|
1385
|
+
- renderContainer.addChild(this.createCallFallback());
|
|
1386
|
+
+ renderContainer.addChild(this.decorateCall(this.createCallFallback()));
|
|
1387
|
+
hasContent = true;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
@@ -302,7 +339,8 @@ export class ToolExecutionComponent extends Container {
|
|
1391
|
+
return getRenderedTextOutput(this.result, this.showImages);
|
|
1392
|
+
}
|
|
1393
|
+
formatToolExecution() {
|
|
1394
|
+
- let text = theme.fg("toolTitle", theme.bold(this.toolName));
|
|
1395
|
+
+ const errorMark = this.result?.isError && !this.isPartial ? theme.fg("error", "\u2717 ") : "";
|
|
1396
|
+
+ let text = errorMark + theme.fg("toolTitle", theme.bold(this.toolName));
|
|
1397
|
+
const content = JSON.stringify(this.args, null, 2);
|
|
1398
|
+
if (content) {
|
|
1399
|
+
text += `\n\n${content}`;
|
|
1122
1400
|
diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js
|
|
1123
1401
|
index 42e655d..d00bec9 100644
|
|
1124
1402
|
--- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js
|
|
@@ -1269,6 +1547,40 @@ index 42e655d..d00bec9 100644
|
|
|
1269
1547
|
}
|
|
1270
1548
|
async getUserInput() {
|
|
1271
1549
|
const queuedInput = this.pendingUserInputs.shift();
|
|
1550
|
+
diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/dark.json b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/dark.json
|
|
1551
|
+
index 9db9cbd..b370180 100644
|
|
1552
|
+
--- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/dark.json
|
|
1553
|
+
+++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/dark.json
|
|
1554
|
+
@@ -39,9 +39,9 @@
|
|
1555
|
+
"customMessageBg": "customMsgBg",
|
|
1556
|
+
"customMessageText": "text",
|
|
1557
|
+
"customMessageLabel": "#9575cd",
|
|
1558
|
+
- "toolPendingBg": "toolPendingBg",
|
|
1559
|
+
- "toolSuccessBg": "toolSuccessBg",
|
|
1560
|
+
- "toolErrorBg": "toolErrorBg",
|
|
1561
|
+
+ "toolPendingBg": "",
|
|
1562
|
+
+ "toolSuccessBg": "",
|
|
1563
|
+
+ "toolErrorBg": "",
|
|
1564
|
+
"toolTitle": "text",
|
|
1565
|
+
"toolOutput": "gray",
|
|
1566
|
+
|
|
1567
|
+
diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/light.json b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/light.json
|
|
1568
|
+
index 74ef3d1..709a21d 100644
|
|
1569
|
+
--- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/light.json
|
|
1570
|
+
+++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/light.json
|
|
1571
|
+
@@ -38,9 +38,9 @@
|
|
1572
|
+
"customMessageBg": "customMsgBg",
|
|
1573
|
+
"customMessageText": "text",
|
|
1574
|
+
"customMessageLabel": "#7e57c2",
|
|
1575
|
+
- "toolPendingBg": "toolPendingBg",
|
|
1576
|
+
- "toolSuccessBg": "toolSuccessBg",
|
|
1577
|
+
- "toolErrorBg": "toolErrorBg",
|
|
1578
|
+
+ "toolPendingBg": "",
|
|
1579
|
+
+ "toolSuccessBg": "",
|
|
1580
|
+
+ "toolErrorBg": "",
|
|
1581
|
+
"toolTitle": "text",
|
|
1582
|
+
"toolOutput": "mediumGray",
|
|
1583
|
+
|
|
1272
1584
|
diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js b/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js
|
|
1273
1585
|
index 4230b18..f47e80f 100644
|
|
1274
1586
|
--- a/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js
|
package/src/engine/errors.ts
CHANGED
|
@@ -134,6 +134,13 @@ export function isAccountCapCode(code: string | null | undefined): boolean {
|
|
|
134
134
|
// patches/@earendil-works+pi-coding-agent+*.patch, which mirrors these two helpers):
|
|
135
135
|
// squeeze the page down to the line a person can act on, and let the STATUS decide
|
|
136
136
|
// retryability rather than a substring of the body.
|
|
137
|
+
//
|
|
138
|
+
// A first-attempt hard 4xx is also terminal for the post-run loop. Stock Pi still
|
|
139
|
+
// falls through to compaction when _retryAttempt is 0, and a session that already
|
|
140
|
+
// has usage will summarise — another LLM call to the same blocked endpoint. The
|
|
141
|
+
// summarizer retries on the raw HTML (it contains "500"/"502"), so the agent looks
|
|
142
|
+
// hung until that budget burns. The patch returns false from _handlePostAgentRun
|
|
143
|
+
// before _checkCompaction when isHardHttpFailure matches.
|
|
137
144
|
|
|
138
145
|
/** Hard cap on an error message we display, persist, or classify. */
|
|
139
146
|
export const MAX_ERROR_CHARS = 2_000;
|