opencode-agent-skill 12.0.0-beta.0 → 13.0.0-beta.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.
@@ -1,4 +1,3 @@
1
- import { Plugin } from "@opencode/plugin"
2
1
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"
3
2
  import { fileURLToPath } from "node:url"
4
3
  import path from "node:path"
@@ -6,6 +5,10 @@ import { spawnSync } from "node:child_process"
6
5
  import { classifyIntent, routeSkillsForPolicy } from "./router.js"
7
6
  import { destructiveShellRisk } from "./safety.js"
8
7
  import { runtimeCapabilities } from "./capabilities.js"
8
+ import { parallelRootBaseline, runEventDrivenDAG } from "./parallel-runtime.js"
9
+ import { readProjectJson, readProjectText } from "./text-runtime.js"
10
+ import { extractVerifierVerdict } from "./verifier-runtime.js"
11
+ import { expandUesPromptAlias } from "./command-runtime.js"
9
12
  import {
10
13
  budgetToolResult,
11
14
  classifyProviderFailure,
@@ -23,6 +26,11 @@ const CONFIG_FILE = path.resolve(
23
26
  "router.json",
24
27
  )
25
28
 
29
+ const COMMAND_TEMPLATE_DIR = path.resolve(
30
+ path.dirname(fileURLToPath(import.meta.url)),
31
+ "command-templates",
32
+ )
33
+
26
34
  function routerConfig() {
27
35
  try {
28
36
  const parsed = JSON.parse(readFileSync(CONFIG_FILE, "utf8"))
@@ -36,8 +44,15 @@ function routerConfig() {
36
44
  }
37
45
 
38
46
 
47
+ function spawnHidden(command, args, options = {}) {
48
+ return spawnSync(command, args, {
49
+ ...options,
50
+ windowsHide: true,
51
+ })
52
+ }
53
+
39
54
  function findWindowsCommand(name) {
40
- const result = spawnSync("where", [name], { encoding: "utf8" })
55
+ const result = spawnHidden("where", [name], { encoding: "utf8" })
41
56
  if (result.status !== 0 || !result.stdout) return null
42
57
  const matches = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
43
58
  return matches.find((item) => /\.(cmd|bat)$/i.test(item)) || matches[0] || null
@@ -61,16 +76,16 @@ function runOcskill(args, cwd) {
61
76
  }
62
77
  let result
63
78
  if (process.platform !== "win32") {
64
- result = spawnSync("ocskill", args, common)
79
+ result = spawnHidden("ocskill", args, common)
65
80
  } else {
66
81
  const resolved = findWindowsCommand("ocskill")
67
82
  if (!resolved) throw new Error("ocskill command was not found on PATH")
68
83
  if (/\.(cmd|bat)$/i.test(resolved)) {
69
84
  const entry = findNodeShimEntry(resolved)
70
85
  if (!entry) throw new Error("refusing to execute an unrecognized ocskill batch shim through cmd.exe")
71
- result = spawnSync(process.execPath, [entry, ...args], common)
86
+ result = spawnHidden(process.execPath, [entry, ...args], common)
72
87
  } else {
73
- result = spawnSync(resolved, args, common)
88
+ result = spawnHidden(resolved, args, common)
74
89
  }
75
90
  }
76
91
  if (result.status !== 0) {
@@ -80,11 +95,31 @@ function runOcskill(args, cwd) {
80
95
  }
81
96
 
82
97
  function runOcskillJSON(args, cwd) {
83
- const output = runOcskill(args, cwd)
98
+ const separator = args.indexOf("--")
99
+ const machineArgs = separator >= 0
100
+ ? [...args.slice(0, separator), "--json", ...args.slice(separator)]
101
+ : [...args, "--json"]
102
+ let output
103
+ try {
104
+ output = runOcskill(machineArgs, cwd)
105
+ } catch (error) {
106
+ let parsed = null
107
+ try { parsed = JSON.parse(String(error?.message || error)) } catch {}
108
+ if (parsed?.error?.message) {
109
+ const structured = new Error(parsed.error.message)
110
+ structured.code = parsed.error.code || "UES_ERROR"
111
+ structured.hint = parsed.error.hint || null
112
+ structured.recoverable = parsed.error.recoverable === true
113
+ throw structured
114
+ }
115
+ throw error
116
+ }
84
117
  try {
85
118
  return JSON.parse(output)
86
119
  } catch {
87
- throw new Error("ocskill returned invalid JSON for: " + args.join(" "))
120
+ const error = new Error("ocskill returned invalid JSON for: " + args.join(" "))
121
+ error.code = "UES_INVALID_JSON"
122
+ throw error
88
123
  }
89
124
  }
90
125
 
@@ -140,7 +175,7 @@ function workspaceSignal(root) {
140
175
  ]
141
176
  const parts = []
142
177
  for (const args of commands) {
143
- const result = spawnSync("git", args, {
178
+ const result = spawnHidden("git", args, {
144
179
  cwd: root,
145
180
  encoding: "utf8",
146
181
  maxBuffer: 2 * 1024 * 1024,
@@ -245,7 +280,7 @@ function projectRoutingFacts(projectRoot) {
245
280
  }
246
281
  }
247
282
 
248
- export default Plugin.define({
283
+ export default {
249
284
  id: "ues-router",
250
285
  async setup(ctx) {
251
286
  const projectRoot = ctx.location.project?.canonical || ctx.location.directory
@@ -499,6 +534,27 @@ export default Plugin.define({
499
534
  options: { namespace: "ues", codemode: true },
500
535
  execute: async () => ({ content: JSON.stringify(capabilities, null, 2) }),
501
536
  })
537
+ editor.add({
538
+ name: "text_read",
539
+ description: "Read a project text or diff file with UTF-8/UTF-16 auto-detection when the normal reader reports binary or encoding trouble. Refuses paths outside the project and real binary data.",
540
+ input: {
541
+ type: "object",
542
+ properties: {
543
+ file: { type: "string" },
544
+ start: { type: "integer", minimum: 0 },
545
+ maxChars: { type: "integer", minimum: 256, maximum: 100000 },
546
+ },
547
+ required: ["file"],
548
+ additionalProperties: false,
549
+ },
550
+ options: { namespace: "ues", codemode: true },
551
+ execute: async (input) => ({
552
+ content: JSON.stringify(readProjectText(projectRoot, input.file, {
553
+ start: input.start,
554
+ maxChars: input.maxChars,
555
+ }), null, 2),
556
+ }),
557
+ })
502
558
  editor.add({
503
559
  name: "task_policy",
504
560
  description: "Classify an engineering request into inline, standard or long-horizon mode with risk/model/context guidance.",
@@ -904,24 +960,7 @@ export default Plugin.define({
904
960
  }
905
961
  },
906
962
  })
907
- editor.add({
908
- name: "dispatch_task",
909
- 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.",
910
- input: {
911
- type: "object",
912
- properties: {
913
- slug: { type: "string" },
914
- task: { type: "string" },
915
- timeoutMs: { type: "integer", minimum: 30000, maximum: 3600000 },
916
- stallMs: { type: "integer", minimum: 30000, maximum: 300000 },
917
- isolate: { type: "boolean" },
918
- integrate: { type: "boolean" },
919
- },
920
- required: ["slug", "task"],
921
- additionalProperties: false,
922
- },
923
- options: { namespace: "ues", codemode: true },
924
- execute: async (input, tool) => {
963
+ const dispatchApprovedTask = async (input, tool, executionOptions = {}) => {
925
964
  if (!capabilities.freshDispatch) {
926
965
  throw new Error("OpenCode runtime does not expose the fresh-session capabilities required by ues.dispatch_task")
927
966
  }
@@ -977,22 +1016,21 @@ export default Plugin.define({
977
1016
  const workingTree = runOcskillJSON(["working-tree", projectRoot], projectRoot)
978
1017
  const rootClean = workingTree?.git === true && workingTree?.clean === true
979
1018
  const writerTask = taskHasWrites(started?.contextPack?.task)
980
- if (input.isolate === true && !rootClean) {
981
- throw new Error("explicit sandbox isolation requires a clean root working tree; commit/stash or integrate existing changes first")
1019
+ if (input.isolate === true && !rootClean && executionOptions.inheritDirtyRoot !== true) {
1020
+ throw new Error("explicit sandbox isolation requires a clean root working tree unless inherited-root snapshot mode is enabled")
982
1021
  }
983
- if (input.isolate !== false && writerTask && !rootClean) {
984
- 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")
1022
+ if (input.isolate !== false && writerTask && !rootClean && executionOptions.inheritDirtyRoot !== true) {
1023
+ throw new Error("writer dispatch requires a clean root unless the parallel runtime enables inherited-root snapshot isolation")
985
1024
  }
986
1025
  const autoIsolate =
987
1026
  input.isolate === true ||
988
- (input.isolate !== false && rootClean && writerTask)
1027
+ (input.isolate !== false && writerTask && (rootClean || executionOptions.inheritDirtyRoot === true))
989
1028
  let sandbox = null
990
1029
  let executionDir = projectRoot
991
1030
  if (autoIsolate) {
992
- sandbox = runOcskillJSON(
993
- ["sandbox", "create", input.slug, input.task, projectRoot],
994
- projectRoot,
995
- )
1031
+ const sandboxArgs = ["sandbox", "create", input.slug, input.task, projectRoot]
1032
+ if (executionOptions.inheritDirtyRoot === true && !rootClean) sandboxArgs.push("--inherit-dirty-root")
1033
+ sandbox = runOcskillJSON(sandboxArgs, projectRoot)
996
1034
  executionDir = sandbox.dir
997
1035
  }
998
1036
  const heartbeatStarted = Date.now()
@@ -1009,7 +1047,9 @@ export default Plugin.define({
1009
1047
  const escalationArgs = ["model-policy", "executor", "--attempt", String(attempt + 1)]
1010
1048
  if (taskText) escalationArgs.push("--text", taskText)
1011
1049
  const escalatedPolicy = runOcskillJSON(escalationArgs, projectRoot)
1012
- let selectedPolicy = policy
1050
+ let selectedPolicy = executionOptions.forceModel === true
1051
+ ? { ...policy, model: executionOptions.modelOverride || null, forcedSingleModel: true }
1052
+ : policy
1013
1053
  let created = null
1014
1054
  let messages = null
1015
1055
  const providerRecovery = []
@@ -1053,6 +1093,12 @@ export default Plugin.define({
1053
1093
  }
1054
1094
 
1055
1095
  await ctx.session.switchAgent({ sessionID: created.id, agent: "ues-executor" })
1096
+ if (capabilities.permissionRules) {
1097
+ await ctx.permission.rules({
1098
+ sessionID: created.id,
1099
+ permissions: [{ action: "subagent", resource: "*", effect: "deny" }],
1100
+ })
1101
+ }
1056
1102
  const selectedModel = modelRef(selectedPolicy?.model)
1057
1103
  if (selectedModel) {
1058
1104
  await ctx.session.switchModel({ sessionID: created.id, model: selectedModel })
@@ -1092,6 +1138,7 @@ export default Plugin.define({
1092
1138
  const failureKind = classifyProviderFailure(error)
1093
1139
  const escalationModel = modelRef(escalatedPolicy?.model)
1094
1140
  const hasEscalationModel = Boolean(
1141
+ executionOptions.disableModelEscalation !== true &&
1095
1142
  escalationModel &&
1096
1143
  escalatedPolicy?.model &&
1097
1144
  escalatedPolicy.model !== selectedPolicy?.model,
@@ -1204,6 +1251,265 @@ export default Plugin.define({
1204
1251
  } finally {
1205
1252
  clearInterval(heartbeat)
1206
1253
  }
1254
+ }
1255
+
1256
+ editor.add({
1257
+ name: "dispatch_task",
1258
+ 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.",
1259
+ input: {
1260
+ type: "object",
1261
+ properties: {
1262
+ slug: { type: "string" },
1263
+ task: { type: "string" },
1264
+ timeoutMs: { type: "integer", minimum: 30000, maximum: 3600000 },
1265
+ stallMs: { type: "integer", minimum: 30000, maximum: 300000 },
1266
+ isolate: { type: "boolean" },
1267
+ integrate: { type: "boolean" },
1268
+ },
1269
+ required: ["slug", "task"],
1270
+ additionalProperties: false,
1271
+ },
1272
+ options: { namespace: "ues", codemode: true },
1273
+ execute: async (input, tool) => dispatchApprovedTask(input, tool),
1274
+ })
1275
+
1276
+ editor.add({
1277
+ name: "dispatch_parallel",
1278
+ description: "Execute an approved UES DAG with multiple fresh sessions of one shared model. Ready tasks run concurrently, each writer is isolated, a fresh same-model verifier must PASS, and integration is serialized with rollback on post-integration failure.",
1279
+ input: {
1280
+ type: "object",
1281
+ properties: {
1282
+ slug: { type: "string" },
1283
+ maxConcurrent: { type: "integer", minimum: 1, maximum: 8 },
1284
+ model: { type: "string" },
1285
+ timeoutMs: { type: "integer", minimum: 30000, maximum: 3600000 },
1286
+ stallMs: { type: "integer", minimum: 30000, maximum: 300000 },
1287
+ verifierTimeoutMs: { type: "integer", minimum: 30000, maximum: 1800000 },
1288
+ },
1289
+ required: ["slug"],
1290
+ additionalProperties: false,
1291
+ },
1292
+ options: { namespace: "ues", codemode: true },
1293
+ execute: async (input, tool) => {
1294
+ if (!capabilities.freshDispatch) {
1295
+ throw new Error("OpenCode runtime does not expose the fresh-session capabilities required by ues.dispatch_parallel")
1296
+ }
1297
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(String(input.slug || ""))) {
1298
+ throw new Error("work slug must use lowercase letters, numbers and dashes")
1299
+ }
1300
+
1301
+ const initialTree = runOcskillJSON(["working-tree", projectRoot], projectRoot)
1302
+ const rootBaseline = parallelRootBaseline(initialTree)
1303
+
1304
+ const workDir = path.join(projectRoot, ".ues-work", input.slug)
1305
+ const planFile = path.join(workDir, "PLAN.json")
1306
+ const stateFile = path.join(workDir, "STATE.json")
1307
+ if (!existsSync(planFile) || !existsSync(stateFile)) {
1308
+ throw new Error("parallel dispatch requires an initialized work item with PLAN.json and STATE.json")
1309
+ }
1310
+ const plan = readProjectJson(projectRoot, path.relative(projectRoot, planFile)).value
1311
+ const state = readProjectJson(projectRoot, path.relative(projectRoot, stateFile)).value
1312
+ if (state?.planApproval?.status !== "passed") throw new Error("parallel dispatch requires an approved active plan")
1313
+ const running = Object.entries(state.tasks || {}).filter(([, record]) => record?.status === "running")
1314
+ if (running.length) throw new Error("parallel dispatch refuses to start while another durable task attempt is already running")
1315
+
1316
+ const completedTaskIds = Object.entries(state.tasks || {})
1317
+ .filter(([, record]) => record?.status === "completed")
1318
+ .map(([id]) => id)
1319
+ const tasks = (plan.tasks || []).filter((task) => !completedTaskIds.includes(task.id))
1320
+ const goalText = String(plan.goal || "parallel approved UES execution")
1321
+ const sharedPolicy = runOcskillJSON(["model-policy", "executor", "--attempt", "1", "--text", goalText], projectRoot)
1322
+ const sharedModel = input.model || sharedPolicy?.model || null
1323
+ if (input.model && !modelRef(input.model)) throw new Error("parallel model must use provider/model[#variant] syntax")
1324
+ if (sharedModel && !capabilities.modelSwitch) {
1325
+ throw new Error("same-model parallel execution selected " + sharedModel + " but this OpenCode runtime cannot switch child-session models")
1326
+ }
1327
+
1328
+ const maxConcurrent = Math.max(1, Math.min(Number(input.maxConcurrent || 4), 8))
1329
+ const verifierTimeoutMs = Math.max(30000, Math.min(Number(input.verifierTimeoutMs || 600000), 1800000))
1330
+ await tool.progress({ status: "parallel UES starting " + tasks.length + " task(s), max " + maxConcurrent + " shared-model workers" })
1331
+
1332
+ const failRunningTask = (taskID, runId, reason) => {
1333
+ try {
1334
+ const failArgs = ["work", "fail", input.slug, taskID, projectRoot, "--reason", String(reason || "parallel task failed")]
1335
+ if (runId) failArgs.push("--run-id", runId)
1336
+ runOcskill(failArgs, projectRoot)
1337
+ } catch {}
1338
+ }
1339
+
1340
+ const scheduler = await runEventDrivenDAG(tasks, {
1341
+ completedTaskIds,
1342
+ maxConcurrent,
1343
+ singleModel: true,
1344
+ model: sharedModel,
1345
+ worker: async (task, context) => {
1346
+ let payload = null
1347
+ try {
1348
+ const response = await dispatchApprovedTask({
1349
+ slug: input.slug,
1350
+ task: task.id,
1351
+ timeoutMs: input.timeoutMs,
1352
+ stallMs: input.stallMs,
1353
+ isolate: true,
1354
+ integrate: false,
1355
+ }, tool, {
1356
+ forceModel: true,
1357
+ modelOverride: context.model,
1358
+ disableModelEscalation: true,
1359
+ inheritDirtyRoot: true,
1360
+ })
1361
+ payload = JSON.parse(response.content)
1362
+
1363
+ const executionDir = payload.sandbox?.dir || payload.executionDir || projectRoot
1364
+ const beforeVerifierSignal = workspaceSignal(executionDir)
1365
+ const verifierSession = await ctx.session.create({
1366
+ title: "UES verify " + input.slug + " " + task.id,
1367
+ location: { directory: executionDir },
1368
+ })
1369
+ runtimeGuard.touch(verifierSession.id)
1370
+ await ctx.session.switchAgent({ sessionID: verifierSession.id, agent: "ues-verifier" })
1371
+ if (capabilities.permissionRules) {
1372
+ await ctx.permission.rules({
1373
+ sessionID: verifierSession.id,
1374
+ permissions: [
1375
+ { action: "edit", resource: "*", effect: "deny" },
1376
+ { action: "subagent", resource: "*", effect: "deny" },
1377
+ ],
1378
+ })
1379
+ }
1380
+ const verifierModel = modelRef(context.model)
1381
+ if (verifierModel) await ctx.session.switchModel({ sessionID: verifierSession.id, model: verifierModel })
1382
+ await ctx.session.prompt({
1383
+ sessionID: verifierSession.id,
1384
+ text:
1385
+ "Independently verify this completed UES task. Do not edit files. Inspect the task-scoped diff, acceptance criteria and declared verification; run fresh verification commands where appropriate. " +
1386
+ "Your final line MUST be exactly one line beginning UES_VERDICT_JSON: followed by compact JSON with verdict PASS or FAIL and concise evidence. Use FAIL if any acceptance criterion or verification is not proven.\n\n" +
1387
+ JSON.stringify({
1388
+ task: task.id,
1389
+ title: task.title || null,
1390
+ summary: task.summary || null,
1391
+ acceptance: task.acceptance || [],
1392
+ verification: task.verification || [],
1393
+ verificationCommands: task.verificationCommands || [],
1394
+ files: task.files || null,
1395
+ risk: task.risk || "medium",
1396
+ }, null, 2),
1397
+ })
1398
+
1399
+ let verifierMessages
1400
+ try {
1401
+ verifierMessages = await waitForExecutorProgress(verifierSession.id, {
1402
+ timeoutMs: verifierTimeoutMs,
1403
+ stallMs: Math.min(Number(input.stallMs || 60000), 300000),
1404
+ })
1405
+ } catch (error) {
1406
+ try { await ctx.session.interrupt({ sessionID: verifierSession.id, continue: false }) } catch {}
1407
+ throw error
1408
+ } finally {
1409
+ runtimeGuard.clear(verifierSession.id)
1410
+ }
1411
+
1412
+ if (beforeVerifierSignal !== workspaceSignal(executionDir)) {
1413
+ throw new Error("independent verifier modified the workspace; verifier sessions must be read-only")
1414
+ }
1415
+ const verdict = extractVerifierVerdict(verifierMessages)
1416
+ if (!verdict || verdict.verdict !== "PASS") {
1417
+ throw new Error("independent verifier did not produce PASS" + (verdict?.evidence ? ": " + verdict.evidence : ""))
1418
+ }
1419
+ return {
1420
+ ...payload,
1421
+ verifier: {
1422
+ sessionID: verifierSession.id,
1423
+ verdict: verdict.verdict,
1424
+ evidence: verdict.evidence || "independent verifier PASS",
1425
+ },
1426
+ }
1427
+ } catch (error) {
1428
+ if (payload?.sandbox?.dir) {
1429
+ try { runOcskill(["sandbox", "remove", payload.sandbox.dir, projectRoot, "--force", "--delete-branch"], projectRoot) } catch {}
1430
+ }
1431
+ if (payload?.runId) failRunningTask(task.id, payload.runId, "parallel verification failed: " + String(error?.message || error))
1432
+ throw error
1433
+ }
1434
+ },
1435
+ integrate: async (task, result) => {
1436
+ let applied = false
1437
+ let durableCompleted = false
1438
+ try {
1439
+ let integration = null
1440
+ if (result.sandbox?.dir) {
1441
+ integration = runOcskillJSON(["sandbox", "integrate", result.sandbox.dir, projectRoot, "--keep"], projectRoot)
1442
+ applied = true
1443
+ }
1444
+ const deterministicReceipts = []
1445
+ for (const commandSpec of task.verificationCommands || []) {
1446
+ const command = String(commandSpec?.command || "").trim()
1447
+ if (!command) continue
1448
+ const commandArgs = Array.isArray(commandSpec?.args)
1449
+ ? commandSpec.args.map((value) => String(value))
1450
+ : []
1451
+ const verified = runOcskillJSON([
1452
+ "work", "verify-command", input.slug, task.id, projectRoot,
1453
+ "--run-id", result.runId,
1454
+ "--", command, ...commandArgs,
1455
+ ], projectRoot)
1456
+ deterministicReceipts.push(verified.receipt)
1457
+ }
1458
+ const receipt = runOcskillJSON([
1459
+ "work", "agent-receipt", input.slug, task.id, projectRoot,
1460
+ "--run-id", result.runId,
1461
+ "--verdict", "PASS",
1462
+ "--verifier", "ues-verifier",
1463
+ "--session-id", result.verifier.sessionID,
1464
+ "--evidence", result.verifier.evidence,
1465
+ ], projectRoot)
1466
+ const completed = runOcskillJSON([
1467
+ "work", "complete", input.slug, task.id, projectRoot,
1468
+ "--run-id", result.runId,
1469
+ "--evidence", result.verifier.evidence,
1470
+ ], projectRoot)
1471
+ durableCompleted = true
1472
+
1473
+ if (result.sandbox?.dir) {
1474
+ try { runOcskill(["sandbox", "remove", result.sandbox.dir, projectRoot, "--force", "--delete-branch"], projectRoot) } catch {}
1475
+ }
1476
+ try { void tool.progress({ status: "parallel task " + task.id + " verified and integrated" }) } catch {}
1477
+ return { integration, deterministicReceipts, receipt, completed }
1478
+ } catch (error) {
1479
+ if (!durableCompleted) {
1480
+ if (applied && result.sandbox?.dir) {
1481
+ try { runOcskill(["sandbox", "rollback", result.sandbox.dir, projectRoot], projectRoot) } catch {}
1482
+ } else if (result.sandbox?.dir) {
1483
+ try { runOcskill(["sandbox", "remove", result.sandbox.dir, projectRoot, "--force", "--delete-branch"], projectRoot) } catch {}
1484
+ }
1485
+ if (result.runId) failRunningTask(task.id, result.runId, "parallel integration failed: " + String(error?.message || error))
1486
+ }
1487
+ throw error
1488
+ }
1489
+ },
1490
+ onEvent: (event) => {
1491
+ if (["task.started", "task.completed", "task.failed"].includes(event.type)) {
1492
+ void tool.progress({ status: "parallel " + event.type + " " + event.task })
1493
+ }
1494
+ },
1495
+ })
1496
+
1497
+ const finalStatus = runOcskillJSON(["work", "status", input.slug, projectRoot], projectRoot)
1498
+ return {
1499
+ content: JSON.stringify({
1500
+ schemaVersion: 1,
1501
+ slug: input.slug,
1502
+ model: sharedModel || "opencode-default",
1503
+ singleModel: true,
1504
+ maxConcurrent,
1505
+ rootBaseline,
1506
+ scheduler,
1507
+ work: finalStatus,
1508
+ next: finalStatus.status === "integration-verification"
1509
+ ? "Run ues-integration-verifier and record the final integration receipt before finalize."
1510
+ : finalStatus.nextAction,
1511
+ }, null, 2),
1512
+ }
1207
1513
  },
1208
1514
  })
1209
1515
  })
@@ -1214,7 +1520,7 @@ export default Plugin.define({
1214
1520
  await ctx.session.hook("context", (event) => {
1215
1521
  event.system.push({
1216
1522
  type: "text",
1217
- text: "UES: use the minimum context that preserves correctness. FAST reads the target and nearest evidence with direct skills only; STANDARD/DEEP expand when risk or evidence requires it. Preserve exact contracts, verify fresh behavior, and escalate after failed attempts instead of stacking patches.",
1523
+ text: "UES: use the minimum context that preserves correctness. FAST reads the target and nearest evidence with direct skills only; STANDARD/DEEP expand when risk or evidence requires it. Preserve exact contracts and verify fresh behavior. On Windows, never redirect git diff through PowerShell into a text file; use ocskill diff . --out <file> so the result is UTF-8. If normal Read reports binary for a diff or known text file, use ues.text_read instead of inventing a converted copy. For independent approved tasks, prefer ues.dispatch_parallel when one shared model can safely work in isolated sessions.",
1218
1524
  })
1219
1525
  const assignment = sessionAssignments.get(event.sessionID)
1220
1526
  if (assignment?.resumeRequired && assignment.checkpoint) {
@@ -1232,17 +1538,32 @@ export default Plugin.define({
1232
1538
  const config = routerConfig()
1233
1539
  if (!config.enabled) return
1234
1540
 
1541
+ const promptAlias = expandUesPromptAlias(event.prompt?.text, COMMAND_TEMPLATE_DIR)
1542
+ if (promptAlias && event.prompt) {
1543
+ event.prompt.text = promptAlias.text
1544
+ event.metadata = {
1545
+ ...event.metadata,
1546
+ uesPromptAlias: {
1547
+ alias: promptAlias.alias,
1548
+ sourceName: promptAlias.sourceName,
1549
+ preferredAgent: promptAlias.agent,
1550
+ transport: "session.prompt",
1551
+ },
1552
+ }
1553
+ }
1554
+
1555
+ const promptText = String(event.prompt?.text || "")
1235
1556
  let policy = null
1236
1557
  try {
1237
- policy = runOcskillJSON(["task-policy", event.prompt.text], projectRoot)
1558
+ policy = runOcskillJSON(["task-policy", promptText], projectRoot)
1238
1559
  } catch {}
1239
- const intent = classifyIntent(event.prompt.text, routingFacts)
1560
+ const intent = classifyIntent(promptText, routingFacts)
1240
1561
  const effectiveMaxSkills = Math.max(
1241
1562
  1,
1242
1563
  Math.min(config.maxSkills, Number(policy?.maxSkills || config.maxSkills)),
1243
1564
  )
1244
1565
  const selected = []
1245
- for (const id of [...routeSkillsForPolicy(event.prompt.text, policy, effectiveMaxSkills, routingFacts), ...policySkills(policy)]) {
1566
+ for (const id of [...routeSkillsForPolicy(promptText, policy, effectiveMaxSkills, routingFacts), ...policySkills(policy)]) {
1246
1567
  if (!selected.includes(id)) selected.push(id)
1247
1568
  if (selected.length >= effectiveMaxSkills) break
1248
1569
  }
@@ -1266,7 +1587,7 @@ export default Plugin.define({
1266
1587
  feedbackDomains: routingFacts.feedbackDomains,
1267
1588
  acceptedLearningCount: routingFacts.acceptedLearningCount,
1268
1589
  },
1269
- version: 11,
1590
+ version: 13,
1270
1591
  effectiveMaxSkills,
1271
1592
  },
1272
1593
  }
@@ -1291,4 +1612,4 @@ export default Plugin.define({
1291
1612
  sessionAssignments.clear()
1292
1613
  }
1293
1614
  },
1294
- })
1615
+ }