@chainingintention/pi-web-cn 1.202608.5 → 1.202608.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.
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/{CodeViewer-BUT2Idcq.js → CodeViewer-CWKEeX0r.js} +1 -1
- package/dist/client/assets/{TerminalPanel-b8CZY7en.js → TerminalPanel-BmA7Ayzk.js} +1 -1
- package/dist/client/assets/{UnifiedDiffViewer-CXY7AXrD.js → UnifiedDiffViewer-2DACwxFY.js} +1 -1
- package/dist/client/assets/{index-80SI7dCz.js → index-D6j67Izj.js} +491 -300
- package/dist/client/index.html +1 -1
- package/dist/config.js +86 -0
- package/dist/config.js.map +1 -1
- package/dist/pi-web-plugins/relays/relaysPanelElement.js +112 -46
- package/dist/server/app.js +4 -1
- package/dist/server/app.js.map +1 -1
- package/dist/server/audit/managementAuditStore.js +211 -0
- package/dist/server/audit/managementAuditStore.js.map +1 -0
- package/dist/server/audit/normalToolAuditCli.js +143 -0
- package/dist/server/audit/normalToolAuditCli.js.map +1 -0
- package/dist/server/audit/normalToolAuditStore.js +264 -0
- package/dist/server/audit/normalToolAuditStore.js.map +1 -0
- package/dist/server/managementEmbed.js +19 -1
- package/dist/server/managementEmbed.js.map +1 -1
- package/dist/server/piWebStatus.js +7 -1
- package/dist/server/piWebStatus.js.map +1 -1
- package/dist/server/requestLogging.js +10 -7
- package/dist/server/requestLogging.js.map +1 -1
- package/dist/server/sessiond/sessionProxyRoutes.js +13 -7
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond/sessionServiceDependencies.js +1 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
- package/dist/server/sessiond.js +91 -4
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/extensionDialogWaiters.js +76 -0
- package/dist/server/sessions/extensionDialogWaiters.js.map +1 -0
- package/dist/server/sessions/managementPermissionSystem.js +8 -8
- package/dist/server/sessions/managementPermissionSystem.js.map +1 -1
- package/dist/server/sessions/pendingExtensionDialogStore.js +196 -0
- package/dist/server/sessions/pendingExtensionDialogStore.js.map +1 -0
- package/dist/server/sessions/piSessionService.js +493 -78
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionRoutes.js +54 -27
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/sessionUiEvents.js +9 -2
- package/dist/server/sessions/sessionUiEvents.js.map +1 -1
- package/dist/server/sessions/spawnSessionTool.js +8 -1
- package/dist/server/sessions/spawnSessionTool.js.map +1 -1
- package/dist/server/sessions/spawnSubsessionTool.js +7 -1
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
- package/dist/server/workbench/accessStateRoutes.js +30 -0
- package/dist/server/workbench/accessStateRoutes.js.map +1 -0
- package/dist/server/workbench/accessStateStore.js +29 -0
- package/dist/server/workbench/accessStateStore.js.map +1 -0
- package/dist/server/workbench/capabilitySearch.js +62 -0
- package/dist/server/workbench/capabilitySearch.js.map +1 -0
- package/dist/server/workbench/gatewayIntegration.js +56 -0
- package/dist/server/workbench/gatewayIntegration.js.map +1 -0
- package/dist/server/workbench/mcpClient.js +148 -0
- package/dist/server/workbench/mcpClient.js.map +1 -0
- package/dist/server/workbench/skillSync.js +375 -0
- package/dist/server/workbench/skillSync.js.map +1 -0
- package/dist/server/workbench/types.js +2 -0
- package/dist/server/workbench/types.js.map +1 -0
- package/dist/server/workbench/workbenchClient.js +136 -0
- package/dist/server/workbench/workbenchClient.js.map +1 -0
- package/dist/server/workbench/workbenchTools.js +145 -0
- package/dist/server/workbench/workbenchTools.js.map +1 -0
- package/dist/shared/apiTypes.d.ts +141 -1
- package/dist/shared/apiTypes.js +8 -0
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/federatedRoutes.js +2 -0
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/piWebConfigParsing.js +94 -0
- package/dist/shared/piWebConfigParsing.js.map +1 -1
- package/docs/config.md +86 -1
- package/docs/plugins.md +14 -0
- package/package.json +7 -3
|
@@ -23,10 +23,13 @@ import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
|
|
|
23
23
|
import { readSessionHeaderSummary } from "./sessionFileHeader.js";
|
|
24
24
|
import { countOutOfListingChildren, locateOutOfListingParents } from "./parentSessionLocator.js";
|
|
25
25
|
import { siblingWorkspaceCwds } from "../workspaces/projectWorkspaceCwds.js";
|
|
26
|
-
import {
|
|
26
|
+
import { workbenchAccessHandle } from "../managementEmbed.js";
|
|
27
|
+
import { DEFAULT_EXTENSION_DIALOGS_TIMEOUT_MS, piWebDataDir } from "../../config.js";
|
|
27
28
|
import { createScopedSettingsManager, resolveSettingsScopeDirectory } from "./projectSettingsScope.js";
|
|
28
29
|
import { createAskUserToolDefinition } from "./askUserTool.js";
|
|
29
30
|
import { PendingAskStore, renderAskUserAnswersText } from "./pendingAskStore.js";
|
|
31
|
+
import { PendingExtensionDialogStore } from "./pendingExtensionDialogStore.js";
|
|
32
|
+
import { ExtensionDialogWaiters, effectiveExtensionDialogTimeoutMs, extensionDialogCancelValue } from "./extensionDialogWaiters.js";
|
|
30
33
|
import { createSpawnSessionToolDefinition } from "./spawnSessionTool.js";
|
|
31
34
|
import { createSubsessionToolDefinitions } from "./spawnSubsessionTool.js";
|
|
32
35
|
import { buildTranscriptView } from "./subsessionTranscript.js";
|
|
@@ -37,7 +40,9 @@ import { SessionNotificationStore } from "./sessionNotificationStore.js";
|
|
|
37
40
|
import { plainTextTheme } from "./plainTextTheme.js";
|
|
38
41
|
import { SessionUnreadStore } from "./sessionUnreadStore.js";
|
|
39
42
|
import { archiveCandidateFromActiveSession, archiveCandidateFromArchivedRecord, archiveCandidateFromListEntry, archiveInputFromActiveSession, archiveInputFromCandidate, archiveInputFromListEntry, clientSessionFromArchivedRecord, compareArchivedRecords, } from "./sessionArchiveMapping.js";
|
|
40
|
-
import { getBoolean, getProperty, getString, toClientEvent } from "./sessionUiEvents.js";
|
|
43
|
+
import { annotateAssistantThinkingLevel, getBoolean, getProperty, getString, toClientEvent } from "./sessionUiEvents.js";
|
|
44
|
+
import { WorkbenchHttpError } from "../workbench/workbenchClient.js";
|
|
45
|
+
import { createWorkbenchToolDefinitions } from "../workbench/workbenchTools.js";
|
|
41
46
|
const noopLogger = { info() { } };
|
|
42
47
|
const DEFAULT_UNREAD_PUBLICATION_RETRY_MS = 1_000;
|
|
43
48
|
/**
|
|
@@ -64,6 +69,28 @@ function spawnTargetError(decision) {
|
|
|
64
69
|
return new Error("派生会话不在已注册项目中");
|
|
65
70
|
return new Error(`cwd 必须是此项目的工作区。允许的路径:${decision.allowedCwds.join(", ")}`);
|
|
66
71
|
}
|
|
72
|
+
function modelSpecOf(model) {
|
|
73
|
+
return `${model.provider}/${model.id}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Parse a strict `provider/model-id` spec: split on the first `/` (model ids
|
|
77
|
+
* may themselves contain `/`) and require both parts to be non-empty.
|
|
78
|
+
*/
|
|
79
|
+
function parseModelSpec(spec) {
|
|
80
|
+
const slash = spec.indexOf("/");
|
|
81
|
+
if (slash <= 0 || slash === spec.length - 1)
|
|
82
|
+
return undefined;
|
|
83
|
+
return { provider: spec.slice(0, slash), modelId: spec.slice(slash + 1) };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Error for a spawn-tool model spec that matched nothing. States the facts —
|
|
87
|
+
* the bad spec and the required format — with deliberately no model list
|
|
88
|
+
* (a list would invite guesses). The agent loop turns the throw into an
|
|
89
|
+
* error tool result; how to recover is the agent's call.
|
|
90
|
+
*/
|
|
91
|
+
function unknownSpawnModelError(modelSpec) {
|
|
92
|
+
return new Error(`未找到模型“${modelSpec}”。请传入精确的“provider/model-id”。`);
|
|
93
|
+
}
|
|
67
94
|
function authLossWarningKey(sessionId, eventScope, provider, modelId) {
|
|
68
95
|
return `${eventScope}:${sessionId}:${provider}/${modelId}`;
|
|
69
96
|
}
|
|
@@ -76,6 +103,9 @@ function isPiSessionRef(ref) {
|
|
|
76
103
|
function lookupMatchesActiveSession(ref, active) {
|
|
77
104
|
return !isPiSessionRef(ref) || cwdPathsEqual(active.runtime.cwd, ref.cwd);
|
|
78
105
|
}
|
|
106
|
+
function lookupMatchesStartupSession(ref, session) {
|
|
107
|
+
return !isPiSessionRef(ref) || cwdPathsEqual(session.sessionManager.getCwd(), ref.cwd);
|
|
108
|
+
}
|
|
79
109
|
function activeSessionKey(sessionId, eventScope) {
|
|
80
110
|
return `${eventScope}\0${sessionId}`;
|
|
81
111
|
}
|
|
@@ -150,6 +180,22 @@ export function filterManagedProjectSkills(cwd, base) {
|
|
|
150
180
|
}),
|
|
151
181
|
};
|
|
152
182
|
}
|
|
183
|
+
export function filterManagedWorkbenchSkills(cwd, base, receipt) {
|
|
184
|
+
const projectRoot = realpathSync(cwd);
|
|
185
|
+
const allowedDirectories = new Set(receipt.skills.map((skill) => resolve(projectRoot, ".pi", "skills", skill.directory)));
|
|
186
|
+
return {
|
|
187
|
+
...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
|
+
}),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
153
199
|
function pathInsideOrEqual(parent, child) {
|
|
154
200
|
const normalizedParent = resolve(parent);
|
|
155
201
|
const normalizedChild = resolve(child);
|
|
@@ -262,42 +308,47 @@ export function dismissSessionWarning(session, dismissId) {
|
|
|
262
308
|
function defaultCreateAgentRuntime(createRuntime, options) {
|
|
263
309
|
if (!(options.sessionManager instanceof SessionManager))
|
|
264
310
|
throw new Error("Default runtime creation requires an SDK SessionManager");
|
|
265
|
-
const runtimeFactory =
|
|
311
|
+
const runtimeFactory = createRuntimeWithContextAndOneShotSessionOptions(createRuntime, options.managementContext, options.initialModel, options.initialThinkingLevel, options.delegationToolsEnabled);
|
|
266
312
|
return createAgentSessionRuntime(runtimeFactory, {
|
|
267
313
|
cwd: options.cwd,
|
|
268
314
|
agentDir: options.agentDir,
|
|
269
315
|
sessionManager: options.sessionManager,
|
|
270
316
|
});
|
|
271
317
|
}
|
|
272
|
-
function
|
|
273
|
-
//
|
|
274
|
-
//
|
|
318
|
+
function createRuntimeWithContextAndOneShotSessionOptions(createRuntime, managementContext, initialModel, initialThinkingLevel, delegationToolsEnabled) {
|
|
319
|
+
// These inherited options belong only to the session being spawned. A later
|
|
320
|
+
// runtime replacement restores its own model and thinking level from disk.
|
|
275
321
|
let pendingInitialModel = initialModel;
|
|
322
|
+
let pendingInitialThinkingLevel = initialThinkingLevel;
|
|
276
323
|
let pendingDelegationToolsEnabled = delegationToolsEnabled;
|
|
277
324
|
return async (options) => {
|
|
278
325
|
const model = pendingInitialModel;
|
|
326
|
+
const thinkingLevel = pendingInitialThinkingLevel;
|
|
279
327
|
const toolsEnabled = pendingDelegationToolsEnabled;
|
|
280
328
|
pendingInitialModel = undefined;
|
|
329
|
+
pendingInitialThinkingLevel = undefined;
|
|
281
330
|
pendingDelegationToolsEnabled = undefined;
|
|
282
331
|
return createRuntime({
|
|
283
332
|
...options,
|
|
284
333
|
...(managementContext === undefined ? {} : { managementContext }),
|
|
285
334
|
...(model === undefined ? {} : { initialModel: model }),
|
|
335
|
+
...(thinkingLevel === undefined ? {} : { initialThinkingLevel: thinkingLevel }),
|
|
286
336
|
...(toolsEnabled === undefined ? {} : { delegationToolsEnabled: toolsEnabled }),
|
|
287
337
|
});
|
|
288
338
|
};
|
|
289
339
|
}
|
|
290
|
-
function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRuntime, resolveSettingsScope, spawn, subsessions, askUser) {
|
|
291
|
-
return async ({ cwd, agentDir, sessionManager, sessionStartEvent, managementContext, initialModel, delegationToolsEnabled = true }) => {
|
|
340
|
+
function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRuntime, resolveSettingsScope, spawn, subsessions, askUser, workbench, logger = noopLogger, managementAudit, managementProjectIdForCwd) {
|
|
341
|
+
return async ({ cwd, agentDir, sessionManager, sessionStartEvent, managementContext, initialModel, initialThinkingLevel, delegationToolsEnabled = true }) => {
|
|
292
342
|
const scopedModelRuntime = managementContext === undefined ? await normalModelRuntimeForCwd(cwd) : await managementModelRuntime();
|
|
293
343
|
if (managementContext !== undefined) {
|
|
294
|
-
return createManagementRuntimeFactory(scopedModelRuntime, resolveSettingsScope, spawn, subsessions, askUser, managementContext)({
|
|
344
|
+
return createManagementRuntimeFactory(scopedModelRuntime, resolveSettingsScope, spawn, subsessions, askUser, managementContext, workbench, logger, managementAudit, managementProjectIdForCwd)({
|
|
295
345
|
cwd,
|
|
296
346
|
agentDir,
|
|
297
347
|
sessionManager,
|
|
298
348
|
delegationToolsEnabled,
|
|
299
349
|
...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
|
|
300
350
|
...(initialModel === undefined ? {} : { initialModel }),
|
|
351
|
+
...(initialThinkingLevel === undefined ? {} : { initialThinkingLevel }),
|
|
301
352
|
});
|
|
302
353
|
}
|
|
303
354
|
const settingsManager = await createScopedSettingsManager({
|
|
@@ -313,13 +364,46 @@ function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRu
|
|
|
313
364
|
customTools,
|
|
314
365
|
...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
|
|
315
366
|
...(initialModel === undefined ? {} : { model: initialModel }),
|
|
367
|
+
...(initialThinkingLevel === undefined ? {} : { thinkingLevel: initialThinkingLevel }),
|
|
316
368
|
});
|
|
317
369
|
return { ...result, services, diagnostics: services.diagnostics };
|
|
318
370
|
};
|
|
319
371
|
}
|
|
320
|
-
function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spawn, subsessions, askUser, managementContext) {
|
|
321
|
-
return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, delegationToolsEnabled = true }) => {
|
|
322
|
-
const
|
|
372
|
+
function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spawn, subsessions, askUser, managementContext, workbench, logger, managementAudit, managementProjectIdForCwd) {
|
|
373
|
+
return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, initialThinkingLevel, delegationToolsEnabled = true }) => {
|
|
374
|
+
const auditIdentity = await resolveManagementAuditIdentity(cwd, managementContext, managementProjectIdForCwd, logger);
|
|
375
|
+
const accessHandle = workbenchAccessHandle(managementContext);
|
|
376
|
+
const state = workbench === undefined ? undefined : workbench.accessStates.require(accessHandle);
|
|
377
|
+
let receipt;
|
|
378
|
+
try {
|
|
379
|
+
receipt = state === undefined ? undefined : await workbench?.skills.synchronize(cwd, state, {
|
|
380
|
+
...(managementAudit === undefined ? {} : { recorder: managementAudit }),
|
|
381
|
+
...auditIdentity,
|
|
382
|
+
sessionId: sessionManager.getSessionId(),
|
|
383
|
+
cwd,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
if (error instanceof WorkbenchHttpError && error.status === 401 && accessHandle !== undefined)
|
|
388
|
+
workbench?.accessStates.delete(accessHandle);
|
|
389
|
+
throw error;
|
|
390
|
+
}
|
|
391
|
+
const workbenchTools = state === undefined || workbench === undefined ? [] : createWorkbenchToolDefinitions({
|
|
392
|
+
getState: () => workbench.accessStates.require(accessHandle),
|
|
393
|
+
workbench: workbench.client,
|
|
394
|
+
mcp: workbench.mcp,
|
|
395
|
+
invalidate: () => { if (accessHandle !== undefined)
|
|
396
|
+
workbench.accessStates.delete(accessHandle); },
|
|
397
|
+
logger,
|
|
398
|
+
...(managementAudit === undefined ? {} : { audit: managementAudit }),
|
|
399
|
+
auditContext: {
|
|
400
|
+
...auditIdentity,
|
|
401
|
+
sessionId: sessionManager.getSessionId(),
|
|
402
|
+
cwd,
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
const workbenchToolNames = workbenchTools.map((tool) => tool.name);
|
|
406
|
+
const policyAgentDir = await writeManagementPermissionSystemPolicy(agentDir, cwd, managementContext, workbenchToolNames);
|
|
323
407
|
return withRuntimeCreationEnvironment({ [PI_PERMISSION_SYSTEM_POLICY_AGENT_DIR]: policyAgentDir }, async () => {
|
|
324
408
|
const settingsManager = await createScopedSettingsManager({
|
|
325
409
|
cwd,
|
|
@@ -333,24 +417,35 @@ function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spaw
|
|
|
333
417
|
settingsManager,
|
|
334
418
|
resourceLoaderOptions: {
|
|
335
419
|
agentsFilesOverride: (base) => filterManagedGlobalContextFiles(cwd, agentDir, base),
|
|
336
|
-
skillsOverride: (base) => filterManagedProjectSkills(cwd, base),
|
|
420
|
+
skillsOverride: (base) => receipt === undefined ? filterManagedProjectSkills(cwd, base) : filterManagedWorkbenchSkills(cwd, base, receipt),
|
|
337
421
|
},
|
|
338
422
|
});
|
|
339
423
|
const managedToolOptions = createManagedAgentToolOptions(cwd);
|
|
424
|
+
const scopedSpawn = spawn === undefined
|
|
425
|
+
? undefined
|
|
426
|
+
: (input) => spawn({ ...input, managementContext });
|
|
427
|
+
const scopedSubsessions = subsessions === undefined
|
|
428
|
+
? undefined
|
|
429
|
+
: {
|
|
430
|
+
...subsessions,
|
|
431
|
+
spawn: (input) => subsessions.spawn({ ...input, managementContext }),
|
|
432
|
+
};
|
|
340
433
|
const customTools = [
|
|
341
434
|
...managedAgentToolDefinitions(cwd, managedToolOptions),
|
|
342
435
|
createManagedPythonToolDefinition(cwd, managementContext),
|
|
343
|
-
...(delegationToolsEnabled &&
|
|
344
|
-
...(delegationToolsEnabled &&
|
|
436
|
+
...(delegationToolsEnabled && scopedSpawn !== undefined ? [defineTool(createSpawnSessionToolDefinition(cwd, { spawn: scopedSpawn }))] : []),
|
|
437
|
+
...(delegationToolsEnabled && scopedSubsessions !== undefined ? createSubsessionToolDefinitions(cwd, scopedSubsessions).map((tool) => defineTool(tool)) : []),
|
|
345
438
|
...(askUser === undefined ? [] : [createAskUserToolDefinition(askUser)]),
|
|
439
|
+
...workbenchTools,
|
|
346
440
|
];
|
|
347
441
|
const options = {
|
|
348
442
|
services,
|
|
349
443
|
sessionManager,
|
|
350
444
|
...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
|
|
351
445
|
customTools,
|
|
352
|
-
tools: managementAgentToolNames(managementContext),
|
|
446
|
+
tools: managementAgentToolNames(managementContext, workbenchToolNames),
|
|
353
447
|
...(initialModel === undefined ? {} : { model: initialModel }),
|
|
448
|
+
...(initialThinkingLevel === undefined ? {} : { thinkingLevel: initialThinkingLevel }),
|
|
354
449
|
};
|
|
355
450
|
const result = await createAgentSessionFromServices(options);
|
|
356
451
|
return { ...result, services, diagnostics: services.diagnostics };
|
|
@@ -404,6 +499,8 @@ export class PiSessionService {
|
|
|
404
499
|
this.active = new Map();
|
|
405
500
|
this.runtimeBySession = new WeakMap();
|
|
406
501
|
this.pendingSessionOpens = new Map();
|
|
502
|
+
/** Sessions whose extension binding is parked on a `session_start` dialog. */
|
|
503
|
+
this.startupSessions = new Map();
|
|
407
504
|
this.activities = new SessionActivityCoordinator();
|
|
408
505
|
/** Runtime-identity gate held while Pi may await abandoned-branch summarization. */
|
|
409
506
|
this.treeNavigations = new WeakSet();
|
|
@@ -434,6 +531,8 @@ export class PiSessionService {
|
|
|
434
531
|
*/
|
|
435
532
|
this.subsessionNotifyArmed = new Map();
|
|
436
533
|
this.notificationGenerationBySession = new WeakMap();
|
|
534
|
+
/** The parked extension Promise resolvers behind the store's open dialogs. */
|
|
535
|
+
this.dialogWaiters = new ExtensionDialogWaiters();
|
|
437
536
|
this.pendingUnreadMutations = [];
|
|
438
537
|
this.unreadPublicationFlushRequested = false;
|
|
439
538
|
this.unreadPublicationStopped = false;
|
|
@@ -474,10 +573,15 @@ export class PiSessionService {
|
|
|
474
573
|
this.spawnTargets = deps.spawnTargets;
|
|
475
574
|
this.projectWorkspaces = deps.projectWorkspaces;
|
|
476
575
|
this.logger = deps.logger ?? noopLogger;
|
|
576
|
+
this.normalToolAudit = deps.normalToolAudit;
|
|
577
|
+
this.managementAudit = deps.managementAudit;
|
|
578
|
+
this.managementProjectIdForCwd = deps.managementProjectIdForCwd;
|
|
477
579
|
this.now = deps.now ?? (() => new Date());
|
|
478
580
|
this.notificationStore = deps.notificationStore ?? new SessionNotificationStore();
|
|
479
581
|
this.unreadStore = deps.unreadStore ?? new SessionUnreadStore();
|
|
480
582
|
this.pendingAskStore = deps.pendingAskStore ?? new PendingAskStore();
|
|
583
|
+
this.pendingExtensionDialogStore = deps.pendingExtensionDialogStore ?? new PendingExtensionDialogStore();
|
|
584
|
+
this.extensionDialogsTimeoutMs = deps.extensionDialogsTimeoutMs ?? DEFAULT_EXTENSION_DIALOGS_TIMEOUT_MS;
|
|
481
585
|
this.catalogRefreshStatus = deps.catalogRefreshStatus;
|
|
482
586
|
this.unreadPublicationRetryInitialMs = Math.max(0, deps.unreadPublicationRetryDelayMs ?? DEFAULT_UNREAD_PUBLICATION_RETRY_MS);
|
|
483
587
|
this.unreadPublicationRetryDelayMs = this.unreadPublicationRetryInitialMs;
|
|
@@ -489,7 +593,7 @@ export class PiSessionService {
|
|
|
489
593
|
list: (parentSessionId, parentSessionFile) => this.listSubsessions(parentSessionId, parentSessionFile),
|
|
490
594
|
check: (parentSessionId, sessionId, parentSessionFile) => this.checkSubsession(parentSessionId, sessionId, parentSessionFile),
|
|
491
595
|
read: (parentSessionId, sessionId, query, parentSessionFile) => this.readSubsession(parentSessionId, sessionId, query, parentSessionFile),
|
|
492
|
-
}, deps.askUserEnabled === true ? { open: (input) => this.openAsk(input) } : undefined);
|
|
596
|
+
}, deps.askUserEnabled === true ? { open: (input) => this.openAsk(input) } : undefined, deps.workbench, this.logger, this.managementAudit, this.managementProjectIdForCwd);
|
|
493
597
|
this.createAgentRuntime = deps.createAgentRuntime ?? defaultCreateAgentRuntime;
|
|
494
598
|
this.workspaceActivity = deps.workspaceActivity;
|
|
495
599
|
this.heartbeat = setInterval(() => { this.publishHeartbeats(); }, deps.heartbeatIntervalMs ?? 2000);
|
|
@@ -578,6 +682,10 @@ export class PiSessionService {
|
|
|
578
682
|
this.clearUnreadPublicationRetry();
|
|
579
683
|
clearInterval(this.heartbeat);
|
|
580
684
|
this.clearCompactionDrainTimers();
|
|
685
|
+
// Same startup-park hazard as closeActive(): settle `session_start` dialogs
|
|
686
|
+
// of sessions still binding extensions before awaiting their pending opens.
|
|
687
|
+
for (const startup of this.startupSessions.values())
|
|
688
|
+
this.endSessionExtensionDialogs(startup.session);
|
|
581
689
|
const pendingOpens = this.pendingSessionOpenPromises();
|
|
582
690
|
if (pendingOpens.length > 0)
|
|
583
691
|
await Promise.allSettled(pendingOpens);
|
|
@@ -585,9 +693,11 @@ export class PiSessionService {
|
|
|
585
693
|
for (const active of activeSessions) {
|
|
586
694
|
this.forgetUnreadActivity(active.runtime.session);
|
|
587
695
|
this.pendingAskStore.forgetSession(active.runtime.session.sessionId);
|
|
696
|
+
this.endSessionExtensionDialogs(active.runtime.session);
|
|
588
697
|
}
|
|
589
698
|
this.active.clear();
|
|
590
699
|
this.pendingSessionOpens.clear();
|
|
700
|
+
this.startupSessions.clear();
|
|
591
701
|
this.activities.clear();
|
|
592
702
|
this.compactionPromptQueues.clear();
|
|
593
703
|
this.authLossWarnings.clear();
|
|
@@ -641,7 +751,7 @@ export class PiSessionService {
|
|
|
641
751
|
return { sessionIds };
|
|
642
752
|
}
|
|
643
753
|
async setPinned(ref, pinned, managementContext) {
|
|
644
|
-
if (
|
|
754
|
+
if (ref.cwd === "")
|
|
645
755
|
throw new Error("置顶会话必须提供工作区");
|
|
646
756
|
const sessions = await this.list(ref.cwd, managementContext);
|
|
647
757
|
const session = sessions.find((candidate) => candidate.id === ref.id);
|
|
@@ -745,6 +855,7 @@ export class PiSessionService {
|
|
|
745
855
|
const active = await this.create(this.sessionManager.create(cwd, options.parentSession === undefined ? undefined : { parentSession: options.parentSession }), cwd, {
|
|
746
856
|
...(options.managementContext === undefined ? {} : { managementContext: options.managementContext }),
|
|
747
857
|
...(options.initialModel === undefined ? {} : { initialModel: options.initialModel }),
|
|
858
|
+
...(options.initialThinkingLevel === undefined ? {} : { initialThinkingLevel: options.initialThinkingLevel }),
|
|
748
859
|
...(options.creationProvenance === undefined ? {} : { creationProvenance: options.creationProvenance }),
|
|
749
860
|
...(options.startupToken === undefined ? {} : { startupToken: options.startupToken }),
|
|
750
861
|
startupIntent: "create",
|
|
@@ -779,10 +890,27 @@ export class PiSessionService {
|
|
|
779
890
|
const decision = await this.spawnTargets.resolveSpawnTarget(input.spawningCwd, input.cwd);
|
|
780
891
|
if (!decision.allowed)
|
|
781
892
|
throw spawnTargetError(decision);
|
|
782
|
-
|
|
783
|
-
|
|
893
|
+
// A model spec overrides the inherited model. Only a spec triggers a
|
|
894
|
+
// spawning-session lookup; the default path must not depend on it.
|
|
895
|
+
let model = input.model;
|
|
896
|
+
if (input.modelSpec !== undefined) {
|
|
897
|
+
model = await this.resolveSpawnModel({ id: input.spawningSessionId, cwd: input.spawningCwd }, input.modelSpec, input.managementContext);
|
|
898
|
+
}
|
|
899
|
+
const created = await this.start(decision.cwd, {
|
|
900
|
+
...(model === undefined ? {} : { initialModel: model }),
|
|
901
|
+
...(input.thinkingLevel === undefined ? {} : { initialThinkingLevel: input.thinkingLevel }),
|
|
902
|
+
...(input.managementContext === undefined ? {} : { managementContext: input.managementContext }),
|
|
903
|
+
});
|
|
904
|
+
const modelUsed = this.activeForLookup(created.id, input.managementContext)?.runtime.session.model;
|
|
905
|
+
await this.prompt(created.id, input.prompt, undefined, undefined, {
|
|
906
|
+
...(input.managementContext === undefined ? {} : { managementContext: input.managementContext }),
|
|
907
|
+
});
|
|
784
908
|
this.logger.info({ spawningCwd: input.spawningCwd, sessionId: created.id, cwd: decision.cwd, promptLength: input.prompt.length }, "spawn_session started a new session");
|
|
785
|
-
return {
|
|
909
|
+
return {
|
|
910
|
+
sessionId: created.id,
|
|
911
|
+
cwd: decision.cwd,
|
|
912
|
+
...(modelUsed === undefined ? {} : { model: modelSpecOf(modelUsed) }),
|
|
913
|
+
};
|
|
786
914
|
}
|
|
787
915
|
/**
|
|
788
916
|
* Start a *tracked* child session on behalf of a LLM. Identical to
|
|
@@ -792,15 +920,23 @@ export class PiSessionService {
|
|
|
792
920
|
*/
|
|
793
921
|
async spawnSubsession(input) {
|
|
794
922
|
if (this.spawnTargets === undefined)
|
|
795
|
-
throw new Error("
|
|
923
|
+
throw new Error("派生会话已禁用");
|
|
796
924
|
const decision = await this.spawnTargets.resolveSpawnTarget(input.spawningCwd, input.cwd);
|
|
797
925
|
if (!decision.allowed)
|
|
798
926
|
throw spawnTargetError(decision);
|
|
927
|
+
// A model spec overrides the inherited model and is resolved against the
|
|
928
|
+
// parent's model runtime; only a spec triggers that lookup.
|
|
929
|
+
const model = input.modelSpec === undefined
|
|
930
|
+
? input.model
|
|
931
|
+
: await this.resolveSpawnModel({ id: input.parentSessionId, cwd: input.spawningCwd }, input.modelSpec, input.managementContext);
|
|
799
932
|
const created = await this.startSession(decision.cwd, {
|
|
800
933
|
...(input.parentSessionFile === undefined ? {} : { parentSession: input.parentSessionFile }),
|
|
801
|
-
...(
|
|
934
|
+
...(model === undefined ? {} : { initialModel: model }),
|
|
935
|
+
...(input.thinkingLevel === undefined ? {} : { initialThinkingLevel: input.thinkingLevel }),
|
|
936
|
+
...(input.managementContext === undefined ? {} : { managementContext: input.managementContext }),
|
|
802
937
|
creationProvenance: "tracked-subsession",
|
|
803
938
|
});
|
|
939
|
+
const modelUsed = this.activeForLookup(created.id, input.managementContext)?.runtime.session.model;
|
|
804
940
|
const parentSessionFile = nonEmptyString(input.parentSessionFile);
|
|
805
941
|
const link = {
|
|
806
942
|
parentSessionId: input.parentSessionId,
|
|
@@ -812,9 +948,45 @@ export class PiSessionService {
|
|
|
812
948
|
await this.registerVerifiedSubsession(link);
|
|
813
949
|
this.persistSubsessionLink(link);
|
|
814
950
|
this.persistSubsessionChildMarker(input.parentSessionId, created.id);
|
|
815
|
-
await this.prompt(created.id, input.prompt
|
|
951
|
+
await this.prompt(created.id, input.prompt, undefined, undefined, {
|
|
952
|
+
...(input.managementContext === undefined ? {} : { managementContext: input.managementContext }),
|
|
953
|
+
});
|
|
816
954
|
this.logger.info({ parentSessionId: input.parentSessionId, sessionId: created.id, cwd: decision.cwd, promptLength: input.prompt.length }, "spawn_subsession started a tracked child session");
|
|
817
|
-
return {
|
|
955
|
+
return {
|
|
956
|
+
sessionId: created.id,
|
|
957
|
+
cwd: decision.cwd,
|
|
958
|
+
...(modelUsed === undefined ? {} : { model: modelSpecOf(modelUsed) }),
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* The models a session may pick from: its scoped set when model-scoped,
|
|
963
|
+
* otherwise the runtime's available snapshot. Refreshes the runtime catalog
|
|
964
|
+
* first so callers see newly configured providers and models.
|
|
965
|
+
*/
|
|
966
|
+
async sessionModelCandidates(session) {
|
|
967
|
+
await session.modelRuntime.refresh();
|
|
968
|
+
return session.scopedModels.length > 0
|
|
969
|
+
? session.scopedModels.map((scoped) => scoped.model)
|
|
970
|
+
: await session.modelRuntime.getAvailable();
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Resolve a strict `provider/model-id` spec from a spawn tool against the
|
|
974
|
+
* *spawning* session's model runtime, using the same candidates
|
|
975
|
+
* {@link setModel} offers plus a direct runtime lookup as fallback. Unknown
|
|
976
|
+
* or malformed specs throw; the agent loop turns that into an error tool
|
|
977
|
+
* result the spawning agent can retry from.
|
|
978
|
+
*/
|
|
979
|
+
async resolveSpawnModel(spawningSession, modelSpec, managementContext) {
|
|
980
|
+
const session = await this.getOrOpen(spawningSession, managementContext);
|
|
981
|
+
const parsed = parseModelSpec(modelSpec);
|
|
982
|
+
const candidates = await this.sessionModelCandidates(session);
|
|
983
|
+
const model = parsed === undefined
|
|
984
|
+
? undefined
|
|
985
|
+
: candidates.find((candidate) => candidate.provider === parsed.provider && candidate.id === parsed.modelId)
|
|
986
|
+
?? session.modelRuntime.getModel(parsed.provider, parsed.modelId);
|
|
987
|
+
if (model === undefined)
|
|
988
|
+
throw unknownSpawnModelError(modelSpec);
|
|
989
|
+
return model;
|
|
818
990
|
}
|
|
819
991
|
/**
|
|
820
992
|
* Register the question set an agent wants the user to answer as the session's
|
|
@@ -895,14 +1067,129 @@ export class PiSessionService {
|
|
|
895
1067
|
this.publishStatus(session);
|
|
896
1068
|
}
|
|
897
1069
|
/**
|
|
898
|
-
*
|
|
899
|
-
*
|
|
1070
|
+
* Record the user's answer to an open extension dialog and resolve the
|
|
1071
|
+
* extension's parked Promise with it. Unlike an ask, nothing is delivered to
|
|
1072
|
+
* the model: the waiter is extension code inside an already in-flight run
|
|
1073
|
+
* (or an idle handler), so no custom message and no turn are triggered.
|
|
900
1074
|
*/
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1075
|
+
async answerDialog(ref, dialogId, value, managementContext) {
|
|
1076
|
+
await this.assertWritable(ref);
|
|
1077
|
+
const session = await this.sessionForStatusOrDialogClose(ref, managementContext);
|
|
1078
|
+
const result = this.pendingExtensionDialogStore.answer(this.dialogSessionKey(session), dialogId, value);
|
|
1079
|
+
if (result.status === "stale")
|
|
1080
|
+
return { result: "stale", sessionStatus: this.statusFromSession(session) };
|
|
1081
|
+
const { outcome } = result;
|
|
1082
|
+
this.publishDialogClosed(session, outcome);
|
|
1083
|
+
// `value` is what the store validated and recorded as the outcome's answer.
|
|
1084
|
+
this.dialogWaiters.settleWithAnswer(dialogId, value);
|
|
1085
|
+
this.publishStatus(session);
|
|
1086
|
+
return { result: "closed", outcome, sessionStatus: this.statusFromSession(session) };
|
|
1087
|
+
}
|
|
1088
|
+
/** Close an open extension dialog without an answer; the extension's wait settles with its kind's cancel value. */
|
|
1089
|
+
async cancelDialog(ref, dialogId, managementContext) {
|
|
1090
|
+
await this.assertWritable(ref);
|
|
1091
|
+
const session = await this.sessionForStatusOrDialogClose(ref, managementContext);
|
|
1092
|
+
const result = this.pendingExtensionDialogStore.cancel(this.dialogSessionKey(session), dialogId, "cancelled");
|
|
1093
|
+
if (result.status === "stale")
|
|
1094
|
+
return { result: "stale", sessionStatus: this.statusFromSession(session) };
|
|
1095
|
+
const { outcome } = result;
|
|
1096
|
+
this.publishDialogClosed(session, outcome);
|
|
1097
|
+
this.dialogWaiters.settleWithCancelValue(dialogId);
|
|
1098
|
+
this.publishStatus(session);
|
|
1099
|
+
return { result: "closed", outcome, sessionStatus: this.statusFromSession(session) };
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Implement one `ctx.ui.select()`/`confirm()`/`input()` call from extension
|
|
1103
|
+
* code: open the store record, tell the browsers, and park a Promise that
|
|
1104
|
+
* settles when the browser answers or cancels, the extension's own
|
|
1105
|
+
* `signal`/`timeout` dismisses the dialog, the daemon default timeout
|
|
1106
|
+
* elapses, or the runtime goes away. `store.open` validates the dialog, so a
|
|
1107
|
+
* malformed one rejects the extension's call rather than rendering garbage.
|
|
1108
|
+
* `async` so a rejected dialog becomes a rejection rather than a synchronous
|
|
1109
|
+
* throw from a promise-returning method.
|
|
1110
|
+
*/
|
|
1111
|
+
async openExtensionDialog(session, request, opts) {
|
|
1112
|
+
const signal = opts?.signal;
|
|
1113
|
+
// A pre-aborted signal dismisses the dialog before it ever opens.
|
|
1114
|
+
if (signal?.aborted === true)
|
|
1115
|
+
return extensionDialogCancelValue(request.kind);
|
|
1116
|
+
const timeoutMs = effectiveExtensionDialogTimeoutMs(opts?.timeout, this.extensionDialogsTimeoutMs);
|
|
1117
|
+
const dialog = this.pendingExtensionDialogStore.open({
|
|
1118
|
+
sessionId: this.dialogSessionKey(session),
|
|
1119
|
+
kind: request.kind,
|
|
1120
|
+
title: request.title,
|
|
1121
|
+
...(request.message === undefined ? {} : { message: request.message }),
|
|
1122
|
+
...(request.options === undefined ? {} : { options: request.options }),
|
|
1123
|
+
...(request.placeholder === undefined ? {} : { placeholder: request.placeholder }),
|
|
1124
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
1125
|
+
runScoped: session.isStreaming,
|
|
1126
|
+
});
|
|
1127
|
+
this.events.publish(session.sessionId, { type: "dialog.opened", dialog }, this.eventScopeForSession(session));
|
|
1128
|
+
this.publishStatus(session);
|
|
1129
|
+
return this.dialogWaiters.park(dialog, {
|
|
1130
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
1131
|
+
...(signal === undefined ? {} : { signal }),
|
|
1132
|
+
onTrigger: (reason) => {
|
|
1133
|
+
if (this.closeExtensionDialogFromTrigger(session, dialog.dialogId, reason))
|
|
1134
|
+
this.publishStatus(session);
|
|
1135
|
+
},
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Close a dialog whose wait ended without the browser (timeout, signal
|
|
1140
|
+
* abort, run end, runtime teardown) and settle its parked Promise. Returns
|
|
1141
|
+
* whether this call closed the dialog; a stale close means a browser answer
|
|
1142
|
+
* or an earlier trigger already settled everything.
|
|
1143
|
+
*/
|
|
1144
|
+
closeExtensionDialogFromTrigger(session, dialogId, reason) {
|
|
1145
|
+
const result = this.pendingExtensionDialogStore.cancel(this.dialogSessionKey(session), dialogId, reason);
|
|
1146
|
+
if (result.status !== "closed")
|
|
1147
|
+
return false;
|
|
1148
|
+
this.publishDialogClosed(session, result.outcome);
|
|
1149
|
+
this.dialogWaiters.settleWithCancelValue(dialogId);
|
|
1150
|
+
return true;
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Settle the session's run-scoped dialogs as `"aborted"`. Runs at
|
|
1154
|
+
* abort-request time (a user abort parks the agent loop behind the dialog
|
|
1155
|
+
* handler, so `agent_end` would never arrive on its own) and again from
|
|
1156
|
+
* the `agent_end` observer as the run-crash backstop — the store makes the
|
|
1157
|
+
* second settlement a stale no-op. Idle-opened dialogs (a `session_start`
|
|
1158
|
+
* probe, say) are not run-scoped and survive, because their waiter
|
|
1159
|
+
* outlives the run.
|
|
1160
|
+
*/
|
|
1161
|
+
abortRunScopedExtensionDialogs(session) {
|
|
1162
|
+
let closedAny = false;
|
|
1163
|
+
for (const dialog of this.pendingExtensionDialogStore.pendingDialogs(this.dialogSessionKey(session))) {
|
|
1164
|
+
if (dialog.runScoped)
|
|
1165
|
+
closedAny = this.closeExtensionDialogFromTrigger(session, dialog.dialogId, "aborted") || closedAny;
|
|
1166
|
+
}
|
|
1167
|
+
if (closedAny)
|
|
1168
|
+
this.publishStatus(session);
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Settle every dialog of the session as `"session-ended"`: the runtime
|
|
1172
|
+
* whose extension code is parked on them is being closed, replaced, or
|
|
1173
|
+
* disposed, so those Promises would otherwise never settle.
|
|
1174
|
+
*/
|
|
1175
|
+
endSessionExtensionDialogs(session) {
|
|
1176
|
+
let closedAny = false;
|
|
1177
|
+
for (const dialog of this.pendingExtensionDialogStore.pendingDialogs(this.dialogSessionKey(session))) {
|
|
1178
|
+
closedAny = this.closeExtensionDialogFromTrigger(session, dialog.dialogId, "session-ended") || closedAny;
|
|
1179
|
+
}
|
|
1180
|
+
// Publishes only while the session is still (or already re-)registered as
|
|
1181
|
+
// active, so teardown paths stay silent and runtime replacement refreshes.
|
|
1182
|
+
if (closedAny)
|
|
904
1183
|
this.publishStatus(session);
|
|
905
1184
|
}
|
|
1185
|
+
publishDialogClosed(session, outcome) {
|
|
1186
|
+
this.events.publish(session.sessionId, {
|
|
1187
|
+
type: "dialog.closed",
|
|
1188
|
+
dialogId: outcome.dialogId,
|
|
1189
|
+
reason: outcome.reason,
|
|
1190
|
+
...(outcome.answer === undefined ? {} : { answer: outcome.answer }),
|
|
1191
|
+
}, this.eventScopeForSession(session));
|
|
1192
|
+
}
|
|
906
1193
|
/** Summaries of the tracked subsessions spawned by `parentSessionId`. */
|
|
907
1194
|
async listSubsessions(parentSessionId, parentSessionFile) {
|
|
908
1195
|
const parentFile = nonEmptyString(parentSessionFile);
|
|
@@ -1227,7 +1514,7 @@ export class PiSessionService {
|
|
|
1227
1514
|
return pageMessagesAtSafeBoundary(historyMessages(session), page);
|
|
1228
1515
|
}
|
|
1229
1516
|
async status(ref, managementContext) {
|
|
1230
|
-
return this.statusFromSession(await this.
|
|
1517
|
+
return this.statusFromSession(await this.sessionForStatusOrDialogClose(ref, managementContext));
|
|
1231
1518
|
}
|
|
1232
1519
|
async searchCandidates(cwd, normalizedQuery, managementContext) {
|
|
1233
1520
|
const listing = await this.listWorkspaceSessions(cwd, managementContext);
|
|
@@ -1255,24 +1542,20 @@ export class PiSessionService {
|
|
|
1255
1542
|
const streamingMessage = session.state.streamingMessage;
|
|
1256
1543
|
const partial = streamingMessage === undefined || streamingMessage === null
|
|
1257
1544
|
? null
|
|
1258
|
-
: projectBrowserMessage(streamingMessage);
|
|
1545
|
+
: annotateAssistantThinkingLevel(projectBrowserMessage(streamingMessage), session.thinkingLevel);
|
|
1259
1546
|
return { seq, partial };
|
|
1260
1547
|
}
|
|
1261
1548
|
async availableModels(ref, managementContext) {
|
|
1262
1549
|
const session = await this.getOrOpen(ref, managementContext);
|
|
1263
|
-
await
|
|
1264
|
-
const models = session.scopedModels.length > 0
|
|
1265
|
-
? session.scopedModels.map((scoped) => scoped.model)
|
|
1266
|
-
: await session.modelRuntime.getAvailable();
|
|
1550
|
+
const models = await this.sessionModelCandidates(session);
|
|
1267
1551
|
return models.map(modelToClientModel);
|
|
1268
1552
|
}
|
|
1269
1553
|
async setModel(ref, provider, modelId, managementContext) {
|
|
1270
1554
|
await this.assertWritable(ref);
|
|
1271
1555
|
const session = await this.getOrOpen(ref, managementContext);
|
|
1272
|
-
|
|
1273
|
-
const candidates = session
|
|
1274
|
-
|
|
1275
|
-
: await session.modelRuntime.getAvailable();
|
|
1556
|
+
this.assertTreeNavigationInactive(session, "change models");
|
|
1557
|
+
const candidates = await this.sessionModelCandidates(session);
|
|
1558
|
+
this.assertTreeNavigationInactive(session, "change models");
|
|
1276
1559
|
const model = candidates.find((candidate) => candidate.provider === provider && candidate.id === modelId)
|
|
1277
1560
|
?? session.modelRuntime.getModel(provider, modelId);
|
|
1278
1561
|
if (model === undefined)
|
|
@@ -1748,6 +2031,7 @@ export class PiSessionService {
|
|
|
1748
2031
|
const sessionId = active.runtime.session.sessionId;
|
|
1749
2032
|
this.clearCompactionPromptQueue(sessionId);
|
|
1750
2033
|
clearSessionQueue(active.runtime.session);
|
|
2034
|
+
this.abortRunScopedExtensionDialogs(active.runtime.session);
|
|
1751
2035
|
try {
|
|
1752
2036
|
await this.abortSessionOperations(active.runtime.session);
|
|
1753
2037
|
this.publishActivity(active.runtime.session, "已停止", "idle");
|
|
@@ -1770,7 +2054,17 @@ export class PiSessionService {
|
|
|
1770
2054
|
return this.statusFromSession(session);
|
|
1771
2055
|
}
|
|
1772
2056
|
async stop(ref, managementContext) {
|
|
1773
|
-
|
|
2057
|
+
let active = this.activeForLookup(ref, managementContext);
|
|
2058
|
+
if (active === undefined) {
|
|
2059
|
+
const startup = this.startupSessionForLookup(ref, managementContext);
|
|
2060
|
+
if (startup !== undefined) {
|
|
2061
|
+
this.endSessionExtensionDialogs(startup);
|
|
2062
|
+
const pendingOpens = this.pendingSessionOpenPromises(startup.sessionId);
|
|
2063
|
+
if (pendingOpens.length > 0)
|
|
2064
|
+
await Promise.allSettled(pendingOpens);
|
|
2065
|
+
active = this.activeForLookup(ref, managementContext);
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
1774
2068
|
if (active !== undefined) {
|
|
1775
2069
|
await this.closeActiveSession(active);
|
|
1776
2070
|
return;
|
|
@@ -1780,19 +2074,7 @@ export class PiSessionService {
|
|
|
1780
2074
|
}
|
|
1781
2075
|
}
|
|
1782
2076
|
async bulkSessionLookupContext(refs) {
|
|
1783
|
-
|
|
1784
|
-
let needsAllSessions = false;
|
|
1785
|
-
for (const ref of refs) {
|
|
1786
|
-
if (ref.cwd === undefined)
|
|
1787
|
-
needsAllSessions = true;
|
|
1788
|
-
else
|
|
1789
|
-
cwdSet.add(ref.cwd);
|
|
1790
|
-
}
|
|
1791
|
-
const [sessionsByCwd, allSessions] = await Promise.all([
|
|
1792
|
-
this.listSessionsByCwd([...cwdSet]),
|
|
1793
|
-
needsAllSessions ? this.sessionManager.listAll?.() ?? Promise.resolve([]) : Promise.resolve(undefined),
|
|
1794
|
-
]);
|
|
1795
|
-
return allSessions === undefined ? { sessionsByCwd } : { sessionsByCwd, allSessions };
|
|
2077
|
+
return { sessionsByCwd: await this.listSessionsByCwd(refs.map((ref) => ref.cwd)) };
|
|
1796
2078
|
}
|
|
1797
2079
|
async listSessionsByCwd(cwds) {
|
|
1798
2080
|
const uniqueCwds = uniqueStrings(cwds);
|
|
@@ -1966,6 +2248,10 @@ export class PiSessionService {
|
|
|
1966
2248
|
return [...names];
|
|
1967
2249
|
}
|
|
1968
2250
|
async closeActive(sessionId, notificationPolicy = CLEAR_RUNTIME_NOTIFICATIONS) {
|
|
2251
|
+
for (const startup of this.startupSessions.values()) {
|
|
2252
|
+
if (startup.session.sessionId === sessionId)
|
|
2253
|
+
this.endSessionExtensionDialogs(startup.session);
|
|
2254
|
+
}
|
|
1969
2255
|
const pendingOpens = this.pendingSessionOpenPromises(sessionId);
|
|
1970
2256
|
if (pendingOpens.length > 0)
|
|
1971
2257
|
await Promise.allSettled(pendingOpens);
|
|
@@ -1996,6 +2282,7 @@ export class PiSessionService {
|
|
|
1996
2282
|
}
|
|
1997
2283
|
this.forgetUnreadActivity(active.runtime.session);
|
|
1998
2284
|
this.pendingAskStore.forgetSession(sessionId);
|
|
2285
|
+
this.endSessionExtensionDialogs(active.runtime.session);
|
|
1999
2286
|
this.active.delete(activeSessionKey(sessionId, active.eventScope));
|
|
2000
2287
|
this.activities.delete(activeSessionKey(sessionId, active.eventScope));
|
|
2001
2288
|
this.workspaceActivity?.removeSession(sessionId, active.runtime.session.sessionManager.getCwd(), active.eventScope);
|
|
@@ -2045,6 +2332,26 @@ export class PiSessionService {
|
|
|
2045
2332
|
async getOrOpen(ref, managementContext) {
|
|
2046
2333
|
return (await this.getActive(ref, managementContext)).runtime.session;
|
|
2047
2334
|
}
|
|
2335
|
+
startupSessionForLookup(ref, managementContext) {
|
|
2336
|
+
const contextKey = managementContextKey(managementContext);
|
|
2337
|
+
const eventScope = eventScopeFromManagementContext(managementContext);
|
|
2338
|
+
const sessionId = sessionIdFromLookup(ref);
|
|
2339
|
+
const exact = this.startupSessions.get(activeSessionKey(sessionId, eventScope));
|
|
2340
|
+
if (exact !== undefined && exact.managementContextKey === contextKey && lookupMatchesStartupSession(ref, exact.session))
|
|
2341
|
+
return exact.session;
|
|
2342
|
+
for (const startup of this.startupSessions.values()) {
|
|
2343
|
+
if (startup.managementContextKey !== contextKey || !startup.session.sessionId.startsWith(sessionId))
|
|
2344
|
+
continue;
|
|
2345
|
+
if (lookupMatchesStartupSession(ref, startup.session))
|
|
2346
|
+
return startup.session;
|
|
2347
|
+
}
|
|
2348
|
+
return undefined;
|
|
2349
|
+
}
|
|
2350
|
+
/** Resolve status and dialog-close calls even while extension startup is waiting for the browser. */
|
|
2351
|
+
async sessionForStatusOrDialogClose(ref, managementContext) {
|
|
2352
|
+
const reachable = this.activeForLookup(ref, managementContext)?.runtime.session ?? this.startupSessionForLookup(ref, managementContext);
|
|
2353
|
+
return reachable ?? this.getOrOpen(ref, managementContext);
|
|
2354
|
+
}
|
|
2048
2355
|
async getActive(ref, managementContext) {
|
|
2049
2356
|
const active = this.activeForLookup(ref, managementContext);
|
|
2050
2357
|
if (active !== undefined)
|
|
@@ -2151,17 +2458,25 @@ export class PiSessionService {
|
|
|
2151
2458
|
startup.end();
|
|
2152
2459
|
}
|
|
2153
2460
|
}
|
|
2154
|
-
async bindSessionExtensions(session, generation) {
|
|
2461
|
+
async bindSessionExtensions(session, generation, owner) {
|
|
2155
2462
|
const uiContext = this.sessionUiContext(session, generation);
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2463
|
+
const key = activeSessionKey(session.sessionId, owner.eventScope);
|
|
2464
|
+
this.startupSessions.set(key, { session, ...owner });
|
|
2465
|
+
try {
|
|
2466
|
+
await session.bindExtensions({
|
|
2467
|
+
uiContext,
|
|
2468
|
+
mode: "rpc",
|
|
2469
|
+
onError: (error) => {
|
|
2470
|
+
const message = `${error.extensionPath}: ${error.error}`;
|
|
2471
|
+
this.publishActivity(session, "扩展错误", "error", message);
|
|
2472
|
+
this.events.publish(session.sessionId, { type: "session.error", message }, owner.eventScope);
|
|
2473
|
+
},
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
finally {
|
|
2477
|
+
if (this.startupSessions.get(key)?.session === session)
|
|
2478
|
+
this.startupSessions.delete(key);
|
|
2479
|
+
}
|
|
2165
2480
|
}
|
|
2166
2481
|
bindRuntime(active, session = active.runtime.session) {
|
|
2167
2482
|
this.runtimeBySession.set(session, active.runtime);
|
|
@@ -2174,9 +2489,12 @@ export class PiSessionService {
|
|
|
2174
2489
|
}
|
|
2175
2490
|
}
|
|
2176
2491
|
active.unsubscribe = session.subscribe((event) => {
|
|
2177
|
-
this.events.publish(session.sessionId, toClientEvent(event), active.eventScope);
|
|
2492
|
+
this.events.publish(session.sessionId, toClientEvent(event, session.thinkingLevel), active.eventScope);
|
|
2493
|
+
this.auditToolExecutionEvent(active, event);
|
|
2178
2494
|
this.publishActivityForEvent(session, event);
|
|
2179
2495
|
const eventType = getString(event, "type");
|
|
2496
|
+
if (eventType === "agent_end")
|
|
2497
|
+
this.abortRunScopedExtensionDialogs(session);
|
|
2180
2498
|
if (eventType === "compaction_end")
|
|
2181
2499
|
this.scheduleCompactionQueueDrain(session.sessionId);
|
|
2182
2500
|
if (eventType === "agent_start" || eventType === "agent_end")
|
|
@@ -2501,6 +2819,46 @@ export class PiSessionService {
|
|
|
2501
2819
|
if (this.hasActiveWork(session))
|
|
2502
2820
|
this.publishActivity(session, "正在处理", "active");
|
|
2503
2821
|
}
|
|
2822
|
+
auditToolExecutionEvent(active, event) {
|
|
2823
|
+
const eventType = getString(event, "type");
|
|
2824
|
+
if (eventType !== "tool_execution_start" && eventType !== "tool_execution_end")
|
|
2825
|
+
return;
|
|
2826
|
+
const isEnd = eventType === "tool_execution_end";
|
|
2827
|
+
const auditEvent = {
|
|
2828
|
+
sessionId: active.runtime.session.sessionId,
|
|
2829
|
+
cwd: active.runtime.cwd,
|
|
2830
|
+
toolName: getString(event, "toolName") ?? "unknown",
|
|
2831
|
+
toolCallId: getString(event, "toolCallId") ?? "unknown",
|
|
2832
|
+
status: isEnd ? (getBoolean(event, "isError") === true ? "failed" : "completed") : "started",
|
|
2833
|
+
};
|
|
2834
|
+
const managementIdentity = active.managementAuditIdentity;
|
|
2835
|
+
this.logger.info({
|
|
2836
|
+
mode: active.managementContextKey === undefined ? "normal" : "management",
|
|
2837
|
+
...(managementIdentity ?? {}),
|
|
2838
|
+
...auditEvent,
|
|
2839
|
+
}, "Pi tool execution audit");
|
|
2840
|
+
if (managementIdentity !== undefined) {
|
|
2841
|
+
try {
|
|
2842
|
+
this.managementAudit?.record({ action: "tool_execution", ...managementIdentity, ...auditEvent });
|
|
2843
|
+
}
|
|
2844
|
+
catch (error) {
|
|
2845
|
+
this.logger.info({ sessionId: auditEvent.sessionId, toolCallId: auditEvent.toolCallId, error: error instanceof Error ? error.message : String(error) }, "failed to enqueue management-mode tool audit");
|
|
2846
|
+
}
|
|
2847
|
+
return;
|
|
2848
|
+
}
|
|
2849
|
+
if (this.normalToolAudit === undefined)
|
|
2850
|
+
return;
|
|
2851
|
+
try {
|
|
2852
|
+
this.normalToolAudit.record(auditEvent);
|
|
2853
|
+
}
|
|
2854
|
+
catch (error) {
|
|
2855
|
+
this.logger.info({
|
|
2856
|
+
sessionId: auditEvent.sessionId,
|
|
2857
|
+
toolCallId: auditEvent.toolCallId,
|
|
2858
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2859
|
+
}, "failed to persist ordinary-mode tool audit");
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2504
2862
|
/**
|
|
2505
2863
|
* Build the reporter for one session construction.
|
|
2506
2864
|
*
|
|
@@ -2589,14 +2947,34 @@ export class PiSessionService {
|
|
|
2589
2947
|
if (active.runtime.session === session)
|
|
2590
2948
|
return active.eventScope;
|
|
2591
2949
|
}
|
|
2950
|
+
for (const startup of this.startupSessions.values()) {
|
|
2951
|
+
if (startup.session === session)
|
|
2952
|
+
return startup.eventScope;
|
|
2953
|
+
}
|
|
2592
2954
|
return NORMAL_SESSION_EVENT_SCOPE;
|
|
2593
2955
|
}
|
|
2956
|
+
dialogSessionKey(session) {
|
|
2957
|
+
const eventScope = this.eventScopeForSession(session);
|
|
2958
|
+
return eventScope === NORMAL_SESSION_EVENT_SCOPE ? session.sessionId : activeSessionKey(session.sessionId, eventScope);
|
|
2959
|
+
}
|
|
2960
|
+
publishStatusForSessionId(sessionId) {
|
|
2961
|
+
const sessions = new Set();
|
|
2962
|
+
for (const active of this.activeSessionsForId(sessionId))
|
|
2963
|
+
sessions.add(active.runtime.session);
|
|
2964
|
+
for (const startup of this.startupSessions.values()) {
|
|
2965
|
+
if (startup.session.sessionId === sessionId)
|
|
2966
|
+
sessions.add(startup.session);
|
|
2967
|
+
}
|
|
2968
|
+
for (const session of sessions)
|
|
2969
|
+
this.publishStatus(session);
|
|
2970
|
+
}
|
|
2594
2971
|
statusFromSession(session) {
|
|
2595
2972
|
const stats = session.getSessionStats();
|
|
2596
2973
|
const model = session.model === undefined ? undefined : modelToClientModel(session.model);
|
|
2597
2974
|
const contextUsage = session.getContextUsage();
|
|
2598
2975
|
const warnings = this.warningsForSession(session);
|
|
2599
2976
|
const pendingAsk = this.pendingAskStore.pendingAsk(session.sessionId);
|
|
2977
|
+
const pendingDialogs = this.pendingExtensionDialogStore.pendingDialogs(this.dialogSessionKey(session));
|
|
2600
2978
|
return {
|
|
2601
2979
|
sessionId: session.sessionId,
|
|
2602
2980
|
persisted: sessionFileExists(session.sessionFile),
|
|
@@ -2613,6 +2991,7 @@ export class PiSessionService {
|
|
|
2613
2991
|
...(contextUsage === undefined ? {} : { contextUsage }),
|
|
2614
2992
|
...(warnings.length === 0 ? {} : { warnings }),
|
|
2615
2993
|
...(pendingAsk === undefined ? {} : { pendingAsk }),
|
|
2994
|
+
...(pendingDialogs.length === 0 ? {} : { pendingDialogs }),
|
|
2616
2995
|
};
|
|
2617
2996
|
}
|
|
2618
2997
|
/**
|
|
@@ -2746,12 +3125,16 @@ export class PiSessionService {
|
|
|
2746
3125
|
delegationToolsEnabled,
|
|
2747
3126
|
...(options.managementContext === undefined ? {} : { managementContext: options.managementContext }),
|
|
2748
3127
|
...(options.initialModel === undefined ? {} : { initialModel: options.initialModel }),
|
|
3128
|
+
...(options.initialThinkingLevel === undefined ? {} : { initialThinkingLevel: options.initialThinkingLevel }),
|
|
2749
3129
|
});
|
|
2750
3130
|
const active = {
|
|
2751
3131
|
runtime,
|
|
2752
3132
|
unsubscribe: noop,
|
|
2753
3133
|
managementContextKey: managementContextKey(options.managementContext),
|
|
2754
3134
|
eventScope: eventScopeFromManagementContext(options.managementContext),
|
|
3135
|
+
...(options.managementContext === undefined ? {} : {
|
|
3136
|
+
managementAuditIdentity: await resolveManagementAuditIdentity(cwd, options.managementContext, this.managementProjectIdForCwd, this.logger),
|
|
3137
|
+
}),
|
|
2755
3138
|
};
|
|
2756
3139
|
let boundSession = runtime.session;
|
|
2757
3140
|
let notificationGeneration = options.notificationGeneration;
|
|
@@ -2783,7 +3166,7 @@ export class PiSessionService {
|
|
|
2783
3166
|
await this.recoverSubsessionTrackingForOpenedSession(runtime.session);
|
|
2784
3167
|
}
|
|
2785
3168
|
startup.report(STARTUP_PHASE_EXTENSIONS);
|
|
2786
|
-
await this.bindSessionExtensions(runtime.session, notificationGeneration);
|
|
3169
|
+
await this.bindSessionExtensions(runtime.session, notificationGeneration, active);
|
|
2787
3170
|
this.bindRuntime(active);
|
|
2788
3171
|
runtime.setRebindSession(async (session) => {
|
|
2789
3172
|
const priorGeneration = notificationGeneration;
|
|
@@ -2796,8 +3179,12 @@ export class PiSessionService {
|
|
|
2796
3179
|
this.notificationGenerationBySession.set(session, candidateGeneration);
|
|
2797
3180
|
}
|
|
2798
3181
|
this.bindRuntime(active, session);
|
|
3182
|
+
// The runtime being replaced parked every dialog the store still
|
|
3183
|
+
// holds for this session; settle those waits before the new
|
|
3184
|
+
// runtime's extensions can open fresh dialogs under the same id.
|
|
3185
|
+
this.endSessionExtensionDialogs(boundSession);
|
|
2799
3186
|
boundSession = session;
|
|
2800
|
-
await this.bindSessionExtensions(session, candidateGeneration);
|
|
3187
|
+
await this.bindSessionExtensions(session, candidateGeneration, active);
|
|
2801
3188
|
if (candidateGeneration !== undefined) {
|
|
2802
3189
|
this.publishNotificationMutations(this.notificationStore.commitReplacement(candidateGeneration));
|
|
2803
3190
|
notificationGeneration = candidateGeneration;
|
|
@@ -2831,6 +3218,7 @@ export class PiSessionService {
|
|
|
2831
3218
|
}
|
|
2832
3219
|
active.unsubscribe();
|
|
2833
3220
|
this.forgetUnreadActivity(boundSession);
|
|
3221
|
+
this.endSessionExtensionDialogs(boundSession);
|
|
2834
3222
|
let removedActive = false;
|
|
2835
3223
|
for (const [sessionKey, candidate] of this.active.entries()) {
|
|
2836
3224
|
if (candidate !== active)
|
|
@@ -2878,15 +3266,26 @@ export class PiSessionService {
|
|
|
2878
3266
|
notificationId: added.notification.id,
|
|
2879
3267
|
});
|
|
2880
3268
|
};
|
|
2881
|
-
// PI WEB owns the browser-facing notification and text-formatting
|
|
2882
|
-
// boundaries
|
|
2883
|
-
//
|
|
3269
|
+
// PI WEB owns the browser-facing dialog, notification, and text-formatting
|
|
3270
|
+
// boundaries: the three dialog primitives park daemon-held Promises that
|
|
3271
|
+
// the browser answers, while every other UI method delegates to Pi's
|
|
3272
|
+
// headless defaults so unsupported surfaces cancel safely instead of
|
|
3273
|
+
// hanging.
|
|
2884
3274
|
return new Proxy(baseUiContext, {
|
|
2885
|
-
get(target, property, receiver) {
|
|
3275
|
+
get: (target, property, receiver) => {
|
|
2886
3276
|
if (property === "notify")
|
|
2887
3277
|
return notify;
|
|
2888
3278
|
if (property === "theme")
|
|
2889
3279
|
return plainTextTheme;
|
|
3280
|
+
if (property === "confirm") {
|
|
3281
|
+
return (title, message, opts) => this.openExtensionDialog(session, { kind: "confirm", title, message }, opts);
|
|
3282
|
+
}
|
|
3283
|
+
if (property === "select") {
|
|
3284
|
+
return (title, options, opts) => this.openExtensionDialog(session, { kind: "select", title, options }, opts);
|
|
3285
|
+
}
|
|
3286
|
+
if (property === "input") {
|
|
3287
|
+
return (title, placeholder, opts) => this.openExtensionDialog(session, { kind: "input", title, placeholder }, opts);
|
|
3288
|
+
}
|
|
2890
3289
|
const value = Reflect.get(target, property, receiver);
|
|
2891
3290
|
return value;
|
|
2892
3291
|
},
|
|
@@ -3040,7 +3439,7 @@ function uniqueBulkSessionRefs(refs) {
|
|
|
3040
3439
|
const seen = new Set();
|
|
3041
3440
|
const unique = [];
|
|
3042
3441
|
for (const ref of refs) {
|
|
3043
|
-
const key = `${ref.cwd
|
|
3442
|
+
const key = `${ref.cwd}\0${ref.id}`;
|
|
3044
3443
|
if (seen.has(key))
|
|
3045
3444
|
continue;
|
|
3046
3445
|
seen.add(key);
|
|
@@ -3049,15 +3448,13 @@ function uniqueBulkSessionRefs(refs) {
|
|
|
3049
3448
|
return unique;
|
|
3050
3449
|
}
|
|
3051
3450
|
function bulkRefToLookup(ref) {
|
|
3052
|
-
return ref
|
|
3451
|
+
return ref;
|
|
3053
3452
|
}
|
|
3054
3453
|
function findArchivedRecordForBulkRef(records, ref) {
|
|
3055
|
-
return records.find((record) =>
|
|
3454
|
+
return records.find((record) => record.cwd === ref.cwd && (record.sessionId === ref.id || record.sessionId.startsWith(ref.id)));
|
|
3056
3455
|
}
|
|
3057
3456
|
function findListedSessionForBulkRef(context, ref) {
|
|
3058
|
-
|
|
3059
|
-
return findSessionByIdOrPrefix(context.sessionsByCwd.get(ref.cwd) ?? [], ref.id);
|
|
3060
|
-
return context.allSessions === undefined ? undefined : findSessionByIdOrPrefix(context.allSessions, ref.id);
|
|
3457
|
+
return findSessionByIdOrPrefix(context.sessionsByCwd.get(ref.cwd) ?? [], ref.id);
|
|
3061
3458
|
}
|
|
3062
3459
|
function findSessionByIdOrPrefix(sessions, sessionId) {
|
|
3063
3460
|
return sessions.find((session) => session.id === sessionId) ?? sessions.find((session) => session.id.startsWith(sessionId));
|
|
@@ -3352,11 +3749,17 @@ function historyMessages(session) {
|
|
|
3352
3749
|
}
|
|
3353
3750
|
function historyMessagesFromManager(sessionManager) {
|
|
3354
3751
|
const messages = [];
|
|
3752
|
+
let thinkingLevel;
|
|
3355
3753
|
for (const entry of sessionManager.getBranch()) {
|
|
3356
3754
|
if (!isRecord(entry))
|
|
3357
3755
|
continue;
|
|
3358
3756
|
if (entry["type"] === "message")
|
|
3359
|
-
messages.push(entry["message"]);
|
|
3757
|
+
messages.push(annotateAssistantThinkingLevel(entry["message"], thinkingLevel));
|
|
3758
|
+
else if (entry["type"] === "thinking_level_change") {
|
|
3759
|
+
const level = getString(entry, "thinkingLevel");
|
|
3760
|
+
if (level !== undefined)
|
|
3761
|
+
thinkingLevel = level;
|
|
3762
|
+
}
|
|
3360
3763
|
else if (entry["type"] === "custom_message" && entry["display"] === true)
|
|
3361
3764
|
messages.push({ role: "custom", content: entry["content"], customType: entry["customType"], details: entry["details"] });
|
|
3362
3765
|
else if (entry["type"] === "compaction")
|
|
@@ -3404,4 +3807,16 @@ function finalAssistantText(messages) {
|
|
|
3404
3807
|
function isRecord(value) {
|
|
3405
3808
|
return typeof value === "object" && value !== null;
|
|
3406
3809
|
}
|
|
3810
|
+
async function resolveManagementAuditIdentity(cwd, context, resolveProjectId, logger) {
|
|
3811
|
+
let projectId = context.projects[0]?.id ?? "default-project";
|
|
3812
|
+
if (resolveProjectId !== undefined) {
|
|
3813
|
+
try {
|
|
3814
|
+
projectId = await resolveProjectId(cwd, context);
|
|
3815
|
+
}
|
|
3816
|
+
catch (error) {
|
|
3817
|
+
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");
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
return { userId: context.user.id, rootUserId: context.user.rootUserId, projectId };
|
|
3821
|
+
}
|
|
3407
3822
|
//# sourceMappingURL=piSessionService.js.map
|