@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,51 @@
|
|
|
1
|
+
import { runJudgeSession, } from './judge-tool-session.js';
|
|
2
|
+
export async function runJudgeWithTools(scorer, ctx, llm, opts = {}) {
|
|
3
|
+
const startedAt = Date.now();
|
|
4
|
+
const retries = getRetryCount(scorer.retry);
|
|
5
|
+
let last;
|
|
6
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
7
|
+
last = await runJudgeSession({ scorer, ctx, llm }, {
|
|
8
|
+
maxRounds: opts.maxRounds,
|
|
9
|
+
toolTimeoutMs: opts.toolTimeoutMs,
|
|
10
|
+
toolExecutor: opts.toolExecutor,
|
|
11
|
+
});
|
|
12
|
+
if (last.code !== 'llm_refused' && last.code !== 'invalid_score')
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
entry: toEntry(scorer, last),
|
|
17
|
+
rounds: last.rounds,
|
|
18
|
+
toolCalls: last.toolCalls,
|
|
19
|
+
tokenUsage: last.tokenUsage,
|
|
20
|
+
wallTimeMs: Date.now() - startedAt,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function getRetryCount(retry) {
|
|
24
|
+
if (retry === true)
|
|
25
|
+
return 1;
|
|
26
|
+
if (typeof retry === 'number' && Number.isFinite(retry)) {
|
|
27
|
+
return Math.max(0, Math.floor(retry));
|
|
28
|
+
}
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
function toEntry(scorer, outcome) {
|
|
32
|
+
if (outcome.code === null) {
|
|
33
|
+
return {
|
|
34
|
+
name: scorer.name,
|
|
35
|
+
type: 'judge',
|
|
36
|
+
score: outcome.score,
|
|
37
|
+
weight: scorer.weight,
|
|
38
|
+
details: outcome.details,
|
|
39
|
+
status: 'ok',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
name: scorer.name,
|
|
44
|
+
type: 'judge',
|
|
45
|
+
score: 0,
|
|
46
|
+
weight: scorer.weight,
|
|
47
|
+
details: outcome.details,
|
|
48
|
+
status: 'error',
|
|
49
|
+
errorCode: outcome.code,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ToolCapableLLMPort } from '../utils/llm-types.js';
|
|
2
|
+
import type { JudgeScorer, ScorerContext, TokenUsage } from './types.js';
|
|
3
|
+
import type { LogEntry } from '../types.js';
|
|
4
|
+
import { type RegisteredTool } from './judge-tools.js';
|
|
5
|
+
export type JudgeErrorCode = 'llm_refused' | 'max_rounds' | 'invalid_score' | 'tool_error_unrecoverable' | 'provider_not_supported';
|
|
6
|
+
export interface JudgeToolCallRecord {
|
|
7
|
+
name: string;
|
|
8
|
+
input: unknown;
|
|
9
|
+
ok: boolean;
|
|
10
|
+
bytes: number;
|
|
11
|
+
errorMessage?: string;
|
|
12
|
+
}
|
|
13
|
+
export type ToolExecutor = (name: string, input: Record<string, unknown>, ctx: ScorerContext) => Promise<string>;
|
|
14
|
+
export interface JudgeSessionInput {
|
|
15
|
+
scorer: JudgeScorer;
|
|
16
|
+
ctx: ScorerContext;
|
|
17
|
+
llm: ToolCapableLLMPort;
|
|
18
|
+
}
|
|
19
|
+
export interface JudgeSessionOptions {
|
|
20
|
+
/** Overrides scorer.maxRounds. */
|
|
21
|
+
maxRounds?: number;
|
|
22
|
+
/** Per-tool-call timeout (ms). Default 10_000. */
|
|
23
|
+
toolTimeoutMs?: number;
|
|
24
|
+
/** Override tool execution dispatch; used by tests and future MCP-backed registries. */
|
|
25
|
+
toolExecutor?: ToolExecutor;
|
|
26
|
+
/** Override the tool registry; defaults to DEFAULT_TOOL_REGISTRY. */
|
|
27
|
+
registry?: ReadonlyMap<string, RegisteredTool>;
|
|
28
|
+
/** Push `judge_tool_call` entries into `ctx.log` as they happen. Default `true`. When `false`, entries are only returned in `SessionOutcome.logEntries`. */
|
|
29
|
+
emitLogs?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export type SessionOutcome = {
|
|
32
|
+
code: null;
|
|
33
|
+
score: number;
|
|
34
|
+
details: string;
|
|
35
|
+
rounds: number;
|
|
36
|
+
toolCalls: JudgeToolCallRecord[];
|
|
37
|
+
tokenUsage: TokenUsage;
|
|
38
|
+
logEntries: LogEntry[];
|
|
39
|
+
} | {
|
|
40
|
+
code: JudgeErrorCode;
|
|
41
|
+
details: string;
|
|
42
|
+
rounds: number;
|
|
43
|
+
toolCalls: JudgeToolCallRecord[];
|
|
44
|
+
tokenUsage: TokenUsage;
|
|
45
|
+
logEntries: LogEntry[];
|
|
46
|
+
};
|
|
47
|
+
export declare function runJudgeSession(input: JudgeSessionInput, options?: JudgeSessionOptions): Promise<SessionOutcome>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import { DEFAULT_TOOL_REGISTRY } from './judge-tools.js';
|
|
3
|
+
import { buildToolUseJudgePrompt } from './judge-prompt-builder.js';
|
|
4
|
+
import { getErrorMessage } from './scorer-utils.js';
|
|
5
|
+
const DEFAULT_MAX_ROUNDS = 10;
|
|
6
|
+
const DEFAULT_TOOL_TIMEOUT_MS = 10_000;
|
|
7
|
+
export async function runJudgeSession(input, options = {}) {
|
|
8
|
+
const { scorer, ctx, llm } = input;
|
|
9
|
+
const tokenUsage = { inputTokens: 0, outputTokens: 0 };
|
|
10
|
+
const toolCalls = [];
|
|
11
|
+
const logEntries = [];
|
|
12
|
+
const emitLogs = options.emitLogs ?? true;
|
|
13
|
+
if (typeof llm.callWithTools !== 'function') {
|
|
14
|
+
return makeOutcome(tokenUsage, toolCalls, logEntries, 0, {
|
|
15
|
+
code: 'provider_not_supported',
|
|
16
|
+
details: 'The active LLM provider does not support tool-use judges (no callWithTools). Set ANTHROPIC_API_KEY to use the Anthropic HTTP provider, or remove `tools` from this judge.',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
await fs.access(ctx.workspace);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return makeOutcome(tokenUsage, toolCalls, logEntries, 0, {
|
|
24
|
+
code: 'tool_error_unrecoverable',
|
|
25
|
+
details: `Workspace does not exist: ${ctx.workspace}`,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const registry = options.registry ?? DEFAULT_TOOL_REGISTRY;
|
|
29
|
+
const allowed = new Set(scorer.tools ?? []);
|
|
30
|
+
const toolSchemas = (scorer.tools ?? []).map((n) => registry.get(n).schema);
|
|
31
|
+
const maxRounds = options.maxRounds ?? scorer.maxRounds ?? DEFAULT_MAX_ROUNDS;
|
|
32
|
+
const toolTimeoutMs = options.toolTimeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS;
|
|
33
|
+
const { system, user } = buildToolUseJudgePrompt(scorer, ctx);
|
|
34
|
+
const messages = [
|
|
35
|
+
{ role: 'user', content: user },
|
|
36
|
+
];
|
|
37
|
+
let rounds = 0;
|
|
38
|
+
while (rounds < maxRounds) {
|
|
39
|
+
rounds++;
|
|
40
|
+
let response;
|
|
41
|
+
try {
|
|
42
|
+
response = await llm.callWithTools(messages, {
|
|
43
|
+
system,
|
|
44
|
+
tools: toolSchemas,
|
|
45
|
+
model: scorer.model,
|
|
46
|
+
cacheControl: scorer.cacheControl,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
return makeOutcome(tokenUsage, toolCalls, logEntries, rounds, {
|
|
51
|
+
code: 'tool_error_unrecoverable',
|
|
52
|
+
details: getErrorMessage(err),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
tokenUsage.inputTokens += response.inputTokens ?? 0;
|
|
56
|
+
tokenUsage.outputTokens += response.outputTokens ?? 0;
|
|
57
|
+
if (response.kind === 'final') {
|
|
58
|
+
if (!response.text.trim()) {
|
|
59
|
+
return makeOutcome(tokenUsage, toolCalls, logEntries, rounds, {
|
|
60
|
+
code: 'llm_refused',
|
|
61
|
+
details: 'LLM returned empty response with no tool calls',
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const parsed = parseFinalScore(response.text);
|
|
65
|
+
if (!parsed.ok) {
|
|
66
|
+
return makeOutcome(tokenUsage, toolCalls, logEntries, rounds, {
|
|
67
|
+
code: 'invalid_score',
|
|
68
|
+
details: parsed.message,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return makeOutcome(tokenUsage, toolCalls, logEntries, rounds, {
|
|
72
|
+
code: null,
|
|
73
|
+
score: parsed.score,
|
|
74
|
+
details: parsed.details,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const outside = response.blocks.find((b) => !allowed.has(b.name));
|
|
78
|
+
if (outside) {
|
|
79
|
+
return makeOutcome(tokenUsage, toolCalls, logEntries, rounds, {
|
|
80
|
+
code: 'tool_error_unrecoverable',
|
|
81
|
+
details: `LLM requested tool not in allowlist: ${outside.name}`,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
messages.push({
|
|
85
|
+
role: 'assistant',
|
|
86
|
+
content: [
|
|
87
|
+
...(response.text ? [{ type: 'text', text: response.text }] : []),
|
|
88
|
+
...response.blocks,
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
const results = await Promise.allSettled(response.blocks.map(async (block) => {
|
|
92
|
+
try {
|
|
93
|
+
const output = await withTimeout(executeTool(block, ctx, registry, options.toolExecutor), toolTimeoutMs);
|
|
94
|
+
const bytes = Buffer.byteLength(output, 'utf8');
|
|
95
|
+
const record = {
|
|
96
|
+
name: block.name,
|
|
97
|
+
input: block.input,
|
|
98
|
+
ok: true,
|
|
99
|
+
bytes,
|
|
100
|
+
};
|
|
101
|
+
toolCalls.push(record);
|
|
102
|
+
recordToolCallLog(ctx, scorer, record, logEntries, emitLogs);
|
|
103
|
+
return toolResultBlock(block.id, output, false);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
const msg = getErrorMessage(err);
|
|
107
|
+
const record = {
|
|
108
|
+
name: block.name,
|
|
109
|
+
input: block.input,
|
|
110
|
+
ok: false,
|
|
111
|
+
bytes: 0,
|
|
112
|
+
errorMessage: msg,
|
|
113
|
+
};
|
|
114
|
+
toolCalls.push(record);
|
|
115
|
+
recordToolCallLog(ctx, scorer, record, logEntries, emitLogs);
|
|
116
|
+
return toolResultBlock(block.id, msg, true);
|
|
117
|
+
}
|
|
118
|
+
}));
|
|
119
|
+
const userBlocks = results.map((r, i) => {
|
|
120
|
+
if (r.status === 'fulfilled')
|
|
121
|
+
return r.value;
|
|
122
|
+
return toolResultBlock(response.blocks[i].id, getErrorMessage(r.reason), true);
|
|
123
|
+
});
|
|
124
|
+
messages.push({ role: 'user', content: userBlocks });
|
|
125
|
+
}
|
|
126
|
+
return makeOutcome(tokenUsage, toolCalls, logEntries, rounds, {
|
|
127
|
+
code: 'max_rounds',
|
|
128
|
+
details: `Tool-use loop exhausted after ${rounds} rounds — consider increasing maxRounds (current: ${maxRounds}).`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function makeOutcome(tokenUsage, toolCalls, logEntries, rounds, outcome) {
|
|
132
|
+
if (outcome.code === null) {
|
|
133
|
+
return {
|
|
134
|
+
code: null,
|
|
135
|
+
score: outcome.score,
|
|
136
|
+
details: outcome.details,
|
|
137
|
+
rounds,
|
|
138
|
+
toolCalls,
|
|
139
|
+
tokenUsage,
|
|
140
|
+
logEntries,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
code: outcome.code,
|
|
145
|
+
details: outcome.details,
|
|
146
|
+
rounds,
|
|
147
|
+
toolCalls,
|
|
148
|
+
tokenUsage,
|
|
149
|
+
logEntries,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
async function executeTool(block, ctx, registry, override) {
|
|
153
|
+
if (override) {
|
|
154
|
+
return override(block.name, block.input, ctx);
|
|
155
|
+
}
|
|
156
|
+
const tool = registry.get(block.name);
|
|
157
|
+
if (!tool)
|
|
158
|
+
throw new Error(`Unknown tool: ${block.name}`);
|
|
159
|
+
return tool.run(block.input, ctx);
|
|
160
|
+
}
|
|
161
|
+
function toolResultBlock(id, content, isError) {
|
|
162
|
+
return { type: 'tool_result', tool_use_id: id, content, is_error: isError };
|
|
163
|
+
}
|
|
164
|
+
function recordToolCallLog(ctx, scorer, record, logEntries, emitLogs) {
|
|
165
|
+
const entry = {
|
|
166
|
+
type: 'judge_tool_call',
|
|
167
|
+
timestamp: new Date().toISOString(),
|
|
168
|
+
judge_tool_call: {
|
|
169
|
+
name: record.name,
|
|
170
|
+
input: record.input,
|
|
171
|
+
ok: record.ok,
|
|
172
|
+
bytes: record.bytes,
|
|
173
|
+
errorMessage: record.errorMessage,
|
|
174
|
+
judge_name: scorer.name,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
logEntries.push(entry);
|
|
178
|
+
if (emitLogs)
|
|
179
|
+
ctx.log.push(entry);
|
|
180
|
+
}
|
|
181
|
+
function withTimeout(promise, ms) {
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
const timer = setTimeout(() => reject(new Error(`Tool call timed out after ${ms}ms`)), ms);
|
|
184
|
+
promise.then((v) => { clearTimeout(timer); resolve(v); }, (e) => { clearTimeout(timer); reject(e); });
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function parseFinalScore(text) {
|
|
188
|
+
const cleaned = text.replace(/```(?:json)?\s*/g, '').replace(/```/g, '').trim();
|
|
189
|
+
const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
|
|
190
|
+
if (!jsonMatch) {
|
|
191
|
+
return { ok: false, message: `No JSON object found in final response: ${text.slice(0, 200)}` };
|
|
192
|
+
}
|
|
193
|
+
let parsed;
|
|
194
|
+
try {
|
|
195
|
+
parsed = JSON.parse(jsonMatch[0]);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
return { ok: false, message: `JSON parse failed: ${getErrorMessage(err)}` };
|
|
199
|
+
}
|
|
200
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
201
|
+
return { ok: false, message: 'Final JSON is not an object' };
|
|
202
|
+
}
|
|
203
|
+
const obj = parsed;
|
|
204
|
+
const rawScore = obj.score;
|
|
205
|
+
const numScore = typeof rawScore === 'number' ? rawScore : parseFloat(String(rawScore));
|
|
206
|
+
if (!Number.isFinite(numScore) || numScore < 0 || numScore > 1) {
|
|
207
|
+
return { ok: false, message: `Score out of range or not a number: ${String(rawScore)}` };
|
|
208
|
+
}
|
|
209
|
+
const details = typeof obj.details === 'string'
|
|
210
|
+
? obj.details
|
|
211
|
+
: typeof obj.reasoning === 'string' ? obj.reasoning : '';
|
|
212
|
+
return { ok: true, score: numScore, details };
|
|
213
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ToolEvent } from '../tool-events.js';
|
|
2
|
+
import type { ToolSchema } from '../utils/llm-types.js';
|
|
3
|
+
import type { CodeJudgeToolName, ScorerContext } from './types.js';
|
|
4
|
+
export interface RegisteredTool {
|
|
5
|
+
schema: ToolSchema;
|
|
6
|
+
run: (input: Record<string, unknown>, ctx: ScorerContext) => Promise<string>;
|
|
7
|
+
}
|
|
8
|
+
export interface JudgeToolContext {
|
|
9
|
+
workspace: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolve a workspace-relative path, rejecting if the resolved path escapes
|
|
13
|
+
* the workspace (including via symlinks).
|
|
14
|
+
*
|
|
15
|
+
* Real-paths the parent directory and appends the basename. We don't realpath
|
|
16
|
+
* the full path because fs.realpath throws ENOENT on missing targets; a naive
|
|
17
|
+
* fallback to path.resolve would not traverse symlinks and could be tricked by
|
|
18
|
+
* a ..-walk against non-existent paths outside the workspace.
|
|
19
|
+
*
|
|
20
|
+
* The lexical fallback for missing-parent paths is unreachable under the tool
|
|
21
|
+
* allowlist (all tools either require an existing target or use resolveInWorkspace
|
|
22
|
+
* on a parent that already exists). Kept for defense in depth.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveInWorkspace(workspace: string, relPath: string): Promise<string>;
|
|
25
|
+
export declare function readFile(ctx: JudgeToolContext, relPath: string): Promise<string>;
|
|
26
|
+
export declare function listDir(ctx: JudgeToolContext, relPath: string): Promise<string[]>;
|
|
27
|
+
export declare function grep(ctx: JudgeToolContext, pattern: string, relPath?: string): Promise<string>;
|
|
28
|
+
export declare function getToolEvents(events: readonly ToolEvent[], actionFilter?: string): Promise<string>;
|
|
29
|
+
export declare const DEFAULT_TOOL_REGISTRY: ReadonlyMap<CodeJudgeToolName, RegisteredTool>;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
const READ_FILE_MAX_BYTES = 200 * 1024;
|
|
4
|
+
const GREP_MAX_MATCHES = 50;
|
|
5
|
+
const GREP_MAX_LINE_LEN = 300;
|
|
6
|
+
const GREP_MAX_FILES = 5_000;
|
|
7
|
+
const GREP_MAX_BYTES = 10 * 1024 * 1024;
|
|
8
|
+
const GREP_EXCLUDED_DIRS = new Set(['node_modules']);
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a workspace-relative path, rejecting if the resolved path escapes
|
|
11
|
+
* the workspace (including via symlinks).
|
|
12
|
+
*
|
|
13
|
+
* Real-paths the parent directory and appends the basename. We don't realpath
|
|
14
|
+
* the full path because fs.realpath throws ENOENT on missing targets; a naive
|
|
15
|
+
* fallback to path.resolve would not traverse symlinks and could be tricked by
|
|
16
|
+
* a ..-walk against non-existent paths outside the workspace.
|
|
17
|
+
*
|
|
18
|
+
* The lexical fallback for missing-parent paths is unreachable under the tool
|
|
19
|
+
* allowlist (all tools either require an existing target or use resolveInWorkspace
|
|
20
|
+
* on a parent that already exists). Kept for defense in depth.
|
|
21
|
+
*/
|
|
22
|
+
export async function resolveInWorkspace(workspace, relPath) {
|
|
23
|
+
if (path.isAbsolute(relPath)) {
|
|
24
|
+
throw new Error(`Path "${relPath}" is outside workspace (absolute paths not allowed)`);
|
|
25
|
+
}
|
|
26
|
+
const workspaceReal = await fs.realpath(workspace);
|
|
27
|
+
const joined = path.resolve(workspaceReal, relPath);
|
|
28
|
+
const parent = path.dirname(joined);
|
|
29
|
+
const basename = path.basename(joined);
|
|
30
|
+
let parentReal;
|
|
31
|
+
try {
|
|
32
|
+
parentReal = await fs.realpath(parent);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
const norm = path.resolve(workspaceReal, relPath);
|
|
36
|
+
if (!isInside(workspaceReal, norm)) {
|
|
37
|
+
throw new Error(`Path "${relPath}" is outside workspace`);
|
|
38
|
+
}
|
|
39
|
+
return norm;
|
|
40
|
+
}
|
|
41
|
+
const resolved = path.join(parentReal, basename);
|
|
42
|
+
if (!isInside(workspaceReal, resolved)) {
|
|
43
|
+
throw new Error(`Path "${relPath}" is outside workspace`);
|
|
44
|
+
}
|
|
45
|
+
return resolved;
|
|
46
|
+
}
|
|
47
|
+
function isInside(root, candidate) {
|
|
48
|
+
const rel = path.relative(root, candidate);
|
|
49
|
+
if (rel === '')
|
|
50
|
+
return true;
|
|
51
|
+
if (rel.startsWith('..'))
|
|
52
|
+
return false;
|
|
53
|
+
if (path.isAbsolute(rel))
|
|
54
|
+
return false;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Final-containment check for traversal. Realpaths the full target (it must
|
|
59
|
+
* exist) and rejects if it is not inside the workspace realpath. Used by grep
|
|
60
|
+
* to refuse following symlinks out of the workspace.
|
|
61
|
+
*/
|
|
62
|
+
async function checkFinalContainment(workspace, absolutePath) {
|
|
63
|
+
try {
|
|
64
|
+
const real = await fs.realpath(absolutePath);
|
|
65
|
+
const workspaceReal = await fs.realpath(workspace);
|
|
66
|
+
return isInside(workspaceReal, real);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export async function readFile(ctx, relPath) {
|
|
73
|
+
const resolved = await resolveInWorkspace(ctx.workspace, relPath);
|
|
74
|
+
let content;
|
|
75
|
+
try {
|
|
76
|
+
content = await fs.readFile(resolved, 'utf8');
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (err.code === 'ENOENT') {
|
|
80
|
+
throw new Error(`File not found: ${relPath}`);
|
|
81
|
+
}
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
if (Buffer.byteLength(content, 'utf8') > READ_FILE_MAX_BYTES) {
|
|
85
|
+
const head = content.slice(0, READ_FILE_MAX_BYTES);
|
|
86
|
+
return `${head}\n[truncated at 200KB]`;
|
|
87
|
+
}
|
|
88
|
+
return content;
|
|
89
|
+
}
|
|
90
|
+
export async function listDir(ctx, relPath) {
|
|
91
|
+
const resolved = await resolveInWorkspace(ctx.workspace, relPath);
|
|
92
|
+
let entries;
|
|
93
|
+
try {
|
|
94
|
+
entries = await fs.readdir(resolved, { withFileTypes: true });
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const code = err.code;
|
|
98
|
+
if (code === 'ENOENT') {
|
|
99
|
+
throw new Error(`Directory not found: ${relPath}`);
|
|
100
|
+
}
|
|
101
|
+
if (code === 'ENOTDIR') {
|
|
102
|
+
throw new Error(`Not a directory: ${relPath}`);
|
|
103
|
+
}
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
return entries.map((e) => (e.isDirectory() ? `${e.name}/` : e.name));
|
|
107
|
+
}
|
|
108
|
+
export async function grep(ctx, pattern, relPath) {
|
|
109
|
+
const rootRel = relPath ?? '.';
|
|
110
|
+
const root = await resolveInWorkspace(ctx.workspace, rootRel);
|
|
111
|
+
const workspaceReal = await fs.realpath(ctx.workspace);
|
|
112
|
+
const regex = new RegExp(pattern);
|
|
113
|
+
const matches = [];
|
|
114
|
+
let filesVisited = 0;
|
|
115
|
+
let bytesRead = 0;
|
|
116
|
+
let boundedOut = false;
|
|
117
|
+
function collectMatches(filePath, content) {
|
|
118
|
+
const displayPath = path.relative(workspaceReal, filePath);
|
|
119
|
+
const lines = content.split('\n');
|
|
120
|
+
for (let i = 0; i < lines.length; i++) {
|
|
121
|
+
if (matches.length >= GREP_MAX_MATCHES)
|
|
122
|
+
break;
|
|
123
|
+
if (!regex.test(lines[i]))
|
|
124
|
+
continue;
|
|
125
|
+
let text = lines[i];
|
|
126
|
+
if (text.length > GREP_MAX_LINE_LEN) {
|
|
127
|
+
text = `${text.slice(0, GREP_MAX_LINE_LEN)}[truncated]`;
|
|
128
|
+
}
|
|
129
|
+
matches.push(`${displayPath}:${i + 1}:${text}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function scanFile(full) {
|
|
133
|
+
filesVisited++;
|
|
134
|
+
if (filesVisited > GREP_MAX_FILES) {
|
|
135
|
+
boundedOut = true;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
let content;
|
|
139
|
+
try {
|
|
140
|
+
content = await fs.readFile(full, 'utf8');
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
bytesRead += Buffer.byteLength(content, 'utf8');
|
|
146
|
+
if (bytesRead > GREP_MAX_BYTES) {
|
|
147
|
+
boundedOut = true;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
collectMatches(full, content);
|
|
151
|
+
}
|
|
152
|
+
async function walk(dir) {
|
|
153
|
+
if (matches.length >= GREP_MAX_MATCHES || boundedOut)
|
|
154
|
+
return;
|
|
155
|
+
let entries;
|
|
156
|
+
try {
|
|
157
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
if (matches.length >= GREP_MAX_MATCHES || boundedOut)
|
|
164
|
+
return;
|
|
165
|
+
if (entry.name.startsWith('.'))
|
|
166
|
+
continue;
|
|
167
|
+
if (entry.isDirectory() && GREP_EXCLUDED_DIRS.has(entry.name))
|
|
168
|
+
continue;
|
|
169
|
+
const full = path.join(dir, entry.name);
|
|
170
|
+
if (!(await checkFinalContainment(workspaceReal, full)))
|
|
171
|
+
continue;
|
|
172
|
+
if (entry.isDirectory()) {
|
|
173
|
+
await walk(full);
|
|
174
|
+
}
|
|
175
|
+
else if (entry.isFile()) {
|
|
176
|
+
await scanFile(full);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
let rootStat;
|
|
181
|
+
try {
|
|
182
|
+
rootStat = await fs.stat(root);
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
const code = err.code;
|
|
186
|
+
if (code === 'ENOENT')
|
|
187
|
+
throw new Error(`Directory not found: ${rootRel}`);
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
190
|
+
if (rootStat.isFile()) {
|
|
191
|
+
await scanFile(root);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
await walk(root);
|
|
195
|
+
}
|
|
196
|
+
const out = matches.join('\n');
|
|
197
|
+
if (matches.length >= GREP_MAX_MATCHES) {
|
|
198
|
+
return `${out}\n[truncated at ${GREP_MAX_MATCHES} matches]`;
|
|
199
|
+
}
|
|
200
|
+
if (boundedOut) {
|
|
201
|
+
return `${out}\n[traversal truncated: exceeded ${GREP_MAX_FILES} files or ${GREP_MAX_BYTES / (1024 * 1024)}MB]`;
|
|
202
|
+
}
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
export async function getToolEvents(events, actionFilter) {
|
|
206
|
+
const filtered = actionFilter
|
|
207
|
+
? events.filter((e) => e.action.includes(actionFilter))
|
|
208
|
+
: events;
|
|
209
|
+
return JSON.stringify(filtered);
|
|
210
|
+
}
|
|
211
|
+
export const DEFAULT_TOOL_REGISTRY = new Map([
|
|
212
|
+
['readFile', {
|
|
213
|
+
schema: {
|
|
214
|
+
name: 'readFile',
|
|
215
|
+
description: 'Read the contents of a file inside the workspace. Content is truncated at 200KB.',
|
|
216
|
+
input_schema: {
|
|
217
|
+
type: 'object',
|
|
218
|
+
properties: { path: { type: 'string', description: 'Workspace-relative path' } },
|
|
219
|
+
required: ['path'],
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
async run(input, ctx) {
|
|
223
|
+
const p = input.path;
|
|
224
|
+
if (typeof p !== 'string' || !p)
|
|
225
|
+
throw new Error('readFile: missing or invalid "path" argument');
|
|
226
|
+
return readFile({ workspace: ctx.workspace }, p);
|
|
227
|
+
},
|
|
228
|
+
}],
|
|
229
|
+
['listDir', {
|
|
230
|
+
schema: {
|
|
231
|
+
name: 'listDir',
|
|
232
|
+
description: 'List entries in a directory (one level). Directory names get a trailing "/".',
|
|
233
|
+
input_schema: {
|
|
234
|
+
type: 'object',
|
|
235
|
+
properties: { path: { type: 'string' } },
|
|
236
|
+
required: ['path'],
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
async run(input, ctx) {
|
|
240
|
+
const p = input.path;
|
|
241
|
+
if (typeof p !== 'string' || !p)
|
|
242
|
+
throw new Error('listDir: missing or invalid "path" argument');
|
|
243
|
+
const entries = await listDir({ workspace: ctx.workspace }, p);
|
|
244
|
+
return entries.join('\n');
|
|
245
|
+
},
|
|
246
|
+
}],
|
|
247
|
+
['grep', {
|
|
248
|
+
schema: {
|
|
249
|
+
name: 'grep',
|
|
250
|
+
description: 'Search a regex pattern through workspace files. Returns path:line:text matches, capped at 50. Dotfiles and node_modules excluded.',
|
|
251
|
+
input_schema: {
|
|
252
|
+
type: 'object',
|
|
253
|
+
properties: {
|
|
254
|
+
pattern: { type: 'string', description: 'Regex pattern (anchored with ^/$ for full line)' },
|
|
255
|
+
path: { type: 'string', description: 'Optional sub-path to scope the search' },
|
|
256
|
+
},
|
|
257
|
+
required: ['pattern'],
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
async run(input, ctx) {
|
|
261
|
+
const pattern = input.pattern;
|
|
262
|
+
if (typeof pattern !== 'string' || !pattern) {
|
|
263
|
+
throw new Error('grep: missing or invalid "pattern" argument');
|
|
264
|
+
}
|
|
265
|
+
const scope = typeof input.path === 'string' && input.path ? input.path : undefined;
|
|
266
|
+
return grep({ workspace: ctx.workspace }, pattern, scope);
|
|
267
|
+
},
|
|
268
|
+
}],
|
|
269
|
+
['getToolEvents', {
|
|
270
|
+
schema: {
|
|
271
|
+
name: 'getToolEvents',
|
|
272
|
+
description: 'Retrieve the agent session tool events as JSON. Optional actionFilter substring-matches against event.action.',
|
|
273
|
+
input_schema: {
|
|
274
|
+
type: 'object',
|
|
275
|
+
properties: { actionFilter: { type: 'string' } },
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
async run(input, ctx) {
|
|
279
|
+
const filter = typeof input.actionFilter === 'string' ? input.actionFilter : undefined;
|
|
280
|
+
return getToolEvents(ctx.toolEvents, filter);
|
|
281
|
+
},
|
|
282
|
+
}],
|
|
283
|
+
]);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { AgentTurnResult, LogEntry } from '../types.js';
|
|
2
|
+
import type { Workspace } from '../providers/workspace.js';
|
|
3
|
+
import type { AgentName, AgentTransport, Message } from './types.js';
|
|
4
|
+
import type { LLMPort } from '../utils/llm-types.js';
|
|
5
|
+
import type { AskBus } from './ask-bus/types.js';
|
|
6
|
+
export interface ManagedSessionDeps {
|
|
7
|
+
ws: Workspace;
|
|
8
|
+
agentName: AgentName;
|
|
9
|
+
timeoutSec: number;
|
|
10
|
+
messages: Message[];
|
|
11
|
+
log: LogEntry[];
|
|
12
|
+
model?: string;
|
|
13
|
+
conversationWindow?: import('./types.js').ConversationWindowConfig | false;
|
|
14
|
+
llm?: LLMPort;
|
|
15
|
+
/**
|
|
16
|
+
* Per-conversation ceiling (ms) for live `ask_user` resolution. Threaded
|
|
17
|
+
* into `createAskBus` at session construction.
|
|
18
|
+
*/
|
|
19
|
+
askUserTimeoutMs?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Pre-constructed bus. When provided, the managed session uses it and does
|
|
22
|
+
* not create its own. Test/advanced paths can plug in a custom bus; normal
|
|
23
|
+
* callers should omit this and rely on default construction.
|
|
24
|
+
*/
|
|
25
|
+
askBus?: AskBus;
|
|
26
|
+
/**
|
|
27
|
+
* Resolved Codex transport. When omitted, runtime-policy planning and
|
|
28
|
+
* driver capabilities fall back to the default (`exec` semantics).
|
|
29
|
+
*/
|
|
30
|
+
transport?: AgentTransport;
|
|
31
|
+
}
|
|
32
|
+
export interface ManagedSession {
|
|
33
|
+
/** Full lifecycle: log start/result, push messages, check exit code. */
|
|
34
|
+
send(message: string): Promise<string>;
|
|
35
|
+
/** Raw turn execution — returns AgentTurnResult. Caller handles logging. */
|
|
36
|
+
executeTurn(message: string): Promise<AgentTurnResult>;
|
|
37
|
+
/** Remaining ms before the session-level deadline. */
|
|
38
|
+
remainingMs(): number;
|
|
39
|
+
/** Per-conversation ask-user bus. */
|
|
40
|
+
readonly askBus: AskBus;
|
|
41
|
+
/**
|
|
42
|
+
* Tear down the underlying agent session (close transport, kill subprocess,
|
|
43
|
+
* release stdio). Optional so that existing mocks and legacy drivers
|
|
44
|
+
* without teardown work unchanged. Idempotent; safe to call before any
|
|
45
|
+
* turn has run; a no-op for agents whose sessions do not implement
|
|
46
|
+
* {@link AgentSession.dispose}.
|
|
47
|
+
*/
|
|
48
|
+
dispose?(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
export declare function createManagedSession(deps: ManagedSessionDeps): ManagedSession;
|