principles-disciple 1.11.0 → 1.13.0

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 (233) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +66 -0
  3. package/esbuild.config.js +1 -1
  4. package/openclaw.plugin.json +4 -4
  5. package/package.json +2 -3
  6. package/run-nocturnal.mjs +30 -0
  7. package/scripts/db-migrate.mjs +170 -0
  8. package/scripts/sync-plugin.mjs +250 -6
  9. package/src/commands/archive-impl.ts +136 -0
  10. package/src/commands/capabilities.ts +4 -2
  11. package/src/commands/context.ts +5 -1
  12. package/src/commands/disable-impl.ts +151 -0
  13. package/src/commands/evolution-status.ts +64 -19
  14. package/src/commands/export.ts +8 -6
  15. package/src/commands/focus.ts +8 -20
  16. package/src/commands/nocturnal-review.ts +5 -7
  17. package/src/commands/nocturnal-rollout.ts +1 -12
  18. package/src/commands/nocturnal-train.ts +21 -47
  19. package/src/commands/pain.ts +10 -5
  20. package/src/commands/principle-rollback.ts +4 -2
  21. package/src/commands/promote-impl.ts +274 -0
  22. package/src/commands/rollback-impl.ts +234 -0
  23. package/src/commands/rollback.ts +6 -3
  24. package/src/commands/samples.ts +2 -0
  25. package/src/commands/thinking-os.ts +3 -4
  26. package/src/commands/workflow-debug.ts +2 -1
  27. package/src/config/errors.ts +1 -0
  28. package/src/core/AGENTS.md +34 -0
  29. package/src/core/adaptive-thresholds.ts +4 -3
  30. package/src/core/code-implementation-storage.ts +241 -0
  31. package/src/core/config.ts +5 -2
  32. package/src/core/control-ui-db.ts +29 -10
  33. package/src/core/detection-funnel.ts +12 -7
  34. package/src/core/diagnostician-task-store.ts +156 -0
  35. package/src/core/dictionary.ts +4 -4
  36. package/src/core/empathy-keyword-matcher.ts +7 -3
  37. package/src/core/empathy-types.ts +13 -2
  38. package/src/core/event-log.ts +14 -6
  39. package/src/core/evolution-engine.ts +27 -31
  40. package/src/core/evolution-logger.ts +3 -2
  41. package/src/core/evolution-reducer.ts +110 -31
  42. package/src/core/evolution-types.ts +10 -0
  43. package/src/core/external-training-contract.ts +1 -0
  44. package/src/core/focus-history.ts +38 -24
  45. package/src/core/hygiene/tracker.ts +10 -6
  46. package/src/core/init.ts +5 -2
  47. package/src/core/migration.ts +3 -3
  48. package/src/core/model-deployment-registry.ts +6 -4
  49. package/src/core/model-training-registry.ts +5 -3
  50. package/src/core/nocturnal-arbiter.ts +13 -14
  51. package/src/core/nocturnal-artifact-lineage.ts +117 -0
  52. package/src/core/nocturnal-artificer.ts +257 -0
  53. package/src/core/nocturnal-candidate-scoring.ts +4 -2
  54. package/src/core/nocturnal-compliance.ts +67 -19
  55. package/src/core/nocturnal-dataset.ts +95 -2
  56. package/src/core/nocturnal-executability.ts +2 -3
  57. package/src/core/nocturnal-export.ts +6 -3
  58. package/src/core/nocturnal-rule-implementation-validator.ts +245 -0
  59. package/src/core/nocturnal-trajectory-extractor.ts +10 -3
  60. package/src/core/nocturnal-trinity.ts +319 -61
  61. package/src/core/pain-context-extractor.ts +29 -15
  62. package/src/core/pain.ts +7 -5
  63. package/src/core/path-resolver.ts +16 -15
  64. package/src/core/paths.ts +2 -1
  65. package/src/core/pd-task-reconciler.ts +463 -0
  66. package/src/core/pd-task-service.ts +42 -0
  67. package/src/core/pd-task-store.ts +77 -0
  68. package/src/core/pd-task-types.ts +128 -0
  69. package/src/core/principle-internalization/deprecated-readiness.ts +91 -0
  70. package/src/core/principle-internalization/internalization-routing-policy.ts +208 -0
  71. package/src/core/principle-internalization/lifecycle-metrics.ts +149 -0
  72. package/src/core/principle-internalization/lifecycle-read-model.ts +243 -0
  73. package/src/core/principle-internalization/lifecycle-refresh.ts +11 -0
  74. package/src/core/principle-internalization/principle-lifecycle-service.ts +167 -0
  75. package/src/core/principle-training-state.ts +95 -370
  76. package/src/core/principle-tree-ledger.ts +733 -0
  77. package/src/core/principle-tree-migration.ts +195 -0
  78. package/src/core/profile.ts +3 -1
  79. package/src/core/promotion-gate.ts +14 -18
  80. package/src/core/replay-engine.ts +562 -0
  81. package/src/core/risk-calculator.ts +6 -4
  82. package/src/core/rule-host-helpers.ts +39 -0
  83. package/src/core/rule-host-types.ts +82 -0
  84. package/src/core/rule-host.ts +245 -0
  85. package/src/core/rule-implementation-runtime.ts +38 -0
  86. package/src/core/schema/db-types.ts +16 -0
  87. package/src/core/schema/index.ts +26 -0
  88. package/src/core/schema/migration-runner.ts +207 -0
  89. package/src/core/schema/migrations/001-init-trajectory.ts +211 -0
  90. package/src/core/schema/migrations/002-init-central.ts +122 -0
  91. package/src/core/schema/migrations/003-init-workflow.ts +55 -0
  92. package/src/core/schema/migrations/004-add-thinking-and-gfi.ts +74 -0
  93. package/src/core/schema/migrations/index.ts +31 -0
  94. package/src/core/schema/schema-definitions.ts +650 -0
  95. package/src/core/session-tracker.ts +6 -4
  96. package/src/core/shadow-observation-registry.ts +6 -3
  97. package/src/core/system-logger.ts +2 -2
  98. package/src/core/thinking-models.ts +182 -46
  99. package/src/core/thinking-os-parser.ts +156 -0
  100. package/src/core/training-program.ts +7 -7
  101. package/src/core/trajectory.ts +42 -36
  102. package/src/core/workspace-context.ts +77 -11
  103. package/src/core/workspace-dir-validation.ts +152 -0
  104. package/src/hooks/AGENTS.md +31 -0
  105. package/src/hooks/bash-risk.ts +3 -1
  106. package/src/hooks/edit-verification.ts +9 -5
  107. package/src/hooks/gate-block-helper.ts +5 -1
  108. package/src/hooks/gate.ts +152 -5
  109. package/src/hooks/gfi-gate.ts +9 -2
  110. package/src/hooks/lifecycle-routing.ts +124 -0
  111. package/src/hooks/lifecycle.ts +12 -12
  112. package/src/hooks/llm.ts +17 -109
  113. package/src/hooks/message-sanitize.ts +5 -3
  114. package/src/hooks/pain.ts +19 -15
  115. package/src/hooks/progressive-trust-gate.ts +7 -1
  116. package/src/hooks/prompt.ts +169 -60
  117. package/src/hooks/subagent.ts +5 -4
  118. package/src/hooks/thinking-checkpoint.ts +2 -0
  119. package/src/hooks/trajectory-collector.ts +15 -12
  120. package/src/http/principles-console-route.ts +31 -68
  121. package/src/i18n/commands.ts +2 -2
  122. package/src/index.ts +130 -40
  123. package/src/service/central-database.ts +131 -43
  124. package/src/service/central-health-service.ts +47 -0
  125. package/src/service/central-overview-service.ts +135 -0
  126. package/src/service/central-sync-service.ts +87 -0
  127. package/src/service/control-ui-query-service.ts +46 -36
  128. package/src/service/event-log-auditor.ts +261 -0
  129. package/src/service/evolution-query-service.ts +23 -22
  130. package/src/service/evolution-worker.ts +565 -261
  131. package/src/service/health-query-service.ts +213 -36
  132. package/src/service/nocturnal-runtime.ts +8 -4
  133. package/src/service/nocturnal-service.ts +503 -59
  134. package/src/service/nocturnal-target-selector.ts +5 -7
  135. package/src/service/runtime-summary-service.ts +2 -1
  136. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +25 -336
  137. package/src/service/subagent-workflow/dynamic-timeout.ts +30 -0
  138. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +48 -386
  139. package/src/service/subagent-workflow/index.ts +2 -0
  140. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +169 -284
  141. package/src/service/subagent-workflow/runtime-direct-driver.ts +114 -16
  142. package/src/service/subagent-workflow/subagent-error-utils.ts +25 -0
  143. package/src/service/subagent-workflow/types.ts +9 -4
  144. package/src/service/subagent-workflow/workflow-manager-base.ts +573 -0
  145. package/src/service/subagent-workflow/workflow-store.ts +71 -11
  146. package/src/service/trajectory-service.ts +2 -1
  147. package/src/tools/critique-prompt.ts +1 -1
  148. package/src/tools/deep-reflect.ts +175 -209
  149. package/src/tools/model-index.ts +2 -1
  150. package/src/types/event-types.ts +2 -2
  151. package/src/types/principle-tree-schema.ts +29 -23
  152. package/src/utils/file-lock.ts +5 -3
  153. package/src/utils/io.ts +5 -2
  154. package/src/utils/nlp.ts +5 -46
  155. package/src/utils/node-vm-polyfill.ts +11 -0
  156. package/src/utils/plugin-logger.ts +2 -0
  157. package/src/utils/retry.ts +572 -0
  158. package/src/utils/subagent-probe.ts +1 -1
  159. package/templates/langs/en/core/AGENTS.md +0 -13
  160. package/templates/langs/en/core/SOUL.md +1 -31
  161. package/templates/langs/en/core/TOOLS.md +0 -4
  162. package/templates/langs/en/principles/THINKING_OS.md +77 -0
  163. package/templates/langs/en/skills/admin/SKILL.md +0 -1
  164. package/templates/langs/en/skills/evolution-framework-update/SKILL.md +1 -1
  165. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +18 -5
  166. package/templates/langs/zh/core/AGENTS.md +0 -22
  167. package/templates/langs/zh/core/SOUL.md +1 -31
  168. package/templates/langs/zh/core/TOOLS.md +0 -4
  169. package/templates/langs/zh/principles/THINKING_OS.md +77 -0
  170. package/templates/langs/zh/skills/admin/SKILL.md +0 -1
  171. package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +1 -1
  172. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +25 -4
  173. package/tests/commands/evolution-status.test.ts +119 -0
  174. package/tests/commands/implementation-lifecycle.test.ts +362 -0
  175. package/tests/core/code-implementation-storage.test.ts +398 -0
  176. package/tests/core/evolution-reducer.detector-metadata.test.ts +28 -28
  177. package/tests/core/nocturnal-artifact-lineage.test.ts +53 -0
  178. package/tests/core/nocturnal-artificer.test.ts +241 -0
  179. package/tests/core/nocturnal-compliance-p-principles.test.ts +133 -0
  180. package/tests/core/nocturnal-rule-implementation-validator.test.ts +127 -0
  181. package/tests/core/pd-task-store.test.ts +126 -0
  182. package/tests/core/principle-internalization/deprecated-readiness.test.ts +193 -0
  183. package/tests/core/principle-internalization/internalization-routing-policy.test.ts +212 -0
  184. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +350 -0
  185. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +211 -0
  186. package/tests/core/principle-training-state.test.ts +228 -1
  187. package/tests/core/principle-tree-ledger.test.ts +423 -0
  188. package/tests/core/regression-v1-9-1.test.ts +265 -0
  189. package/tests/core/replay-engine.test.ts +234 -0
  190. package/tests/core/rule-host-helpers.test.ts +120 -0
  191. package/tests/core/rule-host.test.ts +389 -0
  192. package/tests/core/rule-implementation-runtime.test.ts +64 -0
  193. package/tests/core/workspace-context.test.ts +53 -0
  194. package/tests/core/workspace-dir-validation.test.ts +272 -0
  195. package/tests/hooks/gate-rule-host-pipeline.test.ts +385 -0
  196. package/tests/hooks/pain.test.ts +74 -10
  197. package/tests/hooks/prompt.test.ts +63 -1
  198. package/tests/integration/principle-lifecycle.e2e.test.ts +197 -0
  199. package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +211 -0
  200. package/tests/service/data-endpoints-regression.test.ts +834 -0
  201. package/tests/service/evolution-worker.test.ts +0 -123
  202. package/tests/service/nocturnal-service-code-candidate.test.ts +330 -0
  203. package/tests/utils/nlp.test.ts +1 -19
  204. package/tests/utils/retry.test.ts +327 -0
  205. package/ui/src/App.tsx +1 -1
  206. package/ui/src/api.ts +4 -0
  207. package/ui/src/charts.tsx +366 -0
  208. package/ui/src/components/WorkspaceConfig.tsx +107 -75
  209. package/ui/src/i18n/ui.ts +89 -31
  210. package/ui/src/pages/EvolutionPage.tsx +1 -1
  211. package/ui/src/pages/OverviewPage.tsx +441 -81
  212. package/ui/src/pages/ThinkingModelsPage.tsx +287 -69
  213. package/ui/src/styles.css +43 -0
  214. package/ui/src/types.ts +17 -1
  215. package/src/agents/nocturnal-dreamer.md +0 -152
  216. package/src/agents/nocturnal-philosopher.md +0 -138
  217. package/src/agents/nocturnal-reflector.md +0 -126
  218. package/src/agents/nocturnal-scribe.md +0 -164
  219. package/templates/workspace/.principles/00-kernel.md +0 -51
  220. package/templates/workspace/.principles/DECISION_POLICY.json +0 -44
  221. package/templates/workspace/.principles/PRINCIPLES.md +0 -20
  222. package/templates/workspace/.principles/PROFILE.json +0 -54
  223. package/templates/workspace/.principles/PROFILE.schema.json +0 -56
  224. package/templates/workspace/.principles/THINKING_OS.md +0 -64
  225. package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +0 -7
  226. package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +0 -9
  227. package/templates/workspace/.principles/models/_INDEX.md +0 -27
  228. package/templates/workspace/.principles/models/first_principles.md +0 -62
  229. package/templates/workspace/.principles/models/marketing_4p.md +0 -52
  230. package/templates/workspace/.principles/models/porter_five.md +0 -63
  231. package/templates/workspace/.principles/models/swot.md +0 -60
  232. package/templates/workspace/.principles/models/user_story_map.md +0 -63
  233. package/templates/workspace/.state/WORKBOARD.json +0 -4
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Tests for Unified Retry Utilities
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
6
+ import {
7
+ retryAsync,
8
+ retryAsyncWithInfo,
9
+ withRetry,
10
+ retryLLMCall,
11
+ retryFileOperation,
12
+ retryNetworkRequest,
13
+ isRetryableError,
14
+ computeDynamicTimeout,
15
+ computeRetrySchedule,
16
+ computeAdaptiveTimeout,
17
+ percentile,
18
+ clampTimeout,
19
+ MAX_TIMEOUT_RETRIES,
20
+ RETRY_BACKOFF_MULTIPLIER,
21
+ MIN_TIMEOUT_MS,
22
+ MAX_TIMEOUT_MS,
23
+ type DurationDataSource,
24
+ } from '../../src/utils/retry.js';
25
+
26
+ describe('retryAsync', () => {
27
+ beforeEach(() => {
28
+ vi.useFakeTimers();
29
+ });
30
+
31
+ afterEach(() => {
32
+ vi.useRealTimers();
33
+ });
34
+
35
+ it('should return result on first successful call', async () => {
36
+ const fn = vi.fn().mockResolvedValue('success');
37
+ const result = await retryAsync(fn);
38
+ expect(result).toBe('success');
39
+ expect(fn).toHaveBeenCalledTimes(1);
40
+ });
41
+
42
+ it('should retry on retryable error', async () => {
43
+ const fn = vi.fn()
44
+ .mockRejectedValueOnce(new Error('ETIMEDOUT'))
45
+ .mockResolvedValue('success');
46
+
47
+ const resultPromise = retryAsync(fn, { initialDelayMs: 100 });
48
+
49
+ // Advance past first retry delay
50
+ await vi.advanceTimersByTimeAsync(100);
51
+
52
+ const result = await resultPromise;
53
+ expect(result).toBe('success');
54
+ expect(fn).toHaveBeenCalledTimes(2);
55
+ });
56
+
57
+ it('should throw after max retries exceeded', async () => {
58
+ vi.useRealTimers(); // Use real timers for this test
59
+
60
+ const fn = vi.fn().mockRejectedValue(new Error('ETIMEDOUT'));
61
+
62
+ await expect(retryAsync(fn, { maxRetries: 1, initialDelayMs: 1, logger: { warn: vi.fn() } }))
63
+ .rejects.toThrow('ETIMEDOUT');
64
+ expect(fn).toHaveBeenCalledTimes(2); // initial + 1 retry
65
+ });
66
+
67
+ it('should not retry on non-retryable error', async () => {
68
+ const error = new Error('Invalid input');
69
+ const fn = vi.fn().mockRejectedValue(error);
70
+
71
+ await expect(retryAsync(fn)).rejects.toThrow('Invalid input');
72
+ expect(fn).toHaveBeenCalledTimes(1);
73
+ });
74
+
75
+ it('should use custom isRetryable function', async () => {
76
+ const fn = vi.fn()
77
+ .mockRejectedValueOnce(new Error('custom error'))
78
+ .mockResolvedValue('success');
79
+
80
+ const resultPromise = retryAsync(fn, {
81
+ initialDelayMs: 100,
82
+ isRetryable: (err) => String(err).includes('custom'),
83
+ });
84
+
85
+ await vi.advanceTimersByTimeAsync(100);
86
+
87
+ const result = await resultPromise;
88
+ expect(result).toBe('success');
89
+ expect(fn).toHaveBeenCalledTimes(2);
90
+ });
91
+ });
92
+
93
+ describe('retryAsyncWithInfo', () => {
94
+ beforeEach(() => {
95
+ vi.useFakeTimers();
96
+ });
97
+
98
+ afterEach(() => {
99
+ vi.useRealTimers();
100
+ });
101
+
102
+ it('should return detailed result info', async () => {
103
+ const fn = vi.fn().mockResolvedValue('success');
104
+ const result = await retryAsyncWithInfo(fn);
105
+
106
+ expect(result.value).toBe('success');
107
+ expect(result.attempts).toBe(1);
108
+ expect(result.totalTimeMs).toBeGreaterThanOrEqual(0);
109
+ });
110
+ });
111
+
112
+ describe('withRetry', () => {
113
+ beforeEach(() => {
114
+ vi.useFakeTimers();
115
+ });
116
+
117
+ afterEach(() => {
118
+ vi.useRealTimers();
119
+ });
120
+
121
+ it('should create a retry wrapper', async () => {
122
+ const fn = vi.fn()
123
+ .mockRejectedValueOnce(new Error('ETIMEDOUT'))
124
+ .mockResolvedValue('success');
125
+
126
+ const wrapped = withRetry(fn, { initialDelayMs: 100 });
127
+ const resultPromise = wrapped('arg1', 'arg2');
128
+
129
+ await vi.advanceTimersByTimeAsync(100);
130
+
131
+ const result = await resultPromise;
132
+ expect(result).toBe('success');
133
+ expect(fn).toHaveBeenCalledWith('arg1', 'arg2');
134
+ });
135
+ });
136
+
137
+ describe('isRetryableError', () => {
138
+ it('should detect timeout errors', () => {
139
+ expect(isRetryableError(new Error('ETIMEDOUT'))).toBe(true);
140
+ expect(isRetryableError(new Error('connection timeout'))).toBe(true);
141
+ });
142
+
143
+ it('should detect rate limit errors', () => {
144
+ expect(isRetryableError(new Error('rate limit exceeded'))).toBe(true);
145
+ expect(isRetryableError(new Error('429 Too Many Requests'))).toBe(true);
146
+ });
147
+
148
+ it('should detect LLM errors', () => {
149
+ expect(isRetryableError(new Error('service overloaded'))).toBe(true);
150
+ expect(isRetryableError(new Error('503 Service Unavailable'))).toBe(true);
151
+ });
152
+
153
+ it('should detect OpenClaw errors', () => {
154
+ expect(isRetryableError(new Error('missing scope: operator.admin'))).toBe(true);
155
+ expect(isRetryableError(new Error('gateway request required'))).toBe(true);
156
+ });
157
+
158
+ it('should return false for non-retryable errors', () => {
159
+ expect(isRetryableError(new Error('Invalid input'))).toBe(false);
160
+ expect(isRetryableError(new Error('File not found'))).toBe(false);
161
+ expect(isRetryableError(null)).toBe(false);
162
+ expect(isRetryableError(undefined)).toBe(false);
163
+ });
164
+ });
165
+
166
+ describe('computeDynamicTimeout', () => {
167
+ const createMockDataSource = (durations: number[]): DurationDataSource => ({
168
+ getCompletionDurations: vi.fn().mockReturnValue(durations),
169
+ });
170
+
171
+ it('should return fallback when insufficient samples', () => {
172
+ const source = createMockDataSource([1000, 2000]);
173
+ const result = computeDynamicTimeout(source, 'test-workflow', 30000);
174
+
175
+ expect(result).toBe(30000);
176
+ });
177
+
178
+ it('should compute P95 × safety multiplier', () => {
179
+ // Create data where P95 is around 10000
180
+ const durations = Array(50).fill(0).map((_, i) => 1000 + i * 200);
181
+ const source = createMockDataSource(durations);
182
+ const result = computeDynamicTimeout(source, 'test-workflow', 30000);
183
+
184
+ // P95 should be around 10800, multiplied by 1.5 = ~16200
185
+ expect(result).toBeGreaterThan(10000);
186
+ expect(result).toBeLessThan(30000);
187
+ });
188
+
189
+ it('should clamp to min timeout', () => {
190
+ const durations = [100, 200, 300, 400, 500];
191
+ const source = createMockDataSource(durations);
192
+ const result = computeDynamicTimeout(source, 'test-workflow', 1000);
193
+
194
+ expect(result).toBe(MIN_TIMEOUT_MS);
195
+ });
196
+
197
+ it('should clamp to max timeout', () => {
198
+ const durations = Array(50).fill(0).map(() => 1_000_000);
199
+ const source = createMockDataSource(durations);
200
+ const result = computeDynamicTimeout(source, 'test-workflow', 30000);
201
+
202
+ expect(result).toBe(MAX_TIMEOUT_MS);
203
+ });
204
+ });
205
+
206
+ describe('computeRetrySchedule', () => {
207
+ it('should generate exponential backoff schedule', () => {
208
+ const schedule = computeRetrySchedule(30000);
209
+
210
+ expect(schedule).toHaveLength(MAX_TIMEOUT_RETRIES + 1);
211
+ expect(schedule[0]).toBe(30000);
212
+ expect(schedule[1]).toBe(60000);
213
+ expect(schedule[2]).toBe(120000);
214
+ });
215
+
216
+ it('should clamp values to max timeout', () => {
217
+ const schedule = computeRetrySchedule(200000);
218
+
219
+ // 200000 × 2 = 400000, but should clamp to MAX_TIMEOUT_MS
220
+ expect(schedule[1]).toBeLessThanOrEqual(MAX_TIMEOUT_MS);
221
+ });
222
+ });
223
+
224
+ describe('computeAdaptiveTimeout', () => {
225
+ it('should return fallback when insufficient samples', () => {
226
+ const result = computeAdaptiveTimeout([1000, 2000], 30000);
227
+ expect(result).toBe(30000);
228
+ });
229
+
230
+ it('should compute P95 × safety multiplier', () => {
231
+ const durations = Array(50).fill(0).map((_, i) => 1000 + i * 200);
232
+ const result = computeAdaptiveTimeout(durations, 30000);
233
+
234
+ expect(result).toBeGreaterThan(10000);
235
+ expect(result).toBeLessThan(30000);
236
+ });
237
+
238
+ it('should respect custom options', () => {
239
+ const durations = [100, 200, 300];
240
+ const result = computeAdaptiveTimeout(durations, 10000, {
241
+ minSamples: 1,
242
+ safetyMultiplier: 3,
243
+ });
244
+
245
+ // P95 ≈ 300, × 3 = 900, clamped to min
246
+ expect(result).toBe(MIN_TIMEOUT_MS);
247
+ });
248
+ });
249
+
250
+ describe('percentile', () => {
251
+ it('should compute P50 (median)', () => {
252
+ expect(percentile([1, 2, 3, 4, 5], 50)).toBe(3);
253
+ });
254
+
255
+ it('should compute P95', () => {
256
+ const values = Array(100).fill(0).map((_, i) => i + 1);
257
+ expect(percentile(values, 95)).toBe(95);
258
+ });
259
+
260
+ it('should return median for small samples', () => {
261
+ expect(percentile([1, 2, 3], 95)).toBe(2);
262
+ });
263
+
264
+ it('should handle empty array', () => {
265
+ expect(percentile([], 50)).toBe(0);
266
+ });
267
+ });
268
+
269
+ describe('clampTimeout', () => {
270
+ it('should clamp to min', () => {
271
+ expect(clampTimeout(100)).toBe(MIN_TIMEOUT_MS);
272
+ });
273
+
274
+ it('should clamp to max', () => {
275
+ expect(clampTimeout(1_000_000)).toBe(MAX_TIMEOUT_MS);
276
+ });
277
+
278
+ it('should keep value in range', () => {
279
+ expect(clampTimeout(60000)).toBe(60000);
280
+ });
281
+ });
282
+
283
+ describe('preset retry functions', () => {
284
+ beforeEach(() => {
285
+ vi.useFakeTimers();
286
+ });
287
+
288
+ afterEach(() => {
289
+ vi.useRealTimers();
290
+ });
291
+
292
+ it('retryLLMCall should have appropriate defaults', async () => {
293
+ const fn = vi.fn()
294
+ .mockRejectedValueOnce(new Error('ETIMEDOUT'))
295
+ .mockResolvedValue('success');
296
+
297
+ const resultPromise = retryLLMCall(fn);
298
+ await vi.advanceTimersByTimeAsync(2000);
299
+
300
+ const result = await resultPromise;
301
+ expect(result).toBe('success');
302
+ });
303
+
304
+ it('retryFileOperation should have appropriate defaults', async () => {
305
+ const fn = vi.fn()
306
+ .mockRejectedValueOnce(new Error('EBUSY'))
307
+ .mockResolvedValue('success');
308
+
309
+ const resultPromise = retryFileOperation(fn);
310
+ await vi.advanceTimersByTimeAsync(50);
311
+
312
+ const result = await resultPromise;
313
+ expect(result).toBe('success');
314
+ });
315
+
316
+ it('retryNetworkRequest should have appropriate defaults', async () => {
317
+ const fn = vi.fn()
318
+ .mockRejectedValueOnce(new Error('ECONNRESET'))
319
+ .mockResolvedValue('success');
320
+
321
+ const resultPromise = retryNetworkRequest(fn);
322
+ await vi.advanceTimersByTimeAsync(1000);
323
+
324
+ const result = await resultPromise;
325
+ expect(result).toBe('success');
326
+ });
327
+ });
package/ui/src/App.tsx CHANGED
@@ -31,7 +31,7 @@ function AppRoutes() {
31
31
 
32
32
  export default function App() {
33
33
  return (
34
- <BrowserRouter>
34
+ <BrowserRouter basename="/plugins/principles">
35
35
  <ThemeProvider>
36
36
  <AuthProvider>
37
37
  <Routes>
package/ui/src/api.ts CHANGED
@@ -9,6 +9,7 @@ import type {
9
9
  EvolutionTraceResponse,
10
10
  EvolutionStatsResponse,
11
11
  OverviewHealthResponse,
12
+ CentralHealthResponse,
12
13
  EvolutionPrinciplesResponse,
13
14
  FeedbackGfiResponse,
14
15
  EmpathyEvent,
@@ -192,6 +193,9 @@ export const api = {
192
193
  getOverviewHealth(): Promise<OverviewHealthResponse> {
193
194
  return requestJson('/plugins/principles/api/overview/health');
194
195
  },
196
+ getCentralHealth(): Promise<CentralHealthResponse> {
197
+ return requestJson('/plugins/principles/api/central/health');
198
+ },
195
199
  getEvolutionPrinciples(): Promise<EvolutionPrinciplesResponse> {
196
200
  return requestJson('/plugins/principles/api/evolution/principles');
197
201
  },