Repository navigation
Add magpie-agent-console (page plugin) - #535
Conversation
yetone
left a comment
There was a problem hiding this comment.
Thanks for this. I reviewed the npm package the entry points to, magpie-agent-console@0.1.0: I unpacked the tarball and read index.mjs, page/ and daemon/dist/main.js. I can't add it to the market yet. One security hole has to be fixed first, and a few things need the maintainer under this repo's contributor rules.
1. Any web page can read the daemon's token and drive the user's agents with full access.
checkOrigin(daemon/dist/main.js:5036) acceptsOrigin: null:For that origin, the preflight answersif (origin === void 0 || origin === "null" || origin === "") return { ok: true };
access-control-allow-origin: null(:5074).GET /servesapp/index.htmlwith__TOKEN__replaced by the real token (:5146).- A sandboxed iframe, or a
data:orfile:page, sendsOrigin: null. So any website the user visits can:- fetch
http://127.0.0.1:3488/; - read the token out of the HTML;
- open the WebSocket, which runs the same
checkOrigin, and sendhellowith the token.
- fetch
- From there it has the whole RPC:
project.upsert,thread.setModeandthread.send.bypassPermissionsmaps to Codexdanger-full-accesswith approvalnever(:2473), so that is code execution on the user's machine.
I checked this on a Mac mini in a sandbox HOME (npm install --ignore-scripts, daemon started on port 3591, no real accounts):
curl -H "Origin: null" http://127.0.0.1:3591/ -> 200, access-control-allow-origin: null, token in the page
curl -H "Origin: https://evil.example" http://127.0.0.1:3591/ -> 403
GET /api/ping with that token and Origin: null -> ok
magpie had this same class of bug (an Origin: null page spending a subscription without a key) and fixed it. Please:
- treat
nullas foreign; - never put the token in a page an unauthenticated GET can read: hand it to the frame through magpie's page bridge instead;
- check
Hosttoo, to cover DNS rebinding (a rebound page's GET carries no Origin at all).
2. The daemon starts when the module loads, and outlives magpie. index.mjs spawns daemon/dist/main.js with detached: true and unref() (:147-152) as soon as the plugin is imported, before the user opens the page. Please start it when the page opens, and stop it when magpie exits.
3. Things the maintainer has to clear first (from docs/contributors.md, "Look for harm first"):
- Runtime dependencies, including a native module (
node-pty) and@anthropic-ai/claude-agent-sdk, all on caret ranges. - Minified or bundled code:
app/app.jsis a 977 KB bundle anddaemon/dist/main.jsis 252 KB, neither built in this repo. To review it, we need the source and a lockfile that rebuilds these bytes exactly. - npm 0.1.0 was published after this PR was opened. The entry points at an external package whose contents can change without a PR here, so the version would have to be pinned, or the source brought into
packages/.
I'm requesting changes for item 1. Once it's fixed, please say which version to look at, and I'll check it again.
|
Thanks for the thorough review. All three points are addressed in 1. Token / Origin
2. Lifecycle
3. Maintainer items
|
|
Thanks for the quick turnaround, and for the detail. I haven't reviewed the fixes yet, because they aren't in this PR. Its head is still 38e9f19 from 10-30, and the diff is the same 10-line On pinning, which answers your question: So please move the source into this PR as
What I looked at in 1.0.0 meanwhile (tarball sha512 matches yours; I read it and ran nothing): the gate design looks like the right shape. One more thing to fix while moving it: the daemon imports only |
A Magpie window page that drives local Agent CLIs (Claude Code, Codex, Cursor, and others) via a persistent Node daemon. Co-authored-by: Cursor <cursoragent@cursor.com>
The source of magpie-agent-console 1.0.0 (qingnian108/magpie-agent-console@0d3c19e, tag v1.0.0), moved here as @magpie-community/page-agent-console so every version ships from this repository through a pull request (review of magpie-community#535). - TypeScript source (daemon/src, web/src), index.mjs, lib/, page/, assets/, build and verify scripts, tsconfigs, vitest config, package-lock.json, LICENSE, README, THIRD-PARTY-NOTICES and the vitest suite (test/v1, including pr535-http, gate, lifeline and the new terminal-fallback). - app/ and daemon/dist/ are committed: publish.yml packs and publishes each package as checked in (npm pack --ignore-scripts, no build step), so a PR's bundles are what users get. `npm ci --workspaces=false && npm run verify:build` in the folder rebuilds them byte for byte. - package.json: magpie.page and permissions: [] (the page makes no magpie.read), magpie.icon as a data URI, repository/bugs/homepage here. - dependencies are what the daemon and index.mjs load (ACP SDK, Claude Agent SDK, ignore, ws, zod); node-pty is optional; the 11 UI packages bundled into app/app.js are devDependencies. Versions and lockfile integrities unchanged. - README: why node-pty is 1.2.0-beta.15 and what happens when it can't load. - agent-console.test.mjs: what the root `bun test` can check with no install (package shape, dependency split, the page reads nothing and reaches no network, the gate's Origin/Host/key rules). - bunfig.toml: the root `bun test` skips the package's vitest files, which need its npm dependencies. - README: the package in the Pages table.
…mmunity page Points the entry at the package now in packages/agent-console, marks it community like the other entries, and drops the icon URL into the author's GitHub repository (the package carries its own magpie.icon).
Same change as qingnian108/magpie-agent-console community-move fbcd437. Cursor, Copilot, OpenCode, Pi, Devin CLI and dsh (AcpSession) used to drop attached images, and an image-only message ended without a prompt. - Images go as ACP image blocks when the Agent's initialize advertises promptCapabilities.image, else as the file's path (text + resource_link) with a timeline notice; audio and text files likewise by capability. Inline media is capped at 10 MB a file, 16 MB a message. - An image-only message starts a turn. - capabilities.images for ACP providers follows what the probe or a session's initialize reported (imagesKnown: false until then); the composer notes when the Agent will get a path instead. - test/v1/acp-images.test.ts with a fake ACP agent fixture. - app/ and daemon/dist/ rebuilt; npm run verify:build matches.
…nput Mirrors magpie-agent-console 54cba6d: PNG/JPEG/GIF/WebP go as images; Claude Code gets text files as plain-text documents; Codex gets text by path; other files go by path with a notice. Adds test/v1/attachment-content.test.ts.
Mirrors magpie-agent-console 17e1a29: dsh answers promptCapabilities.image for the route its acp row starts on and refuses image blocks otherwise, so dsh is started with a --patch naming the conversation's route. Adds test/v1/dsh-images.test.ts and an opt-in npm run test:real (real CLIs, a throwaway HOME and a local stand-in for the model API).
Mirrors magpie-agent-console 077b627: pick session/load or session/resume from the Agent's initialize; dsh uses resume. Adds test/v1/acp-resume.test.ts and an opt-in real dsh check.
38e9f19 to
41c7a94
Compare
|
@yetone Thanks again for the review. I've reworked this PR to match what you asked for and pushed an update (force-push of Layout / package
Dependencies / shipping
Security (from the earlier pass; still in place)
Extra fixes in this update
Verification
Could you take another look when you have a moment? Thanks. |
yetone
left a comment
There was a problem hiding this comment.
Thanks for the rework. I reviewed 41c7a94. I read index.mjs, lib/gate.mjs, lib/pathscan.mjs, page/ and the daemon source behind every route: security.ts, http.ts, ws/gateway.ts, main.ts/config.ts, the providers' spawn and permission modes, magpieGateway.ts, and the history import. I also ran it on a Mac mini in a throwaway HOME, with no real accounts.
What I ran
npm ci --ignore-scripts, thennode scripts/verify-build.mjs. All five bundles rebuild byte for byte (app/app.js9a6911f3…,daemon/dist/main.js324ab13b…).vitest run: 44 files, 240 passed, 17 skipped (thetest/realones). That includes the gate, the HTTP/WS policy (Originnull, a foreign Origin, a bad Host, a WS withouthello), the lifeline, and theserver()→session.js→/open→ daemon →stop()run.- The repo root:
HOME=$(mktemp -d) bun testgives 3997 pass, 0 fail.bun run checkis clean. - Installed the way magpie installs a plugin:
npm pack --ignore-scripts, thenbun add --ignore-scripts <tgz>into an empty folder. It installs,index.mjsimports with no side effects, andnode-ptyloads from its prebuild.
The October 30 review: met
- Token. No HTML has the token any more, and an unauthenticated
GET /is clean. Originnullis refused on static routes. CORSnullcomes back only after a valid token. Foreign Origins are always refused. Host is checked on HTTP and on the WS upgrade. The gate key / fragment handoff holds up: a leakedsession.jscan't reach the API. - Daemon lifetime. It's no longer detached. It starts on the first
/open, holds a stdin lifeline, polls its parent pid, and exits after 60 idle minutes. The 0.1.0 daemon is shut down after a token ping. - Maintainer items. The source is in
packages/, every direct dependency is pinned exactly, the UI packages are devDependencies, and there are no install scripts. I looked for harm: the only addresses it calls are local (/v1/modelson a 127.0.0.1/localhost base). It reads~/.claude/settings.jsonand magpie'sapplied.jsonread-only. I found no telemetry, noeval/new Function, and no credential reads. I've cleared the dependency list and the bundles.
One thing to change before it goes in the market: say what it downloads
A cold install through magpie's path is 367 MB in node_modules:
272M @anthropic-ai/claude-agent-sdk-darwin-arm64 (the SDK's native Claude Code binary, an optionalDependency of the SDK)
26M node-pty
23M @anthropic-ai/sdk
4.7M @anthropic-ai/claude-agent-sdk
The chat page, the closest page plugin, has no runtime dependencies. This one is a click in the market that downloads about 370 MB into magpie's shared plugins/ folder. magpie gives an install 5 minutes, and a bun add cut off halfway leaves that folder half-written for every plugin. The README and the registry summary never mention the size. The 272 MB binary is only used when claudeExecutable() returns null: a Windows .cmd/.ps1 shim, or no claude found. Everyone else runs their own claude.
Please:
- add one line to the README and to the registry summary, in en and zh, giving the install size and that it includes a Claude Code binary and node-pty's native terminal module. Then the user decides before clicking;
- if you can find a way to drive the user's own
claudeeverywhere without installing the SDK's platform binary, that is better, but it isn't required.
Notes, not blocking
- The root
bunfig.tomlis needed (rootbun testcan't run vitest files), but it also means nothing on CI runs this package's suite. We'll add a step for it topr.ymlon our side. You don't need to touch.github. - Your README says a leaked gate key lets a page "at most open the console UI in a frame". The daemon's pages send no
frame-ancestors/X-Frame-Options, so that frame can be clickjacked into a UI that can run agents inbypassPermissions. Reaching the key takes magpie's per-run plugin path, so the risk is low. If you find a policy that still works inside magpie's own frame, it's worth adding. bun adddoesn't readpackage-lock.json, so the dependencies of your dependencies resolve at install time. That's how every plugin installs, so it isn't something to fix here.
Once the size line is in, say which head to look at and I'll merge it.
|
@yetone Thanks — head is now de011d1. Install size (your required change)
Optional notes
Please re-check and merge when you are happy. Thanks again. |
yetone
left a comment
There was a problem hiding this comment.
Thanks for this. Getting rid of the 272 MB binary was the better fix I mentioned, and the build side holds up. I reviewed de011d1 and read every line of the diff since 41c7a94. One thing blocks the merge: the license of the code that is now vendored and bundled.
What I ran at de011d1 (Mac mini, throwaway HOME, no real accounts)
npm ci --ignore-scripts --workspaces=false, thennode scripts/verify-build.mjs. All five bundles match byte for byte (daemon/dist/main.jsb4c856df…,app/app.js9a6911f3…).- I replaced
vendor/claude-agent-sdkwith the untouched npm tarball of@anthropic-ai/claude-agent-sdk@0.3.312and rebuilt. It gives the samemain.js. The vendored copy differs from npm only inpackage.json(theoptionalDependenciesdropped, and the description) and in CRLF line endings insdk.d.ts. So the bundle hides no code of its own. vitest run: 44 files, 240 passed, 17 skipped.npm run checkis clean. The repo root'sHOME=$(mktemp -d) bun testgives 3997 pass, 0 fail, andbun run checkis clean.- Installed the way magpie installs a plugin:
npm pack --ignore-scripts, thenbun add --ignore-scripts <tgz>. That gives 48 MB on macOS, 49 MB on Linux (WSL2 Ubuntu 24.04) and 45.6 MB on Windows 11. No@anthropic-ai/*is installed.index.mjsimports on all three.node-ptyloads from its prebuild with no scripts and runs a command in a pty on all three.
1. Blocker: the Claude Agent SDK can't be redistributed in this package
vendor/claude-agent-sdk/LICENSE.md says:
© Anthropic PBC. All rights reserved. Use is subject to the Legal Agreements outlined here: https://code.claude.com/docs/en/legal-and-compliance.
package.json has "license": "SEE LICENSE IN README.md", and that README points to Anthropic's Commercial Terms. Neither grants a right to redistribute. Before, a user's install downloaded the SDK from Anthropic's own npm package. Now this PR puts a copy of Anthropic's code in our repository (vendor/), and publish.yml would publish it inside daemon/dist/main.js (about 1.4 MB of the 1.67 MB file) as @magpie-community/page-agent-console, under that package's MIT LICENSE. We can't host or publish that. The package's own MIT license would also seem to cover code that isn't MIT.
Either of these fixes it:
- (a) Depend on the SDK from npm again. Make it an exact-pinned
dependencyand drop the alias inbuild-v1.mjs, sopackages: "external"leaves it as an import. Then put the real size in the README and the registry summary, in en and zh: about 370 MB, including a Claude Code binary for the user's platform. That was all the last review asked for, and it's the smaller change. - (b) Drop the SDK. Drive the user's own
claudeover its stream-json protocol in your own code (claude -p --input-format stream-json --output-format stream-json --verbose, with permission prompts over the control messages), the way you already drivecodex app-server. That keeps the ~49 MB install, and no Anthropic code ships in the package. It is more work and needs its own tests against a realclaude.
Please don't download the SDK at run time instead (npm install from the daemon on first use). That is code that never passes through a PR here.
Remove vendor/claude-agent-sdk/ from the branch whichever way you go. A squash merge would otherwise still carry it into main's history.
2. Smaller things in this diff (fix them with 1; they only matter if you take (b))
ClaudeSessionnow throws whenclauderesolves to a.cmd/.ps1wrapper. Until now, the SDK's own binary covered those Windows users. If you keep (b), please give the error in English too: the throw is Chinese only, while the rest of the UI follows the user's language.THIRD-PARTY-NOTICES.mdstill lists@anthropic-ai/claude-agent-sdkunder "Installed with the plugin (dependencies)". That's no longer true at this head.package.json's Chinesedescriptionandtitleswere rewritten as\uXXXXescapes. The JSON is the same, but it makes the file harder to read. Probably a side effect of a tool. Please write them back as plain text.
Notes, not blocking
agent-console.test.mjsnow looks only at^import … fromlines. That's fine for this esbuild output, since itsrequire("ajv/…")lines are ajv's code-generation strings, not real imports. If you take (a), the SDK goes back on theRUNTIMElist and the test's "no claude-agent-sdk import" line comes out.- Not tried: a real Claude Code turn through the bundled SDK, and magpie's macOS desktop window (you said the same).
Once 1 is fixed, tell me the head to look at. I'll rerun the same checks and merge it.
…MB install
The Claude Code provider no longer uses any Anthropic package. It starts the
user's own `claude` per conversation:
claude -p --input-format stream-json --output-format stream-json --verbose
--include-partial-messages --permission-prompt-tool stdio
[--model] [--permission-mode] [--effort] [--resume]
and speaks its line-delimited JSON (daemon/src/providers/claude/cli.ts):
initialize (slash commands), can_use_tool asks answered allow/deny,
control_cancel_request, interrupt, set_model, set_permission_mode.
bypassPermissions and effort relaunch with --resume. A .cmd shim runs
through cmd.exe with escaped args, a .ps1 through PowerShell -File, an npm
shim as node + cli.js; argv values are restricted to plain tokens. Errors
for a missing / dead claude are English with zh i18n.
Removed the SDK from dependencies, the lockfile, THIRD-PARTY-NOTICES and
docs. A cold install (npm pack + bun add --ignore-scripts) is ~46 MB of
node_modules, mostly node-pty; README and the registry summary say so in
en and zh, and that users install claude themselves.
Tests: a stand-in stream-json claude (streaming, approve/deny, withdrawn
ask, interrupt, model/mode switch, resume, images, commands, crash, broken
shim, missing claude); opt-in real tests against claude 2.1.316 with a
local fake Messages API.
de011d1 to
8e8f910
Compare
|
@yetone Thanks for catching the license problem. The new head is 8e8f910. The SDK is gone, including from history
Claude Code now runs through the user's own
Small items
Size: a cold install ( Tests
Could you re-run the checks and merge if it looks good? Thank you! |
Summary
magpie-agent-consoleto the registry as a page plugin.magpie-agent-console(0.1.0)Test plan
magpie plugin add magpie-agent-console(after npm publish)