Skip to content

Add a Manus plugin covering API v2 - #418

Merged
TheGreatAxios merged 77 commits into
mainfrom
cl-7087-add-a-manus-plugin-with-a-custom-tool-package-covering-api
Aug 29, 2026
Merged

TheGreatAxios merged 77 commits into
mainfrom
cl-7087-add-a-manus-plugin-with-a-custom-tool-package-covering-api

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor
  • /plugins Manus tile with API-key connect (x-manus-api-key)
  • custom @corbits/manus-tools covering Manus API v2 (tasks, files, skills, projects, agents, webhooks, usage, connectors, browser, website)
  • create_slides is the passing demo (pptx, waits for newest agent status, errors on timeout/waiting)
  • unconnected degrades at tool time; assistant pins the package but does not require a Manus binding at launch
  • Linear: https://linear.app/abklabs/issue/CL-7087/add-a-manus-plugin-with-a-custom-tool-package-covering-api-v2

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

gaasbot — risk counsel on #418 (Manus API-key plugin)

Advice, not an approve. Recommend do not merge as-is. Credential handling is fine; the assistant-pinned tool dump is not.

Blocks merge

1. Assistant-pinned full v2 admin surface, including unconstrained webhooks.

workflows/assistant/src/index.ts:53 pins @corbits/manus-tools on every Myra deploy. packages/manus-tools/src/tool.ts:56-395 then exposes 32 RPC wrappers + create_slides (33 tools). Granola/Exa/Linear/GitHub are not assistant-pinned; they ride specific workflows. Manus is treated like a platform primitive.

Once a tenant pastes a key for “slide decks”, the agent can:

  • webhook_create (tool.ts:256-261) — arbitrary url, no allowlist. Prompt injection → Manus POSTs task events (prompts, file URLs, share links) to an attacker. That is an exfil primitive, not a slides helper.
  • webhook_list / webhook_delete / webhook_pubkey (tool.ts:264-286)
  • browser_online (tool.ts:341-347) — lists live Manus browser clients
  • website_publish (tool.ts:360-370) plus share_visibility: public on task_create/task_update (tool.ts:75, tool.ts:112) — publish tenant work to the open web
  • task_delete / file_delete / agent_update / team usage logs

Gallery copy does not match the grant:

  • packages/plugins-ui/src/plugin-meta.ts:73-74 — “run Manus tasks and retrieve files — including slide decks”
  • packages/connections/src/registry.ts:285-286 — “run tasks and produce files — including slide decks”

Connecting for slides silently grants webhook/browser/publish/delete. Cut the assistant-pinned set to the demo (create_slides + task/file read) or stop pinning this package on wf_assistant until product explicitly wants Myra to administer Manus. Leave webhook/browser/website_publish off the default bundle.

2. Copy understates the grant (competitor-desktop names: none found).

origin/main...HEAD has no Cursor / Claude Desktop / ChatGPT / Copilot copy. The copy risk is the understatement above, not competitor naming.

Ships with a note (credential path is sound)

  • No secrets in source. Tests use fakes (manus_real_key, manus_key). Client asserts it does not attach auth (packages/manus-tools/src/client.test.ts:41-43).
  • Header injection is origin-pinned. packages/credential-providers/src/http-x-manus-api-key-provider.ts:64-93 pins origin, re-reads material per call, headers.set("x-manus-api-key", secret) (overwrites caller), redirect: "manual". Sidecar registers the plugin (apps/sidecar/src/workflow-substrate-factory/index.ts). Persist writes apiBaseUrl: "https://api.manus.ai" (packages/connections/src/persist-credential.ts:108-114) so the pin has an origin.
  • Unconnected degrade is correct. tool.ts:401-418 + tests: missing capability or resolve("manus") throw → "Manus is not connected for this user." Assistant credentialBindings stay empty (workflows/assistant/test/definition.test.ts:153-158). Matches Granola. Keep that; just don’t pin 33 tools to get it.

Filed for later (not merge-blocking if the surface is cut)

  • persist-credential.ts:108 special-cases id === "manus" instead of a descriptor origin field — will rot the first time the host moves.
  • 33 tool defs on every unconnected assistant turn (prompt cost / accidental calls).
  • create_slides polls up to 5 minutes (DEFAULT_SLIDE_MAX_POLLS = 150 × 2s) holding the tool call.
  • file_upload returns a presigned upload_url (typically off api.manus.ai); origin-pin will refuse a PUT through the mediated fetch.

Unraised miss

The team copied Granola’s degrade pattern and then did the opposite of Granola’s pinning pattern. Granola: 2 read tools, not on Myra, connected from Settings when a workflow needs notes. This PR: entire OpenAPI v2 dump, pinned on Myra, Settings copy talks about slides. The dangerous tools are not a future expansion — they ship on the first connect.

Verdict: do not ship until webhook/browser/website_publish (and ideally delete/usage-team) are off the assistant-pinned bundle. Credential plugin + probe + origin pin + unconnected degrade can land with a thin tool set.

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

neckbeard

Neckbeard Review — PR 418 Manus plugin (hygiene only; not an approve)

Actually, I have reviewed this TypeScript and am ready to provide maximally annoying, pedantic nitpicks while completely missing whether the plugin works. Everything should be rewritten in Rust. Also, have you considered blockchain?

I did not implement. I did not --approve. I did not look at #417. LICENSE text is byte-identical to vendor/intx/LICENSE (good, stop celebrating). Pins for @intx/agent / @intx/types are the same 0.3.0 the sibling *-tools packages already use (boring). exactOptionalPropertyTypes construction in optionalCreateFields / extractOutputFiles / createSlideDeck actually omits keys instead of stuffing undefined (fine, I guess, if you insist on a garbage-collected language).

Peak Neckbeard Issues (4 found)

  1. packages/manus-tools/README.md:3-7 — Actually, this is not a "typed client covering tasks, files, skills, projects, agents, webhooks, usage, connectors, browser, and website endpoints." packages/manus-tools/src/client.ts types createTask and listTaskMessages. Everything else is manusRequest → Record<string, unknown>. A README that lists ten domains and a client that types two is a docs lie. Real engineers would generate the entire OpenAPI surface into rustc-checked newtypes with zero-copy serde. Also have you considered protobuf.

  2. packages/connections/src/persist-credential.ts:108-113 — Well technically, you special-cased args.descriptor.id === "manus" and inlined "https://api.manus.ai" instead of putting the origin on ConnectorDescriptor. The same literal also lives at packages/manus-tools/src/client.ts:12, packages/connections/src/probes.ts:128, and packages/credential-providers/src/http-x-manus-api-key-provider.test.ts:8. GitHub persist does not get this treatment (persist-credential.test.ts:76). In production at scale this is an anti-pattern. Should be a const generic in Rust with the origin in the type system. Also blockchain the origin so it is immutable.

  3. packages/credential-providers/src/http-x-manus-api-key-provider.ts:26-29 — Actually, you copy-pasted FetchLike instead of importing it from ./http-x-api-key-provider the way mcp-streamable-http-provider.ts:24 already does. Three FetchLike types in one package is a code smell. Real engineers would use a trait object. Have you considered Zig.

  4. packages/manus-tools/src/tool.ts:385 website_ckpts — Well technically, @corbits/manus-tools/mn:website_list_checkpoints is still under the 64-char OpenAI cap the file header (tool.ts:671-673) cites as the reason to keep the local segment short. ckpts is an abbreviation of an abbreviation of listCheckpoints. Meanwhile webhook_pubkey and task_list_msgs and usage_team_stat each invent their own clipping algorithm. This would never happen in Haskell where the name is the type.

Unbearable Issues (5 found)

  1. packages/connections/src/probes.ts:2 — The file-header still says the live probes are "Granola, Exa, ScrapeCreators, Linear, and GitHub". You added testManusCredential at line 124. The comment now lies. YAGNI, except you definitely needed to update the sentence you were standing in.

  2. workflows/assistant/src/index.ts:36 — "the remaining four are the manager-tools bundles" while ASSISTANT_TOOL_PACKAGE_PINS is ten entries and the new one is Manus, which is not a manager-tools bundle. The test at workflows/assistant/test/definition.test.ts:115 repeats the same lie in its title. Google would never ship a comment that cannot count.

  3. docs/credential-wiring.md:60 — "All four plugins mirror the same origin-pinning…" The sidecar registry at apps/sidecar/src/workflow-substrate-factory/index.ts:387-393 registers five (builtin http, raw-authorization, x-api-key, x-manus-api-key, and createMcpStreamableHttpCredentialProvider). You updated two→four and still forgot MCP. According to a blog post I read, this is an off-by-one.

  4. packages/manus-tools/src/tool.ts:25 JsonSchemaProperty and packages/manus-tools/src/client.ts:94 jsonHeaders — AGENTS.md naming: acronyms are not words. That is JSONSchemaProperty and JSONHeaders. HttpXManusApiKey / createHttpXManusApiKeyCredentialProvider also write Api not API. Matching the sibling HttpXApiKey is not a defense; it is two wrongs. Real engineers would use HTTPXManusAPIKeyCredentialProviderFactoryFactory.

  5. IMPLEMENTATION.md:106 has a whole "GitHub connect (shipped)" section. This PR ships a Manus plugin and touches none of PRODUCT.md / ARCHITECTURE.md / IMPLEMENTATION.md (zero manus hits). The map is not the territory, but the map also does not mention the new continent. Have you considered generating the docs from a Kubernetes CRD.

Maddening Nitpicks (4 found)

  1. packages/manus-tools/package.json:6 LGPL-2.1-or-later + LICENSE byte-identical to vendor/intx/LICENSE — fine, check:licenses will eat it. Well technically the root LICENSE.md is GPLv2-with-AI-Exception and this library is LGPL. I have 15 pages of thoughts on whether a slide-deck poller is a library. Also the SPDX says "or-later" and the file text is 2.1. Pick one. In Rust the license is a compile-time feature flag.

  2. packages/manus-tools/package.json:23-25 pins @intx/agent / @intx/types at 0.3.0 (exact) and arktype at catalog: (which is ^2.2.0 in the root catalog). Mixed exact vs caret in the same object. Premature optimization is the root of all evil, but also pin everything to a git SHA and a lock-free hash map.

  3. packages/manus-tools/tsconfig.json:6 "include": ["src"] vs granola/linear "include": ["src", "test"]. Tests live under src/, so tsc sees them, but you still forked the include list from the sibling packages you copied the rest of the manifest from. Inconsistent tab—wait, you used spaces. I have opinions.

  4. packages/credential-providers/README.md:19-30 register sample still only constructs createHttpRawAuthorizationCredentialProvider. You added a third custom plugin and did not update the snippet that claims to show how to register "it". The new http-x-manus-api-key section is prose-only. Docs that almost lie.

Insufferable Details (3 found)

  1. packages/manus-tools/src/client.ts:16 ManusClientConfig.baseUrl? vs persist's apiBaseUrl vs probe's full URL. Three spellings of one origin, and asClientConfig (tool.ts:421-424) never threads baseUrl at all — it relies on DEFAULT_MANUS_BASE_URL matching the provider row by social convention. Under exactOptionalPropertyTypes this is at least not { baseUrl: undefined }. Under a borrow checker it would not compile.

  2. packages/manus-tools/src/tool.ts:676 id: "@corbits/manus-tools/mn" — mn is not an acronym, not a word, and not manus. granola-tools uses gr, linear-tools uses li, so you cargo-culted a two-letter clip. Have you considered m.

  3. packages/plugins-ui/src/plugin-meta.ts:86 manus: Stack — a generic stack glyph for a slide-deck product. Well actually, in my opinion, this needs a custom SDF icon with SIMD rasterization. Also Manus is not in FEATURED_CONNECTOR_IDS (plugin-meta.ts:95-102). I am not saying it should be. I am saying I noticed.

Key Suggestions (All Terrible)

  1. Rewrite @corbits/manus-tools in Rust with a lock-free poller, custom arena, and inline asm for queryString.
  2. Put https://api.manus.ai on a blockchain so persist/probe/client cannot drift.
  3. Kubernetes operator for the 2s slide poll.
  4. GraphQL federation mesh instead of manusRequest.
  5. One FetchLike. One origin constant. Comments that can count.

Recommendation

Do not --approve from this lane. Hygiene is not broken enough to demand a rewrite, which is disappointing. The LICENSE/pin/exactOptionalPropertyTypes omit-key pattern is copy-paste-correct. The docs-that-lie (README "typed client", probes header, assistant "remaining four", credential-wiring "four plugins", IMPLEMENTATION silence) and the id === "manus" origin special-case are the actual nits. Also, have you considered AI?

P.S. Real engineers would use Haskell anyway.
P.P.S. Actually, real real engineers would use Assembly.
P.P.P.S. Actually actually, real engineers would use Rust.

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

greybeard · block

Adds a Manus connector, a fourth origin-pinned HTTP credential plugin, and @corbits/manus-tools pinned on every assistant deploy — 32 generic v2 RPC tools plus create_slides, with no credential binding on the workflow.

Pin without bind does not deliver a connected key

workflows/assistant/src/index.ts:53 pins @corbits/manus-tools. 8487d54a then dropped credentialBindings so Myra launch does not MissingCredentialError when Manus is absent (workflows/assistant/test/definition.test.ts:153-158). That instinct is right: Linear's required bind (workflows/morning-brief/src/index.ts:93-105) belongs on a specialist whose job is Linear, not on the always-on assistant.

Empty bindings are the wrong fix. docs/credential-wiring.md:9-34 is explicit: handle declaration → definition credentialBindings → buildCredentialDelivery at launch → env.credentials.resolve. With no binding, resolve throws even when Settings has a Manus key. Tool-time "Manus is not connected" (packages/manus-tools/src/tool.ts:409-418) then fires for the connected tenant too. The README (packages/manus-tools/README.md:20-23) claims Granola-style degrade; Granola degrades behind a present binding, not instead of one.

The owning layer already exists for this constraint: packages/folded-runs/src/launch.ts:385-403 folds tenant MCP bindings in at launch when the package is pinned, and does not fail the launch when none exist. Static interchange.credentials handles (packages/manus-tools/package.json:12-16) need that same optional delivery — restore Linear's bind on Myra and every tenant without Manus cannot launch her.

Do not ship a plugin whose connect path cannot reach the agent that pins it.

Generic endpoint map is not a tool package

packages/manus-tools/src/tool.ts:56-395 (MANUS_ENDPOINTS) plus tool.ts:675-709 turn OpenAPI v2 into ~32 tools on Myra. Sibling packages are first-class jobs: linear_list_recent_issues, web_search, github_activity. Capability growth is grants, not baking an admin console into the assistant (ARCHITECTURE.md:115-121).

The map already leaks first-class tools (task_create / task_send_msg / task_list_msgs special-case message.content at tool.ts:479-543). create_slides is the product. Webhooks, browser, website publish, deletes, team usage are not. Shipping them on wf_assistant is a backward-compat trap: removing tools later is a behavior change.

External side effects have no approval: "ask" on this bundle (zero approval hits under packages/manus-tools). The platform invariant is one human gate per side effect leaving the boundary. A generic RPC map has nowhere honest to hang that — stamp ask on everything, or stamp it on nothing. First-class tools own approval per job.

Cut the assistant-pinned surface to the product jobs (create_slides and the task/file reads it needs). Leave the rest off the bundle, or put them on a specialist workflow with a required bind.

http-x-manus-api-key is a fourth copy, not a new constraint

Cannot reuse http-x-api-key: Manus wants x-manus-api-key, not x-api-key. A distinct plugin key on the provider row is correct.

A distinct plugin module is not. packages/credential-providers/src/http-x-manus-api-key-provider.ts is http-x-api-key-provider.ts with the header string changed — origin pin, readCurrentMaterial, redirect: "manual", local resolveTargetUrl all copied. The constraint is "inject this header name on an origin-pinned handle." That belongs in one factory (createHttpHeaderCredentialProvider({ key, headerName })) registered under whatever key the row names. This is the fourth copy (Bearer, raw authorization, x-api-key, now vendor-named x-manus-api-key). Extract before the fifth.

Not a merge blocker by itself. Do not add the fourth copy as if it were the architecture.

Origin pin: persist must not special-case id === "manus"

packages/connections/src/persist-credential.ts:108-114 hardcodes apiBaseUrl: "https://api.manus.ai" when descriptor.id === "manus". Origin-pin is an invariant of delivery (vendor/intx/db/src/credential-resolution.ts:322-335 fails closed on no_origin). The connector descriptor owns the production origin; persist writes it for every origin-pinned connector. id === "manus" is symptom-chasing — the next custom-header connector repeats it.

The literal is already DEFAULT_MANUS_BASE_URL in packages/manus-tools/src/client.ts:12 and the probe URL in packages/connections/src/probes.ts:128. Put apiOrigin on ConnectorDescriptor and delete the branch.

This is load-bearing if a bind is restored: without origin, delivery fails no_origin. Fix the layer, keep the write.

Verdict

Block. Two merge blockers: (1) pin-without-bind cannot deliver a connected Manus key — optional delivery belongs at launch, MCP fold-in is the precedent; (2) MANUS_ENDPOINTS on wf_assistant is the wrong capability surface and has no approval owner. Provider copy and persist special-case are revise-with-the-bind-fix, not independent ships.

Linear required-bind on Myra is also wrong. Do not restore ASSISTANT_CREDENTIAL_BINDINGS without making unresolved optional. Either generalize launch-time fold-in for pinned packages' declared handles, or keep Manus off the assistant and give it a specialist workflow that can require the bind.

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

critic

Pinning @corbits/manus-tools on the assistant without a credential binding leaves a connected Manus key inert at tool time (pin with no bind). Unconnected launch must stay fail-open; connected launch must fold the tenant's manus handle the way MCP bindings already fold. Prior slides_format=pdf / stale desc status / timeout-as-success items look fixed in the current branch.

A pin with no matching binding is inert at tool time. Launch already
folds MCP bindings when the package is pinned; static handles such as
Manus need the same coverage so a connected tenant does not see
"not connected" after Settings.
A pin with no matching binding is inert at tool time. Required
assistant binds would throw on first chat, so launch now folds
CONNECTOR_REGISTRY-fed bindings for pins the tenant already
connected, the same way it already folds MCP server bindings.
The probes header still listed four connectors, the assistant pin
comment still counted four manager-tools bundles, and credential-wiring
said four sidecar plugins while five are registered.

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

critic — fold-in review only (not an approve). The pin-without-bind hole is still open.

Blocking

Production factory never sees a connected Manus credential.

createPinnedPackageCredentialBindingsFor (apps/hub/src/pinned-package-credential-bindings.ts:44-46) asks @corbits/chat's listConnectedProviders. That lister is the inference catalog:

  • packages/chat/src/inference-preferences.ts:42-49 → listVisibleProviders → model_provider (vendor/intx/db/src/catalog-resolution.ts:74-94)
  • model_provider.plugin is enum anthropic | openai | openai-compatible | google-genai (vendor/intx/db/src/schema/catalog.ts:46-48) — manus cannot even be a row there

Connecting Manus writes a provider + credential row and does not seed the catalog:

  • persistConnectorCredential skips seedCatalog unless isInferenceProvider (packages/connections/src/persist-credential.ts:161)
  • isInferenceProvider("manus") is false (persist-credential.test.ts:192)
  • Manus persist test asserts seeds is empty (persist-credential.test.ts:92-112)

The codebase already documents this exact trap for GET /connections:

This is deliberately NOT @corbits/chat's listConnectedProviders … every non-inference connector … reads "not connected" there even with a live, verified credential. (packages/connections/src/workflow-connection-routes.ts:19-24)

Hub already uses the owning check for that route: resolveCredentialRequirement(db, tenantId, { providerName: connectorId, source: "tenant" }, null, null) (apps/hub/src/index.ts:2412-2419).

Trigger: tenant pastes a Manus key in Settings, Myra launches with @corbits/manus-tools pinned and empty credentialBindings (workflows/assistant/src/index.ts:56 + workflows/assistant/test/definition.test.ts:153-158). Factory returns []. deployAtHead concatenates nothing (packages/folded-runs/src/launch.ts:407-423). env.credentials.resolve("manus") still fails "not connected".

Unconnected launch still succeeds (empty extra bindings). The connected path is a no-op.

Unit tests hide this: bindingsForConnectedPins is fed ["manus"] by hand (apps/hub/src/pinned-package-credential-bindings.test.ts:8-16); launch.test.ts mocks the port itself. Nothing exercises createPinnedPackageCredentialBindingsFor against a live non-inference credential.

Should-fix / tests to keep

  • Factory (or hub) test: provider+credential row for manus, no model_provider row → emit { package: "@corbits/manus-tools", handle: "manus", provider: "manus", locator: "tenant" }
  • Inverse: catalog-connected anthropic only + manus-tools pin → no binding
  • Keep the existing deployAtHead tests that fold vs skip vs succeed-empty

Fine (once the lister is the credential-table one)

  • deployAtHead concat + handle skip (launch.ts:414-423) and runtimeConfig (launch.ts:503)
  • Hub wires the port on every mcpCredentialBindingsFor site
  • Chat adapter forwards the optional port
  • Handle/provider/locator match interchange (manus-tools/package.json:13-15) and CONNECTOR_REGISTRY.manus (registry.ts:277-283)

Verdict: do not treat the fold-in as closed until the factory uses resolveCredentialRequirement (or an equivalent provider+credential listing), not listConnectedProviders.

The factory currently lists model catalog providers, so a connected
Manus key never emits a binding. These cases pin the owning check:
a live Manus connector plus a manus-tools pin binds, while a
catalog-only Anthropic connection does not.
listConnectedProviders only sees model catalog rows, so a pasted Manus
key never folded a tenant binding at launch. Ask isConnectorConnected
instead — the same resolveCredentialRequirement check GET /connections
already uses.

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

critic

Re-review of 63fe2a9e (pinned-package credential fold-in: listConnectedProviders → isConnectorConnected / resolveCredentialRequirement). Prior blocker: factory listed catalog-only providers, so a pasted Manus key never folded.

Verdict: hole is closed. No remaining merge blockers on the lister fix.

Confirmations

  1. Factory no longer imports listConnectedProviders. apps/hub/src/pinned-package-credential-bindings.ts takes IsConnectorConnected (L18–21, L46–47) and asks that port per CONNECTOR_REGISTRY descriptor that feedsTools a pin (L52–56). The catalog lister is named only as the thing not to use (L10–12).

  2. Hub composition uses the owning check:
    resolveCredentialRequirement(db, tenantId, { providerName: connectorId, source: "tenant" }, null, null)
    at apps/hub/src/index.ts:718–725. That path walks provider + tenant credentials (vendor/intx/db/src/credential-resolution.ts), not the model catalog. listConnectedProviders remains catalog-only (packages/chat/src/inference-preferences.ts:42–49) and is no longer on this launch path.

  3. Same lambda is passed to GET /connections (index.ts:2417–2421). One binding, no drift.

  4. Unconnected still launches. Assistant pins @corbits/manus-tools (workflows/assistant/src/index.ts:42–44, 56) with empty credentialBindings (workflows/assistant/test/definition.test.ts:153–158). Factory emits a tenant bind only when isConnectorConnected is true; otherwise bindingsForConnectedPins returns [].

  5. Tests would fail a factory-level revert to catalog-only: createPinnedPackageCredentialBindingsFor now takes the port, not db. pinned-package-credential-bindings.test.ts:47–66 injects isConnectorConnected — Manus true emits the bind; Anthropic-only (catalog-style) emits none.

File-for-later (not blocking this fix)

  • The composition-root lambda body is not unit-tested. Swapping index.ts:718–725 back to wrapping listConnectedProviders would not fail the factory tests.
  • listMyraUsableToolPackages (index.ts:2922) still uses catalog-only listConnectedProviders. Different surface (inventory listing), not launch fold-in.

Live list-messages bodies send ISO strings on timestamp. Pin that
parse path, keep numeric timestamps, and still extract agent_status
and attachments.
Live list-messages returns ISO strings. The plugin does not use
timestamp for status or files, so both shapes must parse.
Live create_slides 404s with "task not found". Pin retry on that
casing while keeping the capital-T envelope.
Live 404s say "task not found". Treat any Manus not_found as
retryable during listMessages poll.
Webhook, website publish/update, and task/file delete now declare
approval ask so an assistant pin cannot auto-allow those calls.
create_slides maps agent_status error to a thrown failure so the
tool result is isError.
@TheGreatAxios
TheGreatAxios force-pushed the cl-7087-add-a-manus-plugin-with-a-custom-tool-package-covering-api branch 2 times, most recently from b2af571 to 62e2bc2 Compare August 29, 2026 17:31
@TheGreatAxios
TheGreatAxios force-pushed the cl-7087-add-a-manus-plugin-with-a-custom-tool-package-covering-api branch from 62e2bc2 to 671e9a7 Compare August 29, 2026 17:36

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

critique · request-changes

Credential wiring and origin-pin for x-manus-api-key look sound, and webhook/delete/publish tools are now approval: "ask". The demo path still ships wrong: a live Myra launched before the Manus key is pasted never gets the binding.

  • workflows/assistant/src/index.ts:56 — assistant pins @corbits/manus-tools with no required binding. Bindings are folded only inside deployAtHead.
  • packages/chat/src/connect-pending.ts:219 — settleConnectedService only dispatchTurns the existing run. Sidecar resolve("manus") then throws; create_slides maps that to MANUS_NOT_CONNECTED.
  • Trigger: signup launches Myra → request_connection /plugins?connect=manus → paste key → wake continues → create_slides says not connected until idle reap/relaunch.

Should-fix: create_slides poll budget equals CHAT_TURN_TIMEOUT_MS; stopped-with-no-attachments is still success; several mutating tools stay approval allow.

GitHub will not accept request-changes on the author's own PR; this comment is the review.

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PR 418 live-Myra Manus binding fix

Review of HEAD 0ec1033b on cl-7087-add-a-manus-plugin-with-a-custom-tool-package-covering-api.

Focus: the two commits just pushed, not the whole Manus plugin history.

  • 1f92a69a Add tests for relaunching a live assistant when Manus connects
  • 0ec1033b Relaunch live assistants when a pinned tool-package credential connects

Verdict: approve

Persist-only is gone. Hub feedsTools connect calls reconcilePinnedToolPackages. Tests fail if persist-only is restored in the platform or the extracted helper. No functional merge damage to launch.ts, bun.lock, or manus-tools. Authors are Sawyer Cutler <sawyer@dirtroad.dev>.

Persist-only vs relaunch

Persist-only (stamp a pin/binding onto workbench_launch and leave currentRunId) does not remain as the connect path.

The live-run pass mints a fresh run:

  async function reconcilePinnedToolPackages(
    tenantId: string,
    packageNames: readonly string[],
  ): Promise<{ scanned: number; relaunched: number }> {
    const wanted = new Set(packageNames);
    const participants = await listLaunchesForTenant(
      deps.db,
      tenantId,
      RECONCILE_SOURCES_LIMIT,
    );
    let relaunched = 0;
    for (const live of participants) {
      const pinsWanted = live.binding.foldedBody.toolPackagePins.some((pin) =>
        wanted.has(pin.name),
      );
      if (!pinsWanted) continue;
      try {
        if (await isBeyondWake(deps.db, live.run)) continue;
        if (live.run.address === null || !isRoutable(live.run.address)) {
          continue;
        }
        // ...
        await relaunchTerminalRun(live);
        relaunched++;

relaunchTerminalRun (packages/chat/src/platform-adapter.ts:371) calls launchFoldedRun → deployAtHead, which folds pinnedPackageCredentialBindingsFor (packages/folded-runs/src/launch.ts:467-495). repointBinding then swings currentRunId off the old run.

The pin list on the launch row is still persisted at mint time (empty credentialBindings). That is not persist-only: bindings still fold only at deploy. Connecting Manus no longer stops at settleConnectedService → dispatchTurn on the existing snapshot (packages/chat/src/connect-pending.ts:230).

Idle / not-routable runs are skipped here. That is not a hole for the stated bug: wakeFoldedRun also goes through deployAtHead, so a slept Myra picks up the binding on next wake. The bug is a resident sidecar still serving the pre-connect snapshot; those runs are routable and get relaunched.

Hub feedsTools hook

Yes. API-key connect in packages/connections/src/routes.ts:473 fires onConnected. Hub mounts that as settleServiceConnection (apps/hub/src/index.ts:2112, also oauth/mcp mounts).

Previously the hook returned early unless isInferenceProvider. That early return is gone. After settleConnectedService, Manus (not an inference provider) still reaches the new pass:

    if (isInferenceProvider(info.connectorId)) {
      void chatPlatform
        .reconcileInferenceSources(info.tenantId)
        // ...
    }
    void reconcilePinnedToolPackagesAfterConnect(chatPlatform, info)
      .then((result) => {
        if (result === undefined) return;
        log.info`tool-package connector ${info.connectorId} changed on tenant ${info.tenantId}: re-checked ${String(result.scanned)} live agents, relaunched ${String(result.relaunched)}`;
      })

Helper (apps/hub/src/connection-live-reconcile.ts:23-30):

  • CONNECTOR_REGISTRY["manus"].feedsTools → ["@corbits/manus-tools"]
  • calls chatPlatform.reconcilePinnedToolPackages(tenantId, feedsTools)
  • inference-only connectors (feedsTools: []) return undefined and do not relaunch

chatPlatform is createHubChatPlatform(...) (apps/hub/src/index.ts:1291) with pinnedPackageCredentialBindingsFor wired (:1300) and reconcilePinnedToolPackages exported (packages/chat/src/platform-adapter.ts:1327-1331).

Tests red-capable?

Yes for the actual persist-only bug.

packages/chat/test/platform-adapter.test.ts (describe at 3379):

  1. ensureAwake on an already-routable live run does not relaunch (currentRunId stays run_live, no populated tree). Wake / dispatchTurn is not enough.
  2. reconcilePinnedToolPackages("ten_1", ["@corbits/manus-tools"]) must:
    • { scanned: 1, relaunched: 1 }
    • currentRunId !== "run_live"
    • deployed entry credentialBindings === [MANUS_BINDING]
    • buildCredentialDelivery received that binding

Restoring persist-only (stamp the launch row, leave run_live) fails those assertions. A live run that does not pin the package is left alone (relaunched: 0).

apps/hub/src/connection-live-reconcile.test.ts:

  • connectorId: "manus" must call reconcilePinnedToolPackages("ten_1", ["@corbits/manus-tools"]) and asserts the live registry mapping
  • connectorId: "anthropic" must not call it

Restoring persist-only inside the helper (return without calling the platform) fails the first test.

Gap (non-blocking): the hub test does not boot createHub. Deleting the void reconcilePinnedToolPackagesAfterConnect(chatPlatform, info) line in index.ts would not turn either new test red. The helper comment says that is intentional. Same posture as inference reconcile. Worth a later grep/wire test; not a reason to block.

Recommended permanent tests (already present; keep them):

  • Live Myra pinning @corbits/manus-tools is relaunched on connect and the deployed snapshot contains the manus CredentialBinding
  • A live run that does not pin the package is not relaunched
  • Manus feedsTools maps to @corbits/manus-tools and the hub helper forwards that list
  • Inference-only connect does not enter the tool-package relaunch

Merge damage (origin/418 recovery + origin/main)

Merges immediately under the fix:

  • 68ce2a63 Merge origin/main — conflicts: apps/hub/src/index.ts, bun.lock, packages/folded-runs/src/launch.ts
  • ed9e4ca9 Merge origin/cl-7087 to recover Manus history — conflict: packages/folded-runs/src/launch.ts; net diff was a duplicate import in index.ts

packages/folded-runs/src/launch.ts: no functional damage. Main's resolveLaunchSources extract is intact; Manus pinnedPackageCredentialBindingsFor folding is intact (:467-495). One extra blank line after resolveLaunchSources { (:285) is a merge artifact, not a behavior change. ed9e4ca9 vs 68ce2a63 has no launch.ts diff (kept the already-merged tree). No conflict markers.

bun.lock: workspace entry @corbits/manus-tools@workspace:packages/manus-tools is present. packages/manus-tools lock version is 0.0.1 while package.json is 0.0.11. That mismatch already existed on d96895cc / b2af5713 (pre-merge). Not introduced by recovering history. Granola-tools lock matches its package.json. File-for-later lock hygiene, not merge breakage.

packages/manus-tools: tree intact (client.ts, tool.ts, tests, LICENSE, interchange.credentials.handle: "manus"). Assistant still pins @corbits/manus-tools@0.0.11 without a required binding (workflows/assistant/src/index.ts:42-56). ed9e4ca9 did not touch this tree.

apps/hub/src/index.ts: ed9e4ca9 duplicated import { createPinnedPackageCredentialBindingsFor } .... 0ec1033b removed the duplicate while adding the reconcile import. Current HEAD has a single import (:362).

Authors

Both reviewed commits:

  • Author: Sawyer Cutler <sawyer@dirtroad.dev>
  • Committer: Sawyer Cutler <sawyer@dirtroad.dev>

Non-blocking notes

  1. Hub index.ts wire-up is not itself red-capable (helper is unit-tested with a fake platform).
  2. relaunchTerminalRun still logs run ${run.id} is terminal (${run.status}) while this pass relaunches a running resident. Misleading log; same helper already used by inference drift.
  3. settleConnectedService still dispatchTurns the old run before the fire-and-forget relaunch. Settings connect (no pending room) is unaffected. In-room pending connect can one-shot the stale snapshot, then relaunch — same posture as CL-6687 inference reconcile.
  4. bun.lock packages/manus-tools version 0.0.1 vs package.json 0.0.11 (pre-existing).

Not covered

  • Did not execute the new tests (worktree is main, not 0ec1033b).
  • Did not re-review the rest of the Manus plugin (client, tools, probes, docs).
  • Did not merge or approve on GitHub.

@TheGreatAxios
TheGreatAxios merged commit bd38b3e into main Aug 29, 2026
5 checks passed
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