aimux-cli 0.1.15 → 0.1.18

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 (299) hide show
  1. package/README.md +184 -67
  2. package/bin/aimux-dev +10 -0
  3. package/dist/alert-display.d.ts +21 -0
  4. package/dist/alert-display.js +86 -0
  5. package/dist/alert-display.js.map +1 -0
  6. package/dist/attachment-store.d.ts +0 -7
  7. package/dist/attachment-store.js +2 -86
  8. package/dist/attachment-store.js.map +1 -1
  9. package/dist/builtin-metadata-watchers.js +4 -4
  10. package/dist/builtin-metadata-watchers.js.map +1 -1
  11. package/dist/claude-hooks.d.ts +1 -0
  12. package/dist/claude-hooks.js +25 -0
  13. package/dist/claude-hooks.js.map +1 -1
  14. package/dist/config.d.ts +19 -13
  15. package/dist/config.js +28 -14
  16. package/dist/config.js.map +1 -1
  17. package/dist/connection-targets.d.ts +8 -0
  18. package/dist/connection-targets.js +28 -0
  19. package/dist/connection-targets.js.map +1 -0
  20. package/dist/credentials.d.ts +12 -0
  21. package/dist/credentials.js +49 -0
  22. package/dist/credentials.js.map +1 -0
  23. package/dist/daemon.d.ts +23 -0
  24. package/dist/daemon.js +391 -66
  25. package/dist/daemon.js.map +1 -1
  26. package/dist/dashboard/index.d.ts +13 -10
  27. package/dist/dashboard/index.js +3 -26
  28. package/dist/dashboard/index.js.map +1 -1
  29. package/dist/dashboard/order.d.ts +22 -0
  30. package/dist/dashboard/order.js +55 -0
  31. package/dist/dashboard/order.js.map +1 -0
  32. package/dist/dashboard/pending-actions.d.ts +39 -10
  33. package/dist/dashboard/pending-actions.js +166 -36
  34. package/dist/dashboard/pending-actions.js.map +1 -1
  35. package/dist/dashboard/quick-jump.d.ts +2 -1
  36. package/dist/dashboard/quick-jump.js +7 -4
  37. package/dist/dashboard/quick-jump.js.map +1 -1
  38. package/dist/dashboard/session-actions.d.ts +4 -4
  39. package/dist/dashboard/session-actions.js +1 -1
  40. package/dist/dashboard/session-actions.js.map +1 -1
  41. package/dist/dashboard/session-registry.d.ts +4 -3
  42. package/dist/dashboard/session-registry.js +16 -50
  43. package/dist/dashboard/session-registry.js.map +1 -1
  44. package/dist/dashboard/state.d.ts +1 -1
  45. package/dist/dashboard/state.js.map +1 -1
  46. package/dist/dashboard/ui-state-store.d.ts +16 -1
  47. package/dist/dashboard/ui-state-store.js +73 -2
  48. package/dist/dashboard/ui-state-store.js.map +1 -1
  49. package/dist/debug-state.d.ts +97 -0
  50. package/dist/debug-state.js +541 -0
  51. package/dist/debug-state.js.map +1 -0
  52. package/dist/debug.d.ts +38 -0
  53. package/dist/debug.js +219 -15
  54. package/dist/debug.js.map +1 -1
  55. package/dist/default-plugins/gh-pr-context.d.ts +2 -1
  56. package/dist/default-plugins/gh-pr-context.js +17 -11
  57. package/dist/default-plugins/gh-pr-context.js.map +1 -1
  58. package/dist/default-plugins/transcript-length.js +15 -2
  59. package/dist/default-plugins/transcript-length.js.map +1 -1
  60. package/dist/fast-control.js +37 -19
  61. package/dist/fast-control.js.map +1 -1
  62. package/dist/http-client.js +31 -2
  63. package/dist/http-client.js.map +1 -1
  64. package/dist/local-ui-server.d.ts +22 -0
  65. package/dist/local-ui-server.js +186 -0
  66. package/dist/local-ui-server.js.map +1 -0
  67. package/dist/login-flow.d.ts +7 -0
  68. package/dist/login-flow.js +120 -0
  69. package/dist/login-flow.js.map +1 -0
  70. package/dist/main.js +824 -152
  71. package/dist/main.js.map +1 -1
  72. package/dist/managed-launch-env.js +14 -0
  73. package/dist/managed-launch-env.js.map +1 -1
  74. package/dist/metadata-server.d.ts +36 -36
  75. package/dist/metadata-server.js +638 -137
  76. package/dist/metadata-server.js.map +1 -1
  77. package/dist/metadata-store.d.ts +4 -1
  78. package/dist/metadata-store.js +30 -2
  79. package/dist/metadata-store.js.map +1 -1
  80. package/dist/multiplexer/agent-io-methods.d.ts +2 -10
  81. package/dist/multiplexer/agent-io-methods.js +12 -43
  82. package/dist/multiplexer/agent-io-methods.js.map +1 -1
  83. package/dist/multiplexer/archives.js +8 -9
  84. package/dist/multiplexer/archives.js.map +1 -1
  85. package/dist/multiplexer/dashboard-control.js +45 -13
  86. package/dist/multiplexer/dashboard-control.js.map +1 -1
  87. package/dist/multiplexer/dashboard-interaction.d.ts +8 -2
  88. package/dist/multiplexer/dashboard-interaction.js +187 -28
  89. package/dist/multiplexer/dashboard-interaction.js.map +1 -1
  90. package/dist/multiplexer/dashboard-model.d.ts +10 -3
  91. package/dist/multiplexer/dashboard-model.js +417 -35
  92. package/dist/multiplexer/dashboard-model.js.map +1 -1
  93. package/dist/multiplexer/dashboard-ops.d.ts +9 -7
  94. package/dist/multiplexer/dashboard-ops.js +178 -68
  95. package/dist/multiplexer/dashboard-ops.js.map +1 -1
  96. package/dist/multiplexer/dashboard-state-methods.d.ts +2 -1
  97. package/dist/multiplexer/dashboard-state-methods.js +3 -2
  98. package/dist/multiplexer/dashboard-state-methods.js.map +1 -1
  99. package/dist/multiplexer/dashboard-tail-methods.d.ts +22 -10
  100. package/dist/multiplexer/dashboard-tail-methods.js +164 -47
  101. package/dist/multiplexer/dashboard-tail-methods.js.map +1 -1
  102. package/dist/multiplexer/dashboard-view-methods.d.ts +1 -1
  103. package/dist/multiplexer/dashboard-view-methods.js +23 -8
  104. package/dist/multiplexer/dashboard-view-methods.js.map +1 -1
  105. package/dist/multiplexer/graveyard-view-model.d.ts +9 -1
  106. package/dist/multiplexer/graveyard-view-model.js +39 -0
  107. package/dist/multiplexer/graveyard-view-model.js.map +1 -1
  108. package/dist/multiplexer/index.d.ts +15 -12
  109. package/dist/multiplexer/index.js +64 -43
  110. package/dist/multiplexer/index.js.map +1 -1
  111. package/dist/multiplexer/notifications.js +107 -24
  112. package/dist/multiplexer/notifications.js.map +1 -1
  113. package/dist/multiplexer/persistence-methods.d.ts +31 -4
  114. package/dist/multiplexer/persistence-methods.js +304 -308
  115. package/dist/multiplexer/persistence-methods.js.map +1 -1
  116. package/dist/multiplexer/runtime-lifecycle-methods.d.ts +8 -10
  117. package/dist/multiplexer/runtime-lifecycle-methods.js +104 -86
  118. package/dist/multiplexer/runtime-lifecycle-methods.js.map +1 -1
  119. package/dist/multiplexer/runtime-state.d.ts +8 -10
  120. package/dist/multiplexer/runtime-state.js +82 -145
  121. package/dist/multiplexer/runtime-state.js.map +1 -1
  122. package/dist/multiplexer/runtime-sync.d.ts +2 -10
  123. package/dist/multiplexer/runtime-sync.js +3 -18
  124. package/dist/multiplexer/runtime-sync.js.map +1 -1
  125. package/dist/multiplexer/service-state-snapshot.d.ts +2 -4
  126. package/dist/multiplexer/service-state-snapshot.js +4 -51
  127. package/dist/multiplexer/service-state-snapshot.js.map +1 -1
  128. package/dist/multiplexer/services.d.ts +1 -0
  129. package/dist/multiplexer/services.js +55 -5
  130. package/dist/multiplexer/services.js.map +1 -1
  131. package/dist/multiplexer/session-capture.d.ts +1 -0
  132. package/dist/multiplexer/session-capture.js +24 -0
  133. package/dist/multiplexer/session-capture.js.map +1 -0
  134. package/dist/multiplexer/session-launch.d.ts +4 -1
  135. package/dist/multiplexer/session-launch.js +152 -63
  136. package/dist/multiplexer/session-launch.js.map +1 -1
  137. package/dist/multiplexer/session-runtime-core.d.ts +8 -20
  138. package/dist/multiplexer/session-runtime-core.js +40 -135
  139. package/dist/multiplexer/session-runtime-core.js.map +1 -1
  140. package/dist/multiplexer/subscreens.js +10 -3
  141. package/dist/multiplexer/subscreens.js.map +1 -1
  142. package/dist/multiplexer/worktree-graveyard.d.ts +0 -1
  143. package/dist/multiplexer/worktree-graveyard.js +15 -16
  144. package/dist/multiplexer/worktree-graveyard.js.map +1 -1
  145. package/dist/multiplexer/worktrees.js +96 -40
  146. package/dist/multiplexer/worktrees.js.map +1 -1
  147. package/dist/notification-context.js +8 -4
  148. package/dist/notification-context.js.map +1 -1
  149. package/dist/notifications.js +163 -101
  150. package/dist/notifications.js.map +1 -1
  151. package/dist/notify.d.ts +4 -0
  152. package/dist/notify.js +14 -0
  153. package/dist/notify.js.map +1 -1
  154. package/dist/paths.d.ts +32 -7
  155. package/dist/paths.js +82 -58
  156. package/dist/paths.js.map +1 -1
  157. package/dist/pending-actions.d.ts +5 -0
  158. package/dist/pending-actions.js +14 -0
  159. package/dist/pending-actions.js.map +1 -0
  160. package/dist/plugin-runtime.js +9 -2
  161. package/dist/plugin-runtime.js.map +1 -1
  162. package/dist/project-events.d.ts +1 -10
  163. package/dist/project-events.js +0 -10
  164. package/dist/project-events.js.map +1 -1
  165. package/dist/project-scanner.d.ts +2 -3
  166. package/dist/project-scanner.js +58 -129
  167. package/dist/project-scanner.js.map +1 -1
  168. package/dist/project-service-manifest.d.ts +1 -3
  169. package/dist/project-service-manifest.js +1 -3
  170. package/dist/project-service-manifest.js.map +1 -1
  171. package/dist/relay-client.d.ts +30 -0
  172. package/dist/relay-client.js +191 -0
  173. package/dist/relay-client.js.map +1 -0
  174. package/dist/remote-access.d.ts +16 -0
  175. package/dist/remote-access.js +91 -0
  176. package/dist/remote-access.js.map +1 -0
  177. package/dist/runtime-core/exchange-derived.d.ts +2 -0
  178. package/dist/runtime-core/exchange-derived.js +154 -0
  179. package/dist/runtime-core/exchange-derived.js.map +1 -0
  180. package/dist/runtime-core/exchange-import.d.ts +24 -0
  181. package/dist/runtime-core/exchange-import.js +318 -0
  182. package/dist/runtime-core/exchange-import.js.map +1 -0
  183. package/dist/runtime-core/exchange-store.d.ts +157 -0
  184. package/dist/runtime-core/exchange-store.js +453 -0
  185. package/dist/runtime-core/exchange-store.js.map +1 -0
  186. package/dist/runtime-core/topology-services.d.ts +38 -0
  187. package/dist/runtime-core/topology-services.js +171 -0
  188. package/dist/runtime-core/topology-services.js.map +1 -0
  189. package/dist/runtime-core/topology-sessions.d.ts +52 -0
  190. package/dist/runtime-core/topology-sessions.js +239 -0
  191. package/dist/runtime-core/topology-sessions.js.map +1 -0
  192. package/dist/runtime-core/topology-store.d.ts +171 -0
  193. package/dist/runtime-core/topology-store.js +420 -0
  194. package/dist/runtime-core/topology-store.js.map +1 -0
  195. package/dist/runtime-core/topology-worktrees.d.ts +60 -0
  196. package/dist/runtime-core/topology-worktrees.js +200 -0
  197. package/dist/runtime-core/topology-worktrees.js.map +1 -0
  198. package/dist/runtime-migration.d.ts +69 -0
  199. package/dist/runtime-migration.js +399 -0
  200. package/dist/runtime-migration.js.map +1 -0
  201. package/dist/session-bootstrap.d.ts +8 -6
  202. package/dist/session-bootstrap.js +51 -158
  203. package/dist/session-bootstrap.js.map +1 -1
  204. package/dist/session-runtime.d.ts +2 -0
  205. package/dist/session-runtime.js +1 -0
  206. package/dist/session-runtime.js.map +1 -1
  207. package/dist/session-semantics.d.ts +12 -4
  208. package/dist/session-semantics.js +14 -0
  209. package/dist/session-semantics.js.map +1 -1
  210. package/dist/shell-hooks.js +32 -10
  211. package/dist/shell-hooks.js.map +1 -1
  212. package/dist/shell-state.d.ts +2 -0
  213. package/dist/shell-state.js +26 -1
  214. package/dist/shell-state.js.map +1 -1
  215. package/dist/statusline-model.d.ts +10 -2
  216. package/dist/statusline-model.js +106 -30
  217. package/dist/statusline-model.js.map +1 -1
  218. package/dist/task-workflow.d.ts +6 -9
  219. package/dist/task-workflow.js +37 -84
  220. package/dist/task-workflow.js.map +1 -1
  221. package/dist/tasks.d.ts +6 -33
  222. package/dist/tasks.js +46 -88
  223. package/dist/tasks.js.map +1 -1
  224. package/dist/team.d.ts +29 -0
  225. package/dist/team.js +40 -0
  226. package/dist/team.js.map +1 -1
  227. package/dist/threads.d.ts +6 -35
  228. package/dist/threads.js +89 -98
  229. package/dist/threads.js.map +1 -1
  230. package/dist/tmux/inbox-popup.js +37 -15
  231. package/dist/tmux/inbox-popup.js.map +1 -1
  232. package/dist/tmux/runtime-manager.d.ts +3 -0
  233. package/dist/tmux/runtime-manager.js +21 -4
  234. package/dist/tmux/runtime-manager.js.map +1 -1
  235. package/dist/tmux/statusline.js +49 -9
  236. package/dist/tmux/statusline.js.map +1 -1
  237. package/dist/tmux/window-open.js +1 -2
  238. package/dist/tmux/window-open.js.map +1 -1
  239. package/dist/tool-output-watchers.d.ts +0 -18
  240. package/dist/tool-output-watchers.js +0 -322
  241. package/dist/tool-output-watchers.js.map +1 -1
  242. package/dist/tui/screens/dashboard-renderers.js +37 -25
  243. package/dist/tui/screens/dashboard-renderers.js.map +1 -1
  244. package/dist/tui/screens/overlay-renderers.d.ts +2 -0
  245. package/dist/tui/screens/overlay-renderers.js +37 -1
  246. package/dist/tui/screens/overlay-renderers.js.map +1 -1
  247. package/dist/tui/screens/subscreen-renderers.js +7 -0
  248. package/dist/tui/screens/subscreen-renderers.js.map +1 -1
  249. package/dist/worktree.js +17 -0
  250. package/dist/worktree.js.map +1 -1
  251. package/dist-ui/_expo/static/css/web-30453ede1678c16acb08b97e83e8646d.css +1 -0
  252. package/dist-ui/_expo/static/js/web/entry-477c745b2adc79367a4380ecf07d9ff6.js +14620 -0
  253. package/dist-ui/assets/assets/images/icon.a5413dcd2e811c9f2317d01a28118d8a.png +0 -0
  254. package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
  255. package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
  256. package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
  257. package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
  258. package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
  259. package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
  260. package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
  261. package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
  262. package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
  263. package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
  264. package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
  265. package/dist-ui/assets/node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
  266. package/dist-ui/assets/node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
  267. package/dist-ui/assets/node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
  268. package/dist-ui/assets/node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
  269. package/dist-ui/assets/node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
  270. package/dist-ui/assets/node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
  271. package/dist-ui/assets/node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
  272. package/dist-ui/favicon.ico +0 -0
  273. package/dist-ui/index.html +38 -0
  274. package/dist-ui/metadata.json +1 -0
  275. package/package.json +31 -15
  276. package/dist/agent-message-parts.d.ts +0 -17
  277. package/dist/agent-message-parts.js +0 -31
  278. package/dist/agent-message-parts.js.map +0 -1
  279. package/dist/instance-directory.d.ts +0 -32
  280. package/dist/instance-directory.js +0 -82
  281. package/dist/instance-directory.js.map +0 -1
  282. package/dist/instance-registry.d.ts +0 -39
  283. package/dist/instance-registry.js +0 -208
  284. package/dist/instance-registry.js.map +0 -1
  285. package/dist/multiplexer/session-actions.d.ts +0 -40
  286. package/dist/multiplexer/session-actions.js +0 -110
  287. package/dist/multiplexer/session-actions.js.map +0 -1
  288. package/dist/orchestration-dispatcher.d.ts +0 -25
  289. package/dist/orchestration-dispatcher.js +0 -59
  290. package/dist/orchestration-dispatcher.js.map +0 -1
  291. package/dist/session-input-operations.d.ts +0 -19
  292. package/dist/session-input-operations.js +0 -46
  293. package/dist/session-input-operations.js.map +0 -1
  294. package/dist/session-message-history.d.ts +0 -27
  295. package/dist/session-message-history.js +0 -105
  296. package/dist/session-message-history.js.map +0 -1
  297. package/dist/task-dispatcher.d.ts +0 -64
  298. package/dist/task-dispatcher.js +0 -213
  299. package/dist/task-dispatcher.js.map +0 -1
@@ -8,18 +8,344 @@ import { listThreadSummaries, readMessages } from "../threads.js";
8
8
  import { deriveSessionSemantics } from "../session-semantics.js";
9
9
  import { summarizeUnreadNotificationsBySession } from "../notifications.js";
10
10
  import { requestJson } from "../http-client.js";
11
+ import { isTeammateSession, selectDirectTeammates } from "../team.js";
11
12
  import { buildWorkflowEntries, describeWorkflowNextAction } from "../workflow.js";
12
13
  import { ensureDaemonRunning, ensureProjectService } from "../daemon.js";
13
14
  import { isDashboardWindowName } from "../tmux/runtime-manager.js";
14
15
  import { dashboardCreatedSortKey, sortDashboardEntriesByCreatedAt } from "../dashboard/sort.js";
15
16
  import { listDashboardOperationFailures } from "../dashboard/operation-failures.js";
16
17
  import { listWorktreeGraveyardPaths } from "./worktree-graveyard.js";
18
+ import { setPendingDashboardServiceAction, setPendingDashboardSessionAction } from "./dashboard-ops.js";
19
+ import { listTopologySessionStates } from "../runtime-core/topology-sessions.js";
20
+ const METADATA_PENDING_SETTLE_TIMEOUT_MS = 10_000;
21
+ const METADATA_PENDING_SETTLE_INTERVAL_MS = 100;
22
+ function sleep(ms) {
23
+ return new Promise((resolve) => setTimeout(resolve, ms));
24
+ }
25
+ function listOfflineSessionsForAction(host) {
26
+ const sessionsById = new Map();
27
+ for (const session of host.offlineSessions ?? []) {
28
+ if (session?.id)
29
+ sessionsById.set(session.id, session);
30
+ }
31
+ for (const session of listTopologySessionStates({ statuses: ["offline"] })) {
32
+ if (session?.id && !sessionsById.has(session.id))
33
+ sessionsById.set(session.id, session);
34
+ }
35
+ return [...sessionsById.values()];
36
+ }
37
+ function reconcileSessionsForLifecycleAction(host) {
38
+ host.syncSessionsFromTopology?.();
39
+ host.saveState?.();
40
+ }
41
+ function resolveOfflineSessionForAction(host, sessionId) {
42
+ return listOfflineSessionsForAction(host).find((session) => session.id === sessionId);
43
+ }
44
+ function findDashboardSessionSeed(host, sessionId, fallback) {
45
+ const cached = host.dashboardSessionsCache?.find?.((entry) => entry.id === sessionId);
46
+ if (cached)
47
+ return cached;
48
+ return toDashboardSessionSeed(host.sessions?.find?.((entry) => entry.id === sessionId) ??
49
+ host.offlineSessions?.find?.((entry) => entry.id === sessionId) ??
50
+ (fallback?.id ? fallback : undefined));
51
+ }
52
+ function findDashboardServiceSeed(host, serviceId) {
53
+ const cached = host.dashboardServicesCache?.find?.((entry) => entry.id === serviceId);
54
+ if (cached)
55
+ return cached;
56
+ return toDashboardServiceSeed(host.services?.find?.((entry) => entry.id === serviceId) ??
57
+ host.offlineServices?.find?.((entry) => entry.id === serviceId));
58
+ }
59
+ function toDashboardSessionSeed(seed) {
60
+ if (!seed?.id || !seed.command)
61
+ return undefined;
62
+ return {
63
+ index: typeof seed.index === "number" ? seed.index : -1,
64
+ id: seed.id,
65
+ command: seed.command,
66
+ label: seed.label,
67
+ status: seed.status ?? (seed.lifecycle === "offline" ? "offline" : "running"),
68
+ active: Boolean(seed.active),
69
+ worktreePath: seed.worktreePath,
70
+ createdAt: seed.createdAt,
71
+ backendSessionId: seed.backendSessionId,
72
+ headline: seed.headline,
73
+ team: seed.team,
74
+ };
75
+ }
76
+ function toDashboardServiceSeed(seed) {
77
+ if (!seed?.id)
78
+ return undefined;
79
+ return {
80
+ id: seed.id,
81
+ command: seed.command ?? seed.launchCommandLine ?? seed.label ?? "service",
82
+ args: Array.isArray(seed.args) ? seed.args : [],
83
+ label: seed.label,
84
+ status: seed.status ?? (seed.lifecycle === "offline" ? "offline" : "running"),
85
+ active: Boolean(seed.active),
86
+ worktreePath: seed.worktreePath,
87
+ createdAt: seed.createdAt,
88
+ };
89
+ }
90
+ function buildMetadataPendingSessionSeed(input) {
91
+ return {
92
+ index: -1,
93
+ id: input.sessionId,
94
+ command: input.tool,
95
+ label: input.tool,
96
+ createdAt: new Date().toISOString(),
97
+ status: "waiting",
98
+ active: false,
99
+ worktreePath: input.worktreePath,
100
+ pendingAction: input.pendingAction,
101
+ optimistic: true,
102
+ team: input.team,
103
+ };
104
+ }
105
+ async function waitForMetadataCondition(host, predicate, timeoutMs = METADATA_PENDING_SETTLE_TIMEOUT_MS) {
106
+ const deadline = Date.now() + timeoutMs;
107
+ while (Date.now() < deadline) {
108
+ try {
109
+ host.invalidateDesktopStateSnapshot?.();
110
+ host.refreshLocalDashboardModel?.();
111
+ }
112
+ catch { }
113
+ if (predicate())
114
+ return true;
115
+ await sleep(METADATA_PENDING_SETTLE_INTERVAL_MS);
116
+ }
117
+ try {
118
+ host.invalidateDesktopStateSnapshot?.();
119
+ host.refreshLocalDashboardModel?.();
120
+ }
121
+ catch { }
122
+ return predicate();
123
+ }
124
+ function hasLiveManagedAgentWindow(host, sessionId) {
125
+ try {
126
+ if (!host.tmuxRuntimeManager?.listProjectManagedWindows)
127
+ return false;
128
+ return host.tmuxRuntimeManager.listProjectManagedWindows(process.cwd()).some(({ target, metadata }) => {
129
+ if (isDashboardWindowName(target.windowName))
130
+ return false;
131
+ if (metadata.kind !== "agent" || metadata.sessionId !== sessionId)
132
+ return false;
133
+ if (host.tmuxRuntimeManager.isWindowAlive && !host.tmuxRuntimeManager.isWindowAlive(target))
134
+ return false;
135
+ return true;
136
+ });
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ }
142
+ function hasLiveManagedServiceWindow(host, serviceId) {
143
+ try {
144
+ if (!host.tmuxRuntimeManager?.listProjectManagedWindows)
145
+ return false;
146
+ return host.tmuxRuntimeManager.listProjectManagedWindows(process.cwd()).some(({ target, metadata }) => {
147
+ if (isDashboardWindowName(target.windowName))
148
+ return false;
149
+ if (metadata.kind !== "service" || metadata.sessionId !== serviceId)
150
+ return false;
151
+ if (host.tmuxRuntimeManager.isWindowAlive && !host.tmuxRuntimeManager.isWindowAlive(target))
152
+ return false;
153
+ return true;
154
+ });
155
+ }
156
+ catch {
157
+ return false;
158
+ }
159
+ }
160
+ function isMetadataSessionRunning(host, sessionId) {
161
+ if (host.sessions?.some?.((session) => session.id === sessionId && !session.exited))
162
+ return true;
163
+ if (host.sessionTmuxTargets?.has?.(sessionId))
164
+ return true;
165
+ return hasLiveManagedAgentWindow(host, sessionId);
166
+ }
167
+ async function waitForMetadataSessionRunning(host, sessionId) {
168
+ if (typeof host.waitForSessionStart === "function") {
169
+ try {
170
+ if (await host.waitForSessionStart(sessionId, METADATA_PENDING_SETTLE_TIMEOUT_MS))
171
+ return true;
172
+ }
173
+ catch { }
174
+ }
175
+ return waitForMetadataCondition(host, () => isMetadataSessionRunning(host, sessionId));
176
+ }
177
+ function isMetadataServiceRunning(host, serviceId) {
178
+ const offline = host.offlineServices?.some?.((service) => service.id === serviceId);
179
+ if (offline)
180
+ return false;
181
+ if (host.services?.some?.((service) => service.id === serviceId && service.status !== "offline"))
182
+ return true;
183
+ return hasLiveManagedServiceWindow(host, serviceId);
184
+ }
185
+ function isMetadataServiceOffline(host, serviceId) {
186
+ return Boolean(host.offlineServices?.some?.((service) => service.id === serviceId));
187
+ }
188
+ function isMetadataServiceRemoved(host, serviceId) {
189
+ const offline = host.offlineServices?.some?.((service) => service.id === serviceId);
190
+ if (offline)
191
+ return false;
192
+ return !hasLiveManagedServiceWindow(host, serviceId);
193
+ }
194
+ async function settleMetadataPending(host, description, settle, result) {
195
+ if (!settle)
196
+ return;
197
+ try {
198
+ const settled = await settle(result);
199
+ if (!settled) {
200
+ host.debug?.(`metadata pending action did not settle: ${description}`, "dashboard");
201
+ }
202
+ }
203
+ catch (error) {
204
+ host.debug?.(`metadata pending action settle failed: ${description}: ${error instanceof Error ? error.message : String(error)}`, "dashboard");
205
+ }
206
+ }
207
+ function clearMetadataSessionPendingAfterSettle(host, sessionId, kind, token, settle, result) {
208
+ void (async () => {
209
+ await settleMetadataPending(host, `session ${kind} ${sessionId}`, settle, result);
210
+ if (typeof token === "number") {
211
+ if (host.dashboardPendingActions?.clearSessionActionIfToken?.(sessionId, token)) {
212
+ host.reapplyDashboardPendingActions?.();
213
+ }
214
+ }
215
+ else if (host.dashboardPendingActions?.getSessionAction?.(sessionId) === kind) {
216
+ setPendingDashboardSessionAction(host, sessionId, null);
217
+ }
218
+ })();
219
+ }
220
+ function clearMetadataServicePendingAfterSettle(host, serviceId, kind, token, settle, result) {
221
+ void (async () => {
222
+ await settleMetadataPending(host, `service ${kind} ${serviceId}`, settle, result);
223
+ if (typeof token === "number") {
224
+ if (host.dashboardPendingActions?.clearServiceActionIfToken?.(serviceId, token)) {
225
+ host.reapplyDashboardPendingActions?.();
226
+ }
227
+ }
228
+ else if (host.dashboardPendingActions?.getServiceAction?.(serviceId) === kind) {
229
+ setPendingDashboardServiceAction(host, serviceId, null);
230
+ }
231
+ })();
232
+ }
233
+ export async function withMetadataSessionPending(host, sessionId, kind, work, sessionSeed, settle) {
234
+ let token;
235
+ if (sessionId) {
236
+ token = setPendingDashboardSessionAction(host, sessionId, kind, { sessionSeed });
237
+ }
238
+ try {
239
+ const result = await work();
240
+ if (sessionId) {
241
+ clearMetadataSessionPendingAfterSettle(host, sessionId, kind, token, settle, result);
242
+ }
243
+ return result;
244
+ }
245
+ catch (error) {
246
+ if (sessionId) {
247
+ if (typeof token === "number") {
248
+ if (host.dashboardPendingActions?.clearSessionActionIfToken?.(sessionId, token)) {
249
+ host.reapplyDashboardPendingActions?.();
250
+ }
251
+ }
252
+ else {
253
+ setPendingDashboardSessionAction(host, sessionId, null);
254
+ }
255
+ }
256
+ throw error;
257
+ }
258
+ }
259
+ export async function withMetadataServicePending(host, serviceId, kind, work, settle) {
260
+ const token = setPendingDashboardServiceAction(host, serviceId, kind, {
261
+ serviceSeed: findDashboardServiceSeed(host, serviceId),
262
+ });
263
+ try {
264
+ const result = await work();
265
+ clearMetadataServicePendingAfterSettle(host, serviceId, kind, token, settle, result);
266
+ return result;
267
+ }
268
+ catch (error) {
269
+ if (host.dashboardPendingActions?.clearServiceActionIfToken?.(serviceId, token)) {
270
+ host.reapplyDashboardPendingActions?.();
271
+ }
272
+ throw error;
273
+ }
274
+ }
275
+ function lifecycleFailureMessage(action, failures) {
276
+ const noun = failures.length === 1 ? "teammate" : "teammates";
277
+ const details = failures
278
+ .map(({ sessionId, error }) => `${sessionId}: ${error instanceof Error ? error.message : String(error)}`)
279
+ .join("; ");
280
+ return `Failed to ${action} ${failures.length} ${noun}: ${details}`;
281
+ }
282
+ async function resumeOfflineAgentWithPending(host, sessionId) {
283
+ return withMetadataSessionPending(host, sessionId, "starting", () => {
284
+ reconcileSessionsForLifecycleAction(host);
285
+ const offline = resolveOfflineSessionForAction(host, sessionId);
286
+ if (!offline) {
287
+ throw new Error(`Agent "${sessionId}" not found`);
288
+ }
289
+ host.resumeOfflineSession(offline);
290
+ return { sessionId, status: "running" };
291
+ }, findDashboardSessionSeed(host, sessionId), () => waitForMetadataSessionRunning(host, sessionId));
292
+ }
293
+ async function resumeOfflineAgentWithPendingAndSettle(host, sessionId) {
294
+ const result = await resumeOfflineAgentWithPending(host, sessionId);
295
+ await waitForMetadataSessionRunning(host, sessionId);
296
+ return result;
297
+ }
298
+ async function resumeAgentAndDirectTeammates(host, sessionId) {
299
+ reconcileSessionsForLifecycleAction(host);
300
+ const offline = resolveOfflineSessionForAction(host, sessionId);
301
+ if (!offline) {
302
+ throw new Error(`Agent "${sessionId}" not found`);
303
+ }
304
+ const teammates = isTeammateSession(offline)
305
+ ? []
306
+ : selectDirectTeammates(listOfflineSessionsForAction(host), sessionId);
307
+ const result = await resumeOfflineAgentWithPendingAndSettle(host, sessionId);
308
+ const teammateFailures = [];
309
+ for (const teammate of teammates) {
310
+ try {
311
+ await resumeOfflineAgentWithPendingAndSettle(host, teammate.id);
312
+ }
313
+ catch (error) {
314
+ teammateFailures.push({ sessionId: teammate.id, error });
315
+ }
316
+ }
317
+ if (teammateFailures.length > 0) {
318
+ return {
319
+ ...result,
320
+ warning: lifecycleFailureMessage("resume", teammateFailures),
321
+ teammateFailures: teammateFailures.map(({ sessionId, error }) => ({
322
+ sessionId,
323
+ error: error instanceof Error ? error.message : String(error),
324
+ })),
325
+ };
326
+ }
327
+ return result;
328
+ }
17
329
  function runProjectServiceUiRefresh(host) {
18
330
  host.writeStatuslineFile();
19
331
  if (host.mode === "dashboard") {
20
332
  host.renderCurrentDashboardView();
21
333
  }
22
334
  }
335
+ const projectServiceAgentResumeQueues = new WeakMap();
336
+ async function enqueueProjectServiceAgentResume(host, work) {
337
+ const previous = projectServiceAgentResumeQueues.get(host) ?? Promise.resolve();
338
+ const current = previous.catch(() => undefined).then(work);
339
+ const tracked = current
340
+ .catch(() => undefined)
341
+ .finally(() => {
342
+ if (projectServiceAgentResumeQueues.get(host) === tracked) {
343
+ projectServiceAgentResumeQueues.delete(host);
344
+ }
345
+ });
346
+ projectServiceAgentResumeQueues.set(host, tracked);
347
+ return current;
348
+ }
23
349
  function scheduleProjectServiceUiRefresh(host) {
24
350
  if (host.projectServiceStartupMetadataSettling) {
25
351
  host.projectServiceUiRefreshPending = true;
@@ -82,22 +408,28 @@ export function composeDashboardWorktreeGroups(worktreeGroups, dashSessions, das
82
408
  };
83
409
  }));
84
410
  }
85
- export function applyDashboardModel(host, dashSessions, dashServices, worktreeGroups, mainCheckoutInfo, operationFailures = []) {
411
+ export function applyDashboardModel(host, dashSessions, dashTeammates, dashServices, worktreeGroups, mainCheckoutInfo, operationFailures = []) {
86
412
  const snapshotKey = JSON.stringify({
87
413
  sessions: dashSessions,
414
+ teammates: dashTeammates,
88
415
  services: dashServices,
89
416
  worktreeGroups,
90
417
  mainCheckoutInfo,
91
418
  operationFailures,
419
+ pendingActionsVersion: host.dashboardPendingActions.getVersion?.() ?? 0,
92
420
  });
93
421
  if (snapshotKey === host.dashboardModelSnapshotKey) {
94
422
  host.dashboardModelRefreshedAt = Date.now();
95
423
  return false;
96
424
  }
97
425
  host.dashboardModelSnapshotKey = snapshotKey;
426
+ host.dashboardRawWorktreeGroupsCache = worktreeGroups;
98
427
  host.dashboardSessionsCache = host.dashboardPendingActions.applyToSessions(dashSessions);
428
+ host.dashboardTeammatesCache = host.dashboardPendingActions
429
+ .applyToSessions(dashTeammates, { includeTeammates: true })
430
+ .filter((session) => isTeammateSession(session));
99
431
  host.dashboardServicesCache = host.dashboardPendingActions.applyToServices(dashServices);
100
- host.dashboardWorktreeGroupsCache = composeDashboardWorktreeGroups(host.dashboardPendingActions.applyToWorktrees(worktreeGroups), host.dashboardSessionsCache, host.dashboardServicesCache);
432
+ host.dashboardWorktreeGroupsCache = host.dashboardUiStateStore.orderWorktreeGroups(composeDashboardWorktreeGroups(host.dashboardPendingActions.applyToWorktrees(worktreeGroups), host.dashboardSessionsCache, host.dashboardServicesCache));
101
433
  host.dashboardOperationFailuresCache = operationFailures;
102
434
  host.dashboardMainCheckoutInfoCache = mainCheckoutInfo;
103
435
  host.dashboardModelVersion = (host.dashboardModelVersion ?? 0) + 1;
@@ -111,7 +443,7 @@ export function invalidateDesktopStateSnapshot(host) {
111
443
  export function refreshDesktopStateSnapshot(host) {
112
444
  host.desktopStateSnapshot = buildDesktopStateSnapshot(host);
113
445
  }
114
- export function computeDashboardSessions(host) {
446
+ export function computeDashboardSessions(host, options = {}) {
115
447
  const lastUsedState = loadLastUsedState(process.cwd());
116
448
  const metadata = loadMetadataState().sessions;
117
449
  const threadSummaries = listThreadSummaries();
@@ -187,6 +519,7 @@ export function computeDashboardSessions(host) {
187
519
  id: session.id,
188
520
  command: session.command,
189
521
  backendSessionId: session.backendSessionId,
522
+ team: session.team,
190
523
  createdAt: session.startTime ? new Date(session.startTime).toISOString() : undefined,
191
524
  status: session.status,
192
525
  worktreePath: host.sessionWorktreePaths.get(session.id),
@@ -194,12 +527,12 @@ export function computeDashboardSessions(host) {
194
527
  })),
195
528
  activeIndex: host.activeIndex,
196
529
  offlineSessions: host.offlineSessions,
197
- remoteInstances: [],
198
530
  hiddenWorktreePaths: listWorktreeGraveyardPaths(),
199
531
  mainRepoPath,
532
+ includeTeammates: options.includeTeammates,
200
533
  getSessionLabel: (sessionId) => host.getSessionLabel(sessionId),
201
534
  getSessionHeadline: (sessionId) => host.deriveHeadline(sessionId),
202
- getSessionTaskDescription: (sessionId) => host.taskDispatcher?.getSessionTask(sessionId),
535
+ getSessionTaskDescription: () => undefined,
203
536
  getSessionRole: (sessionId) => host.sessionRoles.get(sessionId),
204
537
  getSessionContext: (sessionId) => metadata[sessionId]?.context,
205
538
  getSessionDerived: (sessionId) => metadata[sessionId]?.derived,
@@ -265,6 +598,7 @@ export function computeDashboardSessions(host) {
265
598
  export function computeDashboardServices(host, worktrees = host.listDesktopWorktrees()) {
266
599
  const hiddenWorktreePaths = listWorktreeGraveyardPaths();
267
600
  const lastUsedState = loadLastUsedState(process.cwd());
601
+ const sessionMetadata = loadMetadataState().sessions;
268
602
  const offlineServiceIds = new Set(host.offlineServices.map((service) => service.id));
269
603
  const worktreeByPath = new Map(worktrees.map((wt) => [wt.path, wt]));
270
604
  const liveServices = host.tmuxRuntimeManager
@@ -275,6 +609,7 @@ export function computeDashboardServices(host, worktrees = host.listDesktopWorkt
275
609
  .map(({ target, metadata }) => {
276
610
  const worktree = metadata.worktreePath ? worktreeByPath.get(metadata.worktreePath) : undefined;
277
611
  const info = readTmuxProcessInfo(host, target);
612
+ const shellMetadata = sessionMetadata[metadata.sessionId]?.derived;
278
613
  return {
279
614
  id: metadata.sessionId,
280
615
  command: metadata.command,
@@ -291,6 +626,8 @@ export function computeDashboardServices(host, worktrees = host.listDesktopWorkt
291
626
  label: metadata.label,
292
627
  cwd: host.tmuxRuntimeManager.displayMessage("#{pane_current_path}", target.windowId) ?? metadata.worktreePath,
293
628
  foregroundCommand: info.command,
629
+ shellCommand: shellMetadata?.shellCommand,
630
+ shellCommandState: shellMetadata?.shellCommandState,
294
631
  pid: info.pid,
295
632
  previewLine: info.previewLine,
296
633
  };
@@ -303,6 +640,7 @@ export function computeDashboardServices(host, worktrees = host.listDesktopWorkt
303
640
  const worktree = service.worktreePath ? worktreeByPath.get(service.worktreePath) : undefined;
304
641
  const label = service.label ?? host.serviceLabelForCommand(service.launchCommandLine ?? "");
305
642
  const previewLine = service.launchCommandLine?.trim() || "Interactive shell";
643
+ const shellMetadata = sessionMetadata[service.id]?.derived;
306
644
  return {
307
645
  id: service.id,
308
646
  command: service.launchCommandLine?.trim() ?? "",
@@ -317,6 +655,8 @@ export function computeDashboardServices(host, worktrees = host.listDesktopWorkt
317
655
  label,
318
656
  cwd: service.cwd ?? service.worktreePath,
319
657
  foregroundCommand: label,
658
+ shellCommand: shellMetadata?.shellCommand,
659
+ shellCommandState: shellMetadata?.shellCommandState,
320
660
  previewLine,
321
661
  };
322
662
  });
@@ -342,7 +682,7 @@ export function readTmuxProcessInfo(host, target) {
342
682
  };
343
683
  }
344
684
  export function buildDesktopStateSnapshot(host) {
345
- host.syncSessionsFromState();
685
+ host.syncSessionsFromTopology();
346
686
  const worktrees = host.listDesktopWorktrees();
347
687
  const realizedWorktreePaths = new Set(worktrees.filter((worktree) => !worktree.operationFailure).map((worktree) => worktree.path));
348
688
  const operationFailures = listDashboardOperationFailures().filter((failure) => !(failure.targetKind === "worktree" &&
@@ -360,6 +700,7 @@ export function buildDesktopStateSnapshot(host) {
360
700
  }
361
701
  return {
362
702
  sessions: computeDashboardSessions(host),
703
+ teammates: computeDashboardSessions(host, { includeTeammates: true }).filter((session) => isTeammateSession(session)),
363
704
  services: computeDashboardServices(host, worktrees),
364
705
  worktrees,
365
706
  operationFailures,
@@ -383,15 +724,16 @@ export async function refreshDashboardModelFromService(host, force = false) {
383
724
  if (endpoint) {
384
725
  try {
385
726
  const { status, json } = await requestJson(`http://${endpoint.host}:${endpoint.port}/desktop-state`, {
386
- timeoutMs: 250,
727
+ timeoutMs: force ? 2000 : 750,
387
728
  });
388
729
  if (status >= 200 && status < 300) {
389
730
  const body = json;
390
731
  const dashSessions = body.sessions ?? [];
732
+ const dashTeammates = body.teammates ?? [];
391
733
  const dashServices = body.services ?? [];
392
734
  const worktrees = body.worktrees ?? [];
393
735
  const worktreeGroups = buildDashboardWorktreeGroups(host, dashSessions, dashServices, worktrees, body.mainCheckoutPath);
394
- return applyDashboardModel(host, dashSessions, dashServices, worktreeGroups, body.mainCheckoutInfo ?? { name: "Main Checkout", branch: "" }, body.operationFailures ?? []);
736
+ return applyDashboardModel(host, dashSessions, dashTeammates, dashServices, worktreeGroups, body.mainCheckoutInfo ?? { name: "Main Checkout", branch: "" }, body.operationFailures ?? []);
395
737
  }
396
738
  }
397
739
  catch {
@@ -416,7 +758,7 @@ export async function refreshDashboardModelFromService(host, force = false) {
416
758
  export function refreshLocalDashboardModel(host) {
417
759
  const snapshot = buildDesktopStateSnapshot(host);
418
760
  const worktreeGroups = buildDashboardWorktreeGroups(host, snapshot.sessions, snapshot.services, snapshot.worktrees, snapshot.mainCheckoutPath);
419
- applyDashboardModel(host, snapshot.sessions, snapshot.services, worktreeGroups, snapshot.mainCheckoutInfo, snapshot.operationFailures);
761
+ applyDashboardModel(host, snapshot.sessions, snapshot.teammates, snapshot.services, worktreeGroups, snapshot.mainCheckoutInfo, snapshot.operationFailures);
420
762
  }
421
763
  export async function startProjectServices(host) {
422
764
  if (host.metadataServer)
@@ -427,20 +769,27 @@ export async function startProjectServices(host) {
427
769
  events: { bus: host.eventBus },
428
770
  desktop: {
429
771
  getState: () => host.buildDesktopState(),
430
- listWorktrees: () => host.listDesktopWorktrees(),
772
+ listWorktrees: () => host.listProjectedDesktopWorktrees(),
431
773
  getSessionDisplayContext: (sessionId) => {
432
774
  const session = host.dashboardSessionsCache.find((entry) => entry.id === sessionId) ??
433
775
  host.sessions.find((entry) => entry.id === sessionId);
434
- const worktreePath = host.sessionWorktreePaths.get(sessionId) ?? session?.worktreePath;
776
+ const service = host.dashboardServicesCache.find((entry) => entry.id === sessionId) ??
777
+ host.services?.find?.((entry) => entry.id === sessionId) ??
778
+ host.offlineServices?.find?.((entry) => entry.id === sessionId);
779
+ const worktreePath = host.sessionWorktreePaths.get(sessionId) ?? session?.worktreePath ?? service?.worktreePath;
435
780
  const group = worktreePath
436
781
  ? host.dashboardWorktreeGroupsCache.find((entry) => entry.path === worktreePath)
437
782
  : host.dashboardWorktreeGroupsCache.find((entry) => !entry.path);
438
783
  return {
439
- label: host.getSessionLabel(sessionId) ?? session?.label ?? session?.command,
440
- command: session?.command,
784
+ label: host.getSessionLabel(sessionId) ??
785
+ session?.label ??
786
+ service?.label ??
787
+ (service ? host.serviceLabelForCommand?.(service.launchCommandLine ?? service.command ?? "") : undefined) ??
788
+ session?.command,
789
+ command: session?.command ?? service?.command ?? service?.launchCommandLine,
441
790
  worktreePath,
442
- worktreeName: session?.worktreeName ?? group?.name,
443
- branch: session?.worktreeBranch ?? group?.branch,
791
+ worktreeName: session?.worktreeName ?? service?.worktreeName ?? group?.name,
792
+ branch: session?.worktreeBranch ?? service?.worktreeBranch ?? group?.branch,
444
793
  };
445
794
  },
446
795
  refreshStatusline: ({ sessionId, force }) => host.refreshProjectStatusline({ sessionId, force }),
@@ -451,17 +800,10 @@ export async function startProjectServices(host) {
451
800
  resurrectGraveyardWorktree: ({ path }) => host.resurrectGraveyardWorktree(path),
452
801
  deleteGraveyardWorktree: ({ path }) => host.deleteGraveyardWorktree(path),
453
802
  createService: ({ command, worktreePath, serviceId }) => host.createService(command ?? "", worktreePath, { serviceId }),
454
- stopService: ({ serviceId }) => host.stopService(serviceId),
455
- resumeService: ({ serviceId }) => host.resumeOfflineServiceById(serviceId),
456
- removeService: ({ serviceId }) => host.removeOfflineService(serviceId),
457
- resumeAgent: ({ sessionId }) => {
458
- const offline = host.offlineSessions.find((session) => session.id === sessionId);
459
- if (!offline) {
460
- throw new Error(`Agent "${sessionId}" not found`);
461
- }
462
- host.resumeOfflineSession(offline);
463
- return { sessionId, status: "running" };
464
- },
803
+ stopService: ({ serviceId }) => withMetadataServicePending(host, serviceId, "stopping", () => host.stopService(serviceId), () => waitForMetadataCondition(host, () => isMetadataServiceOffline(host, serviceId))),
804
+ resumeService: ({ serviceId }) => withMetadataServicePending(host, serviceId, "starting", () => host.resumeOfflineServiceById(serviceId), () => waitForMetadataCondition(host, () => isMetadataServiceRunning(host, serviceId))),
805
+ removeService: ({ serviceId }) => withMetadataServicePending(host, serviceId, "removing", () => host.removeOfflineService(serviceId), () => waitForMetadataCondition(host, () => isMetadataServiceRemoved(host, serviceId))),
806
+ resumeAgent: ({ sessionId }) => enqueueProjectServiceAgentResume(host, () => resumeAgentAndDirectTeammates(host, sessionId)),
465
807
  listGraveyard: () => host.listGraveyardEntries(),
466
808
  resurrectGraveyard: ({ sessionId }) => host.resurrectGraveyardSession(sessionId),
467
809
  },
@@ -472,14 +814,48 @@ export async function startProjectServices(host) {
472
814
  sendHandoff: (input) => host.sendHandoffMessage(input),
473
815
  },
474
816
  lifecycle: {
475
- spawnAgent: (input) => host.spawnAgent({
817
+ spawnAgent: (input) => withMetadataSessionPending(host, input.sessionId, "creating", () => host.spawnAgent({
476
818
  toolConfigKey: input.tool,
477
819
  targetSessionId: input.sessionId,
478
820
  targetWorktreePath: input.worktreePath,
479
821
  open: input.open ?? false,
480
822
  extraArgs: input.extraArgs,
481
- }),
482
- forkAgent: (input) => host.forkAgent({
823
+ }), input.sessionId
824
+ ? buildMetadataPendingSessionSeed({
825
+ sessionId: input.sessionId,
826
+ tool: input.tool,
827
+ worktreePath: input.worktreePath,
828
+ pendingAction: "creating",
829
+ })
830
+ : undefined, (result) => waitForMetadataSessionRunning(host, result.sessionId)),
831
+ createTeammateAgent: (input) => withMetadataSessionPending(host, input.sessionId, "creating", () => host.createTeammateAgent({
832
+ parentSessionId: input.parentSessionId,
833
+ role: input.role,
834
+ label: input.label,
835
+ toolConfigKey: input.tool,
836
+ targetSessionId: input.sessionId,
837
+ targetWorktreePath: input.worktreePath,
838
+ open: input.open ?? false,
839
+ extraArgs: input.extraArgs,
840
+ order: input.order,
841
+ }), input.sessionId
842
+ ? buildMetadataPendingSessionSeed({
843
+ sessionId: input.sessionId,
844
+ tool: input.tool,
845
+ worktreePath: input.worktreePath,
846
+ pendingAction: "creating",
847
+ team: {
848
+ teamId: `team-${input.parentSessionId}`,
849
+ parentSessionId: input.parentSessionId,
850
+ role: typeof input.role === "string" && input.role.trim() ? input.role.trim() : undefined,
851
+ label: typeof input.label === "string" && input.label.trim() ? input.label.trim() : undefined,
852
+ order: typeof input.order === "number" ? input.order : undefined,
853
+ },
854
+ })
855
+ : undefined, (result) => result?.reused && result.sessionId !== input.sessionId
856
+ ? true
857
+ : waitForMetadataSessionRunning(host, result.sessionId)),
858
+ forkAgent: (input) => withMetadataSessionPending(host, input.targetSessionId, "forking", () => host.forkAgent({
483
859
  sourceSessionId: input.sourceSessionId,
484
860
  targetToolConfigKey: input.tool,
485
861
  targetSessionId: input.targetSessionId,
@@ -487,15 +863,21 @@ export async function startProjectServices(host) {
487
863
  targetWorktreePath: input.worktreePath,
488
864
  open: input.open ?? false,
489
865
  extraArgs: input.extraArgs,
490
- }),
491
- stopAgent: (input) => host.stopAgent(input.sessionId),
866
+ }), input.targetSessionId
867
+ ? buildMetadataPendingSessionSeed({
868
+ sessionId: input.targetSessionId,
869
+ tool: input.tool,
870
+ worktreePath: input.worktreePath,
871
+ pendingAction: "forking",
872
+ })
873
+ : undefined, (result) => waitForMetadataSessionRunning(host, result.sessionId)),
874
+ stopAgent: (input) => withMetadataSessionPending(host, input.sessionId, "stopping", () => host.stopAgent(input.sessionId), findDashboardSessionSeed(host, input.sessionId)),
492
875
  interruptAgent: (input) => host.interruptAgent(input.sessionId),
493
876
  renameAgent: (input) => host.renameAgent(input.sessionId, input.label),
494
- migrateAgent: (input) => host.migrateAgent(input.sessionId, input.worktreePath),
495
- killAgent: (input) => host.sendAgentToGraveyard(input.sessionId),
496
- writeAgentInput: (input) => host.writeAgentInput(input.sessionId, input.data, input.parts, input.clientMessageId, input.submit),
877
+ migrateAgent: (input) => withMetadataSessionPending(host, input.sessionId, "migrating", () => host.migrateAgent(input.sessionId, input.worktreePath), findDashboardSessionSeed(host, input.sessionId)),
878
+ killAgent: (input) => withMetadataSessionPending(host, input.sessionId, "graveyarding", () => host.sendAgentToGraveyard(input.sessionId), findDashboardSessionSeed(host, input.sessionId)),
879
+ sendAgentInput: (input) => host.sendAgentInput(input.sessionId, input.text),
497
880
  readAgentOutput: (input) => host.readAgentOutput(input.sessionId, input.startLine),
498
- readAgentHistory: (input) => host.readAgentHistory(input.sessionId, input.lastN),
499
881
  },
500
882
  onChange: () => {
501
883
  scheduleProjectServiceUiRefresh(host);