@principles/host-runtime 0.6.0 → 0.7.1
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.
|
@@ -8,8 +8,10 @@ type Degradation = {
|
|
|
8
8
|
reason: string;
|
|
9
9
|
nextAction: string;
|
|
10
10
|
};
|
|
11
|
-
/**
|
|
11
|
+
/** seed/owner_promoted 词进入高精度 deterministic path 的权重阈值 */
|
|
12
12
|
export declare const HIGH_PRECISION_LEARNED_WEIGHT = 0.7;
|
|
13
|
+
/** PRI-788 G3: llm 学习词 earned precision 的 TP 门槛(且要求零 FP) */
|
|
14
|
+
export declare const EARNED_PRECISION_MIN_TP = 3;
|
|
13
15
|
/** 高精度纠正短语 overlay(已验证的确定性 STRONG 路径,不属于 learner seed 集) */
|
|
14
16
|
export declare const HIGH_PRECISION_CORRECTION_OVERLAY: readonly (readonly [string, number])[];
|
|
15
17
|
/** empathy seed overlay(检测行为不变) */
|
|
@@ -63,13 +63,21 @@ function rowField(row, key) {
|
|
|
63
63
|
}
|
|
64
64
|
// ─── Shared correction keyword store (host-neutral extraction) ──────────────
|
|
65
65
|
const KEYWORD_STORE_FILE = 'correction_keywords.json';
|
|
66
|
-
/**
|
|
66
|
+
/** seed/owner_promoted 词进入高精度 deterministic path 的权重阈值 */
|
|
67
67
|
export const HIGH_PRECISION_LEARNED_WEIGHT = 0.7;
|
|
68
|
+
/** PRI-788 G3: llm 学习词 earned precision 的 TP 门槛(且要求零 FP) */
|
|
69
|
+
export const EARNED_PRECISION_MIN_TP = 3;
|
|
68
70
|
/** 高精度纠正短语 overlay(已验证的确定性 STRONG 路径,不属于 learner seed 集) */
|
|
69
71
|
export const HIGH_PRECISION_CORRECTION_OVERLAY = [
|
|
70
72
|
['这是错的', 0.9],
|
|
71
73
|
['不要自作主张', 0.9],
|
|
72
74
|
['不应该这么做', 0.9],
|
|
75
|
+
// PRI-788 G3(Owner 批准清单):中文高精度纠正句式
|
|
76
|
+
['我说的是', 0.9],
|
|
77
|
+
['不是让你', 0.85],
|
|
78
|
+
['谁让你', 0.85],
|
|
79
|
+
['又搞错', 0.8],
|
|
80
|
+
['都说了', 0.8],
|
|
73
81
|
];
|
|
74
82
|
/** empathy seed overlay(检测行为不变) */
|
|
75
83
|
export const EMPATHY_SEED_OVERLAY = [
|
|
@@ -83,6 +91,14 @@ function isValidLearnedKeyword(v) {
|
|
|
83
91
|
return false;
|
|
84
92
|
if (typeof k.weight !== 'number' || !Number.isFinite(k.weight))
|
|
85
93
|
return false;
|
|
94
|
+
// earned precision 的计数是安全门(tp >= 3 才升 high),必须先约束数值形状:
|
|
95
|
+
// `correction_keywords.json` 里被篡改/损坏的 `truePositiveCount: 3.1` 若放行,
|
|
96
|
+
// 会让只有 3 条样本(甚至根本没有样本)的词项挤进 high 确定性路径。
|
|
97
|
+
// 缺失按可选字段语义放行,存在则必须是【非负安全整数】。
|
|
98
|
+
const isCount = (value) => value === undefined ||
|
|
99
|
+
(typeof value === 'number' && Number.isSafeInteger(value) && value >= 0);
|
|
100
|
+
if (!isCount(k.truePositiveCount) || !isCount(k.falsePositiveCount))
|
|
101
|
+
return false;
|
|
86
102
|
return k.source === 'seed' || k.source === 'llm' || k.source === 'user';
|
|
87
103
|
}
|
|
88
104
|
function mapLearnedSource(source) {
|
|
@@ -92,9 +108,18 @@ function mapLearnedSource(source) {
|
|
|
92
108
|
return 'owner_promoted';
|
|
93
109
|
return 'seed';
|
|
94
110
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
111
|
+
/**
|
|
112
|
+
* PRI-788 G3 earned precision:llm 学习词不再恒为 ambiguous——确认证据达标
|
|
113
|
+
* (TP ≥ 3 且零 FP)时升 high,出现任一 FP 即降回 ambiguous。安全姿态不变:
|
|
114
|
+
* LLM 单次建议无法自升 STRONG,必须积累 LLM 确认的正例。owner_promoted 仍按
|
|
115
|
+
* 权重判定(Owner 显式决策不受证据门槛约束)。
|
|
116
|
+
*/
|
|
117
|
+
function precisionFor(source, weight, stats) {
|
|
118
|
+
if (source === 'llm') {
|
|
119
|
+
const tp = stats?.truePositiveCount ?? 0;
|
|
120
|
+
const fp = stats?.falsePositiveCount ?? 0;
|
|
121
|
+
return tp >= EARNED_PRECISION_MIN_TP && fp === 0 ? 'high' : 'ambiguous';
|
|
122
|
+
}
|
|
98
123
|
return weight >= HIGH_PRECISION_LEARNED_WEIGHT ? 'high' : 'ambiguous';
|
|
99
124
|
}
|
|
100
125
|
function projectLearnedStore(raw) {
|
|
@@ -116,7 +141,7 @@ function projectLearnedStore(raw) {
|
|
|
116
141
|
term,
|
|
117
142
|
category: 'correction',
|
|
118
143
|
weight,
|
|
119
|
-
precision: precisionFor(kw.source, weight),
|
|
144
|
+
precision: precisionFor(kw.source, weight, kw),
|
|
120
145
|
source: mapLearnedSource(kw.source),
|
|
121
146
|
};
|
|
122
147
|
if (kw.source === 'llm')
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type HostEvent, type HostEventResult } from '@principles/core/host';
|
|
1
|
+
import { type HostEvent, type HostEventResult, type HostEventEmitter } from '@principles/core/host';
|
|
2
2
|
import { buildActivePrinciplePromptContext } from './active-principle-prompt.js';
|
|
3
3
|
import { type RuleContextProvider, type RuleInputEnrichmentProvider } from './production-rulehost-gate.js';
|
|
4
4
|
import type { RuleImplementationRuntime } from './rule-implementation-runtime.js';
|
|
@@ -67,4 +67,13 @@ export declare function createProductionHostRuntime(options?: Partial<Pick<HostR
|
|
|
67
67
|
hostKind?: GovernanceHostKind;
|
|
68
68
|
/** PRI-634-F: host-declared tool semantics supplied by the constructing host adapter. */
|
|
69
69
|
toolSemantics?: ToolSemanticRegistry;
|
|
70
|
+
/**
|
|
71
|
+
* PRI-750: optional event emission port. When present, the shared-path
|
|
72
|
+
* handlers record injection/tool events carrying the host's natural
|
|
73
|
+
* turn/tool ids (turnId/toolCallId) in the same events_*.jsonl format as
|
|
74
|
+
* the OpenClaw path (whose events additionally bind to
|
|
75
|
+
* assistant_turns.run_id). Only the Codex host adapter wires this; the
|
|
76
|
+
* OpenClaw plugin path owns its own emission.
|
|
77
|
+
*/
|
|
78
|
+
events?: HostEventEmitter;
|
|
70
79
|
}): HostRuntime;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isHostEvent, isHostEventResult, } from '@principles/core/host';
|
|
2
|
+
import { RUNTIME_V2_PRINCIPLE_BUDGET } from '@principles/core/runtime-v2';
|
|
2
3
|
import { buildActivePrinciplePromptContext } from './active-principle-prompt.js';
|
|
3
4
|
import { createProductionRuleHostGate } from './production-rulehost-gate.js';
|
|
4
5
|
import { createProductionPainEvidenceHandler } from './production-pain-evidence.js';
|
|
@@ -171,6 +172,7 @@ export function createProductionHostRuntime(options = {}) {
|
|
|
171
172
|
...(options.painEnrichmentProvider ? { painEnrichmentProvider: options.painEnrichmentProvider } : {}),
|
|
172
173
|
...(options.painDatabaseFactory ? { painDatabaseFactory: options.painDatabaseFactory } : {}),
|
|
173
174
|
...(options.hostKind ? { hostKind: options.hostKind } : {}),
|
|
175
|
+
...(options.events ? { events: options.events } : {}),
|
|
174
176
|
}),
|
|
175
177
|
beforeToolCall: options.beforeToolCall ?? productionGate,
|
|
176
178
|
async beforePromptBuild(event) {
|
|
@@ -178,12 +180,47 @@ export function createProductionHostRuntime(options = {}) {
|
|
|
178
180
|
workspaceDir: event.context.workspaceDir,
|
|
179
181
|
excludePrincipleIds: options.promptExcludePrincipleIds?.(event),
|
|
180
182
|
});
|
|
181
|
-
|
|
182
|
-
|
|
183
|
+
// PRI-750: record the injection event on the shared path with the host
|
|
184
|
+
// turn id (Codex turn_id → runId) so receipt events carry a turn-level
|
|
185
|
+
// anchor in the same events_*.jsonl format as the OpenClaw path. The
|
|
186
|
+
// OpenClaw plugin additionally persists these to assistant_turns.run_id;
|
|
187
|
+
// the Codex DB-side anchor is a follow-up. Optional port — absent means
|
|
188
|
+
// no-op (the OpenClaw plugin path emits this event itself). Emission
|
|
189
|
+
// failure must not block the prompt result — it degrades to an
|
|
190
|
+
// observable warning (rc-9).
|
|
191
|
+
const emissionWarnings = [];
|
|
192
|
+
try {
|
|
193
|
+
options.events?.recordRuntimeV2ActivationsInjected({
|
|
194
|
+
sessionId: event.context.sessionId,
|
|
195
|
+
workspaceDir: event.context.workspaceDir,
|
|
196
|
+
principleIds: prompt.principleIds,
|
|
197
|
+
activationIds: prompt.activationIds,
|
|
198
|
+
artifactIds: prompt.artifactIds,
|
|
199
|
+
injectedCount: prompt.principleIds.length,
|
|
200
|
+
skippedWarnings: prompt.warnings,
|
|
201
|
+
injectedCharCount: prompt.additionalContext.length,
|
|
202
|
+
budget: RUNTIME_V2_PRINCIPLE_BUDGET,
|
|
203
|
+
...(prompt.truncated !== undefined ? { v2Truncated: prompt.truncated } : {}),
|
|
204
|
+
...(event.context.turnId !== undefined ? { runId: event.context.turnId } : {}),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
emissionWarnings.push(`receipt_event_write_failed:${err instanceof Error ? err.message.slice(0, 200) : String(err).slice(0, 200)}`);
|
|
209
|
+
}
|
|
210
|
+
// PRI-750 review: a custom beforePromptBuild must not swallow the
|
|
211
|
+
// receipt-emission warnings captured above (rc-9) — merge them into the
|
|
212
|
+
// handler result instead of replacing the result wholesale.
|
|
213
|
+
if (options.beforePromptBuild) {
|
|
214
|
+
const custom = await options.beforePromptBuild(event, prompt);
|
|
215
|
+
if (emissionWarnings.length === 0)
|
|
216
|
+
return custom;
|
|
217
|
+
return { ...custom, warnings: [...(custom.warnings ?? []), ...emissionWarnings] };
|
|
218
|
+
}
|
|
183
219
|
return {
|
|
184
220
|
decision: prompt.additionalContext.length > 0 ? 'modify' : 'allow',
|
|
185
221
|
source: event.source,
|
|
186
222
|
...(prompt.additionalContext.length > 0 ? { additionalContext: prompt.additionalContext } : {}),
|
|
223
|
+
...(emissionWarnings.length > 0 ? { warnings: emissionWarnings } : {}),
|
|
187
224
|
};
|
|
188
225
|
},
|
|
189
226
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import type { GovernanceHostKind } from '@principles/core/runtime-v2';
|
|
3
|
-
import type { HostEvent, HostEventResult } from '@principles/core/host';
|
|
3
|
+
import type { HostEvent, HostEventEmitter, HostEventResult } from '@principles/core/host';
|
|
4
4
|
/** Shared with the governance admission path so both gate tool failures identically. */
|
|
5
5
|
export declare const PRODUCTION_WRITE_TOOLS: ReadonlySet<string>;
|
|
6
6
|
export interface PainEvidenceEntry {
|
|
@@ -85,6 +85,7 @@ export declare function createProductionPainEvidenceHandler(options?: {
|
|
|
85
85
|
painEnrichmentProvider?: PainEnrichmentProvider;
|
|
86
86
|
painDatabaseFactory?: PainDatabaseFactory;
|
|
87
87
|
hostKind?: GovernanceHostKind;
|
|
88
|
+
events?: HostEventEmitter;
|
|
88
89
|
}): (event: HostEvent) => Promise<HostEventResult>;
|
|
89
90
|
export declare function resetProductionPainCooldownForTest(): void;
|
|
90
91
|
export declare function productionPainCooldownEntryCountForTest(): number;
|
|
@@ -234,9 +234,31 @@ function hasCanonicalSchema(db) {
|
|
|
234
234
|
*/
|
|
235
235
|
export function createProductionPainEvidenceHandler(options = {}) {
|
|
236
236
|
return async (event) => {
|
|
237
|
+
// PRI-750: shared-path tool event with the host's natural turn/tool ids
|
|
238
|
+
// (Codex turn_id → runId, tool_use_id → toolCallId) so receipt events
|
|
239
|
+
// carry a turn-level anchor in the same events_*.jsonl format as the
|
|
240
|
+
// OpenClaw path. Emitted BEFORE the trajectory-db availability gate so
|
|
241
|
+
// the receipt chain stays observable even when the trajectory store is
|
|
242
|
+
// unavailable. Emission failure must not block the tool result — it
|
|
243
|
+
// degrades to an observable warning (rc-9). Optional port: absent means
|
|
244
|
+
// no-op (the OpenClaw plugin path emits its own tool events).
|
|
245
|
+
let emissionWarning;
|
|
246
|
+
try {
|
|
247
|
+
const earlyOutcome = normalizeOutcome(event);
|
|
248
|
+
options.events?.recordToolCall(event.context.sessionId, {
|
|
249
|
+
toolName: event.context.toolName ?? '',
|
|
250
|
+
...(earlyOutcome.error ? { error: earlyOutcome.error } : {}),
|
|
251
|
+
...(earlyOutcome.exitCode !== 0 ? { exitCode: earlyOutcome.exitCode } : {}),
|
|
252
|
+
...(event.context.turnId !== undefined ? { runId: event.context.turnId } : {}),
|
|
253
|
+
...(event.context.toolCallId !== undefined ? { toolCallId: event.context.toolCallId } : {}),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
emissionWarning = `receipt_event_write_failed:${err instanceof Error ? err.message.slice(0, 200) : String(err).slice(0, 200)}`;
|
|
258
|
+
}
|
|
237
259
|
const dbPath = path.join(event.context.workspaceDir, '.state', 'trajectory.db');
|
|
238
260
|
if (!fs.existsSync(dbPath)) {
|
|
239
|
-
return { decision: 'observe', source: event.source, warnings: ['trajectory_db_not_found'], metadata: { outcome: 'unavailable', admitted: false, duplicate: false, nextAction: 'initialize the selected PD workspace before retrying the hook' } };
|
|
261
|
+
return { decision: 'observe', source: event.source, warnings: [...(emissionWarning ? [emissionWarning] : []), 'trajectory_db_not_found'], metadata: { outcome: 'unavailable', admitted: false, duplicate: false, nextAction: 'initialize the selected PD workspace before retrying the hook' } };
|
|
240
262
|
}
|
|
241
263
|
let db;
|
|
242
264
|
try {
|
|
@@ -245,7 +267,7 @@ export function createProductionPainEvidenceHandler(options = {}) {
|
|
|
245
267
|
if (!hasCanonicalSchema(db)) {
|
|
246
268
|
db.close();
|
|
247
269
|
db = undefined;
|
|
248
|
-
return { decision: 'observe', source: event.source, warnings: ['trajectory_schema_invalid'], metadata: { outcome: 'unavailable', admitted: false, duplicate: false, nextAction: 'run the supported PD workspace migration' } };
|
|
270
|
+
return { decision: 'observe', source: event.source, warnings: [...(emissionWarning ? [emissionWarning] : []), 'trajectory_schema_invalid'], metadata: { outcome: 'unavailable', admitted: false, duplicate: false, nextAction: 'run the supported PD workspace migration' } };
|
|
249
271
|
}
|
|
250
272
|
}
|
|
251
273
|
catch (error) {
|
|
@@ -253,7 +275,7 @@ export function createProductionPainEvidenceHandler(options = {}) {
|
|
|
253
275
|
db?.close();
|
|
254
276
|
}
|
|
255
277
|
catch { /* best-effort cleanup of an unusable handle */ }
|
|
256
|
-
return { decision: 'observe', source: event.source, warnings: [`trajectory_database_unavailable:${error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200)}`], metadata: { outcome: 'unavailable', admitted: false, duplicate: false, nextAction: 'inspect or repair the selected PD trajectory database' } };
|
|
278
|
+
return { decision: 'observe', source: event.source, warnings: [...(emissionWarning ? [emissionWarning] : []), `trajectory_database_unavailable:${error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200)}`], metadata: { outcome: 'unavailable', admitted: false, duplicate: false, nextAction: 'inspect or repair the selected PD trajectory database' } };
|
|
257
279
|
}
|
|
258
280
|
const warnings = [];
|
|
259
281
|
let enrichment;
|
|
@@ -269,7 +291,7 @@ export function createProductionPainEvidenceHandler(options = {}) {
|
|
|
269
291
|
db.close();
|
|
270
292
|
}
|
|
271
293
|
catch { /* no business write occurred */ }
|
|
272
|
-
return { decision: 'observe', source: event.source, warnings: warnings.length > 0 ? warnings : ['pain_enrichment_invalid'], metadata: { outcome: 'unavailable', admitted: false, duplicate: false, nextAction: 'inspect host pain enrichment input' } };
|
|
294
|
+
return { decision: 'observe', source: event.source, warnings: [...(emissionWarning ? [emissionWarning] : []), ...(warnings.length > 0 ? warnings : ['pain_enrichment_invalid'])], metadata: { outcome: 'unavailable', admitted: false, duplicate: false, nextAction: 'inspect host pain enrichment input' } };
|
|
273
295
|
}
|
|
274
296
|
const outcome = normalizeOutcome(event);
|
|
275
297
|
const sanitizedParams = sanitizeToolParams(outcome.params, event.context.workspaceDir);
|
|
@@ -342,7 +364,7 @@ export function createProductionPainEvidenceHandler(options = {}) {
|
|
|
342
364
|
}
|
|
343
365
|
}
|
|
344
366
|
catch (error) {
|
|
345
|
-
return { decision: 'observe', source: event.source, warnings: [`trajectory_write_failed:${error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200)}`], metadata: { outcome: outcome.failure ? 'failure' : 'success', admitted: false, duplicate: false, nextAction: 'inspect the workspace trajectory database and retry' } };
|
|
367
|
+
return { decision: 'observe', source: event.source, warnings: [...(emissionWarning ? [emissionWarning] : []), `trajectory_write_failed:${error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200)}`], metadata: { outcome: outcome.failure ? 'failure' : 'success', admitted: false, duplicate: false, nextAction: 'inspect the workspace trajectory database and retry' } };
|
|
346
368
|
}
|
|
347
369
|
finally {
|
|
348
370
|
try {
|
|
@@ -351,12 +373,17 @@ export function createProductionPainEvidenceHandler(options = {}) {
|
|
|
351
373
|
catch { /* write result already determined; cleanup is best-effort */ }
|
|
352
374
|
}
|
|
353
375
|
const effectiveAdmitted = admitted || duplicateAdmitted;
|
|
354
|
-
return {
|
|
376
|
+
return {
|
|
377
|
+
decision: 'observe',
|
|
378
|
+
source: event.source,
|
|
379
|
+
...(emissionWarning ? { warnings: [emissionWarning] } : {}),
|
|
380
|
+
metadata: {
|
|
355
381
|
eventId, painId: effectiveAdmitted ? painId : null, outcome: outcome.failure ? 'failure' : 'success', admitted: effectiveAdmitted, duplicate,
|
|
356
382
|
sourceKind, failureSource: sourceObservation.failureSource ?? null, triggerOutcome: trigger.outcome,
|
|
357
383
|
triggerReason: trigger.reason, painScore, isRisky, relativePath, agentId: enrichment.agentId ?? null,
|
|
358
384
|
evidence: enrichment.evidence ?? [],
|
|
359
|
-
}
|
|
385
|
+
}
|
|
386
|
+
};
|
|
360
387
|
};
|
|
361
388
|
}
|
|
362
389
|
export function resetProductionPainCooldownForTest() {
|