@baique/pi-web-sky 0.1.55 → 0.1.57
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 -4
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +2 -2
- package/.next/react-loadable-manifest.json +2 -2
- package/.next/required-server-files.js +3 -2
- package/.next/required-server-files.json +3 -2
- package/.next/routes-manifest.json +28 -0
- 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 -3
- package/.next/server/app/_not-found.segments/_full.segment.rsc +4 -3
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +4 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +3 -2
- package/.next/server/app/api/agent/[id]/bash-output/route.js +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/new/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/running/route.js +1 -1
- package/.next/server/app/api/agent/running/route.js.nft.json +1 -1
- package/.next/server/app/api/app-update/route.js +1 -1
- package/.next/server/app/api/auth/all-providers/route.js +1 -1
- package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
- package/.next/server/app/api/auth/login/[provider]/route.js +1 -1
- package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
- package/.next/server/app/api/auth/providers/route.js +1 -1
- package/.next/server/app/api/board-assets/[...path]/route.js +2 -2
- package/.next/server/app/api/board-assets/route.js +2 -2
- package/.next/server/app/api/boards/[id]/add-session/route.js +1 -0
- package/.next/server/app/api/boards/[id]/add-session/route.js.nft.json +1 -0
- package/.next/server/app/api/boards/[id]/add-session/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/boards/[id]/reconcile/route.js +1 -1
- package/.next/server/app/api/boards/[id]/route.js +1 -1
- package/.next/server/app/api/boards/reorder/route.js +2 -2
- package/.next/server/app/api/boards/route.js +1 -1
- package/.next/server/app/api/cwd/browse/route.js +1 -1
- package/.next/server/app/api/cwd/validate/route.js +2 -2
- package/.next/server/app/api/default-cwd/route.js +2 -2
- package/.next/server/app/api/file-index/route.js +2 -2
- package/.next/server/app/api/files/[...path]/route.js +3 -3
- package/.next/server/app/api/git/diff/route.js +1 -1
- package/.next/server/app/api/git/status/route.js +1 -1
- package/.next/server/app/api/home/route.js +1 -1
- package/.next/server/app/api/mcp/route.js +2 -2
- package/.next/server/app/api/models/route.js +2 -2
- package/.next/server/app/api/models-config/catalog/route.js +1 -1
- package/.next/server/app/api/models-config/discover/route.js +1 -1
- package/.next/server/app/api/models-config/route.js +1 -1
- package/.next/server/app/api/models-config/test/route.js +1 -1
- package/.next/server/app/api/plugins/route.js +2 -2
- package/.next/server/app/api/project-trust/route.js +1 -1
- package/.next/server/app/api/project-trust/route.js.nft.json +1 -1
- package/.next/server/app/api/quota/route.js +1 -1
- package/.next/server/app/api/search/route.js +3 -3
- package/.next/server/app/api/sessions/[id]/auto-name/route.js +2 -2
- package/.next/server/app/api/sessions/[id]/auto-name/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/context/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +2 -2
- package/.next/server/app/api/sessions/[id]/export/route.js +2 -2
- package/.next/server/app/api/sessions/[id]/minimap/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/minimap/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/[id]/state/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/state/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/todos/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/todos/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/sessions/summary/route.js +2 -2
- package/.next/server/app/api/skills/check/route.js +1 -1
- package/.next/server/app/api/skills/install/route.js +2 -2
- package/.next/server/app/api/skills/route.js +1 -1
- package/.next/server/app/api/skills/search/route.js +1 -1
- package/.next/server/app/api/skills/update/route.js +1 -1
- package/.next/server/app/api/subagents/[id]/route.js +1 -0
- package/.next/server/app/api/subagents/[id]/route.js.nft.json +1 -0
- package/.next/server/app/api/subagents/[id]/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/subagents/profiles/route.js +30 -0
- package/.next/server/app/api/subagents/profiles/route.js.nft.json +1 -0
- package/.next/server/app/api/subagents/profiles/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/subagents/settings/route.js +1 -0
- package/.next/server/app/api/subagents/settings/route.js.nft.json +1 -0
- package/.next/server/app/api/subagents/settings/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/task-card-questions/[id]/answer/route.js +2 -2
- package/.next/server/app/api/task-card-questions/route.js +2 -2
- package/.next/server/app/api/task-cards/[id]/route.js +1 -1
- package/.next/server/app/api/task-cards/route.js +1 -1
- package/.next/server/app/api/task-cards/unbind/route.js +3 -3
- package/.next/server/app/api/task-scheduler/status/route.js +1 -1
- package/.next/server/app/api/task-scheduler/status/route.js.nft.json +1 -1
- package/.next/server/app/api/tasks/[id]/assign-session/route.js +2 -12
- package/.next/server/app/api/tasks/[id]/board/route.js +1 -1
- package/.next/server/app/api/tasks/[id]/route.js +1 -1
- package/.next/server/app/api/tasks/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/tasks/reorder/route.js +2 -12
- package/.next/server/app/api/tasks/route.js +2 -12
- package/.next/server/app/api/terminal/[id]/events/route.js +2 -2
- package/.next/server/app/api/terminal/[id]/route.js +2 -2
- package/.next/server/app/api/terminal/route.js +2 -2
- package/.next/server/app/api/worktrees/route.js +2 -2
- package/.next/server/app/api/yjs-version/route.js +1 -1
- package/.next/server/app/favicon.ico/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +6 -5
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +6 -5
- package/.next/server/app/index.segments/_full.segment.rsc +6 -5
- package/.next/server/app/index.segments/_tree.segment.rsc +3 -2
- package/.next/server/app/manifest.webmanifest/route.js +2 -2
- package/.next/server/app/page.js +175 -8
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +8 -4
- package/.next/server/chunks/1937.js +14 -13
- package/.next/server/chunks/2524.js +2 -2
- package/.next/server/chunks/3379.js +1 -1
- package/.next/server/chunks/3500.js +1 -1
- package/.next/server/chunks/402.js +28 -7
- package/.next/server/chunks/426.js +14 -0
- package/.next/server/chunks/4367.js +1 -1
- package/.next/server/chunks/4457.js +3 -13
- package/.next/server/chunks/4980.js +1 -1
- package/.next/server/chunks/542.js +23 -4
- package/.next/server/chunks/5794.js +2 -2
- package/.next/server/chunks/5940.js +1 -1
- package/.next/server/chunks/6337.js +1 -1
- package/.next/server/chunks/6429.js +15 -2
- package/.next/server/chunks/6525.js +25 -4
- package/.next/server/chunks/6583.js +12 -11
- package/.next/server/chunks/7048.js +6 -0
- package/.next/server/instrumentation.js +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 +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/MPBMmQqmDG8zo8tM_bkDy/_buildManifest.js +1 -0
- package/.next/static/chunks/752.bafff304e6b00987.js +1 -0
- package/.next/static/chunks/8088.39866dc5f1d15868.js +1 -0
- package/.next/static/chunks/app/_global-error/page-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/bash-output/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/agent/running/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/app-update/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/auth/all-providers/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/auth/api-key/[provider]/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/auth/login/[provider]/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/auth/logout/[provider]/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/auth/providers/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/board-assets/[...path]/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/board-assets/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/boards/[id]/add-session/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/boards/[id]/reconcile/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/boards/[id]/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/boards/reorder/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/boards/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/cwd/browse/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/cwd/validate/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/default-cwd/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/file-index/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/files/[...path]/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/git/diff/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/git/status/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/home/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/mcp/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/models/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/models-config/catalog/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/models-config/discover/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/models-config/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/models-config/test/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/project-trust/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/quota/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/search/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/context/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/minimap/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/state/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/todos/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/sessions/summary/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/skills/check/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/skills/install/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/skills/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/skills/search/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/skills/update/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/subagents/[id]/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/subagents/profiles/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/subagents/settings/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/task-card-questions/[id]/answer/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/task-card-questions/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/task-cards/[id]/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/task-cards/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/task-cards/unbind/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/task-scheduler/status/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/tasks/[id]/assign-session/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/tasks/[id]/board/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/tasks/[id]/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/tasks/reorder/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/tasks/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/terminal/[id]/events/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/terminal/[id]/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/terminal/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/worktrees/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/api/yjs-version/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/layout-fae2b226b3c78448.js +1 -0
- package/.next/static/chunks/app/manifest.webmanifest/route-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/app/page-d5af3bd535589daf.js +256 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-ad6e695deb9de65c.js +1 -0
- package/.next/static/chunks/{webpack-b0effde51c1651b8.js → webpack-1ce35e1249cfd931.js} +1 -1
- package/.next/static/css/bb9361c7f832a89d.css +1 -0
- package/.next/static/css/c7d127e07b1a786c.css +1 -0
- package/.next/trace +8 -7
- package/.next/trace-build +1 -1
- package/.next/types/app/api/boards/[id]/add-session/route.ts +353 -0
- package/.next/types/app/api/subagents/[id]/route.ts +353 -0
- package/.next/types/app/api/subagents/profiles/route.ts +353 -0
- package/.next/types/app/api/subagents/settings/route.ts +353 -0
- package/.next/types/routes.d.ts +5 -1
- package/.next/types/validator.ts +36 -0
- package/lib/api-types.ts +10 -0
- package/lib/board-reconcile.test.mjs +46 -8
- package/lib/board-reconcile.ts +57 -1
- package/lib/board-store.test.mjs +2 -2
- package/lib/chat-lazy-load.test.mjs +0 -62
- package/lib/chat-lazy-load.ts +0 -13
- package/lib/chat-only.ts +23 -0
- package/lib/draft-stash.ts +4 -5
- package/lib/file-types.ts +16 -1
- package/lib/i18n/messages/en.ts +91 -0
- package/lib/i18n/messages/zh-CN.ts +91 -0
- package/lib/id.test.mjs +38 -0
- package/lib/id.ts +17 -0
- package/lib/ime-guard.test.mjs +52 -0
- package/lib/ime-guard.ts +54 -0
- package/lib/message-text.ts +24 -0
- package/lib/models-cache.ts +1 -1
- package/lib/pi-types.ts +19 -1
- package/lib/pin-store.ts +85 -0
- package/lib/powershell-settings.ts +106 -0
- package/lib/quoted-selection.ts +4 -0
- package/lib/rpc-manager.activity.test.mjs +223 -0
- package/lib/rpc-manager.test.mjs +22 -0
- package/lib/rpc-manager.title.test.mjs +152 -0
- package/lib/rpc-manager.ts +301 -28
- package/lib/session-activity.test.mjs +123 -0
- package/lib/session-activity.ts +77 -0
- package/lib/session-delete.test.mjs +45 -41
- package/lib/session-delete.ts +8 -34
- package/lib/session-family.test.mjs +49 -0
- package/lib/session-family.ts +80 -0
- package/lib/session-index-scanner.test.mjs +364 -2
- package/lib/session-index-scanner.ts +199 -55
- package/lib/session-reader.noscan.test.mjs +371 -0
- package/lib/session-reader.project.test.mjs +30 -52
- package/lib/session-reader.test.mjs +2 -65
- package/lib/session-reader.ts +177 -213
- package/lib/session-scanner.test.mjs +151 -38
- package/lib/session-scanner.ts +140 -61
- package/lib/session-search.ts +6 -5
- package/lib/session-time-group.test.mjs +38 -0
- package/lib/session-time-group.ts +55 -0
- package/lib/settings-navigation.ts +124 -0
- package/lib/sqlite-db.test.mjs +26 -0
- package/lib/sqlite-db.ts +10 -1
- package/lib/subagent-extension.test.mjs +424 -0
- package/lib/subagent-extension.ts +309 -0
- package/lib/subagent-input.test.mjs +63 -0
- package/lib/subagent-input.ts +71 -0
- package/lib/subagent-profile-precedence.ts +20 -0
- package/lib/subagent-prompt.test.mjs +64 -0
- package/lib/subagent-prompt.ts +31 -0
- package/lib/subagent-queue.test.mjs +35 -0
- package/lib/subagent-queue.ts +74 -0
- package/lib/subagent-runtime.test.mjs +150 -0
- package/lib/subagent-runtime.ts +629 -0
- package/lib/subagent-session-title.test.mjs +120 -0
- package/lib/subagent-settings.test.mjs +50 -0
- package/lib/subagent-settings.ts +93 -0
- package/lib/subagents.test.mjs +601 -0
- package/lib/subagents.ts +602 -0
- package/lib/task-scheduler.test.mjs +27 -0
- package/lib/task-scheduler.ts +8 -1
- package/lib/task-store.activity.test.mjs +141 -0
- package/lib/task-store.meta-row.test.mjs +25 -5
- package/lib/task-store.partial-row.test.mjs +198 -0
- package/lib/task-store.subtree.test.mjs +172 -0
- package/lib/task-store.test.mjs +46 -20
- package/lib/task-store.ts +344 -68
- package/lib/tool-presets.ts +4 -0
- package/lib/types.ts +19 -0
- package/lib/worktree-isolation.test.mjs +51 -0
- package/next.config.ts +3 -1
- package/package.json +1 -1
- package/.next/static/bsW3fla0-BlBHVMKtAM5I/_buildManifest.js +0 -1
- package/.next/static/chunks/752.0ad92735d5f38143.js +0 -1
- package/.next/static/chunks/8088.53d9cdaf15634d4b.js +0 -1
- package/.next/static/chunks/app/_global-error/page-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/bash-output/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/agent/running/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/app-update/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/auth/all-providers/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/auth/api-key/[provider]/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/auth/login/[provider]/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/auth/logout/[provider]/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/auth/providers/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/board-assets/[...path]/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/board-assets/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/boards/[id]/reconcile/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/boards/[id]/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/boards/reorder/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/boards/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/cwd/browse/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/cwd/validate/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/default-cwd/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/file-index/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/files/[...path]/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/git/diff/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/git/status/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/home/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/mcp/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/models/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/models-config/catalog/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/models-config/discover/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/models-config/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/models-config/test/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/project-trust/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/quota/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/search/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/context/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/minimap/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/state/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/todos/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/sessions/summary/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/skills/check/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/skills/install/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/skills/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/skills/search/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/skills/update/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/task-card-questions/[id]/answer/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/task-card-questions/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/task-cards/[id]/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/task-cards/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/task-cards/unbind/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/task-scheduler/status/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/tasks/[id]/assign-session/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/tasks/[id]/board/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/tasks/[id]/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/tasks/reorder/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/tasks/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/terminal/[id]/events/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/terminal/[id]/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/terminal/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/worktrees/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/api/yjs-version/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/layout-dace09825b2d3857.js +0 -1
- package/.next/static/chunks/app/manifest.webmanifest/route-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/app/page-64b9f4b3567a2f73.js +0 -89
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-2d5f6296c7e693c6.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-2d5f6296c7e693c6.js +0 -1
- package/.next/static/css/fd25673802257262.css +0 -1
- package/lib/extension-command.test.mjs +0 -63
- package/lib/extension-command.ts +0 -81
- package/lib/subagent-widget.test.mjs +0 -87
- package/lib/subagent-widget.ts +0 -109
- /package/.next/static/{bsW3fla0-BlBHVMKtAM5I → MPBMmQqmDG8zo8tM_bkDy}/_ssgManifest.js +0 -0
package/lib/rpc-manager.ts
CHANGED
|
@@ -13,10 +13,26 @@ import {
|
|
|
13
13
|
preferUserBashExtension,
|
|
14
14
|
} from "./project-command-env";
|
|
15
15
|
import { createTodoExtension } from "./todo-extension";
|
|
16
|
-
import {
|
|
16
|
+
import { createSubagentExtension, preferPiWebSubagentExtension } from "./subagent-extension";
|
|
17
|
+
import { listSubagentProfiles, readSubagentRun, readSubagentSessionResources, SUBAGENT_CONTROL_TOOL_NAMES } from "./subagents";
|
|
18
|
+
import { createSubagentController } from "./subagent-runtime";
|
|
19
|
+
import { isBuiltInSubagentsEnabled } from "./subagent-settings";
|
|
20
|
+
import { resolveShellTools } from "./powershell-settings";
|
|
21
|
+
import { CHAT_ONLY_RESOURCE_LOADER_OPTIONS } from "./chat-only";
|
|
22
|
+
import { cacheSessionPath, invalidateSessionListCache, resolveSessionPath } from "./session-reader";
|
|
23
|
+
import { indexSessionFileNow } from "./session-index-scanner";
|
|
24
|
+
import { createSessionActivityTracker, type AgentLikeMessage } from "./session-activity";
|
|
25
|
+
import { FIRST_MESSAGE_PREVIEW_LENGTH } from "./session-scanner";
|
|
17
26
|
import { projectIdentityKey } from "./project-identity";
|
|
18
27
|
import { resolveProject } from "./worktree";
|
|
19
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
ensureSessionMetaRow,
|
|
30
|
+
fillFirstMessageIfEmpty,
|
|
31
|
+
recordSessionOutcome,
|
|
32
|
+
setSessionTitle,
|
|
33
|
+
taskForSession,
|
|
34
|
+
touchSessionActivity,
|
|
35
|
+
} from "./task-store";
|
|
20
36
|
import { reconcileForkBoard } from "./board-reconcile";
|
|
21
37
|
import { getProjectTrustStatus, projectTrustReloadOptions } from "./project-trust";
|
|
22
38
|
import { persistExplicitStartupPreferences } from "./startup-preferences";
|
|
@@ -27,6 +43,7 @@ import type {
|
|
|
27
43
|
ExtensionUiRequest,
|
|
28
44
|
ExtensionUiResponse,
|
|
29
45
|
ExtensionWidgetItem,
|
|
46
|
+
SessionEntry,
|
|
30
47
|
SessionInfo,
|
|
31
48
|
SessionMessageEntry,
|
|
32
49
|
} from "./types";
|
|
@@ -97,6 +114,11 @@ type ExtensionBindingOptions = {
|
|
|
97
114
|
forceEmptySystemPrompt?: boolean;
|
|
98
115
|
};
|
|
99
116
|
|
|
117
|
+
type AgentSessionWrapperOptions = {
|
|
118
|
+
exactSystemPrompt?: () => string;
|
|
119
|
+
chatOnly?: boolean;
|
|
120
|
+
};
|
|
121
|
+
|
|
100
122
|
const IDLE_RESET_EVENT_TYPES = new Set([
|
|
101
123
|
"agent_end",
|
|
102
124
|
"agent_settled",
|
|
@@ -104,6 +126,28 @@ const IDLE_RESET_EVENT_TYPES = new Set([
|
|
|
104
126
|
"compaction_end",
|
|
105
127
|
]);
|
|
106
128
|
|
|
129
|
+
const DEFAULT_SESSION_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolves the PI_WEB_IDLE_TIMEOUT_MS environment variable into a session idle
|
|
133
|
+
* timeout in milliseconds. An unset/blank value returns the 10-minute default,
|
|
134
|
+
* `0` disables idle shutdown, and positive values up to Node's timer limit
|
|
135
|
+
* (2147483647 ms) are used as-is. Invalid or out-of-range values fall back to
|
|
136
|
+
* the default with a console warning.
|
|
137
|
+
*/
|
|
138
|
+
export function resolveSessionIdleTimeoutMs(
|
|
139
|
+
rawValue: string | undefined = process.env.PI_WEB_IDLE_TIMEOUT_MS,
|
|
140
|
+
): number {
|
|
141
|
+
if (rawValue !== undefined && rawValue.trim() !== "") {
|
|
142
|
+
const parsed = Number(rawValue);
|
|
143
|
+
if (Number.isFinite(parsed) && parsed >= 0 && parsed <= 2_147_483_647) return parsed;
|
|
144
|
+
console.warn(`[pi-web] invalid PI_WEB_IDLE_TIMEOUT_MS "${rawValue}", falling back to 10 minutes`);
|
|
145
|
+
}
|
|
146
|
+
return DEFAULT_SESSION_IDLE_TIMEOUT_MS;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const SESSION_IDLE_TIMEOUT_MS = resolveSessionIdleTimeoutMs();
|
|
150
|
+
|
|
107
151
|
export interface RpcSessionStartOptions {
|
|
108
152
|
toolNames?: string[];
|
|
109
153
|
initialModel?: { provider: string; modelId: string };
|
|
@@ -180,8 +224,17 @@ export class AgentSessionWrapper {
|
|
|
180
224
|
private _alive = true;
|
|
181
225
|
/** 最近一次 agent_start 的时间(看板“运行时长”用);0 = 未知 */
|
|
182
226
|
private lastAgentStart = 0;
|
|
183
|
-
|
|
184
|
-
|
|
227
|
+
/** 会话活跃事件 → 落库(last_reply / modified);按 wrapper 实例持有防并发串台 */
|
|
228
|
+
private readonly activityTracker = createSessionActivityTracker();
|
|
229
|
+
private readonly exactSystemPrompt?: () => string;
|
|
230
|
+
private readonly chatOnly: boolean;
|
|
231
|
+
|
|
232
|
+
constructor(public readonly inner: AgentSessionLike, options: AgentSessionWrapperOptions = {}) {
|
|
233
|
+
this.exactSystemPrompt = options.exactSystemPrompt;
|
|
234
|
+
this.chatOnly = options.chatOnly ?? false;
|
|
235
|
+
this.installExactSystemPromptContinuation();
|
|
236
|
+
this.applyExactSystemPrompt();
|
|
237
|
+
}
|
|
185
238
|
|
|
186
239
|
/** 是否有等待用户响应的扩展 UI 请求(waiting_input 判定用) */
|
|
187
240
|
hasPendingUiRequest(): boolean {
|
|
@@ -217,6 +270,10 @@ export class AgentSessionWrapper {
|
|
|
217
270
|
return this._alive;
|
|
218
271
|
}
|
|
219
272
|
|
|
273
|
+
isChatOnly(): boolean {
|
|
274
|
+
return this.chatOnly;
|
|
275
|
+
}
|
|
276
|
+
|
|
220
277
|
isRunning(): boolean {
|
|
221
278
|
return this._alive && (this.pendingPromptCount > 0 || this.inner.isStreaming || this.inner.isCompacting || this.inner.isBashRunning);
|
|
222
279
|
}
|
|
@@ -230,11 +287,42 @@ export class AgentSessionWrapper {
|
|
|
230
287
|
invalidateSessionListCache();
|
|
231
288
|
}
|
|
232
289
|
if (IDLE_RESET_EVENT_TYPES.has(event.type)) this.resetIdleTimer();
|
|
290
|
+
// 会话活跃落库(last_reply / modified)。包在 try/catch 里:库是旁路索引,
|
|
291
|
+
// 写失败必须可见(console.error),但绝不能打断上面几件事与事件分发。
|
|
292
|
+
try {
|
|
293
|
+
this.persistSessionActivity(event);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error(
|
|
296
|
+
`[pi-web] 会话活跃写库失败(${event.type}):`,
|
|
297
|
+
error instanceof Error ? error.message : String(error),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
233
300
|
this.emit(event);
|
|
234
301
|
});
|
|
235
302
|
this.resetIdleTimer();
|
|
236
303
|
}
|
|
237
304
|
|
|
305
|
+
/**
|
|
306
|
+
* 事件 → session_meta 写入(只 UPDATE;行由创建/扫描链路建立):
|
|
307
|
+
* · agent_start → modified 前移(运行中会话浮顶)
|
|
308
|
+
* · message_end → 本轮最后一条 assistant 文本(缓存,不落库)
|
|
309
|
+
* · agent_settled → last_reply + modified(一轮循环真正结束;用户取消走同一路径)
|
|
310
|
+
* 用 agent_settled 而非 agent_end:后者一轮里可能多次(重试/compaction/queue 续跑)。
|
|
311
|
+
*/
|
|
312
|
+
private persistSessionActivity(event: AgentEvent): void {
|
|
313
|
+
const effect = this.activityTracker.handle(event as { type: string; message?: AgentLikeMessage });
|
|
314
|
+
if (!effect) return;
|
|
315
|
+
const sessionId = this.inner.sessionId;
|
|
316
|
+
if (!sessionId) return;
|
|
317
|
+
if (effect.kind === "touch") {
|
|
318
|
+
touchSessionActivity(sessionId);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
recordSessionOutcome(sessionId, effect);
|
|
322
|
+
// 首条用户消息此刻已在内存 entries 里(不读文件,读取路径零扫盘)。
|
|
323
|
+
fillFirstMessageIfEmpty(sessionId, firstUserMessageOf(this.inner.sessionManager.getEntries()));
|
|
324
|
+
}
|
|
325
|
+
|
|
238
326
|
setForceEmptySystemPrompt(force: boolean): void {
|
|
239
327
|
this.forceEmptySystemPrompt = force;
|
|
240
328
|
this.applyForcedEmptySystemPrompt();
|
|
@@ -250,10 +338,31 @@ export class AgentSessionWrapper {
|
|
|
250
338
|
await this.waitForExtensionsBound();
|
|
251
339
|
}
|
|
252
340
|
|
|
341
|
+
private applyExactSystemPrompt(): void {
|
|
342
|
+
if (!this.exactSystemPrompt || !this.inner.agent.state) return;
|
|
343
|
+
this.inner.agent.state.systemPrompt = this.exactSystemPrompt();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private installExactSystemPromptContinuation(): void {
|
|
347
|
+
if (!this.exactSystemPrompt) return;
|
|
348
|
+
const previous = this.inner.agent.prepareNextTurnWithContext;
|
|
349
|
+
this.inner.agent.prepareNextTurnWithContext = async (turn, signal) => {
|
|
350
|
+
const prepared = await previous?.(turn, signal);
|
|
351
|
+
return {
|
|
352
|
+
...prepared,
|
|
353
|
+
context: {
|
|
354
|
+
...(prepared?.context ?? turn.context),
|
|
355
|
+
systemPrompt: this.exactSystemPrompt!(),
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
253
361
|
private ensureExtensionsBound(options: ExtensionBindingOptions = {}): Promise<void> {
|
|
254
362
|
if (options.forceEmptySystemPrompt) this.forceEmptySystemPrompt = true;
|
|
255
363
|
if (this.extensionsBound) {
|
|
256
364
|
this.applyForcedEmptySystemPrompt();
|
|
365
|
+
this.applyExactSystemPrompt();
|
|
257
366
|
return Promise.resolve();
|
|
258
367
|
}
|
|
259
368
|
if (this.extensionBindingPromise) return this.extensionBindingPromise;
|
|
@@ -293,6 +402,7 @@ export class AgentSessionWrapper {
|
|
|
293
402
|
}
|
|
294
403
|
this.extensionsBound = true;
|
|
295
404
|
this.applyForcedEmptySystemPrompt();
|
|
405
|
+
this.applyExactSystemPrompt();
|
|
296
406
|
console.log(`[pi-web] session_start dispatched to extensions for session ${this.inner.sessionId}`);
|
|
297
407
|
})().catch((err) => {
|
|
298
408
|
this.extensionBindingError = err;
|
|
@@ -361,6 +471,8 @@ export class AgentSessionWrapper {
|
|
|
361
471
|
}
|
|
362
472
|
|
|
363
473
|
private resetIdleTimer(): void {
|
|
474
|
+
// A resolved timeout of 0 disables idle shutdown entirely.
|
|
475
|
+
if (SESSION_IDLE_TIMEOUT_MS === 0) return;
|
|
364
476
|
if (this.idleTimer) clearTimeout(this.idleTimer);
|
|
365
477
|
this.idleTimer = setTimeout(() => {
|
|
366
478
|
if (this.isRunning()) {
|
|
@@ -370,7 +482,7 @@ export class AgentSessionWrapper {
|
|
|
370
482
|
void this.shutdown().catch((error) => {
|
|
371
483
|
console.error("[pi-web] failed to shut down idle session:", error instanceof Error ? error.message : error);
|
|
372
484
|
});
|
|
373
|
-
},
|
|
485
|
+
}, SESSION_IDLE_TIMEOUT_MS);
|
|
374
486
|
}
|
|
375
487
|
|
|
376
488
|
onEvent(listener: EventListener): () => void {
|
|
@@ -531,6 +643,42 @@ export class AgentSessionWrapper {
|
|
|
531
643
|
return { id: model.id, provider: model.provider };
|
|
532
644
|
}
|
|
533
645
|
|
|
646
|
+
case "fork_branch": {
|
|
647
|
+
if (this.isRunning()) {
|
|
648
|
+
throw new Error("Cannot fork while the session is running");
|
|
649
|
+
}
|
|
650
|
+
const entryId = command.entryId as string;
|
|
651
|
+
const sessionManager = this.inner.sessionManager;
|
|
652
|
+
const currentSessionFile = this.inner.sessionFile;
|
|
653
|
+
if (!sessionManager.isPersisted()) return { cancelled: true };
|
|
654
|
+
if (!currentSessionFile) throw new Error("Persisted session is missing a session file");
|
|
655
|
+
if (!sessionManager.getEntry(entryId)) throw new Error("Invalid entry ID for forking");
|
|
656
|
+
|
|
657
|
+
const sessionDir = sessionManager.getSessionDir();
|
|
658
|
+
const sourceManager = SessionManager.open(currentSessionFile, sessionDir);
|
|
659
|
+
const forkedPath = sourceManager.createBranchedSession(entryId);
|
|
660
|
+
if (!forkedPath) throw new Error("Failed to create forked session");
|
|
661
|
+
// SDK 惰性落盘契约:fork 点之前无 assistant 消息时不写文件(flushed=false),
|
|
662
|
+
// 只返回路径字符串。不强制落盘则后续 open 不存在文件会 newSession 生成新 id,
|
|
663
|
+
// 索引/列表/看板全部读不到——必须和 fork case 一样手动写盘。
|
|
664
|
+
if (!existsSync(forkedPath)) {
|
|
665
|
+
const content = [sourceManager.getHeader(), ...sourceManager.getEntries()]
|
|
666
|
+
.map((entry) => JSON.stringify(entry))
|
|
667
|
+
.join("\n") + "\n";
|
|
668
|
+
writeFileSync(forkedPath, content, { encoding: "utf8", flag: "wx" });
|
|
669
|
+
(sourceManager as unknown as { flushed: boolean }).flushed = true;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const newSessionId = SessionManager.open(forkedPath, sessionDir).getSessionId();
|
|
673
|
+
cacheSessionPath(newSessionId, forkedPath);
|
|
674
|
+
invalidateSessionListCache();
|
|
675
|
+
// 立即进 session_meta 索引(否则要等 30s 后台扫描才出现在会话列表);
|
|
676
|
+
// 归属继承:引用分支与源会话同任务(否则只会在聊天区当孤儿根出现)。
|
|
677
|
+
const sourceSessionId = sessionManager.getSessionId();
|
|
678
|
+
await indexSessionFileNow(forkedPath, sourceSessionId, taskForSession(sourceSessionId));
|
|
679
|
+
return { cancelled: false, newSessionId };
|
|
680
|
+
}
|
|
681
|
+
|
|
534
682
|
case "fork": {
|
|
535
683
|
if (this.inner.isBashRunning) {
|
|
536
684
|
throw new Error("Cannot fork while a shell command is running");
|
|
@@ -590,6 +738,7 @@ export class AgentSessionWrapper {
|
|
|
590
738
|
// resolveProject 的 await 不能发生在 registry 还挂着已 fork wrapper 的窗口里;
|
|
591
739
|
// 建行只用局部变量(cwd/newSessionFile/sourceSessionId),不依赖 wrapper。
|
|
592
740
|
const cwd = sessionManager.getCwd();
|
|
741
|
+
const sourceTaskId = taskForSession(sourceSessionId); // 归属继承:fork 子会话与父同任务
|
|
593
742
|
await this.shutdown();
|
|
594
743
|
try {
|
|
595
744
|
const project = await resolveProject(cwd ?? "");
|
|
@@ -598,13 +747,28 @@ export class AgentSessionWrapper {
|
|
|
598
747
|
cwd: cwd ?? "",
|
|
599
748
|
projectKey: projectIdentityKey(project?.projectRoot ?? cwd ?? ""),
|
|
600
749
|
parentId: sourceSessionId,
|
|
750
|
+
taskId: sourceTaskId, // 建行即带归属,否则子会话先以临时会话落在聊天区(V1)
|
|
601
751
|
});
|
|
752
|
+
} catch (error) {
|
|
753
|
+
// 不阻塞 fork(文件已经在了,pi 侧已成功),但必须可见:静默失败会让子会话
|
|
754
|
+
// 以「无归属临时会话」出现在聊天区(也是本次要修的 V1 病灶);扫描器的归属
|
|
755
|
+
// 收敛是第二道网,不能当成唯一防线。
|
|
756
|
+
console.error(
|
|
757
|
+
`[pi-web] fork 建行失败 session=${newSessionId}:`,
|
|
758
|
+
error instanceof Error ? error.message : String(error),
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
602
762
|
// fork 卡即时入板:源会话归属任务 → 精准 reconcile 该任务看板。
|
|
603
763
|
// await 保证卡先落 yjs 再返回(前端响应到达时卡已存在,无并发写窗口);
|
|
604
|
-
// 失败不阻塞 fork,10s
|
|
764
|
+
// 失败不阻塞 fork,10s 定时兜底。单独一个 try:与建行失败分开报错,避免
|
|
765
|
+
// 入板失败被误报成「建行失败」。
|
|
605
766
|
await reconcileForkBoard(sourceSessionId);
|
|
606
|
-
} catch {
|
|
607
|
-
|
|
767
|
+
} catch (error) {
|
|
768
|
+
console.error(
|
|
769
|
+
`[pi-web] fork 入板失败 source=${sourceSessionId}:`,
|
|
770
|
+
error instanceof Error ? error.message : String(error),
|
|
771
|
+
);
|
|
608
772
|
}
|
|
609
773
|
return { cancelled: false, newSessionId };
|
|
610
774
|
}
|
|
@@ -644,6 +808,15 @@ export class AgentSessionWrapper {
|
|
|
644
808
|
const name = (command.name as string | undefined)?.trim();
|
|
645
809
|
if (!name) throw new Error("Session name cannot be empty");
|
|
646
810
|
this.inner.setSessionName(name);
|
|
811
|
+
// 列表索引同步:pi 侧改名成功即写 session_meta.title(前端侧栏/看板卡
|
|
812
|
+
// 改名走这条 RPC,不落库则刷新后退回旧标题)。失败处理与路由层同源但
|
|
813
|
+
// 语义有别:这里只记日志、不抛——RPC 已经改了文件名,抛错对调用方没有
|
|
814
|
+
// 可恢复动作,还会断掉命令响应/事件流。
|
|
815
|
+
try {
|
|
816
|
+
await setSessionTitle(this.inner.sessionId, name);
|
|
817
|
+
} catch (error) {
|
|
818
|
+
console.error("[pi-web] 会话标题写库失败:", error);
|
|
819
|
+
}
|
|
647
820
|
invalidateSessionListCache();
|
|
648
821
|
return null;
|
|
649
822
|
}
|
|
@@ -685,9 +858,10 @@ export class AgentSessionWrapper {
|
|
|
685
858
|
case "get_tools": {
|
|
686
859
|
const all: ToolInfo[] = this.inner.getAllTools();
|
|
687
860
|
const active = new Set<string>(this.inner.getActiveToolNames());
|
|
861
|
+
// 对齐上游:展开整个 ToolInfo(含 parameters / promptGuidelines),
|
|
862
|
+
// 之前只挑 name/description/active 会把参数 schema 丢掉 → 面板参数全是“无”。
|
|
688
863
|
return all.map((t) => ({
|
|
689
|
-
|
|
690
|
-
description: t.description,
|
|
864
|
+
...t,
|
|
691
865
|
active: active.has(t.name),
|
|
692
866
|
}));
|
|
693
867
|
}
|
|
@@ -723,6 +897,9 @@ export class AgentSessionWrapper {
|
|
|
723
897
|
|
|
724
898
|
case "set_tools": {
|
|
725
899
|
const toolNames = command.toolNames as string[];
|
|
900
|
+
if (readSubagentSessionResources(this.inner.sessionManager.getEntries() as unknown as SessionEntry[])) {
|
|
901
|
+
throw new Error("Subagent tool selection is fixed by its profile");
|
|
902
|
+
}
|
|
726
903
|
this.setForceEmptySystemPrompt(toolNames.length === 0);
|
|
727
904
|
this.inner.setActiveToolsByName(withExtensionTools(this.inner, toolNames));
|
|
728
905
|
this.applyForcedEmptySystemPrompt();
|
|
@@ -1468,8 +1645,13 @@ async function persistNewSessionFile(manager: SessionManager, sessionId: string)
|
|
|
1468
1645
|
// 与扫描器同源归一化(Windows 大小写折叠等),防新建会话 project_key 与列表查询键不一致
|
|
1469
1646
|
projectKey: projectIdentityKey(project?.projectRoot ?? cwd ?? ""),
|
|
1470
1647
|
});
|
|
1471
|
-
} catch {
|
|
1472
|
-
//
|
|
1648
|
+
} catch (error) {
|
|
1649
|
+
// 建行失败不阻塞开会话(文件已落盘,运行时列表由 runtime union 覆盖),但必须可见:
|
|
1650
|
+
// 静默会让该会话最多 30s 不在列表/任务区(扫描器会补行,但不能当成唯一防线)。
|
|
1651
|
+
console.error(
|
|
1652
|
+
`[pi-web] 新会话建行失败 session=${sessionId}:`,
|
|
1653
|
+
error instanceof Error ? error.message : String(error),
|
|
1654
|
+
);
|
|
1473
1655
|
}
|
|
1474
1656
|
}
|
|
1475
1657
|
|
|
@@ -1487,6 +1669,19 @@ function runtimeMessageText(entry: SessionMessageEntry): string {
|
|
|
1487
1669
|
.join(" ");
|
|
1488
1670
|
}
|
|
1489
1671
|
|
|
1672
|
+
/**
|
|
1673
|
+
* 内存 entries 里首条用户消息的简述(截断与扫描器同一上限)。
|
|
1674
|
+
* 会话活跃事件落库时用它回填 first_message——读取路径不再读文件。
|
|
1675
|
+
*/
|
|
1676
|
+
function firstUserMessageOf(entries: unknown[]): string {
|
|
1677
|
+
for (const entry of entries as SessionMessageEntry[]) {
|
|
1678
|
+
if (entry?.type !== "message" || entry.message?.role !== "user") continue;
|
|
1679
|
+
const text = runtimeMessageText(entry).trim();
|
|
1680
|
+
if (text) return text.slice(0, FIRST_MESSAGE_PREVIEW_LENGTH);
|
|
1681
|
+
}
|
|
1682
|
+
return "";
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1490
1685
|
function runtimeMessageActivityMs(entry: SessionMessageEntry): number | undefined {
|
|
1491
1686
|
if (entry.message.role !== "user" && entry.message.role !== "assistant") return undefined;
|
|
1492
1687
|
if (typeof entry.message.timestamp === "number") return entry.message.timestamp;
|
|
@@ -1518,6 +1713,8 @@ export function getRpcSessionInfos(): SessionInfo[] {
|
|
|
1518
1713
|
// loads commands. Do not leak it into history before a prompt is accepted.
|
|
1519
1714
|
if (!persisted && (!session.isRunning() || !firstUserMessage)) continue;
|
|
1520
1715
|
|
|
1716
|
+
const subagent = readSubagentRun(entries as unknown as SessionEntry[], header?.id ?? session.sessionId, sessionFile ?? "");
|
|
1717
|
+
|
|
1521
1718
|
const created = header?.timestamp
|
|
1522
1719
|
?? entries[0]?.timestamp
|
|
1523
1720
|
?? new Date().toISOString();
|
|
@@ -1537,6 +1734,16 @@ export function getRpcSessionInfos(): SessionInfo[] {
|
|
|
1537
1734
|
modified: new Date(lastActivityMs).toISOString(),
|
|
1538
1735
|
messageCount: messages.length,
|
|
1539
1736
|
firstMessage: firstUserMessage ? runtimeMessageText(firstUserMessage) || "(no messages)" : "(no messages)",
|
|
1737
|
+
...(subagent ? {
|
|
1738
|
+
parentSessionId: subagent.parentSessionId,
|
|
1739
|
+
relation: {
|
|
1740
|
+
kind: "subagent" as const,
|
|
1741
|
+
parentSessionId: subagent.parentSessionId,
|
|
1742
|
+
profile: subagent.profile,
|
|
1743
|
+
description: subagent.description,
|
|
1744
|
+
status: session.isRunning() ? "running" as const : subagent.status,
|
|
1745
|
+
},
|
|
1746
|
+
} : {}),
|
|
1540
1747
|
transient: !persisted,
|
|
1541
1748
|
});
|
|
1542
1749
|
}
|
|
@@ -1646,14 +1853,25 @@ export async function startRpcSession(
|
|
|
1646
1853
|
const sessionCwd = sessionManager.getCwd();
|
|
1647
1854
|
const finishStartingSession = trackStartingSession(sessionCwd);
|
|
1648
1855
|
const starting = (async () => {
|
|
1856
|
+
// subagent 会话(含恢复)按 profile 的资源/工具加载选项重建;普通会话不命中。
|
|
1857
|
+
const subagentResources = sessionFile
|
|
1858
|
+
? readSubagentSessionResources(sessionManager.getEntries() as unknown as SessionEntry[])
|
|
1859
|
+
: null;
|
|
1860
|
+
const subagentLoadsResources = Boolean(subagentResources?.loadExtensions || subagentResources?.loadSkills);
|
|
1861
|
+
// chatOnly 仅当“显式空工具列表”时为真:subagent 会话按 profile 的 tools 判定,
|
|
1862
|
+
// 普通会话按调用方传入的 toolNames(undefined = 默认全量,[] = 全关)。
|
|
1863
|
+
const chatOnly = subagentResources
|
|
1864
|
+
? subagentResources.tools.length === 0 && !subagentLoadsResources
|
|
1865
|
+
: toolNames?.length === 0;
|
|
1866
|
+
|
|
1649
1867
|
// Some extensions access the SDK's global theme even outside the terminal UI.
|
|
1650
|
-
initTheme();
|
|
1868
|
+
if (!chatOnly) initTheme();
|
|
1651
1869
|
const agentDir = getAgentDir();
|
|
1652
1870
|
|
|
1653
1871
|
// Determine which tools to pass based on requested toolNames.
|
|
1654
1872
|
// Since v0.68.0, session creation expects string[] tool names instead of Tool[] instances.
|
|
1655
|
-
let toolsOption: string[] | undefined;
|
|
1656
|
-
if (toolNames !== undefined) {
|
|
1873
|
+
let toolsOption: string[] | undefined = subagentResources?.tools;
|
|
1874
|
+
if (!subagentResources && toolNames !== undefined) {
|
|
1657
1875
|
// toolNames === [] -> "all off" (an empty allow-list disables every tool).
|
|
1658
1876
|
// Otherwise DO NOT pass a builtin-only allow-list: passing CODING_TOOL_NAMES
|
|
1659
1877
|
// set allowedToolNames to coding builtins only, which filtered every
|
|
@@ -1674,18 +1892,37 @@ export async function startRpcSession(
|
|
|
1674
1892
|
cwd: sessionCwd,
|
|
1675
1893
|
agentDir,
|
|
1676
1894
|
settingsManager,
|
|
1677
|
-
resourceLoaderOptions:
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1895
|
+
resourceLoaderOptions: subagentResources
|
|
1896
|
+
? {
|
|
1897
|
+
noExtensions: !subagentResources.loadExtensions,
|
|
1898
|
+
noSkills: !subagentResources.loadSkills,
|
|
1899
|
+
noPromptTemplates: true,
|
|
1900
|
+
noThemes: true,
|
|
1901
|
+
noContextFiles: true,
|
|
1902
|
+
systemPrompt: " ",
|
|
1903
|
+
appendSystemPrompt: subagentResources.appendSystemPrompt,
|
|
1904
|
+
}
|
|
1905
|
+
: chatOnly
|
|
1906
|
+
? CHAT_ONLY_RESOURCE_LOADER_OPTIONS
|
|
1907
|
+
: {
|
|
1908
|
+
extensionFactories: [
|
|
1909
|
+
createProjectCommandBashExtension({
|
|
1910
|
+
cwd: sessionCwd,
|
|
1911
|
+
settings: settingsManager,
|
|
1912
|
+
}),
|
|
1913
|
+
// 内建会话 TODO(工具 + pi-todo.state 快照),装 pi-web-sky 即自带,
|
|
1914
|
+
// 无需用户安装任何 pi 包。工具重名时用户扩展优先(见 pi-todo 文档)。
|
|
1915
|
+
createTodoExtension(),
|
|
1916
|
+
// 内置 subagent(Agent/steer_subagent/get_subagent_result 工具),
|
|
1917
|
+
// 开关见 agents 设置(isBuiltInSubagentsEnabled)。
|
|
1918
|
+
createSubagentExtension(
|
|
1919
|
+
SUBAGENT_CONTROLLER.extensionRuntime,
|
|
1920
|
+
() => listSubagentProfiles(sessionCwd),
|
|
1921
|
+
isBuiltInSubagentsEnabled,
|
|
1922
|
+
),
|
|
1923
|
+
],
|
|
1924
|
+
extensionsOverride: (base) => preferUserBashExtension(preferPiWebSubagentExtension(base)),
|
|
1925
|
+
},
|
|
1689
1926
|
...(trustReloadOptions ? { resourceLoaderReloadOptions: trustReloadOptions } : {}),
|
|
1690
1927
|
});
|
|
1691
1928
|
const scope = await resolveVisibleModels(
|
|
@@ -1751,7 +1988,7 @@ export async function startRpcSession(
|
|
|
1751
1988
|
|
|
1752
1989
|
wrapper.onDestroy(() => registry.delete(realSessionId));
|
|
1753
1990
|
registry.set(realSessionId, wrapper);
|
|
1754
|
-
wrapper.beginExtensionBinding({ forceEmptySystemPrompt: toolNames?.length === 0 });
|
|
1991
|
+
if (!chatOnly) wrapper.beginExtensionBinding({ forceEmptySystemPrompt: toolNames?.length === 0 });
|
|
1755
1992
|
|
|
1756
1993
|
return { session: wrapper, realSessionId };
|
|
1757
1994
|
})().finally(() => {
|
|
@@ -1762,3 +1999,39 @@ export async function startRpcSession(
|
|
|
1762
1999
|
locks.set(sessionId, starting);
|
|
1763
2000
|
return starting;
|
|
1764
2001
|
}
|
|
2002
|
+
|
|
2003
|
+
const SUBAGENT_CONTROLLER = createSubagentController({
|
|
2004
|
+
getSession: (sessionId) => getRegistry().get(sessionId),
|
|
2005
|
+
registerSession: (inner, options) => {
|
|
2006
|
+
const wrapper = new AgentSessionWrapper(inner, {
|
|
2007
|
+
...(options?.exactSystemPrompt !== undefined
|
|
2008
|
+
? { exactSystemPrompt: () => options.exactSystemPrompt! }
|
|
2009
|
+
: {}),
|
|
2010
|
+
chatOnly: options?.chatOnly,
|
|
2011
|
+
});
|
|
2012
|
+
const realSessionId = inner.sessionId as string;
|
|
2013
|
+
const realSessionFile = inner.sessionFile as string | undefined;
|
|
2014
|
+
if (realSessionFile) cacheSessionPath(realSessionId, realSessionFile);
|
|
2015
|
+
wrapper.onDestroy(() => getRegistry().delete(realSessionId));
|
|
2016
|
+
getRegistry().set(realSessionId, wrapper);
|
|
2017
|
+
wrapper.start();
|
|
2018
|
+
if (!wrapper.isChatOnly()) wrapper.beginExtensionBinding();
|
|
2019
|
+
},
|
|
2020
|
+
reopenSession: async (sessionId, sessionFile) =>
|
|
2021
|
+
(await startRpcSession(sessionId, sessionFile, undefined)).session,
|
|
2022
|
+
resolveSessionPath,
|
|
2023
|
+
invalidateSessionList: invalidateSessionListCache,
|
|
2024
|
+
isBuiltInSubagentsEnabled,
|
|
2025
|
+
});
|
|
2026
|
+
|
|
2027
|
+
export function getSubagentRun(sessionId: string) {
|
|
2028
|
+
return SUBAGENT_CONTROLLER.get(sessionId);
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
export function steerSubagent(sessionId: string, message: string) {
|
|
2032
|
+
return SUBAGENT_CONTROLLER.steer(sessionId, message);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
export function abortSubagent(sessionId: string) {
|
|
2036
|
+
return SUBAGENT_CONTROLLER.abort(sessionId);
|
|
2037
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createJiti } from "jiti";
|
|
4
|
+
|
|
5
|
+
const jiti = createJiti(import.meta.url, {
|
|
6
|
+
interopDefault: true,
|
|
7
|
+
moduleCache: false,
|
|
8
|
+
alias: { "@": process.cwd() },
|
|
9
|
+
});
|
|
10
|
+
const { createSessionActivityTracker, lastAssistantText } = await jiti.import("./session-activity.ts");
|
|
11
|
+
|
|
12
|
+
const assistant = (text, ts, stopReason = "stop") => ({
|
|
13
|
+
role: "assistant",
|
|
14
|
+
timestamp: ts,
|
|
15
|
+
stopReason,
|
|
16
|
+
content: [{ type: "text", text }],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/** 依次喂事件,返回所有非 null 效果(真实调用方的用法:一轮事件流按序处理)。 */
|
|
20
|
+
function run(events) {
|
|
21
|
+
const tracker = createSessionActivityTracker();
|
|
22
|
+
const effects = [];
|
|
23
|
+
for (const event of events) {
|
|
24
|
+
const effect = tracker.handle(event);
|
|
25
|
+
if (effect) effects.push(effect);
|
|
26
|
+
}
|
|
27
|
+
return effects;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test("agent_start → touch(只刷活跃时间,浮动到列表顶部)", () => {
|
|
31
|
+
assert.deepEqual(run([{ type: "agent_start" }]), [{ kind: "touch" }]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("单条 message_end 不落 outcome(不是循环最后一条)", () => {
|
|
35
|
+
assert.deepEqual(run([{ type: "message_end", message: assistant("中间回复", 1000) }]), []);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("agent_settled → outcome(带最后一条 assistant 文本与时间戳)", () => {
|
|
39
|
+
const effects = run([
|
|
40
|
+
{ type: "message_end", message: assistant("第一条", 1000) },
|
|
41
|
+
{ type: "message_end", message: { role: "user", content: "再问" } },
|
|
42
|
+
{ type: "message_end", message: assistant("最后一条回复", 2000) },
|
|
43
|
+
{ type: "agent_settled" },
|
|
44
|
+
]);
|
|
45
|
+
assert.deepEqual(effects, [{ kind: "outcome", lastReply: "最后一条回复", at: 2000 }]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("用户取消(stopReason=aborted)同样落 outcome", () => {
|
|
49
|
+
const effects = run([
|
|
50
|
+
{ type: "message_end", message: assistant("被打断的半截回复", 3000, "aborted") },
|
|
51
|
+
{ type: "agent_settled" },
|
|
52
|
+
]);
|
|
53
|
+
assert.deepEqual(effects, [{ kind: "outcome", lastReply: "被打断的半截回复", at: 3000 }]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("本轮没采到任何文本 → 只 touch(空文本不清库)", () => {
|
|
57
|
+
// 刚发出就被取消 / 整轮只有工具调用:agent_settled 不得产出 lastReply:"",
|
|
58
|
+
// 否则调用方会把已存的最后一条回复清空。
|
|
59
|
+
assert.deepEqual(run([{ type: "agent_start" }, { type: "agent_settled" }]), [
|
|
60
|
+
{ kind: "touch" },
|
|
61
|
+
{ kind: "touch" },
|
|
62
|
+
]);
|
|
63
|
+
assert.deepEqual(
|
|
64
|
+
run([
|
|
65
|
+
{ type: "message_end", message: { role: "assistant", timestamp: 1, content: [{ type: "toolCall", name: "bash" }] } },
|
|
66
|
+
{ type: "message_end", message: { role: "assistant", timestamp: 2, content: [{ type: "thinking", thinking: "想一想" }] } },
|
|
67
|
+
{ type: "message_end", message: { role: "assistant", timestamp: 3, content: [{ type: "text", text: " " }] } },
|
|
68
|
+
{ type: "agent_settled" },
|
|
69
|
+
]),
|
|
70
|
+
[{ kind: "touch" }],
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("settle 后 pending 归零:第二段 settle(无新消息)只 touch", () => {
|
|
75
|
+
const tracker = createSessionActivityTracker();
|
|
76
|
+
tracker.handle({ type: "message_end", message: assistant("回复", 1000) });
|
|
77
|
+
assert.deepEqual(tracker.handle({ type: "agent_settled" }), {
|
|
78
|
+
kind: "outcome",
|
|
79
|
+
lastReply: "回复",
|
|
80
|
+
at: 1000,
|
|
81
|
+
});
|
|
82
|
+
assert.deepEqual(tracker.handle({ type: "agent_settled" }), { kind: "touch" });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("tracker 按实例持有状态:并发会话不串台", () => {
|
|
86
|
+
const a = createSessionActivityTracker();
|
|
87
|
+
const b = createSessionActivityTracker();
|
|
88
|
+
a.handle({ type: "message_end", message: assistant("A 的回复", 1000) });
|
|
89
|
+
b.handle({ type: "message_end", message: assistant("B 的回复", 2000) });
|
|
90
|
+
assert.deepEqual(b.handle({ type: "agent_settled" }), {
|
|
91
|
+
kind: "outcome",
|
|
92
|
+
lastReply: "B 的回复",
|
|
93
|
+
at: 2000,
|
|
94
|
+
});
|
|
95
|
+
assert.deepEqual(a.handle({ type: "agent_settled" }), {
|
|
96
|
+
kind: "outcome",
|
|
97
|
+
lastReply: "A 的回复",
|
|
98
|
+
at: 1000,
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("无关事件(tool_execution_end 等)不产生效果,也不破坏 pending", () => {
|
|
103
|
+
const effects = run([
|
|
104
|
+
{ type: "message_end", message: assistant("回复", 1000) },
|
|
105
|
+
{ type: "tool_execution_start", toolName: "bash" },
|
|
106
|
+
{ type: "queue_update", steering: [] },
|
|
107
|
+
{ type: "agent_settled" },
|
|
108
|
+
]);
|
|
109
|
+
assert.deepEqual(effects, [{ kind: "outcome", lastReply: "回复", at: 1000 }]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("lastAssistantText:只取 text 块,thinking/toolCall 忽略", () => {
|
|
113
|
+
assert.equal(
|
|
114
|
+
lastAssistantText({
|
|
115
|
+
role: "assistant",
|
|
116
|
+
content: [{ type: "thinking", thinking: "x" }, { type: "text", text: "a" }, { type: "text", text: "b" }],
|
|
117
|
+
}),
|
|
118
|
+
"a\nb",
|
|
119
|
+
);
|
|
120
|
+
assert.equal(lastAssistantText({ role: "assistant", content: "plain" }), "plain");
|
|
121
|
+
assert.equal(lastAssistantText({ role: "assistant" }), "");
|
|
122
|
+
assert.equal(lastAssistantText({ role: "assistant", content: [{ type: "text", text: " " }] }), "");
|
|
123
|
+
});
|