@fickydev/pigent 0.1.9 → 0.1.10

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,129 @@
1
+ import { and, desc, eq, gte } from "drizzle-orm";
2
+ import { nanoid } from "nanoid";
3
+ import type { DbClient } from "../client";
4
+ import { taskRuns, type TaskRunRow } from "../schema";
5
+
6
+ export type TaskRunStatus = "pending" | "running" | "noop" | "notified" | "failed";
7
+
8
+ export type CreateTaskRunInput = {
9
+ agentId: string;
10
+ taskId: string;
11
+ prompt: string;
12
+ sessionId?: string | null;
13
+ status?: Extract<TaskRunStatus, "pending" | "running">;
14
+ startedAt?: number | null;
15
+ };
16
+
17
+ export type FinishTaskRunInput = {
18
+ result?: string | null;
19
+ error?: string | null;
20
+ finishedAt?: number;
21
+ };
22
+
23
+ export class TaskRepository {
24
+ constructor(private readonly db: DbClient) {}
25
+
26
+ async create(input: CreateTaskRunInput): Promise<TaskRunRow> {
27
+ const id = nanoid();
28
+ const now = Date.now();
29
+
30
+ await this.db.insert(taskRuns).values({
31
+ id,
32
+ agentId: input.agentId,
33
+ taskId: input.taskId,
34
+ prompt: input.prompt,
35
+ sessionId: input.sessionId ?? null,
36
+ status: input.status ?? "pending",
37
+ result: null,
38
+ error: null,
39
+ startedAt: input.startedAt ?? null,
40
+ finishedAt: null,
41
+ createdAt: now,
42
+ });
43
+
44
+ const row = await this.findById(id);
45
+ if (!row) throw new Error(`failed to create task run ${id}`);
46
+
47
+ return row;
48
+ }
49
+
50
+ async markRunning(id: string, startedAt = Date.now()): Promise<TaskRunRow> {
51
+ await this.db
52
+ .update(taskRuns)
53
+ .set({ status: "running", startedAt })
54
+ .where(eq(taskRuns.id, id));
55
+
56
+ return this.requireById(id);
57
+ }
58
+
59
+ async markNoop(id: string, input: FinishTaskRunInput = {}): Promise<TaskRunRow> {
60
+ return this.finish(id, "noop", input);
61
+ }
62
+
63
+ async markNotified(id: string, input: FinishTaskRunInput = {}): Promise<TaskRunRow> {
64
+ return this.finish(id, "notified", input);
65
+ }
66
+
67
+ async markFailed(id: string, input: FinishTaskRunInput): Promise<TaskRunRow> {
68
+ return this.finish(id, "failed", input);
69
+ }
70
+
71
+ async findById(id: string): Promise<TaskRunRow | null> {
72
+ const row = await this.db.query.taskRuns.findFirst({
73
+ where: eq(taskRuns.id, id),
74
+ });
75
+
76
+ return row ?? null;
77
+ }
78
+
79
+ async findLatest(agentId: string, taskId: string): Promise<TaskRunRow | null> {
80
+ const row = await this.db.query.taskRuns.findFirst({
81
+ where: and(eq(taskRuns.agentId, agentId), eq(taskRuns.taskId, taskId)),
82
+ orderBy: desc(taskRuns.createdAt),
83
+ });
84
+
85
+ return row ?? null;
86
+ }
87
+
88
+ async countRecentNotified(agentId: string, taskId: string, since: number): Promise<number> {
89
+ const conditions: any[] = [
90
+ eq(taskRuns.agentId, agentId),
91
+ eq(taskRuns.taskId, taskId),
92
+ eq(taskRuns.status, "notified"),
93
+ ];
94
+
95
+ const rows = await this.db.query.taskRuns.findMany({
96
+ where: and(gte(taskRuns.finishedAt, since), ...conditions),
97
+ columns: { id: true },
98
+ });
99
+
100
+ return rows.length;
101
+ }
102
+
103
+ private async finish(
104
+ id: string,
105
+ status: Extract<TaskRunStatus, "noop" | "notified" | "failed">,
106
+ input: FinishTaskRunInput,
107
+ ): Promise<TaskRunRow> {
108
+ await this.db
109
+ .update(taskRuns)
110
+ .set({
111
+ status,
112
+ result: input.result ?? null,
113
+ error: input.error ?? null,
114
+ finishedAt: input.finishedAt ?? Date.now(),
115
+ })
116
+ .where(eq(taskRuns.id, id));
117
+
118
+ return this.requireById(id);
119
+ }
120
+
121
+ private async requireById(id: string): Promise<TaskRunRow> {
122
+ const row = await this.findById(id);
123
+ if (!row) throw new Error(`task run not found ${id}`);
124
+
125
+ return row;
126
+ }
127
+ }
128
+
129
+
@@ -4,6 +4,8 @@ import { HeartbeatRepository } from "./HeartbeatRepository";
4
4
  import { MessageRepository } from "./MessageRepository";
5
5
  import { RuntimeKvRepository } from "./RuntimeKvRepository";
6
6
  import { SessionRepository } from "./SessionRepository";
7
+ import { TaskConfigRepository } from "./TaskConfigRepository";
8
+ import { TaskRepository } from "./TaskRepository";
7
9
  import { TelegramRepository } from "./TelegramRepository";
8
10
 
9
11
  export type Repositories = ReturnType<typeof createRepositories>;
@@ -15,6 +17,8 @@ export function createRepositories(dbClient = db) {
15
17
  messages: new MessageRepository(dbClient),
16
18
  runtimeKv: new RuntimeKvRepository(dbClient),
17
19
  sessions: new SessionRepository(dbClient),
20
+ taskConfigs: new TaskConfigRepository(dbClient),
21
+ taskRuns: new TaskRepository(dbClient),
18
22
  telegram: new TelegramRepository(dbClient),
19
23
  };
20
24
  }
package/src/db/schema.ts CHANGED
@@ -90,6 +90,32 @@ export const heartbeats = sqliteTable("heartbeats", {
90
90
  createdAt: integer("created_at").notNull(),
91
91
  });
92
92
 
93
+ export const taskRuns = sqliteTable("task_runs", {
94
+ id: text("id").primaryKey(),
95
+ agentId: text("agent_id").notNull(),
96
+ taskId: text("task_id").notNull(),
97
+ prompt: text("prompt").notNull(),
98
+ sessionId: text("session_id"),
99
+ status: text("status", { enum: ["pending", "running", "noop", "notified", "failed"] }).notNull(),
100
+ result: text("result"),
101
+ error: text("error"),
102
+ startedAt: integer("started_at"),
103
+ finishedAt: integer("finished_at"),
104
+ createdAt: integer("created_at").notNull(),
105
+ });
106
+
107
+ export const taskConfigs = sqliteTable("task_configs", {
108
+ id: text("id").primaryKey(),
109
+ agent: text("agent").notNull(),
110
+ intervalMs: integer("interval_ms").notNull(),
111
+ prompt: text("prompt").notNull(),
112
+ channel: text("channel").notNull().default("telegram"),
113
+ chatId: text("chat_id").notNull(),
114
+ enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
115
+ createdAt: integer("created_at").notNull(),
116
+ updatedAt: integer("updated_at").notNull(),
117
+ });
118
+
93
119
  export const runtimeKv = sqliteTable("runtime_kv", {
94
120
  key: text("key").primaryKey(),
95
121
  value: text("value").notNull(),
@@ -106,3 +132,7 @@ export type MessageRow = typeof messages.$inferSelect;
106
132
  export type NewMessageRow = typeof messages.$inferInsert;
107
133
  export type HeartbeatRow = typeof heartbeats.$inferSelect;
108
134
  export type NewHeartbeatRow = typeof heartbeats.$inferInsert;
135
+ export type TaskRunRow = typeof taskRuns.$inferSelect;
136
+ export type NewTaskRunRow = typeof taskRuns.$inferInsert;
137
+ export type TaskConfigRow = typeof taskConfigs.$inferSelect;
138
+ export type NewTaskConfigRow = typeof taskConfigs.$inferInsert;