principles-disciple 1.16.0 → 1.18.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/README.md +13 -5
- package/openclaw.plugin.json +4 -4
- package/package.json +1 -1
- package/src/commands/archive-impl.ts +3 -3
- package/src/commands/capabilities.ts +1 -1
- package/src/commands/context.ts +3 -3
- package/src/commands/disable-impl.ts +1 -1
- package/src/commands/evolution-status.ts +2 -2
- package/src/commands/focus.ts +2 -2
- package/src/commands/nocturnal-train.ts +6 -6
- package/src/commands/pain.ts +4 -4
- package/src/commands/pd-reflect.ts +87 -0
- package/src/commands/rollback-impl.ts +4 -4
- package/src/commands/rollback.ts +2 -2
- package/src/commands/samples.ts +2 -2
- package/src/commands/workflow-debug.ts +1 -1
- package/src/config/errors.ts +1 -1
- package/src/core/adaptive-thresholds.ts +1 -1
- package/src/core/code-implementation-storage.ts +2 -2
- package/src/core/config.ts +1 -1
- package/src/core/diagnostician-task-store.ts +2 -2
- package/src/core/empathy-keyword-matcher.ts +3 -3
- package/src/core/event-log.ts +5 -5
- package/src/core/evolution-engine.ts +4 -4
- package/src/core/evolution-logger.ts +1 -1
- package/src/core/evolution-reducer.ts +3 -3
- package/src/core/evolution-types.ts +5 -5
- package/src/core/external-training-contract.ts +1 -1
- package/src/core/focus-history.ts +14 -14
- package/src/core/hygiene/tracker.ts +1 -1
- package/src/core/init.ts +2 -2
- package/src/core/model-deployment-registry.ts +2 -2
- package/src/core/model-training-registry.ts +2 -2
- package/src/core/nocturnal-arbiter.ts +1 -1
- package/src/core/nocturnal-artificer.ts +2 -2
- package/src/core/nocturnal-candidate-scoring.ts +2 -2
- package/src/core/nocturnal-compliance.ts +4 -3
- package/src/core/nocturnal-dataset.ts +3 -3
- package/src/core/nocturnal-export.ts +4 -4
- package/src/core/nocturnal-rule-implementation-validator.ts +1 -1
- package/src/core/nocturnal-snapshot-contract.ts +112 -0
- package/src/core/nocturnal-trajectory-extractor.ts +7 -5
- package/src/core/nocturnal-trinity.ts +480 -158
- package/src/core/pain-context-extractor.ts +3 -3
- package/src/core/pain.ts +124 -11
- package/src/core/path-resolver.ts +4 -4
- package/src/core/pd-task-reconciler.ts +10 -10
- package/src/core/pd-task-service.ts +1 -1
- package/src/core/pd-task-store.ts +1 -1
- package/src/core/principle-internalization/deprecated-readiness.ts +1 -1
- package/src/core/principle-training-state.ts +2 -2
- package/src/core/principle-tree-ledger.ts +7 -7
- package/src/core/promotion-gate.ts +9 -9
- package/src/core/replay-engine.ts +12 -12
- package/src/core/risk-calculator.ts +1 -1
- package/src/core/rule-host-types.ts +2 -2
- package/src/core/rule-host.ts +5 -5
- package/src/core/schema/db-types.ts +1 -1
- package/src/core/schema/schema-definitions.ts +1 -1
- package/src/core/session-tracker.ts +96 -4
- package/src/core/shadow-observation-registry.ts +3 -3
- package/src/core/system-logger.ts +2 -2
- package/src/core/thinking-os-parser.ts +1 -1
- package/src/core/training-program.ts +2 -2
- package/src/core/trajectory.ts +8 -8
- package/src/core/workspace-context.ts +2 -2
- package/src/core/workspace-dir-service.ts +85 -0
- package/src/core/workspace-dir-validation.ts +30 -107
- package/src/hooks/bash-risk.ts +3 -3
- package/src/hooks/edit-verification.ts +4 -4
- package/src/hooks/gate-block-helper.ts +4 -4
- package/src/hooks/gate.ts +10 -10
- package/src/hooks/gfi-gate.ts +7 -7
- package/src/hooks/lifecycle.ts +2 -2
- package/src/hooks/llm.ts +1 -1
- package/src/hooks/pain.ts +25 -5
- package/src/hooks/progressive-trust-gate.ts +7 -7
- package/src/hooks/prompt.ts +24 -5
- package/src/hooks/subagent.ts +2 -2
- package/src/hooks/thinking-checkpoint.ts +2 -2
- package/src/hooks/trajectory-collector.ts +1 -1
- package/src/http/principles-console-route.ts +14 -6
- package/src/i18n/commands.ts +4 -0
- package/src/index.ts +181 -185
- package/src/service/central-health-service.ts +1 -1
- package/src/service/central-overview-service.ts +3 -3
- package/src/service/evolution-query-service.ts +1 -1
- package/src/service/evolution-worker.ts +221 -109
- package/src/service/health-query-service.ts +27 -17
- package/src/service/monitoring-query-service.ts +3 -3
- package/src/service/nocturnal-runtime.ts +4 -4
- package/src/service/nocturnal-service.ts +40 -23
- package/src/service/nocturnal-target-selector.ts +11 -4
- package/src/service/runtime-summary-service.ts +1 -1
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +1 -1
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +3 -3
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +16 -13
- package/src/service/subagent-workflow/runtime-direct-driver.ts +10 -6
- package/src/service/subagent-workflow/types.ts +4 -4
- package/src/service/subagent-workflow/workflow-manager-base.ts +5 -5
- package/src/service/subagent-workflow/workflow-store.ts +2 -2
- package/src/tools/critique-prompt.ts +2 -3
- package/src/tools/deep-reflect.ts +17 -16
- package/src/tools/model-index.ts +1 -1
- package/src/utils/file-lock.ts +1 -1
- package/src/utils/io.ts +7 -2
- package/src/utils/nlp.ts +1 -1
- package/src/utils/plugin-logger.ts +2 -2
- package/src/utils/retry.ts +3 -2
- package/src/utils/subagent-probe.ts +20 -33
- package/templates/langs/en/skills/pd-pain-signal/SKILL.md +8 -7
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +111 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +1 -1
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +1 -1
- package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +8 -7
- package/templates/pain_settings.json +1 -1
- package/tests/build-artifacts.test.ts +4 -58
- package/tests/commands/pd-reflect.test.ts +49 -0
- package/tests/core/nocturnal-snapshot-contract.test.ts +70 -0
- package/tests/core/pain-auto-repair.test.ts +96 -0
- package/tests/core/pain-integration.test.ts +483 -0
- package/tests/core/pain.test.ts +5 -4
- package/tests/core/workspace-dir-service.test.ts +68 -0
- package/tests/core/workspace-dir-validation.test.ts +56 -192
- package/tests/hooks/pain.test.ts +20 -0
- package/tests/http/principles-console-route.test.ts +42 -20
- package/tests/integration/empathy-workflow-integration.test.ts +1 -2
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +9 -17
- package/tests/service/empathy-observer-workflow-manager.test.ts +1 -2
- package/tests/service/evolution-worker.nocturnal.test.ts +118 -109
- package/tests/service/nocturnal-runtime-hardening.test.ts +33 -0
- package/tests/utils/subagent-probe.test.ts +32 -0
|
@@ -23,10 +23,16 @@
|
|
|
23
23
|
* RUNTIME ADAPTER:
|
|
24
24
|
* - useStubs=true: uses synchronous stub implementations (no external calls)
|
|
25
25
|
* - useStubs=false: requires a TrinityRuntimeAdapter for real subagent execution
|
|
26
|
-
* - Adapter uses
|
|
26
|
+
* - Adapter uses api.runtime.agent.runEmbeddedPiAgent() which works in background contexts
|
|
27
|
+
* (unlike api.runtime.subagent.* which requires gateway request scope)
|
|
28
|
+
* - IMPORTANT: provider and model must be passed explicitly — runEmbeddedPiAgent does NOT
|
|
29
|
+
* read config.agents.defaults.model and falls back to openai/gpt-5.4 if not specified
|
|
27
30
|
*/
|
|
28
31
|
|
|
29
32
|
import { randomUUID } from 'crypto';
|
|
33
|
+
import * as fs from 'fs';
|
|
34
|
+
import * as os from 'os';
|
|
35
|
+
import * as path from 'path';
|
|
30
36
|
import type { NocturnalSessionSnapshot } from './nocturnal-trajectory-extractor.js';
|
|
31
37
|
import { computeThinkingModelDelta } from './nocturnal-trajectory-extractor.js';
|
|
32
38
|
import type { TrinityArtificerContext } from './nocturnal-artificer.js';
|
|
@@ -42,12 +48,18 @@ import {
|
|
|
42
48
|
type ThresholdValues,
|
|
43
49
|
} from './adaptive-thresholds.js';
|
|
44
50
|
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Configurable Model Fallback (avoid hardcoded strings deep in adapters)
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
const FALLBACK_PROVIDER = process.env.OPENCLAW_DEFAULT_PROVIDER || 'minimax-portal';
|
|
56
|
+
const FALLBACK_MODEL = process.env.OPENCLAW_DEFAULT_MODEL || 'MiniMax-M2.7';
|
|
57
|
+
|
|
45
58
|
// ---------------------------------------------------------------------------
|
|
46
59
|
// Embedded Role Prompts
|
|
47
60
|
// ---------------------------------------------------------------------------
|
|
48
|
-
// These prompts are embedded at build time
|
|
49
|
-
//
|
|
50
|
-
// did not copy the agents/ directory into the bundle.
|
|
61
|
+
// These prompts are embedded at build time. The agents/ directory was removed
|
|
62
|
+
// to eliminate fragile runtime file dependencies on the file system.
|
|
51
63
|
|
|
52
64
|
const NOCTURNAL_DREAMER_PROMPT = `# Nocturnal Dreamer — Candidate Generation
|
|
53
65
|
|
|
@@ -107,6 +119,13 @@ You MUST respond with ONLY a valid JSON object. No markdown, no explanation, no
|
|
|
107
119
|
- Provide a principle-grounded rationale (explicitly references the principle)
|
|
108
120
|
- Include a confidence score (0.0-1.0, higher = more confident)
|
|
109
121
|
|
|
122
|
+
### betterDecision FORMAT — Must be executable:
|
|
123
|
+
- MUST start with a concrete action verb: read, check, verify, edit, write, create, delete, search, grep, find, list, review, examine, inspect, test, run, execute, analyze, diagnose, debug
|
|
124
|
+
- MUST reference a specific, concrete target (file, command, config, etc.)
|
|
125
|
+
- MUST describe a bounded, executable action — not a vague principle
|
|
126
|
+
- Examples: "Read the file before editing to verify current content", "Check user permissions before executing privileged commands"
|
|
127
|
+
- Anti-examples: "Per T-01, pause all tasks..." (starts with "Per"), "Be more careful" (vague verb "be")
|
|
128
|
+
|
|
110
129
|
### Candidates should DIFFER from each other:
|
|
111
130
|
- Different candidates should represent genuinely different approaches
|
|
112
131
|
- Do not generate candidates with identical betterDecisions
|
|
@@ -178,13 +197,23 @@ You MUST respond with ONLY a valid JSON object. No markdown, no explanation, no
|
|
|
178
197
|
## Evaluation Criteria
|
|
179
198
|
|
|
180
199
|
### Score Components (0-1 scale each):
|
|
181
|
-
1. **Principle Alignment** (weight: 0.
|
|
182
|
-
2. **Specificity** (weight: 0.
|
|
183
|
-
3. **Actionability** (weight: 0.
|
|
200
|
+
1. **Principle Alignment** (weight: 0.35) — Does the betterDecision properly reflect the target principle?
|
|
201
|
+
2. **Specificity** (weight: 0.25) — Is badDecision specific? Is betterDecision actionable?
|
|
202
|
+
3. **Actionability** (weight: 0.25) — Does betterDecision describe a specific next step?
|
|
203
|
+
4. **Executability** (weight: 0.15) — Does betterDecision start with a bounded verb (read, check, verify, edit, write, etc.) and reference a concrete target?
|
|
204
|
+
|
|
205
|
+
### Executability Check:
|
|
206
|
+
A betterDecision is executable if it:
|
|
207
|
+
- STARTS with a concrete action verb: read, check, verify, edit, write, create, delete, search, grep, find, list, review, examine, inspect, test, run, execute, analyze, diagnose, debug
|
|
208
|
+
- References a specific, concrete target (file, command, config, etc.)
|
|
209
|
+
- Describes a bounded, executable action — not a vague principle
|
|
210
|
+
- Examples that PASS: "Read the file before editing", "Check user permissions before executing"
|
|
211
|
+
- Examples that FAIL: "Per T-01, pause all tasks..." (starts with "Per"), "Be more careful" (vague)
|
|
184
212
|
|
|
185
213
|
### Ranking Rules:
|
|
186
214
|
- Candidates are ranked by score (highest = rank 1)
|
|
187
|
-
- Ties broken by: higher principle alignment, then lower candidateIndex
|
|
215
|
+
- Ties broken by: higher executability, then higher principle alignment, then lower candidateIndex
|
|
216
|
+
- If a candidate's betterDecision is NOT executable, penalize its score by 0.2
|
|
188
217
|
|
|
189
218
|
## Validation
|
|
190
219
|
|
|
@@ -276,7 +305,7 @@ If you cannot synthesize an artifact:
|
|
|
276
305
|
* Interface for Trinity stage invocation.
|
|
277
306
|
* Implementations can use real subagent runtimes or stubs.
|
|
278
307
|
*/
|
|
279
|
-
|
|
308
|
+
|
|
280
309
|
export interface TrinityRuntimeAdapter {
|
|
281
310
|
/**
|
|
282
311
|
* Invoke the Dreamer stage.
|
|
@@ -295,11 +324,13 @@ export interface TrinityRuntimeAdapter {
|
|
|
295
324
|
* Invoke the Philosopher stage.
|
|
296
325
|
* @param dreamerOutput Dreamer's output
|
|
297
326
|
* @param principleId Target principle ID
|
|
327
|
+
* @param snapshot Session snapshot (for violation evidence)
|
|
298
328
|
* @returns Philosopher output JSON
|
|
299
329
|
*/
|
|
300
330
|
invokePhilosopher(
|
|
301
331
|
_dreamerOutput: DreamerOutput,
|
|
302
|
-
_principleId: string
|
|
332
|
+
_principleId: string,
|
|
333
|
+
_snapshot: NocturnalSessionSnapshot
|
|
303
334
|
): Promise<PhilosopherOutput>;
|
|
304
335
|
|
|
305
336
|
/**
|
|
@@ -327,7 +358,7 @@ export interface TrinityRuntimeAdapter {
|
|
|
327
358
|
*/
|
|
328
359
|
close?(): Promise<void>;
|
|
329
360
|
}
|
|
330
|
-
|
|
361
|
+
|
|
331
362
|
|
|
332
363
|
// ---------------------------------------------------------------------------
|
|
333
364
|
// OpenClaw Runtime Adapter
|
|
@@ -335,40 +366,40 @@ export interface TrinityRuntimeAdapter {
|
|
|
335
366
|
|
|
336
367
|
/**
|
|
337
368
|
* OpenClaw-backed Trinity runtime adapter.
|
|
338
|
-
* Uses
|
|
339
|
-
*
|
|
369
|
+
* Uses api.runtime.agent.runEmbeddedPiAgent() which works in background contexts
|
|
370
|
+
* (unlike api.runtime.subagent.* which requires gateway request scope).
|
|
340
371
|
*/
|
|
341
372
|
export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
|
|
342
|
-
|
|
373
|
+
|
|
343
374
|
private readonly api: {
|
|
344
375
|
runtime: {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
376
|
+
agent: {
|
|
377
|
+
runEmbeddedPiAgent: (_opts: {
|
|
378
|
+
sessionId: string;
|
|
379
|
+
sessionFile: string;
|
|
380
|
+
prompt: string;
|
|
349
381
|
extraSystemPrompt?: string;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
getSessionMessages: (_opts: {
|
|
357
|
-
sessionKey: string;
|
|
358
|
-
limit: number;
|
|
382
|
+
config?: unknown;
|
|
383
|
+
provider?: string;
|
|
384
|
+
model?: string;
|
|
385
|
+
timeoutMs: number;
|
|
386
|
+
runId: string;
|
|
387
|
+
disableTools?: boolean;
|
|
359
388
|
}) => Promise<{
|
|
360
|
-
|
|
389
|
+
payloads?: { isError?: boolean; text?: string }[];
|
|
361
390
|
}>;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}) => Promise<void>;
|
|
391
|
+
};
|
|
392
|
+
config?: {
|
|
393
|
+
loadConfig?: () => unknown;
|
|
366
394
|
};
|
|
367
395
|
};
|
|
396
|
+
config?: unknown;
|
|
397
|
+
logger?: { info: (msg: string) => void; warn: (msg: string) => void; error: (msg: string) => void };
|
|
368
398
|
};
|
|
369
|
-
|
|
399
|
+
|
|
370
400
|
|
|
371
401
|
private readonly stageTimeoutMs: number;
|
|
402
|
+
private readonly tempDir: string;
|
|
372
403
|
|
|
373
404
|
constructor(
|
|
374
405
|
api: OpenClawTrinityRuntimeAdapter['api'],
|
|
@@ -376,6 +407,106 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
|
|
|
376
407
|
) {
|
|
377
408
|
this.api = api;
|
|
378
409
|
this.stageTimeoutMs = stageTimeoutMs;
|
|
410
|
+
// Cross-platform temp directory for session files
|
|
411
|
+
this.tempDir = path.join(os.tmpdir(), `pd-trinity-${process.pid}`);
|
|
412
|
+
// Clean up any stale temp files from previous crashed runs
|
|
413
|
+
this.cleanupStaleTempDirs();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Clean up temp directories from previous crashed runs.
|
|
418
|
+
* Matches pattern pd-trinity-* in the OS temp directory.
|
|
419
|
+
*/
|
|
420
|
+
private cleanupStaleTempDirs(): void {
|
|
421
|
+
try {
|
|
422
|
+
const osTempDir = os.tmpdir();
|
|
423
|
+
if (!fs.existsSync(osTempDir)) return;
|
|
424
|
+
const entries = fs.readdirSync(osTempDir);
|
|
425
|
+
for (const entry of entries) {
|
|
426
|
+
if (entry.startsWith('pd-trinity-') && entry !== path.basename(this.tempDir)) {
|
|
427
|
+
const fullPath = path.join(osTempDir, entry);
|
|
428
|
+
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
} catch {
|
|
432
|
+
// Non-fatal: stale temp files will be cleaned up eventually
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Load the full OpenClaw config (including models.providers).
|
|
438
|
+
*
|
|
439
|
+
* Why: `this.api.config` is the plugin config, not the full OpenClaw config.
|
|
440
|
+
* It does NOT contain `models.providers`, which is needed to resolve provider
|
|
441
|
+
* model definitions. `api.runtime.config.loadConfig()` returns the full config.
|
|
442
|
+
*
|
|
443
|
+
* Fallback: If loadConfig() is unavailable, we return the plugin config.
|
|
444
|
+
* The caller (resolveModel) handles this with a minimax-portal fallback.
|
|
445
|
+
*/
|
|
446
|
+
private loadFullConfig(): Record<string, unknown> | undefined {
|
|
447
|
+
// Try runtime.config.loadConfig() first (available in native plugin context)
|
|
448
|
+
const loadConfig = this.api.runtime?.config?.loadConfig;
|
|
449
|
+
if (loadConfig && typeof loadConfig === 'function') {
|
|
450
|
+
try {
|
|
451
|
+
return loadConfig() as Record<string, unknown> | undefined;
|
|
452
|
+
} catch (err) {
|
|
453
|
+
this.api.logger?.warn?.(`[Trinity] loadConfig() failed, falling back to plugin config: ${err instanceof Error ? err.message : String(err)}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// Fallback: plugin config (limited — won't have models.providers)
|
|
457
|
+
// resolveModel() handles this with a minimax-portal/MiniMax-M2.7 fallback
|
|
458
|
+
return this.api.config as Record<string, unknown> | undefined;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Resolve the provider and model from the OpenClaw config.
|
|
463
|
+
* runEmbeddedPiAgent does NOT read config.agents.defaults.model —
|
|
464
|
+
* it requires explicit params.provider and params.model.
|
|
465
|
+
*/
|
|
466
|
+
private resolveModel(): { provider: string; model: string } {
|
|
467
|
+
const config = this.loadFullConfig();
|
|
468
|
+
const agents = config?.agents as Record<string, unknown> | undefined;
|
|
469
|
+
const defaults = agents?.defaults as Record<string, unknown> | undefined;
|
|
470
|
+
const modelConfig = defaults?.model;
|
|
471
|
+
|
|
472
|
+
if (typeof modelConfig === 'string' && modelConfig.includes('/')) {
|
|
473
|
+
const parts = modelConfig.split('/');
|
|
474
|
+
return { provider: parts[0], model: parts.slice(1).join('/') };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (modelConfig && typeof modelConfig === 'object') {
|
|
478
|
+
const mc = modelConfig as Record<string, unknown>;
|
|
479
|
+
const primary = mc.primary as string | undefined;
|
|
480
|
+
if (primary && primary.includes('/')) {
|
|
481
|
+
const parts = primary.split('/');
|
|
482
|
+
return { provider: parts[0], model: parts.slice(1).join('/') };
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Last resort fallback — read from env vars to avoid hardcoded strings
|
|
487
|
+
this.api.logger?.warn?.(`[Trinity] Could not resolve model from config, using fallback: ${FALLBACK_PROVIDER}/${FALLBACK_MODEL}`);
|
|
488
|
+
return { provider: FALLBACK_PROVIDER, model: FALLBACK_MODEL };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Create a valid JSONL session file for runEmbeddedPiAgent.
|
|
493
|
+
*/
|
|
494
|
+
private createSessionFile(stage: string): string {
|
|
495
|
+
if (!fs.existsSync(this.tempDir)) {
|
|
496
|
+
fs.mkdirSync(this.tempDir, { recursive: true });
|
|
497
|
+
}
|
|
498
|
+
return path.join(this.tempDir, `${stage}-${randomUUID()}.jsonl`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Extract text from runEmbeddedPiAgent result.
|
|
503
|
+
*/
|
|
504
|
+
private extractPayloadText(result: { payloads?: { isError?: boolean; text?: string }[] }): string {
|
|
505
|
+
return (result.payloads ?? [])
|
|
506
|
+
.filter(p => !p.isError)
|
|
507
|
+
.map(p => p.text?.trim() ?? '')
|
|
508
|
+
.filter(Boolean)
|
|
509
|
+
.join('\n');
|
|
379
510
|
}
|
|
380
511
|
|
|
381
512
|
async invokeDreamer(
|
|
@@ -383,200 +514,333 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
|
|
|
383
514
|
principleId: string,
|
|
384
515
|
maxCandidates: number
|
|
385
516
|
): Promise<DreamerOutput> {
|
|
386
|
-
const
|
|
387
|
-
const
|
|
388
|
-
|
|
517
|
+
const runId = `dreamer-${randomUUID()}`;
|
|
518
|
+
const sessionFile = this.createSessionFile('dreamer');
|
|
389
519
|
const prompt = this.buildDreamerPrompt(snapshot, principleId, maxCandidates);
|
|
520
|
+
const model = this.resolveModel();
|
|
390
521
|
|
|
391
|
-
|
|
392
|
-
const { runId } = await this.api.runtime.subagent.run({
|
|
393
|
-
sessionKey,
|
|
394
|
-
message: prompt,
|
|
395
|
-
extraSystemPrompt: systemPrompt,
|
|
396
|
-
deliver: false,
|
|
397
|
-
});
|
|
522
|
+
this.api.logger?.info(`[Trinity:Dreamer] Using model: ${model.provider}/${model.model}`);
|
|
398
523
|
|
|
399
|
-
|
|
400
|
-
|
|
524
|
+
try {
|
|
525
|
+
const result = await this.api.runtime.agent.runEmbeddedPiAgent({
|
|
526
|
+
sessionId: runId,
|
|
527
|
+
sessionFile,
|
|
528
|
+
prompt,
|
|
529
|
+
extraSystemPrompt: NOCTURNAL_DREAMER_PROMPT,
|
|
530
|
+
config: this.loadFullConfig(),
|
|
531
|
+
provider: model.provider,
|
|
532
|
+
model: model.model,
|
|
401
533
|
timeoutMs: this.stageTimeoutMs,
|
|
534
|
+
runId,
|
|
535
|
+
disableTools: true,
|
|
402
536
|
});
|
|
403
537
|
|
|
404
|
-
|
|
538
|
+
const outputText = this.extractPayloadText(result);
|
|
539
|
+
if (!outputText) {
|
|
405
540
|
return {
|
|
406
541
|
valid: false,
|
|
407
542
|
candidates: [],
|
|
408
|
-
reason:
|
|
543
|
+
reason: 'Dreamer returned empty response',
|
|
409
544
|
generatedAt: new Date().toISOString(),
|
|
410
545
|
};
|
|
411
546
|
}
|
|
412
547
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
limit: 5,
|
|
416
|
-
});
|
|
548
|
+
// DEBUG: Log Dreamer's actual output
|
|
549
|
+
this.api.logger?.info(`[Trinity:Dreamer] Output preview: ${outputText.slice(0, 500)}`);
|
|
417
550
|
|
|
418
|
-
const outputText = this.extractAssistantText(messages.messages as { role: string; text?: string; content?: string }[]);
|
|
419
551
|
return this.parseDreamerOutput(outputText);
|
|
552
|
+
} catch (err) {
|
|
553
|
+
return {
|
|
554
|
+
valid: false,
|
|
555
|
+
candidates: [],
|
|
556
|
+
reason: `Dreamer failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
557
|
+
generatedAt: new Date().toISOString(),
|
|
558
|
+
};
|
|
420
559
|
} finally {
|
|
421
|
-
|
|
422
|
-
sessionKey,
|
|
423
|
-
deleteTranscript: true,
|
|
424
|
-
}).catch(() => { /* intentionally empty - fire-and-forget session cleanup */ });
|
|
560
|
+
try { fs.unlinkSync(sessionFile); } catch { /* ignore */ }
|
|
425
561
|
}
|
|
426
562
|
}
|
|
427
563
|
|
|
428
564
|
async invokePhilosopher(
|
|
429
565
|
dreamerOutput: DreamerOutput,
|
|
430
|
-
principleId: string
|
|
566
|
+
principleId: string,
|
|
567
|
+
snapshot: NocturnalSessionSnapshot
|
|
431
568
|
): Promise<PhilosopherOutput> {
|
|
432
|
-
const
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
const
|
|
569
|
+
const runId = `philosopher-${randomUUID()}`;
|
|
570
|
+
const sessionFile = this.createSessionFile('philosopher');
|
|
571
|
+
const prompt = this.buildPhilosopherPrompt(dreamerOutput, principleId, snapshot);
|
|
572
|
+
const model = this.resolveModel();
|
|
436
573
|
|
|
437
574
|
try {
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
runId,
|
|
575
|
+
const result = await this.api.runtime.agent.runEmbeddedPiAgent({
|
|
576
|
+
sessionId: runId,
|
|
577
|
+
sessionFile,
|
|
578
|
+
prompt,
|
|
579
|
+
extraSystemPrompt: NOCTURNAL_PHILOSOPHER_PROMPT,
|
|
580
|
+
config: this.loadFullConfig(),
|
|
581
|
+
provider: model.provider,
|
|
582
|
+
model: model.model,
|
|
447
583
|
timeoutMs: this.stageTimeoutMs,
|
|
584
|
+
runId,
|
|
585
|
+
disableTools: true,
|
|
448
586
|
});
|
|
449
587
|
|
|
450
|
-
|
|
588
|
+
const outputText = this.extractPayloadText(result);
|
|
589
|
+
if (!outputText) {
|
|
451
590
|
return {
|
|
452
591
|
valid: false,
|
|
453
592
|
judgments: [],
|
|
454
593
|
overallAssessment: '',
|
|
455
|
-
reason:
|
|
594
|
+
reason: 'Philosopher returned empty response',
|
|
456
595
|
generatedAt: new Date().toISOString(),
|
|
457
596
|
};
|
|
458
597
|
}
|
|
459
598
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
limit: 5,
|
|
463
|
-
});
|
|
599
|
+
// DEBUG: Log Philosopher's actual output
|
|
600
|
+
this.api.logger?.info(`[Trinity:Philosopher] Output preview: ${outputText.slice(0, 500)}`);
|
|
464
601
|
|
|
465
|
-
const outputText = this.extractAssistantText(messages.messages as { role: string; text?: string; content?: string }[]);
|
|
466
602
|
return this.parsePhilosopherOutput(outputText);
|
|
603
|
+
} catch (err) {
|
|
604
|
+
return {
|
|
605
|
+
valid: false,
|
|
606
|
+
judgments: [],
|
|
607
|
+
overallAssessment: '',
|
|
608
|
+
reason: `Philosopher failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
609
|
+
generatedAt: new Date().toISOString(),
|
|
610
|
+
};
|
|
467
611
|
} finally {
|
|
468
|
-
|
|
469
|
-
sessionKey,
|
|
470
|
-
deleteTranscript: true,
|
|
471
|
-
}).catch(() => { /* intentionally empty - fire-and-forget session cleanup */ });
|
|
612
|
+
try { fs.unlinkSync(sessionFile); } catch { /* ignore */ }
|
|
472
613
|
}
|
|
473
614
|
}
|
|
474
615
|
|
|
475
|
-
|
|
616
|
+
|
|
476
617
|
async invokeScribe(
|
|
477
618
|
dreamerOutput: DreamerOutput,
|
|
478
619
|
philosopherOutput: PhilosopherOutput,
|
|
479
620
|
snapshot: NocturnalSessionSnapshot,
|
|
480
621
|
principleId: string,
|
|
481
622
|
telemetry: TrinityTelemetry,
|
|
482
|
-
|
|
623
|
+
|
|
483
624
|
_config: TrinityConfig
|
|
484
625
|
): Promise<TrinityDraftArtifact | null> {
|
|
485
|
-
const
|
|
486
|
-
const
|
|
487
|
-
|
|
626
|
+
const runId = `scribe-${randomUUID()}`;
|
|
627
|
+
const sessionFile = this.createSessionFile('scribe');
|
|
488
628
|
const prompt = this.buildScribePrompt(dreamerOutput, philosopherOutput, snapshot, principleId);
|
|
629
|
+
const model = this.resolveModel();
|
|
489
630
|
|
|
490
631
|
try {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
runId,
|
|
632
|
+
const result = await this.api.runtime.agent.runEmbeddedPiAgent({
|
|
633
|
+
sessionId: runId,
|
|
634
|
+
sessionFile,
|
|
635
|
+
prompt,
|
|
636
|
+
extraSystemPrompt: NOCTURNAL_SCRIBE_PROMPT,
|
|
637
|
+
config: this.loadFullConfig(),
|
|
638
|
+
provider: model.provider,
|
|
639
|
+
model: model.model,
|
|
500
640
|
timeoutMs: this.stageTimeoutMs,
|
|
641
|
+
runId,
|
|
642
|
+
disableTools: true,
|
|
501
643
|
});
|
|
502
644
|
|
|
503
|
-
|
|
645
|
+
const outputText = this.extractPayloadText(result);
|
|
646
|
+
if (!outputText) {
|
|
504
647
|
return null;
|
|
505
648
|
}
|
|
506
649
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
limit: 5,
|
|
510
|
-
});
|
|
650
|
+
// DEBUG: Log Scribe's actual output
|
|
651
|
+
this.api.logger?.info(`[Trinity:Scribe] Output preview: ${outputText.slice(0, 800)}`);
|
|
511
652
|
|
|
512
|
-
const outputText = this.extractAssistantText(messages.messages as { role: string; text?: string; content?: string }[]);
|
|
513
653
|
return this.parseScribeOutput(outputText, snapshot, principleId, telemetry);
|
|
654
|
+
} catch (err) {
|
|
655
|
+
return null;
|
|
514
656
|
} finally {
|
|
515
|
-
|
|
516
|
-
sessionKey,
|
|
517
|
-
deleteTranscript: true,
|
|
518
|
-
}).catch(() => { /* intentionally empty - fire-and-forget session cleanup */ });
|
|
657
|
+
try { fs.unlinkSync(sessionFile); } catch { /* ignore */ }
|
|
519
658
|
}
|
|
520
659
|
}
|
|
521
660
|
|
|
522
|
-
|
|
661
|
+
|
|
523
662
|
async close(): Promise<void> {
|
|
524
|
-
//
|
|
663
|
+
// Clean up temp directory
|
|
664
|
+
try {
|
|
665
|
+
if (fs.existsSync(this.tempDir)) {
|
|
666
|
+
const files = fs.readdirSync(this.tempDir);
|
|
667
|
+
for (const file of files) {
|
|
668
|
+
fs.unlinkSync(path.join(this.tempDir, file));
|
|
669
|
+
}
|
|
670
|
+
fs.rmSync(this.tempDir, { recursive: true, force: true });
|
|
671
|
+
}
|
|
672
|
+
} catch { /* ignore cleanup errors */ }
|
|
525
673
|
}
|
|
526
674
|
|
|
527
675
|
// ---------------------------------------------------------------------------
|
|
528
676
|
// Private Helper Methods
|
|
529
677
|
// ---------------------------------------------------------------------------
|
|
530
678
|
|
|
531
|
-
|
|
532
|
-
private extractAssistantText(
|
|
533
|
-
messages: { role: string; text?: string; content?: string }[]
|
|
534
|
-
): string {
|
|
535
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
536
|
-
const msg = messages[i] as { role: string; text?: string; content?: string };
|
|
537
|
-
if (msg.role === 'assistant') {
|
|
538
|
-
return msg.text ?? msg.content ?? '';
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
return '';
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: pure utility function that doesn't need instance state
|
|
679
|
+
|
|
545
680
|
private buildDreamerPrompt(
|
|
546
681
|
snapshot: NocturnalSessionSnapshot,
|
|
547
682
|
principleId: string,
|
|
548
683
|
maxCandidates: number
|
|
549
684
|
): string {
|
|
550
|
-
|
|
685
|
+
// Build detailed tool failure list
|
|
686
|
+
const failures = snapshot.toolCalls
|
|
687
|
+
.filter(tc => tc.outcome === 'failure')
|
|
688
|
+
.map(tc => {
|
|
689
|
+
let desc = `- ${tc.toolName}`;
|
|
690
|
+
if (tc.filePath) desc += ` on ${tc.filePath}`;
|
|
691
|
+
desc += ` → FAILED: ${tc.errorMessage || 'unknown error'}`;
|
|
692
|
+
return desc;
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// Build detailed pain event list
|
|
696
|
+
const pains = snapshot.painEvents
|
|
697
|
+
.filter(pe => pe.score >= 50)
|
|
698
|
+
.map(pe => `- Pain (score: ${pe.score}): ${pe.reason || 'no reason'} [source: ${pe.source}]`);
|
|
699
|
+
|
|
700
|
+
// Build gate block list
|
|
701
|
+
const blocks = snapshot.gateBlocks
|
|
702
|
+
.map(gb => `- Gate blocked ${gb.toolName}: ${gb.reason}`);
|
|
703
|
+
|
|
704
|
+
// Build assistant decision context (last 3 turns max)
|
|
705
|
+
const recentTurns = snapshot.assistantTurns
|
|
706
|
+
.slice(-3)
|
|
707
|
+
.map((t, i) => `[Turn ${i+1}] ${t.sanitizedText.slice(0, 300)}`)
|
|
708
|
+
.join('\n');
|
|
709
|
+
|
|
710
|
+
// Build user correction cues (if any)
|
|
711
|
+
const userCues = snapshot.userTurns
|
|
712
|
+
.filter(ut => ut.correctionDetected)
|
|
713
|
+
.map(ut => `- User correction: ${ut.correctionCue || 'detected'}`)
|
|
714
|
+
.join('\n');
|
|
715
|
+
|
|
716
|
+
const sections = [
|
|
717
|
+
`## Target Principle`,
|
|
718
|
+
`**Principle ID**: ${principleId}`,
|
|
719
|
+
``,
|
|
720
|
+
`## Session Context`,
|
|
721
|
+
`**Session ID**: ${snapshot.sessionId}`,
|
|
722
|
+
``,
|
|
723
|
+
];
|
|
724
|
+
|
|
725
|
+
if (failures.length > 0) {
|
|
726
|
+
sections.push(`## Tool Failures (${failures.length})`);
|
|
727
|
+
sections.push(failures.join('\n'));
|
|
728
|
+
sections.push('');
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (pains.length > 0) {
|
|
732
|
+
sections.push(`## Pain Signals (${pains.length})`);
|
|
733
|
+
sections.push(pains.join('\n'));
|
|
734
|
+
sections.push('');
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (blocks.length > 0) {
|
|
738
|
+
sections.push(`## Gate Blocks (${blocks.length})`);
|
|
739
|
+
sections.push(blocks.join('\n'));
|
|
740
|
+
sections.push('');
|
|
741
|
+
}
|
|
551
742
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
- Pain Events: ${snapshot.stats.totalPainEvents}
|
|
558
|
-
- Gate Blocks: ${snapshot.stats.totalGateBlocks}
|
|
743
|
+
if (recentTurns) {
|
|
744
|
+
sections.push(`## Assistant Decision Context`);
|
|
745
|
+
sections.push(recentTurns);
|
|
746
|
+
sections.push('');
|
|
747
|
+
}
|
|
559
748
|
|
|
560
|
-
|
|
749
|
+
if (userCues) {
|
|
750
|
+
sections.push(`## User Corrections`);
|
|
751
|
+
sections.push(userCues);
|
|
752
|
+
sections.push('');
|
|
753
|
+
}
|
|
561
754
|
|
|
562
|
-
|
|
755
|
+
sections.push(`## Task`,
|
|
756
|
+
`Analyze the above session and generate ${maxCandidates} candidate corrections.`,
|
|
757
|
+
`Each candidate must:`,
|
|
758
|
+
`1. Identify a specific bad decision from the session`,
|
|
759
|
+
`2. Propose a concrete better decision grounded in principle ${principleId}`,
|
|
760
|
+
`3. The betterDecision MUST START with a bounded verb: read, check, verify, edit, write, create, delete, search, grep, find, list, review, examine, inspect, test, run, execute, analyze, diagnose, debug`,
|
|
761
|
+
`4. Explain the rationale referencing the principle`,
|
|
762
|
+
``,
|
|
763
|
+
`Respond with ONLY a valid JSON object matching the DreamerOutput contract.`
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
return sections.join('\n');
|
|
563
767
|
}
|
|
564
768
|
|
|
565
|
-
|
|
769
|
+
|
|
566
770
|
private buildPhilosopherPrompt(
|
|
567
771
|
dreamerOutput: DreamerOutput,
|
|
568
|
-
principleId: string
|
|
772
|
+
principleId: string,
|
|
773
|
+
snapshot: NocturnalSessionSnapshot
|
|
569
774
|
): string {
|
|
570
775
|
const candidatesJson = JSON.stringify(dreamerOutput.candidates, null, 2);
|
|
571
|
-
return `Target Principle: ${principleId}
|
|
572
776
|
|
|
573
|
-
|
|
574
|
-
|
|
777
|
+
// Build violation summary from snapshot for Philosopher to validate candidates
|
|
778
|
+
const failures = snapshot.toolCalls
|
|
779
|
+
.filter(tc => tc.outcome === 'failure')
|
|
780
|
+
.map(tc => `- ${tc.toolName}${tc.filePath ? ` on ${tc.filePath}` : ''} → FAILED: ${tc.errorMessage || 'unknown error'}`);
|
|
781
|
+
|
|
782
|
+
const pains = snapshot.painEvents
|
|
783
|
+
.filter(pe => pe.score >= 50)
|
|
784
|
+
.map(pe => `- Pain (score: ${pe.score}, severity: ${pe.severity || 'N/A'}): ${pe.reason || 'no reason'} [source: ${pe.source}]`);
|
|
785
|
+
|
|
786
|
+
const blocks = snapshot.gateBlocks
|
|
787
|
+
.map(gb => `- Gate blocked ${gb.toolName}: ${gb.reason}`);
|
|
788
|
+
|
|
789
|
+
const userCues = snapshot.userTurns
|
|
790
|
+
.filter(ut => ut.correctionDetected)
|
|
791
|
+
.map(ut => `- User correction: ${ut.correctionCue || 'detected'}`);
|
|
792
|
+
|
|
793
|
+
const sections = [
|
|
794
|
+
`## Target Principle`,
|
|
795
|
+
`**Principle ID**: ${principleId}`,
|
|
796
|
+
``,
|
|
797
|
+
`## Session Violation Summary`,
|
|
798
|
+
`**Session ID**: ${snapshot.sessionId}`,
|
|
799
|
+
];
|
|
800
|
+
|
|
801
|
+
if (failures.length > 0) {
|
|
802
|
+
sections.push(`\n### Tool Failures (${failures.length})`);
|
|
803
|
+
sections.push(failures.join('\n'));
|
|
804
|
+
}
|
|
575
805
|
|
|
576
|
-
|
|
806
|
+
if (pains.length > 0) {
|
|
807
|
+
sections.push(`\n### Pain Signals (${pains.length})`);
|
|
808
|
+
sections.push(pains.join('\n'));
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (blocks.length > 0) {
|
|
812
|
+
sections.push(`\n### Gate Blocks (${blocks.length})`);
|
|
813
|
+
sections.push(blocks.join('\n'));
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (userCues.length > 0) {
|
|
817
|
+
sections.push(`\n### User Corrections (${userCues.length})`);
|
|
818
|
+
sections.push(userCues.join('\n'));
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
sections.push(
|
|
822
|
+
``,
|
|
823
|
+
`## Dreamer's Candidates`,
|
|
824
|
+
candidatesJson,
|
|
825
|
+
``,
|
|
826
|
+
`## Task`,
|
|
827
|
+
`Evaluate each candidate against the violation summary above.`,
|
|
828
|
+
`For each candidate:`,
|
|
829
|
+
`1. Is the badDecision accurate — does it match the actual violations in the session?`,
|
|
830
|
+
`2. Is the betterDecision specific and actionable?`,
|
|
831
|
+
`3. Does the betterDecision START with a bounded verb (read, check, verify, edit, write, etc.)?`,
|
|
832
|
+
`4. Does the rationale correctly reference principle ${principleId}?`,
|
|
833
|
+
`5. Is the confidence score justified?`,
|
|
834
|
+
``,
|
|
835
|
+
`**Penalize executability**: If betterDecision does NOT start with a bounded verb, reduce score by 0.2.`,
|
|
836
|
+
``,
|
|
837
|
+
`Respond with ONLY a valid JSON object matching the PhilosopherOutput contract.`
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
return sections.join('\n');
|
|
577
841
|
}
|
|
578
842
|
|
|
579
|
-
|
|
843
|
+
|
|
580
844
|
private buildScribePrompt(
|
|
581
845
|
dreamerOutput: DreamerOutput,
|
|
582
846
|
philosopherOutput: PhilosopherOutput,
|
|
@@ -585,18 +849,76 @@ Please evaluate each candidate and rank them by principle alignment, specificity
|
|
|
585
849
|
): string {
|
|
586
850
|
const candidatesJson = JSON.stringify(dreamerOutput.candidates, null, 2);
|
|
587
851
|
const judgmentsJson = JSON.stringify(philosopherOutput.judgments, null, 2);
|
|
588
|
-
return `Target Principle: ${principleId}
|
|
589
|
-
Session ID: ${snapshot.sessionId}
|
|
590
852
|
|
|
591
|
-
|
|
592
|
-
|
|
853
|
+
// Build violation evidence for Scribe to ground the final artifact
|
|
854
|
+
const violations: string[] = [];
|
|
855
|
+
|
|
856
|
+
const failures = snapshot.toolCalls.filter(tc => tc.outcome === 'failure');
|
|
857
|
+
for (const tc of failures) {
|
|
858
|
+
violations.push(`- Tool failure: ${tc.toolName}${tc.filePath ? ` on ${tc.filePath}` : ''} → ${tc.errorMessage || 'unknown error'}`);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const pains = snapshot.painEvents.filter(pe => pe.score >= 50);
|
|
862
|
+
for (const pe of pains) {
|
|
863
|
+
violations.push(`- Pain signal (score: ${pe.score}): ${pe.reason || 'no reason'} [source: ${pe.source}]`);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const blocks = snapshot.gateBlocks;
|
|
867
|
+
for (const gb of blocks) {
|
|
868
|
+
violations.push(`- Gate blocked: ${gb.toolName} → ${gb.reason}`);
|
|
869
|
+
}
|
|
593
870
|
|
|
594
|
-
|
|
595
|
-
|
|
871
|
+
const sections = [
|
|
872
|
+
`## Target Principle`,
|
|
873
|
+
`**Principle ID**: ${principleId}`,
|
|
874
|
+
``,
|
|
875
|
+
`## Original Violation Evidence`,
|
|
876
|
+
`**Session ID**: ${snapshot.sessionId}`,
|
|
877
|
+
];
|
|
878
|
+
|
|
879
|
+
if (violations.length > 0) {
|
|
880
|
+
sections.push(violations.join('\n'));
|
|
881
|
+
} else {
|
|
882
|
+
sections.push(`(No specific violations found in snapshot)`);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
sections.push(
|
|
886
|
+
``,
|
|
887
|
+
`## Dreamer's Candidates`,
|
|
888
|
+
candidatesJson,
|
|
889
|
+
``,
|
|
890
|
+
`## Philosopher's Judgments`,
|
|
891
|
+
judgmentsJson,
|
|
892
|
+
``,
|
|
893
|
+
`## Task`,
|
|
894
|
+
`Select the best candidate (Philosopher's rank 1) and synthesize it into a final TrinityDraftArtifact.`,
|
|
895
|
+
`Use the Original Violation Evidence above to ensure your final badDecision and betterDecision`,
|
|
896
|
+
`are grounded in the actual session events, not just Dreamer's interpretation.`,
|
|
897
|
+
``,
|
|
898
|
+
`## CRITICAL: betterDecision Format Requirements`,
|
|
899
|
+
`Your betterDecision MUST pass executability validation. It MUST:`,
|
|
900
|
+
`1. START with a concrete action verb from this list: read, check, verify, edit, write, create, delete, search, grep, find, list, review, examine, inspect, test, run, execute, analyze, diagnose, debug`,
|
|
901
|
+
`2. Reference a SPECIFIC, concrete target (file path, command name, config key, etc.)`,
|
|
902
|
+
`3. Describe a BOUNDED, executable action — not a vague principle or process`,
|
|
903
|
+
``,
|
|
904
|
+
`**Examples that PASS executability check**:`,
|
|
905
|
+
`- "Read the file before editing to verify current content"`,
|
|
906
|
+
`- "Check user permissions before executing privileged commands"`,
|
|
907
|
+
`- "Verify the routing infrastructure is operational before analyzing system state"`,
|
|
908
|
+
`- "Edit the config file to set timeout=30000ms"`,
|
|
909
|
+
``,
|
|
910
|
+
`**Examples that FAIL executability check**:`,
|
|
911
|
+
`- "Per T-01, pause all analysis tasks..." (starts with "Per", not a bounded verb)`,
|
|
912
|
+
`- "The agent should have first checked..." (starts with "The", not the action verb)`,
|
|
913
|
+
`- "Be more careful with routing tools" (vague verb "be")`,
|
|
914
|
+
`- "Ensure proper authorization" (vague verb "ensure")`,
|
|
915
|
+
``,
|
|
916
|
+
`Respond with ONLY a valid JSON object.`
|
|
917
|
+
);
|
|
596
918
|
|
|
597
|
-
|
|
919
|
+
return sections.join('\n');
|
|
598
920
|
}
|
|
599
|
-
|
|
921
|
+
|
|
600
922
|
|
|
601
923
|
private parseDreamerOutput(text: string): DreamerOutput {
|
|
602
924
|
const json = this.extractJson(text);
|
|
@@ -694,12 +1016,12 @@ Select the best candidate (Philosopher's rank 1) and synthesize it into a final
|
|
|
694
1016
|
}
|
|
695
1017
|
}
|
|
696
1018
|
|
|
697
|
-
|
|
1019
|
+
|
|
698
1020
|
private parseScribeOutput(
|
|
699
1021
|
text: string,
|
|
700
1022
|
snapshot: NocturnalSessionSnapshot,
|
|
701
1023
|
principleId: string,
|
|
702
|
-
|
|
1024
|
+
|
|
703
1025
|
_telemetry: TrinityTelemetry
|
|
704
1026
|
): TrinityDraftArtifact | null {
|
|
705
1027
|
const json = this.extractJson(text);
|
|
@@ -740,7 +1062,7 @@ Select the best candidate (Philosopher's rank 1) and synthesize it into a final
|
|
|
740
1062
|
/**
|
|
741
1063
|
* Extract JSON object from text that may contain markdown code blocks.
|
|
742
1064
|
*/
|
|
743
|
-
|
|
1065
|
+
|
|
744
1066
|
private extractJson(text: string): string | null {
|
|
745
1067
|
// Try direct parse first
|
|
746
1068
|
try {
|
|
@@ -996,9 +1318,9 @@ export function invokeStubDreamer(
|
|
|
996
1318
|
principleId: string,
|
|
997
1319
|
maxCandidates: number
|
|
998
1320
|
): DreamerOutput {
|
|
999
|
-
const hasFailures = snapshot.stats.failureCount > 0;
|
|
1321
|
+
const hasFailures = (snapshot.stats.failureCount ?? 0) > 0;
|
|
1000
1322
|
const hasPain = snapshot.stats.totalPainEvents > 0;
|
|
1001
|
-
const hasGateBlocks = snapshot.stats.totalGateBlocks > 0;
|
|
1323
|
+
const hasGateBlocks = (snapshot.stats.totalGateBlocks ?? 0) > 0;
|
|
1002
1324
|
|
|
1003
1325
|
// #219: Detect fallback data source - stats may be incomplete
|
|
1004
1326
|
const isFallback = snapshot._dataSource === 'pain_context_fallback';
|
|
@@ -1126,8 +1448,8 @@ export function invokeStubDreamer(
|
|
|
1126
1448
|
*/
|
|
1127
1449
|
export function invokeStubPhilosopher(
|
|
1128
1450
|
dreamerOutput: DreamerOutput,
|
|
1129
|
-
|
|
1130
|
-
|
|
1451
|
+
_principleId: string,
|
|
1452
|
+
_snapshot: NocturnalSessionSnapshot
|
|
1131
1453
|
): PhilosopherOutput {
|
|
1132
1454
|
if (!dreamerOutput.valid || dreamerOutput.candidates.length === 0) {
|
|
1133
1455
|
return {
|
|
@@ -1201,7 +1523,7 @@ export function invokeStubPhilosopher(
|
|
|
1201
1523
|
* In production, this would call the actual Scribe subagent.
|
|
1202
1524
|
* The stub uses tournament selection (scoring + thresholds) to pick the winner.
|
|
1203
1525
|
*/
|
|
1204
|
-
|
|
1526
|
+
|
|
1205
1527
|
export function invokeStubScribe(
|
|
1206
1528
|
dreamerOutput: DreamerOutput,
|
|
1207
1529
|
philosopherOutput: PhilosopherOutput,
|
|
@@ -1279,7 +1601,7 @@ export function runTrinity(options: RunTrinityOptions): TrinityResult {
|
|
|
1279
1601
|
|
|
1280
1602
|
// Stub path: use synchronous stub implementations
|
|
1281
1603
|
if (config.useStubs) {
|
|
1282
|
-
|
|
1604
|
+
|
|
1283
1605
|
return runTrinityWithStubs(snapshot, principleId, config);
|
|
1284
1606
|
}
|
|
1285
1607
|
|
|
@@ -1318,7 +1640,7 @@ export async function runTrinityAsync(options: RunTrinityOptions): Promise<Trini
|
|
|
1318
1640
|
|
|
1319
1641
|
if (config.useStubs) {
|
|
1320
1642
|
// Stub path: use synchronous stubs
|
|
1321
|
-
|
|
1643
|
+
|
|
1322
1644
|
return runTrinityWithStubs(snapshot, principleId, config);
|
|
1323
1645
|
}
|
|
1324
1646
|
|
|
@@ -1375,7 +1697,7 @@ export async function runTrinityAsync(options: RunTrinityOptions): Promise<Trini
|
|
|
1375
1697
|
telemetry.candidateCount = dreamerOutput.candidates.length;
|
|
1376
1698
|
|
|
1377
1699
|
// Step 2: Philosopher — rank candidates via real subagent
|
|
1378
|
-
const philosopherOutput = await adapter.invokePhilosopher(dreamerOutput, principleId);
|
|
1700
|
+
const philosopherOutput = await adapter.invokePhilosopher(dreamerOutput, principleId, snapshot);
|
|
1379
1701
|
|
|
1380
1702
|
if (!philosopherOutput.valid || philosopherOutput.judgments.length === 0) {
|
|
1381
1703
|
failures.push({
|
|
@@ -1471,7 +1793,7 @@ function runTrinityWithStubs(
|
|
|
1471
1793
|
telemetry.candidateCount = dreamerOutput.candidates.length;
|
|
1472
1794
|
|
|
1473
1795
|
// Step 2: Philosopher — rank candidates (stub)
|
|
1474
|
-
const philosopherOutput = invokeStubPhilosopher(dreamerOutput, principleId);
|
|
1796
|
+
const philosopherOutput = invokeStubPhilosopher(dreamerOutput, principleId, snapshot);
|
|
1475
1797
|
|
|
1476
1798
|
if (!philosopherOutput.valid || philosopherOutput.judgments.length === 0) {
|
|
1477
1799
|
failures.push({
|