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.
- package/README.md +3 -3
- package/docs/V15-MANAGED-RUNTIME.md +18 -0
- package/global-config/agents/verifier.md +6 -2
- package/lib/affected-tests.mjs +21 -4
- package/lib/capability-fabric.mjs +107 -48
- package/lib/code-intelligence/index.mjs +3 -15
- package/lib/code-intelligence/lsp-pool.mjs +694 -0
- package/lib/code-intelligence/lsp-provider.mjs +160 -20
- package/lib/compaction-resume-guard.mjs +76 -4
- package/lib/completion-auditor.mjs +20 -0
- package/lib/context-manifest.mjs +38 -21
- package/lib/decision-policy.mjs +40 -2
- package/lib/evidence-store.mjs +121 -31
- package/lib/executable-probe.mjs +69 -0
- package/lib/hierarchical-context.mjs +31 -3
- package/lib/learning-engine.mjs +164 -65
- package/lib/memory-engine.mjs +163 -86
- package/lib/model-config.mjs +86 -7
- package/lib/performance-fabric.mjs +92 -0
- package/lib/permission-policy.mjs +232 -0
- package/lib/pi-rpc-pool.mjs +170 -5
- package/lib/provider-recovery.mjs +51 -0
- package/lib/repo-graph.mjs +38 -5
- package/lib/runtime-events.mjs +31 -10
- package/lib/semantic-index.mjs +67 -20
- package/lib/skill-compiler.mjs +84 -18
- package/lib/task-policy.mjs +80 -7
- package/lib/trajectory.mjs +25 -1
- package/lib/untrusted-output.mjs +106 -0
- package/package.json +8 -6
- package/pi/extensions/ues-child-runtime.ts +78 -3
- package/pi/extensions/ues.ts +695 -81
- package/scripts/benchmark-lsp-pool.mjs +128 -0
- package/scripts/check-release-consistency.mjs +16 -4
- package/scripts/check-source-integrity.mjs +60 -1
- package/scripts/run-test-suite.mjs +143 -0
- package/scripts/smoke-package-closure.mjs +4 -0
package/pi/extensions/ues.ts
CHANGED
|
@@ -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
|
|
1190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1317
|
-
|
|
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
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
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
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
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
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
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
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
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
|
-
|
|
3176
|
-
|
|
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:
|
|
3491
|
+
text: resultText,
|
|
3189
3492
|
},
|
|
3190
3493
|
);
|
|
3191
|
-
|
|
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",
|