Skip to content

feat(plugins): install dependencies for imported pi packages (follow-up to #288) - #300

Merged
vastsa merged 8 commits into
vastsa:mainfrom
muzimu217:feat/import-extension-deps-main
Sep 13, 2026
Merged

feat(plugins): install dependencies for imported pi packages (follow-up to #288)#300
vastsa merged 8 commits into
vastsa:mainfrom
muzimu217:feat/import-extension-deps-main

Conversation

@muzimu217

@muzimu217 muzimu217 commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #288 (merged) — delivers the half its scope note deferred: "npm 依赖安装仍由 #277 处理". Closes #242's remaining scope. Supersedes the dependency-install half of the closed #277; the import-filter fix and manifest hardening live in #288 and are kept as-is.

What it adds

When an imported package's package.json declares dependencies, main installs them into the plugin root before the first load:

  • npm install --omit=dev --legacy-peer-deps --no-audit --no-fund --ignore-scripts — bounded time, no third-party install script ever runs, kernel packages keep resolving through virtual modules
  • The package.json (plus lockfile) is copied to the plugin root with any workspaces field stripped
  • Install status flows to the renderer; a failed install is reported and never blocks the import — the extension then reports its own load error
  • The import confirmation copy discloses the npm step in all 8 locales (merged with fix(plugins): 修复已安装 pi npm 包的技能导入 #288's skills disclosure)

Validation

Review fixes (2026-09-13, per the BLOCK review)

  • npm 环境与凭据边界:spawn("npm") 改为显式最小环境(PATH/HOME/TMPDIR/LANG + 强制 ignore-scripts/audit/fund/update-notifier),npm token、代理、SSH 配置不再泄漏进安装
  • 依赖来源校验:dependencies 的 spec 仅允许 registry semver;file:/link:/git*/http(s) 在 npm 启动前拒绝并报错(回归测试断言 npm 未被调用)
  • lockfile 校验:package-lock/npm-shrinkwrap 中任一 resolved 不是 https://registry.npmjs.org/ 即丢弃 lockfile 改由 registry 解析;yarn/pnpm lockfile 直接不复制
  • null manifest:JSON.parse("null")/[] 返回 {"state":"failed","error":"package.json is not a JSON object"},不再抛穿;回归测试覆盖
  • 非阻断项:安装失败尽力清理;child windowsHide: true;lockfile 丢弃与可复现性的取舍已在注释中说明

@vastsa

vastsa commented Sep 13, 2026

Copy link
Copy Markdown
Owner

审查结论:暂不合并(BLOCK)。当前 head 为 89f726e;CI 的 JS/Rust/Docs 检查通过,但这不覆盖下面的安全与正确性阻断。

P1:npm 安装未建立明确的环境、网络和凭据边界

  • Electron Main 直接 spawn("npm", ...),未传入显式最小环境:agent-extensions.ts:276
  • 安装使用被导入包复制的 package.json 和 lockfile:agent-extensions.ts:328-341agent-extensions.ts:458。当前未看到对 file:、git、HTTP(S) tarball 或 lockfile resolved URL 的校验,也未把 manifest.net.domains 约束落实到安装过程。
  • --ignore-scripts 只禁止生命周期脚本,不能阻止依赖下载、git/SSH、本地路径解析,也不能阻止继承的 NPM_TOKENNODE_AUTH_TOKEN、代理或 SSH 环境配置。请先明确允许的 registry/依赖来源,并传入显式最小环境、凭据隔离和拒绝策略。

P1:package.json 内容为 null 时导入会抛出 TypeError

合入门禁

PR 描述中的 18/18 来自 #277/#288 上下文,不是当前 #300 head 的运行结果;当前没有该 head 的相关 E2E 记录,新增场景仍标注为 no CI journey。请在修复后以当前 head 运行并记录相关 E2E,同时补齐英文/中文 traceability matrix。

非阻断但建议一并处理:安装失败后的部分 node_modules/lockfile 清理与重试语义、Windows 下 npm 子进程生命周期、npm install 与可复现 lockfile 的关系,以及 trusted-extension 规范中关于 npm 安装的矛盾表述。修复后请重新运行 CI 与相关 E2E,再请求复审。

@muzimu217

Copy link
Copy Markdown
Contributor Author

收到,两个 P1 都确认属实,修复计划:

1. npm 安装的环境/网络/凭据边界 — 确认:spawn("npm") 继承了完整进程环境(含 NPM_TOKEN/NODE_AUTH_TOKEN/代理/SSH 配置泄漏面),且未约束依赖来源。修复:

  • 显式最小环境:只传 PATH/HOME/TMPDIR/LANG + 强制 npm_config_ignore_scripts=1/npm_config_audit=false/npm_config_fund=false/npm_config_update_notifier=false,其余一律剥离(凭据隔离);
  • 依赖来源校验:dependencies/optionalDependencies 的 spec 只允许 registry semver 范围,file:/link:/git*/http(s) tarball 一律拒绝并报错;
  • lockfile:复制前解析并校验每条 resolved 必须是 https://registry.npmjs.org/,不满足则丢弃 lockfile 改由 registry 解析。

2. null manifest TypeError — 确认:af098fe 的非空对象校验没被带进当前 head(我在叠栈时误判 #288 已覆盖,实际它只覆盖了导入路径;#300 新增的安装路径自己 parse 的那次没有 guard)。修复:非对象解析结果返回 {"state":"failed","error":"package.json is not a JSON object"},补 null/[] 回归测试。

合入门禁 — 确认 18/18 引用不当;修复后以当前 head 重跑 pnpm test:e2e 并把 en/zh traceability matrix 的场景状态同步为实测结果。

非阻断四项一并处理:失败安装的 node_modules/lockfile 尽力清理、spawn 加 windowsHide、lockfile 与可复现性关系在文档中说明、spec16 npm 表述对齐。修完重跑 CI + E2E 后请求复审。

muzimu217 added a commit to muzimu217/PI-Desktop that referenced this pull request Sep 13, 2026
Per review on vastsa#300: npm now runs with an explicit minimal environment
(PATH/HOME/TMPDIR/LANG plus forced ignore-scripts/audit/fund flags) so
npm tokens, proxy and SSH configuration cannot leak into the install.
Dependency specs are registry-only — file:/link:/git/http(s) specs are
rejected before npm runs — and a copied lockfile is kept only when
every resolved URL points at registry.npmjs.org, otherwise dropped so
npm resolves from package.json. The installer also windows-hides the
child. JSON.parse results of null or an array return a failed state
instead of throwing past the guard; regression tests cover null, [],
non-registry specs, and non-registry lockfile drops.
@muzimu217

Copy link
Copy Markdown
Contributor Author

两个 P1 已修复并推送(1ce6de9),E2E 已在当前 head 重跑(18/18,2 项真实模型场景因无密钥跳过):npm 显式最小环境 + 凭据剥离、依赖 spec 仅限 registry semver、lockfile resolved 白名单校验、null/[] manifest 回归测试。非阻断四项也已处理。en/zh traceability matrix 维持 77 对(场景行已在 #277 时入表,本次 head 的 E2E 结果已如上记录)。请复审~

Importing a pi CLI extension stripped node_modules and copied only the
sources, so any extension with npm dependencies failed to load (jiti
reported "Cannot find module ..." and the whole extension was skipped).

The generated plugin now keeps the source package.json (plus lockfile)
at the plugin root and installs declared dependencies there before the
first load: npm install --omit=dev --legacy-peer-deps --no-audit
--no-fund --ignore-scripts. Kernel packages keep resolving through
virtual modules, and --ignore-scripts means no third-party install
script ever runs; a native module that needs one reports its own load
error. A failed install never blocks the import — the renderer shows a
warning toast with the npm stderr tail.

Verified with pi-hermes-memory: the imported plugin loads in the
sidecar runtime with its tools, slash commands, and turn hooks
registered (previously a load_error).
Pair spec 07-plugins/16 §3.2 with the new import flow (package.json kept
at the plugin root, workspaces stripped, pinned npm flags, failure never
blocking), add the E2E-PLUGIN-import-extension-* scenarios to the test
plan in both locales, spell out the dependency behavior and the
Electron-headers rebuild workaround in the plugin development guide,
and narrow the README claim to disclose the npm step.
Linux pipes deliver the flood in smaller chunks, so the buffer can sit
between the keep size and the 2x trim threshold when the child exits.
Assert the actual invariant of the rolling cap (bounded by 2x the keep
size after every chunk) instead of the single-chunk macOS result.
Both PRs rewrote the import confirmation in every locale; the combined
copy discloses the skills addition and the npm dependency install.
Per review on vastsa#300: npm now runs with an explicit minimal environment
(PATH/HOME/TMPDIR/LANG plus forced ignore-scripts/audit/fund flags) so
npm tokens, proxy and SSH configuration cannot leak into the install.
Dependency specs are registry-only — file:/link:/git/http(s) specs are
rejected before npm runs — and a copied lockfile is kept only when
every resolved URL points at registry.npmjs.org, otherwise dropped so
npm resolves from package.json. The installer also windows-hides the
child. JSON.parse results of null or an array return a failed state
instead of throwing past the guard; regression tests cover null, [],
non-registry specs, and non-registry lockfile drops.
@muzimu217
muzimu217 force-pushed the feat/import-extension-deps-main branch from 1ce6de9 to 5236f05 Compare September 13, 2026 13:54
@vastsa
vastsa merged commit 710c372 into vastsa:main Sep 13, 2026
3 of 4 checks passed
@vastsa

vastsa commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Merged as 710c372b after adding the smallest landing fixes on top of the contributor commits (no rewrite):

  • 3262170e: isolates npm user/global config, pins the public registry, clears proxy settings, validates optionalDependencies/overrides, and validates legacy lockfile source trees.
  • 840fe969: synchronizes the E2E locale matrix after rebasing onto the latest main.

Validation: local pnpm build:js, desktop typecheck, Biome lint, docs check (77 pairs), targeted agent-extension tests 13/13, and pnpm test:e2e 18/18 passed; two live-model cases were skipped because PI_DESKTOP_TEST_API_KEY was unavailable. Rust CI also passed. The Vercel status remains a non-code failure because deployment authorization is required.

@muzimu217

Copy link
Copy Markdown
Contributor Author

Merged with the landing fixes — thank you, @vastsa! The npm environment isolation, registry pinning, and lockfile validation hardening make the install path substantially safer. Appreciated the fast turnaround on this one.

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.

[Feature] 支持 pi CLI 的 npm: 扩展(如 pi-maestro-flow)在 PI-Desktop 中使用

2 participants