Skip to content

Add class-based CUA agent surfaces and runtime spec - #8

Merged
rgarcia merged 7 commits into
mainfrom
hypeship/cua-agent-harness-classes
May 13, 2026
Merged

Add class-based CUA agent surfaces and runtime spec#8
rgarcia merged 7 commits into
mainfrom
hypeship/cua-agent-harness-classes

Conversation

@rgarcia

@rgarcia rgarcia commented May 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • replace the old factory-first @onkernel/cua-agent API with class-first CuaAgent and CuaHarness constructors, keeping pi-like options while making browser/client runtime requirements explicit
  • centralize provider-specific defaults in @onkernel/cua-ai via resolveCuaRuntimeSpec() (default tool definitions, system prompts, and optional payload middleware), and consume that spec from cua-agent to keep runtime wiring provider-neutral
  • add strong validation coverage: canonical tool executor exhaustiveness tests, yutori payload/runtime-spec unit tests, env-gated live e2e provider matrix, and runnable examples under packages/agent/examples
  • document the cua-ai vs cua-agent ownership boundary in docs/architecture.md and refresh package docs/changelogs to match the new class-first surface

Test plan

  • npm run build --workspace @onkernel/cua-ai
  • npm run test --workspace @onkernel/cua-ai
  • npm run build --workspace @onkernel/cua-agent
  • npm run test --workspace @onkernel/cua-agent
  • CUA_E2E_LIVE=1 ... npm run test --workspace @onkernel/cua-agent -- test/e2e.live.test.ts

Made with Cursor


Note

Medium Risk
Medium risk because it replaces the @onkernel/cua-agent public construction API and changes how default tools/prompts/payload middleware are selected, which can affect runtime behavior across providers.

Overview
Introduces a class-first API in @onkernel/cua-agent by replacing the createCuaAgent() factory with CuaAgent (extends pi Agent) and a new CuaHarness wrapper, making client/browser required and defaulting getApiKey to CUA env var conventions.

Centralizes provider-specific defaults in @onkernel/cua-ai via resolveCuaRuntimeSpec() (tool definitions, default system prompts, and payload middleware like Yutori filtering and OpenAI store: true), and updates agent tool execution to be provider-neutral/typed with schema validation and fail-fast handling of unsupported tool names.

Expands validation and CI: adds env-gated live e2e provider matrix tests for the agent package, tool-executor exhaustiveness tests, new runnable packages/agent/examples, and updates docs/changelogs plus CI to skip integration/e2e on draft PRs while adding an agent-e2e job.

Reviewed by Cursor Bugbot for commit ed31e65. Bugbot is set up for automated code reviews on this repo. Configure here.

rgarcia and others added 7 commits May 13, 2026 02:00
Switch cua-agent to class-first CuaAgent/CuaHarness constructors, centralize provider-specific defaults in cua-ai via resolveCuaRuntimeSpec, and add exhaustive tool coverage plus live e2e scenarios to validate browser execution across providers.

Co-authored-by: Cursor <cursoragent@cursor.com>
Move provider API-key env conventions into cua-ai, make CuaAgent/CuaHarness default to readable env-key resolution, improve harness quickstarts with transcript usage, and standardize extensionless TypeScript imports with a dist rewrite step for runtime compatibility.

Co-authored-by: Cursor <cursoragent@cursor.com>
Ensure example scripts compile cua-ai and cua-agent first so new package exports are available even from a clean checkout.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a source condition to package exports and run example scripts with --conditions=source so workspace examples resolve TypeScript sources without requiring prebuilt dist artifacts.

Co-authored-by: Cursor <cursoragent@cursor.com>
Set OpenAI runtime payloads to store response items so multi-turn agent loops can reference prior response items without 404 errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
Run CuaAgent/CuaHarness live smoke tests for all configured providers on non-draft PRs and main pushes, and fail on any assistant/tool errors so runtime regressions are caught automatically.

Co-authored-by: Cursor <cursoragent@cursor.com>
Remove the dist import extension rewrite utility and its build hooks, restore explicit fixture entry extensions for node execution, and align CUA provider handling with latest main (google provider naming and runtime spec matching).

Co-authored-by: Cursor <cursoragent@cursor.com>
@rgarcia
rgarcia marked this pull request as ready for review May 13, 2026 15:40
@firetiger-agent

Copy link
Copy Markdown

Firetiger deploy monitoring skipped

This PR didn't match the auto-monitor filter configured on your GitHub connection:

Any PR that changes the kernel API. Monitor changes to API endpoints (packages/api/cmd/api/) and Temporal workflows (packages/api/lib/temporal) in the kernel repo

Reason: PR modifies CUA agent packages and runtime specs, but does not appear to change API endpoints (packages/api/cmd/api/) or Temporal workflows (packages/api/lib/temporal) in the kernel repo.

To monitor this PR anyway, reply with @firetiger monitor this.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Accessing property on void return causes runtime crash
    • Updated the harness smoke example to await harness.prompt() and derive stopReason from the latest assistant message in harness.state.messages instead of reading a value from a void return.

Create PR

Or push these changes by commenting:

@cursor push 68f5011708
Preview (68f5011708)
diff --git a/packages/agent/examples/harness-openai-smoke.ts b/packages/agent/examples/harness-openai-smoke.ts
--- a/packages/agent/examples/harness-openai-smoke.ts
+++ b/packages/agent/examples/harness-openai-smoke.ts
@@ -29,8 +29,9 @@
 
 		const scenario = SCENARIOS[0]!;
 		console.log(`running scenario: ${scenario.name}`);
-		const response = await harness.prompt(scenario.prompt);
-		console.log("assistant stopReason:", response.stopReason);
+		await harness.prompt(scenario.prompt);
+		const assistant = [...harness.state.messages].reverse().find((message) => message.role === "assistant");
+		console.log("assistant stopReason:", assistant?.role === "assistant" ? assistant.stopReason : "unknown");
 	} finally {
 		await client.browsers.deleteByID(browser.session_id);
 	}

You can send follow-ups to the cloud agent here.

Comment thread packages/agent/examples/harness-openai-smoke.ts Outdated
rgarcia added a commit that referenced this pull request May 13, 2026
Tzafon's model is non-deterministic about emitting tool calls under our
test prompts; mirror the requireToolCalls guard from #8 so a no-tool-call
response no longer fails CI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Dropped .js in URL not fixed by build script
    • Restored ./fixture-main.js in the new URL(...) fixture entry path so the spawned script target is explicit and no post-build rewrite is needed.
  • ✅ Fixed: Redundant branches produce identical results in rewrite
    • Collapsed the duplicate extension-preserving branches in rewrite to a single if (ext) path while keeping behavior unchanged.

Create PR

Or push these changes by commenting:

@cursor push d7e125664c
Preview (d7e125664c)
diff --git a/packages/cua-cli/src/tui/testing/fixture.test.ts b/packages/cua-cli/src/tui/testing/fixture.test.ts
--- a/packages/cua-cli/src/tui/testing/fixture.test.ts
+++ b/packages/cua-cli/src/tui/testing/fixture.test.ts
@@ -4,7 +4,7 @@
 import { KeyCtrlC, KeyEnter, spawnSession } from "@onkernel/ptywright";
 
 function spawnFixture() {
-	const fixtureMain = fileURLToPath(new URL("./fixture-main", import.meta.url));
+	const fixtureMain = fileURLToPath(new URL("./fixture-main.js", import.meta.url));
 	const fixtureJson = fileURLToPath(new URL("../../../src/tui/testing/fixtures/basic.json", import.meta.url));
 	const cwd = fileURLToPath(new URL("../../../", import.meta.url));
 	return spawnSession({

diff --git a/scripts/append-js-extensions.mjs b/scripts/append-js-extensions.mjs
--- a/scripts/append-js-extensions.mjs
+++ b/scripts/append-js-extensions.mjs
@@ -24,8 +24,7 @@
 function rewrite(content, re) {
 	return content.replace(re, (_, prefix, specifier, suffix) => {
 		const ext = path.extname(specifier);
-		if (ext && JS_EXTENSIONS.has(ext)) return `${prefix}${specifier}${suffix}`;
-		if (ext && !JS_EXTENSIONS.has(ext)) return `${prefix}${specifier}${suffix}`;
+		if (ext) return `${prefix}${specifier}${suffix}`;
 		return `${prefix}${specifier}.js${suffix}`;
 	});
 }

You can send follow-ups to the cloud agent here.


function spawnFixture() {
const fixtureMain = fileURLToPath(new URL("./fixture-main.js", import.meta.url));
const fixtureMain = fileURLToPath(new URL("./fixture-main", import.meta.url));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped .js in URL not fixed by build script

Medium Severity

The .js extension was removed from new URL("./fixture-main.js", ...) to new URL("./fixture-main", ...). The append-js-extensions.mjs build script only matches from '...' and import('...') patterns via regex — it won't rewrite a string inside new URL(...). In the compiled output, fileURLToPath will resolve to a path without .js, and since the project uses "type": "module" with Node ≥ 20, ESM entry-point resolution requires explicit extensions, so node /path/to/fixture-main will fail with MODULE_NOT_FOUND.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d650c27. Configure here.

Comment thread scripts/append-js-extensions.mjs Outdated
if (ext && !JS_EXTENSIONS.has(ext)) return `${prefix}${specifier}${suffix}`;
return `${prefix}${specifier}.js${suffix}`;
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant branches produce identical results in rewrite

Low Severity

In the rewrite function, the first branch (ext && JS_EXTENSIONS.has(ext)) and second branch (ext && !JS_EXTENSIONS.has(ext)) both return the same unchanged string ${prefix}${specifier}${suffix}. These two conditionals could be collapsed into a single if (ext) check, making the intent clearer: "only append .js when there's no extension at all."

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d650c27. Configure here.

rgarcia added a commit that referenced this pull request May 13, 2026
#7)

* ai: tighten public surface and document CuaProvider + supported models

- Un-export parseCuaModelRef/formatCuaModelRef (internal helpers).
- Add docs/supported-models.md enumerating CUA-supported models per
  provider with source citations, linked from README.
- Add a CuaProvider section to the README explaining the type, its
  relationship to pi-ai's Provider, and the gemini/google rename.
- Drop the now-redundant "See examples/quickstart.ts" line from the
  README quick start.

* ai: rename gemini -> google, gate public API via named re-exports

- Rename CuaProvider key from "gemini" to "google" so it matches pi-ai's
  Model.provider exactly. providerForModel becomes a thin isCuaProvider
  guard. Drops the rename map and the dead piProviderFor switch.
- Switch packages/ai/src/index.ts from `export *` to named re-exports
  for models.ts and providers/common.ts. Keeps the public surface to
  getCuaModel/listCuaModels/providerForModel/isCuaProvider, the action
  types/input types, CUA tool name constants, CUA_ACTION_TYPES, and
  createComputerToolDefinitions. Internal exports (parseCuaModelRef,
  formatCuaModelRef, findCuaAnnotation, CUA_PROVIDERS,
  CUA_MODEL_ANNOTATIONS, schemas) stay reachable from tests via
  ../src/models.js but are no longer part of the package interface.
- Restore parse/format unit tests, plus update annotation tests to the
  new google key.
- Trim README copy: drop the registry/override caveat, drop "with
  source citations", and rewrite the CuaProvider section now that the
  rename is gone. Reword the action-vocabulary section to talk about
  types instead of dropped schemas.

* ai: update integration test gemini ref to google

* ai: stabilize tzafon batch tool integration expectations

Tzafon's model is non-deterministic about emitting tool calls under our
test prompts; mirror the requireToolCalls guard from #8 so a no-tool-call
response no longer fails CI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: rgarcia <72655+rgarcia@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@rgarcia
rgarcia force-pushed the hypeship/cua-agent-harness-classes branch from 6214e3d to ed31e65 Compare May 13, 2026 19:18
@rgarcia
rgarcia merged commit 96047e8 into main May 13, 2026
4 checks passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Default case silently applies OpenAI config to unknown providers
    • Removed the OpenAI default fall-through and added an exhaustive never guard so newly added providers fail loudly instead of inheriting OpenAI runtime settings.

Create PR

Or push these changes by commenting:

@cursor push 16a6bd22eb
Preview (16a6bd22eb)
diff --git a/packages/ai/src/runtime-spec.ts b/packages/ai/src/runtime-spec.ts
--- a/packages/ai/src/runtime-spec.ts
+++ b/packages/ai/src/runtime-spec.ts
@@ -67,7 +67,6 @@
 				onPayload: yutori.yutoriBuiltinToolsOnPayload,
 			};
 		case "openai":
-		default:
 			return {
 				model,
 				provider,
@@ -76,4 +75,7 @@
 				onPayload: openai.openaiResponsesStoreOnPayload,
 			};
 	}
+
+	const exhaustiveProvider: never = provider;
+	throw new Error(`Unsupported CUA provider: ${exhaustiveProvider}`);
 }

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit ed31e65. Configure here.

defaultSystemPrompt: openai.OPENAI_BATCH_INSTRUCTIONS,
onPayload: openai.openaiResponsesStoreOnPayload,
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default case silently applies OpenAI config to unknown providers

Medium Severity

The case "openai": default: fall-through in resolveCuaRuntimeSpec silently applies OpenAI's full runtime spec — tool definitions, system prompt, and the new openaiResponsesStoreOnPayload middleware that forces store: true — for any provider not matched by an explicit case. While currently unreachable (since providerForModel constrains to five known values), this default clause suppresses TypeScript's exhaustiveness checking: if a sixth value is added to CuaProvider, the compiler will not flag the missing case, and the new provider would silently receive OpenAI defaults including involuntary response storage.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ed31e65. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant