aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-23 21:43:45 +0200
committerAgando <roduccomm@gmail.com>2026-10-23 21:43:45 +0200
commit5b05af91fa3fcc9ae7aff00feedc26d062babbc2 (patch)
tree7060f31c71275ad64a22a8ccdab3260ea42d3208
parentd5b6e6f12c8c3c4551050f8fc7d19fb78d6b581b (diff)
feat(newos4): context window warns at 80% and says what trimming dropped (v4.6.91)
P0-5 asked what to do about a 422k-token session against a 400k threshold. There is no 400k threshold in this tree — the budgets are maxContextTokens (12000) and maxContext*2 messages — so rather than chase a number that does not exist, this applies the decision that is defensible either way: warn before the cliff, never lose history without saying so. - 80% of the token budget prints one warning per crossing (no per-turn nagging) - the message-count trim unshifts a marker naming how many messages left the window and quoting the earliest one, instead of splicing them away silently - 'max-context-tokens (default: 2000)' now reports the real code default, 12000 Verified: 900/1000 tokens -> warns once, second call silent; 1500/1000 -> compresses to 17 messages with the summary head intact; 9-message window with maxContext=2 -> 4 messages, marker '[context trim: 6 earlier message(s) ... earliest was: "turn 0 ..."]'.
-rw-r--r--help.html19
-rw-r--r--main.html6
-rw-r--r--modules/core/core-bundle.js29
-rw-r--r--modules/core/module-aiassist.js29
-rw-r--r--modules/core/module-loader.js2
5 files changed, 75 insertions, 10 deletions
diff --git a/help.html b/help.html
index a464d5f..4eec9ef 100644
--- a/help.html
+++ b/help.html
@@ -1689,6 +1689,25 @@ listmods</pre>
<h2>📋 Changelog</h2>
<div class="version-item">
+ <div class="v-header"><span class="v-num">v4.6.91 — October 2026</span>
+ <span class="v-date">context says what it dropped</span></div>
+ <ul>
+ <li><strong>The window warns before it forgets.</strong> At 80% of the token budget the
+ terminal says so once — <code>⚠️ Context 90% full (900/1000 tokens)</code> — instead of
+ only announcing the fall after compression. It does not repeat on every turn.</li>
+ <li><strong>Trimming leaves a marker.</strong> Old turns used to vanish silently, so a long
+ task "remembered" a conversation that never started that way. The window now opens with
+ <code>[context trim: 6 earlier message(s) left this window — the earliest was: "turn 0
+ original brief text…"]</code>.</li>
+ <li><strong>The help text stopped lying.</strong> <code>max-context-tokens</code> claimed a
+ default of 2000; the code has always used 12000.</li>
+ <li><strong>On the "400k / 422k" report:</strong> no such limit exists in this codebase —
+ the only budgets are <code>maxContextTokens</code> (12 000) and <code>maxContext × 2</code>
+ messages. That figure came from outside the app; nothing here was changed to chase it.</li>
+ </ul>
+ </div>
+
+ <div class="version-item">
<div class="v-header"><span class="v-num">v4.6.88–90 — October 2026</span>
<span class="v-date">one phantom entry, three bugs</span></div>
<ul>
diff --git a/main.html b/main.html
index 16b4d63..921c8a8 100644
--- a/main.html
+++ b/main.html
@@ -1349,13 +1349,13 @@
</script>
<!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix -->
-<script src="modules/core/core-bundle.js?v=v4.6.90"></script>
+<script src="modules/core/core-bundle.js?v=v4.6.91"></script>
<!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules -->
-<script src="modules/core/module-loader.js?v=v4.6.90"></script>
+<script src="modules/core/module-loader.js?v=v4.6.91"></script>
<!-- Critical ext bundle loads AFTER webx11 exists so registerModule calls work -->
-<script src="modules/ext/critical-ext-bundle.js?v=v4.6.90"></script>
+<script src="modules/ext/critical-ext-bundle.js?v=v4.6.91"></script>
<script>
// ═══ ONE TRUE VERSION SOURCE ═══
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index f72a20e..8e19189 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -9348,7 +9348,17 @@ buildSystemPrompt() {
// every later turn started blind and the model re-read the same files.
this._flushToolTrace(sess, _traceTag);
if (sess.context.length > this.maxContext * 2) {
- sess.context.splice(0, sess.context.length - this.maxContext * 2);
+ // Silently splicing the opening turns is how a long task forgets its own brief:
+ // the model then "remembers" a conversation that never started that way. Drop
+ // the same messages, but leave a marker saying how many and what the first was.
+ const cap = this.maxContext * 2;
+ const dropped = sess.context.splice(0, sess.context.length - (cap - 1));
+ const firstUser = dropped.find(m => m.role === 'user');
+ const note = '[context trim: ' + dropped.length + ' earlier message(s) left this window'
+ + (firstUser && firstUser.content ? ' — the earliest was: "' + String(firstUser.content).replace(/\s+/g, ' ').slice(0, 90) + '…"' : '')
+ + ']';
+ sess.context.unshift({ role: 'system', content: note });
+ this.terminal.print('✂️ Context trimmed: ' + dropped.length + ' message(s) — the model is told what it lost', 'text-muted');
}
// Reflection: after complex tasks, run health scan and auto-skill creation
if (loopResult.toolTurns >= 2 && !this._cancelled) {
@@ -12197,7 +12207,7 @@ buildSystemPrompt() {
this.terminal.print('=== AI Config ===', 'info');
this.terminal.print(`max-turns: ${this.MAX_TOOL_TURNS} (default: 8)`, '');
this.terminal.print(`max-context: ${this.maxContext} (default: 10)`, '');
- this.terminal.print(`max-context-tokens: ${this.maxContextTokens} (default: 2000)`, '');
+ this.terminal.print(`max-context-tokens: ${this.maxContextTokens} (code default: 12000)`, '');
this.terminal.print(`trivial-optimization: ${(this.terminal?.ollamaManager?.optimizationEnabled) ? 'on' : 'off (default)'}`, '');
this.terminal.print(`confirm-timeout: ${this._confirmTimeoutMs > 0 ? this._confirmTimeoutMs / 1000 + 's' : 'disabled (no auto-deny)'} (default: 120s)`, '');
this.terminal.print(`chat-lock-timeout: ${this._chatLockTimeoutMs / 1000}s (default: 10s)`, '');
@@ -12520,7 +12530,20 @@ buildSystemPrompt() {
async _compressContext() {
if (this.context.length <= 6) return;
const totalTokens = this._estimateContextTokens();
- if (totalTokens < this.maxContextTokens) return;
+ if (totalTokens < this.maxContextTokens) {
+ // Warn before the cliff rather than only reporting the fall. Once per crossing,
+ // so a long session is not nagged every turn.
+ const pct = Math.round((totalTokens / this.maxContextTokens) * 100);
+ if (pct >= 80 && !this._ctxWarned) {
+ this._ctxWarned = true;
+ this.terminal.print('⚠️ Context ' + pct + '% full (' + totalTokens + '/' + this.maxContextTokens +
+ ' tokens) — the next few turns will compress earlier history into a summary', 'warning');
+ } else if (pct < 60) {
+ this._ctxWarned = false;
+ }
+ return;
+ }
+ this._ctxWarned = false;
const keepCount = 8;
const oldMsgs = this.context.slice(0, -keepCount * 2);
if (oldMsgs.length < 2) return;
diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js
index 55dde6f..43cbb11 100644
--- a/modules/core/module-aiassist.js
+++ b/modules/core/module-aiassist.js
@@ -950,7 +950,17 @@ buildSystemPrompt() {
// every later turn started blind and the model re-read the same files.
this._flushToolTrace(sess, _traceTag);
if (sess.context.length > this.maxContext * 2) {
- sess.context.splice(0, sess.context.length - this.maxContext * 2);
+ // Silently splicing the opening turns is how a long task forgets its own brief:
+ // the model then "remembers" a conversation that never started that way. Drop
+ // the same messages, but leave a marker saying how many and what the first was.
+ const cap = this.maxContext * 2;
+ const dropped = sess.context.splice(0, sess.context.length - (cap - 1));
+ const firstUser = dropped.find(m => m.role === 'user');
+ const note = '[context trim: ' + dropped.length + ' earlier message(s) left this window'
+ + (firstUser && firstUser.content ? ' — the earliest was: "' + String(firstUser.content).replace(/\s+/g, ' ').slice(0, 90) + '…"' : '')
+ + ']';
+ sess.context.unshift({ role: 'system', content: note });
+ this.terminal.print('✂️ Context trimmed: ' + dropped.length + ' message(s) — the model is told what it lost', 'text-muted');
}
// Reflection: after complex tasks, run health scan and auto-skill creation
if (loopResult.toolTurns >= 2 && !this._cancelled) {
@@ -3799,7 +3809,7 @@ buildSystemPrompt() {
this.terminal.print('=== AI Config ===', 'info');
this.terminal.print(`max-turns: ${this.MAX_TOOL_TURNS} (default: 8)`, '');
this.terminal.print(`max-context: ${this.maxContext} (default: 10)`, '');
- this.terminal.print(`max-context-tokens: ${this.maxContextTokens} (default: 2000)`, '');
+ this.terminal.print(`max-context-tokens: ${this.maxContextTokens} (code default: 12000)`, '');
this.terminal.print(`trivial-optimization: ${(this.terminal?.ollamaManager?.optimizationEnabled) ? 'on' : 'off (default)'}`, '');
this.terminal.print(`confirm-timeout: ${this._confirmTimeoutMs > 0 ? this._confirmTimeoutMs / 1000 + 's' : 'disabled (no auto-deny)'} (default: 120s)`, '');
this.terminal.print(`chat-lock-timeout: ${this._chatLockTimeoutMs / 1000}s (default: 10s)`, '');
@@ -4122,7 +4132,20 @@ buildSystemPrompt() {
async _compressContext() {
if (this.context.length <= 6) return;
const totalTokens = this._estimateContextTokens();
- if (totalTokens < this.maxContextTokens) return;
+ if (totalTokens < this.maxContextTokens) {
+ // Warn before the cliff rather than only reporting the fall. Once per crossing,
+ // so a long session is not nagged every turn.
+ const pct = Math.round((totalTokens / this.maxContextTokens) * 100);
+ if (pct >= 80 && !this._ctxWarned) {
+ this._ctxWarned = true;
+ this.terminal.print('⚠️ Context ' + pct + '% full (' + totalTokens + '/' + this.maxContextTokens +
+ ' tokens) — the next few turns will compress earlier history into a summary', 'warning');
+ } else if (pct < 60) {
+ this._ctxWarned = false;
+ }
+ return;
+ }
+ this._ctxWarned = false;
const keepCount = 8;
const oldMsgs = this.context.slice(0, -keepCount * 2);
if (oldMsgs.length < 2) return;
diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js
index 95176d9..3ab51d7 100644
--- a/modules/core/module-loader.js
+++ b/modules/core/module-loader.js
@@ -4,7 +4,7 @@
// Core module bundle loaded synchronously in main.html.
// This loader only handles version cache-busting, splash, terminal init, and optional ext modules.
- const LATEST_VER = 'v4.6.90';
+ const LATEST_VER = 'v4.6.91';
const CACHE = '?v=' + LATEST_VER;
const stored = localStorage.getItem('webx11_cache_ver');
if (stored !== LATEST_VER) {