botschat 0.1.20 → 0.1.21

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.
Files changed (52) hide show
  1. package/package.json +1 -1
  2. package/packages/api/src/do/connection-do.ts +186 -382
  3. package/packages/api/src/index.ts +50 -67
  4. package/packages/api/src/routes/agents.ts +3 -3
  5. package/packages/api/src/routes/auth.ts +1 -0
  6. package/packages/api/src/routes/channels.ts +11 -11
  7. package/packages/api/src/routes/demo.ts +156 -0
  8. package/packages/api/src/routes/sessions.ts +5 -5
  9. package/packages/api/src/routes/tasks.ts +33 -33
  10. package/packages/plugin/dist/src/channel.js +50 -0
  11. package/packages/plugin/dist/src/channel.js.map +1 -1
  12. package/packages/plugin/package.json +18 -2
  13. package/packages/web/dist/assets/index-BtPyCBCl.css +1 -0
  14. package/packages/web/dist/assets/index-BtpsFe4Z.js +2 -0
  15. package/packages/web/dist/assets/index-CQbIYr6_.js +2 -0
  16. package/packages/web/dist/assets/{index-CYQMu_-c.js → index-C_GamcQc.js} +1 -1
  17. package/packages/web/dist/assets/index-LiBjPMg2.js +1 -0
  18. package/packages/web/dist/assets/{index-DYCO-ry1.js → index-MyoWvQAH.js} +1 -1
  19. package/packages/web/dist/assets/index-STIPTMK8.js +1516 -0
  20. package/packages/web/dist/assets/{index.esm-CvOpngZM.js → index.esm-BpQAwtdR.js} +1 -1
  21. package/packages/web/dist/assets/{web-D3LMODYp.js → web-BbTzVNLt.js} +1 -1
  22. package/packages/web/dist/assets/{web-1cdhq2RW.js → web-cnzjgNfD.js} +1 -1
  23. package/packages/web/dist/index.html +2 -2
  24. package/packages/web/src/App.tsx +9 -56
  25. package/packages/web/src/api.ts +5 -61
  26. package/packages/web/src/components/ChatWindow.tsx +9 -9
  27. package/packages/web/src/components/CronDetail.tsx +1 -1
  28. package/packages/web/src/components/ImageLightbox.tsx +96 -0
  29. package/packages/web/src/components/LoginPage.tsx +59 -1
  30. package/packages/web/src/components/MessageContent.tsx +17 -2
  31. package/packages/web/src/components/SessionTabs.tsx +1 -1
  32. package/packages/web/src/components/Sidebar.tsx +1 -3
  33. package/packages/web/src/hooks/useIMEComposition.ts +14 -9
  34. package/packages/web/src/store.ts +7 -39
  35. package/packages/web/src/ws.ts +0 -1
  36. package/scripts/dev.sh +0 -53
  37. package/migrations/0013_agents_table.sql +0 -29
  38. package/migrations/0014_agent_sessions.sql +0 -19
  39. package/migrations/0015_message_traces.sql +0 -27
  40. package/migrations/0016_multi_agent_channels_messages.sql +0 -9
  41. package/migrations/0017_rename_cron_job_id.sql +0 -2
  42. package/packages/api/src/protocol-v2.ts +0 -154
  43. package/packages/api/src/routes/agents-v2.ts +0 -192
  44. package/packages/api/src/routes/history-v2.ts +0 -221
  45. package/packages/api/src/routes/migrate-v2.ts +0 -110
  46. package/packages/web/dist/assets/index-BARPtt0v.css +0 -1
  47. package/packages/web/dist/assets/index-Bf-XL3te.js +0 -2
  48. package/packages/web/dist/assets/index-CYlvfpX9.js +0 -1519
  49. package/packages/web/dist/assets/index-CxcpA4Qo.js +0 -1
  50. package/packages/web/dist/assets/index-QebPVqwj.js +0 -2
  51. package/packages/web/src/components/AgentSettings.tsx +0 -328
  52. package/scripts/mock-openclaw-v2.mjs +0 -486
@@ -0,0 +1,96 @@
1
+ import React, { useEffect, useCallback, useState } from "react";
2
+
3
+ type LightboxState = { url: string; filename?: string } | null;
4
+
5
+ let _setLightbox: React.Dispatch<React.SetStateAction<LightboxState>> | null = null;
6
+
7
+ export function openImageLightbox(url: string, filename?: string) {
8
+ _setLightbox?.({ url, filename });
9
+ }
10
+
11
+ export function ImageLightbox() {
12
+ const [state, setState] = useState<LightboxState>(null);
13
+ _setLightbox = setState;
14
+
15
+ const close = useCallback(() => setState(null), []);
16
+
17
+ useEffect(() => {
18
+ if (!state) return;
19
+ const onKey = (e: KeyboardEvent) => {
20
+ if (e.key === "Escape") close();
21
+ };
22
+ window.addEventListener("keydown", onKey);
23
+ return () => window.removeEventListener("keydown", onKey);
24
+ }, [state, close]);
25
+
26
+ if (!state) return null;
27
+
28
+ const handleDownload = async () => {
29
+ try {
30
+ const res = await fetch(state.url);
31
+ const blob = await res.blob();
32
+ const blobUrl = URL.createObjectURL(blob);
33
+ const a = document.createElement("a");
34
+ a.href = blobUrl;
35
+ a.download = state.filename || guessFilename(state.url);
36
+ document.body.appendChild(a);
37
+ a.click();
38
+ document.body.removeChild(a);
39
+ URL.revokeObjectURL(blobUrl);
40
+ } catch {
41
+ window.open(state.url, "_blank");
42
+ }
43
+ };
44
+
45
+ return (
46
+ <div
47
+ className="fixed inset-0 z-[100] flex items-center justify-center"
48
+ style={{ background: "rgba(0,0,0,0.85)" }}
49
+ onClick={close}
50
+ >
51
+ {/* Toolbar */}
52
+ <div
53
+ className="fixed top-0 right-0 flex items-center gap-1 p-3 z-[101]"
54
+ onClick={(e) => e.stopPropagation()}
55
+ >
56
+ <button
57
+ onClick={handleDownload}
58
+ className="p-2 rounded-full hover:bg-white/15 transition-colors"
59
+ title="Download"
60
+ >
61
+ <svg className="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
62
+ <path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
63
+ </svg>
64
+ </button>
65
+ <button
66
+ onClick={close}
67
+ className="p-2 rounded-full hover:bg-white/15 transition-colors"
68
+ title="Close"
69
+ >
70
+ <svg className="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
71
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
72
+ </svg>
73
+ </button>
74
+ </div>
75
+
76
+ {/* Image */}
77
+ <img
78
+ src={state.url}
79
+ alt=""
80
+ className="max-w-[90vw] max-h-[90vh] object-contain rounded select-none"
81
+ style={{ boxShadow: "0 0 40px rgba(0,0,0,0.5)" }}
82
+ onClick={(e) => e.stopPropagation()}
83
+ draggable={false}
84
+ />
85
+ </div>
86
+ );
87
+ }
88
+
89
+ function guessFilename(url: string): string {
90
+ try {
91
+ const path = new URL(url, window.location.href).pathname;
92
+ const name = path.split("/").pop();
93
+ if (name && name.includes(".")) return name;
94
+ } catch { /* ignore */ }
95
+ return `image-${Date.now()}.png`;
96
+ }
@@ -5,6 +5,14 @@ import { useAppDispatch } from "../store";
5
5
  import { dlog } from "../debug-log";
6
6
  import { isFirebaseConfigured, signInWithGoogle, signInWithGitHub, signInWithApple } from "../firebase";
7
7
 
8
+ function PlayIcon() {
9
+ return (
10
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" style={{ flexShrink: 0 }}>
11
+ <path d="M8 5v14l11-7z"/>
12
+ </svg>
13
+ );
14
+ }
15
+
8
16
  /** Google "G" logo SVG */
9
17
  function GoogleIcon() {
10
18
  return (
@@ -44,10 +52,11 @@ export function LoginPage() {
44
52
  const [error, setError] = useState("");
45
53
  const [loading, setLoading] = useState(false);
46
54
  const [oauthLoading, setOauthLoading] = useState<"google" | "github" | "apple" | null>(null);
55
+ const [demoLoading, setDemoLoading] = useState(false);
47
56
  const [authConfig, setAuthConfig] = useState<AuthConfig | null>(null);
48
57
 
49
58
  const firebaseEnabled = isFirebaseConfigured();
50
- const anyLoading = loading || !!oauthLoading;
59
+ const anyLoading = loading || !!oauthLoading || demoLoading;
51
60
 
52
61
  // Fetch server-side auth config to determine which methods are available
53
62
  useEffect(() => {
@@ -133,6 +142,23 @@ export function LoginPage() {
133
142
  }
134
143
  };
135
144
 
145
+ const handleDemoLogin = async () => {
146
+ setError("");
147
+ setDemoLoading(true);
148
+ try {
149
+ dlog.info("Auth", "Starting demo login");
150
+ const res = await authApi.demo();
151
+ dlog.info("Auth", `Demo login success — user ${res.id}`);
152
+ handleAuthSuccess(res);
153
+ } catch (err) {
154
+ const message = err instanceof Error ? err.message : "Demo login failed";
155
+ dlog.error("Auth", `Demo login failed: ${message}`);
156
+ setError(message);
157
+ } finally {
158
+ setDemoLoading(false);
159
+ }
160
+ };
161
+
136
162
  return (
137
163
  <div
138
164
  className="h-screen overflow-y-auto"
@@ -267,6 +293,38 @@ export function LoginPage() {
267
293
  </>
268
294
  )}
269
295
 
296
+ {/* Demo mode */}
297
+ {configLoaded && (
298
+ <>
299
+ <div className="flex items-center gap-3 my-5">
300
+ <div className="flex-1 h-px" style={{ background: "var(--border)" }} />
301
+ <span className="text-caption" style={{ color: "var(--text-muted)" }}>
302
+ or
303
+ </span>
304
+ <div className="flex-1 h-px" style={{ background: "var(--border)" }} />
305
+ </div>
306
+ <button
307
+ type="button"
308
+ onClick={handleDemoLogin}
309
+ disabled={anyLoading}
310
+ className="w-full flex items-center justify-center gap-3 py-2.5 px-4 font-medium text-body rounded-sm disabled:opacity-50 transition-colors hover:brightness-95"
311
+ style={{
312
+ background: "var(--bg-active)",
313
+ color: "#fff",
314
+ }}
315
+ >
316
+ {demoLoading ? (
317
+ <span>Loading demo...</span>
318
+ ) : (
319
+ <>
320
+ <PlayIcon />
321
+ <span>Try Demo</span>
322
+ </>
323
+ )}
324
+ </button>
325
+ </>
326
+ )}
327
+
270
328
  {/* Error display (always visible, e.g. OAuth errors) */}
271
329
  {error && (
272
330
  <div
@@ -1,5 +1,6 @@
1
1
  import React, { useState, useCallback, useMemo, useEffect } from "react";
2
2
  import { E2eService } from "../e2e";
3
+ import { openImageLightbox } from "./ImageLightbox";
3
4
  import ReactMarkdown from "react-markdown";
4
5
  import remarkGfm from "remark-gfm";
5
6
  import rehypeHighlight from "rehype-highlight";
@@ -441,8 +442,9 @@ function renderA2UIComponent(
441
442
  key={id}
442
443
  src={url}
443
444
  alt={alt}
444
- className="max-w-[360px] max-h-64 rounded-md object-contain my-1"
445
+ className="max-w-[360px] max-h-64 rounded-md object-contain my-1 cursor-pointer hover:opacity-90 transition-opacity"
445
446
  style={{ border: "1px solid var(--border)" }}
447
+ onClick={() => openImageLightbox(url)}
446
448
  />
447
449
  );
448
450
  }
@@ -591,6 +593,19 @@ function buildMarkdownComponents(
591
593
  </blockquote>
592
594
  );
593
595
  },
596
+ // Images — open in lightbox
597
+ img({ src, alt, ...props }: React.ImgHTMLAttributes<HTMLImageElement>) {
598
+ return (
599
+ <img
600
+ src={src}
601
+ alt={alt ?? ""}
602
+ className="max-w-[360px] max-h-64 rounded-md object-contain my-1 cursor-pointer hover:opacity-90 transition-opacity"
603
+ style={{ border: "1px solid var(--border)" }}
604
+ onClick={() => src && openImageLightbox(src)}
605
+ {...props}
606
+ />
607
+ );
608
+ },
594
609
  };
595
610
  }
596
611
 
@@ -1138,7 +1153,7 @@ function MediaPreview({ url, mediaContextId }: { url: string; mediaContextId?: s
1138
1153
  alt=""
1139
1154
  className="max-w-[360px] max-h-64 rounded-md object-contain cursor-pointer hover:opacity-90 transition-opacity"
1140
1155
  style={{ border: "1px solid var(--border)" }}
1141
- onClick={() => window.open(effectiveUrl, "_blank")}
1156
+ onClick={() => openImageLightbox(effectiveUrl)}
1142
1157
  />
1143
1158
  );
1144
1159
  }
@@ -143,7 +143,7 @@ export function SessionTabs({ channelId }: SessionTabsProps) {
143
143
  // Auto-create a "General" channel for the default agent (no channelId yet)
144
144
  if (!effectiveChannelId) {
145
145
  dlog.info("Session", "No channel for default agent — auto-creating General channel");
146
- const channel = await channelsApi.create({ name: "General", providerAgentId: "main" });
146
+ const channel = await channelsApi.create({ name: "General", openclawAgentId: "main" });
147
147
  effectiveChannelId = channel.id;
148
148
  // Reload agents and channels so the default agent picks up the new channelId
149
149
  const [{ agents }, { channels: chs }] = await Promise.all([
@@ -128,9 +128,7 @@ export function Sidebar({ onOpenSettings, onNavigate }: { onOpenSettings?: () =>
128
128
  style={{ background: state.openclawConnected ? "var(--accent-green)" : "var(--accent-red)" }}
129
129
  />
130
130
  <span className="text-tiny text-[--text-muted]">
131
- {state.openclawConnected
132
- ? `${Object.values(state.agentConnections).filter(Boolean).length || 1} agent(s) connected`
133
- : "No agents connected"}
131
+ {state.openclawConnected ? "OpenClaw connected" : "OpenClaw offline"}
134
132
  </span>
135
133
  </div>
136
134
  </div>
@@ -7,28 +7,33 @@ import { useRef, useCallback } from "react";
7
7
  * In WebKit/WKWebView, when a user presses Enter to accept an IME candidate,
8
8
  * the event order is: compositionend → keydown(Enter, isComposing=false).
9
9
  * The native `isComposing` flag is already false by the time keydown fires,
10
- * so checking it alone is insufficient. This hook keeps a flag active for one
11
- * animation frame after compositionend to swallow that trailing Enter.
10
+ * so checking it alone is insufficient.
11
+ *
12
+ * We use a timestamp-based guard instead of requestAnimationFrame because
13
+ * rAF timing is unreliable in WKWebView — the callback may fire before the
14
+ * trailing keydown, causing a race condition where Enter is sometimes
15
+ * swallowed and sometimes not.
12
16
  */
17
+ const COMPOSITION_END_GUARD_MS = 80;
18
+
13
19
  export function useIMEComposition() {
14
20
  const composingRef = useRef(false);
15
- const justEndedRef = useRef(false);
21
+ const compositionEndTimeRef = useRef(0);
16
22
 
17
23
  const onCompositionStart = useCallback(() => {
18
24
  composingRef.current = true;
19
- justEndedRef.current = false;
25
+ compositionEndTimeRef.current = 0;
20
26
  }, []);
21
27
 
22
28
  const onCompositionEnd = useCallback(() => {
23
29
  composingRef.current = false;
24
- justEndedRef.current = true;
25
- requestAnimationFrame(() => {
26
- justEndedRef.current = false;
27
- });
30
+ compositionEndTimeRef.current = Date.now();
28
31
  }, []);
29
32
 
30
33
  const isIMEActive = useCallback(
31
- () => composingRef.current || justEndedRef.current,
34
+ () =>
35
+ composingRef.current ||
36
+ Date.now() - compositionEndTimeRef.current < COMPOSITION_END_GUARD_MS,
32
37
  [],
33
38
  );
34
39
 
@@ -1,7 +1,7 @@
1
1
  /** Minimal reactive store using React context + useState. */
2
2
 
3
3
  import { createContext, useContext } from "react";
4
- import type { Agent as ApiAgent, AgentV2, Channel, Task, TaskWithChannel, Job, ModelInfo, Session } from "./api";
4
+ import type { Agent as ApiAgent, Channel, Task, TaskWithChannel, Job, ModelInfo, Session } from "./api";
5
5
 
6
6
  export type ChatMessage = {
7
7
  id: string;
@@ -19,9 +19,6 @@ export type ChatMessage = {
19
19
  encrypted?: boolean | number;
20
20
  /** Whether the media binary was E2E encrypted (derived from sender + encrypted flag) */
21
21
  mediaEncrypted?: boolean;
22
- senderAgentId?: string;
23
- senderAgentName?: string;
24
- targetAgentId?: string;
25
22
  activities?: ActivityItem[];
26
23
  };
27
24
 
@@ -54,10 +51,6 @@ export type AppState = {
54
51
  activeThreadId: string | null;
55
52
  threadReplyCounts: Record<string, number>;
56
53
  openclawConnected: boolean;
57
- /** Per-agent connection status (v2 multi-agent). */
58
- agentConnections: Record<string, boolean>;
59
- /** v2 agent list with detailed info (type, role, skills, capabilities). */
60
- v2Agents: AgentV2[];
61
54
  /** Per-session model override (set via /model or dropdown). null = using defaultModel. */
62
55
  sessionModel: string | null;
63
56
  wsConnected: boolean;
@@ -94,8 +87,6 @@ export const initialState: AppState = {
94
87
  activeThreadId: null,
95
88
  threadReplyCounts: {},
96
89
  openclawConnected: false,
97
- agentConnections: {},
98
- v2Agents: [],
99
90
  sessionModel: null,
100
91
  wsConnected: false,
101
92
  models: [],
@@ -150,8 +141,6 @@ export type AppAction =
150
141
  | { type: "ADD_CRON_JOB"; job: Job }
151
142
  | { type: "UPDATE_CRON_JOB"; job: Job }
152
143
  | { type: "APPEND_JOB_OUTPUT"; jobId: string; text: string }
153
- | { type: "SET_V2_AGENTS"; agents: AgentV2[] }
154
- | { type: "SET_AGENT_CONNECTION"; agentId: string; connected: boolean }
155
144
  | { type: "LOGOUT" };
156
145
 
157
146
  export function appReducer(state: AppState, action: AppAction): AppState {
@@ -335,14 +324,14 @@ export function appReducer(state: AppState, action: AppAction): AppState {
335
324
  threadReplyCounts: updatedCounts,
336
325
  };
337
326
  }
338
- case "SET_OPENCLAW_CONNECTED": {
339
- const anyAgentConnected = action.connected || Object.values(state.agentConnections).some(Boolean);
327
+ case "SET_OPENCLAW_CONNECTED":
328
+ // connection.status carries the gateway defaultModel (OpenClaw primary).
329
+ // Prefer user's saved default (from API/settings); only use gateway default when user has not set one.
340
330
  return {
341
331
  ...state,
342
- openclawConnected: anyAgentConnected,
332
+ openclawConnected: action.connected,
343
333
  defaultModel: state.defaultModel ?? action.defaultModel ?? null,
344
334
  };
345
- }
346
335
  case "SET_SESSION_MODEL":
347
336
  return { ...state, sessionModel: action.model };
348
337
  case "SET_WS_CONNECTED":
@@ -445,7 +434,7 @@ export function appReducer(state: AppState, action: AppAction): AppState {
445
434
  // These fields belong to OpenClaw and are NOT stored in D1.
446
435
  const scanMap = new Map(action.scanTasks.map((s) => [s.cronJobId, s]));
447
436
  const merged = state.cronTasks.map((task) => {
448
- const scan = task.providerJobId ? scanMap.get(task.providerJobId) : null;
437
+ const scan = task.openclawCronJobId ? scanMap.get(task.openclawCronJobId) : null;
449
438
  if (!scan) return task;
450
439
  return {
451
440
  ...task,
@@ -457,7 +446,7 @@ export function appReducer(state: AppState, action: AppAction): AppState {
457
446
  });
458
447
  // Also merge into the messages-view tasks list
459
448
  const mergedTasks = state.tasks.map((task) => {
460
- const scan = task.providerJobId ? scanMap.get(task.providerJobId) : null;
449
+ const scan = task.openclawCronJobId ? scanMap.get(task.openclawCronJobId) : null;
461
450
  if (!scan) return task;
462
451
  return {
463
452
  ...task,
@@ -539,27 +528,6 @@ export function appReducer(state: AppState, action: AppAction): AppState {
539
528
  cronJobs: updateSummary(state.cronJobs),
540
529
  };
541
530
  }
542
- case "SET_V2_AGENTS": {
543
- const connections = Object.fromEntries(
544
- action.agents.map((a) => [a.id, a.status === "connected"]),
545
- );
546
- const anyV2Connected = Object.values(connections).some(Boolean);
547
- return {
548
- ...state,
549
- v2Agents: action.agents,
550
- agentConnections: connections,
551
- openclawConnected: anyV2Connected || state.openclawConnected,
552
- };
553
- }
554
- case "SET_AGENT_CONNECTION": {
555
- const newConnections = { ...state.agentConnections, [action.agentId]: action.connected };
556
- const anyConnected = Object.values(newConnections).some(Boolean);
557
- return {
558
- ...state,
559
- agentConnections: newConnections,
560
- openclawConnected: anyConnected || state.openclawConnected,
561
- };
562
- }
563
531
  case "LOGOUT":
564
532
  return { ...initialState };
565
533
  default:
@@ -148,7 +148,6 @@ export class BotsChatWSClient {
148
148
  this.backoffMs = 1000;
149
149
  this._connected = true;
150
150
  this.opts.onStatusChange(true);
151
- this.opts.onMessage(msg);
152
151
  } else {
153
152
  this.opts.onMessage(msg);
154
153
  }
package/scripts/dev.sh CHANGED
@@ -9,9 +9,6 @@
9
9
  # ./scripts/dev.sh sync — sync plugin to mini.local + rebuild + restart gateway
10
10
  # ./scripts/dev.sh logs — tail gateway logs on mini.local
11
11
  # ./scripts/dev.sh mock — start mock OpenClaw standalone (foreground)
12
- # ./scripts/dev.sh v2 — v2 env: build + migrate (v2 DB) + start on port 8788
13
- # ./scripts/dev.sh v2:reset — nuke v2 local DB, re-migrate, then start
14
- # ./scripts/dev.sh v2:deploy — deploy v2 to Cloudflare
15
12
  set -euo pipefail
16
13
 
17
14
  cd "$(dirname "$0")/.."
@@ -217,40 +214,6 @@ do_logs() {
217
214
  ssh mini.local 'tail -f ~/.openclaw/logs/gateway.log'
218
215
  }
219
216
 
220
- # ── v2 environment ──────────────────────────────────────────────────
221
-
222
- V2_CONFIG="wrangler-v2.toml"
223
- V2_DB="botschat-v2-db"
224
- V2_PORT=8788
225
-
226
- do_v2_migrate() {
227
- info "Applying D1 migrations (v2 local)…"
228
- npx wrangler d1 migrations apply "$V2_DB" --local --config "$V2_CONFIG"
229
- ok "v2 migrations applied"
230
- }
231
-
232
- do_v2_reset() {
233
- warn "Nuking v2 local state (DB + DO)…"
234
- rm -rf "$ROOT/.wrangler/state"
235
- ok "v2 local state wiped"
236
- do_v2_migrate
237
- }
238
-
239
- do_v2_start() {
240
- kill_port "$V2_PORT"
241
- info "Starting wrangler dev (v2) on 0.0.0.0:${V2_PORT}…"
242
- exec npx wrangler dev --config "$V2_CONFIG" --ip 0.0.0.0 --port "$V2_PORT" \
243
- --var ENVIRONMENT:development --var DEV_AUTH_SECRET:"$DEV_AUTH_SECRET"
244
- }
245
-
246
- do_v2_deploy() {
247
- info "Building web frontend…"
248
- npm run build -w packages/web
249
- info "Deploying v2 to Cloudflare…"
250
- npx wrangler deploy --config "$V2_CONFIG"
251
- ok "v2 deployed"
252
- }
253
-
254
217
  # ── Main ─────────────────────────────────────────────────────────────
255
218
 
256
219
  cmd="${1:-}"
@@ -282,22 +245,6 @@ case "$cmd" in
282
245
  shift
283
246
  do_mock "$@"
284
247
  ;;
285
- v2)
286
- do_build_web
287
- do_v2_migrate
288
- do_v2_start
289
- ;;
290
- v2:reset)
291
- do_v2_reset
292
- do_build_web
293
- do_v2_start
294
- ;;
295
- v2:migrate)
296
- do_v2_migrate
297
- ;;
298
- v2:deploy)
299
- do_v2_deploy
300
- ;;
301
248
  *)
302
249
  # Default: full dev experience — build + migrate + server + mock + browser
303
250
  do_build_web
@@ -1,29 +0,0 @@
1
- -- Multi-Agent Architecture: agents table
2
- -- Agent = Type (engine) x Role (persona), independent of channels.
3
-
4
- CREATE TABLE IF NOT EXISTS agents (
5
- id TEXT PRIMARY KEY, -- 'agt_xxx'
6
- user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
7
- name TEXT NOT NULL, -- display name: "OpenClaw", "Cursor", "PM小明"
8
- type TEXT NOT NULL CHECK (type IN ('openclaw', 'cursor_cli', 'cursor_cloud', 'claude_code', 'mock')),
9
- -- Role & Skills
10
- role TEXT NOT NULL DEFAULT 'general', -- 'product_manager', 'developer', 'qa', 'devops', 'general'
11
- system_prompt TEXT NOT NULL DEFAULT '',
12
- skills_json TEXT NOT NULL DEFAULT '[]', -- JSON: [{"name":"...","description":"..."}]
13
- -- Connection credentials (provider-specific, at most one populated)
14
- pairing_token TEXT, -- OpenClaw bc_pat_xxx
15
- api_key TEXT, -- Cursor / Claude API key
16
- config_json TEXT NOT NULL DEFAULT '{}', -- extra: { workspace, repository, ref, ... }
17
- -- Technical capabilities (derived from type)
18
- capabilities TEXT NOT NULL DEFAULT '["chat"]', -- JSON: ["chat","streaming","cron","a2ui","media","code_edit","delegate"]
19
- -- Runtime state
20
- status TEXT NOT NULL DEFAULT 'disconnected' CHECK (status IN ('connected', 'disconnected')),
21
- last_connected_at INTEGER,
22
- connection_count INTEGER NOT NULL DEFAULT 0,
23
- last_ip TEXT,
24
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
25
- updated_at INTEGER NOT NULL DEFAULT (unixepoch())
26
- );
27
- CREATE INDEX IF NOT EXISTS idx_agents_user ON agents(user_id);
28
- CREATE UNIQUE INDEX IF NOT EXISTS idx_agents_pairing_token ON agents(pairing_token) WHERE pairing_token IS NOT NULL;
29
- CREATE UNIQUE INDEX IF NOT EXISTS idx_agents_api_key ON agents(api_key) WHERE api_key IS NOT NULL;
@@ -1,19 +0,0 @@
1
- -- Multi-Agent Architecture: agent_sessions mapping table
2
- -- Maps BotsChat sessions to each agent's provider-side session identifier.
3
- -- e.g. BotsChat ses_001 x Cursor agent -> Cursor chatId "abc-123"
4
-
5
- CREATE TABLE IF NOT EXISTS agent_sessions (
6
- id TEXT PRIMARY KEY, -- 'as_xxx'
7
- session_id TEXT NOT NULL, -- BotsChat session (ses_xxx)
8
- agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
9
- provider_session_id TEXT, -- provider-specific session ID
10
- -- OpenClaw: session_key "agent:main:botschat:u_xxx:ses:ses_xxx"
11
- -- Cursor CLI: chatId (uuid from `agent create-chat`)
12
- -- Cursor Cloud: cloud agent id "bc_xxx"
13
- metadata_json TEXT NOT NULL DEFAULT '{}',
14
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
15
- updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
16
- UNIQUE(session_id, agent_id)
17
- );
18
- CREATE INDEX IF NOT EXISTS idx_agent_sessions_session ON agent_sessions(session_id);
19
- CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent ON agent_sessions(agent_id);
@@ -1,27 +0,0 @@
1
- -- Multi-Agent Architecture: message_traces table
2
- -- Stores agent execution traces at different verbosity levels.
3
- -- lv1 = conclusions (stored in messages table)
4
- -- lv2 = thinking / reasoning process
5
- -- lv3 = reference material / tool call results
6
-
7
- CREATE TABLE IF NOT EXISTS message_traces (
8
- id TEXT PRIMARY KEY, -- 'mt_xxx'
9
- message_id TEXT NOT NULL, -- parent lv1 message (messages.id)
10
- user_id TEXT NOT NULL,
11
- session_key TEXT NOT NULL,
12
- agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
13
- verbose_level INTEGER NOT NULL CHECK (verbose_level IN (2, 3)),
14
- trace_type TEXT NOT NULL,
15
- -- lv2: 'thinking', 'planning', 'reasoning', 'decision'
16
- -- lv3: 'file_read', 'file_write', 'command_exec', 'search_result', 'tool_call', 'reference'
17
- content BLOB NOT NULL,
18
- metadata_json TEXT NOT NULL DEFAULT '{}',
19
- -- lv3 file_read: { "path": "src/auth.ts", "lines": 42 }
20
- -- lv3 command_exec: { "command": "npm test", "exitCode": 0 }
21
- -- lv3 file_write: { "path": "src/auth.ts", "linesChanged": 5 }
22
- encrypted INTEGER NOT NULL DEFAULT 0,
23
- created_at INTEGER NOT NULL DEFAULT (unixepoch())
24
- );
25
- CREATE INDEX IF NOT EXISTS idx_traces_message ON message_traces(message_id);
26
- CREATE INDEX IF NOT EXISTS idx_traces_session_level ON message_traces(session_key, verbose_level, created_at);
27
- CREATE INDEX IF NOT EXISTS idx_traces_agent ON message_traces(agent_id, created_at);
@@ -1,9 +0,0 @@
1
- -- Multi-Agent Architecture: extend channels and messages for multi-agent support.
2
-
3
- -- Channels: add default agent + rename openclaw-specific column
4
- ALTER TABLE channels ADD COLUMN default_agent_id TEXT REFERENCES agents(id);
5
- ALTER TABLE channels RENAME COLUMN openclaw_agent_id TO provider_agent_id;
6
-
7
- -- Messages: track which agent sent/received each message
8
- ALTER TABLE messages ADD COLUMN sender_agent_id TEXT REFERENCES agents(id);
9
- ALTER TABLE messages ADD COLUMN target_agent_id TEXT REFERENCES agents(id);
@@ -1,2 +0,0 @@
1
- -- Multi-Agent: rename openclaw_cron_job_id -> provider_job_id in tasks table
2
- ALTER TABLE tasks RENAME COLUMN openclaw_cron_job_id TO provider_job_id;