@aragon-agent/cli 0.6.13 → 0.6.15
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 +41 -0
- package/README.md +250 -222
- package/dist/agent/controller.d.ts +36 -2
- package/dist/agent/controller.js +244 -57
- package/dist/agent/model-profile-settings.js +11 -2
- 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 +5 -0
- package/dist/agent/system-prompt.js +4 -0
- package/dist/boot/guard.d.ts +3 -3
- package/dist/boot/guard.js +3 -3
- package/dist/cli.js +33 -15
- package/dist/commands/builtins.js +2 -2
- package/dist/compaction/child.d.ts +9 -1
- package/dist/compaction/child.js +43 -13
- package/dist/compaction/command.js +20 -10
- package/dist/compaction/compactor.d.ts +33 -204
- package/dist/compaction/compactor.js +311 -723
- package/dist/compaction/context-command.js +5 -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 +55 -19
- package/dist/compaction/operation.d.ts +18 -0
- package/dist/compaction/operation.js +68 -0
- package/dist/compaction/pressure.d.ts +6 -20
- package/dist/compaction/pressure.js +14 -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 +23 -3
- package/dist/compaction/wiring.d.ts +10 -25
- package/dist/compaction/wiring.js +60 -67
- package/dist/config/load.d.ts +3 -1
- package/dist/config/load.js +8 -4
- package/dist/config/schema.d.ts +3 -3
- package/dist/config/schema.js +1 -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/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 +221 -126
- 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/Logo.d.ts +3 -5
- package/dist/ui/Logo.js +4 -8
- package/dist/ui/PromptInput.d.ts +3 -1
- package/dist/ui/PromptInput.js +35 -42
- 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/capabilities.d.ts +2 -0
- package/dist/ui/capabilities.js +6 -0
- package/dist/ui/color-runtime.d.ts +5 -0
- package/dist/ui/color-runtime.js +29 -0
- 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/glyphs.js +10 -8
- package/dist/ui/ink-runtime.d.ts +3 -0
- package/dist/ui/ink-runtime.js +30 -0
- 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 -83
- 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/render-cache.d.ts +2 -2
- package/dist/ui/render-cache.js +4 -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;
|
|
@@ -135,7 +100,10 @@ export class CompactionWiring {
|
|
|
135
100
|
getMessages: deps.getMessages,
|
|
136
101
|
getSystemPrompt: deps.getSystemPrompt,
|
|
137
102
|
getModelInfo: () => deps.getModelInfoFor(this.mainRef()),
|
|
138
|
-
isWindowKnown: () =>
|
|
103
|
+
isWindowKnown: () => {
|
|
104
|
+
const source = deps.getModelInfoFor(this.mainRef()).contextWindowSource;
|
|
105
|
+
return source === undefined ? deps.isPricedModel(this.mainRef()) : source !== 'fallback';
|
|
106
|
+
},
|
|
139
107
|
getWindowOverride: () => deps.getConfig().contextWindow,
|
|
140
108
|
});
|
|
141
109
|
this.compactor = new Compactor({
|
|
@@ -164,7 +132,14 @@ export class CompactionWiring {
|
|
|
164
132
|
// setter and `contextManager` is never reassigned; the LIVE switch is inside
|
|
165
133
|
// `shouldCompact`, not in the identity of this object.
|
|
166
134
|
this.port = {
|
|
167
|
-
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
|
+
},
|
|
168
143
|
compact: (ctx) => this.compactor.compact(ctx),
|
|
169
144
|
};
|
|
170
145
|
}
|
|
@@ -190,6 +165,7 @@ export class CompactionWiring {
|
|
|
190
165
|
}
|
|
191
166
|
dispose() {
|
|
192
167
|
this.compactor.abort();
|
|
168
|
+
this.compactor.clearPendingManual();
|
|
193
169
|
this.unsubscribe?.();
|
|
194
170
|
this.unsubscribe = null;
|
|
195
171
|
this.listeners.clear();
|
|
@@ -202,6 +178,8 @@ export class CompactionWiring {
|
|
|
202
178
|
}
|
|
203
179
|
setEnabled(enabled) {
|
|
204
180
|
this.enabled = enabled;
|
|
181
|
+
if (!enabled)
|
|
182
|
+
this.compactor.abort();
|
|
205
183
|
// Turning it back on is the user overriding guard 4, which is the only way
|
|
206
184
|
// out of a self-disable short of a restart.
|
|
207
185
|
if (enabled)
|
|
@@ -212,6 +190,8 @@ export class CompactionWiring {
|
|
|
212
190
|
onConfigChanged(enabled) {
|
|
213
191
|
if (enabled !== undefined && enabled !== this.enabled) {
|
|
214
192
|
this.enabled = enabled;
|
|
193
|
+
if (!enabled)
|
|
194
|
+
this.compactor.abort();
|
|
215
195
|
if (enabled)
|
|
216
196
|
this.compactor.clearSelfDisable();
|
|
217
197
|
}
|
|
@@ -241,8 +221,19 @@ export class CompactionWiring {
|
|
|
241
221
|
* for such a session. A deep reset is idempotent, so both firing costs
|
|
242
222
|
* nothing; deleting the controller's copy "to de-duplicate" reintroduces P0-2.
|
|
243
223
|
*/
|
|
244
|
-
onHistoryReplaced() {
|
|
245
|
-
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.');
|
|
246
237
|
}
|
|
247
238
|
/**
|
|
248
239
|
* The per-child manager factory, or `null` when children get none
|
|
@@ -266,7 +257,7 @@ export class CompactionWiring {
|
|
|
266
257
|
// SINK (AC-H12). It is real money on the summarizer's price table, and a
|
|
267
258
|
// background repair inside a background worker is exactly the spend a
|
|
268
259
|
// user would otherwise never see.
|
|
269
|
-
onUsage: (usage) => this.emit({ type: 'usage', usage }),
|
|
260
|
+
onUsage: (usage, billing) => this.emit({ type: 'usage', usage, ...billing }),
|
|
270
261
|
});
|
|
271
262
|
}
|
|
272
263
|
// -----------------------------------------------------------------------
|
|
@@ -281,28 +272,13 @@ export class CompactionWiring {
|
|
|
281
272
|
clearPendingManual() {
|
|
282
273
|
this.compactor.clearPendingManual();
|
|
283
274
|
}
|
|
284
|
-
/**
|
|
285
|
-
* The IDLE `/compact` path (§4.4 / D-25).
|
|
286
|
-
*
|
|
287
|
-
* IT VALIDATES BEFORE IT SPLICES, AND THAT IS NOT OPTIONAL (P1-7). When the
|
|
288
|
-
* agent is idle there is no loop, so §3.3 step 5 does not run — and the only
|
|
289
|
-
* way to replace the history is `Agent.replaceMessages`, which validates
|
|
290
|
-
* nothing. D-4 declares that gate unbypassable and R-2 calls an invalid splice
|
|
291
|
-
* the worst possible outcome, so this path mirrors the engine's steps 3-5
|
|
292
|
-
* exactly, INCLUDING the failure vocabulary: the card, the log and
|
|
293
|
-
* `/compact status` cannot tell the two paths apart.
|
|
294
|
-
*
|
|
295
|
-
* No watchdog handling is needed — `runLoopWithLifecycle`'s `finally` already
|
|
296
|
-
* called `watchdog.stop()`, so nothing is armed while idle.
|
|
297
|
-
*
|
|
298
|
-
* ITS CALLER MUST ADOPT `messages` — THE ARCHIVE IS ALREADY WRITTEN BY THE
|
|
299
|
-
* TIME THIS RETURNS (hardening RV-7). This path settles `applied: true` here,
|
|
300
|
-
* one statement before the caller splices, so the "never archive a live
|
|
301
|
-
* history" rule holds only because that caller applies unconditionally. That is
|
|
302
|
-
* a fact about one call site rather than an invariant, which is why it is
|
|
303
|
-
* written down where it can be checked.
|
|
304
|
-
*/
|
|
275
|
+
/** Prepare, validate and synchronously adopt before publishing a successful verdict. */
|
|
305
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();
|
|
306
282
|
this.compactor.queueManual(args.instructions);
|
|
307
283
|
// BOTH SIDES FROM CORE'S OWN ESTIMATOR, exactly as the in-loop path gets them
|
|
308
284
|
// from `compaction_end` (§5.1). There is no engine event here to supply them,
|
|
@@ -339,6 +315,19 @@ export class CompactionWiring {
|
|
|
339
315
|
this.settlePending({ applied: false, reason, tokensBefore, tokensAfter: tokensBefore });
|
|
340
316
|
return { ok: false, reason };
|
|
341
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
|
+
}
|
|
342
331
|
this.settlePending({
|
|
343
332
|
applied: true,
|
|
344
333
|
tokensBefore,
|
|
@@ -384,6 +373,7 @@ export class CompactionWiring {
|
|
|
384
373
|
...(totals.declined > 0 ? { declined: totals.declined } : {}),
|
|
385
374
|
tokensReclaimed: totals.tokensReclaimed,
|
|
386
375
|
usage: totals.usage,
|
|
376
|
+
costUsd: totals.costUsd,
|
|
387
377
|
pricingUnknown: totals.pricingUnknown,
|
|
388
378
|
inFlight: totals.inFlight,
|
|
389
379
|
selfDisabled: totals.selfDisabled,
|
|
@@ -457,9 +447,13 @@ export class CompactionWiring {
|
|
|
457
447
|
onAgentEvent(event) {
|
|
458
448
|
if (event.type === 'agent_start') {
|
|
459
449
|
this.compactor.onRunStart();
|
|
450
|
+
this.overflowNotified = false;
|
|
460
451
|
return;
|
|
461
452
|
}
|
|
453
|
+
if (event.type === 'agent_end')
|
|
454
|
+
this.compactor.onRunEnd();
|
|
462
455
|
if (event.type === 'turn_end') {
|
|
456
|
+
this.meter.onTurnEnd(event.usage);
|
|
463
457
|
// THE MEASUREMENT IS THE METER'S (context-usage-gauge-accuracy §3.3.4). It
|
|
464
458
|
// subscribes to `turn_end` on the same stream and recomputes the offset,
|
|
465
459
|
// the prefix length and the pressure there; the two calls that used to
|
|
@@ -498,6 +492,7 @@ export class CompactionWiring {
|
|
|
498
492
|
// engine cannot report `true` without a `replace` outcome, which means the
|
|
499
493
|
// compactor's own record already exists — so its zeroed counts are never
|
|
500
494
|
// rendered.
|
|
495
|
+
this.compactor.settleOperation(verdict);
|
|
501
496
|
const record = this.pending ?? this.skeletonRecord();
|
|
502
497
|
this.pending = null;
|
|
503
498
|
this.open = null;
|
|
@@ -530,7 +525,6 @@ export class CompactionWiring {
|
|
|
530
525
|
mode: verdict.applied ? record.mode : 'none',
|
|
531
526
|
};
|
|
532
527
|
if (verdict.applied) {
|
|
533
|
-
this.compactor.recordApplied(settled.tokensBefore, settled.tokensAfter);
|
|
534
528
|
// SITE 1 OF THE FOUR INVALIDATION SITES (hardening §3.2.3), and THE LOAD-
|
|
535
529
|
// BEARING LINE OF THE P0-1 FIX (context-usage-gauge-accuracy I-9).
|
|
536
530
|
//
|
|
@@ -562,9 +556,8 @@ export class CompactionWiring {
|
|
|
562
556
|
* archive a history that is still live" - and the two paths reach that verdict
|
|
563
557
|
* at different moments relative to the splice. In loop, the ENGINE decides
|
|
564
558
|
* after `validateHistory`, so this runs AFTER adoption. On the manual path the
|
|
565
|
-
* WIRING
|
|
566
|
-
*
|
|
567
|
-
* 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.
|
|
568
561
|
*
|
|
569
562
|
* BEST-EFFORT AND SYNCHRONOUS-BUT-GUARDED. `writeArchive` never throws and
|
|
570
563
|
* logs its own failure; a failed archive never affects the compaction, and it
|
package/dist/config/load.d.ts
CHANGED
|
@@ -241,7 +241,9 @@ export interface CliFlags {
|
|
|
241
241
|
* Resolve the effective config by merging all layers, then derive the runtime
|
|
242
242
|
* (non-persisted) fields and enforce the timeout invariant.
|
|
243
243
|
*/
|
|
244
|
-
export declare function loadConfig(flags?: CliFlags
|
|
244
|
+
export declare function loadConfig(flags?: CliFlags, options?: {
|
|
245
|
+
interactive?: boolean;
|
|
246
|
+
}): CliConfig;
|
|
245
247
|
/**
|
|
246
248
|
* Build the `getApiKey(providerId)` resolver the core `Agent` consumes.
|
|
247
249
|
* The one-shot `--api-key` override wins, but only for the active provider.
|
package/dist/config/load.js
CHANGED
|
@@ -16,7 +16,7 @@ import { setEntryRetain } from '../agent/entry-limits.js';
|
|
|
16
16
|
import { readSubmitCount } from './ui-state.js';
|
|
17
17
|
import { getLogger } from '../logging/logger.js';
|
|
18
18
|
import { registerSecret, registerSecretsFrom } from '../logging/secret-registry.js';
|
|
19
|
-
import { detectCapabilities } from '../ui/capabilities.js';
|
|
19
|
+
import { detectCapabilities, resolveTuiCapabilities } from '../ui/capabilities.js';
|
|
20
20
|
/** Interpret a boolean-ish env value (`1/true/on/yes`) as `true`. */
|
|
21
21
|
function envFlagTrue(value) {
|
|
22
22
|
if (!value)
|
|
@@ -436,7 +436,7 @@ function resolveContextWindow(env, file) {
|
|
|
436
436
|
* Resolve the effective config by merging all layers, then derive the runtime
|
|
437
437
|
* (non-persisted) fields and enforce the timeout invariant.
|
|
438
438
|
*/
|
|
439
|
-
export function loadConfig(flags = {}) {
|
|
439
|
+
export function loadConfig(flags = {}, options = {}) {
|
|
440
440
|
const cwd = flags.cwd ? flags.cwd : process.cwd();
|
|
441
441
|
// `.env` must be loaded before reading env overrides.
|
|
442
442
|
loadDotenv(cwd);
|
|
@@ -519,13 +519,17 @@ export function loadConfig(flags = {}) {
|
|
|
519
519
|
// registering twice costs a `Set.add`, missing once costs a credential.
|
|
520
520
|
registerSecretsFrom(apiKeys);
|
|
521
521
|
registerSecret(flags.apiKey);
|
|
522
|
-
const
|
|
522
|
+
const caps = options.interactive
|
|
523
|
+
? resolveTuiCapabilities(process.env, flags.color)
|
|
524
|
+
: detectCapabilities(process.env, process.stdout);
|
|
525
|
+
const color = options.interactive
|
|
526
|
+
? caps.colorLevel !== 0
|
|
527
|
+
: flags.color !== undefined ? flags.color : !process.env.NO_COLOR;
|
|
523
528
|
// `--no-color` implies calmer chrome; env / file may also opt in explicitly.
|
|
524
529
|
const reducedMotion = envFlagTrue(process.env.ARAGON_REDUCED_MOTION) ||
|
|
525
530
|
(file.reducedMotion ?? DEFAULT_CONFIG.reducedMotion) ||
|
|
526
531
|
!color;
|
|
527
532
|
// Detect terminal capabilities once; force monochrome when color is disabled.
|
|
528
|
-
const caps = detectCapabilities(process.env, process.stdout);
|
|
529
533
|
const colorLevel = color ? caps.colorLevel : 0;
|
|
530
534
|
// No flag and no env var: a privacy switch you set once, not a per-run knob.
|
|
531
535
|
const historyEnabled = file.historyEnabled ?? DEFAULT_CONFIG.historyEnabled;
|
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
|
|
@@ -1097,9 +1097,9 @@ export interface CliConfig {
|
|
|
1097
1097
|
cwd: string;
|
|
1098
1098
|
/** Whether ANSI color is enabled. */
|
|
1099
1099
|
color: boolean;
|
|
1100
|
-
/**
|
|
1100
|
+
/** Resolved color depth: TUI policy or non-interactive terminal detection. */
|
|
1101
1101
|
colorLevel?: 0 | 1 | 2 | 3;
|
|
1102
|
-
/**
|
|
1102
|
+
/** TUI always enables Unicode; non-interactive output uses detection. */
|
|
1103
1103
|
unicode?: boolean;
|
|
1104
1104
|
/** One-shot `--api-key` override; applies to the active provider only. */
|
|
1105
1105
|
apiKeyOverride?: string;
|
package/dist/config/schema.js
CHANGED
package/dist/exec/events.d.ts
CHANGED
|
@@ -274,6 +274,14 @@ export interface ExecFastTierEvent {
|
|
|
274
274
|
* it. What a wrapper does need is the SHAPE of what happened, which is here.
|
|
275
275
|
*/
|
|
276
276
|
export interface ExecCompactionEvent {
|
|
277
|
+
decision?: {
|
|
278
|
+
occupied: number;
|
|
279
|
+
contextWindow: number;
|
|
280
|
+
threshold: number;
|
|
281
|
+
source: 'usage' | 'estimate';
|
|
282
|
+
deltaTokens: number;
|
|
283
|
+
};
|
|
284
|
+
memoryVersion?: 2;
|
|
277
285
|
type: 'compaction';
|
|
278
286
|
sessionId: string;
|
|
279
287
|
turn: number;
|
package/dist/exec/index.js
CHANGED
|
@@ -111,7 +111,7 @@ async function execute(input) {
|
|
|
111
111
|
now,
|
|
112
112
|
});
|
|
113
113
|
if (plan.saved) {
|
|
114
|
-
controller.replaceMessages(plan.saved.messages);
|
|
114
|
+
controller.replaceMessages(plan.saved.messages, plan.saved.compactionIdentity);
|
|
115
115
|
controller.restoreTodos(plan.saved.todos);
|
|
116
116
|
}
|
|
117
117
|
const config = controller.getConfig();
|
|
@@ -187,7 +187,7 @@ async function execute(input) {
|
|
|
187
187
|
outputTokens: stats.usage.outputTokens,
|
|
188
188
|
totalTokens: stats.usage.inputTokens + stats.usage.outputTokens,
|
|
189
189
|
},
|
|
190
|
-
cost: buildCost(controller, stats.usage),
|
|
190
|
+
cost: buildCost(controller, stats.usage, stats.compactionBilling),
|
|
191
191
|
model: { provider: config.provider, id: config.model },
|
|
192
192
|
todos: todo ? { total: todo.total, done: todo.doneCount } : null,
|
|
193
193
|
error: stats.errorCode && stats.errorMessage
|
|
@@ -253,17 +253,24 @@ async function drive(input, controller, runner) {
|
|
|
253
253
|
function withPermissionMode(flags, mode) {
|
|
254
254
|
return mode === 'plan' ? { ...flags, plan: true } : flags;
|
|
255
255
|
}
|
|
256
|
-
function buildCost(controller, usage) {
|
|
256
|
+
function buildCost(controller, usage, compaction) {
|
|
257
257
|
const config = controller.getConfig();
|
|
258
|
-
const known = controller.isPricedModel({
|
|
258
|
+
const known = !compaction?.pricingUnknown && controller.isPricedModel({
|
|
259
259
|
providerId: config.provider,
|
|
260
260
|
modelId: config.model,
|
|
261
261
|
});
|
|
262
|
+
const mainUsage = {
|
|
263
|
+
inputTokens: usage.inputTokens - (compaction?.usage.inputTokens ?? 0),
|
|
264
|
+
outputTokens: usage.outputTokens - (compaction?.usage.outputTokens ?? 0),
|
|
265
|
+
cacheReadTokens: (usage.cacheReadTokens ?? 0) - (compaction?.usage.cacheReadTokens ?? 0),
|
|
266
|
+
cacheWriteTokens: (usage.cacheWriteTokens ?? 0) - (compaction?.usage.cacheWriteTokens ?? 0),
|
|
267
|
+
};
|
|
262
268
|
// `amount` is `0` when the price is unknown, mirroring the fast tier's rule
|
|
263
269
|
// that a feature which looks free while it is spending money is worse than one
|
|
264
270
|
// that admits it does not know.
|
|
265
271
|
return {
|
|
266
|
-
amount: known ? computeCost(
|
|
272
|
+
amount: known ? computeCost(mainUsage, controller.getModelInfo().cost) +
|
|
273
|
+
(compaction?.costUsd ?? 0) : 0,
|
|
267
274
|
currency: 'USD',
|
|
268
275
|
known,
|
|
269
276
|
};
|
|
@@ -460,7 +467,7 @@ function persist(plan, controller, turns, usage, cli, at) {
|
|
|
460
467
|
modelId: config.model,
|
|
461
468
|
...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),
|
|
462
469
|
},
|
|
463
|
-
messages: controller.getMessages(),
|
|
470
|
+
...(controller.getSessionSnapshot?.() ?? { messages: controller.getMessages() }),
|
|
464
471
|
entries: [],
|
|
465
472
|
todos: controller.getTodoSnapshot?.()?.items ?? [],
|
|
466
473
|
},
|
package/dist/exec/runner.d.ts
CHANGED
|
@@ -72,7 +72,13 @@ export interface ExecRunnerOptions {
|
|
|
72
72
|
/** Turns already spent by earlier invocations of a resumed session. */
|
|
73
73
|
priorTurns?: number;
|
|
74
74
|
}
|
|
75
|
+
export interface ExecCompactionBilling {
|
|
76
|
+
usage: TokenUsage;
|
|
77
|
+
costUsd: number;
|
|
78
|
+
pricingUnknown: boolean;
|
|
79
|
+
}
|
|
75
80
|
export interface ExecRunStats {
|
|
81
|
+
compactionBilling?: ExecCompactionBilling;
|
|
76
82
|
turns: number;
|
|
77
83
|
usage: TokenUsage;
|
|
78
84
|
lastAssistantText: string;
|
|
@@ -93,6 +99,7 @@ export declare class ExecRunner {
|
|
|
93
99
|
private unsubscribers;
|
|
94
100
|
private turns;
|
|
95
101
|
private readonly usage;
|
|
102
|
+
private compactionBilling;
|
|
96
103
|
private lastAssistantText;
|
|
97
104
|
private errored;
|
|
98
105
|
private sawTurnEnd;
|
|
@@ -143,6 +150,7 @@ export declare class ExecRunner {
|
|
|
143
150
|
* the two faces cannot drift about what "unfinished" means.
|
|
144
151
|
*/
|
|
145
152
|
run(source: ExecPromptSource): Promise<void>;
|
|
153
|
+
private onCompactionUsage;
|
|
146
154
|
private requestSeq;
|
|
147
155
|
private requestHadError;
|
|
148
156
|
private isRequestCancelled;
|
package/dist/exec/runner.js
CHANGED
|
@@ -46,6 +46,7 @@ export class ExecRunner {
|
|
|
46
46
|
unsubscribers = [];
|
|
47
47
|
turns = 0;
|
|
48
48
|
usage = { inputTokens: 0, outputTokens: 0 };
|
|
49
|
+
compactionBilling;
|
|
49
50
|
lastAssistantText = '';
|
|
50
51
|
errored = false;
|
|
51
52
|
sawTurnEnd = false;
|
|
@@ -175,8 +176,7 @@ export class ExecRunner {
|
|
|
175
176
|
// wrapper gets.
|
|
176
177
|
const compaction = controller.subscribeCompaction?.((event) => {
|
|
177
178
|
if (event.type === 'usage') {
|
|
178
|
-
this.
|
|
179
|
-
this.usage.outputTokens += event.usage.outputTokens;
|
|
179
|
+
this.onCompactionUsage(event);
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
182
|
if (event.type === 'compaction_start') {
|
|
@@ -208,6 +208,8 @@ export class ExecRunner {
|
|
|
208
208
|
tokensBefore: r.tokensBefore,
|
|
209
209
|
tokensAfter: r.tokensAfter,
|
|
210
210
|
...(r.tailRelief ? { tailRelief: r.tailRelief } : {}),
|
|
211
|
+
...(r.decision ? { decision: r.decision } : {}),
|
|
212
|
+
...(r.memoryVersion ? { memoryVersion: r.memoryVersion } : {}),
|
|
211
213
|
durationMs: r.durationMs,
|
|
212
214
|
});
|
|
213
215
|
});
|
|
@@ -246,6 +248,9 @@ export class ExecRunner {
|
|
|
246
248
|
return {
|
|
247
249
|
turns: this.turns,
|
|
248
250
|
usage: { ...this.usage },
|
|
251
|
+
...(this.compactionBilling ? { compactionBilling: {
|
|
252
|
+
...this.compactionBilling, usage: { ...this.compactionBilling.usage },
|
|
253
|
+
} } : {}),
|
|
249
254
|
lastAssistantText: this.lastAssistantText,
|
|
250
255
|
errored: this.errored,
|
|
251
256
|
stopReason: this.stopReason,
|
|
@@ -322,6 +327,25 @@ export class ExecRunner {
|
|
|
322
327
|
}
|
|
323
328
|
}
|
|
324
329
|
}
|
|
330
|
+
onCompactionUsage(event) {
|
|
331
|
+
const usage = event.usage;
|
|
332
|
+
this.usage.inputTokens += usage.inputTokens;
|
|
333
|
+
this.usage.outputTokens += usage.outputTokens;
|
|
334
|
+
for (const key of ['cacheReadTokens', 'cacheWriteTokens']) {
|
|
335
|
+
if (usage[key] !== undefined)
|
|
336
|
+
this.usage[key] = (this.usage[key] ?? 0) + usage[key];
|
|
337
|
+
}
|
|
338
|
+
if (event.costUsd === undefined && event.pricingUnknown === undefined)
|
|
339
|
+
return;
|
|
340
|
+
const billing = this.compactionBilling ??= { costUsd: 0, pricingUnknown: false,
|
|
341
|
+
usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 } };
|
|
342
|
+
billing.costUsd += event.costUsd ?? 0;
|
|
343
|
+
billing.pricingUnknown ||= event.pricingUnknown === true;
|
|
344
|
+
billing.usage.inputTokens += usage.inputTokens;
|
|
345
|
+
billing.usage.outputTokens += usage.outputTokens;
|
|
346
|
+
billing.usage.cacheReadTokens += usage.cacheReadTokens ?? 0;
|
|
347
|
+
billing.usage.cacheWriteTokens += usage.cacheWriteTokens ?? 0;
|
|
348
|
+
}
|
|
325
349
|
requestSeq = 0;
|
|
326
350
|
requestHadError = false;
|
|
327
351
|
isRequestCancelled() {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { Message, ModelRef } from '@aragon-agent/core';
|
|
9
9
|
import type { Entry } from '../agent/reducer.js';
|
|
10
10
|
import type { TodoItem } from '../todo/types.js';
|
|
11
|
+
import { type CompactionIdentity } from '../compaction/memory-identity.js';
|
|
11
12
|
/**
|
|
12
13
|
* Bookkeeping `aragon exec` attaches to the sessions it owns
|
|
13
14
|
* (cli-integration-surface §5.2).
|
|
@@ -41,6 +42,7 @@ export interface SavedSession {
|
|
|
41
42
|
savedAt: number;
|
|
42
43
|
model?: ModelRef;
|
|
43
44
|
messages: Message[];
|
|
45
|
+
compactionIdentity?: CompactionIdentity;
|
|
44
46
|
entries: Entry[];
|
|
45
47
|
/**
|
|
46
48
|
* The live todo list at save time (todo-plan-execution §3.13).
|
|
@@ -79,6 +81,7 @@ export declare function resolveSessionPath(nameOrPath: string | undefined, cwd:
|
|
|
79
81
|
export declare function saveSession(filePath: string, data: {
|
|
80
82
|
model: ModelRef;
|
|
81
83
|
messages: Message[];
|
|
84
|
+
compactionIdentity?: CompactionIdentity;
|
|
82
85
|
entries: Entry[];
|
|
83
86
|
todos: TodoItem[];
|
|
84
87
|
meta?: SessionMeta;
|
package/dist/session/persist.js
CHANGED
|
@@ -9,6 +9,8 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
9
9
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
10
10
|
import { getSessionsDir } from '../config/store.js';
|
|
11
11
|
import { validateSession } from './validate-session.js';
|
|
12
|
+
import { validateCompactionIdentity, verifyCompactionIdentity, } from '../compaction/memory-identity.js';
|
|
13
|
+
import { getLogger } from '../logging/logger.js';
|
|
12
14
|
const SESSION_VERSION = 1;
|
|
13
15
|
/** Resolve a `/save` target: absolute/relative path, or a name in the data dir. */
|
|
14
16
|
export function resolveSessionPath(nameOrPath, cwd) {
|
|
@@ -92,6 +94,12 @@ function stripServiceTail(entries) {
|
|
|
92
94
|
* payload with no such key and a file byte-identical to today's.
|
|
93
95
|
*/
|
|
94
96
|
export function saveSession(filePath, data) {
|
|
97
|
+
if (data.compactionIdentity) {
|
|
98
|
+
validateCompactionIdentity(data.compactionIdentity);
|
|
99
|
+
if (!verifyCompactionIdentity(data.messages, data.compactionIdentity)) {
|
|
100
|
+
getLogger().debug('agent', 'invalid_compaction_session_identity');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
95
103
|
const dir = dirname(filePath);
|
|
96
104
|
if (!existsSync(dir))
|
|
97
105
|
mkdirSync(dir, { recursive: true });
|
|
@@ -100,6 +108,7 @@ export function saveSession(filePath, data) {
|
|
|
100
108
|
savedAt: Date.now(),
|
|
101
109
|
model: data.model,
|
|
102
110
|
messages: data.messages,
|
|
111
|
+
...(data.compactionIdentity ? { compactionIdentity: data.compactionIdentity } : {}),
|
|
103
112
|
entries: stripServiceTail(stripLiveToolOutput(data.entries)),
|
|
104
113
|
todos: data.todos,
|
|
105
114
|
...(data.meta ? { meta: data.meta } : {}),
|
package/dist/session/store.js
CHANGED
|
@@ -159,6 +159,8 @@ export function writeSession(input) {
|
|
|
159
159
|
saveSession(path, {
|
|
160
160
|
model: input.session.model,
|
|
161
161
|
messages: input.session.messages,
|
|
162
|
+
...(input.session.compactionIdentity
|
|
163
|
+
? { compactionIdentity: input.session.compactionIdentity } : {}),
|
|
162
164
|
entries: input.session.entries,
|
|
163
165
|
todos: input.session.todos ?? [],
|
|
164
166
|
meta: input.meta,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { isAdapterProvider } from '../config/schema.js';
|
|
2
2
|
import { normalizeTodos } from '../todo/normalize.js';
|
|
3
3
|
import { TODO_LIMITS } from '../todo/limits.js';
|
|
4
|
+
import { validateCompactionIdentity } from '../compaction/memory-identity.js';
|
|
5
|
+
import { MemoryError } from '../compaction/memory.js';
|
|
4
6
|
function fail(path, expected) {
|
|
5
7
|
throw new Error(`Invalid session file: ${path} must be ${expected}.`);
|
|
6
8
|
}
|
|
@@ -26,6 +28,25 @@ const boolean = (value, path) => {
|
|
|
26
28
|
if (typeof value !== 'boolean')
|
|
27
29
|
fail(path, 'a boolean');
|
|
28
30
|
};
|
|
31
|
+
const nonnegative = (value, path) => {
|
|
32
|
+
number(value, path);
|
|
33
|
+
if (value < 0)
|
|
34
|
+
fail(path, 'non-negative');
|
|
35
|
+
};
|
|
36
|
+
const positive = (value, path) => {
|
|
37
|
+
number(value, path);
|
|
38
|
+
if (value <= 0)
|
|
39
|
+
fail(path, 'positive');
|
|
40
|
+
};
|
|
41
|
+
const threshold = (value, path) => {
|
|
42
|
+
number(value, path);
|
|
43
|
+
if (value < 0.5 || value > 0.95)
|
|
44
|
+
fail(path, 'between 0.5 and 0.95');
|
|
45
|
+
};
|
|
46
|
+
const memoryVersion = (value, path) => {
|
|
47
|
+
if (value !== 2)
|
|
48
|
+
fail(path, '2');
|
|
49
|
+
};
|
|
29
50
|
const object = (value, path) => { record(value, path); };
|
|
30
51
|
function optional(check) {
|
|
31
52
|
return (value, path) => { if (value !== undefined)
|
|
@@ -149,6 +170,9 @@ const ENTRY_FIELDS = {
|
|
|
149
170
|
text: optional(string), detail: optional(string), turn: number,
|
|
150
171
|
durationMs: optional(number), live: boolean },
|
|
151
172
|
compaction: {
|
|
173
|
+
decision: optional(shape({ occupied: nonnegative, contextWindow: positive,
|
|
174
|
+
threshold, source: enumeration('usage', 'estimate'), deltaTokens: nonnegative })),
|
|
175
|
+
memoryVersion: optional(memoryVersion),
|
|
152
176
|
index: number, trigger: enumeration('pressure', 'overflow', 'manual'),
|
|
153
177
|
mode: enumeration('summarized', 'truncated', 'relieved', 'none'), applied: boolean,
|
|
154
178
|
reason: optional(string), messagesBefore: number, messagesAfter: number,
|
|
@@ -198,6 +222,16 @@ export function validateSession(value) {
|
|
|
198
222
|
validateEntries(source.entries);
|
|
199
223
|
if (source.model !== undefined)
|
|
200
224
|
validateModel(source.model);
|
|
225
|
+
if (source.compactionIdentity !== undefined) {
|
|
226
|
+
try {
|
|
227
|
+
validateCompactionIdentity(source.compactionIdentity);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
if (!(error instanceof MemoryError))
|
|
231
|
+
throw error;
|
|
232
|
+
fail('compactionIdentity', 'a valid adopted-memory identity');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
201
235
|
// Missing fields remain missing in old files, including exec metadata.
|
|
202
236
|
const prepared = { ...source };
|
|
203
237
|
if (source.todos !== undefined) {
|
package/dist/team/subagent.js
CHANGED
|
@@ -331,6 +331,14 @@ export function createSubagent(spec, deps, hooks) {
|
|
|
331
331
|
}
|
|
332
332
|
break;
|
|
333
333
|
}
|
|
334
|
+
case 'compaction_end':
|
|
335
|
+
contextManager?.onCompactionEnd({
|
|
336
|
+
applied: event.applied,
|
|
337
|
+
...(event.reason !== undefined ? { reason: event.reason } : {}),
|
|
338
|
+
tokensBefore: event.estimatedTokensBefore,
|
|
339
|
+
tokensAfter: event.estimatedTokensAfter,
|
|
340
|
+
});
|
|
341
|
+
break;
|
|
334
342
|
case 'tool_execution_start':
|
|
335
343
|
run.phase = event.toolName === 'team_wait' ? 'waiting' : 'tool';
|
|
336
344
|
run.lastTool = event.toolName;
|