@herbertgao/pi-extensions 2026.9.6 → 2026.9.7

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.
Files changed (240) hide show
  1. package/README.md +3 -8
  2. package/THIRD_PARTY_NOTICES.md +0 -4
  3. package/node_modules/@narumitw/pi-btw/README.md +2 -0
  4. package/node_modules/@narumitw/pi-btw/dist/index.ts +284 -365
  5. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  6. package/node_modules/@narumitw/pi-btw/package.json +52 -53
  7. package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +494 -547
  8. package/node_modules/@narumitw/pi-btw/src/btw.ts +814 -856
  9. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +673 -674
  10. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +228 -270
  11. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +309 -318
  12. package/node_modules/@narumitw/pi-btw/src/menu.ts +416 -454
  13. package/node_modules/@narumitw/pi-btw/src/settings.ts +203 -219
  14. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +173 -201
  15. package/node_modules/@narumitw/pi-btw/src/text.ts +21 -23
  16. package/node_modules/@narumitw/pi-btw/src/transcript-markdown.ts +65 -0
  17. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +611 -662
  18. package/node_modules/pi-lens/CHANGELOG.md +239 -0
  19. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -0
  20. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +30 -4
  21. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +2 -0
  22. package/node_modules/pi-lens/dist/clients/bash-file-access.js +26 -0
  23. package/node_modules/pi-lens/dist/clients/biome-client.js +1 -1
  24. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +403 -35
  25. package/node_modules/pi-lens/dist/clients/bootstrap.js +6 -1
  26. package/node_modules/pi-lens/dist/clients/cascade-format.js +200 -4
  27. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +10 -2
  28. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +16 -1
  29. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +12 -1
  30. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +65 -0
  31. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +28 -0
  32. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +9 -2
  33. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +222 -0
  34. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +389 -13
  35. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +175 -25
  36. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +62 -0
  37. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +21 -41
  38. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +6 -1
  39. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +7 -7
  40. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -12
  41. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +2 -21
  42. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +1 -1
  43. package/node_modules/pi-lens/dist/clients/dispatch/types.js +4 -1
  44. package/node_modules/pi-lens/dist/clients/disposition-publish.js +1 -0
  45. package/node_modules/pi-lens/dist/clients/effective-config.js +1 -1
  46. package/node_modules/pi-lens/dist/clients/file-kinds.js +26 -1
  47. package/node_modules/pi-lens/dist/clients/file-role.js +21 -21
  48. package/node_modules/pi-lens/dist/clients/file-utils.js +156 -10
  49. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +56 -9
  50. package/node_modules/pi-lens/dist/clients/format-service.js +39 -21
  51. package/node_modules/pi-lens/dist/clients/formatters.js +60 -13
  52. package/node_modules/pi-lens/dist/clients/generation-guard.js +2 -2
  53. package/node_modules/pi-lens/dist/clients/git-guard.js +11 -3
  54. package/node_modules/pi-lens/dist/clients/indent-retarget.js +78 -8
  55. package/node_modules/pi-lens/dist/clients/installer/index.js +333 -114
  56. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +7 -0
  57. package/node_modules/pi-lens/dist/clients/instance-reaper.js +20 -10
  58. package/node_modules/pi-lens/dist/clients/language-profile.js +23 -1
  59. package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
  60. package/node_modules/pi-lens/dist/clients/latency-logger.js +4 -1
  61. package/node_modules/pi-lens/dist/clients/lens-engine.js +12 -3
  62. package/node_modules/pi-lens/dist/clients/lens-events.js +2 -1
  63. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +79 -0
  64. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +2 -1
  65. package/node_modules/pi-lens/dist/clients/lsp/client.js +25 -21
  66. package/node_modules/pi-lens/dist/clients/lsp/config.js +2 -2
  67. package/node_modules/pi-lens/dist/clients/lsp/index.js +7 -0
  68. package/node_modules/pi-lens/dist/clients/lsp/launch.js +16 -4
  69. package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -2
  70. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +2 -1
  71. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +5 -3
  72. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +12 -8
  73. package/node_modules/pi-lens/dist/clients/mcp/session.js +36 -4
  74. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +24 -7
  75. package/node_modules/pi-lens/dist/clients/observed-mutation.js +40 -2
  76. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +23 -3
  77. package/node_modules/pi-lens/dist/clients/opengrep-client.js +110 -15
  78. package/node_modules/pi-lens/dist/clients/path-utils.js +123 -3
  79. package/node_modules/pi-lens/dist/clients/performance-report.js +5 -1
  80. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +1 -1
  81. package/node_modules/pi-lens/dist/clients/pipeline.js +74 -5
  82. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +37 -5
  83. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +145 -18
  84. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +4 -1
  85. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +59 -3
  86. package/node_modules/pi-lens/dist/clients/project-lens-config.js +45 -2
  87. package/node_modules/pi-lens/dist/clients/read-guard.js +70 -11
  88. package/node_modules/pi-lens/dist/clients/resource-sampler.js +147 -9
  89. package/node_modules/pi-lens/dist/clients/ruff-client.js +1 -1
  90. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +33 -5
  91. package/node_modules/pi-lens/dist/clients/runtime-context.js +6 -2
  92. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +77 -7
  93. package/node_modules/pi-lens/dist/clients/runtime-session.js +21 -2
  94. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +43 -12
  95. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +330 -85
  96. package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -17
  97. package/node_modules/pi-lens/dist/clients/safe-spawn.js +258 -5
  98. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +6 -0
  99. package/node_modules/pi-lens/dist/clients/session-event-guard.js +50 -5
  100. package/node_modules/pi-lens/dist/clients/sg-runner.js +23 -11
  101. package/node_modules/pi-lens/dist/clients/sgconfig.js +79 -2
  102. package/node_modules/pi-lens/dist/clients/test-runner-client.js +6 -41
  103. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +43 -0
  104. package/node_modules/pi-lens/dist/clients/tool-agreement.js +423 -0
  105. package/node_modules/pi-lens/dist/clients/tool-config.js +50 -0
  106. package/node_modules/pi-lens/dist/clients/tool-cwd.js +47 -76
  107. package/node_modules/pi-lens/dist/clients/tool-policy.js +163 -7
  108. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +120 -0
  109. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +127 -189
  110. package/node_modules/pi-lens/dist/clients/widget-state.js +98 -0
  111. package/node_modules/pi-lens/dist/clients/word-index.js +5 -2
  112. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +7 -0
  113. package/node_modules/pi-lens/dist/index.js +27416 -24153
  114. package/node_modules/pi-lens/dist/mcp/server.js +5 -2
  115. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +7 -1
  116. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +168 -53
  117. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +244 -72
  118. package/node_modules/pi-lens/docs/audit1.md +5 -5
  119. package/node_modules/pi-lens/docs/dependencies.md +11 -0
  120. package/node_modules/pi-lens/docs/dispositions.md +52 -0
  121. package/node_modules/pi-lens/docs/environment-variables.md +2 -2
  122. package/node_modules/pi-lens/docs/features.md +4 -4
  123. package/node_modules/pi-lens/docs/globalconfig.md +3 -1
  124. package/node_modules/pi-lens/docs/language-coverage.md +1 -0
  125. package/node_modules/pi-lens/docs/pi-lens-fixer.md +53 -94
  126. package/node_modules/pi-lens/docs/pi-lens-investigator.md +43 -38
  127. package/node_modules/pi-lens/docs/pi-lens-retro.md +97 -0
  128. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +53 -66
  129. package/node_modules/pi-lens/docs/release-qa-baseline.md +22 -0
  130. package/node_modules/pi-lens/docs/settings.md +7 -4
  131. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  132. package/node_modules/pi-lens/docs/usage.md +2 -0
  133. package/node_modules/pi-lens/package.json +1 -1
  134. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +24 -12
  135. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +40 -0
  136. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +15 -0
  137. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +7 -0
  138. package/package.json +5 -11
  139. package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +0 -376
  140. package/node_modules/@luxusai/pi-hindsight/README.md +0 -92
  141. package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +0 -56
  142. package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +0 -169
  143. package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +0 -107
  144. package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +0 -89
  145. package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +0 -189
  146. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
  147. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
  148. package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +0 -54
  149. package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +0 -75
  150. package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +0 -300
  151. package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +0 -158
  152. package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +0 -164
  153. package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +0 -139
  154. package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +0 -74
  155. package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +0 -261
  156. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +0 -151
  157. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +0 -18
  158. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +0 -46
  159. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +0 -383
  160. package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +0 -240
  161. package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +0 -176
  162. package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +0 -178
  163. package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +0 -41
  164. package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +0 -386
  165. package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +0 -39
  166. package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +0 -34
  167. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +0 -140
  168. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +0 -61
  169. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +0 -925
  170. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +0 -247
  171. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +0 -547
  172. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +0 -480
  173. package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +0 -182
  174. package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +0 -86
  175. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +0 -1010
  176. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +0 -175
  177. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +0 -425
  178. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +0 -210
  179. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +0 -817
  180. package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +0 -25
  181. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +0 -319
  182. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +0 -196
  183. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +0 -189
  184. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +0 -65
  185. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +0 -260
  186. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +0 -244
  187. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +0 -59
  188. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +0 -82
  189. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +0 -45
  190. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +0 -331
  191. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +0 -354
  192. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +0 -66
  193. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +0 -132
  194. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +0 -228
  195. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +0 -74
  196. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +0 -16
  197. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +0 -648
  198. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +0 -105
  199. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +0 -68
  200. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +0 -71
  201. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +0 -13
  202. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +0 -162
  203. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +0 -131
  204. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +0 -104
  205. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +0 -37
  206. package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +0 -893
  207. package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +0 -35
  208. package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +0 -177
  209. package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +0 -7
  210. package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +0 -21
  211. package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +0 -112
  212. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +0 -111
  213. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +0 -214
  214. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +0 -70
  215. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +0 -392
  216. package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +0 -45
  217. package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +0 -9
  218. package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +0 -860
  219. package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +0 -105
  220. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +0 -189
  221. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +0 -299
  222. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +0 -201
  223. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +0 -47
  224. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +0 -239
  225. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +0 -50
  226. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +0 -259
  227. package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +0 -77
  228. package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +0 -534
  229. package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +0 -319
  230. package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +0 -325
  231. package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +0 -42
  232. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +0 -244
  233. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +0 -56
  234. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +0 -50
  235. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +0 -167
  236. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +0 -207
  237. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +0 -120
  238. package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +0 -8
  239. package/node_modules/@luxusai/pi-hindsight/package.json +0 -118
  240. package/node_modules/@luxusai/pi-hindsight/skills/hindsight-memory-doctor/SKILL.md +0 -75
@@ -1,390 +1,381 @@
1
1
  import {
2
- copyToClipboard,
3
- type ExtensionAPI,
4
- type ExtensionCommandContext,
5
- type SessionEntry,
6
- type SessionTreeNode,
7
- TreeSelectorComponent,
2
+ copyToClipboard,
3
+ type ExtensionAPI,
4
+ type ExtensionCommandContext,
5
+ type SessionEntry,
6
+ type SessionTreeNode,
7
+ TreeSelectorComponent,
8
8
  } from "@earendil-works/pi-coding-agent";
9
9
  import { type Component, type Focusable, Key, matchesKey } from "@earendil-works/pi-tui";
10
10
  import { showBtwCustomPreservingEditor } from "./menu.js";
11
11
  import { sanitizeSingleLine } from "./text.js";
12
12
 
13
- export type MainEntryPickResult =
14
- | { kind: "selected"; entryId: string }
15
- | { kind: "back" }
16
- | { kind: "closed" };
13
+ export type MainEntryPickResult = { kind: "selected"; entryId: string } | { kind: "back" } | { kind: "closed" };
17
14
 
18
15
  export interface MainThreadTreeSelector extends Component, Focusable {
19
- onCopy?: (text: string | undefined) => void;
20
- setViewLabel?(entryId: string, label: string | undefined, labelTimestamp?: string): void;
21
- dispose?(): void;
16
+ onCopy?: (text: string | undefined) => void;
17
+ setViewLabel?(entryId: string, label: string | undefined, labelTimestamp?: string): void;
18
+ dispose?(): void;
22
19
  }
23
20
 
24
21
  export interface MainThreadTreeSelectorOptions {
25
- tree: SessionTreeNode[];
26
- currentLeafId: string | null;
27
- terminalRows: number;
28
- onSelect: (entryId: string) => void;
29
- onCancel: () => void;
30
- onCopy: (entryId: string | undefined, displayText: string | undefined) => void;
31
- onLabelChange: (entryId: string, label: string | undefined) => void;
22
+ tree: SessionTreeNode[];
23
+ currentLeafId: string | null;
24
+ terminalRows: number;
25
+ onSelect: (entryId: string) => void;
26
+ onCancel: () => void;
27
+ onCopy: (entryId: string | undefined, displayText: string | undefined) => void;
28
+ onLabelChange: (entryId: string, label: string | undefined) => void;
32
29
  }
33
30
 
34
31
  export interface MainThreadTreePickerDependencies {
35
- createSelector?: (options: MainThreadTreeSelectorOptions) => MainThreadTreeSelector;
36
- copyToClipboard?: (text: string, signal: AbortSignal) => Promise<void>;
32
+ createSelector?: (options: MainThreadTreeSelectorOptions) => MainThreadTreeSelector;
33
+ copyToClipboard?: (text: string, signal: AbortSignal) => Promise<void>;
37
34
  }
38
35
 
39
36
  class MainThreadTreePickerComponent implements Component, Focusable {
40
- constructor(
41
- private readonly selector: MainThreadTreeSelector,
42
- private readonly onClose: () => void,
43
- ) {}
37
+ constructor(
38
+ private readonly selector: MainThreadTreeSelector,
39
+ private readonly onClose: () => void,
40
+ ) {}
44
41
 
45
- get focused(): boolean {
46
- return this.selector.focused;
47
- }
42
+ get focused(): boolean {
43
+ return this.selector.focused;
44
+ }
48
45
 
49
- set focused(value: boolean) {
50
- this.selector.focused = value;
51
- }
46
+ set focused(value: boolean) {
47
+ this.selector.focused = value;
48
+ }
52
49
 
53
- get wantsKeyRelease(): boolean | undefined {
54
- return this.selector.wantsKeyRelease;
55
- }
50
+ get wantsKeyRelease(): boolean | undefined {
51
+ return this.selector.wantsKeyRelease;
52
+ }
56
53
 
57
- render(width: number): string[] {
58
- return this.selector.render(width);
59
- }
54
+ render(width: number): string[] {
55
+ return this.selector.render(width);
56
+ }
60
57
 
61
- handleInput(data: string): void {
62
- if (matchesKey(data, Key.ctrl("c"))) {
63
- this.onClose();
64
- return;
65
- }
66
- this.selector.handleInput?.(data);
67
- }
58
+ handleInput(data: string): void {
59
+ if (matchesKey(data, Key.ctrl("c"))) {
60
+ this.onClose();
61
+ return;
62
+ }
63
+ this.selector.handleInput?.(data);
64
+ }
68
65
 
69
- invalidate(): void {
70
- this.selector.invalidate();
71
- }
66
+ invalidate(): void {
67
+ this.selector.invalidate();
68
+ }
72
69
 
73
- dispose(): void {
74
- this.selector.dispose?.();
75
- this.onClose();
76
- }
70
+ dispose(): void {
71
+ this.selector.dispose?.();
72
+ this.onClose();
73
+ }
77
74
  }
78
75
 
79
76
  export async function pickMainEntry(
80
- pi: ExtensionAPI,
81
- ctx: ExtensionCommandContext,
82
- dependencies: MainThreadTreePickerDependencies = {},
77
+ pi: ExtensionAPI,
78
+ ctx: ExtensionCommandContext,
79
+ dependencies: MainThreadTreePickerDependencies = {},
83
80
  ): Promise<MainEntryPickResult> {
84
- let rawTree: SessionTreeNode[];
85
- let currentLeafId: string | null;
86
- try {
87
- rawTree = ctx.sessionManager.getTree();
88
- currentLeafId = ctx.sessionManager.getLeafId();
89
- } catch {
90
- return { kind: "closed" };
91
- }
81
+ let rawTree: SessionTreeNode[];
82
+ let currentLeafId: string | null;
83
+ try {
84
+ rawTree = ctx.sessionManager.getTree();
85
+ currentLeafId = ctx.sessionManager.getLeafId();
86
+ } catch {
87
+ return { kind: "closed" };
88
+ }
92
89
 
93
- if (rawTree.length === 0) {
94
- notifySafely(ctx, "No main-thread entries are available", "warning");
95
- return { kind: "back" };
96
- }
90
+ if (rawTree.length === 0) {
91
+ notifySafely(ctx, "No main-thread entries are available", "warning");
92
+ return { kind: "back" };
93
+ }
97
94
 
98
- const tree = sanitizeTreeForDisplay(rawTree);
99
- const rawCopyText = collectRawCopyText(rawTree);
100
- const savedLabels = collectSavedLabels(rawTree);
101
- const createSelector = dependencies.createSelector ?? createNativeTreeSelector;
102
- const copy = dependencies.copyToClipboard ?? copyText;
103
- const copyControllers = new Set<AbortController>();
104
- const copyTasks = new Set<Promise<void>>();
105
- const abortCopies = () => {
106
- for (const controller of copyControllers) {
107
- controller.abort(new Error("The main-thread tree picker closed"));
108
- }
109
- };
110
- const result = await showBtwCustomPreservingEditor<MainEntryPickResult>(
111
- ctx,
112
- (tui, _theme, _keybindings, done) => {
113
- let settled = false;
114
- let selector: MainThreadTreeSelector | undefined;
115
- const finish = (value: MainEntryPickResult) => {
116
- if (settled) return;
117
- settled = true;
118
- abortCopies();
119
- done(value);
120
- };
121
- const onCopy = (entryId: string | undefined, displayText: string | undefined) => {
122
- if (settled) return;
123
- const text = entryId ? rawCopyText.get(entryId) : displayText;
124
- if (!text) {
125
- notifySafely(ctx, "Selected entry has no text to copy", "warning");
126
- return;
127
- }
128
- const controller = new AbortController();
129
- copyControllers.add(controller);
130
- let operation: Promise<void>;
131
- try {
132
- operation = copy(text, controller.signal);
133
- } catch (error: unknown) {
134
- operation = Promise.reject(error);
135
- }
136
- let task!: Promise<void>;
137
- task = operation
138
- .then(() => {
139
- if (!settled) notifySafely(ctx, "Copied selected message", "info");
140
- })
141
- .catch((error: unknown) => {
142
- if (!settled && !controller.signal.aborted) {
143
- notifySafely(ctx, `Could not copy selected message: ${formatError(error)}`, "error");
144
- }
145
- })
146
- .finally(() => {
147
- copyControllers.delete(controller);
148
- copyTasks.delete(task);
149
- });
150
- copyTasks.add(task);
151
- };
152
- const restoreLabel = (entryId: string) => {
153
- const previous = savedLabels.get(entryId);
154
- selector?.setViewLabel?.(entryId, previous?.label, previous?.labelTimestamp);
155
- tui.requestRender();
156
- };
157
- const onLabelChange = (entryId: string, label: string | undefined) => {
158
- if (settled) return;
159
- try {
160
- if (!ctx.sessionManager.getEntry(entryId)) {
161
- restoreLabel(entryId);
162
- notifySafely(ctx, "The selected main-thread entry is no longer available", "warning");
163
- return;
164
- }
165
- const persistedLabel = label === undefined ? undefined : sanitizeSingleLine(label);
166
- pi.setLabel(entryId, persistedLabel);
167
- savedLabels.set(entryId, { label: persistedLabel });
168
- selector?.setViewLabel?.(entryId, persistedLabel);
169
- tui.requestRender();
170
- } catch (error: unknown) {
171
- restoreLabel(entryId);
172
- notifySafely(ctx, `Could not update tree label: ${formatError(error)}`, "error");
173
- }
174
- };
175
- selector = createSelector({
176
- tree,
177
- currentLeafId,
178
- terminalRows: tui.terminal.rows,
179
- onSelect: (entryId) => finish({ kind: "selected", entryId }),
180
- onCancel: () => finish({ kind: "back" }),
181
- onCopy,
182
- onLabelChange,
183
- });
184
- return new MainThreadTreePickerComponent(selector, () => finish({ kind: "closed" }));
185
- },
186
- );
187
- abortCopies();
188
- await Promise.allSettled([...copyTasks]);
95
+ const tree = sanitizeTreeForDisplay(rawTree);
96
+ const rawCopyText = collectRawCopyText(rawTree);
97
+ const savedLabels = collectSavedLabels(rawTree);
98
+ const createSelector = dependencies.createSelector ?? createNativeTreeSelector;
99
+ const copy = dependencies.copyToClipboard ?? copyText;
100
+ const copyControllers = new Set<AbortController>();
101
+ const copyTasks = new Set<Promise<void>>();
102
+ const abortCopies = () => {
103
+ for (const controller of copyControllers) {
104
+ controller.abort(new Error("The main-thread tree picker closed"));
105
+ }
106
+ };
107
+ const result = await showBtwCustomPreservingEditor<MainEntryPickResult>(ctx, (tui, _theme, _keybindings, done) => {
108
+ let settled = false;
109
+ let selector: MainThreadTreeSelector | undefined;
110
+ const finish = (value: MainEntryPickResult) => {
111
+ if (settled) return;
112
+ settled = true;
113
+ abortCopies();
114
+ done(value);
115
+ };
116
+ const onCopy = (entryId: string | undefined, displayText: string | undefined) => {
117
+ if (settled) return;
118
+ const text = entryId ? rawCopyText.get(entryId) : displayText;
119
+ if (!text) {
120
+ notifySafely(ctx, "Selected entry has no text to copy", "warning");
121
+ return;
122
+ }
123
+ const controller = new AbortController();
124
+ copyControllers.add(controller);
125
+ let operation: Promise<void>;
126
+ try {
127
+ operation = copy(text, controller.signal);
128
+ } catch (error: unknown) {
129
+ operation = Promise.reject(error);
130
+ }
131
+ let task!: Promise<void>;
132
+ task = operation
133
+ .then(() => {
134
+ if (!settled) notifySafely(ctx, "Copied selected message", "info");
135
+ })
136
+ .catch((error: unknown) => {
137
+ if (!settled && !controller.signal.aborted) {
138
+ notifySafely(ctx, `Could not copy selected message: ${formatError(error)}`, "error");
139
+ }
140
+ })
141
+ .finally(() => {
142
+ copyControllers.delete(controller);
143
+ copyTasks.delete(task);
144
+ });
145
+ copyTasks.add(task);
146
+ };
147
+ const restoreLabel = (entryId: string) => {
148
+ const previous = savedLabels.get(entryId);
149
+ selector?.setViewLabel?.(entryId, previous?.label, previous?.labelTimestamp);
150
+ tui.requestRender();
151
+ };
152
+ const onLabelChange = (entryId: string, label: string | undefined) => {
153
+ if (settled) return;
154
+ try {
155
+ if (!ctx.sessionManager.getEntry(entryId)) {
156
+ restoreLabel(entryId);
157
+ notifySafely(ctx, "The selected main-thread entry is no longer available", "warning");
158
+ return;
159
+ }
160
+ const persistedLabel = label === undefined ? undefined : sanitizeSingleLine(label);
161
+ pi.setLabel(entryId, persistedLabel);
162
+ savedLabels.set(entryId, { label: persistedLabel });
163
+ selector?.setViewLabel?.(entryId, persistedLabel);
164
+ tui.requestRender();
165
+ } catch (error: unknown) {
166
+ restoreLabel(entryId);
167
+ notifySafely(ctx, `Could not update tree label: ${formatError(error)}`, "error");
168
+ }
169
+ };
170
+ selector = createSelector({
171
+ tree,
172
+ currentLeafId,
173
+ terminalRows: tui.terminal.rows,
174
+ onSelect: (entryId) => finish({ kind: "selected", entryId }),
175
+ onCancel: () => finish({ kind: "back" }),
176
+ onCopy,
177
+ onLabelChange,
178
+ });
179
+ return new MainThreadTreePickerComponent(selector, () => finish({ kind: "closed" }));
180
+ });
181
+ abortCopies();
182
+ await Promise.allSettled([...copyTasks]);
189
183
 
190
- return result ?? { kind: "closed" };
184
+ return result ?? { kind: "closed" };
191
185
  }
192
186
 
193
187
  function createNativeTreeSelector(options: MainThreadTreeSelectorOptions): MainThreadTreeSelector {
194
- const selector = new TreeSelectorComponent(
195
- options.tree,
196
- options.currentLeafId,
197
- options.terminalRows,
198
- options.onSelect,
199
- options.onCancel,
200
- options.onLabelChange,
201
- );
202
- selector.onCopy = (displayText) =>
203
- options.onCopy(selector.getTreeList().getSelectedNode()?.entry.id, displayText);
204
- const result = selector as MainThreadTreeSelector;
205
- result.setViewLabel = (entryId, label, labelTimestamp) =>
206
- selector.getTreeList().updateNodeLabel(entryId, label, labelTimestamp);
207
- return result;
188
+ const selector = new TreeSelectorComponent(
189
+ options.tree,
190
+ options.currentLeafId,
191
+ options.terminalRows,
192
+ options.onSelect,
193
+ options.onCancel,
194
+ options.onLabelChange,
195
+ );
196
+ selector.onCopy = (displayText) => options.onCopy(selector.getTreeList().getSelectedNode()?.entry.id, displayText);
197
+ const result = selector as MainThreadTreeSelector;
198
+ result.setViewLabel = (entryId, label, labelTimestamp) =>
199
+ selector.getTreeList().updateNodeLabel(entryId, label, labelTimestamp);
200
+ return result;
208
201
  }
209
202
 
210
203
  function sanitizeTreeForDisplay(tree: readonly SessionTreeNode[]): SessionTreeNode[] {
211
- return tree.map((node) => {
212
- const result: SessionTreeNode = {
213
- entry: sanitizeEntryForDisplay(node.entry),
214
- children: sanitizeTreeForDisplay(node.children),
215
- };
216
- if (node.label !== undefined) result.label = sanitizeSingleLine(node.label);
217
- if (node.labelTimestamp !== undefined) {
218
- result.labelTimestamp = sanitizeSingleLine(node.labelTimestamp);
219
- }
220
- return result;
221
- });
204
+ return tree.map((node) => {
205
+ const result: SessionTreeNode = {
206
+ entry: sanitizeEntryForDisplay(node.entry),
207
+ children: sanitizeTreeForDisplay(node.children),
208
+ };
209
+ if (node.label !== undefined) result.label = sanitizeSingleLine(node.label);
210
+ if (node.labelTimestamp !== undefined) {
211
+ result.labelTimestamp = sanitizeSingleLine(node.labelTimestamp);
212
+ }
213
+ return result;
214
+ });
222
215
  }
223
216
 
224
217
  function sanitizeEntryForDisplay(entry: SessionEntry): SessionEntry {
225
- switch (entry.type) {
226
- case "message": {
227
- const message = { ...entry.message } as Record<string, unknown>;
228
- if ("content" in entry.message)
229
- message.content = sanitizeDisplayContent(entry.message.content);
230
- for (const key of ["role", "errorMessage", "command", "toolName"] as const) {
231
- const value = message[key];
232
- if (typeof value === "string") message[key] = sanitizeSingleLine(value);
233
- }
234
- return { ...entry, message } as unknown as SessionEntry;
235
- }
236
- case "custom_message":
237
- return {
238
- ...entry,
239
- customType: sanitizeSingleLine(entry.customType),
240
- content: sanitizeDisplayContent(entry.content) as typeof entry.content,
241
- };
242
- case "compaction":
243
- return { ...entry, summary: sanitizeSingleLine(entry.summary) };
244
- case "branch_summary":
245
- return { ...entry, summary: sanitizeSingleLine(entry.summary) };
246
- case "model_change":
247
- return {
248
- ...entry,
249
- provider: sanitizeSingleLine(entry.provider),
250
- modelId: sanitizeSingleLine(entry.modelId),
251
- };
252
- case "thinking_level_change":
253
- return { ...entry, thinkingLevel: sanitizeSingleLine(entry.thinkingLevel) };
254
- case "custom":
255
- return { ...entry, customType: sanitizeSingleLine(entry.customType) };
256
- case "label":
257
- return {
258
- ...entry,
259
- label: entry.label === undefined ? undefined : sanitizeSingleLine(entry.label),
260
- };
261
- case "session_info":
262
- return {
263
- ...entry,
264
- name: entry.name === undefined ? undefined : sanitizeSingleLine(entry.name),
265
- };
266
- }
218
+ switch (entry.type) {
219
+ case "message": {
220
+ const message = { ...entry.message } as Record<string, unknown>;
221
+ if ("content" in entry.message) message.content = sanitizeDisplayContent(entry.message.content);
222
+ for (const key of ["role", "errorMessage", "command", "toolName"] as const) {
223
+ const value = message[key];
224
+ if (typeof value === "string") message[key] = sanitizeSingleLine(value);
225
+ }
226
+ return { ...entry, message } as unknown as SessionEntry;
227
+ }
228
+ case "custom_message":
229
+ return {
230
+ ...entry,
231
+ customType: sanitizeSingleLine(entry.customType),
232
+ content: sanitizeDisplayContent(entry.content) as typeof entry.content,
233
+ };
234
+ case "compaction":
235
+ return { ...entry, summary: sanitizeSingleLine(entry.summary) };
236
+ case "branch_summary":
237
+ return { ...entry, summary: sanitizeSingleLine(entry.summary) };
238
+ case "model_change":
239
+ return {
240
+ ...entry,
241
+ provider: sanitizeSingleLine(entry.provider),
242
+ modelId: sanitizeSingleLine(entry.modelId),
243
+ };
244
+ case "thinking_level_change":
245
+ return { ...entry, thinkingLevel: sanitizeSingleLine(entry.thinkingLevel) };
246
+ case "custom":
247
+ return { ...entry, customType: sanitizeSingleLine(entry.customType) };
248
+ case "label":
249
+ return {
250
+ ...entry,
251
+ label: entry.label === undefined ? undefined : sanitizeSingleLine(entry.label),
252
+ };
253
+ case "session_info":
254
+ return {
255
+ ...entry,
256
+ name: entry.name === undefined ? undefined : sanitizeSingleLine(entry.name),
257
+ };
258
+ }
267
259
  }
268
260
 
269
261
  function sanitizeDisplayContent(content: unknown): unknown {
270
- if (typeof content === "string") return sanitizeSingleLine(content);
271
- if (!Array.isArray(content)) return content;
272
- return content.map((block) => {
273
- if (block === null || typeof block !== "object" || !("type" in block)) return block;
274
- if (block.type === "text" && "text" in block && typeof block.text === "string") {
275
- return { ...block, text: sanitizeSingleLine(block.text) };
276
- }
277
- if (block.type === "toolCall") {
278
- const copy = { ...block } as Record<string, unknown>;
279
- if (typeof copy.name === "string") copy.name = sanitizeSingleLine(copy.name);
280
- copy.arguments = sanitizeToolArguments(copy.arguments, new WeakMap());
281
- return copy;
282
- }
283
- return block;
284
- });
262
+ if (typeof content === "string") return sanitizeSingleLine(content);
263
+ if (!Array.isArray(content)) return content;
264
+ return content.map((block) => {
265
+ if (block === null || typeof block !== "object" || !("type" in block)) return block;
266
+ if (block.type === "text" && "text" in block && typeof block.text === "string") {
267
+ return { ...block, text: sanitizeSingleLine(block.text) };
268
+ }
269
+ if (block.type === "toolCall") {
270
+ const copy = { ...block } as Record<string, unknown>;
271
+ if (typeof copy.name === "string") copy.name = sanitizeSingleLine(copy.name);
272
+ copy.arguments = sanitizeToolArguments(copy.arguments, new WeakMap());
273
+ return copy;
274
+ }
275
+ return block;
276
+ });
285
277
  }
286
278
 
287
279
  function sanitizeToolArguments(value: unknown, seen: WeakMap<object, unknown>): unknown {
288
- if (typeof value === "string") return sanitizeSingleLine(value);
289
- if (value === null || typeof value !== "object") return value;
290
- const existing = seen.get(value);
291
- if (existing !== undefined) return existing;
292
- if (Array.isArray(value)) {
293
- const result: unknown[] = [];
294
- seen.set(value, result);
295
- for (const item of value) result.push(sanitizeToolArguments(item, seen));
296
- return result;
297
- }
298
- const result: Record<string, unknown> = {};
299
- seen.set(value, result);
300
- for (const [key, item] of Object.entries(value)) {
301
- result[key] = sanitizeToolArguments(item, seen);
302
- }
303
- return result;
280
+ if (typeof value === "string") return sanitizeSingleLine(value);
281
+ if (value === null || typeof value !== "object") return value;
282
+ const existing = seen.get(value);
283
+ if (existing !== undefined) return existing;
284
+ if (Array.isArray(value)) {
285
+ const result: unknown[] = [];
286
+ seen.set(value, result);
287
+ for (const item of value) result.push(sanitizeToolArguments(item, seen));
288
+ return result;
289
+ }
290
+ const result: Record<string, unknown> = {};
291
+ seen.set(value, result);
292
+ for (const [key, item] of Object.entries(value)) {
293
+ result[key] = sanitizeToolArguments(item, seen);
294
+ }
295
+ return result;
304
296
  }
305
297
 
306
298
  function collectRawCopyText(tree: readonly SessionTreeNode[]): Map<string, string> {
307
- const result = new Map<string, string>();
308
- const visit = (nodes: readonly SessionTreeNode[]) => {
309
- for (const node of nodes) {
310
- const text = getRawCopyText(node.entry);
311
- if (text !== undefined) result.set(node.entry.id, text);
312
- visit(node.children);
313
- }
314
- };
315
- visit(tree);
316
- return result;
299
+ const result = new Map<string, string>();
300
+ const visit = (nodes: readonly SessionTreeNode[]) => {
301
+ for (const node of nodes) {
302
+ const text = getRawCopyText(node.entry);
303
+ if (text !== undefined) result.set(node.entry.id, text);
304
+ visit(node.children);
305
+ }
306
+ };
307
+ visit(tree);
308
+ return result;
317
309
  }
318
310
 
319
311
  function getRawCopyText(entry: SessionEntry): string | undefined {
320
- let text: string | undefined;
321
- if (entry.type === "message") {
322
- if (entry.message.role === "bashExecution") text = entry.message.command;
323
- else if ("content" in entry.message) {
324
- text = extractRawText(entry.message.content);
325
- if (!text && entry.message.role === "assistant") text = entry.message.errorMessage;
326
- }
327
- } else if (entry.type === "custom_message") text = extractRawText(entry.content);
328
- else if (entry.type === "compaction" || entry.type === "branch_summary") text = entry.summary;
329
- return text?.trim() ? text : undefined;
312
+ let text: string | undefined;
313
+ if (entry.type === "message") {
314
+ if (entry.message.role === "bashExecution") text = entry.message.command;
315
+ else if ("content" in entry.message) {
316
+ text = extractRawText(entry.message.content);
317
+ if (!text && entry.message.role === "assistant") text = entry.message.errorMessage;
318
+ }
319
+ } else if (entry.type === "custom_message") text = extractRawText(entry.content);
320
+ else if (entry.type === "compaction" || entry.type === "branch_summary") text = entry.summary;
321
+ return text?.trim() ? text : undefined;
330
322
  }
331
323
 
332
324
  function extractRawText(content: unknown): string {
333
- if (typeof content === "string") return content;
334
- if (!Array.isArray(content)) return "";
335
- return content
336
- .filter(
337
- (block): block is { type: "text"; text: string } =>
338
- block !== null &&
339
- typeof block === "object" &&
340
- "type" in block &&
341
- block.type === "text" &&
342
- "text" in block &&
343
- typeof block.text === "string",
344
- )
345
- .map((block) => block.text)
346
- .join("");
325
+ if (typeof content === "string") return content;
326
+ if (!Array.isArray(content)) return "";
327
+ return content
328
+ .filter(
329
+ (block): block is { type: "text"; text: string } =>
330
+ block !== null &&
331
+ typeof block === "object" &&
332
+ "type" in block &&
333
+ block.type === "text" &&
334
+ "text" in block &&
335
+ typeof block.text === "string",
336
+ )
337
+ .map((block) => block.text)
338
+ .join("");
347
339
  }
348
340
 
349
341
  interface SavedLabel {
350
- label: string | undefined;
351
- labelTimestamp?: string;
342
+ label: string | undefined;
343
+ labelTimestamp?: string;
352
344
  }
353
345
 
354
346
  function collectSavedLabels(tree: readonly SessionTreeNode[]): Map<string, SavedLabel> {
355
- const result = new Map<string, SavedLabel>();
356
- const visit = (nodes: readonly SessionTreeNode[]) => {
357
- for (const node of nodes) {
358
- result.set(node.entry.id, {
359
- label: node.label === undefined ? undefined : sanitizeSingleLine(node.label),
360
- labelTimestamp:
361
- node.labelTimestamp === undefined ? undefined : sanitizeSingleLine(node.labelTimestamp),
362
- });
363
- visit(node.children);
364
- }
365
- };
366
- visit(tree);
367
- return result;
347
+ const result = new Map<string, SavedLabel>();
348
+ const visit = (nodes: readonly SessionTreeNode[]) => {
349
+ for (const node of nodes) {
350
+ result.set(node.entry.id, {
351
+ label: node.label === undefined ? undefined : sanitizeSingleLine(node.label),
352
+ labelTimestamp: node.labelTimestamp === undefined ? undefined : sanitizeSingleLine(node.labelTimestamp),
353
+ });
354
+ visit(node.children);
355
+ }
356
+ };
357
+ visit(tree);
358
+ return result;
368
359
  }
369
360
 
370
361
  async function copyText(text: string, signal: AbortSignal): Promise<void> {
371
- signal.throwIfAborted();
372
- await copyToClipboard(text);
373
- signal.throwIfAborted();
362
+ signal.throwIfAborted();
363
+ await copyToClipboard(text);
364
+ signal.throwIfAborted();
374
365
  }
375
366
 
376
367
  function notifySafely(
377
- ctx: ExtensionCommandContext,
378
- message: string,
379
- level: Parameters<ExtensionCommandContext["ui"]["notify"]>[1],
368
+ ctx: ExtensionCommandContext,
369
+ message: string,
370
+ level: Parameters<ExtensionCommandContext["ui"]["notify"]>[1],
380
371
  ): void {
381
- try {
382
- ctx.ui.notify(sanitizeSingleLine(message), level);
383
- } catch {
384
- // The command context may have been replaced while the selector was open.
385
- }
372
+ try {
373
+ ctx.ui.notify(sanitizeSingleLine(message), level);
374
+ } catch {
375
+ // The command context may have been replaced while the selector was open.
376
+ }
386
377
  }
387
378
 
388
379
  function formatError(error: unknown): string {
389
- return error instanceof Error ? error.message : String(error);
380
+ return error instanceof Error ? error.message : String(error);
390
381
  }