@aragon-agent/cli 0.6.2 → 0.6.4
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 +321 -0
- package/README.md +297 -2
- package/dist/agent/controller.d.ts +193 -1
- package/dist/agent/controller.js +475 -2
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +47 -0
- package/dist/agent/reducer.d.ts +189 -0
- package/dist/agent/reducer.js +301 -2
- package/dist/agent/system-prompt.d.ts +14 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +297 -16
- package/dist/commands/builtins.js +165 -24
- 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 +118 -0
- package/dist/compaction/command.d.ts +41 -0
- package/dist/compaction/command.js +355 -0
- package/dist/compaction/compactor.d.ts +302 -0
- package/dist/compaction/compactor.js +810 -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 +215 -0
- package/dist/compaction/types.js +22 -0
- package/dist/compaction/wiring.d.ts +264 -0
- package/dist/compaction/wiring.js +577 -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 +5 -2
- package/dist/config/env.js +140 -3
- package/dist/config/load.d.ts +42 -0
- package/dist/config/load.js +101 -1
- package/dist/config/schema.d.ts +244 -0
- package/dist/config/schema.js +170 -0
- package/dist/config/store.js +37 -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/limits.d.ts +72 -0
- package/dist/input/limits.js +63 -0
- package/dist/input/mouse-events.d.ts +31 -5
- package/dist/input/mouse-events.js +86 -27
- package/dist/input/paste-parse.d.ts +71 -0
- package/dist/input/paste-parse.js +95 -0
- package/dist/input/stdin-filter.d.ts +81 -0
- package/dist/input/stdin-filter.js +379 -0
- package/dist/logging/install.d.ts +53 -1
- package/dist/logging/install.js +81 -1
- package/dist/logging/logger.d.ts +1 -1
- package/dist/proc/classify.d.ts +52 -0
- package/dist/proc/classify.js +143 -0
- package/dist/proc/kill-tree.d.ts +42 -0
- package/dist/proc/kill-tree.js +119 -0
- package/dist/proc/limits.d.ts +84 -0
- package/dist/proc/limits.js +84 -0
- package/dist/proc/log-ring.d.ts +68 -0
- package/dist/proc/log-ring.js +102 -0
- package/dist/proc/prompt.d.ts +24 -0
- package/dist/proc/prompt.js +49 -0
- package/dist/proc/readiness.d.ts +102 -0
- package/dist/proc/readiness.js +219 -0
- package/dist/proc/supervisor.d.ts +103 -0
- package/dist/proc/supervisor.js +465 -0
- package/dist/proc/types.d.ts +175 -0
- package/dist/proc/types.js +15 -0
- package/dist/session/persist.js +55 -1
- 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/tools/bash-tool.d.ts +51 -2
- package/dist/tools/bash-tool.js +321 -51
- package/dist/tools/fs-tools.d.ts +17 -0
- package/dist/tools/index.d.ts +55 -2
- package/dist/tools/index.js +39 -1
- package/dist/tools/proc-tools.d.ts +25 -0
- package/dist/tools/proc-tools.js +102 -0
- package/dist/ui/ActivityLine.d.ts +19 -1
- package/dist/ui/ActivityLine.js +10 -2
- package/dist/ui/App.d.ts +62 -2
- package/dist/ui/App.js +546 -16
- package/dist/ui/Composer.d.ts +66 -1
- package/dist/ui/Composer.js +50 -6
- package/dist/ui/PromptInput.d.ts +82 -3
- package/dist/ui/PromptInput.js +232 -49
- package/dist/ui/StatusBar.d.ts +59 -0
- package/dist/ui/StatusBar.js +16 -4
- package/dist/ui/TeamPanel.js +7 -1
- package/dist/ui/Transcript.d.ts +21 -1
- package/dist/ui/Transcript.js +61 -3
- package/dist/ui/clipboard.d.ts +52 -0
- package/dist/ui/clipboard.js +91 -0
- package/dist/ui/composer-limits.d.ts +31 -0
- package/dist/ui/composer-limits.js +50 -0
- package/dist/ui/composer-rows.d.ts +73 -0
- package/dist/ui/composer-rows.js +201 -0
- package/dist/ui/editor-reducer.d.ts +37 -0
- package/dist/ui/editor-reducer.js +88 -12
- package/dist/ui/entries/CompactionCard.d.ts +75 -0
- package/dist/ui/entries/CompactionCard.js +158 -0
- package/dist/ui/entries/ServiceCard.d.ts +66 -0
- package/dist/ui/entries/ServiceCard.js +116 -0
- package/dist/ui/entries/UserEntry.d.ts +17 -3
- package/dist/ui/entries/UserEntry.js +34 -5
- 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 +44 -9
- package/dist/ui/layout/budget.js +42 -10
- 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 +85 -1
- package/dist/ui/overlays/HelpOverlay.js +9 -1
- package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
- package/dist/ui/overlays/QuestionOverlay.js +5 -1
- package/dist/ui/overlays/SettingsScreen.d.ts +47 -1
- package/dist/ui/overlays/SettingsScreen.js +74 -2
- package/dist/ui/palettes.d.ts +16 -0
- package/dist/ui/palettes.js +6 -0
- package/dist/ui/paste-frames.d.ts +87 -0
- package/dist/ui/paste-frames.js +112 -0
- package/dist/ui/paste-tokens.d.ts +90 -0
- package/dist/ui/paste-tokens.js +147 -0
- package/dist/ui/screen.d.ts +38 -0
- package/dist/ui/screen.js +113 -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 +72 -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.d.ts +1 -1
- package/dist/ui/use-wheel-routing.js +8 -0
- package/package.json +4 -2
- package/dist/input/stdin-mouse-filter.d.ts +0 -42
- package/dist/input/stdin-mouse-filter.js +0 -153
|
@@ -0,0 +1,577 @@
|
|
|
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
|
+
*/
|
|
38
|
+
import { DEFAULT_RETRY_POLICY, estimatePromptTokens, initProviders, validateHistory, } from '@aragon-agent/core';
|
|
39
|
+
import { getLogger } from '../logging/logger.js';
|
|
40
|
+
import { mintRunId, writeArchive } from './archive.js';
|
|
41
|
+
import { createChildContextManager } from './child.js';
|
|
42
|
+
import { Compactor } from './compactor.js';
|
|
43
|
+
/**
|
|
44
|
+
* One retry, not ten and not zero - the `fast/wiring.ts` argument unchanged
|
|
45
|
+
* (D-10).
|
|
46
|
+
*
|
|
47
|
+
* Inheriting the user's `/retry` policy would let a background repair compete
|
|
48
|
+
* with the user's own work for a rate-limited provider's quota, and it would put
|
|
49
|
+
* up to ten backoffs inside a window the idle watchdog is PAUSED across.
|
|
50
|
+
*/
|
|
51
|
+
const COMPACTION_RETRY_POLICY = { ...DEFAULT_RETRY_POLICY, maxRetries: 1 };
|
|
52
|
+
/** The status of a session that never had compaction (the wiring is `null`). */
|
|
53
|
+
export function offCompactionSnapshot() {
|
|
54
|
+
return {
|
|
55
|
+
live: false,
|
|
56
|
+
model: '',
|
|
57
|
+
compactions: 0,
|
|
58
|
+
tokensReclaimed: 0,
|
|
59
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
60
|
+
pricingUnknown: false,
|
|
61
|
+
inFlight: false,
|
|
62
|
+
selfDisabled: false,
|
|
63
|
+
generation: 0,
|
|
64
|
+
pressure: {
|
|
65
|
+
occupied: 0,
|
|
66
|
+
contextWindow: 0,
|
|
67
|
+
ratio: 0,
|
|
68
|
+
headroom: 0,
|
|
69
|
+
source: 'estimate',
|
|
70
|
+
windowKnown: false,
|
|
71
|
+
deltaTokens: 0,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export class CompactionWiring {
|
|
76
|
+
deps;
|
|
77
|
+
log = getLogger().child('compaction');
|
|
78
|
+
listeners = new Set();
|
|
79
|
+
compactor;
|
|
80
|
+
/** The compactor's OWN transport (D-10). Lazy - see `getRegistry()`. */
|
|
81
|
+
registry = null;
|
|
82
|
+
unsubscribe = null;
|
|
83
|
+
/** The stable port handed to `new Agent({...})`. Never reassigned. */
|
|
84
|
+
port;
|
|
85
|
+
/**
|
|
86
|
+
* The record the compactor produced, waiting for the ENGINE's verdict.
|
|
87
|
+
*
|
|
88
|
+
* `applied` is not knowable on the host side: validation happens after
|
|
89
|
+
* `compact()` returns, inside the loop. So the compactor's record is held here
|
|
90
|
+
* and corrected when the core `compaction_end` arrives (§3.2.1). The IDLE
|
|
91
|
+
* `/compact` path has no core event and settles its own record directly.
|
|
92
|
+
*/
|
|
93
|
+
pending = null;
|
|
94
|
+
/**
|
|
95
|
+
* What the CLI-local `compaction_start` announced, and `null` once the card has
|
|
96
|
+
* been settled.
|
|
97
|
+
*
|
|
98
|
+
* THE ENGINE'S VERDICT CAN ARRIVE BEFORE THE COMPACTOR'S RECORD, and Esc is the
|
|
99
|
+
* ordinary way it happens: `runCompaction` races `compact()` against the run's
|
|
100
|
+
* signal, so an abort resolves the race and emits `compaction_end` while
|
|
101
|
+
* `compact()` is still unwinding its own call. With nothing held here,
|
|
102
|
+
* `settlePending` would find no record, return, and leave the card `live:
|
|
103
|
+
* true` FOREVER — which is C-8's failure mode exactly: `Transcript`'s settled
|
|
104
|
+
* boundary is monotonic, so the whole tail re-renders on every frame for the
|
|
105
|
+
* rest of the session, and the card goes on claiming a compaction is running.
|
|
106
|
+
* `manager_timeout` and a `compact()` that rejects outright reach the same
|
|
107
|
+
* place.
|
|
108
|
+
*
|
|
109
|
+
* So the verdict settles the card from whatever is available — the compactor's
|
|
110
|
+
* record when it exists, this skeleton when it does not — and clearing this
|
|
111
|
+
* field is what tells a LATE record that its card is already closed.
|
|
112
|
+
*/
|
|
113
|
+
open = null;
|
|
114
|
+
/** Live flag, flipped by `/compact on|off`. Starts equal to `enabled`. */
|
|
115
|
+
enabled;
|
|
116
|
+
/**
|
|
117
|
+
* Identifies THIS RUN's archives in a directory several runs share (RV-6).
|
|
118
|
+
*
|
|
119
|
+
* NOT `SessionMeta.id`: that exists only once a session has been saved, and the
|
|
120
|
+
* unattended long run this feature serves is exactly the one nobody saved.
|
|
121
|
+
* Short, filename-safe, and only ever compared for equality - it is a grouping
|
|
122
|
+
* key, not an identity.
|
|
123
|
+
*/
|
|
124
|
+
runId = mintRunId();
|
|
125
|
+
constructor(deps) {
|
|
126
|
+
this.deps = deps;
|
|
127
|
+
this.enabled = deps.getConfig().compaction.enabled;
|
|
128
|
+
this.compactor = new Compactor({
|
|
129
|
+
getConfig: deps.getConfig,
|
|
130
|
+
hasKey: deps.hasKey,
|
|
131
|
+
getApiKey: deps.getApiKey,
|
|
132
|
+
getModelInfoFor: deps.getModelInfoFor,
|
|
133
|
+
isPricedModel: deps.isPricedModel,
|
|
134
|
+
// `shouldCompact` reads these on the ESTIMATE branch (§3.4.2). Forwarded
|
|
135
|
+
// rather than defaulted to `[]`, because an empty history estimates to 0 %
|
|
136
|
+
// and the resumed-session case would then never trigger.
|
|
137
|
+
getMessages: deps.getMessages,
|
|
138
|
+
getSystemPrompt: deps.getSystemPrompt,
|
|
139
|
+
complete: (providerId, request) => this.getRegistry().complete(providerId, request),
|
|
140
|
+
emit: (event) => this.onCompactorEvent(event),
|
|
141
|
+
notify: deps.notify,
|
|
142
|
+
// ONE NUMBER ON SCREEN AND IN THE TRIGGER (R-11 / DH-15). The checkpoint
|
|
143
|
+
// has already measured; publishing THAT pressure rather than re-measuring
|
|
144
|
+
// is what keeps the two from disagreeing by one turn's tool results.
|
|
145
|
+
onPressure: (pressure) => this.emit({ type: 'snapshot', snapshot: this.snapshotWith(pressure) }),
|
|
146
|
+
});
|
|
147
|
+
// ONE OBJECT, FOR THE CONTROLLER'S LIFETIME. The `Agent` therefore needs no
|
|
148
|
+
// setter and `contextManager` is never reassigned; the LIVE switch is inside
|
|
149
|
+
// `shouldCompact`, not in the identity of this object.
|
|
150
|
+
this.port = {
|
|
151
|
+
shouldCompact: (probe) => this.enabled && this.compactor.shouldCompact(probe),
|
|
152
|
+
compact: (ctx) => this.compactor.compact(ctx),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The port for `new Agent({...})`.
|
|
157
|
+
*
|
|
158
|
+
* Spread CONDITIONALLY at the call site so `contextManager` stays genuinely
|
|
159
|
+
* `undefined` when the feature is off (AC-1): the loop's gate is
|
|
160
|
+
* `if (!ctx.contextManager) return false` against an ABSENT field, not a live
|
|
161
|
+
* object whose predicate says no.
|
|
162
|
+
*/
|
|
163
|
+
manager() {
|
|
164
|
+
return this.port;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Subscribe to the lead agent's stream. IDEMPOTENT, and separate from the
|
|
168
|
+
* constructor - see the file header.
|
|
169
|
+
*/
|
|
170
|
+
attach(subscribe) {
|
|
171
|
+
if (this.unsubscribe)
|
|
172
|
+
return;
|
|
173
|
+
this.unsubscribe = subscribe((event) => this.onAgentEvent(event));
|
|
174
|
+
}
|
|
175
|
+
dispose() {
|
|
176
|
+
this.compactor.abort();
|
|
177
|
+
this.unsubscribe?.();
|
|
178
|
+
this.unsubscribe = null;
|
|
179
|
+
this.listeners.clear();
|
|
180
|
+
}
|
|
181
|
+
// -----------------------------------------------------------------------
|
|
182
|
+
// Flags
|
|
183
|
+
// -----------------------------------------------------------------------
|
|
184
|
+
isEnabled() {
|
|
185
|
+
return this.enabled;
|
|
186
|
+
}
|
|
187
|
+
setEnabled(enabled) {
|
|
188
|
+
this.enabled = enabled;
|
|
189
|
+
// Turning it back on is the user overriding guard 4, which is the only way
|
|
190
|
+
// out of a self-disable short of a restart.
|
|
191
|
+
if (enabled)
|
|
192
|
+
this.compactor.clearSelfDisable();
|
|
193
|
+
this.emit({ type: 'snapshot', snapshot: this.snapshot() });
|
|
194
|
+
}
|
|
195
|
+
/** Re-emit the snapshot after any config mutation, so the chip and the gauge move. */
|
|
196
|
+
onConfigChanged() {
|
|
197
|
+
this.emit({ type: 'snapshot', snapshot: this.snapshot() });
|
|
198
|
+
}
|
|
199
|
+
abort() {
|
|
200
|
+
this.compactor.abort();
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* The host replaced or cleared the history (hardening §3.2.3, sites 3 and 4).
|
|
204
|
+
*
|
|
205
|
+
* `/clear`, `/resume`, and any future host-side rewrite. A recorded prefix
|
|
206
|
+
* length indexes an array that no longer exists, so the delta must be
|
|
207
|
+
* abandoned rather than applied to a different history.
|
|
208
|
+
*/
|
|
209
|
+
onHistoryReplaced() {
|
|
210
|
+
this.compactor.invalidateMeasurement();
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* The per-child manager factory, or `null` when children get none
|
|
214
|
+
* (hardening §3.4 / W3).
|
|
215
|
+
*
|
|
216
|
+
* `null` RATHER THAN A NO-OP FACTORY, so `subagent.ts` can spread NOTHING and
|
|
217
|
+
* the child's loop gate tests a genuinely absent `contextManager` - the same
|
|
218
|
+
* byte-identity argument `AgentController` makes for the lead (AC-H10).
|
|
219
|
+
*/
|
|
220
|
+
childFactory() {
|
|
221
|
+
if (!this.deps.getConfig().compaction.subagents)
|
|
222
|
+
return null;
|
|
223
|
+
return (req) => createChildContextManager(req, {
|
|
224
|
+
complete: (providerId, request) => this.getRegistry().complete(providerId, request),
|
|
225
|
+
getConfig: this.deps.getConfig,
|
|
226
|
+
hasKey: this.deps.hasKey,
|
|
227
|
+
getApiKey: this.deps.getApiKey,
|
|
228
|
+
getModelInfoFor: this.deps.getModelInfoFor,
|
|
229
|
+
isPricedModel: this.deps.isPricedModel,
|
|
230
|
+
// A CHILD'S COMPACTION SPEND JOINS THE SESSION TOTALS THROUGH THE LEAD'S
|
|
231
|
+
// SINK (AC-H12). It is real money on the summarizer's price table, and a
|
|
232
|
+
// background repair inside a background worker is exactly the spend a
|
|
233
|
+
// user would otherwise never see.
|
|
234
|
+
onUsage: (usage) => this.emit({ type: 'usage', usage }),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
// -----------------------------------------------------------------------
|
|
238
|
+
// The manual queue (§4.4)
|
|
239
|
+
// -----------------------------------------------------------------------
|
|
240
|
+
queueManual(instructions) {
|
|
241
|
+
this.compactor.queueManual(instructions);
|
|
242
|
+
}
|
|
243
|
+
hasPendingManual() {
|
|
244
|
+
return this.compactor.hasPendingManual();
|
|
245
|
+
}
|
|
246
|
+
clearPendingManual() {
|
|
247
|
+
this.compactor.clearPendingManual();
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* The IDLE `/compact` path (§4.4 / D-25).
|
|
251
|
+
*
|
|
252
|
+
* IT VALIDATES BEFORE IT SPLICES, AND THAT IS NOT OPTIONAL (P1-7). When the
|
|
253
|
+
* agent is idle there is no loop, so §3.3 step 5 does not run — and the only
|
|
254
|
+
* way to replace the history is `Agent.replaceMessages`, which validates
|
|
255
|
+
* nothing. D-4 declares that gate unbypassable and R-2 calls an invalid splice
|
|
256
|
+
* the worst possible outcome, so this path mirrors the engine's steps 3-5
|
|
257
|
+
* exactly, INCLUDING the failure vocabulary: the card, the log and
|
|
258
|
+
* `/compact status` cannot tell the two paths apart.
|
|
259
|
+
*
|
|
260
|
+
* No watchdog handling is needed — `runLoopWithLifecycle`'s `finally` already
|
|
261
|
+
* called `watchdog.stop()`, so nothing is armed while idle.
|
|
262
|
+
*
|
|
263
|
+
* ITS CALLER MUST ADOPT `messages` — THE ARCHIVE IS ALREADY WRITTEN BY THE
|
|
264
|
+
* TIME THIS RETURNS (hardening RV-7). This path settles `applied: true` here,
|
|
265
|
+
* one statement before the caller splices, so the "never archive a live
|
|
266
|
+
* history" rule holds only because that caller applies unconditionally. That is
|
|
267
|
+
* a fact about one call site rather than an invariant, which is why it is
|
|
268
|
+
* written down where it can be checked.
|
|
269
|
+
*/
|
|
270
|
+
async compactNow(args) {
|
|
271
|
+
this.compactor.queueManual(args.instructions);
|
|
272
|
+
// BOTH SIDES FROM CORE'S OWN ESTIMATOR, exactly as the in-loop path gets them
|
|
273
|
+
// from `compaction_end` (§5.1). There is no engine event here to supply them,
|
|
274
|
+
// and the two numbers are the card's headline claim ("118.4k -> 23.1k") and
|
|
275
|
+
// the input to `tokensReclaimed`: a `tokensAfter` of 0 would render a
|
|
276
|
+
// compaction that freed the entire window and inflate the session total by
|
|
277
|
+
// the whole occupancy. Two figures produced by one function are comparable;
|
|
278
|
+
// a measured "before" against a zeroed "after" is not.
|
|
279
|
+
const tokensBefore = estimatePromptTokens(args.messages, args.systemPrompt);
|
|
280
|
+
const outcome = await this.compactor.compact({
|
|
281
|
+
messageCount: args.messages.length,
|
|
282
|
+
turnIndex: 0,
|
|
283
|
+
trigger: 'pressure',
|
|
284
|
+
...(args.lastUsage ? { lastUsage: args.lastUsage } : {}),
|
|
285
|
+
messages: args.messages,
|
|
286
|
+
systemPrompt: args.systemPrompt,
|
|
287
|
+
model: args.model,
|
|
288
|
+
signal: args.signal,
|
|
289
|
+
});
|
|
290
|
+
// NOTHING CHANGED, so "after" IS "before" — the same thing the engine reports
|
|
291
|
+
// when it refuses a splice.
|
|
292
|
+
if (outcome.action !== 'replace') {
|
|
293
|
+
this.settlePending({
|
|
294
|
+
applied: false,
|
|
295
|
+
reason: outcome.reason,
|
|
296
|
+
tokensBefore,
|
|
297
|
+
tokensAfter: tokensBefore,
|
|
298
|
+
});
|
|
299
|
+
return { ok: false, reason: outcome.reason };
|
|
300
|
+
}
|
|
301
|
+
const check = validateHistory(outcome.messages, { previousLength: args.messages.length });
|
|
302
|
+
if (!check.ok) {
|
|
303
|
+
const reason = `invalid_history: ${check.reason}`;
|
|
304
|
+
this.settlePending({ applied: false, reason, tokensBefore, tokensAfter: tokensBefore });
|
|
305
|
+
return { ok: false, reason };
|
|
306
|
+
}
|
|
307
|
+
this.settlePending({
|
|
308
|
+
applied: true,
|
|
309
|
+
tokensBefore,
|
|
310
|
+
tokensAfter: estimatePromptTokens(outcome.messages, args.systemPrompt),
|
|
311
|
+
});
|
|
312
|
+
return { ok: true, messages: outcome.messages };
|
|
313
|
+
}
|
|
314
|
+
// -----------------------------------------------------------------------
|
|
315
|
+
// Status and events
|
|
316
|
+
// -----------------------------------------------------------------------
|
|
317
|
+
snapshot() {
|
|
318
|
+
const pressure = this.compactor.lastMeasured() ??
|
|
319
|
+
this.compactor.measure({
|
|
320
|
+
messages: this.deps.getMessages(),
|
|
321
|
+
systemPrompt: this.deps.getSystemPrompt(),
|
|
322
|
+
});
|
|
323
|
+
return this.snapshotWith(pressure);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* `snapshot()` with the pressure argument substituted for the re-measurement.
|
|
327
|
+
*
|
|
328
|
+
* SO THE CHECKPOINT DOES NOT MEASURE TWICE. `shouldCompact` has already walked
|
|
329
|
+
* the appended slice; measuring again here would double the one cost DH-15
|
|
330
|
+
* accepted, and - worse - the second measurement could differ from the one the
|
|
331
|
+
* decision was made on if anything appended in between.
|
|
332
|
+
*/
|
|
333
|
+
snapshotWith(pressure) {
|
|
334
|
+
const totals = this.compactor.sessionTotals();
|
|
335
|
+
const summarizer = this.compactor.resolveSummarizer();
|
|
336
|
+
return {
|
|
337
|
+
live: this.enabled && summarizer !== null,
|
|
338
|
+
model: summarizer?.ref.modelId ?? '',
|
|
339
|
+
compactions: totals.compactions,
|
|
340
|
+
// OMITTED WHEN ZERO (quiet-noop D-9), so a healthy session's snapshot is
|
|
341
|
+
// shaped exactly as it was before this feature and `/compact status` has
|
|
342
|
+
// nothing extra to render.
|
|
343
|
+
...(totals.declined > 0 ? { declined: totals.declined } : {}),
|
|
344
|
+
tokensReclaimed: totals.tokensReclaimed,
|
|
345
|
+
usage: totals.usage,
|
|
346
|
+
pricingUnknown: totals.pricingUnknown,
|
|
347
|
+
inFlight: totals.inFlight,
|
|
348
|
+
selfDisabled: totals.selfDisabled,
|
|
349
|
+
...(totals.selfDisabledReason ? { selfDisabledReason: totals.selfDisabledReason } : {}),
|
|
350
|
+
generation: totals.generation,
|
|
351
|
+
pressure,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
/** The summarizer that would be used right now, for `/compact status`. */
|
|
355
|
+
summarizerRef() {
|
|
356
|
+
return this.compactor.resolveSummarizer()?.ref ?? null;
|
|
357
|
+
}
|
|
358
|
+
subscribe(listener) {
|
|
359
|
+
this.listeners.add(listener);
|
|
360
|
+
return () => this.listeners.delete(listener);
|
|
361
|
+
}
|
|
362
|
+
// -----------------------------------------------------------------------
|
|
363
|
+
// Internals
|
|
364
|
+
// -----------------------------------------------------------------------
|
|
365
|
+
/**
|
|
366
|
+
* The transport one summarization call goes through - separate from the lead's,
|
|
367
|
+
* and fail-fast (D-10).
|
|
368
|
+
*
|
|
369
|
+
* LAZY, SO OFF STAYS BYTE-IDENTICAL. Nothing is constructed until a compaction
|
|
370
|
+
* actually calls, so a session that never crosses the threshold allocates no
|
|
371
|
+
* registry and registers no adapters.
|
|
372
|
+
*/
|
|
373
|
+
getRegistry() {
|
|
374
|
+
if (this.registry === null) {
|
|
375
|
+
const create = this.deps.createRegistry ?? initProviders;
|
|
376
|
+
this.registry = create({ retryPolicy: COMPACTION_RETRY_POLICY });
|
|
377
|
+
this.log.info('compaction_registry_created', {
|
|
378
|
+
maxRetries: COMPACTION_RETRY_POLICY.maxRetries,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
return this.registry;
|
|
382
|
+
}
|
|
383
|
+
onCompactorEvent(event) {
|
|
384
|
+
if (event.type === 'compaction_start') {
|
|
385
|
+
this.open = { index: event.index, trigger: event.trigger, model: event.model };
|
|
386
|
+
this.pending = null;
|
|
387
|
+
this.emit(event);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (event.type === 'compaction_end') {
|
|
391
|
+
// DROPPED WHEN THE CARD IS ALREADY CLOSED. An abort or the engine's hard
|
|
392
|
+
// ceiling settles the card from the verdict alone, and this record is what
|
|
393
|
+
// arrives afterwards, when `compact()` finally unwinds; emitting it would
|
|
394
|
+
// open a second card for a compaction the user has already been told about.
|
|
395
|
+
if (!this.open)
|
|
396
|
+
return;
|
|
397
|
+
// Otherwise HELD, NOT FORWARDED. `applied` is the engine's verdict and has
|
|
398
|
+
// not happened yet; forwarding an optimistic record would settle the card
|
|
399
|
+
// with a claim the structural gate may be about to refuse.
|
|
400
|
+
this.pending = event.record;
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
this.emit(event);
|
|
404
|
+
}
|
|
405
|
+
onAgentEvent(event) {
|
|
406
|
+
if (event.type === 'agent_start') {
|
|
407
|
+
this.compactor.onRunStart();
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (event.type === 'turn_end') {
|
|
411
|
+
// The calibration offset, re-measured every turn (D-23). Read the history
|
|
412
|
+
// AFTER the turn's assistant message is pushed? No: `turn_end` is emitted
|
|
413
|
+
// BEFORE the push, so this is exactly the history the reported `usage`
|
|
414
|
+
// was measured against, which is what makes the difference meaningful.
|
|
415
|
+
this.compactor.onTurnEnd(event.usage, this.deps.getMessages(), this.deps.getSystemPrompt());
|
|
416
|
+
// The gauge and the trigger read one function (R-11 / AC-3), so the
|
|
417
|
+
// snapshot's pressure is refreshed from the same measurement the status bar
|
|
418
|
+
// is about to render.
|
|
419
|
+
this.compactor.measure({
|
|
420
|
+
lastUsage: event.usage,
|
|
421
|
+
messages: this.deps.getMessages(),
|
|
422
|
+
systemPrompt: this.deps.getSystemPrompt(),
|
|
423
|
+
});
|
|
424
|
+
this.emit({ type: 'snapshot', snapshot: this.snapshot() });
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (event.type === 'compaction_end') {
|
|
428
|
+
this.settlePending({
|
|
429
|
+
applied: event.applied,
|
|
430
|
+
...(event.reason !== undefined ? { reason: event.reason } : {}),
|
|
431
|
+
tokensBefore: event.estimatedTokensBefore,
|
|
432
|
+
tokensAfter: event.estimatedTokensAfter,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Settle the held record with the authoritative verdict, then emit it.
|
|
438
|
+
*
|
|
439
|
+
* `tokensBefore` / `tokensAfter` come from CORE'S OWN estimator on BOTH sides
|
|
440
|
+
* (§5.1), so the card's headline claim compares two numbers in one unit rather
|
|
441
|
+
* than a measurement against a guess. `tokensReclaimed` is accumulated from the
|
|
442
|
+
* same pair, which is what keeps `/compact status` and the card agreeing.
|
|
443
|
+
*/
|
|
444
|
+
settlePending(verdict) {
|
|
445
|
+
// `?? skeletonRecord()` IS THE ABORT PATH, not padding. See `open` above: the
|
|
446
|
+
// engine's verdict can land while `compact()` is still unwinding, and a card
|
|
447
|
+
// that never settles re-renders the tail on every frame for the rest of the
|
|
448
|
+
// session. A skeleton settle can only ever carry `applied: false` — the
|
|
449
|
+
// engine cannot report `true` without a `replace` outcome, which means the
|
|
450
|
+
// compactor's own record already exists — so its zeroed counts are never
|
|
451
|
+
// rendered.
|
|
452
|
+
const record = this.pending ?? this.skeletonRecord();
|
|
453
|
+
this.pending = null;
|
|
454
|
+
this.open = null;
|
|
455
|
+
// CALLED ON EVERY VERDICT AND DISCARDED ON THE ONES THAT DO NOT ARCHIVE
|
|
456
|
+
// (RV-3). `takeLastDropped` releases the reference as it returns it, so this
|
|
457
|
+
// one line is what guarantees the dropped history cannot outlive one
|
|
458
|
+
// compaction no matter which branch the verdict took - including the early
|
|
459
|
+
// `return` below.
|
|
460
|
+
const dropped = this.compactor.takeLastDropped();
|
|
461
|
+
if (!record) {
|
|
462
|
+
// A SILENT DECLINE (quiet-noop §3.6). No card - but the gauge and the chip
|
|
463
|
+
// are still refreshed: `inFlight` has just gone false and `/compact status`
|
|
464
|
+
// reads this same snapshot, which is where the `declined` count surfaces.
|
|
465
|
+
// `snapshot()` uses the CACHED pressure (`compactor.lastMeasured()`), so
|
|
466
|
+
// this costs no re-measurement, and guard 4 caps the number of declines per
|
|
467
|
+
// session at `stuckLimit`.
|
|
468
|
+
this.emit({ type: 'snapshot', snapshot: this.snapshot() });
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const settled = {
|
|
472
|
+
...record,
|
|
473
|
+
applied: verdict.applied,
|
|
474
|
+
...(verdict.reason !== undefined ? { reason: verdict.reason } : {}),
|
|
475
|
+
...(verdict.tokensBefore !== undefined ? { tokensBefore: verdict.tokensBefore } : {}),
|
|
476
|
+
tokensAfter: verdict.tokensAfter,
|
|
477
|
+
mode: verdict.applied ? record.mode : 'none',
|
|
478
|
+
};
|
|
479
|
+
if (verdict.applied) {
|
|
480
|
+
this.compactor.recordApplied(settled.tokensBefore, settled.tokensAfter);
|
|
481
|
+
// SITE 1 OF THE FOUR INVALIDATION SITES (hardening §3.2.3). The in-loop
|
|
482
|
+
// splice replaced the history, so every recorded index means nothing; the
|
|
483
|
+
// manual path settles through here too, so one call covers both.
|
|
484
|
+
this.compactor.invalidateMeasurement();
|
|
485
|
+
const path = this.archive(settled, dropped);
|
|
486
|
+
if (path)
|
|
487
|
+
settled.archivePath = path;
|
|
488
|
+
}
|
|
489
|
+
this.emit({ type: 'compaction_end', record: settled });
|
|
490
|
+
this.emit({ type: 'snapshot', snapshot: this.snapshot() });
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Write the archive, if there is one to write (hardening §3.5.3 / W4).
|
|
494
|
+
*
|
|
495
|
+
* ONLY ON `applied: true`, because the rule the writer enforces is "never
|
|
496
|
+
* archive a history that is still live" - and the two paths reach that verdict
|
|
497
|
+
* at different moments relative to the splice. In loop, the ENGINE decides
|
|
498
|
+
* after `validateHistory`, so this runs AFTER adoption. On the manual path the
|
|
499
|
+
* WIRING decides and `compactNow`'s caller adopts on the next statement, so
|
|
500
|
+
* this runs BEFORE adoption; `compactNow`'s doc comment carries that obligation
|
|
501
|
+
* for its one caller (RV-7).
|
|
502
|
+
*
|
|
503
|
+
* BEST-EFFORT AND SYNCHRONOUS-BUT-GUARDED. `writeArchive` never throws and
|
|
504
|
+
* logs its own failure; a failed archive never affects the compaction, and it
|
|
505
|
+
* never notifies - a toast for a best-effort audit file trains users to ignore
|
|
506
|
+
* toasts.
|
|
507
|
+
*/
|
|
508
|
+
archive(record, dropped) {
|
|
509
|
+
if (!this.deps.getConfig().compaction.archive)
|
|
510
|
+
return null;
|
|
511
|
+
// A RELIEF-ONLY COMPACTION DROPS NOTHING AND IS STILL ARCHIVED, which is why
|
|
512
|
+
// `mode` is `summarized | truncated | relieved` in §3.5.2's document rather
|
|
513
|
+
// than the first two. `/compact history` numbers its rows by COMPACTION
|
|
514
|
+
// INDEX, so skipping this one leaves a hole at exactly the index a user
|
|
515
|
+
// investigating a clipped tail would ask for, and `/compact show <n>` answers
|
|
516
|
+
// "no archive" for the compaction that damaged their data (AC-H9).
|
|
517
|
+
//
|
|
518
|
+
// ANY OTHER VERDICT WITH NO RETAINED SLICE was compacted while the key was
|
|
519
|
+
// off and genuinely has nothing to record - writing `droppedCount: 0` for it
|
|
520
|
+
// would claim a splice dropped nothing when it dropped a hundred messages.
|
|
521
|
+
if (!dropped && record.mode !== 'relieved')
|
|
522
|
+
return null;
|
|
523
|
+
return writeArchive({
|
|
524
|
+
runId: this.runId,
|
|
525
|
+
index: record.index,
|
|
526
|
+
trigger: record.trigger,
|
|
527
|
+
mode: record.mode,
|
|
528
|
+
model: record.model,
|
|
529
|
+
generation: this.compactor.sessionTotals().generation,
|
|
530
|
+
tokensBefore: record.tokensBefore,
|
|
531
|
+
tokensAfter: record.tokensAfter,
|
|
532
|
+
messagesBefore: record.messagesBefore,
|
|
533
|
+
messagesAfter: record.messagesAfter,
|
|
534
|
+
...(record.summary !== undefined ? { summary: record.summary } : {}),
|
|
535
|
+
...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
|
|
536
|
+
dropped: dropped ?? [],
|
|
537
|
+
...(this.deps.archiveDir ? { dir: this.deps.archiveDir } : {}),
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
/** This run's archive key, for `/compact history` and `/compact show`. */
|
|
541
|
+
archiveRunId() {
|
|
542
|
+
return this.runId;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* The minimum a card needs to settle when the compactor's own record has not
|
|
546
|
+
* arrived (see `open`). `null` when no card was ever opened — the
|
|
547
|
+
* `shouldCompact`-threw path, where the engine emits a pair of events the CLI
|
|
548
|
+
* stream knows nothing about.
|
|
549
|
+
*/
|
|
550
|
+
skeletonRecord() {
|
|
551
|
+
if (!this.open)
|
|
552
|
+
return null;
|
|
553
|
+
return {
|
|
554
|
+
index: this.open.index,
|
|
555
|
+
trigger: this.open.trigger,
|
|
556
|
+
mode: 'none',
|
|
557
|
+
applied: false,
|
|
558
|
+
messagesBefore: 0,
|
|
559
|
+
messagesAfter: 0,
|
|
560
|
+
tokensBefore: 0,
|
|
561
|
+
tokensAfter: 0,
|
|
562
|
+
model: this.open.model,
|
|
563
|
+
durationMs: 0,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
emit(event) {
|
|
567
|
+
for (const listener of [...this.listeners]) {
|
|
568
|
+
try {
|
|
569
|
+
listener(event);
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
// One bad subscriber must not take the run down with it - the rule
|
|
573
|
+
// `TeamRuntime.emit` and `FastWiring.emit` both already state.
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
@@ -112,6 +112,20 @@ export declare function getUpdateLockPath(): string;
|
|
|
112
112
|
export declare function getUpdateInstallLogPath(): string;
|
|
113
113
|
export declare function getLogsDir(): string;
|
|
114
114
|
export declare function getSessionsDir(): string;
|
|
115
|
+
/**
|
|
116
|
+
* `<home>/compaction` — the compaction archive
|
|
117
|
+
* (context-auto-compaction-hardening §3.5 / W4).
|
|
118
|
+
*
|
|
119
|
+
* ITS OWN ROOT, AND EXPLICITLY NOT INSIDE `sessions/`. `logs/`, `sessions/` and
|
|
120
|
+
* `skills/` are all SCAN roots — the note above `getUpdateStatePath` says it in
|
|
121
|
+
* as many words: every extra file inside one is another thing each walk has to
|
|
122
|
+
* recognise and skip. An archive dropped into `sessions/` would be offered to the
|
|
123
|
+
* user by `/resume` as a session it cannot load.
|
|
124
|
+
*
|
|
125
|
+
* SHARED BY EVERY `aragon` ON THE MACHINE, which is why the files inside it carry
|
|
126
|
+
* a per-process run id and why retention is two-tier (§3.5.2 / RV-6).
|
|
127
|
+
*/
|
|
128
|
+
export declare function getCompactionArchiveDir(): string;
|
|
115
129
|
/** `<home>` — parent of `skills/`, and where sidecar state such as usage lives. */
|
|
116
130
|
export declare function getUserDataDir(): string;
|
|
117
131
|
export declare function getUserSkillsDir(): string;
|
package/dist/config/app-paths.js
CHANGED
|
@@ -183,6 +183,22 @@ export function getLogsDir() {
|
|
|
183
183
|
export function getSessionsDir() {
|
|
184
184
|
return join(resolution.root, 'sessions');
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* `<home>/compaction` — the compaction archive
|
|
188
|
+
* (context-auto-compaction-hardening §3.5 / W4).
|
|
189
|
+
*
|
|
190
|
+
* ITS OWN ROOT, AND EXPLICITLY NOT INSIDE `sessions/`. `logs/`, `sessions/` and
|
|
191
|
+
* `skills/` are all SCAN roots — the note above `getUpdateStatePath` says it in
|
|
192
|
+
* as many words: every extra file inside one is another thing each walk has to
|
|
193
|
+
* recognise and skip. An archive dropped into `sessions/` would be offered to the
|
|
194
|
+
* user by `/resume` as a session it cannot load.
|
|
195
|
+
*
|
|
196
|
+
* SHARED BY EVERY `aragon` ON THE MACHINE, which is why the files inside it carry
|
|
197
|
+
* a per-process run id and why retention is two-tier (§3.5.2 / RV-6).
|
|
198
|
+
*/
|
|
199
|
+
export function getCompactionArchiveDir() {
|
|
200
|
+
return join(resolution.root, 'compaction');
|
|
201
|
+
}
|
|
186
202
|
/** `<home>` — parent of `skills/`, and where sidecar state such as usage lives. */
|
|
187
203
|
export function getUserDataDir() {
|
|
188
204
|
return resolution.root;
|
|
@@ -54,6 +54,24 @@ export declare const FAST_CONFIG_SET_KEYS: readonly ["fast.enabled", "fast.provi
|
|
|
54
54
|
* `Set update.mode = notify`.
|
|
55
55
|
*/
|
|
56
56
|
export declare const UPDATE_CONFIG_SET_KEYS: readonly ["update.mode", "update.checkIntervalMs", "update.registry", "update.distTag"];
|
|
57
|
+
/**
|
|
58
|
+
* Config keys under `compaction.` that `aragon config set` accepts
|
|
59
|
+
* (context-auto-compaction §4.2).
|
|
60
|
+
*
|
|
61
|
+
* A DEDICATED SETTER RATHER THAN A GENERIC DOTTED WRITER, for the reason the
|
|
62
|
+
* five sets above each record: a generic setter would write `compaction` as a
|
|
63
|
+
* FLAT string key (`"compaction.enabled": "false"` at the top level of the
|
|
64
|
+
* file), the section would silently never take effect, and `config set` would
|
|
65
|
+
* still print `Set compaction.enabled = false`.
|
|
66
|
+
*
|
|
67
|
+
* THIS SET IS WHAT MAKES THE KILL SWITCH REACHABLE FROM A SCRIPT. `config list`
|
|
68
|
+
* enumerates the section dynamically and so shows these keys whether they are
|
|
69
|
+
* here or not; `config set` does not, so without this block
|
|
70
|
+
* `aragon config set compaction.enabled false` fails with "Unknown config key"
|
|
71
|
+
* while the key is visibly listed one command over. Manual-test row 9 — the
|
|
72
|
+
* persisted-off-survives-a-flagless-run check — runs exactly that command.
|
|
73
|
+
*/
|
|
74
|
+
export declare const COMPACTION_CONFIG_SET_KEYS: readonly ["compaction.enabled", "compaction.threshold", "compaction.warnThreshold", "compaction.keepRecentTurns", "compaction.useFastTier", "compaction.onFailure", "compaction.subagents", "compaction.archive"];
|
|
57
75
|
/**
|
|
58
76
|
* Translate a `log.*` dotted key into a patch, or `null` for anything else.
|
|
59
77
|
*
|
|
@@ -120,6 +138,29 @@ export declare function applyFastConfigSet(key: string, value: string): Partial<
|
|
|
120
138
|
* while the caller echoes the STORED value (AC-20).
|
|
121
139
|
*/
|
|
122
140
|
export declare function applyUpdateConfigSet(key: string, value: string): Partial<PersistedConfig> | null;
|
|
141
|
+
/**
|
|
142
|
+
* Translate a `compaction.*` dotted key into a patch, or `null` for anything
|
|
143
|
+
* else (context-auto-compaction §4.2).
|
|
144
|
+
*
|
|
145
|
+
* EVERY KEY ROUTES THROUGH `clampCompactionConfig`, not through a local parse,
|
|
146
|
+
* for the reason `applyRetryConfigSet`, `applyFastConfigSet` and
|
|
147
|
+
* `applyUpdateConfigSet` all record: one gate for both directions means no key
|
|
148
|
+
* here can pick up a different coercion later, and `config set
|
|
149
|
+
* compaction.threshold 0.2` writes the clamped `0.5` while the caller echoes the
|
|
150
|
+
* STORED value.
|
|
151
|
+
*
|
|
152
|
+
* `threshold` and `warnThreshold` additionally accept a PERCENTAGE, because that
|
|
153
|
+
* is the unit the status bar shows and `/compact threshold` accepts.
|
|
154
|
+
*
|
|
155
|
+
* A VALUE THE PARSER REJECTS RESOLVES TO THE DEFAULT, NOT TO THE CURRENT SETTING
|
|
156
|
+
* — the same thing `config set retry.maxRetries banana` does, because `clampOne`
|
|
157
|
+
* merges the field onto `DEFAULT_COMPACTION_CONFIG` before clamping it. That is
|
|
158
|
+
* the house behaviour and it is not silent: `cli.tsx` echoes the STORED value, so
|
|
159
|
+
* a typo prints the number that was actually written rather than the one that was
|
|
160
|
+
* typed. Unlike the settings screen and `/compact threshold`, both of which
|
|
161
|
+
* genuinely ignore an unparseable value, this surface has no third state.
|
|
162
|
+
*/
|
|
163
|
+
export declare function applyCompactionConfigSet(key: string, value: string): Partial<PersistedConfig> | null;
|
|
123
164
|
/**
|
|
124
165
|
* `log.redactSecrets false` is the one setting that lets credentials reach the
|
|
125
166
|
* disk. It is command-line-only (there is no settings-screen entry) and it says
|