@chainingintention/pi-web-cn 1.202607.5 → 1.202607.7

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 (128) hide show
  1. package/README.md +3 -0
  2. package/dist/cli.js +283 -315
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client/assets/CodeViewer-DxWBEGHy.js +4 -0
  5. package/dist/client/assets/TerminalPanel-CLBbwrSg.js +184 -0
  6. package/dist/client/assets/UnifiedDiffViewer-CvVtrOUV.js +39 -0
  7. package/dist/client/assets/index-CEswQiQV.js +3149 -0
  8. package/dist/client/assets/vendor-editor-core-vUi74DnD.js +12 -0
  9. package/dist/client/assets/vendor-editor-languages-DoaXUodB.js +46 -0
  10. package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +1 -0
  11. package/dist/client/assets/vendor-terminal-BrP-ENHg.css +1 -0
  12. package/dist/client/assets/vendor-terminal-D8k4UKM2.js +35 -0
  13. package/dist/client/index.html +6 -6
  14. package/dist/client/manifest.webmanifest +4 -4
  15. package/dist/config.js +221 -9
  16. package/dist/config.js.map +1 -1
  17. package/dist/nativeServices/serviceDoctor.js +480 -0
  18. package/dist/nativeServices/serviceDoctor.js.map +1 -0
  19. package/dist/nativeServices/serviceInstall.js +35 -0
  20. package/dist/nativeServices/serviceInstall.js.map +1 -0
  21. package/dist/nativeServices/servicePlan.js +357 -0
  22. package/dist/nativeServices/servicePlan.js.map +1 -0
  23. package/dist/nativeServices/serviceProbe.js +445 -0
  24. package/dist/nativeServices/serviceProbe.js.map +1 -0
  25. package/dist/nativeServices/serviceRendering.js +122 -0
  26. package/dist/nativeServices/serviceRendering.js.map +1 -0
  27. package/dist/pi-web-plugins/updates/pi-web-plugin.js +11 -0
  28. package/dist/plugin-api.d.ts +2 -0
  29. package/dist/server/activeAgentProfileProvider.js +25 -0
  30. package/dist/server/activeAgentProfileProvider.js.map +1 -0
  31. package/dist/server/app.js +63 -16
  32. package/dist/server/app.js.map +1 -1
  33. package/dist/server/browserMessageProjection.js +56 -0
  34. package/dist/server/browserMessageProjection.js.map +1 -0
  35. package/dist/server/configRoutes.js +152 -10
  36. package/dist/server/configRoutes.js.map +1 -1
  37. package/dist/server/machines/machineClient.js +19 -9
  38. package/dist/server/machines/machineClient.js.map +1 -1
  39. package/dist/server/machines/machinePluginProxyRoutes.js +7 -4
  40. package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
  41. package/dist/server/machines/machineProxyRoutes.js +16 -4
  42. package/dist/server/machines/machineProxyRoutes.js.map +1 -1
  43. package/dist/server/machines/machineRoutes.js +1 -1
  44. package/dist/server/machines/machineRoutes.js.map +1 -1
  45. package/dist/server/machines/machineService.js +2 -2
  46. package/dist/server/machines/machineService.js.map +1 -1
  47. package/dist/server/managementEmbed.js +28 -11
  48. package/dist/server/managementEmbed.js.map +1 -1
  49. package/dist/server/piPackageRoutes.js +7 -3
  50. package/dist/server/piPackageRoutes.js.map +1 -1
  51. package/dist/server/piPackageService.js +34 -2
  52. package/dist/server/piPackageService.js.map +1 -1
  53. package/dist/server/piWebPluginService.js +48 -12
  54. package/dist/server/piWebPluginService.js.map +1 -1
  55. package/dist/server/piWebReleaseLookupCache.js +37 -0
  56. package/dist/server/piWebReleaseLookupCache.js.map +1 -0
  57. package/dist/server/piWebStatus.js +48 -49
  58. package/dist/server/piWebStatus.js.map +1 -1
  59. package/dist/server/piWebStatusCache.js +19 -6
  60. package/dist/server/piWebStatusCache.js.map +1 -1
  61. package/dist/server/realtime/sessionEventHub.js +2 -1
  62. package/dist/server/realtime/sessionEventHub.js.map +1 -1
  63. package/dist/server/realtime/sessionEventScope.js +0 -3
  64. package/dist/server/realtime/sessionEventScope.js.map +1 -1
  65. package/dist/server/sessiond/sessionDaemonStartup.js +49 -0
  66. package/dist/server/sessiond/sessionDaemonStartup.js.map +1 -0
  67. package/dist/server/sessiond.js +113 -70
  68. package/dist/server/sessiond.js.map +1 -1
  69. package/dist/server/sessions/authProviderOptions.js +23 -22
  70. package/dist/server/sessions/authProviderOptions.js.map +1 -1
  71. package/dist/server/sessions/authRoutes.js +14 -12
  72. package/dist/server/sessions/authRoutes.js.map +1 -1
  73. package/dist/server/sessions/authService.js +28 -22
  74. package/dist/server/sessions/authService.js.map +1 -1
  75. package/dist/server/sessions/oauthLoginFlowService.js +17 -19
  76. package/dist/server/sessions/oauthLoginFlowService.js.map +1 -1
  77. package/dist/server/sessions/piSessionManagerGateway.js +22 -21
  78. package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
  79. package/dist/server/sessions/piSessionService.js +261 -111
  80. package/dist/server/sessions/piSessionService.js.map +1 -1
  81. package/dist/server/sessions/projectAuthService.js +135 -0
  82. package/dist/server/sessions/projectAuthService.js.map +1 -0
  83. package/dist/server/sessions/projectSettingsScope.js +263 -0
  84. package/dist/server/sessions/projectSettingsScope.js.map +1 -0
  85. package/dist/server/sessions/sessionArchiveMigration.js +590 -0
  86. package/dist/server/sessions/sessionArchiveMigration.js.map +1 -0
  87. package/dist/server/sessions/sessionArchiveStore.js +7 -4
  88. package/dist/server/sessions/sessionArchiveStore.js.map +1 -1
  89. package/dist/server/sessions/sessionRoutes.js +15 -4
  90. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  91. package/dist/server/sessions/sessionService.js +2 -0
  92. package/dist/server/sessions/sessionService.js.map +1 -0
  93. package/dist/server/sessions/spawnSubsessionTool.js +71 -31
  94. package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
  95. package/dist/server/terminals/terminalService.js +2 -2
  96. package/dist/server/terminals/terminalService.js.map +1 -1
  97. package/dist/sessiond/activeAgentProfile.js +31 -0
  98. package/dist/sessiond/activeAgentProfile.js.map +1 -0
  99. package/dist/sessiond/sessionDaemonClient.js +38 -0
  100. package/dist/sessiond/sessionDaemonClient.js.map +1 -1
  101. package/dist/shared/activeAgentProfile.js +76 -0
  102. package/dist/shared/activeAgentProfile.js.map +1 -0
  103. package/dist/shared/apiTypes.d.ts +26 -0
  104. package/dist/shared/apiTypes.js +2 -0
  105. package/dist/shared/apiTypes.js.map +1 -1
  106. package/dist/shared/capabilities.js +5 -0
  107. package/dist/shared/capabilities.js.map +1 -1
  108. package/dist/shared/federatedRoutes.js +1 -0
  109. package/dist/shared/federatedRoutes.js.map +1 -1
  110. package/dist/shared/piWebConfigParsing.js +0 -5
  111. package/dist/shared/piWebConfigParsing.js.map +1 -1
  112. package/dist/shared/piWebStatusParsing.js +6 -0
  113. package/dist/shared/piWebStatusParsing.js.map +1 -1
  114. package/dist/shared/thinkingLevels.d.ts +1 -1
  115. package/dist/shared/thinkingLevels.js +1 -1
  116. package/dist/shared/thinkingLevels.js.map +1 -1
  117. package/docs/config.md +63 -9
  118. package/docs/plugins.md +18 -5
  119. package/package.json +35 -31
  120. package/dist/client/assets/CodeViewer-CfZodNYb.js +0 -4
  121. package/dist/client/assets/TerminalPanel-eua763mA.js +0 -126
  122. package/dist/client/assets/UnifiedDiffViewer-C9-QeGMj.js +0 -34
  123. package/dist/client/assets/index-BiFWXoRZ.js +0 -3084
  124. package/dist/client/assets/vendor-editor-core-C7aWiQy0.js +0 -12
  125. package/dist/client/assets/vendor-editor-languages-EW34FfVy.js +0 -26
  126. package/dist/client/assets/vendor-editor-legacy-B4QLsWF8.js +0 -1
  127. package/dist/client/assets/vendor-terminal-DDGTF8rc.css +0 -1
  128. package/dist/client/assets/vendor-terminal-DjQ08hXu.js +0 -16
@@ -1,19 +1,21 @@
1
1
  import { statSync } from "node:fs";
2
2
  import { open, readFile, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
4
- import { AuthStorage, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, defineTool, getAgentDir, ModelRegistry, SessionManager, } from "@earendil-works/pi-coding-agent";
4
+ import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, getAgentDir, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, defineTool, ModelRuntime, SessionManager, } from "@earendil-works/pi-coding-agent";
5
5
  import { pageMessagesAtSafeBoundary } from "./messagePaging.js";
6
- import { authScopeFromEventScope, eventScopeFromManagementContext, managementContextKey, NORMAL_SESSION_EVENT_SCOPE } from "../realtime/sessionEventScope.js";
6
+ import { eventScopeFromManagementContext, managementContextKey, NORMAL_SESSION_EVENT_SCOPE } from "../realtime/sessionEventScope.js";
7
7
  import { BUILTIN_COMMANDS } from "./builtinCommands.js";
8
8
  import { SessionCommandService } from "./sessionCommandService.js";
9
9
  import { SessionArchiveStore } from "./sessionArchiveStore.js";
10
10
  import { findArchiveCandidateByIdOrPrefix, planSessionArchiveTree } from "./sessionArchiveTree.js";
11
11
  import { deterministicSessionName, fallbackSessionName, generateShortSessionName } from "./sessionNameGenerator.js";
12
12
  import { computeEditPreview } from "./editPreview.js";
13
- import { createPiSessionManagerGateway } from "./piSessionManagerGateway.js";
14
13
  import { attachmentsToInlineImages, saveAttachmentsToWorkspace } from "./attachmentService.js";
15
14
  import { parsePromptAttachments } from "../../shared/promptAttachments.js";
16
- import { cwdPathsEqual } from "../workingDirectory.js";
15
+ import { createPiSessionManagerGateway } from "./piSessionManagerGateway.js";
16
+ import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
17
+ import { piWebDataDir } from "../../config.js";
18
+ import { createScopedSettingsManager, resolveSettingsScopeDirectory } from "./projectSettingsScope.js";
17
19
  import { createSpawnSessionToolDefinition } from "./spawnSessionTool.js";
18
20
  import { createSubsessionToolDefinitions } from "./spawnSubsessionTool.js";
19
21
  import { buildTranscriptView } from "./subsessionTranscript.js";
@@ -81,46 +83,51 @@ function pathInsideOrEqual(parent, child) {
81
83
  function defaultCreateAgentRuntime(createRuntime, options) {
82
84
  if (!(options.sessionManager instanceof SessionManager))
83
85
  throw new Error("Default runtime creation requires an SDK SessionManager");
84
- const runtimeFactory = createRuntimeWithContextAndOneShotInitialModel(createRuntime, options.managementContext, options.initialModel);
86
+ const runtimeFactory = createRuntimeWithContextAndOneShotInitialModel(createRuntime, options.managementContext, options.initialModel, options.delegationToolsEnabled);
85
87
  return createAgentSessionRuntime(runtimeFactory, {
86
88
  cwd: options.cwd,
87
89
  agentDir: options.agentDir,
88
90
  sessionManager: options.sessionManager,
89
91
  });
90
92
  }
91
- function createRuntimeWithContextAndOneShotInitialModel(createRuntime, managementContext, initialModel) {
93
+ function createRuntimeWithContextAndOneShotInitialModel(createRuntime, managementContext, initialModel, delegationToolsEnabled) {
92
94
  // The inherited model belongs only to the session being spawned. Do not keep
93
95
  // reapplying it if that runtime later creates/forks/switches sessions itself.
94
96
  let pendingInitialModel = initialModel;
97
+ let pendingDelegationToolsEnabled = delegationToolsEnabled;
95
98
  return async (options) => {
96
99
  const model = pendingInitialModel;
100
+ const toolsEnabled = pendingDelegationToolsEnabled;
97
101
  pendingInitialModel = undefined;
102
+ pendingDelegationToolsEnabled = undefined;
98
103
  return createRuntime({
99
104
  ...options,
100
105
  ...(managementContext === undefined ? {} : { managementContext }),
101
106
  ...(model === undefined ? {} : { initialModel: model }),
107
+ ...(toolsEnabled === undefined ? {} : { delegationToolsEnabled: toolsEnabled }),
102
108
  });
103
109
  };
104
110
  }
105
- function createDefaultRuntimeFactory(modelRegistry, managementModelRegistry, spawn, subsessions) {
106
- return async ({ cwd, agentDir, sessionManager, sessionStartEvent, managementContext, initialModel }) => {
107
- const scopedModelRegistry = managementContext === undefined ? modelRegistry : managementModelRegistry;
108
- const authStorage = scopedModelRegistry.authStorage;
111
+ function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRuntime, resolveSettingsScope, spawn, subsessions) {
112
+ return async ({ cwd, agentDir, sessionManager, sessionStartEvent, managementContext, initialModel, delegationToolsEnabled = true }) => {
113
+ const scopedModelRuntime = managementContext === undefined ? await normalModelRuntimeForCwd(cwd) : await managementModelRuntime();
109
114
  if (managementContext !== undefined) {
110
- return createManagementRuntimeFactory(authStorage, scopedModelRegistry, spawn, subsessions, managementContext)({
115
+ return createManagementRuntimeFactory(scopedModelRuntime, resolveSettingsScope, spawn, subsessions, managementContext)({
111
116
  cwd,
112
117
  agentDir,
113
118
  sessionManager,
119
+ delegationToolsEnabled,
114
120
  ...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
115
121
  ...(initialModel === undefined ? {} : { initialModel }),
116
122
  });
117
123
  }
118
- const services = await createAgentSessionServices({ cwd, agentDir, authStorage, modelRegistry: scopedModelRegistry });
119
- const customTools = [
120
- ...managedAgentToolDefinitions(cwd),
121
- ...(spawn === undefined ? [] : [defineTool(createSpawnSessionToolDefinition(cwd, { spawn }))]),
122
- ...(subsessions === undefined ? [] : createSubsessionToolDefinitions(cwd, subsessions).map((tool) => defineTool(tool))),
123
- ];
124
+ const settingsManager = await createScopedSettingsManager({
125
+ cwd,
126
+ scopeDirectory: await resolveSettingsScope(cwd, "normal"),
127
+ globalAgentDir: agentDir,
128
+ });
129
+ const services = await createAgentSessionServices({ cwd, agentDir, modelRuntime: scopedModelRuntime, settingsManager });
130
+ const customTools = createPiWebCustomToolDefinitions(cwd, delegationToolsEnabled, spawn, subsessions);
124
131
  const result = await createAgentSessionFromServices({
125
132
  services,
126
133
  sessionManager,
@@ -131,15 +138,20 @@ function createDefaultRuntimeFactory(modelRegistry, managementModelRegistry, spa
131
138
  return { ...result, services, diagnostics: services.diagnostics };
132
139
  };
133
140
  }
134
- function createManagementRuntimeFactory(authStorage, modelRegistry, spawn, subsessions, managementContext) {
135
- return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel }) => {
141
+ function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spawn, subsessions, managementContext) {
142
+ return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, delegationToolsEnabled = true }) => {
136
143
  const policyAgentDir = await writeManagementPermissionSystemPolicy(agentDir, cwd, managementContext);
137
144
  return withRuntimeCreationEnvironment({ [PI_PERMISSION_SYSTEM_POLICY_AGENT_DIR]: policyAgentDir }, async () => {
145
+ const settingsManager = await createScopedSettingsManager({
146
+ cwd,
147
+ scopeDirectory: await resolveSettingsScope(cwd, "management"),
148
+ globalAgentDir: agentDir,
149
+ });
138
150
  const services = await createAgentSessionServices({
139
151
  cwd,
140
152
  agentDir,
141
- authStorage,
142
- modelRegistry,
153
+ modelRuntime,
154
+ settingsManager,
143
155
  resourceLoaderOptions: {
144
156
  agentsFilesOverride: (base) => filterManagedGlobalContextFiles(cwd, agentDir, base),
145
157
  },
@@ -148,8 +160,8 @@ function createManagementRuntimeFactory(authStorage, modelRegistry, spawn, subse
148
160
  const customTools = [
149
161
  ...managedAgentToolDefinitions(cwd, managedToolOptions),
150
162
  createManagedPythonToolDefinition(cwd, managementContext),
151
- ...(spawn === undefined ? [] : [defineTool(createSpawnSessionToolDefinition(cwd, { spawn }))]),
152
- ...(subsessions === undefined ? [] : createSubsessionToolDefinitions(cwd, subsessions).map((tool) => defineTool(tool))),
163
+ ...(delegationToolsEnabled && spawn !== undefined ? [defineTool(createSpawnSessionToolDefinition(cwd, { spawn }))] : []),
164
+ ...(delegationToolsEnabled && subsessions !== undefined ? createSubsessionToolDefinitions(cwd, subsessions).map((tool) => defineTool(tool)) : []),
153
165
  ];
154
166
  const options = {
155
167
  services,
@@ -164,6 +176,13 @@ function createManagementRuntimeFactory(authStorage, modelRegistry, spawn, subse
164
176
  });
165
177
  };
166
178
  }
179
+ export function createPiWebCustomToolDefinitions(cwd, delegationEnabled, spawn, subsessions) {
180
+ return [
181
+ ...managedAgentToolDefinitions(cwd),
182
+ ...(delegationEnabled && spawn !== undefined ? [defineTool(createSpawnSessionToolDefinition(cwd, { spawn }))] : []),
183
+ ...(delegationEnabled && subsessions !== undefined ? createSubsessionToolDefinitions(cwd, subsessions).map((tool) => defineTool(tool)) : []),
184
+ ];
185
+ }
167
186
  function managedAgentToolDefinitions(cwd, options) {
168
187
  if (options === undefined)
169
188
  return [createPiWebEditToolDefinition(cwd)];
@@ -198,9 +217,10 @@ function createPiWebEditToolDefinition(cwd, options) {
198
217
  });
199
218
  }
200
219
  export class PiSessionService {
201
- constructor(events, deps = {}) {
220
+ constructor(events, deps) {
202
221
  this.events = events;
203
222
  this.active = new Map();
223
+ this.pendingSessionOpens = new Map();
204
224
  this.activities = new Map();
205
225
  this.compactionPromptQueues = new Map();
206
226
  this.compactionDrainTimers = new Map();
@@ -221,16 +241,30 @@ export class PiSessionService {
221
241
  this.subsessionNotifyArmed = new Map();
222
242
  this.archiveStore = deps.archiveStore ?? new SessionArchiveStore();
223
243
  this.agentDir = deps.agentDir ?? getAgentDir();
224
- this.sessionManager = deps.sessionManager ?? createPiSessionManagerGateway({ agentDir: this.agentDir });
225
- this.modelRegistry = deps.modelRegistry ?? ModelRegistry.create(AuthStorage.create());
226
- this.managementModelRegistry = deps.managementModelRegistry ?? this.modelRegistry;
244
+ this.dataDir = deps.dataDir ?? piWebDataDir();
245
+ this.projectPathForCwd = deps.projectPathForCwd;
246
+ this.sessionManager = deps.sessionManager ?? createPiSessionManagerGateway({
247
+ agentDir: this.agentDir,
248
+ env: process.env,
249
+ sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"],
250
+ });
251
+ let defaultModelRuntime;
252
+ const resolveDefaultModelRuntime = () => {
253
+ defaultModelRuntime ??= deps.modelRuntime === undefined ? ModelRuntime.create() : Promise.resolve(deps.modelRuntime);
254
+ return defaultModelRuntime;
255
+ };
256
+ this.normalModelRuntimeForCwd = deps.normalModelRuntimeForCwd ?? resolveDefaultModelRuntime;
257
+ const managementModelRuntime = deps.managementModelRuntime;
258
+ this.managementModelRuntime = managementModelRuntime === undefined
259
+ ? resolveDefaultModelRuntime
260
+ : () => Promise.resolve(managementModelRuntime);
227
261
  this.spawnTargets = deps.spawnTargets;
228
262
  this.logger = deps.logger ?? noopLogger;
229
263
  this.now = deps.now ?? (() => new Date());
230
264
  // Subsessions are a beta capability gated behind their own flag, and they
231
265
  // also require the spawn capability (they share its project-scope resolver).
232
266
  const subsessionsActive = this.spawnTargets !== undefined && deps.subsessionsEnabled === true;
233
- this.createRuntime = deps.createRuntime ?? createDefaultRuntimeFactory(this.modelRegistry, this.managementModelRegistry, this.spawnTargets === undefined ? undefined : (input) => this.spawnSession(input), !subsessionsActive ? undefined : {
267
+ 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 : {
234
268
  spawn: (input) => this.spawnSubsession(input),
235
269
  list: (parentSessionId, parentSessionFile) => this.listSubsessions(parentSessionId, parentSessionFile),
236
270
  check: (parentSessionId, sessionId, parentSessionFile) => this.checkSubsession(parentSessionId, sessionId, parentSessionFile),
@@ -254,6 +288,15 @@ export class PiSessionService {
254
288
  activeCount() {
255
289
  return this.active.size;
256
290
  }
291
+ async resolveSettingsScopeDirectory(cwd, mode) {
292
+ const projectPath = this.projectPathForCwd === undefined ? undefined : await this.projectPathForCwd(cwd);
293
+ return resolveSettingsScopeDirectory({
294
+ dataDir: this.dataDir,
295
+ cwd,
296
+ mode,
297
+ ...(projectPath === undefined ? {} : { projectPath }),
298
+ });
299
+ }
257
300
  async cleanupPreview(request) {
258
301
  return previewResponseFromPlan(await this.cleanupPlan(request));
259
302
  }
@@ -298,8 +341,12 @@ export class PiSessionService {
298
341
  async dispose() {
299
342
  clearInterval(this.heartbeat);
300
343
  this.clearCompactionDrainTimers();
344
+ const pendingOpens = this.pendingSessionOpenPromises();
345
+ if (pendingOpens.length > 0)
346
+ await Promise.allSettled(pendingOpens);
301
347
  const activeSessions = Array.from(new Set(this.active.values()));
302
348
  this.active.clear();
349
+ this.pendingSessionOpens.clear();
303
350
  this.activities.clear();
304
351
  this.compactionPromptQueues.clear();
305
352
  this.authLossWarnings.clear();
@@ -316,6 +363,8 @@ export class PiSessionService {
316
363
  }));
317
364
  }
318
365
  async list(cwd, managementContext) {
366
+ if (managementContext === undefined)
367
+ await this.normalModelRuntimeForCwd(cwd);
319
368
  const [sessions, archivedRecords] = await Promise.all([this.sessionManager.list(cwd), this.archiveStore.list()]);
320
369
  const sessionsById = new Map(sessions.map((session) => [session.id, session]));
321
370
  const archivedForCwd = await Promise.all(archivedRecords
@@ -331,9 +380,15 @@ export class PiSessionService {
331
380
  return [...unarchivedSessions, ...archivedSessions];
332
381
  }
333
382
  async start(cwd, options = {}) {
383
+ if (options.managementContext === undefined)
384
+ await this.normalModelRuntimeForCwd(cwd);
385
+ return this.startSession(cwd, options);
386
+ }
387
+ async startSession(cwd, options) {
334
388
  const active = await this.create(this.sessionManager.create(cwd, options.parentSession === undefined ? undefined : { parentSession: options.parentSession }), cwd, {
335
389
  ...(options.managementContext === undefined ? {} : { managementContext: options.managementContext }),
336
390
  ...(options.initialModel === undefined ? {} : { initialModel: options.initialModel }),
391
+ ...(options.creationProvenance === undefined ? {} : { creationProvenance: options.creationProvenance }),
337
392
  });
338
393
  const { session } = active.runtime;
339
394
  const created = {
@@ -382,9 +437,10 @@ export class PiSessionService {
382
437
  const decision = await this.spawnTargets.resolveSpawnTarget(input.spawningCwd, input.cwd);
383
438
  if (!decision.allowed)
384
439
  throw spawnTargetError(decision);
385
- const created = await this.start(decision.cwd, {
440
+ const created = await this.startSession(decision.cwd, {
386
441
  ...(input.parentSessionFile === undefined ? {} : { parentSession: input.parentSessionFile }),
387
442
  ...(input.model === undefined ? {} : { initialModel: input.model }),
443
+ creationProvenance: "tracked-subsession",
388
444
  });
389
445
  const parentSessionFile = nonEmptyString(input.parentSessionFile);
390
446
  const link = {
@@ -579,61 +635,13 @@ export class PiSessionService {
579
635
  return;
580
636
  this.registerVerifiedSubsession(link);
581
637
  }
582
- async verifiedSubsessionLinkFromOpenedChild(session) {
583
- // Child markers are only hints; the current child header and reciprocal
584
- // parent custom link must agree on the exact ids and files before relinking.
585
- const entries = session.sessionManager.getEntries?.() ?? session.sessionManager.getBranch();
586
- let marker;
587
- for (const entry of entries) {
588
- const parsed = parsePersistedChildSubsessionLink(entry);
589
- if (parsed?.spawnedSessionId === session.sessionId)
590
- marker = parsed;
591
- }
592
- if (marker === undefined)
593
- return undefined;
594
- const childSessionFile = nonEmptyString(session.sessionFile);
595
- if (childSessionFile === undefined)
596
- return undefined;
597
- const childHeader = await readSessionHeaderSummary(childSessionFile);
598
- if (childHeader?.id !== session.sessionId)
599
- return undefined;
600
- const parentSessionFile = nonEmptyString(childHeader.parentSession);
601
- if (parentSessionFile === undefined)
602
- return undefined;
603
- const parentHeader = await readSessionHeaderSummary(parentSessionFile);
604
- if (parentHeader?.id !== marker.spawnedBySessionId)
605
- return undefined;
606
- const parentLink = this.findReciprocalParentSubsessionLink(parentSessionFile, marker.spawnedBySessionId, session.sessionId, childSessionFile);
607
- if (parentLink === undefined)
608
- return undefined;
609
- return {
610
- parentSessionId: marker.spawnedBySessionId,
611
- childSessionId: session.sessionId,
612
- childSessionFile,
613
- parentSessionFile,
614
- cwd: parentLink.cwd ?? session.sessionManager.getCwd(),
615
- };
616
- }
617
- findReciprocalParentSubsessionLink(parentSessionFile, parentSessionId, childSessionId, childSessionFile) {
618
- let parentManager;
619
- try {
620
- parentManager = this.sessionManager.open(parentSessionFile);
621
- }
622
- catch {
623
- return undefined;
624
- }
625
- const entries = parentManager.getEntries?.() ?? parentManager.getBranch();
626
- for (const entry of entries) {
627
- const link = parsePersistedParentSubsessionLink(entry);
628
- if (link === undefined)
629
- continue;
630
- if (link.spawnedBySessionId !== parentSessionId || link.spawnedSessionId !== childSessionId)
631
- continue;
632
- if (link.spawnedSessionFile === undefined || !sessionPathsEqual(link.spawnedSessionFile, childSessionFile))
633
- continue;
634
- return link;
635
- }
636
- return undefined;
638
+ verifiedSubsessionLinkFromOpenedChild(session) {
639
+ return verifiedTrackedSubsessionLink(this.sessionManager, {
640
+ sessionId: session.sessionId,
641
+ sessionFile: session.sessionFile,
642
+ sessionManager: session.sessionManager,
643
+ cwd: session.sessionManager.getCwd(),
644
+ });
637
645
  }
638
646
  async getOrOpenTrackedSubsession(sessionId) {
639
647
  const link = this.subsessionLinks.get(sessionId);
@@ -670,6 +678,16 @@ export class PiSessionService {
670
678
  return "error";
671
679
  return "idle";
672
680
  }
681
+ workingSubsessionIds(parentSessionId) {
682
+ const childIds = this.subsessionChildren.get(parentSessionId);
683
+ if (childIds === undefined)
684
+ return [];
685
+ return [...childIds].filter((childId) => {
686
+ const link = this.subsessionLinks.get(childId);
687
+ const active = link === undefined ? undefined : this.activeChildForSubsessionLink(link);
688
+ return active !== undefined && this.hasActiveWork(active.runtime.session);
689
+ });
690
+ }
673
691
  /**
674
692
  * Drive parent notifications from a tracked child's status. Arms a pending
675
693
  * notification while the child is working, and when it stops fires a single
@@ -691,7 +709,11 @@ export class PiSessionService {
691
709
  const status = this.activityForSession(session)?.phase === "error" ? "error" : "idle";
692
710
  const finalText = finalAssistantText(historyMessages(session));
693
711
  const preview = finalText === "" ? "(no output)" : truncateForNotification(finalText);
694
- const text = `Subsession ${childId} stopped working (status: ${status}). Latest output:\n\n${preview}\n\nUse check_subsession with sessionId "${childId}" for its status and latest output, or read_subsession to look through its full transcript.`;
712
+ const workingIds = this.workingSubsessionIds(link.parentSessionId);
713
+ const next = workingIds.length === 0
714
+ ? "No other tracked subsessions are working."
715
+ : `Still working: ${workingIds.join(", ")}. Continue working, or call yield_to_subsessions alone and last at the next join point. Further completion notices arrive automatically; do not poll.`;
716
+ const text = `Subsession ${childId} stopped working (${status}).\n${next}\n\n--- SUBSESSION OUTPUT: ${childId} ---\n${preview}`;
695
717
  void this.notifyParentOfSubsession(link.parentSessionId, childId, text);
696
718
  }
697
719
  async getOrOpenParentForSubsession(parentSessionId, childSessionId) {
@@ -736,21 +758,21 @@ export class PiSessionService {
736
758
  }
737
759
  async availableModels(ref, managementContext) {
738
760
  const session = await this.getOrOpen(ref, managementContext);
739
- session.modelRegistry.refresh();
761
+ await session.modelRuntime.reloadConfig();
740
762
  const models = session.scopedModels.length > 0
741
763
  ? session.scopedModels.map((scoped) => scoped.model)
742
- : session.modelRegistry.getAvailable();
764
+ : await session.modelRuntime.getAvailable();
743
765
  return models.map(modelToClientModel);
744
766
  }
745
767
  async setModel(ref, provider, modelId, managementContext) {
746
768
  await this.assertWritable(ref);
747
769
  const session = await this.getOrOpen(ref, managementContext);
748
- session.modelRegistry.refresh();
770
+ await session.modelRuntime.reloadConfig();
749
771
  const candidates = session.scopedModels.length > 0
750
772
  ? session.scopedModels.map((scoped) => scoped.model)
751
- : session.modelRegistry.getAvailable();
773
+ : await session.modelRuntime.getAvailable();
752
774
  const model = candidates.find((candidate) => candidate.provider === provider && candidate.id === modelId)
753
- ?? session.modelRegistry.find(provider, modelId);
775
+ ?? session.modelRuntime.getModel(provider, modelId);
754
776
  if (model === undefined)
755
777
  throw new Error(`未找到模型:${provider}/${modelId}`);
756
778
  await session.setModel(model);
@@ -1121,6 +1143,14 @@ export class PiSessionService {
1121
1143
  this.publishActivity(active.runtime.session, "stopped", "idle");
1122
1144
  this.publishStatus(active.runtime.session);
1123
1145
  }
1146
+ async clearQueue(ref, managementContext) {
1147
+ await this.assertWritable(ref);
1148
+ const session = await this.getOrOpen(ref, managementContext);
1149
+ this.clearCompactionPromptQueue(session.sessionId);
1150
+ clearSessionQueue(session);
1151
+ this.publishStatus(session);
1152
+ return this.statusFromSession(session);
1153
+ }
1124
1154
  stop(ref, managementContext) {
1125
1155
  const active = this.activeForLookup(ref, managementContext);
1126
1156
  if (active === undefined)
@@ -1316,6 +1346,9 @@ export class PiSessionService {
1316
1346
  return [...names];
1317
1347
  }
1318
1348
  async closeActive(sessionId) {
1349
+ const pendingOpens = this.pendingSessionOpenPromises(sessionId);
1350
+ if (pendingOpens.length > 0)
1351
+ await Promise.allSettled(pendingOpens);
1319
1352
  const activeSessions = this.activeSessionsForId(sessionId);
1320
1353
  await Promise.all(activeSessions.map((active) => this.closeActiveSession(active)));
1321
1354
  }
@@ -1358,14 +1391,36 @@ export class PiSessionService {
1358
1391
  return active;
1359
1392
  const archived = await this.getArchived(ref);
1360
1393
  if (archived?.archivePath !== undefined) {
1361
- return this.create(this.sessionManager.open(archived.archivePath), archived.cwd, managementContext === undefined ? {} : { managementContext });
1394
+ const archivePath = archived.archivePath;
1395
+ return this.openPending(archived.sessionId, archived.cwd, managementContext, () => this.sessionManager.open(archivePath));
1362
1396
  }
1363
1397
  const match = isPiSessionRef(ref)
1364
1398
  ? (await this.sessionManager.list(ref.cwd)).find((s) => s.id === ref.id || s.id.startsWith(ref.id))
1365
1399
  : (await this.sessionManager.listAll?.() ?? []).find((s) => s.id === ref || s.id.startsWith(ref));
1366
1400
  if (!match)
1367
1401
  throw new Error("未找到会话");
1368
- return this.create(this.sessionManager.open(match.path), match.cwd, managementContext === undefined ? {} : { managementContext });
1402
+ return this.openPending(match.id, match.cwd, managementContext, () => this.sessionManager.open(match.path));
1403
+ }
1404
+ openPending(sessionId, cwd, managementContext, openSessionManager) {
1405
+ const key = JSON.stringify([canonicalizeStoredCwd(cwd), sessionId, managementContextKey(managementContext)]);
1406
+ const existing = this.pendingSessionOpens.get(key);
1407
+ if (existing !== undefined)
1408
+ return existing.promise;
1409
+ const pending = {
1410
+ sessionId,
1411
+ promise: this.create(openSessionManager(), cwd, managementContext === undefined ? {} : { managementContext }),
1412
+ };
1413
+ pending.promise = pending.promise.finally(() => {
1414
+ if (this.pendingSessionOpens.get(key) === pending)
1415
+ this.pendingSessionOpens.delete(key);
1416
+ });
1417
+ this.pendingSessionOpens.set(key, pending);
1418
+ return pending.promise;
1419
+ }
1420
+ pendingSessionOpenPromises(sessionId) {
1421
+ return [...this.pendingSessionOpens.values()]
1422
+ .filter((pending) => sessionId === undefined || pending.sessionId === sessionId)
1423
+ .map((pending) => pending.promise);
1369
1424
  }
1370
1425
  async getArchived(ref) {
1371
1426
  const archived = await this.archiveStore.get(sessionIdFromLookup(ref));
@@ -1425,25 +1480,53 @@ export class PiSessionService {
1425
1480
  return [...new Set(this.active.values())].filter((active) => active.runtime.session.sessionId === sessionId);
1426
1481
  }
1427
1482
  async create(sessionManager, cwd, options = {}) {
1483
+ const delegationToolsEnabled = options.creationProvenance !== "tracked-subsession"
1484
+ && await sessionAllowsDelegationTools(sessionManager, this.sessionManager);
1428
1485
  const runtime = await this.createAgentRuntime(this.createRuntime, {
1429
1486
  cwd,
1430
1487
  agentDir: this.agentDir,
1431
1488
  sessionManager,
1489
+ delegationToolsEnabled,
1432
1490
  ...(options.managementContext === undefined ? {} : { managementContext: options.managementContext }),
1433
1491
  ...(options.initialModel === undefined ? {} : { initialModel: options.initialModel }),
1434
1492
  });
1435
1493
  const active = { runtime, unsubscribe: noop, managementContextKey: managementContextKey(options.managementContext), eventScope: eventScopeFromManagementContext(options.managementContext) };
1436
- this.active.set(activeSessionKey(runtime.session.sessionId, active.eventScope), active);
1437
- await this.bindSessionExtensions(runtime.session);
1438
- this.bindRuntime(active);
1439
- runtime.setRebindSession(async (session) => {
1440
- await this.bindSessionExtensions(session);
1494
+ try {
1495
+ await this.bindSessionExtensions(runtime.session);
1441
1496
  this.bindRuntime(active);
1442
- await this.recoverSubsessionTrackingForOpenedSession(session);
1443
- });
1444
- await this.recoverSubsessionTrackingForOpenedSession(runtime.session);
1445
- this.publishStatus(runtime.session);
1446
- return active;
1497
+ runtime.setRebindSession(async (session) => {
1498
+ await this.bindSessionExtensions(session);
1499
+ this.bindRuntime(active);
1500
+ await this.recoverSubsessionTrackingForOpenedSession(session);
1501
+ });
1502
+ this.active.set(activeSessionKey(runtime.session.sessionId, active.eventScope), active);
1503
+ await this.recoverSubsessionTrackingForOpenedSession(runtime.session);
1504
+ this.publishStatus(runtime.session);
1505
+ return active;
1506
+ }
1507
+ catch (error) {
1508
+ active.unsubscribe();
1509
+ let removedActive = false;
1510
+ for (const [sessionKey, candidate] of this.active.entries()) {
1511
+ if (candidate !== active)
1512
+ continue;
1513
+ this.active.delete(sessionKey);
1514
+ this.activities.delete(sessionKey);
1515
+ removedActive = true;
1516
+ }
1517
+ this.clearAuthLossWarningsForSession(runtime.session.sessionId);
1518
+ this.clearCompactionPromptQueue(runtime.session.sessionId);
1519
+ if (removedActive) {
1520
+ this.workspaceActivity?.removeSession(runtime.session.sessionId, runtime.session.sessionManager.getCwd(), active.eventScope);
1521
+ }
1522
+ try {
1523
+ await runtime.session.abort();
1524
+ }
1525
+ finally {
1526
+ await runtime.dispose();
1527
+ }
1528
+ throw error;
1529
+ }
1447
1530
  }
1448
1531
  async bindSessionExtensions(session) {
1449
1532
  await session.bindExtensions({
@@ -1559,15 +1642,12 @@ export class PiSessionService {
1559
1642
  session.setSessionName(name);
1560
1643
  this.publishSessionName(session);
1561
1644
  }
1562
- applyAuthChange(change = {}) {
1563
- this.modelRegistry.refresh();
1564
- this.managementModelRegistry.refresh();
1565
- const changedScope = change.scope ?? "normal";
1645
+ applyAuthChange(change) {
1646
+ const changedRuntime = change.modelRuntime;
1566
1647
  for (const active of this.active.values()) {
1567
- if (authScopeFromEventScope(active.eventScope) !== changedScope)
1568
- continue;
1569
1648
  const { session } = active.runtime;
1570
- session.modelRegistry.refresh();
1649
+ if (session.modelRuntime !== changedRuntime)
1650
+ continue;
1571
1651
  this.syncCurrentModelAuthWarning(session, active.eventScope, change.removedProviderId);
1572
1652
  this.publishStatus(session);
1573
1653
  }
@@ -1579,10 +1659,10 @@ export class PiSessionService {
1579
1659
  if (model.provider === "unknown" && model.id === "unknown")
1580
1660
  return;
1581
1661
  const warningKey = authLossWarningKey(session.sessionId, eventScope, model.provider, model.id);
1582
- const registered = session.modelRegistry.find(model.provider, model.id);
1662
+ const registered = session.modelRuntime.getModel(model.provider, model.id);
1583
1663
  if (registered === undefined)
1584
1664
  return;
1585
- if (session.modelRegistry.hasConfiguredAuth(registered)) {
1665
+ if (session.modelRuntime.hasConfiguredAuth(registered.provider)) {
1586
1666
  this.authLossWarnings.delete(warningKey);
1587
1667
  return;
1588
1668
  }
@@ -1847,6 +1927,76 @@ function addSessionName(names, name) {
1847
1927
  function isDefined(value) {
1848
1928
  return value !== undefined;
1849
1929
  }
1930
+ /**
1931
+ * Resolve the delegation capability from server-owned, persisted session
1932
+ * provenance. A copied marker is not enough: the child header and reciprocal
1933
+ * parent link must identify the exact same session files.
1934
+ */
1935
+ export async function sessionAllowsDelegationTools(sessionManager, managers) {
1936
+ const trackedLink = await verifiedTrackedSubsessionLink(managers, {
1937
+ sessionId: sessionManager.getSessionId(),
1938
+ sessionFile: sessionManager.getSessionFile(),
1939
+ sessionManager,
1940
+ cwd: sessionManager.getCwd(),
1941
+ });
1942
+ return trackedLink === undefined;
1943
+ }
1944
+ async function verifiedTrackedSubsessionLink(managers, session) {
1945
+ // Child markers are only hints; the current child header and reciprocal
1946
+ // parent custom link must agree on the exact ids and files before relinking.
1947
+ const entries = session.sessionManager.getEntries?.() ?? session.sessionManager.getBranch();
1948
+ let marker;
1949
+ for (const entry of entries) {
1950
+ const parsed = parsePersistedChildSubsessionLink(entry);
1951
+ if (parsed?.spawnedSessionId === session.sessionId)
1952
+ marker = parsed;
1953
+ }
1954
+ if (marker === undefined)
1955
+ return undefined;
1956
+ const childSessionFile = nonEmptyString(session.sessionFile);
1957
+ if (childSessionFile === undefined)
1958
+ return undefined;
1959
+ const childHeader = await readSessionHeaderSummary(childSessionFile);
1960
+ if (childHeader?.id !== session.sessionId)
1961
+ return undefined;
1962
+ const parentSessionFile = nonEmptyString(childHeader.parentSession);
1963
+ if (parentSessionFile === undefined)
1964
+ return undefined;
1965
+ const parentHeader = await readSessionHeaderSummary(parentSessionFile);
1966
+ if (parentHeader?.id !== marker.spawnedBySessionId)
1967
+ return undefined;
1968
+ const parentLink = findReciprocalParentSubsessionLink(managers, parentSessionFile, marker.spawnedBySessionId, session.sessionId, childSessionFile);
1969
+ if (parentLink === undefined)
1970
+ return undefined;
1971
+ return {
1972
+ parentSessionId: marker.spawnedBySessionId,
1973
+ childSessionId: session.sessionId,
1974
+ childSessionFile,
1975
+ parentSessionFile,
1976
+ cwd: parentLink.cwd ?? session.cwd,
1977
+ };
1978
+ }
1979
+ function findReciprocalParentSubsessionLink(managers, parentSessionFile, parentSessionId, childSessionId, childSessionFile) {
1980
+ let parentManager;
1981
+ try {
1982
+ parentManager = managers.open(parentSessionFile);
1983
+ }
1984
+ catch {
1985
+ return undefined;
1986
+ }
1987
+ const entries = parentManager.getEntries?.() ?? parentManager.getBranch();
1988
+ for (const entry of entries) {
1989
+ const link = parsePersistedParentSubsessionLink(entry);
1990
+ if (link === undefined)
1991
+ continue;
1992
+ if (link.spawnedBySessionId !== parentSessionId || link.spawnedSessionId !== childSessionId)
1993
+ continue;
1994
+ if (link.spawnedSessionFile === undefined || !sessionPathsEqual(link.spawnedSessionFile, childSessionFile))
1995
+ continue;
1996
+ return link;
1997
+ }
1998
+ return undefined;
1999
+ }
1850
2000
  function trackedSubsessionLinkFromParentLink(parentSessionId, link, parentSessionFile) {
1851
2001
  return {
1852
2002
  parentSessionId,