agent-office 0.0.0 → 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.
Files changed (33) hide show
  1. package/dist/cli.js +102 -3
  2. package/dist/commands/serve.js +9 -2
  3. package/dist/commands/worker.d.ts +13 -0
  4. package/dist/commands/worker.js +120 -5
  5. package/dist/db/index.d.ts +32 -0
  6. package/dist/db/migrate.js +66 -0
  7. package/dist/manage/app.js +55 -35
  8. package/dist/manage/components/CronList.d.ts +9 -0
  9. package/dist/manage/components/CronList.js +310 -0
  10. package/dist/manage/components/ItemSelector.d.ts +7 -0
  11. package/dist/manage/components/ItemSelector.js +20 -0
  12. package/dist/manage/components/MenuSelect.d.ts +13 -0
  13. package/dist/manage/components/MenuSelect.js +22 -0
  14. package/dist/manage/components/MyMail.d.ts +9 -0
  15. package/dist/manage/components/MyMail.js +143 -0
  16. package/dist/manage/components/Profile.d.ts +8 -0
  17. package/dist/manage/components/Profile.js +60 -0
  18. package/dist/manage/components/ReadMail.d.ts +8 -0
  19. package/dist/manage/components/ReadMail.js +110 -0
  20. package/dist/manage/components/SendMessage.d.ts +9 -0
  21. package/dist/manage/components/SendMessage.js +79 -0
  22. package/dist/manage/components/SessionList.js +392 -31
  23. package/dist/manage/components/SessionSidebar.d.ts +6 -0
  24. package/dist/manage/components/SessionSidebar.js +18 -0
  25. package/dist/manage/hooks/useApi.d.ts +74 -1
  26. package/dist/manage/hooks/useApi.js +69 -3
  27. package/dist/server/cron.d.ts +24 -0
  28. package/dist/server/cron.js +121 -0
  29. package/dist/server/index.d.ts +2 -1
  30. package/dist/server/index.js +3 -3
  31. package/dist/server/routes.d.ts +3 -2
  32. package/dist/server/routes.js +976 -23
  33. package/package.json +3 -2
@@ -0,0 +1,6 @@
1
+ interface SessionSidebarProps {
2
+ serverUrl: string;
3
+ password: string;
4
+ }
5
+ export declare function SessionSidebar({ serverUrl, password }: SessionSidebarProps): import("react/jsx-runtime").JSX.Element | null;
6
+ export {};
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Box, Text } from "ink";
4
+ import { useApi } from "../hooks/useApi.js";
5
+ export function SessionSidebar({ serverUrl, password }) {
6
+ const { listSessions } = useApi(serverUrl, password);
7
+ const [sessions, setSessions] = useState([]);
8
+ useEffect(() => {
9
+ listSessions().then((s) => setSessions(s));
10
+ }, [listSessions]);
11
+ if (sessions.length === 0)
12
+ return null;
13
+ return (_jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, flexDirection: "column", gap: 0, children: [_jsx(Text, { bold: true, color: "cyan", children: "Sessions" }), sessions.map((session, i) => (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: getNodeColor(i), children: "\u25CF" }), _jsx(Text, { children: session.name })] }, session.id)))] }));
14
+ }
15
+ function getNodeColor(index) {
16
+ const colors = ["green", "blue", "yellow", "magenta", "cyan", "red", "white"];
17
+ return colors[index % colors.length];
18
+ }
@@ -3,8 +3,14 @@ export interface Session {
3
3
  name: string;
4
4
  session_id: string;
5
5
  agent_code: string;
6
+ mode: string | null;
6
7
  created_at: string;
7
8
  }
9
+ export interface AppMode {
10
+ name: string;
11
+ description: string;
12
+ model: string;
13
+ }
8
14
  export interface MessagePart {
9
15
  type: "text";
10
16
  text: string;
@@ -13,9 +19,40 @@ export interface SessionMessage {
13
19
  role: "user" | "assistant";
14
20
  parts: MessagePart[];
15
21
  }
22
+ export interface MailMessage {
23
+ id: number;
24
+ from_name: string;
25
+ to_name: string;
26
+ body: string;
27
+ read: boolean;
28
+ injected: boolean;
29
+ created_at: string;
30
+ }
31
+ export interface Config {
32
+ [key: string]: string;
33
+ }
34
+ export interface CronJob {
35
+ id: number;
36
+ name: string;
37
+ session_name: string;
38
+ schedule: string;
39
+ timezone: string | null;
40
+ message: string;
41
+ enabled: boolean;
42
+ created_at: string;
43
+ last_run: string | null;
44
+ next_run: string | null;
45
+ }
46
+ export interface CronHistoryEntry {
47
+ id: number;
48
+ cron_job_id: number;
49
+ executed_at: string;
50
+ success: boolean;
51
+ error_message: string | null;
52
+ }
16
53
  export declare function useApi(serverUrl: string, password: string): {
17
54
  listSessions: () => Promise<Session[]>;
18
- createSession: (name: string) => Promise<Session>;
55
+ createSession: (name: string, mode?: string) => Promise<Session>;
19
56
  deleteSession: (name: string) => Promise<void>;
20
57
  checkHealth: () => Promise<boolean>;
21
58
  getMessages: (name: string, limit?: number) => Promise<SessionMessage[]>;
@@ -24,6 +61,42 @@ export declare function useApi(serverUrl: string, password: string): {
24
61
  messageID: string;
25
62
  }>;
26
63
  regenerateCode: (name: string) => Promise<Session>;
64
+ getConfig: () => Promise<Config>;
65
+ setConfig: (key: string, value: string) => Promise<{
66
+ ok: boolean;
67
+ key: string;
68
+ value: string;
69
+ }>;
70
+ getMailMessages: (name: string, options?: {
71
+ sent?: boolean;
72
+ unreadOnly?: boolean;
73
+ }) => Promise<MailMessage[]>;
74
+ sendMailMessage: (from: string, to: string[], body: string) => Promise<{
75
+ ok: boolean;
76
+ results: Array<{
77
+ to: string;
78
+ messageId: number;
79
+ injected: boolean;
80
+ }>;
81
+ }>;
82
+ markMessageRead: (id: number) => Promise<MailMessage>;
83
+ getModes: () => Promise<AppMode[]>;
84
+ listCrons: (sessionName?: string) => Promise<CronJob[]>;
85
+ createCron: (data: {
86
+ name: string;
87
+ session_name: string;
88
+ schedule: string;
89
+ message: string;
90
+ timezone?: string;
91
+ }) => Promise<CronJob>;
92
+ deleteCron: (id: number) => Promise<{
93
+ deleted: boolean;
94
+ id: number;
95
+ name: string;
96
+ }>;
97
+ enableCron: (id: number) => Promise<CronJob>;
98
+ disableCron: (id: number) => Promise<CronJob>;
99
+ getCronHistory: (id: number, limit?: number) => Promise<CronHistoryEntry[]>;
27
100
  };
28
101
  export declare function useAsyncState<T>(): {
29
102
  run: (fn: () => Promise<T>) => Promise<T | null>;
@@ -28,12 +28,15 @@ export function useApi(serverUrl, password) {
28
28
  const listSessions = useCallback(async () => {
29
29
  return apiFetch(`${base}/sessions`, password);
30
30
  }, [base, password]);
31
- const createSession = useCallback(async (name) => {
31
+ const createSession = useCallback(async (name, mode) => {
32
32
  return apiFetch(`${base}/sessions`, password, {
33
33
  method: "POST",
34
- body: JSON.stringify({ name }),
34
+ body: JSON.stringify({ name, ...(mode ? { mode } : {}) }),
35
35
  });
36
36
  }, [base, password]);
37
+ const getModes = useCallback(async () => {
38
+ return apiFetch(`${base}/modes`, password);
39
+ }, [base, password]);
37
40
  const deleteSession = useCallback(async (name) => {
38
41
  await apiFetch(`${base}/sessions/${encodeURIComponent(name)}`, password, {
39
42
  method: "DELETE",
@@ -57,7 +60,70 @@ export function useApi(serverUrl, password) {
57
60
  const regenerateCode = useCallback(async (name) => {
58
61
  return apiFetch(`${base}/sessions/${encodeURIComponent(name)}/regenerate-code`, password, { method: "POST" });
59
62
  }, [base, password]);
60
- return { listSessions, createSession, deleteSession, checkHealth, getMessages, injectText, regenerateCode };
63
+ const getConfig = useCallback(async () => {
64
+ return apiFetch(`${base}/config`, password);
65
+ }, [base, password]);
66
+ const setConfig = useCallback(async (key, value) => {
67
+ return apiFetch(`${base}/config`, password, { method: "PUT", body: JSON.stringify({ key, value }) });
68
+ }, [base, password]);
69
+ const getMailMessages = useCallback(async (name, options) => {
70
+ const params = new URLSearchParams();
71
+ if (options?.sent)
72
+ params.set("sent", "true");
73
+ if (options?.unreadOnly)
74
+ params.set("unread_only", "true");
75
+ const query = params.toString() ? `?${params.toString()}` : "";
76
+ return apiFetch(`${base}/messages/${encodeURIComponent(name)}${query}`, password);
77
+ }, [base, password]);
78
+ const sendMailMessage = useCallback(async (from, to, body) => {
79
+ return apiFetch(`${base}/messages`, password, { method: "POST", body: JSON.stringify({ from, to, body }) });
80
+ }, [base, password]);
81
+ const markMessageRead = useCallback(async (id) => {
82
+ return apiFetch(`${base}/messages/${id}/read`, password, { method: "POST" });
83
+ }, [base, password]);
84
+ const listCrons = useCallback(async (sessionName) => {
85
+ const params = sessionName ? `?session_name=${encodeURIComponent(sessionName)}` : "";
86
+ return apiFetch(`${base}/crons${params}`, password);
87
+ }, [base, password]);
88
+ const createCron = useCallback(async (data) => {
89
+ return apiFetch(`${base}/crons`, password, {
90
+ method: "POST",
91
+ body: JSON.stringify(data),
92
+ });
93
+ }, [base, password]);
94
+ const deleteCron = useCallback(async (id) => {
95
+ return apiFetch(`${base}/crons/${id}`, password, { method: "DELETE" });
96
+ }, [base, password]);
97
+ const enableCron = useCallback(async (id) => {
98
+ return apiFetch(`${base}/crons/${id}/enable`, password, { method: "POST" });
99
+ }, [base, password]);
100
+ const disableCron = useCallback(async (id) => {
101
+ return apiFetch(`${base}/crons/${id}/disable`, password, { method: "POST" });
102
+ }, [base, password]);
103
+ const getCronHistory = useCallback(async (id, limit = 10) => {
104
+ return apiFetch(`${base}/crons/${id}/history?limit=${limit}`, password);
105
+ }, [base, password]);
106
+ return {
107
+ listSessions,
108
+ createSession,
109
+ deleteSession,
110
+ checkHealth,
111
+ getMessages,
112
+ injectText,
113
+ regenerateCode,
114
+ getConfig,
115
+ setConfig,
116
+ getMailMessages,
117
+ sendMailMessage,
118
+ markMessageRead,
119
+ getModes,
120
+ listCrons,
121
+ createCron,
122
+ deleteCron,
123
+ enableCron,
124
+ disableCron,
125
+ getCronHistory,
126
+ };
61
127
  }
62
128
  export function useAsyncState() {
63
129
  const [state, setState] = useState({
@@ -0,0 +1,24 @@
1
+ import type { Sql } from "../db/index.js";
2
+ import type { OpencodeClient } from "../lib/opencode.js";
3
+ import type { CronJobRow } from "../db/index.js";
4
+ interface CronSchedulerOptions {
5
+ onJobExecuted?: (jobId: number, success: boolean, error?: string) => void;
6
+ }
7
+ export declare class CronScheduler {
8
+ private options;
9
+ private activeJobs;
10
+ private sql;
11
+ private opencode;
12
+ private started;
13
+ constructor(options?: CronSchedulerOptions);
14
+ start(sql: Sql, opencode: OpencodeClient): Promise<void>;
15
+ stop(): void;
16
+ private addJob;
17
+ private executeJob;
18
+ addCronJob(job: CronJobRow): void;
19
+ removeCronJob(jobId: number): void;
20
+ enableCronJob(job: CronJobRow): void;
21
+ disableCronJob(jobId: number): void;
22
+ isTracking(jobId: number): boolean;
23
+ }
24
+ export {};
@@ -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): 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) {
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
- app.use("/", createWorkerRouter(sql));
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));
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): Router;
5
- export declare function createWorkerRouter(sql: Sql): Router;
4
+ import { CronScheduler } from "./cron.js";
5
+ export declare function createRouter(sql: Sql, opencode: OpencodeClient, serverUrl: string, scheduler: CronScheduler): Router;
6
+ export declare function createWorkerRouter(sql: Sql, opencode: OpencodeClient, serverUrl: string): Router;