opencode-agent-skill 15.0.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 +5 -5
- package/docs/V15-MANAGED-RUNTIME.md +18 -0
- package/global-config/agents/verifier.md +6 -2
- package/lib/affected-tests.mjs +58 -8
- package/lib/capability-fabric.mjs +107 -48
- package/lib/code-intelligence/index.mjs +4 -15
- package/lib/code-intelligence/lsp-pool.mjs +694 -0
- package/lib/code-intelligence/lsp-provider.mjs +481 -67
- package/lib/compaction-resume-guard.mjs +327 -0
- package/lib/completion-auditor.mjs +20 -0
- package/lib/context-engine-v11.mjs +20 -7
- 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/execution-contract.mjs +29 -16
- package/lib/hierarchical-context.mjs +31 -3
- package/lib/learning-engine.mjs +164 -65
- package/lib/memory-engine.mjs +348 -98
- package/lib/model-config.mjs +86 -7
- package/lib/performance-fabric.mjs +354 -107
- 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/subagent-artifacts.mjs +182 -0
- package/lib/task-policy.mjs +80 -7
- package/lib/trajectory.mjs +25 -1
- package/lib/untrusted-output.mjs +106 -0
- package/lib/verification-broker.mjs +32 -27
- package/lib/workspace-fingerprint.mjs +43 -8
- package/lib/workspace-hygiene.mjs +481 -0
- package/package.json +8 -6
- package/pi/extensions/ues-child-runtime.ts +103 -5
- package/pi/extensions/ues.ts +984 -95
- 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,17 +33,23 @@ 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";
|
|
39
|
+
import { createSubagentArtifact, failSubagentArtifact, finalizeSubagentArtifact, listSubagentArtifacts, readSubagentArtifact } from "../../lib/subagent-artifacts.mjs";
|
|
38
40
|
import { buildExecutionContract, buildFinalVerdictMatrix, captureInheritedDirtyState, detectInheritedDirtyViolations, enforcePhaseGates, executionContractPrompt, phaseArtifactPayloads, taskExplicitlyAllowsLocalEnvWrite } from "../../lib/execution-contract.mjs";
|
|
41
|
+
import { buildCompactionResumeGuard, checkpointDurableWorkBeforeCompaction, renderCompactionResumeGuard } from "../../lib/compaction-resume-guard.mjs";
|
|
39
42
|
import { sessionNameFromUesInput, uesSessionName } from "../../lib/session-display.mjs";
|
|
40
43
|
import { requireGitWorkspaceRoot, resolveGitWorkspaceRoot } from "../../lib/workspace-root.mjs";
|
|
41
44
|
import { createAdaptiveDeadline } from "../../lib/activity-deadline.mjs";
|
|
42
45
|
import { extractValidatedPlan } from "../../lib/plan-salvage.mjs";
|
|
43
46
|
import { auditCompletion } from "../../lib/completion-auditor.mjs";
|
|
44
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";
|
|
45
50
|
import { McpHealthTracker } from "../../lib/mcp-health.mjs";
|
|
46
|
-
import { runtimeWorkspaceFingerprint, runtimeWorkspaceSnapshot } from "../../lib/workspace-fingerprint.mjs";
|
|
51
|
+
import { captureWorkspaceStateV2, runtimeWorkspaceFingerprint, runtimeWorkspaceSnapshot } from "../../lib/workspace-fingerprint.mjs";
|
|
52
|
+
import { captureWorkspaceHygieneBaseline, postRunFileHygiene, preFinalWorkspaceAudit } from "../../lib/workspace-hygiene.mjs";
|
|
47
53
|
import { appendTrajectoryEvent, createTraceID } from "../../lib/trajectory.mjs";
|
|
48
54
|
import {
|
|
49
55
|
browserEvidenceNeeded,
|
|
@@ -53,6 +59,17 @@ import {
|
|
|
53
59
|
} from "../../lib/browser-mcp-routing.mjs";
|
|
54
60
|
import { clearRepoGraphRuntimeCache } from "../../lib/repo-graph.mjs";
|
|
55
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";
|
|
56
73
|
import {
|
|
57
74
|
createTaskSandbox,
|
|
58
75
|
integrateTaskSandbox,
|
|
@@ -158,6 +175,22 @@ const CONTEXT_CACHE_MAX = configuredCount(
|
|
|
158
175
|
4,
|
|
159
176
|
128,
|
|
160
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
|
+
);
|
|
161
194
|
|
|
162
195
|
function configuredBoolean(name: string, fallback = true) {
|
|
163
196
|
const raw = String(process.env[name] ?? "").trim().toLowerCase();
|
|
@@ -183,6 +216,9 @@ const MCP_HEALTH = new McpHealthTracker({
|
|
|
183
216
|
failureThreshold: 2,
|
|
184
217
|
cooldownMs: configuredDuration("UES_MCP_HEALTH_COOLDOWN_MS", 15_000, 1_000, 5 * 60_000),
|
|
185
218
|
});
|
|
219
|
+
const PERMISSION_POLICY = new PermissionPolicyStore(
|
|
220
|
+
path.join(getUesConfigDir(), ".ues", "permissions.json"),
|
|
221
|
+
);
|
|
186
222
|
|
|
187
223
|
function abortActiveCliChildren() {
|
|
188
224
|
let aborted = 0;
|
|
@@ -272,6 +308,10 @@ type RunResult = {
|
|
|
272
308
|
browserTools?: string[];
|
|
273
309
|
childRuntime?: "rpc" | "cli";
|
|
274
310
|
workerReused?: boolean;
|
|
311
|
+
providerFailure?: string;
|
|
312
|
+
providerRecoveryAttempts?: number;
|
|
313
|
+
providerSessionResumeAttempts?: number;
|
|
314
|
+
subagentArtifact?: any;
|
|
275
315
|
optimizations?: any;
|
|
276
316
|
};
|
|
277
317
|
|
|
@@ -659,6 +699,7 @@ async function runAgentCli(
|
|
|
659
699
|
idleTimeoutMs?: number;
|
|
660
700
|
postToolErrorIdleTimeoutMs?: number;
|
|
661
701
|
allowLocalEnvWrite?: boolean;
|
|
702
|
+
reuseRpcSession?: boolean;
|
|
662
703
|
} = {},
|
|
663
704
|
): Promise<RunResult> {
|
|
664
705
|
const config = AGENTS[agent];
|
|
@@ -719,6 +760,7 @@ async function runAgentCli(
|
|
|
719
760
|
UES_CHILD_TOOL_OUTPUT_LIMIT: String(runtimeOptions.toolOutputLimit || 24 * 1024),
|
|
720
761
|
UES_CHILD_VERIFICATION_TIMEOUT_SEC: String(runtimeOptions.verificationTimeoutSec || 300),
|
|
721
762
|
UES_CHILD_ALLOW_LOCAL_ENV_WRITE: runtimeOptions.allowLocalEnvWrite ? "1" : "0",
|
|
763
|
+
UES_CHILD_EXTERNAL_TOOL_NAMES: extraTools.join(","),
|
|
722
764
|
},
|
|
723
765
|
shell: false,
|
|
724
766
|
detached: process.platform !== "win32",
|
|
@@ -1007,11 +1049,17 @@ async function runAgentCli(
|
|
|
1007
1049
|
}
|
|
1008
1050
|
|
|
1009
1051
|
|
|
1052
|
+
const RPC_PROMPT_PATH_CACHE = new Map<AgentName, string>();
|
|
1053
|
+
|
|
1010
1054
|
function rpcPromptPath(agent: AgentName) {
|
|
1055
|
+
const cached = RPC_PROMPT_PATH_CACHE.get(agent);
|
|
1056
|
+
if (cached && fs.existsSync(cached)) return cached;
|
|
1057
|
+
|
|
1011
1058
|
const dir = path.join(os.tmpdir(), "ues-pi-rpc-prompts");
|
|
1012
1059
|
fs.mkdirSync(dir, { recursive: true });
|
|
1013
1060
|
const file = path.join(dir, agent + ".md");
|
|
1014
1061
|
fs.writeFileSync(file, getAgentPrompt(agent), { encoding: "utf8", mode: 0o600 });
|
|
1062
|
+
RPC_PROMPT_PATH_CACHE.set(agent, file);
|
|
1015
1063
|
return file;
|
|
1016
1064
|
}
|
|
1017
1065
|
|
|
@@ -1044,6 +1092,7 @@ async function runAgentRpc(
|
|
|
1044
1092
|
idleTimeoutMs?: number;
|
|
1045
1093
|
postToolErrorIdleTimeoutMs?: number;
|
|
1046
1094
|
allowLocalEnvWrite?: boolean;
|
|
1095
|
+
reuseRpcSession?: boolean;
|
|
1047
1096
|
} = {},
|
|
1048
1097
|
): Promise<RunResult> {
|
|
1049
1098
|
const config = AGENTS[agent];
|
|
@@ -1124,6 +1173,7 @@ async function runAgentRpc(
|
|
|
1124
1173
|
UES_CHILD_TOOL_OUTPUT_LIMIT: String(runtimeOptions.toolOutputLimit || 24 * 1024),
|
|
1125
1174
|
UES_CHILD_VERIFICATION_TIMEOUT_SEC: String(runtimeOptions.verificationTimeoutSec || 300),
|
|
1126
1175
|
UES_CHILD_ALLOW_LOCAL_ENV_WRITE: runtimeOptions.allowLocalEnvWrite ? "1" : "0",
|
|
1176
|
+
UES_CHILD_EXTERNAL_TOOL_NAMES: extraTools.join(","),
|
|
1127
1177
|
},
|
|
1128
1178
|
},
|
|
1129
1179
|
taskInput,
|
|
@@ -1135,6 +1185,7 @@ async function runAgentRpc(
|
|
|
1135
1185
|
activityWindowMs,
|
|
1136
1186
|
idleTimeoutMs,
|
|
1137
1187
|
postToolErrorIdleTimeoutMs,
|
|
1188
|
+
reuseSession: runtimeOptions.reuseRpcSession === true,
|
|
1138
1189
|
onEvent: (event: any) => {
|
|
1139
1190
|
lastActivityAt = Date.now();
|
|
1140
1191
|
if (event.type === "tool_execution_start") {
|
|
@@ -1182,13 +1233,13 @@ async function runAgentRpc(
|
|
|
1182
1233
|
);
|
|
1183
1234
|
|
|
1184
1235
|
const message = rpc.message;
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1236
|
+
const assistantText = extractAssistantText(message);
|
|
1237
|
+
const baseResult: RunResult = {
|
|
1187
1238
|
agent,
|
|
1188
1239
|
task,
|
|
1189
1240
|
cwd,
|
|
1190
|
-
exitCode: 0,
|
|
1191
|
-
output: cap(output, 100 * 1024),
|
|
1241
|
+
exitCode: message?.stopReason === "error" ? 1 : 0,
|
|
1242
|
+
output: cap(assistantText || rpc.stderr || "(no assistant output)", 100 * 1024),
|
|
1192
1243
|
stderr: cap(String(rpc.stderr || ""), 64 * 1024),
|
|
1193
1244
|
model: message?.model || model,
|
|
1194
1245
|
stopReason: message?.stopReason,
|
|
@@ -1200,6 +1251,16 @@ async function runAgentRpc(
|
|
|
1200
1251
|
childRuntime: "rpc",
|
|
1201
1252
|
workerReused: rpc.workerReused === true,
|
|
1202
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;
|
|
1203
1264
|
} catch (error) {
|
|
1204
1265
|
const message = error instanceof Error ? error.message : String(error);
|
|
1205
1266
|
if (detectedHang) {
|
|
@@ -1284,54 +1345,137 @@ async function runAgent(
|
|
|
1284
1345
|
idleTimeoutMs?: number;
|
|
1285
1346
|
postToolErrorIdleTimeoutMs?: number;
|
|
1286
1347
|
allowLocalEnvWrite?: boolean;
|
|
1348
|
+
reuseRpcSession?: boolean;
|
|
1287
1349
|
} = {},
|
|
1288
1350
|
): Promise<RunResult> {
|
|
1289
|
-
|
|
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;
|
|
1290
1385
|
try {
|
|
1291
|
-
|
|
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(
|
|
1292
1402
|
agent,
|
|
1293
|
-
|
|
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(" "),
|
|
1294
1409
|
cwd,
|
|
1295
|
-
model,
|
|
1410
|
+
result.model || model,
|
|
1296
1411
|
thinkingLevel,
|
|
1297
1412
|
signal,
|
|
1298
1413
|
onProgress,
|
|
1299
1414
|
extraTools,
|
|
1300
|
-
runtimeOptions,
|
|
1415
|
+
{ ...runtimeOptions, reuseRpcSession: true },
|
|
1301
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);
|
|
1302
1424
|
} catch (error) {
|
|
1303
|
-
if (CHILD_RUNTIME === "rpc") throw error;
|
|
1304
|
-
// Auto mode may fall back only when RPC failed before the delegated task
|
|
1305
|
-
// started. In-task failures must not cause a blind second execution.
|
|
1306
|
-
if ((error as any)?.uesRpcPhase && (error as any).uesRpcPhase !== "startup") {
|
|
1307
|
-
throw error;
|
|
1308
|
-
}
|
|
1309
1425
|
try {
|
|
1310
1426
|
onProgress?.({
|
|
1311
|
-
agent,
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
toolCalls: 0,
|
|
1315
|
-
model,
|
|
1316
|
-
phase: "running",
|
|
1317
|
-
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",
|
|
1318
1430
|
});
|
|
1319
1431
|
} catch {}
|
|
1432
|
+
break;
|
|
1320
1433
|
}
|
|
1321
1434
|
}
|
|
1322
|
-
return runAgentCli(
|
|
1323
|
-
agent,
|
|
1324
|
-
task,
|
|
1325
|
-
cwd,
|
|
1326
|
-
model,
|
|
1327
|
-
thinkingLevel,
|
|
1328
|
-
signal,
|
|
1329
|
-
onProgress,
|
|
1330
|
-
extraTools,
|
|
1331
|
-
runtimeOptions,
|
|
1332
|
-
);
|
|
1333
|
-
}
|
|
1334
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
|
+
}
|
|
1335
1479
|
|
|
1336
1480
|
function roleForAgent(agent: AgentName) {
|
|
1337
1481
|
return agent.replace(/^ues-/, "");
|
|
@@ -1382,6 +1526,11 @@ function parseStructuredReport(output: string) {
|
|
|
1382
1526
|
};
|
|
1383
1527
|
}
|
|
1384
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
|
+
|
|
1385
1534
|
function taskRecord(task: string) {
|
|
1386
1535
|
return {
|
|
1387
1536
|
id: "pi-dispatch",
|
|
@@ -1510,8 +1659,24 @@ async function runRoutedAgent(
|
|
|
1510
1659
|
taskPolicyOverride?: any,
|
|
1511
1660
|
contextCacheNamespace?: string,
|
|
1512
1661
|
): Promise<RunResult> {
|
|
1662
|
+
const routedStartedAt = Date.now();
|
|
1513
1663
|
const role = roleForAgent(agent);
|
|
1514
1664
|
const traceRoot = requireGitWorkspaceRoot(cwd, "UES specialist");
|
|
1665
|
+
const workspaceSnapshotStartedAt = Date.now();
|
|
1666
|
+
let agentWorkspaceState: any = null;
|
|
1667
|
+
let workspaceState: any = {
|
|
1668
|
+
cacheable: false,
|
|
1669
|
+
fingerprint: "unknown",
|
|
1670
|
+
changedFiles: [],
|
|
1671
|
+
};
|
|
1672
|
+
try {
|
|
1673
|
+
agentWorkspaceState = captureWorkspaceStateV2(cwd);
|
|
1674
|
+
workspaceState = runtimeWorkspaceSnapshot(cwd, { workspaceState: agentWorkspaceState });
|
|
1675
|
+
} catch {}
|
|
1676
|
+
const agentHygieneBaseline = captureWorkspaceHygieneBaseline(cwd, {
|
|
1677
|
+
workspaceState: agentWorkspaceState,
|
|
1678
|
+
});
|
|
1679
|
+
const workspaceSnapshotMs = Date.now() - workspaceSnapshotStartedAt;
|
|
1515
1680
|
const browserRequested = browserEvidenceNeeded(task, role);
|
|
1516
1681
|
const browserTools = browserRequested
|
|
1517
1682
|
? selectBrowserToolsForTask(HOST_BROWSER_TOOL_NAMES, task, role)
|
|
@@ -1545,6 +1710,8 @@ async function runRoutedAgent(
|
|
|
1545
1710
|
modelTier: selection.tier,
|
|
1546
1711
|
profile: taskPolicy.executionProfile,
|
|
1547
1712
|
risk: taskPolicy.risk,
|
|
1713
|
+
decisionConfidence: taskPolicy.decision?.confidence ?? null,
|
|
1714
|
+
decisionReason: taskPolicy.decision?.reason ?? null,
|
|
1548
1715
|
browserRequested,
|
|
1549
1716
|
}).catch(() => {});
|
|
1550
1717
|
}
|
|
@@ -1573,14 +1740,6 @@ async function runRoutedAgent(
|
|
|
1573
1740
|
? undefined
|
|
1574
1741
|
: inheritedThinking;
|
|
1575
1742
|
|
|
1576
|
-
let workspaceState: any = {
|
|
1577
|
-
cacheable: false,
|
|
1578
|
-
fingerprint: "unknown",
|
|
1579
|
-
changedFiles: [],
|
|
1580
|
-
};
|
|
1581
|
-
try {
|
|
1582
|
-
workspaceState = runtimeWorkspaceSnapshot(cwd);
|
|
1583
|
-
} catch {}
|
|
1584
1743
|
const workspaceFingerprint = String(workspaceState.fingerprint || "unknown");
|
|
1585
1744
|
|
|
1586
1745
|
let enrichedTask = task;
|
|
@@ -1590,6 +1749,8 @@ async function runRoutedAgent(
|
|
|
1590
1749
|
let affectedTests: any = null;
|
|
1591
1750
|
let reusableVerification: any = null;
|
|
1592
1751
|
let contextCacheHit = false;
|
|
1752
|
+
let contextPerformance: any = null;
|
|
1753
|
+
const contextBuildStartedAt = Date.now();
|
|
1593
1754
|
try {
|
|
1594
1755
|
if (fastBoundedContext) {
|
|
1595
1756
|
contextQuality = { schemaVersion: 1, profile: "fast-bounded", bounded: true };
|
|
@@ -1650,41 +1811,42 @@ async function runRoutedAgent(
|
|
|
1650
1811
|
}
|
|
1651
1812
|
}
|
|
1652
1813
|
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
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),
|
|
1661
1822
|
AFFECTED_TEST_HINTS_ENABLED &&
|
|
1662
1823
|
["executor", "debugger", "verifier", "integration-verifier"].includes(role)
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
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),
|
|
1674
1832
|
["verifier", "integration-verifier"].includes(role) &&
|
|
1675
1833
|
!["high", "critical"].includes(String(taskPolicy.risk || "").toLowerCase())
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
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;
|
|
1686
1847
|
|
|
1687
1848
|
contextQuality = pack.contextQuality;
|
|
1849
|
+
contextPerformance = pack.performance || null;
|
|
1688
1850
|
enrichedTask = [
|
|
1689
1851
|
task,
|
|
1690
1852
|
"",
|
|
@@ -1723,6 +1885,7 @@ async function runRoutedAgent(
|
|
|
1723
1885
|
enrichedTask += "\n\n## Previous failed verification\n" + cap(recentFailure, 5000);
|
|
1724
1886
|
}
|
|
1725
1887
|
}
|
|
1888
|
+
const contextBuildMs = Date.now() - contextBuildStartedAt;
|
|
1726
1889
|
|
|
1727
1890
|
if (browserRequested) {
|
|
1728
1891
|
enrichedTask += browserTools.length
|
|
@@ -1747,7 +1910,34 @@ async function runRoutedAgent(
|
|
|
1747
1910
|
taskChars: task.length,
|
|
1748
1911
|
});
|
|
1749
1912
|
const startedAt = Date.now();
|
|
1750
|
-
const
|
|
1913
|
+
const artifactRoot = await taskSandboxOwnerRoot(cwd).catch(() => null) || traceRoot;
|
|
1914
|
+
const childArtifact = await createSubagentArtifact(artifactRoot, {
|
|
1915
|
+
agent,
|
|
1916
|
+
role,
|
|
1917
|
+
attempt,
|
|
1918
|
+
task,
|
|
1919
|
+
traceID,
|
|
1920
|
+
model: selectedModel,
|
|
1921
|
+
modelTier: selection.tier,
|
|
1922
|
+
workspaceFingerprint,
|
|
1923
|
+
}).catch(() => null);
|
|
1924
|
+
if (childArtifact?.handle) {
|
|
1925
|
+
try {
|
|
1926
|
+
onProgress?.({
|
|
1927
|
+
agent,
|
|
1928
|
+
elapsedMs: 0,
|
|
1929
|
+
idleMs: 0,
|
|
1930
|
+
toolCalls: 0,
|
|
1931
|
+
model: selectedModel,
|
|
1932
|
+
phase: "running",
|
|
1933
|
+
note: `subagent handle ${childArtifact.handle}; durable status ${childArtifact.file}`,
|
|
1934
|
+
});
|
|
1935
|
+
} catch {}
|
|
1936
|
+
}
|
|
1937
|
+
let result: RunResult;
|
|
1938
|
+
const modelRunStartedAt = Date.now();
|
|
1939
|
+
try {
|
|
1940
|
+
result = await runAgent(
|
|
1751
1941
|
agent,
|
|
1752
1942
|
enrichedTask,
|
|
1753
1943
|
cwd,
|
|
@@ -1801,8 +1991,56 @@ async function runRoutedAgent(
|
|
|
1801
1991
|
allowLocalEnvWrite:
|
|
1802
1992
|
taskPolicy.localEnvWriteExplicitlyAllowed === true ||
|
|
1803
1993
|
taskExplicitlyAllowsLocalEnvWrite(task),
|
|
1804
|
-
|
|
1805
|
-
|
|
1994
|
+
},
|
|
1995
|
+
);
|
|
1996
|
+
} catch (error) {
|
|
1997
|
+
if (childArtifact?.handle) {
|
|
1998
|
+
await failSubagentArtifact(artifactRoot, childArtifact.handle, error).catch(() => null);
|
|
1999
|
+
}
|
|
2000
|
+
throw error;
|
|
2001
|
+
}
|
|
2002
|
+
const modelRunMs = Date.now() - modelRunStartedAt;
|
|
2003
|
+
const hygieneStartedAt = Date.now();
|
|
2004
|
+
const postRunHygiene = await postRunFileHygiene(cwd, {
|
|
2005
|
+
baseline: agentHygieneBaseline,
|
|
2006
|
+
taskText: task,
|
|
2007
|
+
allowSourceMutations: WRITE_AGENTS.has(agent),
|
|
2008
|
+
autoClean: true,
|
|
2009
|
+
}).catch((error) => ({
|
|
2010
|
+
schemaVersion: 1,
|
|
2011
|
+
safe: false,
|
|
2012
|
+
changed: [],
|
|
2013
|
+
created: [],
|
|
2014
|
+
removed: [],
|
|
2015
|
+
findings: [{
|
|
2016
|
+
kind: "hygiene-audit-error",
|
|
2017
|
+
file: ".",
|
|
2018
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2019
|
+
}],
|
|
2020
|
+
summary: "workspace hygiene audit failed: " + (error instanceof Error ? error.message : String(error)),
|
|
2021
|
+
}));
|
|
2022
|
+
if (!postRunHygiene.safe) {
|
|
2023
|
+
const hygieneText = [
|
|
2024
|
+
"UES post-run workspace hygiene guard rejected this child run.",
|
|
2025
|
+
postRunHygiene.summary || "workspace hygiene failed",
|
|
2026
|
+
].join("\n");
|
|
2027
|
+
result = {
|
|
2028
|
+
...result,
|
|
2029
|
+
exitCode: result.exitCode === 0 ? 2 : result.exitCode,
|
|
2030
|
+
stopReason: "error",
|
|
2031
|
+
errorMessage: hygieneText,
|
|
2032
|
+
output: [result.output, "", hygieneText].filter(Boolean).join("\n"),
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
const hygieneMs = Date.now() - hygieneStartedAt;
|
|
2037
|
+
const finalizedChildArtifact = childArtifact?.handle
|
|
2038
|
+
? await finalizeSubagentArtifact(artifactRoot, childArtifact.handle, {
|
|
2039
|
+
...result,
|
|
2040
|
+
durationMs: Date.now() - startedAt,
|
|
2041
|
+
verdict: verdictFromOutput(result.output),
|
|
2042
|
+
}).catch(() => null)
|
|
2043
|
+
: null;
|
|
1806
2044
|
const enrichedResult: RunResult = {
|
|
1807
2045
|
...result,
|
|
1808
2046
|
task,
|
|
@@ -1833,10 +2071,32 @@ async function runRoutedAgent(
|
|
|
1833
2071
|
turboFastStrategy: turboFast.strategy,
|
|
1834
2072
|
turboFastTimeouts: turboFast.eligible ? TURBO_FAST_TIMEOUTS : null,
|
|
1835
2073
|
planningRuntimeBudget: planningBudget,
|
|
2074
|
+
affectedTestInventorySource: affectedTests?.inventorySource || null,
|
|
2075
|
+
contextPerformance,
|
|
2076
|
+
latencyMs: {
|
|
2077
|
+
workspaceSnapshot: workspaceSnapshotMs,
|
|
2078
|
+
contextBuild: contextBuildMs,
|
|
2079
|
+
modelRun: modelRunMs,
|
|
2080
|
+
hygiene: hygieneMs,
|
|
2081
|
+
total: Date.now() - routedStartedAt,
|
|
2082
|
+
},
|
|
1836
2083
|
},
|
|
1837
2084
|
verdict: verdictFromOutput(result.output),
|
|
1838
2085
|
report: parseStructuredReport(result.output),
|
|
1839
2086
|
durationMs: Date.now() - startedAt,
|
|
2087
|
+
subagentArtifact: finalizedChildArtifact ? {
|
|
2088
|
+
handle: finalizedChildArtifact.handle,
|
|
2089
|
+
status: finalizedChildArtifact.status,
|
|
2090
|
+
file: finalizedChildArtifact.file,
|
|
2091
|
+
taskRef: finalizedChildArtifact.taskRef,
|
|
2092
|
+
outputRef: finalizedChildArtifact.result?.outputRef || null,
|
|
2093
|
+
resume: finalizedChildArtifact.resume || null,
|
|
2094
|
+
} : childArtifact ? {
|
|
2095
|
+
handle: childArtifact.handle,
|
|
2096
|
+
status: childArtifact.status,
|
|
2097
|
+
file: childArtifact.file,
|
|
2098
|
+
taskRef: childArtifact.taskRef,
|
|
2099
|
+
} : null,
|
|
1840
2100
|
};
|
|
1841
2101
|
if (traceID) {
|
|
1842
2102
|
await appendTrajectoryEvent(traceRoot, traceID, "agent.completed", {
|
|
@@ -2813,8 +3073,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
2813
3073
|
const AUTO_ADMISSION_ENABLED = !["0", "false", "off"].includes(
|
|
2814
3074
|
String(process.env.UES_AUTO_ADMIT || "1").trim().toLowerCase(),
|
|
2815
3075
|
);
|
|
2816
|
-
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"]);
|
|
2817
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;
|
|
2818
3086
|
|
|
2819
3087
|
const currentNonUesTools = () =>
|
|
2820
3088
|
pi.getActiveTools().filter((name) => !UES_PARENT_TOOL_NAMES.has(String(name)));
|
|
@@ -2848,6 +3116,40 @@ export default function (pi: ExtensionAPI) {
|
|
|
2848
3116
|
deactivateParentUesTools();
|
|
2849
3117
|
});
|
|
2850
3118
|
|
|
3119
|
+
pi.on("session_before_compact", async (event, ctx) => {
|
|
3120
|
+
const root = resolveGitWorkspaceRoot(ctx.cwd || "");
|
|
3121
|
+
if (!root) return undefined;
|
|
3122
|
+
await checkpointDurableWorkBeforeCompaction(root, {
|
|
3123
|
+
reason: event.reason,
|
|
3124
|
+
maxWorkspaces: 3,
|
|
3125
|
+
}).catch(() => []);
|
|
3126
|
+
return undefined;
|
|
3127
|
+
});
|
|
3128
|
+
|
|
3129
|
+
pi.on("session_compact", async (event, ctx) => {
|
|
3130
|
+
const root = resolveGitWorkspaceRoot(ctx.cwd || "");
|
|
3131
|
+
if (!root) return;
|
|
3132
|
+
const packet = await buildCompactionResumeGuard(root, {
|
|
3133
|
+
reason: event.reason,
|
|
3134
|
+
maxWorkspaces: 3,
|
|
3135
|
+
}).catch(() => null);
|
|
3136
|
+
if (!packet?.workspaces?.length) return;
|
|
3137
|
+
const content = renderCompactionResumeGuard(packet);
|
|
3138
|
+
if (!content) return;
|
|
3139
|
+
pi.sendMessage({
|
|
3140
|
+
customType: "ues-durable-resume-guard",
|
|
3141
|
+
content,
|
|
3142
|
+
display: false,
|
|
3143
|
+
details: {
|
|
3144
|
+
schemaVersion: packet.schemaVersion || 1,
|
|
3145
|
+
reason: event.reason,
|
|
3146
|
+
willRetry: event.willRetry,
|
|
3147
|
+
workspaceCount: packet.workspaceCount,
|
|
3148
|
+
source: packet.source,
|
|
3149
|
+
},
|
|
3150
|
+
}, { triggerTurn: false });
|
|
3151
|
+
});
|
|
3152
|
+
|
|
2851
3153
|
pi.on("session_shutdown", async () => {
|
|
2852
3154
|
directControllerAbort?.abort();
|
|
2853
3155
|
directControllerAbort = null;
|
|
@@ -2861,6 +3163,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2861
3163
|
MCP_HEALTH.clear();
|
|
2862
3164
|
abortActiveCliChildren();
|
|
2863
3165
|
await stopAllServices().catch(() => []);
|
|
3166
|
+
await shutdownLspPool().catch(() => ({ stopped: 0, remaining: 0 }));
|
|
2864
3167
|
await RPC_POOL.stopAll().catch(() => {});
|
|
2865
3168
|
for (const dir of [...ACTIVE_TASK_SANDBOXES.keys()]) {
|
|
2866
3169
|
const ownerRoot = await taskSandboxOwnerRoot(dir).catch(() => null);
|
|
@@ -2966,13 +3269,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
2966
3269
|
return { action: "continue" };
|
|
2967
3270
|
}
|
|
2968
3271
|
|
|
2969
|
-
const
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
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 {}
|
|
2976
3293
|
return { action: "handled" };
|
|
2977
3294
|
}
|
|
2978
3295
|
|
|
@@ -2982,19 +3299,109 @@ export default function (pi: ExtensionAPI) {
|
|
|
2982
3299
|
});
|
|
2983
3300
|
|
|
2984
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
|
+
}
|
|
2985
3314
|
if (uesModeActive()) activateParentUesTools();
|
|
2986
3315
|
else deactivateParentUesTools();
|
|
2987
3316
|
});
|
|
2988
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
|
+
|
|
2989
3392
|
pi.on("agent_end", async () => {
|
|
2990
3393
|
promptUesActive = false;
|
|
3394
|
+
parentProviderRecoveryPending = false;
|
|
3395
|
+
parentProviderRecoveryConsecutive = 0;
|
|
3396
|
+
parentProviderRecoveryTotal = 0;
|
|
2991
3397
|
deactivateParentUesTools();
|
|
2992
3398
|
});
|
|
2993
3399
|
|
|
2994
3400
|
pi.on("tool_call", async (event, ctx) => {
|
|
2995
3401
|
const toolName = String(event.toolName || "");
|
|
3402
|
+
if (!uesModeActive()) parentRunToolCalls += 1;
|
|
2996
3403
|
if (!uesModeActive()) {
|
|
2997
|
-
if (toolName.startsWith("ues_")) {
|
|
3404
|
+
if (toolName.startsWith("ues_") && !ALWAYS_ON_PARENT_TOOLS.has(toolName)) {
|
|
2998
3405
|
return {
|
|
2999
3406
|
block: true,
|
|
3000
3407
|
reason: "UES parent tools are hidden outside admitted UES runs. Submit a normal engineering task or start an explicit /ues-* command.",
|
|
@@ -3002,11 +3409,48 @@ export default function (pi: ExtensionAPI) {
|
|
|
3002
3409
|
}
|
|
3003
3410
|
return undefined;
|
|
3004
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
|
+
|
|
3005
3449
|
if (toolName !== "bash" && toolName !== "powershell") {
|
|
3006
3450
|
const allTools = typeof (pi as any).getAllTools === "function" ? (pi as any).getAllTools() : [];
|
|
3007
3451
|
const descriptor = allTools.find((tool: any) => String(tool?.name || "") === toolName);
|
|
3008
3452
|
const policy = mcpExecutionPolicy(descriptor || { name: toolName });
|
|
3009
|
-
if (policy.confirmationRequired) {
|
|
3453
|
+
if (policy.confirmationRequired && !permissionApproved) {
|
|
3010
3454
|
const allowed = await approveRisk(ctx, `MCP/tool call: ${toolName}`, `mcp-destructive:${toolName}`);
|
|
3011
3455
|
if (!allowed) return { block: true, reason: `Blocked by UES MCP destructive-hint gate: ${toolName}` };
|
|
3012
3456
|
}
|
|
@@ -3025,8 +3469,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
3025
3469
|
}
|
|
3026
3470
|
const risk = destructiveShellRisk(command);
|
|
3027
3471
|
if (!risk.risky) return undefined;
|
|
3028
|
-
|
|
3029
|
-
|
|
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
|
+
}
|
|
3030
3476
|
return undefined;
|
|
3031
3477
|
});
|
|
3032
3478
|
|
|
@@ -3034,14 +3480,222 @@ export default function (pi: ExtensionAPI) {
|
|
|
3034
3480
|
if (!uesModeActive()) return undefined;
|
|
3035
3481
|
const toolName = String((event as any).toolName || "");
|
|
3036
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 });
|
|
3037
3487
|
MCP_HEALTH.finish(
|
|
3038
3488
|
String((event as any).toolCallId || ""),
|
|
3039
3489
|
{
|
|
3040
3490
|
isError: (event as any).isError === true,
|
|
3041
|
-
text:
|
|
3491
|
+
text: resultText,
|
|
3042
3492
|
},
|
|
3043
3493
|
);
|
|
3044
|
-
|
|
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
|
+
},
|
|
3045
3699
|
});
|
|
3046
3700
|
|
|
3047
3701
|
pi.registerTool({
|
|
@@ -3197,6 +3851,60 @@ export default function (pi: ExtensionAPI) {
|
|
|
3197
3851
|
},
|
|
3198
3852
|
});
|
|
3199
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
|
+
|
|
3200
3908
|
const uesExecuteTool: any = {
|
|
3201
3909
|
name: "ues_execute",
|
|
3202
3910
|
label: "UES Execute",
|
|
@@ -3212,7 +3920,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
3212
3920
|
const cwd = requireGitWorkspaceRoot(params.cwd || ctx.cwd, "ues_execute");
|
|
3213
3921
|
const inheritedModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
3214
3922
|
const inheritedThinking = ctx.thinkingLevel as string | undefined;
|
|
3215
|
-
const
|
|
3923
|
+
const controllerWorkspaceState = captureWorkspaceStateV2(cwd);
|
|
3924
|
+
const inheritedDirty = captureInheritedDirtyState(cwd, {
|
|
3925
|
+
workspaceState: controllerWorkspaceState,
|
|
3926
|
+
});
|
|
3927
|
+
const controllerHygieneBaseline = captureWorkspaceHygieneBaseline(cwd, {
|
|
3928
|
+
workspaceState: controllerWorkspaceState,
|
|
3929
|
+
});
|
|
3216
3930
|
const executionContract = buildExecutionContract(params.task, inheritedDirty);
|
|
3217
3931
|
const contractPrompt = executionContractPrompt(executionContract);
|
|
3218
3932
|
const suppliedPolicy = (params as any).__taskPolicy;
|
|
@@ -3243,6 +3957,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
3243
3957
|
profile: policy.executionProfile,
|
|
3244
3958
|
risk: policy.risk,
|
|
3245
3959
|
mode: policy.mode,
|
|
3960
|
+
decisionConfidence: policy.decision?.confidence ?? null,
|
|
3961
|
+
decisionReason: policy.decision?.reason ?? null,
|
|
3246
3962
|
inheritedDirtyCount: executionContract.inheritedDirty?.paths?.length || 0,
|
|
3247
3963
|
explicitPhaseCount: executionContract.phases?.length || 0,
|
|
3248
3964
|
localEnvWriteExplicitlyAllowed: executionContract.localEnvWriteExplicitlyAllowed === true,
|
|
@@ -3430,8 +4146,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
3430
4146
|
"",
|
|
3431
4147
|
"## Acceptance criteria proven",
|
|
3432
4148
|
deterministicPass
|
|
3433
|
-
? "All explicitly requested whitelisted Git inspection commands completed successfully."
|
|
3434
|
-
: "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.",
|
|
3435
4151
|
"",
|
|
3436
4152
|
"## Failures",
|
|
3437
4153
|
failureText,
|
|
@@ -3491,6 +4207,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
3491
4207
|
1,
|
|
3492
4208
|
);
|
|
3493
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
|
+
}
|
|
3494
4231
|
}
|
|
3495
4232
|
|
|
3496
4233
|
const after = runtimeWorkspaceSnapshot(cwd);
|
|
@@ -3505,6 +4242,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3505
4242
|
workspaceSnapshot: after,
|
|
3506
4243
|
behavioralReceipts: [],
|
|
3507
4244
|
requireBehavioralReceipt: false,
|
|
4245
|
+
requireClaimEvidenceStatus: true,
|
|
3508
4246
|
});
|
|
3509
4247
|
if (!unchanged) {
|
|
3510
4248
|
completionAudit.passed = false;
|
|
@@ -4043,6 +4781,56 @@ export default function (pi: ExtensionAPI) {
|
|
|
4043
4781
|
};
|
|
4044
4782
|
}
|
|
4045
4783
|
|
|
4784
|
+
const finalWriteScope = [...new Set(
|
|
4785
|
+
structuredPlan.tasks.flatMap((task: any) => taskWriteFiles(task)),
|
|
4786
|
+
)];
|
|
4787
|
+
const preFinalAudit = await preFinalWorkspaceAudit(cwd, {
|
|
4788
|
+
baseline: controllerHygieneBaseline,
|
|
4789
|
+
taskText: params.task,
|
|
4790
|
+
allowedPaths: finalWriteScope,
|
|
4791
|
+
strictScope: true,
|
|
4792
|
+
allowSourceMutations: true,
|
|
4793
|
+
}).catch((error) => ({
|
|
4794
|
+
safe: false,
|
|
4795
|
+
summary: "pre-final workspace audit failed: " + (error instanceof Error ? error.message : String(error)),
|
|
4796
|
+
findings: [],
|
|
4797
|
+
}));
|
|
4798
|
+
if (!preFinalAudit.safe) {
|
|
4799
|
+
if (durableWork) {
|
|
4800
|
+
await durableRecordIntegration(
|
|
4801
|
+
cwd,
|
|
4802
|
+
durableWork.slug,
|
|
4803
|
+
"PARTIAL",
|
|
4804
|
+
preFinalAudit.summary || "pre-final workspace hygiene failed",
|
|
4805
|
+
signal,
|
|
4806
|
+
).catch(() => {});
|
|
4807
|
+
}
|
|
4808
|
+
return {
|
|
4809
|
+
content: [{
|
|
4810
|
+
type: "text",
|
|
4811
|
+
text: [
|
|
4812
|
+
"UES pre-final workspace audit blocked PASS.",
|
|
4813
|
+
"The final repository contains an undeclared, transient, or Unicode-unsafe change.",
|
|
4814
|
+
"",
|
|
4815
|
+
preFinalAudit.summary || "workspace hygiene failed",
|
|
4816
|
+
].join("\n"),
|
|
4817
|
+
}],
|
|
4818
|
+
details: {
|
|
4819
|
+
mode: "execute",
|
|
4820
|
+
policy,
|
|
4821
|
+
steps,
|
|
4822
|
+
structuredPlan,
|
|
4823
|
+
scheduled,
|
|
4824
|
+
durableWork,
|
|
4825
|
+
verdictMatrix,
|
|
4826
|
+
executionContract,
|
|
4827
|
+
preFinalAudit,
|
|
4828
|
+
traceID,
|
|
4829
|
+
},
|
|
4830
|
+
isError: true,
|
|
4831
|
+
};
|
|
4832
|
+
}
|
|
4833
|
+
|
|
4046
4834
|
let durableFinalization: any = null;
|
|
4047
4835
|
if (durableWork) {
|
|
4048
4836
|
const finalEvidence = finalEvidenceForContract;
|
|
@@ -4219,7 +5007,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4219
5007
|
agent: "ues-deterministic-verifier", task: params.task, cwd, exitCode: 0,
|
|
4220
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"),
|
|
4221
5009
|
stderr: "", verdict: "PASS", durationMs: 0, toolCalls: 0, toolNames: [],
|
|
4222
|
-
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." } },
|
|
4223
5011
|
optimizations: { fastDeterministicVerification: true, behavioralReceiptCount: fastGate.behavioralReceipts.length },
|
|
4224
5012
|
};
|
|
4225
5013
|
steps.push(verification);
|
|
@@ -4237,6 +5025,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
4237
5025
|
].join("\n"), attempt, recentFailure || undefined,
|
|
4238
5026
|
);
|
|
4239
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
|
+
}
|
|
4240
5052
|
}
|
|
4241
5053
|
const verified = verification.exitCode === 0 && verification.verdict === "PASS";
|
|
4242
5054
|
if (!verified) {
|
|
@@ -4323,6 +5135,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4323
5135
|
workspaceSnapshot: completionSnapshot,
|
|
4324
5136
|
behavioralReceipts: freshReceipts,
|
|
4325
5137
|
requireBehavioralReceipt: policy.requireBehavioralReceipt !== false,
|
|
5138
|
+
requireClaimEvidenceStatus: true,
|
|
4326
5139
|
});
|
|
4327
5140
|
if (!completionAudit.passed) {
|
|
4328
5141
|
recentFailure = "Completion auditor rejected PASS: " + completionAudit.failures.join(", ");
|
|
@@ -4381,6 +5194,37 @@ export default function (pi: ExtensionAPI) {
|
|
|
4381
5194
|
};
|
|
4382
5195
|
}
|
|
4383
5196
|
|
|
5197
|
+
const preFinalAudit = await preFinalWorkspaceAudit(cwd, {
|
|
5198
|
+
baseline: controllerHygieneBaseline,
|
|
5199
|
+
taskText: params.task,
|
|
5200
|
+
strictScope: false,
|
|
5201
|
+
allowSourceMutations: true,
|
|
5202
|
+
}).catch((error) => ({
|
|
5203
|
+
safe: false,
|
|
5204
|
+
summary: "pre-final workspace audit failed: " + (error instanceof Error ? error.message : String(error)),
|
|
5205
|
+
findings: [],
|
|
5206
|
+
}));
|
|
5207
|
+
if (!preFinalAudit.safe) {
|
|
5208
|
+
recentFailure = "Pre-final workspace audit rejected PASS: " + (preFinalAudit.summary || "workspace hygiene failed");
|
|
5209
|
+
await recordRuntimeOutcome(implementation, params.task, false, attempt - 1);
|
|
5210
|
+
if (attempt < maxAttempts) continue;
|
|
5211
|
+
return {
|
|
5212
|
+
content: [{ type: "text", text: recentFailure }],
|
|
5213
|
+
details: {
|
|
5214
|
+
mode: "execute",
|
|
5215
|
+
policy,
|
|
5216
|
+
steps,
|
|
5217
|
+
attempts: attempt,
|
|
5218
|
+
completionAudit,
|
|
5219
|
+
verdictMatrix,
|
|
5220
|
+
executionContract,
|
|
5221
|
+
preFinalAudit,
|
|
5222
|
+
traceID,
|
|
5223
|
+
},
|
|
5224
|
+
isError: true,
|
|
5225
|
+
};
|
|
5226
|
+
}
|
|
5227
|
+
|
|
4384
5228
|
await recordRuntimeOutcome(implementation, params.task, true, attempt - 1);
|
|
4385
5229
|
const memory = await rememberVerifiedTask(cwd, params.task, verification, integrationResult);
|
|
4386
5230
|
const final = steps.at(-1);
|
|
@@ -4425,9 +5269,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
4425
5269
|
"Package root: " + PACKAGE_ROOT,
|
|
4426
5270
|
"Child runtime: " + CHILD_RUNTIME,
|
|
4427
5271
|
"Adaptive context: " + (ADAPTIVE_CONTEXT_ENABLED ? "on" : "off"),
|
|
5272
|
+
"Unified workspace snapshot V2: on",
|
|
5273
|
+
"Parallel context preparation: on",
|
|
5274
|
+
"Git-index affected-test inventory: on",
|
|
5275
|
+
"Parallel verification evidence I/O: on",
|
|
4428
5276
|
"Micro skills: " + (MICRO_SKILLS_ENABLED ? "on" : "off"),
|
|
4429
5277
|
"Turbo Fast Path: on",
|
|
4430
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)",
|
|
4431
5283
|
"Zero-friction engineering admission: " + (AUTO_ADMISSION_ENABLED ? "on (native / auto / high-risk + safe continuation)" : "off"),
|
|
4432
5284
|
"Git-root artifact guard: on",
|
|
4433
5285
|
"Inherited dirty-work guard: on",
|
|
@@ -4435,6 +5287,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
4435
5287
|
"Portable temp-path guard: on",
|
|
4436
5288
|
"Explicit phase barriers: on",
|
|
4437
5289
|
"Independent final verdict matrix: on",
|
|
5290
|
+
"Unicode source hygiene: blocking bidi/zero-width/control/homoglyph audit",
|
|
5291
|
+
"Post-run file hygiene: transient cleanup + read-only mutation guard",
|
|
5292
|
+
"Pre-final workspace audit: on",
|
|
4438
5293
|
"Disk hygiene: bounded + auto-clean",
|
|
4439
5294
|
"Writer concurrency: " + MAX_WRITER_CONCURRENCY,
|
|
4440
5295
|
].join("\n");
|
|
@@ -4766,9 +5621,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
4766
5621
|
name: "ues_dispatch",
|
|
4767
5622
|
label: "UES Dispatch",
|
|
4768
5623
|
description:
|
|
4769
|
-
"Run bundled UES specialist agents in isolated child Pi processes. Supports single, parallel, or chain mode. Parallel writer agents fail closed unless each writer has an explicit distinct cwd/worktree. Available agents: " +
|
|
5624
|
+
"Run bundled UES specialist agents in isolated child Pi processes, or inspect durable child handles with action=status/list. Every child run writes a small artifact with exact task/output evidence references. Supports single, parallel, or chain mode. Parallel writer agents fail closed unless each writer has an explicit distinct cwd/worktree. Available agents: " +
|
|
4770
5625
|
Object.keys(AGENTS).join(", "),
|
|
4771
5626
|
parameters: Type.Object({
|
|
5627
|
+
action: Type.Optional(Type.Union([Type.Literal("run"), Type.Literal("status"), Type.Literal("list")])),
|
|
5628
|
+
handle: Type.Optional(Type.String({ description: "Subagent artifact handle for status action" })),
|
|
4772
5629
|
agent: Type.Optional(Type.String({ description: "Agent name for single mode" })),
|
|
4773
5630
|
task: Type.Optional(Type.String({ description: "Task for single mode" })),
|
|
4774
5631
|
cwd: Type.Optional(Type.String({ description: "Working directory for single mode" })),
|
|
@@ -4777,6 +5634,39 @@ export default function (pi: ExtensionAPI) {
|
|
|
4777
5634
|
}),
|
|
4778
5635
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
4779
5636
|
refreshHostBrowserToolNames(pi);
|
|
5637
|
+
const action = String(params.action || "run");
|
|
5638
|
+
const baseCwd = ctx.cwd;
|
|
5639
|
+
if (action === "status") {
|
|
5640
|
+
if (!params.handle) {
|
|
5641
|
+
return {
|
|
5642
|
+
content: [{ type: "text", text: "ues_dispatch action=status requires handle." }],
|
|
5643
|
+
details: { mode: "status" },
|
|
5644
|
+
isError: true,
|
|
5645
|
+
};
|
|
5646
|
+
}
|
|
5647
|
+
try {
|
|
5648
|
+
const root = requireGitWorkspaceRoot(baseCwd, "ues_dispatch status");
|
|
5649
|
+
const artifact = await readSubagentArtifact(root, params.handle);
|
|
5650
|
+
return {
|
|
5651
|
+
content: [{ type: "text", text: JSON.stringify(artifact, null, 2) }],
|
|
5652
|
+
details: { mode: "status", artifact },
|
|
5653
|
+
};
|
|
5654
|
+
} catch (error) {
|
|
5655
|
+
return {
|
|
5656
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
5657
|
+
details: { mode: "status", handle: params.handle },
|
|
5658
|
+
isError: true,
|
|
5659
|
+
};
|
|
5660
|
+
}
|
|
5661
|
+
}
|
|
5662
|
+
if (action === "list") {
|
|
5663
|
+
const root = requireGitWorkspaceRoot(baseCwd, "ues_dispatch list");
|
|
5664
|
+
const artifacts = await listSubagentArtifacts(root, { limit: 20 });
|
|
5665
|
+
return {
|
|
5666
|
+
content: [{ type: "text", text: JSON.stringify(artifacts, null, 2) }],
|
|
5667
|
+
details: { mode: "list", artifacts },
|
|
5668
|
+
};
|
|
5669
|
+
}
|
|
4780
5670
|
const hasSingle = Boolean(params.agent && params.task);
|
|
4781
5671
|
const hasParallel = Boolean(params.tasks?.length);
|
|
4782
5672
|
const hasChain = Boolean(params.chain?.length);
|
|
@@ -4791,7 +5681,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
4791
5681
|
const validateAgent = (name: string): name is AgentName => name in AGENTS;
|
|
4792
5682
|
const model = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
4793
5683
|
const thinking = ctx.thinkingLevel as string | undefined;
|
|
4794
|
-
const baseCwd = ctx.cwd;
|
|
4795
5684
|
|
|
4796
5685
|
if (hasSingle) {
|
|
4797
5686
|
if (!validateAgent(params.agent!)) {
|