@aragon-agent/cli 0.6.14 → 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 +14 -0
- package/README.md +250 -233
- package/dist/agent/controller.d.ts +27 -1
- package/dist/agent/controller.js +182 -45
- package/dist/agent/plan-prompt.d.ts +1 -1
- package/dist/agent/plan-prompt.js +4 -3
- package/dist/agent/project-guidance-prompt.d.ts +4 -0
- package/dist/agent/project-guidance-prompt.js +104 -0
- package/dist/agent/reducer.d.ts +2 -0
- package/dist/agent/reducer.js +4 -0
- package/dist/agent/system-prompt.js +4 -0
- package/dist/commands/builtins.js +2 -2
- package/dist/compaction/child.d.ts +9 -1
- package/dist/compaction/child.js +39 -13
- package/dist/compaction/command.js +20 -10
- package/dist/compaction/compactor.d.ts +33 -204
- package/dist/compaction/compactor.js +311 -726
- package/dist/compaction/digest.d.ts +15 -51
- package/dist/compaction/digest.js +113 -148
- package/dist/compaction/limits.d.ts +19 -142
- package/dist/compaction/limits.js +20 -147
- package/dist/compaction/memory-identity.d.ts +16 -0
- package/dist/compaction/memory-identity.js +63 -0
- package/dist/compaction/memory-input.d.ts +25 -0
- package/dist/compaction/memory-input.js +106 -0
- package/dist/compaction/memory-render.d.ts +4 -0
- package/dist/compaction/memory-render.js +47 -0
- package/dist/compaction/memory-validation.d.ts +21 -0
- package/dist/compaction/memory-validation.js +268 -0
- package/dist/compaction/memory.d.ts +79 -0
- package/dist/compaction/memory.js +118 -0
- package/dist/compaction/meter.d.ts +14 -10
- package/dist/compaction/meter.js +53 -19
- package/dist/compaction/operation.d.ts +18 -0
- package/dist/compaction/operation.js +68 -0
- package/dist/compaction/pressure.d.ts +5 -20
- package/dist/compaction/pressure.js +13 -23
- package/dist/compaction/summarize-call.d.ts +3 -16
- package/dist/compaction/summarize-call.js +20 -20
- package/dist/compaction/summary-prompt.d.ts +29 -66
- package/dist/compaction/summary-prompt.js +132 -178
- package/dist/compaction/types.d.ts +21 -3
- package/dist/compaction/wiring.d.ts +10 -25
- package/dist/compaction/wiring.js +56 -66
- package/dist/config/schema.d.ts +1 -1
- 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 +218 -116
- package/dist/ui/BottomStatusRow.d.ts +5 -10
- package/dist/ui/BottomStatusRow.js +7 -11
- package/dist/ui/Composer.d.ts +4 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +6 -2
- package/dist/ui/Header.js +7 -6
- package/dist/ui/PromptInput.d.ts +3 -1
- package/dist/ui/PromptInput.js +32 -41
- package/dist/ui/StatusBar.d.ts +6 -0
- package/dist/ui/StatusBar.js +8 -7
- package/dist/ui/Transcript.js +1 -1
- package/dist/ui/entries/CompactionCard.d.ts +3 -1
- package/dist/ui/entries/CompactionCard.js +7 -5
- package/dist/ui/frame-differ.js +27 -0
- package/dist/ui/frame-observer.js +18 -5
- package/dist/ui/interaction-copy.d.ts +73 -73
- package/dist/ui/interaction-copy.js +30 -38
- package/dist/ui/interaction-hints.d.ts +8 -1
- package/dist/ui/interaction-hints.js +56 -69
- package/dist/ui/layout/AppShell.d.ts +6 -2
- package/dist/ui/layout/AppShell.js +2 -2
- package/dist/ui/layout/OverlayFrame.d.ts +2 -0
- package/dist/ui/layout/OverlayFrame.js +23 -14
- package/dist/ui/layout/ScrollViewport.d.ts +3 -1
- package/dist/ui/layout/ScrollViewport.js +10 -2
- package/dist/ui/layout/budget.d.ts +25 -26
- package/dist/ui/layout/budget.js +29 -22
- package/dist/ui/layout/document-layout.d.ts +2 -2
- package/dist/ui/layout/document-layout.js +2 -3
- package/dist/ui/layout/queue-layout.js +4 -4
- package/dist/ui/layout/status-detail-layout.d.ts +9 -0
- package/dist/ui/layout/status-detail-layout.js +56 -0
- package/dist/ui/layout/status-layout.d.ts +19 -2
- package/dist/ui/layout/status-layout.js +95 -84
- package/dist/ui/layout/wrap-rows.d.ts +2 -0
- package/dist/ui/layout/wrap-rows.js +21 -7
- package/dist/ui/overlays/ConfirmDialog.d.ts +2 -1
- package/dist/ui/overlays/ConfirmDialog.js +22 -3
- package/dist/ui/overlays/HelpOverlay.js +9 -2
- package/dist/ui/overlays/ModelPicker.d.ts +5 -8
- package/dist/ui/overlays/ModelPicker.js +44 -10
- package/dist/ui/overlays/ModelProfileEditor.js +2 -0
- package/dist/ui/overlays/ModelProfilePicker.d.ts +1 -0
- package/dist/ui/overlays/ModelProfilePicker.js +4 -2
- package/dist/ui/overlays/PlanReviewOverlay.d.ts +2 -1
- package/dist/ui/overlays/PlanReviewOverlay.js +7 -4
- package/dist/ui/overlays/QuestionOverlay.d.ts +2 -1
- package/dist/ui/overlays/QuestionOverlay.js +45 -32
- package/dist/ui/overlays/QueueOverlay.js +2 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +1 -0
- package/dist/ui/overlays/SettingsScreen.js +10 -3
- package/dist/ui/run-metrics.d.ts +23 -0
- package/dist/ui/run-metrics.js +27 -0
- package/dist/ui/status-feedback.d.ts +33 -0
- package/dist/ui/status-feedback.js +36 -0
- package/dist/ui/stdout-frame-writer.js +28 -0
- package/package.json +2 -2
|
@@ -1,40 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* `CompactionWiring` — the controller-side glue for context compaction
|
|
3
|
-
* (context-auto-compaction §3.2.1 / §5.2).
|
|
4
|
-
*
|
|
5
|
-
* ASCII ONLY: `src/compaction/**` is inside the glyph scanner's scope.
|
|
6
|
-
*
|
|
7
|
-
* CONSTRUCT BEFORE THE `Agent`, ATTACH AFTER IT (C-12 / D-19), and the split is
|
|
8
|
-
* the whole reason this class has an `attach` method at all:
|
|
9
|
-
*
|
|
10
|
-
* - The PORT has to be INSIDE the `Agent`'s constructor argument, because
|
|
11
|
-
* `AgentConfig.contextManager` has no setter and the loop reads it from the
|
|
12
|
-
* loop context.
|
|
13
|
-
* - The WIRING has to SUBSCRIBE to the `Agent`, because `compaction_end` is
|
|
14
|
-
* where the engine's `applied` verdict arrives and `agent_start` is what
|
|
15
|
-
* resets the per-run cap.
|
|
16
|
-
*
|
|
17
|
-
* `AgentController` cannot do both in one step: `new Agent({...})` runs before
|
|
18
|
-
* every optional subsystem, precisely because `FastWiring`'s constructor
|
|
19
|
-
* subscribes. So THIS CONSTRUCTOR MUST NOT SUBSCRIBE. A field assigned after the
|
|
20
|
-
* `new Agent` call and spread into it conditionally produces a build where the
|
|
21
|
-
* feature is registered, enabled, and SILENTLY ABSENT - the exact shape of
|
|
22
|
-
* failure `controller.ts`'s `toolOutputs` comment documents.
|
|
23
|
-
*
|
|
24
|
-
* `manager()` returns a STABLE object for the controller's lifetime, whose two
|
|
25
|
-
* methods delegate to a compactor that reads config LIVE - which is what lets
|
|
26
|
-
* `/compact off`, `/compact threshold 0.8` and a settings-screen save take effect
|
|
27
|
-
* mid-session without touching the `Agent`.
|
|
28
|
-
*
|
|
29
|
-
* DECLINED COMPACTIONS PRODUCE NO CLI EVENTS (quiet-noop §3.6). A `pressure`
|
|
30
|
-
* compaction that changed nothing, called nothing and spent nothing never emits
|
|
31
|
-
* `compaction_start`, so `onCompactorEvent` never opens a card and `settlePending`
|
|
32
|
-
* finds neither a held record nor a skeleton to synthesize. CORE still emits its
|
|
33
|
-
* own pair for every attempt, which is why `settlePending` still runs - it just
|
|
34
|
-
* refreshes the snapshot and returns. `onCompactorEvent`'s `if (!this.open)`
|
|
35
|
-
* guard is now unreachable for declines and stays for the abort race it was
|
|
36
|
-
* written for.
|
|
37
|
-
*/
|
|
1
|
+
import { shouldCompactAt } from './pressure.js';
|
|
38
2
|
import { DEFAULT_RETRY_POLICY, estimatePromptTokens, initProviders, validateHistory, } from '@aragon-agent/core';
|
|
39
3
|
import { getLogger } from '../logging/logger.js';
|
|
40
4
|
import { mintRunId, writeArchive } from './archive.js';
|
|
@@ -126,6 +90,7 @@ export class CompactionWiring {
|
|
|
126
90
|
* key, not an identity.
|
|
127
91
|
*/
|
|
128
92
|
runId = mintRunId();
|
|
93
|
+
overflowNotified = false;
|
|
129
94
|
constructor(deps) {
|
|
130
95
|
this.deps = deps;
|
|
131
96
|
this.enabled = deps.getConfig().compaction.enabled;
|
|
@@ -167,7 +132,14 @@ export class CompactionWiring {
|
|
|
167
132
|
// setter and `contextManager` is never reassigned; the LIVE switch is inside
|
|
168
133
|
// `shouldCompact`, not in the identity of this object.
|
|
169
134
|
this.port = {
|
|
170
|
-
shouldCompact: (probe) =>
|
|
135
|
+
shouldCompact: (probe) => {
|
|
136
|
+
if (!this.enabled)
|
|
137
|
+
return false;
|
|
138
|
+
const allowed = this.compactor.shouldCompact(probe);
|
|
139
|
+
if (probe.trigger === 'overflow' && !allowed)
|
|
140
|
+
this.notifyOverflow();
|
|
141
|
+
return allowed;
|
|
142
|
+
},
|
|
171
143
|
compact: (ctx) => this.compactor.compact(ctx),
|
|
172
144
|
};
|
|
173
145
|
}
|
|
@@ -193,6 +165,7 @@ export class CompactionWiring {
|
|
|
193
165
|
}
|
|
194
166
|
dispose() {
|
|
195
167
|
this.compactor.abort();
|
|
168
|
+
this.compactor.clearPendingManual();
|
|
196
169
|
this.unsubscribe?.();
|
|
197
170
|
this.unsubscribe = null;
|
|
198
171
|
this.listeners.clear();
|
|
@@ -205,6 +178,8 @@ export class CompactionWiring {
|
|
|
205
178
|
}
|
|
206
179
|
setEnabled(enabled) {
|
|
207
180
|
this.enabled = enabled;
|
|
181
|
+
if (!enabled)
|
|
182
|
+
this.compactor.abort();
|
|
208
183
|
// Turning it back on is the user overriding guard 4, which is the only way
|
|
209
184
|
// out of a self-disable short of a restart.
|
|
210
185
|
if (enabled)
|
|
@@ -215,6 +190,8 @@ export class CompactionWiring {
|
|
|
215
190
|
onConfigChanged(enabled) {
|
|
216
191
|
if (enabled !== undefined && enabled !== this.enabled) {
|
|
217
192
|
this.enabled = enabled;
|
|
193
|
+
if (!enabled)
|
|
194
|
+
this.compactor.abort();
|
|
218
195
|
if (enabled)
|
|
219
196
|
this.compactor.clearSelfDisable();
|
|
220
197
|
}
|
|
@@ -244,8 +221,19 @@ export class CompactionWiring {
|
|
|
244
221
|
* for such a session. A deep reset is idempotent, so both firing costs
|
|
245
222
|
* nothing; deleting the controller's copy "to de-duplicate" reintroduces P0-2.
|
|
246
223
|
*/
|
|
247
|
-
onHistoryReplaced() {
|
|
248
|
-
this.
|
|
224
|
+
onHistoryReplaced(identity) {
|
|
225
|
+
this.compactor.onHistoryReplaced(identity);
|
|
226
|
+
}
|
|
227
|
+
getIdentity() { return this.compactor.getIdentity(); }
|
|
228
|
+
notifyOverflow() {
|
|
229
|
+
const p = this.meter.current();
|
|
230
|
+
if (this.overflowNotified || !Number.isFinite(p.occupied) || p.occupied < 0 ||
|
|
231
|
+
!Number.isFinite(p.contextWindow) || p.contextWindow <= 0 ||
|
|
232
|
+
shouldCompactAt(p, this.deps.getConfig().compaction.threshold))
|
|
233
|
+
return;
|
|
234
|
+
this.overflowNotified = true;
|
|
235
|
+
this.deps.notify('warn', 'Context limit reached before the configured compaction threshold. ' +
|
|
236
|
+
'Use /compact or correct contextWindow.');
|
|
249
237
|
}
|
|
250
238
|
/**
|
|
251
239
|
* The per-child manager factory, or `null` when children get none
|
|
@@ -269,7 +257,7 @@ export class CompactionWiring {
|
|
|
269
257
|
// SINK (AC-H12). It is real money on the summarizer's price table, and a
|
|
270
258
|
// background repair inside a background worker is exactly the spend a
|
|
271
259
|
// user would otherwise never see.
|
|
272
|
-
onUsage: (usage) => this.emit({ type: 'usage', usage }),
|
|
260
|
+
onUsage: (usage, billing) => this.emit({ type: 'usage', usage, ...billing }),
|
|
273
261
|
});
|
|
274
262
|
}
|
|
275
263
|
// -----------------------------------------------------------------------
|
|
@@ -284,28 +272,13 @@ export class CompactionWiring {
|
|
|
284
272
|
clearPendingManual() {
|
|
285
273
|
this.compactor.clearPendingManual();
|
|
286
274
|
}
|
|
287
|
-
/**
|
|
288
|
-
* The IDLE `/compact` path (§4.4 / D-25).
|
|
289
|
-
*
|
|
290
|
-
* IT VALIDATES BEFORE IT SPLICES, AND THAT IS NOT OPTIONAL (P1-7). When the
|
|
291
|
-
* agent is idle there is no loop, so §3.3 step 5 does not run — and the only
|
|
292
|
-
* way to replace the history is `Agent.replaceMessages`, which validates
|
|
293
|
-
* nothing. D-4 declares that gate unbypassable and R-2 calls an invalid splice
|
|
294
|
-
* the worst possible outcome, so this path mirrors the engine's steps 3-5
|
|
295
|
-
* exactly, INCLUDING the failure vocabulary: the card, the log and
|
|
296
|
-
* `/compact status` cannot tell the two paths apart.
|
|
297
|
-
*
|
|
298
|
-
* No watchdog handling is needed — `runLoopWithLifecycle`'s `finally` already
|
|
299
|
-
* called `watchdog.stop()`, so nothing is armed while idle.
|
|
300
|
-
*
|
|
301
|
-
* ITS CALLER MUST ADOPT `messages` — THE ARCHIVE IS ALREADY WRITTEN BY THE
|
|
302
|
-
* TIME THIS RETURNS (hardening RV-7). This path settles `applied: true` here,
|
|
303
|
-
* one statement before the caller splices, so the "never archive a live
|
|
304
|
-
* history" rule holds only because that caller applies unconditionally. That is
|
|
305
|
-
* a fact about one call site rather than an invariant, which is why it is
|
|
306
|
-
* written down where it can be checked.
|
|
307
|
-
*/
|
|
275
|
+
/** Prepare, validate and synchronously adopt before publishing a successful verdict. */
|
|
308
276
|
async compactNow(args) {
|
|
277
|
+
if (!this.enabled)
|
|
278
|
+
return { ok: false, reason: 'disabled' };
|
|
279
|
+
if (this.compactor.sessionTotals().inFlight)
|
|
280
|
+
return { ok: false, reason: 'already_running' };
|
|
281
|
+
this.compactor.onRunStart();
|
|
309
282
|
this.compactor.queueManual(args.instructions);
|
|
310
283
|
// BOTH SIDES FROM CORE'S OWN ESTIMATOR, exactly as the in-loop path gets them
|
|
311
284
|
// from `compaction_end` (§5.1). There is no engine event here to supply them,
|
|
@@ -342,6 +315,19 @@ export class CompactionWiring {
|
|
|
342
315
|
this.settlePending({ applied: false, reason, tokensBefore, tokensAfter: tokensBefore });
|
|
343
316
|
return { ok: false, reason };
|
|
344
317
|
}
|
|
318
|
+
if (args.signal.aborted || args.isCurrent?.() === false || !args.adopt) {
|
|
319
|
+
const reason = args.signal.aborted ? 'aborted' : 'stale_history';
|
|
320
|
+
this.settlePending({ applied: false, reason, tokensBefore, tokensAfter: tokensBefore });
|
|
321
|
+
return { ok: false, reason };
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
args.adopt(outcome.messages);
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
const reason = `adoption_failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
328
|
+
this.settlePending({ applied: false, reason, tokensBefore, tokensAfter: tokensBefore });
|
|
329
|
+
return { ok: false, reason };
|
|
330
|
+
}
|
|
345
331
|
this.settlePending({
|
|
346
332
|
applied: true,
|
|
347
333
|
tokensBefore,
|
|
@@ -387,6 +373,7 @@ export class CompactionWiring {
|
|
|
387
373
|
...(totals.declined > 0 ? { declined: totals.declined } : {}),
|
|
388
374
|
tokensReclaimed: totals.tokensReclaimed,
|
|
389
375
|
usage: totals.usage,
|
|
376
|
+
costUsd: totals.costUsd,
|
|
390
377
|
pricingUnknown: totals.pricingUnknown,
|
|
391
378
|
inFlight: totals.inFlight,
|
|
392
379
|
selfDisabled: totals.selfDisabled,
|
|
@@ -460,9 +447,13 @@ export class CompactionWiring {
|
|
|
460
447
|
onAgentEvent(event) {
|
|
461
448
|
if (event.type === 'agent_start') {
|
|
462
449
|
this.compactor.onRunStart();
|
|
450
|
+
this.overflowNotified = false;
|
|
463
451
|
return;
|
|
464
452
|
}
|
|
453
|
+
if (event.type === 'agent_end')
|
|
454
|
+
this.compactor.onRunEnd();
|
|
465
455
|
if (event.type === 'turn_end') {
|
|
456
|
+
this.meter.onTurnEnd(event.usage);
|
|
466
457
|
// THE MEASUREMENT IS THE METER'S (context-usage-gauge-accuracy §3.3.4). It
|
|
467
458
|
// subscribes to `turn_end` on the same stream and recomputes the offset,
|
|
468
459
|
// the prefix length and the pressure there; the two calls that used to
|
|
@@ -501,6 +492,7 @@ export class CompactionWiring {
|
|
|
501
492
|
// engine cannot report `true` without a `replace` outcome, which means the
|
|
502
493
|
// compactor's own record already exists — so its zeroed counts are never
|
|
503
494
|
// rendered.
|
|
495
|
+
this.compactor.settleOperation(verdict);
|
|
504
496
|
const record = this.pending ?? this.skeletonRecord();
|
|
505
497
|
this.pending = null;
|
|
506
498
|
this.open = null;
|
|
@@ -533,7 +525,6 @@ export class CompactionWiring {
|
|
|
533
525
|
mode: verdict.applied ? record.mode : 'none',
|
|
534
526
|
};
|
|
535
527
|
if (verdict.applied) {
|
|
536
|
-
this.compactor.recordApplied(settled.tokensBefore, settled.tokensAfter);
|
|
537
528
|
// SITE 1 OF THE FOUR INVALIDATION SITES (hardening §3.2.3), and THE LOAD-
|
|
538
529
|
// BEARING LINE OF THE P0-1 FIX (context-usage-gauge-accuracy I-9).
|
|
539
530
|
//
|
|
@@ -565,9 +556,8 @@ export class CompactionWiring {
|
|
|
565
556
|
* archive a history that is still live" - and the two paths reach that verdict
|
|
566
557
|
* at different moments relative to the splice. In loop, the ENGINE decides
|
|
567
558
|
* after `validateHistory`, so this runs AFTER adoption. On the manual path the
|
|
568
|
-
* WIRING
|
|
569
|
-
*
|
|
570
|
-
* for its one caller (RV-7).
|
|
559
|
+
* WIRING invokes the host adoption callback before settling, so both paths
|
|
560
|
+
* archive only histories that have actually been replaced.
|
|
571
561
|
*
|
|
572
562
|
* BEST-EFFORT AND SYNCHRONOUS-BUT-GUARDED. `writeArchive` never throws and
|
|
573
563
|
* logs its own failure; a failed archive never affects the compaction, and it
|
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
|
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;
|