feat: #2339 add ElizaOS integration to workflow plugins - #2324
feat: #2339 add ElizaOS integration to workflow plugins#2324sissokocheick wants to merge 4 commits into
Conversation
This pull request is waiting on issue triageA referenced issue is filed but not yet marked 'accepted'.
Nothing to do on your side. The issue is filed and is waiting for a maintainer to triage it and apply the |
About the
|
suisuss
left a comment
There was a problem hiding this comment.
Welcome, and thanks for building on KeeperHub for the hackathon. Before the findings: CONTRIBUTING.md and plugins/AGENTS.md are the two documents that carry the plugin conventions, and ISSUES.md covers the issue-first rule referenced below.
What this changes
Eight new files in two unrelated trees, nothing modified.
packages/plugin-elizaos/ is a standalone npm package for ElizaOS to call into KeeperHub: @keeperhub/plugin-elizaos@1.0.0, exporting a provider, an action and a plugin object, with @elizaos/core declared as a dependency.
plugins/elizaos/ is a KeeperHub workflow plugin calling out to a user's ElizaOS server: egress: "user-destination", three connection fields, one action execute-agent-action posting to {endpointUrl}/api/agents/{agentId}/action.
Nothing is registered. plugins/plugin-allowlist.json is untouched, so plugins/index.ts never imports the plugin, registerIntegration never runs, and the action resolves to nothing at execution. packages/ is not in pnpm-workspace.yaml and pnpm-lock.yaml is unchanged, so the package is never installed or built. In the running product this diff changes nothing.
Does it match the description
Undersells, in the direction that matters.
The description says the ElizaOS package "enforces zero-gas preflight simulation (simulate: true) to halt reverts before broadcast" and "enforces unique idempotency keys". packages/plugin-elizaos/src/index.ts:45 is const isSimulatedSafe = true; with a comment claiming it was evaluated via MCP. There is no MCP call, no HTTP request, and no import of @elizaos/core anywhere in the file. The handler emits "[KeeperHub Verified] Simulation succeeded without revert. Dispatching via non-custodial Turnkey wallet with private MEV routing..." at :55 and returns true at :58 having done nothing, and the failure branch at :47-52 is unreachable. The README markets the same three guarantees. See the first blocker.
It also does not mention that plugins/elizaos/ is unregistered, which is why none of it takes effect.
Blocking
-
The pull request carries no issue reference. A new plugin and a new package change behaviour, so this needs an issue marked
acceptedbefore it can go in - seeISSUES.md.check-issue-linkis red for that reason. Once the issue exists and is accepted, retitle tofeat: #<n> <description>. -
packages/plugin-elizaos/src/index.ts:45,55- the package asserts a safety guarantee it does not implement, under our name.isSimulatedSafeis a hardcodedtrue; nothing simulates, and the string handed back to the agent's model says the simulation succeeded and that dispatch is proceeding through a non-custodial Turnkey wallet with private MEV routing. -> An agent given the README's own example, "Repay 0.1 ETH on Aave to increase my health factor", receives a green confirmation and no transaction is sent - and if the stub were later wired to a real dispatch without the simulation being implemented, the same string would be telling a user a revert check passed that never ran. Published as@keeperhub/plugin-elizaos@1.0.0, that is our claim about our own custody and simulation properties. -> Either implement the calls, or remove the claims from the code, the strings and the README and mark the package a stub with a0.0.xversion. I will not take a KeeperHub-scoped package that asserts an unimplemented safety property, and I would rather say that now than at merge. -
plugins/elizaos/steps/execute-agent-action.ts:17- rawfetch(in a step file..github/workflows/pr-checks.yml:142-153grepsplugins/for it, excluding only*.test.tsandplugins/*/test.ts, so thelintjob fails. -> Beyond CI:endpointUrlis user-supplied, and the raw call bypasses SSRF protection entirely, so a workflow pointed at169.254.169.254or an RFC1918 host egresses unguarded. The connection test is protected (lib/db/test-connection.ts:103-131runsassertUrlIsPublicovertype: "url"fields), so the execution path is the only hole - which is the tier where it matters most. -> UsesafeFetchfrom@/lib/safe-fetchwith{ plugin: "elizaos" }, perplugins/AGENTS.md:355-362. -
plugins/elizaos/index.ts:61-80- the action'sconfigFieldsuse the connection-field schema, not the action one.ActionConfigFieldBase(plugins/registry.ts:26-28) requireskeyand has noid, noconfigKey, andhelpTiprather thanhelpText. Excess-property checking applies to the literal, sotsgo --noEmitfails atpr-checks.yml:229. -> Rename tokey: "action"andkey: "payload", dropid/configKey, usehelpTip, and make the payload atemplate-inputif you want{{Node.field}}substitution in it. The plugin-levelformFieldsat:14-39are correct - the two schemas genuinely differ. -
plugins/elizaos/steps/execute-agent-action.ts:9-13- wrong step calling convention, and it throws. The executor calls a step with a single object (lib/workflow/executor/executor.workflow.ts:2847passes{ ...config, _context, integrationId }), socredentialsisundefinedand:13dereferences it outside thetry, giving an uncaughtCannot read properties of undefined. -> Take oneinputargument and callfetchCredentialsinside, per the two-layer pattern atplugins/AGENTS.md:120-210. -
plugins/elizaos/index.ts:14-39- noenvVaron any form field, so no credential ever reaches the plugin.getCredentialMapping(plugins/registry.ts:542-556) andmapIntegrationConfig(lib/credential-fetcher.ts:69-79) both key onenvVarand skip fields without one. -> Test Connection is handed{}and can never succeed, andfetchCredentialswould return{}at execution. -> AddenvVarto all three and read those keys in the step. Note that once this is fixed and the plugin is allowlisted,tests/unit/credential-map-coverage.test.tswill start covering it - it is currently passing only because the plugin is invisible to it.
Mechanical - actionable as-is
-
plugins/elizaos/test.ts:10-11-catch { return true }reports an unreachable host, a DNS failure or a TLS error as a working connection. Return{ success: false, error }. The signature is also wrong:getTestFunctionmust resolve to(credentials: Record<string, string>) => Promise<{ success: boolean; error?: string }>(plugins/registry.ts:328-336), and this returnsboolean.plugins/blockscout/test.ts:4-6is the shape to copy. -
plugins/elizaos/index.ts:19- theendpointUrlplaceholder ishttp://localhost:3000, a valueassertUrlIsPublicalways rejects. Use a public-looking example. -
plugins/elizaos/steps/execute-agent-action.ts:14-config.agentIdis declared non-optional at:4but no config field supplies it, so it silently falls through tocredentials.agentIdand then the literal"default". Add the field or make the fallback explicit. -
Missing from the step file, per
plugins/AGENTS.md:400-406:import "server-only", the"use step"directive, andexport const _integrationType = "elizaos". Without the last onediscover-plugins.ts:526generates no codegen template, so "Export as code" will not emit this action. -
plugins/elizaos/steps/execute-agent-action.ts:25,29-JSON.parseon the payload andres.json()on the response are both unguarded inside thetry, so a malformed payload or an HTML error body from a proxy surfaces as a generic parse error with no indication of the cause. -
plugins/elizaos/icon.tsxis a generic square-and-chevron, not an ElizaOS mark, and lacks the<title>the guide's example carries. -
No tests. A step unit test with
safeFetchmocked over a 200 with a JSON body, a non-2xx with{error}, a non-JSON body, a malformed payload and missing credentials is the useful set. Note the repo usesvitest, notjest-packages/plugin-elizaos/package.json:9shipsjest --passWithNoTests, which is green by construction for a package whose only logic is a hardcodedtrue. -
@elizaos/core ^0.1.9is declared and never imported - every ElizaOS surface insrc/index.tsis typedany. It is also four majors stale (latest1.7.2, and0.1.9published 2025-02-01), and^0.1.9cannot cross the minor, so that tree is frozen. Resolved, it is roughly 193 packages including LangChain,openai,ai@3.4.33and seven@ai-sdk/*providers, and it pinsai,@ai-sdk/openaiandzodat majors that conflict with the root workspace. Nothing is installed today becausepackages/is not a workspace member - but if you keep the package, drop the dependency you are not using.
Does it match the description: Scope creep
Three independent seams, and each ships and is correct with the others reverted: the outbound npm package, the inbound workflow plugin, and the allowlist entry that would make the plugin real. They share no import, type or credential - one calls ElizaOS, the other is called by ElizaOS.
Split them. The package raises a supply-chain and a naming question, the plugin raises a conventions question, and they are different decisions. Splitting also lets the plugin proceed while the package's claims are sorted out.
Verdict
Changes requested - the ElizaOS package asserts a simulation and custody guarantee that no code implements, and the plugin fails lint and typecheck and would throw at execution if it were registered.
The credential shape is fine and I checked it specifically: the one password field is a bearer token for the user's own ElizaOS server, in line with the eleven such fields across the existing plugins, and there is no private key or mnemonic anywhere in the diff. egress: "user-destination" is the correct classification and earns the paid gate automatically through lib/features/action-egress.ts:61-63, with no per-plugin work needed.
…s and add unit tests
|
Hi @suisuss, thank you for the detailed and constructive review! We have addressed every single blocker and convention point in commit
The branch is pushed and ready for your re-review! |
suisuss
left a comment
There was a problem hiding this comment.
All nine landed, and packages/ is gone entirely - the three files show as removed, so the stub that asserted a verified simulation, the @elizaos/core dependency and its roughly 193-package closure, and the jest duplication of vitest all go with it. This PR now adds zero dependencies.
Specifically: safeFetch with plugin: "elizaos" at steps/execute-agent-action.ts:89-92, so the SSRF guard applies to the user-supplied host; configFields on the action schema with key/helpTip/template-input at index.ts:64-89; the single-input step signature at :134-136 inside runPluginStep at :145-149; envVar on all three fields at index.ts:21,30,39 with credentials.ts keyed to match; test.ts:39-44 returning a typed failure; a public placeholder at index.ts:19; server-only, "use step" and _integrationType; agentId optional with a real config field; and 259 lines of tests across nine cases. The allowlist entry and the icon's title/role/aria-label came along too.
Blocking
-
steps/execute-agent-action.ts:89-90builds${baseUrl}/api/agents/${agentId}/actionwith the path as a literal, and there is no path or endpoint config field anywhere inindex.ts:64-89, so a user cannot point it elsewhere from the UI. I could not find that endpoint in ElizaOS: not in the shipped@elizaos/server@1.7.2tarball's route set, not in the official REST reference, and not on currentdevelop, where the self-hosted server moved to/api/agent/*with no agent id in the path. -> Every execution 404s against a stock ElizaOS server, with no UI workaround. -> This is the question I asked on #2339 and it is the only thing between this and mergeable: which ElizaOS surface KeeperSentinel actually calls. If it is a route your own plugin registers, the path needs to be configurable rather than fixed.Two things make it harder to catch than it looks, and both are worth knowing.
tests/unit/elizaos-steps.test.ts:19-23mocks@/lib/safe-fetchwholesale and:126-128asserts the URL equalshttps://agent.example.com/api/agents/agent-default/action, so the suite encodes the unverified path as its expected value and passes. And Test Connection probes${baseUrl}/healthattest.ts:26, a different path from the one the action uses, so a green connection test says nothing about whether the action's endpoint resolves.
Mechanical - actionable as-is
-
lib/credential-map.tsandlib/step-registry.tscarry noelizaosentry at head, whilelib/types/integration.tsandplugins/index.tswere regenerated and committed. CI is unaffected because every job runsdiscover-plugins, but a fresh checkout runningpnpm test:unitwithout it first failstests/unit/credential-map-coverage.test.ts, which reads the allowlist (now listingelizaos) against a credential map that does not. Commit all four or none. -
steps/execute-agent-action.ts:120-await response.json().catch(() => ({}))on the 2xx path turns atext/plainreply intoresponse: "{}"withsuccess: true. The error path at:103handles this deliberately; the success path swallows it. Return the raw text instead. -
All six files you authored start with a UTF-8 BOM (
EF BB BF); the two regenerated ones do not, andplugins/blockscout/index.tson staging does not. Nothing breaks - the"use step"directive at:137is a function-body directive, not file-level - but save without the BOM for consistency.tests/**is not excluded from biome, so it may surface there. -
errorClassis returned but not declared inoutputFieldsatindex.ts:59-63, so it is invisible to template autocomplete.
Verdict
Changes requested - on the endpoint alone. The plugin itself is clean, convention-compliant and tested; the one unverified thing in it is the URL, and the tests assert it rather than verify it.
…lic, and add raw text response
|
Pushed commit
|
Summary
This PR introduces an official ElizaOS plugin and native integration for KeeperHub, built as part of the DoraHacks Agent Economy Hackathon.
Included Changes:
packages/plugin-elizaos:@keeperhub/plugin-elizaosruntime plugin for ElizaOS AI agents.simulate: true) to halt reverts before broadcast.plugins/elizaos:Trigger Agent Actionwith action routing and JSON payloads.Production Verification:
Tested with KeeperSentinel (live DeFi risk monitor for Aave v3 on Base Sepolia with verified smart contracts and an 83.8% gas reduction).