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
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { resolveManagedPiCommand, resolveWindowsCommand } from "./windows-shim.mjs"
|
|
4
|
+
|
|
5
|
+
export function looksLikePiCliEntrypoint(file) {
|
|
6
|
+
const value = String(file || "").replaceAll("\\", "/").toLowerCase()
|
|
7
|
+
if (!value) return false
|
|
8
|
+
|
|
9
|
+
// Managed/npm Pi installs execute the package CLI script. ACP adapters may
|
|
10
|
+
// also be Node scripts, so do not trust an arbitrary existing argv[1].
|
|
11
|
+
if (value.includes("/@earendil-works/pi-coding-agent/")) return true
|
|
12
|
+
|
|
13
|
+
const base = path.posix.basename(value)
|
|
14
|
+
return /^(?:pi)(?:\.[cm]?js)?$/.test(base)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function fromResolvedPi(resolved, args, source) {
|
|
18
|
+
if (!resolved?.executable) return null
|
|
19
|
+
return {
|
|
20
|
+
command: resolved.executable,
|
|
21
|
+
args: [...(resolved.argsPrefix || []), ...args],
|
|
22
|
+
source,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolvePiChildInvocation(args = [], options = {}) {
|
|
27
|
+
const currentScript = options.currentScript ?? process.argv[1]
|
|
28
|
+
const execPath = options.execPath ?? process.execPath
|
|
29
|
+
const platform = options.platform ?? process.platform
|
|
30
|
+
const exists = options.existsSync ?? existsSync
|
|
31
|
+
|
|
32
|
+
if (
|
|
33
|
+
currentScript &&
|
|
34
|
+
!String(currentScript).startsWith("/$bunfs/root/") &&
|
|
35
|
+
exists(currentScript) &&
|
|
36
|
+
looksLikePiCliEntrypoint(currentScript)
|
|
37
|
+
) {
|
|
38
|
+
return {
|
|
39
|
+
command: execPath,
|
|
40
|
+
args: [currentScript, ...args],
|
|
41
|
+
source: "host-pi-cli",
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const execBase = path.basename(String(execPath || "")).toLowerCase()
|
|
46
|
+
if (/^pi(?:\.exe)?$/.test(execBase)) {
|
|
47
|
+
return { command: execPath, args, source: "host-pi-executable" }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (platform === "win32") {
|
|
51
|
+
const managed = options.managedPi === undefined
|
|
52
|
+
? resolveManagedPiCommand()
|
|
53
|
+
: options.managedPi
|
|
54
|
+
const managedInvocation = fromResolvedPi(managed, args, "managed-pi")
|
|
55
|
+
if (managedInvocation) return managedInvocation
|
|
56
|
+
|
|
57
|
+
const pathPi = options.pathPi === undefined
|
|
58
|
+
? resolveWindowsCommand("pi")
|
|
59
|
+
: options.pathPi
|
|
60
|
+
const pathInvocation = fromResolvedPi(pathPi, args, "path-pi")
|
|
61
|
+
if (pathInvocation) return pathInvocation
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { command: "pi", args, source: "path-pi-fallback" }
|
|
65
|
+
}
|
package/lib/pi-rpc-pool.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process"
|
|
2
2
|
import { terminateProcessTree } from "./process-supervisor.mjs"
|
|
3
|
+
import { createAdaptiveDeadline } from "./activity-deadline.mjs"
|
|
3
4
|
|
|
4
5
|
function clamp(value, fallback, min, max) {
|
|
5
6
|
const parsed = Number(value)
|
|
@@ -226,11 +227,37 @@ class RpcWorker {
|
|
|
226
227
|
this.stderr = ""
|
|
227
228
|
|
|
228
229
|
const hardTimeoutMs = clamp(options.hardTimeoutMs, 30 * 60_000, 30_000, 2 * 60 * 60_000)
|
|
230
|
+
const absoluteHardTimeoutMs = clamp(
|
|
231
|
+
options.absoluteHardTimeoutMs,
|
|
232
|
+
hardTimeoutMs,
|
|
233
|
+
hardTimeoutMs,
|
|
234
|
+
2 * 60 * 60_000,
|
|
235
|
+
)
|
|
236
|
+
const activityExtensionMs = clamp(
|
|
237
|
+
options.activityExtensionMs,
|
|
238
|
+
0,
|
|
239
|
+
0,
|
|
240
|
+
Math.max(hardTimeoutMs, absoluteHardTimeoutMs),
|
|
241
|
+
)
|
|
242
|
+
const activityWindowMs = clamp(
|
|
243
|
+
options.activityWindowMs,
|
|
244
|
+
Math.max(5_000, Math.min(activityExtensionMs || hardTimeoutMs, 30_000)),
|
|
245
|
+
1_000,
|
|
246
|
+
Math.max(hardTimeoutMs, absoluteHardTimeoutMs),
|
|
247
|
+
)
|
|
229
248
|
const idleTimeoutMs = clamp(options.idleTimeoutMs, 5 * 60_000, 10_000, 30 * 60_000)
|
|
230
|
-
|
|
249
|
+
const runStartedAt = Date.now()
|
|
250
|
+
let lastActivityAt = runStartedAt
|
|
251
|
+
const adaptiveDeadline = createAdaptiveDeadline({
|
|
252
|
+
hardTimeoutMs,
|
|
253
|
+
absoluteHardTimeoutMs,
|
|
254
|
+
activityExtensionMs,
|
|
255
|
+
activityWindowMs,
|
|
256
|
+
}, runStartedAt)
|
|
231
257
|
this.lastActivityAt = lastActivityAt
|
|
232
258
|
this.active = true
|
|
233
259
|
let finalMessage = null
|
|
260
|
+
let partialAssistantMessage = null
|
|
234
261
|
let toolCalls = 0
|
|
235
262
|
let lastToolErrorAt = 0
|
|
236
263
|
const toolNames = new Set()
|
|
@@ -240,19 +267,37 @@ class RpcWorker {
|
|
|
240
267
|
settledResolve = resolve
|
|
241
268
|
settledReject = reject
|
|
242
269
|
})
|
|
270
|
+
// The active run can be aborted while the prompt transport is still
|
|
271
|
+
// completing, before execution reaches "await settled". Attach a handler
|
|
272
|
+
// immediately so Node never reports a transient unhandled rejection;
|
|
273
|
+
// awaiting the original promise below still preserves the rejection.
|
|
274
|
+
settled.catch(() => {})
|
|
243
275
|
|
|
244
276
|
let aborting = false
|
|
245
277
|
const abortFor = async (reason) => {
|
|
246
278
|
if (aborting) return
|
|
247
279
|
aborting = true
|
|
280
|
+
const error = new Error("UES RPC " + reason)
|
|
281
|
+
error.partialMessage = partialAssistantMessage || finalMessage
|
|
282
|
+
error.stderr = this.stderr
|
|
283
|
+
error.toolCalls = toolCalls
|
|
284
|
+
error.toolNames = [...toolNames]
|
|
285
|
+
error.deadlineExtensions = adaptiveDeadline.extensions
|
|
286
|
+
error.deadlineAt = adaptiveDeadline.deadlineAt
|
|
287
|
+
error.absoluteDeadlineAt = adaptiveDeadline.absoluteAt
|
|
288
|
+
// Settle with the rich timeout error before transport teardown can emit a
|
|
289
|
+
// generic worker-exit error and win the race.
|
|
290
|
+
settledReject(error)
|
|
248
291
|
try { await this.abortTransport() } catch {}
|
|
249
|
-
settledReject(new Error("UES RPC " + reason))
|
|
250
292
|
}
|
|
251
293
|
this.activeAbort = abortFor
|
|
252
294
|
|
|
253
295
|
const unsubscribe = this.onEvent((event) => {
|
|
254
296
|
lastActivityAt = Date.now()
|
|
255
297
|
this.lastActivityAt = lastActivityAt
|
|
298
|
+
if (event?.message?.role === "assistant") {
|
|
299
|
+
partialAssistantMessage = event.message
|
|
300
|
+
}
|
|
256
301
|
if (event.type === "tool_execution_start") {
|
|
257
302
|
toolCalls += 1
|
|
258
303
|
lastToolErrorAt = 0
|
|
@@ -272,22 +317,27 @@ class RpcWorker {
|
|
|
272
317
|
if (event.type === "ues_rpc_worker_error") settledReject(new Error(event.error || "RPC worker failed"))
|
|
273
318
|
})
|
|
274
319
|
|
|
275
|
-
const hardTimer = setTimeout(() => void abortFor("hard-timeout"), hardTimeoutMs)
|
|
276
|
-
hardTimer.unref?.()
|
|
277
320
|
const postToolErrorIdleTimeoutMs = clamp(
|
|
278
321
|
options.postToolErrorIdleTimeoutMs,
|
|
279
322
|
60_000,
|
|
280
323
|
5_000,
|
|
281
324
|
idleTimeoutMs,
|
|
282
325
|
)
|
|
283
|
-
const
|
|
326
|
+
const watchdogTimer = setInterval(() => {
|
|
284
327
|
const now = Date.now()
|
|
285
|
-
const threshold = lastToolErrorAt > 0
|
|
328
|
+
const threshold = lastToolErrorAt > 0
|
|
329
|
+
? Math.min(idleTimeoutMs, postToolErrorIdleTimeoutMs)
|
|
330
|
+
: idleTimeoutMs
|
|
286
331
|
if (now - lastActivityAt >= threshold) {
|
|
287
332
|
void abortFor(lastToolErrorAt > 0 ? "post-tool-error-stall" : "idle-timeout")
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
const deadline = adaptiveDeadline.shouldAbort(now, lastActivityAt)
|
|
336
|
+
if (deadline.abort) {
|
|
337
|
+
void abortFor(deadline.reason || "hard-timeout")
|
|
288
338
|
}
|
|
289
339
|
}, 1000)
|
|
290
|
-
|
|
340
|
+
watchdogTimer.unref?.()
|
|
291
341
|
|
|
292
342
|
const signal = options.signal
|
|
293
343
|
const onAbort = () => void abortFor("aborted")
|
|
@@ -304,6 +354,9 @@ class RpcWorker {
|
|
|
304
354
|
stderr: this.stderr,
|
|
305
355
|
toolCalls,
|
|
306
356
|
toolNames: [...toolNames],
|
|
357
|
+
deadlineExtensions: adaptiveDeadline.extensions,
|
|
358
|
+
deadlineAt: adaptiveDeadline.deadlineAt,
|
|
359
|
+
absoluteDeadlineAt: adaptiveDeadline.absoluteAt,
|
|
307
360
|
}
|
|
308
361
|
} catch (error) {
|
|
309
362
|
if (error && typeof error === "object") error.uesRpcPhase = "runtime"
|
|
@@ -311,8 +364,7 @@ class RpcWorker {
|
|
|
311
364
|
} finally {
|
|
312
365
|
this.active = false
|
|
313
366
|
this.activeAbort = null
|
|
314
|
-
|
|
315
|
-
clearInterval(idleTimer)
|
|
367
|
+
clearInterval(watchdogTimer)
|
|
316
368
|
unsubscribe()
|
|
317
369
|
if (signal) signal.removeEventListener("abort", onAbort)
|
|
318
370
|
}
|
|
@@ -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
|
+
}
|
package/lib/repo-graph.mjs
CHANGED
|
@@ -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([
|
package/lib/repo-inspect.mjs
CHANGED
|
@@ -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
|
+
}
|
package/lib/runtime-events.mjs
CHANGED
|
@@ -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 },
|
package/lib/semantic-index.mjs
CHANGED
|
@@ -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",
|