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.
- package/README.en.md +5 -4
- package/README.md +3 -3
- package/client-src/plugin.cts +2 -2
- package/client-src/settings.cts +18 -1
- package/client-src/shared.cts +4 -4
- package/cordis.patch.yml +5 -0
- package/docs/ARCHITECTURE.en.md +1 -1
- package/docs/ARCHITECTURE.md +2 -1
- package/docs/DEVELOPMENT.md +3 -1
- package/docs/MEMORY.md +32 -4
- package/docs/MEMORY_CONTEXT.md +16 -0
- package/docs/README.md +2 -0
- package/docs/STREAMING.md +2 -2
- 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
- package/docs/prds/README.md +62 -0
- package/lib/client.js +21 -4
- package/lib/config.js +3 -2
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -3
- package/lib/session/agent-bridge.d.ts +3 -4
- package/lib/session/agent-bridge.js +40 -46
- package/lib/session/memory-snapshot.d.ts +4 -0
- package/lib/session/memory-snapshot.js +6 -2
- package/lib/session/runtime.js +15 -5
- package/lib/storage/db.d.ts +6 -1
- package/lib/storage/db.js +57 -0
- package/lib/storage/memory.d.ts +28 -2
- package/lib/storage/memory.js +375 -101
- package/lib/transport/rpc.js +2 -0
- package/lib/types.d.ts +26 -0
- package/package.json +24 -23
- package/prompts/group-compression.md +5 -0
- package/prompts/group-reflection.md +39 -0
- package/prompts/member-batch-reflection.md +39 -0
- package/prompts/member-compression.md +5 -0
- package/prompts/private-reflection.md +32 -0
- package/src/config.ts +3 -2
- package/src/index.ts +2 -3
- package/src/session/agent-bridge.ts +40 -47
- package/src/session/memory-snapshot.ts +6 -3
- package/src/session/runtime.ts +14 -4
- package/src/storage/db.ts +70 -0
- package/src/storage/memory.ts +343 -84
- package/src/transport/rpc.ts +1 -0
- 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=?')
|