@aragon-agent/cli 0.6.2 → 0.6.3
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 +216 -0
- package/README.md +202 -0
- package/dist/agent/controller.d.ts +74 -1
- package/dist/agent/controller.js +200 -1
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +47 -0
- package/dist/agent/reducer.d.ts +120 -0
- package/dist/agent/reducer.js +201 -2
- package/dist/cli.js +224 -5
- package/dist/commands/builtins.js +73 -23
- package/dist/commands/registry.d.ts +27 -0
- package/dist/compaction/archive.d.ts +175 -0
- package/dist/compaction/archive.js +267 -0
- package/dist/compaction/child.d.ts +97 -0
- package/dist/compaction/child.js +113 -0
- package/dist/compaction/command.d.ts +41 -0
- package/dist/compaction/command.js +342 -0
- package/dist/compaction/compactor.d.ts +268 -0
- package/dist/compaction/compactor.js +689 -0
- package/dist/compaction/digest.d.ts +62 -0
- package/dist/compaction/digest.js +164 -0
- package/dist/compaction/limits.d.ts +165 -0
- package/dist/compaction/limits.js +172 -0
- package/dist/compaction/pressure.d.ts +135 -0
- package/dist/compaction/pressure.js +167 -0
- package/dist/compaction/summarize-call.d.ts +70 -0
- package/dist/compaction/summarize-call.js +86 -0
- package/dist/compaction/summary-prompt.d.ts +85 -0
- package/dist/compaction/summary-prompt.js +197 -0
- package/dist/compaction/tail-budget.d.ts +52 -0
- package/dist/compaction/tail-budget.js +67 -0
- package/dist/compaction/types.d.ts +179 -0
- package/dist/compaction/types.js +22 -0
- package/dist/compaction/wiring.d.ts +255 -0
- package/dist/compaction/wiring.js +556 -0
- package/dist/config/app-paths.d.ts +14 -0
- package/dist/config/app-paths.js +16 -0
- package/dist/config/cli-commands.d.ts +41 -0
- package/dist/config/cli-commands.js +77 -1
- package/dist/config/env.d.ts +4 -1
- package/dist/config/env.js +92 -2
- package/dist/config/load.d.ts +31 -0
- package/dist/config/load.js +63 -1
- package/dist/config/schema.d.ts +159 -0
- package/dist/config/schema.js +137 -0
- package/dist/config/store.js +17 -1
- package/dist/config/ui-state.d.ts +28 -1
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +50 -1
- package/dist/exec/runner.js +42 -0
- package/dist/input/mouse-events.d.ts +31 -5
- package/dist/input/mouse-events.js +86 -27
- package/dist/input/stdin-mouse-filter.d.ts +8 -3
- package/dist/logging/install.d.ts +51 -1
- package/dist/logging/install.js +28 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/session/persist.js +19 -0
- package/dist/team/report.js +7 -1
- package/dist/team/runtime.d.ts +10 -0
- package/dist/team/runtime.js +7 -0
- package/dist/team/subagent.d.ts +31 -1
- package/dist/team/subagent.js +42 -15
- package/dist/team/types.d.ts +8 -0
- package/dist/ui/ActivityLine.d.ts +19 -1
- package/dist/ui/ActivityLine.js +10 -2
- package/dist/ui/App.d.ts +48 -1
- package/dist/ui/App.js +294 -13
- package/dist/ui/Composer.d.ts +13 -1
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/PromptInput.d.ts +34 -1
- package/dist/ui/PromptInput.js +54 -3
- package/dist/ui/StatusBar.d.ts +40 -0
- package/dist/ui/StatusBar.js +13 -4
- package/dist/ui/TeamPanel.js +7 -1
- package/dist/ui/Transcript.d.ts +21 -1
- package/dist/ui/Transcript.js +32 -2
- package/dist/ui/clipboard.d.ts +52 -0
- package/dist/ui/clipboard.js +91 -0
- package/dist/ui/entries/CompactionCard.d.ts +75 -0
- package/dist/ui/entries/CompactionCard.js +158 -0
- package/dist/ui/frame-differ.d.ts +46 -0
- package/dist/ui/frame-differ.js +94 -10
- package/dist/ui/gauge.d.ts +16 -1
- package/dist/ui/gauge.js +25 -6
- package/dist/ui/glyphs.d.ts +11 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/ScrollViewport.d.ts +43 -6
- package/dist/ui/layout/ScrollViewport.js +128 -21
- package/dist/ui/layout/budget.d.ts +10 -3
- package/dist/ui/layout/budget.js +10 -3
- package/dist/ui/layout/follow-state.d.ts +137 -0
- package/dist/ui/layout/follow-state.js +150 -0
- package/dist/ui/layout/virtual-window.d.ts +11 -0
- package/dist/ui/layout/virtual-window.js +34 -0
- package/dist/ui/overlays/HelpOverlay.js +6 -1
- package/dist/ui/overlays/SettingsScreen.d.ts +47 -1
- package/dist/ui/overlays/SettingsScreen.js +60 -1
- package/dist/ui/palettes.d.ts +16 -0
- package/dist/ui/palettes.js +6 -0
- package/dist/ui/screen.d.ts +22 -0
- package/dist/ui/screen.js +70 -8
- package/dist/ui/selection/highlight.d.ts +39 -0
- package/dist/ui/selection/highlight.js +64 -0
- package/dist/ui/selection/screen-mirror.d.ts +24 -0
- package/dist/ui/selection/screen-mirror.js +35 -0
- package/dist/ui/selection/selection-controller.d.ts +88 -0
- package/dist/ui/selection/selection-controller.js +282 -0
- package/dist/ui/selection/selection.d.ts +90 -0
- package/dist/ui/selection/selection.js +223 -0
- package/dist/ui/stdout-frame-writer.d.ts +33 -0
- package/dist/ui/stdout-frame-writer.js +15 -0
- package/dist/ui/theme.d.ts +11 -0
- package/dist/ui/theme.js +2 -0
- package/dist/ui/transcript-text.js +23 -0
- package/dist/ui/use-startup-notices.d.ts +27 -1
- package/dist/ui/use-startup-notices.js +43 -11
- package/dist/ui/use-wheel-routing.js +8 -0
- package/package.json +4 -2
package/dist/agent/reducer.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* synthesizes a generic error notice (a swallowed throw is never a no-op).
|
|
12
12
|
*/
|
|
13
13
|
import process from 'node:process';
|
|
14
|
+
import { occupiedTokens } from '../compaction/pressure.js';
|
|
14
15
|
import { computeCost } from './usage.js';
|
|
15
16
|
import { ENTRY_LIMITS, appendBounded, entryRetain, trimEntries } from './entry-limits.js';
|
|
16
17
|
/** Default auto-dismiss window for a toast. */
|
|
@@ -46,11 +47,14 @@ export function initialViewState(seed = {}) {
|
|
|
46
47
|
todos: null,
|
|
47
48
|
retry: null,
|
|
48
49
|
fast: null,
|
|
50
|
+
compaction: null,
|
|
51
|
+
contextTokensEstimated: false,
|
|
49
52
|
seq: 0,
|
|
50
53
|
teamEntryId: undefined,
|
|
51
54
|
todoEntryId: undefined,
|
|
52
55
|
retryEntryId: undefined,
|
|
53
56
|
fastEntryId: undefined,
|
|
57
|
+
compactionEntryId: undefined,
|
|
54
58
|
streamingId: undefined,
|
|
55
59
|
turnProduced: false,
|
|
56
60
|
errorNoticed: false,
|
|
@@ -223,6 +227,17 @@ function reduceStreamEvent(se) {
|
|
|
223
227
|
return [];
|
|
224
228
|
}
|
|
225
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* The opening words of the `context_overflow` banner.
|
|
232
|
+
*
|
|
233
|
+
* A SHARED CONSTANT RATHER THAN TWO LITERALS, because `compactionStart` matches
|
|
234
|
+
* on it to rewrite the notice a successful reactive recovery would otherwise
|
|
235
|
+
* leave behind (P1-4). Two spellings that must agree, with nothing checking that
|
|
236
|
+
* they do, is how that rewrite silently stops matching — at which point the red
|
|
237
|
+
* "start over" banner comes back and only on the recovery path, which is the one
|
|
238
|
+
* place nobody re-tests.
|
|
239
|
+
*/
|
|
240
|
+
export const CONTEXT_OVERFLOW_NOTICE_PREFIX = 'Context length exceeded';
|
|
226
241
|
/** Turn a provider error into a friendly banner, mapping LLMError.errorType. */
|
|
227
242
|
export function formatStreamError(error) {
|
|
228
243
|
const errorType = error.errorType;
|
|
@@ -235,7 +250,13 @@ export function formatStreamError(error) {
|
|
|
235
250
|
case 'overloaded':
|
|
236
251
|
return `Provider overloaded - please retry shortly. (${base})`;
|
|
237
252
|
case 'context_overflow':
|
|
238
|
-
|
|
253
|
+
// `/compact` IS NAMED FIRST, and adding it is the second half of P1-4's
|
|
254
|
+
// fix. This remedy list predates context compaction and was, until it
|
|
255
|
+
// existed, complete: throwing the conversation away really was the only
|
|
256
|
+
// exit. It is not any more, and a banner that offers only `/reset` now
|
|
257
|
+
// tells the user to discard a session the very next line of code may be
|
|
258
|
+
// about to save.
|
|
259
|
+
return `${CONTEXT_OVERFLOW_NOTICE_PREFIX} - try /compact, or start over with /reset. (${base})`;
|
|
239
260
|
case 'network_error':
|
|
240
261
|
return `Network error reaching the provider. (${base})`;
|
|
241
262
|
case 'timeout':
|
|
@@ -295,6 +316,11 @@ function appendEntry(state, entries, entry) {
|
|
|
295
316
|
// `fastEntryId` IS PART OF THIS LIST for the reason the note above gives:
|
|
296
317
|
// a fifth live card left out of it is a card I-L1-1 does not cover.
|
|
297
318
|
state.fastEntryId,
|
|
319
|
+
// And `compactionEntryId` is the sixth. Same reason, and it is the one
|
|
320
|
+
// whose absence would be hardest to notice: a compaction card is rare, so
|
|
321
|
+
// a `mapEntry` that silently stopped matching would be found by a user
|
|
322
|
+
// rather than by a test.
|
|
323
|
+
state.compactionEntryId,
|
|
298
324
|
].filter((id) => typeof id === 'string');
|
|
299
325
|
for (const id of live) {
|
|
300
326
|
if (!trimmed.entries.some((e) => e.id === id)) {
|
|
@@ -584,7 +610,17 @@ export function viewReducer(state, action) {
|
|
|
584
610
|
...state,
|
|
585
611
|
streamingId: undefined,
|
|
586
612
|
turnProduced: true,
|
|
587
|
-
|
|
613
|
+
// ONE FUNCTION, SHARED WITH THE COMPACTION TRIGGER (§3.4.1 / R-11 /
|
|
614
|
+
// AC-3). The old expression — `inputTokens + outputTokens` — silently
|
|
615
|
+
// UNDER-reports for anyone behind a caching gateway, because Anthropic
|
|
616
|
+
// reports `input_tokens` EXCLUDING cached tokens and puts them in two
|
|
617
|
+
// separate fields. That was a live defect in the number this gauge shows;
|
|
618
|
+
// it becomes a much worse one the moment a trigger fires on a different
|
|
619
|
+
// number from the one the user is watching.
|
|
620
|
+
contextTokens: occupiedTokens(action.usage),
|
|
621
|
+
// A MEASUREMENT SUPERSEDES A DERIVATION (§6.2). `compactionEnd` writes an
|
|
622
|
+
// estimate so the bar falls immediately; this is where it stops being one.
|
|
623
|
+
contextTokensEstimated: false,
|
|
588
624
|
usageTotal: {
|
|
589
625
|
inputTokens: state.usageTotal.inputTokens + action.usage.inputTokens,
|
|
590
626
|
outputTokens: state.usageTotal.outputTokens + action.usage.outputTokens,
|
|
@@ -713,6 +749,12 @@ export function viewReducer(state, action) {
|
|
|
713
749
|
// holds, it is a projection of an entry that has just been erased.
|
|
714
750
|
retryEntryId: undefined,
|
|
715
751
|
retry: null,
|
|
752
|
+
// Same reasoning again for the compaction card: the entry is gone, so the
|
|
753
|
+
// id must go with it or `compactionEnd` would `mapEntry` an id that no
|
|
754
|
+
// longer exists. `compaction` STAYS — unlike `retry` it is not a
|
|
755
|
+
// projection of an entry, it is the live state of a subsystem that is
|
|
756
|
+
// still running, and `/clear` clears the screen rather than the session.
|
|
757
|
+
compactionEntryId: undefined,
|
|
716
758
|
};
|
|
717
759
|
case 'resetConversation':
|
|
718
760
|
return {
|
|
@@ -731,6 +773,11 @@ export function viewReducer(state, action) {
|
|
|
731
773
|
todoEntryId: undefined,
|
|
732
774
|
retryEntryId: undefined,
|
|
733
775
|
retry: null,
|
|
776
|
+
compactionEntryId: undefined,
|
|
777
|
+
// `contextTokens` is zeroed above, so the flag that says it is a
|
|
778
|
+
// DERIVATION has to go with it — otherwise a fresh conversation renders
|
|
779
|
+
// `~0%` and the tilde is a lie about a number that is simply zero.
|
|
780
|
+
contextTokensEstimated: false,
|
|
734
781
|
};
|
|
735
782
|
case 'setOverlay':
|
|
736
783
|
return { ...state, overlay: action.overlay };
|
|
@@ -957,6 +1004,158 @@ export function viewReducer(state, action) {
|
|
|
957
1004
|
};
|
|
958
1005
|
case 'fastTier':
|
|
959
1006
|
return { ...state, fast: action.snapshot };
|
|
1007
|
+
// --- Context compaction (context-auto-compaction §5.5) ----------------
|
|
1008
|
+
case 'compactionStart': {
|
|
1009
|
+
const { id, seq } = nextId(state);
|
|
1010
|
+
let entries = state.entries;
|
|
1011
|
+
// RESPONSIBILITY 1 — SEAL ANY LIVE `streamingId` ENTRY, defensively.
|
|
1012
|
+
// C-8's failure mode, and reachable here: `provider.ts` records that
|
|
1013
|
+
// Anthropic can deliver an error IN-STREAM, after the connection is open
|
|
1014
|
+
// and therefore after text deltas have already been emitted. An entry that
|
|
1015
|
+
// never settles pins `Transcript`'s monotonic boundary and re-renders the
|
|
1016
|
+
// tail forever.
|
|
1017
|
+
if (state.streamingId) {
|
|
1018
|
+
entries = mapEntry(entries, state.streamingId, (e) => e.kind === 'assistant' ? { ...e, streaming: false, ...sealThinkingMs(e) } : e);
|
|
1019
|
+
}
|
|
1020
|
+
// RESPONSIBILITY 2 — REWRITE A TRAILING `context_overflow` ERROR NOTICE
|
|
1021
|
+
// (P1-4). `agent-loop.ts` emits `message_update` for EVERY stream event
|
|
1022
|
+
// before the `error` branch throws, and `reduceStreamEvent`'s `case 'error'`
|
|
1023
|
+
// turns that into a `level: 'error'` notice reading "Context length
|
|
1024
|
+
// exceeded". So the HAPPY PATH of the reactive recovery would otherwise end
|
|
1025
|
+
// with a red banner telling the user to throw away the session we are one
|
|
1026
|
+
// frame from saving.
|
|
1027
|
+
//
|
|
1028
|
+
// IT REWRITES RATHER THAN REMOVES, because `<Static>` cannot un-print an
|
|
1029
|
+
// entry it has already drawn — and it matches ONLY when that notice is the
|
|
1030
|
+
// TAIL entry, so it can never touch an older, genuine error. If the
|
|
1031
|
+
// recovery then fails, the ladder's own notice or the re-thrown 400 is what
|
|
1032
|
+
// the user is left with, so neither direction lies.
|
|
1033
|
+
if (action.trigger === 'overflow') {
|
|
1034
|
+
const tail = entries[entries.length - 1];
|
|
1035
|
+
if (tail &&
|
|
1036
|
+
tail.kind === 'notice' &&
|
|
1037
|
+
tail.level === 'error' &&
|
|
1038
|
+
tail.text.startsWith(CONTEXT_OVERFLOW_NOTICE_PREFIX)) {
|
|
1039
|
+
entries = mapEntry(entries, tail.id, (e) => e.kind === 'notice'
|
|
1040
|
+
? { ...e, level: 'info', text: 'Context window exceeded - compacting and retrying.' }
|
|
1041
|
+
: e);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
const entry = {
|
|
1045
|
+
id,
|
|
1046
|
+
kind: 'compaction',
|
|
1047
|
+
index: action.index,
|
|
1048
|
+
trigger: action.trigger,
|
|
1049
|
+
mode: 'none',
|
|
1050
|
+
applied: false,
|
|
1051
|
+
messagesBefore: 0,
|
|
1052
|
+
messagesAfter: 0,
|
|
1053
|
+
tokensBefore: 0,
|
|
1054
|
+
tokensAfter: 0,
|
|
1055
|
+
model: action.model,
|
|
1056
|
+
startedAt: Date.now(),
|
|
1057
|
+
live: true,
|
|
1058
|
+
};
|
|
1059
|
+
return {
|
|
1060
|
+
...state,
|
|
1061
|
+
seq,
|
|
1062
|
+
...appendEntry(state, entries, entry),
|
|
1063
|
+
compactionEntryId: id,
|
|
1064
|
+
streamingId: undefined,
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
case 'compactionEnd': {
|
|
1068
|
+
const { record } = action;
|
|
1069
|
+
const settle = (e) => e.kind === 'compaction'
|
|
1070
|
+
? {
|
|
1071
|
+
...e,
|
|
1072
|
+
index: record.index,
|
|
1073
|
+
trigger: record.trigger,
|
|
1074
|
+
mode: record.mode,
|
|
1075
|
+
applied: record.applied,
|
|
1076
|
+
...(record.reason !== undefined ? { reason: record.reason } : {}),
|
|
1077
|
+
messagesBefore: record.messagesBefore,
|
|
1078
|
+
messagesAfter: record.messagesAfter,
|
|
1079
|
+
tokensBefore: record.tokensBefore,
|
|
1080
|
+
tokensAfter: record.tokensAfter,
|
|
1081
|
+
...(record.summary !== undefined ? { summary: record.summary } : {}),
|
|
1082
|
+
...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
|
|
1083
|
+
model: record.model,
|
|
1084
|
+
durationMs: record.durationMs,
|
|
1085
|
+
// SETTLED. `Transcript`'s boundary is monotonic, so a card that
|
|
1086
|
+
// never settles is re-rendered on every frame for the rest of the
|
|
1087
|
+
// session (C-8).
|
|
1088
|
+
live: false,
|
|
1089
|
+
}
|
|
1090
|
+
: e;
|
|
1091
|
+
// THE SECOND BRANCH IS NOT DEFENSIVE PADDING, exactly as `fastEnd`'s is
|
|
1092
|
+
// not: the IDLE `/compact` path settles a record for a card this session
|
|
1093
|
+
// may never have opened, and "it did nothing" must never be the observable
|
|
1094
|
+
// outcome (R-8 / §6.4).
|
|
1095
|
+
if (state.compactionEntryId) {
|
|
1096
|
+
return {
|
|
1097
|
+
...state,
|
|
1098
|
+
entries: mapEntry(state.entries, state.compactionEntryId, settle),
|
|
1099
|
+
compactionEntryId: undefined,
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
const { id, seq } = nextId(state);
|
|
1103
|
+
const entry = {
|
|
1104
|
+
id,
|
|
1105
|
+
kind: 'compaction',
|
|
1106
|
+
index: record.index,
|
|
1107
|
+
trigger: record.trigger,
|
|
1108
|
+
mode: record.mode,
|
|
1109
|
+
applied: record.applied,
|
|
1110
|
+
...(record.reason !== undefined ? { reason: record.reason } : {}),
|
|
1111
|
+
messagesBefore: record.messagesBefore,
|
|
1112
|
+
messagesAfter: record.messagesAfter,
|
|
1113
|
+
tokensBefore: record.tokensBefore,
|
|
1114
|
+
tokensAfter: record.tokensAfter,
|
|
1115
|
+
...(record.summary !== undefined ? { summary: record.summary } : {}),
|
|
1116
|
+
...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
|
|
1117
|
+
model: record.model,
|
|
1118
|
+
durationMs: record.durationMs,
|
|
1119
|
+
live: false,
|
|
1120
|
+
};
|
|
1121
|
+
return { ...state, seq, ...appendEntry(state, state.entries, entry) };
|
|
1122
|
+
}
|
|
1123
|
+
case 'compactionUsage':
|
|
1124
|
+
// `usageTotal` AND NOTHING ELSE, exactly as `teamUsage` and `fastUsage`. In
|
|
1125
|
+
// particular NOT `contextTokens`: that gauge shows the LEAD's context
|
|
1126
|
+
// occupancy against the lead model's window, and a summarization is a
|
|
1127
|
+
// separate conversation with a possibly different model entirely.
|
|
1128
|
+
return {
|
|
1129
|
+
...state,
|
|
1130
|
+
usageTotal: {
|
|
1131
|
+
inputTokens: state.usageTotal.inputTokens + action.usage.inputTokens,
|
|
1132
|
+
outputTokens: state.usageTotal.outputTokens + action.usage.outputTokens,
|
|
1133
|
+
costUsd: state.usageTotal.costUsd + action.costDelta,
|
|
1134
|
+
},
|
|
1135
|
+
};
|
|
1136
|
+
case 'compactionSnapshot':
|
|
1137
|
+
return { ...state, compaction: action.snapshot };
|
|
1138
|
+
/**
|
|
1139
|
+
* THE GAUGE MUST FALL IMMEDIATELY (§6.2 / AC-5).
|
|
1140
|
+
*
|
|
1141
|
+
* `contextTokens` is otherwise only written at `turnEnd`, so a compaction
|
|
1142
|
+
* that took the session from 92 % to 24 % would leave the bar at 92 % until
|
|
1143
|
+
* the next turn completes — and the user would reasonably conclude that
|
|
1144
|
+
* nothing happened. This is a small change and it is the difference between
|
|
1145
|
+
* the feature feeling real and feeling broken.
|
|
1146
|
+
*
|
|
1147
|
+
* The value arrives ALREADY CALIBRATED from `App` (§6.2 / P1-11): it replaces
|
|
1148
|
+
* a MEASUREMENT that included the tool schemas, and core's raw estimator
|
|
1149
|
+
* never counts them, so writing the raw figure would make the bar fall too
|
|
1150
|
+
* far and then visibly climb again on the next `turn_end` for no reason the
|
|
1151
|
+
* user can see.
|
|
1152
|
+
*/
|
|
1153
|
+
case 'contextTokensEstimated':
|
|
1154
|
+
return {
|
|
1155
|
+
...state,
|
|
1156
|
+
contextTokens: Math.max(0, Math.round(action.tokens)),
|
|
1157
|
+
contextTokensEstimated: true,
|
|
1158
|
+
};
|
|
960
1159
|
// --- API retry (llm-api-retry-backoff §6.4) ---------------------------
|
|
961
1160
|
case 'retryScheduled': {
|
|
962
1161
|
const snapshot = {
|
package/dist/cli.js
CHANGED
|
@@ -23,11 +23,11 @@ import { formatMigrationNotice, migrateLegacyState } from './config/migrate-lega
|
|
|
23
23
|
import { formatHomeMigrationNotice, migrateToHome } from './config/migrate-home.js';
|
|
24
24
|
import { migrateStateOutOfConfig } from './config/migrate-state-out-of-config.js';
|
|
25
25
|
import { runHistoryCommand } from './config/history-commands.js';
|
|
26
|
-
import { FAST_CONFIG_SET_KEYS, LOG_CONFIG_SET_KEYS, RETRY_CONFIG_SET_KEYS, TEAM_CONFIG_SET_KEYS, UPDATE_CONFIG_SET_KEYS, applyFastConfigSet, applyLogConfigSet, applyRetryConfigSet, applyTeamConfigSet, applyUpdateConfigSet, runConfigEdit, runConfigGet, runConfigHome, runConfigList, warnIfRedactionDisabled, } from './config/cli-commands.js';
|
|
26
|
+
import { COMPACTION_CONFIG_SET_KEYS, FAST_CONFIG_SET_KEYS, LOG_CONFIG_SET_KEYS, RETRY_CONFIG_SET_KEYS, TEAM_CONFIG_SET_KEYS, UPDATE_CONFIG_SET_KEYS, applyCompactionConfigSet, applyFastConfigSet, applyLogConfigSet, applyRetryConfigSet, applyTeamConfigSet, applyUpdateConfigSet, runConfigEdit, runConfigGet, runConfigHome, runConfigList, warnIfRedactionDisabled, } from './config/cli-commands.js';
|
|
27
27
|
import { attachAgentEvents, attachTeamEvents, installLogging, setScreenRestore, setSignalTerminator, } from './logging/install.js';
|
|
28
28
|
import { getLogger } from './logging/logger.js';
|
|
29
29
|
import { runLogsCommand } from './logging/cli-commands.js';
|
|
30
|
-
import { ADAPTER_PROVIDERS, clampDensity, clampTheme, clampThinkingLevel, clampAskRounds, clampHumanTimeout, clampSkillsIntegrity, clampSkillsToolPolicy, clampMaxRenderInterval, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAdapterProvider, parseMaxTokensInput, DEFAULT_MAX_RENDER_INTERVAL_MS, DEFAULT_MAX_TOKENS, DEFAULT_PLAN_HUMAN_TIMEOUT_MS, DEFAULT_PLAN_MAX_ASK_ROUNDS, DEFAULT_SKILLS_CONFIG, DEFAULT_TRANSCRIPT_RETAIN, DEFAULT_TRANSCRIPT_WINDOW, } from './config/schema.js';
|
|
30
|
+
import { ADAPTER_PROVIDERS, clampDensity, clampTheme, clampThinkingLevel, clampAskRounds, clampHumanTimeout, clampSkillsIntegrity, clampSkillsToolPolicy, clampMaxRenderInterval, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAdapterProvider, parseMaxTokensInput, DEFAULT_MAX_RENDER_INTERVAL_MS, DEFAULT_MAX_TOKENS, DEFAULT_PLAN_HUMAN_TIMEOUT_MS, DEFAULT_PLAN_MAX_ASK_ROUNDS, DEFAULT_SCROLL_RESUME_MS, DEFAULT_SKILLS_CONFIG, DEFAULT_TRANSCRIPT_RETAIN, DEFAULT_TRANSCRIPT_WINDOW, } from './config/schema.js';
|
|
31
31
|
import { makeGetApiKey } from './config/load.js';
|
|
32
32
|
import { AgentController, DENY_ALL_APPROVAL } from './agent/controller.js';
|
|
33
33
|
import { runSkillsCommand } from './skills/cli-commands.js';
|
|
@@ -52,6 +52,9 @@ import { readExitSnapshot } from './ui/exit-snapshot.js';
|
|
|
52
52
|
import { renderTranscriptText } from './ui/transcript-text.js';
|
|
53
53
|
import { detectCapabilities } from './ui/capabilities.js';
|
|
54
54
|
import { pickGlyphs } from './ui/glyphs.js';
|
|
55
|
+
import { getTheme } from './ui/theme.js';
|
|
56
|
+
import { copyText } from './ui/clipboard.js';
|
|
57
|
+
import { createSelectionController, } from './ui/selection/selection-controller.js';
|
|
55
58
|
import { DENY_ALL_HUMAN_INPUT } from './tools/human-input.js';
|
|
56
59
|
// A VALUE import, and the one deliberate exception to the paragraph above
|
|
57
60
|
// (cli-auto-update-hardening P2-7). `boot/guard.js` statically pulls
|
|
@@ -105,6 +108,11 @@ function toFlags(opts) {
|
|
|
105
108
|
// Without this line the flag never reaches `CliFlags` and `--no-mouse` is
|
|
106
109
|
// inert — a failure with no compile error and no runtime error (P1-2).
|
|
107
110
|
mouse: opts.mouse,
|
|
111
|
+
// Site three of three for `--mouse-select` / `--no-mouse-select` — NINE
|
|
112
|
+
// features, one trap. A flag needs the `.option()` declaration below, a field
|
|
113
|
+
// on `CliFlags`, AND a line here; `config.test.ts` is the regression pin that
|
|
114
|
+
// exists because `--no-mouse` shipped inert once already.
|
|
115
|
+
mouseSelect: opts.mouseSelect,
|
|
108
116
|
skills: opts.skills,
|
|
109
117
|
skill: opts.skill,
|
|
110
118
|
skillsYes: opts.skillsYes,
|
|
@@ -171,6 +179,15 @@ function toFlags(opts) {
|
|
|
171
179
|
// means `--no-update` reports nothing, does nothing, and leaves the updater
|
|
172
180
|
// running.
|
|
173
181
|
update: opts.update,
|
|
182
|
+
// Site three of three for `--compaction` / `--no-compaction` — NINE
|
|
183
|
+
// features, one trap. A flag needs the `.option()` declaration below, a field
|
|
184
|
+
// on `CliFlags`, AND a line here; miss this one and the flag is inert with no
|
|
185
|
+
// compile error and no runtime error. It bites here for the same reason it
|
|
186
|
+
// does one field up: this is the KILL SWITCH for a feature that spends the
|
|
187
|
+
// user's money on a summarizer, so a missing line means `--no-compaction`
|
|
188
|
+
// reports nothing, does nothing, and leaves compaction armed.
|
|
189
|
+
compaction: opts.compaction,
|
|
190
|
+
compactionThreshold: opts.compactionThreshold,
|
|
174
191
|
};
|
|
175
192
|
}
|
|
176
193
|
// ---------------------------------------------------------------------------
|
|
@@ -421,6 +438,20 @@ function runInteractive(flags, extras = {}) {
|
|
|
421
438
|
? tryCreateMouseFilter(process.stdin, (reason) => logger.warn('cli', 'mouse_filter_failed', { reason }))
|
|
422
439
|
: null;
|
|
423
440
|
const disposeMouse = () => mouseFilter?.dispose();
|
|
441
|
+
// --- Drag-select (tui-selection-and-scroll-follow §4.4). ------------------
|
|
442
|
+
//
|
|
443
|
+
// ONE EXPRESSION, READ BY FOUR THINGS, and that is the same discipline
|
|
444
|
+
// `wantMouse` above follows for the same reason: the `?1002h` write, the
|
|
445
|
+
// controller, the differ's `decorate` hook and the startup notice must never
|
|
446
|
+
// disagree about whether drag-select is running. With it false the session is
|
|
447
|
+
// byte-identical to a pre-feature build — no extra escape on the wire (AC-8),
|
|
448
|
+
// no controller, no `decorate`, no mirror.
|
|
449
|
+
const wantSelect = mouseFilter !== null && config.mouseSelect;
|
|
450
|
+
const selectionBridge = {
|
|
451
|
+
controller: null,
|
|
452
|
+
onCopied: null,
|
|
453
|
+
requestRedraw: null,
|
|
454
|
+
};
|
|
424
455
|
// --- Frame differ (tui-input-flicker-fix §3.2 / §4.2). -------------------
|
|
425
456
|
//
|
|
426
457
|
// FULL-SCREEN ONLY, and that is invariant I-3 rather than caution: inline mode
|
|
@@ -443,6 +474,25 @@ function runInteractive(flags, extras = {}) {
|
|
|
443
474
|
// edge inside the differ: a notice per fallback would be a repaint
|
|
444
475
|
// storm triggered by a repaint problem (§5.6).
|
|
445
476
|
onFirstFallback: () => console.warn(FRAME_FALLBACK_NOTICE),
|
|
477
|
+
// ABSENT ENTIRELY without drag-select, not a no-op function: the
|
|
478
|
+
// differ's identity path then allocates nothing and copies nothing,
|
|
479
|
+
// which is what makes `mouseSelect: false` a statement about the code
|
|
480
|
+
// rather than about how cheap an empty function is.
|
|
481
|
+
...(wantSelect
|
|
482
|
+
? {
|
|
483
|
+
decorate: (lines) => selectionBridge.controller?.decorate(lines) ?? lines,
|
|
484
|
+
// P1-7: a pass-through frame writes the RAW Ink frame verbatim,
|
|
485
|
+
// so the highlight is wiped off the terminal while the
|
|
486
|
+
// controller still believes it is painted. Deferred out of the
|
|
487
|
+
// current stack because this fires from inside `transform`,
|
|
488
|
+
// which is inside Ink's own `stdout.write`.
|
|
489
|
+
onInvalidate: () => {
|
|
490
|
+
const controller = selectionBridge.controller;
|
|
491
|
+
if (controller)
|
|
492
|
+
setImmediate(() => controller.clear());
|
|
493
|
+
},
|
|
494
|
+
}
|
|
495
|
+
: {}),
|
|
446
496
|
}))
|
|
447
497
|
: null;
|
|
448
498
|
// `null` when no writer was built, so `/perf` can say "diff render off" rather
|
|
@@ -478,8 +528,9 @@ function runInteractive(flags, extras = {}) {
|
|
|
478
528
|
if (mode === 'fullscreen') {
|
|
479
529
|
// `mouse` here means "a filter is installed", NOT "the user wants mouse
|
|
480
530
|
// support" — see `AltScreenOptions`. `screen.ts` must never derive it for
|
|
481
|
-
// itself, or a later refactor re-opens the gap I-8 closes.
|
|
482
|
-
|
|
531
|
+
// itself, or a later refactor re-opens the gap I-8 closes. `motion` carries
|
|
532
|
+
// the same discipline for `?1002h`: already decided, never re-derived.
|
|
533
|
+
screen = enterAltScreen(process.stdout, { mouse: mouseFilter !== null, motion: wantSelect });
|
|
483
534
|
const restore = () => screen?.restore();
|
|
484
535
|
// Four idempotent restore paths (§4.4). The signal hook is NOT redundant:
|
|
485
536
|
// - `waitUntilExit().then()` is a microtask and may never be reached when
|
|
@@ -516,6 +567,115 @@ function runInteractive(flags, extras = {}) {
|
|
|
516
567
|
// `CI` is excluded on top of the two TTY tests because a CI runner with a pty
|
|
517
568
|
// allocated is otherwise a machine that silently mutates its own global
|
|
518
569
|
// toolchain mid-pipeline (D-11).
|
|
570
|
+
// --- The foreign-write door (§4.4.5). ------------------------------------
|
|
571
|
+
//
|
|
572
|
+
// Where bytes that are OURS but are NOT a frame go — today, the OSC 52
|
|
573
|
+
// clipboard sequence. THREE SHAPES, ONE DOOR, and the third one is the reason
|
|
574
|
+
// this is a resolved value rather than an inline arrow:
|
|
575
|
+
//
|
|
576
|
+
// * with the differ in front of stdout it MUST be `frameWriter.writeForeign`,
|
|
577
|
+
// which invalidates the cache and skips the fallback accounting — through
|
|
578
|
+
// the proxy the same bytes are an unrecognised chunk, which is
|
|
579
|
+
// `passThrough(true)`, which prints `FRAME_FALLBACK_NOTICE` at a user who
|
|
580
|
+
// did nothing but copy something (P1-6);
|
|
581
|
+
// * without one — `--no-diff-render`, and inline mode, which never builds a
|
|
582
|
+
// differ at all — stdout IS the real stream, so a direct write is both safe
|
|
583
|
+
// and what §4.4.5 prescribes. OSC 52 emits no newline, so Ink's own line
|
|
584
|
+
// accounting is untouched and there is nothing to invalidate;
|
|
585
|
+
// * on a non-TTY there is no terminal to accept it, so the door is ABSENT.
|
|
586
|
+
//
|
|
587
|
+
// It must never be a function that accepts a chunk and writes NOWHERE.
|
|
588
|
+
// `copyText` reports `'osc52'` whenever its door took the text, so a silent
|
|
589
|
+
// no-op door — which `frameWriter?.writeForeign(chunk)` is under
|
|
590
|
+
// `--no-diff-render` — makes the toast name a mechanism that was never
|
|
591
|
+
// attempted. Naming the mechanism honestly is the entire reason that return
|
|
592
|
+
// value exists (R-7), and a door that lies is worse than no door.
|
|
593
|
+
const writeForeign = frameWriter
|
|
594
|
+
? (text) => frameWriter.writeForeign(text)
|
|
595
|
+
: process.stdout.isTTY
|
|
596
|
+
? (text) => {
|
|
597
|
+
try {
|
|
598
|
+
process.stdout.write(text);
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
// A closed or broken stdout must never turn a copy into a crash.
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
: null;
|
|
605
|
+
// --- The selection controller (§4.4). ------------------------------------
|
|
606
|
+
//
|
|
607
|
+
// BUILT AFTER `enterAltScreen` AND BEFORE `render()`. After, because it must
|
|
608
|
+
// not exist on a session that never asked the terminal to report motion;
|
|
609
|
+
// before, because `decorate` is read by the differ from the very first frame
|
|
610
|
+
// Ink produces.
|
|
611
|
+
//
|
|
612
|
+
// `mouseCaptured` is the run-time answer for `/mouse`, and it lives here rather
|
|
613
|
+
// than inside `screen.ts`'s closure because `App` needs to READ it and the
|
|
614
|
+
// handle's own flag is private (it has to be — `restore()` reads it, and
|
|
615
|
+
// exposing a setter would be a second way to get the two out of step).
|
|
616
|
+
let mouseCaptured = mouseFilter !== null;
|
|
617
|
+
if (wantSelect && mouseFilter) {
|
|
618
|
+
selectionBridge.controller = createSelectionController({
|
|
619
|
+
source: mouseFilter.source,
|
|
620
|
+
// The frame writer is absent under `--no-diff-render`, where there is no
|
|
621
|
+
// cache to repaint from at all; the controller then falls back to asking
|
|
622
|
+
// `App` for a React redraw on every drag flush, which is slower and
|
|
623
|
+
// correct. That is rung 2 of the fail-safe ladder doing its job.
|
|
624
|
+
repaint: () => frameWriter?.repaint() ?? false,
|
|
625
|
+
requestRedraw: () => selectionBridge.requestRedraw?.(),
|
|
626
|
+
// `?? undefined` and not a wrapper: `copyText` skips OSC 52 entirely when
|
|
627
|
+
// it has no door, and reports `'native'`/`'none'` accordingly.
|
|
628
|
+
copy: (text) => copyText(text, writeForeign ?? undefined),
|
|
629
|
+
onCopied: (via, lines, chars) => selectionBridge.onCopied?.(via, lines, chars),
|
|
630
|
+
// Read at PAINT time, not captured: `/theme` rebuilds the theme mid-session.
|
|
631
|
+
theme: () => getTheme(config.theme, detectCapabilities(process.env, process.stdout)),
|
|
632
|
+
caps: detectCapabilities(process.env, process.stdout),
|
|
633
|
+
cols: () => process.stdout.columns ?? 80,
|
|
634
|
+
// The STATIC half of the gate. The dynamic half — "no overlay is open" —
|
|
635
|
+
// belongs to `App`, which owns that state and calls `setEnabled`.
|
|
636
|
+
isSelectable: () => mouseCaptured,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
const disposeSelection = () => {
|
|
640
|
+
selectionBridge.controller?.dispose();
|
|
641
|
+
selectionBridge.controller = null;
|
|
642
|
+
};
|
|
643
|
+
/**
|
|
644
|
+
* The terminal-facing services `App` cannot build for itself (§4.4).
|
|
645
|
+
*
|
|
646
|
+
* PRESENT IN INLINE MODE TOO, and only for the foreign-write door: §4.4.5
|
|
647
|
+
* routes `/copy` through `ui/clipboard.ts` precisely so it gains OSC 52 — the
|
|
648
|
+
* one mechanism that reaches the clipboard of the machine the USER is sitting
|
|
649
|
+
* at over SSH — and gating the whole bridge on full-screen would have handed
|
|
650
|
+
* that back only to full-screen users while the command is available in both.
|
|
651
|
+
* Every OTHER field stays absent there, so the inline tree is unchanged: there
|
|
652
|
+
* is no filter, hence no `setMouseCapture` and no `ctx.mouse`; `mouseSelect`
|
|
653
|
+
* is `wantSelect`, which is already false without one; and there is no
|
|
654
|
+
* controller, so no `selection`.
|
|
655
|
+
*/
|
|
656
|
+
const terminalBridge = mode === 'fullscreen' || writeForeign
|
|
657
|
+
? {
|
|
658
|
+
mouseSelect: wantSelect,
|
|
659
|
+
...(mouseFilter
|
|
660
|
+
? {
|
|
661
|
+
setMouseCapture: (on) => {
|
|
662
|
+
screen?.setMouseCapture(on);
|
|
663
|
+
mouseCaptured = on;
|
|
664
|
+
// A released mouse cannot be dragging, and a selection left
|
|
665
|
+
// painted over a screen the terminal is now selecting on its
|
|
666
|
+
// own would be two highlights claiming the same rows.
|
|
667
|
+
if (!on)
|
|
668
|
+
selectionBridge.controller?.clear();
|
|
669
|
+
},
|
|
670
|
+
isMouseCaptured: () => mouseCaptured,
|
|
671
|
+
}
|
|
672
|
+
: {}),
|
|
673
|
+
// The same door the controller copies through, so `/copy` and a
|
|
674
|
+
// drag-release can never disagree about which stream OSC 52 goes to.
|
|
675
|
+
...(writeForeign ? { writeForeign } : {}),
|
|
676
|
+
...(selectionBridge.controller ? { selection: selectionBridge } : {}),
|
|
677
|
+
}
|
|
678
|
+
: undefined;
|
|
519
679
|
const updateBridge = { service: null, onAttach: null };
|
|
520
680
|
let updateService = null;
|
|
521
681
|
const updateEligible = !!process.stdout.isTTY &&
|
|
@@ -528,7 +688,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
528
688
|
// the same tree on every machine — reading `process.versions.node` from
|
|
529
689
|
// a component would make the notice appear or vanish according to which
|
|
530
690
|
// Node the developer happened to have installed.
|
|
531
|
-
vtInputWarning: vtInputSupported ? undefined : { nodeVersion: process.versions.node }, updateBridge: updateEligible ? updateBridge : undefined }),
|
|
691
|
+
vtInputWarning: vtInputSupported ? undefined : { nodeVersion: process.versions.node }, updateBridge: updateEligible ? updateBridge : undefined, terminal: terminalBridge }),
|
|
532
692
|
// Full-screen owns console.* itself (I-4): Ink's patchConsole writes
|
|
533
693
|
// straight to stdout and permanently shifts the fixed frame's accounting.
|
|
534
694
|
{
|
|
@@ -597,6 +757,11 @@ function runInteractive(flags, extras = {}) {
|
|
|
597
757
|
.waitUntilExit()
|
|
598
758
|
.then(() => {
|
|
599
759
|
screen?.restore();
|
|
760
|
+
// BEFORE `disposeMouse()`, which clears the listener set it subscribed to,
|
|
761
|
+
// and after `restore()` for the reason that call records: the terminal must
|
|
762
|
+
// stop reporting first, and anything already in flight is still consumed by
|
|
763
|
+
// a live filter.
|
|
764
|
+
disposeSelection();
|
|
600
765
|
disposeMouse();
|
|
601
766
|
// BOTH BRANCHES, the same discipline `controller.dispose()` and
|
|
602
767
|
// `updateService?.dispose()` already follow. It drops the `'resize'`
|
|
@@ -621,6 +786,11 @@ function runInteractive(flags, extras = {}) {
|
|
|
621
786
|
})
|
|
622
787
|
.catch(() => {
|
|
623
788
|
screen?.restore();
|
|
789
|
+
// BEFORE `disposeMouse()`, which clears the listener set it subscribed to,
|
|
790
|
+
// and after `restore()` for the reason that call records: the terminal must
|
|
791
|
+
// stop reporting first, and anything already in flight is still consumed by
|
|
792
|
+
// a live filter.
|
|
793
|
+
disposeSelection();
|
|
624
794
|
disposeMouse();
|
|
625
795
|
disposeFrameWriter();
|
|
626
796
|
controller.dispose();
|
|
@@ -737,6 +907,11 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
737
907
|
// falls through every case, writes nothing, and still prints
|
|
738
908
|
// `Set mouse = false` (P1-2) -- exactly what happened to `density`/`hints`.
|
|
739
909
|
'mouse',
|
|
910
|
+
// Both cased in the switch below, for the reason the note above `mouse`
|
|
911
|
+
// records: a key listed here but absent there writes nothing and still prints
|
|
912
|
+
// `Set mouseSelect = false`.
|
|
913
|
+
'mouseSelect',
|
|
914
|
+
'scrollResumeMs',
|
|
740
915
|
'historyEnabled',
|
|
741
916
|
'planModeDefault',
|
|
742
917
|
'planModeMaxAskRounds',
|
|
@@ -763,6 +938,11 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
763
938
|
...FAST_CONFIG_SET_KEYS,
|
|
764
939
|
// `update.*` keys are handled by `applyUpdateConfigSet`, same reasoning again.
|
|
765
940
|
...UPDATE_CONFIG_SET_KEYS,
|
|
941
|
+
// `compaction.*` keys are handled by `applyCompactionConfigSet`, same
|
|
942
|
+
// reasoning again — and this set is what makes the kill switch reachable from
|
|
943
|
+
// a script at all: `config list` enumerates the section dynamically and so
|
|
944
|
+
// shows these keys whether they are here or not, while `config set` does not.
|
|
945
|
+
...COMPACTION_CONFIG_SET_KEYS,
|
|
766
946
|
]);
|
|
767
947
|
/**
|
|
768
948
|
* `aragon config set maxTokens <n|auto|default>`.
|
|
@@ -849,6 +1029,18 @@ function runConfigSet(key, value) {
|
|
|
849
1029
|
process.stdout.write(`Set ${key} = ${String(merged.update[field])}\n`);
|
|
850
1030
|
return;
|
|
851
1031
|
}
|
|
1032
|
+
const compactionPatch = applyCompactionConfigSet(key, value);
|
|
1033
|
+
if (compactionPatch) {
|
|
1034
|
+
// Echoed from the STORED value, so `config set compaction.threshold 0.2`
|
|
1035
|
+
// prints the clamped 0.5 rather than reporting a number that was never
|
|
1036
|
+
// written — the rule `team.*`, `retry.*`, `fast.*` and `update.*` each state
|
|
1037
|
+
// above. It matters most for `warnThreshold`, which is clamped against the
|
|
1038
|
+
// stored `threshold` and so can move for a reason the caller cannot see.
|
|
1039
|
+
const merged = updatePersistedConfig(compactionPatch);
|
|
1040
|
+
const field = key.slice('compaction.'.length);
|
|
1041
|
+
process.stdout.write(`Set ${key} = ${String(merged.compaction[field])}\n`);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
852
1044
|
const patch = {};
|
|
853
1045
|
switch (key) {
|
|
854
1046
|
case 'provider':
|
|
@@ -914,6 +1106,16 @@ function runConfigSet(key, value) {
|
|
|
914
1106
|
case 'mouse':
|
|
915
1107
|
patch.mouse = value === 'true' || value === '1';
|
|
916
1108
|
break;
|
|
1109
|
+
case 'mouseSelect':
|
|
1110
|
+
patch.mouseSelect = value === 'true' || value === '1';
|
|
1111
|
+
break;
|
|
1112
|
+
// Clamped rather than rejected, for the same reason `planModeDefault` below
|
|
1113
|
+
// records: hardening only the READ path leaves a bad value on disk that
|
|
1114
|
+
// reverts to the default on every launch, which presents as "my setting
|
|
1115
|
+
// won't stick". `0` is a legal value here and its documented off switch.
|
|
1116
|
+
case 'scrollResumeMs':
|
|
1117
|
+
patch.scrollResumeMs = clampScrollResumeMs(value, DEFAULT_SCROLL_RESUME_MS);
|
|
1118
|
+
break;
|
|
917
1119
|
// Turning this off stops new prompts being recorded; it does NOT delete
|
|
918
1120
|
// what is already in `prompt-history.jsonl` (that is `aragon history clear`).
|
|
919
1121
|
case 'historyEnabled':
|
|
@@ -1172,6 +1374,13 @@ function buildProgram() {
|
|
|
1172
1374
|
// Positive form first, same tri-state reason as `--fullscreen` above.
|
|
1173
1375
|
.option('--mouse', 'Wheel scrolls the transcript (full-screen mode)')
|
|
1174
1376
|
.option('--no-mouse', 'Leave the mouse to the terminal (the wheel does nothing)')
|
|
1377
|
+
// Positive form FIRST, for the tri-state reason `--fullscreen` records
|
|
1378
|
+
// below: `mouseSelect` is persisted and defaults to `true`, so a lone
|
|
1379
|
+
// `--no-mouse-select` would make commander default `opts.mouseSelect` to
|
|
1380
|
+
// `true` — indistinguishable from silence, and silently overriding a stored
|
|
1381
|
+
// `false` on every run that passes no flag at all.
|
|
1382
|
+
.option('--mouse-select', 'Drag with the mouse to select text; releasing copies it')
|
|
1383
|
+
.option('--no-mouse-select', 'Keep wheel scrolling, but leave drag-select off')
|
|
1175
1384
|
// Declaring `--fullscreen` BEFORE `--no-fullscreen` keeps the default
|
|
1176
1385
|
// `undefined` instead of `true`, which is what makes the tri-state work:
|
|
1177
1386
|
// "no opinion" must stay distinguishable from "force it".
|
|
@@ -1228,6 +1437,16 @@ function buildProgram() {
|
|
|
1228
1437
|
// feature that installs software would quietly un-set itself (C-11).
|
|
1229
1438
|
.option('--update', 'Check for and install updates in the background (the default)')
|
|
1230
1439
|
.option('--no-update', 'Disable auto-update for this session')
|
|
1440
|
+
// BOTH FORMS, positive first, for the reason recorded eight times above —
|
|
1441
|
+
// and this pair shares `--update`'s polarity, which is the worst one.
|
|
1442
|
+
// `compaction.enabled` is PERSISTED and defaults to `true`, so a lone
|
|
1443
|
+
// `--no-compaction` would make commander default `opts.compaction` to `true`
|
|
1444
|
+
// and silently overwrite a stored `false` on EVERY run that passed no flag at
|
|
1445
|
+
// all: the kill switch for a feature that spends the user's money on a
|
|
1446
|
+
// summarizer would quietly un-set itself (C-13 / P1-5).
|
|
1447
|
+
.option('--compaction', 'Compact the conversation when the context window fills (the default)')
|
|
1448
|
+
.option('--no-compaction', 'Never compact; a full context window ends the run as before')
|
|
1449
|
+
.option('--compaction-threshold <n>', 'Occupancy that triggers compaction for this run: 0.9 or 90% (0.5-0.95)')
|
|
1231
1450
|
.option('--no-skills', 'Disable the skill system entirely (no catalog, no skill tools)')
|
|
1232
1451
|
.option('--skill <name>', 'Force-load a skill at Level 2 for this run (repeatable)', (value, previous = []) => [...previous, value])
|
|
1233
1452
|
.option('--skills-yes', 'Approve skill installs for this run (CI / headless)')
|