opencode-subagent-magazine 1.4.1 → 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.1";
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();
@@ -1479,23 +1726,23 @@ function SubAgentPanel(props) {
1479
1726
  _$insert(_el$25, (() => {
1480
1727
  var _c$2 = _$memo(() => !!timeText());
1481
1728
  return () => _c$2() ? [" ", (() => {
1482
- var _el$89 = _$createElement("span");
1483
- _$insert(_el$89, timeText);
1484
- _$effect((_$p) => _$setProp(_el$89, "style", {
1729
+ var _el$82 = _$createElement("span");
1730
+ _$insert(_el$82, timeText);
1731
+ _$effect((_$p) => _$setProp(_el$82, "style", {
1485
1732
  fg: timeColor()
1486
1733
  }, _$p));
1487
- return _el$89;
1734
+ return _el$82;
1488
1735
  })()] : null;
1489
1736
  })(), null);
1490
1737
  _$insert(_el$25, (() => {
1491
1738
  var _c$3 = _$memo(() => !!tokenText());
1492
1739
  return () => _c$3() ? (() => {
1493
- var _el$90 = _$createElement("span");
1494
- _$insert(_el$90, tokenText);
1495
- _$effect((_$p) => _$setProp(_el$90, "style", {
1740
+ var _el$83 = _$createElement("span");
1741
+ _$insert(_el$83, tokenText);
1742
+ _$effect((_$p) => _$setProp(_el$83, "style", {
1496
1743
  fg: pal().muted
1497
1744
  }, _$p));
1498
- return _el$90;
1745
+ return _el$83;
1499
1746
  })() : null;
1500
1747
  })(), null);
1501
1748
  _$effect((_p$) => {
@@ -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
@@ -1681,16 +1928,54 @@ function SubAgentPanel(props) {
1681
1928
  return entry.cost !== void 0;
1682
1929
  },
1683
1930
  get children() {
1684
- var _el$61 = _$createElement("text"), _el$62 = _$createTextNode(` `), _el$63 = _$createElement("span"), _el$64 = _$createTextNode(`: `), _el$65 = _$createElement("span"), _el$66 = _$createElement("span"), _el$67 = _$createTextNode(`$`);
1931
+ return (() => {
1932
+ const cost = entry.cost;
1933
+ return (() => {
1934
+ var _el$84 = _$createElement("text"), _el$85 = _$createTextNode(` `), _el$86 = _$createElement("span"), _el$87 = _$createTextNode(`: `), _el$88 = _$createElement("span"), _el$89 = _$createElement("span"), _el$90 = _$createTextNode(`$`);
1935
+ _$insertNode(_el$84, _el$85);
1936
+ _$insertNode(_el$84, _el$86);
1937
+ _$insertNode(_el$84, _el$88);
1938
+ _$insertNode(_el$84, _el$89);
1939
+ _$insertNode(_el$86, _el$87);
1940
+ _$insert(_el$86, () => t("cost.label"), _el$87);
1941
+ _$insert(_el$88, () => " ".repeat(expandedPad(t("cost.label"))));
1942
+ _$insertNode(_el$89, _el$90);
1943
+ _$insert(_el$89, () => cost.toFixed(4), null);
1944
+ _$effect((_p$) => {
1945
+ var _v$29 = {
1946
+ fg: pal().primary
1947
+ }, _v$30 = {
1948
+ fg: pal().muted
1949
+ }, _v$31 = {
1950
+ fg: pal().muted
1951
+ };
1952
+ _v$29 !== _p$.e && (_p$.e = _$setProp(_el$86, "style", _v$29, _p$.e));
1953
+ _v$30 !== _p$.t && (_p$.t = _$setProp(_el$88, "style", _v$30, _p$.t));
1954
+ _v$31 !== _p$.a && (_p$.a = _$setProp(_el$89, "style", _v$31, _p$.a));
1955
+ return _p$;
1956
+ }, {
1957
+ e: void 0,
1958
+ t: void 0,
1959
+ a: void 0
1960
+ });
1961
+ return _el$84;
1962
+ })();
1963
+ })();
1964
+ }
1965
+ }), _$createComponent(Show, {
1966
+ get when() {
1967
+ return entry.model;
1968
+ },
1969
+ get children() {
1970
+ var _el$61 = _$createElement("text"), _el$62 = _$createTextNode(` `), _el$63 = _$createElement("span"), _el$64 = _$createTextNode(`: `), _el$65 = _$createElement("span"), _el$66 = _$createElement("span");
1685
1971
  _$insertNode(_el$61, _el$62);
1686
1972
  _$insertNode(_el$61, _el$63);
1687
1973
  _$insertNode(_el$61, _el$65);
1688
1974
  _$insertNode(_el$61, _el$66);
1689
1975
  _$insertNode(_el$63, _el$64);
1690
- _$insert(_el$63, () => t("cost.label"), _el$64);
1691
- _$insert(_el$65, () => " ".repeat(expandedPad(t("cost.label"))));
1692
- _$insertNode(_el$66, _el$67);
1693
- _$insert(_el$66, () => entry.cost.toFixed(4), null);
1976
+ _$insert(_el$63, () => t("model.label"), _el$64);
1977
+ _$insert(_el$65, () => " ".repeat(expandedPad(t("model.label"))));
1978
+ _$insert(_el$66, () => truncate(entry.model, expandedValAvail()));
1694
1979
  _$effect((_p$) => {
1695
1980
  var _v$19 = {
1696
1981
  fg: pal().primary
@@ -1712,18 +1997,20 @@ function SubAgentPanel(props) {
1712
1997
  }
1713
1998
  }), _$createComponent(Show, {
1714
1999
  get when() {
1715
- return entry.model;
2000
+ return entry.todoTotal !== void 0;
1716
2001
  },
1717
2002
  get children() {
1718
- var _el$68 = _$createElement("text"), _el$69 = _$createTextNode(` `), _el$70 = _$createElement("span"), _el$71 = _$createTextNode(`: `), _el$72 = _$createElement("span"), _el$73 = _$createElement("span");
1719
- _$insertNode(_el$68, _el$69);
1720
- _$insertNode(_el$68, _el$70);
1721
- _$insertNode(_el$68, _el$72);
1722
- _$insertNode(_el$68, _el$73);
1723
- _$insertNode(_el$70, _el$71);
1724
- _$insert(_el$70, () => t("model.label"), _el$71);
1725
- _$insert(_el$72, () => " ".repeat(expandedPad(t("model.label"))));
1726
- _$insert(_el$73, () => truncate(entry.model, expandedValAvail()));
2003
+ var _el$67 = _$createElement("text"), _el$68 = _$createTextNode(` `), _el$69 = _$createElement("span"), _el$70 = _$createTextNode(`: `), _el$71 = _$createElement("span"), _el$72 = _$createElement("span"), _el$73 = _$createTextNode(`/`);
2004
+ _$insertNode(_el$67, _el$68);
2005
+ _$insertNode(_el$67, _el$69);
2006
+ _$insertNode(_el$67, _el$71);
2007
+ _$insertNode(_el$67, _el$72);
2008
+ _$insertNode(_el$69, _el$70);
2009
+ _$insert(_el$69, () => t("todo.label"), _el$70);
2010
+ _$insert(_el$71, () => " ".repeat(expandedPad(t("todo.label"))));
2011
+ _$insertNode(_el$72, _el$73);
2012
+ _$insert(_el$72, () => entry.todoDone, _el$73);
2013
+ _$insert(_el$72, () => entry.todoTotal, null);
1727
2014
  _$effect((_p$) => {
1728
2015
  var _v$22 = {
1729
2016
  fg: pal().primary
@@ -1732,33 +2019,55 @@ function SubAgentPanel(props) {
1732
2019
  }, _v$24 = {
1733
2020
  fg: pal().muted
1734
2021
  };
1735
- _v$22 !== _p$.e && (_p$.e = _$setProp(_el$70, "style", _v$22, _p$.e));
1736
- _v$23 !== _p$.t && (_p$.t = _$setProp(_el$72, "style", _v$23, _p$.t));
1737
- _v$24 !== _p$.a && (_p$.a = _$setProp(_el$73, "style", _v$24, _p$.a));
2022
+ _v$22 !== _p$.e && (_p$.e = _$setProp(_el$69, "style", _v$22, _p$.e));
2023
+ _v$23 !== _p$.t && (_p$.t = _$setProp(_el$71, "style", _v$23, _p$.t));
2024
+ _v$24 !== _p$.a && (_p$.a = _$setProp(_el$72, "style", _v$24, _p$.a));
1738
2025
  return _p$;
1739
2026
  }, {
1740
2027
  e: void 0,
1741
2028
  t: void 0,
1742
2029
  a: void 0
1743
2030
  });
1744
- return _el$68;
2031
+ return _el$67;
1745
2032
  }
1746
2033
  }), _$createComponent(Show, {
1747
2034
  get when() {
1748
- return entry.todoTotal !== void 0;
2035
+ return entry.sessionId;
1749
2036
  },
1750
2037
  get children() {
1751
- var _el$74 = _$createElement("text"), _el$75 = _$createTextNode(` `), _el$76 = _$createElement("span"), _el$77 = _$createTextNode(`: `), _el$78 = _$createElement("span"), _el$79 = _$createElement("span"), _el$80 = _$createTextNode(`/`);
2038
+ var _el$74 = _$createElement("text"), _el$75 = _$createTextNode(` `), _el$76 = _$createElement("span"), _el$77 = _$createTextNode(`: `), _el$78 = _$createElement("span"), _el$79 = _$createElement("span"), _el$80 = _$createElement("span");
1752
2039
  _$insertNode(_el$74, _el$75);
1753
2040
  _$insertNode(_el$74, _el$76);
1754
2041
  _$insertNode(_el$74, _el$78);
1755
2042
  _$insertNode(_el$74, _el$79);
2043
+ _$insertNode(_el$74, _el$80);
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) {
2049
+ props.api.ui.toast({
2050
+ variant: "success",
2051
+ title: entry.title || entry.agent,
2052
+ message: t("session.toast.copied"),
2053
+ duration: 2500
2054
+ });
2055
+ return;
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
+ });
2065
+ });
1756
2066
  _$insertNode(_el$76, _el$77);
1757
- _$insert(_el$76, () => t("todo.label"), _el$77);
1758
- _$insert(_el$78, () => " ".repeat(expandedPad(t("todo.label"))));
1759
- _$insertNode(_el$79, _el$80);
1760
- _$insert(_el$79, () => entry.todoDone, _el$80);
1761
- _$insert(_el$79, () => entry.todoTotal, null);
2067
+ _$insert(_el$76, () => t("session.label"), _el$77);
2068
+ _$insert(_el$78, () => " ".repeat(expandedPad(t("session.label"))));
2069
+ _$insert(_el$79, () => truncate(entry.sessionId, expandedValAvail() - visualWidth(" \u2398")));
2070
+ _$insertNode(_el$80, _$createTextNode(` \u2398`));
1762
2071
  _$effect((_p$) => {
1763
2072
  var _v$25 = {
1764
2073
  fg: pal().primary
@@ -1766,59 +2075,13 @@ function SubAgentPanel(props) {
1766
2075
  fg: pal().muted
1767
2076
  }, _v$27 = {
1768
2077
  fg: pal().muted
2078
+ }, _v$28 = {
2079
+ fg: pal().warning
1769
2080
  };
1770
2081
  _v$25 !== _p$.e && (_p$.e = _$setProp(_el$76, "style", _v$25, _p$.e));
1771
2082
  _v$26 !== _p$.t && (_p$.t = _$setProp(_el$78, "style", _v$26, _p$.t));
1772
2083
  _v$27 !== _p$.a && (_p$.a = _$setProp(_el$79, "style", _v$27, _p$.a));
1773
- return _p$;
1774
- }, {
1775
- e: void 0,
1776
- t: void 0,
1777
- a: void 0
1778
- });
1779
- return _el$74;
1780
- }
1781
- }), _$createComponent(Show, {
1782
- get when() {
1783
- return entry.sessionId;
1784
- },
1785
- get children() {
1786
- var _el$81 = _$createElement("text"), _el$82 = _$createTextNode(` `), _el$83 = _$createElement("span"), _el$84 = _$createTextNode(`: `), _el$85 = _$createElement("span"), _el$86 = _$createElement("span"), _el$87 = _$createElement("span");
1787
- _$insertNode(_el$81, _el$82);
1788
- _$insertNode(_el$81, _el$83);
1789
- _$insertNode(_el$81, _el$85);
1790
- _$insertNode(_el$81, _el$86);
1791
- _$insertNode(_el$81, _el$87);
1792
- _$setProp(_el$81, "onMouseUp", () => {
1793
- if (entry.sessionId) {
1794
- props.api.ui.toast({
1795
- title: entry.title || entry.agent,
1796
- message: `${entry.sessionId}
1797
-
1798
- ${t("session.toast.copy")}`,
1799
- duration: 8e3
1800
- });
1801
- }
1802
- });
1803
- _$insertNode(_el$83, _el$84);
1804
- _$insert(_el$83, () => t("session.label"), _el$84);
1805
- _$insert(_el$85, () => " ".repeat(expandedPad(t("session.label"))));
1806
- _$insert(_el$86, () => truncate(entry.sessionId, expandedValAvail() - visualWidth(" \u2398")));
1807
- _$insertNode(_el$87, _$createTextNode(` \u2398`));
1808
- _$effect((_p$) => {
1809
- var _v$28 = {
1810
- fg: pal().primary
1811
- }, _v$29 = {
1812
- fg: pal().muted
1813
- }, _v$30 = {
1814
- fg: pal().muted
1815
- }, _v$31 = {
1816
- fg: pal().warning
1817
- };
1818
- _v$28 !== _p$.e && (_p$.e = _$setProp(_el$83, "style", _v$28, _p$.e));
1819
- _v$29 !== _p$.t && (_p$.t = _$setProp(_el$85, "style", _v$29, _p$.t));
1820
- _v$30 !== _p$.a && (_p$.a = _$setProp(_el$86, "style", _v$30, _p$.a));
1821
- _v$31 !== _p$.o && (_p$.o = _$setProp(_el$87, "style", _v$31, _p$.o));
2084
+ _v$28 !== _p$.o && (_p$.o = _$setProp(_el$80, "style", _v$28, _p$.o));
1822
2085
  return _p$;
1823
2086
  }, {
1824
2087
  e: void 0,
@@ -1826,7 +2089,7 @@ ${t("session.toast.copy")}`,
1826
2089
  a: void 0,
1827
2090
  o: void 0
1828
2091
  });
1829
- return _el$81;
2092
+ return _el$74;
1830
2093
  }
1831
2094
  }), _$createComponent(Show, {
1832
2095
  get when() {
@@ -1834,29 +2097,21 @@ ${t("session.toast.copy")}`,
1834
2097
  },
1835
2098
  get children() {
1836
2099
  return (() => {
1837
- const dismissLabel = () => `- ${t("dismiss.label")}`;
1838
2100
  const openPrefix = () => " \u2192 ";
1839
2101
  const openFull = () => entry.sessionId ? openPrefix() + t("open.label") : "";
1840
2102
  const openW = () => entry.sessionId ? visualWidth(openFull()) : 0;
1841
- const spacerW = () => Math.max(
1842
- 1,
1843
- panelWidth() - openW() - visualWidth(dismissLabel()) - 2
1844
- /* indent */
1845
- );
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);
1846
2107
  return (() => {
1847
- var _el$91 = _$createElement("box");
2108
+ var _el$91 = _$createElement("box"), _el$95 = _$createElement("text");
2109
+ _$insertNode(_el$91, _el$95);
1848
2110
  _$setProp(_el$91, "flexDirection", "row");
1849
2111
  _$insert(_el$91, _$createComponent(Show, {
1850
2112
  get when() {
1851
2113
  return entry.sessionId;
1852
2114
  },
1853
- get fallback() {
1854
- return (() => {
1855
- var _el$98 = _$createElement("text");
1856
- _$insertNode(_el$98, _$createTextNode(` `));
1857
- return _el$98;
1858
- })();
1859
- },
1860
2115
  get children() {
1861
2116
  var _el$92 = _$createElement("text"), _el$93 = _$createElement("span"), _el$94 = _$createElement("span");
1862
2117
  _$insertNode(_el$92, _el$93);
@@ -1887,39 +2142,51 @@ ${t("session.toast.copy")}`,
1887
2142
  });
1888
2143
  return _el$92;
1889
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
+ }
1890
2163
  }), null);
1891
2164
  _$insert(_el$91, _$createComponent(Show, {
1892
2165
  when: isRunning,
1893
2166
  get children() {
1894
- return [(() => {
1895
- var _el$95 = _$createElement("text");
1896
- _$insert(_el$95, () => " ".repeat(spacerW()));
1897
- _$effect((_$p) => _$setProp(_el$95, "style", {
1898
- fg: pal().muted
1899
- }, _$p));
1900
- return _el$95;
1901
- })(), (() => {
1902
- var _el$96 = _$createElement("text"), _el$97 = _$createElement("span");
1903
- _$insertNode(_el$96, _el$97);
1904
- _$setProp(_el$96, "onMouseOver", () => setHoveredDismiss(entry.id));
1905
- _$setProp(_el$96, "onMouseOut", () => setHoveredDismiss(void 0));
1906
- _$setProp(_el$96, "onMouseUp", () => {
1907
- upsertEntry({
1908
- id: entry.id,
1909
- title: entry.title,
1910
- agent: entry.agent,
1911
- prompt: entry.prompt,
1912
- status: "done"
1913
- });
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"
1914
2178
  });
1915
- _$insert(_el$97, dismissLabel);
1916
- _$effect((_$p) => _$setProp(_el$97, "style", {
1917
- fg: hoveredDismiss() === entry.id ? pal().warning : pal().muted
1918
- }, _$p));
1919
- return _el$96;
1920
- })()];
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;
1921
2185
  }
1922
2186
  }), null);
2187
+ _$effect((_$p) => _$setProp(_el$95, "style", {
2188
+ fg: pal().muted
2189
+ }, _$p));
1923
2190
  return _el$91;
1924
2191
  })();
1925
2192
  })();
@@ -2233,7 +2500,7 @@ var tui = async (api) => {
2233
2500
  }
2234
2501
  let count = 0;
2235
2502
  for (const [, entry] of entries) {
2236
- if (entry.status === "running") {
2503
+ if (entry.status === "running" || entry.status === "cancel_requested") {
2237
2504
  entry.status = "done";
2238
2505
  entry.endedAt = Date.now();
2239
2506
  count++;