reeboot 1.0.0
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 +361 -0
- package/container/Dockerfile +48 -0
- package/container/entrypoint.sh +8 -0
- package/dist/agent-runner/index.d.ts +9 -0
- package/dist/agent-runner/index.d.ts.map +1 -0
- package/dist/agent-runner/index.js +21 -0
- package/dist/agent-runner/index.js.map +1 -0
- package/dist/agent-runner/interface.d.ts +56 -0
- package/dist/agent-runner/interface.d.ts.map +1 -0
- package/dist/agent-runner/interface.js +5 -0
- package/dist/agent-runner/interface.js.map +1 -0
- package/dist/agent-runner/pi-runner.d.ts +41 -0
- package/dist/agent-runner/pi-runner.d.ts.map +1 -0
- package/dist/agent-runner/pi-runner.js +162 -0
- package/dist/agent-runner/pi-runner.js.map +1 -0
- package/dist/channels/interface.d.ts +63 -0
- package/dist/channels/interface.d.ts.map +1 -0
- package/dist/channels/interface.js +33 -0
- package/dist/channels/interface.js.map +1 -0
- package/dist/channels/registry.d.ts +30 -0
- package/dist/channels/registry.d.ts.map +1 -0
- package/dist/channels/registry.js +71 -0
- package/dist/channels/registry.js.map +1 -0
- package/dist/channels/signal.d.ts +51 -0
- package/dist/channels/signal.d.ts.map +1 -0
- package/dist/channels/signal.js +263 -0
- package/dist/channels/signal.js.map +1 -0
- package/dist/channels/web.d.ts +35 -0
- package/dist/channels/web.d.ts.map +1 -0
- package/dist/channels/web.js +65 -0
- package/dist/channels/web.js.map +1 -0
- package/dist/channels/whatsapp.d.ts +25 -0
- package/dist/channels/whatsapp.d.ts.map +1 -0
- package/dist/channels/whatsapp.js +150 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/config.d.ts +366 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +140 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +69 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +166 -0
- package/dist/context.js.map +1 -0
- package/dist/credential-proxy.d.ts +25 -0
- package/dist/credential-proxy.d.ts.map +1 -0
- package/dist/credential-proxy.js +96 -0
- package/dist/credential-proxy.js.map +1 -0
- package/dist/daemon.d.ts +25 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +138 -0
- package/dist/daemon.js.map +1 -0
- package/dist/db/index.d.ts +23 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +113 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +408 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +55 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/doctor.d.ts +23 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +217 -0
- package/dist/doctor.js.map +1 -0
- package/dist/extensions/loader.d.ts +19 -0
- package/dist/extensions/loader.d.ts.map +1 -0
- package/dist/extensions/loader.js +124 -0
- package/dist/extensions/loader.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +561 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator.d.ts +60 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +313 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/packages.d.ts +21 -0
- package/dist/packages.d.ts.map +1 -0
- package/dist/packages.js +116 -0
- package/dist/packages.js.map +1 -0
- package/dist/scheduler-registry.d.ts +8 -0
- package/dist/scheduler-registry.d.ts.map +1 -0
- package/dist/scheduler-registry.js +14 -0
- package/dist/scheduler-registry.js.map +1 -0
- package/dist/scheduler.d.ts +60 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +143 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/server.d.ts +18 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +489 -0
- package/dist/server.js.map +1 -0
- package/dist/setup-wizard.d.ts +12 -0
- package/dist/setup-wizard.d.ts.map +1 -0
- package/dist/setup-wizard.js +163 -0
- package/dist/setup-wizard.js.map +1 -0
- package/extensions/confirm-destructive.ts +59 -0
- package/extensions/custom-compaction.ts +114 -0
- package/extensions/protected-paths.ts +30 -0
- package/extensions/sandbox/index.ts +317 -0
- package/extensions/sandbox/package-lock.json +92 -0
- package/extensions/sandbox/package.json +19 -0
- package/extensions/scheduler-tool.ts +65 -0
- package/extensions/session-name.ts +27 -0
- package/extensions/token-meter.ts +55 -0
- package/package.json +68 -0
- package/skills/send-message/SKILL.md +27 -0
- package/skills/web-search/SKILL.md +32 -0
- package/templates/global-agents.md +23 -0
- package/templates/main-agents.md +28 -0
- package/webchat/index.html +421 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduler
|
|
3
|
+
*
|
|
4
|
+
* Loads enabled tasks from the SQLite `tasks` table on startup,
|
|
5
|
+
* registers node-cron jobs, and dispatches prompts to the orchestrator
|
|
6
|
+
* when jobs fire. Updates `last_run` after each execution.
|
|
7
|
+
*/
|
|
8
|
+
import * as cron from 'node-cron';
|
|
9
|
+
import { nanoid } from 'nanoid';
|
|
10
|
+
// ─── Scheduler ────────────────────────────────────────────────────────────────
|
|
11
|
+
export class Scheduler {
|
|
12
|
+
_db;
|
|
13
|
+
_orchestrator;
|
|
14
|
+
_jobs = new Map();
|
|
15
|
+
constructor(db, orchestrator) {
|
|
16
|
+
this._db = db;
|
|
17
|
+
this._orchestrator = orchestrator;
|
|
18
|
+
}
|
|
19
|
+
async start() {
|
|
20
|
+
// Load all enabled tasks and register cron jobs
|
|
21
|
+
const tasks = this._db
|
|
22
|
+
.prepare('SELECT * FROM tasks WHERE enabled = 1')
|
|
23
|
+
.all();
|
|
24
|
+
for (const task of tasks) {
|
|
25
|
+
this.registerJob({
|
|
26
|
+
id: task.id,
|
|
27
|
+
contextId: task.context_id,
|
|
28
|
+
schedule: task.schedule,
|
|
29
|
+
prompt: task.prompt,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
registerJob(task) {
|
|
34
|
+
// Cancel existing job for this task if any
|
|
35
|
+
const existing = this._jobs.get(task.id);
|
|
36
|
+
if (existing) {
|
|
37
|
+
existing.stop();
|
|
38
|
+
}
|
|
39
|
+
const job = cron.schedule(task.schedule, async () => {
|
|
40
|
+
try {
|
|
41
|
+
await this._orchestrator.handleScheduledTask({
|
|
42
|
+
taskId: task.id,
|
|
43
|
+
contextId: task.contextId,
|
|
44
|
+
prompt: task.prompt,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.error(`[Scheduler] Task ${task.id} failed: ${err}`);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
// Update last_run
|
|
52
|
+
this._db
|
|
53
|
+
.prepare("UPDATE tasks SET last_run = datetime('now') WHERE id = ?")
|
|
54
|
+
.run(task.id);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
this._jobs.set(task.id, job);
|
|
58
|
+
}
|
|
59
|
+
cancelJob(taskId) {
|
|
60
|
+
const job = this._jobs.get(taskId);
|
|
61
|
+
if (job) {
|
|
62
|
+
job.stop();
|
|
63
|
+
this._jobs.delete(taskId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
stop() {
|
|
67
|
+
for (const [, job] of this._jobs) {
|
|
68
|
+
job.stop();
|
|
69
|
+
}
|
|
70
|
+
this._jobs.clear();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function createSchedulerTools(db, scheduler) {
|
|
74
|
+
return {
|
|
75
|
+
async schedule_task(params) {
|
|
76
|
+
// Validate cron expression
|
|
77
|
+
if (!cron.validate(params.schedule)) {
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: 'text', text: `Invalid cron expression: ${params.schedule}` }],
|
|
80
|
+
details: {},
|
|
81
|
+
isError: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const contextId = params.contextId ?? 'main';
|
|
85
|
+
const id = nanoid();
|
|
86
|
+
try {
|
|
87
|
+
db.prepare('INSERT INTO tasks (id, context_id, schedule, prompt, enabled) VALUES (?, ?, ?, ?, 1)').run(id, contextId, params.schedule, params.prompt);
|
|
88
|
+
scheduler.registerJob({
|
|
89
|
+
id,
|
|
90
|
+
contextId,
|
|
91
|
+
schedule: params.schedule,
|
|
92
|
+
prompt: params.prompt,
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
content: [{ type: 'text', text: `Scheduled task created (id: ${id})` }],
|
|
96
|
+
details: { id, schedule: params.schedule, contextId },
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
return {
|
|
101
|
+
content: [{ type: 'text', text: `Failed to schedule task: ${err.message}` }],
|
|
102
|
+
details: {},
|
|
103
|
+
isError: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
async list_tasks(_params) {
|
|
108
|
+
const tasks = db
|
|
109
|
+
.prepare('SELECT id, context_id, schedule, prompt, enabled, last_run FROM tasks')
|
|
110
|
+
.all();
|
|
111
|
+
if (tasks.length === 0) {
|
|
112
|
+
return {
|
|
113
|
+
content: [{ type: 'text', text: 'No scheduled tasks.' }],
|
|
114
|
+
details: { tasks: [] },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const lines = tasks.map((t) => `[${t.id}] ${t.schedule} → ${t.prompt} (context: ${t.context_id}, enabled: ${t.enabled ? 'yes' : 'no'}, last_run: ${t.last_run ?? 'never'})`);
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
120
|
+
details: { tasks },
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
async cancel_task(params) {
|
|
124
|
+
const task = db
|
|
125
|
+
.prepare('SELECT id FROM tasks WHERE id = ?')
|
|
126
|
+
.get(params.task_id);
|
|
127
|
+
if (!task) {
|
|
128
|
+
return {
|
|
129
|
+
content: [{ type: 'text', text: `Task not found: ${params.task_id}` }],
|
|
130
|
+
details: {},
|
|
131
|
+
isError: true,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
db.prepare('DELETE FROM tasks WHERE id = ?').run(params.task_id);
|
|
135
|
+
scheduler.cancelJob(params.task_id);
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: 'text', text: `Cancelled task ${params.task_id}` }],
|
|
138
|
+
details: { taskId: params.task_id },
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FastifyInstance } from 'fastify';
|
|
2
|
+
import type Database from 'better-sqlite3';
|
|
3
|
+
export interface ServerOptions {
|
|
4
|
+
port?: number;
|
|
5
|
+
host?: string;
|
|
6
|
+
logLevel?: string;
|
|
7
|
+
/** Injected DB for testing (otherwise uses getDb() singleton) */
|
|
8
|
+
db?: Database.Database;
|
|
9
|
+
/** Override ~/.reeboot directory for testing */
|
|
10
|
+
reebotDir?: string;
|
|
11
|
+
/** Auth token (if set, non-loopback WS connections must provide it) */
|
|
12
|
+
token?: string;
|
|
13
|
+
/** App config for runner creation */
|
|
14
|
+
config?: import('./config.js').Config;
|
|
15
|
+
}
|
|
16
|
+
export declare function startServer(opts?: ServerOptions): Promise<FastifyInstance>;
|
|
17
|
+
export declare function stopServer(): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAC;AAMnE,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAmC3C,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,EAAE,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACvB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,MAAM,CAAC,EAAE,OAAO,aAAa,EAAE,MAAM,CAAC;CACvC;AAoCD,wBAAsB,WAAW,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CA+dpF;AAID,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAmChD"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import Fastify from 'fastify';
|
|
2
|
+
import fastifyWebsocket from '@fastify/websocket';
|
|
3
|
+
import fastifyStatic from '@fastify/static';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { resolve, dirname, join } from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { createRunner } from './agent-runner/index.js';
|
|
8
|
+
import { listContexts, createContext, getContextById, listSessions, initContextWorkspace, createContextsTable, } from './context.js';
|
|
9
|
+
import { nanoid } from 'nanoid';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
// Read version from package.json
|
|
13
|
+
function getVersion() {
|
|
14
|
+
try {
|
|
15
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8'));
|
|
16
|
+
return pkg.version ?? '0.0.0';
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return '0.0.0';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const startTime = Date.now();
|
|
23
|
+
// ─── Singleton ───────────────────────────────────────────────────────────────
|
|
24
|
+
let _server = null;
|
|
25
|
+
// Active runners: contextId → AgentRunner
|
|
26
|
+
const _activeRunners = new Map();
|
|
27
|
+
// Channel adapters (set during startServer)
|
|
28
|
+
let _channelAdapters = new Map();
|
|
29
|
+
// Orchestrator (set during startServer)
|
|
30
|
+
let _orchestrator = null;
|
|
31
|
+
// Scheduler (set during startServer)
|
|
32
|
+
let _scheduler = null;
|
|
33
|
+
// Credential proxy (set during startServer)
|
|
34
|
+
let _credProxy = null;
|
|
35
|
+
// ─── Auth helper ─────────────────────────────────────────────────────────────
|
|
36
|
+
function isLoopback(ip) {
|
|
37
|
+
return ip === '127.0.0.1' || ip === '::1' || ip === 'localhost';
|
|
38
|
+
}
|
|
39
|
+
function extractToken(req) {
|
|
40
|
+
const authHeader = req.headers['authorization'];
|
|
41
|
+
if (authHeader?.startsWith('Bearer '))
|
|
42
|
+
return authHeader.slice(7);
|
|
43
|
+
const url = new URL(req.url, 'http://localhost');
|
|
44
|
+
return url.searchParams.get('token') ?? undefined;
|
|
45
|
+
}
|
|
46
|
+
// ─── startServer ─────────────────────────────────────────────────────────────
|
|
47
|
+
export async function startServer(opts = {}) {
|
|
48
|
+
const port = opts.port ?? 3000;
|
|
49
|
+
const host = opts.host ?? '127.0.0.1';
|
|
50
|
+
const logLevel = opts.logLevel ?? 'info';
|
|
51
|
+
const reebotDir = opts.reebotDir ?? join(homedir(), '.reeboot');
|
|
52
|
+
const serverToken = opts.token;
|
|
53
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
54
|
+
const logger = logLevel === 'silent'
|
|
55
|
+
? false
|
|
56
|
+
: {
|
|
57
|
+
level: logLevel,
|
|
58
|
+
...(isDev ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}),
|
|
59
|
+
};
|
|
60
|
+
const server = Fastify({ logger });
|
|
61
|
+
// Register WebSocket plugin
|
|
62
|
+
await server.register(fastifyWebsocket);
|
|
63
|
+
// Register static file serving for webchat
|
|
64
|
+
const webchatDir = resolve(__dirname, '../webchat');
|
|
65
|
+
try {
|
|
66
|
+
await server.register(fastifyStatic, {
|
|
67
|
+
root: webchatDir,
|
|
68
|
+
prefix: '/',
|
|
69
|
+
decorateReply: false,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// webchat dir may not exist in test environments — that's OK
|
|
74
|
+
}
|
|
75
|
+
// Get or set up the DB
|
|
76
|
+
let db;
|
|
77
|
+
if (opts.db) {
|
|
78
|
+
db = opts.db;
|
|
79
|
+
createContextsTable(db);
|
|
80
|
+
// Ensure main context exists
|
|
81
|
+
if (!getContextById(db, 'main')) {
|
|
82
|
+
createContext(db, { id: 'main', name: 'main', modelProvider: '', modelId: '' });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const { openDatabase } = await import('./db/index.js');
|
|
87
|
+
db = openDatabase();
|
|
88
|
+
// Ensure main context exists
|
|
89
|
+
if (!getContextById(db, 'main')) {
|
|
90
|
+
createContext(db, { id: 'main', name: 'main', modelProvider: '', modelId: '' });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Ensure context workspace for "main"
|
|
94
|
+
await initContextWorkspace('main', reebotDir);
|
|
95
|
+
// ── Channel & Orchestrator init ───────────────────────────────────────────
|
|
96
|
+
const appConfig = opts.config;
|
|
97
|
+
if (appConfig) {
|
|
98
|
+
try {
|
|
99
|
+
// Import built-in adapters so they self-register
|
|
100
|
+
await import('./channels/web.js');
|
|
101
|
+
await import('./channels/whatsapp.js');
|
|
102
|
+
await import('./channels/signal.js');
|
|
103
|
+
const { globalRegistry } = await import('./channels/registry.js');
|
|
104
|
+
const { MessageBus } = await import('./channels/interface.js');
|
|
105
|
+
const { Orchestrator: OrchestratorClass } = await import('./orchestrator.js');
|
|
106
|
+
const bus = new MessageBus();
|
|
107
|
+
// Init channels from config
|
|
108
|
+
_channelAdapters = await globalRegistry.initChannels(appConfig, bus);
|
|
109
|
+
// Build runner map for orchestrator (main context)
|
|
110
|
+
const orchestratorRunners = new Map();
|
|
111
|
+
const contexts = listContexts(db);
|
|
112
|
+
for (const ctx of contexts) {
|
|
113
|
+
orchestratorRunners.set(ctx.id, createRunner({ id: ctx.id, workspacePath: join(reebotDir, 'contexts', ctx.id, 'workspace') }, appConfig));
|
|
114
|
+
}
|
|
115
|
+
_orchestrator = new OrchestratorClass(appConfig, bus, _channelAdapters, orchestratorRunners);
|
|
116
|
+
_orchestrator.start();
|
|
117
|
+
// ── Credential proxy init ──────────────────────────────────────────
|
|
118
|
+
if (appConfig.credentialProxy?.enabled) {
|
|
119
|
+
try {
|
|
120
|
+
const { startProxy } = await import('./credential-proxy.js');
|
|
121
|
+
const proxyServer = await startProxy(appConfig);
|
|
122
|
+
if (proxyServer) {
|
|
123
|
+
_credProxy = proxyServer;
|
|
124
|
+
console.log('[server] Credential proxy started on 127.0.0.1:3001');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
console.error('[server] Credential proxy init failed:', err);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// ── Scheduler init (after orchestrator) ────────────────────────────
|
|
132
|
+
try {
|
|
133
|
+
const { Scheduler } = await import('./scheduler.js');
|
|
134
|
+
const { setGlobalScheduler } = await import('./scheduler-registry.js');
|
|
135
|
+
const schedulerOrchestrator = {
|
|
136
|
+
handleScheduledTask: async (task) => {
|
|
137
|
+
// Inject scheduled task as a message via the bus
|
|
138
|
+
const { MessageBus: MB, createIncomingMessage } = await import('./channels/interface.js');
|
|
139
|
+
bus.publish(createIncomingMessage({
|
|
140
|
+
channelType: 'scheduler',
|
|
141
|
+
peerId: 'scheduler',
|
|
142
|
+
content: task.prompt,
|
|
143
|
+
raw: { taskId: task.taskId },
|
|
144
|
+
}));
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
const schedulerInstance = new Scheduler(db, schedulerOrchestrator);
|
|
148
|
+
await schedulerInstance.start();
|
|
149
|
+
setGlobalScheduler(schedulerInstance);
|
|
150
|
+
_scheduler = schedulerInstance;
|
|
151
|
+
console.log('[server] Scheduler started');
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
console.error('[server] Scheduler init failed:', err);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
console.error('[server] Channel/orchestrator init failed:', err);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// ── Routes ────────────────────────────────────────────────────────────────
|
|
162
|
+
// GET / — serve WebChat
|
|
163
|
+
server.get('/', async (req, reply) => {
|
|
164
|
+
const webchatPath = resolve(__dirname, '../webchat/index.html');
|
|
165
|
+
try {
|
|
166
|
+
const html = readFileSync(webchatPath, 'utf-8');
|
|
167
|
+
reply.type('text/html').send(html);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
reply.status(404).send({ error: 'WebChat not found' });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
// GET /api/health
|
|
174
|
+
server.get('/api/health', async (_req, _reply) => {
|
|
175
|
+
return {
|
|
176
|
+
status: 'ok',
|
|
177
|
+
uptime: Math.floor((Date.now() - startTime) / 1000),
|
|
178
|
+
version: getVersion(),
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
// GET /api/status
|
|
182
|
+
server.get('/api/status', async (_req, _reply) => {
|
|
183
|
+
return {
|
|
184
|
+
agent: { name: 'Reeboot', model: { provider: '', id: '' } },
|
|
185
|
+
channels: [],
|
|
186
|
+
uptime: Math.floor((Date.now() - startTime) / 1000),
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
// ── Channel REST API ──────────────────────────────────────────────────────
|
|
190
|
+
// GET /api/channels
|
|
191
|
+
server.get('/api/channels', async (_req, _reply) => {
|
|
192
|
+
const result = [];
|
|
193
|
+
for (const [type, adapter] of _channelAdapters) {
|
|
194
|
+
result.push({ type, status: adapter.status(), connectedAt: adapter.connectedAt() });
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
});
|
|
198
|
+
// POST /api/channels/:type/login
|
|
199
|
+
server.post('/api/channels/:type/login', async (req, reply) => {
|
|
200
|
+
const { type } = req.params;
|
|
201
|
+
const adapter = _channelAdapters.get(type);
|
|
202
|
+
if (!adapter) {
|
|
203
|
+
return reply.status(404).send({ error: `Unknown channel type: ${type}` });
|
|
204
|
+
}
|
|
205
|
+
// Start login flow asynchronously (QR appears in terminal)
|
|
206
|
+
adapter.start().catch((err) => console.error(`[channels] login error for ${type}:`, err));
|
|
207
|
+
return reply.status(202).send({ message: 'Login initiated. Check terminal for QR code.' });
|
|
208
|
+
});
|
|
209
|
+
// POST /api/channels/:type/logout
|
|
210
|
+
server.post('/api/channels/:type/logout', async (req, reply) => {
|
|
211
|
+
const { type } = req.params;
|
|
212
|
+
const adapter = _channelAdapters.get(type);
|
|
213
|
+
if (!adapter) {
|
|
214
|
+
return reply.status(404).send({ error: `Unknown channel type: ${type}` });
|
|
215
|
+
}
|
|
216
|
+
await adapter.stop();
|
|
217
|
+
return reply.status(200).send({ message: `${type} logged out.` });
|
|
218
|
+
});
|
|
219
|
+
// ── Reload & Restart ──────────────────────────────────────────────────────
|
|
220
|
+
// POST /api/reload — hot-reload extensions/skills on all runners
|
|
221
|
+
server.post('/api/reload', async (_req, reply) => {
|
|
222
|
+
if (!_orchestrator) {
|
|
223
|
+
return reply.status(503).send({ error: 'Orchestrator not running' });
|
|
224
|
+
}
|
|
225
|
+
const errors = [];
|
|
226
|
+
for (const [id, runner] of _orchestrator.runners) {
|
|
227
|
+
try {
|
|
228
|
+
await runner.reload();
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
errors.push(`${id}: ${err.message}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (errors.length > 0) {
|
|
235
|
+
return reply.status(500).send({ error: errors.join('; ') });
|
|
236
|
+
}
|
|
237
|
+
return { message: 'Extensions and skills reloaded.' };
|
|
238
|
+
});
|
|
239
|
+
// POST /api/restart — graceful shutdown, process supervisor restarts
|
|
240
|
+
server.post('/api/restart', async (_req, reply) => {
|
|
241
|
+
reply.status(200).send({ message: 'Restarting...' });
|
|
242
|
+
// Drain in-flight turns (timeout 30s)
|
|
243
|
+
const DRAIN_TIMEOUT_MS = 30_000;
|
|
244
|
+
const drainStart = Date.now();
|
|
245
|
+
// Stop orchestrator so no new messages are dispatched
|
|
246
|
+
if (_orchestrator) {
|
|
247
|
+
_orchestrator.stop();
|
|
248
|
+
}
|
|
249
|
+
// Wait for active runners (ws handler uses _activeRunners)
|
|
250
|
+
while (_activeRunners.size > 0 && Date.now() - drainStart < DRAIN_TIMEOUT_MS) {
|
|
251
|
+
await new Promise(r => setTimeout(r, 100));
|
|
252
|
+
}
|
|
253
|
+
// Stop channels and dispose orchestrator runners
|
|
254
|
+
for (const adapter of _channelAdapters.values()) {
|
|
255
|
+
try {
|
|
256
|
+
await adapter.stop();
|
|
257
|
+
}
|
|
258
|
+
catch { /* ignore */ }
|
|
259
|
+
}
|
|
260
|
+
if (_orchestrator) {
|
|
261
|
+
for (const runner of _orchestrator.runners.values()) {
|
|
262
|
+
try {
|
|
263
|
+
await runner.dispose();
|
|
264
|
+
}
|
|
265
|
+
catch { /* ignore */ }
|
|
266
|
+
}
|
|
267
|
+
_orchestrator = null;
|
|
268
|
+
}
|
|
269
|
+
// Close server and exit — supervisor restarts
|
|
270
|
+
try {
|
|
271
|
+
await _server?.close();
|
|
272
|
+
}
|
|
273
|
+
catch { /* ignore */ }
|
|
274
|
+
process.exit(0);
|
|
275
|
+
});
|
|
276
|
+
// ── REST: Task API ────────────────────────────────────────────────────────
|
|
277
|
+
// GET /api/tasks
|
|
278
|
+
server.get('/api/tasks', async (_req, _reply) => {
|
|
279
|
+
const tasks = db
|
|
280
|
+
.prepare('SELECT id, context_id as contextId, schedule, prompt, enabled, last_run as lastRun, created_at as createdAt FROM tasks')
|
|
281
|
+
.all();
|
|
282
|
+
return tasks;
|
|
283
|
+
});
|
|
284
|
+
// POST /api/tasks
|
|
285
|
+
server.post('/api/tasks', async (req, reply) => {
|
|
286
|
+
const { contextId = 'main', schedule, prompt } = req.body ?? {};
|
|
287
|
+
if (!schedule || !prompt) {
|
|
288
|
+
return reply.status(400).send({ error: 'schedule and prompt are required' });
|
|
289
|
+
}
|
|
290
|
+
// Validate cron expression
|
|
291
|
+
const { validate: cronValidate } = await import('node-cron');
|
|
292
|
+
if (!cronValidate(schedule)) {
|
|
293
|
+
return reply.status(400).send({ error: 'Invalid cron expression' });
|
|
294
|
+
}
|
|
295
|
+
const { nanoid: nanoId } = await import('nanoid');
|
|
296
|
+
const id = nanoId();
|
|
297
|
+
db.prepare('INSERT INTO tasks (id, context_id, schedule, prompt, enabled) VALUES (?, ?, ?, ?, 1)').run(id, contextId, schedule, prompt);
|
|
298
|
+
// Register with scheduler if available
|
|
299
|
+
if (_scheduler) {
|
|
300
|
+
_scheduler.registerJob({ id, contextId, schedule, prompt });
|
|
301
|
+
}
|
|
302
|
+
const task = db
|
|
303
|
+
.prepare('SELECT id, context_id as contextId, schedule, prompt, enabled, last_run as lastRun FROM tasks WHERE id = ?')
|
|
304
|
+
.get(id);
|
|
305
|
+
return reply.status(201).send(task);
|
|
306
|
+
});
|
|
307
|
+
// DELETE /api/tasks/:id
|
|
308
|
+
server.delete('/api/tasks/:id', async (req, reply) => {
|
|
309
|
+
const { id } = req.params;
|
|
310
|
+
const task = db.prepare('SELECT id FROM tasks WHERE id = ?').get(id);
|
|
311
|
+
if (!task) {
|
|
312
|
+
return reply.status(404).send({ error: `Task not found: ${id}` });
|
|
313
|
+
}
|
|
314
|
+
db.prepare('DELETE FROM tasks WHERE id = ?').run(id);
|
|
315
|
+
if (_scheduler) {
|
|
316
|
+
_scheduler.cancelJob(id);
|
|
317
|
+
}
|
|
318
|
+
return reply.status(204).send();
|
|
319
|
+
});
|
|
320
|
+
// ── REST: Context API ─────────────────────────────────────────────────────
|
|
321
|
+
// GET /api/contexts
|
|
322
|
+
server.get('/api/contexts', async (_req, _reply) => {
|
|
323
|
+
return listContexts(db);
|
|
324
|
+
});
|
|
325
|
+
// POST /api/contexts
|
|
326
|
+
server.post('/api/contexts', async (req, reply) => {
|
|
327
|
+
const { name, model_provider, model_id } = req.body ?? {};
|
|
328
|
+
if (!name) {
|
|
329
|
+
return reply.status(400).send({ error: 'name is required' });
|
|
330
|
+
}
|
|
331
|
+
const ctx = createContext(db, {
|
|
332
|
+
name,
|
|
333
|
+
modelProvider: model_provider ?? '',
|
|
334
|
+
modelId: model_id ?? '',
|
|
335
|
+
});
|
|
336
|
+
await initContextWorkspace(ctx.id, reebotDir);
|
|
337
|
+
return reply.status(201).send(ctx);
|
|
338
|
+
});
|
|
339
|
+
// GET /api/contexts/:id/sessions
|
|
340
|
+
server.get('/api/contexts/:id/sessions', async (req, reply) => {
|
|
341
|
+
const ctx = getContextById(db, req.params.id);
|
|
342
|
+
if (!ctx) {
|
|
343
|
+
return reply.status(404).send({ error: 'Context not found' });
|
|
344
|
+
}
|
|
345
|
+
const sessions = await listSessions(req.params.id, reebotDir);
|
|
346
|
+
return sessions;
|
|
347
|
+
});
|
|
348
|
+
// ── WebSocket: /ws/chat/:contextId ───────────────────────────────────────
|
|
349
|
+
server.get('/ws/chat/:contextId', { websocket: true }, async (socket, req) => {
|
|
350
|
+
const { contextId } = req.params;
|
|
351
|
+
// Auth check for non-loopback connections
|
|
352
|
+
if (serverToken) {
|
|
353
|
+
const clientIp = req.socket.remoteAddress ?? '';
|
|
354
|
+
if (!isLoopback(clientIp)) {
|
|
355
|
+
const provided = extractToken(req);
|
|
356
|
+
if (provided !== serverToken) {
|
|
357
|
+
socket.close(1008, 'Unauthorized');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// Validate context exists
|
|
363
|
+
const ctx = getContextById(db, contextId);
|
|
364
|
+
if (!ctx) {
|
|
365
|
+
socket.close(4004, 'Unknown context');
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
// Generate session ID
|
|
369
|
+
const sessionId = nanoid();
|
|
370
|
+
// Send connected message
|
|
371
|
+
socket.send(JSON.stringify({ type: 'connected', contextId, sessionId }));
|
|
372
|
+
let activeRunId = null;
|
|
373
|
+
socket.on('message', async (rawData) => {
|
|
374
|
+
let msg;
|
|
375
|
+
try {
|
|
376
|
+
msg = JSON.parse(rawData.toString());
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
socket.send(JSON.stringify({ type: 'error', message: 'Invalid JSON' }));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (msg.type === 'cancel') {
|
|
383
|
+
const runner = _activeRunners.get(contextId);
|
|
384
|
+
if (runner) {
|
|
385
|
+
runner.abort();
|
|
386
|
+
socket.send(JSON.stringify({ type: 'cancelled', runId: activeRunId }));
|
|
387
|
+
_activeRunners.delete(contextId);
|
|
388
|
+
activeRunId = null;
|
|
389
|
+
}
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (msg.type === 'message') {
|
|
393
|
+
// Check if a turn is already in-flight
|
|
394
|
+
if (_activeRunners.has(contextId)) {
|
|
395
|
+
socket.send(JSON.stringify({
|
|
396
|
+
type: 'error',
|
|
397
|
+
message: 'Agent is busy. Cancel the current turn first.',
|
|
398
|
+
}));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const runId = nanoid();
|
|
402
|
+
activeRunId = runId;
|
|
403
|
+
// Get or create runner
|
|
404
|
+
let runner;
|
|
405
|
+
try {
|
|
406
|
+
const { defaultConfig } = await import('./config.js');
|
|
407
|
+
const cfg = opts.config ?? defaultConfig;
|
|
408
|
+
runner = createRunner({ id: contextId, workspacePath: join(reebotDir, 'contexts', contextId, 'workspace') }, cfg);
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
socket.send(JSON.stringify({ type: 'error', message: String(err.message) }));
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
_activeRunners.set(contextId, runner);
|
|
415
|
+
try {
|
|
416
|
+
await runner.prompt(msg.content ?? '', (event) => {
|
|
417
|
+
socket.send(JSON.stringify(event));
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
if (err?.name !== 'AbortError') {
|
|
422
|
+
socket.send(JSON.stringify({ type: 'error', message: String(err?.message ?? err) }));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
finally {
|
|
426
|
+
_activeRunners.delete(contextId);
|
|
427
|
+
activeRunId = null;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
socket.on('close', () => {
|
|
432
|
+
// Abort runner if still active
|
|
433
|
+
const runner = _activeRunners.get(contextId);
|
|
434
|
+
if (runner) {
|
|
435
|
+
runner.abort();
|
|
436
|
+
_activeRunners.delete(contextId);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
// Custom 404 handler
|
|
441
|
+
server.setNotFoundHandler((_req, reply) => {
|
|
442
|
+
reply.status(404).send({ error: 'Not found' });
|
|
443
|
+
});
|
|
444
|
+
await server.listen({ port, host });
|
|
445
|
+
_server = server;
|
|
446
|
+
return server;
|
|
447
|
+
}
|
|
448
|
+
// ─── stopServer ──────────────────────────────────────────────────────────────
|
|
449
|
+
export async function stopServer() {
|
|
450
|
+
// Stop credential proxy
|
|
451
|
+
if (_credProxy) {
|
|
452
|
+
try {
|
|
453
|
+
await _credProxy.close();
|
|
454
|
+
}
|
|
455
|
+
catch { /* ignore */ }
|
|
456
|
+
_credProxy = null;
|
|
457
|
+
}
|
|
458
|
+
// Stop scheduler
|
|
459
|
+
if (_scheduler) {
|
|
460
|
+
_scheduler.stop();
|
|
461
|
+
_scheduler = null;
|
|
462
|
+
}
|
|
463
|
+
// Stop orchestrator
|
|
464
|
+
if (_orchestrator) {
|
|
465
|
+
_orchestrator.stop();
|
|
466
|
+
_orchestrator = null;
|
|
467
|
+
}
|
|
468
|
+
// Stop channel adapters
|
|
469
|
+
for (const adapter of _channelAdapters.values()) {
|
|
470
|
+
try {
|
|
471
|
+
await adapter.stop();
|
|
472
|
+
}
|
|
473
|
+
catch { /* ignore */ }
|
|
474
|
+
}
|
|
475
|
+
_channelAdapters.clear();
|
|
476
|
+
// Abort all active runners before closing
|
|
477
|
+
for (const runner of _activeRunners.values()) {
|
|
478
|
+
try {
|
|
479
|
+
runner.abort();
|
|
480
|
+
}
|
|
481
|
+
catch { /* ignore */ }
|
|
482
|
+
}
|
|
483
|
+
_activeRunners.clear();
|
|
484
|
+
if (_server) {
|
|
485
|
+
await _server.close();
|
|
486
|
+
_server = null;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
//# sourceMappingURL=server.js.map
|