@chainingintention/pi-web-cn 1.202610.0 → 1.202610.2
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/README.md +60 -106
- package/dist/.plugins-ready +1 -0
- package/dist/cli.js +161 -45
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/index-CFhaGb5m.js +4357 -0
- package/dist/client/assets/vendor-editor-core-YWog6mAR.js +10 -0
- package/dist/client/assets/vendor-editor-languages-DOCw3vc9.js +28 -0
- package/dist/client/index.html +5 -5
- package/dist/config.js +30 -6
- package/dist/config.js.map +1 -1
- package/dist/docker/piWebDockerCommandPlan.js +13 -4
- package/dist/docker/piWebDockerCommandPlan.js.map +1 -1
- package/dist/nativeServices/installedServiceDefinitions.js +23 -10
- package/dist/nativeServices/installedServiceDefinitions.js.map +1 -1
- package/dist/nativeServices/serviceAction.js +78 -20
- package/dist/nativeServices/serviceAction.js.map +1 -1
- package/dist/nativeServices/serviceDoctor.js +339 -67
- package/dist/nativeServices/serviceDoctor.js.map +1 -1
- package/dist/nativeServices/servicePlan.js +2 -2
- package/dist/nativeServices/servicePlan.js.map +1 -1
- package/dist/pi-packages/captains-log/README.md +11 -0
- package/dist/pi-packages/captains-log/dist/browser/channelProtocol.js +29 -0
- package/dist/pi-packages/captains-log/dist/browser/index.js +146 -0
- package/dist/pi-packages/captains-log/dist/browser/markdown.js +67 -0
- package/dist/pi-packages/captains-log/dist/browser/panel.js +151 -0
- package/dist/pi-packages/captains-log/dist/browser/protocol.js +26 -0
- package/dist/pi-packages/captains-log/dist/companion.js +126 -0
- package/dist/pi-packages/captains-log/dist/roundtrip.js +62 -0
- package/dist/pi-packages/captains-log/dist/server.js +210 -0
- package/dist/pi-packages/captains-log/dist/store.js +75 -0
- package/dist/pi-packages/captains-log/docs/usage.md +29 -0
- package/dist/pi-packages/captains-log/package.json +35 -0
- package/dist/pi-packages/captains-log/tsconfig.json +21 -0
- package/dist/pi-packages/captains-log/vite.config.mjs +28 -0
- package/dist/pi-packages/relays/pi-web-plugin.js +9 -9
- package/dist/pi-packages/relays/prompts/relay.md +1 -1
- package/dist/pi-packages/relays/relaysPanelElement.js +8 -8
- package/dist/pi-packages/relays/skills/relay-runner/SKILL.md +21 -13
- package/dist/pi-web-plugins/files/browser/assets/files-icon-DZObYhxb.svg +3 -0
- package/dist/pi-web-plugins/files/browser/pi-web-plugin.js +456 -0
- package/dist/pi-web-plugins/files/package.json +15 -0
- package/dist/pi-web-plugins/git/browser/git-panel.js +55 -32
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +2 -2
- package/dist/pi-web-plugins/git/git-backend.js +19 -26
- package/dist/pi-web-plugins/git/gitStatusCache.js +48 -0
- package/dist/pi-web-plugins/git/package.json +1 -0
- package/dist/pi-web-plugins/git/server-plugin.js +11 -10
- package/dist/pi-web-plugins/info/infoInternals.js +4 -1
- package/dist/pi-web-plugins/info/package.json +1 -1
- package/dist/pi-web-plugins/info/pi-web-plugin.js +8 -8
- package/dist/pi-web-plugins/mermaid/browser/mermaid-engine.js +3590 -0
- package/dist/pi-web-plugins/mermaid/browser/pi-web-plugin.js +6 -0
- package/dist/pi-web-plugins/mermaid/package.json +13 -0
- package/dist/pi-web-plugins/terminal/browser/pi-web-plugin.js +210 -0
- package/dist/pi-web-plugins/terminal/package.json +16 -0
- package/dist/pi-web-plugins/terminal/server-plugin.js +365 -0
- package/dist/pi-web-plugins/terminal/terminalService.js +435 -0
- package/dist/pi-web-plugins/updates/package.json +1 -1
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +69 -68
- package/dist/pi-web-plugins/workspace-tasks/package.json +1 -1
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +3 -3
- package/dist/piWebVersionReport.js +47 -24
- package/dist/piWebVersionReport.js.map +1 -1
- package/dist/plugin-api.d.ts +260 -25
- package/dist/plugin-api.js +1 -0
- package/dist/pluginRecoveryCli.js +3 -2
- package/dist/pluginRecoveryCli.js.map +1 -1
- package/dist/server/activity/workspaceActivityService.js +5 -5
- package/dist/server/activity/workspaceActivityService.js.map +1 -1
- package/dist/server/app.js +71 -44
- package/dist/server/app.js.map +1 -1
- package/dist/server/audit/managementAuditStore.js +1 -1
- package/dist/server/browserMessageProjection.js +0 -2
- package/dist/server/browserMessageProjection.js.map +1 -1
- package/dist/server/configRoutes.js +1 -2
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/knownPiPackages.js +12 -0
- package/dist/server/knownPiPackages.js.map +1 -0
- package/dist/server/machines/machineClient.js +2 -2
- package/dist/server/machines/machineClient.js.map +1 -1
- package/dist/server/machines/machinePluginProxyRoutes.js +120 -21
- package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
- package/dist/server/machines/machineProxyRoutes.js +80 -22
- package/dist/server/machines/machineProxyRoutes.js.map +1 -1
- package/dist/server/machines/machineService.js +22 -0
- package/dist/server/machines/machineService.js.map +1 -1
- package/dist/server/managementEmbed.js +2 -2
- package/dist/server/managementEmbed.js.map +1 -1
- package/dist/server/managementPrivileges.js +7 -1
- package/dist/server/managementPrivileges.js.map +1 -1
- package/dist/server/managementTerminalProxyRoutes.js +124 -0
- package/dist/server/managementTerminalProxyRoutes.js.map +1 -0
- package/dist/server/normalAuth.js +5 -1
- package/dist/server/normalAuth.js.map +1 -1
- package/dist/server/notices/serverNoticeStore.js +50 -6
- package/dist/server/notices/serverNoticeStore.js.map +1 -1
- package/dist/server/piPackageService.js +5 -5
- package/dist/server/piPackageService.js.map +1 -1
- package/dist/server/piWebPluginCatalog.js +211 -34
- package/dist/server/piWebPluginCatalog.js.map +1 -1
- package/dist/server/piWebPluginLifecycle.js +29 -3
- package/dist/server/piWebPluginLifecycle.js.map +1 -1
- package/dist/server/piWebPluginService.js +176 -291
- package/dist/server/piWebPluginService.js.map +1 -1
- package/dist/server/piWebStatus.js +45 -55
- package/dist/server/piWebStatus.js.map +1 -1
- package/dist/server/pluginCallbackDrain.js +35 -0
- package/dist/server/pluginCallbackDrain.js.map +1 -0
- package/dist/server/plugins/pluginBackendChannelProxyAdmission.js +84 -0
- package/dist/server/plugins/pluginBackendChannelProxyAdmission.js.map +1 -0
- package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js +234 -0
- package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js.map +1 -0
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js +63 -0
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js.map +1 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js +7 -4
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -1
- package/dist/server/plugins/pluginBackendRegistry.js +773 -0
- package/dist/server/plugins/pluginBackendRegistry.js.map +1 -0
- package/dist/server/plugins/serverPluginPiSessionEventsCapability.js +34 -0
- package/dist/server/plugins/serverPluginPiSessionEventsCapability.js.map +1 -0
- package/dist/server/plugins/serverPluginPiSessionsCapability.js +185 -0
- package/dist/server/plugins/serverPluginPiSessionsCapability.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +1049 -107
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -1
- package/dist/server/plugins/serverPluginWorkspacesCapability.js +114 -0
- package/dist/server/plugins/serverPluginWorkspacesCapability.js.map +1 -0
- package/dist/server/projectTrustRoutes.js +86 -0
- package/dist/server/projectTrustRoutes.js.map +1 -0
- package/dist/server/projects/projectLifecycleService.js +84 -0
- package/dist/server/projects/projectLifecycleService.js.map +1 -0
- package/dist/server/projects/projectService.js +2 -0
- package/dist/server/projects/projectService.js.map +1 -1
- package/dist/server/realtime/sessionEventHub.js +35 -41
- package/dist/server/realtime/sessionEventHub.js.map +1 -1
- package/dist/server/sessiond/agentHttpDispatcher.js +108 -0
- package/dist/server/sessiond/agentHttpDispatcher.js.map +1 -0
- package/dist/server/sessiond/pluginBackendChannelRoutes.js +397 -0
- package/dist/server/sessiond/pluginBackendChannelRoutes.js.map +1 -0
- package/dist/server/sessiond/pluginBackendRoutes.js +19 -9
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -1
- package/dist/server/sessiond/projectMutationRoutes.js +38 -0
- package/dist/server/sessiond/projectMutationRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js +36 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -0
- package/dist/server/sessiond/sessionDaemonStartup.js +30 -4
- package/dist/server/sessiond/sessionDaemonStartup.js.map +1 -1
- package/dist/server/sessiond/sessionProxyRoutes.js +47 -15
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond/sessionServiceDependencies.js +4 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
- package/dist/server/sessiond/sessiondStateOwnership.js +174 -39
- package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -1
- package/dist/server/sessiond/workspaceRemovalRoutes.js +1 -1
- package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -1
- package/dist/server/sessiond.js +226 -56
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/askUserTool.js +0 -3
- package/dist/server/sessions/askUserTool.js.map +1 -1
- package/dist/server/sessions/attachmentService.js +0 -1
- package/dist/server/sessions/attachmentService.js.map +1 -1
- package/dist/server/sessions/authService.js +8 -1
- package/dist/server/sessions/authService.js.map +1 -1
- package/dist/server/sessions/builtinCommands.js +2 -2
- package/dist/server/sessions/builtinCommands.js.map +1 -1
- package/dist/server/sessions/clientSessionPreview.js +14 -0
- package/dist/server/sessions/clientSessionPreview.js.map +1 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js +82 -9
- package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -1
- package/dist/server/sessions/managementAgentTools.js +11 -6
- package/dist/server/sessions/managementAgentTools.js.map +1 -1
- package/dist/server/sessions/managementPermissionSystem.js +16 -4
- package/dist/server/sessions/managementPermissionSystem.js.map +1 -1
- package/dist/server/sessions/messagePaging.js +2 -2
- package/dist/server/sessions/messagePaging.js.map +1 -1
- package/dist/server/sessions/modelCatalogRefresher.js +4 -3
- package/dist/server/sessions/modelCatalogRefresher.js.map +1 -1
- package/dist/server/sessions/oauthLoginFlowService.js +2 -5
- package/dist/server/sessions/oauthLoginFlowService.js.map +1 -1
- package/dist/server/sessions/pendingAskStore.js +0 -3
- package/dist/server/sessions/pendingAskStore.js.map +1 -1
- package/dist/server/sessions/piSessionEventConnections.js +65 -0
- package/dist/server/sessions/piSessionEventConnections.js.map +1 -0
- package/dist/server/sessions/piSessionManagerGateway.js +56 -3
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +785 -450
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/projectAuthService.js +29 -2
- package/dist/server/sessions/projectAuthService.js.map +1 -1
- package/dist/server/sessions/projectSettingsScope.js +12 -4
- package/dist/server/sessions/projectSettingsScope.js.map +1 -1
- package/dist/server/sessions/projectTrust.js +119 -0
- package/dist/server/sessions/projectTrust.js.map +1 -0
- package/dist/server/sessions/sessionActivityMarker.js +105 -0
- package/dist/server/sessions/sessionActivityMarker.js.map +1 -0
- package/dist/server/sessions/sessionArchiveMapping.js +2 -8
- package/dist/server/sessions/sessionArchiveMapping.js.map +1 -1
- package/dist/server/sessions/sessionCommandService.js +12 -10
- package/dist/server/sessions/sessionCommandService.js.map +1 -1
- package/dist/server/sessions/sessionEnvironmentFacts.js +5 -4
- package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -1
- package/dist/server/sessions/sessionModelScope.js +42 -5
- package/dist/server/sessions/sessionModelScope.js.map +1 -1
- package/dist/server/sessions/sessionNameGenerator.js +6 -6
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
- package/dist/server/sessions/sessionRoutes.js +63 -15
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/sessionTreeProjection.js +29 -29
- package/dist/server/sessions/sessionTreeProjection.js.map +1 -1
- package/dist/server/sessions/sessionUiEvents.js +6 -7
- package/dist/server/sessions/sessionUiEvents.js.map +1 -1
- package/dist/server/sessions/sessionUnreadStore.js +26 -1
- package/dist/server/sessions/sessionUnreadStore.js.map +1 -1
- package/dist/server/sessions/spawnSessionTool.js +3 -3
- package/dist/server/sessions/spawnSessionTool.js.map +1 -1
- package/dist/server/sessions/spawnSubsessionTool.js +5 -5
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
- package/dist/server/sessions/spawnTargetResolver.js +1 -1
- package/dist/server/sessions/transcriptMessages.js +54 -0
- package/dist/server/sessions/transcriptMessages.js.map +1 -0
- package/dist/server/status/machineStatusService.js +3 -1
- package/dist/server/status/machineStatusService.js.map +1 -1
- package/dist/server/terminals/{terminalService.js → managementCommandRunService.js} +79 -107
- package/dist/server/terminals/managementCommandRunService.js.map +1 -0
- package/dist/server/terminals/managementTerminalRoutes.js +91 -0
- package/dist/server/terminals/managementTerminalRoutes.js.map +1 -0
- package/dist/server/terminals/requiredTerminalService.js +103 -0
- package/dist/server/terminals/requiredTerminalService.js.map +1 -0
- package/dist/server/webSocketBridge.js +339 -0
- package/dist/server/webSocketBridge.js.map +1 -1
- package/dist/server/workbench/gatewayIntegration.js +4 -1
- package/dist/server/workbench/gatewayIntegration.js.map +1 -1
- package/dist/server/workspaceExplorerRoutes.js +13 -12
- package/dist/server/workspaceExplorerRoutes.js.map +1 -1
- package/dist/server/workspaces/fileContentService.js +9 -7
- package/dist/server/workspaces/fileContentService.js.map +1 -1
- package/dist/server/workspaces/fileOperationService.js +4 -3
- package/dist/server/workspaces/fileOperationService.js.map +1 -1
- package/dist/server/workspaces/projectWorkspaceCwds.js +0 -5
- package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -1
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +36 -12
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -1
- package/dist/server/workspaces/workspaceCatalog.js +6 -4
- package/dist/server/workspaces/workspaceCatalog.js.map +1 -1
- package/dist/server/workspaces/workspaceDeletionRoutes.js +5 -57
- package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -1
- package/dist/server/workspaces/workspaceProviderRegistry.js +75 -24
- package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -1
- package/dist/server/workspaces/workspaceRemovalService.js +25 -3
- package/dist/server/workspaces/workspaceRemovalService.js.map +1 -1
- package/dist/server-plugin-api.d.ts +274 -44
- package/dist/server-plugin-api.js +319 -2
- package/dist/server-plugin-api.js.map +1 -1
- package/dist/serverPluginRecovery.js +4 -0
- package/dist/serverPluginRecovery.js.map +1 -1
- package/dist/sessiond/activeAgentProfile.js +27 -2
- package/dist/sessiond/activeAgentProfile.js.map +1 -1
- package/dist/sessiond/config.js +4 -3
- package/dist/sessiond/config.js.map +1 -1
- package/dist/sessiond/sessionDaemonClient.js +29 -8
- package/dist/sessiond/sessionDaemonClient.js.map +1 -1
- package/dist/shared/activeAgentProfile.js +2 -34
- package/dist/shared/activeAgentProfile.js.map +1 -1
- package/dist/shared/activity.js +0 -3
- package/dist/shared/activity.js.map +1 -1
- package/dist/shared/apiTypes.js +7 -2
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +5 -0
- package/dist/shared/capabilities.js.map +1 -1
- package/dist/shared/federatedRoutes.js +49 -9
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/machinePluginIds.js +30 -7
- package/dist/shared/machinePluginIds.js.map +1 -1
- package/dist/shared/machineStatus.js +35 -1
- package/dist/shared/machineStatus.js.map +1 -1
- package/dist/shared/piWebStatusParsing.js +29 -0
- package/dist/shared/piWebStatusParsing.js.map +1 -1
- package/dist/shared/pluginApiTypes.d.ts +24 -9
- package/dist/shared/pluginApiTypes.js +2 -0
- package/dist/shared/pluginApiTypes.js.map +1 -0
- package/dist/shared/pluginBackendProtocol.js +163 -2
- package/dist/shared/pluginBackendProtocol.js.map +1 -1
- package/dist/shared/pluginIds.js +10 -1
- package/dist/shared/pluginIds.js.map +1 -1
- package/dist/shared/requiredTerminalPlugin.js +6 -0
- package/dist/shared/requiredTerminalPlugin.js.map +1 -0
- package/dist/shared/serverNoticeContract.js +88 -0
- package/dist/shared/serverNoticeContract.js.map +1 -0
- package/dist/shared/sessionDefaults.js +41 -0
- package/dist/shared/sessionDefaults.js.map +1 -0
- package/dist/shared/workspaceFiles.js.map +1 -1
- package/docs/config.md +270 -323
- package/docs/plugins.md +171 -956
- package/examples/session-bridge-plugin/README.md +20 -0
- package/examples/session-bridge-plugin/docs/usage.md +54 -0
- package/examples/session-bridge-plugin/package.json +26 -0
- package/examples/session-bridge-plugin/src/browser/index.ts +87 -0
- package/examples/session-bridge-plugin/src/browser/protocol.ts +24 -0
- package/examples/session-bridge-plugin/src/companion.ts +65 -0
- package/examples/session-bridge-plugin/src/reviewRun.ts +39 -0
- package/examples/session-bridge-plugin/src/server.ts +85 -0
- package/examples/session-bridge-plugin/src/store.ts +80 -0
- package/examples/session-bridge-plugin/tsconfig.json +21 -0
- package/examples/workspace-provider-plugin/README.md +39 -0
- package/examples/workspace-provider-plugin/package.json +25 -0
- package/examples/workspace-provider-plugin/src/browser/index.ts +69 -0
- package/examples/workspace-provider-plugin/src/server.ts +68 -0
- package/examples/workspace-provider-plugin/tsconfig.json +21 -0
- package/package.json +22 -13
- package/skills/README.md +27 -2
- package/skills/relay-runner/SKILL.md +21 -13
- package/dist/client/assets/CodeViewer-CZHnKRwQ.js +0 -4
- package/dist/client/assets/TerminalPanel-B_EaRPHY.js +0 -187
- package/dist/client/assets/index-Bv1v-Jz7.js +0 -4717
- package/dist/client/assets/vendor-editor-core-vUi74DnD.js +0 -12
- package/dist/client/assets/vendor-editor-languages-DoaXUodB.js +0 -46
- package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +0 -1
- package/dist/client/assets/vendor-terminal-BrP-ENHg.css +0 -1
- package/dist/client/assets/vendor-terminal-D8k4UKM2.js +0 -35
- package/dist/server/git/gitService.js +0 -316
- package/dist/server/git/gitService.js.map +0 -1
- package/dist/server/gitRoutes.js +0 -23
- package/dist/server/gitRoutes.js.map +0 -1
- package/dist/server/terminalProxyRoutes.js +0 -181
- package/dist/server/terminalProxyRoutes.js.map +0 -1
- package/dist/server/terminals/terminalRoutes.js +0 -164
- package/dist/server/terminals/terminalRoutes.js.map +0 -1
- package/dist/server/terminals/terminalService.js.map +0 -1
- package/dist/server/terminals/terminalSize.js +0 -17
- package/dist/server/terminals/terminalSize.js.map +0 -1
- package/dist/server/workspaces/gitWorktreeDiscovery.js +0 -43
- package/dist/server/workspaces/gitWorktreeDiscovery.js.map +0 -1
- package/dist/server/workspaces/workspaceService.js +0 -52
- package/dist/server/workspaces/workspaceService.js.map +0 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { clientSessionFirstMessagePreview } from "./clientSessionPreview.js";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
4
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
-
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, getAgentDir, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, defineTool, readStoredCredential, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createEventBus, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, getAgentDir, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, defineTool, ProjectTrustStore, hasTrustRequiringProjectResources, readStoredCredential, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
5
6
|
import { projectBrowserMessage } from "../browserMessageProjection.js";
|
|
6
7
|
import { pageMessagesAtSafeBoundary } from "./messagePaging.js";
|
|
7
8
|
import { eventScopeFromManagementContext, managementContextKey, NORMAL_SESSION_EVENT_SCOPE } from "../realtime/sessionEventScope.js";
|
|
8
9
|
import { BUILTIN_COMMANDS } from "./builtinCommands.js";
|
|
9
10
|
import { SessionCommandService } from "./sessionCommandService.js";
|
|
11
|
+
import { SessionActivityMarker } from "./sessionActivityMarker.js";
|
|
10
12
|
import { projectSessionTree } from "./sessionTreeProjection.js";
|
|
11
13
|
import { SessionArchiveStore } from "./sessionArchiveStore.js";
|
|
12
14
|
import { findArchiveCandidateByIdOrPrefix, planSessionArchiveTree } from "./sessionArchiveTree.js";
|
|
@@ -16,17 +18,20 @@ import { attachmentsToInlineImages, saveAttachmentsToWorkspace } from "./attachm
|
|
|
16
18
|
import { loadEffectiveProjectAttachmentsConfig } from "../workspaces/projectPiWebConfig.js";
|
|
17
19
|
import { parsePromptAttachments } from "../../shared/promptAttachments.js";
|
|
18
20
|
import { ASK_USER_ANSWERS_CUSTOM_TYPE, SESSION_TREE_CUSTOM_INSTRUCTIONS_MAX_LENGTH, SESSION_UNREAD_LIMIT } from "../../shared/apiTypes.js";
|
|
21
|
+
import { PiSessionEventConnections } from "./piSessionEventConnections.js";
|
|
22
|
+
import { resolveWebProjectTrusted } from "./projectTrust.js";
|
|
19
23
|
import { createPiSessionManagerGateway } from "./piSessionManagerGateway.js";
|
|
20
24
|
import { SessionPinStore } from "./sessionPinStore.js";
|
|
21
25
|
import { SessionActivityCoordinator } from "./sessionActivityCoordinator.js";
|
|
22
26
|
import { searchSessionContent } from "./sessionContentSearch.js";
|
|
23
|
-
import { createLocalOnlyModelRuntime } from "./authService.js";
|
|
27
|
+
import { createLocalOnlyModelRuntime, modelRuntimeAuthPath } from "./authService.js";
|
|
24
28
|
import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
|
|
25
29
|
import { clearSessionFileParent, readSessionHeaderSummary } from "./sessionFileHeader.js";
|
|
26
|
-
import { workbenchAccessHandle } from "../managementEmbed.js";
|
|
27
|
-
import { managementPrivilegeRequested, managementPrivileges } from "../managementPrivileges.js";
|
|
30
|
+
import { isManagedAgentsFile, workbenchAccessHandle } from "../managementEmbed.js";
|
|
31
|
+
import { managementPrivilegeRequested, managementPrivileges, managementUsesNormalPermissions } from "../managementPrivileges.js";
|
|
28
32
|
import { DEFAULT_EXTENSION_DIALOGS_TIMEOUT_MS, piWebDataDir } from "../../config.js";
|
|
29
33
|
import { createScopedSettingsManager, resolveSettingsScopeDirectory } from "./projectSettingsScope.js";
|
|
34
|
+
import { parseSessionDefaults, parseSessionDefaultsUpdate } from "../../shared/sessionDefaults.js";
|
|
30
35
|
import { createAskUserToolDefinition } from "./askUserTool.js";
|
|
31
36
|
import { PendingAskStore, renderAskUserAnswersText } from "./pendingAskStore.js";
|
|
32
37
|
import { PendingExtensionDialogStore } from "./pendingExtensionDialogStore.js";
|
|
@@ -41,8 +46,9 @@ import { SessionNotificationStore } from "./sessionNotificationStore.js";
|
|
|
41
46
|
import { plainTextTheme } from "./plainTextTheme.js";
|
|
42
47
|
import { deduplicateBundledRelaySkills, ensureManagedRelaySkills, MANAGED_RELAY_SKILL_NAMES } from "./relaySkill.js";
|
|
43
48
|
import { SessionUnreadStore } from "./sessionUnreadStore.js";
|
|
44
|
-
import { archiveCandidateFromActiveSession, archiveCandidateFromArchivedRecord, archiveCandidateFromListEntry, archiveInputFromActiveSession, archiveInputFromCandidate, archiveInputFromListEntry, clientSessionFromArchivedRecord,
|
|
45
|
-
import {
|
|
49
|
+
import { archiveCandidateFromActiveSession, archiveCandidateFromArchivedRecord, archiveCandidateFromListEntry, archiveInputFromActiveSession, archiveInputFromCandidate, archiveInputFromListEntry, clientSessionFromArchivedRecord, } from "./sessionArchiveMapping.js";
|
|
50
|
+
import { getBoolean, getProperty, getString, toClientEvent } from "./sessionUiEvents.js";
|
|
51
|
+
import { annotateAssistantThinkingLevel, historyMessagesFromEntries } from "./transcriptMessages.js";
|
|
46
52
|
import { createWorkbenchToolDefinitions } from "../workbench/workbenchTools.js";
|
|
47
53
|
import { applyEnabledModelToggle, catalogWithEnabledFirst, modelScopeId, persistedEnabledModelPatterns, resolveEnabledModelIds, resolveSessionModelOptions, scopedModelsFromEnabledIds } from "./sessionModelScope.js";
|
|
48
54
|
const noopLogger = { info() { } };
|
|
@@ -65,24 +71,16 @@ const MAX_UNREAD_PUBLICATION_RETRY_MS = 30_000;
|
|
|
65
71
|
const MAX_PENDING_UNREAD_MUTATIONS = SESSION_UNREAD_LIMIT + 1;
|
|
66
72
|
const IDLE_SESSION_FILE_RESOLUTION_THROTTLE_MS = 30_000;
|
|
67
73
|
function auditVisibleAssistantMessage(message) {
|
|
68
|
-
if (!
|
|
74
|
+
if (!isRecord(message) || Array.isArray(message))
|
|
69
75
|
return message;
|
|
70
|
-
const
|
|
71
|
-
const content = record["content"];
|
|
76
|
+
const content = message["content"];
|
|
72
77
|
if (!Array.isArray(content))
|
|
73
78
|
return message;
|
|
74
79
|
return {
|
|
75
|
-
...
|
|
76
|
-
content: content.filter(part => (
|
|
77
|
-
|| part === null
|
|
78
|
-
|| Array.isArray(part)
|
|
79
|
-
|| !isAuditRecord(part)
|
|
80
|
-
|| part["type"] !== "thinking")),
|
|
80
|
+
...message,
|
|
81
|
+
content: content.filter(part => !isRecord(part) || Array.isArray(part) || part["type"] !== "thinking"),
|
|
81
82
|
};
|
|
82
83
|
}
|
|
83
|
-
function isAuditRecord(value) {
|
|
84
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
85
|
-
}
|
|
86
84
|
function noop() {
|
|
87
85
|
// Intentionally empty default unsubscribe callback.
|
|
88
86
|
}
|
|
@@ -143,9 +141,9 @@ function sessionTreeNavigationOptions(request) {
|
|
|
143
141
|
case "custom": {
|
|
144
142
|
const customInstructions = request.summary.instructions.trim();
|
|
145
143
|
if (customInstructions === "")
|
|
146
|
-
throw new Error("
|
|
144
|
+
throw new Error("自定义分支汇总要求不能为空");
|
|
147
145
|
if (customInstructions.length > SESSION_TREE_CUSTOM_INSTRUCTIONS_MAX_LENGTH) {
|
|
148
|
-
throw new Error(
|
|
146
|
+
throw new Error(`自定义分支汇总要求不能超过 ${String(SESSION_TREE_CUSTOM_INSTRUCTIONS_MAX_LENGTH)} 个字符`);
|
|
149
147
|
}
|
|
150
148
|
return { summarize: true, customInstructions };
|
|
151
149
|
}
|
|
@@ -178,6 +176,16 @@ function parsePromptStreamingBehavior(value) {
|
|
|
178
176
|
throw new Error('Prompt streamingBehavior must be "steer" or "followUp"');
|
|
179
177
|
}
|
|
180
178
|
const GLOBAL_AGENT_CONTEXT_FILE_NAMES = new Set(["AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"]);
|
|
179
|
+
export function filterPrivilegedManagementContextFiles(cwd, context, base) {
|
|
180
|
+
const injectedInstructions = context.projects.flatMap((project) => [
|
|
181
|
+
...(project.agentsInstructions === undefined ? [] : [project.agentsInstructions]),
|
|
182
|
+
...(project.agentsInstructionsReplaceIfMatches ?? []),
|
|
183
|
+
]).map((content) => content.trim());
|
|
184
|
+
return {
|
|
185
|
+
agentsFiles: base.agentsFiles.filter((file) => !isManagedAgentsFile(file.content)
|
|
186
|
+
&& !(basename(file.path) === "AGENTS.md" && pathInsideOrEqual(dirname(file.path), cwd) && injectedInstructions.includes(file.content.trim()))),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
181
189
|
export function filterManagedGlobalContextFiles(cwd, agentDir, base) {
|
|
182
190
|
return {
|
|
183
191
|
agentsFiles: base.agentsFiles.filter((file) => {
|
|
@@ -371,67 +379,108 @@ export function piWebResourceLoaderOptions(appendSystemPromptSections) {
|
|
|
371
379
|
return undefined;
|
|
372
380
|
return { appendSystemPromptOverride: (base) => [...base, ...appendSystemPromptSections] };
|
|
373
381
|
}
|
|
374
|
-
function
|
|
375
|
-
|
|
382
|
+
async function createNormalPermissionSessionServices(options) {
|
|
383
|
+
const { cwd, agentDir, settingsManager, projectTrustRequiring } = options;
|
|
384
|
+
const projectTrustDiagnostics = [];
|
|
385
|
+
const services = await createAgentSessionServices({
|
|
386
|
+
cwd,
|
|
387
|
+
agentDir,
|
|
388
|
+
modelRuntime: options.modelRuntime,
|
|
389
|
+
settingsManager,
|
|
390
|
+
resourceLoaderOptions: {
|
|
391
|
+
...piWebResourceLoaderOptions(options.appendSystemPromptSections),
|
|
392
|
+
eventBus: options.eventBus,
|
|
393
|
+
...(options.agentsFilesOverride === undefined ? {} : { agentsFilesOverride: options.agentsFilesOverride }),
|
|
394
|
+
skillsOverride: (base) => deduplicateBundledRelaySkills(cwd, base),
|
|
395
|
+
},
|
|
396
|
+
...(projectTrustRequiring ? {
|
|
397
|
+
resourceLoaderReloadOptions: {
|
|
398
|
+
resolveProjectTrust: async ({ extensionsResult }) => resolveWebProjectTrusted({
|
|
399
|
+
cwd,
|
|
400
|
+
trustStore: new ProjectTrustStore(agentDir),
|
|
401
|
+
settingsManager,
|
|
402
|
+
extensionsResult,
|
|
403
|
+
onExtensionError: (message) => projectTrustDiagnostics.push({ type: "warning", message }),
|
|
404
|
+
}),
|
|
405
|
+
},
|
|
406
|
+
} : {}),
|
|
407
|
+
});
|
|
408
|
+
return { services, projectTrustDiagnostics };
|
|
409
|
+
}
|
|
410
|
+
function createDefaultRuntimeFactory(sessionEvents, normalModelRuntimeForCwd, managementModelRuntime, resolveSettingsScope, sessionManagers, spawn, subsessions, askUser, workbench, logger = noopLogger, managementAudit, managementProjectIdForCwd, appendSystemPromptSections = [], managementAllowPrivileged = false) {
|
|
411
|
+
return async ({ cwd, agentDir, sessionManager, sessionStartEvent, managementContext, initialModel, initialThinkingLevel, delegationToolsEnabled }) => {
|
|
376
412
|
const scopedModelRuntime = managementContext === undefined ? await normalModelRuntimeForCwd(cwd) : await managementModelRuntime();
|
|
377
413
|
if (managementContext !== undefined) {
|
|
378
|
-
return createManagementRuntimeFactory(scopedModelRuntime, resolveSettingsScope, spawn, subsessions, askUser, managementContext, workbench, logger, managementAudit, managementProjectIdForCwd, managementAllowPrivileged, appendSystemPromptSections)({
|
|
414
|
+
return createManagementRuntimeFactory(scopedModelRuntime, resolveSettingsScope, sessionManagers, spawn, subsessions, askUser, managementContext, workbench, logger, managementAudit, managementProjectIdForCwd, managementAllowPrivileged, appendSystemPromptSections)({
|
|
379
415
|
cwd,
|
|
380
416
|
agentDir,
|
|
381
417
|
sessionManager,
|
|
382
|
-
delegationToolsEnabled,
|
|
418
|
+
...(delegationToolsEnabled === undefined ? {} : { delegationToolsEnabled }),
|
|
383
419
|
...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
|
|
384
420
|
...(initialModel === undefined ? {} : { initialModel }),
|
|
385
421
|
...(initialThinkingLevel === undefined ? {} : { initialThinkingLevel }),
|
|
386
422
|
});
|
|
387
423
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
424
|
+
// Resource loaders may read process.env; wait for managed overrides to be restored.
|
|
425
|
+
return withRuntimeCreationEnvironment({}, async () => {
|
|
426
|
+
const projectTrustRequiring = hasTrustRequiringProjectResources(cwd);
|
|
427
|
+
const settingsManager = await createScopedSettingsManager({
|
|
428
|
+
cwd,
|
|
429
|
+
scopeDirectory: await resolveSettingsScope(cwd, "normal"),
|
|
430
|
+
globalAgentDir: agentDir,
|
|
431
|
+
mode: "normal",
|
|
432
|
+
projectTrusted: !projectTrustRequiring,
|
|
433
|
+
});
|
|
434
|
+
// Only normal runtimes register a host connection; privileged management
|
|
435
|
+
// packages keep their own event bus without exposing a normal-mode session.
|
|
436
|
+
const eventBus = createEventBus();
|
|
437
|
+
const { services, projectTrustDiagnostics } = await createNormalPermissionSessionServices({
|
|
438
|
+
cwd,
|
|
439
|
+
agentDir,
|
|
440
|
+
modelRuntime: scopedModelRuntime,
|
|
441
|
+
settingsManager,
|
|
442
|
+
projectTrustRequiring,
|
|
443
|
+
appendSystemPromptSections,
|
|
444
|
+
eventBus,
|
|
445
|
+
});
|
|
446
|
+
const modelOptions = await resolveSessionModelOptions({
|
|
447
|
+
services,
|
|
448
|
+
hasExistingSession: sessionManager.buildSessionContext().messages.length > 0,
|
|
449
|
+
...(initialModel === undefined ? {} : { initialModel }),
|
|
450
|
+
...(initialThinkingLevel === undefined ? {} : { initialThinkingLevel }),
|
|
451
|
+
});
|
|
452
|
+
services.diagnostics.push(...modelOptions.diagnostics);
|
|
453
|
+
const resolvedDelegationToolsEnabled = delegationToolsEnabled
|
|
454
|
+
?? await sessionAllowsDelegationTools(sessionManager, sessionManagers);
|
|
455
|
+
const customTools = createPiWebCustomToolDefinitions(cwd, resolvedDelegationToolsEnabled, spawn, subsessions, askUser);
|
|
456
|
+
const result = await createAgentSessionFromServices({
|
|
457
|
+
services,
|
|
458
|
+
sessionManager,
|
|
459
|
+
customTools,
|
|
460
|
+
...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
|
|
461
|
+
...(modelOptions.model === undefined ? {} : { model: modelOptions.model }),
|
|
462
|
+
...(modelOptions.thinkingLevel === undefined ? {} : { thinkingLevel: modelOptions.thinkingLevel }),
|
|
463
|
+
...(modelOptions.scopedModels.length === 0 ? {} : { scopedModels: modelOptions.scopedModels }),
|
|
464
|
+
});
|
|
465
|
+
sessionEvents.register(result.session, eventBus);
|
|
466
|
+
const reload = result.session.reload.bind(result.session);
|
|
467
|
+
result.session.reload = (options) => withRuntimeCreationEnvironment({}, () => reload(options));
|
|
468
|
+
return { ...result, services, diagnostics: [...projectTrustDiagnostics, ...services.diagnostics] };
|
|
421
469
|
});
|
|
422
|
-
return { ...result, services, diagnostics: services.diagnostics };
|
|
423
470
|
};
|
|
424
471
|
}
|
|
425
|
-
function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spawn, subsessions, askUser, managementContext, workbench, logger, managementAudit, managementProjectIdForCwd, managementAllowPrivileged = false, appendSystemPromptSections = []) {
|
|
426
|
-
return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, initialThinkingLevel, delegationToolsEnabled
|
|
472
|
+
function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, sessionManagers, spawn, subsessions, askUser, managementContext, workbench, logger, managementAudit, managementProjectIdForCwd, managementAllowPrivileged = false, appendSystemPromptSections = []) {
|
|
473
|
+
return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, initialThinkingLevel, delegationToolsEnabled }) => {
|
|
474
|
+
const normalPermissions = managementUsesNormalPermissions(managementContext, { allowPrivileged: managementAllowPrivileged });
|
|
427
475
|
const privileges = managementPrivileges(managementContext, { allowPrivileged: managementAllowPrivileged });
|
|
428
|
-
const
|
|
429
|
-
|
|
476
|
+
const resolvedDelegationToolsEnabled = delegationToolsEnabled
|
|
477
|
+
?? await sessionAllowsDelegationTools(sessionManager, sessionManagers);
|
|
478
|
+
if (!normalPermissions)
|
|
430
479
|
await ensureManagedRelaySkills(cwd);
|
|
431
480
|
const auditIdentity = await resolveManagementAuditIdentity(cwd, managementContext, managementProjectIdForCwd, logger);
|
|
432
481
|
const accessHandle = workbenchAccessHandle(managementContext);
|
|
433
|
-
const state = workbench === undefined ? undefined : await workbench.accessStates.prepare(accessHandle, workbench.client);
|
|
434
|
-
const receipt = state === undefined
|
|
482
|
+
const state = normalPermissions || workbench === undefined ? undefined : await workbench.accessStates.prepare(accessHandle, workbench.client);
|
|
483
|
+
const receipt = state === undefined ? undefined : await workbench?.skills.synchronize(cwd, state, {
|
|
435
484
|
...(managementAudit === undefined ? {} : { recorder: managementAudit }),
|
|
436
485
|
...auditIdentity,
|
|
437
486
|
sessionId: sessionManager.getSessionId(),
|
|
@@ -457,13 +506,18 @@ function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spaw
|
|
|
457
506
|
const scopedSubsessions = subsessions === undefined
|
|
458
507
|
? undefined
|
|
459
508
|
: {
|
|
460
|
-
...subsessions,
|
|
461
509
|
spawn: (input) => subsessions.spawn({ ...input, managementContext }),
|
|
510
|
+
list: (parentSessionId, parentSessionFile) => subsessions.list(parentSessionId, parentSessionFile, managementContext),
|
|
511
|
+
check: (parentSessionId, sessionId, parentSessionFile) => subsessions.check(parentSessionId, sessionId, parentSessionFile, managementContext),
|
|
512
|
+
read: (parentSessionId, sessionId, query, parentSessionFile) => subsessions.read(parentSessionId, sessionId, query, parentSessionFile, managementContext),
|
|
462
513
|
};
|
|
514
|
+
const scopedAskUser = askUser === undefined ? undefined : {
|
|
515
|
+
open: (input) => askUser.open({ ...input, managementContext }),
|
|
516
|
+
};
|
|
463
517
|
const controlledManagementTools = [
|
|
464
|
-
...(
|
|
465
|
-
...(
|
|
466
|
-
...(
|
|
518
|
+
...(resolvedDelegationToolsEnabled && scopedSpawn !== undefined ? [defineTool(createSpawnSessionToolDefinition(cwd, { spawn: scopedSpawn }))] : []),
|
|
519
|
+
...(resolvedDelegationToolsEnabled && scopedSubsessions !== undefined ? createSubsessionToolDefinitions(cwd, scopedSubsessions).map((tool) => defineTool(tool)) : []),
|
|
520
|
+
...(scopedAskUser === undefined ? [] : [createAskUserToolDefinition(scopedAskUser)]),
|
|
467
521
|
...workbenchTools,
|
|
468
522
|
];
|
|
469
523
|
const controlledManagementToolNames = controlledManagementTools.map((tool) => tool.name);
|
|
@@ -475,27 +529,36 @@ function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spaw
|
|
|
475
529
|
}, "management privileged grant ignored because managementEmbed.allowPrivileged is disabled");
|
|
476
530
|
}
|
|
477
531
|
const extraToolNames = [...controlledManagementToolNames, ...privilegedManagementToolNames(privileges)];
|
|
478
|
-
const policyAgentDir =
|
|
479
|
-
|
|
532
|
+
const policyAgentDir = normalPermissions ? undefined : await writeManagementPermissionSystemPolicy(agentDir, cwd, managementContext, extraToolNames, privileges);
|
|
533
|
+
const resourceEnvironment = normalPermissions ? {} : { [PI_PERMISSION_SYSTEM_POLICY_AGENT_DIR]: policyAgentDir };
|
|
534
|
+
return withRuntimeCreationEnvironment(resourceEnvironment, async () => {
|
|
480
535
|
const settingsManager = await createScopedSettingsManager({
|
|
481
536
|
cwd,
|
|
482
537
|
scopeDirectory: await resolveSettingsScope(cwd, "management"),
|
|
483
538
|
globalAgentDir: agentDir,
|
|
484
539
|
mode: "management",
|
|
540
|
+
// Signed management entry authorizes the project without changing ordinary trust decisions.
|
|
541
|
+
projectTrusted: true,
|
|
485
542
|
});
|
|
486
|
-
const services = await
|
|
543
|
+
const { services, projectTrustDiagnostics } = normalPermissions ? await createNormalPermissionSessionServices({
|
|
487
544
|
cwd,
|
|
488
545
|
agentDir,
|
|
489
546
|
modelRuntime,
|
|
490
547
|
settingsManager,
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
548
|
+
projectTrustRequiring: false,
|
|
549
|
+
appendSystemPromptSections,
|
|
550
|
+
eventBus: createEventBus(),
|
|
551
|
+
agentsFilesOverride: (base) => filterPrivilegedManagementContextFiles(cwd, managementContext, base),
|
|
552
|
+
}) : { services: await createAgentSessionServices({
|
|
553
|
+
cwd,
|
|
554
|
+
agentDir,
|
|
555
|
+
modelRuntime,
|
|
556
|
+
settingsManager,
|
|
557
|
+
resourceLoaderOptions: {
|
|
558
|
+
agentsFilesOverride: (base) => filterManagedGlobalContextFiles(cwd, agentDir, base),
|
|
559
|
+
skillsOverride: (base) => receipt === undefined ? filterManagedProjectSkills(cwd, base) : filterManagedWorkbenchSkills(cwd, base, receipt),
|
|
560
|
+
},
|
|
561
|
+
}), projectTrustDiagnostics: [] };
|
|
499
562
|
const modelOptions = await resolveSessionModelOptions({
|
|
500
563
|
services,
|
|
501
564
|
hasExistingSession: sessionManager.buildSessionContext().messages.length > 0,
|
|
@@ -503,12 +566,8 @@ function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spaw
|
|
|
503
566
|
...(initialThinkingLevel === undefined ? {} : { initialThinkingLevel }),
|
|
504
567
|
});
|
|
505
568
|
services.diagnostics.push(...modelOptions.diagnostics);
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
...createPiWebCustomToolDefinitions(cwd, delegationToolsEnabled, scopedSpawn, scopedSubsessions, askUser),
|
|
509
|
-
...workbenchTools,
|
|
510
|
-
] : [
|
|
511
|
-
...managedAgentToolDefinitions(cwd, managedToolOptions),
|
|
569
|
+
const customTools = normalPermissions ? createPiWebCustomToolDefinitions(cwd, resolvedDelegationToolsEnabled, scopedSpawn, scopedSubsessions, scopedAskUser) : [
|
|
570
|
+
...managedAgentToolDefinitions(cwd, createManagedAgentToolOptions(cwd)),
|
|
512
571
|
createManagedPythonToolDefinition(cwd, managementContext, { network: privileges.network }),
|
|
513
572
|
createManagedBashToolDefinition(cwd, managementContext, { network: privileges.network, hostFilesystem: privileges.bash }),
|
|
514
573
|
...controlledManagementTools,
|
|
@@ -518,15 +577,16 @@ function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spaw
|
|
|
518
577
|
sessionManager,
|
|
519
578
|
...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
|
|
520
579
|
customTools,
|
|
521
|
-
...(
|
|
522
|
-
? { excludeTools: managementContext.tools?.deny ?? [] }
|
|
523
|
-
: { tools: managementAgentToolNames(managementContext, extraToolNames, privileges) }),
|
|
580
|
+
...(normalPermissions ? {} : { tools: managementAgentToolNames(managementContext, extraToolNames, privileges) }),
|
|
524
581
|
...(modelOptions.model === undefined ? {} : { model: modelOptions.model }),
|
|
525
582
|
...(modelOptions.thinkingLevel === undefined ? {} : { thinkingLevel: modelOptions.thinkingLevel }),
|
|
526
583
|
...(modelOptions.scopedModels.length === 0 ? {} : { scopedModels: modelOptions.scopedModels }),
|
|
527
584
|
};
|
|
528
585
|
const result = await createAgentSessionFromServices(options);
|
|
529
|
-
|
|
586
|
+
// SDK reload re-executes extensions, so it needs the same scoped environment as startup.
|
|
587
|
+
const reload = result.session.reload.bind(result.session);
|
|
588
|
+
result.session.reload = (options) => withRuntimeCreationEnvironment(resourceEnvironment, () => reload(options));
|
|
589
|
+
return { ...result, services, diagnostics: [...projectTrustDiagnostics, ...services.diagnostics] };
|
|
530
590
|
});
|
|
531
591
|
};
|
|
532
592
|
}
|
|
@@ -582,6 +642,7 @@ export class PiSessionService {
|
|
|
582
642
|
this.activities = new SessionActivityCoordinator();
|
|
583
643
|
/** Runtime-identity gate held while Pi may await abandoned-branch summarization. */
|
|
584
644
|
this.treeNavigations = new WeakSet();
|
|
645
|
+
this.unpersistedTreeBranchLeaves = new WeakMap();
|
|
585
646
|
/** Counts async operations that may append an entry before they settle. */
|
|
586
647
|
this.sessionEntryMutationCounts = new WeakMap();
|
|
587
648
|
/** Serializes enabled-model read/modify/write operations. */
|
|
@@ -617,6 +678,7 @@ export class PiSessionService {
|
|
|
617
678
|
this.scopedNotificationStores = new Map();
|
|
618
679
|
this.scopedUnreadStores = new Map();
|
|
619
680
|
this.sessionEventScopes = new WeakMap();
|
|
681
|
+
this.sessionManagementContexts = new WeakMap();
|
|
620
682
|
this.notificationGenerationBySession = new WeakMap();
|
|
621
683
|
this.idleSessionFileResolutions = new WeakMap();
|
|
622
684
|
/** The parked extension Promise resolvers behind the store's open dialogs. */
|
|
@@ -650,6 +712,10 @@ export class PiSessionService {
|
|
|
650
712
|
this.managementAudit = deps.managementAudit;
|
|
651
713
|
this.managementProjectIdForCwd = deps.managementProjectIdForCwd;
|
|
652
714
|
this.now = deps.now ?? (() => new Date());
|
|
715
|
+
this.activityMarker = new SessionActivityMarker({
|
|
716
|
+
now: () => this.now().getTime(),
|
|
717
|
+
onError: (error) => { this.logger.info({ err: error }, "Could not update advisory session activity marker"); },
|
|
718
|
+
});
|
|
653
719
|
this.notificationStore = deps.notificationStore ?? new SessionNotificationStore();
|
|
654
720
|
this.unreadStore = deps.unreadStore ?? new SessionUnreadStore();
|
|
655
721
|
this.onUnreadChanged = deps.onUnreadChanged;
|
|
@@ -663,18 +729,19 @@ export class PiSessionService {
|
|
|
663
729
|
// Subsessions are gated behind their own flag, and they
|
|
664
730
|
// also require the spawn capability (they share its project-scope resolver).
|
|
665
731
|
const subsessionsActive = this.spawnTargets !== undefined && deps.subsessionsEnabled === true;
|
|
666
|
-
this.
|
|
732
|
+
this.sessionEvents = deps.sessionEvents ?? new PiSessionEventConnections();
|
|
733
|
+
this.createRuntime = deps.createRuntime ?? createDefaultRuntimeFactory(this.sessionEvents, this.normalModelRuntimeForCwd, this.managementModelRuntime, (cwd, mode) => this.resolveSettingsScopeDirectory(cwd, mode), this.sessionManager, this.spawnTargets === undefined ? undefined : (input) => this.spawnSession(input), !subsessionsActive ? undefined : {
|
|
667
734
|
spawn: (input) => this.spawnSubsession(input),
|
|
668
|
-
list: (parentSessionId, parentSessionFile) => this.listSubsessions(parentSessionId, parentSessionFile),
|
|
669
|
-
check: (parentSessionId, sessionId, parentSessionFile) => this.checkSubsession(parentSessionId, sessionId, parentSessionFile),
|
|
670
|
-
read: (parentSessionId, sessionId, query, parentSessionFile) => this.readSubsession(parentSessionId, sessionId, query, parentSessionFile),
|
|
671
|
-
}, deps.askUserEnabled === true ? { open: (input) => this.openAsk(input) } : undefined, deps.workbench, this.logger, this.managementAudit, this.managementProjectIdForCwd, deps.appendSystemPromptSections ?? [], deps.managementAllowPrivileged === true);
|
|
735
|
+
list: (parentSessionId, parentSessionFile, managementContext) => this.listSubsessions(parentSessionId, parentSessionFile, managementContext),
|
|
736
|
+
check: (parentSessionId, sessionId, parentSessionFile, managementContext) => this.checkSubsession(parentSessionId, sessionId, parentSessionFile, managementContext),
|
|
737
|
+
read: (parentSessionId, sessionId, query, parentSessionFile, managementContext) => this.readSubsession(parentSessionId, sessionId, query, parentSessionFile, managementContext),
|
|
738
|
+
}, deps.askUserEnabled === true ? { open: (input) => this.openAsk(input, input.managementContext) } : undefined, deps.workbench, this.logger, this.managementAudit, this.managementProjectIdForCwd, deps.appendSystemPromptSections ?? [], deps.managementAllowPrivileged === true);
|
|
672
739
|
this.createAgentRuntime = deps.createAgentRuntime ?? defaultCreateAgentRuntime;
|
|
673
740
|
this.workspaceActivity = deps.workspaceActivity;
|
|
674
741
|
this.heartbeat = setInterval(() => { this.publishHeartbeats(); }, deps.heartbeatIntervalMs ?? 2000);
|
|
675
742
|
this.commandService = new SessionCommandService((sessionId, eventScope) => this.getActiveForCommand(sessionId, eventScope), (sessionId, text, eventScope) => this.submitCommandPrompt(sessionId, text, eventScope), events, {
|
|
676
743
|
onCompactionStart: (session) => {
|
|
677
|
-
this.beginSessionEntryMutation(session, "
|
|
744
|
+
this.beginSessionEntryMutation(session, "压缩会话");
|
|
678
745
|
this.publishActivity(session, "正在压缩上下文", "active");
|
|
679
746
|
this.publishStatus(session);
|
|
680
747
|
},
|
|
@@ -693,7 +760,7 @@ export class PiSessionService {
|
|
|
693
760
|
isTreeNavigationActive: (session) => this.treeNavigations.has(session),
|
|
694
761
|
runSessionReplacement: (session, operation) => {
|
|
695
762
|
const runtime = this.activeRuntimeForSession(session);
|
|
696
|
-
return this.runTreeExclusiveOperation([{ sessionId: session.sessionId, session, ...(runtime === undefined ? {} : { runtime }) }], "
|
|
763
|
+
return this.runTreeExclusiveOperation([{ sessionId: session.sessionId, session, ...(runtime === undefined ? {} : { runtime }) }], "替换会话前请先停止当前会话活动", operation);
|
|
697
764
|
},
|
|
698
765
|
}, { listSessionNames: (cwd) => this.listSessionNames(cwd) });
|
|
699
766
|
}
|
|
@@ -741,7 +808,6 @@ export class PiSessionService {
|
|
|
741
808
|
await this.closeActive(record.sessionId, { kind: "clear", reason: "delete" }, scope);
|
|
742
809
|
readyDeleteRecords.push(record);
|
|
743
810
|
}
|
|
744
|
-
await this.ensureArchivedRecordsMoved(readyDeleteRecords);
|
|
745
811
|
const deletedSessionIds = new Set(await this.archiveStoreDeleteArchivedMany(readyDeleteRecords.map((record) => record.sessionId)));
|
|
746
812
|
deleteRecords.push(...readyDeleteRecords.filter((record) => deletedSessionIds.has(record.sessionId)));
|
|
747
813
|
await this.forgetUnreadSessions(deleteRecords, scope);
|
|
@@ -768,7 +834,7 @@ export class PiSessionService {
|
|
|
768
834
|
const activeSessions = Array.from(new Set(this.active.values()));
|
|
769
835
|
for (const active of activeSessions) {
|
|
770
836
|
this.forgetUnreadActivity(active.runtime.session);
|
|
771
|
-
this.pendingAskStore.forgetSession(active.runtime.session
|
|
837
|
+
this.pendingAskStore.forgetSession(this.dialogSessionKey(active.runtime.session));
|
|
772
838
|
this.endSessionExtensionDialogs(active.runtime.session);
|
|
773
839
|
}
|
|
774
840
|
this.active.clear();
|
|
@@ -787,10 +853,15 @@ export class PiSessionService {
|
|
|
787
853
|
store.clearAll("service-dispose");
|
|
788
854
|
await Promise.all(activeSessions.map(async (active) => {
|
|
789
855
|
active.unsubscribe();
|
|
856
|
+
active.runtime.setRebindSession(undefined);
|
|
790
857
|
this.workspaceActivity?.removeSession(active.runtime.session.sessionId, active.runtime.session.sessionManager.getCwd(), active.eventScope);
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
858
|
+
try {
|
|
859
|
+
await this.abortSessionOperations(active.runtime.session);
|
|
860
|
+
}
|
|
861
|
+
finally {
|
|
862
|
+
await active.runtime.dispose();
|
|
863
|
+
}
|
|
864
|
+
})).finally(() => this.activityMarker.dispose());
|
|
794
865
|
await this.publishUnreadMutations([]);
|
|
795
866
|
}
|
|
796
867
|
async list(cwd, managementContext) {
|
|
@@ -858,9 +929,9 @@ export class PiSessionService {
|
|
|
858
929
|
await this.publishUnreadMutations(this.unreadForScope(eventScopeFromManagementContext(managementContext)).reconcileCwd(canonicalizeStoredCwd(cwd), reconcilableSessionIds));
|
|
859
930
|
}
|
|
860
931
|
const archivedSessions = archivedForCwd
|
|
861
|
-
.sort(compareArchivedRecords)
|
|
862
932
|
.map((record) => clientSessionFromArchivedRecord(record, sessionsById.get(record.sessionId)))
|
|
863
|
-
.filter(isDefined)
|
|
933
|
+
.filter(isDefined)
|
|
934
|
+
.sort((a, b) => Date.parse(b.modified) - Date.parse(a.modified));
|
|
864
935
|
return {
|
|
865
936
|
sessions: [...unarchivedSessions, ...archivedSessions],
|
|
866
937
|
entriesById: new Map(sessions.map((session) => [session.id, session])),
|
|
@@ -872,15 +943,42 @@ export class PiSessionService {
|
|
|
872
943
|
await this.normalModelRuntimeForCwd(cwd);
|
|
873
944
|
return this.startSession(cwd, options);
|
|
874
945
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
946
|
+
/** Publish a host-owned normal-mode conversation without submitting an initial prompt. */
|
|
947
|
+
async createHostedSession(cwd, signal) {
|
|
948
|
+
const created = await this.startManagedSession(cwd, signal);
|
|
949
|
+
return Object.freeze({ id: created.id });
|
|
950
|
+
}
|
|
951
|
+
/** Atomically start and prompt one visible normal-mode, non-delegating session. */
|
|
952
|
+
async startOneShotRun(cwd, text, signal) {
|
|
953
|
+
const promptText = requirePromptText(text);
|
|
954
|
+
return this.startManagedSession(cwd, signal, promptText);
|
|
955
|
+
}
|
|
956
|
+
async startManagedSession(cwd, signal, promptText) {
|
|
957
|
+
signal.throwIfAborted();
|
|
958
|
+
await this.normalModelRuntimeForCwd(cwd);
|
|
959
|
+
const active = await this.create(this.sessionManager.create(cwd), cwd, { startupIntent: "create", creationProvenance: "host-one-shot", startupSignal: signal });
|
|
960
|
+
const { session } = active.runtime;
|
|
961
|
+
try {
|
|
962
|
+
signal.throwIfAborted();
|
|
963
|
+
if (promptText !== undefined)
|
|
964
|
+
this.maybeGenerateSessionName(session, promptText);
|
|
965
|
+
const created = this.announceCreatedSession(active, cwd);
|
|
966
|
+
// Publication transfers lifetime ownership to normal hosting. Plugin
|
|
967
|
+
// cancellation after this point must not abort this or later user work.
|
|
968
|
+
const completion = promptText === undefined ? Promise.resolve() : this.submitInitialRunPrompt(session, promptText);
|
|
969
|
+
return Object.freeze({ id: created.id, completion });
|
|
970
|
+
}
|
|
971
|
+
catch (error) {
|
|
972
|
+
try {
|
|
973
|
+
await this.closeActive(session.sessionId);
|
|
974
|
+
}
|
|
975
|
+
catch (cleanupError) {
|
|
976
|
+
throw new AggregateError([error, cleanupError], `托管会话 ${session.sessionId} 启动失败后清理失败`, { cause: cleanupError });
|
|
977
|
+
}
|
|
978
|
+
throw error;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
announceCreatedSession(active, cwd, parentSession) {
|
|
884
982
|
const { session } = active.runtime;
|
|
885
983
|
const created = {
|
|
886
984
|
id: session.sessionId,
|
|
@@ -893,13 +991,22 @@ export class PiSessionService {
|
|
|
893
991
|
firstMessage: "",
|
|
894
992
|
// Include the parent so listeners can nest the new session in the tree
|
|
895
993
|
// immediately, instead of showing it flat until the next reload.
|
|
896
|
-
...(
|
|
994
|
+
...(parentSession === undefined ? {} : { parentSessionPath: parentSession }),
|
|
897
995
|
};
|
|
898
|
-
// Broadcast so other clients (and the spawning agent's UI) can add the new
|
|
899
|
-
// session to their list without a manual reload.
|
|
900
996
|
this.events.publishGlobal({ type: "session.created", session: created }, active.eventScope);
|
|
901
997
|
return created;
|
|
902
998
|
}
|
|
999
|
+
async startSession(cwd, options) {
|
|
1000
|
+
const active = await this.create(this.sessionManager.create(cwd, options.parentSession === undefined ? undefined : { parentSession: options.parentSession }), cwd, {
|
|
1001
|
+
...(options.managementContext === undefined ? {} : { managementContext: options.managementContext }),
|
|
1002
|
+
...(options.initialModel === undefined ? {} : { initialModel: options.initialModel }),
|
|
1003
|
+
...(options.initialThinkingLevel === undefined ? {} : { initialThinkingLevel: options.initialThinkingLevel }),
|
|
1004
|
+
...(options.creationProvenance === undefined ? {} : { creationProvenance: options.creationProvenance }),
|
|
1005
|
+
...(options.startupToken === undefined ? {} : { startupToken: options.startupToken }),
|
|
1006
|
+
startupIntent: "create",
|
|
1007
|
+
});
|
|
1008
|
+
return this.announceCreatedSession(active, cwd, options.parentSession);
|
|
1009
|
+
}
|
|
903
1010
|
/**
|
|
904
1011
|
* Start a new session on behalf of a LLM and deliver an initial prompt to it.
|
|
905
1012
|
* The target cwd is constrained to a workspace of the same registered project
|
|
@@ -964,9 +1071,9 @@ export class PiSessionService {
|
|
|
964
1071
|
...(parentSessionFile === undefined ? {} : { parentSessionFile }),
|
|
965
1072
|
cwd: decision.cwd,
|
|
966
1073
|
};
|
|
967
|
-
await this.registerVerifiedSubsession(link,
|
|
968
|
-
this.persistSubsessionLink(
|
|
969
|
-
this.persistSubsessionChildMarker(
|
|
1074
|
+
const scopedLink = await this.registerVerifiedSubsession(link, input.managementContext);
|
|
1075
|
+
this.persistSubsessionLink(scopedLink);
|
|
1076
|
+
this.persistSubsessionChildMarker(scopedLink);
|
|
970
1077
|
await this.prompt(created.id, input.prompt, undefined, undefined, {
|
|
971
1078
|
...(input.managementContext === undefined ? {} : { managementContext: input.managementContext }),
|
|
972
1079
|
});
|
|
@@ -1042,6 +1149,23 @@ export class PiSessionService {
|
|
|
1042
1149
|
this.modelScopeMutationQueue = queued.then(() => undefined, () => undefined);
|
|
1043
1150
|
return queued;
|
|
1044
1151
|
}
|
|
1152
|
+
reloadModelScope(session) {
|
|
1153
|
+
return this.runModelScopeMutation(async () => {
|
|
1154
|
+
const context = this.modelScopeContext(session);
|
|
1155
|
+
await this.modelScopeInitializations.get(context.key);
|
|
1156
|
+
const enabledIds = await resolveEnabledModelIds({
|
|
1157
|
+
settingsManager: session.settingsManager,
|
|
1158
|
+
// Runtime-backed sessions hold the SDK ModelRuntime behind the narrow test seam.
|
|
1159
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
1160
|
+
modelRuntime: session.modelRuntime,
|
|
1161
|
+
scopedModels: [],
|
|
1162
|
+
});
|
|
1163
|
+
const revision = ++this.modelScopeRevision;
|
|
1164
|
+
this.modelScopeStates.set(context.key, { enabledIds, revision });
|
|
1165
|
+
await this.synchronizeSessionModelScope(session, session.modelRuntime.getAvailableSnapshot());
|
|
1166
|
+
this.events.publishGlobal({ type: "models.changed", revision }, this.eventScopeForSession(session));
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1045
1169
|
applyEnabledModelScope(session, available, enabledIds) {
|
|
1046
1170
|
const context = this.modelScopeContext(session);
|
|
1047
1171
|
const availableIds = available.map(modelScopeId);
|
|
@@ -1084,14 +1208,16 @@ export class PiSessionService {
|
|
|
1084
1208
|
* agent loop reports to the model as an error tool result.
|
|
1085
1209
|
*/
|
|
1086
1210
|
// eslint-disable-next-line @typescript-eslint/require-await -- async so a rejected question set becomes a rejection rather than a synchronous throw from a promise-returning method.
|
|
1087
|
-
async openAsk(input) {
|
|
1088
|
-
const
|
|
1211
|
+
async openAsk(input, managementContext) {
|
|
1212
|
+
const scope = eventScopeFromManagementContext(managementContext);
|
|
1213
|
+
const sessionKey = scope === NORMAL_SESSION_EVENT_SCOPE ? input.sessionId : activeSessionKey(input.sessionId, scope);
|
|
1214
|
+
const result = this.pendingAskStore.open({ ...input, sessionId: sessionKey });
|
|
1089
1215
|
// A supersede closes the earlier ask, so the browsers watching it must hear
|
|
1090
1216
|
// that before they hear about its replacement.
|
|
1091
1217
|
if (result.superseded !== undefined)
|
|
1092
|
-
this.publishAskClosed(input.sessionId, result.superseded);
|
|
1093
|
-
this.events.publish(input.sessionId, { type: "ask.opened", ask: result.ask });
|
|
1094
|
-
this.publishStatusForSessionId(input.sessionId);
|
|
1218
|
+
this.publishAskClosed(input.sessionId, result.superseded, scope);
|
|
1219
|
+
this.events.publish(input.sessionId, { type: "ask.opened", ask: result.ask }, scope);
|
|
1220
|
+
this.publishStatusForSessionId(input.sessionId, scope);
|
|
1095
1221
|
return result;
|
|
1096
1222
|
}
|
|
1097
1223
|
/**
|
|
@@ -1107,8 +1233,8 @@ export class PiSessionService {
|
|
|
1107
1233
|
const session = await this.getOrOpen(ref, managementContext);
|
|
1108
1234
|
// Checked before the store closes the ask so a refused delivery cannot
|
|
1109
1235
|
// discard answers the user already submitted.
|
|
1110
|
-
this.assertTreeNavigationInactive(session, "
|
|
1111
|
-
return this.closeAsk(session, this.pendingAskStore.submit(session
|
|
1236
|
+
this.assertTreeNavigationInactive(session, "回答问题");
|
|
1237
|
+
return this.closeAsk(session, this.pendingAskStore.submit(this.dialogSessionKey(session), askId, submission));
|
|
1112
1238
|
}
|
|
1113
1239
|
/**
|
|
1114
1240
|
* Close the open ask without answers. The model is still told, naming every
|
|
@@ -1118,8 +1244,8 @@ export class PiSessionService {
|
|
|
1118
1244
|
async cancelAsk(ref, askId, managementContext) {
|
|
1119
1245
|
await this.assertWritable(ref);
|
|
1120
1246
|
const session = await this.getOrOpen(ref, managementContext);
|
|
1121
|
-
this.assertTreeNavigationInactive(session, "
|
|
1122
|
-
return this.closeAsk(session, this.pendingAskStore.cancel(session
|
|
1247
|
+
this.assertTreeNavigationInactive(session, "关闭问题");
|
|
1248
|
+
return this.closeAsk(session, this.pendingAskStore.cancel(this.dialogSessionKey(session), askId));
|
|
1123
1249
|
}
|
|
1124
1250
|
/**
|
|
1125
1251
|
* Publish and deliver a closed ask. A stale close is reported rather than
|
|
@@ -1131,13 +1257,13 @@ export class PiSessionService {
|
|
|
1131
1257
|
if (result.status === "stale")
|
|
1132
1258
|
return { result: "stale", sessionStatus: this.statusFromSession(session) };
|
|
1133
1259
|
const { outcome } = result;
|
|
1134
|
-
this.publishAskClosed(session.sessionId, outcome);
|
|
1135
|
-
await this.runSessionEntryMutation(session, "
|
|
1260
|
+
this.publishAskClosed(session.sessionId, outcome, this.eventScopeForSession(session));
|
|
1261
|
+
await this.runSessionEntryMutation(session, "提交问题回答", () => session.sendCustomMessage({ customType: ASK_USER_ANSWERS_CUSTOM_TYPE, content: renderAskUserAnswersText(outcome), display: true, details: outcome }, { triggerTurn: true, deliverAs: "followUp" }));
|
|
1136
1262
|
this.publishStatus(session);
|
|
1137
1263
|
return { result: "closed", outcome, sessionStatus: this.statusFromSession(session) };
|
|
1138
1264
|
}
|
|
1139
|
-
publishAskClosed(sessionId, outcome) {
|
|
1140
|
-
this.events.publish(sessionId, { type: "ask.closed", askId: outcome.askId, reason: outcome.reason });
|
|
1265
|
+
publishAskClosed(sessionId, outcome, scope) {
|
|
1266
|
+
this.events.publish(sessionId, { type: "ask.closed", askId: outcome.askId, reason: outcome.reason }, scope);
|
|
1141
1267
|
}
|
|
1142
1268
|
/**
|
|
1143
1269
|
* Void the session's open ask because the user sent a chat message instead of
|
|
@@ -1146,11 +1272,11 @@ export class PiSessionService {
|
|
|
1146
1272
|
* itself triggers rather than becoming a turn of its own.
|
|
1147
1273
|
*/
|
|
1148
1274
|
async voidOpenAskForUserMessage(session) {
|
|
1149
|
-
const outcome = this.pendingAskStore.cancelOpen(session
|
|
1275
|
+
const outcome = this.pendingAskStore.cancelOpen(this.dialogSessionKey(session));
|
|
1150
1276
|
if (outcome === undefined)
|
|
1151
1277
|
return;
|
|
1152
|
-
this.publishAskClosed(session.sessionId, outcome);
|
|
1153
|
-
await this.runSessionEntryMutation(session, "
|
|
1278
|
+
this.publishAskClosed(session.sessionId, outcome, this.eventScopeForSession(session));
|
|
1279
|
+
await this.runSessionEntryMutation(session, "作废未回答问题", () => session.sendCustomMessage({ customType: ASK_USER_ANSWERS_CUSTOM_TYPE, content: renderAskUserAnswersText(outcome), display: true, details: outcome }, { triggerTurn: false, deliverAs: "followUp" }));
|
|
1154
1280
|
this.publishStatus(session);
|
|
1155
1281
|
}
|
|
1156
1282
|
/**
|
|
@@ -1195,8 +1321,12 @@ export class PiSessionService {
|
|
|
1195
1321
|
* `async` so a rejected dialog becomes a rejection rather than a synchronous
|
|
1196
1322
|
* throw from a promise-returning method.
|
|
1197
1323
|
*/
|
|
1198
|
-
async openExtensionDialog(session, request, opts) {
|
|
1199
|
-
const signal = opts?.signal
|
|
1324
|
+
async openExtensionDialog(session, request, opts, startupSignal) {
|
|
1325
|
+
const signal = opts?.signal === undefined
|
|
1326
|
+
? startupSignal
|
|
1327
|
+
: startupSignal === undefined
|
|
1328
|
+
? opts.signal
|
|
1329
|
+
: AbortSignal.any([opts.signal, startupSignal]);
|
|
1200
1330
|
// A pre-aborted signal dismisses the dialog before it ever opens.
|
|
1201
1331
|
if (signal?.aborted === true)
|
|
1202
1332
|
return extensionDialogCancelValue(request.kind);
|
|
@@ -1278,18 +1408,19 @@ export class PiSessionService {
|
|
|
1278
1408
|
}, this.eventScopeForSession(session));
|
|
1279
1409
|
}
|
|
1280
1410
|
/** Summaries of the tracked subsessions spawned by `parentSessionId`. */
|
|
1281
|
-
async listSubsessions(parentSessionId, parentSessionFile) {
|
|
1411
|
+
async listSubsessions(parentSessionId, parentSessionFile, managementContext) {
|
|
1282
1412
|
const parentFile = nonEmptyString(parentSessionFile);
|
|
1283
|
-
|
|
1284
|
-
|
|
1413
|
+
const scope = eventScopeFromManagementContext(managementContext);
|
|
1414
|
+
await this.hydrateSubsessionsForParent(parentSessionId, parentFile, managementContext);
|
|
1415
|
+
const childIds = this.subsessionChildren.get(activeSessionKey(parentSessionId, scope));
|
|
1285
1416
|
if (childIds === undefined)
|
|
1286
1417
|
return [];
|
|
1287
|
-
const authorizedChildIds = [...childIds].filter((childId) => this.subsessionLinkBelongsToParent(parentSessionId, parentFile, childId));
|
|
1288
|
-
return Promise.all(authorizedChildIds.map(async (childId) => ({ sessionId: childId, ...(await this.subsessionSummaryFields(childId)) })));
|
|
1418
|
+
const authorizedChildIds = [...childIds].filter((childId) => this.subsessionLinkBelongsToParent(parentSessionId, parentFile, childId, scope));
|
|
1419
|
+
return Promise.all(authorizedChildIds.map(async (childId) => ({ sessionId: childId, ...(await this.subsessionSummaryFields(childId, scope)) })));
|
|
1289
1420
|
}
|
|
1290
1421
|
/** Status and final result of a subsession, scoped to the caller's children. */
|
|
1291
|
-
async checkSubsession(parentSessionId, sessionId, parentSessionFile) {
|
|
1292
|
-
const session = await this.openSubsession(parentSessionId, sessionId, parentSessionFile);
|
|
1422
|
+
async checkSubsession(parentSessionId, sessionId, parentSessionFile, managementContext) {
|
|
1423
|
+
const session = await this.openSubsession(parentSessionId, sessionId, parentSessionFile, managementContext);
|
|
1293
1424
|
const messages = historyMessages(session);
|
|
1294
1425
|
return {
|
|
1295
1426
|
sessionId,
|
|
@@ -1300,8 +1431,8 @@ export class PiSessionService {
|
|
|
1300
1431
|
};
|
|
1301
1432
|
}
|
|
1302
1433
|
/** Filtered, paginated transcript of a subsession, scoped to the caller's children. */
|
|
1303
|
-
async readSubsession(parentSessionId, sessionId, query, parentSessionFile) {
|
|
1304
|
-
const session = await this.openSubsession(parentSessionId, sessionId, parentSessionFile);
|
|
1434
|
+
async readSubsession(parentSessionId, sessionId, query, parentSessionFile, managementContext) {
|
|
1435
|
+
const session = await this.openSubsession(parentSessionId, sessionId, parentSessionFile, managementContext);
|
|
1305
1436
|
const view = buildTranscriptView(historyMessages(session), query);
|
|
1306
1437
|
return {
|
|
1307
1438
|
sessionId,
|
|
@@ -1311,52 +1442,61 @@ export class PiSessionService {
|
|
|
1311
1442
|
};
|
|
1312
1443
|
}
|
|
1313
1444
|
/** Open a session after verifying it is one of the caller's tracked children. */
|
|
1314
|
-
async openSubsession(parentSessionId, sessionId, parentSessionFile) {
|
|
1445
|
+
async openSubsession(parentSessionId, sessionId, parentSessionFile, managementContext) {
|
|
1315
1446
|
const parentFile = nonEmptyString(parentSessionFile);
|
|
1316
|
-
|
|
1317
|
-
|
|
1447
|
+
const scope = eventScopeFromManagementContext(managementContext);
|
|
1448
|
+
await this.hydrateSubsessionsForParent(parentSessionId, parentFile, managementContext);
|
|
1449
|
+
if (this.subsessionParents.get(activeSessionKey(sessionId, scope)) !== parentSessionId || !this.subsessionLinkBelongsToParent(parentSessionId, parentFile, sessionId, scope)) {
|
|
1318
1450
|
throw new Error(`Session ${sessionId} is not one of your subsessions`);
|
|
1319
1451
|
}
|
|
1320
|
-
return this.getOrOpenTrackedSubsession(sessionId);
|
|
1452
|
+
return this.getOrOpenTrackedSubsession(sessionId, scope);
|
|
1321
1453
|
}
|
|
1322
|
-
subsessionLinkBelongsToParent(parentSessionId, parentSessionFile, childSessionId) {
|
|
1323
|
-
const link = this.subsessionLinks.get(childSessionId);
|
|
1454
|
+
subsessionLinkBelongsToParent(parentSessionId, parentSessionFile, childSessionId, scope) {
|
|
1455
|
+
const link = this.subsessionLinks.get(activeSessionKey(childSessionId, scope));
|
|
1324
1456
|
if (link?.parentSessionId !== parentSessionId)
|
|
1325
1457
|
return false;
|
|
1326
1458
|
return parentSessionFile === undefined || trackedLinkParentFileMatches(link, parentSessionFile);
|
|
1327
1459
|
}
|
|
1328
1460
|
activeChildForSubsessionLink(link) {
|
|
1329
|
-
|
|
1461
|
+
const active = this.activeForSessionIdAndScope(link.childSessionId, link.eventScope);
|
|
1462
|
+
return active !== undefined && activeSessionFileMatches(active, link.childSessionFile) ? active : undefined;
|
|
1330
1463
|
}
|
|
1331
1464
|
activeParentForSubsessionLink(link) {
|
|
1332
|
-
|
|
1465
|
+
const active = this.activeForSessionIdAndScope(link.parentSessionId, link.eventScope);
|
|
1466
|
+
return active !== undefined && activeSessionFileMatches(active, link.parentSessionFile) ? active : undefined;
|
|
1333
1467
|
}
|
|
1334
1468
|
subsessionLinkForActiveChild(session) {
|
|
1335
|
-
const
|
|
1336
|
-
const parentId = this.subsessionParents.get(
|
|
1337
|
-
const link = this.subsessionLinks.get(
|
|
1469
|
+
const childKey = activeSessionKey(session.sessionId, this.eventScopeForSession(session));
|
|
1470
|
+
const parentId = this.subsessionParents.get(childKey);
|
|
1471
|
+
const link = this.subsessionLinks.get(childKey);
|
|
1338
1472
|
if (parentId === undefined || link?.parentSessionId !== parentId)
|
|
1339
1473
|
return undefined;
|
|
1340
1474
|
return sessionFileMatches(session, link.childSessionFile) ? link : undefined;
|
|
1341
1475
|
}
|
|
1342
|
-
async registerVerifiedSubsession(link,
|
|
1476
|
+
async registerVerifiedSubsession(link, managementContext) {
|
|
1343
1477
|
const { childSessionId, parentSessionId } = link;
|
|
1344
|
-
const
|
|
1478
|
+
const scope = eventScopeFromManagementContext(managementContext);
|
|
1479
|
+
const childKey = activeSessionKey(childSessionId, scope);
|
|
1480
|
+
const parentKey = activeSessionKey(parentSessionId, scope);
|
|
1481
|
+
const scopedLink = { ...link, eventScope: scope, ...(managementContext === undefined ? {} : { managementContext }) };
|
|
1482
|
+
const previousParentId = this.subsessionParents.get(childKey);
|
|
1345
1483
|
if (previousParentId !== undefined && previousParentId !== parentSessionId) {
|
|
1346
|
-
const
|
|
1484
|
+
const previousParentKey = activeSessionKey(previousParentId, scope);
|
|
1485
|
+
const previousChildren = this.subsessionChildren.get(previousParentKey);
|
|
1347
1486
|
previousChildren?.delete(childSessionId);
|
|
1348
1487
|
if (previousChildren?.size === 0)
|
|
1349
|
-
this.subsessionChildren.delete(
|
|
1488
|
+
this.subsessionChildren.delete(previousParentKey);
|
|
1350
1489
|
}
|
|
1351
|
-
this.subsessionParents.set(
|
|
1352
|
-
const children = this.subsessionChildren.get(
|
|
1490
|
+
this.subsessionParents.set(childKey, parentSessionId);
|
|
1491
|
+
const children = this.subsessionChildren.get(parentKey) ?? new Set();
|
|
1353
1492
|
children.add(childSessionId);
|
|
1354
|
-
this.subsessionChildren.set(
|
|
1355
|
-
this.subsessionLinks.set(
|
|
1356
|
-
if (!this.subsessionNotifyArmed.has(
|
|
1357
|
-
this.subsessionNotifyArmed.set(
|
|
1358
|
-
const cwd = this.cwdForVerifiedSubsession(
|
|
1493
|
+
this.subsessionChildren.set(parentKey, children);
|
|
1494
|
+
this.subsessionLinks.set(childKey, scopedLink);
|
|
1495
|
+
if (!this.subsessionNotifyArmed.has(childKey))
|
|
1496
|
+
this.subsessionNotifyArmed.set(childKey, false);
|
|
1497
|
+
const cwd = this.cwdForVerifiedSubsession(scopedLink);
|
|
1359
1498
|
await this.publishUnreadMutations(this.unreadForScope(scope).excludeSession(childSessionId, cwd));
|
|
1499
|
+
return scopedLink;
|
|
1360
1500
|
}
|
|
1361
1501
|
cwdForVerifiedSubsession(link) {
|
|
1362
1502
|
const activeCwd = this.activeChildForSubsessionLink(link)?.runtime.session.sessionManager.getCwd();
|
|
@@ -1374,17 +1514,19 @@ export class PiSessionService {
|
|
|
1374
1514
|
}
|
|
1375
1515
|
throw new Error("Could not resolve cwd for verified tracked sub-session");
|
|
1376
1516
|
}
|
|
1377
|
-
unregisterSubsession(childSessionId) {
|
|
1378
|
-
const
|
|
1379
|
-
this.subsessionParents.
|
|
1380
|
-
this.
|
|
1381
|
-
this.
|
|
1517
|
+
unregisterSubsession(childSessionId, scope) {
|
|
1518
|
+
const childKey = activeSessionKey(childSessionId, scope);
|
|
1519
|
+
const parentSessionId = this.subsessionParents.get(childKey);
|
|
1520
|
+
this.subsessionParents.delete(childKey);
|
|
1521
|
+
this.subsessionLinks.delete(childKey);
|
|
1522
|
+
this.subsessionNotifyArmed.delete(childKey);
|
|
1382
1523
|
if (parentSessionId === undefined)
|
|
1383
1524
|
return;
|
|
1384
|
-
const
|
|
1525
|
+
const parentKey = activeSessionKey(parentSessionId, scope);
|
|
1526
|
+
const children = this.subsessionChildren.get(parentKey);
|
|
1385
1527
|
children?.delete(childSessionId);
|
|
1386
1528
|
if (children?.size === 0)
|
|
1387
|
-
this.subsessionChildren.delete(
|
|
1529
|
+
this.subsessionChildren.delete(parentKey);
|
|
1388
1530
|
}
|
|
1389
1531
|
persistSubsessionLink(link) {
|
|
1390
1532
|
const parent = this.activeParentForSubsessionLink(link)?.runtime.session;
|
|
@@ -1399,8 +1541,9 @@ export class PiSessionService {
|
|
|
1399
1541
|
this.logger.info({ parentSessionId: link.parentSessionId, sessionId: link.childSessionId, error: error instanceof Error ? error.message : String(error) }, "failed to persist subsession link");
|
|
1400
1542
|
}
|
|
1401
1543
|
}
|
|
1402
|
-
persistSubsessionChildMarker(
|
|
1403
|
-
const
|
|
1544
|
+
persistSubsessionChildMarker(link) {
|
|
1545
|
+
const { parentSessionId, childSessionId } = link;
|
|
1546
|
+
const child = this.activeChildForSubsessionLink(link)?.runtime.session;
|
|
1404
1547
|
if (child === undefined)
|
|
1405
1548
|
return;
|
|
1406
1549
|
if (child.sessionManager.appendCustomEntry === undefined)
|
|
@@ -1412,14 +1555,15 @@ export class PiSessionService {
|
|
|
1412
1555
|
this.logger.info({ parentSessionId, sessionId: childSessionId, error: error instanceof Error ? error.message : String(error) }, "failed to persist subsession child marker");
|
|
1413
1556
|
}
|
|
1414
1557
|
}
|
|
1415
|
-
async hydrateSubsessionsForParent(parentSessionId, parentSessionFile) {
|
|
1416
|
-
const
|
|
1558
|
+
async hydrateSubsessionsForParent(parentSessionId, parentSessionFile, managementContext) {
|
|
1559
|
+
const scope = eventScopeFromManagementContext(managementContext);
|
|
1560
|
+
const hydrationKey = subsessionHydratedParentKey(activeSessionKey(parentSessionId, scope), parentSessionFile);
|
|
1417
1561
|
if (this.subsessionHydratedParents.has(hydrationKey))
|
|
1418
1562
|
return;
|
|
1419
|
-
const activeParent = this.
|
|
1563
|
+
const activeParent = this.activeForSessionIdAndScope(parentSessionId, scope);
|
|
1420
1564
|
if (activeParent !== undefined && (parentSessionFile === undefined || activeSessionFileMatches(activeParent, parentSessionFile))) {
|
|
1421
1565
|
const activeParentFile = nonEmptyString(activeParent.runtime.session.sessionFile);
|
|
1422
|
-
const complete = await this.registerPersistedSubsessionLinks(parentSessionId, activeParent.runtime.session.sessionManager, activeParentFile,
|
|
1566
|
+
const complete = await this.registerPersistedSubsessionLinks(parentSessionId, activeParent.runtime.session.sessionManager, activeParentFile, managementContext);
|
|
1423
1567
|
if (complete)
|
|
1424
1568
|
this.subsessionHydratedParents.add(hydrationKey);
|
|
1425
1569
|
return;
|
|
@@ -1435,11 +1579,11 @@ export class PiSessionService {
|
|
|
1435
1579
|
catch {
|
|
1436
1580
|
return;
|
|
1437
1581
|
}
|
|
1438
|
-
const complete = await this.registerPersistedSubsessionLinks(parentSessionId, parentManager, parentSessionFile);
|
|
1582
|
+
const complete = await this.registerPersistedSubsessionLinks(parentSessionId, parentManager, parentSessionFile, managementContext);
|
|
1439
1583
|
if (complete)
|
|
1440
1584
|
this.subsessionHydratedParents.add(hydrationKey);
|
|
1441
1585
|
}
|
|
1442
|
-
async registerPersistedSubsessionLinks(parentSessionId, parentManager, parentSessionFile,
|
|
1586
|
+
async registerPersistedSubsessionLinks(parentSessionId, parentManager, parentSessionFile, managementContext) {
|
|
1443
1587
|
// Parent custom links are the authoritative recovery record: verify the
|
|
1444
1588
|
// exact live child file/header before tracking. Do not negatively cache a
|
|
1445
1589
|
// scan while a candidate child is temporarily unavailable.
|
|
@@ -1454,7 +1598,7 @@ export class PiSessionService {
|
|
|
1454
1598
|
complete = false;
|
|
1455
1599
|
continue;
|
|
1456
1600
|
}
|
|
1457
|
-
await this.registerVerifiedSubsession(verified,
|
|
1601
|
+
await this.registerVerifiedSubsession(verified, managementContext);
|
|
1458
1602
|
}
|
|
1459
1603
|
return complete;
|
|
1460
1604
|
}
|
|
@@ -1475,7 +1619,7 @@ export class PiSessionService {
|
|
|
1475
1619
|
const link = await this.verifiedSubsessionLinkFromOpenedChild(session);
|
|
1476
1620
|
if (link === undefined)
|
|
1477
1621
|
return;
|
|
1478
|
-
await this.registerVerifiedSubsession(link, this.
|
|
1622
|
+
await this.registerVerifiedSubsession(link, this.sessionManagementContexts.get(session));
|
|
1479
1623
|
}
|
|
1480
1624
|
verifiedSubsessionLinkFromOpenedChild(session) {
|
|
1481
1625
|
return verifiedTrackedSubsessionLink(this.sessionManager, {
|
|
@@ -1485,8 +1629,8 @@ export class PiSessionService {
|
|
|
1485
1629
|
cwd: session.sessionManager.getCwd(),
|
|
1486
1630
|
});
|
|
1487
1631
|
}
|
|
1488
|
-
async getOrOpenTrackedSubsession(sessionId) {
|
|
1489
|
-
const link = this.subsessionLinks.get(sessionId);
|
|
1632
|
+
async getOrOpenTrackedSubsession(sessionId, scope) {
|
|
1633
|
+
const link = this.subsessionLinks.get(activeSessionKey(sessionId, scope));
|
|
1490
1634
|
if (link === undefined)
|
|
1491
1635
|
throw new Error("Session not found");
|
|
1492
1636
|
const active = this.activeChildForSubsessionLink(link);
|
|
@@ -1496,12 +1640,14 @@ export class PiSessionService {
|
|
|
1496
1640
|
if (!(await sessionFileHeaderMatches(link.childSessionFile, { sessionId, parentSessionFile: link.parentSessionFile })))
|
|
1497
1641
|
throw new Error("Session not found");
|
|
1498
1642
|
const sessionManager = this.sessionManager.open(link.childSessionFile);
|
|
1499
|
-
return (await this.create(sessionManager, link.cwd ?? sessionManager.getCwd()
|
|
1643
|
+
return (await this.create(sessionManager, link.cwd ?? sessionManager.getCwd(), {
|
|
1644
|
+
...(link.managementContext === undefined ? {} : { managementContext: link.managementContext }),
|
|
1645
|
+
})).runtime.session;
|
|
1500
1646
|
}
|
|
1501
1647
|
throw new Error("Session not found");
|
|
1502
1648
|
}
|
|
1503
|
-
async subsessionSummaryFields(childSessionId) {
|
|
1504
|
-
const link = this.subsessionLinks.get(childSessionId);
|
|
1649
|
+
async subsessionSummaryFields(childSessionId, scope) {
|
|
1650
|
+
const link = this.subsessionLinks.get(activeSessionKey(childSessionId, scope));
|
|
1505
1651
|
const active = link === undefined ? undefined : this.activeChildForSubsessionLink(link);
|
|
1506
1652
|
if (active !== undefined) {
|
|
1507
1653
|
return { cwd: active.runtime.cwd, status: this.subsessionStatus(active.runtime.session) };
|
|
@@ -1520,12 +1666,12 @@ export class PiSessionService {
|
|
|
1520
1666
|
return "error";
|
|
1521
1667
|
return "idle";
|
|
1522
1668
|
}
|
|
1523
|
-
workingSubsessionIds(parentSessionId) {
|
|
1524
|
-
const childIds = this.subsessionChildren.get(parentSessionId);
|
|
1669
|
+
workingSubsessionIds(parentSessionId, scope) {
|
|
1670
|
+
const childIds = this.subsessionChildren.get(activeSessionKey(parentSessionId, scope));
|
|
1525
1671
|
if (childIds === undefined)
|
|
1526
1672
|
return [];
|
|
1527
1673
|
return [...childIds].filter((childId) => {
|
|
1528
|
-
const link = this.subsessionLinks.get(childId);
|
|
1674
|
+
const link = this.subsessionLinks.get(activeSessionKey(childId, scope));
|
|
1529
1675
|
const active = link === undefined ? undefined : this.activeChildForSubsessionLink(link);
|
|
1530
1676
|
return active !== undefined && this.hasActiveWork(active.runtime.session);
|
|
1531
1677
|
});
|
|
@@ -1541,27 +1687,26 @@ export class PiSessionService {
|
|
|
1541
1687
|
if (link === undefined)
|
|
1542
1688
|
return;
|
|
1543
1689
|
const childId = link.childSessionId;
|
|
1690
|
+
const childKey = activeSessionKey(childId, link.eventScope);
|
|
1544
1691
|
if (this.hasActiveWork(session)) {
|
|
1545
|
-
this.subsessionNotifyArmed.set(
|
|
1692
|
+
this.subsessionNotifyArmed.set(childKey, true);
|
|
1546
1693
|
return;
|
|
1547
1694
|
}
|
|
1548
|
-
if (this.subsessionNotifyArmed.get(
|
|
1695
|
+
if (this.subsessionNotifyArmed.get(childKey) !== true)
|
|
1549
1696
|
return;
|
|
1550
|
-
this.subsessionNotifyArmed.set(
|
|
1697
|
+
this.subsessionNotifyArmed.set(childKey, false);
|
|
1551
1698
|
const status = this.activityForSession(session)?.phase === "error" ? "error" : "idle";
|
|
1552
1699
|
const finalText = finalAssistantText(historyMessages(session));
|
|
1553
1700
|
const outputSection = formatSubsessionNotificationOutput(childId, finalText);
|
|
1554
|
-
const workingIds = this.workingSubsessionIds(link.parentSessionId);
|
|
1701
|
+
const workingIds = this.workingSubsessionIds(link.parentSessionId, link.eventScope);
|
|
1555
1702
|
const next = workingIds.length === 0
|
|
1556
1703
|
? "No other tracked subsessions are working."
|
|
1557
1704
|
: `Still working: ${workingIds.join(", ")}. Continue working, or call yield_to_subsessions alone and last at the next join point. Further completion notices arrive automatically; do not poll.`;
|
|
1558
1705
|
const text = `Subsession ${childId} stopped working (${status}).\n${next}\n\n${outputSection}`;
|
|
1559
|
-
void this.notifyParentOfSubsession(link
|
|
1706
|
+
void this.notifyParentOfSubsession(link, text);
|
|
1560
1707
|
}
|
|
1561
|
-
async getOrOpenParentForSubsession(
|
|
1562
|
-
const
|
|
1563
|
-
if (link?.parentSessionId !== parentSessionId)
|
|
1564
|
-
throw new Error(`Parent session ${parentSessionId} is not available for subsession notification`);
|
|
1708
|
+
async getOrOpenParentForSubsession(link) {
|
|
1709
|
+
const { parentSessionId } = link;
|
|
1565
1710
|
const active = this.activeParentForSubsessionLink(link);
|
|
1566
1711
|
if (active !== undefined)
|
|
1567
1712
|
return active.runtime.session;
|
|
@@ -1572,7 +1717,9 @@ export class PiSessionService {
|
|
|
1572
1717
|
throw new Error(`Parent session ${parentSessionId} is not available for subsession notification`);
|
|
1573
1718
|
}
|
|
1574
1719
|
const sessionManager = this.sessionManager.open(parentSessionFile);
|
|
1575
|
-
return (await this.create(sessionManager, sessionManager.getCwd()
|
|
1720
|
+
return (await this.create(sessionManager, sessionManager.getCwd(), {
|
|
1721
|
+
...(link.managementContext === undefined ? {} : { managementContext: link.managementContext }),
|
|
1722
|
+
})).runtime.session;
|
|
1576
1723
|
}
|
|
1577
1724
|
/**
|
|
1578
1725
|
* Deliver a subsession-completion notice to the parent as a system-authored
|
|
@@ -1581,9 +1728,10 @@ export class PiSessionService {
|
|
|
1581
1728
|
* queues behind in-flight work (`deliverAs: "followUp"`), preserving the
|
|
1582
1729
|
* established "queue if busy, send and act if idle" behavior.
|
|
1583
1730
|
*/
|
|
1584
|
-
async notifyParentOfSubsession(
|
|
1731
|
+
async notifyParentOfSubsession(link, text) {
|
|
1732
|
+
const { parentSessionId: parentId, childSessionId: childId } = link;
|
|
1585
1733
|
try {
|
|
1586
|
-
const session = await this.getOrOpenParentForSubsession(
|
|
1734
|
+
const session = await this.getOrOpenParentForSubsession(link);
|
|
1587
1735
|
if (this.treeNavigations.has(session)) {
|
|
1588
1736
|
const pending = this.deferredSubsessionNotifications.get(session) ?? [];
|
|
1589
1737
|
pending.push({ parentId, childId, text });
|
|
@@ -1602,6 +1750,9 @@ export class PiSessionService {
|
|
|
1602
1750
|
}
|
|
1603
1751
|
async status(ref, managementContext) {
|
|
1604
1752
|
const session = await this.sessionForStatusOrDialogClose(ref, managementContext);
|
|
1753
|
+
if (this.eventScopeForSession(session) === NORMAL_SESSION_EVENT_SCOPE) {
|
|
1754
|
+
await this.activityMarker.refresh(session.sessionFile, this.hasActiveWork(session));
|
|
1755
|
+
}
|
|
1605
1756
|
if (this.hasActiveWork(session))
|
|
1606
1757
|
return this.statusFromSession(session);
|
|
1607
1758
|
const branch = await this.readableSessionBranch(session);
|
|
@@ -1636,6 +1787,60 @@ export class PiSessionService {
|
|
|
1636
1787
|
: annotateAssistantThinkingLevel(projectBrowserMessage(streamingMessage), session.thinkingLevel);
|
|
1637
1788
|
return { seq, partial };
|
|
1638
1789
|
}
|
|
1790
|
+
async getSessionDefaults(ref, managementContext) {
|
|
1791
|
+
const session = await this.getOrOpen(ref, managementContext);
|
|
1792
|
+
const settings = await this.freshSessionDefaultsSettings(session);
|
|
1793
|
+
return parseSessionDefaults(settings.getGlobalSettings());
|
|
1794
|
+
}
|
|
1795
|
+
async setSessionDefaults(ref, defaults, managementContext) {
|
|
1796
|
+
const update = parseSessionDefaultsUpdate({ ...defaults });
|
|
1797
|
+
await this.assertWritable(ref);
|
|
1798
|
+
const session = await this.getOrOpen(ref, managementContext);
|
|
1799
|
+
return this.runModelScopeMutation(async () => {
|
|
1800
|
+
const settings = await this.freshSessionDefaultsSettings(session);
|
|
1801
|
+
if (update.provider !== undefined && update.modelId !== undefined) {
|
|
1802
|
+
await session.modelRuntime.refresh({ allowNetwork: false });
|
|
1803
|
+
const target = `${update.provider}/${update.modelId}`;
|
|
1804
|
+
const available = session.modelRuntime.getAvailableSnapshot();
|
|
1805
|
+
if (!available.some((model) => modelScopeId(model) === target))
|
|
1806
|
+
throw new Error(`未找到模型:${target}`);
|
|
1807
|
+
const enabledIds = await resolveEnabledModelIds({
|
|
1808
|
+
settingsManager: settings,
|
|
1809
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
1810
|
+
modelRuntime: session.modelRuntime,
|
|
1811
|
+
scopedModels: [],
|
|
1812
|
+
});
|
|
1813
|
+
if (enabledIds !== null && !enabledIds.includes(target))
|
|
1814
|
+
throw new Error(`模型未启用:${target}`);
|
|
1815
|
+
settings.setDefaultModelAndProvider(update.provider, update.modelId);
|
|
1816
|
+
}
|
|
1817
|
+
else if (update.thinkingLevel !== undefined) {
|
|
1818
|
+
settings.setDefaultThinkingLevel(update.thinkingLevel);
|
|
1819
|
+
}
|
|
1820
|
+
await settings.flush();
|
|
1821
|
+
this.assertDefaultsSettingsHealthy(settings);
|
|
1822
|
+
return parseSessionDefaults(settings.getGlobalSettings());
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
async freshSessionDefaultsSettings(session) {
|
|
1826
|
+
const cwd = session.sessionManager.getCwd();
|
|
1827
|
+
const mode = this.eventScopeForSession(session) === NORMAL_SESSION_EVENT_SCOPE ? "normal" : "management";
|
|
1828
|
+
const settings = await createScopedSettingsManager({
|
|
1829
|
+
cwd,
|
|
1830
|
+
scopeDirectory: await this.resolveSettingsScopeDirectory(cwd, mode),
|
|
1831
|
+
globalAgentDir: this.agentDir,
|
|
1832
|
+
mode,
|
|
1833
|
+
projectTrusted: session.settingsManager.isProjectTrusted?.() ?? true,
|
|
1834
|
+
});
|
|
1835
|
+
await settings.reload();
|
|
1836
|
+
this.assertDefaultsSettingsHealthy(settings);
|
|
1837
|
+
return settings;
|
|
1838
|
+
}
|
|
1839
|
+
assertDefaultsSettingsHealthy(settings) {
|
|
1840
|
+
const errors = settings.drainErrors();
|
|
1841
|
+
if (errors.length > 0)
|
|
1842
|
+
throw new Error(`会话默认值设置失败:${errors.map(({ error }) => error.message).join("; ")}`);
|
|
1843
|
+
}
|
|
1639
1844
|
async availableModels(ref, managementContext) {
|
|
1640
1845
|
const session = await this.getOrOpen(ref, managementContext);
|
|
1641
1846
|
const models = await this.sessionModelCandidates(session);
|
|
@@ -1655,7 +1860,7 @@ export class PiSessionService {
|
|
|
1655
1860
|
const scope = this.modelScopeContext(session);
|
|
1656
1861
|
if (!scope.editable)
|
|
1657
1862
|
throw new Error("模型可用范围由此工作区的 .pi/settings.json 控制");
|
|
1658
|
-
this.assertTreeNavigationInactive(session, "
|
|
1863
|
+
this.assertTreeNavigationInactive(session, "修改已启用模型");
|
|
1659
1864
|
await session.modelRuntime.refresh({ allowNetwork: false });
|
|
1660
1865
|
const currentIds = (await this.modelScopeSnapshot(session, scope)).enabledIds;
|
|
1661
1866
|
const available = session.modelRuntime.getAvailableSnapshot();
|
|
@@ -1666,7 +1871,7 @@ export class PiSessionService {
|
|
|
1666
1871
|
if (!enabled && session.model !== undefined && modelScopeId(session.model) === targetId)
|
|
1667
1872
|
throw new Error("不能停用当前模型");
|
|
1668
1873
|
const nextIds = applyEnabledModelToggle(currentIds, availableIds, targetId, enabled);
|
|
1669
|
-
this.assertTreeNavigationInactive(session, "
|
|
1874
|
+
this.assertTreeNavigationInactive(session, "修改已启用模型");
|
|
1670
1875
|
if (nextIds !== currentIds)
|
|
1671
1876
|
this.applyEnabledModelScope(session, available, nextIds);
|
|
1672
1877
|
return (await this.enabledModelCatalog(session)).map((entry) => catalogEntryToClientModel(entry));
|
|
@@ -1681,7 +1886,7 @@ export class PiSessionService {
|
|
|
1681
1886
|
const scope = this.modelScopeContext(session);
|
|
1682
1887
|
if (!scope.editable)
|
|
1683
1888
|
throw new Error("模型可用范围由此工作区的 .pi/settings.json 控制");
|
|
1684
|
-
this.assertTreeNavigationInactive(session, "
|
|
1889
|
+
this.assertTreeNavigationInactive(session, "修改已启用模型");
|
|
1685
1890
|
await session.modelRuntime.refresh({ allowNetwork: false });
|
|
1686
1891
|
const available = session.modelRuntime.getAvailableSnapshot();
|
|
1687
1892
|
const availableIds = available.map(modelScopeId);
|
|
@@ -1692,7 +1897,7 @@ export class PiSessionService {
|
|
|
1692
1897
|
throw new Error("当前模型不可用");
|
|
1693
1898
|
nextIds = [modelScopeId(current)];
|
|
1694
1899
|
}
|
|
1695
|
-
this.assertTreeNavigationInactive(session, "
|
|
1900
|
+
this.assertTreeNavigationInactive(session, "修改已启用模型");
|
|
1696
1901
|
this.applyEnabledModelScope(session, available, nextIds);
|
|
1697
1902
|
return (await this.enabledModelCatalog(session)).map((entry) => catalogEntryToClientModel(entry));
|
|
1698
1903
|
});
|
|
@@ -1700,14 +1905,14 @@ export class PiSessionService {
|
|
|
1700
1905
|
async setModel(ref, provider, modelId, managementContext) {
|
|
1701
1906
|
await this.assertWritable(ref);
|
|
1702
1907
|
const session = await this.getOrOpen(ref, managementContext);
|
|
1703
|
-
this.assertTreeNavigationInactive(session, "
|
|
1908
|
+
this.assertTreeNavigationInactive(session, "更改模型");
|
|
1704
1909
|
const candidates = await this.sessionModelCandidates(session);
|
|
1705
|
-
this.assertTreeNavigationInactive(session, "
|
|
1910
|
+
this.assertTreeNavigationInactive(session, "更改模型");
|
|
1706
1911
|
const model = candidates.find((candidate) => candidate.provider === provider && candidate.id === modelId)
|
|
1707
1912
|
?? session.modelRuntime.getModel(provider, modelId);
|
|
1708
1913
|
if (model === undefined)
|
|
1709
1914
|
throw new Error(`未找到模型:${provider}/${modelId}`);
|
|
1710
|
-
await this.runSessionEntryMutation(session, "
|
|
1915
|
+
await this.runSessionEntryMutation(session, "更改模型", () => session.setModel(model));
|
|
1711
1916
|
this.publishActivity(session, `模型:${model.id}`, "idle", model.provider);
|
|
1712
1917
|
this.publishStatus(session);
|
|
1713
1918
|
return this.statusFromSession(session);
|
|
@@ -1718,7 +1923,7 @@ export class PiSessionService {
|
|
|
1718
1923
|
const result = await this.runModelScopeMutation(async () => {
|
|
1719
1924
|
await session.modelRuntime.refresh({ allowNetwork: false });
|
|
1720
1925
|
await this.synchronizeSessionModelScope(session, session.modelRuntime.getAvailableSnapshot());
|
|
1721
|
-
return this.runSessionEntryMutation(session, "
|
|
1926
|
+
return this.runSessionEntryMutation(session, "更改模型", () => session.cycleModel(direction));
|
|
1722
1927
|
});
|
|
1723
1928
|
if (result === undefined)
|
|
1724
1929
|
throw new Error(session.scopedModels.length > 0 ? "作用域内只有一个模型" : "只有一个可用模型");
|
|
@@ -1733,14 +1938,14 @@ export class PiSessionService {
|
|
|
1733
1938
|
async setThinkingLevel(ref, level, managementContext) {
|
|
1734
1939
|
await this.assertWritable(ref);
|
|
1735
1940
|
const session = await this.getOrOpen(ref, managementContext);
|
|
1736
|
-
this.assertTreeNavigationInactive(session, "
|
|
1941
|
+
this.assertTreeNavigationInactive(session, "更改思考级别");
|
|
1737
1942
|
// pi owns the valid set; validate against the session's live levels rather
|
|
1738
1943
|
// than a hardcoded union so this stays correct if pi changes the set.
|
|
1739
1944
|
const available = session.getAvailableThinkingLevels();
|
|
1740
1945
|
const match = available.find((candidate) => candidate === level);
|
|
1741
1946
|
if (match === undefined)
|
|
1742
1947
|
throw new Error(`无效的思考级别:${level}`);
|
|
1743
|
-
await this.runSessionEntryMutation(session, "
|
|
1948
|
+
await this.runSessionEntryMutation(session, "更改思考级别", () => {
|
|
1744
1949
|
session.setThinkingLevel(match);
|
|
1745
1950
|
return Promise.resolve();
|
|
1746
1951
|
});
|
|
@@ -1751,7 +1956,7 @@ export class PiSessionService {
|
|
|
1751
1956
|
async cycleThinkingLevel(ref, managementContext) {
|
|
1752
1957
|
await this.assertWritable(ref);
|
|
1753
1958
|
const session = await this.getOrOpen(ref, managementContext);
|
|
1754
|
-
const level = await this.runSessionEntryMutation(session, "
|
|
1959
|
+
const level = await this.runSessionEntryMutation(session, "更改思考级别", () => Promise.resolve(session.cycleThinkingLevel()));
|
|
1755
1960
|
if (level === undefined)
|
|
1756
1961
|
throw new Error("当前模型不支持思考");
|
|
1757
1962
|
this.publishActivity(session, `思考级别:${level}`, "idle");
|
|
@@ -1765,7 +1970,12 @@ export class PiSessionService {
|
|
|
1765
1970
|
commands.push({ name: command.invocationName, ...(command.description === undefined ? {} : { description: command.description }), source: "extension" });
|
|
1766
1971
|
}
|
|
1767
1972
|
for (const template of session.promptTemplates) {
|
|
1768
|
-
commands.push({
|
|
1973
|
+
commands.push({
|
|
1974
|
+
name: template.name,
|
|
1975
|
+
...(template.description === undefined ? {} : { description: template.description }),
|
|
1976
|
+
...(template.argumentHint === undefined ? {} : { argumentHint: template.argumentHint }),
|
|
1977
|
+
source: "prompt",
|
|
1978
|
+
});
|
|
1769
1979
|
}
|
|
1770
1980
|
for (const skill of session.resourceLoader.getSkills().skills) {
|
|
1771
1981
|
commands.push({ name: `skill:${skill.name}`, ...(skill.description === undefined ? {} : { description: skill.description }), source: "skill" });
|
|
@@ -1784,7 +1994,7 @@ export class PiSessionService {
|
|
|
1784
1994
|
const images = (await attachmentsToInlineImages(parsedAttachments)).map((entry) => entry.image);
|
|
1785
1995
|
await this.assertWritable(ref);
|
|
1786
1996
|
const session = await this.getOrOpen(ref, options?.managementContext);
|
|
1787
|
-
this.assertTreeNavigationInactive(session, "
|
|
1997
|
+
this.assertTreeNavigationInactive(session, "发送消息");
|
|
1788
1998
|
this.maybeGenerateSessionName(session, promptText);
|
|
1789
1999
|
const isQueued = session.isStreaming || session.isCompacting;
|
|
1790
2000
|
const behavior = isQueued ? requestedBehavior ?? "followUp" : undefined;
|
|
@@ -1805,24 +2015,78 @@ export class PiSessionService {
|
|
|
1805
2015
|
void this.submitPrompt(session, promptText, behavior, images, echoUserMessage);
|
|
1806
2016
|
}
|
|
1807
2017
|
submitPrompt(session, text, behavior, images = [], echoUserMessage = true) {
|
|
2018
|
+
const promptPromise = this.beginPromptSubmission(session, text, behavior, images, echoUserMessage).catch(() => undefined);
|
|
2019
|
+
void promptPromise;
|
|
2020
|
+
return promptPromise;
|
|
2021
|
+
}
|
|
2022
|
+
beginPromptSubmission(session, text, behavior, images = [], echoUserMessage = true) {
|
|
1808
2023
|
this.auditUserPrompt(session, text);
|
|
1809
2024
|
this.publishActivity(session, behavior === "steer" ? "插队消息已排队" : behavior === "followUp" ? "消息已排队" : "消息已接收", "active");
|
|
1810
2025
|
const eventScope = this.eventScopeForSession(session);
|
|
1811
2026
|
if (behavior === undefined && echoUserMessage)
|
|
1812
2027
|
this.events.publish(session.sessionId, { type: "message.append", message: userMessage(text, images) }, eventScope);
|
|
1813
2028
|
const promptOptions = buildPromptOptions(behavior, images);
|
|
1814
|
-
const promptPromise = this.runSessionEntryMutation(session, "
|
|
2029
|
+
const promptPromise = this.runSessionEntryMutation(session, "发送消息", () => session.prompt(text, promptOptions));
|
|
2030
|
+
void promptPromise.catch((error) => {
|
|
1815
2031
|
const message = error instanceof Error ? error.message : String(error);
|
|
1816
2032
|
this.publishActivity(session, "发生错误", "error", message);
|
|
1817
2033
|
this.events.publish(session.sessionId, { type: "session.error", message }, eventScope);
|
|
1818
2034
|
});
|
|
1819
|
-
void promptPromise;
|
|
1820
2035
|
return promptPromise;
|
|
1821
2036
|
}
|
|
2037
|
+
async submitInitialRunPrompt(session, text) {
|
|
2038
|
+
let failure;
|
|
2039
|
+
let settled = false;
|
|
2040
|
+
let sawInitialUserMessage = false;
|
|
2041
|
+
const unsubscribe = session.subscribe((event) => {
|
|
2042
|
+
if (settled || !isRecord(event))
|
|
2043
|
+
return;
|
|
2044
|
+
if (event["type"] === "agent_settled") {
|
|
2045
|
+
// Freeze the initial outcome before a later user submission can change it.
|
|
2046
|
+
settled = true;
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
if (event["type"] === "auto_retry_end" && event["success"] === false && event["finalError"] === "Retry cancelled") {
|
|
2050
|
+
// Native Pi cancels retry sleep without an aborted assistant message.
|
|
2051
|
+
failure = new DOMException("初始 PI 会话运行已中止", "AbortError");
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
const message = event["message"];
|
|
2055
|
+
if (!isRecord(message))
|
|
2056
|
+
return;
|
|
2057
|
+
if (event["type"] === "message_start" && message["role"] === "user") {
|
|
2058
|
+
// Pi can drain a queued user follow-up inside the same prompt promise.
|
|
2059
|
+
// Its outcome belongs to that later submission, not this launcher.
|
|
2060
|
+
if (sawInitialUserMessage)
|
|
2061
|
+
settled = true;
|
|
2062
|
+
sawInitialUserMessage = true;
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
if (event["type"] !== "message_end" || message["role"] !== "assistant")
|
|
2066
|
+
return;
|
|
2067
|
+
// A successful retry supersedes a transient provider failure. Pi's
|
|
2068
|
+
// prompt promise can fulfill even when the final assistant is an error.
|
|
2069
|
+
const reason = message["stopReason"];
|
|
2070
|
+
failure = reason === "aborted"
|
|
2071
|
+
? new DOMException("初始 PI 会话运行已中止", "AbortError")
|
|
2072
|
+
: reason === "error"
|
|
2073
|
+
? new Error(typeof message["errorMessage"] === "string" ? message["errorMessage"] : "初始 PI 会话运行失败")
|
|
2074
|
+
: undefined;
|
|
2075
|
+
});
|
|
2076
|
+
try {
|
|
2077
|
+
await this.beginPromptSubmission(session, text, undefined);
|
|
2078
|
+
if (failure !== undefined)
|
|
2079
|
+
throw failure;
|
|
2080
|
+
}
|
|
2081
|
+
finally {
|
|
2082
|
+
unsubscribe();
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
1822
2085
|
enqueuePromptDuringCompaction(session, text, kind, images = [], echoUserMessage = true) {
|
|
1823
|
-
const
|
|
2086
|
+
const sessionKey = activeSessionKey(session.sessionId, this.eventScopeForSession(session));
|
|
2087
|
+
const queue = this.compactionPromptQueues.get(sessionKey) ?? [];
|
|
1824
2088
|
queue.push({ kind, text, ...(images.length > 0 ? { images } : {}), ...(echoUserMessage ? {} : { echoUserMessage: false }) });
|
|
1825
|
-
this.compactionPromptQueues.set(
|
|
2089
|
+
this.compactionPromptQueues.set(sessionKey, queue);
|
|
1826
2090
|
this.publishActivity(session, "压缩期间消息已排队", "active");
|
|
1827
2091
|
this.publishStatus(session);
|
|
1828
2092
|
}
|
|
@@ -1844,7 +2108,7 @@ export class PiSessionService {
|
|
|
1844
2108
|
await this.assertWritable(ref);
|
|
1845
2109
|
const active = await this.getActive(ref, managementContext);
|
|
1846
2110
|
const { session } = active.runtime;
|
|
1847
|
-
this.assertTreeNavigationInactive(session, "
|
|
2111
|
+
this.assertTreeNavigationInactive(session, "运行 Shell 命令");
|
|
1848
2112
|
const isExcluded = text.startsWith("!!");
|
|
1849
2113
|
const command = (isExcluded ? text.slice(2) : text.slice(1)).trim();
|
|
1850
2114
|
if (!command)
|
|
@@ -1854,7 +2118,7 @@ export class PiSessionService {
|
|
|
1854
2118
|
const eventScope = active.eventScope;
|
|
1855
2119
|
this.publishActivity(session, "正在运行命令", "active", command);
|
|
1856
2120
|
this.events.publish(session.sessionId, { type: "shell.start", command, excludeFromContext: isExcluded }, eventScope);
|
|
1857
|
-
void this.runSessionEntryMutation(session, "
|
|
2121
|
+
void this.runSessionEntryMutation(session, "运行 Shell 命令", () => session.executeBash(command, (chunk) => {
|
|
1858
2122
|
this.events.publish(session.sessionId, { type: "shell.chunk", chunk }, eventScope);
|
|
1859
2123
|
this.publishActivity(session, "正在运行命令", "active", command);
|
|
1860
2124
|
this.publishStatus(session);
|
|
@@ -1889,42 +2153,74 @@ export class PiSessionService {
|
|
|
1889
2153
|
}
|
|
1890
2154
|
async navigateTree(ref, request, managementContext) {
|
|
1891
2155
|
if (request.targetId.trim() === "")
|
|
1892
|
-
throw new Error("
|
|
2156
|
+
throw new Error("会话树目标条目不能为空");
|
|
1893
2157
|
if (this.isTreeExclusiveSessionIdentityActive(sessionIdFromLookup(ref))) {
|
|
1894
|
-
throw new Error("
|
|
2158
|
+
throw new Error("切换会话树前请先停止当前会话活动");
|
|
1895
2159
|
}
|
|
1896
2160
|
await this.assertWritable(ref);
|
|
1897
2161
|
const options = sessionTreeNavigationOptions(request);
|
|
1898
2162
|
const session = await this.getOrOpen(ref, managementContext);
|
|
1899
2163
|
if (typeof session.navigateTree !== "function")
|
|
1900
|
-
throw new Error("
|
|
2164
|
+
throw new Error("当前 Pi 运行时不支持会话树导航");
|
|
1901
2165
|
if (this.hasActiveWork(session))
|
|
1902
|
-
throw new Error("
|
|
2166
|
+
throw new Error("切换会话树前请先停止当前会话活动");
|
|
1903
2167
|
// Acquire synchronously after the active-work check. No leaf-producing work
|
|
1904
2168
|
// may enter this runtime until Pi's potentially asynchronous summary settles.
|
|
1905
2169
|
this.treeNavigations.add(session);
|
|
1906
2170
|
try {
|
|
1907
|
-
|
|
1908
|
-
|
|
2171
|
+
const oldLeafId = session.sessionManager.getLeafId();
|
|
2172
|
+
if (oldLeafId !== request.expectedLeafId) {
|
|
2173
|
+
throw new Error("打开 /tree 后会话已发生变化。请重新打开 /tree 后重试。");
|
|
1909
2174
|
}
|
|
2175
|
+
const activeEditableTargetParentId = activeEditableTreeTargetParentId(session.sessionManager, request.targetId, oldLeafId);
|
|
1910
2176
|
this.publishActivity(session, options.summarize ? "正在汇总分支" : "正在切换会话树", "active");
|
|
1911
2177
|
this.publishStatus(session);
|
|
1912
|
-
|
|
2178
|
+
let result = await session.navigateTree(request.targetId, options);
|
|
2179
|
+
if (activeEditableTargetParentId !== undefined && !result.cancelled
|
|
2180
|
+
&& result.editorText === undefined && result.summaryEntry === undefined
|
|
2181
|
+
&& session.sessionManager.getLeafId() === oldLeafId) {
|
|
2182
|
+
setSessionTreeLeaf(session.sessionManager, activeEditableTargetParentId);
|
|
2183
|
+
try {
|
|
2184
|
+
result = await session.navigateTree(request.targetId, options);
|
|
2185
|
+
}
|
|
2186
|
+
catch (error) {
|
|
2187
|
+
session.sessionManager.branch(request.targetId);
|
|
2188
|
+
throw error;
|
|
2189
|
+
}
|
|
2190
|
+
if (result.cancelled)
|
|
2191
|
+
session.sessionManager.branch(request.targetId);
|
|
2192
|
+
}
|
|
1913
2193
|
if (result.cancelled) {
|
|
1914
2194
|
if (this.isCurrentActiveSession(session)) {
|
|
1915
2195
|
this.publishActivity(session, result.aborted === true ? "分支汇总已终止" : "会话树切换已取消", "idle");
|
|
1916
2196
|
}
|
|
1917
2197
|
return { cancelled: true, ...(result.aborted === undefined ? {} : { aborted: result.aborted }) };
|
|
1918
2198
|
}
|
|
2199
|
+
if (session.sessionManager.getLeafId() !== oldLeafId) {
|
|
2200
|
+
// Questions belong to the branch we left. Close them without appending a
|
|
2201
|
+
// cancellation message or waking the rewound session.
|
|
2202
|
+
const outcome = this.pendingAskStore.cancelOpen(this.dialogSessionKey(session));
|
|
2203
|
+
if (outcome !== undefined)
|
|
2204
|
+
this.publishAskClosed(session.sessionId, outcome, this.eventScopeForSession(session));
|
|
2205
|
+
}
|
|
2206
|
+
if (result.summaryEntry !== undefined) {
|
|
2207
|
+
this.unpersistedTreeBranchLeaves.delete(session);
|
|
2208
|
+
}
|
|
2209
|
+
else {
|
|
2210
|
+
const selectedLeafId = session.sessionManager.getLeafId();
|
|
2211
|
+
if (selectedLeafId !== oldLeafId)
|
|
2212
|
+
this.unpersistedTreeBranchLeaves.set(session, selectedLeafId);
|
|
2213
|
+
}
|
|
1919
2214
|
if (this.isCurrentActiveSession(session))
|
|
1920
2215
|
this.publishActivity(session, "已切换会话树", "idle");
|
|
1921
2216
|
return { cancelled: false, ...(result.editorText === undefined ? {} : { editorText: result.editorText }) };
|
|
1922
2217
|
}
|
|
1923
2218
|
catch (error) {
|
|
1924
2219
|
const message = error instanceof Error ? error.message : String(error);
|
|
1925
|
-
this.
|
|
1926
|
-
|
|
1927
|
-
|
|
2220
|
+
if (this.isCurrentActiveSession(session)) {
|
|
2221
|
+
this.publishActivity(session, "会话树切换失败", "error", message);
|
|
2222
|
+
this.events.publish(session.sessionId, { type: "session.error", message }, this.eventScopeForSession(session));
|
|
2223
|
+
}
|
|
1928
2224
|
throw error;
|
|
1929
2225
|
}
|
|
1930
2226
|
finally {
|
|
@@ -1941,19 +2237,22 @@ export class PiSessionService {
|
|
|
1941
2237
|
}
|
|
1942
2238
|
async forkFromTree(ref, request, managementContext) {
|
|
1943
2239
|
if (request.entryId.trim() === "")
|
|
1944
|
-
throw new Error("
|
|
2240
|
+
throw new Error("会话树条目不能为空");
|
|
1945
2241
|
const requestedSessionId = sessionIdFromLookup(ref);
|
|
1946
2242
|
if (this.isTreeExclusiveSessionIdentityActive(requestedSessionId)) {
|
|
1947
|
-
throw new Error("
|
|
2243
|
+
throw new Error("分叉会话树前请先停止当前会话活动");
|
|
1948
2244
|
}
|
|
1949
2245
|
await this.assertWritable(ref);
|
|
1950
2246
|
const session = await this.getOrOpen(ref, managementContext);
|
|
1951
2247
|
if (this.hasActiveWork(session))
|
|
1952
|
-
throw new Error("
|
|
2248
|
+
throw new Error("分叉会话树前请先停止当前会话活动");
|
|
2249
|
+
if (session.sessionManager.getLeafId() !== request.expectedLeafId) {
|
|
2250
|
+
throw new Error("打开 /tree 后会话已发生变化。请重新打开 /tree 后重试。");
|
|
2251
|
+
}
|
|
1953
2252
|
const sourceSessionId = session.sessionId;
|
|
1954
2253
|
const sourceEventScope = this.eventScopeForSession(session);
|
|
1955
2254
|
const sourceCwd = session.sessionManager.getCwd();
|
|
1956
|
-
this.publishActivity(session, "
|
|
2255
|
+
this.publishActivity(session, "正在从条目分叉会话", "active");
|
|
1957
2256
|
this.publishStatus(session);
|
|
1958
2257
|
try {
|
|
1959
2258
|
const result = await this.commandService.forkEntry(session.sessionId, request.entryId, sourceEventScope, {
|
|
@@ -1962,10 +2261,10 @@ export class PiSessionService {
|
|
|
1962
2261
|
if (result.type === "unsupported")
|
|
1963
2262
|
throw new Error(result.message);
|
|
1964
2263
|
if (result.type !== "done")
|
|
1965
|
-
throw new Error("
|
|
2264
|
+
throw new Error("会话分叉不可用");
|
|
1966
2265
|
if (result.session === undefined) {
|
|
1967
2266
|
if (this.isCurrentActiveSession(session)) {
|
|
1968
|
-
this.publishActivity(session, "
|
|
2267
|
+
this.publishActivity(session, "分叉已取消", "idle");
|
|
1969
2268
|
this.publishStatus(session);
|
|
1970
2269
|
}
|
|
1971
2270
|
return { cancelled: true };
|
|
@@ -1986,7 +2285,7 @@ export class PiSessionService {
|
|
|
1986
2285
|
catch (error) {
|
|
1987
2286
|
const message = error instanceof Error ? error.message : String(error);
|
|
1988
2287
|
if (this.isCurrentActiveSession(session)) {
|
|
1989
|
-
this.publishActivity(session, "
|
|
2288
|
+
this.publishActivity(session, "分叉失败", "error", message);
|
|
1990
2289
|
this.events.publish(session.sessionId, { type: "session.error", message }, sourceEventScope);
|
|
1991
2290
|
this.publishStatus(session);
|
|
1992
2291
|
}
|
|
@@ -2145,8 +2444,6 @@ export class PiSessionService {
|
|
|
2145
2444
|
if (this.archiveStore.deleteArchived === undefined)
|
|
2146
2445
|
throw new Error("归档存储不支持删除");
|
|
2147
2446
|
await this.closeActiveInScope(record.sessionId, eventScopeFromManagementContext(managementContext));
|
|
2148
|
-
if (record.archivePath === undefined)
|
|
2149
|
-
await this.ensureArchivedRecordMoved(record);
|
|
2150
2447
|
await this.archiveStore.deleteArchived(record.sessionId);
|
|
2151
2448
|
await this.forgetUnreadSessions([record], eventScopeFromManagementContext(managementContext));
|
|
2152
2449
|
}
|
|
@@ -2180,12 +2477,7 @@ export class PiSessionService {
|
|
|
2180
2477
|
failures.push({ sessionId: item.record.sessionId, error: errorMessage(error) });
|
|
2181
2478
|
}
|
|
2182
2479
|
}
|
|
2183
|
-
const
|
|
2184
|
-
failures.push(...moveFailures);
|
|
2185
|
-
const moveFailureIds = new Set(moveFailures.map((failure) => failure.sessionId));
|
|
2186
|
-
const deleteIds = readyRecords
|
|
2187
|
-
.map((record) => record.sessionId)
|
|
2188
|
-
.filter((sessionId) => !moveFailureIds.has(sessionId));
|
|
2480
|
+
const deleteIds = readyRecords.map((record) => record.sessionId);
|
|
2189
2481
|
let deletedSessionIds = [];
|
|
2190
2482
|
try {
|
|
2191
2483
|
deletedSessionIds = await this.archiveStoreDeleteArchivedMany(deleteIds);
|
|
@@ -2225,6 +2517,7 @@ export class PiSessionService {
|
|
|
2225
2517
|
...(managementContext === undefined ? {} : { managementContext }),
|
|
2226
2518
|
...(candidateGeneration === undefined ? {} : { notificationGeneration: candidateGeneration }),
|
|
2227
2519
|
});
|
|
2520
|
+
await this.reloadModelScope(reopened.runtime.session);
|
|
2228
2521
|
if (candidateGeneration !== undefined) {
|
|
2229
2522
|
this.publishNotificationMutations(this.notificationsForSession(session).commitReplacement(candidateGeneration));
|
|
2230
2523
|
}
|
|
@@ -2240,22 +2533,22 @@ export class PiSessionService {
|
|
|
2240
2533
|
this.publishStatus(reopenedSession);
|
|
2241
2534
|
}
|
|
2242
2535
|
async detachParent(ref, managementContext) {
|
|
2536
|
+
await this.assertWritable(ref);
|
|
2243
2537
|
const session = await this.getOrOpen(ref, managementContext);
|
|
2244
2538
|
const sessionFile = session.sessionFile;
|
|
2245
2539
|
if (sessionFile === undefined || sessionFile === "")
|
|
2246
2540
|
throw new Error("会话尚未持久化");
|
|
2247
2541
|
clearSessionFileParent(sessionFile);
|
|
2248
|
-
this.sessionManager.invalidateSessionFile
|
|
2542
|
+
this.sessionManager.invalidateSessionFile(sessionFile);
|
|
2249
2543
|
clearParentSessionHeader(session.sessionManager);
|
|
2250
|
-
this.unregisterSubsession(session.sessionId);
|
|
2544
|
+
this.unregisterSubsession(session.sessionId, this.eventScopeForSession(session));
|
|
2251
2545
|
await this.forgetUnreadSessions([{ sessionId: session.sessionId, cwd: session.sessionManager.getCwd() }], this.eventScopeForSession(session));
|
|
2252
2546
|
}
|
|
2253
2547
|
async abort(ref, managementContext) {
|
|
2254
2548
|
const active = this.activeForLookup(ref, managementContext);
|
|
2255
2549
|
if (active === undefined)
|
|
2256
2550
|
return;
|
|
2257
|
-
|
|
2258
|
-
this.clearCompactionPromptQueue(sessionId);
|
|
2551
|
+
this.clearCompactionPromptQueue(active.runtime.session);
|
|
2259
2552
|
clearSessionQueue(active.runtime.session);
|
|
2260
2553
|
this.abortRunScopedExtensionDialogs(active.runtime.session);
|
|
2261
2554
|
try {
|
|
@@ -2274,25 +2567,15 @@ export class PiSessionService {
|
|
|
2274
2567
|
async clearQueue(ref, managementContext) {
|
|
2275
2568
|
await this.assertWritable(ref);
|
|
2276
2569
|
const session = await this.getOrOpen(ref, managementContext);
|
|
2277
|
-
this.clearCompactionPromptQueue(session
|
|
2570
|
+
this.clearCompactionPromptQueue(session);
|
|
2278
2571
|
clearSessionQueue(session);
|
|
2279
2572
|
this.publishStatus(session);
|
|
2280
2573
|
return this.statusFromSession(session);
|
|
2281
2574
|
}
|
|
2282
2575
|
async stop(ref, managementContext) {
|
|
2283
|
-
|
|
2284
|
-
if (
|
|
2285
|
-
|
|
2286
|
-
if (startup !== undefined) {
|
|
2287
|
-
this.endSessionExtensionDialogs(startup);
|
|
2288
|
-
const pendingOpens = this.pendingSessionOpenPromises(startup.sessionId);
|
|
2289
|
-
if (pendingOpens.length > 0)
|
|
2290
|
-
await Promise.allSettled(pendingOpens);
|
|
2291
|
-
active = this.activeForLookup(ref, managementContext);
|
|
2292
|
-
}
|
|
2293
|
-
}
|
|
2294
|
-
if (active !== undefined) {
|
|
2295
|
-
await this.closeActiveSession(active);
|
|
2576
|
+
const session = this.activeForLookup(ref, managementContext)?.runtime.session ?? this.startupSessionForLookup(ref, managementContext);
|
|
2577
|
+
if (session !== undefined) {
|
|
2578
|
+
await this.closeActive(session.sessionId, CLEAR_RUNTIME_NOTIFICATIONS, eventScopeFromManagementContext(managementContext));
|
|
2296
2579
|
return;
|
|
2297
2580
|
}
|
|
2298
2581
|
if (isPiSessionRef(ref)) {
|
|
@@ -2310,10 +2593,11 @@ export class PiSessionService {
|
|
|
2310
2593
|
async archiveStoreArchiveMany(inputs) {
|
|
2311
2594
|
if (inputs.length === 0)
|
|
2312
2595
|
return [];
|
|
2596
|
+
const oldestFirst = [...inputs].sort((a, b) => Date.parse(a.modified) - Date.parse(b.modified));
|
|
2313
2597
|
if (this.archiveStore.archiveMany !== undefined)
|
|
2314
|
-
return this.archiveStore.archiveMany(
|
|
2598
|
+
return this.archiveStore.archiveMany(oldestFirst);
|
|
2315
2599
|
const records = [];
|
|
2316
|
-
for (const input of
|
|
2600
|
+
for (const input of oldestFirst)
|
|
2317
2601
|
records.push(await this.archiveStore.archive(input));
|
|
2318
2602
|
return records;
|
|
2319
2603
|
}
|
|
@@ -2328,38 +2612,16 @@ export class PiSessionService {
|
|
|
2328
2612
|
await this.archiveStore.deleteArchived(sessionId);
|
|
2329
2613
|
return [...sessionIds];
|
|
2330
2614
|
}
|
|
2331
|
-
async moveLegacyArchivedRecordsForDelete(records) {
|
|
2332
|
-
const legacyRecords = records.filter((record) => record.archivePath === undefined);
|
|
2333
|
-
if (legacyRecords.length === 0)
|
|
2334
|
-
return [];
|
|
2335
|
-
let sessionsByCwd;
|
|
2336
|
-
try {
|
|
2337
|
-
sessionsByCwd = await this.listSessionsByCwd(legacyRecords.map((record) => record.cwd));
|
|
2338
|
-
}
|
|
2339
|
-
catch (error) {
|
|
2340
|
-
return legacyRecords.map((record) => ({ sessionId: record.sessionId, error: errorMessage(error) }));
|
|
2341
|
-
}
|
|
2342
|
-
const moveInputs = legacyRecords
|
|
2343
|
-
.map((record) => findSessionByIdOrPrefix(sessionsByCwd.get(record.cwd) ?? [], record.sessionId))
|
|
2344
|
-
.filter(isDefined)
|
|
2345
|
-
.map(archiveInputFromListEntry);
|
|
2346
|
-
if (moveInputs.length === 0)
|
|
2347
|
-
return [];
|
|
2348
|
-
try {
|
|
2349
|
-
await this.archiveStoreArchiveMany(moveInputs);
|
|
2350
|
-
return [];
|
|
2351
|
-
}
|
|
2352
|
-
catch (error) {
|
|
2353
|
-
const failedIds = new Set(moveInputs.map((input) => input.sessionId));
|
|
2354
|
-
return legacyRecords
|
|
2355
|
-
.filter((record) => failedIds.has(record.sessionId))
|
|
2356
|
-
.map((record) => ({ sessionId: record.sessionId, error: errorMessage(error) }));
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
2615
|
async cleanupPlan(request) {
|
|
2360
|
-
const [sessions, archivedRecords] = await Promise.all([
|
|
2616
|
+
const [sessions, archivedRecords, scopedSessions] = await Promise.all([
|
|
2617
|
+
this.sessionManager.listAll(),
|
|
2618
|
+
this.archiveStore.list(),
|
|
2619
|
+
request.thresholds.archiveIdleDays === undefined
|
|
2620
|
+
? Promise.resolve(new Map())
|
|
2621
|
+
: this.listSessionsByCwd(request.projectCwds ?? []),
|
|
2622
|
+
]);
|
|
2361
2623
|
return planSessionCleanup({
|
|
2362
|
-
sessions,
|
|
2624
|
+
sessions: [...sessions, ...[...scopedSessions.values()].flat()],
|
|
2363
2625
|
archivedRecords,
|
|
2364
2626
|
activeSessions: this.cleanupActiveSessionStatuses(),
|
|
2365
2627
|
thresholds: request.thresholds,
|
|
@@ -2395,24 +2657,6 @@ export class PiSessionService {
|
|
|
2395
2657
|
return record;
|
|
2396
2658
|
}
|
|
2397
2659
|
}
|
|
2398
|
-
async ensureArchivedRecordMoved(record) {
|
|
2399
|
-
const session = (await this.sessionManager.list(record.cwd)).find((candidate) => candidate.id === record.sessionId);
|
|
2400
|
-
if (session === undefined)
|
|
2401
|
-
return record;
|
|
2402
|
-
const [moved] = await this.archiveStoreArchiveMany([archiveInputFromListEntry(session)]);
|
|
2403
|
-
return moved ?? record;
|
|
2404
|
-
}
|
|
2405
|
-
async ensureArchivedRecordsMoved(records) {
|
|
2406
|
-
const legacyRecords = records.filter((record) => record.archivePath === undefined);
|
|
2407
|
-
if (legacyRecords.length === 0)
|
|
2408
|
-
return;
|
|
2409
|
-
const sessionsByCwd = await this.listSessionsByCwd(legacyRecords.map((record) => record.cwd));
|
|
2410
|
-
const moveInputs = legacyRecords
|
|
2411
|
-
.map((record) => sessionsByCwd.get(record.cwd)?.find((candidate) => candidate.id === record.sessionId))
|
|
2412
|
-
.filter(isDefined)
|
|
2413
|
-
.map(archiveInputFromListEntry);
|
|
2414
|
-
await this.archiveStoreArchiveMany(moveInputs);
|
|
2415
|
-
}
|
|
2416
2660
|
async archiveInputForSession(session) {
|
|
2417
2661
|
const cwd = session.sessionManager.getCwd();
|
|
2418
2662
|
const sessionFile = session.sessionFile;
|
|
@@ -2478,7 +2722,7 @@ export class PiSessionService {
|
|
|
2478
2722
|
if (startup.session.sessionId === sessionId && startup.eventScope === eventScope)
|
|
2479
2723
|
this.endSessionExtensionDialogs(startup.session);
|
|
2480
2724
|
}
|
|
2481
|
-
const pendingOpens = this.pendingSessionOpenPromises(sessionId);
|
|
2725
|
+
const pendingOpens = this.pendingSessionOpenPromises(sessionId, eventScope);
|
|
2482
2726
|
if (pendingOpens.length > 0)
|
|
2483
2727
|
await Promise.allSettled(pendingOpens);
|
|
2484
2728
|
const activeSessions = this.activeSessionsForId(sessionId).filter((active) => active.eventScope === eventScope);
|
|
@@ -2489,13 +2733,7 @@ export class PiSessionService {
|
|
|
2489
2733
|
await Promise.all(activeSessions.map((active) => this.closeActiveSession(active, notificationPolicy)));
|
|
2490
2734
|
}
|
|
2491
2735
|
async closeActiveInScope(sessionId, eventScope, notificationPolicy = CLEAR_RUNTIME_NOTIFICATIONS) {
|
|
2492
|
-
|
|
2493
|
-
if (active !== undefined) {
|
|
2494
|
-
await this.closeActiveSession(active, notificationPolicy);
|
|
2495
|
-
}
|
|
2496
|
-
else if (notificationPolicy.kind === "clear") {
|
|
2497
|
-
this.publishNotificationMutations(this.notificationsForScope(eventScope).clearSession(sessionId, notificationPolicy.reason));
|
|
2498
|
-
}
|
|
2736
|
+
await this.closeActive(sessionId, notificationPolicy, eventScope);
|
|
2499
2737
|
}
|
|
2500
2738
|
async closeActiveSession(active, notificationPolicy = CLEAR_RUNTIME_NOTIFICATIONS) {
|
|
2501
2739
|
const sessionId = active.runtime.session.sessionId;
|
|
@@ -2508,18 +2746,18 @@ export class PiSessionService {
|
|
|
2508
2746
|
this.publishNotificationMutations(mutations);
|
|
2509
2747
|
}
|
|
2510
2748
|
this.forgetUnreadActivity(active.runtime.session);
|
|
2511
|
-
this.pendingAskStore.forgetSession(
|
|
2749
|
+
this.pendingAskStore.forgetSession(this.dialogSessionKey(active.runtime.session));
|
|
2512
2750
|
this.endSessionExtensionDialogs(active.runtime.session);
|
|
2513
2751
|
this.active.delete(activeSessionKey(sessionId, active.eventScope));
|
|
2514
2752
|
this.activities.delete(activeSessionKey(sessionId, active.eventScope));
|
|
2515
2753
|
this.workspaceActivity?.removeSession(sessionId, active.runtime.session.sessionManager.getCwd(), active.eventScope);
|
|
2516
2754
|
this.clearAuthLossWarningsForSession(sessionId);
|
|
2517
|
-
this.clearCompactionPromptQueue(
|
|
2755
|
+
this.clearCompactionPromptQueue(active.runtime.session);
|
|
2518
2756
|
// Disarm subsession notification before teardown so the abort below cannot
|
|
2519
2757
|
// emit a "stopped working" event that notifies the parent (e.g. on archive).
|
|
2520
2758
|
// The parent/children link is kept so the parent can still see the child.
|
|
2521
2759
|
if (this.subsessionLinkForActiveChild(active.runtime.session) !== undefined)
|
|
2522
|
-
this.subsessionNotifyArmed.delete(sessionId);
|
|
2760
|
+
this.subsessionNotifyArmed.delete(activeSessionKey(sessionId, active.eventScope));
|
|
2523
2761
|
clearSessionQueue(active.runtime.session);
|
|
2524
2762
|
active.unsubscribe();
|
|
2525
2763
|
active.runtime.setRebindSession(undefined);
|
|
@@ -2527,6 +2765,8 @@ export class PiSessionService {
|
|
|
2527
2765
|
await this.abortSessionOperations(active.runtime.session);
|
|
2528
2766
|
}
|
|
2529
2767
|
finally {
|
|
2768
|
+
if (active.eventScope === NORMAL_SESSION_EVENT_SCOPE)
|
|
2769
|
+
await this.activityMarker.release(active.runtime.session.sessionFile);
|
|
2530
2770
|
await active.runtime.dispose();
|
|
2531
2771
|
}
|
|
2532
2772
|
}
|
|
@@ -2585,7 +2825,9 @@ export class PiSessionService {
|
|
|
2585
2825
|
const leafId = session.sessionManager.getLeafId();
|
|
2586
2826
|
const latestEntry = session.sessionManager.getEntries?.().at(-1);
|
|
2587
2827
|
// Unsummarized tree navigation changes only the SDK's in-memory leaf.
|
|
2588
|
-
if (
|
|
2828
|
+
if (!this.unpersistedTreeBranchLeaves.has(session)
|
|
2829
|
+
&& isRecord(latestEntry) && typeof latestEntry["id"] === "string"
|
|
2830
|
+
&& latestEntry["id"] !== leafId)
|
|
2589
2831
|
return session.sessionManager.getBranch();
|
|
2590
2832
|
const sessionFile = session.sessionFile ?? session.sessionManager.getSessionFile();
|
|
2591
2833
|
const resolvedPath = sessionFile === undefined ? await this.idleSessionFilePath(session) : undefined;
|
|
@@ -2597,19 +2839,37 @@ export class PiSessionService {
|
|
|
2597
2839
|
const snapshot = await this.sessionManager.readBranch(path);
|
|
2598
2840
|
if (snapshot === undefined)
|
|
2599
2841
|
return session.sessionManager.getBranch();
|
|
2600
|
-
|
|
2842
|
+
if (this.hasActiveWork(session) || session.sessionManager.getLeafId() !== leafId)
|
|
2843
|
+
return session.sessionManager.getBranch();
|
|
2844
|
+
const selectedLeafId = this.unpersistedTreeBranchLeaves.get(session);
|
|
2845
|
+
if (selectedLeafId !== undefined) {
|
|
2846
|
+
const runtimeLeafId = session.sessionManager.getLeafId();
|
|
2847
|
+
const selectedBranchIsAnchored = runtimeLeafId !== null
|
|
2848
|
+
&& runtimeLeafId !== selectedLeafId && transcriptBranchIncludesEntry(snapshot, runtimeLeafId);
|
|
2849
|
+
if (!selectedBranchIsAnchored)
|
|
2850
|
+
return session.sessionManager.getBranch();
|
|
2851
|
+
this.unpersistedTreeBranchLeaves.delete(session);
|
|
2852
|
+
}
|
|
2853
|
+
return snapshot;
|
|
2601
2854
|
}
|
|
2602
2855
|
async idleSessionFilePath(session) {
|
|
2603
2856
|
const cached = this.idleSessionFileResolutions.get(session);
|
|
2604
2857
|
const at = this.now().getTime();
|
|
2605
2858
|
if (cached !== undefined && at - cached.at < IDLE_SESSION_FILE_RESOLUTION_THROTTLE_MS)
|
|
2606
2859
|
return cached.path;
|
|
2607
|
-
const match = await this.sessionManager.resolveSessionFile
|
|
2860
|
+
const match = await this.sessionManager.resolveSessionFile(session.sessionManager.getCwd(), session.sessionId);
|
|
2608
2861
|
this.idleSessionFileResolutions.set(session, { at, path: match?.path });
|
|
2609
2862
|
return match?.path;
|
|
2610
2863
|
}
|
|
2611
2864
|
async getActive(ref, managementContext) {
|
|
2612
2865
|
const active = this.activeForLookup(ref, managementContext);
|
|
2866
|
+
const resolvedRef = active === undefined ? isPiSessionRef(ref) ? ref : undefined
|
|
2867
|
+
: { id: active.runtime.session.sessionId, cwd: active.runtime.cwd };
|
|
2868
|
+
const pending = resolvedRef === undefined ? undefined : this.pendingSessionOpens.get(JSON.stringify([
|
|
2869
|
+
canonicalizeStoredCwd(resolvedRef.cwd), resolvedRef.id, managementContextKey(managementContext),
|
|
2870
|
+
]));
|
|
2871
|
+
if (pending !== undefined)
|
|
2872
|
+
return pending.promise;
|
|
2613
2873
|
if (active !== undefined)
|
|
2614
2874
|
return active;
|
|
2615
2875
|
const archived = await this.getArchived(ref);
|
|
@@ -2618,10 +2878,8 @@ export class PiSessionService {
|
|
|
2618
2878
|
return this.openPending(archived.sessionId, archived.cwd, managementContext, () => this.sessionManager.open(archivePath), { notifications: "disabled" });
|
|
2619
2879
|
}
|
|
2620
2880
|
const match = isPiSessionRef(ref)
|
|
2621
|
-
? this.sessionManager.resolveSessionFile
|
|
2622
|
-
|
|
2623
|
-
: await this.sessionManager.resolveSessionFile(ref.cwd, ref.id)
|
|
2624
|
-
: findSessionByIdOrPrefix(await this.sessionManager.listAll?.() ?? [], ref);
|
|
2881
|
+
? await this.sessionManager.resolveSessionFile(ref.cwd, ref.id)
|
|
2882
|
+
: findSessionByIdOrPrefix(await this.sessionManager.listAll(), ref);
|
|
2625
2883
|
if (!match)
|
|
2626
2884
|
throw new Error("未找到会话");
|
|
2627
2885
|
return this.openPending(match.id, match.cwd, managementContext, () => this.sessionManager.open(match.path));
|
|
@@ -2631,8 +2889,12 @@ export class PiSessionService {
|
|
|
2631
2889
|
const existing = this.pendingSessionOpens.get(key);
|
|
2632
2890
|
if (existing !== undefined)
|
|
2633
2891
|
return existing.promise;
|
|
2892
|
+
const active = this.activeForLookup({ id: sessionId, cwd }, managementContext);
|
|
2893
|
+
if (active !== undefined)
|
|
2894
|
+
return Promise.resolve(active);
|
|
2634
2895
|
const pending = {
|
|
2635
2896
|
sessionId,
|
|
2897
|
+
eventScope: eventScopeFromManagementContext(managementContext),
|
|
2636
2898
|
promise: this.create(openSessionManager(), cwd, {
|
|
2637
2899
|
...options,
|
|
2638
2900
|
...(managementContext === undefined ? {} : { managementContext }),
|
|
@@ -2645,9 +2907,9 @@ export class PiSessionService {
|
|
|
2645
2907
|
this.pendingSessionOpens.set(key, pending);
|
|
2646
2908
|
return pending.promise;
|
|
2647
2909
|
}
|
|
2648
|
-
pendingSessionOpenPromises(sessionId) {
|
|
2910
|
+
pendingSessionOpenPromises(sessionId, eventScope) {
|
|
2649
2911
|
return [...this.pendingSessionOpens.values()]
|
|
2650
|
-
.filter((pending) => sessionId === undefined || pending.sessionId === sessionId)
|
|
2912
|
+
.filter((pending) => (sessionId === undefined || pending.sessionId === sessionId) && (eventScope === undefined || pending.eventScope === eventScope))
|
|
2651
2913
|
.map((pending) => pending.promise);
|
|
2652
2914
|
}
|
|
2653
2915
|
async getArchived(ref) {
|
|
@@ -2696,6 +2958,21 @@ export class PiSessionService {
|
|
|
2696
2958
|
throw new Error(`Session not active: ${sessionId}`);
|
|
2697
2959
|
return this.submitPrompt(active.runtime.session, text, undefined, [], false);
|
|
2698
2960
|
}
|
|
2961
|
+
/**
|
|
2962
|
+
* Attach to one already-hosted normal-mode session. Management scopes are
|
|
2963
|
+
* not searched: host session events are a normal-mode capability.
|
|
2964
|
+
*/
|
|
2965
|
+
connectSessionEvents(ref, lifetime) {
|
|
2966
|
+
const active = this.activeForSessionIdAndScope(ref.id, NORMAL_SESSION_EVENT_SCOPE);
|
|
2967
|
+
if (active?.runtime.session.sessionId !== ref.id
|
|
2968
|
+
|| !cwdPathsEqual(active.runtime.session.sessionManager.getCwd(), ref.cwd)) {
|
|
2969
|
+
throw new Error("Selected session is not hosted in this workspace on this machine");
|
|
2970
|
+
}
|
|
2971
|
+
if (this.startupSessions.has(activeSessionKey(ref.id, NORMAL_SESSION_EVENT_SCOPE))) {
|
|
2972
|
+
throw new Error("Selected session is still initializing its extensions");
|
|
2973
|
+
}
|
|
2974
|
+
return this.sessionEvents.connect(active.runtime.session, lifetime);
|
|
2975
|
+
}
|
|
2699
2976
|
activeForSessionId(sessionId) {
|
|
2700
2977
|
return this.activeSessionsForId(sessionId)[0];
|
|
2701
2978
|
}
|
|
@@ -2707,17 +2984,25 @@ export class PiSessionService {
|
|
|
2707
2984
|
activeSessionsForId(sessionId) {
|
|
2708
2985
|
return [...new Set(this.active.values())].filter((active) => active.runtime.session.sessionId === sessionId);
|
|
2709
2986
|
}
|
|
2710
|
-
|
|
2987
|
+
create(sessionManager, cwd, options = {}) {
|
|
2711
2988
|
const startup = this.startupProgress(sessionManager, options.startupIntent ?? "open", options.startupToken, eventScopeFromManagementContext(options.managementContext));
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2989
|
+
const key = JSON.stringify([canonicalizeStoredCwd(cwd), sessionManager.getSessionId(), managementContextKey(options.managementContext)]);
|
|
2990
|
+
const pending = {
|
|
2991
|
+
sessionId: sessionManager.getSessionId(),
|
|
2992
|
+
eventScope: eventScopeFromManagementContext(options.managementContext),
|
|
2993
|
+
promise: this.createSessionRuntime(sessionManager, cwd, options, startup),
|
|
2994
|
+
};
|
|
2995
|
+
pending.promise = pending.promise.finally(() => {
|
|
2716
2996
|
startup.end();
|
|
2717
|
-
|
|
2997
|
+
if (this.pendingSessionOpens.get(key) === pending)
|
|
2998
|
+
this.pendingSessionOpens.delete(key);
|
|
2999
|
+
});
|
|
3000
|
+
this.pendingSessionOpens.set(key, pending);
|
|
3001
|
+
return pending.promise;
|
|
2718
3002
|
}
|
|
2719
|
-
async bindSessionExtensions(session, generation, owner) {
|
|
2720
|
-
const
|
|
3003
|
+
async bindSessionExtensions(session, generation, owner, startupSignal) {
|
|
3004
|
+
const startupDialogs = { signal: startupSignal };
|
|
3005
|
+
const uiContext = this.sessionUiContext(session, generation, startupDialogs);
|
|
2721
3006
|
const key = activeSessionKey(session.sessionId, owner.eventScope);
|
|
2722
3007
|
this.startupSessions.set(key, { session, ...owner });
|
|
2723
3008
|
try {
|
|
@@ -2732,6 +3017,9 @@ export class PiSessionService {
|
|
|
2732
3017
|
});
|
|
2733
3018
|
}
|
|
2734
3019
|
finally {
|
|
3020
|
+
// A captured startup UI context must not carry plugin cancellation into
|
|
3021
|
+
// later user turns after startup has finished.
|
|
3022
|
+
startupDialogs.signal = undefined;
|
|
2735
3023
|
if (this.startupSessions.get(key)?.session === session)
|
|
2736
3024
|
this.startupSessions.delete(key);
|
|
2737
3025
|
}
|
|
@@ -2740,50 +3028,66 @@ export class PiSessionService {
|
|
|
2740
3028
|
this.sessionEventScopes.set(session, active.eventScope);
|
|
2741
3029
|
this.runtimeBySession.set(session, active.runtime);
|
|
2742
3030
|
active.unsubscribe();
|
|
2743
|
-
for (const [
|
|
3031
|
+
for (const [sessionKey, candidate] of this.active.entries()) {
|
|
2744
3032
|
if (candidate === active) {
|
|
2745
|
-
this.active.delete(
|
|
2746
|
-
if (
|
|
2747
|
-
this.
|
|
3033
|
+
this.active.delete(sessionKey);
|
|
3034
|
+
if (sessionKey !== activeSessionKey(session.sessionId, active.eventScope))
|
|
3035
|
+
this.clearCompactionPromptQueueKey(sessionKey);
|
|
2748
3036
|
}
|
|
2749
3037
|
}
|
|
2750
|
-
active.
|
|
3038
|
+
if (active.eventScope === NORMAL_SESSION_EVENT_SCOPE && active.runtime.session !== session) {
|
|
3039
|
+
this.sessionEvents.close(active.runtime.session);
|
|
3040
|
+
}
|
|
3041
|
+
const unsubscribe = session.subscribe((event) => {
|
|
2751
3042
|
this.events.publish(session.sessionId, toClientEvent(event, session.thinkingLevel), active.eventScope);
|
|
2752
3043
|
this.auditToolExecutionEvent(active, event);
|
|
2753
3044
|
this.auditAssistantResponseEvent(active, event);
|
|
2754
3045
|
this.publishActivityForEvent(session, event);
|
|
2755
3046
|
const eventType = getString(event, "type");
|
|
3047
|
+
if (eventType === "message_start" && isRecord(event) && getString(event["message"], "role") === "user") {
|
|
3048
|
+
void this.voidOpenAskForUserMessage(session).catch((error) => {
|
|
3049
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3050
|
+
this.publishActivity(session, "扩展错误", "error", message);
|
|
3051
|
+
this.events.publish(session.sessionId, { type: "session.error", message }, active.eventScope);
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
2756
3054
|
if (eventType === "agent_end")
|
|
2757
3055
|
this.abortRunScopedExtensionDialogs(session);
|
|
2758
3056
|
if (eventType === "compaction_end")
|
|
2759
|
-
this.scheduleCompactionQueueDrain(session
|
|
3057
|
+
this.scheduleCompactionQueueDrain(session);
|
|
2760
3058
|
if (eventType === "agent_start" || eventType === "agent_end")
|
|
2761
|
-
this.scheduleCompactionQueueDrain(session
|
|
3059
|
+
this.scheduleCompactionQueueDrain(session);
|
|
2762
3060
|
this.publishStatus(session);
|
|
2763
3061
|
this.updateSubsessionTracking(session);
|
|
2764
3062
|
});
|
|
3063
|
+
active.unsubscribe = () => {
|
|
3064
|
+
if (active.eventScope === NORMAL_SESSION_EVENT_SCOPE)
|
|
3065
|
+
this.sessionEvents.close(session);
|
|
3066
|
+
unsubscribe();
|
|
3067
|
+
};
|
|
2765
3068
|
this.active.set(activeSessionKey(session.sessionId, active.eventScope), active);
|
|
2766
3069
|
}
|
|
2767
|
-
scheduleCompactionQueueDrain(
|
|
2768
|
-
|
|
3070
|
+
scheduleCompactionQueueDrain(session, delayMs = 0) {
|
|
3071
|
+
const sessionKey = activeSessionKey(session.sessionId, this.eventScopeForSession(session));
|
|
3072
|
+
if (!this.isCurrentActiveSession(session)
|
|
3073
|
+
|| !this.compactionPromptQueues.has(sessionKey) || this.compactionDrainTimers.has(sessionKey))
|
|
2769
3074
|
return;
|
|
2770
3075
|
const timer = setTimeout(() => {
|
|
2771
|
-
this.compactionDrainTimers.delete(
|
|
2772
|
-
this.drainCompactionPromptQueue(
|
|
3076
|
+
this.compactionDrainTimers.delete(sessionKey);
|
|
3077
|
+
this.drainCompactionPromptQueue(session);
|
|
2773
3078
|
}, delayMs);
|
|
2774
|
-
this.compactionDrainTimers.set(
|
|
3079
|
+
this.compactionDrainTimers.set(sessionKey, timer);
|
|
2775
3080
|
}
|
|
2776
|
-
drainCompactionPromptQueue(
|
|
2777
|
-
|
|
2778
|
-
if (active === undefined)
|
|
3081
|
+
drainCompactionPromptQueue(session) {
|
|
3082
|
+
if (!this.isCurrentActiveSession(session))
|
|
2779
3083
|
return;
|
|
2780
|
-
const
|
|
3084
|
+
const sessionKey = activeSessionKey(session.sessionId, this.eventScopeForSession(session));
|
|
2781
3085
|
if (session.isCompacting) {
|
|
2782
|
-
this.scheduleCompactionQueueDrain(
|
|
3086
|
+
this.scheduleCompactionQueueDrain(session, 100);
|
|
2783
3087
|
return;
|
|
2784
3088
|
}
|
|
2785
3089
|
if (session.isStreaming) {
|
|
2786
|
-
const queued = this.takeCompactionPromptQueue(
|
|
3090
|
+
const queued = this.takeCompactionPromptQueue(sessionKey);
|
|
2787
3091
|
if (queued.length === 0)
|
|
2788
3092
|
return;
|
|
2789
3093
|
this.publishStatus(session);
|
|
@@ -2791,12 +3095,12 @@ export class PiSessionService {
|
|
|
2791
3095
|
void this.submitPrompt(session, prompt.text, prompt.kind, prompt.images, prompt.echoUserMessage ?? true);
|
|
2792
3096
|
return;
|
|
2793
3097
|
}
|
|
2794
|
-
const prompt = this.shiftCompactionPrompt(
|
|
3098
|
+
const prompt = this.shiftCompactionPrompt(sessionKey);
|
|
2795
3099
|
if (prompt === undefined)
|
|
2796
3100
|
return;
|
|
2797
3101
|
this.publishStatus(session);
|
|
2798
3102
|
const submitted = this.submitPrompt(session, prompt.text, undefined, prompt.images, prompt.echoUserMessage ?? true);
|
|
2799
|
-
void submitted.finally(() => { this.scheduleCompactionQueueDrain(
|
|
3103
|
+
void submitted.finally(() => { this.scheduleCompactionQueueDrain(session); });
|
|
2800
3104
|
}
|
|
2801
3105
|
takeCompactionPromptQueue(sessionId) {
|
|
2802
3106
|
const queued = this.compactionPromptQueues.get(sessionId) ?? [];
|
|
@@ -2810,12 +3114,15 @@ export class PiSessionService {
|
|
|
2810
3114
|
this.compactionPromptQueues.delete(sessionId);
|
|
2811
3115
|
return prompt;
|
|
2812
3116
|
}
|
|
2813
|
-
clearCompactionPromptQueue(
|
|
2814
|
-
this.
|
|
2815
|
-
|
|
3117
|
+
clearCompactionPromptQueue(session) {
|
|
3118
|
+
this.clearCompactionPromptQueueKey(activeSessionKey(session.sessionId, this.eventScopeForSession(session)));
|
|
3119
|
+
}
|
|
3120
|
+
clearCompactionPromptQueueKey(sessionKey) {
|
|
3121
|
+
this.compactionPromptQueues.delete(sessionKey);
|
|
3122
|
+
const timer = this.compactionDrainTimers.get(sessionKey);
|
|
2816
3123
|
if (timer !== undefined) {
|
|
2817
3124
|
clearTimeout(timer);
|
|
2818
|
-
this.compactionDrainTimers.delete(
|
|
3125
|
+
this.compactionDrainTimers.delete(sessionKey);
|
|
2819
3126
|
}
|
|
2820
3127
|
}
|
|
2821
3128
|
clearCompactionDrainTimers() {
|
|
@@ -2904,6 +3211,7 @@ export class PiSessionService {
|
|
|
2904
3211
|
// the session still reports active work transiently, so the event-driven
|
|
2905
3212
|
// latch may not fire. The heartbeat re-checks once the session settles.
|
|
2906
3213
|
this.updateSubsessionTracking(session);
|
|
3214
|
+
void this.refreshActivityMarker(session);
|
|
2907
3215
|
const activity = this.activities.get(activeSessionKey(session.sessionId, active.eventScope));
|
|
2908
3216
|
if (!this.hasActiveWork(session)) {
|
|
2909
3217
|
if (activity?.phase === "active")
|
|
@@ -2936,7 +3244,7 @@ export class PiSessionService {
|
|
|
2936
3244
|
return this.treeNavigations.has(session)
|
|
2937
3245
|
|| this.isSessionEntryMutationActive(session)
|
|
2938
3246
|
|| this.isTreeExclusiveOperationActive(session)
|
|
2939
|
-
|| sessionHasActiveWork(session, this.compactionQueuedMessages(session
|
|
3247
|
+
|| sessionHasActiveWork(session, this.compactionQueuedMessages(session).length);
|
|
2940
3248
|
}
|
|
2941
3249
|
async runTreeExclusiveOperation(targets, activeError, operation) {
|
|
2942
3250
|
const sessionIds = new Set();
|
|
@@ -2996,7 +3304,7 @@ export class PiSessionService {
|
|
|
2996
3304
|
}
|
|
2997
3305
|
assertTreeNavigationInactive(session, action) {
|
|
2998
3306
|
if (this.treeNavigations.has(session))
|
|
2999
|
-
throw new Error(
|
|
3307
|
+
throw new Error(`会话树导航期间无法${action}`);
|
|
3000
3308
|
}
|
|
3001
3309
|
async runSessionEntryMutation(session, action, operation) {
|
|
3002
3310
|
this.beginSessionEntryMutation(session, action);
|
|
@@ -3130,16 +3438,16 @@ export class PiSessionService {
|
|
|
3130
3438
|
const message = getProperty(event, "message");
|
|
3131
3439
|
if (getString(message, "role") !== "assistant")
|
|
3132
3440
|
return;
|
|
3133
|
-
this.
|
|
3441
|
+
this.recordManagementMessageAudit(active, "assistant_response", auditVisibleAssistantMessage(message));
|
|
3134
3442
|
}
|
|
3135
3443
|
auditUserPrompt(session, text) {
|
|
3136
3444
|
const active = [...new Set(this.active.values())]
|
|
3137
3445
|
.find(candidate => candidate.runtime.session === session);
|
|
3138
3446
|
if (active === undefined)
|
|
3139
3447
|
return;
|
|
3140
|
-
this.
|
|
3448
|
+
this.recordManagementMessageAudit(active, "user_prompt", text);
|
|
3141
3449
|
}
|
|
3142
|
-
|
|
3450
|
+
recordManagementMessageAudit(active, action, content) {
|
|
3143
3451
|
const identity = active.managementAuditIdentity;
|
|
3144
3452
|
if (identity === undefined || this.managementAudit === undefined)
|
|
3145
3453
|
return;
|
|
@@ -3158,7 +3466,7 @@ export class PiSessionService {
|
|
|
3158
3466
|
sessionId: active.runtime.session.sessionId,
|
|
3159
3467
|
action,
|
|
3160
3468
|
error: error instanceof Error ? error.message : String(error),
|
|
3161
|
-
}, "failed to enqueue management-mode
|
|
3469
|
+
}, "failed to enqueue management-mode message audit");
|
|
3162
3470
|
}
|
|
3163
3471
|
}
|
|
3164
3472
|
/**
|
|
@@ -3220,7 +3528,20 @@ export class PiSessionService {
|
|
|
3220
3528
|
this.events.publishGlobal({ type: "activity.update", activity }, eventScope);
|
|
3221
3529
|
this.observeUnreadActivityState(session);
|
|
3222
3530
|
}
|
|
3531
|
+
async refreshActivityMarker(session) {
|
|
3532
|
+
const eventScope = this.eventScopeForSession(session);
|
|
3533
|
+
if (eventScope !== NORMAL_SESSION_EVENT_SCOPE
|
|
3534
|
+
|| this.activeForSessionIdAndScope(session.sessionId, eventScope)?.runtime.session !== session)
|
|
3535
|
+
return;
|
|
3536
|
+
const previous = this.activityMarker.isActiveElsewhere(session.sessionFile);
|
|
3537
|
+
await this.activityMarker.refresh(session.sessionFile, this.hasActiveWork(session));
|
|
3538
|
+
if (this.activeForSessionIdAndScope(session.sessionId, eventScope)?.runtime.session === session
|
|
3539
|
+
&& previous !== this.activityMarker.isActiveElsewhere(session.sessionFile)) {
|
|
3540
|
+
this.publishStatus(session);
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3223
3543
|
publishStatus(session) {
|
|
3544
|
+
void this.refreshActivityMarker(session);
|
|
3224
3545
|
const status = this.statusFromSession(session);
|
|
3225
3546
|
this.clearStaleActiveActivity(session);
|
|
3226
3547
|
const eventScope = this.eventScopeForSession(session);
|
|
@@ -3262,7 +3583,7 @@ export class PiSessionService {
|
|
|
3262
3583
|
const eventScope = this.eventScopeForSession(session);
|
|
3263
3584
|
return eventScope === NORMAL_SESSION_EVENT_SCOPE ? session.sessionId : activeSessionKey(session.sessionId, eventScope);
|
|
3264
3585
|
}
|
|
3265
|
-
publishStatusForSessionId(sessionId) {
|
|
3586
|
+
publishStatusForSessionId(sessionId, scope) {
|
|
3266
3587
|
const sessions = new Set();
|
|
3267
3588
|
for (const active of this.activeSessionsForId(sessionId))
|
|
3268
3589
|
sessions.add(active.runtime.session);
|
|
@@ -3270,15 +3591,17 @@ export class PiSessionService {
|
|
|
3270
3591
|
if (startup.session.sessionId === sessionId)
|
|
3271
3592
|
sessions.add(startup.session);
|
|
3272
3593
|
}
|
|
3273
|
-
for (const session of sessions)
|
|
3274
|
-
this.
|
|
3594
|
+
for (const session of sessions) {
|
|
3595
|
+
if (scope === undefined || this.eventScopeForSession(session) === scope)
|
|
3596
|
+
this.publishStatus(session);
|
|
3597
|
+
}
|
|
3275
3598
|
}
|
|
3276
3599
|
statusFromSession(session, messageCount = session.messages.length) {
|
|
3277
3600
|
const stats = session.getSessionStats();
|
|
3278
3601
|
const model = session.model === undefined ? undefined : modelToClientModel(session.model);
|
|
3279
3602
|
const contextUsage = session.getContextUsage();
|
|
3280
3603
|
const warnings = this.warningsForSession(session);
|
|
3281
|
-
const pendingAsk = this.pendingAskStore.pendingAsk(session
|
|
3604
|
+
const pendingAsk = this.pendingAskStore.pendingAsk(this.dialogSessionKey(session));
|
|
3282
3605
|
const pendingDialogs = this.pendingExtensionDialogStore.pendingDialogs(this.dialogSessionKey(session));
|
|
3283
3606
|
return {
|
|
3284
3607
|
sessionId: session.sessionId,
|
|
@@ -3289,7 +3612,7 @@ export class PiSessionService {
|
|
|
3289
3612
|
isCompacting: session.isCompacting,
|
|
3290
3613
|
isBashRunning: session.isBashRunning,
|
|
3291
3614
|
pendingMessageCount: this.pendingMessageCount(session),
|
|
3292
|
-
queuedMessages: queuedMessagesFromSession(session, this.compactionQueuedMessages(session
|
|
3615
|
+
queuedMessages: queuedMessagesFromSession(session, this.compactionQueuedMessages(session)),
|
|
3293
3616
|
messageCount,
|
|
3294
3617
|
tokens: stats.tokens,
|
|
3295
3618
|
cost: stats.cost,
|
|
@@ -3308,19 +3631,28 @@ export class PiSessionService {
|
|
|
3308
3631
|
warningsForSession(session) {
|
|
3309
3632
|
const runtime = [...this.active.values()].find((active) => active.runtime.session === session)?.runtime;
|
|
3310
3633
|
const warnings = runtime === undefined ? [] : collectRuntimeWarnings(runtime);
|
|
3311
|
-
|
|
3634
|
+
if (this.eventScopeForSession(session) === NORMAL_SESSION_EVENT_SCOPE
|
|
3635
|
+
&& this.activityMarker.isActiveElsewhere(session.sessionFile)) {
|
|
3636
|
+
warnings.push({
|
|
3637
|
+
severity: "info",
|
|
3638
|
+
message: "此会话最近在另一个 PI WEB 实例中活动。请避免在两个实例中同时操作此会话。",
|
|
3639
|
+
source: "PI-WEB",
|
|
3640
|
+
});
|
|
3641
|
+
}
|
|
3642
|
+
const authPath = modelRuntimeAuthPath(session.modelRuntime);
|
|
3643
|
+
const anthropic = authPath === undefined ? undefined : anthropicSubscriptionWarning(session, authPath);
|
|
3312
3644
|
if (anthropic !== undefined)
|
|
3313
3645
|
warnings.push(anthropic);
|
|
3314
3646
|
return warnings;
|
|
3315
3647
|
}
|
|
3316
3648
|
pendingMessageCount(session) {
|
|
3317
|
-
return session.pendingMessageCount + this.compactionQueuedMessages(session
|
|
3649
|
+
return session.pendingMessageCount + this.compactionQueuedMessages(session).length;
|
|
3318
3650
|
}
|
|
3319
|
-
compactionQueuedMessages(
|
|
3320
|
-
return this.compactionPromptQueues.get(sessionId) ?? [];
|
|
3651
|
+
compactionQueuedMessages(session) {
|
|
3652
|
+
return this.compactionPromptQueues.get(activeSessionKey(session.sessionId, this.eventScopeForSession(session))) ?? [];
|
|
3321
3653
|
}
|
|
3322
3654
|
hasQueuedMessageText(session, text) {
|
|
3323
|
-
return queuedMessagesFromSession(session, this.compactionQueuedMessages(session
|
|
3655
|
+
return queuedMessagesFromSession(session, this.compactionQueuedMessages(session)).some((message) => message.text === text);
|
|
3324
3656
|
}
|
|
3325
3657
|
notificationsForScope(scope) {
|
|
3326
3658
|
if (scope === NORMAL_SESSION_EVENT_SCOPE)
|
|
@@ -3357,6 +3689,9 @@ export class PiSessionService {
|
|
|
3357
3689
|
await this.publishUnreadMutations([]);
|
|
3358
3690
|
return this.unreadForScope(eventScope).durableCatalogSnapshot();
|
|
3359
3691
|
}
|
|
3692
|
+
async reconcileUnreadWorkspaces(cwds) {
|
|
3693
|
+
await this.publishUnreadMutations(this.unreadStore.reconcileWorkspaces(cwds));
|
|
3694
|
+
}
|
|
3360
3695
|
async acknowledgeUnread(sessionId, request, context) {
|
|
3361
3696
|
const store = this.unreadForScope(eventScopeFromManagementContext(context));
|
|
3362
3697
|
const result = store.acknowledge(sessionId, {
|
|
@@ -3380,7 +3715,7 @@ export class PiSessionService {
|
|
|
3380
3715
|
return result.snapshot;
|
|
3381
3716
|
}
|
|
3382
3717
|
async deliverSubsessionNotification(session, notification) {
|
|
3383
|
-
await this.runSessionEntryMutation(session, "
|
|
3718
|
+
await this.runSessionEntryMutation(session, "发送子会话通知", () => session.sendCustomMessage({ customType: SUBSESSION_NOTIFICATION_CUSTOM_TYPE, content: notification.text, display: true, details: { sessionId: notification.childId } }, { triggerTurn: true, deliverAs: "followUp" }));
|
|
3384
3719
|
this.publishStatus(session);
|
|
3385
3720
|
}
|
|
3386
3721
|
logSubsessionNotificationFailure(parentId, childId, error) {
|
|
@@ -3401,6 +3736,7 @@ export class PiSessionService {
|
|
|
3401
3736
|
this.replaceSessionNotificationContext(session, candidateGeneration);
|
|
3402
3737
|
},
|
|
3403
3738
|
});
|
|
3739
|
+
await this.reloadModelScope(session);
|
|
3404
3740
|
if (candidateGeneration !== undefined) {
|
|
3405
3741
|
this.publishNotificationMutations(this.notificationsForSession(session).commitReplacement(candidateGeneration));
|
|
3406
3742
|
}
|
|
@@ -3414,44 +3750,34 @@ export class PiSessionService {
|
|
|
3414
3750
|
}
|
|
3415
3751
|
const message = error instanceof Error ? error.message : String(error);
|
|
3416
3752
|
this.publishActivity(session, "重新加载失败", "error", message);
|
|
3417
|
-
this.events.publish(session.sessionId, { type: "session.error", message });
|
|
3753
|
+
this.events.publish(session.sessionId, { type: "session.error", message }, this.eventScopeForSession(session));
|
|
3418
3754
|
this.publishStatus(session);
|
|
3419
3755
|
throw error;
|
|
3420
3756
|
}
|
|
3421
3757
|
});
|
|
3422
3758
|
}
|
|
3423
|
-
async dismissWarning(ref, dismissId) {
|
|
3424
|
-
const session = await this.getOrOpen(ref);
|
|
3759
|
+
async dismissWarning(ref, dismissId, managementContext) {
|
|
3760
|
+
const session = await this.getOrOpen(ref, managementContext);
|
|
3425
3761
|
dismissSessionWarning(session, dismissId);
|
|
3426
3762
|
this.publishStatus(session);
|
|
3427
3763
|
return this.statusFromSession(session);
|
|
3428
3764
|
}
|
|
3429
3765
|
openExistingSession(sessionId, cwd, openSessionManager, options = {}) {
|
|
3430
|
-
|
|
3431
|
-
if (active !== undefined)
|
|
3432
|
-
return Promise.resolve(active);
|
|
3433
|
-
const key = JSON.stringify([canonicalizeStoredCwd(cwd), sessionId]);
|
|
3434
|
-
const existing = this.pendingSessionOpens.get(key);
|
|
3435
|
-
if (existing !== undefined)
|
|
3436
|
-
return existing.promise;
|
|
3437
|
-
const pending = {
|
|
3438
|
-
sessionId,
|
|
3439
|
-
promise: this.create(openSessionManager(), cwd, options),
|
|
3440
|
-
};
|
|
3441
|
-
pending.promise = pending.promise.finally(() => {
|
|
3442
|
-
if (this.pendingSessionOpens.get(key) === pending)
|
|
3443
|
-
this.pendingSessionOpens.delete(key);
|
|
3444
|
-
});
|
|
3445
|
-
this.pendingSessionOpens.set(key, pending);
|
|
3446
|
-
return pending.promise;
|
|
3766
|
+
return this.openPending(sessionId, cwd, options.managementContext, openSessionManager, options);
|
|
3447
3767
|
}
|
|
3448
3768
|
isCurrentActiveSession(session) {
|
|
3449
3769
|
return [...this.active.values()].some((active) => active.runtime.session === session);
|
|
3450
3770
|
}
|
|
3451
3771
|
async createSessionRuntime(sessionManager, cwd, options, startup) {
|
|
3452
3772
|
startup.report(STARTUP_PHASE_RUNTIME);
|
|
3453
|
-
|
|
3454
|
-
|
|
3773
|
+
options.startupSignal?.throwIfAborted();
|
|
3774
|
+
// Ordinary local sessions (undefined provenance) keep the current rule.
|
|
3775
|
+
// Only an explicit host one-shot forces delegation off; do not treat every
|
|
3776
|
+
// defined provenance as disabled the way upstream does.
|
|
3777
|
+
const delegationToolsEnabled = options.creationProvenance === "host-one-shot"
|
|
3778
|
+
? false
|
|
3779
|
+
: options.creationProvenance !== "tracked-subsession"
|
|
3780
|
+
&& await sessionAllowsDelegationTools(sessionManager, this.sessionManager);
|
|
3455
3781
|
const runtime = await this.createAgentRuntime(this.createRuntime, {
|
|
3456
3782
|
cwd,
|
|
3457
3783
|
agentDir: this.agentDir,
|
|
@@ -3472,6 +3798,13 @@ export class PiSessionService {
|
|
|
3472
3798
|
};
|
|
3473
3799
|
let boundSession = runtime.session;
|
|
3474
3800
|
this.sessionEventScopes.set(boundSession, active.eventScope);
|
|
3801
|
+
if (options.managementContext !== undefined)
|
|
3802
|
+
this.sessionManagementContexts.set(boundSession, options.managementContext);
|
|
3803
|
+
const startupSignal = options.startupSignal;
|
|
3804
|
+
const cancelStartup = () => { this.endSessionExtensionDialogs(boundSession); };
|
|
3805
|
+
if (startupSignal !== undefined && !startupSignal.aborted) {
|
|
3806
|
+
startupSignal.addEventListener("abort", cancelStartup, { once: true });
|
|
3807
|
+
}
|
|
3475
3808
|
const notificationStore = this.notificationsForScope(active.eventScope);
|
|
3476
3809
|
let notificationGeneration = options.notificationGeneration;
|
|
3477
3810
|
let notificationOwnership = options.notifications === "disabled"
|
|
@@ -3495,17 +3828,23 @@ export class PiSessionService {
|
|
|
3495
3828
|
if (notificationGeneration !== undefined)
|
|
3496
3829
|
this.notificationGenerationBySession.set(runtime.session, notificationGeneration);
|
|
3497
3830
|
try {
|
|
3831
|
+
startupSignal?.throwIfAborted();
|
|
3498
3832
|
if (options.creationProvenance === "tracked-subsession") {
|
|
3499
3833
|
await this.publishUnreadMutations(this.unreadForScope(active.eventScope).excludeSession(runtime.session.sessionId, canonicalizeStoredCwd(runtime.session.sessionManager.getCwd())));
|
|
3500
3834
|
}
|
|
3501
3835
|
else {
|
|
3502
3836
|
await this.recoverSubsessionTrackingForOpenedSession(runtime.session);
|
|
3503
3837
|
}
|
|
3838
|
+
startupSignal?.throwIfAborted();
|
|
3504
3839
|
startup.report(STARTUP_PHASE_EXTENSIONS);
|
|
3505
|
-
|
|
3840
|
+
// Startup hooks emit through the same scoped stream as hosted turns.
|
|
3506
3841
|
this.bindRuntime(active);
|
|
3842
|
+
await this.bindSessionExtensions(runtime.session, notificationGeneration, active, startupSignal);
|
|
3843
|
+
startupSignal?.throwIfAborted();
|
|
3507
3844
|
runtime.setRebindSession(async (session) => {
|
|
3508
3845
|
this.sessionEventScopes.set(session, active.eventScope);
|
|
3846
|
+
if (options.managementContext !== undefined)
|
|
3847
|
+
this.sessionManagementContexts.set(session, options.managementContext);
|
|
3509
3848
|
const priorGeneration = notificationGeneration;
|
|
3510
3849
|
let candidateGeneration;
|
|
3511
3850
|
try {
|
|
@@ -3536,6 +3875,7 @@ export class PiSessionService {
|
|
|
3536
3875
|
throw error;
|
|
3537
3876
|
}
|
|
3538
3877
|
});
|
|
3878
|
+
startupSignal?.throwIfAborted();
|
|
3539
3879
|
this.active.set(activeSessionKey(runtime.session.sessionId, active.eventScope), active);
|
|
3540
3880
|
if (notificationOwnership === "replacement" && notificationGeneration !== undefined) {
|
|
3541
3881
|
this.publishNotificationMutations(notificationStore.commitReplacement(notificationGeneration));
|
|
@@ -3563,7 +3903,7 @@ export class PiSessionService {
|
|
|
3563
3903
|
this.active.delete(sessionKey);
|
|
3564
3904
|
this.activities.delete(sessionKey);
|
|
3565
3905
|
this.clearAuthLossWarningsForSession(runtime.session.sessionId);
|
|
3566
|
-
this.clearCompactionPromptQueue(runtime.session
|
|
3906
|
+
this.clearCompactionPromptQueue(runtime.session);
|
|
3567
3907
|
removedActive = true;
|
|
3568
3908
|
}
|
|
3569
3909
|
if (removedActive) {
|
|
@@ -3577,11 +3917,14 @@ export class PiSessionService {
|
|
|
3577
3917
|
}
|
|
3578
3918
|
throw error;
|
|
3579
3919
|
}
|
|
3920
|
+
finally {
|
|
3921
|
+
startupSignal?.removeEventListener("abort", cancelStartup);
|
|
3922
|
+
}
|
|
3580
3923
|
}
|
|
3581
3924
|
replaceSessionNotificationContext(session, generation) {
|
|
3582
3925
|
session.extensionRunner.setUIContext(this.sessionUiContext(session, generation), "rpc");
|
|
3583
3926
|
}
|
|
3584
|
-
sessionUiContext(session, generation) {
|
|
3927
|
+
sessionUiContext(session, generation, startupDialogs = {}) {
|
|
3585
3928
|
const baseUiContext = session.extensionRunner.getUIContext();
|
|
3586
3929
|
const eventScope = this.eventScopeForSession(session);
|
|
3587
3930
|
const notificationStore = this.notificationsForScope(eventScope);
|
|
@@ -3596,14 +3939,6 @@ export class PiSessionService {
|
|
|
3596
3939
|
}
|
|
3597
3940
|
const added = notificationStore.addNotification(generation, message, type);
|
|
3598
3941
|
this.publishNotificationMutations(added.mutations);
|
|
3599
|
-
if (added.notification === undefined)
|
|
3600
|
-
return;
|
|
3601
|
-
this.events.publish(session.sessionId, {
|
|
3602
|
-
type: "command.output",
|
|
3603
|
-
level: type === "error" ? "error" : "info",
|
|
3604
|
-
message,
|
|
3605
|
-
notificationId: added.notification.id,
|
|
3606
|
-
}, eventScope);
|
|
3607
3942
|
};
|
|
3608
3943
|
// PI WEB owns the browser-facing dialog, notification, and text-formatting
|
|
3609
3944
|
// boundaries: the three dialog primitives park daemon-held Promises that
|
|
@@ -3617,13 +3952,13 @@ export class PiSessionService {
|
|
|
3617
3952
|
if (property === "theme")
|
|
3618
3953
|
return plainTextTheme;
|
|
3619
3954
|
if (property === "confirm") {
|
|
3620
|
-
return (title, message, opts) => this.openExtensionDialog(session, { kind: "confirm", title, message }, opts);
|
|
3955
|
+
return (title, message, opts) => this.openExtensionDialog(session, { kind: "confirm", title, message }, opts, startupDialogs.signal);
|
|
3621
3956
|
}
|
|
3622
3957
|
if (property === "select") {
|
|
3623
|
-
return (title, options, opts) => this.openExtensionDialog(session, { kind: "select", title, options }, opts);
|
|
3958
|
+
return (title, options, opts) => this.openExtensionDialog(session, { kind: "select", title, options }, opts, startupDialogs.signal);
|
|
3624
3959
|
}
|
|
3625
3960
|
if (property === "input") {
|
|
3626
|
-
return (title, placeholder, opts) => this.openExtensionDialog(session, { kind: "input", title, placeholder }, opts);
|
|
3961
|
+
return (title, placeholder, opts) => this.openExtensionDialog(session, { kind: "input", title, placeholder }, opts, startupDialogs.signal);
|
|
3627
3962
|
}
|
|
3628
3963
|
const value = Reflect.get(target, property, receiver);
|
|
3629
3964
|
return value;
|
|
@@ -3664,11 +3999,12 @@ export class PiSessionService {
|
|
|
3664
3999
|
}
|
|
3665
4000
|
publishUnreadMutations(mutations) {
|
|
3666
4001
|
if (mutations.length > 0) {
|
|
3667
|
-
const scopes = new
|
|
4002
|
+
const scopes = new Map();
|
|
3668
4003
|
for (const mutation of mutations) {
|
|
3669
|
-
|
|
4004
|
+
const scope = mutation.eventScope ?? NORMAL_SESSION_EVENT_SCOPE;
|
|
4005
|
+
scopes.set(scope, scopes.get(scope) === true || mutation.event.unread !== null);
|
|
3670
4006
|
}
|
|
3671
|
-
scopes.forEach((scope) => this.onUnreadChanged?.(scope));
|
|
4007
|
+
scopes.forEach((hasNewCompletion, scope) => this.onUnreadChanged?.(scope, hasNewCompletion));
|
|
3672
4008
|
}
|
|
3673
4009
|
this.enqueueUnreadMutations(mutations);
|
|
3674
4010
|
this.unreadPublicationFlushRequested = true;
|
|
@@ -3849,7 +4185,7 @@ function clientSessionFromListEntry(session) {
|
|
|
3849
4185
|
created: session.created.toISOString(),
|
|
3850
4186
|
modified: session.modified.toISOString(),
|
|
3851
4187
|
messageCount: session.messageCount,
|
|
3852
|
-
firstMessage: session.firstMessage,
|
|
4188
|
+
firstMessage: clientSessionFirstMessagePreview(session.firstMessage),
|
|
3853
4189
|
...(session.parentSessionPath === undefined ? {} : { parentSessionPath: session.parentSessionPath }),
|
|
3854
4190
|
};
|
|
3855
4191
|
}
|
|
@@ -4083,37 +4419,12 @@ function buildPromptOptions(behavior, images) {
|
|
|
4083
4419
|
options.images = images;
|
|
4084
4420
|
return Object.keys(options).length > 0 ? options : undefined;
|
|
4085
4421
|
}
|
|
4086
|
-
function stringValue(value) {
|
|
4087
|
-
return typeof value === "string" ? value : "";
|
|
4088
|
-
}
|
|
4089
4422
|
function historyMessages(session) {
|
|
4090
4423
|
return historyMessagesFromManager(session.sessionManager);
|
|
4091
4424
|
}
|
|
4092
4425
|
function historyMessagesFromManager(sessionManager) {
|
|
4093
4426
|
return historyMessagesFromEntries(sessionManager.getBranch());
|
|
4094
4427
|
}
|
|
4095
|
-
function historyMessagesFromEntries(entries) {
|
|
4096
|
-
const messages = [];
|
|
4097
|
-
let thinkingLevel;
|
|
4098
|
-
for (const entry of entries) {
|
|
4099
|
-
if (!isRecord(entry))
|
|
4100
|
-
continue;
|
|
4101
|
-
if (entry["type"] === "message")
|
|
4102
|
-
messages.push(annotateAssistantThinkingLevel(entry["message"], thinkingLevel));
|
|
4103
|
-
else if (entry["type"] === "thinking_level_change") {
|
|
4104
|
-
const level = getString(entry, "thinkingLevel");
|
|
4105
|
-
if (level !== undefined)
|
|
4106
|
-
thinkingLevel = level;
|
|
4107
|
-
}
|
|
4108
|
-
else if (entry["type"] === "custom_message" && entry["display"] === true)
|
|
4109
|
-
messages.push({ role: "custom", content: entry["content"], customType: entry["customType"], details: entry["details"] });
|
|
4110
|
-
else if (entry["type"] === "compaction")
|
|
4111
|
-
messages.push({ role: "system", source: "compaction", content: `Compacted history:\n\n${stringValue(entry["summary"])}` });
|
|
4112
|
-
else if (entry["type"] === "branch_summary")
|
|
4113
|
-
messages.push({ role: "system", source: "branch_summary", content: `Branch summary:\n\n${stringValue(entry["summary"])}` });
|
|
4114
|
-
}
|
|
4115
|
-
return messages;
|
|
4116
|
-
}
|
|
4117
4428
|
function transcriptMessageCount(entries) {
|
|
4118
4429
|
let count = 0;
|
|
4119
4430
|
for (const entry of entries) {
|
|
@@ -4122,6 +4433,30 @@ function transcriptMessageCount(entries) {
|
|
|
4122
4433
|
}
|
|
4123
4434
|
return count;
|
|
4124
4435
|
}
|
|
4436
|
+
function activeEditableTreeTargetParentId(manager, targetId, activeLeafId) {
|
|
4437
|
+
if (activeLeafId !== targetId)
|
|
4438
|
+
return undefined;
|
|
4439
|
+
const entry = manager.getBranch().at(-1);
|
|
4440
|
+
if (!isRecord(entry) || entry["id"] !== targetId)
|
|
4441
|
+
return undefined;
|
|
4442
|
+
const isUserMessage = entry["type"] === "message"
|
|
4443
|
+
&& isRecord(entry["message"]) && entry["message"]["role"] === "user";
|
|
4444
|
+
if (!isUserMessage && entry["type"] !== "custom_message")
|
|
4445
|
+
return undefined;
|
|
4446
|
+
const parentId = entry["parentId"];
|
|
4447
|
+
if (parentId === targetId)
|
|
4448
|
+
return undefined;
|
|
4449
|
+
return parentId === null || typeof parentId === "string" ? parentId : undefined;
|
|
4450
|
+
}
|
|
4451
|
+
function setSessionTreeLeaf(manager, leafId) {
|
|
4452
|
+
if (leafId === null)
|
|
4453
|
+
manager.resetLeaf();
|
|
4454
|
+
else
|
|
4455
|
+
manager.branch(leafId);
|
|
4456
|
+
}
|
|
4457
|
+
function transcriptBranchIncludesEntry(entries, entryId) {
|
|
4458
|
+
return entries.some((entry) => isRecord(entry) && entry["id"] === entryId);
|
|
4459
|
+
}
|
|
4125
4460
|
/** custom entry type used to persist parent -> child subsession links outside LLM context. */
|
|
4126
4461
|
const SUBSESSION_LINK_CUSTOM_TYPE = "pi-web.subsession.link";
|
|
4127
4462
|
/** custom entry type used to mark a child as created by spawn_subsession. */
|