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
|
@@ -9,23 +9,41 @@ function clampInt(value, fallback, min, max) {
|
|
|
9
9
|
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
function
|
|
12
|
+
function hintFor(options = {}) {
|
|
13
|
+
return String(options.command || options.source || options.kind || "")
|
|
14
|
+
.trim()
|
|
15
|
+
.replace(/\s+/g, " ")
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function uniqRows(rows, limit = 100) {
|
|
13
20
|
const seen = new Set()
|
|
21
|
+
const result = []
|
|
22
|
+
for (const value of rows || []) {
|
|
23
|
+
const row = String(value || "").trimEnd()
|
|
24
|
+
if (!row || seen.has(row)) continue
|
|
25
|
+
seen.add(row)
|
|
26
|
+
result.push(row)
|
|
27
|
+
if (result.length >= limit) break
|
|
28
|
+
}
|
|
29
|
+
return result
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function matchingLines(source, patterns, limit = 100) {
|
|
14
33
|
const rows = []
|
|
15
|
-
for (const raw of String(
|
|
16
|
-
|
|
17
|
-
if (!line || !SIGNAL_LINE.test(line) || seen.has(line)) continue
|
|
18
|
-
seen.add(line)
|
|
19
|
-
rows.push(line)
|
|
34
|
+
for (const raw of String(source || "").split(/\r?\n/)) {
|
|
35
|
+
if (patterns.some((pattern) => pattern.test(raw))) rows.push(raw)
|
|
20
36
|
if (rows.length >= limit) break
|
|
21
37
|
}
|
|
22
|
-
return rows
|
|
38
|
+
return uniqRows(rows, limit)
|
|
23
39
|
}
|
|
24
40
|
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
function uniqueSignalLines(text, limit = 48) {
|
|
42
|
+
return matchingLines(text, [SIGNAL_LINE], limit)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function npmTestRows(source, limit = 100) {
|
|
46
|
+
return matchingLines(source, [
|
|
29
47
|
/^FAIL\b/i,
|
|
30
48
|
/^PASS\b/i,
|
|
31
49
|
/^Test Suites:/i,
|
|
@@ -38,80 +56,124 @@ function testSummaryLines(source, limit = 80) {
|
|
|
38
56
|
/^\s*●\s+/,
|
|
39
57
|
/^\s*at\s+.+:\d+:\d+/,
|
|
40
58
|
/(?:AssertionError|Expected|Received|Traceback|panic:|FAILED\s)/i,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
seen.add(line)
|
|
46
|
-
rows.push(line)
|
|
47
|
-
if (rows.length >= limit) break
|
|
48
|
-
}
|
|
49
|
-
return rows
|
|
59
|
+
/^not ok\b/i,
|
|
60
|
+
/^ok\s+\d+\b/i,
|
|
61
|
+
/^#\s+(?:tests|suites|pass|fail|cancelled|skipped|todo|duration_ms)\b/i,
|
|
62
|
+
], limit)
|
|
50
63
|
}
|
|
51
64
|
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
function pytestRows(source, limit = 100) {
|
|
66
|
+
return matchingLines(source, [
|
|
67
|
+
/^(?:FAILED|ERROR)\b/,
|
|
68
|
+
/^=+\s+.*(?:passed|failed|error|skipped|xfailed|xpassed).*\s+=+$/i,
|
|
69
|
+
/^E\s{2,}/,
|
|
70
|
+
/^_+\s+.+\s+_+$/,
|
|
71
|
+
/^Traceback \(most recent call last\):/,
|
|
72
|
+
/^\s*File ".+", line \d+/,
|
|
73
|
+
/(?:AssertionError|ImportError|ModuleNotFoundError|pytest\.)/,
|
|
74
|
+
/collected\s+\d+\s+items?/i,
|
|
75
|
+
], limit)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function gitDiffRows(source, limit = 140) {
|
|
79
|
+
return matchingLines(source, [
|
|
80
|
+
/^diff --git /,
|
|
81
|
+
/^index [0-9a-f.]+/i,
|
|
82
|
+
/^@@ /,
|
|
83
|
+
/^(?:---|\+\+\+) /,
|
|
84
|
+
/^fatal:/i,
|
|
85
|
+
/^error:/i,
|
|
86
|
+
/^CONFLICT\b/i,
|
|
87
|
+
/^rename (?:from|to) /,
|
|
88
|
+
/^new file mode /,
|
|
89
|
+
/^deleted file mode /,
|
|
90
|
+
/^\s*\d+ files? changed/i,
|
|
91
|
+
], limit)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function rgRows(source, limit = 100) {
|
|
95
|
+
const lines = String(source || "").split(/\r?\n/).filter(Boolean)
|
|
96
|
+
const matched = []
|
|
97
|
+
for (const line of lines) {
|
|
55
98
|
if (
|
|
56
|
-
/^
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
/^CONFLICT\b/i.test(raw)
|
|
62
|
-
) {
|
|
63
|
-
rows.push(raw)
|
|
64
|
-
if (rows.length >= limit) break
|
|
65
|
-
}
|
|
99
|
+
/^(?:[^:\n]+:)?\d+(?::\d+)?:/.test(line) ||
|
|
100
|
+
/^\.?[^:\n]+:\s/.test(line) ||
|
|
101
|
+
/(?:error|permission denied|binary file|no such file)/i.test(line)
|
|
102
|
+
) matched.push(line)
|
|
103
|
+
if (matched.length >= limit) break
|
|
66
104
|
}
|
|
67
|
-
|
|
105
|
+
const rows = matched.length ? matched : lines.slice(0, limit)
|
|
106
|
+
return uniqRows(rows, limit)
|
|
68
107
|
}
|
|
69
108
|
|
|
109
|
+
function treeRows(source, limit = 120) {
|
|
110
|
+
const lines = String(source || "").split(/\r?\n/).filter(Boolean)
|
|
111
|
+
if (lines.length <= limit) return uniqRows(lines, limit)
|
|
112
|
+
const head = lines.slice(0, Math.max(1, limit - 12))
|
|
113
|
+
const tail = lines.slice(-12)
|
|
114
|
+
return uniqRows([...head, `...[tree omitted ${Math.max(0, lines.length - head.length - tail.length)} rows]...`, ...tail], limit)
|
|
115
|
+
}
|
|
70
116
|
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
117
|
+
function tscRows(source, limit = 120) {
|
|
118
|
+
return matchingLines(source, [
|
|
119
|
+
/\berror\s+TS\d+\b/i,
|
|
120
|
+
/^.+\(\d+,\d+\):\s+error\s+TS\d+/i,
|
|
121
|
+
/^.+:\d+:\d+\s+-\s+error\s+TS\d+/i,
|
|
122
|
+
/^Found\s+\d+\s+errors?/i,
|
|
123
|
+
/\b(?:ELIFECYCLE|ERR_|exit code)\b/i,
|
|
124
|
+
], limit)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function eslintRows(source, limit = 120) {
|
|
128
|
+
return matchingLines(source, [
|
|
129
|
+
/^\/?[^\n]+\.[cm]?[jt]sx?$/i,
|
|
130
|
+
/^\s*\d+:\d+\s+(?:error|warning)\s+/i,
|
|
76
131
|
/\b\d+\s+problems?\b/i,
|
|
77
132
|
/\b\d+\s+errors?\b/i,
|
|
78
133
|
/\b\d+\s+warnings?\b/i,
|
|
79
|
-
/\b(?:eslint|ruff|flake8|pylint|clippy)\b/i,
|
|
80
|
-
/:\d+:\d+\b/,
|
|
81
134
|
/^✖\s+/,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const line = raw.trimEnd()
|
|
85
|
-
if (!line || seen.has(line) || !patterns.some((pattern) => pattern.test(line))) continue
|
|
86
|
-
seen.add(line)
|
|
87
|
-
rows.push(line)
|
|
88
|
-
if (rows.length >= limit) break
|
|
89
|
-
}
|
|
90
|
-
return rows
|
|
135
|
+
/\beslint\b.*\b(?:error|warning|failed)\b/i,
|
|
136
|
+
], limit)
|
|
91
137
|
}
|
|
92
138
|
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
/\
|
|
139
|
+
function dockerRows(source, limit = 120) {
|
|
140
|
+
return matchingLines(source, [
|
|
141
|
+
/^#\d+\s+/,
|
|
142
|
+
/^\s*=>\s+/,
|
|
143
|
+
/\b(?:error|failed|failure|fatal|warning|unhealthy|exited)\b/i,
|
|
144
|
+
/^NAME\s+IMAGE\s+COMMAND/i,
|
|
145
|
+
/^NAME\s+STATUS/i,
|
|
146
|
+
/^\[?\+\]?\s+(?:Running|Started|Stopped|Created|Built|Recreated)/i,
|
|
147
|
+
/(?:container|service|network|volume).*(?:created|started|stopped|failed|error)/i,
|
|
148
|
+
], limit)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function prismaRows(source, limit = 120) {
|
|
152
|
+
return matchingLines(source, [
|
|
153
|
+
/Prisma schema loaded from/i,
|
|
154
|
+
/Datasource\s+".+"/i,
|
|
155
|
+
/Applying migration/i,
|
|
156
|
+
/migrations? found/i,
|
|
157
|
+
/Generated Prisma Client/i,
|
|
158
|
+
/Your database is now in sync/i,
|
|
159
|
+
/The following migration/i,
|
|
160
|
+
/\bP\d{4}\b/,
|
|
161
|
+
/\b(?:error|failed|warning)\b/i,
|
|
162
|
+
/schema\.prisma:\d+/i,
|
|
163
|
+
], limit)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function genericBuildRows(source, limit = 100) {
|
|
167
|
+
return matchingLines(source, [
|
|
98
168
|
/\b(?:compile|compiled|compilation|build)\b.*\b(?:failed|success|succeeded|complete|completed)\b/i,
|
|
99
169
|
/\b(?:failed|fatal|exception|panic)\b/i,
|
|
100
170
|
/\bwarning\b/i,
|
|
101
171
|
/:\d+:\d+\b/,
|
|
102
172
|
/\b(?:ELIFECYCLE|ERR_|exit code)\b/i,
|
|
103
|
-
]
|
|
104
|
-
for (const raw of String(source || "").split(/\r?\n/)) {
|
|
105
|
-
const line = raw.trimEnd()
|
|
106
|
-
if (!line || seen.has(line) || !patterns.some((pattern) => pattern.test(line))) continue
|
|
107
|
-
seen.add(line)
|
|
108
|
-
rows.push(line)
|
|
109
|
-
if (rows.length >= limit) break
|
|
110
|
-
}
|
|
111
|
-
return rows
|
|
173
|
+
], limit)
|
|
112
174
|
}
|
|
113
175
|
|
|
114
|
-
function
|
|
176
|
+
function jsonRows(source, limit = 80) {
|
|
115
177
|
const text = String(source || "").trim()
|
|
116
178
|
if (!text || text.length > 4 * 1024 * 1024 || !/^[\[{]/.test(text)) return []
|
|
117
179
|
let value
|
|
@@ -134,50 +196,132 @@ function jsonSummaryLines(source, limit = 80) {
|
|
|
134
196
|
}
|
|
135
197
|
return
|
|
136
198
|
}
|
|
137
|
-
|
|
138
|
-
rows.push(`${prefix || "$"}: ${String(rendered).slice(0, 160)}`)
|
|
199
|
+
rows.push(`${prefix || "$"}: ${String(JSON.stringify(current)).slice(0, 160)}`)
|
|
139
200
|
}
|
|
140
201
|
visit(value, "", 0)
|
|
141
202
|
return rows
|
|
142
203
|
}
|
|
143
204
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
205
|
+
const REDUCERS = Object.freeze([
|
|
206
|
+
{
|
|
207
|
+
family: "npm-test",
|
|
208
|
+
match: (hint) => /(?:^|\s)(?:npm|pnpm|yarn)\s+(?:run\s+)?test\b|node\s+--test\b|\b(?:jest|vitest)\b/.test(hint),
|
|
209
|
+
rows: npmTestRows,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
family: "pytest",
|
|
213
|
+
match: (hint) => /\b(?:pytest|py\.test)\b/.test(hint),
|
|
214
|
+
rows: pytestRows,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
family: "git-diff",
|
|
218
|
+
match: (hint) => /\bgit\s+(?:diff|show)\b/.test(hint),
|
|
219
|
+
rows: gitDiffRows,
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
family: "ripgrep",
|
|
223
|
+
match: (hint) => /(?:^|\s)(?:rg|ripgrep)(?:\s|$)|\bgrep\b/.test(hint),
|
|
224
|
+
rows: rgRows,
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
family: "tree",
|
|
228
|
+
match: (hint) => /(?:^|[;&|]\s*|\s)(?:tree)(?:\s|$)/.test(hint) || hint === "tree",
|
|
229
|
+
rows: treeRows,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
family: "tsc",
|
|
233
|
+
match: (hint) => /\b(?:tsc|typescript|typecheck)\b/.test(hint),
|
|
234
|
+
rows: tscRows,
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
family: "eslint",
|
|
238
|
+
match: (hint) => /\b(?:eslint|lint)\b/.test(hint),
|
|
239
|
+
rows: eslintRows,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
family: "docker",
|
|
243
|
+
match: (hint) => /\bdocker(?:\s+compose)?\b/.test(hint),
|
|
244
|
+
rows: dockerRows,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
family: "prisma",
|
|
248
|
+
match: (hint) => /\bprisma\b|schema\.prisma/.test(hint),
|
|
249
|
+
rows: prismaRows,
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
family: "build",
|
|
253
|
+
match: (hint) => /\b(?:compile|build|gradle|maven|mvn|cargo\s+check|dotnet\s+build)\b/.test(hint),
|
|
254
|
+
rows: genericBuildRows,
|
|
255
|
+
},
|
|
256
|
+
])
|
|
257
|
+
|
|
258
|
+
export const COMMAND_REDUCER_FAMILIES = Object.freeze(REDUCERS.map((item) => item.family))
|
|
259
|
+
|
|
260
|
+
export function reduceCommandOutput(source, options = {}) {
|
|
261
|
+
const text = String(source || "")
|
|
262
|
+
const hint = hintFor(options)
|
|
263
|
+
const reducer = REDUCERS.find((item) => item.match(hint))
|
|
264
|
+
if (reducer) {
|
|
265
|
+
const rows = reducer.rows(text)
|
|
266
|
+
if (rows.length) {
|
|
267
|
+
return {
|
|
268
|
+
schemaVersion: 2,
|
|
269
|
+
family: reducer.family,
|
|
270
|
+
hint,
|
|
271
|
+
rows,
|
|
272
|
+
summary: `command-aware reducer=${reducer.family}; selected=${rows.length}; totalLines=${text.split(/\r?\n/).length}`,
|
|
273
|
+
}
|
|
274
|
+
}
|
|
157
275
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
276
|
+
|
|
277
|
+
if (/(?:--json|\bjson\b)/.test(hint) || /^[\s]*[\[{]/.test(text)) {
|
|
278
|
+
const rows = jsonRows(text)
|
|
279
|
+
if (rows.length) {
|
|
280
|
+
return {
|
|
281
|
+
schemaVersion: 2,
|
|
282
|
+
family: "json",
|
|
283
|
+
hint,
|
|
284
|
+
rows,
|
|
285
|
+
summary: `command-aware reducer=json; selected=${rows.length}`,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
161
288
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
289
|
+
|
|
290
|
+
const rows = uniqueSignalLines(text)
|
|
291
|
+
return {
|
|
292
|
+
schemaVersion: 2,
|
|
293
|
+
family: rows.length ? "signal" : "generic",
|
|
294
|
+
hint,
|
|
295
|
+
rows,
|
|
296
|
+
summary: rows.length
|
|
297
|
+
? `generic signal reducer; selected=${rows.length}`
|
|
298
|
+
: "generic head/tail fallback; no structured signal reducer matched",
|
|
165
299
|
}
|
|
166
|
-
|
|
167
|
-
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function legacyStrategyKind(source, options = {}) {
|
|
303
|
+
const hint = hintFor(options)
|
|
304
|
+
if (/(jest|vitest|pytest|test|spec)/.test(hint) && npmTestRows(source).length) return "test"
|
|
305
|
+
if (/(eslint|lint|ruff|flake8|pylint|clippy)/.test(hint) && eslintRows(source).length) return "lint"
|
|
306
|
+
if (/(typecheck|tsc|compile|build|gradle|maven|cargo check|dotnet build)/.test(hint) && genericBuildRows(source).length) return "build"
|
|
307
|
+
if (/(git|diff|status)/.test(hint) && gitDiffRows(source).length) return "git"
|
|
308
|
+
if ((/(json|--json)/.test(hint) || /^[\s]*[\[{]/.test(String(source || ""))) && jsonRows(source).length) return "json"
|
|
309
|
+
return uniqueSignalLines(source).length ? "signal" : "generic"
|
|
168
310
|
}
|
|
169
311
|
|
|
170
312
|
function boundedPreview(text, maxChars, options = {}) {
|
|
171
313
|
const source = String(text || "")
|
|
172
|
-
const headBudget = Math.max(
|
|
173
|
-
const tailBudget = Math.max(
|
|
174
|
-
const signalBudget = Math.max(2048, maxChars - headBudget - tailBudget -
|
|
314
|
+
const headBudget = Math.max(1536, Math.floor(maxChars * 0.18))
|
|
315
|
+
const tailBudget = Math.max(2048, Math.floor(maxChars * 0.24))
|
|
316
|
+
const signalBudget = Math.max(2048, maxChars - headBudget - tailBudget - 1900)
|
|
175
317
|
const head = source.slice(0, headBudget)
|
|
176
318
|
const tail = source.slice(Math.max(head.length, source.length - tailBudget))
|
|
177
|
-
const
|
|
178
|
-
let signalText =
|
|
179
|
-
if (signalText.length > signalBudget)
|
|
180
|
-
|
|
319
|
+
const reduced = reduceCommandOutput(source, options)
|
|
320
|
+
let signalText = [reduced.summary, ...reduced.rows].join("\n")
|
|
321
|
+
if (signalText.length > signalBudget) {
|
|
322
|
+
signalText = signalText.slice(0, signalBudget) + "\n...[reducer output truncated]"
|
|
323
|
+
}
|
|
324
|
+
return { head, tail, signalText, reducer: reduced }
|
|
181
325
|
}
|
|
182
326
|
|
|
183
327
|
export async function compactReversibleOutput(root, text, options = {}) {
|
|
@@ -185,9 +329,10 @@ export async function compactReversibleOutput(root, text, options = {}) {
|
|
|
185
329
|
const maxChars = clampInt(options.maxChars, DEFAULT_LIMIT, 8 * 1024, 256 * 1024)
|
|
186
330
|
if (source.length <= maxChars) {
|
|
187
331
|
return {
|
|
188
|
-
schemaVersion:
|
|
332
|
+
schemaVersion: 2,
|
|
189
333
|
compacted: false,
|
|
190
334
|
strategy: "raw",
|
|
335
|
+
commandFamily: null,
|
|
191
336
|
originalChars: source.length,
|
|
192
337
|
returnedChars: source.length,
|
|
193
338
|
evidenceRef: null,
|
|
@@ -195,35 +340,45 @@ export async function compactReversibleOutput(root, text, options = {}) {
|
|
|
195
340
|
}
|
|
196
341
|
}
|
|
197
342
|
|
|
343
|
+
// Raw bytes are persisted before any model-visible reduction. Reducers never
|
|
344
|
+
// become the verification source of truth; verifiers can recover exact data.
|
|
198
345
|
const evidence = await putEvidence(root, source, {
|
|
199
346
|
kind: options.kind || "raw-tool-output",
|
|
200
|
-
source: options.source || "ues-performance-fabric",
|
|
201
|
-
summary: options.summary || `Raw output preserved before model-visible compaction (${source.length} chars)`,
|
|
347
|
+
source: options.source || options.command || "ues-performance-fabric-v2",
|
|
348
|
+
summary: options.summary || `Raw output preserved before command-aware model-visible compaction (${source.length} chars)`,
|
|
202
349
|
})
|
|
203
|
-
const { head, tail, signalText,
|
|
350
|
+
const { head, tail, signalText, reducer } = boundedPreview(source, maxChars, options)
|
|
204
351
|
const recovery = `Raw captured output: ${evidence.ref}. Recover exact captured bytes in slices with: ues store get ${evidence.ref} --start N --max 24000`
|
|
205
352
|
let preview = [
|
|
206
|
-
`[UES reversible
|
|
353
|
+
`[UES command-aware reversible compaction v2: ${source.length} -> <=${maxChars} chars; reducer=${reducer.family}]`,
|
|
207
354
|
recovery,
|
|
208
355
|
"",
|
|
209
|
-
"---
|
|
356
|
+
"--- reducer summary ---",
|
|
357
|
+
signalText,
|
|
358
|
+
"",
|
|
359
|
+
"--- bounded head ---",
|
|
210
360
|
head,
|
|
211
|
-
|
|
212
|
-
"
|
|
361
|
+
"",
|
|
362
|
+
"--- bounded tail ---",
|
|
213
363
|
tail,
|
|
214
364
|
].filter(Boolean).join("\n")
|
|
215
365
|
if (preview.length > maxChars) {
|
|
216
|
-
const reserve = Math.min(
|
|
366
|
+
const reserve = Math.min(1500, recovery.length + 200)
|
|
217
367
|
preview = preview.slice(0, Math.max(0, maxChars - reserve)) + `\n...[model-visible preview truncated]\n${recovery}`
|
|
218
368
|
}
|
|
219
369
|
|
|
370
|
+
const compatibilityKind = legacyStrategyKind(source, options)
|
|
220
371
|
return {
|
|
221
|
-
schemaVersion:
|
|
372
|
+
schemaVersion: 2,
|
|
222
373
|
compacted: true,
|
|
223
|
-
strategy
|
|
374
|
+
// Keep the V14 public strategy contract stable for callers/tests while
|
|
375
|
+
// exposing the richer V2 reducer identity separately.
|
|
376
|
+
strategy: `reversible-head-${compatibilityKind}-tail`,
|
|
377
|
+
strategyV2: `reversible-command-aware-${reducer.family}`,
|
|
378
|
+
commandFamily: reducer.family,
|
|
224
379
|
originalChars: source.length,
|
|
225
380
|
returnedChars: preview.length,
|
|
226
381
|
evidenceRef: evidence.ref,
|
|
227
382
|
text: preview,
|
|
228
383
|
}
|
|
229
|
-
}
|
|
384
|
+
}
|
|
@@ -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
|
}
|