@oh-my-pi/pi-coding-agent 17.1.5 → 17.1.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 (244) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/{CHANGELOG-qs3vd6xf.md → CHANGELOG-5k4dq4g6.md} +54 -0
  3. package/dist/cli.js +3342 -3274
  4. package/dist/types/capability/index.d.ts +1 -1
  5. package/dist/types/capability/types.d.ts +23 -1
  6. package/dist/types/config/settings-schema.d.ts +39 -2
  7. package/dist/types/cursor.d.ts +2 -1
  8. package/dist/types/extensibility/extensions/runner.d.ts +4 -0
  9. package/dist/types/extensibility/shared-events.d.ts +18 -1
  10. package/dist/types/internal-urls/mcp-protocol.d.ts +3 -2
  11. package/dist/types/internal-urls/parse.d.ts +12 -0
  12. package/dist/types/internal-urls/router.d.ts +6 -0
  13. package/dist/types/internal-urls/types.d.ts +6 -0
  14. package/dist/types/lsp/config.d.ts +1 -0
  15. package/dist/types/lsp/types.d.ts +2 -0
  16. package/dist/types/mcp/manager.d.ts +5 -0
  17. package/dist/types/mcp/tool-bridge.d.ts +13 -0
  18. package/dist/types/modes/components/custom-editor.d.ts +5 -0
  19. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  20. package/dist/types/modes/rpc/rpc-mode.d.ts +2 -0
  21. package/dist/types/sdk.d.ts +3 -1
  22. package/dist/types/session/agent-session-types.d.ts +8 -6
  23. package/dist/types/session/agent-session.d.ts +28 -1
  24. package/dist/types/session/model-controls.d.ts +4 -1
  25. package/dist/types/session/session-advisors.d.ts +7 -1
  26. package/dist/types/session/session-tools.d.ts +44 -6
  27. package/dist/types/session/streaming-output.d.ts +7 -2
  28. package/dist/types/session/tool-choice-queue.d.ts +6 -4
  29. package/dist/types/session/turn-recovery.d.ts +5 -3
  30. package/dist/types/task/index.d.ts +1 -1
  31. package/dist/types/task/types.d.ts +3 -11
  32. package/dist/types/thinking.d.ts +21 -2
  33. package/dist/types/tools/index.d.ts +8 -3
  34. package/dist/types/tools/output-meta.d.ts +5 -0
  35. package/dist/types/tools/read.d.ts +9 -1
  36. package/dist/types/tools/xdev.d.ts +53 -67
  37. package/dist/types/utils/cpuprofile.d.ts +51 -0
  38. package/dist/types/utils/inspect-image-mode.d.ts +29 -0
  39. package/dist/types/utils/profile-tree.d.ts +47 -0
  40. package/dist/types/utils/sample-profile.d.ts +67 -0
  41. package/dist/types/utils/title-generator.d.ts +17 -16
  42. package/package.json +12 -12
  43. package/src/capability/index.ts +43 -12
  44. package/src/capability/mcp.ts +21 -0
  45. package/src/capability/types.ts +20 -1
  46. package/src/cli/read-cli.ts +44 -2
  47. package/src/config/settings-schema.ts +42 -2
  48. package/src/config/settings.ts +35 -0
  49. package/src/cursor.ts +4 -3
  50. package/src/discovery/builtin-rules/index.ts +2 -0
  51. package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
  52. package/src/eval/py/runner.py +16 -2
  53. package/src/extensibility/extensions/runner.ts +117 -5
  54. package/src/extensibility/extensions/types.ts +0 -1
  55. package/src/extensibility/extensions/wrapper.ts +74 -42
  56. package/src/extensibility/hooks/tool-wrapper.ts +11 -4
  57. package/src/extensibility/hooks/types.ts +0 -1
  58. package/src/extensibility/shared-events.ts +18 -1
  59. package/src/internal-urls/mcp-protocol.ts +17 -3
  60. package/src/internal-urls/parse.ts +31 -0
  61. package/src/internal-urls/router.ts +24 -4
  62. package/src/internal-urls/types.ts +6 -0
  63. package/src/live/transport.ts +2 -2
  64. package/src/lsp/client.ts +2 -2
  65. package/src/lsp/config.ts +4 -0
  66. package/src/lsp/types.ts +2 -0
  67. package/src/mcp/config.ts +26 -14
  68. package/src/mcp/manager.ts +26 -9
  69. package/src/mcp/tool-bridge.ts +52 -1
  70. package/src/memories/index.ts +25 -6
  71. package/src/modes/components/custom-editor.ts +39 -16
  72. package/src/modes/components/status-line/segments.ts +3 -1
  73. package/src/modes/components/tips.txt +2 -1
  74. package/src/modes/components/tool-execution.ts +8 -7
  75. package/src/modes/controllers/command-controller.ts +10 -10
  76. package/src/modes/controllers/extension-ui-controller.ts +7 -7
  77. package/src/modes/controllers/selector-controller.ts +7 -2
  78. package/src/modes/rpc/rpc-mode.ts +60 -47
  79. package/src/prompts/steering/user-interjection.md +2 -5
  80. package/src/prompts/tools/task.md +4 -2
  81. package/src/sdk.ts +64 -58
  82. package/src/session/agent-session-types.ts +8 -5
  83. package/src/session/agent-session.ts +144 -11
  84. package/src/session/model-controls.ts +48 -12
  85. package/src/session/session-advisors.ts +30 -14
  86. package/src/session/session-listing.ts +66 -4
  87. package/src/session/session-tools.ts +162 -54
  88. package/src/session/streaming-output.ts +18 -6
  89. package/src/session/tool-choice-queue.ts +19 -4
  90. package/src/session/turn-recovery.ts +25 -6
  91. package/src/slash-commands/builtin-registry.ts +69 -0
  92. package/src/task/executor.ts +11 -3
  93. package/src/task/index.ts +43 -32
  94. package/src/task/types.ts +12 -17
  95. package/src/thinking.ts +68 -5
  96. package/src/tools/bash.ts +16 -9
  97. package/src/tools/index.ts +36 -16
  98. package/src/tools/output-meta.ts +20 -0
  99. package/src/tools/read.ts +89 -15
  100. package/src/tools/write.ts +16 -7
  101. package/src/tools/xdev.ts +198 -210
  102. package/src/utils/cpuprofile.ts +235 -0
  103. package/src/utils/inspect-image-mode.ts +39 -0
  104. package/src/utils/profile-tree.ts +111 -0
  105. package/src/utils/sample-profile.ts +437 -0
  106. package/src/utils/title-generator.ts +88 -34
  107. package/dist/types/advisor/__tests__/advisor.test.d.ts +0 -1
  108. package/dist/types/advisor/__tests__/config.test.d.ts +0 -1
  109. package/dist/types/advisor/__tests__/emission-guard.test.d.ts +0 -1
  110. package/dist/types/cli/__tests__/auth-gateway-catalog.test.d.ts +0 -1
  111. package/dist/types/cli/update-cli.test.d.ts +0 -1
  112. package/dist/types/config/__tests__/model-registry.test.d.ts +0 -1
  113. package/dist/types/eval/__tests__/agent-bridge.test.d.ts +0 -1
  114. package/dist/types/eval/__tests__/bridge-timeout.test.d.ts +0 -1
  115. package/dist/types/eval/__tests__/budget-bridge.test.d.ts +0 -1
  116. package/dist/types/eval/__tests__/completion-bridge.test.d.ts +0 -1
  117. package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +0 -1
  118. package/dist/types/eval/__tests__/idle-timeout.test.d.ts +0 -1
  119. package/dist/types/eval/__tests__/js-context-manager.test.d.ts +0 -1
  120. package/dist/types/eval/__tests__/julia-prelude.test.d.ts +0 -1
  121. package/dist/types/eval/__tests__/kernel-spawn.test.d.ts +0 -1
  122. package/dist/types/eval/__tests__/prelude-agent.test.d.ts +0 -1
  123. package/dist/types/eval/__tests__/process-entry-import.test.d.ts +0 -1
  124. package/dist/types/eval/py/__tests__/prelude.test.d.ts +0 -1
  125. package/dist/types/eval/py/__tests__/runner-shell-output.test.d.ts +0 -1
  126. package/dist/types/hindsight/client.test.d.ts +0 -1
  127. package/dist/types/internal-urls/__tests__/agent-protocol-nested.test.d.ts +0 -1
  128. package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +0 -1
  129. package/dist/types/launch/broker-list-order.test.d.ts +0 -1
  130. package/dist/types/launch/broker-output-snapshot.test.d.ts +0 -1
  131. package/dist/types/launch/protocol.test.d.ts +0 -1
  132. package/dist/types/launch/spawn-options.test.d.ts +0 -1
  133. package/dist/types/launch/terminal-output.test.d.ts +0 -1
  134. package/dist/types/live/protocol.test.d.ts +0 -1
  135. package/dist/types/mcp/config-writer.test.d.ts +0 -1
  136. package/dist/types/mcp/smithery-auth.test.d.ts +0 -1
  137. package/dist/types/mcp/smithery-registry.test.d.ts +0 -1
  138. package/dist/types/mcp/transports/stdio.test.d.ts +0 -1
  139. package/dist/types/modes/components/__tests__/dynamic-border.test.d.ts +0 -1
  140. package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +0 -1
  141. package/dist/types/modes/components/__tests__/pause-screen.test.d.ts +0 -1
  142. package/dist/types/modes/components/__tests__/skill-message.test.d.ts +0 -1
  143. package/dist/types/modes/components/custom-editor-plugin-ctor.test.d.ts +0 -1
  144. package/dist/types/modes/components/custom-editor.test.d.ts +0 -1
  145. package/dist/types/modes/components/login-dialog.test.d.ts +0 -1
  146. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +0 -1
  147. package/dist/types/modes/components/status-line/component.test.d.ts +0 -1
  148. package/dist/types/modes/components/tool-execution.test.d.ts +0 -1
  149. package/dist/types/modes/controllers/extension-ui-controller.test.d.ts +0 -1
  150. package/dist/types/modes/noninteractive-dispose.test.d.ts +0 -1
  151. package/dist/types/modes/print-mode.test.d.ts +0 -1
  152. package/dist/types/modes/session-teardown.test.d.ts +0 -1
  153. package/dist/types/modes/theme/mermaid-rendering.test.d.ts +0 -1
  154. package/dist/types/modes/utils/transcript-render-helpers.test.d.ts +0 -1
  155. package/dist/types/modes/warp-events.test.d.ts +0 -1
  156. package/dist/types/plan-mode/approved-plan-prompt.test.d.ts +0 -1
  157. package/dist/types/plan-mode/model-transition.test.d.ts +0 -1
  158. package/dist/types/plan-mode/reentry-prompt.test.d.ts +0 -1
  159. package/dist/types/session/agent-session-error-log.test.d.ts +0 -1
  160. package/dist/types/session/blob-store.test.d.ts +0 -1
  161. package/dist/types/session/messages.test.d.ts +0 -1
  162. package/dist/types/session/session-context.test.d.ts +0 -1
  163. package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +0 -1
  164. package/dist/types/ssh/__tests__/connection-manager-timeout.test.d.ts +0 -1
  165. package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +0 -1
  166. package/dist/types/ssh/__tests__/sshfs-mount.test.d.ts +0 -1
  167. package/dist/types/system-prompt.test.d.ts +0 -1
  168. package/dist/types/task/render.test.d.ts +0 -1
  169. package/dist/types/task/spawn-policy.test.d.ts +0 -1
  170. package/dist/types/tools/__tests__/eval-description.test.d.ts +0 -1
  171. package/dist/types/tools/__tests__/glob.test.d.ts +0 -1
  172. package/dist/types/tools/__tests__/json-tree.test.d.ts +0 -1
  173. package/dist/types/tools/__tests__/vibe-render.test.d.ts +0 -1
  174. package/dist/types/tools/hub/launch-compat.test.d.ts +0 -1
  175. package/dist/types/vibe/__tests__/token-rate.test.d.ts +0 -1
  176. package/src/advisor/__tests__/advisor.test.ts +0 -4889
  177. package/src/advisor/__tests__/config.test.ts +0 -349
  178. package/src/advisor/__tests__/emission-guard.test.ts +0 -147
  179. package/src/cli/__tests__/auth-gateway-catalog.test.ts +0 -111
  180. package/src/cli/update-cli.test.ts +0 -28
  181. package/src/config/__tests__/model-registry.test.ts +0 -182
  182. package/src/eval/__tests__/agent-bridge.test.ts +0 -1509
  183. package/src/eval/__tests__/bridge-timeout.test.ts +0 -170
  184. package/src/eval/__tests__/budget-bridge.test.ts +0 -80
  185. package/src/eval/__tests__/completion-bridge.test.ts +0 -412
  186. package/src/eval/__tests__/helpers-local-roots.test.ts +0 -55
  187. package/src/eval/__tests__/idle-timeout.test.ts +0 -80
  188. package/src/eval/__tests__/js-context-manager.test.ts +0 -456
  189. package/src/eval/__tests__/julia-prelude.test.ts +0 -66
  190. package/src/eval/__tests__/kernel-spawn.test.ts +0 -115
  191. package/src/eval/__tests__/prelude-agent.test.ts +0 -156
  192. package/src/eval/__tests__/process-entry-import.test.ts +0 -137
  193. package/src/eval/py/__tests__/prelude.test.ts +0 -104
  194. package/src/eval/py/__tests__/runner-shell-output.test.ts +0 -157
  195. package/src/hindsight/client.test.ts +0 -75
  196. package/src/internal-urls/__tests__/agent-protocol-nested.test.ts +0 -141
  197. package/src/internal-urls/__tests__/ssh-protocol.test.ts +0 -331
  198. package/src/launch/broker-list-order.test.ts +0 -89
  199. package/src/launch/broker-output-snapshot.test.ts +0 -126
  200. package/src/launch/protocol.test.ts +0 -59
  201. package/src/launch/spawn-options.test.ts +0 -31
  202. package/src/launch/terminal-output.test.ts +0 -107
  203. package/src/live/protocol.test.ts +0 -140
  204. package/src/mcp/config-writer.test.ts +0 -43
  205. package/src/mcp/smithery-auth.test.ts +0 -29
  206. package/src/mcp/smithery-registry.test.ts +0 -51
  207. package/src/mcp/transports/stdio.test.ts +0 -427
  208. package/src/modes/components/__tests__/dynamic-border.test.ts +0 -55
  209. package/src/modes/components/__tests__/move-overlay.test.ts +0 -252
  210. package/src/modes/components/__tests__/pause-screen.test.ts +0 -143
  211. package/src/modes/components/__tests__/skill-message.test.ts +0 -94
  212. package/src/modes/components/custom-editor-plugin-ctor.test.ts +0 -36
  213. package/src/modes/components/custom-editor.test.ts +0 -510
  214. package/src/modes/components/login-dialog.test.ts +0 -56
  215. package/src/modes/components/status-line/component.jj-cache.test.ts +0 -229
  216. package/src/modes/components/status-line/component.test.ts +0 -84
  217. package/src/modes/components/tool-execution.test.ts +0 -162
  218. package/src/modes/controllers/extension-ui-controller.test.ts +0 -250
  219. package/src/modes/noninteractive-dispose.test.ts +0 -73
  220. package/src/modes/print-mode.test.ts +0 -71
  221. package/src/modes/session-teardown.test.ts +0 -219
  222. package/src/modes/theme/mermaid-rendering.test.ts +0 -53
  223. package/src/modes/utils/transcript-render-helpers.test.ts +0 -38
  224. package/src/modes/warp-events.test.ts +0 -794
  225. package/src/plan-mode/approved-plan-prompt.test.ts +0 -36
  226. package/src/plan-mode/model-transition.test.ts +0 -60
  227. package/src/plan-mode/reentry-prompt.test.ts +0 -41
  228. package/src/session/agent-session-error-log.test.ts +0 -59
  229. package/src/session/blob-store.test.ts +0 -56
  230. package/src/session/messages.test.ts +0 -282
  231. package/src/session/session-context.test.ts +0 -384
  232. package/src/ssh/__tests__/connection-manager-args.test.ts +0 -191
  233. package/src/ssh/__tests__/connection-manager-timeout.test.ts +0 -61
  234. package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +0 -105
  235. package/src/ssh/__tests__/sshfs-mount.test.ts +0 -13
  236. package/src/system-prompt.test.ts +0 -236
  237. package/src/task/render.test.ts +0 -290
  238. package/src/task/spawn-policy.test.ts +0 -62
  239. package/src/tools/__tests__/eval-description.test.ts +0 -18
  240. package/src/tools/__tests__/glob.test.ts +0 -37
  241. package/src/tools/__tests__/json-tree.test.ts +0 -35
  242. package/src/tools/__tests__/vibe-render.test.ts +0 -210
  243. package/src/tools/hub/launch-compat.test.ts +0 -40
  244. package/src/vibe/__tests__/token-rate.test.ts +0 -96
@@ -0,0 +1,437 @@
1
+ /**
2
+ * Parser and renderer for macOS `/usr/bin/sample` call-tree reports
3
+ * (conventionally saved as `*.sample.txt`).
4
+ *
5
+ * The raw report is a 10k+ line ASCII call tree with mangled symbols —
6
+ * expensive for an agent to digest. `renderSampleProfile` converts it into a
7
+ * compact bottleneck summary:
8
+ *
9
+ * - per-thread hot paths, pruned to frames with meaningful on-CPU samples,
10
+ * with pass-through chains collapsed and direct recursion flattened
11
+ * - blocked/idle threads reduced to a one-line classification
12
+ * - a process-wide "top functions by self samples" table
13
+ * - Rust v0 and legacy symbols demangled (best-effort, path extraction)
14
+ *
15
+ * Consumed by the read tool: `*.sample.txt` reads show the summary, `:raw`
16
+ * returns the original bytes.
17
+ */
18
+
19
+ import { formatPct, mergeInto, type ProfileNode, type RenderTreeContext, renderProfileNode } from "./profile-tree";
20
+
21
+ /** Matches paths the read tool should treat as macOS sample reports. */
22
+ export function isSampleProfilePath(filePath: string): boolean {
23
+ return /\.sample\.txt$/i.test(filePath);
24
+ }
25
+
26
+ /** Leaf symbols that mean "thread is parked in the kernel", not burning CPU. */
27
+ const WAIT_SYMBOLS: Record<string, true> = {
28
+ __accept: true,
29
+ __psynch_cvwait: true,
30
+ __psynch_mutexwait: true,
31
+ __psynch_rw_rdlock: true,
32
+ __psynch_rw_wrlock: true,
33
+ __recvfrom: true,
34
+ __select: true,
35
+ __semwait_signal: true,
36
+ __sigwait: true,
37
+ __ulock_wait: true,
38
+ __ulock_wait2: true,
39
+ __wait4: true,
40
+ __workq_kernreturn: true,
41
+ kevent: true,
42
+ kevent64: true,
43
+ kevent_qos: true,
44
+ mach_msg2_trap: true,
45
+ mach_msg_trap: true,
46
+ poll: true,
47
+ semaphore_timedwait_trap: true,
48
+ semaphore_wait_trap: true,
49
+ start_wqthread: true,
50
+ swtch_pri: true,
51
+ thread_suspend: true,
52
+ usleep: true,
53
+ };
54
+
55
+ /** One frame in the sampled call tree. Counts are sample hits (subtree total). */
56
+ export interface SampleFrame {
57
+ count: number;
58
+ symbol: string;
59
+ module?: string;
60
+ children: SampleFrame[];
61
+ }
62
+
63
+ /** One sampled thread: `Thread_<id>` root plus its call tree. */
64
+ export interface SampleThread {
65
+ id: string;
66
+ name?: string;
67
+ total: number;
68
+ roots: SampleFrame[];
69
+ }
70
+
71
+ /** Metadata from the report preamble (everything before `Call graph:`). */
72
+ export interface SampleProfileHeader {
73
+ process: string;
74
+ pid: number;
75
+ intervalMs: number;
76
+ path?: string;
77
+ codeType?: string;
78
+ osVersion?: string;
79
+ footprint?: string;
80
+ footprintPeak?: string;
81
+ }
82
+
83
+ /** Parsed macOS sample report. */
84
+ export interface SampleProfile {
85
+ header: SampleProfileHeader;
86
+ threads: SampleThread[];
87
+ }
88
+
89
+ const ANALYSIS_RE = /^Analysis of sampling (.+?) \(pid (\d+)\) every (\d+) milliseconds?/;
90
+ const THREAD_RE = /^Thread_([^\s:]+):?\s*(.*)$/;
91
+
92
+ /**
93
+ * Parse a macOS `sample` report. Returns null when the text does not look
94
+ * like sample output (missing analysis preamble or `Call graph:` section).
95
+ */
96
+ export function parseSampleProfile(text: string): SampleProfile | null {
97
+ const lines = text.split("\n");
98
+ const analysis = ANALYSIS_RE.exec(lines[0] ?? "");
99
+ if (!analysis) return null;
100
+ const callGraphIx = lines.indexOf("Call graph:");
101
+ if (callGraphIx === -1) return null;
102
+
103
+ const header: SampleProfileHeader = {
104
+ process: analysis[1],
105
+ pid: Number(analysis[2]),
106
+ intervalMs: Number(analysis[3]),
107
+ };
108
+ for (const line of lines.slice(1, callGraphIx)) {
109
+ const kv = /^([A-Za-z/ ()]+?):\s+(.*)$/.exec(line);
110
+ if (!kv) continue;
111
+ const value = kv[2].trim();
112
+ switch (kv[1]) {
113
+ case "Path":
114
+ header.path = value;
115
+ break;
116
+ case "Code Type":
117
+ header.codeType = value;
118
+ break;
119
+ case "OS Version":
120
+ header.osVersion = value;
121
+ break;
122
+ case "Physical footprint":
123
+ header.footprint = value;
124
+ break;
125
+ case "Physical footprint (peak)":
126
+ header.footprintPeak = value;
127
+ break;
128
+ }
129
+ }
130
+
131
+ const threads: SampleThread[] = [];
132
+ let thread: SampleThread | undefined;
133
+ // Stack of (depth, frame) for the current thread; depth 1 = thread root frame.
134
+ const stack: Array<{ depth: number; frame: SampleFrame }> = [];
135
+
136
+ for (let ix = callGraphIx + 1; ix < lines.length; ix++) {
137
+ const line = lines[ix];
138
+ if (/^(Total number in stack|Sort by top of stack|Binary Images:)/.test(line)) break;
139
+ if (!line.startsWith(" ")) continue;
140
+ const body = line.slice(4);
141
+ // Decorators are one char + one space per tree level (`+ ! : | `), so the
142
+ // sample count starts at an even offset; scan pairs until the first digit.
143
+ let p = 0;
144
+ while (p < body.length && (body[p] < "0" || body[p] > "9")) p += 2;
145
+ if (p >= body.length) continue;
146
+ const depth = p / 2;
147
+ const rest = /^(\d+)\s+(.*)$/.exec(body.slice(p));
148
+ if (!rest) continue;
149
+ const count = Number(rest[1]);
150
+ const frameText = rest[2];
151
+
152
+ if (depth === 0) {
153
+ const tm = THREAD_RE.exec(frameText);
154
+ if (!tm) continue;
155
+ const name = tm[2].trim().replace(/\s+/g, " ");
156
+ thread = { id: tm[1], name: name || undefined, total: count, roots: [] };
157
+ threads.push(thread);
158
+ stack.length = 0;
159
+ continue;
160
+ }
161
+ if (!thread) continue;
162
+ const frame: SampleFrame = { count, ...parseFrameText(frameText), children: [] };
163
+ while (stack.length > 0 && stack[stack.length - 1].depth >= depth) stack.pop();
164
+ const siblings = stack.length > 0 ? stack[stack.length - 1].frame.children : thread.roots;
165
+ siblings.push(frame);
166
+ stack.push({ depth, frame });
167
+ }
168
+
169
+ if (threads.length === 0) return null;
170
+ return { header, threads };
171
+ }
172
+
173
+ /** Split `symbol (in module) + offsets [addrs]` into symbol + module. */
174
+ function parseFrameText(text: string): { symbol: string; module?: string } {
175
+ let s = text;
176
+ const addrIx = s.lastIndexOf(" [");
177
+ if (addrIx !== -1 && s.endsWith("]")) s = s.slice(0, addrIx);
178
+ s = s.replace(/ \+ [0-9][0-9,.]*$/, "");
179
+ const modIx = s.lastIndexOf(" (in ");
180
+ if (modIx !== -1 && s.endsWith(")")) {
181
+ return { symbol: s.slice(0, modIx).trim(), module: s.slice(modIx + 6, -1) };
182
+ }
183
+ return { symbol: s.trim() };
184
+ }
185
+
186
+ // ---------------------------------------------------------------------------
187
+ // Demangling
188
+ // ---------------------------------------------------------------------------
189
+
190
+ const LEGACY_ESCAPES: ReadonlyArray<[string, string]> = [
191
+ ["$LT$", "<"],
192
+ ["$GT$", ">"],
193
+ ["$RF$", "&"],
194
+ ["$BP$", "*"],
195
+ ["$C$", ","],
196
+ ["$u20$", " "],
197
+ ["$u27$", "'"],
198
+ ["$u7b$", "{"],
199
+ ["$u7d$", "}"],
200
+ ["..", "::"],
201
+ ];
202
+
203
+ const IDENT_RE = /^[A-Za-z0-9_.$]+$/;
204
+ const LEGACY_HASH_RE = /^h[0-9a-f]{16}$/;
205
+
206
+ /**
207
+ * Best-effort demangle of Rust symbols (v0 `_R…` and legacy `_ZN…E`).
208
+ * For v0 this is a path extractor, not a full demangler: identifiers are
209
+ * pulled out in order and joined with `::`, so generic arguments appear as
210
+ * extra path segments. Non-Rust symbols pass through unchanged.
211
+ */
212
+ export function demangleSymbol(raw: string): string {
213
+ if (raw.startsWith("_R")) return demangleV0(raw) ?? raw;
214
+ const legacy = /^_?_ZN(.*)$/.exec(raw);
215
+ if (legacy) return demangleLegacy(legacy[1]) ?? raw;
216
+ return raw;
217
+ }
218
+
219
+ function demangleV0(raw: string): string | null {
220
+ const s = raw.slice(2);
221
+ const parts: string[] = [];
222
+ let i = 0;
223
+ while (i < s.length) {
224
+ const ch = s[i];
225
+ if (ch >= "1" && ch <= "9") {
226
+ let j = i;
227
+ while (j < s.length && s[j] >= "0" && s[j] <= "9") j++;
228
+ const len = Number(s.slice(i, j));
229
+ // v0 inserts a `_` separator when the identifier starts with a digit or `_`.
230
+ let k = j;
231
+ if (s[k] === "_") k++;
232
+ const ident = s.slice(k, k + len);
233
+ if (ident.length === len && IDENT_RE.test(ident)) {
234
+ parts.push(ident);
235
+ i = k + len;
236
+ } else {
237
+ i = j;
238
+ }
239
+ } else if (ch === "s" || ch === "B") {
240
+ // Disambiguator (`s<base62>_`) or backref (`B<base62>_`): skip.
241
+ const m = /^[sB][0-9a-zA-Z]*_/.exec(s.slice(i));
242
+ i += m ? m[0].length : 1;
243
+ } else {
244
+ i++;
245
+ }
246
+ }
247
+ return parts.length > 0 ? parts.join("::") : null;
248
+ }
249
+
250
+ function demangleLegacy(s: string): string | null {
251
+ const parts: string[] = [];
252
+ let i = 0;
253
+ while (i < s.length && s[i] >= "0" && s[i] <= "9") {
254
+ let j = i;
255
+ while (j < s.length && s[j] >= "0" && s[j] <= "9") j++;
256
+ const len = Number(s.slice(i, j));
257
+ const ident = s.slice(j, j + len);
258
+ if (ident.length !== len) break;
259
+ if (!LEGACY_HASH_RE.test(ident)) parts.push(unescapeLegacy(ident));
260
+ i = j + len;
261
+ }
262
+ return parts.length > 0 ? parts.join("::") : null;
263
+ }
264
+
265
+ function unescapeLegacy(ident: string): string {
266
+ let out = ident;
267
+ for (const [from, to] of LEGACY_ESCAPES) out = out.replaceAll(from, to);
268
+ return out;
269
+ }
270
+
271
+ // ---------------------------------------------------------------------------
272
+ // Rendering
273
+ // ---------------------------------------------------------------------------
274
+
275
+ /** Fraction of a thread's on-CPU samples a subtree needs to stay visible. */
276
+ const PRUNE_FRACTION = 0.02;
277
+ /** Threads with fewer on-CPU samples than this share of their total are "idle". */
278
+ const IDLE_FRACTION = 0.002;
279
+ const IDLE_MIN_SAMPLES = 10;
280
+ const TOP_FUNCTIONS = 20;
281
+
282
+ function selfOf(frame: SampleFrame): number {
283
+ let children = 0;
284
+ for (const child of frame.children) children += child.count;
285
+ return Math.max(0, frame.count - children);
286
+ }
287
+
288
+ /**
289
+ * Build a display node for a frame subtree: demangled label, on-CPU value
290
+ * (self samples of non-wait frames), same-symbol siblings merged (`sample`
291
+ * splits them by call-site offset, which only fragments hot totals).
292
+ */
293
+ function buildProfileNode(frame: SampleFrame, demangleCache: Map<string, string>, mainModule: string): ProfileNode {
294
+ let symbol = demangleCache.get(frame.symbol);
295
+ if (symbol === undefined) {
296
+ symbol = demangleSymbol(frame.symbol);
297
+ demangleCache.set(frame.symbol, symbol);
298
+ }
299
+ const children: ProfileNode[] = [];
300
+ for (const rawChild of frame.children) {
301
+ const child = buildProfileNode(rawChild, demangleCache, mainModule);
302
+ const existing = children.find(c => c.key === child.key);
303
+ if (existing) mergeInto(existing, child);
304
+ else children.push(child);
305
+ }
306
+ let cpu = WAIT_SYMBOLS[frame.symbol] ? 0 : selfOf(frame);
307
+ for (const child of children) cpu += child.value;
308
+ const label = frame.module && frame.module !== mainModule ? `${symbol} (${frame.module})` : symbol;
309
+ return { key: symbol, label, value: cpu, recursion: 0, children };
310
+ }
311
+
312
+ /** Aggregate on-CPU self samples per demangled symbol across all threads. */
313
+ function aggregateSelf(
314
+ profile: SampleProfile,
315
+ demangleCache: Map<string, string>,
316
+ ): Map<string, { cpu: number; module?: string }> {
317
+ const totals = new Map<string, { cpu: number; module?: string }>();
318
+ const visit = (frame: SampleFrame): void => {
319
+ if (!WAIT_SYMBOLS[frame.symbol]) {
320
+ const self = selfOf(frame);
321
+ if (self > 0) {
322
+ let symbol = demangleCache.get(frame.symbol);
323
+ if (symbol === undefined) {
324
+ symbol = demangleSymbol(frame.symbol);
325
+ demangleCache.set(frame.symbol, symbol);
326
+ }
327
+ const entry = totals.get(symbol);
328
+ if (entry) entry.cpu += self;
329
+ else totals.set(symbol, { cpu: self, module: frame.module });
330
+ }
331
+ }
332
+ for (const child of frame.children) visit(child);
333
+ };
334
+ for (const thread of profile.threads) for (const root of thread.roots) visit(root);
335
+ return totals;
336
+ }
337
+
338
+ /** Dominant wait leaf of an idle thread ("what is it blocked on"). */
339
+ function dominantWait(thread: SampleThread): string | undefined {
340
+ let best: { symbol: string; count: number } | undefined;
341
+ const visit = (frame: SampleFrame): void => {
342
+ if (frame.children.length === 0 && WAIT_SYMBOLS[frame.symbol]) {
343
+ if (!best || frame.count > best.count) best = { symbol: frame.symbol, count: frame.count };
344
+ }
345
+ for (const child of frame.children) visit(child);
346
+ };
347
+ for (const root of thread.roots) visit(root);
348
+ return best?.symbol;
349
+ }
350
+
351
+ /**
352
+ * Render a macOS sample report as an agent-friendly bottleneck summary.
353
+ * Returns null when `text` is not sample output (caller falls back to the
354
+ * plain-text path).
355
+ */
356
+ export function renderSampleProfile(text: string): string | null {
357
+ const profile = parseSampleProfile(text);
358
+ if (!profile) return null;
359
+ const { header, threads } = profile;
360
+ const demangleCache = new Map<string, string>();
361
+
362
+ const annotated = threads.map(thread => {
363
+ const roots = thread.roots.map(root => buildProfileNode(root, demangleCache, header.process));
364
+ let cpu = 0;
365
+ for (const root of roots) cpu += root.value;
366
+ return { thread, roots, cpu };
367
+ });
368
+ const processCpu = annotated.reduce((sum, t) => sum + t.cpu, 0);
369
+ const maxSamples = threads.reduce((max, t) => Math.max(max, t.total), 0);
370
+
371
+ const out: string[] = [];
372
+ out.push(`macOS sample profile: ${header.process} (pid ${header.pid}), sampled every ${header.intervalMs} ms`);
373
+ const meta: string[] = [];
374
+ if (header.path) meta.push(header.path);
375
+ if (header.codeType) meta.push(header.codeType);
376
+ if (header.osVersion) meta.push(`macOS ${header.osVersion.replace(/^macOS\s+/, "")}`);
377
+ if (meta.length > 0) out.push(meta.join(" | "));
378
+ const durationSec = (maxSamples * header.intervalMs) / 1000;
379
+ let statLine = `Duration: ~${durationSec.toFixed(1)} s (${maxSamples} samples/thread)`;
380
+ if (header.footprint) {
381
+ statLine += ` | Footprint: ${header.footprint}${header.footprintPeak ? ` (peak ${header.footprintPeak})` : ""}`;
382
+ }
383
+ out.push(statLine);
384
+ out.push("");
385
+ out.push(
386
+ `Process total: ${processCpu} on-CPU samples across ${threads.length} threads. Counts and percentages below are on-CPU samples (blocked time excluded).`,
387
+ );
388
+
389
+ const active: typeof annotated = [];
390
+ const idle: typeof annotated = [];
391
+ for (const entry of annotated) {
392
+ const threshold = Math.max(IDLE_MIN_SAMPLES, entry.thread.total * IDLE_FRACTION);
393
+ (entry.cpu >= threshold ? active : idle).push(entry);
394
+ }
395
+ active.sort((a, b) => b.cpu - a.cpu);
396
+
397
+ for (const { thread, roots, cpu } of active) {
398
+ out.push("");
399
+ const title = thread.name ? `${thread.name} (Thread_${thread.id})` : `Thread_${thread.id}`;
400
+ out.push(`## ${title} — ${thread.total} samples, ${cpu} on-CPU (${formatPct(cpu, thread.total)})`);
401
+ const ctx: RenderTreeContext = {
402
+ out,
403
+ total: cpu,
404
+ minValue: Math.max(3, Math.round(cpu * PRUNE_FRACTION)),
405
+ formatValue: String,
406
+ valueWidth: 6,
407
+ };
408
+ const kept = roots.filter(root => root.value >= ctx.minValue).sort((a, b) => b.value - a.value);
409
+ for (const root of kept) renderProfileNode(root, 0, ctx);
410
+ if (kept.length === 0) out.push(` (no call path above ${ctx.minValue} on-CPU samples)`);
411
+ }
412
+
413
+ if (idle.length > 0) {
414
+ out.push("");
415
+ out.push(`## Idle / negligible threads (${idle.length})`);
416
+ for (const { thread, cpu } of idle) {
417
+ const title = thread.name ? `${thread.name} (Thread_${thread.id})` : `Thread_${thread.id}`;
418
+ const wait = dominantWait(thread);
419
+ const state = wait ? `blocked in ${wait} (${cpu} on-CPU)` : `${cpu}/${thread.total} samples on-CPU`;
420
+ out.push(`- ${title}: ${state}`);
421
+ }
422
+ }
423
+
424
+ const totals = [...aggregateSelf(profile, demangleCache).entries()].sort((a, b) => b[1].cpu - a[1].cpu);
425
+ if (totals.length > 0) {
426
+ out.push("");
427
+ out.push("## Top functions by self samples (process-wide, blocked time excluded)");
428
+ for (const [symbol, { cpu, module }] of totals.slice(0, TOP_FUNCTIONS)) {
429
+ const suffix = module && module !== header.process ? ` (${module})` : "";
430
+ out.push(`${String(cpu).padStart(6)} ${formatPct(cpu, processCpu).padStart(6)} ${symbol}${suffix}`);
431
+ }
432
+ }
433
+
434
+ out.push("");
435
+ out.push("[Summarized view of a macOS `sample` call-tree report. Use ':raw' to read the original file.]");
436
+ return out.join("\n");
437
+ }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Generate session titles using a smol, fast model.
3
3
  */
4
+ import { dlopen, FFIType, ptr } from "bun:ffi";
4
5
  import * as path from "node:path";
5
6
 
6
7
  import { type Api, type AssistantMessage, completeSimple, type Model } from "@oh-my-pi/pi-ai";
@@ -23,6 +24,59 @@ const TITLE_MARKER_INSTRUCTION = prompt.render(titleMarkerInstruction);
23
24
  const DEFAULT_TERMINAL_TITLE = "π";
24
25
  const TERMINAL_TITLE_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
25
26
 
27
+ interface WindowsConsoleTitleApi {
28
+ set(title: string): boolean;
29
+ close(): void;
30
+ }
31
+
32
+ let windowsConsoleTitleApi: WindowsConsoleTitleApi | null | undefined;
33
+ let lastTerminalTitle: string | undefined;
34
+
35
+ function getWindowsConsoleTitleApi(): WindowsConsoleTitleApi | null {
36
+ if (process.platform !== "win32") return null;
37
+ if (windowsConsoleTitleApi !== undefined) return windowsConsoleTitleApi;
38
+ try {
39
+ const kernel32 = dlopen("kernel32.dll", {
40
+ SetConsoleTitleW: { args: [FFIType.ptr], returns: FFIType.bool },
41
+ });
42
+ windowsConsoleTitleApi = {
43
+ set(title) {
44
+ const wideTitle = Buffer.from(`${title}\0`, "utf16le");
45
+ return kernel32.symbols.SetConsoleTitleW(ptr(wideTitle));
46
+ },
47
+ close: () => kernel32.close(),
48
+ };
49
+ } catch {
50
+ windowsConsoleTitleApi = null;
51
+ }
52
+ return windowsConsoleTitleApi;
53
+ }
54
+
55
+ function setWindowsConsoleTitle(title: string): boolean {
56
+ const api = getWindowsConsoleTitleApi();
57
+ if (!api) return false;
58
+ try {
59
+ return api.set(title);
60
+ } catch {
61
+ try {
62
+ api.close();
63
+ } catch {
64
+ // Ignore cleanup failures after the native title path has already failed.
65
+ }
66
+ windowsConsoleTitleApi = null;
67
+ return false;
68
+ }
69
+ }
70
+
71
+ function disposeWindowsConsoleTitleApi(): void {
72
+ try {
73
+ windowsConsoleTitleApi?.close();
74
+ } catch {
75
+ // Terminal teardown must remain best-effort.
76
+ }
77
+ windowsConsoleTitleApi = undefined;
78
+ }
79
+
26
80
  // Cover the "backend ignores `disableReasoning`" case unconditionally: the
27
81
  // static `model.reasoning` catalog flag can't distinguish a thinking model that
28
82
  // was declared with `reasoning: false` (e.g. Qwen3 served locally via llama.cpp,
@@ -379,11 +433,16 @@ export function formatSessionTerminalTitle(sessionName: string | undefined, cwd?
379
433
  }
380
434
 
381
435
  /**
382
- * Set the terminal title using OSC 0 (sets both tab and window title). Unsupported terminals ignore it.
436
+ * Set the terminal title through the native Win32 API or OSC 0.
437
+ *
438
+ * Repeating the same sanitized title is a no-op on every platform.
383
439
  */
384
440
  export function setTerminalTitle(title: string): void {
385
441
  if (!process.stdout.isTTY || isTerminalHeadless()) return;
386
- process.stdout.write(`\x1b]0;${sanitizeTerminalTitlePart(title) ?? DEFAULT_TERMINAL_TITLE}\x07`);
442
+ const next = sanitizeTerminalTitlePart(title) ?? DEFAULT_TERMINAL_TITLE;
443
+ if (next === lastTerminalTitle) return;
444
+ if (!setWindowsConsoleTitle(next)) process.stdout.write(`\x1b]0;${next}\x07`);
445
+ lastTerminalTitle = next;
387
446
  }
388
447
 
389
448
  export function setSessionTerminalTitle(sessionName: string | undefined, cwd?: string): void {
@@ -396,9 +455,9 @@ export function setSessionTerminalTitle(sessionName: string | undefined, cwd?: s
396
455
 
397
456
  /**
398
457
  * Set a terminal title from an extension's `setTitle()`. Unlike the session base
399
- * title, this owns the terminal verbatim: the run-state separator will not rewrite
400
- * it on the next spinner tick or state change. Cleared when the app next sets an
401
- * authoritative session title via {@link setSessionTerminalTitle}.
458
+ * title, this owns the terminal verbatim: periodic and run-state updates will not
459
+ * rewrite it. Cleared when the app next sets an authoritative session title via
460
+ * {@link setSessionTerminalTitle}.
402
461
  */
403
462
  export function setExtensionTerminalTitle(title: string): void {
404
463
  terminalTitleRuntime.extensionOverride = title;
@@ -407,12 +466,9 @@ export function setExtensionTerminalTitle(title: string): void {
407
466
 
408
467
  export type TerminalTitleState = "idle" | "working" | "attention";
409
468
 
410
- /** Separator glyphs carrying the run state between the `π` brand and the session
411
- * label the brand itself stays bare (no prefix glyph). Spinner frames animate
412
- * the separator while working; they are self-contained (not the theme's symbol
413
- * set) to avoid a utils→modes import cycle; OSC titles render in tab/window bars
414
- * that handle Unicode. */
415
- const TITLE_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
469
+ /** Windows uses a static working separator instead of scheduling title animation. */
470
+ const WINDOWS_TITLE_WORKING_SEPARATOR = ":";
471
+ const TITLE_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
416
472
  const TITLE_SPINNER_INTERVAL_MS = 80;
417
473
  /** The user's turn: the title reads like a shell prompt awaiting input. */
418
474
  const TITLE_IDLE_SEPARATOR = ">";
@@ -425,7 +481,6 @@ const terminalTitleRuntime: {
425
481
  frame: number;
426
482
  enabled: boolean;
427
483
  timer: NodeJS.Timeout | undefined;
428
- lastEmitted: string | undefined;
429
484
  /** A title an extension set via `setTitle()`. While set, it owns the terminal
430
485
  * title verbatim: the run-state separator never rewrites it. Cleared when the
431
486
  * app next establishes an authoritative session title (rename, new session,
@@ -437,19 +492,16 @@ const terminalTitleRuntime: {
437
492
  frame: 0,
438
493
  enabled: true,
439
494
  timer: undefined,
440
- lastEmitted: undefined,
441
495
  extensionOverride: undefined,
442
496
  };
443
497
 
444
498
  /**
445
- * Compose the terminal title from the `π` brand, a state-carrying SEPARATOR, and
446
- * the session label. The brand never gains a prefix glyph the separator slot
447
- * expresses the run state instead. Pure (no I/O) so the state→separator contract
448
- * is unit-testable:
449
- * - `idle` (user's turn): > label` — reads like a prompt awaiting input;
450
- * - `working`: `π label` — spinner frames animate the separator;
451
- * - `attention`: `π ! label` — agent blocked on the user;
452
- * - disabled: `π: label` — the pre-state layout.
499
+ * Compose the terminal title from the `π` brand, a state-carrying separator, and
500
+ * the session label. Pure (no I/O) so the state→separator contract is testable:
501
+ * - `idle` (user's turn): `π > label`;
502
+ * - `working`: `π ⠋ label` (`π : label` on Windows);
503
+ * - `attention`: ! label`;
504
+ * - disabled: `π: label`.
453
505
  * Without a label the separator trails the brand (`π >`) so the state stays visible.
454
506
  */
455
507
  export function buildTerminalTitleWithState(
@@ -457,11 +509,14 @@ export function buildTerminalTitleWithState(
457
509
  state: TerminalTitleState,
458
510
  frame: number,
459
511
  enabled: boolean,
512
+ platform: NodeJS.Platform = process.platform,
460
513
  ): string {
461
514
  if (!enabled) return label ? `${DEFAULT_TERMINAL_TITLE}: ${label}` : DEFAULT_TERMINAL_TITLE;
462
515
  const separator =
463
516
  state === "working"
464
- ? TITLE_SPINNER_FRAMES[frame % TITLE_SPINNER_FRAMES.length]
517
+ ? platform === "win32"
518
+ ? WINDOWS_TITLE_WORKING_SEPARATOR
519
+ : TITLE_SPINNER_FRAMES[frame % TITLE_SPINNER_FRAMES.length]
465
520
  : state === "attention"
466
521
  ? TITLE_ATTENTION_SEPARATOR
467
522
  : TITLE_IDLE_SEPARATOR;
@@ -469,8 +524,8 @@ export function buildTerminalTitleWithState(
469
524
  }
470
525
 
471
526
  function emitTerminalTitle(): void {
472
- // An extension override owns the terminal verbatim; the run-state separator and
473
- // spinner ticks must not clobber it (still deduped via lastEmitted).
527
+ // An extension override owns the terminal verbatim; the terminal sink
528
+ // deduplicates repeated state updates.
474
529
  const next =
475
530
  terminalTitleRuntime.extensionOverride ??
476
531
  buildTerminalTitleWithState(
@@ -479,20 +534,16 @@ function emitTerminalTitle(): void {
479
534
  terminalTitleRuntime.frame,
480
535
  terminalTitleRuntime.enabled,
481
536
  );
482
- if (next === terminalTitleRuntime.lastEmitted) return;
483
- terminalTitleRuntime.lastEmitted = next;
484
537
  setTerminalTitle(next);
485
538
  }
486
539
 
487
540
  function stopTerminalTitleSpinner(): void {
488
- if (terminalTitleRuntime.timer) {
489
- clearInterval(terminalTitleRuntime.timer);
490
- terminalTitleRuntime.timer = undefined;
491
- }
541
+ clearInterval(terminalTitleRuntime.timer);
542
+ terminalTitleRuntime.timer = undefined;
492
543
  }
493
544
 
494
545
  function startTerminalTitleSpinner(): void {
495
- if (terminalTitleRuntime.timer || !process.stdout.isTTY) return;
546
+ if (process.platform === "win32" || terminalTitleRuntime.timer || !process.stdout.isTTY) return;
496
547
  terminalTitleRuntime.timer = setInterval(() => {
497
548
  terminalTitleRuntime.frame = (terminalTitleRuntime.frame + 1) % TITLE_SPINNER_FRAMES.length;
498
549
  emitTerminalTitle();
@@ -503,8 +554,9 @@ function startTerminalTitleSpinner(): void {
503
554
 
504
555
  /**
505
556
  * Reflect the agent run state in the terminal title's separator: `working`
506
- * animates spinner frames in the separator slot, `idle` shows `>` (your turn),
507
- * `attention` shows `!` (agent blocked on you). Gated off by `tui.titleState`.
557
+ * animates outside Windows and stays `:` on Windows, `idle` shows `>` (your
558
+ * turn), and `attention` shows `!` (agent blocked on you). Gated off by
559
+ * `tui.titleState`.
508
560
  */
509
561
  export function setTerminalTitleState(state: TerminalTitleState): void {
510
562
  terminalTitleRuntime.state = state;
@@ -521,9 +573,11 @@ export function setTerminalTitleStateEnabled(enabled: boolean): void {
521
573
  emitTerminalTitle();
522
574
  }
523
575
 
524
- /** Stop the spinner timer; call on session/UI teardown. */
576
+ /** Release terminal-title runtime resources. */
525
577
  export function disposeTerminalTitleState(): void {
526
578
  stopTerminalTitleSpinner();
579
+ disposeWindowsConsoleTitleApi();
580
+ lastTerminalTitle = undefined;
527
581
  }
528
582
 
529
583
  /**
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};