opencode-agent-skill 15.1.0 → 15.1.2

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 (43) hide show
  1. package/README.md +5 -5
  2. package/docs/V15-MANAGED-RUNTIME.md +18 -0
  3. package/global-config/agents/verifier.md +6 -2
  4. package/lib/affected-tests.mjs +28 -7
  5. package/lib/capability-fabric.mjs +107 -48
  6. package/lib/code-intelligence/index.mjs +50 -24
  7. package/lib/code-intelligence/lsp-pool.mjs +831 -0
  8. package/lib/code-intelligence/lsp-provider.mjs +197 -20
  9. package/lib/compaction-resume-guard.mjs +76 -4
  10. package/lib/completion-auditor.mjs +20 -0
  11. package/lib/context-manifest.mjs +88 -36
  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/execution-contract.mjs +2 -2
  16. package/lib/hierarchical-context.mjs +31 -3
  17. package/lib/learning-engine.mjs +164 -65
  18. package/lib/memory-engine.mjs +177 -95
  19. package/lib/model-config.mjs +86 -7
  20. package/lib/performance-fabric.mjs +92 -0
  21. package/lib/permission-policy.mjs +232 -0
  22. package/lib/pi-rpc-pool.mjs +220 -24
  23. package/lib/provider-recovery.mjs +51 -0
  24. package/lib/repo-graph.mjs +40 -7
  25. package/lib/repo-inspect.mjs +3 -2
  26. package/lib/runtime-events.mjs +31 -10
  27. package/lib/semantic-index.mjs +69 -21
  28. package/lib/skill-compiler.mjs +84 -18
  29. package/lib/task-engine.mjs +28 -9
  30. package/lib/task-policy.mjs +80 -7
  31. package/lib/trajectory.mjs +25 -1
  32. package/lib/untrusted-output.mjs +106 -0
  33. package/lib/workspace-fingerprint.mjs +17 -9
  34. package/lib/workspace-hygiene.mjs +2 -2
  35. package/lib/worktree-sandbox.mjs +14 -8
  36. package/package.json +8 -6
  37. package/pi/extensions/ues-child-runtime.ts +116 -5
  38. package/pi/extensions/ues.ts +780 -81
  39. package/scripts/benchmark-lsp-pool.mjs +128 -0
  40. package/scripts/check-release-consistency.mjs +16 -4
  41. package/scripts/check-source-integrity.mjs +118 -6
  42. package/scripts/run-test-suite.mjs +143 -0
  43. 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,
@@ -142,6 +156,12 @@ const MODEL_VISIBLE_OUTPUT_LIMIT = configuredDuration(
142
156
  16 * 1024,
143
157
  256 * 1024,
144
158
  );
159
+ const PARENT_CODE_VISIBLE_OUTPUT_LIMIT = configuredDuration(
160
+ "UES_PARENT_CODE_VISIBLE_OUTPUT_LIMIT",
161
+ 16 * 1024,
162
+ 8 * 1024,
163
+ 64 * 1024,
164
+ );
145
165
 
146
166
  function configuredCount(name: string, fallback: number, min: number, max: number) {
147
167
  const parsed = Number(process.env[name] || "");
@@ -161,6 +181,22 @@ const CONTEXT_CACHE_MAX = configuredCount(
161
181
  4,
162
182
  128,
163
183
  );
184
+ const PROVIDER_RECOVERY_RETRIES = (() => {
185
+ const parsed = Number(process.env.UES_PROVIDER_RECOVERY_RETRIES ?? "1");
186
+ if (!Number.isFinite(parsed)) return 1;
187
+ return Math.max(0, Math.min(2, Math.trunc(parsed)));
188
+ })();
189
+ const PROVIDER_SESSION_RESUME_RETRIES = (() => {
190
+ const parsed = Number(process.env.UES_PROVIDER_SESSION_RESUME_RETRIES ?? "1");
191
+ if (!Number.isFinite(parsed)) return 1;
192
+ return Math.max(0, Math.min(2, Math.trunc(parsed)));
193
+ })();
194
+ const PROVIDER_RECOVERY_BASE_DELAY_MS = configuredDuration(
195
+ "UES_PROVIDER_RECOVERY_BASE_DELAY_MS",
196
+ 250,
197
+ 50,
198
+ 5_000,
199
+ );
164
200
 
165
201
  function configuredBoolean(name: string, fallback = true) {
166
202
  const raw = String(process.env[name] ?? "").trim().toLowerCase();
@@ -186,6 +222,9 @@ const MCP_HEALTH = new McpHealthTracker({
186
222
  failureThreshold: 2,
187
223
  cooldownMs: configuredDuration("UES_MCP_HEALTH_COOLDOWN_MS", 15_000, 1_000, 5 * 60_000),
188
224
  });
225
+ const PERMISSION_POLICY = new PermissionPolicyStore(
226
+ path.join(getUesConfigDir(), ".ues", "permissions.json"),
227
+ );
189
228
 
190
229
  function abortActiveCliChildren() {
191
230
  let aborted = 0;
@@ -275,6 +314,9 @@ type RunResult = {
275
314
  browserTools?: string[];
276
315
  childRuntime?: "rpc" | "cli";
277
316
  workerReused?: boolean;
317
+ providerFailure?: string;
318
+ providerRecoveryAttempts?: number;
319
+ providerSessionResumeAttempts?: number;
278
320
  subagentArtifact?: any;
279
321
  optimizations?: any;
280
322
  };
@@ -663,6 +705,7 @@ async function runAgentCli(
663
705
  idleTimeoutMs?: number;
664
706
  postToolErrorIdleTimeoutMs?: number;
665
707
  allowLocalEnvWrite?: boolean;
708
+ reuseRpcSession?: boolean;
666
709
  } = {},
667
710
  ): Promise<RunResult> {
668
711
  const config = AGENTS[agent];
@@ -723,6 +766,7 @@ async function runAgentCli(
723
766
  UES_CHILD_TOOL_OUTPUT_LIMIT: String(runtimeOptions.toolOutputLimit || 24 * 1024),
724
767
  UES_CHILD_VERIFICATION_TIMEOUT_SEC: String(runtimeOptions.verificationTimeoutSec || 300),
725
768
  UES_CHILD_ALLOW_LOCAL_ENV_WRITE: runtimeOptions.allowLocalEnvWrite ? "1" : "0",
769
+ UES_CHILD_EXTERNAL_TOOL_NAMES: extraTools.join(","),
726
770
  },
727
771
  shell: false,
728
772
  detached: process.platform !== "win32",
@@ -1011,11 +1055,17 @@ async function runAgentCli(
1011
1055
  }
1012
1056
 
1013
1057
 
1058
+ const RPC_PROMPT_PATH_CACHE = new Map<AgentName, string>();
1059
+
1014
1060
  function rpcPromptPath(agent: AgentName) {
1061
+ const cached = RPC_PROMPT_PATH_CACHE.get(agent);
1062
+ if (cached && fs.existsSync(cached)) return cached;
1063
+
1015
1064
  const dir = path.join(os.tmpdir(), "ues-pi-rpc-prompts");
1016
1065
  fs.mkdirSync(dir, { recursive: true });
1017
1066
  const file = path.join(dir, agent + ".md");
1018
1067
  fs.writeFileSync(file, getAgentPrompt(agent), { encoding: "utf8", mode: 0o600 });
1068
+ RPC_PROMPT_PATH_CACHE.set(agent, file);
1019
1069
  return file;
1020
1070
  }
1021
1071
 
@@ -1048,6 +1098,7 @@ async function runAgentRpc(
1048
1098
  idleTimeoutMs?: number;
1049
1099
  postToolErrorIdleTimeoutMs?: number;
1050
1100
  allowLocalEnvWrite?: boolean;
1101
+ reuseRpcSession?: boolean;
1051
1102
  } = {},
1052
1103
  ): Promise<RunResult> {
1053
1104
  const config = AGENTS[agent];
@@ -1128,6 +1179,7 @@ async function runAgentRpc(
1128
1179
  UES_CHILD_TOOL_OUTPUT_LIMIT: String(runtimeOptions.toolOutputLimit || 24 * 1024),
1129
1180
  UES_CHILD_VERIFICATION_TIMEOUT_SEC: String(runtimeOptions.verificationTimeoutSec || 300),
1130
1181
  UES_CHILD_ALLOW_LOCAL_ENV_WRITE: runtimeOptions.allowLocalEnvWrite ? "1" : "0",
1182
+ UES_CHILD_EXTERNAL_TOOL_NAMES: extraTools.join(","),
1131
1183
  },
1132
1184
  },
1133
1185
  taskInput,
@@ -1139,6 +1191,7 @@ async function runAgentRpc(
1139
1191
  activityWindowMs,
1140
1192
  idleTimeoutMs,
1141
1193
  postToolErrorIdleTimeoutMs,
1194
+ reuseSession: runtimeOptions.reuseRpcSession === true,
1142
1195
  onEvent: (event: any) => {
1143
1196
  lastActivityAt = Date.now();
1144
1197
  if (event.type === "tool_execution_start") {
@@ -1186,13 +1239,13 @@ async function runAgentRpc(
1186
1239
  );
1187
1240
 
1188
1241
  const message = rpc.message;
1189
- const output = extractAssistantText(message) || rpc.stderr || "(no assistant output)";
1190
- return {
1242
+ const assistantText = extractAssistantText(message);
1243
+ const baseResult: RunResult = {
1191
1244
  agent,
1192
1245
  task,
1193
1246
  cwd,
1194
- exitCode: 0,
1195
- output: cap(output, 100 * 1024),
1247
+ exitCode: message?.stopReason === "error" ? 1 : 0,
1248
+ output: cap(assistantText || rpc.stderr || "(no assistant output)", 100 * 1024),
1196
1249
  stderr: cap(String(rpc.stderr || ""), 64 * 1024),
1197
1250
  model: message?.model || model,
1198
1251
  stopReason: message?.stopReason,
@@ -1204,6 +1257,16 @@ async function runAgentRpc(
1204
1257
  childRuntime: "rpc",
1205
1258
  workerReused: rpc.workerReused === true,
1206
1259
  };
1260
+ const providerDecision = classifyProviderFailure(baseResult);
1261
+ if (providerDecision.transient) {
1262
+ return {
1263
+ ...baseResult,
1264
+ exitCode: 1,
1265
+ providerFailure: providerDecision.reason || "empty-provider-response",
1266
+ errorMessage: baseResult.errorMessage || providerDecision.message || "Provider returned no usable assistant content",
1267
+ };
1268
+ }
1269
+ return baseResult;
1207
1270
  } catch (error) {
1208
1271
  const message = error instanceof Error ? error.message : String(error);
1209
1272
  if (detectedHang) {
@@ -1288,54 +1351,137 @@ async function runAgent(
1288
1351
  idleTimeoutMs?: number;
1289
1352
  postToolErrorIdleTimeoutMs?: number;
1290
1353
  allowLocalEnvWrite?: boolean;
1354
+ reuseRpcSession?: boolean;
1291
1355
  } = {},
1292
1356
  ): Promise<RunResult> {
1293
- if (CHILD_RUNTIME !== "cli") {
1357
+ const runOnce = async (): Promise<RunResult> => {
1358
+ if (CHILD_RUNTIME !== "cli") {
1359
+ try {
1360
+ return await runAgentRpc(
1361
+ agent, task, cwd, model, thinkingLevel, signal, onProgress, extraTools, runtimeOptions,
1362
+ );
1363
+ } catch (error) {
1364
+ if (CHILD_RUNTIME === "rpc") throw error;
1365
+ // Auto mode may fall back only when RPC failed before delegated work started.
1366
+ if ((error as any)?.uesRpcPhase && (error as any).uesRpcPhase !== "startup") throw error;
1367
+ try {
1368
+ onProgress?.({
1369
+ agent, elapsedMs: 0, idleMs: 0, toolCalls: 0, model, phase: "running",
1370
+ note: "RPC startup unavailable; falling back to isolated CLI child",
1371
+ });
1372
+ } catch {}
1373
+ }
1374
+ }
1375
+ return runAgentCli(
1376
+ agent, task, cwd, model, thinkingLevel, signal, onProgress, extraTools, runtimeOptions,
1377
+ );
1378
+ };
1379
+
1380
+ let result = await runOnce();
1381
+ let recoveryAttempts = 0;
1382
+ let sessionResumeAttempts = 0;
1383
+ let providerDecision = classifyProviderFailure(result);
1384
+
1385
+ while (
1386
+ providerDecision.transient && providerDecision.safeSessionResume &&
1387
+ result.childRuntime === "rpc" &&
1388
+ sessionResumeAttempts < PROVIDER_SESSION_RESUME_RETRIES && !signal?.aborted
1389
+ ) {
1390
+ sessionResumeAttempts += 1;
1294
1391
  try {
1295
- return await runAgentRpc(
1392
+ onProgress?.({
1393
+ agent, elapsedMs: 0, idleMs: 0, toolCalls: Number(result.toolCalls || 0),
1394
+ model: result.model || model, phase: "running",
1395
+ note: "provider returned no usable content after tool execution; resuming the same RPC session " +
1396
+ sessionResumeAttempts + "/" + PROVIDER_SESSION_RESUME_RETRIES,
1397
+ });
1398
+ } catch {}
1399
+ const delayMs = providerRecoveryBackoffMs(sessionResumeAttempts, {
1400
+ baseMs: PROVIDER_RECOVERY_BASE_DELAY_MS, maxMs: 2_000,
1401
+ });
1402
+ if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
1403
+
1404
+ const priorToolCalls = Number(result.toolCalls || 0);
1405
+ const priorToolNames = Array.isArray(result.toolNames) ? result.toolNames : [];
1406
+ try {
1407
+ const resumed = await runAgentRpc(
1296
1408
  agent,
1297
- task,
1409
+ [
1410
+ "Continue the current delegated task from this existing RPC session after a transient provider failure.",
1411
+ "Do not restart the task and do not repeat tool calls that already completed.",
1412
+ "Use the current conversation, workspace state, and tool evidence as authoritative.",
1413
+ "Continue from the next unfinished step, then return the normal role report/verdict.",
1414
+ ].join(" "),
1298
1415
  cwd,
1299
- model,
1416
+ result.model || model,
1300
1417
  thinkingLevel,
1301
1418
  signal,
1302
1419
  onProgress,
1303
1420
  extraTools,
1304
- runtimeOptions,
1421
+ { ...runtimeOptions, reuseRpcSession: true },
1305
1422
  );
1423
+ result = {
1424
+ ...resumed,
1425
+ task,
1426
+ toolCalls: priorToolCalls + Number(resumed.toolCalls || 0),
1427
+ toolNames: [...new Set([...priorToolNames, ...(resumed.toolNames || [])])],
1428
+ };
1429
+ providerDecision = classifyProviderFailure(result);
1306
1430
  } 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
1431
  try {
1314
1432
  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",
1433
+ agent, elapsedMs: 0, idleMs: 0, toolCalls: priorToolCalls,
1434
+ model: result.model || model, phase: "running",
1435
+ note: "same-session provider recovery unavailable; preserving prior evidence without replay",
1322
1436
  });
1323
1437
  } catch {}
1438
+ break;
1324
1439
  }
1325
1440
  }
1326
- return runAgentCli(
1327
- agent,
1328
- task,
1329
- cwd,
1330
- model,
1331
- thinkingLevel,
1332
- signal,
1333
- onProgress,
1334
- extraTools,
1335
- runtimeOptions,
1336
- );
1337
- }
1338
1441
 
1442
+ while (
1443
+ providerDecision.transient && providerDecision.safeReplay &&
1444
+ recoveryAttempts < PROVIDER_RECOVERY_RETRIES && !signal?.aborted
1445
+ ) {
1446
+ recoveryAttempts += 1;
1447
+ try {
1448
+ onProgress?.({
1449
+ agent, elapsedMs: 0, idleMs: 0, toolCalls: Number(result.toolCalls || 0),
1450
+ model: result.model || model, phase: "running",
1451
+ note: "provider returned no usable content; safe fresh-session retry " +
1452
+ recoveryAttempts + "/" + PROVIDER_RECOVERY_RETRIES,
1453
+ });
1454
+ } catch {}
1455
+ const delayMs = providerRecoveryBackoffMs(recoveryAttempts, {
1456
+ baseMs: PROVIDER_RECOVERY_BASE_DELAY_MS, maxMs: 2_000,
1457
+ });
1458
+ if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
1459
+ result = await runOnce();
1460
+ providerDecision = classifyProviderFailure(result);
1461
+ }
1462
+
1463
+ if (providerDecision.transient) {
1464
+ const recoveryNote = providerDecision.safeReplay
1465
+ ? "UES provider recovery exhausted after " + recoveryAttempts +
1466
+ " bounded fresh-session retry attempt(s); task remains failed instead of being reported as success."
1467
+ : providerDecision.safeSessionResume
1468
+ ? "UES same-session provider recovery exhausted or was unavailable after " + sessionResumeAttempts +
1469
+ " attempt(s). Completed tool side effects were preserved and were not blindly replayed."
1470
+ : "UES provider recovery stopped safely without replaying completed side effects.";
1471
+ result = {
1472
+ ...result,
1473
+ exitCode: result.exitCode === 0 ? 1 : result.exitCode,
1474
+ output: [result.output, "", "[UES provider recovery] " + recoveryNote].filter(Boolean).join("\n"),
1475
+ providerFailure: providerDecision.reason || result.providerFailure || "empty-provider-response",
1476
+ };
1477
+ }
1478
+
1479
+ return {
1480
+ ...result,
1481
+ providerRecoveryAttempts: recoveryAttempts,
1482
+ providerSessionResumeAttempts: sessionResumeAttempts,
1483
+ };
1484
+ }
1339
1485
 
1340
1486
  function roleForAgent(agent: AgentName) {
1341
1487
  return agent.replace(/^ues-/, "");
@@ -1386,6 +1532,11 @@ function parseStructuredReport(output: string) {
1386
1532
  };
1387
1533
  }
1388
1534
 
1535
+ function acceptanceEvidenceStatusPresent(run: any) {
1536
+ const value = String(run?.report?.sections?.["acceptance-criteria-proven"] || "");
1537
+ return /(?:^|\n)\s*(?:[-*]\s*)?(?:VERIFIED|INFERRED|UNKNOWN)\s*:/im.test(value);
1538
+ }
1539
+
1389
1540
  function taskRecord(task: string) {
1390
1541
  return {
1391
1542
  id: "pi-dispatch",
@@ -1565,6 +1716,8 @@ async function runRoutedAgent(
1565
1716
  modelTier: selection.tier,
1566
1717
  profile: taskPolicy.executionProfile,
1567
1718
  risk: taskPolicy.risk,
1719
+ decisionConfidence: taskPolicy.decision?.confidence ?? null,
1720
+ decisionReason: taskPolicy.decision?.reason ?? null,
1568
1721
  browserRequested,
1569
1722
  }).catch(() => {});
1570
1723
  }
@@ -1664,39 +1817,39 @@ async function runRoutedAgent(
1664
1817
  }
1665
1818
  }
1666
1819
 
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 (
1820
+ const [microSkillResult, affectedTestResult, reusableVerificationResult] = await Promise.all([
1821
+ MICRO_SKILLS_ENABLED
1822
+ ? compileSkillContext(taskPolicy, role, {
1823
+ maxSkills: taskPolicy.maxSkills,
1824
+ totalChars: taskPolicy.executionProfile === "fast" ? 1800 : 3200,
1825
+ taskText: task,
1826
+ }).catch(() => null)
1827
+ : Promise.resolve(null),
1675
1828
  AFFECTED_TEST_HINTS_ENABLED &&
1676
1829
  ["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 (
1830
+ ? resolveAffectedTests(cwd, {
1831
+ limit: 10,
1832
+ changedFiles: workspaceState.changedFiles || [],
1833
+ ...(workspaceState.cacheable === true && workspaceFingerprint !== "unknown"
1834
+ ? { workspaceFingerprint }
1835
+ : {}),
1836
+ }).catch(() => null)
1837
+ : Promise.resolve(null),
1688
1838
  ["verifier", "integration-verifier"].includes(role) &&
1689
1839
  !["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
- }
1840
+ ? listReusableVerification(cwd, {
1841
+ limit: 8,
1842
+ maxAgeMs: 30 * 60_000,
1843
+ previewBytes: 2200,
1844
+ ...(workspaceState.cacheable === true && workspaceFingerprint !== "unknown"
1845
+ ? { workspaceFingerprint }
1846
+ : {}),
1847
+ }).catch(() => null)
1848
+ : Promise.resolve(null),
1849
+ ]);
1850
+ microSkills = microSkillResult;
1851
+ affectedTests = affectedTestResult;
1852
+ reusableVerification = reusableVerificationResult;
1700
1853
 
1701
1854
  contextQuality = pack.contextQuality;
1702
1855
  contextPerformance = pack.performance || null;
@@ -2926,8 +3079,16 @@ export default function (pi: ExtensionAPI) {
2926
3079
  const AUTO_ADMISSION_ENABLED = !["0", "false", "off"].includes(
2927
3080
  String(process.env.UES_AUTO_ADMIT || "1").trim().toLowerCase(),
2928
3081
  );
2929
- const UES_PARENT_TOOL_NAMES = new Set(["ues_cli", "ues_execute", "ues_service", "ues_dispatch"]);
3082
+ const UES_PARENT_TOOL_NAMES = new Set(["ues_cli", "ues_execute", "ues_service", "ues_session", "ues_dispatch"]);
3083
+ const ALWAYS_ON_PARENT_TOOLS = new Set(["ues_code"]);
2930
3084
  let normalActiveTools: string[] | null = null;
3085
+ let parentRunToolCalls = 0;
3086
+ let parentRunLastAssistant: any = null;
3087
+ let parentProviderRecoveryConsecutive = 0;
3088
+ let parentProviderRecoveryTotal = 0;
3089
+ let parentProviderRecoveryPending = false;
3090
+ const PARENT_PROVIDER_RECOVERY_MAX_CONSECUTIVE = 1;
3091
+ const PARENT_PROVIDER_RECOVERY_MAX_TOTAL = 3;
2931
3092
 
2932
3093
  const currentNonUesTools = () =>
2933
3094
  pi.getActiveTools().filter((name) => !UES_PARENT_TOOL_NAMES.has(String(name)));
@@ -2986,7 +3147,7 @@ export default function (pi: ExtensionAPI) {
2986
3147
  content,
2987
3148
  display: false,
2988
3149
  details: {
2989
- schemaVersion: 1,
3150
+ schemaVersion: packet.schemaVersion || 1,
2990
3151
  reason: event.reason,
2991
3152
  willRetry: event.willRetry,
2992
3153
  workspaceCount: packet.workspaceCount,
@@ -3008,6 +3169,7 @@ export default function (pi: ExtensionAPI) {
3008
3169
  MCP_HEALTH.clear();
3009
3170
  abortActiveCliChildren();
3010
3171
  await stopAllServices().catch(() => []);
3172
+ await shutdownLspPool().catch(() => ({ stopped: 0, remaining: 0 }));
3011
3173
  await RPC_POOL.stopAll().catch(() => {});
3012
3174
  for (const dir of [...ACTIVE_TASK_SANDBOXES.keys()]) {
3013
3175
  const ownerRoot = await taskSandboxOwnerRoot(dir).catch(() => null);
@@ -3113,13 +3275,27 @@ export default function (pi: ExtensionAPI) {
3113
3275
  return { action: "continue" };
3114
3276
  }
3115
3277
 
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 {}
3278
+ const streamingBehavior = String(event.streamingBehavior || "");
3279
+ const forwarded = streamingBehavior === "followUp"
3280
+ ? await RPC_POOL.followUpActive(text).catch(() => ({
3281
+ accepted: false,
3282
+ reason: "follow-up-failed",
3283
+ active: 0,
3284
+ }))
3285
+ : await RPC_POOL.steerActive(text).catch(() => ({
3286
+ accepted: false,
3287
+ reason: "steer-failed",
3288
+ active: 0,
3289
+ }));
3290
+ if (forwarded.accepted) {
3291
+ try {
3292
+ ctx.ui.notify(
3293
+ streamingBehavior === "followUp"
3294
+ ? "UES: follow-up queued for the active child"
3295
+ : "UES: steering message forwarded to the active child",
3296
+ "info",
3297
+ );
3298
+ } catch {}
3123
3299
  return { action: "handled" };
3124
3300
  }
3125
3301
 
@@ -3129,19 +3305,109 @@ export default function (pi: ExtensionAPI) {
3129
3305
  });
3130
3306
 
3131
3307
  pi.on("before_agent_start", async () => {
3308
+ // Pi may start another provider request inside the same parent run when
3309
+ // agent_before_settle asks to continue. Preserve recovery accounting for
3310
+ // that continuation, but reset it for a genuinely new parent run.
3311
+ if (parentProviderRecoveryPending) {
3312
+ parentProviderRecoveryPending = false;
3313
+ parentRunLastAssistant = null;
3314
+ } else {
3315
+ parentRunToolCalls = 0;
3316
+ parentRunLastAssistant = null;
3317
+ parentProviderRecoveryConsecutive = 0;
3318
+ parentProviderRecoveryTotal = 0;
3319
+ }
3132
3320
  if (uesModeActive()) activateParentUesTools();
3133
3321
  else deactivateParentUesTools();
3134
3322
  });
3135
3323
 
3324
+ pi.on("message_end", async (event) => {
3325
+ if ((event as any)?.message?.role !== "assistant") return;
3326
+ parentRunLastAssistant = (event as any).message;
3327
+
3328
+ // A substantive assistant response proves the previous empty-response
3329
+ // incident recovered. Reset only the consecutive incident budget; keep the
3330
+ // total run budget bounded so a flaky provider cannot loop forever.
3331
+ const text = extractAssistantText(parentRunLastAssistant).trim();
3332
+ if (text && parentRunLastAssistant?.stopReason !== "error") {
3333
+ parentProviderRecoveryConsecutive = 0;
3334
+ parentProviderRecoveryPending = false;
3335
+ }
3336
+ });
3337
+
3338
+ pi.on("agent_before_settle", async (event) => {
3339
+ if (
3340
+ uesModeActive() ||
3341
+ parentProviderRecoveryConsecutive >= PARENT_PROVIDER_RECOVERY_MAX_CONSECUTIVE ||
3342
+ parentProviderRecoveryTotal >= PARENT_PROVIDER_RECOVERY_MAX_TOTAL
3343
+ ) {
3344
+ return undefined;
3345
+ }
3346
+
3347
+ const assistantText = extractAssistantText(parentRunLastAssistant);
3348
+ const decision = classifyProviderFailure({
3349
+ output: assistantText || "(no assistant output)",
3350
+ errorMessage: parentRunLastAssistant?.errorMessage,
3351
+ stopReason: parentRunLastAssistant?.stopReason,
3352
+ toolCalls: parentRunToolCalls,
3353
+ noAssistantMessage: !parentRunLastAssistant,
3354
+ });
3355
+ if (!decision.transient) return undefined;
3356
+
3357
+ parentProviderRecoveryConsecutive += 1;
3358
+ parentProviderRecoveryTotal += 1;
3359
+ parentProviderRecoveryPending = true;
3360
+ const afterTools = parentRunToolCalls > 0;
3361
+ const recoveryMessage = afterTools
3362
+ ? [
3363
+ "UES parent provider recovery: the previous provider response was empty after completed tool calls.",
3364
+ "Continue from the current conversation and existing tool results.",
3365
+ "Do not restart the task and do not repeat tool calls that already completed unless fresh verification is strictly required.",
3366
+ "Resume from the next unfinished step and finish the requested work/report.",
3367
+ ].join(" ")
3368
+ : [
3369
+ "UES parent provider recovery: the previous provider response was empty before any tool side effect.",
3370
+ "Retry the current request once using the existing conversation context and return a complete response.",
3371
+ ].join(" ");
3372
+
3373
+ return {
3374
+ entries: [
3375
+ ...(event.entries || []),
3376
+ {
3377
+ type: "custom_message",
3378
+ customType: "ues-parent-provider-recovery",
3379
+ content: recoveryMessage,
3380
+ display: false,
3381
+ details: {
3382
+ schemaVersion: 2,
3383
+ incidentAttempt: parentProviderRecoveryConsecutive,
3384
+ maxIncidentAttempts: PARENT_PROVIDER_RECOVERY_MAX_CONSECUTIVE,
3385
+ totalAttempts: parentProviderRecoveryTotal,
3386
+ maxTotalAttempts: PARENT_PROVIDER_RECOVERY_MAX_TOTAL,
3387
+ reason: decision.reason,
3388
+ toolCalls: parentRunToolCalls,
3389
+ safeReplay: decision.safeReplay,
3390
+ safeSessionResume: decision.safeSessionResume,
3391
+ },
3392
+ },
3393
+ ],
3394
+ continue: true,
3395
+ };
3396
+ });
3397
+
3136
3398
  pi.on("agent_end", async () => {
3137
3399
  promptUesActive = false;
3400
+ parentProviderRecoveryPending = false;
3401
+ parentProviderRecoveryConsecutive = 0;
3402
+ parentProviderRecoveryTotal = 0;
3138
3403
  deactivateParentUesTools();
3139
3404
  });
3140
3405
 
3141
3406
  pi.on("tool_call", async (event, ctx) => {
3142
3407
  const toolName = String(event.toolName || "");
3408
+ if (!uesModeActive()) parentRunToolCalls += 1;
3143
3409
  if (!uesModeActive()) {
3144
- if (toolName.startsWith("ues_")) {
3410
+ if (toolName.startsWith("ues_") && !ALWAYS_ON_PARENT_TOOLS.has(toolName)) {
3145
3411
  return {
3146
3412
  block: true,
3147
3413
  reason: "UES parent tools are hidden outside admitted UES runs. Submit a normal engineering task or start an explicit /ues-* command.",
@@ -3149,11 +3415,48 @@ export default function (pi: ExtensionAPI) {
3149
3415
  }
3150
3416
  return undefined;
3151
3417
  }
3418
+ const permissionRequest = toolPermissionRequest(toolName, (event as any).input || {});
3419
+ const configuredPermission: any = await PERMISSION_POLICY.evaluate(permissionRequest, {
3420
+ agent: "ues-parent",
3421
+ }).catch((error) => ({
3422
+ configured: true,
3423
+ decision: null,
3424
+ error: error instanceof Error ? error.message : String(error),
3425
+ }));
3426
+ if (configuredPermission.error) {
3427
+ return {
3428
+ block: true,
3429
+ reason: "UES permission policy is invalid: " + configuredPermission.error,
3430
+ };
3431
+ }
3432
+ const permissionEffect = configuredPermission?.decision?.effect;
3433
+ let permissionApproved = false;
3434
+ if (permissionEffect === "deny") {
3435
+ return {
3436
+ block: true,
3437
+ reason: permissionRecoveryHint(permissionRequest, configuredPermission.decision, { effect: "deny" }),
3438
+ };
3439
+ }
3440
+ if (permissionEffect === "ask") {
3441
+ const allowed = await approveRisk(
3442
+ ctx,
3443
+ permissionRequest.resources.join("\n"),
3444
+ "permission:" + permissionRequest.action,
3445
+ );
3446
+ if (!allowed) {
3447
+ return {
3448
+ block: true,
3449
+ reason: permissionRecoveryHint(permissionRequest, configuredPermission.decision, { effect: "ask" }),
3450
+ };
3451
+ }
3452
+ permissionApproved = true;
3453
+ }
3454
+
3152
3455
  if (toolName !== "bash" && toolName !== "powershell") {
3153
3456
  const allTools = typeof (pi as any).getAllTools === "function" ? (pi as any).getAllTools() : [];
3154
3457
  const descriptor = allTools.find((tool: any) => String(tool?.name || "") === toolName);
3155
3458
  const policy = mcpExecutionPolicy(descriptor || { name: toolName });
3156
- if (policy.confirmationRequired) {
3459
+ if (policy.confirmationRequired && !permissionApproved) {
3157
3460
  const allowed = await approveRisk(ctx, `MCP/tool call: ${toolName}`, `mcp-destructive:${toolName}`);
3158
3461
  if (!allowed) return { block: true, reason: `Blocked by UES MCP destructive-hint gate: ${toolName}` };
3159
3462
  }
@@ -3172,8 +3475,10 @@ export default function (pi: ExtensionAPI) {
3172
3475
  }
3173
3476
  const risk = destructiveShellRisk(command);
3174
3477
  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}` };
3478
+ if (!permissionApproved) {
3479
+ const allowed = await approveRisk(ctx, command, risk.id || "destructive");
3480
+ if (!allowed) return { block: true, reason: `Blocked by UES safety gate: ${risk.id}` };
3481
+ }
3177
3482
  return undefined;
3178
3483
  });
3179
3484
 
@@ -3181,14 +3486,301 @@ export default function (pi: ExtensionAPI) {
3181
3486
  if (!uesModeActive()) return undefined;
3182
3487
  const toolName = String((event as any).toolName || "");
3183
3488
  if (!toolName || toolName === "bash" || toolName === "powershell") return undefined;
3489
+ const resultText = toolResultText(event);
3490
+ const allTools = typeof (pi as any).getAllTools === "function" ? (pi as any).getAllTools() : [];
3491
+ const descriptor = allTools.find((tool: any) => String(tool?.name || "") === toolName);
3492
+ const policy = mcpExecutionPolicy(descriptor || { name: toolName });
3184
3493
  MCP_HEALTH.finish(
3185
3494
  String((event as any).toolCallId || ""),
3186
3495
  {
3187
3496
  isError: (event as any).isError === true,
3188
- text: toolResultText(event),
3497
+ text: resultText,
3189
3498
  },
3190
3499
  );
3191
- return undefined;
3500
+
3501
+ const externalBoundary =
3502
+ policy.externalEvidenceBoundary === true ||
3503
+ HOST_BROWSER_TOOL_NAMES.includes(toolName);
3504
+ if (!externalBoundary || !resultText) return undefined;
3505
+
3506
+ const analysis = analyzeUntrustedOutput(resultText, { source: toolName });
3507
+ if (!analysis.flagged) return undefined;
3508
+ const originalContent = Array.isArray((event as any).content)
3509
+ ? (event as any).content
3510
+ : [{ type: "text", text: resultText }];
3511
+ return {
3512
+ content: [
3513
+ { type: "text", text: renderUntrustedOutputWarning(analysis, { source: toolName }) },
3514
+ ...originalContent,
3515
+ ],
3516
+ details: {
3517
+ ...((event as any).details && typeof (event as any).details === "object" ? (event as any).details : {}),
3518
+ uesUntrustedOutputBoundary: analysis,
3519
+ },
3520
+ isError: (event as any).isError === true,
3521
+ usage: (event as any).usage,
3522
+ };
3523
+ });
3524
+
3525
+ // Always-on, read-only intelligence for normal Pi conversations.
3526
+ // Keep this outside UES_PARENT_TOOL_NAMES so session_start does not hide it.
3527
+ pi.registerTool({
3528
+ name: "ues_code",
3529
+ label: "UES Code Intelligence Lite",
3530
+ description:
3531
+ "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.",
3532
+ parameters: Type.Object({
3533
+ action: Type.Union([
3534
+ Type.Literal("status"),
3535
+ Type.Literal("search"),
3536
+ Type.Literal("read"),
3537
+ Type.Literal("diagnostics"),
3538
+ Type.Literal("definition"),
3539
+ Type.Literal("references"),
3540
+ Type.Literal("symbols"),
3541
+ Type.Literal("hover"),
3542
+ Type.Literal("rename-preview"),
3543
+ Type.Literal("incoming-calls"),
3544
+ Type.Literal("outgoing-calls"),
3545
+ Type.Literal("document"),
3546
+ Type.Literal("context-expand"),
3547
+ Type.Literal("context-search"),
3548
+ ]),
3549
+ file: Type.Optional(Type.String()),
3550
+ query: Type.Optional(Type.String()),
3551
+ structuralPattern: Type.Optional(Type.String()),
3552
+ language: Type.Optional(Type.String()),
3553
+ startLine: Type.Optional(Type.Number({ minimum: 1 })),
3554
+ endLine: Type.Optional(Type.Number({ minimum: 1 })),
3555
+ line: Type.Optional(Type.Number({
3556
+ minimum: 1,
3557
+ description: "1-based source line. UES converts it to the LSP protocol's 0-based line internally.",
3558
+ })),
3559
+ character: Type.Optional(Type.Number({
3560
+ minimum: 1,
3561
+ description: "1-based source character/column. UES converts it to the LSP protocol's 0-based character internally.",
3562
+ })),
3563
+ newName: Type.Optional(Type.String({ minLength: 1, maxLength: 512 })),
3564
+ includeDeclaration: Type.Optional(Type.Boolean()),
3565
+ includeSessions: Type.Optional(Type.Boolean()),
3566
+ ref: Type.Optional(Type.String()),
3567
+ maxBytes: Type.Optional(Type.Number({ minimum: 1, maximum: 128000 })),
3568
+ }),
3569
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
3570
+ try {
3571
+ let result: any;
3572
+ if (params.action === "status") {
3573
+ result = probeCodeIntelligence(params.file || "");
3574
+ result = {
3575
+ ...result,
3576
+ mode: "parent-lite",
3577
+ controllerStarted: false,
3578
+ childSpawned: false,
3579
+ lsp: {
3580
+ ...result.lsp,
3581
+ persistentPool: {
3582
+ ...lspPoolStatus({ includeSessions: params.includeSessions === true, persistent: true }),
3583
+ policy: { enabled: true, source: "parent-lite" },
3584
+ },
3585
+ },
3586
+ };
3587
+ } else if (params.action === "search") {
3588
+ if (!params.query) throw new Error("ues_code search requires query");
3589
+ result = await searchCodeIntelligence(ctx.cwd, params.query, {
3590
+ structuralPattern: params.structuralPattern,
3591
+ language: params.language,
3592
+ file: params.file,
3593
+ maxResults: 12,
3594
+ });
3595
+ result.mode = "parent-lite";
3596
+ } else if (params.action === "read") {
3597
+ if (!params.file) throw new Error("ues_code read requires file");
3598
+ const startLine = Math.max(1, Math.trunc(Number(params.startLine || 1)));
3599
+ const endLine = Math.max(startLine, Math.min(startLine + 399, Math.trunc(Number(params.endLine || startLine + 199))));
3600
+ const read = await readAnchoredCode(ctx.cwd, params.file, { startLine, endLine });
3601
+ const rawRead = [
3602
+ `file: ${read.file}; lines: ${read.startLine}-${read.endLine}/${read.lineCount}; sourceHash: ${read.sourceHash}`,
3603
+ "",
3604
+ read.text,
3605
+ ].join("\n");
3606
+ const originalChars = rawRead.length;
3607
+ const bounded = originalChars > PARENT_CODE_VISIBLE_OUTPUT_LIMIT;
3608
+ const exactRead = [
3609
+ `file: ${read.file}; lines: ${read.startLine}-${read.endLine}/${read.lineCount}; sourceHash: ${read.sourceHash}; originalChars: ${originalChars}; bounded: ${bounded}`,
3610
+ "",
3611
+ read.text,
3612
+ ].join("\n");
3613
+ let contextRef: string | null = null;
3614
+ let visibleRead = exactRead;
3615
+ if (bounded) {
3616
+ const preserved = await compactContext(ctx.cwd, exactRead, {
3617
+ kind: "ues-code-read",
3618
+ source: `ues_code:read:${read.file}`,
3619
+ summary: `Exact anchored parent-lite read for ${read.file}`,
3620
+ }).catch(() => null);
3621
+ contextRef = preserved?.ref || null;
3622
+ const previewChars = Math.max(2_000, PARENT_CODE_VISIBLE_OUTPUT_LIMIT - 1_200);
3623
+ visibleRead = exactRead.slice(0, previewChars) +
3624
+ `\n...[anchored read bounded; exact content preserved${contextRef ? `; contextRef=${contextRef}` : ""}]`;
3625
+ }
3626
+ return {
3627
+ content: [{ type: "text", text: visibleRead }],
3628
+ details: {
3629
+ action: params.action,
3630
+ mode: "parent-lite",
3631
+ controllerStarted: false,
3632
+ childSpawned: false,
3633
+ file: read.file,
3634
+ sourceHash: read.sourceHash,
3635
+ startLine: read.startLine,
3636
+ endLine: read.endLine,
3637
+ bounded,
3638
+ originalChars,
3639
+ contextRef,
3640
+ },
3641
+ };
3642
+ } else if (params.action === "diagnostics") {
3643
+ if (!params.file) throw new Error("ues_code diagnostics requires file");
3644
+ result = await diagnoseCode(ctx.cwd, params.file, {
3645
+ timeoutMs: 5000,
3646
+ maxResults: 80,
3647
+ persistent: true,
3648
+ });
3649
+ } else if (["definition", "references", "symbols", "hover", "rename-preview", "incoming-calls", "outgoing-calls"].includes(params.action)) {
3650
+ if (!params.file) throw new Error(`ues_code ${params.action} requires file`);
3651
+ if (params.action === "rename-preview" && !params.newName) throw new Error("ues_code rename-preview requires newName");
3652
+ result = await lspOperation(ctx.cwd, params.file, params.action, {
3653
+ line: params.line || 1,
3654
+ character: params.character || 1,
3655
+ newName: params.newName,
3656
+ includeDeclaration: params.includeDeclaration,
3657
+ timeoutMs: 7000,
3658
+ maxResults: 120,
3659
+ persistent: true,
3660
+ });
3661
+ } else if (params.action === "document") {
3662
+ if (!params.file) throw new Error("ues_code document requires file");
3663
+ const document = await ingestDocument(ctx.cwd, params.file, {
3664
+ maxBytes: Math.min(Number(params.maxBytes || 4 * 1024 * 1024), 4 * 1024 * 1024),
3665
+ });
3666
+ const block = await compactContext(ctx.cwd, document.markdown, {
3667
+ kind: "document-ingestion",
3668
+ source: document.file,
3669
+ summary: `Normalized document from ${document.provider}`,
3670
+ });
3671
+ result = {
3672
+ provider: document.provider,
3673
+ file: document.file,
3674
+ bytes: document.bytes,
3675
+ optionalDependency: document.optionalDependency,
3676
+ contextRef: block.ref,
3677
+ originalChars: block.originalChars,
3678
+ summary: block.levels.T1,
3679
+ };
3680
+ } else if (params.action === "context-expand") {
3681
+ if (!params.ref) throw new Error("ues_code context-expand requires ref");
3682
+ const expanded = await expandContext(ctx.cwd, params.ref, { maxBytes: params.maxBytes || 16000 });
3683
+ return {
3684
+ content: [{ type: "text", text: expanded.content }],
3685
+ details: {
3686
+ action: params.action,
3687
+ mode: "parent-lite",
3688
+ controllerStarted: false,
3689
+ childSpawned: false,
3690
+ ref: expanded.ref,
3691
+ start: expanded.start,
3692
+ returnedBytes: expanded.returnedBytes,
3693
+ truncated: expanded.truncated,
3694
+ },
3695
+ };
3696
+ } else if (params.action === "context-search") {
3697
+ if (!params.ref || !params.query) throw new Error("ues_code context-search requires ref and query");
3698
+ result = await searchContext(ctx.cwd, params.ref, params.query, {
3699
+ maxBytes: params.maxBytes || 512000,
3700
+ maxMatches: 12,
3701
+ });
3702
+ } else {
3703
+ throw new Error("unsupported ues_code action");
3704
+ }
3705
+
3706
+ const payload = {
3707
+ ...result,
3708
+ mode: result?.mode || "parent-lite",
3709
+ controllerStarted: false,
3710
+ childSpawned: false,
3711
+ };
3712
+ const encoded = JSON.stringify(payload, null, 2);
3713
+ const bounded = encoded.length > PARENT_CODE_VISIBLE_OUTPUT_LIMIT;
3714
+ let contextRef: string | null = null;
3715
+ let visible = encoded;
3716
+ if (bounded) {
3717
+ const preserved = await compactContext(ctx.cwd, encoded, {
3718
+ kind: "ues-code-result",
3719
+ source: `ues_code:${params.action}`,
3720
+ summary: `Full parent-lite result for ${params.action}; preserve exact JSON before model-visible bounding`,
3721
+ }).catch(() => null);
3722
+ contextRef = preserved?.ref || null;
3723
+ const pool = payload?.pool || payload?.lsp?.persistentPool || null;
3724
+ const poolSummary = pool && typeof pool === "object"
3725
+ ? {
3726
+ enabled: pool.enabled ?? null,
3727
+ active: pool.active ?? null,
3728
+ busy: pool.busy ?? null,
3729
+ limits: pool.limits || null,
3730
+ metrics: pool.metrics || null,
3731
+ sessionCount: Array.isArray(pool.sessions) ? pool.sessions.length : null,
3732
+ }
3733
+ : null;
3734
+ const previewChars = Math.max(2_000, PARENT_CODE_VISIBLE_OUTPUT_LIMIT - 6_000);
3735
+ const metadataFirst = {
3736
+ schemaVersion: payload?.schemaVersion || 1,
3737
+ action: params.action,
3738
+ file: payload?.file || null,
3739
+ available: payload?.available ?? null,
3740
+ provider: payload?.provider || null,
3741
+ operation: payload?.operation || null,
3742
+ reason: payload?.reason || null,
3743
+ persistent: payload?.persistent ?? null,
3744
+ pool: poolSummary,
3745
+ mode: "parent-lite",
3746
+ controllerStarted: false,
3747
+ childSpawned: false,
3748
+ bounded: true,
3749
+ originalChars: encoded.length,
3750
+ contextRef,
3751
+ preview: encoded.slice(0, previewChars),
3752
+ };
3753
+ visible = JSON.stringify(metadataFirst, null, 2) +
3754
+ "\n...[full result preserved; use ues_code context-expand with contextRef when more evidence is needed]";
3755
+ }
3756
+ return {
3757
+ content: [{ type: "text", text: visible }],
3758
+ details: {
3759
+ action: params.action,
3760
+ mode: "parent-lite",
3761
+ controllerStarted: false,
3762
+ childSpawned: false,
3763
+ bounded,
3764
+ originalChars: encoded.length,
3765
+ contextRef,
3766
+ provider: payload?.provider || null,
3767
+ persistent: payload?.persistent ?? null,
3768
+ pool: payload?.pool || payload?.lsp?.persistentPool || null,
3769
+ },
3770
+ };
3771
+ } catch (error) {
3772
+ return {
3773
+ content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
3774
+ details: {
3775
+ action: params.action,
3776
+ mode: "parent-lite",
3777
+ controllerStarted: false,
3778
+ childSpawned: false,
3779
+ },
3780
+ isError: true,
3781
+ };
3782
+ }
3783
+ },
3192
3784
  });
3193
3785
 
3194
3786
  pi.registerTool({
@@ -3344,6 +3936,60 @@ export default function (pi: ExtensionAPI) {
3344
3936
  },
3345
3937
  });
3346
3938
 
3939
+ pi.registerTool({
3940
+ name: "ues_session",
3941
+ label: "UES Session Control",
3942
+ description:
3943
+ "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.",
3944
+ parameters: Type.Object({
3945
+ action: Type.Union([
3946
+ Type.Literal("status"),
3947
+ Type.Literal("get-state"),
3948
+ Type.Literal("steer"),
3949
+ Type.Literal("follow-up"),
3950
+ Type.Literal("abort"),
3951
+ Type.Literal("clear-queue"),
3952
+ Type.Literal("set-model"),
3953
+ Type.Literal("set-thinking"),
3954
+ Type.Literal("compact"),
3955
+ Type.Literal("wait"),
3956
+ ]),
3957
+ message: Type.Optional(Type.String({ maxLength: 12000 })),
3958
+ provider: Type.Optional(Type.String({ maxLength: 120 })),
3959
+ modelId: Type.Optional(Type.String({ maxLength: 240 })),
3960
+ thinkingLevel: Type.Optional(Type.String({ maxLength: 40 })),
3961
+ compactInstructions: Type.Optional(Type.String({ maxLength: 12000 })),
3962
+ timeoutMs: Type.Optional(Type.Number({ minimum: 500, maximum: 1800000 })),
3963
+ }),
3964
+ async execute(_toolCallId, params) {
3965
+ try {
3966
+ const action = String(params.action || "status");
3967
+ const result = action === "status"
3968
+ ? { ok: true, action, ...RPC_POOL.status() }
3969
+ : await RPC_POOL.controlActive(action, {
3970
+ message: params.message,
3971
+ provider: params.provider,
3972
+ modelId: params.modelId,
3973
+ level: params.thinkingLevel,
3974
+ customInstructions: params.compactInstructions,
3975
+ timeoutMs: params.timeoutMs,
3976
+ });
3977
+ return {
3978
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
3979
+ details: result,
3980
+ isError: result?.ok === false,
3981
+ };
3982
+ } catch (error) {
3983
+ const message = error instanceof Error ? error.message : String(error);
3984
+ return {
3985
+ content: [{ type: "text", text: message }],
3986
+ details: { action: params.action, error: message },
3987
+ isError: true,
3988
+ };
3989
+ }
3990
+ },
3991
+ });
3992
+
3347
3993
  const uesExecuteTool: any = {
3348
3994
  name: "ues_execute",
3349
3995
  label: "UES Execute",
@@ -3396,6 +4042,8 @@ export default function (pi: ExtensionAPI) {
3396
4042
  profile: policy.executionProfile,
3397
4043
  risk: policy.risk,
3398
4044
  mode: policy.mode,
4045
+ decisionConfidence: policy.decision?.confidence ?? null,
4046
+ decisionReason: policy.decision?.reason ?? null,
3399
4047
  inheritedDirtyCount: executionContract.inheritedDirty?.paths?.length || 0,
3400
4048
  explicitPhaseCount: executionContract.phases?.length || 0,
3401
4049
  localEnvWriteExplicitlyAllowed: executionContract.localEnvWriteExplicitlyAllowed === true,
@@ -3583,8 +4231,8 @@ export default function (pi: ExtensionAPI) {
3583
4231
  "",
3584
4232
  "## Acceptance criteria proven",
3585
4233
  deterministicPass
3586
- ? "All explicitly requested whitelisted Git inspection commands completed successfully."
3587
- : "The requested deterministic inspection did not fully pass.",
4234
+ ? "VERIFIED: All explicitly requested whitelisted Git inspection commands completed successfully."
4235
+ : "UNKNOWN: The requested deterministic inspection did not fully pass.",
3588
4236
  "",
3589
4237
  "## Failures",
3590
4238
  failureText,
@@ -3644,6 +4292,27 @@ export default function (pi: ExtensionAPI) {
3644
4292
  1,
3645
4293
  );
3646
4294
  if (isAbortedRun(inspection)) return abortedResponse(inspection, "read-only-inspection");
4295
+ if (
4296
+ inspection.exitCode === 0 &&
4297
+ inspection.verdict === "PASS" &&
4298
+ !acceptanceEvidenceStatusPresent(inspection)
4299
+ ) {
4300
+ inspection = await run(
4301
+ "ues-verifier",
4302
+ [
4303
+ "READ-ONLY VERIFICATION FORMAT RECOVERY. The previous verifier returned PASS but omitted mandatory evidence-status prefixes.",
4304
+ "Do not edit files. Re-check only what is needed to support the existing verdict.",
4305
+ "In ## Acceptance criteria proven, prefix every requested criterion with exactly VERIFIED:, INFERRED:, or UNKNOWN:.",
4306
+ "INFERRED or UNKNOWN criteria must also be listed under ## Unresolved gaps and cannot support PASS.",
4307
+ "",
4308
+ "Inspection request:",
4309
+ params.task,
4310
+ ].join("\n"),
4311
+ 1,
4312
+ "Previous verifier PASS omitted evidence-status markers.",
4313
+ );
4314
+ if (isAbortedRun(inspection)) return abortedResponse(inspection, "read-only-verification-format-recovery");
4315
+ }
3647
4316
  }
3648
4317
 
3649
4318
  const after = runtimeWorkspaceSnapshot(cwd);
@@ -3658,6 +4327,7 @@ export default function (pi: ExtensionAPI) {
3658
4327
  workspaceSnapshot: after,
3659
4328
  behavioralReceipts: [],
3660
4329
  requireBehavioralReceipt: false,
4330
+ requireClaimEvidenceStatus: true,
3661
4331
  });
3662
4332
  if (!unchanged) {
3663
4333
  completionAudit.passed = false;
@@ -4422,7 +5092,7 @@ export default function (pi: ExtensionAPI) {
4422
5092
  agent: "ues-deterministic-verifier", task: params.task, cwd, exitCode: 0,
4423
5093
  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
5094
  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." } },
5095
+ 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
5096
  optimizations: { fastDeterministicVerification: true, behavioralReceiptCount: fastGate.behavioralReceipts.length },
4427
5097
  };
4428
5098
  steps.push(verification);
@@ -4440,6 +5110,30 @@ export default function (pi: ExtensionAPI) {
4440
5110
  ].join("\n"), attempt, recentFailure || undefined,
4441
5111
  );
4442
5112
  if (isAbortedRun(verification)) return abortedResponse(verification, "verification");
5113
+ if (
5114
+ verification.exitCode === 0 &&
5115
+ verification.verdict === "PASS" &&
5116
+ !acceptanceEvidenceStatusPresent(verification)
5117
+ ) {
5118
+ verification = await run(
5119
+ "ues-verifier",
5120
+ [
5121
+ "VERIFICATION FORMAT RECOVERY. The previous verifier returned PASS but omitted mandatory evidence-status prefixes.",
5122
+ "Do not edit files and do not re-run broad checks. Re-check only what is necessary to support the verdict.",
5123
+ "In ## Acceptance criteria proven, prefix every requested criterion with exactly VERIFIED:, INFERRED:, or UNKNOWN:.",
5124
+ "INFERRED or UNKNOWN criteria must also appear under ## Unresolved gaps and cannot support PASS.",
5125
+ "",
5126
+ "Original task:",
5127
+ params.task,
5128
+ "",
5129
+ "Previous verifier output (not proof by itself):",
5130
+ cap(verification.output, 5000),
5131
+ ].join("\n"),
5132
+ attempt,
5133
+ "Previous verifier PASS omitted evidence-status markers.",
5134
+ );
5135
+ if (isAbortedRun(verification)) return abortedResponse(verification, "verification-format-recovery");
5136
+ }
4443
5137
  }
4444
5138
  const verified = verification.exitCode === 0 && verification.verdict === "PASS";
4445
5139
  if (!verified) {
@@ -4526,6 +5220,7 @@ export default function (pi: ExtensionAPI) {
4526
5220
  workspaceSnapshot: completionSnapshot,
4527
5221
  behavioralReceipts: freshReceipts,
4528
5222
  requireBehavioralReceipt: policy.requireBehavioralReceipt !== false,
5223
+ requireClaimEvidenceStatus: true,
4529
5224
  });
4530
5225
  if (!completionAudit.passed) {
4531
5226
  recentFailure = "Completion auditor rejected PASS: " + completionAudit.failures.join(", ");
@@ -4666,6 +5361,10 @@ export default function (pi: ExtensionAPI) {
4666
5361
  "Micro skills: " + (MICRO_SKILLS_ENABLED ? "on" : "off"),
4667
5362
  "Turbo Fast Path: on",
4668
5363
  "Parent UES tools hidden outside UES runs: on",
5364
+ "Parent Code Intelligence Lite: on (always-on read-only ues_code; no controller/child)",
5365
+ "Native Pi RPC session control: on (state/steer/follow-up/abort/model/thinking/compact/wait)",
5366
+ "Permission deny-and-continue recovery: on",
5367
+ "Provider empty-response recovery: on (per-incident parent retry + bounded total + child RPC recovery; no blind replay after tools)",
4669
5368
  "Zero-friction engineering admission: " + (AUTO_ADMISSION_ENABLED ? "on (native / auto / high-risk + safe continuation)" : "off"),
4670
5369
  "Git-root artifact guard: on",
4671
5370
  "Inherited dirty-work guard: on",