@chainingintention/pi-web-cn 1.202609.1 → 1.202609.4
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/client/assets/{CodeViewer-CY319sHK.js → CodeViewer-ujpWthUP.js} +1 -1
- package/dist/client/assets/{TerminalPanel-DvFd2ZkS.js → TerminalPanel-Br4uzklw.js} +1 -1
- package/dist/client/assets/{index-Lt_wrMvn.js → index-kNZIQ5Kv.js} +554 -305
- package/dist/client/index.html +1 -1
- package/dist/server/app.js +1 -1
- package/dist/server/app.js.map +1 -1
- package/dist/server/machines/machineClient.js +13 -11
- package/dist/server/machines/machineClient.js.map +1 -1
- package/dist/server/machines/machineRoutes.js +9 -2
- package/dist/server/machines/machineRoutes.js.map +1 -1
- package/dist/server/normalAuth.js +19 -2
- package/dist/server/normalAuth.js.map +1 -1
- package/dist/server/sessiond/sessionProxyRoutes.js +19 -14
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond.js +2 -4
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +132 -154
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/relaySkill.js +119 -0
- package/dist/server/sessions/relaySkill.js.map +1 -0
- package/dist/server/sessions/sessionFileHeader.js +41 -0
- package/dist/server/sessions/sessionFileHeader.js.map +1 -1
- package/dist/server/sessions/sessionNotificationStore.js +2 -0
- package/dist/server/sessions/sessionNotificationStore.js.map +1 -1
- package/dist/server/sessions/sessionRoutes.js +9 -9
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/sessionUnreadStore.js +2 -0
- package/dist/server/sessions/sessionUnreadStore.js.map +1 -1
- package/dist/server/workbench/skillSync.js +7 -1
- package/dist/server/workbench/skillSync.js.map +1 -1
- package/dist/server/workspaces/fileContentService.js +13 -1
- package/dist/server/workspaces/fileContentService.js.map +1 -1
- package/package.json +10 -8
- package/skills/relay-runner/SKILL.md +245 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
4
|
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, getAgentDir, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, defineTool, readStoredCredential, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
6
5
|
import { projectBrowserMessage } from "../browserMessageProjection.js";
|
|
7
6
|
import { pageMessagesAtSafeBoundary } from "./messagePaging.js";
|
|
@@ -23,7 +22,7 @@ import { SessionActivityCoordinator } from "./sessionActivityCoordinator.js";
|
|
|
23
22
|
import { searchSessionContent } from "./sessionContentSearch.js";
|
|
24
23
|
import { createLocalOnlyModelRuntime } from "./authService.js";
|
|
25
24
|
import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
|
|
26
|
-
import { readSessionHeaderSummary } from "./sessionFileHeader.js";
|
|
25
|
+
import { clearSessionFileParent, readSessionHeaderSummary } from "./sessionFileHeader.js";
|
|
27
26
|
import { workbenchAccessHandle } from "../managementEmbed.js";
|
|
28
27
|
import { DEFAULT_EXTENSION_DIALOGS_TIMEOUT_MS, piWebDataDir } from "../../config.js";
|
|
29
28
|
import { createScopedSettingsManager, resolveSettingsScopeDirectory } from "./projectSettingsScope.js";
|
|
@@ -39,6 +38,7 @@ import { createManagedAgentToolOptions, createManagedPythonToolDefinition } from
|
|
|
39
38
|
import { PI_PERMISSION_SYSTEM_POLICY_AGENT_DIR, managementAgentToolNames, withRuntimeCreationEnvironment, writeManagementPermissionSystemPolicy } from "./managementPermissionSystem.js";
|
|
40
39
|
import { SessionNotificationStore } from "./sessionNotificationStore.js";
|
|
41
40
|
import { plainTextTheme } from "./plainTextTheme.js";
|
|
41
|
+
import { deduplicateBundledRelaySkills, ensureManagedRelaySkills, MANAGED_RELAY_SKILL_NAMES } from "./relaySkill.js";
|
|
42
42
|
import { SessionUnreadStore } from "./sessionUnreadStore.js";
|
|
43
43
|
import { archiveCandidateFromActiveSession, archiveCandidateFromArchivedRecord, archiveCandidateFromListEntry, archiveInputFromActiveSession, archiveInputFromCandidate, archiveInputFromListEntry, clientSessionFromArchivedRecord, compareArchivedRecords, } from "./sessionArchiveMapping.js";
|
|
44
44
|
import { annotateAssistantThinkingLevel, getBoolean, getProperty, getString, toClientEvent } from "./sessionUiEvents.js";
|
|
@@ -178,8 +178,6 @@ function parsePromptStreamingBehavior(value) {
|
|
|
178
178
|
throw new Error('Prompt streamingBehavior must be "steer" or "followUp"');
|
|
179
179
|
}
|
|
180
180
|
const GLOBAL_AGENT_CONTEXT_FILE_NAMES = new Set(["AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"]);
|
|
181
|
-
const MANAGED_RELAY_SKILL_DIRECTORY = join(".pi", "skills", "relay");
|
|
182
|
-
const BUNDLED_RELAY_SKILL_PATH = fileURLToPath(new URL("../../../skills/relay/SKILL.md", import.meta.url));
|
|
183
181
|
export function filterManagedGlobalContextFiles(cwd, agentDir, base) {
|
|
184
182
|
return {
|
|
185
183
|
agentsFiles: base.agentsFiles.filter((file) => {
|
|
@@ -202,29 +200,10 @@ export function filterManagedProjectSkills(cwd, base) {
|
|
|
202
200
|
diagnostics: filterManagedSkillDiagnostics(base.diagnostics, pathAllowed),
|
|
203
201
|
};
|
|
204
202
|
}
|
|
205
|
-
export async function ensureManagedRelaySkill(cwd, bundledSkillPath = BUNDLED_RELAY_SKILL_PATH) {
|
|
206
|
-
const projectRoot = realpathSync(cwd);
|
|
207
|
-
const piDirectory = resolve(projectRoot, ".pi");
|
|
208
|
-
const skillsDirectory = join(piDirectory, "skills");
|
|
209
|
-
const skillDirectory = resolve(projectRoot, MANAGED_RELAY_SKILL_DIRECTORY);
|
|
210
|
-
const skillPath = join(skillDirectory, "SKILL.md");
|
|
211
|
-
for (const directory of [piDirectory, skillsDirectory, skillDirectory]) {
|
|
212
|
-
await ensureManagedSkillDirectory(projectRoot, directory);
|
|
213
|
-
}
|
|
214
|
-
try {
|
|
215
|
-
await copyFile(bundledSkillPath, skillPath, constants.COPYFILE_EXCL);
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
if (!isNodeError(error, "EEXIST"))
|
|
219
|
-
throw error;
|
|
220
|
-
}
|
|
221
|
-
assertManagedSkillPath(projectRoot, skillPath, "file");
|
|
222
|
-
return skillPath;
|
|
223
|
-
}
|
|
224
203
|
export function filterManagedWorkbenchSkills(cwd, base, receipt) {
|
|
225
204
|
const projectRoot = realpathSync(cwd);
|
|
226
205
|
const allowedDirectories = new Set([
|
|
227
|
-
resolve(projectRoot,
|
|
206
|
+
...MANAGED_RELAY_SKILL_NAMES.map((name) => resolve(projectRoot, ".pi", "skills", name)),
|
|
228
207
|
...receipt.skills.map((skill) => resolve(projectRoot, ".pi", "skills", skill.directory)),
|
|
229
208
|
]);
|
|
230
209
|
const pathAllowed = (path) => [...allowedDirectories].some((directory) => pathInsideRealDirectory(directory, path));
|
|
@@ -245,27 +224,6 @@ function pathInsideRealDirectory(directory, path) {
|
|
|
245
224
|
return false;
|
|
246
225
|
}
|
|
247
226
|
}
|
|
248
|
-
async function ensureManagedSkillDirectory(projectRoot, path) {
|
|
249
|
-
try {
|
|
250
|
-
await mkdir(path);
|
|
251
|
-
}
|
|
252
|
-
catch (error) {
|
|
253
|
-
if (!isNodeError(error, "EEXIST"))
|
|
254
|
-
throw error;
|
|
255
|
-
}
|
|
256
|
-
assertManagedSkillPath(projectRoot, path, "directory");
|
|
257
|
-
}
|
|
258
|
-
function assertManagedSkillPath(projectRoot, path, expectedType) {
|
|
259
|
-
const realPath = realpathSync(path);
|
|
260
|
-
const stats = statSync(realPath);
|
|
261
|
-
const hasExpectedType = expectedType === "directory" ? stats.isDirectory() : stats.isFile();
|
|
262
|
-
if (!pathInsideOrEqual(projectRoot, realPath) || !hasExpectedType) {
|
|
263
|
-
throw new Error(`Managed skill path is invalid: ${path}`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function isNodeError(error, code) {
|
|
267
|
-
return error instanceof Error && "code" in error && error.code === code;
|
|
268
|
-
}
|
|
269
227
|
function pathInsideOrEqual(parent, child) {
|
|
270
228
|
const normalizedParent = resolve(parent);
|
|
271
229
|
const normalizedChild = resolve(child);
|
|
@@ -433,13 +391,16 @@ function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRu
|
|
|
433
391
|
globalAgentDir: agentDir,
|
|
434
392
|
mode: "normal",
|
|
435
393
|
});
|
|
436
|
-
const resourceLoaderOptions =
|
|
394
|
+
const resourceLoaderOptions = {
|
|
395
|
+
...piWebResourceLoaderOptions(appendSystemPromptSections),
|
|
396
|
+
skillsOverride: (base) => deduplicateBundledRelaySkills(cwd, base),
|
|
397
|
+
};
|
|
437
398
|
const services = await createAgentSessionServices({
|
|
438
399
|
cwd,
|
|
439
400
|
agentDir,
|
|
440
401
|
modelRuntime: scopedModelRuntime,
|
|
441
402
|
settingsManager,
|
|
442
|
-
|
|
403
|
+
resourceLoaderOptions,
|
|
443
404
|
});
|
|
444
405
|
const modelOptions = await resolveSessionModelOptions({
|
|
445
406
|
services,
|
|
@@ -463,7 +424,7 @@ function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRu
|
|
|
463
424
|
}
|
|
464
425
|
function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spawn, subsessions, askUser, managementContext, workbench, logger, managementAudit, managementProjectIdForCwd) {
|
|
465
426
|
return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, initialThinkingLevel, delegationToolsEnabled = true }) => {
|
|
466
|
-
await
|
|
427
|
+
await ensureManagedRelaySkills(cwd);
|
|
467
428
|
const auditIdentity = await resolveManagementAuditIdentity(cwd, managementContext, managementProjectIdForCwd, logger);
|
|
468
429
|
const accessHandle = workbenchAccessHandle(managementContext);
|
|
469
430
|
const state = workbench === undefined ? undefined : workbench.accessStates.require(accessHandle);
|
|
@@ -641,6 +602,9 @@ export class PiSessionService {
|
|
|
641
602
|
* child that works again (and stops again) notifies the parent each time.
|
|
642
603
|
*/
|
|
643
604
|
this.subsessionNotifyArmed = new Map();
|
|
605
|
+
this.scopedNotificationStores = new Map();
|
|
606
|
+
this.scopedUnreadStores = new Map();
|
|
607
|
+
this.sessionEventScopes = new WeakMap();
|
|
644
608
|
this.notificationGenerationBySession = new WeakMap();
|
|
645
609
|
this.idleSessionFileResolutions = new WeakMap();
|
|
646
610
|
/** The parked extension Promise resolvers behind the store's open dialogs. */
|
|
@@ -736,7 +700,8 @@ export class PiSessionService {
|
|
|
736
700
|
async cleanupPreview(request) {
|
|
737
701
|
return previewResponseFromPlan(await this.cleanupPlan(request));
|
|
738
702
|
}
|
|
739
|
-
async cleanup(request) {
|
|
703
|
+
async cleanup(request, context) {
|
|
704
|
+
const scope = eventScopeFromManagementContext(context);
|
|
740
705
|
const plan = await this.cleanupPlan(request);
|
|
741
706
|
if (plan.deleteRecords.length > 0 && this.archiveStore.deleteArchived === undefined && this.archiveStore.deleteArchivedMany === undefined)
|
|
742
707
|
throw new Error("Archive store does not support deletion");
|
|
@@ -750,24 +715,24 @@ export class PiSessionService {
|
|
|
750
715
|
skippedBusySessionIds.add(input.sessionId);
|
|
751
716
|
continue;
|
|
752
717
|
}
|
|
753
|
-
await this.closeActive(input.sessionId, { kind: "clear", reason: "archive" });
|
|
718
|
+
await this.closeActive(input.sessionId, { kind: "clear", reason: "archive" }, scope);
|
|
754
719
|
readyArchiveInputs.push(input);
|
|
755
720
|
}
|
|
756
721
|
await this.archiveStoreArchiveMany(readyArchiveInputs);
|
|
757
722
|
archiveInputs.push(...readyArchiveInputs);
|
|
758
|
-
await this.forgetUnreadSessions(readyArchiveInputs);
|
|
723
|
+
await this.forgetUnreadSessions(readyArchiveInputs, scope);
|
|
759
724
|
for (const record of plan.deleteRecords) {
|
|
760
725
|
if (this.activeSessionHasWork(record.sessionId)) {
|
|
761
726
|
skippedBusySessionIds.add(record.sessionId);
|
|
762
727
|
continue;
|
|
763
728
|
}
|
|
764
|
-
await this.closeActive(record.sessionId, { kind: "clear", reason: "delete" });
|
|
729
|
+
await this.closeActive(record.sessionId, { kind: "clear", reason: "delete" }, scope);
|
|
765
730
|
readyDeleteRecords.push(record);
|
|
766
731
|
}
|
|
767
732
|
await this.ensureArchivedRecordsMoved(readyDeleteRecords);
|
|
768
733
|
const deletedSessionIds = new Set(await this.archiveStoreDeleteArchivedMany(readyDeleteRecords.map((record) => record.sessionId)));
|
|
769
734
|
deleteRecords.push(...readyDeleteRecords.filter((record) => deletedSessionIds.has(record.sessionId)));
|
|
770
|
-
await this.forgetUnreadSessions(deleteRecords);
|
|
735
|
+
await this.forgetUnreadSessions(deleteRecords, scope);
|
|
771
736
|
return summarizeSessionCleanupExecution({
|
|
772
737
|
archiveInputs,
|
|
773
738
|
deleteRecords,
|
|
@@ -806,6 +771,8 @@ export class PiSessionService {
|
|
|
806
771
|
this.subsessionHydratedParents.clear();
|
|
807
772
|
this.subsessionNotifyArmed.clear();
|
|
808
773
|
this.notificationStore.clearAll("service-dispose");
|
|
774
|
+
for (const store of this.scopedNotificationStores.values())
|
|
775
|
+
store.clearAll("service-dispose");
|
|
809
776
|
await Promise.all(activeSessions.map(async (active) => {
|
|
810
777
|
active.unsubscribe();
|
|
811
778
|
this.workspaceActivity?.removeSession(active.runtime.session.sessionId, active.runtime.session.sessionManager.getCwd(), active.eventScope);
|
|
@@ -870,13 +837,13 @@ export class PiSessionService {
|
|
|
870
837
|
.map((record) => this.ensureArchivedSessionMoved(record, sessionsById.get(record.sessionId))));
|
|
871
838
|
const archivedById = new Map(archivedForCwd.map((record) => [record.sessionId, record]));
|
|
872
839
|
for (const record of archivedForCwd) {
|
|
873
|
-
this.publishNotificationMutations(this.
|
|
840
|
+
this.publishNotificationMutations(this.notificationsForScope(eventScopeFromManagementContext(managementContext)).clearSession(record.sessionId, "archive-reconcile"));
|
|
874
841
|
}
|
|
875
842
|
const unarchivedSessions = sessions.filter((session) => !archivedById.has(session.id)).map(clientSessionFromListEntry);
|
|
876
843
|
const reconcilableSessionIds = this.reconcilableSessionIds(cwd, unarchivedSessions.map((session) => session.id), archivedById);
|
|
877
844
|
this.workspaceActivity?.reconcileSessionActivity(cwd, reconcilableSessionIds, eventScopeFromManagementContext(managementContext));
|
|
878
845
|
if (managementContext === undefined) {
|
|
879
|
-
await this.publishUnreadMutations(this.
|
|
846
|
+
await this.publishUnreadMutations(this.unreadForScope(eventScopeFromManagementContext(managementContext)).reconcileCwd(canonicalizeStoredCwd(cwd), reconcilableSessionIds));
|
|
880
847
|
}
|
|
881
848
|
const archivedSessions = archivedForCwd
|
|
882
849
|
.sort(compareArchivedRecords)
|
|
@@ -985,7 +952,7 @@ export class PiSessionService {
|
|
|
985
952
|
...(parentSessionFile === undefined ? {} : { parentSessionFile }),
|
|
986
953
|
cwd: decision.cwd,
|
|
987
954
|
};
|
|
988
|
-
await this.registerVerifiedSubsession(link);
|
|
955
|
+
await this.registerVerifiedSubsession(link, eventScopeFromManagementContext(input.managementContext));
|
|
989
956
|
this.persistSubsessionLink(link);
|
|
990
957
|
this.persistSubsessionChildMarker(input.parentSessionId, created.id);
|
|
991
958
|
await this.prompt(created.id, input.prompt, undefined, undefined, {
|
|
@@ -1360,7 +1327,7 @@ export class PiSessionService {
|
|
|
1360
1327
|
return undefined;
|
|
1361
1328
|
return sessionFileMatches(session, link.childSessionFile) ? link : undefined;
|
|
1362
1329
|
}
|
|
1363
|
-
async registerVerifiedSubsession(link) {
|
|
1330
|
+
async registerVerifiedSubsession(link, scope = NORMAL_SESSION_EVENT_SCOPE) {
|
|
1364
1331
|
const { childSessionId, parentSessionId } = link;
|
|
1365
1332
|
const previousParentId = this.subsessionParents.get(childSessionId);
|
|
1366
1333
|
if (previousParentId !== undefined && previousParentId !== parentSessionId) {
|
|
@@ -1377,7 +1344,7 @@ export class PiSessionService {
|
|
|
1377
1344
|
if (!this.subsessionNotifyArmed.has(childSessionId))
|
|
1378
1345
|
this.subsessionNotifyArmed.set(childSessionId, false);
|
|
1379
1346
|
const cwd = this.cwdForVerifiedSubsession(link);
|
|
1380
|
-
await this.publishUnreadMutations(this.
|
|
1347
|
+
await this.publishUnreadMutations(this.unreadForScope(scope).excludeSession(childSessionId, cwd));
|
|
1381
1348
|
}
|
|
1382
1349
|
cwdForVerifiedSubsession(link) {
|
|
1383
1350
|
const activeCwd = this.activeChildForSubsessionLink(link)?.runtime.session.sessionManager.getCwd();
|
|
@@ -1440,7 +1407,7 @@ export class PiSessionService {
|
|
|
1440
1407
|
const activeParent = this.activeForSessionId(parentSessionId);
|
|
1441
1408
|
if (activeParent !== undefined && (parentSessionFile === undefined || activeSessionFileMatches(activeParent, parentSessionFile))) {
|
|
1442
1409
|
const activeParentFile = nonEmptyString(activeParent.runtime.session.sessionFile);
|
|
1443
|
-
const complete = await this.registerPersistedSubsessionLinks(parentSessionId, activeParent.runtime.session.sessionManager, activeParentFile);
|
|
1410
|
+
const complete = await this.registerPersistedSubsessionLinks(parentSessionId, activeParent.runtime.session.sessionManager, activeParentFile, activeParent.eventScope);
|
|
1444
1411
|
if (complete)
|
|
1445
1412
|
this.subsessionHydratedParents.add(hydrationKey);
|
|
1446
1413
|
return;
|
|
@@ -1460,7 +1427,7 @@ export class PiSessionService {
|
|
|
1460
1427
|
if (complete)
|
|
1461
1428
|
this.subsessionHydratedParents.add(hydrationKey);
|
|
1462
1429
|
}
|
|
1463
|
-
async registerPersistedSubsessionLinks(parentSessionId, parentManager, parentSessionFile) {
|
|
1430
|
+
async registerPersistedSubsessionLinks(parentSessionId, parentManager, parentSessionFile, scope = NORMAL_SESSION_EVENT_SCOPE) {
|
|
1464
1431
|
// Parent custom links are the authoritative recovery record: verify the
|
|
1465
1432
|
// exact live child file/header before tracking. Do not negatively cache a
|
|
1466
1433
|
// scan while a candidate child is temporarily unavailable.
|
|
@@ -1475,7 +1442,7 @@ export class PiSessionService {
|
|
|
1475
1442
|
complete = false;
|
|
1476
1443
|
continue;
|
|
1477
1444
|
}
|
|
1478
|
-
await this.registerVerifiedSubsession(verified);
|
|
1445
|
+
await this.registerVerifiedSubsession(verified, scope);
|
|
1479
1446
|
}
|
|
1480
1447
|
return complete;
|
|
1481
1448
|
}
|
|
@@ -1496,7 +1463,7 @@ export class PiSessionService {
|
|
|
1496
1463
|
const link = await this.verifiedSubsessionLinkFromOpenedChild(session);
|
|
1497
1464
|
if (link === undefined)
|
|
1498
1465
|
return;
|
|
1499
|
-
await this.registerVerifiedSubsession(link);
|
|
1466
|
+
await this.registerVerifiedSubsession(link, this.eventScopeForSession(session));
|
|
1500
1467
|
}
|
|
1501
1468
|
verifiedSubsessionLinkFromOpenedChild(session) {
|
|
1502
1469
|
return verifiedTrackedSubsessionLink(this.sessionManager, {
|
|
@@ -2034,7 +2001,7 @@ export class PiSessionService {
|
|
|
2034
2001
|
const archiveInput = await this.archiveInputForSession(session);
|
|
2035
2002
|
await this.closeActiveSession(active);
|
|
2036
2003
|
await this.archiveStore.archive(archiveInput);
|
|
2037
|
-
await this.forgetUnreadSessions([archiveInput]);
|
|
2004
|
+
await this.forgetUnreadSessions([archiveInput], active.eventScope);
|
|
2038
2005
|
});
|
|
2039
2006
|
}
|
|
2040
2007
|
async archiveMany(refs, managementContext) {
|
|
@@ -2050,7 +2017,7 @@ export class PiSessionService {
|
|
|
2050
2017
|
for (const ref of uniqueRefs) {
|
|
2051
2018
|
const archived = findArchivedRecordForBulkRef(archivedRecords, ref);
|
|
2052
2019
|
if (archived !== undefined) {
|
|
2053
|
-
this.publishNotificationMutations(this.
|
|
2020
|
+
this.publishNotificationMutations(this.notificationsForScope(eventScopeFromManagementContext(managementContext)).clearSession(archived.sessionId, "archive"));
|
|
2054
2021
|
alreadyArchivedSessionIds.push(archived.sessionId);
|
|
2055
2022
|
unreadArchivedIdentities.push(archived);
|
|
2056
2023
|
continue;
|
|
@@ -2114,7 +2081,7 @@ export class PiSessionService {
|
|
|
2114
2081
|
failures.push({ sessionId: input.sessionId, error: errorMessage(error) });
|
|
2115
2082
|
}
|
|
2116
2083
|
});
|
|
2117
|
-
await this.forgetUnreadSessions(unreadArchivedIdentities);
|
|
2084
|
+
await this.forgetUnreadSessions(unreadArchivedIdentities, eventScopeFromManagementContext(managementContext));
|
|
2118
2085
|
return {
|
|
2119
2086
|
archived: true,
|
|
2120
2087
|
archivedSessionIds: uniqueStrings(archivedSessionIds),
|
|
@@ -2137,13 +2104,13 @@ export class PiSessionService {
|
|
|
2137
2104
|
})), `归档 ${sessionDisplayName(session)} 前请先停止当前会话活动`, async () => {
|
|
2138
2105
|
for (const target of plan.targets) {
|
|
2139
2106
|
if (target.archived)
|
|
2140
|
-
this.publishNotificationMutations(this.
|
|
2107
|
+
this.publishNotificationMutations(this.notificationsForScope(eventScopeFromManagementContext(managementContext)).clearSession(target.id, "archive"));
|
|
2141
2108
|
}
|
|
2142
2109
|
for (const input of archiveInputs)
|
|
2143
2110
|
await this.closeActiveInScope(input.sessionId, eventScopeFromManagementContext(managementContext));
|
|
2144
2111
|
await this.archiveStoreArchiveMany(archiveInputs);
|
|
2145
2112
|
});
|
|
2146
|
-
await this.forgetUnreadSessions(plan.targets.map((target) => ({ sessionId: target.id, cwd: target.cwd })));
|
|
2113
|
+
await this.forgetUnreadSessions(plan.targets.map((target) => ({ sessionId: target.id, cwd: target.cwd })), eventScopeFromManagementContext(managementContext));
|
|
2147
2114
|
return {
|
|
2148
2115
|
archived: true,
|
|
2149
2116
|
sessionIds: archiveInputs.map((input) => input.sessionId),
|
|
@@ -2157,7 +2124,7 @@ export class PiSessionService {
|
|
|
2157
2124
|
throw new Error("未找到会话");
|
|
2158
2125
|
await this.closeActiveInScope(archived.sessionId, eventScopeFromManagementContext(managementContext));
|
|
2159
2126
|
await this.archiveStore.restore(archived.sessionId);
|
|
2160
|
-
await this.forgetUnreadSessions([archived]);
|
|
2127
|
+
await this.forgetUnreadSessions([archived], eventScopeFromManagementContext(managementContext));
|
|
2161
2128
|
}
|
|
2162
2129
|
async deleteArchived(ref, managementContext) {
|
|
2163
2130
|
const record = await this.getArchived(ref);
|
|
@@ -2169,7 +2136,7 @@ export class PiSessionService {
|
|
|
2169
2136
|
if (record.archivePath === undefined)
|
|
2170
2137
|
await this.ensureArchivedRecordMoved(record);
|
|
2171
2138
|
await this.archiveStore.deleteArchived(record.sessionId);
|
|
2172
|
-
await this.forgetUnreadSessions([record]);
|
|
2139
|
+
await this.forgetUnreadSessions([record], eventScopeFromManagementContext(managementContext));
|
|
2173
2140
|
}
|
|
2174
2141
|
async deleteArchivedMany(refs, managementContext) {
|
|
2175
2142
|
if (this.archiveStore.deleteArchived === undefined && this.archiveStore.deleteArchivedMany === undefined)
|
|
@@ -2216,7 +2183,7 @@ export class PiSessionService {
|
|
|
2216
2183
|
failures.push({ sessionId, error: errorMessage(error) });
|
|
2217
2184
|
}
|
|
2218
2185
|
const deletedIdSet = new Set(deletedSessionIds);
|
|
2219
|
-
await this.forgetUnreadSessions(readyRecords.filter((record) => deletedIdSet.has(record.sessionId)));
|
|
2186
|
+
await this.forgetUnreadSessions(readyRecords.filter((record) => deletedIdSet.has(record.sessionId)), eventScopeFromManagementContext(managementContext));
|
|
2220
2187
|
return {
|
|
2221
2188
|
deleted: true,
|
|
2222
2189
|
deletedSessionIds,
|
|
@@ -2241,19 +2208,19 @@ export class PiSessionService {
|
|
|
2241
2208
|
await this.closeActiveSession(active, priorGeneration === undefined ? CLEAR_RUNTIME_NOTIFICATIONS : DEFER_RUNTIME_NOTIFICATIONS);
|
|
2242
2209
|
candidateGeneration = priorGeneration === undefined
|
|
2243
2210
|
? undefined
|
|
2244
|
-
: this.
|
|
2211
|
+
: this.notificationsForSession(session).beginReplacement(priorGeneration, { sessionId, cwd });
|
|
2245
2212
|
const reopened = await this.openExistingSession(sessionId, cwd, () => this.sessionManager.open(sessionFile), {
|
|
2246
2213
|
...(managementContext === undefined ? {} : { managementContext }),
|
|
2247
2214
|
...(candidateGeneration === undefined ? {} : { notificationGeneration: candidateGeneration }),
|
|
2248
2215
|
});
|
|
2249
2216
|
if (candidateGeneration !== undefined) {
|
|
2250
|
-
this.publishNotificationMutations(this.
|
|
2217
|
+
this.publishNotificationMutations(this.notificationsForSession(session).commitReplacement(candidateGeneration));
|
|
2251
2218
|
}
|
|
2252
2219
|
return reopened.runtime.session;
|
|
2253
2220
|
}
|
|
2254
2221
|
catch (error) {
|
|
2255
2222
|
if (candidateGeneration !== undefined) {
|
|
2256
|
-
this.publishNotificationMutations(this.
|
|
2223
|
+
this.publishNotificationMutations(this.notificationsForSession(session).abortReplacement(candidateGeneration));
|
|
2257
2224
|
}
|
|
2258
2225
|
throw error;
|
|
2259
2226
|
}
|
|
@@ -2265,11 +2232,11 @@ export class PiSessionService {
|
|
|
2265
2232
|
const sessionFile = session.sessionFile;
|
|
2266
2233
|
if (sessionFile === undefined || sessionFile === "")
|
|
2267
2234
|
throw new Error("会话尚未持久化");
|
|
2268
|
-
|
|
2235
|
+
clearSessionFileParent(sessionFile);
|
|
2269
2236
|
this.sessionManager.invalidateSessionFile?.(sessionFile);
|
|
2270
2237
|
clearParentSessionHeader(session.sessionManager);
|
|
2271
2238
|
this.unregisterSubsession(session.sessionId);
|
|
2272
|
-
await this.forgetUnreadSessions([{ sessionId: session.sessionId, cwd: session.sessionManager.getCwd() }]);
|
|
2239
|
+
await this.forgetUnreadSessions([{ sessionId: session.sessionId, cwd: session.sessionManager.getCwd() }], this.eventScopeForSession(session));
|
|
2273
2240
|
}
|
|
2274
2241
|
async abort(ref, managementContext) {
|
|
2275
2242
|
const active = this.activeForLookup(ref, managementContext);
|
|
@@ -2317,7 +2284,7 @@ export class PiSessionService {
|
|
|
2317
2284
|
return;
|
|
2318
2285
|
}
|
|
2319
2286
|
if (isPiSessionRef(ref)) {
|
|
2320
|
-
this.publishNotificationMutations(this.
|
|
2287
|
+
this.publishNotificationMutations(this.notificationsForScope(eventScopeFromManagementContext(managementContext)).clearSessionIdentity(ref.id, canonicalizeStoredCwd(ref.cwd), "runtime-close"));
|
|
2321
2288
|
}
|
|
2322
2289
|
}
|
|
2323
2290
|
async bulkSessionLookupContext(refs) {
|
|
@@ -2494,17 +2461,17 @@ export class PiSessionService {
|
|
|
2494
2461
|
}
|
|
2495
2462
|
return [...names];
|
|
2496
2463
|
}
|
|
2497
|
-
async closeActive(sessionId, notificationPolicy = CLEAR_RUNTIME_NOTIFICATIONS) {
|
|
2464
|
+
async closeActive(sessionId, notificationPolicy = CLEAR_RUNTIME_NOTIFICATIONS, eventScope = NORMAL_SESSION_EVENT_SCOPE) {
|
|
2498
2465
|
for (const startup of this.startupSessions.values()) {
|
|
2499
|
-
if (startup.session.sessionId === sessionId)
|
|
2466
|
+
if (startup.session.sessionId === sessionId && startup.eventScope === eventScope)
|
|
2500
2467
|
this.endSessionExtensionDialogs(startup.session);
|
|
2501
2468
|
}
|
|
2502
2469
|
const pendingOpens = this.pendingSessionOpenPromises(sessionId);
|
|
2503
2470
|
if (pendingOpens.length > 0)
|
|
2504
2471
|
await Promise.allSettled(pendingOpens);
|
|
2505
|
-
const activeSessions = this.activeSessionsForId(sessionId);
|
|
2472
|
+
const activeSessions = this.activeSessionsForId(sessionId).filter((active) => active.eventScope === eventScope);
|
|
2506
2473
|
if (activeSessions.length === 0 && notificationPolicy.kind === "clear") {
|
|
2507
|
-
this.publishNotificationMutations(this.
|
|
2474
|
+
this.publishNotificationMutations(this.notificationsForScope(eventScope).clearSession(sessionId, notificationPolicy.reason));
|
|
2508
2475
|
return;
|
|
2509
2476
|
}
|
|
2510
2477
|
await Promise.all(activeSessions.map((active) => this.closeActiveSession(active, notificationPolicy)));
|
|
@@ -2515,16 +2482,17 @@ export class PiSessionService {
|
|
|
2515
2482
|
await this.closeActiveSession(active, notificationPolicy);
|
|
2516
2483
|
}
|
|
2517
2484
|
else if (notificationPolicy.kind === "clear") {
|
|
2518
|
-
this.publishNotificationMutations(this.
|
|
2485
|
+
this.publishNotificationMutations(this.notificationsForScope(eventScope).clearSession(sessionId, notificationPolicy.reason));
|
|
2519
2486
|
}
|
|
2520
2487
|
}
|
|
2521
2488
|
async closeActiveSession(active, notificationPolicy = CLEAR_RUNTIME_NOTIFICATIONS) {
|
|
2522
2489
|
const sessionId = active.runtime.session.sessionId;
|
|
2523
2490
|
if (notificationPolicy.kind === "clear") {
|
|
2524
2491
|
const generation = this.notificationGenerationBySession.get(active.runtime.session);
|
|
2492
|
+
const store = this.notificationsForScope(active.eventScope);
|
|
2525
2493
|
const mutations = generation === undefined
|
|
2526
|
-
?
|
|
2527
|
-
:
|
|
2494
|
+
? store.clearSession(sessionId, notificationPolicy.reason)
|
|
2495
|
+
: store.clearGeneration(generation, notificationPolicy.reason);
|
|
2528
2496
|
this.publishNotificationMutations(mutations);
|
|
2529
2497
|
}
|
|
2530
2498
|
this.forgetUnreadActivity(active.runtime.session);
|
|
@@ -2757,6 +2725,7 @@ export class PiSessionService {
|
|
|
2757
2725
|
}
|
|
2758
2726
|
}
|
|
2759
2727
|
bindRuntime(active, session = active.runtime.session) {
|
|
2728
|
+
this.sessionEventScopes.set(session, active.eventScope);
|
|
2760
2729
|
this.runtimeBySession.set(session, active.runtime);
|
|
2761
2730
|
active.unsubscribe();
|
|
2762
2731
|
for (const [sessionId, candidate] of this.active.entries()) {
|
|
@@ -3264,6 +3233,9 @@ export class PiSessionService {
|
|
|
3264
3233
|
return this.activities.get(activeSessionKey(session.sessionId, this.eventScopeForSession(session)));
|
|
3265
3234
|
}
|
|
3266
3235
|
eventScopeForSession(session) {
|
|
3236
|
+
const boundScope = this.sessionEventScopes.get(session);
|
|
3237
|
+
if (boundScope !== undefined)
|
|
3238
|
+
return boundScope;
|
|
3267
3239
|
for (const active of this.active.values()) {
|
|
3268
3240
|
if (active.runtime.session === session)
|
|
3269
3241
|
return active.eventScope;
|
|
@@ -3338,41 +3310,60 @@ export class PiSessionService {
|
|
|
3338
3310
|
hasQueuedMessageText(session, text) {
|
|
3339
3311
|
return queuedMessagesFromSession(session, this.compactionQueuedMessages(session.sessionId)).some((message) => message.text === text);
|
|
3340
3312
|
}
|
|
3341
|
-
|
|
3342
|
-
|
|
3313
|
+
notificationsForScope(scope) {
|
|
3314
|
+
if (scope === NORMAL_SESSION_EVENT_SCOPE)
|
|
3315
|
+
return this.notificationStore;
|
|
3316
|
+
let store = this.scopedNotificationStores.get(scope);
|
|
3317
|
+
if (store === undefined) {
|
|
3318
|
+
store = new SessionNotificationStore({ eventScope: scope, now: this.now });
|
|
3319
|
+
this.scopedNotificationStores.set(scope, store);
|
|
3320
|
+
}
|
|
3321
|
+
return store;
|
|
3322
|
+
}
|
|
3323
|
+
notificationsForSession(session) {
|
|
3324
|
+
return this.notificationsForScope(this.eventScopeForSession(session));
|
|
3343
3325
|
}
|
|
3344
|
-
|
|
3326
|
+
unreadForScope(scope) {
|
|
3327
|
+
if (scope === NORMAL_SESSION_EVENT_SCOPE)
|
|
3328
|
+
return this.unreadStore;
|
|
3329
|
+
let store = this.scopedUnreadStores.get(scope);
|
|
3330
|
+
if (store === undefined) {
|
|
3331
|
+
store = new SessionUnreadStore({ eventScope: scope, now: this.now });
|
|
3332
|
+
this.scopedUnreadStores.set(scope, store);
|
|
3333
|
+
}
|
|
3334
|
+
return store;
|
|
3335
|
+
}
|
|
3336
|
+
notificationCatalog(context) {
|
|
3337
|
+
return this.notificationsForScope(eventScopeFromManagementContext(context)).catalogSnapshot();
|
|
3338
|
+
}
|
|
3339
|
+
async unreadCatalog(context) {
|
|
3345
3340
|
await this.publishUnreadMutations([]);
|
|
3346
|
-
return this.
|
|
3341
|
+
return this.unreadForScope(eventScopeFromManagementContext(context)).durableCatalogSnapshot();
|
|
3347
3342
|
}
|
|
3348
|
-
/**
|
|
3343
|
+
/** The status tree and HTTP catalog must read the same scoped store. */
|
|
3349
3344
|
async unreadCatalogForScope(eventScope) {
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
return { ...catalog, sessions: catalog.sessions.filter((session) => visible.has(session.sessionId)) };
|
|
3357
|
-
}
|
|
3358
|
-
async acknowledgeUnread(sessionId, request) {
|
|
3359
|
-
const result = this.unreadStore.acknowledge(sessionId, {
|
|
3345
|
+
await this.publishUnreadMutations([]);
|
|
3346
|
+
return this.unreadForScope(eventScope).durableCatalogSnapshot();
|
|
3347
|
+
}
|
|
3348
|
+
async acknowledgeUnread(sessionId, request, context) {
|
|
3349
|
+
const store = this.unreadForScope(eventScopeFromManagementContext(context));
|
|
3350
|
+
const result = store.acknowledge(sessionId, {
|
|
3360
3351
|
...request,
|
|
3361
3352
|
cwd: canonicalizeStoredCwd(request.cwd),
|
|
3362
3353
|
});
|
|
3363
3354
|
await this.publishUnreadMutations(result.mutations);
|
|
3364
|
-
return
|
|
3355
|
+
return store.durableCatalogSnapshot();
|
|
3365
3356
|
}
|
|
3366
|
-
notificationInbox(ref) {
|
|
3367
|
-
return this.
|
|
3357
|
+
notificationInbox(ref, context) {
|
|
3358
|
+
return this.notificationsForScope(eventScopeFromManagementContext(context)).inboxSnapshot(ref.id, canonicalizeStoredCwd(ref.cwd));
|
|
3368
3359
|
}
|
|
3369
|
-
dismissNotification(ref, request) {
|
|
3370
|
-
const result = this.
|
|
3360
|
+
dismissNotification(ref, request, context) {
|
|
3361
|
+
const result = this.notificationsForScope(eventScopeFromManagementContext(context)).dismissNotification(ref.id, canonicalizeStoredCwd(ref.cwd), request.daemonInstanceId, request.notificationId);
|
|
3371
3362
|
this.publishNotificationMutations(result.mutations);
|
|
3372
3363
|
return result.snapshot;
|
|
3373
3364
|
}
|
|
3374
|
-
dismissAllNotifications(ref, request) {
|
|
3375
|
-
const result = this.
|
|
3365
|
+
dismissAllNotifications(ref, request, context) {
|
|
3366
|
+
const result = this.notificationsForScope(eventScopeFromManagementContext(context)).dismissAll(ref.id, canonicalizeStoredCwd(ref.cwd), request.daemonInstanceId, request.throughOrder, request.throughOverflowWatermark);
|
|
3376
3367
|
this.publishNotificationMutations(result.mutations);
|
|
3377
3368
|
return result.snapshot;
|
|
3378
3369
|
}
|
|
@@ -3393,20 +3384,20 @@ export class PiSessionService {
|
|
|
3393
3384
|
try {
|
|
3394
3385
|
await session.reload(priorGeneration === undefined ? undefined : {
|
|
3395
3386
|
beforeSessionStart: () => {
|
|
3396
|
-
candidateGeneration = this.
|
|
3387
|
+
candidateGeneration = this.notificationsForSession(session).beginReplacement(priorGeneration, notificationIdentityForSession(session));
|
|
3397
3388
|
this.notificationGenerationBySession.set(session, candidateGeneration);
|
|
3398
3389
|
this.replaceSessionNotificationContext(session, candidateGeneration);
|
|
3399
3390
|
},
|
|
3400
3391
|
});
|
|
3401
3392
|
if (candidateGeneration !== undefined) {
|
|
3402
|
-
this.publishNotificationMutations(this.
|
|
3393
|
+
this.publishNotificationMutations(this.notificationsForSession(session).commitReplacement(candidateGeneration));
|
|
3403
3394
|
}
|
|
3404
3395
|
this.publishActivity(session, "资源已重新加载", "idle");
|
|
3405
3396
|
this.publishStatus(session);
|
|
3406
3397
|
}
|
|
3407
3398
|
catch (error) {
|
|
3408
3399
|
if (candidateGeneration !== undefined) {
|
|
3409
|
-
this.publishNotificationMutations(this.
|
|
3400
|
+
this.publishNotificationMutations(this.notificationsForSession(session).abortReplacement(candidateGeneration, "candidate"));
|
|
3410
3401
|
this.notificationGenerationBySession.set(session, candidateGeneration);
|
|
3411
3402
|
}
|
|
3412
3403
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -3468,6 +3459,8 @@ export class PiSessionService {
|
|
|
3468
3459
|
}),
|
|
3469
3460
|
};
|
|
3470
3461
|
let boundSession = runtime.session;
|
|
3462
|
+
this.sessionEventScopes.set(boundSession, active.eventScope);
|
|
3463
|
+
const notificationStore = this.notificationsForScope(active.eventScope);
|
|
3471
3464
|
let notificationGeneration = options.notificationGeneration;
|
|
3472
3465
|
let notificationOwnership = options.notifications === "disabled"
|
|
3473
3466
|
? "disabled"
|
|
@@ -3476,13 +3469,13 @@ export class PiSessionService {
|
|
|
3476
3469
|
: "external";
|
|
3477
3470
|
if (notificationOwnership === "registered") {
|
|
3478
3471
|
const notificationIdentity = notificationIdentityForSession(runtime.session);
|
|
3479
|
-
const existingCandidate =
|
|
3472
|
+
const existingCandidate = notificationStore.beginReplacementForSession(notificationIdentity.sessionId, notificationIdentity.cwd);
|
|
3480
3473
|
if (existingCandidate !== undefined) {
|
|
3481
3474
|
notificationGeneration = existingCandidate;
|
|
3482
3475
|
notificationOwnership = "replacement";
|
|
3483
3476
|
}
|
|
3484
3477
|
else {
|
|
3485
|
-
const registration =
|
|
3478
|
+
const registration = notificationStore.registerSession(notificationIdentity.sessionId, notificationIdentity.cwd);
|
|
3486
3479
|
notificationGeneration = registration.generation;
|
|
3487
3480
|
this.publishNotificationMutations(registration.mutations);
|
|
3488
3481
|
}
|
|
@@ -3491,7 +3484,7 @@ export class PiSessionService {
|
|
|
3491
3484
|
this.notificationGenerationBySession.set(runtime.session, notificationGeneration);
|
|
3492
3485
|
try {
|
|
3493
3486
|
if (options.creationProvenance === "tracked-subsession") {
|
|
3494
|
-
await this.publishUnreadMutations(this.
|
|
3487
|
+
await this.publishUnreadMutations(this.unreadForScope(active.eventScope).excludeSession(runtime.session.sessionId, canonicalizeStoredCwd(runtime.session.sessionManager.getCwd())));
|
|
3495
3488
|
}
|
|
3496
3489
|
else {
|
|
3497
3490
|
await this.recoverSubsessionTrackingForOpenedSession(runtime.session);
|
|
@@ -3500,13 +3493,14 @@ export class PiSessionService {
|
|
|
3500
3493
|
await this.bindSessionExtensions(runtime.session, notificationGeneration, active);
|
|
3501
3494
|
this.bindRuntime(active);
|
|
3502
3495
|
runtime.setRebindSession(async (session) => {
|
|
3496
|
+
this.sessionEventScopes.set(session, active.eventScope);
|
|
3503
3497
|
const priorGeneration = notificationGeneration;
|
|
3504
3498
|
let candidateGeneration;
|
|
3505
3499
|
try {
|
|
3506
3500
|
await this.prepareUnreadRuntimeRebind(boundSession, session);
|
|
3507
3501
|
await this.recoverSubsessionTrackingForOpenedSession(session);
|
|
3508
3502
|
if (priorGeneration !== undefined) {
|
|
3509
|
-
candidateGeneration =
|
|
3503
|
+
candidateGeneration = notificationStore.beginReplacement(priorGeneration, notificationIdentityForSession(session));
|
|
3510
3504
|
this.notificationGenerationBySession.set(session, candidateGeneration);
|
|
3511
3505
|
}
|
|
3512
3506
|
this.bindRuntime(active, session);
|
|
@@ -3517,13 +3511,13 @@ export class PiSessionService {
|
|
|
3517
3511
|
boundSession = session;
|
|
3518
3512
|
await this.bindSessionExtensions(session, candidateGeneration, active);
|
|
3519
3513
|
if (candidateGeneration !== undefined) {
|
|
3520
|
-
this.publishNotificationMutations(
|
|
3514
|
+
this.publishNotificationMutations(notificationStore.commitReplacement(candidateGeneration));
|
|
3521
3515
|
notificationGeneration = candidateGeneration;
|
|
3522
3516
|
}
|
|
3523
3517
|
}
|
|
3524
3518
|
catch (error) {
|
|
3525
3519
|
if (candidateGeneration !== undefined) {
|
|
3526
|
-
this.publishNotificationMutations(
|
|
3520
|
+
this.publishNotificationMutations(notificationStore.abortReplacement(candidateGeneration, "candidate"));
|
|
3527
3521
|
notificationGeneration = candidateGeneration;
|
|
3528
3522
|
this.notificationGenerationBySession.set(session, candidateGeneration);
|
|
3529
3523
|
}
|
|
@@ -3532,7 +3526,7 @@ export class PiSessionService {
|
|
|
3532
3526
|
});
|
|
3533
3527
|
this.active.set(activeSessionKey(runtime.session.sessionId, active.eventScope), active);
|
|
3534
3528
|
if (notificationOwnership === "replacement" && notificationGeneration !== undefined) {
|
|
3535
|
-
this.publishNotificationMutations(
|
|
3529
|
+
this.publishNotificationMutations(notificationStore.commitReplacement(notificationGeneration));
|
|
3536
3530
|
notificationOwnership = "external";
|
|
3537
3531
|
}
|
|
3538
3532
|
this.publishStatus(runtime.session);
|
|
@@ -3541,10 +3535,10 @@ export class PiSessionService {
|
|
|
3541
3535
|
catch (error) {
|
|
3542
3536
|
if (notificationGeneration !== undefined) {
|
|
3543
3537
|
if (notificationOwnership === "registered") {
|
|
3544
|
-
this.publishNotificationMutations(
|
|
3538
|
+
this.publishNotificationMutations(notificationStore.clearSession(runtime.session.sessionId, "initialization-failed"));
|
|
3545
3539
|
}
|
|
3546
3540
|
else if (notificationOwnership === "replacement") {
|
|
3547
|
-
this.publishNotificationMutations(
|
|
3541
|
+
this.publishNotificationMutations(notificationStore.abortReplacement(notificationGeneration));
|
|
3548
3542
|
}
|
|
3549
3543
|
}
|
|
3550
3544
|
active.unsubscribe();
|
|
@@ -3577,16 +3571,18 @@ export class PiSessionService {
|
|
|
3577
3571
|
}
|
|
3578
3572
|
sessionUiContext(session, generation) {
|
|
3579
3573
|
const baseUiContext = session.extensionRunner.getUIContext();
|
|
3574
|
+
const eventScope = this.eventScopeForSession(session);
|
|
3575
|
+
const notificationStore = this.notificationsForScope(eventScope);
|
|
3580
3576
|
const notify = (message, type) => {
|
|
3581
3577
|
if (generation === undefined) {
|
|
3582
3578
|
this.events.publish(session.sessionId, {
|
|
3583
3579
|
type: "command.output",
|
|
3584
3580
|
level: type === "error" ? "error" : "info",
|
|
3585
3581
|
message,
|
|
3586
|
-
});
|
|
3582
|
+
}, eventScope);
|
|
3587
3583
|
return;
|
|
3588
3584
|
}
|
|
3589
|
-
const added =
|
|
3585
|
+
const added = notificationStore.addNotification(generation, message, type);
|
|
3590
3586
|
this.publishNotificationMutations(added.mutations);
|
|
3591
3587
|
if (added.notification === undefined)
|
|
3592
3588
|
return;
|
|
@@ -3595,7 +3591,7 @@ export class PiSessionService {
|
|
|
3595
3591
|
level: type === "error" ? "error" : "info",
|
|
3596
3592
|
message,
|
|
3597
3593
|
notificationId: added.notification.id,
|
|
3598
|
-
});
|
|
3594
|
+
}, eventScope);
|
|
3599
3595
|
};
|
|
3600
3596
|
// PI WEB owns the browser-facing dialog, notification, and text-formatting
|
|
3601
3597
|
// boundaries: the three dialog primitives park daemon-held Promises that
|
|
@@ -3624,35 +3620,32 @@ export class PiSessionService {
|
|
|
3624
3620
|
}
|
|
3625
3621
|
publishNotificationMutations(mutations) {
|
|
3626
3622
|
for (const mutation of mutations) {
|
|
3627
|
-
const
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
for (const scope of scopes) {
|
|
3631
|
-
this.events.publish(mutation.sessionId, mutation.inboxEvent, scope);
|
|
3632
|
-
this.events.publishGlobal(mutation.summaryEvent, scope);
|
|
3633
|
-
}
|
|
3623
|
+
const scope = mutation.eventScope ?? NORMAL_SESSION_EVENT_SCOPE;
|
|
3624
|
+
this.events.publish(mutation.sessionId, mutation.inboxEvent, scope);
|
|
3625
|
+
this.events.publishGlobal(mutation.summaryEvent, scope);
|
|
3634
3626
|
}
|
|
3635
3627
|
}
|
|
3636
3628
|
async prepareUnreadRuntimeRebind(previous, next) {
|
|
3637
3629
|
const previousCwd = canonicalizeStoredCwd(previous.sessionManager.getCwd());
|
|
3638
|
-
this.
|
|
3630
|
+
const store = this.unreadForScope(this.eventScopeForSession(previous));
|
|
3631
|
+
store.forgetActivity(previous.sessionId, previousCwd);
|
|
3639
3632
|
const nextCwd = canonicalizeStoredCwd(next.sessionManager.getCwd());
|
|
3640
3633
|
if (previous.sessionId === next.sessionId && cwdPathsEqual(previousCwd, nextCwd))
|
|
3641
3634
|
return;
|
|
3642
|
-
await this.publishUnreadMutations(
|
|
3635
|
+
await this.publishUnreadMutations(store.forgetSession(previous.sessionId, previousCwd));
|
|
3643
3636
|
}
|
|
3644
3637
|
forgetUnreadActivity(session) {
|
|
3645
|
-
this.
|
|
3638
|
+
this.unreadForScope(this.eventScopeForSession(session)).forgetActivity(session.sessionId, canonicalizeStoredCwd(session.sessionManager.getCwd()));
|
|
3646
3639
|
}
|
|
3647
|
-
async forgetUnreadSessions(identities) {
|
|
3640
|
+
async forgetUnreadSessions(identities, scope = NORMAL_SESSION_EVENT_SCOPE) {
|
|
3648
3641
|
const mutations = [];
|
|
3649
3642
|
for (const identity of identities) {
|
|
3650
|
-
mutations.push(...this.
|
|
3643
|
+
mutations.push(...this.unreadForScope(scope).forgetSession(identity.sessionId, canonicalizeStoredCwd(identity.cwd)));
|
|
3651
3644
|
}
|
|
3652
3645
|
await this.publishUnreadMutations(mutations);
|
|
3653
3646
|
}
|
|
3654
3647
|
observeUnreadActivityState(session) {
|
|
3655
|
-
const mutations = this.
|
|
3648
|
+
const mutations = this.unreadForScope(this.eventScopeForSession(session)).observeActivityState(session.sessionId, canonicalizeStoredCwd(session.sessionManager.getCwd()), this.hasActiveWork(session));
|
|
3656
3649
|
if (mutations.length === 0)
|
|
3657
3650
|
return;
|
|
3658
3651
|
void this.publishUnreadMutations(mutations).catch(() => undefined);
|
|
@@ -3661,11 +3654,7 @@ export class PiSessionService {
|
|
|
3661
3654
|
if (mutations.length > 0) {
|
|
3662
3655
|
const scopes = new Set();
|
|
3663
3656
|
for (const mutation of mutations) {
|
|
3664
|
-
|
|
3665
|
-
if (activeScopes.length === 0)
|
|
3666
|
-
scopes.add(NORMAL_SESSION_EVENT_SCOPE);
|
|
3667
|
-
else
|
|
3668
|
-
activeScopes.forEach((scope) => scopes.add(scope));
|
|
3657
|
+
scopes.add(mutation.eventScope ?? NORMAL_SESSION_EVENT_SCOPE);
|
|
3669
3658
|
}
|
|
3670
3659
|
scopes.forEach((scope) => this.onUnreadChanged?.(scope));
|
|
3671
3660
|
}
|
|
@@ -3704,8 +3693,10 @@ export class PiSessionService {
|
|
|
3704
3693
|
let publishedCount = 0;
|
|
3705
3694
|
try {
|
|
3706
3695
|
await this.unreadStore.flush();
|
|
3696
|
+
for (const store of this.scopedUnreadStores.values())
|
|
3697
|
+
await store.flush();
|
|
3707
3698
|
for (const mutation of batch) {
|
|
3708
|
-
this.events.publishGlobal(mutation.event);
|
|
3699
|
+
this.events.publishGlobal(mutation.event, mutation.eventScope ?? NORMAL_SESSION_EVENT_SCOPE);
|
|
3709
3700
|
publishedCount += 1;
|
|
3710
3701
|
}
|
|
3711
3702
|
}
|
|
@@ -4040,19 +4031,6 @@ async function sessionFileHeaderMatches(sessionFile, expected) {
|
|
|
4040
4031
|
return true;
|
|
4041
4032
|
return header.parentSession !== undefined && sessionPathsEqual(header.parentSession, expected.parentSessionFile);
|
|
4042
4033
|
}
|
|
4043
|
-
async function clearParentSession(sessionFile) {
|
|
4044
|
-
const content = await readFile(sessionFile, "utf8");
|
|
4045
|
-
const newlineIndex = content.indexOf("\n");
|
|
4046
|
-
const firstLine = newlineIndex === -1 ? content : content.slice(0, newlineIndex);
|
|
4047
|
-
const rest = newlineIndex === -1 ? "" : content.slice(newlineIndex);
|
|
4048
|
-
const header = JSON.parse(firstLine);
|
|
4049
|
-
if (!isRecord(header) || header["type"] !== "session")
|
|
4050
|
-
throw new Error("Invalid session file header");
|
|
4051
|
-
if (header["parentSession"] === undefined)
|
|
4052
|
-
return;
|
|
4053
|
-
delete header["parentSession"];
|
|
4054
|
-
await writeFile(sessionFile, `${JSON.stringify(header)}${rest}`, "utf8");
|
|
4055
|
-
}
|
|
4056
4034
|
function clearParentSessionHeader(sessionManager) {
|
|
4057
4035
|
const header = sessionManager.getHeader?.();
|
|
4058
4036
|
if (header !== undefined && header !== null)
|