@aragon-agent/cli 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +216 -0
  2. package/README.md +241 -18
  3. package/dist/agent/agent-mode.d.ts +39 -0
  4. package/dist/agent/agent-mode.js +36 -0
  5. package/dist/agent/controller.d.ts +74 -1
  6. package/dist/agent/controller.js +200 -1
  7. package/dist/agent/headless.d.ts +8 -0
  8. package/dist/agent/headless.js +47 -0
  9. package/dist/agent/reducer.d.ts +120 -0
  10. package/dist/agent/reducer.js +201 -2
  11. package/dist/cli.js +297 -6
  12. package/dist/commands/builtins.js +73 -23
  13. package/dist/commands/registry.d.ts +27 -0
  14. package/dist/compaction/archive.d.ts +175 -0
  15. package/dist/compaction/archive.js +267 -0
  16. package/dist/compaction/child.d.ts +97 -0
  17. package/dist/compaction/child.js +113 -0
  18. package/dist/compaction/command.d.ts +41 -0
  19. package/dist/compaction/command.js +342 -0
  20. package/dist/compaction/compactor.d.ts +268 -0
  21. package/dist/compaction/compactor.js +689 -0
  22. package/dist/compaction/digest.d.ts +62 -0
  23. package/dist/compaction/digest.js +164 -0
  24. package/dist/compaction/limits.d.ts +165 -0
  25. package/dist/compaction/limits.js +172 -0
  26. package/dist/compaction/pressure.d.ts +135 -0
  27. package/dist/compaction/pressure.js +167 -0
  28. package/dist/compaction/summarize-call.d.ts +70 -0
  29. package/dist/compaction/summarize-call.js +86 -0
  30. package/dist/compaction/summary-prompt.d.ts +85 -0
  31. package/dist/compaction/summary-prompt.js +197 -0
  32. package/dist/compaction/tail-budget.d.ts +52 -0
  33. package/dist/compaction/tail-budget.js +67 -0
  34. package/dist/compaction/types.d.ts +179 -0
  35. package/dist/compaction/types.js +22 -0
  36. package/dist/compaction/wiring.d.ts +255 -0
  37. package/dist/compaction/wiring.js +556 -0
  38. package/dist/config/app-paths.d.ts +14 -0
  39. package/dist/config/app-paths.js +16 -0
  40. package/dist/config/cli-commands.d.ts +41 -0
  41. package/dist/config/cli-commands.js +77 -1
  42. package/dist/config/env.d.ts +4 -1
  43. package/dist/config/env.js +92 -2
  44. package/dist/config/load.d.ts +31 -0
  45. package/dist/config/load.js +63 -1
  46. package/dist/config/schema.d.ts +159 -0
  47. package/dist/config/schema.js +137 -0
  48. package/dist/config/store.js +17 -1
  49. package/dist/config/ui-state.d.ts +44 -6
  50. package/dist/config/ui-state.js +21 -12
  51. package/dist/diagnostics/doctor.js +41 -1
  52. package/dist/exec/events.d.ts +50 -1
  53. package/dist/exec/index.js +8 -4
  54. package/dist/exec/runner.js +44 -2
  55. package/dist/input/mouse-events.d.ts +31 -5
  56. package/dist/input/mouse-events.js +86 -27
  57. package/dist/input/stdin-mouse-filter.d.ts +8 -3
  58. package/dist/logging/install.d.ts +51 -1
  59. package/dist/logging/install.js +28 -0
  60. package/dist/logging/logger.d.ts +1 -1
  61. package/dist/session/cli-commands.js +23 -1
  62. package/dist/session/persist.js +19 -0
  63. package/dist/session/store.d.ts +18 -0
  64. package/dist/session/store.js +59 -2
  65. package/dist/team/report.js +7 -1
  66. package/dist/team/runtime.d.ts +10 -0
  67. package/dist/team/runtime.js +7 -0
  68. package/dist/team/subagent.d.ts +31 -1
  69. package/dist/team/subagent.js +42 -15
  70. package/dist/team/types.d.ts +8 -0
  71. package/dist/ui/ActivityLine.d.ts +19 -1
  72. package/dist/ui/ActivityLine.js +10 -2
  73. package/dist/ui/App.d.ts +48 -1
  74. package/dist/ui/App.js +317 -17
  75. package/dist/ui/Composer.d.ts +26 -1
  76. package/dist/ui/Composer.js +6 -6
  77. package/dist/ui/PromptInput.d.ts +34 -1
  78. package/dist/ui/PromptInput.js +54 -3
  79. package/dist/ui/StatusBar.d.ts +40 -0
  80. package/dist/ui/StatusBar.js +13 -4
  81. package/dist/ui/TeamPanel.js +7 -1
  82. package/dist/ui/Transcript.d.ts +21 -1
  83. package/dist/ui/Transcript.js +32 -2
  84. package/dist/ui/clipboard.d.ts +52 -0
  85. package/dist/ui/clipboard.js +91 -0
  86. package/dist/ui/entries/CompactionCard.d.ts +75 -0
  87. package/dist/ui/entries/CompactionCard.js +158 -0
  88. package/dist/ui/frame-differ.d.ts +46 -0
  89. package/dist/ui/frame-differ.js +94 -10
  90. package/dist/ui/gauge.d.ts +16 -1
  91. package/dist/ui/gauge.js +25 -6
  92. package/dist/ui/glyphs.d.ts +11 -0
  93. package/dist/ui/glyphs.js +2 -0
  94. package/dist/ui/layout/ScrollViewport.d.ts +43 -6
  95. package/dist/ui/layout/ScrollViewport.js +128 -21
  96. package/dist/ui/layout/budget.d.ts +10 -3
  97. package/dist/ui/layout/budget.js +10 -3
  98. package/dist/ui/layout/follow-state.d.ts +137 -0
  99. package/dist/ui/layout/follow-state.js +150 -0
  100. package/dist/ui/layout/virtual-window.d.ts +11 -0
  101. package/dist/ui/layout/virtual-window.js +34 -0
  102. package/dist/ui/overlays/HelpOverlay.js +18 -2
  103. package/dist/ui/overlays/SettingsScreen.d.ts +47 -1
  104. package/dist/ui/overlays/SettingsScreen.js +60 -1
  105. package/dist/ui/palettes.d.ts +16 -0
  106. package/dist/ui/palettes.js +6 -0
  107. package/dist/ui/screen.d.ts +22 -0
  108. package/dist/ui/screen.js +70 -8
  109. package/dist/ui/selection/highlight.d.ts +39 -0
  110. package/dist/ui/selection/highlight.js +64 -0
  111. package/dist/ui/selection/screen-mirror.d.ts +24 -0
  112. package/dist/ui/selection/screen-mirror.js +35 -0
  113. package/dist/ui/selection/selection-controller.d.ts +88 -0
  114. package/dist/ui/selection/selection-controller.js +282 -0
  115. package/dist/ui/selection/selection.d.ts +90 -0
  116. package/dist/ui/selection/selection.js +223 -0
  117. package/dist/ui/stdout-frame-writer.d.ts +33 -0
  118. package/dist/ui/stdout-frame-writer.js +15 -0
  119. package/dist/ui/theme.d.ts +11 -0
  120. package/dist/ui/theme.js +2 -0
  121. package/dist/ui/transcript-text.js +23 -0
  122. package/dist/ui/use-startup-notices.d.ts +62 -5
  123. package/dist/ui/use-startup-notices.js +81 -17
  124. package/dist/ui/use-wheel-routing.js +8 -0
  125. package/dist/ui/win-vt-force.d.ts +136 -0
  126. package/dist/ui/win-vt-force.js +237 -0
  127. package/package.json +4 -2
@@ -11,6 +11,7 @@
11
11
  * synthesizes a generic error notice (a swallowed throw is never a no-op).
12
12
  */
13
13
  import process from 'node:process';
14
+ import { occupiedTokens } from '../compaction/pressure.js';
14
15
  import { computeCost } from './usage.js';
15
16
  import { ENTRY_LIMITS, appendBounded, entryRetain, trimEntries } from './entry-limits.js';
16
17
  /** Default auto-dismiss window for a toast. */
@@ -46,11 +47,14 @@ export function initialViewState(seed = {}) {
46
47
  todos: null,
47
48
  retry: null,
48
49
  fast: null,
50
+ compaction: null,
51
+ contextTokensEstimated: false,
49
52
  seq: 0,
50
53
  teamEntryId: undefined,
51
54
  todoEntryId: undefined,
52
55
  retryEntryId: undefined,
53
56
  fastEntryId: undefined,
57
+ compactionEntryId: undefined,
54
58
  streamingId: undefined,
55
59
  turnProduced: false,
56
60
  errorNoticed: false,
@@ -223,6 +227,17 @@ function reduceStreamEvent(se) {
223
227
  return [];
224
228
  }
225
229
  }
230
+ /**
231
+ * The opening words of the `context_overflow` banner.
232
+ *
233
+ * A SHARED CONSTANT RATHER THAN TWO LITERALS, because `compactionStart` matches
234
+ * on it to rewrite the notice a successful reactive recovery would otherwise
235
+ * leave behind (P1-4). Two spellings that must agree, with nothing checking that
236
+ * they do, is how that rewrite silently stops matching — at which point the red
237
+ * "start over" banner comes back and only on the recovery path, which is the one
238
+ * place nobody re-tests.
239
+ */
240
+ export const CONTEXT_OVERFLOW_NOTICE_PREFIX = 'Context length exceeded';
226
241
  /** Turn a provider error into a friendly banner, mapping LLMError.errorType. */
227
242
  export function formatStreamError(error) {
228
243
  const errorType = error.errorType;
@@ -235,7 +250,13 @@ export function formatStreamError(error) {
235
250
  case 'overloaded':
236
251
  return `Provider overloaded - please retry shortly. (${base})`;
237
252
  case 'context_overflow':
238
- return `Context length exceeded - start a new conversation with /reset. (${base})`;
253
+ // `/compact` IS NAMED FIRST, and adding it is the second half of P1-4's
254
+ // fix. This remedy list predates context compaction and was, until it
255
+ // existed, complete: throwing the conversation away really was the only
256
+ // exit. It is not any more, and a banner that offers only `/reset` now
257
+ // tells the user to discard a session the very next line of code may be
258
+ // about to save.
259
+ return `${CONTEXT_OVERFLOW_NOTICE_PREFIX} - try /compact, or start over with /reset. (${base})`;
239
260
  case 'network_error':
240
261
  return `Network error reaching the provider. (${base})`;
241
262
  case 'timeout':
@@ -295,6 +316,11 @@ function appendEntry(state, entries, entry) {
295
316
  // `fastEntryId` IS PART OF THIS LIST for the reason the note above gives:
296
317
  // a fifth live card left out of it is a card I-L1-1 does not cover.
297
318
  state.fastEntryId,
319
+ // And `compactionEntryId` is the sixth. Same reason, and it is the one
320
+ // whose absence would be hardest to notice: a compaction card is rare, so
321
+ // a `mapEntry` that silently stopped matching would be found by a user
322
+ // rather than by a test.
323
+ state.compactionEntryId,
298
324
  ].filter((id) => typeof id === 'string');
299
325
  for (const id of live) {
300
326
  if (!trimmed.entries.some((e) => e.id === id)) {
@@ -584,7 +610,17 @@ export function viewReducer(state, action) {
584
610
  ...state,
585
611
  streamingId: undefined,
586
612
  turnProduced: true,
587
- contextTokens: action.usage.inputTokens + action.usage.outputTokens,
613
+ // ONE FUNCTION, SHARED WITH THE COMPACTION TRIGGER (§3.4.1 / R-11 /
614
+ // AC-3). The old expression — `inputTokens + outputTokens` — silently
615
+ // UNDER-reports for anyone behind a caching gateway, because Anthropic
616
+ // reports `input_tokens` EXCLUDING cached tokens and puts them in two
617
+ // separate fields. That was a live defect in the number this gauge shows;
618
+ // it becomes a much worse one the moment a trigger fires on a different
619
+ // number from the one the user is watching.
620
+ contextTokens: occupiedTokens(action.usage),
621
+ // A MEASUREMENT SUPERSEDES A DERIVATION (§6.2). `compactionEnd` writes an
622
+ // estimate so the bar falls immediately; this is where it stops being one.
623
+ contextTokensEstimated: false,
588
624
  usageTotal: {
589
625
  inputTokens: state.usageTotal.inputTokens + action.usage.inputTokens,
590
626
  outputTokens: state.usageTotal.outputTokens + action.usage.outputTokens,
@@ -713,6 +749,12 @@ export function viewReducer(state, action) {
713
749
  // holds, it is a projection of an entry that has just been erased.
714
750
  retryEntryId: undefined,
715
751
  retry: null,
752
+ // Same reasoning again for the compaction card: the entry is gone, so the
753
+ // id must go with it or `compactionEnd` would `mapEntry` an id that no
754
+ // longer exists. `compaction` STAYS — unlike `retry` it is not a
755
+ // projection of an entry, it is the live state of a subsystem that is
756
+ // still running, and `/clear` clears the screen rather than the session.
757
+ compactionEntryId: undefined,
716
758
  };
717
759
  case 'resetConversation':
718
760
  return {
@@ -731,6 +773,11 @@ export function viewReducer(state, action) {
731
773
  todoEntryId: undefined,
732
774
  retryEntryId: undefined,
733
775
  retry: null,
776
+ compactionEntryId: undefined,
777
+ // `contextTokens` is zeroed above, so the flag that says it is a
778
+ // DERIVATION has to go with it — otherwise a fresh conversation renders
779
+ // `~0%` and the tilde is a lie about a number that is simply zero.
780
+ contextTokensEstimated: false,
734
781
  };
735
782
  case 'setOverlay':
736
783
  return { ...state, overlay: action.overlay };
@@ -957,6 +1004,158 @@ export function viewReducer(state, action) {
957
1004
  };
958
1005
  case 'fastTier':
959
1006
  return { ...state, fast: action.snapshot };
1007
+ // --- Context compaction (context-auto-compaction §5.5) ----------------
1008
+ case 'compactionStart': {
1009
+ const { id, seq } = nextId(state);
1010
+ let entries = state.entries;
1011
+ // RESPONSIBILITY 1 — SEAL ANY LIVE `streamingId` ENTRY, defensively.
1012
+ // C-8's failure mode, and reachable here: `provider.ts` records that
1013
+ // Anthropic can deliver an error IN-STREAM, after the connection is open
1014
+ // and therefore after text deltas have already been emitted. An entry that
1015
+ // never settles pins `Transcript`'s monotonic boundary and re-renders the
1016
+ // tail forever.
1017
+ if (state.streamingId) {
1018
+ entries = mapEntry(entries, state.streamingId, (e) => e.kind === 'assistant' ? { ...e, streaming: false, ...sealThinkingMs(e) } : e);
1019
+ }
1020
+ // RESPONSIBILITY 2 — REWRITE A TRAILING `context_overflow` ERROR NOTICE
1021
+ // (P1-4). `agent-loop.ts` emits `message_update` for EVERY stream event
1022
+ // before the `error` branch throws, and `reduceStreamEvent`'s `case 'error'`
1023
+ // turns that into a `level: 'error'` notice reading "Context length
1024
+ // exceeded". So the HAPPY PATH of the reactive recovery would otherwise end
1025
+ // with a red banner telling the user to throw away the session we are one
1026
+ // frame from saving.
1027
+ //
1028
+ // IT REWRITES RATHER THAN REMOVES, because `<Static>` cannot un-print an
1029
+ // entry it has already drawn — and it matches ONLY when that notice is the
1030
+ // TAIL entry, so it can never touch an older, genuine error. If the
1031
+ // recovery then fails, the ladder's own notice or the re-thrown 400 is what
1032
+ // the user is left with, so neither direction lies.
1033
+ if (action.trigger === 'overflow') {
1034
+ const tail = entries[entries.length - 1];
1035
+ if (tail &&
1036
+ tail.kind === 'notice' &&
1037
+ tail.level === 'error' &&
1038
+ tail.text.startsWith(CONTEXT_OVERFLOW_NOTICE_PREFIX)) {
1039
+ entries = mapEntry(entries, tail.id, (e) => e.kind === 'notice'
1040
+ ? { ...e, level: 'info', text: 'Context window exceeded - compacting and retrying.' }
1041
+ : e);
1042
+ }
1043
+ }
1044
+ const entry = {
1045
+ id,
1046
+ kind: 'compaction',
1047
+ index: action.index,
1048
+ trigger: action.trigger,
1049
+ mode: 'none',
1050
+ applied: false,
1051
+ messagesBefore: 0,
1052
+ messagesAfter: 0,
1053
+ tokensBefore: 0,
1054
+ tokensAfter: 0,
1055
+ model: action.model,
1056
+ startedAt: Date.now(),
1057
+ live: true,
1058
+ };
1059
+ return {
1060
+ ...state,
1061
+ seq,
1062
+ ...appendEntry(state, entries, entry),
1063
+ compactionEntryId: id,
1064
+ streamingId: undefined,
1065
+ };
1066
+ }
1067
+ case 'compactionEnd': {
1068
+ const { record } = action;
1069
+ const settle = (e) => e.kind === 'compaction'
1070
+ ? {
1071
+ ...e,
1072
+ index: record.index,
1073
+ trigger: record.trigger,
1074
+ mode: record.mode,
1075
+ applied: record.applied,
1076
+ ...(record.reason !== undefined ? { reason: record.reason } : {}),
1077
+ messagesBefore: record.messagesBefore,
1078
+ messagesAfter: record.messagesAfter,
1079
+ tokensBefore: record.tokensBefore,
1080
+ tokensAfter: record.tokensAfter,
1081
+ ...(record.summary !== undefined ? { summary: record.summary } : {}),
1082
+ ...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
1083
+ model: record.model,
1084
+ durationMs: record.durationMs,
1085
+ // SETTLED. `Transcript`'s boundary is monotonic, so a card that
1086
+ // never settles is re-rendered on every frame for the rest of the
1087
+ // session (C-8).
1088
+ live: false,
1089
+ }
1090
+ : e;
1091
+ // THE SECOND BRANCH IS NOT DEFENSIVE PADDING, exactly as `fastEnd`'s is
1092
+ // not: the IDLE `/compact` path settles a record for a card this session
1093
+ // may never have opened, and "it did nothing" must never be the observable
1094
+ // outcome (R-8 / §6.4).
1095
+ if (state.compactionEntryId) {
1096
+ return {
1097
+ ...state,
1098
+ entries: mapEntry(state.entries, state.compactionEntryId, settle),
1099
+ compactionEntryId: undefined,
1100
+ };
1101
+ }
1102
+ const { id, seq } = nextId(state);
1103
+ const entry = {
1104
+ id,
1105
+ kind: 'compaction',
1106
+ index: record.index,
1107
+ trigger: record.trigger,
1108
+ mode: record.mode,
1109
+ applied: record.applied,
1110
+ ...(record.reason !== undefined ? { reason: record.reason } : {}),
1111
+ messagesBefore: record.messagesBefore,
1112
+ messagesAfter: record.messagesAfter,
1113
+ tokensBefore: record.tokensBefore,
1114
+ tokensAfter: record.tokensAfter,
1115
+ ...(record.summary !== undefined ? { summary: record.summary } : {}),
1116
+ ...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
1117
+ model: record.model,
1118
+ durationMs: record.durationMs,
1119
+ live: false,
1120
+ };
1121
+ return { ...state, seq, ...appendEntry(state, state.entries, entry) };
1122
+ }
1123
+ case 'compactionUsage':
1124
+ // `usageTotal` AND NOTHING ELSE, exactly as `teamUsage` and `fastUsage`. In
1125
+ // particular NOT `contextTokens`: that gauge shows the LEAD's context
1126
+ // occupancy against the lead model's window, and a summarization is a
1127
+ // separate conversation with a possibly different model entirely.
1128
+ return {
1129
+ ...state,
1130
+ usageTotal: {
1131
+ inputTokens: state.usageTotal.inputTokens + action.usage.inputTokens,
1132
+ outputTokens: state.usageTotal.outputTokens + action.usage.outputTokens,
1133
+ costUsd: state.usageTotal.costUsd + action.costDelta,
1134
+ },
1135
+ };
1136
+ case 'compactionSnapshot':
1137
+ return { ...state, compaction: action.snapshot };
1138
+ /**
1139
+ * THE GAUGE MUST FALL IMMEDIATELY (§6.2 / AC-5).
1140
+ *
1141
+ * `contextTokens` is otherwise only written at `turnEnd`, so a compaction
1142
+ * that took the session from 92 % to 24 % would leave the bar at 92 % until
1143
+ * the next turn completes — and the user would reasonably conclude that
1144
+ * nothing happened. This is a small change and it is the difference between
1145
+ * the feature feeling real and feeling broken.
1146
+ *
1147
+ * The value arrives ALREADY CALIBRATED from `App` (§6.2 / P1-11): it replaces
1148
+ * a MEASUREMENT that included the tool schemas, and core's raw estimator
1149
+ * never counts them, so writing the raw figure would make the bar fall too
1150
+ * far and then visibly climb again on the next `turn_end` for no reason the
1151
+ * user can see.
1152
+ */
1153
+ case 'contextTokensEstimated':
1154
+ return {
1155
+ ...state,
1156
+ contextTokens: Math.max(0, Math.round(action.tokens)),
1157
+ contextTokensEstimated: true,
1158
+ };
960
1159
  // --- API retry (llm-api-retry-backoff §6.4) ---------------------------
961
1160
  case 'retryScheduled': {
962
1161
  const snapshot = {