@aragon-agent/cli 0.6.2 → 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 (167) hide show
  1. package/CHANGELOG.md +321 -0
  2. package/README.md +297 -2
  3. package/dist/agent/controller.d.ts +193 -1
  4. package/dist/agent/controller.js +475 -2
  5. package/dist/agent/headless.d.ts +8 -0
  6. package/dist/agent/headless.js +47 -0
  7. package/dist/agent/reducer.d.ts +189 -0
  8. package/dist/agent/reducer.js +301 -2
  9. package/dist/agent/system-prompt.d.ts +14 -0
  10. package/dist/agent/system-prompt.js +2 -0
  11. package/dist/cli.js +297 -16
  12. package/dist/commands/builtins.js +165 -24
  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 +118 -0
  18. package/dist/compaction/command.d.ts +41 -0
  19. package/dist/compaction/command.js +355 -0
  20. package/dist/compaction/compactor.d.ts +302 -0
  21. package/dist/compaction/compactor.js +810 -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 +215 -0
  35. package/dist/compaction/types.js +22 -0
  36. package/dist/compaction/wiring.d.ts +264 -0
  37. package/dist/compaction/wiring.js +577 -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 +5 -2
  43. package/dist/config/env.js +140 -3
  44. package/dist/config/load.d.ts +42 -0
  45. package/dist/config/load.js +101 -1
  46. package/dist/config/schema.d.ts +244 -0
  47. package/dist/config/schema.js +170 -0
  48. package/dist/config/store.js +37 -1
  49. package/dist/config/ui-state.d.ts +28 -1
  50. package/dist/config/ui-state.js +9 -0
  51. package/dist/exec/events.d.ts +50 -1
  52. package/dist/exec/runner.js +42 -0
  53. package/dist/input/limits.d.ts +72 -0
  54. package/dist/input/limits.js +63 -0
  55. package/dist/input/mouse-events.d.ts +31 -5
  56. package/dist/input/mouse-events.js +86 -27
  57. package/dist/input/paste-parse.d.ts +71 -0
  58. package/dist/input/paste-parse.js +95 -0
  59. package/dist/input/stdin-filter.d.ts +81 -0
  60. package/dist/input/stdin-filter.js +379 -0
  61. package/dist/logging/install.d.ts +53 -1
  62. package/dist/logging/install.js +81 -1
  63. package/dist/logging/logger.d.ts +1 -1
  64. package/dist/proc/classify.d.ts +52 -0
  65. package/dist/proc/classify.js +143 -0
  66. package/dist/proc/kill-tree.d.ts +42 -0
  67. package/dist/proc/kill-tree.js +119 -0
  68. package/dist/proc/limits.d.ts +84 -0
  69. package/dist/proc/limits.js +84 -0
  70. package/dist/proc/log-ring.d.ts +68 -0
  71. package/dist/proc/log-ring.js +102 -0
  72. package/dist/proc/prompt.d.ts +24 -0
  73. package/dist/proc/prompt.js +49 -0
  74. package/dist/proc/readiness.d.ts +102 -0
  75. package/dist/proc/readiness.js +219 -0
  76. package/dist/proc/supervisor.d.ts +103 -0
  77. package/dist/proc/supervisor.js +465 -0
  78. package/dist/proc/types.d.ts +175 -0
  79. package/dist/proc/types.js +15 -0
  80. package/dist/session/persist.js +55 -1
  81. package/dist/team/report.js +7 -1
  82. package/dist/team/runtime.d.ts +10 -0
  83. package/dist/team/runtime.js +7 -0
  84. package/dist/team/subagent.d.ts +31 -1
  85. package/dist/team/subagent.js +42 -15
  86. package/dist/team/types.d.ts +8 -0
  87. package/dist/tools/bash-tool.d.ts +51 -2
  88. package/dist/tools/bash-tool.js +321 -51
  89. package/dist/tools/fs-tools.d.ts +17 -0
  90. package/dist/tools/index.d.ts +55 -2
  91. package/dist/tools/index.js +39 -1
  92. package/dist/tools/proc-tools.d.ts +25 -0
  93. package/dist/tools/proc-tools.js +102 -0
  94. package/dist/ui/ActivityLine.d.ts +19 -1
  95. package/dist/ui/ActivityLine.js +10 -2
  96. package/dist/ui/App.d.ts +62 -2
  97. package/dist/ui/App.js +546 -16
  98. package/dist/ui/Composer.d.ts +66 -1
  99. package/dist/ui/Composer.js +50 -6
  100. package/dist/ui/PromptInput.d.ts +82 -3
  101. package/dist/ui/PromptInput.js +232 -49
  102. package/dist/ui/StatusBar.d.ts +59 -0
  103. package/dist/ui/StatusBar.js +16 -4
  104. package/dist/ui/TeamPanel.js +7 -1
  105. package/dist/ui/Transcript.d.ts +21 -1
  106. package/dist/ui/Transcript.js +61 -3
  107. package/dist/ui/clipboard.d.ts +52 -0
  108. package/dist/ui/clipboard.js +91 -0
  109. package/dist/ui/composer-limits.d.ts +31 -0
  110. package/dist/ui/composer-limits.js +50 -0
  111. package/dist/ui/composer-rows.d.ts +73 -0
  112. package/dist/ui/composer-rows.js +201 -0
  113. package/dist/ui/editor-reducer.d.ts +37 -0
  114. package/dist/ui/editor-reducer.js +88 -12
  115. package/dist/ui/entries/CompactionCard.d.ts +75 -0
  116. package/dist/ui/entries/CompactionCard.js +158 -0
  117. package/dist/ui/entries/ServiceCard.d.ts +66 -0
  118. package/dist/ui/entries/ServiceCard.js +116 -0
  119. package/dist/ui/entries/UserEntry.d.ts +17 -3
  120. package/dist/ui/entries/UserEntry.js +34 -5
  121. package/dist/ui/frame-differ.d.ts +46 -0
  122. package/dist/ui/frame-differ.js +94 -10
  123. package/dist/ui/gauge.d.ts +16 -1
  124. package/dist/ui/gauge.js +25 -6
  125. package/dist/ui/glyphs.d.ts +11 -0
  126. package/dist/ui/glyphs.js +2 -0
  127. package/dist/ui/layout/ScrollViewport.d.ts +43 -6
  128. package/dist/ui/layout/ScrollViewport.js +128 -21
  129. package/dist/ui/layout/budget.d.ts +44 -9
  130. package/dist/ui/layout/budget.js +42 -10
  131. package/dist/ui/layout/follow-state.d.ts +137 -0
  132. package/dist/ui/layout/follow-state.js +150 -0
  133. package/dist/ui/layout/virtual-window.d.ts +11 -0
  134. package/dist/ui/layout/virtual-window.js +85 -1
  135. package/dist/ui/overlays/HelpOverlay.js +9 -1
  136. package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
  137. package/dist/ui/overlays/QuestionOverlay.js +5 -1
  138. package/dist/ui/overlays/SettingsScreen.d.ts +47 -1
  139. package/dist/ui/overlays/SettingsScreen.js +74 -2
  140. package/dist/ui/palettes.d.ts +16 -0
  141. package/dist/ui/palettes.js +6 -0
  142. package/dist/ui/paste-frames.d.ts +87 -0
  143. package/dist/ui/paste-frames.js +112 -0
  144. package/dist/ui/paste-tokens.d.ts +90 -0
  145. package/dist/ui/paste-tokens.js +147 -0
  146. package/dist/ui/screen.d.ts +38 -0
  147. package/dist/ui/screen.js +113 -8
  148. package/dist/ui/selection/highlight.d.ts +39 -0
  149. package/dist/ui/selection/highlight.js +64 -0
  150. package/dist/ui/selection/screen-mirror.d.ts +24 -0
  151. package/dist/ui/selection/screen-mirror.js +35 -0
  152. package/dist/ui/selection/selection-controller.d.ts +88 -0
  153. package/dist/ui/selection/selection-controller.js +282 -0
  154. package/dist/ui/selection/selection.d.ts +90 -0
  155. package/dist/ui/selection/selection.js +223 -0
  156. package/dist/ui/stdout-frame-writer.d.ts +33 -0
  157. package/dist/ui/stdout-frame-writer.js +15 -0
  158. package/dist/ui/theme.d.ts +11 -0
  159. package/dist/ui/theme.js +2 -0
  160. package/dist/ui/transcript-text.js +72 -0
  161. package/dist/ui/use-startup-notices.d.ts +27 -1
  162. package/dist/ui/use-startup-notices.js +43 -11
  163. package/dist/ui/use-wheel-routing.d.ts +1 -1
  164. package/dist/ui/use-wheel-routing.js +8 -0
  165. package/package.json +4 -2
  166. package/dist/input/stdin-mouse-filter.d.ts +0 -42
  167. package/dist/input/stdin-mouse-filter.js +0 -153
@@ -59,6 +59,24 @@ function stripLiveToolOutput(entries) {
59
59
  ? { ...e, live: undefined, liveSeq: undefined, lastOutputAt: undefined }
60
60
  : e);
61
61
  }
62
+ /**
63
+ * Drop the service tail on the way to disk, beside `stripLiveToolOutput` and for
64
+ * the same three reasons (background-service-supervision §6).
65
+ *
66
+ * A resumed card's tail describes a PROCESS THAT NO LONGER EXISTS - it died with
67
+ * the session, because nothing in this package outlives the CLI by design. The
68
+ * tail is also the only unbounded field a service entry carries, and dropping it
69
+ * keeps a saved session's size a function of how many services ran rather than
70
+ * of how chatty they were.
71
+ *
72
+ * `rowsSeen` IS KEPT. It is the revision cursor, not display data, and a
73
+ * restored card that reported 0 rows seen while `normalizeLoadedEntries` was
74
+ * flipping its status would produce a revision collision with a live card that
75
+ * genuinely has none.
76
+ */
77
+ function stripServiceTail(entries) {
78
+ return entries.map((e) => (e.kind === 'service' && e.rows.length > 0 ? { ...e, rows: [] } : e));
79
+ }
62
80
  /**
63
81
  * `data` GAINS `todos`, AND WIDENING `SavedSession` ALONE IS NOT ENOUGH (P0-2):
64
82
  * the writer below builds its payload from THIS parameter, not from the type, so
@@ -81,7 +99,7 @@ export function saveSession(filePath, data) {
81
99
  savedAt: Date.now(),
82
100
  model: data.model,
83
101
  messages: data.messages,
84
- entries: stripLiveToolOutput(data.entries),
102
+ entries: stripServiceTail(stripLiveToolOutput(data.entries)),
85
103
  todos: data.todos,
86
104
  ...(data.meta ? { meta: data.meta } : {}),
87
105
  };
@@ -139,6 +157,42 @@ export function normalizeLoadedEntries(entries) {
139
157
  detail: 'interrupted (session resumed)',
140
158
  };
141
159
  }
160
+ // THE FIFTH CLAUSE (context-auto-compaction §5.5 / C-8 / AC-19). A compaction
161
+ // card saved while its summarization was open resumes claiming to be running
162
+ // while nothing is, and — the part that actually costs — it never settles, so
163
+ // `Transcript`'s MONOTONIC boundary never advances past it and every frame for
164
+ // the rest of the session re-renders the whole tail.
165
+ //
166
+ // `applied: false` is also simply TRUE: whatever the call was going to
167
+ // return died with the process, and the history on disk is the PRE-compaction
168
+ // one. A card that claimed otherwise would tell the user their session had
169
+ // been compacted when it had not.
170
+ if (entry.kind === 'compaction' && entry.live) {
171
+ return {
172
+ ...entry,
173
+ live: false,
174
+ applied: false,
175
+ mode: 'none',
176
+ reason: 'interrupted (session resumed)',
177
+ };
178
+ }
179
+ // THE SIXTH CLAUSE (background-service-supervision §6 / R-7). A card saved
180
+ // while its service was up resumes claiming to be running while nothing is -
181
+ // the process died with the session, because nothing here outlives the CLI.
182
+ //
183
+ // AND `starting` IS THE ONE THAT ACTUALLY COSTS: it is the single service
184
+ // status that blocks `Transcript`'s MONOTONIC settled boundary (D-11), so a
185
+ // restored `starting` card would never settle and every frame for the rest of
186
+ // the session would re-render the whole tail. `ready` / `running` would
187
+ // "only" be a lie; this one is a lie that gets slower.
188
+ //
189
+ // `stopped` rather than `exited`: we did not observe an exit code, and
190
+ // claiming one would be inventing a number. The terminal record the session
191
+ // may also carry is already terminal and is left alone.
192
+ if (entry.kind === 'service' &&
193
+ (entry.status === 'starting' || entry.status === 'ready' || entry.status === 'running')) {
194
+ return { ...entry, status: 'stopped', rows: [] };
195
+ }
142
196
  return entry;
143
197
  });
144
198
  }
@@ -143,9 +143,15 @@ function buildSection(run) {
143
143
  // bytes, and it is the only place the lead learns which of its children were
144
144
  // cheap — the fact the whole second cost line is about.
145
145
  const tier = run.tier === 'fast' ? ' [fast]' : '';
146
+ // `compacted N` in the same position `retried Nx` uses, and for the same
147
+ // reason (context-auto-compaction-hardening §3.4.4 / W3): this is the ONLY
148
+ // place the lead learns that a child's own history was summarized mid-task, so
149
+ // a summary that reads thinner than the turn count suggests has a stated cause
150
+ // rather than an unexplained one. Absent when it never happened.
151
+ const compacted = run.compactions ? `, compacted ${run.compactions}` : '';
146
152
  const head = [
147
153
  `### ${run.label}${tier} "${run.description}" ${statusBadge(run)} ${elapsed(run)}, ` +
148
- `${run.turns} turns, ${run.toolCalls} tools${retried}`,
154
+ `${run.turns} turns, ${run.toolCalls} tools${retried}${compacted}`,
149
155
  ];
150
156
  if (run.filesTouched.length > 0)
151
157
  head.push(`files: ${run.filesTouched.join(', ')}`);
@@ -60,6 +60,16 @@ export interface TeamRuntimeDeps {
60
60
  * is the pre-feature behaviour.
61
61
  */
62
62
  resolveTier?: SubagentDeps['resolveTier'];
63
+ /**
64
+ * Per-child context compaction (context-auto-compaction-hardening §3.4 / W3).
65
+ *
66
+ * Passed straight through to `SubagentDeps`; absent means children get none,
67
+ * which is the pre-hardening behaviour exactly. `AgentController` supplies it
68
+ * from `CompactionWiring.childFactory()`, and the closure returns `undefined`
69
+ * when compaction is unregistered or `compaction.subagents` is false - which is
70
+ * what lets `subagent.ts` spread nothing at all.
71
+ */
72
+ contextManagerFor?: SubagentDeps['contextManagerFor'];
63
73
  }
64
74
  export declare class TeamRuntime {
65
75
  private readonly deps;
@@ -132,6 +132,13 @@ export class TeamRuntime {
132
132
  ...(this.deps.humanQueue ? { humanQueue: this.deps.humanQueue } : {}),
133
133
  agentFactory: this.deps.agentFactory ?? defaultSubagentFactory,
134
134
  ...(this.deps.resolveTier ? { resolveTier: this.deps.resolveTier } : {}),
135
+ // Site two of two, and the omission is silent in the same way the policy
136
+ // one above is: declaring it on `TeamRuntimeDeps` and forgetting this line
137
+ // type-checks and forwards nothing, so every child would quietly run with
138
+ // no compaction while the config key says it has one.
139
+ ...(this.deps.contextManagerFor
140
+ ? { contextManagerFor: this.deps.contextManagerFor }
141
+ : {}),
135
142
  };
136
143
  // Hoisted into a factory so a retry can build a fresh child with IDENTICAL
137
144
  // wiring (F-4). Replacing the child rather than re-prompting it is the only
@@ -10,8 +10,9 @@
10
10
  * them (§3.11 / R-6). What a child does NOT get is spelled out below, and each
11
11
  * omission has a reason that is a correctness boundary rather than caution.
12
12
  */
13
- import { type AgentConfig, type AgentEvent, type AgentTool, type ModelRef, type ProviderRegistry, type SkillRegistry, type ThinkingLevel, type TokenUsage, type ToolPolicyDecision, type ToolPolicyVerdict } from '@aragon-agent/core';
13
+ import { type AgentConfig, type AgentEvent, type AgentTool, type Message, type ModelRef, type ProviderRegistry, type SkillRegistry, type ThinkingLevel, type TokenUsage, type ToolPolicyDecision, type ToolPolicyVerdict } from '@aragon-agent/core';
14
14
  import type { AgentMode } from '../agent/agent-mode.js';
15
+ import type { ChildContextManagerProvider } from '../compaction/child.js';
15
16
  import type { CliConfig } from '../config/schema.js';
16
17
  import type { ToolPermission } from '../exec/permission.js';
17
18
  import type { FastTierName } from '../fast/types.js';
@@ -29,6 +30,24 @@ export interface SubagentAgentLike extends WatchdogPausable {
29
30
  prompt(text: string): Promise<void>;
30
31
  abort(): void;
31
32
  subscribe(listener: (event: AgentEvent) => void): () => void;
33
+ /**
34
+ * The child's live history, for its own context manager
35
+ * (context-auto-compaction-hardening §3.4.3 / W3).
36
+ *
37
+ * NARROWED ON PURPOSE. `Agent` satisfies this with its full `AgentState`
38
+ * getter and a stub satisfies it with `{ messages: [] }`. Widening it to
39
+ * `AgentState` would make every test stub fabricate a systemPrompt, a model and
40
+ * a phase to compile, for fields none of them use.
41
+ *
42
+ * A PROPERTY READ, NOT A METHOD CALL, AND THAT IS NOT A STYLE CHOICE: core's
43
+ * `Agent` has NO `getMessages()`. It exposes `get state(): AgentState`;
44
+ * `AgentController.getMessages()` is the CONTROLLER's own one-line forwarder
45
+ * over `this.agent.state.messages`. Adding a method to the engine to serve a
46
+ * host-side convenience would move the core export surface for nothing.
47
+ */
48
+ readonly state: {
49
+ readonly messages: readonly Message[];
50
+ };
32
51
  }
33
52
  export type SubagentAgentFactory = (config: AgentConfig) => SubagentAgentLike;
34
53
  /** The default factory: a real `Agent` from `@aragon-agent/core`. */
@@ -91,6 +110,17 @@ export interface SubagentDeps {
91
110
  ref: ModelRef;
92
111
  thinkingLevel: ThinkingLevel;
93
112
  };
113
+ /**
114
+ * Build this child its own context manager
115
+ * (context-auto-compaction-hardening §3.4.3 / W3).
116
+ *
117
+ * ABSENT means children get none, and `subagent.ts` then spreads NO
118
+ * `contextManager` key at all - so the child's loop gate tests a genuinely
119
+ * undefined field and a `compaction.subagents: false` session is byte-identical
120
+ * to round 1 (AC-H10). `AgentController` supplies it from
121
+ * `CompactionWiring.childFactory()`, which returns `null` when the key is off.
122
+ */
123
+ contextManagerFor?: ChildContextManagerProvider;
94
124
  }
95
125
  export interface SubagentHooks {
96
126
  /** Coalesced at the source (§5.2); phase transitions always pass. */
@@ -183,22 +183,40 @@ export function createSubagent(spec, deps, hooks) {
183
183
  thinkingLevel: deps.config.thinkingLevel,
184
184
  };
185
185
  const model = resolved.ref;
186
- const agent = deps.agentFactory({
187
- systemPrompt: buildSystemPrompt({
188
- cwd: deps.getCwd(),
189
- tools,
190
- skillsBlock: deps.alwaysBlock ?? '',
191
- agentMode: spec.readOnly ? 'plan' : deps.getMode(),
192
- // A child has no overlay, so the plan block must describe the headless
193
- // shape: write the plan as the final message rather than calling tools it
194
- // does not have.
195
- planInteractive: false,
196
- subagentBlock: buildSubagentBlock({
197
- label: spec.label,
198
- description: spec.description,
199
- peers: deps.bus.peersOf(spec.label),
200
- }),
186
+ // HOISTED TO A LOCAL, AND THAT IS WHAT MAKES W3 POSSIBLE AT ALL. This was built
187
+ // INLINE inside the `agentFactory({...})` argument, so there was nothing for the
188
+ // child manager's `getSystemPrompt` accessor to close over - and the accessor
189
+ // cannot read it back off the agent, because `SubagentAgentLike` is narrowed to
190
+ // `state.messages` on purpose.
191
+ const systemPrompt = buildSystemPrompt({
192
+ cwd: deps.getCwd(),
193
+ tools,
194
+ skillsBlock: deps.alwaysBlock ?? '',
195
+ agentMode: spec.readOnly ? 'plan' : deps.getMode(),
196
+ // A child has no overlay, so the plan block must describe the headless
197
+ // shape: write the plan as the final message rather than calling tools it
198
+ // does not have.
199
+ planInteractive: false,
200
+ subagentBlock: buildSubagentBlock({
201
+ label: spec.label,
202
+ description: spec.description,
203
+ peers: deps.bus.peersOf(spec.label),
201
204
  }),
205
+ });
206
+ // LAZY ACCESSORS OVER `agentRef`, WHICH IS ASSIGNED AFTER THIS RETURNS. A
207
+ // non-lazy read here is `undefined` at construction and the child's estimate
208
+ // branch would silently measure an empty history - CR-1 one module over.
209
+ const contextManager = deps.contextManagerFor?.({
210
+ label: spec.label,
211
+ model,
212
+ getMessages: () => agentRef?.state.messages ?? [],
213
+ getSystemPrompt: () => systemPrompt,
214
+ onCompacted: () => {
215
+ run.compactions = (run.compactions ?? 0) + 1;
216
+ },
217
+ });
218
+ const agent = deps.agentFactory({
219
+ systemPrompt,
202
220
  model,
203
221
  tools,
204
222
  thinkingLevel: resolved.thinkingLevel,
@@ -217,6 +235,11 @@ export function createSubagent(spec, deps, hooks) {
217
235
  // explicitly.
218
236
  idleTimeout: deps.config.idleTimeoutMs,
219
237
  },
238
+ // SPREAD CONDITIONALLY, never `contextManager: deps.contextManagerFor?.(...)`.
239
+ // With the feature off the options bag has no such key, so the loop's gate
240
+ // tests a field that is genuinely `undefined` rather than a property holding
241
+ // one - the identical argument `AgentController` makes for the lead (AC-H10).
242
+ ...(contextManager ? { contextManager } : {}),
220
243
  });
221
244
  agentRef = agent;
222
245
  let lastEmit = 0;
@@ -289,6 +312,10 @@ export function createSubagent(spec, deps, hooks) {
289
312
  outputTokens: run.usage.outputTokens + event.usage.outputTokens,
290
313
  };
291
314
  hooks.onUsage(spec.label, event.usage);
315
+ // W1's delta needs the history LENGTH the measurement covered, and the
316
+ // port carries no turn hook. `turn_end` is emitted BEFORE the assistant
317
+ // push, so this is exactly the history the reported `usage` describes.
318
+ contextManager?.onTurnEnd(event.usage, agentRef?.state.messages ?? [], systemPrompt);
292
319
  const text = assistantText(event.message);
293
320
  // The LAST non-empty assistant text wins, which is exactly the child's
294
321
  // final message once the loop stops calling tools. Capturing it here
@@ -78,6 +78,14 @@ export interface SubagentRun {
78
78
  error?: string;
79
79
  /** Hit `team.maxTurnsPerSubagent` and was stopped mid-work. */
80
80
  truncated?: boolean;
81
+ /**
82
+ * Compactions this child performed on its OWN history
83
+ * (context-auto-compaction-hardening §3.4.4 / W3).
84
+ *
85
+ * OPTIONAL, so every existing construction site and fixture compiles
86
+ * unchanged. Absent and `0` mean the same thing to every reader.
87
+ */
88
+ compactions?: number;
81
89
  /**
82
90
  * Prose the child is writing right now, sanitized and clamped to
83
91
  * `TEAM_LIMITS.activityChars`. Set only between tool calls; cleared on
@@ -1,12 +1,61 @@
1
1
  /**
2
- * bash tool — run a shell command at full permission (spec §3.6).
2
+ * bash tool — run a shell command at full permission (spec §3.6), and, since
3
+ * background-service-supervision, supervise the ones that do not exit.
3
4
  *
4
5
  * Shell selection: Windows uses `process.env.ComSpec` (cmd.exe by default);
5
6
  * POSIX uses `/bin/sh -c`. The command's stdout+stderr and exit code are
6
7
  * captured. The `timeout?` param can only SHRINK within the executor ceiling
7
8
  * (the core ToolExecutor owns the real timeout + 100 KB truncation — R4), so
8
9
  * this tool does not re-implement an output cap.
10
+ *
11
+ * I-1 — THIS TOOL NEVER LEAVES ITS PROMISE PENDING. Every exit path goes through
12
+ * `settle`, and `settle` is reachable from FIVE places: `'error'`, `'close'`,
13
+ * `'exit'` plus a drain timer, an abort plus a kill grace, and the `timeout`
14
+ * param plus the same grace. That is the whole of W1, and each of the five
15
+ * closes a way the old implementation could hang:
16
+ *
17
+ * 1. It settled on `'close'`, which fires only when the process has exited AND
18
+ * every stdio stream is closed. A child that spawns a detached grandchild
19
+ * inheriting the pipe — `Start-Process`, `&`, `nohup`, and every
20
+ * `npm run dev` that re-execs — keeps that pipe open after the shell is
21
+ * long gone. The shell exited; the promise did not.
22
+ * 2. `stdio` defaulted to `'pipe'` for stdin, so a command that PROMPTS —
23
+ * `npm init`, a package manager asking to install something, `sudo`, a git
24
+ * credential prompt — blocked on a read that could never be satisfied.
25
+ * 3. An abort killed the tree and then went on waiting for a `'close'` that a
26
+ * detached grandchild was still holding.
27
+ *
28
+ * The happy path is BYTE-IDENTICAL to the pre-change build: `$ cmd\n<output>\n
29
+ * [exit code N]`, same truncation, same footer. That is what keeps the existing
30
+ * `bash` assertions meaningful rather than rewritten.
9
31
  */
10
32
  import { type AgentTool } from '@aragon-agent/core';
33
+ import type { ProcSupervisorPort } from '../proc/types.js';
11
34
  import type { ToolDeps } from './fs-tools.js';
12
- export declare function makeBash(deps: ToolDeps): AgentTool;
35
+ export interface BashToolOptions {
36
+ /**
37
+ * Present only when background launches are on for this session.
38
+ *
39
+ * ITS ABSENCE IS WHAT MAKES I-2 CHECKABLE: no `background` property in the
40
+ * schema, no supervisor call, no readiness timer, and a description string
41
+ * identical to the pre-feature one.
42
+ */
43
+ supervisor?: ProcSupervisorPort;
44
+ /** `cfg.bash.autoBackground`, read LIVE. */
45
+ autoBackground?: () => boolean;
46
+ /** `cfg.bash.startupSettleMs`, read LIVE. */
47
+ startupSettleMs?: () => number;
48
+ /**
49
+ * Whether a card can be rendered and a user can keep a service alive (D-15 /
50
+ * P1-8).
51
+ *
52
+ * FALSE IN `aragon exec` AND `-p`. It suppresses only the CLASSIFIER: an
53
+ * explicit `background: true` is still honoured — the model asked for it —
54
+ * and the result then carries one extra line saying the service dies with the
55
+ * run. Without this rule `aragon -p "npm run dev"` would background a server,
56
+ * return in `startupSettleMs`, and reap it at process exit with no card, no
57
+ * `/bg`, no event and nothing in the result saying so.
58
+ */
59
+ interactive?: boolean;
60
+ }
61
+ export declare function makeBash(deps: ToolDeps, options?: BashToolOptions): AgentTool;