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
package/src/core/trajectory.ts
CHANGED
|
@@ -22,7 +22,6 @@ import type {
|
|
|
22
22
|
TaskKind,
|
|
23
23
|
TaskPriority,
|
|
24
24
|
EvolutionTaskInput,
|
|
25
|
-
EvolutionTaskInputV2,
|
|
26
25
|
EvolutionEventInput,
|
|
27
26
|
EvolutionTaskRecord,
|
|
28
27
|
EvolutionEventRecord,
|
|
@@ -50,7 +49,6 @@ export type {
|
|
|
50
49
|
TaskKind,
|
|
51
50
|
TaskPriority,
|
|
52
51
|
EvolutionTaskInput,
|
|
53
|
-
EvolutionTaskInputV2,
|
|
54
52
|
EvolutionEventInput,
|
|
55
53
|
EvolutionTaskRecord,
|
|
56
54
|
EvolutionEventRecord,
|
|
@@ -210,7 +208,8 @@ export class TrajectoryDatabase {
|
|
|
210
208
|
const createdAt = input.createdAt ?? nowIso();
|
|
211
209
|
// Extract filePath from paramsJson if provided and is an object with filePath
|
|
212
210
|
const paramsObj = input.paramsJson as Record<string, unknown> | undefined;
|
|
213
|
-
|
|
211
|
+
/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: _filePath extracted for potential future use but currently unused */
|
|
212
|
+
const _filePath = paramsObj && typeof paramsObj.filePath === 'string' ? paramsObj.filePath : null;
|
|
214
213
|
const rowId = this.withWrite(() => {
|
|
215
214
|
const result = this.db.prepare(`
|
|
216
215
|
INSERT INTO tool_calls (
|
|
@@ -272,7 +271,7 @@ export class TrajectoryDatabase {
|
|
|
272
271
|
* Search pain_events using FTS5 full-text search (MEM-04).
|
|
273
272
|
* Returns pain events matching the query, ordered by relevance.
|
|
274
273
|
*/
|
|
275
|
-
searchPainEvents(query: string, limit
|
|
274
|
+
searchPainEvents(query: string, limit = 10): {
|
|
276
275
|
id: number;
|
|
277
276
|
sessionId: string;
|
|
278
277
|
source: string;
|
|
@@ -283,7 +282,7 @@ export class TrajectoryDatabase {
|
|
|
283
282
|
confidence: number | null;
|
|
284
283
|
text: string | null;
|
|
285
284
|
createdAt: string;
|
|
286
|
-
}
|
|
285
|
+
}[] {
|
|
287
286
|
if (!query || query.trim().length === 0) {
|
|
288
287
|
return [];
|
|
289
288
|
}
|
|
@@ -299,7 +298,7 @@ export class TrajectoryDatabase {
|
|
|
299
298
|
WHERE pain_events_fts MATCH ?
|
|
300
299
|
ORDER BY bm25(pain_events_fts) DESC
|
|
301
300
|
LIMIT ?
|
|
302
|
-
`).all(ftsQuery, limit) as
|
|
301
|
+
`).all(ftsQuery, limit) as {
|
|
303
302
|
id: number;
|
|
304
303
|
session_id: string;
|
|
305
304
|
source: string;
|
|
@@ -310,7 +309,7 @@ export class TrajectoryDatabase {
|
|
|
310
309
|
confidence: number | null;
|
|
311
310
|
text: string | null;
|
|
312
311
|
created_at: string;
|
|
313
|
-
}
|
|
312
|
+
}[];
|
|
314
313
|
|
|
315
314
|
return results.map(row => ({
|
|
316
315
|
id: row.id,
|
|
@@ -396,7 +395,7 @@ export class TrajectoryDatabase {
|
|
|
396
395
|
recordEvolutionTask(input: EvolutionTaskInput): void {
|
|
397
396
|
const now = nowIso();
|
|
398
397
|
// Cast to V2 to access new fields
|
|
399
|
-
const v2 = input
|
|
398
|
+
const v2 = input;
|
|
400
399
|
this.withWrite(() => {
|
|
401
400
|
this.db.prepare(`
|
|
402
401
|
INSERT INTO evolution_tasks (
|
|
@@ -442,7 +441,7 @@ export class TrajectoryDatabase {
|
|
|
442
441
|
updateEvolutionTask(taskId: string, updates: Partial<Omit<EvolutionTaskInput, 'taskId' | 'traceId' | 'source'>>): void {
|
|
443
442
|
const now = nowIso();
|
|
444
443
|
// Cast to V2 to access new fields
|
|
445
|
-
const v2Updates = updates
|
|
444
|
+
const v2Updates = updates;
|
|
446
445
|
this.withWrite(() => {
|
|
447
446
|
const setClauses: string[] = ['updated_at = ?'];
|
|
448
447
|
const values: unknown[] = [now];
|
|
@@ -553,7 +552,7 @@ export class TrajectoryDatabase {
|
|
|
553
552
|
${whereClause}
|
|
554
553
|
ORDER BY created_at DESC
|
|
555
554
|
LIMIT ? OFFSET ?
|
|
556
|
-
`).all(...values, limit, offset) as
|
|
555
|
+
`).all(...values, limit, offset) as Record<string, unknown>[];
|
|
557
556
|
|
|
558
557
|
return rows.map((row) => ({
|
|
559
558
|
id: Number(row.id),
|
|
@@ -588,7 +587,8 @@ export class TrajectoryDatabase {
|
|
|
588
587
|
const limit = filters.limit ?? 100;
|
|
589
588
|
const offset = filters.offset ?? 0;
|
|
590
589
|
|
|
591
|
-
|
|
590
|
+
// eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in both if/else branches
|
|
591
|
+
let rows: Record<string, unknown>[];
|
|
592
592
|
if (traceId) {
|
|
593
593
|
rows = this.db.prepare(`
|
|
594
594
|
SELECT id, trace_id, task_id, stage, level, message, summary, metadata_json, created_at
|
|
@@ -596,14 +596,14 @@ export class TrajectoryDatabase {
|
|
|
596
596
|
WHERE trace_id = ?
|
|
597
597
|
ORDER BY created_at ASC
|
|
598
598
|
LIMIT ? OFFSET ?
|
|
599
|
-
`).all(traceId, limit, offset) as
|
|
599
|
+
`).all(traceId, limit, offset) as Record<string, unknown>[];
|
|
600
600
|
} else {
|
|
601
601
|
rows = this.db.prepare(`
|
|
602
602
|
SELECT id, trace_id, task_id, stage, level, message, summary, metadata_json, created_at
|
|
603
603
|
FROM evolution_events
|
|
604
604
|
ORDER BY created_at DESC
|
|
605
605
|
LIMIT ? OFFSET ?
|
|
606
|
-
`).all(limit, offset) as
|
|
606
|
+
`).all(limit, offset) as Record<string, unknown>[];
|
|
607
607
|
}
|
|
608
608
|
|
|
609
609
|
return rows.map((row) => ({
|
|
@@ -669,7 +669,7 @@ export class TrajectoryDatabase {
|
|
|
669
669
|
getEvolutionStats(): { total: number; pending: number; inProgress: number; completed: number; failed: number } {
|
|
670
670
|
const rows = this.db.prepare(`
|
|
671
671
|
SELECT status, COUNT(*) as count FROM evolution_tasks GROUP BY status
|
|
672
|
-
`).all() as
|
|
672
|
+
`).all() as { status: string; count: number }[];
|
|
673
673
|
|
|
674
674
|
const stats = { total: 0, pending: 0, inProgress: 0, completed: 0, failed: 0 };
|
|
675
675
|
for (const row of rows) {
|
|
@@ -691,7 +691,7 @@ export class TrajectoryDatabase {
|
|
|
691
691
|
* @param options.dateFrom - Only return sessions updated after this date
|
|
692
692
|
* @param options.dateTo - Only return sessions updated before this date
|
|
693
693
|
*/
|
|
694
|
-
listRecentSessions(options: { limit?: number; dateFrom?: string; dateTo?: string } = {}):
|
|
694
|
+
listRecentSessions(options: { limit?: number; dateFrom?: string; dateTo?: string } = {}): { sessionId: string; startedAt: string; updatedAt: string }[] {
|
|
695
695
|
const conditions: string[] = [];
|
|
696
696
|
const values: unknown[] = [];
|
|
697
697
|
|
|
@@ -713,7 +713,7 @@ export class TrajectoryDatabase {
|
|
|
713
713
|
${whereClause}
|
|
714
714
|
ORDER BY updated_at DESC
|
|
715
715
|
LIMIT ?
|
|
716
|
-
`).all(...values, limit) as
|
|
716
|
+
`).all(...values, limit) as Record<string, unknown>[];
|
|
717
717
|
|
|
718
718
|
return rows.map((row) => ({
|
|
719
719
|
sessionId: String(row.session_id),
|
|
@@ -734,7 +734,7 @@ export class TrajectoryDatabase {
|
|
|
734
734
|
FROM assistant_turns
|
|
735
735
|
WHERE session_id = ?
|
|
736
736
|
ORDER BY id ASC
|
|
737
|
-
`).all(sessionId) as
|
|
737
|
+
`).all(sessionId) as Record<string, unknown>[];
|
|
738
738
|
|
|
739
739
|
return rows.map((row) => ({
|
|
740
740
|
id: Number(row.id),
|
|
@@ -755,7 +755,7 @@ export class TrajectoryDatabase {
|
|
|
755
755
|
* Returns: Analytics data aggregated from trajectory database.
|
|
756
756
|
* Not: Runtime truth or real-time queue state.
|
|
757
757
|
*/
|
|
758
|
-
listToolCallsForSession(sessionId: string):
|
|
758
|
+
listToolCallsForSession(sessionId: string): {
|
|
759
759
|
id: number;
|
|
760
760
|
toolName: string;
|
|
761
761
|
outcome: string;
|
|
@@ -767,14 +767,14 @@ export class TrajectoryDatabase {
|
|
|
767
767
|
gfiBefore: number | null;
|
|
768
768
|
gfiAfter: number | null;
|
|
769
769
|
createdAt: string;
|
|
770
|
-
}
|
|
770
|
+
}[] {
|
|
771
771
|
const rows = this.db.prepare(`
|
|
772
772
|
SELECT id, tool_name, outcome, params_json, duration_ms, exit_code, error_type, error_message,
|
|
773
773
|
gfi_before, gfi_after, created_at
|
|
774
774
|
FROM tool_calls
|
|
775
775
|
WHERE session_id = ?
|
|
776
776
|
ORDER BY id ASC
|
|
777
|
-
`).all(sessionId) as
|
|
777
|
+
`).all(sessionId) as Record<string, unknown>[];
|
|
778
778
|
|
|
779
779
|
return rows.map((row) => {
|
|
780
780
|
// Extract filePath from params_json if present
|
|
@@ -783,6 +783,7 @@ export class TrajectoryDatabase {
|
|
|
783
783
|
try {
|
|
784
784
|
const params = JSON.parse(row.params_json);
|
|
785
785
|
if (params && typeof params.filePath === 'string') {
|
|
786
|
+
// eslint-disable-next-line @typescript-eslint/prefer-destructuring -- Reason: filePath is a reassignable outer let variable - destructuring would lose the assignment semantics
|
|
786
787
|
filePath = params.filePath;
|
|
787
788
|
}
|
|
788
789
|
} catch {
|
|
@@ -811,7 +812,7 @@ export class TrajectoryDatabase {
|
|
|
811
812
|
* Returns: Analytics data aggregated from trajectory database.
|
|
812
813
|
* Not: Runtime truth or real-time queue state.
|
|
813
814
|
*/
|
|
814
|
-
listPainEventsForSession(sessionId: string):
|
|
815
|
+
listPainEventsForSession(sessionId: string): {
|
|
815
816
|
id: number;
|
|
816
817
|
source: string;
|
|
817
818
|
score: number;
|
|
@@ -820,13 +821,13 @@ export class TrajectoryDatabase {
|
|
|
820
821
|
origin: string | null;
|
|
821
822
|
confidence: number | null;
|
|
822
823
|
createdAt: string;
|
|
823
|
-
}
|
|
824
|
+
}[] {
|
|
824
825
|
const rows = this.db.prepare(`
|
|
825
826
|
SELECT id, source, score, reason, severity, origin, confidence, created_at
|
|
826
827
|
FROM pain_events
|
|
827
828
|
WHERE session_id = ?
|
|
828
829
|
ORDER BY created_at ASC
|
|
829
|
-
`).all(sessionId) as
|
|
830
|
+
`).all(sessionId) as Record<string, unknown>[];
|
|
830
831
|
|
|
831
832
|
return rows.map((row) => ({
|
|
832
833
|
id: Number(row.id),
|
|
@@ -844,19 +845,19 @@ export class TrajectoryDatabase {
|
|
|
844
845
|
* List user turns for a session.
|
|
845
846
|
* Returns sanitized/reduced fields for nocturnal use — NO raw text.
|
|
846
847
|
*/
|
|
847
|
-
listUserTurnsForSession(sessionId: string):
|
|
848
|
+
listUserTurnsForSession(sessionId: string): {
|
|
848
849
|
id: number;
|
|
849
850
|
turnIndex: number;
|
|
850
851
|
correctionDetected: boolean;
|
|
851
852
|
correctionCue: string | null;
|
|
852
853
|
createdAt: string;
|
|
853
|
-
}
|
|
854
|
+
}[] {
|
|
854
855
|
const rows = this.db.prepare(`
|
|
855
856
|
SELECT id, turn_index, correction_detected, correction_cue, created_at
|
|
856
857
|
FROM user_turns
|
|
857
858
|
WHERE session_id = ?
|
|
858
859
|
ORDER BY turn_index ASC
|
|
859
|
-
`).all(sessionId) as
|
|
860
|
+
`).all(sessionId) as Record<string, unknown>[];
|
|
860
861
|
|
|
861
862
|
return rows.map((row) => ({
|
|
862
863
|
id: Number(row.id),
|
|
@@ -881,7 +882,7 @@ export class TrajectoryDatabase {
|
|
|
881
882
|
FROM correction_samples
|
|
882
883
|
WHERE review_status = ?
|
|
883
884
|
ORDER BY created_at DESC
|
|
884
|
-
`).all(status) as
|
|
885
|
+
`).all(status) as Record<string, unknown>[];
|
|
885
886
|
|
|
886
887
|
return rows.map((row) => ({
|
|
887
888
|
sampleId: String(row.sample_id),
|
|
@@ -903,20 +904,20 @@ export class TrajectoryDatabase {
|
|
|
903
904
|
* List gate blocks for a session.
|
|
904
905
|
* Returns minimal fields for nocturnal use — no raw text.
|
|
905
906
|
*/
|
|
906
|
-
listGateBlocksForSession(sessionId: string):
|
|
907
|
+
listGateBlocksForSession(sessionId: string): {
|
|
907
908
|
id: number;
|
|
908
909
|
toolName: string;
|
|
909
910
|
filePath: string | null;
|
|
910
911
|
reason: string;
|
|
911
912
|
planStatus: string | null;
|
|
912
913
|
createdAt: string;
|
|
913
|
-
}
|
|
914
|
+
}[] {
|
|
914
915
|
const rows = this.db.prepare(`
|
|
915
916
|
SELECT id, tool_name, file_path, reason, plan_status, created_at
|
|
916
917
|
FROM gate_blocks
|
|
917
918
|
WHERE session_id = ?
|
|
918
919
|
ORDER BY id ASC
|
|
919
|
-
`).all(sessionId) as
|
|
920
|
+
`).all(sessionId) as Record<string, unknown>[];
|
|
920
921
|
|
|
921
922
|
return rows.map((row) => ({
|
|
922
923
|
id: Number(row.id),
|
|
@@ -992,7 +993,7 @@ export class TrajectoryDatabase {
|
|
|
992
993
|
JOIN user_turns ut ON ut.id = cs.user_correction_turn_id
|
|
993
994
|
WHERE (? = 0 OR cs.review_status = 'approved')
|
|
994
995
|
ORDER BY cs.created_at ASC
|
|
995
|
-
`).all(opts.approvedOnly ? 1 : 0) as
|
|
996
|
+
`).all(opts.approvedOnly ? 1 : 0) as Record<string, unknown>[];
|
|
996
997
|
|
|
997
998
|
const exportPath = path.join(this.exportDir, `corrections-${Date.now()}-${opts.mode}.jsonl`);
|
|
998
999
|
const lines = rows.map((row) => {
|
|
@@ -1271,7 +1272,7 @@ export class TrajectoryDatabase {
|
|
|
1271
1272
|
];
|
|
1272
1273
|
for (const col of v2Columns) {
|
|
1273
1274
|
const exists = this.db.prepare(`PRAGMA table_info(evolution_tasks)`).all()
|
|
1274
|
-
.some((row
|
|
1275
|
+
.some((row) => (row as Record<string, unknown>).name === col.name);
|
|
1275
1276
|
if (!exists) {
|
|
1276
1277
|
this.db.exec(`ALTER TABLE evolution_tasks ADD COLUMN ${col.name} ${col.type}`);
|
|
1277
1278
|
}
|
|
@@ -1323,6 +1324,7 @@ export class TrajectoryDatabase {
|
|
|
1323
1324
|
this.importLegacyEvolution();
|
|
1324
1325
|
}
|
|
1325
1326
|
|
|
1327
|
+
/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: _fromVersion reserved for future migration logic */
|
|
1326
1328
|
private migrateSchema(_fromVersion?: number): void {
|
|
1327
1329
|
this.db.exec(`
|
|
1328
1330
|
DROP VIEW IF EXISTS v_daily_metrics;
|
|
@@ -1509,7 +1511,7 @@ export class TrajectoryDatabase {
|
|
|
1509
1511
|
WHERE session_id = ? AND outcome = 'success'
|
|
1510
1512
|
ORDER BY id DESC
|
|
1511
1513
|
LIMIT 3
|
|
1512
|
-
`).all(sessionId) as
|
|
1514
|
+
`).all(sessionId) as Record<string, unknown>[];
|
|
1513
1515
|
if (successfulCalls.length === 0) return;
|
|
1514
1516
|
|
|
1515
1517
|
const sampleId = `sample_${crypto.createHash('md5').update(`${sessionId}:${correctionTurn.id}:${successfulCalls[0].id}`).digest('hex').slice(0, 12)}`;
|
|
@@ -1543,6 +1545,7 @@ export class TrajectoryDatabase {
|
|
|
1543
1545
|
});
|
|
1544
1546
|
}
|
|
1545
1547
|
|
|
1548
|
+
/* eslint-disable @typescript-eslint/max-params -- Reason: Audit record requires exportKind, mode, approvedOnly, filePath, and rowCount */
|
|
1546
1549
|
private recordExportAudit(
|
|
1547
1550
|
exportKind: string,
|
|
1548
1551
|
mode: CorrectionExportMode,
|
|
@@ -1595,7 +1598,7 @@ export class TrajectoryDatabase {
|
|
|
1595
1598
|
SELECT blob_ref FROM assistant_turns WHERE blob_ref IS NOT NULL
|
|
1596
1599
|
UNION
|
|
1597
1600
|
SELECT blob_ref FROM user_turns WHERE blob_ref IS NOT NULL
|
|
1598
|
-
`).all() as
|
|
1601
|
+
`).all() as { blob_ref?: string | null }[];
|
|
1599
1602
|
for (const row of rows) {
|
|
1600
1603
|
if (row.blob_ref) referenced.add(String(row.blob_ref));
|
|
1601
1604
|
}
|
|
@@ -1607,6 +1610,7 @@ export class TrajectoryDatabase {
|
|
|
1607
1610
|
for (const entry of fs.readdirSync(this.blobDir)) {
|
|
1608
1611
|
if (referenced.has(entry)) continue;
|
|
1609
1612
|
const fullPath = path.join(this.blobDir, entry);
|
|
1613
|
+
// eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in try, catch continues
|
|
1610
1614
|
let stat: fs.Stats;
|
|
1611
1615
|
try {
|
|
1612
1616
|
stat = fs.statSync(fullPath);
|
|
@@ -1629,7 +1633,7 @@ export class TrajectoryDatabase {
|
|
|
1629
1633
|
}
|
|
1630
1634
|
|
|
1631
1635
|
export class TrajectoryRegistry {
|
|
1632
|
-
private static instances = new Map<string, TrajectoryDatabase>();
|
|
1636
|
+
private static readonly instances = new Map<string, TrajectoryDatabase>();
|
|
1633
1637
|
|
|
1634
1638
|
static get(workspaceDir: string, opts: Omit<TrajectoryDatabaseOptions, 'workspaceDir'> = {}): TrajectoryDatabase {
|
|
1635
1639
|
const normalized = path.resolve(workspaceDir);
|
|
@@ -1656,7 +1660,9 @@ export class TrajectoryRegistry {
|
|
|
1656
1660
|
this.instances.clear();
|
|
1657
1661
|
}
|
|
1658
1662
|
|
|
1659
|
-
|
|
1663
|
+
/* eslint-disable no-unused-vars -- Reason: db parameter name in callback type signature */
|
|
1664
|
+
static use<T>(workspaceDir: string, fn: (_db: TrajectoryDatabase) => T, opts: Omit<TrajectoryDatabaseOptions, 'workspaceDir'> = {}): T {
|
|
1665
|
+
/* eslint-enable no-unused-vars */
|
|
1660
1666
|
const normalized = path.resolve(workspaceDir);
|
|
1661
1667
|
const existing = this.instances.get(normalized);
|
|
1662
1668
|
if (existing) {
|
|
@@ -1,21 +1,41 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PD_FILES } from './paths.js';
|
|
2
|
+
import { resolvePdPath } from './paths.js';
|
|
2
3
|
import { PathResolver } from './path-resolver.js';
|
|
3
4
|
import { ConfigService } from './config-service.js';
|
|
4
|
-
import { PainConfig } from './config.js';
|
|
5
|
-
import {
|
|
5
|
+
import type { PainConfig } from './config.js';
|
|
6
|
+
import type { EventLog } from './event-log.js';
|
|
7
|
+
import { EventLogService } from './event-log.js';
|
|
6
8
|
import { DictionaryService } from './dictionary-service.js';
|
|
7
|
-
import { PainDictionary } from './dictionary.js';
|
|
9
|
+
import type { PainDictionary } from './dictionary.js';
|
|
8
10
|
import { HygieneTracker } from './hygiene/tracker.js';
|
|
9
11
|
import { EvolutionReducerImpl } from './evolution-reducer.js';
|
|
10
|
-
import { TrajectoryDatabase,
|
|
12
|
+
import type { TrajectoryDatabase, TrajectoryDatabaseOptions } from './trajectory.js';
|
|
13
|
+
import { TrajectoryRegistry } from './trajectory.js';
|
|
14
|
+
import { PrincipleLifecycleService } from './principle-internalization/principle-lifecycle-service.js';
|
|
15
|
+
import {
|
|
16
|
+
getPrincipleSubtree,
|
|
17
|
+
updatePrinciple,
|
|
18
|
+
updatePrincipleValueMetrics,
|
|
19
|
+
type PrincipleSubtree,
|
|
20
|
+
} from './principle-tree-ledger.js';
|
|
21
|
+
import type { Principle, PrincipleValueMetrics } from '../types/principle-tree-schema.js';
|
|
22
|
+
import type { Principle as ActivePrinciple } from './evolution-types.js';
|
|
23
|
+
|
|
24
|
+
/* eslint-disable no-unused-vars -- Reason: interface method param names intentionally unused - implementations provide actual names */
|
|
25
|
+
interface PrincipleTreeLedgerAccessor {
|
|
26
|
+
getPrincipleSubtree(_principleId: string): PrincipleSubtree | undefined;
|
|
27
|
+
updatePrinciple(_principleId: string, updates: Partial<Principle>): Principle;
|
|
28
|
+
updatePrincipleValueMetrics(principleId: string, _metrics: PrincipleValueMetrics): PrincipleValueMetrics;
|
|
29
|
+
}
|
|
30
|
+
/* eslint-enable no-unused-vars */
|
|
11
31
|
|
|
12
32
|
/**
|
|
13
33
|
* WorkspaceContext - Centralized management of workspace-specific paths and services.
|
|
14
34
|
* Implements a cached singleton pattern per workspace directory.
|
|
15
35
|
*/
|
|
16
36
|
export class WorkspaceContext {
|
|
17
|
-
private static instances = new Map<string, WorkspaceContext>();
|
|
18
|
-
private static pathResolver = new PathResolver();
|
|
37
|
+
private static readonly instances = new Map<string, WorkspaceContext>();
|
|
38
|
+
private static readonly pathResolver = new PathResolver();
|
|
19
39
|
|
|
20
40
|
public readonly workspaceDir: string;
|
|
21
41
|
public readonly stateDir: string;
|
|
@@ -26,6 +46,8 @@ export class WorkspaceContext {
|
|
|
26
46
|
private _hygiene?: HygieneTracker;
|
|
27
47
|
private _evolutionReducer?: EvolutionReducerImpl;
|
|
28
48
|
private _trajectory?: TrajectoryDatabase;
|
|
49
|
+
private _principleTreeLedger?: PrincipleTreeLedgerAccessor;
|
|
50
|
+
private _principleLifecycle?: PrincipleLifecycleService;
|
|
29
51
|
|
|
30
52
|
private constructor(workspaceDir: string, stateDir: string) {
|
|
31
53
|
this.workspaceDir = workspaceDir;
|
|
@@ -78,7 +100,7 @@ export class WorkspaceContext {
|
|
|
78
100
|
*/
|
|
79
101
|
get evolutionReducer(): EvolutionReducerImpl {
|
|
80
102
|
if (!this._evolutionReducer) {
|
|
81
|
-
this._evolutionReducer = new EvolutionReducerImpl({ workspaceDir: this.workspaceDir });
|
|
103
|
+
this._evolutionReducer = new EvolutionReducerImpl({ workspaceDir: this.workspaceDir, stateDir: this.stateDir });
|
|
82
104
|
}
|
|
83
105
|
return this._evolutionReducer;
|
|
84
106
|
}
|
|
@@ -93,6 +115,47 @@ export class WorkspaceContext {
|
|
|
93
115
|
return this._trajectory;
|
|
94
116
|
}
|
|
95
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Locked ledger access for principle tree reads and metric writes in this workspace.
|
|
120
|
+
*/
|
|
121
|
+
get principleTreeLedger(): PrincipleTreeLedgerAccessor {
|
|
122
|
+
if (!this._principleTreeLedger) {
|
|
123
|
+
this._principleTreeLedger = {
|
|
124
|
+
getPrincipleSubtree: (principleId: string) => getPrincipleSubtree(this.stateDir, principleId),
|
|
125
|
+
updatePrinciple: (principleId: string, updates: Partial<Principle>) =>
|
|
126
|
+
updatePrinciple(this.stateDir, principleId, updates),
|
|
127
|
+
updatePrincipleValueMetrics: (principleId: string, metrics: PrincipleValueMetrics) =>
|
|
128
|
+
updatePrincipleValueMetrics(this.stateDir, principleId, metrics),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return this._principleTreeLedger;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Phase 15 lifecycle/read-model surface for metrics, assessments, and route recommendations.
|
|
136
|
+
*/
|
|
137
|
+
get principleLifecycle(): PrincipleLifecycleService {
|
|
138
|
+
if (!this._principleLifecycle) {
|
|
139
|
+
this._principleLifecycle = new PrincipleLifecycleService(this.workspaceDir, this.stateDir);
|
|
140
|
+
}
|
|
141
|
+
return this._principleLifecycle;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Retrieve active Principle -> Rule -> Implementation subtrees without bypassing reducer authority.
|
|
146
|
+
*/
|
|
147
|
+
getActivePrincipleSubtrees(): { principle: ActivePrinciple; subtree: PrincipleSubtree }[] {
|
|
148
|
+
return this.evolutionReducer
|
|
149
|
+
.getActivePrinciples()
|
|
150
|
+
.map((principle) => {
|
|
151
|
+
const subtree = this.principleTreeLedger.getPrincipleSubtree(principle.id);
|
|
152
|
+
return subtree ? { principle, subtree } : null;
|
|
153
|
+
})
|
|
154
|
+
.filter(
|
|
155
|
+
(entry): entry is { principle: ActivePrinciple; subtree: PrincipleSubtree } => entry !== null,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
96
159
|
private getTrajectoryOptions(): Omit<TrajectoryDatabaseOptions, 'workspaceDir'> {
|
|
97
160
|
const inlineThreshold = Number(this.config.get('trajectory.blob_inline_threshold_bytes'));
|
|
98
161
|
const busyTimeoutMs = Number(this.config.get('trajectory.busy_timeout_ms'));
|
|
@@ -110,12 +173,13 @@ export class WorkspaceContext {
|
|
|
110
173
|
* Uses PathResolver to handle path normalization and fallback logic.
|
|
111
174
|
* @throws Error if workspaceDir is missing and no fallback available.
|
|
112
175
|
*/
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: OpenClaw plugin framework hook context has dynamic shape - type not available
|
|
113
177
|
static fromHookContext(ctx: any): WorkspaceContext {
|
|
114
|
-
const logger = ctx
|
|
178
|
+
const {logger} = ctx;
|
|
115
179
|
const log = (msg: string) => logger?.info?.(msg);
|
|
116
180
|
const logWarn = (msg: string) => logger?.warn?.(msg);
|
|
117
181
|
|
|
118
|
-
let workspaceDir = ctx
|
|
182
|
+
let {workspaceDir} = ctx;
|
|
119
183
|
|
|
120
184
|
if (!workspaceDir) {
|
|
121
185
|
logWarn('[PD:WorkspaceContext] workspaceDir not provided in context, using PathResolver fallback');
|
|
@@ -132,7 +196,7 @@ export class WorkspaceContext {
|
|
|
132
196
|
const existing = this.instances.get(workspaceDir);
|
|
133
197
|
if (existing) return existing;
|
|
134
198
|
|
|
135
|
-
let stateDir = ctx
|
|
199
|
+
let {stateDir} = ctx;
|
|
136
200
|
if (!stateDir) {
|
|
137
201
|
stateDir = resolvePdPath(workspaceDir, 'STATE_DIR');
|
|
138
202
|
log(`[PD:WorkspaceContext] Computed stateDir: ${stateDir}`);
|
|
@@ -162,6 +226,8 @@ export class WorkspaceContext {
|
|
|
162
226
|
this._dictionary = undefined;
|
|
163
227
|
this._evolutionReducer = undefined;
|
|
164
228
|
this._trajectory = undefined;
|
|
229
|
+
this._principleTreeLedger = undefined;
|
|
230
|
+
this._principleLifecycle = undefined;
|
|
165
231
|
}
|
|
166
232
|
|
|
167
233
|
/**
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkspaceDir Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides runtime validation of workspaceDir to catch OpenClaw context bugs early.
|
|
5
|
+
* When a hook receives an invalid workspaceDir, we warn immediately rather than
|
|
6
|
+
* silently writing to the wrong directory.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/* eslint-disable no-unused-vars -- Reason: type definitions require param names that implementations may not use */
|
|
10
|
+
|
|
11
|
+
import * as os from 'os';
|
|
12
|
+
import type { PluginLogger } from '../openclaw-sdk.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if a path looks like a home directory (not a real workspace).
|
|
16
|
+
* Returns the reason if suspicious, or null if it looks valid.
|
|
17
|
+
*/
|
|
18
|
+
export function validateWorkspaceDir(dir: string | undefined): string | null {
|
|
19
|
+
if (!dir) {
|
|
20
|
+
return 'workspaceDir is undefined/null';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const homeDir = os.homedir();
|
|
24
|
+
|
|
25
|
+
// Home directory itself is not a valid workspace
|
|
26
|
+
if (dir === homeDir) {
|
|
27
|
+
return `workspaceDir equals home directory (${homeDir}), likely missing context field`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Root directory is definitely not a workspace
|
|
31
|
+
if (dir === '/' || dir === '') {
|
|
32
|
+
return `workspaceDir is root or empty: "${dir}"`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check if it looks like a resolved '.' that went wrong
|
|
36
|
+
// Common bad patterns:
|
|
37
|
+
const badPatterns = [
|
|
38
|
+
// Directly under home without a workspace subdirectory
|
|
39
|
+
{ pattern: new RegExp(`^${homeDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`), desc: 'is home directory itself' },
|
|
40
|
+
{ pattern: new RegExp(`^${homeDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/$`), desc: 'is home directory with trailing slash' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
for (const { pattern, desc } of badPatterns) {
|
|
44
|
+
if (pattern.test(dir)) {
|
|
45
|
+
return `workspaceDir ${desc}: "${dir}"`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null; // Looks valid
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Try to resolve workspaceDir from agentId.
|
|
54
|
+
* Returns the resolved path if successful and valid, or null.
|
|
55
|
+
*/
|
|
56
|
+
function tryResolveFromAgentId(
|
|
57
|
+
agentId: string,
|
|
58
|
+
api: {
|
|
59
|
+
runtime: { agent: { resolveAgentWorkspaceDir: (config: unknown, agentId: string) => string } };
|
|
60
|
+
config: unknown;
|
|
61
|
+
},
|
|
62
|
+
onWarning: (msg: string) => void,
|
|
63
|
+
): string | null {
|
|
64
|
+
try {
|
|
65
|
+
const resolved = api.runtime.agent.resolveAgentWorkspaceDir(api.config, agentId);
|
|
66
|
+
const issue = validateWorkspaceDir(resolved);
|
|
67
|
+
if (issue) {
|
|
68
|
+
onWarning(`agentId resolution returned invalid: "${resolved}" (${issue})`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return resolved;
|
|
72
|
+
} catch (err) {
|
|
73
|
+
onWarning(`failed to resolve from agentId: ${String(err)}`);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Validate a fallback workspaceDir and warn if invalid.
|
|
80
|
+
* Returns the path regardless (it's the last resort).
|
|
81
|
+
*/
|
|
82
|
+
function validateFallback(path: string, onWarning: (msg: string) => void): string {
|
|
83
|
+
const issue = validateWorkspaceDir(path);
|
|
84
|
+
if (issue) {
|
|
85
|
+
onWarning(`FINAL FALLBACK "${path}" is also invalid: ${issue}. Events will be written to wrong location!`);
|
|
86
|
+
}
|
|
87
|
+
return path;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Resolve workspaceDir with validation and warning.
|
|
92
|
+
*
|
|
93
|
+
* Usage:
|
|
94
|
+
* const workspaceDir = resolveValidWorkspaceDir(ctx, api, { source: 'after_tool_call' });
|
|
95
|
+
*
|
|
96
|
+
* Fallback chain:
|
|
97
|
+
* 1. ctx.workspaceDir (validated)
|
|
98
|
+
* 2. api.runtime.agent.resolveAgentWorkspaceDir(config, ctx.agentId)
|
|
99
|
+
* 3. api.resolvePath('.') (last resort, warns loudly)
|
|
100
|
+
*/
|
|
101
|
+
export function resolveValidWorkspaceDir(
|
|
102
|
+
ctx: { workspaceDir?: string; agentId?: string },
|
|
103
|
+
api: {
|
|
104
|
+
runtime: { agent: { resolveAgentWorkspaceDir: (config: unknown, agentId: string) => string } };
|
|
105
|
+
config: unknown;
|
|
106
|
+
resolvePath: (input: string) => string;
|
|
107
|
+
logger: PluginLogger;
|
|
108
|
+
},
|
|
109
|
+
options?: { source?: string; onWarning?: (msg: string) => void },
|
|
110
|
+
): string {
|
|
111
|
+
const source = options?.source || 'unknown';
|
|
112
|
+
const onWarning = options?.onWarning || ((msg: string) => api.logger.warn(`[PD:workspaceDir] ${msg}`));
|
|
113
|
+
|
|
114
|
+
// 1. Try ctx.workspaceDir
|
|
115
|
+
if (ctx.workspaceDir) {
|
|
116
|
+
const issue = validateWorkspaceDir(ctx.workspaceDir);
|
|
117
|
+
if (issue) {
|
|
118
|
+
onWarning(`${source}: ctx.workspaceDir="${ctx.workspaceDir}" is invalid: ${issue}`);
|
|
119
|
+
} else {
|
|
120
|
+
return ctx.workspaceDir;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 2. Try agentId resolution
|
|
125
|
+
if (ctx.agentId) {
|
|
126
|
+
const fromAgent = tryResolveFromAgentId(ctx.agentId, api, onWarning);
|
|
127
|
+
if (fromAgent) return fromAgent;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 3. Final fallback
|
|
131
|
+
return validateFallback(api.resolvePath('.'), onWarning);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Log workspaceDir resolution for debugging.
|
|
136
|
+
* Call this once during plugin startup to verify hook contexts.
|
|
137
|
+
*/
|
|
138
|
+
export function logWorkspaceDirHealth(ctx: { workspaceDir?: string; agentId?: string }, source: string, api: {
|
|
139
|
+
runtime: { agent: { resolveAgentWorkspaceDir: (config: unknown, agentId: string) => string } };
|
|
140
|
+
config: unknown;
|
|
141
|
+
resolvePath: (input: string) => string;
|
|
142
|
+
logger: PluginLogger;
|
|
143
|
+
}): void {
|
|
144
|
+
const resolved = resolveValidWorkspaceDir(ctx, api, { source });
|
|
145
|
+
const issue = validateWorkspaceDir(resolved);
|
|
146
|
+
|
|
147
|
+
if (issue) {
|
|
148
|
+
api.logger.error(`[PD:health] ${source}: workspaceDir="${resolved}" - ${issue}`);
|
|
149
|
+
} else {
|
|
150
|
+
api.logger.info(`[PD:health] ${source}: workspaceDir="${resolved}" ✓`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# src/hooks/ — OpenClaw Lifecycle Hooks
|
|
2
|
+
|
|
3
|
+
**8 TypeScript files.** Intercepts agent behavior at key moments: prompt building, tool calls, session lifecycle.
|
|
4
|
+
|
|
5
|
+
## WHERE TO LOOK
|
|
6
|
+
|
|
7
|
+
| Hook Event | File | What It Does |
|
|
8
|
+
|------------|------|--------------|
|
|
9
|
+
| `before_prompt_build` | `prompt.ts` | Multi-layer context injection: identity, trust, evolution, principles, thinking OS |
|
|
10
|
+
| `before_tool_call` | `gate.ts` | Security gate: trust stage checks, risk path blocking, bash security (Cyrillic de-obfuscation, command tokenization) |
|
|
11
|
+
| `after_tool_call` | `pain.ts` | Pain detection: failure → pain score → `.pain_flag` → evolution queue |
|
|
12
|
+
| `before_compaction` | `lifecycle.ts` | Checkpoints state before context loss |
|
|
13
|
+
| `after_compaction` | `lifecycle.ts` | State recovery |
|
|
14
|
+
| `before_reset` / `session_*` | `lifecycle.ts` | Session lifecycle management |
|
|
15
|
+
| `llm_output` | `llm.ts` | Analyzes LLM responses for pain signals |
|
|
16
|
+
| `subagent_*` | `subagent.ts` | Ensures sub-agents inherit mental models |
|
|
17
|
+
| `before_message_write` | `message-sanitize.ts` | Strips sensitive data from messages |
|
|
18
|
+
| — | `trajectory-collector.ts` | Collects tool call trajectories for SQLite analytics |
|
|
19
|
+
|
|
20
|
+
## CONVENTIONS
|
|
21
|
+
|
|
22
|
+
- All hooks receive `HookContext` → use `WorkspaceContext.fromHookContext(ctx)` for services
|
|
23
|
+
- Gate hook is the most security-critical — handles trust stages 1-4, risk paths, bash security
|
|
24
|
+
- Pain hook feeds into EvolutionReducer event sourcing
|
|
25
|
+
- Prompt hook is the main injection point for agent context
|
|
26
|
+
|
|
27
|
+
## ANTI-PATTERNS
|
|
28
|
+
|
|
29
|
+
- ❌ Gate hook must fail-closed (invalid regex → block, not allow)
|
|
30
|
+
- ❌ Never modify hook return values after they're computed
|
|
31
|
+
- ❌ Pain hook must not throw — errors are logged, not propagated
|