@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
|
@@ -1,88 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* growth-tracker:把 pi
|
|
2
|
+
* growth-tracker:把 pi 会话事件接到「每轮一个 git commit」上。
|
|
3
3
|
*
|
|
4
|
-
* -
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* - prepare()(发 prompt 前):把两轮之间用户自己的修改单独提交一次,不混进 pi 这一轮
|
|
5
|
+
* (工作区还没有任何提交时记成基线)
|
|
6
|
+
* - agent_start → agent_settled 是一轮:结束时提交本轮,提问首行做标题、用户消息 entryId 写进 trailers;
|
|
7
|
+
* 没有改动也提交空 commit,轮号与对话一一对应
|
|
8
|
+
* 提交失败不影响 pi,只向浏览器报一次错;项目过大 / 没有 git 时本会话停用。
|
|
9
9
|
*/
|
|
10
|
-
import fs from "node:fs";
|
|
11
|
-
import { realpathSync } from "node:fs";
|
|
12
|
-
import path from "node:path";
|
|
13
10
|
import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
|
|
14
|
-
import { GrowthError,
|
|
15
|
-
import
|
|
11
|
+
import { GrowthError, commitRound, recordWorkspaceChanges } from "./growth-service";
|
|
12
|
+
import { promptsFromEntries } from "./growth-turns";
|
|
13
|
+
import type { GrowthRound, GrowthRoundStatus, WebEvent } from "./types";
|
|
16
14
|
|
|
17
|
-
const READ_ONLY_TOOLS = new Set(["read", "grep", "find", "ls"]);
|
|
18
|
-
const SNAPSHOT_DEBOUNCE_MS = 200;
|
|
19
|
-
const PENDING_THROTTLE_MS = 300;
|
|
20
|
-
const EXTERNAL_DEBOUNCE_MS = 1500;
|
|
21
|
-
const MAX_PENDING = 500;
|
|
22
15
|
const ERROR_THROTTLE_MS = 60_000;
|
|
23
|
-
export const GROWTH_TRACKER_VERSION =
|
|
16
|
+
export const GROWTH_TRACKER_VERSION = 4;
|
|
24
17
|
|
|
25
|
-
interface
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
toolName?: string;
|
|
30
|
-
/** 归属时刻:轮次内的步传轮次开始时间,避免慢快照落账晚被切进下一轮窗口 */
|
|
31
|
-
ts?: number;
|
|
18
|
+
interface SessionEntryLike {
|
|
19
|
+
type?: string;
|
|
20
|
+
id?: string;
|
|
21
|
+
message?: { role?: string; content?: unknown; stopReason?: string };
|
|
32
22
|
}
|
|
33
23
|
|
|
34
24
|
export interface GrowthTracker {
|
|
35
25
|
version: number;
|
|
36
26
|
getError(): string | null;
|
|
37
|
-
/**
|
|
27
|
+
/** 发 prompt 前完成「你的修改」提交,避免它被算进 pi 这一轮 */
|
|
38
28
|
prepare(): Promise<void>;
|
|
39
29
|
onEvent(evt: AgentSessionEvent): void;
|
|
40
|
-
/**
|
|
41
|
-
|
|
30
|
+
/** 立即记录(项目栏按钮):把当前改动提交为「你的修改」;没改动返回 null */
|
|
31
|
+
recordNow(): Promise<GrowthRound | null>;
|
|
42
32
|
dispose(): void;
|
|
43
33
|
}
|
|
44
34
|
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
/** 本轮最后一条助手消息的结束原因 → 轮次状态(用户中止 / 模型报错 / 正常) */
|
|
36
|
+
function roundStatus(entries: readonly SessionEntryLike[]): GrowthRoundStatus {
|
|
37
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
38
|
+
const message = entries[i].type === "message" ? entries[i].message : undefined;
|
|
39
|
+
if (message?.role !== "assistant") continue;
|
|
40
|
+
if (message.stopReason === "aborted") return "aborted";
|
|
41
|
+
if (message.stopReason === "error") return "error";
|
|
42
|
+
return "done";
|
|
43
|
+
}
|
|
44
|
+
return "done";
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
detail = detail.replace(/\s+/g, " ");
|
|
58
|
-
if (detail.length > 60) detail = `${detail.slice(0, 59)}…`;
|
|
59
|
-
return detail ? `${name} · ${detail}` : name;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function relativeToCwd(cwd: string, p: string): string {
|
|
63
|
-
if (!path.isAbsolute(p)) return p.replace(/\\/g, "/");
|
|
64
|
-
const rel = path.relative(cwd, p);
|
|
65
|
-
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) return p.replace(/\\/g, "/");
|
|
66
|
-
return rel.replace(/\\/g, "/");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** 工具链自动生成/频繁改写的文件:进 pending 只会制造蓝点噪音,快照侧由 .gitignore 与影子仓库 exclude 兜底 */
|
|
70
|
-
const WATCHER_FILE_NOISE = /(?:^|\/)(?:\.DS_Store|Thumbs\.db|[^/]*\.log|[^/]*\.tsbuildinfo|next-env\.d\.ts)$/;
|
|
71
|
-
|
|
72
|
-
export function createGrowthTracker(opts: { cwd: string; sessionPath: string; publish: (evt: WebEvent) => void }): GrowthTracker {
|
|
73
|
-
const { cwd, sessionPath, publish } = opts;
|
|
74
|
-
const running = new Set<string>();
|
|
75
|
-
const argsById = new Map<string, unknown>();
|
|
76
|
-
const queue: QueuedMeta[] = [];
|
|
77
|
-
const pending = new Set<string>();
|
|
78
|
-
let pendingStats = 0;
|
|
79
|
-
let flushTimer: ReturnType<typeof setTimeout> | undefined;
|
|
80
|
-
let pendingTimer: ReturnType<typeof setTimeout> | undefined;
|
|
81
|
-
let externalTimer: ReturnType<typeof setTimeout> | undefined;
|
|
82
|
-
let watcher: fs.FSWatcher | null = null;
|
|
83
|
-
let watcherFailed = false;
|
|
84
|
-
let baselineChecked = false;
|
|
85
|
-
let baselinePending: Promise<void> | null = null;
|
|
47
|
+
export function createGrowthTracker(opts: {
|
|
48
|
+
cwd: string;
|
|
49
|
+
sessionPath: string;
|
|
50
|
+
publish: (evt: WebEvent) => void;
|
|
51
|
+
/** 会话的持久条目(SessionManager.getEntries):轮结束时从中取本轮的提问 */
|
|
52
|
+
entries: () => readonly SessionEntryLike[];
|
|
53
|
+
}): GrowthTracker {
|
|
54
|
+
const { cwd, sessionPath, publish, entries } = opts;
|
|
86
55
|
let disabled = false;
|
|
87
56
|
let disabledFailure: GrowthError | null = null;
|
|
88
57
|
let disposed = false;
|
|
@@ -90,243 +59,83 @@ export function createGrowthTracker(opts: { cwd: string; sessionPath: string; pu
|
|
|
90
59
|
let errorActive = false;
|
|
91
60
|
let lastErrorMessage: string | null = null;
|
|
92
61
|
let chain: Promise<void> = Promise.resolve();
|
|
93
|
-
/**
|
|
94
|
-
let
|
|
62
|
+
/** 本轮开始时会话条目的数量;null = 不在一轮之中 */
|
|
63
|
+
let runStart: number | null = null;
|
|
95
64
|
|
|
96
|
-
//
|
|
65
|
+
// 提交失败不打扰用户(项目栏退化成普通文件树 + 查看器):只在服务端日志记一次,没 git / 项目过大就停用本会话的跟踪
|
|
97
66
|
const reportError = (error: unknown) => {
|
|
98
67
|
const message = error instanceof Error ? error.message : String(error);
|
|
99
68
|
const terminal = error instanceof GrowthError && (error.code === "unavailable" || error.code === "too-large");
|
|
100
69
|
if (terminal) {
|
|
101
70
|
disabled = true;
|
|
102
71
|
disabledFailure = error;
|
|
103
|
-
|
|
104
|
-
watcher?.close();
|
|
105
|
-
} catch {
|
|
106
|
-
/* ignore watcher close errors after a terminal snapshot failure */
|
|
107
|
-
}
|
|
108
|
-
watcher = null;
|
|
109
|
-
if (flushTimer) clearTimeout(flushTimer);
|
|
110
|
-
if (pendingTimer) clearTimeout(pendingTimer);
|
|
111
|
-
if (externalTimer) clearTimeout(externalTimer);
|
|
112
|
-
flushTimer = pendingTimer = externalTimer = undefined;
|
|
113
|
-
queue.length = 0;
|
|
114
|
-
running.clear();
|
|
115
|
-
argsById.clear();
|
|
116
|
-
if (pending.size) {
|
|
117
|
-
pending.clear();
|
|
118
|
-
publish({ type: "growth_pending", paths: [], ts: Date.now() });
|
|
119
|
-
}
|
|
72
|
+
runStart = null;
|
|
120
73
|
}
|
|
121
74
|
errorActive = true;
|
|
122
75
|
lastErrorMessage = message;
|
|
123
76
|
const now = Date.now();
|
|
124
77
|
if (!terminal && now - lastErrorAt < ERROR_THROTTLE_MS) return;
|
|
125
78
|
lastErrorAt = now;
|
|
126
|
-
console.warn(`[piweb] growth
|
|
79
|
+
console.warn(`[piweb] growth commit failed for ${cwd}: ${message}`);
|
|
127
80
|
publish({ type: "growth_error", message, ts: now });
|
|
128
81
|
};
|
|
129
82
|
|
|
130
|
-
const publishPending = () => {
|
|
131
|
-
pendingTimer = undefined;
|
|
132
|
-
publish({ type: "growth_pending", paths: [...pending].slice(0, 200), ts: Date.now() });
|
|
133
|
-
};
|
|
134
|
-
|
|
135
83
|
const enqueue = (fn: () => Promise<void>) => {
|
|
136
84
|
chain = chain.then(fn, fn).catch(reportError);
|
|
137
85
|
return chain;
|
|
138
86
|
};
|
|
139
87
|
|
|
140
|
-
|
|
141
|
-
const doRecord = async (meta: QueuedMeta, force = false) => {
|
|
142
|
-
if (disabled || disposed) return;
|
|
143
|
-
const step = await snapshot(cwd, { ...meta, session: sessionPath, force });
|
|
88
|
+
const published = (round: GrowthRound | null) => {
|
|
144
89
|
if (errorActive) {
|
|
145
90
|
errorActive = false;
|
|
146
91
|
lastErrorMessage = null;
|
|
147
92
|
publish({ type: "growth_error", message: null, ts: Date.now() });
|
|
148
93
|
}
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
if (pending.size) {
|
|
152
|
-
pending.clear();
|
|
153
|
-
if (pendingTimer) clearTimeout(pendingTimer);
|
|
154
|
-
publishPending();
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
const record = (meta: QueuedMeta, force = false) => enqueue(() => doRecord(meta, force));
|
|
158
|
-
|
|
159
|
-
const ensureBaseline = (): Promise<void> => {
|
|
160
|
-
if (baselineChecked) return chain;
|
|
161
|
-
if (baselinePending) return baselinePending;
|
|
162
|
-
baselinePending = enqueue(async () => {
|
|
163
|
-
if (disabled || disposed) return;
|
|
164
|
-
if (!(await hasSessionSteps(cwd, sessionPath))) await doRecord({ kind: "baseline", label: "", ts: turnTs }, true);
|
|
165
|
-
baselineChecked = true;
|
|
166
|
-
});
|
|
167
|
-
void baselinePending.finally(() => { baselinePending = null; });
|
|
168
|
-
return baselinePending;
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
const flush = () => {
|
|
172
|
-
flushTimer = undefined;
|
|
173
|
-
const metas = queue.splice(0);
|
|
174
|
-
if (!metas.length) return;
|
|
175
|
-
const tools = metas.filter((x) => x.kind === "tool");
|
|
176
|
-
const head = tools[0] ?? metas[0];
|
|
177
|
-
const extra = tools.length > 1 ? ` +${tools.length - 1}` : "";
|
|
178
|
-
// 归属时刻优先用队里最新的(同轮多个工具取最后一个工具结束时也在轮内);
|
|
179
|
-
// 没有就退回当前轮开始时刻,再退回落账时刻。
|
|
180
|
-
const ts = metas.reduce<number | undefined>((acc, m) => m.ts ?? acc, undefined) ?? turnTs;
|
|
181
|
-
// 首轮工具快照前确保基线已在链上排队:基线落账晚于首轮工具时,
|
|
182
|
-
// buildGrowthRounds 按时间戳切窗会把首轮误判成「快照启用前,未记录」。
|
|
183
|
-
const recordAfterBaseline = baselineChecked
|
|
184
|
-
? Promise.resolve()
|
|
185
|
-
: ensureBaseline().catch(() => undefined);
|
|
186
|
-
void recordAfterBaseline.then(() => {
|
|
187
|
-
if (disposed || disabled) return;
|
|
188
|
-
record({ kind: head.kind, label: `${head.label}${extra}`, toolCallId: head.toolCallId, toolName: head.toolName, ts });
|
|
189
|
-
});
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const schedule = (meta: QueuedMeta) => {
|
|
193
|
-
if (disabled) return;
|
|
194
|
-
if (externalTimer) {
|
|
195
|
-
clearTimeout(externalTimer);
|
|
196
|
-
externalTimer = undefined;
|
|
197
|
-
}
|
|
198
|
-
// 轮内工具步继承轮次开始时刻(外部修改步不标 ts,按实际发生时间归属)
|
|
199
|
-
queue.push(meta.kind === "tool" && turnTs !== undefined ? { ...meta, ts: meta.ts ?? turnTs } : meta);
|
|
200
|
-
if (flushTimer) clearTimeout(flushTimer);
|
|
201
|
-
flushTimer = setTimeout(flush, SNAPSHOT_DEBOUNCE_MS);
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const onFsChange = (_type: string, filename: string | Buffer | null) => {
|
|
205
|
-
if (disabled || disposed) return;
|
|
206
|
-
const rel = filename == null ? "" : String(filename).replace(/\\/g, "/");
|
|
207
|
-
if (rel && (isExcludedRelPath(rel) || WATCHER_FILE_NOISE.test(rel))) return;
|
|
208
|
-
if (running.size > 0) {
|
|
209
|
-
if (!rel || pending.size + pendingStats >= MAX_PENDING) return;
|
|
210
|
-
pendingStats += 1;
|
|
211
|
-
// fs.watch 回调不能同步 stat;目录标记允许异步到达。
|
|
212
|
-
void fs.promises.stat(path.join(cwd, rel)).then(
|
|
213
|
-
(info) => info.isDirectory(),
|
|
214
|
-
() => false,
|
|
215
|
-
).then((isDir) => {
|
|
216
|
-
if (disposed || disabled) return;
|
|
217
|
-
const livePath = isDir ? `${rel}/` : rel;
|
|
218
|
-
if (pending.size >= MAX_PENDING || pending.has(livePath)) return;
|
|
219
|
-
pending.add(livePath);
|
|
220
|
-
if (!pendingTimer) pendingTimer = setTimeout(publishPending, PENDING_THROTTLE_MS);
|
|
221
|
-
}).finally(() => { pendingStats -= 1; });
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
// 工具刚结束、快照还没拍:这次改动归它,不另记外部修改
|
|
225
|
-
if (queue.length || flushTimer) return;
|
|
226
|
-
if (externalTimer) clearTimeout(externalTimer);
|
|
227
|
-
externalTimer = setTimeout(() => {
|
|
228
|
-
externalTimer = undefined;
|
|
229
|
-
if (running.size > 0 || queue.length) return;
|
|
230
|
-
void record({ kind: "external", label: "" });
|
|
231
|
-
}, EXTERNAL_DEBOUNCE_MS);
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
const startWatcher = () => {
|
|
235
|
-
if (watcher || watcherFailed || disabled) return;
|
|
236
|
-
try {
|
|
237
|
-
// Watch the canonical path: on Windows an 8.3 short name (RUNNER~1) can make
|
|
238
|
-
// libuv's directory watcher compare mismatched spellings and crash the process.
|
|
239
|
-
let watchRoot = cwd;
|
|
240
|
-
try {
|
|
241
|
-
watchRoot = realpathSync.native(cwd);
|
|
242
|
-
} catch {
|
|
243
|
-
/* keep the given path when it cannot be resolved */
|
|
244
|
-
}
|
|
245
|
-
watcher = fs.watch(watchRoot, { recursive: true, persistent: false }, onFsChange);
|
|
246
|
-
watcher.on("error", () => {
|
|
247
|
-
watcherFailed = true;
|
|
248
|
-
try {
|
|
249
|
-
watcher?.close();
|
|
250
|
-
} catch {
|
|
251
|
-
/* ignore */
|
|
252
|
-
}
|
|
253
|
-
watcher = null;
|
|
254
|
-
});
|
|
255
|
-
} catch {
|
|
256
|
-
watcherFailed = true;
|
|
257
|
-
watcher = null;
|
|
258
|
-
}
|
|
94
|
+
if (round && !disposed) publish({ type: "growth", round, ts: Date.now() });
|
|
95
|
+
return round;
|
|
259
96
|
};
|
|
260
97
|
|
|
261
98
|
return {
|
|
262
99
|
version: GROWTH_TRACKER_VERSION,
|
|
263
100
|
getError: () => lastErrorMessage,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
101
|
+
prepare() {
|
|
102
|
+
if (disabled || disposed) return chain;
|
|
103
|
+
return enqueue(async () => {
|
|
104
|
+
if (disabled || disposed) return;
|
|
105
|
+
published(await recordWorkspaceChanges(cwd, sessionPath));
|
|
106
|
+
});
|
|
270
107
|
},
|
|
271
108
|
onEvent(evt) {
|
|
272
109
|
if (disposed || disabled) return;
|
|
273
110
|
switch (evt.type) {
|
|
274
111
|
case "agent_start":
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
startWatcher();
|
|
278
|
-
void ensureBaseline();
|
|
112
|
+
// 自动重试会再次 agent_start:一轮只从第一次开始算
|
|
113
|
+
if (runStart === null) runStart = entries().length;
|
|
279
114
|
break;
|
|
280
|
-
case "
|
|
281
|
-
if (
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
115
|
+
case "agent_settled": {
|
|
116
|
+
if (runStart === null) break;
|
|
117
|
+
const all = entries();
|
|
118
|
+
const run = all.slice(runStart);
|
|
119
|
+
const { ids, title } = promptsFromEntries(run);
|
|
120
|
+
runStart = null;
|
|
121
|
+
void enqueue(async () => {
|
|
122
|
+
if (disabled || disposed) return;
|
|
123
|
+
published(await commitRound(cwd, { kind: "round", session: sessionPath, title, promptIds: ids, status: roundStatus(run) }));
|
|
124
|
+
});
|
|
289
125
|
break;
|
|
290
126
|
}
|
|
291
|
-
case "tool_execution_end": {
|
|
292
|
-
if (!isMutatingTool(evt.toolName)) break;
|
|
293
|
-
running.delete(evt.toolCallId);
|
|
294
|
-
const args = argsById.get(evt.toolCallId);
|
|
295
|
-
argsById.delete(evt.toolCallId);
|
|
296
|
-
schedule({ kind: "tool", label: labelForTool(cwd, evt.toolName, args), toolCallId: evt.toolCallId, toolName: evt.toolName });
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
299
|
-
case "agent_end":
|
|
300
|
-
running.clear();
|
|
301
|
-
// 先落下本轮最后一批工具快照,再写不依赖文件变化的轮次终点。
|
|
302
|
-
if (flushTimer) clearTimeout(flushTimer);
|
|
303
|
-
flush();
|
|
304
|
-
// 无文件变更的回合也持久化终点,轮数才能与真实用户轮次一致。
|
|
305
|
-
// turn 步同样按轮次开始时刻归属,防晚落账切错窗口。
|
|
306
|
-
if (!evt.willRetry) void enqueue(() => doRecord({ kind: "turn", label: "", ts: turnTs }, true));
|
|
307
|
-
turnTs = undefined;
|
|
308
|
-
break;
|
|
309
127
|
default:
|
|
310
128
|
break;
|
|
311
129
|
}
|
|
312
130
|
},
|
|
313
|
-
async
|
|
131
|
+
async recordNow() {
|
|
314
132
|
if (disabled) throw disabledFailure ?? new GrowthError("growth tracking is disabled for this workspace", "unavailable");
|
|
315
|
-
let result:
|
|
133
|
+
let result: GrowthRound | null = null;
|
|
316
134
|
let failure: unknown;
|
|
317
135
|
await enqueue(async () => {
|
|
318
136
|
try {
|
|
319
137
|
if (disabled) throw disabledFailure ?? new GrowthError("growth tracking is disabled for this workspace", "unavailable");
|
|
320
|
-
|
|
321
|
-
if (errorActive) {
|
|
322
|
-
errorActive = false;
|
|
323
|
-
lastErrorMessage = null;
|
|
324
|
-
publish({ type: "growth_error", message: null, ts: Date.now() });
|
|
325
|
-
}
|
|
326
|
-
if (step) {
|
|
327
|
-
result = step;
|
|
328
|
-
publish({ type: "growth", step, ts: Date.now() });
|
|
329
|
-
}
|
|
138
|
+
result = published(await recordWorkspaceChanges(cwd, sessionPath));
|
|
330
139
|
} catch (error) {
|
|
331
140
|
failure = error;
|
|
332
141
|
reportError(error);
|
|
@@ -337,15 +146,6 @@ export function createGrowthTracker(opts: { cwd: string; sessionPath: string; pu
|
|
|
337
146
|
},
|
|
338
147
|
dispose() {
|
|
339
148
|
disposed = true;
|
|
340
|
-
if (flushTimer) clearTimeout(flushTimer);
|
|
341
|
-
if (pendingTimer) clearTimeout(pendingTimer);
|
|
342
|
-
if (externalTimer) clearTimeout(externalTimer);
|
|
343
|
-
try {
|
|
344
|
-
watcher?.close();
|
|
345
|
-
} catch {
|
|
346
|
-
/* ignore */
|
|
347
|
-
}
|
|
348
|
-
watcher = null;
|
|
349
149
|
},
|
|
350
150
|
};
|
|
351
151
|
}
|
package/src/lib/growth-turns.ts
CHANGED
|
@@ -7,7 +7,7 @@ interface SessionEntry {
|
|
|
7
7
|
type?: string;
|
|
8
8
|
id?: string;
|
|
9
9
|
timestamp?: string | number;
|
|
10
|
-
message?: { role?: string; timestamp?: number };
|
|
10
|
+
message?: { role?: string; timestamp?: number; content?: unknown };
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/** 与会话统计一样遍历全部 JSONL 条目,不使用压缩后的渲染消息列表。 */
|
|
@@ -21,3 +21,33 @@ export function userTurnsFromEntries(entries: readonly SessionEntry[]): SessionT
|
|
|
21
21
|
}
|
|
22
22
|
return turns;
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
export interface RoundPrompts {
|
|
26
|
+
/** 本轮的用户消息 entryId(按出现顺序) */
|
|
27
|
+
ids: string[];
|
|
28
|
+
/** 第一条用户消息的首个非空行(纯图片提问为空) */
|
|
29
|
+
title: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function textOf(content: unknown): string {
|
|
33
|
+
if (typeof content === "string") return content;
|
|
34
|
+
if (!Array.isArray(content)) return "";
|
|
35
|
+
return content
|
|
36
|
+
.map((part) => (part && typeof part === "object" && (part as { type?: unknown }).type === "text" ? String((part as { text?: unknown }).text ?? "") : ""))
|
|
37
|
+
.join("");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 一次运行(agent_start → agent_settled)新增的用户消息:首条提问 + steer / followUp 追加的消息。
|
|
42
|
+
* 遍历原始 JSONL 条目而不是渲染消息:上下文压缩后不再渲染的消息也算。
|
|
43
|
+
*/
|
|
44
|
+
export function promptsFromEntries(entries: readonly SessionEntry[], fromIndex = 0): RoundPrompts {
|
|
45
|
+
const ids: string[] = [];
|
|
46
|
+
let title = "";
|
|
47
|
+
for (const entry of entries.slice(Math.max(0, fromIndex))) {
|
|
48
|
+
if (entry.type !== "message" || entry.message?.role !== "user" || !entry.id) continue;
|
|
49
|
+
ids.push(entry.id);
|
|
50
|
+
if (!title) title = textOf(entry.message.content).split("\n").map((line) => line.trim()).find(Boolean) ?? "";
|
|
51
|
+
}
|
|
52
|
+
return { ids, title };
|
|
53
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { resolveModelScopeWithDiagnostics } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export type ModelScopeRuntime = Parameters<typeof resolveModelScopeWithDiagnostics>[1];
|
|
4
|
+
|
|
5
|
+
/** 用 pi 自己的模型通配规则过滤 PiWeb 选择器里的模型 */
|
|
6
|
+
export async function filterModelsByPiScope<T extends { provider: string; id: string }>(
|
|
7
|
+
models: T[],
|
|
8
|
+
patterns: string[],
|
|
9
|
+
runtime: ModelScopeRuntime,
|
|
10
|
+
): Promise<T[]> {
|
|
11
|
+
if (patterns.length === 0) return models;
|
|
12
|
+
const { scopedModels } = await resolveModelScopeWithDiagnostics(patterns, runtime);
|
|
13
|
+
const allowed = new Set(scopedModels.map(({ model }) => `${model.provider}/${model.id}`));
|
|
14
|
+
return models.filter((model) => allowed.has(`${model.provider}/${model.id}`));
|
|
15
|
+
}
|
|
@@ -10,7 +10,9 @@ import { request as httpsRequest } from "node:https";
|
|
|
10
10
|
import { BlockList, isIP } from "node:net";
|
|
11
11
|
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { getNodeValue, parseTree, printParseErrorCode, type ParseError } from "jsonc-parser";
|
|
13
|
+
import { filterModelsByPiScope } from "./models/model-scope";
|
|
13
14
|
import { getAgentDir, getModelRuntime, resetModelRuntime } from "./pi";
|
|
15
|
+
import { getEnabledModelPatterns } from "./pi-settings";
|
|
14
16
|
import { reloadSessionsForCwd } from "./agent-manager";
|
|
15
17
|
import {
|
|
16
18
|
getSupportedThinkingLevels, InMemoryCredentialStore, InMemoryModelsStore,
|
|
@@ -103,6 +105,20 @@ export function listModels(): Promise<{
|
|
|
103
105
|
return value;
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
/** 对话里的模型选择器遵循 pi 的 enabledModels;设置页仍用完整目录 */
|
|
109
|
+
export async function listModelsForPiWeb(): Promise<{ providers: ProviderView[]; models: ModelView[] }> {
|
|
110
|
+
const data = await listModels();
|
|
111
|
+
const patterns = await getEnabledModelPatterns();
|
|
112
|
+
if (!patterns?.length) return data;
|
|
113
|
+
const models = await filterModelsByPiScope(data.models, patterns, await getModelRuntime());
|
|
114
|
+
const counts = new Map<string, number>();
|
|
115
|
+
for (const model of models) counts.set(model.provider, (counts.get(model.provider) ?? 0) + 1);
|
|
116
|
+
return {
|
|
117
|
+
models,
|
|
118
|
+
providers: data.providers.map((provider) => ({ ...provider, modelCount: counts.get(provider.id) ?? 0 })),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
106
122
|
/** auth.json 各条目的凭证类型(与 storedCredentialFor 同样的两层数据结构);读取失败不影响目录 */
|
|
107
123
|
async function readStoredAuthTypes(): Promise<Map<string, "api_key" | "oauth">> {
|
|
108
124
|
const out = new Map<string, "api_key" | "oauth">();
|
package/src/lib/pi-settings.ts
CHANGED
|
@@ -43,6 +43,13 @@ async function readSettingsFileStrict(): Promise<Record<string, unknown>> {
|
|
|
43
43
|
return parsed as Record<string, unknown>;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/** enabledModels 是 pi 的模型范围;没写或写错就不过滤 PiWeb 的选择器 */
|
|
47
|
+
export async function getEnabledModelPatterns(): Promise<string[] | null> {
|
|
48
|
+
const patterns = (await readSettingsFileStrict()).enabledModels;
|
|
49
|
+
if (!Array.isArray(patterns) || patterns.some((pattern) => typeof pattern !== "string")) return null;
|
|
50
|
+
return patterns;
|
|
51
|
+
}
|
|
52
|
+
|
|
46
53
|
function section(raw: Record<string, unknown>, key: "compaction" | "retry"): Record<string, unknown> {
|
|
47
54
|
const value = raw[key];
|
|
48
55
|
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
package/src/lib/pi.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
type SettingsManager,
|
|
10
10
|
DefaultPackageManager,
|
|
11
11
|
DefaultResourceLoader,
|
|
12
|
-
type InlineExtension,
|
|
13
12
|
ModelRuntime,
|
|
14
13
|
ProjectTrustStore,
|
|
15
14
|
SessionManager,
|
|
@@ -158,10 +157,10 @@ function loaderKey(cwd: string): string {
|
|
|
158
157
|
}
|
|
159
158
|
|
|
160
159
|
/** 每个热会话必须拥有自己的扩展 runtime;这里只创建,不加入发现缓存。 */
|
|
161
|
-
export async function createSessionResourceLoader(cwd: string
|
|
160
|
+
export async function createSessionResourceLoader(cwd: string): Promise<DefaultResourceLoader> {
|
|
162
161
|
const settingsManager = getSettingsManager(cwd);
|
|
163
162
|
settingsManager.setProjectTrusted(resolveProjectTrust(cwd).trusted);
|
|
164
|
-
const loader = new DefaultResourceLoader({ cwd: path.resolve(cwd), agentDir: getAgentDir(), settingsManager
|
|
163
|
+
const loader = new DefaultResourceLoader({ cwd: path.resolve(cwd), agentDir: getAgentDir(), settingsManager });
|
|
165
164
|
const reload = loader.reload.bind(loader);
|
|
166
165
|
loader.reload = async (options) => {
|
|
167
166
|
settingsManager.setProjectTrusted(resolveProjectTrust(cwd).trusted);
|
|
@@ -160,7 +160,6 @@ export const claudeSource: ImportSourceModule = {
|
|
|
160
160
|
const skipped: string[] = [];
|
|
161
161
|
let sidechains = 0;
|
|
162
162
|
let synthetic = 0;
|
|
163
|
-
let runtimeContext = 0;
|
|
164
163
|
let projectPath = summary.projectPath;
|
|
165
164
|
let model = summary.model;
|
|
166
165
|
let provider: string | undefined;
|
|
@@ -170,12 +169,6 @@ export const claudeSource: ImportSourceModule = {
|
|
|
170
169
|
sidechains += 1;
|
|
171
170
|
continue;
|
|
172
171
|
}
|
|
173
|
-
// Claude 的 attachment/system 是技能列表、token 提醒、环境快照等运行时上下文;
|
|
174
|
-
// pi 会按目标工作区生成自己的版本,不能伪装成真人 user 消息,但要在报告中明确告知。
|
|
175
|
-
if (record.type === "attachment" || record.type === "system") {
|
|
176
|
-
runtimeContext += 1;
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
172
|
if (typeof record.cwd === "string" && record.cwd.trim()) projectPath = projectPath ?? record.cwd;
|
|
180
173
|
const message = record.message as Record<string, unknown> | undefined;
|
|
181
174
|
if (!message) continue;
|
|
@@ -213,12 +206,9 @@ export const claudeSource: ImportSourceModule = {
|
|
|
213
206
|
if (record.type === "user") {
|
|
214
207
|
const { text: textBlocks, results } = splitUserContent((message as { content?: unknown }).content);
|
|
215
208
|
const plain = textBlocks.filter((block) => block.type === "text").map((block) => block.text).join("\n");
|
|
216
|
-
const
|
|
209
|
+
const keepText = textBlocks.filter((block) => block.type !== "text");
|
|
217
210
|
if (plain && !isSynthetic(plain.trim()) && !/^Caveat:/i.test(plain.trim())) {
|
|
218
|
-
entries.push({ type: "message", message: { role: "user", content: [...
|
|
219
|
-
} else if (!plain && nonText.length) {
|
|
220
|
-
// 图片可以是用户消息的全部内容,不能因为没有文字就把整轮丢掉
|
|
221
|
-
entries.push({ type: "message", message: { role: "user", content: nonText, timestamp } as ImportedMessage });
|
|
211
|
+
entries.push({ type: "message", message: { role: "user", content: [...keepText, { type: "text", text: plain }], timestamp } as ImportedMessage });
|
|
222
212
|
} else if (plain) {
|
|
223
213
|
synthetic += 1;
|
|
224
214
|
}
|
|
@@ -233,7 +223,6 @@ export const claudeSource: ImportSourceModule = {
|
|
|
233
223
|
|
|
234
224
|
if (sidechains) skipped.push(`跳过 ${sidechains} 条侧链(子代理)记录`);
|
|
235
225
|
if (synthetic) skipped.push(`跳过 ${synthetic} 条合成输入(命令回显/系统提醒)`);
|
|
236
|
-
if (runtimeContext) skipped.push(`跳过 ${runtimeContext} 条源工具运行时上下文(pi 会按当前工作区重新生成)`);
|
|
237
226
|
if (!entries.some((entry) => entry.type === "message")) return null;
|
|
238
227
|
return { summary: { ...summary, projectPath, model, provider }, entries, skipped };
|
|
239
228
|
},
|