@aragon-agent/cli 0.6.14 → 0.6.16
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 +14 -0
- package/README.md +295 -238
- package/dist/agent/controller.d.ts +37 -1
- package/dist/agent/controller.js +195 -45
- package/dist/agent/plan-prompt.d.ts +1 -1
- package/dist/agent/plan-prompt.js +4 -3
- package/dist/agent/project-guidance-prompt.d.ts +4 -0
- package/dist/agent/project-guidance-prompt.js +104 -0
- package/dist/agent/reducer.d.ts +2 -0
- package/dist/agent/reducer.js +4 -0
- package/dist/agent/system-prompt.js +4 -0
- package/dist/cli.js +49 -2
- package/dist/commands/builtins.js +26 -28
- package/dist/compaction/child.d.ts +9 -1
- package/dist/compaction/child.js +39 -13
- package/dist/compaction/command.js +20 -10
- package/dist/compaction/compactor.d.ts +33 -204
- package/dist/compaction/compactor.js +311 -726
- package/dist/compaction/context-command.js +14 -3
- package/dist/compaction/digest.d.ts +15 -51
- package/dist/compaction/digest.js +113 -148
- package/dist/compaction/limits.d.ts +19 -142
- package/dist/compaction/limits.js +20 -147
- package/dist/compaction/memory-identity.d.ts +16 -0
- package/dist/compaction/memory-identity.js +63 -0
- package/dist/compaction/memory-input.d.ts +25 -0
- package/dist/compaction/memory-input.js +106 -0
- package/dist/compaction/memory-render.d.ts +4 -0
- package/dist/compaction/memory-render.js +47 -0
- package/dist/compaction/memory-validation.d.ts +21 -0
- package/dist/compaction/memory-validation.js +268 -0
- package/dist/compaction/memory.d.ts +79 -0
- package/dist/compaction/memory.js +118 -0
- package/dist/compaction/meter.d.ts +14 -10
- package/dist/compaction/meter.js +53 -19
- package/dist/compaction/operation.d.ts +18 -0
- package/dist/compaction/operation.js +68 -0
- package/dist/compaction/pressure.d.ts +5 -20
- package/dist/compaction/pressure.js +13 -23
- package/dist/compaction/summarize-call.d.ts +3 -16
- package/dist/compaction/summarize-call.js +20 -20
- package/dist/compaction/summary-prompt.d.ts +29 -66
- package/dist/compaction/summary-prompt.js +132 -178
- package/dist/compaction/types.d.ts +27 -4
- package/dist/compaction/wiring.d.ts +10 -25
- package/dist/compaction/wiring.js +56 -66
- package/dist/config/app-paths.d.ts +10 -0
- package/dist/config/app-paths.js +12 -0
- package/dist/config/env.d.ts +2 -1
- package/dist/config/env.js +13 -1
- package/dist/config/load.d.ts +9 -0
- package/dist/config/load.js +14 -0
- package/dist/config/model-windows.d.ts +91 -0
- package/dist/config/model-windows.js +181 -0
- package/dist/config/schema.d.ts +18 -1
- package/dist/config/schema.js +2 -1
- package/dist/exec/events.d.ts +8 -0
- package/dist/exec/index.js +13 -6
- package/dist/exec/runner.d.ts +8 -0
- package/dist/exec/runner.js +26 -2
- package/dist/input/csiu-keys.d.ts +66 -0
- package/dist/input/csiu-keys.js +170 -0
- package/dist/input/keyboard-enhancement.d.ts +42 -0
- package/dist/input/keyboard-enhancement.js +65 -0
- package/dist/input/stdin-filter.d.ts +9 -0
- package/dist/input/stdin-filter.js +45 -9
- package/dist/input/win32-input-mode.d.ts +61 -0
- package/dist/input/win32-input-mode.js +251 -0
- package/dist/session/persist.d.ts +3 -0
- package/dist/session/persist.js +9 -0
- package/dist/session/store.js +2 -0
- package/dist/session/validate-session.js +34 -0
- package/dist/team/subagent.js +8 -0
- package/dist/ui/App.js +218 -116
- package/dist/ui/BottomStatusRow.d.ts +5 -10
- package/dist/ui/BottomStatusRow.js +7 -11
- package/dist/ui/Composer.d.ts +4 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +6 -2
- package/dist/ui/Header.js +7 -6
- package/dist/ui/PromptInput.d.ts +3 -1
- package/dist/ui/PromptInput.js +32 -41
- package/dist/ui/StatusBar.d.ts +6 -0
- package/dist/ui/StatusBar.js +8 -7
- package/dist/ui/Transcript.js +1 -1
- package/dist/ui/entries/CompactionCard.d.ts +3 -1
- package/dist/ui/entries/CompactionCard.js +7 -5
- package/dist/ui/frame-differ.js +27 -0
- package/dist/ui/frame-observer.js +18 -5
- package/dist/ui/interaction-copy.d.ts +73 -73
- package/dist/ui/interaction-copy.js +30 -38
- package/dist/ui/interaction-hints.d.ts +8 -1
- package/dist/ui/interaction-hints.js +56 -69
- package/dist/ui/layout/AppShell.d.ts +6 -2
- package/dist/ui/layout/AppShell.js +2 -2
- package/dist/ui/layout/OverlayFrame.d.ts +2 -0
- package/dist/ui/layout/OverlayFrame.js +23 -14
- package/dist/ui/layout/ScrollViewport.d.ts +3 -1
- package/dist/ui/layout/ScrollViewport.js +10 -2
- package/dist/ui/layout/budget.d.ts +25 -26
- package/dist/ui/layout/budget.js +29 -22
- package/dist/ui/layout/document-layout.d.ts +2 -2
- package/dist/ui/layout/document-layout.js +2 -3
- package/dist/ui/layout/queue-layout.js +4 -4
- package/dist/ui/layout/status-detail-layout.d.ts +9 -0
- package/dist/ui/layout/status-detail-layout.js +56 -0
- package/dist/ui/layout/status-layout.d.ts +19 -2
- package/dist/ui/layout/status-layout.js +95 -84
- package/dist/ui/layout/wrap-rows.d.ts +2 -0
- package/dist/ui/layout/wrap-rows.js +21 -7
- package/dist/ui/overlays/ConfirmDialog.d.ts +2 -1
- package/dist/ui/overlays/ConfirmDialog.js +22 -3
- package/dist/ui/overlays/HelpOverlay.js +9 -2
- package/dist/ui/overlays/ModelPicker.d.ts +5 -8
- package/dist/ui/overlays/ModelPicker.js +44 -10
- package/dist/ui/overlays/ModelProfileEditor.js +2 -0
- package/dist/ui/overlays/ModelProfilePicker.d.ts +1 -0
- package/dist/ui/overlays/ModelProfilePicker.js +4 -2
- package/dist/ui/overlays/PlanReviewOverlay.d.ts +2 -1
- package/dist/ui/overlays/PlanReviewOverlay.js +7 -4
- package/dist/ui/overlays/QuestionOverlay.d.ts +2 -1
- package/dist/ui/overlays/QuestionOverlay.js +45 -32
- package/dist/ui/overlays/QueueOverlay.js +2 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +1 -0
- package/dist/ui/overlays/SettingsScreen.js +10 -3
- package/dist/ui/run-metrics.d.ts +23 -0
- package/dist/ui/run-metrics.js +27 -0
- package/dist/ui/status-feedback.d.ts +33 -0
- package/dist/ui/status-feedback.js +36 -0
- package/dist/ui/stdout-frame-writer.js +28 -0
- package/package.json +2 -2
|
@@ -1,40 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* `CompactionWiring` — the controller-side glue for context compaction
|
|
3
|
-
* (context-auto-compaction §3.2.1 / §5.2).
|
|
4
|
-
*
|
|
5
|
-
* ASCII ONLY: `src/compaction/**` is inside the glyph scanner's scope.
|
|
6
|
-
*
|
|
7
|
-
* CONSTRUCT BEFORE THE `Agent`, ATTACH AFTER IT (C-12 / D-19), and the split is
|
|
8
|
-
* the whole reason this class has an `attach` method at all:
|
|
9
|
-
*
|
|
10
|
-
* - The PORT has to be INSIDE the `Agent`'s constructor argument, because
|
|
11
|
-
* `AgentConfig.contextManager` has no setter and the loop reads it from the
|
|
12
|
-
* loop context.
|
|
13
|
-
* - The WIRING has to SUBSCRIBE to the `Agent`, because `compaction_end` is
|
|
14
|
-
* where the engine's `applied` verdict arrives and `agent_start` is what
|
|
15
|
-
* resets the per-run cap.
|
|
16
|
-
*
|
|
17
|
-
* `AgentController` cannot do both in one step: `new Agent({...})` runs before
|
|
18
|
-
* every optional subsystem, precisely because `FastWiring`'s constructor
|
|
19
|
-
* subscribes. So THIS CONSTRUCTOR MUST NOT SUBSCRIBE. A field assigned after the
|
|
20
|
-
* `new Agent` call and spread into it conditionally produces a build where the
|
|
21
|
-
* feature is registered, enabled, and SILENTLY ABSENT - the exact shape of
|
|
22
|
-
* failure `controller.ts`'s `toolOutputs` comment documents.
|
|
23
|
-
*
|
|
24
|
-
* `manager()` returns a STABLE object for the controller's lifetime, whose two
|
|
25
|
-
* methods delegate to a compactor that reads config LIVE - which is what lets
|
|
26
|
-
* `/compact off`, `/compact threshold 0.8` and a settings-screen save take effect
|
|
27
|
-
* mid-session without touching the `Agent`.
|
|
28
|
-
*
|
|
29
|
-
* DECLINED COMPACTIONS PRODUCE NO CLI EVENTS (quiet-noop §3.6). A `pressure`
|
|
30
|
-
* compaction that changed nothing, called nothing and spent nothing never emits
|
|
31
|
-
* `compaction_start`, so `onCompactorEvent` never opens a card and `settlePending`
|
|
32
|
-
* finds neither a held record nor a skeleton to synthesize. CORE still emits its
|
|
33
|
-
* own pair for every attempt, which is why `settlePending` still runs - it just
|
|
34
|
-
* refreshes the snapshot and returns. `onCompactorEvent`'s `if (!this.open)`
|
|
35
|
-
* guard is now unreachable for declines and stays for the abort race it was
|
|
36
|
-
* written for.
|
|
37
|
-
*/
|
|
1
|
+
import { shouldCompactAt } from './pressure.js';
|
|
38
2
|
import { DEFAULT_RETRY_POLICY, estimatePromptTokens, initProviders, validateHistory, } from '@aragon-agent/core';
|
|
39
3
|
import { getLogger } from '../logging/logger.js';
|
|
40
4
|
import { mintRunId, writeArchive } from './archive.js';
|
|
@@ -126,6 +90,7 @@ export class CompactionWiring {
|
|
|
126
90
|
* key, not an identity.
|
|
127
91
|
*/
|
|
128
92
|
runId = mintRunId();
|
|
93
|
+
overflowNotified = false;
|
|
129
94
|
constructor(deps) {
|
|
130
95
|
this.deps = deps;
|
|
131
96
|
this.enabled = deps.getConfig().compaction.enabled;
|
|
@@ -167,7 +132,14 @@ export class CompactionWiring {
|
|
|
167
132
|
// setter and `contextManager` is never reassigned; the LIVE switch is inside
|
|
168
133
|
// `shouldCompact`, not in the identity of this object.
|
|
169
134
|
this.port = {
|
|
170
|
-
shouldCompact: (probe) =>
|
|
135
|
+
shouldCompact: (probe) => {
|
|
136
|
+
if (!this.enabled)
|
|
137
|
+
return false;
|
|
138
|
+
const allowed = this.compactor.shouldCompact(probe);
|
|
139
|
+
if (probe.trigger === 'overflow' && !allowed)
|
|
140
|
+
this.notifyOverflow();
|
|
141
|
+
return allowed;
|
|
142
|
+
},
|
|
171
143
|
compact: (ctx) => this.compactor.compact(ctx),
|
|
172
144
|
};
|
|
173
145
|
}
|
|
@@ -193,6 +165,7 @@ export class CompactionWiring {
|
|
|
193
165
|
}
|
|
194
166
|
dispose() {
|
|
195
167
|
this.compactor.abort();
|
|
168
|
+
this.compactor.clearPendingManual();
|
|
196
169
|
this.unsubscribe?.();
|
|
197
170
|
this.unsubscribe = null;
|
|
198
171
|
this.listeners.clear();
|
|
@@ -205,6 +178,8 @@ export class CompactionWiring {
|
|
|
205
178
|
}
|
|
206
179
|
setEnabled(enabled) {
|
|
207
180
|
this.enabled = enabled;
|
|
181
|
+
if (!enabled)
|
|
182
|
+
this.compactor.abort();
|
|
208
183
|
// Turning it back on is the user overriding guard 4, which is the only way
|
|
209
184
|
// out of a self-disable short of a restart.
|
|
210
185
|
if (enabled)
|
|
@@ -215,6 +190,8 @@ export class CompactionWiring {
|
|
|
215
190
|
onConfigChanged(enabled) {
|
|
216
191
|
if (enabled !== undefined && enabled !== this.enabled) {
|
|
217
192
|
this.enabled = enabled;
|
|
193
|
+
if (!enabled)
|
|
194
|
+
this.compactor.abort();
|
|
218
195
|
if (enabled)
|
|
219
196
|
this.compactor.clearSelfDisable();
|
|
220
197
|
}
|
|
@@ -244,8 +221,19 @@ export class CompactionWiring {
|
|
|
244
221
|
* for such a session. A deep reset is idempotent, so both firing costs
|
|
245
222
|
* nothing; deleting the controller's copy "to de-duplicate" reintroduces P0-2.
|
|
246
223
|
*/
|
|
247
|
-
onHistoryReplaced() {
|
|
248
|
-
this.
|
|
224
|
+
onHistoryReplaced(identity) {
|
|
225
|
+
this.compactor.onHistoryReplaced(identity);
|
|
226
|
+
}
|
|
227
|
+
getIdentity() { return this.compactor.getIdentity(); }
|
|
228
|
+
notifyOverflow() {
|
|
229
|
+
const p = this.meter.current();
|
|
230
|
+
if (this.overflowNotified || !Number.isFinite(p.occupied) || p.occupied < 0 ||
|
|
231
|
+
!Number.isFinite(p.contextWindow) || p.contextWindow <= 0 ||
|
|
232
|
+
shouldCompactAt(p, this.deps.getConfig().compaction.threshold))
|
|
233
|
+
return;
|
|
234
|
+
this.overflowNotified = true;
|
|
235
|
+
this.deps.notify('warn', 'Context limit reached before the configured compaction threshold. ' +
|
|
236
|
+
'Use /compact or correct contextWindow.');
|
|
249
237
|
}
|
|
250
238
|
/**
|
|
251
239
|
* The per-child manager factory, or `null` when children get none
|
|
@@ -269,7 +257,7 @@ export class CompactionWiring {
|
|
|
269
257
|
// SINK (AC-H12). It is real money on the summarizer's price table, and a
|
|
270
258
|
// background repair inside a background worker is exactly the spend a
|
|
271
259
|
// user would otherwise never see.
|
|
272
|
-
onUsage: (usage) => this.emit({ type: 'usage', usage }),
|
|
260
|
+
onUsage: (usage, billing) => this.emit({ type: 'usage', usage, ...billing }),
|
|
273
261
|
});
|
|
274
262
|
}
|
|
275
263
|
// -----------------------------------------------------------------------
|
|
@@ -284,28 +272,13 @@ export class CompactionWiring {
|
|
|
284
272
|
clearPendingManual() {
|
|
285
273
|
this.compactor.clearPendingManual();
|
|
286
274
|
}
|
|
287
|
-
/**
|
|
288
|
-
* The IDLE `/compact` path (§4.4 / D-25).
|
|
289
|
-
*
|
|
290
|
-
* IT VALIDATES BEFORE IT SPLICES, AND THAT IS NOT OPTIONAL (P1-7). When the
|
|
291
|
-
* agent is idle there is no loop, so §3.3 step 5 does not run — and the only
|
|
292
|
-
* way to replace the history is `Agent.replaceMessages`, which validates
|
|
293
|
-
* nothing. D-4 declares that gate unbypassable and R-2 calls an invalid splice
|
|
294
|
-
* the worst possible outcome, so this path mirrors the engine's steps 3-5
|
|
295
|
-
* exactly, INCLUDING the failure vocabulary: the card, the log and
|
|
296
|
-
* `/compact status` cannot tell the two paths apart.
|
|
297
|
-
*
|
|
298
|
-
* No watchdog handling is needed — `runLoopWithLifecycle`'s `finally` already
|
|
299
|
-
* called `watchdog.stop()`, so nothing is armed while idle.
|
|
300
|
-
*
|
|
301
|
-
* ITS CALLER MUST ADOPT `messages` — THE ARCHIVE IS ALREADY WRITTEN BY THE
|
|
302
|
-
* TIME THIS RETURNS (hardening RV-7). This path settles `applied: true` here,
|
|
303
|
-
* one statement before the caller splices, so the "never archive a live
|
|
304
|
-
* history" rule holds only because that caller applies unconditionally. That is
|
|
305
|
-
* a fact about one call site rather than an invariant, which is why it is
|
|
306
|
-
* written down where it can be checked.
|
|
307
|
-
*/
|
|
275
|
+
/** Prepare, validate and synchronously adopt before publishing a successful verdict. */
|
|
308
276
|
async compactNow(args) {
|
|
277
|
+
if (!this.enabled)
|
|
278
|
+
return { ok: false, reason: 'disabled' };
|
|
279
|
+
if (this.compactor.sessionTotals().inFlight)
|
|
280
|
+
return { ok: false, reason: 'already_running' };
|
|
281
|
+
this.compactor.onRunStart();
|
|
309
282
|
this.compactor.queueManual(args.instructions);
|
|
310
283
|
// BOTH SIDES FROM CORE'S OWN ESTIMATOR, exactly as the in-loop path gets them
|
|
311
284
|
// from `compaction_end` (§5.1). There is no engine event here to supply them,
|
|
@@ -342,6 +315,19 @@ export class CompactionWiring {
|
|
|
342
315
|
this.settlePending({ applied: false, reason, tokensBefore, tokensAfter: tokensBefore });
|
|
343
316
|
return { ok: false, reason };
|
|
344
317
|
}
|
|
318
|
+
if (args.signal.aborted || args.isCurrent?.() === false || !args.adopt) {
|
|
319
|
+
const reason = args.signal.aborted ? 'aborted' : 'stale_history';
|
|
320
|
+
this.settlePending({ applied: false, reason, tokensBefore, tokensAfter: tokensBefore });
|
|
321
|
+
return { ok: false, reason };
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
args.adopt(outcome.messages);
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
const reason = `adoption_failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
328
|
+
this.settlePending({ applied: false, reason, tokensBefore, tokensAfter: tokensBefore });
|
|
329
|
+
return { ok: false, reason };
|
|
330
|
+
}
|
|
345
331
|
this.settlePending({
|
|
346
332
|
applied: true,
|
|
347
333
|
tokensBefore,
|
|
@@ -387,6 +373,7 @@ export class CompactionWiring {
|
|
|
387
373
|
...(totals.declined > 0 ? { declined: totals.declined } : {}),
|
|
388
374
|
tokensReclaimed: totals.tokensReclaimed,
|
|
389
375
|
usage: totals.usage,
|
|
376
|
+
costUsd: totals.costUsd,
|
|
390
377
|
pricingUnknown: totals.pricingUnknown,
|
|
391
378
|
inFlight: totals.inFlight,
|
|
392
379
|
selfDisabled: totals.selfDisabled,
|
|
@@ -460,9 +447,13 @@ export class CompactionWiring {
|
|
|
460
447
|
onAgentEvent(event) {
|
|
461
448
|
if (event.type === 'agent_start') {
|
|
462
449
|
this.compactor.onRunStart();
|
|
450
|
+
this.overflowNotified = false;
|
|
463
451
|
return;
|
|
464
452
|
}
|
|
453
|
+
if (event.type === 'agent_end')
|
|
454
|
+
this.compactor.onRunEnd();
|
|
465
455
|
if (event.type === 'turn_end') {
|
|
456
|
+
this.meter.onTurnEnd(event.usage);
|
|
466
457
|
// THE MEASUREMENT IS THE METER'S (context-usage-gauge-accuracy §3.3.4). It
|
|
467
458
|
// subscribes to `turn_end` on the same stream and recomputes the offset,
|
|
468
459
|
// the prefix length and the pressure there; the two calls that used to
|
|
@@ -501,6 +492,7 @@ export class CompactionWiring {
|
|
|
501
492
|
// engine cannot report `true` without a `replace` outcome, which means the
|
|
502
493
|
// compactor's own record already exists — so its zeroed counts are never
|
|
503
494
|
// rendered.
|
|
495
|
+
this.compactor.settleOperation(verdict);
|
|
504
496
|
const record = this.pending ?? this.skeletonRecord();
|
|
505
497
|
this.pending = null;
|
|
506
498
|
this.open = null;
|
|
@@ -533,7 +525,6 @@ export class CompactionWiring {
|
|
|
533
525
|
mode: verdict.applied ? record.mode : 'none',
|
|
534
526
|
};
|
|
535
527
|
if (verdict.applied) {
|
|
536
|
-
this.compactor.recordApplied(settled.tokensBefore, settled.tokensAfter);
|
|
537
528
|
// SITE 1 OF THE FOUR INVALIDATION SITES (hardening §3.2.3), and THE LOAD-
|
|
538
529
|
// BEARING LINE OF THE P0-1 FIX (context-usage-gauge-accuracy I-9).
|
|
539
530
|
//
|
|
@@ -565,9 +556,8 @@ export class CompactionWiring {
|
|
|
565
556
|
* archive a history that is still live" - and the two paths reach that verdict
|
|
566
557
|
* at different moments relative to the splice. In loop, the ENGINE decides
|
|
567
558
|
* after `validateHistory`, so this runs AFTER adoption. On the manual path the
|
|
568
|
-
* WIRING
|
|
569
|
-
*
|
|
570
|
-
* for its one caller (RV-7).
|
|
559
|
+
* WIRING invokes the host adoption callback before settling, so both paths
|
|
560
|
+
* archive only histories that have actually been replaced.
|
|
571
561
|
*
|
|
572
562
|
* BEST-EFFORT AND SYNCHRONOUS-BUT-GUARDED. `writeArchive` never throws and
|
|
573
563
|
* logs its own failure; a failed archive never affects the compaction, and it
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* ~/.aragon-agent/
|
|
13
13
|
* ├── config.json persisted config (0600 on POSIX)
|
|
14
14
|
* ├── config.json.bak `aragon config edit` pre-edit backup
|
|
15
|
+
* ├── model-windows.json per-model context windows, user-edited
|
|
15
16
|
* ├── prompt-history.jsonl submitted prompts, append-only (0600 on POSIX)
|
|
16
17
|
* ├── state.json UI bookkeeping scalars (0600 on POSIX)
|
|
17
18
|
* ├── update-state.json auto-update coordination (0600 on POSIX)
|
|
@@ -72,6 +73,15 @@ export declare function getHomeRoot(): string;
|
|
|
72
73
|
export declare function getConfigPath(): string;
|
|
73
74
|
/** Written by `aragon config edit` before spawning the editor. */
|
|
74
75
|
export declare function getConfigBackupPath(): string;
|
|
76
|
+
/**
|
|
77
|
+
* `<home>/model-windows.json` — per-model context windows the user declares by
|
|
78
|
+
* hand for ids no table knows (`kimi-k3`, a gateway alias, a self-hosted model).
|
|
79
|
+
*
|
|
80
|
+
* READ-ONLY from the application's side: nothing ever writes or creates it, so
|
|
81
|
+
* a user's file cannot be clobbered by an upgrade and its absence is not an
|
|
82
|
+
* error. `config/model-windows.ts` owns the format and the lookup.
|
|
83
|
+
*/
|
|
84
|
+
export declare function getModelWindowsPath(): string;
|
|
75
85
|
/**
|
|
76
86
|
* `<home>/prompt-history.jsonl` — the prompts the user submitted, one JSON
|
|
77
87
|
* object per line. Deliberately NOT under `logs/`: that directory is governed
|
package/dist/config/app-paths.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* ~/.aragon-agent/
|
|
13
13
|
* ├── config.json persisted config (0600 on POSIX)
|
|
14
14
|
* ├── config.json.bak `aragon config edit` pre-edit backup
|
|
15
|
+
* ├── model-windows.json per-model context windows, user-edited
|
|
15
16
|
* ├── prompt-history.jsonl submitted prompts, append-only (0600 on POSIX)
|
|
16
17
|
* ├── state.json UI bookkeeping scalars (0600 on POSIX)
|
|
17
18
|
* ├── update-state.json auto-update coordination (0600 on POSIX)
|
|
@@ -129,6 +130,17 @@ export function getConfigPath() {
|
|
|
129
130
|
export function getConfigBackupPath() {
|
|
130
131
|
return join(resolution.root, 'config.json.bak');
|
|
131
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* `<home>/model-windows.json` — per-model context windows the user declares by
|
|
135
|
+
* hand for ids no table knows (`kimi-k3`, a gateway alias, a self-hosted model).
|
|
136
|
+
*
|
|
137
|
+
* READ-ONLY from the application's side: nothing ever writes or creates it, so
|
|
138
|
+
* a user's file cannot be clobbered by an upgrade and its absence is not an
|
|
139
|
+
* error. `config/model-windows.ts` owns the format and the lookup.
|
|
140
|
+
*/
|
|
141
|
+
export function getModelWindowsPath() {
|
|
142
|
+
return join(resolution.root, 'model-windows.json');
|
|
143
|
+
}
|
|
132
144
|
/**
|
|
133
145
|
* `<home>/prompt-history.jsonl` — the prompts the user submitted, one JSON
|
|
134
146
|
* object per line. Deliberately NOT under `logs/`: that directory is governed
|
package/dist/config/env.d.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* - Overrides: ARAGON_PROVIDER, ARAGON_MODEL, ARAGON_BASE_URL, ARAGON_THINKING,
|
|
8
8
|
* ARAGON_SHOW_THINKING,
|
|
9
9
|
* ARAGON_MAX_TOKENS (a number, or `auto` / `0` for the per-model ceiling),
|
|
10
|
-
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PASTE,
|
|
10
|
+
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PASTE, ARAGON_KEYBOARD_ENHANCEMENT,
|
|
11
|
+
* ARAGON_PLAN, ARAGON_TEAM,
|
|
11
12
|
* ARAGON_TEAM_MAX, ARAGON_RETRY, ARAGON_RETRY_MAX, ARAGON_FAST,
|
|
12
13
|
* ARAGON_FAST_PROVIDER, ARAGON_FAST_MODEL, ARAGON_FAST_BASE_URL,
|
|
13
14
|
* ARAGON_LOG_LEVEL,
|
package/dist/config/env.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* - Overrides: ARAGON_PROVIDER, ARAGON_MODEL, ARAGON_BASE_URL, ARAGON_THINKING,
|
|
8
8
|
* ARAGON_SHOW_THINKING,
|
|
9
9
|
* ARAGON_MAX_TOKENS (a number, or `auto` / `0` for the per-model ceiling),
|
|
10
|
-
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PASTE,
|
|
10
|
+
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PASTE, ARAGON_KEYBOARD_ENHANCEMENT,
|
|
11
|
+
* ARAGON_PLAN, ARAGON_TEAM,
|
|
11
12
|
* ARAGON_TEAM_MAX, ARAGON_RETRY, ARAGON_RETRY_MAX, ARAGON_FAST,
|
|
12
13
|
* ARAGON_FAST_PROVIDER, ARAGON_FAST_MODEL, ARAGON_FAST_BASE_URL,
|
|
13
14
|
* ARAGON_LOG_LEVEL,
|
|
@@ -151,6 +152,17 @@ export function readEnvConfig() {
|
|
|
151
152
|
if (paste !== undefined && paste.length > 0) {
|
|
152
153
|
partial.paste = paste === '1' || paste === 'true' || paste === 'on' || paste === 'yes';
|
|
153
154
|
}
|
|
155
|
+
// ARAGON_KEYBOARD_ENHANCEMENT=0 stops the Shift+Enter mode push and its
|
|
156
|
+
// translation -- the one-env revert to pre-enhancement input handling.
|
|
157
|
+
// Parsed by the POSITIVE list, exactly as its three neighbours above are.
|
|
158
|
+
const keyboardEnhancement = process.env.ARAGON_KEYBOARD_ENHANCEMENT?.trim().toLowerCase();
|
|
159
|
+
if (keyboardEnhancement !== undefined && keyboardEnhancement.length > 0) {
|
|
160
|
+
partial.keyboardEnhancement =
|
|
161
|
+
keyboardEnhancement === '1' ||
|
|
162
|
+
keyboardEnhancement === 'true' ||
|
|
163
|
+
keyboardEnhancement === 'on' ||
|
|
164
|
+
keyboardEnhancement === 'yes';
|
|
165
|
+
}
|
|
154
166
|
// ARAGON_SCROLL_RESUME_MS=<n> — idle delay before a paused viewport returns to
|
|
155
167
|
// the newest line; `0` disables auto-resume. THE ONLY CHANNEL BESIDES THE
|
|
156
168
|
// CONFIG FILE, because §6.1 gives this key no CLI flag on purpose. `load.ts`
|
package/dist/config/load.d.ts
CHANGED
|
@@ -59,6 +59,15 @@ export interface CliFlags {
|
|
|
59
59
|
* `false` on every run that passed no flag at all.
|
|
60
60
|
*/
|
|
61
61
|
paste?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* `--keyboard-enhancement` ⇒ true, `--no-keyboard-enhancement` ⇒ false,
|
|
64
|
+
* absent ⇒ undefined.
|
|
65
|
+
*
|
|
66
|
+
* DECLARED AS A PAIR and carried through `toFlags`, for the reason the
|
|
67
|
+
* `paste` note above records: the key is persisted and defaults to
|
|
68
|
+
* `true`, so a lone negative flag must not read as silence.
|
|
69
|
+
*/
|
|
70
|
+
keyboardEnhancement?: boolean;
|
|
62
71
|
/** `--no-skills` ⇒ false. Turns the whole skill subsystem off (§7.5). */
|
|
63
72
|
skills?: boolean;
|
|
64
73
|
/** `--skill <name>` ×N — force Level 2 injection for this run. */
|
package/dist/config/load.js
CHANGED
|
@@ -78,6 +78,19 @@ function resolvePaste(flags, env, file) {
|
|
|
78
78
|
return file.paste;
|
|
79
79
|
return DEFAULT_CONFIG.paste;
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Keyboard enhancement: flag > env > file > default `true`. Shaped exactly
|
|
83
|
+
* like `resolvePaste` above, for the reason its own note gives.
|
|
84
|
+
*/
|
|
85
|
+
function resolveKeyboardEnhancement(flags, env, file) {
|
|
86
|
+
if (flags.keyboardEnhancement !== undefined)
|
|
87
|
+
return flags.keyboardEnhancement;
|
|
88
|
+
if (env.keyboardEnhancement !== undefined)
|
|
89
|
+
return env.keyboardEnhancement;
|
|
90
|
+
if (file.keyboardEnhancement !== undefined)
|
|
91
|
+
return file.keyboardEnhancement;
|
|
92
|
+
return DEFAULT_CONFIG.keyboardEnhancement;
|
|
93
|
+
}
|
|
81
94
|
/**
|
|
82
95
|
* Idle scroll-resume delay: env › file › default 5000, clamped to [0, 120000].
|
|
83
96
|
*
|
|
@@ -585,6 +598,7 @@ export function loadConfig(flags = {}, options = {}) {
|
|
|
585
598
|
mouse: resolveMouse(flags, env.partial, file),
|
|
586
599
|
mouseSelect: resolveMouseSelect(flags, env.partial, file),
|
|
587
600
|
paste: resolvePaste(flags, env.partial, file),
|
|
601
|
+
keyboardEnhancement: resolveKeyboardEnhancement(flags, env.partial, file),
|
|
588
602
|
scrollResumeMs: resolveScrollResumeMs(env.partial, file),
|
|
589
603
|
// From `<home>/state.json`, not the config file — see `ui-state.ts`.
|
|
590
604
|
submitCount: readSubmitCount(),
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-declared context windows, read from `~/.aragon-agent/model-windows.json`
|
|
3
|
+
* (path owned by `app-paths.ts`).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS FILE EXISTS. `ModelRegistry.getContextWindow` resolves a window from
|
|
6
|
+
* API discovery, the builtin model lists and the ten-entry catalog - and for
|
|
7
|
+
* every other id (a gateway alias, a self-hosted model, a release too new for
|
|
8
|
+
* the table) it returns the explicitly-untrusted 128k `fallback`. The gauge
|
|
9
|
+
* then renders `?` forever, by design: it refuses to divide by a guessed
|
|
10
|
+
* denominator. The one number that can make the denominator real is the user's
|
|
11
|
+
* own, and `config.json`'s `contextWindow` key can only hold ONE of it. This
|
|
12
|
+
* file is the per-model form of the same assertion.
|
|
13
|
+
*
|
|
14
|
+
* THE APPLICATION NEVER WRITES IT. A file the user owns by hand cannot be
|
|
15
|
+
* clobbered by an upgrade, and its absence is the ordinary case, not an error.
|
|
16
|
+
*
|
|
17
|
+
* ASCII ONLY: `src/config/**` is inside the glyph scanner's scope
|
|
18
|
+
* (`glyphs.test.ts::inScope`).
|
|
19
|
+
*/
|
|
20
|
+
/** Structural bounds only; there is no policy surface to tune here. */
|
|
21
|
+
export interface ModelWindowsLimits {
|
|
22
|
+
/** Entries beyond this many are dropped, in file order. */
|
|
23
|
+
readonly maxEntries: number;
|
|
24
|
+
/** Keys longer than this are dropped rather than trusted as ids. */
|
|
25
|
+
readonly maxKeyLength: number;
|
|
26
|
+
/** A window below the smallest real context (1k) is a typo, not a limit. */
|
|
27
|
+
readonly minWindow: number;
|
|
28
|
+
/** A window above 1e9 tokens is also a typo; no model ships that today. */
|
|
29
|
+
readonly maxWindow: number;
|
|
30
|
+
}
|
|
31
|
+
export declare const MODEL_WINDOWS_LIMITS: ModelWindowsLimits;
|
|
32
|
+
/** Normalized model id -> declared window. Keys are stored ALREADY normalized. */
|
|
33
|
+
export type ModelWindowTable = ReadonlyMap<string, number>;
|
|
34
|
+
/**
|
|
35
|
+
* Same normalization core's `catalogModelId` applies, restated here because it
|
|
36
|
+
* is not part of Core's public surface and a second exporter would be a
|
|
37
|
+
* dependency on an internal. Keep the two in step: a date-stamped id
|
|
38
|
+
* (`kimi-k3-20260716`) and its bare form must resolve to one entry.
|
|
39
|
+
*/
|
|
40
|
+
export declare function normalizeModelKey(modelId: string): string;
|
|
41
|
+
/** What a parse produced, and whether the FILE itself was at fault. */
|
|
42
|
+
export interface ModelWindowsParse {
|
|
43
|
+
table: ModelWindowTable;
|
|
44
|
+
/**
|
|
45
|
+
* True only when the text could not be understood at all (bad JSON, wrong
|
|
46
|
+
* top-level type). A valid file with zero USABLE entries is `false`: the
|
|
47
|
+
* user wrote JSON, the values were the problem, and every dead value was
|
|
48
|
+
* already dropped rather than trusted.
|
|
49
|
+
*/
|
|
50
|
+
malformed: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Parse already-decoded text into a table. NEVER THROWS - a hand-edited file
|
|
54
|
+
* is the rule here, not the exception, and a typo must cost the user one dead
|
|
55
|
+
* entry, not the session. Two accepted shapes:
|
|
56
|
+
*
|
|
57
|
+
* { "version": 1, "windows": { "kimi-k3": 1048576 } }
|
|
58
|
+
* { "kimi-k3": 1048576 } (bare map, same effect)
|
|
59
|
+
*
|
|
60
|
+
* The wrapper exists so the format can grow fields without a migration; the
|
|
61
|
+
* bare map is accepted because the first shape a user types is the simple one.
|
|
62
|
+
* A leading UTF-8 BOM is tolerated: `readFileSync(..., 'utf8')` does not strip
|
|
63
|
+
* it, `JSON.parse` cannot parse it, and Notepad writes it by default.
|
|
64
|
+
*/
|
|
65
|
+
export declare function parseModelWindowsResult(text: string): ModelWindowsParse;
|
|
66
|
+
/** The table alone, for callers that do not care why it might be empty. */
|
|
67
|
+
export declare function parseModelWindows(text: string): ModelWindowTable;
|
|
68
|
+
/**
|
|
69
|
+
* mtime-cached lookup over the file.
|
|
70
|
+
*
|
|
71
|
+
* WHY A STAT PER LOOKUP RATHER THAN A LOAD AT STARTUP: `getModelInfoFor` is
|
|
72
|
+
* called on every measurement, and the meter's `refreshValidity` re-reads the
|
|
73
|
+
* window precisely so a moved denominator (a `/model` switch, a live config
|
|
74
|
+
* edit) re-measures without a relaunch. A user saving this file mid-session
|
|
75
|
+
* gets the same treatment - `statSync` is microseconds, and the read itself
|
|
76
|
+
* only happens when the stamp actually changes.
|
|
77
|
+
*
|
|
78
|
+
* THE STAMP IS mtimeMs+size, so a rewrite that lands in the SAME millisecond
|
|
79
|
+
* with the SAME byte count serves the previous table once. Squeezing that
|
|
80
|
+
* window shut would need a content hash per lookup, which is exactly the cost
|
|
81
|
+
* the stamp exists to avoid; the next differing write clears it regardless.
|
|
82
|
+
*/
|
|
83
|
+
export declare class ModelWindows {
|
|
84
|
+
private readonly path;
|
|
85
|
+
private table;
|
|
86
|
+
private stamp;
|
|
87
|
+
constructor(path?: string);
|
|
88
|
+
/** The declared window for `modelId`, or `undefined` when nobody declared one. */
|
|
89
|
+
lookup(modelId: string): number | undefined;
|
|
90
|
+
private refresh;
|
|
91
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-declared context windows, read from `~/.aragon-agent/model-windows.json`
|
|
3
|
+
* (path owned by `app-paths.ts`).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS FILE EXISTS. `ModelRegistry.getContextWindow` resolves a window from
|
|
6
|
+
* API discovery, the builtin model lists and the ten-entry catalog - and for
|
|
7
|
+
* every other id (a gateway alias, a self-hosted model, a release too new for
|
|
8
|
+
* the table) it returns the explicitly-untrusted 128k `fallback`. The gauge
|
|
9
|
+
* then renders `?` forever, by design: it refuses to divide by a guessed
|
|
10
|
+
* denominator. The one number that can make the denominator real is the user's
|
|
11
|
+
* own, and `config.json`'s `contextWindow` key can only hold ONE of it. This
|
|
12
|
+
* file is the per-model form of the same assertion.
|
|
13
|
+
*
|
|
14
|
+
* THE APPLICATION NEVER WRITES IT. A file the user owns by hand cannot be
|
|
15
|
+
* clobbered by an upgrade, and its absence is the ordinary case, not an error.
|
|
16
|
+
*
|
|
17
|
+
* ASCII ONLY: `src/config/**` is inside the glyph scanner's scope
|
|
18
|
+
* (`glyphs.test.ts::inScope`).
|
|
19
|
+
*/
|
|
20
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
21
|
+
import { getLogger } from '../logging/logger.js';
|
|
22
|
+
import { getModelWindowsPath } from './app-paths.js';
|
|
23
|
+
export const MODEL_WINDOWS_LIMITS = {
|
|
24
|
+
maxEntries: 512,
|
|
25
|
+
maxKeyLength: 200,
|
|
26
|
+
minWindow: 1_024,
|
|
27
|
+
maxWindow: 1_000_000_000,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Same normalization core's `catalogModelId` applies, restated here because it
|
|
31
|
+
* is not part of Core's public surface and a second exporter would be a
|
|
32
|
+
* dependency on an internal. Keep the two in step: a date-stamped id
|
|
33
|
+
* (`kimi-k3-20260716`) and its bare form must resolve to one entry.
|
|
34
|
+
*/
|
|
35
|
+
export function normalizeModelKey(modelId) {
|
|
36
|
+
return modelId.trim().toLowerCase()
|
|
37
|
+
.replace(/^(models|openai|anthropic|google)\//, '')
|
|
38
|
+
.replace(/-(?:\d{4}-\d{2}-\d{2}|\d{8})$/, '');
|
|
39
|
+
}
|
|
40
|
+
function isWindowValue(value) {
|
|
41
|
+
return typeof value === 'number' && Number.isSafeInteger(value) &&
|
|
42
|
+
value >= MODEL_WINDOWS_LIMITS.minWindow && value <= MODEL_WINDOWS_LIMITS.maxWindow;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse already-decoded text into a table. NEVER THROWS - a hand-edited file
|
|
46
|
+
* is the rule here, not the exception, and a typo must cost the user one dead
|
|
47
|
+
* entry, not the session. Two accepted shapes:
|
|
48
|
+
*
|
|
49
|
+
* { "version": 1, "windows": { "kimi-k3": 1048576 } }
|
|
50
|
+
* { "kimi-k3": 1048576 } (bare map, same effect)
|
|
51
|
+
*
|
|
52
|
+
* The wrapper exists so the format can grow fields without a migration; the
|
|
53
|
+
* bare map is accepted because the first shape a user types is the simple one.
|
|
54
|
+
* A leading UTF-8 BOM is tolerated: `readFileSync(..., 'utf8')` does not strip
|
|
55
|
+
* it, `JSON.parse` cannot parse it, and Notepad writes it by default.
|
|
56
|
+
*/
|
|
57
|
+
export function parseModelWindowsResult(text) {
|
|
58
|
+
const body = text.replace(/^\uFEFF/, '');
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = JSON.parse(body);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return { table: new Map(), malformed: true };
|
|
65
|
+
}
|
|
66
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
67
|
+
return { table: new Map(), malformed: true };
|
|
68
|
+
}
|
|
69
|
+
const record = parsed;
|
|
70
|
+
const source = record.windows !== undefined && record.windows !== null &&
|
|
71
|
+
typeof record.windows === 'object' && !Array.isArray(record.windows)
|
|
72
|
+
? record.windows
|
|
73
|
+
: record;
|
|
74
|
+
const table = new Map();
|
|
75
|
+
for (const [rawKey, rawValue] of Object.entries(source)) {
|
|
76
|
+
const key = normalizeModelKey(rawKey);
|
|
77
|
+
if (key.length === 0 || key.length > MODEL_WINDOWS_LIMITS.maxKeyLength)
|
|
78
|
+
continue;
|
|
79
|
+
if (!isWindowValue(rawValue))
|
|
80
|
+
continue;
|
|
81
|
+
table.set(key, rawValue);
|
|
82
|
+
if (table.size >= MODEL_WINDOWS_LIMITS.maxEntries)
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
return { table, malformed: false };
|
|
86
|
+
}
|
|
87
|
+
/** The table alone, for callers that do not care why it might be empty. */
|
|
88
|
+
export function parseModelWindows(text) {
|
|
89
|
+
return parseModelWindowsResult(text).table;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Decode the raw bytes the way a Windows text editor may have saved them.
|
|
93
|
+
*
|
|
94
|
+
* `readFileSync(path, 'utf8')` alone fails THREE ways on this machine's most
|
|
95
|
+
* common editors: a UTF-8 BOM survives into the string and kills `JSON.parse`,
|
|
96
|
+
* and Notepad's "Unicode" saves are UTF-16LE (or BE), which decode as NUL-laden
|
|
97
|
+
* mojibake. All three are silent feature-death for exactly the user who just
|
|
98
|
+
* tried to configure it, so the sniff happens before any parse.
|
|
99
|
+
*/
|
|
100
|
+
function decodeModelWindowsFile(bytes) {
|
|
101
|
+
if (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xfe) {
|
|
102
|
+
return bytes.subarray(2).toString('utf16le');
|
|
103
|
+
}
|
|
104
|
+
if (bytes.length >= 2 && bytes[0] === 0xfe && bytes[1] === 0xff) {
|
|
105
|
+
// Big-endian UTF-16: Node has no utf16be decoder, so swap pairs in place
|
|
106
|
+
// on a COPY and reuse the little-endian decoder.
|
|
107
|
+
const swapped = Buffer.from(bytes.subarray(2));
|
|
108
|
+
for (let i = 0; i + 1 < swapped.length; i += 2) {
|
|
109
|
+
const high = swapped[i];
|
|
110
|
+
swapped[i] = swapped[i + 1];
|
|
111
|
+
swapped[i + 1] = high;
|
|
112
|
+
}
|
|
113
|
+
return swapped.toString('utf16le');
|
|
114
|
+
}
|
|
115
|
+
if (bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) {
|
|
116
|
+
return bytes.subarray(3).toString('utf8');
|
|
117
|
+
}
|
|
118
|
+
return bytes.toString('utf8');
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* mtime-cached lookup over the file.
|
|
122
|
+
*
|
|
123
|
+
* WHY A STAT PER LOOKUP RATHER THAN A LOAD AT STARTUP: `getModelInfoFor` is
|
|
124
|
+
* called on every measurement, and the meter's `refreshValidity` re-reads the
|
|
125
|
+
* window precisely so a moved denominator (a `/model` switch, a live config
|
|
126
|
+
* edit) re-measures without a relaunch. A user saving this file mid-session
|
|
127
|
+
* gets the same treatment - `statSync` is microseconds, and the read itself
|
|
128
|
+
* only happens when the stamp actually changes.
|
|
129
|
+
*
|
|
130
|
+
* THE STAMP IS mtimeMs+size, so a rewrite that lands in the SAME millisecond
|
|
131
|
+
* with the SAME byte count serves the previous table once. Squeezing that
|
|
132
|
+
* window shut would need a content hash per lookup, which is exactly the cost
|
|
133
|
+
* the stamp exists to avoid; the next differing write clears it regardless.
|
|
134
|
+
*/
|
|
135
|
+
export class ModelWindows {
|
|
136
|
+
path;
|
|
137
|
+
table = new Map();
|
|
138
|
+
stamp = null;
|
|
139
|
+
constructor(path = getModelWindowsPath()) {
|
|
140
|
+
this.path = path;
|
|
141
|
+
}
|
|
142
|
+
/** The declared window for `modelId`, or `undefined` when nobody declared one. */
|
|
143
|
+
lookup(modelId) {
|
|
144
|
+
this.refresh();
|
|
145
|
+
return this.table.get(normalizeModelKey(modelId));
|
|
146
|
+
}
|
|
147
|
+
refresh() {
|
|
148
|
+
let stamp;
|
|
149
|
+
try {
|
|
150
|
+
const stats = statSync(this.path);
|
|
151
|
+
stamp = { mtimeMs: stats.mtimeMs, size: stats.size };
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
// A deleted file (ENOENT) is a decision: drop the entries. Any OTHER
|
|
155
|
+
// failure (EACCES, an antivirus lock) is transient, and blanking every
|
|
156
|
+
// declared window because a scanner held the file open for 50 ms would
|
|
157
|
+
// swing the gauge to `?` for a reason the user cannot see - keep the
|
|
158
|
+
// last good table and try again on the next lookup.
|
|
159
|
+
if (error.code === 'ENOENT') {
|
|
160
|
+
this.stamp = null;
|
|
161
|
+
this.table = new Map();
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (stamp.mtimeMs === this.stamp?.mtimeMs && stamp.size === this.stamp.size)
|
|
166
|
+
return;
|
|
167
|
+
let bytes;
|
|
168
|
+
try {
|
|
169
|
+
bytes = readFileSync(this.path);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return; // Appeared in the stat, gone in the read: try again next lookup.
|
|
173
|
+
}
|
|
174
|
+
const result = parseModelWindowsResult(decodeModelWindowsFile(bytes));
|
|
175
|
+
if (result.malformed) {
|
|
176
|
+
getLogger().warn('config', 'model-windows.json could not be parsed; ignoring it until it is fixed', { path: this.path });
|
|
177
|
+
}
|
|
178
|
+
this.table = result.table;
|
|
179
|
+
this.stamp = stamp;
|
|
180
|
+
}
|
|
181
|
+
}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -577,7 +577,7 @@ export interface CompactionConfig {
|
|
|
577
577
|
* SESSION'S OWN MODEL, and the first compaction is a ~30 k-token call on it.
|
|
578
578
|
*/
|
|
579
579
|
useFastTier: boolean;
|
|
580
|
-
/**
|
|
580
|
+
/** Legacy truncate remains readable; all failures now preserve history. */
|
|
581
581
|
onFailure: CompactionFailureMode;
|
|
582
582
|
/**
|
|
583
583
|
* Give `task` children their own compaction
|
|
@@ -786,6 +786,14 @@ export interface PersistedConfig {
|
|
|
786
786
|
* asked for; a kill switch is what a bug report needs.
|
|
787
787
|
*/
|
|
788
788
|
paste: boolean;
|
|
789
|
+
/**
|
|
790
|
+
* Ask the terminal to report modified keys distinctly so Shift+Enter can
|
|
791
|
+
* insert a newline (win32-input-mode on Windows, kitty disambiguate +
|
|
792
|
+
* modifyOtherKeys elsewhere). A KILL SWITCH in the exact sense `paste`
|
|
793
|
+
* above records: the encoding tables are structural and live in
|
|
794
|
+
* `input/win32-input-mode.ts` / `input/csiu-keys.ts`.
|
|
795
|
+
*/
|
|
796
|
+
keyboardEnhancement: boolean;
|
|
789
797
|
/** Idle ms before a paused viewport returns to the newest line; `0` disables. */
|
|
790
798
|
scrollResumeMs: number;
|
|
791
799
|
/** Start every session in PLAN mode (plan-mode §4.4). */
|
|
@@ -971,6 +979,15 @@ export interface CliConfig {
|
|
|
971
979
|
* reach none of them.
|
|
972
980
|
*/
|
|
973
981
|
paste: boolean;
|
|
982
|
+
/**
|
|
983
|
+
* Whether this session may push the keyboard-enhancement modes.
|
|
984
|
+
*
|
|
985
|
+
* IT MUST EXIST HERE AND NOT ONLY IN `PersistedConfig`, for the reason
|
|
986
|
+
* `paste` above records: `runInteractive` reads it -- where `config`
|
|
987
|
+
* is a `CliConfig` -- to decide the filter's `enhancedKeys` feature
|
|
988
|
+
* and the `enableKeyboardEnhancement` push.
|
|
989
|
+
*/
|
|
990
|
+
keyboardEnhancement: boolean;
|
|
974
991
|
/** Idle ms before a paused viewport returns to the newest line; `0` disables. */
|
|
975
992
|
scrollResumeMs: number;
|
|
976
993
|
/**
|