@chainingintention/pi-web-cn 1.202607.5 → 1.202607.6

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 (126) 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 +102 -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 +237 -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/sessionArchiveMigration.js +590 -0
  84. package/dist/server/sessions/sessionArchiveMigration.js.map +1 -0
  85. package/dist/server/sessions/sessionArchiveStore.js +7 -4
  86. package/dist/server/sessions/sessionArchiveStore.js.map +1 -1
  87. package/dist/server/sessions/sessionRoutes.js +15 -4
  88. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  89. package/dist/server/sessions/sessionService.js +2 -0
  90. package/dist/server/sessions/sessionService.js.map +1 -0
  91. package/dist/server/sessions/spawnSubsessionTool.js +71 -31
  92. package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
  93. package/dist/server/terminals/terminalService.js +2 -2
  94. package/dist/server/terminals/terminalService.js.map +1 -1
  95. package/dist/sessiond/activeAgentProfile.js +31 -0
  96. package/dist/sessiond/activeAgentProfile.js.map +1 -0
  97. package/dist/sessiond/sessionDaemonClient.js +38 -0
  98. package/dist/sessiond/sessionDaemonClient.js.map +1 -1
  99. package/dist/shared/activeAgentProfile.js +76 -0
  100. package/dist/shared/activeAgentProfile.js.map +1 -0
  101. package/dist/shared/apiTypes.d.ts +26 -0
  102. package/dist/shared/apiTypes.js +2 -0
  103. package/dist/shared/apiTypes.js.map +1 -1
  104. package/dist/shared/capabilities.js +5 -0
  105. package/dist/shared/capabilities.js.map +1 -1
  106. package/dist/shared/federatedRoutes.js +1 -0
  107. package/dist/shared/federatedRoutes.js.map +1 -1
  108. package/dist/shared/piWebConfigParsing.js +0 -5
  109. package/dist/shared/piWebConfigParsing.js.map +1 -1
  110. package/dist/shared/piWebStatusParsing.js +6 -0
  111. package/dist/shared/piWebStatusParsing.js.map +1 -1
  112. package/dist/shared/thinkingLevels.d.ts +1 -1
  113. package/dist/shared/thinkingLevels.js +1 -1
  114. package/dist/shared/thinkingLevels.js.map +1 -1
  115. package/docs/config.md +63 -9
  116. package/docs/plugins.md +18 -5
  117. package/package.json +35 -31
  118. package/dist/client/assets/CodeViewer-CfZodNYb.js +0 -4
  119. package/dist/client/assets/TerminalPanel-eua763mA.js +0 -126
  120. package/dist/client/assets/UnifiedDiffViewer-C9-QeGMj.js +0 -34
  121. package/dist/client/assets/index-BiFWXoRZ.js +0 -3084
  122. package/dist/client/assets/vendor-editor-core-C7aWiQy0.js +0 -12
  123. package/dist/client/assets/vendor-editor-languages-EW34FfVy.js +0 -26
  124. package/dist/client/assets/vendor-editor-legacy-B4QLsWF8.js +0 -1
  125. package/dist/client/assets/vendor-terminal-DDGTF8rc.css +0 -1
  126. package/dist/client/assets/vendor-terminal-DjQ08hXu.js +0 -16
@@ -1,19 +1,19 @@
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
17
  import { createSpawnSessionToolDefinition } from "./spawnSessionTool.js";
18
18
  import { createSubsessionToolDefinitions } from "./spawnSubsessionTool.js";
19
19
  import { buildTranscriptView } from "./subsessionTranscript.js";
@@ -81,46 +81,46 @@ function pathInsideOrEqual(parent, child) {
81
81
  function defaultCreateAgentRuntime(createRuntime, options) {
82
82
  if (!(options.sessionManager instanceof SessionManager))
83
83
  throw new Error("Default runtime creation requires an SDK SessionManager");
84
- const runtimeFactory = createRuntimeWithContextAndOneShotInitialModel(createRuntime, options.managementContext, options.initialModel);
84
+ const runtimeFactory = createRuntimeWithContextAndOneShotInitialModel(createRuntime, options.managementContext, options.initialModel, options.delegationToolsEnabled);
85
85
  return createAgentSessionRuntime(runtimeFactory, {
86
86
  cwd: options.cwd,
87
87
  agentDir: options.agentDir,
88
88
  sessionManager: options.sessionManager,
89
89
  });
90
90
  }
91
- function createRuntimeWithContextAndOneShotInitialModel(createRuntime, managementContext, initialModel) {
91
+ function createRuntimeWithContextAndOneShotInitialModel(createRuntime, managementContext, initialModel, delegationToolsEnabled) {
92
92
  // The inherited model belongs only to the session being spawned. Do not keep
93
93
  // reapplying it if that runtime later creates/forks/switches sessions itself.
94
94
  let pendingInitialModel = initialModel;
95
+ let pendingDelegationToolsEnabled = delegationToolsEnabled;
95
96
  return async (options) => {
96
97
  const model = pendingInitialModel;
98
+ const toolsEnabled = pendingDelegationToolsEnabled;
97
99
  pendingInitialModel = undefined;
100
+ pendingDelegationToolsEnabled = undefined;
98
101
  return createRuntime({
99
102
  ...options,
100
103
  ...(managementContext === undefined ? {} : { managementContext }),
101
104
  ...(model === undefined ? {} : { initialModel: model }),
105
+ ...(toolsEnabled === undefined ? {} : { delegationToolsEnabled: toolsEnabled }),
102
106
  });
103
107
  };
104
108
  }
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;
109
+ function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRuntime, spawn, subsessions) {
110
+ return async ({ cwd, agentDir, sessionManager, sessionStartEvent, managementContext, initialModel, delegationToolsEnabled = true }) => {
111
+ const scopedModelRuntime = managementContext === undefined ? await normalModelRuntimeForCwd(cwd) : await managementModelRuntime();
109
112
  if (managementContext !== undefined) {
110
- return createManagementRuntimeFactory(authStorage, scopedModelRegistry, spawn, subsessions, managementContext)({
113
+ return createManagementRuntimeFactory(scopedModelRuntime, spawn, subsessions, managementContext)({
111
114
  cwd,
112
115
  agentDir,
113
116
  sessionManager,
117
+ delegationToolsEnabled,
114
118
  ...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
115
119
  ...(initialModel === undefined ? {} : { initialModel }),
116
120
  });
117
121
  }
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
- ];
122
+ const services = await createAgentSessionServices({ cwd, agentDir, modelRuntime: scopedModelRuntime });
123
+ const customTools = createPiWebCustomToolDefinitions(cwd, delegationToolsEnabled, spawn, subsessions);
124
124
  const result = await createAgentSessionFromServices({
125
125
  services,
126
126
  sessionManager,
@@ -131,15 +131,14 @@ function createDefaultRuntimeFactory(modelRegistry, managementModelRegistry, spa
131
131
  return { ...result, services, diagnostics: services.diagnostics };
132
132
  };
133
133
  }
134
- function createManagementRuntimeFactory(authStorage, modelRegistry, spawn, subsessions, managementContext) {
135
- return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel }) => {
134
+ function createManagementRuntimeFactory(modelRuntime, spawn, subsessions, managementContext) {
135
+ return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, delegationToolsEnabled = true }) => {
136
136
  const policyAgentDir = await writeManagementPermissionSystemPolicy(agentDir, cwd, managementContext);
137
137
  return withRuntimeCreationEnvironment({ [PI_PERMISSION_SYSTEM_POLICY_AGENT_DIR]: policyAgentDir }, async () => {
138
138
  const services = await createAgentSessionServices({
139
139
  cwd,
140
140
  agentDir,
141
- authStorage,
142
- modelRegistry,
141
+ modelRuntime,
143
142
  resourceLoaderOptions: {
144
143
  agentsFilesOverride: (base) => filterManagedGlobalContextFiles(cwd, agentDir, base),
145
144
  },
@@ -148,8 +147,8 @@ function createManagementRuntimeFactory(authStorage, modelRegistry, spawn, subse
148
147
  const customTools = [
149
148
  ...managedAgentToolDefinitions(cwd, managedToolOptions),
150
149
  createManagedPythonToolDefinition(cwd, managementContext),
151
- ...(spawn === undefined ? [] : [defineTool(createSpawnSessionToolDefinition(cwd, { spawn }))]),
152
- ...(subsessions === undefined ? [] : createSubsessionToolDefinitions(cwd, subsessions).map((tool) => defineTool(tool))),
150
+ ...(delegationToolsEnabled && spawn !== undefined ? [defineTool(createSpawnSessionToolDefinition(cwd, { spawn }))] : []),
151
+ ...(delegationToolsEnabled && subsessions !== undefined ? createSubsessionToolDefinitions(cwd, subsessions).map((tool) => defineTool(tool)) : []),
153
152
  ];
154
153
  const options = {
155
154
  services,
@@ -164,6 +163,13 @@ function createManagementRuntimeFactory(authStorage, modelRegistry, spawn, subse
164
163
  });
165
164
  };
166
165
  }
166
+ export function createPiWebCustomToolDefinitions(cwd, delegationEnabled, spawn, subsessions) {
167
+ return [
168
+ ...managedAgentToolDefinitions(cwd),
169
+ ...(delegationEnabled && spawn !== undefined ? [defineTool(createSpawnSessionToolDefinition(cwd, { spawn }))] : []),
170
+ ...(delegationEnabled && subsessions !== undefined ? createSubsessionToolDefinitions(cwd, subsessions).map((tool) => defineTool(tool)) : []),
171
+ ];
172
+ }
167
173
  function managedAgentToolDefinitions(cwd, options) {
168
174
  if (options === undefined)
169
175
  return [createPiWebEditToolDefinition(cwd)];
@@ -198,9 +204,10 @@ function createPiWebEditToolDefinition(cwd, options) {
198
204
  });
199
205
  }
200
206
  export class PiSessionService {
201
- constructor(events, deps = {}) {
207
+ constructor(events, deps) {
202
208
  this.events = events;
203
209
  this.active = new Map();
210
+ this.pendingSessionOpens = new Map();
204
211
  this.activities = new Map();
205
212
  this.compactionPromptQueues = new Map();
206
213
  this.compactionDrainTimers = new Map();
@@ -221,16 +228,28 @@ export class PiSessionService {
221
228
  this.subsessionNotifyArmed = new Map();
222
229
  this.archiveStore = deps.archiveStore ?? new SessionArchiveStore();
223
230
  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;
231
+ this.sessionManager = deps.sessionManager ?? createPiSessionManagerGateway({
232
+ agentDir: this.agentDir,
233
+ env: process.env,
234
+ sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"],
235
+ });
236
+ let defaultModelRuntime;
237
+ const resolveDefaultModelRuntime = () => {
238
+ defaultModelRuntime ??= deps.modelRuntime === undefined ? ModelRuntime.create() : Promise.resolve(deps.modelRuntime);
239
+ return defaultModelRuntime;
240
+ };
241
+ this.normalModelRuntimeForCwd = deps.normalModelRuntimeForCwd ?? resolveDefaultModelRuntime;
242
+ const managementModelRuntime = deps.managementModelRuntime;
243
+ this.managementModelRuntime = managementModelRuntime === undefined
244
+ ? resolveDefaultModelRuntime
245
+ : () => Promise.resolve(managementModelRuntime);
227
246
  this.spawnTargets = deps.spawnTargets;
228
247
  this.logger = deps.logger ?? noopLogger;
229
248
  this.now = deps.now ?? (() => new Date());
230
249
  // Subsessions are a beta capability gated behind their own flag, and they
231
250
  // also require the spawn capability (they share its project-scope resolver).
232
251
  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 : {
252
+ this.createRuntime = deps.createRuntime ?? createDefaultRuntimeFactory(this.normalModelRuntimeForCwd, this.managementModelRuntime, this.spawnTargets === undefined ? undefined : (input) => this.spawnSession(input), !subsessionsActive ? undefined : {
234
253
  spawn: (input) => this.spawnSubsession(input),
235
254
  list: (parentSessionId, parentSessionFile) => this.listSubsessions(parentSessionId, parentSessionFile),
236
255
  check: (parentSessionId, sessionId, parentSessionFile) => this.checkSubsession(parentSessionId, sessionId, parentSessionFile),
@@ -298,8 +317,12 @@ export class PiSessionService {
298
317
  async dispose() {
299
318
  clearInterval(this.heartbeat);
300
319
  this.clearCompactionDrainTimers();
320
+ const pendingOpens = this.pendingSessionOpenPromises();
321
+ if (pendingOpens.length > 0)
322
+ await Promise.allSettled(pendingOpens);
301
323
  const activeSessions = Array.from(new Set(this.active.values()));
302
324
  this.active.clear();
325
+ this.pendingSessionOpens.clear();
303
326
  this.activities.clear();
304
327
  this.compactionPromptQueues.clear();
305
328
  this.authLossWarnings.clear();
@@ -316,6 +339,8 @@ export class PiSessionService {
316
339
  }));
317
340
  }
318
341
  async list(cwd, managementContext) {
342
+ if (managementContext === undefined)
343
+ await this.normalModelRuntimeForCwd(cwd);
319
344
  const [sessions, archivedRecords] = await Promise.all([this.sessionManager.list(cwd), this.archiveStore.list()]);
320
345
  const sessionsById = new Map(sessions.map((session) => [session.id, session]));
321
346
  const archivedForCwd = await Promise.all(archivedRecords
@@ -331,9 +356,15 @@ export class PiSessionService {
331
356
  return [...unarchivedSessions, ...archivedSessions];
332
357
  }
333
358
  async start(cwd, options = {}) {
359
+ if (options.managementContext === undefined)
360
+ await this.normalModelRuntimeForCwd(cwd);
361
+ return this.startSession(cwd, options);
362
+ }
363
+ async startSession(cwd, options) {
334
364
  const active = await this.create(this.sessionManager.create(cwd, options.parentSession === undefined ? undefined : { parentSession: options.parentSession }), cwd, {
335
365
  ...(options.managementContext === undefined ? {} : { managementContext: options.managementContext }),
336
366
  ...(options.initialModel === undefined ? {} : { initialModel: options.initialModel }),
367
+ ...(options.creationProvenance === undefined ? {} : { creationProvenance: options.creationProvenance }),
337
368
  });
338
369
  const { session } = active.runtime;
339
370
  const created = {
@@ -382,9 +413,10 @@ export class PiSessionService {
382
413
  const decision = await this.spawnTargets.resolveSpawnTarget(input.spawningCwd, input.cwd);
383
414
  if (!decision.allowed)
384
415
  throw spawnTargetError(decision);
385
- const created = await this.start(decision.cwd, {
416
+ const created = await this.startSession(decision.cwd, {
386
417
  ...(input.parentSessionFile === undefined ? {} : { parentSession: input.parentSessionFile }),
387
418
  ...(input.model === undefined ? {} : { initialModel: input.model }),
419
+ creationProvenance: "tracked-subsession",
388
420
  });
389
421
  const parentSessionFile = nonEmptyString(input.parentSessionFile);
390
422
  const link = {
@@ -579,61 +611,13 @@ export class PiSessionService {
579
611
  return;
580
612
  this.registerVerifiedSubsession(link);
581
613
  }
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;
614
+ verifiedSubsessionLinkFromOpenedChild(session) {
615
+ return verifiedTrackedSubsessionLink(this.sessionManager, {
616
+ sessionId: session.sessionId,
617
+ sessionFile: session.sessionFile,
618
+ sessionManager: session.sessionManager,
619
+ cwd: session.sessionManager.getCwd(),
620
+ });
637
621
  }
638
622
  async getOrOpenTrackedSubsession(sessionId) {
639
623
  const link = this.subsessionLinks.get(sessionId);
@@ -670,6 +654,16 @@ export class PiSessionService {
670
654
  return "error";
671
655
  return "idle";
672
656
  }
657
+ workingSubsessionIds(parentSessionId) {
658
+ const childIds = this.subsessionChildren.get(parentSessionId);
659
+ if (childIds === undefined)
660
+ return [];
661
+ return [...childIds].filter((childId) => {
662
+ const link = this.subsessionLinks.get(childId);
663
+ const active = link === undefined ? undefined : this.activeChildForSubsessionLink(link);
664
+ return active !== undefined && this.hasActiveWork(active.runtime.session);
665
+ });
666
+ }
673
667
  /**
674
668
  * Drive parent notifications from a tracked child's status. Arms a pending
675
669
  * notification while the child is working, and when it stops fires a single
@@ -691,7 +685,11 @@ export class PiSessionService {
691
685
  const status = this.activityForSession(session)?.phase === "error" ? "error" : "idle";
692
686
  const finalText = finalAssistantText(historyMessages(session));
693
687
  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.`;
688
+ const workingIds = this.workingSubsessionIds(link.parentSessionId);
689
+ const next = workingIds.length === 0
690
+ ? "No other tracked subsessions are working."
691
+ : `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.`;
692
+ const text = `Subsession ${childId} stopped working (${status}).\n${next}\n\n--- SUBSESSION OUTPUT: ${childId} ---\n${preview}`;
695
693
  void this.notifyParentOfSubsession(link.parentSessionId, childId, text);
696
694
  }
697
695
  async getOrOpenParentForSubsession(parentSessionId, childSessionId) {
@@ -736,21 +734,21 @@ export class PiSessionService {
736
734
  }
737
735
  async availableModels(ref, managementContext) {
738
736
  const session = await this.getOrOpen(ref, managementContext);
739
- session.modelRegistry.refresh();
737
+ await session.modelRuntime.reloadConfig();
740
738
  const models = session.scopedModels.length > 0
741
739
  ? session.scopedModels.map((scoped) => scoped.model)
742
- : session.modelRegistry.getAvailable();
740
+ : await session.modelRuntime.getAvailable();
743
741
  return models.map(modelToClientModel);
744
742
  }
745
743
  async setModel(ref, provider, modelId, managementContext) {
746
744
  await this.assertWritable(ref);
747
745
  const session = await this.getOrOpen(ref, managementContext);
748
- session.modelRegistry.refresh();
746
+ await session.modelRuntime.reloadConfig();
749
747
  const candidates = session.scopedModels.length > 0
750
748
  ? session.scopedModels.map((scoped) => scoped.model)
751
- : session.modelRegistry.getAvailable();
749
+ : await session.modelRuntime.getAvailable();
752
750
  const model = candidates.find((candidate) => candidate.provider === provider && candidate.id === modelId)
753
- ?? session.modelRegistry.find(provider, modelId);
751
+ ?? session.modelRuntime.getModel(provider, modelId);
754
752
  if (model === undefined)
755
753
  throw new Error(`未找到模型:${provider}/${modelId}`);
756
754
  await session.setModel(model);
@@ -1121,6 +1119,14 @@ export class PiSessionService {
1121
1119
  this.publishActivity(active.runtime.session, "stopped", "idle");
1122
1120
  this.publishStatus(active.runtime.session);
1123
1121
  }
1122
+ async clearQueue(ref, managementContext) {
1123
+ await this.assertWritable(ref);
1124
+ const session = await this.getOrOpen(ref, managementContext);
1125
+ this.clearCompactionPromptQueue(session.sessionId);
1126
+ clearSessionQueue(session);
1127
+ this.publishStatus(session);
1128
+ return this.statusFromSession(session);
1129
+ }
1124
1130
  stop(ref, managementContext) {
1125
1131
  const active = this.activeForLookup(ref, managementContext);
1126
1132
  if (active === undefined)
@@ -1316,6 +1322,9 @@ export class PiSessionService {
1316
1322
  return [...names];
1317
1323
  }
1318
1324
  async closeActive(sessionId) {
1325
+ const pendingOpens = this.pendingSessionOpenPromises(sessionId);
1326
+ if (pendingOpens.length > 0)
1327
+ await Promise.allSettled(pendingOpens);
1319
1328
  const activeSessions = this.activeSessionsForId(sessionId);
1320
1329
  await Promise.all(activeSessions.map((active) => this.closeActiveSession(active)));
1321
1330
  }
@@ -1358,14 +1367,36 @@ export class PiSessionService {
1358
1367
  return active;
1359
1368
  const archived = await this.getArchived(ref);
1360
1369
  if (archived?.archivePath !== undefined) {
1361
- return this.create(this.sessionManager.open(archived.archivePath), archived.cwd, managementContext === undefined ? {} : { managementContext });
1370
+ const archivePath = archived.archivePath;
1371
+ return this.openPending(archived.sessionId, archived.cwd, managementContext, () => this.sessionManager.open(archivePath));
1362
1372
  }
1363
1373
  const match = isPiSessionRef(ref)
1364
1374
  ? (await this.sessionManager.list(ref.cwd)).find((s) => s.id === ref.id || s.id.startsWith(ref.id))
1365
1375
  : (await this.sessionManager.listAll?.() ?? []).find((s) => s.id === ref || s.id.startsWith(ref));
1366
1376
  if (!match)
1367
1377
  throw new Error("未找到会话");
1368
- return this.create(this.sessionManager.open(match.path), match.cwd, managementContext === undefined ? {} : { managementContext });
1378
+ return this.openPending(match.id, match.cwd, managementContext, () => this.sessionManager.open(match.path));
1379
+ }
1380
+ openPending(sessionId, cwd, managementContext, openSessionManager) {
1381
+ const key = JSON.stringify([canonicalizeStoredCwd(cwd), sessionId, managementContextKey(managementContext)]);
1382
+ const existing = this.pendingSessionOpens.get(key);
1383
+ if (existing !== undefined)
1384
+ return existing.promise;
1385
+ const pending = {
1386
+ sessionId,
1387
+ promise: this.create(openSessionManager(), cwd, managementContext === undefined ? {} : { managementContext }),
1388
+ };
1389
+ pending.promise = pending.promise.finally(() => {
1390
+ if (this.pendingSessionOpens.get(key) === pending)
1391
+ this.pendingSessionOpens.delete(key);
1392
+ });
1393
+ this.pendingSessionOpens.set(key, pending);
1394
+ return pending.promise;
1395
+ }
1396
+ pendingSessionOpenPromises(sessionId) {
1397
+ return [...this.pendingSessionOpens.values()]
1398
+ .filter((pending) => sessionId === undefined || pending.sessionId === sessionId)
1399
+ .map((pending) => pending.promise);
1369
1400
  }
1370
1401
  async getArchived(ref) {
1371
1402
  const archived = await this.archiveStore.get(sessionIdFromLookup(ref));
@@ -1425,25 +1456,53 @@ export class PiSessionService {
1425
1456
  return [...new Set(this.active.values())].filter((active) => active.runtime.session.sessionId === sessionId);
1426
1457
  }
1427
1458
  async create(sessionManager, cwd, options = {}) {
1459
+ const delegationToolsEnabled = options.creationProvenance !== "tracked-subsession"
1460
+ && await sessionAllowsDelegationTools(sessionManager, this.sessionManager);
1428
1461
  const runtime = await this.createAgentRuntime(this.createRuntime, {
1429
1462
  cwd,
1430
1463
  agentDir: this.agentDir,
1431
1464
  sessionManager,
1465
+ delegationToolsEnabled,
1432
1466
  ...(options.managementContext === undefined ? {} : { managementContext: options.managementContext }),
1433
1467
  ...(options.initialModel === undefined ? {} : { initialModel: options.initialModel }),
1434
1468
  });
1435
1469
  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);
1470
+ try {
1471
+ await this.bindSessionExtensions(runtime.session);
1441
1472
  this.bindRuntime(active);
1442
- await this.recoverSubsessionTrackingForOpenedSession(session);
1443
- });
1444
- await this.recoverSubsessionTrackingForOpenedSession(runtime.session);
1445
- this.publishStatus(runtime.session);
1446
- return active;
1473
+ runtime.setRebindSession(async (session) => {
1474
+ await this.bindSessionExtensions(session);
1475
+ this.bindRuntime(active);
1476
+ await this.recoverSubsessionTrackingForOpenedSession(session);
1477
+ });
1478
+ this.active.set(activeSessionKey(runtime.session.sessionId, active.eventScope), active);
1479
+ await this.recoverSubsessionTrackingForOpenedSession(runtime.session);
1480
+ this.publishStatus(runtime.session);
1481
+ return active;
1482
+ }
1483
+ catch (error) {
1484
+ active.unsubscribe();
1485
+ let removedActive = false;
1486
+ for (const [sessionKey, candidate] of this.active.entries()) {
1487
+ if (candidate !== active)
1488
+ continue;
1489
+ this.active.delete(sessionKey);
1490
+ this.activities.delete(sessionKey);
1491
+ removedActive = true;
1492
+ }
1493
+ this.clearAuthLossWarningsForSession(runtime.session.sessionId);
1494
+ this.clearCompactionPromptQueue(runtime.session.sessionId);
1495
+ if (removedActive) {
1496
+ this.workspaceActivity?.removeSession(runtime.session.sessionId, runtime.session.sessionManager.getCwd(), active.eventScope);
1497
+ }
1498
+ try {
1499
+ await runtime.session.abort();
1500
+ }
1501
+ finally {
1502
+ await runtime.dispose();
1503
+ }
1504
+ throw error;
1505
+ }
1447
1506
  }
1448
1507
  async bindSessionExtensions(session) {
1449
1508
  await session.bindExtensions({
@@ -1559,15 +1618,12 @@ export class PiSessionService {
1559
1618
  session.setSessionName(name);
1560
1619
  this.publishSessionName(session);
1561
1620
  }
1562
- applyAuthChange(change = {}) {
1563
- this.modelRegistry.refresh();
1564
- this.managementModelRegistry.refresh();
1565
- const changedScope = change.scope ?? "normal";
1621
+ applyAuthChange(change) {
1622
+ const changedRuntime = change.modelRuntime;
1566
1623
  for (const active of this.active.values()) {
1567
- if (authScopeFromEventScope(active.eventScope) !== changedScope)
1568
- continue;
1569
1624
  const { session } = active.runtime;
1570
- session.modelRegistry.refresh();
1625
+ if (session.modelRuntime !== changedRuntime)
1626
+ continue;
1571
1627
  this.syncCurrentModelAuthWarning(session, active.eventScope, change.removedProviderId);
1572
1628
  this.publishStatus(session);
1573
1629
  }
@@ -1579,10 +1635,10 @@ export class PiSessionService {
1579
1635
  if (model.provider === "unknown" && model.id === "unknown")
1580
1636
  return;
1581
1637
  const warningKey = authLossWarningKey(session.sessionId, eventScope, model.provider, model.id);
1582
- const registered = session.modelRegistry.find(model.provider, model.id);
1638
+ const registered = session.modelRuntime.getModel(model.provider, model.id);
1583
1639
  if (registered === undefined)
1584
1640
  return;
1585
- if (session.modelRegistry.hasConfiguredAuth(registered)) {
1641
+ if (session.modelRuntime.hasConfiguredAuth(registered.provider)) {
1586
1642
  this.authLossWarnings.delete(warningKey);
1587
1643
  return;
1588
1644
  }
@@ -1847,6 +1903,76 @@ function addSessionName(names, name) {
1847
1903
  function isDefined(value) {
1848
1904
  return value !== undefined;
1849
1905
  }
1906
+ /**
1907
+ * Resolve the delegation capability from server-owned, persisted session
1908
+ * provenance. A copied marker is not enough: the child header and reciprocal
1909
+ * parent link must identify the exact same session files.
1910
+ */
1911
+ export async function sessionAllowsDelegationTools(sessionManager, managers) {
1912
+ const trackedLink = await verifiedTrackedSubsessionLink(managers, {
1913
+ sessionId: sessionManager.getSessionId(),
1914
+ sessionFile: sessionManager.getSessionFile(),
1915
+ sessionManager,
1916
+ cwd: sessionManager.getCwd(),
1917
+ });
1918
+ return trackedLink === undefined;
1919
+ }
1920
+ async function verifiedTrackedSubsessionLink(managers, session) {
1921
+ // Child markers are only hints; the current child header and reciprocal
1922
+ // parent custom link must agree on the exact ids and files before relinking.
1923
+ const entries = session.sessionManager.getEntries?.() ?? session.sessionManager.getBranch();
1924
+ let marker;
1925
+ for (const entry of entries) {
1926
+ const parsed = parsePersistedChildSubsessionLink(entry);
1927
+ if (parsed?.spawnedSessionId === session.sessionId)
1928
+ marker = parsed;
1929
+ }
1930
+ if (marker === undefined)
1931
+ return undefined;
1932
+ const childSessionFile = nonEmptyString(session.sessionFile);
1933
+ if (childSessionFile === undefined)
1934
+ return undefined;
1935
+ const childHeader = await readSessionHeaderSummary(childSessionFile);
1936
+ if (childHeader?.id !== session.sessionId)
1937
+ return undefined;
1938
+ const parentSessionFile = nonEmptyString(childHeader.parentSession);
1939
+ if (parentSessionFile === undefined)
1940
+ return undefined;
1941
+ const parentHeader = await readSessionHeaderSummary(parentSessionFile);
1942
+ if (parentHeader?.id !== marker.spawnedBySessionId)
1943
+ return undefined;
1944
+ const parentLink = findReciprocalParentSubsessionLink(managers, parentSessionFile, marker.spawnedBySessionId, session.sessionId, childSessionFile);
1945
+ if (parentLink === undefined)
1946
+ return undefined;
1947
+ return {
1948
+ parentSessionId: marker.spawnedBySessionId,
1949
+ childSessionId: session.sessionId,
1950
+ childSessionFile,
1951
+ parentSessionFile,
1952
+ cwd: parentLink.cwd ?? session.cwd,
1953
+ };
1954
+ }
1955
+ function findReciprocalParentSubsessionLink(managers, parentSessionFile, parentSessionId, childSessionId, childSessionFile) {
1956
+ let parentManager;
1957
+ try {
1958
+ parentManager = managers.open(parentSessionFile);
1959
+ }
1960
+ catch {
1961
+ return undefined;
1962
+ }
1963
+ const entries = parentManager.getEntries?.() ?? parentManager.getBranch();
1964
+ for (const entry of entries) {
1965
+ const link = parsePersistedParentSubsessionLink(entry);
1966
+ if (link === undefined)
1967
+ continue;
1968
+ if (link.spawnedBySessionId !== parentSessionId || link.spawnedSessionId !== childSessionId)
1969
+ continue;
1970
+ if (link.spawnedSessionFile === undefined || !sessionPathsEqual(link.spawnedSessionFile, childSessionFile))
1971
+ continue;
1972
+ return link;
1973
+ }
1974
+ return undefined;
1975
+ }
1850
1976
  function trackedSubsessionLinkFromParentLink(parentSessionId, link, parentSessionFile) {
1851
1977
  return {
1852
1978
  parentSessionId,