mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
feat: enable permission-aware subagents
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* isolated-provider.e2e.test.ts — reachability guard for PR #152 (issue #151).
|
||||
*
|
||||
* PR #152 fixes isolated subagents dropping extension-registered custom providers
|
||||
* on Pi >= 0.80.8 (the `modelRegistry` → `modelRuntime` migration). agent-runner
|
||||
* forwards the parent's runtime, read off the ModelRegistry facade as
|
||||
* `ctx.modelRegistry.runtime` via `as unknown as { runtime }`.
|
||||
*
|
||||
* That forwarding is already guarded by the unit test in test/agent-runner.test.ts
|
||||
* ("passes the parent model runtime …") — but against a MOCK whose `.runtime` is
|
||||
* hand-set. The mock cannot catch the one thing that would silently break the fix:
|
||||
* `.runtime` is a `private readonly` field on the real ModelRegistry, absent from
|
||||
* the public type AND the package exports. If a future Pi renames it, makes it a
|
||||
* true #private, or moves the module, the cast quietly yields `undefined`, the fix
|
||||
* omits `modelRuntime`, and the bug returns with no failing test.
|
||||
*
|
||||
* This test closes exactly that gap and nothing else: it asserts the real facade
|
||||
* exposes a runtime-reachable `.runtime` that IS the runtime it wraps. It is not a
|
||||
* guard for the forwarding itself (that's the unit test's job) — a fuller e2e that
|
||||
* drives real `runAgent` end-to-end is tracked as a follow-up.
|
||||
*
|
||||
* VERSION GATE: `.runtime` only exists in the post-migration facade world (Pi >=
|
||||
* 0.80.8, where `ModelRuntime` is first exported). The repo's dev dependency is
|
||||
* pinned post-migration, so this runs by default — but it stays gated because CI
|
||||
* also runs the suite against the peer-range floor (see .github/workflows/ci.yml),
|
||||
* where Pi predates the migration. There we DYNAMICALLY import Pi and skip
|
||||
* cleanly; a static `import { ModelRuntime }` would be a link-time error.
|
||||
*/
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterAll, describe, expect, it } from "vitest";
|
||||
|
||||
// Dynamic, so this file LOADS on pre-migration Pi (a static `import { ModelRuntime }`
|
||||
// would be a link-time error there — 0.80.6 doesn't export it).
|
||||
const pi = (await import("@earendil-works/pi-coding-agent")) as Record<string, unknown>;
|
||||
const ModelRuntime = pi.ModelRuntime as
|
||||
| { create(opts?: Record<string, unknown>): Promise<ModelRuntimeLike> }
|
||||
| undefined;
|
||||
|
||||
// The migration is exactly "ModelRuntime now exists". Absent ⇒ pre-0.80.8 ⇒ there
|
||||
// is no `.runtime` facade to guard.
|
||||
const MIGRATED = typeof ModelRuntime?.create === "function";
|
||||
const RT = ModelRuntime as { create(opts?: Record<string, unknown>): Promise<ModelRuntimeLike> };
|
||||
|
||||
// The one method the reach scenario needs; `.runtime` itself is private (reached below).
|
||||
interface ModelRuntimeLike {
|
||||
registerProvider(id: string, config: Record<string, unknown>): void;
|
||||
}
|
||||
|
||||
const tmpDirs: string[] = [];
|
||||
afterAll(() => {
|
||||
for (const d of tmpDirs.splice(0)) rmSync(d, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe.skipIf(!MIGRATED)("PR #152 reach: real ModelRegistry exposes .runtime (Pi >= 0.80.8)", () => {
|
||||
it("ctx.modelRegistry.runtime is reachable and IS the runtime it wraps", async () => {
|
||||
// A real, configured runtime — as an extension leaves it after registerProvider.
|
||||
const dir = mkdtempSync(join(tmpdir(), "iso-prov-"));
|
||||
tmpDirs.push(dir);
|
||||
const runtime = await RT.create({
|
||||
authPath: join(dir, "auth.json"),
|
||||
modelsPath: join(dir, "models.json"),
|
||||
allowModelNetwork: false,
|
||||
});
|
||||
|
||||
// `.runtime` is private and not in the package exports — reach the compiled
|
||||
// class by file path, exactly the field the patch's cast depends on. If Pi
|
||||
// moves/renames/#privates it, THIS line fails loudly instead of the fix
|
||||
// silently no-op'ing back to the #151 bug.
|
||||
const indexUrl = import.meta.resolve("@earendil-works/pi-coding-agent");
|
||||
const mrUrl = indexUrl.replace(/index\.js$/, "core/model-registry.js");
|
||||
const { ModelRegistry } = (await import(mrUrl)) as {
|
||||
ModelRegistry: new (rt: ModelRuntimeLike) => { runtime?: unknown };
|
||||
};
|
||||
|
||||
const facade = new ModelRegistry(runtime);
|
||||
// This is the exact expression agent-runner reads (`ctx.modelRegistry.runtime`).
|
||||
expect((facade as { runtime?: unknown }).runtime).toBe(runtime);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user