@hienlh/ppm 0.9.30 → 0.9.32
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/CHANGELOG.md +18 -5
- package/dist/web/assets/{browser-tab-D0o6oSlt.js → browser-tab-B9nNKjZX.js} +1 -1
- package/dist/web/assets/{chat-tab-Boo_H1k9.js → chat-tab-6XGhEKaC.js} +2 -2
- package/dist/web/assets/{code-editor-DayGetAZ.js → code-editor-DMZMpzt2.js} +1 -1
- package/dist/web/assets/{database-viewer-CaxAp1qK.js → database-viewer-CnP1FFS2.js} +1 -1
- package/dist/web/assets/{diff-viewer-BvEXe_B4.js → diff-viewer-Cvwd0XBO.js} +1 -1
- package/dist/web/assets/{extension-webview-6XProGzB.js → extension-webview-DkhsRepr.js} +1 -1
- package/dist/web/assets/{git-graph-CvgIIt2x.js → git-graph-C3670Nxm.js} +1 -1
- package/dist/web/assets/index-CcFDEPCo.css +2 -0
- package/dist/web/assets/index-DjIQL8ar.js +30 -0
- package/dist/web/assets/keybindings-store-DHh6rwm-.js +1 -0
- package/dist/web/assets/{markdown-renderer-UCGYJpI-.js → markdown-renderer-Co04dDdI.js} +1 -1
- package/dist/web/assets/{postgres-viewer-TV6kyo6B.js → postgres-viewer-D8K1qnnA.js} +1 -1
- package/dist/web/assets/{settings-tab-EziN5Pco.js → settings-tab-64ODAeQZ.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-D7LPvSkU.js → sqlite-viewer-ClX7FICB.js} +1 -1
- package/dist/web/assets/{terminal-tab-C7Hdv1nq.js → terminal-tab-Dw4IKWGM.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-CI4vTUsh.js → use-monaco-theme-DA7EyR70.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +33 -3
- package/docs/project-changelog.md +47 -0
- package/docs/project-roadmap.md +14 -7
- package/docs/streaming-input-guide.md +267 -0
- package/docs/system-architecture.md +65 -2
- package/package.json +1 -1
- package/snapshot-state.md +1526 -0
- package/src/server/index.ts +8 -1
- package/src/server/routes/settings.ts +72 -1
- package/src/services/clawbot/clawbot-formatter.ts +88 -0
- package/src/services/clawbot/clawbot-memory.ts +333 -0
- package/src/services/clawbot/clawbot-service.ts +500 -0
- package/src/services/clawbot/clawbot-session.ts +188 -0
- package/src/services/clawbot/clawbot-streamer.ts +245 -0
- package/src/services/clawbot/clawbot-telegram.ts +251 -0
- package/src/services/config.service.ts +1 -1
- package/src/services/db.service.ts +279 -1
- package/src/services/supervisor.ts +10 -0
- package/src/types/clawbot.ts +103 -0
- package/src/types/config.ts +22 -0
- package/src/web/components/chat/chat-history-bar.tsx +8 -3
- package/src/web/components/settings/clawbot-settings-section.tsx +270 -0
- package/src/web/components/settings/settings-tab.tsx +4 -1
- package/test-session-ops.mjs +444 -0
- package/test-tokens.mjs +212 -0
- package/dist/web/assets/index-CJvp0DJT.css +0 -2
- package/dist/web/assets/index-DocPzjV6.js +0 -30
- package/dist/web/assets/keybindings-store-2KURy8S3.js +0 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, type ChangeEvent } from "react";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
3
|
+
import { Input } from "@/components/ui/input";
|
|
4
|
+
import { Switch } from "@/components/ui/switch";
|
|
5
|
+
import { api } from "@/lib/api-client";
|
|
6
|
+
import { Trash2, CheckCircle, Clock } from "lucide-react";
|
|
7
|
+
|
|
8
|
+
interface ClawBotConfig {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
default_provider: string;
|
|
11
|
+
default_project: string;
|
|
12
|
+
system_prompt: string;
|
|
13
|
+
show_tool_calls: boolean;
|
|
14
|
+
show_thinking: boolean;
|
|
15
|
+
permission_mode: string;
|
|
16
|
+
debounce_ms: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface PairedChat {
|
|
20
|
+
id: number;
|
|
21
|
+
telegram_chat_id: string;
|
|
22
|
+
telegram_user_id: string | null;
|
|
23
|
+
display_name: string | null;
|
|
24
|
+
pairing_code: string | null;
|
|
25
|
+
status: "pending" | "approved";
|
|
26
|
+
created_at: number;
|
|
27
|
+
approved_at: number | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ClawBotSettingsSection() {
|
|
31
|
+
const [config, setConfig] = useState<ClawBotConfig | null>(null);
|
|
32
|
+
const [saving, setSaving] = useState(false);
|
|
33
|
+
const [status, setStatus] = useState<{ type: "ok" | "err"; msg: string } | null>(null);
|
|
34
|
+
|
|
35
|
+
const [enabled, setEnabled] = useState(false);
|
|
36
|
+
const [defaultProject, setDefaultProject] = useState("");
|
|
37
|
+
const [systemPrompt, setSystemPrompt] = useState("");
|
|
38
|
+
const [showToolCalls, setShowToolCalls] = useState(true);
|
|
39
|
+
const [showThinking, setShowThinking] = useState(false);
|
|
40
|
+
const [debounceMs, setDebounceMs] = useState(2000);
|
|
41
|
+
|
|
42
|
+
const [pairedChats, setPairedChats] = useState<PairedChat[]>([]);
|
|
43
|
+
const [approveCode, setApproveCode] = useState("");
|
|
44
|
+
const [approving, setApproving] = useState(false);
|
|
45
|
+
|
|
46
|
+
const fetchPairedChats = useCallback(async () => {
|
|
47
|
+
try {
|
|
48
|
+
const data = await api.get<PairedChat[]>("/api/settings/clawbot/paired");
|
|
49
|
+
setPairedChats(data);
|
|
50
|
+
} catch {}
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
api.get<ClawBotConfig>("/api/settings/clawbot").then((data) => {
|
|
55
|
+
setConfig(data);
|
|
56
|
+
setEnabled(data.enabled);
|
|
57
|
+
setDefaultProject(data.default_project);
|
|
58
|
+
setSystemPrompt(data.system_prompt);
|
|
59
|
+
setShowToolCalls(data.show_tool_calls);
|
|
60
|
+
setShowThinking(data.show_thinking);
|
|
61
|
+
setDebounceMs(data.debounce_ms);
|
|
62
|
+
}).catch(() => {});
|
|
63
|
+
fetchPairedChats();
|
|
64
|
+
}, [fetchPairedChats]);
|
|
65
|
+
|
|
66
|
+
const save = async () => {
|
|
67
|
+
setSaving(true);
|
|
68
|
+
setStatus(null);
|
|
69
|
+
try {
|
|
70
|
+
const body: Partial<ClawBotConfig> = {
|
|
71
|
+
enabled,
|
|
72
|
+
default_project: defaultProject.trim(),
|
|
73
|
+
system_prompt: systemPrompt,
|
|
74
|
+
show_tool_calls: showToolCalls,
|
|
75
|
+
show_thinking: showThinking,
|
|
76
|
+
debounce_ms: debounceMs,
|
|
77
|
+
};
|
|
78
|
+
const data = await api.put<ClawBotConfig>("/api/settings/clawbot", body);
|
|
79
|
+
setConfig(data);
|
|
80
|
+
setStatus({ type: "ok", msg: enabled ? "Saved — bot started" : "Saved — bot stopped" });
|
|
81
|
+
} catch (e) {
|
|
82
|
+
setStatus({ type: "err", msg: (e as Error).message });
|
|
83
|
+
} finally {
|
|
84
|
+
setSaving(false);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handleApprovePairing = async () => {
|
|
89
|
+
if (!approveCode.trim()) return;
|
|
90
|
+
setApproving(true);
|
|
91
|
+
try {
|
|
92
|
+
await api.post("/api/settings/clawbot/paired/approve", { code: approveCode.trim().toUpperCase() });
|
|
93
|
+
setApproveCode("");
|
|
94
|
+
await fetchPairedChats();
|
|
95
|
+
setStatus({ type: "ok", msg: "Device approved" });
|
|
96
|
+
} catch (e) {
|
|
97
|
+
setStatus({ type: "err", msg: (e as Error).message });
|
|
98
|
+
} finally {
|
|
99
|
+
setApproving(false);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const handleRevokePairing = async (chatId: string) => {
|
|
104
|
+
try {
|
|
105
|
+
await api.del(`/api/settings/clawbot/paired/${chatId}`);
|
|
106
|
+
await fetchPairedChats();
|
|
107
|
+
setStatus({ type: "ok", msg: "Device revoked" });
|
|
108
|
+
} catch (e) {
|
|
109
|
+
setStatus({ type: "err", msg: (e as Error).message });
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (!config) return <p className="text-xs text-muted-foreground">Loading...</p>;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className="space-y-4">
|
|
117
|
+
{/* Enable/Disable */}
|
|
118
|
+
<div className="flex items-center justify-between">
|
|
119
|
+
<div>
|
|
120
|
+
<p className="text-xs font-medium">Enable ClawBot</p>
|
|
121
|
+
<p className="text-[10px] text-muted-foreground">
|
|
122
|
+
Telegram bot that chats with your AI providers
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
<Switch checked={enabled} onCheckedChange={setEnabled} />
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{/* Paired Devices */}
|
|
129
|
+
<div className="space-y-2">
|
|
130
|
+
<p className="text-xs font-medium">Paired Devices</p>
|
|
131
|
+
<p className="text-[10px] text-muted-foreground">
|
|
132
|
+
Send any message to the bot on Telegram to get a pairing code. Enter it below to approve.
|
|
133
|
+
</p>
|
|
134
|
+
|
|
135
|
+
<div className="flex gap-2">
|
|
136
|
+
<Input
|
|
137
|
+
placeholder="Enter pairing code (e.g. A3K7WR)"
|
|
138
|
+
value={approveCode}
|
|
139
|
+
onChange={(e) => setApproveCode(e.target.value.toUpperCase())}
|
|
140
|
+
className="h-8 text-xs font-mono tracking-wider uppercase"
|
|
141
|
+
maxLength={6}
|
|
142
|
+
/>
|
|
143
|
+
<Button
|
|
144
|
+
variant="outline"
|
|
145
|
+
size="sm"
|
|
146
|
+
className="h-8 text-xs shrink-0 cursor-pointer"
|
|
147
|
+
disabled={approving || approveCode.length < 6}
|
|
148
|
+
onClick={handleApprovePairing}
|
|
149
|
+
>
|
|
150
|
+
{approving ? "..." : "Approve"}
|
|
151
|
+
</Button>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{pairedChats.length === 0 ? (
|
|
155
|
+
<p className="text-[10px] text-muted-foreground italic">No paired devices yet.</p>
|
|
156
|
+
) : (
|
|
157
|
+
<div className="space-y-1">
|
|
158
|
+
{pairedChats.map((chat) => (
|
|
159
|
+
<div
|
|
160
|
+
key={chat.id}
|
|
161
|
+
className="flex items-center justify-between rounded-md border p-2"
|
|
162
|
+
>
|
|
163
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
164
|
+
{chat.status === "approved" ? (
|
|
165
|
+
<CheckCircle className="size-3.5 text-green-500 shrink-0" />
|
|
166
|
+
) : (
|
|
167
|
+
<Clock className="size-3.5 text-yellow-500 shrink-0" />
|
|
168
|
+
)}
|
|
169
|
+
<div className="min-w-0">
|
|
170
|
+
<p className="text-xs truncate">
|
|
171
|
+
{chat.display_name || `Chat ${chat.telegram_chat_id}`}
|
|
172
|
+
</p>
|
|
173
|
+
<p className="text-[10px] text-muted-foreground">
|
|
174
|
+
{chat.status === "pending" && chat.pairing_code
|
|
175
|
+
? `Code: ${chat.pairing_code}`
|
|
176
|
+
: chat.status}
|
|
177
|
+
</p>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
<Button
|
|
181
|
+
variant="ghost"
|
|
182
|
+
size="sm"
|
|
183
|
+
className="h-7 w-7 p-0 text-destructive hover:text-destructive cursor-pointer"
|
|
184
|
+
onClick={() => handleRevokePairing(chat.telegram_chat_id)}
|
|
185
|
+
>
|
|
186
|
+
<Trash2 className="size-3.5" />
|
|
187
|
+
</Button>
|
|
188
|
+
</div>
|
|
189
|
+
))}
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{/* Default Project */}
|
|
195
|
+
<div className="space-y-1.5">
|
|
196
|
+
<label className="text-[11px] text-muted-foreground">Default Project</label>
|
|
197
|
+
<Input
|
|
198
|
+
placeholder="my-project"
|
|
199
|
+
value={defaultProject}
|
|
200
|
+
onChange={(e) => setDefaultProject(e.target.value)}
|
|
201
|
+
className="h-7 text-xs"
|
|
202
|
+
/>
|
|
203
|
+
<p className="text-[10px] text-muted-foreground">
|
|
204
|
+
Project used when starting a new chat. Must match a project name in PPM.
|
|
205
|
+
</p>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
{/* System Prompt */}
|
|
209
|
+
<div className="space-y-1.5">
|
|
210
|
+
<label className="text-[11px] text-muted-foreground">System Prompt</label>
|
|
211
|
+
<textarea
|
|
212
|
+
placeholder="You are a helpful assistant..."
|
|
213
|
+
value={systemPrompt}
|
|
214
|
+
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setSystemPrompt(e.target.value)}
|
|
215
|
+
className="flex w-full rounded-md border border-input bg-background px-3 py-2 text-xs min-h-[60px] resize-y ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
216
|
+
rows={3}
|
|
217
|
+
/>
|
|
218
|
+
<p className="text-[10px] text-muted-foreground">
|
|
219
|
+
Custom personality/instructions prepended to each session.
|
|
220
|
+
</p>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
{/* Display Toggles */}
|
|
224
|
+
<div className="space-y-2">
|
|
225
|
+
<div className="flex items-center justify-between">
|
|
226
|
+
<p className="text-xs">Show tool calls</p>
|
|
227
|
+
<Switch checked={showToolCalls} onCheckedChange={setShowToolCalls} />
|
|
228
|
+
</div>
|
|
229
|
+
<div className="flex items-center justify-between">
|
|
230
|
+
<p className="text-xs">Show thinking</p>
|
|
231
|
+
<Switch checked={showThinking} onCheckedChange={setShowThinking} />
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{/* Debounce */}
|
|
236
|
+
<div className="space-y-1.5">
|
|
237
|
+
<label className="text-[11px] text-muted-foreground">Debounce (ms)</label>
|
|
238
|
+
<Input
|
|
239
|
+
type="number"
|
|
240
|
+
min={0}
|
|
241
|
+
max={30000}
|
|
242
|
+
step={500}
|
|
243
|
+
value={debounceMs}
|
|
244
|
+
onChange={(e) => setDebounceMs(Number(e.target.value))}
|
|
245
|
+
className="h-7 text-xs w-24"
|
|
246
|
+
/>
|
|
247
|
+
<p className="text-[10px] text-muted-foreground">
|
|
248
|
+
Merge rapid messages within this window. 0 = no debounce.
|
|
249
|
+
</p>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
{/* Save */}
|
|
253
|
+
<Button
|
|
254
|
+
variant="default"
|
|
255
|
+
size="sm"
|
|
256
|
+
className="h-8 text-xs w-full cursor-pointer"
|
|
257
|
+
disabled={saving}
|
|
258
|
+
onClick={save}
|
|
259
|
+
>
|
|
260
|
+
{saving ? "Saving..." : "Save"}
|
|
261
|
+
</Button>
|
|
262
|
+
|
|
263
|
+
{status && (
|
|
264
|
+
<p className={`text-[11px] ${status.type === "ok" ? "text-green-600 dark:text-green-400" : "text-destructive"}`}>
|
|
265
|
+
{status.msg}
|
|
266
|
+
</p>
|
|
267
|
+
)}
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
@@ -15,6 +15,7 @@ import { TelegramSettingsSection } from "./telegram-settings-section";
|
|
|
15
15
|
import { ProxySettingsSection } from "./proxy-settings-section";
|
|
16
16
|
import { McpSettingsSection } from "./mcp-settings-section";
|
|
17
17
|
import { ExtensionManagerSection } from "./extension-manager-section";
|
|
18
|
+
import { ClawBotSettingsSection } from "./clawbot-settings-section";
|
|
18
19
|
import { ChangePasswordSection } from "./change-password-section";
|
|
19
20
|
import { usePushNotification } from "@/hooks/use-push-notification";
|
|
20
21
|
|
|
@@ -28,11 +29,12 @@ const pushSupported = "PushManager" in window && "serviceWorker" in navigator;
|
|
|
28
29
|
const isIosNonPwa = /iPhone|iPad/.test(navigator.userAgent) &&
|
|
29
30
|
!window.matchMedia("(display-mode: standalone)").matches;
|
|
30
31
|
|
|
31
|
-
type SettingsCategory = "ai" | "notifications" | "proxy" | "shortcuts" | "mcp" | "extensions";
|
|
32
|
+
type SettingsCategory = "ai" | "notifications" | "clawbot" | "proxy" | "shortcuts" | "mcp" | "extensions";
|
|
32
33
|
|
|
33
34
|
const CATEGORIES: { value: SettingsCategory; label: string; subtitle: string; icon: React.ElementType }[] = [
|
|
34
35
|
{ value: "ai", label: "AI Provider", subtitle: "Model, execution mode, limits", icon: Bot },
|
|
35
36
|
{ value: "notifications", label: "Notifications", subtitle: "Push & Telegram alerts", icon: BellRing },
|
|
37
|
+
{ value: "clawbot", label: "ClawBot", subtitle: "Telegram AI bot", icon: Bot },
|
|
36
38
|
{ value: "proxy", label: "API Proxy", subtitle: "Expose accounts as Anthropic API", icon: Globe },
|
|
37
39
|
{ value: "shortcuts", label: "Keyboard Shortcuts", subtitle: "Customize key bindings", icon: Keyboard },
|
|
38
40
|
{ value: "mcp", label: "MCP Servers", subtitle: "Model Context Protocol tools", icon: Plug },
|
|
@@ -87,6 +89,7 @@ export function SettingsTab() {
|
|
|
87
89
|
<div className="p-3">
|
|
88
90
|
{activeCategory === "ai" && <AISettingsSection compact />}
|
|
89
91
|
{activeCategory === "notifications" && <NotificationsContent isSubscribed={isSubscribed} loading={loading} permission={permission} pushError={pushError} subscribe={subscribe} unsubscribe={unsubscribe} />}
|
|
92
|
+
{activeCategory === "clawbot" && <ClawBotSettingsSection />}
|
|
90
93
|
{activeCategory === "proxy" && <ProxySettingsSection />}
|
|
91
94
|
{activeCategory === "shortcuts" && <KeyboardShortcutsSection />}
|
|
92
95
|
{activeCategory === "mcp" && <McpSettingsSection />}
|