agent-office 0.0.0 → 0.0.2
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/cli.js +102 -3
- package/dist/commands/serve.js +9 -2
- package/dist/commands/worker.d.ts +13 -0
- package/dist/commands/worker.js +120 -5
- package/dist/db/index.d.ts +32 -0
- package/dist/db/migrate.js +66 -0
- package/dist/manage/app.js +55 -35
- package/dist/manage/components/CronList.d.ts +9 -0
- package/dist/manage/components/CronList.js +310 -0
- package/dist/manage/components/ItemSelector.d.ts +7 -0
- package/dist/manage/components/ItemSelector.js +20 -0
- package/dist/manage/components/MenuSelect.d.ts +13 -0
- package/dist/manage/components/MenuSelect.js +22 -0
- package/dist/manage/components/MyMail.d.ts +9 -0
- package/dist/manage/components/MyMail.js +143 -0
- package/dist/manage/components/Profile.d.ts +8 -0
- package/dist/manage/components/Profile.js +60 -0
- package/dist/manage/components/ReadMail.d.ts +8 -0
- package/dist/manage/components/ReadMail.js +110 -0
- package/dist/manage/components/SendMessage.d.ts +9 -0
- package/dist/manage/components/SendMessage.js +79 -0
- package/dist/manage/components/SessionList.js +392 -31
- package/dist/manage/components/SessionSidebar.d.ts +6 -0
- package/dist/manage/components/SessionSidebar.js +18 -0
- package/dist/manage/hooks/useApi.d.ts +74 -1
- package/dist/manage/hooks/useApi.js +69 -3
- package/dist/server/cron.d.ts +24 -0
- package/dist/server/cron.js +121 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +3 -3
- package/dist/server/routes.d.ts +3 -2
- package/dist/server/routes.js +976 -23
- package/package.json +3 -2
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import { Select, Spinner } from "@inkjs/ui";
|
|
5
|
+
import { useApi, useAsyncState } from "../hooks/useApi.js";
|
|
6
|
+
export function ReadMail({ serverUrl, password, onBack, contentHeight }) {
|
|
7
|
+
const { listSessions, getMailMessages, markMessageRead } = useApi(serverUrl, password);
|
|
8
|
+
const { run: runList } = useAsyncState();
|
|
9
|
+
const [sessions, setSessions] = useState([]);
|
|
10
|
+
const [sessionsLoading, setSessionsLoading] = useState(true);
|
|
11
|
+
const [stage, setStage] = useState("select-session");
|
|
12
|
+
const [selectedName, setSelectedName] = useState(null);
|
|
13
|
+
const [viewTab, setViewTab] = useState("received");
|
|
14
|
+
const [messages, setMessages] = useState([]);
|
|
15
|
+
const [error, setError] = useState(null);
|
|
16
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
runList(listSessions).then((rows) => {
|
|
19
|
+
setSessions(rows ?? []);
|
|
20
|
+
setSessionsLoading(false);
|
|
21
|
+
});
|
|
22
|
+
}, []);
|
|
23
|
+
const loadMessages = async (name) => {
|
|
24
|
+
setSelectedName(name);
|
|
25
|
+
setStage("loading");
|
|
26
|
+
try {
|
|
27
|
+
const msgs = await getMailMessages(name, { sent: false });
|
|
28
|
+
setMessages(msgs);
|
|
29
|
+
setScrollOffset(0);
|
|
30
|
+
setStage("view-received");
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
34
|
+
setStage("error");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const handleTabSwitch = async (newTab) => {
|
|
38
|
+
if (!selectedName)
|
|
39
|
+
return;
|
|
40
|
+
setStage("loading");
|
|
41
|
+
setViewTab(newTab);
|
|
42
|
+
setScrollOffset(0);
|
|
43
|
+
try {
|
|
44
|
+
const msgs = await getMailMessages(selectedName, { sent: newTab === "sent" });
|
|
45
|
+
setMessages(msgs);
|
|
46
|
+
setStage(newTab === "sent" ? "view-sent" : "view-received");
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
50
|
+
setStage("error");
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
useInput((input, key) => {
|
|
54
|
+
if ((stage === "view-received" || stage === "view-sent")) {
|
|
55
|
+
if (key.upArrow)
|
|
56
|
+
setScrollOffset((o) => Math.max(0, o - 1));
|
|
57
|
+
if (key.downArrow)
|
|
58
|
+
setScrollOffset((o) => o + 1);
|
|
59
|
+
if (input === "r" && stage === "view-received") {
|
|
60
|
+
handleTabSwitch("received");
|
|
61
|
+
}
|
|
62
|
+
if (input === "s" && stage === "view-received") {
|
|
63
|
+
handleTabSwitch("sent");
|
|
64
|
+
}
|
|
65
|
+
if (input === "r" && stage === "view-sent") {
|
|
66
|
+
handleTabSwitch("received");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
if (sessionsLoading) {
|
|
71
|
+
return (_jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: _jsx(Spinner, { label: "Loading sessions..." }) }));
|
|
72
|
+
}
|
|
73
|
+
if (stage === "select-session") {
|
|
74
|
+
if (sessions.length === 0) {
|
|
75
|
+
return (_jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: _jsx(Text, { dimColor: true, children: "No sessions yet. Create one first." }) }));
|
|
76
|
+
}
|
|
77
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, children: "Coworker Mail" }), _jsx(Text, { dimColor: true, children: "Select a session:" }), _jsx(Select, { options: sessions.map((s) => ({ label: s.name, value: s.name })), onChange: loadMessages })] }));
|
|
78
|
+
}
|
|
79
|
+
if (stage === "loading") {
|
|
80
|
+
return (_jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: _jsx(Spinner, { label: `Loading ${viewTab} messages...` }) }));
|
|
81
|
+
}
|
|
82
|
+
if (stage === "error") {
|
|
83
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, children: "Coworker Mail" }), _jsxs(Text, { color: "red", children: ["Error: ", error] })] }));
|
|
84
|
+
}
|
|
85
|
+
const renderMessages = () => {
|
|
86
|
+
if (messages.length === 0) {
|
|
87
|
+
return (_jsx(Box, { height: contentHeight - 6, alignItems: "center", justifyContent: "center", children: _jsxs(Text, { dimColor: true, children: ["No ", viewTab, " messages."] }) }));
|
|
88
|
+
}
|
|
89
|
+
const lines = [];
|
|
90
|
+
const maxNameLen = Math.max(...messages.map((m) => m.from_name.length));
|
|
91
|
+
for (const msg of messages) {
|
|
92
|
+
const timestamp = new Date(msg.created_at).toLocaleString();
|
|
93
|
+
lines.push({ text: `─`, color: "gray" });
|
|
94
|
+
lines.push({ text: `${msg.from_name.padEnd(maxNameLen)} → ${msg.to_name}`, color: "cyan" });
|
|
95
|
+
lines.push({ text: `${timestamp}`, color: "gray" });
|
|
96
|
+
lines.push({ text: msg.read ? "" : " [unread]" });
|
|
97
|
+
lines.push({ text: "" });
|
|
98
|
+
for (const line of msg.body.split("\n")) {
|
|
99
|
+
lines.push({ text: ` ${line}` });
|
|
100
|
+
}
|
|
101
|
+
lines.push({ text: "" });
|
|
102
|
+
}
|
|
103
|
+
const viewHeight = contentHeight - 6;
|
|
104
|
+
const maxOffset = Math.max(0, lines.length - viewHeight);
|
|
105
|
+
const clampedOffset = Math.min(scrollOffset, maxOffset);
|
|
106
|
+
const visible = lines.slice(clampedOffset, clampedOffset + viewHeight);
|
|
107
|
+
return (_jsx(Box, { flexDirection: "column", height: viewHeight, overflow: "hidden", children: visible.map((line, i) => (_jsx(Text, { color: line.color, children: line.text }, i))) }));
|
|
108
|
+
};
|
|
109
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsxs(Box, { gap: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Coworker Mail" }), _jsx(Text, { color: "cyan", children: selectedName }), _jsxs(Text, { dimColor: true, children: ["(", messages.length, " ", viewTab, " messages)"] })] }), _jsxs(Box, { gap: 2, marginBottom: 1, children: [viewTab === "received" ? (_jsx(Text, { bold: true, color: "green", children: "[Received]" })) : (_jsx(Text, { dimColor: true, children: "Received (press r)" })), viewTab === "sent" ? (_jsx(Text, { bold: true, color: "yellow", children: "[Sent]" })) : (_jsx(Text, { dimColor: true, children: "Sent (press s)" }))] }), renderMessages()] }));
|
|
110
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface SendMessageProps {
|
|
2
|
+
serverUrl: string;
|
|
3
|
+
password: string;
|
|
4
|
+
onBack: () => void;
|
|
5
|
+
contentHeight: number;
|
|
6
|
+
initialRecipient?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function SendMessage({ serverUrl, password, onBack, contentHeight, initialRecipient }: SendMessageProps): import("react/jsx-runtime").JSX.Element | null;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
|
+
import { Spinner, TextInput } from "@inkjs/ui";
|
|
5
|
+
import { ItemSelector } from "./ItemSelector.js";
|
|
6
|
+
import { useApi, useAsyncState } from "../hooks/useApi.js";
|
|
7
|
+
export function SendMessage({ serverUrl, password, onBack, contentHeight, initialRecipient }) {
|
|
8
|
+
const { listSessions, sendMailMessage, getConfig } = useApi(serverUrl, password);
|
|
9
|
+
const { run: runList } = useAsyncState();
|
|
10
|
+
const [sessions, setSessions] = useState([]);
|
|
11
|
+
const [sessionsLoading, setSessionsLoading] = useState(!initialRecipient);
|
|
12
|
+
const [stage, setStage] = useState(initialRecipient ? "enter-body" : "select-recipients");
|
|
13
|
+
const [recipients, setRecipients] = useState(initialRecipient ? [initialRecipient] : []);
|
|
14
|
+
const [messageBody, setMessageBody] = useState("");
|
|
15
|
+
const [error, setError] = useState(null);
|
|
16
|
+
const [submitted, setSubmitted] = useState(false);
|
|
17
|
+
const [senderName, setSenderName] = useState("Human");
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
getConfig().then((config) => {
|
|
20
|
+
setSenderName(config.human_name ?? "Human");
|
|
21
|
+
});
|
|
22
|
+
}, [getConfig]);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (initialRecipient)
|
|
25
|
+
return; // no need to load sessions list when replying
|
|
26
|
+
runList(listSessions).then((rows) => {
|
|
27
|
+
setSessions(rows ?? []);
|
|
28
|
+
setSessionsLoading(false);
|
|
29
|
+
});
|
|
30
|
+
}, []);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (stage === "done" || stage === "error") {
|
|
33
|
+
const timer = setTimeout(onBack, 1500);
|
|
34
|
+
return () => clearTimeout(timer);
|
|
35
|
+
}
|
|
36
|
+
}, [stage, onBack]);
|
|
37
|
+
const handleRecipientSelect = (value) => {
|
|
38
|
+
setRecipients([value]);
|
|
39
|
+
setStage("enter-body");
|
|
40
|
+
};
|
|
41
|
+
const handleBodySubmit = async (body) => {
|
|
42
|
+
const trimmed = body.trim();
|
|
43
|
+
if (!trimmed || recipients.length === 0)
|
|
44
|
+
return;
|
|
45
|
+
setSubmitted(true);
|
|
46
|
+
setMessageBody(trimmed);
|
|
47
|
+
setStage("sending");
|
|
48
|
+
try {
|
|
49
|
+
await sendMailMessage(senderName, recipients, trimmed);
|
|
50
|
+
setStage("done");
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
54
|
+
setStage("error");
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
if (sessionsLoading) {
|
|
58
|
+
return (_jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: _jsx(Spinner, { label: "Loading sessions..." }) }));
|
|
59
|
+
}
|
|
60
|
+
if (stage === "select-recipients") {
|
|
61
|
+
if (sessions.length === 0) {
|
|
62
|
+
return (_jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: _jsx(Text, { dimColor: true, children: "No sessions yet. Create one first." }) }));
|
|
63
|
+
}
|
|
64
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, children: "Send Message" }), _jsx(Text, { dimColor: true, children: "Select a recipient agent:" }), _jsx(ItemSelector, { items: sessions.map((s) => s.name), onSelect: handleRecipientSelect, onCancel: onBack })] }));
|
|
65
|
+
}
|
|
66
|
+
if (stage === "enter-body" && !submitted) {
|
|
67
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, children: "Send Message" }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { children: "From: " }), _jsx(Text, { color: "cyan", children: senderName })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { children: "To: " }), _jsx(Text, { color: "cyan", children: recipients[0] })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { children: "Body: " }), _jsx(TextInput, { placeholder: "Type your message...", onSubmit: handleBodySubmit })] })] }));
|
|
68
|
+
}
|
|
69
|
+
if (stage === "sending") {
|
|
70
|
+
return (_jsx(Spinner, { label: "Sending message..." }));
|
|
71
|
+
}
|
|
72
|
+
if (stage === "done") {
|
|
73
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, children: "Send Message" }), _jsx(Text, { color: "green", children: "Message sent!" }), _jsx(Text, { dimColor: true, children: "Returning to menu..." })] }));
|
|
74
|
+
}
|
|
75
|
+
if (stage === "error") {
|
|
76
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, children: "Send Message" }), _jsxs(Text, { color: "red", children: ["Error: ", error] }), _jsx(Text, { dimColor: true, children: "Returning to menu..." })] }));
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
@@ -1,52 +1,413 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState, useCallback } from "react";
|
|
3
3
|
import { Box, Text, useInput } from "ink";
|
|
4
|
-
import { Spinner } from "@inkjs/ui";
|
|
4
|
+
import { TextInput, Spinner, ConfirmInput } from "@inkjs/ui";
|
|
5
5
|
import { useApi, useAsyncState } from "../hooks/useApi.js";
|
|
6
6
|
const MASKED_CODE = "••••••••-••••-••••-••••-••••••••••••";
|
|
7
|
+
function TailView({ serverUrl, password, sessionName, contentHeight, onClose }) {
|
|
8
|
+
const { getMessages } = useApi(serverUrl, password);
|
|
9
|
+
const [messages, setMessages] = useState([]);
|
|
10
|
+
const [loading, setLoading] = useState(true);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
getMessages(sessionName, 50)
|
|
15
|
+
.then((msgs) => { setMessages(msgs); setLoading(false); })
|
|
16
|
+
.catch((err) => { setError(err instanceof Error ? err.message : String(err)); setLoading(false); });
|
|
17
|
+
}, [sessionName]);
|
|
18
|
+
useInput((_input, key) => {
|
|
19
|
+
if (key.escape) {
|
|
20
|
+
onClose();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (key.upArrow)
|
|
24
|
+
setScrollOffset((o) => Math.max(0, o - 1));
|
|
25
|
+
if (key.downArrow)
|
|
26
|
+
setScrollOffset((o) => o + 1);
|
|
27
|
+
});
|
|
28
|
+
if (loading) {
|
|
29
|
+
return (_jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: _jsx(Spinner, { label: `Fetching messages for "${sessionName}"...` }) }));
|
|
30
|
+
}
|
|
31
|
+
if (error) {
|
|
32
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Text, { bold: true, children: ["Messages \u2014 ", _jsx(Text, { color: "cyan", children: sessionName })] }), _jsxs(Text, { color: "red", children: ["Error: ", error] }), _jsx(Text, { dimColor: true, children: "Esc to go back" })] }));
|
|
33
|
+
}
|
|
34
|
+
const messageLines = [];
|
|
35
|
+
for (const msg of messages) {
|
|
36
|
+
for (const part of msg.parts) {
|
|
37
|
+
const lines = part.text.split("\n");
|
|
38
|
+
for (let i = 0; i < lines.length; i++) {
|
|
39
|
+
messageLines.push({ role: msg.role, text: i === 0 ? lines[i] : ` ${lines[i]}` });
|
|
40
|
+
}
|
|
41
|
+
messageLines.push({ role: msg.role, text: "" });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const viewHeight = contentHeight - 3;
|
|
45
|
+
const maxOffset = Math.max(0, messageLines.length - viewHeight);
|
|
46
|
+
const clampedOffset = Math.min(scrollOffset, maxOffset);
|
|
47
|
+
const visible = messageLines.slice(clampedOffset, clampedOffset + viewHeight);
|
|
48
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsxs(Box, { gap: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Messages" }), _jsx(Text, { color: "cyan", children: sessionName }), _jsxs(Text, { dimColor: true, children: ["(", messages.length, " messages)"] })] }), _jsx(Box, { flexDirection: "column", height: viewHeight, overflow: "hidden", children: visible.length === 0 ? (_jsx(Text, { dimColor: true, children: "No messages in this session yet." })) : (visible.map((line, i) => (_jsx(Box, { children: line.role === "user" ? (_jsx(Text, { color: "green", children: line.text })) : (_jsx(Text, { children: line.text })) }, i)))) }), messageLines.length > viewHeight && (_jsxs(Text, { dimColor: true, children: ["[", clampedOffset + 1, "\u2013", Math.min(clampedOffset + viewHeight, messageLines.length), "/", messageLines.length, " lines] \u2191\u2193 scroll \u00B7 Esc back"] }))] }));
|
|
49
|
+
}
|
|
50
|
+
function InjectView({ serverUrl, password, sessionName, contentHeight, onClose }) {
|
|
51
|
+
const { injectText } = useApi(serverUrl, password);
|
|
52
|
+
const [stage, setStage] = useState("input");
|
|
53
|
+
const [error, setError] = useState(null);
|
|
54
|
+
const [submitted, setSubmitted] = useState(false);
|
|
55
|
+
useInput((_input, key) => {
|
|
56
|
+
if (key.escape && stage !== "submitting") {
|
|
57
|
+
onClose();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if ((stage === "done" || stage === "error") && key.escape) {
|
|
61
|
+
onClose();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
// Auto-close after success
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (stage === "done") {
|
|
68
|
+
const t = setTimeout(onClose, 1500);
|
|
69
|
+
return () => clearTimeout(t);
|
|
70
|
+
}
|
|
71
|
+
}, [stage, onClose]);
|
|
72
|
+
const handleSubmit = async (text) => {
|
|
73
|
+
const trimmed = text.trim();
|
|
74
|
+
if (!trimmed)
|
|
75
|
+
return;
|
|
76
|
+
setSubmitted(true);
|
|
77
|
+
setStage("submitting");
|
|
78
|
+
try {
|
|
79
|
+
await injectText(sessionName, trimmed);
|
|
80
|
+
setStage("done");
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
84
|
+
setStage("error");
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { bold: true, children: "Inject Text" }), _jsx(Text, { color: "cyan", children: sessionName })] }), stage === "input" && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { children: "Text: " }), !submitted && (_jsx(TextInput, { placeholder: "Type your message...", onSubmit: (v) => void handleSubmit(v) }))] })), stage === "submitting" && _jsx(Spinner, { label: `Injecting into "${sessionName}"...` }), stage === "done" && (_jsx(Text, { color: "green", children: "Injected. Returning..." })), stage === "error" && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "red", children: ["Error: ", error] }), _jsx(Text, { dimColor: true, children: "Esc to go back" })] }))] }));
|
|
88
|
+
}
|
|
89
|
+
function CoworkerMailView({ serverUrl, password, sessionName, contentHeight, onClose }) {
|
|
90
|
+
const { getMailMessages } = useApi(serverUrl, password);
|
|
91
|
+
const [tab, setTab] = useState("received");
|
|
92
|
+
const [messages, setMessages] = useState([]);
|
|
93
|
+
const [loading, setLoading] = useState(true);
|
|
94
|
+
const [error, setError] = useState(null);
|
|
95
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
96
|
+
const loadTab = useCallback(async (t) => {
|
|
97
|
+
setLoading(true);
|
|
98
|
+
setScrollOffset(0);
|
|
99
|
+
try {
|
|
100
|
+
const msgs = await getMailMessages(sessionName, { sent: t === "sent" });
|
|
101
|
+
setMessages(msgs);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
setLoading(false);
|
|
108
|
+
}
|
|
109
|
+
}, [sessionName]);
|
|
110
|
+
useEffect(() => { void loadTab("received"); }, [loadTab]);
|
|
111
|
+
useInput((input, key) => {
|
|
112
|
+
if (key.escape) {
|
|
113
|
+
onClose();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!loading) {
|
|
117
|
+
if (key.upArrow)
|
|
118
|
+
setScrollOffset((o) => Math.max(0, o - 1));
|
|
119
|
+
if (key.downArrow)
|
|
120
|
+
setScrollOffset((o) => o + 1);
|
|
121
|
+
if (input === "r" && tab !== "received") {
|
|
122
|
+
setTab("received");
|
|
123
|
+
void loadTab("received");
|
|
124
|
+
}
|
|
125
|
+
if (input === "s" && tab !== "sent") {
|
|
126
|
+
setTab("sent");
|
|
127
|
+
void loadTab("sent");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
const renderMessages = () => {
|
|
132
|
+
if (loading)
|
|
133
|
+
return _jsx(Spinner, { label: "Loading..." });
|
|
134
|
+
if (error)
|
|
135
|
+
return _jsxs(Text, { color: "red", children: ["Error: ", error] });
|
|
136
|
+
if (messages.length === 0)
|
|
137
|
+
return _jsxs(Text, { dimColor: true, children: ["No ", tab, " messages."] });
|
|
138
|
+
const lines = [];
|
|
139
|
+
const maxNameLen = Math.max(...messages.map((m) => m.from_name.length));
|
|
140
|
+
for (const msg of messages) {
|
|
141
|
+
const timestamp = new Date(msg.created_at).toLocaleString();
|
|
142
|
+
lines.push({ text: "─", color: "gray" });
|
|
143
|
+
lines.push({ text: `${msg.from_name.padEnd(maxNameLen)} → ${msg.to_name}`, color: "cyan" });
|
|
144
|
+
lines.push({ text: timestamp, color: "gray" });
|
|
145
|
+
if (!msg.read)
|
|
146
|
+
lines.push({ text: " [unread]", color: "yellow" });
|
|
147
|
+
lines.push({ text: "" });
|
|
148
|
+
for (const line of msg.body.split("\n")) {
|
|
149
|
+
lines.push({ text: ` ${line}` });
|
|
150
|
+
}
|
|
151
|
+
lines.push({ text: "" });
|
|
152
|
+
}
|
|
153
|
+
const viewHeight = contentHeight - 7;
|
|
154
|
+
const maxOffset = Math.max(0, lines.length - viewHeight);
|
|
155
|
+
const clamped = Math.min(scrollOffset, maxOffset);
|
|
156
|
+
const visible = lines.slice(clamped, clamped + viewHeight);
|
|
157
|
+
return (_jsx(Box, { flexDirection: "column", height: viewHeight, overflow: "hidden", children: visible.map((line, i) => (_jsx(Text, { color: line.color, children: line.text }, i))) }));
|
|
158
|
+
};
|
|
159
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsxs(Box, { gap: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Coworker Mail" }), _jsx(Text, { color: "cyan", children: sessionName }), _jsxs(Text, { dimColor: true, children: ["(", messages.length, " ", tab, ")"] })] }), _jsxs(Box, { gap: 2, marginBottom: 1, children: [tab === "received"
|
|
160
|
+
? _jsx(Text, { bold: true, color: "green", children: "[Received]" })
|
|
161
|
+
: _jsx(Text, { dimColor: true, children: "Received (r)" }), tab === "sent"
|
|
162
|
+
? _jsx(Text, { bold: true, color: "yellow", children: "[Sent]" })
|
|
163
|
+
: _jsx(Text, { dimColor: true, children: "Sent (s)" })] }), renderMessages()] }));
|
|
164
|
+
}
|
|
165
|
+
// ─── Main component ──────────────────────────────────────────────────────────
|
|
7
166
|
export function SessionList({ serverUrl, password, contentHeight }) {
|
|
8
|
-
const { listSessions } = useApi(serverUrl, password);
|
|
9
|
-
const { data: sessions, loading, error, run } = useAsyncState();
|
|
167
|
+
const { listSessions, createSession, deleteSession, regenerateCode, getModes } = useApi(serverUrl, password);
|
|
168
|
+
const { data: sessions, loading, error: loadError, run } = useAsyncState();
|
|
10
169
|
const [cursor, setCursor] = useState(0);
|
|
11
170
|
const [revealedRows, setRevealedRows] = useState(new Set());
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
171
|
+
const [mode, setMode] = useState("browse");
|
|
172
|
+
const [subView, setSubView] = useState(null);
|
|
173
|
+
const [actionError, setActionError] = useState(null);
|
|
174
|
+
const [actionMsg, setActionMsg] = useState(null);
|
|
175
|
+
const [availableModes, setAvailableModes] = useState([]);
|
|
176
|
+
const [pendingMode, setPendingMode] = useState(null);
|
|
177
|
+
const [modeCursor, setModeCursor] = useState(0);
|
|
178
|
+
const reload = () => void run(listSessions);
|
|
179
|
+
useEffect(() => { reload(); }, []);
|
|
15
180
|
const rows = sessions ?? [];
|
|
181
|
+
// Clamp cursor when list shrinks
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
if (rows.length > 0)
|
|
184
|
+
setCursor((c) => Math.min(c, rows.length - 1));
|
|
185
|
+
}, [rows.length]);
|
|
16
186
|
useInput((input, key) => {
|
|
17
|
-
|
|
187
|
+
// Sub-views own Esc themselves; pass nothing else through
|
|
188
|
+
if (subView !== null)
|
|
18
189
|
return;
|
|
19
|
-
if (
|
|
20
|
-
|
|
190
|
+
if (loading)
|
|
191
|
+
return;
|
|
192
|
+
if (mode === "browse") {
|
|
193
|
+
if (key.upArrow)
|
|
194
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
195
|
+
if (key.downArrow)
|
|
196
|
+
setCursor((c) => Math.min(rows.length - 1, c + 1));
|
|
197
|
+
if (input === "r" && rows.length > 0) {
|
|
198
|
+
const id = rows[cursor]?.id;
|
|
199
|
+
if (id == null)
|
|
200
|
+
return;
|
|
201
|
+
setRevealedRows((prev) => {
|
|
202
|
+
const next = new Set(prev);
|
|
203
|
+
next.has(id) ? next.delete(id) : next.add(id);
|
|
204
|
+
return next;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
if (input === "c") {
|
|
208
|
+
setActionError(null);
|
|
209
|
+
setActionMsg(null);
|
|
210
|
+
setPendingMode(null);
|
|
211
|
+
setModeCursor(0);
|
|
212
|
+
setMode("creating-loading");
|
|
213
|
+
getModes().then((modes) => {
|
|
214
|
+
const safeMode = Array.isArray(modes) ? modes : [];
|
|
215
|
+
setAvailableModes(safeMode);
|
|
216
|
+
setMode(safeMode.length > 0 ? "creating-pick-mode" : "creating-name");
|
|
217
|
+
}).catch(() => {
|
|
218
|
+
setAvailableModes([]);
|
|
219
|
+
setMode("creating-name");
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (input === "d" && rows.length > 0) {
|
|
223
|
+
setActionError(null);
|
|
224
|
+
setActionMsg(null);
|
|
225
|
+
setMode("confirm-delete");
|
|
226
|
+
}
|
|
227
|
+
if (input === "g" && rows.length > 0) {
|
|
228
|
+
setActionError(null);
|
|
229
|
+
setActionMsg(null);
|
|
230
|
+
setMode("confirm-regen");
|
|
231
|
+
}
|
|
232
|
+
if (rows.length > 0) {
|
|
233
|
+
if (input === "t")
|
|
234
|
+
setSubView("tail");
|
|
235
|
+
if (input === "i")
|
|
236
|
+
setSubView("inject");
|
|
237
|
+
if (input === "m")
|
|
238
|
+
setSubView("coworker-mail");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (mode === "creating-pick-mode") {
|
|
242
|
+
// +1 for the "no mode" option at index 0
|
|
243
|
+
const total = availableModes.length + 1;
|
|
244
|
+
if (key.upArrow)
|
|
245
|
+
setModeCursor((c) => (c - 1 + total) % total);
|
|
246
|
+
if (key.downArrow)
|
|
247
|
+
setModeCursor((c) => (c + 1) % total);
|
|
248
|
+
if (key.return) {
|
|
249
|
+
const selected = modeCursor === 0 ? null : (availableModes[modeCursor - 1]?.name ?? null);
|
|
250
|
+
setPendingMode(selected);
|
|
251
|
+
setMode("creating-name");
|
|
252
|
+
}
|
|
253
|
+
if (key.escape) {
|
|
254
|
+
setMode("browse");
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (mode === "creating-name" && key.escape) {
|
|
259
|
+
setMode("browse");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
// Dismiss feedback states with any key
|
|
263
|
+
if (mode === "create-done" || mode === "create-error" || mode === "delete-done" || mode === "delete-error") {
|
|
264
|
+
setMode("browse");
|
|
265
|
+
setActionError(null);
|
|
266
|
+
setActionMsg(null);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
const handleCreate = async (name) => {
|
|
270
|
+
const trimmed = name.trim();
|
|
271
|
+
if (!trimmed)
|
|
272
|
+
return;
|
|
273
|
+
setMode("creating-busy");
|
|
274
|
+
try {
|
|
275
|
+
await createSession(trimmed, pendingMode ?? undefined);
|
|
276
|
+
const modeNote = pendingMode ? ` [${pendingMode}]` : "";
|
|
277
|
+
setActionMsg(`Coworker "${trimmed}"${modeNote} created.`);
|
|
278
|
+
setMode("create-done");
|
|
279
|
+
reload();
|
|
21
280
|
}
|
|
22
|
-
|
|
23
|
-
|
|
281
|
+
catch (err) {
|
|
282
|
+
setActionError(err instanceof Error ? err.message : String(err));
|
|
283
|
+
setMode("create-error");
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const handleConfirmDelete = async (confirmed) => {
|
|
287
|
+
if (!confirmed) {
|
|
288
|
+
setMode("browse");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const target = rows[cursor];
|
|
292
|
+
if (!target) {
|
|
293
|
+
setMode("browse");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
setMode("deleting");
|
|
297
|
+
try {
|
|
298
|
+
await deleteSession(target.name);
|
|
299
|
+
setActionMsg(`Coworker "${target.name}" deleted.`);
|
|
300
|
+
setMode("delete-done");
|
|
301
|
+
reload();
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
setActionError(err instanceof Error ? err.message : String(err));
|
|
305
|
+
setMode("delete-error");
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
const handleConfirmRegen = async (confirmed) => {
|
|
309
|
+
if (!confirmed) {
|
|
310
|
+
setMode("browse");
|
|
311
|
+
return;
|
|
24
312
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
313
|
+
const target = rows[cursor];
|
|
314
|
+
if (!target) {
|
|
315
|
+
setMode("browse");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
setMode("regenerating");
|
|
319
|
+
try {
|
|
320
|
+
await regenerateCode(target.name);
|
|
321
|
+
setActionMsg(`Agent code regenerated for "${target.name}".`);
|
|
29
322
|
setRevealedRows((prev) => {
|
|
30
323
|
const next = new Set(prev);
|
|
31
|
-
if (
|
|
32
|
-
next.
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
next.add(id);
|
|
36
|
-
}
|
|
324
|
+
if (target.id != null)
|
|
325
|
+
next.add(target.id);
|
|
37
326
|
return next;
|
|
38
327
|
});
|
|
328
|
+
setMode("create-done");
|
|
329
|
+
reload();
|
|
39
330
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
331
|
+
catch (err) {
|
|
332
|
+
setActionError(err instanceof Error ? err.message : String(err));
|
|
333
|
+
setMode("create-error");
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
// ── Full-screen create flow ───────────────────────────────────────────────
|
|
337
|
+
if (mode === "creating-loading") {
|
|
338
|
+
return (_jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: _jsx(Spinner, { label: "Loading agent configs..." }) }));
|
|
43
339
|
}
|
|
44
|
-
if (
|
|
45
|
-
|
|
340
|
+
if (mode === "creating-pick-mode") {
|
|
341
|
+
const modeOptions = [
|
|
342
|
+
{ label: "No mode (default)", description: "" },
|
|
343
|
+
...availableModes.map((m) => ({ label: m.name, description: m.description })),
|
|
344
|
+
];
|
|
345
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsxs(Box, { gap: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Create Coworker" }), _jsx(Text, { dimColor: true, children: "Select a mode" })] }), _jsx(Box, { flexDirection: "column", children: modeOptions.map((opt, i) => {
|
|
346
|
+
const sel = i === modeCursor;
|
|
347
|
+
return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: sel ? "cyan" : undefined, bold: sel, children: sel ? "▶" : " " }), _jsx(Text, { color: sel ? "cyan" : undefined, bold: sel, children: opt.label }), opt.description ? _jsxs(Text, { dimColor: true, children: ["\u2014 ", opt.description] }) : null] }, i));
|
|
348
|
+
}) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate \u00B7 Enter select \u00B7 Esc cancel" }) })] }));
|
|
349
|
+
}
|
|
350
|
+
if (mode === "creating-name") {
|
|
351
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { bold: true, children: "Create Coworker" }), pendingMode
|
|
352
|
+
? _jsxs(Text, { color: "yellow", children: ["[", pendingMode, "]"] })
|
|
353
|
+
: _jsx(Text, { dimColor: true, children: "[no mode]" })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { children: "Name: " }), _jsx(TextInput, { placeholder: "e.g. alice", onSubmit: (v) => void handleCreate(v) })] }), _jsx(Text, { dimColor: true, children: "Enter to create \u00B7 Esc cancel" })] }));
|
|
354
|
+
}
|
|
355
|
+
if (mode === "creating-busy") {
|
|
356
|
+
return (_jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: _jsx(Spinner, { label: "Creating coworker..." }) }));
|
|
357
|
+
}
|
|
358
|
+
if (mode === "create-done") {
|
|
359
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, children: [_jsx(Text, { color: "green", children: actionMsg }), _jsx(Text, { dimColor: true, children: "Press any key to continue" })] }));
|
|
360
|
+
}
|
|
361
|
+
if (mode === "create-error") {
|
|
362
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, children: [_jsxs(Text, { color: "red", children: ["Error: ", actionError] }), _jsx(Text, { dimColor: true, children: "Press any key to continue" })] }));
|
|
363
|
+
}
|
|
364
|
+
// ── Sub-view rendering ────────────────────────────────────────────────────
|
|
365
|
+
const activeSession = rows[cursor];
|
|
366
|
+
if (subView !== null && activeSession) {
|
|
367
|
+
const closeSubView = () => setSubView(null);
|
|
368
|
+
if (subView === "tail") {
|
|
369
|
+
return (_jsx(TailView, { serverUrl: serverUrl, password: password, sessionName: activeSession.name, contentHeight: contentHeight, onClose: closeSubView }));
|
|
370
|
+
}
|
|
371
|
+
if (subView === "inject") {
|
|
372
|
+
return (_jsx(InjectView, { serverUrl: serverUrl, password: password, sessionName: activeSession.name, contentHeight: contentHeight, onClose: closeSubView }));
|
|
373
|
+
}
|
|
374
|
+
if (subView === "coworker-mail") {
|
|
375
|
+
return (_jsx(CoworkerMailView, { serverUrl: serverUrl, password: password, sessionName: activeSession.name, contentHeight: contentHeight, onClose: closeSubView }));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// ── Initial load ──────────────────────────────────────────────────────────
|
|
379
|
+
if (loading && rows.length === 0) {
|
|
380
|
+
return (_jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: _jsx(Spinner, { label: "Loading coworkers..." }) }));
|
|
381
|
+
}
|
|
382
|
+
if (loadError) {
|
|
383
|
+
return (_jsxs(Box, { height: contentHeight, flexDirection: "column", gap: 1, children: [_jsx(Text, { color: "red", bold: true, children: "Error" }), _jsx(Text, { children: loadError })] }));
|
|
46
384
|
}
|
|
47
|
-
|
|
385
|
+
// ── Inline action panel ───────────────────────────────────────────────────
|
|
386
|
+
const renderActionPanel = () => {
|
|
387
|
+
const target = rows[cursor];
|
|
388
|
+
if (mode === "confirm-delete" && target)
|
|
389
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, color: "red", children: "Delete Coworker" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Delete ", _jsx(Text, { color: "yellow", bold: true, children: target.name }), "?", " ", _jsx(Text, { dimColor: true, children: "This also removes the OpenCode session." })] }) }), _jsx(Box, { marginTop: 1, children: _jsx(ConfirmInput, { defaultChoice: "cancel", onConfirm: () => void handleConfirmDelete(true), onCancel: () => void handleConfirmDelete(false) }) })] }));
|
|
390
|
+
if (mode === "deleting")
|
|
391
|
+
return (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1, marginBottom: 1, children: _jsx(Spinner, { label: `Deleting "${rows[cursor]?.name}"...` }) }));
|
|
392
|
+
if (mode === "delete-done")
|
|
393
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, marginBottom: 1, children: [_jsx(Text, { color: "green", children: actionMsg }), _jsx(Text, { dimColor: true, children: "Press any key to continue" })] }));
|
|
394
|
+
if (mode === "delete-error")
|
|
395
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1, marginBottom: 1, children: [_jsxs(Text, { color: "red", children: ["Error: ", actionError] }), _jsx(Text, { dimColor: true, children: "Press any key to continue" })] }));
|
|
396
|
+
if (mode === "confirm-regen" && target)
|
|
397
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "Regenerate Agent Code" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Regenerate code for ", _jsx(Text, { color: "cyan", bold: true, children: target.name }), "?", " ", _jsx(Text, { dimColor: true, children: "The old code will stop working immediately." })] }) }), _jsx(Box, { marginTop: 1, children: _jsx(ConfirmInput, { defaultChoice: "cancel", onConfirm: () => void handleConfirmRegen(true), onCancel: () => void handleConfirmRegen(false) }) })] }));
|
|
398
|
+
if (mode === "regenerating")
|
|
399
|
+
return (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginBottom: 1, children: _jsx(Spinner, { label: "Generating new agent code..." }) }));
|
|
400
|
+
return null;
|
|
401
|
+
};
|
|
402
|
+
// ── Coworker table ────────────────────────────────────────────────────────
|
|
403
|
+
const actionPanel = renderActionPanel();
|
|
404
|
+
const panelHeight = actionPanel ? 5 : 0;
|
|
405
|
+
const tableHeight = contentHeight - panelHeight - 3;
|
|
406
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsxs(Box, { gap: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Coworkers" }), _jsxs(Text, { dimColor: true, children: ["(", rows.length, ")"] }), loading && _jsx(Spinner, {})] }), actionPanel, rows.length === 0 ? (_jsx(Box, { height: tableHeight, alignItems: "center", justifyContent: "center", children: _jsx(Text, { dimColor: true, children: "No coworkers yet. Press c to create one." }) })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: " NAME".padEnd(18) }), _jsx(Text, { bold: true, color: "cyan", children: "MODE".padEnd(12) }), _jsx(Text, { bold: true, color: "cyan", children: "OPENCODE SESSION ID".padEnd(36) }), _jsx(Text, { bold: true, color: "cyan", children: "AGENT CODE" })] }), rows.map((s, i) => {
|
|
48
407
|
const selected = i === cursor;
|
|
49
408
|
const revealed = revealedRows.has(s.id);
|
|
50
|
-
return (_jsxs(Box, { gap: 2, children: [_jsxs(Box, { width: 18, children: [_jsx(Text, { color: selected ? "cyan" : undefined, children: selected ? "▶ " : " " }), _jsx(Text, { color: selected ? "cyan" : "green", bold: selected, children: s.name })] }), _jsx(Text, { dimColor: !selected, children: s.
|
|
51
|
-
|
|
409
|
+
return (_jsxs(Box, { gap: 2, children: [_jsxs(Box, { width: 18, children: [_jsx(Text, { color: selected ? "cyan" : undefined, children: selected ? "▶ " : " " }), _jsx(Text, { color: selected ? "cyan" : "green", bold: selected, children: s.name })] }), _jsx(Text, { color: selected ? "magenta" : undefined, dimColor: !selected && !s.mode, children: (s.mode ?? "—").padEnd(12) }), _jsx(Text, { dimColor: !selected, children: s.session_id.padEnd(36) }), revealed
|
|
410
|
+
? _jsx(Text, { color: "yellow", children: s.agent_code })
|
|
411
|
+
: _jsx(Text, { dimColor: true, children: MASKED_CODE })] }, s.id));
|
|
412
|
+
})] }))] }));
|
|
52
413
|
}
|