@chainingintention/pi-web-cn 1.202609.2 → 1.202609.5
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 +127 -110
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/relaySkill.js +25 -13
- package/dist/server/sessions/relaySkill.js.map +1 -1
- package/dist/server/sessions/sessionFileHeader.js +41 -0
- package/dist/server/sessions/sessionFileHeader.js.map +1 -1
- package/dist/server/sessions/sessionNameGenerator.js +1 -1
- package/dist/server/sessions/sessionNameGenerator.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 +9 -9
- package/skills/relay-runner/SKILL.md +245 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { realpathSync, statSync } from "node:fs";
|
|
2
|
-
import { readFile
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, getAgentDir, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, defineTool, readStoredCredential, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { projectBrowserMessage } from "../browserMessageProjection.js";
|
|
@@ -22,7 +22,7 @@ import { SessionActivityCoordinator } from "./sessionActivityCoordinator.js";
|
|
|
22
22
|
import { searchSessionContent } from "./sessionContentSearch.js";
|
|
23
23
|
import { createLocalOnlyModelRuntime } from "./authService.js";
|
|
24
24
|
import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
|
|
25
|
-
import { readSessionHeaderSummary } from "./sessionFileHeader.js";
|
|
25
|
+
import { clearSessionFileParent, readSessionHeaderSummary } from "./sessionFileHeader.js";
|
|
26
26
|
import { workbenchAccessHandle } from "../managementEmbed.js";
|
|
27
27
|
import { DEFAULT_EXTENSION_DIALOGS_TIMEOUT_MS, piWebDataDir } from "../../config.js";
|
|
28
28
|
import { createScopedSettingsManager, resolveSettingsScopeDirectory } from "./projectSettingsScope.js";
|
|
@@ -38,7 +38,7 @@ import { createManagedAgentToolOptions, createManagedPythonToolDefinition } from
|
|
|
38
38
|
import { PI_PERMISSION_SYSTEM_POLICY_AGENT_DIR, managementAgentToolNames, withRuntimeCreationEnvironment, writeManagementPermissionSystemPolicy } from "./managementPermissionSystem.js";
|
|
39
39
|
import { SessionNotificationStore } from "./sessionNotificationStore.js";
|
|
40
40
|
import { plainTextTheme } from "./plainTextTheme.js";
|
|
41
|
-
import {
|
|
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";
|
|
@@ -203,7 +203,7 @@ export function filterManagedProjectSkills(cwd, base) {
|
|
|
203
203
|
export function filterManagedWorkbenchSkills(cwd, base, receipt) {
|
|
204
204
|
const projectRoot = realpathSync(cwd);
|
|
205
205
|
const allowedDirectories = new Set([
|
|
206
|
-
resolve(projectRoot,
|
|
206
|
+
...MANAGED_RELAY_SKILL_NAMES.map((name) => resolve(projectRoot, ".pi", "skills", name)),
|
|
207
207
|
...receipt.skills.map((skill) => resolve(projectRoot, ".pi", "skills", skill.directory)),
|
|
208
208
|
]);
|
|
209
209
|
const pathAllowed = (path) => [...allowedDirectories].some((directory) => pathInsideRealDirectory(directory, path));
|
|
@@ -393,7 +393,7 @@ function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRu
|
|
|
393
393
|
});
|
|
394
394
|
const resourceLoaderOptions = {
|
|
395
395
|
...piWebResourceLoaderOptions(appendSystemPromptSections),
|
|
396
|
-
skillsOverride: (base) =>
|
|
396
|
+
skillsOverride: (base) => deduplicateBundledRelaySkills(cwd, base),
|
|
397
397
|
};
|
|
398
398
|
const services = await createAgentSessionServices({
|
|
399
399
|
cwd,
|
|
@@ -424,7 +424,7 @@ function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRu
|
|
|
424
424
|
}
|
|
425
425
|
function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spawn, subsessions, askUser, managementContext, workbench, logger, managementAudit, managementProjectIdForCwd) {
|
|
426
426
|
return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, initialThinkingLevel, delegationToolsEnabled = true }) => {
|
|
427
|
-
await
|
|
427
|
+
await ensureManagedRelaySkills(cwd);
|
|
428
428
|
const auditIdentity = await resolveManagementAuditIdentity(cwd, managementContext, managementProjectIdForCwd, logger);
|
|
429
429
|
const accessHandle = workbenchAccessHandle(managementContext);
|
|
430
430
|
const state = workbench === undefined ? undefined : workbench.accessStates.require(accessHandle);
|
|
@@ -602,6 +602,9 @@ export class PiSessionService {
|
|
|
602
602
|
* child that works again (and stops again) notifies the parent each time.
|
|
603
603
|
*/
|
|
604
604
|
this.subsessionNotifyArmed = new Map();
|
|
605
|
+
this.scopedNotificationStores = new Map();
|
|
606
|
+
this.scopedUnreadStores = new Map();
|
|
607
|
+
this.sessionEventScopes = new WeakMap();
|
|
605
608
|
this.notificationGenerationBySession = new WeakMap();
|
|
606
609
|
this.idleSessionFileResolutions = new WeakMap();
|
|
607
610
|
/** The parked extension Promise resolvers behind the store's open dialogs. */
|
|
@@ -697,7 +700,8 @@ export class PiSessionService {
|
|
|
697
700
|
async cleanupPreview(request) {
|
|
698
701
|
return previewResponseFromPlan(await this.cleanupPlan(request));
|
|
699
702
|
}
|
|
700
|
-
async cleanup(request) {
|
|
703
|
+
async cleanup(request, context) {
|
|
704
|
+
const scope = eventScopeFromManagementContext(context);
|
|
701
705
|
const plan = await this.cleanupPlan(request);
|
|
702
706
|
if (plan.deleteRecords.length > 0 && this.archiveStore.deleteArchived === undefined && this.archiveStore.deleteArchivedMany === undefined)
|
|
703
707
|
throw new Error("Archive store does not support deletion");
|
|
@@ -711,24 +715,24 @@ export class PiSessionService {
|
|
|
711
715
|
skippedBusySessionIds.add(input.sessionId);
|
|
712
716
|
continue;
|
|
713
717
|
}
|
|
714
|
-
await this.closeActive(input.sessionId, { kind: "clear", reason: "archive" });
|
|
718
|
+
await this.closeActive(input.sessionId, { kind: "clear", reason: "archive" }, scope);
|
|
715
719
|
readyArchiveInputs.push(input);
|
|
716
720
|
}
|
|
717
721
|
await this.archiveStoreArchiveMany(readyArchiveInputs);
|
|
718
722
|
archiveInputs.push(...readyArchiveInputs);
|
|
719
|
-
await this.forgetUnreadSessions(readyArchiveInputs);
|
|
723
|
+
await this.forgetUnreadSessions(readyArchiveInputs, scope);
|
|
720
724
|
for (const record of plan.deleteRecords) {
|
|
721
725
|
if (this.activeSessionHasWork(record.sessionId)) {
|
|
722
726
|
skippedBusySessionIds.add(record.sessionId);
|
|
723
727
|
continue;
|
|
724
728
|
}
|
|
725
|
-
await this.closeActive(record.sessionId, { kind: "clear", reason: "delete" });
|
|
729
|
+
await this.closeActive(record.sessionId, { kind: "clear", reason: "delete" }, scope);
|
|
726
730
|
readyDeleteRecords.push(record);
|
|
727
731
|
}
|
|
728
732
|
await this.ensureArchivedRecordsMoved(readyDeleteRecords);
|
|
729
733
|
const deletedSessionIds = new Set(await this.archiveStoreDeleteArchivedMany(readyDeleteRecords.map((record) => record.sessionId)));
|
|
730
734
|
deleteRecords.push(...readyDeleteRecords.filter((record) => deletedSessionIds.has(record.sessionId)));
|
|
731
|
-
await this.forgetUnreadSessions(deleteRecords);
|
|
735
|
+
await this.forgetUnreadSessions(deleteRecords, scope);
|
|
732
736
|
return summarizeSessionCleanupExecution({
|
|
733
737
|
archiveInputs,
|
|
734
738
|
deleteRecords,
|
|
@@ -767,6 +771,8 @@ export class PiSessionService {
|
|
|
767
771
|
this.subsessionHydratedParents.clear();
|
|
768
772
|
this.subsessionNotifyArmed.clear();
|
|
769
773
|
this.notificationStore.clearAll("service-dispose");
|
|
774
|
+
for (const store of this.scopedNotificationStores.values())
|
|
775
|
+
store.clearAll("service-dispose");
|
|
770
776
|
await Promise.all(activeSessions.map(async (active) => {
|
|
771
777
|
active.unsubscribe();
|
|
772
778
|
this.workspaceActivity?.removeSession(active.runtime.session.sessionId, active.runtime.session.sessionManager.getCwd(), active.eventScope);
|
|
@@ -831,13 +837,13 @@ export class PiSessionService {
|
|
|
831
837
|
.map((record) => this.ensureArchivedSessionMoved(record, sessionsById.get(record.sessionId))));
|
|
832
838
|
const archivedById = new Map(archivedForCwd.map((record) => [record.sessionId, record]));
|
|
833
839
|
for (const record of archivedForCwd) {
|
|
834
|
-
this.publishNotificationMutations(this.
|
|
840
|
+
this.publishNotificationMutations(this.notificationsForScope(eventScopeFromManagementContext(managementContext)).clearSession(record.sessionId, "archive-reconcile"));
|
|
835
841
|
}
|
|
836
842
|
const unarchivedSessions = sessions.filter((session) => !archivedById.has(session.id)).map(clientSessionFromListEntry);
|
|
837
843
|
const reconcilableSessionIds = this.reconcilableSessionIds(cwd, unarchivedSessions.map((session) => session.id), archivedById);
|
|
838
844
|
this.workspaceActivity?.reconcileSessionActivity(cwd, reconcilableSessionIds, eventScopeFromManagementContext(managementContext));
|
|
839
845
|
if (managementContext === undefined) {
|
|
840
|
-
await this.publishUnreadMutations(this.
|
|
846
|
+
await this.publishUnreadMutations(this.unreadForScope(eventScopeFromManagementContext(managementContext)).reconcileCwd(canonicalizeStoredCwd(cwd), reconcilableSessionIds));
|
|
841
847
|
}
|
|
842
848
|
const archivedSessions = archivedForCwd
|
|
843
849
|
.sort(compareArchivedRecords)
|
|
@@ -946,7 +952,7 @@ export class PiSessionService {
|
|
|
946
952
|
...(parentSessionFile === undefined ? {} : { parentSessionFile }),
|
|
947
953
|
cwd: decision.cwd,
|
|
948
954
|
};
|
|
949
|
-
await this.registerVerifiedSubsession(link);
|
|
955
|
+
await this.registerVerifiedSubsession(link, eventScopeFromManagementContext(input.managementContext));
|
|
950
956
|
this.persistSubsessionLink(link);
|
|
951
957
|
this.persistSubsessionChildMarker(input.parentSessionId, created.id);
|
|
952
958
|
await this.prompt(created.id, input.prompt, undefined, undefined, {
|
|
@@ -1321,7 +1327,7 @@ export class PiSessionService {
|
|
|
1321
1327
|
return undefined;
|
|
1322
1328
|
return sessionFileMatches(session, link.childSessionFile) ? link : undefined;
|
|
1323
1329
|
}
|
|
1324
|
-
async registerVerifiedSubsession(link) {
|
|
1330
|
+
async registerVerifiedSubsession(link, scope = NORMAL_SESSION_EVENT_SCOPE) {
|
|
1325
1331
|
const { childSessionId, parentSessionId } = link;
|
|
1326
1332
|
const previousParentId = this.subsessionParents.get(childSessionId);
|
|
1327
1333
|
if (previousParentId !== undefined && previousParentId !== parentSessionId) {
|
|
@@ -1338,7 +1344,7 @@ export class PiSessionService {
|
|
|
1338
1344
|
if (!this.subsessionNotifyArmed.has(childSessionId))
|
|
1339
1345
|
this.subsessionNotifyArmed.set(childSessionId, false);
|
|
1340
1346
|
const cwd = this.cwdForVerifiedSubsession(link);
|
|
1341
|
-
await this.publishUnreadMutations(this.
|
|
1347
|
+
await this.publishUnreadMutations(this.unreadForScope(scope).excludeSession(childSessionId, cwd));
|
|
1342
1348
|
}
|
|
1343
1349
|
cwdForVerifiedSubsession(link) {
|
|
1344
1350
|
const activeCwd = this.activeChildForSubsessionLink(link)?.runtime.session.sessionManager.getCwd();
|
|
@@ -1401,7 +1407,7 @@ export class PiSessionService {
|
|
|
1401
1407
|
const activeParent = this.activeForSessionId(parentSessionId);
|
|
1402
1408
|
if (activeParent !== undefined && (parentSessionFile === undefined || activeSessionFileMatches(activeParent, parentSessionFile))) {
|
|
1403
1409
|
const activeParentFile = nonEmptyString(activeParent.runtime.session.sessionFile);
|
|
1404
|
-
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);
|
|
1405
1411
|
if (complete)
|
|
1406
1412
|
this.subsessionHydratedParents.add(hydrationKey);
|
|
1407
1413
|
return;
|
|
@@ -1421,7 +1427,7 @@ export class PiSessionService {
|
|
|
1421
1427
|
if (complete)
|
|
1422
1428
|
this.subsessionHydratedParents.add(hydrationKey);
|
|
1423
1429
|
}
|
|
1424
|
-
async registerPersistedSubsessionLinks(parentSessionId, parentManager, parentSessionFile) {
|
|
1430
|
+
async registerPersistedSubsessionLinks(parentSessionId, parentManager, parentSessionFile, scope = NORMAL_SESSION_EVENT_SCOPE) {
|
|
1425
1431
|
// Parent custom links are the authoritative recovery record: verify the
|
|
1426
1432
|
// exact live child file/header before tracking. Do not negatively cache a
|
|
1427
1433
|
// scan while a candidate child is temporarily unavailable.
|
|
@@ -1436,7 +1442,7 @@ export class PiSessionService {
|
|
|
1436
1442
|
complete = false;
|
|
1437
1443
|
continue;
|
|
1438
1444
|
}
|
|
1439
|
-
await this.registerVerifiedSubsession(verified);
|
|
1445
|
+
await this.registerVerifiedSubsession(verified, scope);
|
|
1440
1446
|
}
|
|
1441
1447
|
return complete;
|
|
1442
1448
|
}
|
|
@@ -1457,7 +1463,7 @@ export class PiSessionService {
|
|
|
1457
1463
|
const link = await this.verifiedSubsessionLinkFromOpenedChild(session);
|
|
1458
1464
|
if (link === undefined)
|
|
1459
1465
|
return;
|
|
1460
|
-
await this.registerVerifiedSubsession(link);
|
|
1466
|
+
await this.registerVerifiedSubsession(link, this.eventScopeForSession(session));
|
|
1461
1467
|
}
|
|
1462
1468
|
verifiedSubsessionLinkFromOpenedChild(session) {
|
|
1463
1469
|
return verifiedTrackedSubsessionLink(this.sessionManager, {
|
|
@@ -1995,7 +2001,7 @@ export class PiSessionService {
|
|
|
1995
2001
|
const archiveInput = await this.archiveInputForSession(session);
|
|
1996
2002
|
await this.closeActiveSession(active);
|
|
1997
2003
|
await this.archiveStore.archive(archiveInput);
|
|
1998
|
-
await this.forgetUnreadSessions([archiveInput]);
|
|
2004
|
+
await this.forgetUnreadSessions([archiveInput], active.eventScope);
|
|
1999
2005
|
});
|
|
2000
2006
|
}
|
|
2001
2007
|
async archiveMany(refs, managementContext) {
|
|
@@ -2011,7 +2017,7 @@ export class PiSessionService {
|
|
|
2011
2017
|
for (const ref of uniqueRefs) {
|
|
2012
2018
|
const archived = findArchivedRecordForBulkRef(archivedRecords, ref);
|
|
2013
2019
|
if (archived !== undefined) {
|
|
2014
|
-
this.publishNotificationMutations(this.
|
|
2020
|
+
this.publishNotificationMutations(this.notificationsForScope(eventScopeFromManagementContext(managementContext)).clearSession(archived.sessionId, "archive"));
|
|
2015
2021
|
alreadyArchivedSessionIds.push(archived.sessionId);
|
|
2016
2022
|
unreadArchivedIdentities.push(archived);
|
|
2017
2023
|
continue;
|
|
@@ -2075,7 +2081,7 @@ export class PiSessionService {
|
|
|
2075
2081
|
failures.push({ sessionId: input.sessionId, error: errorMessage(error) });
|
|
2076
2082
|
}
|
|
2077
2083
|
});
|
|
2078
|
-
await this.forgetUnreadSessions(unreadArchivedIdentities);
|
|
2084
|
+
await this.forgetUnreadSessions(unreadArchivedIdentities, eventScopeFromManagementContext(managementContext));
|
|
2079
2085
|
return {
|
|
2080
2086
|
archived: true,
|
|
2081
2087
|
archivedSessionIds: uniqueStrings(archivedSessionIds),
|
|
@@ -2098,13 +2104,13 @@ export class PiSessionService {
|
|
|
2098
2104
|
})), `归档 ${sessionDisplayName(session)} 前请先停止当前会话活动`, async () => {
|
|
2099
2105
|
for (const target of plan.targets) {
|
|
2100
2106
|
if (target.archived)
|
|
2101
|
-
this.publishNotificationMutations(this.
|
|
2107
|
+
this.publishNotificationMutations(this.notificationsForScope(eventScopeFromManagementContext(managementContext)).clearSession(target.id, "archive"));
|
|
2102
2108
|
}
|
|
2103
2109
|
for (const input of archiveInputs)
|
|
2104
2110
|
await this.closeActiveInScope(input.sessionId, eventScopeFromManagementContext(managementContext));
|
|
2105
2111
|
await this.archiveStoreArchiveMany(archiveInputs);
|
|
2106
2112
|
});
|
|
2107
|
-
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));
|
|
2108
2114
|
return {
|
|
2109
2115
|
archived: true,
|
|
2110
2116
|
sessionIds: archiveInputs.map((input) => input.sessionId),
|
|
@@ -2118,7 +2124,7 @@ export class PiSessionService {
|
|
|
2118
2124
|
throw new Error("未找到会话");
|
|
2119
2125
|
await this.closeActiveInScope(archived.sessionId, eventScopeFromManagementContext(managementContext));
|
|
2120
2126
|
await this.archiveStore.restore(archived.sessionId);
|
|
2121
|
-
await this.forgetUnreadSessions([archived]);
|
|
2127
|
+
await this.forgetUnreadSessions([archived], eventScopeFromManagementContext(managementContext));
|
|
2122
2128
|
}
|
|
2123
2129
|
async deleteArchived(ref, managementContext) {
|
|
2124
2130
|
const record = await this.getArchived(ref);
|
|
@@ -2130,7 +2136,7 @@ export class PiSessionService {
|
|
|
2130
2136
|
if (record.archivePath === undefined)
|
|
2131
2137
|
await this.ensureArchivedRecordMoved(record);
|
|
2132
2138
|
await this.archiveStore.deleteArchived(record.sessionId);
|
|
2133
|
-
await this.forgetUnreadSessions([record]);
|
|
2139
|
+
await this.forgetUnreadSessions([record], eventScopeFromManagementContext(managementContext));
|
|
2134
2140
|
}
|
|
2135
2141
|
async deleteArchivedMany(refs, managementContext) {
|
|
2136
2142
|
if (this.archiveStore.deleteArchived === undefined && this.archiveStore.deleteArchivedMany === undefined)
|
|
@@ -2177,7 +2183,7 @@ export class PiSessionService {
|
|
|
2177
2183
|
failures.push({ sessionId, error: errorMessage(error) });
|
|
2178
2184
|
}
|
|
2179
2185
|
const deletedIdSet = new Set(deletedSessionIds);
|
|
2180
|
-
await this.forgetUnreadSessions(readyRecords.filter((record) => deletedIdSet.has(record.sessionId)));
|
|
2186
|
+
await this.forgetUnreadSessions(readyRecords.filter((record) => deletedIdSet.has(record.sessionId)), eventScopeFromManagementContext(managementContext));
|
|
2181
2187
|
return {
|
|
2182
2188
|
deleted: true,
|
|
2183
2189
|
deletedSessionIds,
|
|
@@ -2202,19 +2208,19 @@ export class PiSessionService {
|
|
|
2202
2208
|
await this.closeActiveSession(active, priorGeneration === undefined ? CLEAR_RUNTIME_NOTIFICATIONS : DEFER_RUNTIME_NOTIFICATIONS);
|
|
2203
2209
|
candidateGeneration = priorGeneration === undefined
|
|
2204
2210
|
? undefined
|
|
2205
|
-
: this.
|
|
2211
|
+
: this.notificationsForSession(session).beginReplacement(priorGeneration, { sessionId, cwd });
|
|
2206
2212
|
const reopened = await this.openExistingSession(sessionId, cwd, () => this.sessionManager.open(sessionFile), {
|
|
2207
2213
|
...(managementContext === undefined ? {} : { managementContext }),
|
|
2208
2214
|
...(candidateGeneration === undefined ? {} : { notificationGeneration: candidateGeneration }),
|
|
2209
2215
|
});
|
|
2210
2216
|
if (candidateGeneration !== undefined) {
|
|
2211
|
-
this.publishNotificationMutations(this.
|
|
2217
|
+
this.publishNotificationMutations(this.notificationsForSession(session).commitReplacement(candidateGeneration));
|
|
2212
2218
|
}
|
|
2213
2219
|
return reopened.runtime.session;
|
|
2214
2220
|
}
|
|
2215
2221
|
catch (error) {
|
|
2216
2222
|
if (candidateGeneration !== undefined) {
|
|
2217
|
-
this.publishNotificationMutations(this.
|
|
2223
|
+
this.publishNotificationMutations(this.notificationsForSession(session).abortReplacement(candidateGeneration));
|
|
2218
2224
|
}
|
|
2219
2225
|
throw error;
|
|
2220
2226
|
}
|
|
@@ -2226,11 +2232,11 @@ export class PiSessionService {
|
|
|
2226
2232
|
const sessionFile = session.sessionFile;
|
|
2227
2233
|
if (sessionFile === undefined || sessionFile === "")
|
|
2228
2234
|
throw new Error("会话尚未持久化");
|
|
2229
|
-
|
|
2235
|
+
clearSessionFileParent(sessionFile);
|
|
2230
2236
|
this.sessionManager.invalidateSessionFile?.(sessionFile);
|
|
2231
2237
|
clearParentSessionHeader(session.sessionManager);
|
|
2232
2238
|
this.unregisterSubsession(session.sessionId);
|
|
2233
|
-
await this.forgetUnreadSessions([{ sessionId: session.sessionId, cwd: session.sessionManager.getCwd() }]);
|
|
2239
|
+
await this.forgetUnreadSessions([{ sessionId: session.sessionId, cwd: session.sessionManager.getCwd() }], this.eventScopeForSession(session));
|
|
2234
2240
|
}
|
|
2235
2241
|
async abort(ref, managementContext) {
|
|
2236
2242
|
const active = this.activeForLookup(ref, managementContext);
|
|
@@ -2278,7 +2284,7 @@ export class PiSessionService {
|
|
|
2278
2284
|
return;
|
|
2279
2285
|
}
|
|
2280
2286
|
if (isPiSessionRef(ref)) {
|
|
2281
|
-
this.publishNotificationMutations(this.
|
|
2287
|
+
this.publishNotificationMutations(this.notificationsForScope(eventScopeFromManagementContext(managementContext)).clearSessionIdentity(ref.id, canonicalizeStoredCwd(ref.cwd), "runtime-close"));
|
|
2282
2288
|
}
|
|
2283
2289
|
}
|
|
2284
2290
|
async bulkSessionLookupContext(refs) {
|
|
@@ -2455,17 +2461,17 @@ export class PiSessionService {
|
|
|
2455
2461
|
}
|
|
2456
2462
|
return [...names];
|
|
2457
2463
|
}
|
|
2458
|
-
async closeActive(sessionId, notificationPolicy = CLEAR_RUNTIME_NOTIFICATIONS) {
|
|
2464
|
+
async closeActive(sessionId, notificationPolicy = CLEAR_RUNTIME_NOTIFICATIONS, eventScope = NORMAL_SESSION_EVENT_SCOPE) {
|
|
2459
2465
|
for (const startup of this.startupSessions.values()) {
|
|
2460
|
-
if (startup.session.sessionId === sessionId)
|
|
2466
|
+
if (startup.session.sessionId === sessionId && startup.eventScope === eventScope)
|
|
2461
2467
|
this.endSessionExtensionDialogs(startup.session);
|
|
2462
2468
|
}
|
|
2463
2469
|
const pendingOpens = this.pendingSessionOpenPromises(sessionId);
|
|
2464
2470
|
if (pendingOpens.length > 0)
|
|
2465
2471
|
await Promise.allSettled(pendingOpens);
|
|
2466
|
-
const activeSessions = this.activeSessionsForId(sessionId);
|
|
2472
|
+
const activeSessions = this.activeSessionsForId(sessionId).filter((active) => active.eventScope === eventScope);
|
|
2467
2473
|
if (activeSessions.length === 0 && notificationPolicy.kind === "clear") {
|
|
2468
|
-
this.publishNotificationMutations(this.
|
|
2474
|
+
this.publishNotificationMutations(this.notificationsForScope(eventScope).clearSession(sessionId, notificationPolicy.reason));
|
|
2469
2475
|
return;
|
|
2470
2476
|
}
|
|
2471
2477
|
await Promise.all(activeSessions.map((active) => this.closeActiveSession(active, notificationPolicy)));
|
|
@@ -2476,16 +2482,17 @@ export class PiSessionService {
|
|
|
2476
2482
|
await this.closeActiveSession(active, notificationPolicy);
|
|
2477
2483
|
}
|
|
2478
2484
|
else if (notificationPolicy.kind === "clear") {
|
|
2479
|
-
this.publishNotificationMutations(this.
|
|
2485
|
+
this.publishNotificationMutations(this.notificationsForScope(eventScope).clearSession(sessionId, notificationPolicy.reason));
|
|
2480
2486
|
}
|
|
2481
2487
|
}
|
|
2482
2488
|
async closeActiveSession(active, notificationPolicy = CLEAR_RUNTIME_NOTIFICATIONS) {
|
|
2483
2489
|
const sessionId = active.runtime.session.sessionId;
|
|
2484
2490
|
if (notificationPolicy.kind === "clear") {
|
|
2485
2491
|
const generation = this.notificationGenerationBySession.get(active.runtime.session);
|
|
2492
|
+
const store = this.notificationsForScope(active.eventScope);
|
|
2486
2493
|
const mutations = generation === undefined
|
|
2487
|
-
?
|
|
2488
|
-
:
|
|
2494
|
+
? store.clearSession(sessionId, notificationPolicy.reason)
|
|
2495
|
+
: store.clearGeneration(generation, notificationPolicy.reason);
|
|
2489
2496
|
this.publishNotificationMutations(mutations);
|
|
2490
2497
|
}
|
|
2491
2498
|
this.forgetUnreadActivity(active.runtime.session);
|
|
@@ -2718,6 +2725,7 @@ export class PiSessionService {
|
|
|
2718
2725
|
}
|
|
2719
2726
|
}
|
|
2720
2727
|
bindRuntime(active, session = active.runtime.session) {
|
|
2728
|
+
this.sessionEventScopes.set(session, active.eventScope);
|
|
2721
2729
|
this.runtimeBySession.set(session, active.runtime);
|
|
2722
2730
|
active.unsubscribe();
|
|
2723
2731
|
for (const [sessionId, candidate] of this.active.entries()) {
|
|
@@ -3225,6 +3233,9 @@ export class PiSessionService {
|
|
|
3225
3233
|
return this.activities.get(activeSessionKey(session.sessionId, this.eventScopeForSession(session)));
|
|
3226
3234
|
}
|
|
3227
3235
|
eventScopeForSession(session) {
|
|
3236
|
+
const boundScope = this.sessionEventScopes.get(session);
|
|
3237
|
+
if (boundScope !== undefined)
|
|
3238
|
+
return boundScope;
|
|
3228
3239
|
for (const active of this.active.values()) {
|
|
3229
3240
|
if (active.runtime.session === session)
|
|
3230
3241
|
return active.eventScope;
|
|
@@ -3299,41 +3310,60 @@ export class PiSessionService {
|
|
|
3299
3310
|
hasQueuedMessageText(session, text) {
|
|
3300
3311
|
return queuedMessagesFromSession(session, this.compactionQueuedMessages(session.sessionId)).some((message) => message.text === text);
|
|
3301
3312
|
}
|
|
3302
|
-
|
|
3303
|
-
|
|
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));
|
|
3304
3325
|
}
|
|
3305
|
-
|
|
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) {
|
|
3306
3340
|
await this.publishUnreadMutations([]);
|
|
3307
|
-
return this.
|
|
3341
|
+
return this.unreadForScope(eventScopeFromManagementContext(context)).durableCatalogSnapshot();
|
|
3308
3342
|
}
|
|
3309
|
-
/**
|
|
3343
|
+
/** The status tree and HTTP catalog must read the same scoped store. */
|
|
3310
3344
|
async unreadCatalogForScope(eventScope) {
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
return { ...catalog, sessions: catalog.sessions.filter((session) => visible.has(session.sessionId)) };
|
|
3318
|
-
}
|
|
3319
|
-
async acknowledgeUnread(sessionId, request) {
|
|
3320
|
-
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, {
|
|
3321
3351
|
...request,
|
|
3322
3352
|
cwd: canonicalizeStoredCwd(request.cwd),
|
|
3323
3353
|
});
|
|
3324
3354
|
await this.publishUnreadMutations(result.mutations);
|
|
3325
|
-
return
|
|
3355
|
+
return store.durableCatalogSnapshot();
|
|
3326
3356
|
}
|
|
3327
|
-
notificationInbox(ref) {
|
|
3328
|
-
return this.
|
|
3357
|
+
notificationInbox(ref, context) {
|
|
3358
|
+
return this.notificationsForScope(eventScopeFromManagementContext(context)).inboxSnapshot(ref.id, canonicalizeStoredCwd(ref.cwd));
|
|
3329
3359
|
}
|
|
3330
|
-
dismissNotification(ref, request) {
|
|
3331
|
-
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);
|
|
3332
3362
|
this.publishNotificationMutations(result.mutations);
|
|
3333
3363
|
return result.snapshot;
|
|
3334
3364
|
}
|
|
3335
|
-
dismissAllNotifications(ref, request) {
|
|
3336
|
-
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);
|
|
3337
3367
|
this.publishNotificationMutations(result.mutations);
|
|
3338
3368
|
return result.snapshot;
|
|
3339
3369
|
}
|
|
@@ -3354,20 +3384,20 @@ export class PiSessionService {
|
|
|
3354
3384
|
try {
|
|
3355
3385
|
await session.reload(priorGeneration === undefined ? undefined : {
|
|
3356
3386
|
beforeSessionStart: () => {
|
|
3357
|
-
candidateGeneration = this.
|
|
3387
|
+
candidateGeneration = this.notificationsForSession(session).beginReplacement(priorGeneration, notificationIdentityForSession(session));
|
|
3358
3388
|
this.notificationGenerationBySession.set(session, candidateGeneration);
|
|
3359
3389
|
this.replaceSessionNotificationContext(session, candidateGeneration);
|
|
3360
3390
|
},
|
|
3361
3391
|
});
|
|
3362
3392
|
if (candidateGeneration !== undefined) {
|
|
3363
|
-
this.publishNotificationMutations(this.
|
|
3393
|
+
this.publishNotificationMutations(this.notificationsForSession(session).commitReplacement(candidateGeneration));
|
|
3364
3394
|
}
|
|
3365
3395
|
this.publishActivity(session, "资源已重新加载", "idle");
|
|
3366
3396
|
this.publishStatus(session);
|
|
3367
3397
|
}
|
|
3368
3398
|
catch (error) {
|
|
3369
3399
|
if (candidateGeneration !== undefined) {
|
|
3370
|
-
this.publishNotificationMutations(this.
|
|
3400
|
+
this.publishNotificationMutations(this.notificationsForSession(session).abortReplacement(candidateGeneration, "candidate"));
|
|
3371
3401
|
this.notificationGenerationBySession.set(session, candidateGeneration);
|
|
3372
3402
|
}
|
|
3373
3403
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -3429,6 +3459,8 @@ export class PiSessionService {
|
|
|
3429
3459
|
}),
|
|
3430
3460
|
};
|
|
3431
3461
|
let boundSession = runtime.session;
|
|
3462
|
+
this.sessionEventScopes.set(boundSession, active.eventScope);
|
|
3463
|
+
const notificationStore = this.notificationsForScope(active.eventScope);
|
|
3432
3464
|
let notificationGeneration = options.notificationGeneration;
|
|
3433
3465
|
let notificationOwnership = options.notifications === "disabled"
|
|
3434
3466
|
? "disabled"
|
|
@@ -3437,13 +3469,13 @@ export class PiSessionService {
|
|
|
3437
3469
|
: "external";
|
|
3438
3470
|
if (notificationOwnership === "registered") {
|
|
3439
3471
|
const notificationIdentity = notificationIdentityForSession(runtime.session);
|
|
3440
|
-
const existingCandidate =
|
|
3472
|
+
const existingCandidate = notificationStore.beginReplacementForSession(notificationIdentity.sessionId, notificationIdentity.cwd);
|
|
3441
3473
|
if (existingCandidate !== undefined) {
|
|
3442
3474
|
notificationGeneration = existingCandidate;
|
|
3443
3475
|
notificationOwnership = "replacement";
|
|
3444
3476
|
}
|
|
3445
3477
|
else {
|
|
3446
|
-
const registration =
|
|
3478
|
+
const registration = notificationStore.registerSession(notificationIdentity.sessionId, notificationIdentity.cwd);
|
|
3447
3479
|
notificationGeneration = registration.generation;
|
|
3448
3480
|
this.publishNotificationMutations(registration.mutations);
|
|
3449
3481
|
}
|
|
@@ -3452,7 +3484,7 @@ export class PiSessionService {
|
|
|
3452
3484
|
this.notificationGenerationBySession.set(runtime.session, notificationGeneration);
|
|
3453
3485
|
try {
|
|
3454
3486
|
if (options.creationProvenance === "tracked-subsession") {
|
|
3455
|
-
await this.publishUnreadMutations(this.
|
|
3487
|
+
await this.publishUnreadMutations(this.unreadForScope(active.eventScope).excludeSession(runtime.session.sessionId, canonicalizeStoredCwd(runtime.session.sessionManager.getCwd())));
|
|
3456
3488
|
}
|
|
3457
3489
|
else {
|
|
3458
3490
|
await this.recoverSubsessionTrackingForOpenedSession(runtime.session);
|
|
@@ -3461,13 +3493,14 @@ export class PiSessionService {
|
|
|
3461
3493
|
await this.bindSessionExtensions(runtime.session, notificationGeneration, active);
|
|
3462
3494
|
this.bindRuntime(active);
|
|
3463
3495
|
runtime.setRebindSession(async (session) => {
|
|
3496
|
+
this.sessionEventScopes.set(session, active.eventScope);
|
|
3464
3497
|
const priorGeneration = notificationGeneration;
|
|
3465
3498
|
let candidateGeneration;
|
|
3466
3499
|
try {
|
|
3467
3500
|
await this.prepareUnreadRuntimeRebind(boundSession, session);
|
|
3468
3501
|
await this.recoverSubsessionTrackingForOpenedSession(session);
|
|
3469
3502
|
if (priorGeneration !== undefined) {
|
|
3470
|
-
candidateGeneration =
|
|
3503
|
+
candidateGeneration = notificationStore.beginReplacement(priorGeneration, notificationIdentityForSession(session));
|
|
3471
3504
|
this.notificationGenerationBySession.set(session, candidateGeneration);
|
|
3472
3505
|
}
|
|
3473
3506
|
this.bindRuntime(active, session);
|
|
@@ -3478,13 +3511,13 @@ export class PiSessionService {
|
|
|
3478
3511
|
boundSession = session;
|
|
3479
3512
|
await this.bindSessionExtensions(session, candidateGeneration, active);
|
|
3480
3513
|
if (candidateGeneration !== undefined) {
|
|
3481
|
-
this.publishNotificationMutations(
|
|
3514
|
+
this.publishNotificationMutations(notificationStore.commitReplacement(candidateGeneration));
|
|
3482
3515
|
notificationGeneration = candidateGeneration;
|
|
3483
3516
|
}
|
|
3484
3517
|
}
|
|
3485
3518
|
catch (error) {
|
|
3486
3519
|
if (candidateGeneration !== undefined) {
|
|
3487
|
-
this.publishNotificationMutations(
|
|
3520
|
+
this.publishNotificationMutations(notificationStore.abortReplacement(candidateGeneration, "candidate"));
|
|
3488
3521
|
notificationGeneration = candidateGeneration;
|
|
3489
3522
|
this.notificationGenerationBySession.set(session, candidateGeneration);
|
|
3490
3523
|
}
|
|
@@ -3493,7 +3526,7 @@ export class PiSessionService {
|
|
|
3493
3526
|
});
|
|
3494
3527
|
this.active.set(activeSessionKey(runtime.session.sessionId, active.eventScope), active);
|
|
3495
3528
|
if (notificationOwnership === "replacement" && notificationGeneration !== undefined) {
|
|
3496
|
-
this.publishNotificationMutations(
|
|
3529
|
+
this.publishNotificationMutations(notificationStore.commitReplacement(notificationGeneration));
|
|
3497
3530
|
notificationOwnership = "external";
|
|
3498
3531
|
}
|
|
3499
3532
|
this.publishStatus(runtime.session);
|
|
@@ -3502,10 +3535,10 @@ export class PiSessionService {
|
|
|
3502
3535
|
catch (error) {
|
|
3503
3536
|
if (notificationGeneration !== undefined) {
|
|
3504
3537
|
if (notificationOwnership === "registered") {
|
|
3505
|
-
this.publishNotificationMutations(
|
|
3538
|
+
this.publishNotificationMutations(notificationStore.clearSession(runtime.session.sessionId, "initialization-failed"));
|
|
3506
3539
|
}
|
|
3507
3540
|
else if (notificationOwnership === "replacement") {
|
|
3508
|
-
this.publishNotificationMutations(
|
|
3541
|
+
this.publishNotificationMutations(notificationStore.abortReplacement(notificationGeneration));
|
|
3509
3542
|
}
|
|
3510
3543
|
}
|
|
3511
3544
|
active.unsubscribe();
|
|
@@ -3538,16 +3571,18 @@ export class PiSessionService {
|
|
|
3538
3571
|
}
|
|
3539
3572
|
sessionUiContext(session, generation) {
|
|
3540
3573
|
const baseUiContext = session.extensionRunner.getUIContext();
|
|
3574
|
+
const eventScope = this.eventScopeForSession(session);
|
|
3575
|
+
const notificationStore = this.notificationsForScope(eventScope);
|
|
3541
3576
|
const notify = (message, type) => {
|
|
3542
3577
|
if (generation === undefined) {
|
|
3543
3578
|
this.events.publish(session.sessionId, {
|
|
3544
3579
|
type: "command.output",
|
|
3545
3580
|
level: type === "error" ? "error" : "info",
|
|
3546
3581
|
message,
|
|
3547
|
-
});
|
|
3582
|
+
}, eventScope);
|
|
3548
3583
|
return;
|
|
3549
3584
|
}
|
|
3550
|
-
const added =
|
|
3585
|
+
const added = notificationStore.addNotification(generation, message, type);
|
|
3551
3586
|
this.publishNotificationMutations(added.mutations);
|
|
3552
3587
|
if (added.notification === undefined)
|
|
3553
3588
|
return;
|
|
@@ -3556,7 +3591,7 @@ export class PiSessionService {
|
|
|
3556
3591
|
level: type === "error" ? "error" : "info",
|
|
3557
3592
|
message,
|
|
3558
3593
|
notificationId: added.notification.id,
|
|
3559
|
-
});
|
|
3594
|
+
}, eventScope);
|
|
3560
3595
|
};
|
|
3561
3596
|
// PI WEB owns the browser-facing dialog, notification, and text-formatting
|
|
3562
3597
|
// boundaries: the three dialog primitives park daemon-held Promises that
|
|
@@ -3585,35 +3620,32 @@ export class PiSessionService {
|
|
|
3585
3620
|
}
|
|
3586
3621
|
publishNotificationMutations(mutations) {
|
|
3587
3622
|
for (const mutation of mutations) {
|
|
3588
|
-
const
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
for (const scope of scopes) {
|
|
3592
|
-
this.events.publish(mutation.sessionId, mutation.inboxEvent, scope);
|
|
3593
|
-
this.events.publishGlobal(mutation.summaryEvent, scope);
|
|
3594
|
-
}
|
|
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);
|
|
3595
3626
|
}
|
|
3596
3627
|
}
|
|
3597
3628
|
async prepareUnreadRuntimeRebind(previous, next) {
|
|
3598
3629
|
const previousCwd = canonicalizeStoredCwd(previous.sessionManager.getCwd());
|
|
3599
|
-
this.
|
|
3630
|
+
const store = this.unreadForScope(this.eventScopeForSession(previous));
|
|
3631
|
+
store.forgetActivity(previous.sessionId, previousCwd);
|
|
3600
3632
|
const nextCwd = canonicalizeStoredCwd(next.sessionManager.getCwd());
|
|
3601
3633
|
if (previous.sessionId === next.sessionId && cwdPathsEqual(previousCwd, nextCwd))
|
|
3602
3634
|
return;
|
|
3603
|
-
await this.publishUnreadMutations(
|
|
3635
|
+
await this.publishUnreadMutations(store.forgetSession(previous.sessionId, previousCwd));
|
|
3604
3636
|
}
|
|
3605
3637
|
forgetUnreadActivity(session) {
|
|
3606
|
-
this.
|
|
3638
|
+
this.unreadForScope(this.eventScopeForSession(session)).forgetActivity(session.sessionId, canonicalizeStoredCwd(session.sessionManager.getCwd()));
|
|
3607
3639
|
}
|
|
3608
|
-
async forgetUnreadSessions(identities) {
|
|
3640
|
+
async forgetUnreadSessions(identities, scope = NORMAL_SESSION_EVENT_SCOPE) {
|
|
3609
3641
|
const mutations = [];
|
|
3610
3642
|
for (const identity of identities) {
|
|
3611
|
-
mutations.push(...this.
|
|
3643
|
+
mutations.push(...this.unreadForScope(scope).forgetSession(identity.sessionId, canonicalizeStoredCwd(identity.cwd)));
|
|
3612
3644
|
}
|
|
3613
3645
|
await this.publishUnreadMutations(mutations);
|
|
3614
3646
|
}
|
|
3615
3647
|
observeUnreadActivityState(session) {
|
|
3616
|
-
const mutations = this.
|
|
3648
|
+
const mutations = this.unreadForScope(this.eventScopeForSession(session)).observeActivityState(session.sessionId, canonicalizeStoredCwd(session.sessionManager.getCwd()), this.hasActiveWork(session));
|
|
3617
3649
|
if (mutations.length === 0)
|
|
3618
3650
|
return;
|
|
3619
3651
|
void this.publishUnreadMutations(mutations).catch(() => undefined);
|
|
@@ -3622,11 +3654,7 @@ export class PiSessionService {
|
|
|
3622
3654
|
if (mutations.length > 0) {
|
|
3623
3655
|
const scopes = new Set();
|
|
3624
3656
|
for (const mutation of mutations) {
|
|
3625
|
-
|
|
3626
|
-
if (activeScopes.length === 0)
|
|
3627
|
-
scopes.add(NORMAL_SESSION_EVENT_SCOPE);
|
|
3628
|
-
else
|
|
3629
|
-
activeScopes.forEach((scope) => scopes.add(scope));
|
|
3657
|
+
scopes.add(mutation.eventScope ?? NORMAL_SESSION_EVENT_SCOPE);
|
|
3630
3658
|
}
|
|
3631
3659
|
scopes.forEach((scope) => this.onUnreadChanged?.(scope));
|
|
3632
3660
|
}
|
|
@@ -3665,8 +3693,10 @@ export class PiSessionService {
|
|
|
3665
3693
|
let publishedCount = 0;
|
|
3666
3694
|
try {
|
|
3667
3695
|
await this.unreadStore.flush();
|
|
3696
|
+
for (const store of this.scopedUnreadStores.values())
|
|
3697
|
+
await store.flush();
|
|
3668
3698
|
for (const mutation of batch) {
|
|
3669
|
-
this.events.publishGlobal(mutation.event);
|
|
3699
|
+
this.events.publishGlobal(mutation.event, mutation.eventScope ?? NORMAL_SESSION_EVENT_SCOPE);
|
|
3670
3700
|
publishedCount += 1;
|
|
3671
3701
|
}
|
|
3672
3702
|
}
|
|
@@ -4001,19 +4031,6 @@ async function sessionFileHeaderMatches(sessionFile, expected) {
|
|
|
4001
4031
|
return true;
|
|
4002
4032
|
return header.parentSession !== undefined && sessionPathsEqual(header.parentSession, expected.parentSessionFile);
|
|
4003
4033
|
}
|
|
4004
|
-
async function clearParentSession(sessionFile) {
|
|
4005
|
-
const content = await readFile(sessionFile, "utf8");
|
|
4006
|
-
const newlineIndex = content.indexOf("\n");
|
|
4007
|
-
const firstLine = newlineIndex === -1 ? content : content.slice(0, newlineIndex);
|
|
4008
|
-
const rest = newlineIndex === -1 ? "" : content.slice(newlineIndex);
|
|
4009
|
-
const header = JSON.parse(firstLine);
|
|
4010
|
-
if (!isRecord(header) || header["type"] !== "session")
|
|
4011
|
-
throw new Error("Invalid session file header");
|
|
4012
|
-
if (header["parentSession"] === undefined)
|
|
4013
|
-
return;
|
|
4014
|
-
delete header["parentSession"];
|
|
4015
|
-
await writeFile(sessionFile, `${JSON.stringify(header)}${rest}`, "utf8");
|
|
4016
|
-
}
|
|
4017
4034
|
function clearParentSessionHeader(sessionManager) {
|
|
4018
4035
|
const header = sessionManager.getHeader?.();
|
|
4019
4036
|
if (header !== undefined && header !== null)
|