@averyyy/pi-coding-agent 0.82.1-piclient.3 → 0.82.1-piclient.5
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 +12 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/tool-effects.d.ts +6 -0
- package/dist/cli/tool-effects.d.ts.map +1 -0
- package/dist/cli/tool-effects.js +164 -0
- package/dist/cli/tool-effects.js.map +1 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +5 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +12 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +314 -89
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/pi-server-client.d.ts +53 -2
- package/dist/core/pi-server-client.d.ts.map +1 -1
- package/dist/core/pi-server-client.js +2082 -290
- package/dist/core/pi-server-client.js.map +1 -1
- package/dist/core/pi-server-compact-state.d.ts +81 -0
- package/dist/core/pi-server-compact-state.d.ts.map +1 -0
- package/dist/core/pi-server-compact-state.js +438 -0
- package/dist/core/pi-server-compact-state.js.map +1 -0
- package/dist/core/pi-server-protocol.d.ts +2 -0
- package/dist/core/pi-server-protocol.d.ts.map +1 -1
- package/dist/core/pi-server-protocol.js +24 -1
- package/dist/core/pi-server-protocol.js.map +1 -1
- package/dist/core/pi-server-request.d.ts +28 -0
- package/dist/core/pi-server-request.d.ts.map +1 -1
- package/dist/core/pi-server-request.js +492 -75
- package/dist/core/pi-server-request.js.map +1 -1
- package/dist/core/pi-server-run-state.d.ts +41 -0
- package/dist/core/pi-server-run-state.d.ts.map +1 -0
- package/dist/core/pi-server-run-state.js +318 -0
- package/dist/core/pi-server-run-state.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +69 -53
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +86 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +737 -144
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/tool-effect-journal.d.ts +103 -0
- package/dist/core/tool-effect-journal.d.ts.map +1 -0
- package/dist/core/tool-effect-journal.js +1442 -0
- package/dist/core/tool-effect-journal.js.map +1 -0
- package/dist/core/tools/output-accumulator.d.ts +3 -1
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +46 -26
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +13 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +15 -0
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +98 -29
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/package.json +4 -4
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Modes use this class and add their own I/O layer on top.
|
|
14
14
|
*/
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
15
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
17
|
import { basename, dirname } from "node:path";
|
|
17
18
|
import { contentText } from "@earendil-works/pi-ai";
|
|
@@ -29,11 +30,14 @@ import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
|
|
|
29
30
|
import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
|
|
30
31
|
import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
31
32
|
import { ModelRegistry } from "./model-registry.js";
|
|
32
|
-
import { compactPiServer, syncPiServerTree, } from "./pi-server-client.js";
|
|
33
|
+
import { acknowledgePiServerCompaction, acknowledgePiServerRunMessage, acquirePiServerSessionLease, compactPiServer, getPiServerRunDiagnostic, recordPiServerCompactionApplied, recoverPendingPiServerCompaction, releasePiServerRunLease, releasePiServerSessionLease, syncPiServerTree, } from "./pi-server-client.js";
|
|
34
|
+
import { getPiServerCompactStatePath, readPiServerPendingCompact } from "./pi-server-compact-state.js";
|
|
35
|
+
import { getPiServerRunStatePath, readPiServerPendingRun } from "./pi-server-run-state.js";
|
|
33
36
|
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
34
37
|
import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
|
|
35
38
|
import { createSyntheticSourceInfo } from "./source-info.js";
|
|
36
39
|
import { buildSystemPrompt } from "./system-prompt.js";
|
|
40
|
+
import { beginSessionToolEffect, beginSessionToolEffectFinalization, commitSessionToolEffect, recoverToolEffects, writeSessionToolEffectFinalResult, writeSessionToolEffectResult, } from "./tool-effect-journal.js";
|
|
37
41
|
import { createLocalBashOperations } from "./tools/bash.js";
|
|
38
42
|
import { createAllToolDefinitions } from "./tools/index.js";
|
|
39
43
|
import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
|
|
@@ -93,16 +97,26 @@ function getStringField(input, key) {
|
|
|
93
97
|
const value = input[key];
|
|
94
98
|
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
95
99
|
}
|
|
96
|
-
function
|
|
100
|
+
function getPiServerFailureMetadata(message) {
|
|
97
101
|
for (const diagnostic of message.diagnostics ?? []) {
|
|
98
102
|
if (diagnostic.type !== "pi_server_failure")
|
|
99
103
|
continue;
|
|
100
104
|
const phase = diagnostic.details?.phase;
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
const retryable = diagnostic.details?.retryable;
|
|
106
|
+
return {
|
|
107
|
+
phase: typeof phase === "string" ? phase : undefined,
|
|
108
|
+
retryable: typeof retryable === "boolean" ? retryable : undefined,
|
|
109
|
+
};
|
|
103
110
|
}
|
|
104
111
|
return undefined;
|
|
105
112
|
}
|
|
113
|
+
function isPiServerControlPlaneFailure(message) {
|
|
114
|
+
const hasFailure = message.diagnostics?.some((diagnostic) => diagnostic.type === "pi_server_failure") === true;
|
|
115
|
+
return hasFailure && getPiServerRunDiagnostic(message) === undefined;
|
|
116
|
+
}
|
|
117
|
+
function isPiServerRestartUncertainty(message) {
|
|
118
|
+
return getPiServerRunDiagnostic(message)?.restartUnknown === true;
|
|
119
|
+
}
|
|
106
120
|
function toolTextContent(content) {
|
|
107
121
|
return content
|
|
108
122
|
.filter((part) => part.type === "text")
|
|
@@ -165,6 +179,7 @@ export class AgentSession {
|
|
|
165
179
|
_isAgentRunActive = false;
|
|
166
180
|
_idleWaitPromise;
|
|
167
181
|
_resolveIdleWait;
|
|
182
|
+
_toolResultsInFinalization = new WeakSet();
|
|
168
183
|
/** Tracks pending steering messages for UI display. Removed when delivered. */
|
|
169
184
|
_steeringMessages = [];
|
|
170
185
|
/** Tracks pending follow-up messages for UI display. Removed when delivered. */
|
|
@@ -176,6 +191,7 @@ export class AgentSession {
|
|
|
176
191
|
_autoCompactionAbortController = undefined;
|
|
177
192
|
_overflowRecoveryAttempted = false;
|
|
178
193
|
_lastServerCompactionTimestamp = undefined;
|
|
194
|
+
_piServerSessionLeaseOwner;
|
|
179
195
|
// Branch summarization state
|
|
180
196
|
_branchSummaryAbortController = undefined;
|
|
181
197
|
// Retry state
|
|
@@ -208,6 +224,7 @@ export class AgentSession {
|
|
|
208
224
|
_extensionErrorUnsubscriber;
|
|
209
225
|
_autoSessionNameEnabled;
|
|
210
226
|
_autoSessionNameAttempted = false;
|
|
227
|
+
_auxiliaryStreamFunction;
|
|
211
228
|
_modelRuntime;
|
|
212
229
|
// Tool registry for extension getTools/setTools
|
|
213
230
|
_toolRegistry = new Map();
|
|
@@ -227,6 +244,7 @@ export class AgentSession {
|
|
|
227
244
|
this._customTools = config.customTools ?? [];
|
|
228
245
|
this._cwd = config.cwd;
|
|
229
246
|
this._modelRuntime = config.modelRuntime;
|
|
247
|
+
this._auxiliaryStreamFunction = config.auxiliaryStreamFunction;
|
|
230
248
|
this._extensionRunnerRef = config.extensionRunnerRef;
|
|
231
249
|
this._initialActiveToolNames = config.initialActiveToolNames;
|
|
232
250
|
this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
|
|
@@ -234,6 +252,10 @@ export class AgentSession {
|
|
|
234
252
|
this._baseToolsOverride = config.baseToolsOverride;
|
|
235
253
|
this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
|
|
236
254
|
this._autoSessionNameEnabled = config.autoSessionName ?? true;
|
|
255
|
+
const toolEffectRecovery = recoverToolEffects(this.sessionManager);
|
|
256
|
+
if (toolEffectRecovery.recoveredToolCallIds.length > 0) {
|
|
257
|
+
this.agent.state.messages = this.sessionManager.buildSessionContext().messages;
|
|
258
|
+
}
|
|
237
259
|
if (isPiServerMode()) {
|
|
238
260
|
this._detachPiServerTerminalAssistantFailure();
|
|
239
261
|
}
|
|
@@ -250,6 +272,9 @@ export class AgentSession {
|
|
|
250
272
|
get modelRuntime() {
|
|
251
273
|
return this._modelRuntime;
|
|
252
274
|
}
|
|
275
|
+
_getAuxiliaryStreamFunction() {
|
|
276
|
+
return this._auxiliaryStreamFunction ?? this.agent.streamFunction;
|
|
277
|
+
}
|
|
253
278
|
reconcilePiServerHistory(snapshot) {
|
|
254
279
|
this.sessionManager.replaceTree(snapshot.entries, snapshot.leafId);
|
|
255
280
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
@@ -260,6 +285,102 @@ export class AgentSession {
|
|
|
260
285
|
messageCount: snapshot.messages.length,
|
|
261
286
|
});
|
|
262
287
|
}
|
|
288
|
+
async _syncAndAcknowledgePiServerAssistantMessage(message, signal) {
|
|
289
|
+
const persistenceSignal = signal.aborted ? undefined : signal;
|
|
290
|
+
const sessionFile = this.sessionManager.getSessionFile();
|
|
291
|
+
const runStatePath = sessionFile ? getPiServerRunStatePath(sessionFile) : undefined;
|
|
292
|
+
try {
|
|
293
|
+
await syncPiServerTree(this.sessionId, {
|
|
294
|
+
systemPrompt: this.systemPrompt,
|
|
295
|
+
messages: this.agent.state.messages,
|
|
296
|
+
tools: this.agent.state.tools,
|
|
297
|
+
}, { entries: this.sessionManager.getEntries(), leafId: this.sessionManager.getLeafId() }, {
|
|
298
|
+
signal: persistenceSignal,
|
|
299
|
+
onHistoryReconciled: (snapshot) => this.reconcilePiServerHistory(snapshot),
|
|
300
|
+
});
|
|
301
|
+
this.sessionManager.flushSessionFile();
|
|
302
|
+
await acknowledgePiServerRunMessage(this.sessionId, message, runStatePath, persistenceSignal);
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
releasePiServerRunLease(this.sessionId, runStatePath);
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
_acquirePiServerOperationLease(reentrantOwner) {
|
|
310
|
+
if (!isPiServerMode())
|
|
311
|
+
return { path: undefined, acquired: false, owner: undefined };
|
|
312
|
+
const sessionFile = this.sessionManager.getSessionFile();
|
|
313
|
+
const path = sessionFile ? getPiServerRunStatePath(sessionFile) : undefined;
|
|
314
|
+
if (this._piServerSessionLeaseOwner) {
|
|
315
|
+
if (reentrantOwner !== this._piServerSessionLeaseOwner) {
|
|
316
|
+
throw new Error(`Cannot start a concurrent durable pi-server operation for session ${this.sessionId}`);
|
|
317
|
+
}
|
|
318
|
+
return { path, acquired: false, owner: this._piServerSessionLeaseOwner };
|
|
319
|
+
}
|
|
320
|
+
const owner = Symbol("pi-server-session-lease");
|
|
321
|
+
if (path)
|
|
322
|
+
acquirePiServerSessionLease(this.sessionId, path);
|
|
323
|
+
this._piServerSessionLeaseOwner = owner;
|
|
324
|
+
return { path, acquired: true, owner };
|
|
325
|
+
}
|
|
326
|
+
_releasePiServerOperationLease(path, acquired, owner) {
|
|
327
|
+
if (!acquired)
|
|
328
|
+
return;
|
|
329
|
+
if (!owner || this._piServerSessionLeaseOwner !== owner) {
|
|
330
|
+
throw new Error(`Cannot release a durable pi-server operation lease owned by another operation`);
|
|
331
|
+
}
|
|
332
|
+
if (path)
|
|
333
|
+
releasePiServerSessionLease(this.sessionId, path);
|
|
334
|
+
this._piServerSessionLeaseOwner = undefined;
|
|
335
|
+
}
|
|
336
|
+
async _recoverPendingPiServerRunBeforeControlOperation(signal) {
|
|
337
|
+
if (!isPiServerMode())
|
|
338
|
+
return;
|
|
339
|
+
const sessionFile = this.sessionManager.getSessionFile();
|
|
340
|
+
if (!sessionFile) {
|
|
341
|
+
throw new Error("Durable pi-server operations require a persisted session file");
|
|
342
|
+
}
|
|
343
|
+
const runStatePath = getPiServerRunStatePath(sessionFile);
|
|
344
|
+
const pending = readPiServerPendingRun(runStatePath);
|
|
345
|
+
if (!pending)
|
|
346
|
+
return;
|
|
347
|
+
let terminal;
|
|
348
|
+
const entries = this.sessionManager.getEntries();
|
|
349
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
350
|
+
const entry = entries[index];
|
|
351
|
+
if (entry.type !== "message" || entry.message.role !== "assistant")
|
|
352
|
+
continue;
|
|
353
|
+
const message = entry.message;
|
|
354
|
+
const diagnostic = getPiServerRunDiagnostic(message);
|
|
355
|
+
if (!diagnostic || diagnostic.runId !== pending.runId)
|
|
356
|
+
continue;
|
|
357
|
+
if (diagnostic.sessionId !== pending.sessionId ||
|
|
358
|
+
diagnostic.requestMac !== pending.requestHash ||
|
|
359
|
+
pending.sessionId !== this.sessionId) {
|
|
360
|
+
throw new Error("Pending pi-server run terminal does not match its durable client marker");
|
|
361
|
+
}
|
|
362
|
+
terminal = message;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
if (!terminal) {
|
|
366
|
+
throw new Error(`Cannot start compaction while pi-server run ${pending.runId} is pending without a durable terminal assistant`);
|
|
367
|
+
}
|
|
368
|
+
await this._syncAndAcknowledgePiServerAssistantMessage(terminal, signal ?? new AbortController().signal);
|
|
369
|
+
}
|
|
370
|
+
async _recoverPendingPiServerCompaction(reason, willRetry, signal) {
|
|
371
|
+
if (!isPiServerMode())
|
|
372
|
+
return undefined;
|
|
373
|
+
const sessionFile = this.sessionManager.getSessionFile();
|
|
374
|
+
if (!sessionFile)
|
|
375
|
+
return undefined;
|
|
376
|
+
const compactStatePath = getPiServerCompactStatePath(sessionFile);
|
|
377
|
+
if (!readPiServerPendingCompact(compactStatePath))
|
|
378
|
+
return undefined;
|
|
379
|
+
const result = await recoverPendingPiServerCompaction(this.sessionId, { entries: this.sessionManager.getEntries(), leafId: this.sessionManager.getLeafId() }, compactStatePath, { signal });
|
|
380
|
+
if (!result)
|
|
381
|
+
return undefined;
|
|
382
|
+
return this._applyPiServerCompactionResult(result, reason, willRetry);
|
|
383
|
+
}
|
|
263
384
|
async _getRequiredRequestAuth(model) {
|
|
264
385
|
let result;
|
|
265
386
|
try {
|
|
@@ -288,7 +409,7 @@ export class AgentSession {
|
|
|
288
409
|
throw new Error(formatNoApiKeyFoundMessage(model.provider));
|
|
289
410
|
}
|
|
290
411
|
async _getSummarizationRequestAuth(model) {
|
|
291
|
-
if (this.
|
|
412
|
+
if (this._getAuxiliaryStreamFunction() === streamSimple) {
|
|
292
413
|
return this._getRequiredRequestAuth(model);
|
|
293
414
|
}
|
|
294
415
|
try {
|
|
@@ -480,18 +601,33 @@ export class AgentSession {
|
|
|
480
601
|
}
|
|
481
602
|
async _emitAgentSettled() {
|
|
482
603
|
this._isAgentRunActive = false;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
this._emit({ type: "agent_settled" });
|
|
486
|
-
}
|
|
487
|
-
finally {
|
|
488
|
-
this._resolveIdleWaitIfIdle();
|
|
489
|
-
}
|
|
604
|
+
await this._extensionRunner.emit({ type: "agent_settled" });
|
|
605
|
+
this._emit({ type: "agent_settled" });
|
|
490
606
|
}
|
|
491
607
|
// Track last assistant message for auto-compaction check
|
|
492
608
|
_lastAssistantMessage = undefined;
|
|
493
609
|
/** Internal handler for agent events - shared by subscribe and reconnect */
|
|
494
610
|
_handleAgentEvent = async (event, signal) => {
|
|
611
|
+
if (event.type === "tool_execution_start") {
|
|
612
|
+
beginSessionToolEffect(this.sessionManager, event);
|
|
613
|
+
}
|
|
614
|
+
else if (event.type === "tool_execution_end") {
|
|
615
|
+
writeSessionToolEffectResult(this.sessionManager, event.toolResult, { finalizationPending: true });
|
|
616
|
+
this._toolResultsInFinalization.add(event.toolResult);
|
|
617
|
+
}
|
|
618
|
+
else if (event.type === "message_start" &&
|
|
619
|
+
event.message.role === "toolResult" &&
|
|
620
|
+
!this._toolResultsInFinalization.has(event.message)) {
|
|
621
|
+
beginSessionToolEffectFinalization(this.sessionManager, event.message);
|
|
622
|
+
this._toolResultsInFinalization.add(event.message);
|
|
623
|
+
}
|
|
624
|
+
else if (event.type === "message_end" &&
|
|
625
|
+
event.message.role === "toolResult" &&
|
|
626
|
+
!this._toolResultsInFinalization.has(event.message)) {
|
|
627
|
+
beginSessionToolEffectFinalization(this.sessionManager, event.message);
|
|
628
|
+
this._toolResultsInFinalization.add(event.message);
|
|
629
|
+
}
|
|
630
|
+
const finalizingToolResult = event.type === "message_end" && this._toolResultsInFinalization.has(event.message);
|
|
495
631
|
// When a user message starts, check if it's from either queue and remove it BEFORE emitting
|
|
496
632
|
// This ensures the UI sees the updated queue state
|
|
497
633
|
if (event.type === "message_start" && event.message.role === "user") {
|
|
@@ -518,6 +654,13 @@ export class AgentSession {
|
|
|
518
654
|
await this._emitExtensionEvent(event);
|
|
519
655
|
// Notify all listeners
|
|
520
656
|
this._emit(event.type === "agent_end" ? { ...event, willRetry: this._willRetryAfterAgentEnd(event) } : event);
|
|
657
|
+
if (finalizingToolResult) {
|
|
658
|
+
if (event.message.role !== "toolResult") {
|
|
659
|
+
throw new Error("A toolResult message_end replacement must remain a toolResult");
|
|
660
|
+
}
|
|
661
|
+
writeSessionToolEffectFinalResult(this.sessionManager, event.message);
|
|
662
|
+
this._toolResultsInFinalization.delete(event.message);
|
|
663
|
+
}
|
|
521
664
|
// Handle session persistence
|
|
522
665
|
if (event.type === "message_end") {
|
|
523
666
|
// Check if this is a custom message from extensions
|
|
@@ -529,7 +672,11 @@ export class AgentSession {
|
|
|
529
672
|
event.message.role === "assistant" ||
|
|
530
673
|
event.message.role === "toolResult") {
|
|
531
674
|
// Regular LLM message - persist as SessionMessageEntry
|
|
532
|
-
this.sessionManager.appendMessage(event.message);
|
|
675
|
+
const sessionEntryId = this.sessionManager.appendMessage(event.message);
|
|
676
|
+
if (event.message.role === "toolResult") {
|
|
677
|
+
this.sessionManager.flushSessionFile();
|
|
678
|
+
commitSessionToolEffect(this.sessionManager, event.message, sessionEntryId);
|
|
679
|
+
}
|
|
533
680
|
}
|
|
534
681
|
// Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere
|
|
535
682
|
// Track assistant message for auto-compaction (checked on agent_end)
|
|
@@ -549,12 +696,10 @@ export class AgentSession {
|
|
|
549
696
|
});
|
|
550
697
|
this._retryAttempt = 0;
|
|
551
698
|
}
|
|
552
|
-
if (isPiServerMode() &&
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
tools: this.agent.state.tools,
|
|
557
|
-
}, { entries: this.sessionManager.getEntries(), leafId: this.sessionManager.getLeafId() }, { signal, onHistoryReconciled: (snapshot) => this.reconcilePiServerHistory(snapshot) });
|
|
699
|
+
if (isPiServerMode() &&
|
|
700
|
+
!this._isPostAssistantPiServerSyncFailure(assistantMsg) &&
|
|
701
|
+
!isPiServerControlPlaneFailure(assistantMsg)) {
|
|
702
|
+
await this._syncAndAcknowledgePiServerAssistantMessage(assistantMsg, signal);
|
|
558
703
|
}
|
|
559
704
|
}
|
|
560
705
|
}
|
|
@@ -820,7 +965,7 @@ export class AgentSession {
|
|
|
820
965
|
}
|
|
821
966
|
/** Whether the session has no active agent run, retry, auto-compaction, or queued continuation. */
|
|
822
967
|
get isIdle() {
|
|
823
|
-
return !this._isAgentRunActive;
|
|
968
|
+
return !this._isAgentRunActive && !this._piServerSessionLeaseOwner;
|
|
824
969
|
}
|
|
825
970
|
/** Current effective system prompt (includes any per-turn extension modifications) */
|
|
826
971
|
get systemPrompt() {
|
|
@@ -972,9 +1117,11 @@ export class AgentSession {
|
|
|
972
1117
|
// Prompting
|
|
973
1118
|
// =========================================================================
|
|
974
1119
|
async _runAgentPrompt(messages) {
|
|
1120
|
+
const piServerLease = this._acquirePiServerOperationLease();
|
|
975
1121
|
this._resetValidationHintState();
|
|
976
1122
|
this._isAgentRunActive = true;
|
|
977
1123
|
try {
|
|
1124
|
+
await this._recoverPendingPiServerCompaction("threshold", false);
|
|
978
1125
|
await this.agent.prompt(messages);
|
|
979
1126
|
while (await this._handlePostAgentRun()) {
|
|
980
1127
|
try {
|
|
@@ -997,9 +1144,15 @@ export class AgentSession {
|
|
|
997
1144
|
await this._maybeGenerateSessionName();
|
|
998
1145
|
}
|
|
999
1146
|
finally {
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1147
|
+
try {
|
|
1148
|
+
this._systemPromptOverride = undefined;
|
|
1149
|
+
this._flushPendingBashMessages();
|
|
1150
|
+
await this._emitAgentSettled();
|
|
1151
|
+
}
|
|
1152
|
+
finally {
|
|
1153
|
+
this._releasePiServerOperationLease(piServerLease.path, piServerLease.acquired, piServerLease.owner);
|
|
1154
|
+
this._resolveIdleWaitIfIdle();
|
|
1155
|
+
}
|
|
1003
1156
|
}
|
|
1004
1157
|
}
|
|
1005
1158
|
_sessionHasInfoEntry() {
|
|
@@ -1036,7 +1189,7 @@ export class AgentSession {
|
|
|
1036
1189
|
if (!model || !transcript)
|
|
1037
1190
|
return;
|
|
1038
1191
|
try {
|
|
1039
|
-
const stream = await this.
|
|
1192
|
+
const stream = await this._getAuxiliaryStreamFunction()(model, {
|
|
1040
1193
|
systemPrompt: this.systemPrompt,
|
|
1041
1194
|
messages: [
|
|
1042
1195
|
{
|
|
@@ -1049,7 +1202,7 @@ export class AgentSession {
|
|
|
1049
1202
|
}, {
|
|
1050
1203
|
maxTokens: 32,
|
|
1051
1204
|
reasoning: undefined,
|
|
1052
|
-
sessionId:
|
|
1205
|
+
sessionId: randomUUID(),
|
|
1053
1206
|
transport: this.agent.transport,
|
|
1054
1207
|
thinkingBudgets: this.agent.thinkingBudgets,
|
|
1055
1208
|
maxRetryDelayMs: this.agent.maxRetryDelayMs,
|
|
@@ -1682,9 +1835,26 @@ export class AgentSession {
|
|
|
1682
1835
|
// Compaction
|
|
1683
1836
|
// =========================================================================
|
|
1684
1837
|
async _applyPiServerCompactionResult(result, reason, willRetry) {
|
|
1685
|
-
|
|
1838
|
+
const compactionEntry = result.compactionEntry;
|
|
1839
|
+
const operation = compactionEntry.piServerCompactOperation;
|
|
1840
|
+
if (!operation ||
|
|
1841
|
+
!Number.isSafeInteger(operation.baseEntryCount) ||
|
|
1842
|
+
operation.baseEntryCount < 0 ||
|
|
1843
|
+
(operation.baseLeafId !== null &&
|
|
1844
|
+
(typeof operation.baseLeafId !== "string" || operation.baseLeafId.length === 0)) ||
|
|
1845
|
+
typeof operation.baseTreeHash !== "string" ||
|
|
1846
|
+
!/^[a-f0-9]{64}$/u.test(operation.baseTreeHash)) {
|
|
1847
|
+
throw new Error("Pi-server compaction result is missing a valid durable base identity");
|
|
1848
|
+
}
|
|
1849
|
+
this.sessionManager.appendCompactionEntry(compactionEntry, {
|
|
1850
|
+
baseEntryCount: operation.baseEntryCount,
|
|
1851
|
+
baseLeafId: operation.baseLeafId,
|
|
1852
|
+
baseTreeHash: operation.baseTreeHash,
|
|
1853
|
+
});
|
|
1854
|
+
recordPiServerCompactionApplied(result);
|
|
1686
1855
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1687
1856
|
this.agent.state.messages = sessionContext.messages;
|
|
1857
|
+
await acknowledgePiServerCompaction(result);
|
|
1688
1858
|
const firstKeptEntryId = result.compaction.firstKeptEntryId;
|
|
1689
1859
|
if (!firstKeptEntryId) {
|
|
1690
1860
|
throw new Error("Pi-server compaction result is missing firstKeptEntryId");
|
|
@@ -1703,7 +1873,7 @@ export class AgentSession {
|
|
|
1703
1873
|
await this._extensionRunner.emit({
|
|
1704
1874
|
type: "session_compact",
|
|
1705
1875
|
compactionEntry: savedCompactionEntry,
|
|
1706
|
-
fromExtension:
|
|
1876
|
+
fromExtension: savedCompactionEntry.fromHook === true,
|
|
1707
1877
|
reason,
|
|
1708
1878
|
willRetry,
|
|
1709
1879
|
});
|
|
@@ -1720,38 +1890,31 @@ export class AgentSession {
|
|
|
1720
1890
|
await this.abort();
|
|
1721
1891
|
this._compactionAbortController = new AbortController();
|
|
1722
1892
|
this._emit({ type: "compaction_start", reason: "manual" });
|
|
1893
|
+
let piServerLease = {
|
|
1894
|
+
path: undefined,
|
|
1895
|
+
acquired: false,
|
|
1896
|
+
owner: undefined,
|
|
1897
|
+
};
|
|
1723
1898
|
try {
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
if (isPiServerMode()) {
|
|
1729
|
-
this._detachPiServerTerminalAssistantFailure();
|
|
1730
|
-
const piServerResult = await compactPiServer(this.model, {
|
|
1731
|
-
systemPrompt: this.systemPrompt,
|
|
1732
|
-
messages: this.agent.state.messages,
|
|
1733
|
-
tools: this.agent.state.tools,
|
|
1734
|
-
}, {
|
|
1735
|
-
sessionId: this.sessionId,
|
|
1736
|
-
apiKey,
|
|
1737
|
-
headers,
|
|
1738
|
-
customInstructions,
|
|
1739
|
-
settings: this.settingsManager.getCompactionSettings(),
|
|
1740
|
-
sessionTree: { entries: this.sessionManager.getEntries(), leafId: this.sessionManager.getLeafId() },
|
|
1741
|
-
signal: this._compactionAbortController.signal,
|
|
1742
|
-
reasoning: toProviderReasoning(this.thinkingLevel),
|
|
1743
|
-
onHistoryReconciled: (snapshot) => this.reconcilePiServerHistory(snapshot),
|
|
1744
|
-
});
|
|
1745
|
-
const result = await this._applyPiServerCompactionResult(piServerResult, "manual", false);
|
|
1746
|
-
this._lastServerCompactionTimestamp = Date.now();
|
|
1899
|
+
piServerLease = this._acquirePiServerOperationLease();
|
|
1900
|
+
await this._recoverPendingPiServerRunBeforeControlOperation(this._compactionAbortController.signal);
|
|
1901
|
+
const recoveredCompaction = await this._recoverPendingPiServerCompaction("manual", false, this._compactionAbortController.signal);
|
|
1902
|
+
if (recoveredCompaction) {
|
|
1747
1903
|
this._emit({
|
|
1748
1904
|
type: "compaction_end",
|
|
1749
1905
|
reason: "manual",
|
|
1750
|
-
result,
|
|
1906
|
+
result: recoveredCompaction,
|
|
1751
1907
|
aborted: false,
|
|
1752
1908
|
willRetry: false,
|
|
1753
1909
|
});
|
|
1754
|
-
return
|
|
1910
|
+
return recoveredCompaction;
|
|
1911
|
+
}
|
|
1912
|
+
if (!this.model) {
|
|
1913
|
+
throw new Error(formatNoModelSelectedMessage());
|
|
1914
|
+
}
|
|
1915
|
+
const { apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model);
|
|
1916
|
+
if (isPiServerMode()) {
|
|
1917
|
+
this._detachPiServerTerminalAssistantFailure();
|
|
1755
1918
|
}
|
|
1756
1919
|
const pathEntries = this.sessionManager.getBranch();
|
|
1757
1920
|
const settings = this.settingsManager.getCompactionSettings();
|
|
@@ -1784,6 +1947,42 @@ export class AgentSession {
|
|
|
1784
1947
|
fromExtension = true;
|
|
1785
1948
|
}
|
|
1786
1949
|
}
|
|
1950
|
+
if (isPiServerMode()) {
|
|
1951
|
+
const sessionFile = this.sessionManager.getSessionFile();
|
|
1952
|
+
if (!sessionFile) {
|
|
1953
|
+
throw new Error("Durable pi-server compaction requires a persisted session file");
|
|
1954
|
+
}
|
|
1955
|
+
const piServerResult = await compactPiServer(this.model, {
|
|
1956
|
+
systemPrompt: this.systemPrompt,
|
|
1957
|
+
messages: this.agent.state.messages,
|
|
1958
|
+
tools: this.agent.state.tools,
|
|
1959
|
+
}, {
|
|
1960
|
+
sessionId: this.sessionId,
|
|
1961
|
+
apiKey,
|
|
1962
|
+
headers,
|
|
1963
|
+
env,
|
|
1964
|
+
customInstructions,
|
|
1965
|
+
settings,
|
|
1966
|
+
preparation: { firstKeptEntryId: preparation.firstKeptEntryId },
|
|
1967
|
+
extensionCompaction,
|
|
1968
|
+
retry: this.settingsManager.getRetrySettings(),
|
|
1969
|
+
sessionTree: { entries: this.sessionManager.getEntries(), leafId: this.sessionManager.getLeafId() },
|
|
1970
|
+
piServerCompactStatePath: getPiServerCompactStatePath(sessionFile),
|
|
1971
|
+
signal: this._compactionAbortController.signal,
|
|
1972
|
+
reasoning: toProviderReasoning(this.thinkingLevel),
|
|
1973
|
+
onHistoryReconciled: (snapshot) => this.reconcilePiServerHistory(snapshot),
|
|
1974
|
+
});
|
|
1975
|
+
const result = await this._applyPiServerCompactionResult(piServerResult, "manual", false);
|
|
1976
|
+
this._lastServerCompactionTimestamp = Date.now();
|
|
1977
|
+
this._emit({
|
|
1978
|
+
type: "compaction_end",
|
|
1979
|
+
reason: "manual",
|
|
1980
|
+
result,
|
|
1981
|
+
aborted: false,
|
|
1982
|
+
willRetry: false,
|
|
1983
|
+
});
|
|
1984
|
+
return result;
|
|
1985
|
+
}
|
|
1787
1986
|
let summary;
|
|
1788
1987
|
let firstKeptEntryId;
|
|
1789
1988
|
let tokensBefore;
|
|
@@ -1799,7 +1998,7 @@ export class AgentSession {
|
|
|
1799
1998
|
}
|
|
1800
1999
|
else {
|
|
1801
2000
|
// Generate compaction result
|
|
1802
|
-
const result = await compact(preparation, this.model, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel, this.
|
|
2001
|
+
const result = await compact(preparation, this.model, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel, this._getAuxiliaryStreamFunction(), env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason: "manual" }));
|
|
1803
2002
|
summary = result.summary;
|
|
1804
2003
|
firstKeptEntryId = result.firstKeptEntryId;
|
|
1805
2004
|
tokensBefore = result.tokensBefore;
|
|
@@ -1856,6 +2055,7 @@ export class AgentSession {
|
|
|
1856
2055
|
throw error;
|
|
1857
2056
|
}
|
|
1858
2057
|
finally {
|
|
2058
|
+
this._releasePiServerOperationLease(piServerLease.path, piServerLease.acquired, piServerLease.owner);
|
|
1859
2059
|
this._compactionAbortController = undefined;
|
|
1860
2060
|
this._reconnectToAgent();
|
|
1861
2061
|
}
|
|
@@ -1979,45 +2179,33 @@ export class AgentSession {
|
|
|
1979
2179
|
*/
|
|
1980
2180
|
async _runAutoCompaction(reason, willRetry, preparationOverride, preparationOptions) {
|
|
1981
2181
|
const settings = this.settingsManager.getCompactionSettings();
|
|
2182
|
+
const model = this.model;
|
|
1982
2183
|
let started = false;
|
|
2184
|
+
let piServerLease = {
|
|
2185
|
+
path: undefined,
|
|
2186
|
+
acquired: false,
|
|
2187
|
+
owner: undefined,
|
|
2188
|
+
};
|
|
1983
2189
|
try {
|
|
1984
|
-
|
|
1985
|
-
|
|
2190
|
+
piServerLease = this._acquirePiServerOperationLease(this._isAgentRunActive ? this._piServerSessionLeaseOwner : undefined);
|
|
2191
|
+
if (!this._isAgentRunActive) {
|
|
2192
|
+
await this._recoverPendingPiServerRunBeforeControlOperation();
|
|
1986
2193
|
}
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
let env;
|
|
1990
|
-
if (this.agent.streamFunction === streamSimple) {
|
|
1991
|
-
({ apiKey, headers, env } = await this._getRequiredRequestAuth(this.model));
|
|
1992
|
-
}
|
|
1993
|
-
else {
|
|
1994
|
-
({ apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model));
|
|
1995
|
-
}
|
|
1996
|
-
if (isPiServerMode()) {
|
|
2194
|
+
const recoveredCompaction = await this._recoverPendingPiServerCompaction(reason, willRetry);
|
|
2195
|
+
if (recoveredCompaction) {
|
|
1997
2196
|
this._emit({ type: "compaction_start", reason });
|
|
1998
|
-
this._autoCompactionAbortController = new AbortController();
|
|
1999
2197
|
started = true;
|
|
2000
|
-
const signal = this._autoCompactionAbortController.signal;
|
|
2001
|
-
const piServerResult = await compactPiServer(this.model, {
|
|
2002
|
-
systemPrompt: this.systemPrompt,
|
|
2003
|
-
messages: this.agent.state.messages,
|
|
2004
|
-
tools: this.agent.state.tools,
|
|
2005
|
-
}, {
|
|
2006
|
-
sessionId: this.sessionId,
|
|
2007
|
-
apiKey,
|
|
2008
|
-
headers,
|
|
2009
|
-
settings,
|
|
2010
|
-
preparation: preparationOptions,
|
|
2011
|
-
sessionTree: { entries: this.sessionManager.getEntries(), leafId: this.sessionManager.getLeafId() },
|
|
2012
|
-
signal,
|
|
2013
|
-
reasoning: toProviderReasoning(this.thinkingLevel),
|
|
2014
|
-
onHistoryReconciled: (snapshot) => this.reconcilePiServerHistory(snapshot),
|
|
2015
|
-
});
|
|
2016
|
-
const result = await this._applyPiServerCompactionResult(piServerResult, reason, willRetry);
|
|
2017
2198
|
this._lastServerCompactionTimestamp = Date.now();
|
|
2018
|
-
this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
|
|
2199
|
+
this._emit({ type: "compaction_end", reason, result: recoveredCompaction, aborted: false, willRetry });
|
|
2019
2200
|
return willRetry ? true : this.agent.hasQueuedMessages();
|
|
2020
2201
|
}
|
|
2202
|
+
if (!model) {
|
|
2203
|
+
return false;
|
|
2204
|
+
}
|
|
2205
|
+
let apiKey;
|
|
2206
|
+
let headers;
|
|
2207
|
+
let env;
|
|
2208
|
+
({ apiKey, headers, env } = await this._getSummarizationRequestAuth(model));
|
|
2021
2209
|
const pathEntries = this.sessionManager.getBranch();
|
|
2022
2210
|
const preparation = preparationOverride ?? prepareCompaction(pathEntries, settings);
|
|
2023
2211
|
if (!preparation) {
|
|
@@ -2026,6 +2214,7 @@ export class AgentSession {
|
|
|
2026
2214
|
this._emit({ type: "compaction_start", reason });
|
|
2027
2215
|
this._autoCompactionAbortController = new AbortController();
|
|
2028
2216
|
started = true;
|
|
2217
|
+
const signal = this._autoCompactionAbortController.signal;
|
|
2029
2218
|
let extensionCompaction;
|
|
2030
2219
|
let fromExtension = false;
|
|
2031
2220
|
if (this._extensionRunner.hasHandlers("session_before_compact")) {
|
|
@@ -2036,7 +2225,7 @@ export class AgentSession {
|
|
|
2036
2225
|
customInstructions: undefined,
|
|
2037
2226
|
reason,
|
|
2038
2227
|
willRetry,
|
|
2039
|
-
signal
|
|
2228
|
+
signal,
|
|
2040
2229
|
}));
|
|
2041
2230
|
if (extensionResult?.cancel) {
|
|
2042
2231
|
this._emit({
|
|
@@ -2053,6 +2242,35 @@ export class AgentSession {
|
|
|
2053
2242
|
fromExtension = true;
|
|
2054
2243
|
}
|
|
2055
2244
|
}
|
|
2245
|
+
if (isPiServerMode()) {
|
|
2246
|
+
const sessionFile = this.sessionManager.getSessionFile();
|
|
2247
|
+
if (!sessionFile) {
|
|
2248
|
+
throw new Error("Durable pi-server compaction requires a persisted session file");
|
|
2249
|
+
}
|
|
2250
|
+
const piServerResult = await compactPiServer(model, {
|
|
2251
|
+
systemPrompt: this.systemPrompt,
|
|
2252
|
+
messages: this.agent.state.messages,
|
|
2253
|
+
tools: this.agent.state.tools,
|
|
2254
|
+
}, {
|
|
2255
|
+
sessionId: this.sessionId,
|
|
2256
|
+
apiKey,
|
|
2257
|
+
headers,
|
|
2258
|
+
env,
|
|
2259
|
+
settings,
|
|
2260
|
+
retry: this.settingsManager.getRetrySettings(),
|
|
2261
|
+
preparation: preparationOptions ?? { firstKeptEntryId: preparation.firstKeptEntryId },
|
|
2262
|
+
extensionCompaction,
|
|
2263
|
+
sessionTree: { entries: this.sessionManager.getEntries(), leafId: this.sessionManager.getLeafId() },
|
|
2264
|
+
piServerCompactStatePath: getPiServerCompactStatePath(sessionFile),
|
|
2265
|
+
signal,
|
|
2266
|
+
reasoning: toProviderReasoning(this.thinkingLevel),
|
|
2267
|
+
onHistoryReconciled: (snapshot) => this.reconcilePiServerHistory(snapshot),
|
|
2268
|
+
});
|
|
2269
|
+
const result = await this._applyPiServerCompactionResult(piServerResult, reason, willRetry);
|
|
2270
|
+
this._lastServerCompactionTimestamp = Date.now();
|
|
2271
|
+
this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
|
|
2272
|
+
return willRetry ? true : this.agent.hasQueuedMessages();
|
|
2273
|
+
}
|
|
2056
2274
|
let summary;
|
|
2057
2275
|
let firstKeptEntryId;
|
|
2058
2276
|
let tokensBefore;
|
|
@@ -2068,7 +2286,7 @@ export class AgentSession {
|
|
|
2068
2286
|
}
|
|
2069
2287
|
else {
|
|
2070
2288
|
// Generate compaction result
|
|
2071
|
-
const compactResult = await compact(preparation,
|
|
2289
|
+
const compactResult = await compact(preparation, model, apiKey, headers, undefined, this._autoCompactionAbortController.signal, this.thinkingLevel, this._getAuxiliaryStreamFunction(), env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }));
|
|
2072
2290
|
summary = compactResult.summary;
|
|
2073
2291
|
firstKeptEntryId = compactResult.firstKeptEntryId;
|
|
2074
2292
|
tokensBefore = compactResult.tokensBefore;
|
|
@@ -2139,6 +2357,7 @@ export class AgentSession {
|
|
|
2139
2357
|
return false;
|
|
2140
2358
|
}
|
|
2141
2359
|
finally {
|
|
2360
|
+
this._releasePiServerOperationLease(piServerLease.path, piServerLease.acquired, piServerLease.owner);
|
|
2142
2361
|
this._autoCompactionAbortController = undefined;
|
|
2143
2362
|
}
|
|
2144
2363
|
}
|
|
@@ -2554,8 +2773,12 @@ export class AgentSession {
|
|
|
2554
2773
|
return false;
|
|
2555
2774
|
if (this._isPostAssistantPiServerSyncFailure(message))
|
|
2556
2775
|
return false;
|
|
2557
|
-
|
|
2558
|
-
|
|
2776
|
+
if (isPiServerRestartUncertainty(message))
|
|
2777
|
+
return false;
|
|
2778
|
+
const piServerFailure = getPiServerFailureMetadata(message);
|
|
2779
|
+
if (piServerFailure?.phase && piServerFailure.phase !== "provider_stream")
|
|
2780
|
+
return false;
|
|
2781
|
+
if (piServerFailure?.retryable === false)
|
|
2559
2782
|
return false;
|
|
2560
2783
|
if (message.errorMessage && this._isNonRetryableProviderLimitError(message.errorMessage))
|
|
2561
2784
|
return false;
|
|
@@ -2822,6 +3045,7 @@ export class AgentSession {
|
|
|
2822
3045
|
replaceInstructions,
|
|
2823
3046
|
label,
|
|
2824
3047
|
};
|
|
3048
|
+
const piServerLease = this._acquirePiServerOperationLease();
|
|
2825
3049
|
// Set up abort controller for summarization
|
|
2826
3050
|
this._branchSummaryAbortController = new AbortController();
|
|
2827
3051
|
try {
|
|
@@ -2869,7 +3093,7 @@ export class AgentSession {
|
|
|
2869
3093
|
customInstructions,
|
|
2870
3094
|
replaceInstructions,
|
|
2871
3095
|
reserveTokens: branchSummarySettings.reserveTokens,
|
|
2872
|
-
streamFn: this.
|
|
3096
|
+
streamFn: this._getAuxiliaryStreamFunction(),
|
|
2873
3097
|
retry: this.settingsManager.getRetrySettings(),
|
|
2874
3098
|
callbacks: this._summarizationRetryCallbacks({ source: "branchSummary" }),
|
|
2875
3099
|
});
|
|
@@ -2954,6 +3178,7 @@ export class AgentSession {
|
|
|
2954
3178
|
return { editorText, cancelled: false, summaryEntry };
|
|
2955
3179
|
}
|
|
2956
3180
|
finally {
|
|
3181
|
+
this._releasePiServerOperationLease(piServerLease.path, piServerLease.acquired, piServerLease.owner);
|
|
2957
3182
|
this._branchSummaryAbortController = undefined;
|
|
2958
3183
|
}
|
|
2959
3184
|
}
|