dsh-qqchat 0.2.4 → 0.2.6

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 (45) hide show
  1. package/README.en.md +5 -4
  2. package/README.md +3 -3
  3. package/client-src/plugin.cts +2 -2
  4. package/client-src/settings.cts +18 -1
  5. package/client-src/shared.cts +4 -4
  6. package/cordis.patch.yml +5 -0
  7. package/docs/ARCHITECTURE.en.md +1 -1
  8. package/docs/ARCHITECTURE.md +2 -1
  9. package/docs/DEVELOPMENT.md +3 -1
  10. package/docs/MEMORY.md +32 -4
  11. package/docs/MEMORY_CONTEXT.md +16 -0
  12. package/docs/README.md +2 -0
  13. package/docs/STREAMING.md +2 -2
  14. package/docs/prds/PRD-MEM-1-/347/276/244/350/256/260/345/277/206/344/270/216/347/276/244/345/217/213/350/256/260/345/277/206/347/255/226/347/225/245/350/260/203/346/225/264.md +396 -0
  15. package/docs/prds/README.md +62 -0
  16. package/lib/client.js +21 -4
  17. package/lib/config.js +3 -2
  18. package/lib/index.d.ts +2 -2
  19. package/lib/index.js +2 -3
  20. package/lib/session/agent-bridge.d.ts +3 -4
  21. package/lib/session/agent-bridge.js +40 -46
  22. package/lib/session/memory-snapshot.d.ts +4 -0
  23. package/lib/session/memory-snapshot.js +6 -2
  24. package/lib/session/runtime.js +15 -5
  25. package/lib/storage/db.d.ts +6 -1
  26. package/lib/storage/db.js +57 -0
  27. package/lib/storage/memory.d.ts +28 -2
  28. package/lib/storage/memory.js +375 -101
  29. package/lib/transport/rpc.js +2 -0
  30. package/lib/types.d.ts +26 -0
  31. package/package.json +24 -23
  32. package/prompts/group-compression.md +5 -0
  33. package/prompts/group-reflection.md +39 -0
  34. package/prompts/member-batch-reflection.md +39 -0
  35. package/prompts/member-compression.md +5 -0
  36. package/prompts/private-reflection.md +32 -0
  37. package/src/config.ts +3 -2
  38. package/src/index.ts +2 -3
  39. package/src/session/agent-bridge.ts +40 -47
  40. package/src/session/memory-snapshot.ts +6 -3
  41. package/src/session/runtime.ts +14 -4
  42. package/src/storage/db.ts +70 -0
  43. package/src/storage/memory.ts +343 -84
  44. package/src/transport/rpc.ts +1 -0
  45. package/src/types.ts +30 -0
package/src/storage/db.ts CHANGED
@@ -27,9 +27,11 @@ import type {
27
27
  QQQuoteIndexRow,
28
28
  QQQuoteInput,
29
29
  StoredAttachmentSummary,
30
+ ReflectionTaskInput,
30
31
  } from '../types.js'
31
32
 
32
33
  const now = (): number => Date.now()
34
+ const REFLECTION_TASK_STALE_MS = 15 * 60 * 1000
33
35
 
34
36
  function parseStringList(value: string | null | undefined): string[] {
35
37
  if (!value) return []
@@ -233,8 +235,23 @@ export class QQChatDatabase {
233
235
  CREATE TABLE IF NOT EXISTS reflection_state (
234
236
  group_id INTEGER PRIMARY KEY REFERENCES groups(id) ON DELETE CASCADE,
235
237
  last_message_id INTEGER NOT NULL DEFAULT 0,
238
+ last_member_reflected_message_id INTEGER NOT NULL DEFAULT 0,
236
239
  last_reflected_at INTEGER
237
240
  );
241
+ CREATE TABLE IF NOT EXISTS reflection_tasks (
242
+ id INTEGER PRIMARY KEY,
243
+ scope_type TEXT NOT NULL CHECK(scope_type IN ('group','member','private')),
244
+ scope_key INTEGER NOT NULL,
245
+ task_type TEXT NOT NULL CHECK(task_type IN ('group','member-batch','private-owner')),
246
+ start_message_id INTEGER NOT NULL,
247
+ end_message_id INTEGER NOT NULL,
248
+ status TEXT NOT NULL CHECK(status IN ('running','completed','failed')),
249
+ attempts INTEGER NOT NULL DEFAULT 0,
250
+ idempotency_key TEXT NOT NULL UNIQUE,
251
+ created_at INTEGER NOT NULL,
252
+ updated_at INTEGER NOT NULL
253
+ );
254
+ CREATE INDEX IF NOT EXISTS reflection_tasks_scope ON reflection_tasks(scope_type, scope_key, task_type, status);
238
255
  CREATE TABLE IF NOT EXISTS outbox (
239
256
  id INTEGER PRIMARY KEY,
240
257
  account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
@@ -247,6 +264,8 @@ export class QQChatDatabase {
247
264
  error TEXT
248
265
  );
249
266
  `)
267
+ this.db.prepare("UPDATE reflection_tasks SET status='failed',updated_at=? WHERE status='running' AND updated_at<?")
268
+ .run(now(), now() - REFLECTION_TASK_STALE_MS)
250
269
  const columns = new Set(many<{ name: string }>(this.db.prepare('PRAGMA table_info(group_members)').all()).map(column => column.name))
251
270
  const groupColumns = new Set(many<{ name: string }>(this.db.prepare('PRAGMA table_info(groups)').all()).map(column => column.name))
252
271
  if (groupColumns.has('requires_at')) this.db.exec('ALTER TABLE groups DROP COLUMN requires_at')
@@ -254,6 +273,10 @@ export class QQChatDatabase {
254
273
  if (!columns.has('nicknames_json')) this.db.exec("ALTER TABLE group_members ADD COLUMN nicknames_json TEXT NOT NULL DEFAULT '[]'")
255
274
  if (!columns.has('message_count')) this.db.exec('ALTER TABLE group_members ADD COLUMN message_count INTEGER NOT NULL DEFAULT 0')
256
275
  const messageColumns = new Set(many<{ name: string }>(this.db.prepare('PRAGMA table_info(messages)').all()).map(column => column.name))
276
+ const reflectionColumns = new Set(many<{ name: string }>(this.db.prepare('PRAGMA table_info(reflection_state)').all()).map(column => column.name))
277
+ if (!reflectionColumns.has('last_member_reflected_message_id')) {
278
+ this.db.exec('ALTER TABLE reflection_state ADD COLUMN last_member_reflected_message_id INTEGER NOT NULL DEFAULT 0')
279
+ }
257
280
  if (!messageColumns.has('attachments_json')) this.db.exec('ALTER TABLE messages ADD COLUMN attachments_json TEXT')
258
281
  if (!messageColumns.has('quote_json')) this.db.exec('ALTER TABLE messages ADD COLUMN quote_json TEXT')
259
282
  this.db.exec(`UPDATE group_members SET message_count=(
@@ -375,6 +398,7 @@ export class QQChatDatabase {
375
398
  runtimeSettings(defaults: QQChatRuntimeSettings): QQChatRuntimeSettings {
376
399
  return {
377
400
  memoryEnabled: this.getSetting('memoryEnabled', defaults.memoryEnabled),
401
+ memoryMemberBatchEnabled: this.getSetting('memoryMemberBatchEnabled', defaults.memoryMemberBatchEnabled),
378
402
  groupReceiveMode: this.getSetting('groupReceiveMode', defaults.groupReceiveMode),
379
403
  groupReplyFormat: this.getSetting('groupReplyFormat', defaults.groupReplyFormat),
380
404
  directReplyFormat: this.getSetting('directReplyFormat', this.getSetting('replyFormat', defaults.directReplyFormat)),
@@ -690,12 +714,58 @@ export class QQChatDatabase {
690
714
  return Number(row?.n || 0)
691
715
  }
692
716
 
717
+ unreflectedMemberMessages(groupId: number, limit = 80): MessageRow[] {
718
+ const state = one<{ last_member_reflected_message_id: number }>(this.db.prepare(
719
+ 'SELECT last_member_reflected_message_id FROM reflection_state WHERE group_id=?',
720
+ ).get(groupId))
721
+ const after = Number(state?.last_member_reflected_message_id || 0)
722
+ return many<MessageRow>(this.db.prepare(`SELECT m.*,mem.platform_user_id,mem.display_name
723
+ FROM messages m LEFT JOIN members mem ON mem.id=m.member_id
724
+ WHERE m.group_id=? AND m.id>? ORDER BY m.id ASC LIMIT ?`).all(groupId, after, limit))
725
+ }
726
+
727
+ unreflectedMemberCount(groupId: number): number {
728
+ const state = one<{ last_member_reflected_message_id: number }>(this.db.prepare(
729
+ 'SELECT last_member_reflected_message_id FROM reflection_state WHERE group_id=?',
730
+ ).get(groupId))
731
+ const row = one<{ n: number }>(this.db.prepare(
732
+ 'SELECT COUNT(*) AS n FROM messages WHERE group_id=? AND id>?',
733
+ ).get(groupId, Number(state?.last_member_reflected_message_id || 0)))
734
+ return Number(row?.n || 0)
735
+ }
736
+
693
737
  markReflected(groupId: number, messageId: number): void {
694
738
  this.db.prepare(`INSERT INTO reflection_state(group_id,last_message_id,last_reflected_at) VALUES(?,?,?)
695
739
  ON CONFLICT(group_id) DO UPDATE SET last_message_id=excluded.last_message_id,last_reflected_at=excluded.last_reflected_at`)
696
740
  .run(groupId, messageId, now())
697
741
  }
698
742
 
743
+ markMembersReflected(groupId: number, messageId: number): void {
744
+ this.db.prepare(`INSERT INTO reflection_state(group_id,last_message_id,last_member_reflected_message_id,last_reflected_at) VALUES(?,?,?,?)
745
+ ON CONFLICT(group_id) DO UPDATE SET last_member_reflected_message_id=excluded.last_member_reflected_message_id,last_reflected_at=excluded.last_reflected_at`)
746
+ .run(groupId, 0, messageId, now())
747
+ }
748
+
749
+ startReflectionTask(input: ReflectionTaskInput): boolean {
750
+ const existing = one<{ id: number; status: string }>(this.db.prepare(
751
+ 'SELECT id,status FROM reflection_tasks WHERE idempotency_key=?',
752
+ ).get(input.idempotencyKey))
753
+ if (existing?.status === 'completed' || existing?.status === 'running') return false
754
+ if (existing) {
755
+ this.db.prepare(`UPDATE reflection_tasks SET status='running',attempts=attempts+1,updated_at=? WHERE id=?`)
756
+ .run(now(), existing.id)
757
+ return true
758
+ }
759
+ this.db.prepare(`INSERT INTO reflection_tasks(scope_type,scope_key,task_type,start_message_id,end_message_id,status,attempts,idempotency_key,created_at,updated_at)
760
+ VALUES(?,?,?,?,?,'running',1,?,?,?)`)
761
+ .run(input.scopeType, input.scopeKey, input.taskType, input.startMessageId, input.endMessageId, input.idempotencyKey, now(), now())
762
+ return true
763
+ }
764
+
765
+ finishReflectionTask(idempotencyKey: string, status: 'completed' | 'failed'): void {
766
+ this.db.prepare('UPDATE reflection_tasks SET status=?,updated_at=? WHERE idempotency_key=?').run(status, now(), idempotencyKey)
767
+ }
768
+
699
769
  memoryDocs(scopeType: MemoryScopeType, scopeKey: number): MemoryDocuments {
700
770
  const rows = many<{ doc_type: MemoryDocType; content: string; updated_at: number }>(
701
771
  this.db.prepare('SELECT doc_type,content,updated_at FROM memory_documents WHERE scope_type=? AND scope_key=?')