@wix/pathgrade 1.0.9 → 1.0.10
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/dist/agents/{opencode-contract.d.ts → opencode/contract.d.ts} +2 -2
- package/dist/agents/opencode/process-groups.d.ts +3 -0
- package/dist/agents/opencode/process-groups.js +47 -0
- package/dist/agents/opencode.js +25 -13
- package/dist/sdk/agent.js +1 -1
- package/dist/sdk/judge-tool-session.js +18 -1
- package/dist/utils/llm-providers/anthropic.js +6 -1
- package/dist/utils/llm.js +1 -0
- package/package.json +2 -2
- /package/dist/agents/{opencode-contract.js → opencode/contract.js} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { MockMcpServerDescriptor } from '
|
|
2
|
-
import type { AgentName, AgentOptions } from '
|
|
1
|
+
import type { MockMcpServerDescriptor } from '../../core/mcp-mock.types.js';
|
|
2
|
+
import type { AgentName, AgentOptions } from '../../sdk/types.js';
|
|
3
3
|
export declare const OPENCODE_MODEL = "anthropic/claude-sonnet-5";
|
|
4
4
|
export declare const OPENCODE_VERSION = "1.18.18";
|
|
5
5
|
export interface OpenCodeRuntimeLockEntry {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Process-wide ownership of detached OpenCode child process groups. */
|
|
2
|
+
const activeProcessGroups = new Set();
|
|
3
|
+
let processCleanupInstalled = false;
|
|
4
|
+
export function killOpenCodeProcessGroup(pid, signal) {
|
|
5
|
+
try {
|
|
6
|
+
process.kill(-pid, signal);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
// The process group may already have exited.
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function killAllProcessGroups() {
|
|
13
|
+
for (const pid of activeProcessGroups) {
|
|
14
|
+
killOpenCodeProcessGroup(pid, 'SIGKILL');
|
|
15
|
+
}
|
|
16
|
+
activeProcessGroups.clear();
|
|
17
|
+
}
|
|
18
|
+
function uninstallProcessCleanup() {
|
|
19
|
+
if (!processCleanupInstalled)
|
|
20
|
+
return;
|
|
21
|
+
processCleanupInstalled = false;
|
|
22
|
+
process.removeListener('exit', onProcessExit);
|
|
23
|
+
process.removeListener('SIGINT', onProcessSignal);
|
|
24
|
+
process.removeListener('SIGTERM', onProcessSignal);
|
|
25
|
+
}
|
|
26
|
+
function onProcessExit() {
|
|
27
|
+
killAllProcessGroups();
|
|
28
|
+
}
|
|
29
|
+
function onProcessSignal(signal) {
|
|
30
|
+
killAllProcessGroups();
|
|
31
|
+
uninstallProcessCleanup();
|
|
32
|
+
process.kill(process.pid, signal);
|
|
33
|
+
}
|
|
34
|
+
export function registerOpenCodeProcessGroup(pid) {
|
|
35
|
+
activeProcessGroups.add(pid);
|
|
36
|
+
if (processCleanupInstalled)
|
|
37
|
+
return;
|
|
38
|
+
processCleanupInstalled = true;
|
|
39
|
+
process.once('exit', onProcessExit);
|
|
40
|
+
process.once('SIGINT', onProcessSignal);
|
|
41
|
+
process.once('SIGTERM', onProcessSignal);
|
|
42
|
+
}
|
|
43
|
+
export function unregisterOpenCodeProcessGroup(pid) {
|
|
44
|
+
activeProcessGroups.delete(pid);
|
|
45
|
+
if (activeProcessGroups.size === 0)
|
|
46
|
+
uninstallProcessCleanup();
|
|
47
|
+
}
|
package/dist/agents/opencode.js
CHANGED
|
@@ -7,7 +7,8 @@ import fs from 'fs-extra';
|
|
|
7
7
|
import { BaseAgent, getRuntimeEnv, getWorkspacePath, } from '../types.js';
|
|
8
8
|
import { buildSummary, enrichSkillEvents } from '../tool-events.js';
|
|
9
9
|
import { readStagedMcpServers } from '../providers/mcp-config.js';
|
|
10
|
-
import { currentOpenCodePlatformKey, OPENCODE_MODEL, OPENCODE_RUNTIME_LOCK, } from './opencode
|
|
10
|
+
import { currentOpenCodePlatformKey, OPENCODE_MODEL, OPENCODE_RUNTIME_LOCK, } from './opencode/contract.js';
|
|
11
|
+
import { killOpenCodeProcessGroup, registerOpenCodeProcessGroup, unregisterOpenCodeProcessGroup, } from './opencode/process-groups.js';
|
|
11
12
|
const OUTPUT_CAP_BYTES = 16 * 1024 * 1024;
|
|
12
13
|
const FIXED_OPENCODE_ENV = {
|
|
13
14
|
OPENCODE_CLIENT: 'pathgrade',
|
|
@@ -68,6 +69,7 @@ export function spawnOpenCode(executable, args, options) {
|
|
|
68
69
|
let aborted = false;
|
|
69
70
|
let killTimer;
|
|
70
71
|
let settled = false;
|
|
72
|
+
let terminating = false;
|
|
71
73
|
const child = spawn(executable, args, {
|
|
72
74
|
cwd: options.cwd,
|
|
73
75
|
env: options.env,
|
|
@@ -75,22 +77,22 @@ export function spawnOpenCode(executable, args, options) {
|
|
|
75
77
|
detached: true,
|
|
76
78
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
77
79
|
});
|
|
80
|
+
if (child.pid)
|
|
81
|
+
registerOpenCodeProcessGroup(child.pid);
|
|
78
82
|
const killGroup = (signal) => {
|
|
79
83
|
if (!child.pid)
|
|
80
84
|
return;
|
|
81
|
-
|
|
82
|
-
process.kill(-child.pid, signal);
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
// The process may have exited between the state check and kill.
|
|
86
|
-
}
|
|
85
|
+
killOpenCodeProcessGroup(child.pid, signal);
|
|
87
86
|
};
|
|
88
87
|
const terminate = () => {
|
|
88
|
+
terminating = true;
|
|
89
89
|
killGroup('SIGTERM');
|
|
90
90
|
killTimer ??= setTimeout(() => killGroup('SIGKILL'), 250);
|
|
91
91
|
killTimer.unref();
|
|
92
92
|
};
|
|
93
93
|
const onAbort = () => {
|
|
94
|
+
if (aborted)
|
|
95
|
+
return;
|
|
94
96
|
aborted = true;
|
|
95
97
|
terminate();
|
|
96
98
|
};
|
|
@@ -98,9 +100,6 @@ export function spawnOpenCode(executable, args, options) {
|
|
|
98
100
|
overflow = true;
|
|
99
101
|
terminate();
|
|
100
102
|
};
|
|
101
|
-
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
102
|
-
if (options.signal?.aborted)
|
|
103
|
-
onAbort();
|
|
104
103
|
child.stdout.on('data', (chunk) => {
|
|
105
104
|
stdoutBytes += chunk.length;
|
|
106
105
|
if (stdoutBytes <= cap)
|
|
@@ -114,16 +113,23 @@ export function spawnOpenCode(executable, args, options) {
|
|
|
114
113
|
onOverflow();
|
|
115
114
|
});
|
|
116
115
|
child.stdin.on('error', () => undefined);
|
|
116
|
+
child.once('error', (error) => finish(undefined, undefined, error));
|
|
117
|
+
child.once('close', (code, signal) => finish(code, signal));
|
|
118
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
119
|
+
if (options.signal?.aborted)
|
|
120
|
+
onAbort();
|
|
117
121
|
if (!aborted)
|
|
118
122
|
child.stdin.end(options.stdin);
|
|
119
123
|
else
|
|
120
124
|
child.stdin.destroy();
|
|
121
|
-
child.once('error', (error) => finish(undefined, undefined, error));
|
|
122
|
-
child.once('close', (code, signal) => finish(code, signal));
|
|
123
125
|
function finish(code, signal, error) {
|
|
124
126
|
if (settled)
|
|
125
127
|
return;
|
|
126
128
|
settled = true;
|
|
129
|
+
if (child.pid && terminating)
|
|
130
|
+
killOpenCodeProcessGroup(child.pid, 'SIGKILL');
|
|
131
|
+
if (child.pid)
|
|
132
|
+
unregisterOpenCodeProcessGroup(child.pid);
|
|
127
133
|
options.signal?.removeEventListener('abort', onAbort);
|
|
128
134
|
if (killTimer)
|
|
129
135
|
clearTimeout(killTimer);
|
|
@@ -377,6 +383,7 @@ class OpenCodeSession {
|
|
|
377
383
|
disposed = false;
|
|
378
384
|
sessionId;
|
|
379
385
|
inFlight;
|
|
386
|
+
disposeController = new AbortController();
|
|
380
387
|
constructor(runtime, options) {
|
|
381
388
|
this.workspacePath = getWorkspacePath(runtime);
|
|
382
389
|
this.runtimeEnv = getRuntimeEnv(runtime);
|
|
@@ -432,11 +439,15 @@ class OpenCodeSession {
|
|
|
432
439
|
'--model', OPENCODE_MODEL, '--agent', 'build',
|
|
433
440
|
...(this.sessionId ? ['--session', this.sessionId] : []),
|
|
434
441
|
];
|
|
442
|
+
const turnSignal = this.getAbortSignal();
|
|
443
|
+
const signal = turnSignal
|
|
444
|
+
? AbortSignal.any([turnSignal, this.disposeController.signal])
|
|
445
|
+
: this.disposeController.signal;
|
|
435
446
|
const processResult = await spawnOpenCode(this.resolvedExecutable, args, {
|
|
436
447
|
cwd: this.workspacePath,
|
|
437
448
|
env,
|
|
438
449
|
stdin: message,
|
|
439
|
-
signal
|
|
450
|
+
signal,
|
|
440
451
|
});
|
|
441
452
|
const parsed = parseOpenCodeOutput(processResult.stdout, processResult, this.mcpToolNames);
|
|
442
453
|
if (this.sessionId && parsed.sessionId !== this.sessionId) {
|
|
@@ -494,6 +505,7 @@ class OpenCodeSession {
|
|
|
494
505
|
if (this.disposed)
|
|
495
506
|
return;
|
|
496
507
|
this.disposed = true;
|
|
508
|
+
this.disposeController.abort();
|
|
497
509
|
await this.inFlight?.catch(() => undefined);
|
|
498
510
|
await this.cleanupState();
|
|
499
511
|
}
|
package/dist/sdk/agent.js
CHANGED
|
@@ -15,7 +15,7 @@ import { createVerboseEmitter } from '../reporters/verbose-emitter.js';
|
|
|
15
15
|
import fs from 'fs-extra';
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import { cleanDebugRuns, DEFAULT_DEBUG_RETAIN_RUNS, prepareManagedDebugRun, } from '../providers/debug-runs.js';
|
|
18
|
-
import { collectOpenCodeMcpToolNames, validateOpenCodeDeclaration } from '../agents/opencode
|
|
18
|
+
import { collectOpenCodeMcpToolNames, validateOpenCodeDeclaration } from '../agents/opencode/contract.js';
|
|
19
19
|
/**
|
|
20
20
|
* Test-only injection point: override the sink used by the next emitter
|
|
21
21
|
* built inside `createAgent`. Pass `null` to restore the default (stderr).
|
|
@@ -35,13 +35,14 @@ export async function runJudgeSession(input, options = {}) {
|
|
|
35
35
|
{ role: 'user', content: user },
|
|
36
36
|
];
|
|
37
37
|
let rounds = 0;
|
|
38
|
+
let scoreRepairAttempted = false;
|
|
38
39
|
while (rounds < maxRounds) {
|
|
39
40
|
rounds++;
|
|
40
41
|
let response;
|
|
41
42
|
try {
|
|
42
43
|
response = await llm.callWithTools(messages, {
|
|
43
44
|
system,
|
|
44
|
-
tools: toolSchemas,
|
|
45
|
+
tools: scoreRepairAttempted ? [] : toolSchemas,
|
|
45
46
|
model: scorer.model,
|
|
46
47
|
cacheControl: scorer.cacheControl,
|
|
47
48
|
});
|
|
@@ -63,6 +64,22 @@ export async function runJudgeSession(input, options = {}) {
|
|
|
63
64
|
}
|
|
64
65
|
const parsed = parseFinalScore(response.text);
|
|
65
66
|
if (!parsed.ok) {
|
|
67
|
+
if (parsed.message.startsWith('JSON parse failed:')
|
|
68
|
+
&& !scoreRepairAttempted
|
|
69
|
+
&& rounds < maxRounds) {
|
|
70
|
+
scoreRepairAttempted = true;
|
|
71
|
+
messages.push({ role: 'assistant', content: response.text });
|
|
72
|
+
messages.push({
|
|
73
|
+
role: 'user',
|
|
74
|
+
content: [
|
|
75
|
+
`Your final answer was not valid JSON: ${parsed.message}.`,
|
|
76
|
+
'Return ONLY a valid JSON object with double-quoted keys in this exact shape:',
|
|
77
|
+
'{"score": <number 0..1>, "details": "<brief explanation>"}',
|
|
78
|
+
'Do not call more tools or include Markdown fences.',
|
|
79
|
+
].join('\n'),
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
66
83
|
return makeOutcome(tokenUsage, toolCalls, logEntries, rounds, {
|
|
67
84
|
code: 'invalid_score',
|
|
68
85
|
details: parsed.message,
|
|
@@ -11,6 +11,11 @@ function resolveBaseUrl(env) {
|
|
|
11
11
|
|| process.env.APP_ANTHROPIC_BASE_URL
|
|
12
12
|
|| 'https://api.anthropic.com';
|
|
13
13
|
}
|
|
14
|
+
function resolveMessagesUrl(env) {
|
|
15
|
+
const baseUrl = resolveBaseUrl(env).replace(/\/+$/, '');
|
|
16
|
+
const apiRoot = baseUrl.endsWith('/v1') ? baseUrl : `${baseUrl}/v1`;
|
|
17
|
+
return `${apiRoot}/messages`;
|
|
18
|
+
}
|
|
14
19
|
function buildHeaders(apiKey, useCache) {
|
|
15
20
|
const headers = {
|
|
16
21
|
'Content-Type': 'application/json',
|
|
@@ -57,7 +62,7 @@ function resolveTemperature(model, config, temperature) {
|
|
|
57
62
|
return temperature ?? 0;
|
|
58
63
|
}
|
|
59
64
|
async function postAnthropic(apiKey, useCache, body, env) {
|
|
60
|
-
const response = await fetch(
|
|
65
|
+
const response = await fetch(resolveMessagesUrl(env), {
|
|
61
66
|
method: 'POST',
|
|
62
67
|
headers: buildHeaders(apiKey, useCache),
|
|
63
68
|
body: JSON.stringify(body),
|
package/dist/utils/llm.js
CHANGED
|
@@ -199,6 +199,7 @@ export function createAgentLLM(agentName, agentEnv) {
|
|
|
199
199
|
const adapters = agentEnv && Object.keys(agentEnv).length > 0
|
|
200
200
|
? baseAdapters.map((a) => ({
|
|
201
201
|
...a,
|
|
202
|
+
isAvailable: (env) => a.isAvailable({ ...agentEnv, ...env }),
|
|
202
203
|
call: (prompt, opts) => a.call(prompt, { ...opts, env: { ...agentEnv, ...opts.env } }),
|
|
203
204
|
...(a.callWithTools
|
|
204
205
|
? { callWithTools: (messages, opts) => a.callWithTools(messages, { ...opts, env: { ...agentEnv, ...opts.env } }) }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/pathgrade",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"packageManager": "yarn@4.12.0",
|
|
5
5
|
"description": "Evaluate whether AI agents discover and use your skills correctly",
|
|
6
6
|
"exports": {
|
|
@@ -132,5 +132,5 @@
|
|
|
132
132
|
"typescript": "^5.9.3",
|
|
133
133
|
"zod": "4.3.6"
|
|
134
134
|
},
|
|
135
|
-
"falconPackageHash": "
|
|
135
|
+
"falconPackageHash": "be877aa61dfb2cd1b58c716b704cffee9592428675a660382e86e358"
|
|
136
136
|
}
|
|
File without changes
|