principles-disciple 1.7.5 → 1.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/commands/evolution-status.js +32 -44
  2. package/dist/config/defaults/runtime.d.ts +40 -0
  3. package/dist/config/defaults/runtime.js +44 -0
  4. package/dist/config/errors.d.ts +84 -0
  5. package/dist/config/errors.js +94 -0
  6. package/dist/config/index.d.ts +7 -0
  7. package/dist/config/index.js +7 -0
  8. package/dist/constants/diagnostician.d.ts +0 -4
  9. package/dist/constants/diagnostician.js +0 -4
  10. package/dist/core/control-ui-db.d.ts +27 -0
  11. package/dist/core/control-ui-db.js +18 -0
  12. package/dist/core/path-resolver.js +2 -1
  13. package/dist/core/trajectory.d.ts +60 -0
  14. package/dist/core/trajectory.js +72 -2
  15. package/dist/hooks/bash-risk.d.ts +57 -0
  16. package/dist/hooks/bash-risk.js +137 -0
  17. package/dist/hooks/edit-verification.d.ts +62 -0
  18. package/dist/hooks/edit-verification.js +256 -0
  19. package/dist/hooks/gate-block-helper.d.ts +44 -0
  20. package/dist/hooks/gate-block-helper.js +119 -0
  21. package/dist/hooks/gate.d.ts +18 -0
  22. package/dist/hooks/gate.js +62 -751
  23. package/dist/hooks/gfi-gate.d.ts +40 -0
  24. package/dist/hooks/gfi-gate.js +112 -0
  25. package/dist/hooks/progressive-trust-gate.d.ts +79 -0
  26. package/dist/hooks/progressive-trust-gate.js +242 -0
  27. package/dist/hooks/prompt.js +10 -6
  28. package/dist/hooks/thinking-checkpoint.d.ts +37 -0
  29. package/dist/hooks/thinking-checkpoint.js +51 -0
  30. package/dist/http/principles-console-route.js +13 -3
  31. package/dist/service/central-database.js +2 -1
  32. package/dist/service/control-ui-query-service.d.ts +1 -1
  33. package/dist/service/control-ui-query-service.js +3 -3
  34. package/dist/service/evolution-query-service.d.ts +1 -1
  35. package/dist/service/evolution-query-service.js +5 -5
  36. package/dist/service/evolution-worker.d.ts +10 -0
  37. package/dist/service/evolution-worker.js +7 -3
  38. package/dist/service/phase3-input-filter.d.ts +57 -0
  39. package/dist/service/phase3-input-filter.js +93 -3
  40. package/dist/service/runtime-summary-service.d.ts +34 -0
  41. package/dist/service/runtime-summary-service.js +93 -1
  42. package/dist/types/event-types.d.ts +2 -0
  43. package/dist/types/runtime-summary.d.ts +54 -0
  44. package/dist/types/runtime-summary.js +1 -0
  45. package/openclaw.plugin.json +1 -1
  46. package/package.json +1 -1
@@ -210,7 +210,7 @@ export declare class ControlUiQueryService {
210
210
  private readonly uiDb;
211
211
  constructor(workspaceDir: string);
212
212
  dispose(): void;
213
- getOverview(): OverviewResponse;
213
+ getOverview(days?: number): OverviewResponse;
214
214
  listSamples(filters?: SampleListFilters): SamplesResponse;
215
215
  getSampleDetail(sampleId: string): SampleDetailResponse | null;
216
216
  reviewSample(sampleId: string, decision: 'approved' | 'rejected', note?: string): import("../core/trajectory.js").CorrectionSampleRecord;
@@ -43,7 +43,7 @@ export class ControlUiQueryService {
43
43
  dispose() {
44
44
  this.uiDb.dispose();
45
45
  }
46
- getOverview() {
46
+ getOverview(days = 30) {
47
47
  const stats = this.trajectory.getDataStats();
48
48
  const regressionRows = this.uiDb.all('SELECT tool_name, error_type, occurrences FROM v_error_clusters ORDER BY occurrences DESC LIMIT 5');
49
49
  const failureStats = this.uiDb.get(`
@@ -92,8 +92,8 @@ export class ControlUiQueryService {
92
92
  FROM v_daily_metrics dm
93
93
  LEFT JOIN thinking_daily td ON td.day = dm.day
94
94
  ORDER BY dm.day DESC
95
- LIMIT 14
96
- `).reverse();
95
+ LIMIT ?
96
+ `, days).reverse();
97
97
  const counters = Object.fromEntries(sampleCounters.map((row) => [row.review_status, Number(row.total)]));
98
98
  const activeModels = this.uiDb.get('SELECT COUNT(DISTINCT model_id) AS count FROM thinking_model_events')?.count ?? 0;
99
99
  return {
@@ -147,7 +147,7 @@ export declare class EvolutionQueryService {
147
147
  /**
148
148
  * 获取统计数据
149
149
  */
150
- getStats(): EvolutionStatsResponse;
150
+ getStats(days?: number): EvolutionStatsResponse;
151
151
  }
152
152
  /**
153
153
  * 获取 EvolutionQueryService 实例(单例)
@@ -186,19 +186,19 @@ export class EvolutionQueryService {
186
186
  /**
187
187
  * 获取统计数据
188
188
  */
189
- getStats() {
189
+ getStats(days = 30) {
190
190
  // 获取基础统计
191
191
  const stats = this.trajectory.getEvolutionStats();
192
- // 获取近期活动(最近 7 天)
192
+ // 获取近期活动
193
193
  const now = new Date();
194
- const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
194
+ const daysAgo = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
195
195
  const recentTasks = this.trajectory.listEvolutionTasks({
196
- dateFrom: sevenDaysAgo.toISOString(),
196
+ dateFrom: daysAgo.toISOString(),
197
197
  limit: 10000,
198
198
  });
199
199
  // 按天分组
200
200
  const activityByDay = new Map();
201
- for (let i = 0; i < 7; i++) {
201
+ for (let i = 0; i < days; i++) {
202
202
  const day = new Date(now.getTime() - i * 24 * 60 * 60 * 1000);
203
203
  const dayStr = day.toISOString().split('T')[0];
204
204
  activityByDay.set(dayStr, { created: 0, completed: 0 });
@@ -44,6 +44,16 @@ export declare function registerEvolutionTaskSession(workspaceResolve: (key: str
44
44
  warn?: (message: string) => void;
45
45
  info?: (message: string) => void;
46
46
  }): Promise<boolean>;
47
+ /**
48
+ * Evolution Worker - Background service for pain processing and evolution task management.
49
+ *
50
+ * IMPORTANT: evolution_directive.json is a COMPATIBILITY-ONLY DISPLAY ARTIFACT.
51
+ * This service does NOT read or use directive for Phase 3 eligibility or any decisions.
52
+ * Queue (EVOLUTION_QUEUE) is the only authoritative execution truth source.
53
+ *
54
+ * Directive exists solely for UI/backwards compatibility display purposes.
55
+ * Production evidence shows directive stopped updating on 2026-03-22 and is stale.
56
+ */
47
57
  export interface ExtendedEvolutionWorkerService {
48
58
  id: string;
49
59
  api: OpenClawPluginApi | null;
@@ -11,7 +11,9 @@ import { initPersistence, flushAllSessions } from '../core/session-tracker.js';
11
11
  import { acquireLockAsync, releaseLock } from '../utils/file-lock.js';
12
12
  import { getEvolutionLogger } from '../core/evolution-logger.js';
13
13
  import { DIAGNOSTICIAN_PROTOCOL_SUMMARY } from '../constants/diagnostician.js';
14
+ import { LockUnavailableError } from '../config/index.js';
14
15
  let intervalId = null;
16
+ let timeoutId = null;
15
17
  const PAIN_QUEUE_DEDUP_WINDOW_MS = 30 * 60 * 1000;
16
18
  // P0 fix: File lock constants and helper for queue operations (prevents TOCTOU race)
17
19
  export const EVOLUTION_QUEUE_LOCK_SUFFIX = '.lock';
@@ -84,8 +86,8 @@ async function requireQueueLock(resourcePath, logger, scope, lockSuffix = EVOLUT
84
86
  try {
85
87
  return await acquireQueueLock(resourcePath, logger, lockSuffix);
86
88
  }
87
- catch {
88
- throw new Error(`[PD:EvolutionWorker] ${scope}: queue lock unavailable for ${resourcePath}`);
89
+ catch (err) {
90
+ throw new LockUnavailableError(resourcePath, scope, { cause: err });
89
91
  }
90
92
  }
91
93
  export function extractEvolutionTaskId(task) {
@@ -638,7 +640,7 @@ export const EvolutionWorkerService = {
638
640
  logger.error(`[PD:EvolutionWorker] Error in worker interval: ${String(err)}`);
639
641
  });
640
642
  }, interval);
641
- setTimeout(() => {
643
+ timeoutId = setTimeout(() => {
642
644
  void (async () => {
643
645
  await checkPainFlag(wctx, logger);
644
646
  await processEvolutionQueue(wctx, logger, eventLog);
@@ -657,6 +659,8 @@ export const EvolutionWorkerService = {
657
659
  ctx.logger.info('[PD:EvolutionWorker] Stopping background service...');
658
660
  if (intervalId)
659
661
  clearInterval(intervalId);
662
+ if (timeoutId)
663
+ clearTimeout(timeoutId);
660
664
  flushAllSessions();
661
665
  }
662
666
  };
@@ -1,8 +1,41 @@
1
+ /**
2
+ * Phase 3 Input Filter
3
+ *
4
+ * CRITICAL: evaluatePhase3Inputs() does NOT read or use evolution_directive.json
5
+ * Directive is a compatibility-only display artifact, not a truth source.
6
+ *
7
+ * Phase 3 eligibility depends ONLY on:
8
+ * - Queue truth (valid evolution samples from queue)
9
+ * - Trust input (frozen trust scorecard)
10
+ *
11
+ * Any directive file is ignored for eligibility decisions.
12
+ *
13
+ * THREE-LANE CLASSIFICATION:
14
+ * - authoritative: Valid inputs that can be used for Phase 3 eligibility decisions
15
+ * - reference_only: Valid evidence that must NOT be used as positive eligibility input
16
+ * (e.g., timeout-only outcomes - they indicate completion but not success)
17
+ * - rejected: Invalid, corrupt, or policy-prohibited input
18
+ */
19
+ /**
20
+ * Classification for Phase 3 inputs.
21
+ * - authoritative: Can be used for Phase 3 eligibility decisions
22
+ * - reference_only: Valid data but not for eligibility (e.g., timeout outcomes)
23
+ * - rejected: Invalid or corrupt data
24
+ */
25
+ export type Phase3Classification = 'authoritative' | 'reference_only' | 'rejected';
26
+ /**
27
+ * Classification for trust input state.
28
+ * - authoritative: Frozen trust with valid score
29
+ * - unknown: Missing trust score (not silently coerced to 0)
30
+ * - rejected: Unfrozen trust or invalid schema
31
+ */
32
+ export type TrustClassification = 'authoritative' | 'unknown' | 'rejected';
1
33
  export interface Phase3EvolutionInput {
2
34
  id?: string | null;
3
35
  status?: string | null;
4
36
  started_at?: string | null;
5
37
  completed_at?: string | null;
38
+ resolution?: string | null;
6
39
  }
7
40
  export interface Phase3TrustInput {
8
41
  score?: number | null;
@@ -15,6 +48,12 @@ export interface Phase3EvolutionSample {
15
48
  startedAt: string | null;
16
49
  completedAt: string | null;
17
50
  }
51
+ export interface Phase3ReferenceOnlySample {
52
+ taskId: string;
53
+ status: string;
54
+ classification: 'timeout_only' | 'other_reference';
55
+ reason: string;
56
+ }
18
57
  export interface Phase3RejectedEvolutionSample {
19
58
  taskId: string | null;
20
59
  status: string | null;
@@ -22,6 +61,7 @@ export interface Phase3RejectedEvolutionSample {
22
61
  }
23
62
  export interface Phase3TrustResult {
24
63
  eligible: boolean;
64
+ classification: TrustClassification;
25
65
  rejectedReasons: string[];
26
66
  }
27
67
  export interface Phase3InputFilterResult {
@@ -30,8 +70,25 @@ export interface Phase3InputFilterResult {
30
70
  phase3ShadowEligible: boolean;
31
71
  evolution: {
32
72
  eligible: Phase3EvolutionSample[];
73
+ referenceOnly: Phase3ReferenceOnlySample[];
33
74
  rejected: Phase3RejectedEvolutionSample[];
34
75
  };
35
76
  trust: Phase3TrustResult;
36
77
  }
78
+ /**
79
+ * Evaluates Phase 3 readiness based on queue and trust inputs.
80
+ *
81
+ * IMPORTANT: Does NOT use evolution_directive.json.
82
+ * Directive is compatibility-only display artifact, not a truth source.
83
+ * Queue is the only authoritative execution truth source for Phase 3.
84
+ *
85
+ * THREE-LANE CLASSIFICATION:
86
+ * - authoritative: Valid inputs for Phase 3 eligibility
87
+ * - reference_only: Valid evidence but not for eligibility (e.g., timeout outcomes)
88
+ * - rejected: Invalid, corrupt, or policy-prohibited input
89
+ *
90
+ * @param queue - Evolution queue items to validate
91
+ * @param trust - Trust input (frozen scorecard)
92
+ * @returns Phase 3 eligibility results
93
+ */
37
94
  export declare function evaluatePhase3Inputs(queue: Phase3EvolutionInput[], trust: Phase3TrustInput): Phase3InputFilterResult;
@@ -1,3 +1,25 @@
1
+ /**
2
+ * Phase 3 Input Filter
3
+ *
4
+ * CRITICAL: evaluatePhase3Inputs() does NOT read or use evolution_directive.json
5
+ * Directive is a compatibility-only display artifact, not a truth source.
6
+ *
7
+ * Phase 3 eligibility depends ONLY on:
8
+ * - Queue truth (valid evolution samples from queue)
9
+ * - Trust input (frozen trust scorecard)
10
+ *
11
+ * Any directive file is ignored for eligibility decisions.
12
+ *
13
+ * THREE-LANE CLASSIFICATION:
14
+ * - authoritative: Valid inputs that can be used for Phase 3 eligibility decisions
15
+ * - reference_only: Valid evidence that must NOT be used as positive eligibility input
16
+ * (e.g., timeout-only outcomes - they indicate completion but not success)
17
+ * - rejected: Invalid, corrupt, or policy-prohibited input
18
+ */
19
+ /**
20
+ * Legacy queue statuses that are rejected for Phase 3
21
+ */
22
+ const LEGACY_QUEUE_STATUSES = ['resolved', 'blocked', 'failed', 'cancelled', 'paused'];
1
23
  function normalizeTaskId(value) {
2
24
  if (typeof value !== 'string')
3
25
  return null;
@@ -12,6 +34,13 @@ function normalizeStatus(value) {
12
34
  return 'pending';
13
35
  return null;
14
36
  }
37
+ /**
38
+ * Checks if a status is a legacy value that should be rejected
39
+ */
40
+ function isLegacyStatus(value) {
41
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
42
+ return LEGACY_QUEUE_STATUSES.includes(normalized);
43
+ }
15
44
  function normalizeTimestamp(value) {
16
45
  if (typeof value !== 'string')
17
46
  return null;
@@ -24,8 +53,32 @@ function normalizeTimestamp(value) {
24
53
  function dedupe(values) {
25
54
  return [...new Set(values)];
26
55
  }
56
+ /**
57
+ * Checks if a task has a timeout-only outcome (resolution indicates only timeout)
58
+ */
59
+ function isTimeoutOnlyOutcome(item) {
60
+ const resolution = typeof item?.resolution === 'string' ? item.resolution.trim().toLowerCase() : '';
61
+ return resolution === 'auto_completed_timeout';
62
+ }
63
+ /**
64
+ * Evaluates Phase 3 readiness based on queue and trust inputs.
65
+ *
66
+ * IMPORTANT: Does NOT use evolution_directive.json.
67
+ * Directive is compatibility-only display artifact, not a truth source.
68
+ * Queue is the only authoritative execution truth source for Phase 3.
69
+ *
70
+ * THREE-LANE CLASSIFICATION:
71
+ * - authoritative: Valid inputs for Phase 3 eligibility
72
+ * - reference_only: Valid evidence but not for eligibility (e.g., timeout outcomes)
73
+ * - rejected: Invalid, corrupt, or policy-prohibited input
74
+ *
75
+ * @param queue - Evolution queue items to validate
76
+ * @param trust - Trust input (frozen scorecard)
77
+ * @returns Phase 3 eligibility results
78
+ */
27
79
  export function evaluatePhase3Inputs(queue, trust) {
28
80
  const eligible = [];
81
+ const referenceOnly = [];
29
82
  const rejected = [];
30
83
  const taskIdCounts = new Map();
31
84
  for (const item of queue) {
@@ -40,13 +93,22 @@ export function evaluatePhase3Inputs(queue, trust) {
40
93
  const reasons = [];
41
94
  const startedAt = normalizeTimestamp(item?.started_at);
42
95
  const completedAt = normalizeTimestamp(item?.completed_at);
96
+ // Check for legacy statuses first (before other status validation)
97
+ if (isLegacyStatus(item?.status)) {
98
+ reasons.push('legacy_queue_status');
99
+ }
100
+ // Check for null status separately
101
+ if (item?.status === null) {
102
+ reasons.push('missing_status');
103
+ }
43
104
  if (!taskId) {
44
105
  reasons.push('missing_task_id');
45
106
  }
46
107
  else if ((taskIdCounts.get(taskId) ?? 0) > 1) {
47
108
  reasons.push('reused_task_id');
48
109
  }
49
- if (!status) {
110
+ // Only add invalid_status if it's not a legacy status and not null
111
+ if (!status && !isLegacyStatus(item?.status) && item?.status !== null) {
50
112
  reasons.push('invalid_status');
51
113
  }
52
114
  if (typeof item?.started_at === 'string' && item.started_at.trim() && !startedAt) {
@@ -61,6 +123,7 @@ export function evaluatePhase3Inputs(queue, trust) {
61
123
  if (status === 'completed' && !completedAt) {
62
124
  reasons.push('missing_completed_at');
63
125
  }
126
+ // Handle rejected items first
64
127
  if (reasons.length > 0) {
65
128
  rejected.push({
66
129
  taskId,
@@ -72,6 +135,18 @@ export function evaluatePhase3Inputs(queue, trust) {
72
135
  if (!taskId || !status) {
73
136
  continue;
74
137
  }
138
+ // Check for timeout-only outcomes (REFERENCE_ONLY, not rejected)
139
+ // These are valid completions but shouldn't count as positive evidence
140
+ if (status === 'completed' && isTimeoutOnlyOutcome(item)) {
141
+ referenceOnly.push({
142
+ taskId,
143
+ status: 'completed',
144
+ classification: 'timeout_only',
145
+ reason: 'Task completed via timeout - valid execution but not positive capability evidence',
146
+ });
147
+ continue;
148
+ }
149
+ // Valid eligible sample
75
150
  eligible.push({
76
151
  taskId,
77
152
  status,
@@ -79,27 +154,42 @@ export function evaluatePhase3Inputs(queue, trust) {
79
154
  completedAt,
80
155
  });
81
156
  }
157
+ // Trust classification logic
82
158
  const trustRejectedReasons = [];
83
159
  const score = typeof trust.score === 'number' && Number.isFinite(trust.score) ? trust.score : null;
160
+ let trustClassification = 'authoritative';
84
161
  if (trust.frozen !== true) {
85
162
  trustRejectedReasons.push('legacy_or_unfrozen_trust_schema');
163
+ trustClassification = 'rejected';
86
164
  }
87
165
  if (score === null) {
88
166
  trustRejectedReasons.push('missing_trust_score');
167
+ // Missing score = unknown (unless already rejected for unfrozen)
168
+ if (trustClassification !== 'rejected') {
169
+ trustClassification = 'unknown';
170
+ }
89
171
  }
90
172
  const trustInputReady = trustRejectedReasons.length === 0;
91
- const queueTruthReady = queue.length > 0 && rejected.length === 0 && eligible.length > 0;
92
- const phase3ShadowEligible = queueTruthReady && trustInputReady;
173
+ // Queue is ready when:
174
+ // 1. Queue has items
175
+ // 2. No invalid/corrupt items (rejected is empty)
176
+ // 3. Either eligible OR referenceOnly has items (valid data exists)
177
+ // Note: referenceOnly (timeout outcomes) is valid data, just not positive evidence
178
+ const hasValidData = eligible.length > 0 || referenceOnly.length > 0;
179
+ const queueTruthReady = queue.length > 0 && rejected.length === 0 && hasValidData;
180
+ const phase3ShadowEligible = queueTruthReady && trustInputReady && eligible.length > 0;
93
181
  return {
94
182
  queueTruthReady,
95
183
  trustInputReady,
96
184
  phase3ShadowEligible,
97
185
  evolution: {
98
186
  eligible,
187
+ referenceOnly,
99
188
  rejected,
100
189
  },
101
190
  trust: {
102
191
  eligible: trustInputReady,
192
+ classification: trustClassification,
103
193
  rejectedReasons: trustRejectedReasons,
104
194
  },
105
195
  };
@@ -1,3 +1,4 @@
1
+ import type { RuntimeTruth, AnalyticsTruth } from '../types/runtime-summary.js';
1
2
  export type RuntimeDataQuality = 'authoritative' | 'partial';
2
3
  export type RuntimeRewardPolicy = 'frozen_all_positive' | 'frozen_atomic_positive_keep_plan_ready';
3
4
  interface RuntimeSummarySource {
@@ -13,6 +14,17 @@ interface RuntimePainSignal {
13
14
  reason: string | null;
14
15
  }
15
16
  export interface RuntimeSummary {
17
+ /**
18
+ * Runtime truth represents the current state of the system.
19
+ * Used for control decisions, Phase 3 eligibility, and real-time operations.
20
+ */
21
+ runtime: RuntimeTruth;
22
+ /**
23
+ * Analytics truth represents historical data and aggregated metrics.
24
+ * Used for insights, trends, and supporting evidence (where explicitly allowed).
25
+ * NOT used for control decisions or Phase 3 eligibility.
26
+ */
27
+ analytics: AnalyticsTruth;
16
28
  gfi: {
17
29
  current: number | null;
18
30
  peak: number | null;
@@ -45,9 +57,19 @@ export interface RuntimeSummary {
45
57
  trustInputReady: boolean;
46
58
  phase3ShadowEligible: boolean;
47
59
  evolutionEligible: number;
60
+ evolutionReferenceOnly: number;
61
+ evolutionReferenceOnlyReasons: string[];
48
62
  evolutionRejected: number;
49
63
  evolutionRejectedReasons: string[];
50
64
  trustRejectedReasons: string[];
65
+ legacyDirectiveFilePresent: boolean;
66
+ directiveStatus: 'compatibility-only' | 'missing' | 'present';
67
+ directiveIgnoredReason: string;
68
+ /**
69
+ * Source of Phase 3 eligibility calculation.
70
+ * Should always be 'runtime_truth' - analytics not used for control decisions.
71
+ */
72
+ eligibilitySource: 'runtime_truth';
51
73
  };
52
74
  pain: {
53
75
  activeFlag: boolean;
@@ -76,6 +98,11 @@ export declare class RuntimeSummaryService {
76
98
  private static selectSession;
77
99
  private static mergeSessionSnapshots;
78
100
  private static buildQueueStats;
101
+ /**
102
+ * Builds directive summary for compatibility display only.
103
+ * NOT a truth source for Phase 3 eligibility or decisions.
104
+ * Queue is the only authoritative execution truth source.
105
+ */
79
106
  private static buildDirectiveSummary;
80
107
  private static readLegacyTrust;
81
108
  private static readEvents;
@@ -94,5 +121,12 @@ export declare class RuntimeSummaryService {
94
121
  private static warnOnLegacyDirectiveMismatch;
95
122
  private static readJsonFile;
96
123
  private static asFiniteNumber;
124
+ /**
125
+ * Read trajectory analytics data from trajectory database.
126
+ *
127
+ * Returns: Analytics data (historical metrics) aggregated from trajectory database.
128
+ * Not: Runtime truth or real-time queue state.
129
+ */
130
+ private static readTrajectoryStats;
97
131
  }
98
132
  export {};
@@ -5,6 +5,7 @@ import { resolvePdPath } from '../core/paths.js';
5
5
  import { listSessions } from '../core/session-tracker.js';
6
6
  import { WorkspaceContext } from '../core/workspace-context.js';
7
7
  import { evaluatePhase3Inputs } from './phase3-input-filter.js';
8
+ import { TrajectoryRegistry } from '../core/trajectory.js';
8
9
  const MAX_SOURCE_EVENTS = 5;
9
10
  const LEGACY_TRUST_REWARD_POLICY = 'frozen_all_positive';
10
11
  const GFI_PARTIAL_WARNING = 'GFI source attribution remains partial in Phase 2b because only the empathy slice is source-attributed; most non-empathy friction still lacks full per-source attribution.';
@@ -30,8 +31,11 @@ export class RuntimeSummaryService {
30
31
  : [];
31
32
  const events = this.mergeEvents(persistedEvents, bufferedEvents);
32
33
  const dailyStats = this.readJsonFile(path.join(wctx.stateDir, 'logs', 'daily-stats.json'), warnings, false);
34
+ // Get most recent date from daily stats, fallback to today
33
35
  const today = generatedAt.slice(0, 10);
34
- const dailyGfiPeak = dailyStats?.[today]?.gfi?.peak;
36
+ const availableDates = Object.keys(dailyStats || {}).sort().reverse();
37
+ const statsDate = availableDates.length > 0 ? availableDates[0] : today;
38
+ const dailyGfiPeak = dailyStats?.[statsDate]?.gfi?.peak;
35
39
  const gfiCurrent = selectedSession.session && Number.isFinite(selectedSession.session.currentGfi)
36
40
  ? Number(selectedSession.session.currentGfi)
37
41
  : null;
@@ -50,6 +54,8 @@ export class RuntimeSummaryService {
50
54
  pushWarning(warnings, 'No persisted session state was found; current session GFI is unavailable.');
51
55
  }
52
56
  const queue = this.readJsonFile(wctx.resolve('EVOLUTION_QUEUE'), warnings, false);
57
+ // compatibility-only display artifact - not a truth source for Phase 3 eligibility
58
+ // queue is the only authoritative execution truth source for Phase 3
53
59
  const directive = this.readJsonFile(wctx.resolve('EVOLUTION_DIRECTIVE'), warnings, false);
54
60
  const queueStats = this.buildQueueStats(queue);
55
61
  const directiveSummary = this.buildDirectiveSummary(queue, directive, generatedAt, warnings);
@@ -60,7 +66,53 @@ export class RuntimeSummaryService {
60
66
  const lastPainSignal = this.findLastPainSignal(events, selectedSessionId);
61
67
  const gfiSources = this.buildGfiSources(events, selectedSessionId);
62
68
  const gateStats = this.buildGateStats(events, selectedSessionId, warnings);
69
+ // Read trajectory analytics data (historical data, NOT runtime truth)
70
+ const trajectoryStats = this.readTrajectoryStats(workspaceDir, warnings);
71
+ // Build runtime truth section (current state for control decisions)
72
+ const activeSessionIds = sessions.map(s => s.sessionId);
73
+ const runtimeTruth = {
74
+ queueState: {
75
+ total: queueStats.pending + queueStats.inProgress + queueStats.completed,
76
+ pending: queueStats.pending,
77
+ inProgress: queueStats.inProgress,
78
+ completed: queueStats.completed,
79
+ lastUpdated: generatedAt,
80
+ },
81
+ activeSessions: activeSessionIds,
82
+ currentTrustScore: legacyTrust.phase3Input.score ?? null,
83
+ workspaceState: {
84
+ frozen: legacyTrust.phase3Input.frozen ?? null,
85
+ lastUpdated: legacyTrust.phase3Input.lastUpdated ?? null,
86
+ trustClassification: phase3Inputs.trust.classification,
87
+ },
88
+ };
89
+ // Build analytics truth section (historical data for insights)
90
+ const analyticsTruth = {
91
+ trajectoryData: {
92
+ totalTasks: trajectoryStats.assistantTurns + trajectoryStats.userTurns,
93
+ successRate: trajectoryStats.toolCalls > 0
94
+ ? (trajectoryStats.toolCalls - trajectoryStats.failures) / trajectoryStats.toolCalls
95
+ : 0,
96
+ timeoutRate: trajectoryStats.failures > 0
97
+ ? trajectoryStats.failures / (trajectoryStats.assistantTurns + trajectoryStats.userTurns || 1)
98
+ : 0,
99
+ trustChanges: 0, // Not tracked in current trajectory schema
100
+ lastUpdated: trajectoryStats.lastIngestAt ?? generatedAt,
101
+ },
102
+ dailyStats: {
103
+ toolCalls: dailyStats?.[statsDate]?.toolCalls ?? 0,
104
+ painSignals: dailyStats?.[statsDate]?.painSignals ?? 0,
105
+ evolutionTasks: dailyStats?.[statsDate]?.evolutionTasks ?? 0,
106
+ lastUpdated: statsDate,
107
+ },
108
+ trends: {
109
+ sevenDay: { successRateChange: 0, toolCallVolumeChange: 0, painSignalRateChange: 0 },
110
+ thirtyDay: { successRateChange: 0, toolCallVolumeChange: 0, painSignalRateChange: 0 },
111
+ },
112
+ };
63
113
  return {
114
+ runtime: runtimeTruth,
115
+ analytics: analyticsTruth,
64
116
  gfi: {
65
117
  current: gfiCurrent,
66
118
  peak: gfiPeak,
@@ -78,9 +130,15 @@ export class RuntimeSummaryService {
78
130
  trustInputReady: phase3Inputs.trustInputReady,
79
131
  phase3ShadowEligible: phase3Inputs.phase3ShadowEligible,
80
132
  evolutionEligible: phase3Inputs.evolution.eligible.length,
133
+ evolutionReferenceOnly: phase3Inputs.evolution.referenceOnly.length,
134
+ evolutionReferenceOnlyReasons: [...new Set(phase3Inputs.evolution.referenceOnly.map((entry) => entry.classification))],
81
135
  evolutionRejected: phase3Inputs.evolution.rejected.length,
82
136
  evolutionRejectedReasons: phase3Inputs.evolution.rejected.flatMap((entry) => entry.reasons),
83
137
  trustRejectedReasons: phase3Inputs.trust.rejectedReasons,
138
+ legacyDirectiveFilePresent: directive !== null,
139
+ directiveStatus: directive ? 'compatibility-only' : 'missing',
140
+ directiveIgnoredReason: 'queue is only truth source',
141
+ eligibilitySource: 'runtime_truth',
84
142
  },
85
143
  pain: {
86
144
  activeFlag: Object.keys(painFlag).length > 0,
@@ -168,6 +226,11 @@ export class RuntimeSummaryService {
168
226
  }
169
227
  return stats;
170
228
  }
229
+ /**
230
+ * Builds directive summary for compatibility display only.
231
+ * NOT a truth source for Phase 3 eligibility or decisions.
232
+ * Queue is the only authoritative execution truth source.
233
+ */
171
234
  static buildDirectiveSummary(queue, directive, generatedAt, warnings) {
172
235
  const inProgressTask = this.selectInProgressTask(queue);
173
236
  if (!inProgressTask) {
@@ -443,4 +506,33 @@ export class RuntimeSummaryService {
443
506
  static asFiniteNumber(value) {
444
507
  return Number.isFinite(value) ? Number(value) : undefined;
445
508
  }
509
+ /**
510
+ * Read trajectory analytics data from trajectory database.
511
+ *
512
+ * Returns: Analytics data (historical metrics) aggregated from trajectory database.
513
+ * Not: Runtime truth or real-time queue state.
514
+ */
515
+ static readTrajectoryStats(workspaceDir, warnings) {
516
+ try {
517
+ // Use transient database instance to avoid locking issues
518
+ const stats = TrajectoryRegistry.use(workspaceDir, (db) => db.getDataStats());
519
+ return {
520
+ assistantTurns: stats.assistantTurns,
521
+ userTurns: stats.userTurns,
522
+ toolCalls: stats.toolCalls,
523
+ failures: stats.painEvents, // Approximate failures from pain events
524
+ lastIngestAt: stats.lastIngestAt,
525
+ };
526
+ }
527
+ catch (error) {
528
+ pushWarning(warnings, `Failed to read trajectory analytics: ${error instanceof Error ? error.message : String(error)}`);
529
+ return {
530
+ assistantTurns: 0,
531
+ userTurns: 0,
532
+ toolCalls: 0,
533
+ failures: 0,
534
+ lastIngestAt: null,
535
+ };
536
+ }
537
+ }
446
538
  }
@@ -75,6 +75,8 @@ export interface GateBlockEventData {
75
75
  filePath: string;
76
76
  reason: string;
77
77
  planStatus?: string;
78
+ /** Source module that triggered the block (for audit trail) */
79
+ blockSource?: string;
78
80
  }
79
81
  export interface GateBypassEventData {
80
82
  toolName: string;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Runtime truth represents the current state of the system.
3
+ * Used for control decisions, Phase 3 eligibility, and real-time operations.
4
+ * Sources: queue state, workspace trust scorecard, active session registry
5
+ */
6
+ export interface RuntimeTruth {
7
+ queueState: {
8
+ total: number;
9
+ pending: number;
10
+ inProgress: number;
11
+ completed: number;
12
+ lastUpdated: string;
13
+ };
14
+ activeSessions: string[];
15
+ currentTrustScore: number | null;
16
+ workspaceState: {
17
+ frozen: boolean | null;
18
+ lastUpdated: string | null;
19
+ trustClassification: 'authoritative' | 'unknown' | 'rejected';
20
+ };
21
+ }
22
+ /**
23
+ * Analytics truth represents historical data and aggregated metrics.
24
+ * Used for insights, trends, and supporting evidence (where explicitly allowed).
25
+ * NOT used for control decisions or Phase 3 eligibility.
26
+ * Sources: trajectory.db, daily-stats.json, control-ui DB
27
+ */
28
+ export interface AnalyticsTruth {
29
+ trajectoryData: {
30
+ totalTasks: number;
31
+ successRate: number;
32
+ timeoutRate: number;
33
+ trustChanges: number;
34
+ lastUpdated: string;
35
+ };
36
+ dailyStats: {
37
+ toolCalls: number;
38
+ painSignals: number;
39
+ evolutionTasks: number;
40
+ lastUpdated: string;
41
+ };
42
+ trends: {
43
+ sevenDay: TrendMetrics;
44
+ thirtyDay: TrendMetrics;
45
+ };
46
+ }
47
+ /**
48
+ * Trend metrics for analytics aggregation.
49
+ */
50
+ export interface TrendMetrics {
51
+ successRateChange: number;
52
+ toolCallVolumeChange: number;
53
+ painSignalRateChange: number;
54
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -2,7 +2,7 @@
2
2
  "id": "principles-disciple",
3
3
  "name": "Principles Disciple",
4
4
  "description": "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
5
- "version": "1.7.4",
5
+ "version": "1.7.5",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "principles-disciple",
3
- "version": "1.7.5",
3
+ "version": "1.7.6",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",