principles-disciple 1.34.2 → 1.36.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/.dependency-cruiser.json +19 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +6 -3
- package/src/config/defaults/runtime.ts +100 -24
- package/src/core/correction-cue-learner.ts +23 -8
- package/src/core/event-log.ts +87 -20
- package/src/core/init.ts +2 -2
- package/src/core/nocturnal-candidate-scoring.ts +6 -6
- package/src/core/nocturnal-trinity-types.ts +94 -0
- package/src/core/nocturnal-trinity.ts +35 -99
- package/src/core/session-tracker.ts +7 -6
- package/src/core/system-logger.ts +104 -12
- package/src/core/workspace-dir-service.ts +40 -6
- package/src/core/workspace-dir-validation.ts +5 -37
- package/src/hooks/prompt.ts +3 -3
- package/src/hooks/trajectory-collector.ts +7 -7
- package/src/index.ts +8 -68
- package/src/service/central-sync-service.ts +3 -8
- package/src/service/correction-observer-workflow-manager.ts +2 -2
- package/src/service/evolution-worker.ts +13 -22
- package/src/service/keyword-optimization-service.ts +2 -2
- package/src/service/nocturnal-service.ts +62 -43
- package/src/service/subagent-workflow/correction-observer-types.ts +69 -0
- package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +246 -0
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +4 -4
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +4 -4
- package/src/service/subagent-workflow/index.ts +13 -0
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +2 -2
- package/src/service/subagent-workflow/types.ts +69 -3
- package/src/utils/shadow-fingerprint.ts +42 -0
- package/src/utils/workspace-resolver.ts +54 -0
- package/tests/core/correction-cue-learner.test.ts +345 -0
- package/tests/core/workspace-dir-validation.test.ts +1 -1
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +3 -3
- package/vitest.config.ts +53 -6
package/src/index.ts
CHANGED
|
@@ -16,9 +16,7 @@ import type {
|
|
|
16
16
|
PluginHookSubagentSpawningResult,
|
|
17
17
|
PluginHookSubagentContext,
|
|
18
18
|
} from './openclaw-sdk.js';
|
|
19
|
-
import * as crypto from 'crypto';
|
|
20
19
|
import * as path from 'path';
|
|
21
|
-
import type { WorkerProfile } from './core/model-deployment-registry.js';
|
|
22
20
|
import { classifyTask } from './core/local-worker-routing.js';
|
|
23
21
|
import { completeShadowObservation, recordShadowRouting } from './core/shadow-observation-registry.js';
|
|
24
22
|
import { getCommandDescription } from './i18n/commands.js';
|
|
@@ -59,83 +57,25 @@ import { migrateDirectoryStructure } from './core/migration.js';
|
|
|
59
57
|
import { SystemLogger } from './core/system-logger.js';
|
|
60
58
|
import { createDeepReflectTool } from './tools/deep-reflect.js';
|
|
61
59
|
import { createWritePainFlagTool } from './tools/write-pain-flag.js';
|
|
62
|
-
import { PathResolver
|
|
63
|
-
import { validateWorkspaceDir } from './core/workspace-dir-validation.js';
|
|
64
|
-
import { resolveRequiredWorkspaceDir, resolveWorkspaceDir, type WorkspaceResolutionContext } from './core/workspace-dir-service.js';
|
|
60
|
+
import { PathResolver } from './core/path-resolver.js';
|
|
65
61
|
import { createPrinciplesConsoleRoute } from './http/principles-console-route.js';
|
|
66
62
|
import { extractAgentIdFromSessionKey } from './utils/session-key.js';
|
|
63
|
+
import { resolveCommandWorkspaceDir, resolveToolHookWorkspaceDirSafe } from './utils/workspace-resolver.js';
|
|
64
|
+
import { computeRuntimeShadowTaskFingerprint, PD_LOCAL_PROFILES } from './utils/shadow-fingerprint.js';
|
|
65
|
+
import type { WorkerProfile } from './core/model-deployment-registry.js';
|
|
66
|
+
import { validateWorkspaceDir } from './core/workspace-dir-validation.js';
|
|
67
|
+
import { resolveWorkspaceDirFromApi } from './core/path-resolver.js';
|
|
68
|
+
import { resolveRequiredWorkspaceDir } from './core/workspace-dir-service.js';
|
|
67
69
|
|
|
68
70
|
// Track initialization to avoid repeated calls
|
|
69
71
|
let workspaceInitialized = false;
|
|
70
72
|
// Track started evolution workers — one per workspace
|
|
71
73
|
const startedWorkspaces = new Set<string>();
|
|
72
74
|
|
|
73
|
-
/**
|
|
74
|
-
* Resolve workspaceDir for slash commands.
|
|
75
|
-
* Chain: ctx.workspaceDir → resolveWorkspaceDirFromApi (official OpenClaw API + env vars)
|
|
76
|
-
*
|
|
77
|
-
* CRITICAL: Throws if workspaceDir cannot be resolved. Silent failures are dangerous
|
|
78
|
-
* because commands might operate on the wrong directory.
|
|
79
|
-
*/
|
|
80
|
-
function resolveCommandWorkspaceDir(
|
|
81
|
-
api: OpenClawPluginApi,
|
|
82
|
-
ctx: { workspaceDir?: string },
|
|
83
|
-
): string {
|
|
84
|
-
// 1. Direct from command context (most reliable — set by OpenClaw for current session)
|
|
85
|
-
if (ctx.workspaceDir) {
|
|
86
|
-
const issue = validateWorkspaceDir(ctx.workspaceDir);
|
|
87
|
-
if (!issue) return ctx.workspaceDir;
|
|
88
|
-
api.logger.error(`[PD:Command] ctx.workspaceDir="${ctx.workspaceDir}" is invalid: ${issue}`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// 2. Official OpenClaw API → env vars → config file
|
|
92
|
-
const resolved = resolveWorkspaceDirFromApi(api);
|
|
93
|
-
if (resolved) return resolved;
|
|
94
|
-
|
|
95
|
-
// CRITICAL FAILURE: Cannot determine workspace directory
|
|
96
|
-
const errorMsg = `[PD:Command] CRITICAL: Cannot resolve workspace directory. ` +
|
|
97
|
-
`ctx.workspaceDir="${ctx.workspaceDir}" is invalid, and all fallbacks failed. ` +
|
|
98
|
-
`Commands will NOT execute to prevent data corruption.`;
|
|
99
|
-
api.logger.error(errorMsg);
|
|
100
|
-
|
|
101
|
-
throw new Error(errorMsg);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
75
|
// Map from childSessionKey → shadowObservationId
|
|
105
76
|
// Used to complete shadow observations when subagent ends
|
|
106
77
|
const pendingShadowObservations = new Map<string, string>();
|
|
107
78
|
|
|
108
|
-
// PD local worker profiles that are managed by the shadow routing policy
|
|
109
|
-
const PD_LOCAL_PROFILES = new Set<WorkerProfile>(['local-reader', 'local-editor']);
|
|
110
|
-
|
|
111
|
-
function computeRuntimeShadowTaskFingerprint(event: PluginHookSubagentSpawningEvent): string {
|
|
112
|
-
const payload = {
|
|
113
|
-
childSessionKey: event.childSessionKey,
|
|
114
|
-
agentId: event.agentId,
|
|
115
|
-
label: event.label ?? '',
|
|
116
|
-
mode: event.mode,
|
|
117
|
-
threadRequested: event.threadRequested,
|
|
118
|
-
requesterChannel: event.requester?.channel ?? '',
|
|
119
|
-
requesterThreadId: event.requester?.threadId ?? '',
|
|
120
|
-
};
|
|
121
|
-
return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex').slice(0, 16);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function _resolveCommandWorkspaceDirStrict(
|
|
125
|
-
api: OpenClawPluginApi,
|
|
126
|
-
ctx: WorkspaceResolutionContext,
|
|
127
|
-
): string {
|
|
128
|
-
return resolveRequiredWorkspaceDir(api, ctx, { source: 'command' });
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function resolveToolHookWorkspaceDirSafe(
|
|
132
|
-
ctx: WorkspaceResolutionContext,
|
|
133
|
-
api: OpenClawPluginApi,
|
|
134
|
-
source: string,
|
|
135
|
-
): string | undefined {
|
|
136
|
-
return resolveWorkspaceDir(api, ctx, { source });
|
|
137
|
-
}
|
|
138
|
-
|
|
139
79
|
const plugin = {
|
|
140
80
|
name: "Principles Disciple",
|
|
141
81
|
description: "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
|
|
@@ -421,7 +361,7 @@ const plugin = {
|
|
|
421
361
|
|
|
422
362
|
// ── Slash Commands ──
|
|
423
363
|
// Register command with optional short alias
|
|
424
|
-
// eslint-disable-next-line @typescript-eslint/
|
|
364
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
425
365
|
const registerCommandWithAlias = (name: string, alias: string | null, desc: string, handler: any, opts?: { acceptsArgs?: boolean }) => {
|
|
426
366
|
const base = {
|
|
427
367
|
name,
|
|
@@ -7,18 +7,13 @@
|
|
|
7
7
|
|
|
8
8
|
import type { OpenClawPluginService, OpenClawPluginServiceContext, PluginLogger } from '../openclaw-sdk.js';
|
|
9
9
|
import { CentralDatabase } from './central-database.js';
|
|
10
|
+
import { WORKFLOW_TTL_MS } from '../config/defaults/runtime.js';
|
|
10
11
|
|
|
11
12
|
let syncInterval: ReturnType<typeof setInterval> | null = null;
|
|
12
13
|
let logger: PluginLogger | undefined = undefined;
|
|
13
14
|
let centralDb: CentralDatabase | null = null;
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
* Default sync interval: 5 minutes.
|
|
17
|
-
* Can be overridden via config: intervals.central_sync_ms
|
|
18
|
-
*/
|
|
19
|
-
const DEFAULT_SYNC_INTERVAL_MS = 5 * 60 * 1000;
|
|
20
|
-
|
|
21
|
-
// eslint-disable-next-line complexity -- complexity 12, refactor candidate
|
|
16
|
+
|
|
22
17
|
async function runSyncCycle(): Promise<void> {
|
|
23
18
|
if (!centralDb) {
|
|
24
19
|
logger?.warn?.('[PD:CentralSync] CentralDatabase not initialized, skipping sync');
|
|
@@ -51,7 +46,7 @@ export const CentralSyncService: OpenClawPluginService = {
|
|
|
51
46
|
logger = ctxLogger;
|
|
52
47
|
|
|
53
48
|
const { intervals } = config as { intervals?: { central_sync_ms?: number } };
|
|
54
|
-
const intervalMs = intervals?.central_sync_ms ??
|
|
49
|
+
const intervalMs = intervals?.central_sync_ms ?? WORKFLOW_TTL_MS;
|
|
55
50
|
|
|
56
51
|
// Initialize CentralDatabase
|
|
57
52
|
centralDb = new CentralDatabase();
|
|
@@ -22,11 +22,11 @@ import type {
|
|
|
22
22
|
CorrectionObserverPayload,
|
|
23
23
|
CorrectionObserverResult,
|
|
24
24
|
} from './correction-observer-types.js';
|
|
25
|
+
import { WORKFLOW_TTL_MS } from '../config/defaults/runtime.js';
|
|
25
26
|
|
|
26
27
|
const WORKFLOW_SESSION_PREFIX = 'agent:main:subagent:workflow-correction-';
|
|
27
28
|
|
|
28
29
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
29
|
-
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
30
30
|
|
|
31
31
|
// ── Options ─────────────────────────────────────────────────────────────────
|
|
32
32
|
|
|
@@ -162,7 +162,7 @@ export class CorrectionObserverWorkflowManager extends WorkflowManagerBase {
|
|
|
162
162
|
workflowType: 'correction_observer',
|
|
163
163
|
sessionPrefix: WORKFLOW_SESSION_PREFIX,
|
|
164
164
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
165
|
-
defaultTtlMs:
|
|
165
|
+
defaultTtlMs: WORKFLOW_TTL_MS,
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -31,8 +31,8 @@ import {
|
|
|
31
31
|
import { validateNocturnalSnapshotIngress } from '../core/nocturnal-snapshot-contract.js';
|
|
32
32
|
import { isExpectedSubagentError } from './subagent-workflow/subagent-error-utils.js';
|
|
33
33
|
import { readPainFlagContract } from '../core/pain.js';
|
|
34
|
-
import { CorrectionObserverWorkflowManager, correctionObserverWorkflowSpec } from './correction-observer-workflow-manager.js';
|
|
35
|
-
import type { CorrectionObserverPayload } from './correction-observer-types.js';
|
|
34
|
+
import { CorrectionObserverWorkflowManager, correctionObserverWorkflowSpec } from './subagent-workflow/correction-observer-workflow-manager.js';
|
|
35
|
+
import type { CorrectionObserverPayload } from './subagent-workflow/correction-observer-types.js';
|
|
36
36
|
import { KeywordOptimizationService } from './keyword-optimization-service.js';
|
|
37
37
|
import { TrajectoryRegistry } from '../core/trajectory.js';
|
|
38
38
|
import { CorrectionCueLearner } from '../core/correction-cue-learner.js';
|
|
@@ -40,15 +40,6 @@ import { CorrectionCueLearner } from '../core/correction-cue-learner.js';
|
|
|
40
40
|
const WORKFLOW_TTL_MS = 5 * 60 * 1000; // 5 minutes default TTL for helper workflows
|
|
41
41
|
import { OpenClawTrinityRuntimeAdapter } from '../core/nocturnal-trinity.js';
|
|
42
42
|
|
|
43
|
-
/**
|
|
44
|
-
* Atomic file write — write to temp then rename to prevent partial writes on crash.
|
|
45
|
-
*/
|
|
46
|
-
function atomicWriteFileSync(filePath: string, data: string): void {
|
|
47
|
-
const tmpPath = filePath + '.tmp';
|
|
48
|
-
fs.writeFileSync(tmpPath, data, 'utf8');
|
|
49
|
-
fs.renameSync(tmpPath, filePath);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
43
|
// ── Workflow Watchdog ────────────────────────────────────────────────────────
|
|
53
44
|
// Detects stale/orphaned workflows, invalid results, and cleanup failures.
|
|
54
45
|
// Runs every heartbeat cycle, catching bugs like:
|
|
@@ -692,7 +683,7 @@ function shouldSkipForDedup(
|
|
|
692
683
|
* Load and migrate the evolution queue. Returns empty array if file doesn't exist.
|
|
693
684
|
*/
|
|
694
685
|
function loadEvolutionQueue(queuePath: string): EvolutionQueueItem[] {
|
|
695
|
-
|
|
686
|
+
|
|
696
687
|
let rawQueue: RawQueueItem[] = [];
|
|
697
688
|
try {
|
|
698
689
|
rawQueue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
|
|
@@ -733,7 +724,7 @@ function enqueueNewSleepReflectionTask(
|
|
|
733
724
|
recentPainContext,
|
|
734
725
|
});
|
|
735
726
|
|
|
736
|
-
|
|
727
|
+
fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
|
|
737
728
|
logger?.info?.(`[PD:EvolutionWorker] Enqueued sleep_reflection task ${taskId}`);
|
|
738
729
|
}
|
|
739
730
|
|
|
@@ -878,7 +869,7 @@ async function doEnqueuePainTask(
|
|
|
878
869
|
retryCount: 0, maxRetries: 3,
|
|
879
870
|
});
|
|
880
871
|
|
|
881
|
-
|
|
872
|
+
fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
|
|
882
873
|
fs.appendFileSync(painFlagPath, `\nstatus: queued\ntask_id: ${taskId}\n`, 'utf8');
|
|
883
874
|
result.enqueued = true;
|
|
884
875
|
|
|
@@ -1667,7 +1658,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
1667
1658
|
|
|
1668
1659
|
// Write claimed state (includes any pain changes from above) and release lock
|
|
1669
1660
|
if (queueChanged) {
|
|
1670
|
-
|
|
1661
|
+
fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
|
|
1671
1662
|
}
|
|
1672
1663
|
releaseLock();
|
|
1673
1664
|
for (const sleepTask of sleepReflectionTasks) {
|
|
@@ -1921,7 +1912,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
1921
1912
|
freshQueue[idx] = sleepTask;
|
|
1922
1913
|
}
|
|
1923
1914
|
}
|
|
1924
|
-
|
|
1915
|
+
fs.writeFileSync(queuePath, JSON.stringify(freshQueue, null, 2), 'utf8');
|
|
1925
1916
|
|
|
1926
1917
|
// Log completions to EvolutionLogger
|
|
1927
1918
|
for (const sleepTask of sleepReflectionTasks) {
|
|
@@ -2043,7 +2034,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
2043
2034
|
|
|
2044
2035
|
if (parsedResult?.updated) {
|
|
2045
2036
|
koService.applyResult(parsedResult);
|
|
2046
|
-
|
|
2037
|
+
learner.recordOptimizationPerformed();
|
|
2047
2038
|
logger?.info?.(`[PD:EvolutionWorker] keyword_optimization applied mutations: ${parsedResult.summary}`);
|
|
2048
2039
|
} else {
|
|
2049
2040
|
logger?.info?.(`[PD:EvolutionWorker] keyword_optimization completed with no updates`);
|
|
@@ -2096,7 +2087,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
2096
2087
|
freshQueue.push(koTask);
|
|
2097
2088
|
}
|
|
2098
2089
|
}
|
|
2099
|
-
fs.writeFileSync(queuePath, JSON.stringify(freshQueue, null, 2));
|
|
2090
|
+
fs.writeFileSync(queuePath, JSON.stringify(freshQueue, null, 2), 'utf8');
|
|
2100
2091
|
} catch (koResultErr) {
|
|
2101
2092
|
logger?.warn?.(`[PD:EvolutionWorker] Failed to write keyword_optimization results: ${String(koResultErr)}`);
|
|
2102
2093
|
} finally {
|
|
@@ -2106,7 +2097,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
2106
2097
|
}
|
|
2107
2098
|
|
|
2108
2099
|
if (queueChanged) {
|
|
2109
|
-
|
|
2100
|
+
fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
|
|
2110
2101
|
}
|
|
2111
2102
|
|
|
2112
2103
|
// Pipeline observability: log stage-level summary at end of cycle
|
|
@@ -2224,7 +2215,7 @@ export async function registerEvolutionTaskSession(
|
|
|
2224
2215
|
if (!task.started_at) {
|
|
2225
2216
|
task.started_at = new Date().toISOString();
|
|
2226
2217
|
}
|
|
2227
|
-
|
|
2218
|
+
fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
|
|
2228
2219
|
return true;
|
|
2229
2220
|
} finally {
|
|
2230
2221
|
releaseLock();
|
|
@@ -2264,7 +2255,7 @@ interface WorkerStatusReport {
|
|
|
2264
2255
|
function writeWorkerStatus(stateDir: string, report: WorkerStatusReport): void {
|
|
2265
2256
|
try {
|
|
2266
2257
|
const statusPath = path.join(stateDir, 'worker-status.json');
|
|
2267
|
-
|
|
2258
|
+
fs.writeFileSync(statusPath, JSON.stringify(report, null, 2), 'utf8');
|
|
2268
2259
|
} catch (statusErr) {
|
|
2269
2260
|
// Non-critical: worker-status.json is for monitoring, failure is acceptable
|
|
2270
2261
|
// (no logger available in this standalone helper)
|
|
@@ -2295,7 +2286,7 @@ async function processEvolutionQueueWithResult(
|
|
|
2295
2286
|
const purgeResult = purgeStaleFailedTasks(queue, logger);
|
|
2296
2287
|
if (purgeResult.purged > 0) {
|
|
2297
2288
|
// Write back the cleaned queue
|
|
2298
|
-
|
|
2289
|
+
fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
|
|
2299
2290
|
}
|
|
2300
2291
|
|
|
2301
2292
|
queueResult.total = queue.length;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { CorrectionCueLearner } from '../core/correction-cue-learner.js';
|
|
10
|
-
import type { CorrectionObserverResult } from './correction-observer-types.js';
|
|
10
|
+
import type { CorrectionObserverResult } from './subagent-workflow/correction-observer-types.js';
|
|
11
11
|
import type { PluginLogger } from '../openclaw-sdk.js';
|
|
12
12
|
import { TrajectoryRegistry } from '../core/trajectory.js';
|
|
13
13
|
|
|
@@ -137,4 +137,4 @@ export type TrajectoryHistoryEntry = {
|
|
|
137
137
|
};
|
|
138
138
|
|
|
139
139
|
/** Re-export CorrectionObserverPayload for convenience */
|
|
140
|
-
export type { CorrectionObserverPayload } from './correction-observer-types.js';
|
|
140
|
+
export type { CorrectionObserverPayload } from './subagent-workflow/correction-observer-types.js';
|