opencode-agent-skill 7.7.0 → 9.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/CHANGELOG.md +63 -3
- package/README.md +675 -581
- package/bin/ocskill.mjs +358 -156
- package/docs/DETERMINISTIC-TOOLS.md +25 -8
- package/docs/ENGINEERING-DESIGN.md +31 -13
- package/docs/EVALS.md +34 -12
- package/docs/NPM-PUBLISH.md +6 -6
- package/docs/OPENCODE-COMPAT.md +11 -8
- package/docs/TRACE-SCHEMA.md +15 -2
- package/docs/V8-INTELLIGENCE-RELIABILITY.md +206 -0
- package/docs/V9-SPEED-INTELLIGENCE.md +102 -0
- package/evals/live/tasks.json +6 -6
- package/evals/polyglot/fixtures/polyglot-bench/api/generated/client.ts +2 -0
- package/evals/polyglot/fixtures/polyglot-bench/api/openapi.json +25 -0
- package/evals/polyglot/fixtures/polyglot-bench/db/migrations/20260920_add_order_key.sql +1 -0
- package/evals/polyglot/fixtures/polyglot-bench/dotnet/OrderService.cs +8 -0
- package/evals/polyglot/fixtures/polyglot-bench/java/PriceService.java +5 -0
- package/evals/polyglot/fixtures/polyglot-bench/monorepo/package.json +6 -0
- package/evals/polyglot/fixtures/polyglot-bench/monorepo/packages/api/package.json +4 -0
- package/evals/polyglot/fixtures/polyglot-bench/monorepo/packages/web/package.json +7 -0
- package/evals/polyglot/fixtures/polyglot-bench/monorepo/pnpm-lock.yaml +5 -0
- package/evals/polyglot/fixtures/polyglot-bench/next/app/api/products/route.ts +7 -0
- package/evals/polyglot/fixtures/polyglot-bench/python/tenant_auth.py +4 -0
- package/evals/polyglot/fixtures/polyglot-bench/react-native/keyboard.ts +3 -0
- package/evals/polyglot/graders/polyglot-bench.mjs +101 -0
- package/evals/polyglot/tasks.json +54 -0
- package/global-config/AGENTS.md +10 -7
- package/global-config/agents/integration-verifier.md +1 -1
- package/global-config/agents/plan-checker.md +1 -1
- package/global-config/commands/run.md +9 -5
- package/global-config/plugins/ues-router/capabilities.js +4 -0
- package/global-config/plugins/ues-router/index.js +414 -21
- package/global-config/plugins/ues-router/router.js +140 -23
- package/global-config/skills/engineering-orchestrator/references/long-horizon.md +6 -4
- package/lib/aci.mjs +128 -0
- package/lib/benchmark-confidence.mjs +135 -0
- package/lib/cli-utils.mjs +41 -0
- package/lib/container-sandbox.mjs +102 -0
- package/lib/context-manifest.mjs +300 -22
- package/lib/control-center.mjs +36 -3
- package/lib/eval-order.mjs +9 -0
- package/lib/eval-telemetry.mjs +5 -2
- package/lib/gate-receipt.mjs +52 -0
- package/lib/installer.mjs +39 -25
- package/lib/learning-engine.mjs +236 -38
- package/lib/opencode-compat.mjs +25 -10
- package/lib/orchestrator-policy.mjs +101 -20
- package/lib/process-runner.mjs +30 -9
- package/lib/runtime-events.mjs +31 -0
- package/lib/semantic-index.mjs +318 -0
- package/lib/task-engine.mjs +416 -24
- package/lib/trajectory.mjs +89 -0
- package/lib/windows-shim.mjs +227 -0
- package/lib/worktree-sandbox.mjs +85 -3
- package/package.json +10 -4
- package/scripts/check-release-tag.mjs +22 -0
- package/scripts/control-center.mjs +25 -0
- package/scripts/eval-live.mjs +39 -58
- package/scripts/eval-matrix.mjs +155 -0
- package/scripts/smoke-packed-install.mjs +138 -4
- package/scripts/smoke-plain-install.mjs +91 -0
- package/scripts/validate-live-suite.mjs +3 -3
- package/scripts/validate.mjs +27 -5
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Plugin } from "@opencode/plugin"
|
|
2
|
-
import { readFileSync } from "node:fs"
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
3
3
|
import { fileURLToPath } from "node:url"
|
|
4
4
|
import path from "node:path"
|
|
5
5
|
import { spawnSync } from "node:child_process"
|
|
6
|
-
import { routeSkills } from "./router.js"
|
|
6
|
+
import { classifyIntent, routeSkills } from "./router.js"
|
|
7
7
|
import { destructiveShellRisk } from "./safety.js"
|
|
8
8
|
import { runtimeCapabilities } from "./capabilities.js"
|
|
9
9
|
|
|
@@ -28,13 +28,43 @@ function routerConfig() {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
function findWindowsCommand(name) {
|
|
32
|
+
const result = spawnSync("where", [name], { encoding: "utf8" })
|
|
33
|
+
if (result.status !== 0 || !result.stdout) return null
|
|
34
|
+
const matches = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
35
|
+
return matches.find((item) => /\.(cmd|bat)$/i.test(item)) || matches[0] || null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function findNodeShimEntry(cmdPath) {
|
|
39
|
+
const dir = path.dirname(cmdPath)
|
|
40
|
+
let shim = ""
|
|
41
|
+
try { shim = readFileSync(cmdPath, "utf8") } catch {}
|
|
42
|
+
const match = shim.match(/node_modules[\\/][^\s"]+?\.(?:js|mjs)/gi)?.at(-1)
|
|
43
|
+
if (!match) return null
|
|
44
|
+
const entry = path.resolve(dir, match)
|
|
45
|
+
return existsSync(entry) ? entry : null
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
function runOcskill(args, cwd) {
|
|
32
|
-
const
|
|
49
|
+
const common = {
|
|
33
50
|
cwd,
|
|
34
51
|
encoding: "utf8",
|
|
35
|
-
shell: process.platform === "win32",
|
|
36
52
|
maxBuffer: 4 * 1024 * 1024,
|
|
37
|
-
}
|
|
53
|
+
}
|
|
54
|
+
let result
|
|
55
|
+
if (process.platform !== "win32") {
|
|
56
|
+
result = spawnSync("ocskill", args, common)
|
|
57
|
+
} else {
|
|
58
|
+
const resolved = findWindowsCommand("ocskill")
|
|
59
|
+
if (!resolved) throw new Error("ocskill command was not found on PATH")
|
|
60
|
+
if (/\.(cmd|bat)$/i.test(resolved)) {
|
|
61
|
+
const entry = findNodeShimEntry(resolved)
|
|
62
|
+
if (!entry) throw new Error("refusing to execute an unrecognized ocskill batch shim through cmd.exe")
|
|
63
|
+
result = spawnSync(process.execPath, [entry, ...args], common)
|
|
64
|
+
} else {
|
|
65
|
+
result = spawnSync(resolved, args, common)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
38
68
|
if (result.status !== 0) {
|
|
39
69
|
throw new Error((result.stderr || result.stdout || "ocskill command failed").trim())
|
|
40
70
|
}
|
|
@@ -50,6 +80,13 @@ function runOcskillJSON(args, cwd) {
|
|
|
50
80
|
}
|
|
51
81
|
}
|
|
52
82
|
|
|
83
|
+
function appendTrace(traceID, type, payload, cwd) {
|
|
84
|
+
try {
|
|
85
|
+
const encoded = Buffer.from(JSON.stringify(payload || {}), "utf8").toString("base64")
|
|
86
|
+
runOcskill(["trace", "append", traceID, cwd, "--type", type, "--payload-b64", encoded], cwd)
|
|
87
|
+
} catch {}
|
|
88
|
+
}
|
|
89
|
+
|
|
53
90
|
function modelRef(value) {
|
|
54
91
|
if (!value || typeof value !== "string") return null
|
|
55
92
|
const [base, variant] = value.split("#", 2)
|
|
@@ -68,6 +105,62 @@ function messageExcerpt(messages) {
|
|
|
68
105
|
return text.length <= 24000 ? text : text.slice(-24000)
|
|
69
106
|
}
|
|
70
107
|
|
|
108
|
+
function taskHasWrites(task) {
|
|
109
|
+
const files = task?.files
|
|
110
|
+
if (Array.isArray(files)) return files.length > 0
|
|
111
|
+
if (!files || typeof files !== "object") return false
|
|
112
|
+
return ["create", "modify", "test", "delete"].some(
|
|
113
|
+
(key) => Array.isArray(files[key]) && files[key].length > 0,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function policySkills(policy) {
|
|
118
|
+
const selected = []
|
|
119
|
+
const add = (id) => { if (id && !selected.includes(id)) selected.push(id) }
|
|
120
|
+
if (policy?.mode === "long-horizon") {
|
|
121
|
+
add("ues-engineering-orchestrator")
|
|
122
|
+
add("ues-long-task-state")
|
|
123
|
+
add("ues-task-planner")
|
|
124
|
+
} else if (policy?.mode === "standard" || policy?.risk === "high") {
|
|
125
|
+
add("ues-engineering-orchestrator")
|
|
126
|
+
}
|
|
127
|
+
const map = {
|
|
128
|
+
"auth-security": "ues-auth-security",
|
|
129
|
+
payment: "ues-payment-engineering",
|
|
130
|
+
database: "ues-database-engineering",
|
|
131
|
+
"api-contract": "ues-api-contract",
|
|
132
|
+
"react-native": "ues-react-native-engineering",
|
|
133
|
+
nextjs: "ues-nextjs-engineering",
|
|
134
|
+
react: "ues-react-engineering",
|
|
135
|
+
devops: "ues-devops-engineering",
|
|
136
|
+
}
|
|
137
|
+
for (const domain of policy?.domains || []) add(map[domain])
|
|
138
|
+
if (policy?.risk === "high") add("ues-change-impact-analysis")
|
|
139
|
+
return selected
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function projectRoutingFacts(projectRoot) {
|
|
143
|
+
let inspected = null
|
|
144
|
+
let learning = null
|
|
145
|
+
try { inspected = runOcskillJSON(["inspect", projectRoot], projectRoot) } catch {}
|
|
146
|
+
try { learning = runOcskillJSON(["learn", "status", projectRoot], projectRoot) } catch {}
|
|
147
|
+
|
|
148
|
+
const feedbackDomains = []
|
|
149
|
+
for (const item of learning?.accepted || []) {
|
|
150
|
+
if (item.status !== "promoted") continue
|
|
151
|
+
const learned = classifyIntent(item.candidateRule || item.recommendation || item.title || "")
|
|
152
|
+
for (const domain of learned.domains || []) {
|
|
153
|
+
if (!feedbackDomains.includes(domain)) feedbackDomains.push(domain)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
repoStacks: inspected?.stack?.stacks || [],
|
|
159
|
+
feedbackDomains,
|
|
160
|
+
acceptedLearningCount: (learning?.accepted || []).filter((item) => item.status === "promoted").length,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
71
164
|
export default Plugin.define({
|
|
72
165
|
id: "ues-router",
|
|
73
166
|
async setup(ctx) {
|
|
@@ -104,6 +197,75 @@ export default Plugin.define({
|
|
|
104
197
|
content: runOcskill(["task-policy", input.text], projectRoot),
|
|
105
198
|
}),
|
|
106
199
|
})
|
|
200
|
+
editor.add({
|
|
201
|
+
name: "semantic_search",
|
|
202
|
+
description: "Search the persistent incremental semantic index. Returns bounded path/symbol/reference evidence; never treats lexical evidence as semantic proof.",
|
|
203
|
+
input: {
|
|
204
|
+
type: "object",
|
|
205
|
+
properties: {
|
|
206
|
+
query: { type: "string" },
|
|
207
|
+
limit: { type: "integer", minimum: 1, maximum: 50 },
|
|
208
|
+
},
|
|
209
|
+
required: ["query"],
|
|
210
|
+
additionalProperties: false,
|
|
211
|
+
},
|
|
212
|
+
options: { namespace: "ues", codemode: true },
|
|
213
|
+
execute: async (input) => ({
|
|
214
|
+
content: runOcskill(["aci", "search", input.query, projectRoot, "--limit", String(input.limit || 20)], projectRoot),
|
|
215
|
+
}),
|
|
216
|
+
})
|
|
217
|
+
editor.add({
|
|
218
|
+
name: "references",
|
|
219
|
+
description: "Find bounded syntax-aware lexical references and concrete definition lines for one identifier.",
|
|
220
|
+
input: {
|
|
221
|
+
type: "object",
|
|
222
|
+
properties: {
|
|
223
|
+
symbol: { type: "string" },
|
|
224
|
+
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
225
|
+
},
|
|
226
|
+
required: ["symbol"],
|
|
227
|
+
additionalProperties: false,
|
|
228
|
+
},
|
|
229
|
+
options: { namespace: "ues", codemode: true },
|
|
230
|
+
execute: async (input) => ({
|
|
231
|
+
content: runOcskill(["aci", "refs", input.symbol, projectRoot, "--limit", String(input.limit || 40)], projectRoot),
|
|
232
|
+
}),
|
|
233
|
+
})
|
|
234
|
+
editor.add({
|
|
235
|
+
name: "view_file",
|
|
236
|
+
description: "Read a bounded line-numbered window from a repository file; refuses root escapes and oversized/binary files.",
|
|
237
|
+
input: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: {
|
|
240
|
+
file: { type: "string" },
|
|
241
|
+
line: { type: "integer", minimum: 1 },
|
|
242
|
+
lines: { type: "integer", minimum: 1, maximum: 240 },
|
|
243
|
+
},
|
|
244
|
+
required: ["file"],
|
|
245
|
+
additionalProperties: false,
|
|
246
|
+
},
|
|
247
|
+
options: { namespace: "ues", codemode: true },
|
|
248
|
+
execute: async (input) => ({
|
|
249
|
+
content: runOcskill([
|
|
250
|
+
"aci", "view", input.file, projectRoot,
|
|
251
|
+
"--line", String(input.line || 1),
|
|
252
|
+
"--lines", String(input.lines || 120),
|
|
253
|
+
], projectRoot),
|
|
254
|
+
}),
|
|
255
|
+
})
|
|
256
|
+
editor.add({
|
|
257
|
+
name: "sandbox_capability",
|
|
258
|
+
description: "Report whether fail-closed Docker/Podman verification isolation is actually available. Never assumes a container runtime exists.",
|
|
259
|
+
input: {
|
|
260
|
+
type: "object",
|
|
261
|
+
properties: {},
|
|
262
|
+
additionalProperties: false,
|
|
263
|
+
},
|
|
264
|
+
options: { namespace: "ues", codemode: true },
|
|
265
|
+
execute: async () => ({
|
|
266
|
+
content: runOcskill(["sandbox", "capability"], projectRoot),
|
|
267
|
+
}),
|
|
268
|
+
})
|
|
107
269
|
editor.add({
|
|
108
270
|
name: "work_status",
|
|
109
271
|
description: "Read durable status for a UES .ues-work item without modifying it.",
|
|
@@ -149,14 +311,97 @@ export default Plugin.define({
|
|
|
149
311
|
content: runOcskill(["context-pack", input.slug, input.task, projectRoot], projectRoot),
|
|
150
312
|
}),
|
|
151
313
|
})
|
|
314
|
+
editor.add({
|
|
315
|
+
name: "recover_task",
|
|
316
|
+
description: "Safely recover one stale UES task attempt. If an attached executor session still exists, interrupt it before releasing the durable lease.",
|
|
317
|
+
input: {
|
|
318
|
+
type: "object",
|
|
319
|
+
properties: {
|
|
320
|
+
slug: { type: "string" },
|
|
321
|
+
task: { type: "string" },
|
|
322
|
+
force: { type: "boolean" },
|
|
323
|
+
reason: { type: "string" },
|
|
324
|
+
},
|
|
325
|
+
required: ["slug", "task"],
|
|
326
|
+
additionalProperties: false,
|
|
327
|
+
},
|
|
328
|
+
options: { namespace: "ues", codemode: true },
|
|
329
|
+
execute: async (input) => {
|
|
330
|
+
const status = runOcskillJSON(["work", "status", input.slug, projectRoot], projectRoot)
|
|
331
|
+
const running = (status.running || []).find((item) => item.taskID === input.task)
|
|
332
|
+
if (!running) throw new Error("task is not currently running: " + input.task)
|
|
333
|
+
|
|
334
|
+
let interrupted = false
|
|
335
|
+
if (running.sessionID && capabilities.sessionInterrupt) {
|
|
336
|
+
try {
|
|
337
|
+
await ctx.session.interrupt({ sessionID: running.sessionID, continue: false })
|
|
338
|
+
interrupted = true
|
|
339
|
+
} catch {}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const recoverArgs = ["work", "recover-task", input.slug, input.task, projectRoot]
|
|
343
|
+
if (input.force) recoverArgs.push("--force")
|
|
344
|
+
if (input.reason) recoverArgs.push("--reason", input.reason)
|
|
345
|
+
const recovered = runOcskillJSON(recoverArgs, projectRoot)
|
|
346
|
+
return {
|
|
347
|
+
content: JSON.stringify({
|
|
348
|
+
interrupted,
|
|
349
|
+
sessionID: running.sessionID || null,
|
|
350
|
+
recovered,
|
|
351
|
+
}, null, 2),
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
})
|
|
355
|
+
editor.add({
|
|
356
|
+
name: "cancel_task",
|
|
357
|
+
description: "Interrupt a running UES executor session and mark its durable task attempt failed.",
|
|
358
|
+
input: {
|
|
359
|
+
type: "object",
|
|
360
|
+
properties: {
|
|
361
|
+
slug: { type: "string" },
|
|
362
|
+
task: { type: "string" },
|
|
363
|
+
reason: { type: "string" },
|
|
364
|
+
},
|
|
365
|
+
required: ["slug", "task"],
|
|
366
|
+
additionalProperties: false,
|
|
367
|
+
},
|
|
368
|
+
options: { namespace: "ues", codemode: true },
|
|
369
|
+
execute: async (input) => {
|
|
370
|
+
if (!capabilities.sessionInterrupt) {
|
|
371
|
+
throw new Error("OpenCode runtime does not expose session.interrupt")
|
|
372
|
+
}
|
|
373
|
+
const status = runOcskillJSON(["work", "status", input.slug, projectRoot], projectRoot)
|
|
374
|
+
const running = (status.running || []).find((item) => item.taskID === input.task)
|
|
375
|
+
if (!running) throw new Error("task is not currently running: " + input.task)
|
|
376
|
+
if (!running.sessionID) throw new Error("running task has no attached executor session")
|
|
377
|
+
await ctx.session.interrupt({ sessionID: running.sessionID, continue: false })
|
|
378
|
+
const failArgs = [
|
|
379
|
+
"work", "fail", input.slug, input.task, projectRoot,
|
|
380
|
+
"--reason", input.reason || "cancelled by user/runtime",
|
|
381
|
+
]
|
|
382
|
+
if (running.runId) failArgs.push("--run-id", running.runId)
|
|
383
|
+
const failed = runOcskillJSON(failArgs, projectRoot)
|
|
384
|
+
return {
|
|
385
|
+
content: JSON.stringify({
|
|
386
|
+
interrupted: true,
|
|
387
|
+
sessionID: running.sessionID,
|
|
388
|
+
task: input.task,
|
|
389
|
+
state: failed,
|
|
390
|
+
}, null, 2),
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
})
|
|
152
394
|
editor.add({
|
|
153
395
|
name: "dispatch_task",
|
|
154
|
-
description: "Start one approved UES task and execute it in a fresh ues-executor session
|
|
396
|
+
description: "Start one approved UES task and execute it in a fresh ues-executor session with bounded runtime and interrupt-on-timeout. The parent must inspect the diff and record completion evidence separately.",
|
|
155
397
|
input: {
|
|
156
398
|
type: "object",
|
|
157
399
|
properties: {
|
|
158
400
|
slug: { type: "string" },
|
|
159
401
|
task: { type: "string" },
|
|
402
|
+
timeoutMs: { type: "integer", minimum: 30000, maximum: 3600000 },
|
|
403
|
+
isolate: { type: "boolean" },
|
|
404
|
+
integrate: { type: "boolean" },
|
|
160
405
|
},
|
|
161
406
|
required: ["slug", "task"],
|
|
162
407
|
additionalProperties: false,
|
|
@@ -170,15 +415,57 @@ export default Plugin.define({
|
|
|
170
415
|
const started = runOcskillJSON(["work", "start", input.slug, input.task, projectRoot], projectRoot)
|
|
171
416
|
const attempt = started?.record?.attempts || 1
|
|
172
417
|
const runId = started?.record?.runId || null
|
|
418
|
+
const traceID = "dispatch-" + input.slug + "-" + input.task + "-" + String(runId || Date.now()).replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
173
419
|
const taskText = [
|
|
174
420
|
started?.contextPack?.task?.title,
|
|
175
421
|
started?.contextPack?.task?.summary,
|
|
176
422
|
...(started?.contextPack?.task?.acceptance || []),
|
|
423
|
+
started?.contextPack?.task?.risk ? "risk: " + started.contextPack.task.risk : null,
|
|
177
424
|
].filter(Boolean).join(" ")
|
|
178
425
|
const taskPolicy = runOcskillJSON(["task-policy", taskText], projectRoot)
|
|
426
|
+
appendTrace(traceID, "dispatch.started", {
|
|
427
|
+
slug: input.slug,
|
|
428
|
+
task: input.task,
|
|
429
|
+
runId,
|
|
430
|
+
attempt,
|
|
431
|
+
taskText,
|
|
432
|
+
taskPolicy,
|
|
433
|
+
context: {
|
|
434
|
+
files: started?.contextPack?.contextManifest?.files || [],
|
|
435
|
+
strategy: started?.contextPack?.contextManifest?.strategy || null,
|
|
436
|
+
used: started?.contextPack?.contextManifest?.used || 0,
|
|
437
|
+
budget: started?.contextPack?.contextManifest?.budget || 0,
|
|
438
|
+
},
|
|
439
|
+
}, projectRoot)
|
|
440
|
+
const timeoutMs = Math.max(
|
|
441
|
+
30_000,
|
|
442
|
+
Math.min(Number(input.timeoutMs || (taskPolicy.mode === "long-horizon" ? 20 * 60_000 : 10 * 60_000)), 60 * 60_000),
|
|
443
|
+
)
|
|
179
444
|
const policyArgs = ["model-policy", "executor", "--attempt", String(attempt)]
|
|
180
445
|
if (taskText) policyArgs.push("--text", taskText)
|
|
181
446
|
const policy = runOcskillJSON(policyArgs, projectRoot)
|
|
447
|
+
const workStatus = runOcskillJSON(["work", "status", input.slug, projectRoot], projectRoot)
|
|
448
|
+
const workingTree = runOcskillJSON(["working-tree", projectRoot], projectRoot)
|
|
449
|
+
const rootClean = workingTree?.git === true && workingTree?.clean === true
|
|
450
|
+
const writerTask = taskHasWrites(started?.contextPack?.task)
|
|
451
|
+
if (input.isolate === true && !rootClean) {
|
|
452
|
+
throw new Error("explicit sandbox isolation requires a clean root working tree; commit/stash or integrate existing changes first")
|
|
453
|
+
}
|
|
454
|
+
if (input.isolate !== false && writerTask && !rootClean) {
|
|
455
|
+
throw new Error("writer dispatch requires a clean root for automatic worktree isolation; clean the root or explicitly pass isolate:false to accept shared-root writes")
|
|
456
|
+
}
|
|
457
|
+
const autoIsolate =
|
|
458
|
+
input.isolate === true ||
|
|
459
|
+
(input.isolate !== false && rootClean && writerTask)
|
|
460
|
+
let sandbox = null
|
|
461
|
+
let executionDir = projectRoot
|
|
462
|
+
if (autoIsolate) {
|
|
463
|
+
sandbox = runOcskillJSON(
|
|
464
|
+
["sandbox", "create", input.slug, input.task, projectRoot],
|
|
465
|
+
projectRoot,
|
|
466
|
+
)
|
|
467
|
+
executionDir = sandbox.dir
|
|
468
|
+
}
|
|
182
469
|
const heartbeatStarted = Date.now()
|
|
183
470
|
const heartbeat = setInterval(() => {
|
|
184
471
|
try {
|
|
@@ -190,7 +477,25 @@ export default Plugin.define({
|
|
|
190
477
|
}, 30_000)
|
|
191
478
|
|
|
192
479
|
try {
|
|
193
|
-
const created = await ctx.session.create({
|
|
480
|
+
const created = await ctx.session.create({
|
|
481
|
+
title: "UES " + input.slug + " " + input.task,
|
|
482
|
+
location: { directory: executionDir },
|
|
483
|
+
})
|
|
484
|
+
appendTrace(traceID, "dispatch.session-created", {
|
|
485
|
+
sessionID: created.id,
|
|
486
|
+
executionDir,
|
|
487
|
+
isolated: executionDir !== projectRoot,
|
|
488
|
+
}, projectRoot)
|
|
489
|
+
{
|
|
490
|
+
const attachArgs = [
|
|
491
|
+
"work", "attach-session", input.slug, input.task, projectRoot,
|
|
492
|
+
"--session-id", created.id,
|
|
493
|
+
]
|
|
494
|
+
if (runId) attachArgs.push("--run-id", runId)
|
|
495
|
+
attachArgs.push("--execution-dir", executionDir)
|
|
496
|
+
if (sandbox?.dir) attachArgs.push("--sandbox-dir", sandbox.dir)
|
|
497
|
+
runOcskill(attachArgs, projectRoot)
|
|
498
|
+
}
|
|
194
499
|
await ctx.session.switchAgent({ sessionID: created.id, agent: "ues-executor" })
|
|
195
500
|
const selectedModel = modelRef(policy?.model)
|
|
196
501
|
if (selectedModel) {
|
|
@@ -204,21 +509,81 @@ export default Plugin.define({
|
|
|
204
509
|
"Do not broaden scope or launch child agents. Run the declared verification and return the executor report.\n\n" +
|
|
205
510
|
JSON.stringify(started.contextPack, null, 2),
|
|
206
511
|
})
|
|
207
|
-
|
|
512
|
+
|
|
513
|
+
let timer = null
|
|
514
|
+
try {
|
|
515
|
+
await Promise.race([
|
|
516
|
+
ctx.session.wait({ sessionID: created.id }),
|
|
517
|
+
new Promise((_, reject) => {
|
|
518
|
+
timer = setTimeout(
|
|
519
|
+
() => reject(new Error("UES executor timed out after " + timeoutMs + "ms")),
|
|
520
|
+
timeoutMs,
|
|
521
|
+
)
|
|
522
|
+
}),
|
|
523
|
+
])
|
|
524
|
+
} catch (error) {
|
|
525
|
+
try {
|
|
526
|
+
await ctx.session.interrupt({ sessionID: created.id, continue: false })
|
|
527
|
+
} catch {}
|
|
528
|
+
throw error
|
|
529
|
+
} finally {
|
|
530
|
+
if (timer) clearTimeout(timer)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const afterWait = runOcskillJSON(["work", "status", input.slug, projectRoot], projectRoot)
|
|
534
|
+
const activeAttempt = (afterWait.running || []).find(
|
|
535
|
+
(item) => item.taskID === input.task && (!runId || item.runId === runId),
|
|
536
|
+
)
|
|
537
|
+
if (!activeAttempt) {
|
|
538
|
+
throw new Error("UES executor attempt is no longer active; refusing post-cancel integration or completion handoff")
|
|
539
|
+
}
|
|
540
|
+
|
|
208
541
|
const messages = await ctx.session.context({ sessionID: created.id })
|
|
542
|
+
appendTrace(traceID, "dispatch.completed", {
|
|
543
|
+
sessionID: created.id,
|
|
544
|
+
task: input.task,
|
|
545
|
+
runId,
|
|
546
|
+
messageCount: Array.isArray(messages) ? messages.length : null,
|
|
547
|
+
}, projectRoot)
|
|
548
|
+
let integration = null
|
|
549
|
+
if (sandbox && input.integrate === true) {
|
|
550
|
+
integration = runOcskillJSON(
|
|
551
|
+
["sandbox", "integrate", sandbox.dir, projectRoot],
|
|
552
|
+
projectRoot,
|
|
553
|
+
)
|
|
554
|
+
sandbox = null
|
|
555
|
+
}
|
|
209
556
|
return {
|
|
210
557
|
content: JSON.stringify({
|
|
211
558
|
sessionID: created.id,
|
|
212
559
|
task: input.task,
|
|
213
560
|
attempt,
|
|
214
561
|
runId,
|
|
562
|
+
traceID,
|
|
215
563
|
taskPolicy,
|
|
216
564
|
model: policy,
|
|
565
|
+
timeoutMs,
|
|
566
|
+
executionDir,
|
|
567
|
+
isolated: executionDir !== projectRoot,
|
|
568
|
+
sandbox,
|
|
569
|
+
integration,
|
|
217
570
|
messages: messageExcerpt(messages),
|
|
218
|
-
next:
|
|
571
|
+
next: sandbox
|
|
572
|
+
? "Inspect and verify the isolated worktree first. If accepted, integrate it with ocskill sandbox integrate <worktree> . before recording work complete."
|
|
573
|
+
: "Inspect the child diff and verification, then call ocskill work complete or fail.",
|
|
219
574
|
}, null, 2),
|
|
220
575
|
}
|
|
221
576
|
} catch (error) {
|
|
577
|
+
appendTrace(traceID, "dispatch.failed", {
|
|
578
|
+
task: input.task,
|
|
579
|
+
runId,
|
|
580
|
+
error: String(error?.message || error),
|
|
581
|
+
}, projectRoot)
|
|
582
|
+
if (sandbox?.dir) {
|
|
583
|
+
try {
|
|
584
|
+
runOcskill(["sandbox", "remove", sandbox.dir, projectRoot, "--force", "--delete-branch"], projectRoot)
|
|
585
|
+
} catch {}
|
|
586
|
+
}
|
|
222
587
|
try {
|
|
223
588
|
const failArgs = [
|
|
224
589
|
"work", "fail", input.slug, input.task, projectRoot,
|
|
@@ -235,19 +600,35 @@ export default Plugin.define({
|
|
|
235
600
|
})
|
|
236
601
|
})
|
|
237
602
|
|
|
238
|
-
|
|
603
|
+
const routingFacts = projectRoutingFacts(projectRoot)
|
|
604
|
+
|
|
605
|
+
if (capabilities.sessionHook) {
|
|
606
|
+
await ctx.session.hook("context", (event) => {
|
|
239
607
|
if (event.agent === "title" || event.agent === "summary" || event.agent === "compaction") return
|
|
240
608
|
event.system.push({
|
|
241
609
|
type: "text",
|
|
242
|
-
text: "UES
|
|
610
|
+
text: "UES runtime: choose FAST/STANDARD/DEEP from deterministic task policy, prefer bounded semantic/ACI evidence over broad scans, trust durable .ues-work state and EVENTS.jsonl over conversation memory, never treat lexical matches as semantic proof, require fresh verification for non-trivial completion, and fail closed when a high-risk capability is missing.",
|
|
243
611
|
})
|
|
244
612
|
})
|
|
245
613
|
|
|
246
|
-
|
|
614
|
+
await ctx.session.hook("prompt", (event) => {
|
|
247
615
|
const config = routerConfig()
|
|
248
616
|
if (!config.enabled) return
|
|
249
617
|
|
|
250
|
-
|
|
618
|
+
let policy = null
|
|
619
|
+
try {
|
|
620
|
+
policy = runOcskillJSON(["task-policy", event.prompt.text], projectRoot)
|
|
621
|
+
} catch {}
|
|
622
|
+
const intent = classifyIntent(event.prompt.text, routingFacts)
|
|
623
|
+
const effectiveMaxSkills = Math.max(
|
|
624
|
+
1,
|
|
625
|
+
Math.min(config.maxSkills, Number(policy?.maxSkills || config.maxSkills)),
|
|
626
|
+
)
|
|
627
|
+
const selected = []
|
|
628
|
+
for (const id of [...routeSkills(event.prompt.text, effectiveMaxSkills, routingFacts), ...policySkills(policy)]) {
|
|
629
|
+
if (!selected.includes(id)) selected.push(id)
|
|
630
|
+
if (selected.length >= effectiveMaxSkills) break
|
|
631
|
+
}
|
|
251
632
|
if (selected.length === 0) return
|
|
252
633
|
|
|
253
634
|
event.prompt.skills ??= []
|
|
@@ -261,17 +642,29 @@ export default Plugin.define({
|
|
|
261
642
|
...event.metadata,
|
|
262
643
|
uesRouter: {
|
|
263
644
|
selected,
|
|
264
|
-
|
|
645
|
+
policy,
|
|
646
|
+
intent,
|
|
647
|
+
facts: {
|
|
648
|
+
repoStacks: routingFacts.repoStacks,
|
|
649
|
+
feedbackDomains: routingFacts.feedbackDomains,
|
|
650
|
+
acceptedLearningCount: routingFacts.acceptedLearningCount,
|
|
651
|
+
},
|
|
652
|
+
version: 6,
|
|
653
|
+
effectiveMaxSkills,
|
|
265
654
|
},
|
|
266
655
|
}
|
|
267
656
|
})
|
|
268
657
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (capabilities.permissionHook) {
|
|
661
|
+
await ctx.permission.hook("evaluate", (event) => {
|
|
662
|
+
if (event.action !== "shell") return
|
|
663
|
+
const risk = destructiveShellRisk(event.resources.join("\n"))
|
|
664
|
+
if (!risk.risky) return
|
|
665
|
+
event.effect = "ask"
|
|
666
|
+
event.message = "UES safety gate: confirm destructive/high-impact shell action (" + risk.id + ")."
|
|
667
|
+
})
|
|
668
|
+
}
|
|
276
669
|
},
|
|
277
670
|
})
|