@chrysb/alphaclaw 0.3.3 → 0.3.4
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/bin/alphaclaw.js +18 -0
- package/lib/plugin/usage-tracker/index.js +308 -0
- package/lib/plugin/usage-tracker/openclaw.plugin.json +8 -0
- package/lib/public/css/explorer.css +51 -1
- package/lib/public/css/shell.css +3 -1
- package/lib/public/css/theme.css +35 -0
- package/lib/public/js/app.js +73 -24
- package/lib/public/js/components/file-tree.js +231 -28
- package/lib/public/js/components/file-viewer.js +193 -20
- package/lib/public/js/components/segmented-control.js +33 -0
- package/lib/public/js/components/sidebar.js +14 -32
- package/lib/public/js/components/telegram-workspace/index.js +353 -0
- package/lib/public/js/components/telegram-workspace/manage.js +397 -0
- package/lib/public/js/components/telegram-workspace/onboarding.js +616 -0
- package/lib/public/js/components/usage-tab.js +528 -0
- package/lib/public/js/components/watchdog-tab.js +1 -1
- package/lib/public/js/lib/api.js +25 -1
- package/lib/public/js/lib/telegram-api.js +78 -0
- package/lib/public/js/lib/ui-settings.js +38 -0
- package/lib/public/setup.html +34 -30
- package/lib/server/alphaclaw-version.js +3 -3
- package/lib/server/constants.js +1 -0
- package/lib/server/onboarding/openclaw.js +15 -0
- package/lib/server/routes/auth.js +5 -1
- package/lib/server/routes/telegram.js +185 -60
- package/lib/server/routes/usage.js +133 -0
- package/lib/server/usage-db.js +570 -0
- package/lib/server.js +21 -1
- package/lib/setup/core-prompts/AGENTS.md +0 -101
- package/package.json +1 -1
- package/lib/public/js/components/telegram-workspace.js +0 -1365
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import { useState, useEffect } from "https://esm.sh/preact/hooks";
|
|
3
|
+
import htm from "https://esm.sh/htm";
|
|
4
|
+
import { showToast } from "../toast.js";
|
|
5
|
+
import { ActionButton } from "../action-button.js";
|
|
6
|
+
import { ConfirmDialog } from "../confirm-dialog.js";
|
|
7
|
+
import * as api from "../../lib/telegram-api.js";
|
|
8
|
+
|
|
9
|
+
const html = htm.bind(h);
|
|
10
|
+
|
|
11
|
+
export const ManageTelegramWorkspace = ({
|
|
12
|
+
groupId,
|
|
13
|
+
groupName,
|
|
14
|
+
initialTopics,
|
|
15
|
+
configAgentMaxConcurrent,
|
|
16
|
+
configSubagentMaxConcurrent,
|
|
17
|
+
debugEnabled,
|
|
18
|
+
onResetOnboarding,
|
|
19
|
+
}) => {
|
|
20
|
+
const [topics, setTopics] = useState(initialTopics || {});
|
|
21
|
+
const [newTopicName, setNewTopicName] = useState("");
|
|
22
|
+
const [newTopicInstructions, setNewTopicInstructions] = useState("");
|
|
23
|
+
const [showCreateTopic, setShowCreateTopic] = useState(false);
|
|
24
|
+
const [creating, setCreating] = useState(false);
|
|
25
|
+
const [deleting, setDeleting] = useState(null);
|
|
26
|
+
const [editingTopicId, setEditingTopicId] = useState("");
|
|
27
|
+
const [editingTopicName, setEditingTopicName] = useState("");
|
|
28
|
+
const [editingTopicInstructions, setEditingTopicInstructions] = useState("");
|
|
29
|
+
const [renamingTopicId, setRenamingTopicId] = useState("");
|
|
30
|
+
const [error, setError] = useState(null);
|
|
31
|
+
const [deleteTopicConfirm, setDeleteTopicConfirm] = useState(null);
|
|
32
|
+
|
|
33
|
+
const loadTopics = async () => {
|
|
34
|
+
const data = await api.listTopics(groupId);
|
|
35
|
+
if (data.ok) setTopics(data.topics || {});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
loadTopics();
|
|
40
|
+
}, [groupId]);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (initialTopics && Object.keys(initialTopics).length > 0) {
|
|
43
|
+
setTopics(initialTopics);
|
|
44
|
+
}
|
|
45
|
+
}, [initialTopics]);
|
|
46
|
+
|
|
47
|
+
const createSingle = async () => {
|
|
48
|
+
const name = newTopicName.trim();
|
|
49
|
+
const systemInstructions = newTopicInstructions.trim();
|
|
50
|
+
if (!name) return;
|
|
51
|
+
setCreating(true);
|
|
52
|
+
setError(null);
|
|
53
|
+
try {
|
|
54
|
+
const data = await api.createTopicsBulk(groupId, [
|
|
55
|
+
{ name, ...(systemInstructions ? { systemInstructions } : {}) },
|
|
56
|
+
]);
|
|
57
|
+
if (!data.ok)
|
|
58
|
+
throw new Error(data.results?.[0]?.error || "Failed to create topic");
|
|
59
|
+
const failed = data.results.filter((r) => !r.ok);
|
|
60
|
+
if (failed.length > 0) throw new Error(failed[0].error);
|
|
61
|
+
setNewTopicName("");
|
|
62
|
+
setNewTopicInstructions("");
|
|
63
|
+
setShowCreateTopic(false);
|
|
64
|
+
await loadTopics();
|
|
65
|
+
showToast(`Created topic: ${name}`, "success");
|
|
66
|
+
} catch (e) {
|
|
67
|
+
setError(e.message);
|
|
68
|
+
}
|
|
69
|
+
setCreating(false);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const handleDelete = async (topicId, topicName) => {
|
|
73
|
+
setDeleting(topicId);
|
|
74
|
+
try {
|
|
75
|
+
const data = await api.deleteTopic(groupId, topicId);
|
|
76
|
+
if (!data.ok) throw new Error(data.error);
|
|
77
|
+
await loadTopics();
|
|
78
|
+
if (data.removedFromRegistryOnly) {
|
|
79
|
+
showToast(`Removed stale topic from registry: ${topicName}`, "success");
|
|
80
|
+
} else {
|
|
81
|
+
showToast(`Deleted topic: ${topicName}`, "success");
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
showToast(`Failed to delete: ${e.message}`, "error");
|
|
85
|
+
}
|
|
86
|
+
setDeleting(null);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const startRename = (topicId, topicName, topicInstructions = "") => {
|
|
90
|
+
setEditingTopicId(String(topicId));
|
|
91
|
+
setEditingTopicName(String(topicName || ""));
|
|
92
|
+
setEditingTopicInstructions(String(topicInstructions || ""));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const cancelRename = () => {
|
|
96
|
+
setEditingTopicId("");
|
|
97
|
+
setEditingTopicName("");
|
|
98
|
+
setEditingTopicInstructions("");
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const saveRename = async (topicId) => {
|
|
102
|
+
const nextName = editingTopicName.trim();
|
|
103
|
+
const nextSystemInstructions = editingTopicInstructions.trim();
|
|
104
|
+
if (!nextName) {
|
|
105
|
+
setError("Topic name is required");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
setRenamingTopicId(String(topicId));
|
|
109
|
+
setError(null);
|
|
110
|
+
try {
|
|
111
|
+
const data = await api.updateTopic(groupId, topicId, {
|
|
112
|
+
name: nextName,
|
|
113
|
+
systemInstructions: nextSystemInstructions,
|
|
114
|
+
});
|
|
115
|
+
if (!data.ok) throw new Error(data.error || "Failed to rename topic");
|
|
116
|
+
await loadTopics();
|
|
117
|
+
showToast(`Renamed topic: ${nextName}`, "success");
|
|
118
|
+
cancelRename();
|
|
119
|
+
} catch (e) {
|
|
120
|
+
setError(e.message);
|
|
121
|
+
}
|
|
122
|
+
setRenamingTopicId("");
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const topicEntries = Object.entries(topics || {});
|
|
126
|
+
const topicCount = topicEntries.length;
|
|
127
|
+
const computedMaxConcurrent = Math.max(topicCount * 3, 8);
|
|
128
|
+
const computedSubagentMaxConcurrent = Math.max(computedMaxConcurrent - 2, 4);
|
|
129
|
+
const maxConcurrent = Number.isFinite(configAgentMaxConcurrent)
|
|
130
|
+
? configAgentMaxConcurrent
|
|
131
|
+
: computedMaxConcurrent;
|
|
132
|
+
const subagentMaxConcurrent = Number.isFinite(configSubagentMaxConcurrent)
|
|
133
|
+
? configSubagentMaxConcurrent
|
|
134
|
+
: computedSubagentMaxConcurrent;
|
|
135
|
+
|
|
136
|
+
return html`
|
|
137
|
+
<div class="space-y-4">
|
|
138
|
+
${debugEnabled &&
|
|
139
|
+
html`
|
|
140
|
+
<div class="flex justify-end">
|
|
141
|
+
<button
|
|
142
|
+
onclick=${onResetOnboarding}
|
|
143
|
+
class="text-xs px-3 py-1.5 rounded-lg border border-border text-gray-400 hover:text-gray-200 hover:border-gray-500 transition-colors"
|
|
144
|
+
>
|
|
145
|
+
Reset onboarding
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
`}
|
|
149
|
+
<div class="bg-black/20 border border-border rounded-lg p-3 space-y-1">
|
|
150
|
+
<p class="text-sm text-gray-300 font-medium">${groupName || groupId}</p>
|
|
151
|
+
<p class="text-xs text-gray-500 font-mono">${groupId}</p>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div class="space-y-2">
|
|
155
|
+
<h2 class="card-label mb-3">Existing Topics</h2>
|
|
156
|
+
${topicEntries.length > 0
|
|
157
|
+
? html`
|
|
158
|
+
<div
|
|
159
|
+
class="bg-black/20 border border-border rounded-lg overflow-hidden"
|
|
160
|
+
>
|
|
161
|
+
<table class="w-full text-xs table-fixed">
|
|
162
|
+
<thead>
|
|
163
|
+
<tr class="border-b border-border">
|
|
164
|
+
<th class="text-left px-3 py-2 text-gray-500 font-medium">
|
|
165
|
+
Topic
|
|
166
|
+
</th>
|
|
167
|
+
<th
|
|
168
|
+
class="text-left px-3 py-2 text-gray-500 font-medium w-36"
|
|
169
|
+
>
|
|
170
|
+
Thread ID
|
|
171
|
+
</th>
|
|
172
|
+
<th class="px-3 py-2 w-28" />
|
|
173
|
+
</tr>
|
|
174
|
+
</thead>
|
|
175
|
+
<tbody>
|
|
176
|
+
${topicEntries.map(
|
|
177
|
+
([id, topic]) => html`
|
|
178
|
+
${editingTopicId === String(id)
|
|
179
|
+
? html`
|
|
180
|
+
<tr
|
|
181
|
+
class="border-b border-border last:border-0 align-top"
|
|
182
|
+
>
|
|
183
|
+
<td class="px-3 py-2" colspan="3">
|
|
184
|
+
<div class="space-y-2">
|
|
185
|
+
<input
|
|
186
|
+
type="text"
|
|
187
|
+
value=${editingTopicName}
|
|
188
|
+
onInput=${(e) =>
|
|
189
|
+
setEditingTopicName(e.target.value)}
|
|
190
|
+
onKeyDown=${(e) => {
|
|
191
|
+
if (e.key === "Enter") saveRename(id);
|
|
192
|
+
if (e.key === "Escape") cancelRename();
|
|
193
|
+
}}
|
|
194
|
+
class="w-full bg-black/30 border border-border rounded-lg px-2 py-1.5 text-xs text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500"
|
|
195
|
+
/>
|
|
196
|
+
<textarea
|
|
197
|
+
value=${editingTopicInstructions}
|
|
198
|
+
onInput=${(e) =>
|
|
199
|
+
setEditingTopicInstructions(
|
|
200
|
+
e.target.value,
|
|
201
|
+
)}
|
|
202
|
+
placeholder="System instructions (optional)"
|
|
203
|
+
rows="6"
|
|
204
|
+
class="w-full bg-black/30 border border-border rounded-lg px-2 py-1.5 text-xs text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500 resize-y"
|
|
205
|
+
/>
|
|
206
|
+
<div class="flex items-center gap-2">
|
|
207
|
+
<button
|
|
208
|
+
onclick=${() => saveRename(id)}
|
|
209
|
+
disabled=${renamingTopicId ===
|
|
210
|
+
String(id)}
|
|
211
|
+
class="text-xs px-2 py-1 rounded transition-all ac-btn-cyan ${renamingTopicId ===
|
|
212
|
+
String(id)
|
|
213
|
+
? "opacity-50 cursor-not-allowed"
|
|
214
|
+
: ""}"
|
|
215
|
+
>
|
|
216
|
+
Save
|
|
217
|
+
</button>
|
|
218
|
+
<button
|
|
219
|
+
onclick=${cancelRename}
|
|
220
|
+
class="text-xs px-2 py-1 rounded border border-border text-gray-400 hover:text-gray-200 hover:border-gray-500"
|
|
221
|
+
>
|
|
222
|
+
Cancel
|
|
223
|
+
</button>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</td>
|
|
227
|
+
</tr>
|
|
228
|
+
`
|
|
229
|
+
: html`
|
|
230
|
+
<tr
|
|
231
|
+
class="border-b border-border last:border-0 align-middle"
|
|
232
|
+
>
|
|
233
|
+
<td class="px-3 py-2 text-gray-300">
|
|
234
|
+
<div class="flex items-center gap-2">
|
|
235
|
+
<span>${topic.name}</span>
|
|
236
|
+
<button
|
|
237
|
+
onclick=${() =>
|
|
238
|
+
startRename(
|
|
239
|
+
id,
|
|
240
|
+
topic.name,
|
|
241
|
+
topic.systemInstructions,
|
|
242
|
+
)}
|
|
243
|
+
class="inline-flex items-center justify-center text-white/80 hover:text-white transition-colors"
|
|
244
|
+
title="Edit topic"
|
|
245
|
+
aria-label="Rename topic"
|
|
246
|
+
>
|
|
247
|
+
<svg
|
|
248
|
+
width="14"
|
|
249
|
+
height="14"
|
|
250
|
+
viewBox="0 0 16 16"
|
|
251
|
+
fill="currentColor"
|
|
252
|
+
aria-hidden="true"
|
|
253
|
+
>
|
|
254
|
+
<path
|
|
255
|
+
d="M11.854 1.146a.5.5 0 00-.708 0L3 9.293V13h3.707l8.146-8.146a.5.5 0 000-.708l-3-3zM3.5 12.5v-2.793l7-7L13.793 6l-7 7H3.5z"
|
|
256
|
+
/>
|
|
257
|
+
</svg>
|
|
258
|
+
</button>
|
|
259
|
+
</div>
|
|
260
|
+
${topic.systemInstructions &&
|
|
261
|
+
html`
|
|
262
|
+
<p
|
|
263
|
+
class="text-[11px] text-gray-500 mt-1 line-clamp-1"
|
|
264
|
+
>
|
|
265
|
+
${topic.systemInstructions}
|
|
266
|
+
</p>
|
|
267
|
+
`}
|
|
268
|
+
</td>
|
|
269
|
+
<td
|
|
270
|
+
class="px-3 py-2 text-gray-500 font-mono w-36"
|
|
271
|
+
>
|
|
272
|
+
${id}
|
|
273
|
+
</td>
|
|
274
|
+
<td class="px-3 py-2">
|
|
275
|
+
<div
|
|
276
|
+
class="flex items-center gap-2 justify-end"
|
|
277
|
+
>
|
|
278
|
+
<button
|
|
279
|
+
onclick=${() =>
|
|
280
|
+
setDeleteTopicConfirm({
|
|
281
|
+
id: String(id),
|
|
282
|
+
name: String(topic.name || ""),
|
|
283
|
+
})}
|
|
284
|
+
disabled=${deleting === id}
|
|
285
|
+
class="text-xs px-2 py-1 rounded border border-border text-gray-500 hover:text-red-300 hover:border-red-500 ${deleting ===
|
|
286
|
+
id
|
|
287
|
+
? "opacity-50 cursor-not-allowed"
|
|
288
|
+
: ""}"
|
|
289
|
+
title="Delete topic"
|
|
290
|
+
>
|
|
291
|
+
Delete
|
|
292
|
+
</button>
|
|
293
|
+
</div>
|
|
294
|
+
</td>
|
|
295
|
+
</tr>
|
|
296
|
+
`}
|
|
297
|
+
`,
|
|
298
|
+
)}
|
|
299
|
+
</tbody>
|
|
300
|
+
</table>
|
|
301
|
+
</div>
|
|
302
|
+
`
|
|
303
|
+
: html`<p class="text-xs text-gray-500">No topics yet.</p>`}
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
${showCreateTopic &&
|
|
307
|
+
html`
|
|
308
|
+
<div class="space-y-2 bg-black/20 border border-border rounded-lg p-3">
|
|
309
|
+
<label class="text-xs text-gray-500">Create new topic</label>
|
|
310
|
+
<div class="space-y-2">
|
|
311
|
+
<input
|
|
312
|
+
type="text"
|
|
313
|
+
value=${newTopicName}
|
|
314
|
+
onInput=${(e) => setNewTopicName(e.target.value)}
|
|
315
|
+
onKeyDown=${(e) => {
|
|
316
|
+
if (e.key === "Enter") createSingle();
|
|
317
|
+
}}
|
|
318
|
+
placeholder="Topic name"
|
|
319
|
+
class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500"
|
|
320
|
+
/>
|
|
321
|
+
<textarea
|
|
322
|
+
value=${newTopicInstructions}
|
|
323
|
+
onInput=${(e) => setNewTopicInstructions(e.target.value)}
|
|
324
|
+
placeholder="System instructions (optional)"
|
|
325
|
+
rows="5"
|
|
326
|
+
class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500 resize-y"
|
|
327
|
+
/>
|
|
328
|
+
<div class="flex justify-end">
|
|
329
|
+
<${ActionButton}
|
|
330
|
+
onClick=${createSingle}
|
|
331
|
+
disabled=${creating || !newTopicName.trim()}
|
|
332
|
+
loading=${creating}
|
|
333
|
+
tone="secondary"
|
|
334
|
+
size="lg"
|
|
335
|
+
idleLabel="Add topic"
|
|
336
|
+
loadingLabel="Creating..."
|
|
337
|
+
/>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
`}
|
|
342
|
+
${error &&
|
|
343
|
+
html`
|
|
344
|
+
<div class="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
|
|
345
|
+
<p class="text-sm text-red-400">${error}</p>
|
|
346
|
+
</div>
|
|
347
|
+
`}
|
|
348
|
+
|
|
349
|
+
<div class="flex items-center justify-start">
|
|
350
|
+
<button
|
|
351
|
+
onclick=${() => setShowCreateTopic((v) => !v)}
|
|
352
|
+
class="${showCreateTopic
|
|
353
|
+
? "w-auto text-sm font-medium px-4 py-2 rounded-xl transition-all border border-border text-gray-300 hover:border-gray-500"
|
|
354
|
+
: "w-auto text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan"}"
|
|
355
|
+
>
|
|
356
|
+
${showCreateTopic ? "Close create topic" : "Create topic"}
|
|
357
|
+
</button>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
<div class="border-t border-white/10" />
|
|
361
|
+
|
|
362
|
+
<p class="text-xs text-gray-500">
|
|
363
|
+
Concurrency is auto-scaled to support your group:
|
|
364
|
+
<span class="text-gray-300"> agent ${maxConcurrent}</span>,
|
|
365
|
+
<span class="text-gray-300"> subagent ${subagentMaxConcurrent}</span>
|
|
366
|
+
<span class="text-gray-600"> (${topicCount} topics)</span>.
|
|
367
|
+
</p>
|
|
368
|
+
<p class="text-[11px] text-gray-500">
|
|
369
|
+
This registry can drift if topics are created, renamed, or removed
|
|
370
|
+
outside this page. Your agent will update the registry if it notices a
|
|
371
|
+
discrepancy.
|
|
372
|
+
</p>
|
|
373
|
+
<${ConfirmDialog}
|
|
374
|
+
visible=${!!deleteTopicConfirm}
|
|
375
|
+
title="Delete topic?"
|
|
376
|
+
message=${deleteTopicConfirm
|
|
377
|
+
? `This will delete "${deleteTopicConfirm.name}" (thread ${deleteTopicConfirm.id}) from your Telegram workspace.`
|
|
378
|
+
: "This will delete this topic from your Telegram workspace."}
|
|
379
|
+
confirmLabel="Delete topic"
|
|
380
|
+
confirmLoadingLabel="Deleting..."
|
|
381
|
+
confirmTone="warning"
|
|
382
|
+
confirmLoading=${!!deleting}
|
|
383
|
+
cancelLabel="Cancel"
|
|
384
|
+
onCancel=${() => {
|
|
385
|
+
if (deleting) return;
|
|
386
|
+
setDeleteTopicConfirm(null);
|
|
387
|
+
}}
|
|
388
|
+
onConfirm=${async () => {
|
|
389
|
+
if (!deleteTopicConfirm) return;
|
|
390
|
+
const pendingDelete = deleteTopicConfirm;
|
|
391
|
+
setDeleteTopicConfirm(null);
|
|
392
|
+
await handleDelete(pendingDelete.id, pendingDelete.name);
|
|
393
|
+
}}
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
396
|
+
`;
|
|
397
|
+
};
|