@chainingintention/pi-web-cn 1.202608.8 → 2.0.0
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 +2 -0
- package/dist/cli.js +123 -72
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/{CodeViewer-CWKEeX0r.js → CodeViewer-DCl56K0Z.js} +2 -2
- package/dist/client/assets/{TerminalPanel-BmA7Ayzk.js → TerminalPanel-DCvoEjUF.js} +6 -6
- package/dist/client/assets/{index-D6j67Izj.js → index-CMvms5cF.js} +534 -512
- package/dist/client/index.html +1 -1
- package/dist/config.js +39 -18
- package/dist/config.js.map +1 -1
- package/dist/nativeServices/serviceAction.js +128 -0
- package/dist/nativeServices/serviceAction.js.map +1 -0
- package/dist/pi-web-plugins/git/browser/git-contract.js +108 -0
- package/dist/pi-web-plugins/git/browser/git-panel.js +609 -0
- package/dist/pi-web-plugins/git/browser/gitFileList.js +43 -0
- package/dist/pi-web-plugins/git/browser/gitFileShared.js +16 -0
- package/dist/pi-web-plugins/git/browser/gitFileTree.js +81 -0
- package/dist/pi-web-plugins/git/browser/gitFileViewPreference.js +35 -0
- package/dist/pi-web-plugins/git/browser/gitRoute.js +42 -0
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +10 -0
- package/dist/pi-web-plugins/git/browser/unifiedDiff.js +301 -0
- package/dist/pi-web-plugins/git/git-backend.js +433 -0
- package/dist/pi-web-plugins/git/package.json +15 -0
- package/dist/pi-web-plugins/git/server-plugin.js +176 -0
- package/dist/pi-web-plugins/info/infoInternals.js +1 -1
- package/dist/pi-web-plugins/info/pi-web-plugin.js +2 -2
- package/dist/pi-web-plugins/relays/pi-web-plugin.js +1 -1
- package/dist/pi-web-plugins/relays/relayDiscovery.js +128 -23
- package/dist/pi-web-plugins/relays/relaysPanelElement.js +138 -16
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +1 -1
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +1 -1
- package/dist/piWebVersionReport.js +63 -4
- package/dist/piWebVersionReport.js.map +1 -1
- package/dist/plugin-api.d.ts +40 -18
- package/dist/pluginRecoveryCli.js +172 -0
- package/dist/pluginRecoveryCli.js.map +1 -0
- package/dist/server/activity/workspaceActivityService.js +7 -1
- package/dist/server/activity/workspaceActivityService.js.map +1 -1
- package/dist/server/app.js +49 -23
- package/dist/server/app.js.map +1 -1
- package/dist/server/audit/managementAuditStore.js +4 -2
- package/dist/server/audit/managementAuditStore.js.map +1 -1
- package/dist/server/configRoutes.js +4 -5
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/machines/machineClient.js +23 -4
- package/dist/server/machines/machineClient.js.map +1 -1
- package/dist/server/machines/machineProxyRoutes.js +162 -8
- package/dist/server/machines/machineProxyRoutes.js.map +1 -1
- package/dist/server/managementEmbed.js +6 -0
- package/dist/server/managementEmbed.js.map +1 -1
- package/dist/server/piWebPluginCatalog.js +441 -0
- package/dist/server/piWebPluginCatalog.js.map +1 -0
- package/dist/server/piWebPluginLifecycle.js +162 -0
- package/dist/server/piWebPluginLifecycle.js.map +1 -0
- package/dist/server/piWebPluginService.js +55 -7
- package/dist/server/piWebPluginService.js.map +1 -1
- package/dist/server/plugins/pluginBackendProxyRoutes.js +80 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -0
- package/dist/server/plugins/serverPluginExec.js +184 -0
- package/dist/server/plugins/serverPluginExec.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +480 -0
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -0
- package/dist/server/realtime/sessionEventHub.js +13 -0
- package/dist/server/realtime/sessionEventHub.js.map +1 -1
- package/dist/server/realtime/sessionEventScope.js +8 -1
- package/dist/server/realtime/sessionEventScope.js.map +1 -1
- package/dist/server/requestCancellation.js +28 -0
- package/dist/server/requestCancellation.js.map +1 -0
- package/dist/server/sessiond/agentProcessEnvironment.js +61 -0
- package/dist/server/sessiond/agentProcessEnvironment.js.map +1 -0
- package/dist/server/sessiond/managementProjectResolver.js +16 -0
- package/dist/server/sessiond/managementProjectResolver.js.map +1 -0
- package/dist/server/sessiond/pluginBackendRoutes.js +78 -0
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond/sessionServiceDependencies.js +0 -1
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
- package/dist/server/sessiond/sessiondStateOwnership.js +139 -0
- package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -0
- package/dist/server/sessiond/workspaceCatalogRoutes.js +38 -0
- package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js +56 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -0
- package/dist/server/sessiond.js +98 -11
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/authService.js +15 -51
- package/dist/server/sessions/authService.js.map +1 -1
- package/dist/server/sessions/dockerEnvironmentFacts.js +181 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/managementAgentTools.js +12 -4
- package/dist/server/sessions/managementAgentTools.js.map +1 -1
- package/dist/server/sessions/managementSandbox.js +5 -5
- package/dist/server/sessions/managementSandbox.js.map +1 -1
- package/dist/server/sessions/piSessionManagerGateway.js +129 -2
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +268 -119
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionCommandService.js +15 -1
- package/dist/server/sessions/sessionCommandService.js.map +1 -1
- package/dist/server/sessions/sessionEnvironmentFacts.js +48 -0
- package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/sessionFileFormat.js +27 -0
- package/dist/server/sessions/sessionFileFormat.js.map +1 -0
- package/dist/server/sessions/sessionFileHeader.js +82 -23
- package/dist/server/sessions/sessionFileHeader.js.map +1 -1
- package/dist/server/sessions/sessionRoutes.js +16 -2
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/sessionSummaryScanner.js +491 -0
- package/dist/server/sessions/sessionSummaryScanner.js.map +1 -0
- package/dist/server/sessions/spawnSubsessionTool.js +4 -8
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
- package/dist/server/sessions/spawnTargetResolver.js +2 -2
- package/dist/server/sessions/spawnTargetResolver.js.map +1 -1
- package/dist/server/status/machineStatusRoutes.js +10 -0
- package/dist/server/status/machineStatusRoutes.js.map +1 -0
- package/dist/server/status/machineStatusService.js +115 -0
- package/dist/server/status/machineStatusService.js.map +1 -0
- package/dist/server/status/workspaceAttribution.js +71 -0
- package/dist/server/status/workspaceAttribution.js.map +1 -0
- package/dist/server/terminalProxyRoutes.js.map +1 -1
- package/dist/server/workbench/skillSync.js +9 -1
- package/dist/server/workbench/skillSync.js.map +1 -1
- package/dist/server/workspaceExplorerRoutes.js +12 -25
- package/dist/server/workspaceExplorerRoutes.js.map +1 -1
- package/dist/server/workspaces/effectivePathAccess.js +3 -2
- package/dist/server/workspaces/effectivePathAccess.js.map +1 -1
- package/dist/server/workspaces/fileContentService.js +17 -9
- package/dist/server/workspaces/fileContentService.js.map +1 -1
- package/dist/server/workspaces/fileOperationService.js +4 -17
- package/dist/server/workspaces/fileOperationService.js.map +1 -1
- package/dist/server/workspaces/filePreviewResponseHeaders.js +18 -0
- package/dist/server/workspaces/filePreviewResponseHeaders.js.map +1 -0
- package/dist/server/workspaces/filePreviewResponsePolicy.js +55 -0
- package/dist/server/workspaces/filePreviewResponsePolicy.js.map +1 -0
- package/dist/server/workspaces/filePreviewService.js +81 -0
- package/dist/server/workspaces/filePreviewService.js.map +1 -0
- package/dist/server/workspaces/projectWorkspaceCwds.js +2 -2
- package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -1
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +416 -0
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -0
- package/dist/server/workspaces/workspaceCatalog.js +87 -0
- package/dist/server/workspaces/workspaceCatalog.js.map +1 -0
- package/dist/server/workspaces/workspaceContext.js +5 -5
- package/dist/server/workspaces/workspaceContext.js.map +1 -1
- package/dist/server/workspaces/workspaceDeletionRoutes.js +114 -99
- package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -1
- package/dist/server/workspaces/workspaceProviderRegistry.js +650 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -0
- package/dist/server/workspaces/workspaceRemovalService.js +258 -0
- package/dist/server/workspaces/workspaceRemovalService.js.map +1 -0
- package/dist/server/workspaces/workspaceRouteContext.js +2 -1
- package/dist/server/workspaces/workspaceRouteContext.js.map +1 -1
- package/dist/server/workspaces/workspaceRouteErrors.js +7 -0
- package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -0
- package/dist/server/workspaces/worktreePreRemoveHook.js +39 -0
- package/dist/server/workspaces/worktreePreRemoveHook.js.map +1 -0
- package/dist/server-plugin-api.d.ts +115 -0
- package/dist/server-plugin-api.js +2 -0
- package/dist/server-plugin-api.js.map +1 -0
- package/dist/serverPluginRecovery.js +138 -0
- package/dist/serverPluginRecovery.js.map +1 -0
- package/dist/sessiond/config.js +6 -0
- package/dist/sessiond/config.js.map +1 -1
- package/dist/sessiond/sessionDaemonClient.js +7 -6
- package/dist/sessiond/sessionDaemonClient.js.map +1 -1
- package/dist/shared/apiTypes.js +2 -0
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +2 -0
- package/dist/shared/capabilities.js.map +1 -1
- package/dist/shared/federatedRoutes.js +28 -2
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/machineStatus.js +60 -0
- package/dist/shared/machineStatus.js.map +1 -0
- package/dist/shared/pluginApiTypes.d.ts +155 -0
- package/dist/shared/pluginBackendProtocol.js +110 -0
- package/dist/shared/pluginBackendProtocol.js.map +1 -0
- package/dist/shared/pluginIds.js +3 -0
- package/dist/shared/pluginIds.js.map +1 -1
- package/dist/shared/pluginRecoveryCommands.js +14 -0
- package/dist/shared/pluginRecoveryCommands.js.map +1 -0
- package/dist/shared/workspaceFiles.js +41 -2
- package/dist/shared/workspaceFiles.js.map +1 -1
- package/dist/shared/workspaceRemovalProtocol.js +24 -0
- package/dist/shared/workspaceRemovalProtocol.js.map +1 -0
- package/docs/config.md +12 -5
- package/docs/plugins.md +15 -21
- package/package.json +27 -8
- package/server-plugin-api.d.ts +1 -0
- package/skills/README.md +12 -0
- package/skills/relay/SKILL.md +115 -0
- package/dist/client/assets/UnifiedDiffViewer-2DACwxFY.js +0 -39
- package/dist/plugin-api/unstable.d.ts +0 -22
- package/dist/server/sessions/parentSessionLocator.js +0 -75
- package/dist/server/sessions/parentSessionLocator.js.map +0 -1
- package/dist/server/workspaces/imagePreviewService.js +0 -40
- package/dist/server/workspaces/imagePreviewService.js.map +0 -1
- package/dist/shared/apiTypes.d.ts +0 -1323
- package/dist/shared/thinkingLevels.d.ts +0 -29
- package/plugin-api/unstable.d.ts +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { realpathSync, statSync } from "node:fs";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { constants, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, getAgentDir, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, defineTool, 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";
|
|
@@ -19,10 +20,9 @@ import { createPiSessionManagerGateway } from "./piSessionManagerGateway.js";
|
|
|
19
20
|
import { SessionPinStore } from "./sessionPinStore.js";
|
|
20
21
|
import { SessionActivityCoordinator } from "./sessionActivityCoordinator.js";
|
|
21
22
|
import { searchSessionContent } from "./sessionContentSearch.js";
|
|
23
|
+
import { createLocalOnlyModelRuntime } from "./authService.js";
|
|
22
24
|
import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
|
|
23
25
|
import { readSessionHeaderSummary } from "./sessionFileHeader.js";
|
|
24
|
-
import { countOutOfListingChildren, locateOutOfListingParents } from "./parentSessionLocator.js";
|
|
25
|
-
import { siblingWorkspaceCwds } from "../workspaces/projectWorkspaceCwds.js";
|
|
26
26
|
import { workbenchAccessHandle } from "../managementEmbed.js";
|
|
27
27
|
import { DEFAULT_EXTENSION_DIALOGS_TIMEOUT_MS, piWebDataDir } from "../../config.js";
|
|
28
28
|
import { createScopedSettingsManager, resolveSettingsScopeDirectory } from "./projectSettingsScope.js";
|
|
@@ -61,6 +61,25 @@ const STARTUP_PHASE_EXTENSIONS = "正在加载会话扩展";
|
|
|
61
61
|
const STARTUP_CONCURRENT_CATALOG_REFRESH = "服务商模型列表正在刷新";
|
|
62
62
|
const MAX_UNREAD_PUBLICATION_RETRY_MS = 30_000;
|
|
63
63
|
const MAX_PENDING_UNREAD_MUTATIONS = SESSION_UNREAD_LIMIT + 1;
|
|
64
|
+
function auditVisibleAssistantMessage(message) {
|
|
65
|
+
if (!isAuditRecord(message))
|
|
66
|
+
return message;
|
|
67
|
+
const record = message;
|
|
68
|
+
const content = record["content"];
|
|
69
|
+
if (!Array.isArray(content))
|
|
70
|
+
return message;
|
|
71
|
+
return {
|
|
72
|
+
...record,
|
|
73
|
+
content: content.filter(part => (typeof part !== "object"
|
|
74
|
+
|| part === null
|
|
75
|
+
|| Array.isArray(part)
|
|
76
|
+
|| !isAuditRecord(part)
|
|
77
|
+
|| part["type"] !== "thinking")),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function isAuditRecord(value) {
|
|
81
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
82
|
+
}
|
|
64
83
|
function noop() {
|
|
65
84
|
// Intentionally empty default unsubscribe callback.
|
|
66
85
|
}
|
|
@@ -69,6 +88,9 @@ function spawnTargetError(decision) {
|
|
|
69
88
|
return new Error("派生会话不在已注册项目中");
|
|
70
89
|
return new Error(`cwd 必须是此项目的工作区。允许的路径:${decision.allowedCwds.join(", ")}`);
|
|
71
90
|
}
|
|
91
|
+
function subsessionCwdError(spawningCwd, requestedCwd) {
|
|
92
|
+
return new Error(`跟踪子会话只能在父会话的工作区中运行(${spawningCwd}),不能使用 ${requestedCwd}。如需在其它工作区运行,请使用 spawn_session 创建独立会话。`);
|
|
93
|
+
}
|
|
72
94
|
function modelSpecOf(model) {
|
|
73
95
|
return `${model.provider}/${model.id}`;
|
|
74
96
|
}
|
|
@@ -153,6 +175,8 @@ function parsePromptStreamingBehavior(value) {
|
|
|
153
175
|
throw new Error('Prompt streamingBehavior must be "steer" or "followUp"');
|
|
154
176
|
}
|
|
155
177
|
const GLOBAL_AGENT_CONTEXT_FILE_NAMES = new Set(["AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"]);
|
|
178
|
+
const MANAGED_RELAY_SKILL_DIRECTORY = join(".pi", "skills", "relay");
|
|
179
|
+
const BUNDLED_RELAY_SKILL_PATH = fileURLToPath(new URL("../../../skills/relay/SKILL.md", import.meta.url));
|
|
156
180
|
export function filterManagedGlobalContextFiles(cwd, agentDir, base) {
|
|
157
181
|
return {
|
|
158
182
|
agentsFiles: base.agentsFiles.filter((file) => {
|
|
@@ -168,34 +192,77 @@ export function filterManagedGlobalContextFiles(cwd, agentDir, base) {
|
|
|
168
192
|
}
|
|
169
193
|
export function filterManagedProjectSkills(cwd, base) {
|
|
170
194
|
const projectRoot = realpathSync(cwd);
|
|
195
|
+
const pathAllowed = (path) => pathInsideRealDirectory(projectRoot, path);
|
|
171
196
|
return {
|
|
172
197
|
...base,
|
|
173
|
-
skills: base.skills.filter((skill) =>
|
|
174
|
-
|
|
175
|
-
return pathInsideOrEqual(projectRoot, realpathSync(skill.filePath));
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
}),
|
|
198
|
+
skills: base.skills.filter((skill) => pathAllowed(skill.filePath)),
|
|
199
|
+
diagnostics: filterManagedSkillDiagnostics(base.diagnostics, pathAllowed),
|
|
181
200
|
};
|
|
182
201
|
}
|
|
202
|
+
export async function ensureManagedRelaySkill(cwd, bundledSkillPath = BUNDLED_RELAY_SKILL_PATH) {
|
|
203
|
+
const projectRoot = realpathSync(cwd);
|
|
204
|
+
const piDirectory = resolve(projectRoot, ".pi");
|
|
205
|
+
const skillsDirectory = join(piDirectory, "skills");
|
|
206
|
+
const skillDirectory = resolve(projectRoot, MANAGED_RELAY_SKILL_DIRECTORY);
|
|
207
|
+
const skillPath = join(skillDirectory, "SKILL.md");
|
|
208
|
+
for (const directory of [piDirectory, skillsDirectory, skillDirectory]) {
|
|
209
|
+
await ensureManagedSkillDirectory(projectRoot, directory);
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
await copyFile(bundledSkillPath, skillPath, constants.COPYFILE_EXCL);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (!isNodeError(error, "EEXIST"))
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
assertManagedSkillPath(projectRoot, skillPath, "file");
|
|
219
|
+
return skillPath;
|
|
220
|
+
}
|
|
183
221
|
export function filterManagedWorkbenchSkills(cwd, base, receipt) {
|
|
184
222
|
const projectRoot = realpathSync(cwd);
|
|
185
|
-
const allowedDirectories = new Set(
|
|
223
|
+
const allowedDirectories = new Set([
|
|
224
|
+
resolve(projectRoot, MANAGED_RELAY_SKILL_DIRECTORY),
|
|
225
|
+
...receipt.skills.map((skill) => resolve(projectRoot, ".pi", "skills", skill.directory)),
|
|
226
|
+
]);
|
|
227
|
+
const pathAllowed = (path) => [...allowedDirectories].some((directory) => pathInsideRealDirectory(directory, path));
|
|
186
228
|
return {
|
|
187
229
|
...base,
|
|
188
|
-
skills: base.skills.filter((skill) =>
|
|
189
|
-
|
|
190
|
-
const path = realpathSync(skill.filePath);
|
|
191
|
-
return [...allowedDirectories].some((directory) => pathInsideOrEqual(directory, path));
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
}),
|
|
230
|
+
skills: base.skills.filter((skill) => pathAllowed(skill.filePath)),
|
|
231
|
+
diagnostics: filterManagedSkillDiagnostics(base.diagnostics, pathAllowed),
|
|
197
232
|
};
|
|
198
233
|
}
|
|
234
|
+
function filterManagedSkillDiagnostics(diagnostics, pathAllowed) {
|
|
235
|
+
return diagnostics.filter((diagnostic) => diagnostic.path === undefined || pathAllowed(diagnostic.path));
|
|
236
|
+
}
|
|
237
|
+
function pathInsideRealDirectory(directory, path) {
|
|
238
|
+
try {
|
|
239
|
+
return pathInsideOrEqual(directory, realpathSync(path));
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async function ensureManagedSkillDirectory(projectRoot, path) {
|
|
246
|
+
try {
|
|
247
|
+
await mkdir(path);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
if (!isNodeError(error, "EEXIST"))
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
assertManagedSkillPath(projectRoot, path, "directory");
|
|
254
|
+
}
|
|
255
|
+
function assertManagedSkillPath(projectRoot, path, expectedType) {
|
|
256
|
+
const realPath = realpathSync(path);
|
|
257
|
+
const stats = statSync(realPath);
|
|
258
|
+
const hasExpectedType = expectedType === "directory" ? stats.isDirectory() : stats.isFile();
|
|
259
|
+
if (!pathInsideOrEqual(projectRoot, realPath) || !hasExpectedType) {
|
|
260
|
+
throw new Error(`Managed skill path is invalid: ${path}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function isNodeError(error, code) {
|
|
264
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
265
|
+
}
|
|
199
266
|
function pathInsideOrEqual(parent, child) {
|
|
200
267
|
const normalizedParent = resolve(parent);
|
|
201
268
|
const normalizedChild = resolve(child);
|
|
@@ -337,7 +404,13 @@ function createRuntimeWithContextAndOneShotSessionOptions(createRuntime, managem
|
|
|
337
404
|
});
|
|
338
405
|
};
|
|
339
406
|
}
|
|
340
|
-
|
|
407
|
+
/** Append PI WEB-owned prompt sections after the operator's Pi prompt files. */
|
|
408
|
+
export function piWebResourceLoaderOptions(appendSystemPromptSections) {
|
|
409
|
+
if (appendSystemPromptSections.length === 0)
|
|
410
|
+
return undefined;
|
|
411
|
+
return { appendSystemPromptOverride: (base) => [...base, ...appendSystemPromptSections] };
|
|
412
|
+
}
|
|
413
|
+
function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRuntime, resolveSettingsScope, spawn, subsessions, askUser, workbench, logger = noopLogger, managementAudit, managementProjectIdForCwd, appendSystemPromptSections = []) {
|
|
341
414
|
return async ({ cwd, agentDir, sessionManager, sessionStartEvent, managementContext, initialModel, initialThinkingLevel, delegationToolsEnabled = true }) => {
|
|
342
415
|
const scopedModelRuntime = managementContext === undefined ? await normalModelRuntimeForCwd(cwd) : await managementModelRuntime();
|
|
343
416
|
if (managementContext !== undefined) {
|
|
@@ -356,7 +429,14 @@ function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRu
|
|
|
356
429
|
scopeDirectory: await resolveSettingsScope(cwd, "normal"),
|
|
357
430
|
globalAgentDir: agentDir,
|
|
358
431
|
});
|
|
359
|
-
const
|
|
432
|
+
const resourceLoaderOptions = piWebResourceLoaderOptions(appendSystemPromptSections);
|
|
433
|
+
const services = await createAgentSessionServices({
|
|
434
|
+
cwd,
|
|
435
|
+
agentDir,
|
|
436
|
+
modelRuntime: scopedModelRuntime,
|
|
437
|
+
settingsManager,
|
|
438
|
+
...(resourceLoaderOptions === undefined ? {} : { resourceLoaderOptions }),
|
|
439
|
+
});
|
|
360
440
|
const customTools = createPiWebCustomToolDefinitions(cwd, delegationToolsEnabled, spawn, subsessions, askUser);
|
|
361
441
|
const result = await createAgentSessionFromServices({
|
|
362
442
|
services,
|
|
@@ -371,6 +451,7 @@ function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRu
|
|
|
371
451
|
}
|
|
372
452
|
function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spawn, subsessions, askUser, managementContext, workbench, logger, managementAudit, managementProjectIdForCwd) {
|
|
373
453
|
return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, initialThinkingLevel, delegationToolsEnabled = true }) => {
|
|
454
|
+
await ensureManagedRelaySkill(cwd);
|
|
374
455
|
const auditIdentity = await resolveManagementAuditIdentity(cwd, managementContext, managementProjectIdForCwd, logger);
|
|
375
456
|
const accessHandle = workbenchAccessHandle(managementContext);
|
|
376
457
|
const state = workbench === undefined ? undefined : workbench.accessStates.require(accessHandle);
|
|
@@ -526,7 +607,6 @@ export class PiSessionService {
|
|
|
526
607
|
/** Parent id/file identities whose persisted links have already been loaded. */
|
|
527
608
|
this.subsessionHydratedParents = new Set();
|
|
528
609
|
/** Session file path -> its parsed header. Headers are written once, so successful reads are cached. */
|
|
529
|
-
this.sessionHeaderCache = new Map();
|
|
530
610
|
/**
|
|
531
611
|
* Tracked subsession id -> whether a completion notification is armed.
|
|
532
612
|
* Armed when the child starts working; firing on completion disarms it so a
|
|
@@ -539,20 +619,6 @@ export class PiSessionService {
|
|
|
539
619
|
this.pendingUnreadMutations = [];
|
|
540
620
|
this.unreadPublicationFlushRequested = false;
|
|
541
621
|
this.unreadPublicationStopped = false;
|
|
542
|
-
/**
|
|
543
|
-
* Read a session file header, memoized per path. Pi writes the header once at
|
|
544
|
-
* session creation, so a successful read stays valid for the process lifetime;
|
|
545
|
-
* failures are not cached so a session file that appears later is picked up.
|
|
546
|
-
*/
|
|
547
|
-
this.readCachedSessionHeader = async (sessionFile) => {
|
|
548
|
-
const cached = this.sessionHeaderCache.get(sessionFile);
|
|
549
|
-
if (cached !== undefined)
|
|
550
|
-
return cached;
|
|
551
|
-
const header = await readSessionHeaderSummary(sessionFile);
|
|
552
|
-
if (header !== undefined)
|
|
553
|
-
this.sessionHeaderCache.set(sessionFile, header);
|
|
554
|
-
return header;
|
|
555
|
-
};
|
|
556
622
|
this.archiveStore = deps.archiveStore ?? new SessionArchiveStore();
|
|
557
623
|
this.pinStore = deps.pinStore ?? new SessionPinStore(join(deps.dataDir ?? piWebDataDir(), "session-pins.json"));
|
|
558
624
|
this.agentDir = deps.agentDir ?? getAgentDir();
|
|
@@ -565,7 +631,7 @@ export class PiSessionService {
|
|
|
565
631
|
});
|
|
566
632
|
let defaultModelRuntime;
|
|
567
633
|
const resolveDefaultModelRuntime = () => {
|
|
568
|
-
defaultModelRuntime ??= deps.modelRuntime === undefined ?
|
|
634
|
+
defaultModelRuntime ??= deps.modelRuntime === undefined ? createLocalOnlyModelRuntime() : Promise.resolve(deps.modelRuntime);
|
|
569
635
|
return defaultModelRuntime;
|
|
570
636
|
};
|
|
571
637
|
this.normalModelRuntimeForCwd = deps.normalModelRuntimeForCwd ?? resolveDefaultModelRuntime;
|
|
@@ -574,7 +640,6 @@ export class PiSessionService {
|
|
|
574
640
|
? resolveDefaultModelRuntime
|
|
575
641
|
: () => Promise.resolve(managementModelRuntime);
|
|
576
642
|
this.spawnTargets = deps.spawnTargets;
|
|
577
|
-
this.projectWorkspaces = deps.projectWorkspaces;
|
|
578
643
|
this.logger = deps.logger ?? noopLogger;
|
|
579
644
|
this.normalToolAudit = deps.normalToolAudit;
|
|
580
645
|
this.managementAudit = deps.managementAudit;
|
|
@@ -582,13 +647,14 @@ export class PiSessionService {
|
|
|
582
647
|
this.now = deps.now ?? (() => new Date());
|
|
583
648
|
this.notificationStore = deps.notificationStore ?? new SessionNotificationStore();
|
|
584
649
|
this.unreadStore = deps.unreadStore ?? new SessionUnreadStore();
|
|
650
|
+
this.onUnreadChanged = deps.onUnreadChanged;
|
|
585
651
|
this.pendingAskStore = deps.pendingAskStore ?? new PendingAskStore();
|
|
586
652
|
this.pendingExtensionDialogStore = deps.pendingExtensionDialogStore ?? new PendingExtensionDialogStore();
|
|
587
653
|
this.extensionDialogsTimeoutMs = deps.extensionDialogsTimeoutMs ?? DEFAULT_EXTENSION_DIALOGS_TIMEOUT_MS;
|
|
588
654
|
this.catalogRefreshStatus = deps.catalogRefreshStatus;
|
|
589
655
|
this.unreadPublicationRetryInitialMs = Math.max(0, deps.unreadPublicationRetryDelayMs ?? DEFAULT_UNREAD_PUBLICATION_RETRY_MS);
|
|
590
656
|
this.unreadPublicationRetryDelayMs = this.unreadPublicationRetryInitialMs;
|
|
591
|
-
// Subsessions are
|
|
657
|
+
// Subsessions are gated behind their own flag, and they
|
|
592
658
|
// also require the spawn capability (they share its project-scope resolver).
|
|
593
659
|
const subsessionsActive = this.spawnTargets !== undefined && deps.subsessionsEnabled === true;
|
|
594
660
|
this.createRuntime = deps.createRuntime ?? createDefaultRuntimeFactory(this.normalModelRuntimeForCwd, this.managementModelRuntime, (cwd, mode) => this.resolveSettingsScopeDirectory(cwd, mode), this.spawnTargets === undefined ? undefined : (input) => this.spawnSession(input), !subsessionsActive ? undefined : {
|
|
@@ -596,7 +662,7 @@ export class PiSessionService {
|
|
|
596
662
|
list: (parentSessionId, parentSessionFile) => this.listSubsessions(parentSessionId, parentSessionFile),
|
|
597
663
|
check: (parentSessionId, sessionId, parentSessionFile) => this.checkSubsession(parentSessionId, sessionId, parentSessionFile),
|
|
598
664
|
read: (parentSessionId, sessionId, query, parentSessionFile) => this.readSubsession(parentSessionId, sessionId, query, parentSessionFile),
|
|
599
|
-
}, deps.askUserEnabled === true ? { open: (input) => this.openAsk(input) } : undefined, deps.workbench, this.logger, this.managementAudit, this.managementProjectIdForCwd);
|
|
665
|
+
}, deps.askUserEnabled === true ? { open: (input) => this.openAsk(input) } : undefined, deps.workbench, this.logger, this.managementAudit, this.managementProjectIdForCwd, deps.appendSystemPromptSections ?? []);
|
|
600
666
|
this.createAgentRuntime = deps.createAgentRuntime ?? defaultCreateAgentRuntime;
|
|
601
667
|
this.workspaceActivity = deps.workspaceActivity;
|
|
602
668
|
this.heartbeat = setInterval(() => { this.publishHeartbeats(); }, deps.heartbeatIntervalMs ?? 2000);
|
|
@@ -709,7 +775,6 @@ export class PiSessionService {
|
|
|
709
775
|
this.subsessionLinks.clear();
|
|
710
776
|
this.subsessionHydratedParents.clear();
|
|
711
777
|
this.subsessionNotifyArmed.clear();
|
|
712
|
-
this.sessionHeaderCache.clear();
|
|
713
778
|
this.notificationStore.clearAll("service-dispose");
|
|
714
779
|
await Promise.all(activeSessions.map(async (active) => {
|
|
715
780
|
active.unsubscribe();
|
|
@@ -788,67 +853,11 @@ export class PiSessionService {
|
|
|
788
853
|
.map((record) => clientSessionFromArchivedRecord(record, sessionsById.get(record.sessionId)))
|
|
789
854
|
.filter(isDefined);
|
|
790
855
|
return {
|
|
791
|
-
sessions:
|
|
856
|
+
sessions: [...unarchivedSessions, ...archivedSessions],
|
|
792
857
|
entriesById: new Map(sessions.map((session) => [session.id, session])),
|
|
793
858
|
archivedById,
|
|
794
859
|
};
|
|
795
860
|
}
|
|
796
|
-
/**
|
|
797
|
-
* Annotate a listing with the session relationships that cross workspace
|
|
798
|
-
* boundaries: where an out-of-listing parent lives, and how many children a
|
|
799
|
-
* listed session has in sibling workspaces.
|
|
800
|
-
*
|
|
801
|
-
* Both directions are best-effort. An unreadable parent header or a sibling
|
|
802
|
-
* workspace that cannot be listed leaves the session unannotated rather than
|
|
803
|
-
* failing the listing, and the browser falls back to its generic states.
|
|
804
|
-
*/
|
|
805
|
-
async withRelatedSessionsElsewhere(sessions, cwd) {
|
|
806
|
-
const [parentLocations, childCounts] = await Promise.all([
|
|
807
|
-
locateOutOfListingParents(sessions, cwd, this.readCachedSessionHeader),
|
|
808
|
-
this.countChildrenInSiblingWorkspaces(sessions, cwd),
|
|
809
|
-
]);
|
|
810
|
-
return sessions.map((session) => {
|
|
811
|
-
const parent = session.parentSessionPath === undefined ? undefined : parentLocations.get(session.parentSessionPath);
|
|
812
|
-
const childrenElsewhere = childCounts.get(session.path);
|
|
813
|
-
if (parent === undefined && childrenElsewhere === undefined)
|
|
814
|
-
return session;
|
|
815
|
-
const annotated = { ...session };
|
|
816
|
-
if (parent !== undefined) {
|
|
817
|
-
annotated.parentSessionId = parent.parentSessionId;
|
|
818
|
-
annotated.parentSessionCwd = parent.parentSessionCwd;
|
|
819
|
-
}
|
|
820
|
-
if (childrenElsewhere !== undefined)
|
|
821
|
-
annotated.childSessionsElsewhere = childrenElsewhere;
|
|
822
|
-
return annotated;
|
|
823
|
-
});
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* Count children of the listed sessions that live in other workspaces of the
|
|
827
|
-
* same project.
|
|
828
|
-
*
|
|
829
|
-
* Only sibling workspaces are scanned: agents may only spawn into workspaces
|
|
830
|
-
* of the spawning session's own project, so that bounds where a child can be.
|
|
831
|
-
* Listing is skipped entirely when no project-workspace locator is configured
|
|
832
|
-
* or the cwd belongs to no registered project.
|
|
833
|
-
*/
|
|
834
|
-
async countChildrenInSiblingWorkspaces(sessions, cwd) {
|
|
835
|
-
if (this.projectWorkspaces === undefined || sessions.length === 0)
|
|
836
|
-
return new Map();
|
|
837
|
-
try {
|
|
838
|
-
const siblingCwds = await siblingWorkspaceCwds(this.projectWorkspaces, cwd);
|
|
839
|
-
if (siblingCwds.length === 0)
|
|
840
|
-
return new Map();
|
|
841
|
-
const listings = await Promise.all(siblingCwds.map(async (siblingCwd) => {
|
|
842
|
-
const entries = await this.sessionManager.list(siblingCwd);
|
|
843
|
-
return entries.flatMap((entry) => entry.parentSessionPath === undefined ? [] : [entry.parentSessionPath]);
|
|
844
|
-
}));
|
|
845
|
-
return countOutOfListingChildren(sessions, listings.flat());
|
|
846
|
-
}
|
|
847
|
-
catch (error) {
|
|
848
|
-
this.logger.info({ cwd, error: error instanceof Error ? error.message : String(error) }, "failed to count child sessions in sibling workspaces");
|
|
849
|
-
return new Map();
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
861
|
async start(cwd, options = {}) {
|
|
853
862
|
if (options.managementContext === undefined)
|
|
854
863
|
await this.normalModelRuntimeForCwd(cwd);
|
|
@@ -890,7 +899,7 @@ export class PiSessionService {
|
|
|
890
899
|
async spawnSession(input) {
|
|
891
900
|
if (this.spawnTargets === undefined)
|
|
892
901
|
throw new Error("派生会话已禁用");
|
|
893
|
-
const decision = await this.spawnTargets.resolveSpawnTarget(input.spawningCwd, input.cwd);
|
|
902
|
+
const decision = await this.spawnTargets.resolveSpawnTarget(input.spawningCwd, input.cwd, eventScopeFromManagementContext(input.managementContext));
|
|
894
903
|
if (!decision.allowed)
|
|
895
904
|
throw spawnTargetError(decision);
|
|
896
905
|
// A model spec overrides the inherited model. Only a spec triggers a
|
|
@@ -915,16 +924,14 @@ export class PiSessionService {
|
|
|
915
924
|
...(modelUsed === undefined ? {} : { model: modelSpecOf(modelUsed) }),
|
|
916
925
|
};
|
|
917
926
|
}
|
|
918
|
-
/**
|
|
919
|
-
* Start a *tracked* child session on behalf of a LLM. Identical to
|
|
920
|
-
* {@link spawnSession} in how the target cwd is resolved, but the child
|
|
921
|
-
* records its parent (so it shows in the session tree) and is registered so
|
|
922
|
-
* the parent is notified when it stops working and can inspect it later.
|
|
923
|
-
*/
|
|
927
|
+
/** Start a tracked child in the parent's workspace so it remains visible in the same session tree. */
|
|
924
928
|
async spawnSubsession(input) {
|
|
925
929
|
if (this.spawnTargets === undefined)
|
|
926
930
|
throw new Error("派生会话已禁用");
|
|
927
|
-
|
|
931
|
+
if (input.cwd !== undefined && input.cwd !== "" && !cwdPathsEqual(input.cwd, input.spawningCwd)) {
|
|
932
|
+
throw subsessionCwdError(input.spawningCwd, input.cwd);
|
|
933
|
+
}
|
|
934
|
+
const decision = await this.spawnTargets.resolveSpawnTarget(input.spawningCwd, undefined, eventScopeFromManagementContext(input.managementContext));
|
|
928
935
|
if (!decision.allowed)
|
|
929
936
|
throw spawnTargetError(decision);
|
|
930
937
|
// A model spec overrides the inherited model and is resolved against the
|
|
@@ -967,7 +974,9 @@ export class PiSessionService {
|
|
|
967
974
|
* first so callers see newly configured providers and models.
|
|
968
975
|
*/
|
|
969
976
|
async sessionModelCandidates(session) {
|
|
970
|
-
|
|
977
|
+
// Pi 0.84 keeps request paths local-only; the bounded catalog refresher is
|
|
978
|
+
// the only path allowed to fetch provider catalogs over the network.
|
|
979
|
+
await session.modelRuntime.refresh({ allowNetwork: false });
|
|
971
980
|
return session.scopedModels.length > 0
|
|
972
981
|
? session.scopedModels.map((scoped) => scoped.model)
|
|
973
982
|
: await session.modelRuntime.getAvailable();
|
|
@@ -1018,9 +1027,9 @@ export class PiSessionService {
|
|
|
1018
1027
|
* work (`deliverAs: "followUp"`), which is how the run that `ask_user`
|
|
1019
1028
|
* terminated continues.
|
|
1020
1029
|
*/
|
|
1021
|
-
async submitAsk(ref, askId, submission) {
|
|
1030
|
+
async submitAsk(ref, askId, submission, managementContext) {
|
|
1022
1031
|
await this.assertWritable(ref);
|
|
1023
|
-
const session = await this.getOrOpen(ref);
|
|
1032
|
+
const session = await this.getOrOpen(ref, managementContext);
|
|
1024
1033
|
// Checked before the store closes the ask so a refused delivery cannot
|
|
1025
1034
|
// discard answers the user already submitted.
|
|
1026
1035
|
this.assertTreeNavigationInactive(session, "answer questions");
|
|
@@ -1031,9 +1040,9 @@ export class PiSessionService {
|
|
|
1031
1040
|
* question as unanswered: it was promised a follow-up message and would
|
|
1032
1041
|
* otherwise wait for one that never comes.
|
|
1033
1042
|
*/
|
|
1034
|
-
async cancelAsk(ref, askId) {
|
|
1043
|
+
async cancelAsk(ref, askId, managementContext) {
|
|
1035
1044
|
await this.assertWritable(ref);
|
|
1036
|
-
const session = await this.getOrOpen(ref);
|
|
1045
|
+
const session = await this.getOrOpen(ref, managementContext);
|
|
1037
1046
|
this.assertTreeNavigationInactive(session, "dismiss questions");
|
|
1038
1047
|
return this.closeAsk(session, this.pendingAskStore.cancel(session.sessionId, askId));
|
|
1039
1048
|
}
|
|
@@ -1657,6 +1666,7 @@ export class PiSessionService {
|
|
|
1657
1666
|
void this.submitPrompt(session, promptText, behavior, images, echoUserMessage);
|
|
1658
1667
|
}
|
|
1659
1668
|
submitPrompt(session, text, behavior, images = [], echoUserMessage = true) {
|
|
1669
|
+
this.auditUserPrompt(session, text);
|
|
1660
1670
|
this.publishActivity(session, behavior === "steer" ? "插队消息已排队" : behavior === "followUp" ? "消息已排队" : "消息已接收", "active");
|
|
1661
1671
|
const eventScope = this.eventScopeForSession(session);
|
|
1662
1672
|
if (behavior === undefined && echoUserMessage)
|
|
@@ -1784,6 +1794,73 @@ export class PiSessionService {
|
|
|
1784
1794
|
}
|
|
1785
1795
|
}
|
|
1786
1796
|
}
|
|
1797
|
+
async forkFromTree(ref, request, managementContext) {
|
|
1798
|
+
if (request.entryId.trim() === "")
|
|
1799
|
+
throw new Error("Session tree entry is required");
|
|
1800
|
+
const requestedSessionId = sessionIdFromLookup(ref);
|
|
1801
|
+
if (this.isTreeExclusiveSessionIdentityActive(requestedSessionId)) {
|
|
1802
|
+
throw new Error("Stop current session activity before forking the session tree");
|
|
1803
|
+
}
|
|
1804
|
+
await this.assertWritable(ref);
|
|
1805
|
+
const session = await this.getOrOpen(ref, managementContext);
|
|
1806
|
+
if (this.hasActiveWork(session))
|
|
1807
|
+
throw new Error("Stop current session activity before forking the session tree");
|
|
1808
|
+
const sourceSessionId = session.sessionId;
|
|
1809
|
+
const sourceEventScope = this.eventScopeForSession(session);
|
|
1810
|
+
const sourceCwd = session.sessionManager.getCwd();
|
|
1811
|
+
this.publishActivity(session, "正在从会话树条目分叉", "active");
|
|
1812
|
+
this.publishStatus(session);
|
|
1813
|
+
try {
|
|
1814
|
+
const result = await this.commandService.forkEntry(session.sessionId, request.entryId, sourceEventScope, {
|
|
1815
|
+
expectedLeafId: request.expectedLeafId,
|
|
1816
|
+
});
|
|
1817
|
+
if (result.type === "unsupported")
|
|
1818
|
+
throw new Error(result.message);
|
|
1819
|
+
if (result.type !== "done")
|
|
1820
|
+
throw new Error("Session fork is unavailable");
|
|
1821
|
+
if (result.session === undefined) {
|
|
1822
|
+
if (this.isCurrentActiveSession(session)) {
|
|
1823
|
+
this.publishActivity(session, "会话分叉已取消", "idle");
|
|
1824
|
+
this.publishStatus(session);
|
|
1825
|
+
}
|
|
1826
|
+
return { cancelled: true };
|
|
1827
|
+
}
|
|
1828
|
+
const forked = this.activeForLookup({ id: result.session.id, cwd: result.session.cwd }, managementContext)?.runtime.session;
|
|
1829
|
+
if (forked !== undefined && this.isCurrentActiveSession(forked)) {
|
|
1830
|
+
this.publishActivity(forked, "会话已分叉", "idle");
|
|
1831
|
+
this.publishStatus(forked);
|
|
1832
|
+
}
|
|
1833
|
+
if (result.session.id !== sourceSessionId)
|
|
1834
|
+
this.clearSupersededSessionActivity(sourceSessionId, sourceCwd, sourceEventScope);
|
|
1835
|
+
return {
|
|
1836
|
+
cancelled: false,
|
|
1837
|
+
session: result.session,
|
|
1838
|
+
...(result.promptDraft === undefined ? {} : { promptDraft: result.promptDraft }),
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
catch (error) {
|
|
1842
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1843
|
+
if (this.isCurrentActiveSession(session)) {
|
|
1844
|
+
this.publishActivity(session, "会话分叉失败", "error", message);
|
|
1845
|
+
this.events.publish(session.sessionId, { type: "session.error", message }, sourceEventScope);
|
|
1846
|
+
this.publishStatus(session);
|
|
1847
|
+
}
|
|
1848
|
+
throw error;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
clearSupersededSessionActivity(sessionId, cwd, eventScope) {
|
|
1852
|
+
const sessionKey = activeSessionKey(sessionId, eventScope);
|
|
1853
|
+
if (this.activities.get(sessionKey)?.phase === "active") {
|
|
1854
|
+
const at = new Date().toISOString();
|
|
1855
|
+
const stored = { phase: "idle", label: "空闲", at };
|
|
1856
|
+
this.activities.set(sessionKey, stored);
|
|
1857
|
+
const activity = { sessionId, ...stored };
|
|
1858
|
+
this.events.publish(sessionId, { type: "activity.update", activity }, eventScope);
|
|
1859
|
+
this.events.publishGlobal({ type: "activity.update", activity }, eventScope);
|
|
1860
|
+
}
|
|
1861
|
+
this.workspaceActivity?.removeSession(sessionId, cwd, eventScope);
|
|
1862
|
+
this.activities.delete(sessionKey);
|
|
1863
|
+
}
|
|
1787
1864
|
async archive(ref, managementContext) {
|
|
1788
1865
|
const active = await this.getActive(ref, managementContext);
|
|
1789
1866
|
const session = active.runtime.session;
|
|
@@ -2023,6 +2100,7 @@ export class PiSessionService {
|
|
|
2023
2100
|
if (sessionFile === undefined || sessionFile === "")
|
|
2024
2101
|
throw new Error("会话尚未持久化");
|
|
2025
2102
|
await clearParentSession(sessionFile);
|
|
2103
|
+
this.sessionManager.invalidateSessionFile?.(sessionFile);
|
|
2026
2104
|
clearParentSessionHeader(session.sessionManager);
|
|
2027
2105
|
this.unregisterSubsession(session.sessionId);
|
|
2028
2106
|
await this.forgetUnreadSessions([{ sessionId: session.sessionId, cwd: session.sessionManager.getCwd() }]);
|
|
@@ -2365,8 +2443,10 @@ export class PiSessionService {
|
|
|
2365
2443
|
return this.openPending(archived.sessionId, archived.cwd, managementContext, () => this.sessionManager.open(archivePath), { notifications: "disabled" });
|
|
2366
2444
|
}
|
|
2367
2445
|
const match = isPiSessionRef(ref)
|
|
2368
|
-
?
|
|
2369
|
-
|
|
2446
|
+
? this.sessionManager.resolveSessionFile === undefined
|
|
2447
|
+
? findSessionByIdOrPrefix(await this.sessionManager.list(ref.cwd), ref.id)
|
|
2448
|
+
: await this.sessionManager.resolveSessionFile(ref.cwd, ref.id)
|
|
2449
|
+
: findSessionByIdOrPrefix(await this.sessionManager.listAll?.() ?? [], ref);
|
|
2370
2450
|
if (!match)
|
|
2371
2451
|
throw new Error("未找到会话");
|
|
2372
2452
|
return this.openPending(match.id, match.cwd, managementContext, () => this.sessionManager.open(match.path));
|
|
@@ -2494,6 +2574,7 @@ export class PiSessionService {
|
|
|
2494
2574
|
active.unsubscribe = session.subscribe((event) => {
|
|
2495
2575
|
this.events.publish(session.sessionId, toClientEvent(event, session.thinkingLevel), active.eventScope);
|
|
2496
2576
|
this.auditToolExecutionEvent(active, event);
|
|
2577
|
+
this.auditAssistantResponseEvent(active, event);
|
|
2497
2578
|
this.publishActivityForEvent(session, event);
|
|
2498
2579
|
const eventType = getString(event, "type");
|
|
2499
2580
|
if (eventType === "agent_end")
|
|
@@ -2842,7 +2923,12 @@ export class PiSessionService {
|
|
|
2842
2923
|
}, "Pi tool execution audit");
|
|
2843
2924
|
if (managementIdentity !== undefined) {
|
|
2844
2925
|
try {
|
|
2845
|
-
this.managementAudit?.record({
|
|
2926
|
+
this.managementAudit?.record({
|
|
2927
|
+
action: "tool_execution",
|
|
2928
|
+
...managementIdentity,
|
|
2929
|
+
...auditEvent,
|
|
2930
|
+
content: isEnd ? getProperty(event, "result") : getProperty(event, "args"),
|
|
2931
|
+
});
|
|
2846
2932
|
}
|
|
2847
2933
|
catch (error) {
|
|
2848
2934
|
this.logger.info({ sessionId: auditEvent.sessionId, toolCallId: auditEvent.toolCallId, error: error instanceof Error ? error.message : String(error) }, "failed to enqueue management-mode tool audit");
|
|
@@ -2862,6 +2948,43 @@ export class PiSessionService {
|
|
|
2862
2948
|
}, "failed to persist ordinary-mode tool audit");
|
|
2863
2949
|
}
|
|
2864
2950
|
}
|
|
2951
|
+
auditAssistantResponseEvent(active, event) {
|
|
2952
|
+
if (getString(event, "type") !== "message_end")
|
|
2953
|
+
return;
|
|
2954
|
+
const message = getProperty(event, "message");
|
|
2955
|
+
if (getString(message, "role") !== "assistant")
|
|
2956
|
+
return;
|
|
2957
|
+
this.recordManagementContentAudit(active, "assistant_response", auditVisibleAssistantMessage(message));
|
|
2958
|
+
}
|
|
2959
|
+
auditUserPrompt(session, text) {
|
|
2960
|
+
const active = [...new Set(this.active.values())]
|
|
2961
|
+
.find(candidate => candidate.runtime.session === session);
|
|
2962
|
+
if (active === undefined)
|
|
2963
|
+
return;
|
|
2964
|
+
this.recordManagementContentAudit(active, "user_prompt", text);
|
|
2965
|
+
}
|
|
2966
|
+
recordManagementContentAudit(active, action, content) {
|
|
2967
|
+
const identity = active.managementAuditIdentity;
|
|
2968
|
+
if (identity === undefined || this.managementAudit === undefined)
|
|
2969
|
+
return;
|
|
2970
|
+
try {
|
|
2971
|
+
this.managementAudit.record({
|
|
2972
|
+
action,
|
|
2973
|
+
status: "completed",
|
|
2974
|
+
...identity,
|
|
2975
|
+
sessionId: active.runtime.session.sessionId,
|
|
2976
|
+
cwd: active.runtime.cwd,
|
|
2977
|
+
content,
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2980
|
+
catch (error) {
|
|
2981
|
+
this.logger.info({
|
|
2982
|
+
sessionId: active.runtime.session.sessionId,
|
|
2983
|
+
action,
|
|
2984
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2985
|
+
}, "failed to enqueue management-mode content audit");
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2865
2988
|
/**
|
|
2866
2989
|
* Build the reporter for one session construction.
|
|
2867
2990
|
*
|
|
@@ -3027,6 +3150,16 @@ export class PiSessionService {
|
|
|
3027
3150
|
await this.publishUnreadMutations([]);
|
|
3028
3151
|
return this.unreadStore.durableCatalogSnapshot();
|
|
3029
3152
|
}
|
|
3153
|
+
/** Scope-filtered unread view used only by the daemon-owned status tree. */
|
|
3154
|
+
async unreadCatalogForScope(eventScope) {
|
|
3155
|
+
const catalog = await this.unreadCatalog();
|
|
3156
|
+
if (eventScope === NORMAL_SESSION_EVENT_SCOPE)
|
|
3157
|
+
return catalog;
|
|
3158
|
+
const visible = new Set([...this.active.values()]
|
|
3159
|
+
.filter((active) => active.eventScope === eventScope)
|
|
3160
|
+
.map((active) => active.runtime.session.sessionId));
|
|
3161
|
+
return { ...catalog, sessions: catalog.sessions.filter((session) => visible.has(session.sessionId)) };
|
|
3162
|
+
}
|
|
3030
3163
|
async acknowledgeUnread(sessionId, request) {
|
|
3031
3164
|
const result = this.unreadStore.acknowledge(sessionId, {
|
|
3032
3165
|
...request,
|
|
@@ -3330,6 +3463,17 @@ export class PiSessionService {
|
|
|
3330
3463
|
void this.publishUnreadMutations(mutations).catch(() => undefined);
|
|
3331
3464
|
}
|
|
3332
3465
|
publishUnreadMutations(mutations) {
|
|
3466
|
+
if (mutations.length > 0) {
|
|
3467
|
+
const scopes = new Set();
|
|
3468
|
+
for (const mutation of mutations) {
|
|
3469
|
+
const activeScopes = this.activeSessionsForId(mutation.event.sessionId).map((active) => active.eventScope);
|
|
3470
|
+
if (activeScopes.length === 0)
|
|
3471
|
+
scopes.add(NORMAL_SESSION_EVENT_SCOPE);
|
|
3472
|
+
else
|
|
3473
|
+
activeScopes.forEach((scope) => scopes.add(scope));
|
|
3474
|
+
}
|
|
3475
|
+
scopes.forEach((scope) => this.onUnreadChanged?.(scope));
|
|
3476
|
+
}
|
|
3333
3477
|
this.enqueueUnreadMutations(mutations);
|
|
3334
3478
|
this.unreadPublicationFlushRequested = true;
|
|
3335
3479
|
if (this.unreadPublication === undefined && this.unreadPublicationRetryTimer !== undefined) {
|
|
@@ -3820,6 +3964,11 @@ async function resolveManagementAuditIdentity(cwd, context, resolveProjectId, lo
|
|
|
3820
3964
|
logger.info({ userId: context.user.id, cwd, error: error instanceof Error ? error.message : String(error) }, "failed to resolve management audit project; using the authorized default");
|
|
3821
3965
|
}
|
|
3822
3966
|
}
|
|
3823
|
-
return {
|
|
3967
|
+
return {
|
|
3968
|
+
userId: context.user.id,
|
|
3969
|
+
rootUserId: context.user.rootUserId,
|
|
3970
|
+
...(context.user.displayName === undefined ? {} : { userDisplayName: context.user.displayName }),
|
|
3971
|
+
projectId,
|
|
3972
|
+
};
|
|
3824
3973
|
}
|
|
3825
3974
|
//# sourceMappingURL=piSessionService.js.map
|