verybot 0.1.8
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/README.md +167 -0
- package/dist/aliases/store.d.ts +21 -0
- package/dist/aliases/store.js +148 -0
- package/dist/aliases/types.d.ts +6 -0
- package/dist/aliases/types.js +1 -0
- package/dist/brain/agent-registry.d.ts +96 -0
- package/dist/brain/agent-registry.js +141 -0
- package/dist/brain/agent.d.ts +167 -0
- package/dist/brain/agent.js +932 -0
- package/dist/brain/channel-store.d.ts +27 -0
- package/dist/brain/channel-store.js +78 -0
- package/dist/brain/compaction.d.ts +37 -0
- package/dist/brain/compaction.js +214 -0
- package/dist/brain/context.d.ts +43 -0
- package/dist/brain/context.js +139 -0
- package/dist/brain/delegation-store.d.ts +33 -0
- package/dist/brain/delegation-store.js +106 -0
- package/dist/brain/loop.d.ts +24 -0
- package/dist/brain/loop.js +318 -0
- package/dist/brain/mcp-adapter.d.ts +43 -0
- package/dist/brain/mcp-adapter.js +244 -0
- package/dist/brain/memory-extractor.d.ts +26 -0
- package/dist/brain/memory-extractor.js +82 -0
- package/dist/brain/providers.d.ts +14 -0
- package/dist/brain/providers.js +85 -0
- package/dist/brain/queue.d.ts +18 -0
- package/dist/brain/queue.js +111 -0
- package/dist/brain/run-tools.d.ts +50 -0
- package/dist/brain/run-tools.js +136 -0
- package/dist/brain/session-key.d.ts +23 -0
- package/dist/brain/session-key.js +41 -0
- package/dist/brain/session-state.d.ts +36 -0
- package/dist/brain/session-state.js +51 -0
- package/dist/brain/session-store.d.ts +50 -0
- package/dist/brain/session-store.js +207 -0
- package/dist/brain/session.d.ts +32 -0
- package/dist/brain/session.js +75 -0
- package/dist/brain/task-subscriber.d.ts +56 -0
- package/dist/brain/task-subscriber.js +317 -0
- package/dist/brain/user-content.d.ts +16 -0
- package/dist/brain/user-content.js +32 -0
- package/dist/brain/utils.d.ts +4 -0
- package/dist/brain/utils.js +26 -0
- package/dist/brain/worker-coordinator.d.ts +25 -0
- package/dist/brain/worker-coordinator.js +83 -0
- package/dist/channels/commands.d.ts +50 -0
- package/dist/channels/commands.js +132 -0
- package/dist/channels/discord/channel.d.ts +29 -0
- package/dist/channels/discord/channel.js +159 -0
- package/dist/channels/discord/markdown.d.ts +19 -0
- package/dist/channels/discord/markdown.js +62 -0
- package/dist/channels/manager.d.ts +29 -0
- package/dist/channels/manager.js +100 -0
- package/dist/channels/slack/channel.d.ts +37 -0
- package/dist/channels/slack/channel.js +227 -0
- package/dist/channels/slack/markdown.d.ts +19 -0
- package/dist/channels/slack/markdown.js +62 -0
- package/dist/channels/specs.d.ts +32 -0
- package/dist/channels/specs.js +99 -0
- package/dist/channels/telegram/channel.d.ts +29 -0
- package/dist/channels/telegram/channel.js +182 -0
- package/dist/channels/telegram/markdown.d.ts +17 -0
- package/dist/channels/telegram/markdown.js +66 -0
- package/dist/channels/types.d.ts +26 -0
- package/dist/channels/types.js +1 -0
- package/dist/channels/whatsapp/channel.d.ts +34 -0
- package/dist/channels/whatsapp/channel.js +276 -0
- package/dist/channels/whatsapp/markdown.d.ts +20 -0
- package/dist/channels/whatsapp/markdown.js +51 -0
- package/dist/cli/claude-login.d.ts +5 -0
- package/dist/cli/claude-login.js +47 -0
- package/dist/cli/config.d.ts +5 -0
- package/dist/cli/config.js +78 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.js +96 -0
- package/dist/computer/browser/actions.d.ts +31 -0
- package/dist/computer/browser/actions.js +148 -0
- package/dist/computer/browser/context-manager.d.ts +28 -0
- package/dist/computer/browser/context-manager.js +78 -0
- package/dist/computer/browser/manager.d.ts +91 -0
- package/dist/computer/browser/manager.js +344 -0
- package/dist/computer/browser/profile-badge.d.ts +13 -0
- package/dist/computer/browser/profile-badge.js +67 -0
- package/dist/computer/browser/screenshot.d.ts +5 -0
- package/dist/computer/browser/screenshot.js +21 -0
- package/dist/computer/browser/snapshot.d.ts +30 -0
- package/dist/computer/browser/snapshot.js +242 -0
- package/dist/computer/browser/tools.d.ts +5 -0
- package/dist/computer/browser/tools.js +167 -0
- package/dist/computer/browser/types.d.ts +26 -0
- package/dist/computer/browser/types.js +1 -0
- package/dist/computer/desktop/adapter.d.ts +25 -0
- package/dist/computer/desktop/adapter.js +11 -0
- package/dist/computer/desktop/macos.d.ts +24 -0
- package/dist/computer/desktop/macos.js +223 -0
- package/dist/computer/desktop/tools.d.ts +25 -0
- package/dist/computer/desktop/tools.js +114 -0
- package/dist/config/agent-config.d.ts +55 -0
- package/dist/config/agent-config.js +16 -0
- package/dist/config/model-catalog.d.ts +22 -0
- package/dist/config/model-catalog.js +112 -0
- package/dist/config/model-spec.d.ts +8 -0
- package/dist/config/model-spec.js +66 -0
- package/dist/config/store.d.ts +25 -0
- package/dist/config/store.js +143 -0
- package/dist/config.d.ts +110 -0
- package/dist/config.js +259 -0
- package/dist/control-ui/assets/index-Cbl7G5Sc.css +1 -0
- package/dist/control-ui/assets/index-Cu1P4C62.js +266 -0
- package/dist/control-ui/assets/noto-sans-cyrillic-ext-wght-normal-DSNfmdVt.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-cyrillic-wght-normal-B2hlT84T.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-devanagari-wght-normal-Cv-Vwajv.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-greek-ext-wght-normal-12T8GTDR.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-greek-wght-normal-Ymb6dZNd.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-latin-ext-wght-normal-W1qJv59z.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-latin-wght-normal-BYSzYMf3.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-vietnamese-wght-normal-DLTJy58D.woff2 +0 -0
- package/dist/control-ui/index.html +14 -0
- package/dist/control-ui/vite.svg +1 -0
- package/dist/events.d.ts +2 -0
- package/dist/events.js +11 -0
- package/dist/gateway/broadcast.d.ts +5 -0
- package/dist/gateway/broadcast.js +33 -0
- package/dist/gateway/methods/aliases.d.ts +17 -0
- package/dist/gateway/methods/aliases.js +22 -0
- package/dist/gateway/methods/chat.d.ts +33 -0
- package/dist/gateway/methods/chat.js +37 -0
- package/dist/gateway/methods/config.d.ts +14 -0
- package/dist/gateway/methods/config.js +24 -0
- package/dist/gateway/methods/models.d.ts +10 -0
- package/dist/gateway/methods/models.js +14 -0
- package/dist/gateway/methods/playbooks.d.ts +45 -0
- package/dist/gateway/methods/playbooks.js +488 -0
- package/dist/gateway/methods/prompt-templates.d.ts +27 -0
- package/dist/gateway/methods/prompt-templates.js +106 -0
- package/dist/gateway/methods/scheduler.d.ts +62 -0
- package/dist/gateway/methods/scheduler.js +129 -0
- package/dist/gateway/methods/sessions.d.ts +44 -0
- package/dist/gateway/methods/sessions.js +111 -0
- package/dist/gateway/methods/system.d.ts +12 -0
- package/dist/gateway/methods/system.js +39 -0
- package/dist/gateway/methods/tasks.d.ts +40 -0
- package/dist/gateway/methods/tasks.js +151 -0
- package/dist/gateway/methods/teams.d.ts +69 -0
- package/dist/gateway/methods/teams.js +376 -0
- package/dist/gateway/methods/tools.d.ts +6 -0
- package/dist/gateway/methods/tools.js +7 -0
- package/dist/gateway/methods/whatsapp.d.ts +19 -0
- package/dist/gateway/methods/whatsapp.js +35 -0
- package/dist/gateway/rpc.d.ts +38 -0
- package/dist/gateway/rpc.js +79 -0
- package/dist/gateway/server.d.ts +9 -0
- package/dist/gateway/server.js +137 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +254 -0
- package/dist/integrations/github.d.ts +7 -0
- package/dist/integrations/github.js +133 -0
- package/dist/integrations/mcp.d.ts +7 -0
- package/dist/integrations/mcp.js +106 -0
- package/dist/integrations/registry.d.ts +47 -0
- package/dist/integrations/registry.js +332 -0
- package/dist/integrations/scanner.d.ts +10 -0
- package/dist/integrations/scanner.js +122 -0
- package/dist/integrations/twitter.d.ts +10 -0
- package/dist/integrations/twitter.js +120 -0
- package/dist/integrations/types.d.ts +72 -0
- package/dist/integrations/types.js +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +104 -0
- package/dist/markdown/chunk.d.ts +9 -0
- package/dist/markdown/chunk.js +52 -0
- package/dist/markdown/ir.d.ts +37 -0
- package/dist/markdown/ir.js +529 -0
- package/dist/markdown/render.d.ts +22 -0
- package/dist/markdown/render.js +148 -0
- package/dist/markdown/table-render.d.ts +43 -0
- package/dist/markdown/table-render.js +219 -0
- package/dist/markdown/tables.d.ts +17 -0
- package/dist/markdown/tables.js +27 -0
- package/dist/memory/embedding.d.ts +16 -0
- package/dist/memory/embedding.js +66 -0
- package/dist/memory/explicit.d.ts +16 -0
- package/dist/memory/explicit.js +29 -0
- package/dist/memory/extractor.d.ts +13 -0
- package/dist/memory/extractor.js +82 -0
- package/dist/memory/search.d.ts +15 -0
- package/dist/memory/search.js +57 -0
- package/dist/memory/session-learning.d.ts +23 -0
- package/dist/memory/session-learning.js +55 -0
- package/dist/memory/store.d.ts +36 -0
- package/dist/memory/store.js +334 -0
- package/dist/memory/types.d.ts +9 -0
- package/dist/memory/types.js +2 -0
- package/dist/paths.d.ts +28 -0
- package/dist/paths.js +48 -0
- package/dist/prompt-templates/builtins/index.d.ts +4 -0
- package/dist/prompt-templates/builtins/index.js +5 -0
- package/dist/prompt-templates/builtins/planner.d.ts +4 -0
- package/dist/prompt-templates/builtins/planner.js +77 -0
- package/dist/prompt-templates/store.d.ts +45 -0
- package/dist/prompt-templates/store.js +224 -0
- package/dist/prompt-templates/types.d.ts +10 -0
- package/dist/prompt-templates/types.js +1 -0
- package/dist/scheduler/connected-channels.d.ts +24 -0
- package/dist/scheduler/connected-channels.js +57 -0
- package/dist/scheduler/scheduler.d.ts +22 -0
- package/dist/scheduler/scheduler.js +132 -0
- package/dist/scheduler/store.d.ts +27 -0
- package/dist/scheduler/store.js +205 -0
- package/dist/scheduler/types.d.ts +29 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/security/command-validator.d.ts +22 -0
- package/dist/security/command-validator.js +160 -0
- package/dist/security/docker-sandbox.d.ts +48 -0
- package/dist/security/docker-sandbox.js +218 -0
- package/dist/security/env-filter.d.ts +8 -0
- package/dist/security/env-filter.js +41 -0
- package/dist/skills/loader.d.ts +33 -0
- package/dist/skills/loader.js +132 -0
- package/dist/skills/prompt.d.ts +6 -0
- package/dist/skills/prompt.js +17 -0
- package/dist/skills/read-tool.d.ts +7 -0
- package/dist/skills/read-tool.js +24 -0
- package/dist/skills/scanner.d.ts +6 -0
- package/dist/skills/scanner.js +73 -0
- package/dist/skills/types.d.ts +15 -0
- package/dist/skills/types.js +1 -0
- package/dist/tasks/inline-attachment-content.d.ts +9 -0
- package/dist/tasks/inline-attachment-content.js +64 -0
- package/dist/tasks/store.d.ts +112 -0
- package/dist/tasks/store.js +519 -0
- package/dist/tasks/types.d.ts +129 -0
- package/dist/tasks/types.js +80 -0
- package/dist/teams/status-config.d.ts +8 -0
- package/dist/teams/status-config.js +40 -0
- package/dist/teams/store.d.ts +111 -0
- package/dist/teams/store.js +671 -0
- package/dist/teams/types.d.ts +30 -0
- package/dist/teams/types.js +1 -0
- package/dist/tools/bash.d.ts +18 -0
- package/dist/tools/bash.js +64 -0
- package/dist/tools/channel-history.d.ts +10 -0
- package/dist/tools/channel-history.js +43 -0
- package/dist/tools/delegate.d.ts +20 -0
- package/dist/tools/delegate.js +299 -0
- package/dist/tools/fs.d.ts +4 -0
- package/dist/tools/fs.js +335 -0
- package/dist/tools/integration-toggle.d.ts +14 -0
- package/dist/tools/integration-toggle.js +47 -0
- package/dist/tools/memory.d.ts +13 -0
- package/dist/tools/memory.js +59 -0
- package/dist/tools/prompt-templates.d.ts +7 -0
- package/dist/tools/prompt-templates.js +133 -0
- package/dist/tools/registry.d.ts +6 -0
- package/dist/tools/registry.js +9 -0
- package/dist/tools/schedule.d.ts +8 -0
- package/dist/tools/schedule.js +219 -0
- package/dist/tools/speak.d.ts +10 -0
- package/dist/tools/speak.js +56 -0
- package/dist/tools/tasks.d.ts +67 -0
- package/dist/tools/tasks.js +288 -0
- package/dist/tools/teams.d.ts +22 -0
- package/dist/tools/teams.js +470 -0
- package/dist/tools/web-fetch.d.ts +3 -0
- package/dist/tools/web-fetch.js +22 -0
- package/dist/tts/edge.d.ts +10 -0
- package/dist/tts/edge.js +60 -0
- package/dist/tts/speak.d.ts +12 -0
- package/dist/tts/speak.js +81 -0
- package/dist/tts/transcribe.d.ts +5 -0
- package/dist/tts/transcribe.js +40 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +22 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +13 -0
- package/package.json +102 -0
- package/verybot.js +2 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { mkdirSync } from "fs";
|
|
2
|
+
import { dirname } from "path";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
import Database from "better-sqlite3";
|
|
5
|
+
import { logger } from "../logger.js";
|
|
6
|
+
/** Max allowed length for template names. */
|
|
7
|
+
export const MAX_TEMPLATE_NAME_LENGTH = 128;
|
|
8
|
+
/** Max allowed length for template content. */
|
|
9
|
+
export const MAX_TEMPLATE_CONTENT_LENGTH = 50_000;
|
|
10
|
+
const VALID_ROLES = new Set(["orchestrator", "worker"]);
|
|
11
|
+
const FORK_NAME_SUFFIX = "Copy";
|
|
12
|
+
const FIRST_FORK_DUPLICATE_INDEX = 2;
|
|
13
|
+
/**
|
|
14
|
+
* SQLite-backed persistence for prompt templates.
|
|
15
|
+
* Shares the same DB file as TeamStore, TaskStore, etc.
|
|
16
|
+
*/
|
|
17
|
+
export class PromptTemplateStore {
|
|
18
|
+
db;
|
|
19
|
+
constructor(db) {
|
|
20
|
+
this.db = db;
|
|
21
|
+
}
|
|
22
|
+
static async create(dbPath) {
|
|
23
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
24
|
+
const db = new Database(dbPath);
|
|
25
|
+
db.pragma("journal_mode = WAL");
|
|
26
|
+
const store = new PromptTemplateStore(db);
|
|
27
|
+
store.createSchema();
|
|
28
|
+
return store;
|
|
29
|
+
}
|
|
30
|
+
createSchema() {
|
|
31
|
+
this.db.pragma("foreign_keys = ON");
|
|
32
|
+
this.db.exec(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS prompt_templates (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
name TEXT NOT NULL UNIQUE,
|
|
36
|
+
description TEXT NOT NULL DEFAULT '',
|
|
37
|
+
role TEXT NOT NULL,
|
|
38
|
+
content TEXT NOT NULL DEFAULT '',
|
|
39
|
+
builtin INTEGER NOT NULL DEFAULT 0,
|
|
40
|
+
created_at INTEGER NOT NULL,
|
|
41
|
+
updated_at INTEGER NOT NULL
|
|
42
|
+
);
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
createPromptTemplate(input) {
|
|
46
|
+
validateName(input.name);
|
|
47
|
+
validateContent(input.content);
|
|
48
|
+
validateRole(input.role);
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
const id = input.id ?? randomUUID();
|
|
51
|
+
try {
|
|
52
|
+
this.db.prepare(`INSERT INTO prompt_templates (id, name, description, role, content, builtin, created_at, updated_at)
|
|
53
|
+
VALUES (?, ?, ?, ?, ?, 0, ?, ?)`).run(id, input.name, input.description ?? "", input.role, input.content, now, now);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
|
|
57
|
+
throw new Error(`A template named "${input.name}" already exists`);
|
|
58
|
+
}
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
id, name: input.name, description: input.description ?? "",
|
|
63
|
+
role: input.role, content: input.content, builtin: false,
|
|
64
|
+
createdAt: now, updatedAt: now,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
forkPromptTemplate(sourceId, input = {}) {
|
|
68
|
+
const source = this.getPromptTemplateById(sourceId);
|
|
69
|
+
if (!source)
|
|
70
|
+
return null;
|
|
71
|
+
const name = input.name === undefined
|
|
72
|
+
? buildForkName(source.name, this.listPromptTemplateNames())
|
|
73
|
+
: input.name.trim();
|
|
74
|
+
return this.createPromptTemplate({
|
|
75
|
+
name,
|
|
76
|
+
description: input.description ?? source.description,
|
|
77
|
+
role: source.role,
|
|
78
|
+
content: source.content,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
updatePromptTemplate(id, input) {
|
|
82
|
+
const existing = this.getPromptTemplateById(id);
|
|
83
|
+
if (!existing)
|
|
84
|
+
return null;
|
|
85
|
+
if (existing.builtin)
|
|
86
|
+
throw new Error("Cannot modify a built-in template");
|
|
87
|
+
if (input.name !== undefined)
|
|
88
|
+
validateName(input.name);
|
|
89
|
+
if (input.content !== undefined)
|
|
90
|
+
validateContent(input.content);
|
|
91
|
+
if (input.role !== undefined)
|
|
92
|
+
validateRole(input.role);
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const updated = {
|
|
95
|
+
...existing,
|
|
96
|
+
...(input.name !== undefined && { name: input.name }),
|
|
97
|
+
...(input.description !== undefined && { description: input.description }),
|
|
98
|
+
...(input.role !== undefined && { role: input.role }),
|
|
99
|
+
...(input.content !== undefined && { content: input.content }),
|
|
100
|
+
updatedAt: now,
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
this.db.prepare(`UPDATE prompt_templates SET name = ?, description = ?, role = ?, content = ?, updated_at = ? WHERE id = ?`).run(updated.name, updated.description, updated.role, updated.content, now, id);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
|
|
107
|
+
throw new Error(`A template named "${input.name}" already exists`);
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
return updated;
|
|
112
|
+
}
|
|
113
|
+
deletePromptTemplate(id) {
|
|
114
|
+
const existing = this.getPromptTemplateById(id);
|
|
115
|
+
if (existing?.builtin)
|
|
116
|
+
throw new Error("Cannot delete a built-in template");
|
|
117
|
+
const info = this.db.prepare("DELETE FROM prompt_templates WHERE id = ?").run(id);
|
|
118
|
+
return info.changes > 0;
|
|
119
|
+
}
|
|
120
|
+
getPromptTemplateById(id) {
|
|
121
|
+
const row = this.db.prepare("SELECT * FROM prompt_templates WHERE id = ?").get(id);
|
|
122
|
+
return row ? toPromptTemplate(row) : null;
|
|
123
|
+
}
|
|
124
|
+
listPromptTemplates() {
|
|
125
|
+
const rows = this.db.prepare("SELECT * FROM prompt_templates ORDER BY builtin DESC, created_at ASC").all();
|
|
126
|
+
return rows.map(toPromptTemplate);
|
|
127
|
+
}
|
|
128
|
+
listPromptTemplateNames() {
|
|
129
|
+
const rows = this.db.prepare("SELECT name FROM prompt_templates").all();
|
|
130
|
+
return rows.map((row) => row.name);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Upsert built-in templates by id on boot.
|
|
134
|
+
* Only updates name/description/role/content for existing builtins.
|
|
135
|
+
*/
|
|
136
|
+
seedBuiltins(templates) {
|
|
137
|
+
const upsert = this.db.prepare(`
|
|
138
|
+
INSERT INTO prompt_templates (id, name, description, role, content, builtin, created_at, updated_at)
|
|
139
|
+
VALUES (?, ?, ?, ?, ?, 1, ?, ?)
|
|
140
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
141
|
+
name = excluded.name,
|
|
142
|
+
description = excluded.description,
|
|
143
|
+
role = excluded.role,
|
|
144
|
+
content = excluded.content,
|
|
145
|
+
builtin = 1,
|
|
146
|
+
updated_at = excluded.updated_at
|
|
147
|
+
`);
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
const keepIds = templates.map((t) => t.id);
|
|
150
|
+
const runAll = this.db.transaction(() => {
|
|
151
|
+
for (const t of templates) {
|
|
152
|
+
upsert.run(t.id, t.name, t.description, t.role, t.content, now, now);
|
|
153
|
+
}
|
|
154
|
+
if (keepIds.length === 0) {
|
|
155
|
+
this.db.prepare(`DELETE FROM prompt_templates WHERE builtin = 1`).run();
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
this.db.prepare(`DELETE FROM prompt_templates WHERE builtin = 1 AND id NOT IN (${keepIds.map(() => "?").join(",")})`).run(...keepIds);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
runAll();
|
|
162
|
+
logger.info(`Seeded ${templates.length} built-in prompt templates`);
|
|
163
|
+
}
|
|
164
|
+
close() {
|
|
165
|
+
this.db.close();
|
|
166
|
+
logger.info("Prompt template store closed");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function toPromptTemplate(row) {
|
|
170
|
+
return {
|
|
171
|
+
id: row.id,
|
|
172
|
+
name: row.name,
|
|
173
|
+
description: row.description ?? "",
|
|
174
|
+
role: row.role,
|
|
175
|
+
content: row.content ?? "",
|
|
176
|
+
builtin: row.builtin === 1,
|
|
177
|
+
createdAt: row.created_at,
|
|
178
|
+
updatedAt: row.updated_at,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function validateName(name) {
|
|
182
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
183
|
+
throw new Error("Template name is required");
|
|
184
|
+
}
|
|
185
|
+
if (name.length > MAX_TEMPLATE_NAME_LENGTH) {
|
|
186
|
+
throw new Error(`Template name exceeds maximum length of ${MAX_TEMPLATE_NAME_LENGTH}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function validateContent(content) {
|
|
190
|
+
if (typeof content !== "string") {
|
|
191
|
+
throw new Error("Template content must be a string");
|
|
192
|
+
}
|
|
193
|
+
if (content.length > MAX_TEMPLATE_CONTENT_LENGTH) {
|
|
194
|
+
throw new Error(`Template content exceeds maximum length of ${MAX_TEMPLATE_CONTENT_LENGTH}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function validateRole(role) {
|
|
198
|
+
if (!VALID_ROLES.has(role)) {
|
|
199
|
+
throw new Error("role must be 'orchestrator' or 'worker'");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function buildForkName(sourceName, existingNames) {
|
|
203
|
+
const trimmedSourceName = sourceName.trim();
|
|
204
|
+
const firstForkName = `${trimmedSourceName} (${FORK_NAME_SUFFIX})`;
|
|
205
|
+
const existing = new Set(existingNames);
|
|
206
|
+
if (!existing.has(firstForkName))
|
|
207
|
+
return firstForkName;
|
|
208
|
+
const escapedName = escapeRegex(trimmedSourceName);
|
|
209
|
+
const forkPattern = new RegExp(`^${escapedName} \\(${FORK_NAME_SUFFIX}(?: (\\d+))?\\)$`);
|
|
210
|
+
let nextIndex = FIRST_FORK_DUPLICATE_INDEX;
|
|
211
|
+
for (const candidateName of existing) {
|
|
212
|
+
const match = candidateName.match(forkPattern);
|
|
213
|
+
if (!match)
|
|
214
|
+
continue;
|
|
215
|
+
const usedIndex = match[1] ? Number(match[1]) : 1;
|
|
216
|
+
if (!Number.isFinite(usedIndex))
|
|
217
|
+
continue;
|
|
218
|
+
nextIndex = Math.max(nextIndex, usedIndex + 1);
|
|
219
|
+
}
|
|
220
|
+
return `${trimmedSourceName} (${FORK_NAME_SUFFIX} ${nextIndex})`;
|
|
221
|
+
}
|
|
222
|
+
function escapeRegex(input) {
|
|
223
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
224
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ConnectedChannel {
|
|
2
|
+
channelType: string;
|
|
3
|
+
channelId: string;
|
|
4
|
+
send: (text: string) => Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* In-memory registry tracking which channels are connected to which team's scheduler.
|
|
8
|
+
* Multiple channels (web UI, Telegram, etc.) can connect simultaneously.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ConnectedChannelRegistry {
|
|
11
|
+
/** Map<teamId, Map<connectionKey, ConnectedChannel>> */
|
|
12
|
+
private channels;
|
|
13
|
+
/** Register a channel connection. Returns a unique connection key for later disconnect. */
|
|
14
|
+
connect(teamId: string, channel: ConnectedChannel): string;
|
|
15
|
+
/** Unregister a channel connection. */
|
|
16
|
+
disconnect(teamId: string, key: string): void;
|
|
17
|
+
/** Get all connected channels for a team. */
|
|
18
|
+
getAll(teamId: string): ConnectedChannel[];
|
|
19
|
+
/**
|
|
20
|
+
* Broadcast a message to all connected channels for a team.
|
|
21
|
+
* `excludeKey` skips a specific connection (e.g., the sender that already has the reply).
|
|
22
|
+
*/
|
|
23
|
+
broadcastToTeam(teamId: string, text: string, excludeKey?: string): Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import { logger } from "../logger.js";
|
|
3
|
+
/**
|
|
4
|
+
* In-memory registry tracking which channels are connected to which team's scheduler.
|
|
5
|
+
* Multiple channels (web UI, Telegram, etc.) can connect simultaneously.
|
|
6
|
+
*/
|
|
7
|
+
export class ConnectedChannelRegistry {
|
|
8
|
+
/** Map<teamId, Map<connectionKey, ConnectedChannel>> */
|
|
9
|
+
channels = new Map();
|
|
10
|
+
/** Register a channel connection. Returns a unique connection key for later disconnect. */
|
|
11
|
+
connect(teamId, channel) {
|
|
12
|
+
const key = randomUUID();
|
|
13
|
+
let teamMap = this.channels.get(teamId);
|
|
14
|
+
if (!teamMap) {
|
|
15
|
+
teamMap = new Map();
|
|
16
|
+
this.channels.set(teamId, teamMap);
|
|
17
|
+
}
|
|
18
|
+
teamMap.set(key, channel);
|
|
19
|
+
logger.info(`[scheduler] Channel connected: ${channel.channelType}:${channel.channelId} → team ${teamId} (key: ${key.slice(0, 8)})`);
|
|
20
|
+
return key;
|
|
21
|
+
}
|
|
22
|
+
/** Unregister a channel connection. */
|
|
23
|
+
disconnect(teamId, key) {
|
|
24
|
+
const teamMap = this.channels.get(teamId);
|
|
25
|
+
if (!teamMap)
|
|
26
|
+
return;
|
|
27
|
+
teamMap.delete(key);
|
|
28
|
+
if (teamMap.size === 0)
|
|
29
|
+
this.channels.delete(teamId);
|
|
30
|
+
logger.info(`[scheduler] Channel disconnected: team ${teamId} (key: ${key.slice(0, 8)})`);
|
|
31
|
+
}
|
|
32
|
+
/** Get all connected channels for a team. */
|
|
33
|
+
getAll(teamId) {
|
|
34
|
+
const teamMap = this.channels.get(teamId);
|
|
35
|
+
if (!teamMap)
|
|
36
|
+
return [];
|
|
37
|
+
return [...teamMap.values()];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Broadcast a message to all connected channels for a team.
|
|
41
|
+
* `excludeKey` skips a specific connection (e.g., the sender that already has the reply).
|
|
42
|
+
*/
|
|
43
|
+
async broadcastToTeam(teamId, text, excludeKey) {
|
|
44
|
+
const teamMap = this.channels.get(teamId);
|
|
45
|
+
if (!teamMap)
|
|
46
|
+
return;
|
|
47
|
+
const sends = [];
|
|
48
|
+
for (const [key, channel] of teamMap) {
|
|
49
|
+
if (key === excludeKey)
|
|
50
|
+
continue;
|
|
51
|
+
sends.push(channel.send(text).catch((err) => {
|
|
52
|
+
logger.warn(`[scheduler] Broadcast to ${channel.channelType}:${channel.channelId} failed: ${err instanceof Error ? err.message : err}`);
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
await Promise.all(sends);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ScheduleStore } from "./store.js";
|
|
2
|
+
import type { Agent } from "../brain/agent.js";
|
|
3
|
+
import type { ConnectedChannelRegistry } from "./connected-channels.js";
|
|
4
|
+
export declare class Scheduler {
|
|
5
|
+
private store;
|
|
6
|
+
private agent;
|
|
7
|
+
private connectedChannels;
|
|
8
|
+
private tickMs;
|
|
9
|
+
private timer;
|
|
10
|
+
private running;
|
|
11
|
+
constructor(store: ScheduleStore, agent: Agent, connectedChannels: ConnectedChannelRegistry, tickMs?: number);
|
|
12
|
+
start(): void;
|
|
13
|
+
stop(): void;
|
|
14
|
+
/**
|
|
15
|
+
* On startup: fire missed one-shots, skip missed recurring.
|
|
16
|
+
*/
|
|
17
|
+
private catchUp;
|
|
18
|
+
private tick;
|
|
19
|
+
private executeTask;
|
|
20
|
+
private advanceSchedule;
|
|
21
|
+
private handleError;
|
|
22
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { logger } from "../logger.js";
|
|
2
|
+
const TICK_INTERVAL_MS = 5_000;
|
|
3
|
+
const MAX_RETRY_COUNT = 1;
|
|
4
|
+
/** Prefix that LLM uses to signal "not worth notifying". */
|
|
5
|
+
const SKIP_SIGNAL = "[SKIP]";
|
|
6
|
+
export class Scheduler {
|
|
7
|
+
store;
|
|
8
|
+
agent;
|
|
9
|
+
connectedChannels;
|
|
10
|
+
tickMs;
|
|
11
|
+
timer = null;
|
|
12
|
+
running = false;
|
|
13
|
+
constructor(store, agent, connectedChannels, tickMs = TICK_INTERVAL_MS) {
|
|
14
|
+
this.store = store;
|
|
15
|
+
this.agent = agent;
|
|
16
|
+
this.connectedChannels = connectedChannels;
|
|
17
|
+
this.tickMs = tickMs;
|
|
18
|
+
}
|
|
19
|
+
start() {
|
|
20
|
+
// Catch up missed tasks on startup
|
|
21
|
+
this.catchUp().catch((err) => {
|
|
22
|
+
logger.error(`Scheduler catch-up failed: ${err instanceof Error ? err.message : err}`);
|
|
23
|
+
});
|
|
24
|
+
this.timer = setInterval(() => {
|
|
25
|
+
this.tick().catch((err) => {
|
|
26
|
+
logger.error(`Scheduler tick failed: ${err instanceof Error ? err.message : err}`);
|
|
27
|
+
});
|
|
28
|
+
}, this.tickMs);
|
|
29
|
+
logger.info(`Scheduler started (tick every ${this.tickMs}ms)`);
|
|
30
|
+
}
|
|
31
|
+
stop() {
|
|
32
|
+
if (this.timer) {
|
|
33
|
+
clearInterval(this.timer);
|
|
34
|
+
this.timer = null;
|
|
35
|
+
}
|
|
36
|
+
logger.info("Scheduler stopped");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* On startup: fire missed one-shots, skip missed recurring.
|
|
40
|
+
*/
|
|
41
|
+
async catchUp() {
|
|
42
|
+
const now = new Date();
|
|
43
|
+
const due = this.store.getDueSchedules(now);
|
|
44
|
+
if (due.length === 0)
|
|
45
|
+
return;
|
|
46
|
+
const oneShots = due.filter((s) => s.type === "one_shot");
|
|
47
|
+
const recurring = due.filter((s) => s.type === "recurring");
|
|
48
|
+
// Skip missed recurring — advance to next future run
|
|
49
|
+
for (const schedule of recurring) {
|
|
50
|
+
const nextRun = this.store.computeNextRun(schedule);
|
|
51
|
+
this.store.markCompleted(schedule.id, nextRun);
|
|
52
|
+
logger.info(`[scheduler] Skipped missed recurring "${schedule.prompt.slice(0, 40)}..." → next: ${nextRun}`);
|
|
53
|
+
}
|
|
54
|
+
// Fire missed one-shots
|
|
55
|
+
if (oneShots.length > 0) {
|
|
56
|
+
logger.info(`[scheduler] Catching up ${oneShots.length} missed one-shot task(s)`);
|
|
57
|
+
await Promise.allSettled(oneShots.map((s) => this.executeTask(s)));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async tick() {
|
|
61
|
+
if (this.running)
|
|
62
|
+
return; // prevent overlapping ticks
|
|
63
|
+
this.running = true;
|
|
64
|
+
try {
|
|
65
|
+
const due = this.store.getDueSchedules(new Date());
|
|
66
|
+
if (due.length === 0)
|
|
67
|
+
return;
|
|
68
|
+
logger.info(`[scheduler] ${due.length} task(s) due`);
|
|
69
|
+
await Promise.allSettled(due.map((s) => this.executeTask(s)));
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
this.running = false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async executeTask(schedule) {
|
|
76
|
+
const taskLabel = `"${schedule.prompt.slice(0, 50)}..." (${schedule.id})`;
|
|
77
|
+
logger.info(`[scheduler] Executing task ${taskLabel}`);
|
|
78
|
+
try {
|
|
79
|
+
const reply = await this.agent.runScheduledTask({
|
|
80
|
+
prompt: schedule.prompt,
|
|
81
|
+
teamId: schedule.teamId,
|
|
82
|
+
integrations: schedule.integrations.split(",").filter(Boolean),
|
|
83
|
+
});
|
|
84
|
+
// Check for [SKIP] signal on conditional tasks
|
|
85
|
+
if (schedule.conditional && reply.trimStart().startsWith(SKIP_SIGNAL)) {
|
|
86
|
+
logger.info(`[scheduler] Task ${taskLabel} skipped by LLM`);
|
|
87
|
+
this.advanceSchedule(schedule);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Broadcast to all connected channels for this team
|
|
91
|
+
await this.connectedChannels.broadcastToTeam(schedule.teamId, reply);
|
|
92
|
+
logger.info(`[scheduler] Task ${taskLabel} broadcast to team ${schedule.teamId}`);
|
|
93
|
+
// Reset fail count and advance
|
|
94
|
+
this.advanceSchedule(schedule);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
await this.handleError(schedule, err);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
advanceSchedule(schedule) {
|
|
101
|
+
const nextRun = schedule.type === "recurring" ? this.store.computeNextRun(schedule) : null;
|
|
102
|
+
this.store.markCompleted(schedule.id, nextRun);
|
|
103
|
+
if (nextRun) {
|
|
104
|
+
logger.info(`[scheduler] Next run for ${schedule.id}: ${nextRun}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async handleError(schedule, err) {
|
|
108
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
109
|
+
const taskLabel = `"${schedule.prompt.slice(0, 50)}..." (${schedule.id})`;
|
|
110
|
+
// Re-read to get current fail count
|
|
111
|
+
const current = this.store.getById(schedule.id);
|
|
112
|
+
const failCount = (current?.failCount ?? schedule.failCount) + 1;
|
|
113
|
+
if (failCount <= MAX_RETRY_COUNT) {
|
|
114
|
+
// Retry: increment fail count, leave next_run unchanged (fires on next tick)
|
|
115
|
+
logger.warn(`[scheduler] Task ${taskLabel} failed (attempt ${failCount}), will retry: ${msg}`);
|
|
116
|
+
this.store.update(schedule.id, { failCount });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Max retries exceeded — advance the schedule and notify user
|
|
120
|
+
logger.error(`[scheduler] Task ${taskLabel} failed after ${failCount} attempts: ${msg}`);
|
|
121
|
+
const nextRun = schedule.type === "recurring" ? this.store.computeNextRun(schedule) : null;
|
|
122
|
+
this.store.markFailed(schedule.id, nextRun);
|
|
123
|
+
// Best-effort notification to connected channels
|
|
124
|
+
try {
|
|
125
|
+
const notification = `Scheduled task failed: "${schedule.prompt.slice(0, 80)}..."\nError: ${msg}`;
|
|
126
|
+
await this.connectedChannels.broadcastToTeam(schedule.teamId, notification);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
logger.warn(`[scheduler] Could not notify about failed task ${schedule.id}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Schedule, ScheduleStatus } from "./types.js";
|
|
2
|
+
export declare class ScheduleStore {
|
|
3
|
+
private db;
|
|
4
|
+
private constructor();
|
|
5
|
+
/** Async factory — mirrors MemoryStore.create pattern. */
|
|
6
|
+
static create(dbPath: string): Promise<ScheduleStore>;
|
|
7
|
+
private createSchema;
|
|
8
|
+
create(schedule: Schedule): void;
|
|
9
|
+
getById(id: string): Schedule | null;
|
|
10
|
+
listByTeam(teamId: string, status?: ScheduleStatus): Schedule[];
|
|
11
|
+
/** Get all schedules that are due (next_run <= now and status = 'active'). */
|
|
12
|
+
getDueSchedules(now: Date): Schedule[];
|
|
13
|
+
/** Get all schedules with a specific status. */
|
|
14
|
+
getByStatus(status: ScheduleStatus): Schedule[];
|
|
15
|
+
/** Update specific fields on a schedule. */
|
|
16
|
+
update(id: string, fields: Partial<Pick<Schedule, "status" | "nextRun" | "lastRun" | "failCount">>): void;
|
|
17
|
+
delete(id: string): boolean;
|
|
18
|
+
/** Mark a schedule as completed for this run and advance to the next. */
|
|
19
|
+
markCompleted(id: string, nextRun: string | null): void;
|
|
20
|
+
/** Increment fail count and optionally advance the schedule. */
|
|
21
|
+
markFailed(id: string, nextRun: string | null): void;
|
|
22
|
+
/** Compute the next run time for a recurring schedule using croner. */
|
|
23
|
+
computeNextRun(schedule: Schedule): string | null;
|
|
24
|
+
setTimezone(teamId: string, timezone: string): void;
|
|
25
|
+
getTimezone(teamId: string): string | null;
|
|
26
|
+
close(): void;
|
|
27
|
+
}
|