@rexvane/piweb 0.3.22 → 0.3.24
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +8 -7
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +4 -4
- package/.next/react-loadable-manifest.json +11 -4
- package/.next/routes-manifest.json +8 -0
- package/.next/server/app/_global-error/page.js +2 -2
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/side-chat/route.js +5 -0
- package/.next/server/app/api/agent/[id]/side-chat/route.js.nft.json +1 -0
- package/.next/server/app/api/agent/[id]/side-chat/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js +1 -1
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/files/route.js +1 -1
- package/.next/server/app/api/files/route.js.nft.json +1 -1
- package/.next/server/app/api/fs/route.js +1 -1
- package/.next/server/app/api/fs/route.js.nft.json +1 -1
- package/.next/server/app/api/git/route.js +2 -2
- package/.next/server/app/api/git/route.js.nft.json +1 -1
- package/.next/server/app/api/github/route.js +2 -2
- package/.next/server/app/api/github/route.js.nft.json +1 -1
- package/.next/server/app/api/growth/route.js +1 -1
- package/.next/server/app/api/growth/route.js.nft.json +1 -1
- package/.next/server/app/api/health/route.js +1 -1
- package/.next/server/app/api/health/route.js.nft.json +1 -1
- package/.next/server/app/api/models/defaults/route.js +1 -1
- package/.next/server/app/api/models/defaults/route.js.nft.json +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/models/route.js.nft.json +1 -1
- package/.next/server/app/api/pi-settings/route.js +1 -1
- package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/plugins/route.js.nft.json +1 -1
- package/.next/server/app/api/prompts/route.js +1 -1
- package/.next/server/app/api/prompts/route.js.nft.json +1 -1
- package/.next/server/app/api/remote/route.js +3 -3
- package/.next/server/app/api/remote/route.js.nft.json +1 -1
- package/.next/server/app/api/security/route.js +1 -1
- package/.next/server/app/api/security/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/import/route.js +2 -2
- package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +2 -2
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/update/route.js +2 -2
- package/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/api/version/route.js.nft.json +1 -1
- package/.next/server/app/api/web-auth/route.js +2 -2
- package/.next/server/app/api/web-auth/route.js.nft.json +1 -1
- package/.next/server/app/api/workspaces/route.js +1 -1
- package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/.next/server/app/icon.svg/route.js +1 -1
- package/.next/server/app/icon.svg/route.js.nft.json +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +7 -6
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +7 -6
- package/.next/server/app/index.segments/_full.segment.rsc +7 -6
- package/.next/server/app/index.segments/_tree.segment.rsc +3 -2
- package/.next/server/app/login/page.js +1 -1
- package/.next/server/app/login/page.js.nft.json +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +3 -3
- package/.next/server/app/login.segments/_full.segment.rsc +3 -3
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +8 -7
- package/.next/server/chunks/{813.js → 1813.js} +1 -1
- package/.next/server/chunks/{430.js → 2430.js} +1 -1
- package/.next/server/chunks/{970.js → 2970.js} +1 -1
- package/.next/server/chunks/3421.js +1 -0
- package/.next/server/chunks/3924.js +6 -0
- package/.next/server/chunks/4807.js +1 -0
- package/.next/server/chunks/5393.js +1 -0
- package/.next/server/chunks/5802.js +2 -0
- package/.next/server/chunks/6229.js +1 -0
- package/.next/server/chunks/7496.js +1 -0
- package/.next/server/chunks/7662.js +2 -0
- package/.next/server/chunks/8686.js +1 -0
- package/.next/server/chunks/{593.js → 974.js} +1 -1
- package/.next/server/chunks/{920.js → 9920.js} +1 -1
- package/.next/server/functions-config-manifest.json +2 -1
- package/.next/server/instrumentation.js +1 -1
- package/.next/server/instrumentation.js.nft.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/middleware.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/preview-props.json +3 -3
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/server/webpack-runtime.js +1 -1
- package/.next/static/chunks/2716.44fdf1b21454b961.js +1 -0
- package/.next/static/chunks/4673.e261707dce391be7.js +1 -0
- package/.next/static/chunks/4686-0cd87513f9ea3538.js +1 -0
- package/.next/static/chunks/7411.5f79671208209b13.js +1 -0
- package/.next/static/chunks/app/_global-error/page-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/side-chat/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/config/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/files/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/fs/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/git/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/github/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/growth/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/health/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/models/defaults/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/models/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/remote/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/security/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/skills/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/update/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/version/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/page-dc3cb41d91c7432e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/webpack-2ac3d76b81895f5b.js +1 -0
- package/.next/static/css/9fc559c2c4ad1d0f.css +1 -0
- package/.next/static/css/c6e2227c36ec2fd6.css +1 -0
- package/.next/types/app/api/agent/[id]/side-chat/route.ts +355 -0
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +10 -0
- package/next.config.ts +2 -0
- package/package.json +9 -3
- package/scripts/release.mjs +3 -7
- package/scripts/runtime/check-installed.mjs +66 -0
- package/scripts/runtime/pi-packages.mjs +52 -0
- package/scripts/verify-package.mjs +29 -7
- package/src/app/api/agent/[id]/side-chat/route.ts +77 -0
- package/src/app/globals.css +1576 -14
- package/src/components/AppShell.tsx +63 -13
- package/src/components/ChatInput.tsx +33 -29
- package/src/components/ChatWindow.tsx +6 -7
- package/src/components/ContextMeter.tsx +35 -16
- package/src/components/DrawerPanel.tsx +1 -1
- package/src/components/FileViewer.tsx +89 -8
- package/src/components/GrowthTree.tsx +30 -5
- package/src/components/ModelSelector.tsx +21 -5
- package/src/components/SettingsPanel.tsx +25 -16
- package/src/components/chat/reading-anchor.ts +81 -0
- package/src/components/composer/ComposerImages.tsx +31 -11
- package/src/components/side-chat/SideChatPanel.module.css +131 -0
- package/src/components/side-chat/SideChatPanel.tsx +189 -0
- package/src/components/side-chat/icons.tsx +8 -0
- package/src/components/side-chat/request-history.ts +29 -0
- package/src/components/side-chat/store.ts +248 -0
- package/src/hooks/useFileViewer.ts +6 -4
- package/src/i18n.tsx +50 -8
- package/src/lib/files-service.ts +66 -2
- package/src/lib/path-security.ts +24 -32
- package/src/lib/session-reader.ts +1 -1
- package/src/lib/side-chat/context.ts +123 -0
- package/src/lib/side-chat/service.ts +191 -0
- package/src/lib/side-chat/types.ts +61 -0
- package/src/lib/types.ts +1 -1
- package/src/lib/update-service.ts +4 -6
- package/.next/server/chunks/229.js +0 -1
- package/.next/server/chunks/393.js +0 -1
- package/.next/server/chunks/421.js +0 -1
- package/.next/server/chunks/496.js +0 -1
- package/.next/server/chunks/662.js +0 -2
- package/.next/server/chunks/686.js +0 -1
- package/.next/server/chunks/802.js +0 -2
- package/.next/server/chunks/807.js +0 -1
- package/.next/server/chunks/924.js +0 -6
- package/.next/static/chunks/2716.7ad073038e94a802.js +0 -1
- package/.next/static/chunks/4673.4cf66cbdd8364c57.js +0 -1
- package/.next/static/chunks/4686-09fcd0028079006c.js +0 -1
- package/.next/static/chunks/7411.67366dc5032a141d.js +0 -1
- package/.next/static/chunks/app/_global-error/page-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/config/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/files/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/fs/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/git/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/github/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/growth/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/health/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/models/defaults/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/models/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/remote/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/security/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/sessions/import/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/skills/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/update/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/version/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/page-fc4ed2ff29b6235e.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/webpack-22ebffc20adb44ae.js +0 -1
- package/.next/static/css/6671d0d1589fdc0e.css +0 -1
- /package/.next/static/{WMazYvD-gbeLsCUfUxvvM → lvKCJmjFj8LuqSojRVLRF}/_buildManifest.js +0 -0
- /package/.next/static/{WMazYvD-gbeLsCUfUxvvM → lvKCJmjFj8LuqSojRVLRF}/_ssgManifest.js +0 -0
package/scripts/release.mjs
CHANGED
|
@@ -7,6 +7,8 @@ import { pathToFileURL, fileURLToPath } from "node:url";
|
|
|
7
7
|
import { assertInstallation, assertLocalPath, readProductionBuild, resolveBuildOutput, RELEASES_DIR, RELEASE_MANIFEST } from "./build-output.mjs";
|
|
8
8
|
import { runCommand } from "./process-runner.mjs";
|
|
9
9
|
import { isMainModule } from "./entrypoint.mjs";
|
|
10
|
+
import { installPiRuntime, PI_PACKAGE } from "./runtime/pi-packages.mjs";
|
|
11
|
+
export { PI_PACKAGE, PI_AI_PACKAGE } from "./runtime/pi-packages.mjs";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Root of the PiWeb installation this module belongs to.
|
|
@@ -92,9 +94,6 @@ export async function verifyReleaseConfig(root = process.cwd(), env = process.en
|
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
export const PI_PACKAGE = "@earendil-works/pi-coding-agent";
|
|
96
|
-
export const PI_AI_PACKAGE = "@earendil-works/pi-ai";
|
|
97
|
-
|
|
98
97
|
/**
|
|
99
98
|
* Shared by the UI and update:pi; called only after the building state is durable.
|
|
100
99
|
* @param {{root: string, env: Record<string, string | undefined>, run: typeof runCommand, signal?: AbortSignal}} context
|
|
@@ -102,10 +101,7 @@ export const PI_AI_PACKAGE = "@earendil-works/pi-ai";
|
|
|
102
101
|
* @returns {Promise<string>}
|
|
103
102
|
*/
|
|
104
103
|
export async function installPiUpdate({ root, env, run, signal }, assertIdle = async () => {}) {
|
|
105
|
-
await assertIdle
|
|
106
|
-
await run("npm", ["install", "--save-exact", `${PI_PACKAGE}@latest`, `${PI_AI_PACKAGE}@latest`, "--no-audit", "--no-fund"], {
|
|
107
|
-
cwd: root, env, timeoutMs: 600_000, signal,
|
|
108
|
-
});
|
|
104
|
+
await installPiRuntime({ root, env, run, signal }, { assertIdle });
|
|
109
105
|
await run("npm", ["run", "sync:provider-icons"], { cwd: root, env, timeoutMs: 60_000, signal });
|
|
110
106
|
const manifest = JSON.parse(await fs.readFile(path.join(root, "node_modules", PI_PACKAGE, "package.json"), "utf8"));
|
|
111
107
|
if (typeof manifest.version !== "string" || !manifest.version) throw new Error("Pi engine package version is missing");
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Exercise the SDK and production startup inside the freshly installed artifact. */
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import http from "node:http";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import next from "next";
|
|
8
|
+
import { isMainModule } from "../entrypoint.mjs";
|
|
9
|
+
import { PI_RUNTIME_PACKAGES, verifyPiRuntimePins } from "./pi-packages.mjs";
|
|
10
|
+
|
|
11
|
+
export async function checkInstalledRuntime(root = process.cwd()) {
|
|
12
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
13
|
+
const version = verifyPiRuntimePins(manifest);
|
|
14
|
+
for (const name of PI_RUNTIME_PACKAGES) {
|
|
15
|
+
let dir = path.dirname(fileURLToPath(import.meta.resolve(name)));
|
|
16
|
+
while (!fs.existsSync(path.join(dir, "package.json"))) {
|
|
17
|
+
const parent = path.dirname(dir);
|
|
18
|
+
if (parent === dir) throw new Error("package.json missing: " + name);
|
|
19
|
+
dir = parent;
|
|
20
|
+
}
|
|
21
|
+
const actual = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")).version;
|
|
22
|
+
if (actual !== version) throw new Error(name + " resolved to " + actual + ", expected " + version);
|
|
23
|
+
}
|
|
24
|
+
await import("@earendil-works/pi-coding-agent");
|
|
25
|
+
await import("@earendil-works/pi-ai");
|
|
26
|
+
|
|
27
|
+
const app = next({ dev: false, dir: root });
|
|
28
|
+
const server = http.createServer(app.getRequestHandler());
|
|
29
|
+
const routes = ["/api/health", "/api/version", "/api/models", "/api/sessions", "/api/workspaces", "/api/pi-settings"];
|
|
30
|
+
try {
|
|
31
|
+
await app.prepare();
|
|
32
|
+
await new Promise((resolve, reject) => {
|
|
33
|
+
server.once("error", reject);
|
|
34
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
35
|
+
});
|
|
36
|
+
const base = "http://127.0.0.1:" + server.address().port;
|
|
37
|
+
for (const route of routes) {
|
|
38
|
+
const response = await fetch(base + route, { signal: AbortSignal.timeout(30_000) });
|
|
39
|
+
const body = await response.json();
|
|
40
|
+
if (!response.ok || body.success !== true) {
|
|
41
|
+
throw new Error(route + " failed: " + response.status + " " + JSON.stringify(body));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const page = await fetch(base + "/", { signal: AbortSignal.timeout(30_000) });
|
|
45
|
+
if (!page.ok || !(page.headers.get("content-type") ?? "").includes("text/html") || (await page.text()).length < 500) {
|
|
46
|
+
throw new Error("production home page failed: " + page.status);
|
|
47
|
+
}
|
|
48
|
+
return { version, routes, homePage: true };
|
|
49
|
+
} finally {
|
|
50
|
+
if (server.listening) {
|
|
51
|
+
server.closeAllConnections();
|
|
52
|
+
await new Promise((resolve) => server.close(resolve));
|
|
53
|
+
}
|
|
54
|
+
await app.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (isMainModule(import.meta.url)) {
|
|
59
|
+
try {
|
|
60
|
+
console.log(JSON.stringify(await checkInstalledRuntime()));
|
|
61
|
+
process.exit(0);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(error);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Pi's packages are released together and must resolve to the same runtime version. */
|
|
2
|
+
export const PI_PACKAGE = "@earendil-works/pi-coding-agent";
|
|
3
|
+
export const PI_AI_PACKAGE = "@earendil-works/pi-ai";
|
|
4
|
+
export const PI_RUNTIME_PACKAGES = Object.freeze([
|
|
5
|
+
PI_PACKAGE, PI_AI_PACKAGE,
|
|
6
|
+
"@earendil-works/pi-agent-core",
|
|
7
|
+
"@earendil-works/pi-codemode",
|
|
8
|
+
"@earendil-works/pi-mcp",
|
|
9
|
+
"@earendil-works/pi-telemetry",
|
|
10
|
+
"@earendil-works/pi-tui",
|
|
11
|
+
"@earendil-works/chord",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
/** @param {string} version */
|
|
15
|
+
export function piRuntimeSpecs(version) {
|
|
16
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
17
|
+
throw new Error("Pi runtime requires an exact version; received " + JSON.stringify(version));
|
|
18
|
+
}
|
|
19
|
+
return PI_RUNTIME_PACKAGES.map((name) => name + "@" + version);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The repository lockfile is not used when a user installs a published package. */
|
|
23
|
+
export function verifyPiRuntimePins(manifest) {
|
|
24
|
+
const version = manifest.dependencies?.[PI_PACKAGE];
|
|
25
|
+
piRuntimeSpecs(version);
|
|
26
|
+
for (const name of PI_RUNTIME_PACKAGES) {
|
|
27
|
+
if (manifest.dependencies?.[name] !== version) {
|
|
28
|
+
throw new Error("Pi runtime dependency " + name + " must be pinned to " + version
|
|
29
|
+
+ "; a published package can otherwise mix incompatible Pi versions");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return version;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the release once, then install every Pi package at that exact version.
|
|
37
|
+
* Separate @latest lookups can mix releases while upstream is publishing.
|
|
38
|
+
* @param {{root: string, env: Record<string, string | undefined>, run: typeof import("../process-runner.mjs").runCommand, signal?: AbortSignal}} context
|
|
39
|
+
* @param {{omitDev?: boolean, assertIdle?: () => Promise<void>}} [options]
|
|
40
|
+
*/
|
|
41
|
+
export async function installPiRuntime({ root, env, run, signal }, { omitDev = false, assertIdle = async () => {} } = {}) {
|
|
42
|
+
await assertIdle();
|
|
43
|
+
const version = (await run("npm", ["view", PI_PACKAGE + "@latest", "version"], {
|
|
44
|
+
cwd: root, env, timeoutMs: 30_000, signal,
|
|
45
|
+
})).trim();
|
|
46
|
+
const specs = piRuntimeSpecs(version);
|
|
47
|
+
await assertIdle();
|
|
48
|
+
await run("npm", ["install", "--save-exact", ...specs, ...(omitDev ? ["--omit=dev"] : []), "--no-audit", "--no-fund"], {
|
|
49
|
+
cwd: root, env, timeoutMs: 600_000, signal,
|
|
50
|
+
});
|
|
51
|
+
return version;
|
|
52
|
+
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Release gate for the prebuilt artifact: pack the package exactly as
|
|
4
4
|
* `npm publish` would, unpack it somewhere else, and validate the copy with the
|
|
5
|
-
* same check the launcher runs before starting.
|
|
5
|
+
* same check the launcher runs before starting. The CLI also installs the tarball
|
|
6
|
+
* into an empty global prefix and imports the real Pi SDK.
|
|
6
7
|
*
|
|
7
8
|
* `files` is a whitelist, so enumerating Next internals by hand ships a package
|
|
8
9
|
* that installs cleanly and then dies on first start with a missing manifest
|
|
@@ -16,6 +17,7 @@ import { spawnSync } from "node:child_process";
|
|
|
16
17
|
import { fileURLToPath } from "node:url";
|
|
17
18
|
import { readProductionBuild } from "./build-output.mjs";
|
|
18
19
|
import { isMainModule } from "./entrypoint.mjs";
|
|
20
|
+
import { PI_PACKAGE, verifyPiRuntimePins } from "./runtime/pi-packages.mjs";
|
|
19
21
|
|
|
20
22
|
const DEFAULT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
21
23
|
|
|
@@ -31,17 +33,36 @@ function run(command, args, options = {}) {
|
|
|
31
33
|
* as well as unsafe with paths containing spaces. Whenever npm invoked us (npm test,
|
|
32
34
|
* npm run verify:package, CI) it hands us its own CLI entry, so run that with node.
|
|
33
35
|
*/
|
|
34
|
-
function runNpm(args,
|
|
35
|
-
const cli = process.env.npm_execpath
|
|
36
|
-
|
|
37
|
-
return run(
|
|
36
|
+
function runNpm(args, options) {
|
|
37
|
+
const cli = [process.env.npm_execpath, path.join(path.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")]
|
|
38
|
+
.find((file) => file?.endsWith(".js") && fs.existsSync(file));
|
|
39
|
+
if (cli) return run(process.execPath, [cli, ...args], options);
|
|
40
|
+
return run("npm", args, { ...options, shell: process.platform === "win32" });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Install the tarball into an empty global prefix; checkout node_modules cannot hide drift. */
|
|
44
|
+
function verifyInstalledRuntime(tarball, tmp, manifest, log) {
|
|
45
|
+
const prefix = path.join(tmp, "installed");
|
|
46
|
+
const env = { ...process.env, PI_CODING_AGENT_DIR: path.join(tmp, "agent"), PI_OFFLINE: "1" };
|
|
47
|
+
runNpm(["install", "--global", "--prefix", prefix, "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund", tarball], {
|
|
48
|
+
cwd: tmp, env, timeout: 600_000,
|
|
49
|
+
});
|
|
50
|
+
const modules = process.platform === "win32" ? path.join(prefix, "node_modules") : path.join(prefix, "lib", "node_modules");
|
|
51
|
+
const installed = path.join(modules, ...manifest.name.split("/"));
|
|
52
|
+
run(process.execPath, [path.join(installed, "scripts", "runtime", "check-installed.mjs")], {
|
|
53
|
+
cwd: installed, timeout: 180_000,
|
|
54
|
+
env: { ...env, NODE_ENV: "production", PI_WEB_ROOT: installed, PIWEB_REMOTE_LAN: "0", PI_WEB_AUTH: "0", PI_WEB_UPDATE_CHECK: "0", NEXT_TELEMETRY_DISABLED: "1" },
|
|
55
|
+
});
|
|
56
|
+
log("[piweb] Clean global installation: Pi " + verifyPiRuntimePins(manifest) + " SDK, production startup, home page and first-paint APIs passed");
|
|
38
57
|
}
|
|
39
58
|
|
|
40
59
|
/**
|
|
41
60
|
* @param {string} root
|
|
42
61
|
* @returns {{filename: string, files: string[], buildId: string, bytes: number}}
|
|
43
62
|
*/
|
|
44
|
-
export function verifyPackedPackage(root, { log = console.log } = {}) {
|
|
63
|
+
export function verifyPackedPackage(root, { log = console.log, runtime = false } = {}) {
|
|
64
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
65
|
+
if (manifest.dependencies?.[PI_PACKAGE]) verifyPiRuntimePins(manifest);
|
|
45
66
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "piweb-verify-"));
|
|
46
67
|
try {
|
|
47
68
|
const packed = JSON.parse(runNpm(["pack", "--json", "--pack-destination", tmp], { cwd: root }));
|
|
@@ -58,6 +79,7 @@ export function verifyPackedPackage(root, { log = console.log } = {}) {
|
|
|
58
79
|
run("tar", ["-xzf", entry.filename, "-C", "unpacked"], { cwd: tmp });
|
|
59
80
|
// Same validation the launcher applies to an installed package.
|
|
60
81
|
const build = readProductionBuild(path.join(unpacked, "package"), ".next");
|
|
82
|
+
if (runtime) verifyInstalledRuntime(path.join(tmp, entry.filename), tmp, manifest, log);
|
|
61
83
|
const bytes = fs.statSync(path.join(tmp, entry.filename)).size;
|
|
62
84
|
log(`[piweb] ${entry.filename}: ${files.length} files, ${(bytes / 1024 / 1024).toFixed(1)} MB, build ${build.buildId}`);
|
|
63
85
|
return { filename: entry.filename, files, buildId: build.buildId, bytes };
|
|
@@ -68,7 +90,7 @@ export function verifyPackedPackage(root, { log = console.log } = {}) {
|
|
|
68
90
|
|
|
69
91
|
if (isMainModule(import.meta.url)) {
|
|
70
92
|
try {
|
|
71
|
-
verifyPackedPackage(DEFAULT_ROOT);
|
|
93
|
+
verifyPackedPackage(DEFAULT_ROOT, { runtime: true });
|
|
72
94
|
} catch (error) {
|
|
73
95
|
console.error(`[piweb] ${error instanceof Error ? error.message : error}`);
|
|
74
96
|
process.exitCode = 1;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { BoundaryError, resolveSessionPath } from "@/lib/path-security";
|
|
3
|
+
import { parseSideChatRequest } from "@/lib/side-chat/context";
|
|
4
|
+
import { pollSideChat, startSideChat, stopSideChat } from "@/lib/side-chat/service";
|
|
5
|
+
import { isSideChatId, SIDE_CHAT_BODY_LIMIT } from "@/lib/side-chat/types";
|
|
6
|
+
|
|
7
|
+
export const dynamic = "force-dynamic";
|
|
8
|
+
export const runtime = "nodejs";
|
|
9
|
+
type RouteParams = { params: Promise<{ id: string }> };
|
|
10
|
+
const headers = { "Cache-Control": "no-store" };
|
|
11
|
+
|
|
12
|
+
function failure(error: unknown): NextResponse {
|
|
13
|
+
return NextResponse.json({ success: false, error: error instanceof Error ? error.message : "side-chat request failed" }, {
|
|
14
|
+
status: error instanceof BoundaryError || error instanceof SyntaxError ? 400 : 500, headers,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function readBody(req: Request): Promise<unknown> {
|
|
19
|
+
if (!req.body) throw new BoundaryError("missing side-chat request");
|
|
20
|
+
const reader = req.body.getReader();
|
|
21
|
+
const chunks: Uint8Array[] = [];
|
|
22
|
+
let size = 0;
|
|
23
|
+
try {
|
|
24
|
+
for (;;) {
|
|
25
|
+
const chunk = await reader.read();
|
|
26
|
+
if (chunk.done) break;
|
|
27
|
+
size += chunk.value.byteLength;
|
|
28
|
+
if (size > SIDE_CHAT_BODY_LIMIT) {
|
|
29
|
+
await reader.cancel();
|
|
30
|
+
throw new BoundaryError("side-chat history is too large; start a new side chat");
|
|
31
|
+
}
|
|
32
|
+
chunks.push(chunk.value);
|
|
33
|
+
}
|
|
34
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
35
|
+
} finally {
|
|
36
|
+
reader.releaseLock();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function POST(req: Request, { params }: RouteParams) {
|
|
41
|
+
try {
|
|
42
|
+
const { id } = await params;
|
|
43
|
+
const sessionPath = await resolveSessionPath(id, { allowPending: true });
|
|
44
|
+
const request = parseSideChatRequest(await readBody(req));
|
|
45
|
+
if (req.signal.aborted) throw new BoundaryError("side-chat request was cancelled");
|
|
46
|
+
return NextResponse.json({ success: true, data: startSideChat(sessionPath, request) }, { headers });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return failure(error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function GET(req: Request, { params }: RouteParams) {
|
|
53
|
+
try {
|
|
54
|
+
const { id } = await params;
|
|
55
|
+
const sessionPath = await resolveSessionPath(id, { allowPending: true });
|
|
56
|
+
const query = new URL(req.url).searchParams;
|
|
57
|
+
const requestId = query.get("requestId");
|
|
58
|
+
const after = Number(query.get("after") ?? "-1");
|
|
59
|
+
if (!isSideChatId(requestId) || !Number.isSafeInteger(after) || after < -1) throw new BoundaryError("invalid side-chat cursor");
|
|
60
|
+
return NextResponse.json({ success: true, data: await pollSideChat(sessionPath, requestId, after, req.signal) }, { headers });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return failure(error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function DELETE(req: Request, { params }: RouteParams) {
|
|
67
|
+
try {
|
|
68
|
+
const { id } = await params;
|
|
69
|
+
const sessionPath = await resolveSessionPath(id, { allowPending: true });
|
|
70
|
+
const requestId = new URL(req.url).searchParams.get("requestId");
|
|
71
|
+
if (!isSideChatId(requestId)) throw new BoundaryError("invalid side-chat id");
|
|
72
|
+
stopSideChat(sessionPath, requestId);
|
|
73
|
+
return NextResponse.json({ success: true }, { headers });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return failure(error);
|
|
76
|
+
}
|
|
77
|
+
}
|