@equinor/fusion-framework-cli-plugin-copilot 2.0.1 → 2.0.3
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/CHANGELOG.md +17 -0
- package/dist/esm/commands/app/{session-logger.js → attach-session-logger.js} +16 -2
- package/dist/esm/commands/app/attach-session-logger.js.map +1 -0
- package/dist/esm/commands/app/{command.js → copilot-command.js} +22 -11
- package/dist/esm/commands/app/copilot-command.js.map +1 -0
- package/dist/esm/commands/app/{eval.js → create-session.js} +8 -6
- package/dist/esm/commands/app/create-session.js.map +1 -0
- package/dist/esm/commands/app/format-plan.js +24 -0
- package/dist/esm/commands/app/format-plan.js.map +1 -0
- package/dist/esm/commands/app/format-step-result.js +14 -0
- package/dist/esm/commands/app/format-step-result.js.map +1 -0
- package/dist/esm/commands/app/format-verdict.js +38 -0
- package/dist/esm/commands/app/format-verdict.js.map +1 -0
- package/dist/esm/commands/app/prompts/{system.dev-server.prompt.js → create-dev-server-context.js} +1 -1
- package/dist/esm/commands/app/prompts/create-dev-server-context.js.map +1 -0
- package/dist/esm/commands/app/prompts/{execute-step.prompt.js → create-execute-step-prompt.js} +1 -1
- package/dist/esm/commands/app/prompts/create-execute-step-prompt.js.map +1 -0
- package/dist/esm/commands/app/prompts/{judge.prompt.js → create-judge-prompt.js} +5 -1
- package/dist/esm/commands/app/prompts/create-judge-prompt.js.map +1 -0
- package/dist/esm/commands/app/prompts/{plan.prompt.js → create-plan-prompt.js} +1 -1
- package/dist/esm/commands/app/prompts/create-plan-prompt.js.map +1 -0
- package/dist/esm/commands/app/prompts/{step.prompt.js → create-step-prompt.js} +3 -2
- package/dist/esm/commands/app/prompts/create-step-prompt.js.map +1 -0
- package/dist/esm/commands/app/prompts/{system.prompt.js → create-system-prompt.js} +1 -1
- package/dist/esm/commands/app/prompts/create-system-prompt.js.map +1 -0
- package/dist/esm/commands/app/{login.js → run-login.js} +5 -1
- package/dist/esm/commands/app/run-login.js.map +1 -0
- package/dist/esm/commands/app/{server.js → start-app-server.js} +7 -1
- package/dist/esm/commands/app/start-app-server.js.map +1 -0
- package/dist/esm/commands/app/status-icon.js +18 -0
- package/dist/esm/commands/app/status-icon.js.map +1 -0
- package/dist/esm/commands/app/tools/{registry.js → AgentBrowserToolRegistry.js} +1 -1
- package/dist/esm/commands/app/tools/AgentBrowserToolRegistry.js.map +1 -0
- package/dist/esm/commands/app/tools/{write-file.js → create-append-file-tool.js} +5 -38
- package/dist/esm/commands/app/tools/create-append-file-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{click.js → create-click-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-click-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{errors.js → create-errors-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-errors-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{eval-js.js → create-eval-js-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-eval-js-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{fill.js → create-fill-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-fill-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{find.js → create-find-tool.js} +4 -1
- package/dist/esm/commands/app/tools/create-find-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{get-styles.js → create-get-styles-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-get-styles-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{get-url.js → create-get-url-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-get-url-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{go-back.js → create-go-back-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-go-back-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{hover.js → create-hover-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-hover-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{navigate.js → create-navigate-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-navigate-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{press-key.js → create-press-key-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-press-key-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{read-file.js → create-read-file-tool.js} +4 -2
- package/dist/esm/commands/app/tools/create-read-file-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{reload.js → create-reload-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-reload-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{screenshot.js → create-screenshot-tool.js} +7 -1
- package/dist/esm/commands/app/tools/create-screenshot-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{scroll.js → create-scroll-tool.js} +3 -1
- package/dist/esm/commands/app/tools/create-scroll-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{select.js → create-select-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-select-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{snapshot.js → create-snapshot-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-snapshot-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{type-text.js → create-type-text-tool.js} +1 -1
- package/dist/esm/commands/app/tools/create-type-text-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/{wait.js → create-wait-tool.js} +5 -1
- package/dist/esm/commands/app/tools/create-wait-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/create-write-file-tool.js +65 -0
- package/dist/esm/commands/app/tools/create-write-file-tool.js.map +1 -0
- package/dist/esm/commands/app/tools/index.js +19 -19
- package/dist/esm/commands/app/tools/index.js.map +1 -1
- package/dist/esm/commands/app/truncate.js +11 -0
- package/dist/esm/commands/app/truncate.js.map +1 -0
- package/dist/esm/commands/app/try-format-message.js +39 -0
- package/dist/esm/commands/app/try-format-message.js.map +1 -0
- package/dist/esm/exit-with-resolution-error.js +16 -0
- package/dist/esm/exit-with-resolution-error.js.map +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/resolve-eval-command-input.js +37 -0
- package/dist/esm/resolve-eval-command-input.js.map +1 -0
- package/dist/esm/resolve-eval-files.js +49 -0
- package/dist/esm/resolve-eval-files.js.map +1 -0
- package/dist/esm/utils/ab-error-message.js +34 -0
- package/dist/esm/utils/ab-error-message.js.map +1 -0
- package/dist/esm/utils/{agent-browser.js → ab.js} +3 -32
- package/dist/esm/utils/ab.js.map +1 -0
- package/dist/esm/utils/create-run-dir.js +24 -0
- package/dist/esm/utils/create-run-dir.js.map +1 -0
- package/dist/esm/utils/{process.js → helpers.js} +28 -11
- package/dist/esm/utils/helpers.js.map +1 -0
- package/dist/esm/utils/index.js +8 -5
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/{browser-poll.js → poll-console.js} +5 -3
- package/dist/esm/utils/poll-console.js.map +1 -0
- package/dist/esm/utils/{daemon.js → reset-daemon.js} +5 -14
- package/dist/esm/utils/reset-daemon.js.map +1 -0
- package/dist/esm/utils/resolve-app-key.js +20 -0
- package/dist/esm/utils/resolve-app-key.js.map +1 -0
- package/dist/esm/utils/wait-for-server.js +26 -0
- package/dist/esm/utils/wait-for-server.js.map +1 -0
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/commands/app/{command.d.ts → copilot-command.d.ts} +1 -1
- package/dist/types/commands/app/format-plan.d.ts +8 -0
- package/dist/types/commands/app/format-step-result.d.ts +8 -0
- package/dist/types/commands/app/format-verdict.d.ts +8 -0
- package/dist/types/commands/app/prompts/{step.prompt.d.ts → create-step-prompt.d.ts} +1 -1
- package/dist/types/commands/app/status-icon.d.ts +8 -0
- package/dist/types/commands/app/tools/create-append-file-tool.d.ts +10 -0
- package/dist/types/commands/app/tools/{click.d.ts → create-click-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{errors.d.ts → create-errors-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{eval-js.d.ts → create-eval-js-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{fill.d.ts → create-fill-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{find.d.ts → create-find-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{get-styles.d.ts → create-get-styles-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{get-url.d.ts → create-get-url-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{go-back.d.ts → create-go-back-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{hover.d.ts → create-hover-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{navigate.d.ts → create-navigate-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{press-key.d.ts → create-press-key-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{read-file.d.ts → create-read-file-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{reload.d.ts → create-reload-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{screenshot.d.ts → create-screenshot-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{scroll.d.ts → create-scroll-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{select.d.ts → create-select-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{snapshot.d.ts → create-snapshot-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{type-text.d.ts → create-type-text-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/{wait.d.ts → create-wait-tool.d.ts} +1 -1
- package/dist/types/commands/app/tools/create-write-file-tool.d.ts +10 -0
- package/dist/types/commands/app/truncate.d.ts +8 -0
- package/dist/types/commands/app/try-format-message.d.ts +10 -0
- package/dist/types/exit-with-resolution-error.d.ts +8 -0
- package/dist/types/{eval-resolve.d.ts → resolve-eval-command-input.d.ts} +0 -8
- package/dist/types/resolve-eval-files.d.ts +8 -0
- package/dist/types/utils/ab-error-message.d.ts +11 -0
- package/dist/types/utils/{agent-browser.d.ts → ab.d.ts} +0 -13
- package/dist/types/utils/create-run-dir.d.ts +8 -0
- package/dist/types/utils/{process.d.ts → helpers.d.ts} +13 -6
- package/dist/types/utils/index.d.ts +8 -5
- package/dist/types/utils/{daemon.d.ts → reset-daemon.d.ts} +0 -5
- package/dist/types/utils/resolve-app-key.d.ts +9 -0
- package/dist/types/utils/wait-for-server.d.ts +9 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +6 -6
- package/src/commands/app/{session-logger.ts → attach-session-logger.ts} +15 -1
- package/src/commands/app/{command.ts → copilot-command.ts} +21 -12
- package/src/commands/app/{eval.ts → create-session.ts} +7 -5
- package/src/commands/app/format-plan.ts +26 -0
- package/src/commands/app/format-step-result.ts +16 -0
- package/src/commands/app/format-verdict.ts +43 -0
- package/src/commands/app/prompts/{judge.prompt.ts → create-judge-prompt.ts} +4 -0
- package/src/commands/app/prompts/{step.prompt.ts → create-step-prompt.ts} +2 -1
- package/src/commands/app/{login.ts → run-login.ts} +4 -0
- package/src/commands/app/{server.ts → start-app-server.ts} +6 -0
- package/src/commands/app/status-icon.ts +16 -0
- package/src/commands/app/tools/{write-file.ts → create-append-file-tool.ts} +4 -39
- package/src/commands/app/tools/{find.ts → create-find-tool.ts} +3 -0
- package/src/commands/app/tools/{read-file.ts → create-read-file-tool.ts} +3 -1
- package/src/commands/app/tools/{screenshot.ts → create-screenshot-tool.ts} +6 -0
- package/src/commands/app/tools/{scroll.ts → create-scroll-tool.ts} +2 -0
- package/src/commands/app/tools/{wait.ts → create-wait-tool.ts} +4 -0
- package/src/commands/app/tools/create-write-file-tool.ts +69 -0
- package/src/commands/app/tools/index.ts +19 -19
- package/src/commands/app/truncate.ts +10 -0
- package/src/commands/app/try-format-message.ts +42 -0
- package/src/exit-with-resolution-error.ts +15 -0
- package/src/index.ts +1 -1
- package/src/resolve-eval-command-input.ts +59 -0
- package/src/resolve-eval-files.ts +57 -0
- package/src/utils/ab-error-message.ts +31 -0
- package/src/utils/{agent-browser.ts → ab.ts} +2 -30
- package/src/utils/create-run-dir.ts +24 -0
- package/src/utils/{process.ts → helpers.ts} +28 -10
- package/src/utils/index.ts +8 -5
- package/src/utils/{browser-poll.ts → poll-console.ts} +4 -2
- package/src/utils/{daemon.ts → reset-daemon.ts} +4 -13
- package/src/utils/resolve-app-key.ts +20 -0
- package/src/utils/wait-for-server.ts +24 -0
- package/src/version.ts +1 -1
- package/tsconfig.json +3 -1
- package/dist/esm/commands/app/command.js.map +0 -1
- package/dist/esm/commands/app/eval.js.map +0 -1
- package/dist/esm/commands/app/format.js +0 -93
- package/dist/esm/commands/app/format.js.map +0 -1
- package/dist/esm/commands/app/login.js.map +0 -1
- package/dist/esm/commands/app/prompts/execute-step.prompt.js.map +0 -1
- package/dist/esm/commands/app/prompts/judge.prompt.js.map +0 -1
- package/dist/esm/commands/app/prompts/plan.prompt.js.map +0 -1
- package/dist/esm/commands/app/prompts/step.prompt.js.map +0 -1
- package/dist/esm/commands/app/prompts/system.dev-server.prompt.js.map +0 -1
- package/dist/esm/commands/app/prompts/system.prompt.js.map +0 -1
- package/dist/esm/commands/app/server.js.map +0 -1
- package/dist/esm/commands/app/session-logger.js.map +0 -1
- package/dist/esm/commands/app/tools/click.js.map +0 -1
- package/dist/esm/commands/app/tools/errors.js.map +0 -1
- package/dist/esm/commands/app/tools/eval-js.js.map +0 -1
- package/dist/esm/commands/app/tools/fill.js.map +0 -1
- package/dist/esm/commands/app/tools/find.js.map +0 -1
- package/dist/esm/commands/app/tools/get-styles.js.map +0 -1
- package/dist/esm/commands/app/tools/get-url.js.map +0 -1
- package/dist/esm/commands/app/tools/go-back.js.map +0 -1
- package/dist/esm/commands/app/tools/hover.js.map +0 -1
- package/dist/esm/commands/app/tools/navigate.js.map +0 -1
- package/dist/esm/commands/app/tools/press-key.js.map +0 -1
- package/dist/esm/commands/app/tools/read-file.js.map +0 -1
- package/dist/esm/commands/app/tools/registry.js.map +0 -1
- package/dist/esm/commands/app/tools/reload.js.map +0 -1
- package/dist/esm/commands/app/tools/screenshot.js.map +0 -1
- package/dist/esm/commands/app/tools/scroll.js.map +0 -1
- package/dist/esm/commands/app/tools/select.js.map +0 -1
- package/dist/esm/commands/app/tools/snapshot.js.map +0 -1
- package/dist/esm/commands/app/tools/type-text.js.map +0 -1
- package/dist/esm/commands/app/tools/wait.js.map +0 -1
- package/dist/esm/commands/app/tools/write-file.js.map +0 -1
- package/dist/esm/eval-resolve.js +0 -83
- package/dist/esm/eval-resolve.js.map +0 -1
- package/dist/esm/utils/agent-browser.js.map +0 -1
- package/dist/esm/utils/browser-poll.js.map +0 -1
- package/dist/esm/utils/daemon.js.map +0 -1
- package/dist/esm/utils/process.js.map +0 -1
- package/dist/esm/utils/server.js +0 -65
- package/dist/esm/utils/server.js.map +0 -1
- package/dist/types/commands/app/format.d.ts +0 -14
- package/dist/types/commands/app/tools/write-file.d.ts +0 -19
- package/dist/types/utils/server.d.ts +0 -26
- package/src/commands/app/format.ts +0 -110
- package/src/eval-resolve.ts +0 -113
- package/src/utils/server.ts +0 -66
- /package/dist/types/commands/app/{session-logger.d.ts → attach-session-logger.d.ts} +0 -0
- /package/dist/types/commands/app/{eval.d.ts → create-session.d.ts} +0 -0
- /package/dist/types/commands/app/prompts/{system.dev-server.prompt.d.ts → create-dev-server-context.d.ts} +0 -0
- /package/dist/types/commands/app/prompts/{execute-step.prompt.d.ts → create-execute-step-prompt.d.ts} +0 -0
- /package/dist/types/commands/app/prompts/{judge.prompt.d.ts → create-judge-prompt.d.ts} +0 -0
- /package/dist/types/commands/app/prompts/{plan.prompt.d.ts → create-plan-prompt.d.ts} +0 -0
- /package/dist/types/commands/app/prompts/{system.prompt.d.ts → create-system-prompt.d.ts} +0 -0
- /package/dist/types/commands/app/{login.d.ts → run-login.d.ts} +0 -0
- /package/dist/types/commands/app/{server.d.ts → start-app-server.d.ts} +0 -0
- /package/dist/types/commands/app/tools/{registry.d.ts → AgentBrowserToolRegistry.d.ts} +0 -0
- /package/dist/types/utils/{browser-poll.d.ts → poll-console.d.ts} +0 -0
- /package/src/commands/app/prompts/{system.dev-server.prompt.ts → create-dev-server-context.ts} +0 -0
- /package/src/commands/app/prompts/{execute-step.prompt.ts → create-execute-step-prompt.ts} +0 -0
- /package/src/commands/app/prompts/{plan.prompt.ts → create-plan-prompt.ts} +0 -0
- /package/src/commands/app/prompts/{system.prompt.ts → create-system-prompt.ts} +0 -0
- /package/src/commands/app/tools/{registry.ts → AgentBrowserToolRegistry.ts} +0 -0
- /package/src/commands/app/tools/{click.ts → create-click-tool.ts} +0 -0
- /package/src/commands/app/tools/{errors.ts → create-errors-tool.ts} +0 -0
- /package/src/commands/app/tools/{eval-js.ts → create-eval-js-tool.ts} +0 -0
- /package/src/commands/app/tools/{fill.ts → create-fill-tool.ts} +0 -0
- /package/src/commands/app/tools/{get-styles.ts → create-get-styles-tool.ts} +0 -0
- /package/src/commands/app/tools/{get-url.ts → create-get-url-tool.ts} +0 -0
- /package/src/commands/app/tools/{go-back.ts → create-go-back-tool.ts} +0 -0
- /package/src/commands/app/tools/{hover.ts → create-hover-tool.ts} +0 -0
- /package/src/commands/app/tools/{navigate.ts → create-navigate-tool.ts} +0 -0
- /package/src/commands/app/tools/{press-key.ts → create-press-key-tool.ts} +0 -0
- /package/src/commands/app/tools/{reload.ts → create-reload-tool.ts} +0 -0
- /package/src/commands/app/tools/{select.ts → create-select-tool.ts} +0 -0
- /package/src/commands/app/tools/{snapshot.ts → create-snapshot-tool.ts} +0 -0
- /package/src/commands/app/tools/{type-text.ts → create-type-text-tool.ts} +0 -0
|
@@ -2,17 +2,18 @@ import { createCommand } from 'commander';
|
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
3
|
|
|
4
4
|
import { resetDaemon, cleanup, createRunDir } from '../../utils/index.js';
|
|
5
|
-
import { runLogin } from './login.js';
|
|
6
|
-
import { resolveEvalCommandInput
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
5
|
+
import { runLogin } from './run-login.js';
|
|
6
|
+
import { resolveEvalCommandInput } from '../../resolve-eval-command-input.js';
|
|
7
|
+
import { resolveEvalFiles } from '../../resolve-eval-files.js';
|
|
8
|
+
import { createSession } from './create-session.js';
|
|
9
|
+
import { startAppServer } from './start-app-server.js';
|
|
10
|
+
import { attachSessionLogger } from './attach-session-logger.js';
|
|
10
11
|
import type { CopilotEvalOptions, Plan, Verdict } from './types.js';
|
|
11
|
-
import { formatVerdict } from './format.js';
|
|
12
|
+
import { formatVerdict } from './format-verdict.js';
|
|
12
13
|
import { existsSync, readFileSync } from 'node:fs';
|
|
13
|
-
import { createPlanPrompt } from './prompts/plan
|
|
14
|
-
import { createStepPrompt } from './prompts/step
|
|
15
|
-
import { createJudgePrompt } from './prompts/judge
|
|
14
|
+
import { createPlanPrompt } from './prompts/create-plan-prompt.js';
|
|
15
|
+
import { createStepPrompt } from './prompts/create-step-prompt.js';
|
|
16
|
+
import { createJudgePrompt } from './prompts/create-judge-prompt.js';
|
|
16
17
|
import chalk from 'chalk';
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -58,6 +59,7 @@ const _appEvalCommand = createCommand('eval')
|
|
|
58
59
|
const port = parseInt(options.port, 10);
|
|
59
60
|
const verbose = options.verbose;
|
|
60
61
|
|
|
62
|
+
// Reject ambiguous input that selects an eval two different ways
|
|
61
63
|
if (resolvedInput.evalFile && options.eval) {
|
|
62
64
|
console.error('❌ Do not combine a positional eval markdown file with --eval');
|
|
63
65
|
process.exit(1);
|
|
@@ -73,9 +75,9 @@ const _appEvalCommand = createCommand('eval')
|
|
|
73
75
|
const evalFiles = resolvedInput.evalFile
|
|
74
76
|
? [resolvedInput.evalFile]
|
|
75
77
|
: resolveEvalFiles(absAppPath, options.eval);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
);
|
|
78
|
+
// Summarize which eval files were discovered before the run starts
|
|
79
|
+
const evalFileNames = evalFiles.map((f) => basename(f)).join(', ');
|
|
80
|
+
console.log(`📋 Found ${evalFiles.length} eval(s): ${evalFileNames}`);
|
|
79
81
|
|
|
80
82
|
// Start the app dev server (unless --url provides an already-running URL)
|
|
81
83
|
const { serverProcess, appUrl } = await startAppServer(absAppPath, {
|
|
@@ -103,6 +105,7 @@ const _appEvalCommand = createCommand('eval')
|
|
|
103
105
|
|
|
104
106
|
let failures = 0;
|
|
105
107
|
try {
|
|
108
|
+
// Run every resolved eval file sequentially so output stays readable
|
|
106
109
|
for (const evalFilePath of evalFiles) {
|
|
107
110
|
const evalName = basename(evalFilePath, '.md');
|
|
108
111
|
const query = readFileSync(evalFilePath, 'utf-8').trim();
|
|
@@ -129,6 +132,7 @@ const _appEvalCommand = createCommand('eval')
|
|
|
129
132
|
await session.sendAndWait({ prompt: createPlanPrompt(query, ctx) }, 300_000);
|
|
130
133
|
|
|
131
134
|
const planPath = `${runDir}/plan.json`;
|
|
135
|
+
// Bail out of this eval when the planning phase produced no plan file
|
|
132
136
|
if (!existsSync(planPath)) {
|
|
133
137
|
console.error(
|
|
134
138
|
chalk.red(
|
|
@@ -138,6 +142,7 @@ const _appEvalCommand = createCommand('eval')
|
|
|
138
142
|
session.disconnect();
|
|
139
143
|
logger.stop();
|
|
140
144
|
failures++;
|
|
145
|
+
// Skip execution/judging phases; this eval already failed at planning
|
|
141
146
|
continue;
|
|
142
147
|
}
|
|
143
148
|
|
|
@@ -147,6 +152,7 @@ const _appEvalCommand = createCommand('eval')
|
|
|
147
152
|
// ── Phase 2: Execute + Evaluate each step ──────────────────
|
|
148
153
|
for (const step of plan.steps) {
|
|
149
154
|
console.log(chalk.cyan(`\n── Scenario: ${step.scenario}`));
|
|
155
|
+
// Print each acceptance criterion the agent will be judged against
|
|
150
156
|
for (const c of step.criteria) {
|
|
151
157
|
console.log(chalk.dim(` • ${c}`));
|
|
152
158
|
}
|
|
@@ -162,9 +168,11 @@ const _appEvalCommand = createCommand('eval')
|
|
|
162
168
|
|
|
163
169
|
// Print the verdict
|
|
164
170
|
const verdictPath = `${runDir}/verdict.json`;
|
|
171
|
+
// Only report a verdict when the judge phase actually produced one
|
|
165
172
|
if (existsSync(verdictPath)) {
|
|
166
173
|
const verdict = JSON.parse(readFileSync(verdictPath, 'utf-8')) as Verdict;
|
|
167
174
|
console.log(formatVerdict(verdict));
|
|
175
|
+
// A failing verdict counts toward the overall exit-code failure total
|
|
168
176
|
if (!verdict.pass) failures++;
|
|
169
177
|
} else {
|
|
170
178
|
console.error(chalk.red(`❌ Judge did not produce ${verdictPath}.`));
|
|
@@ -174,6 +182,7 @@ const _appEvalCommand = createCommand('eval')
|
|
|
174
182
|
logger?.stop();
|
|
175
183
|
const msg = err instanceof Error ? err.message : String(err);
|
|
176
184
|
console.error(chalk.red(`\n❌ Eval "${evalName}" failed: ${msg}`));
|
|
185
|
+
// Print the stack trace too when one is available for debugging
|
|
177
186
|
if (err instanceof Error && err.stack) {
|
|
178
187
|
console.error(chalk.dim(err.stack));
|
|
179
188
|
}
|
|
@@ -3,12 +3,13 @@ import { join } from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import { ab, abErrorMessage } from '../../utils/index.js';
|
|
5
5
|
import { createAgentBrowserTools } from './tools/index.js';
|
|
6
|
-
import { createWriteFileTool
|
|
7
|
-
import {
|
|
6
|
+
import { createWriteFileTool } from './tools/create-write-file-tool.js';
|
|
7
|
+
import { createAppendFileTool } from './tools/create-append-file-tool.js';
|
|
8
|
+
import { createReadFileTool } from './tools/create-read-file-tool.js';
|
|
8
9
|
import type { RuntimeExecutionContext } from './types.js';
|
|
9
10
|
import type { CopilotSession, SessionConfig } from '@github/copilot-sdk';
|
|
10
|
-
import { createSystemPrompt } from './prompts/system
|
|
11
|
-
import { createDevServerContext } from './prompts/
|
|
11
|
+
import { createSystemPrompt } from './prompts/create-system-prompt.js';
|
|
12
|
+
import { createDevServerContext } from './prompts/create-dev-server-context.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Lazily imports the Copilot SDK to avoid module resolution errors at CLI startup.
|
|
@@ -35,7 +36,7 @@ export const createSession = async (options: SessionOptions): Promise<CopilotSes
|
|
|
35
36
|
const { ctx, config, defaultToolTimeoutMs } = options;
|
|
36
37
|
|
|
37
38
|
const { CopilotClient, defineTool, approveAll } = await loadCopilotSdk();
|
|
38
|
-
const client = new CopilotClient(
|
|
39
|
+
const client = new CopilotClient();
|
|
39
40
|
await client.start();
|
|
40
41
|
|
|
41
42
|
const fileTools = [
|
|
@@ -44,6 +45,7 @@ export const createSession = async (options: SessionOptions): Promise<CopilotSes
|
|
|
44
45
|
createAppendFileTool(ctx.outDir, defineTool),
|
|
45
46
|
];
|
|
46
47
|
|
|
48
|
+
// Combine built-in browser and file tools with any caller-supplied tools
|
|
47
49
|
const tools = [
|
|
48
50
|
...createBrowserTools(defineTool, {
|
|
49
51
|
cwd: ctx.outDir,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
import type { Plan } from './types.js';
|
|
4
|
+
import { truncate } from './truncate.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders a structured plan as a human-readable summary.
|
|
8
|
+
*
|
|
9
|
+
* @param plan - The plan to render, containing a summary and ordered steps.
|
|
10
|
+
* @returns A multi-line string suitable for console output.
|
|
11
|
+
*/
|
|
12
|
+
export function formatPlan(plan: Plan): string {
|
|
13
|
+
const lines = [
|
|
14
|
+
'',
|
|
15
|
+
chalk.bold(`📋 ${plan.summary}`),
|
|
16
|
+
'',
|
|
17
|
+
...plan.steps.flatMap((s, si) => [
|
|
18
|
+
` ${chalk.dim(`${si + 1}.`)} ${s.scenario}`,
|
|
19
|
+
...s.criteria
|
|
20
|
+
// Truncate each criterion so long descriptions don't wrap the terminal
|
|
21
|
+
.map((c) => chalk.dim(` • ${truncate(c, 70)}`)),
|
|
22
|
+
]),
|
|
23
|
+
'',
|
|
24
|
+
];
|
|
25
|
+
return lines.join('\n');
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
import type { StepResult } from './types.js';
|
|
4
|
+
import { statusIcon } from './status-icon.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders a single step execution result.
|
|
8
|
+
*
|
|
9
|
+
* @param result - The step result to render, including its outcome, note, and evidence.
|
|
10
|
+
* @returns A formatted two-line string describing the step outcome.
|
|
11
|
+
*/
|
|
12
|
+
export function formatStepResult(result: StepResult): string {
|
|
13
|
+
const icon = statusIcon(result.ok);
|
|
14
|
+
const files = result.evidence.length ? chalk.dim(` [${result.evidence.join(', ')}]`) : '';
|
|
15
|
+
return ` ${icon} ${result.criterion}\n${chalk.dim(` ${result.note}`)}${files}`;
|
|
16
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
import type { Verdict } from './types.js';
|
|
4
|
+
import { statusIcon } from './status-icon.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders the final verdict as a full-width coloured report.
|
|
8
|
+
*
|
|
9
|
+
* @param verdict - The verdict to render, including per-step results, reasoning, and UX feedback.
|
|
10
|
+
* @returns A multi-line string suitable for console output.
|
|
11
|
+
*/
|
|
12
|
+
export function formatVerdict(verdict: Verdict): string {
|
|
13
|
+
const bar = '═'.repeat(60);
|
|
14
|
+
const banner = verdict.pass
|
|
15
|
+
? chalk.bgGreen.black.bold(' ✅ PASS ')
|
|
16
|
+
: chalk.bgRed.white.bold(' ❌ FAIL ');
|
|
17
|
+
|
|
18
|
+
const lines = [
|
|
19
|
+
'',
|
|
20
|
+
bar,
|
|
21
|
+
` ${banner}`,
|
|
22
|
+
bar,
|
|
23
|
+
'',
|
|
24
|
+
...verdict.steps
|
|
25
|
+
// Render each step's outcome as a status glyph, criterion, and note
|
|
26
|
+
.map((s) => ` ${statusIcon(s.ok)} ${s.criterion}\n${chalk.dim(` ${s.note}`)}`),
|
|
27
|
+
'',
|
|
28
|
+
chalk.dim(` ${verdict.reasoning}`),
|
|
29
|
+
'',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// Append optional UX feedback only when the judge actually provided any
|
|
33
|
+
if (verdict.ux && verdict.ux.length > 0) {
|
|
34
|
+
lines.push(chalk.bold(' 💡 UX Feedback'), '');
|
|
35
|
+
// List every UX hint as its own bullet line
|
|
36
|
+
for (const hint of verdict.ux) {
|
|
37
|
+
lines.push(chalk.yellow(` • ${hint}`));
|
|
38
|
+
}
|
|
39
|
+
lines.push('');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
}
|
|
@@ -19,6 +19,7 @@ function collectEvidence(outDir: string): string {
|
|
|
19
19
|
|
|
20
20
|
// ── executions.jsonl ──
|
|
21
21
|
const execPath = join(outDir, 'executions.jsonl');
|
|
22
|
+
// Fall back to a placeholder note when the executions log is missing
|
|
22
23
|
if (existsSync(execPath)) {
|
|
23
24
|
parts.push('### executions.jsonl', readFileSync(execPath, 'utf-8').trim());
|
|
24
25
|
} else {
|
|
@@ -27,10 +28,13 @@ function collectEvidence(outDir: string): string {
|
|
|
27
28
|
|
|
28
29
|
// ── evidence/ directory ──
|
|
29
30
|
const evidenceDir = join(outDir, 'evidence');
|
|
31
|
+
// Only scan for evidence files when the directory actually exists
|
|
30
32
|
if (existsSync(evidenceDir)) {
|
|
31
33
|
const files = readdirSync(evidenceDir).sort();
|
|
34
|
+
// Inline readable text evidence; list binary evidence by name only
|
|
32
35
|
for (const file of files) {
|
|
33
36
|
const ext = extname(file).toLowerCase();
|
|
37
|
+
// Inline the content of readable text extensions; list other files by name only
|
|
34
38
|
if (TEXT_EXTENSIONS.has(ext)) {
|
|
35
39
|
const content = readFileSync(join(evidenceDir, file), 'utf-8').trim();
|
|
36
40
|
parts.push(`### evidence/${file}`, content);
|
|
@@ -10,8 +10,9 @@ import type { PlanStep, RuntimeExecutionContext } from '../types.js';
|
|
|
10
10
|
* @param ctx - Runtime context with output directory and app URL
|
|
11
11
|
* @returns The fully interpolated prompt string
|
|
12
12
|
*/
|
|
13
|
-
export const createStepPrompt = (step: PlanStep,
|
|
13
|
+
export const createStepPrompt = (step: PlanStep, _ctx: RuntimeExecutionContext): string => {
|
|
14
14
|
const slug = slugify(step.scenario);
|
|
15
|
+
// Render each criterion with its expected pass/fail evidence for the prompt
|
|
15
16
|
const criteriaBlock = step.criteria
|
|
16
17
|
.map((c, i) => `${i + 1}. ${c}\n Pass: ${step.pass[i] ?? ''}\n Fail: ${step.fail[i] ?? ''}`)
|
|
17
18
|
.join('\n');
|
|
@@ -28,11 +28,13 @@ import {
|
|
|
28
28
|
*/
|
|
29
29
|
export async function runLogin(absAppPath: string, port: number, host: string): Promise<void> {
|
|
30
30
|
const pkgJsonPath = join(absAppPath, 'package.json');
|
|
31
|
+
// Login needs a valid Fusion app directory; bail out clearly if this isn't one
|
|
31
32
|
if (!existsSync(pkgJsonPath)) {
|
|
32
33
|
console.error(`❌ No package.json found at ${absAppPath}`);
|
|
33
34
|
process.exit(1);
|
|
34
35
|
}
|
|
35
36
|
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as { name?: string };
|
|
37
|
+
// The app name is required to resolve the appKey later
|
|
36
38
|
if (!pkgJson.name) {
|
|
37
39
|
console.error(`❌ package.json at ${absAppPath} has no "name" field`);
|
|
38
40
|
process.exit(1);
|
|
@@ -51,6 +53,7 @@ export async function runLogin(absAppPath: string, port: number, host: string):
|
|
|
51
53
|
|
|
52
54
|
const serverUrl = `http://localhost:${port}`;
|
|
53
55
|
const ready = await waitForServer(serverUrl, 60);
|
|
56
|
+
// Abort login when the dev server never comes up within the timeout
|
|
54
57
|
if (!ready) {
|
|
55
58
|
console.error('❌ Server failed to start within 60s');
|
|
56
59
|
cleanup(serverProcess);
|
|
@@ -79,6 +82,7 @@ export async function runLogin(absAppPath: string, port: number, host: string):
|
|
|
79
82
|
300_000,
|
|
80
83
|
);
|
|
81
84
|
|
|
85
|
+
// Report success or a soft timeout without failing the login flow
|
|
82
86
|
if (initialized) {
|
|
83
87
|
await sleep(2000);
|
|
84
88
|
console.log('✅ Login complete — session saved.');
|
|
@@ -22,10 +22,12 @@ export interface AppServerResult {
|
|
|
22
22
|
*/
|
|
23
23
|
function freePort(port: number): void {
|
|
24
24
|
try {
|
|
25
|
+
// Drop the empty string lsof emits when no matching process was found
|
|
25
26
|
const pids = execSync(`lsof -ti tcp:${port}`, { encoding: 'utf-8' })
|
|
26
27
|
.trim()
|
|
27
28
|
.split('\n')
|
|
28
29
|
.filter(Boolean);
|
|
30
|
+
// Terminate each stale process bound to the port
|
|
29
31
|
for (const pid of pids) {
|
|
30
32
|
try {
|
|
31
33
|
process.kill(parseInt(pid, 10), 'SIGTERM');
|
|
@@ -33,6 +35,7 @@ function freePort(port: number): void {
|
|
|
33
35
|
// already dead
|
|
34
36
|
}
|
|
35
37
|
}
|
|
38
|
+
// Only log when we actually killed something
|
|
36
39
|
if (pids.length) {
|
|
37
40
|
console.log(`🔄 Killed stale process(es) on port ${port}: ${pids.join(', ')}`);
|
|
38
41
|
}
|
|
@@ -63,11 +66,13 @@ export async function startAppServer(
|
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
const pkgJsonPath = join(absAppPath, 'package.json');
|
|
69
|
+
// Startup needs a valid Fusion app directory; bail out clearly if this isn't one
|
|
66
70
|
if (!existsSync(pkgJsonPath)) {
|
|
67
71
|
console.error(`❌ No package.json found at ${absAppPath}`);
|
|
68
72
|
process.exit(1);
|
|
69
73
|
}
|
|
70
74
|
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as { name?: string };
|
|
75
|
+
// The app name is required to resolve the appKey later
|
|
71
76
|
if (!pkgJson.name) {
|
|
72
77
|
console.error(`❌ package.json at ${absAppPath} has no "name" field`);
|
|
73
78
|
process.exit(1);
|
|
@@ -92,6 +97,7 @@ export async function startAppServer(
|
|
|
92
97
|
|
|
93
98
|
const serverUrl = `http://localhost:${options.port}`;
|
|
94
99
|
const ready = await waitForServer(serverUrl, 60);
|
|
100
|
+
// Abort startup when the dev server never comes up within the timeout
|
|
95
101
|
if (!ready) {
|
|
96
102
|
console.error('❌ Server failed to start within 60s');
|
|
97
103
|
cleanup(serverProcess);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Renders a status glyph for a step or verdict outcome.
|
|
5
|
+
*
|
|
6
|
+
* @param ok - Outcome value: `true` (pass), `false` (fail), or a string label
|
|
7
|
+
* such as `'blocked'` or `'flaky'` for anything in between.
|
|
8
|
+
* @returns A single coloured glyph representing the outcome.
|
|
9
|
+
*/
|
|
10
|
+
export function statusIcon(ok: boolean | string): string {
|
|
11
|
+
// A literal boolean outcome maps directly to pass/fail glyphs
|
|
12
|
+
if (ok === true) return chalk.green('✔');
|
|
13
|
+
// An explicit failure gets its own glyph, distinct from unknown/other states
|
|
14
|
+
if (ok === false) return chalk.red('✖');
|
|
15
|
+
return chalk.yellow('⚠'); // "blocked", "flaky", etc.
|
|
16
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendFileSync, mkdirSync
|
|
1
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { dirname, isAbsolute, join } from 'node:path';
|
|
3
3
|
|
|
4
4
|
import type { DefineTool } from './types.js';
|
|
@@ -18,55 +18,20 @@ import type { DefineTool } from './types.js';
|
|
|
18
18
|
function resolveSafe(baseDir: string, input: string): string {
|
|
19
19
|
// If the model echoed the full absolute path from the prompt, strip the base prefix.
|
|
20
20
|
const relative = isAbsolute(input)
|
|
21
|
-
? input.startsWith(baseDir
|
|
21
|
+
? input.startsWith(`${baseDir}/`) || input.startsWith(`${baseDir}\\`)
|
|
22
22
|
? input.slice(baseDir.length + 1)
|
|
23
23
|
: (() => {
|
|
24
24
|
throw new Error(`Absolute path outside output directory: ${input}`);
|
|
25
25
|
})()
|
|
26
26
|
: input;
|
|
27
27
|
const resolved = join(baseDir, relative);
|
|
28
|
+
// Reject any resolved path that escapes the allowed base directory
|
|
28
29
|
if (!resolved.startsWith(baseDir)) {
|
|
29
30
|
throw new Error(`Path escapes the output directory: ${input}`);
|
|
30
31
|
}
|
|
31
32
|
return resolved;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
/**
|
|
35
|
-
* Creates the write-file tool so the agent can save artifacts
|
|
36
|
-
* (plan.json, verdict.json, etc.) to the run output directory.
|
|
37
|
-
*
|
|
38
|
-
* @param baseDir - The run output directory that scopes all writes
|
|
39
|
-
* @param defineTool - Copilot SDK helper used to declare tools
|
|
40
|
-
* @returns Copilot tool definition for writing a file
|
|
41
|
-
*/
|
|
42
|
-
export function createWriteFileTool(baseDir: string, defineTool: DefineTool) {
|
|
43
|
-
return defineTool('write_file', {
|
|
44
|
-
description:
|
|
45
|
-
'Write content to a file in the output directory. Creates parent directories as needed.',
|
|
46
|
-
parameters: {
|
|
47
|
-
type: 'object' as const,
|
|
48
|
-
properties: {
|
|
49
|
-
path: {
|
|
50
|
-
type: 'string',
|
|
51
|
-
description: 'File path relative to the output directory (e.g. "plan.json")',
|
|
52
|
-
},
|
|
53
|
-
content: {
|
|
54
|
-
type: 'string',
|
|
55
|
-
description: 'The full file content to write',
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
required: ['path', 'content'],
|
|
59
|
-
},
|
|
60
|
-
handler: async (args) => {
|
|
61
|
-
const { path, content } = args as { path: string; content: string };
|
|
62
|
-
const resolved = resolveSafe(baseDir, path);
|
|
63
|
-
mkdirSync(dirname(resolved), { recursive: true });
|
|
64
|
-
writeFileSync(resolved, content);
|
|
65
|
-
return `Wrote ${content.length} bytes to ${path}`;
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
35
|
/**
|
|
71
36
|
* Creates the append-file tool so the agent can append lines
|
|
72
37
|
* to artifacts like executions.jsonl.
|
|
@@ -97,7 +62,7 @@ export function createAppendFileTool(baseDir: string, defineTool: DefineTool) {
|
|
|
97
62
|
const { path, content } = args as { path: string; content: string };
|
|
98
63
|
const resolved = resolveSafe(baseDir, path);
|
|
99
64
|
mkdirSync(dirname(resolved), { recursive: true });
|
|
100
|
-
appendFileSync(resolved, content.endsWith('\n') ? content : content
|
|
65
|
+
appendFileSync(resolved, content.endsWith('\n') ? content : `${content}\n`);
|
|
101
66
|
return `Appended to ${path}`;
|
|
102
67
|
},
|
|
103
68
|
});
|
|
@@ -40,8 +40,11 @@ export function createFindTool(context: AgentBrowserToolContext, defineTool: Def
|
|
|
40
40
|
fillValue?: string;
|
|
41
41
|
};
|
|
42
42
|
const command = ['find', by, value];
|
|
43
|
+
// Append optional refinements only when the caller actually provided them
|
|
43
44
|
if (action) command.push(action);
|
|
45
|
+
// Attach the accessible-name filter when the caller supplied one
|
|
44
46
|
if (name) command.push('--name', name);
|
|
47
|
+
// Attach the fill value when the action is 'fill'
|
|
45
48
|
if (fillValue) command.push(fillValue);
|
|
46
49
|
return context.invoke(command);
|
|
47
50
|
},
|
|
@@ -27,13 +27,14 @@ const IMAGE_MIME: Record<string, string> = {
|
|
|
27
27
|
*/
|
|
28
28
|
function resolveSafe(baseDir: string, input: string): string {
|
|
29
29
|
const relative = isAbsolute(input)
|
|
30
|
-
? input.startsWith(baseDir
|
|
30
|
+
? input.startsWith(`${baseDir}/`) || input.startsWith(`${baseDir}\\`)
|
|
31
31
|
? input.slice(baseDir.length + 1)
|
|
32
32
|
: (() => {
|
|
33
33
|
throw new Error(`Absolute path outside output directory: ${input}`);
|
|
34
34
|
})()
|
|
35
35
|
: input;
|
|
36
36
|
const resolved = join(baseDir, relative);
|
|
37
|
+
// Reject any resolved path that escapes the allowed base directory
|
|
37
38
|
if (!resolved.startsWith(baseDir)) {
|
|
38
39
|
throw new Error(`Path escapes the output directory: ${input}`);
|
|
39
40
|
}
|
|
@@ -72,6 +73,7 @@ export function createReadFileTool(baseDir: string, defineTool: DefineTool) {
|
|
|
72
73
|
const { path } = args as { path: string };
|
|
73
74
|
const resolved = resolveSafe(baseDir, path);
|
|
74
75
|
|
|
76
|
+
// Report a friendly message instead of throwing when the path doesn't exist
|
|
75
77
|
if (!existsSync(resolved)) {
|
|
76
78
|
return `File not found: ${path}`;
|
|
77
79
|
}
|
|
@@ -26,15 +26,18 @@ function resolveScreenshotArtifactName(
|
|
|
26
26
|
requestedPath: string | undefined,
|
|
27
27
|
format: 'jpeg' | 'png',
|
|
28
28
|
): string | undefined {
|
|
29
|
+
// No requested path means the caller doesn't need a specific artifact name
|
|
29
30
|
if (!requestedPath) return undefined;
|
|
30
31
|
const fileName = basename(requestedPath);
|
|
31
32
|
const currentExtension = extname(fileName).toLowerCase();
|
|
32
33
|
const expectedExtension = format === 'jpeg' ? '.jpg' : '.png';
|
|
33
34
|
|
|
35
|
+
// The extension already matches the selected format; reuse it verbatim
|
|
34
36
|
if (currentExtension === expectedExtension) {
|
|
35
37
|
return fileName;
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
// Swap a mismatched extension for the one that matches the selected format
|
|
38
41
|
if (currentExtension.length > 0) {
|
|
39
42
|
return `${fileName.slice(0, -currentExtension.length)}${expectedExtension}`;
|
|
40
43
|
}
|
|
@@ -53,6 +56,7 @@ function resolveScreenshotDirectory(
|
|
|
53
56
|
evidenceDir: string,
|
|
54
57
|
requestedDirectory: string | undefined,
|
|
55
58
|
): string {
|
|
59
|
+
// No requested directory means the default evidence directory is used
|
|
56
60
|
if (!requestedDirectory) {
|
|
57
61
|
return evidenceDir;
|
|
58
62
|
}
|
|
@@ -150,10 +154,12 @@ export function createScreenshotTool(context: AgentBrowserToolContext, defineToo
|
|
|
150
154
|
screenshotDirectory,
|
|
151
155
|
];
|
|
152
156
|
|
|
157
|
+
// Overlay numbered element labels only when the model explicitly asked for it
|
|
153
158
|
if (annotate) {
|
|
154
159
|
command.push('--annotate');
|
|
155
160
|
}
|
|
156
161
|
|
|
162
|
+
// JPEG quality only applies when the selected format is actually JPEG
|
|
157
163
|
if (screenshotFormat === 'jpeg') {
|
|
158
164
|
const screenshotQuality = Math.min(100, Math.max(0, quality ?? 60));
|
|
159
165
|
command.push('--screenshot-quality', String(screenshotQuality));
|
|
@@ -33,11 +33,13 @@ export function createScrollTool(context: AgentBrowserToolContext, defineTool: D
|
|
|
33
33
|
amount?: number;
|
|
34
34
|
target?: string;
|
|
35
35
|
};
|
|
36
|
+
// A target ref overrides plain direction/amount scrolling
|
|
36
37
|
if (target) {
|
|
37
38
|
return context.invoke(['scrollintoview', target]);
|
|
38
39
|
}
|
|
39
40
|
const scrollDirection = direction ?? 'down';
|
|
40
41
|
const command = ['scroll', scrollDirection];
|
|
42
|
+
// Only pass an explicit pixel amount when the caller provided one
|
|
41
43
|
if (amount) command.push(String(amount));
|
|
42
44
|
return context.invoke(command);
|
|
43
45
|
},
|
|
@@ -30,9 +30,13 @@ export function createWaitTool(context: AgentBrowserToolContext, defineTool: Def
|
|
|
30
30
|
load?: string;
|
|
31
31
|
timeout?: number;
|
|
32
32
|
};
|
|
33
|
+
// Prefer whichever single wait condition the caller specified, in priority order
|
|
33
34
|
if (text) return context.invoke(['wait', '--text', text], 60_000);
|
|
35
|
+
// Fall back to waiting on a page load state
|
|
34
36
|
if (load) return context.invoke(['wait', '--load', load], 60_000);
|
|
37
|
+
// Fall back to waiting on a specific element/selector
|
|
35
38
|
if (selector) return context.invoke(['wait', selector], 60_000);
|
|
39
|
+
// A plain timeout falls back to a bare delay instead of an invoke command
|
|
36
40
|
if (timeout) {
|
|
37
41
|
await new Promise((resolve) => setTimeout(resolve, timeout));
|
|
38
42
|
return `Waited ${timeout}ms`;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, isAbsolute, join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import type { DefineTool } from './types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolves a path within a base directory, rejecting escapes.
|
|
8
|
+
*
|
|
9
|
+
* Accepts both relative paths and absolute paths that fall within
|
|
10
|
+
* the base directory. Strips the base prefix from absolute paths
|
|
11
|
+
* before resolving to handle models that echo full paths from prompts.
|
|
12
|
+
*
|
|
13
|
+
* @param baseDir - The allowed root directory
|
|
14
|
+
* @param input - The user-provided path (relative or absolute within baseDir)
|
|
15
|
+
* @returns The resolved absolute path
|
|
16
|
+
* @throws {Error} When the path escapes the base directory
|
|
17
|
+
*/
|
|
18
|
+
function resolveSafe(baseDir: string, input: string): string {
|
|
19
|
+
// If the model echoed the full absolute path from the prompt, strip the base prefix.
|
|
20
|
+
const relative = isAbsolute(input)
|
|
21
|
+
? input.startsWith(`${baseDir}/`) || input.startsWith(`${baseDir}\\`)
|
|
22
|
+
? input.slice(baseDir.length + 1)
|
|
23
|
+
: (() => {
|
|
24
|
+
throw new Error(`Absolute path outside output directory: ${input}`);
|
|
25
|
+
})()
|
|
26
|
+
: input;
|
|
27
|
+
const resolved = join(baseDir, relative);
|
|
28
|
+
// Reject any resolved path that escapes the allowed base directory
|
|
29
|
+
if (!resolved.startsWith(baseDir)) {
|
|
30
|
+
throw new Error(`Path escapes the output directory: ${input}`);
|
|
31
|
+
}
|
|
32
|
+
return resolved;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates the write-file tool so the agent can save artifacts
|
|
37
|
+
* (plan.json, verdict.json, etc.) to the run output directory.
|
|
38
|
+
*
|
|
39
|
+
* @param baseDir - The run output directory that scopes all writes
|
|
40
|
+
* @param defineTool - Copilot SDK helper used to declare tools
|
|
41
|
+
* @returns Copilot tool definition for writing a file
|
|
42
|
+
*/
|
|
43
|
+
export function createWriteFileTool(baseDir: string, defineTool: DefineTool) {
|
|
44
|
+
return defineTool('write_file', {
|
|
45
|
+
description:
|
|
46
|
+
'Write content to a file in the output directory. Creates parent directories as needed.',
|
|
47
|
+
parameters: {
|
|
48
|
+
type: 'object' as const,
|
|
49
|
+
properties: {
|
|
50
|
+
path: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'File path relative to the output directory (e.g. "plan.json")',
|
|
53
|
+
},
|
|
54
|
+
content: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description: 'The full file content to write',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ['path', 'content'],
|
|
60
|
+
},
|
|
61
|
+
handler: async (args) => {
|
|
62
|
+
const { path, content } = args as { path: string; content: string };
|
|
63
|
+
const resolved = resolveSafe(baseDir, path);
|
|
64
|
+
mkdirSync(dirname(resolved), { recursive: true });
|
|
65
|
+
writeFileSync(resolved, content);
|
|
66
|
+
return `Wrote ${content.length} bytes to ${path}`;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { createClickTool } from './click.js';
|
|
2
|
-
import { createErrorsTool } from './errors.js';
|
|
3
|
-
import { createFillTool } from './fill.js';
|
|
4
|
-
import { createFindTool } from './find.js';
|
|
5
|
-
import { createGetUrlTool } from './get-url.js';
|
|
6
|
-
import { createGoBackTool } from './go-back.js';
|
|
7
|
-
import { createHoverTool } from './hover.js';
|
|
8
|
-
import { createNavigateTool } from './navigate.js';
|
|
9
|
-
import { createPressKeyTool } from './press-key.js';
|
|
10
|
-
import { createReloadTool } from './reload.js';
|
|
11
|
-
import { createEvalJsTool } from './eval-js.js';
|
|
12
|
-
import { createGetStylesTool } from './get-styles.js';
|
|
13
|
-
import { AgentBrowserToolRegistry } from './
|
|
14
|
-
import { createScreenshotTool } from './screenshot.js';
|
|
15
|
-
import { createScrollTool } from './scroll.js';
|
|
16
|
-
import { createSelectTool } from './select.js';
|
|
17
|
-
import { createSnapshotTool } from './snapshot.js';
|
|
18
|
-
import { createTypeTextTool } from './type-text.js';
|
|
1
|
+
import { createClickTool } from './create-click-tool.js';
|
|
2
|
+
import { createErrorsTool } from './create-errors-tool.js';
|
|
3
|
+
import { createFillTool } from './create-fill-tool.js';
|
|
4
|
+
import { createFindTool } from './create-find-tool.js';
|
|
5
|
+
import { createGetUrlTool } from './create-get-url-tool.js';
|
|
6
|
+
import { createGoBackTool } from './create-go-back-tool.js';
|
|
7
|
+
import { createHoverTool } from './create-hover-tool.js';
|
|
8
|
+
import { createNavigateTool } from './create-navigate-tool.js';
|
|
9
|
+
import { createPressKeyTool } from './create-press-key-tool.js';
|
|
10
|
+
import { createReloadTool } from './create-reload-tool.js';
|
|
11
|
+
import { createEvalJsTool } from './create-eval-js-tool.js';
|
|
12
|
+
import { createGetStylesTool } from './create-get-styles-tool.js';
|
|
13
|
+
import { AgentBrowserToolRegistry } from './AgentBrowserToolRegistry.js';
|
|
14
|
+
import { createScreenshotTool } from './create-screenshot-tool.js';
|
|
15
|
+
import { createScrollTool } from './create-scroll-tool.js';
|
|
16
|
+
import { createSelectTool } from './create-select-tool.js';
|
|
17
|
+
import { createSnapshotTool } from './create-snapshot-tool.js';
|
|
18
|
+
import { createTypeTextTool } from './create-type-text-tool.js';
|
|
19
19
|
import type { AgentBrowserToolContext, AgentBrowserToolList, DefineTool } from './types.js';
|
|
20
|
-
import { createWaitTool } from './wait.js';
|
|
20
|
+
import { createWaitTool } from './create-wait-tool.js';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Creates the full set of Copilot browser tools for an eval session.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncates a string to a maximum length, appending an ellipsis when cut.
|
|
3
|
+
*
|
|
4
|
+
* @param str - The string to truncate.
|
|
5
|
+
* @param max - Maximum length of the returned string, including the ellipsis.
|
|
6
|
+
* @returns The original string, or a shortened version ending in `…`.
|
|
7
|
+
*/
|
|
8
|
+
export function truncate(str: string, max: number): string {
|
|
9
|
+
return str.length > max ? `${str.slice(0, max - 1)}…` : str;
|
|
10
|
+
}
|