@code-fixer-23/pi-session-manager 1.0.3 → 1.0.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/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## 1.0.5 (2026-07-08)
2
+
3
+ ### 🩹 Fixes
4
+
5
+ - prompt parsing and session-series continuation ([#6](https://github.com/louiss0/pi-packages/pull/6))
6
+
7
+ ### ❤️ Thank You
8
+
9
+ - louiss0 @louiss0
10
+
11
+ ## 1.0.4 (2026-07-04)
12
+
13
+ ### 🩹 Fixes
14
+
15
+ - **pi-session-manager:** do proper session restoration ([271d67d](https://github.com/louiss0/pi-packages/commit/271d67d))
16
+
17
+ ### ❤️ Thank You
18
+
19
+ - louiss0 @louiss0
20
+
1
21
  ## 1.0.3 (2026-07-03)
2
22
 
3
23
  ### 🩹 Fixes
@@ -11,6 +11,7 @@ import {
11
11
  handleSessionCleanOlderThan,
12
12
  handleSessionDeleteLast,
13
13
  handleSessionSeries,
14
+ getSessionEntryWithSeries,
14
15
  getSessionSeriesDataTempPath,
15
16
  persistSessionSeriesData,
16
17
  consumePersistedSessionSeriesData,
@@ -254,6 +255,7 @@ describe("persisted session series data", () => {
254
255
  entry: {
255
256
  customType: sessionSeriesEntrySchema.entries.customType.literal,
256
257
  series: "Implement Auth",
258
+ sessionTitle: "Create JWT Token",
257
259
  createdAt: new Date().toISOString(),
258
260
  },
259
261
  };
@@ -279,6 +281,7 @@ describe("persisted session series data", () => {
279
281
  expect(pi.setSessionName).toHaveBeenCalledWith(sessionData.sessionName);
280
282
  expect(pi.appendEntry).toHaveBeenCalledWith(sessionData.entry.customType, {
281
283
  series: sessionData.entry.series,
284
+ sessionTitle: sessionData.entry.sessionTitle,
282
285
  createdAt: sessionData.entry.createdAt,
283
286
  });
284
287
  expect(ctx.ui.notify).toHaveBeenCalledWith(
@@ -288,6 +291,82 @@ describe("persisted session series data", () => {
288
291
  });
289
292
  });
290
293
 
294
+ describe("getSessionEntryWithSeries", () => {
295
+ it("only returns the series entry that matches the current session name", () => {
296
+ const createdAt = new Date().toISOString();
297
+ const lspCoverageEntry = {
298
+ type: "custom",
299
+ customType: sessionSeriesEntrySchema.entries.customType.literal,
300
+ data: {
301
+ series: "LSP Coverage",
302
+ sessionTitle: "Add diagnostics",
303
+ createdAt,
304
+ },
305
+ } as SessionSeriesEntry;
306
+ const authEntry = {
307
+ type: "custom",
308
+ customType: sessionSeriesEntrySchema.entries.customType.literal,
309
+ data: {
310
+ series: "Auth Cleanup",
311
+ sessionTitle: "Fix token refresh",
312
+ createdAt,
313
+ },
314
+ } as SessionSeriesEntry;
315
+
316
+ expect(
317
+ getSessionEntryWithSeries(
318
+ [lspCoverageEntry, authEntry],
319
+ `Auth Cleanup${SESION_TITLE_SEPARATOR}Fix token refresh`,
320
+ ),
321
+ ).toBe(authEntry);
322
+ });
323
+
324
+ it("does not return another session's series entry", () => {
325
+ const createdAt = new Date().toISOString();
326
+ const lspCoverageEntry = {
327
+ type: "custom",
328
+ customType: sessionSeriesEntrySchema.entries.customType.literal,
329
+ data: {
330
+ series: "LSP Coverage",
331
+ sessionTitle: "Add diagnostics",
332
+ createdAt,
333
+ },
334
+ } as SessionSeriesEntry;
335
+
336
+ expect(
337
+ getSessionEntryWithSeries(
338
+ [lspCoverageEntry],
339
+ `Auth Cleanup${SESION_TITLE_SEPARATOR}Fix token refresh`,
340
+ ),
341
+ ).toBeUndefined();
342
+ });
343
+
344
+ it("falls back to the session name when legacy series entries are missing sessionTitle", () => {
345
+ const createdAt = new Date().toISOString();
346
+ const legacyEntry = {
347
+ type: "custom",
348
+ customType: sessionSeriesEntrySchema.entries.customType.literal,
349
+ data: {
350
+ series: "Auth Cleanup",
351
+ createdAt,
352
+ },
353
+ } as unknown as SessionSeriesEntry;
354
+
355
+ expect(
356
+ getSessionEntryWithSeries(
357
+ [legacyEntry],
358
+ `Auth Cleanup${SESION_TITLE_SEPARATOR}Fix token refresh`,
359
+ ),
360
+ ).toEqual({
361
+ ...legacyEntry,
362
+ data: {
363
+ ...legacyEntry.data,
364
+ sessionTitle: "Fix token refresh",
365
+ },
366
+ });
367
+ });
368
+ });
369
+
291
370
  describe("handleSessionCleanInactive", () => {
292
371
  test("gets rid of all sessions that haven't been modified in the last three days", ({
293
372
  sessions,
@@ -507,6 +586,40 @@ describe("handleSessionSeries", () => {
507
586
  );
508
587
  });
509
588
 
589
+ it("stops creating a session series when the series prompt is cancelled", async () => {
590
+ const context = {
591
+ cwd: "/pi-packages",
592
+ newSession: vi.fn<ExtensionCommandContext["newSession"]>(),
593
+ ui: {
594
+ notify: vi.fn<ExtensionUIContext["notify"]>(),
595
+ input: vi
596
+ .fn<ExtensionUIContext["input"]>()
597
+ .mockResolvedValue(undefined),
598
+ },
599
+ } satisfies MockExtenstionCommandContext;
600
+
601
+ await handleSessionSeries(
602
+ "create",
603
+ {
604
+ sessionManagerConfigurator: new SessionManagerConfiguratorMock(),
605
+ sessionFilter: new MockSessionFilter(
606
+ [],
607
+ new MockPastTimestampCalculator(),
608
+ ),
609
+ getSessionEntryWithSeries() {
610
+ return undefined;
611
+ },
612
+ removeSessionFiles() {
613
+ return;
614
+ },
615
+ },
616
+ castToExtensionContext(context),
617
+ );
618
+
619
+ expect(context.ui.input).toHaveBeenCalledTimes(1);
620
+ expect(context.newSession).not.toHaveBeenCalled();
621
+ });
622
+
510
623
  const seriesInput = "Implement Auth";
511
624
  it("keeps asking for a unique trimmed session series when creating", async () => {
512
625
  const sessionCtx = {
@@ -834,6 +947,50 @@ describe("handleSessionSeries", () => {
834
947
  );
835
948
  });
836
949
 
950
+ it("stops creating a new session in a series when the title prompt is cancelled", async () => {
951
+ const selectedSeries = "refactor-auth-middleware";
952
+ const context = {
953
+ cwd: "/user/work/cancel-new-title",
954
+ newSession: vi.fn<ExtensionCommandContext["newSession"]>(),
955
+ ui: {
956
+ notify: vi.fn<ExtensionUIContext["notify"]>(),
957
+ input: vi
958
+ .fn<ExtensionUIContext["input"]>()
959
+ .mockResolvedValue(undefined),
960
+ select: vi
961
+ .fn<ExtensionUIContext["select"]>()
962
+ .mockResolvedValue(selectedSeries),
963
+ },
964
+ } satisfies MockExtenstionCommandContext;
965
+
966
+ await handleSessionSeries(
967
+ "new",
968
+ {
969
+ sessionManagerConfigurator: new SessionManagerConfiguratorMock({
970
+ seriesRecord: {
971
+ [context.cwd]: {
972
+ [selectedSeries]: [],
973
+ },
974
+ },
975
+ }),
976
+ sessionFilter: new MockSessionFilter(
977
+ [],
978
+ new MockPastTimestampCalculator(),
979
+ ),
980
+ getSessionEntryWithSeries() {
981
+ return undefined;
982
+ },
983
+ removeSessionFiles() {
984
+ return;
985
+ },
986
+ },
987
+ castToExtensionContext(context),
988
+ );
989
+
990
+ expect(context.ui.input).toHaveBeenCalledTimes(1);
991
+ expect(context.newSession).not.toHaveBeenCalled();
992
+ });
993
+
837
994
  it("keeps asking for a unique trimmed title when creating a new session in a series", async () => {
838
995
  const selectedSeries = "refactor-auth-middleware";
839
996
 
@@ -922,6 +1079,55 @@ describe("handleSessionSeries", () => {
922
1079
  );
923
1080
  });
924
1081
 
1082
+ it("stops continuing a session in a series when the title prompt is cancelled", async () => {
1083
+ const context = {
1084
+ cwd: "/user/work/cancel-continue-title",
1085
+ sessionManager: {
1086
+ getEntries:
1087
+ vi.fn<ExtensionCommandContext["sessionManager"]["getEntries"]>(),
1088
+ getSessionName: vi
1089
+ .fn<ExtensionCommandContext["sessionManager"]["getSessionName"]>()
1090
+ .mockReturnValue("refactor-auth-middleware--current-task"),
1091
+ },
1092
+ newSession: vi.fn<ExtensionCommandContext["newSession"]>(),
1093
+ ui: {
1094
+ notify: vi.fn<ExtensionUIContext["notify"]>(),
1095
+ input: vi
1096
+ .fn<ExtensionUIContext["input"]>()
1097
+ .mockResolvedValue(undefined),
1098
+ },
1099
+ } satisfies MockExtenstionCommandContext;
1100
+
1101
+ await handleSessionSeries(
1102
+ "continue",
1103
+ {
1104
+ sessionFilter: new MockSessionFilter(
1105
+ [],
1106
+ new MockPastTimestampCalculator(),
1107
+ ),
1108
+ getSessionEntryWithSeries() {
1109
+ return {
1110
+ type: "custom",
1111
+ customType: sessionSeriesEntrySchema.entries.customType.literal,
1112
+ data: {
1113
+ series: "refactor-auth-middleware",
1114
+ sessionTitle: "current-task",
1115
+ createdAt: new Date().toISOString(),
1116
+ },
1117
+ } as SessionSeriesEntry;
1118
+ },
1119
+ sessionManagerConfigurator: new SessionManagerConfiguratorMock(),
1120
+ removeSessionFiles() {
1121
+ return;
1122
+ },
1123
+ },
1124
+ castToExtensionContext(context),
1125
+ );
1126
+
1127
+ expect(context.ui.input).toHaveBeenCalledTimes(1);
1128
+ expect(context.newSession).not.toHaveBeenCalled();
1129
+ });
1130
+
925
1131
  it("continues a session in a series when continue is passed", async () => {
926
1132
  const sessionCtx = {
927
1133
  cwd: "/session/continue",
@@ -935,6 +1141,9 @@ describe("handleSessionSeries", () => {
935
1141
  sessionManager: {
936
1142
  getEntries:
937
1143
  vi.fn<ExtensionCommandContext["sessionManager"]["getEntries"]>(),
1144
+ getSessionName: vi
1145
+ .fn<ExtensionCommandContext["sessionManager"]["getSessionName"]>()
1146
+ .mockReturnValue("refactor-auth-middleware--current-task"),
938
1147
  },
939
1148
  newSession: vi.fn<ExtensionCommandContext["newSession"]>(
940
1149
  async (options) => {
@@ -957,6 +1166,7 @@ describe("handleSessionSeries", () => {
957
1166
  customType: sessionSeriesEntrySchema.entries.customType.literal,
958
1167
  data: {
959
1168
  series: "refactor-auth-middleware",
1169
+ sessionTitle: "current-task",
960
1170
  createdAt: new Date().toISOString(),
961
1171
  },
962
1172
  } as SessionSeriesEntry;
@@ -1010,6 +1220,7 @@ describe("handleSessionSeries", () => {
1010
1220
 
1011
1221
  expect(getSessionEntryWithSeries).toHaveBeenCalledWith(
1012
1222
  context.sessionManager.getEntries.mock.results[0]?.value,
1223
+ context.sessionManager.getSessionName(),
1013
1224
  );
1014
1225
 
1015
1226
  const entry = getSessionEntryWithSeries.mock.results[0]
@@ -45,14 +45,17 @@ export default function (pi: ExtensionAPI) {
45
45
 
46
46
  pi.on("session_start", async (event, ctx) => {
47
47
  sessionManagerConfigurator = new SessionManagerConfigurator();
48
+
49
+ if (event.reason !== "fork" && event.reason !== "reload") {
50
+ applyPersistedSessionSeriesData(pi, ctx);
51
+ }
52
+
48
53
  const eventIsNotReloadOrStartUp =
49
54
  event.reason !== "reload" && event.reason !== "startup";
50
55
  if (eventIsNotReloadOrStartUp) {
51
56
  return;
52
57
  }
53
58
 
54
- applyPersistedSessionSeriesData(pi, ctx);
55
-
56
59
  const dayLimitResult =
57
60
  sessionManagerConfigurator.getSessionDeletionDayLimit();
58
61
 
@@ -512,14 +515,56 @@ class SessionManagerConfigurator implements $SessionManagerConfigurator {
512
515
  }
513
516
  }
514
517
 
515
- function getSessionEntryWithSeries(
516
- sesssionEntries: SessionEntry[],
518
+ export function getSessionEntryWithSeries(
519
+ sessionEntries: SessionEntry[],
520
+ sessionName?: string,
517
521
  ): SessionSeriesEntry | undefined {
518
- return sesssionEntries.find(
522
+ const matchingEntries = sessionEntries.filter(
519
523
  (entry): entry is SessionSeriesEntry =>
520
524
  entry.type === sessionSeriesEntrySchema.entries.type.literal &&
521
525
  entry.customType === sessionSeriesEntrySchema.entries.customType.literal,
522
526
  );
527
+
528
+ if (!sessionName) {
529
+ return;
530
+ }
531
+
532
+ const normalizedSessionName = sessionName.trim();
533
+ const matchedEntry = matchingEntries.find((entry) => {
534
+ const sessionTitle =
535
+ entry.data.sessionTitle ??
536
+ getSessionTitleFromSessionName(normalizedSessionName, entry.data.series);
537
+
538
+ if (!sessionTitle) {
539
+ return false;
540
+ }
541
+
542
+ return (
543
+ `${entry.data.series}${SESION_TITLE_SEPARATOR}${sessionTitle}` ===
544
+ normalizedSessionName
545
+ );
546
+ });
547
+
548
+ if (!matchedEntry || matchedEntry.data.sessionTitle) {
549
+ return matchedEntry;
550
+ }
551
+
552
+ const sessionTitle = getSessionTitleFromSessionName(
553
+ normalizedSessionName,
554
+ matchedEntry.data.series,
555
+ );
556
+
557
+ if (!sessionTitle) {
558
+ return matchedEntry;
559
+ }
560
+
561
+ return {
562
+ ...matchedEntry,
563
+ data: {
564
+ ...matchedEntry.data,
565
+ sessionTitle,
566
+ },
567
+ } as SessionSeriesEntry;
523
568
  }
524
569
 
525
570
  export type GetSessionEntryWithSeries = typeof getSessionEntryWithSeries;
@@ -617,6 +662,17 @@ export function handleSessionDeleteLast(
617
662
  }
618
663
 
619
664
  export const SESION_TITLE_SEPARATOR = "--";
665
+
666
+ function getSessionTitleFromSessionName(sessionName: string, series: string) {
667
+ const prefix = `${series.trim()}${SESION_TITLE_SEPARATOR}`;
668
+
669
+ if (!sessionName.startsWith(prefix)) {
670
+ return;
671
+ }
672
+
673
+ return sessionName.slice(prefix.length).trim();
674
+ }
675
+
620
676
  export const sessionSeriesCommandsSchema = picklist([
621
677
  "create",
622
678
  "delete",
@@ -628,6 +684,7 @@ export const sessionSeriesEntrySchema = object({
628
684
  customType: literal("session-manager/series"),
629
685
  data: object({
630
686
  series: string(),
687
+ sessionTitle: string(),
631
688
  createdAt: pipe(string(), isoTimestamp()),
632
689
  }),
633
690
  });
@@ -640,6 +697,7 @@ export const sessionSeriesDataSchema = object({
640
697
  entry: object({
641
698
  customType: literal("session-manager/series"),
642
699
  series: string(),
700
+ sessionTitle: string(),
643
701
  createdAt: pipe(string(), isoTimestamp()),
644
702
  }),
645
703
  });
@@ -718,6 +776,10 @@ function promptForUniqueTrimmedInput(
718
776
  const value = await ctx.ui.input(prompt, description);
719
777
  const trimmedValue = value?.trim();
720
778
 
779
+ if (value === undefined) {
780
+ return;
781
+ }
782
+
721
783
  if (!trimmedValue) {
722
784
  continue;
723
785
  }
@@ -777,6 +839,10 @@ export async function handleSessionSeries(
777
839
  (value) => `This series has already been added ${value}`,
778
840
  );
779
841
 
842
+ if (!series) {
843
+ return;
844
+ }
845
+
780
846
  const titlesResult =
781
847
  deps.sessionManagerConfigurator.getSessionTitlesForSeriesBasedOnCwd(
782
848
  ctx.cwd,
@@ -796,11 +862,17 @@ export async function handleSessionSeries(
796
862
  (value) => `This title has already been added ${value}`,
797
863
  );
798
864
 
865
+ if (!title) {
866
+ return;
867
+ }
868
+
869
+ const sessionName = `${series}${SESION_TITLE_SEPARATOR}${title}`;
799
870
  const sessionData = {
800
- sessionName: `${series}${SESION_TITLE_SEPARATOR}${title}`,
871
+ sessionName,
801
872
  entry: {
802
873
  customType: sessionSeriesEntrySchema.entries.customType.literal,
803
874
  series,
875
+ sessionTitle: title,
804
876
  createdAt: new Date().toISOString(),
805
877
  },
806
878
  };
@@ -890,11 +962,17 @@ export async function handleSessionSeries(
890
962
  (value) => `This title has already been added ${value}`,
891
963
  );
892
964
 
965
+ if (!title) {
966
+ return;
967
+ }
968
+
969
+ const sessionName = `${series}${SESION_TITLE_SEPARATOR}${title}`;
893
970
  const sessionData = {
894
- sessionName: `${series}${SESION_TITLE_SEPARATOR}${title}`,
971
+ sessionName,
895
972
  entry: {
896
973
  customType: sessionSeriesEntrySchema.entries.customType.literal,
897
974
  series,
975
+ sessionTitle: title,
898
976
  createdAt: new Date().toISOString(),
899
977
  },
900
978
  };
@@ -920,7 +998,10 @@ export async function handleSessionSeries(
920
998
 
921
999
  case "continue": {
922
1000
  const entries = ctx.sessionManager.getEntries();
923
- const entry = deps.getSessionEntryWithSeries(entries);
1001
+ const entry = deps.getSessionEntryWithSeries(
1002
+ entries,
1003
+ ctx.sessionManager.getSessionName(),
1004
+ );
924
1005
 
925
1006
  if (!entry) {
926
1007
  ctx.ui.notify("No session series was found", "error");
@@ -942,11 +1023,17 @@ export async function handleSessionSeries(
942
1023
  (value) => `This title has already been added ${value}`,
943
1024
  );
944
1025
 
1026
+ if (!title) {
1027
+ return;
1028
+ }
1029
+
1030
+ const sessionName = `${entry.data.series}${SESION_TITLE_SEPARATOR}${title}`;
945
1031
  const sessionData = {
946
- sessionName: `${entry.data.series}${SESION_TITLE_SEPARATOR}${title}`,
1032
+ sessionName,
947
1033
  entry: {
948
1034
  customType: sessionSeriesEntrySchema.entries.customType.literal,
949
1035
  series: entry.data.series,
1036
+ sessionTitle: title,
950
1037
  createdAt: new Date().toISOString(),
951
1038
  },
952
1039
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@code-fixer-23/pi-session-manager",
3
3
  "type": "module",
4
- "version": "1.0.3",
4
+ "version": "1.0.5",
5
5
  "keywords": [
6
6
  "pi-package"
7
7
  ],