pockcode 0.0.1

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.
@@ -0,0 +1,305 @@
1
+ #!/usr/bin/env node
2
+ import { createServer } from "node:http";
3
+ import { createReadStream } from "node:fs";
4
+ import { readFile, stat } from "node:fs/promises";
5
+ import { dirname, extname, resolve, sep } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ //#region bin/pockcode.ts
8
+ var defaultHost = "127.0.0.1";
9
+ var defaultPort = 4733;
10
+ async function main() {
11
+ const parsed = parseArgs(process.argv.slice(2));
12
+ if (parsed === "help") {
13
+ console.log(helpText());
14
+ return;
15
+ }
16
+ if (parsed === "version") {
17
+ console.log(await readPackageVersion());
18
+ return;
19
+ }
20
+ process.env.NODE_ENV ||= "production";
21
+ if (parsed.home) process.env.POCKCODE_HOME = parsed.home;
22
+ const [api, auth, socket, chatMonitor, scheduleMonitor, pluginManager] = await Promise.all([
23
+ import("./assets/api.server-D6ONM6ZJ.js"),
24
+ import("./assets/auth.server-B_P8Jm6O.js"),
25
+ import("./assets/socket.server-7dC-9Fnw.js").then((n) => n.a),
26
+ import("./assets/chat-status-monitor.server-D2VG_D8P.js"),
27
+ import("./assets/message-schedule-monitor.server-C4NZg62k.js"),
28
+ import("./assets/manager.server-Cru4ZxHo.js").then((n) => n.r)
29
+ ]);
30
+ const clientRoot = await resolveClientRoot();
31
+ const server = createServer((req, res) => {
32
+ (async () => {
33
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
34
+ if (await handleAuthGate(req, res, url, auth)) return;
35
+ if (url.pathname.startsWith("/api/")) {
36
+ await api.handleApiRequest(req, res, url).catch((error) => api.sendRouteError(res, error));
37
+ return;
38
+ }
39
+ await serveClientAsset(req, res, url, clientRoot);
40
+ })().catch((error) => {
41
+ sendText(res, 500, error instanceof Error ? error.message : "Request failed.");
42
+ });
43
+ });
44
+ socket.installProviderSocketServer(server, { authenticateRequest: async (req) => await auth.hasConfiguredPassword() && await auth.isRequestAuthorized(req) });
45
+ chatMonitor.startChatStatusMonitor();
46
+ scheduleMonitor.startMessageScheduleMonitor();
47
+ pluginManager.startPluginRuntimeManager();
48
+ server.listen(parsed.port, parsed.host, () => {
49
+ console.log(`pockcode listening on http://${displayHost(parsed.host)}:${parsed.port}`);
50
+ console.log("First visit will ask you to set a password if one is not configured.");
51
+ });
52
+ for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => {
53
+ server.close(() => process.exit(0));
54
+ });
55
+ }
56
+ async function handleAuthGate(req, res, url, auth) {
57
+ if (!await auth.hasConfiguredPassword()) {
58
+ if (url.pathname === "/auth/setup" && req.method === "POST") {
59
+ await handlePasswordSetup(req, res, auth);
60
+ return true;
61
+ }
62
+ if (req.method === "GET" || req.method === "HEAD") {
63
+ sendSetupPage(res);
64
+ return true;
65
+ }
66
+ sendJson(res, 428, { error: "Pockcode password setup is required." });
67
+ return true;
68
+ }
69
+ if (await auth.isRequestAuthorized(req)) return false;
70
+ res.statusCode = 401;
71
+ res.setHeader("WWW-Authenticate", `Basic realm="${auth.pockcodeAuthRealm()}", charset="UTF-8"`);
72
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
73
+ res.end("Authentication required.");
74
+ return true;
75
+ }
76
+ async function handlePasswordSetup(req, res, auth) {
77
+ const body = await readRequestBody(req, 32e3);
78
+ const contentType = req.headers["content-type"] ?? "";
79
+ const wantsJson = req.headers.accept?.includes("application/json") || contentType.includes("application/json");
80
+ const password = contentType.includes("application/json") ? readJsonPassword(body) : new URLSearchParams(body).get("password") ?? "";
81
+ try {
82
+ await auth.setupPassword(password);
83
+ } catch (error) {
84
+ const message = error instanceof Error ? error.message : "Unable to configure password.";
85
+ if (wantsJson) sendJson(res, 400, { error: message });
86
+ else sendSetupPage(res, message, 400);
87
+ return;
88
+ }
89
+ if (wantsJson) {
90
+ sendJson(res, 201, { ok: true });
91
+ return;
92
+ }
93
+ res.statusCode = 303;
94
+ res.setHeader("Location", "/");
95
+ res.end();
96
+ }
97
+ async function serveClientAsset(req, res, url, clientRoot) {
98
+ if (req.method !== "GET" && req.method !== "HEAD") {
99
+ sendText(res, 405, `${req.method ?? "METHOD"} is not supported.`);
100
+ return;
101
+ }
102
+ const filePath = await resolveClientPath(clientRoot, url.pathname);
103
+ if (!filePath) {
104
+ sendText(res, 403, "Forbidden.");
105
+ return;
106
+ }
107
+ const stats = await stat(filePath).catch(() => null);
108
+ const targetPath = stats?.isFile() ? filePath : resolve(clientRoot, "index.html");
109
+ const targetStats = stats?.isFile() ? stats : await stat(targetPath).catch(() => null);
110
+ if (!targetStats?.isFile()) {
111
+ sendText(res, 404, "Pockcode client build was not found. Run pnpm build first.");
112
+ return;
113
+ }
114
+ res.statusCode = 200;
115
+ res.setHeader("Content-Length", String(targetStats.size));
116
+ res.setHeader("Content-Type", contentTypeFor(targetPath));
117
+ if (targetPath.includes(`${sep}assets${sep}`)) res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
118
+ else res.setHeader("Cache-Control", "no-store");
119
+ if (req.method === "HEAD") {
120
+ res.end();
121
+ return;
122
+ }
123
+ createReadStream(targetPath).pipe(res);
124
+ }
125
+ async function resolveClientPath(clientRoot, pathname) {
126
+ let decoded = "/";
127
+ try {
128
+ decoded = decodeURIComponent(pathname);
129
+ } catch {
130
+ return null;
131
+ }
132
+ if (decoded.includes("\0")) return null;
133
+ const filePath = resolve(clientRoot, decoded === "/" ? "index.html" : decoded.replace(/^\/+/u, ""));
134
+ return filePath === clientRoot || filePath.startsWith(`${clientRoot}${sep}`) ? filePath : null;
135
+ }
136
+ async function resolveClientRoot() {
137
+ const here = dirname(fileURLToPath(import.meta.url));
138
+ const candidates = [
139
+ process.env.POCKCODE_CLIENT_DIR,
140
+ resolve(here, "../build/client"),
141
+ resolve(process.cwd(), "build/client")
142
+ ].filter((value) => Boolean(value));
143
+ for (const candidate of candidates) {
144
+ const root = resolve(candidate);
145
+ if (await stat(resolve(root, "index.html")).then((stats) => stats.isFile()).catch(() => false)) return root;
146
+ }
147
+ return resolve(candidates[0] ?? "build/client");
148
+ }
149
+ async function readRequestBody(req, limit) {
150
+ const chunks = [];
151
+ let size = 0;
152
+ for await (const chunk of req) {
153
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
154
+ size += buffer.length;
155
+ if (size > limit) throw new Error("Request body is too large.");
156
+ chunks.push(buffer);
157
+ }
158
+ return Buffer.concat(chunks).toString("utf8");
159
+ }
160
+ function readJsonPassword(body) {
161
+ try {
162
+ const value = JSON.parse(body);
163
+ return value && typeof value === "object" && !Array.isArray(value) && typeof value.password === "string" ? value.password : "";
164
+ } catch {
165
+ return "";
166
+ }
167
+ }
168
+ function parseArgs(argv) {
169
+ const options = {
170
+ host: defaultHost,
171
+ port: readPort(process.env.PORT) ?? defaultPort
172
+ };
173
+ for (let index = 0; index < argv.length; index += 1) {
174
+ const arg = argv[index];
175
+ if (arg === "--help" || arg === "-h") return "help";
176
+ if (arg === "--version" || arg === "-v") return "version";
177
+ if (arg === "--host" || arg === "--bind" || arg === "-H") {
178
+ options.host = readValue(argv, ++index, arg);
179
+ continue;
180
+ }
181
+ if (arg.startsWith("--host=") || arg.startsWith("--bind=")) {
182
+ options.host = arg.slice(arg.indexOf("=") + 1);
183
+ continue;
184
+ }
185
+ if (arg === "--port" || arg === "-p") {
186
+ options.port = readPort(readValue(argv, ++index, arg)) ?? invalidPort(arg);
187
+ continue;
188
+ }
189
+ if (arg.startsWith("--port=")) {
190
+ options.port = readPort(arg.slice(7)) ?? invalidPort("--port");
191
+ continue;
192
+ }
193
+ if (arg === "--home") {
194
+ options.home = readValue(argv, ++index, arg);
195
+ continue;
196
+ }
197
+ if (arg.startsWith("--home=")) {
198
+ options.home = arg.slice(7);
199
+ continue;
200
+ }
201
+ throw new Error(`Unknown option: ${arg}\n\n${helpText()}`);
202
+ }
203
+ return options;
204
+ }
205
+ function readValue(argv, index, option) {
206
+ const value = argv[index];
207
+ if (!value || value.startsWith("-")) throw new Error(`${option} requires a value.`);
208
+ return value;
209
+ }
210
+ function readPort(value) {
211
+ if (!value) return null;
212
+ const port = Number.parseInt(value, 10);
213
+ return Number.isInteger(port) && port > 0 && port <= 65535 ? port : null;
214
+ }
215
+ function invalidPort(option) {
216
+ throw new Error(`${option} requires a port between 1 and 65535.`);
217
+ }
218
+ function sendSetupPage(res, error, status = 200) {
219
+ res.statusCode = status;
220
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
221
+ res.end(`<!doctype html>
222
+ <html lang="en">
223
+ <head>
224
+ <meta charset="utf-8" />
225
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
226
+ <title>pockcode setup</title>
227
+ <style>
228
+ :root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
229
+ body { min-height: 100vh; margin: 0; display: grid; place-items: center; background: #0f1115; color: #f6f7fb; }
230
+ main { width: min(420px, calc(100vw - 32px)); border: 1px solid #2a2f3a; border-radius: 10px; background: #161922; padding: 24px; box-shadow: 0 24px 80px rgb(0 0 0 / 0.35); }
231
+ h1 { margin: 0 0 8px; font-size: 20px; }
232
+ p { margin: 0 0 18px; color: #aeb6c5; font-size: 14px; line-height: 1.5; }
233
+ label { display: grid; gap: 8px; font-size: 13px; font-weight: 600; }
234
+ input { height: 38px; border-radius: 8px; border: 1px solid #343b49; background: #0f1115; color: #f6f7fb; padding: 0 12px; font: inherit; }
235
+ button { margin-top: 16px; height: 38px; width: 100%; border: 0; border-radius: 8px; background: #f6f7fb; color: #111318; font-weight: 700; cursor: pointer; }
236
+ .error { margin-bottom: 14px; color: #ff9a9a; font-size: 13px; }
237
+ </style>
238
+ </head>
239
+ <body>
240
+ <main>
241
+ <h1>Set up pockcode</h1>
242
+ <p>Create the password used for HTTP Basic authentication.</p>
243
+ ${error ? `<div class="error">${escapeHtml(error)}</div>` : ""}
244
+ <form method="post" action="/auth/setup">
245
+ <label>
246
+ Password
247
+ <input name="password" type="password" minlength="8" autocomplete="new-password" autofocus required />
248
+ </label>
249
+ <button type="submit">Save password</button>
250
+ </form>
251
+ </main>
252
+ </body>
253
+ </html>`);
254
+ }
255
+ function sendJson(res, status, data) {
256
+ res.statusCode = status;
257
+ res.setHeader("Content-Type", "application/json");
258
+ res.end(JSON.stringify(data));
259
+ }
260
+ function sendText(res, status, text) {
261
+ if (res.writableEnded) return;
262
+ res.statusCode = status;
263
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
264
+ res.end(text);
265
+ }
266
+ function contentTypeFor(filePath) {
267
+ const extension = extname(filePath);
268
+ if (extension === ".html") return "text/html; charset=utf-8";
269
+ if (extension === ".css") return "text/css; charset=utf-8";
270
+ if (extension === ".js" || extension === ".mjs") return "text/javascript; charset=utf-8";
271
+ if (extension === ".json") return "application/json; charset=utf-8";
272
+ if (extension === ".svg") return "image/svg+xml";
273
+ if (extension === ".png") return "image/png";
274
+ if (extension === ".jpg" || extension === ".jpeg") return "image/jpeg";
275
+ if (extension === ".webp") return "image/webp";
276
+ if (extension === ".woff2") return "font/woff2";
277
+ return "application/octet-stream";
278
+ }
279
+ function displayHost(host) {
280
+ return host === "0.0.0.0" || host === "::" ? "localhost" : host;
281
+ }
282
+ async function readPackageVersion() {
283
+ const packagePath = resolve(dirname(fileURLToPath(import.meta.url)), "../package.json");
284
+ return JSON.parse(await readFile(packagePath, "utf8")).version ?? "0.0.0";
285
+ }
286
+ function escapeHtml(value) {
287
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
288
+ }
289
+ function helpText() {
290
+ return `Usage: pockcode [options]
291
+
292
+ Options:
293
+ -H, --host, --bind <host> Host/interface to bind (default: ${defaultHost})
294
+ -p, --port <port> Port to listen on (default: ${defaultPort})
295
+ --home <path> Pockcode data directory (default: ~/.pockcode)
296
+ -v, --version Print version
297
+ -h, --help Print help
298
+ `;
299
+ }
300
+ main().catch((error) => {
301
+ console.error(error instanceof Error ? error.message : error);
302
+ process.exit(1);
303
+ });
304
+ //#endregion
305
+ export {};
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "pockcode",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/monokaijs/pockcode.git"
8
+ },
9
+ "engines": {
10
+ "node": ">=20.0.0"
11
+ },
12
+ "bin": {
13
+ "pockcode": "./dist/pockcode.js"
14
+ },
15
+ "files": [
16
+ "build/client",
17
+ "dist",
18
+ "prisma/schema.prisma"
19
+ ],
20
+ "publishConfig": {
21
+ "registry": "https://registry.npmjs.org/"
22
+ },
23
+ "packageManager": "pnpm@10.17.1",
24
+ "scripts": {
25
+ "dev": "vite --host 0.0.0.0",
26
+ "build": "tsc -b && vite build --config vite.client.config.ts && vite build --config vite.cli.config.ts",
27
+ "postinstall": "prisma generate --schema=prisma/schema.prisma",
28
+ "prepack": "npm run build",
29
+ "preview": "node dist/pockcode.js --host 0.0.0.0",
30
+ "test": "vitest run",
31
+ "test:real-codex": "node scripts/real-codex-smoke.mjs",
32
+ "typecheck": "tsc --noEmit"
33
+ },
34
+ "dependencies": {
35
+ "@base-ui/react": "^1.6.0",
36
+ "@fontsource-variable/geist": "^5.2.9",
37
+ "@monaco-editor/react": "^4.7.0",
38
+ "@openai/codex": "^0.130.0",
39
+ "@prisma/client": "^6.19.3",
40
+ "@react-router/dev": "^7.15.1",
41
+ "@xterm/addon-fit": "^0.11.0",
42
+ "@xterm/xterm": "^6.0.0",
43
+ "class-variance-authority": "^0.7.1",
44
+ "clsx": "^2.1.1",
45
+ "dotenv": "^17.4.2",
46
+ "grammy": "^1.44.0",
47
+ "lucide-react": "^1.21.0",
48
+ "monaco-editor": "^0.55.1",
49
+ "node-pty": "^1.1.0",
50
+ "prisma": "^6.19.3",
51
+ "react": "^19.2.7",
52
+ "react-dom": "^19.2.7",
53
+ "react-router": "^7.15.1",
54
+ "smol-toml": "^1.7.0",
55
+ "socket.io": "^4.8.3",
56
+ "socket.io-client": "^4.8.3",
57
+ "tailwind-merge": "^3.6.0",
58
+ "tw-animate-css": "^1.4.0"
59
+ },
60
+ "devDependencies": {
61
+ "@tailwindcss/vite": "^4.3.1",
62
+ "@types/node": "^24.12.4",
63
+ "@types/react": "^19.2.14",
64
+ "@types/react-dom": "^19.2.3",
65
+ "@vitejs/plugin-react": "^6.0.2",
66
+ "tailwindcss": "^4.3.1",
67
+ "typescript": "^5.9.3",
68
+ "vite": "^8.0.16",
69
+ "vitest": "^4.1.9"
70
+ }
71
+ }
@@ -0,0 +1,234 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "sqlite"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ enum ProviderAccountStatus {
11
+ DISCONNECTED
12
+ AUTHENTICATING
13
+ CONNECTED
14
+ INVALIDATED
15
+ ERROR
16
+ }
17
+
18
+ enum ChatStatus {
19
+ IDLE
20
+ RUNNING
21
+ ARCHIVED
22
+ }
23
+
24
+ enum MessageScheduleStatus {
25
+ ACTIVE
26
+ PAUSED
27
+ COMPLETED
28
+ ARCHIVED
29
+ }
30
+
31
+ enum MessageScheduleRunStatus {
32
+ QUEUED
33
+ RUNNING
34
+ COMPLETED
35
+ FAILED
36
+ CANCELLED
37
+ }
38
+
39
+ enum RunStatus {
40
+ QUEUED
41
+ RUNNING
42
+ COMPLETED
43
+ FAILED
44
+ CANCELLED
45
+ }
46
+
47
+ model ProviderSetting {
48
+ providerId String @id
49
+ settings Json @default("{}")
50
+ createdAt DateTime @default(now())
51
+ updatedAt DateTime @updatedAt
52
+ }
53
+
54
+ model PluginSetting {
55
+ pluginId String @id
56
+ enabled Boolean @default(false)
57
+ settings Json @default("{}")
58
+ secrets Json @default("{}")
59
+ createdAt DateTime @default(now())
60
+ updatedAt DateTime @updatedAt
61
+ }
62
+
63
+ model PluginState {
64
+ pluginId String @id
65
+ state Json @default("{}")
66
+ createdAt DateTime @default(now())
67
+ updatedAt DateTime @updatedAt
68
+ }
69
+
70
+ model WorkspaceHistory {
71
+ id String @default(uuid()) @unique
72
+ path String @id
73
+ name String
74
+ lastOpenedAt DateTime @default(now())
75
+ createdAt DateTime @default(now())
76
+ updatedAt DateTime @updatedAt
77
+
78
+ @@index([lastOpenedAt])
79
+ }
80
+
81
+ model ProviderAccount {
82
+ id String @id @default(uuid())
83
+ providerId String
84
+ displayName String
85
+ status ProviderAccountStatus @default(DISCONNECTED)
86
+ settings Json @default("{}")
87
+ runtimeDefaults Json @default("{}")
88
+ authState Json?
89
+ lastAuthUrl String?
90
+ lastAuthMode String?
91
+ lastAuthLoginId String?
92
+ lastAuthUserCode String?
93
+ lastError String?
94
+ createdAt DateTime @default(now())
95
+ updatedAt DateTime @updatedAt
96
+ chats Chat[] @relation("ChatAccount")
97
+ runs ChatRun[] @relation("RunAccount")
98
+
99
+ @@index([providerId])
100
+ @@index([status])
101
+ }
102
+
103
+ model McpServer {
104
+ id String @id @default(uuid())
105
+ name String @unique
106
+ displayName String?
107
+ transport String
108
+ config Json @default("{}")
109
+ enabled Boolean @default(true)
110
+ required Boolean @default(false)
111
+ startupTimeoutSec Float?
112
+ toolTimeoutSec Float?
113
+ toolPolicy Json @default("{}")
114
+ adapterSettings Json @default("{}")
115
+ createdAt DateTime @default(now())
116
+ updatedAt DateTime @updatedAt
117
+ installations McpServerInstallation[]
118
+ }
119
+
120
+ model McpServerInstallation {
121
+ id String @id @default(uuid())
122
+ serverId String
123
+ providerId String
124
+ accountId String
125
+ enabled Boolean @default(true)
126
+ lastSyncAt DateTime?
127
+ lastStatus String?
128
+ lastError String?
129
+ createdAt DateTime @default(now())
130
+ updatedAt DateTime @updatedAt
131
+ server McpServer @relation(fields: [serverId], references: [id], onDelete: Cascade)
132
+
133
+ @@unique([serverId, providerId, accountId])
134
+ @@index([providerId, accountId])
135
+ }
136
+
137
+ model Chat {
138
+ id String @id @default(uuid())
139
+ providerId String
140
+ accountId String?
141
+ autoRotateAccount Boolean @default(false)
142
+ title String
143
+ workingDirectory String?
144
+ model String?
145
+ reasoningEffort String?
146
+ serviceTier String?
147
+ collaborationMode String @default("default")
148
+ permissionMode String @default("default")
149
+ status ChatStatus @default(IDLE)
150
+ externalThreadId String?
151
+ lastActivityAt DateTime @default(now())
152
+ createdAt DateTime @default(now())
153
+ updatedAt DateTime @updatedAt
154
+ account ProviderAccount? @relation("ChatAccount", fields: [accountId], references: [id], onDelete: SetNull)
155
+ runs ChatRun[]
156
+
157
+ @@index([providerId])
158
+ @@index([accountId])
159
+ @@index([updatedAt])
160
+ @@index([lastActivityAt])
161
+ @@index([externalThreadId])
162
+ }
163
+
164
+ model ChatRun {
165
+ id String @id @default(uuid())
166
+ chatId String
167
+ providerId String
168
+ accountId String?
169
+ status RunStatus @default(QUEUED)
170
+ request Json @default("{}")
171
+ error String?
172
+ externalTurnId String?
173
+ interruptRequestedAt DateTime?
174
+ startedAt DateTime?
175
+ endedAt DateTime?
176
+ createdAt DateTime @default(now())
177
+ updatedAt DateTime @updatedAt
178
+ chat Chat @relation(fields: [chatId], references: [id], onDelete: Cascade)
179
+ account ProviderAccount? @relation("RunAccount", fields: [accountId], references: [id], onDelete: SetNull)
180
+
181
+ @@index([chatId, createdAt])
182
+ @@index([providerId])
183
+ @@index([accountId])
184
+ @@index([externalTurnId])
185
+ }
186
+
187
+ model MessageSchedule {
188
+ id String @id @default(uuid())
189
+ title String
190
+ message String
191
+ workingDirectory String
192
+ chatId String?
193
+ providerId String
194
+ accountId String?
195
+ model String?
196
+ reasoningEffort String?
197
+ serviceTier String?
198
+ collaborationMode String @default("default")
199
+ permissionMode String @default("default")
200
+ goalObjective String?
201
+ status MessageScheduleStatus @default(ACTIVE)
202
+ recurrence Json @default("{}")
203
+ nextRunAt DateTime?
204
+ lastRunAt DateTime?
205
+ lastRunStatus String?
206
+ createdAt DateTime @default(now())
207
+ updatedAt DateTime @updatedAt
208
+ runs MessageScheduleRun[]
209
+
210
+ @@index([workingDirectory])
211
+ @@index([status, nextRunAt])
212
+ @@index([chatId])
213
+ @@index([accountId])
214
+ }
215
+
216
+ model MessageScheduleRun {
217
+ id String @id @default(uuid())
218
+ scheduleId String
219
+ chatId String?
220
+ chatRunId String?
221
+ scheduledFor DateTime
222
+ status MessageScheduleRunStatus @default(QUEUED)
223
+ error String?
224
+ startedAt DateTime?
225
+ endedAt DateTime?
226
+ createdAt DateTime @default(now())
227
+ updatedAt DateTime @updatedAt
228
+ schedule MessageSchedule @relation(fields: [scheduleId], references: [id], onDelete: Cascade)
229
+
230
+ @@index([scheduleId, scheduledFor])
231
+ @@index([chatId])
232
+ @@index([chatRunId])
233
+ @@index([status])
234
+ }