@aragon-agent/cli 0.6.31 → 0.6.32

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 (91) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +2957 -2826
  3. package/dist/agent/controller.d.ts +46 -0
  4. package/dist/agent/controller.js +175 -7
  5. package/dist/agent/job-notices.d.ts +143 -0
  6. package/dist/agent/job-notices.js +182 -0
  7. package/dist/agent/reducer.d.ts +21 -1
  8. package/dist/agent/reducer.js +7 -0
  9. package/dist/agent/system-prompt.d.ts +12 -0
  10. package/dist/agent/system-prompt.js +2 -0
  11. package/dist/cli.js +232 -8
  12. package/dist/commands/builtins.js +23 -8
  13. package/dist/commands/perf.d.ts +54 -1
  14. package/dist/commands/perf.js +28 -4
  15. package/dist/config/cli-commands.d.ts +27 -0
  16. package/dist/config/cli-commands.js +54 -1
  17. package/dist/config/env.js +38 -0
  18. package/dist/config/load.js +50 -1
  19. package/dist/config/schema.d.ts +73 -0
  20. package/dist/config/schema.js +34 -0
  21. package/dist/config/store.js +14 -1
  22. package/dist/exec/events.d.ts +29 -3
  23. package/dist/exec/events.js +3 -12
  24. package/dist/exec/runner.d.ts +24 -0
  25. package/dist/exec/runner.js +79 -0
  26. package/dist/input/cursor-report.d.ts +61 -0
  27. package/dist/input/cursor-report.js +97 -0
  28. package/dist/input/stdin-filter.d.ts +22 -0
  29. package/dist/input/stdin-filter.js +48 -3
  30. package/dist/proc/limits.d.ts +76 -2
  31. package/dist/proc/limits.js +83 -2
  32. package/dist/proc/log-mirror.d.ts +59 -0
  33. package/dist/proc/log-mirror.js +203 -0
  34. package/dist/proc/log-ring.d.ts +22 -0
  35. package/dist/proc/log-ring.js +20 -1
  36. package/dist/proc/notices.d.ts +57 -0
  37. package/dist/proc/notices.js +101 -0
  38. package/dist/proc/progress.d.ts +40 -0
  39. package/dist/proc/progress.js +135 -0
  40. package/dist/proc/prompt.d.ts +24 -2
  41. package/dist/proc/prompt.js +51 -2
  42. package/dist/proc/supervisor.d.ts +35 -2
  43. package/dist/proc/supervisor.js +142 -43
  44. package/dist/proc/types.d.ts +89 -1
  45. package/dist/proc/wait.d.ts +57 -0
  46. package/dist/proc/wait.js +133 -0
  47. package/dist/session/persist.js +3 -1
  48. package/dist/session/validate-session.js +17 -0
  49. package/dist/tools/bash-tool.d.ts +12 -1
  50. package/dist/tools/bash-tool.js +100 -13
  51. package/dist/tools/index.d.ts +26 -2
  52. package/dist/tools/index.js +43 -2
  53. package/dist/tools/job-tools.d.ts +54 -0
  54. package/dist/tools/job-tools.js +329 -0
  55. package/dist/tools/proc-tools.d.ts +1 -1
  56. package/dist/tools/proc-tools.js +49 -17
  57. package/dist/ui/ActivityLine.js +7 -0
  58. package/dist/ui/App.js +64 -6
  59. package/dist/ui/ScrollbackShell.d.ts +30 -4
  60. package/dist/ui/ScrollbackShell.js +8 -3
  61. package/dist/ui/StatusBar.d.ts +5 -0
  62. package/dist/ui/StatusBar.js +1 -0
  63. package/dist/ui/Transcript.d.ts +14 -0
  64. package/dist/ui/Transcript.js +14 -2
  65. package/dist/ui/entries/ServiceCard.d.ts +17 -1
  66. package/dist/ui/entries/ServiceCard.js +39 -4
  67. package/dist/ui/layout/status-layout.d.ts +13 -1
  68. package/dist/ui/layout/status-layout.js +10 -0
  69. package/dist/ui/layout/virtual-window.js +14 -1
  70. package/dist/ui/run-status-row.d.ts +12 -0
  71. package/dist/ui/screen.d.ts +17 -4
  72. package/dist/ui/screen.js +14 -6
  73. package/dist/ui/scrollback/LiveTail.d.ts +64 -0
  74. package/dist/ui/scrollback/LiveTail.js +27 -0
  75. package/dist/ui/scrollback/band.d.ts +105 -0
  76. package/dist/ui/scrollback/band.js +109 -0
  77. package/dist/ui/scrollback/budget.d.ts +32 -3
  78. package/dist/ui/scrollback/budget.js +43 -6
  79. package/dist/ui/scrollback/canvas.d.ts +99 -0
  80. package/dist/ui/scrollback/canvas.js +113 -0
  81. package/dist/ui/scrollback/cursor-probe.d.ts +71 -0
  82. package/dist/ui/scrollback/cursor-probe.js +133 -0
  83. package/dist/ui/scrollback/limits.d.ts +49 -0
  84. package/dist/ui/scrollback/limits.js +49 -0
  85. package/dist/ui/scrollback/scrollback-writer.d.ts +104 -16
  86. package/dist/ui/scrollback/scrollback-writer.js +368 -82
  87. package/dist/ui/scrollback/use-scrollback-view.d.ts +11 -0
  88. package/dist/ui/scrollback/use-scrollback-view.js +37 -6
  89. package/dist/ui/scrollback/writer-shapes.d.ts +68 -2
  90. package/dist/ui/scrollback/writer-shapes.js +53 -8
  91. package/package.json +3 -3
@@ -64,7 +64,7 @@ function pruneEntryIds(printed, entries) {
64
64
  printed.delete(id);
65
65
  }
66
66
  export function useScrollbackView(args) {
67
- const { enabled, bridge, entries, runActive, theme, caps, density, thinkingVisible, reducedMotion, rows, cols, headerNode, openerNode, overlayOpen, draftRows, popupRows, statusExpanded, forceInactive = false, } = args;
67
+ const { enabled, bridge, entries, runActive, theme, caps, density, thinkingVisible, reducedMotion, rows, cols, headerNode, openerNode, overlayOpen, draftRows, popupRows, statusExpanded, forceInactive = false, itemCount, panelEnabled, teamRowsNatural, } = args;
68
68
  const contentCols = Math.max(1, cols - 1);
69
69
  const startedRef = useRef(false);
70
70
  // Everything the asynchronous pass reads travels here, refreshed on every render.
@@ -86,6 +86,12 @@ export function useScrollbackView(args) {
86
86
  const cursorRef = useRef(null);
87
87
  const pendingReset = useRef(null);
88
88
  const heldTailRows = useRef(0);
89
+ // Band hysteresis (release-gate-and-native-panels §6.7). Same lifecycle and same
90
+ // reason as `heldTailRows`: the writer re-lays out the canvas on every batch, so a
91
+ // band that shrank for two frames would make everything above the composer jump.
92
+ const heldRailRows = useRef(0);
93
+ const heldTeamRows = useRef(0);
94
+ const heldGeometry = useRef({ rows, cols });
89
95
  const pumpQueued = useRef(false);
90
96
  const mountedRef = useRef(true);
91
97
  const ensureRenderer = useCallback(() => {
@@ -227,19 +233,44 @@ export function useScrollbackView(args) {
227
233
  : 0;
228
234
  // D11 · the preview height only grows while anything is unsettled, then resets.
229
235
  heldTailRows.current = unsettled.length === 0 ? 0 : Math.max(heldTailRows.current, wanted);
236
+ // The band's hysteresis has exactly THREE reset points and no others: the plan
237
+ // emptied, the dispatch finished, or the geometry changed (a new size is a new
238
+ // measurement - the same treatment `App` gives `guardTripped`).
239
+ if (heldGeometry.current.rows !== rows || heldGeometry.current.cols !== cols) {
240
+ heldGeometry.current = { rows, cols };
241
+ heldRailRows.current = 0;
242
+ heldTeamRows.current = 0;
243
+ }
244
+ if (itemCount === 0)
245
+ heldRailRows.current = 0;
246
+ if (teamRowsNatural === 0)
247
+ heldTeamRows.current = 0;
230
248
  const budget = useMemo(() => enabled
231
249
  ? buildScrollbackBudget({
232
250
  rows, cols, draftRows, popupRows, statusExpanded, overlayOpen,
233
251
  liveTailWanted: heldTailRows.current, forceInactive,
252
+ band: {
253
+ itemCount, panelEnabled, teamRowsNatural,
254
+ heldRailRows: heldRailRows.current, heldTeamRows: heldTeamRows.current,
255
+ },
234
256
  })
235
257
  : null,
236
- // `heldTailRows.current` is read at memo time; `wanted` is what moves it.
258
+ // `heldTailRows.current` and the two band refs are read at memo time; `wanted`,
259
+ // `itemCount` and `teamRowsNatural` are what move them. ONE memo, deliberately:
260
+ // a second one depending on the first would render the previous frame's band.
237
261
  // eslint-disable-next-line react-hooks/exhaustive-deps
238
262
  [enabled, rows, cols, draftRows, popupRows, statusExpanded, overlayOpen, wanted,
239
- unsettled.length, forceInactive]);
263
+ unsettled.length, forceInactive, itemCount, panelEnabled, teamRowsNatural]);
264
+ if (budget) {
265
+ // Monotone memory, fed back into the memo above on the next frame.
266
+ heldRailRows.current = budget.band.heldRailRows;
267
+ heldTeamRows.current = budget.band.heldTeamRows;
268
+ }
269
+ // Print keeps `contentCols`; only the live preview gives up the rail's columns (I-5).
270
+ const previewCols = budget ? budget.previewCols : contentCols;
240
271
  const liveTailLines = useMemo(() => enabled && budget && budget.liveTailRows > 0
241
- ? buildLivePreview({ unsettled, cols: contentCols, rows: budget.liveTailRows, thinkingVisible })
242
- : [], [enabled, budget, unsettled, contentCols, thinkingVisible]);
272
+ ? buildLivePreview({ unsettled, cols: previewCols, rows: budget.liveTailRows, thinkingVisible })
273
+ : [], [enabled, budget, unsettled, previewCols, thinkingVisible]);
243
274
  const reprintExpanded = useCallback((entryId) => {
244
275
  if (!enabled || !bridge)
245
276
  return false;
@@ -259,5 +290,5 @@ export function useScrollbackView(args) {
259
290
  return;
260
291
  pendingReset.current = { kind, captured: latest.current.entries };
261
292
  }, [enabled]);
262
- return { budget, liveTailLines, reprintExpanded, settleAll, notifyReset };
293
+ return { budget, liveTailLines, unsettledEntries: unsettled, reprintExpanded, settleAll, notifyReset };
263
294
  }
@@ -21,6 +21,60 @@ export declare function seedBytes(input: {
21
21
  /** After a foreign write the cursor may sit mid-line; start on a new one (RV-11). */
22
22
  reseed: boolean;
23
23
  }): string;
24
+ /**
25
+ * CUD(n): move down n rows WITHOUT writing a single cell.
26
+ *
27
+ * This is how the canvas spends its `gap` (D3). A blank row that is never written
28
+ * cannot be pushed into the scrollback by a later overflow, which is the whole
29
+ * reason the gap is not a run of `\r\n` (I-B3). The sequence is already whitelisted
30
+ * (`diffBytes` uses it to step between changed rows); this is the same CUD used to
31
+ * skip empty space instead.
32
+ */
33
+ export declare function skipRows(rows: number): string;
34
+ /**
35
+ * First canvas batch (or the first one after a reset): the screen below the cursor
36
+ * is assumed empty, so there is nothing to erase — only `[gap][content][region]` to
37
+ * write, ending with the cursor parked on the last row.
38
+ */
39
+ export declare function canvasSeedBytes(input: {
40
+ /**
41
+ * Rows to descend from the cursor to the canvas top, when the canvas is shorter
42
+ * than the space measured below the cursor.
43
+ *
44
+ * Non-zero only on a terminal taller than the start-of-canvas cap: the canvas then
45
+ * begins BELOW the cursor, and a seed that started writing at the cursor would park
46
+ * the region low and leave the top of the canvas empty for the whole session.
47
+ */
48
+ downToTop: number;
49
+ /** Blank rows to skip before the first content row. */
50
+ gap: number;
51
+ /** Content rows, already clipped, oldest first. */
52
+ held: readonly string[];
53
+ /** The live region. */
54
+ lines: readonly string[];
55
+ sync: boolean;
56
+ /** After a foreign write the cursor may sit mid-line; start on a new one. */
57
+ reseed: boolean;
58
+ }): string;
59
+ /**
60
+ * Redraw the whole canvas: erase the previous one, then write `[gap][content][region]`.
61
+ *
62
+ * ORDER IS SEMANTICS: `ED0`, then the gap, then the content, then the region. Moving
63
+ * the gap after the content puts the blank space back between the reply and the
64
+ * composer, which is the exact defect this feature fixes (M1 in the spec).
65
+ *
66
+ * `held` is the FULL content sequence, not the part that will still be on screen: the
67
+ * rows that do not fit are supposed to leave through the bottom, and the terminal can
68
+ * only scroll away what it was given.
69
+ */
70
+ export declare function canvasBytes(input: {
71
+ /** Previous canvas height: the erase distance. */
72
+ eraseUp: number;
73
+ gap: number;
74
+ held: readonly string[];
75
+ lines: readonly string[];
76
+ sync: boolean;
77
+ }): string;
24
78
  /** Erase exactly the previous live region, print static rows, redraw the region. */
25
79
  export declare function fullBytes(input: {
26
80
  lines: readonly string[];
@@ -29,6 +83,20 @@ export declare function fullBytes(input: {
29
83
  eraseUp: number;
30
84
  sync: boolean;
31
85
  }): string;
86
+ /**
87
+ * Fill the screen down to the LAST row before the first batch (D4 rung 2 / §4.3).
88
+ *
89
+ * This is the fallback for a terminal that never answers `ESC[6n`: from any row, a
90
+ * run of `rows - 1` line feeds is guaranteed to leave the cursor on the bottom row
91
+ * (extra feeds only scroll, which is why the count is the caller's business). It is
92
+ * written ONCE, before anything else, so the rows it scrolls away are the host's old
93
+ * output — which is still in the scrollback — and never this session's content.
94
+ *
95
+ * The argument is the NUMBER OF LINE FEEDS, not the terminal height: `cli.tsx` passes
96
+ * `rows - 1`. Naming it after the height instead made the caller subtract twice and
97
+ * left the cursor one row short of the bottom.
98
+ */
99
+ export declare function fillBytes(count: number): string;
32
100
  /** Same height, no static rows: rewrite only the rows that changed. `''` if none did. */
33
101
  export declare function diffBytes(input: {
34
102
  lines: readonly string[];
@@ -57,5 +125,3 @@ export declare function clipLine(line: string, maxCols: number): {
57
125
  text: string;
58
126
  clipped: boolean;
59
127
  };
60
- /** Rows the previous region occupies after the terminal re-flowed it to `cols` (§4.10). */
61
- export declare function estimateReflowRows(lines: readonly string[], cols: number): number;
@@ -32,10 +32,63 @@ export function seedBytes(input) {
32
32
  const lead = input.reseed ? '\r\n' : '';
33
33
  return wrapSync(lead + '\r' + SGR0 + rows(input.statics) + rows(input.lines), input.sync);
34
34
  }
35
+ /**
36
+ * CUD(n): move down n rows WITHOUT writing a single cell.
37
+ *
38
+ * This is how the canvas spends its `gap` (D3). A blank row that is never written
39
+ * cannot be pushed into the scrollback by a later overflow, which is the whole
40
+ * reason the gap is not a run of `\r\n` (I-B3). The sequence is already whitelisted
41
+ * (`diffBytes` uses it to step between changed rows); this is the same CUD used to
42
+ * skip empty space instead.
43
+ */
44
+ export function skipRows(rows) {
45
+ return down(Math.max(0, Math.floor(Number.isFinite(rows) ? rows : 0)));
46
+ }
47
+ /**
48
+ * First canvas batch (or the first one after a reset): the screen below the cursor
49
+ * is assumed empty, so there is nothing to erase — only `[gap][content][region]` to
50
+ * write, ending with the cursor parked on the last row.
51
+ */
52
+ export function canvasSeedBytes(input) {
53
+ const lead = input.reseed ? '\r\n' : '';
54
+ const skip = skipRows(input.downToTop) + skipRows(input.gap);
55
+ return wrapSync(lead + '\r' + SGR0 + skip + rows(input.held) + rows(input.lines), input.sync);
56
+ }
57
+ /**
58
+ * Redraw the whole canvas: erase the previous one, then write `[gap][content][region]`.
59
+ *
60
+ * ORDER IS SEMANTICS: `ED0`, then the gap, then the content, then the region. Moving
61
+ * the gap after the content puts the blank space back between the reply and the
62
+ * composer, which is the exact defect this feature fixes (M1 in the spec).
63
+ *
64
+ * `held` is the FULL content sequence, not the part that will still be on screen: the
65
+ * rows that do not fit are supposed to leave through the bottom, and the terminal can
66
+ * only scroll away what it was given.
67
+ */
68
+ export function canvasBytes(input) {
69
+ return wrapSync(up(input.eraseUp) + '\r' + SGR0 + ED0 + skipRows(input.gap) +
70
+ rows(input.held) + rows(input.lines), input.sync);
71
+ }
35
72
  /** Erase exactly the previous live region, print static rows, redraw the region. */
36
73
  export function fullBytes(input) {
37
74
  return wrapSync(up(input.eraseUp) + '\r' + SGR0 + ED0 + rows(input.statics) + rows(input.lines), input.sync);
38
75
  }
76
+ /**
77
+ * Fill the screen down to the LAST row before the first batch (D4 rung 2 / §4.3).
78
+ *
79
+ * This is the fallback for a terminal that never answers `ESC[6n`: from any row, a
80
+ * run of `rows - 1` line feeds is guaranteed to leave the cursor on the bottom row
81
+ * (extra feeds only scroll, which is why the count is the caller's business). It is
82
+ * written ONCE, before anything else, so the rows it scrolls away are the host's old
83
+ * output — which is still in the scrollback — and never this session's content.
84
+ *
85
+ * The argument is the NUMBER OF LINE FEEDS, not the terminal height: `cli.tsx` passes
86
+ * `rows - 1`. Naming it after the height instead made the caller subtract twice and
87
+ * left the cursor one row short of the bottom.
88
+ */
89
+ export function fillBytes(count) {
90
+ return '\r\n'.repeat(Math.max(0, Math.floor(count)));
91
+ }
39
92
  /** Same height, no static rows: rewrite only the rows that changed. `''` if none did. */
40
93
  export function diffBytes(input) {
41
94
  const { lines, prev } = input;
@@ -104,11 +157,3 @@ export function clipLine(line, maxCols) {
104
157
  }
105
158
  return { text: out, clipped: false };
106
159
  }
107
- /** Rows the previous region occupies after the terminal re-flowed it to `cols` (§4.10). */
108
- export function estimateReflowRows(lines, cols) {
109
- const width = Math.max(1, cols);
110
- let total = 0;
111
- for (const line of lines)
112
- total += Math.max(1, Math.ceil(stringWidth(stripAnsi(line)) / width));
113
- return total;
114
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aragon-agent/cli",
3
- "version": "0.6.31",
3
+ "version": "0.6.32",
4
4
  "description": "AragonAgent CLI — a Claude-Code / Codex-style interactive terminal UI (TUI) on top of the @aragon-agent/core engine. Installable and runnable via npm / npx.",
5
5
  "license": "MIT",
6
6
  "author": "AragonAgent contributors",
@@ -56,7 +56,7 @@
56
56
  "prepublishOnly": "npm run build"
57
57
  },
58
58
  "dependencies": {
59
- "@aragon-agent/core": "^0.2.43",
59
+ "@aragon-agent/core": "^0.2.44",
60
60
  "chalk": "^5.6.2",
61
61
  "cli-highlight": "^2.0.0",
62
62
  "commander": "^12.1.0",
@@ -76,6 +76,6 @@
76
76
  "@types/react": "^18.3.0",
77
77
  "ink-testing-library": "^4.0.0",
78
78
  "typescript": "^5.9.3",
79
- "vitest": "^4.1.0"
79
+ "vitest": "4.1.9"
80
80
  }
81
81
  }