@principles/core 1.296.2 → 1.297.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.
Files changed (122) hide show
  1. package/README.md +0 -1
  2. package/dist/runtime-v2/adapter/index.d.ts +0 -2
  3. package/dist/runtime-v2/adapter/index.d.ts.map +1 -1
  4. package/dist/runtime-v2/adapter/index.js +0 -1
  5. package/dist/runtime-v2/adapter/index.js.map +1 -1
  6. package/dist/runtime-v2/adapter/output-schema-registry.d.ts +3 -3
  7. package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts.map +1 -1
  8. package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +1 -2
  9. package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
  10. package/dist/runtime-v2/cli/probe.d.ts +4 -15
  11. package/dist/runtime-v2/cli/probe.d.ts.map +1 -1
  12. package/dist/runtime-v2/cli/probe.js +24 -44
  13. package/dist/runtime-v2/cli/probe.js.map +1 -1
  14. package/dist/runtime-v2/config/index.d.ts +1 -1
  15. package/dist/runtime-v2/config/index.d.ts.map +1 -1
  16. package/dist/runtime-v2/config/index.js.map +1 -1
  17. package/dist/runtime-v2/config/pd-config-agent-binding.d.ts +10 -17
  18. package/dist/runtime-v2/config/pd-config-agent-binding.d.ts.map +1 -1
  19. package/dist/runtime-v2/config/pd-config-agent-binding.js +23 -27
  20. package/dist/runtime-v2/config/pd-config-agent-binding.js.map +1 -1
  21. package/dist/runtime-v2/config/pd-config-defaults.d.ts +3 -5
  22. package/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -1
  23. package/dist/runtime-v2/config/pd-config-defaults.js +17 -20
  24. package/dist/runtime-v2/config/pd-config-defaults.js.map +1 -1
  25. package/dist/runtime-v2/config/pd-config-effective.d.ts.map +1 -1
  26. package/dist/runtime-v2/config/pd-config-effective.js +4 -1
  27. package/dist/runtime-v2/config/pd-config-effective.js.map +1 -1
  28. package/dist/runtime-v2/config/pd-config-redaction.d.ts.map +1 -1
  29. package/dist/runtime-v2/config/pd-config-redaction.js +4 -7
  30. package/dist/runtime-v2/config/pd-config-redaction.js.map +1 -1
  31. package/dist/runtime-v2/config/pd-config-types.d.ts +5 -1
  32. package/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
  33. package/dist/runtime-v2/config/pd-config-types.js +6 -0
  34. package/dist/runtime-v2/config/pd-config-types.js.map +1 -1
  35. package/dist/runtime-v2/config/pd-config-validate.d.ts.map +1 -1
  36. package/dist/runtime-v2/config/pd-config-validate.js +6 -0
  37. package/dist/runtime-v2/config/pd-config-validate.js.map +1 -1
  38. package/dist/runtime-v2/golden-trace.d.ts.map +1 -1
  39. package/dist/runtime-v2/golden-trace.js +8 -1
  40. package/dist/runtime-v2/golden-trace.js.map +1 -1
  41. package/dist/runtime-v2/index.d.ts +4 -4
  42. package/dist/runtime-v2/index.d.ts.map +1 -1
  43. package/dist/runtime-v2/index.js +2 -3
  44. package/dist/runtime-v2/index.js.map +1 -1
  45. package/dist/runtime-v2/internalization/artificer-intent-preflight.d.ts.map +1 -1
  46. package/dist/runtime-v2/internalization/artificer-intent-preflight.js +26 -95
  47. package/dist/runtime-v2/internalization/artificer-intent-preflight.js.map +1 -1
  48. package/dist/runtime-v2/internalization/bash-risk.d.ts +32 -0
  49. package/dist/runtime-v2/internalization/bash-risk.d.ts.map +1 -0
  50. package/dist/runtime-v2/internalization/bash-risk.js +785 -0
  51. package/dist/runtime-v2/internalization/bash-risk.js.map +1 -0
  52. package/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts +25 -2
  53. package/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts.map +1 -1
  54. package/dist/runtime-v2/internalization/evaluator-prompt-builder.js +177 -14
  55. package/dist/runtime-v2/internalization/evaluator-prompt-builder.js.map +1 -1
  56. package/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
  57. package/dist/runtime-v2/internalization/evaluator-runner.js +14 -2
  58. package/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
  59. package/dist/runtime-v2/internalization/internalization-orchestrator.d.ts +47 -0
  60. package/dist/runtime-v2/internalization/internalization-orchestrator.d.ts.map +1 -1
  61. package/dist/runtime-v2/internalization/internalization-orchestrator.js +164 -3
  62. package/dist/runtime-v2/internalization/internalization-orchestrator.js.map +1 -1
  63. package/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +9 -1
  64. package/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
  65. package/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
  66. package/dist/runtime-v2/internalization/pitask-metadata.d.ts +39 -0
  67. package/dist/runtime-v2/internalization/pitask-metadata.d.ts.map +1 -1
  68. package/dist/runtime-v2/internalization/pitask-metadata.js +59 -0
  69. package/dist/runtime-v2/internalization/pitask-metadata.js.map +1 -1
  70. package/dist/runtime-v2/internalization/rule-source-correction.d.ts +60 -0
  71. package/dist/runtime-v2/internalization/rule-source-correction.d.ts.map +1 -0
  72. package/dist/runtime-v2/internalization/rule-source-correction.js +214 -0
  73. package/dist/runtime-v2/internalization/rule-source-correction.js.map +1 -0
  74. package/dist/runtime-v2/internalization/rulecode-verification-obligations.d.ts.map +1 -1
  75. package/dist/runtime-v2/internalization/rulecode-verification-obligations.js +65 -0
  76. package/dist/runtime-v2/internalization/rulecode-verification-obligations.js.map +1 -1
  77. package/dist/runtime-v2/internalization/scribe-output.d.ts +14 -2
  78. package/dist/runtime-v2/internalization/scribe-output.d.ts.map +1 -1
  79. package/dist/runtime-v2/internalization/scribe-output.js +57 -2
  80. package/dist/runtime-v2/internalization/scribe-output.js.map +1 -1
  81. package/dist/runtime-v2/internalization/scribe-prompt-builder.d.ts +13 -1
  82. package/dist/runtime-v2/internalization/scribe-prompt-builder.d.ts.map +1 -1
  83. package/dist/runtime-v2/internalization/scribe-prompt-builder.js +15 -3
  84. package/dist/runtime-v2/internalization/scribe-prompt-builder.js.map +1 -1
  85. package/dist/runtime-v2/internalization/scribe-runner.d.ts +34 -0
  86. package/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
  87. package/dist/runtime-v2/internalization/scribe-runner.js +128 -4
  88. package/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
  89. package/dist/runtime-v2/internalization/textual-basis-anchor.d.ts +72 -0
  90. package/dist/runtime-v2/internalization/textual-basis-anchor.d.ts.map +1 -0
  91. package/dist/runtime-v2/internalization/textual-basis-anchor.js +100 -0
  92. package/dist/runtime-v2/internalization/textual-basis-anchor.js.map +1 -0
  93. package/dist/runtime-v2/pain-signal-runtime-factory.d.ts +9 -7
  94. package/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
  95. package/dist/runtime-v2/pain-signal-runtime-factory.js +59 -121
  96. package/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
  97. package/dist/runtime-v2/runtime-protocol.d.ts +2 -3
  98. package/dist/runtime-v2/runtime-protocol.d.ts.map +1 -1
  99. package/dist/runtime-v2/runtime-protocol.js +15 -10
  100. package/dist/runtime-v2/runtime-protocol.js.map +1 -1
  101. package/dist/runtime-v2/store/lifecycle/lease-manager.d.ts.map +1 -1
  102. package/dist/runtime-v2/store/lifecycle/lease-manager.js +11 -2
  103. package/dist/runtime-v2/store/lifecycle/lease-manager.js.map +1 -1
  104. package/dist/runtime-v2/tools/artificer-l2-tool-contract.d.ts.map +1 -1
  105. package/dist/runtime-v2/tools/artificer-l2-tool-contract.js +26 -0
  106. package/dist/runtime-v2/tools/artificer-l2-tool-contract.js.map +1 -1
  107. package/dist/telemetry-event.d.ts +2 -2
  108. package/dist/telemetry-event.d.ts.map +1 -1
  109. package/dist/telemetry-event.js +5 -0
  110. package/dist/telemetry-event.js.map +1 -1
  111. package/dist/workflow-funnel-loader.d.ts +0 -2
  112. package/dist/workflow-funnel-loader.d.ts.map +1 -1
  113. package/dist/workflow-funnel-loader.js.map +1 -1
  114. package/package.json +5 -1
  115. package/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.d.ts +0 -72
  116. package/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.d.ts.map +0 -1
  117. package/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.js +0 -837
  118. package/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.js.map +0 -1
  119. package/dist/runtime-v2/utils/cli-process-runner.d.ts +0 -35
  120. package/dist/runtime-v2/utils/cli-process-runner.d.ts.map +0 -1
  121. package/dist/runtime-v2/utils/cli-process-runner.js +0 -263
  122. package/dist/runtime-v2/utils/cli-process-runner.js.map +0 -1
@@ -1,837 +0,0 @@
1
- /**
2
- * OpenClawCliRuntimeAdapter — PDRuntimeAdapter implementation for openclaw-cli.
3
- *
4
- * One-shot run: startRun() spawns 'openclaw agent', blocks until CLI exits,
5
- * stores CliOutput in memory. pollRun()/fetchOutput() operate on stored state.
6
- *
7
- * Error mapping (D-04):
8
- * ENOENT (binary not found) → runtime_unavailable
9
- * timedOut → timeout
10
- * non-zero exit code → execution_failed
11
- * JSON parse failure → output_invalid
12
- * DiagnosticianOutputV1Schema validation failure → output_invalid
13
- */
14
- import { Value } from '@sinclair/typebox/value';
15
- import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
16
- import { tmpdir } from 'node:os';
17
- import { join } from 'node:path';
18
- import { runCliProcess } from '../utils/cli-process-runner.js';
19
- import { PDRuntimeError } from '../error-categories.js';
20
- import { DiagnosticianOutputV1Schema } from '../diagnostician-output.js';
21
- import { resolveOutputSchema } from './output-schema-registry.js';
22
- import { storeEmitter } from '../store/event-emitter.js';
23
- import { safeStringifyPreview, truncatePreview } from './output-repair-contract.js';
24
- import { redactTelemetryString } from '../feedback/redact-sensitive.js';
25
- import { redactSensitiveFields } from '../feedback/redact-sensitive.js';
26
- import { repairMalformedJson } from './json-extractor.js';
27
- /**
28
- * Persist an inter-process message payload so the runtime CLI can read it via
29
- * --message-file. When no workspaceDir is configured, the payload is written
30
- * inside a fresh mkdtemp root owned solely by this call; on any write failure
31
- * that root is removed here so failed writes cannot leak temp dirs
32
- * (PRI-827 / js/insecure-temporary-file). Workspace-owned directories are
33
- * never removed: they are shared, not exclusive to this call.
34
- */
35
- async function writeMessageFile(message, workspaceDir) {
36
- // Keep the payload in the PD workspace so cleanup and workspace ownership remain explicit.
37
- const baseDir = workspaceDir
38
- ? join(workspaceDir, '.pd', 'tmp')
39
- : await mkdtemp(join(tmpdir(), 'pd-msg-'));
40
- const cleanupDir = workspaceDir ? undefined : baseDir;
41
- try {
42
- await mkdir(baseDir, { recursive: true });
43
- const filePath = join(baseDir, `msg-${Date.now()}-${Math.random().toString(36).slice(2, 7)}.json`);
44
- await writeFile(filePath, message, 'utf8');
45
- return { filePath, cleanupDir };
46
- }
47
- catch (error) {
48
- if (cleanupDir !== undefined) {
49
- try {
50
- await rm(cleanupDir, { recursive: true, force: true });
51
- }
52
- catch {
53
- // Best effort: preserve the original error even if cleanup also fails.
54
- }
55
- }
56
- throw error;
57
- }
58
- }
59
- /**
60
- * Remove a message file created by writeMessageFile, including its unique temp
61
- * root when one was created. Best effort on both success and failure paths.
62
- */
63
- async function cleanupMessageFile(ref) {
64
- if (!ref)
65
- return;
66
- try {
67
- if (ref.cleanupDir !== undefined) {
68
- await rm(ref.cleanupDir, { recursive: true, force: true });
69
- }
70
- else {
71
- await rm(ref.filePath, { force: true });
72
- }
73
- }
74
- catch {
75
- // File may already be gone; ignore cleanup errors
76
- }
77
- }
78
- /**
79
- * Parse CliOutput stdout into the runtime's native result structure.
80
- *
81
- * OCRA-03 (revised): OpenClaw `--json` produces a CliOutput object (from openclaw's own
82
- * output.ts), NOT DiagnosticianOutputV1 directly. The LLM's diagnosis JSON lives inside
83
- * CliOutput.text as a string that may be:
84
- *
85
- * 1. Bare DiagnosticianOutputV1 JSON — { "valid": true, ... }
86
- * 2. Local envelope — { payloads: [{ text: "<json>" }], meta: ... }
87
- * 3. Gateway envelope — { result: { payloads: [{ text: "<json>" }] }, ... }
88
- *
89
- * This function normalises all three into a raw JSON value (string or object) that
90
- * DiagnosticianOutputV1Schema can validate.
91
- *
92
- * @returns The parsed JSON value, or null if stdout could not be parsed at all.
93
- */
94
- function extractPayloadFromCliOutput(stdout, stderr) {
95
- for (const source of [stdout, stderr ?? '']) {
96
- if (!source)
97
- continue;
98
- // Attempt 1: direct parse — covers case 1 (bare JSON)
99
- try {
100
- return JSON.parse(source);
101
- }
102
- catch { /* fall through */ }
103
- // Attempt 2: balanced-bracket extraction — handles text that surrounds JSON.
104
- // When multiple JSON objects are present (e.g. probe message + agent response in
105
- // stderr), return the LAST complete object — the agent's response comes after the
106
- // probe message that was injected into the session.
107
- const objects = [];
108
- let depth = 0;
109
- let start = -1;
110
- for (let i = 0; i < source.length; i++) {
111
- const ch = source[i];
112
- if (!ch)
113
- continue;
114
- if (ch === '{') {
115
- if (start === -1)
116
- start = i;
117
- depth++;
118
- }
119
- else if (ch === '}') {
120
- depth--;
121
- if (depth === 0 && start !== -1) {
122
- try {
123
- objects.push(JSON.parse(source.slice(start, i + 1)));
124
- }
125
- catch { /* keep scanning */ }
126
- start = -1;
127
- }
128
- }
129
- }
130
- if (objects.length > 0)
131
- return objects[objects.length - 1];
132
- }
133
- return null;
134
- }
135
- /**
136
- * Search for a single JSON object literal inside `text` using a balanced-bracket
137
- * scan. Returns the parsed object, or null if no valid object is found.
138
- */
139
- function extractJsonObject(text) {
140
- let depth = 0;
141
- let start = -1;
142
- for (let i = 0; i < text.length; i++) {
143
- const ch = text[i];
144
- if (ch === '{') {
145
- if (start === -1)
146
- start = i;
147
- depth++;
148
- }
149
- else if (ch === '}') {
150
- depth--;
151
- if (depth === 0 && start !== -1) {
152
- try {
153
- return JSON.parse(text.slice(start, i + 1));
154
- }
155
- catch { /* keep scanning */ }
156
- start = -1;
157
- }
158
- }
159
- }
160
- return null;
161
- }
162
- /**
163
- * Navigate the three possible envelope shapes to find the payload text that
164
- * contains the DiagnosticianOutputV1 JSON string.
165
- *
166
- * Returns the parsed JSON object (not a string) if extraction succeeds,
167
- * or null if the envelope structure doesn't match any known shape.
168
- */
169
- function extractDiagnosisFromEnvelope(parsed) {
170
- if (typeof parsed !== 'object' || parsed === null) {
171
- return null;
172
- }
173
- // Case 2: local envelope — { payloads: [{ text: "<json>" }] }
174
- const asLocal = parsed;
175
- if (Array.isArray(asLocal.payloads) && asLocal.payloads.length > 0) {
176
- const [first] = asLocal.payloads;
177
- if (first && typeof first.text === 'string') {
178
- const inner = first.text.trim();
179
- try {
180
- return JSON.parse(inner);
181
- }
182
- catch { /* fall through — try prose extraction */ }
183
- const extracted = extractJsonObject(inner);
184
- if (extracted !== null)
185
- return extracted;
186
- }
187
- }
188
- // Case 3: gateway envelope — { result: { payloads: [{ text: "<json>" }] } }
189
- const asGateway = parsed;
190
- if (asGateway.result &&
191
- typeof asGateway.result === 'object' &&
192
- Array.isArray(asGateway.result.payloads) &&
193
- asGateway.result.payloads.length > 0) {
194
- const [first] = asGateway.result.payloads;
195
- if (first && typeof first.text === 'string') {
196
- const inner = first.text.trim();
197
- try {
198
- return JSON.parse(inner);
199
- }
200
- catch { /* fall through */ }
201
- const extracted = extractJsonObject(inner);
202
- if (extracted !== null)
203
- return extracted;
204
- }
205
- }
206
- // No matching envelope shape
207
- return null;
208
- }
209
- /**
210
- * Extract the inner result object from an OpenClaw agent response envelope.
211
- *
212
- * OpenClaw `--json --local` wraps the agent's response text inside an envelope:
213
- * Case A — local envelope: { payloads: [{ text: "<json or prose>" }] }
214
- * Case B — gateway envelope: { result: { payloads: [{ text: "<json or prose>" }] } }
215
- * Case C — bare object: { ok: true } (no envelope, direct response)
216
- *
217
- * The inner text may be bare JSON (`{"ok":true}`) or wrapped in a small amount
218
- * of prose that surrounds the JSON. We balance-bracket extract to find the JSON
219
- * object within the text, then return the parsed result.
220
- *
221
- * Returns the parsed inner object on success, or null if extraction fails.
222
- */
223
- function extractProbeResult(parsed) {
224
- // Case A: local envelope — { payloads: [{ text: "..." }] }
225
- const asLocal = parsed;
226
- if (Array.isArray(asLocal.payloads) && asLocal.payloads.length > 0) {
227
- const [first] = asLocal.payloads;
228
- if (first && typeof first.text === 'string') {
229
- const inner = first.text.trim();
230
- try {
231
- return JSON.parse(inner);
232
- }
233
- catch { /* fall through — try prose extraction */ }
234
- const extracted = extractJsonObject(inner);
235
- if (extracted !== null)
236
- return extracted;
237
- }
238
- }
239
- // Case B: gateway envelope — { result: { payloads: [{ text: "..." }] } }
240
- const asGateway = parsed;
241
- if (asGateway.result &&
242
- typeof asGateway.result === 'object' &&
243
- Array.isArray(asGateway.result.payloads) &&
244
- asGateway.result.payloads.length > 0) {
245
- const [first] = asGateway.result.payloads;
246
- if (first && typeof first.text === 'string') {
247
- const inner = first.text.trim();
248
- try {
249
- return JSON.parse(inner);
250
- }
251
- catch { /* fall through */ }
252
- const extracted = extractJsonObject(inner);
253
- if (extracted !== null)
254
- return extracted;
255
- }
256
- }
257
- // Case C: bare object — already the result
258
- if (typeof parsed === 'object' && parsed !== null) {
259
- return parsed;
260
- }
261
- return null;
262
- }
263
- export class OpenClawCliRuntimeAdapter {
264
- runStateMap = new Map();
265
- runtimeMode;
266
- workspaceDir;
267
- agentId;
268
- eventEmitter;
269
- cliProcessRunner;
270
- constructor(options) {
271
- this.runtimeMode = options.runtimeMode;
272
- this.workspaceDir = options.workspaceDir;
273
- this.agentId = options.agentId ?? 'main';
274
- this.eventEmitter = options.eventEmitter ?? storeEmitter;
275
- this.cliProcessRunner = options.cliProcessRunner ?? runCliProcess;
276
- }
277
- // OCRA-01: kind() returns RuntimeKind literal 'openclaw-cli'
278
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
279
- kind() {
280
- return 'openclaw-cli';
281
- }
282
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
283
- async getCapabilities() {
284
- return {
285
- supportsStructuredJsonOutput: true,
286
- supportsToolUse: false,
287
- supportsWorkingDirectory: false,
288
- supportsModelSelection: false,
289
- supportsLongRunningSessions: false,
290
- supportsCancellation: true,
291
- supportsArtifactWriteBack: false,
292
- supportsConcurrentRuns: false,
293
- supportsStreaming: false,
294
- };
295
- }
296
- /**
297
- * Probe runtime health by running a minimal openclaw command.
298
- *
299
- * Verifies:
300
- * 1. openclaw binary is executable (run `openclaw --version`)
301
- * 2. The configured agent is available (run `openclaw agents list --json`)
302
- * 3. The agent can actually respond (one-shot minimal probe with --agent)
303
- *
304
- * Returns healthy=false with appropriate error category when checks fail.
305
- * Per DPB-09: No silent fallback — failures are reported accurately.
306
- * Per P1 hard gate: Binary-only check is insufficient; must verify real invocation.
307
- */
308
- async healthCheck() {
309
- const warnings = [];
310
- let healthy = true;
311
- let degraded = false;
312
- // Probe 1: verify openclaw binary is executable
313
- try {
314
- const versionResult = await this.cliProcessRunner({
315
- command: 'openclaw',
316
- args: ['--version'],
317
- cwd: this.workspaceDir,
318
- timeoutMs: 10_000,
319
- });
320
- if (versionResult.spawnError === 'ENOENT') {
321
- return {
322
- healthy: false,
323
- degraded: false,
324
- warnings: ['openclaw binary not found'],
325
- lastCheckedAt: new Date().toISOString(),
326
- };
327
- }
328
- if (versionResult.spawnError === 'EACCES' || versionResult.spawnError === 'EMFILE') {
329
- return {
330
- healthy: false,
331
- degraded: false,
332
- warnings: [`openclaw binary not executable: ${versionResult.spawnError}`],
333
- lastCheckedAt: new Date().toISOString(),
334
- };
335
- }
336
- if (versionResult.timedOut) {
337
- return {
338
- healthy: false,
339
- degraded: false,
340
- warnings: ['openclaw --version timed out'],
341
- lastCheckedAt: new Date().toISOString(),
342
- };
343
- }
344
- if (versionResult.exitCode !== null && versionResult.exitCode !== 0) {
345
- degraded = true;
346
- warnings.push(`openclaw --version exited with code ${versionResult.exitCode}`);
347
- }
348
- }
349
- catch (err) {
350
- return {
351
- healthy: false,
352
- degraded: false,
353
- warnings: [err instanceof Error ? err.message : String(err)],
354
- lastCheckedAt: new Date().toISOString(),
355
- };
356
- }
357
- // Probe 2: verify the configured agent is available via agents list
358
- // P1 fix: Must verify agent exists, not just the binary
359
- // Using `openclaw agents list --json` to check if agentId is registered
360
- try {
361
- const listArgs = ['agents', 'list', '--json'];
362
- if (this.runtimeMode === 'local') {
363
- listArgs.push('--local');
364
- }
365
- const listResult = await this.cliProcessRunner({
366
- command: 'openclaw',
367
- args: listArgs,
368
- cwd: this.workspaceDir,
369
- timeoutMs: 15_000,
370
- });
371
- if (listResult.spawnError === 'ENOENT') {
372
- return {
373
- healthy: false,
374
- degraded: false,
375
- warnings: ['openclaw binary not found'],
376
- lastCheckedAt: new Date().toISOString(),
377
- };
378
- }
379
- if (listResult.spawnError === 'EACCES' || listResult.spawnError === 'EMFILE') {
380
- return {
381
- healthy: false,
382
- degraded: false,
383
- warnings: [`openclaw binary not executable: ${listResult.spawnError}`],
384
- lastCheckedAt: new Date().toISOString(),
385
- };
386
- }
387
- if (listResult.timedOut) {
388
- degraded = true;
389
- warnings.push(`openclaw agents list timed out`);
390
- }
391
- else if (listResult.exitCode !== null && listResult.exitCode !== 0) {
392
- // agents list failed — degraded state
393
- degraded = true;
394
- warnings.push(`openclaw agents list exited with code ${listResult.exitCode}`);
395
- }
396
- else {
397
- // Parse agent list and check if target agent exists
398
- // The output may contain config warnings/ANSI codes before the JSON array
399
- try {
400
- const jsonStart = listResult.stdout.indexOf('[');
401
- if (jsonStart === -1)
402
- throw new Error('No JSON array found');
403
- const jsonStr = listResult.stdout.slice(jsonStart);
404
- const parsed = JSON.parse(jsonStr);
405
- // agents list returns an array of agent objects (each with an `id` field)
406
- const agentIds = parsed.map((a) => typeof a === 'string' ? a : a.id);
407
- if (!agentIds.includes(this.agentId)) {
408
- healthy = false;
409
- warnings.push(`agent '${this.agentId}' not found in agents list`);
410
- }
411
- }
412
- catch {
413
- // JSON parse failed — cannot verify agent, treat as degraded
414
- degraded = true;
415
- warnings.push(`failed to parse agents list output`);
416
- }
417
- }
418
- }
419
- catch (err) {
420
- // Any error in the agent probe is a hard failure
421
- return {
422
- healthy: false,
423
- degraded: false,
424
- warnings: [err instanceof Error ? err.message : String(err)],
425
- lastCheckedAt: new Date().toISOString(),
426
- };
427
- }
428
- // Probe 3: One-shot minimal agent invocation (P1 hard gate)
429
- // Verifies the agent can actually respond, not just that it exists in the list.
430
- // This catches plugin loading failures, permission issues, etc.
431
- let probeMessageRef = undefined;
432
- try {
433
- // Unique session ID for probe to avoid lock conflicts with concurrent runs
434
- const probeSessionId = `pd-runtime-probe-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
435
- probeMessageRef = await writeMessageFile(JSON.stringify({ probe: 'pd-runtime-v2', instruction: 'reply with {"ok":true} only' }), this.workspaceDir);
436
- const probeArgs = [
437
- 'agent',
438
- '--agent', this.agentId,
439
- '--message-file', probeMessageRef.filePath,
440
- '--session-id', probeSessionId,
441
- '--timeout', '240',
442
- '--json',
443
- ];
444
- if (this.runtimeMode === 'local') {
445
- probeArgs.push('--local');
446
- }
447
- const probeResult = await this.cliProcessRunner({
448
- command: 'openclaw',
449
- args: probeArgs,
450
- cwd: this.workspaceDir,
451
- timeoutMs: 240_000, // 240s — agent init + bootstrap + skill loading can take >60s on cold start
452
- });
453
- if (probeResult.spawnError === 'ENOENT') {
454
- return {
455
- healthy: false,
456
- degraded: false,
457
- warnings: ['openclaw binary not found'],
458
- lastCheckedAt: new Date().toISOString(),
459
- };
460
- }
461
- if (probeResult.spawnError === 'EACCES' || probeResult.spawnError === 'EMFILE') {
462
- return {
463
- healthy: false,
464
- degraded: false,
465
- warnings: [`openclaw binary not executable: ${probeResult.spawnError}`],
466
- lastCheckedAt: new Date().toISOString(),
467
- };
468
- }
469
- if (probeResult.timedOut) {
470
- return {
471
- healthy: false,
472
- degraded: false,
473
- warnings: ['openclaw agent probe timed out'],
474
- lastCheckedAt: new Date().toISOString(),
475
- };
476
- }
477
- if (probeResult.exitCode !== null && probeResult.exitCode !== 0) {
478
- return {
479
- healthy: false,
480
- degraded: false,
481
- warnings: [
482
- `openclaw agent '${this.agentId}' probe failed with exit code ${probeResult.exitCode}`,
483
- ...(probeResult.stderr ? [`stderr: ${probeResult.stderr.substring(0, 2000)}`] : []),
484
- ],
485
- lastCheckedAt: new Date().toISOString(),
486
- };
487
- }
488
- // exit code 0 — verify the output is parseable JSON
489
- const rawParsed = extractPayloadFromCliOutput(probeResult.stdout, probeResult.stderr);
490
- if (rawParsed === null) {
491
- return {
492
- healthy: false,
493
- degraded: false,
494
- warnings: ['openclaw agent probe produced unparseable output'],
495
- lastCheckedAt: new Date().toISOString(),
496
- };
497
- }
498
- // Extract the inner result from the OpenClaw envelope and verify {ok: true}
499
- const result = extractProbeResult(rawParsed);
500
- if (typeof result !== 'object' ||
501
- result === null ||
502
- !Object.hasOwn(result, 'ok') ||
503
- result.ok !== true) {
504
- const excerpt = rawParsed && typeof rawParsed === 'object'
505
- ? JSON.stringify(rawParsed).substring(0, 2000)
506
- : String(rawParsed).substring(0, 2000);
507
- return {
508
- healthy: false,
509
- degraded: false,
510
- warnings: [
511
- `openclaw agent '${this.agentId}' probe returned unexpected result: ${excerpt}`,
512
- ],
513
- lastCheckedAt: new Date().toISOString(),
514
- };
515
- }
516
- }
517
- catch (err) {
518
- return {
519
- healthy: false,
520
- degraded: false,
521
- warnings: [err instanceof Error ? err.message : String(err)],
522
- lastCheckedAt: new Date().toISOString(),
523
- };
524
- }
525
- finally {
526
- await cleanupMessageFile(probeMessageRef);
527
- }
528
- return {
529
- healthy,
530
- degraded,
531
- warnings,
532
- lastCheckedAt: new Date().toISOString(),
533
- };
534
- }
535
- /**
536
- * Map CLI output to telemetry event type and error category.
537
- */
538
- static mapCliResultToTelemetry(cliOutput) {
539
- // spawnError is set by runCliProcess on ENOENT/EACCES/EMFILE — use it
540
- if (cliOutput.spawnError === 'ENOENT') {
541
- return { telemetryEventType: 'runtime_invocation_failed', errorCategory: 'runtime_unavailable', stderrExcerpt: cliOutput.stderr };
542
- }
543
- if (cliOutput.spawnError === 'EACCES' || cliOutput.spawnError === 'EMFILE') {
544
- return { telemetryEventType: 'runtime_invocation_failed', errorCategory: 'execution_failed', stderrExcerpt: cliOutput.stderr };
545
- }
546
- if (cliOutput.timedOut) {
547
- return { telemetryEventType: 'runtime_invocation_failed', errorCategory: 'timeout', stderrExcerpt: cliOutput.stderr };
548
- }
549
- if (cliOutput.exitCode !== null && cliOutput.exitCode !== 0) {
550
- return { telemetryEventType: 'runtime_invocation_failed', errorCategory: 'execution_failed', stderrExcerpt: cliOutput.stderr };
551
- }
552
- return { telemetryEventType: 'runtime_invocation_succeeded', errorCategory: undefined, stderrExcerpt: undefined };
553
- }
554
- // OCRA-02: startRun synchronously invokes openclaw agent with correct CLI args
555
- async startRun(input) {
556
- // PRI-966 G2 P2 contract (declared capability boundary): StartRunInput.
557
- // maxTokens is a per-request model output budget. This adapter delegates
558
- // to the OpenClaw host CLI process, which exposes NO per-request
559
- // output-token control — honoring the field here is impossible, and
560
- // silently ignoring it is forbidden (rc-9). Fail loud as input_invalid
561
- // BEFORE any CLI process starts.
562
- if (input.maxTokens !== undefined) {
563
- throw new PDRuntimeError('input_invalid', `OpenClawCliRuntimeAdapter does not support StartRunInput.maxTokens (received ${String(input.maxTokens)}): the host CLI owns its own model calls and exposes no per-request output-token control.`, {
564
- maxTokens: input.maxTokens,
565
- nextAction: 'Use a PiAi/L2 runtime adapter for per-request output budgets, or omit maxTokens for this runtime.',
566
- });
567
- }
568
- const runId = crypto.randomUUID();
569
- const startedAt = new Date().toISOString();
570
- // Initialize state — cliOutput null until CLI completes.
571
- // Capture outputSchemaRef so fetchOutput can validate against the runner's
572
- // expected schema (e.g. diag-rootcause-output-v1 for Stage A) instead of
573
- // hardcoding DiagnosticianOutputV1Schema.
574
- const state = { runId, startedAt, cliOutput: null, completed: false, outputSchemaRef: input.outputSchemaRef };
575
- this.runStateMap.set(runId, state);
576
- // Build CLI args per D-02.
577
- // PRI-633: the run's base-layer systemPrompt (agent role + protocol) is
578
- // folded back into the message file. The OpenClaw host keeps full
579
- // ownership of its own agent system prompt (DPB-07), so PD continues to
580
- // deliver its instructions through the message channel; only the
581
- // placement changed (appended section after the payload JSON instead of
582
- // an instruction field inside it). Absent systemPrompt → unchanged bytes.
583
- const payloadJson = typeof input.inputPayload === 'string'
584
- ? input.inputPayload
585
- : JSON.stringify(input.inputPayload);
586
- const jsonPayload = input.systemPrompt
587
- ? payloadJson + '\n\n' + input.systemPrompt
588
- : payloadJson;
589
- const timeoutSeconds = Math.ceil((input.timeoutMs ?? 600000) / 1000);
590
- const agentId = input.agentSpec?.agentId ?? 'main';
591
- const messageRef = await writeMessageFile(jsonPayload, this.workspaceDir);
592
- // DPB-09 (LOCKED): --local only passed when runtimeMode === 'local'
593
- // HG-03 (HARD GATE): No silent fallback — must be explicit
594
- // Unique session ID per run — avoids session lock conflicts and context pollution
595
- // between concurrent or sequential PD diagnostician runs on the same agent.
596
- const sessionId = `pd-runtime-${runId}`;
597
- const args = [
598
- 'agent',
599
- '--agent', agentId,
600
- '--message-file', messageRef.filePath,
601
- '--session-id', sessionId,
602
- '--json',
603
- ];
604
- if (this.runtimeMode === 'local') {
605
- args.push('--local');
606
- }
607
- args.push('--timeout', String(timeoutSeconds));
608
- // DPB-08 (LOCKED): Pass workspaceDir as cwd for workspace boundary control
609
- // HG-02 (HARD GATE): Two distinct boundaries — cwd controls PD workspace
610
- // TELE-02: runtime_invocation_started — CLI process about to spawn
611
- this.eventEmitter.emitTelemetry({
612
- eventType: 'runtime_invocation_started',
613
- traceId: input.taskRef?.taskId ?? runId,
614
- timestamp: new Date().toISOString(),
615
- sessionId: 'openclaw-cli-adapter',
616
- agentId: 'openclaw-cli-adapter',
617
- payload: {
618
- runId,
619
- runtimeKind: 'openclaw-cli',
620
- runtimeMode: this.runtimeMode,
621
- timeoutMs: input.timeoutMs,
622
- },
623
- });
624
- const cliOutput = await (async () => {
625
- try {
626
- return await this.cliProcessRunner({
627
- command: 'openclaw',
628
- args,
629
- cwd: this.workspaceDir,
630
- timeoutMs: input.timeoutMs,
631
- });
632
- }
633
- finally {
634
- await cleanupMessageFile(messageRef);
635
- }
636
- })();
637
- // Store result in memory
638
- state.cliOutput = cliOutput;
639
- state.completed = true;
640
- // TELE-03: runtime_invocation_succeeded/failed — CLI process completed
641
- const endedAt = new Date().toISOString();
642
- const { telemetryEventType, errorCategory, stderrExcerpt } = OpenClawCliRuntimeAdapter.mapCliResultToTelemetry(cliOutput);
643
- this.eventEmitter.emitTelemetry({
644
- eventType: telemetryEventType,
645
- traceId: input.taskRef?.taskId ?? runId,
646
- timestamp: endedAt,
647
- sessionId: 'openclaw-cli-adapter',
648
- agentId: 'openclaw-cli-adapter',
649
- payload: {
650
- runId,
651
- runtimeKind: 'openclaw-cli',
652
- runtimeMode: this.runtimeMode,
653
- exitCode: cliOutput.exitCode,
654
- timedOut: cliOutput.timedOut,
655
- ...(errorCategory ? { errorCategory } : {}),
656
- ...(stderrExcerpt ? { stderrExcerpt } : {}),
657
- },
658
- });
659
- return { runId, runtimeKind: 'openclaw-cli', startedAt };
660
- }
661
- async pollRun(runId) {
662
- const state = this.runStateMap.get(runId);
663
- if (!state) {
664
- throw new PDRuntimeError('output_invalid', `Run ${runId} not found`);
665
- }
666
- if (!state.completed || !state.cliOutput) {
667
- return { runId, status: 'running', startedAt: state.startedAt };
668
- }
669
- const { cliOutput } = state;
670
- if (cliOutput.timedOut) {
671
- return {
672
- runId,
673
- status: 'timed_out',
674
- startedAt: state.startedAt,
675
- endedAt: new Date().toISOString(),
676
- reason: 'CLI timeout exceeded',
677
- };
678
- }
679
- if (cliOutput.exitCode !== null && cliOutput.exitCode !== 0) {
680
- const stderrExcerpt = cliOutput.stderr
681
- ? cliOutput.stderr.substring(0, 2000)
682
- : undefined;
683
- return {
684
- runId,
685
- status: 'failed',
686
- startedAt: state.startedAt,
687
- endedAt: new Date().toISOString(),
688
- reason: stderrExcerpt
689
- ? `CLI exited with code ${cliOutput.exitCode}. stderr: ${stderrExcerpt}`
690
- : `CLI exited with code ${cliOutput.exitCode}`,
691
- };
692
- }
693
- return {
694
- runId,
695
- status: 'succeeded',
696
- startedAt: state.startedAt,
697
- endedAt: new Date().toISOString(),
698
- };
699
- }
700
- // OCRA-03: fetchOutput parses CliOutput.stdout and returns DiagnosticianOutputV1
701
- // OCRA-05: Malformed JSON repair + evidence persistence on output_invalid
702
- async fetchOutput(runId) {
703
- const state = this.runStateMap.get(runId);
704
- if (!state || !state.completed || !state.cliOutput) {
705
- throw new PDRuntimeError('output_invalid', `Run ${runId} not completed`);
706
- }
707
- const { cliOutput } = state;
708
- // OCRA-04: Map ENOENT (binary not found) to runtime_unavailable
709
- // spawnError is set explicitly by runCliProcess when spawn fails
710
- if (cliOutput.spawnError === 'ENOENT') {
711
- throw new PDRuntimeError('runtime_unavailable', 'openclaw binary not found');
712
- }
713
- // OCRA-04: Map timeout to timeout
714
- if (cliOutput.timedOut) {
715
- throw new PDRuntimeError('timeout', 'CLI timeout exceeded');
716
- }
717
- // OCRA-04: Map non-zero exit to execution_failed
718
- if (cliOutput.exitCode !== null && cliOutput.exitCode !== 0) {
719
- const stderrExcerpt = cliOutput.stderr
720
- ? cliOutput.stderr.substring(0, 2000)
721
- : undefined;
722
- throw new PDRuntimeError('execution_failed', stderrExcerpt
723
- ? `CLI exited with code ${cliOutput.exitCode}. stderr: ${stderrExcerpt}`
724
- : `CLI exited with code ${cliOutput.exitCode}`);
725
- }
726
- // OCRA-03: Parse CliOutput — the stdout may be raw JSON, or wrapped in an OpenClaw envelope
727
- // (local or gateway). First extract the JSON value, then navigate to the diagnosis JSON.
728
- let parsed;
729
- const rawParsed = extractPayloadFromCliOutput(cliOutput.stdout, cliOutput.stderr);
730
- if (rawParsed !== null) {
731
- // Try envelope extraction first (cases 2 and 3)
732
- const fromEnvelope = extractDiagnosisFromEnvelope(rawParsed);
733
- parsed = fromEnvelope ?? rawParsed;
734
- }
735
- else {
736
- parsed = null;
737
- }
738
- // OCRA-05: When initial JSON parse fails, attempt bounded repair via extractJsonObject
739
- // and repairMalformedJson on the raw stdout/stderr text before giving up.
740
- if (parsed === null) {
741
- // Repair step 1: scan raw text for a JSON object using balanced-bracket extraction
742
- const repairSources = [cliOutput.stdout, cliOutput.stderr ?? ''];
743
- for (const source of repairSources) {
744
- if (!source)
745
- continue;
746
- const repaired = extractJsonObject(source);
747
- if (repaired !== null) {
748
- parsed = repaired;
749
- break;
750
- }
751
- }
752
- // Repair step 2: attempt syntactic repair of malformed JSON (e.g., unescaped quotes)
753
- if (parsed === null) {
754
- for (const source of repairSources) {
755
- if (!source)
756
- continue;
757
- const repaired = repairMalformedJson(source);
758
- if (repaired !== null) {
759
- parsed = repaired;
760
- break;
761
- }
762
- }
763
- }
764
- if (parsed === null) {
765
- // OCRA-05: Evidence persistence on output_invalid — no silent fallback (ERR-002)
766
- // ERR-055/056: redact tokens/paths from raw output before persisting preview
767
- const rawText = cliOutput.stdout || cliOutput.stderr || '';
768
- const redacted = redactTelemetryString(rawText.substring(0, 2000));
769
- const boundedPreview = truncatePreview(typeof redacted === 'string' ? redacted : '', 500);
770
- throw new PDRuntimeError('output_invalid', 'Failed to parse CLI output as JSON', {
771
- parseFailureReason: 'no_json_object_found',
772
- boundedRawOutputPreview: boundedPreview,
773
- nextAction: 'Check openclaw agent output format; ensure agent returns valid JSON',
774
- });
775
- }
776
- }
777
- // OCRA-03: Validate against the schema named by outputSchemaRef.
778
- // The split diagnostician pipeline runs 3 stages with DIFFERENT output
779
- // shapes: Stage A (diag_rootcause) → DiagRootCauseOutputV1, Stage B
780
- // (diag_distiller) → DiagDistillerOutputV1, Stage C (diag_router) →
781
- // DiagnosticianOutputV1. Each runner passes outputSchemaRef to name its
782
- // expected shape. Previously this hardcoded DiagnosticianOutputV1Schema,
783
- // which rejected every valid Stage A/B output → 0 candidates.
784
- // Resolution rules (rc-3-fail-loud-missing):
785
- // - outputSchemaRef present + in registry → validate against that schema
786
- // - outputSchemaRef present + NOT in registry → fail loud (unknown ref)
787
- // - outputSchemaRef absent → fall back to DiagnosticianOutputV1Schema
788
- // (backward compat for legacy callers / monolithic diagnostician path)
789
- let schema;
790
- if (state.outputSchemaRef) {
791
- const resolved = resolveOutputSchema(state.outputSchemaRef);
792
- if (!resolved) {
793
- throw new PDRuntimeError('output_invalid', `Unknown outputSchemaRef: ${state.outputSchemaRef}`, {
794
- parseFailureReason: 'unknown_output_schema_ref',
795
- outputSchemaRef: state.outputSchemaRef,
796
- nextAction: `Register the schema in OUTPUT_SCHEMA_REGISTRY or pass a known ref (e.g. 'diag-rootcause-output-v1')`,
797
- });
798
- }
799
- schema = resolved;
800
- }
801
- else {
802
- schema = DiagnosticianOutputV1Schema;
803
- }
804
- // OCRA-05: On schema mismatch, include evidence with bounded preview (ERR-014/016/017)
805
- // ERR-055/056: redact sensitive fields from parsed payload before persisting preview
806
- if (!Value.Check(schema, parsed)) {
807
- const schemaErrors = [...Value.Errors(schema, parsed)]
808
- .slice(0, 10)
809
- .map(e => ({ path: e.path, message: e.message }));
810
- const redacted = redactSensitiveFields(parsed);
811
- const boundedPreview = redacted.ok
812
- ? safeStringifyPreview(redacted.value, 500)
813
- : safeStringifyPreview(parsed, 500);
814
- throw new PDRuntimeError('output_invalid', `CLI output does not match ${state.outputSchemaRef ?? 'diagnostician-output-v1'} schema`, {
815
- parseFailureReason: 'schema_validation_failed',
816
- outputSchemaRef: state.outputSchemaRef ?? 'diagnostician-output-v1',
817
- boundedRawOutputPreview: boundedPreview,
818
- schemaErrors,
819
- nextAction: 'Review schema errors; check LLM output format and required fields',
820
- });
821
- }
822
- return { runId, payload: parsed };
823
- }
824
- async cancelRun(runId) {
825
- // For one-shot run: if still in-flight, mark as cancelled
826
- const state = this.runStateMap.get(runId);
827
- if (state && !state.completed) {
828
- state.completed = true;
829
- }
830
- }
831
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
832
- async fetchArtifacts(_runId) {
833
- // No artifact refs produced by openclaw-cli adapter directly
834
- return [];
835
- }
836
- }
837
- //# sourceMappingURL=openclaw-cli-runtime-adapter.js.map