principles-disciple 1.182.0 → 1.184.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/dist/commands/context.js +17 -11
- package/dist/core/init.js +3 -4
- package/dist/hooks/prompt.d.ts +3 -2
- package/dist/hooks/prompt.js +6 -70
- package/dist/service/evolution-worker.d.ts +2 -2
- package/dist/service/evolution-worker.js +17 -35
- package/dist/service/queue-io.js +14 -2
- package/dist/service/queue-migration.d.ts +25 -0
- package/dist/service/queue-migration.js +57 -0
- package/dist/types.d.ts +5 -25
- package/dist/types.js +4 -9
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/commands/context.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import * as yaml from 'js-yaml';
|
|
3
4
|
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
4
5
|
import { atomicWriteFileSync, normalizeCommandArgs } from '../utils/io.js';
|
|
5
6
|
import { defaultContextConfig } from '../types.js';
|
|
6
7
|
import { loadContextInjectionConfig } from '../hooks/prompt.js';
|
|
8
|
+
import { getPdConfigPath } from '../core/pd-config-loader.js';
|
|
7
9
|
/**
|
|
8
10
|
* Get workspace directory from context
|
|
9
11
|
*/
|
|
@@ -12,26 +14,30 @@ function getWorkspaceDir(ctx) {
|
|
|
12
14
|
return resolvePluginCommandWorkspaceDir(ctx, 'context');
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
|
-
* Save context injection config to
|
|
17
|
+
* Save context injection config to .pd/config.yaml
|
|
16
18
|
*/
|
|
17
19
|
function saveConfig(workspaceDir, config) {
|
|
18
|
-
const
|
|
20
|
+
const configPath = getPdConfigPath(workspaceDir);
|
|
19
21
|
try {
|
|
20
22
|
// Ensure directory exists
|
|
21
|
-
const dir = path.dirname(
|
|
23
|
+
const dir = path.dirname(configPath);
|
|
22
24
|
if (!fs.existsSync(dir)) {
|
|
23
25
|
fs.mkdirSync(dir, { recursive: true });
|
|
24
26
|
}
|
|
25
|
-
// Load existing
|
|
26
|
-
let
|
|
27
|
-
if (fs.existsSync(
|
|
28
|
-
const raw = fs.readFileSync(
|
|
29
|
-
|
|
27
|
+
// Load existing config or start fresh
|
|
28
|
+
let rawConfig = {};
|
|
29
|
+
if (fs.existsSync(configPath)) {
|
|
30
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
31
|
+
const parsed = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
|
|
32
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
33
|
+
rawConfig = parsed;
|
|
34
|
+
}
|
|
30
35
|
}
|
|
31
36
|
// Update contextInjection
|
|
32
|
-
|
|
33
|
-
// Write back
|
|
34
|
-
|
|
37
|
+
rawConfig.contextInjection = config;
|
|
38
|
+
// Write back as YAML
|
|
39
|
+
const yamlStr = yaml.dump(rawConfig, { indent: 2, lineWidth: 120, noRefs: true, forceQuotes: false });
|
|
40
|
+
atomicWriteFileSync(configPath, yamlStr);
|
|
35
41
|
return true;
|
|
36
42
|
}
|
|
37
43
|
catch (e) {
|
package/dist/core/init.js
CHANGED
|
@@ -2,7 +2,6 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { PD_DIRS } from './paths.js';
|
|
5
|
-
import { defaultContextConfig } from '../types.js';
|
|
6
5
|
import { loadStore, setPrincipleState } from './principle-training-state.js';
|
|
7
6
|
import { addPrincipleToLedger } from './principle-tree-ledger.js';
|
|
8
7
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
@@ -10,11 +9,11 @@ import { createDefaultKeywordStore, saveKeywordStore } from './empathy-keyword-m
|
|
|
10
9
|
import { CORE_PRINCIPLES } from '@principles/core/runtime-v2';
|
|
11
10
|
/**
|
|
12
11
|
* Default PROFILE.json content
|
|
12
|
+
* Note: contextInjection is migrated to .pd/config.yaml — not initialized here.
|
|
13
13
|
*/
|
|
14
14
|
const DEFAULT_PROFILE = {
|
|
15
15
|
name: "Principles Disciple Agent",
|
|
16
16
|
version: "1.0.0",
|
|
17
|
-
contextInjection: defaultContextConfig
|
|
18
17
|
};
|
|
19
18
|
const CORE_GUIDANCE_VERSION = 'pd-core-guidance-v2';
|
|
20
19
|
const CORE_GUIDANCE_FILES = new Set(['AGENTS.md', 'TOOLS.md']);
|
|
@@ -85,7 +84,7 @@ export function ensureWorkspaceTemplates(api, workspaceDir, language = 'en') {
|
|
|
85
84
|
}
|
|
86
85
|
copyRecursiveSync(painTemplatesDir, painDestDir, api);
|
|
87
86
|
}
|
|
88
|
-
// 4. Initialize PROFILE.json with
|
|
87
|
+
// 4. Initialize PROFILE.json with project identity metadata
|
|
89
88
|
const principlesDir = path.join(workspaceDir, PD_DIRS.IDENTITY);
|
|
90
89
|
const profilePath = path.join(principlesDir, 'PROFILE.json');
|
|
91
90
|
if (!fs.existsSync(profilePath)) {
|
|
@@ -94,7 +93,7 @@ export function ensureWorkspaceTemplates(api, workspaceDir, language = 'en') {
|
|
|
94
93
|
fs.mkdirSync(principlesDir, { recursive: true });
|
|
95
94
|
}
|
|
96
95
|
atomicWriteFileSync(profilePath, JSON.stringify(DEFAULT_PROFILE, null, 2));
|
|
97
|
-
api.logger.info(`[PD] Initialized PROFILE.json with
|
|
96
|
+
api.logger.info(`[PD] Initialized PROFILE.json with identity metadata (contextInjection migrated to .pd/config.yaml)`);
|
|
98
97
|
}
|
|
99
98
|
}
|
|
100
99
|
catch (err) {
|
package/dist/hooks/prompt.d.ts
CHANGED
|
@@ -11,8 +11,9 @@ export declare function resetPromptStateForTest(workspaceDir?: string): void;
|
|
|
11
11
|
* Constructs the system prompt injected into LLM context for Principles Disciple
|
|
12
12
|
*/
|
|
13
13
|
/**
|
|
14
|
-
* Loads context injection config from .
|
|
15
|
-
*
|
|
14
|
+
* Loads context injection config from .pd/config.yaml via loadPdConfigForPlugin.
|
|
15
|
+
* The resolved config is computed by the core config layer (pd-config-effective.ts),
|
|
16
|
+
* which merges user partial input with DEFAULT_CONTEXT_INJECTION defaults.
|
|
16
17
|
* @internal Used by evolution engine for context settings
|
|
17
18
|
*/
|
|
18
19
|
export declare function loadContextInjectionConfig(workspaceDir: string): ContextInjectionConfig;
|
package/dist/hooks/prompt.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
2
|
import { clearInjectedProbationIds, getSession, resetFriction, setInjectedProbationIds, decayGfi, getGfiDecayElapsed } from '../core/session-tracker.js';
|
|
4
3
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
5
|
-
import { defaultContextConfig } from '../types.js';
|
|
6
4
|
// local-worker-routing module and its routing helpers removed entirely per PRI-448.
|
|
7
5
|
// Routing guidance is no longer injected into prompts.
|
|
8
6
|
import { extractSummary, getHistoryVersions, parseWorkingMemorySection, workingMemoryToInjection, autoCompressFocus, safeReadCurrentFocus } from '../core/focus-history.js';
|
|
@@ -11,7 +9,7 @@ import { selectPrinciplesForInjection, DEFAULT_PRINCIPLE_BUDGET } from '../core/
|
|
|
11
9
|
import { getCachedMaskedPrincipleSet, RUNTIME_V2_PRINCIPLE_BUDGET, trimToBudget, renderPrinciplesToDirectives } from '@principles/core/runtime-v2';
|
|
12
10
|
import { truncateInjectionToBudget } from '@principles/core/prompt-builder';
|
|
13
11
|
import { PromptActivationReader } from '../core/runtime-v2-prompt-activation-reader.js';
|
|
14
|
-
import { loadFeatureFlagFromConfig } from '../core/pd-config-loader.js';
|
|
12
|
+
import { loadPdConfigForPlugin, loadFeatureFlagFromConfig } from '../core/pd-config-loader.js';
|
|
15
13
|
import { safeReadIntentDoc, resetIntentDocCacheForTest } from '../core/intent-doc-reader.js';
|
|
16
14
|
import { resolveIntentLang } from '../core/intent-doc-reader-adapter.js';
|
|
17
15
|
import { buildIntentFrictionBlock } from '@principles/core/runtime-v2';
|
|
@@ -108,81 +106,19 @@ function getSignalCollectorHost(wctx, logger) {
|
|
|
108
106
|
}
|
|
109
107
|
return host;
|
|
110
108
|
}
|
|
111
|
-
function parseContextInjectionConfig(value) {
|
|
112
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
113
|
-
return null;
|
|
114
|
-
const config = { ...defaultContextConfig };
|
|
115
|
-
const thinkingOs = getOwnValue(value, 'thinkingOs');
|
|
116
|
-
const projectFocus = getOwnValue(value, 'projectFocus');
|
|
117
|
-
const evolutionContext = getOwnValue(value, 'evolutionContext');
|
|
118
|
-
if (thinkingOs !== undefined) {
|
|
119
|
-
if (typeof thinkingOs !== 'boolean')
|
|
120
|
-
return null;
|
|
121
|
-
config.thinkingOs = thinkingOs;
|
|
122
|
-
}
|
|
123
|
-
if (projectFocus !== undefined) {
|
|
124
|
-
if (projectFocus !== 'full' && projectFocus !== 'summary' && projectFocus !== 'off')
|
|
125
|
-
return null;
|
|
126
|
-
config.projectFocus = projectFocus;
|
|
127
|
-
}
|
|
128
|
-
if (evolutionContext !== undefined) {
|
|
129
|
-
if (typeof evolutionContext !== 'object' || evolutionContext === null || Array.isArray(evolutionContext))
|
|
130
|
-
return null;
|
|
131
|
-
config.evolutionContext = { ...defaultContextConfig.evolutionContext };
|
|
132
|
-
const enabled = getOwnValue(evolutionContext, 'enabled');
|
|
133
|
-
const maxMessages = getOwnValue(evolutionContext, 'maxMessages');
|
|
134
|
-
const maxCharsPerMessage = getOwnValue(evolutionContext, 'maxCharsPerMessage');
|
|
135
|
-
if (enabled !== undefined) {
|
|
136
|
-
if (typeof enabled !== 'boolean')
|
|
137
|
-
return null;
|
|
138
|
-
config.evolutionContext.enabled = enabled;
|
|
139
|
-
}
|
|
140
|
-
if (maxMessages !== undefined) {
|
|
141
|
-
if (typeof maxMessages !== 'number' || !Number.isFinite(maxMessages))
|
|
142
|
-
return null;
|
|
143
|
-
config.evolutionContext.maxMessages = maxMessages;
|
|
144
|
-
}
|
|
145
|
-
if (maxCharsPerMessage !== undefined) {
|
|
146
|
-
if (typeof maxCharsPerMessage !== 'number' || !Number.isFinite(maxCharsPerMessage))
|
|
147
|
-
return null;
|
|
148
|
-
config.evolutionContext.maxCharsPerMessage = maxCharsPerMessage;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return config;
|
|
152
|
-
}
|
|
153
109
|
/**
|
|
154
110
|
* OpenClaw API Prompt Hook
|
|
155
111
|
* Constructs the system prompt injected into LLM context for Principles Disciple
|
|
156
112
|
*/
|
|
157
113
|
/**
|
|
158
|
-
* Loads context injection config from .
|
|
159
|
-
*
|
|
114
|
+
* Loads context injection config from .pd/config.yaml via loadPdConfigForPlugin.
|
|
115
|
+
* The resolved config is computed by the core config layer (pd-config-effective.ts),
|
|
116
|
+
* which merges user partial input with DEFAULT_CONTEXT_INJECTION defaults.
|
|
160
117
|
* @internal Used by evolution engine for context settings
|
|
161
118
|
*/
|
|
162
119
|
export function loadContextInjectionConfig(workspaceDir) {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
const raw = cachedReadFile(profilePath, workspaceDir);
|
|
166
|
-
if (raw) {
|
|
167
|
-
const profile = JSON.parse(raw);
|
|
168
|
-
if (profile && typeof profile === 'object' && !Array.isArray(profile)) {
|
|
169
|
-
const contextInjection = getOwnValue(profile, 'contextInjection');
|
|
170
|
-
if (contextInjection !== undefined) {
|
|
171
|
-
const parsed = parseContextInjectionConfig(contextInjection);
|
|
172
|
-
if (!parsed) {
|
|
173
|
-
console.warn(`[PD:Prompt] Invalid contextInjection config in ${profilePath}; using defaults.`);
|
|
174
|
-
return { ...defaultContextConfig };
|
|
175
|
-
}
|
|
176
|
-
return parsed;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
catch (e) {
|
|
182
|
-
// Failed to load config — continue with defaults, but log for diagnostics
|
|
183
|
-
console.warn(`[PD:Prompt] Failed to load contextInjection config: ${String(e)}`);
|
|
184
|
-
}
|
|
185
|
-
return { ...defaultContextConfig };
|
|
120
|
+
const result = loadPdConfigForPlugin(workspaceDir);
|
|
121
|
+
return { ...result.effective.resolvedContextInjection };
|
|
186
122
|
}
|
|
187
123
|
export async function handleBeforePromptBuild(event, ctx) {
|
|
188
124
|
const { workspaceDir } = ctx;
|
|
@@ -53,8 +53,8 @@ export interface EvolutionQueueItem {
|
|
|
53
53
|
resultRef?: string;
|
|
54
54
|
painEventId?: number;
|
|
55
55
|
}
|
|
56
|
-
import { migrateToV2, isLegacyQueueItem, migrateQueueToV2, LegacyEvolutionQueueItem, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES, type RawQueueItem } from './queue-migration.js';
|
|
57
|
-
export { migrateToV2, isLegacyQueueItem, migrateQueueToV2, LegacyEvolutionQueueItem, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES };
|
|
56
|
+
import { migrateToV2, isLegacyQueueItem, migrateQueueToV2, LegacyEvolutionQueueItem, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES, validateQueueItem, VALID_TASK_KINDS, isValidQueueItem, type RawQueueItem } from './queue-migration.js';
|
|
57
|
+
export { migrateToV2, isLegacyQueueItem, migrateQueueToV2, LegacyEvolutionQueueItem, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES, validateQueueItem, VALID_TASK_KINDS, isValidQueueItem };
|
|
58
58
|
export type { RawQueueItem };
|
|
59
59
|
export declare function extractEvolutionTaskId(task: string): string | null;
|
|
60
60
|
export declare function purgeStaleFailedTasks(queue: EvolutionQueueItem[], logger: PluginLogger): {
|
|
@@ -11,7 +11,7 @@ import { atomicWriteFileSync } from '../utils/io.js';
|
|
|
11
11
|
// Re-export queue I/O (extracted to queue-io.ts)
|
|
12
12
|
export { loadEvolutionQueue, saveEvolutionQueue, withQueueLock, acquireQueueLock, requireQueueLock } from './queue-io.js';
|
|
13
13
|
export { EVOLUTION_QUEUE_LOCK_SUFFIX, LOCK_MAX_RETRIES, LOCK_RETRY_DELAY_MS, LOCK_STALE_MS } from './queue-io.js';
|
|
14
|
-
import { saveEvolutionQueue, requireQueueLock } from './queue-io.js';
|
|
14
|
+
import { saveEvolutionQueue, requireQueueLock, loadEvolutionQueue } from './queue-io.js';
|
|
15
15
|
import { WorkflowStore } from './subagent-workflow/workflow-store.js';
|
|
16
16
|
import { PrincipleCompiler } from '../core/principle-compiler/index.js';
|
|
17
17
|
import { loadLedger, updatePrinciple } from '../core/principle-tree-ledger.js';
|
|
@@ -58,8 +58,8 @@ import { runWorkflowWatchdog } from './workflow-watchdog.js';
|
|
|
58
58
|
export { runWorkflowWatchdog };
|
|
59
59
|
let timeoutId = null;
|
|
60
60
|
// ── Queue Migration (extracted to queue-migration.ts) ────────────────────────
|
|
61
|
-
import { migrateToV2, isLegacyQueueItem, migrateQueueToV2, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES } from './queue-migration.js';
|
|
62
|
-
export { migrateToV2, isLegacyQueueItem, migrateQueueToV2, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES };
|
|
61
|
+
import { migrateToV2, isLegacyQueueItem, migrateQueueToV2, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES, validateQueueItem, VALID_TASK_KINDS, isValidQueueItem } from './queue-migration.js';
|
|
62
|
+
export { migrateToV2, isLegacyQueueItem, migrateQueueToV2, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES, validateQueueItem, VALID_TASK_KINDS, isValidQueueItem };
|
|
63
63
|
// Queue lock constants and requireQueueLock are imported from queue-io.ts
|
|
64
64
|
export function extractEvolutionTaskId(task) {
|
|
65
65
|
if (!task)
|
|
@@ -302,30 +302,12 @@ async function processEvolutionQueue(wctx, logger, _eventLog, _api) {
|
|
|
302
302
|
logger?.info?.(`[PD:EvolutionWorker] Dropped ${beforeLegacyPainDrop - queue.length} legacy pain_diagnosis queue item(s); use PainSignalBridge/pd pain record`);
|
|
303
303
|
}
|
|
304
304
|
// Validate queue items — filter out malformed entries before processing.
|
|
305
|
-
//
|
|
305
|
+
// rc-1/rc-4 (ERR-007): consolidated in validateQueueItem (queue-migration.ts)
|
|
306
|
+
// so every load path applies the same filter. Malformed items are logged +
|
|
307
|
+
// skipped; they never crash the evolution cycle.
|
|
306
308
|
const beforeValidation = queue.length;
|
|
307
309
|
queue = queue.filter((item) => {
|
|
308
|
-
const errors =
|
|
309
|
-
if (!item.id || typeof item.id !== 'string')
|
|
310
|
-
errors.push('missing/invalid id');
|
|
311
|
-
if (!item.source || typeof item.source !== 'string')
|
|
312
|
-
errors.push('missing/invalid source');
|
|
313
|
-
if (typeof item.score !== 'number')
|
|
314
|
-
errors.push('missing/invalid score');
|
|
315
|
-
if (!item.status || typeof item.status !== 'string')
|
|
316
|
-
errors.push('missing/invalid status');
|
|
317
|
-
if (!item.taskKind || typeof item.taskKind !== 'string')
|
|
318
|
-
errors.push('missing/invalid taskKind');
|
|
319
|
-
else {
|
|
320
|
-
const validTaskKinds = ['model_eval'];
|
|
321
|
-
if (!validTaskKinds.includes(item.taskKind)) {
|
|
322
|
-
errors.push(`invalid taskKind value '${item.taskKind}' (expected one of: ${validTaskKinds.join(', ')})`);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
if (typeof item.retryCount !== 'number')
|
|
326
|
-
errors.push('missing/invalid retryCount');
|
|
327
|
-
if (typeof item.maxRetries !== 'number')
|
|
328
|
-
errors.push('missing/invalid maxRetries');
|
|
310
|
+
const errors = validateQueueItem(item);
|
|
329
311
|
if (errors.length > 0) {
|
|
330
312
|
logger?.warn?.(`[PD:EvolutionWorker] Skipping malformed queue item: ${errors.join(', ')} | ${JSON.stringify(item).slice(0, 200)}`);
|
|
331
313
|
SystemLogger.log(wctx.workspaceDir, 'QUEUE_ITEM_MALFORMED', `Skipped: ${errors.join(', ')} | id=${item.id || 'N/A'}`);
|
|
@@ -361,16 +343,16 @@ export async function registerEvolutionTaskSession(workspaceResolve, taskId, ses
|
|
|
361
343
|
return false;
|
|
362
344
|
const releaseLock = await requireQueueLock(queuePath, logger, 'registerEvolutionTaskSession');
|
|
363
345
|
try {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
//
|
|
373
|
-
const queue =
|
|
346
|
+
// loadEvolutionQueue handles JSON parse + migrate + validate (rc-1/rc-4).
|
|
347
|
+
// Previously this inlined JSON.parse + migrate cast without validating,
|
|
348
|
+
// so malformed queue items could reach the find() below. Reusing the
|
|
349
|
+
// shared loader keeps a single chokepoint for queue validation.
|
|
350
|
+
// Note: loadEvolutionQueue returns core/evolution-types.EvolutionQueueItem,
|
|
351
|
+
// which saveEvolutionQueue also accepts. We deliberately avoid annotating
|
|
352
|
+
// with the local EvolutionQueueItem interface (line 87) to prevent the
|
|
353
|
+
// known type-drift between the two definitions (see follow-up: unify the
|
|
354
|
+
// three EvolutionQueueItem copies).
|
|
355
|
+
const queue = loadEvolutionQueue(queuePath);
|
|
374
356
|
const task = queue.find((item) => item.id === taskId && item.status === 'in_progress');
|
|
375
357
|
if (!task) {
|
|
376
358
|
logger?.warn?.(`[PD:EvolutionWorker] Could not find in-progress evolution task ${taskId} for session assignment`);
|
package/dist/service/queue-io.js
CHANGED
|
@@ -11,7 +11,7 @@ import { createHash } from 'crypto';
|
|
|
11
11
|
import { acquireLockAsync, releaseLock as releaseImportedLock } from '../utils/file-lock.js';
|
|
12
12
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
13
13
|
import { LockUnavailableError } from '../config/errors.js';
|
|
14
|
-
import { migrateQueueToV2 } from './queue-migration.js';
|
|
14
|
+
import { migrateQueueToV2, validateQueueItem } from './queue-migration.js';
|
|
15
15
|
export const EVOLUTION_QUEUE_LOCK_SUFFIX = '.lock';
|
|
16
16
|
export const LOCK_MAX_RETRIES = 50;
|
|
17
17
|
export const LOCK_RETRY_DELAY_MS = 50;
|
|
@@ -72,7 +72,19 @@ export function loadEvolutionQueue(queuePath) {
|
|
|
72
72
|
rawQueue = [];
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
// rc-1/rc-4 (ERR-007): every element of the parsed array is untrusted disk
|
|
76
|
+
// JSON — validate before returning. Malformed items are filtered out and
|
|
77
|
+
// reported via console.warn (rc-9: no silent drop). This is the single
|
|
78
|
+
// chokepoint that loadEvolutionQueue callers inherit.
|
|
79
|
+
const migrated = migrateQueueToV2(rawQueue);
|
|
80
|
+
return migrated.filter((item) => {
|
|
81
|
+
const errors = validateQueueItem(item);
|
|
82
|
+
if (errors.length > 0) {
|
|
83
|
+
console.warn(`[queue-io] Dropping malformed queue item: ${errors.join(', ')} | id=${item?.id ?? 'N/A'}`);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
});
|
|
76
88
|
}
|
|
77
89
|
export function saveEvolutionQueue(queuePath, queue) {
|
|
78
90
|
atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
|
|
@@ -81,3 +81,28 @@ export declare function isLegacyQueueItem(item: RawQueueItem): boolean;
|
|
|
81
81
|
* Returns a new array with all items migrated to V2 format.
|
|
82
82
|
*/
|
|
83
83
|
export declare function migrateQueueToV2(queue: RawQueueItem[]): EvolutionQueueItem[];
|
|
84
|
+
/**
|
|
85
|
+
* Canonical set of valid taskKind values. Single source of truth — mirrors
|
|
86
|
+
* TaskKind in trajectory-types.ts. Previously the inline validator in
|
|
87
|
+
* evolution-worker.ts hard-coded `['model_eval']`, which silently rejected
|
|
88
|
+
* three legitimate taskKind values (pain_diagnosis / sleep_reflection /
|
|
89
|
+
* keyword_optimization). Keep this in sync with TaskKind.
|
|
90
|
+
*/
|
|
91
|
+
export declare const VALID_TASK_KINDS: readonly TaskKind[];
|
|
92
|
+
/**
|
|
93
|
+
* Validate a single queue item loaded from untrusted disk JSON.
|
|
94
|
+
*
|
|
95
|
+
* Returns an array of human-readable error reasons; an empty array means the
|
|
96
|
+
* item is valid. Use {@link isValidQueueItem} for a type-guard form.
|
|
97
|
+
*
|
|
98
|
+
* This consolidates the inline validation that lived in processEvolutionQueue
|
|
99
|
+
* (evolution-worker.ts) so every queue load path (loadEvolutionQueue,
|
|
100
|
+
* registerEvolutionTaskSession) applies the same filter. rc-4 (ERR-007):
|
|
101
|
+
* array elements from parsed JSON must be element-wise validated.
|
|
102
|
+
*/
|
|
103
|
+
export declare function validateQueueItem(item: unknown): string[];
|
|
104
|
+
/**
|
|
105
|
+
* Type-guard convenience over {@link validateQueueItem}.
|
|
106
|
+
* Drops the reason strings (callers that need them should call validateQueueItem).
|
|
107
|
+
*/
|
|
108
|
+
export declare function isValidQueueItem(item: unknown): item is EvolutionQueueItem;
|
|
@@ -54,3 +54,60 @@ export function isLegacyQueueItem(item) {
|
|
|
54
54
|
export function migrateQueueToV2(queue) {
|
|
55
55
|
return queue.map(item => isLegacyQueueItem(item) ? migrateToV2(item) : item);
|
|
56
56
|
}
|
|
57
|
+
// ── Validation (rc-1/rc-2, rc-4: validate array elements from untrusted JSON) ─
|
|
58
|
+
/**
|
|
59
|
+
* Canonical set of valid taskKind values. Single source of truth — mirrors
|
|
60
|
+
* TaskKind in trajectory-types.ts. Previously the inline validator in
|
|
61
|
+
* evolution-worker.ts hard-coded `['model_eval']`, which silently rejected
|
|
62
|
+
* three legitimate taskKind values (pain_diagnosis / sleep_reflection /
|
|
63
|
+
* keyword_optimization). Keep this in sync with TaskKind.
|
|
64
|
+
*/
|
|
65
|
+
export const VALID_TASK_KINDS = [
|
|
66
|
+
'pain_diagnosis',
|
|
67
|
+
'sleep_reflection',
|
|
68
|
+
'model_eval',
|
|
69
|
+
'keyword_optimization',
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* Validate a single queue item loaded from untrusted disk JSON.
|
|
73
|
+
*
|
|
74
|
+
* Returns an array of human-readable error reasons; an empty array means the
|
|
75
|
+
* item is valid. Use {@link isValidQueueItem} for a type-guard form.
|
|
76
|
+
*
|
|
77
|
+
* This consolidates the inline validation that lived in processEvolutionQueue
|
|
78
|
+
* (evolution-worker.ts) so every queue load path (loadEvolutionQueue,
|
|
79
|
+
* registerEvolutionTaskSession) applies the same filter. rc-4 (ERR-007):
|
|
80
|
+
* array elements from parsed JSON must be element-wise validated.
|
|
81
|
+
*/
|
|
82
|
+
export function validateQueueItem(item) {
|
|
83
|
+
const errors = [];
|
|
84
|
+
if (item === null || typeof item !== 'object' || Array.isArray(item)) {
|
|
85
|
+
return ['item is not an object'];
|
|
86
|
+
}
|
|
87
|
+
const it = item;
|
|
88
|
+
if (!it.id || typeof it.id !== 'string')
|
|
89
|
+
errors.push('missing/invalid id');
|
|
90
|
+
if (!it.source || typeof it.source !== 'string')
|
|
91
|
+
errors.push('missing/invalid source');
|
|
92
|
+
if (typeof it.score !== 'number')
|
|
93
|
+
errors.push('missing/invalid score');
|
|
94
|
+
if (!it.status || typeof it.status !== 'string')
|
|
95
|
+
errors.push('missing/invalid status');
|
|
96
|
+
if (!it.taskKind || typeof it.taskKind !== 'string')
|
|
97
|
+
errors.push('missing/invalid taskKind');
|
|
98
|
+
else if (!VALID_TASK_KINDS.includes(it.taskKind)) {
|
|
99
|
+
errors.push(`invalid taskKind value '${it.taskKind}' (expected one of: ${VALID_TASK_KINDS.join(', ')})`);
|
|
100
|
+
}
|
|
101
|
+
if (typeof it.retryCount !== 'number')
|
|
102
|
+
errors.push('missing/invalid retryCount');
|
|
103
|
+
if (typeof it.maxRetries !== 'number')
|
|
104
|
+
errors.push('missing/invalid maxRetries');
|
|
105
|
+
return errors;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Type-guard convenience over {@link validateQueueItem}.
|
|
109
|
+
* Drops the reason strings (callers that need them should call validateQueueItem).
|
|
110
|
+
*/
|
|
111
|
+
export function isValidQueueItem(item) {
|
|
112
|
+
return validateQueueItem(item).length === 0;
|
|
113
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,33 +1,13 @@
|
|
|
1
1
|
export type { PluginCommandContext, PluginCommandResult } from './openclaw-sdk.js';
|
|
2
|
-
|
|
3
|
-
* Context Injection Configuration
|
|
4
|
-
* Controls what content gets injected into the LLM prompt.
|
|
5
|
-
*
|
|
6
|
-
* NOTE: Core Principles (PRINCIPLES.md) are ALWAYS injected and cannot be disabled.
|
|
7
|
-
* This is by design - principles are the foundation of the agent's behavior.
|
|
8
|
-
*/
|
|
9
|
-
export type ProjectFocusMode = 'full' | 'summary' | 'off';
|
|
10
|
-
export interface EvolutionContextConfig {
|
|
11
|
-
/** Enable conversation context in evolution task (default: true) */
|
|
12
|
-
enabled: boolean;
|
|
13
|
-
/** Max recent messages included in evolution task (default: 4) */
|
|
14
|
-
maxMessages: number;
|
|
15
|
-
/** Max chars per message snippet (default: 200) */
|
|
16
|
-
maxCharsPerMessage: number;
|
|
17
|
-
}
|
|
18
|
-
export interface ContextInjectionConfig {
|
|
19
|
-
/** Thinking OS (mental models) - can be toggled */
|
|
20
|
-
thinkingOs: boolean;
|
|
21
|
-
/** Project context (CURRENT_FOCUS.md) mode */
|
|
22
|
-
projectFocus: ProjectFocusMode;
|
|
23
|
-
/** Evolution task context injection settings */
|
|
24
|
-
evolutionContext: EvolutionContextConfig;
|
|
25
|
-
}
|
|
2
|
+
export type { ContextInjectionConfig, EvolutionContextConfig, ProjectFocusMode, } from '@principles/core';
|
|
26
3
|
/**
|
|
27
4
|
* Default context injection configuration
|
|
28
5
|
* Based on MVP-first strategy (ADR-0014):
|
|
29
6
|
* - principles: always on (not configurable)
|
|
30
7
|
* - thinkingOs: false by default (MVP-Quiet, user can opt-in via /pd-context)
|
|
31
8
|
* - projectFocus: 'off' (default closed, user can enable)
|
|
9
|
+
*
|
|
10
|
+
* Migrated to DEFAULT_CONTEXT_INJECTION in @principles/core.
|
|
11
|
+
* Re-exported for backward compatibility.
|
|
32
12
|
*/
|
|
33
|
-
export
|
|
13
|
+
export { DEFAULT_CONTEXT_INJECTION as defaultContextConfig } from '@principles/core';
|
package/dist/types.js
CHANGED
|
@@ -4,13 +4,8 @@
|
|
|
4
4
|
* - principles: always on (not configurable)
|
|
5
5
|
* - thinkingOs: false by default (MVP-Quiet, user can opt-in via /pd-context)
|
|
6
6
|
* - projectFocus: 'off' (default closed, user can enable)
|
|
7
|
+
*
|
|
8
|
+
* Migrated to DEFAULT_CONTEXT_INJECTION in @principles/core.
|
|
9
|
+
* Re-exported for backward compatibility.
|
|
7
10
|
*/
|
|
8
|
-
export
|
|
9
|
-
thinkingOs: false,
|
|
10
|
-
projectFocus: 'off',
|
|
11
|
-
evolutionContext: {
|
|
12
|
-
enabled: true,
|
|
13
|
-
maxMessages: 4,
|
|
14
|
-
maxCharsPerMessage: 200,
|
|
15
|
-
},
|
|
16
|
-
};
|
|
11
|
+
export { DEFAULT_CONTEXT_INJECTION as defaultContextConfig } from '@principles/core';
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "1.184.0",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onCapabilities": [
|
|
8
8
|
"hook"
|