@heyhuynhgiabuu/pi-pretty 0.3.2 → 0.3.3

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/README.md CHANGED
@@ -51,6 +51,16 @@ When running in **tmux**, pi-pretty uses passthrough escape sequences.
51
51
  >
52
52
  > (or run once in a session: `tmux set -g allow-passthrough on`)
53
53
 
54
+ ## FFF data directory
55
+
56
+ When FFF is available, pi-pretty now stores its frecency/history data under a pi-pretty-specific path:
57
+
58
+ ```text
59
+ ~/.pi/agent/pi-pretty/fff/
60
+ ```
61
+
62
+ This makes it clear that the cache belongs to this extension rather than Pi core.
63
+
54
64
  ## Configuration
55
65
 
56
66
  Optional environment variables:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyhuynhgiabuu/pi-pretty",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Pretty terminal output for pi — syntax-highlighted file reads, colored bash output, tree-view directory listings, and more.",
5
5
  "author": "huynhgiabuu",
6
6
  "license": "MIT",
@@ -0,0 +1,48 @@
1
+ # pi-pretty v0.3.3
2
+
3
+ ## Summary
4
+ This patch release combines the recent rendering and FFF-path fixes into a single `v0.3.3` release.
5
+
6
+ ## What changed
7
+ - Fixed themed tool background rendering so `toolSuccessBg` and `toolErrorBg` fill the full rendered width for core tool output.
8
+ - Preserved the active tool background across ANSI reset sequences to avoid broken background bands inside highlighted output.
9
+ - Kept error rendering on `theme.fg("error", ...)` while switching error panels to `toolErrorBg`.
10
+ - Moved pi-pretty FFF data from:
11
+ - `~/.pi/agent/fff`
12
+ - To:
13
+ - `~/.pi/agent/pi-pretty/fff`
14
+ - Added a helper to resolve the pi-pretty-specific FFF directory under `getAgentDir()`.
15
+ - Updated README to document the pi-pretty-owned FFF data location.
16
+ - Added a test that verifies the generated frecency/history DB paths use the new per-extension location.
17
+
18
+ ## Files
19
+ - `src/index.ts`
20
+ - `test/fff-integration.test.ts`
21
+ - `README.md`
22
+ - `package.json`
23
+ - `package-lock.json`
24
+
25
+ ## Verification
26
+ - `npm run typecheck` ✅
27
+ - `npm test` ✅ (47 tests)
28
+ - `npm run lint` ⚠️ still reports pre-existing Biome diagnostics in legacy code paths; no new release-blocking failure was introduced for this patch.
29
+
30
+ ## Upgrade notes
31
+ If your Pi theme defines:
32
+
33
+ ```json
34
+ {
35
+ "toolSuccessBg": "#1e2e1e",
36
+ "toolErrorBg": "#2e1e1e"
37
+ }
38
+ ```
39
+
40
+ core tool panels now render those backgrounds across the full line width instead of only behind printed characters.
41
+
42
+ When FFF is available, pi-pretty now stores frecency/history data under:
43
+
44
+ ```text
45
+ ~/.pi/agent/pi-pretty/fff/
46
+ ```
47
+
48
+ Existing data under `~/.pi/agent/fff` is not migrated automatically by this release.
package/src/index.ts CHANGED
@@ -71,28 +71,41 @@ const FG_PURPLE = "\x1b[38;2;170;120;200m";
71
71
  const BG_STDERR = "\x1b[48;2;40;25;25m";
72
72
 
73
73
  const BG_DEFAULT = "\x1b[49m";
74
- let BG_BASE = BG_DEFAULT; // tool box base bg — updated from theme's toolSuccessBg
74
+ let BG_BASE = BG_DEFAULT; // tool box success/base bg — updated from theme's toolSuccessBg
75
+ let BG_ERROR = BG_DEFAULT; // tool box error bg — updated from theme's toolErrorBg
76
+
77
+ type BgTheme = { getBgAnsi?: (key: string) => string };
78
+ type FgTheme = { fg: (key: string, text: string) => string };
75
79
 
76
80
  /** Parse an ANSI 24-bit color escape into { r, g, b }. Handles both fg (38;2) and bg (48;2). */
77
81
  function parseAnsiRgb(ansi: string): { r: number; g: number; b: number } | null {
78
- const m = ansi.match(/\x1b\[(?:38|48);2;(\d+);(\d+);(\d+)m/);
82
+ const m = ansi.match(new RegExp(`${ESC_RE}\\[(?:38|48);2;(\\d+);(\\d+);(\\d+)m`));
79
83
  return m ? { r: +m[1], g: +m[2], b: +m[3] } : null;
80
84
  }
81
85
 
82
- /** Read toolSuccessBg from the pi theme and update BG_BASE + RST.
86
+ function getThemeBgAnsi(theme: BgTheme, key: string): string | null {
87
+ try {
88
+ const bgAnsi = theme.getBgAnsi?.(key);
89
+ return bgAnsi && parseAnsiRgb(bgAnsi) ? bgAnsi : null;
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+
95
+ /** Read themed tool backgrounds and update BG_BASE / BG_ERROR + RST.
83
96
  * Call once when theme is first available. Idempotent. */
84
97
  let _bgBaseResolved = false;
85
- function resolveBaseBackground(theme: any): void {
98
+ function resolveBaseBackground(theme: BgTheme | null | undefined): void {
86
99
  if (_bgBaseResolved || !theme?.getBgAnsi) return;
87
100
  _bgBaseResolved = true;
88
- try {
89
- const bgAnsi = theme.getBgAnsi("toolSuccessBg");
90
- const parsed = parseAnsiRgb(bgAnsi);
91
- if (parsed) {
92
- BG_BASE = bgAnsi;
93
- RST = `\x1b[0m${BG_BASE}`;
94
- }
95
- } catch { /* ignore — keep defaults */ }
101
+
102
+ BG_BASE = getThemeBgAnsi(theme, "toolSuccessBg") ?? BG_DEFAULT;
103
+ BG_ERROR = getThemeBgAnsi(theme, "toolErrorBg") ?? BG_BASE;
104
+ RST = `\x1b[0m${BG_BASE}`;
105
+ }
106
+
107
+ function renderToolError(error: string, theme: FgTheme): string {
108
+ return fillToolBackground(`\n${theme.fg("error", error)}`, BG_ERROR);
96
109
  }
97
110
 
98
111
  const ESC_RE = "\u001b";
@@ -126,6 +139,25 @@ function strip(s: string): string {
126
139
  return s.replace(ANSI_RE, "");
127
140
  }
128
141
 
142
+ function preserveToolBackground(ansi: string, bg: string): string {
143
+ return ansi.replace(ANSI_CAPTURE_RE, (seq, params: string) => {
144
+ const codes = params.split(";");
145
+ return params === "0" || codes.includes("49") ? `${seq}${bg}` : seq;
146
+ });
147
+ }
148
+
149
+ function fillToolBackground(text: string, bg = BG_BASE): string {
150
+ const width = termW();
151
+ return text
152
+ .split("\n")
153
+ .map((line) => {
154
+ const normalized = preserveToolBackground(line, bg);
155
+ const padding = Math.max(0, width - strip(normalized).length);
156
+ return `${bg}${normalized}${" ".repeat(padding)}${RST}`;
157
+ })
158
+ .join("\n");
159
+ }
160
+
129
161
  function termW(): number {
130
162
  const raw =
131
163
  process.stdout.columns || (process.stderr as any).columns || Number.parseInt(process.env.COLUMNS ?? "", 10) || 200;
@@ -780,6 +812,10 @@ let _fffPartialIndex = false;
780
812
  let _fffDbDir: string | null = null;
781
813
  const FFF_SCAN_TIMEOUT = 15_000;
782
814
 
815
+ function getPiPrettyFffDir(agentDir: string): string {
816
+ return join(agentDir, "pi-pretty", "fff");
817
+ }
818
+
783
819
  async function fffEnsureFinder(cwd: string): Promise<any> {
784
820
  if (_fffFinder && !_fffFinder.isDestroyed) return _fffFinder;
785
821
  if (!_fffModule || !_fffDbDir) return null;
@@ -874,7 +910,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
874
910
  try {
875
911
  _fffModule = require("@ff-labs/fff-node");
876
912
  if (getAgentDir) {
877
- _fffDbDir = join(getAgentDir(), "fff");
913
+ _fffDbDir = getPiPrettyFffDir(getAgentDir());
878
914
  try {
879
915
  mkdirSync(_fffDbDir, { recursive: true });
880
916
  } catch {}
@@ -883,7 +919,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
883
919
  /* FFF not installed — SDK tools will be used */
884
920
  }
885
921
  } else if (_fffModule && getAgentDir) {
886
- _fffDbDir = join(getAgentDir(), "fff");
922
+ _fffDbDir = getPiPrettyFffDir(getAgentDir());
887
923
  try {
888
924
  mkdirSync(_fffDbDir, { recursive: true });
889
925
  } catch {}
@@ -901,7 +937,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
901
937
 
902
938
  if (!_fffDbDir) {
903
939
  const agentDir = getAgentDir?.() ?? join(home, ".pi/agent");
904
- _fffDbDir = join(agentDir, "fff");
940
+ _fffDbDir = getPiPrettyFffDir(agentDir);
905
941
  try {
906
942
  mkdirSync(_fffDbDir, { recursive: true });
907
943
  } catch {}
@@ -978,7 +1014,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
978
1014
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
979
1015
  const offset = args?.offset ? ` ${theme.fg("muted", `from line ${args.offset}`)}` : "";
980
1016
  const limit = args?.limit ? ` ${theme.fg("muted", `(${args.limit} lines)`)}` : "";
981
- text.setText(`${theme.fg("toolTitle", theme.bold("read"))} ${theme.fg("accent", sp(fp))}${offset}${limit}`);
1017
+ text.setText(fillToolBackground(`${theme.fg("toolTitle", theme.bold("read"))} ${theme.fg("accent", sp(fp))}${offset}${limit}`));
982
1018
  return text;
983
1019
  },
984
1020
 
@@ -992,7 +1028,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
992
1028
  ?.filter((c: any) => c.type === "text")
993
1029
  .map((c: any) => c.text || "")
994
1030
  .join("\n") ?? "Error";
995
- text.setText(`\n${theme.fg("error", e)}`);
1031
+ text.setText(renderToolError(e, theme));
996
1032
  return text;
997
1033
  }
998
1034
 
@@ -1034,7 +1070,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1034
1070
  }
1035
1071
 
1036
1072
  out.push(rule(tw));
1037
- text.setText(out.join("\n"));
1073
+ text.setText(fillToolBackground(out.join("\n")));
1038
1074
  return text;
1039
1075
  }
1040
1076
 
@@ -1043,24 +1079,24 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1043
1079
  if (ctx.state._rk !== key) {
1044
1080
  ctx.state._rk = key;
1045
1081
  const info = `${FG_DIM}${d.lineCount} lines${RST}`;
1046
- ctx.state._rt = ` ${info}`;
1082
+ ctx.state._rt = fillToolBackground(` ${info}`);
1047
1083
 
1048
1084
  const maxShow = ctx.expanded ? d.lineCount : MAX_PREVIEW_LINES;
1049
1085
  renderFileContent(d.content, d.filePath, d.offset, maxShow)
1050
1086
  .then((rendered: string) => {
1051
1087
  if (ctx.state._rk !== key) return;
1052
- ctx.state._rt = ` ${info}\n${rendered}`;
1088
+ ctx.state._rt = fillToolBackground(` ${info}\n${rendered}`);
1053
1089
  ctx.invalidate();
1054
1090
  })
1055
1091
  .catch(() => {});
1056
1092
  }
1057
- text.setText(ctx.state._rt ?? ` ${FG_DIM}${d.lineCount} lines${RST}`);
1093
+ text.setText(ctx.state._rt ?? fillToolBackground(` ${FG_DIM}${d.lineCount} lines${RST}`));
1058
1094
  return text;
1059
1095
  }
1060
1096
 
1061
1097
  // Fallback
1062
1098
  const fallback = result.content?.[0]?.text ?? "read";
1063
- text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
1099
+ text.setText(fillToolBackground(` ${theme.fg("dim", String(fallback).slice(0, 120))}`));
1064
1100
  return text;
1065
1101
  },
1066
1102
  });
@@ -1111,7 +1147,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1111
1147
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
1112
1148
  const timeout = args?.timeout ? ` ${theme.fg("muted", `(${args.timeout}s timeout)`)}` : "";
1113
1149
  text.setText(
1114
- `${theme.fg("toolTitle", theme.bold("bash"))} ${theme.fg("accent", cmd.length > 80 ? cmd.slice(0, 77) + "…" : cmd)}${timeout}`,
1150
+ fillToolBackground(`${theme.fg("toolTitle", theme.bold("bash"))} ${theme.fg("accent", cmd.length > 80 ? cmd.slice(0, 77) + "…" : cmd)}${timeout}`),
1115
1151
  );
1116
1152
  return text;
1117
1153
  },
@@ -1126,7 +1162,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1126
1162
  ?.filter((c: any) => c.type === "text")
1127
1163
  .map((c: any) => c.text || "")
1128
1164
  .join("\n") ?? "Error";
1129
- text.setText(`\n${theme.fg("error", e)}`);
1165
+ text.setText(renderToolError(e, theme));
1130
1166
  return text;
1131
1167
  }
1132
1168
 
@@ -1151,15 +1187,15 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1151
1187
  if (lineCount > maxShow) {
1152
1188
  out.push(`${FG_DIM} … ${lineCount - maxShow} more lines${RST}`);
1153
1189
  }
1154
- text.setText(out.join("\n"));
1190
+ text.setText(fillToolBackground(out.join("\n")));
1155
1191
  } else {
1156
- text.setText(header);
1192
+ text.setText(fillToolBackground(header));
1157
1193
  }
1158
1194
  return text;
1159
1195
  }
1160
1196
 
1161
1197
  const fallback = result.content?.[0]?.text ?? "done";
1162
- text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
1198
+ text.setText(fillToolBackground(` ${theme.fg("dim", String(fallback).slice(0, 120))}`));
1163
1199
  return text;
1164
1200
  },
1165
1201
  });
@@ -1201,7 +1237,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1201
1237
  resolveBaseBackground(theme);
1202
1238
  const fp = args?.path ?? ".";
1203
1239
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
1204
- text.setText(`${theme.fg("toolTitle", theme.bold("ls"))} ${theme.fg("accent", sp(fp))}`);
1240
+ text.setText(fillToolBackground(`${theme.fg("toolTitle", theme.bold("ls"))} ${theme.fg("accent", sp(fp))}`));
1205
1241
  return text;
1206
1242
  },
1207
1243
 
@@ -1215,7 +1251,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1215
1251
  ?.filter((c: any) => c.type === "text")
1216
1252
  .map((c: any) => c.text || "")
1217
1253
  .join("\n") ?? "Error";
1218
- text.setText(`\n${theme.fg("error", e)}`);
1254
+ text.setText(renderToolError(e, theme));
1219
1255
  return text;
1220
1256
  }
1221
1257
 
@@ -1223,12 +1259,12 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1223
1259
  if (d?._type === "lsResult" && d.text) {
1224
1260
  const tree = renderTree(d.text, d.path);
1225
1261
  const info = `${FG_DIM}${d.entryCount} entries${RST}`;
1226
- text.setText(` ${info}\n${tree}`);
1262
+ text.setText(fillToolBackground(` ${info}\n${tree}`));
1227
1263
  return text;
1228
1264
  }
1229
1265
 
1230
1266
  const fallback = result.content?.[0]?.text ?? "listed";
1231
- text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
1267
+ text.setText(fillToolBackground(` ${theme.fg("dim", String(fallback).slice(0, 120))}`));
1232
1268
  return text;
1233
1269
  },
1234
1270
  });
@@ -1307,7 +1343,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1307
1343
  const pattern = args?.pattern ?? "";
1308
1344
  const path = args?.path ? ` ${theme.fg("muted", `in ${sp(args.path)}`)}` : "";
1309
1345
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
1310
- text.setText(`${theme.fg("toolTitle", theme.bold("find"))} ${theme.fg("accent", pattern)}${path}`);
1346
+ text.setText(fillToolBackground(`${theme.fg("toolTitle", theme.bold("find"))} ${theme.fg("accent", pattern)}${path}`));
1311
1347
  return text;
1312
1348
  },
1313
1349
 
@@ -1321,7 +1357,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1321
1357
  ?.filter((c: any) => c.type === "text")
1322
1358
  .map((c: any) => c.text || "")
1323
1359
  .join("\n") ?? "Error";
1324
- text.setText(`\n${theme.fg("error", e)}`);
1360
+ text.setText(renderToolError(e, theme));
1325
1361
  return text;
1326
1362
  }
1327
1363
 
@@ -1329,12 +1365,12 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1329
1365
  if (d?._type === "findResult" && d.text) {
1330
1366
  const rendered = renderFindResults(d.text);
1331
1367
  const info = `${FG_DIM}${d.matchCount} files${RST}`;
1332
- text.setText(` ${info}\n${rendered}`);
1368
+ text.setText(fillToolBackground(` ${info}\n${rendered}`));
1333
1369
  return text;
1334
1370
  }
1335
1371
 
1336
1372
  const fallback = result.content?.[0]?.text ?? "found";
1337
- text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
1373
+ text.setText(fillToolBackground(` ${theme.fg("dim", String(fallback).slice(0, 120))}`));
1338
1374
  return text;
1339
1375
  },
1340
1376
  });
@@ -1434,7 +1470,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1434
1470
  const path = args?.path ? ` ${theme.fg("muted", `in ${sp(args.path)}`)}` : "";
1435
1471
  const glob = args?.glob ? ` ${theme.fg("muted", `(${args.glob})`)}` : "";
1436
1472
  const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
1437
- text.setText(`${theme.fg("toolTitle", theme.bold("grep"))} ${theme.fg("accent", pattern)}${path}${glob}`);
1473
+ text.setText(fillToolBackground(`${theme.fg("toolTitle", theme.bold("grep"))} ${theme.fg("accent", pattern)}${path}${glob}`));
1438
1474
  return text;
1439
1475
  },
1440
1476
 
@@ -1448,7 +1484,7 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1448
1484
  ?.filter((c: any) => c.type === "text")
1449
1485
  .map((c: any) => c.text || "")
1450
1486
  .join("\n") ?? "Error";
1451
- text.setText(`\n${theme.fg("error", e)}`);
1487
+ text.setText(renderToolError(e, theme));
1452
1488
  return text;
1453
1489
  }
1454
1490
 
@@ -1458,22 +1494,22 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
1458
1494
  if (ctx.state._gk !== key) {
1459
1495
  ctx.state._gk = key;
1460
1496
  const info = `${FG_DIM}${d.matchCount} matches${RST}`;
1461
- ctx.state._gt = ` ${info}`;
1497
+ ctx.state._gt = fillToolBackground(` ${info}`);
1462
1498
 
1463
1499
  renderGrepResults(d.text, d.pattern)
1464
1500
  .then((rendered: string) => {
1465
1501
  if (ctx.state._gk !== key) return;
1466
- ctx.state._gt = ` ${info}\n${rendered}`;
1502
+ ctx.state._gt = fillToolBackground(` ${info}\n${rendered}`);
1467
1503
  ctx.invalidate();
1468
1504
  })
1469
1505
  .catch(() => {});
1470
1506
  }
1471
- text.setText(ctx.state._gt ?? ` ${FG_DIM}${d.matchCount} matches${RST}`);
1507
+ text.setText(ctx.state._gt ?? fillToolBackground(` ${FG_DIM}${d.matchCount} matches${RST}`));
1472
1508
  return text;
1473
1509
  }
1474
1510
 
1475
1511
  const fallback = result.content?.[0]?.text ?? "searched";
1476
- text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
1512
+ text.setText(fillToolBackground(` ${theme.fg("dim", String(fallback).slice(0, 120))}`));
1477
1513
  return text;
1478
1514
  },
1479
1515
  });
@@ -175,6 +175,9 @@ describe("piPrettyExtension integration", () => {
175
175
 
176
176
  function makeDeps(withFFF: boolean, finderOverrides?: Record<string, any>): PiPrettyDeps {
177
177
  const finder = mkFinder(finderOverrides);
178
+ const fffModule = finderOverrides?.FileFinder
179
+ ? { FileFinder: finderOverrides.FileFinder }
180
+ : { FileFinder: { create: vi.fn().mockReturnValue({ ok: true, value: finder }) } };
178
181
  return {
179
182
  sdk: {
180
183
  createReadToolDefinition: mockToolFactory(readExec),
@@ -185,9 +188,7 @@ describe("piPrettyExtension integration", () => {
185
188
  getAgentDir: () => "/tmp/pi-pretty-test",
186
189
  },
187
190
  TextComponent: class { private t = ""; setText(v: string) { this.t = v; } getText() { return this.t; } },
188
- fffModule: withFFF
189
- ? { FileFinder: { create: vi.fn().mockReturnValue({ ok: true, value: finder }) } }
190
- : undefined,
191
+ fffModule: withFFF ? fffModule : undefined,
191
192
  };
192
193
  }
193
194
 
@@ -445,6 +446,18 @@ describe("piPrettyExtension integration", () => {
445
446
  // ---- session lifecycle ---------------------------------------------
446
447
 
447
448
  describe("session lifecycle", () => {
449
+ it("stores FFF data under a pi-pretty-specific directory", async () => {
450
+ const create = vi.fn().mockReturnValue({ ok: true, value: mkFinder() });
451
+ load(true, { FileFinder: { create } });
452
+ const start = events.get("session_start")!;
453
+ expect(start, "session_start not registered").toBeDefined();
454
+ await start({}, { cwd: "/tmp/test" });
455
+ expect(create).toHaveBeenCalledWith(expect.objectContaining({
456
+ frecencyDbPath: "/tmp/pi-pretty-test/pi-pretty/fff/frecency.mdb",
457
+ historyDbPath: "/tmp/pi-pretty-test/pi-pretty/fff/history.mdb",
458
+ }));
459
+ });
460
+
448
461
  it("shutdown → subsequent find falls back to SDK", async () => {
449
462
  await loadWithFFF();
450
463
  await events.get("session_shutdown")!();