principles-disciple 1.139.0 → 1.141.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/detection-funnel.d.ts +25 -11
- package/dist/core/detection-funnel.js +32 -80
- package/dist/core/pain-diagnostic-gate.d.ts +26 -28
- package/dist/core/pain-diagnostic-gate.js +42 -89
- package/dist/hooks/prompt-helpers.d.ts +87 -0
- package/dist/hooks/prompt-helpers.js +251 -0
- package/dist/hooks/prompt-types.d.ts +66 -0
- package/dist/hooks/prompt-types.js +12 -0
- package/dist/hooks/prompt.d.ts +7 -38
- package/dist/hooks/prompt.js +130 -255
- package/dist/hooks/raw-observation-adapter.d.ts +9 -50
- package/dist/hooks/raw-observation-adapter.js +9 -164
- package/dist/hooks/raw-observation-types.d.ts +6 -57
- package/dist/hooks/raw-observation-types.js +5 -9
- package/dist/hooks/triage-adapter.js +6 -26
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/hooks/subagent.d.ts +0 -9
- package/dist/hooks/subagent.js +0 -122
|
@@ -1,24 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection Funnel — PRI-446 thin adapter
|
|
3
|
+
*
|
|
4
|
+
* The pure three-layer funnel logic (LRU cache, async queue, layer dispatch)
|
|
5
|
+
* now lives in principles-core
|
|
6
|
+
* (runtime-v2/detection/detection-funnel-policy.ts). This file is a thin shell
|
|
7
|
+
* that wires DetectionFunnelCore with the two environment-coupled dependencies
|
|
8
|
+
* the plugin owns:
|
|
9
|
+
* - crypto.createHash (the L2 cache key hasher)
|
|
10
|
+
* - PainDictionary.match (the L1 exact matcher)
|
|
11
|
+
* - shouldIgnorePainProtocolText (the protocol-token gate)
|
|
12
|
+
*
|
|
13
|
+
* It re-exports DetectionFunnel (same class name + constructor signature) and
|
|
14
|
+
* DetectionResult so DetectionService, llm.ts, and evolution-worker keep working
|
|
15
|
+
* unchanged.
|
|
16
|
+
*
|
|
17
|
+
* ERR checklist:
|
|
18
|
+
* - ERR-011: this is a thin adapter delegating to core pure logic.
|
|
19
|
+
*/
|
|
1
20
|
import { type PainDictionary } from './dictionary.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
severity?: number;
|
|
5
|
-
ruleId?: string;
|
|
6
|
-
source: 'l1_exact' | 'l2_cache' | 'l3_async_queued' | 'l3_semantic_hit';
|
|
7
|
-
}
|
|
21
|
+
import { type DetectionResult } from '@principles/core/runtime-v2';
|
|
22
|
+
export type { DetectionResult };
|
|
8
23
|
/**
|
|
9
24
|
* Orchestrates the three-layer detection funnel for pain signals.
|
|
25
|
+
*
|
|
26
|
+
* Delegates to the core DetectionFunnelCore, injecting the crypto hasher,
|
|
27
|
+
* the dictionary matcher, and the protocol-token gate.
|
|
10
28
|
*/
|
|
11
29
|
export declare class DetectionFunnel {
|
|
12
|
-
private readonly
|
|
13
|
-
private asyncQueue;
|
|
14
|
-
private readonly dictionary;
|
|
30
|
+
private readonly core;
|
|
15
31
|
constructor(dictionary: PainDictionary);
|
|
16
32
|
/**
|
|
17
33
|
* Detects pain in the given text using L1 (Exact), L2 (Cache), and L3 (Async).
|
|
18
34
|
*/
|
|
19
35
|
detect(text: string): DetectionResult;
|
|
20
|
-
private static computeHash;
|
|
21
|
-
private enqueueAsync;
|
|
22
36
|
/**
|
|
23
37
|
* Internal method for the worker to update the cache after a semantic hit.
|
|
24
38
|
*/
|
|
@@ -1,104 +1,56 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
2
|
-
import { shouldIgnorePainProtocolText } from './dictionary.js';
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
2
|
+
* Detection Funnel — PRI-446 thin adapter
|
|
3
|
+
*
|
|
4
|
+
* The pure three-layer funnel logic (LRU cache, async queue, layer dispatch)
|
|
5
|
+
* now lives in principles-core
|
|
6
|
+
* (runtime-v2/detection/detection-funnel-policy.ts). This file is a thin shell
|
|
7
|
+
* that wires DetectionFunnelCore with the two environment-coupled dependencies
|
|
8
|
+
* the plugin owns:
|
|
9
|
+
* - crypto.createHash (the L2 cache key hasher)
|
|
10
|
+
* - PainDictionary.match (the L1 exact matcher)
|
|
11
|
+
* - shouldIgnorePainProtocolText (the protocol-token gate)
|
|
12
|
+
*
|
|
13
|
+
* It re-exports DetectionFunnel (same class name + constructor signature) and
|
|
14
|
+
* DetectionResult so DetectionService, llm.ts, and evolution-worker keep working
|
|
15
|
+
* unchanged.
|
|
16
|
+
*
|
|
17
|
+
* ERR checklist:
|
|
18
|
+
* - ERR-011: this is a thin adapter delegating to core pure logic.
|
|
5
19
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
constructor(maxSize = 100) {
|
|
10
|
-
this.cache = new Map();
|
|
11
|
-
this.maxSize = maxSize;
|
|
12
|
-
}
|
|
13
|
-
get(key) {
|
|
14
|
-
const item = this.cache.get(key);
|
|
15
|
-
if (item !== undefined) {
|
|
16
|
-
// Refresh: delete and re-insert
|
|
17
|
-
this.cache.delete(key);
|
|
18
|
-
this.cache.set(key, item);
|
|
19
|
-
}
|
|
20
|
-
return item;
|
|
21
|
-
}
|
|
22
|
-
set(key, value) {
|
|
23
|
-
if (this.cache.has(key)) {
|
|
24
|
-
this.cache.delete(key);
|
|
25
|
-
}
|
|
26
|
-
else if (this.cache.size >= this.maxSize) {
|
|
27
|
-
// Remove the oldest (first) item
|
|
28
|
-
const firstKey = this.cache.keys().next().value;
|
|
29
|
-
if (firstKey !== undefined) {
|
|
30
|
-
this.cache.delete(firstKey);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
this.cache.set(key, value);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
20
|
+
import { createHash } from 'crypto';
|
|
21
|
+
import { shouldIgnorePainProtocolText } from './dictionary.js';
|
|
22
|
+
import { DetectionFunnelCore, } from '@principles/core/runtime-v2';
|
|
36
23
|
/**
|
|
37
24
|
* Orchestrates the three-layer detection funnel for pain signals.
|
|
25
|
+
*
|
|
26
|
+
* Delegates to the core DetectionFunnelCore, injecting the crypto hasher,
|
|
27
|
+
* the dictionary matcher, and the protocol-token gate.
|
|
38
28
|
*/
|
|
39
29
|
export class DetectionFunnel {
|
|
40
|
-
|
|
41
|
-
asyncQueue = [];
|
|
42
|
-
dictionary;
|
|
30
|
+
core;
|
|
43
31
|
constructor(dictionary) {
|
|
44
|
-
this.
|
|
32
|
+
this.core = new DetectionFunnelCore({
|
|
33
|
+
match: (text) => dictionary.match(text),
|
|
34
|
+
hash: (text) => createHash('sha256').update(text).digest('hex'),
|
|
35
|
+
shouldIgnoreProtocol: shouldIgnorePainProtocolText,
|
|
36
|
+
});
|
|
45
37
|
}
|
|
46
38
|
/**
|
|
47
39
|
* Detects pain in the given text using L1 (Exact), L2 (Cache), and L3 (Async).
|
|
48
40
|
*/
|
|
49
41
|
detect(text) {
|
|
50
|
-
|
|
51
|
-
return { detected: false, source: 'l1_exact' };
|
|
52
|
-
}
|
|
53
|
-
// --- Layer 1: Exact Match (Sync) ---
|
|
54
|
-
const exactMatch = this.dictionary.match(text);
|
|
55
|
-
if (exactMatch) {
|
|
56
|
-
return {
|
|
57
|
-
detected: true,
|
|
58
|
-
severity: exactMatch.severity,
|
|
59
|
-
ruleId: exactMatch.ruleId,
|
|
60
|
-
source: 'l1_exact'
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
// --- Layer 2: LRU Cache (Sync) ---
|
|
64
|
-
const hash = DetectionFunnel.computeHash(text);
|
|
65
|
-
const cached = this.cache.get(hash);
|
|
66
|
-
if (cached) {
|
|
67
|
-
return {
|
|
68
|
-
detected: cached.detected,
|
|
69
|
-
severity: cached.severity,
|
|
70
|
-
source: 'l2_cache'
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
// --- Layer 3: Async Semantic Queue ---
|
|
74
|
-
this.enqueueAsync(text);
|
|
75
|
-
return {
|
|
76
|
-
detected: false,
|
|
77
|
-
source: 'l3_async_queued'
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
static computeHash(text) {
|
|
81
|
-
return createHash('sha256').update(text).digest('hex');
|
|
82
|
-
}
|
|
83
|
-
enqueueAsync(text) {
|
|
84
|
-
if (this.asyncQueue.length < 1000) {
|
|
85
|
-
this.asyncQueue.push(text);
|
|
86
|
-
}
|
|
87
|
-
// Worker will pick this up and perform semantic search via createMemorySearchTool
|
|
42
|
+
return this.core.detect(text);
|
|
88
43
|
}
|
|
89
44
|
/**
|
|
90
45
|
* Internal method for the worker to update the cache after a semantic hit.
|
|
91
46
|
*/
|
|
92
47
|
updateCache(text, result) {
|
|
93
|
-
|
|
94
|
-
this.cache.set(hash, result);
|
|
48
|
+
this.core.updateCache(text, result);
|
|
95
49
|
}
|
|
96
50
|
/**
|
|
97
51
|
* Retrieves and clears the current asynchronous queue.
|
|
98
52
|
*/
|
|
99
53
|
flushQueue() {
|
|
100
|
-
|
|
101
|
-
this.asyncQueue = [];
|
|
102
|
-
return queue;
|
|
54
|
+
return this.core.flushQueue();
|
|
103
55
|
}
|
|
104
56
|
}
|
|
@@ -1,30 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
export interface PainDiagnosticGateDecision {
|
|
23
|
-
shouldDiagnose: boolean;
|
|
24
|
-
reason: PainDiagnosticGateReason;
|
|
25
|
-
episodeKey: string;
|
|
26
|
-
detail: string;
|
|
27
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Pain Diagnostic Gate — PRI-446 thin adapter
|
|
3
|
+
*
|
|
4
|
+
* The pure decision logic (threshold tree, cooldown comparison, episode-key
|
|
5
|
+
* construction) now lives in principles-core
|
|
6
|
+
* (runtime-v2/pain-gate/pain-diagnostic-gate-policy.ts). This file is the
|
|
7
|
+
* stateful adapter that owns:
|
|
8
|
+
* - the cooldown Map (lastDiagnosedAtByEpisode)
|
|
9
|
+
* - Date.now() injection
|
|
10
|
+
* - SystemLogger for unknown-source telemetry
|
|
11
|
+
*
|
|
12
|
+
* It preserves the original export names (evaluatePainDiagnosticGate,
|
|
13
|
+
* isCooldownActiveForEpisode, resetPainDiagnosticGateForTest) so all 5 callers
|
|
14
|
+
* (gate-block-helper, llm, pain, prompt x2) and the characterization test keep
|
|
15
|
+
* working unchanged.
|
|
16
|
+
*
|
|
17
|
+
* ERR checklist:
|
|
18
|
+
* - ERR-011: this is a stateful adapter delegating to core pure logic.
|
|
19
|
+
*/
|
|
20
|
+
import { type PainDiagnosticSource, type PainDiagnosticGateReason, type PainDiagnosticGateInput, type PainDiagnosticGateDecision } from '@principles/core/runtime-v2';
|
|
21
|
+
export type { PainDiagnosticSource, PainDiagnosticGateReason, PainDiagnosticGateInput, PainDiagnosticGateDecision, };
|
|
28
22
|
export declare function resetPainDiagnosticGateForTest(): void;
|
|
29
23
|
/**
|
|
30
24
|
* Check whether cooldown is currently active for a given episode.
|
|
@@ -32,5 +26,9 @@ export declare function resetPainDiagnosticGateForTest(): void;
|
|
|
32
26
|
* with the PainDiagnosticGate's cooldown state.
|
|
33
27
|
*/
|
|
34
28
|
export declare function isCooldownActiveForEpisode(source: string, sessionId: string | undefined, errorHash: string | undefined, cooldownMs?: number): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Evaluate the pain diagnostic gate. Delegates the pure decision to core and,
|
|
31
|
+
* when the decision is to diagnose, records the current time against the
|
|
32
|
+
* episode so subsequent calls within the cooldown window are suppressed.
|
|
33
|
+
*/
|
|
35
34
|
export declare function evaluatePainDiagnosticGate(input: PainDiagnosticGateInput): PainDiagnosticGateDecision;
|
|
36
|
-
export {};
|
|
@@ -1,42 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pain Diagnostic Gate — PRI-446 thin adapter
|
|
3
|
+
*
|
|
4
|
+
* The pure decision logic (threshold tree, cooldown comparison, episode-key
|
|
5
|
+
* construction) now lives in principles-core
|
|
6
|
+
* (runtime-v2/pain-gate/pain-diagnostic-gate-policy.ts). This file is the
|
|
7
|
+
* stateful adapter that owns:
|
|
8
|
+
* - the cooldown Map (lastDiagnosedAtByEpisode)
|
|
9
|
+
* - Date.now() injection
|
|
10
|
+
* - SystemLogger for unknown-source telemetry
|
|
11
|
+
*
|
|
12
|
+
* It preserves the original export names (evaluatePainDiagnosticGate,
|
|
13
|
+
* isCooldownActiveForEpisode, resetPainDiagnosticGateForTest) so all 5 callers
|
|
14
|
+
* (gate-block-helper, llm, pain, prompt x2) and the characterization test keep
|
|
15
|
+
* working unchanged.
|
|
16
|
+
*
|
|
17
|
+
* ERR checklist:
|
|
18
|
+
* - ERR-011: this is a stateful adapter delegating to core pure logic.
|
|
19
|
+
*/
|
|
1
20
|
import { SystemLogger } from './system-logger.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
'tool_failure',
|
|
5
|
-
'dispatch_error',
|
|
6
|
-
'gate_blocked',
|
|
7
|
-
'user_empathy',
|
|
8
|
-
'llm_paralysis',
|
|
9
|
-
'semantic',
|
|
10
|
-
'subagent_error',
|
|
11
|
-
];
|
|
12
|
-
const DEFAULT_COOLDOWN_MS = 15 * 60 * 1000;
|
|
21
|
+
import { evaluatePainDiagnosticGateDecision, normalizedSource, buildEpisodeKey, isCooldownActive as isCooldownActiveCore, } from '@principles/core/runtime-v2';
|
|
22
|
+
// Module-level cooldown state — owned by this adapter (core is stateless).
|
|
13
23
|
const lastDiagnosedAtByEpisode = new Map();
|
|
14
|
-
function normalizedSource(source) {
|
|
15
|
-
if (source.startsWith('llm_') && source !== 'llm_paralysis') {
|
|
16
|
-
return 'semantic';
|
|
17
|
-
}
|
|
18
|
-
if (!PAIN_DIAGNOSTIC_SOURCES.includes(source)) {
|
|
19
|
-
SystemLogger.log('', 'GATE_UNKNOWN_SOURCE', `Unknown pain source: "${source}"`);
|
|
20
|
-
}
|
|
21
|
-
return source;
|
|
22
|
-
}
|
|
23
|
-
function buildEpisodeKey(input) {
|
|
24
|
-
const source = normalizedSource(input.source);
|
|
25
|
-
const sessionId = input.sessionId || 'unknown';
|
|
26
|
-
const hash = input.errorHash || 'no-hash';
|
|
27
|
-
return `${sessionId}:${source}:${hash}`;
|
|
28
|
-
}
|
|
29
|
-
function withinCooldown(input, episodeKey) {
|
|
30
|
-
const cooldownMs = input.cooldownMs ?? DEFAULT_COOLDOWN_MS;
|
|
31
|
-
if (cooldownMs <= 0)
|
|
32
|
-
return false;
|
|
33
|
-
const nowMs = input.nowMs ?? Date.now();
|
|
34
|
-
const last = lastDiagnosedAtByEpisode.get(episodeKey);
|
|
35
|
-
return last !== undefined && nowMs - last < cooldownMs;
|
|
36
|
-
}
|
|
37
|
-
function markDiagnosed(input, episodeKey) {
|
|
38
|
-
lastDiagnosedAtByEpisode.set(episodeKey, input.nowMs ?? Date.now());
|
|
39
|
-
}
|
|
40
24
|
export function resetPainDiagnosticGateForTest() {
|
|
41
25
|
lastDiagnosedAtByEpisode.clear();
|
|
42
26
|
}
|
|
@@ -46,60 +30,29 @@ export function resetPainDiagnosticGateForTest() {
|
|
|
46
30
|
* with the PainDiagnosticGate's cooldown state.
|
|
47
31
|
*/
|
|
48
32
|
export function isCooldownActiveForEpisode(source, sessionId, errorHash, cooldownMs) {
|
|
49
|
-
const episodeKey = buildEpisodeKey({ source, sessionId, errorHash });
|
|
50
|
-
|
|
33
|
+
const episodeKey = buildEpisodeKey({ source, sessionId, errorHash, score: 0, currentGfi: 0 });
|
|
34
|
+
const last = lastDiagnosedAtByEpisode.get(episodeKey);
|
|
35
|
+
const nowMs = Date.now();
|
|
36
|
+
return isCooldownActiveCore({ source, sessionId, errorHash, cooldownMs, nowMs, lastDiagnosedAtMs: last });
|
|
51
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Evaluate the pain diagnostic gate. Delegates the pure decision to core and,
|
|
40
|
+
* when the decision is to diagnose, records the current time against the
|
|
41
|
+
* episode so subsequent calls within the cooldown window are suppressed.
|
|
42
|
+
*/
|
|
52
43
|
export function evaluatePainDiagnosticGate(input) {
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const highGfi = input.thresholds?.highGfi ?? Math.max(highSeverity, painTrigger + 30);
|
|
58
|
-
const repeatedFailure = input.thresholds?.repeatedFailure ?? 4;
|
|
59
|
-
const semanticPain = input.thresholds?.semanticPain ?? Math.max(painTrigger, 60);
|
|
60
|
-
const score = Number.isFinite(input.score) ? input.score : 0;
|
|
61
|
-
const currentGfi = Number.isFinite(input.currentGfi) ? input.currentGfi : 0;
|
|
62
|
-
const consecutiveErrors = Number.isFinite(input.consecutiveErrors) ? input.consecutiveErrors : 0;
|
|
63
|
-
const approve = (reason, detail) => {
|
|
64
|
-
if (withinCooldown(input, episodeKey)) {
|
|
65
|
-
return {
|
|
66
|
-
shouldDiagnose: false,
|
|
67
|
-
reason: 'cooldown',
|
|
68
|
-
episodeKey,
|
|
69
|
-
detail: `recently diagnosed; ${detail}`,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
markDiagnosed(input, episodeKey);
|
|
73
|
-
return { shouldDiagnose: true, reason, episodeKey, detail };
|
|
74
|
-
};
|
|
75
|
-
if (source === 'manual') {
|
|
76
|
-
return approve('manual', 'manual pain signal bypasses automatic gate');
|
|
77
|
-
}
|
|
78
|
-
if (source === 'subagent_error' && score >= painTrigger) {
|
|
79
|
-
return approve('subagent_error', `subagent error score ${score} >= ${painTrigger}`);
|
|
80
|
-
}
|
|
81
|
-
if (source === 'llm_paralysis' && score >= painTrigger) {
|
|
82
|
-
return approve('llm_paralysis', `llm paralysis score ${score} >= ${painTrigger}`);
|
|
83
|
-
}
|
|
84
|
-
if (source === 'gate_blocked' && score >= painTrigger) {
|
|
85
|
-
return approve('gate_blocked', `gate blocked score ${score} >= ${painTrigger}`);
|
|
86
|
-
}
|
|
87
|
-
if ((source === 'user_empathy' || source === 'semantic') && score >= semanticPain) {
|
|
88
|
-
return approve('semantic_pain', `semantic pain score ${score} >= ${semanticPain}`);
|
|
44
|
+
// Surface unknown sources via telemetry (core cannot log).
|
|
45
|
+
const { unknown } = normalizedSource(input.source);
|
|
46
|
+
if (unknown) {
|
|
47
|
+
SystemLogger.log('', 'GATE_UNKNOWN_SOURCE', `Unknown pain source: "${input.source}"`);
|
|
89
48
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return approve('high_gfi', `GFI ${currentGfi.toFixed(1)} >= ${highGfi}`);
|
|
49
|
+
const episodeKey = buildEpisodeKey(input);
|
|
50
|
+
const last = lastDiagnosedAtByEpisode.get(episodeKey);
|
|
51
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
52
|
+
const decision = evaluatePainDiagnosticGateDecision({ ...input, nowMs }, last);
|
|
53
|
+
// Record the diagnosis time when approved (matches prior markDiagnosed behavior).
|
|
54
|
+
if (decision.shouldDiagnose) {
|
|
55
|
+
lastDiagnosedAtByEpisode.set(episodeKey, nowMs);
|
|
98
56
|
}
|
|
99
|
-
return
|
|
100
|
-
shouldDiagnose: false,
|
|
101
|
-
reason: 'below_gate',
|
|
102
|
-
episodeKey,
|
|
103
|
-
detail: `score=${score}; gfi=${currentGfi.toFixed(1)}; consecutive=${consecutiveErrors}`,
|
|
104
|
-
};
|
|
57
|
+
return decision;
|
|
105
58
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure-logic helpers for prompt assembly.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from hooks/prompt.ts per PRI-444. These functions contain NO I/O
|
|
5
|
+
* and NO side effects — they are independently unit-testable.
|
|
6
|
+
*
|
|
7
|
+
* I/O helpers (cachedReadFile, loadContextInjectionConfig, resolveEmpathyObserver)
|
|
8
|
+
* remain in prompt.ts because they depend on module-level cache state and fs.
|
|
9
|
+
*
|
|
10
|
+
* Pattern follows after-tool-call-helpers.ts (PRI-326): plugin-internal
|
|
11
|
+
* decomposition + core pure-function reuse.
|
|
12
|
+
*
|
|
13
|
+
* ERR checklist:
|
|
14
|
+
* EP-01: All unknown inputs use typeof/Object.hasOwn guards, never `as`
|
|
15
|
+
* EP-03: Pure functions never swallow errors; invalid input returns empty string
|
|
16
|
+
* EP-09: Pure functions are independently unit-testable without mocks
|
|
17
|
+
*/
|
|
18
|
+
import type { ExtractedUserMessage, CorePrincipleEntry, EvolutionPrincipleEntry, AppendSystemContextParts } from './prompt-types.js';
|
|
19
|
+
/**
|
|
20
|
+
* Extract the actual user message from the raw prompt text.
|
|
21
|
+
*
|
|
22
|
+
* The prompt may contain:
|
|
23
|
+
* - Boot check messages (system-generated, return empty)
|
|
24
|
+
* - Feishu wrapper format 1: "Sender (untrusted metadata): ```json {...}``` text"
|
|
25
|
+
* - Feishu wrapper format 2: "Conversation info (untrusted metadata): ```json {...}``` text"
|
|
26
|
+
* - Clean user message text
|
|
27
|
+
*
|
|
28
|
+
* Also detects empathy observer output (to prevent recursive spawn) and
|
|
29
|
+
* agent-to-agent messages (to skip empathy evaluation).
|
|
30
|
+
*
|
|
31
|
+
* Pure logic — no I/O, no side effects.
|
|
32
|
+
*/
|
|
33
|
+
export declare function extractUserMessageFromPrompt(prompt: string, sessionId: string | undefined): ExtractedUserMessage;
|
|
34
|
+
/**
|
|
35
|
+
* Build the minimal Agent Identity section for prependSystemContext.
|
|
36
|
+
*
|
|
37
|
+
* EvolutionWorker-era INTERNAL SYSTEM LAYOUT removed per PRI-294.
|
|
38
|
+
* The EVOLUTION_WORKER PathResolver key and system layout reference are
|
|
39
|
+
* not MVP-Core; agents discover what they need via tool calls.
|
|
40
|
+
*
|
|
41
|
+
* Pure logic — returns a constant string.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildAgentIdentity(): string;
|
|
44
|
+
/**
|
|
45
|
+
* Build the empathy output restriction constraint text.
|
|
46
|
+
*
|
|
47
|
+
* Pure logic — returns a constant string.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildEmpathySilenceConstraint(): string;
|
|
50
|
+
/**
|
|
51
|
+
* Wrap heartbeat checklist content in XML tags.
|
|
52
|
+
*
|
|
53
|
+
* Pure logic — no I/O, no side effects.
|
|
54
|
+
*/
|
|
55
|
+
export declare function assembleHeartbeatChecklist(content: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Format core principles into prompt-ready text.
|
|
58
|
+
*
|
|
59
|
+
* Pure logic — uses escapeXml for safe XML embedding.
|
|
60
|
+
*
|
|
61
|
+
* @param principles Active principles from evolution reducer
|
|
62
|
+
* @returns Formatted lines (empty string if no principles)
|
|
63
|
+
*/
|
|
64
|
+
export declare function formatCorePrinciples(principles: CorePrincipleEntry[]): string;
|
|
65
|
+
/**
|
|
66
|
+
* Format evolution principles (active + probation) into prompt-ready text.
|
|
67
|
+
*
|
|
68
|
+
* Pure logic — uses escapeXml for safe XML embedding.
|
|
69
|
+
*
|
|
70
|
+
* @param active Active principles (high priority)
|
|
71
|
+
* @param probation Probation principles (contextual, caution)
|
|
72
|
+
* @returns Formatted lines (empty string if no principles)
|
|
73
|
+
*/
|
|
74
|
+
export declare function formatEvolutionPrinciples(active: EvolutionPrincipleEntry[], probation: EvolutionPrincipleEntry[]): string;
|
|
75
|
+
/**
|
|
76
|
+
* Assemble appendSystemContext from ordered parts.
|
|
77
|
+
*
|
|
78
|
+
* Content order (most important last):
|
|
79
|
+
* behavioral_constraints → project_context → working_memory →
|
|
80
|
+
* thinking_os → evolution_principles → core_principles
|
|
81
|
+
*
|
|
82
|
+
* Pure logic — string assembly only, no I/O.
|
|
83
|
+
*
|
|
84
|
+
* @param parts Ordered content parts (empty/undefined parts are skipped)
|
|
85
|
+
* @returns Assembled appendSystemContext (empty string if no parts)
|
|
86
|
+
*/
|
|
87
|
+
export declare function assembleAppendSystemContext(parts: AppendSystemContextParts): string;
|