Skip to content

Add service job provider runtime#9

Open
ai-virtual-b wants to merge 12 commits intomainfrom
service-jobs-be-runtime
Open

Add service job provider runtime#9
ai-virtual-b wants to merge 12 commits intomainfrom
service-jobs-be-runtime

Conversation

@ai-virtual-b
Copy link
Copy Markdown
Contributor

@ai-virtual-b ai-virtual-b commented Apr 28, 2026

Summary

  • add acp serve commands for service-job provider runtimes: init, start, endpoints, deploy bundle, stop, logs
  • add handler/budget/offering scaffolds and a runtime that connects outbound to the BE service-job namespace
  • keep x402/MPP public endpoints on BE while the provider self-hosts or deploys the handler runtime
  • keep --settle-8183 reserved but disabled for x402/MPP until ERC-8183 service-job support exists

Notes

  • deploy currently creates a Docker-ready bundle and next steps; it does not execute a hosting-provider deployment
  • provider runtime needs ACP_AGENT_TOKEN and outbound access to BE

Tests

  • npm run build
  • tsx bin/acp.ts serve endpoints --dir /tmp/acp-cli-smoke --json

Note

High Risk
Adds a new provider runtime that verifies and settles x402/MPP payments (on-chain transactions) and introduces new auth-token issuance/storage and outbound Socket.IO relays, which are security- and funds-sensitive paths.

Overview
Adds a new acp serve command suite (init, start, endpoints, deploy, stop, logs) to scaffold and run a provider “service-job” runtime that executes a developer handler.ts for selected offerings.

Introduces a serve/ runtime/server that connects outbound to the BE /service-jobs Socket.IO namespace, builds payment challenges, verifies and settles x402 (EIP-3009) and MPP (tempo via mppx) payments, and returns deliverables + protocol response headers back to BE; --settle-8183 is explicitly reserved/blocked for relay-based protocols.

Adds sandboxed handler execution support, offering/budget scaffolding templates, and a deployable bundle flow (Dockerfile + optional Railway CLI execution). Also updates config/auth plumbing to support ACP_API_URL selection and persisted ACP_AGENT_TOKEN fetched via signed message (/auth/agent).

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

Comment thread src/commands/serve.ts
Comment thread src/commands/serve.ts Outdated
Comment thread serve/server/index.ts
Comment thread src/commands/serve.ts Outdated
Comment thread serve/runtime/loader.ts Outdated
Comment thread src/commands/serve.ts Outdated
Comment thread serve/runtime/sandbox.ts
Comment thread serve/server/index.ts
Comment thread serve/server/payment/chain.ts Outdated
Comment thread serve/server/payment/mpp.ts
Comment thread src/commands/serve.ts
Comment thread src/commands/serve.ts Outdated
Comment thread serve/server/payment/chain.ts Outdated
Comment thread serve/server/payment/chain.ts Outdated
Comment thread src/commands/serve.ts Outdated
Comment thread src/commands/serve.ts Outdated
Comment thread src/commands/serve.ts Outdated
process.kill(pid, "SIGTERM");
stopped += 1;
} catch {}
break;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unconditional break prevents fallback to legacy PID files

Medium Severity

The break statement in the stop command's PID file loop is unconditional — it sits outside the try/catch, so it always executes after the first existing PID file is found, even when process.kill() throws (e.g., stale PID). This prevents the loop from falling through to check the legacy PID file path, which is the entire reason the loop and pidFiles array exist.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6eccf1e. Configure here.

Comment thread src/commands/serve.ts Outdated
"package-lock.json",
"tsconfig.json",
]) {
const source = resolve(process.cwd(), relativePath);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deploy bundle copies from cwd instead of CLI source

Medium Severity

copyRuntimeBundle resolves CLI source directories (bin, src, serve, etc.) relative to process.cwd(), but process.cwd() is the user's working directory, not the CLI package's installation directory. The init command correctly uses import.meta.url to locate scaffold files, but deploy does not, so the bundle will silently miss CLI source files if the command is run from a directory other than the repo root.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6eccf1e. Configure here.

Comment thread serve/runtime/sandbox-worker.ts
Comment thread serve/server/payment/chain.ts Outdated
Comment thread src/commands/serve.ts
),
opts.bundleDir,
);
commands.push(result.command);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

MPP secret key exposed in command-line arguments

Medium Severity

The MPP_SECRET_KEY is passed as a command-line argument to railway variable set via the envVars array, making it visible in process listings. The agent token is correctly protected by passing it via --stdin, but the MPP secret key (a cryptographic secret used for signing payment challenges) receives no such protection.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3ceb483. Configure here.

input: unknown;
};
const originalEnv = process.env;
process.env = {};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sandbox environment clearing breaks handler module resolution

Medium Severity

Setting process.env = {} before import(handlerPath) clears the entire environment, which can break Node.js module resolution and any handler dependencies that read environment variables at import time (e.g., NODE_PATH, database connection strings). The finally block restores process.env only after execution completes, which is too late for import()-time env reads. Additionally, the sandbox provides minimal actual isolation since the handler can access workerData, the filesystem, and the network.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit be65840. Configure here.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

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.

There are 6 total unresolved issues (including 4 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7338dc2. Configure here.

Comment thread src/commands/serve.ts
const existing = getAgentToken(wallet);
if (existing && !isTokenExpired(existing)) return existing;

const chainId = Number(process.env.ACP_CHAIN_ID || "84532");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Auth token chain ID defaults to wrong network

High Severity

getOrCreateAgentToken defaults chainId to "84532" (Base Sepolia testnet) when ACP_CHAIN_ID is not set, while getDefaultChainId() in the payment system defaults to base.id (8453, Base mainnet). This means the auth token is generated for a different chain than the one used for payment processing, likely causing authentication failures when no explicit ACP_CHAIN_ID env var is provided.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7338dc2. Configure here.

const chainId = getDefaultChainId();
const network = `eip155:${chainId}` as Network;
const asset = DEFAULT_STABLECOINS[network].address;
const decimals = DEFAULT_STABLECOINS[network].decimals;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing null check on stablecoin lookup in MPP

Medium Severity

DEFAULT_STABLECOINS[network] is accessed directly for .address and .decimals without checking if it's defined. If the network isn't supported, this throws an unhelpful TypeError. The equivalent code in x402.ts properly guards with if (!asset) throw new Error(...) before accessing properties.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7338dc2. 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.

2 participants