@principles/host-runtime 0.3.10 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* env getter. Everything else is the existing production path.
|
|
22
22
|
*/
|
|
23
23
|
import { createHash } from 'node:crypto';
|
|
24
|
-
import { createRuntimeStateHandle, InternalizationOrchestrator, DreamerRunner, PhilosopherRunner, ScribeRunner, ArtificerRunner, EvaluatorRunner, RolloutReviewerRunner, DefaultDreamerValidator, DefaultPhilosopherValidator, DefaultScribeValidator, DefaultArtificerValidator, DefaultEvaluatorValidator, DefaultRolloutReviewerValidator, PiAiRuntimeAdapter, L2AgentLoopAdapter, buildL2PrincipleReaderFromLedger, OpenClawCliRuntimeAdapter, storeEmitter, createProductionGateDeps, resolveRuntimeConfigFromPdConfig, isRuntimeConfigError, computeConsumerDecision, FULL_CHAIN_CONSUMER_RUNNER_KINDS, DEFAULT_CONSUMER_RUNNER_KINDS, InternalizationQueueReadModel, MVP_CORE_TASK_KINDS, SqliteConnection, SqliteReconciliationCursorStore, SUCCEEDED_TRANSITIONS_SCOPE,
|
|
24
|
+
import { createRuntimeStateHandle, InternalizationOrchestrator, DreamerRunner, PhilosopherRunner, ScribeRunner, ArtificerRunner, EvaluatorRunner, RolloutReviewerRunner, DefaultDreamerValidator, DefaultPhilosopherValidator, DefaultScribeValidator, DefaultArtificerValidator, DefaultEvaluatorValidator, DefaultRolloutReviewerValidator, PiAiRuntimeAdapter, L2AgentLoopAdapter, buildL2PrincipleReaderFromLedger, OpenClawCliRuntimeAdapter, storeEmitter, createProductionGateDeps, resolveRuntimeConfigFromPdConfig, resolveRuntimeConfigForAgent, AGENT_NAME_FOR_TASK_KIND, isRuntimeConfigError, computeConsumerDecision, FULL_CHAIN_CONSUMER_RUNNER_KINDS, DEFAULT_CONSUMER_RUNNER_KINDS, InternalizationQueueReadModel, MVP_CORE_TASK_KINDS, SqliteConnection, SqliteReconciliationCursorStore, SUCCEEDED_TRANSITIONS_SCOPE,
|
|
25
25
|
// PRI-714: resolve outputLanguage from the same effective config the
|
|
26
26
|
// runners already receive (EP-07: canonical resolved value, not raw input).
|
|
27
27
|
resolveOutputLanguage, } from '@principles/core/runtime-v2';
|
|
@@ -240,13 +240,50 @@ export async function runInternalizationConsumerCycle(workspaceDir, ports) {
|
|
|
240
240
|
// Advance the configured runner kinds in priority order (dreamer first,
|
|
241
241
|
// then philosopher→…→rollout_reviewer under full-chain scope). Lease the
|
|
242
242
|
// first ready task whose dependencies are satisfied.
|
|
243
|
+
//
|
|
244
|
+
// PRI-719 (EP002-R2 F4): the runtime profile is resolved PER KIND, before
|
|
245
|
+
// wakeOnce — `internalAgents.agents[kind].runtimeProfile` (fallback
|
|
246
|
+
// defaultRuntime) now actually governs the stage that runs on it. Before,
|
|
247
|
+
// the whole chain shared the diagnostician binding and every per-agent
|
|
248
|
+
// profile declaration was silently ignored. A kind whose profile cannot
|
|
249
|
+
// resolve is skipped observably (no lease taken for it).
|
|
243
250
|
let wakeResult = null;
|
|
244
251
|
let lastSkipDecision = 'no_ready_tasks';
|
|
245
252
|
let lastSkipReason;
|
|
253
|
+
let taskRuntimeConfig = null;
|
|
246
254
|
for (const kind of decision.runnerKinds) {
|
|
255
|
+
const agentName = AGENT_NAME_FOR_TASK_KIND[kind];
|
|
256
|
+
if (agentName === undefined) {
|
|
257
|
+
lastSkipDecision = 'runtime_config_error';
|
|
258
|
+
lastSkipReason = `no internal agent mapping for task kind '${kind}'`;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const kindRuntime = resolveRuntimeConfigForAgent(configResult.effective, agentName, {
|
|
262
|
+
getEnvVar: (name) => envGetter(name),
|
|
263
|
+
// Consumer stage scope is the internalization_full_chain flag, not
|
|
264
|
+
// agents[kind].enabled (the shipped default disables
|
|
265
|
+
// philosopher/evaluator/rolloutReviewer yet full-chain runs them) —
|
|
266
|
+
// see ResolveRuntimeConfigForAgentOptions.
|
|
267
|
+
ignoreAgentEnabled: true,
|
|
268
|
+
});
|
|
269
|
+
if (isRuntimeConfigError(kindRuntime)) {
|
|
270
|
+
// rc-9: the degradation is observable per kind; other kinds still run.
|
|
271
|
+
emitEvent('INTERNALIZATION_CONSUMER_SKIP', JSON.stringify({
|
|
272
|
+
reason: 'runtime_config_error',
|
|
273
|
+
taskKind: kind,
|
|
274
|
+
agentName,
|
|
275
|
+
message: kindRuntime.message,
|
|
276
|
+
nextAction: kindRuntime.nextAction,
|
|
277
|
+
}));
|
|
278
|
+
logger.warn(`[PD:${logLabel}] Runtime config error for ${kind} (agent ${agentName}): ${kindRuntime.message}`);
|
|
279
|
+
lastSkipDecision = 'runtime_config_error';
|
|
280
|
+
lastSkipReason = kindRuntime.message;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
247
283
|
const candidate = await orchestrator.wakeOnce(kind);
|
|
248
284
|
if (candidate.decision === 'would_lease') {
|
|
249
285
|
wakeResult = candidate;
|
|
286
|
+
taskRuntimeConfig = kindRuntime;
|
|
250
287
|
break;
|
|
251
288
|
}
|
|
252
289
|
// Keep decision/reason paired across iterations so the final SKIP log
|
|
@@ -254,7 +291,7 @@ export async function runInternalizationConsumerCycle(workspaceDir, ports) {
|
|
|
254
291
|
lastSkipDecision = candidate.decision;
|
|
255
292
|
lastSkipReason = candidate.decision === 'no_ready_tasks' ? candidate.reason : undefined;
|
|
256
293
|
}
|
|
257
|
-
if (!wakeResult) {
|
|
294
|
+
if (!wakeResult || taskRuntimeConfig === null) {
|
|
258
295
|
// A: 预算由周期 finally 统一执行 (每周期恰一次,含 backlog 场景)
|
|
259
296
|
const skipPayload = { decision: lastSkipDecision };
|
|
260
297
|
if (lastSkipReason) {
|
|
@@ -264,8 +301,11 @@ export async function runInternalizationConsumerCycle(workspaceDir, ports) {
|
|
|
264
301
|
logger.info(`[PD:${logLabel}] No task to consume: ${lastSkipDecision}`);
|
|
265
302
|
return { ran: false, skipReason: lastSkipDecision };
|
|
266
303
|
}
|
|
304
|
+
// PRI-719: everything downstream reads the LEASED TASK's resolved config —
|
|
305
|
+
// the kind-specific profile, not the shared gate resolution.
|
|
306
|
+
const taskRuntimeKind = taskRuntimeConfig.runtimeKind;
|
|
267
307
|
let adapter;
|
|
268
|
-
if (
|
|
308
|
+
if (taskRuntimeKind === 'pi-ai') {
|
|
269
309
|
// PRI-419: when l2_dreamer flag is on AND this is a dreamer task, route
|
|
270
310
|
// through the L2 multi-turn agent loop. Non-dreamer runners always use PiAi.
|
|
271
311
|
const l2Flag = loadFeatureFlagFromConfig(workspaceDir, 'l2_dreamer');
|
|
@@ -275,14 +315,14 @@ export async function runInternalizationConsumerCycle(workspaceDir, ports) {
|
|
|
275
315
|
logger: { warn: (msg) => logger.warn(msg) },
|
|
276
316
|
});
|
|
277
317
|
adapter = new L2AgentLoopAdapter({
|
|
278
|
-
provider:
|
|
279
|
-
model:
|
|
280
|
-
apiKeyEnv:
|
|
281
|
-
baseUrl:
|
|
318
|
+
provider: taskRuntimeConfig.provider ?? 'openai',
|
|
319
|
+
model: taskRuntimeConfig.model ?? 'gpt-4o',
|
|
320
|
+
apiKeyEnv: taskRuntimeConfig.apiKeyEnv ?? 'OPENAI_API_KEY',
|
|
321
|
+
baseUrl: taskRuntimeConfig.baseUrl,
|
|
282
322
|
workspace: workspaceDir,
|
|
283
|
-
totalBudgetMs:
|
|
323
|
+
totalBudgetMs: taskRuntimeConfig.timeoutMs,
|
|
284
324
|
// PRI-633: profile systemPrompt rides as the append layer.
|
|
285
|
-
...(
|
|
325
|
+
...(taskRuntimeConfig.systemPrompt ? { systemPrompt: taskRuntimeConfig.systemPrompt } : {}),
|
|
286
326
|
}, {
|
|
287
327
|
artifactReader: {
|
|
288
328
|
// Explicit adapter: PIArtifactRecord → PdL2ArtifactReader. The store returns
|
|
@@ -301,27 +341,27 @@ export async function runInternalizationConsumerCycle(workspaceDir, ports) {
|
|
|
301
341
|
}
|
|
302
342
|
else {
|
|
303
343
|
adapter = new PiAiRuntimeAdapter({
|
|
304
|
-
provider:
|
|
305
|
-
model:
|
|
306
|
-
apiKeyEnv:
|
|
307
|
-
maxRetries:
|
|
308
|
-
maxTokens:
|
|
309
|
-
timeoutMs:
|
|
310
|
-
baseUrl:
|
|
344
|
+
provider: taskRuntimeConfig.provider ?? 'openai',
|
|
345
|
+
model: taskRuntimeConfig.model ?? 'gpt-4o',
|
|
346
|
+
apiKeyEnv: taskRuntimeConfig.apiKeyEnv ?? 'OPENAI_API_KEY',
|
|
347
|
+
maxRetries: taskRuntimeConfig.maxRetries,
|
|
348
|
+
maxTokens: taskRuntimeConfig.maxTokens,
|
|
349
|
+
timeoutMs: taskRuntimeConfig.timeoutMs,
|
|
350
|
+
baseUrl: taskRuntimeConfig.baseUrl,
|
|
311
351
|
workspace: workspaceDir,
|
|
312
352
|
// PRI-633: profile systemPrompt rides as the append layer.
|
|
313
|
-
...(
|
|
353
|
+
...(taskRuntimeConfig.systemPrompt ? { systemPrompt: taskRuntimeConfig.systemPrompt } : {}),
|
|
314
354
|
});
|
|
315
355
|
}
|
|
316
356
|
}
|
|
317
|
-
else if (
|
|
357
|
+
else if (taskRuntimeKind === 'openclaw-cli') {
|
|
318
358
|
adapter = new OpenClawCliRuntimeAdapter({
|
|
319
|
-
runtimeMode:
|
|
359
|
+
runtimeMode: taskRuntimeConfig.openclawMode ?? 'default',
|
|
320
360
|
workspaceDir: workspaceDir,
|
|
321
361
|
});
|
|
322
362
|
}
|
|
323
363
|
else {
|
|
324
|
-
throw new Error(`Unsupported runtime kind resolved for auto-consumer: ${
|
|
364
|
+
throw new Error(`Unsupported runtime kind resolved for auto-consumer: ${taskRuntimeKind}`);
|
|
325
365
|
}
|
|
326
366
|
const { taskId } = wakeResult;
|
|
327
367
|
const { taskKind } = wakeResult;
|
|
@@ -340,9 +380,9 @@ export async function runInternalizationConsumerCycle(workspaceDir, ports) {
|
|
|
340
380
|
// same effective config (EP-07 canonical value).
|
|
341
381
|
const runnerOptions = {
|
|
342
382
|
owner,
|
|
343
|
-
runtimeKind,
|
|
383
|
+
runtimeKind: taskRuntimeKind,
|
|
344
384
|
effectiveConfig: configResult.effective,
|
|
345
|
-
timeoutMs:
|
|
385
|
+
timeoutMs: taskRuntimeConfig.timeoutMs,
|
|
346
386
|
outputLanguage: resolveOutputLanguage(configResult.effective.config.principles?.outputLanguage).outputLanguage,
|
|
347
387
|
};
|
|
348
388
|
// PRI-634 A3: workspace-scoped telemetry sink for the evaluator runner.
|
|
@@ -423,10 +463,18 @@ export async function runInternalizationConsumerCycle(workspaceDir, ports) {
|
|
|
423
463
|
logger.warn(`[PD:${logLabel}] No consumer runner for task kind '${taskKind}'; skipping. Advance manually: pd runtime internalization run-once --runner ${taskKind}`);
|
|
424
464
|
return { ran: false, skipReason: 'no_runner_for_kind', taskKind };
|
|
425
465
|
}
|
|
426
|
-
|
|
466
|
+
// PRI-719: run evidence answers "which declared profile/provider/model
|
|
467
|
+
// actually executed this task" (declared == executed contract).
|
|
468
|
+
const runtimeEvidence = {
|
|
469
|
+
...(taskRuntimeConfig.runtimeProfileId !== undefined ? { runtimeProfileId: taskRuntimeConfig.runtimeProfileId } : {}),
|
|
470
|
+
...(taskRuntimeConfig.provider !== undefined ? { provider: taskRuntimeConfig.provider } : {}),
|
|
471
|
+
...(taskRuntimeConfig.model !== undefined ? { model: taskRuntimeConfig.model } : {}),
|
|
472
|
+
};
|
|
473
|
+
logger.info(`[PD:${logLabel}] Running ${taskKind} task: ${taskId}${Object.keys(runtimeEvidence).length > 0 ? ` (runtime: ${JSON.stringify(runtimeEvidence)})` : ''}`);
|
|
427
474
|
emitEvent('INTERNALIZATION_CONSUMER_RUN', JSON.stringify({
|
|
428
475
|
taskId,
|
|
429
476
|
taskKind,
|
|
477
|
+
...runtimeEvidence,
|
|
430
478
|
}));
|
|
431
479
|
let runResult;
|
|
432
480
|
try {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* dedupes by `${artifactId}::${channel}`; reopen idempotency is owned by
|
|
15
15
|
* orchestrator.reopenTaskForRevision.
|
|
16
16
|
*/
|
|
17
|
-
import { ActivationDispatcher, PromptWriter, DeferArchiveWriter, RuleHostWriter, SqliteConnection, SqliteActivationStateStore, SqliteApprovalQueueStore, SqlitePIArtifactStore, createProductionGateDeps, createPITaskDiagnosticJson, computeFeatureFlagsFromConfig, isFeatureEnabled, } from '@principles/core/runtime-v2';
|
|
17
|
+
import { ActivationDispatcher, PromptWriter, DeferArchiveWriter, RuleHostWriter, SqliteConnection, SqliteActivationStateStore, SqliteApprovalQueueStore, SqlitePIArtifactStore, createProductionGateDeps, createPITaskDiagnosticJson, artificerRepairTaskId, computeFeatureFlagsFromConfig, isFeatureEnabled, } from '@principles/core/runtime-v2';
|
|
18
18
|
import { loadPdConfigForPlugin } from './pd-config.js';
|
|
19
19
|
function normalizeDecision(decision) {
|
|
20
20
|
if (decision.decision === 'activated') {
|
|
@@ -116,10 +116,21 @@ export function createEvaluatorRepairDeps(workspaceDir, stateManager, logger) {
|
|
|
116
116
|
seedArtificerRepairTask: async (params) => {
|
|
117
117
|
// P0-4: 确定性 revision identity — evaluatorTaskId + iteration 唯一定位
|
|
118
118
|
// 一个逻辑 repair 任务; 重放 (consumer 重复周期 / crash 恢复) reuse 而非再建。
|
|
119
|
-
|
|
119
|
+
// PRI-718: id 约定收敛到 pitask-metadata 的单一 owner。
|
|
120
|
+
const repairTaskId = artificerRepairTaskId(params.repairPayload.sourceEvaluatorTaskId, params.repairPayload.repairIteration);
|
|
120
121
|
const existing = await stateManager.getTask(repairTaskId);
|
|
121
122
|
if (existing) {
|
|
122
|
-
|
|
123
|
+
// PRI-718 (revise ≠ resume): a TERMINAL repair round is finished
|
|
124
|
+
// corrective work, not a vehicle for new corrective work. Returning
|
|
125
|
+
// it here made the evaluator re-run against a superseded artifact
|
|
126
|
+
// forever (EP002-R2: same-artifact score oscillation, one LLM call
|
|
127
|
+
// per cycle, no new repair). New work requires a new revision epoch
|
|
128
|
+
// (Owner revise_once) — surface as a seed failure so the evaluator
|
|
129
|
+
// degrades to needs_human_review instead of looping.
|
|
130
|
+
if (existing.status === 'succeeded' || existing.status === 'failed' || existing.status === 'needs_human_review') {
|
|
131
|
+
throw new Error(`repair task ${repairTaskId} already reached terminal status ${existing.status}; refusing to reuse it as new corrective work (PRI-718 revise-ne-resume)`);
|
|
132
|
+
}
|
|
133
|
+
logger?.info?.(`[PD:Consumer] repair task ${repairTaskId} already exists (in-flight ${existing.status}); reusing (idempotent seed)`);
|
|
123
134
|
return repairTaskId;
|
|
124
135
|
}
|
|
125
136
|
await stateManager.createTask({
|