agent-office 0.4.5 → 0.4.7
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 +48 -21
- package/dist/cli.js +103 -4
- package/dist/commands/communicator.js +582 -0
- package/dist/commands/serve.d.ts +3 -0
- package/dist/commands/serve.js +30 -3
- package/dist/commands/task-board.d.ts +29 -0
- package/dist/commands/task-board.js +251 -0
- package/dist/commands/worker.d.ts +2 -1
- package/dist/commands/worker.js +7 -3
- package/dist/db/index.d.ts +23 -0
- package/dist/db/postgresql-storage.d.ts +17 -1
- package/dist/db/postgresql-storage.js +175 -0
- package/dist/db/sqlite-storage.d.ts +17 -1
- package/dist/db/sqlite-storage.js +241 -0
- package/dist/db/storage-base.d.ts +17 -1
- package/dist/db/storage.d.ts +17 -1
- package/dist/lib/pi-coding-server.d.ts +20 -0
- package/dist/lib/pi-coding-server.js +162 -0
- package/dist/manage/app.js +6 -1
- package/dist/manage/components/CronRequests.d.ts +8 -0
- package/dist/manage/components/CronRequests.js +181 -0
- package/dist/manage/hooks/useApi.d.ts +22 -0
- package/dist/manage/hooks/useApi.js +18 -0
- package/dist/server/routes.js +184 -35
- package/package.json +3 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
interface TaskBoardOptions {
|
|
2
|
+
databaseUrl?: string;
|
|
3
|
+
sqlite?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function listTasks(options: TaskBoardOptions, cmdOptions: {
|
|
6
|
+
column?: string;
|
|
7
|
+
}): Promise<void>;
|
|
8
|
+
export declare function addTask(options: TaskBoardOptions, title: string, description: string, cmdOptions: {
|
|
9
|
+
assignee?: string;
|
|
10
|
+
column: string;
|
|
11
|
+
dependencies: string;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
export declare function updateTask(options: TaskBoardOptions, idStr: string, cmdOptions: {
|
|
14
|
+
title?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
assignee?: string;
|
|
17
|
+
column?: string;
|
|
18
|
+
dependencies?: string;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
export declare function deleteTask(options: TaskBoardOptions, idStr: string): Promise<void>;
|
|
21
|
+
export declare function moveTask(options: TaskBoardOptions, idStr: string, column: string): Promise<void>;
|
|
22
|
+
export declare function searchTasks(options: TaskBoardOptions, query: string, cmdOptions: {
|
|
23
|
+
assignee?: string;
|
|
24
|
+
column?: string;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
export declare function assignTask(options: TaskBoardOptions, idStr: string, assignee: string): Promise<void>;
|
|
27
|
+
export declare function showTask(options: TaskBoardOptions, idStr: string): Promise<void>;
|
|
28
|
+
export declare function showStats(options: TaskBoardOptions): Promise<void>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { createPostgresqlStorage, createSqliteStorage } from "../db/index.js";
|
|
2
|
+
import { runMigrations } from "../db/migrate.js";
|
|
3
|
+
const VALID_COLUMNS = ["idea", "approved idea", "working on", "blocked", "ready for review", "done"];
|
|
4
|
+
function validateColumn(column) {
|
|
5
|
+
if (!VALID_COLUMNS.includes(column)) {
|
|
6
|
+
console.error(`Error: invalid column "${column}". Valid columns are: ${VALID_COLUMNS.join(", ")}`);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
async function getStorage(options) {
|
|
11
|
+
let storage;
|
|
12
|
+
if (options.sqlite) {
|
|
13
|
+
storage = createSqliteStorage(options.sqlite);
|
|
14
|
+
}
|
|
15
|
+
else if (options.databaseUrl) {
|
|
16
|
+
storage = createPostgresqlStorage(options.databaseUrl);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
console.error("Error: either --database-url or --sqlite is required");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
// Run migrations
|
|
23
|
+
await runMigrations(storage);
|
|
24
|
+
return storage;
|
|
25
|
+
}
|
|
26
|
+
export async function listTasks(options, cmdOptions) {
|
|
27
|
+
const storage = await getStorage(options);
|
|
28
|
+
try {
|
|
29
|
+
const tasks = await storage.listTasks();
|
|
30
|
+
let filtered = tasks;
|
|
31
|
+
if (cmdOptions.column) {
|
|
32
|
+
validateColumn(cmdOptions.column);
|
|
33
|
+
filtered = tasks.filter(t => t.column === cmdOptions.column);
|
|
34
|
+
}
|
|
35
|
+
if (filtered.length === 0) {
|
|
36
|
+
console.log("No tasks found.");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
for (const task of filtered) {
|
|
40
|
+
console.log(`#${task.id} [${task.column}] ${task.title}`);
|
|
41
|
+
if (task.assignee)
|
|
42
|
+
console.log(` Assignee: ${task.assignee}`);
|
|
43
|
+
console.log(` Description: ${task.description}`);
|
|
44
|
+
if (task.dependencies.length > 0) {
|
|
45
|
+
console.log(` Dependencies: ${task.dependencies.join(", ")}`);
|
|
46
|
+
}
|
|
47
|
+
console.log(` Created: ${task.created_at.toISOString()}`);
|
|
48
|
+
console.log();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
await storage.close();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function addTask(options, title, description, cmdOptions) {
|
|
56
|
+
const storage = await getStorage(options);
|
|
57
|
+
try {
|
|
58
|
+
validateColumn(cmdOptions.column);
|
|
59
|
+
const dependencies = cmdOptions.dependencies ? cmdOptions.dependencies.split(",").map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n)) : [];
|
|
60
|
+
const task = await storage.createTask(title, description, cmdOptions.assignee || null, cmdOptions.column, dependencies);
|
|
61
|
+
console.log(`Task #${task.id} created.`);
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
await storage.close();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export async function updateTask(options, idStr, cmdOptions) {
|
|
68
|
+
const storage = await getStorage(options);
|
|
69
|
+
try {
|
|
70
|
+
const id = parseInt(idStr, 10);
|
|
71
|
+
if (isNaN(id)) {
|
|
72
|
+
console.error("Error: invalid task ID");
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
const updates = {};
|
|
76
|
+
if (cmdOptions.title !== undefined)
|
|
77
|
+
updates.title = cmdOptions.title;
|
|
78
|
+
if (cmdOptions.description !== undefined)
|
|
79
|
+
updates.description = cmdOptions.description;
|
|
80
|
+
if (cmdOptions.assignee !== undefined)
|
|
81
|
+
updates.assignee = cmdOptions.assignee;
|
|
82
|
+
if (cmdOptions.column !== undefined) {
|
|
83
|
+
validateColumn(cmdOptions.column);
|
|
84
|
+
updates.column = cmdOptions.column;
|
|
85
|
+
}
|
|
86
|
+
if (cmdOptions.dependencies !== undefined) {
|
|
87
|
+
updates.dependencies = cmdOptions.dependencies.split(",").map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
|
|
88
|
+
}
|
|
89
|
+
const task = await storage.updateTask(id, updates);
|
|
90
|
+
if (!task) {
|
|
91
|
+
console.error("Error: task not found");
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
console.log(`Task #${task.id} updated.`);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
await storage.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export async function deleteTask(options, idStr) {
|
|
101
|
+
const storage = await getStorage(options);
|
|
102
|
+
try {
|
|
103
|
+
const id = parseInt(idStr, 10);
|
|
104
|
+
if (isNaN(id)) {
|
|
105
|
+
console.error("Error: invalid task ID");
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
await storage.deleteTask(id);
|
|
109
|
+
console.log(`Task #${id} deleted.`);
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
await storage.close();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export async function moveTask(options, idStr, column) {
|
|
116
|
+
const storage = await getStorage(options);
|
|
117
|
+
try {
|
|
118
|
+
const id = parseInt(idStr, 10);
|
|
119
|
+
if (isNaN(id)) {
|
|
120
|
+
console.error("Error: invalid task ID");
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
validateColumn(column);
|
|
124
|
+
const task = await storage.updateTask(id, { column });
|
|
125
|
+
if (!task) {
|
|
126
|
+
console.error("Error: task not found");
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
console.log(`Task #${task.id} moved to "${column}".`);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
await storage.close();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export async function searchTasks(options, query, cmdOptions) {
|
|
136
|
+
const storage = await getStorage(options);
|
|
137
|
+
try {
|
|
138
|
+
if (cmdOptions.column) {
|
|
139
|
+
validateColumn(cmdOptions.column);
|
|
140
|
+
}
|
|
141
|
+
const tasks = await storage.searchTasks(query, cmdOptions);
|
|
142
|
+
if (tasks.length === 0) {
|
|
143
|
+
console.log("No tasks found matching the search criteria.");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
console.log(`Found ${tasks.length} task(s) matching "${query}":`);
|
|
147
|
+
console.log();
|
|
148
|
+
for (const task of tasks) {
|
|
149
|
+
console.log(`#${task.id} [${task.column}] ${task.title}`);
|
|
150
|
+
if (task.assignee)
|
|
151
|
+
console.log(` Assignee: ${task.assignee}`);
|
|
152
|
+
console.log(` Description: ${task.description}`);
|
|
153
|
+
if (task.dependencies.length > 0) {
|
|
154
|
+
console.log(` Dependencies: ${task.dependencies.join(", ")}`);
|
|
155
|
+
}
|
|
156
|
+
console.log(` Created: ${task.created_at.toISOString()}`);
|
|
157
|
+
console.log();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
await storage.close();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export async function assignTask(options, idStr, assignee) {
|
|
165
|
+
const storage = await getStorage(options);
|
|
166
|
+
try {
|
|
167
|
+
const id = parseInt(idStr, 10);
|
|
168
|
+
if (isNaN(id)) {
|
|
169
|
+
console.error("Error: invalid task ID");
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
const task = await storage.updateTask(id, { assignee });
|
|
173
|
+
if (!task) {
|
|
174
|
+
console.error("Error: task not found");
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
console.log(`Task #${task.id} assigned to "${assignee}".`);
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
await storage.close();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export async function showTask(options, idStr) {
|
|
184
|
+
const storage = await getStorage(options);
|
|
185
|
+
try {
|
|
186
|
+
const id = parseInt(idStr, 10);
|
|
187
|
+
if (isNaN(id)) {
|
|
188
|
+
console.error("Error: invalid task ID");
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
const task = await storage.getTaskById(id);
|
|
192
|
+
if (!task) {
|
|
193
|
+
console.error("Error: task not found");
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
console.log(`Task #${task.id}`);
|
|
197
|
+
console.log(`Title: ${task.title}`);
|
|
198
|
+
console.log(`Description: ${task.description}`);
|
|
199
|
+
console.log(`Column: ${task.column}`);
|
|
200
|
+
console.log(`Assignee: ${task.assignee || "Unassigned"}`);
|
|
201
|
+
console.log(`Dependencies: ${task.dependencies.length > 0 ? task.dependencies.join(", ") : "None"}`);
|
|
202
|
+
console.log(`Created: ${task.created_at.toISOString()}`);
|
|
203
|
+
console.log(`Updated: ${task.updated_at.toISOString()}`);
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
await storage.close();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export async function showStats(options) {
|
|
210
|
+
const storage = await getStorage(options);
|
|
211
|
+
try {
|
|
212
|
+
const tasks = await storage.listTasks();
|
|
213
|
+
const stats = {
|
|
214
|
+
total: tasks.length,
|
|
215
|
+
byColumn: {},
|
|
216
|
+
byAssignee: {},
|
|
217
|
+
unassigned: 0,
|
|
218
|
+
};
|
|
219
|
+
for (const task of tasks) {
|
|
220
|
+
// By column
|
|
221
|
+
stats.byColumn[task.column] = (stats.byColumn[task.column] || 0) + 1;
|
|
222
|
+
// By assignee
|
|
223
|
+
if (task.assignee) {
|
|
224
|
+
stats.byAssignee[task.assignee] = (stats.byAssignee[task.assignee] || 0) + 1;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
stats.unassigned++;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
console.log("Task Board Statistics");
|
|
231
|
+
console.log("====================");
|
|
232
|
+
console.log(`Total tasks: ${stats.total}`);
|
|
233
|
+
console.log();
|
|
234
|
+
console.log("Tasks by column:");
|
|
235
|
+
for (const column of VALID_COLUMNS) {
|
|
236
|
+
const count = stats.byColumn[column] || 0;
|
|
237
|
+
console.log(` ${column}: ${count}`);
|
|
238
|
+
}
|
|
239
|
+
console.log();
|
|
240
|
+
console.log("Tasks by assignee:");
|
|
241
|
+
if (stats.unassigned > 0) {
|
|
242
|
+
console.log(` Unassigned: ${stats.unassigned}`);
|
|
243
|
+
}
|
|
244
|
+
for (const [assignee, count] of Object.entries(stats.byAssignee)) {
|
|
245
|
+
console.log(` ${assignee}: ${count}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
await storage.close();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -2,13 +2,14 @@ export declare function listCoworkers(token: string): Promise<void>;
|
|
|
2
2
|
export declare function setStatus(token: string, status: string | null): Promise<void>;
|
|
3
3
|
export declare function sendMessage(token: string, recipients: string[], body: string): Promise<void>;
|
|
4
4
|
export declare function listCrons(token: string): Promise<void>;
|
|
5
|
-
export declare function
|
|
5
|
+
export declare function requestCron(token: string, options: {
|
|
6
6
|
name: string;
|
|
7
7
|
schedule: string;
|
|
8
8
|
message: string;
|
|
9
9
|
respondTo: string;
|
|
10
10
|
timezone?: string;
|
|
11
11
|
}): Promise<void>;
|
|
12
|
+
export declare function listCronRequests(token: string): Promise<void>;
|
|
12
13
|
export declare function deleteCron(token: string, cronId: number): Promise<void>;
|
|
13
14
|
export declare function enableCron(token: string, cronId: number): Promise<void>;
|
|
14
15
|
export declare function disableCron(token: string, cronId: number): Promise<void>;
|
package/dist/commands/worker.js
CHANGED
|
@@ -95,10 +95,14 @@ export async function listCrons(token) {
|
|
|
95
95
|
const crons = await fetchWorker(token, "/worker/crons");
|
|
96
96
|
console.log(JSON.stringify(crons, null, 2));
|
|
97
97
|
}
|
|
98
|
-
export async function
|
|
98
|
+
export async function requestCron(token, options) {
|
|
99
99
|
const finalMessage = `Action: ${options.message}\n\nWho to respond to when done: ${options.respondTo}`;
|
|
100
|
-
const
|
|
101
|
-
console.log(JSON.stringify(
|
|
100
|
+
const request = await postWorker(token, "/worker/cron-requests", { name: options.name, schedule: options.schedule, message: finalMessage, timezone: options.timezone });
|
|
101
|
+
console.log(JSON.stringify(request, null, 2));
|
|
102
|
+
}
|
|
103
|
+
export async function listCronRequests(token) {
|
|
104
|
+
const requests = await fetchWorker(token, "/worker/cron-requests");
|
|
105
|
+
console.log(JSON.stringify(requests, null, 2));
|
|
102
106
|
}
|
|
103
107
|
export async function deleteCron(token, cronId) {
|
|
104
108
|
const { agentCode, serverUrl } = parseToken(token);
|
package/dist/db/index.d.ts
CHANGED
|
@@ -45,3 +45,26 @@ export interface CronHistoryRow {
|
|
|
45
45
|
success: boolean;
|
|
46
46
|
error_message: string | null;
|
|
47
47
|
}
|
|
48
|
+
export interface CronRequestRow {
|
|
49
|
+
id: number;
|
|
50
|
+
name: string;
|
|
51
|
+
session_name: string;
|
|
52
|
+
schedule: string;
|
|
53
|
+
timezone: string | null;
|
|
54
|
+
message: string;
|
|
55
|
+
status: "pending" | "approved" | "rejected";
|
|
56
|
+
requested_at: Date;
|
|
57
|
+
reviewed_at: Date | null;
|
|
58
|
+
reviewed_by: string | null;
|
|
59
|
+
reviewer_notes: string | null;
|
|
60
|
+
}
|
|
61
|
+
export interface TaskRow {
|
|
62
|
+
id: number;
|
|
63
|
+
title: string;
|
|
64
|
+
description: string;
|
|
65
|
+
assignee: string | null;
|
|
66
|
+
column: string;
|
|
67
|
+
dependencies: number[];
|
|
68
|
+
created_at: Date;
|
|
69
|
+
updated_at: Date;
|
|
70
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentOfficeStorageBase } from "./storage-base.js";
|
|
2
|
-
import type { SessionRow, ConfigRow, MessageRow, CronJobRow, CronHistoryRow, Sql } from "./index.js";
|
|
2
|
+
import type { SessionRow, ConfigRow, MessageRow, CronJobRow, CronHistoryRow, CronRequestRow, TaskRow, Sql } from "./index.js";
|
|
3
3
|
export declare class AgentOfficePostgresqlStorage extends AgentOfficeStorageBase {
|
|
4
4
|
private sql;
|
|
5
5
|
constructor(sql: Sql);
|
|
@@ -43,6 +43,22 @@ export declare class AgentOfficePostgresqlStorage extends AgentOfficeStorageBase
|
|
|
43
43
|
cronJobExistsForSession(name: string, sessionName: string): Promise<boolean>;
|
|
44
44
|
listCronHistory(cronJobId: number, limit: number): Promise<CronHistoryRow[]>;
|
|
45
45
|
createCronHistory(cronJobId: number, executedAt: Date, success: boolean, errorMessage?: string): Promise<void>;
|
|
46
|
+
listCronRequests(filters?: {
|
|
47
|
+
status?: string;
|
|
48
|
+
sessionName?: string;
|
|
49
|
+
}): Promise<CronRequestRow[]>;
|
|
50
|
+
getCronRequestById(id: number): Promise<CronRequestRow | null>;
|
|
51
|
+
createCronRequest(name: string, sessionName: string, schedule: string, timezone: string | null, message: string): Promise<CronRequestRow>;
|
|
52
|
+
updateCronRequestStatus(id: number, status: "approved" | "rejected", reviewedBy: string, reviewerNotes?: string): Promise<CronRequestRow | null>;
|
|
53
|
+
listTasks(): Promise<TaskRow[]>;
|
|
54
|
+
getTaskById(id: number): Promise<TaskRow | null>;
|
|
55
|
+
createTask(title: string, description: string, assignee: string | null, column: string, dependencies: number[]): Promise<TaskRow>;
|
|
56
|
+
updateTask(id: number, updates: Partial<Pick<TaskRow, 'title' | 'description' | 'assignee' | 'column' | 'dependencies'>>): Promise<TaskRow | null>;
|
|
57
|
+
deleteTask(id: number): Promise<void>;
|
|
58
|
+
searchTasks(query: string, filters?: {
|
|
59
|
+
assignee?: string;
|
|
60
|
+
column?: string;
|
|
61
|
+
}): Promise<TaskRow[]>;
|
|
46
62
|
runMigrations(): Promise<void>;
|
|
47
63
|
}
|
|
48
64
|
export declare function createPostgresqlStorage(databaseUrl: string): AgentOfficePostgresqlStorage;
|
|
@@ -260,6 +260,142 @@ export class AgentOfficePostgresqlStorage extends AgentOfficeStorageBase {
|
|
|
260
260
|
`;
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
|
+
// Cron Requests
|
|
264
|
+
async listCronRequests(filters) {
|
|
265
|
+
if (filters?.status && filters?.sessionName) {
|
|
266
|
+
return this.sql `
|
|
267
|
+
SELECT id, name, session_name, schedule, timezone, message, status, requested_at, reviewed_at, reviewed_by, reviewer_notes
|
|
268
|
+
FROM cron_requests
|
|
269
|
+
WHERE status = ${filters.status} AND session_name = ${filters.sessionName}
|
|
270
|
+
ORDER BY requested_at DESC
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
273
|
+
else if (filters?.status) {
|
|
274
|
+
return this.sql `
|
|
275
|
+
SELECT id, name, session_name, schedule, timezone, message, status, requested_at, reviewed_at, reviewed_by, reviewer_notes
|
|
276
|
+
FROM cron_requests
|
|
277
|
+
WHERE status = ${filters.status}
|
|
278
|
+
ORDER BY requested_at DESC
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
else if (filters?.sessionName) {
|
|
282
|
+
return this.sql `
|
|
283
|
+
SELECT id, name, session_name, schedule, timezone, message, status, requested_at, reviewed_at, reviewed_by, reviewer_notes
|
|
284
|
+
FROM cron_requests
|
|
285
|
+
WHERE session_name = ${filters.sessionName}
|
|
286
|
+
ORDER BY requested_at DESC
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
return this.sql `
|
|
291
|
+
SELECT id, name, session_name, schedule, timezone, message, status, requested_at, reviewed_at, reviewed_by, reviewer_notes
|
|
292
|
+
FROM cron_requests
|
|
293
|
+
ORDER BY requested_at DESC
|
|
294
|
+
`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async getCronRequestById(id) {
|
|
298
|
+
const [row] = await this.sql `
|
|
299
|
+
SELECT id, name, session_name, schedule, timezone, message, status, requested_at, reviewed_at, reviewed_by, reviewer_notes
|
|
300
|
+
FROM cron_requests WHERE id = ${id}
|
|
301
|
+
`;
|
|
302
|
+
return row ?? null;
|
|
303
|
+
}
|
|
304
|
+
async createCronRequest(name, sessionName, schedule, timezone, message) {
|
|
305
|
+
const [row] = await this.sql `
|
|
306
|
+
INSERT INTO cron_requests (name, session_name, schedule, timezone, message)
|
|
307
|
+
VALUES (${name}, ${sessionName}, ${schedule}, ${timezone}, ${message})
|
|
308
|
+
RETURNING id, name, session_name, schedule, timezone, message, status, requested_at, reviewed_at, reviewed_by, reviewer_notes
|
|
309
|
+
`;
|
|
310
|
+
return row;
|
|
311
|
+
}
|
|
312
|
+
async updateCronRequestStatus(id, status, reviewedBy, reviewerNotes) {
|
|
313
|
+
const [row] = await this.sql `
|
|
314
|
+
UPDATE cron_requests
|
|
315
|
+
SET status = ${status}, reviewed_at = NOW(), reviewed_by = ${reviewedBy}, reviewer_notes = ${reviewerNotes ?? null}
|
|
316
|
+
WHERE id = ${id}
|
|
317
|
+
RETURNING id, name, session_name, schedule, timezone, message, status, requested_at, reviewed_at, reviewed_by, reviewer_notes
|
|
318
|
+
`;
|
|
319
|
+
return row ?? null;
|
|
320
|
+
}
|
|
321
|
+
// Tasks
|
|
322
|
+
async listTasks() {
|
|
323
|
+
return this.sql `
|
|
324
|
+
SELECT id, title, description, assignee, column_name as column, dependencies, created_at, updated_at
|
|
325
|
+
FROM tasks
|
|
326
|
+
ORDER BY created_at DESC
|
|
327
|
+
`;
|
|
328
|
+
}
|
|
329
|
+
async getTaskById(id) {
|
|
330
|
+
const [row] = await this.sql `
|
|
331
|
+
SELECT id, title, description, assignee, column_name as column, dependencies, created_at, updated_at
|
|
332
|
+
FROM tasks WHERE id = ${id}
|
|
333
|
+
`;
|
|
334
|
+
return row ?? null;
|
|
335
|
+
}
|
|
336
|
+
async createTask(title, description, assignee, column, dependencies) {
|
|
337
|
+
const [row] = await this.sql `
|
|
338
|
+
INSERT INTO tasks (title, description, assignee, column_name, dependencies)
|
|
339
|
+
VALUES (${title}, ${description}, ${assignee}, ${column}, ${JSON.stringify(dependencies)}::jsonb)
|
|
340
|
+
RETURNING id, title, description, assignee, column_name as column, dependencies, created_at, updated_at
|
|
341
|
+
`;
|
|
342
|
+
return row;
|
|
343
|
+
}
|
|
344
|
+
async updateTask(id, updates) {
|
|
345
|
+
const setParts = [];
|
|
346
|
+
const values = [];
|
|
347
|
+
if (updates.title !== undefined) {
|
|
348
|
+
setParts.push('title = ?');
|
|
349
|
+
values.push(updates.title);
|
|
350
|
+
}
|
|
351
|
+
if (updates.description !== undefined) {
|
|
352
|
+
setParts.push('description = ?');
|
|
353
|
+
values.push(updates.description);
|
|
354
|
+
}
|
|
355
|
+
if (updates.assignee !== undefined) {
|
|
356
|
+
setParts.push('assignee = ?');
|
|
357
|
+
values.push(updates.assignee);
|
|
358
|
+
}
|
|
359
|
+
if (updates.column !== undefined) {
|
|
360
|
+
setParts.push('column_name = ?');
|
|
361
|
+
values.push(updates.column);
|
|
362
|
+
}
|
|
363
|
+
if (updates.dependencies !== undefined) {
|
|
364
|
+
setParts.push('dependencies = ?');
|
|
365
|
+
values.push(JSON.stringify(updates.dependencies));
|
|
366
|
+
}
|
|
367
|
+
if (setParts.length === 0)
|
|
368
|
+
return this.getTaskById(id);
|
|
369
|
+
setParts.push('updated_at = NOW()');
|
|
370
|
+
const sql = `UPDATE tasks SET ${setParts.join(', ')} WHERE id = ? RETURNING id, title, description, assignee, column_name as column, dependencies, created_at, updated_at`;
|
|
371
|
+
values.push(id);
|
|
372
|
+
const [row] = await this.sql.unsafe(sql, values);
|
|
373
|
+
return row ?? null;
|
|
374
|
+
}
|
|
375
|
+
async deleteTask(id) {
|
|
376
|
+
await this.sql `DELETE FROM tasks WHERE id = ${id}`;
|
|
377
|
+
}
|
|
378
|
+
async searchTasks(query, filters) {
|
|
379
|
+
const conditions = [];
|
|
380
|
+
const params = [];
|
|
381
|
+
conditions.push(`(title ILIKE $${params.length + 1} OR description ILIKE $${params.length + 1})`);
|
|
382
|
+
params.push(`${query}%`);
|
|
383
|
+
if (filters?.assignee) {
|
|
384
|
+
conditions.push(`assignee = $${params.length + 1}`);
|
|
385
|
+
params.push(filters.assignee);
|
|
386
|
+
}
|
|
387
|
+
if (filters?.column) {
|
|
388
|
+
conditions.push(`column_name = $${params.length + 1}`);
|
|
389
|
+
params.push(filters.column);
|
|
390
|
+
}
|
|
391
|
+
const sql = `
|
|
392
|
+
SELECT id, title, description, assignee, column_name as column, dependencies, created_at, updated_at
|
|
393
|
+
FROM tasks
|
|
394
|
+
WHERE ${conditions.join(' AND ')}
|
|
395
|
+
ORDER BY created_at DESC
|
|
396
|
+
`;
|
|
397
|
+
return this.sql.unsafe(sql, params);
|
|
398
|
+
}
|
|
263
399
|
async runMigrations() {
|
|
264
400
|
const MIGRATIONS = [
|
|
265
401
|
{
|
|
@@ -368,6 +504,45 @@ export class AgentOfficePostgresqlStorage extends AgentOfficeStorageBase {
|
|
|
368
504
|
name: "add_notified_to_messages",
|
|
369
505
|
sql: `
|
|
370
506
|
ALTER TABLE messages ADD COLUMN IF NOT EXISTS notified BOOLEAN NOT NULL DEFAULT FALSE;
|
|
507
|
+
`,
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
version: 10,
|
|
511
|
+
name: "create_tasks_table",
|
|
512
|
+
sql: `
|
|
513
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
514
|
+
id SERIAL PRIMARY KEY,
|
|
515
|
+
title TEXT NOT NULL,
|
|
516
|
+
description TEXT NOT NULL,
|
|
517
|
+
assignee TEXT,
|
|
518
|
+
column_name TEXT NOT NULL,
|
|
519
|
+
dependencies JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
520
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
521
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
522
|
+
);
|
|
523
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee ON tasks(assignee);
|
|
524
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_column ON tasks(column_name);
|
|
525
|
+
`,
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
version: 11,
|
|
529
|
+
name: "create_cron_requests_table",
|
|
530
|
+
sql: `
|
|
531
|
+
CREATE TABLE IF NOT EXISTS cron_requests (
|
|
532
|
+
id SERIAL PRIMARY KEY,
|
|
533
|
+
name VARCHAR(255) NOT NULL,
|
|
534
|
+
session_name VARCHAR(255) NOT NULL REFERENCES sessions(name) ON DELETE CASCADE,
|
|
535
|
+
schedule TEXT NOT NULL,
|
|
536
|
+
timezone VARCHAR(100),
|
|
537
|
+
message TEXT NOT NULL,
|
|
538
|
+
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
539
|
+
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
540
|
+
reviewed_at TIMESTAMPTZ,
|
|
541
|
+
reviewed_by TEXT,
|
|
542
|
+
reviewer_notes TEXT
|
|
543
|
+
);
|
|
544
|
+
CREATE INDEX IF NOT EXISTS idx_cron_requests_session_name ON cron_requests(session_name);
|
|
545
|
+
CREATE INDEX IF NOT EXISTS idx_cron_requests_status ON cron_requests(status);
|
|
371
546
|
`,
|
|
372
547
|
},
|
|
373
548
|
];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
2
|
import { AgentOfficeStorageBase } from "./storage-base.js";
|
|
3
|
-
import type { SessionRow, ConfigRow, MessageRow, CronJobRow, CronHistoryRow } from "./index.js";
|
|
3
|
+
import type { SessionRow, ConfigRow, MessageRow, CronJobRow, CronHistoryRow, CronRequestRow, TaskRow } from "./index.js";
|
|
4
4
|
export declare class AgentOfficeSqliteStorage extends AgentOfficeStorageBase {
|
|
5
5
|
private db;
|
|
6
6
|
constructor(db: Database.Database);
|
|
@@ -44,6 +44,22 @@ export declare class AgentOfficeSqliteStorage extends AgentOfficeStorageBase {
|
|
|
44
44
|
cronJobExistsForSession(name: string, sessionName: string): Promise<boolean>;
|
|
45
45
|
listCronHistory(cronJobId: number, limit: number): Promise<CronHistoryRow[]>;
|
|
46
46
|
createCronHistory(cronJobId: number, executedAt: Date, success: boolean, errorMessage?: string): Promise<void>;
|
|
47
|
+
listCronRequests(filters?: {
|
|
48
|
+
status?: string;
|
|
49
|
+
sessionName?: string;
|
|
50
|
+
}): Promise<CronRequestRow[]>;
|
|
51
|
+
getCronRequestById(id: number): Promise<CronRequestRow | null>;
|
|
52
|
+
createCronRequest(name: string, sessionName: string, schedule: string, timezone: string | null, message: string): Promise<CronRequestRow>;
|
|
53
|
+
updateCronRequestStatus(id: number, status: "approved" | "rejected", reviewedBy: string, reviewerNotes?: string): Promise<CronRequestRow | null>;
|
|
54
|
+
listTasks(): Promise<TaskRow[]>;
|
|
55
|
+
getTaskById(id: number): Promise<TaskRow | null>;
|
|
56
|
+
createTask(title: string, description: string, assignee: string | null, column: string, dependencies: number[]): Promise<TaskRow>;
|
|
57
|
+
updateTask(id: number, updates: Partial<Pick<TaskRow, 'title' | 'description' | 'assignee' | 'column' | 'dependencies'>>): Promise<TaskRow | null>;
|
|
58
|
+
deleteTask(id: number): Promise<void>;
|
|
59
|
+
searchTasks(query: string, filters?: {
|
|
60
|
+
assignee?: string;
|
|
61
|
+
column?: string;
|
|
62
|
+
}): Promise<TaskRow[]>;
|
|
47
63
|
runMigrations(): Promise<void>;
|
|
48
64
|
}
|
|
49
65
|
export declare function createSqliteStorage(databasePath: string): AgentOfficeSqliteStorage;
|