agent-office 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,121 @@
1
+ import { Cron } from "croner";
2
+ const CRON_INJECTION_BLURB = [
3
+ ``,
4
+ `---`,
5
+ `You have a scheduled cron job trigger. Please review the injected message above`,
6
+ `and take appropriate action.`,
7
+ ].join("\n");
8
+ export class CronScheduler {
9
+ options;
10
+ activeJobs = new Map();
11
+ sql = null;
12
+ opencode = null;
13
+ started = false;
14
+ constructor(options = {}) {
15
+ this.options = options;
16
+ }
17
+ async start(sql, opencode) {
18
+ if (this.started)
19
+ return;
20
+ this.sql = sql;
21
+ this.opencode = opencode;
22
+ const rows = await sql `
23
+ SELECT id, name, schedule, timezone, message, session_name
24
+ FROM cron_jobs
25
+ WHERE enabled = TRUE
26
+ `;
27
+ for (const job of rows) {
28
+ this.addJob(job);
29
+ }
30
+ this.started = true;
31
+ console.log(`Cron scheduler started: tracking ${rows.length} active jobs`);
32
+ }
33
+ stop() {
34
+ for (const cronInst of this.activeJobs.values()) {
35
+ cronInst.cron.stop();
36
+ }
37
+ this.activeJobs.clear();
38
+ this.started = false;
39
+ console.log("Cron scheduler stopped");
40
+ }
41
+ addJob(job) {
42
+ if (!this.opencode || !this.sql)
43
+ return;
44
+ const options = {
45
+ protect: true,
46
+ name: job.name,
47
+ };
48
+ if (job.timezone) {
49
+ options.timezone = job.timezone;
50
+ }
51
+ const cron = new Cron(job.schedule, options, async () => {
52
+ await this.executeJob(job);
53
+ });
54
+ this.activeJobs.set(job.id, { cron, job });
55
+ }
56
+ async executeJob(job) {
57
+ if (!this.sql || !this.opencode)
58
+ return;
59
+ const executedAt = new Date();
60
+ try {
61
+ const [session] = await this.sql `
62
+ SELECT session_id FROM sessions WHERE name = ${job.session_name}
63
+ `;
64
+ if (!session) {
65
+ throw new Error(`Session "${job.session_name}" not found`);
66
+ }
67
+ const providers = await this.opencode.app.providers();
68
+ const defaultEntry = Object.entries(providers.default)[0];
69
+ if (!defaultEntry) {
70
+ throw new Error("No default model configured");
71
+ }
72
+ const injectText = `[Cron Job "${job.name}" — ${executedAt.toISOString()}]\n${job.message}${CRON_INJECTION_BLURB}`;
73
+ await this.opencode.session.chat(session.session_id, {
74
+ modelID: defaultEntry[0],
75
+ providerID: defaultEntry[1],
76
+ parts: [{ type: "text", text: injectText }],
77
+ });
78
+ await this.sql `
79
+ UPDATE cron_jobs SET last_run = ${executedAt} WHERE id = ${job.id}
80
+ `;
81
+ await this.sql `
82
+ INSERT INTO cron_history (cron_job_id, executed_at, success)
83
+ VALUES (${job.id}, ${executedAt}, TRUE)
84
+ `;
85
+ this.options.onJobExecuted?.(job.id, true);
86
+ console.log(`Cron job "${job.name}" executed successfully`);
87
+ }
88
+ catch (err) {
89
+ const errorMessage = err instanceof Error ? err.message : String(err);
90
+ await this.sql `
91
+ INSERT INTO cron_history (cron_job_id, executed_at, success, error_message)
92
+ VALUES (${job.id}, ${executedAt}, FALSE, ${errorMessage})
93
+ `;
94
+ this.options.onJobExecuted?.(job.id, false, errorMessage);
95
+ console.error(`Cron job "${job.name}" failed:`, errorMessage);
96
+ }
97
+ }
98
+ addCronJob(job) {
99
+ if (job.enabled) {
100
+ this.addJob(job);
101
+ }
102
+ }
103
+ removeCronJob(jobId) {
104
+ const cronInst = this.activeJobs.get(jobId);
105
+ if (cronInst) {
106
+ cronInst.cron.stop();
107
+ this.activeJobs.delete(jobId);
108
+ }
109
+ }
110
+ enableCronJob(job) {
111
+ if (!this.activeJobs.has(job.id) && job.enabled) {
112
+ this.addJob(job);
113
+ }
114
+ }
115
+ disableCronJob(jobId) {
116
+ this.removeCronJob(jobId);
117
+ }
118
+ isTracking(jobId) {
119
+ return this.activeJobs.has(jobId);
120
+ }
121
+ }
@@ -1,3 +1,4 @@
1
1
  import type { Sql } from "../db/index.js";
2
2
  import type { OpencodeClient } from "../lib/opencode.js";
3
- export declare function createApp(sql: Sql, opencode: OpencodeClient, password: string, serverUrl: string): import("express-serve-static-core").Express;
3
+ import { CronScheduler } from "./cron.js";
4
+ export declare function createApp(sql: Sql, opencode: OpencodeClient, password: string, serverUrl: string, cronScheduler: CronScheduler): import("express-serve-static-core").Express;
@@ -10,13 +10,13 @@ function authMiddleware(password) {
10
10
  next();
11
11
  };
12
12
  }
13
- export function createApp(sql, opencode, password, serverUrl) {
13
+ export function createApp(sql, opencode, password, serverUrl, cronScheduler) {
14
14
  const app = express();
15
15
  app.use(express.json());
16
16
  // Worker routes are unauthenticated — mounted before auth middleware
17
17
  app.use("/", createWorkerRouter(sql, opencode, serverUrl));
18
18
  // Everything else requires Bearer auth
19
19
  app.use(authMiddleware(password));
20
- app.use("/", createRouter(sql, opencode, serverUrl));
20
+ app.use("/", createRouter(sql, opencode, serverUrl, cronScheduler));
21
21
  return app;
22
22
  }
@@ -1,5 +1,6 @@
1
1
  import { Router } from "express";
2
2
  import type { Sql } from "../db/index.js";
3
3
  import type { OpencodeClient } from "../lib/opencode.js";
4
- export declare function createRouter(sql: Sql, opencode: OpencodeClient, serverUrl: string): Router;
4
+ import { CronScheduler } from "./cron.js";
5
+ export declare function createRouter(sql: Sql, opencode: OpencodeClient, serverUrl: string, scheduler: CronScheduler): Router;
5
6
  export declare function createWorkerRouter(sql: Sql, opencode: OpencodeClient, serverUrl: string): Router;