@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.
Files changed (199) hide show
  1. package/README.md +2 -0
  2. package/dist/cli.js +123 -72
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client/assets/{CodeViewer-CWKEeX0r.js → CodeViewer-DCl56K0Z.js} +2 -2
  5. package/dist/client/assets/{TerminalPanel-BmA7Ayzk.js → TerminalPanel-DCvoEjUF.js} +6 -6
  6. package/dist/client/assets/{index-D6j67Izj.js → index-CMvms5cF.js} +534 -512
  7. package/dist/client/index.html +1 -1
  8. package/dist/config.js +39 -18
  9. package/dist/config.js.map +1 -1
  10. package/dist/nativeServices/serviceAction.js +128 -0
  11. package/dist/nativeServices/serviceAction.js.map +1 -0
  12. package/dist/pi-web-plugins/git/browser/git-contract.js +108 -0
  13. package/dist/pi-web-plugins/git/browser/git-panel.js +609 -0
  14. package/dist/pi-web-plugins/git/browser/gitFileList.js +43 -0
  15. package/dist/pi-web-plugins/git/browser/gitFileShared.js +16 -0
  16. package/dist/pi-web-plugins/git/browser/gitFileTree.js +81 -0
  17. package/dist/pi-web-plugins/git/browser/gitFileViewPreference.js +35 -0
  18. package/dist/pi-web-plugins/git/browser/gitRoute.js +42 -0
  19. package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +10 -0
  20. package/dist/pi-web-plugins/git/browser/unifiedDiff.js +301 -0
  21. package/dist/pi-web-plugins/git/git-backend.js +433 -0
  22. package/dist/pi-web-plugins/git/package.json +15 -0
  23. package/dist/pi-web-plugins/git/server-plugin.js +176 -0
  24. package/dist/pi-web-plugins/info/infoInternals.js +1 -1
  25. package/dist/pi-web-plugins/info/pi-web-plugin.js +2 -2
  26. package/dist/pi-web-plugins/relays/pi-web-plugin.js +1 -1
  27. package/dist/pi-web-plugins/relays/relayDiscovery.js +128 -23
  28. package/dist/pi-web-plugins/relays/relaysPanelElement.js +138 -16
  29. package/dist/pi-web-plugins/updates/pi-web-plugin.js +1 -1
  30. package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +1 -1
  31. package/dist/piWebVersionReport.js +63 -4
  32. package/dist/piWebVersionReport.js.map +1 -1
  33. package/dist/plugin-api.d.ts +40 -18
  34. package/dist/pluginRecoveryCli.js +172 -0
  35. package/dist/pluginRecoveryCli.js.map +1 -0
  36. package/dist/server/activity/workspaceActivityService.js +7 -1
  37. package/dist/server/activity/workspaceActivityService.js.map +1 -1
  38. package/dist/server/app.js +49 -23
  39. package/dist/server/app.js.map +1 -1
  40. package/dist/server/audit/managementAuditStore.js +4 -2
  41. package/dist/server/audit/managementAuditStore.js.map +1 -1
  42. package/dist/server/configRoutes.js +4 -5
  43. package/dist/server/configRoutes.js.map +1 -1
  44. package/dist/server/machines/machineClient.js +23 -4
  45. package/dist/server/machines/machineClient.js.map +1 -1
  46. package/dist/server/machines/machineProxyRoutes.js +162 -8
  47. package/dist/server/machines/machineProxyRoutes.js.map +1 -1
  48. package/dist/server/managementEmbed.js +6 -0
  49. package/dist/server/managementEmbed.js.map +1 -1
  50. package/dist/server/piWebPluginCatalog.js +441 -0
  51. package/dist/server/piWebPluginCatalog.js.map +1 -0
  52. package/dist/server/piWebPluginLifecycle.js +162 -0
  53. package/dist/server/piWebPluginLifecycle.js.map +1 -0
  54. package/dist/server/piWebPluginService.js +55 -7
  55. package/dist/server/piWebPluginService.js.map +1 -1
  56. package/dist/server/plugins/pluginBackendProxyRoutes.js +80 -0
  57. package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -0
  58. package/dist/server/plugins/serverPluginExec.js +184 -0
  59. package/dist/server/plugins/serverPluginExec.js.map +1 -0
  60. package/dist/server/plugins/serverPluginRuntime.js +480 -0
  61. package/dist/server/plugins/serverPluginRuntime.js.map +1 -0
  62. package/dist/server/realtime/sessionEventHub.js +13 -0
  63. package/dist/server/realtime/sessionEventHub.js.map +1 -1
  64. package/dist/server/realtime/sessionEventScope.js +8 -1
  65. package/dist/server/realtime/sessionEventScope.js.map +1 -1
  66. package/dist/server/requestCancellation.js +28 -0
  67. package/dist/server/requestCancellation.js.map +1 -0
  68. package/dist/server/sessiond/agentProcessEnvironment.js +61 -0
  69. package/dist/server/sessiond/agentProcessEnvironment.js.map +1 -0
  70. package/dist/server/sessiond/managementProjectResolver.js +16 -0
  71. package/dist/server/sessiond/managementProjectResolver.js.map +1 -0
  72. package/dist/server/sessiond/pluginBackendRoutes.js +78 -0
  73. package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -0
  74. package/dist/server/sessiond/sessionProxyRoutes.js +1 -0
  75. package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
  76. package/dist/server/sessiond/sessionServiceDependencies.js +0 -1
  77. package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
  78. package/dist/server/sessiond/sessiondStateOwnership.js +139 -0
  79. package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -0
  80. package/dist/server/sessiond/workspaceCatalogRoutes.js +38 -0
  81. package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -0
  82. package/dist/server/sessiond/workspaceRemovalRoutes.js +56 -0
  83. package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -0
  84. package/dist/server/sessiond.js +98 -11
  85. package/dist/server/sessiond.js.map +1 -1
  86. package/dist/server/sessions/authService.js +15 -51
  87. package/dist/server/sessions/authService.js.map +1 -1
  88. package/dist/server/sessions/dockerEnvironmentFacts.js +181 -0
  89. package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -0
  90. package/dist/server/sessions/managementAgentTools.js +12 -4
  91. package/dist/server/sessions/managementAgentTools.js.map +1 -1
  92. package/dist/server/sessions/managementSandbox.js +5 -5
  93. package/dist/server/sessions/managementSandbox.js.map +1 -1
  94. package/dist/server/sessions/piSessionManagerGateway.js +129 -2
  95. package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
  96. package/dist/server/sessions/piSessionService.js +268 -119
  97. package/dist/server/sessions/piSessionService.js.map +1 -1
  98. package/dist/server/sessions/sessionCommandService.js +15 -1
  99. package/dist/server/sessions/sessionCommandService.js.map +1 -1
  100. package/dist/server/sessions/sessionEnvironmentFacts.js +48 -0
  101. package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -0
  102. package/dist/server/sessions/sessionFileFormat.js +27 -0
  103. package/dist/server/sessions/sessionFileFormat.js.map +1 -0
  104. package/dist/server/sessions/sessionFileHeader.js +82 -23
  105. package/dist/server/sessions/sessionFileHeader.js.map +1 -1
  106. package/dist/server/sessions/sessionRoutes.js +16 -2
  107. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  108. package/dist/server/sessions/sessionSummaryScanner.js +491 -0
  109. package/dist/server/sessions/sessionSummaryScanner.js.map +1 -0
  110. package/dist/server/sessions/spawnSubsessionTool.js +4 -8
  111. package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
  112. package/dist/server/sessions/spawnTargetResolver.js +2 -2
  113. package/dist/server/sessions/spawnTargetResolver.js.map +1 -1
  114. package/dist/server/status/machineStatusRoutes.js +10 -0
  115. package/dist/server/status/machineStatusRoutes.js.map +1 -0
  116. package/dist/server/status/machineStatusService.js +115 -0
  117. package/dist/server/status/machineStatusService.js.map +1 -0
  118. package/dist/server/status/workspaceAttribution.js +71 -0
  119. package/dist/server/status/workspaceAttribution.js.map +1 -0
  120. package/dist/server/terminalProxyRoutes.js.map +1 -1
  121. package/dist/server/workbench/skillSync.js +9 -1
  122. package/dist/server/workbench/skillSync.js.map +1 -1
  123. package/dist/server/workspaceExplorerRoutes.js +12 -25
  124. package/dist/server/workspaceExplorerRoutes.js.map +1 -1
  125. package/dist/server/workspaces/effectivePathAccess.js +3 -2
  126. package/dist/server/workspaces/effectivePathAccess.js.map +1 -1
  127. package/dist/server/workspaces/fileContentService.js +17 -9
  128. package/dist/server/workspaces/fileContentService.js.map +1 -1
  129. package/dist/server/workspaces/fileOperationService.js +4 -17
  130. package/dist/server/workspaces/fileOperationService.js.map +1 -1
  131. package/dist/server/workspaces/filePreviewResponseHeaders.js +18 -0
  132. package/dist/server/workspaces/filePreviewResponseHeaders.js.map +1 -0
  133. package/dist/server/workspaces/filePreviewResponsePolicy.js +55 -0
  134. package/dist/server/workspaces/filePreviewResponsePolicy.js.map +1 -0
  135. package/dist/server/workspaces/filePreviewService.js +81 -0
  136. package/dist/server/workspaces/filePreviewService.js.map +1 -0
  137. package/dist/server/workspaces/projectWorkspaceCwds.js +2 -2
  138. package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -1
  139. package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +416 -0
  140. package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -0
  141. package/dist/server/workspaces/workspaceCatalog.js +87 -0
  142. package/dist/server/workspaces/workspaceCatalog.js.map +1 -0
  143. package/dist/server/workspaces/workspaceContext.js +5 -5
  144. package/dist/server/workspaces/workspaceContext.js.map +1 -1
  145. package/dist/server/workspaces/workspaceDeletionRoutes.js +114 -99
  146. package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -1
  147. package/dist/server/workspaces/workspaceProviderRegistry.js +650 -0
  148. package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -0
  149. package/dist/server/workspaces/workspaceRemovalService.js +258 -0
  150. package/dist/server/workspaces/workspaceRemovalService.js.map +1 -0
  151. package/dist/server/workspaces/workspaceRouteContext.js +2 -1
  152. package/dist/server/workspaces/workspaceRouteContext.js.map +1 -1
  153. package/dist/server/workspaces/workspaceRouteErrors.js +7 -0
  154. package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -0
  155. package/dist/server/workspaces/worktreePreRemoveHook.js +39 -0
  156. package/dist/server/workspaces/worktreePreRemoveHook.js.map +1 -0
  157. package/dist/server-plugin-api.d.ts +115 -0
  158. package/dist/server-plugin-api.js +2 -0
  159. package/dist/server-plugin-api.js.map +1 -0
  160. package/dist/serverPluginRecovery.js +138 -0
  161. package/dist/serverPluginRecovery.js.map +1 -0
  162. package/dist/sessiond/config.js +6 -0
  163. package/dist/sessiond/config.js.map +1 -1
  164. package/dist/sessiond/sessionDaemonClient.js +7 -6
  165. package/dist/sessiond/sessionDaemonClient.js.map +1 -1
  166. package/dist/shared/apiTypes.js +2 -0
  167. package/dist/shared/apiTypes.js.map +1 -1
  168. package/dist/shared/capabilities.js +2 -0
  169. package/dist/shared/capabilities.js.map +1 -1
  170. package/dist/shared/federatedRoutes.js +28 -2
  171. package/dist/shared/federatedRoutes.js.map +1 -1
  172. package/dist/shared/machineStatus.js +60 -0
  173. package/dist/shared/machineStatus.js.map +1 -0
  174. package/dist/shared/pluginApiTypes.d.ts +155 -0
  175. package/dist/shared/pluginBackendProtocol.js +110 -0
  176. package/dist/shared/pluginBackendProtocol.js.map +1 -0
  177. package/dist/shared/pluginIds.js +3 -0
  178. package/dist/shared/pluginIds.js.map +1 -1
  179. package/dist/shared/pluginRecoveryCommands.js +14 -0
  180. package/dist/shared/pluginRecoveryCommands.js.map +1 -0
  181. package/dist/shared/workspaceFiles.js +41 -2
  182. package/dist/shared/workspaceFiles.js.map +1 -1
  183. package/dist/shared/workspaceRemovalProtocol.js +24 -0
  184. package/dist/shared/workspaceRemovalProtocol.js.map +1 -0
  185. package/docs/config.md +12 -5
  186. package/docs/plugins.md +15 -21
  187. package/package.json +27 -8
  188. package/server-plugin-api.d.ts +1 -0
  189. package/skills/README.md +12 -0
  190. package/skills/relay/SKILL.md +115 -0
  191. package/dist/client/assets/UnifiedDiffViewer-2DACwxFY.js +0 -39
  192. package/dist/plugin-api/unstable.d.ts +0 -22
  193. package/dist/server/sessions/parentSessionLocator.js +0 -75
  194. package/dist/server/sessions/parentSessionLocator.js.map +0 -1
  195. package/dist/server/workspaces/imagePreviewService.js +0 -40
  196. package/dist/server/workspaces/imagePreviewService.js.map +0 -1
  197. package/dist/shared/apiTypes.d.ts +0 -1323
  198. package/dist/shared/thinkingLevels.d.ts +0 -29
  199. 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 { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, getAgentDir, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, defineTool, ModelRuntime, readStoredCredential, SessionManager, } from "@earendil-works/pi-coding-agent";
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
- try {
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(receipt.skills.map((skill) => resolve(projectRoot, ".pi", "skills", skill.directory)));
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
- try {
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
- function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRuntime, resolveSettingsScope, spawn, subsessions, askUser, workbench, logger = noopLogger, managementAudit, managementProjectIdForCwd) {
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 services = await createAgentSessionServices({ cwd, agentDir, modelRuntime: scopedModelRuntime, settingsManager });
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 ? ModelRuntime.create() : Promise.resolve(deps.modelRuntime);
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 a beta capability gated behind their own flag, and they
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: await this.withRelatedSessionsElsewhere([...unarchivedSessions, ...archivedSessions], cwd),
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
- const decision = await this.spawnTargets.resolveSpawnTarget(input.spawningCwd, input.cwd);
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
- await session.modelRuntime.refresh();
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
- ? (await this.sessionManager.list(ref.cwd)).find((s) => s.id === ref.id || s.id.startsWith(ref.id))
2369
- : (await this.sessionManager.listAll?.() ?? []).find((s) => s.id === ref || s.id.startsWith(ref));
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({ action: "tool_execution", ...managementIdentity, ...auditEvent });
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 { userId: context.user.id, rootUserId: context.user.rootUserId, projectId };
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