@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.
Files changed (94) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +95 -2
  3. package/dist/agent/controller.d.ts +119 -0
  4. package/dist/agent/controller.js +275 -1
  5. package/dist/agent/reducer.d.ts +69 -0
  6. package/dist/agent/reducer.js +100 -0
  7. package/dist/agent/system-prompt.d.ts +14 -0
  8. package/dist/agent/system-prompt.js +2 -0
  9. package/dist/cli.js +84 -22
  10. package/dist/commands/builtins.js +92 -1
  11. package/dist/compaction/child.js +5 -0
  12. package/dist/compaction/command.js +14 -1
  13. package/dist/compaction/compactor.d.ts +34 -0
  14. package/dist/compaction/compactor.js +158 -37
  15. package/dist/compaction/types.d.ts +36 -0
  16. package/dist/compaction/wiring.d.ts +9 -0
  17. package/dist/compaction/wiring.js +22 -1
  18. package/dist/config/env.d.ts +1 -1
  19. package/dist/config/env.js +48 -1
  20. package/dist/config/load.d.ts +11 -0
  21. package/dist/config/load.js +39 -1
  22. package/dist/config/schema.d.ts +85 -0
  23. package/dist/config/schema.js +33 -0
  24. package/dist/config/store.js +21 -1
  25. package/dist/input/limits.d.ts +72 -0
  26. package/dist/input/limits.js +63 -0
  27. package/dist/input/paste-parse.d.ts +71 -0
  28. package/dist/input/paste-parse.js +95 -0
  29. package/dist/input/stdin-filter.d.ts +81 -0
  30. package/dist/input/stdin-filter.js +379 -0
  31. package/dist/logging/install.d.ts +2 -0
  32. package/dist/logging/install.js +53 -1
  33. package/dist/proc/classify.d.ts +52 -0
  34. package/dist/proc/classify.js +143 -0
  35. package/dist/proc/kill-tree.d.ts +42 -0
  36. package/dist/proc/kill-tree.js +119 -0
  37. package/dist/proc/limits.d.ts +84 -0
  38. package/dist/proc/limits.js +84 -0
  39. package/dist/proc/log-ring.d.ts +68 -0
  40. package/dist/proc/log-ring.js +102 -0
  41. package/dist/proc/prompt.d.ts +24 -0
  42. package/dist/proc/prompt.js +49 -0
  43. package/dist/proc/readiness.d.ts +102 -0
  44. package/dist/proc/readiness.js +219 -0
  45. package/dist/proc/supervisor.d.ts +103 -0
  46. package/dist/proc/supervisor.js +465 -0
  47. package/dist/proc/types.d.ts +175 -0
  48. package/dist/proc/types.js +15 -0
  49. package/dist/session/persist.js +36 -1
  50. package/dist/tools/bash-tool.d.ts +51 -2
  51. package/dist/tools/bash-tool.js +321 -51
  52. package/dist/tools/fs-tools.d.ts +17 -0
  53. package/dist/tools/index.d.ts +55 -2
  54. package/dist/tools/index.js +39 -1
  55. package/dist/tools/proc-tools.d.ts +25 -0
  56. package/dist/tools/proc-tools.js +102 -0
  57. package/dist/ui/App.d.ts +15 -2
  58. package/dist/ui/App.js +256 -7
  59. package/dist/ui/Composer.d.ts +54 -1
  60. package/dist/ui/Composer.js +50 -6
  61. package/dist/ui/PromptInput.d.ts +49 -3
  62. package/dist/ui/PromptInput.js +184 -52
  63. package/dist/ui/StatusBar.d.ts +19 -0
  64. package/dist/ui/StatusBar.js +5 -2
  65. package/dist/ui/Transcript.js +29 -1
  66. package/dist/ui/composer-limits.d.ts +31 -0
  67. package/dist/ui/composer-limits.js +50 -0
  68. package/dist/ui/composer-rows.d.ts +73 -0
  69. package/dist/ui/composer-rows.js +201 -0
  70. package/dist/ui/editor-reducer.d.ts +37 -0
  71. package/dist/ui/editor-reducer.js +88 -12
  72. package/dist/ui/entries/ServiceCard.d.ts +66 -0
  73. package/dist/ui/entries/ServiceCard.js +116 -0
  74. package/dist/ui/entries/UserEntry.d.ts +17 -3
  75. package/dist/ui/entries/UserEntry.js +34 -5
  76. package/dist/ui/layout/budget.d.ts +34 -6
  77. package/dist/ui/layout/budget.js +32 -7
  78. package/dist/ui/layout/virtual-window.js +51 -1
  79. package/dist/ui/overlays/HelpOverlay.js +3 -0
  80. package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
  81. package/dist/ui/overlays/QuestionOverlay.js +5 -1
  82. package/dist/ui/overlays/SettingsScreen.js +14 -1
  83. package/dist/ui/paste-frames.d.ts +87 -0
  84. package/dist/ui/paste-frames.js +112 -0
  85. package/dist/ui/paste-tokens.d.ts +90 -0
  86. package/dist/ui/paste-tokens.js +147 -0
  87. package/dist/ui/screen.d.ts +16 -0
  88. package/dist/ui/screen.js +45 -2
  89. package/dist/ui/selection/selection-controller.d.ts +1 -1
  90. package/dist/ui/transcript-text.js +49 -0
  91. package/dist/ui/use-wheel-routing.d.ts +1 -1
  92. package/package.json +2 -2
  93. package/dist/input/stdin-mouse-filter.d.ts +0 -47
  94. package/dist/input/stdin-mouse-filter.js +0 -153
@@ -12,6 +12,7 @@ import { type AgentTool, type ToolPolicyDecision, type ToolPolicyVerdict } from
12
12
  import type { AgentMode } from '../agent/agent-mode.js';
13
13
  import type { ToolPermission } from '../exec/permission.js';
14
14
  import type { FilePatch } from './patch.js';
15
+ import type { ProcSupervisorPort } from '../proc/types.js';
15
16
  export interface ConfirmRequest {
16
17
  tool: string;
17
18
  summary: string;
@@ -151,6 +152,58 @@ export interface BuiltinToolsOptions {
151
152
  * `--no-skills` is.
152
153
  */
153
154
  teamTools?: AgentTool[];
155
+ /**
156
+ * The process supervisor (background-service-supervision §3.5).
157
+ *
158
+ * THE TWO-INTERFACE RULE: it is declared on `ToolDeps` as well, and adding it
159
+ * to only one of the two type-checks and forwards nothing — `bash` never sees
160
+ * a supervisor and every long-running command keeps hanging, with nothing
161
+ * anywhere erroring. Both, or neither.
162
+ *
163
+ * The conditional spread below is what makes I-2 true at this layer: with
164
+ * background services off the controller passes no key, so no property is
165
+ * added and `deps` is the object it is today.
166
+ */
167
+ procs?: ProcSupervisorPort;
168
+ /**
169
+ * `bash_output` / `bash_kill`, appended verbatim.
170
+ *
171
+ * THROUGH THE FACTORY FOR THE REASON `teamTools` AND `todoTools` EACH RECORD
172
+ * IN CAPITALS (P0-2): `tools.test.ts::C7` asserts `HOST_TOOL_NAMES` equals
173
+ * what this factory PRODUCES, so appending these two in `AgentController`
174
+ * after the factory returned would turn C7 red with a message about two lists
175
+ * of names that says nothing about background services.
176
+ *
177
+ * An empty array (`bash.background: false`) leaves the tool list
178
+ * byte-identical to the pre-feature build — I-2, provable by object identity
179
+ * exactly as `--no-skills` and `--no-team` are.
180
+ */
181
+ procTools?: AgentTool[];
182
+ /**
183
+ * `cfg.bash.autoBackground`, READ AT CALL TIME.
184
+ *
185
+ * Never cached: `aragon config set bash.autoBackground false` takes effect in
186
+ * the running session, and a cached `true` would go on backgrounding commands
187
+ * the user just told it not to.
188
+ */
189
+ autoBackground?: () => boolean;
190
+ /** `cfg.bash.startupSettleMs`, read at call time for the same reason. */
191
+ startupSettleMs?: () => number;
192
+ /**
193
+ * Whether this session can render a service card and keep a service alive
194
+ * (D-15 / P1-8).
195
+ *
196
+ * FALSE UNDER `aragon exec` AND `-p`. It suppresses only the CLASSIFIER: an
197
+ * explicit `background: true` is still honoured, and its result then says the
198
+ * service dies with the run. Without it `aragon -p "npm run dev"` would
199
+ * background a server, return in `startupSettleMs`, and reap it at process
200
+ * exit with nothing anywhere saying so.
201
+ *
202
+ * It is the SAME SIGNAL `planTools` already uses to decide that no overlay can
203
+ * be rendered, so this costs one boolean rather than a new capability probe.
204
+ * Absent means interactive, which is what every existing caller is.
205
+ */
206
+ interactive?: boolean;
154
207
  /**
155
208
  * Tool names that skip the CEILING wrapper only (never the plan gate, never
156
209
  * confirmation).
@@ -199,7 +252,7 @@ export interface BuiltinToolsOptions {
199
252
  * is the whole of what this mechanism is worth, and saying so plainly beats
200
253
  * implying it covers more.
201
254
  */
202
- export declare const SKILL_TOOL_FLOOR: readonly ["read_file", "list_dir", "glob", "grep", "skill", "skill_find", "ask_user", "submit_plan", "task", "todo_write"];
255
+ export declare const SKILL_TOOL_FLOOR: readonly ["read_file", "list_dir", "glob", "grep", "skill", "skill_find", "ask_user", "submit_plan", "task", "todo_write", "bash_output"];
203
256
  /**
204
257
  * The five tools plan mode refuses: exactly the complement of `SKILL_TOOL_FLOOR`
205
258
  * within `HOST_TOOL_NAMES` — "the five that change something", the set this file
@@ -232,5 +285,5 @@ export declare const PLAN_MODE_BLOCKED_TOOLS: ReadonlySet<string>;
232
285
  * declaration MEANS, and that reading must not change with how the CLI was
233
286
  * started.
234
287
  */
235
- export declare const HOST_TOOL_NAMES: readonly ["read_file", "write_file", "edit_file", "list_dir", "glob", "grep", "bash", "skill", "skill_find", "skill_install", "skill_create", "ask_user", "submit_plan", "todo_write", "task"];
288
+ export declare const HOST_TOOL_NAMES: readonly ["read_file", "write_file", "edit_file", "list_dir", "glob", "grep", "bash", "skill", "skill_find", "skill_install", "skill_create", "ask_user", "submit_plan", "todo_write", "bash_output", "bash_kill", "task"];
236
289
  export declare function createBuiltinTools(options: BuiltinToolsOptions): AgentTool[];
@@ -59,6 +59,12 @@ export const SKILL_TOOL_FLOOR = [
59
59
  // inside that skill's frame — the identical dead end that put `ask_user` /
60
60
  // `submit_plan` / `task` here.
61
61
  'todo_write',
62
+ // `bash_output` joins the FLOOR rather than the blocked set (D-12), and the
63
+ // partition test below is what forces that choice to be conscious. It READS A
64
+ // BUFFER: it cannot write to disk or run a shell, which is the floor's stated
65
+ // membership test. Blocking it in plan mode would also let the agent start a
66
+ // service it then could not read — the dead end the floor exists to prevent.
67
+ 'bash_output',
62
68
  ];
63
69
  /**
64
70
  * The five tools plan mode refuses: exactly the complement of `SKILL_TOOL_FLOOR`
@@ -77,6 +83,11 @@ export const PLAN_MODE_BLOCKED_TOOLS = new Set([
77
83
  'bash',
78
84
  'skill_install',
79
85
  'skill_create',
86
+ // `bash_kill` joins the BLOCKED set (D-12). It TERMINATES A PROCESS, which is
87
+ // a mutation of the world and the complement's stated membership test. Nothing
88
+ // is dead-ended by it: `Ctrl+C` and `/bg stop` both remain available to the
89
+ // USER in plan mode, so the person can always stop what the agent started.
90
+ 'bash_kill',
80
91
  ]);
81
92
  /**
82
93
  * Every tool name this host CAN register (7 built-ins + 4 skill tools).
@@ -113,6 +124,13 @@ export const HOST_TOOL_NAMES = [
113
124
  'ask_user',
114
125
  'submit_plan',
115
126
  'todo_write',
127
+ // Both names are here even though `bash.background: false` registers NEITHER,
128
+ // and that is the constant's documented meaning rather than an inconsistency:
129
+ // it is the SUPERSET of what this host CAN register, answering "can a skill's
130
+ // `allowed-tools` declaration ever take effect?" — the same relationship
131
+ // `skills doctor` already has with `--no-skills`.
132
+ 'bash_output',
133
+ 'bash_kill',
116
134
  // `team_send` / `team_wait` are DELIBERATELY ABSENT (P2-5). This constant
117
135
  // answers "can a skill's `allowed-tools` declaration ever take effect?", and
118
136
  // those two are never registered on a lead — so a skill naming them genuinely
@@ -221,6 +239,10 @@ export function createBuiltinTools(options) {
221
239
  ...(options.recordChange ? { recordChange: options.recordChange } : {}),
222
240
  // Added in the same shape and for the same reason (P1-1).
223
241
  ...(options.recordOutput ? { recordOutput: options.recordOutput } : {}),
242
+ // And the third, in the same shape and for the same reason. The FORWARD half
243
+ // of the two-interface note on `procs` above: without this line the field is
244
+ // on both interfaces, nothing errors, and `bash` still hangs.
245
+ ...(options.procs ? { procs: options.procs } : {}),
224
246
  };
225
247
  let tools = [
226
248
  makeReadFile(deps),
@@ -229,7 +251,18 @@ export function createBuiltinTools(options) {
229
251
  makeListDir(deps),
230
252
  makeGlob(deps),
231
253
  makeGrep(deps),
232
- makeBash(deps),
254
+ // The second argument is spread CONDITIONALLY on the supervisor being
255
+ // present, so with background services off `makeBash` receives its default
256
+ // `{}` and produces the pre-feature tool: no `background` property in the
257
+ // schema, the pre-feature description string, no supervisor call (I-2).
258
+ makeBash(deps, options.procs
259
+ ? {
260
+ supervisor: options.procs,
261
+ ...(options.autoBackground ? { autoBackground: options.autoBackground } : {}),
262
+ ...(options.startupSettleMs ? { startupSettleMs: options.startupSettleMs } : {}),
263
+ ...(options.interactive !== undefined ? { interactive: options.interactive } : {}),
264
+ }
265
+ : {}),
233
266
  ];
234
267
  if (options.confirmTools && options.confirm) {
235
268
  const confirm = options.confirm;
@@ -246,6 +279,11 @@ export function createBuiltinTools(options) {
246
279
  if (options.todoTools && options.todoTools.length > 0) {
247
280
  tools = [...tools, ...options.todoTools];
248
281
  }
282
+ // AFTER `todoTools`, BEFORE `teamTools`, so a lead's `task` stays last and
283
+ // `HOST_TOOL_NAMES` changes by two insertions rather than a reshuffle.
284
+ if (options.procTools && options.procTools.length > 0) {
285
+ tools = [...tools, ...options.procTools];
286
+ }
249
287
  // BEFORE both wrappers, so the lead's `task` is covered by the plan gate and
250
288
  // the ceiling like everything else, and a child's comm tools are covered by
251
289
  // everything except the ceiling (see `policyExempt`).
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `bash_output` and `bash_kill` — the model's two handles on a supervised
3
+ * service (background-service-supervision §5.1).
4
+ *
5
+ * REGISTERED THROUGH A FACTORY OPTION, NEVER APPENDED AFTER THE FACT
6
+ * (`tools/index.ts::procTools`). `tools.test.ts::C7` asserts `HOST_TOOL_NAMES`
7
+ * equals what `createBuiltinTools` PRODUCES, so appending these in
8
+ * `AgentController` after the factory returned would turn that test red with a
9
+ * message about two lists of names that says nothing about background services —
10
+ * the trap `teamTools` and `todoTools` each record in capitals.
11
+ *
12
+ * NEITHER JOINS `MUTATING_TOOLS` (P2-5). `bash_output` is a read. `bash_kill`
13
+ * stops something THIS SESSION STARTED and is showing on screen, and the plan
14
+ * gate already refuses it in plan mode; putting a `--confirm` modal between the
15
+ * user and stopping their own runaway server is the wrong trade. `bash` itself
16
+ * stays in `MUTATING_TOOLS` unchanged, including when `background: true` — the
17
+ * command is still arbitrary.
18
+ */
19
+ import { type AgentTool } from '@aragon-agent/core';
20
+ import type { ProcSupervisorPort } from '../proc/types.js';
21
+ export interface ProcToolDeps {
22
+ supervisor: ProcSupervisorPort;
23
+ }
24
+ export declare function makeBashOutput(deps: ProcToolDeps): AgentTool;
25
+ export declare function makeBashKill(deps: ProcToolDeps): AgentTool;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * `bash_output` and `bash_kill` — the model's two handles on a supervised
3
+ * service (background-service-supervision §5.1).
4
+ *
5
+ * REGISTERED THROUGH A FACTORY OPTION, NEVER APPENDED AFTER THE FACT
6
+ * (`tools/index.ts::procTools`). `tools.test.ts::C7` asserts `HOST_TOOL_NAMES`
7
+ * equals what `createBuiltinTools` PRODUCES, so appending these in
8
+ * `AgentController` after the factory returned would turn that test red with a
9
+ * message about two lists of names that says nothing about background services —
10
+ * the trap `teamTools` and `todoTools` each record in capitals.
11
+ *
12
+ * NEITHER JOINS `MUTATING_TOOLS` (P2-5). `bash_output` is a read. `bash_kill`
13
+ * stops something THIS SESSION STARTED and is showing on screen, and the plan
14
+ * gate already refuses it in plan mode; putting a `--confirm` modal between the
15
+ * user and stopping their own runaway server is the wrong trade. `bash` itself
16
+ * stays in `MUTATING_TOOLS` unchanged, including when `background: true` — the
17
+ * command is still arbitrary.
18
+ */
19
+ import { defineTool, errorResult, textResult } from '@aragon-agent/core';
20
+ import { PROC_LIMITS } from '../proc/limits.js';
21
+ /** One line of status, shared by both tools so the two can never disagree. */
22
+ function statusLine(service) {
23
+ const bits = [`service ${service.id} ${service.status}`];
24
+ if (service.url)
25
+ bits.push(service.url);
26
+ if (service.exitCode !== null)
27
+ bits.push(`exit code ${service.exitCode}`);
28
+ else if (service.signal)
29
+ bits.push(`signal ${service.signal}`);
30
+ if (service.killIncomplete)
31
+ bits.push('(may have left a detached child)');
32
+ return `${bits.join(' ')}\n$ ${service.command}`;
33
+ }
34
+ export function makeBashOutput(deps) {
35
+ return defineTool({
36
+ name: 'bash_output',
37
+ label: 'Service log',
38
+ description: 'Read a background service log tail and its current status. Pass the ' +
39
+ '`since` cursor returned by a previous call to read only what is new. ' +
40
+ 'Use this to verify a service actually started, and to read a build or ' +
41
+ 'test watcher as it runs.',
42
+ parameters: {
43
+ type: 'object',
44
+ properties: {
45
+ service: { type: 'string', description: 'The service id, e.g. "s1".' },
46
+ since: {
47
+ type: 'number',
48
+ description: 'Cursor from a previous call; omit to read the whole tail.',
49
+ },
50
+ },
51
+ required: ['service'],
52
+ },
53
+ async execute(_id, rawParams) {
54
+ const params = rawParams;
55
+ const service = deps.supervisor.get(params.service);
56
+ if (!service) {
57
+ return errorResult(`No service "${params.service}". Start one with bash({ command, background: true }).`);
58
+ }
59
+ const page = deps.supervisor.read(params.service, params.since);
60
+ const lines = [statusLine(service)];
61
+ if (page?.truncated) {
62
+ lines.push(`[note] older rows were dropped - the tail keeps the last ` +
63
+ `${PROC_LIMITS.serviceTailRows} rows.`);
64
+ }
65
+ if (!page || page.rows.length === 0) {
66
+ lines.push('(no new output)');
67
+ }
68
+ else {
69
+ lines.push(...page.rows);
70
+ }
71
+ lines.push(`[cursor ${page?.cursor ?? service.rowsSeen}]`);
72
+ return textResult(lines.join('\n'));
73
+ },
74
+ });
75
+ }
76
+ export function makeBashKill(deps) {
77
+ return defineTool({
78
+ name: 'bash_kill',
79
+ label: 'Stop service',
80
+ description: 'Stop a background service by id, or every service with "all". Stop the ' +
81
+ 'services you started once you are finished with them, unless the user ' +
82
+ 'asked for them to stay up.',
83
+ parameters: {
84
+ type: 'object',
85
+ properties: {
86
+ service: { type: 'string', description: 'The service id, or "all".' },
87
+ },
88
+ required: ['service'],
89
+ },
90
+ async execute(_id, rawParams) {
91
+ const params = rawParams;
92
+ if (params.service !== 'all' && !deps.supervisor.get(params.service)) {
93
+ return errorResult(`No service "${params.service}".`);
94
+ }
95
+ const stopped = await deps.supervisor.stop(params.service);
96
+ if (stopped.length === 0)
97
+ return textResult('Nothing to stop.');
98
+ const lines = stopped.map((s) => statusLine(s));
99
+ return textResult(`Stopped ${stopped.length} service${stopped.length === 1 ? '' : 's'}.\n${lines.join('\n')}`);
100
+ },
101
+ });
102
+ }
package/dist/ui/App.d.ts CHANGED
@@ -10,7 +10,8 @@ import type { ConfirmRequest } from '../tools/index.js';
10
10
  import { type Overlay, type ToastLevel } from '../agent/reducer.js';
11
11
  import type { UpdateBridge } from '../update/types.js';
12
12
  import { type RenderMode } from './layout/frame.js';
13
- import type { MouseSource } from '../input/stdin-mouse-filter.js';
13
+ import type { MouseSource } from '../input/stdin-filter.js';
14
+ import type { PasteBridge } from '../input/limits.js';
14
15
  import type { SelectionBridge } from './selection/selection-controller.js';
15
16
  import type { HumanInputBridge } from '../tools/human-input.js';
16
17
  export interface ConfirmBridge {
@@ -73,6 +74,18 @@ export interface AppProps {
73
74
  * 'off'`, on a non-TTY, in CI, and in every test that does not exercise it.
74
75
  */
75
76
  updateBridge?: UpdateBridge;
77
+ /**
78
+ * The stdin filter's channel for a refused paste (tui-paste-handling section
79
+ * 5.1.2 / I-15).
80
+ *
81
+ * A BRIDGE for the reason `updateBridge` above is one: the filter is built in
82
+ * `cli.tsx` BEFORE `render()`, so it has no `dispatch`. Its only other outlet
83
+ * is the logger, and a limit that writes only to a log file is
84
+ * indistinguishable — from the user's chair — from a paste that silently did
85
+ * nothing, which is what D-11 exists to rule out. Absent under `--no-paste`,
86
+ * on a non-TTY, and in every test that does not exercise it.
87
+ */
88
+ pasteBridge?: PasteBridge;
76
89
  /**
77
90
  * The terminal-facing services `cli.tsx` owns and this component cannot build
78
91
  * (tui-selection-and-scroll-follow §4.4).
@@ -120,4 +133,4 @@ export interface TerminalBridge {
120
133
  writeForeign?: (text: string) => void;
121
134
  selection?: SelectionBridge;
122
135
  }
123
- export declare function App({ controller, version, mode, initialOverlay, initialPrompt, confirmBridge, humanInputBridge, mouseSource, vtInputWarning, updateBridge, terminal, }: AppProps): React.ReactElement;
136
+ export declare function App({ controller, version, mode, initialOverlay, initialPrompt, confirmBridge, humanInputBridge, mouseSource, vtInputWarning, updateBridge, pasteBridge, terminal, }: AppProps): React.ReactElement;