@averyyy/pi-coding-agent 0.80.3-piclient.3 → 0.80.3-piclient.6
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 +1 -0
- package/dist/core/agent-session.d.ts +12 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +225 -18
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +4 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +27 -2
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/pi-server-client.d.ts +2 -1
- package/dist/core/pi-server-client.d.ts.map +1 -1
- package/dist/core/pi-server-client.js +1 -0
- package/dist/core/pi-server-client.js.map +1 -1
- package/dist/core/pi-server-request.d.ts.map +1 -1
- package/dist/core/pi-server-request.js +13 -107
- package/dist/core/pi-server-request.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +1 -0
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/pi-client-cli.js +0 -0
- package/package.json +8 -5
- package/npm-shrinkwrap.json +0 -1824
|
@@ -62,12 +62,36 @@ function estimateMessagesTokens(messages) {
|
|
|
62
62
|
// ============================================================================
|
|
63
63
|
/** Standard thinking levels */
|
|
64
64
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
|
|
65
|
+
const INTRA_TURN_COMPACTION_MARKER_TYPE = "pi:intra-turn-compaction";
|
|
66
|
+
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.";
|
|
67
|
+
const VALIDATION_HINT_MESSAGE_TYPE = "pi:validation-hint";
|
|
68
|
+
const VALIDATION_HINT_FILE_LIMIT = 12;
|
|
69
|
+
const VALIDATION_HINT_FAILURE_CHAR_LIMIT = 1200;
|
|
65
70
|
function isPiServerMode() {
|
|
66
71
|
return process.env.PI_SERVER_MODE === "true";
|
|
67
72
|
}
|
|
68
73
|
function toProviderReasoning(thinkingLevel) {
|
|
69
74
|
return thinkingLevel === "off" ? undefined : thinkingLevel;
|
|
70
75
|
}
|
|
76
|
+
function getStringField(input, key) {
|
|
77
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
78
|
+
return undefined;
|
|
79
|
+
const value = input[key];
|
|
80
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
81
|
+
}
|
|
82
|
+
function toolTextContent(content) {
|
|
83
|
+
return content
|
|
84
|
+
.filter((part) => part.type === "text")
|
|
85
|
+
.map((part) => part.text)
|
|
86
|
+
.join("\n")
|
|
87
|
+
.trim();
|
|
88
|
+
}
|
|
89
|
+
function compactValidationSummary(text) {
|
|
90
|
+
const trimmed = text.trim();
|
|
91
|
+
if (trimmed.length <= VALIDATION_HINT_FAILURE_CHAR_LIMIT)
|
|
92
|
+
return trimmed;
|
|
93
|
+
return `...${trimmed.slice(trimmed.length - VALIDATION_HINT_FAILURE_CHAR_LIMIT + 3)}`;
|
|
94
|
+
}
|
|
71
95
|
// ============================================================================
|
|
72
96
|
// AgentSession Class
|
|
73
97
|
// ============================================================================
|
|
@@ -98,6 +122,8 @@ export class AgentSession {
|
|
|
98
122
|
// Bash execution state
|
|
99
123
|
_bashAbortController = undefined;
|
|
100
124
|
_pendingBashMessages = [];
|
|
125
|
+
_validationModifiedFiles = new Set();
|
|
126
|
+
_validationFailure = undefined;
|
|
101
127
|
// Extension system
|
|
102
128
|
_extensionRunner;
|
|
103
129
|
_turnIndex = 0;
|
|
@@ -197,6 +223,71 @@ export class AgentSession {
|
|
|
197
223
|
const result = await this._modelRegistry.getApiKeyAndHeaders(model);
|
|
198
224
|
return result.ok ? { apiKey: result.apiKey, headers: result.headers, env: result.env } : {};
|
|
199
225
|
}
|
|
226
|
+
_resetValidationHintState() {
|
|
227
|
+
this._validationModifiedFiles.clear();
|
|
228
|
+
this._validationFailure = undefined;
|
|
229
|
+
}
|
|
230
|
+
_recordValidationToolResult(toolName, input, content, isError) {
|
|
231
|
+
if ((toolName === "edit" || toolName === "write") && !isError) {
|
|
232
|
+
const path = getStringField(input, "path") ?? getStringField(input, "file_path");
|
|
233
|
+
if (path) {
|
|
234
|
+
this._validationModifiedFiles.add(path);
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (toolName !== "bash") {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const command = getStringField(input, "command");
|
|
242
|
+
if (!command) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (isError) {
|
|
246
|
+
this._validationFailure = {
|
|
247
|
+
command,
|
|
248
|
+
summary: compactValidationSummary(toolTextContent(content)),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
else if (this._validationFailure?.command === command) {
|
|
252
|
+
this._resetValidationHintState();
|
|
253
|
+
}
|
|
254
|
+
else if (!this._validationFailure) {
|
|
255
|
+
this._validationModifiedFiles.clear();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
_createValidationHintMessage() {
|
|
259
|
+
const modifiedFiles = Array.from(this._validationModifiedFiles).slice(-VALIDATION_HINT_FILE_LIMIT);
|
|
260
|
+
if (modifiedFiles.length === 0 && !this._validationFailure) {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
const lines = ["<validation-hint>"];
|
|
264
|
+
if (modifiedFiles.length > 0) {
|
|
265
|
+
lines.push("Recent modified files:", ...modifiedFiles.map((file) => `- ${file}`));
|
|
266
|
+
}
|
|
267
|
+
if (this._validationFailure) {
|
|
268
|
+
lines.push("Recent failed command:", this._validationFailure.command, "Failure summary:", this._validationFailure.summary || "(no output)", "Suggested next validation command:", this._validationFailure.command);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
lines.push("Suggested next step:", "Run the smallest project validation that covers the modified files.");
|
|
272
|
+
}
|
|
273
|
+
lines.push("Inspect failures, fix the root cause, then rerun validation before finalizing.", "</validation-hint>");
|
|
274
|
+
return {
|
|
275
|
+
role: "custom",
|
|
276
|
+
customType: VALIDATION_HINT_MESSAGE_TYPE,
|
|
277
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
278
|
+
display: false,
|
|
279
|
+
details: { modifiedFiles, failedCommand: this._validationFailure?.command },
|
|
280
|
+
timestamp: Date.now(),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
_withValidationHint(context) {
|
|
284
|
+
const messages = context.messages.filter((message) => !(message.role === "custom" && message.customType === VALIDATION_HINT_MESSAGE_TYPE));
|
|
285
|
+
const hint = this._createValidationHintMessage();
|
|
286
|
+
return {
|
|
287
|
+
...context,
|
|
288
|
+
messages: hint ? [...messages, hint] : messages,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
200
291
|
/**
|
|
201
292
|
* Install tool hooks once on the Agent instance.
|
|
202
293
|
*
|
|
@@ -228,18 +319,20 @@ export class AgentSession {
|
|
|
228
319
|
};
|
|
229
320
|
this.agent.afterToolCall = async ({ toolCall, args, result, isError }) => {
|
|
230
321
|
const runner = this._extensionRunner;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
322
|
+
const hookResult = runner.hasHandlers("tool_result")
|
|
323
|
+
? await runner.emitToolResult({
|
|
324
|
+
type: "tool_result",
|
|
325
|
+
toolName: toolCall.name,
|
|
326
|
+
toolCallId: toolCall.id,
|
|
327
|
+
input: args,
|
|
328
|
+
content: result.content,
|
|
329
|
+
details: result.details,
|
|
330
|
+
isError,
|
|
331
|
+
})
|
|
332
|
+
: undefined;
|
|
333
|
+
const finalContent = hookResult?.content ?? result.content;
|
|
334
|
+
const finalIsError = hookResult?.isError ?? isError;
|
|
335
|
+
this._recordValidationToolResult(toolCall.name, args, finalContent, finalIsError);
|
|
243
336
|
if (!hookResult) {
|
|
244
337
|
return undefined;
|
|
245
338
|
}
|
|
@@ -256,15 +349,17 @@ export class AgentSession {
|
|
|
256
349
|
? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal)
|
|
257
350
|
: undefined);
|
|
258
351
|
this.agent.prepareNextTurnWithContext = async (turn, signal) => {
|
|
259
|
-
const
|
|
260
|
-
const
|
|
352
|
+
const compactedContext = await this._compactBeforeNextProviderRequest(turn, signal);
|
|
353
|
+
const nextTurn = compactedContext ? { ...turn, context: compactedContext } : turn;
|
|
354
|
+
const previousSnapshot = await previousPrepareNextTurnWithContext?.(nextTurn, signal);
|
|
355
|
+
const previousContext = previousSnapshot?.context ?? nextTurn.context;
|
|
261
356
|
return {
|
|
262
357
|
...previousSnapshot,
|
|
263
|
-
context: {
|
|
358
|
+
context: this._withValidationHint({
|
|
264
359
|
...previousContext,
|
|
265
360
|
systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
|
|
266
361
|
tools: this.agent.state.tools.slice(),
|
|
267
|
-
},
|
|
362
|
+
}),
|
|
268
363
|
model: this.agent.state.model,
|
|
269
364
|
thinkingLevel: this.agent.state.thinkingLevel,
|
|
270
365
|
};
|
|
@@ -394,6 +489,20 @@ export class AgentSession {
|
|
|
394
489
|
_isTerminalAssistantFailure(message) {
|
|
395
490
|
return message.stopReason === "error" || message.stopReason === "aborted" || message.stopReason === "length";
|
|
396
491
|
}
|
|
492
|
+
_isPostAssistantPiServerSyncFailure(message) {
|
|
493
|
+
if (!isPiServerMode() || !message.errorMessage) {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
if (!/^(Session init|Session tree (append|sync|switch)|Session history reconciliation) failed/.test(message.errorMessage)) {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
const messages = this.agent.state.messages;
|
|
500
|
+
if (messages[messages.length - 1] !== message) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
const previous = messages[messages.length - 2];
|
|
504
|
+
return previous?.role === "assistant" && !this._isTerminalAssistantFailure(previous);
|
|
505
|
+
}
|
|
397
506
|
_detachTerminalAssistantFailure() {
|
|
398
507
|
const branch = this.sessionManager.getBranch();
|
|
399
508
|
let terminalMessageEntry;
|
|
@@ -423,6 +532,16 @@ export class AgentSession {
|
|
|
423
532
|
_detachPiServerTerminalAssistantFailure() {
|
|
424
533
|
this._detachTerminalAssistantFailure();
|
|
425
534
|
}
|
|
535
|
+
_restoreSessionLeaf(leafId) {
|
|
536
|
+
if (leafId) {
|
|
537
|
+
this.sessionManager.branch(leafId);
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
this.sessionManager.resetLeaf();
|
|
541
|
+
}
|
|
542
|
+
const sessionContext = this.sessionManager.buildSessionContext();
|
|
543
|
+
this.agent.state.messages = sessionContext.messages;
|
|
544
|
+
}
|
|
426
545
|
_replaceMessageInPlace(target, replacement) {
|
|
427
546
|
// Agent-core stores the finalized message object in its state before emitting message_end.
|
|
428
547
|
// SessionManager persistence happens later in _handleAgentEvent() with event.message.
|
|
@@ -743,10 +862,26 @@ export class AgentSession {
|
|
|
743
862
|
// Prompting
|
|
744
863
|
// =========================================================================
|
|
745
864
|
async _runAgentPrompt(messages) {
|
|
865
|
+
this._resetValidationHintState();
|
|
746
866
|
try {
|
|
747
867
|
await this.agent.prompt(messages);
|
|
748
868
|
while (await this._handlePostAgentRun()) {
|
|
749
|
-
|
|
869
|
+
try {
|
|
870
|
+
await this.agent.continue();
|
|
871
|
+
}
|
|
872
|
+
catch (error) {
|
|
873
|
+
if (this._retryAttempt > 0) {
|
|
874
|
+
const attempt = this._retryAttempt;
|
|
875
|
+
this._retryAttempt = 0;
|
|
876
|
+
this._emit({
|
|
877
|
+
type: "auto_retry_end",
|
|
878
|
+
success: false,
|
|
879
|
+
attempt,
|
|
880
|
+
finalError: error instanceof Error ? error.message : String(error),
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
throw error;
|
|
884
|
+
}
|
|
750
885
|
}
|
|
751
886
|
}
|
|
752
887
|
finally {
|
|
@@ -1636,7 +1771,7 @@ export class AgentSession {
|
|
|
1636
1771
|
/**
|
|
1637
1772
|
* Internal: Run auto-compaction with events.
|
|
1638
1773
|
*/
|
|
1639
|
-
async _runAutoCompaction(reason, willRetry, preparationOverride) {
|
|
1774
|
+
async _runAutoCompaction(reason, willRetry, preparationOverride, preparationOptions) {
|
|
1640
1775
|
const settings = this.settingsManager.getCompactionSettings();
|
|
1641
1776
|
let started = false;
|
|
1642
1777
|
try {
|
|
@@ -1672,6 +1807,7 @@ export class AgentSession {
|
|
|
1672
1807
|
apiKey,
|
|
1673
1808
|
headers,
|
|
1674
1809
|
settings,
|
|
1810
|
+
preparation: preparationOptions,
|
|
1675
1811
|
sessionTree: { entries: this.sessionManager.getEntries(), leafId: this.sessionManager.getLeafId() },
|
|
1676
1812
|
signal,
|
|
1677
1813
|
reasoning: toProviderReasoning(this.thinkingLevel),
|
|
@@ -1812,6 +1948,63 @@ export class AgentSession {
|
|
|
1812
1948
|
get autoCompactionEnabled() {
|
|
1813
1949
|
return this.settingsManager.getCompactionEnabled();
|
|
1814
1950
|
}
|
|
1951
|
+
_shouldCompactBeforeNextProviderRequest(turn) {
|
|
1952
|
+
const settings = this.settingsManager.getCompactionSettings();
|
|
1953
|
+
if (!settings.enabled || turn.toolResults.length === 0)
|
|
1954
|
+
return false;
|
|
1955
|
+
const contextWindow = this.model?.contextWindow ?? 0;
|
|
1956
|
+
if (contextWindow <= 0)
|
|
1957
|
+
return false;
|
|
1958
|
+
const projectedTokens = estimateContextTokens(turn.context.messages).tokens;
|
|
1959
|
+
if (projectedTokens >= contextWindow - settings.reserveTokens)
|
|
1960
|
+
return true;
|
|
1961
|
+
const toolResultTokens = estimateMessagesTokens(turn.toolResults);
|
|
1962
|
+
return toolResultTokens >= settings.keepRecentTokens;
|
|
1963
|
+
}
|
|
1964
|
+
_appendIntraTurnCompactionMarker() {
|
|
1965
|
+
const marker = {
|
|
1966
|
+
role: "custom",
|
|
1967
|
+
customType: INTRA_TURN_COMPACTION_MARKER_TYPE,
|
|
1968
|
+
content: [{ type: "text", text: INTRA_TURN_COMPACTION_MARKER_TEXT }],
|
|
1969
|
+
display: false,
|
|
1970
|
+
details: { reason: "intra-turn-compaction" },
|
|
1971
|
+
timestamp: Date.now(),
|
|
1972
|
+
};
|
|
1973
|
+
const markerId = this.sessionManager.appendCustomMessageEntry(marker.customType, marker.content, marker.display, marker.details);
|
|
1974
|
+
this.agent.state.messages = [...this.agent.state.messages, marker];
|
|
1975
|
+
return markerId;
|
|
1976
|
+
}
|
|
1977
|
+
_currentAgentContext(baseContext) {
|
|
1978
|
+
return {
|
|
1979
|
+
...baseContext,
|
|
1980
|
+
messages: this.agent.state.messages.slice(),
|
|
1981
|
+
systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
|
|
1982
|
+
tools: this.agent.state.tools.slice(),
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
async _compactBeforeNextProviderRequest(turn, signal) {
|
|
1986
|
+
if (signal?.aborted || !this._shouldCompactBeforeNextProviderRequest(turn)) {
|
|
1987
|
+
return undefined;
|
|
1988
|
+
}
|
|
1989
|
+
const settings = this.settingsManager.getCompactionSettings();
|
|
1990
|
+
const markerParentId = this.sessionManager.getLeafId();
|
|
1991
|
+
const markerId = this._appendIntraTurnCompactionMarker();
|
|
1992
|
+
const preparationOptions = { firstKeptEntryId: markerId };
|
|
1993
|
+
const branchWithMarker = this.sessionManager.getBranch();
|
|
1994
|
+
const previousCompactionId = getLatestCompactionEntry(branchWithMarker)?.id;
|
|
1995
|
+
const preparation = prepareCompaction(branchWithMarker, settings, preparationOptions);
|
|
1996
|
+
if (!preparation) {
|
|
1997
|
+
this._restoreSessionLeaf(markerParentId);
|
|
1998
|
+
return undefined;
|
|
1999
|
+
}
|
|
2000
|
+
await this._runAutoCompaction("threshold", false, preparation, preparationOptions);
|
|
2001
|
+
const currentCompactionId = getLatestCompactionEntry(this.sessionManager.getBranch())?.id;
|
|
2002
|
+
if (currentCompactionId === previousCompactionId) {
|
|
2003
|
+
this._restoreSessionLeaf(markerParentId);
|
|
2004
|
+
return undefined;
|
|
2005
|
+
}
|
|
2006
|
+
return this._currentAgentContext(turn.context);
|
|
2007
|
+
}
|
|
1815
2008
|
async bindExtensions(bindings) {
|
|
1816
2009
|
if (bindings.uiContext !== undefined) {
|
|
1817
2010
|
this._extensionUIContext = bindings.uiContext;
|
|
@@ -2150,6 +2343,8 @@ export class AgentSession {
|
|
|
2150
2343
|
// Context overflow is handled by compaction, not retry.
|
|
2151
2344
|
if (isContextOverflow(message, this.model?.contextWindow ?? 0))
|
|
2152
2345
|
return false;
|
|
2346
|
+
if (this._isPostAssistantPiServerSyncFailure(message))
|
|
2347
|
+
return false;
|
|
2153
2348
|
if (message.errorMessage && this._isNonRetryableProviderLimitError(message.errorMessage))
|
|
2154
2349
|
return false;
|
|
2155
2350
|
return isRetryableAssistantError(message);
|
|
@@ -2187,6 +2382,18 @@ export class AgentSession {
|
|
|
2187
2382
|
this.agent.state.messages = messages.slice(0, -1);
|
|
2188
2383
|
}
|
|
2189
2384
|
}
|
|
2385
|
+
const lastMessage = this.agent.state.messages[this.agent.state.messages.length - 1];
|
|
2386
|
+
if (!lastMessage || lastMessage.role === "assistant") {
|
|
2387
|
+
const attempt = this._retryAttempt;
|
|
2388
|
+
this._retryAttempt = 0;
|
|
2389
|
+
this._emit({
|
|
2390
|
+
type: "auto_retry_end",
|
|
2391
|
+
success: false,
|
|
2392
|
+
attempt,
|
|
2393
|
+
finalError: lastMessage ? "Cannot continue from message role: assistant" : "No messages to continue from",
|
|
2394
|
+
});
|
|
2395
|
+
return false;
|
|
2396
|
+
}
|
|
2190
2397
|
// Wait with exponential backoff (abortable)
|
|
2191
2398
|
this._retryAbortController = new AbortController();
|
|
2192
2399
|
try {
|