mixdog 0.9.104 → 0.9.105
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/README.md +3 -4
- package/package.json +1 -1
- package/src/rules/shared/01-tool.md +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +7 -14
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +37 -5
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +31 -31
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +36 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +2 -3
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +22 -1
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +40 -10
- package/src/session-runtime/context-status.mjs +11 -5
- package/src/standalone/explore-tool.mjs +1 -2
- package/src/tui/app/usage-context-panels.mjs +5 -2
- package/src/tui/components/ContextPanel.jsx +2 -0
- package/src/tui/dist/index.mjs +8 -3
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ every number above live under `benchmarks/terminal-bench-2.1/`.
|
|
|
103
103
|
role mode for scripting.
|
|
104
104
|
- Mixdog Desktop: a full agent workbench for Windows/macOS/Linux (see
|
|
105
105
|
below).
|
|
106
|
-
-
|
|
106
|
+
- Installable web app over relay pairing — scan a QR code to open your
|
|
107
107
|
running sessions in a phone browser and keep going from any network.
|
|
108
108
|
- Optional Discord/Telegram channels, webhook endpoints, and cron schedules
|
|
109
109
|
with quiet hours for remote/event-driven workflows; channel voice messages
|
|
@@ -246,7 +246,7 @@ wizard covers first-run setup. For development run `npm run dev` inside
|
|
|
246
246
|
- **Automation** — visual editors for workflow and agent packs, cron
|
|
247
247
|
schedules, webhooks, and channel integrations.
|
|
248
248
|
- **Settings hub** — provider auth, capability sweep, git identity, and
|
|
249
|
-
QR device pairing for the web
|
|
249
|
+
QR device pairing for the installable web app, preloaded so every
|
|
250
250
|
category opens instantly.
|
|
251
251
|
|
|
252
252
|
## Scripts
|
|
@@ -302,8 +302,7 @@ src/
|
|
|
302
302
|
rules/ # Lead and agent instructions
|
|
303
303
|
apps/
|
|
304
304
|
desktop/ # Mixdog Desktop — Electron workbench (main/preload/renderer)
|
|
305
|
-
|
|
306
|
-
relay/ # relay server for remote/web/mobile access
|
|
305
|
+
relay/ # relay server for remote web-app access
|
|
307
306
|
scripts/
|
|
308
307
|
smoke*.mjs # smoke checks
|
|
309
308
|
*test.mjs # focused node:test checks
|
package/package.json
CHANGED
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
facets in one query array. It
|
|
15
15
|
returns the minimal complete direct `path:line` anchors, not analysis or
|
|
16
16
|
solutions; resume baseline routing from those anchors.
|
|
17
|
-
- Use verified paths (cwd/project/user/tool)
|
|
17
|
+
- Use verified paths (cwd/project/user/tool). Within the current project, pass
|
|
18
|
+
project-relative paths and omit optional scopes equal to its root; explicit
|
|
19
|
+
paths may be outside cwd only for targets outside the project;
|
|
18
20
|
stay focused on the requested outcome. Avoid investigation, implementation,
|
|
19
21
|
or verification not required to satisfy it; once the requirements are met
|
|
20
22
|
and proven, stop.
|
|
@@ -292,20 +292,13 @@ export function applyAnthropicEffortToBody(
|
|
|
292
292
|
// Adaptive-thinking models (4.6+) require `thinking:{type:"adaptive"}`
|
|
293
293
|
// rather than the legacy budget_tokens shape — sending
|
|
294
294
|
// `thinking:{type:"enabled"}` here 400s on sonnet-5/opus-4-7/4-8.
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
// replayed into later requests (saves the 1h cache-write + re-read on
|
|
303
|
-
// accumulated thinking) at the cost of losing visible reasoning and
|
|
304
|
-
// cross-iteration thinking continuity. Default stays summarized.
|
|
305
|
-
const display = (process.env.MIXDOG_ANTHROPIC_THINKING_DISPLAY || '').trim() === 'omitted'
|
|
306
|
-
? 'omitted'
|
|
307
|
-
: 'summarized';
|
|
308
|
-
body.thinking = { type: 'adaptive', display };
|
|
295
|
+
// Match Claude Code's default wire shape: omit `display` and let the
|
|
296
|
+
// model/API choose its default. Operators and benchmarks can explicitly
|
|
297
|
+
// request either supported display mode.
|
|
298
|
+
const display = (process.env.MIXDOG_ANTHROPIC_THINKING_DISPLAY || '').trim();
|
|
299
|
+
body.thinking = display === 'summarized' || display === 'omitted'
|
|
300
|
+
? { type: 'adaptive', display }
|
|
301
|
+
: { type: 'adaptive' };
|
|
309
302
|
// Adaptive/4.7+ models reject any non-default sampling param with a 400.
|
|
310
303
|
delete body.temperature;
|
|
311
304
|
delete body.top_p;
|
|
@@ -199,6 +199,22 @@ function compactPressureTokens(messageTokensEst, policy) {
|
|
|
199
199
|
return Math.max(0, Math.round((messageTokensEst + requestReserve) * calibration) + otherReserve);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
// Provider-visible context estimate without operator-only compaction reserve.
|
|
203
|
+
// Request/schema reserve remains included because those bytes are sent to the
|
|
204
|
+
// model; configured reserve is merely local headroom and must not inflate the
|
|
205
|
+
// user-facing context gauge.
|
|
206
|
+
function currentContextEstimateTokens(messageTokensEst, policy) {
|
|
207
|
+
if (messageTokensEst === null) return 0;
|
|
208
|
+
const calibration = Number(policy?.tokenCalibration) > 0 ? Number(policy.tokenCalibration) : 1;
|
|
209
|
+
const configured = Math.max(0, Number(policy?.configuredReserveTokens) || 0);
|
|
210
|
+
const totalReserve = Math.max(0, Number(policy?.reserveTokens) || 0);
|
|
211
|
+
const requestReserve = Math.min(
|
|
212
|
+
totalReserve,
|
|
213
|
+
Math.max(0, Number(policy?.requestReserveTokens ?? (totalReserve - configured)) || 0),
|
|
214
|
+
);
|
|
215
|
+
return Math.max(0, Math.round((messageTokensEst + requestReserve) * calibration));
|
|
216
|
+
}
|
|
217
|
+
|
|
202
218
|
function providerPressureTokens(sessionRef, usage) {
|
|
203
219
|
if (!usage || typeof usage !== 'object') return 0;
|
|
204
220
|
const input = Math.max(0, Number(usage.mainInputTokens ?? usage.inputTokens) || 0);
|
|
@@ -268,7 +284,9 @@ export function invalidateProviderContextBaseline(sessionRef) {
|
|
|
268
284
|
// transcript did NOT grow keeps its baseline regardless of age.
|
|
269
285
|
const BASELINE_MAX_STALE_GROWTH_MS = 30 * 60 * 1000;
|
|
270
286
|
|
|
271
|
-
function providerBaselinePressureTokens(messages, sessionRef, policy
|
|
287
|
+
function providerBaselinePressureTokens(messages, sessionRef, policy, {
|
|
288
|
+
includeConfiguredReserve = true,
|
|
289
|
+
} = {}) {
|
|
272
290
|
if (!Array.isArray(messages) || !sessionRef
|
|
273
291
|
|| sessionRef.lastContextTokensStaleAfterCompact === true) return null;
|
|
274
292
|
let tokens = positiveTokenInt(sessionRef.contextPressureBaselineTokens);
|
|
@@ -303,16 +321,31 @@ function providerBaselinePressureTokens(messages, sessionRef, policy) {
|
|
|
303
321
|
const growth = count < messages.length
|
|
304
322
|
? Math.round(estimateMessagesTokens(messages.slice(count)) * calibration)
|
|
305
323
|
: 0;
|
|
306
|
-
|
|
324
|
+
const configuredReserve = includeConfiguredReserve
|
|
325
|
+
? Math.max(0, Number(policy?.configuredReserveTokens) || 0)
|
|
326
|
+
: 0;
|
|
327
|
+
return Math.max(0, tokens + growth + configuredReserve);
|
|
307
328
|
} catch {
|
|
308
329
|
return null;
|
|
309
330
|
}
|
|
310
331
|
}
|
|
311
332
|
|
|
333
|
+
function preferAlignedBaseline(baseline, estimate) {
|
|
334
|
+
if (baseline == null) return estimate;
|
|
335
|
+
if (Number.isFinite(estimate) && estimate > 0 && baseline * 2 < estimate) return estimate;
|
|
336
|
+
return baseline;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function resolveCurrentContextTokens(messageTokensEst, policy, { messages, sessionRef } = {}) {
|
|
340
|
+
const baseline = providerBaselinePressureTokens(messages, sessionRef, policy, {
|
|
341
|
+
includeConfiguredReserve: false,
|
|
342
|
+
});
|
|
343
|
+
return preferAlignedBaseline(baseline, currentContextEstimateTokens(messageTokensEst, policy));
|
|
344
|
+
}
|
|
345
|
+
|
|
312
346
|
export function resolveCompactionPressureTokens(messageTokensEst, policy, { messages, sessionRef } = {}) {
|
|
313
347
|
const baseline = providerBaselinePressureTokens(messages, sessionRef, policy);
|
|
314
348
|
const estimate = compactPressureTokens(messageTokensEst, policy);
|
|
315
|
-
if (baseline == null) return estimate;
|
|
316
349
|
// Sanity band: the baseline exists to correct OVER-counting estimates
|
|
317
350
|
// (dense-data floors can inflate the estimate up to ~2x real usage), so a
|
|
318
351
|
// lower baseline is normally preferred. But a corrupt/stale baseline below
|
|
@@ -322,8 +355,7 @@ export function resolveCompactionPressureTokens(messageTokensEst, policy, { mess
|
|
|
322
355
|
// both the gauge and the compaction decision. Erring toward the estimate
|
|
323
356
|
// may compact somewhat early; erring toward a rotten baseline blows past
|
|
324
357
|
// the context window at full token cost.
|
|
325
|
-
|
|
326
|
-
return baseline;
|
|
358
|
+
return preferAlignedBaseline(baseline, estimate);
|
|
327
359
|
}
|
|
328
360
|
|
|
329
361
|
/** Telemetry pressure when a reactive overflow retry forces the next compact. */
|
|
@@ -856,6 +856,32 @@ function dropUndefinedArgs(args) {
|
|
|
856
856
|
}
|
|
857
857
|
}
|
|
858
858
|
|
|
859
|
+
// Provider-facing built-ins use short keys/enum values to reduce repeated
|
|
860
|
+
// tool-call output. Canonicalize them before validation so executors, traces,
|
|
861
|
+
// saved calls, and legacy long-form callers keep one stable internal contract.
|
|
862
|
+
function normalizeCompactSurfaceArgs(toolName, args) {
|
|
863
|
+
if (['grep', 'glob', 'find', 'list'].includes(toolName)
|
|
864
|
+
&& !hasOwn(args, 'head_limit')
|
|
865
|
+
&& hasOwn(args, 'limit')) {
|
|
866
|
+
args.head_limit = args.limit;
|
|
867
|
+
delete args.limit;
|
|
868
|
+
}
|
|
869
|
+
if (toolName === 'grep'
|
|
870
|
+
&& !hasOwn(args, 'output_mode')
|
|
871
|
+
&& typeof args.mode === 'string') {
|
|
872
|
+
const mode = args.mode.trim();
|
|
873
|
+
const canonical = {
|
|
874
|
+
content: 'content_with_context',
|
|
875
|
+
files: 'files_with_matches',
|
|
876
|
+
count: 'count',
|
|
877
|
+
}[mode];
|
|
878
|
+
if (canonical) {
|
|
879
|
+
args.output_mode = canonical;
|
|
880
|
+
delete args.mode;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
859
885
|
// string | string[] arguments arrive nested ([["a","b"]] from a batching
|
|
860
886
|
// wrapper), padded with null/empty entries, or empty. The intent is
|
|
861
887
|
// unambiguous, so flatten one level, drop the blanks, and remove a key that
|
|
@@ -866,9 +892,13 @@ const STRING_LIST_ARG_KEYS = [
|
|
|
866
892
|
'pattern', 'patterns', 'query', 'regex', 'needle',
|
|
867
893
|
'glob', 'file_pattern', 'include', 'type', 'symbols',
|
|
868
894
|
];
|
|
869
|
-
function normalizeStringListArgs(args) {
|
|
895
|
+
function normalizeStringListArgs(args, toolName) {
|
|
870
896
|
for (const key of STRING_LIST_ARG_KEYS) {
|
|
871
897
|
if (!hasOwn(args, key) || !Array.isArray(args[key])) continue;
|
|
898
|
+
// read.path may contain compact [path,offset,limit] tuples. Flattening
|
|
899
|
+
// here would destroy their boundaries before guardRead canonicalizes
|
|
900
|
+
// them through coerceReadFamilyPathArg().
|
|
901
|
+
if (toolName === 'read' && key === 'path' && args[key].some(Array.isArray)) continue;
|
|
872
902
|
const source = args[key];
|
|
873
903
|
// Flatten ONE nesting level and drop blank entries; every other entry
|
|
874
904
|
// type is left untouched so the per-tool guards still see (and coerce
|
|
@@ -896,7 +926,8 @@ export function validateBuiltinArgs(toolName, args) {
|
|
|
896
926
|
return `Error: ${toolName} arguments must be an object (got ${describeType(args)})`;
|
|
897
927
|
}
|
|
898
928
|
dropUndefinedArgs(args);
|
|
899
|
-
|
|
929
|
+
normalizeCompactSurfaceArgs(toolName, args);
|
|
930
|
+
normalizeStringListArgs(args, toolName);
|
|
900
931
|
if (toolName === 'grep') applyGrepContextLeadPolicy(args);
|
|
901
932
|
try {
|
|
902
933
|
return guard(args) || null;
|
|
@@ -498,7 +498,10 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
498
498
|
const promotedTimeoutMs = hasExplicitTimeout && backgroundOnTimeout
|
|
499
499
|
? Math.max(0, totalTimeout - timeout)
|
|
500
500
|
: 0;
|
|
501
|
-
|
|
501
|
+
// Provider schema intentionally omits this low-value toggle; all observed
|
|
502
|
+
// live calls requested merged output, which is also the useful default for
|
|
503
|
+
// in-turn diagnostics. Internal callers may still opt out explicitly.
|
|
504
|
+
const mergeStderr = args.merge_stderr !== false;
|
|
502
505
|
const longForegroundHint = foregroundLongCommandHint(
|
|
503
506
|
command,
|
|
504
507
|
timeout,
|
|
@@ -46,24 +46,28 @@ export const BUILTIN_TOOLS = [
|
|
|
46
46
|
items: {
|
|
47
47
|
anyOf: [
|
|
48
48
|
{ type: 'string' },
|
|
49
|
+
{ type: 'number' },
|
|
49
50
|
{
|
|
50
|
-
type: '
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
type: 'array',
|
|
52
|
+
items: {
|
|
53
|
+
anyOf: [
|
|
54
|
+
{ type: 'string' },
|
|
55
|
+
{ type: 'number' },
|
|
56
|
+
],
|
|
55
57
|
},
|
|
56
|
-
|
|
58
|
+
minItems: 2,
|
|
59
|
+
maxItems: 3,
|
|
60
|
+
description: '[path,offset,limit?].',
|
|
57
61
|
},
|
|
58
62
|
],
|
|
59
63
|
},
|
|
60
64
|
minItems: 1,
|
|
61
65
|
},
|
|
62
66
|
],
|
|
63
|
-
description: '
|
|
67
|
+
description: 'Project-relative file path, string[] files, [path,offset,limit?] range, or range[]; absolute only outside the project.',
|
|
64
68
|
},
|
|
65
69
|
offset: { type: 'number', minimum: 0, description: 'Lines to skip.' },
|
|
66
|
-
limit: { type: 'number', minimum: 1, description: 'Max lines
|
|
70
|
+
limit: { type: 'number', minimum: 1, description: 'Max lines; default 2000.' },
|
|
67
71
|
},
|
|
68
72
|
required: ['path'],
|
|
69
73
|
additionalProperties: false,
|
|
@@ -78,14 +82,11 @@ export const BUILTIN_TOOLS = [
|
|
|
78
82
|
type: 'object',
|
|
79
83
|
properties: {
|
|
80
84
|
command: { type: 'string', description: `Command.${_shellSyntaxCheat}` },
|
|
81
|
-
cwd: { type: 'string', description: '
|
|
85
|
+
cwd: { type: 'string', description: 'Omit for current directory; use a project-relative subdir or explicit external path.' },
|
|
82
86
|
timeout: {
|
|
83
87
|
type: 'number',
|
|
84
|
-
description: `Timeout ms; default ${_shellDefaultTimeoutMs()}.
|
|
85
|
-
+ 'Sync timeout may return task_id; an explicit value is its deadline. '
|
|
86
|
-
+ 'Sleeps are killed, not promoted.',
|
|
88
|
+
description: `Timeout ms; default ${_shellDefaultTimeoutMs()}. Explicit values are deadlines; sync may return task_id.`,
|
|
87
89
|
},
|
|
88
|
-
merge_stderr: { type: 'boolean', description: 'Merge stderr.' },
|
|
89
90
|
mode: { type: 'string', enum: ['sync', 'async'], description: executionModeSchemaDescription('sync') },
|
|
90
91
|
shell: { type: 'string', enum: ['bash', 'powershell'], description: 'Force shell.' },
|
|
91
92
|
},
|
|
@@ -101,9 +102,9 @@ export const BUILTIN_TOOLS = [
|
|
|
101
102
|
inputSchema: {
|
|
102
103
|
type: 'object',
|
|
103
104
|
properties: {
|
|
104
|
-
task_id: { type: 'string', description: '
|
|
105
|
-
action: { type: 'string', enum: ['list', 'status', 'read', 'wait', 'cancel'], description: 'Default list; with task_id
|
|
106
|
-
timeout_ms: { type: 'number', description: 'Wait timeout ms.' },
|
|
105
|
+
task_id: { type: 'string', description: 'Shell task_id.' },
|
|
106
|
+
action: { type: 'string', enum: ['list', 'status', 'read', 'wait', 'cancel'], description: 'Default list; with task_id: wait.' },
|
|
107
|
+
timeout_ms: { type: 'number', description: 'Wait timeout (ms).' },
|
|
107
108
|
},
|
|
108
109
|
required: [],
|
|
109
110
|
additionalProperties: false,
|
|
@@ -122,14 +123,14 @@ export const BUILTIN_TOOLS = [
|
|
|
122
123
|
{ type: 'string' },
|
|
123
124
|
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
124
125
|
],
|
|
125
|
-
description: 'Text/regex; pattern[] batches exact query literals and identifier variants
|
|
126
|
+
description: 'Text/regex; pattern[] batches exact query literals and identifier variants.',
|
|
126
127
|
},
|
|
127
128
|
path: {
|
|
128
129
|
anyOf: [
|
|
129
130
|
{ type: 'string' },
|
|
130
131
|
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
131
132
|
],
|
|
132
|
-
description: '
|
|
133
|
+
description: 'Project-relative file/dir scope(s); omit for project root; absolute only outside.',
|
|
133
134
|
},
|
|
134
135
|
glob: {
|
|
135
136
|
anyOf: [
|
|
@@ -138,10 +139,10 @@ export const BUILTIN_TOOLS = [
|
|
|
138
139
|
],
|
|
139
140
|
description: 'Glob filter.',
|
|
140
141
|
},
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
offset: { type: 'number', minimum: 0, description: '
|
|
144
|
-
context: { type: 'number', minimum: 0, description: '
|
|
142
|
+
mode: { type: 'string', enum: ['content', 'files', 'count'], description: 'content default; files/count for existence.' },
|
|
143
|
+
limit: { type: 'number', minimum: 0, description: 'Max results; default 250; 0 unlimited.' },
|
|
144
|
+
offset: { type: 'number', minimum: 0, description: 'Result offset.' },
|
|
145
|
+
context: { type: 'number', minimum: 0, description: 'Omit for automatic context; 0 for matches only.' },
|
|
145
146
|
},
|
|
146
147
|
anyOf: [
|
|
147
148
|
{ required: ['pattern'] },
|
|
@@ -163,17 +164,16 @@ export const BUILTIN_TOOLS = [
|
|
|
163
164
|
{ type: 'string' },
|
|
164
165
|
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
165
166
|
],
|
|
166
|
-
description: 'Glob
|
|
167
|
+
description: 'Glob(s); pattern[] batches.',
|
|
167
168
|
},
|
|
168
169
|
path: {
|
|
169
170
|
anyOf: [
|
|
170
171
|
{ type: 'string' },
|
|
171
172
|
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
172
173
|
],
|
|
173
|
-
description: '
|
|
174
|
+
description: 'Project-relative base dir(s); omit for project root; path[] batches; absolute only outside.',
|
|
174
175
|
},
|
|
175
|
-
|
|
176
|
-
offset: { type: 'number', description: 'Skip entries.' },
|
|
176
|
+
limit: { type: 'number', description: 'Max entries; default 100; 0 unlimited.' },
|
|
177
177
|
},
|
|
178
178
|
required: ['pattern'],
|
|
179
179
|
additionalProperties: false,
|
|
@@ -194,8 +194,8 @@ export const BUILTIN_TOOLS = [
|
|
|
194
194
|
],
|
|
195
195
|
description: 'Filename or directory path fragments matched against path strings; query[] batches.',
|
|
196
196
|
},
|
|
197
|
-
path: { type: 'string', description: '
|
|
198
|
-
|
|
197
|
+
path: { type: 'string', description: 'Project-relative base; omit for project root; absolute only outside.' },
|
|
198
|
+
limit: { type: 'number', description: 'Max paths across the call. Defaults to 25.' },
|
|
199
199
|
},
|
|
200
200
|
required: ['query'],
|
|
201
201
|
additionalProperties: false,
|
|
@@ -214,12 +214,12 @@ export const BUILTIN_TOOLS = [
|
|
|
214
214
|
{ type: 'string' },
|
|
215
215
|
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
216
216
|
],
|
|
217
|
-
description: '
|
|
217
|
+
description: 'Project-relative directory; omit for project root; path[] batches; absolute only outside.',
|
|
218
218
|
},
|
|
219
219
|
hidden: { type: 'boolean', description: 'Include dotfiles.' },
|
|
220
220
|
meta: { type: 'boolean', description: 'Per-entry size bytes, UTC mtime, octal mode.' },
|
|
221
|
-
|
|
222
|
-
offset: { type: 'number', description: '
|
|
221
|
+
limit: { type: 'number', description: 'Max entries; default 200; 0 unlimited.' },
|
|
222
|
+
offset: { type: 'number', description: 'Entry offset.' },
|
|
223
223
|
},
|
|
224
224
|
required: [],
|
|
225
225
|
additionalProperties: false,
|
|
@@ -110,6 +110,10 @@ export const GREP_AUTO_CONTEXT_LINES = 25;
|
|
|
110
110
|
|
|
111
111
|
export function normalizeGrepArgs(args) {
|
|
112
112
|
if (!args || typeof args !== 'object') return args;
|
|
113
|
+
if ((args.head_limit === undefined || args.head_limit === null) && args.limit !== undefined) {
|
|
114
|
+
args.head_limit = args.limit;
|
|
115
|
+
delete args.limit;
|
|
116
|
+
}
|
|
113
117
|
if (args.pattern === undefined || args.pattern === null || args.pattern === '') {
|
|
114
118
|
const alias = firstPresentArg(args, ['query', 'regex', 'regexp', 'needle', 'search', 'literal']);
|
|
115
119
|
if (alias !== undefined) args.pattern = alias;
|
|
@@ -124,13 +128,24 @@ export function normalizeGrepArgs(args) {
|
|
|
124
128
|
}
|
|
125
129
|
if ((args.output_mode === undefined || args.output_mode === null || args.output_mode === '') && typeof args.mode === 'string') {
|
|
126
130
|
const mode = args.mode.trim();
|
|
127
|
-
|
|
131
|
+
const canonical = {
|
|
132
|
+
content: 'content_with_context',
|
|
133
|
+
files: 'files_with_matches',
|
|
134
|
+
count: 'count',
|
|
135
|
+
content_with_context: 'content_with_context',
|
|
136
|
+
files_with_matches: 'files_with_matches',
|
|
137
|
+
}[mode];
|
|
138
|
+
if (canonical) args.output_mode = canonical;
|
|
128
139
|
}
|
|
129
140
|
return args;
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
export function normalizeGlobArgs(args) {
|
|
133
144
|
if (!args || typeof args !== 'object') return args;
|
|
145
|
+
if ((args.head_limit === undefined || args.head_limit === null) && args.limit !== undefined) {
|
|
146
|
+
args.head_limit = args.limit;
|
|
147
|
+
delete args.limit;
|
|
148
|
+
}
|
|
134
149
|
if (args.pattern === undefined || args.pattern === null || args.pattern === '') {
|
|
135
150
|
const alias = firstPresentArg(args, ['glob', 'file_pattern', 'filePattern', 'name', 'include', 'includes', 'files']);
|
|
136
151
|
if (alias !== undefined) args.pattern = alias;
|
|
@@ -247,6 +262,20 @@ export function coerceShapeFlex(value) {
|
|
|
247
262
|
// JSON-stringified path arrays (path:"[]") and empty arrays mean "search cwd".
|
|
248
263
|
// Bracket-shaped strings that exist on disk (e.g. a literal `[x]` directory) are
|
|
249
264
|
// left untouched — JSON reinterpretation only runs after a stat miss.
|
|
265
|
+
function compactReadRangeTuple(value) {
|
|
266
|
+
if (!Array.isArray(value) || value.length < 2 || value.length > 3 || typeof value[0] !== 'string') return null;
|
|
267
|
+
const numeric = value.slice(1).every((part) => (
|
|
268
|
+
typeof part === 'number'
|
|
269
|
+
|| (typeof part === 'string' && /^-?\d+(?:\.\d+)?$/.test(part.trim()))
|
|
270
|
+
));
|
|
271
|
+
if (!numeric) return null;
|
|
272
|
+
return {
|
|
273
|
+
path: value[0],
|
|
274
|
+
offset: value[1],
|
|
275
|
+
...(value.length === 3 ? { limit: value[2] } : {}),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
250
279
|
export function coerceReadFamilyPathArg(path, workDir = null) {
|
|
251
280
|
if (path === undefined || path === null || path === '') return path;
|
|
252
281
|
if (typeof path === 'string' && typeof workDir === 'string' && workDir) {
|
|
@@ -305,10 +334,13 @@ export function coerceReadFamilyPathArg(path, workDir = null) {
|
|
|
305
334
|
}
|
|
306
335
|
const coerced = coerceShapeFlex(path);
|
|
307
336
|
if (!Array.isArray(coerced)) return coerced;
|
|
337
|
+
const directRange = compactReadRangeTuple(coerced);
|
|
338
|
+
if (directRange) return [directRange];
|
|
308
339
|
const list = coerced
|
|
309
|
-
//
|
|
310
|
-
// read's batch dispatcher
|
|
311
|
-
|
|
340
|
+
// Provider-facing range tuples are compact; canonicalize them to the
|
|
341
|
+
// legacy region objects consumed by read's batch dispatcher. Existing
|
|
342
|
+
// object callers remain valid and pass through untouched.
|
|
343
|
+
.map((p) => compactReadRangeTuple(p) ?? (typeof p === 'string' ? p.trim() : p))
|
|
312
344
|
.filter((p) => (typeof p === 'string' ? p.length > 0 : (p && typeof p === 'object')));
|
|
313
345
|
if (list.length === 0) return '.';
|
|
314
346
|
// Collapse to scalar only for a lone string; a lone region object must
|
|
@@ -8,13 +8,12 @@ export const CODE_GRAPH_TOOL_DEFS = [
|
|
|
8
8
|
type: 'object',
|
|
9
9
|
properties: {
|
|
10
10
|
mode: { type: 'string', enum: ['overview', 'imports', 'dependents', 'related', 'impact', 'symbols', 'find_symbol', 'symbol_search', 'search', 'references', 'callers', 'callees'], description: 'File modes: overview/imports/dependents/related/impact. symbols with files[] gives a file outline; others are symbol modes.' },
|
|
11
|
-
files: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: '
|
|
11
|
+
files: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Project-relative source file path(s); absolute only outside the project; supported targets only.' },
|
|
12
12
|
symbols: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Exact identifiers or keywords (symbol-index terms); batch multiple in one symbols[] call.' },
|
|
13
13
|
body: { type: 'boolean', description: 'Include body.' },
|
|
14
14
|
limit: { type: 'number', minimum: 1, description: 'Max results.' },
|
|
15
15
|
depth: { type: 'number', minimum: 1, maximum: 5, description: 'Caller depth.' },
|
|
16
|
-
|
|
17
|
-
cwd: { type: 'string', description: 'Explicit root.' },
|
|
16
|
+
cwd: { type: 'string', description: 'Explicit root outside the project; omit for project root.' },
|
|
18
17
|
},
|
|
19
18
|
required: ['mode'],
|
|
20
19
|
additionalProperties: false,
|
|
@@ -680,6 +680,26 @@ function extractInlinePatchRoot(patchText) {
|
|
|
680
680
|
};
|
|
681
681
|
}
|
|
682
682
|
|
|
683
|
+
export function expandCompactPatchInput(patchText) {
|
|
684
|
+
const text = String(patchText || '').replace(/^\uFEFF/, '');
|
|
685
|
+
if (/^\s*\*\*\* Begin Patch(?:\r?\n|$)/.test(text)) return text;
|
|
686
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
687
|
+
while (lines.length && lines[0] === '') lines.shift();
|
|
688
|
+
while (lines.length && lines[lines.length - 1] === '') lines.pop();
|
|
689
|
+
if (!lines.length || !/^[RADU] /.test(lines[0])) return text;
|
|
690
|
+
const mapped = lines.map((line) => {
|
|
691
|
+
if (line.startsWith('R ')) return `*** Root: ${line.slice(2)}`;
|
|
692
|
+
if (line.startsWith('A ')) return `*** Add File: ${line.slice(2)}`;
|
|
693
|
+
if (line.startsWith('D ')) return `*** Delete File: ${line.slice(2)}`;
|
|
694
|
+
if (line.startsWith('U ')) return `*** Update File: ${line.slice(2)}`;
|
|
695
|
+
if (line.startsWith('M ')) return `*** Move to: ${line.slice(2)}`;
|
|
696
|
+
if (line === '@') return '@@';
|
|
697
|
+
if (line.startsWith('@ ')) return `@@ ${line.slice(2)}`;
|
|
698
|
+
return line;
|
|
699
|
+
});
|
|
700
|
+
return ['*** Begin Patch', ...mapped, '*** End Patch', ''].join('\n');
|
|
701
|
+
}
|
|
702
|
+
|
|
683
703
|
function isFilesystemRootSpecifier(value, cwd) {
|
|
684
704
|
const raw = String(value || '').trim();
|
|
685
705
|
if (!raw) return false;
|
|
@@ -692,7 +712,8 @@ function isFilesystemRootSpecifier(value, cwd) {
|
|
|
692
712
|
|
|
693
713
|
async function apply_patch(args, cwd, options = {}) {
|
|
694
714
|
args = salvageShatteredV4APatchArgs(args);
|
|
695
|
-
let patchStr =
|
|
715
|
+
let patchStr = expandCompactPatchInput(typeof args?.patch === 'string' ? args.patch : '');
|
|
716
|
+
patchStr = salvageV4AOpening(patchStr);
|
|
696
717
|
const inlineRoot = extractInlinePatchRoot(patchStr);
|
|
697
718
|
patchStr = inlineRoot.patch;
|
|
698
719
|
if (inlineRoot.root) {
|
|
@@ -20,16 +20,41 @@ eof_line: "*** End of File" LF
|
|
|
20
20
|
%import common.LF
|
|
21
21
|
`;
|
|
22
22
|
|
|
23
|
+
const COMPACT_PATCH_LARK_GRAMMAR = `start: root_line? hunk+
|
|
24
|
+
root_line: "R " filename LF
|
|
25
|
+
|
|
26
|
+
hunk: add_hunk | delete_hunk | update_hunk
|
|
27
|
+
add_hunk: "A " filename LF add_line+
|
|
28
|
+
delete_hunk: "D " filename LF
|
|
29
|
+
update_hunk: "U " filename LF change_move? change
|
|
30
|
+
|
|
31
|
+
filename: /(.+)/
|
|
32
|
+
add_line: "+" /(.*)/ LF -> line
|
|
33
|
+
|
|
34
|
+
change_move: "M " filename LF
|
|
35
|
+
change: (change_context | change_line)+ eof_line?
|
|
36
|
+
change_context: ("@" | "@ " /(.+)/) LF
|
|
37
|
+
change_line: ("+" | "-" | " ") /(.*)/ LF
|
|
38
|
+
eof_line: "*** End of File" LF
|
|
39
|
+
|
|
40
|
+
%import common.LF
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const USE_COMPACT_PATCH_FORMAT = process.env.MIXDOG_COMPACT_PATCH_GRAMMAR !== '0';
|
|
44
|
+
|
|
23
45
|
// Public contract: apply_patch is the PRIMARY edit tool and takes
|
|
24
46
|
// a raw freeform V4A patch (no JSON envelope) on providers that support custom
|
|
25
47
|
// grammar tools. No prior `read` is required or implied — send the patch as
|
|
26
48
|
// soon as the target and content are known. The JSON schema below is only the
|
|
27
49
|
// fallback for providers that cannot carry freeform/custom tools, so it exposes
|
|
28
|
-
// the patch string
|
|
50
|
+
// the patch string and an optional explicit base; runtime-only knobs stay off
|
|
51
|
+
// the model surface.
|
|
29
52
|
// Batching stays a rules-level policy: every new edit goes in one patch, with
|
|
30
53
|
// one file block per target.
|
|
31
54
|
const APPLY_PATCH_FREEFORM_DESCRIPTION =
|
|
32
55
|
'Edit files with `apply_patch`. FREEFORM input; do not wrap the patch in JSON.';
|
|
56
|
+
const COMPACT_PATCH_FREEFORM_DESCRIPTION =
|
|
57
|
+
'Compact patch: U/A/D path, optional M rename or R root, @ hunks, then space/-/+ lines.';
|
|
33
58
|
|
|
34
59
|
// JSON-schema fallback providers (Anthropic and other non-grammar surfaces)
|
|
35
60
|
// get the full V4A instructions inline: without a grammar the model has
|
|
@@ -40,12 +65,20 @@ const APPLY_PATCH_FREEFORM_DESCRIPTION =
|
|
|
40
65
|
const APPLY_PATCH_JSON_DESCRIPTION = [
|
|
41
66
|
'Edit files with this V4A patch:',
|
|
42
67
|
'*** Begin Patch',
|
|
68
|
+
'[optional *** Root: <path> for out-of-session writes]',
|
|
43
69
|
'[file sections]',
|
|
44
70
|
'*** End Patch',
|
|
45
71
|
'Each section starts with exactly one: *** Add File: <path> (+ lines), *** Delete File: <path> (header only), or *** Update File: <path> (optional *** Move to: <new path>).',
|
|
46
72
|
'Hunks start with @@ or @@ <symbol>; lines start space, -, or +; optional *** End of File.',
|
|
47
73
|
'Use 3 verbatim context lines from newest output (post-patch body after edits); avoid overlap; stack @@ only if ambiguous.',
|
|
48
|
-
'
|
|
74
|
+
'Project-relative paths; explicit absolute paths only outside the project; + every added line. Never send compacted-history markers; re-read first.',
|
|
75
|
+
].join('\n');
|
|
76
|
+
|
|
77
|
+
const COMPACT_PATCH_JSON_DESCRIPTION = [
|
|
78
|
+
'Edit with a compact patch.',
|
|
79
|
+
'Sections: A <path> (+ lines), D <path>, or U <path> (optional M <new path>).',
|
|
80
|
+
'Optional first line R <root>. Update hunks start @ or @ <symbol>; lines start space, -, or +; optional *** End of File.',
|
|
81
|
+
'Use 3 verbatim context lines. Project-relative paths; absolute only outside the project.',
|
|
49
82
|
].join('\n');
|
|
50
83
|
|
|
51
84
|
export const PATCH_TOOL_DEFS = [
|
|
@@ -53,21 +86,18 @@ export const PATCH_TOOL_DEFS = [
|
|
|
53
86
|
name: 'apply_patch',
|
|
54
87
|
title: 'Mixdog Apply Patch',
|
|
55
88
|
annotations: { title: 'Mixdog Apply Patch', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false, compressible: false, compressibleLossless: true },
|
|
56
|
-
description: APPLY_PATCH_JSON_DESCRIPTION,
|
|
57
|
-
freeformDescription: APPLY_PATCH_FREEFORM_DESCRIPTION,
|
|
89
|
+
description: USE_COMPACT_PATCH_FORMAT ? COMPACT_PATCH_JSON_DESCRIPTION : APPLY_PATCH_JSON_DESCRIPTION,
|
|
90
|
+
freeformDescription: USE_COMPACT_PATCH_FORMAT ? COMPACT_PATCH_FREEFORM_DESCRIPTION : APPLY_PATCH_FREEFORM_DESCRIPTION,
|
|
58
91
|
freeform: {
|
|
59
92
|
type: 'grammar',
|
|
60
93
|
syntax: 'lark',
|
|
61
|
-
definition: APPLY_PATCH_LARK_GRAMMAR,
|
|
94
|
+
definition: USE_COMPACT_PATCH_FORMAT ? COMPACT_PATCH_LARK_GRAMMAR : APPLY_PATCH_LARK_GRAMMAR,
|
|
62
95
|
},
|
|
63
96
|
inputSchema: {
|
|
64
97
|
type: 'object',
|
|
65
98
|
properties: {
|
|
66
|
-
patch: { type: 'string', description: '
|
|
67
|
-
root: {
|
|
68
|
-
type: 'string',
|
|
69
|
-
description: 'Write root only outside the session directory.',
|
|
70
|
-
},
|
|
99
|
+
patch: { type: 'string', description: USE_COMPACT_PATCH_FORMAT ? 'Compact patch.' : 'V4A patch.' },
|
|
100
|
+
root: { type: 'string', description: 'Explicit patch base directory.' },
|
|
71
101
|
},
|
|
72
102
|
required: ['patch'],
|
|
73
103
|
additionalProperties: false,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { SUMMARY_PREFIX } from '../runtime/agent/orchestrator/session/compact.mjs';
|
|
12
12
|
import { hasUserConversationMessage } from '../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
|
|
13
13
|
import {
|
|
14
|
+
resolveCurrentContextTokens,
|
|
14
15
|
resolveCompactionPressureTokens,
|
|
15
16
|
resolveWorkerCompactPolicy,
|
|
16
17
|
} from '../runtime/agent/orchestrator/session/loop/compact-policy.mjs';
|
|
@@ -268,15 +269,18 @@ export function createContextStatus({
|
|
|
268
269
|
...resolveSessionCompactPolicy(session || {}, compactBoundaryTokens),
|
|
269
270
|
tokenCalibration: providerTokenCalibration(session?.provider || route.provider),
|
|
270
271
|
};
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
// configured reserves, rather than a separate transcript-only estimate.
|
|
272
|
+
// Keep the user-facing gauge aligned to provider-visible context. The
|
|
273
|
+
// compaction branch retains its stricter pressure numerator separately.
|
|
274
274
|
const compactionPressureTokens = resolveCompactionPressureTokens(
|
|
275
275
|
messageSummary.estimatedTokens,
|
|
276
276
|
compactPolicy,
|
|
277
277
|
{ messages, sessionRef: session },
|
|
278
278
|
);
|
|
279
|
-
const usedTokens =
|
|
279
|
+
const usedTokens = resolveCurrentContextTokens(
|
|
280
|
+
messageSummary.estimatedTokens,
|
|
281
|
+
compactPolicy,
|
|
282
|
+
{ messages, sessionRef: session },
|
|
283
|
+
);
|
|
280
284
|
const freeTokens = displayWindow ? Math.max(0, displayWindow - usedTokens) : 0;
|
|
281
285
|
const compactTriggerTokens = compactPolicy.triggerTokens || 0;
|
|
282
286
|
const compactBufferTokens = Number.isFinite(Number(compactPolicy.bufferTokens))
|
|
@@ -297,7 +301,7 @@ export function createContextStatus({
|
|
|
297
301
|
effectiveContextWindowPercent: session?.effectiveContextWindowPercent || null,
|
|
298
302
|
usedTokens,
|
|
299
303
|
usedSource: 'estimated',
|
|
300
|
-
currentEstimatedTokens:
|
|
304
|
+
currentEstimatedTokens: usedTokens,
|
|
301
305
|
lastApiRequestTokens: lastContextTokens || 0,
|
|
302
306
|
lastApiRequestStale: lastUsageStale,
|
|
303
307
|
freeTokens,
|
|
@@ -308,6 +312,8 @@ export function createContextStatus({
|
|
|
308
312
|
bufferTokens: Number.isFinite(compactBufferTokens) ? compactBufferTokens : null,
|
|
309
313
|
bufferRatio: compactBufferRatio,
|
|
310
314
|
currentEstimatedTokens: compactionPressureTokens,
|
|
315
|
+
pressureTokens: compactionPressureTokens,
|
|
316
|
+
reserveTokens: Math.max(0, Number(compactPolicy.configuredReserveTokens) || 0),
|
|
311
317
|
lastApiRequestTokens: lastContextTokens || 0,
|
|
312
318
|
lastApiRequestStale: lastUsageStale,
|
|
313
319
|
},
|
|
@@ -24,10 +24,9 @@ export const EXPLORE_TOOL = {
|
|
|
24
24
|
type: 'object',
|
|
25
25
|
properties: {
|
|
26
26
|
query: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'One concrete unknown target per query; return its minimal complete direct path:line set. Never a topic list; array = independent targets fanned out.' },
|
|
27
|
-
cwd: { type: 'string', description: 'Project/root directory.' },
|
|
28
27
|
roots: {
|
|
29
28
|
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }],
|
|
30
|
-
description: '
|
|
29
|
+
description: 'Project-relative or absolute external search root(s); omit for the caller project; use multiple roots for cross-project or machine lookup.',
|
|
31
30
|
},
|
|
32
31
|
},
|
|
33
32
|
required: [],
|
|
@@ -157,6 +157,8 @@ export function createUsageContextPanels({
|
|
|
157
157
|
const compactDescription = compactDuration
|
|
158
158
|
? `${compactState} · ${compactDuration}`
|
|
159
159
|
: compactState;
|
|
160
|
+
const compactPressure = Number(compaction.pressureTokens || compaction.currentEstimatedTokens || 0);
|
|
161
|
+
const compactReserve = Number(compaction.reserveTokens || 0);
|
|
160
162
|
const contextRows = [
|
|
161
163
|
{
|
|
162
164
|
value: 'summary',
|
|
@@ -167,7 +169,7 @@ export function createUsageContextPanels({
|
|
|
167
169
|
{
|
|
168
170
|
value: 'compaction',
|
|
169
171
|
label: 'Compaction',
|
|
170
|
-
description: compactDescription
|
|
172
|
+
description: `${compactDescription} · ${fmt(compactPressure)} pressure · ${fmt(compactReserve)} reserve`,
|
|
171
173
|
_action: 'compaction',
|
|
172
174
|
},
|
|
173
175
|
{
|
|
@@ -249,7 +251,8 @@ export function createUsageContextPanels({
|
|
|
249
251
|
if (compactBoundary && compactTrigger) return Math.max(0, compactBoundary - compactTrigger);
|
|
250
252
|
return null;
|
|
251
253
|
})(),
|
|
252
|
-
pressureTokens: Number(compaction.lastPressureTokens || compaction.currentEstimatedTokens || 0) || null,
|
|
254
|
+
pressureTokens: Number(compaction.lastPressureTokens || compaction.pressureTokens || compaction.currentEstimatedTokens || 0) || null,
|
|
255
|
+
reserveTokens: Number(compaction.reserveTokens || 0) || null,
|
|
253
256
|
lastChanged: compaction.lastChanged === true,
|
|
254
257
|
},
|
|
255
258
|
messages: {
|
|
@@ -176,6 +176,8 @@ function ContextUsageView({ detail, columns }) {
|
|
|
176
176
|
compaction.type ? `type ${compaction.type}` : '',
|
|
177
177
|
compaction.triggerTokens ? `trigger ${formatTokens(compaction.triggerTokens)}` : '',
|
|
178
178
|
compaction.boundaryTokens ? `boundary ${formatTokens(compaction.boundaryTokens)}` : '',
|
|
179
|
+
compaction.pressureTokens ? `pressure ${formatTokens(compaction.pressureTokens)}` : '',
|
|
180
|
+
compaction.reserveTokens ? `reserve ${formatTokens(compaction.reserveTokens)}` : '',
|
|
179
181
|
]);
|
|
180
182
|
const sourceLine = metricValue([
|
|
181
183
|
usage.effective ? `effective ${formatTokens(windowTokens)}` : `window ${formatTokens(windowTokens)}`,
|
package/src/tui/dist/index.mjs
CHANGED
|
@@ -7626,7 +7626,9 @@ function ContextUsageView({ detail, columns }) {
|
|
|
7626
7626
|
compaction.state,
|
|
7627
7627
|
compaction.type ? `type ${compaction.type}` : "",
|
|
7628
7628
|
compaction.triggerTokens ? `trigger ${formatTokens(compaction.triggerTokens)}` : "",
|
|
7629
|
-
compaction.boundaryTokens ? `boundary ${formatTokens(compaction.boundaryTokens)}` : ""
|
|
7629
|
+
compaction.boundaryTokens ? `boundary ${formatTokens(compaction.boundaryTokens)}` : "",
|
|
7630
|
+
compaction.pressureTokens ? `pressure ${formatTokens(compaction.pressureTokens)}` : "",
|
|
7631
|
+
compaction.reserveTokens ? `reserve ${formatTokens(compaction.reserveTokens)}` : ""
|
|
7630
7632
|
]);
|
|
7631
7633
|
const sourceLine = metricValue([
|
|
7632
7634
|
usage.effective ? `effective ${formatTokens(windowTokens)}` : `window ${formatTokens(windowTokens)}`,
|
|
@@ -13797,6 +13799,8 @@ function createUsageContextPanels({
|
|
|
13797
13799
|
const compactReactive = String(compaction.lastTrigger || "").toLowerCase() === "reactive";
|
|
13798
13800
|
const compactState = compactRunning ? "Compacting conversation" : compactInterrupted ? "Compact interrupted" : autoClearFailed ? `auto-clear skipped${compaction.lastClearCompactError ? `: ${compaction.lastClearCompactError}` : ""}` : autoClearStage ? "Auto-clear complete" : compaction.lastChanged ? compactReactive ? "Compact complete (overflow recovery)" : "Compact complete" : "Compact checked";
|
|
13799
13801
|
const compactDescription = compactDuration ? `${compactState} \xB7 ${compactDuration}` : compactState;
|
|
13802
|
+
const compactPressure = Number(compaction.pressureTokens || compaction.currentEstimatedTokens || 0);
|
|
13803
|
+
const compactReserve = Number(compaction.reserveTokens || 0);
|
|
13800
13804
|
const contextRows = [
|
|
13801
13805
|
{
|
|
13802
13806
|
value: "summary",
|
|
@@ -13807,7 +13811,7 @@ function createUsageContextPanels({
|
|
|
13807
13811
|
{
|
|
13808
13812
|
value: "compaction",
|
|
13809
13813
|
label: "Compaction",
|
|
13810
|
-
description: compactDescription
|
|
13814
|
+
description: `${compactDescription} \xB7 ${fmt(compactPressure)} pressure \xB7 ${fmt(compactReserve)} reserve`,
|
|
13811
13815
|
_action: "compaction"
|
|
13812
13816
|
},
|
|
13813
13817
|
{
|
|
@@ -13889,7 +13893,8 @@ function createUsageContextPanels({
|
|
|
13889
13893
|
if (compactBoundary && compactTrigger) return Math.max(0, compactBoundary - compactTrigger);
|
|
13890
13894
|
return null;
|
|
13891
13895
|
})(),
|
|
13892
|
-
pressureTokens: Number(compaction.lastPressureTokens || compaction.currentEstimatedTokens || 0) || null,
|
|
13896
|
+
pressureTokens: Number(compaction.lastPressureTokens || compaction.pressureTokens || compaction.currentEstimatedTokens || 0) || null,
|
|
13897
|
+
reserveTokens: Number(compaction.reserveTokens || 0) || null,
|
|
13893
13898
|
lastChanged: compaction.lastChanged === true
|
|
13894
13899
|
},
|
|
13895
13900
|
messages: {
|