From b689960e5b7c67df7c31e683764cc4a17398aa5d Mon Sep 17 00:00:00 2001 From: xjx <2869418079@qq.com> Date: Thu, 17 Sep 2026 20:53:09 +0800 Subject: [PATCH] fix(plugins): generate explicit CommonJS import wrappers --- .../desktop/electron/main/agent-extensions.ts | 6 +-- apps/desktop/test/agent-extensions.test.mjs | 2 +- .../imported-package-skills-runtime.test.mjs | 47 ++++++++++++++++++- docs/spec/06-delivery/04-e2e-test-plan.md | 25 +++++++++- docs/spec/07-plugins/16-trusted-extensions.md | 10 +++- .../spec/06-delivery/04-e2e-test-plan.md | 19 +++++++- .../spec/07-plugins/16-trusted-extensions.md | 7 ++- 7 files changed, 104 insertions(+), 12 deletions(-) diff --git a/apps/desktop/electron/main/agent-extensions.ts b/apps/desktop/electron/main/agent-extensions.ts index 606cfafbb..838cdb683 100644 --- a/apps/desktop/electron/main/agent-extensions.ts +++ b/apps/desktop/electron/main/agent-extensions.ts @@ -289,7 +289,7 @@ function slugFor(path: string): string { /** * Build a plugin directory from a pi extension file or directory (spec §3): * copies the source under `src/`, writes a manifest that declares the entry - * files as `contributes.agentExtensions`, and a no-op `main.js`. A directory + * files as `contributes.agentExtensions`, and a CommonJS no-op `main.cjs`. A directory * that ships a `package.json` also gets it (plus its lockfile) at the plugin * root so {@link installExtensionDependencies} can resolve its dependencies * there; `node_modules` itself is never copied — it is reinstalled. @@ -373,7 +373,7 @@ export function generateImportedExtensionPlugin( name: slug, version: "0.0.0", description: `Imported pi extension from ${resolved}`, - main: "main.js", + main: "main.cjs", permissions: [...(entries.length ? ["agent.extension"] : []), ...(skills.length ? ["agent.prompt.inject"] : [])], contributes: { ...(entries.length ? { agentExtensions: entries } : {}), @@ -386,7 +386,7 @@ export function generateImportedExtensionPlugin( }; writeFileSync(join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8"); writeFileSync( - join(dir, "main.js"), + join(dir, "main.cjs"), "// Generated by PI-Desktop: declarative skills and/or agent extensions.\nmodule.exports = {};\n", "utf8", ); diff --git a/apps/desktop/test/agent-extensions.test.mjs b/apps/desktop/test/agent-extensions.test.mjs index ea3ad9239..aa152387c 100644 --- a/apps/desktop/test/agent-extensions.test.mjs +++ b/apps/desktop/test/agent-extensions.test.mjs @@ -270,7 +270,7 @@ test("importing a pi extension directory or file generates a plugin holding agen assert.deepEqual(manifest.permissions, ["agent.extension"]); assert.deepEqual(manifest.contributes, { agentExtensions: ["src/index.ts"] }); assert.ok(existsSync(join(dir.path, "src", "lib", "util.ts")), "the whole directory is copied"); - assert.match(readFileSync(join(dir.path, "main.js"), "utf8"), /module\.exports = \{\}/); + assert.match(readFileSync(join(dir.path, "main.cjs"), "utf8"), /module\.exports = \{\}/); const file = join(root, "solo.ts"); writeFileSync(file, "export default function () {}\n"); diff --git a/apps/desktop/test/imported-package-skills-runtime.test.mjs b/apps/desktop/test/imported-package-skills-runtime.test.mjs index c850ca1af..10b2fa111 100644 --- a/apps/desktop/test/imported-package-skills-runtime.test.mjs +++ b/apps/desktop/test/imported-package-skills-runtime.test.mjs @@ -6,6 +6,7 @@ import { mkdtempSync, readFileSync, realpathSync, + renameSync, rmSync, writeFileSync, } from "node:fs"; @@ -20,7 +21,7 @@ const { generateImportedExtensionPlugin } = await import("../electron/main/agent const { PluginRuntime } = await import("../electron/main/plugin-runtime.ts"); const hostEntry = fileURLToPath(new URL("../electron/main/plugin-host-process.mjs", import.meta.url)); -function createHarness(t, { skillsOnly = false } = {}) { +function createHarness(t, { skillsOnly = false, packageType } = {}) { const root = mkdtempSync(join(tmpdir(), "pi-imported-package-skills-")); // The selected package itself lives under node_modules, as an npm global // installation would. Its own nested dependency tree must still be excluded. @@ -46,6 +47,7 @@ function createHarness(t, { skillsOnly = false } = {}) { write("package.json", JSON.stringify({ name: "@fixture/package-skills", version: "1.0.0", + ...(packageType ? { type: packageType } : {}), pi: { ...(!skillsOnly ? { extensions: ["index.ts"] } : {}), skills: ["skills/direct.md", "skills/release", "skills/catalog"], @@ -65,7 +67,7 @@ function createHarness(t, { skillsOnly = false } = {}) { hostEntry, spawnProcess: ({ entry }) => { // Execute only the repository's real plugin host and the importer's - // generated no-op main.js. Fixture extension modules are only catalogued. + // generated no-op main.cjs. Fixture extension modules are only catalogued. const child = fork(entry, [], { stdio: ["ignore", "pipe", "pipe", "ipc"] }); return { postMessage: (message) => { if (child.connected) child.send(message); }, @@ -152,3 +154,44 @@ test("a skill-only pi package loads in the plugin runtime without requiring exec assert.ok(manifest.permissions.includes("agent.prompt.inject")); assert.equal(manifest.permissions.includes("agent.extension"), false); }); + +for (const packageType of ["module", "commonjs", undefined]) { + test(`imported ${packageType ?? "untyped"} packages initialize in the real plugin host`, async (t) => { + const { source, importRoot, runtime, bodies, paths } = createHarness(t, { packageType }); + const imported = generateImportedExtensionPlugin(source, importRoot); + await runtime.loadFromPath(imported.path); + assertSkillCatalog(runtime, imported, bodies, paths); + assert.equal(runtime.getAgentExtensions().length, 1); + const manifest = runtime.getLoaded(imported.id).manifest; + assert.equal(manifest.main, "main.cjs"); + assert.ok(existsSync(join(imported.path, manifest.main))); + assert.equal(existsSync(join(imported.path, "main.js")), false); + const original = readFileSync(join(source, "package.json"), "utf8"); + for (const path of ["package.json", "src/package.json"]) { + assert.equal(readFileSync(join(imported.path, path), "utf8"), original); + } + }); +} + +test("re-importing an older ESM package creates a loadable copy without rewriting the existing plugin", async (t) => { + const { source, importRoot, runtime, bodies, paths } = createHarness(t, { packageType: "module" }); + const previous = generateImportedExtensionPlugin(source, importRoot); + // Model the on-disk layout generated before explicit CommonJS wrappers. + const oldManifest = JSON.parse(readFileSync(join(previous.path, "manifest.json"), "utf8")); + oldManifest.main = "main.js"; + writeFileSync(join(previous.path, "manifest.json"), JSON.stringify(oldManifest)); + renameSync(join(previous.path, "main.cjs"), join(previous.path, "main.js")); + const oldFiles = new Map(["manifest.json", "main.js", "package.json", "src/package.json"] + .map((path) => [path, readFileSync(join(previous.path, path), "utf8")])); + + const imported = generateImportedExtensionPlugin(source, importRoot); + assert.notEqual(imported.id, previous.id); + assert.notEqual(imported.path, previous.path); + for (const [path, contents] of oldFiles) { + assert.equal(readFileSync(join(previous.path, path), "utf8"), contents); + } + assert.equal(existsSync(join(previous.path, "main.cjs")), false); + await runtime.loadFromPath(imported.path); + assertSkillCatalog(runtime, imported, bodies, paths); + assert.equal(runtime.getAgentExtensions().length, 1); +}); diff --git a/docs/spec/06-delivery/04-e2e-test-plan.md b/docs/spec/06-delivery/04-e2e-test-plan.md index 8c6e02b97..cec2c67ad 100644 --- a/docs/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/spec/06-delivery/04-e2e-test-plan.md @@ -7465,7 +7465,7 @@ identify the platform validation still needed. | E — Tools & permissions | E2E-008a, E2E-014, E2E-015, E2E-016, E2E-017, E2E-018, E2E-019, E2E-024I, E2E-024K, E2E-040, E2E-049, E2E-074, E2E-093, E2E-097, E2E-099, E2E-100, E2E-101, E2E-102, E2E-102d, E2E-102e, E2E-102g, E2E-103, E2E-105, E2E-106, E2E-107, E2E-111, E2E-112, E2E-113, E2E-114, E2E-115, E2E-116, E2E-119, E2E-121, E2E-122, E2E-142, E2E-145, E2E-147, E2E-155, E2E-158, E2E-166, E2E-181, E2E-PLUGIN-imported-pi-package-skills | | F — Persistence | E2E-020, E2E-021, E2E-021a, E2E-036, E2E-037, E2E-038, E2E-040, E2E-042, E2E-047, E2E-048, E2E-051, E2E-054, E2E-056, E2E-061, E2E-062, E2E-064, E2E-066, E2E-068, E2E-071, E2E-072, E2E-073, E2E-082, E2E-084, E2E-096, E2E-098, E2E-102, E2E-102b, E2E-102c, E2E-102d, E2E-102g, E2E-102i, E2E-103, E2E-AGENTS-001, E2E-061a, E2E-073a, E2E-104, E2E-106, E2E-107, E2E-108, E2E-109, E2E-110, E2E-112, E2E-118, E2E-119, E2E-120, E2E-121, E2E-123, E2E-142, E2E-146, E2E-146a, E2E-148, E2E-151, E2E-158, E2E-160, E2E-168, E2E-171, E2E-177, E2E-178, E2E-183, E2E-186, E2E-005J, E2E-PLUGIN-session-orchestrator-real-workers | | F — Persistence (project ordering) | E2E-251 | -| G — Plugins | E2E-022, E2E-022A, E2E-022B, E2E-022C, E2E-023, E2E-024, E2E-024B, E2E-024C, E2E-024D, E2E-024E, E2E-024W, E2E-024F, E2E-024G, E2E-024H, E2E-024I, E2E-024J, E2E-024K, E2E-024L, E2E-024M, E2E-024N, E2E-024O, E2E-024P, E2E-025, E2E-026, E2E-105, E2E-117, E2E-120, E2E-122, E2E-123, E2E-024Q, E2E-148, E2E-152, E2E-153, E2E-PLUGIN-imported-pi-package-skills, E2E-PLUGIN-import-extension-installs-dependencies, E2E-PLUGIN-import-extension-reports-missing-dependency, E2E-PLUGIN-global-shortcut-owns-only-its-own-command, E2E-PLUGIN-permission-gate-for-real-time-capabilities, E2E-PLUGIN-background-audio-and-realtime-connection, E2E-PLUGIN-fs-root-follows-the-calling-session | +| G — Plugins | E2E-022, E2E-022A, E2E-022B, E2E-022C, E2E-023, E2E-024, E2E-024B, E2E-024C, E2E-024D, E2E-024E, E2E-024W, E2E-024F, E2E-024G, E2E-024H, E2E-024I, E2E-024J, E2E-024K, E2E-024L, E2E-024M, E2E-024N, E2E-024O, E2E-024P, E2E-025, E2E-026, E2E-105, E2E-117, E2E-120, E2E-122, E2E-123, E2E-024Q, E2E-148, E2E-152, E2E-153, E2E-PLUGIN-imported-pi-package-skills, E2E-PLUGIN-imported-pi-package-wrapper, E2E-PLUGIN-import-extension-installs-dependencies, E2E-PLUGIN-import-extension-reports-missing-dependency, E2E-PLUGIN-global-shortcut-owns-only-its-own-command, E2E-PLUGIN-permission-gate-for-real-time-capabilities, E2E-PLUGIN-background-audio-and-realtime-connection, E2E-PLUGIN-fs-root-follows-the-calling-session | | H — Diagnostics | E2E-027, E2E-031, E2E-034, E2E-042, E2E-096, E2E-098, E2E-104, E2E-107, E2E-108, E2E-109, E2E-110, E2E-113, E2E-115, E2E-116, E2E-118, E2E-121, E2E-146, E2E-146a, E2E-155, E2E-159, E2E-176, E2E-194, E2E-195 | | Security | E2E-028, E2E-029, E2E-030, E2E-024J, E2E-024K, E2E-024M, E2E-049, E2E-068, E2E-086, E2E-102c, E2E-102d, E2E-102e, E2E-105, E2E-106, E2E-107, E2E-108, E2E-109, E2E-110, E2E-112, E2E-113, E2E-115, E2E-116, E2E-117, E2E-119, E2E-121, E2E-122, E2E-123, E2E-142, E2E-148, E2E-151, E2E-153, E2E-158, E2E-187, E2E-196c, E2E-196b, E2E-196, E2E-PLUGIN-fs-root-follows-the-calling-session | | Quality | E2E-032, E2E-033, E2E-039, E2E-043, E2E-044, E2E-045, E2E-046, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-050, E2E-053, E2E-055, E2E-056, E2E-057, E2E-058, E2E-059, E2E-060, E2E-061, E2E-062, E2E-063, E2E-064, E2E-065, E2E-066, E2E-067, E2E-068, E2E-069, E2E-070, E2E-071, E2E-072, E2E-073, E2E-074, E2E-075, E2E-076, E2E-077, E2E-078, E2E-079, E2E-080, E2E-081, E2E-082, E2E-083, E2E-084, E2E-085, E2E-086, E2E-092, E2E-093, E2E-094, E2E-095, E2E-096, E2E-097, E2E-098, E2E-099, E2E-100, E2E-101, E2E-102, E2E-102a, E2E-102b, E2E-102c, E2E-102d, E2E-102e, E2E-103, E2E-AGENTS-001, E2E-021a, E2E-024N, E2E-059a, E2E-060b, E2E-060c, E2E-061a, E2E-073a, E2E-111, E2E-114, E2E-117, E2E-118, E2E-119, E2E-120, E2E-122, E2E-123, E2E-142, E2E-143, E2E-144, E2E-145, E2E-146, E2E-147, E2E-148, E2E-150, E2E-151, E2E-153, E2E-155, E2E-158, E2E-159, E2E-160, E2E-161, E2E-162, E2E-163, E2E-168, E2E-172, E2E-173, E2E-174, E2E-011g, E2E-176, E2E-177, E2E-178, E2E-179, E2E-180, E2E-181, E2E-182, E2E-183, E2E-186, E2E-187, E2E-194, E2E-195, E2E-196a, E2E-196b, E2E-196c, E2E-198, E2E-199, E2E-200, E2E-196, E2E-201, E2E-204, E2E-202, E2E-203, E2E-205, E2E-206, E2E-207, E2E-208, E2E-209, E2E-210, E2E-218, E2E-219, E2E-250, E2E-252, E2E-102i, E2E-SUBAGENT-settlement-updates-before-parent-poll, E2E-PLUGIN-imported-pi-package-skills, E2E-PLUGIN-fs-root-follows-the-calling-session, E2E-SUBAGENT-resume-a-settled-delegation | @@ -11673,6 +11673,29 @@ plugin-form fixtures in an isolated temporary directory at runtime. - **Milestone**: Post-MVP (R7 v1) - **Status**: Partially automated (`pnpm test:e2e:trusted-extensions`); the headless journey covers plugin discovery/projection, project scope, load state, and diagnostics, while native picker import and explicit enablement remain renderer/platform validation. +#### E2E-PLUGIN-imported-pi-package-wrapper: Imported package module types preserve plugin initialization + +- **Preconditions**: Isolated local Pi packages declare `type: module`, + `type: commonjs`, or no `type`; each has an extension and skill contributions. + A fourth fixture represents an older imported ESM package with the generated + CommonJS `main.js` and matching manifest. +- **Steps**: Run `node --test apps/desktop/test/imported-package-skills-runtime.test.mjs`. + Generate each plugin through the production importer, load it through + `PluginRuntime` and the real child-process plugin host, read its skill catalog + and bodies, and inspect its declared extension. Re-import the older fixture. +- **Expected**: Each new manifest points to an existing `main.cjs`; initialization + succeeds for all three package types. Source and both copied `package.json` + files retain the same bytes. The repeated import has a distinct path/id and + loads successfully; the older manifest, wrapper, and package files remain + unchanged. There is no automatic migration of older imported plugins. +- **Specs linked**: `07-plugins/16-trusted-extensions.md` §3.2; ADR 0215. +- **Acceptance**: Quality +- **Status**: Automated import-to-plugin-host fixture. Run against the committed + task candidate after incorporating the latest `origin/main`; record the tested + candidate, base, result, and environment in delivery evidence. Native picker, + npm dependency installation, Windows execution, and third-party extension + execution during a provider turn are not covered by this fixture. + #### E2E-PLUGIN-imported-pi-package-skills: Explicit package import exposes skills through plugin grants - **Preconditions**: A local fixture package under an npm-style diff --git a/docs/spec/07-plugins/16-trusted-extensions.md b/docs/spec/07-plugins/16-trusted-extensions.md index b1d83bcc1..712ad4e83 100644 --- a/docs/spec/07-plugins/16-trusted-extensions.md +++ b/docs/spec/07-plugins/16-trusted-extensions.md @@ -78,11 +78,17 @@ manifest that lists entries without the permission is invalid Plugins → "Import pi extension" opens a native picker (main owns the path, D344) for an explicit local file or directory. Main copies the selected source under `/plugins/imported//src/`, writes a generated -no-op `main.js` and a manifest with id `imported.` (a unique suffix is +CommonJS no-op `main.cjs` and a manifest with id `imported.` (a unique suffix is added for repeated imports), and registers the directory through the existing local-plugin flow. The confirmation before the picker remains the trust decision; the generated manifest declares the permissions needed by its actual -contributions. +contributions. The manifest's `main` points to `main.cjs` regardless of the +source package's `type`; both copied package declarations retain their module +semantics. Existing imported directories are not rewritten on upgrade. To +repair an older import whose generated `main.js` fails under `type: module`, +remove that failed imported plugin and import its source again. Re-importing +without removal creates a separate plugin with a unique suffix and leaves the +old copy unchanged; it does not migrate its grants or activation scope. For extension files and packages without `pi.skills`, entry discovery keeps the existing `pi-coding-agent` rules: `package.json` `pi.extensions`, otherwise diff --git a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md index d0de4f30f..7230bdcbb 100644 --- a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md @@ -5010,7 +5010,7 @@ IPC 请求无法关闭。 | E——工具和权限 | E2E-008a、E2E-014、E2E-015、E2E-016、E2E-017、E2E-018、E2E-019、E2E-024I、E2E-024K、E2E-040、E2E-049、E2E-074、E2E-093、E2E-097、 E2E-099、E2E-100、E2E-101、E2E-102、E2E-103、E2E-105、E2E-106、E2E-107、E2E-111、E2E-112、E2E-113、E2E-114、E2E-115、E2E-116、 E2E-119、E2E-121、E2E-122、E2E-123、E2E-142、E2E-145、E2E-147、E2E-PLUGIN-imported-pi-package-skills、E2E-166 | | F——坚持 | E2E-020、E2E-021、E2E-036、E2E-037、E2E-038、E2E-040、E2E-042、E2E-047、E2E-048、E2E-051、E2E-054、E2E-056、E2E-061、E2E-062、 E2E-064、E2E-066、E2E-068、E2E-071、E2E-072、E2E-073、E2E-082、E2E-084、E2E-096、E2E-098、E2E-102、E2E-102b、E2E-103、E2E-代理-001、 E2E-061a、E2E-073a、E2E-104、E2E-106、E2E-107、E2E-108、E2E-109、E2E-110、E2E-112、E2E-118、E2E-119、E2E-120、E2E-121、E2E-123、E2E-142、E2E-146、E2E-148、E2E-151、E2E-171、E2E-005J | | F——持久化(项目排序) | E2E-253 | -| G——插件 | E2E-022、E2E-022A、E2E-022B、E2E-022C、E2E-023、E2E-024、E2E-024B、E2E-024C、E2E-024D、E2E-024E、E2E-024W、E2E-024F、E2E-024G、E2E-024H、 E2E-024I、E2E-024J、E2E-024K、E2E-024L、E2E-024M、E2E-024N、E2E-024O、E2E-024P、E2E-025、E2E-026、E2E-105、E2E-117、E2E-120、E2E-122、E2E-123、E2E-148、E2E-153、E2E-PLUGIN-imported-pi-package-skills、E2E-PLUGIN-import-extension-installs-dependencies、E2E-PLUGIN-import-extension-reports-missing-dependency、E2E-PLUGIN-global-shortcut-owns-only-its-own-command、E2E-PLUGIN-permission-gate-for-real-time-capabilities、E2E-PLUGIN-background-audio-and-realtime-connection | +| G——插件 | E2E-022、E2E-022A、E2E-022B、E2E-022C、E2E-023、E2E-024、E2E-024B、E2E-024C、E2E-024D、E2E-024E、E2E-024W、E2E-024F、E2E-024G、E2E-024H、 E2E-024I、E2E-024J、E2E-024K、E2E-024L、E2E-024M、E2E-024N、E2E-024O、E2E-024P、E2E-025、E2E-026、E2E-105、E2E-117、E2E-120、E2E-122、E2E-123、E2E-148、E2E-153、E2E-PLUGIN-imported-pi-package-skills、E2E-PLUGIN-imported-pi-package-wrapper、E2E-PLUGIN-import-extension-installs-dependencies、E2E-PLUGIN-import-extension-reports-missing-dependency、E2E-PLUGIN-global-shortcut-owns-only-its-own-command、E2E-PLUGIN-permission-gate-for-real-time-capabilities、E2E-PLUGIN-background-audio-and-realtime-connection | | H——诊断 | E2E-027、E2E-031、E2E-034、E2E-042、E2E-096、E2E-098、E2E-104、E2E-107、E2E-108、E2E-109、E2E-110、E2E-113、E2E-115、E2E-116、 E2E-118、E2E-121、E2E-146、E2E-194、E2E-195 | | 安全性 | E2E-028、E2E-029、E2E-030、E2E-024J、E2E-024K、E2E-024M、E2E-049、E2E-068、E2E-086、E2E-105、E2E-106、E2E-107、E2E-108、E2E-109、 E2E-110、E2E-112、E2E-113、E2E-115、E2E-116、E2E-117、E2E-119、E2E-121、E2E-122、E2E-123、E2E-142、E2E-148、E2E-151、E2E-153 | | 品质 | E2E-032、E2E-033、E2E-039、E2E-043、E2E-044、E2E-045、E2E-046、E2E-047、E2E-048、E2E-048A、E2E-049、E2E-050、E2E-053、E2E-055、 E2E-056、E2E-057、E2E-058、E2E-059、E2E-060、E2E-061、E2E-062、E2E-063、E2E-064、E2E-065、E2E-066、E2E-067、E2E-068、E2E-069、 E2E-070、E2E-071、E2E-072、E2E-073、E2E-074、E2E-075、E2E-076、E2E-077、E2E-078、E2E-079、E2E-080、E2E-081、E2E-082、E2E-083、 E2E-084、E2E-085、E2E-086、E2E-092、E2E-093、E2E-094、E2E-095、E2E-096、E2E-097、E2E-098、E2E-099、E2E-100、E2E-101、E2E-102、 E2E-102a、E2E-102b、E2E-103、E2E-AGENTS-001、E2E-024N、E2E-024O、E2E-059a、E2E-060b、E2E-060c、E2E-060d、E2E-061a、E2E-073a、E2E-111、 E2E-114、E2E-117、E2E-118、E2E-119、E2E-120、E2E-122、E2E-123、E2E-142、E2E-143、E2E-144、E2E-145、E2E-146、E2E-147、E2E-148、E2E-150、E2E-151、E2E-153、E2E-194、E2E-195、E2E-199、E2E-200、E2E-201、E2E-202、E2E-203、E2E-204、E2E-209、E2E-210、E2E-250、E2E-PLUGIN-imported-pi-package-skills、E2E-SUBAGENT-resume-a-settled-delegation | @@ -6936,6 +6936,23 @@ runner 会在运行时的隔离临时目录中生成六个插件形态 fixture - **里程碑**:MVP 后(R7 v1) - **状态**:部分自动化(`pnpm test:e2e:trusted-extensions`);无头旅程覆盖插件发现/投影、项目范围、加载状态和诊断;原生选择器导入与显式启用仍需渲染器/平台验证 +#### E2E-PLUGIN-imported-pi-package-wrapper:导入包的模块类型不妨碍插件初始化 + +- **前置条件**:隔离的本地 Pi 包分别声明 `type: module`、`type: commonjs` 或不声明 + `type`,各自包含扩展和技能贡献。另一个夹具模拟旧版导入的 ESM 包,包含生成的 + CommonJS `main.js` 及指向它的 manifest。 +- **步骤**:运行 `node --test apps/desktop/test/imported-package-skills-runtime.test.mjs`。 + 使用生产导入器生成各个插件,通过 `PluginRuntime` 与真实子进程插件宿主加载, + 读取技能目录与正文,并检查声明的扩展。对旧版夹具重新导入。 +- **预期**:每份新 manifest 都指向实际存在的 `main.cjs`,三类包均初始化成功。 + 源包及两份复制的 `package.json` 字节保持一致。重新导入得到不同的路径和 id 并成功 + 加载;旧 manifest、包装器和包文件保持原样。不会自动迁移既有导入插件。 +- **关联规范**:`07-plugins/16-trusted-extensions.md` §3.2;ADR 0215。 +- **验收**:质量 +- **状态**:已实现导入到插件宿主的自动化夹具。合入最新 `origin/main` 后,在已提交的 + 任务候选上执行,并在交付证据中记录候选、基线、结果和环境。本夹具不覆盖原生选择器、 + npm 依赖安装、Windows 运行或模型回合中的第三方扩展执行。 + #### E2E-PLUGIN-imported-pi-package-skills:显式导入包后按插件权限提供技能 - **前提条件**:一个本地夹具包位于 npm 风格的 diff --git a/docs/zh-CN/spec/07-plugins/16-trusted-extensions.md b/docs/zh-CN/spec/07-plugins/16-trusted-extensions.md index 662fdc8a9..e5747ce1f 100644 --- a/docs/zh-CN/spec/07-plugins/16-trusted-extensions.md +++ b/docs/zh-CN/spec/07-plugins/16-trusted-extensions.md @@ -63,9 +63,12 @@ agent 循环上注册工具、命令和事件处理器。`ExtensionAPI` 契约 插件页 →“导入 pi 扩展”打开原生选择器(main 拥有路径,D344),由用户明确选择本地 文件或目录。main 把所选源码复制到 `/plugins/imported//src/`,生成空操作 -`main.js` 和 id 为 `imported.` 的 manifest(重复导入时追加唯一后缀),再通过 +CommonJS `main.cjs` 和 id 为 `imported.` 的 manifest(重复导入时追加唯一后缀),再通过 既有本地插件流程注册。选择器之前的确认仍是信任决定;生成的 manifest 只声明实际贡献 -所需的权限。 +所需的权限。无论源包的 `type` 为何,manifest 的 `main` 都指向 `main.cjs`; +两份复制的包声明保留原有模块语义。升级不会重写既有导入目录。若旧导入的生成文件 +`main.js` 因 `type: module` 而加载失败,删除该失败插件后重新导入源包即可。 +不删除就重新导入会创建带唯一后缀的独立插件,旧副本保持原样;不会迁移其授权或激活范围。 扩展文件及未声明 `pi.skills` 的包保持既有 `pi-coding-agent` 入口发现规则:先取 `package.json` 的 `pi.extensions`,否则取 `index.ts` / `index.js`,再否则取一层深度内