@caupulican/pi-adaptative 0.80.31 → 0.80.37
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 +24 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +13 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +18 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +130 -17
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +6 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/session-manager.d.ts +2 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +6 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +3 -6
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +45 -21
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +24 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +412 -96
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/extensions.md +24 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +5 -5
|
@@ -68,6 +68,12 @@ import { TrustSelectorComponent } from "./components/trust-selector.js";
|
|
|
68
68
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
69
69
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
70
70
|
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
71
|
+
const TUI_HISTORY_RELOAD_MAX_LINES = 1000;
|
|
72
|
+
const TUI_HISTORY_RELOAD_WRAP_WIDTH = 100;
|
|
73
|
+
const TUI_HISTORY_RELOAD_CHUNK_SIZE = 20;
|
|
74
|
+
const TUI_LIVE_HISTORY_MAX_COMPONENTS = 260;
|
|
75
|
+
const TUI_LIVE_HISTORY_TRIM_TO_COMPONENTS = 220;
|
|
76
|
+
const STREAMING_UI_UPDATE_INTERVAL_MS = 80;
|
|
71
77
|
function isExpandable(obj) {
|
|
72
78
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
73
79
|
}
|
|
@@ -102,7 +108,7 @@ const AUTO_LEARN_DEFAULTS = {
|
|
|
102
108
|
maxConcurrentLearners: 1,
|
|
103
109
|
applyHighConfidence: false,
|
|
104
110
|
reflectionReview: true,
|
|
105
|
-
reflectionMinToolCalls:
|
|
111
|
+
reflectionMinToolCalls: 5,
|
|
106
112
|
reflectionCooldownMinutes: 24 * 60,
|
|
107
113
|
};
|
|
108
114
|
const AUTONOMY_AUTO_LEARN_PRESETS = {
|
|
@@ -117,7 +123,7 @@ const AUTONOMY_AUTO_LEARN_PRESETS = {
|
|
|
117
123
|
maxConcurrentLearners: 1,
|
|
118
124
|
applyHighConfidence: false,
|
|
119
125
|
reflectionReview: true,
|
|
120
|
-
reflectionMinToolCalls:
|
|
126
|
+
reflectionMinToolCalls: 5,
|
|
121
127
|
reflectionCooldownMinutes: 24 * 60,
|
|
122
128
|
},
|
|
123
129
|
balanced: {
|
|
@@ -130,7 +136,7 @@ const AUTONOMY_AUTO_LEARN_PRESETS = {
|
|
|
130
136
|
maxConcurrentLearners: 1,
|
|
131
137
|
applyHighConfidence: false,
|
|
132
138
|
reflectionReview: true,
|
|
133
|
-
reflectionMinToolCalls:
|
|
139
|
+
reflectionMinToolCalls: 5,
|
|
134
140
|
reflectionCooldownMinutes: 24 * 60,
|
|
135
141
|
},
|
|
136
142
|
full: {
|
|
@@ -143,13 +149,14 @@ const AUTONOMY_AUTO_LEARN_PRESETS = {
|
|
|
143
149
|
maxConcurrentLearners: 1,
|
|
144
150
|
applyHighConfidence: true,
|
|
145
151
|
reflectionReview: true,
|
|
146
|
-
reflectionMinToolCalls:
|
|
152
|
+
reflectionMinToolCalls: 5,
|
|
147
153
|
reflectionCooldownMinutes: 24 * 60,
|
|
148
154
|
},
|
|
149
155
|
};
|
|
150
156
|
const AUTONOMY_MODES = ["off", "safe", "balanced", "full"];
|
|
151
157
|
const AUTO_LEARN_RESERVATION_MS = 2 * 60 * 1000;
|
|
152
158
|
const AUTO_LEARN_THINKING_LEVEL = "xhigh";
|
|
159
|
+
const AUTO_LEARN_COMPLEX_TASK_TOOL_CALLS = 5;
|
|
153
160
|
export const AUTO_LEARN_HISTORY_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
154
161
|
function definedStringSet(values) {
|
|
155
162
|
const set = new Set();
|
|
@@ -407,9 +414,16 @@ export class InteractiveMode {
|
|
|
407
414
|
// Status line tracking (for mutating immediately-sequential status updates)
|
|
408
415
|
lastStatusSpacer = undefined;
|
|
409
416
|
lastStatusText = undefined;
|
|
417
|
+
// Live TUI history cap. Full session history remains in SessionManager/model state.
|
|
418
|
+
liveHistoryHiddenNotice = undefined;
|
|
419
|
+
liveHistoryHiddenComponents = 0;
|
|
420
|
+
tuiHistoryLoaded = false;
|
|
421
|
+
tuiHistoryLoadInProgress = false;
|
|
410
422
|
// Streaming message tracking
|
|
411
423
|
streamingComponent = undefined;
|
|
412
424
|
streamingMessage = undefined;
|
|
425
|
+
streamingUiUpdateTimer = undefined;
|
|
426
|
+
lastStreamingUiUpdateAt = 0;
|
|
413
427
|
// Tool execution tracking and session-scoped reusable panels
|
|
414
428
|
toolPanels = new ToolPanelRegistry();
|
|
415
429
|
// Tool output expansion state
|
|
@@ -687,7 +701,7 @@ export class InteractiveMode {
|
|
|
687
701
|
hint("app.thinking.cycle", "to cycle thinking level"),
|
|
688
702
|
rawKeyHint(`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`, "to cycle models"),
|
|
689
703
|
hint("app.model.select", "to select model"),
|
|
690
|
-
hint("app.tools.expand", "to expand tools"),
|
|
704
|
+
hint("app.tools.expand", "to load history / expand tools"),
|
|
691
705
|
hint("app.thinking.toggle", "to expand thinking"),
|
|
692
706
|
hint("app.editor.external", "for external editor"),
|
|
693
707
|
rawKeyHint("/", "for commands"),
|
|
@@ -705,9 +719,9 @@ export class InteractiveMode {
|
|
|
705
719
|
rawKeyHint(`${keyText("app.clear")}/${keyText("app.exit")}`, "clear/exit"),
|
|
706
720
|
rawKeyHint("/", "commands"),
|
|
707
721
|
rawKeyHint("!", "bash"),
|
|
708
|
-
hint("app.tools.expand", "more"),
|
|
722
|
+
hint("app.tools.expand", "history/more"),
|
|
709
723
|
].join(theme.fg("muted", " · "));
|
|
710
|
-
const compactOnboarding = theme.fg("dim", `Press ${keyText("app.tools.expand")} to show full startup help and loaded resources.`);
|
|
724
|
+
const compactOnboarding = theme.fg("dim", `Press ${keyText("app.tools.expand")} to load session history or show full startup help and loaded resources.`);
|
|
711
725
|
const onboarding = theme.fg("dim", `Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.`);
|
|
712
726
|
this.builtInHeader = new ExpandableText(() => `${logo}\n${compactInstructions}\n${compactOnboarding}\n\n${onboarding}`, () => `${logo}\n${expandedInstructions}\n\n${onboarding}`, this.getStartupExpansionState(), 1, 0);
|
|
713
727
|
// Setup UI layout
|
|
@@ -1434,7 +1448,6 @@ export class InteractiveMode {
|
|
|
1434
1448
|
if (result.cancelled) {
|
|
1435
1449
|
return { cancelled: true };
|
|
1436
1450
|
}
|
|
1437
|
-
this.chatContainer.clear();
|
|
1438
1451
|
await this.renderInitialMessages();
|
|
1439
1452
|
if (result.editorText && !this.editor.getText().trim()) {
|
|
1440
1453
|
this.editor.setText(result.editorText);
|
|
@@ -1502,7 +1515,6 @@ export class InteractiveMode {
|
|
|
1502
1515
|
process.exit(1);
|
|
1503
1516
|
}
|
|
1504
1517
|
renderCurrentSessionState() {
|
|
1505
|
-
this.chatContainer.clear();
|
|
1506
1518
|
this.pendingMessagesContainer.clear();
|
|
1507
1519
|
this.compactionQueuedMessages = [];
|
|
1508
1520
|
this.streamingComponent = undefined;
|
|
@@ -1527,21 +1539,25 @@ export class InteractiveMode {
|
|
|
1527
1539
|
const toolGroup = allowGrouping ? component.toolGroup?.trim() : undefined;
|
|
1528
1540
|
if (!toolGroup) {
|
|
1529
1541
|
this.chatContainer.addChild(component);
|
|
1542
|
+
this.trimLiveTuiHistory();
|
|
1530
1543
|
return;
|
|
1531
1544
|
}
|
|
1532
1545
|
const children = this.chatContainer.children;
|
|
1533
1546
|
const lastChild = children[children.length - 1];
|
|
1534
1547
|
if (lastChild instanceof ToolGroupComponent && lastChild.toolGroup === toolGroup) {
|
|
1535
1548
|
lastChild.addTool(component);
|
|
1549
|
+
this.trimLiveTuiHistory();
|
|
1536
1550
|
return;
|
|
1537
1551
|
}
|
|
1538
1552
|
if (lastChild instanceof ToolExecutionComponent && lastChild.toolGroup?.trim() === toolGroup) {
|
|
1539
1553
|
const group = new ToolGroupComponent(toolGroup, [lastChild, component]);
|
|
1540
1554
|
group.setExpanded(this.toolOutputExpanded);
|
|
1541
1555
|
children[children.length - 1] = group;
|
|
1556
|
+
this.trimLiveTuiHistory();
|
|
1542
1557
|
return;
|
|
1543
1558
|
}
|
|
1544
1559
|
this.chatContainer.addChild(component);
|
|
1560
|
+
this.trimLiveTuiHistory();
|
|
1545
1561
|
}
|
|
1546
1562
|
detachToolExecutionComponent(component) {
|
|
1547
1563
|
const children = this.chatContainer.children;
|
|
@@ -2286,7 +2302,7 @@ export class InteractiveMode {
|
|
|
2286
2302
|
// Global debug handler on TUI (works regardless of focus)
|
|
2287
2303
|
this.ui.onDebug = () => this.handleDebugCommand();
|
|
2288
2304
|
this.defaultEditor.onAction("app.model.select", () => void this.showModelSelector());
|
|
2289
|
-
this.defaultEditor.onAction("app.tools.expand", () => this.
|
|
2305
|
+
this.defaultEditor.onAction("app.tools.expand", () => this.loadTuiHistoryOnDemand());
|
|
2290
2306
|
this.defaultEditor.onAction("app.thinking.toggle", () => void this.toggleThinkingBlockVisibility());
|
|
2291
2307
|
this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
|
|
2292
2308
|
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
@@ -2623,31 +2639,18 @@ export class InteractiveMode {
|
|
|
2623
2639
|
this.ui.requestRender();
|
|
2624
2640
|
}
|
|
2625
2641
|
else if (event.message.role === "assistant") {
|
|
2642
|
+
this.clearPendingStreamingUiUpdate();
|
|
2643
|
+
this.lastStreamingUiUpdateAt = 0;
|
|
2626
2644
|
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
2627
2645
|
this.streamingMessage = event.message;
|
|
2628
2646
|
this.chatContainer.addChild(this.streamingComponent);
|
|
2629
|
-
this.
|
|
2630
|
-
this.
|
|
2647
|
+
this.applyStreamingMessageUpdate(this.streamingMessage, { force: true });
|
|
2648
|
+
this.trimLiveTuiHistory();
|
|
2631
2649
|
}
|
|
2632
2650
|
break;
|
|
2633
2651
|
case "message_update":
|
|
2634
2652
|
if (this.streamingComponent && event.message.role === "assistant") {
|
|
2635
|
-
this.
|
|
2636
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2637
|
-
for (const content of this.streamingMessage.content) {
|
|
2638
|
-
if (content.type === "toolCall") {
|
|
2639
|
-
if (!this.toolPanels.hasActive(content.id)) {
|
|
2640
|
-
this.attachToolExecutionComponent(content.name, content.id, content.arguments);
|
|
2641
|
-
}
|
|
2642
|
-
else {
|
|
2643
|
-
const component = this.toolPanels.getActive(content.id);
|
|
2644
|
-
if (component) {
|
|
2645
|
-
component.updateArgs(content.arguments);
|
|
2646
|
-
}
|
|
2647
|
-
}
|
|
2648
|
-
}
|
|
2649
|
-
}
|
|
2650
|
-
this.ui.requestRender();
|
|
2653
|
+
this.applyStreamingMessageUpdate(event.message);
|
|
2651
2654
|
}
|
|
2652
2655
|
break;
|
|
2653
2656
|
case "message_end":
|
|
@@ -2664,7 +2667,7 @@ export class InteractiveMode {
|
|
|
2664
2667
|
: "Operation aborted";
|
|
2665
2668
|
this.streamingMessage.errorMessage = errorMessage;
|
|
2666
2669
|
}
|
|
2667
|
-
this.
|
|
2670
|
+
this.applyStreamingMessageUpdate(this.streamingMessage, { force: true });
|
|
2668
2671
|
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
2669
2672
|
if (!errorMessage) {
|
|
2670
2673
|
errorMessage = this.streamingMessage.errorMessage || "Error";
|
|
@@ -2777,7 +2780,6 @@ export class InteractiveMode {
|
|
|
2777
2780
|
}
|
|
2778
2781
|
}
|
|
2779
2782
|
else if (event.result) {
|
|
2780
|
-
this.chatContainer.clear();
|
|
2781
2783
|
await this.rebuildChatFromMessages();
|
|
2782
2784
|
this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString()));
|
|
2783
2785
|
this.footer.invalidate();
|
|
@@ -2849,19 +2851,139 @@ export class InteractiveMode {
|
|
|
2849
2851
|
: message.content.filter((c) => c.type === "text");
|
|
2850
2852
|
return textBlocks.map((c) => c.text).join("");
|
|
2851
2853
|
}
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2854
|
+
resetLiveTuiHistoryTrim() {
|
|
2855
|
+
this.liveHistoryHiddenNotice = undefined;
|
|
2856
|
+
this.liveHistoryHiddenComponents = 0;
|
|
2857
|
+
}
|
|
2858
|
+
clearPendingStreamingUiUpdate() {
|
|
2859
|
+
if (!this.streamingUiUpdateTimer)
|
|
2860
|
+
return;
|
|
2861
|
+
clearTimeout(this.streamingUiUpdateTimer);
|
|
2862
|
+
this.streamingUiUpdateTimer = undefined;
|
|
2863
|
+
}
|
|
2864
|
+
getSessionEntryCount() {
|
|
2865
|
+
const manager = this.sessionManager;
|
|
2866
|
+
return manager.getEntryCount?.() ?? manager.getEntries().length;
|
|
2867
|
+
}
|
|
2868
|
+
showDeferredHistoryPlaceholder(options = {}) {
|
|
2869
|
+
this.chatContainer.children = [];
|
|
2870
|
+
this.resetLiveTuiHistoryTrim();
|
|
2871
|
+
this.clearRenderedToolPanelState();
|
|
2872
|
+
const entryCount = this.getSessionEntryCount();
|
|
2873
|
+
if (entryCount > 0) {
|
|
2874
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", `History hidden for typing performance (${entryCount} entries). Press ${keyText("app.tools.expand")} to load session history on demand.`), 1, 0));
|
|
2875
|
+
}
|
|
2876
|
+
if (options.requestRender ?? true)
|
|
2877
|
+
this.ui.requestRender();
|
|
2878
|
+
}
|
|
2879
|
+
loadTuiHistoryOnDemand() {
|
|
2880
|
+
if (this.tuiHistoryLoadInProgress)
|
|
2881
|
+
return;
|
|
2882
|
+
if (this.tuiHistoryLoaded || this.getSessionEntryCount() === 0) {
|
|
2883
|
+
this.toggleToolOutputExpansion();
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2886
|
+
this.tuiHistoryLoadInProgress = true;
|
|
2887
|
+
void (async () => {
|
|
2888
|
+
try {
|
|
2889
|
+
await this.renderInitialMessages({ forceHistoryLoad: true });
|
|
2890
|
+
}
|
|
2891
|
+
catch (error) {
|
|
2892
|
+
this.showError(`Failed to load TUI history: ${error instanceof Error ? error.message : String(error)}`);
|
|
2893
|
+
}
|
|
2894
|
+
finally {
|
|
2895
|
+
this.tuiHistoryLoadInProgress = false;
|
|
2896
|
+
}
|
|
2897
|
+
})();
|
|
2898
|
+
}
|
|
2899
|
+
attachStreamingToolPanels(message) {
|
|
2900
|
+
for (const content of message.content) {
|
|
2901
|
+
if (content.type !== "toolCall")
|
|
2902
|
+
continue;
|
|
2903
|
+
if (!this.toolPanels.hasActive(content.id)) {
|
|
2904
|
+
this.attachToolExecutionComponent(content.name, content.id, content.arguments);
|
|
2905
|
+
}
|
|
2906
|
+
else {
|
|
2907
|
+
const component = this.toolPanels.getActive(content.id);
|
|
2908
|
+
if (component) {
|
|
2909
|
+
component.updateArgs(content.arguments);
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
applyStreamingMessageUpdate(message, options = {}) {
|
|
2915
|
+
this.streamingMessage = message;
|
|
2916
|
+
if (!this.streamingComponent)
|
|
2917
|
+
return;
|
|
2918
|
+
const now = performance.now();
|
|
2919
|
+
const elapsed = now - this.lastStreamingUiUpdateAt;
|
|
2920
|
+
const hasToolCall = message.content.some((content) => content.type === "toolCall");
|
|
2921
|
+
const shouldUpdateNow = options.force || hasToolCall || elapsed >= STREAMING_UI_UPDATE_INTERVAL_MS;
|
|
2922
|
+
const update = () => {
|
|
2923
|
+
if (!this.streamingComponent || !this.streamingMessage)
|
|
2924
|
+
return;
|
|
2925
|
+
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2926
|
+
this.attachStreamingToolPanels(this.streamingMessage);
|
|
2927
|
+
this.lastStreamingUiUpdateAt = performance.now();
|
|
2928
|
+
this.ui.requestRender();
|
|
2929
|
+
};
|
|
2930
|
+
if (shouldUpdateNow) {
|
|
2931
|
+
this.clearPendingStreamingUiUpdate();
|
|
2932
|
+
update();
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2935
|
+
if (this.streamingUiUpdateTimer)
|
|
2936
|
+
return;
|
|
2937
|
+
this.streamingUiUpdateTimer = setTimeout(() => {
|
|
2938
|
+
this.streamingUiUpdateTimer = undefined;
|
|
2939
|
+
update();
|
|
2940
|
+
}, Math.max(0, STREAMING_UI_UPDATE_INTERVAL_MS - elapsed));
|
|
2941
|
+
}
|
|
2942
|
+
trimLiveTuiHistory() {
|
|
2943
|
+
const children = this.chatContainer.children;
|
|
2944
|
+
if (children.length <= TUI_LIVE_HISTORY_MAX_COMPONENTS)
|
|
2945
|
+
return;
|
|
2946
|
+
let protectedStart = children.length;
|
|
2947
|
+
const protect = (component) => {
|
|
2948
|
+
if (!component)
|
|
2949
|
+
return;
|
|
2950
|
+
const index = children.indexOf(component);
|
|
2951
|
+
if (index !== -1 && index < protectedStart)
|
|
2952
|
+
protectedStart = index;
|
|
2953
|
+
};
|
|
2954
|
+
protect(this.streamingComponent);
|
|
2955
|
+
protect(this.lastStatusSpacer);
|
|
2956
|
+
protect(this.lastStatusText);
|
|
2957
|
+
for (const [, component] of this.toolPanels.activeEntries()) {
|
|
2958
|
+
protect(component);
|
|
2959
|
+
}
|
|
2960
|
+
const trimStart = children[0] === this.liveHistoryHiddenNotice ? 1 : 0;
|
|
2961
|
+
const targetTrimEnd = children.length - TUI_LIVE_HISTORY_TRIM_TO_COMPONENTS;
|
|
2962
|
+
const trimEnd = Math.min(targetTrimEnd, protectedStart);
|
|
2963
|
+
if (trimEnd <= trimStart)
|
|
2964
|
+
return;
|
|
2965
|
+
const removed = children.splice(trimStart, trimEnd - trimStart);
|
|
2966
|
+
this.liveHistoryHiddenComponents += removed.length;
|
|
2967
|
+
if (removed.includes(this.lastStatusSpacer))
|
|
2968
|
+
this.lastStatusSpacer = undefined;
|
|
2969
|
+
if (removed.includes(this.lastStatusText))
|
|
2970
|
+
this.lastStatusText = undefined;
|
|
2971
|
+
const noticeText = theme.fg("dim", `Older TUI history hidden to preserve FPS (${this.liveHistoryHiddenComponents} components). Full session remains available to the model.`);
|
|
2972
|
+
if (children[0] === this.liveHistoryHiddenNotice) {
|
|
2973
|
+
this.liveHistoryHiddenNotice?.setText(noticeText);
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2976
|
+
this.liveHistoryHiddenNotice = new Text(noticeText, 1, 0);
|
|
2977
|
+
children.unshift(this.liveHistoryHiddenNotice);
|
|
2978
|
+
}
|
|
2979
|
+
appendStatusToChat(message, options = {}) {
|
|
2859
2980
|
const children = this.chatContainer.children;
|
|
2860
2981
|
const last = children.length > 0 ? children[children.length - 1] : undefined;
|
|
2861
2982
|
const secondLast = children.length > 1 ? children[children.length - 2] : undefined;
|
|
2862
2983
|
if (last && secondLast && last === this.lastStatusText && secondLast === this.lastStatusSpacer) {
|
|
2863
2984
|
this.lastStatusText.setText(theme.fg("dim", message));
|
|
2864
|
-
|
|
2985
|
+
if (options.requestRender ?? true)
|
|
2986
|
+
this.ui.requestRender();
|
|
2865
2987
|
return;
|
|
2866
2988
|
}
|
|
2867
2989
|
const spacer = new Spacer(1);
|
|
@@ -2870,7 +2992,18 @@ export class InteractiveMode {
|
|
|
2870
2992
|
this.chatContainer.addChild(text);
|
|
2871
2993
|
this.lastStatusSpacer = spacer;
|
|
2872
2994
|
this.lastStatusText = text;
|
|
2873
|
-
this.
|
|
2995
|
+
this.trimLiveTuiHistory();
|
|
2996
|
+
if (options.requestRender ?? true)
|
|
2997
|
+
this.ui.requestRender();
|
|
2998
|
+
}
|
|
2999
|
+
/**
|
|
3000
|
+
* Show a status message in the chat.
|
|
3001
|
+
*
|
|
3002
|
+
* If multiple status messages are emitted back-to-back (without anything else being added to the chat),
|
|
3003
|
+
* we update the previous status line instead of appending new ones to avoid log spam.
|
|
3004
|
+
*/
|
|
3005
|
+
showStatus(message) {
|
|
3006
|
+
this.appendStatusToChat(message);
|
|
2874
3007
|
}
|
|
2875
3008
|
addMessageToChat(message, options) {
|
|
2876
3009
|
switch (message.role) {
|
|
@@ -2947,6 +3080,117 @@ export class InteractiveMode {
|
|
|
2947
3080
|
const _exhaustive = message;
|
|
2948
3081
|
}
|
|
2949
3082
|
}
|
|
3083
|
+
this.trimLiveTuiHistory();
|
|
3084
|
+
}
|
|
3085
|
+
getContentText(content) {
|
|
3086
|
+
if (typeof content === "string")
|
|
3087
|
+
return content;
|
|
3088
|
+
if (Array.isArray(content)) {
|
|
3089
|
+
return content
|
|
3090
|
+
.map((part) => {
|
|
3091
|
+
const maybeText = part.text;
|
|
3092
|
+
return typeof maybeText === "string" ? maybeText : "";
|
|
3093
|
+
})
|
|
3094
|
+
.join("");
|
|
3095
|
+
}
|
|
3096
|
+
return "";
|
|
3097
|
+
}
|
|
3098
|
+
getTuiHistoryMessageText(message) {
|
|
3099
|
+
switch (message.role) {
|
|
3100
|
+
case "bashExecution":
|
|
3101
|
+
return [message.command, message.output ?? ""].filter(Boolean).join("\n");
|
|
3102
|
+
case "user":
|
|
3103
|
+
return this.getUserMessageText(message);
|
|
3104
|
+
case "assistant":
|
|
3105
|
+
return this.getContentText(message.content);
|
|
3106
|
+
case "toolResult":
|
|
3107
|
+
return this.getContentText(message.content);
|
|
3108
|
+
case "custom":
|
|
3109
|
+
return this.getContentText(message.content);
|
|
3110
|
+
case "compactionSummary":
|
|
3111
|
+
case "branchSummary":
|
|
3112
|
+
return message.summary;
|
|
3113
|
+
default: {
|
|
3114
|
+
const _exhaustive = message;
|
|
3115
|
+
return JSON.stringify(_exhaustive);
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
estimateTuiHistoryLines(message) {
|
|
3120
|
+
const text = this.getTuiHistoryMessageText(message);
|
|
3121
|
+
const hardLines = text.length > 0 ? text.split(/\r\n|\r|\n/).length : 1;
|
|
3122
|
+
const wrappedLines = Math.ceil(text.length / TUI_HISTORY_RELOAD_WRAP_WIDTH);
|
|
3123
|
+
// Add one line for role/tool chrome or spacing. Tool-call-only assistant messages
|
|
3124
|
+
// have little text but still render a component.
|
|
3125
|
+
return Math.max(1, hardLines, wrappedLines) + 1;
|
|
3126
|
+
}
|
|
3127
|
+
trimTextToTuiHistoryTail(text, maxEstimatedLines) {
|
|
3128
|
+
const maxLines = Math.max(1, maxEstimatedLines);
|
|
3129
|
+
const lines = text.split(/\r\n|\r|\n/);
|
|
3130
|
+
if (lines.length > maxLines) {
|
|
3131
|
+
const omitted = lines.length - maxLines;
|
|
3132
|
+
return `[Earlier ${omitted} line${omitted === 1 ? "" : "s"} omitted from TUI reload history; full session remains available to the model.]\n${lines.slice(-maxLines).join("\n")}`;
|
|
3133
|
+
}
|
|
3134
|
+
const maxChars = Math.max(TUI_HISTORY_RELOAD_WRAP_WIDTH, maxLines * TUI_HISTORY_RELOAD_WRAP_WIDTH);
|
|
3135
|
+
if (text.length > maxChars) {
|
|
3136
|
+
const omitted = text.length - maxChars;
|
|
3137
|
+
return `[Earlier ${omitted} character${omitted === 1 ? "" : "s"} omitted from TUI reload history; full session remains available to the model.]\n${text.slice(-maxChars)}`;
|
|
3138
|
+
}
|
|
3139
|
+
return text;
|
|
3140
|
+
}
|
|
3141
|
+
trimMessageToTuiHistoryTail(message, maxEstimatedLines) {
|
|
3142
|
+
const text = this.getTuiHistoryMessageText(message);
|
|
3143
|
+
const trimmedText = this.trimTextToTuiHistoryTail(text, maxEstimatedLines);
|
|
3144
|
+
if (trimmedText === text)
|
|
3145
|
+
return message;
|
|
3146
|
+
const clone = JSON.parse(JSON.stringify(message));
|
|
3147
|
+
const mutable = clone;
|
|
3148
|
+
if (mutable.role === "bashExecution" && typeof mutable.output === "string") {
|
|
3149
|
+
mutable.output = trimmedText;
|
|
3150
|
+
}
|
|
3151
|
+
else if (mutable.role === "compactionSummary" || mutable.role === "branchSummary") {
|
|
3152
|
+
mutable.summary = trimmedText;
|
|
3153
|
+
}
|
|
3154
|
+
else if (typeof mutable.content === "string") {
|
|
3155
|
+
mutable.content = trimmedText;
|
|
3156
|
+
}
|
|
3157
|
+
else {
|
|
3158
|
+
mutable.content = [{ type: "text", text: trimmedText }];
|
|
3159
|
+
}
|
|
3160
|
+
return clone;
|
|
3161
|
+
}
|
|
3162
|
+
messagesForTuiHistoryReload(messages) {
|
|
3163
|
+
let estimatedLines = 0;
|
|
3164
|
+
let start = messages.length;
|
|
3165
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3166
|
+
const nextLines = this.estimateTuiHistoryLines(messages[i]);
|
|
3167
|
+
if (start < messages.length && estimatedLines + nextLines > TUI_HISTORY_RELOAD_MAX_LINES)
|
|
3168
|
+
break;
|
|
3169
|
+
estimatedLines += nextLines;
|
|
3170
|
+
start = i;
|
|
3171
|
+
if (estimatedLines >= TUI_HISTORY_RELOAD_MAX_LINES)
|
|
3172
|
+
break;
|
|
3173
|
+
}
|
|
3174
|
+
const selected = messages.slice(start);
|
|
3175
|
+
if (selected.length > 0 && estimatedLines > TUI_HISTORY_RELOAD_MAX_LINES) {
|
|
3176
|
+
const tailLines = selected.slice(1).reduce((sum, message) => sum + this.estimateTuiHistoryLines(message), 0);
|
|
3177
|
+
const firstAllowance = TUI_HISTORY_RELOAD_MAX_LINES - tailLines;
|
|
3178
|
+
if (firstAllowance <= 4) {
|
|
3179
|
+
selected.shift();
|
|
3180
|
+
start += 1;
|
|
3181
|
+
estimatedLines = tailLines;
|
|
3182
|
+
}
|
|
3183
|
+
else {
|
|
3184
|
+
// Reserve room for truncation marker, role chrome, and wrap variance.
|
|
3185
|
+
selected[0] = this.trimMessageToTuiHistoryTail(selected[0], firstAllowance - 4);
|
|
3186
|
+
estimatedLines = tailLines + this.estimateTuiHistoryLines(selected[0]);
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
return {
|
|
3190
|
+
messages: selected,
|
|
3191
|
+
omittedMessages: start,
|
|
3192
|
+
estimatedLines,
|
|
3193
|
+
};
|
|
2950
3194
|
}
|
|
2951
3195
|
/**
|
|
2952
3196
|
* Render session context to chat. Used for initial load and rebuild after compaction.
|
|
@@ -2956,77 +3200,123 @@ export class InteractiveMode {
|
|
|
2956
3200
|
*/
|
|
2957
3201
|
renderGeneration = 0;
|
|
2958
3202
|
async renderSessionContext(sessionContext, options = {}) {
|
|
2959
|
-
//
|
|
2960
|
-
//
|
|
2961
|
-
//
|
|
3203
|
+
// Build long history offscreen, then atomically swap it into the visible
|
|
3204
|
+
// chat container. This keeps the TUI responsive without flashing blank or
|
|
3205
|
+
// partial transcript frames during resume/reload/compaction rebuilds.
|
|
2962
3206
|
const generation = ++this.renderGeneration;
|
|
2963
|
-
const CHUNK_SIZE = 20;
|
|
2964
3207
|
let processed = 0;
|
|
3208
|
+
let committed = false;
|
|
3209
|
+
const visibleChatContainer = this.chatContainer;
|
|
3210
|
+
const previousLiveHistoryHiddenNotice = this.liveHistoryHiddenNotice;
|
|
3211
|
+
const previousLiveHistoryHiddenComponents = this.liveHistoryHiddenComponents;
|
|
3212
|
+
const previousLastStatusSpacer = this.lastStatusSpacer;
|
|
3213
|
+
const previousLastStatusText = this.lastStatusText;
|
|
3214
|
+
const stagingChatContainer = new Container();
|
|
3215
|
+
this.chatContainer = stagingChatContainer;
|
|
3216
|
+
this.resetLiveTuiHistoryTrim();
|
|
2965
3217
|
this.clearRenderedToolPanelState();
|
|
2966
3218
|
const renderedPendingTools = new Map();
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
for (const message of sessionContext.messages) {
|
|
2972
|
-
if (processed > 0 && processed % CHUNK_SIZE === 0) {
|
|
2973
|
-
this.ui.requestRender();
|
|
2974
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
2975
|
-
if (generation !== this.renderGeneration)
|
|
2976
|
-
return;
|
|
3219
|
+
try {
|
|
3220
|
+
if (options.updateFooter) {
|
|
3221
|
+
this.footer.invalidate();
|
|
3222
|
+
this.updateEditorBorderColor();
|
|
2977
3223
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
3224
|
+
const tuiHistory = this.messagesForTuiHistoryReload(sessionContext.messages);
|
|
3225
|
+
if (tuiHistory.omittedMessages > 0) {
|
|
3226
|
+
this.appendStatusToChat(`Showing last ~${TUI_HISTORY_RELOAD_MAX_LINES} TUI history lines; omitted ${tuiHistory.omittedMessages} older message${tuiHistory.omittedMessages === 1 ? "" : "s"}. Full session remains available to the model.`, { requestRender: false });
|
|
3227
|
+
}
|
|
3228
|
+
for (const message of tuiHistory.messages) {
|
|
3229
|
+
if (processed > 0 && processed % TUI_HISTORY_RELOAD_CHUNK_SIZE === 0) {
|
|
3230
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
3231
|
+
if (generation !== this.renderGeneration)
|
|
3232
|
+
return;
|
|
3233
|
+
}
|
|
3234
|
+
processed++;
|
|
3235
|
+
// Assistant messages need special handling for tool calls
|
|
3236
|
+
if (message.role === "assistant") {
|
|
3237
|
+
this.addMessageToChat(message);
|
|
3238
|
+
// Render tool call components
|
|
3239
|
+
for (const content of message.content) {
|
|
3240
|
+
if (content.type === "toolCall") {
|
|
3241
|
+
const component = this.attachToolExecutionComponent(content.name, content.id, content.arguments);
|
|
3242
|
+
if (message.stopReason === "aborted" || message.stopReason === "error") {
|
|
3243
|
+
let errorMessage;
|
|
3244
|
+
if (message.stopReason === "aborted") {
|
|
3245
|
+
const retryAttempt = this.session.retryAttempt;
|
|
3246
|
+
errorMessage =
|
|
3247
|
+
retryAttempt > 0
|
|
3248
|
+
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
3249
|
+
: "Operation aborted";
|
|
3250
|
+
}
|
|
3251
|
+
else {
|
|
3252
|
+
errorMessage = message.errorMessage || "Error";
|
|
3253
|
+
}
|
|
3254
|
+
component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
|
|
3255
|
+
this.toolPanels.finish(content.id);
|
|
2994
3256
|
}
|
|
2995
3257
|
else {
|
|
2996
|
-
|
|
3258
|
+
renderedPendingTools.set(content.id, component);
|
|
2997
3259
|
}
|
|
2998
|
-
component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
|
|
2999
|
-
this.toolPanels.finish(content.id);
|
|
3000
|
-
}
|
|
3001
|
-
else {
|
|
3002
|
-
renderedPendingTools.set(content.id, component);
|
|
3003
3260
|
}
|
|
3004
3261
|
}
|
|
3005
3262
|
}
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3263
|
+
else if (message.role === "toolResult") {
|
|
3264
|
+
// Match tool results to pending tool components
|
|
3265
|
+
const component = renderedPendingTools.get(message.toolCallId);
|
|
3266
|
+
if (component) {
|
|
3267
|
+
component.updateResult(message);
|
|
3268
|
+
renderedPendingTools.delete(message.toolCallId);
|
|
3269
|
+
this.toolPanels.finish(message.toolCallId);
|
|
3270
|
+
}
|
|
3014
3271
|
}
|
|
3272
|
+
else {
|
|
3273
|
+
// All other messages use standard rendering
|
|
3274
|
+
this.addMessageToChat(message, options);
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
if (generation !== this.renderGeneration)
|
|
3278
|
+
return;
|
|
3279
|
+
visibleChatContainer.children = stagingChatContainer.children;
|
|
3280
|
+
committed = true;
|
|
3281
|
+
}
|
|
3282
|
+
finally {
|
|
3283
|
+
const stagedLiveHistoryHiddenNotice = this.liveHistoryHiddenNotice;
|
|
3284
|
+
const stagedLiveHistoryHiddenComponents = this.liveHistoryHiddenComponents;
|
|
3285
|
+
const stagedLastStatusSpacer = this.lastStatusSpacer;
|
|
3286
|
+
const stagedLastStatusText = this.lastStatusText;
|
|
3287
|
+
this.chatContainer = visibleChatContainer;
|
|
3288
|
+
if (committed) {
|
|
3289
|
+
this.liveHistoryHiddenNotice = stagedLiveHistoryHiddenNotice;
|
|
3290
|
+
this.liveHistoryHiddenComponents = stagedLiveHistoryHiddenComponents;
|
|
3291
|
+
this.lastStatusSpacer = stagedLastStatusSpacer;
|
|
3292
|
+
this.lastStatusText = stagedLastStatusText;
|
|
3015
3293
|
}
|
|
3016
3294
|
else {
|
|
3017
|
-
|
|
3018
|
-
this.
|
|
3295
|
+
this.liveHistoryHiddenNotice = previousLiveHistoryHiddenNotice;
|
|
3296
|
+
this.liveHistoryHiddenComponents = previousLiveHistoryHiddenComponents;
|
|
3297
|
+
this.lastStatusSpacer = previousLastStatusSpacer;
|
|
3298
|
+
this.lastStatusText = previousLastStatusText;
|
|
3019
3299
|
}
|
|
3020
3300
|
}
|
|
3021
|
-
|
|
3301
|
+
if (committed)
|
|
3302
|
+
this.ui.requestRender();
|
|
3022
3303
|
}
|
|
3023
|
-
async renderInitialMessages() {
|
|
3024
|
-
|
|
3304
|
+
async renderInitialMessages(options = {}) {
|
|
3305
|
+
if (!options.forceHistoryLoad) {
|
|
3306
|
+
this.tuiHistoryLoaded = false;
|
|
3307
|
+
this.showDeferredHistoryPlaceholder({ requestRender: true });
|
|
3308
|
+
this.footer.invalidate();
|
|
3309
|
+
this.updateEditorBorderColor();
|
|
3310
|
+
return;
|
|
3311
|
+
}
|
|
3312
|
+
// Get aligned messages and entries from session context only when the user
|
|
3313
|
+
// explicitly requests TUI history. The model/session state is already loaded.
|
|
3025
3314
|
const context = this.sessionManager.buildSessionContext();
|
|
3026
3315
|
await this.renderSessionContext(context, {
|
|
3027
3316
|
updateFooter: true,
|
|
3028
3317
|
populateHistory: true,
|
|
3029
3318
|
});
|
|
3319
|
+
this.tuiHistoryLoaded = true;
|
|
3030
3320
|
// Show compaction info if session was compacted
|
|
3031
3321
|
const allEntries = this.sessionManager.getEntries();
|
|
3032
3322
|
const compactionCount = allEntries.filter((e) => e.type === "compaction").length;
|
|
@@ -3048,7 +3338,10 @@ export class InteractiveMode {
|
|
|
3048
3338
|
});
|
|
3049
3339
|
}
|
|
3050
3340
|
async rebuildChatFromMessages() {
|
|
3051
|
-
this.
|
|
3341
|
+
if (!this.tuiHistoryLoaded) {
|
|
3342
|
+
this.showDeferredHistoryPlaceholder({ requestRender: true });
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3052
3345
|
const context = this.sessionManager.buildSessionContext();
|
|
3053
3346
|
await this.renderSessionContext(context);
|
|
3054
3347
|
}
|
|
@@ -3330,7 +3623,6 @@ export class InteractiveMode {
|
|
|
3330
3623
|
this.hideThinkingBlock = !this.hideThinkingBlock;
|
|
3331
3624
|
this.settingsManager.setHideThinkingBlock(this.hideThinkingBlock);
|
|
3332
3625
|
// Rebuild chat from session messages
|
|
3333
|
-
this.chatContainer.clear();
|
|
3334
3626
|
await this.rebuildChatFromMessages();
|
|
3335
3627
|
// If streaming, re-add the streaming component with updated visibility and re-render
|
|
3336
3628
|
if (this.streamingComponent && this.streamingMessage) {
|
|
@@ -4020,16 +4312,16 @@ export class InteractiveMode {
|
|
|
4020
4312
|
? `\n\nLatest completed turn digest (bounded; use only as current-session evidence, not as longitudinal proof):\n<turn_digest>\n${options.turnDigest}\n</turn_digest>`
|
|
4021
4313
|
: "";
|
|
4022
4314
|
const objective = options.kind === "reflection"
|
|
4023
|
-
? "review the latest completed turn for durable memory, skill, validation, and
|
|
4315
|
+
? "review the latest completed turn for durable memory, skill, validation, tooling, and code-baked self-improvement cues, then run one bounded continuous-learning pass if the learning tools are available"
|
|
4024
4316
|
: "run one bounded continuous-learning pass for this Pi tenant";
|
|
4025
|
-
return `You are Pi Auto Learn running as a background learner.\n\nObjective: ${objective}.\nTrigger: ${reason}.\n\n${authorityBlock}\n\nRequired workflow:\n1. Query existing durable memory/rules first when tools allow it. Memory confrontation is mandatory before accepting, merging, upgrading, or rejecting learning candidates.\n2. Run the available Auto Learn tooling, preferably learning_run_auto, with applyHighConfidence=${settings.applyHighConfidence}. Process candidate validation in vectorized chunks/batches; avoid scalar per-candidate memory queries except for final selected writes.\n3. Apply the learning validation tree to each candidate chunk: (a) Why is this good for the user? (b) Is it unique, or similar to existing memory/skills/agents so it should merge or upgrade existing knowledge? (c) Will this make Pi a better agent? Candidates that cannot answer all three are noise.\n4. Treat the latest-turn digest as current-session evidence only; do not auto-commit one-off cues unless deterministic tooling and memory confrontation corroborate them.\
|
|
4317
|
+
return `You are Pi Auto Learn running as a background learner.\n\nObjective: ${objective}.\nTrigger: ${reason}.\n\n${authorityBlock}\n\nRequired workflow:\n1. Query existing durable memory/rules first when tools allow it. Memory confrontation is mandatory before accepting, merging, upgrading, or rejecting learning candidates.\n2. Run the available Auto Learn tooling, preferably learning_run_auto, with applyHighConfidence=${settings.applyHighConfidence}. Process candidate validation in vectorized chunks/batches; avoid scalar per-candidate memory queries except for final selected writes.\n3. Apply the learning validation tree to each candidate chunk: (a) Why is this good for the user? (b) Is it unique, or similar to existing memory/skills/agents so it should merge or upgrade existing knowledge? (c) Will this make Pi a better agent? Candidates that cannot answer all three are noise.\n4. Hermes-style learning cycle: after a complex task (${AUTO_LEARN_COMPLEX_TASK_TOOL_CALLS}+ tool calls), user correction, repeated steering pattern, non-trivial fix/workaround/debugging path, loaded-skill defect, trigger gap, tool gap, or harness workflow defect, actively create or update durable learning artifacts. Memory stores compact facts/preferences/state; skills/prompts/agents/extensions/source store procedural behavior. When a lesson changes how Pi should act on a future class of task, memory alone is not completion.\n5. Skill update preference order: (1) patch the currently loaded or consulted skill that governed the task; (2) patch an existing class-level umbrella skill/agent/prompt; (3) add a support file under references/, templates/, or scripts/ and add a SKILL.md pointer; (4) create a new class-level umbrella skill only when no existing artifact fits. Never create one-off PR/error/codename/session skills.\n6. Behavioral self-improvement is code-baked by default: prefer the lowest durable executable layer that fixes the behavior — patch an existing skill/prompt/agent/extension/tool, tune an approved setting, or edit the authorized Pi source when source authority is available. Use Automata only for concise facts/evidence pointers that support the baked change.\n7. Do not harden transient or environment-dependent failures into durable behavior: missing binaries, fresh-install package gaps, credentials not configured, path mismatches, one-off task narratives, or negative tool-broken claims should become setup/troubleshooting fixes only when the fix itself is reusable.\n8. Treat the latest-turn digest as current-session evidence only; do not auto-commit one-off cues unless deterministic tooling and memory confrontation corroborate them.\n9. In mode=full, apply safe memory/skill/user-extension/authorized-source improvements under the standing grant above; otherwise keep them proposal-gated.\n10. Never cross hard-stop boundaries from the authority policy.\n11. If the learning tools are unavailable, report BLOCKED with the missing tool names and do not improvise.\n12. Finish with PASS, BLOCKED, or FAIL and concise evidence, including chunk counts, merge/upgrade/code-bake decisions, changed paths/settings, validation, and cleanup/purge status.${reflectionBlock}`;
|
|
4026
4318
|
}
|
|
4027
4319
|
reserveAutoLearnRun(params) {
|
|
4028
4320
|
return this.withAutoLearnStateLock((current) => {
|
|
4029
4321
|
const now = Date.now();
|
|
4030
4322
|
const state = this.pruneAutoLearnHistoryFromState(current, now);
|
|
4031
4323
|
const tenant = this.getAutoLearnTenantKey();
|
|
4032
|
-
if (params.cooldownKind === "reflection") {
|
|
4324
|
+
if (params.cooldownKind === "reflection" && !params.bypassReflectionCooldown) {
|
|
4033
4325
|
const lastReflection = state.lastReflectionByTenant?.[tenant] ?? 0;
|
|
4034
4326
|
const cooldownMs = params.settings.reflectionCooldownMinutes * 60 * 1000;
|
|
4035
4327
|
if (Math.max(0, lastReflection + cooldownMs - now) > 0) {
|
|
@@ -4168,6 +4460,7 @@ export class InteractiveMode {
|
|
|
4168
4460
|
settings,
|
|
4169
4461
|
force,
|
|
4170
4462
|
cooldownKind: options.cooldownKind,
|
|
4463
|
+
bypassReflectionCooldown: options.bypassReflectionCooldown,
|
|
4171
4464
|
runId,
|
|
4172
4465
|
modelPattern,
|
|
4173
4466
|
reason,
|
|
@@ -4332,6 +4625,10 @@ export class InteractiveMode {
|
|
|
4332
4625
|
.map((message) => this.getAgentMessagePlainText(message))
|
|
4333
4626
|
.join("\n");
|
|
4334
4627
|
const correctionSignal = /\b(next time|for future|from now on|remember this|don't|do not|avoid|instead|you should|should have|you forgot|you missed|not what i asked|wrong again)\b/i.test(userText);
|
|
4628
|
+
const behavioralSelfImprovementSignal = /\b(harness|pi|agent|autonomy|autonomous|self[- ]?improv(?:e|ement|ing)?|steer(?:ing)?|trigger(?:s)?|skill(?:s)?|code[- ]?bak(?:e|ed)|bake(?:d)? into code|not (?:automata|memory)|reference agent|hermes)\b/i.test(userText) &&
|
|
4629
|
+
/\b(improve|automatic(?:ally)?|autonomous|trigger|fire|skill|steer|self[- ]?improv(?:e|ement|ing)?|code[- ]?bak(?:e|ed)|bake(?:d)?|too much|less)\b/i.test(userText);
|
|
4630
|
+
const complexTaskSignal = toolCalls >= AUTO_LEARN_COMPLEX_TASK_TOOL_CALLS;
|
|
4631
|
+
const bypassCooldown = correctionSignal || behavioralSelfImprovementSignal || complexTaskSignal;
|
|
4335
4632
|
const base = { messageCount, contextPercent, cooldownRemainingMs, runningCount, toolCalls };
|
|
4336
4633
|
if (!settings.enabled)
|
|
4337
4634
|
return { ...base, shouldRun: false, reason: "disabled" };
|
|
@@ -4344,14 +4641,34 @@ export class InteractiveMode {
|
|
|
4344
4641
|
reason: `max tenant learners running (${runningCount}/${settings.maxConcurrentLearners})`,
|
|
4345
4642
|
};
|
|
4346
4643
|
}
|
|
4347
|
-
if (cooldownRemainingMs > 0)
|
|
4644
|
+
if (cooldownRemainingMs > 0 && !bypassCooldown) {
|
|
4348
4645
|
return { ...base, shouldRun: false, reason: "reflection cooldown" };
|
|
4646
|
+
}
|
|
4647
|
+
if (behavioralSelfImprovementSignal) {
|
|
4648
|
+
return {
|
|
4649
|
+
...base,
|
|
4650
|
+
shouldRun: true,
|
|
4651
|
+
reason: "reflection behavioral self-improvement signal",
|
|
4652
|
+
digest: this.buildAutonomyReviewDigest(messages),
|
|
4653
|
+
bypassCooldown: true,
|
|
4654
|
+
};
|
|
4655
|
+
}
|
|
4349
4656
|
if (correctionSignal) {
|
|
4350
4657
|
return {
|
|
4351
4658
|
...base,
|
|
4352
4659
|
shouldRun: true,
|
|
4353
4660
|
reason: "reflection correction signal",
|
|
4354
4661
|
digest: this.buildAutonomyReviewDigest(messages),
|
|
4662
|
+
bypassCooldown: true,
|
|
4663
|
+
};
|
|
4664
|
+
}
|
|
4665
|
+
if (complexTaskSignal) {
|
|
4666
|
+
return {
|
|
4667
|
+
...base,
|
|
4668
|
+
shouldRun: true,
|
|
4669
|
+
reason: `reflection complex task learning signal (${toolCalls}/${AUTO_LEARN_COMPLEX_TASK_TOOL_CALLS} tool calls)`,
|
|
4670
|
+
digest: this.buildAutonomyReviewDigest(messages),
|
|
4671
|
+
bypassCooldown: true,
|
|
4355
4672
|
};
|
|
4356
4673
|
}
|
|
4357
4674
|
if (autonomy.mode === "full") {
|
|
@@ -4395,6 +4712,7 @@ export class InteractiveMode {
|
|
|
4395
4712
|
cooldownKind: "reflection",
|
|
4396
4713
|
promptKind: "reflection",
|
|
4397
4714
|
turnDigest: decision.digest,
|
|
4715
|
+
bypassReflectionCooldown: decision.bypassCooldown,
|
|
4398
4716
|
});
|
|
4399
4717
|
if (!message.startsWith("Auto Learn started"))
|
|
4400
4718
|
this.showStatus(message);
|
|
@@ -4630,7 +4948,6 @@ export class InteractiveMode {
|
|
|
4630
4948
|
child.setHideThinkingBlock(hidden);
|
|
4631
4949
|
}
|
|
4632
4950
|
}
|
|
4633
|
-
this.chatContainer.clear();
|
|
4634
4951
|
void this.rebuildChatFromMessages();
|
|
4635
4952
|
},
|
|
4636
4953
|
onCollapseChangelogChange: (collapsed) => {
|
|
@@ -5010,7 +5327,6 @@ export class InteractiveMode {
|
|
|
5010
5327
|
return;
|
|
5011
5328
|
}
|
|
5012
5329
|
// Update UI
|
|
5013
|
-
this.chatContainer.clear();
|
|
5014
5330
|
await this.renderInitialMessages();
|
|
5015
5331
|
if (result.editorText && !this.editor.getText().trim()) {
|
|
5016
5332
|
this.editor.setText(result.editorText);
|