gsd-pi 2.43.0-next.8 → 2.44.0-dev.62b5d6c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +13 -1
- package/dist/help-text.js +24 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +21 -8
- package/dist/resources/extensions/gsd/auto-prompts.js +130 -51
- package/dist/resources/extensions/gsd/auto-worktree.js +16 -2
- package/dist/resources/extensions/gsd/dispatch-guard.js +34 -10
- package/dist/resources/extensions/gsd/markdown-renderer.js +7 -5
- package/dist/resources/extensions/gsd/reactive-graph.js +13 -2
- package/dist/resources/extensions/gsd/skill-health.js +3 -1
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +2 -11
- package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -10
- package/dist/resources/extensions/gsd/visualizer-data.js +45 -13
- package/dist/resources/extensions/gsd/workspace-index.js +46 -15
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +4 -4
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
- package/dist/web/standalone/.next/server/chunks/229.js +1 -1
- package/dist/web/standalone/.next/server/chunks/471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-f2a7482d42a5614b.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{main-app-2f2ee7b85712c2bd.js → main-app-fdab67f7802d7832.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/package.json +4 -4
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +3 -3
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +11 -34
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +4 -4
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +37 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/fallback-resolver.js +2 -3
- package/packages/pi-coding-agent/dist/core/fallback-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js +12 -2
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +38 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +192 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +255 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +40 -3
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-commands.d.ts +25 -0
- package/packages/pi-coding-agent/dist/core/package-commands.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/package-commands.js +253 -0
- package/packages/pi-coding-agent/dist/core/package-commands.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/package-commands.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/package-commands.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/package-commands.test.js +225 -0
- package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +4 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +3 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +11 -199
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +13 -37
- package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +2 -2
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +3 -3
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +4 -4
- package/packages/pi-coding-agent/src/core/extensions/index.ts +5 -0
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +23 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +44 -0
- package/packages/pi-coding-agent/src/core/fallback-resolver.test.ts +15 -2
- package/packages/pi-coding-agent/src/core/fallback-resolver.ts +2 -3
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +274 -0
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +288 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +39 -3
- package/packages/pi-coding-agent/src/core/package-commands.test.ts +240 -0
- package/packages/pi-coding-agent/src/core/package-commands.ts +310 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +4 -0
- package/packages/pi-coding-agent/src/index.ts +7 -0
- package/packages/pi-coding-agent/src/main.ts +11 -232
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +22 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +109 -42
- package/src/resources/extensions/gsd/auto-worktree.ts +16 -3
- package/src/resources/extensions/gsd/dispatch-guard.ts +28 -10
- package/src/resources/extensions/gsd/markdown-renderer.ts +7 -5
- package/src/resources/extensions/gsd/reactive-graph.ts +12 -2
- package/src/resources/extensions/gsd/skill-health.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-stash-merge.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +1 -1
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +1 -18
- package/src/resources/extensions/gsd/tools/plan-slice.ts +1 -15
- package/src/resources/extensions/gsd/visualizer-data.ts +46 -14
- package/src/resources/extensions/gsd/workspace-index.ts +49 -18
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-e07acdb7dd069836.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/layout-745c6ed5fea5fb06.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/page-801b53eff6e83579.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-e6255954dccfcf0a.js +0 -1
- /package/dist/web/standalone/.next/static/{drUWS0zys9uepCfCwecJv → fOnWQBjWXMKUs4bqTg530}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{drUWS0zys9uepCfCwecJv → fOnWQBjWXMKUs4bqTg530}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import type { Api, Model } from "@gsd/pi-ai";
|
|
4
|
+
import type { AuthStorage } from "./auth-storage.js";
|
|
5
|
+
import { ModelRegistry } from "./model-registry.js";
|
|
6
|
+
|
|
7
|
+
function createRegistry(hasAuthFn?: (provider: string) => boolean): ModelRegistry {
|
|
8
|
+
const authStorage = {
|
|
9
|
+
setFallbackResolver: () => {},
|
|
10
|
+
onCredentialChange: () => {},
|
|
11
|
+
getOAuthProviders: () => [],
|
|
12
|
+
get: () => undefined,
|
|
13
|
+
hasAuth: hasAuthFn ?? (() => false),
|
|
14
|
+
getApiKey: async () => undefined,
|
|
15
|
+
} as unknown as AuthStorage;
|
|
16
|
+
|
|
17
|
+
return new ModelRegistry(authStorage, undefined);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createProviderModel(id: string): NonNullable<Parameters<ModelRegistry["registerProvider"]>[1]["models"]>[number] {
|
|
21
|
+
return {
|
|
22
|
+
id,
|
|
23
|
+
name: id,
|
|
24
|
+
api: "openai-completions",
|
|
25
|
+
reasoning: false,
|
|
26
|
+
input: ["text"],
|
|
27
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
28
|
+
contextWindow: 128000,
|
|
29
|
+
maxTokens: 16384,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function findModel(registry: ModelRegistry, provider: string, id: string): Model<Api> | undefined {
|
|
34
|
+
return registry.getAvailable().find((m) => m.provider === provider && m.id === id);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── Registration ─────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
describe("ModelRegistry authMode — registration", () => {
|
|
40
|
+
it("registers externalCli provider without apiKey/oauth", () => {
|
|
41
|
+
const registry = createRegistry();
|
|
42
|
+
assert.doesNotThrow(() => {
|
|
43
|
+
registry.registerProvider("cli-provider", {
|
|
44
|
+
authMode: "externalCli",
|
|
45
|
+
baseUrl: "https://cli.local",
|
|
46
|
+
api: "openai-completions",
|
|
47
|
+
models: [createProviderModel("cli-model")],
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("registers none provider without apiKey/oauth", () => {
|
|
53
|
+
const registry = createRegistry();
|
|
54
|
+
assert.doesNotThrow(() => {
|
|
55
|
+
registry.registerProvider("none-provider", {
|
|
56
|
+
authMode: "none",
|
|
57
|
+
baseUrl: "http://localhost:11434",
|
|
58
|
+
api: "openai-completions",
|
|
59
|
+
models: [createProviderModel("local-model")],
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("rejects apiKey provider without apiKey or oauth", () => {
|
|
65
|
+
const registry = createRegistry();
|
|
66
|
+
assert.throws(() => {
|
|
67
|
+
registry.registerProvider("apikey-provider", {
|
|
68
|
+
authMode: "apiKey",
|
|
69
|
+
baseUrl: "https://api.local",
|
|
70
|
+
api: "openai-completions",
|
|
71
|
+
models: [createProviderModel("model")],
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("rejects provider with no authMode and no apiKey/oauth (defaults to apiKey)", () => {
|
|
77
|
+
const registry = createRegistry();
|
|
78
|
+
assert.throws(() => {
|
|
79
|
+
registry.registerProvider("bare-provider", {
|
|
80
|
+
baseUrl: "https://api.local",
|
|
81
|
+
api: "openai-completions",
|
|
82
|
+
models: [createProviderModel("model")],
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ─── getProviderAuthMode ──────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
describe("ModelRegistry authMode — getProviderAuthMode", () => {
|
|
91
|
+
it("returns apiKey for unregistered (built-in) providers", () => {
|
|
92
|
+
const registry = createRegistry();
|
|
93
|
+
assert.equal(registry.getProviderAuthMode("anthropic"), "apiKey");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("returns explicit authMode when set", () => {
|
|
97
|
+
const registry = createRegistry();
|
|
98
|
+
registry.registerProvider("cli", {
|
|
99
|
+
authMode: "externalCli",
|
|
100
|
+
baseUrl: "https://cli.local",
|
|
101
|
+
api: "openai-completions",
|
|
102
|
+
models: [createProviderModel("m")],
|
|
103
|
+
});
|
|
104
|
+
assert.equal(registry.getProviderAuthMode("cli"), "externalCli");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("returns none when authMode is none", () => {
|
|
108
|
+
const registry = createRegistry();
|
|
109
|
+
registry.registerProvider("local", {
|
|
110
|
+
authMode: "none",
|
|
111
|
+
baseUrl: "http://localhost:11434",
|
|
112
|
+
api: "openai-completions",
|
|
113
|
+
models: [createProviderModel("m")],
|
|
114
|
+
});
|
|
115
|
+
assert.equal(registry.getProviderAuthMode("local"), "none");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ─── isProviderRequestReady ───────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
describe("ModelRegistry authMode — isProviderRequestReady", () => {
|
|
122
|
+
it("returns true for externalCli without stored auth", () => {
|
|
123
|
+
const registry = createRegistry(() => false);
|
|
124
|
+
registry.registerProvider("cli", {
|
|
125
|
+
authMode: "externalCli",
|
|
126
|
+
baseUrl: "https://cli.local",
|
|
127
|
+
api: "openai-completions",
|
|
128
|
+
models: [createProviderModel("m")],
|
|
129
|
+
});
|
|
130
|
+
assert.equal(registry.isProviderRequestReady("cli"), true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("returns true for none without stored auth", () => {
|
|
134
|
+
const registry = createRegistry(() => false);
|
|
135
|
+
registry.registerProvider("local", {
|
|
136
|
+
authMode: "none",
|
|
137
|
+
baseUrl: "http://localhost:11434",
|
|
138
|
+
api: "openai-completions",
|
|
139
|
+
models: [createProviderModel("m")],
|
|
140
|
+
});
|
|
141
|
+
assert.equal(registry.isProviderRequestReady("local"), true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("returns false for apiKey provider without stored auth", () => {
|
|
145
|
+
const registry = createRegistry(() => false);
|
|
146
|
+
assert.equal(registry.isProviderRequestReady("anthropic"), false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("returns true for apiKey provider with stored auth", () => {
|
|
150
|
+
const registry = createRegistry(() => true);
|
|
151
|
+
assert.equal(registry.isProviderRequestReady("anthropic"), true);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ─── isReady callback ─────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
describe("ModelRegistry authMode — isReady callback", () => {
|
|
158
|
+
it("calls isReady and returns its result for externalCli provider", () => {
|
|
159
|
+
const registry = createRegistry(() => false);
|
|
160
|
+
registry.registerProvider("cli-down", {
|
|
161
|
+
authMode: "externalCli",
|
|
162
|
+
baseUrl: "https://cli.local",
|
|
163
|
+
api: "openai-completions",
|
|
164
|
+
isReady: () => false,
|
|
165
|
+
models: [createProviderModel("m")],
|
|
166
|
+
});
|
|
167
|
+
assert.equal(registry.isProviderRequestReady("cli-down"), false);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("calls isReady for apiKey provider (overrides hasAuth)", () => {
|
|
171
|
+
const registry = createRegistry(() => true);
|
|
172
|
+
registry.registerProvider("strict-provider", {
|
|
173
|
+
apiKey: "MY_KEY",
|
|
174
|
+
baseUrl: "https://api.local",
|
|
175
|
+
api: "openai-completions",
|
|
176
|
+
isReady: () => false,
|
|
177
|
+
models: [createProviderModel("m")],
|
|
178
|
+
});
|
|
179
|
+
assert.equal(registry.isProviderRequestReady("strict-provider"), false);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("isReady returning true makes provider available", () => {
|
|
183
|
+
const registry = createRegistry(() => false);
|
|
184
|
+
registry.registerProvider("healthy-cli", {
|
|
185
|
+
authMode: "externalCli",
|
|
186
|
+
baseUrl: "https://cli.local",
|
|
187
|
+
api: "openai-completions",
|
|
188
|
+
isReady: () => true,
|
|
189
|
+
models: [createProviderModel("m")],
|
|
190
|
+
});
|
|
191
|
+
assert.equal(registry.isProviderRequestReady("healthy-cli"), true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("falls through to default behavior when isReady not provided", () => {
|
|
195
|
+
const registry = createRegistry(() => false);
|
|
196
|
+
registry.registerProvider("no-callback", {
|
|
197
|
+
authMode: "externalCli",
|
|
198
|
+
baseUrl: "https://cli.local",
|
|
199
|
+
api: "openai-completions",
|
|
200
|
+
models: [createProviderModel("m")],
|
|
201
|
+
});
|
|
202
|
+
// externalCli without isReady → true (default)
|
|
203
|
+
assert.equal(registry.isProviderRequestReady("no-callback"), true);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ─── getAvailable ─────────────────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
describe("ModelRegistry authMode — getAvailable", () => {
|
|
210
|
+
it("includes externalCli models without stored auth", () => {
|
|
211
|
+
const registry = createRegistry(() => false);
|
|
212
|
+
registry.registerProvider("cli", {
|
|
213
|
+
authMode: "externalCli",
|
|
214
|
+
baseUrl: "https://cli.local",
|
|
215
|
+
api: "openai-completions",
|
|
216
|
+
models: [createProviderModel("cli-model")],
|
|
217
|
+
});
|
|
218
|
+
assert.ok(findModel(registry, "cli", "cli-model"));
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("includes none models without stored auth", () => {
|
|
222
|
+
const registry = createRegistry(() => false);
|
|
223
|
+
registry.registerProvider("local", {
|
|
224
|
+
authMode: "none",
|
|
225
|
+
baseUrl: "http://localhost:11434",
|
|
226
|
+
api: "openai-completions",
|
|
227
|
+
models: [createProviderModel("local-model")],
|
|
228
|
+
});
|
|
229
|
+
assert.ok(findModel(registry, "local", "local-model"));
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("excludes externalCli models when isReady returns false", () => {
|
|
233
|
+
const registry = createRegistry(() => false);
|
|
234
|
+
registry.registerProvider("cli-down", {
|
|
235
|
+
authMode: "externalCli",
|
|
236
|
+
baseUrl: "https://cli.local",
|
|
237
|
+
api: "openai-completions",
|
|
238
|
+
isReady: () => false,
|
|
239
|
+
models: [createProviderModel("m")],
|
|
240
|
+
});
|
|
241
|
+
assert.equal(findModel(registry, "cli-down", "m"), undefined);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("excludes apiKey models without stored auth", () => {
|
|
245
|
+
const registry = createRegistry(() => false);
|
|
246
|
+
// Built-in providers have no registeredProviders entry, so authMode defaults to apiKey
|
|
247
|
+
// getAvailable filters by isProviderRequestReady → hasAuth → false
|
|
248
|
+
const available = registry.getAvailable();
|
|
249
|
+
// No models should be available since hasAuth returns false for everything
|
|
250
|
+
assert.equal(available.length, 0);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// ─── getApiKey ────────────────────────────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
describe("ModelRegistry authMode — getApiKey", () => {
|
|
257
|
+
it("returns undefined for externalCli provider", async () => {
|
|
258
|
+
const registry = createRegistry();
|
|
259
|
+
registry.registerProvider("cli", {
|
|
260
|
+
authMode: "externalCli",
|
|
261
|
+
baseUrl: "https://cli.local",
|
|
262
|
+
api: "openai-completions",
|
|
263
|
+
models: [createProviderModel("m")],
|
|
264
|
+
});
|
|
265
|
+
const model = registry.getAll().find((m) => m.provider === "cli")!;
|
|
266
|
+
assert.equal(await registry.getApiKey(model), undefined);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("returns undefined for none provider", async () => {
|
|
270
|
+
const registry = createRegistry();
|
|
271
|
+
registry.registerProvider("local", {
|
|
272
|
+
authMode: "none",
|
|
273
|
+
baseUrl: "http://localhost:11434",
|
|
274
|
+
api: "openai-completions",
|
|
275
|
+
models: [createProviderModel("m")],
|
|
276
|
+
});
|
|
277
|
+
const model = registry.getAll().find((m) => m.provider === "local")!;
|
|
278
|
+
assert.equal(await registry.getApiKey(model), undefined);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("delegates to authStorage for apiKey provider", async () => {
|
|
282
|
+
const registry = createRegistry();
|
|
283
|
+
// authStorage.getApiKey returns undefined (no key configured)
|
|
284
|
+
// For apiKey providers this is an expected "no key" response, not early exit
|
|
285
|
+
const key = await registry.getApiKeyForProvider("anthropic");
|
|
286
|
+
assert.equal(key, undefined);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
@@ -128,6 +128,8 @@ ajv.addSchema(ModelsConfigSchema, "ModelsConfig");
|
|
|
128
128
|
|
|
129
129
|
type ModelsConfig = Static<typeof ModelsConfigSchema>;
|
|
130
130
|
|
|
131
|
+
export type ProviderAuthMode = "apiKey" | "oauth" | "externalCli" | "none";
|
|
132
|
+
|
|
131
133
|
/** Provider override config (baseUrl, headers, apiKey) without custom models */
|
|
132
134
|
interface ProviderOverride {
|
|
133
135
|
baseUrl?: string;
|
|
@@ -513,7 +515,31 @@ export class ModelRegistry {
|
|
|
513
515
|
* This is a fast check that doesn't refresh OAuth tokens.
|
|
514
516
|
*/
|
|
515
517
|
getAvailable(): Model<Api>[] {
|
|
516
|
-
return this.models.filter((m) => this.
|
|
518
|
+
return this.models.filter((m) => this.isProviderRequestReady(m.provider));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Get auth mode for a provider.
|
|
523
|
+
* Defaults to "apiKey" for built-ins and providers without explicit mode.
|
|
524
|
+
*/
|
|
525
|
+
getProviderAuthMode(provider: string): ProviderAuthMode {
|
|
526
|
+
const config = this.registeredProviders.get(provider);
|
|
527
|
+
if (!config) return "apiKey";
|
|
528
|
+
if (config.authMode) return config.authMode;
|
|
529
|
+
if (config.oauth) return "oauth";
|
|
530
|
+
if (config.apiKey) return "apiKey";
|
|
531
|
+
return "apiKey";
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Whether a provider can be used for requests/fallback without hard auth gating.
|
|
536
|
+
*/
|
|
537
|
+
isProviderRequestReady(provider: string): boolean {
|
|
538
|
+
const config = this.registeredProviders.get(provider);
|
|
539
|
+
if (config?.isReady) return config.isReady();
|
|
540
|
+
const authMode = this.getProviderAuthMode(provider);
|
|
541
|
+
if (authMode === "externalCli" || authMode === "none") return true;
|
|
542
|
+
return this.authStorage.hasAuth(provider);
|
|
517
543
|
}
|
|
518
544
|
|
|
519
545
|
/**
|
|
@@ -525,17 +551,23 @@ export class ModelRegistry {
|
|
|
525
551
|
|
|
526
552
|
/**
|
|
527
553
|
* Get API key for a model.
|
|
554
|
+
* Returns undefined for externalCli/none providers (no key needed).
|
|
528
555
|
* @param sessionId - Optional session ID for sticky credential selection
|
|
529
556
|
*/
|
|
530
557
|
async getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined> {
|
|
558
|
+
const authMode = this.getProviderAuthMode(model.provider);
|
|
559
|
+
if (authMode === "externalCli" || authMode === "none") return undefined;
|
|
531
560
|
return this.authStorage.getApiKey(model.provider, sessionId);
|
|
532
561
|
}
|
|
533
562
|
|
|
534
563
|
/**
|
|
535
564
|
* Get API key for a provider.
|
|
565
|
+
* Returns undefined for externalCli/none providers (no key needed).
|
|
536
566
|
* @param sessionId - Optional session ID for sticky credential selection
|
|
537
567
|
*/
|
|
538
568
|
async getApiKeyForProvider(provider: string, sessionId?: string): Promise<string | undefined> {
|
|
569
|
+
const authMode = this.getProviderAuthMode(provider);
|
|
570
|
+
if (authMode === "externalCli" || authMode === "none") return undefined;
|
|
539
571
|
return this.authStorage.getApiKey(provider, sessionId);
|
|
540
572
|
}
|
|
541
573
|
|
|
@@ -614,7 +646,8 @@ export class ModelRegistry {
|
|
|
614
646
|
if (!config.baseUrl) {
|
|
615
647
|
throw new Error(`Provider ${providerName}: "baseUrl" is required when defining models.`);
|
|
616
648
|
}
|
|
617
|
-
|
|
649
|
+
const authMode = config.authMode ?? (config.oauth ? "oauth" : config.apiKey ? "apiKey" : "apiKey");
|
|
650
|
+
if (authMode === "apiKey" && !config.apiKey && !config.oauth) {
|
|
618
651
|
throw new Error(`Provider ${providerName}: "apiKey" or "oauth" is required when defining models.`);
|
|
619
652
|
}
|
|
620
653
|
|
|
@@ -702,7 +735,7 @@ export class ModelRegistry {
|
|
|
702
735
|
|
|
703
736
|
try {
|
|
704
737
|
const apiKey = await this.authStorage.getApiKey(providerName);
|
|
705
|
-
if (!apiKey && providerName
|
|
738
|
+
if (!apiKey && !this.isProviderRequestReady(providerName)) continue;
|
|
706
739
|
|
|
707
740
|
const models = await adapter.fetchModels(apiKey ?? "", undefined);
|
|
708
741
|
this.discoveryCache.set(providerName, models);
|
|
@@ -780,6 +813,9 @@ export class ModelRegistry {
|
|
|
780
813
|
* Input type for registerProvider API.
|
|
781
814
|
*/
|
|
782
815
|
export interface ProviderConfigInput {
|
|
816
|
+
authMode?: ProviderAuthMode;
|
|
817
|
+
/** Optional readiness check. Called by isProviderRequestReady() before default auth checks. */
|
|
818
|
+
isReady?: () => boolean;
|
|
783
819
|
baseUrl?: string;
|
|
784
820
|
apiKey?: string;
|
|
785
821
|
api?: Api;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { Writable } from "node:stream";
|
|
6
|
+
import { describe, it } from "node:test";
|
|
7
|
+
import { runPackageCommand } from "./package-commands.js";
|
|
8
|
+
|
|
9
|
+
function createCaptureStream() {
|
|
10
|
+
let output = "";
|
|
11
|
+
const stream = new Writable({
|
|
12
|
+
write(chunk, _encoding, callback) {
|
|
13
|
+
output += chunk.toString();
|
|
14
|
+
callback();
|
|
15
|
+
},
|
|
16
|
+
}) as unknown as NodeJS.WriteStream;
|
|
17
|
+
return { stream, getOutput: () => output };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writePackage(root: string, files: Record<string, string>): void {
|
|
21
|
+
for (const [relPath, content] of Object.entries(files)) {
|
|
22
|
+
const abs = join(root, relPath);
|
|
23
|
+
mkdirSync(join(abs, ".."), { recursive: true });
|
|
24
|
+
writeFileSync(abs, content, "utf-8");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("runPackageCommand lifecycle hooks", () => {
|
|
29
|
+
it("executes registered beforeInstall and afterInstall handlers for local packages", async () => {
|
|
30
|
+
const root = mkdtempSync(join(tmpdir(), "pi-lifecycle-install-"));
|
|
31
|
+
const cwd = join(root, "cwd");
|
|
32
|
+
const agentDir = join(root, "agent");
|
|
33
|
+
const extensionDir = join(root, "ext-registered");
|
|
34
|
+
mkdirSync(cwd, { recursive: true });
|
|
35
|
+
mkdirSync(agentDir, { recursive: true });
|
|
36
|
+
mkdirSync(extensionDir, { recursive: true });
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
writePackage(extensionDir, {
|
|
40
|
+
"package.json": JSON.stringify({
|
|
41
|
+
name: "ext-registered",
|
|
42
|
+
type: "module",
|
|
43
|
+
pi: { extensions: ["./index.js"] },
|
|
44
|
+
}),
|
|
45
|
+
"index.js": `
|
|
46
|
+
import { writeFileSync } from "node:fs";
|
|
47
|
+
import { join } from "node:path";
|
|
48
|
+
export default function (pi) {
|
|
49
|
+
pi.registerBeforeInstall((ctx) => {
|
|
50
|
+
writeFileSync(join(ctx.installedPath, "before-install-ran.txt"), "ok", "utf-8");
|
|
51
|
+
});
|
|
52
|
+
pi.registerAfterInstall((ctx) => {
|
|
53
|
+
writeFileSync(join(ctx.installedPath, "after-install-ran.txt"), "ok", "utf-8");
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
`,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const stdout = createCaptureStream();
|
|
60
|
+
const stderr = createCaptureStream();
|
|
61
|
+
const result = await runPackageCommand({
|
|
62
|
+
appName: "pi",
|
|
63
|
+
args: ["install", extensionDir],
|
|
64
|
+
cwd,
|
|
65
|
+
agentDir,
|
|
66
|
+
stdout: stdout.stream,
|
|
67
|
+
stderr: stderr.stream,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
assert.equal(result.handled, true);
|
|
71
|
+
assert.equal(result.exitCode, 0);
|
|
72
|
+
assert.equal(readFileSync(join(extensionDir, "before-install-ran.txt"), "utf-8"), "ok");
|
|
73
|
+
assert.equal(readFileSync(join(extensionDir, "after-install-ran.txt"), "utf-8"), "ok");
|
|
74
|
+
assert.ok(stdout.getOutput().includes(`Installed ${extensionDir}`));
|
|
75
|
+
} finally {
|
|
76
|
+
rmSync(root, { recursive: true, force: true });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("runs legacy named lifecycle hooks when no registered hooks exist", async () => {
|
|
81
|
+
const root = mkdtempSync(join(tmpdir(), "pi-lifecycle-legacy-"));
|
|
82
|
+
const cwd = join(root, "cwd");
|
|
83
|
+
const agentDir = join(root, "agent");
|
|
84
|
+
const extensionDir = join(root, "ext-legacy");
|
|
85
|
+
mkdirSync(cwd, { recursive: true });
|
|
86
|
+
mkdirSync(agentDir, { recursive: true });
|
|
87
|
+
mkdirSync(extensionDir, { recursive: true });
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
writePackage(extensionDir, {
|
|
91
|
+
"package.json": JSON.stringify({
|
|
92
|
+
name: "ext-legacy",
|
|
93
|
+
type: "module",
|
|
94
|
+
pi: { extensions: ["./index.js"] },
|
|
95
|
+
}),
|
|
96
|
+
"index.js": `
|
|
97
|
+
import { writeFileSync } from "node:fs";
|
|
98
|
+
import { join } from "node:path";
|
|
99
|
+
export default function () {}
|
|
100
|
+
export async function beforeInstall(ctx) {
|
|
101
|
+
writeFileSync(join(ctx.installedPath, "legacy-before-install.txt"), "ok", "utf-8");
|
|
102
|
+
}
|
|
103
|
+
export async function afterInstall(ctx) {
|
|
104
|
+
writeFileSync(join(ctx.installedPath, "legacy-after-install.txt"), "ok", "utf-8");
|
|
105
|
+
}
|
|
106
|
+
export async function beforeRemove(ctx) {
|
|
107
|
+
writeFileSync(join(ctx.installedPath, "legacy-before-remove.txt"), "ok", "utf-8");
|
|
108
|
+
}
|
|
109
|
+
export async function afterRemove(ctx) {
|
|
110
|
+
writeFileSync(join(ctx.installedPath, "legacy-after-remove.txt"), "ok", "utf-8");
|
|
111
|
+
}
|
|
112
|
+
`,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const stdout = createCaptureStream();
|
|
116
|
+
const stderr = createCaptureStream();
|
|
117
|
+
const installResult = await runPackageCommand({
|
|
118
|
+
appName: "pi",
|
|
119
|
+
args: ["install", extensionDir],
|
|
120
|
+
cwd,
|
|
121
|
+
agentDir,
|
|
122
|
+
stdout: stdout.stream,
|
|
123
|
+
stderr: stderr.stream,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
assert.equal(installResult.handled, true);
|
|
127
|
+
assert.equal(installResult.exitCode, 0);
|
|
128
|
+
assert.equal(readFileSync(join(extensionDir, "legacy-before-install.txt"), "utf-8"), "ok");
|
|
129
|
+
assert.equal(readFileSync(join(extensionDir, "legacy-after-install.txt"), "utf-8"), "ok");
|
|
130
|
+
|
|
131
|
+
const removeResult = await runPackageCommand({
|
|
132
|
+
appName: "pi",
|
|
133
|
+
args: ["remove", extensionDir],
|
|
134
|
+
cwd,
|
|
135
|
+
agentDir,
|
|
136
|
+
stdout: stdout.stream,
|
|
137
|
+
stderr: stderr.stream,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
assert.equal(removeResult.handled, true);
|
|
141
|
+
assert.equal(removeResult.exitCode, 0);
|
|
142
|
+
assert.equal(readFileSync(join(extensionDir, "legacy-before-remove.txt"), "utf-8"), "ok");
|
|
143
|
+
assert.equal(readFileSync(join(extensionDir, "legacy-after-remove.txt"), "utf-8"), "ok");
|
|
144
|
+
} finally {
|
|
145
|
+
rmSync(root, { recursive: true, force: true });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("skips lifecycle phases with no hooks declared", async () => {
|
|
150
|
+
const root = mkdtempSync(join(tmpdir(), "pi-lifecycle-skip-"));
|
|
151
|
+
const cwd = join(root, "cwd");
|
|
152
|
+
const agentDir = join(root, "agent");
|
|
153
|
+
const extensionDir = join(root, "ext-empty");
|
|
154
|
+
mkdirSync(cwd, { recursive: true });
|
|
155
|
+
mkdirSync(agentDir, { recursive: true });
|
|
156
|
+
mkdirSync(extensionDir, { recursive: true });
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
writePackage(extensionDir, {
|
|
160
|
+
"package.json": JSON.stringify({
|
|
161
|
+
name: "ext-empty",
|
|
162
|
+
type: "module",
|
|
163
|
+
pi: { extensions: ["./index.js"] },
|
|
164
|
+
}),
|
|
165
|
+
"index.js": `export default function () {}`,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const stdout = createCaptureStream();
|
|
169
|
+
const stderr = createCaptureStream();
|
|
170
|
+
const installResult = await runPackageCommand({
|
|
171
|
+
appName: "pi",
|
|
172
|
+
args: ["install", extensionDir],
|
|
173
|
+
cwd,
|
|
174
|
+
agentDir,
|
|
175
|
+
stdout: stdout.stream,
|
|
176
|
+
stderr: stderr.stream,
|
|
177
|
+
});
|
|
178
|
+
assert.equal(installResult.handled, true);
|
|
179
|
+
assert.equal(installResult.exitCode, 0);
|
|
180
|
+
|
|
181
|
+
const removeResult = await runPackageCommand({
|
|
182
|
+
appName: "pi",
|
|
183
|
+
args: ["remove", extensionDir],
|
|
184
|
+
cwd,
|
|
185
|
+
agentDir,
|
|
186
|
+
stdout: stdout.stream,
|
|
187
|
+
stderr: stderr.stream,
|
|
188
|
+
});
|
|
189
|
+
assert.equal(removeResult.handled, true);
|
|
190
|
+
assert.equal(removeResult.exitCode, 0);
|
|
191
|
+
assert.equal(stderr.getOutput().includes("Hook failed"), false);
|
|
192
|
+
} finally {
|
|
193
|
+
rmSync(root, { recursive: true, force: true });
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("fails install when manifest runtime dependency is missing", async () => {
|
|
198
|
+
const root = mkdtempSync(join(tmpdir(), "pi-lifecycle-deps-"));
|
|
199
|
+
const cwd = join(root, "cwd");
|
|
200
|
+
const agentDir = join(root, "agent");
|
|
201
|
+
const extensionDir = join(root, "ext-runtime-deps");
|
|
202
|
+
mkdirSync(cwd, { recursive: true });
|
|
203
|
+
mkdirSync(agentDir, { recursive: true });
|
|
204
|
+
mkdirSync(extensionDir, { recursive: true });
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
writePackage(extensionDir, {
|
|
208
|
+
"package.json": JSON.stringify({
|
|
209
|
+
name: "ext-runtime-deps",
|
|
210
|
+
type: "module",
|
|
211
|
+
pi: { extensions: ["./index.js"] },
|
|
212
|
+
}),
|
|
213
|
+
"index.js": `export default function () {}`,
|
|
214
|
+
"extension-manifest.json": JSON.stringify({
|
|
215
|
+
id: "ext-runtime-deps",
|
|
216
|
+
name: "Runtime Dep Test",
|
|
217
|
+
version: "1.0.0",
|
|
218
|
+
dependencies: { runtime: ["__definitely_missing_command_for_test__"] },
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const stdout = createCaptureStream();
|
|
223
|
+
const stderr = createCaptureStream();
|
|
224
|
+
const result = await runPackageCommand({
|
|
225
|
+
appName: "pi",
|
|
226
|
+
args: ["install", extensionDir],
|
|
227
|
+
cwd,
|
|
228
|
+
agentDir,
|
|
229
|
+
stdout: stdout.stream,
|
|
230
|
+
stderr: stderr.stream,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
assert.equal(result.handled, true);
|
|
234
|
+
assert.equal(result.exitCode, 1);
|
|
235
|
+
assert.ok(stderr.getOutput().includes("Missing runtime dependencies"));
|
|
236
|
+
} finally {
|
|
237
|
+
rmSync(root, { recursive: true, force: true });
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
});
|