opencode-agent-skill 14.2.0-beta.1 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +51 -18
  2. package/docs/PI-COMPAT.md +167 -3
  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 +1 -1
  13. package/lib/capability-fabric.mjs +40 -0
  14. package/lib/code-intelligence/edit-anchor.mjs +102 -0
  15. package/lib/code-intelligence/index.mjs +95 -0
  16. package/lib/code-intelligence/lsp-provider.mjs +189 -0
  17. package/lib/completion-auditor.mjs +102 -0
  18. package/lib/document-ingestion.mjs +60 -0
  19. package/lib/evidence-store.mjs +92 -8
  20. package/lib/execution-contract.mjs +579 -0
  21. package/lib/fast-verification-gate.mjs +69 -0
  22. package/lib/leaf-runtime-optimizer.mjs +76 -0
  23. package/lib/mcp-health.mjs +144 -0
  24. package/lib/mcp-tool-policy.mjs +19 -0
  25. package/lib/memory-engine.mjs +28 -0
  26. package/lib/pi-child-invocation.mjs +65 -0
  27. package/lib/pi-rpc-pool.mjs +61 -9
  28. package/lib/plan-salvage.mjs +124 -0
  29. package/lib/planning-speed-policy.mjs +131 -0
  30. package/lib/prompt-cache.mjs +26 -12
  31. package/lib/repo-graph.mjs +1 -1
  32. package/lib/repo-inspect.mjs +1 -0
  33. package/lib/reversible-context.mjs +49 -0
  34. package/lib/runtime-artifacts.mjs +42 -0
  35. package/lib/runtime-events.mjs +33 -1
  36. package/lib/safety.mjs +1 -0
  37. package/lib/semantic-index.mjs +1 -1
  38. package/lib/service-manager.mjs +526 -0
  39. package/lib/session-display.mjs +49 -0
  40. package/lib/task-engine.mjs +6 -10
  41. package/lib/task-graph.mjs +87 -0
  42. package/lib/task-policy.mjs +210 -8
  43. package/lib/trajectory.mjs +111 -1
  44. package/lib/turbo-fast-path.mjs +44 -0
  45. package/lib/workspace-fingerprint.mjs +2 -11
  46. package/lib/workspace-root.mjs +49 -0
  47. package/lib/worktree-sandbox.mjs +234 -11
  48. package/package.json +9 -6
  49. package/pi/extensions/ues-child-runtime.ts +300 -2
  50. package/pi/extensions/ues.ts +1887 -160
  51. package/scripts/check-release-consistency.mjs +25 -2
  52. package/scripts/check-source-integrity.mjs +532 -3
  53. package/scripts/eval-pi.mjs +76 -36
  54. package/scripts/smoke-pi-extension.mjs +1 -1
  55. package/pi/prompts/ues-run.md +0 -17
@@ -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
+ }
@@ -5,31 +5,35 @@ function canonical(value) {
5
5
  if (!value || typeof value !== "object") return value
6
6
  return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]))
7
7
  }
8
-
9
- function stringify(value) {
10
- return typeof value === "string" ? value : JSON.stringify(canonical(value))
11
- }
12
-
13
- function digest(value) {
14
- return createHash("sha256").update(stringify(value)).digest("hex")
8
+ function stringify(value) { return typeof value === "string" ? value : JSON.stringify(canonical(value)) }
9
+ function digest(value) { return createHash("sha256").update(stringify(value)).digest("hex") }
10
+ function chars(value) { return stringify(value ?? "").length }
11
+ function finiteTokenCount(value) {
12
+ const number = Number(value)
13
+ return Number.isFinite(number) && number >= 0 ? number : null
15
14
  }
16
-
17
- function chars(value) {
18
- return stringify(value ?? "").length
15
+ function changedStableKeys(previous, current) {
16
+ const left = previous?.stable || {}
17
+ const right = current?.stable || {}
18
+ return [...new Set([...Object.keys(left), ...Object.keys(right)])]
19
+ .filter((key) => digest(left[key]) !== digest(right[key]))
20
+ .sort()
19
21
  }
20
22
 
21
23
  export function buildPromptEnvelope(input = {}) {
24
+ const memorySnapshot = input.memorySnapshot || null
22
25
  const stable = {
23
26
  invariants: input.invariants || "",
24
27
  role: input.role || "",
25
28
  skills: input.skills || [],
26
29
  projectFacts: input.projectFacts || {},
27
30
  toolPolicy: input.toolPolicy || {},
31
+ memorySnapshot,
28
32
  }
29
33
  const dynamic = {
30
34
  task: input.task || {},
31
35
  evidence: input.evidence || [],
32
- memories: input.memories || [],
36
+ memories: memorySnapshot ? [] : (input.memories || []),
33
37
  contextHints: input.contextHints || {},
34
38
  recentFailure: input.recentFailure || null,
35
39
  nextAction: input.nextAction || null,
@@ -45,18 +49,28 @@ export function buildPromptEnvelope(input = {}) {
45
49
  dynamicHash: digest(dynamic),
46
50
  stableChars,
47
51
  dynamicChars,
52
+ cacheableChars: stableChars,
48
53
  cacheableRatio: stableChars + dynamicChars ? stableChars / (stableChars + dynamicChars) : 0,
54
+ cacheReadTokens: finiteTokenCount(input.cacheReadTokens),
55
+ cacheWriteTokens: finiteTokenCount(input.cacheWriteTokens),
49
56
  }
50
57
  }
51
58
 
52
- export function comparePromptEnvelopes(previous, current) {
59
+ export function comparePromptEnvelopes(previous, current, usage = {}) {
53
60
  const stableReused = Boolean(previous?.stablePrefixHash && previous.stablePrefixHash === current?.stablePrefixHash)
54
61
  const repeatedStableChars = stableReused ? Number(current?.stableChars || 0) : 0
62
+ const mutated = stableReused ? [] : changedStableKeys(previous, current)
55
63
  return {
56
64
  schemaVersion: 1,
57
65
  stableReused,
58
66
  repeatedStableChars,
59
67
  avoidableRepeatedChars: stableReused ? 0 : Math.min(Number(previous?.stableChars || 0), Number(current?.stableChars || 0)),
60
68
  currentCacheableRatio: Number(current?.cacheableRatio || 0),
69
+ cacheableChars: Number(current?.cacheableChars ?? current?.stableChars ?? 0),
70
+ dynamicChars: Number(current?.dynamicChars || 0),
71
+ cacheReadTokens: finiteTokenCount(usage.cacheReadTokens ?? current?.cacheReadTokens),
72
+ cacheWriteTokens: finiteTokenCount(usage.cacheWriteTokens ?? current?.cacheWriteTokens),
73
+ prefixMutationReason: stableReused ? null : (mutated.length ? mutated.join(",") : "stable-prefix-changed"),
74
+ mutatedStableKeys: mutated,
61
75
  }
62
76
  }
@@ -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,49 @@
1
+ import { putEvidence, getEvidence, getEvidenceSelected } from "./evidence-store.mjs"
2
+
3
+ function normalize(value) { return String(value ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n") }
4
+ function significantLines(text) {
5
+ const lines = normalize(text).split("\n")
6
+ const scored = lines.map((line, index) => {
7
+ const trimmed = line.trim(); let score = 0
8
+ if (/^#{1,6}\s/.test(trimmed)) score += 8
9
+ if (/^(?:[-*+] |\d+[.)]\s)/.test(trimmed)) score += 5
10
+ if (/\b(?:error|fail|warning|todo|fixme|pass|test|result|summary|important|must|required)\b/i.test(trimmed)) score += 4
11
+ if (/^(?:export|import|class|function|def|interface|type|CREATE|ALTER)\b/i.test(trimmed)) score += 3
12
+ if (trimmed.length > 0 && trimmed.length <= 180) score += 1
13
+ return { index, line, score }
14
+ })
15
+ return { lines, scored }
16
+ }
17
+ function summary(text, maxChars) {
18
+ const { lines, scored } = significantLines(text)
19
+ if (normalize(text).length <= maxChars) return normalize(text)
20
+ const wanted = new Set(scored.filter((row) => row.score > 0).sort((a,b) => b.score - a.score || a.index - b.index).slice(0,80).map((row) => row.index))
21
+ for (let i = Math.max(0, lines.length - 8); i < lines.length; i += 1) wanted.add(i)
22
+ let out = ""
23
+ for (const index of [...wanted].sort((a,b) => a-b)) {
24
+ const candidate = (out ? out + "\n" : "") + lines[index]
25
+ if (candidate.length > maxChars) break
26
+ out = candidate
27
+ }
28
+ return out || normalize(text).slice(0, maxChars)
29
+ }
30
+ export async function compactContext(root, content, options = {}) {
31
+ const raw = normalize(content)
32
+ const stored = await putEvidence(root, raw, { kind: options.kind || "context-block", source: options.source || "ues-context", summary: options.summary || "Reversible raw context block" })
33
+ return { schemaVersion: 1, ref: stored.ref, originalChars: raw.length, levels: { T1: summary(raw, Math.max(512, Number(options.t1Chars || 1600))), T2: summary(raw, Math.max(1200, Number(options.t2Chars || 5000))), T3: summary(raw, Math.max(2400, Number(options.t3Chars || 12000))) } }
34
+ }
35
+ export async function expandContext(root, ref, options = {}) {
36
+ return getEvidenceSelected(root, ref, { start: Math.max(0, Number(options.start || 0)), maxBytes: Math.max(1, Math.min(128_000, Number(options.maxBytes || 16_000))) })
37
+ }
38
+ export async function searchContext(root, ref, query, options = {}) {
39
+ const maxBytes = Math.max(16_000, Math.min(4 * 1024 * 1024, Number(options.maxBytes || 512_000)))
40
+ const evidence = await getEvidence(root, ref, { maxBytes })
41
+ const source = normalize(evidence?.content ?? evidence ?? "")
42
+ const terms = [...new Set(String(query || "").toLowerCase().split(/[^\p{L}\p{N}_$.-]+/u).filter((item) => item.length >= 2))].slice(0, 16)
43
+ const lines = source.split("\n"); const radius = Math.max(0, Math.min(12, Number(options.radius || 2))); const maxMatches = Math.max(1, Math.min(50, Number(options.maxMatches || 10))); const hits = []
44
+ for (let index = 0; index < lines.length && hits.length < maxMatches; index += 1) {
45
+ const lower = lines[index].toLowerCase(); const matched = terms.filter((term) => lower.includes(term)); if (!matched.length) continue
46
+ hits.push({ line: index + 1, terms: matched, excerpt: lines.slice(Math.max(0,index-radius), Math.min(lines.length,index+radius+1)).join("\n") })
47
+ }
48
+ return { schemaVersion: 1, ref, query: String(query || ""), terms, hits, truncatedSource: source.length >= maxBytes }
49
+ }
@@ -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",