aimux-cli 0.1.13 → 0.1.14

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 (212) hide show
  1. package/README.md +54 -0
  2. package/dist/agent-prompt-delivery.d.ts +31 -0
  3. package/dist/agent-prompt-delivery.js +119 -0
  4. package/dist/agent-prompt-delivery.js.map +1 -0
  5. package/dist/agent-tracker.js +4 -2
  6. package/dist/agent-tracker.js.map +1 -1
  7. package/dist/atomic-write.d.ts +1 -0
  8. package/dist/atomic-write.js +9 -0
  9. package/dist/atomic-write.js.map +1 -0
  10. package/dist/builtin-metadata-watchers.js +10 -6
  11. package/dist/builtin-metadata-watchers.js.map +1 -1
  12. package/dist/daemon.js +3 -6
  13. package/dist/daemon.js.map +1 -1
  14. package/dist/dashboard/index.d.ts +26 -15
  15. package/dist/dashboard/index.js +26 -56
  16. package/dist/dashboard/index.js.map +1 -1
  17. package/dist/dashboard/operation-failures.d.ts +30 -0
  18. package/dist/dashboard/operation-failures.js +86 -0
  19. package/dist/dashboard/operation-failures.js.map +1 -0
  20. package/dist/dashboard/pending-actions.d.ts +9 -2
  21. package/dist/dashboard/pending-actions.js +66 -7
  22. package/dist/dashboard/pending-actions.js.map +1 -1
  23. package/dist/dashboard/quick-jump.d.ts +3 -1
  24. package/dist/dashboard/quick-jump.js +27 -8
  25. package/dist/dashboard/quick-jump.js.map +1 -1
  26. package/dist/dashboard/runtime-evidence.d.ts +4 -0
  27. package/dist/dashboard/runtime-evidence.js +22 -0
  28. package/dist/dashboard/runtime-evidence.js.map +1 -0
  29. package/dist/dashboard/session-actions.d.ts +1 -1
  30. package/dist/dashboard/session-actions.js +3 -1
  31. package/dist/dashboard/session-actions.js.map +1 -1
  32. package/dist/dashboard/session-registry.d.ts +1 -0
  33. package/dist/dashboard/session-registry.js +47 -30
  34. package/dist/dashboard/session-registry.js.map +1 -1
  35. package/dist/dashboard/state.d.ts +9 -0
  36. package/dist/dashboard/state.js +33 -1
  37. package/dist/dashboard/state.js.map +1 -1
  38. package/dist/dashboard/targets.js +11 -3
  39. package/dist/dashboard/targets.js.map +1 -1
  40. package/dist/dashboard/ui-state-store.d.ts +3 -3
  41. package/dist/dashboard/ui-state-store.js +23 -14
  42. package/dist/dashboard/ui-state-store.js.map +1 -1
  43. package/dist/default-plugins/gh-pr-context.d.ts +8 -0
  44. package/dist/default-plugins/gh-pr-context.js +256 -0
  45. package/dist/default-plugins/gh-pr-context.js.map +1 -0
  46. package/dist/default-plugins/transcript-length.d.ts +1 -0
  47. package/dist/default-plugins/transcript-length.js +10 -0
  48. package/dist/default-plugins/transcript-length.js.map +1 -1
  49. package/dist/fast-control.d.ts +8 -3
  50. package/dist/fast-control.js +13 -14
  51. package/dist/fast-control.js.map +1 -1
  52. package/dist/main.js +22 -35
  53. package/dist/main.js.map +1 -1
  54. package/dist/managed-launch-env.d.ts +10 -0
  55. package/dist/managed-launch-env.js +51 -0
  56. package/dist/managed-launch-env.js.map +1 -0
  57. package/dist/metadata-server.d.ts +41 -0
  58. package/dist/metadata-server.js +272 -15
  59. package/dist/metadata-server.js.map +1 -1
  60. package/dist/metadata-store.d.ts +1 -0
  61. package/dist/metadata-store.js +3 -5
  62. package/dist/metadata-store.js.map +1 -1
  63. package/dist/multiplexer/agent-io-methods.d.ts +1 -1
  64. package/dist/multiplexer/agent-io-methods.js +36 -29
  65. package/dist/multiplexer/agent-io-methods.js.map +1 -1
  66. package/dist/multiplexer/archives.d.ts +1 -0
  67. package/dist/multiplexer/archives.js +143 -25
  68. package/dist/multiplexer/archives.js.map +1 -1
  69. package/dist/multiplexer/dashboard-actions-methods.d.ts +10 -1
  70. package/dist/multiplexer/dashboard-actions-methods.js +16 -4
  71. package/dist/multiplexer/dashboard-actions-methods.js.map +1 -1
  72. package/dist/multiplexer/dashboard-control.d.ts +13 -1
  73. package/dist/multiplexer/dashboard-control.js +150 -121
  74. package/dist/multiplexer/dashboard-control.js.map +1 -1
  75. package/dist/multiplexer/dashboard-interaction.d.ts +2 -1
  76. package/dist/multiplexer/dashboard-interaction.js +175 -113
  77. package/dist/multiplexer/dashboard-interaction.js.map +1 -1
  78. package/dist/multiplexer/dashboard-model.d.ts +5 -1
  79. package/dist/multiplexer/dashboard-model.js +83 -8
  80. package/dist/multiplexer/dashboard-model.js.map +1 -1
  81. package/dist/multiplexer/dashboard-ops.d.ts +32 -1
  82. package/dist/multiplexer/dashboard-ops.js +517 -15
  83. package/dist/multiplexer/dashboard-ops.js.map +1 -1
  84. package/dist/multiplexer/dashboard-state-methods.d.ts +7 -0
  85. package/dist/multiplexer/dashboard-state-methods.js +79 -4
  86. package/dist/multiplexer/dashboard-state-methods.js.map +1 -1
  87. package/dist/multiplexer/dashboard-tail-methods.d.ts +12 -2
  88. package/dist/multiplexer/dashboard-tail-methods.js +18 -6
  89. package/dist/multiplexer/dashboard-tail-methods.js.map +1 -1
  90. package/dist/multiplexer/dashboard-view-methods.d.ts +3 -1
  91. package/dist/multiplexer/dashboard-view-methods.js +53 -38
  92. package/dist/multiplexer/dashboard-view-methods.js.map +1 -1
  93. package/dist/multiplexer/graveyard-view-model.d.ts +68 -0
  94. package/dist/multiplexer/graveyard-view-model.js +185 -0
  95. package/dist/multiplexer/graveyard-view-model.js.map +1 -0
  96. package/dist/multiplexer/index.d.ts +18 -11
  97. package/dist/multiplexer/index.js +30 -18
  98. package/dist/multiplexer/index.js.map +1 -1
  99. package/dist/multiplexer/navigation.d.ts +1 -1
  100. package/dist/multiplexer/navigation.js +31 -11
  101. package/dist/multiplexer/navigation.js.map +1 -1
  102. package/dist/multiplexer/notifications.d.ts +1 -0
  103. package/dist/multiplexer/notifications.js +27 -19
  104. package/dist/multiplexer/notifications.js.map +1 -1
  105. package/dist/multiplexer/persistence-methods.d.ts +16 -0
  106. package/dist/multiplexer/persistence-methods.js +349 -28
  107. package/dist/multiplexer/persistence-methods.js.map +1 -1
  108. package/dist/multiplexer/runtime-lifecycle-methods.js +81 -12
  109. package/dist/multiplexer/runtime-lifecycle-methods.js.map +1 -1
  110. package/dist/multiplexer/runtime-state.d.ts +6 -2
  111. package/dist/multiplexer/runtime-state.js +144 -23
  112. package/dist/multiplexer/runtime-state.js.map +1 -1
  113. package/dist/multiplexer/service-state-snapshot.d.ts +14 -0
  114. package/dist/multiplexer/service-state-snapshot.js +121 -0
  115. package/dist/multiplexer/service-state-snapshot.js.map +1 -0
  116. package/dist/multiplexer/services.d.ts +19 -1
  117. package/dist/multiplexer/services.js +114 -16
  118. package/dist/multiplexer/services.js.map +1 -1
  119. package/dist/multiplexer/session-actions.d.ts +3 -0
  120. package/dist/multiplexer/session-actions.js +10 -8
  121. package/dist/multiplexer/session-actions.js.map +1 -1
  122. package/dist/multiplexer/session-launch.d.ts +1 -1
  123. package/dist/multiplexer/session-launch.js +63 -22
  124. package/dist/multiplexer/session-launch.js.map +1 -1
  125. package/dist/multiplexer/session-runtime-core.d.ts +2 -1
  126. package/dist/multiplexer/session-runtime-core.js +107 -69
  127. package/dist/multiplexer/session-runtime-core.js.map +1 -1
  128. package/dist/multiplexer/subscreens.d.ts +1 -0
  129. package/dist/multiplexer/subscreens.js +17 -12
  130. package/dist/multiplexer/subscreens.js.map +1 -1
  131. package/dist/multiplexer/tool-picker.d.ts +6 -2
  132. package/dist/multiplexer/tool-picker.js +231 -81
  133. package/dist/multiplexer/tool-picker.js.map +1 -1
  134. package/dist/multiplexer/worktree-graveyard.d.ts +13 -0
  135. package/dist/multiplexer/worktree-graveyard.js +21 -0
  136. package/dist/multiplexer/worktree-graveyard.js.map +1 -0
  137. package/dist/multiplexer/worktrees.d.ts +1 -0
  138. package/dist/multiplexer/worktrees.js +151 -69
  139. package/dist/multiplexer/worktrees.js.map +1 -1
  140. package/dist/notification-context.js +9 -15
  141. package/dist/notification-context.js.map +1 -1
  142. package/dist/notifications.d.ts +22 -0
  143. package/dist/notifications.js +66 -11
  144. package/dist/notifications.js.map +1 -1
  145. package/dist/orchestration-actions.js +3 -3
  146. package/dist/orchestration-actions.js.map +1 -1
  147. package/dist/orchestration-dispatcher.d.ts +4 -3
  148. package/dist/orchestration-dispatcher.js +9 -7
  149. package/dist/orchestration-dispatcher.js.map +1 -1
  150. package/dist/orchestration-routing.d.ts +2 -2
  151. package/dist/orchestration-routing.js +3 -7
  152. package/dist/orchestration-routing.js.map +1 -1
  153. package/dist/paths.d.ts +3 -0
  154. package/dist/paths.js +9 -0
  155. package/dist/paths.js.map +1 -1
  156. package/dist/plugin-runtime.d.ts +5 -0
  157. package/dist/plugin-runtime.js +98 -34
  158. package/dist/plugin-runtime.js.map +1 -1
  159. package/dist/project-events.js +4 -2
  160. package/dist/project-events.js.map +1 -1
  161. package/dist/session-bootstrap.d.ts +0 -3
  162. package/dist/session-bootstrap.js +12 -104
  163. package/dist/session-bootstrap.js.map +1 -1
  164. package/dist/session-semantics.d.ts +44 -7
  165. package/dist/session-semantics.js +164 -69
  166. package/dist/session-semantics.js.map +1 -1
  167. package/dist/shell-args.d.ts +1 -0
  168. package/dist/shell-args.js +57 -0
  169. package/dist/shell-args.js.map +1 -0
  170. package/dist/shell-hooks.d.ts +3 -0
  171. package/dist/shell-hooks.js +19 -2
  172. package/dist/shell-hooks.js.map +1 -1
  173. package/dist/statusline-model.d.ts +5 -1
  174. package/dist/statusline-model.js +18 -29
  175. package/dist/statusline-model.js.map +1 -1
  176. package/dist/task-dispatcher.d.ts +3 -3
  177. package/dist/task-dispatcher.js +6 -5
  178. package/dist/task-dispatcher.js.map +1 -1
  179. package/dist/task-workflow.d.ts +2 -0
  180. package/dist/task-workflow.js +15 -6
  181. package/dist/task-workflow.js.map +1 -1
  182. package/dist/tasks.d.ts +4 -1
  183. package/dist/tasks.js +19 -1
  184. package/dist/tasks.js.map +1 -1
  185. package/dist/tmux/doctor.d.ts +1 -0
  186. package/dist/tmux/doctor.js +4 -1
  187. package/dist/tmux/doctor.js.map +1 -1
  188. package/dist/tmux/runtime-manager.d.ts +6 -0
  189. package/dist/tmux/runtime-manager.js +56 -18
  190. package/dist/tmux/runtime-manager.js.map +1 -1
  191. package/dist/tmux/statusline.js +17 -16
  192. package/dist/tmux/statusline.js.map +1 -1
  193. package/dist/tmux/window-open.d.ts +1 -0
  194. package/dist/tmux/window-open.js +10 -10
  195. package/dist/tmux/window-open.js.map +1 -1
  196. package/dist/tool-output-watchers.d.ts +2 -0
  197. package/dist/tool-output-watchers.js +123 -12
  198. package/dist/tool-output-watchers.js.map +1 -1
  199. package/dist/tui/screens/dashboard-renderers.d.ts +2 -21
  200. package/dist/tui/screens/dashboard-renderers.js +126 -32
  201. package/dist/tui/screens/dashboard-renderers.js.map +1 -1
  202. package/dist/tui/screens/overlay-renderers.d.ts +11 -1
  203. package/dist/tui/screens/overlay-renderers.js +68 -28
  204. package/dist/tui/screens/overlay-renderers.js.map +1 -1
  205. package/dist/tui/screens/subscreen-renderers.js +156 -33
  206. package/dist/tui/screens/subscreen-renderers.js.map +1 -1
  207. package/dist/workflow.js +2 -2
  208. package/dist/workflow.js.map +1 -1
  209. package/dist/worktree.d.ts +3 -0
  210. package/dist/worktree.js +27 -2
  211. package/dist/worktree.js.map +1 -1
  212. package/package.json +5 -1
@@ -1,7 +1,8 @@
1
1
  import { createServer } from "node:http";
2
2
  import { createHash } from "node:crypto";
3
3
  import { readFileSync, writeFileSync } from "node:fs";
4
- import { getDashboardUiStatePath, getProjectId, getProjectStateDir } from "./paths.js";
4
+ import { basename } from "node:path";
5
+ import { getDashboardClientUiStatePath, getProjectId, getProjectStateDir } from "./paths.js";
5
6
  import { updateSessionMetadata, clearSessionLogs, saveMetadataEndpoint, loadMetadataState, } from "./metadata-store.js";
6
7
  import { notifyAlert } from "./notify.js";
7
8
  import { clearNotifications, listNotifications, markNotificationsRead, unreadNotificationCount, } from "./notifications.js";
@@ -21,17 +22,129 @@ import { listSwitchableAgentItems, resolveAttentionAgent, resolveNextAgent, reso
21
22
  import { TmuxRuntimeManager } from "./tmux/runtime-manager.js";
22
23
  import { openTargetForClient } from "./tmux/window-open.js";
23
24
  import { getDashboardCommandSpec } from "./dashboard/command-spec.js";
24
- function persistDashboardScreenPreference(screen) {
25
+ function compactSessionId(sessionId) {
26
+ const compact = sessionId.replace(/-[a-z0-9]{4,}$/i, "");
27
+ return compact || sessionId;
28
+ }
29
+ function metadataDisplayContext(metadata) {
30
+ return {
31
+ label: metadata?.label,
32
+ worktreePath: metadata?.context?.worktreePath,
33
+ worktreeName: metadata?.context?.worktreeName,
34
+ branch: metadata?.context?.branch,
35
+ };
36
+ }
37
+ function mergeDisplayContext(base, override) {
38
+ return {
39
+ label: override.label ?? base.label,
40
+ command: override.command ?? base.command,
41
+ worktreePath: override.worktreePath ?? base.worktreePath,
42
+ worktreeName: override.worktreeName ?? base.worktreeName,
43
+ branch: override.branch ?? base.branch,
44
+ };
45
+ }
46
+ function displayWorktreeLabel(context) {
47
+ const worktreeName = context.worktreeName?.trim();
48
+ const branch = context.branch?.trim();
49
+ if (worktreeName)
50
+ return worktreeName;
51
+ if (branch)
52
+ return branch;
53
+ const path = context.worktreePath?.trim();
54
+ return path ? basename(path) : undefined;
55
+ }
56
+ function sessionAlertSubject(sessionId, context) {
57
+ if (!sessionId)
58
+ return undefined;
59
+ const label = context?.label?.trim() || context?.command?.trim() || compactSessionId(sessionId);
60
+ const worktree = context ? displayWorktreeLabel(context) : undefined;
61
+ return worktree ? `${label} @ ${worktree}` : label;
62
+ }
63
+ function sessionAlertTitle(kind, sessionId, fallback, context) {
64
+ const title = fallback?.trim();
65
+ const subject = sessionAlertSubject(sessionId, context);
66
+ if (!subject)
67
+ return title || "aimux";
68
+ if (kind === "needs_input")
69
+ return `${subject} needs input`;
70
+ if (kind === "blocked") {
71
+ if (!title || (sessionId && title === `${sessionId} is blocked`))
72
+ return `${subject} is blocked`;
73
+ return title;
74
+ }
75
+ if (kind === "task_failed") {
76
+ if (!title || (sessionId && title === `${sessionId} errored`))
77
+ return `${subject} errored`;
78
+ return title;
79
+ }
80
+ if (kind === "task_done") {
81
+ if (!title || (sessionId && title === `${sessionId} finished`))
82
+ return `${subject} finished`;
83
+ return title;
84
+ }
85
+ if (!title)
86
+ return subject;
87
+ if (title.includes(subject))
88
+ return title;
89
+ if (sessionId && title.includes(sessionId))
90
+ return title.replace(sessionId, subject);
91
+ return `${subject}: ${title}`;
92
+ }
93
+ function dashboardClientKeyFromSession(sessionName) {
94
+ return sessionName.replace(/[^a-zA-Z0-9._-]/g, "_");
95
+ }
96
+ function persistDashboardClientPreference(clientSession, update) {
97
+ const path = getDashboardClientUiStatePath(dashboardClientKeyFromSession(clientSession));
98
+ let snapshot = {};
25
99
  try {
26
- const path = getDashboardUiStatePath();
27
- const raw = readFileSync(path, "utf-8");
28
- const snapshot = JSON.parse(raw);
29
- snapshot.screen = screen;
30
- writeFileSync(path, JSON.stringify(snapshot, null, 2) + "\n");
100
+ snapshot = JSON.parse(readFileSync(path, "utf-8"));
31
101
  }
32
- catch {
33
- writeFileSync(getDashboardUiStatePath(), JSON.stringify({ screen }, null, 2) + "\n");
102
+ catch { }
103
+ update(snapshot);
104
+ writeFileSync(path, JSON.stringify(snapshot, null, 2) + "\n");
105
+ }
106
+ function persistDashboardReturnSelection(tmux, projectRoot, currentClientSession, currentWindowId) {
107
+ persistDashboardClientPreference(currentClientSession, (snapshot) => {
108
+ snapshot.screen = "dashboard";
109
+ if (!currentWindowId)
110
+ return;
111
+ const match = tmux
112
+ .listProjectManagedWindows(projectRoot)
113
+ .find((entry) => entry.target.windowId === currentWindowId);
114
+ if (!match)
115
+ return;
116
+ snapshot.focusedWorktreePath = match.metadata.worktreePath;
117
+ snapshot.level = "sessions";
118
+ snapshot.selectedEntryKind = match.metadata.kind === "service" ? "service" : "session";
119
+ snapshot.selectedEntryId = match.metadata.sessionId;
120
+ });
121
+ }
122
+ function markActiveWindowFocused(tmux, projectRoot, currentClientSession, currentWindow, currentWindowId, tracker) {
123
+ if (currentWindow && /^dashboard/.test(currentWindow)) {
124
+ updateNotificationContext("tui", {
125
+ focused: true,
126
+ screen: "dashboard",
127
+ panelOpen: false,
128
+ sessionId: undefined,
129
+ });
130
+ return true;
34
131
  }
132
+ if (!currentWindowId)
133
+ return false;
134
+ const match = tmux.listProjectManagedWindows(projectRoot).find((entry) => entry.target.windowId === currentWindowId);
135
+ if (!match)
136
+ return false;
137
+ updateNotificationContext("tui", {
138
+ focused: true,
139
+ sessionId: match.metadata.sessionId,
140
+ panelOpen: false,
141
+ });
142
+ if (match.metadata.kind === "agent") {
143
+ tracker.markSeen(match.metadata.sessionId);
144
+ markNotificationsRead({ sessionId: match.metadata.sessionId });
145
+ }
146
+ markTargetUsed(tmux, projectRoot, match.target, currentClientSession, match.metadata.sessionId);
147
+ return true;
35
148
  }
36
149
  function markTargetUsed(tmux, projectRoot, target, currentClientSession, itemId) {
37
150
  const resolvedItemId = itemId ||
@@ -144,7 +257,26 @@ export class MetadataServer {
144
257
  });
145
258
  }
146
259
  emitAlert(input) {
147
- this.eventBus.publishAlert(input);
260
+ const displayContext = this.resolveSessionAlertDisplayContext(input.sessionId, input.worktreePath);
261
+ this.eventBus.publishAlert({
262
+ ...input,
263
+ title: sessionAlertTitle(input.kind, input.sessionId, input.title, displayContext),
264
+ worktreePath: input.worktreePath ?? displayContext?.worktreePath,
265
+ });
266
+ }
267
+ resolveSessionAlertDisplayContext(sessionId, worktreePath) {
268
+ if (!sessionId)
269
+ return worktreePath ? { worktreePath } : undefined;
270
+ let context = {};
271
+ try {
272
+ context = metadataDisplayContext(loadMetadataState().sessions[sessionId]);
273
+ }
274
+ catch { }
275
+ const liveContext = this.options.desktop?.getSessionDisplayContext?.(sessionId);
276
+ context = mergeDisplayContext(context, liveContext ?? {});
277
+ if (worktreePath)
278
+ context.worktreePath = worktreePath;
279
+ return Object.values(context).some((value) => value !== undefined) ? context : undefined;
148
280
  }
149
281
  emitThreadWaitingAlert(input) {
150
282
  for (const recipient of [...new Set((input.recipients ?? []).map((value) => value?.trim()).filter(Boolean))]) {
@@ -371,7 +503,11 @@ export class MetadataServer {
371
503
  send(res, 501, { ok: false, error: "graveyard listing not supported by this service" });
372
504
  return;
373
505
  }
374
- send(res, 200, { ok: true, entries: this.options.desktop.listGraveyard() });
506
+ send(res, 200, {
507
+ ok: true,
508
+ entries: this.options.desktop.listGraveyard(),
509
+ worktrees: this.options.desktop.listWorktreeGraveyard?.() ?? [],
510
+ });
375
511
  return;
376
512
  }
377
513
  if (req.method === "GET" && url.pathname === "/threads") {
@@ -525,10 +661,12 @@ export class MetadataServer {
525
661
  : {};
526
662
  const currentClientSession = body.currentClientSession?.trim() || url.searchParams.get("currentClientSession")?.trim() || undefined;
527
663
  const clientTty = body.clientTty?.trim() || url.searchParams.get("clientTty")?.trim() || undefined;
664
+ const currentWindowId = body.currentWindowId?.trim() || url.searchParams.get("currentWindowId")?.trim() || undefined;
528
665
  if (!currentClientSession) {
529
666
  send(res, 400, { ok: false, error: "currentClientSession is required" });
530
667
  return;
531
668
  }
669
+ persistDashboardReturnSelection(new TmuxRuntimeManager(), process.cwd(), currentClientSession, currentWindowId);
532
670
  const tmux = new TmuxRuntimeManager();
533
671
  const { dashboardCommand, dashboardBuildStamp } = getDashboardCommandSpec(process.cwd());
534
672
  const dashboardSession = tmux.ensureProjectSession(process.cwd(), dashboardCommand);
@@ -555,7 +693,9 @@ export class MetadataServer {
555
693
  send(res, 400, { ok: false, error: "currentClientSession is required" });
556
694
  return;
557
695
  }
558
- persistDashboardScreenPreference("notifications");
696
+ persistDashboardClientPreference(currentClientSession, (snapshot) => {
697
+ snapshot.screen = "notifications";
698
+ });
559
699
  const tmux = new TmuxRuntimeManager();
560
700
  const { dashboardCommand, dashboardBuildStamp } = getDashboardCommandSpec(process.cwd());
561
701
  const dashboardSession = tmux.ensureProjectSession(process.cwd(), dashboardCommand);
@@ -674,6 +814,23 @@ export class MetadataServer {
674
814
  send(res, 200, { ok: true });
675
815
  return;
676
816
  }
817
+ if ((req.method === "GET" || req.method === "POST") && url.pathname === "/control/active-window") {
818
+ const body = req.method === "POST"
819
+ ? (await readJson(req))
820
+ : {};
821
+ const currentClientSession = body.currentClientSession?.trim() || url.searchParams.get("currentClientSession")?.trim() || undefined;
822
+ const currentWindow = body.currentWindow?.trim() || url.searchParams.get("currentWindow")?.trim() || undefined;
823
+ const currentWindowId = body.currentWindowId?.trim() || url.searchParams.get("currentWindowId")?.trim() || undefined;
824
+ const tmux = new TmuxRuntimeManager();
825
+ const ok = markActiveWindowFocused(tmux, process.cwd(), currentClientSession, currentWindow, currentWindowId, this.tracker);
826
+ if (!ok) {
827
+ send(res, 404, { ok: false, error: "window not found" });
828
+ return;
829
+ }
830
+ this.options.onChange?.();
831
+ send(res, 200, { ok: true });
832
+ return;
833
+ }
677
834
  if ((req.method === "GET" || req.method === "POST") && url.pathname === "/control/switch-next") {
678
835
  const body = req.method === "POST"
679
836
  ? (await readJson(req))
@@ -798,6 +955,46 @@ export class MetadataServer {
798
955
  if (req.method === "POST" && url.pathname === "/event") {
799
956
  const body = (await readJson(req));
800
957
  this.tracker.emit(body.session, body.event);
958
+ if (body.event.kind === "needs_input") {
959
+ this.emitAlert({
960
+ kind: "needs_input",
961
+ sessionId: body.session,
962
+ title: `${body.session} needs input`,
963
+ message: body.event.message || "Agent is waiting for input.",
964
+ dedupeKey: `needs_input:${body.session}`,
965
+ cooldownMs: 15_000,
966
+ });
967
+ }
968
+ else if (body.event.kind === "blocked") {
969
+ this.emitAlert({
970
+ kind: "blocked",
971
+ sessionId: body.session,
972
+ title: `${body.session} is blocked`,
973
+ message: body.event.message || "Agent reported a blocked state.",
974
+ dedupeKey: `blocked:${body.session}`,
975
+ cooldownMs: 15_000,
976
+ });
977
+ }
978
+ else if (body.event.kind === "task_failed" || body.event.tone === "error") {
979
+ this.emitAlert({
980
+ kind: "task_failed",
981
+ sessionId: body.session,
982
+ title: `${body.session} errored`,
983
+ message: body.event.message || "Agent reported an error state.",
984
+ dedupeKey: `error:${body.session}`,
985
+ cooldownMs: 15_000,
986
+ });
987
+ }
988
+ else if (body.event.kind === "notify") {
989
+ this.emitAlert({
990
+ kind: "notification",
991
+ sessionId: body.session,
992
+ title: sessionAlertTitle("notification", body.session, body.event.source),
993
+ message: body.event.message || "Agent notification.",
994
+ dedupeKey: body.event.message ? `notify:${body.session}:${body.event.message}` : undefined,
995
+ cooldownMs: 15_000,
996
+ });
997
+ }
801
998
  this.options.onChange?.();
802
999
  send(res, 200, { ok: true });
803
1000
  return;
@@ -880,14 +1077,26 @@ export class MetadataServer {
880
1077
  : requestedKind === "review_waiting"
881
1078
  ? "review_waiting"
882
1079
  : "needs_input";
1080
+ const sessionId = body.sessionId?.trim() || undefined;
1081
+ const dedupeKey = body.force
1082
+ ? undefined
1083
+ : kind === "needs_input" && sessionId
1084
+ ? `needs_input:${sessionId}`
1085
+ : kind === "blocked" && sessionId
1086
+ ? `blocked:${sessionId}`
1087
+ : kind === "task_failed" && sessionId
1088
+ ? `error:${sessionId}`
1089
+ : kind === "task_done"
1090
+ ? `notify:complete:${body.title ?? body.message ?? "aimux"}`
1091
+ : undefined;
883
1092
  this.emitAlert({
884
1093
  kind,
885
- sessionId: body.sessionId?.trim() || undefined,
886
- title: body.title?.trim() || "aimux",
1094
+ sessionId,
1095
+ title: sessionAlertTitle(kind, sessionId, body.title),
887
1096
  message: [body.subtitle?.trim(), body.message?.trim() || body.title?.trim() || "aimux"]
888
1097
  .filter(Boolean)
889
1098
  .join(" — "),
890
- dedupeKey: kind === "task_done" ? `notify:complete:${body.title ?? body.message ?? "aimux"}` : undefined,
1099
+ dedupeKey,
891
1100
  forceNotify: Boolean(body.force),
892
1101
  });
893
1102
  send(res, 200, { ok: true });
@@ -1186,6 +1395,17 @@ export class MetadataServer {
1186
1395
  send(res, 200, { ok: true, ...result });
1187
1396
  return;
1188
1397
  }
1398
+ if (req.method === "POST" && url.pathname === "/agents/resume") {
1399
+ const body = (await readJson(req));
1400
+ if (!this.options.desktop?.resumeAgent) {
1401
+ send(res, 501, { ok: false, error: "agent resume not supported by this service" });
1402
+ return;
1403
+ }
1404
+ const result = await this.options.desktop.resumeAgent(body);
1405
+ this.options.onChange?.();
1406
+ send(res, 200, { ok: true, ...result });
1407
+ return;
1408
+ }
1189
1409
  if (req.method === "POST" && url.pathname === "/agents/interrupt") {
1190
1410
  const body = (await readJson(req));
1191
1411
  if (!this.options.lifecycle?.interruptAgent) {
@@ -1204,6 +1424,10 @@ export class MetadataServer {
1204
1424
  return;
1205
1425
  }
1206
1426
  const result = await this.options.lifecycle.renameAgent(body);
1427
+ updateSessionMetadata(body.sessionId, (current) => ({
1428
+ ...current,
1429
+ label: body.label?.trim() || undefined,
1430
+ }));
1207
1431
  this.options.onChange?.();
1208
1432
  send(res, 200, { ok: true, ...result });
1209
1433
  return;
@@ -1382,6 +1606,17 @@ export class MetadataServer {
1382
1606
  send(res, 202, { ok: true, path: body.path, status: "removing" });
1383
1607
  return;
1384
1608
  }
1609
+ if (req.method === "POST" && url.pathname === "/worktrees/graveyard") {
1610
+ const body = (await readJson(req));
1611
+ if (!this.options.desktop?.graveyardWorktree) {
1612
+ send(res, 501, { ok: false, error: "worktree graveyard not supported by this service" });
1613
+ return;
1614
+ }
1615
+ const result = await this.options.desktop.graveyardWorktree(body);
1616
+ this.options.onChange?.();
1617
+ send(res, 200, { ok: true, ...result });
1618
+ return;
1619
+ }
1385
1620
  if (req.method === "POST" && url.pathname === "/services/create") {
1386
1621
  const body = (await readJson(req));
1387
1622
  if (!this.options.desktop?.createService) {
@@ -1437,6 +1672,28 @@ export class MetadataServer {
1437
1672
  send(res, 200, { ok: true, ...result });
1438
1673
  return;
1439
1674
  }
1675
+ if (req.method === "POST" && url.pathname === "/graveyard/worktrees/resurrect") {
1676
+ const body = (await readJson(req));
1677
+ if (!this.options.desktop?.resurrectGraveyardWorktree) {
1678
+ send(res, 501, { ok: false, error: "worktree graveyard resurrect not supported by this service" });
1679
+ return;
1680
+ }
1681
+ const result = await this.options.desktop.resurrectGraveyardWorktree(body);
1682
+ this.options.onChange?.();
1683
+ send(res, 200, { ok: true, ...result });
1684
+ return;
1685
+ }
1686
+ if (req.method === "POST" && url.pathname === "/graveyard/worktrees/delete") {
1687
+ const body = (await readJson(req));
1688
+ if (!this.options.desktop?.deleteGraveyardWorktree) {
1689
+ send(res, 501, { ok: false, error: "worktree graveyard delete not supported by this service" });
1690
+ return;
1691
+ }
1692
+ const result = await this.options.desktop.deleteGraveyardWorktree(body);
1693
+ this.options.onChange?.();
1694
+ send(res, 200, { ok: true, ...result });
1695
+ return;
1696
+ }
1440
1697
  if (req.method === "POST" && url.pathname === "/reviews/approve") {
1441
1698
  const body = (await readJson(req));
1442
1699
  const result = this.options.actions?.approveReview