opencode-subagent-magazine 1.4.2 → 1.5.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.
package/dist/tui.js CHANGED
@@ -11,7 +11,103 @@ import { createElement as _$createElement } from "@opentui/solid";
11
11
  import { createMemo, createSignal, createEffect, onMount, onCleanup, untrack, Show, For } from "solid-js";
12
12
 
13
13
  // src/_version.ts
14
- var PLUGIN_VERSION = "1.4.2";
14
+ var PLUGIN_VERSION = "1.5.0";
15
+
16
+ // src/clipboard.ts
17
+ import { spawn } from "node:child_process";
18
+ import { platform, release } from "node:os";
19
+ function runWithInput(command, args, input) {
20
+ return new Promise((resolve, reject) => {
21
+ const child = spawn(command, args, {
22
+ stdio: ["pipe", "ignore", "ignore"],
23
+ windowsHide: true
24
+ });
25
+ child.once("error", reject);
26
+ child.once("close", (code) => {
27
+ if (code === 0) resolve();
28
+ else reject(new Error(`${command} exited with code ${code}`));
29
+ });
30
+ child.stdin?.end(input);
31
+ });
32
+ }
33
+ function writeOsc52(text) {
34
+ if (!process.stdout.isTTY) return false;
35
+ const payload = Buffer.from(text, "utf8").toString("base64");
36
+ const sequence = `\x1B]52;c;${payload}\x07`;
37
+ const wrapped = process.env.TMUX || process.env.STY ? `\x1BPtmux;\x1B${sequence}\x1B\\` : sequence;
38
+ process.stdout.write(wrapped);
39
+ return true;
40
+ }
41
+ async function tryCommand(method, command, args, text) {
42
+ try {
43
+ await runWithInput(command, args, text);
44
+ return { copied: true, method };
45
+ } catch {
46
+ return void 0;
47
+ }
48
+ }
49
+ async function copyText(text) {
50
+ if (!text) return { copied: false, method: "none", error: "empty_text" };
51
+ const os = platform();
52
+ const isWsl = release().toLowerCase().includes("microsoft");
53
+ const attempts = [];
54
+ if (os === "darwin") {
55
+ attempts.push(() => tryCommand("pbcopy", "pbcopy", [], text));
56
+ attempts.push(
57
+ () => tryCommand(
58
+ "osascript",
59
+ "osascript",
60
+ ["-e", 'set the clipboard to (read (POSIX file "/dev/stdin") as text)'],
61
+ text
62
+ )
63
+ );
64
+ }
65
+ if (os === "linux" && isWsl) {
66
+ attempts.push(
67
+ () => tryCommand(
68
+ "powershell",
69
+ "powershell.exe",
70
+ [
71
+ "-NonInteractive",
72
+ "-NoProfile",
73
+ "-Command",
74
+ "[Console]::InputEncoding=[Text.Encoding]::UTF8; Set-Clipboard ([Console]::In.ReadToEnd())"
75
+ ],
76
+ text
77
+ )
78
+ );
79
+ } else if (os === "linux") {
80
+ if (process.env.WAYLAND_DISPLAY) {
81
+ attempts.push(() => tryCommand("wl-copy", "wl-copy", [], text));
82
+ }
83
+ attempts.push(() => tryCommand("xclip", "xclip", ["-selection", "clipboard"], text));
84
+ attempts.push(() => tryCommand("xsel", "xsel", ["--clipboard", "--input"], text));
85
+ }
86
+ if (os === "win32") {
87
+ attempts.push(
88
+ () => tryCommand(
89
+ "powershell",
90
+ "powershell.exe",
91
+ [
92
+ "-NonInteractive",
93
+ "-NoProfile",
94
+ "-Command",
95
+ "[Console]::InputEncoding=[Text.Encoding]::UTF8; Set-Clipboard ([Console]::In.ReadToEnd())"
96
+ ],
97
+ text
98
+ )
99
+ );
100
+ }
101
+ for (const attempt of attempts) {
102
+ const result = await attempt();
103
+ if (result) return result;
104
+ }
105
+ try {
106
+ if (writeOsc52(text)) return { copied: true, method: "osc52" };
107
+ } catch {
108
+ }
109
+ return { copied: false, method: "none", error: "clipboard_unavailable" };
110
+ }
15
111
 
16
112
  // src/index.tsx
17
113
  var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["task", "delegate", "call_omo_agent"]);
@@ -28,14 +124,18 @@ var I18N = {
28
124
  "todo.label": "\u8FDB\u5EA6",
29
125
  "session.label": "\u4F1A\u8BDD ID",
30
126
  "session.toast.copy": "\u53EF\u624B\u52A8\u590D\u5236\u4E0A\u65B9 ID",
127
+ "session.toast.copied": "\u4F1A\u8BDD ID \u5DF2\u590D\u5236",
128
+ "session.toast.copy_failed": "\u65E0\u6CD5\u8BBF\u95EE\u7CFB\u7EDF\u526A\u8D34\u677F\uFF0C\u8BF7\u624B\u52A8\u590D\u5236\u4E0A\u65B9 ID",
31
129
  "open.label": "\u8FDB\u5165\u4F1A\u8BDD",
32
130
  "cost.label": "\u8D39\u7528",
33
131
  "scroll.more": "\u66F4\u591A",
34
132
  "scroll.top": "\u56DE\u9876",
35
133
  "scroll.bottom": "\u56DE\u5E95",
36
134
  "dismiss.label": "\u6807\u8BB0\u5B8C\u6210",
135
+ "cancel.label": "\u53D6\u6D88",
37
136
  "status.running": "\u8FD0\u884C\u4E2D",
38
137
  "status.done": "\u5DF2\u5B8C\u6210",
138
+ "status.cancelled": "\u5DF2\u53D6\u6D88",
39
139
  "status.error": "\u9519\u8BEF",
40
140
  "order.desc": "\u964D\u5E8F\uFF08\u6700\u65B0\u5728\u524D\uFF09",
41
141
  "order.asc": "\u5347\u5E8F\uFF08\u6700\u65E9\u5728\u524D\uFF09",
@@ -53,7 +153,15 @@ var I18N = {
53
153
  "clear.prompt": "\u786E\u5B9A\u6E05\u9664\u5F53\u524D\u4F1A\u8BDD\u6240\u6709\u5B50\u4EE3\u7406\u8BB0\u5F55\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500\u3002",
54
154
  "clear.prompt_running": "\u5F53\u524D\u6709 {n} \u4E2A\u8FD0\u884C\u4E2D\u7684\u5B50\u4EE3\u7406\uFF0C\u6E05\u9664\u540E\u5C06\u4E0D\u53EF\u6062\u590D\u3002\u786E\u5B9A\u7EE7\u7EED\uFF1F",
55
155
  "clear.done": "\u5DF2\u6E05\u9664 {n} \u6761\u5B50\u4EE3\u7406\u8BB0\u5F55",
56
- "clear.empty": "\u5F53\u524D\u4F1A\u8BDD\u65E0\u5B50\u4EE3\u7406\u8BB0\u5F55"
156
+ "clear.empty": "\u5F53\u524D\u4F1A\u8BDD\u65E0\u5B50\u4EE3\u7406\u8BB0\u5F55",
157
+ "cancel.no_session": "\u5B50\u4F1A\u8BDD ID \u4E0D\u53EF\u7528",
158
+ "cancel.not_child": "\u76EE\u6807\u4E0D\u662F\u5B50\u4F1A\u8BDD",
159
+ "cancel.read_error": "\u65E0\u6CD5\u8BFB\u53D6\u4F1A\u8BDD\u4FE1\u606F",
160
+ "cancel.outside_tree": "\u76EE\u6807\u4E0D\u5728\u5F53\u524D\u76D1\u63A7\u4F1A\u8BDD\u6811\u4E2D",
161
+ "cancel.already_ended": "\u4F1A\u8BDD\u5DF2\u7ED3\u675F\uFF0C\u65E0\u9700\u53D6\u6D88",
162
+ "cancel.status_error": "\u65E0\u6CD5\u67E5\u8BE2\u4F1A\u8BDD\u72B6\u6001",
163
+ "cancel.sent": "\u5DF2\u53D1\u9001\u53D6\u6D88\u6307\u4EE4",
164
+ "cancel.failed": "\u53D6\u6D88\u5931\u8D25"
57
165
  },
58
166
  en: {
59
167
  "panel.title": "SubAgent",
@@ -67,14 +175,18 @@ var I18N = {
67
175
  "todo.label": "todo",
68
176
  "session.label": "session ID",
69
177
  "session.toast.copy": "Copy the ID above manually",
178
+ "session.toast.copied": "Session ID copied",
179
+ "session.toast.copy_failed": "Cannot access the system clipboard; copy the ID above manually",
70
180
  "open.label": "Open session",
71
181
  "cost.label": "cost",
72
182
  "scroll.more": "more",
73
183
  "scroll.top": "Top",
74
184
  "scroll.bottom": "Bottom",
75
185
  "dismiss.label": "dismiss",
186
+ "cancel.label": "Cancel",
76
187
  "status.running": "running",
77
188
  "status.done": "done",
189
+ "status.cancelled": "cancelled",
78
190
  "status.error": "error",
79
191
  "order.desc": "Desc (newest first)",
80
192
  "order.asc": "Asc (oldest first)",
@@ -92,7 +204,15 @@ var I18N = {
92
204
  "clear.prompt": "Clear all sub-agent records for this session? This cannot be undone.",
93
205
  "clear.prompt_running": "{n} sub-agent(s) are still running. Clearing will discard them permanently. Continue?",
94
206
  "clear.done": "Cleared {n} sub-agent record(s)",
95
- "clear.empty": "No sub-agent records in this session"
207
+ "clear.empty": "No sub-agent records in this session",
208
+ "cancel.no_session": "Child session ID is unavailable",
209
+ "cancel.not_child": "Target is not a child session",
210
+ "cancel.read_error": "Cannot read session info",
211
+ "cancel.outside_tree": "Target is outside the monitored session tree",
212
+ "cancel.already_ended": "Session already ended, no need to cancel",
213
+ "cancel.status_error": "Cannot query session status",
214
+ "cancel.sent": "Cancel instruction sent",
215
+ "cancel.failed": "Cancellation failed"
96
216
  }
97
217
  };
98
218
  function detectLang() {
@@ -410,7 +530,7 @@ function SubAgentPanel(props) {
410
530
  let needsImmediateFlush = false;
411
531
  for (const [id, entry] of next) {
412
532
  const prevEntry = prev.get(id);
413
- if (prevEntry?.status === "running" && (entry.status === "done" || entry.status === "error")) {
533
+ if (prevEntry?.status === "running" && (entry.status === "done" || entry.status === "error" || entry.status === "cancelled")) {
414
534
  needsImmediateFlush = true;
415
535
  break;
416
536
  }
@@ -481,6 +601,7 @@ function SubAgentPanel(props) {
481
601
  })());
482
602
  const [hoveredOpen, setHoveredOpen] = createSignal(void 0);
483
603
  const [hoveredDismiss, setHoveredDismiss] = createSignal(void 0);
604
+ const [hoveredCancel, setHoveredCancel] = createSignal(void 0);
484
605
  const [hoveredTop, setHoveredTop] = createSignal(false);
485
606
  const [hoveredMoreAbove, setHoveredMoreAbove] = createSignal(false);
486
607
  const [hoveredMoreBelow, setHoveredMoreBelow] = createSignal(false);
@@ -574,7 +695,7 @@ function SubAgentPanel(props) {
574
695
  const next = new Map(prev);
575
696
  const nowTs = Date.now();
576
697
  const e = partial.status;
577
- const ended = e === "done" || e === "error";
698
+ const ended = e === "done" || e === "error" || e === "cancelled";
578
699
  next.set(partial.id, {
579
700
  ...existing ?? {
580
701
  startedAt: nowTs
@@ -586,6 +707,123 @@ function SubAgentPanel(props) {
586
707
  return next;
587
708
  });
588
709
  };
710
+ const isDescendantOf = (childId, rootId) => {
711
+ const visited = /* @__PURE__ */ new Set();
712
+ try {
713
+ let current = props.api.state.session.get(childId);
714
+ while (current?.parentID) {
715
+ if (visited.has(current.id)) return false;
716
+ visited.add(current.id);
717
+ if (current.parentID === rootId) return true;
718
+ current = props.api.state.session.get(current.parentID);
719
+ }
720
+ } catch {
721
+ }
722
+ return false;
723
+ };
724
+ const settleOnIdle = (entry) => {
725
+ if (entry.status === "cancel_requested" && entry.abortAccepted) return "cancelled";
726
+ return "done";
727
+ };
728
+ const cancelEntry = async (entry) => {
729
+ const childId = entry.sessionId;
730
+ if (!childId) {
731
+ props.api.ui.toast({
732
+ title: entry.title || entry.agent,
733
+ message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.no_session"] ?? "Child session ID is unavailable")
734
+ });
735
+ return;
736
+ }
737
+ try {
738
+ const child = props.api.state.session.get(childId);
739
+ if (!child?.parentID) {
740
+ props.api.ui.toast({
741
+ title: entry.title || entry.agent,
742
+ message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.not_child"] ?? "Target is not a child session")
743
+ });
744
+ return;
745
+ }
746
+ } catch {
747
+ props.api.ui.toast({
748
+ title: entry.title || entry.agent,
749
+ message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.read_error"] ?? "Cannot read session info")
750
+ });
751
+ return;
752
+ }
753
+ if (!isDescendantOf(childId, props.sessionId)) {
754
+ props.api.ui.toast({
755
+ title: entry.title || entry.agent,
756
+ message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.outside_tree"] ?? "Target is outside the monitored session tree")
757
+ });
758
+ return;
759
+ }
760
+ try {
761
+ const st = props.api.state.session.status(childId);
762
+ if (st?.type !== "busy") {
763
+ const tokens = readSessionTokens(childId);
764
+ const cost = readSessionCost(childId);
765
+ upsertEntry({
766
+ id: entry.id,
767
+ title: entry.title,
768
+ agent: entry.agent,
769
+ prompt: entry.prompt,
770
+ status: "done",
771
+ sessionId: entry.sessionId,
772
+ tokens,
773
+ cost
774
+ });
775
+ return;
776
+ }
777
+ } catch {
778
+ props.api.ui.toast({
779
+ title: entry.title || entry.agent,
780
+ message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.status_error"] ?? "Cannot query session status")
781
+ });
782
+ return;
783
+ }
784
+ upsertEntry({
785
+ id: entry.id,
786
+ title: entry.title,
787
+ agent: entry.agent,
788
+ prompt: entry.prompt,
789
+ status: "cancel_requested",
790
+ sessionId: entry.sessionId,
791
+ cancelRequestedAt: Date.now(),
792
+ abortAccepted: false,
793
+ cancelReason: "manual"
794
+ });
795
+ try {
796
+ await props.api.client.session.abort({
797
+ sessionID: childId
798
+ });
799
+ upsertEntry({
800
+ id: entry.id,
801
+ title: entry.title,
802
+ agent: entry.agent,
803
+ prompt: entry.prompt,
804
+ status: "cancel_requested",
805
+ sessionId: entry.sessionId,
806
+ abortAccepted: true
807
+ });
808
+ props.api.ui.toast({
809
+ message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.sent"] ?? "Cancel instruction sent")
810
+ });
811
+ } catch (err) {
812
+ upsertEntry({
813
+ id: entry.id,
814
+ title: entry.title,
815
+ agent: entry.agent,
816
+ prompt: entry.prompt,
817
+ status: "error",
818
+ sessionId: entry.sessionId,
819
+ error: String(err)
820
+ });
821
+ props.api.ui.toast({
822
+ title: entry.title || entry.agent,
823
+ message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.failed"] ?? "Cancellation failed")
824
+ });
825
+ }
826
+ };
589
827
  const handlePartUpdated = (event) => {
590
828
  const e = event;
591
829
  const props_ = e.properties;
@@ -690,8 +928,9 @@ function SubAgentPanel(props) {
690
928
  }
691
929
  const tryMatchAndUpdate = (entriesMap, targetSid, targetStatus, nowTs) => {
692
930
  for (const [, entry] of entriesMap) {
693
- if (entry.sessionId === targetSid && entry.status === "running") {
694
- entry.status = targetStatus;
931
+ if (entry.sessionId === targetSid && (entry.status === "running" || entry.status === "cancel_requested")) {
932
+ const finalStatus = targetStatus === "error" ? "error" : settleOnIdle(entry);
933
+ entry.status = finalStatus;
695
934
  entry.endedAt = nowTs;
696
935
  entry.tokens = entry.tokens ?? sessionTokens;
697
936
  entry.cost = entry.cost ?? sessionCost;
@@ -707,7 +946,7 @@ function SubAgentPanel(props) {
707
946
  const saNorm = normalize(sessionAgent);
708
947
  let best = null;
709
948
  for (const [, entry] of entriesMap) {
710
- if (entry.status !== "running") continue;
949
+ if (entry.status !== "running" && entry.status !== "cancel_requested") continue;
711
950
  const eaNorm = normalize(entry.agent);
712
951
  if (!eaNorm || !saNorm) continue;
713
952
  if (!eaNorm.includes(saNorm) && !saNorm.includes(eaNorm)) continue;
@@ -719,7 +958,7 @@ function SubAgentPanel(props) {
719
958
  }
720
959
  if (!best) {
721
960
  for (const [, entry] of entriesMap) {
722
- if (entry.status !== "running") continue;
961
+ if (entry.status !== "running" && entry.status !== "cancel_requested") continue;
723
962
  if (entry.sessionId) continue;
724
963
  const gap = nowTs - (entry.startedAt || 0);
725
964
  if (!best || gap > best.gap) best = {
@@ -729,7 +968,8 @@ function SubAgentPanel(props) {
729
968
  }
730
969
  }
731
970
  if (best) {
732
- best.entry.status = targetStatus;
971
+ const finalStatus = targetStatus === "error" ? "error" : settleOnIdle(best.entry);
972
+ best.entry.status = finalStatus;
733
973
  best.entry.endedAt = nowTs;
734
974
  best.entry.tokens = best.entry.tokens ?? sessionTokens;
735
975
  best.entry.cost = best.entry.cost ?? sessionCost;
@@ -748,13 +988,14 @@ function SubAgentPanel(props) {
748
988
  const next = new Map(prev);
749
989
  for (const [id, entry] of next) {
750
990
  if (entry.sessionId !== sid) continue;
751
- if (entry.status !== "running" && entry.status !== "done") continue;
991
+ if (entry.status !== "running" && entry.status !== "done" && entry.status !== "cancel_requested") continue;
752
992
  if (sid === props.sessionId) continue;
753
- const alreadySettled = entry.status !== "running";
993
+ const alreadySettled = entry.status !== "running" && entry.status !== "cancel_requested";
994
+ const finalStatus = status === "error" ? "error" : settleOnIdle(entry);
754
995
  next.set(id, {
755
996
  ...entry,
756
997
  ...alreadySettled ? {} : {
757
- status,
998
+ status: finalStatus,
758
999
  endedAt: Date.now()
759
1000
  },
760
1001
  tokens: entry.tokens ?? sessionTokens,
@@ -772,7 +1013,7 @@ function SubAgentPanel(props) {
772
1013
  const saNorm = normalize(sessionAgent);
773
1014
  let best = null;
774
1015
  for (const [id, entry] of next) {
775
- if (entry.status !== "running") continue;
1016
+ if (entry.status !== "running" && entry.status !== "cancel_requested") continue;
776
1017
  const eaNorm = normalize(entry.agent);
777
1018
  if (!eaNorm || !saNorm) continue;
778
1019
  if (!eaNorm.includes(saNorm) && !saNorm.includes(eaNorm)) continue;
@@ -784,7 +1025,7 @@ function SubAgentPanel(props) {
784
1025
  }
785
1026
  if (!best) {
786
1027
  for (const [id, entry] of next) {
787
- if (entry.status !== "running") continue;
1028
+ if (entry.status !== "running" && entry.status !== "cancel_requested") continue;
788
1029
  if (entry.sessionId) continue;
789
1030
  const gap = nowTs - (entry.startedAt || 0);
790
1031
  if (!best || gap > best.gap) best = {
@@ -795,9 +1036,10 @@ function SubAgentPanel(props) {
795
1036
  }
796
1037
  if (best) {
797
1038
  const entry = next.get(best.id);
1039
+ const finalStatus = status === "error" ? "error" : settleOnIdle(entry);
798
1040
  next.set(best.id, {
799
1041
  ...entry,
800
- status,
1042
+ status: finalStatus,
801
1043
  endedAt: nowTs,
802
1044
  tokens: sessionTokens || entry.tokens,
803
1045
  cost: sessionCost || entry.cost,
@@ -1020,7 +1262,7 @@ function SubAgentPanel(props) {
1020
1262
  const scanHasChild = scanStMeta?.session_id !== void 0 || scanStMeta?.sessionId !== void 0;
1021
1263
  if (scanHasChild) status = "running";
1022
1264
  }
1023
- if (exists && exists.status !== "running") continue;
1265
+ if (exists && exists.status !== "running" && exists.status !== "cancel_requested") continue;
1024
1266
  if (exists && status === "running") {
1025
1267
  if (!rawStatus) {
1026
1268
  const msgTokens = msg?.tokens;
@@ -1069,15 +1311,16 @@ function SubAgentPanel(props) {
1069
1311
  let changed = false;
1070
1312
  const next = new Map(prev);
1071
1313
  for (const [id, entry] of next) {
1072
- if (entry.status !== "running" || !entry.sessionId) continue;
1314
+ if (entry.status !== "running" && entry.status !== "cancel_requested" || !entry.sessionId) continue;
1073
1315
  try {
1074
1316
  const st = props.api.state.session.status(entry.sessionId);
1075
1317
  if (!st || st.type !== "idle") continue;
1076
1318
  const tokens = readSessionTokens(entry.sessionId);
1077
1319
  const cost = readSessionCost(entry.sessionId);
1320
+ const finalStatus = entry.status === "cancel_requested" && entry.abortAccepted ? "cancelled" : "done";
1078
1321
  next.set(id, {
1079
1322
  ...entry,
1080
- status: "done",
1323
+ status: finalStatus,
1081
1324
  endedAt: Date.now(),
1082
1325
  tokens: tokens ?? entry.tokens,
1083
1326
  cost: cost ?? entry.cost
@@ -1173,8 +1416,8 @@ function SubAgentPanel(props) {
1173
1416
  elapsed: (e.endedAt ?? nowVal) - e.startedAt
1174
1417
  }));
1175
1418
  });
1176
- const doneCount = createMemo(() => entryList().filter((e) => e.status === "done").length);
1177
- const runningCount = createMemo(() => entryList().filter((e) => e.status === "running").length);
1419
+ const doneCount = createMemo(() => entryList().filter((e) => e.status === "done" || e.status === "cancelled").length);
1420
+ const runningCount = createMemo(() => entryList().filter((e) => e.status === "running" || e.status === "cancel_requested").length);
1178
1421
  const errCount = createMemo(() => entryList().filter((e) => e.status === "error").length);
1179
1422
  const anyEntry = () => entryList().length > 0;
1180
1423
  const totalTokens = createMemo(() => {
@@ -1433,11 +1676,15 @@ function SubAgentPanel(props) {
1433
1676
  children: (entry) => {
1434
1677
  const isExpanded = () => expanded() === entry.id;
1435
1678
  const isRunning = entry.status === "running";
1679
+ const isCancelRequested = entry.status === "cancel_requested";
1680
+ const isCancelled = entry.status === "cancelled";
1436
1681
  const isError = entry.status === "error";
1682
+ const isActiveRunning = isRunning || isCancelRequested;
1437
1683
  const elapsed = () => (entry.endedAt ?? now()) - entry.startedAt;
1438
1684
  const statusDot = () => "\u25CF";
1439
1685
  const statusColor = () => {
1440
- if (!isRunning) return isError ? pal().error : pal().success;
1686
+ if (isCancelled) return pal().muted;
1687
+ if (!isActiveRunning) return isError ? pal().error : pal().success;
1441
1688
  const t2 = (Math.sin(now() % 2e3 / 2e3 * Math.PI * 2 - Math.PI / 2) + 1) / 2;
1442
1689
  const a = rgb(pal().muted), b = rgb(pal().warning);
1443
1690
  if (!a || !b) return pal().warning;
@@ -1446,9 +1693,9 @@ function SubAgentPanel(props) {
1446
1693
  const bl = Math.round(a.b + (b.b - a.b) * t2);
1447
1694
  return "#" + [r, g, bl].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("");
1448
1695
  };
1449
- const timeColor = () => isRunning ? pal().warning : isError ? pal().error : pal().muted;
1696
+ const timeColor = () => isActiveRunning ? pal().warning : isError ? pal().error : pal().muted;
1450
1697
  const tokenText = () => !isExpanded() && entry.tokens !== void 0 && entry.tokens > 0 ? ` ${fmtTokens(entry.tokens)}` : "";
1451
- const timeText = () => !isExpanded() && (elapsed() >= 2e3 || entry.endedAt !== void 0) ? fmtDurationShort(elapsed(), isRunning) : "";
1698
+ const timeText = () => !isExpanded() && (elapsed() >= 2e3 || entry.endedAt !== void 0) ? fmtDurationShort(elapsed(), isActiveRunning) : "";
1452
1699
  const suffixW = () => {
1453
1700
  let w = 0;
1454
1701
  const t2 = timeText();
@@ -1558,14 +1805,14 @@ function SubAgentPanel(props) {
1558
1805
  _$insertNode(_el$39, _el$40);
1559
1806
  _$insert(_el$39, () => t("status.label"), _el$40);
1560
1807
  _$insert(_el$41, () => " ".repeat(expandedPad(t("status.label"))));
1561
- _$insert(_el$42, () => isRunning ? t("status.running") : isError ? t("status.error") : t("status.done"));
1808
+ _$insert(_el$42, () => isActiveRunning ? t("status.running") : isCancelled ? t("status.cancelled") : isError ? t("status.error") : t("status.done"));
1562
1809
  _$effect((_p$) => {
1563
1810
  var _v$9 = {
1564
1811
  fg: pal().primary
1565
1812
  }, _v$0 = {
1566
1813
  fg: pal().muted
1567
1814
  }, _v$1 = {
1568
- fg: isRunning ? pal().warning : isError ? pal().error : pal().success
1815
+ fg: isActiveRunning ? pal().warning : isCancelled ? pal().muted : isError ? pal().error : pal().success
1569
1816
  };
1570
1817
  _v$9 !== _p$.e && (_p$.e = _$setProp(_el$39, "style", _v$9, _p$.e));
1571
1818
  _v$0 !== _p$.t && (_p$.t = _$setProp(_el$41, "style", _v$0, _p$.t));
@@ -1590,7 +1837,7 @@ function SubAgentPanel(props) {
1590
1837
  _$insertNode(_el$45, _el$46);
1591
1838
  _$insert(_el$45, () => t("time.label"), _el$46);
1592
1839
  _$insert(_el$47, () => " ".repeat(expandedPad(t("time.label"))));
1593
- _$insert(_el$48, () => fmtDurationShort(elapsed(), isRunning));
1840
+ _$insert(_el$48, () => fmtDurationShort(elapsed(), isActiveRunning));
1594
1841
  _$effect((_p$) => {
1595
1842
  var _v$10 = {
1596
1843
  fg: pal().primary
@@ -1794,16 +2041,27 @@ function SubAgentPanel(props) {
1794
2041
  _$insertNode(_el$74, _el$78);
1795
2042
  _$insertNode(_el$74, _el$79);
1796
2043
  _$insertNode(_el$74, _el$80);
1797
- _$setProp(_el$74, "onMouseUp", () => {
1798
- if (entry.sessionId) {
2044
+ _$setProp(_el$74, "onMouseUp", async () => {
2045
+ const sessionId = entry.sessionId;
2046
+ if (!sessionId) return;
2047
+ const result = await copyText(sessionId);
2048
+ if (result.copied) {
1799
2049
  props.api.ui.toast({
2050
+ variant: "success",
1800
2051
  title: entry.title || entry.agent,
1801
- message: `${entry.sessionId}
1802
-
1803
- ${t("session.toast.copy")}`,
1804
- duration: 8e3
2052
+ message: t("session.toast.copied"),
2053
+ duration: 2500
1805
2054
  });
2055
+ return;
1806
2056
  }
2057
+ props.api.ui.toast({
2058
+ variant: "warning",
2059
+ title: entry.title || entry.agent,
2060
+ message: `${sessionId}
2061
+
2062
+ ${t("session.toast.copy_failed")}`,
2063
+ duration: 8e3
2064
+ });
1807
2065
  });
1808
2066
  _$insertNode(_el$76, _el$77);
1809
2067
  _$insert(_el$76, () => t("session.label"), _el$77);
@@ -1839,29 +2097,21 @@ ${t("session.toast.copy")}`,
1839
2097
  },
1840
2098
  get children() {
1841
2099
  return (() => {
1842
- const dismissLabel = () => `- ${t("dismiss.label")}`;
1843
2100
  const openPrefix = () => " \u2192 ";
1844
2101
  const openFull = () => entry.sessionId ? openPrefix() + t("open.label") : "";
1845
2102
  const openW = () => entry.sessionId ? visualWidth(openFull()) : 0;
1846
- const spacerW = () => Math.max(
1847
- 1,
1848
- panelWidth() - openW() - visualWidth(dismissLabel()) - 2
1849
- /* indent */
1850
- );
2103
+ const cancelLabel = () => ` ${t("cancel.label")}`;
2104
+ const dismissLabel = () => ` ${t("dismiss.label")}`;
2105
+ const rightW = (isRunning ? visualWidth(dismissLabel()) : 0) + (isRunning && entry.sessionId ? visualWidth(cancelLabel()) : 0);
2106
+ const spacerW = () => Math.max(1, panelWidth() - openW() - rightW - 2);
1851
2107
  return (() => {
1852
- var _el$91 = _$createElement("box");
2108
+ var _el$91 = _$createElement("box"), _el$95 = _$createElement("text");
2109
+ _$insertNode(_el$91, _el$95);
1853
2110
  _$setProp(_el$91, "flexDirection", "row");
1854
2111
  _$insert(_el$91, _$createComponent(Show, {
1855
2112
  get when() {
1856
2113
  return entry.sessionId;
1857
2114
  },
1858
- get fallback() {
1859
- return (() => {
1860
- var _el$98 = _$createElement("text");
1861
- _$insertNode(_el$98, _$createTextNode(` `));
1862
- return _el$98;
1863
- })();
1864
- },
1865
2115
  get children() {
1866
2116
  var _el$92 = _$createElement("text"), _el$93 = _$createElement("span"), _el$94 = _$createElement("span");
1867
2117
  _$insertNode(_el$92, _el$93);
@@ -1892,39 +2142,51 @@ ${t("session.toast.copy")}`,
1892
2142
  });
1893
2143
  return _el$92;
1894
2144
  }
2145
+ }), _el$95);
2146
+ _$insert(_el$95, () => " ".repeat(spacerW()));
2147
+ _$insert(_el$91, _$createComponent(Show, {
2148
+ get when() {
2149
+ return isRunning && entry.sessionId;
2150
+ },
2151
+ get children() {
2152
+ var _el$96 = _$createElement("text"), _el$97 = _$createElement("span");
2153
+ _$insertNode(_el$96, _el$97);
2154
+ _$setProp(_el$96, "onMouseOver", () => setHoveredCancel(entry.id));
2155
+ _$setProp(_el$96, "onMouseOut", () => setHoveredCancel(void 0));
2156
+ _$setProp(_el$96, "onMouseUp", () => cancelEntry(entry));
2157
+ _$insert(_el$97, cancelLabel);
2158
+ _$effect((_$p) => _$setProp(_el$97, "style", {
2159
+ fg: hoveredCancel() === entry.id ? pal().warning : pal().error
2160
+ }, _$p));
2161
+ return _el$96;
2162
+ }
1895
2163
  }), null);
1896
2164
  _$insert(_el$91, _$createComponent(Show, {
1897
2165
  when: isRunning,
1898
2166
  get children() {
1899
- return [(() => {
1900
- var _el$95 = _$createElement("text");
1901
- _$insert(_el$95, () => " ".repeat(spacerW()));
1902
- _$effect((_$p) => _$setProp(_el$95, "style", {
1903
- fg: pal().muted
1904
- }, _$p));
1905
- return _el$95;
1906
- })(), (() => {
1907
- var _el$96 = _$createElement("text"), _el$97 = _$createElement("span");
1908
- _$insertNode(_el$96, _el$97);
1909
- _$setProp(_el$96, "onMouseOver", () => setHoveredDismiss(entry.id));
1910
- _$setProp(_el$96, "onMouseOut", () => setHoveredDismiss(void 0));
1911
- _$setProp(_el$96, "onMouseUp", () => {
1912
- upsertEntry({
1913
- id: entry.id,
1914
- title: entry.title,
1915
- agent: entry.agent,
1916
- prompt: entry.prompt,
1917
- status: "done"
1918
- });
2167
+ var _el$98 = _$createElement("text"), _el$99 = _$createElement("span");
2168
+ _$insertNode(_el$98, _el$99);
2169
+ _$setProp(_el$98, "onMouseOver", () => setHoveredDismiss(entry.id));
2170
+ _$setProp(_el$98, "onMouseOut", () => setHoveredDismiss(void 0));
2171
+ _$setProp(_el$98, "onMouseUp", () => {
2172
+ upsertEntry({
2173
+ id: entry.id,
2174
+ title: entry.title,
2175
+ agent: entry.agent,
2176
+ prompt: entry.prompt,
2177
+ status: "done"
1919
2178
  });
1920
- _$insert(_el$97, dismissLabel);
1921
- _$effect((_$p) => _$setProp(_el$97, "style", {
1922
- fg: hoveredDismiss() === entry.id ? pal().warning : pal().muted
1923
- }, _$p));
1924
- return _el$96;
1925
- })()];
2179
+ });
2180
+ _$insert(_el$99, dismissLabel);
2181
+ _$effect((_$p) => _$setProp(_el$99, "style", {
2182
+ fg: hoveredDismiss() === entry.id ? pal().warning : pal().muted
2183
+ }, _$p));
2184
+ return _el$98;
1926
2185
  }
1927
2186
  }), null);
2187
+ _$effect((_$p) => _$setProp(_el$95, "style", {
2188
+ fg: pal().muted
2189
+ }, _$p));
1928
2190
  return _el$91;
1929
2191
  })();
1930
2192
  })();
@@ -2238,7 +2500,7 @@ var tui = async (api) => {
2238
2500
  }
2239
2501
  let count = 0;
2240
2502
  for (const [, entry] of entries) {
2241
- if (entry.status === "running") {
2503
+ if (entry.status === "running" || entry.status === "cancel_requested") {
2242
2504
  entry.status = "done";
2243
2505
  entry.endedAt = Date.now();
2244
2506
  count++;