@principles/host-runtime 0.7.11 → 0.7.12
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.
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PromptSelectionPolicy } from '@principles/core/runtime-v2';
|
|
1
2
|
export interface PromptInjectionProjection {
|
|
2
3
|
/** Which real injection route the workspace is on (abstraction_layer_v1 flag). */
|
|
3
4
|
route: 'legacy_trim' | 'shared_render';
|
|
@@ -13,6 +14,38 @@ export interface PromptInjectionProjection {
|
|
|
13
14
|
injectedPrincipleIds: string[];
|
|
14
15
|
injectedActivationIds: string[];
|
|
15
16
|
warnings: string[];
|
|
17
|
+
/**
|
|
18
|
+
* PRI-935: the budget-packing policy that produced THIS projection
|
|
19
|
+
* (`legacy_fifo_prefix_v1` | `fair_rotation_v1`). The console must not
|
|
20
|
+
* describe a fair-rotation workspace in FIFO terms, so the policy travels
|
|
21
|
+
* with the projection instead of being re-derived by each consumer.
|
|
22
|
+
*/
|
|
23
|
+
selectionPolicy: PromptSelectionPolicy;
|
|
24
|
+
/**
|
|
25
|
+
* PRI-935: activations that the production route will inject under SOME
|
|
26
|
+
* round key, i.e. activations that are merely rotated out of the current
|
|
27
|
+
* window rather than structurally starved.
|
|
28
|
+
*
|
|
29
|
+
* Reachability is a property of the SELECTION POLICY, not of any single
|
|
30
|
+
* round: a fair-rotation workspace reaches every non-oversized eligible
|
|
31
|
+
* entry within N turns. So this is computed under the production policy
|
|
32
|
+
* (fair rotation whenever the route actually rotates) and is therefore
|
|
33
|
+
* non-empty even for a caller that supplied no round key — which is
|
|
34
|
+
* exactly the console's situation. Empty only when the production route
|
|
35
|
+
* genuinely does not rotate (shared route) or nothing is eligible.
|
|
36
|
+
*/
|
|
37
|
+
eventuallyInjectedActivationIds: string[];
|
|
38
|
+
/** Number of eligible candidates that reached the selector. */
|
|
39
|
+
eligibleCount: number;
|
|
40
|
+
/**
|
|
41
|
+
* PRI-935: whether the PRODUCTION route for this workspace rotates.
|
|
42
|
+
* The forecast's own `selectionPolicy` answers "which policy did THIS
|
|
43
|
+
* projection run", which is legacy for a console that holds no session
|
|
44
|
+
* round key; this answers "will the agent's real injection rotate",
|
|
45
|
+
* which is what determines whether an out-of-window activation is queued
|
|
46
|
+
* or starved. They differ exactly in the case this whole fix is about.
|
|
47
|
+
*/
|
|
48
|
+
productionRotates: boolean;
|
|
16
49
|
}
|
|
17
50
|
/**
|
|
18
51
|
* PR #1844 follow-up: forecast the prompt-injection budget from the SAME
|
|
@@ -31,8 +64,25 @@ export interface PromptInjectionProjection {
|
|
|
31
64
|
* candidates against the legacy evolution ledger before trimming; the
|
|
32
65
|
* console cannot replay that reducer read-only, so this projection may
|
|
33
66
|
* forecast slightly MORE consumption than the real injection (never less).
|
|
67
|
+
*
|
|
68
|
+
* PRI-935 — round-key alignment. The plugin derives its fair-rotation round
|
|
69
|
+
* key from the CURRENT session's turn ordinal
|
|
70
|
+
* (`nextSessionTurnOrdinal`), which the console has no access to. Passing no
|
|
71
|
+
* key therefore does NOT make the forecast "more conservative": it selects a
|
|
72
|
+
* DIFFERENT policy (`legacy_fifo_prefix_v1`) than the one production runs, and
|
|
73
|
+
* legacy FIFO structurally excludes the newest activation from every
|
|
74
|
+
* truncated selection. The console must either supply the round key or
|
|
75
|
+
* report the policy honestly, so this function takes the key as an explicit
|
|
76
|
+
* input and exposes `selectionPolicy` on the result.
|
|
34
77
|
*/
|
|
35
78
|
export declare function buildLivePromptInjectionProjection(input: {
|
|
36
79
|
workspaceDir: string;
|
|
37
80
|
excludePrincipleIds?: ReadonlySet<string>;
|
|
81
|
+
/**
|
|
82
|
+
* PRI-935: the fair-rotation round key the production route is using this
|
|
83
|
+
* turn. Callers that hold one (the plugin) pass it; callers that do not
|
|
84
|
+
* (the console) omit it and MUST read `selectionPolicy` off the result
|
|
85
|
+
* rather than assuming FIFO.
|
|
86
|
+
*/
|
|
87
|
+
roundKey?: number;
|
|
38
88
|
}): Promise<PromptInjectionProjection>;
|
|
@@ -2,6 +2,28 @@ import { RUNTIME_V2_PRINCIPLE_BUDGET, computeFeatureFlagsFromConfig, trimToBudge
|
|
|
2
2
|
import { escapeXml } from '@principles/core/prompt-builder';
|
|
3
3
|
import { loadPdConfigForPlugin } from './pd-config.js';
|
|
4
4
|
import { buildActivePrinciplePromptContext, readPromptActivationCandidates } from './active-principle-prompt.js';
|
|
5
|
+
/**
|
|
6
|
+
* PRI-935: which activations a fair-rotation selector injects across the
|
|
7
|
+
* whole ring. The plugin's round key advances by one per recorded user turn
|
|
8
|
+
* within a continuously advancing session, so N consecutive turns cover all
|
|
9
|
+
* N ring positions: an activation absent from the current window but present
|
|
10
|
+
* in ANY round is reachable, not starved.
|
|
11
|
+
*
|
|
12
|
+
* Kept here (not in the console) so the production selector's own arithmetic
|
|
13
|
+
* stays the single authority for what "reachable" means.
|
|
14
|
+
*/
|
|
15
|
+
function collectReachableActivationIds(principles, budget) {
|
|
16
|
+
const reachable = new Set();
|
|
17
|
+
for (let roundKey = 0; roundKey < principles.length; roundKey += 1) {
|
|
18
|
+
const result = trimToBudget(principles, budget, escapeXml, roundKey);
|
|
19
|
+
for (const principleId of result.injectedIds) {
|
|
20
|
+
const match = principles.find((p) => p.principleId === principleId);
|
|
21
|
+
if (match)
|
|
22
|
+
reachable.add(match.activationId);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return [...reachable];
|
|
26
|
+
}
|
|
5
27
|
/**
|
|
6
28
|
* PR #1844 follow-up: forecast the prompt-injection budget from the SAME
|
|
7
29
|
* projection the agent injection itself uses, routed by the same
|
|
@@ -19,6 +41,16 @@ import { buildActivePrinciplePromptContext, readPromptActivationCandidates } fro
|
|
|
19
41
|
* candidates against the legacy evolution ledger before trimming; the
|
|
20
42
|
* console cannot replay that reducer read-only, so this projection may
|
|
21
43
|
* forecast slightly MORE consumption than the real injection (never less).
|
|
44
|
+
*
|
|
45
|
+
* PRI-935 — round-key alignment. The plugin derives its fair-rotation round
|
|
46
|
+
* key from the CURRENT session's turn ordinal
|
|
47
|
+
* (`nextSessionTurnOrdinal`), which the console has no access to. Passing no
|
|
48
|
+
* key therefore does NOT make the forecast "more conservative": it selects a
|
|
49
|
+
* DIFFERENT policy (`legacy_fifo_prefix_v1`) than the one production runs, and
|
|
50
|
+
* legacy FIFO structurally excludes the newest activation from every
|
|
51
|
+
* truncated selection. The console must either supply the round key or
|
|
52
|
+
* report the policy honestly, so this function takes the key as an explicit
|
|
53
|
+
* input and exposes `selectionPolicy` on the result.
|
|
22
54
|
*/
|
|
23
55
|
export async function buildLivePromptInjectionProjection(input) {
|
|
24
56
|
const sharedRoute = computeFeatureFlagsFromConfig(loadPdConfigForPlugin(input.workspaceDir).effective).flags.abstraction_layer_v1?.enabled === true;
|
|
@@ -32,6 +64,13 @@ export async function buildLivePromptInjectionProjection(input) {
|
|
|
32
64
|
injectedPrincipleIds: context.principleIds,
|
|
33
65
|
injectedActivationIds: context.activationIds,
|
|
34
66
|
warnings: context.warnings,
|
|
67
|
+
// The shared production path deliberately passes no round key
|
|
68
|
+
// (host-runtime/src/index.ts), so this route genuinely does not rotate
|
|
69
|
+
// and claims nothing about eventual reachability.
|
|
70
|
+
selectionPolicy: context.selectionPolicy ?? 'legacy_fifo_prefix_v1',
|
|
71
|
+
eventuallyInjectedActivationIds: [],
|
|
72
|
+
eligibleCount: context.eligibleCount ?? 0,
|
|
73
|
+
productionRotates: false,
|
|
35
74
|
};
|
|
36
75
|
}
|
|
37
76
|
const candidates = await readPromptActivationCandidates(input);
|
|
@@ -45,10 +84,23 @@ export async function buildLivePromptInjectionProjection(input) {
|
|
|
45
84
|
injectedPrincipleIds: [],
|
|
46
85
|
injectedActivationIds: [],
|
|
47
86
|
warnings,
|
|
87
|
+
selectionPolicy: 'legacy_fifo_prefix_v1',
|
|
88
|
+
eventuallyInjectedActivationIds: [],
|
|
89
|
+
eligibleCount: 0,
|
|
90
|
+
productionRotates: false,
|
|
48
91
|
};
|
|
49
92
|
}
|
|
50
|
-
|
|
93
|
+
// PRI-935: pass the caller's round key so the forecast runs the SAME
|
|
94
|
+
// packing policy production runs this turn.
|
|
95
|
+
const trimmed = trimToBudget(principles, RUNTIME_V2_PRINCIPLE_BUDGET, escapeXml, input.roundKey);
|
|
51
96
|
const injected = principles.filter((p) => trimmed.injectedIds.has(p.principleId));
|
|
97
|
+
// PRI-935: the legacy_trim route IS the OpenClaw plugin-local route, which
|
|
98
|
+
// rotates on every recorded user turn (PRI-904). Reachability is therefore
|
|
99
|
+
// computed under fair rotation regardless of whether THIS forecast holds a
|
|
100
|
+
// round key — a console without a session key must still be able to tell
|
|
101
|
+
// "queued behind rotation" apart from "structurally starved", otherwise it
|
|
102
|
+
// keeps telling the Owner to deactivate healthy principles.
|
|
103
|
+
const eventuallyInjectedActivationIds = collectReachableActivationIds(principles, RUNTIME_V2_PRINCIPLE_BUDGET);
|
|
52
104
|
return {
|
|
53
105
|
route: 'legacy_trim',
|
|
54
106
|
budget: RUNTIME_V2_PRINCIPLE_BUDGET,
|
|
@@ -57,5 +109,9 @@ export async function buildLivePromptInjectionProjection(input) {
|
|
|
57
109
|
injectedPrincipleIds: [...trimmed.injectedIds],
|
|
58
110
|
injectedActivationIds: injected.map((p) => p.activationId),
|
|
59
111
|
warnings,
|
|
112
|
+
selectionPolicy: trimmed.selectionPolicy,
|
|
113
|
+
eventuallyInjectedActivationIds,
|
|
114
|
+
eligibleCount: trimmed.eligibleCount,
|
|
115
|
+
productionRotates: true,
|
|
60
116
|
};
|
|
61
117
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/host-runtime",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.12",
|
|
4
4
|
"description": "Shared host-neutral orchestration for Principles Disciple MVP-Core hook paths.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"lint": "eslint \"src/**/*.ts\""
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@principles/core": "^1.287.
|
|
24
|
+
"@principles/core": "^1.287.7",
|
|
25
25
|
"@principles/install-layout": "^0.2.7",
|
|
26
26
|
"better-sqlite3": "^13.0.3",
|
|
27
27
|
"js-yaml": "^5.4.1"
|