@molecule/app-ide-react 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/LICENSE +115 -0
  2. package/dist/chatTimelineOrdering.d.ts +50 -0
  3. package/dist/chatTimelineOrdering.d.ts.map +1 -0
  4. package/dist/chatTimelineOrdering.js +46 -0
  5. package/dist/chatTimelineOrdering.js.map +1 -0
  6. package/dist/command-metadata.d.ts +78 -0
  7. package/dist/command-metadata.d.ts.map +1 -0
  8. package/dist/command-metadata.js +214 -0
  9. package/dist/command-metadata.js.map +1 -0
  10. package/dist/components/ActivityCard.d.ts +28 -0
  11. package/dist/components/ActivityCard.d.ts.map +1 -0
  12. package/dist/components/ActivityCard.js +62 -0
  13. package/dist/components/ActivityCard.js.map +1 -0
  14. package/dist/components/AutoCommitBadge.d.ts +58 -0
  15. package/dist/components/AutoCommitBadge.d.ts.map +1 -0
  16. package/dist/components/AutoCommitBadge.js +78 -0
  17. package/dist/components/AutoCommitBadge.js.map +1 -0
  18. package/dist/components/ChatPanel.d.ts +184 -0
  19. package/dist/components/ChatPanel.d.ts.map +1 -0
  20. package/dist/components/ChatPanel.js +6691 -0
  21. package/dist/components/ChatPanel.js.map +1 -0
  22. package/dist/components/CommandPalette.d.ts +18 -0
  23. package/dist/components/CommandPalette.d.ts.map +1 -0
  24. package/dist/components/CommandPalette.js +33 -0
  25. package/dist/components/CommandPalette.js.map +1 -0
  26. package/dist/components/DeviceFrameSelector.d.ts +60 -0
  27. package/dist/components/DeviceFrameSelector.d.ts.map +1 -0
  28. package/dist/components/DeviceFrameSelector.js +230 -0
  29. package/dist/components/DeviceFrameSelector.js.map +1 -0
  30. package/dist/components/EditorPanel.d.ts +17 -0
  31. package/dist/components/EditorPanel.d.ts.map +1 -0
  32. package/dist/components/EditorPanel.js +241 -0
  33. package/dist/components/EditorPanel.js.map +1 -0
  34. package/dist/components/FileExplorer.d.ts +64 -0
  35. package/dist/components/FileExplorer.d.ts.map +1 -0
  36. package/dist/components/FileExplorer.js +1111 -0
  37. package/dist/components/FileExplorer.js.map +1 -0
  38. package/dist/components/FileExplorerContextMenu.d.ts +37 -0
  39. package/dist/components/FileExplorerContextMenu.d.ts.map +1 -0
  40. package/dist/components/FileExplorerContextMenu.js +241 -0
  41. package/dist/components/FileExplorerContextMenu.js.map +1 -0
  42. package/dist/components/HelpCard.d.ts +34 -0
  43. package/dist/components/HelpCard.d.ts.map +1 -0
  44. package/dist/components/HelpCard.js +28 -0
  45. package/dist/components/HelpCard.js.map +1 -0
  46. package/dist/components/Icon.d.ts +50 -0
  47. package/dist/components/Icon.d.ts.map +1 -0
  48. package/dist/components/Icon.js +25 -0
  49. package/dist/components/Icon.js.map +1 -0
  50. package/dist/components/KeyboardShortcutsPanel.d.ts +19 -0
  51. package/dist/components/KeyboardShortcutsPanel.d.ts.map +1 -0
  52. package/dist/components/KeyboardShortcutsPanel.js +187 -0
  53. package/dist/components/KeyboardShortcutsPanel.js.map +1 -0
  54. package/dist/components/MarkdownContent.d.ts +52 -0
  55. package/dist/components/MarkdownContent.d.ts.map +1 -0
  56. package/dist/components/MarkdownContent.js +303 -0
  57. package/dist/components/MarkdownContent.js.map +1 -0
  58. package/dist/components/ModelsTable.d.ts +38 -0
  59. package/dist/components/ModelsTable.d.ts.map +1 -0
  60. package/dist/components/ModelsTable.js +96 -0
  61. package/dist/components/ModelsTable.js.map +1 -0
  62. package/dist/components/PreviewPanel.d.ts +66 -0
  63. package/dist/components/PreviewPanel.d.ts.map +1 -0
  64. package/dist/components/PreviewPanel.js +1752 -0
  65. package/dist/components/PreviewPanel.js.map +1 -0
  66. package/dist/components/QuickOpen.d.ts +20 -0
  67. package/dist/components/QuickOpen.d.ts.map +1 -0
  68. package/dist/components/QuickOpen.js +72 -0
  69. package/dist/components/QuickOpen.js.map +1 -0
  70. package/dist/components/QuickPicker.d.ts +21 -0
  71. package/dist/components/QuickPicker.d.ts.map +1 -0
  72. package/dist/components/QuickPicker.js +174 -0
  73. package/dist/components/QuickPicker.js.map +1 -0
  74. package/dist/components/RelevantSkillSuggestion.d.ts +41 -0
  75. package/dist/components/RelevantSkillSuggestion.d.ts.map +1 -0
  76. package/dist/components/RelevantSkillSuggestion.js +85 -0
  77. package/dist/components/RelevantSkillSuggestion.js.map +1 -0
  78. package/dist/components/ReportModal.d.ts +35 -0
  79. package/dist/components/ReportModal.d.ts.map +1 -0
  80. package/dist/components/ReportModal.js +112 -0
  81. package/dist/components/ReportModal.js.map +1 -0
  82. package/dist/components/ResizeHandle.d.ts +21 -0
  83. package/dist/components/ResizeHandle.d.ts.map +1 -0
  84. package/dist/components/ResizeHandle.js +109 -0
  85. package/dist/components/ResizeHandle.js.map +1 -0
  86. package/dist/components/ScriptsCard.d.ts +37 -0
  87. package/dist/components/ScriptsCard.d.ts.map +1 -0
  88. package/dist/components/ScriptsCard.js +177 -0
  89. package/dist/components/ScriptsCard.js.map +1 -0
  90. package/dist/components/SearchPanel.d.ts +18 -0
  91. package/dist/components/SearchPanel.d.ts.map +1 -0
  92. package/dist/components/SearchPanel.js +449 -0
  93. package/dist/components/SearchPanel.js.map +1 -0
  94. package/dist/components/SettingsCard.d.ts +49 -0
  95. package/dist/components/SettingsCard.d.ts.map +1 -0
  96. package/dist/components/SettingsCard.js +59 -0
  97. package/dist/components/SettingsCard.js.map +1 -0
  98. package/dist/components/ShareModal.d.ts +32 -0
  99. package/dist/components/ShareModal.d.ts.map +1 -0
  100. package/dist/components/ShareModal.js +126 -0
  101. package/dist/components/ShareModal.js.map +1 -0
  102. package/dist/components/SidebarTabs.d.ts +18 -0
  103. package/dist/components/SidebarTabs.d.ts.map +1 -0
  104. package/dist/components/SidebarTabs.js +20 -0
  105. package/dist/components/SidebarTabs.js.map +1 -0
  106. package/dist/components/SkillsCard.d.ts +86 -0
  107. package/dist/components/SkillsCard.d.ts.map +1 -0
  108. package/dist/components/SkillsCard.js +227 -0
  109. package/dist/components/SkillsCard.js.map +1 -0
  110. package/dist/components/StreamingIndicator.d.ts +50 -0
  111. package/dist/components/StreamingIndicator.d.ts.map +1 -0
  112. package/dist/components/StreamingIndicator.js +142 -0
  113. package/dist/components/StreamingIndicator.js.map +1 -0
  114. package/dist/components/TabBar.d.ts +37 -0
  115. package/dist/components/TabBar.d.ts.map +1 -0
  116. package/dist/components/TabBar.js +187 -0
  117. package/dist/components/TabBar.js.map +1 -0
  118. package/dist/components/TipCard.d.ts +22 -0
  119. package/dist/components/TipCard.d.ts.map +1 -0
  120. package/dist/components/TipCard.js +73 -0
  121. package/dist/components/TipCard.js.map +1 -0
  122. package/dist/components/ToolCallCard.d.ts +18 -0
  123. package/dist/components/ToolCallCard.d.ts.map +1 -0
  124. package/dist/components/ToolCallCard.js +716 -0
  125. package/dist/components/ToolCallCard.js.map +1 -0
  126. package/dist/components/UserAvatar.d.ts +45 -0
  127. package/dist/components/UserAvatar.d.ts.map +1 -0
  128. package/dist/components/UserAvatar.js +80 -0
  129. package/dist/components/UserAvatar.js.map +1 -0
  130. package/dist/components/WorkspaceLayout.d.ts +19 -0
  131. package/dist/components/WorkspaceLayout.d.ts.map +1 -0
  132. package/dist/components/WorkspaceLayout.js +56 -0
  133. package/dist/components/WorkspaceLayout.js.map +1 -0
  134. package/dist/components/activity-utilities.d.ts +107 -0
  135. package/dist/components/activity-utilities.d.ts.map +1 -0
  136. package/dist/components/activity-utilities.js +154 -0
  137. package/dist/components/activity-utilities.js.map +1 -0
  138. package/dist/components/chat-autocommit-utilities.d.ts +172 -0
  139. package/dist/components/chat-autocommit-utilities.d.ts.map +1 -0
  140. package/dist/components/chat-autocommit-utilities.js +185 -0
  141. package/dist/components/chat-autocommit-utilities.js.map +1 -0
  142. package/dist/components/chat-card-style.d.ts +64 -0
  143. package/dist/components/chat-card-style.d.ts.map +1 -0
  144. package/dist/components/chat-card-style.js +74 -0
  145. package/dist/components/chat-card-style.js.map +1 -0
  146. package/dist/components/chat-commands.d.ts +17 -0
  147. package/dist/components/chat-commands.d.ts.map +1 -0
  148. package/dist/components/chat-commands.js +17 -0
  149. package/dist/components/chat-commands.js.map +1 -0
  150. package/dist/components/chat-commit-utilities.d.ts +29 -0
  151. package/dist/components/chat-commit-utilities.d.ts.map +1 -0
  152. package/dist/components/chat-commit-utilities.js +31 -0
  153. package/dist/components/chat-commit-utilities.js.map +1 -0
  154. package/dist/components/chat-cost-utilities.d.ts +62 -0
  155. package/dist/components/chat-cost-utilities.d.ts.map +1 -0
  156. package/dist/components/chat-cost-utilities.js +66 -0
  157. package/dist/components/chat-cost-utilities.js.map +1 -0
  158. package/dist/components/chat-effort-utilities.d.ts +96 -0
  159. package/dist/components/chat-effort-utilities.d.ts.map +1 -0
  160. package/dist/components/chat-effort-utilities.js +97 -0
  161. package/dist/components/chat-effort-utilities.js.map +1 -0
  162. package/dist/components/chat-help-utilities.d.ts +89 -0
  163. package/dist/components/chat-help-utilities.d.ts.map +1 -0
  164. package/dist/components/chat-help-utilities.js +131 -0
  165. package/dist/components/chat-help-utilities.js.map +1 -0
  166. package/dist/components/chat-model-mode-utilities.d.ts +143 -0
  167. package/dist/components/chat-model-mode-utilities.d.ts.map +1 -0
  168. package/dist/components/chat-model-mode-utilities.js +191 -0
  169. package/dist/components/chat-model-mode-utilities.js.map +1 -0
  170. package/dist/components/chat-models-utilities.d.ts +98 -0
  171. package/dist/components/chat-models-utilities.d.ts.map +1 -0
  172. package/dist/components/chat-models-utilities.js +136 -0
  173. package/dist/components/chat-models-utilities.js.map +1 -0
  174. package/dist/components/chat-report-utilities.d.ts +149 -0
  175. package/dist/components/chat-report-utilities.d.ts.map +1 -0
  176. package/dist/components/chat-report-utilities.js +151 -0
  177. package/dist/components/chat-report-utilities.js.map +1 -0
  178. package/dist/components/chat-scripts-utilities.d.ts +140 -0
  179. package/dist/components/chat-scripts-utilities.d.ts.map +1 -0
  180. package/dist/components/chat-scripts-utilities.js +158 -0
  181. package/dist/components/chat-scripts-utilities.js.map +1 -0
  182. package/dist/components/chat-settings-utilities.d.ts +72 -0
  183. package/dist/components/chat-settings-utilities.d.ts.map +1 -0
  184. package/dist/components/chat-settings-utilities.js +56 -0
  185. package/dist/components/chat-settings-utilities.js.map +1 -0
  186. package/dist/components/chat-share-utilities.d.ts +104 -0
  187. package/dist/components/chat-share-utilities.d.ts.map +1 -0
  188. package/dist/components/chat-share-utilities.js +90 -0
  189. package/dist/components/chat-share-utilities.js.map +1 -0
  190. package/dist/components/chat-skills-utilities.d.ts +242 -0
  191. package/dist/components/chat-skills-utilities.d.ts.map +1 -0
  192. package/dist/components/chat-skills-utilities.js +433 -0
  193. package/dist/components/chat-skills-utilities.js.map +1 -0
  194. package/dist/components/chat-stream-utilities.d.ts +74 -0
  195. package/dist/components/chat-stream-utilities.d.ts.map +1 -0
  196. package/dist/components/chat-stream-utilities.js +103 -0
  197. package/dist/components/chat-stream-utilities.js.map +1 -0
  198. package/dist/components/chat-tips-utilities.d.ts +108 -0
  199. package/dist/components/chat-tips-utilities.d.ts.map +1 -0
  200. package/dist/components/chat-tips-utilities.js +137 -0
  201. package/dist/components/chat-tips-utilities.js.map +1 -0
  202. package/dist/components/device-cycle.d.ts +84 -0
  203. package/dist/components/device-cycle.d.ts.map +1 -0
  204. package/dist/components/device-cycle.js +77 -0
  205. package/dist/components/device-cycle.js.map +1 -0
  206. package/dist/components/index.d.ts +36 -0
  207. package/dist/components/index.d.ts.map +1 -0
  208. package/dist/components/index.js +33 -0
  209. package/dist/components/index.js.map +1 -0
  210. package/dist/components/tool-call-utilities.d.ts +69 -0
  211. package/dist/components/tool-call-utilities.d.ts.map +1 -0
  212. package/dist/components/tool-call-utilities.js +368 -0
  213. package/dist/components/tool-call-utilities.js.map +1 -0
  214. package/dist/components/user-avatar-utilities.d.ts +41 -0
  215. package/dist/components/user-avatar-utilities.d.ts.map +1 -0
  216. package/dist/components/user-avatar-utilities.js +56 -0
  217. package/dist/components/user-avatar-utilities.js.map +1 -0
  218. package/dist/components/workspace-layout-utilities.d.ts +37 -0
  219. package/dist/components/workspace-layout-utilities.d.ts.map +1 -0
  220. package/dist/components/workspace-layout-utilities.js +58 -0
  221. package/dist/components/workspace-layout-utilities.js.map +1 -0
  222. package/dist/customEventCards.d.ts +117 -0
  223. package/dist/customEventCards.d.ts.map +1 -0
  224. package/dist/customEventCards.js +36 -0
  225. package/dist/customEventCards.js.map +1 -0
  226. package/dist/hooks/index.d.ts +8 -0
  227. package/dist/hooks/index.d.ts.map +1 -0
  228. package/dist/hooks/index.js +8 -0
  229. package/dist/hooks/index.js.map +1 -0
  230. package/dist/hooks/useKeyboardShortcuts.d.ts +48 -0
  231. package/dist/hooks/useKeyboardShortcuts.d.ts.map +1 -0
  232. package/dist/hooks/useKeyboardShortcuts.js +112 -0
  233. package/dist/hooks/useKeyboardShortcuts.js.map +1 -0
  234. package/dist/hooks/useViewport.d.ts +42 -0
  235. package/dist/hooks/useViewport.d.ts.map +1 -0
  236. package/dist/hooks/useViewport.js +61 -0
  237. package/dist/hooks/useViewport.js.map +1 -0
  238. package/dist/index.d.ts +75 -0
  239. package/dist/index.d.ts.map +1 -0
  240. package/dist/index.js +74 -0
  241. package/dist/index.js.map +1 -0
  242. package/dist/settings-metadata.d.ts +78 -0
  243. package/dist/settings-metadata.d.ts.map +1 -0
  244. package/dist/settings-metadata.js +131 -0
  245. package/dist/settings-metadata.js.map +1 -0
  246. package/dist/types.d.ts +751 -0
  247. package/dist/types.d.ts.map +1 -0
  248. package/dist/types.js +25 -0
  249. package/dist/types.js.map +1 -0
  250. package/package.json +90 -0
@@ -0,0 +1,433 @@
1
+ /**
2
+ * Pure helpers backing the `/skills` browser and the proactive "Relevant skill"
3
+ * suggestion.
4
+ *
5
+ * Following the 2026 Agent Skills folder convention, a skill is a *directory*
6
+ * under the project's `.agents/skills/` tree whose entry file is `SKILL.md`
7
+ * (e.g. `.agents/skills/auth/SKILL.md`), carrying a YAML frontmatter block that
8
+ * declares `name` and `description`. Discovery deliberately matches ONLY those
9
+ * `SKILL.md` entry files — loose Markdown fragments under `patterns/` and the
10
+ * `examples/` reference corpus are scaffold support material, NOT user-facing
11
+ * skills, so surfacing them would bury the real skills (the bug this convention
12
+ * fixes). These helpers discover skill files via the SAME file-list /
13
+ * file-content API the chat already uses for `@`-mentions (injected as
14
+ * functions so this layer stays decoupled from any HTTP client), parse each
15
+ * skill's metadata, filter the list by a query, and run a lightweight relevance
16
+ * pass over recent messages so the most relevant skill can be offered with a
17
+ * one-click load.
18
+ *
19
+ * All functions here are deterministic and side-effect free (apart from the
20
+ * caller-supplied fetchers in {@link loadProjectSkills}), so they can be unit
21
+ * tested without rendering or a real backend.
22
+ *
23
+ * @module
24
+ */
25
+ /**
26
+ * Normalizes a file path returned by the file-list API into a project-relative
27
+ * path: strips a leading `/workspace/` and any leading slash.
28
+ *
29
+ * @param path - The raw file path.
30
+ * @returns The project-relative path.
31
+ */
32
+ export function toRelativePath(path) {
33
+ return path.replace(/^\/workspace\//, '').replace(/^\/+/, '');
34
+ }
35
+ /**
36
+ * Reserved directory names directly under `.agents/skills/` that are NOT
37
+ * user-facing skills: `patterns/` holds layout/styling fragments synced from the
38
+ * polish pipeline, and `examples/` is a large reference corpus. A `SKILL.md`
39
+ * inside either is scaffold support material, so discovery skips them — that is
40
+ * the whole point of the SKILL.md-folder convention (keep the ~9 real skills
41
+ * from being buried under pattern fragments + example READMEs).
42
+ */
43
+ const RESERVED_SKILL_DIRS = new Set(['patterns', 'examples']);
44
+ /**
45
+ * Whether a path points at a skill's entry file under the 2026 Agent Skills
46
+ * folder convention: a `SKILL.md` directly inside a skill directory under
47
+ * `.agents/skills/` (i.e. `.agents/skills/<name>/SKILL.md`). Loose `*.md`
48
+ * fragments and the reserved `patterns/` + `examples/` support trees are NOT
49
+ * skills and return `false`. Tolerant of `/workspace/`-prefixed or
50
+ * leading-slash paths.
51
+ *
52
+ * @param path - The file path to test.
53
+ * @returns `true` if the path is a skill's `SKILL.md` entry file.
54
+ */
55
+ export function isSkillFile(path) {
56
+ const rel = toRelativePath(path);
57
+ const match = rel.match(/(?:^|\/)\.agents\/skills\/([^/]+)\/SKILL\.md$/i);
58
+ if (!match)
59
+ return false;
60
+ return !RESERVED_SKILL_DIRS.has(match[1].toLowerCase());
61
+ }
62
+ /**
63
+ * Derives a fallback skill name from its path: the filename without extension,
64
+ * or — for the conventional `SKILL.md`/`INDEX.md` entry files — the parent
65
+ * directory name (which names the skill).
66
+ *
67
+ * @param path - The skill file's relative path.
68
+ * @returns A human-ish name derived from the path.
69
+ */
70
+ export function deriveSkillNameFromPath(path) {
71
+ const rel = toRelativePath(path);
72
+ const segments = rel.split('/').filter(Boolean);
73
+ const file = segments[segments.length - 1] ?? rel;
74
+ const base = file.replace(/\.md$/i, '');
75
+ if (/^(skill|index|readme)$/i.test(base) && segments.length >= 2) {
76
+ return segments[segments.length - 2];
77
+ }
78
+ return base;
79
+ }
80
+ /**
81
+ * Slugifies a skill display name into a filesystem-safe directory name for its
82
+ * folder under `.agents/skills/`: lowercased, with every run of non-alphanumeric
83
+ * characters collapsed to a single hyphen and surrounding hyphens trimmed. Falls
84
+ * back to `'new-skill'` when the name has no usable characters, so the derived
85
+ * path is always valid.
86
+ *
87
+ * @param name - The skill's display name.
88
+ * @returns A safe directory slug for the skill.
89
+ */
90
+ export function slugifySkillName(name) {
91
+ const slug = name
92
+ .toLowerCase()
93
+ .replace(/[^a-z0-9]+/g, '-')
94
+ .replace(/^-+|-+$/g, '');
95
+ return slug || 'new-skill';
96
+ }
97
+ /**
98
+ * The project-relative path of a new skill's entry file under the 2026 Agent
99
+ * Skills folder convention: `.agents/skills/<slug>/SKILL.md` (no leading slash),
100
+ * where `<slug>` is {@link slugifySkillName} of the display name. The result is
101
+ * exactly the shape {@link isSkillFile} recognizes, so a freshly created skill is
102
+ * immediately discoverable by the same `/skills` browser.
103
+ *
104
+ * @param name - The skill's display name.
105
+ * @returns The new skill's `SKILL.md` relative path.
106
+ */
107
+ export function newSkillPath(name) {
108
+ return `.agents/skills/${slugifySkillName(name)}/SKILL.md`;
109
+ }
110
+ /**
111
+ * Builds the starter `SKILL.md` content for a new skill: a YAML frontmatter
112
+ * block declaring the `name` and a `description` placeholder (so the skill shows
113
+ * up — with a clear "edit me" hint — in the `/skills` browser right away),
114
+ * followed by a short authoring scaffold. The content round-trips through
115
+ * {@link parseSkillMeta} (the same parser discovery uses), so a created skill's
116
+ * metadata is identical whether read from this template or re-discovered on disk.
117
+ *
118
+ * @param name - The skill's display name (used verbatim as the frontmatter `name`).
119
+ * @returns The `SKILL.md` file content for a new skill.
120
+ */
121
+ export function buildNewSkillTemplate(name) {
122
+ const display = name.trim() || slugifySkillName(name);
123
+ return [
124
+ '---',
125
+ `name: ${display}`,
126
+ 'description: One-line summary of when this skill applies (edit me).',
127
+ '---',
128
+ '',
129
+ `# ${display}`,
130
+ '',
131
+ 'Describe when this skill applies and the steps to follow.',
132
+ '',
133
+ ].join('\n');
134
+ }
135
+ /** Parses a leading YAML frontmatter block into a flat key→value map. */
136
+ function parseFrontmatter(content) {
137
+ const match = content.match(/^\uFEFF?---\r?\n([\s\S]*?)\r?\n---/);
138
+ if (!match)
139
+ return {};
140
+ const out = {};
141
+ for (const line of match[1].split(/\r?\n/)) {
142
+ const kv = line.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*)$/);
143
+ if (kv)
144
+ out[kv[1].toLowerCase()] = kv[2].trim().replace(/^["']|["']$/g, '');
145
+ }
146
+ return out;
147
+ }
148
+ /** Returns the body content with any leading frontmatter block removed. */
149
+ function stripFrontmatter(content) {
150
+ return content.replace(/^\uFEFF?---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
151
+ }
152
+ /**
153
+ * Extracts a skill's display name and description from its content. Prefers the
154
+ * frontmatter `name`/`description`; falls back to the first Markdown heading
155
+ * for the name and the first non-empty body paragraph for the description, and
156
+ * finally to a path-derived name.
157
+ *
158
+ * @param path - The skill file's relative path (used for the name fallback).
159
+ * @param content - The raw file content (may be empty if it couldn't be read).
160
+ * @returns The resolved `name` and `description`.
161
+ */
162
+ export function parseSkillMeta(path, content) {
163
+ const fm = parseFrontmatter(content);
164
+ const body = stripFrontmatter(content);
165
+ let name = fm.name;
166
+ if (!name) {
167
+ const heading = body.match(/^#{1,6}\s+(.+?)\s*$/m);
168
+ if (heading)
169
+ name = heading[1].trim();
170
+ }
171
+ if (!name)
172
+ name = deriveSkillNameFromPath(path);
173
+ let description = fm.description;
174
+ if (!description) {
175
+ for (const line of body.split(/\r?\n/)) {
176
+ const trimmed = line.trim();
177
+ if (trimmed && !trimmed.startsWith('#') && trimmed !== '---') {
178
+ description = trimmed;
179
+ break;
180
+ }
181
+ }
182
+ }
183
+ return { name, description: description ?? '' };
184
+ }
185
+ /**
186
+ * Filters skills by a free-text query, matching (case-insensitively) against
187
+ * the name, description, and path. An empty/blank query returns all skills.
188
+ *
189
+ * @param skills - The skills to filter.
190
+ * @param query - The search query.
191
+ * @returns The matching skills, in input order.
192
+ */
193
+ export function filterSkills(skills, query) {
194
+ const q = query.trim().toLowerCase();
195
+ if (!q)
196
+ return [...skills];
197
+ return skills.filter((s) => s.name.toLowerCase().includes(q) ||
198
+ s.description.toLowerCase().includes(q) ||
199
+ s.path.toLowerCase().includes(q));
200
+ }
201
+ /**
202
+ * Discovers and loads every project skill. Uses the injected `fetchList` to get
203
+ * the project's file paths, keeps the ones under `.agents/skills/`, reads each
204
+ * via `fetchContent`, parses its metadata, and returns the list sorted by name.
205
+ *
206
+ * The fetchers are injected (rather than importing an HTTP client) so this stays
207
+ * decoupled and unit-testable. A single unreadable skill file is tolerated: it
208
+ * still appears with a path-derived name and no description.
209
+ *
210
+ * @param fetchList - Returns all project file paths.
211
+ * @param fetchContent - Returns the content of one skill file by relative path.
212
+ * @returns The discovered skills, sorted by display name.
213
+ */
214
+ export async function loadProjectSkills(fetchList, fetchContent) {
215
+ const files = await fetchList();
216
+ const relPaths = [...new Set(files.filter(isSkillFile).map(toRelativePath))];
217
+ const skills = await Promise.all(relPaths.map(async (path) => {
218
+ let content;
219
+ try {
220
+ content = await fetchContent(path);
221
+ }
222
+ catch (_error) {
223
+ // One unreadable skill file must not drop the whole list — fall back to
224
+ // a path-derived name with no description (handled by parseSkillMeta).
225
+ content = '';
226
+ }
227
+ const meta = parseSkillMeta(path, content);
228
+ return { path, name: meta.name, description: meta.description };
229
+ }));
230
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
231
+ }
232
+ /**
233
+ * Loads project skills with bounded retry, for the window where a sandbox has
234
+ * booted far enough to answer the file-list call but has NOT yet materialized
235
+ * its `.agents/skills/` tree.
236
+ *
237
+ * The server announces "Loaded N skills" from a static, always-available source
238
+ * (its baked-in built-in skill set); the `/skills` browser reads the LIVE
239
+ * sandbox file list, which is empty until scaffolding finishes. Clicking the
240
+ * announce card during that gap previously hit a
241
+ * permanent "No skills found" dead-end. Because a ready sandbox effectively
242
+ * always has the built-in skills scaffolded, "zero skill files" (or a transient
243
+ * fetch error) is treated as "still booting" and retried a bounded number of
244
+ * times before settling — so the skills appear on their own once ready, with an
245
+ * honest waiting state in between instead of a misleading empty one.
246
+ *
247
+ * @param fetchList - Returns all project file paths.
248
+ * @param fetchContent - Returns the content of one skill file by relative path.
249
+ * @param options - Retry tuning + injectables for cancellation and tests.
250
+ * @param options.attempts - Total tries before settling. Defaults to 10.
251
+ * @param options.delayMs - Delay between tries, in ms. Defaults to 2000.
252
+ * @param options.sleep - Awaitable delay (injected in tests). Defaults to `setTimeout`.
253
+ * @param options.isCancelled - Bail-out predicate (e.g. the card was closed).
254
+ * @param options.onRetry - Invoked before each retry sleep (e.g. to show "waiting").
255
+ * @returns The discovered skills plus whether the sandbox still looks like it's booting.
256
+ */
257
+ export async function loadProjectSkillsResilient(fetchList, fetchContent, options) {
258
+ const attempts = options?.attempts ?? 10;
259
+ const delayMs = options?.delayMs ?? 2000;
260
+ const sleep = options?.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
261
+ const isCancelled = options?.isCancelled ?? (() => false);
262
+ for (let attempt = 0; attempt < attempts; attempt++) {
263
+ if (isCancelled())
264
+ return { skills: [], stillBooting: false };
265
+ const isLast = attempt === attempts - 1;
266
+ try {
267
+ const skills = await loadProjectSkills(fetchList, fetchContent);
268
+ // Found skills → ready. Still empty after the last attempt → a ready sandbox
269
+ // effectively always has the built-in skills scaffolded, so report it as
270
+ // still-booting (the caller keeps an honest "waiting", not a false "no skills").
271
+ if (skills.length > 0)
272
+ return { skills, stillBooting: false };
273
+ if (isLast)
274
+ return { skills, stillBooting: true };
275
+ }
276
+ catch (error) {
277
+ // A transient error (sandbox 404/503 while booting) is retried; only the
278
+ // last failed attempt surfaces, letting the caller show its error state.
279
+ if (isLast)
280
+ throw error;
281
+ }
282
+ if (isCancelled())
283
+ return { skills: [], stillBooting: false };
284
+ options?.onRetry?.();
285
+ await sleep(delayMs);
286
+ }
287
+ // Unreachable: the final iteration always returns or throws. Satisfies the type checker.
288
+ return { skills: [], stillBooting: true };
289
+ }
290
+ /**
291
+ * Common words ignored by the relevance pass so generic chatter ("how do I add
292
+ * this for you") doesn't manufacture spurious skill matches.
293
+ */
294
+ const RELEVANCE_STOPWORDS = new Set([
295
+ 'the',
296
+ 'and',
297
+ 'for',
298
+ 'with',
299
+ 'this',
300
+ 'that',
301
+ 'you',
302
+ 'your',
303
+ 'are',
304
+ 'can',
305
+ 'from',
306
+ 'have',
307
+ 'how',
308
+ 'what',
309
+ 'when',
310
+ 'where',
311
+ 'will',
312
+ 'would',
313
+ 'should',
314
+ 'could',
315
+ 'about',
316
+ 'into',
317
+ 'use',
318
+ 'using',
319
+ 'used',
320
+ 'add',
321
+ 'make',
322
+ 'need',
323
+ 'want',
324
+ 'please',
325
+ 'help',
326
+ 'some',
327
+ 'any',
328
+ 'all',
329
+ 'not',
330
+ 'but',
331
+ 'was',
332
+ 'were',
333
+ 'has',
334
+ 'had',
335
+ 'out',
336
+ 'our',
337
+ 'let',
338
+ 'get',
339
+ 'got',
340
+ 'new',
341
+ ]);
342
+ /** Lowercases text and splits it into meaningful word tokens (≥3 chars, no stopwords). */
343
+ function tokenize(text) {
344
+ return (text.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((w) => w.length >= 3 && !RELEVANCE_STOPWORDS.has(w));
345
+ }
346
+ /**
347
+ * Joins the text of the most recent USER messages into a single relevance
348
+ * signal. Assistant/system turns are excluded so the suggestion tracks what the
349
+ * user is actually asking for, not the agent's own verbiage.
350
+ *
351
+ * @param messages - Conversation messages (newest last), each with a `role` and `content`.
352
+ * @param count - How many trailing user messages to include. Defaults to 3.
353
+ * @returns The concatenated text of the recent user messages.
354
+ */
355
+ export function recentUserText(messages, count = 3) {
356
+ const userContent = messages.filter((m) => m.role === 'user').map((m) => m.content);
357
+ return userContent.slice(-count).join('\n');
358
+ }
359
+ /**
360
+ * Runs a lightweight relevance pass: scores each skill by how many of its
361
+ * name/description keywords appear in `recentText` (name matches weigh heavier),
362
+ * and returns the top matches above a minimum score. Deterministic and
363
+ * side-effect free, so the caller can recompute it as the conversation grows.
364
+ *
365
+ * @param skills - The candidate skills (already excluding loaded/dismissed ones).
366
+ * @param recentText - Recent conversation text (see {@link recentUserText}).
367
+ * @param options - Tuning: `max` results (default 1) and `minScore` threshold (default 2).
368
+ * @param options.max - Maximum number of suggestions to return.
369
+ * @param options.minScore - Minimum score a skill must reach to be suggested.
370
+ * @returns The most relevant skills, highest score first (ties broken by name).
371
+ */
372
+ export function suggestRelevantSkills(skills, recentText, options = {}) {
373
+ const { max = 1, minScore = 2 } = options;
374
+ const textTokens = tokenize(recentText);
375
+ if (textTokens.length === 0)
376
+ return [];
377
+ const textCounts = new Map();
378
+ for (const token of textTokens)
379
+ textCounts.set(token, (textCounts.get(token) ?? 0) + 1);
380
+ return skills
381
+ .map((skill) => {
382
+ const nameTokens = new Set(tokenize(skill.name));
383
+ const descTokens = new Set(tokenize(skill.description));
384
+ let score = 0;
385
+ for (const token of nameTokens) {
386
+ // A skill whose NAME the user typed is a strong signal — weigh it ×3.
387
+ score += (textCounts.get(token) ?? 0) * 3;
388
+ }
389
+ for (const token of descTokens) {
390
+ if (nameTokens.has(token))
391
+ continue;
392
+ score += textCounts.get(token) ?? 0;
393
+ }
394
+ return { skill, score };
395
+ })
396
+ .filter((s) => s.score >= minScore)
397
+ .sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name))
398
+ .slice(0, max);
399
+ }
400
+ /**
401
+ * The single skill to NUDGE the user to load for the current conversation, or null when there's
402
+ * nothing useful to suggest. Picks the genuine top match for `recentText` (via
403
+ * {@link suggestRelevantSkills}) from the candidate pool — all skills minus the ones the user
404
+ * dismissed or already @-attached — then returns null if that top match is ALREADY LOADED:
405
+ * either @-loaded this session (`loaded`) OR default-loaded (`defaultLoaded`, its full body
406
+ * always-on in the system prompt). Suggesting an already-loaded skill just points at context
407
+ * the agent already has, so we never do it; for a fresh project where every skill is
408
+ * default-loaded the tip simply never fires. We null the top match (rather than dropping loaded
409
+ * skills from the pool) so the next-best doesn't immediately pop up — the tip changes only when
410
+ * the conversation produces a genuinely different top match, never just because the last one got
411
+ * loaded (the "suggests one after another" parade the user reported).
412
+ *
413
+ * @param skills - All discovered project skills.
414
+ * @param recentText - Recent user text to match against.
415
+ * @param sets - Exclusion sets, all keyed by skill `path`.
416
+ * @param sets.dismissed - Skills the user dismissed.
417
+ * @param sets.attachedPaths - Already-@-attached file paths (leading-slash form, matched against `'/' + skill.path`).
418
+ * @param sets.loaded - Skills @-loaded this session.
419
+ * @param sets.defaultLoaded - Default-loaded skills (always-on in the prompt).
420
+ * @returns The skill to suggest, or null.
421
+ */
422
+ export function pickRelevantSkill(skills, recentText, sets) {
423
+ if (skills.length === 0)
424
+ return null;
425
+ const candidates = skills.filter((s) => !sets.dismissed.has(s.path) && !sets.attachedPaths.has('/' + s.path));
426
+ if (candidates.length === 0)
427
+ return null;
428
+ const top = suggestRelevantSkills(candidates, recentText)[0]?.skill ?? null;
429
+ if (!top)
430
+ return null;
431
+ return sets.loaded.has(top.path) || sets.defaultLoaded.has(top.path) ? null : top;
432
+ }
433
+ //# sourceMappingURL=chat-skills-utilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-skills-utilities.js","sourceRoot":"","sources":["../../src/components/chat-skills-utilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAYH;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;AAC/D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAA;AAE7D;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;IAChC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAA;IACzE,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IACxB,OAAO,CAAC,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;AACzD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;IAChC,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAC/C,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,GAAG,CAAA;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;IACvC,IAAI,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACjE,OAAO,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACtC,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,IAAI,GAAG,IAAI;SACd,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;IAC1B,OAAO,IAAI,IAAI,WAAW,CAAA;AAC5B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,kBAAkB,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAA;AAC5D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAA;IACrD,OAAO;QACL,KAAK;QACL,SAAS,OAAO,EAAE;QAClB,qEAAqE;QACrE,KAAK;QACL,EAAE;QACF,KAAK,OAAO,EAAE;QACd,EAAE;QACF,2DAA2D;QAC3D,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACd,CAAC;AAED,yEAAyE;AACzE,SAAS,gBAAgB,CAAC,OAAe;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAA;IACjE,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAA;IACrB,MAAM,GAAG,GAA2B,EAAE,CAAA;IACtC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAA;QACtD,IAAI,EAAE;YAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAA;IAC7E,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,2EAA2E;AAC3E,SAAS,gBAAgB,CAAC,OAAe;IACvC,OAAO,OAAO,CAAC,OAAO,CAAC,wCAAwC,EAAE,EAAE,CAAC,CAAA;AACtE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,OAAe;IAEf,MAAM,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACpC,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;IAEtC,IAAI,IAAI,GAAG,EAAE,CAAC,IAAI,CAAA;IAClB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAA;QAClD,IAAI,OAAO;YAAE,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IACvC,CAAC;IACD,IAAI,CAAC,IAAI;QAAE,IAAI,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAA;IAE/C,IAAI,WAAW,GAAG,EAAE,CAAC,WAAW,CAAA;IAChC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;YAC3B,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;gBAC7D,WAAW,GAAG,OAAO,CAAA;gBACrB,MAAK;YACP,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,IAAI,EAAE,EAAE,CAAA;AACjD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,MAA4B,EAAE,KAAa;IACtE,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACpC,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,GAAG,MAAM,CAAC,CAAA;IAC1B,OAAO,MAAM,CAAC,MAAM,CAClB,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAChC,CAAC,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACvC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CACnC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAkC,EAClC,YAAuD;IAEvD,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAA;IAC/B,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAA;IAC5E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAC1B,IAAI,OAAe,CAAA;QACnB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAA;QACpC,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YAChB,wEAAwE;YACxE,uEAAuE;YACvE,OAAO,GAAG,EAAE,CAAA;QACd,CAAC;QACD,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAC1C,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAA;IACjE,CAAC,CAAC,CACH,CAAA;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;AAC5D,CAAC;AAcD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,SAAkC,EAClC,YAAuD,EACvD,OAMC;IAED,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,EAAE,CAAA;IACxC,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,IAAI,CAAA;IACxC,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IACjG,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAA;IAEzD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC;QACpD,IAAI,WAAW,EAAE;YAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,CAAA;QAC7D,MAAM,MAAM,GAAG,OAAO,KAAK,QAAQ,GAAG,CAAC,CAAA;QACvC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;YAC/D,6EAA6E;YAC7E,yEAAyE;YACzE,iFAAiF;YACjF,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,CAAA;YAC7D,IAAI,MAAM;gBAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,CAAA;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,yEAAyE;YACzE,yEAAyE;YACzE,IAAI,MAAM;gBAAE,MAAM,KAAK,CAAA;QACzB,CAAC;QACD,IAAI,WAAW,EAAE;YAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,CAAA;QAC7D,OAAO,EAAE,OAAO,EAAE,EAAE,CAAA;QACpB,MAAM,KAAK,CAAC,OAAO,CAAC,CAAA;IACtB,CAAC;IACD,yFAAyF;IACzF,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAA;AAC3C,CAAC;AAUD;;;GAGG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAClC,KAAK;IACL,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;IACN,KAAK;IACL,MAAM;IACN,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;IACP,QAAQ;IACR,OAAO;IACP,OAAO;IACP,MAAM;IACN,KAAK;IACL,OAAO;IACP,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;IACN,QAAQ;IACR,MAAM;IACN,MAAM;IACN,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,MAAM;IACN,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;CACN,CAAC,CAAA;AAEF,0FAA0F;AAC1F,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAC1D,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,CACpD,CAAA;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAC5B,QAA0D,EAC1D,KAAK,GAAG,CAAC;IAET,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;IACnF,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC7C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAA4B,EAC5B,UAAkB,EAClB,UAA+C,EAAE;IAEjD,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,QAAQ,GAAG,CAAC,EAAE,GAAG,OAAO,CAAA;IACzC,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAA;IACvC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IAEtC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC5C,KAAK,MAAM,KAAK,IAAI,UAAU;QAAE,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAEvF,OAAO,MAAM;SACV,GAAG,CAAC,CAAC,KAAK,EAAmB,EAAE;QAC9B,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;QAChD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAA;QACvD,IAAI,KAAK,GAAG,CAAC,CAAA;QACb,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;YAC/B,sEAAsE;YACtE,KAAK,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;QAC3C,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;YAC/B,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAQ;YACnC,KAAK,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACrC,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IACzB,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,QAAQ,CAAC;SAClC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SAC7E,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;AAClB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAA4B,EAC5B,UAAkB,EAClB,IAKC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACpC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAC9B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAC5E,CAAA;IACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACxC,MAAM,GAAG,GAAG,qBAAqB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI,CAAA;IAC3E,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAA;AACnF,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Helpers for the live streaming-message indicator (token estimate).
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Character length of a tool call's input, for the live token estimate. While the
8
+ * input is still streaming, counts the bytes seen so far; once the final input has
9
+ * arrived, serializes it ONCE and caches the length by object identity.
10
+ * @param tc - The tool call.
11
+ * @param tc.input - The final tool input, once it has fully arrived.
12
+ * @param tc.streamInputChars - Bytes of the input received so far while streaming.
13
+ * @returns Approximate character length of the input.
14
+ */
15
+ export declare function toolInputChars(tc: {
16
+ input?: unknown;
17
+ streamInputChars?: number;
18
+ }): number;
19
+ /**
20
+ * Rough estimate of tokens generated so far in a streaming message (~4 chars/token),
21
+ * from the assistant text + thinking content + tool-call inputs. Cheap to call on
22
+ * every stream flush: string lengths are O(1) and each completed tool input's length
23
+ * is cached (see {@link toolInputChars}), so this is O(blocks + toolCalls), NOT
24
+ * O(total content) — the difference between a smooth stream and a frozen tab.
25
+ * @param msg - The streaming assistant message.
26
+ * @param msg.content - Accumulated assistant text.
27
+ * @param msg.blocks - Ordered stream blocks (thinking content counts).
28
+ * @param msg.toolCalls - Tool calls (their inputs count as generated output).
29
+ * @returns The estimated token count.
30
+ */
31
+ export declare function estimateStreamTokens(msg: {
32
+ content?: string;
33
+ blocks?: Array<{
34
+ type: string;
35
+ content?: string;
36
+ }>;
37
+ toolCalls?: Array<{
38
+ input?: unknown;
39
+ streamInputChars?: number;
40
+ }>;
41
+ }): number;
42
+ /** A chat message as seen by the turn-token estimator: role + the fields it sums. */
43
+ interface TurnTokenMessage {
44
+ role: string;
45
+ hidden?: boolean;
46
+ content?: string;
47
+ blocks?: Array<{
48
+ type: string;
49
+ content?: string;
50
+ }>;
51
+ toolCalls?: Array<{
52
+ input?: unknown;
53
+ streamInputChars?: number;
54
+ }>;
55
+ }
56
+ /**
57
+ * Estimated tokens generated across the WHOLE current turn — the sum of every
58
+ * assistant message back to (but excluding) the last genuine user message. This is
59
+ * what the live activity counter should show: it must span the same turn as the
60
+ * elapsed timer, so it climbs monotonically and merely PLATEAUS during tool-execution
61
+ * gaps (when no message is streaming) instead of vanishing and restarting from ~0 at
62
+ * each new assistant message — the cause of the "counter stops while the timer keeps
63
+ * going" mismatch.
64
+ *
65
+ * The turn boundary is the last NON-hidden user message, so auto-continue rounds
66
+ * (whose injected user messages are hidden) correctly count as part of the same turn.
67
+ * Cost stays O(blocks + toolCalls across the turn) — `estimateStreamTokens` reads
68
+ * O(1) string lengths and WeakMap-cached tool inputs — not O(total content).
69
+ * @param messages - The full ordered message list (newest last).
70
+ * @returns The estimated token count for the in-progress turn.
71
+ */
72
+ export declare function estimateTurnTokens(messages: ReadonlyArray<TurnTokenMessage>): number;
73
+ export {};
74
+ //# sourceMappingURL=chat-stream-utilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-stream-utilities.d.ts","sourceRoot":"","sources":["../../src/components/chat-stream-utilities.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAuBzF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE;IACxC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClD,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAClE,GAAG,MAAM,CAST;AAED,qFAAqF;AACrF,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClD,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAClE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAQpF"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Helpers for the live streaming-message indicator (token estimate).
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Cache of a tool input's serialized character length, keyed by the input object.
8
+ * A completed tool input is immutable, so we serialize it ONCE and reuse the length
9
+ * on every subsequent render. Without this, {@link estimateStreamTokens} ran a full
10
+ * `JSON.stringify` over EVERY tool call's input — and a `write_file` input is the
11
+ * whole file content — on every ~50ms stream flush. That is O(total content) per
12
+ * flush, i.e. O(n²) across a build, which pegged the main thread and froze the IDE
13
+ * on large builds. A WeakMap auto-evicts entries once the input objects are GC'd.
14
+ */
15
+ const toolInputCharCache = new WeakMap();
16
+ /**
17
+ * Character length of a tool call's input, for the live token estimate. While the
18
+ * input is still streaming, counts the bytes seen so far; once the final input has
19
+ * arrived, serializes it ONCE and caches the length by object identity.
20
+ * @param tc - The tool call.
21
+ * @param tc.input - The final tool input, once it has fully arrived.
22
+ * @param tc.streamInputChars - Bytes of the input received so far while streaming.
23
+ * @returns Approximate character length of the input.
24
+ */
25
+ export function toolInputChars(tc) {
26
+ const input = tc.input;
27
+ // Still streaming — the final input object hasn't arrived; count bytes so far.
28
+ if (input === undefined || input === null)
29
+ return tc.streamInputChars ?? 0;
30
+ if (typeof input === 'object') {
31
+ const cached = toolInputCharCache.get(input);
32
+ if (cached !== undefined)
33
+ return cached;
34
+ let len = 0;
35
+ try {
36
+ len = JSON.stringify(input).length;
37
+ }
38
+ catch (_error) {
39
+ // Unserializable input (circular refs, BigInt, etc.) — 0 is a safe fallback;
40
+ // the token estimate degrades gracefully and the caller never throws.
41
+ }
42
+ toolInputCharCache.set(input, len);
43
+ return len;
44
+ }
45
+ try {
46
+ return JSON.stringify(input).length;
47
+ }
48
+ catch (_error) {
49
+ // Unserializable primitive (e.g. BigInt) — 0 is a safe fallback for the token estimate.
50
+ return 0;
51
+ }
52
+ }
53
+ /**
54
+ * Rough estimate of tokens generated so far in a streaming message (~4 chars/token),
55
+ * from the assistant text + thinking content + tool-call inputs. Cheap to call on
56
+ * every stream flush: string lengths are O(1) and each completed tool input's length
57
+ * is cached (see {@link toolInputChars}), so this is O(blocks + toolCalls), NOT
58
+ * O(total content) — the difference between a smooth stream and a frozen tab.
59
+ * @param msg - The streaming assistant message.
60
+ * @param msg.content - Accumulated assistant text.
61
+ * @param msg.blocks - Ordered stream blocks (thinking content counts).
62
+ * @param msg.toolCalls - Tool calls (their inputs count as generated output).
63
+ * @returns The estimated token count.
64
+ */
65
+ export function estimateStreamTokens(msg) {
66
+ let chars = msg.content?.length ?? 0;
67
+ for (const b of msg.blocks ?? []) {
68
+ if (b.type === 'thinking')
69
+ chars += b.content?.length ?? 0;
70
+ }
71
+ for (const tc of msg.toolCalls ?? []) {
72
+ chars += toolInputChars(tc);
73
+ }
74
+ return Math.round(chars / 4);
75
+ }
76
+ /**
77
+ * Estimated tokens generated across the WHOLE current turn — the sum of every
78
+ * assistant message back to (but excluding) the last genuine user message. This is
79
+ * what the live activity counter should show: it must span the same turn as the
80
+ * elapsed timer, so it climbs monotonically and merely PLATEAUS during tool-execution
81
+ * gaps (when no message is streaming) instead of vanishing and restarting from ~0 at
82
+ * each new assistant message — the cause of the "counter stops while the timer keeps
83
+ * going" mismatch.
84
+ *
85
+ * The turn boundary is the last NON-hidden user message, so auto-continue rounds
86
+ * (whose injected user messages are hidden) correctly count as part of the same turn.
87
+ * Cost stays O(blocks + toolCalls across the turn) — `estimateStreamTokens` reads
88
+ * O(1) string lengths and WeakMap-cached tool inputs — not O(total content).
89
+ * @param messages - The full ordered message list (newest last).
90
+ * @returns The estimated token count for the in-progress turn.
91
+ */
92
+ export function estimateTurnTokens(messages) {
93
+ let total = 0;
94
+ for (let i = messages.length - 1; i >= 0; i--) {
95
+ const m = messages[i];
96
+ if (m.role === 'user' && !m.hidden)
97
+ break;
98
+ if (m.role === 'assistant')
99
+ total += estimateStreamTokens(m);
100
+ }
101
+ return total;
102
+ }
103
+ //# sourceMappingURL=chat-stream-utilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-stream-utilities.js","sourceRoot":"","sources":["../../src/components/chat-stream-utilities.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;GAQG;AACH,MAAM,kBAAkB,GAAG,IAAI,OAAO,EAAkB,CAAA;AAExD;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,EAAkD;IAC/E,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAA;IACtB,+EAA+E;IAC/E,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC,gBAAgB,IAAI,CAAC,CAAA;IAC1E,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC5C,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAA;QACvC,IAAI,GAAG,GAAG,CAAC,CAAA;QACX,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,MAAM,CAAA;QACpC,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YAChB,6EAA6E;YAC7E,sEAAsE;QACxE,CAAC;QACD,kBAAkB,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QAClC,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,MAAM,CAAA;IACrC,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,wFAAwF;QACxF,OAAO,CAAC,CAAA;IACV,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAIpC;IACC,IAAI,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,CAAA;IACpC,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;QACjC,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU;YAAE,KAAK,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,CAAA;IAC5D,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;QACrC,KAAK,IAAI,cAAc,CAAC,EAAE,CAAC,CAAA;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;AAC9B,CAAC;AAWD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAyC;IAC1E,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;QACrB,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,MAAM;YAAE,MAAK;QACzC,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW;YAAE,KAAK,IAAI,oBAAoB,CAAC,CAAC,CAAC,CAAA;IAC9D,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}