tmux-ide 2.6.1 → 2.8.0

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 (154) hide show
  1. package/README.md +36 -14
  2. package/bin/cli.js +4174 -1235
  3. package/bin/cli.ts +426 -72
  4. package/package.json +10 -6
  5. package/packages/contracts/src/__tests__/control.test.ts +154 -0
  6. package/packages/contracts/src/control.ts +217 -0
  7. package/packages/contracts/src/index.ts +1 -0
  8. package/packages/daemon/dist/agent-explain.d.ts +8 -1
  9. package/packages/daemon/dist/agent-explain.js +19 -3
  10. package/packages/daemon/dist/control/client.d.ts +23 -0
  11. package/packages/daemon/dist/control/client.js +105 -0
  12. package/packages/daemon/dist/control/dispatch.d.ts +34 -0
  13. package/packages/daemon/dist/control/dispatch.js +83 -0
  14. package/packages/daemon/dist/control/fanout.d.ts +19 -0
  15. package/packages/daemon/dist/control/fanout.js +37 -0
  16. package/packages/daemon/dist/control/frames.d.ts +23 -0
  17. package/packages/daemon/dist/control/frames.js +37 -0
  18. package/packages/daemon/dist/control/lifecycle.d.ts +45 -0
  19. package/packages/daemon/dist/control/lifecycle.js +114 -0
  20. package/packages/daemon/dist/control/server.d.ts +16 -0
  21. package/packages/daemon/dist/control/server.js +214 -0
  22. package/packages/daemon/dist/control/verbs.d.ts +11 -0
  23. package/packages/daemon/dist/control/verbs.js +91 -0
  24. package/packages/daemon/dist/doctor.d.ts +18 -0
  25. package/packages/daemon/dist/doctor.js +105 -15
  26. package/packages/daemon/dist/lib/agent-discovery.d.ts +27 -2
  27. package/packages/daemon/dist/lib/agent-discovery.js +29 -14
  28. package/packages/daemon/dist/lib/app-config.d.ts +106 -0
  29. package/packages/daemon/dist/lib/app-config.js +104 -5
  30. package/packages/daemon/dist/lib/manifest-pack.d.ts +79 -0
  31. package/packages/daemon/dist/lib/manifest-pack.js +232 -0
  32. package/packages/daemon/dist/lib/state-home.d.ts +2 -0
  33. package/packages/daemon/dist/lib/state-home.js +12 -0
  34. package/packages/daemon/dist/lib/tui-binary.d.ts +57 -0
  35. package/packages/daemon/dist/lib/tui-binary.js +130 -0
  36. package/packages/daemon/dist/lib/update-check.js +5 -0
  37. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +34 -0
  38. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  39. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/PkgInfo +1 -0
  40. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/AppIcon.icns +0 -0
  41. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  42. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +139 -0
  43. package/packages/daemon/dist/restore.d.ts +35 -8
  44. package/packages/daemon/dist/restore.js +52 -15
  45. package/packages/daemon/dist/send.d.ts +33 -1
  46. package/packages/daemon/dist/send.js +32 -19
  47. package/packages/daemon/dist/widgets/explorer/breadcrumbs.d.ts +1 -1
  48. package/packages/daemon/dist/widgets/explorer/footer.d.ts +1 -1
  49. package/packages/daemon/dist/widgets/explorer/tree.d.ts +1 -1
  50. package/packages/daemon/dist/widgets/lib/help-overlay.d.ts +1 -1
  51. package/packages/daemon/dist/widgets/setup/agent-naming.d.ts +1 -1
  52. package/packages/daemon/dist/widgets/setup/config-tree.d.ts +1 -1
  53. package/packages/daemon/dist/widgets/setup/detect-panel.d.ts +1 -1
  54. package/packages/daemon/dist/widgets/setup/field-editor.d.ts +1 -1
  55. package/packages/daemon/dist/widgets/setup/footer.d.ts +1 -1
  56. package/packages/daemon/dist/widgets/setup/layout-picker.d.ts +1 -1
  57. package/packages/daemon/src/agent-explain.ts +34 -6
  58. package/packages/daemon/src/control/client.ts +128 -0
  59. package/packages/daemon/src/control/dispatch.ts +107 -0
  60. package/packages/daemon/src/control/fanout.ts +44 -0
  61. package/packages/daemon/src/control/frames.ts +40 -0
  62. package/packages/daemon/src/control/lifecycle.ts +151 -0
  63. package/packages/daemon/src/control/server.ts +237 -0
  64. package/packages/daemon/src/control/verbs.ts +118 -0
  65. package/packages/daemon/src/doctor.ts +113 -28
  66. package/packages/daemon/src/lib/agent-discovery.ts +53 -13
  67. package/packages/daemon/src/lib/app-config.ts +194 -5
  68. package/packages/daemon/src/lib/manifest-pack.ts +255 -0
  69. package/packages/daemon/src/lib/state-home.ts +13 -0
  70. package/packages/daemon/src/lib/tui-binary.ts +165 -0
  71. package/packages/daemon/src/lib/update-check.ts +5 -0
  72. package/packages/daemon/src/restore.ts +53 -15
  73. package/packages/daemon/src/send.ts +55 -21
  74. package/packages/daemon/src/tui/chrome/events.ts +4 -4
  75. package/packages/daemon/src/tui/chrome/front-door.ts +39 -0
  76. package/packages/daemon/src/tui/chrome/notify-prefs.ts +58 -0
  77. package/packages/daemon/src/tui/chrome/notify-state.ts +76 -0
  78. package/packages/daemon/src/tui/chrome/notify.ts +737 -52
  79. package/packages/daemon/src/tui/chrome/updater.ts +318 -30
  80. package/packages/daemon/src/tui/compiled.ts +11 -3
  81. package/packages/daemon/src/tui/detect/classify.ts +49 -0
  82. package/packages/daemon/src/tui/detect/manifest-loader.ts +56 -6
  83. package/packages/daemon/src/tui/detect/manifest.ts +39 -3
  84. package/packages/daemon/src/tui/detect/manifests.ts +395 -33
  85. package/packages/daemon/src/tui/detect/process-tree.ts +33 -3
  86. package/packages/daemon/src/tui/detect/session-id.ts +503 -0
  87. package/packages/daemon/src/tui/integrations/opencode.ts +121 -0
  88. package/packages/daemon/src/tui/main.ts +13 -1
  89. package/packages/daemon/src/tui/mirror/ack-writer.ts +77 -0
  90. package/packages/daemon/src/tui/mirror/agent-chip.ts +126 -0
  91. package/packages/daemon/src/tui/mirror/agent-lifecycle.ts +437 -0
  92. package/packages/daemon/src/tui/mirror/agent-rows.ts +155 -0
  93. package/packages/daemon/src/tui/mirror/app-state.ts +342 -0
  94. package/packages/daemon/src/tui/mirror/app.tsx +7048 -0
  95. package/packages/daemon/src/tui/mirror/attention.ts +110 -0
  96. package/packages/daemon/src/tui/mirror/blit.ts +186 -0
  97. package/packages/daemon/src/tui/mirror/control-client.ts +80 -9
  98. package/packages/daemon/src/tui/mirror/dialog-model.ts +298 -0
  99. package/packages/daemon/src/tui/mirror/dialog-stack.ts +367 -0
  100. package/packages/daemon/src/tui/mirror/diff-model.ts +387 -0
  101. package/packages/daemon/src/tui/mirror/editor-buffer.ts +117 -0
  102. package/packages/daemon/src/tui/mirror/file-tree.ts +322 -0
  103. package/packages/daemon/src/tui/mirror/focus-border.ts +57 -0
  104. package/packages/daemon/src/tui/mirror/folder-picker.ts +124 -0
  105. package/packages/daemon/src/tui/mirror/home-model.ts +174 -0
  106. package/packages/daemon/src/tui/mirror/host-terminal.ts +49 -0
  107. package/packages/daemon/src/tui/mirror/hosted.ts +205 -0
  108. package/packages/daemon/src/tui/mirror/input-coalescer.ts +105 -0
  109. package/packages/daemon/src/tui/mirror/layout-parse.ts +154 -0
  110. package/packages/daemon/src/tui/mirror/menu-model.ts +210 -0
  111. package/packages/daemon/src/tui/mirror/palette.ts +564 -0
  112. package/packages/daemon/src/tui/mirror/pane-mirror.ts +578 -20
  113. package/packages/daemon/src/tui/mirror/pane-surface.tsx +422 -0
  114. package/packages/daemon/src/tui/mirror/perf-tap.ts +186 -0
  115. package/packages/daemon/src/tui/mirror/resize-model.ts +85 -0
  116. package/packages/daemon/src/tui/mirror/scrollbar-model.ts +88 -0
  117. package/packages/daemon/src/tui/mirror/search-model.ts +70 -0
  118. package/packages/daemon/src/tui/mirror/selection.ts +376 -0
  119. package/packages/daemon/src/tui/mirror/session-mirror.ts +724 -0
  120. package/packages/daemon/src/tui/mirror/settings-model.ts +425 -0
  121. package/packages/daemon/src/tui/mirror/sidebar.tsx +218 -0
  122. package/packages/daemon/src/tui/mirror/size-truth.ts +130 -0
  123. package/packages/daemon/src/tui/mirror/spans.ts +46 -0
  124. package/packages/daemon/src/tui/mirror/status-grammar.ts +32 -0
  125. package/packages/daemon/src/tui/mirror/theme.ts +45 -0
  126. package/packages/daemon/src/tui/team/entry.ts +34 -7
  127. package/packages/daemon/src/tui/team/fuzzy.ts +20 -0
  128. package/packages/daemon/src/tui/team/report.ts +11 -1
  129. package/packages/daemon/src/tui/team/sessions.ts +184 -17
  130. package/packages/daemon/src/tui/team/wait.ts +144 -0
  131. package/scripts/build-macos-notifier.mjs +160 -0
  132. package/scripts/build-tui.mjs +11 -4
  133. package/scripts/perf-mirror.mjs +313 -0
  134. package/scripts/postinstall.js +8 -1
  135. package/scripts/prepublish-check.mjs +37 -1
  136. package/scripts/publish-tap.sh +55 -0
  137. package/skill/SKILL.md +110 -2
  138. package/templates/AGENTS.md +14 -7
  139. package/templates/agent-team-monorepo.yml +8 -0
  140. package/templates/agent-team-nextjs.yml +8 -0
  141. package/templates/agent-team.yml +10 -0
  142. package/templates/convex.yml +2 -0
  143. package/templates/default.yml +11 -5
  144. package/templates/go.yml +4 -0
  145. package/templates/missions.yml +6 -0
  146. package/templates/nextjs.yml +4 -0
  147. package/templates/python.yml +4 -0
  148. package/templates/skills/backend.md +5 -12
  149. package/templates/skills/frontend.md +5 -12
  150. package/templates/skills/general-worker.md +5 -12
  151. package/templates/skills/researcher.md +7 -12
  152. package/templates/skills/reviewer.md +7 -16
  153. package/templates/vite.yml +4 -0
  154. package/packages/daemon/src/tui/mirror/viewer.tsx +0 -166
@@ -8,14 +8,26 @@
8
8
  * stay minimal. Treat every matcher below as a heuristic to tune, not a
9
9
  * contract — edit freely as agent UIs change.
10
10
  *
11
- * EVIDENCE PROVENANCE. The `claude` and `codex` manifests are tuned against
12
- * REAL captured screens (see `manifest-corpus.test.ts` for the sanitized
13
- * fixtures). Each matcher cites the invariant it was built from ("seen: …").
14
- * The remaining manifests (`opencode`, `gemini`, `aider`, `copilot`) are
15
- * best-effort from public docs/common knowledge — every one of their matchers
16
- * is marked "untuned — needs real captures" and kept HIGH-PRECISION
17
- * (esc-to-interrupt / spinner / explicit y-n) so they can never false-positive
18
- * on a plain prompt. Users can override any of these with a JSON file in
11
+ * EVIDENCE PROVENANCE (per-manifest `confidence`):
12
+ * - `tuned` — built from REAL captured screens or the agent's own source
13
+ * strings. `claude`, `codex`, and `aider` are tuned: codex from live
14
+ * `tmux capture-pane` frames driven through a real turn (idle / working /
15
+ * command-approval / trust prompt), aider from the verbatim prompt strings
16
+ * in its installed source (`io.py` confirm_ask, `waiting.py` spinner).
17
+ * Each matcher cites the invariant it was built from ("seen: …").
18
+ * - `conservative` — best-effort from public docs/common knowledge
19
+ * (`opencode`, `gemini`, `copilot`, `cursor`, `goose`, `amp`, and the
20
+ * M25.4 breadth set: `devin`, `kimi`, `pi`, `grok`, `kiro`, `cline`,
21
+ * `droid`, `kilo`). Every matcher is HIGH-PRECISION (esc-to-interrupt /
22
+ * spinner / explicit y-n / "Do you want") so it can never false-positive
23
+ * on a plain prompt, but it may miss real states until a live capture
24
+ * upgrades it. `opencode` was attempted live but its local auth DB errored
25
+ * and the TUI rendered blank, so it stays conservative. Several M25.4
26
+ * manifests carry source/binary-VERBATIM markers (see each manifest's
27
+ * provenance comment) but stay conservative until a live capture confirms
28
+ * the surrounding chrome.
29
+ *
30
+ * Users can override any of these with a JSON file in
19
31
  * `~/.tmux-ide/agent-detection/` (see `manifest-loader.ts`).
20
32
  */
21
33
  import type { AgentManifest } from "./manifest.ts";
@@ -27,6 +39,7 @@ const BRAILLE_SPINNER = "[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]";
27
39
  const CLAUDE: AgentManifest = {
28
40
  id: "claude",
29
41
  commands: ["claude"],
42
+ confidence: "tuned",
30
43
  states: {
31
44
  // Approval / confirmation prompts — Claude is waiting on the user.
32
45
  // Claude's approval UI is a bordered box asking a "Do you want …?" question
@@ -74,41 +87,57 @@ const CLAUDE: AgentManifest = {
74
87
 
75
88
  const CODEX: AgentManifest = {
76
89
  id: "codex",
77
- commands: ["codex"],
90
+ commands: ["codex", "codex.exe"],
91
+ confidence: "tuned",
78
92
  states: {
93
+ // TUNED against real captures (codex-cli v0.142.5, driven through a turn).
94
+ // The command-approval dialog and the directory-trust prompt are the two
95
+ // "blocked" screens. Codex's approval menu uses a "› 1." numbered arrow —
96
+ // note the arrow is "›" (U+203A), NOT claude's "❯".
79
97
  blocked: {
80
98
  any: [
81
- // untuned — needs real captures. Codex approval prompts (docs/common
82
- // knowledge): a command-approval question before running a shell
83
- // command. Kept high-precision so it can't fire on the idle "›" box.
84
- { region: "bottom", contains: "Allow command", caseInsensitive: true },
85
- { region: "bottom", contains: "Do you want" },
86
- { region: "bottom", contains: "approve", caseInsensitive: true },
87
- { region: "bottom", contains: "(y/n)", caseInsensitive: true },
99
+ // seen (command approval): "Would you like to run the following
100
+ // command?" above a "$ <cmd>" preview and the numbered menu.
101
+ { region: "bottom", contains: "Would you like to run", caseInsensitive: true },
102
+ // seen: the highlighted approval option "› 1. Yes, proceed".
103
+ { region: "bottom", contains: "Yes, proceed" },
104
+ // seen: "3. No, and tell Codex what to do differently (esc)".
105
+ { region: "bottom", contains: "No, and tell Codex" },
106
+ // seen: the confirm footer under the approval menu.
107
+ { region: "bottom", contains: "Press enter to confirm", caseInsensitive: true },
108
+ // seen (directory-trust prompt on first launch in an untrusted dir):
109
+ // "Do you trust the contents of this directory?" + "1. Yes, continue".
110
+ { region: "bottom", contains: "Do you trust the contents", caseInsensitive: true },
88
111
  ],
89
112
  },
90
113
  working: {
91
114
  any: [
92
- // untuned for the exact string — Codex shows an elapsed-time working
93
- // line with an interrupt hint while a turn runs. "esc to interrupt" is
94
- // the shared CLI-TUI invariant; the spinner is the fallback.
115
+ // seen (verbatim): the working status line is
116
+ // "• Working (6s • esc to interrupt)".
117
+ // Both the "Working (" prefix and the shared "esc to interrupt" hint
118
+ // are present for the whole turn.
119
+ { region: "bottom", regex: "Working \\(\\d" },
95
120
  { region: "bottom", contains: "esc to interrupt", caseInsensitive: true },
96
121
  { region: "bottom", regex: BRAILLE_SPINNER },
97
122
  { region: "title", regex: BRAILLE_SPINNER },
98
123
  ],
99
124
  },
100
- // done: omitted. NOTE (seen, NOT used): idle Codex shows a "›" input prompt
101
- // and a "gpt-5.5 high · <cwd> Goal achieved (5m)" status line —
102
- // "Goal achieved" is a finished/idle marker, not "working", so it is left
103
- // out (a done rule would collapse to idle in the instant classifier anyway).
125
+ // done: omitted. NOTE (seen, NOT used): a finished turn leaves the agent's
126
+ // answer above the idle "›" input box (placeholder "Find and fix a bug in
127
+ // @filename") and a "gpt-5.5 xhigh · <cwd>" status line; older builds also
128
+ // showed "Goal achieved (5m)". None are working/blocked evidence, so codex
129
+ // correctly falls through to idle and the classifier infers done.
104
130
  },
105
131
  };
106
132
 
107
133
  const OPENCODE: AgentManifest = {
108
134
  id: "opencode",
109
- commands: ["opencode"],
135
+ commands: ["opencode", "opencode.exe"],
136
+ confidence: "conservative",
110
137
  states: {
111
- // untuned — needs real captures. High-precision only.
138
+ // conservative — a live capture was attempted (opencode v1.17.10) but its
139
+ // local auth DB errored ("no such column: name") and the TUI rendered
140
+ // blank, so these stay best-effort. High-precision only.
112
141
  blocked: {
113
142
  any: [
114
143
  { region: "bottom", contains: "(y/n)", caseInsensitive: true },
@@ -129,8 +158,10 @@ const OPENCODE: AgentManifest = {
129
158
  const GEMINI: AgentManifest = {
130
159
  id: "gemini",
131
160
  commands: ["gemini"],
161
+ confidence: "conservative",
132
162
  states: {
133
- // untuned — needs real captures. gemini-cli. High-precision only.
163
+ // conservative — gemini-cli needs a Google account/API key to reach a
164
+ // working state, so no live capture was taken. High-precision only.
134
165
  blocked: {
135
166
  any: [
136
167
  { region: "bottom", contains: "(y/n)", caseInsensitive: true },
@@ -153,20 +184,33 @@ const GEMINI: AgentManifest = {
153
184
  const AIDER: AgentManifest = {
154
185
  id: "aider",
155
186
  commands: ["aider"],
187
+ confidence: "tuned",
156
188
  states: {
157
- // untuned — needs real captures. aider uses "(Y)es/(N)o" confirmation
158
- // prompts, which are its most reliable blocked signal.
189
+ // TUNED from aider's installed source (v0.86.2). Every confirmation renders
190
+ // through `io.confirm_ask` (io.py), which appends the literal option string
191
+ // " (Y)es/(N)o" (plus "/(A)ll/(S)kip all" or "/(D)on't ask again") and a
192
+ // "[Yes]:"/"[No]:" default — so "(Y)es/(N)o" is aider's exact, universal
193
+ // blocked marker. The specific questions below are verbatim from
194
+ // base_coder.py / commands.py.
159
195
  blocked: {
160
196
  any: [
161
197
  { region: "bottom", contains: "(Y)es/(N)o", caseInsensitive: true },
162
- { region: "bottom", contains: "? [Yes]", caseInsensitive: true },
163
198
  { region: "bottom", contains: "Add file to the chat", caseInsensitive: true },
199
+ { region: "bottom", contains: "Allow edits to file", caseInsensitive: true },
200
+ { region: "bottom", contains: "Add command output to the chat", caseInsensitive: true },
201
+ { region: "bottom", contains: "Run pip install", caseInsensitive: true },
164
202
  ],
165
203
  },
166
- // working: aider streams tokens without a stable status line, so we leave
167
- // it to idle-by-default rather than risk a false positive.
204
+ // TUNED: while a turn runs aider shows a `WaitingSpinner` (waiting.py)
205
+ // rendered as "[░█ ] Waiting for <model>" — the text is literally
206
+ // "Waiting for LLM" or "Waiting for " + the model name (base_coder.py:1440).
207
+ // aider's spinner uses a "░█" scanner, NOT braille, so "Waiting for " is the
208
+ // real invariant; the braille probe is kept only as a harmless fallback.
168
209
  working: {
169
- any: [{ region: "bottom", regex: BRAILLE_SPINNER }],
210
+ any: [
211
+ { region: "bottom", contains: "Waiting for ", caseInsensitive: false },
212
+ { region: "bottom", regex: BRAILLE_SPINNER },
213
+ ],
170
214
  },
171
215
  },
172
216
  };
@@ -174,8 +218,10 @@ const AIDER: AgentManifest = {
174
218
  const COPILOT: AgentManifest = {
175
219
  id: "copilot",
176
220
  commands: ["copilot", "github-copilot", "github-copilot-cli"],
221
+ confidence: "conservative",
177
222
  states: {
178
- // untuned — needs real captures. github-copilot-cli. High-precision only.
223
+ // conservative — github-copilot-cli needs a GitHub account, so no live
224
+ // capture was taken. High-precision only.
179
225
  blocked: {
180
226
  any: [
181
227
  { region: "bottom", contains: "(y/n)", caseInsensitive: true },
@@ -193,9 +239,314 @@ const COPILOT: AgentManifest = {
193
239
  },
194
240
  };
195
241
 
242
+ const CURSOR: AgentManifest = {
243
+ id: "cursor",
244
+ commands: ["cursor-agent", "cursor"],
245
+ confidence: "conservative",
246
+ states: {
247
+ // conservative — cursor-agent (Cursor CLI) was launched live but sits on a
248
+ // "Press any key to log in…" pre-auth screen without an account, so no
249
+ // working/blocked turn could be captured. The pre-auth splash ("Cursor
250
+ // Agent" / "Press any key to log in") is idle chrome and deliberately NOT
251
+ // matched here. Markers below are high-precision guesses from public
252
+ // knowledge of its approval/streaming UI. NOTE: cursor-agent runs under
253
+ // `node`, so it resolves via the process-tree (argv0 basename), not the
254
+ // pane's `current_command`.
255
+ blocked: {
256
+ any: [
257
+ { region: "bottom", contains: "Do you want", caseInsensitive: false },
258
+ { region: "bottom", contains: "Run this command", caseInsensitive: true },
259
+ { region: "bottom", contains: "Apply this edit", caseInsensitive: true },
260
+ { region: "bottom", contains: "(y/n)", caseInsensitive: true },
261
+ ],
262
+ },
263
+ working: {
264
+ any: [
265
+ { region: "bottom", contains: "esc to interrupt", caseInsensitive: true },
266
+ { region: "bottom", regex: BRAILLE_SPINNER },
267
+ { region: "title", regex: BRAILLE_SPINNER },
268
+ ],
269
+ },
270
+ },
271
+ };
272
+
273
+ const GOOSE: AgentManifest = {
274
+ id: "goose",
275
+ commands: ["goose"],
276
+ confidence: "conservative",
277
+ states: {
278
+ // conservative — Block's goose CLI needs a configured provider, so no live
279
+ // capture was taken. High-precision only; markers are best-effort from
280
+ // public knowledge of its confirmation/streaming UI.
281
+ blocked: {
282
+ any: [
283
+ { region: "bottom", contains: "Do you want", caseInsensitive: false },
284
+ { region: "bottom", contains: "Allow this tool", caseInsensitive: true },
285
+ { region: "bottom", contains: "(y/n)", caseInsensitive: true },
286
+ { region: "bottom", contains: "[y/n]", caseInsensitive: true },
287
+ ],
288
+ },
289
+ working: {
290
+ any: [
291
+ { region: "bottom", contains: "esc to interrupt", caseInsensitive: true },
292
+ { region: "bottom", regex: BRAILLE_SPINNER },
293
+ { region: "title", regex: BRAILLE_SPINNER },
294
+ ],
295
+ },
296
+ },
297
+ };
298
+
299
+ const AMP: AgentManifest = {
300
+ id: "amp",
301
+ commands: ["amp"],
302
+ confidence: "conservative",
303
+ states: {
304
+ // conservative — Sourcegraph's amp CLI needs an account, so no live capture
305
+ // was taken. High-precision only; markers are best-effort from public
306
+ // knowledge of its approval/streaming UI.
307
+ blocked: {
308
+ any: [
309
+ { region: "bottom", contains: "Do you want", caseInsensitive: false },
310
+ { region: "bottom", contains: "Allow", caseInsensitive: false },
311
+ { region: "bottom", contains: "(y/n)", caseInsensitive: true },
312
+ ],
313
+ },
314
+ working: {
315
+ any: [
316
+ { region: "bottom", contains: "esc to interrupt", caseInsensitive: true },
317
+ { region: "bottom", regex: BRAILLE_SPINNER },
318
+ { region: "title", regex: BRAILLE_SPINNER },
319
+ ],
320
+ },
321
+ },
322
+ };
323
+
324
+ const DEVIN: AgentManifest = {
325
+ id: "devin",
326
+ commands: ["devin"],
327
+ confidence: "conservative",
328
+ states: {
329
+ // conservative — Devin CLI (Cognition; closed-source native Rust binary,
330
+ // needs an account). Blocked markers are VERBATIM from docs.devin.ai
331
+ // (cli/changelog + essential-commands via llms-full.txt): the plan-mode
332
+ // approval menu and the always-allow option. Devin has NO verified working
333
+ // text (its spinner is unlabeled; docs say interrupt is Ctrl+C, not esc),
334
+ // so working keeps only the shared spinner probe.
335
+ blocked: {
336
+ any: [
337
+ // seen (docs): "Yes, implement plan and accept edits" / "…and bypass
338
+ // permissions" — the shared prefix covers both.
339
+ { region: "bottom", contains: "Yes, implement plan and" },
340
+ { region: "bottom", contains: "No, plan needs changes" },
341
+ { region: "bottom", contains: "Yes, always allow" },
342
+ ],
343
+ },
344
+ working: {
345
+ any: [
346
+ { region: "bottom", regex: BRAILLE_SPINNER },
347
+ { region: "title", regex: BRAILLE_SPINNER },
348
+ ],
349
+ },
350
+ },
351
+ };
352
+
353
+ const KIMI: AgentManifest = {
354
+ id: "kimi",
355
+ commands: ["kimi"],
356
+ confidence: "conservative",
357
+ states: {
358
+ // conservative — kimi CLI (MoonshotAI/kimi-cli, Python/prompt_toolkit).
359
+ // Blocked markers are VERBATIM from its source (_approval_panel.py): the
360
+ // approval panel's "<tool> is requesting approval to …" header and its
361
+ // option labels. Its working spinner is a text-less moon animation (not
362
+ // braille), so working may under-detect until tuned — the braille probe is
363
+ // kept only as the shared fallback. ("Thinking"/"Thought for" deliberately
364
+ // NOT matched: they persist in the transcript after the turn.)
365
+ blocked: {
366
+ any: [
367
+ { region: "bottom", contains: " is requesting approval to " },
368
+ { region: "bottom", contains: "Approve for this session" },
369
+ { region: "bottom", contains: "Reject, tell the model" },
370
+ ],
371
+ },
372
+ working: {
373
+ any: [
374
+ { region: "bottom", regex: BRAILLE_SPINNER },
375
+ { region: "title", regex: BRAILLE_SPINNER },
376
+ ],
377
+ },
378
+ },
379
+ };
380
+
381
+ const PI: AgentManifest = {
382
+ id: "pi",
383
+ commands: ["pi"],
384
+ confidence: "conservative",
385
+ states: {
386
+ // conservative — pi (@mariozechner/pi-coding-agent, node). Working marker
387
+ // is VERBATIM from its source (interactive-mode.ts): the status line is
388
+ // `Working... (esc to interrupt)`, with `(esc to cancel)` retry/compacting
389
+ // variants. Its core has NO distinctive approval wording (generic Yes/No
390
+ // via extensions), so blocked is deliberately ABSENT — authority
391
+ // self-reporting or the classifier's fallback carry it.
392
+ working: {
393
+ any: [
394
+ { region: "bottom", contains: "Working... (esc to interrupt)" },
395
+ { region: "bottom", contains: "(esc to cancel)" },
396
+ { region: "bottom", regex: BRAILLE_SPINNER },
397
+ { region: "title", regex: BRAILLE_SPINNER },
398
+ ],
399
+ },
400
+ },
401
+ };
402
+
403
+ const GROK: AgentManifest = {
404
+ id: "grok",
405
+ commands: ["grok"],
406
+ confidence: "conservative",
407
+ states: {
408
+ // conservative — the community grok CLI (@vibe-kit/grok-cli /
409
+ // superagent-ai/grok-cli, node/OpenTUI). Working markers are VERBATIM from
410
+ // its source (app.tsx / headless output.ts): the mid-turn placeholder
411
+ // carries "(esc to interrupt)" and headless prints "⏳ Processing...".
412
+ // Its ConfirmationDialog wording could not be verified, so blocked keeps
413
+ // only a high-precision generic. NOTE: xAI's separate official
414
+ // `grok-build` binary substring-matches this manifest but has no verified
415
+ // strings — it will simply read idle until evidence exists.
416
+ blocked: {
417
+ any: [{ region: "bottom", contains: "(y/n)", caseInsensitive: true }],
418
+ },
419
+ working: {
420
+ any: [
421
+ { region: "bottom", contains: "esc to interrupt", caseInsensitive: true },
422
+ { region: "bottom", contains: "⏳ Processing" },
423
+ { region: "bottom", regex: BRAILLE_SPINNER },
424
+ { region: "title", regex: BRAILLE_SPINNER },
425
+ ],
426
+ },
427
+ },
428
+ };
429
+
430
+ const KIRO: AgentManifest = {
431
+ id: "kiro",
432
+ commands: ["kiro-cli", "kiro"],
433
+ confidence: "conservative",
434
+ states: {
435
+ // conservative — Kiro CLI (AWS; the rebranded Amazon Q Developer CLI, a
436
+ // native Rust binary). Markers are VERBATIM from the open-source
437
+ // predecessor (aws/amazon-q-developer-cli chat-cli/src/cli/chat/mod.rs):
438
+ // the tool-approval question and the Spinners::Dots "Thinking..." status.
439
+ // The closed Kiro build may drift — same lineage, pending live capture.
440
+ // NOTE: the predecessor's 1-char `q` binary is deliberately NOT a command
441
+ // token (a single letter substring-matches far too much).
442
+ blocked: {
443
+ any: [
444
+ { region: "bottom", contains: "Allow this action?" },
445
+ { region: "bottom", contains: "to trust (always allow) this tool" },
446
+ ],
447
+ },
448
+ working: {
449
+ any: [
450
+ { region: "bottom", contains: "Thinking..." },
451
+ { region: "bottom", regex: BRAILLE_SPINNER },
452
+ { region: "title", regex: BRAILLE_SPINNER },
453
+ ],
454
+ },
455
+ },
456
+ };
457
+
458
+ const CLINE: AgentManifest = {
459
+ id: "cline",
460
+ commands: ["cline"],
461
+ confidence: "conservative",
462
+ states: {
463
+ // conservative — cline CLI (cline.bot; CLI 2.0 ships prebuilt platform
464
+ // binaries, OpenTUI). Markers are VERBATIM from its source: the approval
465
+ // dialog's "Cline needs permission" title + "Approve tool call?" header
466
+ // (tui/components/dialogs/tool-approval.tsx) and the streaming
467
+ // "Thinking... (esc to cancel)" status (chat-message-list.tsx).
468
+ blocked: {
469
+ any: [
470
+ { region: "bottom", contains: "Cline needs permission" },
471
+ { region: "bottom", contains: "Approve tool call?" },
472
+ { region: "bottom", contains: "[y] Approve" },
473
+ ],
474
+ },
475
+ working: {
476
+ any: [
477
+ { region: "bottom", contains: "esc to cancel", caseInsensitive: true },
478
+ { region: "bottom", regex: BRAILLE_SPINNER },
479
+ { region: "title", regex: BRAILLE_SPINNER },
480
+ ],
481
+ },
482
+ },
483
+ };
484
+
485
+ const DROID: AgentManifest = {
486
+ id: "droid",
487
+ commands: ["droid"],
488
+ confidence: "conservative",
489
+ states: {
490
+ // conservative — droid (Factory CLI) was launched live (v0.114.1) but sits
491
+ // on an auth-gated login menu without a Factory account, so only the login
492
+ // splash could be captured (it reads IDLE and is deliberately not matched:
493
+ // "Please login with your Factory account to continue." / "> Login").
494
+ // Verified live: droid runs as its OWN process (`pane_current_command` =
495
+ // "droid"), so command matching needs no process-tree help.
496
+ // Blocked markers are docs-derived (docs.factory.ai auto-run levels,
497
+ // corroborated third-party walkthrough) — the Spec-mode proceed menu names
498
+ // its autonomy levels verbatim. Pending live-capture confirmation.
499
+ blocked: {
500
+ any: [
501
+ { region: "bottom", contains: "Proceed, manual approval" },
502
+ { region: "bottom", contains: "Proceed, allow safe commands" },
503
+ { region: "bottom", contains: "Proceed, allow all commands" },
504
+ ],
505
+ },
506
+ working: {
507
+ any: [
508
+ { region: "bottom", contains: "esc to interrupt", caseInsensitive: true },
509
+ { region: "bottom", regex: BRAILLE_SPINNER },
510
+ { region: "title", regex: BRAILLE_SPINNER },
511
+ ],
512
+ },
513
+ },
514
+ };
515
+
516
+ const KILO: AgentManifest = {
517
+ id: "kilo",
518
+ commands: ["kilo", ".kilo", "kilocode"],
519
+ confidence: "conservative",
520
+ states: {
521
+ // conservative, but the blocked markers are VERBATIM from the shipped
522
+ // @kilocode/cli binary (v7.4.5, strings-extracted — no account to drive a
523
+ // live capture): the permission dialog renders a "△ Permission required"
524
+ // header over an "Allow once" / "Allow always" / "Reject" option list.
525
+ // NOTE: the CLI's npm launcher (`bin/kilo`, node) execs the platform
526
+ // binary installed as `bin/.kilo`, hence the ".kilo" command token.
527
+ blocked: {
528
+ any: [
529
+ { region: "bottom", contains: "Permission required" },
530
+ { region: "bottom", contains: "Allow once" },
531
+ { region: "bottom", contains: "Allow always" },
532
+ ],
533
+ },
534
+ // No working-state string was found in the binary (its status UI renders
535
+ // from dynamic parts) — only the shared spinner probe, so kilo may read
536
+ // idle while working until a live capture tunes it.
537
+ working: {
538
+ any: [
539
+ { region: "bottom", regex: BRAILLE_SPINNER },
540
+ { region: "title", regex: BRAILLE_SPINNER },
541
+ ],
542
+ },
543
+ },
544
+ };
545
+
196
546
  const SHELL: AgentManifest = {
197
547
  id: "shell",
198
548
  commands: ["bash", "zsh", "sh", "fish", "nu"],
549
+ confidence: "conservative",
199
550
  states: {
200
551
  // Catch-all: a raw shell is almost always idle. We only flag an explicit
201
552
  // interactive confirmation as blocked; "working" is unreliable to read
@@ -222,5 +573,16 @@ export const BUNDLED_MANIFESTS: AgentManifest[] = [
222
573
  GEMINI,
223
574
  AIDER,
224
575
  COPILOT,
576
+ CURSOR,
577
+ GOOSE,
578
+ AMP,
579
+ DEVIN,
580
+ KIMI,
581
+ PI,
582
+ GROK,
583
+ KIRO,
584
+ CLINE,
585
+ DROID,
586
+ KILO,
225
587
  SHELL,
226
588
  ];
@@ -53,6 +53,16 @@ export function parsePsOutput(raw: string): ProcEntry[] {
53
53
  * depth are guarded.
54
54
  */
55
55
  export function subtreeCommands(entries: ProcEntry[], rootPid: number, maxDepth = 6): string[] {
56
+ return subtreeEntries(entries, rootPid, maxDepth).map((entry) => entry.command);
57
+ }
58
+
59
+ /**
60
+ * The full {@link ProcEntry} rows of the subtree rooted at `rootPid`, in the
61
+ * same deepest-first order as {@link subtreeCommands}. Callers that need PIDs
62
+ * (e.g. the session-id capture probing a pane's agent process by open files)
63
+ * use this; the command-only view above stays the common path.
64
+ */
65
+ export function subtreeEntries(entries: ProcEntry[], rootPid: number, maxDepth = 6): ProcEntry[] {
56
66
  const childrenByParent = new Map<number, ProcEntry[]>();
57
67
  const byPid = new Map<number, ProcEntry>();
58
68
  for (const entry of entries) {
@@ -65,7 +75,7 @@ export function subtreeCommands(entries: ProcEntry[], rootPid: number, maxDepth
65
75
  const root = byPid.get(rootPid);
66
76
  if (!root) return [];
67
77
 
68
- const commands: string[] = [];
78
+ const out: ProcEntry[] = [];
69
79
  const visited = new Set<number>();
70
80
 
71
81
  const walk = (pid: number, depth: number): void => {
@@ -76,11 +86,31 @@ export function subtreeCommands(entries: ProcEntry[], rootPid: number, maxDepth
76
86
  walk(child.pid, depth + 1);
77
87
  }
78
88
  const self = byPid.get(pid);
79
- if (self) commands.push(self.command);
89
+ if (self) out.push(self);
80
90
  };
81
91
 
82
92
  walk(rootPid, 0);
83
- return commands;
93
+ return out;
94
+ }
95
+
96
+ /**
97
+ * Summarize the executable-ish tokens seen in a pane's process subtree, in the
98
+ * order the tree-walk visits them (deepest first), de-duplicated. Pure.
99
+ *
100
+ * This is a DIAGNOSTIC for `agent explain`: when no manifest resolves, showing
101
+ * "process-tree saw: node, sleep" tells the user WHY nothing matched and what
102
+ * to point an `@agent_hint` at. Tokens are the same basenames
103
+ * `resolveAgentCommand` matches against, so the report reflects reality.
104
+ */
105
+ export function describeSubtree(entries: ProcEntry[], rootPid: number, limit = 8): string[] {
106
+ const seen: string[] = [];
107
+ for (const command of subtreeCommands(entries, rootPid)) {
108
+ for (const token of commandTokens(command)) {
109
+ if (!seen.includes(token)) seen.push(token);
110
+ if (seen.length >= limit) return seen;
111
+ }
112
+ }
113
+ return seen;
84
114
  }
85
115
 
86
116
  /**