opencode-agent-skill 14.4.0 → 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.
- package/README.md +33 -15
- package/docs/PI-COMPAT.md +151 -2
- package/docs/V15-MANAGED-RUNTIME.md +128 -0
- package/global-config/agents/architect.md +63 -1
- package/global-config/agents/executor.md +7 -0
- package/global-config/agents/plan-checker.md +8 -1
- package/global-config/agents/verifier.md +3 -3
- package/global-config/skills/database-engineering/references/workflow.md +15 -0
- package/global-config/skills/engineering-orchestrator/references/long-horizon.md +21 -11
- package/lib/activity-deadline.mjs +70 -0
- package/lib/adaptive-context-budget.mjs +25 -5
- package/lib/affected-tests.mjs +1 -1
- package/lib/capability-fabric.mjs +17 -0
- package/lib/completion-auditor.mjs +25 -5
- package/lib/evidence-store.mjs +92 -8
- package/lib/execution-contract.mjs +579 -0
- package/lib/leaf-runtime-optimizer.mjs +76 -0
- package/lib/pi-child-invocation.mjs +65 -0
- package/lib/pi-rpc-pool.mjs +61 -9
- package/lib/plan-salvage.mjs +124 -0
- package/lib/planning-speed-policy.mjs +131 -0
- package/lib/repo-graph.mjs +1 -1
- package/lib/repo-inspect.mjs +1 -0
- package/lib/runtime-artifacts.mjs +42 -0
- package/lib/runtime-events.mjs +33 -1
- package/lib/safety.mjs +1 -0
- package/lib/semantic-index.mjs +1 -1
- package/lib/service-manager.mjs +526 -0
- package/lib/session-display.mjs +49 -0
- package/lib/task-graph.mjs +87 -0
- package/lib/task-policy.mjs +150 -4
- package/lib/trajectory.mjs +111 -1
- package/lib/turbo-fast-path.mjs +44 -0
- package/lib/workspace-fingerprint.mjs +2 -11
- package/lib/workspace-root.mjs +49 -0
- package/lib/worktree-sandbox.mjs +234 -11
- package/package.json +8 -5
- package/pi/extensions/ues-child-runtime.ts +145 -2
- package/pi/extensions/ues.ts +1760 -160
- package/scripts/check-release-consistency.mjs +25 -2
- package/scripts/check-source-integrity.mjs +471 -4
- package/scripts/eval-pi.mjs +76 -36
- package/scripts/smoke-pi-extension.mjs +1 -1
- package/pi/prompts/ues-run.md +0 -17
package/lib/task-policy.mjs
CHANGED
|
@@ -2,12 +2,32 @@ const SENSITIVE_DOMAIN = /(\bauth\b|authorization|authentication|security|permis
|
|
|
2
2
|
const HIGH_RISK_MUTATION = /((?:fix|change|modify|update|alter|migrate|drop|truncate|delete|remove|rotate|deploy|publish|push|sửa|thay đổi|cập nhật|xóa|xoá|di trú|chuyển đổi|triển khai).{0,64}(?:\bauth\b|authorization|authentication|security|permission|payment(?: handling| flow)?|schema|database|production|public api|secret|credential|phân quyền|bảo mật|thanh toán|cơ sở dữ liệu|api công khai|bí mật|thông tin xác thực)|(?:\bauth\b|authorization|authentication|security|permission|payment(?: handling| flow)?|schema|database|production|public api|secret|credential|phân quyền|bảo mật|thanh toán|cơ sở dữ liệu|api công khai|bí mật|thông tin xác thực).{0,64}(?:fix|change|modify|update|alter|migrate|drop|truncate|delete|remove|rotate|deploy|publish|push|sửa|thay đổi|cập nhật|xóa|xoá|di trú|chuyển đổi|triển khai)|database migration|schema migration|migrate database|migrate schema|drop table|truncate table|deploy(?:ment)?\s+(?:to\s+)?production|production\s+deploy(?:ment)?|rotate\s+(?:secret|credential)|breaking\s+(?:change\s+to\s+)?(?:public\s+)?api|npm publish|git push|force push|reset --hard|git clean)/i
|
|
3
3
|
const LONG = /(whole repo|whole repository|whole project|entire repo|entire project|large refactor|major refactor|long[- ](?:running|horizon)|durable state|dependency graph|integration verification|resume|refactor all|multi[- ]step migration|migration across|toàn bộ repo|toàn bộ repository|toàn bộ dự án|toàn bộ project|refactor lớn|tác vụ dài|nhiều file|nhiều module|tiếp tục công việc|refactor toàn bộ|xác minh tích hợp|kiểm tra tích hợp|chia (?:công việc|task|tác vụ).*(?:dependency|phụ thuộc))/i
|
|
4
4
|
const DEBUG = /(fix|bug|debug|crash|regression|failure|error|broken|sửa lỗi|lỗi|điều tra lỗi|không chạy)/i
|
|
5
|
+
const CONCRETE_DIAGNOSIS = /(\bdebug\b|diagnos|reproduce|stack\s*trace|exception|\bcrash(?:es|ed|ing)?\b|failing\s+test|tests?\s+(?:currently\s+)?fail(?:ing|ed)?|failure\s+(?:affects|occurs|happens|when|on)|error\s*[:=]|expected.{0,48}actual|actual.{0,48}expected|exit\s+code\s*[1-9]|\b(?:ECONN\w*|SQLSTATE|TypeError|ReferenceError|AssertionError)\b|điều\s+tra\s+lỗi|tái\s+hiện\s+lỗi|lỗi\s*[:=]|test.{0,32}(?:thất\s+bại|fail))/i
|
|
5
6
|
const CONTRACT = /(public api|api contract|openapi|response schema|request schema|breaking api|hợp đồng api|api công khai)/i
|
|
6
7
|
const DATA = /(database|sql|migration|schema|transaction|index|cơ sở dữ liệu|dữ liệu|migrate)/i
|
|
7
8
|
const BROAD_FILE_SCOPE = /(across\s+(?:multiple|several|many)\s+(?:files|modules|packages)|multiple\s+(?:files|modules|packages)|several\s+(?:files|modules|packages)|many\s+(?:files|modules|packages)|nhiều\s+(?:file|tệp|module)|toàn bộ\s+(?:repo|repository|project|dự án))/i
|
|
8
9
|
const CRITICAL_LOCAL_MUTATION = /(drop\s+table|truncate\s+table|reset\s+--hard|git\s+clean|force\s+push|npm\s+publish|deploy(?:ment)?\s+(?:to\s+)?production|production\s+deploy(?:ment)?|rotate\s+(?:secret|credential)|(?:secret|credential|token).{0,48}(?:rotate|replace|revoke|delete)|(?:authorization|authentication|permission|phân quyền|xác thực).{0,64}(?:change|modify|update|bypass|remove|delete|sửa|thay đổi)|(?:database|sql|schema).{0,64}(?:migration|migrate|drop|truncate|alter)|(?:migration|migrate).{0,64}(?:database|sql|schema)|payment.{0,64}(?:charge|capture|refund|webhook|checkout)|(?:charge|capture|refund|webhook|checkout).{0,64}payment|breaking\s+(?:change\s+to\s+)?(?:public\s+)?api)/i
|
|
10
|
+
const EXPLICIT_READ_ONLY = /(?:\bread[- ]?only\b|\bdo not\s+(?:edit|modify|change|write|create|delete|remove)\b|\bwithout\s+(?:editing|modifying|changing|writing|creating|deleting|removing)\b|\bno\s+(?:source|file|code)?\s*(?:edits?|changes?|writes?)\b|không\s+(?:sửa|chỉnh sửa|thay đổi|ghi|tạo|xóa|xoá)\b|chỉ\s+(?:đọc|kiểm tra|rà soát)\b)/i
|
|
11
|
+
const READ_ONLY_MODE = /(?:\bread[- ]?only\b|chế\s+độ\s+chỉ\s+đọc|chế\s+độ\s+read[- ]?only)/i
|
|
12
|
+
const MUTATION_INTENT = /(?:\b(?:fix|repair|implement|add|edit|modify|change|write|create|delete|remove|update|refactor|migrate|rename)\b|(?:sửa|chỉnh sửa|thay đổi|ghi|tạo|xóa|xoá|cập nhật|refactor|di trú|đổi tên))/i
|
|
9
13
|
const SOURCE_FILE_RE = /(?:^|[\s("'\x60])((?:[A-Za-z]:)?(?:[./\\\w@-]+[\\/])*[\w@.-]+\.(?:[cm]?[jt]sx?|py|go|rs|java|kt|kts|cs|rb|php|swift|dart|vue|svelte))(?=$|[\s"'\x60),:;.])/gi
|
|
10
14
|
|
|
15
|
+
const COMMAND_ONLY_READ_ONLY = /(?:chỉ\s+(?:chạy|thực hiện)(?:\s+đúng)?|only\s+(?:run|execute)|run\s+only|execute\s+only|exactly\s+(?:run|execute))/i
|
|
16
|
+
|
|
17
|
+
const AUTO_ENGINEERING_ACTION = /(?:\b(?:fix|repair|debug|implement|add|edit|modify|change|update|refactor|migrate|review|audit|verify|test|build|run|inspect|scan|check|analy[sz]e|optimi[sz]e|cleanup|clean up|commit|push|continue|resume)\b|(?:sửa|chỉnh sửa|xử lý|điều tra|thêm|tạo|cập nhật|thay đổi|refactor|di trú|rà soát|kiểm tra|xác minh|chạy|test|build|xem|phân tích|tối ưu|dọn|commit|push|tiếp tục))/i
|
|
18
|
+
const AUTO_ENGINEERING_TARGET = /(?:\b(?:repo(?:sitory)?|project|code|source|file|module|package|branch|commit|git|npm|pnpm|yarn|node|typescript|javascript|python|java|kotlin|swift|react|expo|android|ios|api|backend|frontend|mobile|web|database|db|sql|prisma|test|e2e|ci|build|lint|typecheck|runtime|server|client|bug|error|failure|exception)\b|(?:dự án|mã nguồn|tệp|nhánh|cơ sở dữ liệu|dữ liệu|kiểm thử|lỗi|ứng dụng|giao diện|máy chủ))/i
|
|
19
|
+
const AUTO_INFORMATIONAL_ONLY = /(?:^|\b)(?:explain|what\s+is|what\s+are|how\s+does|why\s+does|compare|difference\s+between|giải\s+thích|là\s+gì|hoạt\s+động\s+(?:ra\s+sao|thế\s+nào)|tại\s+sao|so\s+sánh)(?:\b|\s)/i
|
|
20
|
+
const AUTO_NON_TASK_CHAT = /^(?:xin\s*chào|chào|hello|hi|hey|cảm\s*ơn|cam\s*on|thanks?|ok(?:ay)?|được|ừ|uh|vâng|vang)[!?.\s]*$/i
|
|
21
|
+
const AUTO_ACTIVE_CONTINUATION = /^(?:continue|resume|keep\s+going|go\s+on|tiếp\s+tục|làm\s+tiếp|lam\s+tiep|sửa\s+tiếp|sua\s+tiep|kiểm\s+tra\s+tiếp|kiem\s+tra\s+tiep)(?:\b|[.!?,;:\s])/i
|
|
22
|
+
|
|
23
|
+
const SAFE_READ_ONLY_GIT_CHECKS = [
|
|
24
|
+
{ id: "git-status-short", label: "git status --short", match: /^git\s+status\s+--short\b/i, args: ["status", "--short"] },
|
|
25
|
+
{ id: "git-branch-current", label: "git branch --show-current", match: /^git\s+branch\s+--show-current\b/i, args: ["branch", "--show-current"] },
|
|
26
|
+
{ id: "git-rev-parse-head", label: "git rev-parse HEAD", match: /^git\s+rev-parse\s+head\b/i, args: ["rev-parse", "HEAD"] },
|
|
27
|
+
{ id: "git-rev-parse-root", label: "git rev-parse --show-toplevel", match: /^git\s+rev-parse\s+--show-toplevel\b/i, args: ["rev-parse", "--show-toplevel"] },
|
|
28
|
+
{ id: "git-status", label: "git status", match: /^git\s+status\b/i, args: ["status"] },
|
|
29
|
+
]
|
|
30
|
+
|
|
11
31
|
function referencedSourceFiles(value) {
|
|
12
32
|
const files = []
|
|
13
33
|
const seen = new Set()
|
|
@@ -47,6 +67,107 @@ function riskTextFor(value) {
|
|
|
47
67
|
.replace(/chỉ\s+đọc[^.\n]*/gi, "")
|
|
48
68
|
}
|
|
49
69
|
|
|
70
|
+
export function automaticUesContinuation(value, facts = {}) {
|
|
71
|
+
const text = String(value || "").trim()
|
|
72
|
+
if (!facts.activeRun) return { forward: false, reason: "no-active-run" }
|
|
73
|
+
if (!text || text.startsWith("/")) return { forward: false, reason: "not-continuation" }
|
|
74
|
+
if (!AUTO_ACTIVE_CONTINUATION.test(text)) return { forward: false, reason: "not-continuation" }
|
|
75
|
+
return { forward: true, reason: "active-run-continuation" }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function automaticUesAdmission(value, facts = {}) {
|
|
79
|
+
const text = String(value || "").trim()
|
|
80
|
+
const native = (reason, policy = null) => ({
|
|
81
|
+
admit: false,
|
|
82
|
+
route: "native",
|
|
83
|
+
confidence: "low",
|
|
84
|
+
reason,
|
|
85
|
+
policy,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
if (!text) return native("empty")
|
|
89
|
+
if (text.startsWith("/")) return native("explicit-command")
|
|
90
|
+
if (AUTO_NON_TASK_CHAT.test(text)) return native("casual-chat")
|
|
91
|
+
if (facts.inGitWorkspace === false) return native("not-git-workspace")
|
|
92
|
+
|
|
93
|
+
const policy = classifyEngineeringTask(text, facts)
|
|
94
|
+
const action = AUTO_ENGINEERING_ACTION.test(text)
|
|
95
|
+
const target = AUTO_ENGINEERING_TARGET.test(text)
|
|
96
|
+
const informationalOnly =
|
|
97
|
+
AUTO_INFORMATIONAL_ONLY.test(text) &&
|
|
98
|
+
!/(?:\b(?:fix|repair|debug|implement|edit|modify|change|update|refactor|migrate|test|build|run|commit|push)\b|(?:sửa|chỉnh sửa|xử lý|thay đổi|cập nhật|refactor|chạy|test|build|commit|push))/i.test(text)
|
|
99
|
+
const longStructured =
|
|
100
|
+
target &&
|
|
101
|
+
(
|
|
102
|
+
text.length >= 700 ||
|
|
103
|
+
policy.mode === "long-horizon" ||
|
|
104
|
+
/(?:^|\n)\s*(?:phase\s+\d+|mục\s+tiêu|requirements?|acceptance criteria|final report)\b/i.test(text)
|
|
105
|
+
)
|
|
106
|
+
const readOnlyEngineering =
|
|
107
|
+
policy.readOnly === true &&
|
|
108
|
+
/(?:repo(?:sitory)?|project|code|source|git|file|test|dự án|mã nguồn|tệp|kiểm tra repository)/i.test(text)
|
|
109
|
+
|
|
110
|
+
if (informationalOnly && !longStructured && !readOnlyEngineering) {
|
|
111
|
+
return native("informational-question", policy)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const admit = longStructured || (action && target) || readOnlyEngineering
|
|
115
|
+
if (!admit) return native("not-confident-engineering-task", policy)
|
|
116
|
+
|
|
117
|
+
const route = policy.risk === "high" ? "guarded" : "auto"
|
|
118
|
+
return {
|
|
119
|
+
admit: true,
|
|
120
|
+
route,
|
|
121
|
+
confidence: longStructured || readOnlyEngineering ? "high" : "medium",
|
|
122
|
+
reason:
|
|
123
|
+
route === "guarded"
|
|
124
|
+
? "high-risk-engineering-task"
|
|
125
|
+
: longStructured
|
|
126
|
+
? "long-structured-engineering-task"
|
|
127
|
+
: readOnlyEngineering
|
|
128
|
+
? "read-only-engineering-task"
|
|
129
|
+
: "engineering-action-target",
|
|
130
|
+
policy,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function deterministicReadOnlyGitCommands(value) {
|
|
135
|
+
const text = String(value || "")
|
|
136
|
+
if (!COMMAND_ONLY_READ_ONLY.test(text)) return []
|
|
137
|
+
|
|
138
|
+
const mentions = []
|
|
139
|
+
const lower = text.toLowerCase()
|
|
140
|
+
const gitRe = /\bgit\s+/g
|
|
141
|
+
let hit
|
|
142
|
+
while ((hit = gitRe.exec(lower))) {
|
|
143
|
+
const tail = text.slice(hit.index)
|
|
144
|
+
const matched = SAFE_READ_ONLY_GIT_CHECKS.find((item) => item.match.test(tail))
|
|
145
|
+
if (!matched) return []
|
|
146
|
+
mentions.push({ index: hit.index, id: matched.id, label: matched.label, command: "git", args: [...matched.args] })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!mentions.length) return []
|
|
150
|
+
const seen = new Set()
|
|
151
|
+
return mentions
|
|
152
|
+
.sort((a, b) => a.index - b.index)
|
|
153
|
+
.filter((item) => {
|
|
154
|
+
if (seen.has(item.id)) return false
|
|
155
|
+
seen.add(item.id)
|
|
156
|
+
return true
|
|
157
|
+
})
|
|
158
|
+
.map(({ id, label, command, args }) => ({ id, label, command, args }))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function readOnlyTask(value, facts = {}) {
|
|
162
|
+
if (facts.readOnly === true) return true
|
|
163
|
+
const text = String(value || "")
|
|
164
|
+
if (READ_ONLY_MODE.test(text)) return true
|
|
165
|
+
if (!EXPLICIT_READ_ONLY.test(text)) return false
|
|
166
|
+
// Negative file-scope constraints ("fix X, but do not modify Y") must not
|
|
167
|
+
// convert a mutating engineering task into a read-only inspection.
|
|
168
|
+
return !MUTATION_INTENT.test(riskTextFor(text))
|
|
169
|
+
}
|
|
170
|
+
|
|
50
171
|
function signal(name, matched, weight) {
|
|
51
172
|
return matched ? { name, weight } : null
|
|
52
173
|
}
|
|
@@ -185,6 +306,17 @@ export function shouldRunDedicatedDiagnosis(taskPolicy = {}, attempt = 1) {
|
|
|
185
306
|
return recoveryPolicyForAttempt(taskPolicy, normalizedAttempt).requireDiagnosis === true
|
|
186
307
|
}
|
|
187
308
|
|
|
309
|
+
// Long-horizon work already has an architect + independent plan gate. A generic
|
|
310
|
+
// word such as "fix" must not force a redundant debugger pass before planning.
|
|
311
|
+
// Keep first-pass diagnosis when the request contains concrete failure evidence.
|
|
312
|
+
if (
|
|
313
|
+
taskPolicy.mode === "long-horizon" &&
|
|
314
|
+
taskPolicy.requirePlanCheck === true &&
|
|
315
|
+
taskPolicy.diagnosisEvidence !== true
|
|
316
|
+
) {
|
|
317
|
+
return false
|
|
318
|
+
}
|
|
319
|
+
|
|
188
320
|
return taskPolicy.executionProfile !== "fast" || taskPolicy.risk !== "low"
|
|
189
321
|
}
|
|
190
322
|
|
|
@@ -192,7 +324,8 @@ export function classifyEngineeringTask(text, facts = {}) {
|
|
|
192
324
|
const value = String(text || "")
|
|
193
325
|
const riskText = riskTextFor(value)
|
|
194
326
|
const sensitiveDomain = SENSITIVE_DOMAIN.test(value)
|
|
195
|
-
const
|
|
327
|
+
const readOnly = readOnlyTask(value, facts)
|
|
328
|
+
const singleFileBounded = readOnly ? false : singleFileBoundedTask(value, facts)
|
|
196
329
|
const rawSensitiveMutation = HIGH_RISK_MUTATION.test(riskText)
|
|
197
330
|
const sensitiveMutation =
|
|
198
331
|
rawSensitiveMutation &&
|
|
@@ -223,6 +356,7 @@ export function classifyEngineeringTask(text, facts = {}) {
|
|
|
223
356
|
signal("declared-high-risk", declaredHighRisk, 2),
|
|
224
357
|
signal("explicit-long-horizon", explicitLongHorizon, 2),
|
|
225
358
|
signal("debugging", DEBUG.test(value), 1),
|
|
359
|
+
signal("read-only", readOnly, -1),
|
|
226
360
|
signal("single-file-bounded", singleFileBounded, -2),
|
|
227
361
|
signal("public-contract", CONTRACT.test(value) || facts.hasPublicContract, 2),
|
|
228
362
|
signal("data-migration", dataMigration, 2),
|
|
@@ -234,6 +368,7 @@ export function classifyEngineeringTask(text, facts = {}) {
|
|
|
234
368
|
|
|
235
369
|
const score = Math.max(0, signals.reduce((sum, item) => sum + item.weight, 0))
|
|
236
370
|
const highRisk = declaredHighRisk || sensitiveMutation || Boolean(facts.hasMigration) || Boolean(facts.hasPublicContract)
|
|
371
|
+
const diagnosisEvidence = facts.failureEvidence === true || CONCRETE_DIAGNOSIS.test(value)
|
|
237
372
|
const risk = highRisk ? "high" : singleFileBounded ? "low" : score >= 3 ? "medium" : "low"
|
|
238
373
|
const mode =
|
|
239
374
|
explicitLongHorizon
|
|
@@ -270,11 +405,22 @@ export function classifyEngineeringTask(text, facts = {}) {
|
|
|
270
405
|
maxAttempts,
|
|
271
406
|
contextBudget: profile.contextBudget,
|
|
272
407
|
maxSkills: profile.maxSkills,
|
|
273
|
-
requirePlanCheck: profile.durableState || risk === "high",
|
|
274
|
-
requireIntegrationVerification: mode !== "inline" || risk === "high",
|
|
408
|
+
requirePlanCheck: readOnly ? false : profile.durableState || risk === "high",
|
|
409
|
+
requireIntegrationVerification: readOnly ? false : mode !== "inline" || risk === "high",
|
|
275
410
|
requireFreshEvidence: true,
|
|
411
|
+
requireBehavioralReceipt: !readOnly,
|
|
412
|
+
readOnly,
|
|
276
413
|
singleFileBounded,
|
|
277
|
-
|
|
414
|
+
diagnosisEvidence,
|
|
415
|
+
profile: readOnly
|
|
416
|
+
? {
|
|
417
|
+
...profile,
|
|
418
|
+
worktree: "off",
|
|
419
|
+
verification: "command-evidence",
|
|
420
|
+
fullCI: false,
|
|
421
|
+
containerVerification: "off",
|
|
422
|
+
}
|
|
423
|
+
: profile,
|
|
278
424
|
domains: [...new Set(domains)],
|
|
279
425
|
recovery: {
|
|
280
426
|
escalateAfterFailure: true,
|
package/lib/trajectory.mjs
CHANGED
|
@@ -1,9 +1,115 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto"
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises"
|
|
2
|
+
import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises"
|
|
3
3
|
import path from "node:path"
|
|
4
4
|
|
|
5
5
|
const TRACE_DIR = ".ues-traces"
|
|
6
6
|
const MAX_EVENT_BYTES = 64 * 1024
|
|
7
|
+
const DEFAULT_MAX_TRACE_FILE_BYTES = 4 * 1024 * 1024
|
|
8
|
+
const DEFAULT_MAX_TRACE_TOTAL_BYTES = 24 * 1024 * 1024
|
|
9
|
+
const DEFAULT_MAX_TRACE_FILES = 32
|
|
10
|
+
const DEFAULT_MAX_TRACE_AGE_MS = 7 * 86_400_000
|
|
11
|
+
|
|
12
|
+
function positiveInt(value, fallback, min, max) {
|
|
13
|
+
const parsed = Number(value)
|
|
14
|
+
if (!Number.isFinite(parsed)) return fallback
|
|
15
|
+
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function traceLimits(options = {}) {
|
|
19
|
+
return {
|
|
20
|
+
maxFileBytes: positiveInt(
|
|
21
|
+
options.maxFileBytes ?? process.env.UES_TRACE_MAX_FILE_BYTES,
|
|
22
|
+
DEFAULT_MAX_TRACE_FILE_BYTES,
|
|
23
|
+
256 * 1024,
|
|
24
|
+
64 * 1024 * 1024,
|
|
25
|
+
),
|
|
26
|
+
maxTotalBytes: positiveInt(
|
|
27
|
+
options.maxTotalBytes ?? process.env.UES_TRACE_MAX_TOTAL_BYTES,
|
|
28
|
+
DEFAULT_MAX_TRACE_TOTAL_BYTES,
|
|
29
|
+
1024 * 1024,
|
|
30
|
+
512 * 1024 * 1024,
|
|
31
|
+
),
|
|
32
|
+
maxFiles: positiveInt(
|
|
33
|
+
options.maxFiles ?? process.env.UES_TRACE_MAX_FILES,
|
|
34
|
+
DEFAULT_MAX_TRACE_FILES,
|
|
35
|
+
4,
|
|
36
|
+
256,
|
|
37
|
+
),
|
|
38
|
+
maxAgeMs: positiveInt(
|
|
39
|
+
options.maxAgeMs ?? process.env.UES_TRACE_MAX_AGE_MS,
|
|
40
|
+
DEFAULT_MAX_TRACE_AGE_MS,
|
|
41
|
+
60_000,
|
|
42
|
+
90 * 86_400_000,
|
|
43
|
+
),
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function compactTraceFile(file, maxFileBytes) {
|
|
48
|
+
const info = await stat(file).catch(() => null)
|
|
49
|
+
if (!info?.isFile() || info.size < maxFileBytes) return false
|
|
50
|
+
|
|
51
|
+
const source = await readFile(file)
|
|
52
|
+
const keepBytes = Math.max(128 * 1024, Math.floor(maxFileBytes / 2))
|
|
53
|
+
let kept = source.subarray(Math.max(0, source.length - keepBytes))
|
|
54
|
+
const newline = kept.indexOf(0x0a)
|
|
55
|
+
if (newline >= 0 && source.length > keepBytes) kept = kept.subarray(newline + 1)
|
|
56
|
+
const marker = Buffer.from(JSON.stringify({
|
|
57
|
+
schemaVersion: 1,
|
|
58
|
+
type: "trace.compacted",
|
|
59
|
+
at: new Date().toISOString(),
|
|
60
|
+
payload: { previousBytes: source.length, retainedBytes: kept.length },
|
|
61
|
+
}) + "\n")
|
|
62
|
+
await writeFile(file, Buffer.concat([marker, kept]))
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function pruneTrajectoryStore(root = process.cwd(), options = {}) {
|
|
67
|
+
root = path.resolve(root)
|
|
68
|
+
const dir = path.join(root, TRACE_DIR)
|
|
69
|
+
const limits = traceLimits(options)
|
|
70
|
+
const protect = options.protectTraceID ? cleanID(options.protectTraceID) + ".jsonl" : null
|
|
71
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => [])
|
|
72
|
+
const rows = []
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue
|
|
75
|
+
const file = path.join(dir, entry.name)
|
|
76
|
+
const info = await stat(file).catch(() => null)
|
|
77
|
+
if (!info?.isFile()) continue
|
|
78
|
+
rows.push({ name: entry.name, file, bytes: info.size, mtimeMs: info.mtimeMs })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
rows.sort((a, b) => b.mtimeMs - a.mtimeMs)
|
|
82
|
+
let retainedBytes = 0
|
|
83
|
+
let retainedFiles = 0
|
|
84
|
+
const removed = []
|
|
85
|
+
const now = Date.now()
|
|
86
|
+
|
|
87
|
+
for (const row of rows) {
|
|
88
|
+
if (row.name === protect) {
|
|
89
|
+
retainedBytes += row.bytes
|
|
90
|
+
retainedFiles += 1
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
const expired = limits.maxAgeMs > 0 && now - row.mtimeMs > limits.maxAgeMs
|
|
94
|
+
const overFiles = retainedFiles >= limits.maxFiles
|
|
95
|
+
const overBytes = retainedBytes + row.bytes > limits.maxTotalBytes
|
|
96
|
+
if (expired || overFiles || overBytes) {
|
|
97
|
+
await rm(row.file, { force: true }).catch(() => {})
|
|
98
|
+
removed.push(row.name)
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
retainedBytes += row.bytes
|
|
102
|
+
retainedFiles += 1
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
removed,
|
|
107
|
+
removedCount: removed.length,
|
|
108
|
+
retainedFiles,
|
|
109
|
+
retainedBytes,
|
|
110
|
+
limits,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
7
113
|
|
|
8
114
|
function cleanID(value) {
|
|
9
115
|
const text = String(value || "").trim()
|
|
@@ -49,8 +155,12 @@ export function trajectoryFile(root, traceID) {
|
|
|
49
155
|
}
|
|
50
156
|
|
|
51
157
|
export async function appendTrajectoryEvent(root, traceID, type, payload = {}) {
|
|
158
|
+
root = path.resolve(root)
|
|
52
159
|
const file = trajectoryFile(root, traceID)
|
|
53
160
|
await mkdir(path.dirname(file), { recursive: true })
|
|
161
|
+
const limits = traceLimits()
|
|
162
|
+
await compactTraceFile(file, limits.maxFileBytes).catch(() => false)
|
|
163
|
+
await pruneTrajectoryStore(root, { ...limits, protectTraceID: traceID }).catch(() => null)
|
|
54
164
|
const scrubbed = scrubTrajectoryValue(payload)
|
|
55
165
|
const rawPayload = JSON.stringify(scrubbed)
|
|
56
166
|
const bounded = Buffer.byteLength(rawPayload, "utf8") <= MAX_EVENT_BYTES
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const FAST_ROLES = new Set(["executor", "verifier"])
|
|
2
|
+
|
|
3
|
+
export function turboFastPathDecision(taskPolicy = {}, options = {}) {
|
|
4
|
+
const role = String(options.role || "")
|
|
5
|
+
const attempt = Math.max(1, Number(options.attempt || 1))
|
|
6
|
+
const browserRequested = options.browserRequested === true
|
|
7
|
+
const visualRequired = options.visualRequired === true
|
|
8
|
+
const reasons = []
|
|
9
|
+
|
|
10
|
+
if (attempt !== 1) reasons.push("recovery-attempt")
|
|
11
|
+
if (taskPolicy.executionProfile !== "fast") reasons.push("not-fast-profile")
|
|
12
|
+
if (taskPolicy.singleFileBounded !== true) reasons.push("not-single-file-bounded")
|
|
13
|
+
if (String(taskPolicy.risk || "").toLowerCase() !== "low") reasons.push("risk-not-low")
|
|
14
|
+
if (taskPolicy.requireIntegrationVerification === true) reasons.push("integration-required")
|
|
15
|
+
if (!FAST_ROLES.has(role)) reasons.push("role-not-fast-lane")
|
|
16
|
+
if (browserRequested) reasons.push("browser-required")
|
|
17
|
+
if (visualRequired) reasons.push("visual-required")
|
|
18
|
+
|
|
19
|
+
const eligible = reasons.length === 0
|
|
20
|
+
return {
|
|
21
|
+
schemaVersion: 1,
|
|
22
|
+
eligible,
|
|
23
|
+
strategy: eligible ? "single-model-deterministic-first" : "standard",
|
|
24
|
+
deterministicFirst: eligible,
|
|
25
|
+
verifierOnDemand: eligible,
|
|
26
|
+
failClosed: true,
|
|
27
|
+
maxModelLanes: eligible ? 1 : null,
|
|
28
|
+
reasons,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function turboFastTimeoutBudget(options = {}) {
|
|
33
|
+
const number = (value, fallback, min, max) => {
|
|
34
|
+
const parsed = Number(value)
|
|
35
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback
|
|
36
|
+
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
hardTimeoutMs: number(options.hardTimeoutMs, 180_000, 30_000, 10 * 60_000),
|
|
40
|
+
idleTimeoutMs: number(options.idleTimeoutMs, 60_000, 20_000, 5 * 60_000),
|
|
41
|
+
postToolErrorIdleTimeoutMs: number(options.postToolErrorIdleTimeoutMs, 30_000, 5_000, 2 * 60_000),
|
|
42
|
+
verificationTimeoutSec: number(options.verificationTimeoutSec, 90, 30, 300),
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -2,20 +2,11 @@ import { createHash } from "node:crypto"
|
|
|
2
2
|
import { lstatSync, readFileSync, readlinkSync } from "node:fs"
|
|
3
3
|
import { spawnSync } from "node:child_process"
|
|
4
4
|
import path from "node:path"
|
|
5
|
+
import { UES_RUNTIME_DIRS } from "./runtime-artifacts.mjs"
|
|
5
6
|
|
|
6
|
-
const RUNTIME_DIRS = [
|
|
7
|
-
".ues-cache",
|
|
8
|
-
".ues-traces",
|
|
9
|
-
".ues-work",
|
|
10
|
-
".ues-learning",
|
|
11
|
-
".ues-dashboard",
|
|
12
|
-
".ues-sandboxes",
|
|
13
|
-
".ues-memory",
|
|
14
|
-
".ues-evals",
|
|
15
|
-
]
|
|
16
7
|
const RUNTIME_PATHSPECS = [
|
|
17
8
|
".",
|
|
18
|
-
...
|
|
9
|
+
...UES_RUNTIME_DIRS.map((dir) => `:(exclude)${dir}/**`),
|
|
19
10
|
]
|
|
20
11
|
|
|
21
12
|
let nonGitNonce = 0
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
|
|
4
|
+
function runGit(cwd, args) {
|
|
5
|
+
return spawnSync("git", ["-C", cwd, ...args], {
|
|
6
|
+
encoding: "utf8",
|
|
7
|
+
windowsHide: true,
|
|
8
|
+
shell: false,
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function resolveGitWorkspaceRoot(start = process.cwd()) {
|
|
13
|
+
const requested = path.resolve(String(start || process.cwd()))
|
|
14
|
+
const inside = runGit(requested, ["rev-parse", "--is-inside-work-tree"])
|
|
15
|
+
if (inside.status !== 0 || String(inside.stdout || "").trim() !== "true") {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
requested,
|
|
19
|
+
root: null,
|
|
20
|
+
error: String(inside.stderr || inside.stdout || "not a git worktree").trim(),
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const top = runGit(requested, ["rev-parse", "--show-toplevel"])
|
|
25
|
+
if (top.status !== 0) {
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
requested,
|
|
29
|
+
root: null,
|
|
30
|
+
error: String(top.stderr || top.stdout || "could not resolve git root").trim(),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const root = path.resolve(String(top.stdout || "").trim())
|
|
35
|
+
return { ok: true, requested, root, error: null }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function requireGitWorkspaceRoot(start = process.cwd(), purpose = "UES") {
|
|
39
|
+
const resolved = resolveGitWorkspaceRoot(start)
|
|
40
|
+
if (resolved.ok) return resolved.root
|
|
41
|
+
|
|
42
|
+
const detail = resolved.error ? ": " + resolved.error : ""
|
|
43
|
+
throw new Error(
|
|
44
|
+
purpose +
|
|
45
|
+
" requires Pi to be opened inside a Git repository. Refusing to create UES runtime artifacts in " +
|
|
46
|
+
resolved.requested +
|
|
47
|
+
detail,
|
|
48
|
+
)
|
|
49
|
+
}
|