@bugmole/cli 0.4.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/.bugmole.env.example +20 -0
- package/LICENSE +7 -0
- package/README.md +293 -0
- package/TESTING.md +117 -0
- package/bugmole.config.yaml +39 -0
- package/package.json +85 -0
- package/scripts/billing/paypal-setup.mjs +121 -0
- package/scripts/bugmole-continue.ts +318 -0
- package/scripts/bugmole-init.ts +188 -0
- package/scripts/bugmole.cjs +17 -0
- package/scripts/bugmole.test.ts +344 -0
- package/scripts/bugmole.ts +657 -0
- package/scripts/ensure-maestro.cjs +79 -0
- package/scripts/ios-tunnel-keeper.sh +45 -0
- package/scripts/ios-wda-keeper.sh +66 -0
- package/scripts/sync-plan-catalog.d.mts +3 -0
- package/scripts/sync-plan-catalog.mjs +16 -0
- package/scripts/ui-parity-diff.py +65 -0
- package/scripts/ui-parity-requirements.txt +1 -0
- package/scripts/verify-manage-to-plans.mts +194 -0
- package/spec/app-ui-audit.schema.json +176 -0
- package/spec/blockers.yaml +79 -0
- package/spec/bugs.index.json +42 -0
- package/spec/design-dna.schema.json +38 -0
- package/spec/domain_rules.yaml +24 -0
- package/spec/flows/flow_manage-to-plans-64c3c61b.yaml +8 -0
- package/spec/flows/flow_screen-to-evidence-6c3ab309.yaml +7 -0
- package/spec/journey_graph.yaml +126 -0
- package/spec/journeys.graph.json +2618 -0
- package/spec/plans/dashboard-smoke.flow.yaml +20 -0
- package/spec/plans/example.flow.yaml +99 -0
- package/spec/plans/flow_api-keys-to-logout-7396cd5d.flow.yaml +33 -0
- package/spec/plans/flow_api-keys-to-screen-157e10a6.flow.yaml +33 -0
- package/spec/plans/flow_manage-to-audits-176c1eb8.flow.yaml +33 -0
- package/spec/plans/flow_manage-to-blockers-633282c8.flow.yaml +112 -0
- package/spec/plans/flow_manage-to-devices-ad57e202.flow.yaml +33 -0
- package/spec/plans/flow_manage-to-logout-86c54656.flow.yaml +33 -0
- package/spec/plans/flow_manage-to-manage-resource-action-6325f99a.flow.yaml +185 -0
- package/spec/plans/flow_manage-to-parity-issues-01fa2579.flow.yaml +34 -0
- package/spec/plans/flow_manage-to-plans-64c3c61b.flow.yaml +123 -0
- package/spec/plans/flow_manage-to-screen-6857f915.flow.yaml +106 -0
- package/spec/plans/flow_manage-to-tasks-c5791c95.flow.yaml +33 -0
- package/spec/plans/flow_profile-to-screen-6045aa3d.flow.yaml +33 -0
- package/spec/plans/flow_register-to-api-auth-login-460a9cc8.flow.yaml +34 -0
- package/spec/plans/flow_screen-to-audits-07666356.flow.yaml +33 -0
- package/spec/plans/flow_screen-to-blockers-091d258b.flow.yaml +33 -0
- package/spec/plans/flow_screen-to-devices-cad72f75.flow.yaml +33 -0
- package/spec/plans/flow_screen-to-evidence-6c3ab309.flow.yaml +126 -0
- package/spec/plans/flow_screen-to-parity-issues-2218e3ad.flow.yaml +34 -0
- package/spec/plans/flow_screen-to-plans-8232a94f.flow.yaml +33 -0
- package/spec/plans/flow_screen-to-tasks-540671d1.flow.yaml +33 -0
- package/spec/plans/login.flow.yaml +22 -0
- package/spec/plans/owner-operations.flow.yaml +20 -0
- package/spec/project_config.yaml +55 -0
- package/spec/roles.yaml +30 -0
- package/spec/schema.md +394 -0
- package/spec/test-case-results.schema.json +62 -0
- package/spec/test-cases.schema.json +85 -0
- package/spec/ui-parity-audit.schema.json +194 -0
- package/spec/ui-reverse-engineering.schema.json +94 -0
- package/src/billing/plan-catalog.test.ts +46 -0
- package/src/billing/plan-catalog.ts +199 -0
- package/src/integrations/aws-sigv4.test.ts +42 -0
- package/src/integrations/aws-sigv4.ts +72 -0
- package/src/integrations/device-farm.ts +155 -0
- package/src/integrations/github-app.test.ts +57 -0
- package/src/integrations/github-app.ts +143 -0
- package/src/integrations/gitlab.ts +81 -0
- package/src/integrations/temp-email.test.ts +123 -0
- package/src/integrations/temp-email.ts +175 -0
- package/src/integrations/testflight-feedback.test.ts +51 -0
- package/src/integrations/testflight-feedback.ts +173 -0
- package/src/integrations/webdriver-client.ts +131 -0
- package/src/mcp/server.test.ts +1220 -0
- package/src/mcp/server.ts +3064 -0
- package/src/mcp/write-test-cases.test.ts +287 -0
- package/src/registry/api-key-client.ts +39 -0
- package/src/registry/control-plane-client.ts +212 -0
- package/src/registry/migrations/0001_registry.sql +47 -0
- package/src/registry/migrations/0002_device_authorizations.sql +23 -0
- package/src/registry/migrations/0003_project_environments.sql +25 -0
- package/src/registry/migrations/0004_device_authorization_email.sql +1 -0
- package/src/registry/migrations/0005_testing_control_plane.sql +55 -0
- package/src/registry/migrations/0006_workspaces.sql +36 -0
- package/src/registry/migrations/0007_project_apps.sql +26 -0
- package/src/registry/migrations/0008_agent_tasks.sql +30 -0
- package/src/registry/migrations/0009_journey_revisions.sql +17 -0
- package/src/registry/migrations/0010_agent_task_journey.sql +2 -0
- package/src/registry/migrations/0011_run_journey_revision.sql +1 -0
- package/src/registry/migrations/0012_canonical_flow_execution.sql +10 -0
- package/src/registry/migrations/0013_device_sessions.sql +22 -0
- package/src/registry/migrations/0014_agent_task_step.sql +1 -0
- package/src/registry/migrations/0015_agent_task_attempts.sql +6 -0
- package/src/registry/migrations/0016_github_issue_tracker.sql +54 -0
- package/src/registry/migrations/0017_run_targets.sql +7 -0
- package/src/registry/migrations/0018_run_fix_from.sql +4 -0
- package/src/registry/migrations/0019_workspace_flags.sql +9 -0
- package/src/registry/migrations/0020_orgs.sql +40 -0
- package/src/registry/migrations/0021_billing_core.sql +58 -0
- package/src/registry/migrations/0022_cloud_runners.sql +19 -0
- package/src/registry/migrations/0023_signup.sql +4 -0
- package/src/registry/migrations/0024_billing.sql +67 -0
- package/src/registry/migrations/0025_notifications.sql +47 -0
- package/src/registry/migrations/0026_repo_bindings.sql +28 -0
- package/src/registry/migrations/0027_feedback.sql +29 -0
- package/src/registry/migrations/0028_devices.sql +48 -0
- package/src/registry/migrations/0029_sso.sql +31 -0
- package/src/registry/migrations/0030_workspace_domains.sql +18 -0
- package/src/registry/migrations/0031_gitlab_and_teams.sql +45 -0
- package/src/registry/migrations/0032_personas.sql +15 -0
- package/src/registry/migrations/0033_bugmole_rename.sql +11 -0
- package/src/registry/task-scheduling.test.ts +100 -0
- package/src/registry/task-scheduling.ts +80 -0
- package/src/registry-worker/ai/platform-model.ts +77 -0
- package/src/registry-worker/ai/routes.test.ts +88 -0
- package/src/registry-worker/ai/routes.ts +90 -0
- package/src/registry-worker/artifacts.test.ts +98 -0
- package/src/registry-worker/artifacts.ts +85 -0
- package/src/registry-worker/billing/billing-core.test.ts +175 -0
- package/src/registry-worker/billing/checkout-routes.ts +209 -0
- package/src/registry-worker/billing/enforcement.ts +69 -0
- package/src/registry-worker/billing/entitlements.ts +108 -0
- package/src/registry-worker/billing/ledger.ts +186 -0
- package/src/registry-worker/billing/paypal/api.ts +259 -0
- package/src/registry-worker/billing/paypal/client.ts +91 -0
- package/src/registry-worker/billing/paypal/provider.ts +143 -0
- package/src/registry-worker/billing/paypal.test.ts +466 -0
- package/src/registry-worker/billing/provider.ts +114 -0
- package/src/registry-worker/billing/routes.ts +66 -0
- package/src/registry-worker/billing/subscriptions.ts +780 -0
- package/src/registry-worker/billing/thresholds.ts +107 -0
- package/src/registry-worker/core.ts +308 -0
- package/src/registry-worker/devices/devices.test.ts +185 -0
- package/src/registry-worker/devices/policy.ts +71 -0
- package/src/registry-worker/devices/routes.ts +453 -0
- package/src/registry-worker/domains/domains.test.ts +210 -0
- package/src/registry-worker/domains/routes.ts +139 -0
- package/src/registry-worker/domains.ts +88 -0
- package/src/registry-worker/email/sender.ts +75 -0
- package/src/registry-worker/env.d.ts +14716 -0
- package/src/registry-worker/features.ts +20 -0
- package/src/registry-worker/feedback/feedback.test.ts +230 -0
- package/src/registry-worker/feedback/format.ts +148 -0
- package/src/registry-worker/feedback/routes.ts +386 -0
- package/src/registry-worker/flags.ts +39 -0
- package/src/registry-worker/github/checks.test.ts +177 -0
- package/src/registry-worker/github/checks.ts +374 -0
- package/src/registry-worker/gitlab/checks.test.ts +141 -0
- package/src/registry-worker/gitlab/checks.ts +349 -0
- package/src/registry-worker/hooks.ts +54 -0
- package/src/registry-worker/index.ts +2077 -0
- package/src/registry-worker/jobs/index.ts +29 -0
- package/src/registry-worker/jobs/retention.ts +68 -0
- package/src/registry-worker/mcp/mcp.test.ts +355 -0
- package/src/registry-worker/mcp/routes.ts +215 -0
- package/src/registry-worker/mcp/token.ts +126 -0
- package/src/registry-worker/mcp/tools.ts +563 -0
- package/src/registry-worker/notifications/alerts.ts +212 -0
- package/src/registry-worker/notifications/notifications.test.ts +298 -0
- package/src/registry-worker/notifications/outbox.ts +83 -0
- package/src/registry-worker/notifications/routes.ts +280 -0
- package/src/registry-worker/notifications/secrets.ts +49 -0
- package/src/registry-worker/notifications/slack.ts +96 -0
- package/src/registry-worker/notifications/teams.ts +46 -0
- package/src/registry-worker/org/audit.ts +116 -0
- package/src/registry-worker/org/routes.test.ts +163 -0
- package/src/registry-worker/org/routes.ts +302 -0
- package/src/registry-worker/personas/personas.test.ts +78 -0
- package/src/registry-worker/personas/routes.ts +100 -0
- package/src/registry-worker/repo-triggers.ts +20 -0
- package/src/registry-worker/routes/index.ts +74 -0
- package/src/registry-worker/run-events.ts +24 -0
- package/src/registry-worker/runner/dispatch.ts +219 -0
- package/src/registry-worker/runner/jobs.ts +43 -0
- package/src/registry-worker/runner/metering.ts +82 -0
- package/src/registry-worker/runner/policy.ts +59 -0
- package/src/registry-worker/runner/routes.ts +171 -0
- package/src/registry-worker/runner/runner.test.ts +358 -0
- package/src/registry-worker/runner/tokens.ts +93 -0
- package/src/registry-worker/runs.test.ts +60 -0
- package/src/registry-worker/signup/policy.ts +57 -0
- package/src/registry-worker/signup/routes.ts +106 -0
- package/src/registry-worker/signup/signup.test.ts +81 -0
- package/src/registry-worker/sso/aegis.ts +141 -0
- package/src/registry-worker/sso/membership.ts +157 -0
- package/src/registry-worker/sso/routes.ts +458 -0
- package/src/registry-worker/sso/sso.test.ts +344 -0
- package/src/registry-worker/testing/d1-shim.ts +180 -0
- package/src/registry-worker/testing/harness.ts +137 -0
- package/src/runner-worker/index.ts +108 -0
- package/src/runtime/ai-analysis.ts +97 -0
- package/src/runtime/ai-exploration.test.ts +32 -0
- package/src/runtime/ai-exploration.ts +69 -0
- package/src/runtime/ai-repair.ts +74 -0
- package/src/runtime/ai-work.test.ts +99 -0
- package/src/runtime/android-screen-record.test.ts +75 -0
- package/src/runtime/android-screen-record.ts +192 -0
- package/src/runtime/app-understanding.test.ts +123 -0
- package/src/runtime/app-understanding.ts +201 -0
- package/src/runtime/appium-driver.test.ts +179 -0
- package/src/runtime/appium-driver.ts +295 -0
- package/src/runtime/blocker-resolution.test.ts +113 -0
- package/src/runtime/blocker-resolution.ts +111 -0
- package/src/runtime/browser-matrix.integration.test.ts +212 -0
- package/src/runtime/browser-matrix.test.ts +143 -0
- package/src/runtime/browser-matrix.ts +200 -0
- package/src/runtime/canonical-flow.test.ts +52 -0
- package/src/runtime/config-validate.ts +185 -0
- package/src/runtime/continuous-execution.ts +291 -0
- package/src/runtime/cursor-applescript.ts +573 -0
- package/src/runtime/cursor-cli-driver.test.ts +78 -0
- package/src/runtime/cursor-cli-driver.ts +156 -0
- package/src/runtime/cursor-driver-example.ts +117 -0
- package/src/runtime/cursor-driver-index.ts +65 -0
- package/src/runtime/cursor-driver-init.ts +277 -0
- package/src/runtime/cursor-driver-run.test.ts +15 -0
- package/src/runtime/cursor-driver-run.ts +323 -0
- package/src/runtime/cursor-driver.ts +332 -0
- package/src/runtime/cursor-llm-example.ts +90 -0
- package/src/runtime/cursor-llm.ts +206 -0
- package/src/runtime/cursor-mcp-monitor.ts +386 -0
- package/src/runtime/device-clouds/browserstack.ts +73 -0
- package/src/runtime/device-clouds/device-farm.ts +52 -0
- package/src/runtime/device-clouds/index.ts +92 -0
- package/src/runtime/device-clouds/kobiton.ts +70 -0
- package/src/runtime/device-clouds/targets.ts +44 -0
- package/src/runtime/device-clouds/types.ts +62 -0
- package/src/runtime/diff-proposal.ts +84 -0
- package/src/runtime/discovery-task.test.ts +29 -0
- package/src/runtime/discovery-task.ts +284 -0
- package/src/runtime/driver-recovery.ts +69 -0
- package/src/runtime/driver.ts +79 -0
- package/src/runtime/environment.test.ts +104 -0
- package/src/runtime/environment.ts +137 -0
- package/src/runtime/executor.test.ts +509 -0
- package/src/runtime/executor.ts +921 -0
- package/src/runtime/explorer.test.ts +101 -0
- package/src/runtime/explorer.ts +1013 -0
- package/src/runtime/failure-analysis.test.ts +111 -0
- package/src/runtime/failure-analysis.ts +272 -0
- package/src/runtime/fixtures/fake-maestro.sh +36 -0
- package/src/runtime/flow-language.test.ts +268 -0
- package/src/runtime/flow-language.ts +414 -0
- package/src/runtime/init-wizard.ts +354 -0
- package/src/runtime/ios-screen-record.test.ts +68 -0
- package/src/runtime/ios-screen-record.ts +155 -0
- package/src/runtime/journey-editor.ts +452 -0
- package/src/runtime/journey-evidence.test.ts +161 -0
- package/src/runtime/journey-evidence.ts +180 -0
- package/src/runtime/journey-graph.test.ts +257 -0
- package/src/runtime/journey-graph.ts +170 -0
- package/src/runtime/legacy-names.ts +32 -0
- package/src/runtime/llm-example.ts +105 -0
- package/src/runtime/llm.ts +527 -0
- package/src/runtime/local-browser.test.ts +45 -0
- package/src/runtime/local-browser.ts +48 -0
- package/src/runtime/local-registry-stub.test.ts +325 -0
- package/src/runtime/local-registry-stub.ts +803 -0
- package/src/runtime/maestro-driver.test.ts +84 -0
- package/src/runtime/maestro-driver.ts +209 -0
- package/src/runtime/mole-voice.ts +21 -0
- package/src/runtime/nav-crawl.test.ts +100 -0
- package/src/runtime/nav-crawl.ts +153 -0
- package/src/runtime/pipeline.test.ts +405 -0
- package/src/runtime/pipeline.ts +833 -0
- package/src/runtime/planner.test.ts +37 -0
- package/src/runtime/planner.ts +274 -0
- package/src/runtime/platform-ai.ts +76 -0
- package/src/runtime/playwright-driver.test.ts +93 -0
- package/src/runtime/playwright-driver.ts +620 -0
- package/src/runtime/project-spec.ts +140 -0
- package/src/runtime/record-run-verdicts.ts +68 -0
- package/src/runtime/reporter.test.ts +56 -0
- package/src/runtime/reporter.ts +158 -0
- package/src/runtime/reset.test.ts +44 -0
- package/src/runtime/reset.ts +61 -0
- package/src/runtime/reviewer.test.ts +73 -0
- package/src/runtime/reviewer.ts +158 -0
- package/src/runtime/run-job.ts +136 -0
- package/src/runtime/run-once.test.ts +207 -0
- package/src/runtime/run-once.ts +168 -0
- package/src/runtime/run.ts +132 -0
- package/src/runtime/screen-recording.ts +34 -0
- package/src/runtime/serve-gateway.test.ts +74 -0
- package/src/runtime/serve-gateway.ts +164 -0
- package/src/runtime/serve-worker.test.ts +23 -0
- package/src/runtime/serve-worker.ts +278 -0
- package/src/runtime/site-discovery.test.ts +168 -0
- package/src/runtime/site-discovery.ts +308 -0
- package/src/runtime/target-runner.ts +144 -0
- package/src/runtime/test-case-verdicts.test.ts +94 -0
- package/src/runtime/test-case-verdicts.ts +120 -0
- package/src/runtime/ui-reverse-engineering/coordinator.test.ts +59 -0
- package/src/runtime/ui-reverse-engineering/coordinator.ts +391 -0
- package/src/runtime/ui-reverse-engineering/types.ts +197 -0
- package/src/runtime/web-suite.test.ts +97 -0
- package/src/runtime/web-suite.ts +176 -0
- package/src/storage/create-object-store.ts +144 -0
- package/src/storage/keys.ts +34 -0
- package/src/storage/local-artifact-server.test.ts +314 -0
- package/src/storage/local-artifact-server.ts +357 -0
- package/src/storage/object-store.test.ts +28 -0
- package/src/storage/object-store.ts +101 -0
- package/src/storage/registry-object-store.ts +88 -0
- package/src/storage/remote-object-store.ts +104 -0
- package/src/storage/storage-directory.test.ts +43 -0
- package/src/storage/storage-directory.ts +24 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CURSOR_DRIVER main execution function
|
|
3
|
+
*
|
|
4
|
+
* This is the LLM API adapter that replaces traditional LLM calls.
|
|
5
|
+
* It orchestrates the full flow:
|
|
6
|
+
* 1. Generate prompt with finalization protocol
|
|
7
|
+
* 2. Inject prompt into Cursor via AppleScript
|
|
8
|
+
* 3. Monitor MCP server for commit response
|
|
9
|
+
* 4. Return structured response
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
generateCursorDriverPrompt,
|
|
14
|
+
validateCommitPayload,
|
|
15
|
+
CursorDriverResponse,
|
|
16
|
+
CursorDriverRunOptions,
|
|
17
|
+
} from "./cursor-driver.js";
|
|
18
|
+
import { injectPrompt, isCursorRunning, openCursorInDirectory } from "./cursor-applescript.js";
|
|
19
|
+
import { waitForCommitResponse } from "./cursor-mcp-monitor.js";
|
|
20
|
+
import { runCursorCliDriver, type CursorCliDriverOptions } from "./cursor-cli-driver.js";
|
|
21
|
+
import { ControlPlaneClient, isClaimableTask, type RegistryTask } from "../registry/control-plane-client.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Main CURSOR_DRIVER function - acts as an LLM API adapter
|
|
25
|
+
*
|
|
26
|
+
* @param task - The task/prompt to send to Cursor
|
|
27
|
+
* @param options - Configuration options
|
|
28
|
+
* @returns Structured response from Cursor
|
|
29
|
+
*/
|
|
30
|
+
export async function runCursorDriver(
|
|
31
|
+
task: string,
|
|
32
|
+
options: CursorDriverRunOptions,
|
|
33
|
+
): Promise<CursorDriverResponse> {
|
|
34
|
+
const {
|
|
35
|
+
mcpHost = "127.0.0.1",
|
|
36
|
+
mcpPort = 3187,
|
|
37
|
+
artifactsDir,
|
|
38
|
+
timeout = 300000,
|
|
39
|
+
pollInterval = 1000,
|
|
40
|
+
injectionOptions = {},
|
|
41
|
+
...promptOptions
|
|
42
|
+
} = options;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Step 1: Open Cursor in project directory if specified
|
|
46
|
+
if (options.projectDirectory) {
|
|
47
|
+
console.log(`[CURSOR_DRIVER] Opening Cursor in directory: ${options.projectDirectory}`);
|
|
48
|
+
await openCursorInDirectory(options.projectDirectory);
|
|
49
|
+
// Wait for Cursor workspace to load AND for MCP tools to initialize
|
|
50
|
+
// This is critical - MCP tools must be available before injecting prompts
|
|
51
|
+
console.log("[CURSOR_DRIVER] Waiting for workspace and MCP tools to initialize...");
|
|
52
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
53
|
+
console.log("[CURSOR_DRIVER] Workspace and MCP tools should be ready");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Step 2: Verify Cursor is running
|
|
57
|
+
const cursorRunning = await isCursorRunning();
|
|
58
|
+
if (!cursorRunning) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
error: "Cursor application is not running",
|
|
62
|
+
errorCode: "MCP_UNAVAILABLE",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Step 3: Generate prompt with finalization protocol
|
|
67
|
+
const fullPrompt = generateCursorDriverPrompt(task, promptOptions);
|
|
68
|
+
console.log("[CURSOR_DRIVER] Generated prompt with finalization protocol");
|
|
69
|
+
|
|
70
|
+
// Step 4: Inject prompt into Cursor
|
|
71
|
+
console.log("[CURSOR_DRIVER] Injecting prompt into Cursor...");
|
|
72
|
+
await injectPrompt(fullPrompt, {
|
|
73
|
+
...injectionOptions,
|
|
74
|
+
mcpHost,
|
|
75
|
+
mcpPort,
|
|
76
|
+
});
|
|
77
|
+
console.log("[CURSOR_DRIVER] Prompt injected successfully");
|
|
78
|
+
|
|
79
|
+
// Step 4.5: Grace period for planning models
|
|
80
|
+
// Planning models (like GPT-4) may take 10-30 seconds to think before calling MCP tools
|
|
81
|
+
// Give them time to process before we start monitoring
|
|
82
|
+
const gracePeriod = 15000; // 15 seconds
|
|
83
|
+
console.log(
|
|
84
|
+
`[CURSOR_DRIVER] Waiting ${gracePeriod / 1000}s grace period for model to process...`,
|
|
85
|
+
);
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, gracePeriod));
|
|
87
|
+
console.log("[CURSOR_DRIVER] Grace period complete, starting to monitor...");
|
|
88
|
+
|
|
89
|
+
// Step 5: Monitor for commit response
|
|
90
|
+
console.log("[CURSOR_DRIVER] Waiting for commit response...");
|
|
91
|
+
|
|
92
|
+
// Set up nudge callback (enabled by default unless explicitly disabled)
|
|
93
|
+
const nudgeOptions = options.nudgeOptions;
|
|
94
|
+
const nudgeEnabled = nudgeOptions?.enabled !== false; // Default to true
|
|
95
|
+
let nudgeCallback: ((nudgeCount: number) => Promise<void>) | undefined;
|
|
96
|
+
|
|
97
|
+
if (nudgeEnabled) {
|
|
98
|
+
// Import sendNudge dynamically to avoid circular dependencies
|
|
99
|
+
const { sendNudge } = await import("./cursor-applescript.js");
|
|
100
|
+
const nudgeMessage =
|
|
101
|
+
nudgeOptions?.customMessage ||
|
|
102
|
+
"Please continue working on the task. Remember to call bugmole_commit_response when you are fully finished.";
|
|
103
|
+
|
|
104
|
+
nudgeCallback = async (nudgeCount: number) => {
|
|
105
|
+
await sendNudge(nudgeMessage);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const commit = await waitForCommitResponse({
|
|
110
|
+
host: mcpHost,
|
|
111
|
+
port: mcpPort,
|
|
112
|
+
artifactsDir,
|
|
113
|
+
timeout,
|
|
114
|
+
pollInterval,
|
|
115
|
+
requestId: promptOptions.requestId,
|
|
116
|
+
nudgeOptions: {
|
|
117
|
+
enabled: nudgeEnabled,
|
|
118
|
+
progressReportTimeout: nudgeOptions?.progressReportTimeout,
|
|
119
|
+
nudgeInterval: nudgeOptions?.nudgeInterval,
|
|
120
|
+
maxNudges: nudgeOptions?.maxNudges,
|
|
121
|
+
customMessage: nudgeOptions?.customMessage,
|
|
122
|
+
onNudge: nudgeCallback,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
console.log(`[CURSOR_DRIVER] Received commit: ${commit.id}`);
|
|
127
|
+
|
|
128
|
+
// Step 6: Validate payload
|
|
129
|
+
const validation = validateCommitPayload(commit.payload);
|
|
130
|
+
if (!validation.valid) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
commitId: commit.id,
|
|
134
|
+
error: `Payload validation failed: ${validation.errors.join(", ")}`,
|
|
135
|
+
errorCode: "VALIDATION_FAILED",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Step 7: Return structured response
|
|
140
|
+
return {
|
|
141
|
+
success: true,
|
|
142
|
+
commitId: commit.id,
|
|
143
|
+
payload: commit.payload,
|
|
144
|
+
};
|
|
145
|
+
} catch (error: any) {
|
|
146
|
+
// Handle timeout
|
|
147
|
+
if (error.message?.includes("Timeout")) {
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: error.message,
|
|
151
|
+
errorCode: "TIMEOUT",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Handle MCP unavailable
|
|
156
|
+
if (error.message?.includes("not responding") || error.message?.includes("MCP")) {
|
|
157
|
+
return {
|
|
158
|
+
success: false,
|
|
159
|
+
error: error.message,
|
|
160
|
+
errorCode: "MCP_UNAVAILABLE",
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Handle injection failures
|
|
165
|
+
if (error.message?.includes("inject") || error.message?.includes("AppleScript")) {
|
|
166
|
+
return {
|
|
167
|
+
success: false,
|
|
168
|
+
error: error.message,
|
|
169
|
+
errorCode: "INJECTION_FAILED",
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Generic error
|
|
174
|
+
return {
|
|
175
|
+
success: false,
|
|
176
|
+
error: error.message || "Unknown error",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Runs a prompt through a real coding agent, preferring the headless
|
|
183
|
+
* `cursor-agent` CLI (no GUI, no accessibility permissions, no polling) and
|
|
184
|
+
* falling back to the AppleScript-driven Cursor GUI automation only when the
|
|
185
|
+
* CLI is unavailable on this machine.
|
|
186
|
+
*/
|
|
187
|
+
export async function runCursorAgent(
|
|
188
|
+
task: string,
|
|
189
|
+
options: CursorDriverRunOptions & Partial<CursorCliDriverOptions>,
|
|
190
|
+
): Promise<CursorDriverResponse> {
|
|
191
|
+
const cliResponse = await runCursorCliDriver(task, {
|
|
192
|
+
...options,
|
|
193
|
+
workspaceDirectory: options.workspaceDirectory ?? options.projectDirectory,
|
|
194
|
+
});
|
|
195
|
+
if (!shouldFallBackToCursorGui(cliResponse)) return cliResponse;
|
|
196
|
+
return runCursorDriver(task, options);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The GUI fallback drives the Cursor app through AppleScript, which exists only on macOS. On a
|
|
201
|
+
* Linux worker a missing `cursor-agent` is reported as-is instead of failing inside osascript.
|
|
202
|
+
*/
|
|
203
|
+
export function shouldFallBackToCursorGui(response: CursorDriverResponse, platform: NodeJS.Platform = process.platform): boolean {
|
|
204
|
+
return !response.success && response.errorCode === "MCP_UNAVAILABLE" && platform === "darwin";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function runNextAgentTask(
|
|
208
|
+
options: CursorDriverRunOptions & {
|
|
209
|
+
registryUrl: string;
|
|
210
|
+
apiKey: string;
|
|
211
|
+
projectId: string;
|
|
212
|
+
workerId: string;
|
|
213
|
+
},
|
|
214
|
+
): Promise<{ task: RegistryTask | null; response?: CursorDriverResponse; message?: string }> {
|
|
215
|
+
const client = new ControlPlaneClient({
|
|
216
|
+
registryUrl: options.registryUrl,
|
|
217
|
+
apiKey: options.apiKey,
|
|
218
|
+
projectId: options.projectId,
|
|
219
|
+
});
|
|
220
|
+
const available = await client.listTasks();
|
|
221
|
+
// Discovery tasks are run by the worker's own browser, not a coding agent.
|
|
222
|
+
const next = available.tasks.find((task) => task.taskType !== "discovery" && isClaimableTask(task));
|
|
223
|
+
if (!next) return { task: null, message: "No queued agent task is available." };
|
|
224
|
+
// Losing a claim is a normal race, not a failure: another worker got
|
|
225
|
+
// there first, or the task changed state between listing and claiming.
|
|
226
|
+
// Letting it throw aborted the whole poll, which also skipped the
|
|
227
|
+
// pipeline reconciliation that runs afterwards — one contended task
|
|
228
|
+
// stalled all forward progress.
|
|
229
|
+
let claimed: Awaited<ReturnType<typeof client.claimTask>>;
|
|
230
|
+
try {
|
|
231
|
+
claimed = await client.claimTask(next.id, options.workerId);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
return {
|
|
234
|
+
task: null,
|
|
235
|
+
message: `Could not claim ${next.id}: ${error instanceof Error ? error.message : "claim rejected"}`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const taskPrompt = [
|
|
239
|
+
`Continue the Bugmole dashboard task "${next.title}".`,
|
|
240
|
+
"",
|
|
241
|
+
next.description,
|
|
242
|
+
"",
|
|
243
|
+
"Read bugmole://guidance/agent-task-queue before acting.",
|
|
244
|
+
"Report progress as you work and use bugmole_task_update for the final status.",
|
|
245
|
+
next.taskType === "ui"
|
|
246
|
+
? "This is a UI task: rerun parity after the fix and do not complete without a fresh parity audit id and match score."
|
|
247
|
+
: "",
|
|
248
|
+
].filter(Boolean).join("\n");
|
|
249
|
+
// Pass the task type through so a long exploration gets the longer CLI
|
|
250
|
+
// budget instead of being SIGTERM'd at the short default mid-flight.
|
|
251
|
+
// Keep the lease fresh for as long as the agent is genuinely working.
|
|
252
|
+
// Without this a long exploration would outlive its 60s lease and look
|
|
253
|
+
// abandoned, and the stale-lease recovery below would hand the same task
|
|
254
|
+
// to another worker while the first was still running it.
|
|
255
|
+
const heartbeat = setInterval(() => {
|
|
256
|
+
void client.updateTask(next.id, {
|
|
257
|
+
workerId: options.workerId,
|
|
258
|
+
status: "running",
|
|
259
|
+
progressMessage: `Working on "${next.title}"…`,
|
|
260
|
+
}).catch(() => undefined);
|
|
261
|
+
}, 30_000);
|
|
262
|
+
if (typeof heartbeat.unref === "function") heartbeat.unref();
|
|
263
|
+
|
|
264
|
+
let response: CursorDriverResponse;
|
|
265
|
+
try {
|
|
266
|
+
response = await runCursorAgent(taskPrompt, { ...options, taskType: next.taskType });
|
|
267
|
+
} finally {
|
|
268
|
+
clearInterval(heartbeat);
|
|
269
|
+
}
|
|
270
|
+
if (!response.success) {
|
|
271
|
+
await client.updateTask(next.id, {
|
|
272
|
+
workerId: options.workerId,
|
|
273
|
+
status: "blocked",
|
|
274
|
+
errorMessage: response.error ?? "Cursor task execution failed",
|
|
275
|
+
progressMessage: "Cursor execution stopped; task can be continued.",
|
|
276
|
+
});
|
|
277
|
+
return { task: claimed.task, response };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// The agent is supposed to close its own task, but nothing guaranteed it
|
|
281
|
+
// did — and a task left "running" blocks the pipeline forever, since the
|
|
282
|
+
// reconciler treats an in-flight task as a reason not to queue the next
|
|
283
|
+
// stage. Close it here only if the agent did not, so a self-reported
|
|
284
|
+
// status (with its parity evidence and summary) always wins.
|
|
285
|
+
const after = await client.listTasks().catch(() => null);
|
|
286
|
+
const current = after?.tasks.find((task) => task.id === next.id);
|
|
287
|
+
if (current && current.status !== "completed" && current.status !== "error") {
|
|
288
|
+
await client.updateTask(next.id, {
|
|
289
|
+
workerId: options.workerId,
|
|
290
|
+
status: "completed",
|
|
291
|
+
completionSummary: response.payload?.summary
|
|
292
|
+
?? "Worker completed the task; the agent did not report a status.",
|
|
293
|
+
}).catch(() => undefined);
|
|
294
|
+
}
|
|
295
|
+
return { task: claimed.task, response };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Convenience function for simple text completion (LLM API-like interface)
|
|
300
|
+
*/
|
|
301
|
+
export async function cursorComplete(
|
|
302
|
+
prompt: string,
|
|
303
|
+
options: CursorDriverRunOptions,
|
|
304
|
+
): Promise<string> {
|
|
305
|
+
const response = await runCursorDriver(prompt, options);
|
|
306
|
+
|
|
307
|
+
if (!response.success || !response.payload) {
|
|
308
|
+
throw new Error(response.error || "Cursor driver failed to complete task");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Return the markdown answer, or summary as fallback
|
|
312
|
+
return response.payload.answer_markdown || response.payload.summary;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Convenience function for structured completion (returns full payload)
|
|
317
|
+
*/
|
|
318
|
+
export async function cursorCompleteStructured(
|
|
319
|
+
prompt: string,
|
|
320
|
+
options: CursorDriverRunOptions,
|
|
321
|
+
): Promise<CursorDriverResponse> {
|
|
322
|
+
return runCursorDriver(prompt, options);
|
|
323
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CURSOR_DRIVER utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for generating prompts that enforce the mandatory
|
|
5
|
+
* MCP finalization protocol, ensuring deterministic completion signals.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface CursorDriverOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Whether to include the mandatory finalization protocol
|
|
11
|
+
* @default true
|
|
12
|
+
*/
|
|
13
|
+
includeFinalization?: boolean;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Custom instructions to append before the finalization protocol
|
|
17
|
+
*/
|
|
18
|
+
customInstructions?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Request ID for tracking parallel runs
|
|
22
|
+
*/
|
|
23
|
+
requestId?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generates the mandatory MCP finalization protocol footer
|
|
28
|
+
* that must be appended to all CURSOR_DRIVER prompts.
|
|
29
|
+
*/
|
|
30
|
+
export function generateFinalizationProtocol(options: CursorDriverOptions = {}): string {
|
|
31
|
+
const { requestId } = options;
|
|
32
|
+
|
|
33
|
+
let protocol = "\n\n⸻\n\n";
|
|
34
|
+
|
|
35
|
+
if (requestId) {
|
|
36
|
+
protocol += `REQUEST_ID: ${requestId}\n\n`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protocol += `MCP FINALIZATION (MANDATORY)\n\n`;
|
|
40
|
+
|
|
41
|
+
protocol += `PROGRESS REPORTING:\n`;
|
|
42
|
+
protocol += `- For long-running tasks (more than 2-3 minutes), periodically call bugmole_report_progress every 2-3 minutes.\n`;
|
|
43
|
+
protocol += `- This lets the framework know you are still working and prevents unnecessary nudges.\n`;
|
|
44
|
+
protocol += `- Use bugmole_report_progress with status "working", "blocked", or "completed".\n`;
|
|
45
|
+
protocol += `- Only nudge if you have not reported progress for an extended period.\n\n`;
|
|
46
|
+
|
|
47
|
+
protocol += `INCREMENTAL JOURNEY EXPLORATION:\n`;
|
|
48
|
+
protocol += `- After every bugmole_explore or bugmole_record_discovery call, inspect the selected flow with bugmole_journey_get.\n`;
|
|
49
|
+
protocol += `- Call bugmole_journey_next, perform its actionable task, and record the observed step/transition.\n`;
|
|
50
|
+
protocol += `- Repeat until bugmole_journey_next returns exhausted or an explicit blocked reason; do not plan from an unchecked actionable flow.\n\n`;
|
|
51
|
+
|
|
52
|
+
protocol += `COMPLETION:\n`;
|
|
53
|
+
protocol += `1. Perform all reasoning and work first.\n`;
|
|
54
|
+
protocol += `2. CRITICAL RULE: If your answer mentions "Next Steps", "TODO", "should do", "need to", "use bugmole_", or any future actions, you MUST include them in "next_steps" array.\n`;
|
|
55
|
+
protocol += `3. The "next_steps" field is MANDATORY if you describe any remaining work, even in answer_markdown.\n`;
|
|
56
|
+
protocol += `4. ONLY omit "next_steps" when EVERYTHING is 100% done and NO further work exists.\n`;
|
|
57
|
+
protocol += `5. When fully finished (with no more work), call bugmole_commit_response exactly once.\n`;
|
|
58
|
+
protocol += `6. The final answer MUST be included only in the tool payload.\n`;
|
|
59
|
+
protocol += `7. Do NOT print the final answer in chat.\n`;
|
|
60
|
+
protocol += `8. The payload must be valid JSON. Use this SIMPLE format (all fields except status/summary are optional):\n\n`;
|
|
61
|
+
|
|
62
|
+
protocol += `MINIMAL EXAMPLE (use this if you're unsure):\n`;
|
|
63
|
+
protocol += `{\n`;
|
|
64
|
+
protocol += ` "status": "ok",\n`;
|
|
65
|
+
protocol += ` "summary": "What you did",\n`;
|
|
66
|
+
protocol += ` "next_steps": ["Next action if any"]\n`;
|
|
67
|
+
protocol += `}\n\n`;
|
|
68
|
+
|
|
69
|
+
protocol += `EXAMPLE WITH NEXT STEPS:\n`;
|
|
70
|
+
protocol += `{\n`;
|
|
71
|
+
protocol += ` "status": "ok",\n`;
|
|
72
|
+
protocol += ` "summary": "Investigated project, ready for exploration",\n`;
|
|
73
|
+
protocol += ` "next_steps": [\n`;
|
|
74
|
+
protocol += ` "Run bugmole_explore with baseUrl=http://localhost:8000",\n`;
|
|
75
|
+
protocol += ` "Create test plans for discovered journeys"\n`;
|
|
76
|
+
protocol += ` ]\n`;
|
|
77
|
+
protocol += `}\n\n`;
|
|
78
|
+
|
|
79
|
+
protocol += `FULL SCHEMA (only include fields you have):\n`;
|
|
80
|
+
protocol += `{\n`;
|
|
81
|
+
protocol += ` "status": "ok" | "error", // REQUIRED\n`;
|
|
82
|
+
protocol += ` "summary": "Short summary", // REQUIRED\n`;
|
|
83
|
+
protocol += ` "answer_markdown": "Optional full answer", // OPTIONAL\n`;
|
|
84
|
+
protocol += ` "actions": [], // OPTIONAL - only if you made changes\n`;
|
|
85
|
+
protocol += ` "artifacts": [], // OPTIONAL - only if you created artifacts\n`;
|
|
86
|
+
protocol += ` "next_steps": [] // OPTIONAL - only if more work remains\n`;
|
|
87
|
+
protocol += `}\n\n`;
|
|
88
|
+
|
|
89
|
+
protocol += `SIMPLE RULES:\n`;
|
|
90
|
+
protocol += `- Only "status" and "summary" are required\n`;
|
|
91
|
+
protocol += `- Everything else is optional - only include if you have it\n`;
|
|
92
|
+
protocol += `- Keep it simple - smaller JSON is easier to generate correctly\n`;
|
|
93
|
+
protocol += `- If you have next steps, include them as an array of strings\n\n`;
|
|
94
|
+
|
|
95
|
+
protocol += `⚠️ CRITICAL RULE ABOUT next_steps:\n`;
|
|
96
|
+
protocol += `- If you write ANY section titled "Next Steps", "TODO", "Remaining Work", or list future actions, you MUST include them in "next_steps".\n`;
|
|
97
|
+
protocol += `- If you mention commands like "use bugmole_explore", "run bugmole_plan", "execute bugmole_run", these MUST be in "next_steps".\n`;
|
|
98
|
+
protocol += `- If you say "should do", "need to", "next", "then", these indicate remaining work → include in "next_steps".\n`;
|
|
99
|
+
protocol += `- Example: If you write "Next: Run bugmole_explore", then next_steps MUST be: ["Run bugmole_explore with baseUrl=http://localhost:8000"]\n`;
|
|
100
|
+
protocol += `- ONLY omit "next_steps" when you have completed EVERYTHING and there is ZERO remaining work.\n`;
|
|
101
|
+
protocol += `- When in doubt, include next_steps. It's better to continue than to stop prematurely.\n\n`;
|
|
102
|
+
|
|
103
|
+
protocol += `💡 TIP FOR SMALLER MODELS: Keep it simple!\n`;
|
|
104
|
+
protocol += `- Minimal example: {"status": "ok", "summary": "Done", "next_steps": ["Continue with X"]}\n`;
|
|
105
|
+
protocol += `- Don't worry about actions/artifacts if you don't have them\n`;
|
|
106
|
+
protocol += `- Focus on getting status, summary, and next_steps correct\n\n`;
|
|
107
|
+
|
|
108
|
+
protocol += `9. If MCP tools are unavailable, output exactly: MCP_UNAVAILABLE\n\n`;
|
|
109
|
+
|
|
110
|
+
protocol += `⸻\n`;
|
|
111
|
+
|
|
112
|
+
return protocol;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Appends the mandatory finalization protocol to a prompt.
|
|
117
|
+
* This is the primary function for preparing CURSOR_DRIVER prompts.
|
|
118
|
+
*/
|
|
119
|
+
export function appendFinalizationProtocol(
|
|
120
|
+
prompt: string,
|
|
121
|
+
options: CursorDriverOptions = {},
|
|
122
|
+
): string {
|
|
123
|
+
const { includeFinalization = true, customInstructions } = options;
|
|
124
|
+
|
|
125
|
+
if (!includeFinalization) {
|
|
126
|
+
return prompt;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let finalPrompt = prompt;
|
|
130
|
+
|
|
131
|
+
if (customInstructions) {
|
|
132
|
+
finalPrompt += `\n\n${customInstructions}\n`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
finalPrompt += generateFinalizationProtocol(options);
|
|
136
|
+
|
|
137
|
+
return finalPrompt;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Validates that a commit response payload matches the expected schema.
|
|
142
|
+
* This is intentionally lenient - only status and summary are required.
|
|
143
|
+
* All other fields are optional and validation is forgiving.
|
|
144
|
+
*/
|
|
145
|
+
export function validateCommitPayload(payload: any): {
|
|
146
|
+
valid: boolean;
|
|
147
|
+
errors: string[];
|
|
148
|
+
} {
|
|
149
|
+
const errors: string[] = [];
|
|
150
|
+
|
|
151
|
+
if (!payload || typeof payload !== "object") {
|
|
152
|
+
errors.push("Payload must be an object");
|
|
153
|
+
return { valid: false, errors };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Only status and summary are required
|
|
157
|
+
if (!payload.status || !["ok", "error"].includes(payload.status)) {
|
|
158
|
+
errors.push('status must be "ok" or "error"');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!payload.summary || typeof payload.summary !== "string") {
|
|
162
|
+
errors.push("summary is required and must be a string");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Everything else is optional - only validate if present
|
|
166
|
+
if (payload.actions !== undefined) {
|
|
167
|
+
if (!Array.isArray(payload.actions)) {
|
|
168
|
+
errors.push("actions must be an array if provided");
|
|
169
|
+
} else {
|
|
170
|
+
// Only validate action structure if actions array exists
|
|
171
|
+
payload.actions.forEach((action: any, index: number) => {
|
|
172
|
+
if (action && typeof action === "object") {
|
|
173
|
+
if (action.type && !["edit", "create", "delete", "command"].includes(action.type)) {
|
|
174
|
+
errors.push(`actions[${index}].type must be one of: edit, create, delete, command`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// If action is malformed, we'll just skip it rather than fail validation
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (payload.artifacts !== undefined) {
|
|
183
|
+
if (!Array.isArray(payload.artifacts)) {
|
|
184
|
+
errors.push("artifacts must be an array if provided");
|
|
185
|
+
} else {
|
|
186
|
+
// Only validate artifact structure if artifacts array exists
|
|
187
|
+
payload.artifacts.forEach((artifact: any, index: number) => {
|
|
188
|
+
if (artifact && typeof artifact === "object") {
|
|
189
|
+
if (artifact.type && !["patch", "config", "command", "note"].includes(artifact.type)) {
|
|
190
|
+
errors.push(`artifacts[${index}].type must be one of: patch, config, command, note`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// If artifact is malformed, we'll just skip it rather than fail validation
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// next_steps is optional - if present, should be an array of strings
|
|
199
|
+
if (payload.next_steps !== undefined && !Array.isArray(payload.next_steps)) {
|
|
200
|
+
errors.push("next_steps must be an array if provided");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
valid: errors.length === 0,
|
|
205
|
+
errors,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Generates a complete CURSOR_DRIVER prompt with task and finalization protocol.
|
|
211
|
+
*/
|
|
212
|
+
export function generateCursorDriverPrompt(
|
|
213
|
+
task: string,
|
|
214
|
+
options: CursorDriverOptions = {},
|
|
215
|
+
): string {
|
|
216
|
+
return appendFinalizationProtocol(task, options);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* CURSOR_DRIVER response structure
|
|
221
|
+
*/
|
|
222
|
+
export interface CursorDriverResponse {
|
|
223
|
+
success: boolean;
|
|
224
|
+
commitId?: string;
|
|
225
|
+
payload?: {
|
|
226
|
+
status: "ok" | "error";
|
|
227
|
+
summary: string;
|
|
228
|
+
answer_markdown?: string;
|
|
229
|
+
actions?: Array<{
|
|
230
|
+
type: "edit" | "create" | "delete" | "command";
|
|
231
|
+
target: string;
|
|
232
|
+
details: string;
|
|
233
|
+
}>;
|
|
234
|
+
artifacts?: Array<{
|
|
235
|
+
type: "patch" | "config" | "command" | "note";
|
|
236
|
+
content: string;
|
|
237
|
+
}>;
|
|
238
|
+
next_steps?: string[];
|
|
239
|
+
};
|
|
240
|
+
error?: string;
|
|
241
|
+
errorCode?: "TIMEOUT" | "MCP_UNAVAILABLE" | "INJECTION_FAILED" | "VALIDATION_FAILED";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* CURSOR_DRIVER options for the main driver function
|
|
246
|
+
*/
|
|
247
|
+
export interface CursorDriverRunOptions extends CursorDriverOptions {
|
|
248
|
+
/**
|
|
249
|
+
* Task type of the work being run. Drives the CLI timeout default, since
|
|
250
|
+
* an exploration needs far longer than a small code edit.
|
|
251
|
+
*/
|
|
252
|
+
taskType?: string;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* MCP server host
|
|
256
|
+
* @default '127.0.0.1'
|
|
257
|
+
*/
|
|
258
|
+
mcpHost?: string;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* MCP server port
|
|
262
|
+
* @default 3187
|
|
263
|
+
*/
|
|
264
|
+
mcpPort?: number;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Artifacts directory
|
|
268
|
+
*/
|
|
269
|
+
artifactsDir: string;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Timeout for waiting for commit response (ms)
|
|
273
|
+
* @default 300000 (5 minutes)
|
|
274
|
+
*/
|
|
275
|
+
timeout?: number;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Polling interval for checking commits (ms)
|
|
279
|
+
* @default 1000
|
|
280
|
+
*/
|
|
281
|
+
pollInterval?: number;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* AppleScript injection options
|
|
285
|
+
*/
|
|
286
|
+
injectionOptions?: {
|
|
287
|
+
pasteDelay?: number;
|
|
288
|
+
submitDelay?: number;
|
|
289
|
+
maxRetries?: number;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Nudge configuration for when model stops working
|
|
294
|
+
* Nudges are only sent if no progress report is received for an extended period
|
|
295
|
+
*/
|
|
296
|
+
nudgeOptions?: {
|
|
297
|
+
/**
|
|
298
|
+
* Enable nudging when model appears inactive
|
|
299
|
+
* @default true
|
|
300
|
+
*/
|
|
301
|
+
enabled?: boolean;
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Time in ms without progress report before sending first nudge
|
|
305
|
+
* @default 300000 (5 minutes)
|
|
306
|
+
*/
|
|
307
|
+
progressReportTimeout?: number;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Minimum interval between nudges in ms
|
|
311
|
+
* @default 120000 (2 minutes)
|
|
312
|
+
*/
|
|
313
|
+
nudgeInterval?: number;
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Maximum number of nudges to send
|
|
317
|
+
* @default 10
|
|
318
|
+
*/
|
|
319
|
+
maxNudges?: number;
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Custom nudge message (optional)
|
|
323
|
+
*/
|
|
324
|
+
customMessage?: string;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Project directory to open in Cursor before testing
|
|
329
|
+
* If specified, will open a new Cursor window in this directory
|
|
330
|
+
*/
|
|
331
|
+
projectDirectory?: string;
|
|
332
|
+
}
|