@wix/pathgrade 0.29.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/bin/pathgrade.js +2 -0
- package/dist/affected/anchor.d.ts +18 -0
- package/dist/affected/anchor.js +42 -0
- package/dist/affected/config.d.ts +25 -0
- package/dist/affected/config.js +98 -0
- package/dist/affected/format.d.ts +45 -0
- package/dist/affected/format.js +87 -0
- package/dist/affected/git.d.ts +33 -0
- package/dist/affected/git.js +68 -0
- package/dist/affected/glob.d.ts +17 -0
- package/dist/affected/glob.js +25 -0
- package/dist/affected/meta.d.ts +26 -0
- package/dist/affected/meta.js +158 -0
- package/dist/affected/select.d.ts +30 -0
- package/dist/affected/select.js +101 -0
- package/dist/affected/sidecar.d.ts +32 -0
- package/dist/affected/sidecar.js +86 -0
- package/dist/affected/types.d.ts +43 -0
- package/dist/affected/types.js +7 -0
- package/dist/agents/claude/ask-user-answer-store.d.ts +28 -0
- package/dist/agents/claude/ask-user-answer-store.js +29 -0
- package/dist/agents/claude/ask-user-bridge.d.ts +56 -0
- package/dist/agents/claude/ask-user-bridge.js +162 -0
- package/dist/agents/claude/sdk-message-projector.d.ts +43 -0
- package/dist/agents/claude/sdk-message-projector.js +208 -0
- package/dist/agents/claude/sdk-options.d.ts +52 -0
- package/dist/agents/claude/sdk-options.js +79 -0
- package/dist/agents/claude.d.ts +56 -0
- package/dist/agents/claude.js +143 -0
- package/dist/agents/codex-app-server/agent.d.ts +35 -0
- package/dist/agents/codex-app-server/agent.js +388 -0
- package/dist/agents/codex-app-server/fixtures/app-server-client.d.ts +19 -0
- package/dist/agents/codex-app-server/fixtures/app-server-client.js +104 -0
- package/dist/agents/codex-app-server/fixtures/run-gate.d.ts +25 -0
- package/dist/agents/codex-app-server/fixtures/run-gate.js +48 -0
- package/dist/agents/codex-app-server/protocol/ClientRequest.d.ts +27 -0
- package/dist/agents/codex-app-server/protocol/ClientRequest.js +7 -0
- package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.d.ts +9 -0
- package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.js +4 -0
- package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.d.ts +6 -0
- package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.js +7 -0
- package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.d.ts +23 -0
- package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.js +8 -0
- package/dist/agents/codex-app-server/protocol/Op.d.ts +11 -0
- package/dist/agents/codex-app-server/protocol/Op.js +5 -0
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.d.ts +10 -0
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.js +6 -0
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.d.ts +15 -0
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.js +4 -0
- package/dist/agents/codex-app-server/protocol/SandboxMode.d.ts +1 -0
- package/dist/agents/codex-app-server/protocol/SandboxMode.js +4 -0
- package/dist/agents/codex-app-server/protocol/ServerRequest.d.ts +42 -0
- package/dist/agents/codex-app-server/protocol/ServerRequest.js +7 -0
- package/dist/agents/codex-app-server/protocol/ThreadStartParams.d.ts +45 -0
- package/dist/agents/codex-app-server/protocol/ThreadStartParams.js +10 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.d.ts +6 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.js +4 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.d.ts +7 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.js +4 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.d.ts +10 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.js +4 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.d.ts +12 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.js +4 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.d.ts +9 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.js +4 -0
- package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.d.ts +5 -0
- package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.js +4 -0
- package/dist/agents/codex-app-server/protocol/index.d.ts +16 -0
- package/dist/agents/codex-app-server/protocol/index.js +11 -0
- package/dist/agents/codex-app-server/transport.d.ts +84 -0
- package/dist/agents/codex-app-server/transport.js +246 -0
- package/dist/agents/codex-app-server/wire-translators.d.ts +19 -0
- package/dist/agents/codex-app-server/wire-translators.js +52 -0
- package/dist/agents/codex.d.ts +7 -0
- package/dist/agents/codex.js +164 -0
- package/dist/agents/cursor.d.ts +39 -0
- package/dist/agents/cursor.js +233 -0
- package/dist/agents/registry.d.ts +17 -0
- package/dist/agents/registry.js +27 -0
- package/dist/agents/transcript-agent.d.ts +14 -0
- package/dist/agents/transcript-agent.js +59 -0
- package/dist/analytics/engine.d.ts +18 -0
- package/dist/analytics/engine.js +66 -0
- package/dist/commands/affected.d.ts +31 -0
- package/dist/commands/affected.js +150 -0
- package/dist/commands/analyze.d.ts +21 -0
- package/dist/commands/analyze.js +167 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +242 -0
- package/dist/commands/preview-reactions.d.ts +10 -0
- package/dist/commands/preview-reactions.js +88 -0
- package/dist/commands/preview.d.ts +5 -0
- package/dist/commands/preview.js +17 -0
- package/dist/commands/report.d.ts +39 -0
- package/dist/commands/report.js +108 -0
- package/dist/commands/run-args.d.ts +22 -0
- package/dist/commands/run-args.js +69 -0
- package/dist/commands/run-changed.d.ts +26 -0
- package/dist/commands/run-changed.js +138 -0
- package/dist/commands/validate.d.ts +13 -0
- package/dist/commands/validate.js +233 -0
- package/dist/core/mcp-mock.d.ts +3 -0
- package/dist/core/mcp-mock.js +23 -0
- package/dist/core/mcp-mock.types.d.ts +15 -0
- package/dist/core/mcp-mock.types.js +1 -0
- package/dist/core/skills.d.ts +15 -0
- package/dist/core/skills.js +76 -0
- package/dist/evals/discovery.d.ts +7 -0
- package/dist/evals/discovery.js +69 -0
- package/dist/mcp-mock-server.d.ts +1 -0
- package/dist/mcp-mock-server.js +116 -0
- package/dist/pathgrade.d.ts +10 -0
- package/dist/pathgrade.js +243 -0
- package/dist/plugin/index.d.ts +9 -0
- package/dist/plugin/index.js +81 -0
- package/dist/plugin/lifecycle.d.ts +45 -0
- package/dist/plugin/lifecycle.js +146 -0
- package/dist/plugin/reporter.d.ts +36 -0
- package/dist/plugin/reporter.js +275 -0
- package/dist/plugin/setup.d.ts +1 -0
- package/dist/plugin/setup.js +3 -0
- package/dist/providers/copy-filter.d.ts +18 -0
- package/dist/providers/copy-filter.js +66 -0
- package/dist/providers/credentials.d.ts +36 -0
- package/dist/providers/credentials.js +202 -0
- package/dist/providers/mcp-config.d.ts +34 -0
- package/dist/providers/mcp-config.js +55 -0
- package/dist/providers/sandbox-exec.d.ts +8 -0
- package/dist/providers/sandbox-exec.js +82 -0
- package/dist/providers/sandbox.d.ts +22 -0
- package/dist/providers/sandbox.js +158 -0
- package/dist/providers/sandboxed-claude-spawn.d.ts +24 -0
- package/dist/providers/sandboxed-claude-spawn.js +57 -0
- package/dist/providers/workspace.d.ts +15 -0
- package/dist/providers/workspace.js +73 -0
- package/dist/reporters/browser.d.ts +2 -0
- package/dist/reporters/browser.js +85 -0
- package/dist/reporters/cli.d.ts +2 -0
- package/dist/reporters/cli.js +131 -0
- package/dist/reporters/diagnostics.d.ts +46 -0
- package/dist/reporters/diagnostics.js +147 -0
- package/dist/reporters/github-comment.d.ts +67 -0
- package/dist/reporters/github-comment.js +252 -0
- package/dist/reporters/loader.d.ts +8 -0
- package/dist/reporters/loader.js +36 -0
- package/dist/reporters/results-path.d.ts +2 -0
- package/dist/reporters/results-path.js +10 -0
- package/dist/reporters/verbose-emitter.d.ts +69 -0
- package/dist/reporters/verbose-emitter.js +103 -0
- package/dist/sdk/agent-crash.d.ts +19 -0
- package/dist/sdk/agent-crash.js +20 -0
- package/dist/sdk/agent-resolution.d.ts +12 -0
- package/dist/sdk/agent-resolution.js +21 -0
- package/dist/sdk/agent-result-log.d.ts +19 -0
- package/dist/sdk/agent-result-log.js +45 -0
- package/dist/sdk/agent.d.ts +4 -0
- package/dist/sdk/agent.js +354 -0
- package/dist/sdk/ask-bus/bus.d.ts +21 -0
- package/dist/sdk/ask-bus/bus.js +129 -0
- package/dist/sdk/ask-bus/handler.d.ts +31 -0
- package/dist/sdk/ask-bus/handler.js +244 -0
- package/dist/sdk/ask-bus/parsers.d.ts +9 -0
- package/dist/sdk/ask-bus/parsers.js +30 -0
- package/dist/sdk/ask-bus/projection.d.ts +26 -0
- package/dist/sdk/ask-bus/projection.js +45 -0
- package/dist/sdk/ask-bus/types.d.ts +69 -0
- package/dist/sdk/ask-bus/types.js +1 -0
- package/dist/sdk/chat.d.ts +29 -0
- package/dist/sdk/chat.js +93 -0
- package/dist/sdk/conversation-window.d.ts +14 -0
- package/dist/sdk/conversation-window.js +38 -0
- package/dist/sdk/converse.d.ts +42 -0
- package/dist/sdk/converse.js +329 -0
- package/dist/sdk/eval-runtime.d.ts +10 -0
- package/dist/sdk/eval-runtime.js +17 -0
- package/dist/sdk/evaluate.d.ts +13 -0
- package/dist/sdk/evaluate.js +325 -0
- package/dist/sdk/index.d.ts +31 -0
- package/dist/sdk/index.js +20 -0
- package/dist/sdk/judge-pipeline.d.ts +7 -0
- package/dist/sdk/judge-pipeline.js +178 -0
- package/dist/sdk/judge-prompt-builder.d.ts +7 -0
- package/dist/sdk/judge-prompt-builder.js +76 -0
- package/dist/sdk/judge-tool-runner.d.ts +19 -0
- package/dist/sdk/judge-tool-runner.js +51 -0
- package/dist/sdk/judge-tool-session.d.ts +47 -0
- package/dist/sdk/judge-tool-session.js +213 -0
- package/dist/sdk/judge-tools.d.ts +29 -0
- package/dist/sdk/judge-tools.js +283 -0
- package/dist/sdk/managed-session.d.ts +50 -0
- package/dist/sdk/managed-session.js +110 -0
- package/dist/sdk/persona.d.ts +5 -0
- package/dist/sdk/persona.js +33 -0
- package/dist/sdk/reaction-loader.d.ts +4 -0
- package/dist/sdk/reaction-loader.js +119 -0
- package/dist/sdk/reaction-preview.d.ts +6 -0
- package/dist/sdk/reaction-preview.js +94 -0
- package/dist/sdk/run-scorer.d.ts +10 -0
- package/dist/sdk/run-scorer.js +124 -0
- package/dist/sdk/runtime-policy.d.ts +21 -0
- package/dist/sdk/runtime-policy.js +89 -0
- package/dist/sdk/scorer-utils.d.ts +6 -0
- package/dist/sdk/scorer-utils.js +39 -0
- package/dist/sdk/scorers.d.ts +64 -0
- package/dist/sdk/scorers.js +80 -0
- package/dist/sdk/snapshots.d.ts +41 -0
- package/dist/sdk/snapshots.js +148 -0
- package/dist/sdk/types.d.ts +416 -0
- package/dist/sdk/types.js +17 -0
- package/dist/sdk/visible-turn.d.ts +4 -0
- package/dist/sdk/visible-turn.js +23 -0
- package/dist/tool-events.d.ts +45 -0
- package/dist/tool-events.js +167 -0
- package/dist/types.d.ts +372 -0
- package/dist/types.js +37 -0
- package/dist/utils/cli.d.ts +55 -0
- package/dist/utils/cli.js +131 -0
- package/dist/utils/env.d.ts +8 -0
- package/dist/utils/env.js +26 -0
- package/dist/utils/llm-mocks.d.ts +43 -0
- package/dist/utils/llm-mocks.js +108 -0
- package/dist/utils/llm-providers/anthropic.d.ts +2 -0
- package/dist/utils/llm-providers/anthropic.js +132 -0
- package/dist/utils/llm-providers/cli.d.ts +20 -0
- package/dist/utils/llm-providers/cli.js +162 -0
- package/dist/utils/llm-providers/openai.d.ts +2 -0
- package/dist/utils/llm-providers/openai.js +56 -0
- package/dist/utils/llm-types.d.ts +107 -0
- package/dist/utils/llm-types.js +1 -0
- package/dist/utils/llm.d.ts +36 -0
- package/dist/utils/llm.js +209 -0
- package/dist/utils/shutdown.d.ts +15 -0
- package/dist/utils/shutdown.js +41 -0
- package/dist/utils/timeout.d.ts +8 -0
- package/dist/utils/timeout.js +32 -0
- package/dist/viewer.html +1178 -0
- package/package.json +88 -0
- package/templates/eval.ts.template +28 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createAgentSession } from '../types.js';
|
|
2
|
+
import { createAgentEnvironment } from '../agents/registry.js';
|
|
3
|
+
import { withAbortTimeout } from '../utils/timeout.js';
|
|
4
|
+
import { buildModelAgentResultLogEntry } from './agent-result-log.js';
|
|
5
|
+
import { planRuntimePolicies } from './runtime-policy.js';
|
|
6
|
+
import { getVisibleAssistantMessage } from './visible-turn.js';
|
|
7
|
+
import { createAskBus } from './ask-bus/bus.js';
|
|
8
|
+
export function createManagedSession(deps) {
|
|
9
|
+
const { ws, agentName, timeoutSec, messages, log, model, conversationWindow, llm } = deps;
|
|
10
|
+
const agentTimeoutMs = timeoutSec * 1000;
|
|
11
|
+
const deadlineMs = Date.now() + agentTimeoutMs;
|
|
12
|
+
const label = `Agent (limit: ${timeoutSec}s)`;
|
|
13
|
+
const transport = deps.transport;
|
|
14
|
+
const agent = createAgentEnvironment(agentName, transport);
|
|
15
|
+
const runtimePolicies = planRuntimePolicies(agentName, transport);
|
|
16
|
+
const askBus = deps.askBus
|
|
17
|
+
?? createAskBus({ askUserTimeoutMs: deps.askUserTimeoutMs ?? 30_000 });
|
|
18
|
+
const sessionOptions = {
|
|
19
|
+
...(ws.mcpConfigPath ? { mcpConfigPath: ws.mcpConfigPath } : {}),
|
|
20
|
+
...(model ? { model } : {}),
|
|
21
|
+
...(conversationWindow !== undefined ? { conversationWindow } : {}),
|
|
22
|
+
...(runtimePolicies.length > 0 ? { runtimePolicies } : {}),
|
|
23
|
+
...(llm ? { llm } : {}),
|
|
24
|
+
askBus,
|
|
25
|
+
...(transport !== undefined ? { transport } : {}),
|
|
26
|
+
};
|
|
27
|
+
let session = null;
|
|
28
|
+
let setupDone = false;
|
|
29
|
+
let currentSignal;
|
|
30
|
+
const runCommand = async (cmd) => {
|
|
31
|
+
const result = await ws.exec(cmd, { signal: currentSignal });
|
|
32
|
+
log.push({
|
|
33
|
+
type: 'command',
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
command: cmd,
|
|
36
|
+
stdout: result.stdout,
|
|
37
|
+
stderr: result.stderr,
|
|
38
|
+
exitCode: result.exitCode,
|
|
39
|
+
});
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
const executeTurn = async (message) => {
|
|
43
|
+
const remaining = deadlineMs - Date.now();
|
|
44
|
+
if (remaining <= 0)
|
|
45
|
+
throw new Error(`${label} timed out`);
|
|
46
|
+
return withAbortTimeout(async (signal) => {
|
|
47
|
+
currentSignal = signal;
|
|
48
|
+
if (!session) {
|
|
49
|
+
// Run auth setup commands before first agent turn
|
|
50
|
+
if (!setupDone) {
|
|
51
|
+
for (const cmd of ws.setupCommands) {
|
|
52
|
+
await ws.exec(cmd);
|
|
53
|
+
}
|
|
54
|
+
setupDone = true;
|
|
55
|
+
}
|
|
56
|
+
// Carry the workspace's resolved env (from `prepareWorkspace
|
|
57
|
+
// → resolveCredentials`) into the runtime handle so drivers
|
|
58
|
+
// that auth through `Options.env` (notably the Claude SDK
|
|
59
|
+
// driver) can lift Anthropic keys out. Drivers that only
|
|
60
|
+
// need the workspace path keep using `getWorkspacePath`.
|
|
61
|
+
const runtime = {
|
|
62
|
+
handle: ws.path,
|
|
63
|
+
workspacePath: ws.path,
|
|
64
|
+
env: ws.env,
|
|
65
|
+
};
|
|
66
|
+
session = await createAgentSession(agent, runtime, runCommand, sessionOptions);
|
|
67
|
+
return session.start({ message });
|
|
68
|
+
}
|
|
69
|
+
return session.reply({ message });
|
|
70
|
+
}, remaining, label);
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
async send(message) {
|
|
74
|
+
const isFirst = messages.length === 0;
|
|
75
|
+
log.push({
|
|
76
|
+
type: isFirst ? 'agent_start' : 'user_reply',
|
|
77
|
+
timestamp: new Date().toISOString(),
|
|
78
|
+
instruction: message,
|
|
79
|
+
});
|
|
80
|
+
messages.push({ role: 'user', content: message });
|
|
81
|
+
const turnResult = await executeTurn(message);
|
|
82
|
+
const response = getVisibleAssistantMessage(turnResult);
|
|
83
|
+
log.push(buildModelAgentResultLogEntry({
|
|
84
|
+
timestamp: new Date().toISOString(),
|
|
85
|
+
turnResult,
|
|
86
|
+
assistantMessage: response,
|
|
87
|
+
}));
|
|
88
|
+
for (const toolEvent of turnResult.toolEvents) {
|
|
89
|
+
log.push({ type: 'tool_event', timestamp: new Date().toISOString(), tool_event: toolEvent });
|
|
90
|
+
}
|
|
91
|
+
messages.push({ role: 'agent', content: response });
|
|
92
|
+
if (turnResult.exitCode !== 0) {
|
|
93
|
+
if (turnResult.timedOut)
|
|
94
|
+
throw new Error(`${label} timed out (agent killed)`);
|
|
95
|
+
throw new Error(`Agent exited with code ${turnResult.exitCode}`);
|
|
96
|
+
}
|
|
97
|
+
return response;
|
|
98
|
+
},
|
|
99
|
+
executeTurn,
|
|
100
|
+
remainingMs() {
|
|
101
|
+
return Math.max(0, deadlineMs - Date.now());
|
|
102
|
+
},
|
|
103
|
+
async dispose() {
|
|
104
|
+
if (!session)
|
|
105
|
+
return;
|
|
106
|
+
await session.dispose?.();
|
|
107
|
+
},
|
|
108
|
+
askBus,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createConversationWindow } from './conversation-window.js';
|
|
2
|
+
export function createPersona(config) {
|
|
3
|
+
const llm = config.llm;
|
|
4
|
+
const window = config.conversationWindow !== false
|
|
5
|
+
? createConversationWindow({ ...config.conversationWindow, model: config.model, llm })
|
|
6
|
+
: null;
|
|
7
|
+
return {
|
|
8
|
+
async reply(chat) {
|
|
9
|
+
const prompt = await buildPrompt(config, chat, window);
|
|
10
|
+
const response = await llm.call(prompt, { model: config.model });
|
|
11
|
+
return response.text.trim();
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function formatMessages(messages) {
|
|
16
|
+
return messages
|
|
17
|
+
.map(m => `${m.role === 'user' ? 'User' : 'Agent'}: ${m.content}`)
|
|
18
|
+
.join('\n\n');
|
|
19
|
+
}
|
|
20
|
+
async function buildPrompt(config, chat, window) {
|
|
21
|
+
const sections = [];
|
|
22
|
+
sections.push(`## Persona\n${config.description}`);
|
|
23
|
+
if (config.facts.length > 0) {
|
|
24
|
+
const factsList = config.facts.map(f => `- ${f}`).join('\n');
|
|
25
|
+
sections.push(`## Facts\n${factsList}`);
|
|
26
|
+
}
|
|
27
|
+
const history = window
|
|
28
|
+
? await window.getHistory(chat.messages)
|
|
29
|
+
: formatMessages(chat.messages);
|
|
30
|
+
sections.push(`## Conversation History\n${history}`);
|
|
31
|
+
sections.push('Reply in character as the persona described above. Respond to the agent\'s latest message naturally and concisely.');
|
|
32
|
+
return sections.join('\n\n');
|
|
33
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AskUserOption, AskUserQuestion, AskUserReaction, Message, Reaction, TextReaction } from './types.js';
|
|
2
|
+
export declare function loadReactionSnapshotMessages(snapshotPath: string): Promise<Message[]>;
|
|
3
|
+
export declare function loadReactionsFromFile(filePath: string): Promise<Reaction[]>;
|
|
4
|
+
export type { AskUserOption, AskUserQuestion, AskUserReaction, TextReaction };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import createJiti from 'jiti';
|
|
4
|
+
function isMessage(value) {
|
|
5
|
+
if (!value || typeof value !== 'object')
|
|
6
|
+
return false;
|
|
7
|
+
const candidate = value;
|
|
8
|
+
return (candidate.role === 'user' || candidate.role === 'agent') && typeof candidate.content === 'string';
|
|
9
|
+
}
|
|
10
|
+
function compilePattern(pattern, fieldName, source) {
|
|
11
|
+
try {
|
|
12
|
+
return new RegExp(pattern);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
16
|
+
throw new Error(`Invalid ${fieldName} pattern in ${source}: ${detail}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function toTextReaction(value, source) {
|
|
20
|
+
if (!(value.when instanceof RegExp) && typeof value.when !== 'string') {
|
|
21
|
+
throw new Error(`Text reaction entry in ${source} must define a RegExp or string "when" pattern.`);
|
|
22
|
+
}
|
|
23
|
+
if (typeof value.reply !== 'string') {
|
|
24
|
+
throw new Error(`Text reaction entry in ${source} must define a string "reply".`);
|
|
25
|
+
}
|
|
26
|
+
if (value.unless !== undefined && !(value.unless instanceof RegExp) && typeof value.unless !== 'string') {
|
|
27
|
+
throw new Error(`Text reaction entry in ${source} must define a RegExp or string "unless" pattern when present.`);
|
|
28
|
+
}
|
|
29
|
+
if (value.once !== undefined && typeof value.once !== 'boolean') {
|
|
30
|
+
throw new Error(`Text reaction entry in ${source} must define a boolean "once" flag when present.`);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
when: value.when instanceof RegExp ? value.when : compilePattern(value.when, 'when', source),
|
|
34
|
+
...(value.unless === undefined
|
|
35
|
+
? {}
|
|
36
|
+
: {
|
|
37
|
+
unless: value.unless instanceof RegExp
|
|
38
|
+
? value.unless
|
|
39
|
+
: compilePattern(value.unless, 'unless', source),
|
|
40
|
+
}),
|
|
41
|
+
reply: value.reply,
|
|
42
|
+
...(value.once === undefined ? {} : { once: value.once }),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function toAskUserReaction(value, source) {
|
|
46
|
+
const whenAsked = value.whenAsked;
|
|
47
|
+
const answer = value.answer;
|
|
48
|
+
if (!(whenAsked instanceof RegExp) && typeof whenAsked !== 'string' && typeof whenAsked !== 'function') {
|
|
49
|
+
throw new Error(`Ask-user reaction entry in ${source} must define a RegExp, string, or function "whenAsked".`);
|
|
50
|
+
}
|
|
51
|
+
if (typeof answer !== 'string' && !Array.isArray(answer) && typeof answer !== 'function') {
|
|
52
|
+
throw new Error(`Ask-user reaction entry in ${source} must define a string, string[], or function "answer".`);
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(answer) && !answer.every((x) => typeof x === 'string')) {
|
|
55
|
+
throw new Error(`Ask-user reaction entry in ${source} "answer" array must contain only strings.`);
|
|
56
|
+
}
|
|
57
|
+
if (value.once !== undefined && typeof value.once !== 'boolean') {
|
|
58
|
+
throw new Error(`Ask-user reaction entry in ${source} must define a boolean "once" flag when present.`);
|
|
59
|
+
}
|
|
60
|
+
const compiledWhenAsked = whenAsked instanceof RegExp
|
|
61
|
+
? whenAsked
|
|
62
|
+
: typeof whenAsked === 'function'
|
|
63
|
+
? whenAsked
|
|
64
|
+
: compilePattern(whenAsked, 'whenAsked', source);
|
|
65
|
+
return {
|
|
66
|
+
whenAsked: compiledWhenAsked,
|
|
67
|
+
answer: answer,
|
|
68
|
+
...(value.once === undefined ? {} : { once: value.once }),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function toReaction(value, source) {
|
|
72
|
+
if (!value || typeof value !== 'object') {
|
|
73
|
+
throw new Error(`Reaction entry in ${source} must be an object.`);
|
|
74
|
+
}
|
|
75
|
+
const reaction = value;
|
|
76
|
+
const hasWhen = reaction.when !== undefined;
|
|
77
|
+
const hasWhenAsked = reaction.whenAsked !== undefined;
|
|
78
|
+
if (hasWhen && hasWhenAsked) {
|
|
79
|
+
throw new Error(`Reaction entry in ${source} must not define both "when" and "whenAsked".`);
|
|
80
|
+
}
|
|
81
|
+
if (hasWhenAsked) {
|
|
82
|
+
return toAskUserReaction(reaction, source);
|
|
83
|
+
}
|
|
84
|
+
return toTextReaction(reaction, source);
|
|
85
|
+
}
|
|
86
|
+
function extractReactionList(moduleExports, source) {
|
|
87
|
+
if (Array.isArray(moduleExports))
|
|
88
|
+
return moduleExports;
|
|
89
|
+
if (moduleExports && typeof moduleExports === 'object') {
|
|
90
|
+
const record = moduleExports;
|
|
91
|
+
if (Array.isArray(record.default))
|
|
92
|
+
return record.default;
|
|
93
|
+
if (Array.isArray(record.reactions))
|
|
94
|
+
return record.reactions;
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`Reaction file ${source} must export an array directly, as a default export, or as a named "reactions" export.`);
|
|
97
|
+
}
|
|
98
|
+
export async function loadReactionSnapshotMessages(snapshotPath) {
|
|
99
|
+
const resolvedPath = path.resolve(snapshotPath);
|
|
100
|
+
const snapshot = await fs.readJson(resolvedPath);
|
|
101
|
+
if (!Array.isArray(snapshot.messages) || !snapshot.messages.every(isMessage)) {
|
|
102
|
+
throw new Error(`Snapshot ${resolvedPath} must contain a valid messages array.`);
|
|
103
|
+
}
|
|
104
|
+
return snapshot.messages;
|
|
105
|
+
}
|
|
106
|
+
export async function loadReactionsFromFile(filePath) {
|
|
107
|
+
const resolvedPath = path.resolve(filePath);
|
|
108
|
+
const extension = path.extname(resolvedPath).toLowerCase();
|
|
109
|
+
if (extension === '.json') {
|
|
110
|
+
const json = await fs.readJson(resolvedPath);
|
|
111
|
+
if (!Array.isArray(json)) {
|
|
112
|
+
throw new Error(`Reaction JSON ${resolvedPath} must contain an array.`);
|
|
113
|
+
}
|
|
114
|
+
return json.map((entry, index) => toReaction(entry, `${resolvedPath}#${index}`));
|
|
115
|
+
}
|
|
116
|
+
const jiti = createJiti(__filename, { moduleCache: false, interopDefault: false });
|
|
117
|
+
const loaded = jiti(resolvedPath);
|
|
118
|
+
return extractReactionList(loaded, resolvedPath).map((entry, index) => toReaction(entry, `${resolvedPath}#${index}`));
|
|
119
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AskUserReaction, Message, Reaction, ReactionPreviewEntry, ReactionPreviewResult, TextReaction } from './types.js';
|
|
2
|
+
export declare function isAskUserReaction(reaction: Reaction): reaction is AskUserReaction;
|
|
3
|
+
export declare function isTextReaction(reaction: Reaction): reaction is TextReaction;
|
|
4
|
+
export declare function inspectReactions(agentMessage: string, reactions: Reaction[], firedOnce: Set<number>): ReactionPreviewEntry[];
|
|
5
|
+
export declare function pickReactionReply(agentMessage: string, reactions: Reaction[], firedOnce: Set<number>): string | null;
|
|
6
|
+
export declare function previewReactions(messages: Message[], reactions: Reaction[]): ReactionPreviewResult;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
function testRegExp(pattern, input) {
|
|
2
|
+
const previousLastIndex = pattern.lastIndex;
|
|
3
|
+
try {
|
|
4
|
+
pattern.lastIndex = 0;
|
|
5
|
+
return pattern.test(input);
|
|
6
|
+
}
|
|
7
|
+
finally {
|
|
8
|
+
pattern.lastIndex = previousLastIndex;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function testUnless(pattern, input) {
|
|
12
|
+
if (!pattern)
|
|
13
|
+
return false;
|
|
14
|
+
try {
|
|
15
|
+
return testRegExp(pattern, input);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
19
|
+
console.warn(`Reaction unless check failed: ${detail}`);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function isAskUserReaction(reaction) {
|
|
24
|
+
return 'whenAsked' in reaction;
|
|
25
|
+
}
|
|
26
|
+
export function isTextReaction(reaction) {
|
|
27
|
+
return 'when' in reaction;
|
|
28
|
+
}
|
|
29
|
+
export function inspectReactions(agentMessage, reactions, firedOnce) {
|
|
30
|
+
let firedThisTurn = false;
|
|
31
|
+
return reactions.map((reaction, reactionIndex) => {
|
|
32
|
+
if (isAskUserReaction(reaction)) {
|
|
33
|
+
// Ask-user reactions are never evaluated against assistant free text.
|
|
34
|
+
// They fire only on structured ask_user handshake turns (consumer in slice #4/#6).
|
|
35
|
+
return {
|
|
36
|
+
kind: 'ask_user',
|
|
37
|
+
reactionIndex,
|
|
38
|
+
whenAskedMatched: false,
|
|
39
|
+
fired: false,
|
|
40
|
+
status: 'no-match',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (reaction.once && firedOnce.has(reactionIndex)) {
|
|
44
|
+
return {
|
|
45
|
+
kind: 'text',
|
|
46
|
+
reactionIndex,
|
|
47
|
+
whenMatched: false,
|
|
48
|
+
unlessMatched: false,
|
|
49
|
+
fired: false,
|
|
50
|
+
status: 'no-match',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const whenMatched = testRegExp(reaction.when, agentMessage);
|
|
54
|
+
const unlessMatched = whenMatched ? testUnless(reaction.unless, agentMessage) : false;
|
|
55
|
+
const eligible = whenMatched && !unlessMatched;
|
|
56
|
+
const fired = eligible && !firedThisTurn;
|
|
57
|
+
if (fired && reaction.once) {
|
|
58
|
+
firedOnce.add(reactionIndex);
|
|
59
|
+
}
|
|
60
|
+
if (fired) {
|
|
61
|
+
firedThisTurn = true;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
kind: 'text',
|
|
65
|
+
reactionIndex,
|
|
66
|
+
whenMatched,
|
|
67
|
+
unlessMatched,
|
|
68
|
+
fired,
|
|
69
|
+
status: fired ? 'fired' : (unlessMatched ? 'vetoed' : 'no-match'),
|
|
70
|
+
...(fired ? { reply: reaction.reply } : {}),
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export function pickReactionReply(agentMessage, reactions, firedOnce) {
|
|
75
|
+
const evaluations = inspectReactions(agentMessage, reactions, firedOnce);
|
|
76
|
+
const match = evaluations.find((evaluation) => evaluation.fired && evaluation.kind === 'text');
|
|
77
|
+
return match && match.kind === 'text' ? match.reply ?? null : null;
|
|
78
|
+
}
|
|
79
|
+
export function previewReactions(messages, reactions) {
|
|
80
|
+
const firedOnce = new Set();
|
|
81
|
+
const turns = [];
|
|
82
|
+
let turn = 0;
|
|
83
|
+
for (const message of messages) {
|
|
84
|
+
if (message.role !== 'agent')
|
|
85
|
+
continue;
|
|
86
|
+
turn++;
|
|
87
|
+
turns.push({
|
|
88
|
+
turn,
|
|
89
|
+
agentMessage: message.content,
|
|
90
|
+
reactions: inspectReactions(message.content, reactions, firedOnce),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return { turns };
|
|
94
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LLMPort } from '../utils/llm-types.js';
|
|
2
|
+
import type { CheckScorer, Scorer, ScoreScorer, ScorerContext, ScorerResultEntry, ToolUsageScorer } from './types.js';
|
|
3
|
+
export interface RunScorerOptions {
|
|
4
|
+
/** Required for `judge` scorers; ignored for other types. */
|
|
5
|
+
llm?: LLMPort;
|
|
6
|
+
}
|
|
7
|
+
export declare function runScorer(scorer: Scorer, ctx: ScorerContext, opts?: RunScorerOptions): Promise<ScorerResultEntry>;
|
|
8
|
+
export declare function runCheckScorer(scorer: CheckScorer, ctx: ScorerContext): Promise<ScorerResultEntry>;
|
|
9
|
+
export declare function runScoreScorer(scorer: ScoreScorer, ctx: ScorerContext): Promise<ScorerResultEntry>;
|
|
10
|
+
export declare function runToolUsageScorer(scorer: ToolUsageScorer, ctx: ScorerContext): Promise<ScorerResultEntry>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { runJudgePipeline } from './judge-pipeline.js';
|
|
2
|
+
import { runJudgeSession } from './judge-tool-session.js';
|
|
3
|
+
import { clamp, makeErroredResult, matchesExpectation } from './scorer-utils.js';
|
|
4
|
+
export async function runScorer(scorer, ctx, opts = {}) {
|
|
5
|
+
switch (scorer.type) {
|
|
6
|
+
case 'check':
|
|
7
|
+
return runCheckScorer(scorer, ctx);
|
|
8
|
+
case 'score':
|
|
9
|
+
return runScoreScorer(scorer, ctx);
|
|
10
|
+
case 'tool_usage':
|
|
11
|
+
return runToolUsageScorer(scorer, ctx);
|
|
12
|
+
case 'judge': {
|
|
13
|
+
if (!opts.llm) {
|
|
14
|
+
throw new Error('runScorer() requires opts.llm for judge scorers. Pass an LLM provider: runScorer(scorer, ctx, { llm: anthropicProvider })');
|
|
15
|
+
}
|
|
16
|
+
if (scorer.tools && scorer.tools.length > 0) {
|
|
17
|
+
const outcome = await runJudgeSession({ scorer, ctx, llm: opts.llm }, { emitLogs: false });
|
|
18
|
+
return buildEntryFromOutcome(scorer, outcome);
|
|
19
|
+
}
|
|
20
|
+
const [entry] = await runJudgePipeline(scorer, ctx, { llm: opts.llm });
|
|
21
|
+
return entry;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function buildEntryFromOutcome(scorer, outcome) {
|
|
26
|
+
if (outcome.code === null) {
|
|
27
|
+
return {
|
|
28
|
+
name: scorer.name,
|
|
29
|
+
type: 'judge',
|
|
30
|
+
score: outcome.score,
|
|
31
|
+
weight: scorer.weight,
|
|
32
|
+
details: outcome.details,
|
|
33
|
+
status: 'ok',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
name: scorer.name,
|
|
38
|
+
type: 'judge',
|
|
39
|
+
score: 0,
|
|
40
|
+
weight: scorer.weight,
|
|
41
|
+
details: outcome.details,
|
|
42
|
+
status: 'error',
|
|
43
|
+
errorCode: outcome.code,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export async function runCheckScorer(scorer, ctx) {
|
|
47
|
+
try {
|
|
48
|
+
const passed = await scorer.fn(ctx);
|
|
49
|
+
return {
|
|
50
|
+
name: scorer.name,
|
|
51
|
+
type: 'check',
|
|
52
|
+
score: passed ? 1.0 : 0.0,
|
|
53
|
+
weight: scorer.weight,
|
|
54
|
+
details: passed ? 'passed' : 'failed',
|
|
55
|
+
status: 'ok',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return makeErroredResult('check', scorer.name, scorer.weight, error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export async function runScoreScorer(scorer, ctx) {
|
|
63
|
+
try {
|
|
64
|
+
const raw = await scorer.fn(ctx);
|
|
65
|
+
if (typeof raw === 'number') {
|
|
66
|
+
return {
|
|
67
|
+
name: scorer.name,
|
|
68
|
+
type: 'score',
|
|
69
|
+
score: clamp(raw),
|
|
70
|
+
weight: scorer.weight,
|
|
71
|
+
status: 'ok',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
name: scorer.name,
|
|
76
|
+
type: 'score',
|
|
77
|
+
score: clamp(raw.score),
|
|
78
|
+
weight: scorer.weight,
|
|
79
|
+
details: raw.details,
|
|
80
|
+
status: 'ok',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return makeErroredResult('score', scorer.name, scorer.weight, error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export async function runToolUsageScorer(scorer, ctx) {
|
|
88
|
+
try {
|
|
89
|
+
const toolEvents = ctx.toolEvents;
|
|
90
|
+
if (toolEvents.length === 0) {
|
|
91
|
+
return {
|
|
92
|
+
name: scorer.name,
|
|
93
|
+
type: 'tool_usage',
|
|
94
|
+
score: 0,
|
|
95
|
+
weight: scorer.weight,
|
|
96
|
+
details: 'No tool events captured',
|
|
97
|
+
status: 'ok',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const checks = scorer.expectations.map((expectation) => {
|
|
101
|
+
const matches = toolEvents.filter((event) => matchesExpectation(event, expectation));
|
|
102
|
+
const passed = matches.length >= (expectation.min ?? 1)
|
|
103
|
+
&& (expectation.max === undefined || matches.length <= expectation.max);
|
|
104
|
+
return {
|
|
105
|
+
passed,
|
|
106
|
+
weight: expectation.weight ?? 1,
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
const totalWeight = checks.reduce((sum, c) => sum + c.weight, 0);
|
|
110
|
+
const earnedWeight = checks.filter((c) => c.passed).reduce((sum, c) => sum + c.weight, 0);
|
|
111
|
+
const score = totalWeight === 0 ? 0 : earnedWeight / totalWeight;
|
|
112
|
+
return {
|
|
113
|
+
name: scorer.name,
|
|
114
|
+
type: 'tool_usage',
|
|
115
|
+
score,
|
|
116
|
+
weight: scorer.weight,
|
|
117
|
+
details: `${earnedWeight}/${totalWeight} expectation weight passed`,
|
|
118
|
+
status: 'ok',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
return makeErroredResult('tool_usage', scorer.name, scorer.weight, error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AgentName, AgentTransport } from './types.js';
|
|
2
|
+
import type { AgentTurnResult, LogEntry } from '../types.js';
|
|
3
|
+
export interface RuntimePolicyDescriptor {
|
|
4
|
+
id: string;
|
|
5
|
+
version: string;
|
|
6
|
+
}
|
|
7
|
+
export interface RuntimePolicyRenderContext {
|
|
8
|
+
agent: AgentName;
|
|
9
|
+
}
|
|
10
|
+
export interface RuntimePolicyAuditEntry {
|
|
11
|
+
id: string;
|
|
12
|
+
version: string;
|
|
13
|
+
turns: number[];
|
|
14
|
+
}
|
|
15
|
+
export declare const NONINTERACTIVE_RUNTIME_POLICY: RuntimePolicyDescriptor;
|
|
16
|
+
export declare function planRuntimePolicies(agent: AgentName, transport?: AgentTransport): RuntimePolicyDescriptor[];
|
|
17
|
+
export declare function renderRuntimePolicy(policy: RuntimePolicyDescriptor, context: RuntimePolicyRenderContext): string;
|
|
18
|
+
export declare function renderRuntimePolicies(policies: RuntimePolicyDescriptor[], context: RuntimePolicyRenderContext): string;
|
|
19
|
+
export declare function prependRuntimePolicies(instruction: string, policies: RuntimePolicyDescriptor[], context: RuntimePolicyRenderContext): string;
|
|
20
|
+
export declare function getRuntimePolicyLogMetadata(turnResult: AgentTurnResult): Record<string, unknown>;
|
|
21
|
+
export declare function extractRuntimePolicyAuditEntries(log: LogEntry[]): RuntimePolicyAuditEntry[];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { getAgentCapabilities } from './types.js';
|
|
2
|
+
export const NONINTERACTIVE_RUNTIME_POLICY = {
|
|
3
|
+
id: 'noninteractive-user-question',
|
|
4
|
+
version: '1',
|
|
5
|
+
};
|
|
6
|
+
const INTERACTIVE_TOOL_NAMES = {
|
|
7
|
+
claude: 'AskUserQuestion',
|
|
8
|
+
codex: 'request_user_input',
|
|
9
|
+
cursor: 'AskQuestion',
|
|
10
|
+
};
|
|
11
|
+
export function planRuntimePolicies(agent, transport) {
|
|
12
|
+
const capabilities = getAgentCapabilities(agent, transport);
|
|
13
|
+
if (!capabilities) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
return capabilities.interactiveQuestionTransport === 'noninteractive'
|
|
17
|
+
? [NONINTERACTIVE_RUNTIME_POLICY]
|
|
18
|
+
: [];
|
|
19
|
+
}
|
|
20
|
+
export function renderRuntimePolicy(policy, context) {
|
|
21
|
+
if (policy.id !== NONINTERACTIVE_RUNTIME_POLICY.id) {
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
const toolName = INTERACTIVE_TOOL_NAMES[context.agent];
|
|
25
|
+
return [
|
|
26
|
+
'Runtime policy: non-interactive approval checkpoints',
|
|
27
|
+
`If the structured user-question tool ${toolName} cannot execute, treat that event as a blocked checkpoint.`,
|
|
28
|
+
'You must not retry the same checkpoint with paraphrases or formatting variants.',
|
|
29
|
+
'You must not infer approval or rejection from earlier user intent.',
|
|
30
|
+
'You must not continue past the blocked checkpoint.',
|
|
31
|
+
'Ask only the earliest outstanding approval checkpoint.',
|
|
32
|
+
].join('\n');
|
|
33
|
+
}
|
|
34
|
+
export function renderRuntimePolicies(policies, context) {
|
|
35
|
+
return policies
|
|
36
|
+
.map((policy) => renderRuntimePolicy(policy, context))
|
|
37
|
+
.filter((policy) => policy.length > 0)
|
|
38
|
+
.join('\n\n');
|
|
39
|
+
}
|
|
40
|
+
export function prependRuntimePolicies(instruction, policies, context) {
|
|
41
|
+
const rendered = renderRuntimePolicies(policies, context);
|
|
42
|
+
if (!rendered) {
|
|
43
|
+
return instruction;
|
|
44
|
+
}
|
|
45
|
+
// Claude slash commands only activate when the first line still starts with `/command`.
|
|
46
|
+
// Preserve that first line, then inject runtime policy guidance immediately after it.
|
|
47
|
+
if (instruction.startsWith('/')) {
|
|
48
|
+
const newlineIndex = instruction.indexOf('\n');
|
|
49
|
+
if (newlineIndex === -1) {
|
|
50
|
+
return `${instruction}\n\n${rendered}`;
|
|
51
|
+
}
|
|
52
|
+
const firstLine = instruction.slice(0, newlineIndex);
|
|
53
|
+
const remainder = instruction.slice(newlineIndex + 1);
|
|
54
|
+
return `${firstLine}\n\n${rendered}\n${remainder}`;
|
|
55
|
+
}
|
|
56
|
+
return `${rendered}\n\n${instruction}`;
|
|
57
|
+
}
|
|
58
|
+
export function getRuntimePolicyLogMetadata(turnResult) {
|
|
59
|
+
return turnResult.runtimePoliciesApplied && turnResult.runtimePoliciesApplied.length > 0
|
|
60
|
+
? { runtime_policies_applied: turnResult.runtimePoliciesApplied }
|
|
61
|
+
: {};
|
|
62
|
+
}
|
|
63
|
+
export function extractRuntimePolicyAuditEntries(log) {
|
|
64
|
+
const policies = new Map();
|
|
65
|
+
for (const entry of log) {
|
|
66
|
+
if (entry.type !== 'agent_result' || !entry.runtime_policies_applied || entry.runtime_policies_applied.length === 0) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
for (const policy of entry.runtime_policies_applied) {
|
|
70
|
+
const key = `${policy.id}@${policy.version}`;
|
|
71
|
+
const existing = policies.get(key);
|
|
72
|
+
if (!existing) {
|
|
73
|
+
policies.set(key, {
|
|
74
|
+
id: policy.id,
|
|
75
|
+
version: policy.version,
|
|
76
|
+
turns: typeof entry.turn_number === 'number' ? [entry.turn_number] : [],
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (typeof entry.turn_number === 'number' && !existing.turns.includes(entry.turn_number)) {
|
|
81
|
+
existing.turns.push(entry.turn_number);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return [...policies.values()].map((policy) => ({
|
|
86
|
+
...policy,
|
|
87
|
+
turns: [...policy.turns].sort((a, b) => a - b),
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ToolEvent } from '../tool-events.js';
|
|
2
|
+
import type { ScorerResultEntry, ToolExpectation } from './types.js';
|
|
3
|
+
export declare function clamp(n: number): number;
|
|
4
|
+
export declare function getErrorMessage(error: unknown): string;
|
|
5
|
+
export declare function matchesExpectation(event: ToolEvent, expectation: ToolExpectation): boolean;
|
|
6
|
+
export declare function makeErroredResult(type: ScorerResultEntry['type'], name: string, weight: number, error: unknown): ScorerResultEntry;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function clamp(n) {
|
|
2
|
+
return Math.max(0, Math.min(1, n));
|
|
3
|
+
}
|
|
4
|
+
export function getErrorMessage(error) {
|
|
5
|
+
if (error instanceof Error) {
|
|
6
|
+
return error.message;
|
|
7
|
+
}
|
|
8
|
+
return String(error);
|
|
9
|
+
}
|
|
10
|
+
export function matchesExpectation(event, expectation) {
|
|
11
|
+
if (event.action !== expectation.action)
|
|
12
|
+
return false;
|
|
13
|
+
if (expectation.toolName && event.providerToolName !== expectation.toolName)
|
|
14
|
+
return false;
|
|
15
|
+
if (expectation.path && event.arguments?.path !== expectation.path)
|
|
16
|
+
return false;
|
|
17
|
+
if (expectation.commandContains) {
|
|
18
|
+
const cmd = String(event.arguments?.cmd || event.arguments?.command || '');
|
|
19
|
+
if (!cmd.includes(expectation.commandContains))
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (expectation.argumentPattern) {
|
|
23
|
+
const re = new RegExp(expectation.argumentPattern);
|
|
24
|
+
const values = Object.values(event.arguments || {}).filter((v) => typeof v === 'string');
|
|
25
|
+
if (!values.some((v) => re.test(v)))
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
export function makeErroredResult(type, name, weight, error) {
|
|
31
|
+
return {
|
|
32
|
+
name,
|
|
33
|
+
type,
|
|
34
|
+
score: 0,
|
|
35
|
+
weight,
|
|
36
|
+
details: getErrorMessage(error),
|
|
37
|
+
status: 'error',
|
|
38
|
+
};
|
|
39
|
+
}
|