opencode-agent-skill 15.1.0 → 15.1.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 (37) hide show
  1. package/README.md +3 -3
  2. package/docs/V15-MANAGED-RUNTIME.md +18 -0
  3. package/global-config/agents/verifier.md +6 -2
  4. package/lib/affected-tests.mjs +21 -4
  5. package/lib/capability-fabric.mjs +107 -48
  6. package/lib/code-intelligence/index.mjs +3 -15
  7. package/lib/code-intelligence/lsp-pool.mjs +694 -0
  8. package/lib/code-intelligence/lsp-provider.mjs +160 -20
  9. package/lib/compaction-resume-guard.mjs +76 -4
  10. package/lib/completion-auditor.mjs +20 -0
  11. package/lib/context-manifest.mjs +38 -21
  12. package/lib/decision-policy.mjs +40 -2
  13. package/lib/evidence-store.mjs +121 -31
  14. package/lib/executable-probe.mjs +69 -0
  15. package/lib/hierarchical-context.mjs +31 -3
  16. package/lib/learning-engine.mjs +164 -65
  17. package/lib/memory-engine.mjs +163 -86
  18. package/lib/model-config.mjs +86 -7
  19. package/lib/performance-fabric.mjs +92 -0
  20. package/lib/permission-policy.mjs +232 -0
  21. package/lib/pi-rpc-pool.mjs +170 -5
  22. package/lib/provider-recovery.mjs +51 -0
  23. package/lib/repo-graph.mjs +38 -5
  24. package/lib/runtime-events.mjs +31 -10
  25. package/lib/semantic-index.mjs +67 -20
  26. package/lib/skill-compiler.mjs +84 -18
  27. package/lib/task-policy.mjs +80 -7
  28. package/lib/trajectory.mjs +25 -1
  29. package/lib/untrusted-output.mjs +106 -0
  30. package/package.json +8 -6
  31. package/pi/extensions/ues-child-runtime.ts +78 -3
  32. package/pi/extensions/ues.ts +695 -81
  33. package/scripts/benchmark-lsp-pool.mjs +128 -0
  34. package/scripts/check-release-consistency.mjs +16 -4
  35. package/scripts/check-source-integrity.mjs +60 -1
  36. package/scripts/run-test-suite.mjs +143 -0
  37. package/scripts/smoke-package-closure.mjs +4 -0
@@ -33,6 +33,7 @@ import { findReusableVerification, listReusableVerification, recordVerification
33
33
  import { evaluateFastVerificationGate } from "../../lib/fast-verification-gate.mjs";
34
34
  import { turboFastPathDecision, turboFastTimeoutBudget } from "../../lib/turbo-fast-path.mjs";
35
35
  import { failureDelta, leafTaskPolicy } from "../../lib/leaf-runtime-optimizer.mjs";
36
+ import { classifyProviderFailure, providerRecoveryBackoffMs } from "../../lib/provider-recovery.mjs";
36
37
  import { planningRuntimeBudget, shouldSoftSteerArchitect, shouldSoftSteerPlanningRole } from "../../lib/planning-speed-policy.mjs";
37
38
  import { sourceFacingPaths, sourceGitPathspecs } from "../../lib/runtime-artifacts.mjs";
38
39
  import { createSubagentArtifact, failSubagentArtifact, finalizeSubagentArtifact, listSubagentArtifacts, readSubagentArtifact } from "../../lib/subagent-artifacts.mjs";
@@ -44,6 +45,8 @@ import { createAdaptiveDeadline } from "../../lib/activity-deadline.mjs";
44
45
  import { extractValidatedPlan } from "../../lib/plan-salvage.mjs";
45
46
  import { auditCompletion } from "../../lib/completion-auditor.mjs";
46
47
  import { mcpExecutionPolicy } from "../../lib/mcp-tool-policy.mjs";
48
+ import { PermissionPolicyStore, permissionRecoveryHint, toolPermissionRequest } from "../../lib/permission-policy.mjs";
49
+ import { analyzeUntrustedOutput, renderUntrustedOutputWarning } from "../../lib/untrusted-output.mjs";
47
50
  import { McpHealthTracker } from "../../lib/mcp-health.mjs";
48
51
  import { captureWorkspaceStateV2, runtimeWorkspaceFingerprint, runtimeWorkspaceSnapshot } from "../../lib/workspace-fingerprint.mjs";
49
52
  import { captureWorkspaceHygieneBaseline, postRunFileHygiene, preFinalWorkspaceAudit } from "../../lib/workspace-hygiene.mjs";
@@ -56,6 +59,17 @@ import {
56
59
  } from "../../lib/browser-mcp-routing.mjs";
57
60
  import { clearRepoGraphRuntimeCache } from "../../lib/repo-graph.mjs";
58
61
  import { clearSemanticIndexRuntimeCache } from "../../lib/semantic-index.mjs";
62
+ import {
63
+ diagnoseCode,
64
+ lspOperation,
65
+ lspPoolStatus,
66
+ probeCodeIntelligence,
67
+ readAnchoredCode,
68
+ searchCodeIntelligence,
69
+ shutdownLspPool,
70
+ } from "../../lib/code-intelligence/index.mjs";
71
+ import { ingestDocument } from "../../lib/document-ingestion.mjs";
72
+ import { compactContext, expandContext, searchContext } from "../../lib/reversible-context.mjs";
59
73
  import {
60
74
  createTaskSandbox,
61
75
  integrateTaskSandbox,
@@ -161,6 +175,22 @@ const CONTEXT_CACHE_MAX = configuredCount(
161
175
  4,
162
176
  128,
163
177
  );
178
+ const PROVIDER_RECOVERY_RETRIES = (() => {
179
+ const parsed = Number(process.env.UES_PROVIDER_RECOVERY_RETRIES ?? "1");
180
+ if (!Number.isFinite(parsed)) return 1;
181
+ return Math.max(0, Math.min(2, Math.trunc(parsed)));
182
+ })();
183
+ const PROVIDER_SESSION_RESUME_RETRIES = (() => {
184
+ const parsed = Number(process.env.UES_PROVIDER_SESSION_RESUME_RETRIES ?? "1");
185
+ if (!Number.isFinite(parsed)) return 1;
186
+ return Math.max(0, Math.min(2, Math.trunc(parsed)));
187
+ })();
188
+ const PROVIDER_RECOVERY_BASE_DELAY_MS = configuredDuration(
189
+ "UES_PROVIDER_RECOVERY_BASE_DELAY_MS",
190
+ 250,
191
+ 50,
192
+ 5_000,
193
+ );
164
194
 
165
195
  function configuredBoolean(name: string, fallback = true) {
166
196
  const raw = String(process.env[name] ?? "").trim().toLowerCase();
@@ -186,6 +216,9 @@ const MCP_HEALTH = new McpHealthTracker({
186
216
  failureThreshold: 2,
187
217
  cooldownMs: configuredDuration("UES_MCP_HEALTH_COOLDOWN_MS", 15_000, 1_000, 5 * 60_000),
188
218
  });
219
+ const PERMISSION_POLICY = new PermissionPolicyStore(
220
+ path.join(getUesConfigDir(), ".ues", "permissions.json"),
221
+ );
189
222
 
190
223
  function abortActiveCliChildren() {
191
224
  let aborted = 0;
@@ -275,6 +308,9 @@ type RunResult = {
275
308
  browserTools?: string[];
276
309
  childRuntime?: "rpc" | "cli";
277
310
  workerReused?: boolean;
311
+ providerFailure?: string;
312
+ providerRecoveryAttempts?: number;
313
+ providerSessionResumeAttempts?: number;
278
314
  subagentArtifact?: any;
279
315
  optimizations?: any;
280
316
  };
@@ -663,6 +699,7 @@ async function runAgentCli(
663
699
  idleTimeoutMs?: number;
664
700
  postToolErrorIdleTimeoutMs?: number;
665
701
  allowLocalEnvWrite?: boolean;
702
+ reuseRpcSession?: boolean;
666
703
  } = {},
667
704
  ): Promise<RunResult> {
668
705
  const config = AGENTS[agent];
@@ -723,6 +760,7 @@ async function runAgentCli(
723
760
  UES_CHILD_TOOL_OUTPUT_LIMIT: String(runtimeOptions.toolOutputLimit || 24 * 1024),
724
761
  UES_CHILD_VERIFICATION_TIMEOUT_SEC: String(runtimeOptions.verificationTimeoutSec || 300),
725
762
  UES_CHILD_ALLOW_LOCAL_ENV_WRITE: runtimeOptions.allowLocalEnvWrite ? "1" : "0",
763
+ UES_CHILD_EXTERNAL_TOOL_NAMES: extraTools.join(","),
726
764
  },
727
765
  shell: false,
728
766
  detached: process.platform !== "win32",
@@ -1011,11 +1049,17 @@ async function runAgentCli(
1011
1049
  }
1012
1050
 
1013
1051
 
1052
+ const RPC_PROMPT_PATH_CACHE = new Map<AgentName, string>();
1053
+
1014
1054
  function rpcPromptPath(agent: AgentName) {
1055
+ const cached = RPC_PROMPT_PATH_CACHE.get(agent);
1056
+ if (cached && fs.existsSync(cached)) return cached;
1057
+
1015
1058
  const dir = path.join(os.tmpdir(), "ues-pi-rpc-prompts");
1016
1059
  fs.mkdirSync(dir, { recursive: true });
1017
1060
  const file = path.join(dir, agent + ".md");
1018
1061
  fs.writeFileSync(file, getAgentPrompt(agent), { encoding: "utf8", mode: 0o600 });
1062
+ RPC_PROMPT_PATH_CACHE.set(agent, file);
1019
1063
  return file;
1020
1064
  }
1021
1065
 
@@ -1048,6 +1092,7 @@ async function runAgentRpc(
1048
1092
  idleTimeoutMs?: number;
1049
1093
  postToolErrorIdleTimeoutMs?: number;
1050
1094
  allowLocalEnvWrite?: boolean;
1095
+ reuseRpcSession?: boolean;
1051
1096
  } = {},
1052
1097
  ): Promise<RunResult> {
1053
1098
  const config = AGENTS[agent];
@@ -1128,6 +1173,7 @@ async function runAgentRpc(
1128
1173
  UES_CHILD_TOOL_OUTPUT_LIMIT: String(runtimeOptions.toolOutputLimit || 24 * 1024),
1129
1174
  UES_CHILD_VERIFICATION_TIMEOUT_SEC: String(runtimeOptions.verificationTimeoutSec || 300),
1130
1175
  UES_CHILD_ALLOW_LOCAL_ENV_WRITE: runtimeOptions.allowLocalEnvWrite ? "1" : "0",
1176
+ UES_CHILD_EXTERNAL_TOOL_NAMES: extraTools.join(","),
1131
1177
  },
1132
1178
  },
1133
1179
  taskInput,
@@ -1139,6 +1185,7 @@ async function runAgentRpc(
1139
1185
  activityWindowMs,
1140
1186
  idleTimeoutMs,
1141
1187
  postToolErrorIdleTimeoutMs,
1188
+ reuseSession: runtimeOptions.reuseRpcSession === true,
1142
1189
  onEvent: (event: any) => {
1143
1190
  lastActivityAt = Date.now();
1144
1191
  if (event.type === "tool_execution_start") {
@@ -1186,13 +1233,13 @@ async function runAgentRpc(
1186
1233
  );
1187
1234
 
1188
1235
  const message = rpc.message;
1189
- const output = extractAssistantText(message) || rpc.stderr || "(no assistant output)";
1190
- return {
1236
+ const assistantText = extractAssistantText(message);
1237
+ const baseResult: RunResult = {
1191
1238
  agent,
1192
1239
  task,
1193
1240
  cwd,
1194
- exitCode: 0,
1195
- output: cap(output, 100 * 1024),
1241
+ exitCode: message?.stopReason === "error" ? 1 : 0,
1242
+ output: cap(assistantText || rpc.stderr || "(no assistant output)", 100 * 1024),
1196
1243
  stderr: cap(String(rpc.stderr || ""), 64 * 1024),
1197
1244
  model: message?.model || model,
1198
1245
  stopReason: message?.stopReason,
@@ -1204,6 +1251,16 @@ async function runAgentRpc(
1204
1251
  childRuntime: "rpc",
1205
1252
  workerReused: rpc.workerReused === true,
1206
1253
  };
1254
+ const providerDecision = classifyProviderFailure(baseResult);
1255
+ if (providerDecision.transient) {
1256
+ return {
1257
+ ...baseResult,
1258
+ exitCode: 1,
1259
+ providerFailure: providerDecision.reason || "empty-provider-response",
1260
+ errorMessage: baseResult.errorMessage || providerDecision.message || "Provider returned no usable assistant content",
1261
+ };
1262
+ }
1263
+ return baseResult;
1207
1264
  } catch (error) {
1208
1265
  const message = error instanceof Error ? error.message : String(error);
1209
1266
  if (detectedHang) {
@@ -1288,54 +1345,137 @@ async function runAgent(
1288
1345
  idleTimeoutMs?: number;
1289
1346
  postToolErrorIdleTimeoutMs?: number;
1290
1347
  allowLocalEnvWrite?: boolean;
1348
+ reuseRpcSession?: boolean;
1291
1349
  } = {},
1292
1350
  ): Promise<RunResult> {
1293
- if (CHILD_RUNTIME !== "cli") {
1351
+ const runOnce = async (): Promise<RunResult> => {
1352
+ if (CHILD_RUNTIME !== "cli") {
1353
+ try {
1354
+ return await runAgentRpc(
1355
+ agent, task, cwd, model, thinkingLevel, signal, onProgress, extraTools, runtimeOptions,
1356
+ );
1357
+ } catch (error) {
1358
+ if (CHILD_RUNTIME === "rpc") throw error;
1359
+ // Auto mode may fall back only when RPC failed before delegated work started.
1360
+ if ((error as any)?.uesRpcPhase && (error as any).uesRpcPhase !== "startup") throw error;
1361
+ try {
1362
+ onProgress?.({
1363
+ agent, elapsedMs: 0, idleMs: 0, toolCalls: 0, model, phase: "running",
1364
+ note: "RPC startup unavailable; falling back to isolated CLI child",
1365
+ });
1366
+ } catch {}
1367
+ }
1368
+ }
1369
+ return runAgentCli(
1370
+ agent, task, cwd, model, thinkingLevel, signal, onProgress, extraTools, runtimeOptions,
1371
+ );
1372
+ };
1373
+
1374
+ let result = await runOnce();
1375
+ let recoveryAttempts = 0;
1376
+ let sessionResumeAttempts = 0;
1377
+ let providerDecision = classifyProviderFailure(result);
1378
+
1379
+ while (
1380
+ providerDecision.transient && providerDecision.safeSessionResume &&
1381
+ result.childRuntime === "rpc" &&
1382
+ sessionResumeAttempts < PROVIDER_SESSION_RESUME_RETRIES && !signal?.aborted
1383
+ ) {
1384
+ sessionResumeAttempts += 1;
1294
1385
  try {
1295
- return await runAgentRpc(
1386
+ onProgress?.({
1387
+ agent, elapsedMs: 0, idleMs: 0, toolCalls: Number(result.toolCalls || 0),
1388
+ model: result.model || model, phase: "running",
1389
+ note: "provider returned no usable content after tool execution; resuming the same RPC session " +
1390
+ sessionResumeAttempts + "/" + PROVIDER_SESSION_RESUME_RETRIES,
1391
+ });
1392
+ } catch {}
1393
+ const delayMs = providerRecoveryBackoffMs(sessionResumeAttempts, {
1394
+ baseMs: PROVIDER_RECOVERY_BASE_DELAY_MS, maxMs: 2_000,
1395
+ });
1396
+ if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
1397
+
1398
+ const priorToolCalls = Number(result.toolCalls || 0);
1399
+ const priorToolNames = Array.isArray(result.toolNames) ? result.toolNames : [];
1400
+ try {
1401
+ const resumed = await runAgentRpc(
1296
1402
  agent,
1297
- task,
1403
+ [
1404
+ "Continue the current delegated task from this existing RPC session after a transient provider failure.",
1405
+ "Do not restart the task and do not repeat tool calls that already completed.",
1406
+ "Use the current conversation, workspace state, and tool evidence as authoritative.",
1407
+ "Continue from the next unfinished step, then return the normal role report/verdict.",
1408
+ ].join(" "),
1298
1409
  cwd,
1299
- model,
1410
+ result.model || model,
1300
1411
  thinkingLevel,
1301
1412
  signal,
1302
1413
  onProgress,
1303
1414
  extraTools,
1304
- runtimeOptions,
1415
+ { ...runtimeOptions, reuseRpcSession: true },
1305
1416
  );
1417
+ result = {
1418
+ ...resumed,
1419
+ task,
1420
+ toolCalls: priorToolCalls + Number(resumed.toolCalls || 0),
1421
+ toolNames: [...new Set([...priorToolNames, ...(resumed.toolNames || [])])],
1422
+ };
1423
+ providerDecision = classifyProviderFailure(result);
1306
1424
  } catch (error) {
1307
- if (CHILD_RUNTIME === "rpc") throw error;
1308
- // Auto mode may fall back only when RPC failed before the delegated task
1309
- // started. In-task failures must not cause a blind second execution.
1310
- if ((error as any)?.uesRpcPhase && (error as any).uesRpcPhase !== "startup") {
1311
- throw error;
1312
- }
1313
1425
  try {
1314
1426
  onProgress?.({
1315
- agent,
1316
- elapsedMs: 0,
1317
- idleMs: 0,
1318
- toolCalls: 0,
1319
- model,
1320
- phase: "running",
1321
- note: "RPC startup unavailable; falling back to isolated CLI child",
1427
+ agent, elapsedMs: 0, idleMs: 0, toolCalls: priorToolCalls,
1428
+ model: result.model || model, phase: "running",
1429
+ note: "same-session provider recovery unavailable; preserving prior evidence without replay",
1322
1430
  });
1323
1431
  } catch {}
1432
+ break;
1324
1433
  }
1325
1434
  }
1326
- return runAgentCli(
1327
- agent,
1328
- task,
1329
- cwd,
1330
- model,
1331
- thinkingLevel,
1332
- signal,
1333
- onProgress,
1334
- extraTools,
1335
- runtimeOptions,
1336
- );
1337
- }
1338
1435
 
1436
+ while (
1437
+ providerDecision.transient && providerDecision.safeReplay &&
1438
+ recoveryAttempts < PROVIDER_RECOVERY_RETRIES && !signal?.aborted
1439
+ ) {
1440
+ recoveryAttempts += 1;
1441
+ try {
1442
+ onProgress?.({
1443
+ agent, elapsedMs: 0, idleMs: 0, toolCalls: Number(result.toolCalls || 0),
1444
+ model: result.model || model, phase: "running",
1445
+ note: "provider returned no usable content; safe fresh-session retry " +
1446
+ recoveryAttempts + "/" + PROVIDER_RECOVERY_RETRIES,
1447
+ });
1448
+ } catch {}
1449
+ const delayMs = providerRecoveryBackoffMs(recoveryAttempts, {
1450
+ baseMs: PROVIDER_RECOVERY_BASE_DELAY_MS, maxMs: 2_000,
1451
+ });
1452
+ if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
1453
+ result = await runOnce();
1454
+ providerDecision = classifyProviderFailure(result);
1455
+ }
1456
+
1457
+ if (providerDecision.transient) {
1458
+ const recoveryNote = providerDecision.safeReplay
1459
+ ? "UES provider recovery exhausted after " + recoveryAttempts +
1460
+ " bounded fresh-session retry attempt(s); task remains failed instead of being reported as success."
1461
+ : providerDecision.safeSessionResume
1462
+ ? "UES same-session provider recovery exhausted or was unavailable after " + sessionResumeAttempts +
1463
+ " attempt(s). Completed tool side effects were preserved and were not blindly replayed."
1464
+ : "UES provider recovery stopped safely without replaying completed side effects.";
1465
+ result = {
1466
+ ...result,
1467
+ exitCode: result.exitCode === 0 ? 1 : result.exitCode,
1468
+ output: [result.output, "", "[UES provider recovery] " + recoveryNote].filter(Boolean).join("\n"),
1469
+ providerFailure: providerDecision.reason || result.providerFailure || "empty-provider-response",
1470
+ };
1471
+ }
1472
+
1473
+ return {
1474
+ ...result,
1475
+ providerRecoveryAttempts: recoveryAttempts,
1476
+ providerSessionResumeAttempts: sessionResumeAttempts,
1477
+ };
1478
+ }
1339
1479
 
1340
1480
  function roleForAgent(agent: AgentName) {
1341
1481
  return agent.replace(/^ues-/, "");
@@ -1386,6 +1526,11 @@ function parseStructuredReport(output: string) {
1386
1526
  };
1387
1527
  }
1388
1528
 
1529
+ function acceptanceEvidenceStatusPresent(run: any) {
1530
+ const value = String(run?.report?.sections?.["acceptance-criteria-proven"] || "");
1531
+ return /(?:^|\n)\s*(?:[-*]\s*)?(?:VERIFIED|INFERRED|UNKNOWN)\s*:/im.test(value);
1532
+ }
1533
+
1389
1534
  function taskRecord(task: string) {
1390
1535
  return {
1391
1536
  id: "pi-dispatch",
@@ -1565,6 +1710,8 @@ async function runRoutedAgent(
1565
1710
  modelTier: selection.tier,
1566
1711
  profile: taskPolicy.executionProfile,
1567
1712
  risk: taskPolicy.risk,
1713
+ decisionConfidence: taskPolicy.decision?.confidence ?? null,
1714
+ decisionReason: taskPolicy.decision?.reason ?? null,
1568
1715
  browserRequested,
1569
1716
  }).catch(() => {});
1570
1717
  }
@@ -1664,39 +1811,39 @@ async function runRoutedAgent(
1664
1811
  }
1665
1812
  }
1666
1813
 
1667
- if (MICRO_SKILLS_ENABLED) {
1668
- microSkills = await compileSkillContext(taskPolicy, role, {
1669
- maxSkills: taskPolicy.maxSkills,
1670
- totalChars: taskPolicy.executionProfile === "fast" ? 1800 : 3200,
1671
- }).catch(() => null);
1672
- }
1673
-
1674
- if (
1814
+ const [microSkillResult, affectedTestResult, reusableVerificationResult] = await Promise.all([
1815
+ MICRO_SKILLS_ENABLED
1816
+ ? compileSkillContext(taskPolicy, role, {
1817
+ maxSkills: taskPolicy.maxSkills,
1818
+ totalChars: taskPolicy.executionProfile === "fast" ? 1800 : 3200,
1819
+ taskText: task,
1820
+ }).catch(() => null)
1821
+ : Promise.resolve(null),
1675
1822
  AFFECTED_TEST_HINTS_ENABLED &&
1676
1823
  ["executor", "debugger", "verifier", "integration-verifier"].includes(role)
1677
- ) {
1678
- affectedTests = await resolveAffectedTests(cwd, {
1679
- limit: 10,
1680
- changedFiles: workspaceState.changedFiles || [],
1681
- ...(workspaceState.cacheable === true && workspaceFingerprint !== "unknown"
1682
- ? { workspaceFingerprint }
1683
- : {}),
1684
- }).catch(() => null);
1685
- }
1686
-
1687
- if (
1824
+ ? resolveAffectedTests(cwd, {
1825
+ limit: 10,
1826
+ changedFiles: workspaceState.changedFiles || [],
1827
+ ...(workspaceState.cacheable === true && workspaceFingerprint !== "unknown"
1828
+ ? { workspaceFingerprint }
1829
+ : {}),
1830
+ }).catch(() => null)
1831
+ : Promise.resolve(null),
1688
1832
  ["verifier", "integration-verifier"].includes(role) &&
1689
1833
  !["high", "critical"].includes(String(taskPolicy.risk || "").toLowerCase())
1690
- ) {
1691
- reusableVerification = await listReusableVerification(cwd, {
1692
- limit: 8,
1693
- maxAgeMs: 30 * 60_000,
1694
- previewBytes: 2200,
1695
- ...(workspaceState.cacheable === true && workspaceFingerprint !== "unknown"
1696
- ? { workspaceFingerprint }
1697
- : {}),
1698
- }).catch(() => null);
1699
- }
1834
+ ? listReusableVerification(cwd, {
1835
+ limit: 8,
1836
+ maxAgeMs: 30 * 60_000,
1837
+ previewBytes: 2200,
1838
+ ...(workspaceState.cacheable === true && workspaceFingerprint !== "unknown"
1839
+ ? { workspaceFingerprint }
1840
+ : {}),
1841
+ }).catch(() => null)
1842
+ : Promise.resolve(null),
1843
+ ]);
1844
+ microSkills = microSkillResult;
1845
+ affectedTests = affectedTestResult;
1846
+ reusableVerification = reusableVerificationResult;
1700
1847
 
1701
1848
  contextQuality = pack.contextQuality;
1702
1849
  contextPerformance = pack.performance || null;
@@ -2926,8 +3073,16 @@ export default function (pi: ExtensionAPI) {
2926
3073
  const AUTO_ADMISSION_ENABLED = !["0", "false", "off"].includes(
2927
3074
  String(process.env.UES_AUTO_ADMIT || "1").trim().toLowerCase(),
2928
3075
  );
2929
- const UES_PARENT_TOOL_NAMES = new Set(["ues_cli", "ues_execute", "ues_service", "ues_dispatch"]);
3076
+ const UES_PARENT_TOOL_NAMES = new Set(["ues_cli", "ues_execute", "ues_service", "ues_session", "ues_dispatch"]);
3077
+ const ALWAYS_ON_PARENT_TOOLS = new Set(["ues_code"]);
2930
3078
  let normalActiveTools: string[] | null = null;
3079
+ let parentRunToolCalls = 0;
3080
+ let parentRunLastAssistant: any = null;
3081
+ let parentProviderRecoveryConsecutive = 0;
3082
+ let parentProviderRecoveryTotal = 0;
3083
+ let parentProviderRecoveryPending = false;
3084
+ const PARENT_PROVIDER_RECOVERY_MAX_CONSECUTIVE = 1;
3085
+ const PARENT_PROVIDER_RECOVERY_MAX_TOTAL = 3;
2931
3086
 
2932
3087
  const currentNonUesTools = () =>
2933
3088
  pi.getActiveTools().filter((name) => !UES_PARENT_TOOL_NAMES.has(String(name)));
@@ -2986,7 +3141,7 @@ export default function (pi: ExtensionAPI) {
2986
3141
  content,
2987
3142
  display: false,
2988
3143
  details: {
2989
- schemaVersion: 1,
3144
+ schemaVersion: packet.schemaVersion || 1,
2990
3145
  reason: event.reason,
2991
3146
  willRetry: event.willRetry,
2992
3147
  workspaceCount: packet.workspaceCount,
@@ -3008,6 +3163,7 @@ export default function (pi: ExtensionAPI) {
3008
3163
  MCP_HEALTH.clear();
3009
3164
  abortActiveCliChildren();
3010
3165
  await stopAllServices().catch(() => []);
3166
+ await shutdownLspPool().catch(() => ({ stopped: 0, remaining: 0 }));
3011
3167
  await RPC_POOL.stopAll().catch(() => {});
3012
3168
  for (const dir of [...ACTIVE_TASK_SANDBOXES.keys()]) {
3013
3169
  const ownerRoot = await taskSandboxOwnerRoot(dir).catch(() => null);
@@ -3113,13 +3269,27 @@ export default function (pi: ExtensionAPI) {
3113
3269
  return { action: "continue" };
3114
3270
  }
3115
3271
 
3116
- const steered = await RPC_POOL.steerActive(text).catch(() => ({
3117
- accepted: false,
3118
- reason: "steer-failed",
3119
- active: 0,
3120
- }));
3121
- if (steered.accepted) {
3122
- try { ctx.ui.notify("UES: steering message forwarded to the active child", "info"); } catch {}
3272
+ const streamingBehavior = String(event.streamingBehavior || "");
3273
+ const forwarded = streamingBehavior === "followUp"
3274
+ ? await RPC_POOL.followUpActive(text).catch(() => ({
3275
+ accepted: false,
3276
+ reason: "follow-up-failed",
3277
+ active: 0,
3278
+ }))
3279
+ : await RPC_POOL.steerActive(text).catch(() => ({
3280
+ accepted: false,
3281
+ reason: "steer-failed",
3282
+ active: 0,
3283
+ }));
3284
+ if (forwarded.accepted) {
3285
+ try {
3286
+ ctx.ui.notify(
3287
+ streamingBehavior === "followUp"
3288
+ ? "UES: follow-up queued for the active child"
3289
+ : "UES: steering message forwarded to the active child",
3290
+ "info",
3291
+ );
3292
+ } catch {}
3123
3293
  return { action: "handled" };
3124
3294
  }
3125
3295
 
@@ -3129,19 +3299,109 @@ export default function (pi: ExtensionAPI) {
3129
3299
  });
3130
3300
 
3131
3301
  pi.on("before_agent_start", async () => {
3302
+ // Pi may start another provider request inside the same parent run when
3303
+ // agent_before_settle asks to continue. Preserve recovery accounting for
3304
+ // that continuation, but reset it for a genuinely new parent run.
3305
+ if (parentProviderRecoveryPending) {
3306
+ parentProviderRecoveryPending = false;
3307
+ parentRunLastAssistant = null;
3308
+ } else {
3309
+ parentRunToolCalls = 0;
3310
+ parentRunLastAssistant = null;
3311
+ parentProviderRecoveryConsecutive = 0;
3312
+ parentProviderRecoveryTotal = 0;
3313
+ }
3132
3314
  if (uesModeActive()) activateParentUesTools();
3133
3315
  else deactivateParentUesTools();
3134
3316
  });
3135
3317
 
3318
+ pi.on("message_end", async (event) => {
3319
+ if ((event as any)?.message?.role !== "assistant") return;
3320
+ parentRunLastAssistant = (event as any).message;
3321
+
3322
+ // A substantive assistant response proves the previous empty-response
3323
+ // incident recovered. Reset only the consecutive incident budget; keep the
3324
+ // total run budget bounded so a flaky provider cannot loop forever.
3325
+ const text = extractAssistantText(parentRunLastAssistant).trim();
3326
+ if (text && parentRunLastAssistant?.stopReason !== "error") {
3327
+ parentProviderRecoveryConsecutive = 0;
3328
+ parentProviderRecoveryPending = false;
3329
+ }
3330
+ });
3331
+
3332
+ pi.on("agent_before_settle", async (event) => {
3333
+ if (
3334
+ uesModeActive() ||
3335
+ parentProviderRecoveryConsecutive >= PARENT_PROVIDER_RECOVERY_MAX_CONSECUTIVE ||
3336
+ parentProviderRecoveryTotal >= PARENT_PROVIDER_RECOVERY_MAX_TOTAL
3337
+ ) {
3338
+ return undefined;
3339
+ }
3340
+
3341
+ const assistantText = extractAssistantText(parentRunLastAssistant);
3342
+ const decision = classifyProviderFailure({
3343
+ output: assistantText || "(no assistant output)",
3344
+ errorMessage: parentRunLastAssistant?.errorMessage,
3345
+ stopReason: parentRunLastAssistant?.stopReason,
3346
+ toolCalls: parentRunToolCalls,
3347
+ noAssistantMessage: !parentRunLastAssistant,
3348
+ });
3349
+ if (!decision.transient) return undefined;
3350
+
3351
+ parentProviderRecoveryConsecutive += 1;
3352
+ parentProviderRecoveryTotal += 1;
3353
+ parentProviderRecoveryPending = true;
3354
+ const afterTools = parentRunToolCalls > 0;
3355
+ const recoveryMessage = afterTools
3356
+ ? [
3357
+ "UES parent provider recovery: the previous provider response was empty after completed tool calls.",
3358
+ "Continue from the current conversation and existing tool results.",
3359
+ "Do not restart the task and do not repeat tool calls that already completed unless fresh verification is strictly required.",
3360
+ "Resume from the next unfinished step and finish the requested work/report.",
3361
+ ].join(" ")
3362
+ : [
3363
+ "UES parent provider recovery: the previous provider response was empty before any tool side effect.",
3364
+ "Retry the current request once using the existing conversation context and return a complete response.",
3365
+ ].join(" ");
3366
+
3367
+ return {
3368
+ entries: [
3369
+ ...(event.entries || []),
3370
+ {
3371
+ type: "custom_message",
3372
+ customType: "ues-parent-provider-recovery",
3373
+ content: recoveryMessage,
3374
+ display: false,
3375
+ details: {
3376
+ schemaVersion: 2,
3377
+ incidentAttempt: parentProviderRecoveryConsecutive,
3378
+ maxIncidentAttempts: PARENT_PROVIDER_RECOVERY_MAX_CONSECUTIVE,
3379
+ totalAttempts: parentProviderRecoveryTotal,
3380
+ maxTotalAttempts: PARENT_PROVIDER_RECOVERY_MAX_TOTAL,
3381
+ reason: decision.reason,
3382
+ toolCalls: parentRunToolCalls,
3383
+ safeReplay: decision.safeReplay,
3384
+ safeSessionResume: decision.safeSessionResume,
3385
+ },
3386
+ },
3387
+ ],
3388
+ continue: true,
3389
+ };
3390
+ });
3391
+
3136
3392
  pi.on("agent_end", async () => {
3137
3393
  promptUesActive = false;
3394
+ parentProviderRecoveryPending = false;
3395
+ parentProviderRecoveryConsecutive = 0;
3396
+ parentProviderRecoveryTotal = 0;
3138
3397
  deactivateParentUesTools();
3139
3398
  });
3140
3399
 
3141
3400
  pi.on("tool_call", async (event, ctx) => {
3142
3401
  const toolName = String(event.toolName || "");
3402
+ if (!uesModeActive()) parentRunToolCalls += 1;
3143
3403
  if (!uesModeActive()) {
3144
- if (toolName.startsWith("ues_")) {
3404
+ if (toolName.startsWith("ues_") && !ALWAYS_ON_PARENT_TOOLS.has(toolName)) {
3145
3405
  return {
3146
3406
  block: true,
3147
3407
  reason: "UES parent tools are hidden outside admitted UES runs. Submit a normal engineering task or start an explicit /ues-* command.",
@@ -3149,11 +3409,48 @@ export default function (pi: ExtensionAPI) {
3149
3409
  }
3150
3410
  return undefined;
3151
3411
  }
3412
+ const permissionRequest = toolPermissionRequest(toolName, (event as any).input || {});
3413
+ const configuredPermission: any = await PERMISSION_POLICY.evaluate(permissionRequest, {
3414
+ agent: "ues-parent",
3415
+ }).catch((error) => ({
3416
+ configured: true,
3417
+ decision: null,
3418
+ error: error instanceof Error ? error.message : String(error),
3419
+ }));
3420
+ if (configuredPermission.error) {
3421
+ return {
3422
+ block: true,
3423
+ reason: "UES permission policy is invalid: " + configuredPermission.error,
3424
+ };
3425
+ }
3426
+ const permissionEffect = configuredPermission?.decision?.effect;
3427
+ let permissionApproved = false;
3428
+ if (permissionEffect === "deny") {
3429
+ return {
3430
+ block: true,
3431
+ reason: permissionRecoveryHint(permissionRequest, configuredPermission.decision, { effect: "deny" }),
3432
+ };
3433
+ }
3434
+ if (permissionEffect === "ask") {
3435
+ const allowed = await approveRisk(
3436
+ ctx,
3437
+ permissionRequest.resources.join("\n"),
3438
+ "permission:" + permissionRequest.action,
3439
+ );
3440
+ if (!allowed) {
3441
+ return {
3442
+ block: true,
3443
+ reason: permissionRecoveryHint(permissionRequest, configuredPermission.decision, { effect: "ask" }),
3444
+ };
3445
+ }
3446
+ permissionApproved = true;
3447
+ }
3448
+
3152
3449
  if (toolName !== "bash" && toolName !== "powershell") {
3153
3450
  const allTools = typeof (pi as any).getAllTools === "function" ? (pi as any).getAllTools() : [];
3154
3451
  const descriptor = allTools.find((tool: any) => String(tool?.name || "") === toolName);
3155
3452
  const policy = mcpExecutionPolicy(descriptor || { name: toolName });
3156
- if (policy.confirmationRequired) {
3453
+ if (policy.confirmationRequired && !permissionApproved) {
3157
3454
  const allowed = await approveRisk(ctx, `MCP/tool call: ${toolName}`, `mcp-destructive:${toolName}`);
3158
3455
  if (!allowed) return { block: true, reason: `Blocked by UES MCP destructive-hint gate: ${toolName}` };
3159
3456
  }
@@ -3172,8 +3469,10 @@ export default function (pi: ExtensionAPI) {
3172
3469
  }
3173
3470
  const risk = destructiveShellRisk(command);
3174
3471
  if (!risk.risky) return undefined;
3175
- const allowed = await approveRisk(ctx, command, risk.id || "destructive");
3176
- if (!allowed) return { block: true, reason: `Blocked by UES safety gate: ${risk.id}` };
3472
+ if (!permissionApproved) {
3473
+ const allowed = await approveRisk(ctx, command, risk.id || "destructive");
3474
+ if (!allowed) return { block: true, reason: `Blocked by UES safety gate: ${risk.id}` };
3475
+ }
3177
3476
  return undefined;
3178
3477
  });
3179
3478
 
@@ -3181,14 +3480,222 @@ export default function (pi: ExtensionAPI) {
3181
3480
  if (!uesModeActive()) return undefined;
3182
3481
  const toolName = String((event as any).toolName || "");
3183
3482
  if (!toolName || toolName === "bash" || toolName === "powershell") return undefined;
3483
+ const resultText = toolResultText(event);
3484
+ const allTools = typeof (pi as any).getAllTools === "function" ? (pi as any).getAllTools() : [];
3485
+ const descriptor = allTools.find((tool: any) => String(tool?.name || "") === toolName);
3486
+ const policy = mcpExecutionPolicy(descriptor || { name: toolName });
3184
3487
  MCP_HEALTH.finish(
3185
3488
  String((event as any).toolCallId || ""),
3186
3489
  {
3187
3490
  isError: (event as any).isError === true,
3188
- text: toolResultText(event),
3491
+ text: resultText,
3189
3492
  },
3190
3493
  );
3191
- return undefined;
3494
+
3495
+ const externalBoundary =
3496
+ policy.externalEvidenceBoundary === true ||
3497
+ HOST_BROWSER_TOOL_NAMES.includes(toolName);
3498
+ if (!externalBoundary || !resultText) return undefined;
3499
+
3500
+ const analysis = analyzeUntrustedOutput(resultText, { source: toolName });
3501
+ if (!analysis.flagged) return undefined;
3502
+ const originalContent = Array.isArray((event as any).content)
3503
+ ? (event as any).content
3504
+ : [{ type: "text", text: resultText }];
3505
+ return {
3506
+ content: [
3507
+ { type: "text", text: renderUntrustedOutputWarning(analysis, { source: toolName }) },
3508
+ ...originalContent,
3509
+ ],
3510
+ details: {
3511
+ ...((event as any).details && typeof (event as any).details === "object" ? (event as any).details : {}),
3512
+ uesUntrustedOutputBoundary: analysis,
3513
+ },
3514
+ isError: (event as any).isError === true,
3515
+ usage: (event as any).usage,
3516
+ };
3517
+ });
3518
+
3519
+ // Always-on, read-only intelligence for normal Pi conversations.
3520
+ // Keep this outside UES_PARENT_TOOL_NAMES so session_start does not hide it.
3521
+ pi.registerTool({
3522
+ name: "ues_code",
3523
+ label: "UES Code Intelligence Lite",
3524
+ description:
3525
+ "Always-on read-only UES code intelligence for normal Pi chats. Provides bounded semantic/AST search, anchored reads, persistent LSP navigation/diagnostics, document ingestion, and reversible context access without starting the UES controller or a specialist child.",
3526
+ parameters: Type.Object({
3527
+ action: Type.Union([
3528
+ Type.Literal("status"),
3529
+ Type.Literal("search"),
3530
+ Type.Literal("read"),
3531
+ Type.Literal("diagnostics"),
3532
+ Type.Literal("definition"),
3533
+ Type.Literal("references"),
3534
+ Type.Literal("symbols"),
3535
+ Type.Literal("hover"),
3536
+ Type.Literal("rename-preview"),
3537
+ Type.Literal("incoming-calls"),
3538
+ Type.Literal("outgoing-calls"),
3539
+ Type.Literal("document"),
3540
+ Type.Literal("context-expand"),
3541
+ Type.Literal("context-search"),
3542
+ ]),
3543
+ file: Type.Optional(Type.String()),
3544
+ query: Type.Optional(Type.String()),
3545
+ structuralPattern: Type.Optional(Type.String()),
3546
+ language: Type.Optional(Type.String()),
3547
+ startLine: Type.Optional(Type.Number({ minimum: 1 })),
3548
+ endLine: Type.Optional(Type.Number({ minimum: 1 })),
3549
+ line: Type.Optional(Type.Number({ minimum: 1 })),
3550
+ character: Type.Optional(Type.Number({ minimum: 1 })),
3551
+ newName: Type.Optional(Type.String({ minLength: 1, maxLength: 512 })),
3552
+ includeDeclaration: Type.Optional(Type.Boolean()),
3553
+ ref: Type.Optional(Type.String()),
3554
+ maxBytes: Type.Optional(Type.Number({ minimum: 1, maximum: 128000 })),
3555
+ }),
3556
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
3557
+ try {
3558
+ let result: any;
3559
+ if (params.action === "status") {
3560
+ result = probeCodeIntelligence(params.file || "");
3561
+ result = {
3562
+ ...result,
3563
+ mode: "parent-lite",
3564
+ controllerStarted: false,
3565
+ childSpawned: false,
3566
+ lsp: {
3567
+ ...result.lsp,
3568
+ persistentPool: {
3569
+ ...lspPoolStatus({ includeSessions: false, persistent: true }),
3570
+ policy: { enabled: true, source: "parent-lite" },
3571
+ },
3572
+ },
3573
+ };
3574
+ } else if (params.action === "search") {
3575
+ if (!params.query) throw new Error("ues_code search requires query");
3576
+ result = await searchCodeIntelligence(ctx.cwd, params.query, {
3577
+ structuralPattern: params.structuralPattern,
3578
+ language: params.language,
3579
+ file: params.file,
3580
+ maxResults: 12,
3581
+ });
3582
+ result.mode = "parent-lite";
3583
+ } else if (params.action === "read") {
3584
+ if (!params.file) throw new Error("ues_code read requires file");
3585
+ const startLine = Math.max(1, Math.trunc(Number(params.startLine || 1)));
3586
+ const endLine = Math.max(startLine, Math.min(startLine + 399, Math.trunc(Number(params.endLine || startLine + 199))));
3587
+ const read = await readAnchoredCode(ctx.cwd, params.file, { startLine, endLine });
3588
+ return {
3589
+ content: [{ type: "text", text: [
3590
+ `file: ${read.file}; lines: ${read.startLine}-${read.endLine}/${read.lineCount}; sourceHash: ${read.sourceHash}`,
3591
+ "",
3592
+ read.text,
3593
+ ].join("\n") }],
3594
+ details: {
3595
+ action: params.action,
3596
+ mode: "parent-lite",
3597
+ controllerStarted: false,
3598
+ childSpawned: false,
3599
+ file: read.file,
3600
+ sourceHash: read.sourceHash,
3601
+ startLine: read.startLine,
3602
+ endLine: read.endLine,
3603
+ },
3604
+ };
3605
+ } else if (params.action === "diagnostics") {
3606
+ if (!params.file) throw new Error("ues_code diagnostics requires file");
3607
+ result = await diagnoseCode(ctx.cwd, params.file, {
3608
+ timeoutMs: 5000,
3609
+ maxResults: 80,
3610
+ persistent: true,
3611
+ });
3612
+ } else if (["definition", "references", "symbols", "hover", "rename-preview", "incoming-calls", "outgoing-calls"].includes(params.action)) {
3613
+ if (!params.file) throw new Error(`ues_code ${params.action} requires file`);
3614
+ if (params.action === "rename-preview" && !params.newName) throw new Error("ues_code rename-preview requires newName");
3615
+ result = await lspOperation(ctx.cwd, params.file, params.action, {
3616
+ line: params.line || 1,
3617
+ character: params.character || 1,
3618
+ newName: params.newName,
3619
+ includeDeclaration: params.includeDeclaration,
3620
+ timeoutMs: 7000,
3621
+ maxResults: 120,
3622
+ persistent: true,
3623
+ });
3624
+ } else if (params.action === "document") {
3625
+ if (!params.file) throw new Error("ues_code document requires file");
3626
+ const document = await ingestDocument(ctx.cwd, params.file, {
3627
+ maxBytes: Math.min(Number(params.maxBytes || 4 * 1024 * 1024), 4 * 1024 * 1024),
3628
+ });
3629
+ const block = await compactContext(ctx.cwd, document.markdown, {
3630
+ kind: "document-ingestion",
3631
+ source: document.file,
3632
+ summary: `Normalized document from ${document.provider}`,
3633
+ });
3634
+ result = {
3635
+ provider: document.provider,
3636
+ file: document.file,
3637
+ bytes: document.bytes,
3638
+ optionalDependency: document.optionalDependency,
3639
+ contextRef: block.ref,
3640
+ originalChars: block.originalChars,
3641
+ summary: block.levels.T1,
3642
+ };
3643
+ } else if (params.action === "context-expand") {
3644
+ if (!params.ref) throw new Error("ues_code context-expand requires ref");
3645
+ const expanded = await expandContext(ctx.cwd, params.ref, { maxBytes: params.maxBytes || 16000 });
3646
+ return {
3647
+ content: [{ type: "text", text: expanded.content }],
3648
+ details: {
3649
+ action: params.action,
3650
+ mode: "parent-lite",
3651
+ controllerStarted: false,
3652
+ childSpawned: false,
3653
+ ref: expanded.ref,
3654
+ start: expanded.start,
3655
+ returnedBytes: expanded.returnedBytes,
3656
+ truncated: expanded.truncated,
3657
+ },
3658
+ };
3659
+ } else if (params.action === "context-search") {
3660
+ if (!params.ref || !params.query) throw new Error("ues_code context-search requires ref and query");
3661
+ result = await searchContext(ctx.cwd, params.ref, params.query, {
3662
+ maxBytes: params.maxBytes || 512000,
3663
+ maxMatches: 12,
3664
+ });
3665
+ } else {
3666
+ throw new Error("unsupported ues_code action");
3667
+ }
3668
+
3669
+ const payload = {
3670
+ ...result,
3671
+ mode: result?.mode || "parent-lite",
3672
+ controllerStarted: false,
3673
+ childSpawned: false,
3674
+ };
3675
+ const encoded = JSON.stringify(payload, null, 2);
3676
+ return {
3677
+ content: [{ type: "text", text: encoded.length <= 32000 ? encoded : encoded.slice(0, 32000) + "\n...[bounded by ues_code parent-lite]" }],
3678
+ details: {
3679
+ action: params.action,
3680
+ mode: "parent-lite",
3681
+ controllerStarted: false,
3682
+ childSpawned: false,
3683
+ bounded: encoded.length > 32000,
3684
+ },
3685
+ };
3686
+ } catch (error) {
3687
+ return {
3688
+ content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
3689
+ details: {
3690
+ action: params.action,
3691
+ mode: "parent-lite",
3692
+ controllerStarted: false,
3693
+ childSpawned: false,
3694
+ },
3695
+ isError: true,
3696
+ };
3697
+ }
3698
+ },
3192
3699
  });
3193
3700
 
3194
3701
  pi.registerTool({
@@ -3344,6 +3851,60 @@ export default function (pi: ExtensionAPI) {
3344
3851
  },
3345
3852
  });
3346
3853
 
3854
+ pi.registerTool({
3855
+ name: "ues_session",
3856
+ label: "UES Session Control",
3857
+ description:
3858
+ "Control the single active Pi RPC specialist session without shell orchestration. Supports state inspection, steer vs follow-up queueing, abort, queue clearing, model/thinking changes, compaction, and bounded wait. Fails closed when zero or multiple child sessions are active.",
3859
+ parameters: Type.Object({
3860
+ action: Type.Union([
3861
+ Type.Literal("status"),
3862
+ Type.Literal("get-state"),
3863
+ Type.Literal("steer"),
3864
+ Type.Literal("follow-up"),
3865
+ Type.Literal("abort"),
3866
+ Type.Literal("clear-queue"),
3867
+ Type.Literal("set-model"),
3868
+ Type.Literal("set-thinking"),
3869
+ Type.Literal("compact"),
3870
+ Type.Literal("wait"),
3871
+ ]),
3872
+ message: Type.Optional(Type.String({ maxLength: 12000 })),
3873
+ provider: Type.Optional(Type.String({ maxLength: 120 })),
3874
+ modelId: Type.Optional(Type.String({ maxLength: 240 })),
3875
+ thinkingLevel: Type.Optional(Type.String({ maxLength: 40 })),
3876
+ compactInstructions: Type.Optional(Type.String({ maxLength: 12000 })),
3877
+ timeoutMs: Type.Optional(Type.Number({ minimum: 500, maximum: 1800000 })),
3878
+ }),
3879
+ async execute(_toolCallId, params) {
3880
+ try {
3881
+ const action = String(params.action || "status");
3882
+ const result = action === "status"
3883
+ ? { ok: true, action, ...RPC_POOL.status() }
3884
+ : await RPC_POOL.controlActive(action, {
3885
+ message: params.message,
3886
+ provider: params.provider,
3887
+ modelId: params.modelId,
3888
+ level: params.thinkingLevel,
3889
+ customInstructions: params.compactInstructions,
3890
+ timeoutMs: params.timeoutMs,
3891
+ });
3892
+ return {
3893
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
3894
+ details: result,
3895
+ isError: result?.ok === false,
3896
+ };
3897
+ } catch (error) {
3898
+ const message = error instanceof Error ? error.message : String(error);
3899
+ return {
3900
+ content: [{ type: "text", text: message }],
3901
+ details: { action: params.action, error: message },
3902
+ isError: true,
3903
+ };
3904
+ }
3905
+ },
3906
+ });
3907
+
3347
3908
  const uesExecuteTool: any = {
3348
3909
  name: "ues_execute",
3349
3910
  label: "UES Execute",
@@ -3396,6 +3957,8 @@ export default function (pi: ExtensionAPI) {
3396
3957
  profile: policy.executionProfile,
3397
3958
  risk: policy.risk,
3398
3959
  mode: policy.mode,
3960
+ decisionConfidence: policy.decision?.confidence ?? null,
3961
+ decisionReason: policy.decision?.reason ?? null,
3399
3962
  inheritedDirtyCount: executionContract.inheritedDirty?.paths?.length || 0,
3400
3963
  explicitPhaseCount: executionContract.phases?.length || 0,
3401
3964
  localEnvWriteExplicitlyAllowed: executionContract.localEnvWriteExplicitlyAllowed === true,
@@ -3583,8 +4146,8 @@ export default function (pi: ExtensionAPI) {
3583
4146
  "",
3584
4147
  "## Acceptance criteria proven",
3585
4148
  deterministicPass
3586
- ? "All explicitly requested whitelisted Git inspection commands completed successfully."
3587
- : "The requested deterministic inspection did not fully pass.",
4149
+ ? "VERIFIED: All explicitly requested whitelisted Git inspection commands completed successfully."
4150
+ : "UNKNOWN: The requested deterministic inspection did not fully pass.",
3588
4151
  "",
3589
4152
  "## Failures",
3590
4153
  failureText,
@@ -3644,6 +4207,27 @@ export default function (pi: ExtensionAPI) {
3644
4207
  1,
3645
4208
  );
3646
4209
  if (isAbortedRun(inspection)) return abortedResponse(inspection, "read-only-inspection");
4210
+ if (
4211
+ inspection.exitCode === 0 &&
4212
+ inspection.verdict === "PASS" &&
4213
+ !acceptanceEvidenceStatusPresent(inspection)
4214
+ ) {
4215
+ inspection = await run(
4216
+ "ues-verifier",
4217
+ [
4218
+ "READ-ONLY VERIFICATION FORMAT RECOVERY. The previous verifier returned PASS but omitted mandatory evidence-status prefixes.",
4219
+ "Do not edit files. Re-check only what is needed to support the existing verdict.",
4220
+ "In ## Acceptance criteria proven, prefix every requested criterion with exactly VERIFIED:, INFERRED:, or UNKNOWN:.",
4221
+ "INFERRED or UNKNOWN criteria must also be listed under ## Unresolved gaps and cannot support PASS.",
4222
+ "",
4223
+ "Inspection request:",
4224
+ params.task,
4225
+ ].join("\n"),
4226
+ 1,
4227
+ "Previous verifier PASS omitted evidence-status markers.",
4228
+ );
4229
+ if (isAbortedRun(inspection)) return abortedResponse(inspection, "read-only-verification-format-recovery");
4230
+ }
3647
4231
  }
3648
4232
 
3649
4233
  const after = runtimeWorkspaceSnapshot(cwd);
@@ -3658,6 +4242,7 @@ export default function (pi: ExtensionAPI) {
3658
4242
  workspaceSnapshot: after,
3659
4243
  behavioralReceipts: [],
3660
4244
  requireBehavioralReceipt: false,
4245
+ requireClaimEvidenceStatus: true,
3661
4246
  });
3662
4247
  if (!unchanged) {
3663
4248
  completionAudit.passed = false;
@@ -4422,7 +5007,7 @@ export default function (pi: ExtensionAPI) {
4422
5007
  agent: "ues-deterministic-verifier", task: params.task, cwd, exitCode: 0,
4423
5008
  output: ["FAST bounded verification reused fresh behavioral evidence captured at the tool boundary.", ...fastGate.behavioralReceipts.map((item: any) => "- " + item.command), "", "UES_VERDICT: PASS"].join("\n"),
4424
5009
  stderr: "", verdict: "PASS", durationMs: 0, toolCalls: 0, toolNames: [],
4425
- report: { schemaVersion: 1, valid: true, verdict: "PASS", sections: { "checks-run": fastGate.behavioralReceipts.map((item: any) => item.command).join("\n"), "acceptance-criteria-proven": "Fresh behavioral verification receipt(s) exist at the post-implementation workspace fingerprint.", "completion-evidence": "Deterministic PASS receipt captured after this implementation attempt." } },
5010
+ report: { schemaVersion: 1, valid: true, verdict: "PASS", sections: { "checks-run": fastGate.behavioralReceipts.map((item: any) => item.command).join("\n"), "acceptance-criteria-proven": "VERIFIED: Fresh behavioral verification receipt(s) exist at the post-implementation workspace fingerprint.", "completion-evidence": "Deterministic PASS receipt captured after this implementation attempt." } },
4426
5011
  optimizations: { fastDeterministicVerification: true, behavioralReceiptCount: fastGate.behavioralReceipts.length },
4427
5012
  };
4428
5013
  steps.push(verification);
@@ -4440,6 +5025,30 @@ export default function (pi: ExtensionAPI) {
4440
5025
  ].join("\n"), attempt, recentFailure || undefined,
4441
5026
  );
4442
5027
  if (isAbortedRun(verification)) return abortedResponse(verification, "verification");
5028
+ if (
5029
+ verification.exitCode === 0 &&
5030
+ verification.verdict === "PASS" &&
5031
+ !acceptanceEvidenceStatusPresent(verification)
5032
+ ) {
5033
+ verification = await run(
5034
+ "ues-verifier",
5035
+ [
5036
+ "VERIFICATION FORMAT RECOVERY. The previous verifier returned PASS but omitted mandatory evidence-status prefixes.",
5037
+ "Do not edit files and do not re-run broad checks. Re-check only what is necessary to support the verdict.",
5038
+ "In ## Acceptance criteria proven, prefix every requested criterion with exactly VERIFIED:, INFERRED:, or UNKNOWN:.",
5039
+ "INFERRED or UNKNOWN criteria must also appear under ## Unresolved gaps and cannot support PASS.",
5040
+ "",
5041
+ "Original task:",
5042
+ params.task,
5043
+ "",
5044
+ "Previous verifier output (not proof by itself):",
5045
+ cap(verification.output, 5000),
5046
+ ].join("\n"),
5047
+ attempt,
5048
+ "Previous verifier PASS omitted evidence-status markers.",
5049
+ );
5050
+ if (isAbortedRun(verification)) return abortedResponse(verification, "verification-format-recovery");
5051
+ }
4443
5052
  }
4444
5053
  const verified = verification.exitCode === 0 && verification.verdict === "PASS";
4445
5054
  if (!verified) {
@@ -4526,6 +5135,7 @@ export default function (pi: ExtensionAPI) {
4526
5135
  workspaceSnapshot: completionSnapshot,
4527
5136
  behavioralReceipts: freshReceipts,
4528
5137
  requireBehavioralReceipt: policy.requireBehavioralReceipt !== false,
5138
+ requireClaimEvidenceStatus: true,
4529
5139
  });
4530
5140
  if (!completionAudit.passed) {
4531
5141
  recentFailure = "Completion auditor rejected PASS: " + completionAudit.failures.join(", ");
@@ -4666,6 +5276,10 @@ export default function (pi: ExtensionAPI) {
4666
5276
  "Micro skills: " + (MICRO_SKILLS_ENABLED ? "on" : "off"),
4667
5277
  "Turbo Fast Path: on",
4668
5278
  "Parent UES tools hidden outside UES runs: on",
5279
+ "Parent Code Intelligence Lite: on (always-on read-only ues_code; no controller/child)",
5280
+ "Native Pi RPC session control: on (state/steer/follow-up/abort/model/thinking/compact/wait)",
5281
+ "Permission deny-and-continue recovery: on",
5282
+ "Provider empty-response recovery: on (per-incident parent retry + bounded total + child RPC recovery; no blind replay after tools)",
4669
5283
  "Zero-friction engineering admission: " + (AUTO_ADMISSION_ENABLED ? "on (native / auto / high-risk + safe continuation)" : "off"),
4670
5284
  "Git-root artifact guard: on",
4671
5285
  "Inherited dirty-work guard: on",