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,463 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import type { PDTaskSpec } from './pd-task-types.js';
|
|
5
|
+
import { BUILTIN_PD_TASKS } from './pd-task-types.js';
|
|
6
|
+
import { readTasks, writeTasks } from './pd-task-store.js';
|
|
7
|
+
import { withLockAsync } from '../utils/file-lock.js';
|
|
8
|
+
|
|
9
|
+
const CRON_STORE_PATH = path.join(
|
|
10
|
+
os.homedir(),
|
|
11
|
+
'.openclaw',
|
|
12
|
+
'cron',
|
|
13
|
+
'jobs.json',
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export interface CronJobState {
|
|
17
|
+
nextRunAtMs?: number;
|
|
18
|
+
runningAtMs?: number;
|
|
19
|
+
lastRunAtMs?: number;
|
|
20
|
+
lastRunStatus?: string;
|
|
21
|
+
lastStatus?: 'ok' | 'error' | 'skipped';
|
|
22
|
+
lastError?: string;
|
|
23
|
+
lastDurationMs?: number;
|
|
24
|
+
consecutiveErrors?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CronJob {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
agentId?: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
deleteAfterRun?: boolean;
|
|
34
|
+
createdAtMs: number;
|
|
35
|
+
updatedAtMs: number;
|
|
36
|
+
schedule: { kind: 'every'; everyMs: number } | { kind: 'at'; at: string } | { kind: 'cron'; expr: string; tz?: string };
|
|
37
|
+
sessionTarget: string;
|
|
38
|
+
wakeMode: string;
|
|
39
|
+
payload: { kind: 'systemEvent'; text: string } | { kind: 'agentTurn'; message: string; model?: string; thinking?: string; timeoutSeconds?: number; lightContext?: boolean; toolsAllow?: string[] };
|
|
40
|
+
delivery?: { mode: string; channel?: string; to?: string };
|
|
41
|
+
state: CronJobState;
|
|
42
|
+
metadata?: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface CronStoreFile {
|
|
46
|
+
version: number;
|
|
47
|
+
jobs: CronJob[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type DiffActionType = 'CREATE' | 'UPDATE' | 'DISABLE' | 'ORPHAN' | 'SKIP';
|
|
51
|
+
|
|
52
|
+
export interface DiffAction {
|
|
53
|
+
type: DiffActionType;
|
|
54
|
+
task?: PDTaskSpec;
|
|
55
|
+
job?: CronJob;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ReconcileError {
|
|
59
|
+
taskId: string;
|
|
60
|
+
message: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ReconcileResult {
|
|
64
|
+
created: string[];
|
|
65
|
+
updated: string[];
|
|
66
|
+
skipped: string[];
|
|
67
|
+
orphaned: string[];
|
|
68
|
+
errors: ReconcileError[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
export interface ReconcileOptions {
|
|
73
|
+
dryRun?: boolean;
|
|
74
|
+
workspaceDir: string;
|
|
75
|
+
/* eslint-disable no-unused-vars -- Reason: logger callback param names intentionally unused - callbacks only invoked for side effects */
|
|
76
|
+
logger?: { info?: (_: string) => void; warn?: (_: string) => void };
|
|
77
|
+
/* eslint-enable no-unused-vars */
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* eslint-disable no-unused-vars -- Reason: logger callbacks have unused param names in type */
|
|
81
|
+
async function readCronStore(logger?: { info?: (_: string) => void; warn?: (_: string) => void }): Promise<CronStoreFile> {
|
|
82
|
+
/* eslint-enable no-unused-vars */
|
|
83
|
+
if (!fs.existsSync(CRON_STORE_PATH)) {
|
|
84
|
+
logger?.info?.(`[PD:Reconciler] cron/jobs.json not found, starting with empty store`);
|
|
85
|
+
return { version: 1, jobs: [] };
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const raw = fs.readFileSync(CRON_STORE_PATH, 'utf-8');
|
|
89
|
+
const store = JSON.parse(raw) as CronStoreFile;
|
|
90
|
+
logger?.info?.(`[PD:Reconciler] Loaded cron/jobs.json: ${store.jobs.length} jobs`);
|
|
91
|
+
return store;
|
|
92
|
+
} catch (err) {
|
|
93
|
+
logger?.warn?.(`[PD:Reconciler] Failed to parse cron/jobs.json: ${String(err)}`);
|
|
94
|
+
return { version: 1, jobs: [] };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function writeCronStore(store: CronStoreFile): Promise<void> {
|
|
99
|
+
await withLockAsync(CRON_STORE_PATH, async () => {
|
|
100
|
+
const tmpPath = CRON_STORE_PATH + '.tmp';
|
|
101
|
+
fs.writeFileSync(tmpPath, JSON.stringify(store, null, 2), 'utf-8');
|
|
102
|
+
fs.renameSync(tmpPath, CRON_STORE_PATH);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function diff(declared: PDTaskSpec[], actual: CronJob[]): DiffAction[] {
|
|
107
|
+
const actions: DiffAction[] = [];
|
|
108
|
+
const actualByName = new Map<string, CronJob>();
|
|
109
|
+
for (const job of actual) {
|
|
110
|
+
actualByName.set(job.name, job);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const task of declared) {
|
|
114
|
+
const job = actualByName.get(task.name);
|
|
115
|
+
|
|
116
|
+
if (!job) {
|
|
117
|
+
if (task.enabled) {
|
|
118
|
+
actions.push({ type: 'CREATE', task });
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
const pdVersion = job.metadata?.pdVersion;
|
|
122
|
+
if (!task.enabled) {
|
|
123
|
+
actions.push({ type: 'DISABLE', task, job });
|
|
124
|
+
} else if (!pdVersion || pdVersion !== task.version) {
|
|
125
|
+
actions.push({ type: 'UPDATE', task, job });
|
|
126
|
+
} else {
|
|
127
|
+
actions.push({ type: 'SKIP', task, job });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const job of actual) {
|
|
133
|
+
if (!job.name.startsWith('PD ')) continue;
|
|
134
|
+
const found = declared.find((t) => t.name === job.name);
|
|
135
|
+
if (!found) {
|
|
136
|
+
actions.push({ type: 'ORPHAN', job });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return actions;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function buildCronJob(
|
|
144
|
+
task: PDTaskSpec,
|
|
145
|
+
nowMs: number,
|
|
146
|
+
// eslint-disable-next-line no-unused-vars -- logger callback param unused in type
|
|
147
|
+
logger?: { info?: (_: string) => void },
|
|
148
|
+
): CronJob {
|
|
149
|
+
logger?.info?.(`[PD:Reconciler] Building cron job: ${task.name} (id=${task.id}, interval=${task.schedule.everyMs}ms)`);
|
|
150
|
+
return {
|
|
151
|
+
id: `pd-${task.id}-${nowMs}`,
|
|
152
|
+
name: task.name,
|
|
153
|
+
agentId: task.agentId || 'main',
|
|
154
|
+
description: task.description,
|
|
155
|
+
enabled: task.enabled,
|
|
156
|
+
schedule: { kind: 'every', everyMs: task.schedule.everyMs },
|
|
157
|
+
sessionTarget: 'isolated',
|
|
158
|
+
wakeMode: 'now',
|
|
159
|
+
payload: {
|
|
160
|
+
kind: 'agentTurn',
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: buildTaskPrompt is defined later in this file, called here for organizational reasons
|
|
162
|
+
message: buildTaskPrompt(task, logger),
|
|
163
|
+
lightContext: task.execution.lightContext ?? true,
|
|
164
|
+
timeoutSeconds: task.execution.timeoutSeconds ?? 120,
|
|
165
|
+
toolsAllow: task.execution.toolsAllow,
|
|
166
|
+
},
|
|
167
|
+
delivery: { mode: task.delivery.mode },
|
|
168
|
+
createdAtMs: nowMs,
|
|
169
|
+
updatedAtMs: nowMs,
|
|
170
|
+
state: {
|
|
171
|
+
nextRunAtMs: nowMs + task.schedule.everyMs,
|
|
172
|
+
},
|
|
173
|
+
metadata: {
|
|
174
|
+
pdVersion: task.version,
|
|
175
|
+
pdTaskId: task.id,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// eslint-disable-next-line no-unused-vars -- logger callback param unused in type
|
|
181
|
+
function buildTaskPrompt(task: PDTaskSpec, logger?: { info?: (_: string) => void }): string {
|
|
182
|
+
if (task.id === 'empathy-optimizer') {
|
|
183
|
+
logger?.info?.(`[PD:Reconciler] Building empathy optimizer prompt`);
|
|
184
|
+
return `You are the Principles Disciple Empathy Keyword Optimizer.
|
|
185
|
+
|
|
186
|
+
## TASK
|
|
187
|
+
Analyze the current empathy keyword store and recent user message logs to:
|
|
188
|
+
1. Discover NEW frustration expressions not in the current store
|
|
189
|
+
2. ADJUST weights of existing terms based on actual hit frequency
|
|
190
|
+
3. REMOVE terms that produce too many false positives
|
|
191
|
+
|
|
192
|
+
## WORKFLOW (execute in order)
|
|
193
|
+
|
|
194
|
+
### Step 1: Read current keyword store
|
|
195
|
+
Use read_file to load:
|
|
196
|
+
\`~/.openclaw/workspace-main/.state/empathy_keywords.json\`
|
|
197
|
+
|
|
198
|
+
Examine the "terms" object. For each term note:
|
|
199
|
+
- weight (0.1-0.9): higher = stronger frustration signal
|
|
200
|
+
- hitCount: how many times it matched
|
|
201
|
+
- falsePositiveRate (0.05-0.5): how often it's a false alarm
|
|
202
|
+
|
|
203
|
+
### Step 2: Read recent message logs
|
|
204
|
+
Use search_file_content to scan:
|
|
205
|
+
\`~/.openclaw/workspace-main/.state/logs/events.jsonl\`
|
|
206
|
+
|
|
207
|
+
Look for user messages containing frustration signals:
|
|
208
|
+
- Negation: "不对", "错了", "不行", "重做"
|
|
209
|
+
- Anger: "垃圾", "蠢", "废物", "白做"
|
|
210
|
+
- Disappointment: "不行啊", "还是不对", "没解决"
|
|
211
|
+
- Escalation: "你到底在干什么", "你确定吗", "what are you doing"
|
|
212
|
+
|
|
213
|
+
### Step 3: Write updated keyword store
|
|
214
|
+
Use write_file to save the updated store back to:
|
|
215
|
+
\`~/.openclaw/workspace-main/.state/empathy_keywords.json\`
|
|
216
|
+
|
|
217
|
+
The file format is:
|
|
218
|
+
\`\`\`json
|
|
219
|
+
{
|
|
220
|
+
"version": 1,
|
|
221
|
+
"lastUpdated": "ISO timestamp",
|
|
222
|
+
"lastOptimizedAt": "ISO timestamp",
|
|
223
|
+
"terms": {
|
|
224
|
+
"TERM": {
|
|
225
|
+
"weight": 0.5,
|
|
226
|
+
"source": "seed|llm_discovered|user_reported",
|
|
227
|
+
"hitCount": 0,
|
|
228
|
+
"falsePositiveRate": 0.15
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
"stats": {
|
|
232
|
+
"totalHits": 0,
|
|
233
|
+
"totalFalsePositives": 0,
|
|
234
|
+
"optimizationCount": 1
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
\`\`\`
|
|
238
|
+
|
|
239
|
+
**IMPORTANT**: You MUST use the write_file tool. Do NOT just return JSON in your response.
|
|
240
|
+
|
|
241
|
+
### Step 4: Report summary
|
|
242
|
+
After writing the file, reply with a brief summary:
|
|
243
|
+
\`\`\`
|
|
244
|
+
Empathy keyword optimization complete:
|
|
245
|
+
- Added: N new terms (list them)
|
|
246
|
+
- Updated: M terms (list changes)
|
|
247
|
+
- Removed: K terms (list them)
|
|
248
|
+
- Total terms in store: X
|
|
249
|
+
\`\`\`
|
|
250
|
+
|
|
251
|
+
## RULES
|
|
252
|
+
- ADD: If you find frustration expressions in logs NOT in current terms
|
|
253
|
+
- source = "llm_discovered", discoveredAt = current ISO timestamp
|
|
254
|
+
- weight: 0.5-0.7 for new terms (start conservative)
|
|
255
|
+
- falsePositiveRate: 0.2-0.3 (uncertain until validated)
|
|
256
|
+
- UPDATE: Adjust based on evidence
|
|
257
|
+
- High hitCount + low FPR → increase weight
|
|
258
|
+
- Low hitCount + high FPR → decrease weight
|
|
259
|
+
- Keep weight in 0.1-0.9, FPR in 0.05-0.5
|
|
260
|
+
- REMOVE: If hitCount=0 AND falsePositiveRate > 0.3 AND term is clearly generic
|
|
261
|
+
- Don't remove terms that might be valid but rare
|
|
262
|
+
- PRESERVE: Keep existing hitCount, lastHitAt, discoveredAt for existing terms
|
|
263
|
+
- Bump stats.optimizationCount by 1
|
|
264
|
+
- Set lastOptimizedAt to current ISO timestamp
|
|
265
|
+
|
|
266
|
+
## EXAMPLES
|
|
267
|
+
- "不对" has hitCount=50 → increase weight from 0.5 to 0.7
|
|
268
|
+
- "呵呵" has hitCount=0, FPR=0.4, generic term → REMOVE
|
|
269
|
+
- User says "烦死了" in logs, not in store → ADD weight=0.6, FPR=0.25`;
|
|
270
|
+
}
|
|
271
|
+
return task.description;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export async function reconcilePDTasks(
|
|
275
|
+
workspaceDir: string,
|
|
276
|
+
options?: Partial<ReconcileOptions>,
|
|
277
|
+
): Promise<ReconcileResult> {
|
|
278
|
+
const dryRun = options?.dryRun ?? false;
|
|
279
|
+
const logger = options?.logger ?? console;
|
|
280
|
+
const nowMs = Date.now();
|
|
281
|
+
|
|
282
|
+
const result: ReconcileResult = { created: [], updated: [], skipped: [], orphaned: [], errors: [] };
|
|
283
|
+
|
|
284
|
+
const storedTasks = readTasks(workspaceDir);
|
|
285
|
+
const storedById = new Map(storedTasks.map((t) => [t.id, t]));
|
|
286
|
+
const declared: PDTaskSpec[] = BUILTIN_PD_TASKS.map((t) => {
|
|
287
|
+
const stored = storedById.get(t.id);
|
|
288
|
+
if (stored) {
|
|
289
|
+
return { ...t, meta: { ...t.meta, ...stored.meta } };
|
|
290
|
+
}
|
|
291
|
+
return { ...t, meta: { createdAtMs: nowMs } };
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const cronStore = await readCronStore(logger);
|
|
295
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: healthCheck is defined later in this file, called here for organizational reasons
|
|
296
|
+
const healthUpdated = healthCheck(declared, cronStore, logger);
|
|
297
|
+
const actions = diff(healthUpdated, cronStore.jobs);
|
|
298
|
+
|
|
299
|
+
for (const action of actions) {
|
|
300
|
+
switch (action.type) {
|
|
301
|
+
case 'CREATE':
|
|
302
|
+
if (action.task) {
|
|
303
|
+
if (!dryRun) {
|
|
304
|
+
const job = buildCronJob(action.task, nowMs, logger);
|
|
305
|
+
cronStore.jobs.push(job);
|
|
306
|
+
logger.info?.(`[PD:Reconciler] Created job: ${action.task.name}`);
|
|
307
|
+
}
|
|
308
|
+
result.created.push(action.task.name);
|
|
309
|
+
}
|
|
310
|
+
break;
|
|
311
|
+
case 'UPDATE':
|
|
312
|
+
if (action.task && action.job) {
|
|
313
|
+
if (!dryRun) {
|
|
314
|
+
const idx = cronStore.jobs.indexOf(action.job);
|
|
315
|
+
const newJob = buildCronJob(action.task, nowMs, logger);
|
|
316
|
+
newJob.id = action.job.id;
|
|
317
|
+
// Preserve original state — only CronService should recalculate nextRunAtMs
|
|
318
|
+
newJob.state = {
|
|
319
|
+
...action.job.state,
|
|
320
|
+
nextRunAtMs: undefined, // Let CronService recalculate
|
|
321
|
+
};
|
|
322
|
+
cronStore.jobs[idx] = newJob;
|
|
323
|
+
logger.info?.(`[PD:Reconciler] Updated job: ${action.task.name}`);
|
|
324
|
+
}
|
|
325
|
+
result.updated.push(action.task.name);
|
|
326
|
+
}
|
|
327
|
+
break;
|
|
328
|
+
case 'DISABLE':
|
|
329
|
+
if (action.job) {
|
|
330
|
+
if (!dryRun) {
|
|
331
|
+
action.job.enabled = false;
|
|
332
|
+
action.job.updatedAtMs = nowMs;
|
|
333
|
+
}
|
|
334
|
+
logger.warn?.(`[PD:Reconciler] Disabled job: ${action.job.name}`);
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
case 'ORPHAN':
|
|
338
|
+
if (action.job) {
|
|
339
|
+
logger.warn?.(`[PD:Reconciler] Orphaned job (no declaration): ${action.job.name}`);
|
|
340
|
+
result.orphaned.push(action.job.name);
|
|
341
|
+
}
|
|
342
|
+
break;
|
|
343
|
+
case 'SKIP':
|
|
344
|
+
if (action.task) {
|
|
345
|
+
result.skipped.push(action.task.name);
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!dryRun && (result.created.length > 0 || result.updated.length > 0)) {
|
|
352
|
+
await writeCronStore(cronStore);
|
|
353
|
+
logger.info?.(`[PD:Reconciler] Wrote cron/jobs.json: ${cronStore.jobs.length} total jobs`);
|
|
354
|
+
for (const job of cronStore.jobs) {
|
|
355
|
+
if (job.name.startsWith('PD ')) {
|
|
356
|
+
logger.info?.(`[PD:Reconciler] PD job: name=${job.name}, enabled=${job.enabled}, schedule=${JSON.stringify(job.schedule)}, nextRun=${job.state.nextRunAtMs ? new Date(job.state.nextRunAtMs).toISOString() : 'TBD'}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (!dryRun && declared.length > 0) {
|
|
362
|
+
await writeTasks(workspaceDir, declared);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
function healthCheck(
|
|
370
|
+
tasks: PDTaskSpec[],
|
|
371
|
+
cronStore: CronStoreFile,
|
|
372
|
+
/* eslint-disable no-unused-vars -- Reason: callback type signature parameters */
|
|
373
|
+
logger: { info?: (_msg: string) => void; warn?: (_msg: string) => void },
|
|
374
|
+
/* eslint-enable no-unused-vars */
|
|
375
|
+
): PDTaskSpec[] {
|
|
376
|
+
const jobByName = new Map(cronStore.jobs.map((j) => [j.name, j]));
|
|
377
|
+
|
|
378
|
+
for (const task of tasks) {
|
|
379
|
+
const job = jobByName.get(task.name);
|
|
380
|
+
if (!job) continue;
|
|
381
|
+
|
|
382
|
+
const errors = job.state.consecutiveErrors ?? 0;
|
|
383
|
+
const lastError = job.state.lastError ?? '';
|
|
384
|
+
const isTimeout = lastError.includes('timed out') || lastError.includes('timeout');
|
|
385
|
+
|
|
386
|
+
// Auto-increase timeout on timeout error (exponential backoff: 2x, max 1800s)
|
|
387
|
+
if (isTimeout && errors > 0 && job.payload.kind === 'agentTurn') {
|
|
388
|
+
const currentTimeout = job.payload.timeoutSeconds ?? 120;
|
|
389
|
+
const newTimeout = Math.min(1800, currentTimeout * 2);
|
|
390
|
+
if (newTimeout > currentTimeout) {
|
|
391
|
+
job.payload.timeoutSeconds = newTimeout;
|
|
392
|
+
job.state.consecutiveErrors = 0;
|
|
393
|
+
job.state.lastError = undefined;
|
|
394
|
+
logger.info?.(`[PD:Reconciler] Auto-increased timeout for '${task.name}': ${currentTimeout}s → ${newTimeout}s (was ${errors} consecutive timeouts)`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Auto-disable only for non-timeout errors after 3 consecutive failures
|
|
399
|
+
if (errors >= 3 && !isTimeout && !task.meta?.autoDisabled) {
|
|
400
|
+
if (!task.meta) task.meta = {};
|
|
401
|
+
task.meta.autoDisabled = true;
|
|
402
|
+
task.meta.autoDisabledAt = Date.now();
|
|
403
|
+
task.meta.autoDisabledReason = `consecutiveErrors=${errors}`;
|
|
404
|
+
logger.warn?.(`[PD:Reconciler] Auto-disabled task '${task.id}' due to ${errors} consecutive errors: ${lastError.substring(0, 80)}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Reset consecutiveErrors on non-error runs
|
|
408
|
+
if (errors === 0 && task.meta?.autoDisabled) {
|
|
409
|
+
logger.info?.(`[PD:Reconciler] Task '${task.id}' was previously auto-disabled but has 0 consecutive errors now`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (task.meta) {
|
|
413
|
+
task.meta.consecutiveFailCount = errors;
|
|
414
|
+
if (job.state.lastRunAtMs) {
|
|
415
|
+
task.meta.lastFailedAtMs = job.state.lastRunAtMs;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return tasks;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export async function trigger(
|
|
423
|
+
taskId: string,
|
|
424
|
+
workspaceDir: string,
|
|
425
|
+
options?: { force?: boolean },
|
|
426
|
+
): Promise<{ ok: boolean; error?: string }> {
|
|
427
|
+
const tasks = readTasks(workspaceDir);
|
|
428
|
+
const task = tasks.find((t) => t.id === taskId);
|
|
429
|
+
if (!task) {
|
|
430
|
+
return { ok: false, error: `Task '${taskId}' not found` };
|
|
431
|
+
}
|
|
432
|
+
if (task.meta?.autoDisabled && !options?.force) {
|
|
433
|
+
return { ok: false, error: 'Task is auto-disabled. Use force=true to override.' };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const log = (msg: string) => console.info(`[PD:Trigger] ${msg}`);
|
|
437
|
+
const cronStore = await readCronStore({ info: log, warn: log });
|
|
438
|
+
const nowMs = Date.now();
|
|
439
|
+
const existingJob = cronStore.jobs.find((j) => j.name === task.name);
|
|
440
|
+
|
|
441
|
+
if (existingJob) {
|
|
442
|
+
log(`Manually triggering existing job: ${task.name} (id=${existingJob.id})`);
|
|
443
|
+
existingJob.enabled = true;
|
|
444
|
+
existingJob.updatedAtMs = nowMs;
|
|
445
|
+
existingJob.state.nextRunAtMs = nowMs;
|
|
446
|
+
existingJob.deleteAfterRun = undefined;
|
|
447
|
+
} else {
|
|
448
|
+
log(`Creating new job for manual trigger: ${task.name}`);
|
|
449
|
+
const newJob = buildCronJob(task, nowMs, { info: log });
|
|
450
|
+
newJob.enabled = true;
|
|
451
|
+
newJob.state.nextRunAtMs = nowMs;
|
|
452
|
+
cronStore.jobs.push(newJob);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (!task.meta) task.meta = {};
|
|
456
|
+
task.meta.lastTriggeredAtMs = nowMs;
|
|
457
|
+
task.meta.lastTriggerStatus = 'pending';
|
|
458
|
+
|
|
459
|
+
await writeCronStore(cronStore);
|
|
460
|
+
await writeTasks(workspaceDir, tasks);
|
|
461
|
+
log(`Trigger complete: nextRunAt=${nowMs}, will run on next cron cycle`);
|
|
462
|
+
return { ok: true };
|
|
463
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { OpenClawPluginService, OpenClawPluginServiceContext } from '../openclaw-sdk.js';
|
|
2
|
+
import { reconcilePDTasks } from './pd-task-reconciler.js';
|
|
3
|
+
|
|
4
|
+
export const PDTaskService: OpenClawPluginService = {
|
|
5
|
+
id: 'principles-disciple-task-manager',
|
|
6
|
+
|
|
7
|
+
async start(ctx: OpenClawPluginServiceContext): Promise<void> {
|
|
8
|
+
const {workspaceDir} = ctx;
|
|
9
|
+
if (!workspaceDir) {
|
|
10
|
+
ctx.logger?.warn?.(`[PD:TaskManager] No workspaceDir, skipping PD task reconciliation`);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const {logger} = ctx;
|
|
15
|
+
logger.info?.(`[PD:TaskManager] Starting PD task reconciliation...`);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const result = await reconcilePDTasks(workspaceDir, { logger });
|
|
19
|
+
logger.info?.(
|
|
20
|
+
`[PD:TaskManager] Reconcile complete: +${result.created.length} ~${result.updated.length} =${result.skipped.length} orphan=${result.orphaned.length}`,
|
|
21
|
+
);
|
|
22
|
+
if (result.created.length > 0) {
|
|
23
|
+
logger.info?.(`[PD:TaskManager] Created jobs: ${result.created.join(', ')}`);
|
|
24
|
+
}
|
|
25
|
+
if (result.updated.length > 0) {
|
|
26
|
+
logger.info?.(`[PD:TaskManager] Updated jobs: ${result.updated.join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
if (result.errors.length > 0) {
|
|
29
|
+
logger.warn?.(
|
|
30
|
+
`[PD:TaskManager] Reconcile errors: ${result.errors.map((e) => e.message).join(', ')}`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
logger.warn?.(`[PD:TaskManager] Reconcile failed: ${String(err)}`);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: stop method required by service interface but no cleanup needed
|
|
39
|
+
stop(_ctx: OpenClawPluginServiceContext): void {
|
|
40
|
+
/* intentionally empty - no cleanup required for this service */
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import type { PDTaskSpec } from './pd-task-types.js';
|
|
4
|
+
import { withLockAsync } from '../utils/file-lock.js';
|
|
5
|
+
|
|
6
|
+
const PD_TASKS_FILENAME = 'pd_tasks.json';
|
|
7
|
+
|
|
8
|
+
function resolvePdTasksPath(workspaceDir: string): string {
|
|
9
|
+
return path.join(workspaceDir, '.state', PD_TASKS_FILENAME);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function ensureStateDir(workspaceDir: string): void {
|
|
13
|
+
const stateDir = path.join(workspaceDir, '.state');
|
|
14
|
+
if (!fs.existsSync(stateDir)) {
|
|
15
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function readTasks(workspaceDir: string): PDTaskSpec[] {
|
|
20
|
+
const filePath = resolvePdTasksPath(workspaceDir);
|
|
21
|
+
if (!fs.existsSync(filePath)) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
26
|
+
const parsed = JSON.parse(raw);
|
|
27
|
+
if (Array.isArray(parsed)) {
|
|
28
|
+
return parsed as PDTaskSpec[];
|
|
29
|
+
}
|
|
30
|
+
return [];
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.warn(`[PD:TaskStore] Failed to parse ${PD_TASKS_FILENAME}: ${String(err)}`);
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function writeTasks(workspaceDir: string, tasks: PDTaskSpec[]): Promise<void> {
|
|
38
|
+
const filePath = resolvePdTasksPath(workspaceDir);
|
|
39
|
+
ensureStateDir(workspaceDir);
|
|
40
|
+
|
|
41
|
+
await withLockAsync(filePath, async () => {
|
|
42
|
+
const tmpPath = filePath + '.tmp';
|
|
43
|
+
fs.writeFileSync(tmpPath, JSON.stringify(tasks, null, 2), 'utf-8');
|
|
44
|
+
fs.renameSync(tmpPath, filePath);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function initTaskMeta(task: PDTaskSpec): PDTaskSpec {
|
|
49
|
+
if (!task.meta) {
|
|
50
|
+
task.meta = {};
|
|
51
|
+
}
|
|
52
|
+
if (!task.meta.createdAtMs) {
|
|
53
|
+
task.meta.createdAtMs = Date.now();
|
|
54
|
+
}
|
|
55
|
+
return task;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- Reason: sync meta update requires task + status + optional jobId + error - refactoring would break API
|
|
59
|
+
export function updateSyncMeta(
|
|
60
|
+
task: PDTaskSpec,
|
|
61
|
+
status: 'ok' | 'error',
|
|
62
|
+
jobId?: string,
|
|
63
|
+
error?: string,
|
|
64
|
+
): PDTaskSpec {
|
|
65
|
+
if (!task.meta) {
|
|
66
|
+
task.meta = {};
|
|
67
|
+
}
|
|
68
|
+
task.meta.lastSyncedAtMs = Date.now();
|
|
69
|
+
task.meta.lastSyncStatus = status;
|
|
70
|
+
if (jobId) {
|
|
71
|
+
task.meta.lastSyncedJobId = jobId;
|
|
72
|
+
}
|
|
73
|
+
if (error) {
|
|
74
|
+
task.meta.lastSyncError = error;
|
|
75
|
+
}
|
|
76
|
+
return task;
|
|
77
|
+
}
|