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.
- package/CHANGELOG.md +26 -0
- package/README.md +66 -0
- package/esbuild.config.js +1 -1
- package/openclaw.plugin.json +4 -4
- package/package.json +2 -3
- package/run-nocturnal.mjs +30 -0
- package/scripts/db-migrate.mjs +170 -0
- package/scripts/sync-plugin.mjs +250 -6
- package/src/commands/archive-impl.ts +136 -0
- package/src/commands/capabilities.ts +4 -2
- package/src/commands/context.ts +5 -1
- package/src/commands/disable-impl.ts +151 -0
- package/src/commands/evolution-status.ts +64 -19
- package/src/commands/export.ts +8 -6
- package/src/commands/focus.ts +8 -20
- package/src/commands/nocturnal-review.ts +5 -7
- package/src/commands/nocturnal-rollout.ts +1 -12
- package/src/commands/nocturnal-train.ts +21 -47
- package/src/commands/pain.ts +10 -5
- package/src/commands/principle-rollback.ts +4 -2
- package/src/commands/promote-impl.ts +274 -0
- package/src/commands/rollback-impl.ts +234 -0
- package/src/commands/rollback.ts +6 -3
- package/src/commands/samples.ts +2 -0
- package/src/commands/thinking-os.ts +3 -4
- package/src/commands/workflow-debug.ts +2 -1
- package/src/config/errors.ts +1 -0
- package/src/core/AGENTS.md +34 -0
- package/src/core/adaptive-thresholds.ts +4 -3
- package/src/core/code-implementation-storage.ts +241 -0
- package/src/core/config.ts +5 -2
- package/src/core/control-ui-db.ts +29 -10
- package/src/core/detection-funnel.ts +12 -7
- package/src/core/diagnostician-task-store.ts +156 -0
- package/src/core/dictionary.ts +4 -4
- package/src/core/empathy-keyword-matcher.ts +7 -3
- package/src/core/empathy-types.ts +13 -2
- package/src/core/event-log.ts +14 -6
- package/src/core/evolution-engine.ts +27 -31
- package/src/core/evolution-logger.ts +3 -2
- package/src/core/evolution-reducer.ts +110 -31
- package/src/core/evolution-types.ts +10 -0
- package/src/core/external-training-contract.ts +1 -0
- package/src/core/focus-history.ts +38 -24
- package/src/core/hygiene/tracker.ts +10 -6
- package/src/core/init.ts +5 -2
- package/src/core/migration.ts +3 -3
- package/src/core/model-deployment-registry.ts +6 -4
- package/src/core/model-training-registry.ts +5 -3
- package/src/core/nocturnal-arbiter.ts +13 -14
- package/src/core/nocturnal-artifact-lineage.ts +117 -0
- package/src/core/nocturnal-artificer.ts +257 -0
- package/src/core/nocturnal-candidate-scoring.ts +4 -2
- package/src/core/nocturnal-compliance.ts +67 -19
- package/src/core/nocturnal-dataset.ts +95 -2
- package/src/core/nocturnal-executability.ts +2 -3
- package/src/core/nocturnal-export.ts +6 -3
- package/src/core/nocturnal-rule-implementation-validator.ts +245 -0
- package/src/core/nocturnal-trajectory-extractor.ts +10 -3
- package/src/core/nocturnal-trinity.ts +319 -61
- package/src/core/pain-context-extractor.ts +29 -15
- package/src/core/pain.ts +7 -5
- package/src/core/path-resolver.ts +16 -15
- package/src/core/paths.ts +2 -1
- package/src/core/pd-task-reconciler.ts +463 -0
- package/src/core/pd-task-service.ts +42 -0
- package/src/core/pd-task-store.ts +77 -0
- package/src/core/pd-task-types.ts +128 -0
- package/src/core/principle-internalization/deprecated-readiness.ts +91 -0
- package/src/core/principle-internalization/internalization-routing-policy.ts +208 -0
- package/src/core/principle-internalization/lifecycle-metrics.ts +149 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +243 -0
- package/src/core/principle-internalization/lifecycle-refresh.ts +11 -0
- package/src/core/principle-internalization/principle-lifecycle-service.ts +167 -0
- package/src/core/principle-training-state.ts +95 -370
- package/src/core/principle-tree-ledger.ts +733 -0
- package/src/core/principle-tree-migration.ts +195 -0
- package/src/core/profile.ts +3 -1
- package/src/core/promotion-gate.ts +14 -18
- package/src/core/replay-engine.ts +562 -0
- package/src/core/risk-calculator.ts +6 -4
- package/src/core/rule-host-helpers.ts +39 -0
- package/src/core/rule-host-types.ts +82 -0
- package/src/core/rule-host.ts +245 -0
- package/src/core/rule-implementation-runtime.ts +38 -0
- package/src/core/schema/db-types.ts +16 -0
- package/src/core/schema/index.ts +26 -0
- package/src/core/schema/migration-runner.ts +207 -0
- package/src/core/schema/migrations/001-init-trajectory.ts +211 -0
- package/src/core/schema/migrations/002-init-central.ts +122 -0
- package/src/core/schema/migrations/003-init-workflow.ts +55 -0
- package/src/core/schema/migrations/004-add-thinking-and-gfi.ts +74 -0
- package/src/core/schema/migrations/index.ts +31 -0
- package/src/core/schema/schema-definitions.ts +650 -0
- package/src/core/session-tracker.ts +6 -4
- package/src/core/shadow-observation-registry.ts +6 -3
- package/src/core/system-logger.ts +2 -2
- package/src/core/thinking-models.ts +182 -46
- package/src/core/thinking-os-parser.ts +156 -0
- package/src/core/training-program.ts +7 -7
- package/src/core/trajectory.ts +42 -36
- package/src/core/workspace-context.ts +77 -11
- package/src/core/workspace-dir-validation.ts +152 -0
- package/src/hooks/AGENTS.md +31 -0
- package/src/hooks/bash-risk.ts +3 -1
- package/src/hooks/edit-verification.ts +9 -5
- package/src/hooks/gate-block-helper.ts +5 -1
- package/src/hooks/gate.ts +152 -5
- package/src/hooks/gfi-gate.ts +9 -2
- package/src/hooks/lifecycle-routing.ts +124 -0
- package/src/hooks/lifecycle.ts +12 -12
- package/src/hooks/llm.ts +17 -109
- package/src/hooks/message-sanitize.ts +5 -3
- package/src/hooks/pain.ts +19 -15
- package/src/hooks/progressive-trust-gate.ts +7 -1
- package/src/hooks/prompt.ts +169 -60
- package/src/hooks/subagent.ts +5 -4
- package/src/hooks/thinking-checkpoint.ts +2 -0
- package/src/hooks/trajectory-collector.ts +15 -12
- package/src/http/principles-console-route.ts +31 -68
- package/src/i18n/commands.ts +2 -2
- package/src/index.ts +130 -40
- package/src/service/central-database.ts +131 -43
- package/src/service/central-health-service.ts +47 -0
- package/src/service/central-overview-service.ts +135 -0
- package/src/service/central-sync-service.ts +87 -0
- package/src/service/control-ui-query-service.ts +46 -36
- package/src/service/event-log-auditor.ts +261 -0
- package/src/service/evolution-query-service.ts +23 -22
- package/src/service/evolution-worker.ts +565 -261
- package/src/service/health-query-service.ts +213 -36
- package/src/service/nocturnal-runtime.ts +8 -4
- package/src/service/nocturnal-service.ts +503 -59
- package/src/service/nocturnal-target-selector.ts +5 -7
- package/src/service/runtime-summary-service.ts +2 -1
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +25 -336
- package/src/service/subagent-workflow/dynamic-timeout.ts +30 -0
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +48 -386
- package/src/service/subagent-workflow/index.ts +2 -0
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +169 -284
- package/src/service/subagent-workflow/runtime-direct-driver.ts +114 -16
- package/src/service/subagent-workflow/subagent-error-utils.ts +25 -0
- package/src/service/subagent-workflow/types.ts +9 -4
- package/src/service/subagent-workflow/workflow-manager-base.ts +573 -0
- package/src/service/subagent-workflow/workflow-store.ts +71 -11
- package/src/service/trajectory-service.ts +2 -1
- package/src/tools/critique-prompt.ts +1 -1
- package/src/tools/deep-reflect.ts +175 -209
- package/src/tools/model-index.ts +2 -1
- package/src/types/event-types.ts +2 -2
- package/src/types/principle-tree-schema.ts +29 -23
- package/src/utils/file-lock.ts +5 -3
- package/src/utils/io.ts +5 -2
- package/src/utils/nlp.ts +5 -46
- package/src/utils/node-vm-polyfill.ts +11 -0
- package/src/utils/plugin-logger.ts +2 -0
- package/src/utils/retry.ts +572 -0
- package/src/utils/subagent-probe.ts +1 -1
- package/templates/langs/en/core/AGENTS.md +0 -13
- package/templates/langs/en/core/SOUL.md +1 -31
- package/templates/langs/en/core/TOOLS.md +0 -4
- package/templates/langs/en/principles/THINKING_OS.md +77 -0
- package/templates/langs/en/skills/admin/SKILL.md +0 -1
- package/templates/langs/en/skills/evolution-framework-update/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +18 -5
- package/templates/langs/zh/core/AGENTS.md +0 -22
- package/templates/langs/zh/core/SOUL.md +1 -31
- package/templates/langs/zh/core/TOOLS.md +0 -4
- package/templates/langs/zh/principles/THINKING_OS.md +77 -0
- package/templates/langs/zh/skills/admin/SKILL.md +0 -1
- package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +25 -4
- package/tests/commands/evolution-status.test.ts +119 -0
- package/tests/commands/implementation-lifecycle.test.ts +362 -0
- package/tests/core/code-implementation-storage.test.ts +398 -0
- package/tests/core/evolution-reducer.detector-metadata.test.ts +28 -28
- package/tests/core/nocturnal-artifact-lineage.test.ts +53 -0
- package/tests/core/nocturnal-artificer.test.ts +241 -0
- package/tests/core/nocturnal-compliance-p-principles.test.ts +133 -0
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +127 -0
- package/tests/core/pd-task-store.test.ts +126 -0
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +193 -0
- package/tests/core/principle-internalization/internalization-routing-policy.test.ts +212 -0
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +350 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +211 -0
- package/tests/core/principle-training-state.test.ts +228 -1
- package/tests/core/principle-tree-ledger.test.ts +423 -0
- package/tests/core/regression-v1-9-1.test.ts +265 -0
- package/tests/core/replay-engine.test.ts +234 -0
- package/tests/core/rule-host-helpers.test.ts +120 -0
- package/tests/core/rule-host.test.ts +389 -0
- package/tests/core/rule-implementation-runtime.test.ts +64 -0
- package/tests/core/workspace-context.test.ts +53 -0
- package/tests/core/workspace-dir-validation.test.ts +272 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +385 -0
- package/tests/hooks/pain.test.ts +74 -10
- package/tests/hooks/prompt.test.ts +63 -1
- package/tests/integration/principle-lifecycle.e2e.test.ts +197 -0
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +211 -0
- package/tests/service/data-endpoints-regression.test.ts +834 -0
- package/tests/service/evolution-worker.test.ts +0 -123
- package/tests/service/nocturnal-service-code-candidate.test.ts +330 -0
- package/tests/utils/nlp.test.ts +1 -19
- package/tests/utils/retry.test.ts +327 -0
- package/ui/src/App.tsx +1 -1
- package/ui/src/api.ts +4 -0
- package/ui/src/charts.tsx +366 -0
- package/ui/src/components/WorkspaceConfig.tsx +107 -75
- package/ui/src/i18n/ui.ts +89 -31
- package/ui/src/pages/EvolutionPage.tsx +1 -1
- package/ui/src/pages/OverviewPage.tsx +441 -81
- package/ui/src/pages/ThinkingModelsPage.tsx +287 -69
- package/ui/src/styles.css +43 -0
- package/ui/src/types.ts +17 -1
- package/src/agents/nocturnal-dreamer.md +0 -152
- package/src/agents/nocturnal-philosopher.md +0 -138
- package/src/agents/nocturnal-reflector.md +0 -126
- package/src/agents/nocturnal-scribe.md +0 -164
- package/templates/workspace/.principles/00-kernel.md +0 -51
- package/templates/workspace/.principles/DECISION_POLICY.json +0 -44
- package/templates/workspace/.principles/PRINCIPLES.md +0 -20
- package/templates/workspace/.principles/PROFILE.json +0 -54
- package/templates/workspace/.principles/PROFILE.schema.json +0 -56
- package/templates/workspace/.principles/THINKING_OS.md +0 -64
- package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +0 -7
- package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +0 -9
- package/templates/workspace/.principles/models/_INDEX.md +0 -27
- package/templates/workspace/.principles/models/first_principles.md +0 -62
- package/templates/workspace/.principles/models/marketing_4p.md +0 -52
- package/templates/workspace/.principles/models/porter_five.md +0 -63
- package/templates/workspace/.principles/models/swot.md +0 -60
- package/templates/workspace/.principles/models/user_story_map.md +0 -63
- 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
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
|
},
|