@aragon-agent/cli 0.6.38 → 0.6.40

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 (209) hide show
  1. package/CHANGELOG.md +263 -0
  2. package/README.md +544 -11
  3. package/dist/agent/controller.d.ts +245 -10
  4. package/dist/agent/controller.js +367 -22
  5. package/dist/agent/model-profile-settings.js +12 -1
  6. package/dist/agent/plan-prompt.d.ts +1 -3
  7. package/dist/agent/plan-prompt.js +13 -7
  8. package/dist/agent/reducer.d.ts +70 -2
  9. package/dist/agent/reducer.js +59 -6
  10. package/dist/agent/skill-triage-prompt.d.ts +42 -0
  11. package/dist/agent/skill-triage-prompt.js +74 -0
  12. package/dist/agent/system-prompt.d.ts +28 -2
  13. package/dist/agent/system-prompt.js +12 -4
  14. package/dist/cli.js +106 -32
  15. package/dist/commands/builtins.js +24 -3
  16. package/dist/commands/keys-command.d.ts +23 -0
  17. package/dist/commands/keys-command.js +38 -0
  18. package/dist/commands/registry.d.ts +39 -0
  19. package/dist/commands/skills.js +86 -1
  20. package/dist/commands/subagent-command.d.ts +25 -0
  21. package/dist/commands/subagent-command.js +242 -0
  22. package/dist/commands/task-command.d.ts +39 -0
  23. package/dist/commands/task-command.js +102 -0
  24. package/dist/compaction/command.js +16 -3
  25. package/dist/compaction/compactor.d.ts +25 -0
  26. package/dist/compaction/compactor.js +111 -10
  27. package/dist/compaction/digest.d.ts +24 -1
  28. package/dist/compaction/digest.js +139 -17
  29. package/dist/compaction/limits.d.ts +21 -0
  30. package/dist/compaction/limits.js +21 -0
  31. package/dist/compaction/reason-text.d.ts +36 -0
  32. package/dist/compaction/reason-text.js +78 -0
  33. package/dist/compaction/types.d.ts +82 -14
  34. package/dist/compaction/wiring.d.ts +13 -8
  35. package/dist/compaction/wiring.js +1 -0
  36. package/dist/config/cli-commands.d.ts +23 -0
  37. package/dist/config/cli-commands.js +36 -0
  38. package/dist/config/env.js +17 -4
  39. package/dist/config/load.js +55 -3
  40. package/dist/config/schema.d.ts +125 -10
  41. package/dist/config/schema.js +138 -14
  42. package/dist/config/store.js +23 -3
  43. package/dist/diagnostics/doctor.d.ts +9 -0
  44. package/dist/diagnostics/doctor.js +34 -0
  45. package/dist/diagnostics/info.d.ts +71 -0
  46. package/dist/diagnostics/info.js +91 -5
  47. package/dist/exec/bridge/caller-tools.d.ts +96 -0
  48. package/dist/exec/bridge/caller-tools.js +213 -0
  49. package/dist/exec/bridge/decisions.d.ts +78 -0
  50. package/dist/exec/bridge/decisions.js +116 -0
  51. package/dist/exec/bridge/human-answers.d.ts +38 -0
  52. package/dist/exec/bridge/human-answers.js +164 -0
  53. package/dist/exec/bridge/limits.d.ts +68 -0
  54. package/dist/exec/bridge/limits.js +68 -0
  55. package/dist/exec/bridge/ndjson.d.ts +17 -0
  56. package/dist/exec/bridge/ndjson.js +83 -0
  57. package/dist/exec/bridge/pending.d.ts +70 -0
  58. package/dist/exec/bridge/pending.js +132 -0
  59. package/dist/exec/bridge/protocol.d.ts +346 -0
  60. package/dist/exec/bridge/protocol.js +256 -0
  61. package/dist/exec/bridge/server.d.ts +142 -0
  62. package/dist/exec/bridge/server.js +502 -0
  63. package/dist/exec/bridge/source.d.ts +58 -0
  64. package/dist/exec/bridge/source.js +145 -0
  65. package/dist/exec/bridge/writer.d.ts +26 -0
  66. package/dist/exec/bridge/writer.js +156 -0
  67. package/dist/exec/cli-commands.js +30 -2
  68. package/dist/exec/emitter.d.ts +16 -2
  69. package/dist/exec/emitter.js +26 -6
  70. package/dist/exec/events.d.ts +465 -10
  71. package/dist/exec/events.js +47 -1
  72. package/dist/exec/hooks/dispatch.d.ts +76 -0
  73. package/dist/exec/hooks/dispatch.js +169 -0
  74. package/dist/exec/hooks/index.d.ts +83 -0
  75. package/dist/exec/hooks/index.js +205 -0
  76. package/dist/exec/hooks/limits.d.ts +56 -0
  77. package/dist/exec/hooks/limits.js +49 -0
  78. package/dist/exec/hooks/registry.d.ts +49 -0
  79. package/dist/exec/hooks/registry.js +91 -0
  80. package/dist/exec/hooks/tool-wrapper.d.ts +94 -0
  81. package/dist/exec/hooks/tool-wrapper.js +154 -0
  82. package/dist/exec/hooks/turn.d.ts +61 -0
  83. package/dist/exec/hooks/turn.js +59 -0
  84. package/dist/exec/hooks/validate.d.ts +85 -0
  85. package/dist/exec/hooks/validate.js +310 -0
  86. package/dist/exec/index.d.ts +130 -4
  87. package/dist/exec/index.js +501 -10
  88. package/dist/exec/options.d.ts +124 -2
  89. package/dist/exec/options.js +413 -6
  90. package/dist/exec/permission.d.ts +9 -1
  91. package/dist/exec/permission.js +7 -1
  92. package/dist/exec/request/limits.d.ts +51 -0
  93. package/dist/exec/request/limits.js +49 -0
  94. package/dist/exec/request/scope.d.ts +106 -0
  95. package/dist/exec/request/scope.js +149 -0
  96. package/dist/exec/result/limits.d.ts +74 -0
  97. package/dist/exec/result/limits.js +72 -0
  98. package/dist/exec/result/prompt.d.ts +28 -0
  99. package/dist/exec/result/prompt.js +74 -0
  100. package/dist/exec/result/schema.d.ts +99 -0
  101. package/dist/exec/result/schema.js +464 -0
  102. package/dist/exec/result/sink.d.ts +56 -0
  103. package/dist/exec/result/sink.js +50 -0
  104. package/dist/exec/result/tool.d.ts +37 -0
  105. package/dist/exec/result/tool.js +118 -0
  106. package/dist/exec/runner.d.ts +395 -1
  107. package/dist/exec/runner.js +860 -8
  108. package/dist/exec/threads/aggregate.d.ts +34 -0
  109. package/dist/exec/threads/aggregate.js +30 -0
  110. package/dist/exec/threads/capacity.d.ts +23 -0
  111. package/dist/exec/threads/capacity.js +38 -0
  112. package/dist/exec/threads/dispatch-source.d.ts +60 -0
  113. package/dist/exec/threads/dispatch-source.js +106 -0
  114. package/dist/exec/threads/execute-threads.d.ts +53 -0
  115. package/dist/exec/threads/execute-threads.js +234 -0
  116. package/dist/exec/threads/gate.d.ts +42 -0
  117. package/dist/exec/threads/gate.js +81 -0
  118. package/dist/exec/threads/identity.d.ts +36 -0
  119. package/dist/exec/threads/identity.js +67 -0
  120. package/dist/exec/threads/interrupt.d.ts +29 -0
  121. package/dist/exec/threads/interrupt.js +79 -0
  122. package/dist/exec/threads/limits.d.ts +58 -0
  123. package/dist/exec/threads/limits.js +35 -0
  124. package/dist/exec/threads/outcome.d.ts +47 -0
  125. package/dist/exec/threads/outcome.js +26 -0
  126. package/dist/exec/threads/registry.d.ts +81 -0
  127. package/dist/exec/threads/registry.js +427 -0
  128. package/dist/exec/threads/runtime.d.ts +107 -0
  129. package/dist/exec/threads/runtime.js +523 -0
  130. package/dist/exec/threads/thread-source.d.ts +20 -0
  131. package/dist/exec/threads/thread-source.js +134 -0
  132. package/dist/fast/limits.d.ts +54 -0
  133. package/dist/fast/limits.js +54 -0
  134. package/dist/fast/summary-call.d.ts +72 -0
  135. package/dist/fast/summary-call.js +133 -0
  136. package/dist/fast/summary-prompt.d.ts +106 -0
  137. package/dist/fast/summary-prompt.js +320 -0
  138. package/dist/fast/task-summary-types.d.ts +66 -0
  139. package/dist/fast/task-summary-types.js +17 -0
  140. package/dist/logging/logger.d.ts +1 -1
  141. package/dist/proc/limits.d.ts +7 -0
  142. package/dist/proc/limits.js +7 -0
  143. package/dist/proc/log-mirror.d.ts +21 -4
  144. package/dist/proc/log-mirror.js +29 -7
  145. package/dist/proc/output-streams.d.ts +50 -0
  146. package/dist/proc/output-streams.js +54 -0
  147. package/dist/skills/service.d.ts +41 -0
  148. package/dist/skills/service.js +62 -0
  149. package/dist/skills/triage.d.ts +194 -0
  150. package/dist/skills/triage.js +393 -0
  151. package/dist/team/forced-dispatch.d.ts +95 -0
  152. package/dist/team/forced-dispatch.js +208 -0
  153. package/dist/team/limits.d.ts +21 -1
  154. package/dist/team/limits.js +21 -1
  155. package/dist/team/normalize.d.ts +15 -0
  156. package/dist/team/normalize.js +21 -4
  157. package/dist/team/prompt.d.ts +6 -3
  158. package/dist/team/prompt.js +9 -5
  159. package/dist/team/runtime.d.ts +9 -0
  160. package/dist/team/runtime.js +8 -0
  161. package/dist/team/subagent.d.ts +15 -0
  162. package/dist/team/subagent.js +45 -1
  163. package/dist/team/task-tool.js +11 -7
  164. package/dist/tools/bash-tool.d.ts +10 -0
  165. package/dist/tools/bash-tool.js +71 -6
  166. package/dist/tools/human-input.d.ts +35 -2
  167. package/dist/tools/human-input.js +38 -0
  168. package/dist/tools/index.d.ts +93 -1
  169. package/dist/tools/index.js +112 -3
  170. package/dist/tools/plan-tools.d.ts +20 -12
  171. package/dist/tools/plan-tools.js +24 -17
  172. package/dist/tools/tool-gate.d.ts +100 -0
  173. package/dist/tools/tool-gate.js +87 -0
  174. package/dist/ui/App.js +238 -12
  175. package/dist/ui/Header.d.ts +28 -16
  176. package/dist/ui/Header.js +21 -19
  177. package/dist/ui/StatusBar.d.ts +1 -0
  178. package/dist/ui/StatusBar.js +1 -0
  179. package/dist/ui/Transcript.js +1 -1
  180. package/dist/ui/entries/CompactionCard.d.ts +11 -1
  181. package/dist/ui/entries/CompactionCard.js +65 -4
  182. package/dist/ui/interaction-hints.js +6 -0
  183. package/dist/ui/layout/OverlayFrame.d.ts +18 -0
  184. package/dist/ui/layout/OverlayFrame.js +19 -4
  185. package/dist/ui/layout/header-hints.d.ts +55 -62
  186. package/dist/ui/layout/header-hints.js +59 -166
  187. package/dist/ui/layout/header-left.d.ts +115 -0
  188. package/dist/ui/layout/header-left.js +206 -0
  189. package/dist/ui/layout/shortcut-panel-layout.d.ts +191 -0
  190. package/dist/ui/layout/shortcut-panel-layout.js +321 -0
  191. package/dist/ui/layout/status-detail-layout.js +5 -1
  192. package/dist/ui/layout/status-layout.d.ts +10 -0
  193. package/dist/ui/layout/status-layout.js +18 -1
  194. package/dist/ui/layout/task-summary-cell.d.ts +61 -0
  195. package/dist/ui/layout/task-summary-cell.js +65 -0
  196. package/dist/ui/overlays/HelpOverlay.d.ts +12 -0
  197. package/dist/ui/overlays/HelpOverlay.js +29 -2
  198. package/dist/ui/overlays/SettingsScreen.js +29 -5
  199. package/dist/ui/overlays/ShortcutPanel.d.ts +39 -0
  200. package/dist/ui/overlays/ShortcutPanel.js +106 -0
  201. package/dist/ui/overlays/TeamFanoutPage.d.ts +35 -0
  202. package/dist/ui/overlays/TeamFanoutPage.js +78 -0
  203. package/dist/ui/overlays/model-profile-state.d.ts +19 -1
  204. package/dist/ui/overlays/model-profile-state.js +12 -0
  205. package/dist/ui/overlays/team-fanout-state.d.ts +73 -0
  206. package/dist/ui/overlays/team-fanout-state.js +97 -0
  207. package/dist/ui/shortcuts.d.ts +81 -0
  208. package/dist/ui/shortcuts.js +221 -0
  209. package/package.json +2 -2
@@ -13,6 +13,10 @@
13
13
  * the same reason: a shape, erased at compile time.
14
14
  */
15
15
  import type { ServiceStatus } from '../proc/types.js';
16
+ import type { AgentScope, DecisionInstallPayload, DecisionKind, DecisionToolPayload } from './bridge/protocol.js';
17
+ import type { ExecThreadEndReason } from './threads/limits.js';
18
+ import type { HookPoint } from './hooks/limits.js';
19
+ import type { NormalizedPlan, NormalizedQuestion } from '../tools/human-input.js';
16
20
  /**
17
21
  * `EXEC_SCHEMA_VERSION` appears on `system` and on `result` and NOWHERE ELSE.
18
22
  * ADDING A FIELD OR AN EVENT TYPE NEVER BUMPS IT; renaming or removing one does.
@@ -23,9 +27,38 @@ import type { ServiceStatus } from '../proc/types.js';
23
27
  */
24
28
  export declare const EXEC_SCHEMA_VERSION = 1;
25
29
  /** The three baselines of section 4.2. `plan` is a MODE, never a filter. */
26
- export type ExecPermissionMode = 'auto' | 'plan' | 'strict';
30
+ export type ExecPermissionMode = 'auto' | 'plan' | 'strict' | 'ask';
27
31
  export type ExecOutputFormat = 'text' | 'json' | 'stream-json';
28
- export type ExecInputFormat = 'text' | 'stream-json';
32
+ /**
33
+ * `bridge` is `stream-json` plus a request/response channel on stdin
34
+ * (headless-host-bridge). It REQUIRES `--output-format stream-json`, for the same
35
+ * structural reason `stream-json` does: a per-event write is where `seq` is
36
+ * stamped, and `json` writes one object at the very end.
37
+ */
38
+ export type ExecInputFormat = 'text' | 'stream-json' | 'bridge';
39
+ /**
40
+ * Present on every event a bridge caller can correlate. All five are OPTIONAL and
41
+ * absent in `stream-json` mode, which is what keeps that stream byte-identical
42
+ * (AC-2): a field that is not set is not serialized.
43
+ *
44
+ * `seq` is stamped by `BridgeWriter`, never by an emitter, so "gap-free" is a
45
+ * property of one loop rather than of every construction site.
46
+ */
47
+ export interface ExecCorrelation {
48
+ seq?: number;
49
+ requestId?: string;
50
+ /**
51
+ * WHICH CONVERSATION THIS EVENT BELONGS TO (headless-threads-and-lifecycle-hooks
52
+ * section 2.3.10, I-T10).
53
+ *
54
+ * ABSENT OUTSIDE THREAD MODE, which is what keeps `stream-json` byte-identical
55
+ * (I-C1): a field that is not set is not serialized. Inside thread mode it is stamped
56
+ * on EVERY line that carries a `sessionId` - see `ExecRunner.emit`, which is the one
57
+ * place that can promise it, plus the six explicit `bridge.write` sites that bypass
58
+ * `emit()`.
59
+ */
60
+ threadId?: string;
61
+ }
29
62
  /**
30
63
  * Why the run ended.
31
64
  *
@@ -58,7 +91,7 @@ export interface ExecErrorInfo {
58
91
  code: string;
59
92
  message: string;
60
93
  }
61
- export interface ExecSystemInitEvent {
94
+ export interface ExecSystemInitEvent extends ExecCorrelation {
62
95
  type: 'system';
63
96
  subtype: 'init';
64
97
  schemaVersion: number;
@@ -87,6 +120,16 @@ export interface ExecSystemInitEvent {
87
120
  * wrong costs the user their conversation, so this build states it.
88
121
  */
89
122
  capabilities?: string[];
123
+ /**
124
+ * `2` when this run speaks the bridge protocol, absent otherwise.
125
+ *
126
+ * A NEW OPTIONAL FIELD, so `EXEC_SCHEMA_VERSION` does not move (file header).
127
+ * It exists because `capabilities` answers "which frames may I send" and this
128
+ * answers "what shape are they" - and only the second can ever be bumped
129
+ * incompatibly. A caller that checks it before `includes('bridge')` is doing the
130
+ * two-step in the right order.
131
+ */
132
+ protocolVersion?: number;
90
133
  /**
91
134
  * Absolute path of the session file THIS run reads and writes; `null` under
92
135
  * `--no-save-session` (web-use-durable-control-and-tier-accountability W2).
@@ -111,8 +154,27 @@ export interface ExecSystemInitEvent {
111
154
  * latter.
112
155
  */
113
156
  sessionFile?: string | null;
157
+ /**
158
+ * Thread mode only: the pool this run opened with (section 4.3 / AC-8).
159
+ *
160
+ * DECLARED HERE BECAUSE THE LINE CARRIES IT: an object literal spread into a return value
161
+ * escapes excess-property checking, so a misspelled key or a dropped field compiles and
162
+ * then silently disappears from the wire. `default` names the thread a caller is already
163
+ * talking to when it sends no `threadId`.
164
+ */
165
+ threads?: {
166
+ maxOpen: number;
167
+ concurrency: number;
168
+ default: string;
169
+ };
170
+ /**
171
+ * `true` when this run accepts `hook_register` / `hook_answer`, i.e. whenever it speaks the
172
+ * bridge protocol (section 4.6). A CAPABILITY, NOT A COUNT: zero hooks registered is still
173
+ * a hook-capable run.
174
+ */
175
+ hooks?: boolean;
114
176
  }
115
- export interface ExecUserEvent {
177
+ export interface ExecUserEvent extends ExecCorrelation {
116
178
  type: 'user';
117
179
  sessionId: string;
118
180
  turn: number;
@@ -121,11 +183,18 @@ export interface ExecUserEvent {
121
183
  * `todo_continue` marks a message the CLI generated on the caller's behalf
122
184
  * through the follow-through loop. A wrapper replaying a transcript has to be
123
185
  * able to tell those apart from what it actually sent.
186
+ *
187
+ * `result_repair` is the second such message (headless-request-scope-and-typed-
188
+ * result section 2.3.5): the CLI telling the model that its `submit_result` was
189
+ * rejected, or never happened at all. A NEW MEMBER OF AN EXISTING UNION, which is
190
+ * the additive shape this file's header allows - a consumer that only knows the
191
+ * first two members already has to handle a value it does not recognise, and a
192
+ * value it does not recognise must not change what it does with the other two.
124
193
  */
125
- source: 'caller' | 'todo_continue';
194
+ source: 'caller' | 'todo_continue' | 'result_repair';
126
195
  }
127
196
  /** One caller input and its complete automatic follow-through chain. */
128
- export interface ExecTurnStateEvent {
197
+ export interface ExecTurnStateEvent extends ExecCorrelation {
129
198
  type: 'turn_state';
130
199
  sessionId: string;
131
200
  requestSeq: number;
@@ -152,13 +221,13 @@ export interface ExecThinkingEvent {
152
221
  turn: number;
153
222
  text: string;
154
223
  }
155
- export interface ExecAssistantEvent {
224
+ export interface ExecAssistantEvent extends ExecCorrelation {
156
225
  type: 'assistant';
157
226
  sessionId: string;
158
227
  turn: number;
159
228
  text: string;
160
229
  }
161
- export interface ExecToolCallEvent {
230
+ export interface ExecToolCallEvent extends ExecCorrelation {
162
231
  type: 'tool_call';
163
232
  sessionId: string;
164
233
  turn: number;
@@ -166,7 +235,7 @@ export interface ExecToolCallEvent {
166
235
  name: string;
167
236
  input: Record<string, unknown>;
168
237
  }
169
- export interface ExecToolResultEvent {
238
+ export interface ExecToolResultEvent extends ExecCorrelation {
170
239
  type: 'tool_result';
171
240
  sessionId: string;
172
241
  turn: number;
@@ -352,6 +421,135 @@ export interface ExecRetryEvent {
352
421
  errorType: string;
353
422
  delayMs: number;
354
423
  }
424
+ /**
425
+ * The bridge control channel (headless-host-bridge §4.2.2).
426
+ *
427
+ * SEVEN ADDITIVE EVENT TYPES, AND `EXEC_SCHEMA_VERSION` DOES NOT MOVE - the rule
428
+ * this file's header states: adding a field or an event type never bumps it.
429
+ *
430
+ * They exist because a callback API has to be a wire API for an embedder, and a
431
+ * wire API needs an id, an answer and an outcome that can be logged. `ack` proves
432
+ * a request was received; `decision_request` / `decision_resolved` are one
433
+ * decision's two ends, and keeping BOTH is what makes "the caller denied it" and
434
+ * "the caller never answered" distinguishable after the fact - a run that only
435
+ * recorded the refusal would look identical to a timeout in every transcript.
436
+ */
437
+ export interface ExecAckEvent extends ExecCorrelation {
438
+ type: 'ack';
439
+ /** The caller's own id, echoed. Absent when the frame carried none. */
440
+ id?: string;
441
+ accepted: boolean;
442
+ /** Why it was refused, when it was: `not_running`, `plan_mode_read_only`, ... */
443
+ code?: string;
444
+ message?: string;
445
+ /** `false` means this frame started driving a turn right away. */
446
+ queued?: boolean;
447
+ /** The runner's own turn counter, known only when the turn begins. */
448
+ requestSeq?: number;
449
+ /** `steer` only: the receipt id Core will confirm in a `steering` event. */
450
+ steeringId?: string;
451
+ /**
452
+ * `interrupt{requestId}` only: the message had not started, so it was deleted
453
+ * from the queue rather than aborting a turn. A caller that asked to withdraw
454
+ * its own queued message needs this to know nothing else was disturbed.
455
+ */
456
+ cancelled?: 'queued';
457
+ /** `tool_register` only: whose tool array was replaced. Always `'lead'`. */
458
+ scope?: 'lead';
459
+ /** `tool_register` only: the names that are now registered. */
460
+ tools?: string[];
461
+ /** `tool_register` only: the first turn the new set can be used in. */
462
+ appliedAtTurn?: number;
463
+ /**
464
+ * `thread_open` only: the session id this thread writes (section 2.3.2's ack table).
465
+ *
466
+ * Stated rather than left to be derived: `explicitSessionId` wins and the default thread
467
+ * reuses the process id, so a caller that guessed `${base}-${threadId}` would be wrong in
468
+ * two of the three cases.
469
+ */
470
+ sessionId?: string;
471
+ /** `thread_open` only: the thread's session file, `null` under `--no-save-session`. */
472
+ sessionFile?: string | null;
473
+ /** `thread_open` only. Always `false`: a thread never resumes (section 1.4). */
474
+ resumed?: boolean;
475
+ /** `hook_register` only: the ids that are now live, in registration order. */
476
+ hooks?: string[];
477
+ /** `thread_list` only: one summary per thread, in open order. */
478
+ threads?: Array<{
479
+ threadId: string;
480
+ sessionId: string;
481
+ state: 'opening' | 'open' | 'closing' | 'closed';
482
+ requests: number;
483
+ turns: number;
484
+ inFlight: boolean;
485
+ }>;
486
+ /** Non-fatal notes about an ACCEPTED frame (a clipped description, say). */
487
+ warnings?: string[];
488
+ }
489
+ export interface ExecDecisionRequestEvent {
490
+ type: 'decision_request';
491
+ requestId: string;
492
+ sessionId: string;
493
+ kind: DecisionKind;
494
+ createdAt: number;
495
+ deadlineAt: number;
496
+ timeoutMs: number;
497
+ tool?: DecisionToolPayload;
498
+ questions?: NormalizedQuestion[];
499
+ plan?: NormalizedPlan;
500
+ install?: DecisionInstallPayload;
501
+ /** Always present on the wire; the type keeps it optional for stub callers. */
502
+ agent?: AgentScope;
503
+ }
504
+ export interface ExecDecisionResolvedEvent {
505
+ type: 'decision_resolved';
506
+ requestId: string;
507
+ sessionId: string;
508
+ kind: DecisionKind;
509
+ /**
510
+ * `error` is the fail-closed answer to a frame that could not be trusted; it is
511
+ * distinct from `aborted` because one is the caller's fault and the other is
512
+ * the turn's.
513
+ */
514
+ outcome: 'allow' | 'deny' | 'timeout' | 'aborted' | 'error';
515
+ scope?: 'once' | 'session';
516
+ reason?: string;
517
+ }
518
+ export interface ExecToolInvokeEvent {
519
+ type: 'tool_invoke';
520
+ requestId: string;
521
+ sessionId: string;
522
+ /** Core's tool call id, so this pairs with the `tool_call` event beside it. */
523
+ callId: string;
524
+ turn: number;
525
+ tool: string;
526
+ input: Record<string, unknown>;
527
+ timeoutMs: number;
528
+ deadlineAt: number;
529
+ agent: AgentScope;
530
+ }
531
+ export interface ExecToolCancelEvent {
532
+ type: 'tool_cancel';
533
+ requestId: string;
534
+ sessionId: string;
535
+ reason: 'timeout' | 'turn_aborted' | 'host_shutdown';
536
+ }
537
+ export interface ExecPongEvent extends ExecCorrelation {
538
+ type: 'pong';
539
+ id?: string;
540
+ now: number;
541
+ }
542
+ export interface ExecSteeringEvent extends ExecCorrelation {
543
+ type: 'steering';
544
+ sessionId: string;
545
+ /**
546
+ * ARRAY, NOT A SINGLE ID (review P1-7). Core's `steering_accepted` carries
547
+ * `ids: string[]` because one drain can accept a whole batch; reporting only the
548
+ * first would leave the rest permanently unconfirmed on the caller's side, which
549
+ * is precisely what the receipt exists to prevent.
550
+ */
551
+ steeringIds: string[];
552
+ }
355
553
  export interface ExecErrorEvent {
356
554
  type: 'error';
357
555
  sessionId: string;
@@ -396,6 +594,57 @@ export interface ExecResultEvent {
396
594
  passed: number;
397
595
  failed: number;
398
596
  } | null;
597
+ /**
598
+ * The accepted structured result (headless-request-scope-and-typed-result
599
+ * section 2.3.7), or `null` when `--result-schema` was given and nothing was
600
+ * ever accepted.
601
+ *
602
+ * ABSENT, NOT `null`, WITHOUT THE FLAG (I-C1). "The field is missing" and "this
603
+ * build has no such capability" look identical in JSON, which is exactly why the
604
+ * flag-ON failure path must instead be an EXPLICIT `null`: a caller that passed
605
+ * `--result-schema` can then read one field and know whether it got what it asked
606
+ * for. Two different questions, two different encodings.
607
+ */
608
+ structured?: {
609
+ value: unknown;
610
+ schemaName: string;
611
+ schemaBytes: number;
612
+ attempts: number;
613
+ acceptedAt: number;
614
+ } | null;
615
+ /**
616
+ * The model's last prose, present only when `result.result` was REPLACED by the
617
+ * canonical JSON text - that is, when a structured result was accepted.
618
+ *
619
+ * The README's `aragon exec --output-format json ... | jq -r .result` idiom has to
620
+ * keep working, and under a schema the answer IS the object, so `result` carries
621
+ * the object and the prose moves here instead of being lost.
622
+ */
623
+ text?: string;
624
+ /**
625
+ * The thread-by-thread account, thread mode only (section 2.3.2 / AC-37).
626
+ *
627
+ * ABSENT IN EVERY NON-THREAD RUN. `items` is ordered by OPEN ORDER so a wrapper can
628
+ * zip it with the `thread_state` events it received; `usage`/`cost` on the `result`
629
+ * itself are the sums of these, which is why the same numbers appear twice - once as
630
+ * the total a single-call embedder wants, once per tenant.
631
+ */
632
+ threads?: {
633
+ opened: number;
634
+ closed: number;
635
+ /** Threads whose account reported `isError`. */
636
+ failed: number;
637
+ items: Array<{
638
+ threadId: string;
639
+ sessionId: string;
640
+ requests: number;
641
+ turns: number;
642
+ usage: ExecUsage;
643
+ cost: ExecCost;
644
+ isError: boolean;
645
+ reason: ExecThreadEndReason;
646
+ }>;
647
+ };
399
648
  error: ExecErrorInfo | null;
400
649
  }
401
650
  /**
@@ -421,7 +670,135 @@ export interface ExecJobEvent {
421
670
  progress?: string;
422
671
  exitCode?: number | null;
423
672
  }
424
- export type ExecEvent = ExecSystemInitEvent | ExecUserEvent | ExecTurnStateEvent | ExecProgressEvent | ExecTextDeltaEvent | ExecThinkingEvent | ExecAssistantEvent | ExecToolCallEvent | ExecToolResultEvent | ExecTodoEvent | ExecTeamEvent | ExecFastReviewEvent | ExecFastTierEvent | ExecCompactionEvent | ExecRetryEvent | ExecJobEvent | ExecErrorEvent | ExecResultEvent;
673
+ /**
674
+ * Why ONE request ended (headless-request-scope-and-typed-result section 2.4.6).
675
+ *
676
+ * A SEPARATE UNION FROM `ExecStopReason`, ON PURPOSE. Adding members to the process
677
+ * union would break every downstream exhaustive `switch` over it, and the two answer
678
+ * genuinely different questions: "why did this process end" and "why did this
679
+ * request end". A request can stop while the process keeps serving the next one -
680
+ * that is the entire point of the feature - so the two must not share a vocabulary
681
+ * that implies they move together.
682
+ */
683
+ export type ExecRequestStopReason = 'completed' | 'max_turns' | 'timeout' | 'cancelled' | 'error';
684
+ /** A request-level failure. `code` is stable; `message` is prose for a human. */
685
+ export interface ExecRequestError {
686
+ code: string;
687
+ message: string;
688
+ }
689
+ /**
690
+ * One request's account: what it cost, how it ended, and whether the caller got
691
+ * what it asked for (section 2.4.6).
692
+ *
693
+ * BRIDGE MODE ONLY (I-C3). The v1 input format (`--input-format stream-json`) is
694
+ * already a multi-prompt loop, but emitting this event there would change its byte
695
+ * output, which is the one thing the compatibility strategy cannot allow.
696
+ */
697
+ export interface ExecRequestEndEvent extends ExecCorrelation {
698
+ type: 'request_end';
699
+ sessionId: string;
700
+ /** The caller's id, or the CLI's `prompt_<n>` when the frame carried none. */
701
+ requestId: string;
702
+ /**
703
+ * The same number space as `turn_state.requestSeq` and the `ack` for this
704
+ * request.
705
+ *
706
+ * OPTIONAL, AND THAT IS NOT SLOPPINESS (review P0-2). The number is minted at the
707
+ * moment a prompt is SERVED. A prompt cancelled while still in the queue was
708
+ * never served, so it never got one - and inventing one would collide with a
709
+ * later real request. Absent is the honest answer.
710
+ */
711
+ requestSeq?: number;
712
+ stopReason: ExecRequestStopReason;
713
+ isError: boolean;
714
+ /** Turns this request took, including repair and follow-through continuations. */
715
+ turns: number;
716
+ durationMs: number;
717
+ usage: ExecUsage;
718
+ cost: ExecCost;
719
+ /** This request's last assistant text, clipped to `REQUEST_LIMITS.maxResultChars`. */
720
+ result: string;
721
+ /** The accepted structured result, or `null`; absent when no schema was given. */
722
+ structured?: {
723
+ value: unknown;
724
+ schemaName: string;
725
+ attempts: number;
726
+ } | null;
727
+ error: ExecRequestError | null;
728
+ }
729
+ /** Everything `request_end` needs; `type` is stamped by the builder below. */
730
+ export type ExecRequestEndParams = Omit<ExecRequestEndEvent, 'type'>;
731
+ export declare function buildRequestEndEvent(params: ExecRequestEndParams): ExecRequestEndEvent;
732
+ /**
733
+ * One thread's lifecycle (headless-threads-and-lifecycle-hooks section 2.3.2).
734
+ *
735
+ * `phase` HAS THREE MEMBERS AND NOT FIVE, deliberately: `busy` / `idle` are already
736
+ * carried by `turn_state`, and two state machines describing one fact will eventually
737
+ * disagree. This file's own history has one such lesson (`ui/team-status.ts`), which is
738
+ * why the smaller set is the one that survived.
739
+ */
740
+ export interface ExecThreadStateEvent extends ExecCorrelation {
741
+ type: 'thread';
742
+ sessionId: string;
743
+ threadId: string;
744
+ phase: 'opened' | 'closing' | 'closed';
745
+ /** Only on `'closed'`. */
746
+ reason?: ExecThreadEndReason;
747
+ /** Only on `'closed'`: this thread's account. */
748
+ requests?: number;
749
+ turns?: number;
750
+ durationMs?: number;
751
+ usage?: ExecUsage;
752
+ cost?: ExecCost;
753
+ isError?: boolean;
754
+ }
755
+ /**
756
+ * One hook round trip, outward (headless-threads-and-lifecycle-hooks section 2.4.2).
757
+ *
758
+ * `requestId` IS REQUIRED, like `decision_request`'s, and for the same reason: a hook
759
+ * request that cannot name the request it belongs to cannot be timed out, cancelled or
760
+ * audited. `prompt_pre` therefore fires after `scope.begin()` and before the `ack`, so
761
+ * the id always exists.
762
+ */
763
+ export interface ExecHookRequestEvent extends ExecCorrelation {
764
+ type: 'hook_request';
765
+ sessionId: string;
766
+ requestId: string;
767
+ /**
768
+ * THIS ROUND TRIP'S OWN ID: what `hook_answer.respondsTo` must name.
769
+ *
770
+ * IT IS NOT `requestId`, AND THAT IS A GAP FOUND DURING IMPLEMENTATION. One hook fires many
771
+ * times inside one request, so the semantic id cannot identify a round trip - while
772
+ * `decision_request` does not have the problem, because one decision belongs to one request.
773
+ * Recorded in the spec's implementation-notes section.
774
+ */
775
+ hookRequestId: string;
776
+ hookId: string;
777
+ point: HookPoint;
778
+ /** The point-specific payload; see the tool/prompt/turn modules below. */
779
+ payload: Record<string, unknown>;
780
+ }
781
+ /**
782
+ * The other end of exactly one `hook_request` (I-H5).
783
+ *
784
+ * `outcome` is the operational fact and `code` the stable reason; keeping BOTH is what
785
+ * makes "the hook timed out" and "the hook answered nonsense" distinguishable after the
786
+ * fact, which a single boolean never is.
787
+ */
788
+ export interface ExecHookResolvedEvent extends ExecCorrelation {
789
+ type: 'hook_resolved';
790
+ sessionId: string;
791
+ requestId: string;
792
+ /** The same `hookRequestId` its request carried, so the pair is unambiguous. */
793
+ hookRequestId?: string;
794
+ hookId: string;
795
+ point: HookPoint;
796
+ outcome: 'answered' | 'timeout' | 'aborted' | 'invalid' | 'saturated' | 'unregistered';
797
+ action?: string;
798
+ /** Present when `outcome !== 'answered'`, and for a rejected answer. */
799
+ code?: string;
800
+ }
801
+ export type ExecEvent = ExecSystemInitEvent | ExecUserEvent | ExecTurnStateEvent | ExecProgressEvent | ExecTextDeltaEvent | ExecThinkingEvent | ExecAssistantEvent | ExecToolCallEvent | ExecToolResultEvent | ExecTodoEvent | ExecTeamEvent | ExecFastReviewEvent | ExecFastTierEvent | ExecCompactionEvent | ExecRetryEvent | ExecJobEvent | ExecAckEvent | ExecDecisionRequestEvent | ExecDecisionResolvedEvent | ExecToolInvokeEvent | ExecToolCancelEvent | ExecPongEvent | ExecSteeringEvent | ExecThreadStateEvent | ExecHookRequestEvent | ExecHookResolvedEvent | ExecErrorEvent | ExecRequestEndEvent | ExecResultEvent;
425
802
  /** Everything `system/init` needs that the runner cannot derive for itself. */
426
803
  export interface ExecInitParams {
427
804
  sessionId: string;
@@ -436,11 +813,54 @@ export interface ExecInitParams {
436
813
  permissionMode: ExecPermissionMode;
437
814
  tools: string[];
438
815
  resumed: boolean;
816
+ /**
817
+ * Thread mode only: the pool this run opened with, so a wrapper can decide whether
818
+ * opening a fifth thread is worth the round trip (section 2.3.2 / AC-8).
819
+ *
820
+ * ABSENT ON EVERY NON-THREAD RUN (I-C1). `default` is stated rather than implied: a caller
821
+ * that never sends `threadId` needs to know which conversation it is already talking to.
822
+ */
823
+ threads?: {
824
+ maxOpen: number;
825
+ concurrency: number;
826
+ default: string;
827
+ };
828
+ /**
829
+ * `true` when this run accepts `hook_register` / `hook_answer`, i.e. in bridge mode.
830
+ *
831
+ * A CAPABILITY, NOT A COUNT: zero hooks registered is still a hook-capable run, and a
832
+ * caller that cannot tell the two apart would conclude "hooks are unsupported" from
833
+ * "I have not registered one".
834
+ *
835
+ * IT IS NOT GATED ON `--threads`, so a bridge run's init line gains this key and the
836
+ * `hooks` capability string. That is an ADDITIVE change to a v1 bridge line - a caller
837
+ * that compares capability SETS is unaffected, one that compares literals is not - and it
838
+ * is the price of "declare it where it is true" (section 4.6). `threads` is the key that
839
+ * stays absent, which is what keeps a non-thread run byte-identical.
840
+ */
841
+ hooks?: boolean;
439
842
  /**
440
843
  * Absolute path of the session file this run reads and writes; `null` under
441
844
  * `--no-save-session`. See `ExecSystemInitEvent.sessionFile`.
442
845
  */
443
846
  sessionFile?: string | null;
847
+ /**
848
+ * `true` under `--input-format bridge`. Stamped by `runExec`, never by a caller,
849
+ * and it is what adds `protocolVersion` plus the bridge capability strings -
850
+ * declaring `bridge` in a `stream-json` run would tell a wrapper to send frames
851
+ * nothing is reading.
852
+ */
853
+ bridge?: boolean;
854
+ /**
855
+ * `true` when this run carries `--result-schema`, so `submit_result` is
856
+ * registered and the `<structured_result>` block is in the system prompt.
857
+ *
858
+ * A SEPARATE FLAG FROM THE OPTIONS, not derived from them here: this module is
859
+ * pure types and builders and must not learn what a flag is. `runExec` stamps it,
860
+ * which also keeps the capability list a fact about the run rather than about the
861
+ * argv it was started with.
862
+ */
863
+ structuredResult?: boolean;
444
864
  }
445
865
  /**
446
866
  * What this build announces in `system/init`.
@@ -470,6 +890,41 @@ export interface ExecInitParams {
470
890
  * is still resumable instead of guessing the path.
471
891
  */
472
892
  export declare const EXEC_CAPABILITIES: readonly string[];
893
+ /**
894
+ * What a run must be able to observe about ITSELF to answer "which capability
895
+ * strings do I announce".
896
+ *
897
+ * AN OBJECT, NOT A BOOLEAN (review P0-4). Until this round the list was assembled
898
+ * from one flag (`bridge`), and `structured_result` cannot join it: that capability
899
+ * is TRUE ONLY FOR A RUN THAT ACTUALLY CARRIES `--result-schema`. Appending it to
900
+ * the `EXEC_CAPABILITIES` constant would make a run without the flag announce a
901
+ * capability it does not have, which is precisely the failure the field exists to
902
+ * prevent - a caller would send a schema-shaped expectation and get prose back.
903
+ *
904
+ * Named for what it describes: the capabilities of THIS RUN, not of this build.
905
+ */
906
+ export interface ExecCapabilities {
907
+ bridge: boolean;
908
+ /** True ONLY when this run carries `--result-schema`. */
909
+ structuredResult: boolean;
910
+ /** True only for a run that opened `--threads` (review P1-5). */
911
+ threads?: boolean;
912
+ /** True only for a bridge run, which is the only run that accepts hook frames. */
913
+ hooks?: boolean;
914
+ }
915
+ /**
916
+ * The capability strings one run announces: `EXEC_CAPABILITIES` plus whatever its
917
+ * own switches add, APPEND ONLY.
918
+ *
919
+ * A SEPARATE LIST RATHER THAN A LONGER `EXEC_CAPABILITIES`, because declaring
920
+ * `bridge` in a `stream-json` run would tell a wrapper to send frames nothing is
921
+ * reading - and it would send them into a stdin parser that ignores them, so the
922
+ * caller's first real symptom would be "no decision_request ever arrives".
923
+ *
924
+ * `'structured_result'` IS PUSHED HERE AND NOWHERE ELSE. There is exactly one
925
+ * derivation point, so `system/init` and `aragon info` cannot disagree.
926
+ */
927
+ export declare function execCapabilities(caps: ExecCapabilities): string[];
473
928
  export declare function buildInitEvent(params: ExecInitParams): ExecSystemInitEvent;
474
929
  /** Everything `result` needs. `schemaVersion` is stamped here, never by a caller. */
475
930
  export type ExecResultParams = Omit<ExecResultEvent, 'type' | 'schemaVersion'>;
@@ -1,3 +1,7 @@
1
+ // BY VALUE, AND THE DIRECTION IS ONE-WAY: `bridge/protocol.ts` never imports this
2
+ // module, so a wrapper reading `system/init` and a caller writing frames share one
3
+ // copy of the two bridge constants instead of two that can disagree.
4
+ import { BRIDGE_CAPABILITIES, BRIDGE_PROTOCOL_VERSION } from './bridge/protocol.js';
1
5
  /**
2
6
  * `EXEC_SCHEMA_VERSION` appears on `system` and on `result` and NOWHERE ELSE.
3
7
  * ADDING A FIELD OR AN EVENT TYPE NEVER BUMPS IT; renaming or removing one does.
@@ -7,6 +11,9 @@
7
11
  * additive change and every wrapper would pin an exact number forever.
8
12
  */
9
13
  export const EXEC_SCHEMA_VERSION = 1;
14
+ export function buildRequestEndEvent(params) {
15
+ return { type: 'request_end', ...params };
16
+ }
10
17
  /**
11
18
  * What this build announces in `system/init`.
12
19
  *
@@ -49,7 +56,40 @@ export const EXEC_CAPABILITIES = [
49
56
  // asking for this string, and an older wrapper never asks.
50
57
  'todo-acceptance',
51
58
  ];
59
+ /**
60
+ * The capability strings one run announces: `EXEC_CAPABILITIES` plus whatever its
61
+ * own switches add, APPEND ONLY.
62
+ *
63
+ * A SEPARATE LIST RATHER THAN A LONGER `EXEC_CAPABILITIES`, because declaring
64
+ * `bridge` in a `stream-json` run would tell a wrapper to send frames nothing is
65
+ * reading - and it would send them into a stdin parser that ignores them, so the
66
+ * caller's first real symptom would be "no decision_request ever arrives".
67
+ *
68
+ * `'structured_result'` IS PUSHED HERE AND NOWHERE ELSE. There is exactly one
69
+ * derivation point, so `system/init` and `aragon info` cannot disagree.
70
+ */
71
+ export function execCapabilities(caps) {
72
+ const list = [...EXEC_CAPABILITIES];
73
+ if (caps.structuredResult)
74
+ list.push('structured_result');
75
+ // THE TWO NEW STRINGS FOLLOW THE SAME RULE as `structured_result` (review P1-5):
76
+ // conditions on THIS RUN, pushed from the ONE derivation point. `hooks` keys off
77
+ // `bridge` rather than off `threads` because hooks need only the frame channel - tying
78
+ // them to threads would force the commonest embedding shape ("one conversation, one
79
+ // redactor") to sign a multi-tenant contract it does not use (section 9.9).
80
+ if (caps.threads)
81
+ list.push('threads');
82
+ if (caps.hooks)
83
+ list.push('hooks');
84
+ if (caps.bridge)
85
+ list.push(...BRIDGE_CAPABILITIES);
86
+ return list;
87
+ }
52
88
  export function buildInitEvent(params) {
89
+ const bridge = params.bridge === true;
90
+ const structuredResult = params.structuredResult === true;
91
+ const threads = params.threads !== undefined;
92
+ const hooks = params.hooks === true;
53
93
  return {
54
94
  type: 'system',
55
95
  subtype: 'init',
@@ -62,7 +102,13 @@ export function buildInitEvent(params) {
62
102
  permissionMode: params.permissionMode,
63
103
  tools: params.tools,
64
104
  resumed: params.resumed,
65
- capabilities: [...EXEC_CAPABILITIES],
105
+ capabilities: execCapabilities({ bridge, structuredResult, threads, hooks }),
106
+ ...(bridge ? { protocolVersion: BRIDGE_PROTOCOL_VERSION } : {}),
107
+ // CONDITIONAL SPREADS, so a non-thread run's init line keeps the exact key set it
108
+ // had before this round (I-C1). The counters live in the type, the absence is the
109
+ // proof.
110
+ ...(params.threads !== undefined ? { threads: params.threads } : {}),
111
+ ...(hooks ? { hooks: true } : {}),
66
112
  // `?? null` rather than a conditional spread: the field's contract is
67
113
  // three-valued for a CONSUMER (`undefined` = an old build that cannot say,
68
114
  // `null` = this run saves nothing, a string = here it is), and omitting it