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.
- package/README.md +34 -16
- 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 +38 -5
- package/lib/capability-fabric.mjs +17 -0
- package/lib/code-intelligence/index.mjs +3 -2
- package/lib/code-intelligence/lsp-provider.mjs +326 -52
- package/lib/compaction-resume-guard.mjs +255 -0
- package/lib/completion-auditor.mjs +25 -5
- package/lib/context-engine-v11.mjs +20 -7
- package/lib/evidence-store.mjs +92 -8
- package/lib/execution-contract.mjs +592 -0
- package/lib/leaf-runtime-optimizer.mjs +76 -0
- package/lib/memory-engine.mjs +189 -16
- package/lib/performance-fabric.mjs +262 -107
- 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/subagent-artifacts.mjs +182 -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/verification-broker.mjs +32 -27
- package/lib/workspace-fingerprint.mjs +45 -19
- package/lib/workspace-hygiene.mjs +481 -0
- 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 +170 -4
- package/pi/extensions/ues.ts +2063 -188
- 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
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
3
|
+
import { readdir, readFile } from "node:fs/promises"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { checkpointWork } from "./task-engine.mjs"
|
|
6
|
+
|
|
7
|
+
const WORK_DIR = ".ues-work"
|
|
8
|
+
const MAX_WORKSPACES = 3
|
|
9
|
+
const MAX_TASKS = 28
|
|
10
|
+
const MAX_RECEIPTS = 20
|
|
11
|
+
|
|
12
|
+
function digest(value) {
|
|
13
|
+
return createHash("sha256").update(String(value || "")).digest("hex").slice(0, 20)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function readJson(file, fallback = null) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(await readFile(file, "utf8"))
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error?.code === "ENOENT") return fallback
|
|
21
|
+
return fallback
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function inside(base, target) {
|
|
26
|
+
const root = path.resolve(base)
|
|
27
|
+
const resolved = path.resolve(target)
|
|
28
|
+
return resolved === root || resolved.startsWith(root + path.sep)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizePhasePath(value) {
|
|
32
|
+
return String(value || "").replaceAll("\\", "/").replace(/^\.\/+/, "")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function activeWorkspaces(root, max = MAX_WORKSPACES) {
|
|
36
|
+
const base = path.join(path.resolve(root), WORK_DIR)
|
|
37
|
+
if (!existsSync(base)) return []
|
|
38
|
+
const entries = await readdir(base, { withFileTypes: true }).catch(() => [])
|
|
39
|
+
const rows = []
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (!entry.isDirectory()) continue
|
|
42
|
+
const dir = path.join(base, entry.name)
|
|
43
|
+
const state = await readJson(path.join(dir, "STATE.json"), null)
|
|
44
|
+
if (!state || String(state.status || "").toLowerCase() === "completed") continue
|
|
45
|
+
rows.push({
|
|
46
|
+
slug: entry.name,
|
|
47
|
+
dir,
|
|
48
|
+
state,
|
|
49
|
+
updatedAt: Date.parse(state.updatedAt || state.createdAt || "") || 0,
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
return rows
|
|
53
|
+
.sort((a, b) => b.updatedAt - a.updatedAt || a.slug.localeCompare(b.slug))
|
|
54
|
+
.slice(0, Math.max(1, Math.min(10, Number(max || MAX_WORKSPACES))))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function taskRows(plan, state) {
|
|
58
|
+
const tasks = Array.isArray(plan?.tasks) ? plan.tasks : []
|
|
59
|
+
const rows = tasks.map((task) => {
|
|
60
|
+
const runtime = state?.tasks?.[task.id] || {}
|
|
61
|
+
return {
|
|
62
|
+
id: task.id,
|
|
63
|
+
title: String(task.title || "").slice(0, 240),
|
|
64
|
+
status: runtime.status || "unknown",
|
|
65
|
+
attempts: Number(runtime.attempts || 0),
|
|
66
|
+
runId: runtime.runId || null,
|
|
67
|
+
dependsOn: Array.isArray(task.dependsOn) ? task.dependsOn.slice(0, 24) : [],
|
|
68
|
+
evidenceStrength: runtime.evidenceStrength || null,
|
|
69
|
+
report: runtime.report || null,
|
|
70
|
+
lastError: runtime.lastError ? String(runtime.lastError).slice(0, 800) : null,
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
rows.sort((a, b) => {
|
|
74
|
+
const rank = (row) => row.status === "running" ? 0 : row.status === "completed" ? 2 : 1
|
|
75
|
+
return rank(a) - rank(b) || String(a.id).localeCompare(String(b.id))
|
|
76
|
+
})
|
|
77
|
+
return rows.slice(0, MAX_TASKS)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function receiptRows(evidence) {
|
|
81
|
+
return (Array.isArray(evidence?.receipts) ? evidence.receipts : [])
|
|
82
|
+
.slice(-MAX_RECEIPTS)
|
|
83
|
+
.map((item) => ({
|
|
84
|
+
id: item.id || null,
|
|
85
|
+
kind: item.kind || "verification-receipt",
|
|
86
|
+
task: item.task || null,
|
|
87
|
+
runId: item.runId || null,
|
|
88
|
+
passed: item.passed === true,
|
|
89
|
+
verdict: item.verdict || (item.passed === true ? "PASS" : null),
|
|
90
|
+
command: item.command || null,
|
|
91
|
+
workspaceAfter: item.workspaceAfter || null,
|
|
92
|
+
recordedAt: item.recordedAt || item.finishedAt || null,
|
|
93
|
+
}))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function gateRows(evidence) {
|
|
97
|
+
return (Array.isArray(evidence?.gateReceipts) ? evidence.gateReceipts : [])
|
|
98
|
+
.slice(-12)
|
|
99
|
+
.map((item) => ({
|
|
100
|
+
id: item.id || null,
|
|
101
|
+
kind: item.kind || null,
|
|
102
|
+
verdict: item.verdict || null,
|
|
103
|
+
verifier: item.verifier || null,
|
|
104
|
+
planHash: item.planHash || null,
|
|
105
|
+
workspaceFingerprint: item.workspaceFingerprint || null,
|
|
106
|
+
recordedAt: item.recordedAt || null,
|
|
107
|
+
}))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function phaseRows(dir, manifest) {
|
|
111
|
+
const artifacts = Array.isArray(manifest?.artifacts) ? manifest.artifacts : []
|
|
112
|
+
const rows = []
|
|
113
|
+
for (const relative of artifacts.slice(0, 32)) {
|
|
114
|
+
const normalized = normalizePhasePath(relative)
|
|
115
|
+
const target = path.resolve(dir, normalized)
|
|
116
|
+
if (!normalized || !inside(dir, target)) continue
|
|
117
|
+
const phase = await readJson(target, null)
|
|
118
|
+
if (!phase) continue
|
|
119
|
+
rows.push({
|
|
120
|
+
artifact: normalized,
|
|
121
|
+
phase: phase.phase ?? null,
|
|
122
|
+
title: String(phase.title || "").slice(0, 240),
|
|
123
|
+
status: phase.status || "UNKNOWN",
|
|
124
|
+
sourceBodyHash: phase.sourceBodyHash || null,
|
|
125
|
+
evidenceCount: Array.isArray(phase.evidence) ? phase.evidence.length : 0,
|
|
126
|
+
finalVerdicts: phase.finalVerdicts || null,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
return rows
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function stateSummary(state) {
|
|
133
|
+
return {
|
|
134
|
+
status: state?.status || null,
|
|
135
|
+
goal: state?.goal ? String(state.goal).slice(0, 800) : null,
|
|
136
|
+
updatedAt: state?.updatedAt || null,
|
|
137
|
+
planHash: state?.planHash || null,
|
|
138
|
+
planApproved: state?.planApproved === true,
|
|
139
|
+
nextAction: state?.nextAction || null,
|
|
140
|
+
checkpoint: state?.checkpoint ? {
|
|
141
|
+
id: state.checkpoint.id || null,
|
|
142
|
+
reason: state.checkpoint.reason || null,
|
|
143
|
+
createdAt: state.checkpoint.createdAt || null,
|
|
144
|
+
currentTaskId: state.checkpoint.currentTaskId || null,
|
|
145
|
+
runId: state.checkpoint.runId || null,
|
|
146
|
+
planHash: state.checkpoint.planHash || null,
|
|
147
|
+
workspaceFingerprint: state.checkpoint.workspaceFingerprint || null,
|
|
148
|
+
nextAction: state.checkpoint.nextAction || null,
|
|
149
|
+
evidencePointers: Array.isArray(state.checkpoint.evidencePointers)
|
|
150
|
+
? state.checkpoint.evidencePointers.slice(-20)
|
|
151
|
+
: [],
|
|
152
|
+
} : null,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function checkpointDurableWorkBeforeCompaction(root, options = {}) {
|
|
157
|
+
const reason = String(options.reason || "unknown")
|
|
158
|
+
const rows = await activeWorkspaces(root, options.maxWorkspaces || MAX_WORKSPACES)
|
|
159
|
+
const results = []
|
|
160
|
+
for (const row of rows) {
|
|
161
|
+
try {
|
|
162
|
+
const checkpoint = await checkpointWork(root, row.slug, {
|
|
163
|
+
reason: `pi-compaction:${reason}`,
|
|
164
|
+
})
|
|
165
|
+
results.push({ slug: row.slug, checkpoint, ok: true })
|
|
166
|
+
} catch (error) {
|
|
167
|
+
results.push({
|
|
168
|
+
slug: row.slug,
|
|
169
|
+
checkpoint: null,
|
|
170
|
+
ok: false,
|
|
171
|
+
error: error instanceof Error ? error.message : String(error),
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return results
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function buildCompactionResumeGuard(root, options = {}) {
|
|
179
|
+
const rows = await activeWorkspaces(root, options.maxWorkspaces || MAX_WORKSPACES)
|
|
180
|
+
const workspaces = []
|
|
181
|
+
for (const row of rows) {
|
|
182
|
+
const [plan, evidence, contract, manifest] = await Promise.all([
|
|
183
|
+
readJson(path.join(row.dir, "PLAN.json"), null),
|
|
184
|
+
readJson(path.join(row.dir, "EVIDENCE.json"), {}),
|
|
185
|
+
readJson(path.join(row.dir, "EXECUTION_CONTRACT.json"), null),
|
|
186
|
+
readJson(path.join(row.dir, "phases", "MANIFEST.json"), null),
|
|
187
|
+
])
|
|
188
|
+
const phases = await phaseRows(row.dir, manifest)
|
|
189
|
+
const sources = {
|
|
190
|
+
state: path.relative(root, path.join(row.dir, "STATE.json")).replaceAll("\\", "/"),
|
|
191
|
+
plan: path.relative(root, path.join(row.dir, "PLAN.json")).replaceAll("\\", "/"),
|
|
192
|
+
evidence: path.relative(root, path.join(row.dir, "EVIDENCE.json")).replaceAll("\\", "/"),
|
|
193
|
+
executionContract: contract
|
|
194
|
+
? path.relative(root, path.join(row.dir, "EXECUTION_CONTRACT.json")).replaceAll("\\", "/")
|
|
195
|
+
: null,
|
|
196
|
+
phaseManifest: manifest
|
|
197
|
+
? path.relative(root, path.join(row.dir, "phases", "MANIFEST.json")).replaceAll("\\", "/")
|
|
198
|
+
: null,
|
|
199
|
+
}
|
|
200
|
+
workspaces.push({
|
|
201
|
+
slug: row.slug,
|
|
202
|
+
sources,
|
|
203
|
+
sourceHashes: {
|
|
204
|
+
state: digest(JSON.stringify(row.state)),
|
|
205
|
+
plan: digest(JSON.stringify(plan || {})),
|
|
206
|
+
evidence: digest(JSON.stringify(evidence || {})),
|
|
207
|
+
executionContract: contract ? digest(JSON.stringify(contract)) : null,
|
|
208
|
+
phaseManifest: manifest ? digest(JSON.stringify(manifest)) : null,
|
|
209
|
+
},
|
|
210
|
+
state: stateSummary(row.state),
|
|
211
|
+
tasks: taskRows(plan, row.state),
|
|
212
|
+
receipts: receiptRows(evidence),
|
|
213
|
+
gateReceipts: gateRows(evidence),
|
|
214
|
+
executionContract: contract ? {
|
|
215
|
+
schemaVersion: contract.schemaVersion || null,
|
|
216
|
+
taskHash: contract.taskHash || null,
|
|
217
|
+
gates: contract.gates || null,
|
|
218
|
+
phaseCount: Array.isArray(contract.phases) ? contract.phases.length : 0,
|
|
219
|
+
approvedInheritedDirtyPaths: Array.isArray(contract.approvedInheritedDirtyPaths)
|
|
220
|
+
? contract.approvedInheritedDirtyPaths.slice(0, 40)
|
|
221
|
+
: [],
|
|
222
|
+
} : null,
|
|
223
|
+
phases,
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
schemaVersion: 1,
|
|
229
|
+
kind: "ues-durable-compaction-resume-guard",
|
|
230
|
+
generatedAt: new Date().toISOString(),
|
|
231
|
+
compactionReason: options.reason || null,
|
|
232
|
+
source: "deterministic-filesystem-artifacts",
|
|
233
|
+
modelSummaryTrustedForDurableState: false,
|
|
234
|
+
workspaceCount: workspaces.length,
|
|
235
|
+
workspaces,
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function renderCompactionResumeGuard(packet, options = {}) {
|
|
240
|
+
if (!packet?.workspaces?.length) return ""
|
|
241
|
+
const maxChars = Math.max(8000, Math.min(48000, Number(options.maxChars || 28000)))
|
|
242
|
+
const preface = [
|
|
243
|
+
"## UES Durable Compaction Resume Guard",
|
|
244
|
+
"Authoritative durable state was rebuilt from filesystem artifacts after Pi compaction.",
|
|
245
|
+
"The model-generated compaction summary is advisory only for durable execution state.",
|
|
246
|
+
"If the summary conflicts with STATE.json, PLAN.json, EXECUTION_CONTRACT.json, phase artifacts, or evidence receipts below, the deterministic artifacts win.",
|
|
247
|
+
"Do not mark a task/phase complete unless the rebuilt state and fresh receipts support it.",
|
|
248
|
+
"Follow checkpoint.nextAction/state.nextAction and re-read the referenced artifact when more detail is required.",
|
|
249
|
+
"",
|
|
250
|
+
].join("\n")
|
|
251
|
+
const encoded = JSON.stringify(packet, null, 2)
|
|
252
|
+
if (preface.length + encoded.length <= maxChars) return preface + encoded
|
|
253
|
+
return preface + encoded.slice(0, Math.max(0, maxChars - preface.length - 120)) +
|
|
254
|
+
"\n...[resume packet bounded; re-read referenced .ues-work artifacts for exact detail]"
|
|
255
|
+
}
|
|
@@ -11,13 +11,30 @@ function passRun(run) {
|
|
|
11
11
|
return Boolean(run && Number(run.exitCode) === 0 && run.verdict === "PASS")
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function
|
|
15
|
-
|
|
14
|
+
function normalizeSection(value) {
|
|
15
|
+
return text(value)
|
|
16
16
|
.toLowerCase()
|
|
17
17
|
.replace(/[`*_#]/g, "")
|
|
18
18
|
.replace(/[.!:;]+$/g, "")
|
|
19
|
+
.replace(/\s+/g, " ")
|
|
19
20
|
.trim()
|
|
20
|
-
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function benignEmptySection(value) {
|
|
24
|
+
const normalized = normalizeSection(value)
|
|
25
|
+
return !normalized || /^(?:none(?: observed| found| remaining)?|n\/a|na|nil|0|no failures?(?: observed| found| remaining)?|no unresolved gaps?(?: observed| found| remaining)?|no gaps?(?: observed| found| remaining)?|not applicable|không có|không có gì|không có lỗi|không phát hiện lỗi|không còn khoảng trống|không có khoảng trống)$/.test(normalized)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function benignFailureSection(value) {
|
|
29
|
+
const normalized = normalizeSection(value)
|
|
30
|
+
return benignEmptySection(normalized) ||
|
|
31
|
+
/^(?:không có|không phát hiện) .{0,120}(?:lỗi|thất bại)(?: nào)?$/.test(normalized)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function outOfScopeGapOnly(value) {
|
|
35
|
+
const normalized = normalizeSection(value)
|
|
36
|
+
if (benignEmptySection(normalized)) return true
|
|
37
|
+
return /(?:not requested|outside (?:the )?scope|out of scope|not required|không có yêu cầu|không được yêu cầu|ngoài phạm vi|không thuộc phạm vi)/.test(normalized)
|
|
21
38
|
}
|
|
22
39
|
|
|
23
40
|
function receiptPassed(row = {}) {
|
|
@@ -43,8 +60,11 @@ export function auditCompletion(input = {}) {
|
|
|
43
60
|
for (const key of ["checks-run", "acceptance-criteria-proven", "completion-evidence"]) {
|
|
44
61
|
if (!text(sections[key])) failures.push(`missing-report-section:${key}`)
|
|
45
62
|
}
|
|
46
|
-
if (Object.hasOwn(sections, "failures") && !
|
|
47
|
-
if (Object.hasOwn(sections, "unresolved-gaps") && !benignEmptySection(sections["unresolved-gaps"]))
|
|
63
|
+
if (Object.hasOwn(sections, "failures") && !benignFailureSection(sections.failures)) failures.push("verification-reports-failures")
|
|
64
|
+
if (Object.hasOwn(sections, "unresolved-gaps") && !benignEmptySection(sections["unresolved-gaps"])) {
|
|
65
|
+
if (outOfScopeGapOnly(sections["unresolved-gaps"])) warnings.push("verification-reports-out-of-scope-gap")
|
|
66
|
+
else failures.push("verification-reports-unresolved-gaps")
|
|
67
|
+
}
|
|
48
68
|
if (Object.hasOwn(sections, "checks-not-run") && !benignEmptySection(sections["checks-not-run"])) warnings.push("verification-reports-checks-not-run")
|
|
49
69
|
|
|
50
70
|
const snapshot = input.workspaceSnapshot || null
|
|
@@ -101,6 +101,16 @@ export async function buildAdaptiveTaskContext(root, task, options = {}) {
|
|
|
101
101
|
const policy = options.policy || {}
|
|
102
102
|
const capabilities = options.capabilities || inferTaskCapabilities(taskText(task), options.facts || {})
|
|
103
103
|
const evidenceBudget = options.evidenceBudget || planEvidenceBudget(policy, task, capabilities.required)
|
|
104
|
+
const contextBuildStartedAt = Date.now()
|
|
105
|
+
const memoryPromise = retrieveMemories(root, taskText(task), {
|
|
106
|
+
files: taskFilesForMemory(task),
|
|
107
|
+
limit: options.memoryLimit ?? 6,
|
|
108
|
+
taskClass: options.taskClass || task.type || policy.executionProfile || policy.profile?.name || null,
|
|
109
|
+
touch: options.touchMemories !== false,
|
|
110
|
+
}).catch(() => ({ schemaVersion: 1, query: taskText(task), eligible: 0, results: [] }))
|
|
111
|
+
const fabricPromise = capabilityFabricStatus(root).catch(() => null)
|
|
112
|
+
|
|
113
|
+
const manifestStartedAt = Date.now()
|
|
104
114
|
const manifest = await buildContextManifest(root, task, {
|
|
105
115
|
budget: evidenceBudget.total,
|
|
106
116
|
evidenceBudget,
|
|
@@ -110,21 +120,18 @@ export async function buildAdaptiveTaskContext(root, task, options = {}) {
|
|
|
110
120
|
changedFiles: options.changedFiles,
|
|
111
121
|
workspaceFingerprint: options.workspaceFingerprint,
|
|
112
122
|
})
|
|
123
|
+
const manifestMs = Date.now() - manifestStartedAt
|
|
113
124
|
|
|
125
|
+
const externalizeStartedAt = Date.now()
|
|
114
126
|
const externalized = await externalizeContextExcerpts(root, manifest, {
|
|
115
127
|
task,
|
|
116
128
|
threshold: options.externalizeThreshold,
|
|
117
129
|
inlineChars: options.inlineChars,
|
|
118
130
|
})
|
|
131
|
+
const externalizeMs = Date.now() - externalizeStartedAt
|
|
119
132
|
|
|
120
|
-
const memoryRetrieval = await
|
|
121
|
-
files: taskFilesForMemory(task),
|
|
122
|
-
limit: options.memoryLimit ?? 6,
|
|
123
|
-
taskClass: options.taskClass || task.type || policy.executionProfile || policy.profile?.name || null,
|
|
124
|
-
touch: options.touchMemories !== false,
|
|
125
|
-
}).catch(() => ({ schemaVersion: 1, query: taskText(task), eligible: 0, results: [] }))
|
|
133
|
+
const [memoryRetrieval, fabric] = await Promise.all([memoryPromise, fabricPromise])
|
|
126
134
|
const memories = memoryRetrieval.results || []
|
|
127
|
-
const fabric = await capabilityFabricStatus(root).catch(() => null)
|
|
128
135
|
const providerHints = (fabric?.rows || [])
|
|
129
136
|
.filter((row) => row.selected)
|
|
130
137
|
.slice(0, 12)
|
|
@@ -202,6 +209,12 @@ export async function buildAdaptiveTaskContext(root, task, options = {}) {
|
|
|
202
209
|
entries: externalized.externalized,
|
|
203
210
|
},
|
|
204
211
|
promptEnvelope,
|
|
212
|
+
performance: {
|
|
213
|
+
parallelContextPreparation: true,
|
|
214
|
+
manifestMs,
|
|
215
|
+
externalizeMs,
|
|
216
|
+
totalMs: Date.now() - contextBuildStartedAt,
|
|
217
|
+
},
|
|
205
218
|
promptCache: {
|
|
206
219
|
stablePrefixHash: promptEnvelope.stablePrefixHash,
|
|
207
220
|
dynamicHash: promptEnvelope.dynamicHash,
|
package/lib/evidence-store.mjs
CHANGED
|
@@ -5,6 +5,41 @@ import path from "node:path"
|
|
|
5
5
|
|
|
6
6
|
const STORE_VERSION = 1
|
|
7
7
|
const REF_PREFIX = "evidence:sha256:"
|
|
8
|
+
const DEFAULT_MAX_STORE_BYTES = 192 * 1024 * 1024
|
|
9
|
+
const DEFAULT_MAX_STORE_ENTRIES = 1200
|
|
10
|
+
const DEFAULT_MAX_STORE_AGE_DAYS = 14
|
|
11
|
+
const AUTO_GC_EVERY_WRITES = 8
|
|
12
|
+
const AUTO_GC_LARGE_ITEM_BYTES = 16 * 1024 * 1024
|
|
13
|
+
const PUT_COUNTS = new Map()
|
|
14
|
+
|
|
15
|
+
function boundedNumber(value, fallback, min, max) {
|
|
16
|
+
const parsed = Number(value)
|
|
17
|
+
if (!Number.isFinite(parsed)) return fallback
|
|
18
|
+
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function defaultStoreLimits() {
|
|
22
|
+
return {
|
|
23
|
+
maxBytes: boundedNumber(
|
|
24
|
+
process.env.UES_EVIDENCE_MAX_BYTES,
|
|
25
|
+
DEFAULT_MAX_STORE_BYTES,
|
|
26
|
+
16 * 1024 * 1024,
|
|
27
|
+
2 * 1024 * 1024 * 1024,
|
|
28
|
+
),
|
|
29
|
+
maxEntries: boundedNumber(
|
|
30
|
+
process.env.UES_EVIDENCE_MAX_ENTRIES,
|
|
31
|
+
DEFAULT_MAX_STORE_ENTRIES,
|
|
32
|
+
100,
|
|
33
|
+
20_000,
|
|
34
|
+
),
|
|
35
|
+
maxAgeDays: boundedNumber(
|
|
36
|
+
process.env.UES_EVIDENCE_MAX_AGE_DAYS,
|
|
37
|
+
DEFAULT_MAX_STORE_AGE_DAYS,
|
|
38
|
+
1,
|
|
39
|
+
365,
|
|
40
|
+
),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
8
43
|
|
|
9
44
|
function stableValue(value) {
|
|
10
45
|
if (Array.isArray(value)) return value.map(stableValue)
|
|
@@ -21,6 +56,24 @@ function toBytes(value, options = {}) {
|
|
|
21
56
|
return { bytes: Buffer.from(text, "utf8"), encoding: "utf8", mediaType: options.mediaType || "application/json" }
|
|
22
57
|
}
|
|
23
58
|
|
|
59
|
+
async function protectedEvidenceHashes(root) {
|
|
60
|
+
const file = path.join(path.resolve(root), ".ues-memory", "MEMORY.json")
|
|
61
|
+
const parsed = await readFile(file, "utf8")
|
|
62
|
+
.then((raw) => JSON.parse(raw))
|
|
63
|
+
.catch(() => null)
|
|
64
|
+
const protectedHashes = new Set()
|
|
65
|
+
const now = Date.now()
|
|
66
|
+
for (const memory of parsed?.memories || []) {
|
|
67
|
+
if (memory?.status !== "verified" || memory?.supersededBy) continue
|
|
68
|
+
const expiresAt = Date.parse(String(memory?.expiresAt || ""))
|
|
69
|
+
if (expiresAt && expiresAt <= now) continue
|
|
70
|
+
for (const ref of memory?.evidenceRefs || []) {
|
|
71
|
+
try { protectedHashes.add(normalizeHash(ref)) } catch {}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return protectedHashes
|
|
75
|
+
}
|
|
76
|
+
|
|
24
77
|
function normalizeHash(ref) {
|
|
25
78
|
const value = String(ref || "").trim()
|
|
26
79
|
const hash = value.startsWith(REF_PREFIX) ? value.slice(REF_PREFIX.length) : value.replace(/^sha256:/, "")
|
|
@@ -79,6 +132,13 @@ export async function putEvidence(root, value, options = {}) {
|
|
|
79
132
|
preview,
|
|
80
133
|
}
|
|
81
134
|
await writeFile(target.meta, JSON.stringify(metadata, null, 2) + "\n", "utf8")
|
|
135
|
+
|
|
136
|
+
const writes = (PUT_COUNTS.get(root) || 0) + 1
|
|
137
|
+
PUT_COUNTS.set(root, writes)
|
|
138
|
+
if (writes % AUTO_GC_EVERY_WRITES === 0 || encoded.bytes.length >= AUTO_GC_LARGE_ITEM_BYTES) {
|
|
139
|
+
await gcEvidenceStore(root, defaultStoreLimits()).catch(() => null)
|
|
140
|
+
}
|
|
141
|
+
|
|
82
142
|
return metadata
|
|
83
143
|
}
|
|
84
144
|
|
|
@@ -216,25 +276,44 @@ export async function evidenceStoreStatus(root = process.cwd()) {
|
|
|
216
276
|
}
|
|
217
277
|
|
|
218
278
|
export async function gcEvidenceStore(root = process.cwd(), options = {}) {
|
|
279
|
+
root = path.resolve(root)
|
|
219
280
|
const files = await metadataFiles(root)
|
|
220
|
-
const
|
|
221
|
-
const
|
|
281
|
+
const defaults = defaultStoreLimits()
|
|
282
|
+
const maxEntries = Math.max(10, Number(options.maxEntries ?? defaults.maxEntries))
|
|
283
|
+
const maxBytes = Math.max(1024 * 1024, Number(options.maxBytes ?? defaults.maxBytes))
|
|
284
|
+
const maxAgeMs = Math.max(0, Number(options.maxAgeDays ?? defaults.maxAgeDays)) * 86_400_000
|
|
222
285
|
const now = Date.now()
|
|
286
|
+
const protectedHashes = options.protectMemoryRefs === false
|
|
287
|
+
? new Set()
|
|
288
|
+
: await protectedEvidenceHashes(root)
|
|
223
289
|
const rows = []
|
|
224
290
|
for (const metaFile of files) {
|
|
225
291
|
const meta = JSON.parse(await readFile(metaFile, "utf8").catch(() => "{}"))
|
|
226
292
|
const time = Date.parse(meta.lastSeenAt || meta.createdAt || 0) || 0
|
|
227
|
-
rows.push({ metaFile, meta, time })
|
|
293
|
+
rows.push({ metaFile, meta, time, bytes: Math.max(0, Number(meta.bytes || 0)) })
|
|
228
294
|
}
|
|
229
295
|
rows.sort((a, b) => b.time - a.time)
|
|
230
296
|
|
|
231
297
|
const removed = []
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
298
|
+
let retainedEntries = 0
|
|
299
|
+
let retainedBytes = 0
|
|
300
|
+
let protectedEntries = 0
|
|
301
|
+
let protectedBytes = 0
|
|
302
|
+
for (const row of rows) {
|
|
303
|
+
const hash = normalizeHash(row.meta.sha256 || path.basename(row.metaFile, ".json"))
|
|
304
|
+
const protectedByMemory = protectedHashes.has(hash)
|
|
305
|
+
const tooMany = retainedEntries >= maxEntries
|
|
235
306
|
const tooOld = maxAgeMs > 0 && row.time > 0 && now - row.time > maxAgeMs
|
|
236
|
-
|
|
237
|
-
|
|
307
|
+
const tooLarge = retainedBytes + row.bytes > maxBytes
|
|
308
|
+
if (protectedByMemory || (!tooMany && !tooOld && !tooLarge)) {
|
|
309
|
+
retainedEntries += 1
|
|
310
|
+
retainedBytes += row.bytes
|
|
311
|
+
if (protectedByMemory) {
|
|
312
|
+
protectedEntries += 1
|
|
313
|
+
protectedBytes += row.bytes
|
|
314
|
+
}
|
|
315
|
+
continue
|
|
316
|
+
}
|
|
238
317
|
const target = evidencePaths(root, hash)
|
|
239
318
|
await rm(target.meta, { force: true })
|
|
240
319
|
await rm(target.data, { force: true })
|
|
@@ -244,6 +323,11 @@ export async function gcEvidenceStore(root = process.cwd(), options = {}) {
|
|
|
244
323
|
return {
|
|
245
324
|
removed,
|
|
246
325
|
removedCount: removed.length,
|
|
326
|
+
retainedEntries,
|
|
327
|
+
retainedBytes,
|
|
328
|
+
protectedEntries,
|
|
329
|
+
protectedBytes,
|
|
330
|
+
limits: { maxEntries, maxBytes, maxAgeMs },
|
|
247
331
|
status: await evidenceStoreStatus(root),
|
|
248
332
|
}
|
|
249
333
|
}
|