create-principles-disciple 1.146.8 → 1.147.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/_release/product-identity.json +1 -1
- package/codex-adapter/dist/ingestion/explicit-owner-statement.d.ts +6 -0
- package/codex-adapter/dist/ingestion/explicit-owner-statement.js +6 -0
- package/codex-adapter/dist/pd-hook.js +5 -2
- package/codex-adapter/dist/worker/workspace-worker.d.ts +13 -1
- package/codex-adapter/dist/worker/workspace-worker.js +26 -3
- package/codex-adapter/package.json +1 -1
- package/console/dist/server/models/ExaminerEvidenceModel.d.ts +159 -0
- package/console/dist/server/models/ExaminerEvidenceModel.js +274 -0
- package/console/dist/server/models/ReceiptsConsoleModel.js +15 -1
- package/console/dist/server/routes/examiner-executor.d.ts +40 -4
- package/console/dist/server/routes/examiner-executor.js +54 -45
- package/console/dist/server/routes/examiner-runs.d.ts +196 -0
- package/console/dist/server/routes/examiner-runs.js +938 -0
- package/console/dist/server/routes/examiner-study-inputs.d.ts +147 -0
- package/console/dist/server/routes/examiner-study-inputs.js +263 -0
- package/console/dist/server/routes/examiner.d.ts +26 -17
- package/console/dist/server/routes/examiner.js +261 -12
- package/console/dist/types.d.ts +1 -0
- package/console/dist/web/assets/app.css +92 -0
- package/console/dist/web/assets/app.js +38 -32
- package/console/lab/lib/challenge-openclaw-host.mjs +17 -0
- package/console/lab/lib/challenge-runner.mjs +9 -0
- package/console/lab/lib/examiner-study.mjs +55 -0
- package/core/dist/runtime-v2/index.d.ts +2 -2
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +1 -1
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/pain-ingress-payload.d.ts +13 -1
- package/core/dist/runtime-v2/pain-ingress-payload.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-ingress-payload.js +21 -2
- package/core/dist/runtime-v2/pain-ingress-payload.js.map +1 -1
- package/core/dist/runtime-v2/pain-ingress.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-ingress.js +3 -0
- package/core/dist/runtime-v2/pain-ingress.js.map +1 -1
- package/core/dist/runtime-v2/signal-collector/confidence-gate.d.ts +35 -0
- package/core/dist/runtime-v2/signal-collector/confidence-gate.d.ts.map +1 -0
- package/core/dist/runtime-v2/signal-collector/confidence-gate.js +48 -0
- package/core/dist/runtime-v2/signal-collector/confidence-gate.js.map +1 -0
- package/core/dist/runtime-v2/signal-collector/index.d.ts +1 -0
- package/core/dist/runtime-v2/signal-collector/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/signal-collector/index.js +1 -0
- package/core/dist/runtime-v2/signal-collector/index.js.map +1 -1
- package/core/dist/runtime-v2/signal-collector/keyword-stage.d.ts +4 -6
- package/core/dist/runtime-v2/signal-collector/keyword-stage.d.ts.map +1 -1
- package/core/dist/runtime-v2/signal-collector/keyword-stage.js +100 -9
- package/core/dist/runtime-v2/signal-collector/keyword-stage.js.map +1 -1
- package/core/dist/runtime-v2/store/context/sqlite-context-assembler.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/context/sqlite-context-assembler.js +8 -4
- package/core/dist/runtime-v2/store/context/sqlite-context-assembler.js.map +1 -1
- package/core/package.json +1 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +52 -1
- package/dist/installer.js.map +1 -1
- package/dist/installers/mcp-registration-conflict.d.ts +35 -0
- package/dist/installers/mcp-registration-conflict.d.ts.map +1 -0
- package/dist/installers/mcp-registration-conflict.js +559 -0
- package/dist/installers/mcp-registration-conflict.js.map +1 -0
- package/dist/installers/openclaw-host-installer.d.ts.map +1 -1
- package/dist/installers/openclaw-host-installer.js +20 -0
- package/dist/installers/openclaw-host-installer.js.map +1 -1
- package/dist/installers/workbuddy-host-installer.d.ts.map +1 -1
- package/dist/installers/workbuddy-host-installer.js +124 -2
- package/dist/installers/workbuddy-host-installer.js.map +1 -1
- package/dist/mcp-server-layout-compat.d.ts +18 -0
- package/dist/mcp-server-layout-compat.d.ts.map +1 -0
- package/dist/mcp-server-layout-compat.js +22 -0
- package/dist/mcp-server-layout-compat.js.map +1 -0
- package/host-runtime/dist/governance-signal-admission.d.ts +147 -4
- package/host-runtime/dist/governance-signal-admission.js +1065 -60
- package/host-runtime/dist/index.d.ts +2 -0
- package/host-runtime/dist/index.js +2 -0
- package/host-runtime/dist/signal-classifier.d.ts +46 -0
- package/host-runtime/dist/signal-classifier.js +167 -0
- package/host-runtime/dist/signal-stage2-cycle.d.ts +42 -0
- package/host-runtime/dist/signal-stage2-cycle.js +184 -0
- package/host-runtime/package.json +1 -1
- package/install-layout/dist/index.d.ts +15 -0
- package/install-layout/dist/index.js +10 -0
- package/install-layout/package.json +1 -1
- package/package.json +9 -7
- package/pd-cli/dist/commands/health-codex.d.ts.map +1 -1
- package/pd-cli/dist/commands/health-codex.js +12 -0
- package/pd-cli/dist/commands/health-codex.js.map +1 -1
- package/pd-cli/dist/commands/health-mcp.d.ts +74 -0
- package/pd-cli/dist/commands/health-mcp.d.ts.map +1 -0
- package/pd-cli/dist/commands/health-mcp.js +723 -0
- package/pd-cli/dist/commands/health-mcp.js.map +1 -0
- package/pd-cli/dist/commands/health.d.ts +2 -0
- package/pd-cli/dist/commands/health.d.ts.map +1 -1
- package/pd-cli/dist/commands/health.js +11 -6
- package/pd-cli/dist/commands/health.js.map +1 -1
- package/pd-mcp-server/dist/budget.d.ts +15 -0
- package/pd-mcp-server/dist/budget.js +19 -0
- package/pd-mcp-server/dist/deadline.d.ts +75 -0
- package/pd-mcp-server/dist/deadline.js +136 -0
- package/pd-mcp-server/dist/envelope.d.ts +52 -0
- package/pd-mcp-server/dist/envelope.js +63 -0
- package/pd-mcp-server/dist/index.d.ts +10 -0
- package/pd-mcp-server/dist/index.js +76 -0
- package/pd-mcp-server/dist/logging.d.ts +12 -0
- package/pd-mcp-server/dist/logging.js +42 -0
- package/pd-mcp-server/dist/server.d.ts +90 -0
- package/pd-mcp-server/dist/server.js +178 -0
- package/pd-mcp-server/dist/version.d.ts +13 -0
- package/pd-mcp-server/dist/version.js +13 -0
- package/pd-mcp-server/dist/workspace.d.ts +23 -0
- package/pd-mcp-server/dist/workspace.js +77 -0
- package/pd-mcp-server/package.json +49 -0
- package/plugin/dist/bundle.js +414 -412
- package/plugin/dist/core/mcp-binding.d.ts +41 -0
- package/plugin/dist/index.d.ts +33 -0
- package/plugin/dist/openclaw.plugin.json +10 -1
- package/plugin/dist/utils/workspace-resolver.d.ts +17 -0
- package/plugin/openclaw.plugin.json +10 -1
- package/plugin/package.json +2 -1
- package/release-manager/dist/installer.js +52 -1
- package/release-manager/dist/installers/mcp-registration-conflict.js +559 -0
- package/release-manager/dist/installers/openclaw-host-installer.js +20 -0
- package/release-manager/dist/installers/workbuddy-host-installer.js +124 -2
- package/release-manager/dist/mcp-server-layout-compat.js +22 -0
- package/release-manager/package.json +7 -5
- package/workbuddy-adapter/dist/pd-hook.js +28 -5
- package/workbuddy-adapter/dist/worker/workspace-worker.d.ts +6 -1
- package/workbuddy-adapter/dist/worker/workspace-worker.js +24 -3
- package/workbuddy-adapter/package.json +1 -1
- package/workbuddy-adapter/plugin/.codebuddy-plugin/plugin.json +2 -2
- package/workbuddy-adapter/plugin/.mcp.json +10 -0
- package/workbuddy-adapter/plugin/mcp/launcher.cjs +347 -0
|
@@ -16,6 +16,8 @@ export * from './rule-implementation-runtime.js';
|
|
|
16
16
|
export * from './production-pain-evidence.js';
|
|
17
17
|
export * from './governance-observation-store.js';
|
|
18
18
|
export * from './governance-signal-admission.js';
|
|
19
|
+
export * from './signal-classifier.js';
|
|
20
|
+
export * from './signal-stage2-cycle.js';
|
|
19
21
|
export * from './host-liveness-contract.js';
|
|
20
22
|
export * from './internalization-consumer-governance.js';
|
|
21
23
|
export * from './internalization-consumer-cycle.js';
|
|
@@ -26,6 +26,8 @@ export * from './rule-implementation-runtime.js';
|
|
|
26
26
|
export * from './production-pain-evidence.js';
|
|
27
27
|
export * from './governance-observation-store.js';
|
|
28
28
|
export * from './governance-signal-admission.js';
|
|
29
|
+
export * from './signal-classifier.js';
|
|
30
|
+
export * from './signal-stage2-cycle.js';
|
|
29
31
|
export * from './host-liveness-contract.js';
|
|
30
32
|
// PRI-624 Slice C: shared internalization consumer execution (OpenClaw
|
|
31
33
|
// auto-consumer + Companion workspace worker call the same cycle).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type LlmClassificationResult } from '@principles/core/runtime-v2';
|
|
2
|
+
export interface SignalClassifierCallOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Per-call timeout override. The worker passes the cycle's REMAINING
|
|
5
|
+
* aggregate budget so the real adapter request (startRun/poll deadline)
|
|
6
|
+
* uses that budget instead of relying on an outer Promise race.
|
|
7
|
+
*/
|
|
8
|
+
readonly timeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export type SignalLlmClassifier = (text: string, options?: SignalClassifierCallOptions) => Promise<LlmClassificationResult | null>;
|
|
11
|
+
export interface SignalClassifierProfile {
|
|
12
|
+
readonly provider: string;
|
|
13
|
+
readonly model: string;
|
|
14
|
+
readonly apiKeyEnv: string;
|
|
15
|
+
readonly timeoutMs: number;
|
|
16
|
+
readonly maxTokens?: number;
|
|
17
|
+
readonly baseUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SignalClassifierLog {
|
|
20
|
+
(event: string, detail: string): void;
|
|
21
|
+
}
|
|
22
|
+
export interface CreateSignalClassifierOptions {
|
|
23
|
+
readonly log?: SignalClassifierLog;
|
|
24
|
+
readonly timeoutMs?: number;
|
|
25
|
+
}
|
|
26
|
+
/** Build the classifier from an already-resolved profile (no config I/O). */
|
|
27
|
+
export declare function createSignalLlmClassifier(profile: SignalClassifierProfile, workspaceDir: string, options?: CreateSignalClassifierOptions): SignalLlmClassifier;
|
|
28
|
+
export type SignalClassifierResolution = {
|
|
29
|
+
ok: true;
|
|
30
|
+
classifier: SignalLlmClassifier;
|
|
31
|
+
profile: SignalClassifierProfile;
|
|
32
|
+
} | {
|
|
33
|
+
ok: false;
|
|
34
|
+
reason: string;
|
|
35
|
+
nextAction: string;
|
|
36
|
+
readiness: 'disabled' | 'needs_setup' | 'config_malformed';
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the `signalCollector` internal agent's pi-ai profile from
|
|
40
|
+
* `.pd/config.yaml` and build the classifier. This is the ONE resolution path
|
|
41
|
+
* for the Codex/WorkBuddy workers and the OpenClaw plugin factory (which adds
|
|
42
|
+
* its own bounded warning on top).
|
|
43
|
+
*/
|
|
44
|
+
export declare function createSignalClassifierForWorkspace(workspaceDir: string, options?: CreateSignalClassifierOptions & {
|
|
45
|
+
readonly flagId?: string;
|
|
46
|
+
}): SignalClassifierResolution;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Stage-2 signal classifier factory (SPEC v0.3 §3.4 / B4).
|
|
3
|
+
*
|
|
4
|
+
* ONE implementation of the pi-ai classifier call for every host:
|
|
5
|
+
* OpenClaw's plugin factory, the Codex worker and the WorkBuddy worker all
|
|
6
|
+
* consume this instead of re-implementing startRun/pollRun/fetchOutput.
|
|
7
|
+
*
|
|
8
|
+
* The classifier contract is core-owned: `buildLlmPrompt`,
|
|
9
|
+
* `signal-classification-output-v1` and `resolveLlmClassificationPayload`
|
|
10
|
+
* come from `@principles/core/runtime-v2`. This module only owns acquisition:
|
|
11
|
+
* reading the signalCollector runtime profile and calling the adapter.
|
|
12
|
+
*
|
|
13
|
+
* No second model runtime is introduced (ADR-0029: pi-ai is the only backend).
|
|
14
|
+
*/
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { PiAiRuntimeAdapter, SIGNAL_CLASSIFIER_SYSTEM_PROMPT, buildLlmPrompt, resolveLlmClassificationPayload, safeStringifyPreview, } from '@principles/core/runtime-v2';
|
|
17
|
+
import { loadPdConfigForPlugin } from './pd-config.js';
|
|
18
|
+
/** Build the classifier from an already-resolved profile (no config I/O). */
|
|
19
|
+
export function createSignalLlmClassifier(profile, workspaceDir, options = {}) {
|
|
20
|
+
const timeoutMs = options.timeoutMs ?? profile.timeoutMs;
|
|
21
|
+
const adapter = new PiAiRuntimeAdapter({
|
|
22
|
+
provider: profile.provider,
|
|
23
|
+
model: profile.model,
|
|
24
|
+
apiKeyEnv: profile.apiKeyEnv,
|
|
25
|
+
timeoutMs,
|
|
26
|
+
maxTokens: profile.maxTokens,
|
|
27
|
+
baseUrl: profile.baseUrl,
|
|
28
|
+
workspace: workspaceDir,
|
|
29
|
+
// Stage-2 emits a 5-field JSON. json_mode_first avoids depending on the
|
|
30
|
+
// provider's tool-call support; one repair attempt bounds latency.
|
|
31
|
+
outputPathStrategy: 'json_mode_first',
|
|
32
|
+
maxRepairAttempts: 1,
|
|
33
|
+
});
|
|
34
|
+
return async (text, callOptions) => {
|
|
35
|
+
const prompt = buildLlmPrompt(text);
|
|
36
|
+
// v0.3.1 §2.2: the effective timeout is the worker's remaining budget,
|
|
37
|
+
// and it is handed to the adapter itself (not only to an outer race).
|
|
38
|
+
const effectiveTimeoutMs = callOptions?.timeoutMs ?? timeoutMs;
|
|
39
|
+
try {
|
|
40
|
+
const handle = await adapter.startRun({
|
|
41
|
+
agentSpec: { agentId: 'signal-collector', schemaVersion: '1' },
|
|
42
|
+
inputPayload: { prompt },
|
|
43
|
+
contextItems: [],
|
|
44
|
+
timeoutMs: effectiveTimeoutMs,
|
|
45
|
+
outputSchemaRef: 'signal-classification-output-v1',
|
|
46
|
+
systemPrompt: SIGNAL_CLASSIFIER_SYSTEM_PROMPT,
|
|
47
|
+
});
|
|
48
|
+
let status = await adapter.pollRun(handle.runId);
|
|
49
|
+
const deadline = performance.now() + effectiveTimeoutMs;
|
|
50
|
+
while (status.status === 'running' && performance.now() < deadline) {
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
52
|
+
status = await adapter.pollRun(handle.runId);
|
|
53
|
+
}
|
|
54
|
+
if (status.status !== 'succeeded') {
|
|
55
|
+
options.log?.('SIGNAL_LLM_TIMEOUT', JSON.stringify({ status: status.status }));
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const output = await adapter.fetchOutput(handle.runId);
|
|
59
|
+
const resolved = resolveLlmClassificationPayload(output?.payload);
|
|
60
|
+
if (resolved.path === 'structured' && resolved.value)
|
|
61
|
+
return resolved.value;
|
|
62
|
+
if ((resolved.path === 'legacy_string' || resolved.path === 'legacy_envelope') && resolved.value) {
|
|
63
|
+
options.log?.('SIGNAL_LLM_LEGACY_STRING_PAYLOAD', `adapter payload path=${resolved.path}`);
|
|
64
|
+
return resolved.value;
|
|
65
|
+
}
|
|
66
|
+
options.log?.('SIGNAL_LLM_PARSE_FAIL', `unusable classifier payload path=${resolved.path}: ${safeStringifyPreview(output?.payload)}`);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
options.log?.('SIGNAL_LLM_FAILED', String(error).slice(0, 200));
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the `signalCollector` internal agent's pi-ai profile from
|
|
77
|
+
* `.pd/config.yaml` and build the classifier. This is the ONE resolution path
|
|
78
|
+
* for the Codex/WorkBuddy workers and the OpenClaw plugin factory (which adds
|
|
79
|
+
* its own bounded warning on top).
|
|
80
|
+
*/
|
|
81
|
+
export function createSignalClassifierForWorkspace(workspaceDir, options = {}) {
|
|
82
|
+
const configResult = loadPdConfigForPlugin(workspaceDir);
|
|
83
|
+
if (!configResult.ok) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
readiness: 'config_malformed',
|
|
87
|
+
reason: `Config validation failed: ${configResult.errors.map((error) => error.reason).join('; ')}`,
|
|
88
|
+
nextAction: configResult.errors[0]?.nextAction ?? 'Fix .pd/config.yaml and retry',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const { config } = configResult.effective;
|
|
92
|
+
const flagId = options.flagId ?? 'signal_collector';
|
|
93
|
+
const agentName = 'signalCollector';
|
|
94
|
+
const feature = config.features[flagId];
|
|
95
|
+
if (!feature?.enabled) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
readiness: 'disabled',
|
|
99
|
+
reason: `${flagId} is disabled in .pd/config.yaml`,
|
|
100
|
+
nextAction: `Enable it in the existing "features:" block as ${flagId}: {enabled: true}`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const agent = config.internalAgents.agents[agentName];
|
|
104
|
+
if (!agent?.enabled) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
readiness: 'disabled',
|
|
108
|
+
reason: `internalAgents.agents.${agentName}.enabled is false`,
|
|
109
|
+
nextAction: `Enable internalAgents > agents > ${agentName} > enabled: true`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const runtimeProfileId = agent.runtimeProfile ?? config.internalAgents.defaultRuntime;
|
|
113
|
+
const profile = config.runtimeProfiles[runtimeProfileId];
|
|
114
|
+
if (!profile) {
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
readiness: 'needs_setup',
|
|
118
|
+
reason: `Runtime profile '${runtimeProfileId}' not found`,
|
|
119
|
+
nextAction: `Add runtime profile '${runtimeProfileId}' to runtimeProfiles`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (profile.type !== 'pi-ai') {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
readiness: 'needs_setup',
|
|
126
|
+
reason: `Runtime profile '${runtimeProfileId}' is '${profile.type}', not pi-ai`,
|
|
127
|
+
nextAction: 'ADR-0029 requires a pi-ai runtime profile for signal classification',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (!profile.apiKeyEnv) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
readiness: 'needs_setup',
|
|
134
|
+
reason: `pi-ai profile '${runtimeProfileId}' missing apiKeyEnv`,
|
|
135
|
+
nextAction: `Add apiKeyEnv to runtime profile '${runtimeProfileId}'`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (!profile.provider || !profile.model) {
|
|
139
|
+
return {
|
|
140
|
+
ok: false,
|
|
141
|
+
readiness: 'needs_setup',
|
|
142
|
+
reason: `pi-ai profile '${runtimeProfileId}' missing provider/model`,
|
|
143
|
+
nextAction: `Set provider and model on runtime profile '${runtimeProfileId}'`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (!Object.hasOwn(process.env, profile.apiKeyEnv) || !process.env[profile.apiKeyEnv]) {
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
readiness: 'needs_setup',
|
|
150
|
+
reason: `Environment variable '${profile.apiKeyEnv}' is not set or empty`,
|
|
151
|
+
nextAction: `Set the environment variable '${profile.apiKeyEnv}' with a valid API key`,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const resolved = {
|
|
155
|
+
provider: profile.provider,
|
|
156
|
+
model: profile.model,
|
|
157
|
+
apiKeyEnv: profile.apiKeyEnv,
|
|
158
|
+
timeoutMs: options.timeoutMs ?? profile.timeoutMs ?? 60_000,
|
|
159
|
+
...(profile.maxTokens !== undefined ? { maxTokens: profile.maxTokens } : {}),
|
|
160
|
+
...(profile.baseUrl !== undefined ? { baseUrl: profile.baseUrl } : {}),
|
|
161
|
+
};
|
|
162
|
+
return {
|
|
163
|
+
ok: true,
|
|
164
|
+
profile: resolved,
|
|
165
|
+
classifier: createSignalLlmClassifier(resolved, path.resolve(workspaceDir), options),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded Stage-2 correction review step shared by the Codex and WorkBuddy
|
|
3
|
+
* workers (SPEC v0.3 §4.4.7 / v0.3.1 §2).
|
|
4
|
+
*
|
|
5
|
+
* Contract:
|
|
6
|
+
* - runs at the END of the worker cycle (never ahead of the Diagnostician);
|
|
7
|
+
* - at most 2 candidates per cycle;
|
|
8
|
+
* - one aggregate wall-clock budget (default 60s), checked before each
|
|
9
|
+
* candidate and clamped into the single classification timeout;
|
|
10
|
+
* - serial, CAS-claimed candidates; no new worker process, no new flag.
|
|
11
|
+
*
|
|
12
|
+
* The classifier itself comes from `createSignalClassifierForWorkspace`
|
|
13
|
+
* (B4). A missing classifier does NOT fail the cycle: candidates with a
|
|
14
|
+
* durable review result are still settled without any LLM call.
|
|
15
|
+
*/
|
|
16
|
+
import type { SignalLlmClassifier } from './signal-classifier.js';
|
|
17
|
+
export interface Stage2CorrectionCycleReport {
|
|
18
|
+
readonly ok: boolean;
|
|
19
|
+
readonly classifier: 'used' | 'unavailable' | 'disabled';
|
|
20
|
+
readonly claimed: number;
|
|
21
|
+
readonly admitted: number;
|
|
22
|
+
readonly resolvedNone: number;
|
|
23
|
+
readonly abandoned: number;
|
|
24
|
+
readonly stillPending: number;
|
|
25
|
+
readonly skipped: number;
|
|
26
|
+
readonly expired: number;
|
|
27
|
+
readonly durationMs: number;
|
|
28
|
+
readonly reason?: string;
|
|
29
|
+
readonly nextAction?: string;
|
|
30
|
+
readonly degradations: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
export interface Stage2CorrectionCycleInput {
|
|
33
|
+
readonly workspaceDir: string;
|
|
34
|
+
readonly classifier: SignalLlmClassifier | null;
|
|
35
|
+
readonly classifierState?: 'used' | 'unavailable' | 'disabled';
|
|
36
|
+
readonly maxCandidates?: number;
|
|
37
|
+
readonly aggregateBudgetMs?: number;
|
|
38
|
+
readonly classificationTimeoutMs?: number;
|
|
39
|
+
readonly now?: Date;
|
|
40
|
+
readonly onEvent?: (event: string, payloadJson: string) => void;
|
|
41
|
+
}
|
|
42
|
+
export declare function runStage2CorrectionCycle(input: Stage2CorrectionCycleInput): Promise<Stage2CorrectionCycleReport>;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { claimPendingCorrectionCandidates, recordPendingCorrectionReview, releasePendingCorrectionClaim, settlePendingCorrection, } from './governance-signal-admission.js';
|
|
2
|
+
const DEFAULT_MAX_CANDIDATES = 2;
|
|
3
|
+
const DEFAULT_AGGREGATE_BUDGET_MS = 60_000;
|
|
4
|
+
const DEFAULT_CLASSIFICATION_TIMEOUT_MS = 60_000;
|
|
5
|
+
function withTimeout(promise, timeoutMs) {
|
|
6
|
+
if (timeoutMs <= 0)
|
|
7
|
+
return Promise.resolve(null);
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
const timer = setTimeout(() => resolve(null), timeoutMs);
|
|
10
|
+
void promise.then((value) => {
|
|
11
|
+
clearTimeout(timer);
|
|
12
|
+
resolve(value);
|
|
13
|
+
}, () => {
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
resolve(null);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function emptyReport(classifier, reason, nextAction) {
|
|
20
|
+
return {
|
|
21
|
+
ok: reason === undefined,
|
|
22
|
+
classifier,
|
|
23
|
+
claimed: 0,
|
|
24
|
+
admitted: 0,
|
|
25
|
+
resolvedNone: 0,
|
|
26
|
+
abandoned: 0,
|
|
27
|
+
stillPending: 0,
|
|
28
|
+
skipped: 0,
|
|
29
|
+
expired: 0,
|
|
30
|
+
durationMs: 0,
|
|
31
|
+
...(reason !== undefined ? { reason } : {}),
|
|
32
|
+
...(nextAction !== undefined ? { nextAction } : {}),
|
|
33
|
+
degradations: [],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export async function runStage2CorrectionCycle(input) {
|
|
37
|
+
const classifierState = input.classifierState ?? (input.classifier === null ? 'unavailable' : 'used');
|
|
38
|
+
// Monotonic clock for the aggregate budget; wall clock stays for DB/TTL.
|
|
39
|
+
const startedAt = performance.now();
|
|
40
|
+
const now = input.now ?? new Date();
|
|
41
|
+
const maxCandidates = Math.min(Math.max(input.maxCandidates ?? DEFAULT_MAX_CANDIDATES, 1), DEFAULT_MAX_CANDIDATES);
|
|
42
|
+
const budgetMs = Math.max(0, input.aggregateBudgetMs ?? DEFAULT_AGGREGATE_BUDGET_MS);
|
|
43
|
+
const classificationTimeoutMs = Math.max(0, input.classificationTimeoutMs ?? DEFAULT_CLASSIFICATION_TIMEOUT_MS);
|
|
44
|
+
const claimedResult = claimPendingCorrectionCandidates({ workspaceDir: input.workspaceDir, limit: maxCandidates, now });
|
|
45
|
+
if (!claimedResult.ok) {
|
|
46
|
+
return {
|
|
47
|
+
...emptyReport(classifierState, claimedResult.reason, claimedResult.nextAction),
|
|
48
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
49
|
+
degradations: [claimedResult.reason],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
let admitted = 0;
|
|
53
|
+
let resolvedNone = 0;
|
|
54
|
+
let abandoned = 0;
|
|
55
|
+
let stillPending = 0;
|
|
56
|
+
let skipped = 0;
|
|
57
|
+
const degradations = [];
|
|
58
|
+
/**
|
|
59
|
+
* Release our claim when classification has NOT started. The release is
|
|
60
|
+
* guarded inside gsa (never clears a durable reviewResult, CAS on the
|
|
61
|
+
* fencing token), so a concurrent worker's state cannot be touched.
|
|
62
|
+
*/
|
|
63
|
+
const releaseClaim = (candidate) => {
|
|
64
|
+
if (candidate.claimToken === null)
|
|
65
|
+
return;
|
|
66
|
+
const released = releasePendingCorrectionClaim({
|
|
67
|
+
workspaceDir: input.workspaceDir,
|
|
68
|
+
logicalObservationKey: candidate.logicalObservationKey,
|
|
69
|
+
claimToken: candidate.claimToken,
|
|
70
|
+
});
|
|
71
|
+
if (!released.ok) {
|
|
72
|
+
degradations.push(`${candidate.logicalObservationKey}:claim_release_failed:${released.reason}`);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const tally = (outcome) => {
|
|
76
|
+
if (outcome.disposition === 'admitted')
|
|
77
|
+
admitted += 1;
|
|
78
|
+
else if (outcome.disposition === 'resolved_none')
|
|
79
|
+
resolvedNone += 1;
|
|
80
|
+
else if (outcome.disposition === 'abandoned')
|
|
81
|
+
abandoned += 1;
|
|
82
|
+
else if (outcome.disposition === 'pending')
|
|
83
|
+
stillPending += 1;
|
|
84
|
+
else
|
|
85
|
+
skipped += 1;
|
|
86
|
+
};
|
|
87
|
+
for (const candidate of claimedResult.candidates) {
|
|
88
|
+
const elapsed = performance.now() - startedAt;
|
|
89
|
+
const remaining = budgetMs - elapsed;
|
|
90
|
+
if (remaining <= 0) {
|
|
91
|
+
releaseClaim(candidate);
|
|
92
|
+
skipped += 1;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
let review = candidate.reviewResult;
|
|
97
|
+
if (review === null) {
|
|
98
|
+
if (input.classifier === null) {
|
|
99
|
+
// No LLM configured/available: release the claim (no attempt
|
|
100
|
+
// consumed) so a later cycle can retry immediately. NOT a verdict.
|
|
101
|
+
releaseClaim(candidate);
|
|
102
|
+
skipped += 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const timeoutMs = Math.max(1, Math.min(classificationTimeoutMs, remaining));
|
|
106
|
+
// The remaining budget goes INTO the adapter request as well as the
|
|
107
|
+
// outer race (v0.3.1 §2.2); an outer timeout does not cancel a
|
|
108
|
+
// provider request that the adapter does not support cancelling.
|
|
109
|
+
const llm = await withTimeout(input.classifier(candidate.text, { timeoutMs }), timeoutMs);
|
|
110
|
+
const { claimToken } = candidate;
|
|
111
|
+
if (claimToken === null) {
|
|
112
|
+
// A freshly claimed candidate always carries a token; its absence
|
|
113
|
+
// means the claim was consumed elsewhere — do not write without it.
|
|
114
|
+
degradations.push(`${candidate.logicalObservationKey}:claim_token_missing`);
|
|
115
|
+
skipped += 1;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const recorded = recordPendingCorrectionReview({
|
|
119
|
+
workspaceDir: input.workspaceDir,
|
|
120
|
+
logicalObservationKey: candidate.logicalObservationKey,
|
|
121
|
+
claimToken,
|
|
122
|
+
llmResult: llm,
|
|
123
|
+
now,
|
|
124
|
+
});
|
|
125
|
+
if (!recorded.ok) {
|
|
126
|
+
// Nothing durable was written; release so the next cycle retries
|
|
127
|
+
// instead of waiting out the claim TTL.
|
|
128
|
+
releaseClaim(candidate);
|
|
129
|
+
degradations.push(`${candidate.logicalObservationKey}:${recorded.reason}`);
|
|
130
|
+
skipped += 1;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (recorded.disposition === 'concurrent_loss') {
|
|
134
|
+
skipped += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// Read the freshly persisted verdict through the settle step; the
|
|
138
|
+
// recorded verdict is durable even if a later step throws.
|
|
139
|
+
}
|
|
140
|
+
const settled = await settlePendingCorrection({
|
|
141
|
+
workspaceDir: input.workspaceDir,
|
|
142
|
+
logicalObservationKey: candidate.logicalObservationKey,
|
|
143
|
+
// Fencing token only when this cycle holds the claim; crash recovery
|
|
144
|
+
// of an already durable reviewResult settles without one.
|
|
145
|
+
...(candidate.claimToken !== null ? { claimToken: candidate.claimToken } : {}),
|
|
146
|
+
now,
|
|
147
|
+
});
|
|
148
|
+
if (!settled.ok) {
|
|
149
|
+
degradations.push(`${candidate.logicalObservationKey}:${settled.reason}`);
|
|
150
|
+
skipped += 1;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
tally(settled);
|
|
154
|
+
input.onEvent?.('SIGNAL_STAGE2_SETTLED', JSON.stringify({
|
|
155
|
+
logicalObservationKey: candidate.logicalObservationKey,
|
|
156
|
+
disposition: settled.disposition,
|
|
157
|
+
reason: settled.reason,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
// Guarded release: a durable reviewResult is never cleared here.
|
|
162
|
+
releaseClaim(candidate);
|
|
163
|
+
const detail = error instanceof Error ? error.message.slice(0, 160) : String(error).slice(0, 160);
|
|
164
|
+
degradations.push(`${candidate.logicalObservationKey}:stage2_failed:${detail}`);
|
|
165
|
+
skipped += 1;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
ok: degradations.length === 0,
|
|
170
|
+
classifier: classifierState,
|
|
171
|
+
claimed: claimedResult.candidates.length,
|
|
172
|
+
admitted,
|
|
173
|
+
resolvedNone,
|
|
174
|
+
abandoned,
|
|
175
|
+
stillPending,
|
|
176
|
+
skipped,
|
|
177
|
+
expired: claimedResult.expired,
|
|
178
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
179
|
+
...(degradations.length > 0
|
|
180
|
+
? { reason: 'stage2_correction_degradations', nextAction: 'Inspect the per-candidate degradations; pending candidates remain durable and retry next cycle.' }
|
|
181
|
+
: {}),
|
|
182
|
+
degradations: degradations.slice(0, 10),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -72,6 +72,14 @@ export interface InstallLayoutPaths {
|
|
|
72
72
|
* the same way the Codex hook resolves the canonical runtime.
|
|
73
73
|
*/
|
|
74
74
|
workbuddyAdapterDir: string;
|
|
75
|
+
/**
|
|
76
|
+
* Gate 4 (PRI-1071): the shared read-only MCP server component (payload dir
|
|
77
|
+
* pd-mcp-server/, npm name @principles/pd-mcp-server). This is the Install
|
|
78
|
+
* Layout SSoT for the canonical server entry OpenClaw/WorkBuddy launchers
|
|
79
|
+
* bind to; Codex keeps its plugin-data runtime layout and records that dir
|
|
80
|
+
* in its own binding.
|
|
81
|
+
*/
|
|
82
|
+
mcpServerDir: string;
|
|
75
83
|
}
|
|
76
84
|
export interface LayoutResolution {
|
|
77
85
|
mode: InstallLayoutMode | 'missing';
|
|
@@ -111,6 +119,13 @@ export declare function parseInstallManifest(value: unknown): {
|
|
|
111
119
|
error?: string;
|
|
112
120
|
};
|
|
113
121
|
export declare function getInstallLayoutPaths(homeDir: string): InstallLayoutPaths;
|
|
122
|
+
/**
|
|
123
|
+
* Canonical installed MCP server entry for the shared runtime layout
|
|
124
|
+
* (~/.pd/runtime/pd-mcp-server/dist/index.js). Launchers, binding writers and
|
|
125
|
+
* health diagnostics derive the path from this single owner instead of each
|
|
126
|
+
* repeating the segment list.
|
|
127
|
+
*/
|
|
128
|
+
export declare function getMcpServerEntry(paths: InstallLayoutPaths): string;
|
|
114
129
|
/**
|
|
115
130
|
* WorkBuddy governance workspace (SPEC workbuddy-host-adapter §4.1 OD-07,
|
|
116
131
|
* frozen by Phase 0 probe P06 2026-10-12): an explicit user-level directory
|
|
@@ -140,8 +140,18 @@ export function getInstallLayoutPaths(homeDir) {
|
|
|
140
140
|
releaseManagerDir: path.join(runtimeDir, 'release-manager'),
|
|
141
141
|
codexAdapterDir: path.join(runtimeDir, 'codex-adapter'),
|
|
142
142
|
workbuddyAdapterDir: path.join(runtimeDir, 'workbuddy-adapter'),
|
|
143
|
+
mcpServerDir: path.join(runtimeDir, 'pd-mcp-server'),
|
|
143
144
|
};
|
|
144
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Canonical installed MCP server entry for the shared runtime layout
|
|
148
|
+
* (~/.pd/runtime/pd-mcp-server/dist/index.js). Launchers, binding writers and
|
|
149
|
+
* health diagnostics derive the path from this single owner instead of each
|
|
150
|
+
* repeating the segment list.
|
|
151
|
+
*/
|
|
152
|
+
export function getMcpServerEntry(paths) {
|
|
153
|
+
return path.join(paths.mcpServerDir, 'dist', 'index.js');
|
|
154
|
+
}
|
|
145
155
|
/**
|
|
146
156
|
* WorkBuddy governance workspace (SPEC workbuddy-host-adapter §4.1 OD-07,
|
|
147
157
|
* frozen by Phase 0 probe P06 2026-10-12): an explicit user-level directory
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-principles-disciple",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.147.0",
|
|
4
4
|
"description": "Interactive CLI installer for Principles Disciple OpenClaw plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"host-runtime",
|
|
27
27
|
"codex-adapter",
|
|
28
28
|
"workbuddy-adapter",
|
|
29
|
+
"pd-mcp-server",
|
|
29
30
|
"install-layout",
|
|
30
31
|
"release-manager",
|
|
31
32
|
"trust/root.json",
|
|
@@ -36,13 +37,14 @@
|
|
|
36
37
|
],
|
|
37
38
|
"scripts": {
|
|
38
39
|
"build": "tsc",
|
|
40
|
+
"build:deps": "npm run build --workspace=@principles/core && npm run build --workspace=@principles/install-layout && node scripts/mcp-server-build.mjs",
|
|
39
41
|
"dev": "tsc --watch",
|
|
40
42
|
"bundle": "node scripts/bundle-plugin.mjs",
|
|
41
|
-
"build:release-asset": "node scripts/build-self-contained-release.mjs",
|
|
43
|
+
"build:release-asset": "npm run build:deps && node scripts/build-self-contained-release.mjs",
|
|
42
44
|
"generate:release-locks": "node scripts/generate-release-locks.mjs",
|
|
43
45
|
"check:release-locks": "node scripts/check-release-locks.mjs",
|
|
44
|
-
"prepack": "npm run build && npm run bundle",
|
|
45
|
-
"prepublishOnly": "npm run build && npm run bundle",
|
|
46
|
+
"prepack": "npm run build:deps && npm run build && npm run bundle",
|
|
47
|
+
"prepublishOnly": "npm run build:deps && npm run build && npm run bundle",
|
|
46
48
|
"test": "vitest run",
|
|
47
49
|
"test:fast": "vitest run --exclude tests/smoke-packaged-install.test.ts --exclude tests/release-asset-smoke.test.ts --exclude tests/release-upgrade-gate.test.ts --exclude tests/release-junction-lifecycle.test.ts",
|
|
48
50
|
"test:smoke:packaged": "vitest run tests/smoke-packaged-install.test.ts",
|
|
@@ -60,7 +62,7 @@
|
|
|
60
62
|
"dependencies": {
|
|
61
63
|
"@earendil-works/pi-agent-core": "1.2.0",
|
|
62
64
|
"@earendil-works/pi-ai": "1.4.0",
|
|
63
|
-
"@principles/install-layout": "^0.4.
|
|
65
|
+
"@principles/install-layout": "^0.4.1",
|
|
64
66
|
"@sinclair/typebox": "^0.34.52",
|
|
65
67
|
"@tufjs/models": "3.0.1",
|
|
66
68
|
"@inquirer/prompts": "^8.7.2",
|
|
@@ -96,7 +98,7 @@
|
|
|
96
98
|
"directory": "packages/create-principles-disciple"
|
|
97
99
|
},
|
|
98
100
|
"pd": {
|
|
99
|
-
"bundledPluginVersion": "2.
|
|
101
|
+
"bundledPluginVersion": "2.4.0"
|
|
100
102
|
},
|
|
101
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "d3355465b090daf098581cdb4c174634fe1e5279"
|
|
102
104
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health-codex.d.ts","sourceRoot":"","sources":["../../src/commands/health-codex.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"health-codex.d.ts","sourceRoot":"","sources":["../../src/commands/health-codex.ts"],"names":[],"mappings":"AAmDA,UAAU,kBAAkB;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA+PD,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoZpF"}
|
|
@@ -31,6 +31,7 @@ import { createRequire } from 'module';
|
|
|
31
31
|
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
32
32
|
import { loadPdConfigForPlugin, resolveNearestPdWorkspace, readCodexIngestionConsent, deriveCodexIngestionConsentState, listGovernanceCheckpoints, readGovernanceObservationStats, readGovernanceAdmissionCounts, detectLegacyCodexHookRegistration, CODEX_INGESTION_DISCLOSURE_VERSION, } from '@principles/host-runtime';
|
|
33
33
|
import { assessCodexInstall, CODEX_INSTALL_NEXT_ACTIONS } from './health-codex-install.js';
|
|
34
|
+
import { assessMcpHealth, formatMcpHealthLines } from './health-mcp.js';
|
|
34
35
|
import { getInstallLayoutPaths, parseInstallManifest } from '@principles/install-layout';
|
|
35
36
|
import { SqliteConnection, SqliteTaskStore, computeFeatureFlagsFromConfig, isFeatureEnabled } from '@principles/core/runtime-v2';
|
|
36
37
|
const require = createRequire(import.meta.url);
|
|
@@ -441,6 +442,14 @@ export async function handleHealthCodex(opts = {}) {
|
|
|
441
442
|
}
|
|
442
443
|
}
|
|
443
444
|
// ── §15 ready conjunction ──────────────────────────────────────────────────
|
|
445
|
+
// PRI-1071 Gate 4: the shared MCP recall surface is reported separately.
|
|
446
|
+
// It is NOT part of the §15 ingestion/closure conjunction — a missing MCP
|
|
447
|
+
// binding must be visible and actionable, but it must not reclassify the
|
|
448
|
+
// (independent) Codex ingestion readiness contract.
|
|
449
|
+
const mcp = assessMcpHealth({ host: 'codex', homeDir: os.homedir(), env: process.env });
|
|
450
|
+
if (mcp.status !== 'HEALTHY') {
|
|
451
|
+
warnings.push(`mcp: ${mcp.status} — ${mcp.reason ?? 'unverified'}. ${mcp.nextAction ?? ''}`.trim());
|
|
452
|
+
}
|
|
444
453
|
if (!hostCodexEnabled)
|
|
445
454
|
noteReadyBlocker('host.codex', 'disabled');
|
|
446
455
|
if (workerEvaluation.mode === 'paused')
|
|
@@ -492,6 +501,7 @@ export async function handleHealthCodex(opts = {}) {
|
|
|
492
501
|
cliStatusSource: install.cliStatusSource,
|
|
493
502
|
}
|
|
494
503
|
: { reason: install.reason, nextAction: install.nextAction },
|
|
504
|
+
mcp,
|
|
495
505
|
ready,
|
|
496
506
|
readyBlockers: readyBlockers.slice(0, 12),
|
|
497
507
|
warnings,
|
|
@@ -526,6 +536,8 @@ export async function handleHealthCodex(opts = {}) {
|
|
|
526
536
|
}
|
|
527
537
|
line('codexIngestionMinVersion', report.codexIngestionMinVersion);
|
|
528
538
|
line('codexIngestionVerifiedVersion', report.codexIngestionVerifiedVersion);
|
|
539
|
+
for (const mcpLine of formatMcpHealthLines(report.mcp))
|
|
540
|
+
console.log(mcpLine);
|
|
529
541
|
line('featureFlag.host.codex', `${String(report.featureFlag.enabled)} (${report.featureFlag.source})`);
|
|
530
542
|
if ('enabled' in report.ingestionFlag) {
|
|
531
543
|
line('ingestionFlag.codex_conversation_ingestion', `${String(report.ingestionFlag.enabled)} (${report.ingestionFlag.source})`);
|