@mariozechner/pi-coding-agent 0.27.4 → 0.27.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 +25 -1
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +137 -40
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction.d.ts +10 -0
- package/dist/core/compaction.d.ts.map +1 -1
- package/dist/core/compaction.js +35 -0
- package/dist/core/compaction.js.map +1 -1
- package/dist/core/export-html.d.ts +11 -2
- package/dist/core/export-html.d.ts.map +1 -1
- package/dist/core/export-html.js +551 -94
- package/dist/core/export-html.js.map +1 -1
- package/dist/core/hooks/runner.d.ts.map +1 -1
- package/dist/core/hooks/runner.js +11 -3
- package/dist/core/hooks/runner.js.map +1 -1
- package/dist/core/hooks/types.d.ts +28 -2
- package/dist/core/hooks/types.d.ts.map +1 -1
- package/dist/core/hooks/types.js.map +1 -1
- package/dist/core/model-config.d.ts +7 -2
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +7 -2
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +24 -11
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +25 -21
- package/dist/core/session-manager.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +5 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +1 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/hooks.md +126 -12
- package/examples/hooks/README.md +3 -0
- package/examples/hooks/custom-compaction.ts +115 -0
- package/package.json +6 -5
|
@@ -15,10 +15,9 @@
|
|
|
15
15
|
import { isContextOverflow, supportsXhigh } from "@mariozechner/pi-ai";
|
|
16
16
|
import { getModelsPath } from "../config.js";
|
|
17
17
|
import { executeBash as executeBashCommand } from "./bash-executor.js";
|
|
18
|
-
import { calculateContextTokens, compact, shouldCompact } from "./compaction.js";
|
|
18
|
+
import { calculateContextTokens, compact, prepareCompaction, shouldCompact } from "./compaction.js";
|
|
19
19
|
import { exportSessionToHtml } from "./export-html.js";
|
|
20
20
|
import { getApiKeyForModel, getAvailableModels } from "./model-config.js";
|
|
21
|
-
import { loadSessionFromEntries } from "./session-manager.js";
|
|
22
21
|
import { expandSlashCommand } from "./slash-commands.js";
|
|
23
22
|
// ============================================================================
|
|
24
23
|
// Constants
|
|
@@ -370,7 +369,7 @@ export class AgentSession {
|
|
|
370
369
|
*/
|
|
371
370
|
async reset() {
|
|
372
371
|
const previousSessionFile = this.sessionFile;
|
|
373
|
-
const entries = this.sessionManager.
|
|
372
|
+
const entries = this.sessionManager.getEntries();
|
|
374
373
|
// Emit before_clear event (can be cancelled)
|
|
375
374
|
if (this._hookRunner?.hasHandlers("session")) {
|
|
376
375
|
const result = (await this._hookRunner.emit({
|
|
@@ -561,10 +560,8 @@ export class AgentSession {
|
|
|
561
560
|
* @param customInstructions Optional instructions for the compaction summary
|
|
562
561
|
*/
|
|
563
562
|
async compact(customInstructions) {
|
|
564
|
-
// Abort any running operation
|
|
565
563
|
this._disconnectFromAgent();
|
|
566
564
|
await this.abort();
|
|
567
|
-
// Create abort controller
|
|
568
565
|
this._compactionAbortController = new AbortController();
|
|
569
566
|
try {
|
|
570
567
|
if (!this.model) {
|
|
@@ -574,16 +571,69 @@ export class AgentSession {
|
|
|
574
571
|
if (!apiKey) {
|
|
575
572
|
throw new Error(`No API key for ${this.model.provider}`);
|
|
576
573
|
}
|
|
577
|
-
const entries = this.sessionManager.
|
|
574
|
+
const entries = this.sessionManager.getEntries();
|
|
578
575
|
const settings = this.settingsManager.getCompactionSettings();
|
|
579
|
-
const
|
|
576
|
+
const preparation = prepareCompaction(entries, settings);
|
|
577
|
+
if (!preparation) {
|
|
578
|
+
throw new Error("Already compacted");
|
|
579
|
+
}
|
|
580
|
+
// Find previous compaction summary if any
|
|
581
|
+
let previousSummary;
|
|
582
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
583
|
+
if (entries[i].type === "compaction") {
|
|
584
|
+
previousSummary = entries[i].summary;
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
let compactionEntry;
|
|
589
|
+
let fromHook = false;
|
|
590
|
+
if (this._hookRunner?.hasHandlers("session")) {
|
|
591
|
+
const result = (await this._hookRunner.emit({
|
|
592
|
+
type: "session",
|
|
593
|
+
entries,
|
|
594
|
+
sessionFile: this.sessionFile,
|
|
595
|
+
previousSessionFile: null,
|
|
596
|
+
reason: "before_compact",
|
|
597
|
+
cutPoint: preparation.cutPoint,
|
|
598
|
+
previousSummary,
|
|
599
|
+
messagesToSummarize: [...preparation.messagesToSummarize],
|
|
600
|
+
messagesToKeep: [...preparation.messagesToKeep],
|
|
601
|
+
tokensBefore: preparation.tokensBefore,
|
|
602
|
+
customInstructions,
|
|
603
|
+
model: this.model,
|
|
604
|
+
resolveApiKey: this._resolveApiKey,
|
|
605
|
+
signal: this._compactionAbortController.signal,
|
|
606
|
+
}));
|
|
607
|
+
if (result?.cancel) {
|
|
608
|
+
throw new Error("Compaction cancelled");
|
|
609
|
+
}
|
|
610
|
+
if (result?.compactionEntry) {
|
|
611
|
+
compactionEntry = result.compactionEntry;
|
|
612
|
+
fromHook = true;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (!compactionEntry) {
|
|
616
|
+
compactionEntry = await compact(entries, this.model, settings, apiKey, this._compactionAbortController.signal, customInstructions);
|
|
617
|
+
}
|
|
580
618
|
if (this._compactionAbortController.signal.aborted) {
|
|
581
619
|
throw new Error("Compaction cancelled");
|
|
582
620
|
}
|
|
583
|
-
// Save and reload
|
|
584
621
|
this.sessionManager.saveCompaction(compactionEntry);
|
|
585
|
-
const
|
|
586
|
-
this.
|
|
622
|
+
const newEntries = this.sessionManager.getEntries();
|
|
623
|
+
const sessionContext = this.sessionManager.buildSessionContext();
|
|
624
|
+
this.agent.replaceMessages(sessionContext.messages);
|
|
625
|
+
if (this._hookRunner) {
|
|
626
|
+
await this._hookRunner.emit({
|
|
627
|
+
type: "session",
|
|
628
|
+
entries: newEntries,
|
|
629
|
+
sessionFile: this.sessionFile,
|
|
630
|
+
previousSessionFile: null,
|
|
631
|
+
reason: "compact",
|
|
632
|
+
compactionEntry,
|
|
633
|
+
tokensBefore: compactionEntry.tokensBefore,
|
|
634
|
+
fromHook,
|
|
635
|
+
});
|
|
636
|
+
}
|
|
587
637
|
return {
|
|
588
638
|
tokensBefore: compactionEntry.tokensBefore,
|
|
589
639
|
summary: compactionEntry.summary,
|
|
@@ -657,41 +707,89 @@ export class AgentSession {
|
|
|
657
707
|
this._emit({ type: "auto_compaction_end", result: null, aborted: false, willRetry: false });
|
|
658
708
|
return;
|
|
659
709
|
}
|
|
660
|
-
const entries = this.sessionManager.
|
|
661
|
-
const
|
|
710
|
+
const entries = this.sessionManager.getEntries();
|
|
711
|
+
const preparation = prepareCompaction(entries, settings);
|
|
712
|
+
if (!preparation) {
|
|
713
|
+
this._emit({ type: "auto_compaction_end", result: null, aborted: false, willRetry: false });
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
// Find previous compaction summary if any
|
|
717
|
+
let previousSummary;
|
|
718
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
719
|
+
if (entries[i].type === "compaction") {
|
|
720
|
+
previousSummary = entries[i].summary;
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
let compactionEntry;
|
|
725
|
+
let fromHook = false;
|
|
726
|
+
if (this._hookRunner?.hasHandlers("session")) {
|
|
727
|
+
const hookResult = (await this._hookRunner.emit({
|
|
728
|
+
type: "session",
|
|
729
|
+
entries,
|
|
730
|
+
sessionFile: this.sessionFile,
|
|
731
|
+
previousSessionFile: null,
|
|
732
|
+
reason: "before_compact",
|
|
733
|
+
cutPoint: preparation.cutPoint,
|
|
734
|
+
previousSummary,
|
|
735
|
+
messagesToSummarize: [...preparation.messagesToSummarize],
|
|
736
|
+
messagesToKeep: [...preparation.messagesToKeep],
|
|
737
|
+
tokensBefore: preparation.tokensBefore,
|
|
738
|
+
customInstructions: undefined,
|
|
739
|
+
model: this.model,
|
|
740
|
+
resolveApiKey: this._resolveApiKey,
|
|
741
|
+
signal: this._autoCompactionAbortController.signal,
|
|
742
|
+
}));
|
|
743
|
+
if (hookResult?.cancel) {
|
|
744
|
+
this._emit({ type: "auto_compaction_end", result: null, aborted: true, willRetry: false });
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (hookResult?.compactionEntry) {
|
|
748
|
+
compactionEntry = hookResult.compactionEntry;
|
|
749
|
+
fromHook = true;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (!compactionEntry) {
|
|
753
|
+
compactionEntry = await compact(entries, this.model, settings, apiKey, this._autoCompactionAbortController.signal);
|
|
754
|
+
}
|
|
662
755
|
if (this._autoCompactionAbortController.signal.aborted) {
|
|
663
756
|
this._emit({ type: "auto_compaction_end", result: null, aborted: true, willRetry: false });
|
|
664
757
|
return;
|
|
665
758
|
}
|
|
666
759
|
this.sessionManager.saveCompaction(compactionEntry);
|
|
667
|
-
const
|
|
668
|
-
this.
|
|
760
|
+
const newEntries = this.sessionManager.getEntries();
|
|
761
|
+
const sessionContext = this.sessionManager.buildSessionContext();
|
|
762
|
+
this.agent.replaceMessages(sessionContext.messages);
|
|
763
|
+
if (this._hookRunner) {
|
|
764
|
+
await this._hookRunner.emit({
|
|
765
|
+
type: "session",
|
|
766
|
+
entries: newEntries,
|
|
767
|
+
sessionFile: this.sessionFile,
|
|
768
|
+
previousSessionFile: null,
|
|
769
|
+
reason: "compact",
|
|
770
|
+
compactionEntry,
|
|
771
|
+
tokensBefore: compactionEntry.tokensBefore,
|
|
772
|
+
fromHook,
|
|
773
|
+
});
|
|
774
|
+
}
|
|
669
775
|
const result = {
|
|
670
776
|
tokensBefore: compactionEntry.tokensBefore,
|
|
671
777
|
summary: compactionEntry.summary,
|
|
672
778
|
};
|
|
673
779
|
this._emit({ type: "auto_compaction_end", result, aborted: false, willRetry });
|
|
674
|
-
// Auto-retry if needed - use continue() since user message is already in context
|
|
675
780
|
if (willRetry) {
|
|
676
|
-
// Remove trailing error message from agent state (it's kept in session file for history)
|
|
677
|
-
// This is needed because continue() requires last message to be user or toolResult
|
|
678
781
|
const messages = this.agent.state.messages;
|
|
679
782
|
const lastMsg = messages[messages.length - 1];
|
|
680
783
|
if (lastMsg?.role === "assistant" && lastMsg.stopReason === "error") {
|
|
681
784
|
this.agent.replaceMessages(messages.slice(0, -1));
|
|
682
785
|
}
|
|
683
|
-
// Use setTimeout to break out of the event handler chain
|
|
684
786
|
setTimeout(() => {
|
|
685
|
-
this.agent.continue().catch(() => {
|
|
686
|
-
// Retry failed - silently ignore, user can manually retry
|
|
687
|
-
});
|
|
787
|
+
this.agent.continue().catch(() => { });
|
|
688
788
|
}, 100);
|
|
689
789
|
}
|
|
690
790
|
}
|
|
691
791
|
catch (error) {
|
|
692
|
-
// Compaction failed - emit end event without retry
|
|
693
792
|
this._emit({ type: "auto_compaction_end", result: null, aborted: false, willRetry: false });
|
|
694
|
-
// If this was overflow recovery and compaction failed, we have a hard stop
|
|
695
793
|
if (reason === "overflow") {
|
|
696
794
|
throw new Error(`Context overflow: ${error instanceof Error ? error.message : "compaction failed"}. Your input may be too large for the context window.`);
|
|
697
795
|
}
|
|
@@ -927,7 +1025,7 @@ export class AgentSession {
|
|
|
927
1025
|
*/
|
|
928
1026
|
async switchSession(sessionPath) {
|
|
929
1027
|
const previousSessionFile = this.sessionFile;
|
|
930
|
-
const oldEntries = this.sessionManager.
|
|
1028
|
+
const oldEntries = this.sessionManager.getEntries();
|
|
931
1029
|
// Emit before_switch event (can be cancelled)
|
|
932
1030
|
if (this._hookRunner?.hasHandlers("session")) {
|
|
933
1031
|
const result = (await this._hookRunner.emit({
|
|
@@ -947,8 +1045,8 @@ export class AgentSession {
|
|
|
947
1045
|
// Set new session
|
|
948
1046
|
this.sessionManager.setSessionFile(sessionPath);
|
|
949
1047
|
// Reload messages
|
|
950
|
-
const entries = this.sessionManager.
|
|
951
|
-
const
|
|
1048
|
+
const entries = this.sessionManager.getEntries();
|
|
1049
|
+
const sessionContext = this.sessionManager.buildSessionContext();
|
|
952
1050
|
// Emit session event to hooks
|
|
953
1051
|
if (this._hookRunner) {
|
|
954
1052
|
this._hookRunner.setSessionFile(sessionPath);
|
|
@@ -962,20 +1060,18 @@ export class AgentSession {
|
|
|
962
1060
|
}
|
|
963
1061
|
// Emit session event to custom tools
|
|
964
1062
|
await this._emitToolSessionEvent("switch", previousSessionFile);
|
|
965
|
-
this.agent.replaceMessages(
|
|
1063
|
+
this.agent.replaceMessages(sessionContext.messages);
|
|
966
1064
|
// Restore model if saved
|
|
967
|
-
|
|
968
|
-
if (savedModel) {
|
|
1065
|
+
if (sessionContext.model) {
|
|
969
1066
|
const availableModels = (await getAvailableModels()).models;
|
|
970
|
-
const match = availableModels.find((m) => m.provider ===
|
|
1067
|
+
const match = availableModels.find((m) => m.provider === sessionContext.model.provider && m.id === sessionContext.model.modelId);
|
|
971
1068
|
if (match) {
|
|
972
1069
|
this.agent.setModel(match);
|
|
973
1070
|
}
|
|
974
1071
|
}
|
|
975
1072
|
// Restore thinking level if saved (setThinkingLevel clamps to model capabilities)
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
this.setThinkingLevel(savedThinking);
|
|
1073
|
+
if (sessionContext.thinkingLevel) {
|
|
1074
|
+
this.setThinkingLevel(sessionContext.thinkingLevel);
|
|
979
1075
|
}
|
|
980
1076
|
this._reconnectToAgent();
|
|
981
1077
|
return true;
|
|
@@ -991,7 +1087,7 @@ export class AgentSession {
|
|
|
991
1087
|
*/
|
|
992
1088
|
async branch(entryIndex) {
|
|
993
1089
|
const previousSessionFile = this.sessionFile;
|
|
994
|
-
const entries = this.sessionManager.
|
|
1090
|
+
const entries = this.sessionManager.getEntries();
|
|
995
1091
|
const selectedEntry = entries[entryIndex];
|
|
996
1092
|
if (!selectedEntry || selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
|
|
997
1093
|
throw new Error("Invalid entry index for branching");
|
|
@@ -1020,8 +1116,8 @@ export class AgentSession {
|
|
|
1020
1116
|
this.sessionManager.setSessionFile(newSessionFile);
|
|
1021
1117
|
}
|
|
1022
1118
|
// Reload messages from entries (works for both file and in-memory mode)
|
|
1023
|
-
const newEntries = this.sessionManager.
|
|
1024
|
-
const
|
|
1119
|
+
const newEntries = this.sessionManager.getEntries();
|
|
1120
|
+
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1025
1121
|
// Emit branch event to hooks (after branch completes)
|
|
1026
1122
|
if (this._hookRunner) {
|
|
1027
1123
|
this._hookRunner.setSessionFile(newSessionFile);
|
|
@@ -1037,7 +1133,7 @@ export class AgentSession {
|
|
|
1037
1133
|
// Emit session event to custom tools (with reason "branch")
|
|
1038
1134
|
await this._emitToolSessionEvent("branch", previousSessionFile);
|
|
1039
1135
|
if (!skipConversationRestore) {
|
|
1040
|
-
this.agent.replaceMessages(
|
|
1136
|
+
this.agent.replaceMessages(sessionContext.messages);
|
|
1041
1137
|
}
|
|
1042
1138
|
return { selectedText, cancelled: false };
|
|
1043
1139
|
}
|
|
@@ -1045,7 +1141,7 @@ export class AgentSession {
|
|
|
1045
1141
|
* Get all user messages from session for branch selector.
|
|
1046
1142
|
*/
|
|
1047
1143
|
getUserMessagesForBranching() {
|
|
1048
|
-
const entries = this.sessionManager.
|
|
1144
|
+
const entries = this.sessionManager.getEntries();
|
|
1049
1145
|
const result = [];
|
|
1050
1146
|
for (let i = 0; i < entries.length; i++) {
|
|
1051
1147
|
const entry = entries[i];
|
|
@@ -1120,7 +1216,8 @@ export class AgentSession {
|
|
|
1120
1216
|
* @returns Path to exported file
|
|
1121
1217
|
*/
|
|
1122
1218
|
exportToHtml(outputPath) {
|
|
1123
|
-
|
|
1219
|
+
const themeName = this.settingsManager.getTheme();
|
|
1220
|
+
return exportSessionToHtml(this.sessionManager, this.state, { outputPath, themeName });
|
|
1124
1221
|
}
|
|
1125
1222
|
// =========================================================================
|
|
1126
1223
|
// Utilities
|
|
@@ -1172,7 +1269,7 @@ export class AgentSession {
|
|
|
1172
1269
|
*/
|
|
1173
1270
|
async _emitToolSessionEvent(reason, previousSessionFile) {
|
|
1174
1271
|
const event = {
|
|
1175
|
-
entries: this.sessionManager.
|
|
1272
|
+
entries: this.sessionManager.getEntries(),
|
|
1176
1273
|
sessionFile: this.sessionFile,
|
|
1177
1274
|
previousSessionFile,
|
|
1178
1275
|
reason,
|