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
@@ -223,6 +223,11 @@ export async function runUpdateCheck({ now = Date.now() } = {}) {
223
223
  const cache = readUpdateCache();
224
224
  if (!shouldCheck(cache?.lastCheckedAt ?? null, now))
225
225
  return;
226
+ // The agent-detection manifest-pack refresh RIDES this same daily throttle
227
+ // (M25.4) — no timer of its own, gated again inside on `updates.manifests`
228
+ // (default false) and never throwing. Dynamic import: manifest-pack imports
229
+ // this module for getCurrentVersion, so a static import would be a cycle.
230
+ void import("./manifest-pack.js").then((m) => m.maybeRefreshManifestPack()).catch(() => { });
226
231
  const fetched = await fetchLatestVersion();
227
232
  writeUpdateCache({
228
233
  lastCheckedAt: now,
@@ -0,0 +1,34 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>en</string>
7
+ <key>CFBundleDisplayName</key>
8
+ <string>tmux-ide</string>
9
+ <key>CFBundleExecutable</key>
10
+ <string>tmux-ide-notifier</string>
11
+ <key>CFBundleIconFile</key>
12
+ <string>AppIcon</string>
13
+ <key>CFBundleIconName</key>
14
+ <string>AppIcon</string>
15
+ <key>CFBundleIdentifier</key>
16
+ <string>com.wavyrai.tmuxide.notifier</string>
17
+ <key>CFBundleInfoDictionaryVersion</key>
18
+ <string>6.0</string>
19
+ <key>CFBundleName</key>
20
+ <string>tmux-ide</string>
21
+ <key>CFBundlePackageType</key>
22
+ <string>APPL</string>
23
+ <key>CFBundleShortVersionString</key>
24
+ <string>2.8.0</string>
25
+ <key>CFBundleVersion</key>
26
+ <string>2.8.0</string>
27
+ <key>LSMinimumSystemVersion</key>
28
+ <string>11.0</string>
29
+ <key>LSUIElement</key>
30
+ <true/>
31
+ <key>NSPrincipalClass</key>
32
+ <string>NSApplication</string>
33
+ </dict>
34
+ </plist>
@@ -0,0 +1,139 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>files</key>
6
+ <dict>
7
+ <key>Resources/AppIcon.icns</key>
8
+ <data>
9
+ X3UpT06Bz44e6JibpJhhiMxRfno=
10
+ </data>
11
+ <key>Resources/Assets.car</key>
12
+ <data>
13
+ I3z+NDxWadVSrUS0xGMi1yZxHfA=
14
+ </data>
15
+ </dict>
16
+ <key>files2</key>
17
+ <dict>
18
+ <key>Resources/AppIcon.icns</key>
19
+ <dict>
20
+ <key>hash2</key>
21
+ <data>
22
+ tkz+Ee3lkawLrys701JjQo2Z+YlNtbudhtH13NTsrQw=
23
+ </data>
24
+ </dict>
25
+ <key>Resources/Assets.car</key>
26
+ <dict>
27
+ <key>hash2</key>
28
+ <data>
29
+ vuVGO00nkAAaL21f21SA22M7NZ311c+KH7/lizezdck=
30
+ </data>
31
+ </dict>
32
+ </dict>
33
+ <key>rules</key>
34
+ <dict>
35
+ <key>^Resources/</key>
36
+ <true/>
37
+ <key>^Resources/.*\.lproj/</key>
38
+ <dict>
39
+ <key>optional</key>
40
+ <true/>
41
+ <key>weight</key>
42
+ <real>1000</real>
43
+ </dict>
44
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
45
+ <dict>
46
+ <key>omit</key>
47
+ <true/>
48
+ <key>weight</key>
49
+ <real>1100</real>
50
+ </dict>
51
+ <key>^Resources/Base\.lproj/</key>
52
+ <dict>
53
+ <key>weight</key>
54
+ <real>1010</real>
55
+ </dict>
56
+ <key>^version.plist$</key>
57
+ <true/>
58
+ </dict>
59
+ <key>rules2</key>
60
+ <dict>
61
+ <key>.*\.dSYM($|/)</key>
62
+ <dict>
63
+ <key>weight</key>
64
+ <real>11</real>
65
+ </dict>
66
+ <key>^(.*/)?\.DS_Store$</key>
67
+ <dict>
68
+ <key>omit</key>
69
+ <true/>
70
+ <key>weight</key>
71
+ <real>2000</real>
72
+ </dict>
73
+ <key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
74
+ <dict>
75
+ <key>nested</key>
76
+ <true/>
77
+ <key>weight</key>
78
+ <real>10</real>
79
+ </dict>
80
+ <key>^.*</key>
81
+ <true/>
82
+ <key>^Info\.plist$</key>
83
+ <dict>
84
+ <key>omit</key>
85
+ <true/>
86
+ <key>weight</key>
87
+ <real>20</real>
88
+ </dict>
89
+ <key>^PkgInfo$</key>
90
+ <dict>
91
+ <key>omit</key>
92
+ <true/>
93
+ <key>weight</key>
94
+ <real>20</real>
95
+ </dict>
96
+ <key>^Resources/</key>
97
+ <dict>
98
+ <key>weight</key>
99
+ <real>20</real>
100
+ </dict>
101
+ <key>^Resources/.*\.lproj/</key>
102
+ <dict>
103
+ <key>optional</key>
104
+ <true/>
105
+ <key>weight</key>
106
+ <real>1000</real>
107
+ </dict>
108
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
109
+ <dict>
110
+ <key>omit</key>
111
+ <true/>
112
+ <key>weight</key>
113
+ <real>1100</real>
114
+ </dict>
115
+ <key>^Resources/Base\.lproj/</key>
116
+ <dict>
117
+ <key>weight</key>
118
+ <real>1010</real>
119
+ </dict>
120
+ <key>^[^/]+$</key>
121
+ <dict>
122
+ <key>nested</key>
123
+ <true/>
124
+ <key>weight</key>
125
+ <real>10</real>
126
+ </dict>
127
+ <key>^embedded\.provisionprofile$</key>
128
+ <dict>
129
+ <key>weight</key>
130
+ <real>20</real>
131
+ </dict>
132
+ <key>^version\.plist$</key>
133
+ <dict>
134
+ <key>weight</key>
135
+ <real>20</real>
136
+ </dict>
137
+ </dict>
138
+ </dict>
139
+ </plist>
@@ -32,19 +32,46 @@ export interface RestorePlan {
32
32
  * config; failing that, we raw-rebuild from the recorded windows/panes.
33
33
  */
34
34
  export declare function buildRestorePlan(snapshot: FleetSnapshot, liveSessionNames: Iterable<string>, ideProjects?: Map<string, string>): RestorePlan;
35
+ /**
36
+ * The native session-resume invocation per agent kind (M24.1 — the table was
37
+ * claude-only until then). ONLY VERIFIED entries ship; each spelling's source:
38
+ * - `claude --resume <id>`: the shipped integration (its hooks record the id).
39
+ * - `codex resume <id>`: `codex resume --help` — "Usage: codex resume
40
+ * [OPTIONS] [SESSION_ID] … Session id (UUID) or session name".
41
+ * - `opencode --session <id>`: `opencode --help` — "-s, --session session id
42
+ * to continue".
43
+ * - `cursor-agent --resume <id>`: `cursor-agent --help` — "--resume [chatId]
44
+ * Select a session to resume".
45
+ * - `copilot --resume=<id>`: GitHub Copilot CLI command reference —
46
+ * "--resume[=VALUE] Resume a previous interactive session … Optionally
47
+ * specify a session ID" (the `=` form, since the value is optional).
48
+ * Unverified (no entry): gemini, aider, goose, amp — no confirmed native
49
+ * per-session resume invocation at the time of writing.
50
+ *
51
+ * How `@agent_session_id` gets recorded per kind (M25.3):
52
+ * - claude — the hooks integration (`integration install claude`).
53
+ * - opencode — the plugin integration (`integration install opencode`).
54
+ * - codex, cursor — automatic: the chrome updater probes the agent's own
55
+ * on-disk session state ({@link ./tui/detect/session-id.ts}).
56
+ * - copilot — no automatic capture (skipped with evidence — see the
57
+ * session-id module header); fires only for panes whose agent self-reported
58
+ * the id per the agent contract
59
+ * (`tmux set-option -p @agent_session_id <id>`), which any kind may do.
60
+ */
61
+ export declare const AGENT_RESUME_COMMANDS: Record<string, (id: string) => string>;
35
62
  /**
36
63
  * PURE — the command to relaunch a pane so its agent conversation is revived,
37
64
  * or `null` for "no resume — leave it a plain shell (or replay `command`)".
38
65
  *
39
66
  * Decision table (only fires when `resumeAgents` is on):
40
- * - claude + a recorded `@agent_session_id` → `claude --resume <id>`. The id is
41
- * a uuid, but we don't trust the snapshot: unless it matches
42
- * {@link SAFE_SESSION_ID} (`[A-Za-z0-9-]`) we bail to `null` rather than risk
43
- * feeding shell metacharacters into send-keys.
44
- * - claude with NO session id → `null`. `claude --continue` would be too
45
- * magical here (it resumes the most-recent conversation in the cwd, which may
46
- * belong to a different pane); a fresh shell is the safe, predictable default.
47
- * - any other agent → `null`. No native resume story yet.
67
+ * - a kind in {@link AGENT_RESUME_COMMANDS} + a recorded `@agent_session_id` →
68
+ * that kind's resume invocation. The id is uuid-ish, but we don't trust the
69
+ * snapshot: unless it matches {@link SAFE_SESSION_ID} we bail to `null`
70
+ * rather than risk feeding shell metacharacters into send-keys.
71
+ * - a known kind with NO session id → `null`. A "continue most recent" flag
72
+ * would be too magical here (it resumes the cwd's most-recent conversation,
73
+ * which may belong to a different pane); a fresh shell is the safe default.
74
+ * - any other agent → `null`. No VERIFIED resume story.
48
75
  * - `resumeAgents` off → always `null`.
49
76
  */
50
77
  export declare function paneResumeCommand(pane: PaneSnapshot, opts: {
@@ -13,9 +13,11 @@
13
13
  *
14
14
  * `--resume-agents` (or `{ "restore": { "resumeAgents": true } }` in
15
15
  * `~/.tmux-ide/config.json`) layers native agent-resume on top: a rebuilt
16
- * claude pane that carries a recorded `@agent_session_id` relaunches as
17
- * `claude --resume <id>`, reviving the actual conversation rather than opening
18
- * a fresh shell. See {@link paneResumeCommand} for the exact decision table.
16
+ * agent pane that carries a recorded `@agent_session_id` relaunches with its
17
+ * kind's VERIFIED resume invocation (`claude --resume <id>`, `codex resume
18
+ * <id>`, … — {@link AGENT_RESUME_COMMANDS}), reviving the actual conversation
19
+ * rather than opening a fresh shell. See {@link paneResumeCommand} for the
20
+ * exact decision table.
19
21
  *
20
22
  * {@link buildRestorePlan} + {@link paneResumeCommand} + {@link restorePrefs}
21
23
  * are PURE (unit-tested); {@link restore} is the thin io wrapper that reads the
@@ -58,32 +60,67 @@ export function buildRestorePlan(snapshot, liveSessionNames, ideProjects = new M
58
60
  // ---------------------------------------------------------------------------
59
61
  // Pure — agent resume decision
60
62
  // ---------------------------------------------------------------------------
61
- /** A session id is trusted only if it's the uuid alphabet — nothing shell-active. */
62
- const SAFE_SESSION_ID = /^[A-Za-z0-9-]+$/;
63
+ /** A session id is trusted only if it's uuid-ish (`[A-Za-z0-9_-]` — underscore
64
+ * covers opencode's `ses_…` ids) — nothing shell-active. */
65
+ const SAFE_SESSION_ID = /^[A-Za-z0-9_-]+$/;
66
+ /**
67
+ * The native session-resume invocation per agent kind (M24.1 — the table was
68
+ * claude-only until then). ONLY VERIFIED entries ship; each spelling's source:
69
+ * - `claude --resume <id>`: the shipped integration (its hooks record the id).
70
+ * - `codex resume <id>`: `codex resume --help` — "Usage: codex resume
71
+ * [OPTIONS] [SESSION_ID] … Session id (UUID) or session name".
72
+ * - `opencode --session <id>`: `opencode --help` — "-s, --session session id
73
+ * to continue".
74
+ * - `cursor-agent --resume <id>`: `cursor-agent --help` — "--resume [chatId]
75
+ * Select a session to resume".
76
+ * - `copilot --resume=<id>`: GitHub Copilot CLI command reference —
77
+ * "--resume[=VALUE] Resume a previous interactive session … Optionally
78
+ * specify a session ID" (the `=` form, since the value is optional).
79
+ * Unverified (no entry): gemini, aider, goose, amp — no confirmed native
80
+ * per-session resume invocation at the time of writing.
81
+ *
82
+ * How `@agent_session_id` gets recorded per kind (M25.3):
83
+ * - claude — the hooks integration (`integration install claude`).
84
+ * - opencode — the plugin integration (`integration install opencode`).
85
+ * - codex, cursor — automatic: the chrome updater probes the agent's own
86
+ * on-disk session state ({@link ./tui/detect/session-id.ts}).
87
+ * - copilot — no automatic capture (skipped with evidence — see the
88
+ * session-id module header); fires only for panes whose agent self-reported
89
+ * the id per the agent contract
90
+ * (`tmux set-option -p @agent_session_id <id>`), which any kind may do.
91
+ */
92
+ export const AGENT_RESUME_COMMANDS = {
93
+ claude: (id) => `claude --resume ${id}`,
94
+ codex: (id) => `codex resume ${id}`,
95
+ opencode: (id) => `opencode --session ${id}`,
96
+ cursor: (id) => `cursor-agent --resume ${id}`,
97
+ copilot: (id) => `copilot --resume=${id}`,
98
+ };
63
99
  /**
64
100
  * PURE — the command to relaunch a pane so its agent conversation is revived,
65
101
  * or `null` for "no resume — leave it a plain shell (or replay `command`)".
66
102
  *
67
103
  * Decision table (only fires when `resumeAgents` is on):
68
- * - claude + a recorded `@agent_session_id` → `claude --resume <id>`. The id is
69
- * a uuid, but we don't trust the snapshot: unless it matches
70
- * {@link SAFE_SESSION_ID} (`[A-Za-z0-9-]`) we bail to `null` rather than risk
71
- * feeding shell metacharacters into send-keys.
72
- * - claude with NO session id → `null`. `claude --continue` would be too
73
- * magical here (it resumes the most-recent conversation in the cwd, which may
74
- * belong to a different pane); a fresh shell is the safe, predictable default.
75
- * - any other agent → `null`. No native resume story yet.
104
+ * - a kind in {@link AGENT_RESUME_COMMANDS} + a recorded `@agent_session_id` →
105
+ * that kind's resume invocation. The id is uuid-ish, but we don't trust the
106
+ * snapshot: unless it matches {@link SAFE_SESSION_ID} we bail to `null`
107
+ * rather than risk feeding shell metacharacters into send-keys.
108
+ * - a known kind with NO session id → `null`. A "continue most recent" flag
109
+ * would be too magical here (it resumes the cwd's most-recent conversation,
110
+ * which may belong to a different pane); a fresh shell is the safe default.
111
+ * - any other agent → `null`. No VERIFIED resume story.
76
112
  * - `resumeAgents` off → always `null`.
77
113
  */
78
114
  export function paneResumeCommand(pane, opts) {
79
115
  if (!opts.resumeAgents)
80
116
  return null;
81
- if (pane.agent !== "claude")
117
+ const resume = pane.agent ? AGENT_RESUME_COMMANDS[pane.agent] : undefined;
118
+ if (!resume)
82
119
  return null;
83
120
  const id = pane.agentSessionId;
84
121
  if (!id || !SAFE_SESSION_ID.test(id))
85
122
  return null;
86
- return `claude --resume ${id}`;
123
+ return resume(id);
87
124
  }
88
125
  /** PURE — how many of a session's panes would resume under `resumeAgents`. */
89
126
  export function countResumableAgents(session, resumeAgents) {
@@ -1,4 +1,4 @@
1
- import { type PaneInfo } from "./widgets/lib/pane-comms.ts";
1
+ import { type PaneInfo, type PaneBusyStatus } from "./widgets/lib/pane-comms.ts";
2
2
  export declare const LONG_MESSAGE_THRESHOLD = 150;
3
3
  /**
4
4
  * Write a long message to a dispatch file and return the short trigger command.
@@ -23,5 +23,37 @@ interface SendOptions {
23
23
  * 5. Case-insensitive partial title match
24
24
  */
25
25
  export declare function resolvePane(panes: PaneInfo[], target: string): PaneInfo | null;
26
+ /** What a delivery reports back (the `send --json` payload, and the control
27
+ * socket's `send` verb data). */
28
+ export interface DeliverResult {
29
+ ok: true;
30
+ session: string;
31
+ target: {
32
+ paneId: string;
33
+ name: string | null;
34
+ title: string;
35
+ role: string | null;
36
+ };
37
+ message: string;
38
+ busyStatus: PaneBusyStatus;
39
+ sentViaFile: boolean;
40
+ warning?: "agent_busy";
41
+ }
42
+ /**
43
+ * Deliver `message` to a pane of `session` — the SHARED core behind the
44
+ * `tmux-ide send` CLI case and the control socket's `send` verb. Resolves
45
+ * `target` (pane id / @ide_name / title / role / partial title), adapts to
46
+ * the pane's busy status, and routes long messages through a dispatch file
47
+ * under `dir` when one is given (without a `dir` the text is sent directly).
48
+ * Throws `IdeError` (SESSION_NOT_FOUND / PANE_NOT_FOUND) — no printing here.
49
+ */
50
+ export declare function deliverMessage(opts: {
51
+ session: string;
52
+ target: string;
53
+ message: string;
54
+ noEnter?: boolean;
55
+ /** The project dir long messages are dispatched through as a file. */
56
+ dir?: string;
57
+ }): DeliverResult;
26
58
  export declare function send(targetDir: string | undefined, opts: SendOptions): Promise<void>;
27
59
  export {};
@@ -64,21 +64,16 @@ function prepareMessage(message, busyStatus) {
64
64
  }
65
65
  return message;
66
66
  }
67
- export async function send(targetDir, opts) {
68
- const dir = resolve(targetDir ?? ".");
69
- const { name: session } = getSessionName(dir);
70
- const { json, to: target, message: rawMessage, noEnter } = opts;
71
- if (!target) {
72
- throw new IdeError("Missing target. Usage: tmux-ide send <target> <message>", {
73
- code: "USAGE",
74
- });
75
- }
76
- if (!rawMessage) {
77
- throw new IdeError("Missing message. Usage: tmux-ide send <target> <message>", {
78
- code: "USAGE",
79
- });
80
- }
81
- // Verify session is running
67
+ /**
68
+ * Deliver `message` to a pane of `session` — the SHARED core behind the
69
+ * `tmux-ide send` CLI case and the control socket's `send` verb. Resolves
70
+ * `target` (pane id / @ide_name / title / role / partial title), adapts to
71
+ * the pane's busy status, and routes long messages through a dispatch file
72
+ * under `dir` when one is given (without a `dir` the text is sent directly).
73
+ * Throws `IdeError` (SESSION_NOT_FOUND / PANE_NOT_FOUND) — no printing here.
74
+ */
75
+ export function deliverMessage(opts) {
76
+ const { session, target, noEnter, dir } = opts;
82
77
  const state = getSessionState(session);
83
78
  if (!state.running) {
84
79
  throw new IdeError(`Session "${session}" is not running`, {
@@ -99,13 +94,13 @@ export async function send(targetDir, opts) {
99
94
  });
100
95
  }
101
96
  const busyStatus = getPaneBusyStatus(session, pane.id);
102
- const message = prepareMessage(rawMessage, busyStatus);
97
+ const message = prepareMessage(opts.message, busyStatus);
103
98
  let sentViaFile = false;
104
99
  if (noEnter) {
105
100
  sendText(session, pane.id, message);
106
101
  }
107
102
  else {
108
- const dispatch = writeDispatchFile(dir, pane.id, message);
103
+ const dispatch = dir ? writeDispatchFile(dir, pane.id, message) : null;
109
104
  if (dispatch) {
110
105
  sendCommand(session, pane.id, dispatch.triggerCmd);
111
106
  sentViaFile = true;
@@ -114,7 +109,7 @@ export async function send(targetDir, opts) {
114
109
  sendCommand(session, pane.id, message);
115
110
  }
116
111
  }
117
- const result = {
112
+ return {
118
113
  ok: true,
119
114
  session,
120
115
  target: {
@@ -128,13 +123,31 @@ export async function send(targetDir, opts) {
128
123
  sentViaFile,
129
124
  ...(busyStatus === "agent" ? { warning: "agent_busy" } : {}),
130
125
  };
126
+ }
127
+ export async function send(targetDir, opts) {
128
+ const dir = resolve(targetDir ?? ".");
129
+ const { name: session } = getSessionName(dir);
130
+ const { json, to: target, message: rawMessage, noEnter } = opts;
131
+ if (!target) {
132
+ throw new IdeError("Missing target. Usage: tmux-ide send <target> <message>", {
133
+ code: "USAGE",
134
+ });
135
+ }
136
+ if (!rawMessage) {
137
+ throw new IdeError("Missing message. Usage: tmux-ide send <target> <message>", {
138
+ code: "USAGE",
139
+ });
140
+ }
141
+ const result = deliverMessage({ session, target, message: rawMessage, noEnter, dir });
142
+ const { message, busyStatus } = result;
143
+ const pane = result.target;
131
144
  if (json) {
132
145
  console.log(JSON.stringify(result, null, 2));
133
146
  return;
134
147
  }
135
148
  const label = pane.name ?? pane.title;
136
149
  const preview = message.length > 60 ? message.slice(0, 60) + "..." : message;
137
- console.log(`Sent to "${label}" (${pane.id}): ${preview}`);
150
+ console.log(`Sent to "${label}" (${pane.paneId}): ${preview}`);
138
151
  if (busyStatus === "agent") {
139
152
  console.log("Warning: agent appears busy. Message sent anyway.");
140
153
  }
@@ -6,5 +6,5 @@ interface BreadcrumbsProps {
6
6
  theme: WidgetTheme;
7
7
  onNavigate: (absolutePath: string) => void;
8
8
  }
9
- export declare function Breadcrumbs(props: BreadcrumbsProps): any;
9
+ export declare function Breadcrumbs(props: BreadcrumbsProps): import("solid-js").JSX.Element;
10
10
  export {};
@@ -2,5 +2,5 @@ import type { WidgetTheme } from "../lib/theme.ts";
2
2
  interface FooterProps {
3
3
  theme: WidgetTheme;
4
4
  }
5
- export declare function Footer(props: FooterProps): any;
5
+ export declare function Footer(props: FooterProps): import("solid-js").JSX.Element;
6
6
  export {};
@@ -9,5 +9,5 @@ interface FileTreeProps {
9
9
  onActivate: (node: TreeNode) => void;
10
10
  onInputModeChange: (mode: "keyboard" | "mouse") => void;
11
11
  }
12
- export declare function FileTree(props: FileTreeProps): any;
12
+ export declare function FileTree(props: FileTreeProps): import("solid-js").JSX.Element;
13
13
  export {};
@@ -16,4 +16,4 @@ export interface HelpOverlayProps {
16
16
  * the surface's own keys. Replaces the surface body while `?` is held open;
17
17
  * `esc`/`q`/`?` close it.
18
18
  */
19
- export declare function HelpOverlay(props: HelpOverlayProps): any;
19
+ export declare function HelpOverlay(props: HelpOverlayProps): import("solid-js").JSX.Element;
@@ -5,5 +5,5 @@ interface AgentNamingProps {
5
5
  onContinue: (names: string[]) => void;
6
6
  theme: WidgetTheme;
7
7
  }
8
- export declare function AgentNaming(props: AgentNamingProps): any;
8
+ export declare function AgentNaming(props: AgentNamingProps): import("solid-js").JSX.Element;
9
9
  export {};
@@ -9,5 +9,5 @@ interface ConfigTreeProps {
9
9
  onConfigChange: (config: IdeConfig) => void;
10
10
  theme: WidgetTheme;
11
11
  }
12
- export declare function ConfigTree(props: ConfigTreeProps): any;
12
+ export declare function ConfigTree(props: ConfigTreeProps): import("solid-js").JSX.Element;
13
13
  export {};
@@ -10,5 +10,5 @@ interface DetectPanelProps {
10
10
  onContinue: () => void;
11
11
  theme: WidgetTheme;
12
12
  }
13
- export declare function DetectPanel(props: DetectPanelProps): any;
13
+ export declare function DetectPanel(props: DetectPanelProps): import("solid-js").JSX.Element;
14
14
  export {};
@@ -9,4 +9,4 @@ export interface FieldEditorProps {
9
9
  onCancel: () => void;
10
10
  theme: WidgetTheme;
11
11
  }
12
- export declare function FieldEditor(props: FieldEditorProps): any;
12
+ export declare function FieldEditor(props: FieldEditorProps): import("solid-js").JSX.Element;
@@ -13,5 +13,5 @@ interface FooterProps {
13
13
  theme: WidgetTheme;
14
14
  actions?: FooterActions;
15
15
  }
16
- export declare function Footer(props: FooterProps): any;
16
+ export declare function Footer(props: FooterProps): import("solid-js").JSX.Element;
17
17
  export {};
@@ -5,5 +5,5 @@ interface LayoutPickerProps {
5
5
  onSelect: (preset: LayoutPreset) => void;
6
6
  theme: WidgetTheme;
7
7
  }
8
- export declare function LayoutPicker(props: LayoutPickerProps): any;
8
+ export declare function LayoutPicker(props: LayoutPickerProps): import("solid-js").JSX.Element;
9
9
  export {};
@@ -14,7 +14,7 @@
14
14
  * the target pane.
15
15
  */
16
16
  import { execFileSync } from "node:child_process";
17
- import { explain, type DetectedState } from "./tui/detect/manifest.ts";
17
+ import { explain, type DetectedState, type ManifestConfidence } from "./tui/detect/manifest.ts";
18
18
  import {
19
19
  classifyInstant,
20
20
  parseAuthority,
@@ -22,7 +22,11 @@ import {
22
22
  type InstantState,
23
23
  } from "./tui/detect/classify.ts";
24
24
  import { getManifests } from "./tui/detect/manifest-loader.ts";
25
- import { readProcessTable, resolveAgentCommand } from "./tui/detect/process-tree.ts";
25
+ import {
26
+ describeSubtree,
27
+ readProcessTable,
28
+ resolveAgentCommand,
29
+ } from "./tui/detect/process-tree.ts";
26
30
  import { readPaneSnapshot } from "./tui/detect/snapshot.ts";
27
31
  import { IdeError } from "./lib/errors.ts";
28
32
 
@@ -53,6 +57,13 @@ export interface ExplainReport {
53
57
  manifestId: string | null;
54
58
  matchedCommand: string;
55
59
  source: "hint" | "fast" | "tree" | "none";
60
+ /** The matched manifest's evidence confidence (null when nothing resolved). */
61
+ confidence: ManifestConfidence | null;
62
+ /**
63
+ * Executable-ish tokens seen in the pane's process subtree. Populated for
64
+ * the "no manifest matched" case so the report can say what it DID find.
65
+ */
66
+ subtree: string[];
56
67
  };
57
68
  states: Array<{ state: DetectedState; matched: boolean }>;
58
69
  winner: DetectedState | null;
@@ -137,6 +148,9 @@ export function buildReport(target: string): ExplainReport {
137
148
  hint: info.hintRaw,
138
149
  });
139
150
  const manifest = resolved.manifest;
151
+ // When nothing resolved, record what the process-tree DID see so `explain`
152
+ // can point the user at an `@agent_hint` target instead of a dead end.
153
+ const subtree = manifest ? [] : describeSubtree(table, info.pid);
140
154
 
141
155
  // Snapshot + per-state explanation.
142
156
  const snapshot = { ...readPaneSnapshot(info.id), title: info.title };
@@ -168,6 +182,8 @@ export function buildReport(target: string): ExplainReport {
168
182
  manifestId: manifest?.id ?? null,
169
183
  matchedCommand: resolved.matchedCommand,
170
184
  source: resolved.source,
185
+ confidence: manifest ? (manifest.confidence ?? "conservative") : null,
186
+ subtree,
171
187
  },
172
188
  states: explained.checked,
173
189
  winner: explained.state,
@@ -223,10 +239,22 @@ export function renderReport(r: ExplainReport, opts: { color?: boolean } = {}):
223
239
  }
224
240
 
225
241
  // Resolution
226
- const mid = r.resolution.manifestId ?? dim("none");
227
- out.push(
228
- ` ${label("manifest")} ${mid} ${dim(`via ${r.resolution.source}` + (r.resolution.matchedCommand ? ` "${r.resolution.matchedCommand}"` : ""))}`,
229
- );
242
+ if (r.resolution.manifestId) {
243
+ const conf =
244
+ r.resolution.confidence === "tuned"
245
+ ? c("\x1b[32m", "tuned")
246
+ : dim(r.resolution.confidence ?? "conservative");
247
+ out.push(
248
+ ` ${label("manifest")} ${r.resolution.manifestId} ${dim(`via ${r.resolution.source}` + (r.resolution.matchedCommand ? ` "${r.resolution.matchedCommand}"` : ""))} [${conf}]`,
249
+ );
250
+ } else {
251
+ // No manifest matched — say so, and report what the process-tree DID see so
252
+ // the user can set an `@agent_hint` or add a manifest.
253
+ const saw =
254
+ r.resolution.subtree.length > 0 ? r.resolution.subtree.join(", ") : r.pane.cmd || "(nothing)";
255
+ out.push(` ${label("manifest")} ${dim("none matched")} — ${dim(`process-tree saw: ${saw}`)}`);
256
+ out.push(` ${dim("set `tmux set-option -p @agent_hint <agent>` to force one")}`);
257
+ }
230
258
 
231
259
  // Per-state rule results
232
260
  out.push("");