opencode-agent-skill 14.4.0 → 15.1.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.
Files changed (53) hide show
  1. package/README.md +34 -16
  2. package/docs/PI-COMPAT.md +151 -2
  3. package/docs/V15-MANAGED-RUNTIME.md +128 -0
  4. package/global-config/agents/architect.md +63 -1
  5. package/global-config/agents/executor.md +7 -0
  6. package/global-config/agents/plan-checker.md +8 -1
  7. package/global-config/agents/verifier.md +3 -3
  8. package/global-config/skills/database-engineering/references/workflow.md +15 -0
  9. package/global-config/skills/engineering-orchestrator/references/long-horizon.md +21 -11
  10. package/lib/activity-deadline.mjs +70 -0
  11. package/lib/adaptive-context-budget.mjs +25 -5
  12. package/lib/affected-tests.mjs +38 -5
  13. package/lib/capability-fabric.mjs +17 -0
  14. package/lib/code-intelligence/index.mjs +3 -2
  15. package/lib/code-intelligence/lsp-provider.mjs +326 -52
  16. package/lib/compaction-resume-guard.mjs +255 -0
  17. package/lib/completion-auditor.mjs +25 -5
  18. package/lib/context-engine-v11.mjs +20 -7
  19. package/lib/evidence-store.mjs +92 -8
  20. package/lib/execution-contract.mjs +592 -0
  21. package/lib/leaf-runtime-optimizer.mjs +76 -0
  22. package/lib/memory-engine.mjs +189 -16
  23. package/lib/performance-fabric.mjs +262 -107
  24. package/lib/pi-child-invocation.mjs +65 -0
  25. package/lib/pi-rpc-pool.mjs +61 -9
  26. package/lib/plan-salvage.mjs +124 -0
  27. package/lib/planning-speed-policy.mjs +131 -0
  28. package/lib/repo-graph.mjs +1 -1
  29. package/lib/repo-inspect.mjs +1 -0
  30. package/lib/runtime-artifacts.mjs +42 -0
  31. package/lib/runtime-events.mjs +33 -1
  32. package/lib/safety.mjs +1 -0
  33. package/lib/semantic-index.mjs +1 -1
  34. package/lib/service-manager.mjs +526 -0
  35. package/lib/session-display.mjs +49 -0
  36. package/lib/subagent-artifacts.mjs +182 -0
  37. package/lib/task-graph.mjs +87 -0
  38. package/lib/task-policy.mjs +150 -4
  39. package/lib/trajectory.mjs +111 -1
  40. package/lib/turbo-fast-path.mjs +44 -0
  41. package/lib/verification-broker.mjs +32 -27
  42. package/lib/workspace-fingerprint.mjs +45 -19
  43. package/lib/workspace-hygiene.mjs +481 -0
  44. package/lib/workspace-root.mjs +49 -0
  45. package/lib/worktree-sandbox.mjs +234 -11
  46. package/package.json +8 -5
  47. package/pi/extensions/ues-child-runtime.ts +170 -4
  48. package/pi/extensions/ues.ts +2063 -188
  49. package/scripts/check-release-consistency.mjs +25 -2
  50. package/scripts/check-source-integrity.mjs +471 -4
  51. package/scripts/eval-pi.mjs +76 -36
  52. package/scripts/smoke-pi-extension.mjs +1 -1
  53. package/pi/prompts/ues-run.md +0 -17
@@ -0,0 +1,124 @@
1
+ import {
2
+ normalizePlanForValidation,
3
+ validatePlan,
4
+ } from "./task-graph.mjs"
5
+
6
+ function balancedObject(source, start) {
7
+ let depth = 0
8
+ let inString = false
9
+ let escaped = false
10
+ for (let index = start; index < source.length; index++) {
11
+ const char = source[index]
12
+ if (inString) {
13
+ if (escaped) escaped = false
14
+ else if (char === "\\") escaped = true
15
+ else if (char === "\"") inString = false
16
+ continue
17
+ }
18
+ if (char === "\"") {
19
+ inString = true
20
+ continue
21
+ }
22
+ if (char === "{") depth += 1
23
+ else if (char === "}") {
24
+ depth -= 1
25
+ if (depth === 0) {
26
+ const raw = source.slice(start, index + 1)
27
+ try {
28
+ return { value: JSON.parse(raw), raw, end: index + 1 }
29
+ } catch {
30
+ return null
31
+ }
32
+ }
33
+ }
34
+ }
35
+ return null
36
+ }
37
+
38
+ function markedCandidate(source, marker) {
39
+ const markerIndex = source.lastIndexOf(marker)
40
+ if (markerIndex < 0) return null
41
+ const start = source.indexOf("{", markerIndex + marker.length)
42
+ if (start < 0) return null
43
+ const parsed = balancedObject(source, start)
44
+ return parsed ? { ...parsed, source: "marked" } : null
45
+ }
46
+
47
+ function fencedCandidates(source) {
48
+ const results = []
49
+ const fence = String.fromCharCode(96, 96, 96)
50
+ let offset = 0
51
+ while (offset < source.length) {
52
+ const open = source.indexOf(fence, offset)
53
+ if (open < 0) break
54
+ const bodyStart = source.indexOf("\n", open + fence.length)
55
+ if (bodyStart < 0) break
56
+ const close = source.indexOf(fence, bodyStart + 1)
57
+ if (close < 0) break
58
+ const body = source.slice(bodyStart + 1, close).trim()
59
+ if (body.startsWith("{")) {
60
+ try { results.push({ value: JSON.parse(body), raw: body, source: "fenced" }) } catch {}
61
+ }
62
+ offset = close + fence.length
63
+ }
64
+ return results
65
+ }
66
+
67
+ function balancedCandidates(source, limit = 24) {
68
+ const starts = []
69
+ for (let i = source.length - 1; i >= 0 && starts.length < limit; i--) {
70
+ if (source[i] === "{") starts.push(i)
71
+ }
72
+ const results = []
73
+ for (const start of starts) {
74
+ const parsed = balancedObject(source, start)
75
+ if (parsed) results.push({ ...parsed, source: "unmarked-balanced" })
76
+ }
77
+ return results
78
+ }
79
+
80
+ function planLike(value) {
81
+ return Boolean(
82
+ value &&
83
+ typeof value === "object" &&
84
+ Array.isArray(value.tasks) &&
85
+ (typeof value.goal === "string" || value.schemaVersion === 1)
86
+ )
87
+ }
88
+
89
+ export function extractValidatedPlan(output, options = {}) {
90
+ const source = String(output || "")
91
+ const marker = String(options.marker || "UES_PLAN_JSON:")
92
+ const candidates = []
93
+ const marked = markedCandidate(source, marker)
94
+ if (marked) candidates.push(marked)
95
+ candidates.push(...fencedCandidates(source))
96
+ candidates.push(...balancedCandidates(source))
97
+
98
+ const seen = new Set()
99
+ let bestInvalid = null
100
+ for (const candidate of candidates) {
101
+ if (!planLike(candidate.value)) continue
102
+ let normalized
103
+ try { normalized = normalizePlanForValidation(candidate.value) } catch { continue }
104
+ const key = JSON.stringify(normalized)
105
+ if (seen.has(key)) continue
106
+ seen.add(key)
107
+ const validation = validatePlan(normalized)
108
+ const result = {
109
+ plan: normalized,
110
+ validation,
111
+ source: candidate.source,
112
+ salvaged: candidate.source !== "marked",
113
+ }
114
+ if (validation && validation.valid === true) return result
115
+ if (!bestInvalid) bestInvalid = result
116
+ }
117
+
118
+ return bestInvalid || {
119
+ plan: null,
120
+ validation: null,
121
+ source: null,
122
+ salvaged: false,
123
+ }
124
+ }
@@ -0,0 +1,131 @@
1
+ function bounded(value, fallback, min, max) {
2
+ const parsed = Number(value)
3
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback
4
+ return Math.max(min, Math.min(max, Math.trunc(parsed)))
5
+ }
6
+
7
+ export function planningRuntimeBudget(role, attempt = 1, options = {}) {
8
+ const normalizedRole = String(role || "").replace(/^ues-/, "")
9
+ const normalizedAttempt = Math.max(1, Math.trunc(Number(attempt) || 1))
10
+
11
+ if (normalizedRole === "architect") {
12
+ const recovery = normalizedAttempt > 1
13
+ const deep = String(options.executionProfile || "") === "deep"
14
+ const largePrompt = Number(options.taskChars || 0) >= 2500
15
+ const initialHard = recovery ? 50_000 : 60_000
16
+ const absoluteHard = recovery
17
+ ? 95_000
18
+ : deep || largePrompt
19
+ ? 150_000
20
+ : 120_000
21
+ return {
22
+ hardTimeoutMs: bounded(
23
+ recovery ? options.architectRecoveryHardTimeoutMs : options.architectHardTimeoutMs,
24
+ initialHard,
25
+ 30_000,
26
+ 5 * 60_000,
27
+ ),
28
+ absoluteHardTimeoutMs: bounded(
29
+ recovery ? options.architectRecoveryAbsoluteHardTimeoutMs : options.architectAbsoluteHardTimeoutMs,
30
+ absoluteHard,
31
+ initialHard,
32
+ 6 * 60_000,
33
+ ),
34
+ activityExtensionMs: bounded(
35
+ recovery ? options.architectRecoveryActivityExtensionMs : options.architectActivityExtensionMs,
36
+ recovery ? 25_000 : 35_000,
37
+ 10_000,
38
+ 90_000,
39
+ ),
40
+ activityWindowMs: bounded(
41
+ options.architectActivityWindowMs,
42
+ 20_000,
43
+ 5_000,
44
+ 60_000,
45
+ ),
46
+ idleTimeoutMs: bounded(
47
+ recovery ? options.architectRecoveryIdleTimeoutMs : options.architectIdleTimeoutMs,
48
+ recovery ? 28_000 : 40_000,
49
+ 10_000,
50
+ 2 * 60_000,
51
+ ),
52
+ softSteerMs: bounded(
53
+ options.architectSoftSteerMs,
54
+ 30_000,
55
+ 15_000,
56
+ 2 * 60_000,
57
+ ),
58
+ maxExplorationTools: bounded(options.architectMaxExplorationTools, 16, 8, 80),
59
+ }
60
+ }
61
+
62
+ if (normalizedRole === "plan-checker") {
63
+ const recovery = normalizedAttempt > 1
64
+ const initialHard = recovery ? 45_000 : 70_000
65
+ return {
66
+ hardTimeoutMs: bounded(
67
+ recovery ? options.planCheckerRecoveryHardTimeoutMs : options.planCheckerHardTimeoutMs,
68
+ initialHard,
69
+ 30_000,
70
+ 5 * 60_000,
71
+ ),
72
+ absoluteHardTimeoutMs: bounded(
73
+ recovery ? options.planCheckerRecoveryAbsoluteHardTimeoutMs : options.planCheckerAbsoluteHardTimeoutMs,
74
+ recovery ? 80_000 : 130_000,
75
+ initialHard,
76
+ 5 * 60_000,
77
+ ),
78
+ activityExtensionMs: bounded(
79
+ recovery ? options.planCheckerRecoveryActivityExtensionMs : options.planCheckerActivityExtensionMs,
80
+ recovery ? 20_000 : 30_000,
81
+ 10_000,
82
+ 60_000,
83
+ ),
84
+ activityWindowMs: bounded(
85
+ options.planCheckerActivityWindowMs,
86
+ 20_000,
87
+ 5_000,
88
+ 60_000,
89
+ ),
90
+ idleTimeoutMs: bounded(
91
+ recovery ? options.planCheckerRecoveryIdleTimeoutMs : options.planCheckerIdleTimeoutMs,
92
+ recovery ? 24_000 : 38_000,
93
+ 10_000,
94
+ 2 * 60_000,
95
+ ),
96
+ softSteerMs: bounded(
97
+ recovery ? options.planCheckerRecoverySoftSteerMs : options.planCheckerSoftSteerMs,
98
+ recovery ? 18_000 : 35_000,
99
+ 10_000,
100
+ 2 * 60_000,
101
+ ),
102
+ maxExplorationTools: bounded(
103
+ recovery ? options.planCheckerRecoveryMaxExplorationTools : options.planCheckerMaxExplorationTools,
104
+ recovery ? 6 : 10,
105
+ 4,
106
+ 40,
107
+ ),
108
+ }
109
+ }
110
+
111
+ return null
112
+ }
113
+
114
+ export function shouldSoftSteerArchitect(progress = {}, budget = null) {
115
+ if (!budget) return false
116
+ const elapsedMs = Number(progress.elapsedMs || 0)
117
+ const toolCalls = Number(progress.toolCalls || 0)
118
+ const idleMs = Number(progress.idleMs || 0)
119
+
120
+ if (budget.softSteerMs && elapsedMs >= budget.softSteerMs) return true
121
+ if (budget.maxExplorationTools && toolCalls >= budget.maxExplorationTools) return true
122
+
123
+ // If the model has gone quiet after doing real exploration, ask it to emit
124
+ // the plan before the hard idle watchdog has to kill the worker.
125
+ return toolCalls > 0 && idleMs >= Math.max(10_000, Math.floor((budget.idleTimeoutMs || 30_000) * 0.65))
126
+ }
127
+
128
+
129
+ export function shouldSoftSteerPlanningRole(progress = {}, budget = null) {
130
+ return shouldSoftSteerArchitect(progress, budget)
131
+ }
@@ -6,7 +6,7 @@ const SKIP = new Set([
6
6
  ".git", "node_modules", ".next", "dist", "build", "coverage", ".venv", "venv",
7
7
  "Pods", "DerivedData", ".gradle", ".idea", ".cache", ".turbo", "target", "obj",
8
8
  ".ues-work", ".ues-cache", ".ues-traces", ".ues-learning", ".ues-dashboard",
9
- ".ues-sandboxes", ".ues-memory", ".ues-evals",
9
+ ".ues-sandboxes", ".ues-memory", ".ues-evals", ".ues-services",
10
10
  ])
11
11
 
12
12
  const SOURCE_EXTENSIONS = new Set([
@@ -6,6 +6,7 @@ import { spawnSync } from "node:child_process"
6
6
  const SKIP_DIRS = new Set([
7
7
  ".git", "node_modules", ".next", "dist", "build", "coverage", ".venv", "venv",
8
8
  "Pods", "DerivedData", ".gradle", ".idea", ".cache", ".turbo", "target", "bin", "obj",
9
+ ".ues-services",
9
10
  ])
10
11
 
11
12
  const TEXT_EXTENSIONS = new Set([
@@ -0,0 +1,42 @@
1
+ export const UES_RUNTIME_DIRS = Object.freeze([
2
+ ".ues-cache",
3
+ ".ues-traces",
4
+ ".ues-work",
5
+ ".ues-learning",
6
+ ".ues-dashboard",
7
+ ".ues-sandboxes",
8
+ ".ues-memory",
9
+ ".ues-evals",
10
+ ".ues-services",
11
+ ])
12
+
13
+ export function normalizeRuntimePath(value) {
14
+ return String(value || "")
15
+ .replaceAll("\\", "/")
16
+ .replace(/^\.\//, "")
17
+ .replace(/^\/+/, "")
18
+ }
19
+
20
+ export function isUesRuntimeArtifactPath(value) {
21
+ const normalized = normalizeRuntimePath(value)
22
+ if (!normalized) return false
23
+ return UES_RUNTIME_DIRS.some(
24
+ (dir) => normalized === dir || normalized.startsWith(dir + "/"),
25
+ )
26
+ }
27
+
28
+ export function sourceFacingPaths(values = []) {
29
+ return [...new Set(
30
+ (values || [])
31
+ .map(normalizeRuntimePath)
32
+ .filter(Boolean)
33
+ .filter((value) => !isUesRuntimeArtifactPath(value)),
34
+ )]
35
+ }
36
+
37
+ export function sourceGitPathspecs() {
38
+ return [
39
+ ".",
40
+ ...UES_RUNTIME_DIRS.map((dir) => ":(exclude)" + dir + "/**"),
41
+ ]
42
+ }
@@ -1,7 +1,39 @@
1
1
  import { randomUUID } from "node:crypto"
2
- import { appendFile, readFile } from "node:fs/promises"
2
+ import { appendFile, readFile, stat, writeFile } from "node:fs/promises"
3
+
4
+ const DEFAULT_MAX_EVENT_FILE_BYTES = 4 * 1024 * 1024
5
+ const DEFAULT_RETAIN_EVENT_BYTES = 2 * 1024 * 1024
6
+
7
+ async function compactRuntimeEventFile(file, options = {}) {
8
+ const maxBytes = Math.max(
9
+ 256 * 1024,
10
+ Math.min(64 * 1024 * 1024, Number(options.maxBytes || process.env.UES_EVENT_LOG_MAX_BYTES || DEFAULT_MAX_EVENT_FILE_BYTES)),
11
+ )
12
+ const retainBytes = Math.max(
13
+ 128 * 1024,
14
+ Math.min(maxBytes, Number(options.retainBytes || DEFAULT_RETAIN_EVENT_BYTES)),
15
+ )
16
+ const info = await stat(file).catch(() => null)
17
+ if (!info?.isFile() || info.size < maxBytes) return false
18
+
19
+ const raw = await readFile(file)
20
+ let kept = raw.subarray(Math.max(0, raw.length - retainBytes))
21
+ const newline = kept.indexOf(0x0a)
22
+ if (newline >= 0 && raw.length > retainBytes) kept = kept.subarray(newline + 1)
23
+ const marker = Buffer.from(JSON.stringify({
24
+ schemaVersion: 1,
25
+ id: randomUUID(),
26
+ type: "runtime-events.compacted",
27
+ at: new Date().toISOString(),
28
+ previousBytes: raw.length,
29
+ retainedBytes: kept.length,
30
+ }) + "\n")
31
+ await writeFile(file, Buffer.concat([marker, kept]))
32
+ return true
33
+ }
3
34
 
4
35
  export async function appendRuntimeEvent(file, type, data = {}) {
36
+ await compactRuntimeEventFile(file).catch(() => false)
5
37
  const event = {
6
38
  schemaVersion: 1,
7
39
  id: randomUUID(),
package/lib/safety.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  const RULES = [
2
2
  { id: "git-force", pattern: /\bgit\s+(?:push\b[^\n]*(?:--force(?:-with-lease)?|(?:^|\s)-f(?:\s|$))|reset\s+--hard|clean\s+-[^\n]*f)/i },
3
+ { id: "git-discard-work", pattern: /\bgit\s+(?:restore\b|checkout\s+--(?:\s|$)|stash(?:\s|$))/i },
3
4
  { id: "history-rewrite", pattern: /\bgit\s+(?:rebase\b|filter-branch\b|filter-repo\b)/i },
4
5
  { id: "publish", pattern: /\b(?:npm\s+publish|pnpm\s+publish|yarn\s+npm\s+publish)\b/i },
5
6
  { id: "destructive-files", pattern: /(?:^|[;&|]\s*)(?:rm\s+-[^\n]*r[^\n]*f|rmdir\s+\/s|del\s+\/s|remove-item\b[^\n]*-recurse[^\n]*-force)/i },
@@ -12,7 +12,7 @@ const SOURCE_EXTENSIONS = new Set([
12
12
  const SKIP_DIRS = new Set([
13
13
  ".git", "node_modules", ".next", "dist", "build", "coverage", ".venv", "venv",
14
14
  "Pods", "DerivedData", ".gradle", ".idea", ".cache", ".turbo", "target", "obj",
15
- ".ues-work", ".ues-learning", ".ues-memory", ".ues-dashboard", ".ues-sandboxes", ".ues-cache", ".ues-traces",
15
+ ".ues-work", ".ues-learning", ".ues-memory", ".ues-dashboard", ".ues-sandboxes", ".ues-cache", ".ues-traces", ".ues-services",
16
16
  ])
17
17
  const STOP_IDENTIFIERS = new Set([
18
18
  "const","let","var","function","class","interface","type","export","import","from","return",