@chainingintention/pi-web-cn 1.202606.26 → 1.202607.0

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.
@@ -53,7 +53,7 @@
53
53
  html, body { margin: 0; height: 100%; overflow: hidden; background: var(--pi-bg); color: var(--pi-text); }
54
54
  body { min-height: 100dvh; }
55
55
  </style>
56
- <script type="module" crossorigin src="/assets/index-iRSp25EB.js"></script>
56
+ <script type="module" crossorigin src="/assets/index-DFaXWsru.js"></script>
57
57
  <link rel="modulepreload" crossorigin href="/assets/vendor-editor-core-C7aWiQy0.js">
58
58
  <link rel="modulepreload" crossorigin href="/assets/vendor-editor-languages-EW34FfVy.js">
59
59
  </head>
@@ -260,10 +260,12 @@ export class PiSessionService {
260
260
  }
261
261
  async cleanup(request) {
262
262
  const plan = await this.cleanupPlan(request);
263
- if (plan.deleteRecords.length > 0 && this.archiveStore.deleteArchived === undefined)
263
+ if (plan.deleteRecords.length > 0 && this.archiveStore.deleteArchived === undefined && this.archiveStore.deleteArchivedMany === undefined)
264
264
  throw new Error("Archive store does not support deletion");
265
265
  const archiveInputs = [];
266
+ const readyArchiveInputs = [];
266
267
  const deleteRecords = [];
268
+ const readyDeleteRecords = [];
267
269
  const skippedBusySessionIds = new Set(plan.skippedBusySessionIds);
268
270
  for (const input of plan.archiveInputs) {
269
271
  if (this.activeSessionHasWork(input.sessionId)) {
@@ -271,20 +273,21 @@ export class PiSessionService {
271
273
  continue;
272
274
  }
273
275
  await this.closeActive(input.sessionId);
274
- await this.archiveStore.archive(input);
275
- archiveInputs.push(input);
276
+ readyArchiveInputs.push(input);
276
277
  }
278
+ await this.archiveStoreArchiveMany(readyArchiveInputs);
279
+ archiveInputs.push(...readyArchiveInputs);
277
280
  for (const record of plan.deleteRecords) {
278
281
  if (this.activeSessionHasWork(record.sessionId)) {
279
282
  skippedBusySessionIds.add(record.sessionId);
280
283
  continue;
281
284
  }
282
285
  await this.closeActive(record.sessionId);
283
- if (record.archivePath === undefined)
284
- await this.ensureArchivedRecordMoved(record);
285
- await this.archiveStore.deleteArchived?.(record.sessionId);
286
- deleteRecords.push(record);
286
+ readyDeleteRecords.push(record);
287
287
  }
288
+ await this.ensureArchivedRecordsMoved(readyDeleteRecords);
289
+ const deletedSessionIds = new Set(await this.archiveStoreDeleteArchivedMany(readyDeleteRecords.map((record) => record.sessionId)));
290
+ deleteRecords.push(...readyDeleteRecords.filter((record) => deletedSessionIds.has(record.sessionId)));
288
291
  return summarizeSessionCleanupExecution({
289
292
  archiveInputs,
290
293
  deleteRecords,
@@ -920,6 +923,69 @@ export class PiSessionService {
920
923
  await this.closeActive(session.sessionId);
921
924
  await this.archiveStore.archive(archiveInput);
922
925
  }
926
+ async archiveMany(refs) {
927
+ const uniqueRefs = uniqueBulkSessionRefs(refs);
928
+ const [archivedRecords, sessionContext] = await Promise.all([
929
+ this.archiveStore.list(),
930
+ this.bulkSessionLookupContext(uniqueRefs),
931
+ ]);
932
+ const failures = [];
933
+ const alreadyArchivedSessionIds = [];
934
+ const planItems = [];
935
+ for (const ref of uniqueRefs) {
936
+ const archived = findArchivedRecordForBulkRef(archivedRecords, ref);
937
+ if (archived !== undefined) {
938
+ alreadyArchivedSessionIds.push(archived.sessionId);
939
+ continue;
940
+ }
941
+ const active = this.activeForLookup(bulkRefToLookup(ref));
942
+ const listed = findListedSessionForBulkRef(sessionContext, ref);
943
+ const resolvedSessionId = active?.runtime.session.sessionId ?? listed?.id ?? ref.id;
944
+ if (active !== undefined && this.hasActiveWork(active.runtime.session)) {
945
+ failures.push({ sessionId: resolvedSessionId, error: "Stop current session activity before archiving" });
946
+ continue;
947
+ }
948
+ try {
949
+ if (listed !== undefined) {
950
+ planItems.push({ input: archiveInputFromListEntry(listed) });
951
+ }
952
+ else if (active !== undefined) {
953
+ planItems.push({ input: archiveInputFromActiveSession(active.runtime.session) });
954
+ }
955
+ else {
956
+ failures.push({ sessionId: ref.id, error: "Session not found" });
957
+ }
958
+ }
959
+ catch (error) {
960
+ failures.push({ sessionId: resolvedSessionId, error: errorMessage(error) });
961
+ }
962
+ }
963
+ const readyInputs = [];
964
+ for (const item of planItems) {
965
+ try {
966
+ await this.closeActive(item.input.sessionId);
967
+ readyInputs.push(item.input);
968
+ }
969
+ catch (error) {
970
+ failures.push({ sessionId: item.input.sessionId, error: errorMessage(error) });
971
+ }
972
+ }
973
+ const archivedSessionIds = [...alreadyArchivedSessionIds];
974
+ try {
975
+ const archived = await this.archiveStoreArchiveMany(readyInputs);
976
+ archivedSessionIds.push(...archived.map((record) => record.sessionId));
977
+ }
978
+ catch (error) {
979
+ for (const input of readyInputs)
980
+ failures.push({ sessionId: input.sessionId, error: errorMessage(error) });
981
+ }
982
+ return {
983
+ archived: true,
984
+ archivedSessionIds: uniqueStrings(archivedSessionIds),
985
+ failures,
986
+ generatedAt: new Date().toISOString(),
987
+ };
988
+ }
923
989
  async archiveTree(ref) {
924
990
  const session = await this.getOrOpen(ref);
925
991
  const catalog = await this.workspaceArchiveCandidates(session.sessionManager.getCwd());
@@ -931,8 +997,7 @@ export class PiSessionService {
931
997
  const archiveInputs = plan.unarchivedTargets.map((target) => archiveInputFromCandidate(target));
932
998
  for (const input of archiveInputs)
933
999
  await this.closeActive(input.sessionId);
934
- for (const input of archiveInputs)
935
- await this.archiveStore.archive(input);
1000
+ await this.archiveStoreArchiveMany(archiveInputs);
936
1001
  return {
937
1002
  archived: true,
938
1003
  sessionIds: archiveInputs.map((input) => input.sessionId),
@@ -958,6 +1023,57 @@ export class PiSessionService {
958
1023
  await this.ensureArchivedRecordMoved(record);
959
1024
  await this.archiveStore.deleteArchived(record.sessionId);
960
1025
  }
1026
+ async deleteArchivedMany(refs) {
1027
+ if (this.archiveStore.deleteArchived === undefined && this.archiveStore.deleteArchivedMany === undefined)
1028
+ throw new Error("Archive store does not support deletion");
1029
+ const uniqueRefs = uniqueBulkSessionRefs(refs);
1030
+ const archivedRecords = await this.archiveStore.list();
1031
+ const failures = [];
1032
+ const planItems = [];
1033
+ for (const ref of uniqueRefs) {
1034
+ const record = findArchivedRecordForBulkRef(archivedRecords, ref);
1035
+ if (record === undefined) {
1036
+ failures.push({ sessionId: ref.id, error: "Archived session not found" });
1037
+ continue;
1038
+ }
1039
+ const active = this.activeForLookup({ id: record.sessionId, cwd: record.cwd });
1040
+ if (active !== undefined && this.hasActiveWork(active.runtime.session)) {
1041
+ failures.push({ sessionId: record.sessionId, error: "Stop current session activity before deleting archived session" });
1042
+ continue;
1043
+ }
1044
+ planItems.push({ record });
1045
+ }
1046
+ const readyRecords = [];
1047
+ for (const item of planItems) {
1048
+ try {
1049
+ await this.closeActive(item.record.sessionId);
1050
+ readyRecords.push(item.record);
1051
+ }
1052
+ catch (error) {
1053
+ failures.push({ sessionId: item.record.sessionId, error: errorMessage(error) });
1054
+ }
1055
+ }
1056
+ const moveFailures = await this.moveLegacyArchivedRecordsForDelete(readyRecords);
1057
+ failures.push(...moveFailures);
1058
+ const moveFailureIds = new Set(moveFailures.map((failure) => failure.sessionId));
1059
+ const deleteIds = readyRecords
1060
+ .map((record) => record.sessionId)
1061
+ .filter((sessionId) => !moveFailureIds.has(sessionId));
1062
+ let deletedSessionIds = [];
1063
+ try {
1064
+ deletedSessionIds = await this.archiveStoreDeleteArchivedMany(deleteIds);
1065
+ }
1066
+ catch (error) {
1067
+ for (const sessionId of deleteIds)
1068
+ failures.push({ sessionId, error: errorMessage(error) });
1069
+ }
1070
+ return {
1071
+ deleted: true,
1072
+ deletedSessionIds,
1073
+ failures,
1074
+ generatedAt: new Date().toISOString(),
1075
+ };
1076
+ }
961
1077
  async reload(ref) {
962
1078
  await this.assertWritable(ref);
963
1079
  const session = await this.getOrOpen(ref);
@@ -995,6 +1111,75 @@ export class PiSessionService {
995
1111
  // Best-effort shutdown; callers that need errors await closeActive directly.
996
1112
  });
997
1113
  }
1114
+ async bulkSessionLookupContext(refs) {
1115
+ const cwdSet = new Set();
1116
+ let needsAllSessions = false;
1117
+ for (const ref of refs) {
1118
+ if (ref.cwd === undefined)
1119
+ needsAllSessions = true;
1120
+ else
1121
+ cwdSet.add(ref.cwd);
1122
+ }
1123
+ const [sessionsByCwd, allSessions] = await Promise.all([
1124
+ this.listSessionsByCwd([...cwdSet]),
1125
+ needsAllSessions ? this.sessionManager.listAll?.() ?? Promise.resolve([]) : Promise.resolve(undefined),
1126
+ ]);
1127
+ return allSessions === undefined ? { sessionsByCwd } : { sessionsByCwd, allSessions };
1128
+ }
1129
+ async listSessionsByCwd(cwds) {
1130
+ const uniqueCwds = uniqueStrings(cwds);
1131
+ const entries = await Promise.all(uniqueCwds.map(async (cwd) => [cwd, await this.sessionManager.list(cwd)]));
1132
+ return new Map(entries);
1133
+ }
1134
+ async archiveStoreArchiveMany(inputs) {
1135
+ if (inputs.length === 0)
1136
+ return [];
1137
+ if (this.archiveStore.archiveMany !== undefined)
1138
+ return this.archiveStore.archiveMany(inputs);
1139
+ const records = [];
1140
+ for (const input of inputs)
1141
+ records.push(await this.archiveStore.archive(input));
1142
+ return records;
1143
+ }
1144
+ async archiveStoreDeleteArchivedMany(sessionIds) {
1145
+ if (sessionIds.length === 0)
1146
+ return [];
1147
+ if (this.archiveStore.deleteArchivedMany !== undefined)
1148
+ return this.archiveStore.deleteArchivedMany(sessionIds);
1149
+ if (this.archiveStore.deleteArchived === undefined)
1150
+ throw new Error("Archive store does not support deletion");
1151
+ for (const sessionId of sessionIds)
1152
+ await this.archiveStore.deleteArchived(sessionId);
1153
+ return [...sessionIds];
1154
+ }
1155
+ async moveLegacyArchivedRecordsForDelete(records) {
1156
+ const legacyRecords = records.filter((record) => record.archivePath === undefined);
1157
+ if (legacyRecords.length === 0)
1158
+ return [];
1159
+ let sessionsByCwd;
1160
+ try {
1161
+ sessionsByCwd = await this.listSessionsByCwd(legacyRecords.map((record) => record.cwd));
1162
+ }
1163
+ catch (error) {
1164
+ return legacyRecords.map((record) => ({ sessionId: record.sessionId, error: errorMessage(error) }));
1165
+ }
1166
+ const moveInputs = legacyRecords
1167
+ .map((record) => findSessionByIdOrPrefix(sessionsByCwd.get(record.cwd) ?? [], record.sessionId))
1168
+ .filter(isDefined)
1169
+ .map(archiveInputFromListEntry);
1170
+ if (moveInputs.length === 0)
1171
+ return [];
1172
+ try {
1173
+ await this.archiveStoreArchiveMany(moveInputs);
1174
+ return [];
1175
+ }
1176
+ catch (error) {
1177
+ const failedIds = new Set(moveInputs.map((input) => input.sessionId));
1178
+ return legacyRecords
1179
+ .filter((record) => failedIds.has(record.sessionId))
1180
+ .map((record) => ({ sessionId: record.sessionId, error: errorMessage(error) }));
1181
+ }
1182
+ }
998
1183
  async cleanupPlan(request) {
999
1184
  const [sessions, archivedRecords] = await Promise.all([this.sessionManager.listAll?.() ?? [], this.archiveStore.list()]);
1000
1185
  return planSessionCleanup({
@@ -1039,7 +1224,19 @@ export class PiSessionService {
1039
1224
  const session = (await this.sessionManager.list(record.cwd)).find((candidate) => candidate.id === record.sessionId);
1040
1225
  if (session === undefined)
1041
1226
  return record;
1042
- return this.archiveStore.archive(archiveInputFromListEntry(session));
1227
+ const [moved] = await this.archiveStoreArchiveMany([archiveInputFromListEntry(session)]);
1228
+ return moved ?? record;
1229
+ }
1230
+ async ensureArchivedRecordsMoved(records) {
1231
+ const legacyRecords = records.filter((record) => record.archivePath === undefined);
1232
+ if (legacyRecords.length === 0)
1233
+ return;
1234
+ const sessionsByCwd = await this.listSessionsByCwd(legacyRecords.map((record) => record.cwd));
1235
+ const moveInputs = legacyRecords
1236
+ .map((record) => sessionsByCwd.get(record.cwd)?.find((candidate) => candidate.id === record.sessionId))
1237
+ .filter(isDefined)
1238
+ .map(archiveInputFromListEntry);
1239
+ await this.archiveStoreArchiveMany(moveInputs);
1043
1240
  }
1044
1241
  async archiveInputForSession(session) {
1045
1242
  const cwd = session.sessionManager.getCwd();
@@ -1507,6 +1704,38 @@ function previewResponseFromPlan(plan) {
1507
1704
  ...(plan.skippedBusySessionIds.length === 0 ? {} : { skippedBusySessionIds: plan.skippedBusySessionIds }),
1508
1705
  };
1509
1706
  }
1707
+ function uniqueBulkSessionRefs(refs) {
1708
+ const seen = new Set();
1709
+ const unique = [];
1710
+ for (const ref of refs) {
1711
+ const key = `${ref.cwd ?? ""}\0${ref.id}`;
1712
+ if (seen.has(key))
1713
+ continue;
1714
+ seen.add(key);
1715
+ unique.push(ref);
1716
+ }
1717
+ return unique;
1718
+ }
1719
+ function bulkRefToLookup(ref) {
1720
+ return ref.cwd === undefined ? ref.id : { id: ref.id, cwd: ref.cwd };
1721
+ }
1722
+ function findArchivedRecordForBulkRef(records, ref) {
1723
+ return records.find((record) => (ref.cwd === undefined || record.cwd === ref.cwd) && (record.sessionId === ref.id || record.sessionId.startsWith(ref.id)));
1724
+ }
1725
+ function findListedSessionForBulkRef(context, ref) {
1726
+ if (ref.cwd !== undefined)
1727
+ return findSessionByIdOrPrefix(context.sessionsByCwd.get(ref.cwd) ?? [], ref.id);
1728
+ return context.allSessions === undefined ? undefined : findSessionByIdOrPrefix(context.allSessions, ref.id);
1729
+ }
1730
+ function findSessionByIdOrPrefix(sessions, sessionId) {
1731
+ return sessions.find((session) => session.id === sessionId) ?? sessions.find((session) => session.id.startsWith(sessionId));
1732
+ }
1733
+ function uniqueStrings(values) {
1734
+ return [...new Set(values)];
1735
+ }
1736
+ function errorMessage(error) {
1737
+ return error instanceof Error ? error.message : String(error);
1738
+ }
1510
1739
  function modelToClientModel(model) {
1511
1740
  if (model === undefined)
1512
1741
  return {};