@rexvane/piweb 0.3.16 → 0.3.17
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 -11
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +6 -6
- package/.next/react-loadable-manifest.json +4 -16
- package/.next/required-server-files.js +1 -3
- package/.next/required-server-files.json +1 -3
- package/.next/routes-manifest.json +0 -20
- package/.next/server/app/_global-error/page.js +2 -2
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +4 -7
- package/.next/server/app/_not-found.segments/_full.segment.rsc +4 -7
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -5
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -5
- 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]/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/git/route.js +2 -2
- package/.next/server/app/api/git/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/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/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 +3 -3
- 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 +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/index.html +1 -1
- package/.next/server/app/index.rsc +6 -9
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -7
- package/.next/server/app/index.segments/_full.segment.rsc +6 -9
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -5
- package/.next/server/app/login/page.js +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 +5 -8
- package/.next/server/app/login.segments/_full.segment.rsc +5 -8
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -5
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -6
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +8 -11
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/532.js +1 -1
- package/.next/server/chunks/686.js +4 -8
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/2608.ec8ff1f5767c9056.js +1 -0
- package/.next/static/chunks/2716.93be8ad5c970c516.js +1 -0
- package/.next/static/chunks/4632.112d2e893d48d188.js +1 -0
- package/.next/static/chunks/4673.c1fb1580efc6ace7.js +1 -0
- package/.next/static/chunks/4686-f4a037d609cea3f2.js +1 -0
- package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/page-b51087831582ee37.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/webpack-c31263fd83d75ce6.js +1 -0
- package/.next/static/css/850c2fd130ffa2fc.css +1 -0
- package/.next/static/zsynuO_9Kx6nrXgkTUJ04/_buildManifest.js +1 -0
- package/.next/types/routes.d.ts +1 -4
- package/.next/types/validator.ts +0 -27
- package/README.md +17 -29
- package/assets/archive.jpg +0 -0
- package/assets/growth-1.jpg +0 -0
- package/assets/growth-2.jpg +0 -0
- package/assets/import.jpg +0 -0
- package/assets/main.jpg +0 -0
- package/assets/outline.jpg +0 -0
- package/assets/prompts-1.jpg +0 -0
- package/assets/prompts-2.jpg +0 -0
- package/assets/providers-1.jpg +0 -0
- package/assets/providers-2.jpg +0 -0
- package/assets/providers-3.jpg +0 -0
- package/assets/trace-1.jpg +0 -0
- package/assets/trace-2.png +0 -0
- package/assets/trace-3.png +0 -0
- package/assets/trace-4.png +0 -0
- package/assets/viewer-1.jpg +0 -0
- package/assets/viewer-2.jpg +0 -0
- package/bin/pi.js +50 -0
- package/next.config.ts +32 -7
- package/package.json +4 -9
- package/scripts/launcher.mjs +1 -1
- package/scripts/pick-folder.ps1 +2 -11
- package/scripts/release.mjs +31 -11
- package/scripts/verify-package.mjs +0 -53
- package/src/app/api/config/route.ts +1 -4
- package/src/app/api/growth/route.ts +14 -14
- package/src/app/api/models/route.ts +2 -1
- package/src/app/api/security/route.ts +3 -8
- package/src/app/api/update/route.ts +2 -8
- package/src/app/globals.css +90 -777
- package/src/app/layout.tsx +6 -13
- package/src/components/AppShell.tsx +229 -521
- package/src/components/ChatInput.tsx +26 -153
- package/src/components/ChatWindow.tsx +231 -51
- package/src/components/ContextMeter.tsx +17 -11
- package/src/components/FileViewer.tsx +11 -10
- package/src/components/GitPanel.tsx +11 -13
- package/src/components/ModelSelector.tsx +6 -19
- package/src/components/PiMark.tsx +38 -5
- package/src/components/ProjectPanel.tsx +111 -95
- package/src/components/PromptPanel.tsx +51 -132
- package/src/components/SessionImportSection.tsx +37 -53
- package/src/components/SessionSidebar.tsx +57 -159
- package/src/components/SettingsPanel.tsx +193 -330
- package/src/components/icons.tsx +0 -23
- package/src/hooks/useGrowth.ts +123 -156
- package/src/hooks/usePiWeb.ts +15 -122
- package/src/i18n.tsx +42 -98
- package/src/lib/agent-manager.ts +21 -187
- package/src/lib/command-validation.ts +1 -9
- package/src/lib/growth-service.ts +390 -166
- package/src/lib/growth-tracker.ts +64 -264
- package/src/lib/growth-turns.ts +31 -1
- package/src/lib/models/model-scope.ts +15 -0
- package/src/lib/models-service.ts +16 -0
- package/src/lib/pi-settings.ts +7 -0
- package/src/lib/pi.ts +2 -3
- package/src/lib/session-import/claude.ts +2 -13
- package/src/lib/session-import/codex.ts +8 -44
- package/src/lib/session-import/files.ts +0 -14
- package/src/lib/session-import/grok.ts +24 -54
- package/src/lib/session-import/index.ts +0 -5
- package/src/lib/session-import/types.ts +0 -12
- package/src/lib/session-import/writer.ts +3 -8
- package/src/lib/skills-service.ts +9 -16
- package/src/lib/theme.ts +1 -68
- package/src/lib/trajectory.ts +0 -1
- package/src/lib/types.ts +27 -36
- package/tsconfig.json +2 -1
- package/.next/server/app/api/swarm/[id]/route.js +0 -1
- package/.next/server/app/api/swarm/[id]/route.js.nft.json +0 -1
- package/.next/server/app/api/swarm/[id]/route_client-reference-manifest.js +0 -1
- package/.next/server/app/api/swarm/route.js +0 -1
- package/.next/server/app/api/swarm/route.js.nft.json +0 -1
- package/.next/server/app/api/swarm/route_client-reference-manifest.js +0 -1
- package/.next/server/app/api/tests/route.js +0 -2
- package/.next/server/app/api/tests/route.js.nft.json +0 -1
- package/.next/server/app/api/tests/route_client-reference-manifest.js +0 -1
- package/.next/server/chunks/757.js +0 -1
- package/.next/server/chunks/775.js +0 -3
- package/.next/static/0t1jBUAuVaTFTXxRyIo3q/_buildManifest.js +0 -1
- package/.next/static/chunks/2608.43c58236c9cce8e6.js +0 -1
- package/.next/static/chunks/2716.4ec42df1d51354c9.js +0 -1
- package/.next/static/chunks/4632.62139b565b657aac.js +0 -1
- package/.next/static/chunks/4673.7796104395deb8d7.js +0 -1
- package/.next/static/chunks/4686-17f21a006739a596.js +0 -1
- package/.next/static/chunks/7577.10af9bb661652da8.js +0 -1
- package/.next/static/chunks/8866.38ec4ac4ee5a1b74.js +0 -1
- package/.next/static/chunks/app/_global-error/page-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/config/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/files/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/git/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/growth/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/health/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/models/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/security/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/sessions/import/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/skills/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/swarm/[id]/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/swarm/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/tests/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/update/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/version/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/page-14379b8060d5cf07.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/webpack-d1a69ad9edb38b11.js +0 -1
- package/.next/static/css/d0a0162cb481abe8.css +0 -1
- package/.next/types/app/api/swarm/[id]/route.ts +0 -353
- package/.next/types/app/api/swarm/route.ts +0 -353
- package/.next/types/app/api/tests/route.ts +0 -353
- package/assets/archive.png +0 -0
- package/assets/growth-1.png +0 -0
- package/assets/growth-2.png +0 -0
- package/assets/import.png +0 -0
- package/assets/main.png +0 -0
- package/assets/outline.png +0 -0
- package/assets/prompts-1.png +0 -0
- package/assets/prompts-2.png +0 -0
- package/assets/providers-1.png +0 -0
- package/assets/providers-2.png +0 -0
- package/assets/providers-3.png +0 -0
- package/assets/trace-1.png +0 -0
- package/assets/viewer-1.png +0 -0
- package/assets/viewer-2.png +0 -0
- package/scripts/managed-niubash-assets.mjs +0 -146
- package/scripts/prepare-niubash-packages.mjs +0 -182
- package/scripts/run-test-file.mjs +0 -34
- package/scripts/smoke-managed-niubash.mjs +0 -62
- package/scripts/verify-release-config.mjs +0 -24
- package/src/app/api/swarm/[id]/route.ts +0 -28
- package/src/app/api/swarm/route.ts +0 -24
- package/src/app/api/tests/route.ts +0 -27
- package/src/components/SwarmCoordinatorPod.tsx +0 -1021
- package/src/components/TestInspectorPod.tsx +0 -549
- package/src/lib/delegation.ts +0 -93
- package/src/lib/growth-rounds.ts +0 -84
- package/src/lib/managed-niubash.ts +0 -231
- package/src/lib/runtime-activity.ts +0 -27
- package/src/lib/session-import/lock.ts +0 -25
- package/src/lib/swarm-service.ts +0 -371
- package/src/lib/test-run-service.ts +0 -122
- package/src/lib/workflow.ts +0 -55
- /package/.next/static/{0t1jBUAuVaTFTXxRyIo3q → zsynuO_9Kx6nrXgkTUJ04}/_ssgManifest.js +0 -0
package/src/lib/growth-rounds.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import type { GrowthStep } from "./types";
|
|
2
|
-
|
|
3
|
-
export interface GrowthRound {
|
|
4
|
-
/** 真实用户轮次;不会因没有文件改动而跳号。 */
|
|
5
|
-
id: number;
|
|
6
|
-
startedAt: number;
|
|
7
|
-
first: GrowthStep | null;
|
|
8
|
-
/** 此轮结束时可用的最后一张快照;无改动轮沿用前一张。 */
|
|
9
|
-
last: GrowthStep | null;
|
|
10
|
-
steps: GrowthStep[];
|
|
11
|
-
/** 相邻两轮终点的 tree;第一轮从 prompt 前的基线开始。 */
|
|
12
|
-
fromTree: string | null;
|
|
13
|
-
toTree: string | null;
|
|
14
|
-
/** 旧会话可能在启用生长账本前已发生,不能假装它是零改动。 */
|
|
15
|
-
recorded: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** 兼容没有消息时间戳的调用者:按账本里的 turn 标记分组。 */
|
|
19
|
-
function ledgerRounds(steps: GrowthStep[]): GrowthRound[] {
|
|
20
|
-
const rounds: GrowthRound[] = [];
|
|
21
|
-
let current: GrowthStep[] = [];
|
|
22
|
-
const flush = () => {
|
|
23
|
-
if (!current.length) return;
|
|
24
|
-
const first = current[0];
|
|
25
|
-
const last = current[current.length - 1];
|
|
26
|
-
rounds.push({ id: rounds.length + 1, startedAt: first.ts, first, last, steps: current, fromTree: first.parent, toTree: last.tree, recorded: true });
|
|
27
|
-
current = [];
|
|
28
|
-
};
|
|
29
|
-
for (const [index, step] of steps.entries()) {
|
|
30
|
-
if (index === 0 && step.initial) continue;
|
|
31
|
-
if (step.kind === "baseline") {
|
|
32
|
-
flush();
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
if (step.kind === "external" || step.kind === "manual") flush();
|
|
36
|
-
current.push(step);
|
|
37
|
-
if (step.kind === "turn" || step.kind === "external" || step.kind === "manual") flush();
|
|
38
|
-
}
|
|
39
|
-
flush();
|
|
40
|
-
return rounds;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** 轮数以持久会话的用户消息为准;快照只负责证明这一轮的前后树与差异。 */
|
|
44
|
-
export function buildGrowthRounds(steps: GrowthStep[], turnStarts?: number[]): GrowthRound[] {
|
|
45
|
-
if (turnStarts === undefined) return ledgerRounds(steps);
|
|
46
|
-
if (!turnStarts.length) return [];
|
|
47
|
-
const starts: number[] = [];
|
|
48
|
-
for (const value of turnStarts) {
|
|
49
|
-
const previous = starts[starts.length - 1];
|
|
50
|
-
starts.push(Number.isFinite(value) ? Math.max(value, previous === undefined ? value : previous + 0.001) : (previous ?? 0) + 0.001);
|
|
51
|
-
}
|
|
52
|
-
const ordered = [...steps].sort((a, b) => a.ts - b.ts || a.seq - b.seq);
|
|
53
|
-
const rounds: GrowthRound[] = [];
|
|
54
|
-
let cursor = 0;
|
|
55
|
-
let before: GrowthStep | null = null;
|
|
56
|
-
let previousEnd: GrowthStep | null = null;
|
|
57
|
-
for (let index = 0; index < starts.length; index += 1) {
|
|
58
|
-
const start = starts[index];
|
|
59
|
-
const end = starts[index + 1] ?? Infinity;
|
|
60
|
-
while (cursor < ordered.length && ordered[cursor].ts < start) before = ordered[cursor++];
|
|
61
|
-
const atStart = before;
|
|
62
|
-
const current: GrowthStep[] = [];
|
|
63
|
-
while (cursor < ordered.length && ordered[cursor].ts < end) {
|
|
64
|
-
const step = ordered[cursor++];
|
|
65
|
-
if (step.kind !== "baseline") current.push(step);
|
|
66
|
-
before = step;
|
|
67
|
-
}
|
|
68
|
-
const first = current[0] ?? null;
|
|
69
|
-
const last: GrowthStep | null = current[current.length - 1] ?? (atStart && (!previousEnd || atStart.ts > previousEnd.ts) ? atStart : previousEnd);
|
|
70
|
-
const fromTree = previousEnd?.tree ?? atStart?.tree ?? first?.parent ?? null;
|
|
71
|
-
rounds.push({
|
|
72
|
-
id: index + 1,
|
|
73
|
-
startedAt: start,
|
|
74
|
-
first,
|
|
75
|
-
last,
|
|
76
|
-
steps: current,
|
|
77
|
-
fromTree,
|
|
78
|
-
toTree: last?.tree ?? null,
|
|
79
|
-
recorded: current.length > 0 && fromTree !== null && !first?.initial,
|
|
80
|
-
});
|
|
81
|
-
previousEnd = last;
|
|
82
|
-
}
|
|
83
|
-
return rounds;
|
|
84
|
-
}
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import * as nodeModule from "node:module";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { createBashToolDefinition, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { APP_ROOT } from "./app-root";
|
|
7
|
-
|
|
8
|
-
const DISTRIBUTION_VERSION = "1.1.4-piweb.0";
|
|
9
|
-
const NIUBASH_VERSION = "1.1.4";
|
|
10
|
-
const SOURCE_REPOSITORY = "https://github.com/unixwin/niubash";
|
|
11
|
-
const SOURCE_TAG = "v1.1.4";
|
|
12
|
-
|
|
13
|
-
const PACKAGE_BY_ARCH = {
|
|
14
|
-
x64: {
|
|
15
|
-
packageName: "@rexvane/piweb-niubash-win32-x64",
|
|
16
|
-
directory: "piweb-niubash-win32-x64",
|
|
17
|
-
asset: "niubash-v1.1.4-win-x64.zip",
|
|
18
|
-
assetSha256: "9f543634288a316f7ed8e5a76ff794eca402cb21e74b8f8a551daffafde162f5",
|
|
19
|
-
machine: 0x8664,
|
|
20
|
-
},
|
|
21
|
-
arm64: {
|
|
22
|
-
packageName: "@rexvane/piweb-niubash-win32-arm64",
|
|
23
|
-
directory: "piweb-niubash-win32-arm64",
|
|
24
|
-
asset: "niubash-v1.1.4-win-arm64.zip",
|
|
25
|
-
assetSha256: "77e98ba8d7f6be70c35388bcc28bdbc883c06bcaafffbe186a88788ad32ccf00",
|
|
26
|
-
machine: 0xaa64,
|
|
27
|
-
},
|
|
28
|
-
} as const;
|
|
29
|
-
|
|
30
|
-
const LICENSE_HASHES = {
|
|
31
|
-
"NIUBASH.txt": "1290aae99e71239ee3154e7847830e49e7a36c758fa6d659d73fa752dc9f81c8",
|
|
32
|
-
"RUBASH.txt": "e426a75a20633f3088da22aef61a5a44d06f1f884c600f6011d00702c3989079",
|
|
33
|
-
"WINUXCMD.txt": "8ffc4ed56f5e6ab8183a7ece2e3aa6ea4800b0a4e8170d11bd60856f2dd760f3",
|
|
34
|
-
} as const;
|
|
35
|
-
|
|
36
|
-
export type SupportedArch = keyof typeof PACKAGE_BY_ARCH;
|
|
37
|
-
|
|
38
|
-
type PackageExport = {
|
|
39
|
-
schema?: unknown;
|
|
40
|
-
packageName?: unknown;
|
|
41
|
-
distributionVersion?: unknown;
|
|
42
|
-
niubashVersion?: unknown;
|
|
43
|
-
platform?: unknown;
|
|
44
|
-
arch?: unknown;
|
|
45
|
-
runtimeDir?: unknown;
|
|
46
|
-
shellPath?: unknown;
|
|
47
|
-
manifestPath?: unknown;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
type LoadedPackage = { entryPath: string; value: unknown };
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* npm 安装(全局或本地)普通情况下由 Node 解析:npm 会把可选包提升到任意祖先
|
|
54
|
-
* node_modules,所以解析必须从包自己的 package.json 出发。仓库检出(开发 / CI /
|
|
55
|
-
* 发布前的 runtime 准备)没有安装这一步,此时按约定找 platform-packages/<dir>——
|
|
56
|
-
* 这与平台包发布后的目录布局一一对应,不引入第二套解析规则。
|
|
57
|
-
*/
|
|
58
|
-
export function resolveManagedNiubashPackageEntry(packageName: string, arch: SupportedArch, root = APP_ROOT): LoadedPackage {
|
|
59
|
-
// 必须通过命名空间调用。webpack 会改写从 node:module 具名导入的 createRequire:
|
|
60
|
-
// 参数不是字符串字面量时把调用擦掉,运行时变成对 undefined 调用 resolve。
|
|
61
|
-
const loadPackage = nodeModule.createRequire(path.join(root, "package.json"));
|
|
62
|
-
const resolveEntry = loadPackage.resolve.bind(loadPackage);
|
|
63
|
-
try {
|
|
64
|
-
const entryPath = resolveEntry(packageName);
|
|
65
|
-
return { entryPath, value: loadPackage(packageName) };
|
|
66
|
-
} catch (error) {
|
|
67
|
-
const checkoutEntry = path.join(root, "platform-packages", PACKAGE_BY_ARCH[arch].directory, "index.cjs");
|
|
68
|
-
if (fs.existsSync(checkoutEntry)) return { entryPath: checkoutEntry, value: loadPackage(checkoutEntry) };
|
|
69
|
-
throw error;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export type ManagedNiubashRuntime = {
|
|
74
|
-
packageName: string;
|
|
75
|
-
distributionVersion: string;
|
|
76
|
-
niubashVersion: string;
|
|
77
|
-
arch: SupportedArch;
|
|
78
|
-
runtimeDir: string;
|
|
79
|
-
shellPath: string;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export type ManagedNiubashResolveOptions = {
|
|
83
|
-
platform?: string;
|
|
84
|
-
arch?: string;
|
|
85
|
-
/** Base for package resolution; tests and the prepare script point it at a checkout root. */
|
|
86
|
-
root?: string;
|
|
87
|
-
loadPackage?: (packageName: string, arch: SupportedArch) => LoadedPackage;
|
|
88
|
-
warn?: (message: string) => void;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
function sha256File(filename: string): string {
|
|
92
|
-
return crypto.createHash("sha256").update(fs.readFileSync(filename)).digest("hex");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function readJson(filename: string): any {
|
|
96
|
-
const stat = fs.statSync(filename);
|
|
97
|
-
if (!stat.isFile() || stat.size > 2 * 1024 * 1024) throw new Error(`${path.basename(filename)} is missing or too large`);
|
|
98
|
-
return JSON.parse(fs.readFileSync(filename, "utf8"));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function samePath(left: string, right: string): boolean {
|
|
102
|
-
const a = path.resolve(left);
|
|
103
|
-
const b = path.resolve(right);
|
|
104
|
-
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function peMachine(filename: string): number {
|
|
108
|
-
const data = fs.readFileSync(filename);
|
|
109
|
-
if (data.length < 64 || data[0] !== 0x4d || data[1] !== 0x5a) throw new Error(`${path.basename(filename)} is not a PE executable`);
|
|
110
|
-
const offset = data.readUInt32LE(0x3c);
|
|
111
|
-
if (offset + 6 > data.length || data.toString("ascii", offset, offset + 4) !== "PE\0\0") {
|
|
112
|
-
throw new Error(`${path.basename(filename)} has an invalid PE header`);
|
|
113
|
-
}
|
|
114
|
-
return data.readUInt16LE(offset + 4);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function validatePackage(loaded: LoadedPackage, arch: SupportedArch): ManagedNiubashRuntime {
|
|
118
|
-
const expected = PACKAGE_BY_ARCH[arch];
|
|
119
|
-
if (!loaded.value || typeof loaded.value !== "object") throw new Error(`${expected.packageName} has no runtime export`);
|
|
120
|
-
const exported = loaded.value as PackageExport;
|
|
121
|
-
const packageRoot = path.dirname(path.resolve(loaded.entryPath));
|
|
122
|
-
const runtimeDir = path.join(packageRoot, "runtime");
|
|
123
|
-
const shellPath = path.join(runtimeDir, "niu.exe");
|
|
124
|
-
const manifestPath = path.join(packageRoot, "runtime-manifest.json");
|
|
125
|
-
|
|
126
|
-
const checks: Array<[unknown, unknown, string]> = [
|
|
127
|
-
[exported.schema, 1, "export schema"],
|
|
128
|
-
[exported.packageName, expected.packageName, "export package name"],
|
|
129
|
-
[exported.distributionVersion, DISTRIBUTION_VERSION, "export distribution version"],
|
|
130
|
-
[exported.niubashVersion, NIUBASH_VERSION, "export niubash version"],
|
|
131
|
-
[exported.platform, "win32", "export platform"],
|
|
132
|
-
[exported.arch, arch, "export architecture"],
|
|
133
|
-
];
|
|
134
|
-
for (const [actual, wanted, label] of checks) {
|
|
135
|
-
if (actual !== wanted) throw new Error(`${label} mismatch`);
|
|
136
|
-
}
|
|
137
|
-
if (typeof exported.runtimeDir !== "string" || !samePath(exported.runtimeDir, runtimeDir)) throw new Error("runtime directory escapes its package");
|
|
138
|
-
if (typeof exported.shellPath !== "string" || !samePath(exported.shellPath, shellPath)) throw new Error("shell path escapes its package");
|
|
139
|
-
if (typeof exported.manifestPath !== "string" || !samePath(exported.manifestPath, manifestPath)) throw new Error("manifest path escapes its package");
|
|
140
|
-
|
|
141
|
-
const packageInfo = readJson(path.join(packageRoot, "package.json"));
|
|
142
|
-
if (packageInfo.name !== expected.packageName || packageInfo.version !== DISTRIBUTION_VERSION) throw new Error("package identity mismatch");
|
|
143
|
-
if (packageInfo.os?.length !== 1 || packageInfo.os[0] !== "win32") throw new Error("package OS restriction mismatch");
|
|
144
|
-
if (packageInfo.cpu?.length !== 1 || packageInfo.cpu[0] !== arch) throw new Error("package CPU restriction mismatch");
|
|
145
|
-
|
|
146
|
-
const manifest = readJson(manifestPath);
|
|
147
|
-
if (manifest.schema !== 1
|
|
148
|
-
|| manifest.source?.repository !== SOURCE_REPOSITORY
|
|
149
|
-
|| manifest.source?.tag !== SOURCE_TAG
|
|
150
|
-
|| manifest.source?.asset !== expected.asset
|
|
151
|
-
|| manifest.source?.sha256 !== expected.assetSha256) {
|
|
152
|
-
throw new Error("runtime provenance mismatch");
|
|
153
|
-
}
|
|
154
|
-
if (!manifest.files || typeof manifest.files !== "object" || Array.isArray(manifest.files)) throw new Error("runtime file manifest is missing");
|
|
155
|
-
const files = Object.entries(manifest.files) as Array<[string, unknown]>;
|
|
156
|
-
if (files.length < 300 || files.length > 1000) throw new Error(`runtime file count is invalid (${files.length})`);
|
|
157
|
-
for (const [relative, digest] of files) {
|
|
158
|
-
if (!relative || relative.includes("\\") || path.posix.isAbsolute(relative) || /^[A-Za-z]:/.test(relative) || relative.split("/").includes("..")) {
|
|
159
|
-
throw new Error(`unsafe runtime path: ${relative}`);
|
|
160
|
-
}
|
|
161
|
-
if (typeof digest !== "string" || !/^[a-f0-9]{64}$/.test(digest)) throw new Error(`invalid runtime hash: ${relative}`);
|
|
162
|
-
const filename = path.join(runtimeDir, ...relative.split("/"));
|
|
163
|
-
if (!fs.statSync(filename).isFile()) throw new Error(`runtime file is missing: ${relative}`);
|
|
164
|
-
if (sha256File(filename) !== digest) throw new Error(`runtime file failed integrity check: ${relative}`);
|
|
165
|
-
}
|
|
166
|
-
for (const required of ["niu.exe", "winuxcmd/usr/bin/winuxcmd.exe", "bundles/oh-my-niu/bundle.toml"]) {
|
|
167
|
-
if (!(required in manifest.files)) throw new Error(`runtime file is missing: ${required}`);
|
|
168
|
-
}
|
|
169
|
-
for (const executable of [shellPath, path.join(runtimeDir, "winuxcmd", "usr", "bin", "winuxcmd.exe")]) {
|
|
170
|
-
if (peMachine(executable) !== expected.machine) throw new Error(`${path.basename(executable)} architecture mismatch`);
|
|
171
|
-
}
|
|
172
|
-
for (const [name, digest] of Object.entries(LICENSE_HASHES)) {
|
|
173
|
-
if (sha256File(path.join(packageRoot, "licenses", name)) !== digest) throw new Error(`license integrity check failed: ${name}`);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return Object.freeze({
|
|
177
|
-
packageName: expected.packageName,
|
|
178
|
-
distributionVersion: DISTRIBUTION_VERSION,
|
|
179
|
-
niubashVersion: NIUBASH_VERSION,
|
|
180
|
-
arch,
|
|
181
|
-
runtimeDir,
|
|
182
|
-
shellPath,
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function reasonOf(error: unknown, packageName: string): string {
|
|
187
|
-
if ((error as NodeJS.ErrnoException | undefined)?.code === "MODULE_NOT_FOUND") return `${packageName} is not installed`;
|
|
188
|
-
return error instanceof Error ? error.message : String(error);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/** Resolve and fully validate the platform package. Failures deliberately fall back to Pi's normal Bash. */
|
|
192
|
-
export function resolveManagedNiubash(options: ManagedNiubashResolveOptions = {}): ManagedNiubashRuntime | null {
|
|
193
|
-
const platform = options.platform ?? process.platform;
|
|
194
|
-
const arch = options.arch ?? process.arch;
|
|
195
|
-
if (platform !== "win32") return null;
|
|
196
|
-
const expected = PACKAGE_BY_ARCH[arch as SupportedArch];
|
|
197
|
-
if (!expected) {
|
|
198
|
-
(options.warn ?? console.warn)(`[piweb] Managed niubash does not support Windows ${arch}; using Pi's configured/default Bash.`);
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
const supported = arch as SupportedArch;
|
|
202
|
-
try {
|
|
203
|
-
const loaded = options.loadPackage
|
|
204
|
-
? options.loadPackage(expected.packageName, supported)
|
|
205
|
-
: resolveManagedNiubashPackageEntry(expected.packageName, supported, options.root);
|
|
206
|
-
return validatePackage(loaded, supported);
|
|
207
|
-
} catch (error) {
|
|
208
|
-
(options.warn ?? console.warn)(
|
|
209
|
-
`[piweb] Managed niubash is unavailable (${reasonOf(error, expected.packageName)}); using Pi's configured/default Bash. `
|
|
210
|
-
+ "Reinstall @rexvane/piweb to restore the bundled Windows shell (`npm run prepare:niubash` in a checkout).",
|
|
211
|
-
);
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const globalForManagedNiubash = globalThis as typeof globalThis & {
|
|
217
|
-
__piWebManagedNiubash?: { resolved: ManagedNiubashRuntime | null };
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
function defaultRuntime(): ManagedNiubashRuntime | null {
|
|
221
|
-
if (!globalForManagedNiubash.__piWebManagedNiubash) {
|
|
222
|
-
globalForManagedNiubash.__piWebManagedNiubash = { resolved: resolveManagedNiubash() };
|
|
223
|
-
}
|
|
224
|
-
return globalForManagedNiubash.__piWebManagedNiubash.resolved;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** Same-name custom tool intentionally replaces Pi's built-in bash only inside PiWeb sessions. */
|
|
228
|
-
export function createManagedNiubashTools(cwd: string, options?: ManagedNiubashResolveOptions): ToolDefinition[] {
|
|
229
|
-
const runtime = options ? resolveManagedNiubash(options) : defaultRuntime();
|
|
230
|
-
return runtime ? [createBashToolDefinition(cwd, { shellPath: runtime.shellPath }) as ToolDefinition] : [];
|
|
231
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
type ActivityKind = "session" | "swarm" | "tests";
|
|
2
|
-
interface Registry { active: Map<symbol, ActivityKind>; maintenance: boolean }
|
|
3
|
-
|
|
4
|
-
// Route bundles must share a process-wide gate, not separate module-local registries.
|
|
5
|
-
const globals = globalThis as typeof globalThis & { __piwebRuntimeActivity?: Registry };
|
|
6
|
-
const registry = globals.__piwebRuntimeActivity ??= { active: new Map(), maintenance: false };
|
|
7
|
-
|
|
8
|
-
export class RuntimeBusyError extends Error {}
|
|
9
|
-
|
|
10
|
-
export function beginActivity(kind: ActivityKind): () => void {
|
|
11
|
-
if (registry.maintenance) throw new RuntimeBusyError("update maintenance is active; wait for the release to finish");
|
|
12
|
-
const token = Symbol(kind);
|
|
13
|
-
registry.active.set(token, kind);
|
|
14
|
-
return () => { registry.active.delete(token); };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function beginMaintenance(): () => void {
|
|
18
|
-
if (registry.maintenance || registry.active.size) {
|
|
19
|
-
throw new RuntimeBusyError("work is active; wait for sessions, swarms and tests to finish before entering the maintenance window");
|
|
20
|
-
}
|
|
21
|
-
registry.maintenance = true;
|
|
22
|
-
let released = false;
|
|
23
|
-
return () => {
|
|
24
|
-
if (!released) registry.maintenance = false;
|
|
25
|
-
released = true;
|
|
26
|
-
};
|
|
27
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import lockfile from "proper-lockfile";
|
|
4
|
-
|
|
5
|
-
const pending = new Map<string, Promise<void>>();
|
|
6
|
-
|
|
7
|
-
export async function withImportLock<T>(agentDir: string, operation: () => Promise<T>): Promise<T> {
|
|
8
|
-
await fs.mkdir(agentDir, { recursive: true });
|
|
9
|
-
const real = await fs.realpath(agentDir);
|
|
10
|
-
const key = process.platform === "win32" ? real.toLowerCase() : real;
|
|
11
|
-
const previous = pending.get(key) ?? Promise.resolve();
|
|
12
|
-
const result = previous.then(async () => {
|
|
13
|
-
// Serialize the global provenance lookup and write, including across server processes.
|
|
14
|
-
const release = await lockfile.lock(path.join(real, "piweb-session-import"), {
|
|
15
|
-
realpath: false, stale: 120_000,
|
|
16
|
-
retries: { retries: 60, minTimeout: 100, maxTimeout: 500 },
|
|
17
|
-
});
|
|
18
|
-
try { return await operation(); }
|
|
19
|
-
finally { await release(); }
|
|
20
|
-
});
|
|
21
|
-
const settled = result.then(() => undefined, () => undefined);
|
|
22
|
-
pending.set(key, settled);
|
|
23
|
-
void settled.then(() => { if (pending.get(key) === settled) pending.delete(key); });
|
|
24
|
-
return result;
|
|
25
|
-
}
|