Repository navigation
feat: NEAR Intents Agent as the agents plugin (tickets 06-12) - #297
elliotBraem wants to merge 18 commits into
Conversation
Copies contracts, outlayer, relayer, owner-auth, database from NEARFDE/near-intents-agent-api at 66833ad as private workspace packages, verbatim source. Mechanical adaptations only: - exports import conditions resolve src (no dist builds; consumers bundle TS) - shared deps aligned to the workspace catalog (zod, drizzle-orm, pg, @scure/base, pglite) to avoid duplicate instances in bundles - tsdown/pnpm scripts removed; typecheck + vitest via the repo toolchain - VENDOR.md per package records the upstream SHA and refresh procedure - biome excludes the generated drizzle migration artifacts Surface-contract smoke tests pin each package's public API (19 tests); all five typecheck clean under TS 7.
…icket 07)
Copies NEARFDE/near-intents-agent-api apps/server/src/{lib,shared,config}
at 66833ad into packages/agents-core — zero web-framework imports, the
slot-based dependency locator (configureRuntime/configureDatabase) is the
injection seam the plugin's initialize layer will drive.
Excluded, replaced by the platform: lib/auth.ts (Better-Auth server setup —
the better-near-auth session is the identity here; the unused auth field was
removed from AppRuntime — the only non-verbatim change) and lib/maintenance.ts
(ports with the modules into the plugin).
Slot-pattern and sponsor-key provisioning tests (7 tests); typecheck clean.
…ng (ticket 08) plugins/agents from the template scaffold: - contract typed from the plugin's own routes (ContractType exported for generated types); ping + dbHealth as the ticket-08 surface - dedicated database: dual-driver db layer — pglite (dev/test, in-memory, trivial advisory serialization) and the vendored production driver (pg Pool + queued session locks + dedicated lock pool) - the vendored migration history applies through the shared migration runner with the per-plugin journal; drift detection wired - initialize wires the agents-core slots exactly once (database, runtime, outlayer client) via a discard-layer composed with Layer.provide — one handle serves both the core and the handlers - sponsor clients configure only when AGENTS_SPONSOR_KEYS is present and the engine supports advisory locks; absent keys log a clear notice Registered in bos.config.json (local dev attachment, secrets list); typecheck green across all 10 workspaces; plugin tests green.
… prep) Copies the complete control-plane engine — modules/ (agents, developer, intents, operations, wallet; 75 files) plus the framework-free mapping helpers api/camel.ts and api/views.ts — at upstream 66833ad. The modules' relative imports resolve within agents-core unchanged; the Hono route shell stays upstream and is replaced by the plugin shell. The root barrel exposes a named service surface (intents generate/submit/ status, agents onboarding and reads, the Actor type) for the plugin shell — star re-exports are avoided across module families on purpose. Whole-tree typecheck clean under TS 7 on first pass; no logic changes.
…s shell (ticket 09) Plugin shell over the vendored core: - contract routes from the upstream wire schemas verbatim (generateIntent, submitIntent, status) — idempotency keys arrive via headers in a follow-up - session→actor seam (src/actor.ts): the better-near-auth session user is the tenant; first activity provisions the tenant row the schema's FKs expect - tracer test: agent_create end-to-end through the real module code — custody provisioning (fake OutLayer client via the core slot), policy staging (storage-estimator seam + the upstream NEAR RPC double fixture), owner intent persisted PENDING_SIGNATURE with the nep366 standard, preview and expiry Submit's sponsor-relay leg is wired but its test harness (sponsor keys + relay doubles, the upstream intent-signers fixtures) is the next session's scope, with the delegated-actions (ticket 11) port after.
…s port (ticket 09) Completes the owner intent flow tracer: - submit-relay leg tested end-to-end with real Ed25519 wallet doubles: the owner's NEP-366 delegate is verified against the prepared policy, storage funding journals through the wallet sponsor, the owner sponsor broadcasts, and the reconciler observes finality into SUCCESS — broadcast alone leaves the agent PENDING (sponsor status 'pending' holds it at PROCESSING) - settled intents replay their status instead of rebroadcasting (the upstream wire contract); the sponsor relay count stays at one - idempotency keys dedupe interrupted generations: same key replays the original correlationId and intent; the plugin handler reads the Idempotency-Key header from the host request context (context schema added) - test harness ported from upstream: stateful provider double (per-wallet policy reads, custody credential tracking), owner-policy sponsor and wallet-sponsor doubles, real NEP-413 proof verification against the RPC double, per-test PGlite with the session→actor mapping in place of API keys
Read routes wired to the core's actor-scoped services, using the vendored wire schemas verbatim so the plugin contract cannot drift from the upstream OpenAPI: agents page/get, wallet identity, balances, policy, policy history, limits, budget, timelock, scheduled executions, token catalog, and per-agent history. Lifecycle actions (policy_update, budget_set, timelock_set, freeze/unfreeze, archive/restore, delete) flow through the existing generate/submit/status routes — they are intent types in the core. Tests: reads are scoped to the session's owner (a second session cannot touch another's agent), and a policy_update intent completes with one wallet signature into revision 2 with the frozen flag applied.
Grant intents complete with one owner signature: the NEP-413 consent signs the credential commitment (never the token), issuance stores the token encrypted at rest, and the listGrants route returns views with the token absent. Revocation is a tombstone — revokedAt set, matching the upstream wire contract. The delegated execution routes (swap/withdraw/transfer under X-Grant-Token) need the upstream api/executions.ts orchestration vendored and are the next slice of ticket 11.
- auth failures use the shared every-plugin UNAUTHORIZED error shape (apiKeyProvided data) instead of a message-only override - dbHealth reports a broken database as not-ok instead of swallowing the error into ok: true - cross-owner refusals asserted for list (empty page), wallet, and policy reads, not just one route - expired unsigned intents fail with intent_expired per the wire contract (new test) - drop explanatory comments from the plugin glue per repo style
The upstream api/executions.ts orchestration is vendored into agents-core (swap/withdraw/transfer/balance-move/deposit/recover public bodies mapped onto the internal ExecutionRequest; quotes via wallet queries). The plugin exposes the execution routes — swap, withdraw (both with dry-run quotes), transfer, shield, unshield, confidential deposit, cross-chain deposit, and recover — with the grant token from the x-grant-token header and the required idempotency key from the idempotency-key header, exactly like the upstream mount. Core tests prove the layered controls: a grant-scoped transfer dispatches once; an action outside the grant's action list is refused per-layer (grant_action_denied); a revoked grant stops new work (agent_grant_required); and the same idempotency key with a different payload is refused (idempotency_conflict). The session→actor mapping now provisions the session's api_keys row — delegated dispatch share-locks the admitting key (dispatch-fence), so a session without its key row could never dispatch grant work. That gap also fixes the owner-policy relay path the earlier sponsor tests exercised.
…ticket 12, first slice) Two invariant suites from the upstream integration estate now run against PGlite through the plugin harness: - draft authority: an unsigned freeze proposal blocks neither policy readiness nor privileged artifact delivery; an ACCEPTED freeze (signed, relayed, pending finality) blocks both while the operation processes — readiness is gated on on-chain authority, not on intent drafts - completion atomicity: an injected owner_intents trigger failure rolls the timelock revision, epoch snapshot, nonces and audit trail back as one transaction; the retry commits exactly once and the ambiguous-state transitions still replay the completed status Remaining ticket-12 suites (operation admission, policy finalization, spend-budget and maintenance races) run against the test Postgres and port with the race infrastructure.
…ertion The session apiKeys row is the admitting authority for delegated dispatch (the dispatch fence caps delegation validity at min(key, grant) expiry). Its expiry was write-once: thirty days after first sign-in every grant execution would refuse with authorization_stale and no recovery path. Each actorForSession call now extends the expiry — revocation still sticks (the update touches only expiresAt) — and requireActor binds the guarded userId to a const instead of a non-null assertion.
…ants (tickets 11-12) Ticket 11 tail: - signMessage route: detached NEP-413 identity signatures under sign-grants, with the policy allowlist and the grant's signing audiences both enforced; unlisted audiences are refused (grant_recipient_denied) and non-challenge messages never reach the provider (signing_identity_challenge_invalid) - grant revocation surfaces the committed correlation ids of work whose dispatch committed but whose outcome no provider has observed yet, with the truncation flag; observed work is excluded - the owner's USD budget charges at dispatch commitment: spend lands in the budget window before the response, the spend that would exceed the cap is refused before any provider write (spend_budget_exceeded) and leaves spend unchanged, and the exact remainder still fits Ticket 12 race slice (against a real Postgres, not pglite): - eight concurrent grant dispatches of one request agree on the correlation id and commit the provider write exactly once — admission serializes through row locks, one winner, idempotent replays for the rest The provider double gains signNearMessage with its call counter and a mutable mutation status so tests can hold an outcome unobserved.
…e race suite on a provisioned postgres A request violating both the grant's recipient list and the USD budget is refused with the GRANT's code and charges nothing — the documented layer order (grant before budget) is asserted, not just single layers. The postgres race suite now derives its admin URL from AGENTS_RACE_DATABASE_URL and skips entirely when unset, so the standard test commands stay green without a provisioned server and the suite never drops a database on a server it does not own.
…ance race suites (ticket 12) The remaining invariant-bearing suites from the upstream postgres estate now run against a real Postgres through the plugin harness, env-gated on AGENTS_RACE_DATABASE_URL and skipped entirely when unset: - spend-budget races (6 scenarios): 24 concurrent commitments against a .50 cap commit exactly the three that fit and refuse the rest with spend_budget_exceeded, charges exist exactly when commitments do; a cap that commits before an in-flight dispatch governs it; a lowered cap applies; a commit-first dispatch is charged under the old caps and cannot be retracted; clearing caps keeps the shared ledger so restored caps count old usage; a losing worker neither dispatches nor refunds the winner (operation_not_pending, one charge) - policy finalization races: five concurrent confirmations apply the revision and advance the policy epoch exactly once with one applied audit event; racing applied/failed outcomes settle operation and policy to the same side under either ordering - maintenance races: a stale interrupted-preparation sweep never fails work that progressed (pending, dispatching, or settled) — the sweep samples before the worker commits and must decide against the state after; concurrent sweeps fail a truly interrupted preparation once (preparation_interrupted, agent abandoned, two audit events) The barrel exposes the internal seams the suites drive: admission (createPendingOperation, claimOperationDispatch), the dispatch commitment (commitExecutionDispatch), finalization, sweeps, and budget reads. Refunds and tombstones ride the same cleanup paths as upstream. This also closes ticket 11's concurrent-over-cap acceptance with the real race.
The four postgres race suites (admission, spend-budget, policy finalization, maintenance) now share tests/support/race-db.ts: the admin drop/create against AGENTS_RACE_DATABASE_URL, the migrations-applying withDatabase wrapper (which configures the core's database handle itself, so callers no longer do it), and the enabled gate the suites skip on. Per-suite database names keep parallel workers isolated. Unused barrel exports (refundSpend, sweepUncommittedOperations) are removed, and biome pruned the unused imports/renamed the unused params it flagged.
|
Ticket 12 race estate — landed through c0d0e08 Now ported and passing against a real Postgres (env-gated on
Gates: plugin suite 30/30 with races live, typecheck 10/10 workspaces, biome 0 errors, UI 411/411 + API 184/184 (e2e failures are the pre-existing environmental ones — local DBs not provisioned). Ticket status update: ticket 11's concurrent-over-cap box is closed by the budget races. Ticket 12 remains partial: ~13 upstream scenarios unported (refund paths, lock-order/deadlock, readback-mismatch fence, onboarding-admission sweeps, operation-admission detail cases) and the "run in CI" box — CI wiring was dropped deliberately, so the suites skip without the env var provisioned. Tickets 14/15 will not be created upstream (per owner call). Remaining for this PR: ticket 13 (boot verification, docs, contract error surface, staging legs). |
The agents plugin ships its own folder-form UI (plugins/agents/ui, the auth-ui pattern): an agents list with a wallet-signed agent_create onboarding CTA, an agent detail page (wallet, balances with USD estimates, policy, USD budget windows, timelock, activity, grants), an execute console (swap with dry-quote preview, withdraw, deposit, sign-message) and grant issue/revoke — all driven by owner-signed intents (NEP-366 delegates via buildSignedDelegateAction, NEP-413 consent via Near.signMessage). Grant tokens are created client-side (WebCrypto) and only their SHA-256 commitment is ever submitted; they ride a per-token oRPC client instance as the x-grant-token header. Everything-dev fix: a local plugin's associated ui development entry was stripped during config resolution (only entries with a nested bos.config.json kept local paths), so the dev harness never allocated a UI port or passed BOS_UI_PORT — the folder-form UI rsbuild then auto-ported into the plugin API's port and MF chunk loads returned HTML. The associated ui now keeps its local development whenever the plugin entry itself resolves locally. The agents plugin (with its ui) is also declared in the authored citynode descriptor, and the plugin contract's generate-intent output now carries the full upstream wire shape (intent + signer) — the old flat schema stripped them, which no browser client could sign around. Go regression boot test + usage docs included.
The relying-party loop now has its server surface: getSignature reads a completed sign-message's artifact once (only the authorizing grant's token can collect it — credential-hash, live epoch, sign-action and audit checks all live in the core), and acknowledgeSignature erases it (ciphertext/keyId/nonce nulled, consumedAt set; acks are idempotent, further reads are 410 consumed). Routes are grant-token-gated like signMessage and mapped through views.signatureDelivery onto the upstream wire schemas. Also variablize the two citynode leftovers: agentsTrustedOrigins (required, comma-separated — feeds configureRuntime's trustedOrigins) and agentsServiceUrl (its hostname is the NEP-413 recipient owners sign into every owner message) replace the hardcoded TRUSTED_ORIGINS/BETTER_AUTH_URL values in the plugin shell; declared in bos.config.json and the authored citynode descriptor.
|
Closed in favor of NEARBuilders/everything-dev#403 |
Ports the NEARFDE near-intents-agent-api server into this monorepo as the `agents` plugin, per plans/deploy-agents-rebuild (tickets 06-12). Draft — invariant suites still landing.
Ticket statuses tracked in the tickets PR; remaining: maintenance-race port, boot verification, staging legs.