opencode-agent-skill 13.0.0-beta.2 → 14.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +1556 -607
  2. package/bin/ocskill.mjs +172 -24
  3. package/docs/OPENCODE-COMPAT.md +34 -97
  4. package/docs/PI-COMPAT.md +188 -0
  5. package/docs/V14-CONTEXT-MEMORY-FABRIC.md +70 -0
  6. package/docs/V14.1-QUALITY-PERFORMANCE-FABRIC.md +114 -0
  7. package/docs/V14.2-TURBO-WEAK-MODEL-RUNTIME.md +448 -0
  8. package/evals/v14/tasks.json +46 -0
  9. package/global-config/agents/executor.md +7 -0
  10. package/global-config/agents/visual-verifier.md +22 -3
  11. package/global-config/plugins/ues-router/index.js +13 -8
  12. package/global-config/plugins/ues-router/policy-runtime.js +7 -0
  13. package/global-config/plugins/ues-router/router.js +12 -2
  14. package/global-config/skills/ecommerce-engineering/SKILL.md +1 -1
  15. package/global-config/skills/file-upload-engineering/SKILL.md +1 -1
  16. package/global-config/skills/git-safety/SKILL.md +1 -1
  17. package/global-config/skills/nestjs-engineering/SKILL.md +1 -1
  18. package/global-config/skills/performance-engineering/SKILL.md +1 -1
  19. package/global-config/skills/react-native-engineering/SKILL.md +1 -1
  20. package/global-config/skills/rest-api-design/SKILL.md +1 -1
  21. package/global-config/skills/ui-ux-engineering/SKILL.md +1 -1
  22. package/lib/adaptive-context-budget.mjs +97 -0
  23. package/lib/affected-tests.mjs +260 -0
  24. package/lib/benchmark-confidence.mjs +41 -2
  25. package/lib/browser-mcp-routing.mjs +166 -0
  26. package/lib/capability-fabric.mjs +336 -0
  27. package/lib/capability-registry.mjs +9 -0
  28. package/lib/context-engine-v11.mjs +65 -1
  29. package/lib/context-graph-rank.mjs +118 -0
  30. package/lib/context-manifest.mjs +97 -18
  31. package/lib/control-center.mjs +19 -1
  32. package/lib/dynamic-workflow.mjs +3 -1
  33. package/lib/evidence-store.mjs +82 -1
  34. package/lib/hierarchical-context.mjs +215 -0
  35. package/lib/memory-engine.mjs +465 -0
  36. package/lib/model-performance.mjs +33 -8
  37. package/lib/model-policy.mjs +3 -3
  38. package/lib/orchestrator-policy.mjs +5 -209
  39. package/lib/performance-fabric.mjs +229 -0
  40. package/lib/pi-rpc-pool.mjs +433 -0
  41. package/lib/process-hang-detector.mjs +83 -0
  42. package/lib/process-supervisor.mjs +193 -0
  43. package/lib/prompt-cache.mjs +2 -0
  44. package/lib/repo-graph.mjs +53 -2
  45. package/lib/runtime-config.mjs +31 -0
  46. package/lib/safety.mjs +132 -0
  47. package/lib/semantic-index.mjs +52 -3
  48. package/lib/skill-compiler.mjs +128 -0
  49. package/lib/skill-quality.mjs +48 -2
  50. package/lib/task-engine.mjs +66 -5
  51. package/lib/task-policy.mjs +235 -0
  52. package/lib/verification-broker.mjs +284 -0
  53. package/lib/verification-command.mjs +111 -0
  54. package/lib/windows-shim.mjs +35 -0
  55. package/lib/workspace-fingerprint.mjs +198 -0
  56. package/package.json +52 -42
  57. package/pi/extensions/ues-child-runtime.ts +238 -0
  58. package/pi/extensions/ues.ts +3200 -0
  59. package/pi/prompts/ues-audit.md +9 -0
  60. package/pi/prompts/ues-critique.md +9 -0
  61. package/pi/prompts/ues-debug.md +9 -0
  62. package/pi/prompts/ues-feature.md +9 -0
  63. package/pi/prompts/ues-fix.md +9 -0
  64. package/pi/prompts/ues-plan.md +9 -0
  65. package/pi/prompts/ues-research.md +9 -0
  66. package/pi/prompts/ues-resume.md +9 -0
  67. package/pi/prompts/ues-review.md +7 -0
  68. package/pi/prompts/ues-run.md +17 -0
  69. package/pi/prompts/ues-verify.md +9 -0
  70. package/scripts/check-release-consistency.mjs +119 -185
  71. package/scripts/check-runtime-exports.mjs +66 -0
  72. package/scripts/check-source-integrity.mjs +184 -0
  73. package/scripts/eval-pi.mjs +492 -0
  74. package/scripts/install.mjs +16 -0
  75. package/scripts/smoke-package-closure.mjs +110 -0
  76. package/scripts/smoke-packed-install.mjs +24 -11
  77. package/scripts/smoke-pi-extension.mjs +144 -0
  78. package/scripts/uninstall.mjs +44 -0
  79. package/CHANGELOG.md +0 -415
  80. package/docs/DETERMINISTIC-TOOLS.md +0 -105
  81. package/docs/ENGINEERING-DESIGN.md +0 -194
  82. package/docs/EVALS.md +0 -158
  83. package/docs/GITHUB-RULESET.md +0 -50
  84. package/docs/NPM-PUBLISH.md +0 -116
  85. package/docs/RESEARCH-SOURCES.md +0 -37
  86. package/docs/TRACE-SCHEMA.md +0 -122
  87. package/docs/V11-PERCEPTION-ADAPTIVE-EXECUTION.md +0 -75
  88. package/docs/V11-PERCEPTION-ADAPTIVE.md +0 -220
  89. package/docs/V12-WEAK-MODEL-INTELLIGENCE.md +0 -27
  90. package/docs/V13-PARALLEL-WEAK-MODEL-RUNTIME.md +0 -86
  91. package/docs/V7-INTELLIGENCE-RUNTIME.md +0 -166
  92. package/docs/V8-INTELLIGENCE-RELIABILITY.md +0 -206
  93. package/docs/V9-SPEED-INTELLIGENCE.md +0 -102
@@ -0,0 +1,184 @@
1
+ import assert from "node:assert/strict"
2
+ import { readFileSync, statSync } from "node:fs"
3
+ import path from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
7
+
8
+ const CONTRACTS = [
9
+ {
10
+ file: "pi/extensions/ues.ts",
11
+ minBytes: 80_000,
12
+ startsWith: "import { spawn }",
13
+ required: [
14
+ 'name: "ues_cli"',
15
+ 'name: "ues_execute"',
16
+ 'name: "ues_dispatch"',
17
+ "runRoutedAgent",
18
+ "PiRpcWorkerPool",
19
+ "CHILD_RUNTIME_EXTENSION",
20
+ "isAbortedRun",
21
+ "abortedResponse",
22
+ 'reason: "aborted"',
23
+ ],
24
+ },
25
+ {
26
+ file: "pi/extensions/ues-child-runtime.ts",
27
+ minBytes: 6_000,
28
+ startsWith: 'import type { ExtensionAPI }',
29
+ required: [
30
+ 'pi.on("tool_call"',
31
+ 'pi.on("tool_result"',
32
+ 'name: "ues_evidence_get"',
33
+ "recordVerification",
34
+ "canonicalVerificationCommand",
35
+ "destructiveShellRisk",
36
+ ],
37
+ },
38
+ {
39
+ file: "bin/ocskill.mjs",
40
+ minBytes: 74_000,
41
+ startsWith: "#!/usr/bin/env node",
42
+ required: [
43
+ 'case "context-pack"',
44
+ 'case "work"',
45
+ 'case "task-policy"',
46
+ 'case "sandbox"',
47
+ 'case "store"',
48
+ 'case "capabilities"',
49
+ 'case "capability-fabric"',
50
+ 'case "hierarchy"',
51
+ 'case "memory"',
52
+ 'case "visual"',
53
+ 'case "browser"',
54
+ 'case "workflow-plan"',
55
+ 'case "dashboard"',
56
+ 'case "models"',
57
+ 'case "update"',
58
+ 'case "remove"',
59
+ 'case "help"',
60
+ "getEvidenceSelected",
61
+ ],
62
+ },
63
+ {
64
+ file: "lib/task-engine.mjs",
65
+ minBytes: 45_000,
66
+ startsWith: "import ",
67
+ required: ["workspaceFingerprint", "initWork", "finalizeWork"],
68
+ },
69
+ {
70
+ file: "lib/model-performance.mjs",
71
+ minBytes: 5_500,
72
+ startsWith: "export const MODEL_TASK_CLASSES",
73
+ required: [
74
+ "recordPerformanceOutcome",
75
+ "wilsonLowerBound",
76
+ "rerankCapabilitySelection",
77
+ ],
78
+ },
79
+ {
80
+ file: "lib/evidence-store.mjs",
81
+ minBytes: 7_500,
82
+ startsWith: "import ",
83
+ required: ["putEvidence", "getEvidence", "getEvidenceSelected"],
84
+ },
85
+ {
86
+ file: "lib/process-supervisor.mjs",
87
+ minBytes: 4_500,
88
+ startsWith: "import ",
89
+ required: ["terminateProcessTree", "runSupervisedProcess", "drainTimeoutMs"],
90
+ },
91
+ {
92
+ file: "lib/pi-rpc-pool.mjs",
93
+ minBytes: 8_000,
94
+ startsWith: "import ",
95
+ required: ["class RpcWorker", "steerActive", "abortActive", "abortTransport", "activeAbort", "uesRpcPhase", "stopAll"],
96
+ },
97
+ {
98
+ file: "lib/workspace-fingerprint.mjs",
99
+ minBytes: 3_000,
100
+ startsWith: "import ",
101
+ required: ["runtimeWorkspaceFingerprint", "runtimeWorkspaceSnapshot", "changedFiles", "cacheable", "untrackedContentDigest", "untracked-total-too-large"],
102
+ },
103
+ {
104
+ file: "lib/skill-compiler.mjs",
105
+ minBytes: 4_000,
106
+ startsWith: "import ",
107
+ required: ["compileSkillContext", "clearSkillCompilerCache", "COMPILED_SKILL_CACHE.set", "return result"],
108
+ },
109
+ {
110
+ file: "lib/verification-command.mjs",
111
+ minBytes: 2_000,
112
+ startsWith: "export const VERIFICATION_COMMAND_RE",
113
+ required: [
114
+ "looksLikeVerificationCommand",
115
+ "canRecordReusableVerification",
116
+ "canonicalVerificationCommand",
117
+ ],
118
+ },
119
+ {
120
+ file: "lib/verification-broker.mjs",
121
+ minBytes: 8_000,
122
+ startsWith: "import ",
123
+ required: [
124
+ "recordVerification",
125
+ "findReusableVerification",
126
+ "listReusableVerification",
127
+ "withCrossProcessCacheLock",
128
+ "entryMatchesKey",
129
+ "freshEnough",
130
+ ],
131
+ },
132
+ {
133
+ file: "test/v14.2-runtime.test.mjs",
134
+ minBytes: 30_000,
135
+ startsWith: 'import assert from "node:assert/strict"',
136
+ required: [
137
+ 'test("V14.2 hot-path helper exports are live"',
138
+ 'test("external RPC abort rejects the active run instead of settling normally"',
139
+ 'test("verification broker preserves concurrent receipts in one workspace"',
140
+ 'test("RPC pool never evicts an active worker when an idle-capacity limit is exceeded"',
141
+ ],
142
+ },
143
+
144
+ {
145
+ file: "lib/affected-tests.mjs",
146
+ minBytes: 7_500,
147
+ startsWith: "import ",
148
+ required: ["resolveAffectedTests", "clearAffectedTestCache", "AFFECTED_TEST_CACHE.set", "return result"],
149
+ },
150
+ ]
151
+
152
+ const problems = []
153
+ for (const contract of CONTRACTS) {
154
+ const absolute = path.join(root, ...contract.file.split("/"))
155
+ let source = ""
156
+ let bytes = 0
157
+ try {
158
+ bytes = statSync(absolute).size
159
+ source = readFileSync(absolute, "utf8")
160
+ } catch (error) {
161
+ problems.push(`${contract.file}: unreadable (${error?.message || error})`)
162
+ continue
163
+ }
164
+
165
+ if (bytes < contract.minBytes) {
166
+ problems.push(`${contract.file}: ${bytes} bytes < required ${contract.minBytes}; possible fragment overwrite`)
167
+ }
168
+ if (!source.startsWith(contract.startsWith)) {
169
+ problems.push(`${contract.file}: source prefix mismatch; possible missing file head`)
170
+ }
171
+ for (const marker of contract.required) {
172
+ if (!source.includes(marker)) {
173
+ problems.push(`${contract.file}: missing integrity marker ${JSON.stringify(marker)}`)
174
+ }
175
+ }
176
+ }
177
+
178
+ assert.deepEqual(
179
+ problems,
180
+ [],
181
+ "Critical UES source-integrity validation failed:\n" + problems.map((item) => "- " + item).join("\n"),
182
+ )
183
+
184
+ console.log(`Source integrity passed for ${CONTRACTS.length} critical runtime files.`)
@@ -0,0 +1,492 @@
1
+ import { existsSync } from "node:fs"
2
+ import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"
3
+ import os from "node:os"
4
+ import path from "node:path"
5
+ import { spawnSync } from "node:child_process"
6
+ import { fileURLToPath } from "node:url"
7
+ import { evalModeOrder } from "../lib/eval-order.mjs"
8
+ import { runProcess } from "../lib/process-runner.mjs"
9
+ import { snapshotWorkspace, diffWorkspaceSnapshots } from "../lib/workspace-snapshot.mjs"
10
+ import { resolveManagedPiCommand, resolveWindowsCommand } from "../lib/windows-shim.mjs"
11
+ import { summarizeEvalResults } from "../lib/eval-report.mjs"
12
+ import { pairedBenchmarkConfidence } from "../lib/benchmark-confidence.mjs"
13
+
14
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
15
+ const args = process.argv.slice(2)
16
+ const abortController = new AbortController()
17
+ let interrupted = false
18
+
19
+ process.once("SIGINT", () => {
20
+ interrupted = true
21
+ console.warn("\n[pi-eval] interrupt requested; stopping the active Pi process tree...")
22
+ abortController.abort()
23
+ })
24
+
25
+ function argValue(name, fallback = null) {
26
+ const index = args.indexOf(name)
27
+ return index >= 0 && index + 1 < args.length ? args[index + 1] : fallback
28
+ }
29
+
30
+ function hasArg(name) {
31
+ return args.includes(name)
32
+ }
33
+
34
+ function argValues(name) {
35
+ const values = []
36
+ for (let index = 0; index < args.length; index += 1) {
37
+ if (args[index] === name && index + 1 < args.length) {
38
+ values.push(args[index + 1])
39
+ index += 1
40
+ }
41
+ }
42
+ return values
43
+ }
44
+
45
+ function defaultProviderExtensions(modelRef) {
46
+ const provider = String(modelRef || "").split("/", 1)[0].toLowerCase()
47
+ if (provider !== "kilo") return []
48
+
49
+ const installed = path.join(
50
+ os.homedir(),
51
+ ".pi",
52
+ "agent",
53
+ "git",
54
+ "github.com",
55
+ "Kilo-Org",
56
+ "kilo-pi-provider",
57
+ )
58
+ if (existsSync(installed)) return [installed]
59
+ return ["git:github.com/Kilo-Org/kilo-pi-provider"]
60
+ }
61
+
62
+ function positiveInt(value, fallback) {
63
+ const parsed = Number.parseInt(value ?? "", 10)
64
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback
65
+ }
66
+
67
+ function excerpt(value, limit = 16000) {
68
+ const text = String(value || "")
69
+ return text.length <= limit ? text : text.slice(0, limit) + "\n...[truncated]"
70
+ }
71
+
72
+ function resolveCommand(executable) {
73
+ if (process.platform !== "win32") return { executable, argsPrefix: [] }
74
+ const resolved = resolveWindowsCommand(executable)
75
+ if (resolved) return resolved
76
+ if (String(executable).toLowerCase() === "pi") return resolveManagedPiCommand()
77
+ return null
78
+ }
79
+
80
+ function runSync(executable, commandArgs, options = {}) {
81
+ const resolved = resolveCommand(executable)
82
+ if (!resolved) {
83
+ return {
84
+ status: 127,
85
+ stdout: "",
86
+ stderr: "No safely executable command found for: " + executable,
87
+ }
88
+ }
89
+ return spawnSync(resolved.executable, [...resolved.argsPrefix, ...commandArgs], options)
90
+ }
91
+
92
+ function runAsync(executable, commandArgs, options = {}) {
93
+ const resolved = resolveCommand(executable)
94
+ if (!resolved) {
95
+ return Promise.resolve({
96
+ status: 127,
97
+ signal: null,
98
+ stdout: "",
99
+ stderr: "No safely executable command found for: " + executable,
100
+ durationMs: 0,
101
+ timedOut: false,
102
+ idleTimedOut: false,
103
+ aborted: false,
104
+ })
105
+ }
106
+ return runProcess(resolved.executable, [...resolved.argsPrefix, ...commandArgs], options)
107
+ }
108
+
109
+ function zeroUsage() {
110
+ return {
111
+ input: 0,
112
+ output: 0,
113
+ cacheRead: 0,
114
+ cacheWrite: 0,
115
+ totalTokens: 0,
116
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
117
+ }
118
+ }
119
+
120
+ function addUsage(target, usage) {
121
+ if (!usage || typeof usage !== "object") return target
122
+ for (const key of ["input", "output", "cacheRead", "cacheWrite", "totalTokens"]) {
123
+ target[key] += Number(usage[key] || 0)
124
+ }
125
+ const cost = usage.cost || {}
126
+ for (const key of ["input", "output", "cacheRead", "cacheWrite", "total"]) {
127
+ target.cost[key] += Number(cost[key] || 0)
128
+ }
129
+ return target
130
+ }
131
+
132
+ function parsePiTelemetry(stdout) {
133
+ const parentUsage = zeroUsage()
134
+ const childUsage = zeroUsage()
135
+ const toolNames = {}
136
+ let parentUsageSamples = 0
137
+ let childUsageSamples = 0
138
+ let parentToolCalls = 0
139
+ let childToolCalls = 0
140
+ let controllerUsed = false
141
+ let controllerPass = false
142
+ let finalAssistant = ""
143
+ let jsonLines = 0
144
+ const turbo = {
145
+ routedAgentSteps: 0,
146
+ rpcRuns: 0,
147
+ cliRuns: 0,
148
+ warmWorkerReuses: 0,
149
+ contextCacheHits: 0,
150
+ microSkillCacheHits: 0,
151
+ affectedTestCacheHits: 0,
152
+ reusableVerificationReceipts: 0,
153
+ compactedToolOutputSteps: 0,
154
+ adaptiveContextChars: 0,
155
+ baseContextChars: 0,
156
+ }
157
+
158
+ for (const line of String(stdout || "").split(/\r?\n/)) {
159
+ if (!line.trim()) continue
160
+ let event
161
+ try {
162
+ event = JSON.parse(line)
163
+ } catch {
164
+ continue
165
+ }
166
+ jsonLines += 1
167
+
168
+ if (event.type === "tool_execution_start") {
169
+ parentToolCalls += 1
170
+ const name = String(event.toolName || "unknown")
171
+ toolNames[name] = Number(toolNames[name] || 0) + 1
172
+ if (name === "ues_execute") controllerUsed = true
173
+ }
174
+
175
+ if (event.type === "message_end" && event.message?.role === "assistant") {
176
+ if (event.message.usage) {
177
+ addUsage(parentUsage, event.message.usage)
178
+ parentUsageSamples += 1
179
+ }
180
+ if (Array.isArray(event.message.content)) {
181
+ const text = event.message.content
182
+ .filter((part) => part?.type === "text" && typeof part.text === "string")
183
+ .map((part) => part.text)
184
+ .join("\n")
185
+ if (text) finalAssistant = text
186
+ }
187
+ }
188
+
189
+ if (event.type === "tool_execution_end" && event.toolName === "ues_execute") {
190
+ const details = event.result?.details
191
+ const steps = Array.isArray(details?.steps) ? details.steps : []
192
+ for (const step of steps) {
193
+ const optimization = step?.optimizations
194
+ if (optimization && typeof optimization === "object") {
195
+ turbo.routedAgentSteps += 1
196
+ if (optimization.childRuntime === "rpc") turbo.rpcRuns += 1
197
+ if (optimization.childRuntime === "cli") turbo.cliRuns += 1
198
+ if (optimization.warmWorkerReused === true) turbo.warmWorkerReuses += 1
199
+ if (optimization.contextCacheHit === true) turbo.contextCacheHits += 1
200
+ if (optimization.microSkillCacheHit === true) turbo.microSkillCacheHits += 1
201
+ if (optimization.affectedTestCacheHit === true) turbo.affectedTestCacheHits += 1
202
+ turbo.reusableVerificationReceipts += Number(optimization.reusableVerificationReceipts || 0)
203
+ if (optimization.compactToolOutput === true) turbo.compactedToolOutputSteps += 1
204
+ turbo.adaptiveContextChars += Number(optimization.runtimeContextBudget || 0)
205
+ turbo.baseContextChars += Number(optimization.baseContextBudget || 0)
206
+ }
207
+ if (step?.usage) {
208
+ addUsage(childUsage, step.usage)
209
+ childUsageSamples += 1
210
+ }
211
+ childToolCalls += Number(step?.toolCalls || 0)
212
+ for (const name of step?.toolNames || []) {
213
+ const key = "child:" + String(name)
214
+ toolNames[key] = Number(toolNames[key] || 0) + 1
215
+ }
216
+ }
217
+ if (event.isError !== true && details?.mode === "execute") controllerPass = true
218
+ }
219
+ }
220
+
221
+ const totalUsage = zeroUsage()
222
+ addUsage(totalUsage, parentUsage)
223
+ addUsage(totalUsage, childUsage)
224
+
225
+ return {
226
+ schemaVersion: 1,
227
+ runtime: "pi",
228
+ jsonLines,
229
+ toolCalls: parentToolCalls + childToolCalls,
230
+ parentToolCalls,
231
+ childToolCalls,
232
+ toolNames,
233
+ usageSamples: parentUsageSamples + childUsageSamples,
234
+ parentUsageSamples,
235
+ childUsageSamples,
236
+ tokens: {
237
+ input: totalUsage.input,
238
+ output: totalUsage.output,
239
+ cacheRead: totalUsage.cacheRead,
240
+ cacheWrite: totalUsage.cacheWrite,
241
+ total: totalUsage.totalTokens,
242
+ },
243
+ costSamples: parentUsageSamples + childUsageSamples,
244
+ cost: totalUsage.cost.total,
245
+ parentUsage,
246
+ childUsage,
247
+ controllerUsed,
248
+ controllerPass,
249
+ finalAssistant,
250
+ turbo: {
251
+ ...turbo,
252
+ warmWorkerReuseRate: turbo.rpcRuns > 0 ? turbo.warmWorkerReuses / turbo.rpcRuns : null,
253
+ contextCacheHitRate: turbo.routedAgentSteps > 0 ? turbo.contextCacheHits / turbo.routedAgentSteps : null,
254
+ adaptiveContextRatio:
255
+ turbo.baseContextChars > 0 ? turbo.adaptiveContextChars / turbo.baseContextChars : null,
256
+ },
257
+ }
258
+ }
259
+
260
+ function safeName(value) {
261
+ return String(value || "model").replace(/[^a-z0-9._-]+/gi, "_").slice(0, 120)
262
+ }
263
+
264
+ const piCommand = argValue("--pi-command", process.env.UES_PI_COMMAND || "pi")
265
+ const model = argValue("--model", process.env.UES_EVAL_MODEL)
266
+ const thinking = argValue("--thinking", argValue("--variant", process.env.UES_EVAL_VARIANT))
267
+ const trials = Math.min(positiveInt(argValue("--trials"), 1), 20)
268
+ const taskFilter = argValue("--task")
269
+ const requestedMode = argValue("--mode", "both")
270
+ const suiteName = argValue("--suite", "live")
271
+ const keep = hasArg("--keep")
272
+ const timeoutMs = positiveInt(argValue("--timeout-ms"), 20 * 60_000)
273
+ const idleTimeoutMs = positiveInt(argValue("--idle-timeout-ms"), 7 * 60_000)
274
+ const heartbeatMs = positiveInt(argValue("--heartbeat-ms"), 30_000)
275
+ const explicitProviderExtensions = argValues("--provider-extension")
276
+ const providerExtensions = explicitProviderExtensions.length
277
+ ? explicitProviderExtensions
278
+ : defaultProviderExtensions(model)
279
+ const suiteRoot = path.join(root, "evals", suiteName)
280
+
281
+ if (!model) {
282
+ console.error("Usage: node scripts/eval-pi.mjs --model provider/model [--pi-command path-or-command] [--provider-extension path|npm:spec|git:spec] [--thinking off|minimal|low|medium|high|xhigh] [--suite live] [--trials N] [--task id] [--mode baseline|ues|both] [--keep]")
283
+ process.exit(2)
284
+ }
285
+ if (!["baseline", "ues", "both"].includes(requestedMode)) {
286
+ console.error("--mode must be baseline, ues, or both")
287
+ process.exit(2)
288
+ }
289
+ if (!existsSync(path.join(suiteRoot, "tasks.json"))) {
290
+ console.error("Unknown eval suite or missing tasks.json: " + suiteName)
291
+ process.exit(2)
292
+ }
293
+
294
+ const probe = runSync(piCommand, ["--version"], { encoding: "utf8" })
295
+ if (probe.status !== 0) {
296
+ console.error("Pi CLI is required for Pi-native evals. Checked command: " + piCommand)
297
+ console.error(excerpt(probe.stderr || probe.stdout))
298
+ process.exit(2)
299
+ }
300
+ const piVersion = String(probe.stdout || probe.stderr || "").trim()
301
+
302
+ const suite = JSON.parse(await readFile(path.join(suiteRoot, "tasks.json"), "utf8"))
303
+ let tasks = suite.tasks || []
304
+ if (taskFilter) tasks = tasks.filter((task) => task.id === taskFilter)
305
+ if (tasks.length === 0) {
306
+ console.error(taskFilter ? "Unknown task: " + taskFilter : "No eval tasks found.")
307
+ process.exit(2)
308
+ }
309
+
310
+ const invocationDir = process.cwd()
311
+ const runRoot = await mkdtemp(path.join(os.tmpdir(), "ues-pi-eval-"))
312
+ const resultDir = path.resolve(argValue("--output-dir", path.join(invocationDir, ".ues-evals")))
313
+ await mkdir(resultDir, { recursive: true })
314
+
315
+ const results = []
316
+
317
+ try {
318
+ for (const [taskIndex, task] of tasks.entries()) {
319
+ if (interrupted) break
320
+ for (let trial = 1; trial <= trials; trial += 1) {
321
+ if (interrupted) break
322
+ const trialModes = evalModeOrder(requestedMode, taskIndex, trial)
323
+
324
+ for (const mode of trialModes) {
325
+ if (interrupted) break
326
+
327
+ const isolatedRoot = path.join(runRoot, task.id + "-" + mode + "-" + trial)
328
+ const workspace = path.join(isolatedRoot, "workspace")
329
+ const uesConfigDir = path.join(isolatedRoot, "ues-config")
330
+ await mkdir(uesConfigDir, { recursive: true })
331
+ await cp(path.join(suiteRoot, task.fixture), workspace, { recursive: true })
332
+
333
+ const beforeSnapshot = await snapshotWorkspace(workspace)
334
+ const childEnv = {
335
+ ...process.env,
336
+ UES_CONFIG_DIR: uesConfigDir,
337
+ PI_SKIP_VERSION_CHECK: "true",
338
+ NO_COLOR: "1",
339
+ }
340
+
341
+ const piArgs = [
342
+ "--mode", "json",
343
+ "-p",
344
+ "--no-session",
345
+ "--no-extensions",
346
+ "--no-skills",
347
+ "--no-prompt-templates",
348
+ "--no-context-files",
349
+ "--model", model,
350
+ ]
351
+ if (thinking) piArgs.push("--thinking", thinking)
352
+ for (const extension of providerExtensions) {
353
+ piArgs.push("--extension", extension)
354
+ }
355
+
356
+ let prompt = task.prompt
357
+ if (mode === "ues") {
358
+ piArgs.push("--extension", path.join(root, "pi", "extensions", "ues.ts"))
359
+ prompt = [
360
+ "Use the UES runtime controller for this benchmark task.",
361
+ "You MUST call the ues_execute tool exactly once with the complete engineering task below and the current working directory.",
362
+ "Do not edit project files directly before that tool call. After the tool returns, report its verified result concisely.",
363
+ "",
364
+ "Engineering task:",
365
+ task.prompt,
366
+ ].join("\n")
367
+ }
368
+ piArgs.push(prompt)
369
+
370
+ console.log(
371
+ "[" + mode + "] " + task.id + " trial " + trial + ": starting Pi " + piVersion +
372
+ (providerExtensions.length ? " with provider extension isolation" : ""),
373
+ )
374
+ const agentRun = await runAsync(piCommand, piArgs, {
375
+ cwd: workspace,
376
+ env: childEnv,
377
+ maxBuffer: 8 * 1024 * 1024,
378
+ heartbeatMs,
379
+ timeoutMs,
380
+ idleTimeoutMs,
381
+ signal: abortController.signal,
382
+ onHeartbeat: ({ elapsedMs, idleMs }) => {
383
+ console.log(
384
+ "[" + mode + "] " + task.id + " trial " + trial +
385
+ ": still running (" + Math.round(elapsedMs / 1000) + "s elapsed, " +
386
+ Math.round(idleMs / 1000) + "s since output)",
387
+ )
388
+ },
389
+ })
390
+
391
+ const graderPath = path.join(suiteRoot, task.grader)
392
+ const graderRun = spawnSync(process.execPath, [graderPath], {
393
+ cwd: workspace,
394
+ env: { ...childEnv, UES_EVAL_WORKSPACE: workspace, UES_EVAL_TASK: task.id },
395
+ encoding: "utf8",
396
+ maxBuffer: 1024 * 1024,
397
+ })
398
+
399
+ const afterSnapshot = await snapshotWorkspace(workspace)
400
+ const changedFiles = diffWorkspaceSnapshots(beforeSnapshot, afterSnapshot)
401
+ const telemetry = parsePiTelemetry(agentRun.stdout)
402
+ const baselineIsolated = mode !== "baseline" || telemetry.controllerUsed === false
403
+ const controllerValid =
404
+ mode === "baseline"
405
+ ? baselineIsolated
406
+ : (telemetry.controllerUsed && telemetry.controllerPass)
407
+ const passed = agentRun.status === 0 && graderRun.status === 0 && controllerValid
408
+
409
+ results.push({
410
+ task: task.id,
411
+ mode,
412
+ runtime: "pi",
413
+ model,
414
+ variant: thinking || null,
415
+ trial,
416
+ passed,
417
+ agentExit: agentRun.status,
418
+ graderExit: graderRun.status,
419
+ controllerValid,
420
+ baselineIsolated,
421
+ durationMs: agentRun.durationMs,
422
+ timedOut: agentRun.timedOut,
423
+ idleTimedOut: agentRun.idleTimedOut,
424
+ aborted: agentRun.aborted,
425
+ piVersion,
426
+ telemetry,
427
+ changedFiles,
428
+ agentStdout: excerpt(agentRun.stdout),
429
+ agentStderr: excerpt(agentRun.stderr),
430
+ graderStdout: excerpt(graderRun.stdout),
431
+ graderStderr: excerpt(graderRun.stderr),
432
+ workspace: keep ? workspace : null,
433
+ timestamp: new Date().toISOString(),
434
+ })
435
+
436
+ console.log(
437
+ "[" + mode + "] " + task.id + " trial " + trial + ": " +
438
+ (passed ? "PASS" : "FAIL") +
439
+ " (agent=" + agentRun.status + ", grader=" + graderRun.status +
440
+ ", controller=" + controllerValid +
441
+ (mode === "baseline" ? ", isolated=" + baselineIsolated : "") + ")",
442
+ )
443
+ }
444
+ }
445
+ }
446
+
447
+ const summary = summarizeEvalResults(results)
448
+ const confidence = requestedMode === "both"
449
+ ? pairedBenchmarkConfidence(results, {
450
+ minPairs: positiveInt(argValue("--min-pairs"), 20),
451
+ suiteRegressionTolerance: Number(argValue("--suite-regression-tolerance", "0")),
452
+ qualityRegressionTolerance: Number(argValue("--quality-regression-tolerance", "0")),
453
+ maxDurationRatio: Number(argValue("--max-duration-ratio", "1.75")),
454
+ maxInitialInputRatio: Number(argValue("--max-initial-input-ratio", "1.5")),
455
+ maxTokenRatio: Number(argValue("--max-token-ratio", "1.75")),
456
+ })
457
+ : null
458
+ const payload = {
459
+ schemaVersion: 1,
460
+ runtime: "pi",
461
+ suite: suiteName,
462
+ model,
463
+ variant: thinking || null,
464
+ trials,
465
+ requestedMode,
466
+ piVersion,
467
+ providerExtensionCount: providerExtensions.length,
468
+ providerExtensionSource: explicitProviderExtensions.length ? "explicit" : (providerExtensions.length ? "auto" : "none"),
469
+ interrupted,
470
+ generatedAt: new Date().toISOString(),
471
+ summary,
472
+ confidence,
473
+ results,
474
+ }
475
+
476
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-")
477
+ const outputFile = path.join(
478
+ resultDir,
479
+ "pi-" + safeName(suiteName) + "-" + safeName(model) + "-" + stamp + ".json",
480
+ )
481
+ await writeFile(outputFile, JSON.stringify(payload, null, 2) + "\n", "utf8")
482
+
483
+ console.log("\nPi-native eval summary")
484
+ console.log(JSON.stringify(summary, null, 2))
485
+ if (confidence) {
486
+ console.log("\nPi-native paired confidence")
487
+ console.log(JSON.stringify(confidence, null, 2))
488
+ }
489
+ console.log("Result file: " + outputFile)
490
+ } finally {
491
+ if (!keep) await rm(runRoot, { recursive: true, force: true }).catch(() => {})
492
+ }
@@ -5,6 +5,16 @@ import { installResources } from "../lib/installer.mjs"
5
5
 
6
6
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
7
7
 
8
+ function isPiPackageInstall() {
9
+ const normalized = packageRoot.replaceAll("\\", "/").toLowerCase()
10
+ return [
11
+ "/.pi/agent/git/",
12
+ "/.pi/agent/npm/",
13
+ "/.pi/git/",
14
+ "/.pi/npm/",
15
+ ].some((marker) => normalized.includes(marker))
16
+ }
17
+
8
18
  function npmGlobalPrefix() {
9
19
  if (process.platform === "win32") {
10
20
  const result = spawnSync(
@@ -30,6 +40,12 @@ function isGlobalInstall() {
30
40
  return packageRoot.toLowerCase().startsWith(path.resolve(prefix).toLowerCase())
31
41
  }
32
42
 
43
+ if (isPiPackageInstall()) {
44
+ console.log("[ocskill] Pi package install detected; Pi will load UES from the package manifest.")
45
+ console.log("[ocskill] Skipping OpenCode global resource setup.")
46
+ process.exit(0)
47
+ }
48
+
33
49
  if (!isGlobalInstall()) {
34
50
  console.log("[ocskill] Local npm install detected; skipping OpenCode global setup.")
35
51
  console.log("[ocskill] Use 'node bin/ocskill.mjs install' to test installation manually.")