agent-office 0.2.3 → 0.3.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/dist/cli.js +26 -10
- package/dist/commands/communicator.d.ts +1 -1
- package/dist/commands/communicator.js +1 -1
- package/dist/commands/screensaver.d.ts +8 -0
- package/dist/commands/screensaver.js +1236 -0
- package/dist/commands/serve.d.ts +2 -1
- package/dist/commands/serve.js +22 -15
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.js +2 -0
- package/dist/db/migrate.d.ts +2 -2
- package/dist/db/migrate.js +2 -124
- package/dist/db/postgresql-storage.d.ts +41 -0
- package/dist/db/postgresql-storage.js +366 -0
- package/dist/db/sqlite-storage.d.ts +42 -0
- package/dist/db/sqlite-storage.js +471 -0
- package/dist/db/storage-base.d.ts +65 -0
- package/dist/db/storage-base.js +113 -0
- package/dist/db/storage.d.ts +51 -0
- package/dist/db/storage.js +1 -0
- package/dist/lib/agentic-coding-server.d.ts +13 -1
- package/dist/lib/opencode-coding-server.d.ts +3 -2
- package/dist/lib/opencode-coding-server.js +28 -13
- package/dist/manage/components/SessionList.js +71 -66
- package/dist/server/cron.d.ts +3 -3
- package/dist/server/cron.js +15 -29
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +3 -3
- package/dist/server/routes.d.ts +3 -3
- package/dist/server/routes.js +154 -309
- package/package.json +3 -2
package/dist/commands/serve.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
interface ServeOptions {
|
|
2
2
|
databaseUrl?: string;
|
|
3
|
-
|
|
3
|
+
sqlite?: string;
|
|
4
4
|
host: string;
|
|
5
5
|
port: string;
|
|
6
6
|
memoryPath: string;
|
|
7
7
|
password?: string;
|
|
8
|
+
opencodeUrl: string;
|
|
8
9
|
}
|
|
9
10
|
export declare function serve(options: ServeOptions): Promise<void>;
|
|
10
11
|
export {};
|
package/dist/commands/serve.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createPostgresqlStorage, createSqliteStorage } from "../db/index.js";
|
|
2
2
|
import { runMigrations } from "../db/migrate.js";
|
|
3
3
|
import { OpenCodeCodingServer } from "../lib/opencode-coding-server.js";
|
|
4
4
|
import { createApp } from "../server/index.js";
|
|
@@ -10,32 +10,39 @@ export async function serve(options) {
|
|
|
10
10
|
console.error("Error: --password is required (or set AGENT_OFFICE_PASSWORD)");
|
|
11
11
|
process.exit(1);
|
|
12
12
|
}
|
|
13
|
-
const databaseUrl = options.databaseUrl;
|
|
14
|
-
if (!databaseUrl) {
|
|
15
|
-
console.error("Error: --database-url is required (or set DATABASE_URL)");
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
13
|
const port = parseInt(options.port, 10);
|
|
19
14
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
20
15
|
console.error(`Error: invalid port "${options.port}"`);
|
|
21
16
|
process.exit(1);
|
|
22
17
|
}
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
// Determine storage backend
|
|
19
|
+
let storage;
|
|
20
|
+
if (options.sqlite) {
|
|
21
|
+
console.log(`Connecting to SQLite database at ${options.sqlite}...`);
|
|
22
|
+
storage = createSqliteStorage(options.sqlite);
|
|
23
|
+
}
|
|
24
|
+
else if (options.databaseUrl) {
|
|
25
|
+
console.log("Connecting to PostgreSQL database...");
|
|
26
|
+
storage = createPostgresqlStorage(options.databaseUrl);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.error("Error: either --database-url (or DATABASE_URL) or --sqlite (or AGENT_OFFICE_SQLITE) is required");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
26
32
|
// Run migrations
|
|
27
33
|
console.log("Running migrations...");
|
|
28
34
|
try {
|
|
29
|
-
await runMigrations(
|
|
35
|
+
await runMigrations(storage);
|
|
30
36
|
console.log("Migrations complete.");
|
|
31
37
|
}
|
|
32
38
|
catch (err) {
|
|
33
39
|
console.error("Migration failed:", err);
|
|
34
|
-
await
|
|
40
|
+
await storage.close();
|
|
35
41
|
process.exit(1);
|
|
36
42
|
}
|
|
37
43
|
// Init agentic coding server (OpenCode implementation)
|
|
38
44
|
const agenticCodingServer = new OpenCodeCodingServer(options.opencodeUrl);
|
|
45
|
+
console.log(`Connecting to OpenCode server at ${options.opencodeUrl}...`);
|
|
39
46
|
const serverUrl = `http://${options.host}:${port}`;
|
|
40
47
|
// Create memory manager and verify embedding model
|
|
41
48
|
const memoryManager = new MemoryManager(options.memoryPath);
|
|
@@ -49,15 +56,15 @@ export async function serve(options) {
|
|
|
49
56
|
console.error("Try deleting the model cache and restarting:");
|
|
50
57
|
console.error(` rm -rf ${options.memoryPath}/.model-cache`);
|
|
51
58
|
memoryManager.closeAll();
|
|
52
|
-
await
|
|
59
|
+
await storage.close();
|
|
53
60
|
process.exit(1);
|
|
54
61
|
}
|
|
55
62
|
// Create cron scheduler
|
|
56
63
|
const cronScheduler = new CronScheduler();
|
|
57
64
|
// Create Express app
|
|
58
|
-
const app = createApp(
|
|
65
|
+
const app = createApp(storage, agenticCodingServer, password, serverUrl, cronScheduler, memoryManager);
|
|
59
66
|
// Start cron scheduler
|
|
60
|
-
await cronScheduler.start(
|
|
67
|
+
await cronScheduler.start(storage, agenticCodingServer, serverUrl);
|
|
61
68
|
// Start server
|
|
62
69
|
const server = app.listen(port, options.host, () => {
|
|
63
70
|
console.log(`agent-office server listening on ${serverUrl}`);
|
|
@@ -68,7 +75,7 @@ export async function serve(options) {
|
|
|
68
75
|
server.close(async () => {
|
|
69
76
|
cronScheduler.stop();
|
|
70
77
|
memoryManager.closeAll();
|
|
71
|
-
await
|
|
78
|
+
await storage.close();
|
|
72
79
|
console.log("Goodbye.");
|
|
73
80
|
process.exit(0);
|
|
74
81
|
});
|
package/dist/db/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import postgres from "postgres";
|
|
2
2
|
export type Sql = ReturnType<typeof postgres>;
|
|
3
3
|
export declare function createDb(databaseUrl: string): Sql;
|
|
4
|
+
export type { AgentOfficeStorage, WatchListener, WatchState, SenderInfo } from "./storage.js";
|
|
5
|
+
export { AgentOfficePostgresqlStorage, createPostgresqlStorage } from "./postgresql-storage.js";
|
|
6
|
+
export { AgentOfficeSqliteStorage, createSqliteStorage } from "./sqlite-storage.js";
|
|
4
7
|
export interface SessionRow {
|
|
5
8
|
id: number;
|
|
6
9
|
name: string;
|
package/dist/db/index.js
CHANGED
package/dist/db/migrate.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function runMigrations(
|
|
1
|
+
import type { AgentOfficeStorage } from "./storage.js";
|
|
2
|
+
export declare function runMigrations(storage: AgentOfficeStorage): Promise<void>;
|
package/dist/db/migrate.js
CHANGED
|
@@ -1,125 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
version: 1,
|
|
4
|
-
name: "create_sessions",
|
|
5
|
-
sql: `
|
|
6
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
7
|
-
id SERIAL PRIMARY KEY,
|
|
8
|
-
name VARCHAR(255) UNIQUE NOT NULL,
|
|
9
|
-
session_id VARCHAR(255) UNIQUE NOT NULL,
|
|
10
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
11
|
-
);
|
|
12
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_name ON sessions(name);
|
|
13
|
-
`,
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
version: 2,
|
|
17
|
-
name: "add_agent_code",
|
|
18
|
-
sql: `
|
|
19
|
-
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS agent_code UUID NOT NULL DEFAULT gen_random_uuid();
|
|
20
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_agent_code ON sessions(agent_code);
|
|
21
|
-
`,
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
version: 3,
|
|
25
|
-
name: "create_config_table",
|
|
26
|
-
sql: `
|
|
27
|
-
CREATE TABLE IF NOT EXISTS config (
|
|
28
|
-
key VARCHAR(255) PRIMARY KEY,
|
|
29
|
-
value TEXT NOT NULL
|
|
30
|
-
);
|
|
31
|
-
INSERT INTO config (key, value) VALUES ('human_name', 'Human') ON CONFLICT DO NOTHING;
|
|
32
|
-
INSERT INTO config (key, value) VALUES ('human_description', '') ON CONFLICT DO NOTHING;
|
|
33
|
-
`,
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
version: 5,
|
|
37
|
-
name: "add_mode_to_sessions",
|
|
38
|
-
sql: `
|
|
39
|
-
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS mode VARCHAR(255) NULL;
|
|
40
|
-
`,
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
version: 4,
|
|
44
|
-
name: "create_messages_table",
|
|
45
|
-
sql: `
|
|
46
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
47
|
-
id SERIAL PRIMARY KEY,
|
|
48
|
-
from_name VARCHAR(255) NOT NULL,
|
|
49
|
-
to_name VARCHAR(255) NOT NULL,
|
|
50
|
-
body TEXT NOT NULL,
|
|
51
|
-
read BOOLEAN NOT NULL DEFAULT FALSE,
|
|
52
|
-
injected BOOLEAN NOT NULL DEFAULT FALSE,
|
|
53
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
54
|
-
);
|
|
55
|
-
CREATE INDEX IF NOT EXISTS idx_messages_to_name ON messages(to_name);
|
|
56
|
-
CREATE INDEX IF NOT EXISTS idx_messages_from_name ON messages(from_name);
|
|
57
|
-
CREATE INDEX IF NOT EXISTS idx_messages_read ON messages(read);
|
|
58
|
-
`,
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
version: 6,
|
|
62
|
-
name: "create_cron_tables",
|
|
63
|
-
sql: `
|
|
64
|
-
CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
65
|
-
id SERIAL PRIMARY KEY,
|
|
66
|
-
name VARCHAR(255) NOT NULL,
|
|
67
|
-
session_name VARCHAR(255) NOT NULL REFERENCES sessions(name) ON DELETE CASCADE,
|
|
68
|
-
schedule TEXT NOT NULL,
|
|
69
|
-
timezone VARCHAR(100),
|
|
70
|
-
message TEXT NOT NULL,
|
|
71
|
-
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
72
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
73
|
-
last_run TIMESTAMPTZ
|
|
74
|
-
);
|
|
75
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_cron_jobs_name_session ON cron_jobs(name, session_name);
|
|
76
|
-
CREATE INDEX IF NOT EXISTS idx_cron_jobs_session_name ON cron_jobs(session_name);
|
|
77
|
-
CREATE INDEX IF NOT EXISTS idx_cron_jobs_enabled ON cron_jobs(enabled);
|
|
78
|
-
|
|
79
|
-
CREATE TABLE IF NOT EXISTS cron_history (
|
|
80
|
-
id SERIAL PRIMARY KEY,
|
|
81
|
-
cron_job_id INTEGER NOT NULL REFERENCES cron_jobs(id) ON DELETE CASCADE,
|
|
82
|
-
executed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
83
|
-
success BOOLEAN NOT NULL DEFAULT TRUE,
|
|
84
|
-
error_message TEXT
|
|
85
|
-
);
|
|
86
|
-
CREATE INDEX IF NOT EXISTS idx_cron_history_job_id ON cron_history(cron_job_id);
|
|
87
|
-
`,
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
version: 7,
|
|
91
|
-
name: "add_status_to_sessions",
|
|
92
|
-
sql: `
|
|
93
|
-
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS status TEXT NULL;
|
|
94
|
-
`,
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
version: 8,
|
|
98
|
-
name: "rename_mode_to_agent",
|
|
99
|
-
sql: `
|
|
100
|
-
ALTER TABLE sessions RENAME COLUMN mode TO agent;
|
|
101
|
-
`,
|
|
102
|
-
},
|
|
103
|
-
];
|
|
104
|
-
export async function runMigrations(sql) {
|
|
105
|
-
await sql `
|
|
106
|
-
CREATE TABLE IF NOT EXISTS _migrations (
|
|
107
|
-
version INTEGER PRIMARY KEY,
|
|
108
|
-
name VARCHAR(255) NOT NULL,
|
|
109
|
-
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
110
|
-
)
|
|
111
|
-
`;
|
|
112
|
-
const applied = await sql `
|
|
113
|
-
SELECT version FROM _migrations ORDER BY version
|
|
114
|
-
`;
|
|
115
|
-
const appliedVersions = new Set(applied.map((r) => r.version));
|
|
116
|
-
for (const migration of MIGRATIONS) {
|
|
117
|
-
if (appliedVersions.has(migration.version))
|
|
118
|
-
continue;
|
|
119
|
-
console.log(` Applying migration ${migration.version}: ${migration.name}`);
|
|
120
|
-
await sql.begin(async (tx) => {
|
|
121
|
-
await tx.unsafe(migration.sql);
|
|
122
|
-
await tx.unsafe(`INSERT INTO _migrations (version, name) VALUES ($1, $2)`, [migration.version, migration.name]);
|
|
123
|
-
});
|
|
124
|
-
}
|
|
1
|
+
export async function runMigrations(storage) {
|
|
2
|
+
await storage.runMigrations();
|
|
125
3
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AgentOfficeStorageBase } from "./storage-base.js";
|
|
2
|
+
import type { SessionRow, ConfigRow, MessageRow, CronJobRow, CronHistoryRow, Sql } from "./index.js";
|
|
3
|
+
export declare class AgentOfficePostgresqlStorage extends AgentOfficeStorageBase {
|
|
4
|
+
private sql;
|
|
5
|
+
constructor(sql: Sql);
|
|
6
|
+
close(): Promise<void>;
|
|
7
|
+
begin<T>(callback: (tx: import("./storage.js").AgentOfficeStorage) => Promise<T>): Promise<T>;
|
|
8
|
+
listSessions(): Promise<SessionRow[]>;
|
|
9
|
+
getSessionByName(name: string): Promise<SessionRow | null>;
|
|
10
|
+
getSessionByAgentCode(agentCode: string): Promise<SessionRow | null>;
|
|
11
|
+
getSessionIdByName(name: string): Promise<number | null>;
|
|
12
|
+
createSession(name: string, sessionId: string, agent: string): Promise<SessionRow>;
|
|
13
|
+
deleteSession(id: number): Promise<void>;
|
|
14
|
+
regenerateAgentCode(name: string): Promise<SessionRow>;
|
|
15
|
+
updateSessionAgent(name: string, agent: string): Promise<SessionRow>;
|
|
16
|
+
updateSessionStatus(agentCode: string, status: string | null): Promise<void>;
|
|
17
|
+
updateSessionId(name: string, sessionId: string): Promise<void>;
|
|
18
|
+
sessionExists(name: string): Promise<boolean>;
|
|
19
|
+
getAllConfig(): Promise<ConfigRow[]>;
|
|
20
|
+
getConfig(key: string): Promise<string | null>;
|
|
21
|
+
setConfig(key: string, value: string): Promise<void>;
|
|
22
|
+
listMessagesForRecipient(name: string, unreadOnly: boolean): Promise<MessageRow[]>;
|
|
23
|
+
listMessagesFromSender(name: string): Promise<MessageRow[]>;
|
|
24
|
+
createMessageImpl(from: string, to: string, body: string): Promise<MessageRow>;
|
|
25
|
+
markMessageAsRead(id: number): Promise<MessageRow | null>;
|
|
26
|
+
markMessageAsInjected(id: number): Promise<void>;
|
|
27
|
+
listCronJobs(): Promise<CronJobRow[]>;
|
|
28
|
+
listCronJobsForSession(sessionName: string): Promise<CronJobRow[]>;
|
|
29
|
+
getCronJobById(id: number): Promise<CronJobRow | null>;
|
|
30
|
+
getCronJobByNameAndSession(name: string, sessionName: string): Promise<CronJobRow | null>;
|
|
31
|
+
createCronJob(name: string, sessionName: string, schedule: string, timezone: string | null, message: string): Promise<CronJobRow>;
|
|
32
|
+
deleteCronJob(id: number): Promise<void>;
|
|
33
|
+
enableCronJob(id: number): Promise<void>;
|
|
34
|
+
disableCronJob(id: number): Promise<void>;
|
|
35
|
+
updateCronJobLastRun(id: number, lastRun: Date): Promise<void>;
|
|
36
|
+
cronJobExistsForSession(name: string, sessionName: string): Promise<boolean>;
|
|
37
|
+
listCronHistory(cronJobId: number, limit: number): Promise<CronHistoryRow[]>;
|
|
38
|
+
createCronHistory(cronJobId: number, executedAt: Date, success: boolean, errorMessage?: string): Promise<void>;
|
|
39
|
+
runMigrations(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
export declare function createPostgresqlStorage(databaseUrl: string): AgentOfficePostgresqlStorage;
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import postgres from "postgres";
|
|
2
|
+
import { AgentOfficeStorageBase } from "./storage-base.js";
|
|
3
|
+
export class AgentOfficePostgresqlStorage extends AgentOfficeStorageBase {
|
|
4
|
+
sql;
|
|
5
|
+
constructor(sql) {
|
|
6
|
+
super();
|
|
7
|
+
this.sql = sql;
|
|
8
|
+
}
|
|
9
|
+
async close() {
|
|
10
|
+
await this.sql.end();
|
|
11
|
+
}
|
|
12
|
+
async begin(callback) {
|
|
13
|
+
return this.sql.begin(async (tx) => {
|
|
14
|
+
const txStorage = new AgentOfficePostgresqlStorage(tx);
|
|
15
|
+
return callback(txStorage);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
// Sessions
|
|
19
|
+
async listSessions() {
|
|
20
|
+
return this.sql `
|
|
21
|
+
SELECT id, name, session_id, agent_code, agent, status, created_at
|
|
22
|
+
FROM sessions
|
|
23
|
+
ORDER BY created_at DESC
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
26
|
+
async getSessionByName(name) {
|
|
27
|
+
const [row] = await this.sql `
|
|
28
|
+
SELECT id, name, session_id, agent_code, agent, status, created_at
|
|
29
|
+
FROM sessions WHERE name = ${name}
|
|
30
|
+
`;
|
|
31
|
+
return row ?? null;
|
|
32
|
+
}
|
|
33
|
+
async getSessionByAgentCode(agentCode) {
|
|
34
|
+
const [row] = await this.sql `
|
|
35
|
+
SELECT id, name, session_id, agent_code, agent, status, created_at
|
|
36
|
+
FROM sessions WHERE agent_code = ${agentCode}
|
|
37
|
+
`;
|
|
38
|
+
return row ?? null;
|
|
39
|
+
}
|
|
40
|
+
async getSessionIdByName(name) {
|
|
41
|
+
const [row] = await this.sql `
|
|
42
|
+
SELECT id FROM sessions WHERE name = ${name}
|
|
43
|
+
`;
|
|
44
|
+
return row?.id ?? null;
|
|
45
|
+
}
|
|
46
|
+
async createSession(name, sessionId, agent) {
|
|
47
|
+
const [row] = await this.sql `
|
|
48
|
+
INSERT INTO sessions (name, session_id, agent)
|
|
49
|
+
VALUES (${name}, ${sessionId}, ${agent})
|
|
50
|
+
RETURNING id, name, session_id, agent_code, agent, status, created_at
|
|
51
|
+
`;
|
|
52
|
+
return row;
|
|
53
|
+
}
|
|
54
|
+
async deleteSession(id) {
|
|
55
|
+
await this.sql `DELETE FROM sessions WHERE id = ${id}`;
|
|
56
|
+
}
|
|
57
|
+
async regenerateAgentCode(name) {
|
|
58
|
+
const [row] = await this.sql `
|
|
59
|
+
UPDATE sessions
|
|
60
|
+
SET agent_code = gen_random_uuid()
|
|
61
|
+
WHERE name = ${name}
|
|
62
|
+
RETURNING id, name, session_id, agent_code, agent, status, created_at
|
|
63
|
+
`;
|
|
64
|
+
return row;
|
|
65
|
+
}
|
|
66
|
+
async updateSessionAgent(name, agent) {
|
|
67
|
+
const [row] = await this.sql `
|
|
68
|
+
UPDATE sessions
|
|
69
|
+
SET agent = ${agent}
|
|
70
|
+
WHERE name = ${name}
|
|
71
|
+
RETURNING id, name, session_id, agent_code, agent, status, created_at
|
|
72
|
+
`;
|
|
73
|
+
return row;
|
|
74
|
+
}
|
|
75
|
+
async updateSessionStatus(agentCode, status) {
|
|
76
|
+
await this.sql `
|
|
77
|
+
UPDATE sessions
|
|
78
|
+
SET status = ${status}
|
|
79
|
+
WHERE agent_code = ${agentCode}
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
async updateSessionId(name, sessionId) {
|
|
83
|
+
await this.sql `
|
|
84
|
+
UPDATE sessions SET session_id = ${sessionId} WHERE name = ${name}
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
async sessionExists(name) {
|
|
88
|
+
const [row] = await this.sql `
|
|
89
|
+
SELECT id FROM sessions WHERE name = ${name}
|
|
90
|
+
`;
|
|
91
|
+
return !!row;
|
|
92
|
+
}
|
|
93
|
+
// Config
|
|
94
|
+
async getAllConfig() {
|
|
95
|
+
return this.sql `SELECT key, value FROM config`;
|
|
96
|
+
}
|
|
97
|
+
async getConfig(key) {
|
|
98
|
+
const [row] = await this.sql `
|
|
99
|
+
SELECT value FROM config WHERE key = ${key}
|
|
100
|
+
`;
|
|
101
|
+
return row?.value ?? null;
|
|
102
|
+
}
|
|
103
|
+
async setConfig(key, value) {
|
|
104
|
+
await this.sql `
|
|
105
|
+
INSERT INTO config (key, value) VALUES (${key}, ${value})
|
|
106
|
+
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
// Messages
|
|
110
|
+
async listMessagesForRecipient(name, unreadOnly) {
|
|
111
|
+
if (unreadOnly) {
|
|
112
|
+
return this.sql `
|
|
113
|
+
SELECT id, from_name, to_name, body, read, injected, created_at
|
|
114
|
+
FROM messages
|
|
115
|
+
WHERE to_name = ${name} AND read = FALSE
|
|
116
|
+
ORDER BY created_at DESC
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
return this.sql `
|
|
120
|
+
SELECT id, from_name, to_name, body, read, injected, created_at
|
|
121
|
+
FROM messages
|
|
122
|
+
WHERE to_name = ${name}
|
|
123
|
+
ORDER BY created_at DESC
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
async listMessagesFromSender(name) {
|
|
127
|
+
return this.sql `
|
|
128
|
+
SELECT id, from_name, to_name, body, read, injected, created_at
|
|
129
|
+
FROM messages
|
|
130
|
+
WHERE from_name = ${name}
|
|
131
|
+
ORDER BY created_at DESC
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
async createMessageImpl(from, to, body) {
|
|
135
|
+
const [row] = await this.sql `
|
|
136
|
+
INSERT INTO messages (from_name, to_name, body)
|
|
137
|
+
VALUES (${from}, ${to}, ${body})
|
|
138
|
+
RETURNING id, from_name, to_name, body, read, injected, created_at
|
|
139
|
+
`;
|
|
140
|
+
return row;
|
|
141
|
+
}
|
|
142
|
+
async markMessageAsRead(id) {
|
|
143
|
+
const [row] = await this.sql `
|
|
144
|
+
UPDATE messages SET read = TRUE WHERE id = ${id}
|
|
145
|
+
RETURNING id, from_name, to_name, body, read, injected, created_at
|
|
146
|
+
`;
|
|
147
|
+
return row ?? null;
|
|
148
|
+
}
|
|
149
|
+
async markMessageAsInjected(id) {
|
|
150
|
+
await this.sql `UPDATE messages SET injected = TRUE WHERE id = ${id}`;
|
|
151
|
+
}
|
|
152
|
+
// Cron Jobs
|
|
153
|
+
async listCronJobs() {
|
|
154
|
+
return this.sql `
|
|
155
|
+
SELECT id, name, session_name, schedule, timezone, message, enabled, created_at, last_run
|
|
156
|
+
FROM cron_jobs
|
|
157
|
+
ORDER BY name
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
160
|
+
async listCronJobsForSession(sessionName) {
|
|
161
|
+
return this.sql `
|
|
162
|
+
SELECT id, name, session_name, schedule, timezone, message, enabled, created_at, last_run
|
|
163
|
+
FROM cron_jobs
|
|
164
|
+
WHERE session_name = ${sessionName}
|
|
165
|
+
ORDER BY name
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
async getCronJobById(id) {
|
|
169
|
+
const [row] = await this.sql `
|
|
170
|
+
SELECT id, name, session_name, schedule, timezone, message, enabled, created_at, last_run
|
|
171
|
+
FROM cron_jobs WHERE id = ${id}
|
|
172
|
+
`;
|
|
173
|
+
return row ?? null;
|
|
174
|
+
}
|
|
175
|
+
async getCronJobByNameAndSession(name, sessionName) {
|
|
176
|
+
const [row] = await this.sql `
|
|
177
|
+
SELECT id, name, session_name, schedule, timezone, message, enabled, created_at, last_run
|
|
178
|
+
FROM cron_jobs WHERE name = ${name} AND session_name = ${sessionName}
|
|
179
|
+
`;
|
|
180
|
+
return row ?? null;
|
|
181
|
+
}
|
|
182
|
+
async createCronJob(name, sessionName, schedule, timezone, message) {
|
|
183
|
+
const [row] = await this.sql `
|
|
184
|
+
INSERT INTO cron_jobs (name, session_name, schedule, timezone, message)
|
|
185
|
+
VALUES (${name}, ${sessionName}, ${schedule}, ${timezone}, ${message})
|
|
186
|
+
RETURNING id, name, session_name, schedule, timezone, message, enabled, created_at, last_run
|
|
187
|
+
`;
|
|
188
|
+
return row;
|
|
189
|
+
}
|
|
190
|
+
async deleteCronJob(id) {
|
|
191
|
+
await this.sql `DELETE FROM cron_jobs WHERE id = ${id}`;
|
|
192
|
+
}
|
|
193
|
+
async enableCronJob(id) {
|
|
194
|
+
await this.sql `UPDATE cron_jobs SET enabled = TRUE WHERE id = ${id}`;
|
|
195
|
+
}
|
|
196
|
+
async disableCronJob(id) {
|
|
197
|
+
await this.sql `UPDATE cron_jobs SET enabled = FALSE WHERE id = ${id}`;
|
|
198
|
+
}
|
|
199
|
+
async updateCronJobLastRun(id, lastRun) {
|
|
200
|
+
await this.sql `UPDATE cron_jobs SET last_run = ${lastRun} WHERE id = ${id}`;
|
|
201
|
+
}
|
|
202
|
+
async cronJobExistsForSession(name, sessionName) {
|
|
203
|
+
const [row] = await this.sql `
|
|
204
|
+
SELECT id FROM cron_jobs WHERE name = ${name} AND session_name = ${sessionName}
|
|
205
|
+
`;
|
|
206
|
+
return !!row;
|
|
207
|
+
}
|
|
208
|
+
// Cron History
|
|
209
|
+
async listCronHistory(cronJobId, limit) {
|
|
210
|
+
return this.sql `
|
|
211
|
+
SELECT id, cron_job_id, executed_at, success, error_message
|
|
212
|
+
FROM cron_history
|
|
213
|
+
WHERE cron_job_id = ${cronJobId}
|
|
214
|
+
ORDER BY executed_at DESC
|
|
215
|
+
LIMIT ${limit}
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
218
|
+
async createCronHistory(cronJobId, executedAt, success, errorMessage) {
|
|
219
|
+
if (success) {
|
|
220
|
+
await this.sql `
|
|
221
|
+
INSERT INTO cron_history (cron_job_id, executed_at, success)
|
|
222
|
+
VALUES (${cronJobId}, ${executedAt}, TRUE)
|
|
223
|
+
`;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
await this.sql `
|
|
227
|
+
INSERT INTO cron_history (cron_job_id, executed_at, success, error_message)
|
|
228
|
+
VALUES (${cronJobId}, ${executedAt}, FALSE, ${errorMessage ?? null})
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async runMigrations() {
|
|
233
|
+
const MIGRATIONS = [
|
|
234
|
+
{
|
|
235
|
+
version: 1,
|
|
236
|
+
name: "create_sessions",
|
|
237
|
+
sql: `
|
|
238
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
239
|
+
id SERIAL PRIMARY KEY,
|
|
240
|
+
name VARCHAR(255) UNIQUE NOT NULL,
|
|
241
|
+
session_id VARCHAR(255) UNIQUE NOT NULL,
|
|
242
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
243
|
+
);
|
|
244
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_name ON sessions(name);
|
|
245
|
+
`,
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
version: 2,
|
|
249
|
+
name: "add_agent_code",
|
|
250
|
+
sql: `
|
|
251
|
+
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS agent_code UUID NOT NULL DEFAULT gen_random_uuid();
|
|
252
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_agent_code ON sessions(agent_code);
|
|
253
|
+
`,
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
version: 3,
|
|
257
|
+
name: "create_config_table",
|
|
258
|
+
sql: `
|
|
259
|
+
CREATE TABLE IF NOT EXISTS config (
|
|
260
|
+
key VARCHAR(255) PRIMARY KEY,
|
|
261
|
+
value TEXT NOT NULL
|
|
262
|
+
);
|
|
263
|
+
INSERT INTO config (key, value) VALUES ('human_name', 'Human') ON CONFLICT DO NOTHING;
|
|
264
|
+
INSERT INTO config (key, value) VALUES ('human_description', '') ON CONFLICT DO NOTHING;
|
|
265
|
+
`,
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
version: 5,
|
|
269
|
+
name: "add_mode_to_sessions",
|
|
270
|
+
sql: `
|
|
271
|
+
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS mode VARCHAR(255) NULL;
|
|
272
|
+
`,
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
version: 4,
|
|
276
|
+
name: "create_messages_table",
|
|
277
|
+
sql: `
|
|
278
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
279
|
+
id SERIAL PRIMARY KEY,
|
|
280
|
+
from_name VARCHAR(255) NOT NULL,
|
|
281
|
+
to_name VARCHAR(255) NOT NULL,
|
|
282
|
+
body TEXT NOT NULL,
|
|
283
|
+
read BOOLEAN NOT NULL DEFAULT FALSE,
|
|
284
|
+
injected BOOLEAN NOT NULL DEFAULT FALSE,
|
|
285
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
286
|
+
);
|
|
287
|
+
CREATE INDEX IF NOT EXISTS idx_messages_to_name ON messages(to_name);
|
|
288
|
+
CREATE INDEX IF NOT EXISTS idx_messages_from_name ON messages(from_name);
|
|
289
|
+
CREATE INDEX IF NOT EXISTS idx_messages_read ON messages(read);
|
|
290
|
+
`,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
version: 6,
|
|
294
|
+
name: "create_cron_tables",
|
|
295
|
+
sql: `
|
|
296
|
+
CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
297
|
+
id SERIAL PRIMARY KEY,
|
|
298
|
+
name VARCHAR(255) NOT NULL,
|
|
299
|
+
session_name VARCHAR(255) NOT NULL REFERENCES sessions(name) ON DELETE CASCADE,
|
|
300
|
+
schedule TEXT NOT NULL,
|
|
301
|
+
timezone VARCHAR(100),
|
|
302
|
+
message TEXT NOT NULL,
|
|
303
|
+
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
304
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
305
|
+
last_run TIMESTAMPTZ
|
|
306
|
+
);
|
|
307
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_cron_jobs_name_session ON cron_jobs(name, session_name);
|
|
308
|
+
CREATE INDEX IF NOT EXISTS idx_cron_jobs_session_name ON cron_jobs(session_name);
|
|
309
|
+
CREATE INDEX IF NOT EXISTS idx_cron_jobs_enabled ON cron_jobs(enabled);
|
|
310
|
+
|
|
311
|
+
CREATE TABLE IF NOT EXISTS cron_history (
|
|
312
|
+
id SERIAL PRIMARY KEY,
|
|
313
|
+
cron_job_id INTEGER NOT NULL REFERENCES cron_jobs(id) ON DELETE CASCADE,
|
|
314
|
+
executed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
315
|
+
success BOOLEAN NOT NULL DEFAULT TRUE,
|
|
316
|
+
error_message TEXT
|
|
317
|
+
);
|
|
318
|
+
CREATE INDEX IF NOT EXISTS idx_cron_history_job_id ON cron_history(cron_job_id);
|
|
319
|
+
`,
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
version: 7,
|
|
323
|
+
name: "add_status_to_sessions",
|
|
324
|
+
sql: `
|
|
325
|
+
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS status TEXT NULL;
|
|
326
|
+
`,
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
version: 8,
|
|
330
|
+
name: "rename_mode_to_agent",
|
|
331
|
+
sql: `
|
|
332
|
+
ALTER TABLE sessions RENAME COLUMN mode TO agent;
|
|
333
|
+
`,
|
|
334
|
+
},
|
|
335
|
+
];
|
|
336
|
+
await this.sql `
|
|
337
|
+
CREATE TABLE IF NOT EXISTS _migrations (
|
|
338
|
+
version INTEGER PRIMARY KEY,
|
|
339
|
+
name VARCHAR(255) NOT NULL,
|
|
340
|
+
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
341
|
+
)
|
|
342
|
+
`;
|
|
343
|
+
const applied = await this.sql `
|
|
344
|
+
SELECT version FROM _migrations ORDER BY version
|
|
345
|
+
`;
|
|
346
|
+
const appliedVersions = new Set(applied.map((r) => r.version));
|
|
347
|
+
for (const migration of MIGRATIONS) {
|
|
348
|
+
if (appliedVersions.has(migration.version))
|
|
349
|
+
continue;
|
|
350
|
+
console.log(` Applying migration ${migration.version}: ${migration.name}`);
|
|
351
|
+
await this.sql.begin(async (tx) => {
|
|
352
|
+
await tx.unsafe(migration.sql);
|
|
353
|
+
await tx.unsafe(`INSERT INTO _migrations (version, name) VALUES ($1, $2)`, [migration.version, migration.name]);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
export function createPostgresqlStorage(databaseUrl) {
|
|
359
|
+
const sql = postgres(databaseUrl, {
|
|
360
|
+
max: 10,
|
|
361
|
+
idle_timeout: 30,
|
|
362
|
+
connect_timeout: 10,
|
|
363
|
+
onnotice: () => { },
|
|
364
|
+
});
|
|
365
|
+
return new AgentOfficePostgresqlStorage(sql);
|
|
366
|
+
}
|