@wix/pathgrade 1.0.9 → 1.0.11
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/claude/sdk-message-projector.d.ts +5 -0
- package/dist/agents/claude/sdk-message-projector.js +32 -6
- package/dist/agents/claude/tool-results.d.ts +19 -0
- package/dist/agents/claude/tool-results.js +117 -0
- package/dist/agents/claude.d.ts +4 -0
- package/dist/agents/claude.js +17 -2
- 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 +9 -15
- package/dist/sdk/chat.js +10 -12
- package/dist/sdk/judge-tool-session.js +18 -1
- package/dist/sdk/judge-tools.js +23 -2
- package/dist/sdk/managed-session.js +6 -4
- package/dist/sdk/tool-event-log.d.ts +3 -0
- package/dist/sdk/tool-event-log.js +7 -0
- package/dist/tool-events.d.ts +19 -0
- package/dist/utils/llm-providers/anthropic.js +6 -1
- package/dist/utils/llm.js +1 -0
- package/dist/viewer.html +4 -4
- package/package.json +2 -2
- /package/dist/agents/{opencode-contract.js → opencode/contract.js} +0 -0
|
@@ -19,9 +19,12 @@ import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
|
19
19
|
import type { AgentTurnResult } from '../../types.js';
|
|
20
20
|
import type { AskUserAnswerStore } from './ask-user-answer-store.js';
|
|
21
21
|
import type { ClaudeDeniedMcpEventStore } from './denied-mcp-event-store.js';
|
|
22
|
+
import { type ClaudeSdkMessageTiming } from './tool-results.js';
|
|
22
23
|
export interface ProjectTurnInput {
|
|
23
24
|
/** Buffered typed-message stream from one `query()` call. */
|
|
24
25
|
messages: SDKMessage[];
|
|
26
|
+
/** Local receive timing captured while consuming each corresponding SDK message. */
|
|
27
|
+
messageTimings?: readonly (ClaudeSdkMessageTiming | undefined)[];
|
|
25
28
|
/** Forwarded onto `ToolEvent.turnNumber`; optional for projector unit use. */
|
|
26
29
|
turnNumber?: number;
|
|
27
30
|
/** First user message of the turn — used for slash-command skill detection. */
|
|
@@ -39,6 +42,8 @@ export interface ProjectTurnInput {
|
|
|
39
42
|
mcpServerNames?: readonly string[];
|
|
40
43
|
/** Policy-denied MCP events recorded by the per-turn permission bridge. */
|
|
41
44
|
deniedMcpEvents?: ClaudeDeniedMcpEventStore;
|
|
45
|
+
/** Exact runtime credential values to redact before tool results become trace data. */
|
|
46
|
+
sensitiveValues?: readonly string[];
|
|
42
47
|
}
|
|
43
48
|
export interface ProjectedTurn {
|
|
44
49
|
result: AgentTurnResult;
|
|
@@ -23,6 +23,7 @@ const SDK_ERROR_SUBTYPES = [
|
|
|
23
23
|
];
|
|
24
24
|
import { TOOL_NAME_MAP, buildSummary, enrichSkillEvents } from '../../tool-events.js';
|
|
25
25
|
import { parseClaudeSdkMcpToolName } from './mcp-tool-name.js';
|
|
26
|
+
import { applyObservedToolResult, extractObservedToolResults, } from './tool-results.js';
|
|
26
27
|
export function projectSdkMessages(input) {
|
|
27
28
|
let sessionId;
|
|
28
29
|
let initSkills;
|
|
@@ -37,7 +38,9 @@ export function projectSdkMessages(input) {
|
|
|
37
38
|
let costUsd;
|
|
38
39
|
let errorSubtype;
|
|
39
40
|
const toolEvents = [];
|
|
40
|
-
|
|
41
|
+
const pendingTools = new Map();
|
|
42
|
+
for (const [messageIndex, msg] of input.messages.entries()) {
|
|
43
|
+
const timing = input.messageTimings?.[messageIndex];
|
|
41
44
|
switch (msg.type) {
|
|
42
45
|
case 'system': {
|
|
43
46
|
const sid = msg.session_id;
|
|
@@ -60,13 +63,30 @@ export function projectSdkMessages(input) {
|
|
|
60
63
|
continue;
|
|
61
64
|
}
|
|
62
65
|
if (block.type === 'tool_use') {
|
|
63
|
-
const event = buildToolEvent(block, input.turnNumber, input.answerStore, input.mcpServerNames ?? [], input.deniedMcpEvents);
|
|
64
|
-
if (event)
|
|
66
|
+
const event = buildToolEvent(block, input.turnNumber, input.answerStore, input.mcpServerNames ?? [], input.deniedMcpEvents, timing);
|
|
67
|
+
if (event) {
|
|
65
68
|
toolEvents.push(event);
|
|
69
|
+
if (event.toolUseId) {
|
|
70
|
+
pendingTools.set(event.toolUseId, {
|
|
71
|
+
event,
|
|
72
|
+
startedMonotonicMs: timing?.receivedMonotonicMs,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
break;
|
|
69
79
|
}
|
|
80
|
+
case 'user': {
|
|
81
|
+
for (const observed of extractObservedToolResults(msg, timing)) {
|
|
82
|
+
const pending = pendingTools.get(observed.toolUseId);
|
|
83
|
+
if (!pending)
|
|
84
|
+
continue;
|
|
85
|
+
applyObservedToolResult(pending.event, observed, pending.startedMonotonicMs, input.sensitiveValues);
|
|
86
|
+
pendingTools.delete(observed.toolUseId);
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
70
90
|
case 'result': {
|
|
71
91
|
const r = msg;
|
|
72
92
|
if (r.session_id)
|
|
@@ -167,7 +187,7 @@ function prependSlashCommandSkillEvent(events, firstMessage, initSkills) {
|
|
|
167
187
|
...events,
|
|
168
188
|
];
|
|
169
189
|
}
|
|
170
|
-
function buildToolEvent(block, turnNumber, answerStore, mcpServerNames, deniedMcpEvents) {
|
|
190
|
+
function buildToolEvent(block, turnNumber, answerStore, mcpServerNames, deniedMcpEvents, timing) {
|
|
171
191
|
const providerToolName = String(block.name || 'unknown');
|
|
172
192
|
const rawInput = block.input ?? undefined;
|
|
173
193
|
const toolUseId = typeof block.id === 'string' ? block.id : undefined;
|
|
@@ -180,15 +200,18 @@ function buildToolEvent(block, turnNumber, answerStore, mcpServerNames, deniedMc
|
|
|
180
200
|
...(rawInput ?? {}),
|
|
181
201
|
server: mcpTool.server,
|
|
182
202
|
tool: mcpTool.tool,
|
|
183
|
-
status: '
|
|
203
|
+
status: 'incomplete',
|
|
184
204
|
};
|
|
185
205
|
return {
|
|
186
206
|
action: 'mcp_tool_call',
|
|
187
207
|
provider: 'claude',
|
|
188
208
|
providerToolName: normalizedProviderToolName,
|
|
209
|
+
...(toolUseId ? { toolUseId } : {}),
|
|
189
210
|
turnNumber,
|
|
190
211
|
arguments: args,
|
|
191
|
-
|
|
212
|
+
status: 'incomplete',
|
|
213
|
+
...(timing ? { startedAt: timing.receivedAt } : {}),
|
|
214
|
+
summary: `MCP tool ${normalizedProviderToolName}`,
|
|
192
215
|
confidence: 'high',
|
|
193
216
|
rawSnippet: JSON.stringify(block).slice(0, 200),
|
|
194
217
|
};
|
|
@@ -203,8 +226,11 @@ function buildToolEvent(block, turnNumber, answerStore, mcpServerNames, deniedMc
|
|
|
203
226
|
action,
|
|
204
227
|
provider: 'claude',
|
|
205
228
|
providerToolName,
|
|
229
|
+
...(toolUseId ? { toolUseId } : {}),
|
|
206
230
|
turnNumber,
|
|
207
231
|
arguments: args,
|
|
232
|
+
status: 'incomplete',
|
|
233
|
+
...(timing ? { startedAt: timing.receivedAt } : {}),
|
|
208
234
|
summary,
|
|
209
235
|
confidence: 'high',
|
|
210
236
|
rawSnippet,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import type { ToolEvent } from '../../tool-events.js';
|
|
3
|
+
export declare const TOOL_RESULT_MAX_CHARS: number;
|
|
4
|
+
export interface ClaudeSdkMessageTiming {
|
|
5
|
+
receivedAt: string;
|
|
6
|
+
receivedMonotonicMs: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ObservedToolResult {
|
|
9
|
+
toolUseId: string;
|
|
10
|
+
isError: boolean;
|
|
11
|
+
structuredResult?: Record<string, unknown>;
|
|
12
|
+
content?: string;
|
|
13
|
+
completedAt?: string;
|
|
14
|
+
completedMonotonicMs?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function hasToolLifecycleBoundary(message: SDKMessage): boolean;
|
|
17
|
+
export declare function extractObservedToolResults(message: SDKMessage, timing?: ClaudeSdkMessageTiming): ObservedToolResult[];
|
|
18
|
+
export declare function applyObservedToolResult(event: ToolEvent, observed: ObservedToolResult, startedMonotonicMs?: number, sensitiveValues?: readonly string[]): void;
|
|
19
|
+
export declare function collectSensitiveEnvValues(env: Readonly<Record<string, string>>): string[];
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export const TOOL_RESULT_MAX_CHARS = 64 * 1024;
|
|
2
|
+
const SECRET_ENV_KEY_PATTERN = /(^|[_-])(api[_-]?key|token|secret|password|authorization|auth|bearer)([_-]|$)/i;
|
|
3
|
+
export function hasToolLifecycleBoundary(message) {
|
|
4
|
+
if (message.type !== 'assistant' && message.type !== 'user')
|
|
5
|
+
return false;
|
|
6
|
+
const content = message.message?.content;
|
|
7
|
+
return Array.isArray(content) && content.some((block) => isRecord(block) && (block.type === 'tool_use' || block.type === 'tool_result'));
|
|
8
|
+
}
|
|
9
|
+
export function extractObservedToolResults(message, timing) {
|
|
10
|
+
if (message.type !== 'user')
|
|
11
|
+
return [];
|
|
12
|
+
const user = message;
|
|
13
|
+
const content = user.message?.content;
|
|
14
|
+
if (!Array.isArray(content))
|
|
15
|
+
return [];
|
|
16
|
+
const blocks = content.filter(isToolResultBlock);
|
|
17
|
+
const structuredResult = blocks.length === 1 && isRecord(user.tool_use_result)
|
|
18
|
+
? user.tool_use_result
|
|
19
|
+
: undefined;
|
|
20
|
+
return blocks.flatMap((block) => {
|
|
21
|
+
if (typeof block.tool_use_id !== 'string')
|
|
22
|
+
return [];
|
|
23
|
+
const text = extractTextContent(block.content);
|
|
24
|
+
return [{
|
|
25
|
+
toolUseId: block.tool_use_id,
|
|
26
|
+
isError: block.is_error === true,
|
|
27
|
+
...(structuredResult ? { structuredResult } : {}),
|
|
28
|
+
...(text === undefined ? {} : { content: text }),
|
|
29
|
+
...(timing ? {
|
|
30
|
+
completedAt: timing.receivedAt,
|
|
31
|
+
completedMonotonicMs: timing.receivedMonotonicMs,
|
|
32
|
+
} : {}),
|
|
33
|
+
}];
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function applyObservedToolResult(event, observed, startedMonotonicMs, sensitiveValues = []) {
|
|
37
|
+
const remainsBackgrounded = event.arguments?.run_in_background === true;
|
|
38
|
+
const result = buildBoundedToolResult(observed, sensitiveValues);
|
|
39
|
+
const failedExit = result.exitCode !== undefined && result.exitCode !== 0;
|
|
40
|
+
const failed = observed.isError || failedExit;
|
|
41
|
+
event.status = failed ? 'error' : (remainsBackgrounded ? 'incomplete' : 'completed');
|
|
42
|
+
if (!remainsBackgrounded || failed) {
|
|
43
|
+
if (observed.completedAt)
|
|
44
|
+
event.completedAt = observed.completedAt;
|
|
45
|
+
if (startedMonotonicMs !== undefined && observed.completedMonotonicMs !== undefined) {
|
|
46
|
+
event.durationMs = Math.max(0, observed.completedMonotonicMs - startedMonotonicMs);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (Object.keys(result).length > 0)
|
|
50
|
+
event.result = result;
|
|
51
|
+
if (event.action === 'mcp_tool_call' && event.arguments) {
|
|
52
|
+
event.arguments.status = event.status;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function collectSensitiveEnvValues(env) {
|
|
56
|
+
return [...new Set(Object.entries(env)
|
|
57
|
+
.filter(([key, value]) => SECRET_ENV_KEY_PATTERN.test(key) && value.length > 0)
|
|
58
|
+
.map(([, value]) => value))]
|
|
59
|
+
.sort((a, b) => b.length - a.length);
|
|
60
|
+
}
|
|
61
|
+
function buildBoundedToolResult(observed, sensitiveValues) {
|
|
62
|
+
const raw = observed.structuredResult;
|
|
63
|
+
const result = {};
|
|
64
|
+
let truncated = false;
|
|
65
|
+
const addBounded = (key, value) => {
|
|
66
|
+
if (typeof value !== 'string')
|
|
67
|
+
return;
|
|
68
|
+
const bounded = boundText(redactSensitiveValues(value, sensitiveValues));
|
|
69
|
+
result[key] = bounded.value;
|
|
70
|
+
truncated ||= bounded.truncated;
|
|
71
|
+
};
|
|
72
|
+
addBounded('stdout', raw?.stdout);
|
|
73
|
+
addBounded('stderr', raw?.stderr);
|
|
74
|
+
if (observed.content !== raw?.stdout)
|
|
75
|
+
addBounded('content', observed.content);
|
|
76
|
+
const exitCode = raw?.exitCode ?? raw?.exit_code;
|
|
77
|
+
if (typeof exitCode === 'number' && Number.isFinite(exitCode))
|
|
78
|
+
result.exitCode = exitCode;
|
|
79
|
+
if (truncated)
|
|
80
|
+
result.truncated = true;
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
function redactSensitiveValues(value, sensitiveValues) {
|
|
84
|
+
let redacted = value;
|
|
85
|
+
for (const secret of sensitiveValues) {
|
|
86
|
+
if (secret.length > 0)
|
|
87
|
+
redacted = redacted.split(secret).join('<redacted>');
|
|
88
|
+
}
|
|
89
|
+
return redacted;
|
|
90
|
+
}
|
|
91
|
+
function boundText(value) {
|
|
92
|
+
if (value.length <= TOOL_RESULT_MAX_CHARS)
|
|
93
|
+
return { value, truncated: false };
|
|
94
|
+
const marker = '\n[truncated by PathGrade]';
|
|
95
|
+
return {
|
|
96
|
+
value: `${value.slice(0, TOOL_RESULT_MAX_CHARS - marker.length)}${marker}`,
|
|
97
|
+
truncated: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function extractTextContent(content) {
|
|
101
|
+
if (typeof content === 'string')
|
|
102
|
+
return content;
|
|
103
|
+
if (!Array.isArray(content))
|
|
104
|
+
return undefined;
|
|
105
|
+
const text = content
|
|
106
|
+
.filter(isRecord)
|
|
107
|
+
.filter((block) => block.type === 'text' && typeof block.text === 'string')
|
|
108
|
+
.map((block) => block.text)
|
|
109
|
+
.join('');
|
|
110
|
+
return text || undefined;
|
|
111
|
+
}
|
|
112
|
+
function isToolResultBlock(value) {
|
|
113
|
+
return isRecord(value) && value.type === 'tool_result';
|
|
114
|
+
}
|
|
115
|
+
function isRecord(value) {
|
|
116
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
117
|
+
}
|
package/dist/agents/claude.d.ts
CHANGED
|
@@ -39,6 +39,10 @@ export interface ClaudeAgentDeps {
|
|
|
39
39
|
envExecutable?: string;
|
|
40
40
|
/** Optional macOS sandbox-exec profile. None today; preserves the seam. */
|
|
41
41
|
sandboxProfile?: string;
|
|
42
|
+
/** Local wall-clock seam for deterministic tool lifecycle tests. */
|
|
43
|
+
now?: () => Date;
|
|
44
|
+
/** Monotonic clock seam for deterministic tool duration tests. */
|
|
45
|
+
monotonicNow?: () => number;
|
|
42
46
|
}
|
|
43
47
|
export interface ClaudeAgentOptions {
|
|
44
48
|
/**
|
package/dist/agents/claude.js
CHANGED
|
@@ -22,11 +22,13 @@
|
|
|
22
22
|
* parser wholesale.
|
|
23
23
|
*/
|
|
24
24
|
import { query as sdkQuery, } from '@anthropic-ai/claude-agent-sdk';
|
|
25
|
+
import { performance } from 'node:perf_hooks';
|
|
25
26
|
import { BaseAgent, getRuntimeEnv, getWorkspacePath, } from '../types.js';
|
|
26
27
|
import { createSandboxedClaudeSpawn } from '../providers/sandboxed-claude-spawn.js';
|
|
27
28
|
import { assertClaudeLiveMcpSafetyPreflight, assertStdioMcpServersStartForClaudeSdk, mountMcpForClaudeSdk, } from '../providers/mcp-runtime-mounting.js';
|
|
28
29
|
import { buildClaudeSdkOptions, resolveClaudeCodeExecutable, } from './claude/sdk-options.js';
|
|
29
30
|
import { projectSdkMessages } from './claude/sdk-message-projector.js';
|
|
31
|
+
import { collectSensitiveEnvValues, hasToolLifecycleBoundary, } from './claude/tool-results.js';
|
|
30
32
|
import { createAskUserAnswerStore } from './claude/ask-user-answer-store.js';
|
|
31
33
|
import { createClaudeToolPermissionBridge } from './claude/tool-permission-bridge.js';
|
|
32
34
|
import { createClaudeDeniedMcpEventStore } from './claude/denied-mcp-event-store.js';
|
|
@@ -63,6 +65,8 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
63
65
|
const platform = this.deps.platform ?? process.platform;
|
|
64
66
|
const hostEnv = this.deps.hostEnv ?? process.env;
|
|
65
67
|
const envExecutable = this.deps.envExecutable ?? process.env.PATHGRADE_CLAUDE_CODE_EXECUTABLE;
|
|
68
|
+
const runtimeEnv = getRuntimeEnv(runtime);
|
|
69
|
+
const sensitiveValues = collectSensitiveEnvValues(runtimeEnv);
|
|
66
70
|
const sandboxedSpawn = createSandboxedClaudeSpawn({
|
|
67
71
|
platform,
|
|
68
72
|
hostEnv,
|
|
@@ -75,7 +79,7 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
75
79
|
const mcpMountOptions = {
|
|
76
80
|
workspacePath,
|
|
77
81
|
mcpConfigPath: sessionOptions?.mcpConfigPath,
|
|
78
|
-
runtimeEnv
|
|
82
|
+
runtimeEnv,
|
|
79
83
|
};
|
|
80
84
|
await assertClaudeLiveMcpSafetyPreflight({
|
|
81
85
|
...mcpMountOptions,
|
|
@@ -117,7 +121,7 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
117
121
|
workspacePath,
|
|
118
122
|
spawnClaudeCodeProcess: sandboxedSpawn,
|
|
119
123
|
canUseTool: bridge,
|
|
120
|
-
runtimeEnv
|
|
124
|
+
runtimeEnv,
|
|
121
125
|
model: sessionOptions?.model,
|
|
122
126
|
claudeCodeExecutable,
|
|
123
127
|
resume: priorSessionId,
|
|
@@ -125,9 +129,18 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
125
129
|
abortController: createLinkedAbortController(getTurnAbortSignal(sessionOptions)),
|
|
126
130
|
});
|
|
127
131
|
const messages = [];
|
|
132
|
+
const messageTimings = [];
|
|
133
|
+
const now = this.deps.now ?? (() => new Date());
|
|
134
|
+
const monotonicNow = this.deps.monotonicNow ?? (() => performance.now());
|
|
128
135
|
const stream = queryFn({ prompt: message, options: sdkOptions });
|
|
129
136
|
for await (const msg of stream) {
|
|
130
137
|
messages.push(msg);
|
|
138
|
+
messageTimings.push(hasToolLifecycleBoundary(msg)
|
|
139
|
+
? {
|
|
140
|
+
receivedAt: now().toISOString(),
|
|
141
|
+
receivedMonotonicMs: monotonicNow(),
|
|
142
|
+
}
|
|
143
|
+
: undefined);
|
|
131
144
|
}
|
|
132
145
|
// The legacy NDJSON parser only synthesized the slash-command
|
|
133
146
|
// `use_skill` event from the *opening* user message. The Claude
|
|
@@ -140,11 +153,13 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
140
153
|
const projectorFirstMessage = turnNumber === 1 ? message : undefined;
|
|
141
154
|
const projected = projectSdkMessages({
|
|
142
155
|
messages,
|
|
156
|
+
messageTimings,
|
|
143
157
|
turnNumber,
|
|
144
158
|
firstMessage: projectorFirstMessage,
|
|
145
159
|
answerStore,
|
|
146
160
|
mcpServerNames: mcpServers ? Object.keys(mcpServers) : [],
|
|
147
161
|
deniedMcpEvents,
|
|
162
|
+
sensitiveValues,
|
|
148
163
|
});
|
|
149
164
|
// Capture the SDK-reported session id BEFORE checking for a bus
|
|
150
165
|
// rejection so the next turn's `Options.resume` points at this
|
|
@@ -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
|
@@ -9,13 +9,14 @@ import { createManagedSession } from './managed-session.js';
|
|
|
9
9
|
import { createAgentLLM } from '../utils/llm.js';
|
|
10
10
|
import { buildRunSnapshot } from './snapshots.js';
|
|
11
11
|
import { buildModelAgentResultLogEntry } from './agent-result-log.js';
|
|
12
|
+
import { buildToolEventLogEntry } from './tool-event-log.js';
|
|
12
13
|
import { getVisibleAssistantMessage } from './visible-turn.js';
|
|
13
14
|
import { getCurrentCaseContext } from './case-context.js';
|
|
14
15
|
import { createVerboseEmitter } from '../reporters/verbose-emitter.js';
|
|
15
16
|
import fs from 'fs-extra';
|
|
16
17
|
import * as path from 'path';
|
|
17
18
|
import { cleanDebugRuns, DEFAULT_DEBUG_RETAIN_RUNS, prepareManagedDebugRun, } from '../providers/debug-runs.js';
|
|
18
|
-
import { collectOpenCodeMcpToolNames, validateOpenCodeDeclaration } from '../agents/opencode
|
|
19
|
+
import { collectOpenCodeMcpToolNames, validateOpenCodeDeclaration } from '../agents/opencode/contract.js';
|
|
19
20
|
/**
|
|
20
21
|
* Test-only injection point: override the sink used by the next emitter
|
|
21
22
|
* built inside `createAgent`. Pass `null` to restore the default (stderr).
|
|
@@ -140,21 +141,18 @@ class AgentImpl {
|
|
|
140
141
|
this.accumulateTurnUsage(turnResult);
|
|
141
142
|
const response = getVisibleAssistantMessage(turnResult);
|
|
142
143
|
const durationMs = Date.now() - turnStart;
|
|
144
|
+
const turnCompletedAt = timestamp();
|
|
145
|
+
for (const toolEvent of turnResult.toolEvents) {
|
|
146
|
+
this._log.push(buildToolEventLogEntry(toolEvent, turnCompletedAt));
|
|
147
|
+
this.verbose.toolEvent({ action: toolEvent.action, summary: toolEvent.summary });
|
|
148
|
+
}
|
|
143
149
|
this._log.push(buildModelAgentResultLogEntry({
|
|
144
|
-
timestamp:
|
|
150
|
+
timestamp: turnCompletedAt,
|
|
145
151
|
turnNumber,
|
|
146
152
|
durationMs,
|
|
147
153
|
turnResult,
|
|
148
154
|
assistantMessage: response,
|
|
149
155
|
}));
|
|
150
|
-
for (const toolEvent of turnResult.toolEvents) {
|
|
151
|
-
this._log.push({
|
|
152
|
-
type: 'tool_event',
|
|
153
|
-
timestamp: timestamp(),
|
|
154
|
-
tool_event: toolEvent,
|
|
155
|
-
});
|
|
156
|
-
this.verbose.toolEvent({ action: toolEvent.action, summary: toolEvent.summary });
|
|
157
|
-
}
|
|
158
156
|
this._messages.push({ role: 'agent', content: response });
|
|
159
157
|
this.verbose.turnEnd({
|
|
160
158
|
turn: turnNumber,
|
|
@@ -234,11 +232,7 @@ class AgentImpl {
|
|
|
234
232
|
// it undefined, in which case this is a no-op.
|
|
235
233
|
this.accumulateTurnUsage(turnResult);
|
|
236
234
|
for (const toolEvent of turnResult.toolEvents) {
|
|
237
|
-
this._log.push(
|
|
238
|
-
type: 'tool_event',
|
|
239
|
-
timestamp: new Date().toISOString(),
|
|
240
|
-
tool_event: toolEvent,
|
|
241
|
-
});
|
|
235
|
+
this._log.push(buildToolEventLogEntry(toolEvent, new Date().toISOString()));
|
|
242
236
|
}
|
|
243
237
|
// Exit-code failures are no longer thrown here. The runConversation
|
|
244
238
|
// loop projects the partial-turn through `pushModelAgentMessage`
|
package/dist/sdk/chat.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildModelAgentResultLogEntry } from './agent-result-log.js';
|
|
2
2
|
import { getVisibleAssistantMessage } from './visible-turn.js';
|
|
3
|
+
import { buildToolEventLogEntry } from './tool-event-log.js';
|
|
3
4
|
export class ChatSessionImpl {
|
|
4
5
|
_turn;
|
|
5
6
|
_done = false;
|
|
@@ -51,24 +52,21 @@ export class ChatSessionImpl {
|
|
|
51
52
|
}
|
|
52
53
|
const response = getVisibleAssistantMessage(turnResult);
|
|
53
54
|
const durationMs = Date.now() - turnStart;
|
|
54
|
-
|
|
55
|
-
timestamp: timestamp(),
|
|
56
|
-
turnNumber,
|
|
57
|
-
durationMs,
|
|
58
|
-
turnResult,
|
|
59
|
-
assistantMessage: response,
|
|
60
|
-
}));
|
|
55
|
+
const turnCompletedAt = timestamp();
|
|
61
56
|
for (const toolEvent of turnResult.toolEvents) {
|
|
62
|
-
this.deps.log.push(
|
|
63
|
-
type: 'tool_event',
|
|
64
|
-
timestamp: timestamp(),
|
|
65
|
-
tool_event: toolEvent,
|
|
66
|
-
});
|
|
57
|
+
this.deps.log.push(buildToolEventLogEntry(toolEvent, turnCompletedAt));
|
|
67
58
|
this.deps.verbose?.toolEvent({
|
|
68
59
|
action: toolEvent.action,
|
|
69
60
|
summary: toolEvent.summary,
|
|
70
61
|
});
|
|
71
62
|
}
|
|
63
|
+
this.deps.log.push(buildModelAgentResultLogEntry({
|
|
64
|
+
timestamp: turnCompletedAt,
|
|
65
|
+
turnNumber,
|
|
66
|
+
durationMs,
|
|
67
|
+
turnResult,
|
|
68
|
+
assistantMessage: response,
|
|
69
|
+
}));
|
|
72
70
|
this.deps.messages.push({ role: 'agent', content: response });
|
|
73
71
|
this.deps.verbose?.turnEnd({
|
|
74
72
|
turn: turnNumber,
|
|
@@ -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,
|
package/dist/sdk/judge-tools.js
CHANGED
|
@@ -206,7 +206,28 @@ export async function getToolEvents(events, actionFilter) {
|
|
|
206
206
|
const filtered = actionFilter
|
|
207
207
|
? events.filter((e) => e.action.includes(actionFilter))
|
|
208
208
|
: events;
|
|
209
|
-
|
|
209
|
+
const serialized = JSON.stringify(filtered);
|
|
210
|
+
const maxChars = 200 * 1024;
|
|
211
|
+
if (serialized.length <= maxChars)
|
|
212
|
+
return serialized;
|
|
213
|
+
const included = [];
|
|
214
|
+
for (const event of filtered) {
|
|
215
|
+
const candidate = JSON.stringify({
|
|
216
|
+
events: [...included, event],
|
|
217
|
+
truncated: true,
|
|
218
|
+
totalEvents: filtered.length,
|
|
219
|
+
includedEvents: included.length + 1,
|
|
220
|
+
});
|
|
221
|
+
if (candidate.length > maxChars)
|
|
222
|
+
break;
|
|
223
|
+
included.push(event);
|
|
224
|
+
}
|
|
225
|
+
return JSON.stringify({
|
|
226
|
+
events: included,
|
|
227
|
+
truncated: true,
|
|
228
|
+
totalEvents: filtered.length,
|
|
229
|
+
includedEvents: included.length,
|
|
230
|
+
});
|
|
210
231
|
}
|
|
211
232
|
export const DEFAULT_TOOL_REGISTRY = new Map([
|
|
212
233
|
['readFile', {
|
|
@@ -269,7 +290,7 @@ export const DEFAULT_TOOL_REGISTRY = new Map([
|
|
|
269
290
|
['getToolEvents', {
|
|
270
291
|
schema: {
|
|
271
292
|
name: 'getToolEvents',
|
|
272
|
-
description: 'Retrieve the agent session tool events as JSON. Optional actionFilter substring-matches against event.action.',
|
|
293
|
+
description: 'Retrieve the agent session tool events as JSON. Optional actionFilter substring-matches against event.action. Oversized responses use a valid { events, truncated, totalEvents, includedEvents } JSON envelope.',
|
|
273
294
|
input_schema: {
|
|
274
295
|
type: 'object',
|
|
275
296
|
properties: { actionFilter: { type: 'string' } },
|
|
@@ -2,6 +2,7 @@ import { createAgentSession } from '../types.js';
|
|
|
2
2
|
import { createAgentEnvironment } from '../agents/registry.js';
|
|
3
3
|
import { withAbortTimeout } from '../utils/timeout.js';
|
|
4
4
|
import { buildModelAgentResultLogEntry } from './agent-result-log.js';
|
|
5
|
+
import { buildToolEventLogEntry } from './tool-event-log.js';
|
|
5
6
|
import { planRuntimePolicies } from './runtime-policy.js';
|
|
6
7
|
import { getVisibleAssistantMessage } from './visible-turn.js';
|
|
7
8
|
import { createAskBus } from './ask-bus/bus.js';
|
|
@@ -84,14 +85,15 @@ export function createManagedSession(deps) {
|
|
|
84
85
|
messages.push({ role: 'user', content: message });
|
|
85
86
|
const turnResult = await executeTurn(message);
|
|
86
87
|
const response = getVisibleAssistantMessage(turnResult);
|
|
88
|
+
const turnCompletedAt = new Date().toISOString();
|
|
89
|
+
for (const toolEvent of turnResult.toolEvents) {
|
|
90
|
+
log.push(buildToolEventLogEntry(toolEvent, turnCompletedAt));
|
|
91
|
+
}
|
|
87
92
|
log.push(buildModelAgentResultLogEntry({
|
|
88
|
-
timestamp:
|
|
93
|
+
timestamp: turnCompletedAt,
|
|
89
94
|
turnResult,
|
|
90
95
|
assistantMessage: response,
|
|
91
96
|
}));
|
|
92
|
-
for (const toolEvent of turnResult.toolEvents) {
|
|
93
|
-
log.push({ type: 'tool_event', timestamp: new Date().toISOString(), tool_event: toolEvent });
|
|
94
|
-
}
|
|
95
97
|
messages.push({ role: 'agent', content: response });
|
|
96
98
|
if (turnResult.exitCode !== 0) {
|
|
97
99
|
if (turnResult.timedOut)
|
package/dist/tool-events.d.ts
CHANGED
|
@@ -3,13 +3,32 @@ export interface ToolEvent {
|
|
|
3
3
|
action: ToolAction;
|
|
4
4
|
provider: 'claude' | 'codex' | 'cursor' | 'opencode';
|
|
5
5
|
providerToolName: string;
|
|
6
|
+
/** Provider correlation identifier joining a tool invocation to its result. */
|
|
7
|
+
toolUseId?: string;
|
|
6
8
|
turnNumber?: number;
|
|
7
9
|
arguments?: Record<string, unknown>;
|
|
10
|
+
/** Lifecycle state observed by PathGrade. Absent on legacy provider events. */
|
|
11
|
+
status?: 'completed' | 'error' | 'incomplete';
|
|
12
|
+
/** Local wall-clock receive time for the invocation boundary. */
|
|
13
|
+
startedAt?: string;
|
|
14
|
+
/** Local wall-clock receive time for the matching result boundary. */
|
|
15
|
+
completedAt?: string;
|
|
16
|
+
/** Monotonic receive-time delta between invocation and result. */
|
|
17
|
+
durationMs?: number;
|
|
18
|
+
/** Bounded provider result data. Fields remain absent when unavailable. */
|
|
19
|
+
result?: ToolEventResult;
|
|
8
20
|
summary: string;
|
|
9
21
|
confidence: 'high' | 'medium' | 'low';
|
|
10
22
|
rawSnippet: string;
|
|
11
23
|
skillName?: string;
|
|
12
24
|
}
|
|
25
|
+
export interface ToolEventResult {
|
|
26
|
+
content?: string;
|
|
27
|
+
stdout?: string;
|
|
28
|
+
stderr?: string;
|
|
29
|
+
exitCode?: number;
|
|
30
|
+
truncated?: boolean;
|
|
31
|
+
}
|
|
13
32
|
export declare function summarizeToolEvents(events: ToolEvent[]): string;
|
|
14
33
|
/**
|
|
15
34
|
* Map from provider-specific tool names to normalized Pathgrade actions.
|
|
@@ -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/dist/viewer.html
CHANGED
|
@@ -1127,10 +1127,10 @@
|
|
|
1127
1127
|
+ (e.value?.toFixed(2) || '0.00') + '</span>'
|
|
1128
1128
|
+ (e.output ? '<div class="scorer-details" style="margin-top:0.25rem">' + esc(e.output) + '</div>' : '');
|
|
1129
1129
|
break;
|
|
1130
|
-
case 'tool_event':
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1130
|
+
case 'tool_event': {
|
|
1131
|
+
const tool = e.tool_event || {}; const toolResult = tool.result || {}; const resultText = toolResult.content || toolResult.stdout || ''; const stderrText = toolResult.stderr || ''; const duration = typeof tool.durationMs === 'number' ? `${Math.round(tool.durationMs)}ms` : ''; const exit = typeof toolResult.exitCode === 'number' ? `exit ${toolResult.exitCode}` : ''; const meta = [tool.status || 'unknown', duration, exit].filter(Boolean).join(' · '); const output = resultText || stderrText ? '<div class="log-command-output"><pre class="code-block">' + esc(resultText) + (stderrText ? '<span class="stderr-text">' + esc(stderrText) + '</span>' : '') + '</pre></div>' : '';
|
|
1132
|
+
body = '<details class="log-command-details"><summary class="log-command-summary"><span class="badge badge-type">' + esc(tool.action || 'unknown') + '</span> <span class="scorer-details">' + esc(tool.summary || '') + '</span><span class="log-command-meta">' + esc(meta) + '</span></summary>' + output + '</details>'; break;
|
|
1133
|
+
}
|
|
1134
1134
|
case 'judge_tool_call': {
|
|
1135
1135
|
const tc = e.judge_tool_call || {};
|
|
1136
1136
|
const okBadge = tc.ok
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/pathgrade",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
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": "0bfcf787a0b4fe0c48683c6aa662dd0e3c9679800fdcc9811414571d"
|
|
136
136
|
}
|
|
File without changes
|