@aragon-agent/cli 0.6.3 → 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 +105 -0
- package/README.md +95 -2
- package/dist/agent/controller.d.ts +119 -0
- package/dist/agent/controller.js +275 -1
- package/dist/agent/reducer.d.ts +69 -0
- package/dist/agent/reducer.js +100 -0
- package/dist/agent/system-prompt.d.ts +14 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +84 -22
- package/dist/commands/builtins.js +92 -1
- package/dist/compaction/child.js +5 -0
- package/dist/compaction/command.js +14 -1
- package/dist/compaction/compactor.d.ts +34 -0
- package/dist/compaction/compactor.js +158 -37
- package/dist/compaction/types.d.ts +36 -0
- package/dist/compaction/wiring.d.ts +9 -0
- package/dist/compaction/wiring.js +22 -1
- package/dist/config/env.d.ts +1 -1
- package/dist/config/env.js +48 -1
- package/dist/config/load.d.ts +11 -0
- package/dist/config/load.js +39 -1
- package/dist/config/schema.d.ts +85 -0
- package/dist/config/schema.js +33 -0
- package/dist/config/store.js +21 -1
- package/dist/input/limits.d.ts +72 -0
- package/dist/input/limits.js +63 -0
- 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 +2 -0
- package/dist/logging/install.js +53 -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 +36 -1
- 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/App.d.ts +15 -2
- package/dist/ui/App.js +256 -7
- package/dist/ui/Composer.d.ts +54 -1
- package/dist/ui/Composer.js +50 -6
- package/dist/ui/PromptInput.d.ts +49 -3
- package/dist/ui/PromptInput.js +184 -52
- package/dist/ui/StatusBar.d.ts +19 -0
- package/dist/ui/StatusBar.js +5 -2
- package/dist/ui/Transcript.js +29 -1
- 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/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/layout/budget.d.ts +34 -6
- package/dist/ui/layout/budget.js +32 -7
- package/dist/ui/layout/virtual-window.js +51 -1
- package/dist/ui/overlays/HelpOverlay.js +3 -0
- package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
- package/dist/ui/overlays/QuestionOverlay.js +5 -1
- package/dist/ui/overlays/SettingsScreen.js +14 -1
- 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 +16 -0
- package/dist/ui/screen.js +45 -2
- package/dist/ui/selection/selection-controller.d.ts +1 -1
- package/dist/ui/transcript-text.js +49 -0
- package/dist/ui/use-wheel-routing.d.ts +1 -1
- package/package.json +2 -2
- package/dist/input/stdin-mouse-filter.d.ts +0 -47
- package/dist/input/stdin-mouse-filter.js +0 -153
|
@@ -26,6 +26,15 @@ export class Compactor {
|
|
|
26
26
|
lastCompactionTurn = -Infinity;
|
|
27
27
|
// --- Per-session state ---
|
|
28
28
|
compactions = 0;
|
|
29
|
+
/**
|
|
30
|
+
* How many `compact()` calls returned WITHOUT announcing (quiet-noop D-9).
|
|
31
|
+
*
|
|
32
|
+
* A SESSION TOTAL, LIKE `compactions`, and reset by neither `onRunStart()` nor
|
|
33
|
+
* `clearSelfDisable()`: `/compact on` overrides a guard, it does not rewrite
|
|
34
|
+
* history. `/compact status` is its only reader, and it is the whole of what
|
|
35
|
+
* keeps a decline reportable once the transcript stops carrying one.
|
|
36
|
+
*/
|
|
37
|
+
declined = 0;
|
|
29
38
|
tokensReclaimed = 0;
|
|
30
39
|
sessionUsage = { inputTokens: 0, outputTokens: 0 };
|
|
31
40
|
pricingUnknown = false;
|
|
@@ -218,22 +227,19 @@ export class Compactor {
|
|
|
218
227
|
const uiTrigger = manual ? 'manual' : ctx.trigger;
|
|
219
228
|
const config = this.deps.getConfig().compaction;
|
|
220
229
|
const summarizer = this.resolveSummarizer();
|
|
230
|
+
// GUARDS 1 AND 2 COUNT *ATTEMPTS*, NOT ANNOUNCEMENTS, and they stay here
|
|
231
|
+
// (quiet-noop §3.3). A decline still consumed a checkpoint: it still has to
|
|
232
|
+
// serve the cooldown and it still has to be charged to the per-run cap, or a
|
|
233
|
+
// history that can never be compacted would re-attempt on every turn for the
|
|
234
|
+
// whole run. `inFlight` stays for the same reason - the chip reports what the
|
|
235
|
+
// compactor is DOING, not what it has decided to say.
|
|
221
236
|
this.compactionsThisRun += 1;
|
|
222
237
|
this.lastCompactionTurn = ctx.turnIndex;
|
|
223
|
-
this.compactions += 1;
|
|
224
238
|
this.inFlight = true;
|
|
225
|
-
const index = this.compactions;
|
|
226
|
-
this.deps.emit({
|
|
227
|
-
type: 'compaction_start',
|
|
228
|
-
index,
|
|
229
|
-
trigger: uiTrigger,
|
|
230
|
-
model: summarizer?.ref.modelId ?? '',
|
|
231
|
-
});
|
|
232
239
|
const started = Date.now();
|
|
233
240
|
try {
|
|
234
241
|
return await this.runCompaction({
|
|
235
242
|
ctx,
|
|
236
|
-
index,
|
|
237
243
|
uiTrigger,
|
|
238
244
|
config,
|
|
239
245
|
summarizer,
|
|
@@ -250,8 +256,56 @@ export class Compactor {
|
|
|
250
256
|
// =========================================================================
|
|
251
257
|
// Internals
|
|
252
258
|
// =========================================================================
|
|
259
|
+
/**
|
|
260
|
+
* Open the card (quiet-noop §3.2).
|
|
261
|
+
*
|
|
262
|
+
* THE ONLY EMITTER OF `compaction_start`, AND THE ONLY PLACE `this.compactions`
|
|
263
|
+
* ADVANCES. A compaction that never reaches an announce point is a DECLINE: it
|
|
264
|
+
* changed nothing, called nothing and spent nothing, and it is deliberately
|
|
265
|
+
* indistinguishable - on every human surface except `/compact status`'s
|
|
266
|
+
* `declined` count (D-9) - from a turn at which `shouldCompact` returned false.
|
|
267
|
+
* `finish` is the other half of that rule.
|
|
268
|
+
*
|
|
269
|
+
* IT RETURNS THE ANNOUNCEMENT RATHER THAN STORING IT (P1-1), and the caller
|
|
270
|
+
* keeps it in a LOCAL for the lifetime of one `runCompaction` call - the same
|
|
271
|
+
* slot `index` occupied before this change. An instance field would have needed
|
|
272
|
+
* a no-re-entrancy argument, and the obvious one is WRONG: guard 1 lives in
|
|
273
|
+
* `shouldCompact`, while the idle `/compact` path calls `compact()` straight
|
|
274
|
+
* through `wiring.compactNow()` without consulting it. What actually keeps two
|
|
275
|
+
* compactions apart is that `command.ts` QUEUES instead of calling `compactNow`
|
|
276
|
+
* while the loop runs - a property of a different file, which is exactly why
|
|
277
|
+
* this one must not depend on it. A clobbered field would leave `finish` unable
|
|
278
|
+
* to settle the card it opened, and a card that never settles pins
|
|
279
|
+
* `Transcript`'s monotonic boundary for the rest of the session.
|
|
280
|
+
*/
|
|
281
|
+
announce(trigger, summarizer) {
|
|
282
|
+
this.compactions += 1;
|
|
283
|
+
this.deps.emit({
|
|
284
|
+
type: 'compaction_start',
|
|
285
|
+
index: this.compactions,
|
|
286
|
+
trigger,
|
|
287
|
+
model: summarizer?.ref.modelId ?? '',
|
|
288
|
+
});
|
|
289
|
+
return { index: this.compactions };
|
|
290
|
+
}
|
|
253
291
|
async runCompaction(args) {
|
|
254
292
|
const { ctx, config, summarizer } = args;
|
|
293
|
+
// THE ANNOUNCEMENT IS A LOCAL, NEVER A FIELD (quiet-noop §3.2 / P1-1), and
|
|
294
|
+
// `??=` is what makes the eager path and the two commit points idempotent
|
|
295
|
+
// with respect to each other.
|
|
296
|
+
let announced = null;
|
|
297
|
+
const announce = (model) => {
|
|
298
|
+
announced ??= this.announce(args.uiTrigger, model);
|
|
299
|
+
};
|
|
300
|
+
// A REQUESTED compaction and a REACTIVE one announce BEFORE they know the
|
|
301
|
+
// answer, for two different reasons. `manual` is a user instruction, and its
|
|
302
|
+
// QUEUED form (`/compact` typed mid-run) has NO other reporting surface - a
|
|
303
|
+
// silent decline there would be a command that did nothing and said nothing.
|
|
304
|
+
// `overflow` runs after the provider has ALREADY refused the request; if it
|
|
305
|
+
// cannot recover, the card is the only place that says why the run is about
|
|
306
|
+
// to die. Both paths stay byte-identical to the round before this one.
|
|
307
|
+
if (args.uiTrigger !== 'pressure')
|
|
308
|
+
announce(summarizer);
|
|
255
309
|
const before = ctx.messages;
|
|
256
310
|
const protectedPrefix = countProtectedPrefix(before);
|
|
257
311
|
// THE ONE LEVER THE GUARDS DO NOT HAVE (D-24). A tail that alone exceeds the
|
|
@@ -271,6 +325,12 @@ export class Compactor {
|
|
|
271
325
|
// `plan === null` branch tries it before conceding.
|
|
272
326
|
const reliefOnly = this.relieveInPlace(before, protectedPrefix, ctx.systemPrompt);
|
|
273
327
|
if (reliefOnly) {
|
|
328
|
+
// COMMIT POINT A (quiet-noop §3.4). Relief changes the history the run
|
|
329
|
+
// continues from - a bounded, announced data loss - so it is announced
|
|
330
|
+
// even though no model was called. `null` summarizer for the same reason
|
|
331
|
+
// `finish` writes `model: ''` on a relief-only record: naming a model here
|
|
332
|
+
// would claim a call that never ran.
|
|
333
|
+
announce(null);
|
|
274
334
|
this.checkProgress(ctx, args.uiTrigger, reliefOnly.messages);
|
|
275
335
|
return this.finish({ ...args, tailRelief: reliefSummary(reliefOnly) }, {
|
|
276
336
|
action: 'replace',
|
|
@@ -282,17 +342,51 @@ export class Compactor {
|
|
|
282
342
|
mode: 'truncated',
|
|
283
343
|
messages: reliefOnly.messages,
|
|
284
344
|
reason: 'tail_relief_only',
|
|
285
|
-
}, { droppedMessages: 0 }, keepRecentTurns);
|
|
345
|
+
}, { droppedMessages: 0 }, keepRecentTurns, announced);
|
|
286
346
|
}
|
|
287
347
|
this.noteNoProgress(args.uiTrigger, 'nothing_to_drop');
|
|
288
|
-
return this.finish(args, { action: 'keep', reason: 'nothing_to_drop' }, null, keepRecentTurns);
|
|
348
|
+
return this.finish(args, { action: 'keep', reason: 'nothing_to_drop' }, null, keepRecentTurns, announced);
|
|
289
349
|
}
|
|
290
350
|
const anchor = buildAnchor(before);
|
|
291
351
|
if (!anchor) {
|
|
292
352
|
// A history with no `user` message at all cannot have a goal anchor, and
|
|
293
353
|
// D-6 makes the anchor non-negotiable. Refusing is the honest outcome.
|
|
294
|
-
|
|
354
|
+
//
|
|
355
|
+
// STRUCTURALLY UNREACHABLE, AND KEPT ANYWAY (quiet-noop E-23 / §3.4). This
|
|
356
|
+
// is reached only after `plan !== null`, and `planCompaction` returns a plan
|
|
357
|
+
// only when some cut leaves `countTurns >= keep` behind it - `countTurns`
|
|
358
|
+
// counts `user`-role messages and `keep` is at least 1, so a plan PROVES the
|
|
359
|
+
// one thing `buildAnchor` looks for. A provably dead guard is worth strictly
|
|
360
|
+
// more than the non-null assertion that would replace it. It stays ABOVE the
|
|
361
|
+
// commit point on purpose: if the plan/anchor relationship ever changes, the
|
|
362
|
+
// reason that appears is one decided before any work began, and silence is
|
|
363
|
+
// what it should get - but any new PRE-COMMIT decline must then either
|
|
364
|
+
// charge guard 4 (as `nothing_to_drop` does) or be proven unreachable in
|
|
365
|
+
// turn, because guard 4 is what bounds how long a silent decline can repeat.
|
|
366
|
+
return this.finish(args, { action: 'keep', reason: 'no_anchor' }, null, keepRecentTurns, announced);
|
|
295
367
|
}
|
|
368
|
+
// COMMIT POINT B (quiet-noop §3.4). Everything below this line either spends
|
|
369
|
+
// money (`runLadder`), rewrites the history, or faults - and all three are the
|
|
370
|
+
// user's business.
|
|
371
|
+
//
|
|
372
|
+
// BEFORE `buildDigest`, NOT AFTER. A throw inside the digest renderer reaches
|
|
373
|
+
// the engine as `manager_threw`, and the engine settles the card from the
|
|
374
|
+
// verdict alone; with no card open, that fault would be invisible on every
|
|
375
|
+
// human surface. Announcing first costs one event on a path that is about to
|
|
376
|
+
// spend seconds and dollars.
|
|
377
|
+
//
|
|
378
|
+
// WHAT IT STILL DOES NOT COVER (recorded, not closed - quiet-noop §12 / IF-2).
|
|
379
|
+
// A throw in the SYNCHRONOUS region ABOVE this line - `countProtectedPrefix`,
|
|
380
|
+
// `planCompaction`, `relieveInPlace`, `buildAnchor` - also reaches the engine
|
|
381
|
+
// as `manager_threw`, and on `pressure` that fault now has no card, charges no
|
|
382
|
+
// guard (`noteNoProgress` is never reached) and does not advance `declined`
|
|
383
|
+
// (`finish` is never called) - so a host bug there would repeat at every
|
|
384
|
+
// checkpoint for a whole run in silence. `manager_timeout` cannot land there:
|
|
385
|
+
// that region holds no `await`, which is the property AC-Q12 pins. Closing it
|
|
386
|
+
// means hoisting the announcement out of this function, which is precisely
|
|
387
|
+
// what P1-1 decided against - so it is written down rather than fixed under
|
|
388
|
+
// another change's name.
|
|
389
|
+
announce(summarizer);
|
|
296
390
|
const head = before.slice(protectedPrefix, plan.cutIndex);
|
|
297
391
|
const priorBlock = before.slice(0, protectedPrefix).find((m) => isPriorSummaryBlock(m));
|
|
298
392
|
// The prior block is carried into the DIGEST rather than the tail, so the
|
|
@@ -312,14 +406,14 @@ export class Compactor {
|
|
|
312
406
|
...(args.instructions ? { instructions: args.instructions } : {}),
|
|
313
407
|
});
|
|
314
408
|
if (ladder.kind === 'aborted') {
|
|
315
|
-
return this.finish(args, { action: 'keep', reason: 'aborted' }, null, keepRecentTurns);
|
|
409
|
+
return this.finish(args, { action: 'keep', reason: 'aborted' }, null, keepRecentTurns, announced);
|
|
316
410
|
}
|
|
317
411
|
if (ladder.kind === 'failed' && config.onFailure === 'stop') {
|
|
318
412
|
// Rung 4. The run proceeds and will probably hit `context_overflow`, which
|
|
319
413
|
// is what the user asked for by setting this.
|
|
320
414
|
this.deps.notify('error', `Context compaction failed (${ladder.reason}) and compaction.onFailure is "stop". ` +
|
|
321
415
|
'The next request may exceed the context window. Try /compact again, or /clear, or /reset.');
|
|
322
|
-
return this.finish(args, { action: 'keep', reason: `summarize_failed: ${ladder.reason}` }, null, keepRecentTurns);
|
|
416
|
+
return this.finish(args, { action: 'keep', reason: `summarize_failed: ${ladder.reason}` }, null, keepRecentTurns, announced);
|
|
323
417
|
}
|
|
324
418
|
const truncated = ladder.kind === 'failed';
|
|
325
419
|
const mode = truncated ? 'truncated' : 'summarized';
|
|
@@ -365,7 +459,7 @@ export class Compactor {
|
|
|
365
459
|
messages,
|
|
366
460
|
...(truncated ? {} : { summary: ladder.text }),
|
|
367
461
|
...(truncated ? { reason: `truncated: ${ladder.reason}` } : {}),
|
|
368
|
-
}, { droppedMessages: plan.droppedMessages, summary: truncated ? undefined : ladder.text }, keepRecentTurns);
|
|
462
|
+
}, { droppedMessages: plan.droppedMessages, summary: truncated ? undefined : ladder.text }, keepRecentTurns, announced);
|
|
369
463
|
}
|
|
370
464
|
/** Tail relief against this session's window (hardening §3.3.3 / W2). */
|
|
371
465
|
relieveInPlace(messages, from, systemPrompt) {
|
|
@@ -522,19 +616,57 @@ export class Compactor {
|
|
|
522
616
|
`${this.deps.getConfig().compaction.keepRecentTurns} turns alone exceed the threshold. ` +
|
|
523
617
|
'Lower compaction.keepRecentTurns, or use /clear or /reset.');
|
|
524
618
|
}
|
|
525
|
-
finish(args, outcome, applied, keepRecentTurnsUsed) {
|
|
619
|
+
finish(args, outcome, applied, keepRecentTurnsUsed, announced) {
|
|
526
620
|
const before = args.ctx.messages.length;
|
|
527
621
|
const after = outcome.action === 'replace' ? outcome.messages.length : before;
|
|
528
622
|
const tokensBefore = this.lastPressure?.occupied ?? 0;
|
|
623
|
+
// `'relieved'` IS A CLI-ONLY WORD (RV-5 / DH-20). The port reports
|
|
624
|
+
// `'truncated'` for a relief-only compaction, which is accurate for the
|
|
625
|
+
// engine's narrower question; the card's `summarized N messages with
|
|
626
|
+
// <model>` line would otherwise render "summarized 0 messages with
|
|
627
|
+
// claude-haiku-4-5" for a compaction in which no model was called.
|
|
628
|
+
const mode = reliefOnly(outcome)
|
|
629
|
+
? 'relieved'
|
|
630
|
+
: outcome.action === 'replace'
|
|
631
|
+
? outcome.mode
|
|
632
|
+
: 'none';
|
|
633
|
+
// NO MODEL ON A RELIEF-ONLY RECORD, because no model was called. The card
|
|
634
|
+
// names the summarizer from this field, and naming one here would be the
|
|
635
|
+
// same claim `mode: 'relieved'` exists to stop making.
|
|
636
|
+
const model = reliefOnly(outcome) ? '' : args.summarizer?.ref.modelId ?? '';
|
|
637
|
+
const durationMs = Date.now() - args.started;
|
|
638
|
+
// THE ON-DISK RECORD OF EVERY ATTEMPT IS UNCHANGED (quiet-noop §9). This
|
|
639
|
+
// feature makes the TRANSCRIPT quiet, not the logs: a declined compaction is
|
|
640
|
+
// still one `compaction` line here, still one `compaction_start` /
|
|
641
|
+
// `compaction_end` pair from CORE in the JSONL sink, and still reflected in
|
|
642
|
+
// `/compact status`.
|
|
643
|
+
this.log.info('compaction', {
|
|
644
|
+
trigger: args.uiTrigger,
|
|
645
|
+
announced: announced !== null,
|
|
646
|
+
before,
|
|
647
|
+
after,
|
|
648
|
+
droppedMessages: applied?.droppedMessages ?? 0,
|
|
649
|
+
mode,
|
|
650
|
+
model,
|
|
651
|
+
durationMs,
|
|
652
|
+
});
|
|
653
|
+
// A SILENT DECLINE (quiet-noop §3.1). No card was opened, so there is nothing
|
|
654
|
+
// to settle and nothing to say IN THE TRANSCRIPT. Returning here is what makes
|
|
655
|
+
// a declined checkpoint indistinguishable from one at which `shouldCompact`
|
|
656
|
+
// said no.
|
|
657
|
+
//
|
|
658
|
+
// `declined` IS THE ONE THING THAT STILL SPEAKS (D-9). Quiet is the goal;
|
|
659
|
+
// invisible is not, and `/compact status` is a published guaranteed surface
|
|
660
|
+
// (`context-auto-compaction` §6.5). One integer here is what keeps that
|
|
661
|
+
// promise true while the card goes away.
|
|
662
|
+
if (!announced) {
|
|
663
|
+
this.declined += 1;
|
|
664
|
+
return outcome;
|
|
665
|
+
}
|
|
529
666
|
const record = {
|
|
530
|
-
index:
|
|
667
|
+
index: announced.index,
|
|
531
668
|
trigger: args.uiTrigger,
|
|
532
|
-
|
|
533
|
-
// `'truncated'` for a relief-only compaction, which is accurate for the
|
|
534
|
-
// engine's narrower question; the card's `summarized N messages with
|
|
535
|
-
// <model>` line would otherwise render "summarized 0 messages with
|
|
536
|
-
// claude-haiku-4-5" for a compaction in which no model was called.
|
|
537
|
-
mode: reliefOnly(outcome) ? 'relieved' : outcome.action === 'replace' ? outcome.mode : 'none',
|
|
669
|
+
mode,
|
|
538
670
|
// OPTIMISTIC AND CORRECTED BY THE WIRING (§3.2.1). Only the ENGINE knows
|
|
539
671
|
// whether the splice passed the structural gate, and it says so on
|
|
540
672
|
// `compaction_end`; the wiring rewrites this before the card settles.
|
|
@@ -546,23 +678,11 @@ export class Compactor {
|
|
|
546
678
|
tokensBefore,
|
|
547
679
|
tokensAfter: 0, // filled by the wiring from the engine's own estimator
|
|
548
680
|
...(applied?.summary ? { summary: applied.summary } : {}),
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
// same claim `mode: 'relieved'` exists to stop making.
|
|
552
|
-
model: reliefOnly(outcome) ? '' : args.summarizer?.ref.modelId ?? '',
|
|
553
|
-
durationMs: Date.now() - args.started,
|
|
681
|
+
model,
|
|
682
|
+
durationMs,
|
|
554
683
|
...(args.ctx.trigger === 'overflow' ? { keepRecentTurnsUsed } : {}),
|
|
555
684
|
...(args.tailRelief ? { tailRelief: args.tailRelief } : {}),
|
|
556
685
|
};
|
|
557
|
-
this.log.info('compaction', {
|
|
558
|
-
trigger: args.uiTrigger,
|
|
559
|
-
before,
|
|
560
|
-
after,
|
|
561
|
-
droppedMessages: applied?.droppedMessages ?? 0,
|
|
562
|
-
mode: record.mode,
|
|
563
|
-
model: record.model,
|
|
564
|
-
durationMs: record.durationMs,
|
|
565
|
-
});
|
|
566
686
|
this.deps.emit({ type: 'compaction_end', record });
|
|
567
687
|
return outcome;
|
|
568
688
|
}
|
|
@@ -605,6 +725,7 @@ export class Compactor {
|
|
|
605
725
|
sessionTotals() {
|
|
606
726
|
return {
|
|
607
727
|
compactions: this.compactions,
|
|
728
|
+
declined: this.declined,
|
|
608
729
|
tokensReclaimed: this.tokensReclaimed,
|
|
609
730
|
usage: this.sessionUsage,
|
|
610
731
|
pricingUnknown: this.pricingUnknown,
|
|
@@ -143,7 +143,26 @@ export interface CompactionSnapshot {
|
|
|
143
143
|
live: boolean;
|
|
144
144
|
/** The summarizer's model id, or `''`. */
|
|
145
145
|
model: string;
|
|
146
|
+
/**
|
|
147
|
+
* How many compactions ANNOUNCED themselves (quiet-noop §6.2 / D-5).
|
|
148
|
+
*
|
|
149
|
+
* COMPACTIONS THAT RAN, NOT CHECKPOINTS THAT FIRED. A `pressure` compaction
|
|
150
|
+
* that declines before calling a model or changing the history never advances
|
|
151
|
+
* this, which is what keeps the card's `#N` contiguous. `declined` below is
|
|
152
|
+
* the other half of the same total.
|
|
153
|
+
*/
|
|
146
154
|
compactions: number;
|
|
155
|
+
/**
|
|
156
|
+
* How many `pressure` checkpoints declined BEFORE committing to any work
|
|
157
|
+
* (quiet-noop D-9 / P1-2).
|
|
158
|
+
*
|
|
159
|
+
* OPTIONAL SO EVERY EXISTING CONSTRUCTION SITE STAYS UNTOUCHED, and rendered
|
|
160
|
+
* by `/compact status` only when non-zero — a healthy session must not read
|
|
161
|
+
* `(0 checkpoints declined)`. This is the one human surface that still speaks
|
|
162
|
+
* about a decline after the transcript went quiet, which is what keeps
|
|
163
|
+
* `context-auto-compaction` §6.5's "guaranteed reporting surface" promise true.
|
|
164
|
+
*/
|
|
165
|
+
declined?: number;
|
|
147
166
|
/** Sum of `(tokensBefore - tokensAfter)` over applied compactions. */
|
|
148
167
|
tokensReclaimed: number;
|
|
149
168
|
/** What compaction itself has spent this session. */
|
|
@@ -161,6 +180,23 @@ export interface CompactionSnapshot {
|
|
|
161
180
|
generation: number;
|
|
162
181
|
pressure: Pressure;
|
|
163
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* THE COMPACTION EVENT STREAM.
|
|
185
|
+
*
|
|
186
|
+
* `compaction_start` and `compaction_end` are emitted ONLY for compactions that
|
|
187
|
+
* COMMITTED TO WORK (quiet-noop §3.1): a `pressure` compaction that declines
|
|
188
|
+
* before calling a model or changing the history emits NEITHER, and is
|
|
189
|
+
* deliberately indistinguishable from a turn at which `shouldCompact` returned
|
|
190
|
+
* false. `manual` and `overflow` always emit both.
|
|
191
|
+
*
|
|
192
|
+
* The pair is still BALANCED: every `compaction_start` is followed by exactly
|
|
193
|
+
* one `compaction_end` (a `finally` in the engine guarantees the verdict, and
|
|
194
|
+
* `settlePending` synthesizes a skeleton record when the compactor's own has not
|
|
195
|
+
* arrived). Consumers that count PAIRS are unaffected; consumers that counted
|
|
196
|
+
* ATTEMPTS must read `/compact status` or the JSONL diagnostic log instead -
|
|
197
|
+
* CORE's own `compaction_start` / `compaction_end` pair still fires for every
|
|
198
|
+
* attempt, declined or not, and that is the stream the log subscribes to.
|
|
199
|
+
*/
|
|
164
200
|
export type CompactionEvent = {
|
|
165
201
|
type: 'compaction_start';
|
|
166
202
|
index: number;
|
|
@@ -25,6 +25,15 @@
|
|
|
25
25
|
* methods delegate to a compactor that reads config LIVE - which is what lets
|
|
26
26
|
* `/compact off`, `/compact threshold 0.8` and a settings-screen save take effect
|
|
27
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.
|
|
28
37
|
*/
|
|
29
38
|
import { type AgentEvent, type ContextManager, type Message, type ModelInfo, type ModelRef, type ProviderRegistry, type ProviderRegistryOptions, type TokenUsage } from '@aragon-agent/core';
|
|
30
39
|
import type { CliConfig } from '../config/schema.js';
|
|
@@ -25,6 +25,15 @@
|
|
|
25
25
|
* methods delegate to a compactor that reads config LIVE - which is what lets
|
|
26
26
|
* `/compact off`, `/compact threshold 0.8` and a settings-screen save take effect
|
|
27
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.
|
|
28
37
|
*/
|
|
29
38
|
import { DEFAULT_RETRY_POLICY, estimatePromptTokens, initProviders, validateHistory, } from '@aragon-agent/core';
|
|
30
39
|
import { getLogger } from '../logging/logger.js';
|
|
@@ -328,6 +337,10 @@ export class CompactionWiring {
|
|
|
328
337
|
live: this.enabled && summarizer !== null,
|
|
329
338
|
model: summarizer?.ref.modelId ?? '',
|
|
330
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 } : {}),
|
|
331
344
|
tokensReclaimed: totals.tokensReclaimed,
|
|
332
345
|
usage: totals.usage,
|
|
333
346
|
pricingUnknown: totals.pricingUnknown,
|
|
@@ -445,8 +458,16 @@ export class CompactionWiring {
|
|
|
445
458
|
// compaction no matter which branch the verdict took - including the early
|
|
446
459
|
// `return` below.
|
|
447
460
|
const dropped = this.compactor.takeLastDropped();
|
|
448
|
-
if (!record)
|
|
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() });
|
|
449
469
|
return;
|
|
470
|
+
}
|
|
450
471
|
const settled = {
|
|
451
472
|
...record,
|
|
452
473
|
applied: verdict.applied,
|
package/dist/config/env.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - Overrides: ARAGON_PROVIDER, ARAGON_MODEL, ARAGON_BASE_URL, ARAGON_THINKING,
|
|
8
8
|
* ARAGON_SHOW_THINKING,
|
|
9
9
|
* ARAGON_MAX_TOKENS (a number, or `auto` / `0` for the per-model ceiling),
|
|
10
|
-
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PLAN, ARAGON_TEAM,
|
|
10
|
+
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PASTE, ARAGON_PLAN, ARAGON_TEAM,
|
|
11
11
|
* ARAGON_TEAM_MAX, ARAGON_RETRY, ARAGON_RETRY_MAX, ARAGON_FAST,
|
|
12
12
|
* ARAGON_FAST_PROVIDER, ARAGON_FAST_MODEL, ARAGON_FAST_BASE_URL,
|
|
13
13
|
* ARAGON_LOG_LEVEL,
|
package/dist/config/env.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - Overrides: ARAGON_PROVIDER, ARAGON_MODEL, ARAGON_BASE_URL, ARAGON_THINKING,
|
|
8
8
|
* ARAGON_SHOW_THINKING,
|
|
9
9
|
* ARAGON_MAX_TOKENS (a number, or `auto` / `0` for the per-model ceiling),
|
|
10
|
-
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PLAN, ARAGON_TEAM,
|
|
10
|
+
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PASTE, ARAGON_PLAN, ARAGON_TEAM,
|
|
11
11
|
* ARAGON_TEAM_MAX, ARAGON_RETRY, ARAGON_RETRY_MAX, ARAGON_FAST,
|
|
12
12
|
* ARAGON_FAST_PROVIDER, ARAGON_FAST_MODEL, ARAGON_FAST_BASE_URL,
|
|
13
13
|
* ARAGON_LOG_LEVEL,
|
|
@@ -141,6 +141,13 @@ export function readEnvConfig() {
|
|
|
141
141
|
mouseSelect === 'on' ||
|
|
142
142
|
mouseSelect === 'yes';
|
|
143
143
|
}
|
|
144
|
+
// ARAGON_PASTE=0 turns paste recognition off, which is the documented one-env
|
|
145
|
+
// revert to v0.6.3 input handling (tui-paste-handling section 7.2). Parsed by
|
|
146
|
+
// the POSITIVE list, exactly as its two neighbours above are.
|
|
147
|
+
const paste = process.env.ARAGON_PASTE?.trim().toLowerCase();
|
|
148
|
+
if (paste !== undefined && paste.length > 0) {
|
|
149
|
+
partial.paste = paste === '1' || paste === 'true' || paste === 'on' || paste === 'yes';
|
|
150
|
+
}
|
|
144
151
|
// ARAGON_SCROLL_RESUME_MS=<n> — idle delay before a paused viewport returns to
|
|
145
152
|
// the newest line; `0` disables auto-resume. THE ONLY CHANNEL BESIDES THE
|
|
146
153
|
// CONFIG FILE, because §6.1 gives this key no CLI flag on purpose. `load.ts`
|
|
@@ -239,6 +246,46 @@ export function readEnvConfig() {
|
|
|
239
246
|
if (Object.keys(section).length > 0)
|
|
240
247
|
partial.todo = section;
|
|
241
248
|
}
|
|
249
|
+
// ARAGON_BASH_BACKGROUND=0 turns background services off entirely (no
|
|
250
|
+
// `background` param, no `bash_output`/`bash_kill`, no prompt block, no chip);
|
|
251
|
+
// ARAGON_BASH_AUTO_BACKGROUND=0 keeps them but stops the classifier firing on
|
|
252
|
+
// an unset `background`. Parsed by the POSITIVE list, exactly as
|
|
253
|
+
// ARAGON_FULLSCREEN / ARAGON_MOUSE / ARAGON_PLAN / ARAGON_TEAM / ARAGON_TODO
|
|
254
|
+
// are - NOT by `envBool` below, which uses a negative list.
|
|
255
|
+
//
|
|
256
|
+
// Both pass the test every env var in this file has to pass: an unattended run
|
|
257
|
+
// in a container with a fixed entrypoint can reach neither a flag nor a config
|
|
258
|
+
// file, and "do not leave a dev server running in my CI job" is exactly the
|
|
259
|
+
// kind of decision that channel needs to make.
|
|
260
|
+
//
|
|
261
|
+
// ONE ACCUMULATED SECTION, ASSIGNED ONCE - the shape the `team` and `todo`
|
|
262
|
+
// branches above already use, and NOT two `if` blocks each assigning
|
|
263
|
+
// `partial.bash` (the `todo` P1-4 trap). THE CAST IS WHAT WOULD MAKE THAT
|
|
264
|
+
// MISTAKE SILENT: a second assignment compiles clean and drops the first key,
|
|
265
|
+
// so `ARAGON_BASH_BACKGROUND=0 ARAGON_BASH_AUTO_BACKGROUND=0` would quietly
|
|
266
|
+
// re-register the tools the user turned off.
|
|
267
|
+
const bashBackground = process.env.ARAGON_BASH_BACKGROUND?.trim().toLowerCase();
|
|
268
|
+
const bashAuto = process.env.ARAGON_BASH_AUTO_BACKGROUND?.trim().toLowerCase();
|
|
269
|
+
if ((bashBackground !== undefined && bashBackground.length > 0) ||
|
|
270
|
+
(bashAuto !== undefined && bashAuto.length > 0)) {
|
|
271
|
+
const section = {};
|
|
272
|
+
if (bashBackground !== undefined && bashBackground.length > 0) {
|
|
273
|
+
section.background =
|
|
274
|
+
bashBackground === '1' ||
|
|
275
|
+
bashBackground === 'true' ||
|
|
276
|
+
bashBackground === 'on' ||
|
|
277
|
+
bashBackground === 'yes';
|
|
278
|
+
}
|
|
279
|
+
if (bashAuto !== undefined && bashAuto.length > 0) {
|
|
280
|
+
section.autoBackground =
|
|
281
|
+
bashAuto === '1' || bashAuto === 'true' || bashAuto === 'on' || bashAuto === 'yes';
|
|
282
|
+
}
|
|
283
|
+
// A PARTIAL section, safe only because every consumer deep-merges it - the
|
|
284
|
+
// same contract `partial.skills`, `partial.log`, `partial.team` and
|
|
285
|
+
// `partial.todo` rely on.
|
|
286
|
+
if (Object.keys(section).length > 0)
|
|
287
|
+
partial.bash = section;
|
|
288
|
+
}
|
|
242
289
|
// ARAGON_RETRY=0 is the kill switch for API retry; ARAGON_RETRY_MAX=<n>
|
|
243
290
|
// overrides the count. Parsed by the POSITIVE list, exactly as
|
|
244
291
|
// ARAGON_FULLSCREEN / ARAGON_MOUSE / ARAGON_PLAN / ARAGON_TEAM / ARAGON_TODO
|
package/dist/config/load.d.ts
CHANGED
|
@@ -49,6 +49,17 @@ export interface CliFlags {
|
|
|
49
49
|
* `false` on every run (the `--todo-panel` trap).
|
|
50
50
|
*/
|
|
51
51
|
mouseSelect?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* `--paste` ⇒ true, `--no-paste` ⇒ false, absent ⇒ undefined
|
|
54
|
+
* (tui-paste-handling section 7.1).
|
|
55
|
+
*
|
|
56
|
+
* DECLARED AS A PAIR, and carried through `toFlags`, for the reason the two
|
|
57
|
+
* notes above record. `paste` is additionally PERSISTED and defaults to
|
|
58
|
+
* `true`, so a lone `--no-paste` would make commander default `opts.paste` to
|
|
59
|
+
* `true` -- indistinguishable from silence, and silently overriding a stored
|
|
60
|
+
* `false` on every run that passed no flag at all.
|
|
61
|
+
*/
|
|
62
|
+
paste?: boolean;
|
|
52
63
|
/** `--no-skills` ⇒ false. Turns the whole skill subsystem off (§7.5). */
|
|
53
64
|
skills?: boolean;
|
|
54
65
|
/** `--skill <name>` ×N — force Level 2 injection for this run. */
|
package/dist/config/load.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* legitimately long single tool run can never be watchdog-killed.
|
|
7
7
|
*/
|
|
8
8
|
import process from 'node:process';
|
|
9
|
-
import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampCompactionConfig, clampLogConfig, clampFastConfig, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampUpdateConfig, clampTheme, clampThinkingLevel, clampMaxTokens, clampMaxRenderInterval, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, parseThresholdInput, } from './schema.js';
|
|
9
|
+
import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampCompactionConfig, clampLogConfig, clampFastConfig, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampBashConfig, clampUpdateConfig, clampTheme, clampThinkingLevel, clampMaxTokens, clampMaxRenderInterval, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, parseThresholdInput, } from './schema.js';
|
|
10
10
|
import { loadDotenv, readEnvConfig, readEnvDisabledSkills } from './env.js';
|
|
11
11
|
import { readConfigFile } from './store.js';
|
|
12
12
|
import { getConfigPath } from './app-paths.js';
|
|
@@ -80,6 +80,24 @@ function resolveMouseSelect(flags, env, file) {
|
|
|
80
80
|
return file.mouseSelect;
|
|
81
81
|
return DEFAULT_CONFIG.mouseSelect;
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Paste handling: flag > env > file > default `true` (tui-paste-handling
|
|
85
|
+
* section 7.4).
|
|
86
|
+
*
|
|
87
|
+
* SHAPED EXACTLY LIKE `resolveMouse` above, for the reason its own note gives:
|
|
88
|
+
* reaching for the `hints` shape -- `flags.x !== undefined ? ... : file.x ??
|
|
89
|
+
* DEFAULT`, which reads NO ENV AT ALL -- would ship `ARAGON_PASTE` as
|
|
90
|
+
* documented-but-dead.
|
|
91
|
+
*/
|
|
92
|
+
function resolvePaste(flags, env, file) {
|
|
93
|
+
if (flags.paste !== undefined)
|
|
94
|
+
return flags.paste;
|
|
95
|
+
if (env.paste !== undefined)
|
|
96
|
+
return env.paste;
|
|
97
|
+
if (file.paste !== undefined)
|
|
98
|
+
return file.paste;
|
|
99
|
+
return DEFAULT_CONFIG.paste;
|
|
100
|
+
}
|
|
83
101
|
/**
|
|
84
102
|
* Idle scroll-resume delay: env › file › default 5000, clamped to [0, 120000].
|
|
85
103
|
*
|
|
@@ -153,6 +171,24 @@ function resolveTodoConfig(flags, env, file) {
|
|
|
153
171
|
...(flags.todoFollow !== undefined ? { followThrough: flags.todoFollow } : {}),
|
|
154
172
|
});
|
|
155
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Merge the `bash` section across the three layers that supply it
|
|
176
|
+
* (defaults > file > env), background-service-supervision §5.4.
|
|
177
|
+
*
|
|
178
|
+
* THREE LAYERS, NOT FOUR, AND THAT IS NOT AN OVERSIGHT. There is deliberately no
|
|
179
|
+
* `--no-background` flag: the two switches this section carries are already
|
|
180
|
+
* reachable from `config.json` (a per-machine preference) and from the
|
|
181
|
+
* environment (the channel a container entrypoint has), which is the two-part
|
|
182
|
+
* test every knob in this file is held to. A third spelling would be a third
|
|
183
|
+
* place for the answer to differ.
|
|
184
|
+
*/
|
|
185
|
+
function resolveBashConfig(env, file) {
|
|
186
|
+
return clampBashConfig({
|
|
187
|
+
...DEFAULT_CONFIG.bash,
|
|
188
|
+
...(file.bash ?? {}),
|
|
189
|
+
...(env.bash ?? {}),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
156
192
|
/**
|
|
157
193
|
* Merge the `retry` section across all four layers (defaults › file › env ›
|
|
158
194
|
* flags), llm-api-retry-backoff §6.2.
|
|
@@ -520,6 +556,7 @@ export function loadConfig(flags = {}) {
|
|
|
520
556
|
hints,
|
|
521
557
|
mouse: resolveMouse(flags, env.partial, file),
|
|
522
558
|
mouseSelect: resolveMouseSelect(flags, env.partial, file),
|
|
559
|
+
paste: resolvePaste(flags, env.partial, file),
|
|
523
560
|
scrollResumeMs: resolveScrollResumeMs(env.partial, file),
|
|
524
561
|
// From `<home>/state.json`, not the config file — see `ui-state.ts`.
|
|
525
562
|
submitCount: readSubmitCount(),
|
|
@@ -545,6 +582,7 @@ export function loadConfig(flags = {}) {
|
|
|
545
582
|
fast: resolveFastConfig(flags, env.partial, file),
|
|
546
583
|
update: resolveUpdateConfig(flags, env.partial, file),
|
|
547
584
|
compaction: resolveCompactionConfig(flags, env.partial, file),
|
|
585
|
+
bash: resolveBashConfig(env.partial, file),
|
|
548
586
|
cwd,
|
|
549
587
|
color,
|
|
550
588
|
colorLevel,
|