@aigne/afs-persona 1.11.0-beta.11 → 1.11.0-beta.13

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/db.cjs CHANGED
@@ -43,7 +43,21 @@ const SCHEMA_STATEMENTS = [
43
43
  assignments TEXT,
44
44
  status TEXT NOT NULL DEFAULT 'active',
45
45
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
46
- )`
46
+ )`,
47
+ `CREATE TABLE IF NOT EXISTS messages (
48
+ id INTEGER PRIMARY KEY,
49
+ conversation_id TEXT NOT NULL,
50
+ bot_name TEXT NOT NULL,
51
+ message_id TEXT NOT NULL,
52
+ sender_id TEXT NOT NULL,
53
+ sender_name TEXT NOT NULL,
54
+ sender_is_bot BOOLEAN DEFAULT 0,
55
+ text TEXT,
56
+ event_path TEXT,
57
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
58
+ )`,
59
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_dedup ON messages(conversation_id, message_id)`,
60
+ `CREATE INDEX IF NOT EXISTS idx_messages_conv ON messages(conversation_id, created_at)`
47
61
  ];
48
62
  var PersonaDB = class PersonaDB {
49
63
  db;
@@ -176,6 +190,34 @@ var PersonaDB = class PersonaDB {
176
190
  await this.ensureInitialized();
177
191
  return (await this.db.all(_aigne_sqlite.sql`SELECT * FROM tasks WHERE id = ${id}`).execute())[0] ?? null;
178
192
  }
193
+ async insertMessage(params) {
194
+ await this.ensureInitialized();
195
+ const senderIsBot = params.sender_is_bot ? 1 : 0;
196
+ await this.db.run(_aigne_sqlite.sql`INSERT OR IGNORE INTO messages (conversation_id, bot_name, message_id, sender_id, sender_name, sender_is_bot, text, event_path)
197
+ VALUES (${params.conversation_id}, ${params.bot_name}, ${params.message_id}, ${params.sender_id}, ${params.sender_name}, ${senderIsBot}, ${params.text ?? null}, ${params.event_path ?? null})`).execute();
198
+ return (await this.db.all(_aigne_sqlite.sql.raw("SELECT last_insert_rowid() as id")).execute())[0]?.id ?? 0;
199
+ }
200
+ async getRecentMessages(conversationId, limit = 20) {
201
+ await this.ensureInitialized();
202
+ return this.db.all(_aigne_sqlite.sql`SELECT * FROM messages WHERE conversation_id = ${conversationId} ORDER BY created_at ASC, id ASC LIMIT ${limit}`).execute();
203
+ }
204
+ async getTodayReplyCount(character) {
205
+ await this.ensureInitialized();
206
+ return (await this.db.all(_aigne_sqlite.sql`SELECT COUNT(*) as cnt FROM interactions
207
+ WHERE character = ${character}
208
+ AND type = 'messaging_reply'
209
+ AND created_at >= date('now')`).execute())[0]?.cnt ?? 0;
210
+ }
211
+ async getConsecutiveBotCount(conversationId) {
212
+ await this.ensureInitialized();
213
+ const rows = await this.db.all(_aigne_sqlite.sql`SELECT sender_is_bot FROM messages
214
+ WHERE conversation_id = ${conversationId}
215
+ ORDER BY created_at DESC, id DESC`).execute();
216
+ let count = 0;
217
+ for (const row of rows) if (row.sender_is_bot) count++;
218
+ else break;
219
+ return count;
220
+ }
179
221
  static parseJSON(value) {
180
222
  if (value === null) return null;
181
223
  try {
package/dist/db.d.cts CHANGED
@@ -38,6 +38,18 @@ interface TaskRow {
38
38
  status: string;
39
39
  created_at: string;
40
40
  }
41
+ interface MessageRow {
42
+ id: number;
43
+ conversation_id: string;
44
+ bot_name: string;
45
+ message_id: string;
46
+ sender_id: string;
47
+ sender_name: string;
48
+ sender_is_bot: number;
49
+ text: string | null;
50
+ event_path: string | null;
51
+ created_at: string;
52
+ }
41
53
  declare class PersonaDB {
42
54
  private db;
43
55
  private initialized;
@@ -95,8 +107,21 @@ declare class PersonaDB {
95
107
  assignments?: Record<string, string> | null;
96
108
  }): Promise<void>;
97
109
  getTask(id: string): Promise<TaskRow | null>;
110
+ insertMessage(params: {
111
+ conversation_id: string;
112
+ bot_name: string;
113
+ message_id: string;
114
+ sender_id: string;
115
+ sender_name: string;
116
+ sender_is_bot: boolean;
117
+ text?: string | null;
118
+ event_path?: string | null;
119
+ }): Promise<number>;
120
+ getRecentMessages(conversationId: string, limit?: number): Promise<MessageRow[]>;
121
+ getTodayReplyCount(character: string): Promise<number>;
122
+ getConsecutiveBotCount(conversationId: string): Promise<number>;
98
123
  static parseJSON<T>(value: string | null): T | null;
99
124
  }
100
125
  //#endregion
101
- export { InteractionRow, PersonaDB, SeededTopicRow, SessionRow, TaskRow };
126
+ export { InteractionRow, MessageRow, PersonaDB, SeededTopicRow, SessionRow, TaskRow };
102
127
  //# sourceMappingURL=db.d.cts.map
package/dist/db.d.cts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.cts","names":[],"sources":["../src/db.ts"],"mappings":";UAgDiB,cAAA;EACf,EAAA;EACA,SAAA;EACA,QAAA;EACA,IAAA;EACA,OAAA;EACA,SAAA;EACA,WAAA;EACA,QAAA;EACA,UAAA;AAAA;AAAA,UAGe,UAAA;EACf,EAAA;EACA,SAAA;EACA,MAAA;EACA,aAAA;EACA,UAAA;EACA,QAAA;AAAA;AAAA,UAGe,cAAA;EACf,EAAA;EACA,SAAA;EACA,OAAA;EACA,KAAA;EACA,KAAA;EACA,MAAA;EACA,SAAA;EACA,UAAA;AAAA;AAAA,UAGe,OAAA;EACf,EAAA;EACA,KAAA;EACA,IAAA;EACA,QAAA;EACA,MAAA;EACA,WAAA;EACA,MAAA;EACA,UAAA;AAAA;AAAA,cAGW,SAAA;EAAA,QACH,EAAA;EAAA,QACA,WAAA;EAAA,QAED,WAAA,CAAA;EAAA,OAIM,MAAA,CAAO,MAAA,WAAiB,OAAA,CAAQ,SAAA;EAWvC,iBAAA,CAAA,GAAqB,OAAA;EAQ3B,KAAA,CAAA;EArCA;;;EA4CM,OAAA,CAAQ,KAAA,WAAgB,OAAA;EAxC9B;;;EA+CM,QAAA,GAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,CAAA;EAMpC,iBAAA,CAAkB,MAAA;IACtB,SAAA;IACA,QAAA;IACA,IAAA;IACA,OAAA;IACA,SAAA;IACA,WAAA;IACA,QAAA,GAAW,MAAA;EAAA,IACT,OAAA;EAeE,qBAAA,CAAsB,SAAA,UAAmB,KAAA,YAAa,OAAA,CAAQ,cAAA;EAW9D,aAAA,CAAc,SAAA,WAAoB,OAAA;EASlC,aAAA,CACJ,EAAA,UACA,MAAA;IACE,MAAA;IACA,QAAA;IACA,aAAA;EAAA,IAED,OAAA;EAuBG,iBAAA,CAAkB,SAAA,WAAoB,OAAA,CAAQ,UAAA;EAU9C,cAAA,CAAe,SAAA,WAAoB,OAAA,CAAQ,UAAA;EAU3C,kBAAA,CAAmB,SAAA,WAAoB,OAAA,CAAQ,UAAA;EAU/C,gBAAA,CAAiB,gBAAA,YAAwB,OAAA,CAAQ,UAAA;EAUjD,iBAAA,CAAkB,gBAAA,YAAwB,OAAA;EAc1C,iBAAA,CAAkB,MAAA;IACtB,SAAA;IACA,OAAA;IACA,KAAA;IACA,KAAA;EAAA,IACE,OAAA;EAcE,eAAA,CAAgB,SAAA,UAAmB,MAAA,YAAqB,OAAA,CAAQ,cAAA;EAAR;;;;EAaxD,gBAAA,CAAiB,OAAA,UAAiB,MAAA,WAAiB,OAAA;EA2CnD,UAAA,CAAW,MAAA;IACf,EAAA;IACA,KAAA;IACA,IAAA;IACA,QAAA,GAAW,MAAA;IACX,MAAA,GAAS,MAAA;IACT,WAAA,GAAc,MAAA;EAAA,IACZ,OAAA;EAaE,OAAA,CAAQ,EAAA,WAAa,OAAA,CAAQ,OAAA;EAAA,OAQ5B,SAAA,GAAA,CAAa,KAAA,kBAAuB,CAAA;AAAA"}
1
+ {"version":3,"file":"db.d.cts","names":[],"sources":["../src/db.ts"],"mappings":";UA8DiB,cAAA;EACf,EAAA;EACA,SAAA;EACA,QAAA;EACA,IAAA;EACA,OAAA;EACA,SAAA;EACA,WAAA;EACA,QAAA;EACA,UAAA;AAAA;AAAA,UAGe,UAAA;EACf,EAAA;EACA,SAAA;EACA,MAAA;EACA,aAAA;EACA,UAAA;EACA,QAAA;AAAA;AAAA,UAGe,cAAA;EACf,EAAA;EACA,SAAA;EACA,OAAA;EACA,KAAA;EACA,KAAA;EACA,MAAA;EACA,SAAA;EACA,UAAA;AAAA;AAAA,UAGe,OAAA;EACf,EAAA;EACA,KAAA;EACA,IAAA;EACA,QAAA;EACA,MAAA;EACA,WAAA;EACA,MAAA;EACA,UAAA;AAAA;AAAA,UAGe,UAAA;EACf,EAAA;EACA,eAAA;EACA,QAAA;EACA,UAAA;EACA,SAAA;EACA,WAAA;EACA,aAAA;EACA,IAAA;EACA,UAAA;EACA,UAAA;AAAA;AAAA,cAGW,SAAA;EAAA,QACH,EAAA;EAAA,QACA,WAAA;EAAA,QAED,WAAA,CAAA;EAAA,OAIM,MAAA,CAAO,MAAA,WAAiB,OAAA,CAAQ,SAAA;EAWvC,iBAAA,CAAA,GAAqB,OAAA;EAQ3B,KAAA,CAAA;;;;EAOM,OAAA,CAAQ,KAAA,WAAgB,OAAA;EA5C9B;;;EAmDM,QAAA,GAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,CAAA;EAMpC,iBAAA,CAAkB,MAAA;IACtB,SAAA;IACA,QAAA;IACA,IAAA;IACA,OAAA;IACA,SAAA;IACA,WAAA;IACA,QAAA,GAAW,MAAA;EAAA,IACT,OAAA;EAeE,qBAAA,CAAsB,SAAA,UAAmB,KAAA,YAAa,OAAA,CAAQ,cAAA;EAW9D,aAAA,CAAc,SAAA,WAAoB,OAAA;EASlC,aAAA,CACJ,EAAA,UACA,MAAA;IACE,MAAA;IACA,QAAA;IACA,aAAA;EAAA,IAED,OAAA;EAuBG,iBAAA,CAAkB,SAAA,WAAoB,OAAA,CAAQ,UAAA;EAU9C,cAAA,CAAe,SAAA,WAAoB,OAAA,CAAQ,UAAA;EAU3C,kBAAA,CAAmB,SAAA,WAAoB,OAAA,CAAQ,UAAA;EAU/C,gBAAA,CAAiB,gBAAA,YAAwB,OAAA,CAAQ,UAAA;EAUjD,iBAAA,CAAkB,gBAAA,YAAwB,OAAA;EAc1C,iBAAA,CAAkB,MAAA;IACtB,SAAA;IACA,OAAA;IACA,KAAA;IACA,KAAA;EAAA,IACE,OAAA;EAcE,eAAA,CAAgB,SAAA,UAAmB,MAAA,YAAqB,OAAA,CAAQ,cAAA;EA3CvB;;;;EAwDzC,gBAAA,CAAiB,OAAA,UAAiB,MAAA,WAAiB,OAAA;EA2CnD,UAAA,CAAW,MAAA;IACf,EAAA;IACA,KAAA;IACA,IAAA;IACA,QAAA,GAAW,MAAA;IACX,MAAA,GAAS,MAAA;IACT,WAAA,GAAc,MAAA;EAAA,IACZ,OAAA;EAaE,OAAA,CAAQ,EAAA,WAAa,OAAA,CAAQ,OAAA;EAQ7B,aAAA,CAAc,MAAA;IAClB,eAAA;IACA,QAAA;IACA,UAAA;IACA,SAAA;IACA,WAAA;IACA,aAAA;IACA,IAAA;IACA,UAAA;EAAA,IACE,OAAA;EAeE,iBAAA,CAAkB,cAAA,UAAwB,KAAA,YAAa,OAAA,CAAQ,UAAA;EAS/D,kBAAA,CAAmB,SAAA,WAAoB,OAAA;EAavC,sBAAA,CAAuB,cAAA,WAAyB,OAAA;EAAA,OAyB/C,SAAA,GAAA,CAAa,KAAA,kBAAuB,CAAA;AAAA"}
package/dist/db.d.mts CHANGED
@@ -38,6 +38,18 @@ interface TaskRow {
38
38
  status: string;
39
39
  created_at: string;
40
40
  }
41
+ interface MessageRow {
42
+ id: number;
43
+ conversation_id: string;
44
+ bot_name: string;
45
+ message_id: string;
46
+ sender_id: string;
47
+ sender_name: string;
48
+ sender_is_bot: number;
49
+ text: string | null;
50
+ event_path: string | null;
51
+ created_at: string;
52
+ }
41
53
  declare class PersonaDB {
42
54
  private db;
43
55
  private initialized;
@@ -95,8 +107,21 @@ declare class PersonaDB {
95
107
  assignments?: Record<string, string> | null;
96
108
  }): Promise<void>;
97
109
  getTask(id: string): Promise<TaskRow | null>;
110
+ insertMessage(params: {
111
+ conversation_id: string;
112
+ bot_name: string;
113
+ message_id: string;
114
+ sender_id: string;
115
+ sender_name: string;
116
+ sender_is_bot: boolean;
117
+ text?: string | null;
118
+ event_path?: string | null;
119
+ }): Promise<number>;
120
+ getRecentMessages(conversationId: string, limit?: number): Promise<MessageRow[]>;
121
+ getTodayReplyCount(character: string): Promise<number>;
122
+ getConsecutiveBotCount(conversationId: string): Promise<number>;
98
123
  static parseJSON<T>(value: string | null): T | null;
99
124
  }
100
125
  //#endregion
101
- export { InteractionRow, PersonaDB, SeededTopicRow, SessionRow, TaskRow };
126
+ export { InteractionRow, MessageRow, PersonaDB, SeededTopicRow, SessionRow, TaskRow };
102
127
  //# sourceMappingURL=db.d.mts.map
package/dist/db.d.mts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.mts","names":[],"sources":["../src/db.ts"],"mappings":";UAgDiB,cAAA;EACf,EAAA;EACA,SAAA;EACA,QAAA;EACA,IAAA;EACA,OAAA;EACA,SAAA;EACA,WAAA;EACA,QAAA;EACA,UAAA;AAAA;AAAA,UAGe,UAAA;EACf,EAAA;EACA,SAAA;EACA,MAAA;EACA,aAAA;EACA,UAAA;EACA,QAAA;AAAA;AAAA,UAGe,cAAA;EACf,EAAA;EACA,SAAA;EACA,OAAA;EACA,KAAA;EACA,KAAA;EACA,MAAA;EACA,SAAA;EACA,UAAA;AAAA;AAAA,UAGe,OAAA;EACf,EAAA;EACA,KAAA;EACA,IAAA;EACA,QAAA;EACA,MAAA;EACA,WAAA;EACA,MAAA;EACA,UAAA;AAAA;AAAA,cAGW,SAAA;EAAA,QACH,EAAA;EAAA,QACA,WAAA;EAAA,QAED,WAAA,CAAA;EAAA,OAIM,MAAA,CAAO,MAAA,WAAiB,OAAA,CAAQ,SAAA;EAWvC,iBAAA,CAAA,GAAqB,OAAA;EAQ3B,KAAA,CAAA;EArCA;;;EA4CM,OAAA,CAAQ,KAAA,WAAgB,OAAA;EAxC9B;;;EA+CM,QAAA,GAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,CAAA;EAMpC,iBAAA,CAAkB,MAAA;IACtB,SAAA;IACA,QAAA;IACA,IAAA;IACA,OAAA;IACA,SAAA;IACA,WAAA;IACA,QAAA,GAAW,MAAA;EAAA,IACT,OAAA;EAeE,qBAAA,CAAsB,SAAA,UAAmB,KAAA,YAAa,OAAA,CAAQ,cAAA;EAW9D,aAAA,CAAc,SAAA,WAAoB,OAAA;EASlC,aAAA,CACJ,EAAA,UACA,MAAA;IACE,MAAA;IACA,QAAA;IACA,aAAA;EAAA,IAED,OAAA;EAuBG,iBAAA,CAAkB,SAAA,WAAoB,OAAA,CAAQ,UAAA;EAU9C,cAAA,CAAe,SAAA,WAAoB,OAAA,CAAQ,UAAA;EAU3C,kBAAA,CAAmB,SAAA,WAAoB,OAAA,CAAQ,UAAA;EAU/C,gBAAA,CAAiB,gBAAA,YAAwB,OAAA,CAAQ,UAAA;EAUjD,iBAAA,CAAkB,gBAAA,YAAwB,OAAA;EAc1C,iBAAA,CAAkB,MAAA;IACtB,SAAA;IACA,OAAA;IACA,KAAA;IACA,KAAA;EAAA,IACE,OAAA;EAcE,eAAA,CAAgB,SAAA,UAAmB,MAAA,YAAqB,OAAA,CAAQ,cAAA;EAAR;;;;EAaxD,gBAAA,CAAiB,OAAA,UAAiB,MAAA,WAAiB,OAAA;EA2CnD,UAAA,CAAW,MAAA;IACf,EAAA;IACA,KAAA;IACA,IAAA;IACA,QAAA,GAAW,MAAA;IACX,MAAA,GAAS,MAAA;IACT,WAAA,GAAc,MAAA;EAAA,IACZ,OAAA;EAaE,OAAA,CAAQ,EAAA,WAAa,OAAA,CAAQ,OAAA;EAAA,OAQ5B,SAAA,GAAA,CAAa,KAAA,kBAAuB,CAAA;AAAA"}
1
+ {"version":3,"file":"db.d.mts","names":[],"sources":["../src/db.ts"],"mappings":";UA8DiB,cAAA;EACf,EAAA;EACA,SAAA;EACA,QAAA;EACA,IAAA;EACA,OAAA;EACA,SAAA;EACA,WAAA;EACA,QAAA;EACA,UAAA;AAAA;AAAA,UAGe,UAAA;EACf,EAAA;EACA,SAAA;EACA,MAAA;EACA,aAAA;EACA,UAAA;EACA,QAAA;AAAA;AAAA,UAGe,cAAA;EACf,EAAA;EACA,SAAA;EACA,OAAA;EACA,KAAA;EACA,KAAA;EACA,MAAA;EACA,SAAA;EACA,UAAA;AAAA;AAAA,UAGe,OAAA;EACf,EAAA;EACA,KAAA;EACA,IAAA;EACA,QAAA;EACA,MAAA;EACA,WAAA;EACA,MAAA;EACA,UAAA;AAAA;AAAA,UAGe,UAAA;EACf,EAAA;EACA,eAAA;EACA,QAAA;EACA,UAAA;EACA,SAAA;EACA,WAAA;EACA,aAAA;EACA,IAAA;EACA,UAAA;EACA,UAAA;AAAA;AAAA,cAGW,SAAA;EAAA,QACH,EAAA;EAAA,QACA,WAAA;EAAA,QAED,WAAA,CAAA;EAAA,OAIM,MAAA,CAAO,MAAA,WAAiB,OAAA,CAAQ,SAAA;EAWvC,iBAAA,CAAA,GAAqB,OAAA;EAQ3B,KAAA,CAAA;;;;EAOM,OAAA,CAAQ,KAAA,WAAgB,OAAA;EA5C9B;;;EAmDM,QAAA,GAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,CAAA;EAMpC,iBAAA,CAAkB,MAAA;IACtB,SAAA;IACA,QAAA;IACA,IAAA;IACA,OAAA;IACA,SAAA;IACA,WAAA;IACA,QAAA,GAAW,MAAA;EAAA,IACT,OAAA;EAeE,qBAAA,CAAsB,SAAA,UAAmB,KAAA,YAAa,OAAA,CAAQ,cAAA;EAW9D,aAAA,CAAc,SAAA,WAAoB,OAAA;EASlC,aAAA,CACJ,EAAA,UACA,MAAA;IACE,MAAA;IACA,QAAA;IACA,aAAA;EAAA,IAED,OAAA;EAuBG,iBAAA,CAAkB,SAAA,WAAoB,OAAA,CAAQ,UAAA;EAU9C,cAAA,CAAe,SAAA,WAAoB,OAAA,CAAQ,UAAA;EAU3C,kBAAA,CAAmB,SAAA,WAAoB,OAAA,CAAQ,UAAA;EAU/C,gBAAA,CAAiB,gBAAA,YAAwB,OAAA,CAAQ,UAAA;EAUjD,iBAAA,CAAkB,gBAAA,YAAwB,OAAA;EAc1C,iBAAA,CAAkB,MAAA;IACtB,SAAA;IACA,OAAA;IACA,KAAA;IACA,KAAA;EAAA,IACE,OAAA;EAcE,eAAA,CAAgB,SAAA,UAAmB,MAAA,YAAqB,OAAA,CAAQ,cAAA;EA3CvB;;;;EAwDzC,gBAAA,CAAiB,OAAA,UAAiB,MAAA,WAAiB,OAAA;EA2CnD,UAAA,CAAW,MAAA;IACf,EAAA;IACA,KAAA;IACA,IAAA;IACA,QAAA,GAAW,MAAA;IACX,MAAA,GAAS,MAAA;IACT,WAAA,GAAc,MAAA;EAAA,IACZ,OAAA;EAaE,OAAA,CAAQ,EAAA,WAAa,OAAA,CAAQ,OAAA;EAQ7B,aAAA,CAAc,MAAA;IAClB,eAAA;IACA,QAAA;IACA,UAAA;IACA,SAAA;IACA,WAAA;IACA,aAAA;IACA,IAAA;IACA,UAAA;EAAA,IACE,OAAA;EAeE,iBAAA,CAAkB,cAAA,UAAwB,KAAA,YAAa,OAAA,CAAQ,UAAA;EAS/D,kBAAA,CAAmB,SAAA,WAAoB,OAAA;EAavC,sBAAA,CAAuB,cAAA,WAAyB,OAAA;EAAA,OAyB/C,SAAA,GAAA,CAAa,KAAA,kBAAuB,CAAA;AAAA"}
package/dist/db.mjs CHANGED
@@ -43,7 +43,21 @@ const SCHEMA_STATEMENTS = [
43
43
  assignments TEXT,
44
44
  status TEXT NOT NULL DEFAULT 'active',
45
45
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
46
- )`
46
+ )`,
47
+ `CREATE TABLE IF NOT EXISTS messages (
48
+ id INTEGER PRIMARY KEY,
49
+ conversation_id TEXT NOT NULL,
50
+ bot_name TEXT NOT NULL,
51
+ message_id TEXT NOT NULL,
52
+ sender_id TEXT NOT NULL,
53
+ sender_name TEXT NOT NULL,
54
+ sender_is_bot BOOLEAN DEFAULT 0,
55
+ text TEXT,
56
+ event_path TEXT,
57
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
58
+ )`,
59
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_dedup ON messages(conversation_id, message_id)`,
60
+ `CREATE INDEX IF NOT EXISTS idx_messages_conv ON messages(conversation_id, created_at)`
47
61
  ];
48
62
  var PersonaDB = class PersonaDB {
49
63
  db;
@@ -176,6 +190,34 @@ var PersonaDB = class PersonaDB {
176
190
  await this.ensureInitialized();
177
191
  return (await this.db.all(sql`SELECT * FROM tasks WHERE id = ${id}`).execute())[0] ?? null;
178
192
  }
193
+ async insertMessage(params) {
194
+ await this.ensureInitialized();
195
+ const senderIsBot = params.sender_is_bot ? 1 : 0;
196
+ await this.db.run(sql`INSERT OR IGNORE INTO messages (conversation_id, bot_name, message_id, sender_id, sender_name, sender_is_bot, text, event_path)
197
+ VALUES (${params.conversation_id}, ${params.bot_name}, ${params.message_id}, ${params.sender_id}, ${params.sender_name}, ${senderIsBot}, ${params.text ?? null}, ${params.event_path ?? null})`).execute();
198
+ return (await this.db.all(sql.raw("SELECT last_insert_rowid() as id")).execute())[0]?.id ?? 0;
199
+ }
200
+ async getRecentMessages(conversationId, limit = 20) {
201
+ await this.ensureInitialized();
202
+ return this.db.all(sql`SELECT * FROM messages WHERE conversation_id = ${conversationId} ORDER BY created_at ASC, id ASC LIMIT ${limit}`).execute();
203
+ }
204
+ async getTodayReplyCount(character) {
205
+ await this.ensureInitialized();
206
+ return (await this.db.all(sql`SELECT COUNT(*) as cnt FROM interactions
207
+ WHERE character = ${character}
208
+ AND type = 'messaging_reply'
209
+ AND created_at >= date('now')`).execute())[0]?.cnt ?? 0;
210
+ }
211
+ async getConsecutiveBotCount(conversationId) {
212
+ await this.ensureInitialized();
213
+ const rows = await this.db.all(sql`SELECT sender_is_bot FROM messages
214
+ WHERE conversation_id = ${conversationId}
215
+ ORDER BY created_at DESC, id DESC`).execute();
216
+ let count = 0;
217
+ for (const row of rows) if (row.sender_is_bot) count++;
218
+ else break;
219
+ return count;
220
+ }
179
221
  static parseJSON(value) {
180
222
  if (value === null) return null;
181
223
  try {
package/dist/db.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"db.mjs","names":[],"sources":["../src/db.ts"],"sourcesContent":["import { existsSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { initDatabase, sql } from \"@aigne/sqlite\";\nimport type { LibSQLDatabase } from \"drizzle-orm/libsql\";\n\nconst SCHEMA_STATEMENTS = [\n `CREATE TABLE IF NOT EXISTS interactions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n character TEXT NOT NULL,\n platform TEXT NOT NULL,\n type TEXT NOT NULL,\n content TEXT,\n thread_id TEXT,\n external_id TEXT,\n metadata TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )`,\n `CREATE TABLE IF NOT EXISTS sessions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n character TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'running',\n actions_taken TEXT,\n started_at TEXT NOT NULL DEFAULT (datetime('now')),\n ended_at TEXT\n )`,\n `CREATE TABLE IF NOT EXISTS seeded_topics (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n character TEXT NOT NULL,\n task_id TEXT NOT NULL,\n topic TEXT NOT NULL,\n angle TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n posted_at TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )`,\n `CREATE INDEX IF NOT EXISTS idx_seeded_recent ON seeded_topics(task_id, status, posted_at)`,\n `CREATE TABLE IF NOT EXISTS tasks (\n id TEXT PRIMARY KEY,\n topic TEXT NOT NULL,\n goal TEXT,\n material TEXT,\n config TEXT,\n assignments TEXT,\n status TEXT NOT NULL DEFAULT 'active',\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )`,\n];\n\nexport interface InteractionRow {\n id: number;\n character: string;\n platform: string;\n type: string;\n content: string | null;\n thread_id: string | null;\n external_id: string | null;\n metadata: string | null;\n created_at: string;\n}\n\nexport interface SessionRow {\n id: number;\n character: string;\n status: string;\n actions_taken: string | null;\n started_at: string;\n ended_at: string | null;\n}\n\nexport interface SeededTopicRow {\n id: number;\n character: string;\n task_id: string;\n topic: string;\n angle: string | null;\n status: string;\n posted_at: string | null;\n created_at: string;\n}\n\nexport interface TaskRow {\n id: string;\n topic: string;\n goal: string | null;\n material: string | null;\n config: string | null;\n assignments: string | null;\n status: string;\n created_at: string;\n}\n\nexport class PersonaDB {\n private db: LibSQLDatabase;\n private initialized = false;\n\n private constructor(db: LibSQLDatabase) {\n this.db = db;\n }\n\n static async create(dbPath: string): Promise<PersonaDB> {\n const dir = dirname(dbPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n // initDatabase expects a file: URL, not a bare path\n const url = dbPath.startsWith(\"file:\") ? dbPath : `file:${dbPath}`;\n const db = await initDatabase({ url, wal: true });\n return new PersonaDB(db);\n }\n\n async ensureInitialized(): Promise<void> {\n if (this.initialized) return;\n for (const stmt of SCHEMA_STATEMENTS) {\n await this.db.run(sql.raw(stmt)).execute();\n }\n this.initialized = true;\n }\n\n close(): void {\n // LibSQLDatabase has no close method — no-op\n }\n\n /**\n * Execute raw SQL. Public helper for tests that need direct DB manipulation.\n */\n async execRaw(query: string): Promise<void> {\n await this.db.run(sql.raw(query)).execute();\n }\n\n /**\n * Query raw SQL returning rows. Public helper for tests.\n */\n async queryRaw<T>(query: string): Promise<T[]> {\n return this.db.all<T>(sql.raw(query)).execute();\n }\n\n // ========== Interactions ==========\n\n async insertInteraction(params: {\n character: string;\n platform: string;\n type: string;\n content?: string | null;\n thread_id?: string | null;\n external_id?: string | null;\n metadata?: Record<string, unknown> | null;\n }): Promise<number> {\n await this.ensureInitialized();\n const metadataStr = params.metadata ? JSON.stringify(params.metadata) : null;\n await this.db\n .run(\n sql`INSERT INTO interactions (character, platform, type, content, thread_id, external_id, metadata)\n VALUES (${params.character}, ${params.platform}, ${params.type}, ${params.content ?? null}, ${params.thread_id ?? null}, ${params.external_id ?? null}, ${metadataStr})`,\n )\n .execute();\n const rows = await this.db\n .all<{ id: number }>(sql.raw(\"SELECT last_insert_rowid() as id\"))\n .execute();\n return rows[0]?.id ?? 0;\n }\n\n async getRecentInteractions(character: string, limit = 10): Promise<InteractionRow[]> {\n await this.ensureInitialized();\n return this.db\n .all<InteractionRow>(\n sql`SELECT * FROM interactions WHERE character = ${character} ORDER BY created_at DESC LIMIT ${limit}`,\n )\n .execute();\n }\n\n // ========== Sessions ==========\n\n async insertSession(character: string): Promise<number> {\n await this.ensureInitialized();\n await this.db.run(sql`INSERT INTO sessions (character) VALUES (${character})`).execute();\n const rows = await this.db\n .all<{ id: number }>(sql.raw(\"SELECT last_insert_rowid() as id\"))\n .execute();\n return rows[0]?.id ?? 0;\n }\n\n async updateSession(\n id: number,\n params: {\n status?: string;\n ended_at?: string;\n actions_taken?: unknown[] | null;\n },\n ): Promise<number> {\n await this.ensureInitialized();\n\n const clauses: ReturnType<typeof sql>[] = [];\n if (params.status !== undefined) {\n clauses.push(sql`status = ${params.status}`);\n }\n if (params.ended_at !== undefined) {\n clauses.push(sql`ended_at = ${params.ended_at}`);\n }\n if (params.actions_taken !== undefined) {\n const actionsStr = params.actions_taken ? JSON.stringify(params.actions_taken) : null;\n clauses.push(sql`actions_taken = ${actionsStr}`);\n }\n\n if (clauses.length === 0) return 0;\n\n const result = await this.db\n .run(sql`UPDATE sessions SET ${sql.join(clauses, sql`, `)} WHERE id = ${id}`)\n .execute();\n return result.rowsAffected;\n }\n\n async getRunningSession(character: string): Promise<SessionRow | null> {\n await this.ensureInitialized();\n const rows = await this.db\n .all<SessionRow>(\n sql`SELECT * FROM sessions WHERE character = ${character} AND status = 'running' LIMIT 1`,\n )\n .execute();\n return rows[0] ?? null;\n }\n\n async getLastSession(character: string): Promise<SessionRow | null> {\n await this.ensureInitialized();\n const rows = await this.db\n .all<SessionRow>(\n sql`SELECT * FROM sessions WHERE character = ${character} AND status != 'running' ORDER BY ended_at DESC LIMIT 1`,\n )\n .execute();\n return rows[0] ?? null;\n }\n\n async getLastDoneSession(character: string): Promise<SessionRow | null> {\n await this.ensureInitialized();\n const rows = await this.db\n .all<SessionRow>(\n sql`SELECT * FROM sessions WHERE character = ${character} AND status = 'done' ORDER BY ended_at DESC LIMIT 1`,\n )\n .execute();\n return rows[0] ?? null;\n }\n\n async getStaleSessions(thresholdMinutes = 60): Promise<SessionRow[]> {\n await this.ensureInitialized();\n const threshold = `-${thresholdMinutes} minutes`;\n return this.db\n .all<SessionRow>(\n sql`SELECT * FROM sessions WHERE status = 'running' AND started_at < datetime('now', ${threshold})`,\n )\n .execute();\n }\n\n async markStaleSessions(thresholdMinutes = 60): Promise<number> {\n await this.ensureInitialized();\n const threshold = `-${thresholdMinutes} minutes`;\n const result = await this.db\n .run(\n sql`UPDATE sessions SET status = 'failed', ended_at = datetime('now')\n WHERE status = 'running' AND started_at < datetime('now', ${threshold})`,\n )\n .execute();\n return result.rowsAffected;\n }\n\n // ========== Seeded Topics ==========\n\n async insertSeededTopic(params: {\n character: string;\n task_id: string;\n topic: string;\n angle?: string | null;\n }): Promise<number> {\n await this.ensureInitialized();\n await this.db\n .run(\n sql`INSERT INTO seeded_topics (character, task_id, topic, angle)\n VALUES (${params.character}, ${params.task_id}, ${params.topic}, ${params.angle ?? null})`,\n )\n .execute();\n const rows = await this.db\n .all<{ id: number }>(sql.raw(\"SELECT last_insert_rowid() as id\"))\n .execute();\n return rows[0]?.id ?? 0;\n }\n\n async getSeededTopics(character: string, status = \"pending\"): Promise<SeededTopicRow[]> {\n await this.ensureInitialized();\n return this.db\n .all<SeededTopicRow>(\n sql`SELECT * FROM seeded_topics WHERE character = ${character} AND status = ${status} ORDER BY created_at ASC`,\n )\n .execute();\n }\n\n /**\n * Atomically claim a seeded topic for posting.\n * Returns true if claimed, false if 4-hour dedup prevents it.\n */\n async claimSeededTopic(topicId: number, taskId: string): Promise<boolean> {\n await this.ensureInitialized();\n\n // Use manual transaction control for atomicity\n await this.db.run(sql.raw(\"BEGIN IMMEDIATE\")).execute();\n try {\n // Check for same task_id posted within last 4 hours\n const existing = await this.db\n .all<{ x: number }>(\n sql`SELECT 1 as x FROM seeded_topics\n WHERE task_id = ${taskId} AND status = 'posted'\n AND posted_at > datetime('now', '-4 hours')\n LIMIT 1`,\n )\n .execute();\n\n if (existing.length > 0) {\n await this.db.run(sql.raw(\"ROLLBACK\")).execute();\n return false;\n }\n\n // Claim it\n const result = await this.db\n .run(\n sql`UPDATE seeded_topics SET status = 'posted', posted_at = datetime('now')\n WHERE id = ${topicId} AND status = 'pending'`,\n )\n .execute();\n\n await this.db.run(sql.raw(\"COMMIT\")).execute();\n return result.rowsAffected > 0;\n } catch (e) {\n try {\n await this.db.run(sql.raw(\"ROLLBACK\")).execute();\n } catch {\n // ignore rollback errors\n }\n throw e;\n }\n }\n\n // ========== Tasks ==========\n\n async insertTask(params: {\n id: string;\n topic: string;\n goal?: string | null;\n material?: Record<string, unknown> | null;\n config?: Record<string, unknown> | null;\n assignments?: Record<string, string> | null;\n }): Promise<void> {\n await this.ensureInitialized();\n const materialStr = params.material ? JSON.stringify(params.material) : null;\n const configStr = params.config ? JSON.stringify(params.config) : null;\n const assignmentsStr = params.assignments ? JSON.stringify(params.assignments) : null;\n await this.db\n .run(\n sql`INSERT INTO tasks (id, topic, goal, material, config, assignments)\n VALUES (${params.id}, ${params.topic}, ${params.goal ?? null}, ${materialStr}, ${configStr}, ${assignmentsStr})`,\n )\n .execute();\n }\n\n async getTask(id: string): Promise<TaskRow | null> {\n await this.ensureInitialized();\n const rows = await this.db.all<TaskRow>(sql`SELECT * FROM tasks WHERE id = ${id}`).execute();\n return rows[0] ?? null;\n }\n\n // ========== JSON Helpers ==========\n\n static parseJSON<T>(value: string | null): T | null {\n if (value === null) return null;\n try {\n return JSON.parse(value) as T;\n } catch {\n return null;\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"db.mjs","names":[],"sources":["../src/db.ts"],"sourcesContent":["import { existsSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { initDatabase, sql } from \"@aigne/sqlite\";\nimport type { LibSQLDatabase } from \"drizzle-orm/libsql\";\n\nconst SCHEMA_STATEMENTS = [\n `CREATE TABLE IF NOT EXISTS interactions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n character TEXT NOT NULL,\n platform TEXT NOT NULL,\n type TEXT NOT NULL,\n content TEXT,\n thread_id TEXT,\n external_id TEXT,\n metadata TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )`,\n `CREATE TABLE IF NOT EXISTS sessions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n character TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'running',\n actions_taken TEXT,\n started_at TEXT NOT NULL DEFAULT (datetime('now')),\n ended_at TEXT\n )`,\n `CREATE TABLE IF NOT EXISTS seeded_topics (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n character TEXT NOT NULL,\n task_id TEXT NOT NULL,\n topic TEXT NOT NULL,\n angle TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n posted_at TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )`,\n `CREATE INDEX IF NOT EXISTS idx_seeded_recent ON seeded_topics(task_id, status, posted_at)`,\n `CREATE TABLE IF NOT EXISTS tasks (\n id TEXT PRIMARY KEY,\n topic TEXT NOT NULL,\n goal TEXT,\n material TEXT,\n config TEXT,\n assignments TEXT,\n status TEXT NOT NULL DEFAULT 'active',\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )`,\n `CREATE TABLE IF NOT EXISTS messages (\n id INTEGER PRIMARY KEY,\n conversation_id TEXT NOT NULL,\n bot_name TEXT NOT NULL,\n message_id TEXT NOT NULL,\n sender_id TEXT NOT NULL,\n sender_name TEXT NOT NULL,\n sender_is_bot BOOLEAN DEFAULT 0,\n text TEXT,\n event_path TEXT,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n )`,\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_dedup ON messages(conversation_id, message_id)`,\n `CREATE INDEX IF NOT EXISTS idx_messages_conv ON messages(conversation_id, created_at)`,\n];\n\nexport interface InteractionRow {\n id: number;\n character: string;\n platform: string;\n type: string;\n content: string | null;\n thread_id: string | null;\n external_id: string | null;\n metadata: string | null;\n created_at: string;\n}\n\nexport interface SessionRow {\n id: number;\n character: string;\n status: string;\n actions_taken: string | null;\n started_at: string;\n ended_at: string | null;\n}\n\nexport interface SeededTopicRow {\n id: number;\n character: string;\n task_id: string;\n topic: string;\n angle: string | null;\n status: string;\n posted_at: string | null;\n created_at: string;\n}\n\nexport interface TaskRow {\n id: string;\n topic: string;\n goal: string | null;\n material: string | null;\n config: string | null;\n assignments: string | null;\n status: string;\n created_at: string;\n}\n\nexport interface MessageRow {\n id: number;\n conversation_id: string;\n bot_name: string;\n message_id: string;\n sender_id: string;\n sender_name: string;\n sender_is_bot: number; // 0 or 1 (SQLite boolean)\n text: string | null;\n event_path: string | null;\n created_at: string;\n}\n\nexport class PersonaDB {\n private db: LibSQLDatabase;\n private initialized = false;\n\n private constructor(db: LibSQLDatabase) {\n this.db = db;\n }\n\n static async create(dbPath: string): Promise<PersonaDB> {\n const dir = dirname(dbPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n // initDatabase expects a file: URL, not a bare path\n const url = dbPath.startsWith(\"file:\") ? dbPath : `file:${dbPath}`;\n const db = await initDatabase({ url, wal: true });\n return new PersonaDB(db);\n }\n\n async ensureInitialized(): Promise<void> {\n if (this.initialized) return;\n for (const stmt of SCHEMA_STATEMENTS) {\n await this.db.run(sql.raw(stmt)).execute();\n }\n this.initialized = true;\n }\n\n close(): void {\n // LibSQLDatabase has no close method — no-op\n }\n\n /**\n * Execute raw SQL. Public helper for tests that need direct DB manipulation.\n */\n async execRaw(query: string): Promise<void> {\n await this.db.run(sql.raw(query)).execute();\n }\n\n /**\n * Query raw SQL returning rows. Public helper for tests.\n */\n async queryRaw<T>(query: string): Promise<T[]> {\n return this.db.all<T>(sql.raw(query)).execute();\n }\n\n // ========== Interactions ==========\n\n async insertInteraction(params: {\n character: string;\n platform: string;\n type: string;\n content?: string | null;\n thread_id?: string | null;\n external_id?: string | null;\n metadata?: Record<string, unknown> | null;\n }): Promise<number> {\n await this.ensureInitialized();\n const metadataStr = params.metadata ? JSON.stringify(params.metadata) : null;\n await this.db\n .run(\n sql`INSERT INTO interactions (character, platform, type, content, thread_id, external_id, metadata)\n VALUES (${params.character}, ${params.platform}, ${params.type}, ${params.content ?? null}, ${params.thread_id ?? null}, ${params.external_id ?? null}, ${metadataStr})`,\n )\n .execute();\n const rows = await this.db\n .all<{ id: number }>(sql.raw(\"SELECT last_insert_rowid() as id\"))\n .execute();\n return rows[0]?.id ?? 0;\n }\n\n async getRecentInteractions(character: string, limit = 10): Promise<InteractionRow[]> {\n await this.ensureInitialized();\n return this.db\n .all<InteractionRow>(\n sql`SELECT * FROM interactions WHERE character = ${character} ORDER BY created_at DESC LIMIT ${limit}`,\n )\n .execute();\n }\n\n // ========== Sessions ==========\n\n async insertSession(character: string): Promise<number> {\n await this.ensureInitialized();\n await this.db.run(sql`INSERT INTO sessions (character) VALUES (${character})`).execute();\n const rows = await this.db\n .all<{ id: number }>(sql.raw(\"SELECT last_insert_rowid() as id\"))\n .execute();\n return rows[0]?.id ?? 0;\n }\n\n async updateSession(\n id: number,\n params: {\n status?: string;\n ended_at?: string;\n actions_taken?: unknown[] | null;\n },\n ): Promise<number> {\n await this.ensureInitialized();\n\n const clauses: ReturnType<typeof sql>[] = [];\n if (params.status !== undefined) {\n clauses.push(sql`status = ${params.status}`);\n }\n if (params.ended_at !== undefined) {\n clauses.push(sql`ended_at = ${params.ended_at}`);\n }\n if (params.actions_taken !== undefined) {\n const actionsStr = params.actions_taken ? JSON.stringify(params.actions_taken) : null;\n clauses.push(sql`actions_taken = ${actionsStr}`);\n }\n\n if (clauses.length === 0) return 0;\n\n const result = await this.db\n .run(sql`UPDATE sessions SET ${sql.join(clauses, sql`, `)} WHERE id = ${id}`)\n .execute();\n return result.rowsAffected;\n }\n\n async getRunningSession(character: string): Promise<SessionRow | null> {\n await this.ensureInitialized();\n const rows = await this.db\n .all<SessionRow>(\n sql`SELECT * FROM sessions WHERE character = ${character} AND status = 'running' LIMIT 1`,\n )\n .execute();\n return rows[0] ?? null;\n }\n\n async getLastSession(character: string): Promise<SessionRow | null> {\n await this.ensureInitialized();\n const rows = await this.db\n .all<SessionRow>(\n sql`SELECT * FROM sessions WHERE character = ${character} AND status != 'running' ORDER BY ended_at DESC LIMIT 1`,\n )\n .execute();\n return rows[0] ?? null;\n }\n\n async getLastDoneSession(character: string): Promise<SessionRow | null> {\n await this.ensureInitialized();\n const rows = await this.db\n .all<SessionRow>(\n sql`SELECT * FROM sessions WHERE character = ${character} AND status = 'done' ORDER BY ended_at DESC LIMIT 1`,\n )\n .execute();\n return rows[0] ?? null;\n }\n\n async getStaleSessions(thresholdMinutes = 60): Promise<SessionRow[]> {\n await this.ensureInitialized();\n const threshold = `-${thresholdMinutes} minutes`;\n return this.db\n .all<SessionRow>(\n sql`SELECT * FROM sessions WHERE status = 'running' AND started_at < datetime('now', ${threshold})`,\n )\n .execute();\n }\n\n async markStaleSessions(thresholdMinutes = 60): Promise<number> {\n await this.ensureInitialized();\n const threshold = `-${thresholdMinutes} minutes`;\n const result = await this.db\n .run(\n sql`UPDATE sessions SET status = 'failed', ended_at = datetime('now')\n WHERE status = 'running' AND started_at < datetime('now', ${threshold})`,\n )\n .execute();\n return result.rowsAffected;\n }\n\n // ========== Seeded Topics ==========\n\n async insertSeededTopic(params: {\n character: string;\n task_id: string;\n topic: string;\n angle?: string | null;\n }): Promise<number> {\n await this.ensureInitialized();\n await this.db\n .run(\n sql`INSERT INTO seeded_topics (character, task_id, topic, angle)\n VALUES (${params.character}, ${params.task_id}, ${params.topic}, ${params.angle ?? null})`,\n )\n .execute();\n const rows = await this.db\n .all<{ id: number }>(sql.raw(\"SELECT last_insert_rowid() as id\"))\n .execute();\n return rows[0]?.id ?? 0;\n }\n\n async getSeededTopics(character: string, status = \"pending\"): Promise<SeededTopicRow[]> {\n await this.ensureInitialized();\n return this.db\n .all<SeededTopicRow>(\n sql`SELECT * FROM seeded_topics WHERE character = ${character} AND status = ${status} ORDER BY created_at ASC`,\n )\n .execute();\n }\n\n /**\n * Atomically claim a seeded topic for posting.\n * Returns true if claimed, false if 4-hour dedup prevents it.\n */\n async claimSeededTopic(topicId: number, taskId: string): Promise<boolean> {\n await this.ensureInitialized();\n\n // Use manual transaction control for atomicity\n await this.db.run(sql.raw(\"BEGIN IMMEDIATE\")).execute();\n try {\n // Check for same task_id posted within last 4 hours\n const existing = await this.db\n .all<{ x: number }>(\n sql`SELECT 1 as x FROM seeded_topics\n WHERE task_id = ${taskId} AND status = 'posted'\n AND posted_at > datetime('now', '-4 hours')\n LIMIT 1`,\n )\n .execute();\n\n if (existing.length > 0) {\n await this.db.run(sql.raw(\"ROLLBACK\")).execute();\n return false;\n }\n\n // Claim it\n const result = await this.db\n .run(\n sql`UPDATE seeded_topics SET status = 'posted', posted_at = datetime('now')\n WHERE id = ${topicId} AND status = 'pending'`,\n )\n .execute();\n\n await this.db.run(sql.raw(\"COMMIT\")).execute();\n return result.rowsAffected > 0;\n } catch (e) {\n try {\n await this.db.run(sql.raw(\"ROLLBACK\")).execute();\n } catch {\n // ignore rollback errors\n }\n throw e;\n }\n }\n\n // ========== Tasks ==========\n\n async insertTask(params: {\n id: string;\n topic: string;\n goal?: string | null;\n material?: Record<string, unknown> | null;\n config?: Record<string, unknown> | null;\n assignments?: Record<string, string> | null;\n }): Promise<void> {\n await this.ensureInitialized();\n const materialStr = params.material ? JSON.stringify(params.material) : null;\n const configStr = params.config ? JSON.stringify(params.config) : null;\n const assignmentsStr = params.assignments ? JSON.stringify(params.assignments) : null;\n await this.db\n .run(\n sql`INSERT INTO tasks (id, topic, goal, material, config, assignments)\n VALUES (${params.id}, ${params.topic}, ${params.goal ?? null}, ${materialStr}, ${configStr}, ${assignmentsStr})`,\n )\n .execute();\n }\n\n async getTask(id: string): Promise<TaskRow | null> {\n await this.ensureInitialized();\n const rows = await this.db.all<TaskRow>(sql`SELECT * FROM tasks WHERE id = ${id}`).execute();\n return rows[0] ?? null;\n }\n\n // ========== Messages ==========\n\n async insertMessage(params: {\n conversation_id: string;\n bot_name: string;\n message_id: string;\n sender_id: string;\n sender_name: string;\n sender_is_bot: boolean;\n text?: string | null;\n event_path?: string | null;\n }): Promise<number> {\n await this.ensureInitialized();\n const senderIsBot = params.sender_is_bot ? 1 : 0;\n await this.db\n .run(\n sql`INSERT OR IGNORE INTO messages (conversation_id, bot_name, message_id, sender_id, sender_name, sender_is_bot, text, event_path)\n VALUES (${params.conversation_id}, ${params.bot_name}, ${params.message_id}, ${params.sender_id}, ${params.sender_name}, ${senderIsBot}, ${params.text ?? null}, ${params.event_path ?? null})`,\n )\n .execute();\n const rows = await this.db\n .all<{ id: number }>(sql.raw(\"SELECT last_insert_rowid() as id\"))\n .execute();\n return rows[0]?.id ?? 0;\n }\n\n async getRecentMessages(conversationId: string, limit = 20): Promise<MessageRow[]> {\n await this.ensureInitialized();\n return this.db\n .all<MessageRow>(\n sql`SELECT * FROM messages WHERE conversation_id = ${conversationId} ORDER BY created_at ASC, id ASC LIMIT ${limit}`,\n )\n .execute();\n }\n\n async getTodayReplyCount(character: string): Promise<number> {\n await this.ensureInitialized();\n const rows = await this.db\n .all<{ cnt: number }>(\n sql`SELECT COUNT(*) as cnt FROM interactions\n WHERE character = ${character}\n AND type = 'messaging_reply'\n AND created_at >= date('now')`,\n )\n .execute();\n return rows[0]?.cnt ?? 0;\n }\n\n async getConsecutiveBotCount(conversationId: string): Promise<number> {\n await this.ensureInitialized();\n // Get recent messages in reverse chronological order\n // Count consecutive bot messages from the end\n const rows = await this.db\n .all<{ sender_is_bot: number }>(\n sql`SELECT sender_is_bot FROM messages\n WHERE conversation_id = ${conversationId}\n ORDER BY created_at DESC, id DESC`,\n )\n .execute();\n\n let count = 0;\n for (const row of rows) {\n if (row.sender_is_bot) {\n count++;\n } else {\n break; // Human message breaks the streak\n }\n }\n return count;\n }\n\n // ========== JSON Helpers ==========\n\n static parseJSON<T>(value: string | null): T | null {\n if (value === null) return null;\n try {\n return JSON.parse(value) as T;\n } catch {\n return null;\n }\n }\n}\n"],"mappings":"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"}