@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
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
2
|
+
import {
|
|
3
|
+
defineTool,
|
|
4
|
+
type ExtensionContext,
|
|
5
|
+
type InlineExtension,
|
|
6
|
+
type LoadExtensionsResult,
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import {
|
|
9
|
+
SUBAGENT_CONTROL_TOOL_NAMES,
|
|
10
|
+
type SubagentProfile,
|
|
11
|
+
type SubagentRunInfo,
|
|
12
|
+
} from "./subagents";
|
|
13
|
+
import { MAX_SUBAGENT_INPUT_FILES } from "./subagent-input";
|
|
14
|
+
|
|
15
|
+
export const HOST_SUBAGENT_EXTENSION_NAME = "pi-web-subagents";
|
|
16
|
+
const HOST_SUBAGENT_EXTENSION_PATH = `<inline:${HOST_SUBAGENT_EXTENSION_NAME}>`;
|
|
17
|
+
const SUBAGENT_TOOL_NAMES = new Set<string>(SUBAGENT_CONTROL_TOOL_NAMES);
|
|
18
|
+
const LEGACY_SUBAGENT_PACKAGE_NAME = "pi-subagents";
|
|
19
|
+
|
|
20
|
+
export interface SubagentToolDetails {
|
|
21
|
+
kind: "pi-web-subagent";
|
|
22
|
+
sessionId: string;
|
|
23
|
+
profile: string;
|
|
24
|
+
description: string;
|
|
25
|
+
status: SubagentRunInfo["status"];
|
|
26
|
+
runInBackground: boolean;
|
|
27
|
+
createdAt: string;
|
|
28
|
+
completedAt?: string;
|
|
29
|
+
error?: string;
|
|
30
|
+
worktreePath?: string;
|
|
31
|
+
worktreeBranch?: string;
|
|
32
|
+
worktreeCleanupError?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface StartSubagentRequest {
|
|
36
|
+
parentContext: ExtensionContext;
|
|
37
|
+
parentToolCallId: string;
|
|
38
|
+
profile: string;
|
|
39
|
+
task: string;
|
|
40
|
+
inputFiles?: string[];
|
|
41
|
+
description: string;
|
|
42
|
+
runInBackground?: boolean;
|
|
43
|
+
model?: string;
|
|
44
|
+
thinking?: string;
|
|
45
|
+
maxTurns?: number;
|
|
46
|
+
inheritContext?: boolean;
|
|
47
|
+
isolation?: "worktree";
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
onUpdate?: (run: SubagentRunInfo) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ResumeSubagentRequest {
|
|
53
|
+
parentContext: ExtensionContext;
|
|
54
|
+
parentToolCallId: string;
|
|
55
|
+
sessionId: string;
|
|
56
|
+
task: string;
|
|
57
|
+
description: string;
|
|
58
|
+
runInBackground?: boolean;
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
onUpdate?: (run: SubagentRunInfo) => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SubagentExecution {
|
|
64
|
+
run: SubagentRunInfo;
|
|
65
|
+
completion: Promise<SubagentRunInfo>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SubagentExtensionRuntime {
|
|
69
|
+
start(request: StartSubagentRequest): Promise<SubagentExecution>;
|
|
70
|
+
resume(request: ResumeSubagentRequest): Promise<SubagentExecution>;
|
|
71
|
+
get(sessionId: string): Promise<SubagentRunInfo | null>;
|
|
72
|
+
steer(sessionId: string, message: string): Promise<void>;
|
|
73
|
+
notifyParent(run: SubagentRunInfo): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type SubagentProfileProvider = () => readonly SubagentProfile[];
|
|
77
|
+
export type SubagentEnabledProvider = () => boolean;
|
|
78
|
+
|
|
79
|
+
function agentTypeDescription(profiles: readonly SubagentProfile[]): string {
|
|
80
|
+
const available = profiles.filter((profile) => profile.enabled);
|
|
81
|
+
if (available.length === 0) return "No subagent profiles are currently enabled.";
|
|
82
|
+
return available.map((profile) => {
|
|
83
|
+
const details = [`Tools: ${profile.tools.length > 0 ? profile.tools.join(", ") : "none"}`];
|
|
84
|
+
if (profile.model) details.push(`Model: ${profile.model}`);
|
|
85
|
+
return `- ${profile.name}: ${profile.description} (${details.join("; ")})`;
|
|
86
|
+
}).join("\n");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function subagentToolDetails(run: SubagentRunInfo): SubagentToolDetails {
|
|
90
|
+
return {
|
|
91
|
+
kind: "pi-web-subagent",
|
|
92
|
+
sessionId: run.sessionId,
|
|
93
|
+
profile: run.profile,
|
|
94
|
+
description: run.description,
|
|
95
|
+
status: run.status,
|
|
96
|
+
runInBackground: run.runInBackground,
|
|
97
|
+
createdAt: run.createdAt,
|
|
98
|
+
...(run.completedAt ? { completedAt: run.completedAt } : {}),
|
|
99
|
+
...(run.error ? { error: run.error } : {}),
|
|
100
|
+
...(run.worktreePath ? { worktreePath: run.worktreePath } : {}),
|
|
101
|
+
...(run.worktreeBranch ? { worktreeBranch: run.worktreeBranch } : {}),
|
|
102
|
+
...(run.worktreeCleanupError ? { worktreeCleanupError: run.worktreeCleanupError } : {}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function subagentFinalText(run: SubagentRunInfo): string {
|
|
107
|
+
if (run.status === "starting" || run.status === "running") {
|
|
108
|
+
return `Subagent ${run.sessionId} is ${run.status}.`;
|
|
109
|
+
}
|
|
110
|
+
if (run.status === "completed") return run.result?.trim() || "Subagent completed without text output.";
|
|
111
|
+
if (run.status === "aborted") return `Subagent ${run.sessionId} was stopped.`;
|
|
112
|
+
if (run.status === "interrupted") return `Subagent ${run.sessionId} was interrupted before completion.`;
|
|
113
|
+
return `Subagent ${run.sessionId} failed: ${run.error ?? "Unknown error"}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function createSubagentExtension(
|
|
117
|
+
runtime: SubagentExtensionRuntime,
|
|
118
|
+
getProfiles: SubagentProfileProvider,
|
|
119
|
+
isEnabled: SubagentEnabledProvider = () => true,
|
|
120
|
+
): InlineExtension {
|
|
121
|
+
return {
|
|
122
|
+
name: HOST_SUBAGENT_EXTENSION_NAME,
|
|
123
|
+
hidden: true,
|
|
124
|
+
factory: (pi) => {
|
|
125
|
+
if (!isEnabled()) return;
|
|
126
|
+
const profiles = getProfiles().filter((profile) => profile.enabled);
|
|
127
|
+
const profileNames = profiles.map((profile) => profile.name);
|
|
128
|
+
const availableTypes = profileNames.length > 0 ? profileNames.join(", ") : "none";
|
|
129
|
+
pi.registerTool(defineTool({
|
|
130
|
+
name: "Agent",
|
|
131
|
+
label: "Agent",
|
|
132
|
+
description: `Delegate a focused task to a configured subagent. Each subagent runs as a full, inspectable Pi session. Use background mode for independent work and foreground mode when the result is needed immediately.\n\nAvailable agent types:\n${agentTypeDescription(profiles)}`,
|
|
133
|
+
promptSnippet: "Delegate a focused task to an inspectable subagent session",
|
|
134
|
+
promptGuidelines: [
|
|
135
|
+
"Use Agent for a focused task that benefits from an isolated context.",
|
|
136
|
+
"Use multiple background Agent calls in the same response for independent parallel work.",
|
|
137
|
+
"Do not duplicate work already delegated to a running subagent.",
|
|
138
|
+
],
|
|
139
|
+
executionMode: "parallel",
|
|
140
|
+
parameters: Type.Object({
|
|
141
|
+
subagent_type: Type.Optional(Type.String({ description: `Configured agent profile. Available types: ${availableTypes}. Default: general-purpose.` })),
|
|
142
|
+
prompt: Type.String({ description: "The complete task for the subagent." }),
|
|
143
|
+
resume: Type.Optional(Type.String({ description: "Existing subagent session ID to continue instead of creating a new session." })),
|
|
144
|
+
input_files: Type.Optional(Type.Array(Type.String(), {
|
|
145
|
+
description: "UTF-8 text files under the session cwd to include with the task.",
|
|
146
|
+
maxItems: MAX_SUBAGENT_INPUT_FILES,
|
|
147
|
+
})),
|
|
148
|
+
description: Type.String({ description: "Short activity label shown in the UI." }),
|
|
149
|
+
run_in_background: Type.Optional(Type.Boolean({ description: "Return immediately and notify this session when complete." })),
|
|
150
|
+
model: Type.Optional(Type.String({ description: "Optional provider/modelId override." })),
|
|
151
|
+
thinking: Type.Optional(Type.String({ description: "Optional thinking level override." })),
|
|
152
|
+
max_turns: Type.Optional(Type.Number({ description: "Optional positive agent turn limit." })),
|
|
153
|
+
inherit_context: Type.Optional(Type.Boolean({ description: "Include the parent session's active conversation context." })),
|
|
154
|
+
isolation: Type.Optional(Type.String({ description: "Run the subagent in an isolated git worktree." })),
|
|
155
|
+
}),
|
|
156
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
157
|
+
try {
|
|
158
|
+
const resume = params.resume?.trim();
|
|
159
|
+
const execution = resume
|
|
160
|
+
? await runtime.resume({
|
|
161
|
+
parentContext: ctx,
|
|
162
|
+
parentToolCallId: toolCallId,
|
|
163
|
+
sessionId: resume,
|
|
164
|
+
task: params.prompt,
|
|
165
|
+
description: params.description,
|
|
166
|
+
...(params.run_in_background !== undefined ? { runInBackground: params.run_in_background } : {}),
|
|
167
|
+
signal,
|
|
168
|
+
onUpdate: (run) => onUpdate?.({
|
|
169
|
+
content: [{ type: "text", text: `${run.profile}: ${run.description} (${run.status})` }],
|
|
170
|
+
details: subagentToolDetails(run),
|
|
171
|
+
}),
|
|
172
|
+
})
|
|
173
|
+
: await runtime.start({
|
|
174
|
+
parentContext: ctx,
|
|
175
|
+
parentToolCallId: toolCallId,
|
|
176
|
+
profile: params.subagent_type ?? "general-purpose",
|
|
177
|
+
task: params.prompt,
|
|
178
|
+
...(params.input_files ? { inputFiles: params.input_files } : {}),
|
|
179
|
+
description: params.description,
|
|
180
|
+
...(params.run_in_background !== undefined ? { runInBackground: params.run_in_background } : {}),
|
|
181
|
+
...(params.model ? { model: params.model } : {}),
|
|
182
|
+
...(params.thinking ? { thinking: params.thinking } : {}),
|
|
183
|
+
...(params.max_turns ? { maxTurns: params.max_turns } : {}),
|
|
184
|
+
...(params.inherit_context !== undefined ? { inheritContext: params.inherit_context } : {}),
|
|
185
|
+
...(params.isolation === "worktree" ? { isolation: "worktree" as const } : {}),
|
|
186
|
+
signal,
|
|
187
|
+
onUpdate: (run) => onUpdate?.({
|
|
188
|
+
content: [{ type: "text", text: `${run.profile}: ${run.description} (${run.status})` }],
|
|
189
|
+
details: subagentToolDetails(run),
|
|
190
|
+
}),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (execution.run.runInBackground) {
|
|
194
|
+
void execution.completion
|
|
195
|
+
.then((run) => runtime.notifyParent(run))
|
|
196
|
+
.catch((error) => {
|
|
197
|
+
console.error(
|
|
198
|
+
"[pi-web] failed to deliver subagent completion:",
|
|
199
|
+
error instanceof Error ? error.message : error,
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
return {
|
|
203
|
+
content: [{ type: "text", text: `Subagent started in background. Session ID: ${execution.run.sessionId}. You will be notified when it completes.` }],
|
|
204
|
+
details: subagentToolDetails(execution.run),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const run = await execution.completion;
|
|
209
|
+
return {
|
|
210
|
+
content: [{ type: "text", text: subagentFinalText(run) }],
|
|
211
|
+
details: subagentToolDetails(run),
|
|
212
|
+
...(run.status === "failed" ? { isError: true } : {}),
|
|
213
|
+
};
|
|
214
|
+
} catch (error) {
|
|
215
|
+
return {
|
|
216
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
217
|
+
details: undefined,
|
|
218
|
+
isError: true,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
}));
|
|
223
|
+
|
|
224
|
+
pi.registerTool(defineTool({
|
|
225
|
+
name: "get_subagent_result",
|
|
226
|
+
label: "Get agent result",
|
|
227
|
+
description: "Check an inspectable subagent session and retrieve its latest result.",
|
|
228
|
+
parameters: Type.Object({
|
|
229
|
+
agent_id: Type.String({ description: "Subagent session ID." }),
|
|
230
|
+
wait: Type.Optional(Type.Boolean({ description: "Wait until the subagent finishes." })),
|
|
231
|
+
}),
|
|
232
|
+
async execute(_toolCallId, params, signal) {
|
|
233
|
+
let run = await runtime.get(params.agent_id);
|
|
234
|
+
if (!run) return { content: [{ type: "text", text: `Subagent not found: ${params.agent_id}` }], details: undefined, isError: true };
|
|
235
|
+
while (params.wait && (run.status === "starting" || run.status === "running")) {
|
|
236
|
+
await new Promise<void>((resolve, reject) => {
|
|
237
|
+
const onAbort = () => {
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
reject(new Error("Result wait aborted"));
|
|
240
|
+
};
|
|
241
|
+
const timer = setTimeout(() => {
|
|
242
|
+
signal?.removeEventListener("abort", onAbort);
|
|
243
|
+
resolve();
|
|
244
|
+
}, 500);
|
|
245
|
+
if (signal?.aborted) onAbort();
|
|
246
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
247
|
+
});
|
|
248
|
+
run = await runtime.get(params.agent_id);
|
|
249
|
+
if (!run) return { content: [{ type: "text", text: `Subagent not found: ${params.agent_id}` }], details: undefined, isError: true };
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: "text", text: subagentFinalText(run) }],
|
|
253
|
+
details: subagentToolDetails(run),
|
|
254
|
+
...(run.status === "failed" ? { isError: true } : {}),
|
|
255
|
+
};
|
|
256
|
+
},
|
|
257
|
+
}));
|
|
258
|
+
|
|
259
|
+
pi.registerTool(defineTool({
|
|
260
|
+
name: "steer_subagent",
|
|
261
|
+
label: "Steer agent",
|
|
262
|
+
description: "Send a steering message to a currently running subagent session.",
|
|
263
|
+
parameters: Type.Object({
|
|
264
|
+
agent_id: Type.String({ description: "Subagent session ID." }),
|
|
265
|
+
message: Type.String({ description: "Instruction to inject after the current tool execution." }),
|
|
266
|
+
}),
|
|
267
|
+
async execute(_toolCallId, params) {
|
|
268
|
+
try {
|
|
269
|
+
await runtime.steer(params.agent_id, params.message);
|
|
270
|
+
return { content: [{ type: "text", text: `Steering message sent to ${params.agent_id}.` }], details: undefined };
|
|
271
|
+
} catch (error) {
|
|
272
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], details: undefined, isError: true };
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
}));
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Keep Pi Web's integrated implementation when the legacy package is loaded. */
|
|
281
|
+
export function preferPiWebSubagentExtension(base: LoadExtensionsResult): LoadExtensionsResult {
|
|
282
|
+
const host = base.extensions.find((extension) => extension.path === HOST_SUBAGENT_EXTENSION_PATH);
|
|
283
|
+
if (!host?.tools.has("Agent")) return base;
|
|
284
|
+
const legacyPaths = new Set(base.extensions
|
|
285
|
+
.filter((extension) => extension.path !== HOST_SUBAGENT_EXTENSION_PATH)
|
|
286
|
+
.filter((extension) => {
|
|
287
|
+
const source = extension.sourceInfo?.source ?? "";
|
|
288
|
+
const sourcePackage = source.replace(/^npm:/, "").split("@")[0];
|
|
289
|
+
const pathSegments = extension.path.replaceAll("\\", "/").split("/");
|
|
290
|
+
return sourcePackage === LEGACY_SUBAGENT_PACKAGE_NAME
|
|
291
|
+
|| pathSegments.some((segment) => segment === LEGACY_SUBAGENT_PACKAGE_NAME);
|
|
292
|
+
})
|
|
293
|
+
.filter((extension) => [...SUBAGENT_TOOL_NAMES].some((name) => extension.tools.has(name)))
|
|
294
|
+
.map((extension) => extension.path));
|
|
295
|
+
if (legacyPaths.size === 0) return base;
|
|
296
|
+
return {
|
|
297
|
+
...base,
|
|
298
|
+
extensions: base.extensions.filter((extension) => !legacyPaths.has(extension.path)),
|
|
299
|
+
errors: base.errors.filter((error) => {
|
|
300
|
+
if (legacyPaths.has(error.path)) return false;
|
|
301
|
+
if (error.path !== HOST_SUBAGENT_EXTENSION_PATH) return true;
|
|
302
|
+
return ![...legacyPaths].some((legacyPath) =>
|
|
303
|
+
[...SUBAGENT_TOOL_NAMES].some((name) =>
|
|
304
|
+
error.error === `Tool "${name}" conflicts with ${legacyPath}`
|
|
305
|
+
)
|
|
306
|
+
);
|
|
307
|
+
}),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, mkdir, rm, symlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import { createJiti } from "jiti";
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
appendSubagentInputFiles,
|
|
10
|
+
loadSubagentInputFiles,
|
|
11
|
+
MAX_SUBAGENT_INPUT_BYTES,
|
|
12
|
+
MAX_SUBAGENT_INPUT_FILES,
|
|
13
|
+
} = await createJiti(import.meta.url).import("./subagent-input.ts");
|
|
14
|
+
|
|
15
|
+
test("loads cwd-relative UTF-8 files and formats them as delegated user input", async () => {
|
|
16
|
+
const cwd = await mkdtemp(join(tmpdir(), "pi-web-subagent-input-"));
|
|
17
|
+
try {
|
|
18
|
+
await mkdir(join(cwd, "docs"));
|
|
19
|
+
await writeFile(join(cwd, "docs", "article.md"), "# Article\n\nEvidence.", "utf8");
|
|
20
|
+
|
|
21
|
+
const files = loadSubagentInputFiles(cwd, ["docs/article.md"]);
|
|
22
|
+
assert.deepEqual(files, [{ path: "docs/article.md", content: "# Article\n\nEvidence." }]);
|
|
23
|
+
assert.equal(
|
|
24
|
+
appendSubagentInputFiles("Analyze the argument.", files),
|
|
25
|
+
"Analyze the argument.\n\n<documents>\n<document path=\"docs/article.md\">\n# Article\n\nEvidence.\n</document>\n</documents>",
|
|
26
|
+
);
|
|
27
|
+
} finally {
|
|
28
|
+
await rm(cwd, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("rejects files outside cwd, including symlink targets", async () => {
|
|
33
|
+
const cwd = await mkdtemp(join(tmpdir(), "pi-web-subagent-input-cwd-"));
|
|
34
|
+
const outside = await mkdtemp(join(tmpdir(), "pi-web-subagent-input-outside-"));
|
|
35
|
+
try {
|
|
36
|
+
const outsideFile = join(outside, "article.md");
|
|
37
|
+
await writeFile(outsideFile, "Private", "utf8");
|
|
38
|
+
await symlink(outsideFile, join(cwd, "linked.md"));
|
|
39
|
+
|
|
40
|
+
assert.throws(() => loadSubagentInputFiles(cwd, [outsideFile]), /outside the session cwd/);
|
|
41
|
+
assert.throws(() => loadSubagentInputFiles(cwd, ["linked.md"]), /outside the session cwd/);
|
|
42
|
+
} finally {
|
|
43
|
+
await rm(cwd, { recursive: true, force: true });
|
|
44
|
+
await rm(outside, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("rejects invalid UTF-8 and bounded input sets", async () => {
|
|
49
|
+
const cwd = await mkdtemp(join(tmpdir(), "pi-web-subagent-input-"));
|
|
50
|
+
try {
|
|
51
|
+
await writeFile(join(cwd, "binary.txt"), Buffer.from([0xc3, 0x28]));
|
|
52
|
+
await writeFile(join(cwd, "large.txt"), "x".repeat(MAX_SUBAGENT_INPUT_BYTES + 1), "utf8");
|
|
53
|
+
|
|
54
|
+
assert.throws(() => loadSubagentInputFiles(cwd, ["binary.txt"]), /not valid UTF-8/);
|
|
55
|
+
assert.throws(() => loadSubagentInputFiles(cwd, ["large.txt"]), /total limit/);
|
|
56
|
+
assert.throws(
|
|
57
|
+
() => loadSubagentInputFiles(cwd, Array(MAX_SUBAGENT_INPUT_FILES + 1).fill("binary.txt")),
|
|
58
|
+
/at most/,
|
|
59
|
+
);
|
|
60
|
+
} finally {
|
|
61
|
+
await rm(cwd, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readFileSync, realpathSync, statSync } from "fs";
|
|
2
|
+
import { relative, resolve } from "path";
|
|
3
|
+
import { isPathWithinRoots } from "./path-security";
|
|
4
|
+
import { toSlashPath } from "./paths";
|
|
5
|
+
|
|
6
|
+
export const MAX_SUBAGENT_INPUT_FILES = 8;
|
|
7
|
+
export const MAX_SUBAGENT_INPUT_BYTES = 512 * 1024;
|
|
8
|
+
|
|
9
|
+
export interface SubagentInputFile {
|
|
10
|
+
path: string;
|
|
11
|
+
content: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function loadSubagentInputFiles(cwd: string, requestedPaths: readonly string[]): SubagentInputFile[] {
|
|
15
|
+
if (requestedPaths.length > MAX_SUBAGENT_INPUT_FILES) {
|
|
16
|
+
throw new Error(`Agent input_files accepts at most ${MAX_SUBAGENT_INPUT_FILES} files`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const realCwd = realpathSync(cwd);
|
|
20
|
+
const allowedRoots = new Set([realCwd]);
|
|
21
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
22
|
+
const seen = new Set<string>();
|
|
23
|
+
const files: SubagentInputFile[] = [];
|
|
24
|
+
let totalBytes = 0;
|
|
25
|
+
|
|
26
|
+
for (const requestedPath of requestedPaths) {
|
|
27
|
+
if (!requestedPath.trim()) throw new Error("Agent input_files paths must not be empty");
|
|
28
|
+
|
|
29
|
+
let filePath: string;
|
|
30
|
+
try {
|
|
31
|
+
filePath = realpathSync(resolve(cwd, requestedPath));
|
|
32
|
+
} catch {
|
|
33
|
+
throw new Error(`Agent input file does not exist: ${requestedPath}`);
|
|
34
|
+
}
|
|
35
|
+
if (!isPathWithinRoots(filePath, allowedRoots)) {
|
|
36
|
+
throw new Error(`Agent input file is outside the session cwd: ${requestedPath}`);
|
|
37
|
+
}
|
|
38
|
+
if (!statSync(filePath).isFile()) {
|
|
39
|
+
throw new Error(`Agent input path is not a file: ${requestedPath}`);
|
|
40
|
+
}
|
|
41
|
+
if (seen.has(filePath)) continue;
|
|
42
|
+
seen.add(filePath);
|
|
43
|
+
|
|
44
|
+
const buffer = readFileSync(filePath);
|
|
45
|
+
totalBytes += buffer.byteLength;
|
|
46
|
+
if (totalBytes > MAX_SUBAGENT_INPUT_BYTES) {
|
|
47
|
+
throw new Error(`Agent input_files exceeds the ${MAX_SUBAGENT_INPUT_BYTES}-byte total limit`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let content: string;
|
|
51
|
+
try {
|
|
52
|
+
content = decoder.decode(buffer);
|
|
53
|
+
} catch {
|
|
54
|
+
throw new Error(`Agent input file is not valid UTF-8 text: ${requestedPath}`);
|
|
55
|
+
}
|
|
56
|
+
files.push({
|
|
57
|
+
path: toSlashPath(relative(realCwd, filePath)),
|
|
58
|
+
content,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return files;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function appendSubagentInputFiles(task: string, files: readonly SubagentInputFile[]): string {
|
|
66
|
+
if (files.length === 0) return task;
|
|
67
|
+
const documents = files.map((file) =>
|
|
68
|
+
`<document path=${JSON.stringify(file.path)}>\n${file.content}\n</document>`
|
|
69
|
+
).join("\n\n");
|
|
70
|
+
return `${task}\n\n<documents>\n${documents}\n</documents>`;
|
|
71
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SubagentProfile, SubagentScope } from "./subagents";
|
|
2
|
+
|
|
3
|
+
const SUBAGENT_SCOPE_PRIORITY: Record<SubagentScope, number> = {
|
|
4
|
+
builtin: 0,
|
|
5
|
+
global: 1,
|
|
6
|
+
workspace: 2,
|
|
7
|
+
project: 3,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function isSubagentProfileOverridden(
|
|
11
|
+
profile: Pick<SubagentProfile, "name" | "scope">,
|
|
12
|
+
profiles: readonly Pick<SubagentProfile, "name" | "scope">[],
|
|
13
|
+
): boolean {
|
|
14
|
+
const name = profile.name.toLowerCase();
|
|
15
|
+
const priority = SUBAGENT_SCOPE_PRIORITY[profile.scope];
|
|
16
|
+
return profiles.some((candidate) =>
|
|
17
|
+
candidate.name.toLowerCase() === name
|
|
18
|
+
&& SUBAGENT_SCOPE_PRIORITY[candidate.scope] > priority
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createJiti } from "jiti";
|
|
4
|
+
|
|
5
|
+
const { buildSubagentPromptPlan } = await createJiti(import.meta.url).import("./subagent-prompt.ts");
|
|
6
|
+
|
|
7
|
+
test("a tool-free subagent uses only its profile as the exact system prompt", () => {
|
|
8
|
+
const plan = buildSubagentPromptPlan({
|
|
9
|
+
profileSystemPrompt: "Review carefully.",
|
|
10
|
+
tools: [],
|
|
11
|
+
task: "Inspect the parser.",
|
|
12
|
+
inheritedParentContext: "Parent context",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
assert.equal(plan.chatOnly, true);
|
|
16
|
+
assert.equal(plan.exactSystemPrompt, "Review carefully.");
|
|
17
|
+
assert.deepEqual(plan.appendSystemPrompt, ["Review carefully."]);
|
|
18
|
+
assert.equal(plan.delegatedTask, "Inspect the parser.\n\nParent context");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("a tool-enabled subagent retains inherited context in its appended system prompt", () => {
|
|
22
|
+
const plan = buildSubagentPromptPlan({
|
|
23
|
+
profileSystemPrompt: "Explore carefully.",
|
|
24
|
+
tools: ["read"],
|
|
25
|
+
task: "Inspect the parser.",
|
|
26
|
+
inheritedParentContext: "Parent context",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
assert.equal(plan.chatOnly, false);
|
|
30
|
+
assert.equal(plan.exactSystemPrompt, undefined);
|
|
31
|
+
assert.deepEqual(plan.appendSystemPrompt, ["Explore carefully.", "Parent context"]);
|
|
32
|
+
assert.equal(plan.delegatedTask, "Inspect the parser.");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("a resource-enabled tool-free subagent keeps the normal system prompt pipeline", () => {
|
|
36
|
+
for (const resources of [
|
|
37
|
+
{ loadSkills: true, loadExtensions: false },
|
|
38
|
+
{ loadSkills: false, loadExtensions: true },
|
|
39
|
+
]) {
|
|
40
|
+
const plan = buildSubagentPromptPlan({
|
|
41
|
+
profileSystemPrompt: "Use loaded resources.",
|
|
42
|
+
tools: [],
|
|
43
|
+
...resources,
|
|
44
|
+
task: "Inspect the parser.",
|
|
45
|
+
inheritedParentContext: "Parent context",
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
assert.equal(plan.chatOnly, false);
|
|
49
|
+
assert.equal(plan.exactSystemPrompt, undefined);
|
|
50
|
+
assert.deepEqual(plan.appendSystemPrompt, ["Use loaded resources.", "Parent context"]);
|
|
51
|
+
assert.equal(plan.delegatedTask, "Inspect the parser.");
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("replace prompt mode makes the profile body the exact system prompt", () => {
|
|
56
|
+
const plan = buildSubagentPromptPlan({
|
|
57
|
+
profileSystemPrompt: "Only these instructions.",
|
|
58
|
+
tools: ["read"],
|
|
59
|
+
promptMode: "replace",
|
|
60
|
+
task: "Inspect the parser.",
|
|
61
|
+
});
|
|
62
|
+
assert.equal(plan.exactSystemPrompt, "Only these instructions.");
|
|
63
|
+
assert.equal(plan.delegatedTask, "Inspect the parser.");
|
|
64
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface SubagentPromptPlan {
|
|
2
|
+
chatOnly: boolean;
|
|
3
|
+
appendSystemPrompt: string[];
|
|
4
|
+
delegatedTask: string;
|
|
5
|
+
exactSystemPrompt?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function buildSubagentPromptPlan(options: {
|
|
9
|
+
profileSystemPrompt: string;
|
|
10
|
+
tools: readonly string[];
|
|
11
|
+
loadSkills?: boolean;
|
|
12
|
+
loadExtensions?: boolean;
|
|
13
|
+
promptMode?: "replace" | "append";
|
|
14
|
+
task: string;
|
|
15
|
+
inheritedParentContext?: string;
|
|
16
|
+
}): SubagentPromptPlan {
|
|
17
|
+
const chatOnly = options.tools.length === 0 && !options.loadSkills && !options.loadExtensions;
|
|
18
|
+
const replacePrompt = options.promptMode === "replace";
|
|
19
|
+
const appendSystemPrompt = [options.profileSystemPrompt];
|
|
20
|
+
if (options.inheritedParentContext && !chatOnly) {
|
|
21
|
+
appendSystemPrompt.push(options.inheritedParentContext);
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
chatOnly,
|
|
25
|
+
appendSystemPrompt,
|
|
26
|
+
delegatedTask: options.inheritedParentContext && chatOnly
|
|
27
|
+
? `${options.task}\n\n${options.inheritedParentContext}`
|
|
28
|
+
: options.task,
|
|
29
|
+
...(chatOnly || replacePrompt ? { exactSystemPrompt: options.profileSystemPrompt } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createJiti } from "jiti";
|
|
4
|
+
|
|
5
|
+
const { SubagentQueue } = await createJiti(import.meta.url).import("./subagent-queue.ts");
|
|
6
|
+
|
|
7
|
+
test("runs FIFO with a per-parent concurrency limit and drains after completion", async () => {
|
|
8
|
+
const queue = new SubagentQueue();
|
|
9
|
+
const events = [];
|
|
10
|
+
let release;
|
|
11
|
+
const first = queue.enqueue("parent", 1, () => new Promise((resolve) => { release = () => resolve("first"); }), (state) => events.push(["first", state]));
|
|
12
|
+
const second = queue.enqueue("parent", 1, async () => "second", (state) => events.push(["second", state]));
|
|
13
|
+
assert.deepEqual(events, [["first", "queued"], ["first", "running"], ["second", "queued"]]);
|
|
14
|
+
release();
|
|
15
|
+
assert.equal(await first.promise, "first");
|
|
16
|
+
assert.equal(await second.promise, "second");
|
|
17
|
+
assert.deepEqual(events, [
|
|
18
|
+
["first", "queued"], ["first", "running"], ["second", "queued"], ["second", "running"],
|
|
19
|
+
]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("cancels queued work without starting it", async () => {
|
|
23
|
+
const queue = new SubagentQueue();
|
|
24
|
+
let release;
|
|
25
|
+
const first = queue.enqueue("parent", 1, () => new Promise((resolve) => { release = resolve; }), () => {});
|
|
26
|
+
let started = false;
|
|
27
|
+
let cancelled = false;
|
|
28
|
+
const second = queue.enqueue("parent", 1, async () => { started = true; return "bad"; }, () => {}, () => { cancelled = true; });
|
|
29
|
+
assert.equal(second.cancel(), true);
|
|
30
|
+
assert.equal(cancelled, true);
|
|
31
|
+
release();
|
|
32
|
+
await first.promise;
|
|
33
|
+
assert.equal(await second.promise, undefined);
|
|
34
|
+
assert.equal(started, false);
|
|
35
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export type SubagentQueueState = "queued" | "running";
|
|
2
|
+
|
|
3
|
+
interface QueueItem<T> {
|
|
4
|
+
run: () => Promise<T>;
|
|
5
|
+
onState: (state: SubagentQueueState) => void;
|
|
6
|
+
onCancel?: () => void | Promise<void>;
|
|
7
|
+
resolve: (value: T) => void;
|
|
8
|
+
reject: (error: unknown) => void;
|
|
9
|
+
state: SubagentQueueState;
|
|
10
|
+
cancelled: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type ParentQueue<T> = {
|
|
14
|
+
limit: number;
|
|
15
|
+
active: number;
|
|
16
|
+
items: QueueItem<T>[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export interface EnqueuedSubagent<T> {
|
|
20
|
+
promise: Promise<T>;
|
|
21
|
+
cancel(): boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** FIFO per parent session; separate parents do not block one another. */
|
|
25
|
+
export class SubagentQueue<T> {
|
|
26
|
+
private readonly parents = new Map<string, ParentQueue<T>>();
|
|
27
|
+
|
|
28
|
+
enqueue(
|
|
29
|
+
parentId: string,
|
|
30
|
+
limit: number,
|
|
31
|
+
run: () => Promise<T>,
|
|
32
|
+
onState: (state: SubagentQueueState) => void,
|
|
33
|
+
onCancel?: () => void,
|
|
34
|
+
): EnqueuedSubagent<T> {
|
|
35
|
+
const parent = this.parents.get(parentId) ?? { limit: 1, active: 0, items: [] };
|
|
36
|
+
parent.limit = Math.max(1, Math.floor(limit) || 1);
|
|
37
|
+
let item!: QueueItem<T>;
|
|
38
|
+
const promise = new Promise<T>((resolve, reject) => {
|
|
39
|
+
item = { run, onState, onCancel, resolve, reject, state: "queued", cancelled: false };
|
|
40
|
+
});
|
|
41
|
+
parent.items.push(item);
|
|
42
|
+
this.parents.set(parentId, parent);
|
|
43
|
+
onState("queued");
|
|
44
|
+
this.pump(parentId, parent);
|
|
45
|
+
return {
|
|
46
|
+
promise,
|
|
47
|
+
cancel: () => {
|
|
48
|
+
if (item.state !== "queued" || item.cancelled) return false;
|
|
49
|
+
item.cancelled = true;
|
|
50
|
+
Promise.resolve(item.onCancel?.()).then(
|
|
51
|
+
() => item.resolve(undefined as T),
|
|
52
|
+
(error) => item.reject(error),
|
|
53
|
+
);
|
|
54
|
+
this.pump(parentId, parent);
|
|
55
|
+
return true;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private pump(parentId: string, parent: ParentQueue<T>): void {
|
|
61
|
+
while (parent.active < parent.limit && parent.items.length > 0) {
|
|
62
|
+
const item = parent.items.shift()!;
|
|
63
|
+
if (item.cancelled) continue;
|
|
64
|
+
item.state = "running";
|
|
65
|
+
item.onState("running");
|
|
66
|
+
parent.active += 1;
|
|
67
|
+
void item.run().then(item.resolve, item.reject).finally(() => {
|
|
68
|
+
parent.active -= 1;
|
|
69
|
+
this.pump(parentId, parent);
|
|
70
|
+
if (parent.active === 0 && parent.items.length === 0) this.parents.delete(parentId);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|