diff --git a/.github/workflows/release-loop.yml b/.github/workflows/release-loop.yml index 65effe6..50e4036 100644 --- a/.github/workflows/release-loop.yml +++ b/.github/workflows/release-loop.yml @@ -74,9 +74,10 @@ jobs: npm install "$RUNNER_TEMP"/onkernel-loop-*.tgz cat > smoke.mjs <<'NODE' import { compileLoopToolCatalog, formatBrowserActResult, loop } from "@onkernel/loop"; - import { attach, getLoopModel, NodeExecutionEnv } from "@onkernel/loop/pi"; + import * as loopPi from "@onkernel/loop/pi"; - for (const [name, value] of Object.entries({ compileLoopToolCatalog, formatBrowserActResult, attach, getLoopModel, NodeExecutionEnv })) { + const { attach, createLoopModels, getLoopModel } = loopPi; + for (const [name, value] of Object.entries({ compileLoopToolCatalog, formatBrowserActResult, attach, createLoopModels, getLoopModel })) { if (typeof value !== "function") { throw new Error(`expected ${name} to be a function, got ${typeof value}`); } @@ -84,6 +85,9 @@ jobs: if (typeof loop !== "object" || loop === null) { throw new Error(`expected loop to be an object, got ${typeof loop}`); } + if ("Agent" in loopPi || "NodeExecutionEnv" in loopPi) { + throw new Error("expected @onkernel/loop/pi not to re-export pi-agent-core"); + } console.log("ESM import smoke OK"); NODE node smoke.mjs diff --git a/README.md b/README.md index efe1e9a..a705890 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,9 @@ plain pi objects. Nothing here is a Loop type you have to learn: ```ts import Kernel from "@onkernel/sdk"; +import { Agent } from "@earendil-works/pi-agent-core"; import { loop } from "@onkernel/loop"; -import { Agent, attach } from "@onkernel/loop/pi"; +import { attach } from "@onkernel/loop/pi"; const client = new Kernel({ apiKey: process.env.KERNEL_API_KEY! }); const browser = await client.browsers.create({ stealth: true }); diff --git a/package-lock.json b/package-lock.json index 963a95e..6ffb415 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4008,6 +4008,7 @@ "node": ">=22.19.0" }, "peerDependencies": { + "@earendil-works/pi-agent-core": "0.83.0", "@earendil-works/pi-coding-agent": "*" }, "peerDependenciesMeta": { diff --git a/packages/loop/README.md b/packages/loop/README.md index a6e203a..7758482 100644 --- a/packages/loop/README.md +++ b/packages/loop/README.md @@ -17,7 +17,7 @@ extension described under [pi extension](#pi-extension). ## Install ```bash -npm install @onkernel/loop @onkernel/sdk +npm install @onkernel/loop @onkernel/sdk @earendil-works/pi-agent-core ``` Requires Node 22.19 or newer, `KERNEL_API_KEY` for browser execution, and the @@ -31,8 +31,9 @@ you construct whatever pi agent you want with them. There is no agent class here ```ts import Kernel from "@onkernel/sdk"; +import { Agent } from "@earendil-works/pi-agent-core"; import { loop } from "@onkernel/loop"; -import { Agent, attach } from "@onkernel/loop/pi"; +import { attach } from "@onkernel/loop/pi"; const client = new Kernel({ apiKey: process.env.KERNEL_API_KEY! }); const browser = await client.browsers.create({ stealth: true }); @@ -72,8 +73,9 @@ package owns that are pi event handlers rather than constructor options, and points the handle's `models` at this catalog: ```ts +import { AgentHarness, InMemorySessionRepo } from "@earendil-works/pi-agent-core"; import { loop } from "@onkernel/loop"; -import { AgentHarness, attach, InMemorySessionRepo } from "@onkernel/loop/pi"; +import { attach } from "@onkernel/loop/pi"; const session = await new InMemorySessionRepo().create(); const kb = attach({ client, browser }); @@ -105,8 +107,9 @@ the new one. Changing the model or the tool list compiles a new pair; nothing mutates in place, and one shared execution-resource pool survives every change, so browser refs, tabs, connections, and translator state are not reset. -`@onkernel/loop/pi` re-exports pi-agent-core's session, skill, prompt-template, -compaction, and execution-environment primitives used with the harness. +`@onkernel/loop/pi` does not re-export pi: install `@earendil-works/pi-agent-core` +and import its session, skill, prompt-template, compaction, and +execution-environment primitives directly, as these examples do. ### Tool context @@ -116,15 +119,10 @@ and pi delivers the exact object (or the result of a zero-argument provider) to every tool call: ```ts +import { AgentHarness, createBashTool, createReadTool, type ExecutionToolContext } from "@earendil-works/pi-agent-core"; +import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node"; import { loop } from "@onkernel/loop"; -import { - AgentHarness, - attach, - NodeExecutionEnv, - createBashTool, - createReadTool, - type ExecutionToolContext, -} from "@onkernel/loop/pi"; +import { attach } from "@onkernel/loop/pi"; const compiled = kb.compile({ model: "openai:gpt-5.6-sol", diff --git a/packages/loop/examples/agent-openai-smoke.ts b/packages/loop/examples/agent-openai-smoke.ts index 69c0ffe..352a302 100644 --- a/packages/loop/examples/agent-openai-smoke.ts +++ b/packages/loop/examples/agent-openai-smoke.ts @@ -1,6 +1,7 @@ import Kernel from "@onkernel/sdk"; import { loop } from "../src/index"; -import { Agent, attach, type LoopModelRef, requireLoopEnvApiKeyForModel } from "../src/pi/index"; +import { Agent } from "@earendil-works/pi-agent-core"; +import { attach, type LoopModelRef, requireLoopEnvApiKeyForModel } from "../src/pi/index"; import { logAgentEvent, logAssistant } from "./shared/logging"; import { SCENARIOS } from "./shared/scenarios"; diff --git a/packages/loop/examples/agent-provider-matrix.ts b/packages/loop/examples/agent-provider-matrix.ts index f5fb018..f86f45f 100644 --- a/packages/loop/examples/agent-provider-matrix.ts +++ b/packages/loop/examples/agent-provider-matrix.ts @@ -1,5 +1,6 @@ import Kernel from "@onkernel/sdk"; -import { Agent, attach, type LoopModelRef, requireLoopEnvApiKeyForModel } from "../src/pi/index"; +import { Agent } from "@earendil-works/pi-agent-core"; +import { attach, type LoopModelRef, requireLoopEnvApiKeyForModel } from "../src/pi/index"; import { logAgentEvent, logAssistant } from "./shared/logging"; import { SCENARIOS } from "./shared/scenarios"; import { toolsForModel } from "./shared/tools"; diff --git a/packages/loop/examples/anthropic-native-smoke.ts b/packages/loop/examples/anthropic-native-smoke.ts index ef7c6a8..eec7195 100644 --- a/packages/loop/examples/anthropic-native-smoke.ts +++ b/packages/loop/examples/anthropic-native-smoke.ts @@ -5,7 +5,8 @@ // CONFIG selects the requested catalog; nothing is inferred or appended. import Kernel from "@onkernel/sdk"; import { loop, type LoopAgentTool } from "../src/index"; -import { Agent, attach, type LoopModelRef, requireLoopEnvApiKeyForModel } from "../src/pi/index"; +import { Agent } from "@earendil-works/pi-agent-core"; +import { attach, type LoopModelRef, requireLoopEnvApiKeyForModel } from "../src/pi/index"; import { logAgentEvent, logAssistant } from "./shared/logging"; const modelRef = (process.env.MODEL_REF as LoopModelRef | undefined) ?? "anthropic:claude-opus-5"; diff --git a/packages/loop/examples/harness-openai-smoke.ts b/packages/loop/examples/harness-openai-smoke.ts index 9eb7ddd..59fe14c 100644 --- a/packages/loop/examples/harness-openai-smoke.ts +++ b/packages/loop/examples/harness-openai-smoke.ts @@ -1,12 +1,7 @@ import Kernel from "@onkernel/sdk"; import { loop } from "../src/index"; -import { - AgentHarness, - attach, - InMemorySessionRepo, - type LoopModelRef, - requireLoopEnvApiKeyForModel, -} from "../src/pi/index"; +import { AgentHarness, InMemorySessionRepo } from "@earendil-works/pi-agent-core"; +import { attach, type LoopModelRef, requireLoopEnvApiKeyForModel } from "../src/pi/index"; import { logAgentEvent, logAssistant } from "./shared/logging"; import { SCENARIOS } from "./shared/scenarios"; diff --git a/packages/loop/examples/harness-provider-matrix.ts b/packages/loop/examples/harness-provider-matrix.ts index 19a21ee..c758d38 100644 --- a/packages/loop/examples/harness-provider-matrix.ts +++ b/packages/loop/examples/harness-provider-matrix.ts @@ -1,11 +1,6 @@ import Kernel from "@onkernel/sdk"; -import { - AgentHarness, - attach, - InMemorySessionRepo, - type LoopModelRef, - requireLoopEnvApiKeyForModel, -} from "../src/pi/index"; +import { AgentHarness, InMemorySessionRepo } from "@earendil-works/pi-agent-core"; +import { attach, type LoopModelRef, requireLoopEnvApiKeyForModel } from "../src/pi/index"; import { logAgentEvent, logAssistant } from "./shared/logging"; import { SCENARIOS } from "./shared/scenarios"; import { toolsForModel } from "./shared/tools"; diff --git a/packages/loop/examples/shared/logging.ts b/packages/loop/examples/shared/logging.ts index 7d68b5b..779afe1 100644 --- a/packages/loop/examples/shared/logging.ts +++ b/packages/loop/examples/shared/logging.ts @@ -1,4 +1,4 @@ -import type { AgentEvent, AgentHarnessEvent } from "../../src/pi/index"; +import type { AgentEvent, AgentHarnessEvent } from "@earendil-works/pi-agent-core"; type AssistantLike = { content: Array<{ type: string; text?: string }>; diff --git a/packages/loop/package.json b/packages/loop/package.json index ca86910..b9efb95 100644 --- a/packages/loop/package.json +++ b/packages/loop/package.json @@ -71,6 +71,7 @@ "typebox": "1.3.7" }, "peerDependencies": { + "@earendil-works/pi-agent-core": "0.83.0", "@earendil-works/pi-coding-agent": "*" }, "peerDependenciesMeta": { diff --git a/packages/loop/src/pi/index.ts b/packages/loop/src/pi/index.ts index 6cec36d..5f1081d 100644 --- a/packages/loop/src/pi/index.ts +++ b/packages/loop/src/pi/index.ts @@ -1,6 +1,3 @@ -export * from "@earendil-works/pi-agent-core"; -export { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node"; - export * from "./api-keys"; export * from "./models"; export { diff --git a/packages/loop/test/attach-session.test.ts b/packages/loop/test/attach-session.test.ts index 17a1ffd..15acbb4 100644 --- a/packages/loop/test/attach-session.test.ts +++ b/packages/loop/test/attach-session.test.ts @@ -11,14 +11,16 @@ import { AgentHarness, type AgentMessage, type AgentTool, + InMemorySessionRepo, + type Session, + type StreamFn, +} from "@earendil-works/pi-agent-core"; +import { attach, createLoopModels, getLoopModel, GOOGLE_INTERACTIONS_API, - InMemorySessionRepo, type LoopModelInput, - type Session, - type StreamFn, } from "../src/pi/index"; import { type KernelBrowser, loop, type LoopHarnessTool } from "../src/index"; import type Kernel from "@onkernel/sdk"; diff --git a/packages/loop/test/attach.test.ts b/packages/loop/test/attach.test.ts index ccb2eb4..f6d64d7 100644 --- a/packages/loop/test/attach.test.ts +++ b/packages/loop/test/attach.test.ts @@ -5,16 +5,13 @@ import { createAssistantMessageEventStream, type Model, } from "@earendil-works/pi-ai"; +import { Agent, AgentHarness, InMemorySessionRepo, type StreamFn } from "@earendil-works/pi-agent-core"; import { - Agent, - AgentHarness, attach, createLoopModels, GOOGLE_INTERACTIONS_API, - InMemorySessionRepo, type LoopSimpleStreamOptions, OPENAI_COMPUTER_USE_API, - type StreamFn, } from "../src/pi/index"; import { type KernelBrowser, loop } from "../src/index"; import type Kernel from "@onkernel/sdk"; diff --git a/packages/loop/test/e2e.live.test.ts b/packages/loop/test/e2e.live.test.ts index 59db5b6..6e7cccc 100644 --- a/packages/loop/test/e2e.live.test.ts +++ b/packages/loop/test/e2e.live.test.ts @@ -6,9 +6,9 @@ import { AgentHarness, type AgentHarnessEvent, type AgentMessage, - attach, InMemorySessionRepo, -} from "../src/pi/index"; +} from "@earendil-works/pi-agent-core"; +import { attach } from "../src/pi/index"; import { loop } from "../src/index"; const LIVE = process.env.LOOP_E2E_LIVE === "1"; diff --git a/packages/loop/test/harness-context.test.ts b/packages/loop/test/harness-context.test.ts index 0188160..932eda7 100644 --- a/packages/loop/test/harness-context.test.ts +++ b/packages/loop/test/harness-context.test.ts @@ -6,17 +6,16 @@ import { type AssistantMessage, createAssistantMessageEventStream, type Model } import { AgentHarness, type AgentHarnessTool, - attach, createBashTool, createEditTool, - createLoopModels, createReadTool, createWriteTool, type ExecutionToolContext, InMemorySessionRepo, - NodeExecutionEnv, type StreamFn, -} from "../src/pi/index"; +} from "@earendil-works/pi-agent-core"; +import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node"; +import { attach, createLoopModels } from "../src/pi/index"; import type Kernel from "@onkernel/sdk"; import type { KernelBrowser } from "../src/index"; diff --git a/packages/loop/test/pi-export-surface.test.ts b/packages/loop/test/pi-export-surface.test.ts new file mode 100644 index 0000000..3e0ea78 --- /dev/null +++ b/packages/loop/test/pi-export-surface.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from "vitest"; +import * as pi from "../src/pi/index"; + +// ./pi exports only loop-owned symbols. pi-agent-core is composed with, not +// re-exported: consumers construct Agent/AgentHarness from their own +// pi-agent-core install. +describe("./pi export surface", () => { + it("exports the loop pi binding", () => { + for (const name of ["attach", "createLoopModels", "loopModels", "getLoopModel", "listLoopModels", "loopApiKeyEnvVarsForProvider"]) { + expect(pi, name).toHaveProperty(name); + } + }); + + it("does not re-export pi-agent-core", () => { + const leaked = [ + "Agent", + "AgentHarness", + "InMemorySessionRepo", + "NodeExecutionEnv", + "createBashTool", + "createEditTool", + "createReadTool", + "createWriteTool", + ].filter((name) => name in pi); + expect(leaked).toEqual([]); + }); +}); diff --git a/packages/loop/test/published-declarations.test.ts b/packages/loop/test/published-declarations.test.ts index 793e58d..8f94c53 100644 --- a/packages/loop/test/published-declarations.test.ts +++ b/packages/loop/test/published-declarations.test.ts @@ -24,17 +24,16 @@ import { import { Agent, AgentHarness, - attach, - getLoopModel, InMemorySessionRepo, - NodeExecutionEnv, createBashTool, createEditTool, createReadTool, createWriteTool, type AgentHarnessTool, type ExecutionToolContext, -} from "@onkernel/loop/pi"; +} from "@earendil-works/pi-agent-core"; +import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node"; +import { attach, getLoopModel } from "@onkernel/loop/pi"; import type Kernel from "@onkernel/sdk"; declare const browser: KernelBrowser; @@ -159,6 +158,9 @@ describe("published declarations", () => { paths: { "@onkernel/loop": [join(out, "index.d.ts")], "@onkernel/loop/pi": [join(out, "pi", "index.d.ts")], + // The "*" fallback resolves a package root through its package.json, + // but not subpath exports; map this one explicitly. + "@earendil-works/pi-agent-core/node": [join(repoRoot, "node_modules", "@earendil-works", "pi-agent-core", "dist", "node.d.ts")], "*": ["./node_modules/*"], }, }, diff --git a/packages/loop/test/tool-manager.test.ts b/packages/loop/test/tool-manager.test.ts index 8d21d84..90aa549 100644 --- a/packages/loop/test/tool-manager.test.ts +++ b/packages/loop/test/tool-manager.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from "vitest"; -import { type AgentTool, GOOGLE_INTERACTIONS_API } from "../src/pi/index"; +import type { AgentTool } from "@earendil-works/pi-agent-core"; +import { GOOGLE_INTERACTIONS_API } from "../src/pi/index"; import { callerToolIdentity, type KernelBrowser,