@lemoncat7/dsh-knowledge 2.3.3 → 2.3.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 (86) hide show
  1. package/README.md +5 -3
  2. package/docs/architecture.md +2 -0
  3. package/docs/releases/2.3.5.md +26 -0
  4. package/docs/releases/2.3.6.md +17 -0
  5. package/docs/ui-maintenance.md +97 -0
  6. package/lib/api.d.ts.map +1 -1
  7. package/lib/api.js +20 -1
  8. package/lib/api.js.map +1 -1
  9. package/lib/client.d.ts.map +1 -1
  10. package/lib/client.js +357 -206
  11. package/lib/client.js.map +4 -4
  12. package/lib/design-tokens.d.ts +12 -0
  13. package/lib/design-tokens.d.ts.map +1 -0
  14. package/lib/design-tokens.js +58 -0
  15. package/lib/design-tokens.js.map +1 -0
  16. package/lib/extraction.d.ts.map +1 -1
  17. package/lib/extraction.js +2 -1
  18. package/lib/extraction.js.map +1 -1
  19. package/lib/knowledge-activity-controller.d.ts +3 -8
  20. package/lib/knowledge-activity-controller.d.ts.map +1 -1
  21. package/lib/knowledge-activity-controller.js +19 -7
  22. package/lib/knowledge-activity-controller.js.map +1 -1
  23. package/lib/knowledge-activity-notes.d.ts.map +1 -1
  24. package/lib/knowledge-activity-notes.js +19 -10
  25. package/lib/knowledge-activity-notes.js.map +1 -1
  26. package/lib/knowledge-activity-panel.d.ts.map +1 -1
  27. package/lib/knowledge-activity-panel.js +33 -21
  28. package/lib/knowledge-activity-panel.js.map +1 -1
  29. package/lib/knowledge-activity-presentation.d.ts +14 -0
  30. package/lib/knowledge-activity-presentation.d.ts.map +1 -0
  31. package/lib/knowledge-activity-presentation.js +77 -0
  32. package/lib/knowledge-activity-presentation.js.map +1 -0
  33. package/lib/knowledge-activity-state.d.ts +17 -0
  34. package/lib/knowledge-activity-state.d.ts.map +1 -0
  35. package/lib/knowledge-activity-state.js +14 -0
  36. package/lib/knowledge-activity-state.js.map +1 -0
  37. package/lib/latest-request.d.ts +7 -0
  38. package/lib/latest-request.d.ts.map +1 -0
  39. package/lib/latest-request.js +14 -0
  40. package/lib/latest-request.js.map +1 -0
  41. package/lib/local-provider.d.ts +0 -2
  42. package/lib/local-provider.d.ts.map +1 -1
  43. package/lib/local-provider.js +2 -323
  44. package/lib/local-provider.js.map +1 -1
  45. package/lib/notes/share-page-styles.d.ts +2 -0
  46. package/lib/notes/share-page-styles.d.ts.map +1 -0
  47. package/lib/notes/share-page-styles.js +11 -0
  48. package/lib/notes/share-page-styles.js.map +1 -0
  49. package/lib/notes/share-page.d.ts.map +1 -1
  50. package/lib/notes/share-page.js +1 -8
  51. package/lib/notes/share-page.js.map +1 -1
  52. package/lib/remote-provider.d.ts.map +1 -1
  53. package/lib/remote-provider.js +41 -6
  54. package/lib/remote-provider.js.map +1 -1
  55. package/lib/search-query.d.ts +2 -0
  56. package/lib/search-query.d.ts.map +1 -0
  57. package/lib/search-query.js +50 -0
  58. package/lib/search-query.js.map +1 -0
  59. package/lib/storage/migrations.d.ts +5 -0
  60. package/lib/storage/migrations.d.ts.map +1 -0
  61. package/lib/storage/migrations.js +325 -0
  62. package/lib/storage/migrations.js.map +1 -0
  63. package/lib/theme-bridge.d.ts +9 -7
  64. package/lib/theme-bridge.d.ts.map +1 -1
  65. package/lib/theme-bridge.js +3 -23
  66. package/lib/theme-bridge.js.map +1 -1
  67. package/lib/web-note-editor-selection.d.ts.map +1 -1
  68. package/lib/web-note-editor-selection.js +50 -15
  69. package/lib/web-note-editor-selection.js.map +1 -1
  70. package/lib/web-note-editor.d.ts.map +1 -1
  71. package/lib/web-note-editor.js +15 -1
  72. package/lib/web-note-editor.js.map +1 -1
  73. package/lib/web.d.ts.map +1 -1
  74. package/lib/web.js +4 -0
  75. package/lib/web.js.map +1 -1
  76. package/package.json +1 -1
  77. package/web/app.js +78 -176
  78. package/web/design-tokens.css +1 -0
  79. package/web/dialogs.js +94 -0
  80. package/web/document-actions.js +68 -0
  81. package/web/index.html +1 -0
  82. package/web/model-catalog.js +22 -0
  83. package/web/note-editor.js +57 -57
  84. package/web/select-control.js +193 -0
  85. package/web/styles.css +93 -185
  86. package/web/ui-primitives.js +5 -0
@@ -3,6 +3,7 @@ import { createHash, randomBytes, randomUUID } from 'node:crypto';
3
3
  import { mkdirSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { DatabaseSync } from 'node:sqlite';
6
+ import { migrateKnowledgeDatabase } from './storage/migrations.js';
6
7
  import { contentHash, DEFAULT_KNOWLEDGE_BASE_ID, newId, normalizeDraft, normalizeKnowledgeBaseDraft, normalizeKnowledgeMountDraft, normalizeKnowledgeSettings, nowIso, } from './domain.js';
7
8
  import { markdownHash, renderKnowledgeMarkdown } from './documents/markdown.js';
8
9
  import { knowledgeDocumentPath } from './documents/path.js';
@@ -41,331 +42,9 @@ export class LocalKnowledgeProvider {
41
42
  this.documentStore = new KnowledgeDocumentStore(join(storageRoot, 'documents'));
42
43
  this.db = new DatabaseSync(path);
43
44
  this.db.exec('PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000;');
44
- this.migrate();
45
+ migrateKnowledgeDatabase(this.db, this.notes);
45
46
  this.documentsReady = this.enqueueDocumentSync(() => this.syncAllDocuments());
46
47
  }
47
- migrate() {
48
- let version = Number(this.db.prepare('PRAGMA user_version').get().user_version ?? 0);
49
- if (version > 13)
50
- throw new Error(`knowledge database schema ${version} is newer than this plugin supports`);
51
- if (version === 0)
52
- this.db.exec(`
53
- BEGIN IMMEDIATE;
54
- CREATE TABLE knowledge_entries (
55
- id TEXT PRIMARY KEY,
56
- title TEXT NOT NULL,
57
- body TEXT NOT NULL,
58
- type TEXT NOT NULL CHECK(type IN ('preference','fact','decision','procedure','lesson')),
59
- tags_json TEXT NOT NULL,
60
- scope_kind TEXT NOT NULL CHECK(scope_kind IN ('global','project')),
61
- scope_id TEXT,
62
- confidence REAL NOT NULL CHECK(confidence >= 0 AND confidence <= 1),
63
- status TEXT NOT NULL CHECK(status IN ('active','archived')),
64
- version INTEGER NOT NULL,
65
- content_hash TEXT NOT NULL,
66
- source_json TEXT,
67
- created_at TEXT NOT NULL,
68
- updated_at TEXT NOT NULL,
69
- CHECK((scope_kind = 'global' AND scope_id IS NULL) OR (scope_kind = 'project' AND length(scope_id) > 0))
70
- );
71
- CREATE INDEX knowledge_entries_scope_status ON knowledge_entries(status, scope_kind, scope_id, updated_at DESC);
72
- CREATE INDEX knowledge_entries_type ON knowledge_entries(type, status);
73
- CREATE UNIQUE INDEX knowledge_entries_active_hash ON knowledge_entries(content_hash) WHERE status = 'active';
74
-
75
- CREATE TABLE knowledge_versions (
76
- id TEXT PRIMARY KEY,
77
- knowledge_id TEXT NOT NULL REFERENCES knowledge_entries(id) ON DELETE CASCADE,
78
- version INTEGER NOT NULL,
79
- snapshot_json TEXT NOT NULL,
80
- change_kind TEXT NOT NULL CHECK(change_kind IN ('create','update','archive','restore')),
81
- created_at TEXT NOT NULL,
82
- UNIQUE(knowledge_id, version)
83
- );
84
-
85
- CREATE TABLE knowledge_candidates (
86
- id TEXT PRIMARY KEY,
87
- action TEXT NOT NULL CHECK(action IN ('create','update','conflict')),
88
- target_id TEXT,
89
- draft_json TEXT NOT NULL,
90
- reason TEXT NOT NULL,
91
- status TEXT NOT NULL CHECK(status IN ('pending','approved','rejected')),
92
- source_key TEXT,
93
- proposal_hash TEXT NOT NULL,
94
- created_at TEXT NOT NULL,
95
- reviewed_at TEXT,
96
- review_note TEXT
97
- );
98
- CREATE UNIQUE INDEX knowledge_candidates_dedupe ON knowledge_candidates(source_key, proposal_hash) WHERE source_key IS NOT NULL;
99
- CREATE INDEX knowledge_candidates_status ON knowledge_candidates(status, created_at DESC);
100
-
101
- CREATE TABLE extraction_jobs (
102
- source_key TEXT PRIMARY KEY,
103
- status TEXT NOT NULL CHECK(status IN ('running','completed','failed')),
104
- attempts INTEGER NOT NULL,
105
- candidate_count INTEGER NOT NULL DEFAULT 0,
106
- last_error TEXT,
107
- updated_at TEXT NOT NULL
108
- );
109
-
110
- CREATE TABLE api_tokens (
111
- id TEXT PRIMARY KEY,
112
- name TEXT NOT NULL,
113
- token_hash TEXT NOT NULL UNIQUE,
114
- permissions_json TEXT NOT NULL,
115
- created_at TEXT NOT NULL,
116
- last_used_at TEXT,
117
- revoked_at TEXT
118
- );
119
-
120
- CREATE VIRTUAL TABLE knowledge_fts USING fts5(
121
- knowledge_id UNINDEXED,
122
- title,
123
- body,
124
- tags,
125
- tokenize = 'unicode61 remove_diacritics 2'
126
- );
127
- PRAGMA user_version = 1;
128
- COMMIT;
129
- `);
130
- if (version === 0)
131
- version = 1;
132
- if (version === 1)
133
- this.db.exec(`
134
- BEGIN IMMEDIATE;
135
- CREATE TABLE knowledge_bases (
136
- id TEXT PRIMARY KEY,
137
- name TEXT NOT NULL,
138
- description TEXT NOT NULL,
139
- default_tags_json TEXT NOT NULL,
140
- extraction_instructions TEXT NOT NULL,
141
- status TEXT NOT NULL CHECK(status IN ('active','archived')),
142
- created_at TEXT NOT NULL,
143
- updated_at TEXT NOT NULL
144
- );
145
- INSERT INTO knowledge_bases(
146
- id,name,description,default_tags_json,extraction_instructions,status,created_at,updated_at
147
- ) VALUES(
148
- 'default','默认知识库','','[]','仅收录可跨会话复用、且与当前挂载范围相关的知识。','active',datetime('now'),datetime('now')
149
- );
150
- ALTER TABLE knowledge_entries ADD COLUMN knowledge_base_id TEXT NOT NULL DEFAULT 'default';
151
- CREATE INDEX knowledge_entries_base_status ON knowledge_entries(knowledge_base_id, status, updated_at DESC);
152
- CREATE TABLE knowledge_mounts (
153
- id TEXT PRIMARY KEY,
154
- target_kind TEXT NOT NULL CHECK(target_kind IN ('project','session')),
155
- target_id TEXT NOT NULL,
156
- knowledge_base_id TEXT NOT NULL REFERENCES knowledge_bases(id) ON DELETE CASCADE,
157
- enabled INTEGER NOT NULL CHECK(enabled IN (0,1)),
158
- recall_enabled INTEGER NOT NULL CHECK(recall_enabled IN (0,1)),
159
- write_mode TEXT NOT NULL CHECK(write_mode IN ('none','audit','direct')),
160
- include_tags_json TEXT NOT NULL,
161
- exclude_tags_json TEXT NOT NULL,
162
- extraction_instructions TEXT NOT NULL,
163
- created_at TEXT NOT NULL,
164
- updated_at TEXT NOT NULL,
165
- UNIQUE(target_kind, target_id, knowledge_base_id)
166
- );
167
- CREATE INDEX knowledge_mounts_target ON knowledge_mounts(target_kind, target_id, enabled);
168
- PRAGMA user_version = 2;
169
- COMMIT;
170
- `);
171
- if (version <= 1)
172
- version = 2;
173
- if (version === 2)
174
- this.db.exec(`
175
- BEGIN IMMEDIATE;
176
- ALTER TABLE knowledge_bases ADD COLUMN writeback_provider TEXT;
177
- ALTER TABLE knowledge_bases ADD COLUMN writeback_model TEXT;
178
- PRAGMA user_version = 3;
179
- COMMIT;
180
- `);
181
- if (version <= 2)
182
- version = 3;
183
- if (version === 3)
184
- this.db.exec(`
185
- BEGIN IMMEDIATE;
186
- CREATE TABLE knowledge_documents (
187
- id TEXT PRIMARY KEY,
188
- knowledge_base_id TEXT NOT NULL REFERENCES knowledge_bases(id) ON DELETE CASCADE,
189
- rel_path TEXT NOT NULL,
190
- title TEXT NOT NULL,
191
- content TEXT NOT NULL,
192
- entry_count INTEGER NOT NULL CHECK(entry_count >= 0),
193
- content_hash TEXT NOT NULL,
194
- created_at TEXT NOT NULL,
195
- updated_at TEXT NOT NULL,
196
- UNIQUE(knowledge_base_id, rel_path)
197
- );
198
- CREATE INDEX knowledge_documents_base_updated ON knowledge_documents(knowledge_base_id, updated_at DESC);
199
- PRAGMA user_version = 4;
200
- COMMIT;
201
- `);
202
- if (version <= 3)
203
- version = 4;
204
- if (version === 4)
205
- this.db.exec(`
206
- BEGIN IMMEDIATE;
207
- CREATE TABLE knowledge_settings (
208
- id INTEGER PRIMARY KEY CHECK(id = 1),
209
- writeback_policy TEXT NOT NULL CHECK(writeback_policy IN ('conservative','proactive')),
210
- updated_at TEXT NOT NULL
211
- );
212
- INSERT INTO knowledge_settings(id,writeback_policy,updated_at)
213
- VALUES(1,'conservative',datetime('now'));
214
- PRAGMA user_version = 5;
215
- COMMIT;
216
- `);
217
- if (version <= 4)
218
- version = 5;
219
- if (version === 5)
220
- this.db.exec('PRAGMA user_version = 6;');
221
- if (version <= 5)
222
- version = 6;
223
- if (version === 6)
224
- this.db.exec(`
225
- BEGIN IMMEDIATE;
226
- ALTER TABLE knowledge_entries ADD COLUMN document_state TEXT NOT NULL DEFAULT 'open'
227
- CHECK(document_state IN ('open','resolved','complete'));
228
- ALTER TABLE knowledge_entries ADD COLUMN finalized_at TEXT;
229
- ALTER TABLE knowledge_entries ADD COLUMN finalization_note TEXT;
230
- ALTER TABLE knowledge_documents ADD COLUMN document_state TEXT NOT NULL DEFAULT 'open'
231
- CHECK(document_state IN ('open','resolved','complete'));
232
- ALTER TABLE knowledge_documents ADD COLUMN finalized_at TEXT;
233
- ALTER TABLE knowledge_documents ADD COLUMN finalization_note TEXT;
234
- PRAGMA user_version = 7;
235
- COMMIT;
236
- `);
237
- if (version <= 6)
238
- version = 7;
239
- if (version === 7)
240
- this.db.exec(`
241
- BEGIN IMMEDIATE;
242
- ALTER TABLE knowledge_bases ADD COLUMN writeback_policy TEXT NOT NULL DEFAULT 'conservative'
243
- CHECK(writeback_policy IN ('conservative','proactive'));
244
- UPDATE knowledge_bases SET writeback_policy=(SELECT writeback_policy FROM knowledge_settings WHERE id=1);
245
- PRAGMA user_version = 8;
246
- COMMIT;
247
- `);
248
- if (version <= 7)
249
- version = 8;
250
- if (version === 8)
251
- this.db.exec(`
252
- BEGIN IMMEDIATE;
253
- ALTER TABLE knowledge_settings ADD COLUMN writeback_provider TEXT;
254
- ALTER TABLE knowledge_settings ADD COLUMN writeback_model TEXT;
255
- PRAGMA user_version = 9;
256
- COMMIT;
257
- `);
258
- if (version <= 8)
259
- version = 9;
260
- if (version === 9) {
261
- this.db.exec('BEGIN IMMEDIATE');
262
- try {
263
- this.db.exec(`
264
- CREATE TABLE knowledge_note_references (
265
- knowledge_id TEXT NOT NULL REFERENCES knowledge_entries(id) ON DELETE CASCADE,
266
- note_id TEXT NOT NULL,
267
- source TEXT NOT NULL CHECK(source IN ('user','agent','legacy')),
268
- source_session_id TEXT,
269
- created_at TEXT NOT NULL,
270
- PRIMARY KEY(knowledge_id, note_id)
271
- );
272
- CREATE INDEX knowledge_note_references_note ON knowledge_note_references(note_id, created_at DESC);
273
- `);
274
- this.backfillLegacyNoteReferences();
275
- this.db.exec('PRAGMA user_version = 10; COMMIT');
276
- }
277
- catch (error) {
278
- this.db.exec('ROLLBACK');
279
- throw error;
280
- }
281
- }
282
- if (version <= 9)
283
- version = 10;
284
- if (version === 10) {
285
- this.db.exec('BEGIN IMMEDIATE');
286
- try {
287
- const candidateTable = this.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='knowledge_candidates'").get();
288
- const candidateColumns = candidateTable === undefined
289
- ? []
290
- : this.db.prepare('PRAGMA table_info(knowledge_candidates)').all();
291
- if (candidateTable !== undefined && !candidateColumns.some(column => String(column.name) === 'change_json')) {
292
- this.db.exec('ALTER TABLE knowledge_candidates ADD COLUMN change_json TEXT');
293
- }
294
- this.db.exec('PRAGMA user_version = 11; COMMIT');
295
- }
296
- catch (error) {
297
- this.db.exec('ROLLBACK');
298
- throw error;
299
- }
300
- }
301
- if (version <= 10)
302
- version = 11;
303
- if (version === 11)
304
- this.db.exec(`
305
- BEGIN IMMEDIATE;
306
- CREATE INDEX IF NOT EXISTS knowledge_documents_index_order ON knowledge_documents(
307
- knowledge_base_id,
308
- (CASE WHEN rel_path='README.md' THEN 0 ELSE 1 END),
309
- rel_path,
310
- id
311
- );
312
- PRAGMA user_version = 12;
313
- COMMIT;
314
- `);
315
- if (version <= 11)
316
- version = 12;
317
- if (version === 12) {
318
- this.db.exec('BEGIN IMMEDIATE');
319
- try {
320
- const extractionTable = this.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='extraction_jobs'").get();
321
- if (extractionTable === undefined) {
322
- this.db.exec(`
323
- CREATE TABLE extraction_jobs (
324
- source_key TEXT PRIMARY KEY,
325
- status TEXT NOT NULL CHECK(status IN ('running','completed','failed')),
326
- attempts INTEGER NOT NULL,
327
- candidate_count INTEGER NOT NULL DEFAULT 0,
328
- last_error TEXT,
329
- completion_json TEXT,
330
- updated_at TEXT NOT NULL
331
- )
332
- `);
333
- }
334
- else {
335
- const extractionColumns = this.db.prepare('PRAGMA table_info(extraction_jobs)').all();
336
- if (!extractionColumns.some(column => String(column.name) === 'completion_json')) {
337
- this.db.exec('ALTER TABLE extraction_jobs ADD COLUMN completion_json TEXT');
338
- }
339
- }
340
- this.db.exec('PRAGMA user_version = 13; COMMIT');
341
- }
342
- catch (error) {
343
- this.db.exec('ROLLBACK');
344
- throw error;
345
- }
346
- }
347
- // Alpha v2 used a migration note as the default base's routing description.
348
- // Clear only that exact placeholder so existing user-authored descriptions stay untouched.
349
- this.db.prepare("UPDATE knowledge_bases SET description='' WHERE id=? AND description=?")
350
- .run(DEFAULT_KNOWLEDGE_BASE_ID, '由 0.2 版本迁移的知识。');
351
- }
352
- backfillLegacyNoteReferences() {
353
- const insert = this.db.prepare(`
354
- INSERT OR IGNORE INTO knowledge_note_references(knowledge_id,note_id,source,source_session_id,created_at)
355
- VALUES(?,?,'legacy',NULL,?)
356
- `);
357
- const timestamp = nowIso();
358
- const rows = this.db.prepare("SELECT id,body FROM knowledge_entries WHERE body LIKE '%note://note_%'").all();
359
- for (const row of rows) {
360
- const noteIds = String(row.body).match(/note:\/\/(note_[a-f0-9]{32})/giu) ?? [];
361
- for (const reference of new Set(noteIds)) {
362
- const noteId = reference.slice('note://'.length).toLocaleLowerCase();
363
- const note = this.notes.get(noteId);
364
- if (note !== undefined && note.kind !== 'folder')
365
- insert.run(String(row.id), note.id, timestamp);
366
- }
367
- }
368
- }
369
48
  assertOpen() {
370
49
  if (this.closed)
371
50
  throw new Error('knowledge provider is closed');