opencode-agent-skill 14.2.0-beta.1 → 15.0.0
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 +51 -18
- package/docs/PI-COMPAT.md +167 -3
- package/docs/V15-MANAGED-RUNTIME.md +128 -0
- package/global-config/agents/architect.md +63 -1
- package/global-config/agents/executor.md +7 -0
- package/global-config/agents/plan-checker.md +8 -1
- package/global-config/agents/verifier.md +3 -3
- package/global-config/skills/database-engineering/references/workflow.md +15 -0
- package/global-config/skills/engineering-orchestrator/references/long-horizon.md +21 -11
- package/lib/activity-deadline.mjs +70 -0
- package/lib/adaptive-context-budget.mjs +25 -5
- package/lib/affected-tests.mjs +1 -1
- package/lib/capability-fabric.mjs +40 -0
- package/lib/code-intelligence/edit-anchor.mjs +102 -0
- package/lib/code-intelligence/index.mjs +95 -0
- package/lib/code-intelligence/lsp-provider.mjs +189 -0
- package/lib/completion-auditor.mjs +102 -0
- package/lib/document-ingestion.mjs +60 -0
- package/lib/evidence-store.mjs +92 -8
- package/lib/execution-contract.mjs +579 -0
- package/lib/fast-verification-gate.mjs +69 -0
- package/lib/leaf-runtime-optimizer.mjs +76 -0
- package/lib/mcp-health.mjs +144 -0
- package/lib/mcp-tool-policy.mjs +19 -0
- package/lib/memory-engine.mjs +28 -0
- package/lib/pi-child-invocation.mjs +65 -0
- package/lib/pi-rpc-pool.mjs +61 -9
- package/lib/plan-salvage.mjs +124 -0
- package/lib/planning-speed-policy.mjs +131 -0
- package/lib/prompt-cache.mjs +26 -12
- package/lib/repo-graph.mjs +1 -1
- package/lib/repo-inspect.mjs +1 -0
- package/lib/reversible-context.mjs +49 -0
- package/lib/runtime-artifacts.mjs +42 -0
- package/lib/runtime-events.mjs +33 -1
- package/lib/safety.mjs +1 -0
- package/lib/semantic-index.mjs +1 -1
- package/lib/service-manager.mjs +526 -0
- package/lib/session-display.mjs +49 -0
- package/lib/task-engine.mjs +6 -10
- package/lib/task-graph.mjs +87 -0
- package/lib/task-policy.mjs +210 -8
- package/lib/trajectory.mjs +111 -1
- package/lib/turbo-fast-path.mjs +44 -0
- package/lib/workspace-fingerprint.mjs +2 -11
- package/lib/workspace-root.mjs +49 -0
- package/lib/worktree-sandbox.mjs +234 -11
- package/package.json +9 -6
- package/pi/extensions/ues-child-runtime.ts +300 -2
- package/pi/extensions/ues.ts +1887 -160
- package/scripts/check-release-consistency.mjs +25 -2
- package/scripts/check-source-integrity.mjs +532 -3
- package/scripts/eval-pi.mjs +76 -36
- package/scripts/smoke-pi-extension.mjs +1 -1
- package/pi/prompts/ues-run.md +0 -17
package/pi/extensions/ues.ts
CHANGED
|
@@ -7,15 +7,16 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { Type } from "typebox";
|
|
9
9
|
import { destructiveShellRisk } from "../../lib/safety.mjs";
|
|
10
|
-
import { classifyEngineeringTask, shouldRunDedicatedDiagnosis } from "../../lib/task-policy.mjs";
|
|
10
|
+
import { automaticUesAdmission, automaticUesContinuation, classifyEngineeringTask, deterministicReadOnlyGitCommands, shouldRunDedicatedDiagnosis } from "../../lib/task-policy.mjs";
|
|
11
11
|
import { resolveCapabilityModel } from "../../lib/model-policy.mjs";
|
|
12
12
|
import { readModelPolicy, recordModelPerformance } from "../../lib/model-config.mjs";
|
|
13
13
|
import { getUesConfigDir } from "../../lib/runtime-config.mjs";
|
|
14
14
|
import { buildAdaptiveTaskContext } from "../../lib/context-engine-v11.mjs";
|
|
15
15
|
import { recordVerifiedTaskMemory } from "../../lib/memory-engine.mjs";
|
|
16
|
-
import { computeSafeWaves, taskVerificationCommands, taskWriteFiles, validatePlan } from "../../lib/task-graph.mjs";
|
|
16
|
+
import { computeSafeWaves, normalizePlanForValidation, taskVerificationCommands, taskWriteFiles, validatePlan } from "../../lib/task-graph.mjs";
|
|
17
17
|
import { planDynamicWorkflow } from "../../lib/dynamic-workflow.mjs";
|
|
18
18
|
import { compactReversibleOutput } from "../../lib/performance-fabric.mjs";
|
|
19
|
+
import { gcEvidenceStore } from "../../lib/evidence-store.mjs";
|
|
19
20
|
import {
|
|
20
21
|
createToolOutputAccumulator,
|
|
21
22
|
detectHungToolEvidence,
|
|
@@ -24,10 +25,24 @@ import {
|
|
|
24
25
|
} from "../../lib/process-hang-detector.mjs";
|
|
25
26
|
import { runSupervisedProcess, terminateProcessTree } from "../../lib/process-supervisor.mjs";
|
|
26
27
|
import { PiRpcWorkerPool } from "../../lib/pi-rpc-pool.mjs";
|
|
28
|
+
import { resolvePiChildInvocation } from "../../lib/pi-child-invocation.mjs";
|
|
27
29
|
import { adaptiveContextBudget } from "../../lib/adaptive-context-budget.mjs";
|
|
28
30
|
import { clearSkillCompilerCache, compileSkillContext } from "../../lib/skill-compiler.mjs";
|
|
29
31
|
import { clearAffectedTestCache, resolveAffectedTests } from "../../lib/affected-tests.mjs";
|
|
30
32
|
import { findReusableVerification, listReusableVerification, recordVerification } from "../../lib/verification-broker.mjs";
|
|
33
|
+
import { evaluateFastVerificationGate } from "../../lib/fast-verification-gate.mjs";
|
|
34
|
+
import { turboFastPathDecision, turboFastTimeoutBudget } from "../../lib/turbo-fast-path.mjs";
|
|
35
|
+
import { failureDelta, leafTaskPolicy } from "../../lib/leaf-runtime-optimizer.mjs";
|
|
36
|
+
import { planningRuntimeBudget, shouldSoftSteerArchitect, shouldSoftSteerPlanningRole } from "../../lib/planning-speed-policy.mjs";
|
|
37
|
+
import { sourceFacingPaths, sourceGitPathspecs } from "../../lib/runtime-artifacts.mjs";
|
|
38
|
+
import { buildExecutionContract, buildFinalVerdictMatrix, captureInheritedDirtyState, detectInheritedDirtyViolations, enforcePhaseGates, executionContractPrompt, phaseArtifactPayloads, taskExplicitlyAllowsLocalEnvWrite } from "../../lib/execution-contract.mjs";
|
|
39
|
+
import { sessionNameFromUesInput, uesSessionName } from "../../lib/session-display.mjs";
|
|
40
|
+
import { requireGitWorkspaceRoot, resolveGitWorkspaceRoot } from "../../lib/workspace-root.mjs";
|
|
41
|
+
import { createAdaptiveDeadline } from "../../lib/activity-deadline.mjs";
|
|
42
|
+
import { extractValidatedPlan } from "../../lib/plan-salvage.mjs";
|
|
43
|
+
import { auditCompletion } from "../../lib/completion-auditor.mjs";
|
|
44
|
+
import { mcpExecutionPolicy } from "../../lib/mcp-tool-policy.mjs";
|
|
45
|
+
import { McpHealthTracker } from "../../lib/mcp-health.mjs";
|
|
31
46
|
import { runtimeWorkspaceFingerprint, runtimeWorkspaceSnapshot } from "../../lib/workspace-fingerprint.mjs";
|
|
32
47
|
import { appendTrajectoryEvent, createTraceID } from "../../lib/trajectory.mjs";
|
|
33
48
|
import {
|
|
@@ -43,14 +58,37 @@ import {
|
|
|
43
58
|
integrateTaskSandbox,
|
|
44
59
|
removeTaskSandbox,
|
|
45
60
|
rollbackTaskSandbox,
|
|
61
|
+
pruneOrphanTaskSandboxes,
|
|
62
|
+
taskSandboxOwnerRoot,
|
|
46
63
|
} from "../../lib/worktree-sandbox.mjs";
|
|
64
|
+
import {
|
|
65
|
+
looksLikeLongRunningServiceCommand,
|
|
66
|
+
restartService,
|
|
67
|
+
serviceLogs,
|
|
68
|
+
serviceStatus,
|
|
69
|
+
startService,
|
|
70
|
+
stopAllServices,
|
|
71
|
+
stopService,
|
|
72
|
+
waitForService,
|
|
73
|
+
} from "../../lib/service-manager.mjs";
|
|
47
74
|
|
|
48
75
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
76
|
+
const PACKAGE_VERSION = (() => {
|
|
77
|
+
try {
|
|
78
|
+
return String(JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf8"))?.version || "unknown");
|
|
79
|
+
} catch {
|
|
80
|
+
return "unknown";
|
|
81
|
+
}
|
|
82
|
+
})();
|
|
49
83
|
const OCSKILL_BIN = path.join(PACKAGE_ROOT, "bin", "ocskill.mjs");
|
|
50
84
|
const CHILD_RUNTIME_EXTENSION = path.join(PACKAGE_ROOT, "pi", "extensions", "ues-child-runtime.ts");
|
|
51
85
|
const AGENT_DIR = path.join(PACKAGE_ROOT, "global-config", "agents");
|
|
52
86
|
const MAX_PARALLEL_TASKS = 8;
|
|
53
87
|
const MAX_CONCURRENCY = 4;
|
|
88
|
+
const MAX_WRITER_CONCURRENCY = Math.max(
|
|
89
|
+
1,
|
|
90
|
+
Math.min(4, Number(process.env.UES_MAX_WRITER_CONCURRENCY || (process.platform === "win32" ? 2 : 3))),
|
|
91
|
+
);
|
|
54
92
|
const OUTPUT_LIMIT = 512 * 1024;
|
|
55
93
|
|
|
56
94
|
function configuredDuration(name: string, fallback: number, min: number, max: number) {
|
|
@@ -89,6 +127,12 @@ const POST_TOOL_ERROR_IDLE_TIMEOUT_MS = configuredDuration(
|
|
|
89
127
|
5_000,
|
|
90
128
|
5 * 60_000,
|
|
91
129
|
);
|
|
130
|
+
const TURBO_FAST_TIMEOUTS = turboFastTimeoutBudget({
|
|
131
|
+
hardTimeoutMs: configuredDuration("UES_FAST_CHILD_HARD_TIMEOUT_MS", 180_000, 30_000, 10 * 60_000),
|
|
132
|
+
idleTimeoutMs: configuredDuration("UES_FAST_CHILD_IDLE_TIMEOUT_MS", 60_000, 20_000, 5 * 60_000),
|
|
133
|
+
postToolErrorIdleTimeoutMs: configuredDuration("UES_FAST_POST_TOOL_ERROR_IDLE_TIMEOUT_MS", 30_000, 5_000, 2 * 60_000),
|
|
134
|
+
verificationTimeoutSec: configuredDuration("UES_FAST_VERIFICATION_TIMEOUT_SEC", 90, 30, 300),
|
|
135
|
+
});
|
|
92
136
|
const MODEL_VISIBLE_OUTPUT_LIMIT = configuredDuration(
|
|
93
137
|
"UES_MODEL_VISIBLE_OUTPUT_LIMIT",
|
|
94
138
|
64 * 1024,
|
|
@@ -135,6 +179,10 @@ const ACTIVE_CLI_CHILDREN = new Map<number, {
|
|
|
135
179
|
proc: any;
|
|
136
180
|
abort: () => boolean;
|
|
137
181
|
}>();
|
|
182
|
+
const MCP_HEALTH = new McpHealthTracker({
|
|
183
|
+
failureThreshold: 2,
|
|
184
|
+
cooldownMs: configuredDuration("UES_MCP_HEALTH_COOLDOWN_MS", 15_000, 1_000, 5 * 60_000),
|
|
185
|
+
});
|
|
138
186
|
|
|
139
187
|
function abortActiveCliChildren() {
|
|
140
188
|
let aborted = 0;
|
|
@@ -152,6 +200,7 @@ function abortActiveCliChildren() {
|
|
|
152
200
|
|
|
153
201
|
let HOST_BROWSER_TOOL_NAMES: string[] = [];
|
|
154
202
|
const CONTEXT_PACK_CACHE = new Map<string, any>();
|
|
203
|
+
const ACTIVE_TASK_SANDBOXES = new Map<string, string>();
|
|
155
204
|
|
|
156
205
|
function configuredBrowserToolNames() {
|
|
157
206
|
return String(process.env.UES_BROWSER_MCP_TOOL_NAMES || "")
|
|
@@ -165,7 +214,8 @@ function refreshHostBrowserToolNames(pi: ExtensionAPI) {
|
|
|
165
214
|
typeof (pi as any).getAllTools === "function"
|
|
166
215
|
? (pi as any).getAllTools()
|
|
167
216
|
: [];
|
|
168
|
-
|
|
217
|
+
const healthyTools = tools.filter((tool: any) => MCP_HEALTH.available(String(tool?.name || "")));
|
|
218
|
+
HOST_BROWSER_TOOL_NAMES = selectBrowserMcpToolNames(healthyTools.length ? healthyTools : tools, {
|
|
169
219
|
explicitNames: configuredBrowserToolNames(),
|
|
170
220
|
limit: BROWSER_MCP_TOOL_LIMIT,
|
|
171
221
|
});
|
|
@@ -247,6 +297,10 @@ function getAgentPrompt(agent: AgentName) {
|
|
|
247
297
|
`- If \`ues_cli\` is unavailable, invoke the bundled CLI as \`${fallbackCli} ...\`; do not assume a global \`ocskill\` binary exists.`,
|
|
248
298
|
"- Do not call OpenCode-only dispatch tools such as \`ues.dispatch_task\` or \`ues.dispatch_parallel\`.",
|
|
249
299
|
"- Respect the original role's edit/read-only boundary and return evidence to the parent Pi session.",
|
|
300
|
+
"- Prefer ues_code for bounded semantic/AST search, hash-anchored reads and optional LSP diagnostics. Writer roles may use ues_code_edit only after an anchored read; stale anchors must be re-read rather than fuzzily retried.",
|
|
301
|
+
"- Never launch a persistent dev server/watcher in foreground bash/powershell. Use ues_service start, wait-ready/status/logs, then stop; the runtime blocks common foreground-service commands to prevent hangs.",
|
|
302
|
+
"- On Windows, never pass /tmp or /var/tmp paths between Pi file tools and bash/powershell: their path namespaces may differ. Keep temporary transforms inside one shell pipeline, or use an ignored repository-local scratch path such as .ues-cache/tmp for cross-tool scratch.",
|
|
303
|
+
"- For PDF/DOCX/PPTX/XLSX evidence, ues_code action=document may use optional MarkItDown when installed; do not install it unless that capability is needed.",
|
|
250
304
|
"",
|
|
251
305
|
].join("\n");
|
|
252
306
|
const verdictContract =
|
|
@@ -261,16 +315,8 @@ function getAgentPrompt(agent: AgentName) {
|
|
|
261
315
|
}
|
|
262
316
|
|
|
263
317
|
function getPiInvocation(args: string[]): { command: string; args: string[] } {
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
if (currentScript && !isBunVirtualScript && fs.existsSync(currentScript)) {
|
|
267
|
-
return { command: process.execPath, args: [currentScript, ...args] };
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const execName = path.basename(process.execPath).toLowerCase();
|
|
271
|
-
const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName);
|
|
272
|
-
if (!isGenericRuntime) return { command: process.execPath, args };
|
|
273
|
-
return { command: "pi", args };
|
|
318
|
+
const invocation = resolvePiChildInvocation(args);
|
|
319
|
+
return { command: invocation.command, args: invocation.args };
|
|
274
320
|
}
|
|
275
321
|
|
|
276
322
|
function extractAssistantText(message: any): string {
|
|
@@ -334,6 +380,83 @@ async function runOcskillJson(args: string[], cwd: string, signal?: AbortSignal)
|
|
|
334
380
|
}
|
|
335
381
|
}
|
|
336
382
|
|
|
383
|
+
async function persistExecutionContractArtifacts(dir: string, contract: any) {
|
|
384
|
+
if (!contract || !dir) return null;
|
|
385
|
+
const phasesDir = path.join(dir, "phases");
|
|
386
|
+
await fs.promises.mkdir(phasesDir, { recursive: true });
|
|
387
|
+
await fs.promises.writeFile(
|
|
388
|
+
path.join(dir, "EXECUTION_CONTRACT.json"),
|
|
389
|
+
JSON.stringify(contract, null, 2) + "\n",
|
|
390
|
+
"utf8",
|
|
391
|
+
);
|
|
392
|
+
await fs.promises.writeFile(
|
|
393
|
+
path.join(phasesDir, "MANIFEST.json"),
|
|
394
|
+
JSON.stringify({
|
|
395
|
+
schemaVersion: 1,
|
|
396
|
+
taskHash: contract.taskHash,
|
|
397
|
+
phaseCount: contract.phases?.length || 0,
|
|
398
|
+
artifacts: phaseArtifactPayloads(contract).map((item: any) => item.file),
|
|
399
|
+
}, null, 2) + "\n",
|
|
400
|
+
"utf8",
|
|
401
|
+
);
|
|
402
|
+
for (const item of phaseArtifactPayloads(contract)) {
|
|
403
|
+
const target = path.join(dir, item.file);
|
|
404
|
+
await fs.promises.mkdir(path.dirname(target), { recursive: true });
|
|
405
|
+
await fs.promises.writeFile(target, JSON.stringify(item.value, null, 2) + "\n", "utf8");
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
contract: path.join(dir, "EXECUTION_CONTRACT.json"),
|
|
409
|
+
manifest: path.join(phasesDir, "MANIFEST.json"),
|
|
410
|
+
phaseCount: contract.phases?.length || 0,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function finalizeExecutionContractArtifacts(
|
|
415
|
+
dir: string,
|
|
416
|
+
contract: any,
|
|
417
|
+
verdictMatrix: any,
|
|
418
|
+
evidence: string,
|
|
419
|
+
) {
|
|
420
|
+
if (!dir || !contract) return null;
|
|
421
|
+
const payload = {
|
|
422
|
+
schemaVersion: 1,
|
|
423
|
+
verdictMatrix,
|
|
424
|
+
evidence: cap(evidence || "", 12000),
|
|
425
|
+
finalizedAt: new Date().toISOString(),
|
|
426
|
+
};
|
|
427
|
+
await fs.promises.writeFile(
|
|
428
|
+
path.join(dir, "FINAL_VERDICTS.json"),
|
|
429
|
+
JSON.stringify(payload, null, 2) + "\n",
|
|
430
|
+
"utf8",
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
for (const item of phaseArtifactPayloads(contract)) {
|
|
434
|
+
const phaseText = String(item.value.title || "") + "\n" + String(item.value.sourceBody || "");
|
|
435
|
+
let status = verdictMatrix?.source === "SOURCE_PASS" ? "VERIFIED" : "NOT_VERIFIED";
|
|
436
|
+
if (/device|expo go|thiết bị/i.test(phaseText) && verdictMatrix?.device !== "DEVICE_PASS") {
|
|
437
|
+
status = "NOT_VERIFIED";
|
|
438
|
+
}
|
|
439
|
+
if (/(?:cleanup|fixture|database|db|dữ liệu)/i.test(phaseText) && contract.gates?.dbClean === true && verdictMatrix?.dbClean !== "DB_CLEAN_PASS") {
|
|
440
|
+
status = "NOT_VERIFIED";
|
|
441
|
+
}
|
|
442
|
+
if (/(?:runtime|smoke|e2e|integration|typecheck|lint|test|verify|xác minh|kiểm tra)/i.test(phaseText) && contract.gates?.runtime === true && verdictMatrix?.runtime !== "RUNTIME_PASS") {
|
|
443
|
+
status = "NOT_VERIFIED";
|
|
444
|
+
}
|
|
445
|
+
const target = path.join(dir, item.file);
|
|
446
|
+
await fs.promises.writeFile(
|
|
447
|
+
target,
|
|
448
|
+
JSON.stringify({
|
|
449
|
+
...item.value,
|
|
450
|
+
status,
|
|
451
|
+
finalVerdicts: verdictMatrix,
|
|
452
|
+
evidence: [cap(evidence || "", 6000)],
|
|
453
|
+
}, null, 2) + "\n",
|
|
454
|
+
"utf8",
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
return payload;
|
|
458
|
+
}
|
|
459
|
+
|
|
337
460
|
async function initializeDurableControllerWork(
|
|
338
461
|
root: string,
|
|
339
462
|
originalTask: string,
|
|
@@ -529,9 +652,22 @@ async function runAgentCli(
|
|
|
529
652
|
compactToolOutput?: boolean;
|
|
530
653
|
toolOutputLimit?: number;
|
|
531
654
|
verificationTimeoutSec?: number;
|
|
655
|
+
hardTimeoutMs?: number;
|
|
656
|
+
absoluteHardTimeoutMs?: number;
|
|
657
|
+
activityExtensionMs?: number;
|
|
658
|
+
activityWindowMs?: number;
|
|
659
|
+
idleTimeoutMs?: number;
|
|
660
|
+
postToolErrorIdleTimeoutMs?: number;
|
|
661
|
+
allowLocalEnvWrite?: boolean;
|
|
532
662
|
} = {},
|
|
533
663
|
): Promise<RunResult> {
|
|
534
664
|
const config = AGENTS[agent];
|
|
665
|
+
const hardTimeoutMs = Number(runtimeOptions.hardTimeoutMs || CHILD_HARD_TIMEOUT_MS);
|
|
666
|
+
const absoluteHardTimeoutMs = Number(runtimeOptions.absoluteHardTimeoutMs || hardTimeoutMs);
|
|
667
|
+
const activityExtensionMs = Number(runtimeOptions.activityExtensionMs || 0);
|
|
668
|
+
const activityWindowMs = Number(runtimeOptions.activityWindowMs || Math.max(5_000, Math.min(activityExtensionMs || hardTimeoutMs, 30_000)));
|
|
669
|
+
const idleTimeoutMs = Number(runtimeOptions.idleTimeoutMs || CHILD_IDLE_TIMEOUT_MS);
|
|
670
|
+
const postToolErrorIdleTimeoutMs = Number(runtimeOptions.postToolErrorIdleTimeoutMs || POST_TOOL_ERROR_IDLE_TIMEOUT_MS);
|
|
535
671
|
const args: string[] = [
|
|
536
672
|
"--mode", "json", "-p", "--no-session",
|
|
537
673
|
// Keep extension discovery enabled so custom model providers (for example
|
|
@@ -542,8 +678,13 @@ async function runAgentCli(
|
|
|
542
678
|
];
|
|
543
679
|
if (model) args.push("--model", model);
|
|
544
680
|
if (thinkingLevel) args.push("--thinking", thinkingLevel);
|
|
681
|
+
const codeIntelligenceTools = WRITE_AGENTS.has(agent)
|
|
682
|
+
? ["ues_code", "ues_code_edit"]
|
|
683
|
+
: ["ues_code"];
|
|
545
684
|
const allowedTools = [...new Set([
|
|
546
685
|
...config.tools,
|
|
686
|
+
...codeIntelligenceTools,
|
|
687
|
+
"ues_service",
|
|
547
688
|
...extraTools,
|
|
548
689
|
...(runtimeOptions.compactToolOutput ? ["ues_evidence_get"] : []),
|
|
549
690
|
])];
|
|
@@ -573,9 +714,11 @@ async function runAgentCli(
|
|
|
573
714
|
env: {
|
|
574
715
|
...process.env,
|
|
575
716
|
UES_CHILD_PROCESS: "1",
|
|
717
|
+
UES_CHILD_AGENT: agent,
|
|
576
718
|
UES_CHILD_TOOL_COMPACTION: runtimeOptions.compactToolOutput ? "1" : "0",
|
|
577
719
|
UES_CHILD_TOOL_OUTPUT_LIMIT: String(runtimeOptions.toolOutputLimit || 24 * 1024),
|
|
578
720
|
UES_CHILD_VERIFICATION_TIMEOUT_SEC: String(runtimeOptions.verificationTimeoutSec || 300),
|
|
721
|
+
UES_CHILD_ALLOW_LOCAL_ENV_WRITE: runtimeOptions.allowLocalEnvWrite ? "1" : "0",
|
|
579
722
|
},
|
|
580
723
|
shell: false,
|
|
581
724
|
detached: process.platform !== "win32",
|
|
@@ -584,6 +727,12 @@ async function runAgentCli(
|
|
|
584
727
|
});
|
|
585
728
|
const childStartedAt = Date.now();
|
|
586
729
|
let lastActivityAt = childStartedAt;
|
|
730
|
+
const adaptiveDeadline = createAdaptiveDeadline({
|
|
731
|
+
hardTimeoutMs,
|
|
732
|
+
absoluteHardTimeoutMs,
|
|
733
|
+
activityExtensionMs,
|
|
734
|
+
activityWindowMs,
|
|
735
|
+
}, childStartedAt);
|
|
587
736
|
let buffer = "";
|
|
588
737
|
let settled = false;
|
|
589
738
|
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
@@ -707,19 +856,20 @@ async function runAgentCli(
|
|
|
707
856
|
heartbeatTimer.unref?.();
|
|
708
857
|
watchdogTimer = setInterval(() => {
|
|
709
858
|
const now = Date.now();
|
|
710
|
-
|
|
859
|
+
const deadline = adaptiveDeadline.shouldAbort(now, lastActivityAt);
|
|
860
|
+
if (deadline.abort) {
|
|
711
861
|
terminateForTimeout("hard");
|
|
712
862
|
return;
|
|
713
863
|
}
|
|
714
864
|
if (
|
|
715
865
|
lastToolErrorAt > 0 &&
|
|
716
|
-
now - lastToolErrorAt >=
|
|
717
|
-
now - lastActivityAt >=
|
|
866
|
+
now - lastToolErrorAt >= postToolErrorIdleTimeoutMs &&
|
|
867
|
+
now - lastActivityAt >= postToolErrorIdleTimeoutMs
|
|
718
868
|
) {
|
|
719
869
|
terminateForTimeout("post-tool-error");
|
|
720
870
|
return;
|
|
721
871
|
}
|
|
722
|
-
if (now - lastActivityAt >=
|
|
872
|
+
if (now - lastActivityAt >= idleTimeoutMs) {
|
|
723
873
|
terminateForTimeout("idle");
|
|
724
874
|
}
|
|
725
875
|
}, 1000);
|
|
@@ -772,8 +922,13 @@ async function runAgentCli(
|
|
|
772
922
|
lastToolErrorEvidence = "";
|
|
773
923
|
}
|
|
774
924
|
}
|
|
775
|
-
if (event.type === "message_update"
|
|
776
|
-
usage = event.usage;
|
|
925
|
+
if (event.type === "message_update") {
|
|
926
|
+
if (event.usage) usage = event.usage;
|
|
927
|
+
if (event.message?.role === "assistant") {
|
|
928
|
+
const partial = extractAssistantText(event.message);
|
|
929
|
+
if (partial) output = partial;
|
|
930
|
+
seenModel = event.message.model || seenModel;
|
|
931
|
+
}
|
|
777
932
|
}
|
|
778
933
|
if (event.type === "message_end" && event.message) {
|
|
779
934
|
const text = extractAssistantText(event.message);
|
|
@@ -882,9 +1037,22 @@ async function runAgentRpc(
|
|
|
882
1037
|
compactToolOutput?: boolean;
|
|
883
1038
|
toolOutputLimit?: number;
|
|
884
1039
|
verificationTimeoutSec?: number;
|
|
1040
|
+
hardTimeoutMs?: number;
|
|
1041
|
+
absoluteHardTimeoutMs?: number;
|
|
1042
|
+
activityExtensionMs?: number;
|
|
1043
|
+
activityWindowMs?: number;
|
|
1044
|
+
idleTimeoutMs?: number;
|
|
1045
|
+
postToolErrorIdleTimeoutMs?: number;
|
|
1046
|
+
allowLocalEnvWrite?: boolean;
|
|
885
1047
|
} = {},
|
|
886
1048
|
): Promise<RunResult> {
|
|
887
1049
|
const config = AGENTS[agent];
|
|
1050
|
+
const hardTimeoutMs = Number(runtimeOptions.hardTimeoutMs || CHILD_HARD_TIMEOUT_MS);
|
|
1051
|
+
const absoluteHardTimeoutMs = Number(runtimeOptions.absoluteHardTimeoutMs || hardTimeoutMs);
|
|
1052
|
+
const activityExtensionMs = Number(runtimeOptions.activityExtensionMs || 0);
|
|
1053
|
+
const activityWindowMs = Number(runtimeOptions.activityWindowMs || Math.max(5_000, Math.min(activityExtensionMs || hardTimeoutMs, 30_000)));
|
|
1054
|
+
const idleTimeoutMs = Number(runtimeOptions.idleTimeoutMs || CHILD_IDLE_TIMEOUT_MS);
|
|
1055
|
+
const postToolErrorIdleTimeoutMs = Number(runtimeOptions.postToolErrorIdleTimeoutMs || POST_TOOL_ERROR_IDLE_TIMEOUT_MS);
|
|
888
1056
|
const args: string[] = [
|
|
889
1057
|
"--mode", "rpc", "--no-session",
|
|
890
1058
|
"--no-skills", "--no-prompt-templates", "--no-context-files",
|
|
@@ -892,8 +1060,13 @@ async function runAgentRpc(
|
|
|
892
1060
|
];
|
|
893
1061
|
if (model) args.push("--model", model);
|
|
894
1062
|
if (thinkingLevel) args.push("--thinking", thinkingLevel);
|
|
1063
|
+
const codeIntelligenceTools = WRITE_AGENTS.has(agent)
|
|
1064
|
+
? ["ues_code", "ues_code_edit"]
|
|
1065
|
+
: ["ues_code"];
|
|
895
1066
|
const allowedTools = [...new Set([
|
|
896
1067
|
...config.tools,
|
|
1068
|
+
...codeIntelligenceTools,
|
|
1069
|
+
"ues_service",
|
|
897
1070
|
...extraTools,
|
|
898
1071
|
...(runtimeOptions.compactToolOutput ? ["ues_evidence_get"] : []),
|
|
899
1072
|
])];
|
|
@@ -909,6 +1082,7 @@ async function runAgentRpc(
|
|
|
909
1082
|
Boolean(runtimeOptions.compactToolOutput),
|
|
910
1083
|
Number(runtimeOptions.toolOutputLimit || 0),
|
|
911
1084
|
Number(runtimeOptions.verificationTimeoutSec || 0),
|
|
1085
|
+
Boolean(runtimeOptions.allowLocalEnvWrite),
|
|
912
1086
|
]);
|
|
913
1087
|
const taskInput = `Task: ${task}\n`;
|
|
914
1088
|
const startedAt = Date.now();
|
|
@@ -945,17 +1119,22 @@ async function runAgentRpc(
|
|
|
945
1119
|
env: {
|
|
946
1120
|
...process.env,
|
|
947
1121
|
UES_CHILD_PROCESS: "1",
|
|
1122
|
+
UES_CHILD_AGENT: agent,
|
|
948
1123
|
UES_CHILD_TOOL_COMPACTION: runtimeOptions.compactToolOutput ? "1" : "0",
|
|
949
1124
|
UES_CHILD_TOOL_OUTPUT_LIMIT: String(runtimeOptions.toolOutputLimit || 24 * 1024),
|
|
950
1125
|
UES_CHILD_VERIFICATION_TIMEOUT_SEC: String(runtimeOptions.verificationTimeoutSec || 300),
|
|
1126
|
+
UES_CHILD_ALLOW_LOCAL_ENV_WRITE: runtimeOptions.allowLocalEnvWrite ? "1" : "0",
|
|
951
1127
|
},
|
|
952
1128
|
},
|
|
953
1129
|
taskInput,
|
|
954
1130
|
{
|
|
955
1131
|
signal,
|
|
956
|
-
hardTimeoutMs
|
|
957
|
-
|
|
958
|
-
|
|
1132
|
+
hardTimeoutMs,
|
|
1133
|
+
absoluteHardTimeoutMs,
|
|
1134
|
+
activityExtensionMs,
|
|
1135
|
+
activityWindowMs,
|
|
1136
|
+
idleTimeoutMs,
|
|
1137
|
+
postToolErrorIdleTimeoutMs,
|
|
959
1138
|
onEvent: (event: any) => {
|
|
960
1139
|
lastActivityAt = Date.now();
|
|
961
1140
|
if (event.type === "tool_execution_start") {
|
|
@@ -1057,12 +1236,16 @@ async function runAgentRpc(
|
|
|
1057
1236
|
if ((error as any)?.uesRpcPhase === "runtime") {
|
|
1058
1237
|
const timeout = /hard-timeout|idle-timeout/i.test(message);
|
|
1059
1238
|
const toolStall = /post-tool-error-stall/i.test(message);
|
|
1239
|
+
const partialOutput = extractAssistantText((error as any)?.partialMessage);
|
|
1240
|
+
const recoveredOutput = partialOutput
|
|
1241
|
+
? partialOutput + "\n\n[UES transport note: " + message + "]"
|
|
1242
|
+
: message;
|
|
1060
1243
|
return {
|
|
1061
1244
|
agent,
|
|
1062
1245
|
task,
|
|
1063
1246
|
cwd,
|
|
1064
1247
|
exitCode: timeout ? 124 : toolStall ? 125 : 1,
|
|
1065
|
-
output:
|
|
1248
|
+
output: recoveredOutput,
|
|
1066
1249
|
stderr: message,
|
|
1067
1250
|
model,
|
|
1068
1251
|
stopReason: timeout ? "timeout" : toolStall ? "tool-error-stall" : "rpc-runtime-error",
|
|
@@ -1094,6 +1277,13 @@ async function runAgent(
|
|
|
1094
1277
|
compactToolOutput?: boolean;
|
|
1095
1278
|
toolOutputLimit?: number;
|
|
1096
1279
|
verificationTimeoutSec?: number;
|
|
1280
|
+
hardTimeoutMs?: number;
|
|
1281
|
+
absoluteHardTimeoutMs?: number;
|
|
1282
|
+
activityExtensionMs?: number;
|
|
1283
|
+
activityWindowMs?: number;
|
|
1284
|
+
idleTimeoutMs?: number;
|
|
1285
|
+
postToolErrorIdleTimeoutMs?: number;
|
|
1286
|
+
allowLocalEnvWrite?: boolean;
|
|
1097
1287
|
} = {},
|
|
1098
1288
|
): Promise<RunResult> {
|
|
1099
1289
|
if (CHILD_RUNTIME !== "cli") {
|
|
@@ -1236,9 +1426,37 @@ function cachedContextKey(
|
|
|
1236
1426
|
role: string,
|
|
1237
1427
|
budget: number,
|
|
1238
1428
|
fingerprint = "unknown",
|
|
1429
|
+
namespace = cwd,
|
|
1239
1430
|
) {
|
|
1240
1431
|
const auxStamp = runtimeContextAuxStamp(cwd);
|
|
1241
|
-
return [
|
|
1432
|
+
return [path.resolve(namespace), fingerprint, auxStamp, role, String(budget), task].join("\u0000");
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function deepExplorationContract(role: string) {
|
|
1436
|
+
const common = [
|
|
1437
|
+
"## UES DEEP bounded exploration",
|
|
1438
|
+
"Quality gates remain unchanged. Reduce latency by avoiding duplicate discovery, not by skipping evidence.",
|
|
1439
|
+
"Use the supplied runtime context pack, hierarchy, ranked references and declared files before broad repository exploration.",
|
|
1440
|
+
"Prefer targeted ues_code/direct reads. Do not inventory the repository or repeat unchanged reads unless a named evidence gap requires it.",
|
|
1441
|
+
];
|
|
1442
|
+
if (role === "architect") {
|
|
1443
|
+
common.push(
|
|
1444
|
+
"Stop exploring once every planned task has exact repository-relative scope, dependency ordering, observable acceptance, concrete verification, and risk/rollback coverage.",
|
|
1445
|
+
);
|
|
1446
|
+
} else if (role === "plan-checker") {
|
|
1447
|
+
common.push(
|
|
1448
|
+
"Inspect only disputed or unproven plan properties; once each blocking property is proven or rejected, return PASS/REVISE immediately.",
|
|
1449
|
+
);
|
|
1450
|
+
} else if (role === "executor") {
|
|
1451
|
+
common.push(
|
|
1452
|
+
"Start with declared task files and direct consumers/tests only. Once the safe minimal edit is supported by evidence, implement and spend remaining effort on fresh verification rather than more discovery.",
|
|
1453
|
+
);
|
|
1454
|
+
} else if (role === "debugger") {
|
|
1455
|
+
common.push(
|
|
1456
|
+
"Investigate only the concrete failure path and nearest evidence needed to reject/confirm the active hypothesis; avoid general repository surveys.",
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
return common.join("\n");
|
|
1242
1460
|
}
|
|
1243
1461
|
|
|
1244
1462
|
function compactContextPack(pack: any, recentFailure?: string) {
|
|
@@ -1289,13 +1507,23 @@ async function runRoutedAgent(
|
|
|
1289
1507
|
signal?: AbortSignal,
|
|
1290
1508
|
onProgress?: Parameters<typeof runAgent>[6],
|
|
1291
1509
|
traceID?: string,
|
|
1510
|
+
taskPolicyOverride?: any,
|
|
1511
|
+
contextCacheNamespace?: string,
|
|
1292
1512
|
): Promise<RunResult> {
|
|
1293
1513
|
const role = roleForAgent(agent);
|
|
1514
|
+
const traceRoot = requireGitWorkspaceRoot(cwd, "UES specialist");
|
|
1294
1515
|
const browserRequested = browserEvidenceNeeded(task, role);
|
|
1295
1516
|
const browserTools = browserRequested
|
|
1296
1517
|
? selectBrowserToolsForTask(HOST_BROWSER_TOOL_NAMES, task, role)
|
|
1297
1518
|
: [];
|
|
1298
|
-
const taskPolicy = classifyEngineeringTask(task);
|
|
1519
|
+
const taskPolicy = taskPolicyOverride || classifyEngineeringTask(task);
|
|
1520
|
+
const turboFast = turboFastPathDecision(taskPolicy, {
|
|
1521
|
+
role,
|
|
1522
|
+
attempt,
|
|
1523
|
+
browserRequested,
|
|
1524
|
+
visualRequired: visualEvidenceNeeded(task),
|
|
1525
|
+
});
|
|
1526
|
+
const fastBoundedContext = turboFast.eligible;
|
|
1299
1527
|
const budgetDecision = adaptiveContextBudget(taskPolicy, role, attempt, {
|
|
1300
1528
|
disabled: !ADAPTIVE_CONTEXT_ENABLED,
|
|
1301
1529
|
});
|
|
@@ -1310,7 +1538,7 @@ async function runRoutedAgent(
|
|
|
1310
1538
|
const modelPolicy = await readModelPolicy(getUesConfigDir());
|
|
1311
1539
|
const selection = resolveCapabilityModel(role, attempt, task, taskPolicy, modelPolicy);
|
|
1312
1540
|
if (traceID) {
|
|
1313
|
-
await appendTrajectoryEvent(
|
|
1541
|
+
await appendTrajectoryEvent(traceRoot, traceID, "agent.started", {
|
|
1314
1542
|
agent,
|
|
1315
1543
|
role,
|
|
1316
1544
|
attempt,
|
|
@@ -1363,12 +1591,29 @@ async function runRoutedAgent(
|
|
|
1363
1591
|
let reusableVerification: any = null;
|
|
1364
1592
|
let contextCacheHit = false;
|
|
1365
1593
|
try {
|
|
1594
|
+
if (fastBoundedContext) {
|
|
1595
|
+
contextQuality = { schemaVersion: 1, profile: "fast-bounded", bounded: true };
|
|
1596
|
+
enrichedTask = [
|
|
1597
|
+
task,
|
|
1598
|
+
"",
|
|
1599
|
+
"## UES FAST bounded lane",
|
|
1600
|
+
"This is a low-risk single-file task. Read the named target file first and avoid broad repository scans unless direct evidence shows the task is wider than declared.",
|
|
1601
|
+
role === "executor"
|
|
1602
|
+
? "Implement every explicit acceptance branch, then run the narrowest behavioral check available. When the repository has no suitable JS/TS test, prefer a temporary .ues-cache/fast-acceptance.test.mjs probe and run `node --test .ues-cache/fast-acceptance.test.mjs`; cover every enumerated edge/error/idempotency/non-mutation requirement and remove only the temporary probe afterwards."
|
|
1603
|
+
: "Verify every explicit acceptance clause independently. Read the final diff/target file, then run a narrow behavioral check. Compilation or syntax alone is not proof. If any enumerated edge/error/idempotency/non-mutation case lacks fresh executable evidence, return FAIL.",
|
|
1604
|
+
].join("\n");
|
|
1605
|
+
} else {
|
|
1606
|
+
const cacheBudgetKey =
|
|
1607
|
+
["architect", "plan-checker"].includes(role)
|
|
1608
|
+
? Number(budgetDecision.baseBudget || budgetDecision.budget)
|
|
1609
|
+
: budgetDecision.budget;
|
|
1366
1610
|
const cacheKey = cachedContextKey(
|
|
1367
1611
|
cwd,
|
|
1368
1612
|
task,
|
|
1369
1613
|
role,
|
|
1370
|
-
|
|
1614
|
+
cacheBudgetKey,
|
|
1371
1615
|
workspaceFingerprint,
|
|
1616
|
+
contextCacheNamespace || cwd,
|
|
1372
1617
|
);
|
|
1373
1618
|
const cacheableContext =
|
|
1374
1619
|
workspaceState.cacheable === true &&
|
|
@@ -1397,8 +1642,9 @@ async function runRoutedAgent(
|
|
|
1397
1642
|
cwd,
|
|
1398
1643
|
task,
|
|
1399
1644
|
role,
|
|
1400
|
-
|
|
1645
|
+
cacheBudgetKey,
|
|
1401
1646
|
workspaceFingerprint,
|
|
1647
|
+
contextCacheNamespace || cwd,
|
|
1402
1648
|
);
|
|
1403
1649
|
if (postBuildKey !== cacheKey) rememberContextPack(postBuildKey, pack);
|
|
1404
1650
|
}
|
|
@@ -1465,7 +1711,12 @@ async function runRoutedAgent(
|
|
|
1465
1711
|
row.stdoutPreview ? ` preview: ${cap(String(row.stdoutPreview).replace(/\s+/g, " "), 900)}` : "",
|
|
1466
1712
|
].filter(Boolean).join("\n")).join("\n")
|
|
1467
1713
|
: "",
|
|
1714
|
+
taskPolicy.executionProfile === "deep" &&
|
|
1715
|
+
["architect", "plan-checker", "executor", "debugger"].includes(role)
|
|
1716
|
+
? "\n" + deepExplorationContract(role)
|
|
1717
|
+
: "",
|
|
1468
1718
|
].filter(Boolean).join("\n");
|
|
1719
|
+
}
|
|
1469
1720
|
} catch (error) {
|
|
1470
1721
|
contextError = error instanceof Error ? error.message : String(error);
|
|
1471
1722
|
if (recentFailure) {
|
|
@@ -1490,6 +1741,11 @@ async function runRoutedAgent(
|
|
|
1490
1741
|
].join("\n");
|
|
1491
1742
|
}
|
|
1492
1743
|
|
|
1744
|
+
const planningBudget = planningRuntimeBudget(role, attempt, {
|
|
1745
|
+
executionProfile: taskPolicy.executionProfile,
|
|
1746
|
+
risk: taskPolicy.risk,
|
|
1747
|
+
taskChars: task.length,
|
|
1748
|
+
});
|
|
1493
1749
|
const startedAt = Date.now();
|
|
1494
1750
|
const result = await runAgent(
|
|
1495
1751
|
agent,
|
|
@@ -1511,13 +1767,40 @@ async function runRoutedAgent(
|
|
|
1511
1767
|
? 24 * 1024
|
|
1512
1768
|
: 48 * 1024,
|
|
1513
1769
|
verificationTimeoutSec:
|
|
1514
|
-
|
|
1515
|
-
?
|
|
1516
|
-
: taskPolicy.
|
|
1517
|
-
?
|
|
1518
|
-
: taskPolicy.executionProfile === "
|
|
1519
|
-
?
|
|
1520
|
-
:
|
|
1770
|
+
turboFast.eligible
|
|
1771
|
+
? TURBO_FAST_TIMEOUTS.verificationTimeoutSec
|
|
1772
|
+
: taskPolicy.risk === "high"
|
|
1773
|
+
? 900
|
|
1774
|
+
: taskPolicy.executionProfile === "fast"
|
|
1775
|
+
? 120
|
|
1776
|
+
: taskPolicy.executionProfile === "standard"
|
|
1777
|
+
? 300
|
|
1778
|
+
: 600,
|
|
1779
|
+
hardTimeoutMs:
|
|
1780
|
+
turboFast.eligible
|
|
1781
|
+
? TURBO_FAST_TIMEOUTS.hardTimeoutMs
|
|
1782
|
+
: planningBudget?.hardTimeoutMs || CHILD_HARD_TIMEOUT_MS,
|
|
1783
|
+
absoluteHardTimeoutMs:
|
|
1784
|
+
turboFast.eligible
|
|
1785
|
+
? TURBO_FAST_TIMEOUTS.hardTimeoutMs
|
|
1786
|
+
: planningBudget?.absoluteHardTimeoutMs || CHILD_HARD_TIMEOUT_MS,
|
|
1787
|
+
activityExtensionMs:
|
|
1788
|
+
turboFast.eligible ? 0 : planningBudget?.activityExtensionMs || 0,
|
|
1789
|
+
activityWindowMs:
|
|
1790
|
+
turboFast.eligible ? 0 : planningBudget?.activityWindowMs || 0,
|
|
1791
|
+
idleTimeoutMs:
|
|
1792
|
+
turboFast.eligible
|
|
1793
|
+
? TURBO_FAST_TIMEOUTS.idleTimeoutMs
|
|
1794
|
+
: planningBudget?.idleTimeoutMs || CHILD_IDLE_TIMEOUT_MS,
|
|
1795
|
+
postToolErrorIdleTimeoutMs:
|
|
1796
|
+
turboFast.eligible
|
|
1797
|
+
? TURBO_FAST_TIMEOUTS.postToolErrorIdleTimeoutMs
|
|
1798
|
+
: planningBudget?.idleTimeoutMs
|
|
1799
|
+
? Math.min(POST_TOOL_ERROR_IDLE_TIMEOUT_MS, planningBudget.idleTimeoutMs)
|
|
1800
|
+
: POST_TOOL_ERROR_IDLE_TIMEOUT_MS,
|
|
1801
|
+
allowLocalEnvWrite:
|
|
1802
|
+
taskPolicy.localEnvWriteExplicitlyAllowed === true ||
|
|
1803
|
+
taskExplicitlyAllowsLocalEnvWrite(task),
|
|
1521
1804
|
},
|
|
1522
1805
|
);
|
|
1523
1806
|
const enrichedResult: RunResult = {
|
|
@@ -1546,13 +1829,17 @@ async function runRoutedAgent(
|
|
|
1546
1829
|
!["high", "critical"].includes(String(taskPolicy.risk || "").toLowerCase()),
|
|
1547
1830
|
runtimeContextBudget: budgetDecision.budget,
|
|
1548
1831
|
baseContextBudget: budgetDecision.baseBudget,
|
|
1832
|
+
turboFastPath: turboFast.eligible,
|
|
1833
|
+
turboFastStrategy: turboFast.strategy,
|
|
1834
|
+
turboFastTimeouts: turboFast.eligible ? TURBO_FAST_TIMEOUTS : null,
|
|
1835
|
+
planningRuntimeBudget: planningBudget,
|
|
1549
1836
|
},
|
|
1550
1837
|
verdict: verdictFromOutput(result.output),
|
|
1551
1838
|
report: parseStructuredReport(result.output),
|
|
1552
1839
|
durationMs: Date.now() - startedAt,
|
|
1553
1840
|
};
|
|
1554
1841
|
if (traceID) {
|
|
1555
|
-
await appendTrajectoryEvent(
|
|
1842
|
+
await appendTrajectoryEvent(traceRoot, traceID, "agent.completed", {
|
|
1556
1843
|
agent,
|
|
1557
1844
|
role,
|
|
1558
1845
|
attempt,
|
|
@@ -1648,19 +1935,64 @@ function extractMarkedJson(output: string, marker: string) {
|
|
|
1648
1935
|
return null;
|
|
1649
1936
|
}
|
|
1650
1937
|
|
|
1651
|
-
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
1938
|
+
function sandboxSourceIntentBatches(values: string[]) {
|
|
1939
|
+
const batches: string[][] = [];
|
|
1940
|
+
let batch: string[] = [];
|
|
1941
|
+
let chars = 0;
|
|
1942
|
+
for (const value of sourceFacingPaths(values)) {
|
|
1943
|
+
const cost = value.length + 3;
|
|
1944
|
+
if (batch.length && (batch.length >= 64 || chars + cost > 12_000)) {
|
|
1945
|
+
batches.push(batch);
|
|
1946
|
+
batch = [];
|
|
1947
|
+
chars = 0;
|
|
1948
|
+
}
|
|
1949
|
+
batch.push(value);
|
|
1950
|
+
chars += cost;
|
|
1951
|
+
}
|
|
1952
|
+
if (batch.length) batches.push(batch);
|
|
1953
|
+
return batches;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
async function sandboxIntentToAddSourceFiles(dir: string, signal?: AbortSignal) {
|
|
1957
|
+
const pathspecs = sourceGitPathspecs();
|
|
1958
|
+
const untracked = await runProcess(
|
|
1959
|
+
"git",
|
|
1960
|
+
["ls-files", "--others", "--exclude-standard", "-z", "--", ...pathspecs],
|
|
1961
|
+
dir,
|
|
1962
|
+
signal,
|
|
1963
|
+
);
|
|
1964
|
+
if (untracked.exitCode !== 0) {
|
|
1965
|
+
throw new Error(untracked.stderr || untracked.stdout || "git ls-files for intent-to-add failed");
|
|
1655
1966
|
}
|
|
1656
|
-
|
|
1967
|
+
|
|
1968
|
+
const candidates = sourceFacingPaths(
|
|
1969
|
+
untracked.stdout
|
|
1970
|
+
.split("\0")
|
|
1971
|
+
.map((value) => value.replaceAll("\\", "/"))
|
|
1972
|
+
.filter(Boolean),
|
|
1973
|
+
);
|
|
1974
|
+
for (const batch of sandboxSourceIntentBatches(candidates)) {
|
|
1975
|
+
const intent = await runProcess("git", ["add", "-N", "--", ...batch], dir, signal);
|
|
1976
|
+
if (intent.exitCode !== 0) {
|
|
1977
|
+
throw new Error(intent.stderr || intent.stdout || "git add -N failed");
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
return candidates;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
async function sandboxChangedFiles(dir: string, base: string, signal?: AbortSignal) {
|
|
1984
|
+
const pathspecs = sourceGitPathspecs();
|
|
1985
|
+
await sandboxIntentToAddSourceFiles(dir, signal);
|
|
1986
|
+
const diff = await runProcess("git", ["diff", "--name-only", base, "--", ...pathspecs], dir, signal);
|
|
1657
1987
|
if (diff.exitCode !== 0) {
|
|
1658
1988
|
throw new Error(diff.stderr || diff.stdout || "git diff --name-only failed");
|
|
1659
1989
|
}
|
|
1660
|
-
return
|
|
1661
|
-
.
|
|
1662
|
-
|
|
1663
|
-
|
|
1990
|
+
return sourceFacingPaths(
|
|
1991
|
+
diff.stdout
|
|
1992
|
+
.split(/\r?\n/)
|
|
1993
|
+
.map((value) => value.trim().replaceAll("\\", "/"))
|
|
1994
|
+
.filter(Boolean),
|
|
1995
|
+
);
|
|
1664
1996
|
}
|
|
1665
1997
|
|
|
1666
1998
|
async function cleanupSandboxes(
|
|
@@ -1669,11 +2001,30 @@ async function cleanupSandboxes(
|
|
|
1669
2001
|
) {
|
|
1670
2002
|
for (const item of prepared) {
|
|
1671
2003
|
if (!item.sandbox?.dir) continue;
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
2004
|
+
const resolved = path.resolve(item.sandbox.dir);
|
|
2005
|
+
try {
|
|
2006
|
+
await removeTaskSandbox(root, resolved, {
|
|
2007
|
+
force: true,
|
|
2008
|
+
deleteBranch: true,
|
|
2009
|
+
});
|
|
2010
|
+
ACTIVE_TASK_SANDBOXES.delete(resolved);
|
|
2011
|
+
} catch {}
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
async function cleanupTraceSandboxes(root: string, traceID: string) {
|
|
2016
|
+
const targets = [...ACTIVE_TASK_SANDBOXES.entries()]
|
|
2017
|
+
.filter(([, ownerTrace]) => ownerTrace === traceID)
|
|
2018
|
+
.map(([dir]) => dir);
|
|
2019
|
+
let removed = 0;
|
|
2020
|
+
for (const dir of targets) {
|
|
2021
|
+
try {
|
|
2022
|
+
await removeTaskSandbox(root, dir, { force: true, deleteBranch: true });
|
|
2023
|
+
ACTIVE_TASK_SANDBOXES.delete(dir);
|
|
2024
|
+
removed += 1;
|
|
2025
|
+
} catch {}
|
|
1676
2026
|
}
|
|
2027
|
+
return removed;
|
|
1677
2028
|
}
|
|
1678
2029
|
|
|
1679
2030
|
async function executeStructuredPlan(input: {
|
|
@@ -1682,6 +2033,7 @@ async function executeStructuredPlan(input: {
|
|
|
1682
2033
|
inheritedModel?: string;
|
|
1683
2034
|
inheritedThinking?: string;
|
|
1684
2035
|
maxAttempts: number;
|
|
2036
|
+
rootPolicy?: any;
|
|
1685
2037
|
durableSlug?: string;
|
|
1686
2038
|
traceID?: string;
|
|
1687
2039
|
signal?: AbortSignal;
|
|
@@ -1754,6 +2106,7 @@ async function executeStructuredPlan(input: {
|
|
|
1754
2106
|
for (let waveIndex = 0; waveIndex < safe.waves.length; waveIndex++) {
|
|
1755
2107
|
const ids = safe.waves[waveIndex];
|
|
1756
2108
|
let lastWaveFailure = "";
|
|
2109
|
+
const failureByTask = new Map<string, string>();
|
|
1757
2110
|
|
|
1758
2111
|
for (let attempt = 1; attempt <= input.maxAttempts; attempt++) {
|
|
1759
2112
|
const prepared: Array<{
|
|
@@ -1772,12 +2125,13 @@ async function executeStructuredPlan(input: {
|
|
|
1772
2125
|
let cwd = input.root;
|
|
1773
2126
|
let sandbox: any = undefined;
|
|
1774
2127
|
|
|
1775
|
-
if (gitCapable) {
|
|
2128
|
+
if (gitCapable && writeFiles.length > 0) {
|
|
1776
2129
|
const slug = "runtime-" + randomUUID().slice(0, 8) + "-w" + waveIndex + "-a" + attempt;
|
|
1777
2130
|
sandbox = await createTaskSandbox(input.root, slug, id, {
|
|
1778
2131
|
inheritDirtyRoot: true,
|
|
1779
2132
|
});
|
|
1780
2133
|
cwd = sandbox.dir;
|
|
2134
|
+
ACTIVE_TASK_SANDBOXES.set(path.resolve(sandbox.dir), String(input.traceID || "structured"));
|
|
1781
2135
|
}
|
|
1782
2136
|
|
|
1783
2137
|
prepared.push({ task, cwd, sandbox, writeFiles });
|
|
@@ -1795,22 +2149,37 @@ async function executeStructuredPlan(input: {
|
|
|
1795
2149
|
}
|
|
1796
2150
|
|
|
1797
2151
|
let completed = 0;
|
|
2152
|
+
const writerCount = prepared.filter((item) => item.writeFiles.length > 0).length;
|
|
2153
|
+
const waveConcurrency = !gitCapable
|
|
2154
|
+
? 1
|
|
2155
|
+
: writerCount > 0
|
|
2156
|
+
? Math.min(MAX_CONCURRENCY, MAX_WRITER_CONCURRENCY)
|
|
2157
|
+
: MAX_CONCURRENCY;
|
|
1798
2158
|
const waveResults = await mapLimit(
|
|
1799
2159
|
prepared,
|
|
1800
|
-
|
|
2160
|
+
waveConcurrency,
|
|
1801
2161
|
async (item) => {
|
|
1802
2162
|
const taskText = [
|
|
1803
2163
|
"Execute exactly this structured plan task.",
|
|
1804
2164
|
"Do not broaden file scope. If the declared write file list is empty, do not edit files.",
|
|
2165
|
+
"Start with the declared files/interfaces and supplied context. Do not inventory the repository. Use targeted symbol/path search only for a concrete unresolved acceptance or dependency gap; once the safe edit is understood, implement and verify instead of continuing discovery.",
|
|
1805
2166
|
"",
|
|
1806
2167
|
JSON.stringify(item.task, null, 2),
|
|
1807
2168
|
"",
|
|
1808
|
-
"
|
|
1809
|
-
String(input.plan.goal || ""),
|
|
1810
|
-
|
|
1811
|
-
? "\nFresh failure evidence from the previous wave attempt:\n" + cap(lastWaveFailure, 7000)
|
|
1812
|
-
: "",
|
|
2169
|
+
"Parent goal (context only; never broaden this leaf task):",
|
|
2170
|
+
cap(String(input.plan.goal || ""), 700),
|
|
2171
|
+
input.rootPolicy?.executionContractPrompt || "",
|
|
1813
2172
|
].filter(Boolean).join("\n");
|
|
2173
|
+
const leafPolicy = leafTaskPolicy(item.task, input.rootPolicy || {});
|
|
2174
|
+
const taskFailure = failureByTask.get(String(item.task.id)) || "";
|
|
2175
|
+
const leafVisualRequired = visualEvidenceNeeded(taskText);
|
|
2176
|
+
const leafFastDecision = turboFastPathDecision(leafPolicy, {
|
|
2177
|
+
role: "executor",
|
|
2178
|
+
attempt,
|
|
2179
|
+
browserRequested: browserEvidenceNeeded(taskText, "executor"),
|
|
2180
|
+
visualRequired: leafVisualRequired,
|
|
2181
|
+
});
|
|
2182
|
+
const leafAttemptStartedAt = Date.now();
|
|
1814
2183
|
|
|
1815
2184
|
const plannedExecution: any = dynamicTaskByID.get(item.task.id);
|
|
1816
2185
|
const deterministicReadOnly =
|
|
@@ -1943,8 +2312,8 @@ async function executeStructuredPlan(input: {
|
|
|
1943
2312
|
"",
|
|
1944
2313
|
"Overall goal:",
|
|
1945
2314
|
String(input.plan.goal || ""),
|
|
1946
|
-
|
|
1947
|
-
? "\nFresh failure
|
|
2315
|
+
taskFailure
|
|
2316
|
+
? "\nFresh task-local failure delta from the previous attempt:\n" + cap(taskFailure, 4200)
|
|
1948
2317
|
: "",
|
|
1949
2318
|
].filter(Boolean).join("\n"),
|
|
1950
2319
|
item.cwd,
|
|
@@ -1955,6 +2324,8 @@ async function executeStructuredPlan(input: {
|
|
|
1955
2324
|
input.signal,
|
|
1956
2325
|
undefined,
|
|
1957
2326
|
input.traceID,
|
|
2327
|
+
leafPolicy,
|
|
2328
|
+
input.root,
|
|
1958
2329
|
);
|
|
1959
2330
|
results.push({
|
|
1960
2331
|
wave: waveIndex,
|
|
@@ -1992,8 +2363,8 @@ async function executeStructuredPlan(input: {
|
|
|
1992
2363
|
};
|
|
1993
2364
|
}
|
|
1994
2365
|
|
|
1995
|
-
let focusedFailure =
|
|
1996
|
-
if (attempt > 1 && runtimeFailureNeedsDiagnosis(
|
|
2366
|
+
let focusedFailure = taskFailure;
|
|
2367
|
+
if (attempt > 1 && runtimeFailureNeedsDiagnosis(taskFailure)) {
|
|
1997
2368
|
const diagnosis = await runRoutedAgent(
|
|
1998
2369
|
"ues-debugger",
|
|
1999
2370
|
[
|
|
@@ -2003,16 +2374,18 @@ async function executeStructuredPlan(input: {
|
|
|
2003
2374
|
JSON.stringify(item.task, null, 2),
|
|
2004
2375
|
"",
|
|
2005
2376
|
"Previous runtime failure:",
|
|
2006
|
-
cap(
|
|
2377
|
+
cap(taskFailure, 4200),
|
|
2007
2378
|
].join("\n"),
|
|
2008
2379
|
item.cwd,
|
|
2009
2380
|
input.inheritedModel,
|
|
2010
2381
|
input.inheritedThinking,
|
|
2011
2382
|
attempt,
|
|
2012
|
-
|
|
2383
|
+
taskFailure,
|
|
2013
2384
|
input.signal,
|
|
2014
2385
|
undefined,
|
|
2015
2386
|
input.traceID,
|
|
2387
|
+
leafPolicy,
|
|
2388
|
+
input.root,
|
|
2016
2389
|
);
|
|
2017
2390
|
results.push({
|
|
2018
2391
|
wave: waveIndex,
|
|
@@ -2035,16 +2408,15 @@ async function executeStructuredPlan(input: {
|
|
|
2035
2408
|
}
|
|
2036
2409
|
}
|
|
2037
2410
|
|
|
2411
|
+
const retryDelta = failureDelta(focusedFailure || taskFailure);
|
|
2038
2412
|
const implementation = await runRoutedAgent(
|
|
2039
2413
|
"ues-executor",
|
|
2040
|
-
taskText
|
|
2041
|
-
? "\n\nFocused diagnosis before retry:\n" + cap(focusedFailure, 7000)
|
|
2042
|
-
: ""),
|
|
2414
|
+
taskText,
|
|
2043
2415
|
item.cwd,
|
|
2044
2416
|
input.inheritedModel,
|
|
2045
2417
|
input.inheritedThinking,
|
|
2046
2418
|
attempt,
|
|
2047
|
-
|
|
2419
|
+
retryDelta || undefined,
|
|
2048
2420
|
input.signal,
|
|
2049
2421
|
(progress) => {
|
|
2050
2422
|
input.onUpdate?.({
|
|
@@ -2058,8 +2430,10 @@ async function executeStructuredPlan(input: {
|
|
|
2058
2430
|
});
|
|
2059
2431
|
},
|
|
2060
2432
|
input.traceID,
|
|
2433
|
+
leafPolicy,
|
|
2434
|
+
input.root,
|
|
2061
2435
|
);
|
|
2062
|
-
results.push({ wave: waveIndex, attempt, task: item.task.id, phase: "execute", ...implementation });
|
|
2436
|
+
results.push({ wave: waveIndex, attempt, task: item.task.id, phase: "execute", leafPolicy, ...implementation });
|
|
2063
2437
|
|
|
2064
2438
|
if (implementation.exitCode !== 0 || implementation.stopReason === "error") {
|
|
2065
2439
|
if (!isAbortedRun(implementation)) {
|
|
@@ -2079,7 +2453,75 @@ async function executeStructuredPlan(input: {
|
|
|
2079
2453
|
};
|
|
2080
2454
|
}
|
|
2081
2455
|
|
|
2082
|
-
|
|
2456
|
+
let verification: RunResult;
|
|
2457
|
+
let leafFastGate: any = null;
|
|
2458
|
+
if (leafFastDecision.eligible) {
|
|
2459
|
+
const snapshot = runtimeWorkspaceSnapshot(item.cwd);
|
|
2460
|
+
const receipts = await listReusableVerification(item.cwd, {
|
|
2461
|
+
limit: 12,
|
|
2462
|
+
maxAgeMs: 10 * 60_000,
|
|
2463
|
+
previewBytes: 2200,
|
|
2464
|
+
...(snapshot.cacheable === true && snapshot.fingerprint
|
|
2465
|
+
? { workspaceFingerprint: snapshot.fingerprint }
|
|
2466
|
+
: {}),
|
|
2467
|
+
}).catch(() => ({ results: [] }));
|
|
2468
|
+
leafFastGate = evaluateFastVerificationGate({
|
|
2469
|
+
policy: leafPolicy,
|
|
2470
|
+
implementation,
|
|
2471
|
+
receipts: receipts?.results || [],
|
|
2472
|
+
attemptStartedAtMs: leafAttemptStartedAt,
|
|
2473
|
+
visualRequired: leafVisualRequired,
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
if (leafFastGate?.passed === true) {
|
|
2478
|
+
verification = {
|
|
2479
|
+
agent: "ues-deterministic-verifier",
|
|
2480
|
+
task: taskText,
|
|
2481
|
+
cwd: item.cwd,
|
|
2482
|
+
exitCode: 0,
|
|
2483
|
+
output: [
|
|
2484
|
+
"Per-leaf Turbo verification reused fresh behavioral evidence captured after this leaf implementation.",
|
|
2485
|
+
...leafFastGate.behavioralReceipts.map((entry: any) => "- " + entry.command),
|
|
2486
|
+
"",
|
|
2487
|
+
"UES_VERDICT: PASS",
|
|
2488
|
+
].join("\n"),
|
|
2489
|
+
stderr: "",
|
|
2490
|
+
verdict: "PASS",
|
|
2491
|
+
durationMs: 0,
|
|
2492
|
+
toolCalls: 0,
|
|
2493
|
+
toolNames: [],
|
|
2494
|
+
report: {
|
|
2495
|
+
schemaVersion: 1,
|
|
2496
|
+
valid: true,
|
|
2497
|
+
verdict: "PASS",
|
|
2498
|
+
sections: {
|
|
2499
|
+
"checks-run": leafFastGate.behavioralReceipts.map((entry: any) => entry.command).join("\n"),
|
|
2500
|
+
"acceptance-criteria-proven": "Fresh behavioral receipt(s) exist at the post-implementation workspace fingerprint.",
|
|
2501
|
+
"completion-evidence": "Deterministic per-leaf PASS after this implementation attempt.",
|
|
2502
|
+
},
|
|
2503
|
+
},
|
|
2504
|
+
optimizations: {
|
|
2505
|
+
perLeafTurbo: true,
|
|
2506
|
+
behavioralReceiptCount: leafFastGate.behavioralReceipts.length,
|
|
2507
|
+
},
|
|
2508
|
+
};
|
|
2509
|
+
input.onUpdate?.({
|
|
2510
|
+
content: [{
|
|
2511
|
+
type: "text",
|
|
2512
|
+
text: `UES Per-Leaf Turbo: ${item.task.id} reused ${leafFastGate.behavioralReceipts.length} fresh behavioral receipt(s); skipped verifier model turn`,
|
|
2513
|
+
}],
|
|
2514
|
+
details: {
|
|
2515
|
+
wave: waveIndex,
|
|
2516
|
+
attempt,
|
|
2517
|
+
task: item.task.id,
|
|
2518
|
+
phase: "leaf-turbo-verified",
|
|
2519
|
+
leafPolicy,
|
|
2520
|
+
leafFastGate,
|
|
2521
|
+
},
|
|
2522
|
+
});
|
|
2523
|
+
} else {
|
|
2524
|
+
verification = await runRoutedAgent(
|
|
2083
2525
|
"ues-verifier",
|
|
2084
2526
|
[
|
|
2085
2527
|
"Verify exactly this structured plan task in the current isolated worktree.",
|
|
@@ -2106,8 +2548,11 @@ async function executeStructuredPlan(input: {
|
|
|
2106
2548
|
});
|
|
2107
2549
|
},
|
|
2108
2550
|
input.traceID,
|
|
2551
|
+
leafPolicy,
|
|
2552
|
+
input.root,
|
|
2109
2553
|
);
|
|
2110
|
-
|
|
2554
|
+
}
|
|
2555
|
+
results.push({ wave: waveIndex, attempt, task: item.task.id, phase: "verify", leafPolicy, leafFastGate, ...verification });
|
|
2111
2556
|
const passed = verification.exitCode === 0 && verification.verdict === "PASS";
|
|
2112
2557
|
if (!isAbortedRun(verification)) {
|
|
2113
2558
|
await recordRuntimeOutcome(implementation, taskText, passed, attempt - 1);
|
|
@@ -2153,8 +2598,12 @@ async function executeStructuredPlan(input: {
|
|
|
2153
2598
|
|
|
2154
2599
|
const failed = waveResults.filter((item) => !item.passed);
|
|
2155
2600
|
if (failed.length) {
|
|
2601
|
+
for (const row of failed) {
|
|
2602
|
+
const rawFailure = row.verification?.output || row.implementation?.output || "unknown failure";
|
|
2603
|
+
failureByTask.set(String(row.item?.task?.id || "unknown"), failureDelta(rawFailure));
|
|
2604
|
+
}
|
|
2156
2605
|
lastWaveFailure = failed
|
|
2157
|
-
.map((item) => item.
|
|
2606
|
+
.map((item) => failureByTask.get(String(item.item?.task?.id || "unknown")) || "unknown failure")
|
|
2158
2607
|
.join("\n\n---\n\n");
|
|
2159
2608
|
await failDurablePrepared(prepared, lastWaveFailure);
|
|
2160
2609
|
await cleanupSandboxes(input.root, prepared);
|
|
@@ -2172,9 +2621,9 @@ async function executeStructuredPlan(input: {
|
|
|
2172
2621
|
};
|
|
2173
2622
|
}
|
|
2174
2623
|
|
|
2175
|
-
if (!gitCapable) {
|
|
2176
|
-
//
|
|
2177
|
-
//
|
|
2624
|
+
if (!gitCapable || prepared.every((item) => !item.sandbox?.dir)) {
|
|
2625
|
+
// Read-only waves do not need a duplicate Git worktree. Their fresh
|
|
2626
|
+
// verifier evidence is enough as long as the workspace stays unchanged.
|
|
2178
2627
|
await completeDurableWave(waveResults);
|
|
2179
2628
|
break;
|
|
2180
2629
|
}
|
|
@@ -2182,13 +2631,21 @@ async function executeStructuredPlan(input: {
|
|
|
2182
2631
|
const actualByTask = new Map<string, string[]>();
|
|
2183
2632
|
let scopeFailure = "";
|
|
2184
2633
|
for (const item of prepared) {
|
|
2634
|
+
if (!item.sandbox?.dir) {
|
|
2635
|
+
actualByTask.set(item.task.id, []);
|
|
2636
|
+
continue;
|
|
2637
|
+
}
|
|
2185
2638
|
const changed = await sandboxChangedFiles(item.sandbox.dir, item.sandbox.integrationBase, input.signal);
|
|
2186
2639
|
actualByTask.set(item.task.id, changed);
|
|
2187
2640
|
const allowed = new Set(item.writeFiles);
|
|
2188
2641
|
const unexpected = changed.filter((file) => !allowed.has(file));
|
|
2189
2642
|
if (unexpected.length) {
|
|
2190
|
-
|
|
2191
|
-
`${item.task.id}: changed files outside declared write scope: ${unexpected.join(", ")}
|
|
2643
|
+
const scopeMessage =
|
|
2644
|
+
`${item.task.id}: changed real source files outside declared write scope: ${unexpected.join(", ")}. ` +
|
|
2645
|
+
"On retry, revert or avoid these files and satisfy the task strictly within its declared write scope. " +
|
|
2646
|
+
"Do not silently broaden scope.";
|
|
2647
|
+
failureByTask.set(String(item.task.id), failureDelta(scopeMessage, { maxChars: 2200 }));
|
|
2648
|
+
scopeFailure += scopeMessage + "\n";
|
|
2192
2649
|
}
|
|
2193
2650
|
}
|
|
2194
2651
|
|
|
@@ -2197,7 +2654,12 @@ async function executeStructuredPlan(input: {
|
|
|
2197
2654
|
for (const file of changed) {
|
|
2198
2655
|
const previous = changedOwners.get(file);
|
|
2199
2656
|
if (previous && previous !== taskID) {
|
|
2200
|
-
|
|
2657
|
+
const conflictMessage = `wave conflict: ${previous} and ${taskID} both changed real source file ${file}`;
|
|
2658
|
+
const previousFailure = failureByTask.get(previous) || "";
|
|
2659
|
+
const currentFailure = failureByTask.get(taskID) || "";
|
|
2660
|
+
failureByTask.set(previous, failureDelta([previousFailure, conflictMessage].filter(Boolean).join("\n"), { maxChars: 2200 }));
|
|
2661
|
+
failureByTask.set(taskID, failureDelta([currentFailure, conflictMessage].filter(Boolean).join("\n"), { maxChars: 2200 }));
|
|
2662
|
+
scopeFailure += conflictMessage + "\n";
|
|
2201
2663
|
} else {
|
|
2202
2664
|
changedOwners.set(file, taskID);
|
|
2203
2665
|
}
|
|
@@ -2225,6 +2687,7 @@ async function executeStructuredPlan(input: {
|
|
|
2225
2687
|
const integrated: Array<{ item: any; receipt: any }> = [];
|
|
2226
2688
|
try {
|
|
2227
2689
|
for (const item of prepared) {
|
|
2690
|
+
if (!item.sandbox?.dir) continue;
|
|
2228
2691
|
const receipt = await integrateTaskSandbox(input.root, item.sandbox.dir, { keep: true });
|
|
2229
2692
|
integrated.push({ item, receipt });
|
|
2230
2693
|
integrations.push({ wave: waveIndex, task: item.task.id, ...receipt });
|
|
@@ -2344,36 +2807,97 @@ export default function (pi: ExtensionAPI) {
|
|
|
2344
2807
|
// controller inside specialist workers.
|
|
2345
2808
|
if (process.env.UES_CHILD_PROCESS === "1") return;
|
|
2346
2809
|
|
|
2810
|
+
let directControllerAbort: AbortController | null = null;
|
|
2811
|
+
let promptUesActive = false;
|
|
2812
|
+
let directControllerRunner: ((task: string, ctx: any, admission: "command" | "automatic", admissionDecision?: any) => Promise<void>) | null = null;
|
|
2813
|
+
const AUTO_ADMISSION_ENABLED = !["0", "false", "off"].includes(
|
|
2814
|
+
String(process.env.UES_AUTO_ADMIT || "1").trim().toLowerCase(),
|
|
2815
|
+
);
|
|
2816
|
+
const UES_PARENT_TOOL_NAMES = new Set(["ues_cli", "ues_execute", "ues_service", "ues_dispatch"]);
|
|
2817
|
+
let normalActiveTools: string[] | null = null;
|
|
2818
|
+
|
|
2819
|
+
const currentNonUesTools = () =>
|
|
2820
|
+
pi.getActiveTools().filter((name) => !UES_PARENT_TOOL_NAMES.has(String(name)));
|
|
2821
|
+
|
|
2822
|
+
const deactivateParentUesTools = () => {
|
|
2823
|
+
const normal = currentNonUesTools();
|
|
2824
|
+
normalActiveTools = normal;
|
|
2825
|
+
try { pi.setActiveTools(normal); } catch {}
|
|
2826
|
+
};
|
|
2827
|
+
|
|
2828
|
+
const activateParentUesTools = () => {
|
|
2829
|
+
const normal = normalActiveTools || currentNonUesTools();
|
|
2830
|
+
const registered = new Set(
|
|
2831
|
+
(typeof (pi as any).getAllTools === "function" ? (pi as any).getAllTools() : [])
|
|
2832
|
+
.map((tool: any) => String(tool?.name || "")),
|
|
2833
|
+
);
|
|
2834
|
+
const uesTools = [...UES_PARENT_TOOL_NAMES].filter((name) => registered.has(name));
|
|
2835
|
+
try { pi.setActiveTools([...new Set([...normal, ...uesTools])]); } catch {}
|
|
2836
|
+
};
|
|
2837
|
+
|
|
2838
|
+
const uesModeActive = () =>
|
|
2839
|
+
promptUesActive || Boolean(directControllerAbort && !directControllerAbort.signal.aborted);
|
|
2840
|
+
|
|
2841
|
+
const syncSessionIdentity = (name: string, ctx: any) => {
|
|
2842
|
+
if (!name) return;
|
|
2843
|
+
try { pi.setSessionName(name); } catch {}
|
|
2844
|
+
try { ctx?.ui?.setTitle?.(name); } catch {}
|
|
2845
|
+
};
|
|
2846
|
+
|
|
2847
|
+
pi.on("session_start", async () => {
|
|
2848
|
+
deactivateParentUesTools();
|
|
2849
|
+
});
|
|
2850
|
+
|
|
2347
2851
|
pi.on("session_shutdown", async () => {
|
|
2852
|
+
directControllerAbort?.abort();
|
|
2853
|
+
directControllerAbort = null;
|
|
2854
|
+
promptUesActive = false;
|
|
2855
|
+
normalActiveTools = null;
|
|
2348
2856
|
CONTEXT_PACK_CACHE.clear();
|
|
2349
2857
|
clearSkillCompilerCache();
|
|
2350
2858
|
clearAffectedTestCache();
|
|
2351
2859
|
clearRepoGraphRuntimeCache();
|
|
2352
2860
|
clearSemanticIndexRuntimeCache();
|
|
2861
|
+
MCP_HEALTH.clear();
|
|
2353
2862
|
abortActiveCliChildren();
|
|
2863
|
+
await stopAllServices().catch(() => []);
|
|
2354
2864
|
await RPC_POOL.stopAll().catch(() => {});
|
|
2865
|
+
for (const dir of [...ACTIVE_TASK_SANDBOXES.keys()]) {
|
|
2866
|
+
const ownerRoot = await taskSandboxOwnerRoot(dir).catch(() => null);
|
|
2867
|
+
if (!ownerRoot) continue;
|
|
2868
|
+
await removeTaskSandbox(ownerRoot, dir, { force: true, deleteBranch: true }).catch(() => {});
|
|
2869
|
+
ACTIVE_TASK_SANDBOXES.delete(dir);
|
|
2870
|
+
}
|
|
2355
2871
|
});
|
|
2356
2872
|
|
|
2357
2873
|
pi.on("input", async (event, ctx) => {
|
|
2358
2874
|
if (process.env.UES_CHILD_PROCESS === "1") return { action: "continue" };
|
|
2359
|
-
if (
|
|
2360
|
-
event.source !== "interactive" ||
|
|
2361
|
-
!["steer", "followUp"].includes(String(event.streamingBehavior || ""))
|
|
2362
|
-
) {
|
|
2363
|
-
return { action: "continue" };
|
|
2364
|
-
}
|
|
2875
|
+
if (event.source !== "interactive") return { action: "continue" };
|
|
2365
2876
|
|
|
2366
2877
|
const text = String(event.text || "").trim();
|
|
2367
2878
|
if (!text) return { action: "continue" };
|
|
2368
2879
|
|
|
2880
|
+
const sessionName = sessionNameFromUesInput(text, ctx.cwd || "");
|
|
2881
|
+
if (sessionName) syncSessionIdentity(sessionName, ctx);
|
|
2882
|
+
if (/^\/ues-(?:resume|fix|feature|debug|review|audit|plan|research|critique|verify)(?:\s|$)/i.test(text)) {
|
|
2883
|
+
promptUesActive = true;
|
|
2884
|
+
activateParentUesTools();
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2369
2887
|
if (/^(?:stop|cancel|abort|dừng|dung|hủy|huy)(?:\s|$)/i.test(text)) {
|
|
2888
|
+
if (!uesModeActive()) return { action: "continue" };
|
|
2889
|
+
let directAborted = 0;
|
|
2890
|
+
if (directControllerAbort && !directControllerAbort.signal.aborted) {
|
|
2891
|
+
directControllerAbort.abort();
|
|
2892
|
+
directAborted = 1;
|
|
2893
|
+
}
|
|
2370
2894
|
const result = await RPC_POOL.abortActive();
|
|
2371
2895
|
const cliAborted = abortActiveCliChildren();
|
|
2372
|
-
const total = result.aborted + cliAborted;
|
|
2896
|
+
const total = directAborted + result.aborted + cliAborted;
|
|
2373
2897
|
if (total > 0) {
|
|
2374
2898
|
try {
|
|
2375
2899
|
ctx.ui.notify(
|
|
2376
|
-
`UES: aborted ${total} active child worker(s)` +
|
|
2900
|
+
`UES: aborted ${total} active controller/child worker(s)` +
|
|
2377
2901
|
(cliAborted ? ` (${cliAborted} CLI fallback)` : ""),
|
|
2378
2902
|
"warning",
|
|
2379
2903
|
);
|
|
@@ -2383,6 +2907,65 @@ export default function (pi: ExtensionAPI) {
|
|
|
2383
2907
|
return { action: "continue" };
|
|
2384
2908
|
}
|
|
2385
2909
|
|
|
2910
|
+
if (!["steer", "followUp"].includes(String(event.streamingBehavior || ""))) {
|
|
2911
|
+
if (directControllerAbort && !directControllerAbort.signal.aborted) {
|
|
2912
|
+
const continuation = automaticUesContinuation(text, { activeRun: true });
|
|
2913
|
+
if (continuation.forward) {
|
|
2914
|
+
const forwarded = await RPC_POOL.steerActive(text).catch(() => ({
|
|
2915
|
+
accepted: false,
|
|
2916
|
+
reason: "steer-failed",
|
|
2917
|
+
active: 0,
|
|
2918
|
+
}));
|
|
2919
|
+
if (forwarded.accepted) {
|
|
2920
|
+
try { ctx.ui.notify("UES: natural continuation forwarded to the active child", "info"); } catch {}
|
|
2921
|
+
return { action: "handled" };
|
|
2922
|
+
}
|
|
2923
|
+
try {
|
|
2924
|
+
ctx.ui.notify(
|
|
2925
|
+
"UES is already running; continuation could not be targeted safely, so the prompt was not swallowed.",
|
|
2926
|
+
"warning",
|
|
2927
|
+
);
|
|
2928
|
+
} catch {}
|
|
2929
|
+
}
|
|
2930
|
+
return { action: "continue" };
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
if (
|
|
2934
|
+
AUTO_ADMISSION_ENABLED &&
|
|
2935
|
+
directControllerRunner &&
|
|
2936
|
+
!(Array.isArray((event as any).images) && (event as any).images.length > 0)
|
|
2937
|
+
) {
|
|
2938
|
+
const preliminaryAdmission = automaticUesAdmission(text);
|
|
2939
|
+
const workspaceRoot = preliminaryAdmission.admit === true
|
|
2940
|
+
? resolveGitWorkspaceRoot(ctx.cwd || "")
|
|
2941
|
+
: null;
|
|
2942
|
+
const admission = workspaceRoot
|
|
2943
|
+
? automaticUesAdmission(text, { inGitWorkspace: true })
|
|
2944
|
+
: preliminaryAdmission;
|
|
2945
|
+
if (workspaceRoot && admission.admit === true) {
|
|
2946
|
+
try {
|
|
2947
|
+
ctx.ui.notify(
|
|
2948
|
+
"UES " + PACKAGE_VERSION + ": " +
|
|
2949
|
+
(admission.route === "guarded" ? "high-risk engineering task admitted" : "engineering task auto-admitted") +
|
|
2950
|
+
" (" + admission.reason + ", confidence " + admission.confidence + ")",
|
|
2951
|
+
admission.route === "guarded" ? "warning" : "info",
|
|
2952
|
+
);
|
|
2953
|
+
} catch {}
|
|
2954
|
+
void directControllerRunner(text, ctx, "automatic", admission).catch((error) => {
|
|
2955
|
+
try {
|
|
2956
|
+
ctx.ui.notify(
|
|
2957
|
+
"UES automatic controller failed to start: " +
|
|
2958
|
+
(error instanceof Error ? error.message : String(error)),
|
|
2959
|
+
"error",
|
|
2960
|
+
);
|
|
2961
|
+
} catch {}
|
|
2962
|
+
});
|
|
2963
|
+
return { action: "handled" };
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
return { action: "continue" };
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2386
2969
|
const steered = await RPC_POOL.steerActive(text).catch(() => ({
|
|
2387
2970
|
accepted: false,
|
|
2388
2971
|
reason: "steer-failed",
|
|
@@ -2398,17 +2981,69 @@ export default function (pi: ExtensionAPI) {
|
|
|
2398
2981
|
return { action: "continue" };
|
|
2399
2982
|
});
|
|
2400
2983
|
|
|
2984
|
+
pi.on("before_agent_start", async () => {
|
|
2985
|
+
if (uesModeActive()) activateParentUesTools();
|
|
2986
|
+
else deactivateParentUesTools();
|
|
2987
|
+
});
|
|
2988
|
+
|
|
2989
|
+
pi.on("agent_end", async () => {
|
|
2990
|
+
promptUesActive = false;
|
|
2991
|
+
deactivateParentUesTools();
|
|
2992
|
+
});
|
|
2993
|
+
|
|
2401
2994
|
pi.on("tool_call", async (event, ctx) => {
|
|
2402
|
-
|
|
2995
|
+
const toolName = String(event.toolName || "");
|
|
2996
|
+
if (!uesModeActive()) {
|
|
2997
|
+
if (toolName.startsWith("ues_")) {
|
|
2998
|
+
return {
|
|
2999
|
+
block: true,
|
|
3000
|
+
reason: "UES parent tools are hidden outside admitted UES runs. Submit a normal engineering task or start an explicit /ues-* command.",
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
return undefined;
|
|
3004
|
+
}
|
|
3005
|
+
if (toolName !== "bash" && toolName !== "powershell") {
|
|
3006
|
+
const allTools = typeof (pi as any).getAllTools === "function" ? (pi as any).getAllTools() : [];
|
|
3007
|
+
const descriptor = allTools.find((tool: any) => String(tool?.name || "") === toolName);
|
|
3008
|
+
const policy = mcpExecutionPolicy(descriptor || { name: toolName });
|
|
3009
|
+
if (policy.confirmationRequired) {
|
|
3010
|
+
const allowed = await approveRisk(ctx, `MCP/tool call: ${toolName}`, `mcp-destructive:${toolName}`);
|
|
3011
|
+
if (!allowed) return { block: true, reason: `Blocked by UES MCP destructive-hint gate: ${toolName}` };
|
|
3012
|
+
}
|
|
3013
|
+
MCP_HEALTH.begin(String((event as any).toolCallId || ""), toolName, policy);
|
|
3014
|
+
return undefined;
|
|
3015
|
+
}
|
|
3016
|
+
|
|
2403
3017
|
const command = String((event.input as any)?.command || "");
|
|
3018
|
+
if (looksLikeLongRunningServiceCommand(command)) {
|
|
3019
|
+
return {
|
|
3020
|
+
block: true,
|
|
3021
|
+
reason:
|
|
3022
|
+
"UES detected a likely long-running foreground service command. " +
|
|
3023
|
+
"Use ues_service start/wait-ready/logs/stop so Pi can continue without blocking on the server process.",
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
2404
3026
|
const risk = destructiveShellRisk(command);
|
|
2405
3027
|
if (!risk.risky) return undefined;
|
|
2406
|
-
|
|
2407
3028
|
const allowed = await approveRisk(ctx, command, risk.id || "destructive");
|
|
2408
3029
|
if (!allowed) return { block: true, reason: `Blocked by UES safety gate: ${risk.id}` };
|
|
2409
3030
|
return undefined;
|
|
2410
3031
|
});
|
|
2411
3032
|
|
|
3033
|
+
pi.on("tool_result", async (event) => {
|
|
3034
|
+
if (!uesModeActive()) return undefined;
|
|
3035
|
+
const toolName = String((event as any).toolName || "");
|
|
3036
|
+
if (!toolName || toolName === "bash" || toolName === "powershell") return undefined;
|
|
3037
|
+
MCP_HEALTH.finish(
|
|
3038
|
+
String((event as any).toolCallId || ""),
|
|
3039
|
+
{
|
|
3040
|
+
isError: (event as any).isError === true,
|
|
3041
|
+
text: toolResultText(event),
|
|
3042
|
+
},
|
|
3043
|
+
);
|
|
3044
|
+
return undefined;
|
|
3045
|
+
});
|
|
3046
|
+
|
|
2412
3047
|
pi.registerTool({
|
|
2413
3048
|
name: "ues_cli",
|
|
2414
3049
|
label: "UES CLI",
|
|
@@ -2419,7 +3054,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2419
3054
|
cwd: Type.Optional(Type.String({ description: "Working directory; defaults to the current Pi cwd" })),
|
|
2420
3055
|
}),
|
|
2421
3056
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
2422
|
-
const cwd =
|
|
3057
|
+
const cwd = requireGitWorkspaceRoot(params.cwd || ctx.cwd, "ues_cli");
|
|
2423
3058
|
const separator = params.args.indexOf("--");
|
|
2424
3059
|
if (separator >= 0 && separator < params.args.length - 1) {
|
|
2425
3060
|
const command = params.args.slice(separator + 1).join(" ");
|
|
@@ -2478,6 +3113,91 @@ export default function (pi: ExtensionAPI) {
|
|
|
2478
3113
|
});
|
|
2479
3114
|
|
|
2480
3115
|
pi.registerTool({
|
|
3116
|
+
name: "ues_service",
|
|
3117
|
+
label: "UES Managed Service",
|
|
3118
|
+
description:
|
|
3119
|
+
"Start and manage long-running dev servers/watchers without blocking Pi. Uses shell-free execution, bounded logs, readiness probes, evidence snapshots, and session cleanup. For start/restart, command is the executable only (for example node or npm); put every argument in args.",
|
|
3120
|
+
parameters: Type.Object({
|
|
3121
|
+
action: Type.Union([
|
|
3122
|
+
Type.Literal("start"),
|
|
3123
|
+
Type.Literal("wait-ready"),
|
|
3124
|
+
Type.Literal("status"),
|
|
3125
|
+
Type.Literal("logs"),
|
|
3126
|
+
Type.Literal("stop"),
|
|
3127
|
+
Type.Literal("restart"),
|
|
3128
|
+
]),
|
|
3129
|
+
name: Type.String({ minLength: 1, maxLength: 64 }),
|
|
3130
|
+
command: Type.Optional(Type.String({ minLength: 1 })),
|
|
3131
|
+
args: Type.Optional(Type.Array(Type.String(), { maxItems: 128 })),
|
|
3132
|
+
cwd: Type.Optional(Type.String()),
|
|
3133
|
+
readyPort: Type.Optional(Type.Number({ minimum: 1, maximum: 65535 })),
|
|
3134
|
+
readyHost: Type.Optional(Type.String({ maxLength: 255 })),
|
|
3135
|
+
readyLog: Type.Optional(Type.String({ maxLength: 512 })),
|
|
3136
|
+
timeoutMs: Type.Optional(Type.Number({ minimum: 100, maximum: 600000 })),
|
|
3137
|
+
lifetimeMs: Type.Optional(Type.Number({ minimum: 10000, maximum: 7200000 })),
|
|
3138
|
+
idleTimeoutMs: Type.Optional(Type.Number({ minimum: 5000, maximum: 3600000 })),
|
|
3139
|
+
maxChars: Type.Optional(Type.Number({ minimum: 256, maximum: 128000 })),
|
|
3140
|
+
}),
|
|
3141
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
3142
|
+
try {
|
|
3143
|
+
const hostCwd = path.resolve(ctx.cwd);
|
|
3144
|
+
const serviceRoot = requireGitWorkspaceRoot(hostCwd, "ues_service");
|
|
3145
|
+
const requestedServiceCwd = params.cwd
|
|
3146
|
+
? path.relative(serviceRoot, path.resolve(hostCwd, params.cwd)) || "."
|
|
3147
|
+
: path.relative(serviceRoot, hostCwd) || ".";
|
|
3148
|
+
let result: any;
|
|
3149
|
+
if (params.action === "start") {
|
|
3150
|
+
if (!params.command) throw new Error("ues_service start requires command");
|
|
3151
|
+
const riskText = [params.command, ...(params.args || [])].join(" ");
|
|
3152
|
+
const risk = destructiveShellRisk(riskText);
|
|
3153
|
+
if (risk.risky) throw new Error(`UES service safety blocked ${risk.id || "destructive"} command`);
|
|
3154
|
+
result = await startService(serviceRoot, {
|
|
3155
|
+
name: params.name,
|
|
3156
|
+
command: params.command,
|
|
3157
|
+
args: params.args || [],
|
|
3158
|
+
cwd: requestedServiceCwd,
|
|
3159
|
+
readyPort: params.readyPort,
|
|
3160
|
+
readyHost: params.readyHost,
|
|
3161
|
+
readyLog: params.readyLog,
|
|
3162
|
+
timeoutMs: params.timeoutMs,
|
|
3163
|
+
lifetimeMs: params.lifetimeMs,
|
|
3164
|
+
idleTimeoutMs: params.idleTimeoutMs,
|
|
3165
|
+
});
|
|
3166
|
+
} else if (params.action === "wait-ready") {
|
|
3167
|
+
result = await waitForService(serviceRoot, params.name, { timeoutMs: params.timeoutMs });
|
|
3168
|
+
} else if (params.action === "status") {
|
|
3169
|
+
result = await serviceStatus(serviceRoot, params.name);
|
|
3170
|
+
} else if (params.action === "logs") {
|
|
3171
|
+
result = await serviceLogs(serviceRoot, params.name, { maxChars: params.maxChars, evidence: true });
|
|
3172
|
+
} else if (params.action === "stop") {
|
|
3173
|
+
result = await stopService(serviceRoot, params.name, { timeoutMs: params.timeoutMs });
|
|
3174
|
+
} else if (params.action === "restart") {
|
|
3175
|
+
result = await restartService(serviceRoot, params.name, {
|
|
3176
|
+
timeoutMs: params.timeoutMs,
|
|
3177
|
+
lifetimeMs: params.lifetimeMs,
|
|
3178
|
+
idleTimeoutMs: params.idleTimeoutMs,
|
|
3179
|
+
});
|
|
3180
|
+
} else {
|
|
3181
|
+
throw new Error("Unknown ues_service action");
|
|
3182
|
+
}
|
|
3183
|
+
const failed =
|
|
3184
|
+
(params.action === "start" || params.action === "wait-ready") && result?.ready !== true;
|
|
3185
|
+
return {
|
|
3186
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
3187
|
+
details: result,
|
|
3188
|
+
isError: failed,
|
|
3189
|
+
};
|
|
3190
|
+
} catch (error) {
|
|
3191
|
+
return {
|
|
3192
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
3193
|
+
details: { action: params.action, name: params.name },
|
|
3194
|
+
isError: true,
|
|
3195
|
+
};
|
|
3196
|
+
}
|
|
3197
|
+
},
|
|
3198
|
+
});
|
|
3199
|
+
|
|
3200
|
+
const uesExecuteTool: any = {
|
|
2481
3201
|
name: "ues_execute",
|
|
2482
3202
|
label: "UES Execute",
|
|
2483
3203
|
description:
|
|
@@ -2489,16 +3209,55 @@ export default function (pi: ExtensionAPI) {
|
|
|
2489
3209
|
}),
|
|
2490
3210
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
2491
3211
|
const hostBrowserTools = refreshHostBrowserToolNames(pi);
|
|
2492
|
-
const cwd =
|
|
3212
|
+
const cwd = requireGitWorkspaceRoot(params.cwd || ctx.cwd, "ues_execute");
|
|
2493
3213
|
const inheritedModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
2494
3214
|
const inheritedThinking = ctx.thinkingLevel as string | undefined;
|
|
2495
|
-
const
|
|
2496
|
-
const
|
|
3215
|
+
const inheritedDirty = captureInheritedDirtyState(cwd);
|
|
3216
|
+
const executionContract = buildExecutionContract(params.task, inheritedDirty);
|
|
3217
|
+
const contractPrompt = executionContractPrompt(executionContract);
|
|
3218
|
+
const suppliedPolicy = (params as any).__taskPolicy;
|
|
3219
|
+
const basePolicy =
|
|
3220
|
+
suppliedPolicy && typeof suppliedPolicy === "object"
|
|
3221
|
+
? suppliedPolicy
|
|
3222
|
+
: classifyEngineeringTask(params.task);
|
|
3223
|
+
const policy = {
|
|
3224
|
+
...basePolicy,
|
|
3225
|
+
localEnvWriteExplicitlyAllowed: executionContract.localEnvWriteExplicitlyAllowed === true,
|
|
3226
|
+
executionContractPrompt: contractPrompt,
|
|
3227
|
+
};
|
|
3228
|
+
const traceID = String((params as any).__traceID || createTraceID("ues-execute"));
|
|
3229
|
+
const orphanCleanup = await pruneOrphanTaskSandboxes(cwd, {
|
|
3230
|
+
minAgeMs: 5 * 60_000,
|
|
3231
|
+
legacyMinAgeMs: 30 * 60_000,
|
|
3232
|
+
ownedMinAgeMs: 60_000,
|
|
3233
|
+
reclaimOwnerPid: process.pid,
|
|
3234
|
+
protectedDirs: [...ACTIVE_TASK_SANDBOXES.keys()],
|
|
3235
|
+
}).catch(() => ({ removed: [], skipped: [], sidecarsRemoved: [] }));
|
|
3236
|
+
if (orphanCleanup.removed?.length) {
|
|
3237
|
+
onUpdate?.({
|
|
3238
|
+
content: [{ type: "text", text: `UES sandbox cleanup: removed ${orphanCleanup.removed.length} orphan worktree(s)` }],
|
|
3239
|
+
details: { mode: "execute", phase: "sandbox-cleanup", orphanCleanup },
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
2497
3242
|
await appendTrajectoryEvent(cwd, traceID, "controller.started", {
|
|
2498
3243
|
profile: policy.executionProfile,
|
|
2499
3244
|
risk: policy.risk,
|
|
2500
3245
|
mode: policy.mode,
|
|
3246
|
+
inheritedDirtyCount: executionContract.inheritedDirty?.paths?.length || 0,
|
|
3247
|
+
explicitPhaseCount: executionContract.phases?.length || 0,
|
|
3248
|
+
localEnvWriteExplicitlyAllowed: executionContract.localEnvWriteExplicitlyAllowed === true,
|
|
2501
3249
|
}).catch(() => {});
|
|
3250
|
+
if ((executionContract.inheritedDirty?.paths?.length || 0) > 0 || (executionContract.phases?.length || 0) > 0) {
|
|
3251
|
+
onUpdate?.({
|
|
3252
|
+
content: [{
|
|
3253
|
+
type: "text",
|
|
3254
|
+
text:
|
|
3255
|
+
`UES contract: inherited dirty ${executionContract.inheritedDirty?.paths?.length || 0}; ` +
|
|
3256
|
+
`explicit phases ${executionContract.phases?.length || 0}; local .env write ${executionContract.localEnvWriteExplicitlyAllowed ? "authorized" : "blocked"}`,
|
|
3257
|
+
}],
|
|
3258
|
+
details: { mode: "execute", phase: "execution-contract", executionContract, traceID },
|
|
3259
|
+
});
|
|
3260
|
+
}
|
|
2502
3261
|
const browserLaneRequested =
|
|
2503
3262
|
browserEvidenceNeeded(params.task, "executor") || visualEvidenceNeeded(params.task);
|
|
2504
3263
|
if (browserLaneRequested) {
|
|
@@ -2543,9 +3302,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
2543
3302
|
});
|
|
2544
3303
|
|
|
2545
3304
|
const run = async (agent: AgentName, task: string, attempt = 1, failure?: string) => {
|
|
3305
|
+
const planningBudget = planningRuntimeBudget(roleForAgent(agent), attempt, {
|
|
3306
|
+
executionProfile: policy.executionProfile,
|
|
3307
|
+
risk: policy.risk,
|
|
3308
|
+
taskChars: task.length,
|
|
3309
|
+
});
|
|
3310
|
+
let softSteerSent = false;
|
|
3311
|
+
const governedTask = [task, "", contractPrompt].filter(Boolean).join("\n");
|
|
2546
3312
|
const result = await runRoutedAgent(
|
|
2547
3313
|
agent,
|
|
2548
|
-
|
|
3314
|
+
governedTask,
|
|
2549
3315
|
cwd,
|
|
2550
3316
|
inheritedModel,
|
|
2551
3317
|
inheritedThinking,
|
|
@@ -2553,6 +3319,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
2553
3319
|
failure,
|
|
2554
3320
|
signal,
|
|
2555
3321
|
(progress) => {
|
|
3322
|
+
if (
|
|
3323
|
+
!softSteerSent &&
|
|
3324
|
+
(
|
|
3325
|
+
(agent === "ues-architect" && shouldSoftSteerArchitect(progress, planningBudget)) ||
|
|
3326
|
+
(agent === "ues-plan-checker" && shouldSoftSteerPlanningRole(progress, planningBudget))
|
|
3327
|
+
)
|
|
3328
|
+
) {
|
|
3329
|
+
softSteerSent = true;
|
|
3330
|
+
const steerText =
|
|
3331
|
+
agent === "ues-architect"
|
|
3332
|
+
? "Stop repository exploration now. Use the evidence already gathered and emit the required UES_PLAN_JSON object immediately as the next substantive output. Do not start new broad searches or delay the JSON behind prose."
|
|
3333
|
+
: "Stop broad validation now. Check only unresolved declared paths and dependencies, then return the verdict immediately. End with exactly UES_VERDICT: PASS or UES_VERDICT: REVISE.";
|
|
3334
|
+
void RPC_POOL.steerActive(steerText).catch(() => ({ accepted: false }));
|
|
3335
|
+
onUpdate?.({
|
|
3336
|
+
content: [{
|
|
3337
|
+
type: "text",
|
|
3338
|
+
text:
|
|
3339
|
+
agent === "ues-architect"
|
|
3340
|
+
? "UES planning fast-stop: architect evidence budget reached; requesting immediate plan emission"
|
|
3341
|
+
: "UES planning fast-stop: plan-checker budget reached; requesting immediate verdict",
|
|
3342
|
+
}],
|
|
3343
|
+
details: { mode: "execute", phase: "planning-soft-steer", policy, progress, planningBudget, traceID },
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
2556
3346
|
onUpdate?.({
|
|
2557
3347
|
content: [{
|
|
2558
3348
|
type: "text",
|
|
@@ -2567,6 +3357,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2567
3357
|
});
|
|
2568
3358
|
},
|
|
2569
3359
|
traceID,
|
|
3360
|
+
policy,
|
|
2570
3361
|
);
|
|
2571
3362
|
steps.push(result);
|
|
2572
3363
|
onUpdate?.({
|
|
@@ -2579,6 +3370,176 @@ export default function (pi: ExtensionAPI) {
|
|
|
2579
3370
|
return result;
|
|
2580
3371
|
};
|
|
2581
3372
|
|
|
3373
|
+
if (policy.readOnly === true) {
|
|
3374
|
+
const before = runtimeWorkspaceSnapshot(cwd);
|
|
3375
|
+
const deterministicChecks = deterministicReadOnlyGitCommands(params.task);
|
|
3376
|
+
let inspection: any;
|
|
3377
|
+
|
|
3378
|
+
if (deterministicChecks.length > 0) {
|
|
3379
|
+
const rows: any[] = [];
|
|
3380
|
+
for (const spec of deterministicChecks) {
|
|
3381
|
+
const result = await runProcess(spec.command, spec.args, cwd, signal);
|
|
3382
|
+
if (result.exitCode === 130 || result.stopReason === "aborted") {
|
|
3383
|
+
const aborted: any = {
|
|
3384
|
+
agent: "ues-deterministic-read-only",
|
|
3385
|
+
task: params.task,
|
|
3386
|
+
cwd,
|
|
3387
|
+
exitCode: 130,
|
|
3388
|
+
output: "Deterministic read-only command aborted: " + spec.label,
|
|
3389
|
+
stderr: result.stderr || "",
|
|
3390
|
+
verdict: null,
|
|
3391
|
+
durationMs: result.durationMs || 0,
|
|
3392
|
+
toolCalls: 0,
|
|
3393
|
+
toolNames: [],
|
|
3394
|
+
report: parseStructuredReport(""),
|
|
3395
|
+
stopReason: "aborted",
|
|
3396
|
+
};
|
|
3397
|
+
steps.push(aborted);
|
|
3398
|
+
return abortedResponse(aborted, "read-only-deterministic-check");
|
|
3399
|
+
}
|
|
3400
|
+
rows.push({
|
|
3401
|
+
...spec,
|
|
3402
|
+
exitCode: result.exitCode,
|
|
3403
|
+
stdout: cap(result.stdout || "", 8000),
|
|
3404
|
+
stderr: cap(result.stderr || "", 4000),
|
|
3405
|
+
});
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
const afterChecks = runtimeWorkspaceSnapshot(cwd);
|
|
3409
|
+
const unchangedAfterChecks =
|
|
3410
|
+
before.cacheable === true &&
|
|
3411
|
+
afterChecks.cacheable === true &&
|
|
3412
|
+
before.fingerprint === afterChecks.fingerprint;
|
|
3413
|
+
const failedRows = rows.filter((row) => Number(row.exitCode) !== 0);
|
|
3414
|
+
const deterministicPass = failedRows.length === 0 && unchangedAfterChecks;
|
|
3415
|
+
const checksText = rows.map((row) => {
|
|
3416
|
+
const payload = [row.stdout, row.stderr].filter(Boolean).join("\n").trim() || "(no output)";
|
|
3417
|
+
return [
|
|
3418
|
+
row.label + " — exit " + row.exitCode,
|
|
3419
|
+
payload,
|
|
3420
|
+
].join("\n");
|
|
3421
|
+
}).join("\n\n");
|
|
3422
|
+
const failureText = [
|
|
3423
|
+
...failedRows.map((row) => row.label + " exited " + row.exitCode),
|
|
3424
|
+
...(unchangedAfterChecks ? [] : ["read-only workspace fingerprint changed"]),
|
|
3425
|
+
].join("\n") || "None.";
|
|
3426
|
+
|
|
3427
|
+
const output = [
|
|
3428
|
+
"## Checks run",
|
|
3429
|
+
checksText,
|
|
3430
|
+
"",
|
|
3431
|
+
"## Acceptance criteria proven",
|
|
3432
|
+
deterministicPass
|
|
3433
|
+
? "All explicitly requested whitelisted Git inspection commands completed successfully."
|
|
3434
|
+
: "The requested deterministic inspection did not fully pass.",
|
|
3435
|
+
"",
|
|
3436
|
+
"## Failures",
|
|
3437
|
+
failureText,
|
|
3438
|
+
"",
|
|
3439
|
+
"## Unresolved gaps",
|
|
3440
|
+
"None.",
|
|
3441
|
+
"",
|
|
3442
|
+
"## Checks not run",
|
|
3443
|
+
"None.",
|
|
3444
|
+
"",
|
|
3445
|
+
"## Completion evidence",
|
|
3446
|
+
"UES executed " + rows.length + " command-only read-only Git check(s) directly and compared the source workspace fingerprint before and after.",
|
|
3447
|
+
"",
|
|
3448
|
+
"UES_VERDICT: " + (deterministicPass ? "PASS" : "FAIL"),
|
|
3449
|
+
].join("\n");
|
|
3450
|
+
|
|
3451
|
+
inspection = {
|
|
3452
|
+
agent: "ues-deterministic-read-only",
|
|
3453
|
+
task: params.task,
|
|
3454
|
+
cwd,
|
|
3455
|
+
exitCode: deterministicPass ? 0 : 1,
|
|
3456
|
+
output,
|
|
3457
|
+
stderr: "",
|
|
3458
|
+
verdict: deterministicPass ? "PASS" : "FAIL",
|
|
3459
|
+
durationMs: 0,
|
|
3460
|
+
toolCalls: rows.length,
|
|
3461
|
+
toolNames: rows.map((row) => row.label),
|
|
3462
|
+
report: parseStructuredReport(output),
|
|
3463
|
+
optimizations: {
|
|
3464
|
+
deterministicReadOnlyGit: true,
|
|
3465
|
+
checkCount: rows.length,
|
|
3466
|
+
},
|
|
3467
|
+
};
|
|
3468
|
+
steps.push(inspection);
|
|
3469
|
+
onUpdate?.({
|
|
3470
|
+
content: [{
|
|
3471
|
+
type: "text",
|
|
3472
|
+
text: "UES read-only fast path: executed " + rows.length + " whitelisted Git check(s) deterministically; skipped verifier model turn",
|
|
3473
|
+
}],
|
|
3474
|
+
details: { mode: "execute", phase: "read-only-deterministic", policy, traceID, checks: rows.map((row) => row.label) },
|
|
3475
|
+
});
|
|
3476
|
+
} else {
|
|
3477
|
+
inspection = await run(
|
|
3478
|
+
"ues-verifier",
|
|
3479
|
+
[
|
|
3480
|
+
"READ-ONLY INSPECTION MODE. Do not edit, create, delete, stage, commit, install dependencies, or start persistent services.",
|
|
3481
|
+
"Use only the narrowest read-only commands and source inspection needed to answer the request.",
|
|
3482
|
+
"Fresh command output is valid completion evidence for an inspection task; do not require behavioral tests when the user did not request a code change.",
|
|
3483
|
+
"Return exactly these H2 sections: ## Checks run, ## Acceptance criteria proven, ## Failures, ## Unresolved gaps, ## Checks not run, ## Completion evidence.",
|
|
3484
|
+
"Write exactly None in Failures and Unresolved gaps when there is no real requested failure or gap.",
|
|
3485
|
+
"End with exactly UES_VERDICT: PASS or UES_VERDICT: FAIL.",
|
|
3486
|
+
"Explicitly verify that source-facing workspace state is unchanged before returning PASS.",
|
|
3487
|
+
"",
|
|
3488
|
+
"Inspection request:",
|
|
3489
|
+
params.task,
|
|
3490
|
+
].join("\n"),
|
|
3491
|
+
1,
|
|
3492
|
+
);
|
|
3493
|
+
if (isAbortedRun(inspection)) return abortedResponse(inspection, "read-only-inspection");
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
const after = runtimeWorkspaceSnapshot(cwd);
|
|
3497
|
+
const unchanged =
|
|
3498
|
+
before.cacheable === true &&
|
|
3499
|
+
after.cacheable === true &&
|
|
3500
|
+
before.fingerprint === after.fingerprint;
|
|
3501
|
+
const completionAudit = auditCompletion({
|
|
3502
|
+
verification: inspection,
|
|
3503
|
+
requireIntegration: false,
|
|
3504
|
+
requireVisual: false,
|
|
3505
|
+
workspaceSnapshot: after,
|
|
3506
|
+
behavioralReceipts: [],
|
|
3507
|
+
requireBehavioralReceipt: false,
|
|
3508
|
+
});
|
|
3509
|
+
if (!unchanged) {
|
|
3510
|
+
completionAudit.passed = false;
|
|
3511
|
+
completionAudit.failures = [...new Set([
|
|
3512
|
+
...(completionAudit.failures || []),
|
|
3513
|
+
"read-only-workspace-mutated",
|
|
3514
|
+
])];
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
if (!completionAudit.passed) {
|
|
3518
|
+
return {
|
|
3519
|
+
content: [{
|
|
3520
|
+
type: "text",
|
|
3521
|
+
text: "UES read-only inspection did not pass: " + completionAudit.failures.join(", ") +
|
|
3522
|
+
"\n\n" + inspection.output,
|
|
3523
|
+
}],
|
|
3524
|
+
details: { mode: "execute", policy, steps, completionAudit, traceID },
|
|
3525
|
+
isError: true,
|
|
3526
|
+
};
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
return {
|
|
3530
|
+
content: [{
|
|
3531
|
+
type: "text",
|
|
3532
|
+
text: [
|
|
3533
|
+
"UES read-only inspection PASS.",
|
|
3534
|
+
"Workspace source fingerprint remained unchanged.",
|
|
3535
|
+
"",
|
|
3536
|
+
inspection.output,
|
|
3537
|
+
].join("\n"),
|
|
3538
|
+
}],
|
|
3539
|
+
details: { mode: "execute", policy, steps, completionAudit, traceID },
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
|
|
2582
3543
|
if (shouldRunDedicatedDiagnosis(policy, 1)) {
|
|
2583
3544
|
const diagnosis = await run("ues-debugger", params.task, 1);
|
|
2584
3545
|
if (isAbortedRun(diagnosis)) return abortedResponse(diagnosis, "diagnosis");
|
|
@@ -2597,9 +3558,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
2597
3558
|
params.task,
|
|
2598
3559
|
"",
|
|
2599
3560
|
"Produce an implementation plan grounded in the current repository. Include exact files/interfaces, dependencies, risk controls, rollback notes, acceptance criteria and verification commands.",
|
|
2600
|
-
"For deterministic scheduling,
|
|
3561
|
+
"For deterministic scheduling, emit UES_PLAN_JSON: followed by one valid JSON object with schemaVersion=1, goal, and tasks as the first substantive output. Do not delay the JSON behind long prose.",
|
|
2601
3562
|
"Each task must have id, title, summary, dependsOn, files ({create,modify,test,delete,read}), acceptance, verification, and risk.",
|
|
3563
|
+
executionContract.phases?.length
|
|
3564
|
+
? "PHASE CONTRACT: every execution task must include an integer phase matching one explicit execution PHASE number from the user request. Constraint/guardrail-only phases are invariants, not fake tasks. Do not omit, merge away, or invent execution phases. UES will add deterministic previous-phase barriers after validation."
|
|
3565
|
+
: "",
|
|
3566
|
+
"STRICT JSON CONTRACT: acceptance and verification are non-empty arrays of strings. risk is exactly one of low|medium|high|critical. Put descriptive risk prose in riskNotes. verificationCommands is optional and does not replace verification.",
|
|
2602
3567
|
"Declare every file a task may write. Do not invent files: inspect the repository first.",
|
|
3568
|
+
"DEEP efficiency rule: use the supplied runtime context/ranked references first; do not inventory the whole repository or re-read unchanged files. You have a bounded planning budget: prefer at most one targeted lookup per unresolved boundary, then emit the plan. Stop exploration once exact task scope, dependencies, acceptance, verification, and risk/rollback are grounded.",
|
|
2603
3569
|
].join("\n");
|
|
2604
3570
|
|
|
2605
3571
|
let architect = await run(
|
|
@@ -2609,70 +3575,237 @@ export default function (pi: ExtensionAPI) {
|
|
|
2609
3575
|
recentFailure || undefined,
|
|
2610
3576
|
);
|
|
2611
3577
|
if (isAbortedRun(architect)) return abortedResponse(architect, "planning");
|
|
2612
|
-
if (architect.exitCode !== 0 || architect.stopReason === "error") {
|
|
2613
|
-
return {
|
|
2614
|
-
content: [{ type: "text", text: `Architecture pass failed:\n\n${architect.output}` }],
|
|
2615
|
-
details: { mode: "execute", policy, steps },
|
|
2616
|
-
isError: true,
|
|
2617
|
-
};
|
|
2618
|
-
}
|
|
2619
3578
|
|
|
2620
|
-
|
|
2621
|
-
|
|
3579
|
+
let planCandidate = extractValidatedPlan(architect.output);
|
|
3580
|
+
structuredPlan = planCandidate.plan;
|
|
3581
|
+
let structuredValidation = planCandidate.validation;
|
|
2622
3582
|
|
|
2623
|
-
if (
|
|
2624
|
-
(
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
3583
|
+
if (planCandidate.salvaged && structuredValidation?.valid === true) {
|
|
3584
|
+
onUpdate?.({
|
|
3585
|
+
content: [{
|
|
3586
|
+
type: "text",
|
|
3587
|
+
text: `UES planning salvage: recovered a valid plan from ${planCandidate.source} architect output`,
|
|
3588
|
+
}],
|
|
3589
|
+
details: {
|
|
3590
|
+
mode: "execute",
|
|
3591
|
+
phase: "planning-salvage",
|
|
3592
|
+
source: planCandidate.source,
|
|
3593
|
+
traceID,
|
|
3594
|
+
},
|
|
3595
|
+
});
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
const firstPlanValid =
|
|
3599
|
+
structuredPlan &&
|
|
3600
|
+
structuredValidation?.valid === true;
|
|
3601
|
+
|
|
3602
|
+
if (!firstPlanValid) {
|
|
3603
|
+
const recoveryEvidence = failureDelta([
|
|
3604
|
+
architect.exitCode !== 0 || architect.stopReason
|
|
3605
|
+
? "Previous architect pass stopped before a valid plan: " + String(architect.stopReason || architect.exitCode)
|
|
3606
|
+
: "Previous architect pass returned an invalid plan.",
|
|
3607
|
+
structuredValidation
|
|
3608
|
+
? JSON.stringify(structuredValidation, null, 2)
|
|
3609
|
+
: "UES_PLAN_JSON marker or valid JSON object was missing.",
|
|
3610
|
+
"RECOVERY RULE: do not restart repository exploration. Reuse the existing context/evidence, perform at most one targeted lookup for any blocking gap, then return the corrected plan immediately.",
|
|
3611
|
+
"acceptance and verification must be non-empty string arrays; risk must be low|medium|high|critical; descriptive prose belongs in riskNotes.",
|
|
3612
|
+
].join("\n"), { maxChars: 4200 });
|
|
3613
|
+
|
|
3614
|
+
architect = await run("ues-architect", planInstruction, 2, recoveryEvidence);
|
|
3615
|
+
if (isAbortedRun(architect)) return abortedResponse(architect, "plan-recovery");
|
|
3616
|
+
planCandidate = extractValidatedPlan(architect.output);
|
|
3617
|
+
structuredPlan = planCandidate.plan;
|
|
3618
|
+
structuredValidation = planCandidate.validation;
|
|
3619
|
+
if (planCandidate.salvaged && structuredValidation?.valid === true) {
|
|
3620
|
+
onUpdate?.({
|
|
2643
3621
|
content: [{
|
|
2644
3622
|
type: "text",
|
|
2645
|
-
text:
|
|
2646
|
-
(structuredValidation ? JSON.stringify(structuredValidation, null, 2) : architect.output),
|
|
3623
|
+
text: `UES planning salvage: recovery produced a valid ${planCandidate.source} plan`,
|
|
2647
3624
|
}],
|
|
2648
|
-
details: {
|
|
2649
|
-
|
|
2650
|
-
|
|
3625
|
+
details: {
|
|
3626
|
+
mode: "execute",
|
|
3627
|
+
phase: "planning-salvage",
|
|
3628
|
+
source: planCandidate.source,
|
|
3629
|
+
traceID,
|
|
3630
|
+
},
|
|
3631
|
+
});
|
|
2651
3632
|
}
|
|
2652
3633
|
}
|
|
2653
3634
|
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
3635
|
+
if (
|
|
3636
|
+
!structuredPlan ||
|
|
3637
|
+
structuredValidation?.valid !== true
|
|
3638
|
+
) {
|
|
3639
|
+
return {
|
|
3640
|
+
content: [{
|
|
3641
|
+
type: "text",
|
|
3642
|
+
text: "Long-horizon planning exhausted its bounded fast-planning recovery without a valid deterministic task graph.\n\n" +
|
|
3643
|
+
(structuredValidation ? JSON.stringify(structuredValidation, null, 2) : architect.output),
|
|
3644
|
+
}],
|
|
3645
|
+
details: { mode: "execute", policy, steps, structuredPlan, structuredValidation },
|
|
3646
|
+
isError: true,
|
|
3647
|
+
};
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
let phaseGate = enforcePhaseGates(structuredPlan, executionContract);
|
|
3651
|
+
if (phaseGate.valid !== true) {
|
|
3652
|
+
const phaseRevisionEvidence = failureDelta([
|
|
3653
|
+
"The deterministic explicit-phase gate rejected the plan.",
|
|
3654
|
+
"Do not rescan the repository. Revise only phase assignment/coverage/dependencies.",
|
|
3655
|
+
"Every execution task must include an integer phase. Every explicit execution PHASE must have at least one task. Constraint-only phases are invariants, not tasks.",
|
|
3656
|
+
"Phase gate errors:",
|
|
3657
|
+
phaseGate.errors.join("\n"),
|
|
3658
|
+
"Current plan:",
|
|
3659
|
+
JSON.stringify(structuredPlan, null, 2),
|
|
3660
|
+
].join("\n"), { maxChars: 6200 });
|
|
3661
|
+
const phaseRevisedArchitect = await run(
|
|
3662
|
+
"ues-architect",
|
|
3663
|
+
planInstruction,
|
|
3664
|
+
2,
|
|
3665
|
+
phaseRevisionEvidence,
|
|
3666
|
+
);
|
|
3667
|
+
if (isAbortedRun(phaseRevisedArchitect)) {
|
|
3668
|
+
return abortedResponse(phaseRevisedArchitect, "phase-plan-auto-revise");
|
|
3669
|
+
}
|
|
3670
|
+
const phaseRevisedCandidate = extractValidatedPlan(phaseRevisedArchitect.output);
|
|
3671
|
+
if (phaseRevisedCandidate.plan && phaseRevisedCandidate.validation?.valid === true) {
|
|
3672
|
+
const revisedPhaseGate = enforcePhaseGates(
|
|
3673
|
+
phaseRevisedCandidate.plan,
|
|
3674
|
+
executionContract,
|
|
3675
|
+
);
|
|
3676
|
+
if (revisedPhaseGate.valid === true) {
|
|
3677
|
+
architect = phaseRevisedArchitect;
|
|
3678
|
+
structuredPlan = revisedPhaseGate.plan;
|
|
3679
|
+
structuredValidation = phaseRevisedCandidate.validation;
|
|
3680
|
+
phaseGate = revisedPhaseGate;
|
|
3681
|
+
onUpdate?.({
|
|
3682
|
+
content: [{
|
|
3683
|
+
type: "text",
|
|
3684
|
+
text: "UES phase gate: auto-revised phase coverage/dependencies once and recovered a valid gated plan",
|
|
3685
|
+
}],
|
|
3686
|
+
details: { mode: "execute", phase: "phase-plan-auto-revise", phaseGate, traceID },
|
|
3687
|
+
});
|
|
3688
|
+
} else {
|
|
3689
|
+
phaseGate = revisedPhaseGate;
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
if (phaseGate.valid !== true) {
|
|
3694
|
+
return {
|
|
3695
|
+
content: [{
|
|
3696
|
+
type: "text",
|
|
3697
|
+
text:
|
|
3698
|
+
"Explicit phase contract gate did not pass after bounded auto-recovery. UES will not flatten or skip user-declared phases.\n\n" +
|
|
3699
|
+
phaseGate.errors.join("\n"),
|
|
3700
|
+
}],
|
|
3701
|
+
details: { mode: "execute", policy, steps, structuredPlan, phaseGate, executionContract },
|
|
3702
|
+
isError: true,
|
|
3703
|
+
};
|
|
3704
|
+
}
|
|
3705
|
+
structuredPlan = phaseGate.plan;
|
|
3706
|
+
|
|
3707
|
+
const buildPlanCheckTask = () => [
|
|
3708
|
+
"Validate the following inline plan against the current repository. If persistent SPEC/PLAN files do not exist yet, evaluate this inline plan directly instead of failing only because those files are absent.",
|
|
3709
|
+
"Use declared files and dependencies first. Do not inventory the repository. Resolve only concrete grounding gaps, then return the verdict.",
|
|
3710
|
+
"",
|
|
3711
|
+
"Original task:",
|
|
3712
|
+
params.task,
|
|
3713
|
+
"",
|
|
3714
|
+
"Inline plan:",
|
|
3715
|
+
structuredPlan ? JSON.stringify(structuredPlan, null, 2) : architect.output,
|
|
3716
|
+
].join("\n");
|
|
3717
|
+
|
|
3718
|
+
let planCheckTask = buildPlanCheckTask();
|
|
3719
|
+
let planCheck = await run("ues-plan-checker", planCheckTask, 1);
|
|
2667
3720
|
if (isAbortedRun(planCheck)) return abortedResponse(planCheck, "plan-verification");
|
|
3721
|
+
|
|
3722
|
+
const planCheckTransportFailure =
|
|
3723
|
+
planCheck.verdict !== "PASS" &&
|
|
3724
|
+
planCheck.verdict !== "REVISE" &&
|
|
3725
|
+
(
|
|
3726
|
+
planCheck.exitCode !== 0 ||
|
|
3727
|
+
/hard-timeout|idle-timeout|UES RPC hard-timeout|UES RPC idle-timeout/i.test(
|
|
3728
|
+
String(planCheck.output || "") + "\n" +
|
|
3729
|
+
String(planCheck.stderr || "") + "\n" +
|
|
3730
|
+
String(planCheck.stopReason || ""),
|
|
3731
|
+
)
|
|
3732
|
+
);
|
|
3733
|
+
|
|
3734
|
+
if (planCheckTransportFailure) {
|
|
3735
|
+
const planCheckRecoveryEvidence = failureDelta([
|
|
3736
|
+
"Previous plan-checker runtime failure:",
|
|
3737
|
+
String(planCheck.output || planCheck.stderr || planCheck.stopReason || "unknown"),
|
|
3738
|
+
"Reuse warm context. Do not scan broadly. Check only unresolved declared paths/dependencies and return PASS or REVISE.",
|
|
3739
|
+
].join("\n"), { maxChars: 2600 });
|
|
3740
|
+
|
|
3741
|
+
planCheck = await run(
|
|
3742
|
+
"ues-plan-checker",
|
|
3743
|
+
planCheckTask,
|
|
3744
|
+
2,
|
|
3745
|
+
planCheckRecoveryEvidence,
|
|
3746
|
+
);
|
|
3747
|
+
if (isAbortedRun(planCheck)) return abortedResponse(planCheck, "plan-verification-recovery");
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3750
|
+
if (planCheck.verdict === "REVISE") {
|
|
3751
|
+
const revisionEvidence = failureDelta([
|
|
3752
|
+
"The independent plan-checker requested a bounded revision.",
|
|
3753
|
+
"Do not restart broad repository exploration. Change only the rejected plan claims/scope.",
|
|
3754
|
+
"Plan-checker feedback:",
|
|
3755
|
+
String(planCheck.output || ""),
|
|
3756
|
+
"Current plan:",
|
|
3757
|
+
JSON.stringify(structuredPlan, null, 2),
|
|
3758
|
+
].join("\n"), { maxChars: 5200 });
|
|
3759
|
+
|
|
3760
|
+
const revisedArchitect = await run(
|
|
3761
|
+
"ues-architect",
|
|
3762
|
+
planInstruction,
|
|
3763
|
+
2,
|
|
3764
|
+
revisionEvidence,
|
|
3765
|
+
);
|
|
3766
|
+
if (isAbortedRun(revisedArchitect)) return abortedResponse(revisedArchitect, "plan-auto-revise");
|
|
3767
|
+
const revisedCandidate = extractValidatedPlan(revisedArchitect.output);
|
|
3768
|
+
if (revisedCandidate.plan && revisedCandidate.validation?.valid === true) {
|
|
3769
|
+
architect = revisedArchitect;
|
|
3770
|
+
structuredPlan = revisedCandidate.plan;
|
|
3771
|
+
structuredValidation = revisedCandidate.validation;
|
|
3772
|
+
planCheckTask = buildPlanCheckTask();
|
|
3773
|
+
onUpdate?.({
|
|
3774
|
+
content: [{ type: "text", text: "UES plan gate: auto-revised the rejected plan; re-checking once" }],
|
|
3775
|
+
details: { mode: "execute", phase: "plan-auto-revise", policy, traceID },
|
|
3776
|
+
});
|
|
3777
|
+
planCheck = await run(
|
|
3778
|
+
"ues-plan-checker",
|
|
3779
|
+
planCheckTask,
|
|
3780
|
+
2,
|
|
3781
|
+
failureDelta(String(planCheck.output || ""), { maxChars: 2600 }),
|
|
3782
|
+
);
|
|
3783
|
+
if (isAbortedRun(planCheck)) return abortedResponse(planCheck, "plan-auto-revise-check");
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
const finalPhaseGate = enforcePhaseGates(structuredPlan, executionContract);
|
|
3788
|
+
if (finalPhaseGate.valid !== true) {
|
|
3789
|
+
return {
|
|
3790
|
+
content: [{
|
|
3791
|
+
type: "text",
|
|
3792
|
+
text:
|
|
3793
|
+
"Plan revision lost the deterministic explicit-phase contract. UES will not execute it.\n\n" +
|
|
3794
|
+
finalPhaseGate.errors.join("\n"),
|
|
3795
|
+
}],
|
|
3796
|
+
details: { mode: "execute", policy, steps, structuredPlan, finalPhaseGate, executionContract },
|
|
3797
|
+
isError: true,
|
|
3798
|
+
};
|
|
3799
|
+
}
|
|
3800
|
+
structuredPlan = finalPhaseGate.plan;
|
|
3801
|
+
|
|
2668
3802
|
if (planCheck.exitCode !== 0 || planCheck.verdict !== "PASS") {
|
|
2669
3803
|
return {
|
|
2670
|
-
content: [{ type: "text", text: `Plan gate did not pass:\n\n${planCheck.output}` }],
|
|
3804
|
+
content: [{ type: "text", text: `Plan gate did not pass after bounded auto-recovery:\n\n${planCheck.output}` }],
|
|
2671
3805
|
details: { mode: "execute", policy, steps, structuredPlan },
|
|
2672
3806
|
isError: true,
|
|
2673
3807
|
};
|
|
2674
3808
|
}
|
|
2675
|
-
|
|
2676
3809
|
const durableRequested =
|
|
2677
3810
|
policy.mode === "long-horizon" || policy.profile?.durableState === true;
|
|
2678
3811
|
if (durableRequested) {
|
|
@@ -2694,6 +3827,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
2694
3827
|
planCheck.output,
|
|
2695
3828
|
signal,
|
|
2696
3829
|
);
|
|
3830
|
+
durableWork.executionArtifacts = await persistExecutionContractArtifacts(
|
|
3831
|
+
durableWork.dir,
|
|
3832
|
+
executionContract,
|
|
3833
|
+
);
|
|
2697
3834
|
onUpdate?.({
|
|
2698
3835
|
content: [{
|
|
2699
3836
|
type: "text",
|
|
@@ -2727,6 +3864,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2727
3864
|
inheritedModel,
|
|
2728
3865
|
inheritedThinking,
|
|
2729
3866
|
maxAttempts,
|
|
3867
|
+
rootPolicy: policy,
|
|
2730
3868
|
durableSlug: durableWork?.slug,
|
|
2731
3869
|
traceID,
|
|
2732
3870
|
signal,
|
|
@@ -2833,12 +3971,81 @@ export default function (pi: ExtensionAPI) {
|
|
|
2833
3971
|
}
|
|
2834
3972
|
}
|
|
2835
3973
|
|
|
3974
|
+
const verdictMatrix = buildFinalVerdictMatrix(params.task, {
|
|
3975
|
+
contract: executionContract,
|
|
3976
|
+
primaryPass: true,
|
|
3977
|
+
integrationPass: true,
|
|
3978
|
+
primaryOutput: (scheduled.results || [])
|
|
3979
|
+
.map((row: any) => row?.verification?.output || row?.implementation?.output || "")
|
|
3980
|
+
.filter(Boolean)
|
|
3981
|
+
.join("\n"),
|
|
3982
|
+
integrationOutput: integration.output,
|
|
3983
|
+
visualOutput: visualResult?.output || "",
|
|
3984
|
+
primaryChecks: (scheduled.results || [])
|
|
3985
|
+
.flatMap((row: any) => Object.values(row?.verification?.report?.sections || {}))
|
|
3986
|
+
.filter(Boolean)
|
|
3987
|
+
.join("\n"),
|
|
3988
|
+
integrationChecks: integration.report?.sections?.["checks-run"] || "",
|
|
3989
|
+
visualChecks: visualResult?.report?.sections?.["checks-run"] || "",
|
|
3990
|
+
});
|
|
3991
|
+
|
|
3992
|
+
const finalEvidenceForContract = [
|
|
3993
|
+
integration.output,
|
|
3994
|
+
visualResult?.output || "",
|
|
3995
|
+
].filter(Boolean).join("\n\n--- VISUAL ---\n\n");
|
|
3996
|
+
|
|
3997
|
+
if (verdictMatrix.final !== "PASS") {
|
|
3998
|
+
if (durableWork) {
|
|
3999
|
+
await durableRecordIntegration(
|
|
4000
|
+
cwd,
|
|
4001
|
+
durableWork.slug,
|
|
4002
|
+
"PARTIAL",
|
|
4003
|
+
finalEvidenceForContract,
|
|
4004
|
+
signal,
|
|
4005
|
+
).catch(() => {});
|
|
4006
|
+
await finalizeExecutionContractArtifacts(
|
|
4007
|
+
durableWork.dir,
|
|
4008
|
+
executionContract,
|
|
4009
|
+
verdictMatrix,
|
|
4010
|
+
finalEvidenceForContract,
|
|
4011
|
+
).catch(() => null);
|
|
4012
|
+
}
|
|
4013
|
+
const deviceOnlyPending =
|
|
4014
|
+
verdictMatrix.final === "SOURCE_RUNTIME_PASS_DEVICE_NOT_VERIFIED";
|
|
4015
|
+
return {
|
|
4016
|
+
content: [{
|
|
4017
|
+
type: "text",
|
|
4018
|
+
text: [
|
|
4019
|
+
deviceOnlyPending
|
|
4020
|
+
? "UES source/runtime verification passed, but requested real-device verification is still pending."
|
|
4021
|
+
: "UES deterministic final gate is not fully verified.",
|
|
4022
|
+
"",
|
|
4023
|
+
verdictMatrix.source,
|
|
4024
|
+
verdictMatrix.runtime,
|
|
4025
|
+
verdictMatrix.dbClean,
|
|
4026
|
+
verdictMatrix.device,
|
|
4027
|
+
"",
|
|
4028
|
+
integration.output,
|
|
4029
|
+
].join("\n"),
|
|
4030
|
+
}],
|
|
4031
|
+
details: {
|
|
4032
|
+
mode: "execute",
|
|
4033
|
+
policy,
|
|
4034
|
+
steps,
|
|
4035
|
+
structuredPlan,
|
|
4036
|
+
scheduled,
|
|
4037
|
+
durableWork,
|
|
4038
|
+
verdictMatrix,
|
|
4039
|
+
executionContract,
|
|
4040
|
+
traceID,
|
|
4041
|
+
},
|
|
4042
|
+
isError: !deviceOnlyPending,
|
|
4043
|
+
};
|
|
4044
|
+
}
|
|
4045
|
+
|
|
2836
4046
|
let durableFinalization: any = null;
|
|
2837
4047
|
if (durableWork) {
|
|
2838
|
-
const finalEvidence =
|
|
2839
|
-
integration.output,
|
|
2840
|
-
visualResult?.output || "",
|
|
2841
|
-
].filter(Boolean).join("\n\n--- VISUAL ---\n\n");
|
|
4048
|
+
const finalEvidence = finalEvidenceForContract;
|
|
2842
4049
|
try {
|
|
2843
4050
|
durableFinalization = await durableRecordIntegration(
|
|
2844
4051
|
cwd,
|
|
@@ -2861,6 +4068,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
2861
4068
|
}
|
|
2862
4069
|
}
|
|
2863
4070
|
|
|
4071
|
+
if (durableWork) {
|
|
4072
|
+
await finalizeExecutionContractArtifacts(
|
|
4073
|
+
durableWork.dir,
|
|
4074
|
+
executionContract,
|
|
4075
|
+
verdictMatrix,
|
|
4076
|
+
finalEvidenceForContract,
|
|
4077
|
+
).catch(() => null);
|
|
4078
|
+
}
|
|
4079
|
+
|
|
2864
4080
|
const memoryFiles = [...new Set(structuredPlan.tasks.flatMap((task: any) => taskWriteFiles(task)))];
|
|
2865
4081
|
const memory = durableWork
|
|
2866
4082
|
? durableFinalization?.finalized?.memory || null
|
|
@@ -2872,6 +4088,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
2872
4088
|
`UES scheduled execution PASS across ${structuredPlan.tasks.length} task(s).`,
|
|
2873
4089
|
`Safe waves: ${scheduled.schedule?.safeWaves?.length || 0}; integrations: ${scheduled.integrations?.length || 0}.`,
|
|
2874
4090
|
durableWork ? `Durable state: .ues-work/${durableWork.slug} finalized with fresh integration receipt.` : "",
|
|
4091
|
+
verdictMatrix.source,
|
|
4092
|
+
verdictMatrix.runtime,
|
|
4093
|
+
verdictMatrix.dbClean,
|
|
4094
|
+
verdictMatrix.device,
|
|
2875
4095
|
"",
|
|
2876
4096
|
integration.output,
|
|
2877
4097
|
].join("\n"),
|
|
@@ -2886,6 +4106,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
2886
4106
|
memory,
|
|
2887
4107
|
durableWork,
|
|
2888
4108
|
durableFinalization,
|
|
4109
|
+
verdictMatrix,
|
|
4110
|
+
executionContract,
|
|
2889
4111
|
traceID,
|
|
2890
4112
|
},
|
|
2891
4113
|
};
|
|
@@ -2919,9 +4141,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
2919
4141
|
recentFailure = diagnosis.output;
|
|
2920
4142
|
}
|
|
2921
4143
|
|
|
4144
|
+
const fastDecision = turboFastPathDecision(policy, {
|
|
4145
|
+
role: "executor",
|
|
4146
|
+
attempt,
|
|
4147
|
+
browserRequested: browserEvidenceNeeded(params.task, "executor"),
|
|
4148
|
+
visualRequired: visualEvidenceNeeded(params.task),
|
|
4149
|
+
});
|
|
4150
|
+
const fastBoundedLane = fastDecision.eligible;
|
|
4151
|
+
const fastAttemptStartedAt = Date.now();
|
|
2922
4152
|
const executorTask = [
|
|
2923
4153
|
params.task,
|
|
2924
4154
|
recentFailure ? "\nEvidence from diagnosis/previous failed verification:\n" + cap(recentFailure, 7000) : "",
|
|
4155
|
+
fastBoundedLane
|
|
4156
|
+
? "\nFAST bounded rule: stay on the named target file. Read that file first; do not inventory the repository or run broad searches unless the target is missing or direct evidence proves wider scope. Make the smallest edit, then run one focused behavioral check and stop once a fresh PASS receipt covers every explicit branch. If no project-native JS/TS test exists, use one temporary .ues-cache/fast-acceptance.test.mjs probe, run node --test .ues-cache/fast-acceptance.test.mjs, and remove only that probe afterwards. Syntax/build alone is insufficient."
|
|
4157
|
+
: "",
|
|
2925
4158
|
"\nImplement the smallest coherent change. Do not push, publish, deploy, rewrite history, or broaden scope without evidence.",
|
|
2926
4159
|
].filter(Boolean).join("\n");
|
|
2927
4160
|
|
|
@@ -2938,23 +4171,77 @@ export default function (pi: ExtensionAPI) {
|
|
|
2938
4171
|
continue;
|
|
2939
4172
|
}
|
|
2940
4173
|
|
|
2941
|
-
const
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
params.task,
|
|
2946
|
-
"",
|
|
2947
|
-
"Implementation handoff (not proof by itself):",
|
|
2948
|
-
implementation.output,
|
|
2949
|
-
].join("\n"),
|
|
2950
|
-
attempt,
|
|
2951
|
-
recentFailure || undefined,
|
|
4174
|
+
const inheritedDirtyCheck = detectInheritedDirtyViolations(
|
|
4175
|
+
cwd,
|
|
4176
|
+
executionContract.inheritedDirty,
|
|
4177
|
+
executionContract.approvedInheritedDirtyPaths || [],
|
|
2952
4178
|
);
|
|
2953
|
-
if (
|
|
4179
|
+
if (!inheritedDirtyCheck.safe) {
|
|
4180
|
+
await recordRuntimeOutcome(implementation, params.task, false, attempt - 1);
|
|
4181
|
+
return {
|
|
4182
|
+
content: [{
|
|
4183
|
+
type: "text",
|
|
4184
|
+
text: [
|
|
4185
|
+
"UES inherited dirty-work guard stopped completion.",
|
|
4186
|
+
"The implementation changed pre-existing dirty source work that was not explicitly authorized by the original task.",
|
|
4187
|
+
"UES will not auto-restore or overwrite these files.",
|
|
4188
|
+
"",
|
|
4189
|
+
...inheritedDirtyCheck.violations.map((item: any) => "- " + item.path),
|
|
4190
|
+
].join("\n"),
|
|
4191
|
+
}],
|
|
4192
|
+
details: {
|
|
4193
|
+
mode: "execute",
|
|
4194
|
+
policy,
|
|
4195
|
+
steps,
|
|
4196
|
+
attempts: attempt,
|
|
4197
|
+
executionContract,
|
|
4198
|
+
inheritedDirtyCheck,
|
|
4199
|
+
traceID,
|
|
4200
|
+
},
|
|
4201
|
+
isError: true,
|
|
4202
|
+
};
|
|
4203
|
+
}
|
|
4204
|
+
|
|
4205
|
+
let verification: RunResult;
|
|
4206
|
+
let fastGate: any = null;
|
|
4207
|
+
if (fastBoundedLane) {
|
|
4208
|
+
const snapshot = runtimeWorkspaceSnapshot(cwd);
|
|
4209
|
+
const receipts = await listReusableVerification(cwd, {
|
|
4210
|
+
limit: 12,
|
|
4211
|
+
maxAgeMs: 10 * 60_000,
|
|
4212
|
+
previewBytes: 2200,
|
|
4213
|
+
...(snapshot.cacheable === true && snapshot.fingerprint ? { workspaceFingerprint: snapshot.fingerprint } : {}),
|
|
4214
|
+
}).catch(() => ({ results: [] }));
|
|
4215
|
+
fastGate = evaluateFastVerificationGate({ policy, implementation, receipts: receipts?.results || [], attemptStartedAtMs: fastAttemptStartedAt, visualRequired: false });
|
|
4216
|
+
}
|
|
4217
|
+
if (fastGate?.passed === true) {
|
|
4218
|
+
verification = {
|
|
4219
|
+
agent: "ues-deterministic-verifier", task: params.task, cwd, exitCode: 0,
|
|
4220
|
+
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
|
+
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." } },
|
|
4223
|
+
optimizations: { fastDeterministicVerification: true, behavioralReceiptCount: fastGate.behavioralReceipts.length },
|
|
4224
|
+
};
|
|
4225
|
+
steps.push(verification);
|
|
4226
|
+
onUpdate?.({
|
|
4227
|
+
content: [{ type: "text", text: "UES Turbo Fast Path: reused " + fastGate.behavioralReceipts.length + " fresh behavioral receipt(s); skipped verifier model turn" }],
|
|
4228
|
+
details: { mode: "execute", phase: "turbo-fast-verified", policy, fastGate, fastDecision, traceID },
|
|
4229
|
+
});
|
|
4230
|
+
} else {
|
|
4231
|
+
verification = await run(
|
|
4232
|
+
"ues-verifier",
|
|
4233
|
+
[
|
|
4234
|
+
fastBoundedLane ? "FAST bounded verification: independently verify the final target file and every explicit acceptance branch. Run one narrow behavioral check; compilation/syntax alone is insufficient. Return FAIL if any enumerated edge/error/idempotency/non-mutation case lacks fresh executable evidence." : "Independently verify the current working tree against this task:",
|
|
4235
|
+
params.task, "", "Implementation handoff (not proof by itself):", implementation.output,
|
|
4236
|
+
fastGate?.reason ? "\nFAST receipt gate did not short-circuit because: " + fastGate.reason : "",
|
|
4237
|
+
].join("\n"), attempt, recentFailure || undefined,
|
|
4238
|
+
);
|
|
4239
|
+
if (isAbortedRun(verification)) return abortedResponse(verification, "verification");
|
|
4240
|
+
}
|
|
2954
4241
|
const verified = verification.exitCode === 0 && verification.verdict === "PASS";
|
|
2955
|
-
await recordRuntimeOutcome(implementation, params.task, verified, attempt - 1);
|
|
2956
4242
|
if (!verified) {
|
|
2957
4243
|
recentFailure = verification.output;
|
|
4244
|
+
await recordRuntimeOutcome(implementation, params.task, false, attempt - 1);
|
|
2958
4245
|
continue;
|
|
2959
4246
|
}
|
|
2960
4247
|
|
|
@@ -2973,6 +4260,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2973
4260
|
if (isAbortedRun(integrationResult)) return abortedResponse(integrationResult, "integration-verification");
|
|
2974
4261
|
if (integrationResult.exitCode !== 0 || integrationResult.verdict !== "PASS") {
|
|
2975
4262
|
recentFailure = integrationResult.output;
|
|
4263
|
+
await recordRuntimeOutcome(implementation, params.task, false, attempt - 1);
|
|
2976
4264
|
if (attempt < maxAttempts) continue;
|
|
2977
4265
|
return {
|
|
2978
4266
|
content: [{ type: "text", text: `Integration verification did not pass:\n\n${integrationResult.output}` }],
|
|
@@ -3000,6 +4288,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3000
4288
|
if (isAbortedRun(visualResult)) return abortedResponse(visualResult, "visual-verification");
|
|
3001
4289
|
if (visualResult.exitCode !== 0 || visualResult.verdict !== "PASS") {
|
|
3002
4290
|
recentFailure = visualResult.output;
|
|
4291
|
+
await recordRuntimeOutcome(implementation, params.task, false, attempt - 1);
|
|
3003
4292
|
if (attempt < maxAttempts) continue;
|
|
3004
4293
|
return {
|
|
3005
4294
|
content: [{
|
|
@@ -3012,6 +4301,87 @@ export default function (pi: ExtensionAPI) {
|
|
|
3012
4301
|
}
|
|
3013
4302
|
}
|
|
3014
4303
|
|
|
4304
|
+
const completionSnapshot = runtimeWorkspaceSnapshot(cwd);
|
|
4305
|
+
const completionReceipts = await listReusableVerification(cwd, {
|
|
4306
|
+
limit: 24,
|
|
4307
|
+
maxAgeMs: 10 * 60_000,
|
|
4308
|
+
previewBytes: 1200,
|
|
4309
|
+
...(completionSnapshot.cacheable === true && completionSnapshot.fingerprint
|
|
4310
|
+
? { workspaceFingerprint: completionSnapshot.fingerprint }
|
|
4311
|
+
: {}),
|
|
4312
|
+
}).catch(() => ({ results: [] }));
|
|
4313
|
+
const freshReceipts = (completionReceipts?.results || []).filter((row: any) => {
|
|
4314
|
+
const finished = Date.parse(row?.finishedAt || row?.receipt?.finishedAt || "");
|
|
4315
|
+
return Number.isFinite(finished) && finished >= fastAttemptStartedAt;
|
|
4316
|
+
});
|
|
4317
|
+
const completionAudit = auditCompletion({
|
|
4318
|
+
verification,
|
|
4319
|
+
integration: integrationResult,
|
|
4320
|
+
visual: visualResult,
|
|
4321
|
+
requireIntegration: policy.requireIntegrationVerification === true,
|
|
4322
|
+
requireVisual: visualEvidenceNeeded(params.task),
|
|
4323
|
+
workspaceSnapshot: completionSnapshot,
|
|
4324
|
+
behavioralReceipts: freshReceipts,
|
|
4325
|
+
requireBehavioralReceipt: policy.requireBehavioralReceipt !== false,
|
|
4326
|
+
});
|
|
4327
|
+
if (!completionAudit.passed) {
|
|
4328
|
+
recentFailure = "Completion auditor rejected PASS: " + completionAudit.failures.join(", ");
|
|
4329
|
+
await recordRuntimeOutcome(implementation, params.task, false, attempt - 1);
|
|
4330
|
+
if (attempt < maxAttempts) continue;
|
|
4331
|
+
return {
|
|
4332
|
+
content: [{ type: "text", text: recentFailure }],
|
|
4333
|
+
details: { mode: "execute", policy, steps, attempts: attempt, completionAudit, traceID },
|
|
4334
|
+
isError: true,
|
|
4335
|
+
};
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4338
|
+
const verdictMatrix = buildFinalVerdictMatrix(params.task, {
|
|
4339
|
+
contract: executionContract,
|
|
4340
|
+
primaryPass: true,
|
|
4341
|
+
integrationPass: integrationResult ? true : undefined,
|
|
4342
|
+
primaryOutput: verification.output,
|
|
4343
|
+
integrationOutput: integrationResult?.output || "",
|
|
4344
|
+
visualOutput: visualResult?.output || "",
|
|
4345
|
+
primaryChecks: verification.report?.sections?.["checks-run"] || "",
|
|
4346
|
+
integrationChecks: integrationResult?.report?.sections?.["checks-run"] || "",
|
|
4347
|
+
visualChecks: visualResult?.report?.sections?.["checks-run"] || "",
|
|
4348
|
+
});
|
|
4349
|
+
|
|
4350
|
+
if (verdictMatrix.final !== "PASS") {
|
|
4351
|
+
await recordRuntimeOutcome(implementation, params.task, false, attempt - 1);
|
|
4352
|
+
const deviceOnlyPending =
|
|
4353
|
+
verdictMatrix.final === "SOURCE_RUNTIME_PASS_DEVICE_NOT_VERIFIED";
|
|
4354
|
+
return {
|
|
4355
|
+
content: [{
|
|
4356
|
+
type: "text",
|
|
4357
|
+
text: [
|
|
4358
|
+
deviceOnlyPending
|
|
4359
|
+
? "UES source/runtime verification passed, but requested real-device verification is still pending."
|
|
4360
|
+
: "UES deterministic final gate is not fully verified.",
|
|
4361
|
+
"",
|
|
4362
|
+
verdictMatrix.source,
|
|
4363
|
+
verdictMatrix.runtime,
|
|
4364
|
+
verdictMatrix.dbClean,
|
|
4365
|
+
verdictMatrix.device,
|
|
4366
|
+
"",
|
|
4367
|
+
integrationResult?.output || verification.output,
|
|
4368
|
+
].join("\n"),
|
|
4369
|
+
}],
|
|
4370
|
+
details: {
|
|
4371
|
+
mode: "execute",
|
|
4372
|
+
policy,
|
|
4373
|
+
steps,
|
|
4374
|
+
attempts: attempt,
|
|
4375
|
+
completionAudit,
|
|
4376
|
+
verdictMatrix,
|
|
4377
|
+
executionContract,
|
|
4378
|
+
traceID,
|
|
4379
|
+
},
|
|
4380
|
+
isError: !deviceOnlyPending,
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
|
|
4384
|
+
await recordRuntimeOutcome(implementation, params.task, true, attempt - 1);
|
|
3015
4385
|
const memory = await rememberVerifiedTask(cwd, params.task, verification, integrationResult);
|
|
3016
4386
|
const final = steps.at(-1);
|
|
3017
4387
|
return {
|
|
@@ -3020,11 +4390,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
3020
4390
|
text: [
|
|
3021
4391
|
`UES execution PASS after ${attempt} attempt(s).`,
|
|
3022
4392
|
`Policy: ${policy.executionProfile}/${policy.risk}; model tier: ${implementation.modelTier || "default"}.`,
|
|
4393
|
+
verdictMatrix.source,
|
|
4394
|
+
verdictMatrix.runtime,
|
|
4395
|
+
verdictMatrix.dbClean,
|
|
4396
|
+
verdictMatrix.device,
|
|
3023
4397
|
"",
|
|
3024
4398
|
final?.output || verification.output,
|
|
3025
4399
|
].join("\n"),
|
|
3026
4400
|
}],
|
|
3027
|
-
details: { mode: "execute", policy, steps, attempts: attempt, memory, traceID },
|
|
4401
|
+
details: { mode: "execute", policy, steps, attempts: attempt, memory, completionAudit, verdictMatrix, executionContract, traceID },
|
|
3028
4402
|
};
|
|
3029
4403
|
}
|
|
3030
4404
|
|
|
@@ -3037,6 +4411,355 @@ export default function (pi: ExtensionAPI) {
|
|
|
3037
4411
|
isError: true,
|
|
3038
4412
|
};
|
|
3039
4413
|
},
|
|
4414
|
+
};
|
|
4415
|
+
pi.registerTool(uesExecuteTool);
|
|
4416
|
+
|
|
4417
|
+
// Extension commands are resolved before prompt templates in Pi. Registering
|
|
4418
|
+
// /ues-run here makes controller admission deterministic: weak models never
|
|
4419
|
+
// have to remember to call ues_execute themselves.
|
|
4420
|
+
pi.registerCommand("ues-status", {
|
|
4421
|
+
description: "Show the loaded UES runtime version and current session policy basics",
|
|
4422
|
+
handler: async (_args, ctx) => {
|
|
4423
|
+
const status = [
|
|
4424
|
+
"UES runtime: " + PACKAGE_VERSION,
|
|
4425
|
+
"Package root: " + PACKAGE_ROOT,
|
|
4426
|
+
"Child runtime: " + CHILD_RUNTIME,
|
|
4427
|
+
"Adaptive context: " + (ADAPTIVE_CONTEXT_ENABLED ? "on" : "off"),
|
|
4428
|
+
"Micro skills: " + (MICRO_SKILLS_ENABLED ? "on" : "off"),
|
|
4429
|
+
"Turbo Fast Path: on",
|
|
4430
|
+
"Parent UES tools hidden outside UES runs: on",
|
|
4431
|
+
"Zero-friction engineering admission: " + (AUTO_ADMISSION_ENABLED ? "on (native / auto / high-risk + safe continuation)" : "off"),
|
|
4432
|
+
"Git-root artifact guard: on",
|
|
4433
|
+
"Inherited dirty-work guard: on",
|
|
4434
|
+
"Local .env mutation guard: on",
|
|
4435
|
+
"Portable temp-path guard: on",
|
|
4436
|
+
"Explicit phase barriers: on",
|
|
4437
|
+
"Independent final verdict matrix: on",
|
|
4438
|
+
"Disk hygiene: bounded + auto-clean",
|
|
4439
|
+
"Writer concurrency: " + MAX_WRITER_CONCURRENCY,
|
|
4440
|
+
].join("\n");
|
|
4441
|
+
pi.sendMessage({
|
|
4442
|
+
customType: "ues-runtime-status",
|
|
4443
|
+
content: status,
|
|
4444
|
+
display: true,
|
|
4445
|
+
details: { version: PACKAGE_VERSION, packageRoot: PACKAGE_ROOT, childRuntime: CHILD_RUNTIME },
|
|
4446
|
+
}, { triggerTurn: false });
|
|
4447
|
+
try { ctx.ui.notify("UES runtime " + PACKAGE_VERSION + " loaded", "info"); } catch {}
|
|
4448
|
+
},
|
|
4449
|
+
});
|
|
4450
|
+
|
|
4451
|
+
pi.registerCommand("ues-clean", {
|
|
4452
|
+
description: "Safely remove stale UES task sandboxes and orphan metadata for the current repository",
|
|
4453
|
+
handler: async (_args, ctx) => {
|
|
4454
|
+
if (uesModeActive()) {
|
|
4455
|
+
try { ctx.ui.notify("UES cleanup refused while a UES task is active", "warning"); } catch {}
|
|
4456
|
+
return;
|
|
4457
|
+
}
|
|
4458
|
+
let workspaceRoot: string;
|
|
4459
|
+
try {
|
|
4460
|
+
workspaceRoot = requireGitWorkspaceRoot(ctx.cwd, "/ues-clean");
|
|
4461
|
+
} catch (error) {
|
|
4462
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4463
|
+
pi.sendMessage({
|
|
4464
|
+
customType: "ues-cleanup-result",
|
|
4465
|
+
content: "UES cleanup refused unsafe workspace.\n" + message,
|
|
4466
|
+
display: true,
|
|
4467
|
+
details: { error: message },
|
|
4468
|
+
}, { triggerTurn: false });
|
|
4469
|
+
try { ctx.ui.notify(message, "error"); } catch {}
|
|
4470
|
+
return;
|
|
4471
|
+
}
|
|
4472
|
+
const cleanup = await pruneOrphanTaskSandboxes(workspaceRoot, {
|
|
4473
|
+
minAgeMs: 5 * 60_000,
|
|
4474
|
+
legacyMinAgeMs: 30 * 60_000,
|
|
4475
|
+
ownedMinAgeMs: 0,
|
|
4476
|
+
reclaimOwnerPid: process.pid,
|
|
4477
|
+
protectedDirs: [...ACTIVE_TASK_SANDBOXES.keys()],
|
|
4478
|
+
}).catch((error) => ({
|
|
4479
|
+
removed: [],
|
|
4480
|
+
skipped: [],
|
|
4481
|
+
sidecarsRemoved: [],
|
|
4482
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4483
|
+
}));
|
|
4484
|
+
const removed = Number(cleanup?.removed?.length || 0);
|
|
4485
|
+
const sidecars = Number(cleanup?.sidecarsRemoved?.length || 0);
|
|
4486
|
+
const skipped = Number(cleanup?.skipped?.length || 0);
|
|
4487
|
+
await stopAllServices(workspaceRoot).catch(() => []);
|
|
4488
|
+
const transientDirs = [".ues-traces", ".ues-services", ".ues-dashboard"];
|
|
4489
|
+
const removedRuntimeDirs: string[] = [];
|
|
4490
|
+
for (const name of transientDirs) {
|
|
4491
|
+
const target = path.join(workspaceRoot, name);
|
|
4492
|
+
if (!fs.existsSync(target)) continue;
|
|
4493
|
+
await fs.promises.rm(target, { recursive: true, force: true }).catch(() => {});
|
|
4494
|
+
if (!fs.existsSync(target)) removedRuntimeDirs.push(name);
|
|
4495
|
+
}
|
|
4496
|
+
|
|
4497
|
+
const cacheDir = path.join(workspaceRoot, ".ues-cache");
|
|
4498
|
+
const removedCacheEntries: string[] = [];
|
|
4499
|
+
if (fs.existsSync(cacheDir)) {
|
|
4500
|
+
const entries = await fs.promises.readdir(cacheDir, { withFileTypes: true }).catch(() => []);
|
|
4501
|
+
for (const entry of entries) {
|
|
4502
|
+
if (entry.name === "evidence-v1") continue;
|
|
4503
|
+
if (
|
|
4504
|
+
entry.name === "semantic-index-v1.json" ||
|
|
4505
|
+
entry.name.startsWith("semantic-index-v1.json.") ||
|
|
4506
|
+
entry.name === "verification-broker-v1.json" ||
|
|
4507
|
+
entry.name.startsWith("verification-broker-v1.json.")
|
|
4508
|
+
) {
|
|
4509
|
+
await fs.promises.rm(path.join(cacheDir, entry.name), { recursive: true, force: true }).catch(() => {});
|
|
4510
|
+
removedCacheEntries.push(entry.name);
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
const evidenceGc = await gcEvidenceStore(workspaceRoot, {
|
|
4515
|
+
maxBytes: 96 * 1024 * 1024,
|
|
4516
|
+
maxEntries: 600,
|
|
4517
|
+
maxAgeDays: 14,
|
|
4518
|
+
}).catch((error) => ({
|
|
4519
|
+
removed: [],
|
|
4520
|
+
removedCount: 0,
|
|
4521
|
+
protectedEntries: 0,
|
|
4522
|
+
protectedBytes: 0,
|
|
4523
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4524
|
+
}));
|
|
4525
|
+
|
|
4526
|
+
if (fs.existsSync(cacheDir)) {
|
|
4527
|
+
const remaining = await fs.promises.readdir(cacheDir).catch(() => ["unknown"]);
|
|
4528
|
+
if (remaining.length === 0) await fs.promises.rm(cacheDir, { recursive: true, force: true }).catch(() => {});
|
|
4529
|
+
}
|
|
4530
|
+
|
|
4531
|
+
const text = [
|
|
4532
|
+
"UES cleanup complete.",
|
|
4533
|
+
"Removed sandboxes: " + removed,
|
|
4534
|
+
"Removed orphan metadata: " + sidecars,
|
|
4535
|
+
"Removed transient runtime dirs: " + (removedRuntimeDirs.join(", ") || "none"),
|
|
4536
|
+
"Removed rebuildable cache entries: " + (removedCacheEntries.length || 0),
|
|
4537
|
+
"Pruned evidence blobs: " + Number(evidenceGc?.removedCount || 0),
|
|
4538
|
+
"Protected verified-memory evidence: " + Number(evidenceGc?.protectedEntries || 0),
|
|
4539
|
+
"Protected/recent entries kept: " + skipped,
|
|
4540
|
+
"Preserved durable state: .ues-work, .ues-memory, .ues-learning, .ues-evals and verified-memory evidence",
|
|
4541
|
+
cleanup?.baseRemoved ? "Sandbox base directory removed because it is empty." : "",
|
|
4542
|
+
cleanup?.error ? "Error: " + cleanup.error : "",
|
|
4543
|
+
].filter(Boolean).join("\n");
|
|
4544
|
+
pi.sendMessage({
|
|
4545
|
+
customType: "ues-cleanup-result",
|
|
4546
|
+
content: text,
|
|
4547
|
+
display: true,
|
|
4548
|
+
details: { ...cleanup, removedRuntimeDirs, removedCacheEntries, evidenceGc },
|
|
4549
|
+
}, { triggerTurn: false });
|
|
4550
|
+
try {
|
|
4551
|
+
ctx.ui.notify(
|
|
4552
|
+
"UES cleanup removed/pruned " +
|
|
4553
|
+
(removed + sidecars + removedRuntimeDirs.length + removedCacheEntries.length + Number(evidenceGc?.removedCount || 0)) +
|
|
4554
|
+
" stale/transient artifact(s)",
|
|
4555
|
+
"info",
|
|
4556
|
+
);
|
|
4557
|
+
} catch {}
|
|
4558
|
+
},
|
|
4559
|
+
});
|
|
4560
|
+
|
|
4561
|
+
directControllerRunner = async (task, ctx, admission, admissionDecision) => {
|
|
4562
|
+
if (directControllerAbort && !directControllerAbort.signal.aborted) {
|
|
4563
|
+
try { ctx.ui.notify("UES controller is already running in this session", "warning"); } catch {}
|
|
4564
|
+
return;
|
|
4565
|
+
}
|
|
4566
|
+
|
|
4567
|
+
let workspaceRoot: string;
|
|
4568
|
+
try {
|
|
4569
|
+
workspaceRoot = requireGitWorkspaceRoot(ctx.cwd, admission === "automatic" ? "automatic UES admission" : "/ues-run");
|
|
4570
|
+
} catch (error) {
|
|
4571
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4572
|
+
pi.sendMessage({
|
|
4573
|
+
customType: "ues-controller-result",
|
|
4574
|
+
content: message,
|
|
4575
|
+
display: true,
|
|
4576
|
+
details: { controllerUsed: false, controllerPass: false, reason: "unsafe-workspace-root" },
|
|
4577
|
+
}, { triggerTurn: false });
|
|
4578
|
+
try { ctx.ui.notify(message, "error"); } catch {}
|
|
4579
|
+
return;
|
|
4580
|
+
}
|
|
4581
|
+
|
|
4582
|
+
const abort = new AbortController();
|
|
4583
|
+
directControllerAbort = abort;
|
|
4584
|
+
const directTraceID = createTraceID(admission === "automatic" ? "ues-auto" : "ues-run");
|
|
4585
|
+
syncSessionIdentity(uesSessionName("run", task, workspaceRoot), ctx);
|
|
4586
|
+
let result: any;
|
|
4587
|
+
let lastProgressNoticeAt = 0;
|
|
4588
|
+
let lastProgressKey = "";
|
|
4589
|
+
try {
|
|
4590
|
+
const directPolicy =
|
|
4591
|
+
admissionDecision?.policy && typeof admissionDecision.policy === "object"
|
|
4592
|
+
? admissionDecision.policy
|
|
4593
|
+
: classifyEngineeringTask(task);
|
|
4594
|
+
const routeLabel =
|
|
4595
|
+
admission === "automatic"
|
|
4596
|
+
? admissionDecision?.route === "guarded"
|
|
4597
|
+
? "high-risk auto controller started ("
|
|
4598
|
+
: "auto controller started ("
|
|
4599
|
+
: "controller started (";
|
|
4600
|
+
try {
|
|
4601
|
+
ctx.ui.notify(
|
|
4602
|
+
"UES " + PACKAGE_VERSION + ": " + routeLabel +
|
|
4603
|
+
String(directPolicy.executionProfile || directPolicy.mode || "unknown") +
|
|
4604
|
+
"/" + String(directPolicy.risk || "unknown") + ")",
|
|
4605
|
+
admissionDecision?.route === "guarded" ? "warning" : "info",
|
|
4606
|
+
);
|
|
4607
|
+
} catch {}
|
|
4608
|
+
result = await uesExecuteTool.execute(
|
|
4609
|
+
`ues-run-${randomUUID()}`,
|
|
4610
|
+
{ task, cwd: workspaceRoot, __traceID: directTraceID, __taskPolicy: directPolicy },
|
|
4611
|
+
abort.signal,
|
|
4612
|
+
(update: any) => {
|
|
4613
|
+
const text = (update?.content || [])
|
|
4614
|
+
.filter((part: any) => part?.type === "text" && typeof part.text === "string")
|
|
4615
|
+
.map((part: any) => part.text)
|
|
4616
|
+
.join("\n")
|
|
4617
|
+
.trim();
|
|
4618
|
+
if (text) {
|
|
4619
|
+
try { ctx.ui.setStatus("ues-run", cap(text, 180)); } catch {}
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4622
|
+
const progress = update?.details?.progress;
|
|
4623
|
+
if (progress) {
|
|
4624
|
+
const progressKey = String(progress.agent || "") + ":" + String(update?.details?.phase || "");
|
|
4625
|
+
const now = Date.now();
|
|
4626
|
+
const phaseChanged = progressKey && progressKey !== lastProgressKey;
|
|
4627
|
+
const heartbeatDue = now - lastProgressNoticeAt >= 60_000;
|
|
4628
|
+
if (phaseChanged || heartbeatDue) {
|
|
4629
|
+
const message =
|
|
4630
|
+
"UES: " + String(progress.agent || "worker") +
|
|
4631
|
+
" running " + Math.round(Number(progress.elapsedMs || 0) / 1000) + "s" +
|
|
4632
|
+
" (idle " + Math.round(Number(progress.idleMs || 0) / 1000) + "s, tools " +
|
|
4633
|
+
Number(progress.toolCalls || 0) +
|
|
4634
|
+
(progress.activeTool ? ", " + String(progress.activeTool) : "") + ")";
|
|
4635
|
+
try {
|
|
4636
|
+
ctx.ui.notify(
|
|
4637
|
+
message,
|
|
4638
|
+
Number(progress.idleMs || 0) >= 45_000 ? "warning" : "info",
|
|
4639
|
+
);
|
|
4640
|
+
} catch {}
|
|
4641
|
+
lastProgressNoticeAt = now;
|
|
4642
|
+
lastProgressKey = progressKey;
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
|
|
4646
|
+
if (process.env.UES_EVAL_DIRECT_TELEMETRY === "1") {
|
|
4647
|
+
process.stderr.write(JSON.stringify({
|
|
4648
|
+
type: "ues_controller_progress",
|
|
4649
|
+
phase: update?.details?.phase || null,
|
|
4650
|
+
task: update?.details?.task || null,
|
|
4651
|
+
agent: progress?.agent || null,
|
|
4652
|
+
elapsedMs: Number(progress?.elapsedMs || 0),
|
|
4653
|
+
idleMs: Number(progress?.idleMs || 0),
|
|
4654
|
+
toolCalls: Number(progress?.toolCalls || 0),
|
|
4655
|
+
activeTool: progress?.activeTool || null,
|
|
4656
|
+
note: progress?.note || null,
|
|
4657
|
+
text: text ? cap(text, 240) : null,
|
|
4658
|
+
}) + "\n");
|
|
4659
|
+
}
|
|
4660
|
+
},
|
|
4661
|
+
ctx,
|
|
4662
|
+
);
|
|
4663
|
+
} catch (error) {
|
|
4664
|
+
result = {
|
|
4665
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
4666
|
+
details: { mode: "execute", reason: "direct-controller-exception" },
|
|
4667
|
+
isError: true,
|
|
4668
|
+
};
|
|
4669
|
+
} finally {
|
|
4670
|
+
const traceRemoved = await cleanupTraceSandboxes(workspaceRoot, directTraceID).catch(() => 0);
|
|
4671
|
+
const staleCleanup = await pruneOrphanTaskSandboxes(workspaceRoot, {
|
|
4672
|
+
minAgeMs: 5 * 60_000,
|
|
4673
|
+
legacyMinAgeMs: 30 * 60_000,
|
|
4674
|
+
ownedMinAgeMs: 0,
|
|
4675
|
+
reclaimOwnerPid: process.pid,
|
|
4676
|
+
protectedDirs: [...ACTIVE_TASK_SANDBOXES.keys()],
|
|
4677
|
+
}).catch(() => ({ removed: [], skipped: [], sidecarsRemoved: [] }));
|
|
4678
|
+
const cleaned =
|
|
4679
|
+
Number(traceRemoved || 0) +
|
|
4680
|
+
Number(staleCleanup?.removed?.length || 0) +
|
|
4681
|
+
Number(staleCleanup?.sidecarsRemoved?.length || 0);
|
|
4682
|
+
if (cleaned > 0) {
|
|
4683
|
+
try { ctx.ui.notify("UES cleanup: removed " + cleaned + " stale sandbox artifact(s)", "info"); } catch {}
|
|
4684
|
+
}
|
|
4685
|
+
if (directControllerAbort === abort) directControllerAbort = null;
|
|
4686
|
+
try { ctx.ui.setStatus("ues-run", undefined); } catch {}
|
|
4687
|
+
}
|
|
4688
|
+
|
|
4689
|
+
const content = (result?.content || [])
|
|
4690
|
+
.filter((part: any) => part?.type === "text" && typeof part.text === "string")
|
|
4691
|
+
.map((part: any) => part.text)
|
|
4692
|
+
.join("\n")
|
|
4693
|
+
.trim() || "(UES controller returned no text)";
|
|
4694
|
+
const controllerPass = result?.isError !== true;
|
|
4695
|
+
|
|
4696
|
+
if (process.env.UES_EVAL_DIRECT_TELEMETRY === "1") {
|
|
4697
|
+
const details = result?.details || null;
|
|
4698
|
+
const telemetryDetails = details
|
|
4699
|
+
? {
|
|
4700
|
+
mode: details.mode,
|
|
4701
|
+
attempts: details.attempts,
|
|
4702
|
+
policy: details.policy
|
|
4703
|
+
? {
|
|
4704
|
+
mode: details.policy.mode,
|
|
4705
|
+
executionProfile: details.policy.executionProfile,
|
|
4706
|
+
risk: details.policy.risk,
|
|
4707
|
+
}
|
|
4708
|
+
: null,
|
|
4709
|
+
steps: Array.isArray(details.steps)
|
|
4710
|
+
? details.steps.map((step: any) => ({
|
|
4711
|
+
agent: step?.agent,
|
|
4712
|
+
exitCode: step?.exitCode,
|
|
4713
|
+
optimizations: step?.optimizations || null,
|
|
4714
|
+
usage: step?.usage || null,
|
|
4715
|
+
toolCalls: Number(step?.toolCalls || 0),
|
|
4716
|
+
toolNames: Array.isArray(step?.toolNames) ? step.toolNames : [],
|
|
4717
|
+
}))
|
|
4718
|
+
: [],
|
|
4719
|
+
}
|
|
4720
|
+
: null;
|
|
4721
|
+
process.stderr.write(JSON.stringify({
|
|
4722
|
+
type: "ues_controller_direct",
|
|
4723
|
+
controllerUsed: true,
|
|
4724
|
+
controllerPass,
|
|
4725
|
+
details: telemetryDetails,
|
|
4726
|
+
}) + "\n");
|
|
4727
|
+
}
|
|
4728
|
+
|
|
4729
|
+
pi.sendMessage({
|
|
4730
|
+
customType: "ues-controller-result",
|
|
4731
|
+
content,
|
|
4732
|
+
display: true,
|
|
4733
|
+
details: {
|
|
4734
|
+
controllerUsed: true,
|
|
4735
|
+
controllerPass,
|
|
4736
|
+
admission,
|
|
4737
|
+
admissionRoute: admissionDecision?.route || (admission === "command" ? "command" : "auto"),
|
|
4738
|
+
admissionConfidence: admissionDecision?.confidence || null,
|
|
4739
|
+
admissionReason: admissionDecision?.reason || null,
|
|
4740
|
+
...(result?.details || {}),
|
|
4741
|
+
},
|
|
4742
|
+
});
|
|
4743
|
+
|
|
4744
|
+
try {
|
|
4745
|
+
ctx.ui.notify(
|
|
4746
|
+
controllerPass ? "UES " + PACKAGE_VERSION + ": verified controller run completed" : "UES " + PACKAGE_VERSION + ": controller run failed verification",
|
|
4747
|
+
controllerPass ? "info" : "error",
|
|
4748
|
+
);
|
|
4749
|
+
} catch {}
|
|
4750
|
+
|
|
4751
|
+
};
|
|
4752
|
+
|
|
4753
|
+
pi.registerCommand("ues-run", {
|
|
4754
|
+
description: "Run an engineering task directly through the deterministic UES controller",
|
|
4755
|
+
handler: async (args, ctx) => {
|
|
4756
|
+
const task = String(args || "").trim();
|
|
4757
|
+
if (!task) {
|
|
4758
|
+
try { ctx.ui.notify("Usage: /ues-run <engineering task>", "warning"); } catch {}
|
|
4759
|
+
return;
|
|
4760
|
+
}
|
|
4761
|
+
await directControllerRunner?.(task, ctx, "command");
|
|
4762
|
+
},
|
|
3040
4763
|
});
|
|
3041
4764
|
|
|
3042
4765
|
pi.registerTool({
|
|
@@ -3166,7 +4889,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
3166
4889
|
}
|
|
3167
4890
|
|
|
3168
4891
|
let completed = 0;
|
|
3169
|
-
const
|
|
4892
|
+
const dispatchHasWriters = tasks.some((item) => WRITE_AGENTS.has(item.agent));
|
|
4893
|
+
const dispatchConcurrency = dispatchHasWriters
|
|
4894
|
+
? Math.min(MAX_CONCURRENCY, MAX_WRITER_CONCURRENCY)
|
|
4895
|
+
: MAX_CONCURRENCY;
|
|
4896
|
+
const results = await mapLimit(tasks, dispatchConcurrency, async (item) => {
|
|
3170
4897
|
const result = await runRoutedAgent(
|
|
3171
4898
|
item.agent as AgentName,
|
|
3172
4899
|
item.task,
|