@xopcai/xopc 0.0.307 → 0.0.308

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 (165) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CZeQo09T.js → agent-avatar-dicebear-dUotwUO6.js} +1 -1
  4. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-C6LflMXr.js +1 -0
  5. package/dist/gateway/static/root/assets/{agents-BlrHMbRx.js → agents-VPDIGRSS.js} +1 -1
  6. package/dist/gateway/static/root/assets/{app-management-settings-panel-Bzw4fQP8.js → app-management-settings-panel-zPz0rOND.js} +1 -1
  7. package/dist/gateway/static/root/assets/{appearance-settings-8GZpUXva.js → appearance-settings-DhvhR43G.js} +1 -1
  8. package/dist/gateway/static/root/assets/{apps-page-BkQQtIxH.js → apps-page-Pmslbzzr.js} +1 -1
  9. package/dist/gateway/static/root/assets/{archive-plugin-CQ2NaoHb.js → archive-plugin-BQVZMreT.js} +1 -1
  10. package/dist/gateway/static/root/assets/{attachment-load-BFi1H7yJ.js → attachment-load-CPE-TMPf.js} +1 -1
  11. package/dist/gateway/static/root/assets/{automations-page-CDBXcii_.js → automations-page-C4IAy_zF.js} +1 -1
  12. package/dist/gateway/static/root/assets/automations-workspace-Fx5VUJRu.js +6 -0
  13. package/dist/gateway/static/root/assets/{binary-plugins-D07eWmOx.js → binary-plugins-DgTl7diZ.js} +1 -1
  14. package/dist/gateway/static/root/assets/{block-editor-CtVnjyYU.js → block-editor-CcOVA-Gw.js} +3 -3
  15. package/dist/gateway/static/root/assets/{browser-automations-page-DyrSbDdG.js → browser-automations-page--zKAgj1k.js} +1 -1
  16. package/dist/gateway/static/root/assets/browser-settings-page-DBms6d35.js +3 -0
  17. package/dist/gateway/static/root/assets/capabilities-settings-panel-epdDWIZy.js +8 -0
  18. package/dist/gateway/static/root/assets/{channels-settings-DAD9qE3J.js → channels-settings-B0bsDtmP.js} +1 -1
  19. package/dist/gateway/static/root/assets/{chat-terminal-dock-B62SZdAo.js → chat-terminal-dock-BfLd2rIJ.js} +1 -1
  20. package/dist/gateway/static/root/assets/{computer-settings-page-ClSresNq.js → computer-settings-page-Ckgc0Py-.js} +1 -1
  21. package/dist/gateway/static/root/assets/connector-service-page-BlC1OXX4.js +1 -0
  22. package/dist/gateway/static/root/assets/connectors-page-By33-bF1.js +2 -0
  23. package/dist/gateway/static/root/assets/{cron-expression-CTMi_tql.js → cron-expression-CqNE-nf6.js} +1 -1
  24. package/dist/gateway/static/root/assets/{date-picker-B71AJK50.js → date-picker-BS9ZjzYJ.js} +1 -1
  25. package/dist/gateway/static/root/assets/{dependency-picker-DlyGK-kA.js → dependency-picker-CF_zYzxX.js} +1 -1
  26. package/dist/gateway/static/root/assets/{desktop-pet-DGkbTE8_.js → desktop-pet-CDwtLR7Z.js} +1 -1
  27. package/dist/gateway/static/root/assets/{desktop-pet-settings-BY8r9j66.js → desktop-pet-settings-C0djIc1J.js} +1 -1
  28. package/dist/gateway/static/root/assets/{device-pairing-wizard-DzRrgd59.js → device-pairing-wizard-CDKj9LQj.js} +1 -1
  29. package/dist/gateway/static/root/assets/{extension-debug-page-L8Mm1n3A.js → extension-debug-page-CX5OXxEU.js} +1 -1
  30. package/dist/gateway/static/root/assets/{extension-page-CFc4vSNU.js → extension-page-BytR1PaY.js} +1 -1
  31. package/dist/gateway/static/root/assets/{extension-settings-page-B5zWyHr3.js → extension-settings-page-CkaMJA5F.js} +1 -1
  32. package/dist/gateway/static/root/assets/{gateway-settings-D7qmTH4R.js → gateway-settings-_YU-1p0z.js} +1 -1
  33. package/dist/gateway/static/root/assets/{heartbeat-settings-Glsl3X5b.js → heartbeat-settings-CVIvvm4x.js} +1 -1
  34. package/dist/gateway/static/root/assets/home-page-COCHelcL.js +1 -0
  35. package/dist/gateway/static/root/assets/{imports-page-ByBX7ome.js → imports-page-DrtbjL8O.js} +1 -1
  36. package/dist/gateway/static/root/assets/index-CDfcPiPb.js +79 -0
  37. package/dist/gateway/static/root/assets/index-CRu1p38D.css +1 -0
  38. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-C45RlO8a.js → keyboard-shortcuts-settings-BlZh41Xq.js} +1 -1
  39. package/dist/gateway/static/root/assets/{local-app-workbench-page-DP0x2jpR.js → local-app-workbench-page-D7_Raxjw.js} +2 -2
  40. package/dist/gateway/static/root/assets/{local-apps-page-C_AQc2bI.js → local-apps-page-DbP7wWuh.js} +1 -1
  41. package/dist/gateway/static/root/assets/{logs-page-DvM8Pyok.js → logs-page-C0n66FiO.js} +1 -1
  42. package/dist/gateway/static/root/assets/{management-settings-D1uwSutV.js → management-settings-i6GhxpGV.js} +1 -1
  43. package/dist/gateway/static/root/assets/{media-plugins-C4mhf13f.js → media-plugins-CnWChQ3Y.js} +1 -1
  44. package/dist/gateway/static/root/assets/note-detail-panel-C59d8vDW.js +5 -0
  45. package/dist/gateway/static/root/assets/notes-page-C1GBPrVI.js +8 -0
  46. package/dist/gateway/static/root/assets/{page-tabs-1go_BtX1.js → page-tabs-BWitjb3e.js} +1 -1
  47. package/dist/gateway/static/root/assets/{preview-open-alternatives-DmoSC0j1.js → preview-open-alternatives-UaCWCxZv.js} +1 -1
  48. package/dist/gateway/static/root/assets/{proactive-page-xAjKTIID.js → proactive-page-CtSFJxoM.js} +1 -1
  49. package/dist/gateway/static/root/assets/project-detail-page-ILTmQ1YN.js +1 -0
  50. package/dist/gateway/static/root/assets/projects-page-snl4rOQZ.js +1 -0
  51. package/dist/gateway/static/root/assets/remote-access-hub-BbaAcEN3.js +1 -0
  52. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-ClkOoxDl.js → runtime-tools-settings-panel-CtOjKGWg.js} +1 -1
  53. package/dist/gateway/static/root/assets/{schema-form-BuEmLiaU.js → schema-form-DkyyDcl2.js} +1 -1
  54. package/dist/gateway/static/root/assets/sessions-page-DrWL71O8.js +1 -0
  55. package/dist/gateway/static/root/assets/settings-advanced-gate-BG0kl4HT.js +1 -0
  56. package/dist/gateway/static/root/assets/{settings-form-section-Dor39QON.js → settings-form-section-C0JDGN_V.js} +1 -1
  57. package/dist/gateway/static/root/assets/{settings-page-Xj-yvIc8.js → settings-page-DiIyMQ52.js} +1 -1
  58. package/dist/gateway/static/root/assets/setup-status-panel-EPazhoZO.js +1 -0
  59. package/dist/gateway/static/root/assets/{share-preview-page-BFWZoXdm.js → share-preview-page-Bt1K657Y.js} +1 -1
  60. package/dist/gateway/static/root/assets/shares-settings-g2MdRXSZ.js +1 -0
  61. package/dist/gateway/static/root/assets/skill-reload-api-B1DPzu98.js +1 -0
  62. package/dist/gateway/static/root/assets/skills-page-Wd0uYSPG.js +2 -0
  63. package/dist/gateway/static/root/assets/{skills-page.utils-Ii8qeXhs.js → skills-page.utils-vq5Ujm03.js} +1 -1
  64. package/dist/gateway/static/root/assets/{system-settings-panel-Di6OSO-0.js → system-settings-panel-Cz1P-HDH.js} +1 -1
  65. package/dist/gateway/static/root/assets/task-detail-page-BG3A5gPZ.js +1 -0
  66. package/dist/gateway/static/root/assets/{tunnel-api-DDOlOYlj.js → tunnel-api-DQGoFuZO.js} +1 -1
  67. package/dist/gateway/static/root/assets/{use-autosave-CQ8uWNnB.js → use-autosave-DVDlBb95.js} +1 -1
  68. package/dist/gateway/static/root/assets/user-model-page-qM5UiTGx.js +2 -0
  69. package/dist/gateway/static/root/assets/{voice-api-key-field-COTzshjL.js → voice-api-key-field-B2pZCcxY.js} +1 -1
  70. package/dist/gateway/static/root/assets/{work-discovery-overlay-_URrXbi4.js → work-discovery-overlay-Bptv7bEY.js} +1 -1
  71. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-CALs-1nd.js → workflow-run-setup-panel-BijCrNc4.js} +2 -2
  72. package/dist/gateway/static/root/assets/workflows-page-DRy1RAYH.js +6 -0
  73. package/dist/gateway/static/root/index.html +2 -2
  74. package/dist/package.js +1 -1
  75. package/dist/packages/gateway-contract/src/notifications.js +1 -1
  76. package/dist/src/agent/background-review/prompts.js +3 -1
  77. package/dist/src/agent/context/execution-context.js +4 -4
  78. package/dist/src/agent/memory/compaction-promotion.d.ts +1 -1
  79. package/dist/src/agent/memory/compaction-promotion.js +4 -3
  80. package/dist/src/agent/tools/knowledge-memory-tool.d.ts +1 -1
  81. package/dist/src/agent/tools/knowledge-memory-tool.js +3 -3
  82. package/dist/src/agent/tools/user-context-tool.js +17 -7
  83. package/dist/src/chat-commands/builtins/understanding.js +1 -1
  84. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  85. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  86. package/dist/src/config/schema.d.ts +2 -4
  87. package/dist/src/config/schema.js +2 -2
  88. package/dist/src/connectors/connected-source-understanding.d.ts +1 -0
  89. package/dist/src/connectors/connected-source-understanding.js +1 -0
  90. package/dist/src/connectors/learning-coordinator.js +31 -9
  91. package/dist/src/gateway/hono/lib/config-payload.d.ts +2 -2
  92. package/dist/src/gateway/hono/routes/connectors.js +1 -1
  93. package/dist/src/gateway/hono/routes/context-sources.js +0 -23
  94. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  95. package/dist/src/gateway/hono/routes/projects.js +8 -0
  96. package/dist/src/gateway/hono/routes/user-model-refresh.d.ts +5 -0
  97. package/dist/src/gateway/hono/routes/user-model-refresh.js +128 -0
  98. package/dist/src/gateway/hono/routes/user-model.js +12 -5
  99. package/dist/src/gateway/hono/routes/work-discovery.js +0 -30
  100. package/dist/src/gateway/service.js +6 -6
  101. package/dist/src/infra/update-restart.js +1 -1
  102. package/dist/src/knowledge/connected-knowledge-pipeline.js +1 -1
  103. package/dist/src/knowledge-memory/index.js +2 -2
  104. package/dist/src/knowledge-memory/repository.d.ts +7 -0
  105. package/dist/src/knowledge-memory/repository.js +58 -8
  106. package/dist/src/memory-maintenance/service.js +61 -31
  107. package/dist/src/migrations/memory-config.d.ts +2 -0
  108. package/dist/src/migrations/memory-config.js +77 -0
  109. package/dist/src/migrations/runner.js +2 -2
  110. package/dist/src/notifications/planner.js +5 -5
  111. package/dist/src/notifications/service.d.ts +2 -2
  112. package/dist/src/notifications/service.js +1 -1
  113. package/dist/src/storage/sqlite/migrations/184_memory_suppressions.sql +12 -0
  114. package/dist/src/storage/sqlite/migrations/185_understanding_refresh_batches.sql +6 -0
  115. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  116. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  117. package/dist/src/storage/sqlite/schema.js +1 -1
  118. package/dist/src/user-context/config.d.ts +4 -9
  119. package/dist/src/user-context/config.js +7 -27
  120. package/dist/src/user-context/memory-suppression.d.ts +7 -0
  121. package/dist/src/user-context/memory-suppression.js +23 -0
  122. package/dist/src/user-context/relationship-continuity.js +1 -1
  123. package/dist/src/user-context/sources/refresh-service.d.ts +48 -0
  124. package/dist/src/user-context/sources/refresh-service.js +221 -0
  125. package/dist/src/user-context/sources/repository.js +1 -1
  126. package/dist/src/user-model/bootstrap.js +27 -24
  127. package/dist/src/user-model/capture/executor.d.ts +1 -1
  128. package/dist/src/user-model/capture/executor.js +17 -11
  129. package/dist/src/user-model/capture/semantic.js +1 -1
  130. package/dist/src/user-model/domain.d.ts +6 -2
  131. package/dist/src/user-model/index.js +3 -3
  132. package/dist/src/user-model/profile.js +1 -1
  133. package/dist/src/user-model/repository.d.ts +4 -1
  134. package/dist/src/user-model/repository.js +96 -6
  135. package/dist/src/user-model/usage-policy.d.ts +2 -0
  136. package/dist/src/user-model/usage-policy.js +6 -2
  137. package/dist/src/work-discovery/project-understanding.js +2 -1
  138. package/dist/src/work-discovery/service.d.ts +8 -24
  139. package/dist/src/work-discovery/service.js +138 -180
  140. package/package.json +3 -3
  141. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-BPsRb9Ck.js +0 -1
  142. package/dist/gateway/static/root/assets/automations-workspace-CUpiTTiY.js +0 -6
  143. package/dist/gateway/static/root/assets/browser-settings-page-CqTxLpil.js +0 -3
  144. package/dist/gateway/static/root/assets/capabilities-settings-panel-BZ_-pnS6.js +0 -8
  145. package/dist/gateway/static/root/assets/connector-service-page-CPqVmNvx.js +0 -1
  146. package/dist/gateway/static/root/assets/connectors-page-BUQyg3CW.js +0 -2
  147. package/dist/gateway/static/root/assets/home-page-s9hDQ8Hc.js +0 -1
  148. package/dist/gateway/static/root/assets/index-RBCkv8a_.js +0 -79
  149. package/dist/gateway/static/root/assets/index-cFU4S35z.css +0 -1
  150. package/dist/gateway/static/root/assets/note-detail-panel-D35sErp2.js +0 -5
  151. package/dist/gateway/static/root/assets/notes-page-8jCDklDn.js +0 -8
  152. package/dist/gateway/static/root/assets/project-detail-page-BUMQHy_N.js +0 -1
  153. package/dist/gateway/static/root/assets/projects-page-DT-Mt7XR.js +0 -1
  154. package/dist/gateway/static/root/assets/remote-access-hub-Clz3byir.js +0 -1
  155. package/dist/gateway/static/root/assets/sessions-page-tQRwktKm.js +0 -1
  156. package/dist/gateway/static/root/assets/settings-advanced-gate-DqWg9XS5.js +0 -1
  157. package/dist/gateway/static/root/assets/setup-status-panel-BB_sMB7P.js +0 -1
  158. package/dist/gateway/static/root/assets/shares-settings-oDALm4b6.js +0 -1
  159. package/dist/gateway/static/root/assets/skill-reload-api-DmwelnfK.js +0 -1
  160. package/dist/gateway/static/root/assets/skills-page-BE9JXz4U.js +0 -2
  161. package/dist/gateway/static/root/assets/task-detail-page-D1AWszc7.js +0 -1
  162. package/dist/gateway/static/root/assets/user-model-page-C00i4ea0.js +0 -2
  163. package/dist/gateway/static/root/assets/workflows-page-BUiBjz6m.js +0 -6
  164. package/dist/src/migrations/knowledge-memory-read-policy-config.d.ts +0 -2
  165. package/dist/src/migrations/knowledge-memory-read-policy-config.js +0 -48
@@ -1,2 +1,2 @@
1
- import { KnowledgeReviewConflictError, classifyKnowledgeContentSource, getKnowledgeItem, knowledgeItemAllowed, listKnowledgeItems, listKnowledgeStatusEvents, reviewKnowledgeItem, searchKnowledgeItems, transitionKnowledgeStatus, writeKnowledgeItem } from "./repository.js";
2
- export { KnowledgeReviewConflictError, classifyKnowledgeContentSource, getKnowledgeItem, knowledgeItemAllowed, listKnowledgeItems, listKnowledgeStatusEvents, reviewKnowledgeItem, searchKnowledgeItems, transitionKnowledgeStatus, writeKnowledgeItem };
1
+ import { KnowledgeReviewConflictError, classifyKnowledgeContentSource, deleteKnowledgeItem, getKnowledgeItem, isKnowledgeCurrent, knowledgeItemAllowed, listKnowledgeItems, listKnowledgeStatusEvents, reviewKnowledgeItem, searchKnowledgeItems, transitionKnowledgeStatus, writeKnowledgeItem } from "./repository.js";
2
+ export { KnowledgeReviewConflictError, classifyKnowledgeContentSource, deleteKnowledgeItem, getKnowledgeItem, isKnowledgeCurrent, knowledgeItemAllowed, listKnowledgeItems, listKnowledgeStatusEvents, reviewKnowledgeItem, searchKnowledgeItems, transitionKnowledgeStatus, writeKnowledgeItem };
@@ -21,6 +21,7 @@ export interface WriteKnowledgeInput {
21
21
  derivedFromRecalledContext?: boolean;
22
22
  source?: Record<string, unknown>;
23
23
  replaceExisting?: boolean;
24
+ restoreDeleted?: boolean;
24
25
  now?: number;
25
26
  }
26
27
  export declare class KnowledgeReviewConflictError extends Error {
@@ -29,6 +30,10 @@ export declare class KnowledgeReviewConflictError extends Error {
29
30
  export declare function writeKnowledgeItem(input: WriteKnowledgeInput): {
30
31
  item: KnowledgeItem;
31
32
  created: boolean;
33
+ } | {
34
+ item?: undefined;
35
+ created: false;
36
+ suppressed: true;
32
37
  };
33
38
  export declare function getKnowledgeItem(id: string): KnowledgeItem | undefined;
34
39
  export declare function listKnowledgeItems(input?: {
@@ -70,3 +75,5 @@ export declare function searchKnowledgeItems(input: {
70
75
  }): KnowledgeItem[];
71
76
  export declare function classifyKnowledgeContentSource(item: KnowledgeItem): KnowledgeContentSource;
72
77
  export declare function knowledgeItemAllowed(item: KnowledgeItem, policy: KnowledgeReadPolicy): boolean;
78
+ export declare function deleteKnowledgeItem(id: string, now?: number): boolean;
79
+ export declare function isKnowledgeCurrent(item: KnowledgeItem, asOf?: number): boolean;
@@ -1,6 +1,7 @@
1
1
  import { getSqliteDatabase, init_transaction, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
2
  import { validateScope } from "../user-model/domain.js";
3
3
  import { buildFts5SearchQuery, init_fts } from "../storage/sqlite/fts.js";
4
+ import { clearMemoryAudit, isMemorySuppressed, memoryFingerprint, restoreMemory, suppressMemory } from "../user-context/memory-suppression.js";
4
5
  import { randomUUID } from "node:crypto";
5
6
  //#region src/knowledge-memory/repository.ts
6
7
  init_fts();
@@ -60,7 +61,14 @@ function writeKnowledgeItem(input) {
60
61
  if (input.validFrom !== void 0 && input.validTo !== void 0 && input.validTo < input.validFrom) throw new Error("Knowledge validTo must be greater than or equal to validFrom.");
61
62
  const principalId = input.principalId ?? "local-owner";
62
63
  const now = input.now ?? Date.now();
64
+ const admittedStatus = input.status ?? (input.originClass === "owner" || input.originClass !== "untrusted" && input.confidence >= .7 && !input.derivedFromRecalledContext ? "active" : "candidate");
63
65
  return runSqliteWriteTransaction((db) => {
66
+ const keys = knowledgeFingerprints(input);
67
+ if (input.restoreDeleted && input.originClass === "owner") restoreMemory(db, keys);
68
+ if (isMemorySuppressed(db, keys)) return {
69
+ created: false,
70
+ suppressed: true
71
+ };
64
72
  const existingStatuses = input.replaceExisting ? [
65
73
  "candidate",
66
74
  "active",
@@ -76,12 +84,12 @@ function writeKnowledgeItem(input) {
76
84
  const existing = db.prepare(`SELECT * FROM knowledge_items WHERE principal_id = ?
77
85
  AND canonical_key = ? AND scope_type = ? AND COALESCE(scope_id, '') = COALESCE(?, '')
78
86
  AND status IN (${existingStatuses.map(() => "?").join(", ")})`).get(principalId, input.canonicalKey.trim(), input.scope.type, input.scope.id ?? null, ...existingStatuses);
79
- if (existing && !input.replaceExisting) return {
87
+ if (existing && (!input.replaceExisting || existing.origin_class === "owner" && input.originClass !== "owner")) return {
80
88
  item: fromRow(existing),
81
89
  created: false
82
90
  };
83
91
  if (existing) {
84
- const nextStatus = input.status ?? (input.originClass === "owner" ? "active" : "candidate");
92
+ const nextStatus = admittedStatus;
85
93
  db.prepare(`UPDATE knowledge_items SET kind = ?, content = ?, record_class = ?, status = ?, confidence = ?,
86
94
  importance = ?, valid_from = ?, valid_to = ?, expires_at = ?, review_at = ?,
87
95
  origin_class = ?, source_agent_id = ?, source_conversation_id = ?, source_turn_id = ?,
@@ -102,7 +110,7 @@ function writeKnowledgeItem(input) {
102
110
  };
103
111
  }
104
112
  const id = randomUUID();
105
- const status = input.status ?? (input.originClass === "owner" ? "active" : "candidate");
113
+ const status = admittedStatus;
106
114
  db.prepare(`INSERT INTO knowledge_items (
107
115
  knowledge_id, principal_id, kind, scope_type, scope_id, content, canonical_key, record_class,
108
116
  status, confidence, importance, valid_from, valid_to, expires_at, review_at,
@@ -154,12 +162,14 @@ function transitionKnowledgeStatus(input) {
154
162
  const current = db.prepare("SELECT * FROM knowledge_items WHERE knowledge_id = ?").get(input.id);
155
163
  if (!current) return void 0;
156
164
  if (input.expectedStatus && current.status !== input.expectedStatus) throw new KnowledgeReviewConflictError(`Knowledge status changed from ${input.expectedStatus} to ${current.status}.`);
157
- db.prepare(`UPDATE knowledge_items SET status = ?, content = COALESCE(?, content), updated_at = ?
158
- WHERE knowledge_id = ?`).run(input.status, content ?? null, now, input.id);
165
+ db.prepare(`UPDATE knowledge_items SET status = ?, content = COALESCE(?, content),
166
+ origin_class = CASE WHEN ? THEN 'owner' ELSE origin_class END, updated_at = ?
167
+ WHERE knowledge_id = ?`).run(input.status, content ?? null, input.actor === "user" && input.status === "active" ? 1 : 0, now, input.id);
159
168
  if (content !== void 0) {
160
169
  db.prepare("DELETE FROM knowledge_items_fts WHERE knowledge_id = ?").run(input.id);
161
170
  db.prepare("INSERT INTO knowledge_items_fts(content, knowledge_id) VALUES (?, ?)").run(content, input.id);
162
171
  }
172
+ if (input.actor === "user" && (input.status === "rejected" || input.status === "archived")) suppressMemory(db, knowledgeFingerprints(fromRow(current)), now);
163
173
  insertStatusEvent(db, {
164
174
  knowledgeId: input.id,
165
175
  fromStatus: current.status,
@@ -184,7 +194,8 @@ const REVIEW_TRANSITIONS = {
184
194
  from: [
185
195
  "candidate",
186
196
  "needs_review",
187
- "stale"
197
+ "stale",
198
+ "active"
188
199
  ],
189
200
  to: "active",
190
201
  requiresContent: true
@@ -292,11 +303,12 @@ function searchKnowledgeItems(input) {
292
303
  AND (k.valid_from IS NULL OR k.valid_from <= ?)
293
304
  AND (k.valid_to IS NULL OR k.valid_to >= ?)
294
305
  AND (k.expires_at IS NULL OR k.expires_at >= ?)
306
+ AND (k.review_at IS NULL OR k.review_at > ?)
295
307
  ${input.recordClass ? "AND k.record_class = ?" : ""}
296
308
  ${input.trustedOnly ? "AND k.origin_class != 'untrusted'" : ""}
297
309
  AND ${contentSource}
298
310
  AND ${visible.sql}
299
- ORDER BY rank ASC, k.importance DESC, k.updated_at DESC LIMIT ?`).all(fts, principalId, asOf, asOf, asOf, ...input.recordClass ? [input.recordClass] : [], ...visible.values, limit).map(fromRow);
311
+ ORDER BY rank ASC, k.importance DESC, k.updated_at DESC LIMIT ?`).all(fts, principalId, asOf, asOf, asOf, asOf, ...input.recordClass ? [input.recordClass] : [], ...visible.values, limit).map(fromRow);
300
312
  }
301
313
  function classifyKnowledgeContentSource(item) {
302
314
  if (item.recordClass === "memory") return "memory";
@@ -305,5 +317,43 @@ function classifyKnowledgeContentSource(item) {
305
317
  function knowledgeItemAllowed(item, policy) {
306
318
  return policy.scopes.includes(item.scope.type) && policy.contentSources.includes(classifyKnowledgeContentSource(item));
307
319
  }
320
+ function knowledgeFingerprints(input) {
321
+ const principal = input.principalId ?? "local-owner";
322
+ return [memoryFingerprint([
323
+ "knowledge-key",
324
+ principal,
325
+ input.scope.type,
326
+ input.scope.id ?? "",
327
+ input.canonicalKey
328
+ ]), memoryFingerprint([
329
+ "knowledge-content",
330
+ principal,
331
+ input.scope.type,
332
+ input.scope.id ?? "",
333
+ input.content
334
+ ])];
335
+ }
336
+ function deleteKnowledgeItem(id, now = Date.now()) {
337
+ return runSqliteWriteTransaction((db) => {
338
+ const rows = db.prepare(`WITH RECURSIVE deleted(id) AS (
339
+ SELECT knowledge_id FROM knowledge_items WHERE knowledge_id = ?
340
+ UNION SELECT k.knowledge_id FROM knowledge_items k JOIN deleted d
341
+ ON json_extract(k.source_json, '$.episodeKnowledgeId') = d.id
342
+ OR k.knowledge_id = (SELECT json_extract(source_json, '$.episodeKnowledgeId')
343
+ FROM knowledge_items WHERE knowledge_id = d.id)
344
+ ) SELECT * FROM knowledge_items WHERE knowledge_id IN (SELECT id FROM deleted)`).all(id);
345
+ for (const row of rows) {
346
+ const item = fromRow(row);
347
+ suppressMemory(db, knowledgeFingerprints(item), now);
348
+ clearMemoryAudit(db, "knowledge", item.id);
349
+ db.prepare("DELETE FROM knowledge_items_fts WHERE knowledge_id = ?").run(item.id);
350
+ db.prepare("DELETE FROM knowledge_items WHERE knowledge_id = ?").run(item.id);
351
+ }
352
+ return rows.length > 0;
353
+ });
354
+ }
355
+ function isKnowledgeCurrent(item, asOf = Date.now()) {
356
+ return item.status === "active" && (item.validFrom === void 0 || item.validFrom <= asOf) && (item.validTo === void 0 || item.validTo >= asOf) && (item.expiresAt === void 0 || item.expiresAt >= asOf) && (item.reviewAt === void 0 || item.reviewAt > asOf);
357
+ }
308
358
  //#endregion
309
- export { KnowledgeReviewConflictError, classifyKnowledgeContentSource, getKnowledgeItem, knowledgeItemAllowed, listKnowledgeItems, listKnowledgeStatusEvents, reviewKnowledgeItem, searchKnowledgeItems, transitionKnowledgeStatus, writeKnowledgeItem };
359
+ export { KnowledgeReviewConflictError, classifyKnowledgeContentSource, deleteKnowledgeItem, getKnowledgeItem, isKnowledgeCurrent, knowledgeItemAllowed, listKnowledgeItems, listKnowledgeStatusEvents, reviewKnowledgeItem, searchKnowledgeItems, transitionKnowledgeStatus, writeKnowledgeItem };
@@ -3,7 +3,7 @@ import "../user-model/domain.js";
3
3
  import { randomUUID } from "node:crypto";
4
4
  //#region src/memory-maintenance/service.ts
5
5
  init_transaction();
6
- const ALGORITHM_VERSION = "user-model-maintenance-v1";
6
+ const ALGORITHM_VERSION = "user-model-maintenance-v2";
7
7
  function emptyMetrics() {
8
8
  return {
9
9
  scanned: 0,
@@ -32,29 +32,22 @@ function addDecision(db, runId, objectType, objectId, action, reason, before, af
32
32
  decision_id, run_id, object_type, object_id, action, reason, before_json, after_json, created_at
33
33
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), runId, objectType, objectId, action, reason, before === void 0 ? null : JSON.stringify(before), after === void 0 ? null : JSON.stringify(after), now);
34
34
  }
35
- function transitionAssertions(db, runId, now, limit, metrics) {
36
- const expired = db.prepare(`SELECT assertion_id, status, valid_to FROM user_assertions
37
- WHERE status = 'active' AND valid_to IS NOT NULL AND valid_to < ? LIMIT ?`).all(now, limit);
38
- metrics.scanned += expired.length;
39
- for (const row of expired) {
40
- db.prepare("UPDATE user_assertions SET status = 'stale' WHERE assertion_id = ? AND status = 'active'").run(row.assertion_id);
35
+ function transitionAssertions(db, runId, principalId, now, limit, metrics) {
36
+ const rows = db.prepare(`SELECT a.assertion_id, a.status FROM user_assertions a
37
+ JOIN user_assertion_slots s ON s.slot_id = a.slot_id
38
+ WHERE s.principal_id = ? AND a.status IN ('active', 'candidate', 'needs_review')
39
+ AND ((a.valid_to IS NOT NULL AND a.valid_to < ?)
40
+ OR (a.review_at IS NOT NULL AND a.review_at <= ?))
41
+ ORDER BY COALESCE(a.valid_to, a.review_at), a.assertion_id LIMIT ?`).all(principalId, now, now, limit);
42
+ metrics.scanned += rows.length;
43
+ for (const row of rows) {
44
+ db.prepare("UPDATE user_assertions SET status = 'stale' WHERE assertion_id = ?").run(row.assertion_id);
41
45
  db.prepare(`INSERT INTO user_assertion_status_events (
42
46
  event_id, assertion_id, from_status, to_status, actor_type, reason, source_run_id, created_at
43
- ) VALUES (?, ?, 'active', 'stale', 'maintenance', 'Validity interval ended.', ?, ?)`).run(randomUUID(), row.assertion_id, runId, now);
44
- addDecision(db, runId, "assertion", row.assertion_id, "stale", "validity_ended", row, { status: "stale" }, now);
47
+ ) VALUES (?, ?, ?, 'stale', 'maintenance', 'No current supporting evidence at review time.', ?, ?)`).run(randomUUID(), row.assertion_id, row.status, runId, now);
48
+ addDecision(db, runId, "assertion", row.assertion_id, "stale", "validity_or_review_ended", row, { status: "stale" }, now);
45
49
  metrics.stale += 1;
46
50
  }
47
- const reviewDue = db.prepare(`SELECT assertion_id, status, review_at FROM user_assertions
48
- WHERE status = 'active' AND review_at IS NOT NULL AND review_at <= ? LIMIT ?`).all(now, Math.max(0, limit - expired.length));
49
- metrics.scanned += reviewDue.length;
50
- for (const row of reviewDue) {
51
- db.prepare("UPDATE user_assertions SET status = 'needs_review' WHERE assertion_id = ? AND status = 'active'").run(row.assertion_id);
52
- db.prepare(`INSERT INTO user_assertion_status_events (
53
- event_id, assertion_id, from_status, to_status, actor_type, reason, source_run_id, created_at
54
- ) VALUES (?, ?, 'active', 'needs_review', 'maintenance', 'Scheduled review became due.', ?, ?)`).run(randomUUID(), row.assertion_id, runId, now);
55
- addDecision(db, runId, "assertion", row.assertion_id, "needs_review", "review_due", row, { status: "needs_review" }, now);
56
- metrics.needsReview += 1;
57
- }
58
51
  }
59
52
  function transitionPriorities(db, runId, principalId, now, limit, metrics) {
60
53
  const rows = db.prepare(`SELECT priority_id, status, valid_to FROM user_priority_windows
@@ -68,7 +61,7 @@ function transitionPriorities(db, runId, principalId, now, limit, metrics) {
68
61
  }
69
62
  function transitionKnowledge(db, runId, principalId, now, limit, metrics) {
70
63
  const expired = db.prepare(`SELECT knowledge_id, status, expires_at FROM knowledge_items
71
- WHERE principal_id = ? AND status = 'active' AND expires_at IS NOT NULL AND expires_at < ? LIMIT ?`).all(principalId, now, limit);
64
+ WHERE principal_id = ? AND status IN ('active', 'candidate') AND expires_at IS NOT NULL AND expires_at < ? LIMIT ?`).all(principalId, now, limit);
72
65
  metrics.scanned += expired.length;
73
66
  for (const row of expired) {
74
67
  db.prepare("UPDATE knowledge_items SET status = 'stale', updated_at = ? WHERE knowledge_id = ?").run(now, row.knowledge_id);
@@ -76,12 +69,12 @@ function transitionKnowledge(db, runId, principalId, now, limit, metrics) {
76
69
  metrics.stale += 1;
77
70
  }
78
71
  const reviewDue = db.prepare(`SELECT knowledge_id, status, review_at FROM knowledge_items
79
- WHERE principal_id = ? AND status = 'active' AND review_at IS NOT NULL AND review_at <= ? LIMIT ?`).all(principalId, now, Math.max(0, limit - expired.length));
72
+ WHERE principal_id = ? AND status IN ('active', 'candidate', 'needs_review') AND review_at IS NOT NULL AND review_at <= ? LIMIT ?`).all(principalId, now, Math.max(0, limit - expired.length));
80
73
  metrics.scanned += reviewDue.length;
81
74
  for (const row of reviewDue) {
82
- db.prepare("UPDATE knowledge_items SET status = 'needs_review', updated_at = ? WHERE knowledge_id = ?").run(now, row.knowledge_id);
83
- addDecision(db, runId, "knowledge", row.knowledge_id, "needs_review", "review_due", row, { status: "needs_review" }, now);
84
- metrics.needsReview += 1;
75
+ db.prepare("UPDATE knowledge_items SET status = 'stale', updated_at = ? WHERE knowledge_id = ?").run(now, row.knowledge_id);
76
+ addDecision(db, runId, "knowledge", row.knowledge_id, "stale", "review_due", row, { status: "stale" }, now);
77
+ metrics.stale += 1;
85
78
  }
86
79
  }
87
80
  function reconcileEvidence(db, runId, principalId, now, limit, evidenceThreshold, metrics) {
@@ -89,7 +82,10 @@ function reconcileEvidence(db, runId, principalId, now, limit, evidenceThreshold
89
82
  FROM user_assertions a
90
83
  JOIN user_assertion_slots s ON s.slot_id = a.slot_id
91
84
  JOIN user_assertion_evidence ae ON ae.assertion_id = a.assertion_id AND ae.relation = 'contradicts'
92
- WHERE s.principal_id = ? AND a.status = 'active' LIMIT ?`).all(principalId, limit);
85
+ JOIN context_evidence e ON e.evidence_id = ae.evidence_id
86
+ WHERE s.principal_id = ? AND a.status = 'active'
87
+ AND e.trust_level = 'owner' AND e.source_type IN ('conversation', 'user')
88
+ AND e.observed_at > a.observed_at AND ae.confidence >= 0.7 LIMIT ?`).all(principalId, limit);
93
89
  metrics.scanned += contradicted.length;
94
90
  for (const row of contradicted) {
95
91
  db.prepare("UPDATE user_assertions SET status = 'needs_review' WHERE assertion_id = ?").run(row.assertion_id);
@@ -99,16 +95,31 @@ function reconcileEvidence(db, runId, principalId, now, limit, evidenceThreshold
99
95
  addDecision(db, runId, "assertion", row.assertion_id, "needs_review", "contradictory_evidence", row, { status: "needs_review" }, now);
100
96
  metrics.needsReview += 1;
101
97
  }
102
- const candidates = db.prepare(`SELECT a.assertion_id, a.status, COUNT(DISTINCT
103
- e.source_type || ':' || COALESCE(e.source_instance_id, '') || ':' || e.source_ref
104
- ) AS evidence_count
98
+ const candidates = db.prepare(`SELECT a.assertion_id, a.status,
99
+ MIN(COUNT(DISTINCT COALESCE(e.content_hash, e.source_ref)),
100
+ COUNT(DISTINCT e.source_type || ':' || COALESCE(e.source_instance_id, '') || ':' ||
101
+ COALESCE(e.source_item_id, e.source_ref))) AS evidence_count
105
102
  FROM user_assertions a
106
103
  JOIN user_assertion_slots s ON s.slot_id = a.slot_id
107
104
  JOIN user_assertion_evidence ae ON ae.assertion_id = a.assertion_id AND ae.relation = 'supports'
108
105
  JOIN context_evidence e ON e.evidence_id = ae.evidence_id AND e.trust_level = 'owner'
109
106
  WHERE s.principal_id = ? AND a.status = 'candidate'
110
107
  AND a.authority IN ('user_observed', 'system_inferred')
111
- GROUP BY a.assertion_id HAVING evidence_count >= ? LIMIT ?`).all(principalId, evidenceThreshold, limit);
108
+ AND a.confidence >= 0.7 AND ae.confidence >= 0.7
109
+ AND a.sensitivity = 'normal' AND a.consequence IN ('low', 'medium')
110
+ AND a.disclosure_policy != 'ask_before_reference'
111
+ AND e.source_type IN ('conversation', 'user', 'connector')
112
+ AND (a.valid_from IS NULL OR a.valid_from <= ?)
113
+ AND (a.valid_to IS NULL OR a.valid_to >= ?)
114
+ AND (a.review_at IS NULL OR a.review_at > ?)
115
+ AND NOT EXISTS (SELECT 1 FROM user_assertion_evidence bad
116
+ WHERE bad.assertion_id = a.assertion_id AND bad.relation = 'contradicts')
117
+ AND (s.cardinality = 'multiple' OR NOT EXISTS (
118
+ SELECT 1 FROM user_assertions other WHERE other.slot_id = a.slot_id
119
+ AND other.assertion_id != a.assertion_id AND other.status IN ('active', 'conflicted')
120
+ AND other.normalized_value != a.normalized_value
121
+ AND (other.valid_to IS NULL OR other.valid_to >= ?)))
122
+ GROUP BY a.assertion_id HAVING evidence_count >= ? ORDER BY a.recorded_at LIMIT ?`).all(principalId, now, now, now, now, evidenceThreshold, limit);
112
123
  metrics.scanned += candidates.length;
113
124
  for (const row of candidates) {
114
125
  db.prepare("UPDATE user_assertions SET status = 'active' WHERE assertion_id = ? AND status = 'candidate'").run(row.assertion_id);
@@ -119,6 +130,24 @@ function reconcileEvidence(db, runId, principalId, now, limit, evidenceThreshold
119
130
  metrics.activated += 1;
120
131
  }
121
132
  }
133
+ function activateKnowledge(db, runId, principalId, now, limit, metrics) {
134
+ const rows = db.prepare(`SELECT knowledge_id FROM knowledge_items
135
+ WHERE principal_id = ? AND record_class = 'memory' AND status = 'candidate'
136
+ AND origin_class IN ('owner', 'agent', 'system') AND confidence >= 0.7
137
+ AND derived_from_recalled_context = 0
138
+ AND (valid_from IS NULL OR valid_from <= ?) AND (valid_to IS NULL OR valid_to >= ?)
139
+ AND (expires_at IS NULL OR expires_at >= ?) AND (review_at IS NULL OR review_at > ?)
140
+ ORDER BY created_at, knowledge_id LIMIT ?`).all(principalId, now, now, now, now, limit);
141
+ metrics.scanned += rows.length;
142
+ for (const row of rows) {
143
+ db.prepare("UPDATE knowledge_items SET status = 'active', updated_at = ? WHERE knowledge_id = ?").run(now, row.knowledge_id);
144
+ db.prepare(`INSERT INTO knowledge_item_status_events
145
+ (event_id, knowledge_id, from_status, to_status, actor_type, reason, created_at)
146
+ VALUES (?, ?, 'candidate', 'active', 'maintenance', 'Eligible scoped memory activated automatically.', ?)`).run(randomUUID(), row.knowledge_id, now);
147
+ addDecision(db, runId, "knowledge", row.knowledge_id, "activated", "eligible_scoped_memory", { status: "candidate" }, { status: "active" }, now);
148
+ metrics.activated += 1;
149
+ }
150
+ }
122
151
  function repairIndexes(db, runId, now, metrics) {
123
152
  const assertionMissing = db.prepare(`SELECT COUNT(*) AS count FROM user_assertions a
124
153
  LEFT JOIN user_assertions_fts f ON f.assertion_id = a.assertion_id
@@ -178,11 +207,12 @@ function runMemoryMaintenance(input) {
178
207
  });
179
208
  try {
180
209
  runSqliteWriteTransaction((db) => {
181
- transitionAssertions(db, runId, now, limit, metrics);
210
+ transitionAssertions(db, runId, principalId, now, limit, metrics);
182
211
  transitionPriorities(db, runId, principalId, now, limit, metrics);
183
212
  transitionKnowledge(db, runId, principalId, now, limit, metrics);
184
213
  if (input.jobType === "daily_reconciliation" || input.jobType === "manual_repair") {
185
214
  reconcileEvidence(db, runId, principalId, now, limit, evidenceThreshold, metrics);
215
+ activateKnowledge(db, runId, principalId, now, limit, metrics);
186
216
  repairIndexes(db, runId, now, metrics);
187
217
  }
188
218
  if (input.jobType === "weekly_knowledge" || input.jobType === "manual_repair") archiveOldKnowledge(db, runId, principalId, now - (input.staleRetentionDays ?? 30) * 24 * 60 * 60 * 1e3, now, limit, metrics);
@@ -0,0 +1,2 @@
1
+ import type { Migration } from './types.js';
2
+ export declare const memoryConfigMigration: Migration;
@@ -0,0 +1,77 @@
1
+ import { init_lock, isGatewayConfigLockedByAnotherProcessSync } from "../gateway/lock.js";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ //#region src/migrations/memory-config.ts
4
+ init_lock();
5
+ const id = "automatic-memory-config";
6
+ function normalizeKnowledgeMemoryConfigInput(input) {
7
+ if (!input || typeof input !== "object" || Array.isArray(input)) return input;
8
+ const memory = input;
9
+ if (!Array.isArray(memory.sources)) return input;
10
+ const oldSources = memory.sources.filter((source) => typeof source === "string");
11
+ const visibilityScopes = new Set([
12
+ "session",
13
+ "workspace",
14
+ "project"
15
+ ]);
16
+ const normalized = { ...memory };
17
+ if (!Array.isArray(normalized.readScopes)) normalized.readScopes = oldSources.filter((source) => visibilityScopes.has(source));
18
+ if (!Array.isArray(normalized.contentSources)) normalized.contentSources = [
19
+ ...oldSources.some((source) => visibilityScopes.has(source)) ? ["memory"] : [],
20
+ ...oldSources.includes("workspace") ? ["local_import"] : [],
21
+ ...oldSources.includes("connector") ? ["connector"] : []
22
+ ];
23
+ delete normalized.sources;
24
+ return normalized;
25
+ }
26
+ function convertedConfig(ctx) {
27
+ if (!existsSync(ctx.configPath)) return null;
28
+ const config = JSON.parse(readFileSync(ctx.configPath, "utf8"));
29
+ const userContext = config.userContext;
30
+ if (!userContext || typeof userContext !== "object" || Array.isArray(userContext)) return null;
31
+ const context = userContext;
32
+ let changed = false;
33
+ for (const key of ["userModel", "knowledgeMemory"]) {
34
+ const section = context[key];
35
+ if (!section || typeof section !== "object" || Array.isArray(section)) continue;
36
+ const memory = section;
37
+ if (memory.writePolicy === "confirm") {
38
+ memory.writePolicy = "allow";
39
+ changed = true;
40
+ }
41
+ if (key === "knowledgeMemory" && Array.isArray(memory.sources)) {
42
+ context[key] = normalizeKnowledgeMemoryConfigInput(memory);
43
+ changed = true;
44
+ }
45
+ }
46
+ if (!changed) return null;
47
+ return config;
48
+ }
49
+ function item(status) {
50
+ return {
51
+ id,
52
+ title: "Automatic memory configuration",
53
+ kind: "config",
54
+ safety: "auto",
55
+ status,
56
+ message: "Enable automatic ordinary memory and separate visibility scopes from content sources."
57
+ };
58
+ }
59
+ const memoryConfigMigration = {
60
+ id,
61
+ kind: "config",
62
+ safety: "auto",
63
+ detect: (ctx) => convertedConfig(ctx) ? item("planned") : null,
64
+ apply: (ctx) => {
65
+ if (isGatewayConfigLockedByAnotherProcessSync(ctx.configPath)) return {
66
+ ...item("skipped"),
67
+ message: "Deferred knowledge memory config migration until the running gateway restarts."
68
+ };
69
+ const config = convertedConfig(ctx);
70
+ return config ? {
71
+ ...item("applied"),
72
+ details: { config }
73
+ } : item("not_needed");
74
+ }
75
+ };
76
+ //#endregion
77
+ export { memoryConfigMigration };
@@ -3,7 +3,7 @@ import { init_logger } from "../utils/logger.js";
3
3
  import { ConfigSchema, init_schema } from "../config/schema.js";
4
4
  import { listRegisteredMigrations } from "./registry.js";
5
5
  import { conversationRoutingConfigMigration } from "./conversation-routing-config.js";
6
- import { knowledgeMemoryReadPolicyConfigMigration } from "./knowledge-memory-read-policy-config.js";
6
+ import { memoryConfigMigration } from "./memory-config.js";
7
7
  import { dirname, join } from "node:path";
8
8
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
9
9
  //#region src/migrations/runner.ts
@@ -12,7 +12,7 @@ init_logger();
12
12
  const log = createLogger("Migrations");
13
13
  const MIGRATION_LEDGER_FILENAME = "migrations.json";
14
14
  const CONFIG_BACKUP_COUNT = 10;
15
- const CORE_MIGRATIONS = [conversationRoutingConfigMigration, knowledgeMemoryReadPolicyConfigMigration];
15
+ const CORE_MIGRATIONS = [conversationRoutingConfigMigration, memoryConfigMigration];
16
16
  function listAllMigrations() {
17
17
  return [...CORE_MIGRATIONS, ...listRegisteredMigrations().map((entry) => entry.migration)];
18
18
  }
@@ -162,7 +162,7 @@ function workDiscoveryPlan(eventType, payload) {
162
162
  const event = payload;
163
163
  const completed = eventType === "work-discovery.completed";
164
164
  if (typeof event.runId !== "string" || !event.runId || typeof event.conversationId !== "string" || !event.conversationId || event.status !== (completed ? "completed" : "failed")) return null;
165
- const type = completed ? "work_discovery.review_ready" : "work_discovery.failed";
165
+ const type = completed ? "work_discovery.completed" : "work_discovery.failed";
166
166
  return {
167
167
  dedupeKey: `${type}:${event.runId}`,
168
168
  notification: {
@@ -174,15 +174,15 @@ function workDiscoveryPlan(eventType, payload) {
174
174
  },
175
175
  priority: completed ? "normal" : "high",
176
176
  title: completed ? {
177
- en: "Understanding ready for review",
178
- zh: "用户理解已可确认"
177
+ en: "Understanding updated",
178
+ zh: "用户理解已更新"
179
179
  } : {
180
180
  en: "Understanding needs attention",
181
181
  zh: "用户理解需要处理"
182
182
  },
183
183
  body: completed ? {
184
- en: "Review what xopc learned about your work.",
185
- zh: "查看并确认 xopc 对你工作的理解。"
184
+ en: "See what xopc learned. You can edit or delete it anytime.",
185
+ zh: "理解已自动整理,可随时查看、修改或删除。"
186
186
  } : {
187
187
  en: "Open user understanding to review or retry.",
188
188
  zh: "打开用户理解,查看详情或重试。"
@@ -19,7 +19,7 @@ export declare class NotificationService {
19
19
  persistGatewayEvent(type: string, payload: unknown): {
20
20
  schemaVersion: 1;
21
21
  id: string;
22
- type: "work_discovery.failed" | "chat.completed" | "chat.failed" | "task.needs_input" | "task.blocked" | "task.failed" | "task.completed" | "automation.completed" | "automation.failed" | "proactive.insight" | "work_discovery.review_ready";
22
+ type: "work_discovery.completed" | "work_discovery.failed" | "chat.completed" | "chat.failed" | "task.needs_input" | "task.blocked" | "task.failed" | "task.completed" | "automation.completed" | "automation.failed" | "proactive.insight";
23
23
  priority: "normal" | "high";
24
24
  title: {
25
25
  en: string;
@@ -56,7 +56,7 @@ export declare class NotificationService {
56
56
  persistPlan(plan: NotificationPlan): {
57
57
  schemaVersion: 1;
58
58
  id: string;
59
- type: "work_discovery.failed" | "chat.completed" | "chat.failed" | "task.needs_input" | "task.blocked" | "task.failed" | "task.completed" | "automation.completed" | "automation.failed" | "proactive.insight" | "work_discovery.review_ready";
59
+ type: "work_discovery.completed" | "work_discovery.failed" | "chat.completed" | "chat.failed" | "task.needs_input" | "task.blocked" | "task.failed" | "task.completed" | "automation.completed" | "automation.failed" | "proactive.insight";
60
60
  priority: "normal" | "high";
61
61
  title: {
62
62
  en: string;
@@ -40,7 +40,7 @@ function preferenceAllows(type, preferences) {
40
40
  case "automation.completed": return preferences.automationCompleted;
41
41
  case "automation.failed": return preferences.automationFailed;
42
42
  case "proactive.insight": return preferences.proactiveInsight;
43
- case "work_discovery.review_ready":
43
+ case "work_discovery.completed":
44
44
  case "work_discovery.failed": return true;
45
45
  }
46
46
  }
@@ -0,0 +1,12 @@
1
+ CREATE TABLE memory_suppressions (
2
+ fingerprint TEXT PRIMARY KEY,
3
+ created_at INTEGER NOT NULL
4
+ );
5
+
6
+ UPDATE notification_events SET
7
+ event_type = 'work_discovery.completed',
8
+ dedupe_key = replace(dedupe_key, 'work_discovery.review_ready:', 'work_discovery.completed:'),
9
+ title_en = 'Understanding updated', title_zh = '用户理解已更新',
10
+ body_en = 'See what xopc learned. You can edit or delete it anytime.',
11
+ body_zh = '理解已自动整理,可随时查看、修改或删除。'
12
+ WHERE event_type = 'work_discovery.review_ready';
@@ -0,0 +1,6 @@
1
+ CREATE TABLE understanding_refresh_batches (
2
+ batch_id TEXT PRIMARY KEY,
3
+ source_run_ids_json TEXT NOT NULL,
4
+ created_at INTEGER NOT NULL
5
+ );
6
+ CREATE INDEX idx_understanding_refresh_batches_created ON understanding_refresh_batches(created_at DESC);
@@ -3,7 +3,7 @@ import type { ApplyMigrationsOptions } from './types.js';
3
3
  /** Baseline schema version applied from schema.sql on first open. */
4
4
  export declare const XOPC_DB_BASELINE_SCHEMA_VERSION = 165;
5
5
  /** Latest schema version this release supports (increment when adding migrations). */
6
- export declare const XOPC_DB_SCHEMA_VERSION = 183;
6
+ export declare const XOPC_DB_SCHEMA_VERSION = 185;
7
7
  export declare function resolveMigrationsDir(override?: string): string;
8
8
  /**
9
9
  * Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.
@@ -63,7 +63,7 @@ function applySingleMigration(db, migration) {
63
63
  * Each step runs in its own transaction and updates schema_meta on success.
64
64
  */
65
65
  function applyPendingMigrations(db, options = {}) {
66
- const targetVersion = options.targetVersion ?? 183;
66
+ const targetVersion = options.targetVersion ?? 185;
67
67
  let currentVersion = readSchemaVersion(db);
68
68
  if (currentVersion > targetVersion) throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);
69
69
  if (!options.migrationsDir && currentVersion < 165) throw new DatabaseSchemaTooOldError(currentVersion, 165);
@@ -118,7 +118,7 @@ function applyPendingMigrations(db, options = {}) {
118
118
  }
119
119
  /** Inspect schema version without mutating the database. */
120
120
  function inspectSchemaMigrationStatus(db, options = {}) {
121
- const appVersion = options.targetVersion ?? 183;
121
+ const appVersion = options.targetVersion ?? 185;
122
122
  const dbVersion = readSchemaVersion(db);
123
123
  if (dbVersion > appVersion) return {
124
124
  dbVersion,
@@ -157,7 +157,7 @@ var init_runner = __esmMin((() => {
157
157
  init_errors();
158
158
  log = createLogger("Sqlite:Migrations");
159
159
  XOPC_DB_BASELINE_SCHEMA_VERSION = 165;
160
- XOPC_DB_SCHEMA_VERSION = 183;
160
+ XOPC_DB_SCHEMA_VERSION = 185;
161
161
  MODULE_DIR = dirname(fileURLToPath(import.meta.url));
162
162
  }));
163
163
  //#endregion
@@ -33,7 +33,7 @@ function ensureXopcDatabaseSchema(db, options = {}) {
33
33
  currentVersion = 165;
34
34
  }
35
35
  applyPendingMigrations(db, {
36
- targetVersion: 183,
36
+ targetVersion: 185,
37
37
  databasePath: fresh ? void 0 : options.databasePath
38
38
  });
39
39
  }
@@ -39,7 +39,6 @@ export declare const UserModelConfigSchema: z.ZodDefault<z.ZodObject<{
39
39
  enabled: z.ZodDefault<z.ZodBoolean>;
40
40
  writePolicy: z.ZodDefault<z.ZodEnum<{
41
41
  allow: "allow";
42
- confirm: "confirm";
43
42
  deny: "deny";
44
43
  }>>;
45
44
  sensitiveWritePolicy: z.ZodDefault<z.ZodEnum<{
@@ -76,12 +75,10 @@ export declare const UserModelConfigSchema: z.ZodDefault<z.ZodObject<{
76
75
  staleRetentionDays: z.ZodDefault<z.ZodNumber>;
77
76
  }, z.core.$strict>>;
78
77
  }, z.core.$strict>>;
79
- export declare function normalizeKnowledgeMemoryConfigInput(input: unknown): unknown;
80
- export declare const KnowledgeMemoryConfigSchema: z.ZodDefault<z.ZodPreprocess<z.ZodObject<{
78
+ export declare const KnowledgeMemoryConfigSchema: z.ZodDefault<z.ZodObject<{
81
79
  enabled: z.ZodDefault<z.ZodBoolean>;
82
80
  writePolicy: z.ZodDefault<z.ZodEnum<{
83
81
  allow: "allow";
84
- confirm: "confirm";
85
82
  deny: "deny";
86
83
  }>>;
87
84
  readScopes: z.ZodDefault<z.ZodArray<z.ZodEnum<{
@@ -114,7 +111,7 @@ export declare const KnowledgeMemoryConfigSchema: z.ZodDefault<z.ZodPreprocess<z
114
111
  allowExternalWrites: z.ZodDefault<z.ZodBoolean>;
115
112
  allowedProviderIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
116
113
  autoWriteKinds: z.ZodOptional<z.ZodArray<z.ZodString>>;
117
- }, z.core.$strict>>>;
114
+ }, z.core.$strict>>;
118
115
  export declare const ContextPlanningConfigSchema: z.ZodDefault<z.ZodObject<{
119
116
  enabled: z.ZodDefault<z.ZodBoolean>;
120
117
  maxAssertions: z.ZodDefault<z.ZodNumber>;
@@ -152,7 +149,6 @@ export declare const UserContextConfigSchema: z.ZodDefault<z.ZodObject<{
152
149
  enabled: z.ZodDefault<z.ZodBoolean>;
153
150
  writePolicy: z.ZodDefault<z.ZodEnum<{
154
151
  allow: "allow";
155
- confirm: "confirm";
156
152
  deny: "deny";
157
153
  }>>;
158
154
  sensitiveWritePolicy: z.ZodDefault<z.ZodEnum<{
@@ -189,11 +185,10 @@ export declare const UserContextConfigSchema: z.ZodDefault<z.ZodObject<{
189
185
  staleRetentionDays: z.ZodDefault<z.ZodNumber>;
190
186
  }, z.core.$strict>>;
191
187
  }, z.core.$strict>>;
192
- knowledgeMemory: z.ZodDefault<z.ZodPreprocess<z.ZodObject<{
188
+ knowledgeMemory: z.ZodDefault<z.ZodObject<{
193
189
  enabled: z.ZodDefault<z.ZodBoolean>;
194
190
  writePolicy: z.ZodDefault<z.ZodEnum<{
195
191
  allow: "allow";
196
- confirm: "confirm";
197
192
  deny: "deny";
198
193
  }>>;
199
194
  readScopes: z.ZodDefault<z.ZodArray<z.ZodEnum<{
@@ -226,7 +221,7 @@ export declare const UserContextConfigSchema: z.ZodDefault<z.ZodObject<{
226
221
  allowExternalWrites: z.ZodDefault<z.ZodBoolean>;
227
222
  allowedProviderIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
228
223
  autoWriteKinds: z.ZodOptional<z.ZodArray<z.ZodString>>;
229
- }, z.core.$strict>>>;
224
+ }, z.core.$strict>>;
230
225
  contextPlanning: z.ZodDefault<z.ZodObject<{
231
226
  enabled: z.ZodDefault<z.ZodBoolean>;
232
227
  maxAssertions: z.ZodDefault<z.ZodNumber>;