@principles/pd-cli 1.147.16 → 1.147.17
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/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-once.js +23 -7
- package/dist/commands/runtime-internalization-run-once.js.map +1 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.js +4 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +1 -1
- package/dist/services/resolve-runtime-from-pd-config.d.ts +28 -2
- package/dist/services/resolve-runtime-from-pd-config.d.ts.map +1 -1
- package/dist/services/resolve-runtime-from-pd-config.js +19 -6
- package/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
- package/dist/services/rulehost-readiness.js +1 -1
- package/dist/services/rulehost-readiness.js.map +1 -1
- package/dist/services/runtime-adapter-resolver.d.ts +14 -1
- package/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
- package/dist/services/runtime-adapter-resolver.js +4 -1
- package/dist/services/runtime-adapter-resolver.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/runtime-internalization-run-once.ts +23 -7
- package/src/services/__tests__/runtime-adapter-resolver.test.ts +4 -1
- package/src/services/resolve-runtime-from-pd-config.ts +44 -6
- package/src/services/rulehost-readiness.ts +1 -1
- package/src/services/runtime-adapter-resolver.ts +18 -2
- package/tests/commands/pri-393-runtime-config-unification.test.ts +13 -11
- package/tests/commands/runtime-internalization-run-once-language.test.ts +12 -0
- package/tests/commands/runtime-internalization-run-once-real-config.test.ts +237 -0
- package/tests/commands/runtime-internalization-run-once.test.ts +21 -5
- package/tests/services/resolve-runtime-from-pd-config.test.ts +3 -3
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
TestDoubleRuntimeAdapter,
|
|
19
19
|
} from '@principles/core/runtime-v2';
|
|
20
20
|
import type { WakeOnceResult, DreamerRunnerResult, PhilosopherRunnerResult, ScribeRunnerResult, ArtificerRunnerResult, EvaluatorRunnerResult, RolloutReviewerRunnerResult, PDRuntimeAdapter, PeerRunnerKind, OutputLanguage } from '@principles/core/runtime-v2';
|
|
21
|
-
import {
|
|
21
|
+
import { resolveRuntimeConfigForAgent, AGENT_NAME_FOR_TASK_KIND, isRuntimeConfigError } from '@principles/core/runtime-v2';
|
|
22
22
|
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
23
23
|
import { readOutputLanguageFromWorkspace } from '../config-reader.js';
|
|
24
24
|
import { loadPdConfig } from '../services/pd-config-loader.js';
|
|
@@ -501,13 +501,24 @@ export async function handleRuntimeInternalizationRunOnce(opts: RunOnceOptions):
|
|
|
501
501
|
const outputLangResult = readOutputLanguageFromWorkspace(workspaceDir);
|
|
502
502
|
const outputLanguage: OutputLanguage | undefined = outputLangResult.outputLanguage;
|
|
503
503
|
|
|
504
|
-
// PRI-670: profile timeout fallback for the runner deadline.
|
|
505
|
-
//
|
|
506
|
-
//
|
|
507
|
-
//
|
|
504
|
+
// PRI-670: profile timeout fallback for the runner deadline. PRI-719: the
|
|
505
|
+
// profile resolves for the SELECTED RUNNER's agent binding — the shared
|
|
506
|
+
// resolver used to read the diagnostician binding for every stage, silently
|
|
507
|
+
// ignoring per-agent runtimeProfile declarations (EP002-R2 F4). On any
|
|
508
|
+
// resolution error, fall back to the 300s default rather than refusing the
|
|
509
|
+
// run: the flag-off/legacy behavior was 300s.
|
|
510
|
+
// PRI-719 review: explicit run-once is a PEER execution path — its scope
|
|
511
|
+
// is the internalization_full_chain flag, not agents[kind].enabled (the
|
|
512
|
+
// shipped default disables evaluator/rolloutReviewer, which must not block
|
|
513
|
+
// an explicit stage run). Same ignoreAgentEnabled semantics as the
|
|
514
|
+
// auto-consumer; `enabled` keeps gating the diagnostician bridge.
|
|
515
|
+
const runnerAgentName = AGENT_NAME_FOR_TASK_KIND[runnerKind];
|
|
508
516
|
let profileTimeoutMs: number | undefined;
|
|
509
|
-
if (effectiveConfig) {
|
|
510
|
-
const resolved =
|
|
517
|
+
if (effectiveConfig && runnerAgentName !== undefined) {
|
|
518
|
+
const resolved = resolveRuntimeConfigForAgent(effectiveConfig, runnerAgentName, {
|
|
519
|
+
getEnvVar: (name) => process.env[name],
|
|
520
|
+
ignoreAgentEnabled: true,
|
|
521
|
+
});
|
|
511
522
|
if (!isRuntimeConfigError(resolved)) {
|
|
512
523
|
profileTimeoutMs = resolved.timeoutMs;
|
|
513
524
|
}
|
|
@@ -544,6 +555,11 @@ export async function handleRuntimeInternalizationRunOnce(opts: RunOnceOptions):
|
|
|
544
555
|
runtimeKind,
|
|
545
556
|
workspaceDir,
|
|
546
557
|
runnerKind,
|
|
558
|
+
// PRI-719: resolve the SELECTED runner's own agent binding so its
|
|
559
|
+
// declared runtimeProfile governs the adapter — with the SAME
|
|
560
|
+
// ignoreAgentEnabled peer semantics as the timeout resolution above
|
|
561
|
+
// (declared profile == timeout profile == adapter profile).
|
|
562
|
+
...(runnerAgentName !== undefined ? { agentName: runnerAgentName, ignoreAgentEnabled: true } : {}),
|
|
547
563
|
timeoutMs: cliTimeoutMs,
|
|
548
564
|
allowTestDouble: true,
|
|
549
565
|
testDoublePayloadBuilder: () => buildTestDoubleAdapter(runnerKind, wakeResult.taskId),
|
|
@@ -422,7 +422,10 @@ describe('resolveRuntimeAdapterFromConfig (PRI-431)', () => {
|
|
|
422
422
|
});
|
|
423
423
|
|
|
424
424
|
expect(result).toHaveProperty('__type', 'PiAiRuntimeAdapter');
|
|
425
|
-
|
|
425
|
+
// PRI-719 (+review): the resolver threads the optional per-call options
|
|
426
|
+
// (agentName + ignoreAgentEnabled, both undefined when the caller does
|
|
427
|
+
// not select a peer stage).
|
|
428
|
+
expect(mockResolveRuntimeFromPdConfig).toHaveBeenCalledWith('/ws', { agentName: undefined, ignoreAgentEnabled: undefined });
|
|
426
429
|
});
|
|
427
430
|
|
|
428
431
|
it('delegates to openclaw-cli when config resolves to openclaw-cli', () => {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
|
-
|
|
17
|
+
resolveRuntimeConfigForAgent,
|
|
18
18
|
isRuntimeConfigError,
|
|
19
19
|
resolveAgentRuntimeBinding,
|
|
20
20
|
} from '@principles/core/runtime-v2';
|
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
RuntimeConfig,
|
|
24
24
|
RuntimeConfigError,
|
|
25
25
|
} from '@principles/core/runtime-v2';
|
|
26
|
+
import type { InternalAgentName } from '@principles/core/runtime-v2';
|
|
26
27
|
import { loadPdConfig } from './pd-config-loader.js';
|
|
27
28
|
import type { PdConfigLoadResult } from './pd-config-loader.js';
|
|
28
29
|
|
|
@@ -64,6 +65,31 @@ function buildProfileLabel(profileId: string, profile: { type: string; provider?
|
|
|
64
65
|
return `pi-ai: ${profile.provider ?? 'unknown'}/${profile.model ?? 'unknown'}`;
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
/**
|
|
69
|
+
* PRI-719: per-call resolution options for resolveRuntimeFromPdConfig.
|
|
70
|
+
*/
|
|
71
|
+
export interface ResolveRuntimeFromPdConfigOptions {
|
|
72
|
+
/** Env var accessor, defaults to process.env. */
|
|
73
|
+
readonly getEnvVar?: (name: string) => string | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Whose `internalAgents.agents[agent]` .runtimeProfile binding resolves
|
|
76
|
+
* (default 'diagnostician' — the pain-signal bridge path). run-once passes
|
|
77
|
+
* the selected runner's agent so each stage executes on ITS declared
|
|
78
|
+
* profile (EP002-R2 F4).
|
|
79
|
+
*/
|
|
80
|
+
readonly agentName?: InternalAgentName;
|
|
81
|
+
/**
|
|
82
|
+
* PRI-719 review: resolve the binding even when the agent is disabled.
|
|
83
|
+
* Peer execution scope (auto-consumer AND explicit run-once) is governed
|
|
84
|
+
* by the internalization_full_chain FLAG, not by
|
|
85
|
+
* internalAgents.agents[kind].enabled — the shipped default config
|
|
86
|
+
* disables philosopher/evaluator/rolloutReviewer yet the full chain runs
|
|
87
|
+
* them. `enabled` keeps gating the diagnostician bridge (callers that
|
|
88
|
+
* omit this option honor it, PRI-638 semantics unchanged).
|
|
89
|
+
*/
|
|
90
|
+
readonly ignoreAgentEnabled?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
67
93
|
/**
|
|
68
94
|
* Resolve runtime configuration exclusively from .pd/config.yaml.
|
|
69
95
|
*
|
|
@@ -72,13 +98,15 @@ function buildProfileLabel(profileId: string, profile: { type: string; provider?
|
|
|
72
98
|
* called by probe/run-once/diagnose/pain-retry.
|
|
73
99
|
*
|
|
74
100
|
* @param workspaceDir - The resolved workspace directory.
|
|
75
|
-
* @param
|
|
101
|
+
* @param options - Per-call resolution options (defaults = diagnostician
|
|
102
|
+
* binding, honor enabled, process.env).
|
|
76
103
|
* @returns Resolved runtime config with legacy warnings.
|
|
77
104
|
*/
|
|
78
105
|
export function resolveRuntimeFromPdConfig(
|
|
79
106
|
workspaceDir: string,
|
|
80
|
-
|
|
107
|
+
options: ResolveRuntimeFromPdConfigOptions = {},
|
|
81
108
|
): ResolvedRuntimeFromPdConfig {
|
|
109
|
+
const { getEnvVar = (name) => process.env[name], agentName = 'diagnostician', ignoreAgentEnabled = false } = options;
|
|
82
110
|
const configLoadResult = loadPdConfig(workspaceDir);
|
|
83
111
|
|
|
84
112
|
// Malformed config → fail loud. Do NOT fall back to defaults for execution.
|
|
@@ -110,15 +138,25 @@ export function resolveRuntimeFromPdConfig(
|
|
|
110
138
|
};
|
|
111
139
|
}
|
|
112
140
|
|
|
113
|
-
const result =
|
|
141
|
+
const result = resolveRuntimeConfigForAgent(configLoadResult.effective, agentName, { getEnvVar, ignoreAgentEnabled });
|
|
114
142
|
|
|
115
143
|
// PRI-402: Extract profile ID and label for probe output alignment with doctor
|
|
116
144
|
let runtimeProfileId: string | null = null;
|
|
117
145
|
let runtimeProfileLabel: string | null = null;
|
|
118
|
-
const bindingResult = resolveAgentRuntimeBinding(configLoadResult.effective,
|
|
146
|
+
const bindingResult = resolveAgentRuntimeBinding(configLoadResult.effective, agentName);
|
|
119
147
|
if (bindingResult.ok) {
|
|
120
148
|
runtimeProfileId = bindingResult.profileId;
|
|
121
149
|
runtimeProfileLabel = buildProfileLabel(bindingResult.profileId, bindingResult.profile);
|
|
150
|
+
} else if (!isRuntimeConfigError(result) && result.runtimeProfileId !== undefined) {
|
|
151
|
+
// PRI-719 review: on the peer path (ignoreAgentEnabled) the runtime may
|
|
152
|
+
// resolve fine for a shipped-disabled agent while the raw binding still
|
|
153
|
+
// reports disabled — derive the label from the resolved profile identity.
|
|
154
|
+
const { runtimeProfileId: resolvedProfileId } = result;
|
|
155
|
+
const profile = configLoadResult.effective.config.runtimeProfiles[resolvedProfileId];
|
|
156
|
+
if (resolvedProfileId !== undefined && profile) {
|
|
157
|
+
runtimeProfileId = resolvedProfileId;
|
|
158
|
+
runtimeProfileLabel = buildProfileLabel(resolvedProfileId, profile);
|
|
159
|
+
}
|
|
122
160
|
}
|
|
123
161
|
|
|
124
162
|
const legacyWarnings = configLoadResult.legacyFilesDetected.length > 0
|
|
@@ -161,7 +199,7 @@ export function resolveRuntimeWithOverrides(
|
|
|
161
199
|
},
|
|
162
200
|
getEnvVar: (name: string) => string | undefined = (name) => process.env[name],
|
|
163
201
|
): ResolvedRuntimeFromPdConfig & { mergedConfig: RuntimeConfig | null } {
|
|
164
|
-
const base = resolveRuntimeFromPdConfig(workspaceDir, getEnvVar);
|
|
202
|
+
const base = resolveRuntimeFromPdConfig(workspaceDir, { getEnvVar });
|
|
165
203
|
|
|
166
204
|
if (isRuntimeConfigError(base.result)) {
|
|
167
205
|
return { ...base, mergedConfig: null };
|
|
@@ -207,7 +207,7 @@ function resolveRuleHostReadinessUnchecked(
|
|
|
207
207
|
getEnvVar: (name: string) => string | undefined,
|
|
208
208
|
): RuleHostReadinessResult {
|
|
209
209
|
// ── Step 1: Load config ──
|
|
210
|
-
const { configLoadResult } = resolveRuntimeFromPdConfig(workspaceDir, getEnvVar);
|
|
210
|
+
const { configLoadResult } = resolveRuntimeFromPdConfig(workspaceDir, { getEnvVar });
|
|
211
211
|
|
|
212
212
|
if (!configLoadResult.ok) {
|
|
213
213
|
const [firstError] = configLoadResult.errors;
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
isRuntimeConfigError,
|
|
33
33
|
validateRuntimeConfig,
|
|
34
34
|
} from '@principles/core/runtime-v2';
|
|
35
|
-
import type { PDRuntimeAdapter, PdL2ArtifactReader, RuntimeConfig, RuntimeConfigResult } from '@principles/core/runtime-v2';
|
|
35
|
+
import type { PDRuntimeAdapter, PdL2ArtifactReader, RuntimeConfig, RuntimeConfigResult, InternalAgentName } from '@principles/core/runtime-v2';
|
|
36
36
|
import { loadLedger } from '@principles/core/principle-tree-ledger';
|
|
37
37
|
import { loadPdConfig, computeFlagsFromLoadResult } from './pd-config-loader.js';
|
|
38
38
|
import { resolveRuntimeFromPdConfig } from './resolve-runtime-from-pd-config.js';
|
|
@@ -80,6 +80,19 @@ export interface ResolveAdapterOptions {
|
|
|
80
80
|
workspaceDir: string;
|
|
81
81
|
/** Runner kind (for L2 dreamer routing). Optional. */
|
|
82
82
|
runnerKind?: string;
|
|
83
|
+
/**
|
|
84
|
+
* PRI-719: internal agent whose runtimeProfile binding resolves
|
|
85
|
+
* (default 'diagnostician'). run-once passes the selected runner's agent
|
|
86
|
+
* so each stage executes on its own declared profile.
|
|
87
|
+
*/
|
|
88
|
+
agentName?: InternalAgentName;
|
|
89
|
+
/**
|
|
90
|
+
* PRI-719 review: resolve the binding even when the agent is disabled —
|
|
91
|
+
* peer execution scope is the internalization_full_chain flag (auto-
|
|
92
|
+
* consumer AND explicit run-once), not agents[kind].enabled. Omitted by
|
|
93
|
+
* non-peer callers (probe/diagnose) which keep honoring `enabled`.
|
|
94
|
+
*/
|
|
95
|
+
ignoreAgentEnabled?: boolean;
|
|
83
96
|
/** CLI timeout override. Takes precedence over config timeoutMs. */
|
|
84
97
|
timeoutMs?: number;
|
|
85
98
|
/**
|
|
@@ -163,7 +176,10 @@ export function resolveRuntimeAdapterFromConfig(opts: ResolveAdapterOptions): PD
|
|
|
163
176
|
}
|
|
164
177
|
|
|
165
178
|
// ── Resolve config from .pd/config.yaml (for pi-ai, openclaw-cli, config) ──
|
|
166
|
-
const resolved = resolveRuntimeFromPdConfig(opts.workspaceDir
|
|
179
|
+
const resolved = resolveRuntimeFromPdConfig(opts.workspaceDir, {
|
|
180
|
+
agentName: opts.agentName,
|
|
181
|
+
ignoreAgentEnabled: opts.ignoreAgentEnabled,
|
|
182
|
+
});
|
|
167
183
|
const configResult: RuntimeConfigResult = resolved.result;
|
|
168
184
|
|
|
169
185
|
// PRI-431 Step 1d: invoke onConfigResolved callback (for telemetry, legacyWarnings, etc.)
|
|
@@ -119,8 +119,10 @@ describe('PRI-393: runtime config unification', () => {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
for (const block of importBlocks) {
|
|
122
|
-
// Allow
|
|
123
|
-
|
|
122
|
+
// Allow the canonical resolver family — resolveRuntimeConfigFromPdConfig
|
|
123
|
+
// (shared diagnostician binding) and resolveRuntimeConfigForAgent (the
|
|
124
|
+
// PRI-719 per-agent variant) — but NOT bare legacy resolveRuntimeConfig
|
|
125
|
+
if (block.includes('resolveRuntimeConfig') && !block.includes('resolveRuntimeConfigFromPdConfig') && !block.includes('resolveRuntimeConfigForAgent')) {
|
|
124
126
|
// This is the legacy import — fail
|
|
125
127
|
expect.fail(
|
|
126
128
|
`${file} still imports legacy resolveRuntimeConfig from @principles/core/runtime-v2. ` +
|
|
@@ -212,7 +214,7 @@ describe('PRI-393: runtime config unification', () => {
|
|
|
212
214
|
|
|
213
215
|
// Dynamically import to avoid module resolution issues
|
|
214
216
|
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
215
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => 'test-key');
|
|
217
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
216
218
|
|
|
217
219
|
expect(resolved.configSource).toBe('.pd/config.yaml');
|
|
218
220
|
expect(resolved.result).toBeDefined();
|
|
@@ -238,7 +240,7 @@ funnels:
|
|
|
238
240
|
`);
|
|
239
241
|
|
|
240
242
|
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
241
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => 'test-key');
|
|
243
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
242
244
|
|
|
243
245
|
// Should have legacy warning
|
|
244
246
|
expect(resolved.legacyWarnings.length).toBeGreaterThan(0);
|
|
@@ -259,7 +261,7 @@ funnels:
|
|
|
259
261
|
|
|
260
262
|
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
261
263
|
const { isRuntimeConfigError: isErr } = await import('@principles/core/runtime-v2');
|
|
262
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => 'test-key');
|
|
264
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
263
265
|
|
|
264
266
|
// Malformed config must produce a RuntimeConfigError — never fall back to defaults
|
|
265
267
|
expect(resolved.configLoadResult.ok).toBe(false);
|
|
@@ -277,7 +279,7 @@ funnels:
|
|
|
277
279
|
writeConfigYaml(tmpDir, makeValidConfigYaml());
|
|
278
280
|
|
|
279
281
|
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
280
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => 'test-key');
|
|
282
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
281
283
|
|
|
282
284
|
const jsonStr = JSON.stringify(resolved.result);
|
|
283
285
|
const parsed = JSON.parse(jsonStr);
|
|
@@ -355,7 +357,7 @@ funnels:
|
|
|
355
357
|
it('handles empty workspace directory gracefully', async () => {
|
|
356
358
|
// No .pd directory at all
|
|
357
359
|
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
358
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => 'test-key');
|
|
360
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
359
361
|
|
|
360
362
|
// Missing config should return defaults (ok=true with source='defaults')
|
|
361
363
|
expect(resolved.configLoadResult.ok).toBe(true);
|
|
@@ -371,7 +373,7 @@ funnels:
|
|
|
371
373
|
|
|
372
374
|
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
373
375
|
const { isRuntimeConfigError } = await import('@principles/core/runtime-v2');
|
|
374
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => 'test-key');
|
|
376
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
375
377
|
|
|
376
378
|
// Missing runtimeProfiles should either load with defaults or produce
|
|
377
379
|
// a RuntimeConfigError — never silently succeed with wrong config.
|
|
@@ -400,7 +402,7 @@ funnels:
|
|
|
400
402
|
|
|
401
403
|
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
402
404
|
const { isRuntimeConfigError } = await import('@principles/core/runtime-v2');
|
|
403
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => 'test-key');
|
|
405
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
404
406
|
|
|
405
407
|
// Invalid type should be caught — verify external contract, not internal state
|
|
406
408
|
expect(isRuntimeConfigError(resolved.result)).toBe(true);
|
|
@@ -414,7 +416,7 @@ funnels:
|
|
|
414
416
|
writeConfigYaml(tmpDir, makeValidConfigYaml());
|
|
415
417
|
|
|
416
418
|
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
417
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => undefined);
|
|
419
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => undefined });
|
|
418
420
|
|
|
419
421
|
// Should handle undefined env var gracefully
|
|
420
422
|
expect(resolved.result).toBeDefined();
|
|
@@ -432,7 +434,7 @@ funnels:
|
|
|
432
434
|
fs.writeFileSync(path.join(ffDir, 'feature-flags.yaml'), 'flags: []', 'utf8');
|
|
433
435
|
|
|
434
436
|
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
435
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => 'test-key');
|
|
437
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
436
438
|
|
|
437
439
|
expect(resolved.legacyWarnings.length).toBeGreaterThan(0);
|
|
438
440
|
expect(resolved.configLoadResult.legacyFilesDetected.length).toBeGreaterThanOrEqual(2);
|
|
@@ -187,6 +187,18 @@ vi.mock('@principles/core/runtime-v2', () => ({
|
|
|
187
187
|
return { validate: vi.fn().mockResolvedValue({ valid: true, errors: [] }) };
|
|
188
188
|
}),
|
|
189
189
|
resolveRuntimeConfigFromPdConfig: vi.fn().mockReturnValue({ runtimeKind: 'pi-ai', provider: 'test-provider', model: 'test-model', apiKeyEnv: 'TEST_API_KEY', timeoutMs: 300_000, agentId: 'main' }),
|
|
190
|
+
// PRI-719: run-once resolves the SELECTED runner's agent binding through
|
|
191
|
+
// the per-agent variant; mirror the real taskKind→agent mapping (whole
|
|
192
|
+
// core module is mocked here).
|
|
193
|
+
resolveRuntimeConfigForAgent: vi.fn().mockReturnValue({ runtimeKind: 'pi-ai', provider: 'test-provider', model: 'test-model', apiKeyEnv: 'TEST_API_KEY', timeoutMs: 300_000, agentId: 'main' }),
|
|
194
|
+
AGENT_NAME_FOR_TASK_KIND: {
|
|
195
|
+
dreamer: 'dreamer',
|
|
196
|
+
philosopher: 'philosopher',
|
|
197
|
+
scribe: 'scribe',
|
|
198
|
+
artificer: 'artificer',
|
|
199
|
+
evaluator: 'evaluator',
|
|
200
|
+
rollout_reviewer: 'rolloutReviewer',
|
|
201
|
+
},
|
|
190
202
|
resolveRuntimeConfig: vi.fn().mockReturnValue({ runtimeKind: 'pi-ai', timeoutMs: 300_000, agentId: 'main', provider: 'test-provider', model: 'test-model', apiKeyEnv: 'TEST_API_KEY' }),
|
|
191
203
|
validateRuntimeConfig: vi.fn(),
|
|
192
204
|
isRuntimeConfigError: vi.fn().mockImplementation((result: unknown) => result != null && typeof result === 'object' && Object.hasOwn(result, 'reason') && !Object.hasOwn(result, 'runtimeKind')),
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRI-719 review — real-config composition tests (NO core module mock).
|
|
3
|
+
*
|
|
4
|
+
* The run-once unit suites mock `@principles/core/runtime-v2`, which cannot
|
|
5
|
+
* prove real default-config behavior. These tests drive the REAL resolver
|
|
6
|
+
* chain (resolveRuntimeFromPdConfig → resolveRuntimeConfigForAgent →
|
|
7
|
+
* resolveRuntimeAdapterFromConfig) against real workspaces built from
|
|
8
|
+
* `getDefaultPdConfig()`:
|
|
9
|
+
*
|
|
10
|
+
* Case 1 evaluator shipped disabled + explicit runtimeProfile → an
|
|
11
|
+
* explicit peer run-once resolves ITS profile (no
|
|
12
|
+
* "Agent 'evaluator' is disabled"), because peer execution scope
|
|
13
|
+
* is the internalization_full_chain flag — the same
|
|
14
|
+
* ignoreAgentEnabled semantics the auto-consumer uses.
|
|
15
|
+
* Case 2 rolloutReviewer shipped disabled — same resolution.
|
|
16
|
+
* Case 3 per-agent isolation: diagnostician→profile-A, evaluator→B;
|
|
17
|
+
* the run-once seam resolves profile-B.
|
|
18
|
+
* Case 4 the diagnostician bridge gate still HONORS enabled (no global
|
|
19
|
+
* bypass leaked from the peer option).
|
|
20
|
+
* B7 cross-entry equivalence: run-once path and auto-consumer path
|
|
21
|
+
* resolve IDENTICAL runtimeProfileId/provider/model for the same
|
|
22
|
+
* config (runtime binding must not drift across entry points).
|
|
23
|
+
*/
|
|
24
|
+
import { describe, expect, it } from 'vitest';
|
|
25
|
+
import fs from 'node:fs';
|
|
26
|
+
import os from 'node:os';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
import {
|
|
29
|
+
computeEffectivePdConfig,
|
|
30
|
+
getDefaultPdConfig,
|
|
31
|
+
resolveRuntimeConfigForAgent,
|
|
32
|
+
AGENT_NAME_FOR_TASK_KIND,
|
|
33
|
+
type RuntimeConfigError,
|
|
34
|
+
} from '@principles/core/runtime-v2';
|
|
35
|
+
import { resolveRuntimeFromPdConfig } from '../../src/services/resolve-runtime-from-pd-config.js';
|
|
36
|
+
import { resolveRuntimeAdapterFromConfig } from '../../src/services/runtime-adapter-resolver.js';
|
|
37
|
+
|
|
38
|
+
const dirs: string[] = [];
|
|
39
|
+
|
|
40
|
+
function makeWorkspace(): string {
|
|
41
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-runonce-profile-'));
|
|
42
|
+
dirs.push(root);
|
|
43
|
+
fs.mkdirSync(path.join(root, '.pd'), { recursive: true });
|
|
44
|
+
return root;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function writeProfileConfig(root: string, agents: Record<string, string>): void {
|
|
48
|
+
const config = getDefaultPdConfig();
|
|
49
|
+
(config.runtimeProfiles as Record<string, unknown>) = {
|
|
50
|
+
...(config.runtimeProfiles as Record<string, unknown>),
|
|
51
|
+
'profile-diagnostician': {
|
|
52
|
+
type: 'pi-ai', provider: 'prov-diag', model: 'diag-model', apiKeyEnv: 'DIAG_KEY',
|
|
53
|
+
baseUrl: 'http://127.0.0.1:9001/v1',
|
|
54
|
+
},
|
|
55
|
+
'profile-evaluator': {
|
|
56
|
+
type: 'pi-ai', provider: 'prov-evaluator', model: 'evaluator-model', apiKeyEnv: 'EVAL_KEY',
|
|
57
|
+
baseUrl: 'http://127.0.0.1:9002/v1',
|
|
58
|
+
},
|
|
59
|
+
'profile-rollout': {
|
|
60
|
+
type: 'pi-ai', provider: 'prov-rollout', model: 'rollout-model', apiKeyEnv: 'ROLLOUT_KEY',
|
|
61
|
+
baseUrl: 'http://127.0.0.1:9003/v1',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const agentBindings = config.internalAgents.agents as Record<string, { enabled: boolean; runtimeProfile?: string }>;
|
|
65
|
+
// The shipped default config pins every agent's runtimeProfile — replace
|
|
66
|
+
// the DECLARED profiles per the scenario; enabled flags stay at their
|
|
67
|
+
// shipped values (evaluator/rolloutReviewer/philosopher = false).
|
|
68
|
+
for (const [agent, profileId] of Object.entries(agents)) {
|
|
69
|
+
agentBindings[agent] = { ...agentBindings[agent], runtimeProfile: profileId };
|
|
70
|
+
}
|
|
71
|
+
// JSON is valid YAML — same trick the sibling composition tests use.
|
|
72
|
+
fs.writeFileSync(path.join(root, '.pd', 'config.yaml'), JSON.stringify(config));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readAdapterConfig(adapter: unknown): { provider?: string; model?: string } {
|
|
76
|
+
return (adapter as unknown as { config: { provider?: string; model?: string } }).config;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isErr(result: unknown): RuntimeConfigError {
|
|
80
|
+
return result as RuntimeConfigError;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe('PRI-719 review — explicit run-once resolves shipped-disabled peer agents on their own profile (real config)', () => {
|
|
84
|
+
it('Case 1: evaluator enabled=false (default) + declared profile → run-once seam resolves profile-evaluator', () => {
|
|
85
|
+
const ws = makeWorkspace();
|
|
86
|
+
writeProfileConfig(ws, { evaluator: 'profile-evaluator' });
|
|
87
|
+
process.env.EVAL_KEY = 'test-key';
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const resolved = resolveRuntimeFromPdConfig(ws, {
|
|
91
|
+
agentName: 'evaluator',
|
|
92
|
+
ignoreAgentEnabled: true,
|
|
93
|
+
});
|
|
94
|
+
expect(resolved.result).not.toHaveProperty('ok');
|
|
95
|
+
if (resolved.result.ok === false) return;
|
|
96
|
+
expect(resolved.result.runtimeKind).toBe('pi-ai');
|
|
97
|
+
expect(resolved.result.provider).toBe('prov-evaluator');
|
|
98
|
+
expect(resolved.result.model).toBe('evaluator-model');
|
|
99
|
+
expect(resolved.result.runtimeProfileId).toBe('profile-evaluator');
|
|
100
|
+
|
|
101
|
+
// The run-once ADAPTER seam resolves the same profile into the adapter.
|
|
102
|
+
const adapter = resolveRuntimeAdapterFromConfig({
|
|
103
|
+
runtimeKind: 'config',
|
|
104
|
+
workspaceDir: ws,
|
|
105
|
+
runnerKind: 'evaluator',
|
|
106
|
+
agentName: 'evaluator',
|
|
107
|
+
ignoreAgentEnabled: true,
|
|
108
|
+
});
|
|
109
|
+
// The real (unmocked) adapter has no `__type` marker — assert via kind().
|
|
110
|
+
expect(adapter.kind()).toBe('pi-ai');
|
|
111
|
+
expect(readAdapterConfig(adapter)).toMatchObject({
|
|
112
|
+
provider: 'prov-evaluator', model: 'evaluator-model',
|
|
113
|
+
});
|
|
114
|
+
} finally {
|
|
115
|
+
delete process.env.EVAL_KEY;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('Case 2: rolloutReviewer enabled=false (default) → run-once seam resolves profile-rollout', () => {
|
|
120
|
+
const ws = makeWorkspace();
|
|
121
|
+
writeProfileConfig(ws, { rolloutReviewer: 'profile-rollout' });
|
|
122
|
+
process.env.ROLLOUT_KEY = 'test-key';
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// Through the canonical taskKind→agent map, as run-once does.
|
|
126
|
+
const agentName = AGENT_NAME_FOR_TASK_KIND['rollout_reviewer'];
|
|
127
|
+
expect(agentName).toBe('rolloutReviewer');
|
|
128
|
+
const resolved = resolveRuntimeFromPdConfig(ws, { agentName, ignoreAgentEnabled: true });
|
|
129
|
+
if (resolved.result.ok === false) throw new Error(resolved.result.message);
|
|
130
|
+
expect(resolved.result.provider).toBe('prov-rollout');
|
|
131
|
+
expect(resolved.result.runtimeProfileId).toBe('profile-rollout');
|
|
132
|
+
} finally {
|
|
133
|
+
delete process.env.ROLLOUT_KEY;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('Case 3: per-agent isolation — evaluator resolves ITS profile, not the diagnostician one', () => {
|
|
138
|
+
const ws = makeWorkspace();
|
|
139
|
+
writeProfileConfig(ws, { diagnostician: 'profile-diagnostician', evaluator: 'profile-evaluator' });
|
|
140
|
+
process.env.DIAG_KEY = 'test-key';
|
|
141
|
+
process.env.EVAL_KEY = 'test-key';
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const adapter = resolveRuntimeAdapterFromConfig({
|
|
145
|
+
runtimeKind: 'config',
|
|
146
|
+
workspaceDir: ws,
|
|
147
|
+
runnerKind: 'evaluator',
|
|
148
|
+
agentName: 'evaluator',
|
|
149
|
+
ignoreAgentEnabled: true,
|
|
150
|
+
});
|
|
151
|
+
expect(readAdapterConfig(adapter)).toMatchObject({
|
|
152
|
+
provider: 'prov-evaluator', model: 'evaluator-model',
|
|
153
|
+
});
|
|
154
|
+
} finally {
|
|
155
|
+
delete process.env.DIAG_KEY;
|
|
156
|
+
delete process.env.EVAL_KEY;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('negative control: without ignoreAgentEnabled the shipped-disabled evaluator still refuses', () => {
|
|
161
|
+
const ws = makeWorkspace();
|
|
162
|
+
writeProfileConfig(ws, { evaluator: 'profile-evaluator' });
|
|
163
|
+
process.env.EVAL_KEY = 'test-key';
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
// Pins that the option (not something else) lifts the gate: the same
|
|
167
|
+
// call without it honors enabled and refuses.
|
|
168
|
+
expect(() => resolveRuntimeAdapterFromConfig({
|
|
169
|
+
runtimeKind: 'config',
|
|
170
|
+
workspaceDir: ws,
|
|
171
|
+
runnerKind: 'evaluator',
|
|
172
|
+
agentName: 'evaluator',
|
|
173
|
+
})).toThrow(/disabled/);
|
|
174
|
+
} finally {
|
|
175
|
+
delete process.env.EVAL_KEY;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('Case 4: diagnostician bridge gate still HONORS enabled (no global bypass)', () => {
|
|
180
|
+
const ws = makeWorkspace();
|
|
181
|
+
writeProfileConfig(ws, { diagnostician: 'profile-diagnostician' });
|
|
182
|
+
process.env.DIAG_KEY = 'test-key';
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const config = JSON.parse(fs.readFileSync(path.join(ws, '.pd', 'config.yaml'), 'utf8'));
|
|
186
|
+
config.internalAgents.agents.diagnostician.enabled = false;
|
|
187
|
+
fs.writeFileSync(path.join(ws, '.pd', 'config.yaml'), JSON.stringify(config));
|
|
188
|
+
|
|
189
|
+
// Canonical diagnostician resolution (the pain-signal bridge path)
|
|
190
|
+
// keeps failing closed with readiness=disabled.
|
|
191
|
+
const viaWrapper = resolveRuntimeFromPdConfig(ws);
|
|
192
|
+
expect(viaWrapper.result).toHaveProperty('ok', false);
|
|
193
|
+
expect(isErr(viaWrapper.result).reason).toBe('disabled');
|
|
194
|
+
|
|
195
|
+
const viaCore = resolveRuntimeConfigForAgent(
|
|
196
|
+
computeEffectivePdConfig(config),
|
|
197
|
+
'diagnostician',
|
|
198
|
+
{ getEnvVar: (name) => process.env[name] },
|
|
199
|
+
);
|
|
200
|
+
expect(viaCore).toHaveProperty('ok', false);
|
|
201
|
+
expect(isErr(viaCore).reason).toBe('disabled');
|
|
202
|
+
} finally {
|
|
203
|
+
delete process.env.DIAG_KEY;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('B7 cross-entry equivalence: run-once path and auto-consumer path resolve the SAME binding', () => {
|
|
208
|
+
const ws = makeWorkspace();
|
|
209
|
+
writeProfileConfig(ws, { diagnostician: 'profile-diagnostician', evaluator: 'profile-evaluator' });
|
|
210
|
+
process.env.EVAL_KEY = 'test-key';
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
// Auto-consumer path: per-kind resolution with peer scope semantics.
|
|
214
|
+
const consumerResult = resolveRuntimeConfigForAgent(
|
|
215
|
+
computeEffectivePdConfig(JSON.parse(fs.readFileSync(path.join(ws, '.pd', 'config.yaml'), 'utf8'))),
|
|
216
|
+
'evaluator',
|
|
217
|
+
{ getEnvVar: (name) => process.env[name], ignoreAgentEnabled: true },
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Explicit run-once path: resolveRuntimeFromPdConfig with the same
|
|
221
|
+
// agent + scope semantics (exactly what run-once's two resolution
|
|
222
|
+
// points use).
|
|
223
|
+
const runOnceResult = resolveRuntimeFromPdConfig(ws, {
|
|
224
|
+
agentName: 'evaluator',
|
|
225
|
+
ignoreAgentEnabled: true,
|
|
226
|
+
}).result;
|
|
227
|
+
|
|
228
|
+
expect(runOnceResult).toEqual(consumerResult);
|
|
229
|
+
if (runOnceResult.ok === false || consumerResult.ok === false) return;
|
|
230
|
+
expect(runOnceResult.runtimeProfileId).toBe('profile-evaluator');
|
|
231
|
+
expect(runOnceResult.provider).toBe('prov-evaluator');
|
|
232
|
+
expect(runOnceResult.model).toBe('evaluator-model');
|
|
233
|
+
} finally {
|
|
234
|
+
delete process.env.EVAL_KEY;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
});
|
|
@@ -163,6 +163,19 @@ vi.mock('@principles/core/runtime-v2', () => ({
|
|
|
163
163
|
isRuntimeConfigError: vi.fn().mockImplementation((result: unknown) => result != null && typeof result === 'object' && Object.hasOwn(result, 'reason') && !Object.hasOwn(result, 'runtimeKind')),
|
|
164
164
|
validateRuntimeConfig: vi.fn(),
|
|
165
165
|
resolveRuntimeConfigFromPdConfig: vi.fn().mockReturnValue({ runtimeKind: 'pi-ai', provider: 'test-provider', model: 'test-model', apiKeyEnv: 'TEST_API_KEY', timeoutMs: 300_000, agentId: 'main' }),
|
|
166
|
+
// PRI-719: run-once resolves the SELECTED runner's agent binding through
|
|
167
|
+
// the per-agent variant — this is the seam the PRI-670 timeout tests mock.
|
|
168
|
+
resolveRuntimeConfigForAgent: vi.fn().mockReturnValue({ runtimeKind: 'pi-ai', provider: 'test-provider', model: 'test-model', apiKeyEnv: 'TEST_API_KEY', timeoutMs: 300_000, agentId: 'main' }),
|
|
169
|
+
// PRI-719: the real taskKind→agent mapping (stable constant, mirrored here
|
|
170
|
+
// because the whole core module is mocked in this file).
|
|
171
|
+
AGENT_NAME_FOR_TASK_KIND: {
|
|
172
|
+
dreamer: 'dreamer',
|
|
173
|
+
philosopher: 'philosopher',
|
|
174
|
+
scribe: 'scribe',
|
|
175
|
+
artificer: 'artificer',
|
|
176
|
+
evaluator: 'evaluator',
|
|
177
|
+
rollout_reviewer: 'rolloutReviewer',
|
|
178
|
+
},
|
|
166
179
|
resolveOutputLanguage: vi.fn().mockReturnValue({ outputLanguage: 'zh-CN' }),
|
|
167
180
|
}));
|
|
168
181
|
|
|
@@ -508,9 +521,12 @@ describe('handleRuntimeInternalizationRunOnce', () => {
|
|
|
508
521
|
const customWs = '/tmp/test-workspace';
|
|
509
522
|
await handleRuntimeInternalizationRunOnce({ workspace: customWs, runtime: 'config', json: true });
|
|
510
523
|
|
|
511
|
-
// PRI-393:
|
|
524
|
+
// PRI-393 + PRI-719 (+review): resolveRuntimeFromPdConfig is called with
|
|
525
|
+
// the workspace dir AND the selected runner's agent binding under the
|
|
526
|
+
// PEER ignoreAgentEnabled semantics (same as the auto-consumer).
|
|
512
527
|
expect(mockResolveRuntimeFromPdConfig).toHaveBeenCalledWith(
|
|
513
528
|
expect.stringContaining('test-workspace'),
|
|
529
|
+
{ agentName: 'dreamer', ignoreAgentEnabled: true },
|
|
514
530
|
);
|
|
515
531
|
});
|
|
516
532
|
|
|
@@ -824,7 +840,7 @@ describe('handleRuntimeInternalizationRunOnce', () => {
|
|
|
824
840
|
// the PRI-670 fix the runner deadline stayed hardcoded at 300s and the
|
|
825
841
|
// profile value only reached the adapter as a per-request timeout.
|
|
826
842
|
const coreModule = await import('@principles/core/runtime-v2');
|
|
827
|
-
vi.mocked(coreModule.
|
|
843
|
+
vi.mocked(coreModule.resolveRuntimeConfigForAgent).mockReturnValue({
|
|
828
844
|
runtimeKind: 'pi-ai', provider: 'test-provider', model: 'test-model',
|
|
829
845
|
apiKeyEnv: 'TEST_API_KEY', timeoutMs: 600_000, agentId: 'main',
|
|
830
846
|
} as never);
|
|
@@ -857,7 +873,7 @@ describe('handleRuntimeInternalizationRunOnce', () => {
|
|
|
857
873
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
|
858
874
|
expect(output.effectiveTimeoutMs).toBe(600_000);
|
|
859
875
|
} finally {
|
|
860
|
-
vi.mocked(coreModule.
|
|
876
|
+
vi.mocked(coreModule.resolveRuntimeConfigForAgent).mockReturnValue({
|
|
861
877
|
runtimeKind: 'pi-ai', provider: 'test-provider', model: 'test-model',
|
|
862
878
|
apiKeyEnv: 'TEST_API_KEY', timeoutMs: 300_000, agentId: 'main',
|
|
863
879
|
} as never);
|
|
@@ -866,7 +882,7 @@ describe('handleRuntimeInternalizationRunOnce', () => {
|
|
|
866
882
|
|
|
867
883
|
it('PRI-670: --timeout-ms still wins over the profile timeoutMs', async () => {
|
|
868
884
|
const coreModule = await import('@principles/core/runtime-v2');
|
|
869
|
-
vi.mocked(coreModule.
|
|
885
|
+
vi.mocked(coreModule.resolveRuntimeConfigForAgent).mockReturnValue({
|
|
870
886
|
runtimeKind: 'pi-ai', provider: 'test-provider', model: 'test-model',
|
|
871
887
|
apiKeyEnv: 'TEST_API_KEY', timeoutMs: 600_000, agentId: 'main',
|
|
872
888
|
} as never);
|
|
@@ -892,7 +908,7 @@ describe('handleRuntimeInternalizationRunOnce', () => {
|
|
|
892
908
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
|
893
909
|
expect(output.effectiveTimeoutMs).toBe(120_000);
|
|
894
910
|
} finally {
|
|
895
|
-
vi.mocked(coreModule.
|
|
911
|
+
vi.mocked(coreModule.resolveRuntimeConfigForAgent).mockReturnValue({
|
|
896
912
|
runtimeKind: 'pi-ai', provider: 'test-provider', model: 'test-model',
|
|
897
913
|
apiKeyEnv: 'TEST_API_KEY', timeoutMs: 300_000, agentId: 'main',
|
|
898
914
|
} as never);
|