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/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/clipboard.d.ts +7 -0
- package/dist/clipboard.js +85 -0
- package/dist/index.js +188 -35
- package/dist/tui.js +336 -74
- package/package.json +2 -1
- package/src/_version.ts +1 -1
- package/src/clipboard.ts +134 -0
- package/src/index.tsx +213 -50
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.
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 (
|
|
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 = () =>
|
|
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(),
|
|
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, () =>
|
|
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:
|
|
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(),
|
|
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
|
-
|
|
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:
|
|
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
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
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
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
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
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
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++;
|