@principles/host-runtime 0.8.2 → 0.9.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.
@@ -3,24 +3,32 @@ import { escapeXml } from '@principles/core/prompt-builder';
3
3
  import { loadPdConfigForPlugin } from './pd-config.js';
4
4
  import { buildActivePrinciplePromptContext, readPromptActivationCandidates, MAX_SHARED_DIAGNOSTIC_IDS } from './active-principle-prompt.js';
5
5
  import { readWorkspaceHostKindFacts } from './host-liveness-contract.js';
6
- export const PROMPT_INJECTION_TARGET_HOSTS = ['openclaw', 'codex'];
6
+ /** Single host roster (review P2): every consumer derives from this list, so a
7
+ * new host cannot fork route-unconfirmed per-host forecasts from the routes. */
8
+ export const PROMPT_INJECTION_TARGET_HOSTS = ['openclaw', 'codex', 'workbuddy'];
9
+ /** Runtime membership test derived from the roster (rc-2): the ONE guard for
10
+ * "is this untrusted host-kind string a host with a known prompt-injection
11
+ * route". Every consumer narrows with this — never by re-typing the literals
12
+ * (review R1: each re-typed list forked the roster and dropped WorkBuddy). */
13
+ export function isPromptInjectionTargetHost(kind) {
14
+ return typeof kind === 'string' && PROMPT_INJECTION_TARGET_HOSTS.includes(kind);
15
+ }
7
16
  export const PROMPT_INJECTION_UNIT = 'utf16_code_units';
8
17
  /**
9
18
  * THE one route formula (P4): Codex always injects through the shared route
10
- * (codex-adapter has no flag branch); OpenClaw routes by its
11
- * `abstraction_layer_v1` gate. Every consumer that needs "which route would
12
- * host X take" must call this — never re-type the ternary.
19
+ * (codex-adapter has no flag branch); so does WorkBuddy (workbuddy-adapter
20
+ * renders through the shared runtime unconditionally — it has no legacy
21
+ * route); OpenClaw routes by its `abstraction_layer_v1` gate. Every consumer
22
+ * that needs "which route would host X take" must call this — never re-type
23
+ * the ternary.
13
24
  */
14
25
  export function promptRouteForHost(hostKind, flagEnabled) {
15
- if (hostKind === 'codex')
26
+ if (hostKind === 'codex' || hostKind === 'workbuddy')
16
27
  return 'shared_render';
17
28
  return flagEnabled ? 'shared_render' : 'legacy_trim';
18
29
  }
19
30
  function standardHostRoutes(flagEnabled) {
20
- return [
21
- { hostKind: 'openclaw', route: promptRouteForHost('openclaw', flagEnabled) },
22
- { hostKind: 'codex', route: promptRouteForHost('codex', flagEnabled) },
23
- ];
31
+ return PROMPT_INJECTION_TARGET_HOSTS.map((hostKind) => ({ hostKind, route: promptRouteForHost(hostKind, flagEnabled) }));
24
32
  }
25
33
  /**
26
34
  * Resolve the effective route for this workspace. Pure read of existing
@@ -83,8 +91,8 @@ export function resolvePromptInjectionRouteDecision(input) {
83
91
  perHost: standardHostRoutes(flagEnabled),
84
92
  };
85
93
  }
86
- const knownKinds = facts.effectiveKinds.filter((kind) => kind === 'openclaw' || kind === 'codex');
87
- const unknownKinds = facts.effectiveKinds.filter((kind) => kind !== 'openclaw' && kind !== 'codex');
94
+ const knownKinds = facts.effectiveKinds.filter(isPromptInjectionTargetHost);
95
+ const unknownKinds = facts.effectiveKinds.filter((kind) => !isPromptInjectionTargetHost(kind));
88
96
  if (unknownKinds.length > 0) {
89
97
  return {
90
98
  status: 'unconfirmed',
@@ -92,7 +100,7 @@ export function resolvePromptInjectionRouteDecision(input) {
92
100
  declaredHostKinds: facts.declaredKinds,
93
101
  effectiveHostKinds: facts.effectiveKinds,
94
102
  unconfirmedReason: `unknown_host_kind_declared: ${unknownKinds.join(', ')} has no known prompt-injection route`,
95
- nextAction: 'pass an explicit target host (openclaw|codex) for this request, or remove the unknown host declaration',
103
+ nextAction: 'pass an explicit target host (openclaw|codex|workbuddy) for this request, or remove the unknown host declaration',
96
104
  perHost: standardHostRoutes(flagEnabled),
97
105
  };
98
106
  }
@@ -117,7 +125,7 @@ export function resolvePromptInjectionRouteDecision(input) {
117
125
  declaredHostKinds: facts.declaredKinds,
118
126
  effectiveHostKinds: facts.effectiveKinds,
119
127
  unconfirmedReason: 'no_host_facts_and_flag_off: no host declaration or host evidence exists, and the applicable hosts would take different routes',
120
- nextAction: 'pass an explicit target host (openclaw|codex), or let a host run once in this workspace so it persists its declaration',
128
+ nextAction: 'pass an explicit target host (openclaw|codex|workbuddy), or let a host run once in this workspace so it persists its declaration',
121
129
  perHost: standardHostRoutes(flagEnabled),
122
130
  };
123
131
  }
@@ -151,8 +159,8 @@ export function resolvePromptInjectionRouteDecision(input) {
151
159
  flagEnabled,
152
160
  declaredHostKinds: facts.declaredKinds,
153
161
  effectiveHostKinds: facts.effectiveKinds,
154
- unconfirmedReason: 'multi_host_routes_differ: openclaw would take the legacy list route and codex the shared route under the current flag-off config',
155
- nextAction: 'pass an explicit target host (openclaw|codex) for this request, or enable abstraction_layer_v1 so both hosts share one route',
162
+ unconfirmedReason: 'multi_host_routes_differ: openclaw would take the legacy list route while codex/workbuddy would take the shared route under the current flag-off config',
163
+ nextAction: 'pass an explicit target host (openclaw|codex|workbuddy) for this request, or enable abstraction_layer_v1 so all hosts share one route',
156
164
  perHost: standardHostRoutes(flagEnabled),
157
165
  };
158
166
  }
@@ -216,7 +224,13 @@ function isDiagnosticCapHit(ids) {
216
224
  */
217
225
  export async function buildLivePromptInjectionProjection(input) {
218
226
  const flagEnabled = computeFeatureFlagsFromConfig(loadPdConfigForPlugin(input.workspaceDir).effective).flags.abstraction_layer_v1?.enabled === true;
219
- const sharedRoute = input.targetHost === 'codex' ? true : flagEnabled;
227
+ // Route face follows THE one route formula (review P2-2: a re-typed
228
+ // codex-only ternary forecast WorkBuddy as legacy_trim while its adapter
229
+ // renders shared unconditionally). No explicit target (console callers)
230
+ // keeps the flag-gated workspace default.
231
+ const sharedRoute = input.targetHost === undefined
232
+ ? flagEnabled
233
+ : promptRouteForHost(input.targetHost, flagEnabled) === 'shared_render';
220
234
  if (sharedRoute) {
221
235
  const context = await buildActivePrinciplePromptContext(input);
222
236
  const oversized = context.oversizedActivationIds ?? [];
@@ -0,0 +1,24 @@
1
+ /**
2
+ * WorkBuddy worker status-mode semantics — host-runtime authority (mirrors
3
+ * the Codex Slice D placement): the CLI worker --status and any health
4
+ * surface share ONE mode evaluation, which only depends on host-neutral
5
+ * inputs (governance workspace directory, .pd/config.yaml flags, install
6
+ * manifest registration). The WorkBuddy evaluation has NO ingestion step to
7
+ * gate — the hook is the only evidence producer in the WorkBuddy MVP
8
+ * (SPEC workbuddy-host-adapter §1 Excluded: no transcript reads).
9
+ *
10
+ * SPEC §15 worker mode, evaluated WITHOUT executing anything (no lease, no
11
+ * LLM). `manual_action_required` means no Companion-registered worker serves
12
+ * this governance workspace — manual `pd diagnose` / `pd runtime
13
+ * internalization run-once` is the recovery route.
14
+ */
15
+ export type WorkbuddyWorkerMode = 'ready' | 'manual_action_required' | 'paused' | 'degraded';
16
+ export interface WorkbuddyWorkerStatusEvaluation {
17
+ readonly mode: WorkbuddyWorkerMode;
18
+ readonly reason?: string;
19
+ readonly nextAction?: string;
20
+ }
21
+ export declare function computeWorkbuddyWorkerStatusMode(input: {
22
+ workspaceDir: string;
23
+ registeredInInstallManifest: boolean;
24
+ }): WorkbuddyWorkerStatusEvaluation;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * WorkBuddy worker status-mode semantics — host-runtime authority (mirrors
3
+ * the Codex Slice D placement): the CLI worker --status and any health
4
+ * surface share ONE mode evaluation, which only depends on host-neutral
5
+ * inputs (governance workspace directory, .pd/config.yaml flags, install
6
+ * manifest registration). The WorkBuddy evaluation has NO ingestion step to
7
+ * gate — the hook is the only evidence producer in the WorkBuddy MVP
8
+ * (SPEC workbuddy-host-adapter §1 Excluded: no transcript reads).
9
+ *
10
+ * SPEC §15 worker mode, evaluated WITHOUT executing anything (no lease, no
11
+ * LLM). `manual_action_required` means no Companion-registered worker serves
12
+ * this governance workspace — manual `pd diagnose` / `pd runtime
13
+ * internalization run-once` is the recovery route.
14
+ */
15
+ import * as fs from 'node:fs';
16
+ import * as path from 'node:path';
17
+ import { loadPdConfigForPlugin } from './pd-config.js';
18
+ import { computeFeatureFlagsFromConfig } from '@principles/core/runtime-v2';
19
+ function directoryExists(dir) {
20
+ try {
21
+ return fs.statSync(dir).isDirectory();
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ export function computeWorkbuddyWorkerStatusMode(input) {
28
+ const workspaceDir = path.resolve(input.workspaceDir);
29
+ if (!directoryExists(workspaceDir)) {
30
+ return { mode: 'degraded', reason: 'workspace_missing', nextAction: 'The governance workspace directory does not exist; run the WorkBuddy setup flow to initialize it.' };
31
+ }
32
+ const config = loadPdConfigForPlugin(workspaceDir);
33
+ if (!config.ok) {
34
+ const [first] = config.errors;
35
+ return { mode: 'degraded', reason: `pd_config_invalid:${first?.reason ?? 'unknown'}`, nextAction: first?.nextAction ?? 'Repair the governance workspace .pd/config.yaml.' };
36
+ }
37
+ const { flags } = computeFeatureFlagsFromConfig(config.effective);
38
+ if (flags['host.workbuddy']?.enabled !== true) {
39
+ return { mode: 'paused', reason: 'host.workbuddy_disabled', nextAction: 'Set features.host.workbuddy.enabled=true in the governance workspace .pd/config.yaml to enable WorkBuddy PD behavior.' };
40
+ }
41
+ if (flags.internalization_auto_consumer?.enabled !== true) {
42
+ return { mode: 'paused', reason: 'internalization_auto_consumer_disabled', nextAction: 'Automatic execution is paused; manual commands remain available: pd diagnose, pd runtime internalization run-once.' };
43
+ }
44
+ if (!input.registeredInInstallManifest) {
45
+ return {
46
+ mode: 'manual_action_required',
47
+ reason: 'workspace_not_in_install_manifest',
48
+ nextAction: `No Companion worker is registered for this governance workspace. Manual path: pd diagnose --workspace "${workspaceDir}", then pd runtime internalization run-once.`,
49
+ };
50
+ }
51
+ return { mode: 'ready' };
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principles/host-runtime",
3
- "version": "0.8.2",
3
+ "version": "0.9.1",
4
4
  "description": "Shared host-neutral orchestration for Principles Disciple MVP-Core hook paths.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,7 +13,9 @@
13
13
  "./package.json": "./package.json"
14
14
  },
15
15
  "files": [
16
- "dist"
16
+ "dist",
17
+ "LICENSE",
18
+ "NOTICE"
17
19
  ],
18
20
  "scripts": {
19
21
  "build": "tsc",
@@ -21,22 +23,22 @@
21
23
  "lint": "eslint \"src/**/*.ts\""
22
24
  },
23
25
  "dependencies": {
24
- "@principles/core": "^1.291.3",
25
- "@principles/install-layout": "^0.2.7",
26
+ "@principles/core": "^1.292.1",
27
+ "@principles/install-layout": "^0.3.1",
26
28
  "better-sqlite3": "^13.0.3",
27
- "js-yaml": "^5.4.1"
29
+ "js-yaml": "^5.4.3"
28
30
  },
29
31
  "devDependencies": {
30
32
  "@types/better-sqlite3": "^9.6.0",
31
33
  "@types/node": "^26.4.1",
32
34
  "@types/js-yaml": "^4.0.9",
33
35
  "typescript": "^7.0.2",
34
- "vite": "^8.3.0",
36
+ "vite": "^8.3.4",
35
37
  "vitest": "^5.0.0"
36
38
  },
37
39
  "repository": {
38
40
  "type": "git",
39
41
  "url": "git+https://github.com/csuzngjh/principles.git"
40
42
  },
41
- "license": "MIT"
43
+ "license": "Apache-2.0"
42
44
  }