@hongmaple0820/scale-engine 0.49.0 → 0.50.1

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 (190) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/api/DashboardHttpConfig.d.ts +28 -0
  4. package/dist/api/DashboardHttpConfig.js +110 -0
  5. package/dist/api/DashboardHttpConfig.js.map +1 -0
  6. package/dist/api/cli.js +102 -11
  7. package/dist/api/cli.js.map +1 -1
  8. package/dist/api/http.d.ts +1 -0
  9. package/dist/api/http.js +50 -0
  10. package/dist/api/http.js.map +1 -0
  11. package/dist/artifact/types.d.ts +5 -0
  12. package/dist/artifact/types.js.map +1 -1
  13. package/dist/bootstrap/DependencyBootstrap.d.ts +1 -0
  14. package/dist/bootstrap/DependencyBootstrap.js +14 -3
  15. package/dist/bootstrap/DependencyBootstrap.js.map +1 -1
  16. package/dist/cli/cortexApplyCommand.d.ts +26 -0
  17. package/dist/cli/cortexApplyCommand.js +74 -0
  18. package/dist/cli/cortexApplyCommand.js.map +1 -0
  19. package/dist/cli/cortexCandidateCommands.d.ts +42 -0
  20. package/dist/cli/cortexCandidateCommands.js +119 -0
  21. package/dist/cli/cortexCandidateCommands.js.map +1 -0
  22. package/dist/cli/cortexCommands.d.ts +15 -0
  23. package/dist/cli/cortexCommands.js +57 -15
  24. package/dist/cli/cortexCommands.js.map +1 -1
  25. package/dist/cli/engineBootstrap.d.ts +1 -1
  26. package/dist/cli/engineBootstrap.js +2 -0
  27. package/dist/cli/engineBootstrap.js.map +1 -1
  28. package/dist/cli/evalCommands.js +1 -0
  29. package/dist/cli/evalCommands.js.map +1 -1
  30. package/dist/cli/phaseCommands.d.ts +28 -0
  31. package/dist/cli/phaseCommands.js +148 -9
  32. package/dist/cli/phaseCommands.js.map +1 -1
  33. package/dist/cli/runtimeSkillCommands.js +12 -2
  34. package/dist/cli/runtimeSkillCommands.js.map +1 -1
  35. package/dist/cli/shieldCommands.d.ts +1 -0
  36. package/dist/cli/shieldCommands.js +20 -7
  37. package/dist/cli/shieldCommands.js.map +1 -1
  38. package/dist/cli/workflowEvidenceCommands.d.ts +120 -0
  39. package/dist/cli/workflowEvidenceCommands.js +228 -2
  40. package/dist/cli/workflowEvidenceCommands.js.map +1 -1
  41. package/dist/cortex/AutoFixEventObservations.d.ts +11 -0
  42. package/dist/cortex/AutoFixEventObservations.js +72 -0
  43. package/dist/cortex/AutoFixEventObservations.js.map +1 -0
  44. package/dist/cortex/GateEvidenceObservations.d.ts +22 -0
  45. package/dist/cortex/GateEvidenceObservations.js +179 -0
  46. package/dist/cortex/GateEvidenceObservations.js.map +1 -0
  47. package/dist/cortex/GovernanceMetrics.d.ts +2 -0
  48. package/dist/cortex/GovernanceMetrics.js +112 -22
  49. package/dist/cortex/GovernanceMetrics.js.map +1 -1
  50. package/dist/cortex/InstinctApplicationRecorder.d.ts +28 -0
  51. package/dist/cortex/InstinctApplicationRecorder.js +145 -0
  52. package/dist/cortex/InstinctApplicationRecorder.js.map +1 -0
  53. package/dist/cortex/InstinctCandidateAudit.d.ts +3 -0
  54. package/dist/cortex/InstinctCandidateAudit.js +39 -0
  55. package/dist/cortex/InstinctCandidateAudit.js.map +1 -0
  56. package/dist/cortex/InstinctCandidateReview.d.ts +32 -0
  57. package/dist/cortex/InstinctCandidateReview.js +125 -0
  58. package/dist/cortex/InstinctCandidateReview.js.map +1 -0
  59. package/dist/cortex/InstinctExtractor.d.ts +1 -0
  60. package/dist/cortex/InstinctExtractor.js +24 -17
  61. package/dist/cortex/InstinctExtractor.js.map +1 -1
  62. package/dist/cortex/InstinctRuntimeEvidence.d.ts +14 -0
  63. package/dist/cortex/InstinctRuntimeEvidence.js +120 -0
  64. package/dist/cortex/InstinctRuntimeEvidence.js.map +1 -0
  65. package/dist/cortex/InstinctStore.d.ts +18 -3
  66. package/dist/cortex/InstinctStore.js +30 -9
  67. package/dist/cortex/InstinctStore.js.map +1 -1
  68. package/dist/cortex/SessionInjector.d.ts +1 -0
  69. package/dist/cortex/SessionInjector.js +15 -2
  70. package/dist/cortex/SessionInjector.js.map +1 -1
  71. package/dist/dashboard/DashboardServer.d.ts +79 -0
  72. package/dist/dashboard/DashboardServer.js +330 -6
  73. package/dist/dashboard/DashboardServer.js.map +1 -1
  74. package/dist/dashboard/spa/app.js +515 -0
  75. package/dist/dashboard/spa/components/DataTable.js +53 -0
  76. package/dist/dashboard/spa/components/EventStream.js +66 -0
  77. package/dist/dashboard/spa/components/LoadingState.js +39 -0
  78. package/dist/dashboard/spa/components/MetricCard.js +30 -0
  79. package/dist/dashboard/spa/components/Panel.js +27 -0
  80. package/dist/dashboard/spa/components/StatusBadge.js +51 -0
  81. package/dist/dashboard/spa/i18n.js +767 -0
  82. package/dist/dashboard/spa/index.html +463 -0
  83. package/dist/dashboard/spa/pages/costs.js +522 -0
  84. package/dist/dashboard/spa/pages/documents.js +540 -0
  85. package/dist/dashboard/spa/pages/knowledge.js +457 -0
  86. package/dist/dashboard/spa/pages/monitoring.js +361 -0
  87. package/dist/dashboard/spa/pages/overview.js +301 -0
  88. package/dist/dashboard/spa/pages/topology-renderers.js +251 -0
  89. package/dist/dashboard/spa/pages/topology.js +370 -0
  90. package/dist/dashboard/spa/pages/workflow-renderers.js +239 -0
  91. package/dist/dashboard/spa/pages/workflow.js +217 -0
  92. package/dist/env/EnvironmentDoctor.js +12 -7
  93. package/dist/env/EnvironmentDoctor.js.map +1 -1
  94. package/dist/eval/WorkflowEval.d.ts +9 -0
  95. package/dist/eval/WorkflowEval.js +348 -2
  96. package/dist/eval/WorkflowEval.js.map +1 -1
  97. package/dist/memory/MemoryBrain.d.ts +13 -0
  98. package/dist/memory/MemoryBrain.js +47 -0
  99. package/dist/memory/MemoryBrain.js.map +1 -1
  100. package/dist/memory/MemoryFabric.d.ts +1 -0
  101. package/dist/memory/MemoryFabric.js +12 -8
  102. package/dist/memory/MemoryFabric.js.map +1 -1
  103. package/dist/memory/MemoryLearning.d.ts +1 -0
  104. package/dist/memory/MemoryLearning.js +6 -3
  105. package/dist/memory/MemoryLearning.js.map +1 -1
  106. package/dist/memory/MemoryProviders.d.ts +8 -1
  107. package/dist/memory/MemoryProviders.js +143 -29
  108. package/dist/memory/MemoryProviders.js.map +1 -1
  109. package/dist/runtime/AiOsRuntime.d.ts +14 -1
  110. package/dist/runtime/AiOsRuntime.js +59 -3
  111. package/dist/runtime/AiOsRuntime.js.map +1 -1
  112. package/dist/runtime/RuntimeDoctor.js +3 -1
  113. package/dist/runtime/RuntimeDoctor.js.map +1 -1
  114. package/dist/runtime/RuntimeEvidenceLedger.d.ts +6 -0
  115. package/dist/runtime/RuntimeEvidenceLedger.js +52 -1
  116. package/dist/runtime/RuntimeEvidenceLedger.js.map +1 -1
  117. package/dist/runtime/SessionLedger.d.ts +2 -0
  118. package/dist/runtime/SessionLedger.js +4 -0
  119. package/dist/runtime/SessionLedger.js.map +1 -1
  120. package/dist/setup/SetupVerification.js +53 -5
  121. package/dist/setup/SetupVerification.js.map +1 -1
  122. package/dist/shield/PolicyCompiler.js +73 -12
  123. package/dist/shield/PolicyCompiler.js.map +1 -1
  124. package/dist/shield/ProtectedPaths.js +4 -2
  125. package/dist/shield/ProtectedPaths.js.map +1 -1
  126. package/dist/skills/SkillCatalog.d.ts +2 -0
  127. package/dist/skills/SkillCatalog.js +8 -0
  128. package/dist/skills/SkillCatalog.js.map +1 -1
  129. package/dist/skills/SkillDoctor.d.ts +19 -2
  130. package/dist/skills/SkillDoctor.js +163 -13
  131. package/dist/skills/SkillDoctor.js.map +1 -1
  132. package/dist/tools/SafeCommandRunner.d.ts +1 -0
  133. package/dist/tools/SafeCommandRunner.js +1 -0
  134. package/dist/tools/SafeCommandRunner.js.map +1 -1
  135. package/dist/tools/ToolCapabilityRegistry.js +25 -3
  136. package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
  137. package/dist/tools/ToolOrchestrator.js +21 -0
  138. package/dist/tools/ToolOrchestrator.js.map +1 -1
  139. package/dist/version.d.ts +1 -1
  140. package/dist/version.js +1 -1
  141. package/dist/workflow/AgentLoopReadiness.d.ts +103 -0
  142. package/dist/workflow/AgentLoopReadiness.js +371 -0
  143. package/dist/workflow/AgentLoopReadiness.js.map +1 -0
  144. package/dist/workflow/EcosystemReadinessGate.d.ts +46 -0
  145. package/dist/workflow/EcosystemReadinessGate.js +126 -0
  146. package/dist/workflow/EcosystemReadinessGate.js.map +1 -0
  147. package/dist/workflow/EngineeringStandards.js +48 -3
  148. package/dist/workflow/EngineeringStandards.js.map +1 -1
  149. package/dist/workflow/GateCatalog.js +9 -0
  150. package/dist/workflow/GateCatalog.js.map +1 -1
  151. package/dist/workflow/GovernanceTemplatePacks.js +2 -26
  152. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
  153. package/dist/workflow/GovernanceTemplates.js +8 -1
  154. package/dist/workflow/GovernanceTemplates.js.map +1 -1
  155. package/dist/workflow/ReleaseDeploymentLedger.d.ts +63 -0
  156. package/dist/workflow/ReleaseDeploymentLedger.js +154 -0
  157. package/dist/workflow/ReleaseDeploymentLedger.js.map +1 -0
  158. package/dist/workflow/ReviewAnalyzer.js +50 -3
  159. package/dist/workflow/ReviewAnalyzer.js.map +1 -1
  160. package/dist/workflow/SessionPreamble.d.ts +7 -0
  161. package/dist/workflow/SessionPreamble.js +48 -9
  162. package/dist/workflow/SessionPreamble.js.map +1 -1
  163. package/dist/workflow/VerificationCommands.d.ts +1 -0
  164. package/dist/workflow/VerificationCommands.js.map +1 -1
  165. package/dist/workflow/VerificationProfile.d.ts +5 -0
  166. package/dist/workflow/VerificationProfile.js +26 -0
  167. package/dist/workflow/VerificationProfile.js.map +1 -1
  168. package/dist/workflow/VerificationSchema.d.ts +3 -0
  169. package/dist/workflow/VerificationSchema.js +6 -0
  170. package/dist/workflow/VerificationSchema.js.map +1 -1
  171. package/dist/workflow/WorkflowEffectiveness.d.ts +97 -0
  172. package/dist/workflow/WorkflowEffectiveness.js +302 -0
  173. package/dist/workflow/WorkflowEffectiveness.js.map +1 -0
  174. package/dist/workflow/WorkflowEffectivenessRenderer.d.ts +2 -0
  175. package/dist/workflow/WorkflowEffectivenessRenderer.js +67 -0
  176. package/dist/workflow/WorkflowEffectivenessRenderer.js.map +1 -0
  177. package/dist/workflow/WorkflowEffectivenessScoring.d.ts +6 -0
  178. package/dist/workflow/WorkflowEffectivenessScoring.js +243 -0
  179. package/dist/workflow/WorkflowEffectivenessScoring.js.map +1 -0
  180. package/dist/workflow/gates/GateSystem.d.ts +16 -0
  181. package/dist/workflow/gates/GateSystem.js +208 -41
  182. package/dist/workflow/gates/GateSystem.js.map +1 -1
  183. package/dist/workflow/gates/MetaGovernanceGates.js +269 -8
  184. package/dist/workflow/gates/MetaGovernanceGates.js.map +1 -1
  185. package/docs/reference/cli.md +2 -1
  186. package/docs/start/agent-governance-demo.md +1 -1
  187. package/docs/workflow/README.md +1 -1
  188. package/docs/workflow/templates/github-actions-scale-preflight.yml +4 -1
  189. package/package.json +6 -3
  190. package/scripts/workflow/run-vitest.mjs +123 -0
@@ -0,0 +1,457 @@
1
+ /**
2
+ * Knowledge Page - Memory Brain nodes, provider status, and explicit recall.
3
+ */
4
+ ;(() => {
5
+ 'use strict'
6
+
7
+ const { copyText, downloadText, fetchJSON, formatTime, runtimeLabel, t, $, dom } = window.Dashboard
8
+ const { autoRefreshControl, dataNote, dataTable, el, emptyState, metricCard, panel, renderText } = dom
9
+ let lastReport = null
10
+ let lastQuery = ''
11
+ let localStatusFilter = ''
12
+ let reviewMessage = ''
13
+
14
+ async function renderKnowledge() {
15
+ const app = $('#app')
16
+ const search = el('input', {
17
+ id: 'knowledge-search',
18
+ type: 'text',
19
+ className: 'search-box',
20
+ placeholder: t('knowledge.searchPlaceholder'),
21
+ style: { flex: '1', maxWidth: '460px' },
22
+ })
23
+ const recall = el('button', { id: 'knowledge-recall', className: 'topo-btn', text: t('knowledge.recall') })
24
+ const refresh = el('button', { id: 'knowledge-refresh', className: 'topo-btn', text: t('common.refresh') })
25
+ const copyButton = el('button', { id: 'knowledge-copy-json', className: 'topo-btn', text: t('common.copyJson') })
26
+ const exportButton = el('button', { id: 'knowledge-export', className: 'topo-btn', text: t('knowledge.exportAll') })
27
+ const localFilter = el('select', {
28
+ id: 'knowledge-local-filter',
29
+ className: 'search-box',
30
+ title: t('knowledge.localFilter'),
31
+ style: { width: '180px' },
32
+ }, [
33
+ el('option', { value: '', text: t('knowledge.allStatuses') }),
34
+ ])
35
+
36
+ app.replaceChildren(
37
+ el('div', { className: 'page-toolbar' }, [
38
+ search,
39
+ recall,
40
+ refresh,
41
+ copyButton,
42
+ exportButton,
43
+ autoRefreshControl(() => loadKnowledge(search.value.trim())),
44
+ el('label', { className: 'field-label' }, [
45
+ el('span', { text: t('knowledge.localFilter') }),
46
+ localFilter,
47
+ ]),
48
+ ]),
49
+ el('div', { id: 'knowledge-data-note' }),
50
+ el('div', { className: 'metric-grid', id: 'knowledge-summary' }),
51
+ panel(t('knowledge.operations'), 'knowledge-operations'),
52
+ panel(t('knowledge.reviewQueue'), 'knowledge-review'),
53
+ el('div', { className: 'grid-2', style: { marginTop: '16px' } }, [
54
+ panel(t('knowledge.providers'), 'knowledge-providers'),
55
+ panel(t('knowledge.recallResults'), 'knowledge-recall-results'),
56
+ ]),
57
+ panel(t('knowledge.localMemory'), 'knowledge-local', { className: 'mt-3' }),
58
+ panel(t('knowledge.warnings'), 'knowledge-warnings', { className: 'mt-3' }),
59
+ )
60
+
61
+ const load = () => loadKnowledge(search.value.trim())
62
+ recall.addEventListener('click', load)
63
+ refresh.addEventListener('click', () => loadKnowledge(''))
64
+ copyButton.addEventListener('click', () => copyKnowledgeSection('all', copyButton))
65
+ exportButton.addEventListener('click', exportKnowledge)
66
+ localFilter.addEventListener('change', () => {
67
+ localStatusFilter = localFilter.value
68
+ renderLocal(lastReport?.local)
69
+ })
70
+ search.addEventListener('keydown', event => {
71
+ if (event.key === 'Enter') load()
72
+ })
73
+
74
+ await loadKnowledge('')
75
+ }
76
+
77
+ async function loadKnowledge(query) {
78
+ const params = new URLSearchParams({ limit: '20' })
79
+ if (query) {
80
+ params.set('query', query)
81
+ params.set('recall', '1')
82
+ }
83
+ const report = await fetchJSON(`/api/knowledge?${params.toString()}`)
84
+ const localContainer = $('#knowledge-local')
85
+ if (!report) {
86
+ if (localContainer) renderText(localContainer, t('knowledge.failedToLoad'))
87
+ return
88
+ }
89
+ if (!$('#knowledge-summary')) return
90
+ lastReport = report
91
+ lastQuery = query
92
+ renderSummary(report)
93
+ renderDataNote(report)
94
+ renderOperations(report)
95
+ renderReviewQueue(report)
96
+ renderProviders(report.providers)
97
+ renderRecall(report.recall)
98
+ renderLocal(report.local)
99
+ renderWarnings(report.warnings || [])
100
+ }
101
+
102
+ function renderDataNote(report) {
103
+ const node = $('#knowledge-data-note')
104
+ if (!node) return
105
+ const providerCount = report.providers?.availableProviderCount ?? 0
106
+ node.replaceChildren(dataNote([
107
+ { strong: true, text: t('common.snapshot') },
108
+ `${t('common.lastLoaded')}: ${formatTime(Date.now())}`,
109
+ t('knowledge.dataHint'),
110
+ `${t('knowledge.providersReady')}: ${providerCount}`,
111
+ ]))
112
+ }
113
+
114
+ function renderSummary(report) {
115
+ const summary = $('#knowledge-summary')
116
+ if (!summary) return
117
+ const local = report.local || { total: 0, byStatus: {} }
118
+ const providers = report.providers?.providers || []
119
+ const recallItems = report.recall?.items || []
120
+ summary.replaceChildren(
121
+ metricCard(t('knowledge.localNodes'), local.total ?? 0),
122
+ metricCard(t('knowledge.activeNodes'), local.byStatus?.active ?? 0),
123
+ metricCard(t('knowledge.providersReady'), providers.filter(provider => provider.available).length),
124
+ metricCard(t('knowledge.recalledItems'), recallItems.length),
125
+ )
126
+ }
127
+
128
+ function renderOperations(report) {
129
+ const container = $('#knowledge-operations')
130
+ if (!container) return
131
+ const local = report.local || { total: 0, nodes: [] }
132
+ const providers = report.providers?.providers || []
133
+ const recallItems = report.recall?.items || []
134
+ const actions = [
135
+ actionButton(t('knowledge.copyLocal'), button => copyKnowledgeSection('local', button)),
136
+ actionButton(t('knowledge.exportLocal'), () => exportKnowledgeSection('local')),
137
+ actionButton(t('knowledge.copyRecall'), button => copyKnowledgeSection('recall', button)),
138
+ actionButton(t('knowledge.exportRecall'), () => exportKnowledgeSection('recall')),
139
+ ]
140
+ const rows = [
141
+ sourceRow(t('knowledge.localBrainSource'), local.available ? local.total : 0, true),
142
+ sourceRow(t('knowledge.providerSource'), providers.length, true),
143
+ sourceRow(t('knowledge.recallSource'), recallItems.length, true),
144
+ ]
145
+ container.replaceChildren(
146
+ el('div', { className: 'action-row', style: { marginBottom: '12px' } }, actions),
147
+ dataNote([
148
+ { strong: true, text: t('knowledge.managementScope') },
149
+ t('knowledge.managementScopeHint'),
150
+ t('knowledge.fullProviderExportHint'),
151
+ ]),
152
+ dataTable([t('knowledge.source'), t('knowledge.count'), t('knowledge.exportable')], rows),
153
+ )
154
+ }
155
+
156
+ function renderReviewQueue(report) {
157
+ const container = $('#knowledge-review')
158
+ if (!container) return
159
+ const packet = memoryReviewPacket(report)
160
+ const copy = actionButton(t('knowledge.copyReviewPacket'), button => copyText(JSON.stringify(packet, null, 2), button))
161
+ const exportButton = actionButton(t('knowledge.exportReviewPacket'), () => {
162
+ downloadText(`scale-memory-review-${Date.now()}.json`, JSON.stringify(packet, null, 2), 'application/json;charset=utf-8')
163
+ })
164
+ const summary = packet.summary
165
+ const blocks = [
166
+ reviewBlock(t('knowledge.candidates'), summary.candidate),
167
+ reviewBlock(t('knowledge.stale'), summary.stale),
168
+ reviewBlock(t('knowledge.rejected'), summary.rejected),
169
+ reviewBlock(t('knowledge.missingEvidence'), summary.missingEvidence),
170
+ ]
171
+
172
+ const children = [
173
+ el('div', { className: 'action-row', style: { marginBottom: '12px' } }, [copy, exportButton]),
174
+ el('div', { id: 'knowledge-review-message', className: 'text-muted text-sm', text: reviewMessage, style: { marginBottom: '10px' } }),
175
+ dataNote([{ strong: true, text: t('knowledge.reviewQueue') }, t('knowledge.reviewQueueHint')]),
176
+ el('div', { className: 'review-grid' }, blocks),
177
+ ]
178
+
179
+ if (packet.items.length === 0) {
180
+ children.push(emptyStateWithHint(t('knowledge.noReviewItems'), t('knowledge.noReviewItemsHint'), '\u25cc'))
181
+ } else {
182
+ children.push(el('div', { className: 'list' }, packet.items.map(item => renderReviewItem(item))))
183
+ }
184
+ container.replaceChildren(...children)
185
+ }
186
+
187
+ function reviewBlock(label, count) {
188
+ return el('div', { className: 'review-item' }, [
189
+ el('div', { className: 'review-count', text: count }),
190
+ el('div', { className: 'review-label', text: label }),
191
+ ])
192
+ }
193
+
194
+ function renderReviewItem(item) {
195
+ const copyId = actionButton(t('common.copy'), button => copyText(item.id, button))
196
+ const copyEvidence = actionButton(t('knowledge.evidence'), button => copyText((item.evidencePaths || []).join('\n'), button))
197
+ const reviewActions = reviewActionsFor(item).map(action => actionButton(reviewActionLabel(action), button => reviewMemoryNode(item.id, action, button)))
198
+ return el('div', { className: 'list-item' }, [
199
+ el('div', { style: { display: 'flex', justifyContent: 'space-between', gap: '12px' } }, [
200
+ el('strong', { text: item.title || item.id }),
201
+ el('span', { className: 'status-badge', text: runtimeLabel('status', item.status) }),
202
+ ]),
203
+ el('p', { text: item.summary || '', style: { margin: '6px 0', lineHeight: '1.5' } }),
204
+ el('div', { className: 'text-muted text-sm', text: `${item.type} · ${item.layer} · ${t('common.confidence')} ${formatScore(item.confidence)}` }),
205
+ el('div', { className: 'action-row', style: { marginTop: '8px' } }, [copyId, copyEvidence, ...reviewActions]),
206
+ ])
207
+ }
208
+
209
+ function reviewActionsFor(item) {
210
+ if (item.status === 'candidate') return ['approve', 'reject']
211
+ if (item.status === 'active' && item.reason === 'missing-evidence') return ['stale']
212
+ if (item.status === 'stale' || item.status === 'rejected') return ['restore']
213
+ return []
214
+ }
215
+
216
+ function reviewActionLabel(action) {
217
+ if (action === 'approve') return t('knowledge.approveMemory')
218
+ if (action === 'reject') return t('knowledge.rejectMemory')
219
+ if (action === 'stale') return t('knowledge.markStale')
220
+ return t('knowledge.restoreMemory')
221
+ }
222
+
223
+ async function reviewMemoryNode(id, action, button) {
224
+ const message = $('#knowledge-review-message')
225
+ const original = button.textContent
226
+ button.disabled = true
227
+ button.textContent = t('common.refreshing')
228
+ try {
229
+ const response = await fetch(`/api/knowledge/local/${encodeURIComponent(id)}/review`, {
230
+ method: 'POST',
231
+ headers: { 'Content-Type': 'application/json' },
232
+ body: JSON.stringify({ action, reason: t('knowledge.reviewReason') }),
233
+ })
234
+ const result = await response.json().catch(() => ({}))
235
+ if (!response.ok || !result.success) throw new Error(result.error || `${response.status} ${response.statusText}`)
236
+ reviewMessage = t('knowledge.reviewSucceeded', { id: result.evidence?.id || '-' })
237
+ if (message) message.textContent = reviewMessage
238
+ await loadKnowledge(lastQuery)
239
+ } catch (error) {
240
+ reviewMessage = t('knowledge.reviewFailed', { error: errorMessage(error) })
241
+ if (message) message.textContent = reviewMessage
242
+ } finally {
243
+ button.disabled = false
244
+ button.textContent = original
245
+ }
246
+ }
247
+
248
+ function memoryReviewPacket(report) {
249
+ const nodes = report?.local?.nodes || []
250
+ const items = nodes
251
+ .filter(node => ['candidate', 'stale', 'rejected'].includes(node.status) || (node.status === 'active' && (node.evidencePaths || []).length === 0))
252
+ .map(node => ({
253
+ id: node.id,
254
+ title: node.title,
255
+ summary: node.summary,
256
+ type: node.type,
257
+ layer: node.layer,
258
+ status: node.status,
259
+ confidence: node.confidence,
260
+ evidencePaths: node.evidencePaths || [],
261
+ updatedAt: node.updatedAt,
262
+ reason: reviewReason(node),
263
+ }))
264
+ return {
265
+ exportedAt: new Date().toISOString(),
266
+ project: report?.project ?? null,
267
+ summary: {
268
+ total: nodes.length,
269
+ candidate: nodes.filter(node => node.status === 'candidate').length,
270
+ stale: nodes.filter(node => node.status === 'stale').length,
271
+ rejected: nodes.filter(node => node.status === 'rejected').length,
272
+ missingEvidence: nodes.filter(node => node.status === 'active' && (node.evidencePaths || []).length === 0).length,
273
+ reviewItems: items.length,
274
+ },
275
+ items,
276
+ writePolicy: t('knowledge.managementScopeHint'),
277
+ }
278
+ }
279
+
280
+ function reviewReason(node) {
281
+ if (node.status === 'candidate') return 'candidate'
282
+ if (node.status === 'stale') return 'stale'
283
+ if (node.status === 'rejected') return 'rejected'
284
+ if (node.status === 'active' && (node.evidencePaths || []).length === 0) return 'missing-evidence'
285
+ return 'review'
286
+ }
287
+
288
+ function renderProviders(providersReport) {
289
+ const container = $('#knowledge-providers')
290
+ if (!container) return
291
+ const providers = providersReport?.providers || []
292
+ if (providers.length === 0) {
293
+ container.replaceChildren(emptyState(t('knowledge.noProviders'), '\u25cc'))
294
+ return
295
+ }
296
+ container.replaceChildren(el('div', { className: 'list' }, providers.map(provider => el('div', { className: 'list-item' }, [
297
+ el('div', { style: { display: 'flex', justifyContent: 'space-between', gap: '12px' } }, [
298
+ el('strong', { text: provider.id }),
299
+ el('span', {
300
+ className: provider.available ? 'status-badge passed' : 'status-badge failed',
301
+ text: provider.available ? t('knowledge.available') : t('knowledge.unavailable'),
302
+ }),
303
+ ]),
304
+ el('div', { className: 'text-muted text-sm', text: `${provider.kind || '-'} · ${provider.reason || ''}` }),
305
+ ]))))
306
+ }
307
+
308
+ function renderRecall(recall) {
309
+ const container = $('#knowledge-recall-results')
310
+ if (!container) return
311
+ const items = recall?.items || []
312
+ if (items.length === 0) {
313
+ container.replaceChildren(emptyStateWithHint(t('knowledge.noRecall'), t('knowledge.noRecallHint'), '\u2315'))
314
+ return
315
+ }
316
+ container.replaceChildren(el('div', { className: 'list' }, items.map(item => renderKnowledgeItem({
317
+ title: item.title,
318
+ summary: item.summary,
319
+ status: item.provider,
320
+ meta: `${t('common.score')} ${formatScore(item.score)} · ${t('common.confidence')} ${formatScore(item.confidence)}`,
321
+ evidencePaths: item.evidencePaths || [],
322
+ }))))
323
+ }
324
+
325
+ function renderLocal(local) {
326
+ const container = $('#knowledge-local')
327
+ if (!container) return
328
+ const nodes = local?.nodes || []
329
+ if (!local?.available) {
330
+ container.replaceChildren(emptyStateWithHint(t('knowledge.noLocalBrain'), t('knowledge.noLocalBrainHint'), '\u25cc'))
331
+ return
332
+ }
333
+ if (nodes.length === 0) {
334
+ container.replaceChildren(emptyStateWithHint(t('knowledge.noLocalNodes'), t('knowledge.noLocalNodesHint'), '\u25cc'))
335
+ return
336
+ }
337
+ const filtered = localStatusFilter ? nodes.filter(node => node.status === localStatusFilter) : nodes
338
+ updateLocalFilter(nodes)
339
+ if (filtered.length === 0) {
340
+ container.replaceChildren(emptyStateWithHint(t('common.noData'), t('knowledge.managementScopeHint'), '\u25cc'))
341
+ return
342
+ }
343
+ container.replaceChildren(el('div', { className: 'list' }, filtered.map(node => renderKnowledgeItem({
344
+ title: node.title,
345
+ summary: node.summary,
346
+ status: node.status,
347
+ meta: `${node.type} · ${node.layer} · ${t('common.confidence')} ${formatScore(node.confidence)}`,
348
+ evidencePaths: node.evidencePaths || [],
349
+ }))))
350
+ }
351
+
352
+ function updateLocalFilter(nodes) {
353
+ const filter = $('#knowledge-local-filter')
354
+ if (!filter) return
355
+ const statuses = [...new Set(nodes.map(node => node.status).filter(Boolean))].sort()
356
+ const current = filter.value
357
+ filter.replaceChildren(
358
+ el('option', { value: '', text: t('knowledge.allStatuses') }),
359
+ ...statuses.map(status => el('option', {
360
+ value: status,
361
+ text: runtimeLabel('status', status),
362
+ attrs: { selected: status === current ? true : null },
363
+ })),
364
+ )
365
+ filter.value = statuses.includes(current) ? current : ''
366
+ localStatusFilter = filter.value
367
+ }
368
+
369
+ function renderWarnings(warnings) {
370
+ const container = $('#knowledge-warnings')
371
+ if (!container) return
372
+ if (!warnings.length) {
373
+ container.replaceChildren(el('div', { className: 'text-muted text-sm', text: t('knowledge.noWarnings') }))
374
+ return
375
+ }
376
+ container.replaceChildren(el('ul', {}, warnings.map(warning => el('li', { text: warning }))))
377
+ }
378
+
379
+ function renderKnowledgeItem(item) {
380
+ return el('div', { className: 'list-item' }, [
381
+ el('div', { style: { display: 'flex', justifyContent: 'space-between', gap: '12px' } }, [
382
+ el('strong', { text: item.title || '(untitled)' }),
383
+ el('span', { className: 'status-badge', text: runtimeLabel('status', item.status) }),
384
+ ]),
385
+ el('p', { text: item.summary || '', style: { margin: '6px 0', lineHeight: '1.5' } }),
386
+ el('div', { className: 'text-muted text-sm', text: item.meta || '' }),
387
+ renderEvidence(item.evidencePaths),
388
+ ])
389
+ }
390
+
391
+ function renderEvidence(paths) {
392
+ if (!paths?.length) return el('div', { className: 'text-muted text-sm', text: t('knowledge.noEvidence') })
393
+ return el('div', {
394
+ className: 'text-muted text-sm',
395
+ text: `${t('knowledge.evidence')}: ${paths.slice(0, 3).join(', ')}${paths.length > 3 ? '...' : ''}`,
396
+ })
397
+ }
398
+
399
+ function emptyStateWithHint(message, hint, icon) {
400
+ const state = emptyState(message, icon)
401
+ state.appendChild(el('p', { className: 'text-muted text-sm', text: hint, style: { marginTop: '8px' } }))
402
+ return state
403
+ }
404
+
405
+ function exportKnowledge() {
406
+ const payload = knowledgePayload('all')
407
+ const text = JSON.stringify(payload, null, 2)
408
+ downloadText(`scale-knowledge-${Date.now()}.json`, text, 'application/json;charset=utf-8')
409
+ }
410
+
411
+ function exportKnowledgeSection(section) {
412
+ const text = JSON.stringify(knowledgePayload(section), null, 2)
413
+ downloadText(`scale-knowledge-${section}-${Date.now()}.json`, text, 'application/json;charset=utf-8')
414
+ }
415
+
416
+ function copyKnowledgeSection(section, button) {
417
+ return copyText(JSON.stringify(knowledgePayload(section), null, 2), button)
418
+ }
419
+
420
+ function knowledgePayload(section) {
421
+ const payload = {
422
+ exportedAt: new Date().toISOString(),
423
+ query: lastQuery,
424
+ section,
425
+ }
426
+ if (section === 'local') return { ...payload, local: lastReport?.local ?? null }
427
+ if (section === 'recall') return { ...payload, recall: lastReport?.recall ?? null }
428
+ if (section === 'providers') return { ...payload, providers: lastReport?.providers ?? null }
429
+ return { ...payload, report: lastReport }
430
+ }
431
+
432
+ function actionButton(text, onClick) {
433
+ const button = el('button', { className: 'topo-btn', text })
434
+ button.addEventListener('click', () => onClick(button))
435
+ return button
436
+ }
437
+
438
+ function sourceRow(source, count, exportable) {
439
+ return el('tr', {}, [
440
+ el('td', { text: source }),
441
+ el('td', { text: String(count ?? 0) }),
442
+ el('td', { text: exportable ? t('common.yes') : t('common.no') }),
443
+ ])
444
+ }
445
+
446
+ function formatScore(value) {
447
+ if (typeof value !== 'number' || Number.isNaN(value)) return '-'
448
+ return value.toFixed(2)
449
+ }
450
+
451
+ function errorMessage(error) {
452
+ return error instanceof Error ? error.message : String(error || 'Unknown error')
453
+ }
454
+
455
+ window.DashboardPages = window.DashboardPages || {}
456
+ window.DashboardPages.knowledge = renderKnowledge
457
+ })()