cue-console 0.1.17 → 0.1.18
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/package.json
CHANGED
|
@@ -12,9 +12,15 @@ import {
|
|
|
12
12
|
type SetStateAction,
|
|
13
13
|
} from "react";
|
|
14
14
|
import { Button } from "@/components/ui/button";
|
|
15
|
+
import {
|
|
16
|
+
Dialog,
|
|
17
|
+
DialogContent,
|
|
18
|
+
DialogHeader,
|
|
19
|
+
DialogTitle,
|
|
20
|
+
} from "@/components/ui/dialog";
|
|
15
21
|
import { cn, getAgentEmoji } from "@/lib/utils";
|
|
16
22
|
import { setAgentDisplayName } from "@/lib/actions";
|
|
17
|
-
import { CornerUpLeft, GripVertical, Plus, Send, Trash2, X } from "lucide-react";
|
|
23
|
+
import { Bot, CornerUpLeft, GripVertical, Plus, Send, Trash2, X } from "lucide-react";
|
|
18
24
|
|
|
19
25
|
type MentionDraft = {
|
|
20
26
|
userId: string;
|
|
@@ -71,6 +77,8 @@ export function ChatComposer({
|
|
|
71
77
|
setImages,
|
|
72
78
|
setNotice,
|
|
73
79
|
setPreviewImage,
|
|
80
|
+
botEnabled,
|
|
81
|
+
onToggleBot,
|
|
74
82
|
handleSend,
|
|
75
83
|
enqueueCurrent,
|
|
76
84
|
queue,
|
|
@@ -113,6 +121,8 @@ export function ChatComposer({
|
|
|
113
121
|
setImages: Dispatch<SetStateAction<{ mime_type: string; base64_data: string; file_name?: string }[]>>;
|
|
114
122
|
setNotice: Dispatch<SetStateAction<string | null>>;
|
|
115
123
|
setPreviewImage: Dispatch<SetStateAction<{ mime_type: string; base64_data: string } | null>>;
|
|
124
|
+
botEnabled: boolean;
|
|
125
|
+
onToggleBot: () => Promise<boolean>;
|
|
116
126
|
handleSend: () => void | Promise<void>;
|
|
117
127
|
enqueueCurrent: () => void;
|
|
118
128
|
queue: QueuedMessage[];
|
|
@@ -152,6 +162,8 @@ export function ChatComposer({
|
|
|
152
162
|
}, [onBack]);
|
|
153
163
|
|
|
154
164
|
const [dragIndex, setDragIndex] = useState<number | null>(null);
|
|
165
|
+
const [botToggling, setBotToggling] = useState(false);
|
|
166
|
+
const [botConfirmOpen, setBotConfirmOpen] = useState(false);
|
|
155
167
|
const isComposingRef = useRef(false);
|
|
156
168
|
|
|
157
169
|
const submitOrQueue = () => {
|
|
@@ -251,6 +263,54 @@ export function ChatComposer({
|
|
|
251
263
|
</div>
|
|
252
264
|
)}
|
|
253
265
|
|
|
266
|
+
<Dialog open={botConfirmOpen} onOpenChange={setBotConfirmOpen}>
|
|
267
|
+
<DialogContent className="sm:max-w-110">
|
|
268
|
+
<DialogHeader>
|
|
269
|
+
<DialogTitle>Enable bot mode?</DialogTitle>
|
|
270
|
+
</DialogHeader>
|
|
271
|
+
|
|
272
|
+
<div className="text-sm text-muted-foreground space-y-3">
|
|
273
|
+
<p>
|
|
274
|
+
Bot mode will automatically reply to <span className="text-foreground font-medium">cue</span> requests
|
|
275
|
+
in this conversation.
|
|
276
|
+
</p>
|
|
277
|
+
<ul className="list-disc pl-5 space-y-1">
|
|
278
|
+
<li>Only affects the current {type === "group" ? "group" : "agent"} conversation.</li>
|
|
279
|
+
<li>May immediately reply to currently pending cue requests.</li>
|
|
280
|
+
<li>Does not reply to pause confirmations.</li>
|
|
281
|
+
<li>You can turn it off anytime.</li>
|
|
282
|
+
</ul>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
<div className="mt-4 flex items-center justify-end gap-2">
|
|
286
|
+
<Button
|
|
287
|
+
type="button"
|
|
288
|
+
variant="outline"
|
|
289
|
+
disabled={botToggling}
|
|
290
|
+
onClick={() => setBotConfirmOpen(false)}
|
|
291
|
+
>
|
|
292
|
+
Cancel
|
|
293
|
+
</Button>
|
|
294
|
+
<Button
|
|
295
|
+
type="button"
|
|
296
|
+
disabled={botToggling}
|
|
297
|
+
onClick={async () => {
|
|
298
|
+
if (botToggling) return;
|
|
299
|
+
setBotToggling(true);
|
|
300
|
+
try {
|
|
301
|
+
await onToggleBot();
|
|
302
|
+
setBotConfirmOpen(false);
|
|
303
|
+
} finally {
|
|
304
|
+
setBotToggling(false);
|
|
305
|
+
}
|
|
306
|
+
}}
|
|
307
|
+
>
|
|
308
|
+
{botToggling ? "Enabling…" : "Enable"}
|
|
309
|
+
</Button>
|
|
310
|
+
</div>
|
|
311
|
+
</DialogContent>
|
|
312
|
+
</Dialog>
|
|
313
|
+
|
|
254
314
|
{/* Image Preview */}
|
|
255
315
|
{images.length > 0 && (
|
|
256
316
|
<div className="flex max-w-full gap-2 overflow-x-auto px-0.5 pt-0.5">
|
|
@@ -614,6 +674,48 @@ export function ChatComposer({
|
|
|
614
674
|
>
|
|
615
675
|
Queue
|
|
616
676
|
</Button>
|
|
677
|
+
|
|
678
|
+
<div className="relative group">
|
|
679
|
+
<Button
|
|
680
|
+
type="button"
|
|
681
|
+
variant="ghost"
|
|
682
|
+
size="icon"
|
|
683
|
+
disabled={busy || botToggling}
|
|
684
|
+
className={cn(
|
|
685
|
+
"relative h-9 w-9 rounded-2xl",
|
|
686
|
+
"hover:bg-white/40",
|
|
687
|
+
botEnabled ? "text-primary" : "text-muted-foreground",
|
|
688
|
+
(busy || botToggling) && "opacity-60 cursor-not-allowed"
|
|
689
|
+
)}
|
|
690
|
+
onClick={async () => {
|
|
691
|
+
if (busy || botToggling) return;
|
|
692
|
+
if (!botEnabled) {
|
|
693
|
+
setBotConfirmOpen(true);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
setBotToggling(true);
|
|
697
|
+
try {
|
|
698
|
+
await onToggleBot();
|
|
699
|
+
} finally {
|
|
700
|
+
setBotToggling(false);
|
|
701
|
+
}
|
|
702
|
+
}}
|
|
703
|
+
aria-label={botEnabled ? "Stop bot" : "Start bot"}
|
|
704
|
+
title={botToggling ? "Turning…" : botEnabled ? "Stop bot" : "Start bot"}
|
|
705
|
+
>
|
|
706
|
+
{botEnabled && (
|
|
707
|
+
<span className="pointer-events-none absolute inset-0 rounded-xl">
|
|
708
|
+
<span className="absolute inset-0 rounded-2xl bg-primary/15 blur-md animate-pulse" />
|
|
709
|
+
</span>
|
|
710
|
+
)}
|
|
711
|
+
<Bot
|
|
712
|
+
className={cn(
|
|
713
|
+
"relative z-10 h-5 w-5",
|
|
714
|
+
botEnabled && "drop-shadow-[0_0_12px_rgba(99,102,241,0.45)]"
|
|
715
|
+
)}
|
|
716
|
+
/>
|
|
717
|
+
</Button>
|
|
718
|
+
</div>
|
|
617
719
|
</div>
|
|
618
720
|
|
|
619
721
|
<Button
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
submitResponse,
|
|
26
26
|
cancelRequest,
|
|
27
27
|
batchRespond,
|
|
28
|
+
processBotTick,
|
|
28
29
|
type CueRequest,
|
|
29
30
|
} from "@/lib/actions";
|
|
30
31
|
import { ChatComposer } from "@/components/chat-composer";
|
|
@@ -77,6 +78,12 @@ function ChatViewContent({ type, id, name, onBack }: ChatViewProps) {
|
|
|
77
78
|
const deferredInput = useDeferredValue(input);
|
|
78
79
|
const imagesRef = useRef(images);
|
|
79
80
|
|
|
81
|
+
const botStorageKey = useMemo(() => {
|
|
82
|
+
return `cue-console:botEnabled:${type}:${id}`;
|
|
83
|
+
}, [type, id]);
|
|
84
|
+
|
|
85
|
+
const [botEnabled, setBotEnabled] = useState(false);
|
|
86
|
+
|
|
80
87
|
const { soundEnabled, setSoundEnabled, playDing } = useAudioNotification();
|
|
81
88
|
|
|
82
89
|
const {
|
|
@@ -103,6 +110,17 @@ function ChatViewContent({ type, id, name, onBack }: ChatViewProps) {
|
|
|
103
110
|
|
|
104
111
|
const [composerPadPx, setComposerPadPx] = useState(36 * 4);
|
|
105
112
|
|
|
113
|
+
const botHolderIdRef = useRef<string>(
|
|
114
|
+
(() => {
|
|
115
|
+
try {
|
|
116
|
+
return globalThis.crypto?.randomUUID?.() || `bot-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
117
|
+
} catch {
|
|
118
|
+
return `bot-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
119
|
+
}
|
|
120
|
+
})()
|
|
121
|
+
);
|
|
122
|
+
const botTickBusyRef = useRef(false);
|
|
123
|
+
|
|
106
124
|
const nextCursorRef = useRef<string | null>(null);
|
|
107
125
|
const loadingMoreRef = useRef(false);
|
|
108
126
|
|
|
@@ -207,6 +225,85 @@ function ChatViewContent({ type, id, name, onBack }: ChatViewProps) {
|
|
|
207
225
|
setError,
|
|
208
226
|
});
|
|
209
227
|
|
|
228
|
+
const triggerBotTickOnce = useCallback(async () => {
|
|
229
|
+
if (document.visibilityState !== "visible") return;
|
|
230
|
+
if (botTickBusyRef.current) return;
|
|
231
|
+
botTickBusyRef.current = true;
|
|
232
|
+
try {
|
|
233
|
+
const res = await processBotTick({
|
|
234
|
+
holderId: botHolderIdRef.current,
|
|
235
|
+
convType: type,
|
|
236
|
+
convId: id,
|
|
237
|
+
limit: 80,
|
|
238
|
+
});
|
|
239
|
+
if (res.success && res.replied > 0) {
|
|
240
|
+
await refreshLatest();
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
} finally {
|
|
244
|
+
botTickBusyRef.current = false;
|
|
245
|
+
}
|
|
246
|
+
}, [id, refreshLatest, type]);
|
|
247
|
+
|
|
248
|
+
const toggleBot = useCallback(async (): Promise<boolean> => {
|
|
249
|
+
const prev = botEnabled;
|
|
250
|
+
const next = !prev;
|
|
251
|
+
setBotEnabled(next);
|
|
252
|
+
try {
|
|
253
|
+
window.localStorage.setItem(botStorageKey, next ? "1" : "0");
|
|
254
|
+
if (next) void triggerBotTickOnce();
|
|
255
|
+
return next;
|
|
256
|
+
} catch {
|
|
257
|
+
setBotEnabled(prev);
|
|
258
|
+
setNotice("Failed to toggle bot");
|
|
259
|
+
return prev;
|
|
260
|
+
}
|
|
261
|
+
}, [botEnabled, botStorageKey, setNotice, triggerBotTickOnce]);
|
|
262
|
+
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
try {
|
|
265
|
+
const raw = window.localStorage.getItem(botStorageKey);
|
|
266
|
+
setBotEnabled(raw === "1");
|
|
267
|
+
} catch {
|
|
268
|
+
setBotEnabled(false);
|
|
269
|
+
}
|
|
270
|
+
}, [botStorageKey]);
|
|
271
|
+
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
if (!botEnabled) return;
|
|
274
|
+
|
|
275
|
+
let cancelled = false;
|
|
276
|
+
|
|
277
|
+
const tick = async () => {
|
|
278
|
+
if (cancelled) return;
|
|
279
|
+
if (document.visibilityState !== "visible") return;
|
|
280
|
+
if (botTickBusyRef.current) return;
|
|
281
|
+
botTickBusyRef.current = true;
|
|
282
|
+
try {
|
|
283
|
+
const res = await processBotTick({
|
|
284
|
+
holderId: botHolderIdRef.current,
|
|
285
|
+
convType: type,
|
|
286
|
+
convId: id,
|
|
287
|
+
limit: 80,
|
|
288
|
+
});
|
|
289
|
+
if (cancelled) return;
|
|
290
|
+
if (res.success && res.replied > 0) {
|
|
291
|
+
await refreshLatest();
|
|
292
|
+
}
|
|
293
|
+
} catch {
|
|
294
|
+
} finally {
|
|
295
|
+
botTickBusyRef.current = false;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
void tick();
|
|
300
|
+
const interval = setInterval(() => void tick(), 2500);
|
|
301
|
+
return () => {
|
|
302
|
+
cancelled = true;
|
|
303
|
+
clearInterval(interval);
|
|
304
|
+
};
|
|
305
|
+
}, [botEnabled, id, refreshLatest, type]);
|
|
306
|
+
|
|
210
307
|
|
|
211
308
|
const handleTitleChange = async (newTitle: string) => {
|
|
212
309
|
if (type === "agent") {
|
|
@@ -666,6 +763,8 @@ function ChatViewContent({ type, id, name, onBack }: ChatViewProps) {
|
|
|
666
763
|
setImages={setImages}
|
|
667
764
|
setNotice={setNotice}
|
|
668
765
|
setPreviewImage={setPreviewImage}
|
|
766
|
+
botEnabled={botEnabled}
|
|
767
|
+
onToggleBot={toggleBot}
|
|
669
768
|
handleSend={send}
|
|
670
769
|
enqueueCurrent={enqueueCurrent}
|
|
671
770
|
queue={queue}
|
|
@@ -4,6 +4,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
4
4
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
5
5
|
import { Badge } from "@/components/ui/badge";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { DEFAULT_USER_CONFIG } from "@/lib/user-config";
|
|
7
9
|
import {
|
|
8
10
|
Dialog,
|
|
9
11
|
DialogContent,
|
|
@@ -49,7 +51,6 @@ import {
|
|
|
49
51
|
Settings,
|
|
50
52
|
X,
|
|
51
53
|
} from "lucide-react";
|
|
52
|
-
import { Input } from "@/components/ui/input";
|
|
53
54
|
import { ConfirmDialog } from "@/components/confirm-dialog";
|
|
54
55
|
|
|
55
56
|
function perfEnabled(): boolean {
|
|
@@ -118,6 +119,9 @@ export function ConversationList({
|
|
|
118
119
|
const [conversationModeDefault, setConversationModeDefault] = useState<"chat" | "agent">("agent");
|
|
119
120
|
const [chatModeAppendText, setChatModeAppendText] = useState("只做分析,不要对代码/文件做任何改动。");
|
|
120
121
|
const [pendingRequestTimeoutMs, setPendingRequestTimeoutMs] = useState("600000");
|
|
122
|
+
const [botModeReplyText, setBotModeReplyText] = useState(
|
|
123
|
+
"不要为这些“可逆的工程细节”反复向我确认,不能分阶段汇报,我需要你端到端执行完我的任务才能向我汇报,你不能问我要授权,因为我已经给你了全部授权,如果这些细节不影响我的目标那就按照你的推荐来。你有执行一切的权利。"
|
|
124
|
+
);
|
|
121
125
|
const [pinnedKeys, setPinnedKeys] = useState<string[]>([]);
|
|
122
126
|
|
|
123
127
|
useEffect(() => {
|
|
@@ -127,8 +131,14 @@ export function ConversationList({
|
|
|
127
131
|
setSoundEnabled(Boolean(cfg.sound_enabled));
|
|
128
132
|
const nextMode = cfg.conversation_mode_default === "chat" ? "chat" : "agent";
|
|
129
133
|
setConversationModeDefault(nextMode);
|
|
130
|
-
setChatModeAppendText(String(cfg.chat_mode_append_text ||
|
|
134
|
+
setChatModeAppendText(String(cfg.chat_mode_append_text || DEFAULT_USER_CONFIG.chat_mode_append_text));
|
|
131
135
|
setPendingRequestTimeoutMs(String(cfg.pending_request_timeout_ms ?? 600000));
|
|
136
|
+
setBotModeReplyText(
|
|
137
|
+
String(
|
|
138
|
+
(cfg as any).bot_mode_reply_text ||
|
|
139
|
+
DEFAULT_USER_CONFIG.bot_mode_reply_text
|
|
140
|
+
)
|
|
141
|
+
);
|
|
132
142
|
try {
|
|
133
143
|
window.localStorage.setItem("cue-console:conversationModeDefault", nextMode);
|
|
134
144
|
} catch {
|
|
@@ -1259,6 +1269,41 @@ export function ConversationList({
|
|
|
1259
1269
|
</Button>
|
|
1260
1270
|
</div>
|
|
1261
1271
|
</div>
|
|
1272
|
+
|
|
1273
|
+
<div className="mt-6">
|
|
1274
|
+
<div className="text-sm font-medium">Bot reply text</div>
|
|
1275
|
+
<div className="text-xs text-muted-foreground">
|
|
1276
|
+
Multi-line
|
|
1277
|
+
</div>
|
|
1278
|
+
<div className="mt-2 flex items-start justify-end gap-2">
|
|
1279
|
+
<textarea
|
|
1280
|
+
value={botModeReplyText}
|
|
1281
|
+
onChange={(e) => setBotModeReplyText(e.target.value)}
|
|
1282
|
+
placeholder="Bot reply"
|
|
1283
|
+
className="min-h-24 flex-1 min-w-0 resize-y rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
1284
|
+
/>
|
|
1285
|
+
<Button
|
|
1286
|
+
type="button"
|
|
1287
|
+
variant="secondary"
|
|
1288
|
+
size="sm"
|
|
1289
|
+
className="h-9 rounded-md px-3 text-xs"
|
|
1290
|
+
onClick={async () => {
|
|
1291
|
+
const next = botModeReplyText;
|
|
1292
|
+
try {
|
|
1293
|
+
await setUserConfig({ bot_mode_reply_text: next });
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1296
|
+
window.dispatchEvent(
|
|
1297
|
+
new CustomEvent("cue-console:configUpdated", {
|
|
1298
|
+
detail: { bot_mode_reply_text: next },
|
|
1299
|
+
})
|
|
1300
|
+
);
|
|
1301
|
+
}}
|
|
1302
|
+
>
|
|
1303
|
+
Save
|
|
1304
|
+
</Button>
|
|
1305
|
+
</div>
|
|
1306
|
+
</div>
|
|
1262
1307
|
</DialogContent>
|
|
1263
1308
|
</Dialog>
|
|
1264
1309
|
|
|
@@ -2,13 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
|
|
4
4
|
import { getUserConfig, type UserConfig } from "@/lib/actions";
|
|
5
|
-
|
|
6
|
-
const defaultConfig: UserConfig = {
|
|
7
|
-
sound_enabled: true,
|
|
8
|
-
conversation_mode_default: "agent",
|
|
9
|
-
chat_mode_append_text: "只做分析,不要对代码/文件做任何改动。",
|
|
10
|
-
pending_request_timeout_ms: 10 * 60 * 1000,
|
|
11
|
-
};
|
|
5
|
+
import { DEFAULT_USER_CONFIG } from "@/lib/user-config";
|
|
12
6
|
|
|
13
7
|
type ConfigContextValue = {
|
|
14
8
|
config: UserConfig;
|
|
@@ -25,7 +19,7 @@ export function useConfig() {
|
|
|
25
19
|
}
|
|
26
20
|
|
|
27
21
|
export function ConfigProvider({ children }: { children: ReactNode }) {
|
|
28
|
-
const [config, setConfig] = useState<UserConfig>(
|
|
22
|
+
const [config, setConfig] = useState<UserConfig>(DEFAULT_USER_CONFIG);
|
|
29
23
|
|
|
30
24
|
useEffect(() => {
|
|
31
25
|
let cancelled = false;
|
|
@@ -69,7 +63,26 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
|
|
|
69
63
|
);
|
|
70
64
|
} catch {
|
|
71
65
|
}
|
|
72
|
-
|
|
66
|
+
try {
|
|
67
|
+
window.localStorage.setItem(
|
|
68
|
+
"cue-console:bot_mode_enabled",
|
|
69
|
+
config.bot_mode_enabled ? "1" : "0"
|
|
70
|
+
);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
window.localStorage.setItem(
|
|
75
|
+
"cue-console:bot_mode_reply_text",
|
|
76
|
+
String(config.bot_mode_reply_text || "")
|
|
77
|
+
);
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
}, [
|
|
81
|
+
config.pending_request_timeout_ms,
|
|
82
|
+
config.chat_mode_append_text,
|
|
83
|
+
config.bot_mode_enabled,
|
|
84
|
+
config.bot_mode_reply_text,
|
|
85
|
+
]);
|
|
73
86
|
|
|
74
87
|
const value = useMemo<ConfigContextValue>(() => ({ config }), [config]);
|
|
75
88
|
|
package/src/lib/actions.ts
CHANGED
|
@@ -56,12 +56,15 @@ export type { Group, UserResponse, ImageContent, ConversationItem } from "./type
|
|
|
56
56
|
export type { CueRequest, CueResponse, AgentTimelineItem } from "./db";
|
|
57
57
|
import { v4 as uuidv4 } from "uuid";
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
import {
|
|
60
|
+
DEFAULT_USER_CONFIG,
|
|
61
|
+
clampNumber,
|
|
62
|
+
normalizeMultiline,
|
|
63
|
+
normalizeSingleLine,
|
|
64
|
+
type UserConfig,
|
|
65
|
+
} from "./user-config";
|
|
66
|
+
|
|
67
|
+
export type { UserConfig } from "./user-config";
|
|
65
68
|
|
|
66
69
|
export type QueuedMessage = {
|
|
67
70
|
id: string;
|
|
@@ -70,23 +73,6 @@ export type QueuedMessage = {
|
|
|
70
73
|
createdAt: number;
|
|
71
74
|
};
|
|
72
75
|
|
|
73
|
-
const defaultUserConfig: UserConfig = {
|
|
74
|
-
sound_enabled: true,
|
|
75
|
-
conversation_mode_default: "agent",
|
|
76
|
-
chat_mode_append_text: "只做分析,不要对代码/文件做任何改动。",
|
|
77
|
-
pending_request_timeout_ms: 10 * 60 * 1000,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
function clampNumber(n: number, min: number, max: number): number {
|
|
81
|
-
if (!Number.isFinite(n)) return min;
|
|
82
|
-
return Math.max(min, Math.min(max, n));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function normalizeSingleLine(s: string): string {
|
|
86
|
-
const t = String(s ?? "").replace(/\r?\n/g, " ").trim();
|
|
87
|
-
return t;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
76
|
function getUserConfigPath(): string {
|
|
91
77
|
return path.join(os.homedir(), ".cue", "config.json");
|
|
92
78
|
}
|
|
@@ -100,24 +86,34 @@ export async function getUserConfig(): Promise<UserConfig> {
|
|
|
100
86
|
sound_enabled:
|
|
101
87
|
typeof parsed.sound_enabled === "boolean"
|
|
102
88
|
? parsed.sound_enabled
|
|
103
|
-
:
|
|
89
|
+
: DEFAULT_USER_CONFIG.sound_enabled,
|
|
104
90
|
conversation_mode_default:
|
|
105
91
|
parsed.conversation_mode_default === "chat" || parsed.conversation_mode_default === "agent"
|
|
106
92
|
? parsed.conversation_mode_default
|
|
107
|
-
:
|
|
93
|
+
: DEFAULT_USER_CONFIG.conversation_mode_default,
|
|
108
94
|
|
|
109
95
|
chat_mode_append_text:
|
|
110
96
|
typeof parsed.chat_mode_append_text === "string" && normalizeSingleLine(parsed.chat_mode_append_text).length > 0
|
|
111
97
|
? normalizeSingleLine(parsed.chat_mode_append_text)
|
|
112
|
-
:
|
|
98
|
+
: DEFAULT_USER_CONFIG.chat_mode_append_text,
|
|
113
99
|
|
|
114
100
|
pending_request_timeout_ms:
|
|
115
101
|
typeof parsed.pending_request_timeout_ms === "number"
|
|
116
102
|
? clampNumber(parsed.pending_request_timeout_ms, 60_000, 86_400_000)
|
|
117
|
-
:
|
|
103
|
+
: DEFAULT_USER_CONFIG.pending_request_timeout_ms,
|
|
104
|
+
|
|
105
|
+
bot_mode_enabled:
|
|
106
|
+
typeof parsed.bot_mode_enabled === "boolean"
|
|
107
|
+
? parsed.bot_mode_enabled
|
|
108
|
+
: DEFAULT_USER_CONFIG.bot_mode_enabled,
|
|
109
|
+
|
|
110
|
+
bot_mode_reply_text:
|
|
111
|
+
typeof parsed.bot_mode_reply_text === "string" && normalizeMultiline(parsed.bot_mode_reply_text).length > 0
|
|
112
|
+
? normalizeMultiline(parsed.bot_mode_reply_text)
|
|
113
|
+
: DEFAULT_USER_CONFIG.bot_mode_reply_text,
|
|
118
114
|
};
|
|
119
115
|
} catch {
|
|
120
|
-
return
|
|
116
|
+
return DEFAULT_USER_CONFIG;
|
|
121
117
|
}
|
|
122
118
|
}
|
|
123
119
|
|
|
@@ -140,6 +136,14 @@ export async function setUserConfig(next: Partial<UserConfig>): Promise<UserConf
|
|
|
140
136
|
typeof next.pending_request_timeout_ms === "number"
|
|
141
137
|
? clampNumber(next.pending_request_timeout_ms, 60_000, 86_400_000)
|
|
142
138
|
: prev.pending_request_timeout_ms,
|
|
139
|
+
|
|
140
|
+
bot_mode_enabled:
|
|
141
|
+
typeof next.bot_mode_enabled === "boolean" ? next.bot_mode_enabled : prev.bot_mode_enabled,
|
|
142
|
+
|
|
143
|
+
bot_mode_reply_text:
|
|
144
|
+
typeof next.bot_mode_reply_text === "string" && normalizeMultiline(next.bot_mode_reply_text).length > 0
|
|
145
|
+
? normalizeMultiline(next.bot_mode_reply_text)
|
|
146
|
+
: prev.bot_mode_reply_text,
|
|
143
147
|
};
|
|
144
148
|
const p = getUserConfigPath();
|
|
145
149
|
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
@@ -147,6 +151,63 @@ export async function setUserConfig(next: Partial<UserConfig>): Promise<UserConf
|
|
|
147
151
|
return merged;
|
|
148
152
|
}
|
|
149
153
|
|
|
154
|
+
export async function processBotTick(args: {
|
|
155
|
+
holderId: string;
|
|
156
|
+
convType: ConversationType;
|
|
157
|
+
convId: string;
|
|
158
|
+
limit?: number;
|
|
159
|
+
}): Promise<{ success: true; acquired: boolean; replied: number } | { success: false; error: string }> {
|
|
160
|
+
try {
|
|
161
|
+
const holderId = String(args.holderId || "").trim();
|
|
162
|
+
if (!holderId) return { success: false, error: "holderId required" } as const;
|
|
163
|
+
|
|
164
|
+
const convType = args.convType === "group" ? "group" : "agent";
|
|
165
|
+
const convId = String(args.convId || "").trim();
|
|
166
|
+
if (!convId) return { success: false, error: "convId required" } as const;
|
|
167
|
+
|
|
168
|
+
const cfg = await getUserConfig();
|
|
169
|
+
|
|
170
|
+
const lease = acquireWorkerLease({
|
|
171
|
+
leaseKey: `cue-console:bot-mode:${convType}:${convId}`,
|
|
172
|
+
holderId,
|
|
173
|
+
ttlMs: 5_000,
|
|
174
|
+
});
|
|
175
|
+
if (!lease.acquired) return { success: true, acquired: false, replied: 0 } as const;
|
|
176
|
+
|
|
177
|
+
const limit = Math.max(1, Math.min(200, args.limit ?? 50));
|
|
178
|
+
const pending =
|
|
179
|
+
convType === "agent"
|
|
180
|
+
? getRequestsByAgent(convId)
|
|
181
|
+
.filter((r) => r.status === "PENDING")
|
|
182
|
+
.filter((r) => {
|
|
183
|
+
if (!r.payload) return true;
|
|
184
|
+
try {
|
|
185
|
+
const obj = JSON.parse(r.payload) as Record<string, unknown>;
|
|
186
|
+
return !(obj?.type === "confirm" && obj?.variant === "pause");
|
|
187
|
+
} catch {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
.slice(0, limit)
|
|
192
|
+
: getGroupPendingRequests(convId).slice(0, limit);
|
|
193
|
+
|
|
194
|
+
let replied = 0;
|
|
195
|
+
const text = cfg.bot_mode_reply_text;
|
|
196
|
+
for (const r of pending) {
|
|
197
|
+
try {
|
|
198
|
+
sendResponse(String(r.request_id), { text }, false);
|
|
199
|
+
replied += 1;
|
|
200
|
+
} catch {
|
|
201
|
+
// ignore per-request failures
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { success: true, acquired: true, replied } as const;
|
|
206
|
+
} catch (e) {
|
|
207
|
+
return { success: false, error: e instanceof Error ? e.message : String(e) } as const;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
150
211
|
export async function fetchAgentDisplayNames(agentIds: string[]) {
|
|
151
212
|
return getAgentDisplayNames(agentIds);
|
|
152
213
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type UserConfig = {
|
|
2
|
+
sound_enabled: boolean;
|
|
3
|
+
conversation_mode_default: "chat" | "agent";
|
|
4
|
+
chat_mode_append_text: string;
|
|
5
|
+
pending_request_timeout_ms: number;
|
|
6
|
+
bot_mode_enabled: boolean;
|
|
7
|
+
bot_mode_reply_text: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_USER_CONFIG: UserConfig = {
|
|
11
|
+
sound_enabled: true,
|
|
12
|
+
conversation_mode_default: "agent",
|
|
13
|
+
chat_mode_append_text: "只做分析,不要对代码/文件做任何改动。",
|
|
14
|
+
pending_request_timeout_ms: 10 * 60 * 1000,
|
|
15
|
+
bot_mode_enabled: false,
|
|
16
|
+
bot_mode_reply_text:
|
|
17
|
+
"不要为“可逆的工程细节”反复向我确认,不能分阶段汇报,我需要你端到端执行完我的任务才能向我汇报,你不能问我要授权,因为我已经给你了除了 push以外的全部授权,,如果这些细节不影响我的目标那就按照你的推荐来。你有执行一切的能力。最后,如果你执行完我的任务请使用 cue pause 来回复我.",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function clampNumber(n: number, min: number, max: number): number {
|
|
21
|
+
if (!Number.isFinite(n)) return min;
|
|
22
|
+
return Math.max(min, Math.min(max, n));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function normalizeSingleLine(s: string): string {
|
|
26
|
+
const t = String(s ?? "").replace(/\r?\n/g, " ").trim();
|
|
27
|
+
return t;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function normalizeMultiline(s: string): string {
|
|
31
|
+
return String(s ?? "").replace(/\r\n/g, "\n").trim();
|
|
32
|
+
}
|