@desktalk/core 0.1.0-alpha.1

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 (281) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli/index.d.ts +3 -0
  3. package/dist/cli/index.d.ts.map +1 -0
  4. package/dist/cli/index.js +82 -0
  5. package/dist/cli/index.js.map +1 -0
  6. package/dist/frontend/assets/frontend-B3QNYf3p.js +413 -0
  7. package/dist/frontend/assets/frontend-B3QNYf3p.js.map +1 -0
  8. package/dist/frontend/assets/frontend-B4aeXn9d.js +416 -0
  9. package/dist/frontend/assets/frontend-B4aeXn9d.js.map +1 -0
  10. package/dist/frontend/assets/frontend-BwyRSlHp.js +6513 -0
  11. package/dist/frontend/assets/frontend-BwyRSlHp.js.map +1 -0
  12. package/dist/frontend/assets/frontend-Dix2OWTT.js +229 -0
  13. package/dist/frontend/assets/frontend-Dix2OWTT.js.map +1 -0
  14. package/dist/frontend/assets/frontend-Dx41dEM9.js +407 -0
  15. package/dist/frontend/assets/frontend-Dx41dEM9.js.map +1 -0
  16. package/dist/frontend/assets/frontend-WpQng8Mt.js +1991 -0
  17. package/dist/frontend/assets/frontend-WpQng8Mt.js.map +1 -0
  18. package/dist/frontend/assets/frontend-rTwBdJbn.js +2123 -0
  19. package/dist/frontend/assets/frontend-rTwBdJbn.js.map +1 -0
  20. package/dist/frontend/assets/highlighted-body-TPN3WLV5-DD4wpkf4.js +2 -0
  21. package/dist/frontend/assets/highlighted-body-TPN3WLV5-DD4wpkf4.js.map +1 -0
  22. package/dist/frontend/assets/index-C5-XUOS7.js +1863 -0
  23. package/dist/frontend/assets/index-C5-XUOS7.js.map +1 -0
  24. package/dist/frontend/assets/index-C_e3_6yE.css +1 -0
  25. package/dist/frontend/index.html +22 -0
  26. package/dist/frontend/pcm-capture-processor.js +65 -0
  27. package/dist/i18n/manifest.json +34 -0
  28. package/dist/i18n/zh-CN.json +7 -0
  29. package/dist/index.d.ts +6 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +6 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/server/admin-routes.d.ts +14 -0
  34. package/dist/server/admin-routes.d.ts.map +1 -0
  35. package/dist/server/admin-routes.js +118 -0
  36. package/dist/server/admin-routes.js.map +1 -0
  37. package/dist/server/api-routes.d.ts +8 -0
  38. package/dist/server/api-routes.d.ts.map +1 -0
  39. package/dist/server/api-routes.js +203 -0
  40. package/dist/server/api-routes.js.map +1 -0
  41. package/dist/server/auth-routes.d.ts +19 -0
  42. package/dist/server/auth-routes.d.ts.map +1 -0
  43. package/dist/server/auth-routes.js +155 -0
  44. package/dist/server/auth-routes.js.map +1 -0
  45. package/dist/server/dtfs-routes.d.ts +3 -0
  46. package/dist/server/dtfs-routes.d.ts.map +1 -0
  47. package/dist/server/dtfs-routes.js +183 -0
  48. package/dist/server/dtfs-routes.js.map +1 -0
  49. package/dist/server/index.d.ts +15 -0
  50. package/dist/server/index.d.ts.map +1 -0
  51. package/dist/server/index.js +158 -0
  52. package/dist/server/index.js.map +1 -0
  53. package/dist/server/monaco-routes.d.ts +3 -0
  54. package/dist/server/monaco-routes.d.ts.map +1 -0
  55. package/dist/server/monaco-routes.js +45 -0
  56. package/dist/server/monaco-routes.js.map +1 -0
  57. package/dist/server/voice-routes.d.ts +7 -0
  58. package/dist/server/voice-routes.d.ts.map +1 -0
  59. package/dist/server/voice-routes.js +142 -0
  60. package/dist/server/voice-routes.js.map +1 -0
  61. package/dist/server/ws-routes.d.ts +29 -0
  62. package/dist/server/ws-routes.d.ts.map +1 -0
  63. package/dist/server/ws-routes.js +334 -0
  64. package/dist/server/ws-routes.js.map +1 -0
  65. package/dist/services/ai/action-tool.d.ts +9 -0
  66. package/dist/services/ai/action-tool.d.ts.map +1 -0
  67. package/dist/services/ai/action-tool.js +51 -0
  68. package/dist/services/ai/action-tool.js.map +1 -0
  69. package/dist/services/ai/app-tools.d.ts +15 -0
  70. package/dist/services/ai/app-tools.d.ts.map +1 -0
  71. package/dist/services/ai/app-tools.js +119 -0
  72. package/dist/services/ai/app-tools.js.map +1 -0
  73. package/dist/services/ai/chat-service.d.ts +27 -0
  74. package/dist/services/ai/chat-service.d.ts.map +1 -0
  75. package/dist/services/ai/chat-service.js +213 -0
  76. package/dist/services/ai/chat-service.js.map +1 -0
  77. package/dist/services/ai/create-liveapp-tool.d.ts +15 -0
  78. package/dist/services/ai/create-liveapp-tool.d.ts.map +1 -0
  79. package/dist/services/ai/create-liveapp-tool.js +142 -0
  80. package/dist/services/ai/create-liveapp-tool.js.map +1 -0
  81. package/dist/services/ai/desktop-tool.d.ts +27 -0
  82. package/dist/services/ai/desktop-tool.d.ts.map +1 -0
  83. package/dist/services/ai/desktop-tool.js +106 -0
  84. package/dist/services/ai/desktop-tool.js.map +1 -0
  85. package/dist/services/ai/edit-history.d.ts +16 -0
  86. package/dist/services/ai/edit-history.d.ts.map +1 -0
  87. package/dist/services/ai/edit-history.js +137 -0
  88. package/dist/services/ai/edit-history.js.map +1 -0
  89. package/dist/services/ai/edit-tool.d.ts +9 -0
  90. package/dist/services/ai/edit-tool.d.ts.map +1 -0
  91. package/dist/services/ai/edit-tool.js +113 -0
  92. package/dist/services/ai/edit-tool.js.map +1 -0
  93. package/dist/services/ai/generate-html-tool.d.ts +15 -0
  94. package/dist/services/ai/generate-html-tool.d.ts.map +1 -0
  95. package/dist/services/ai/generate-html-tool.js +140 -0
  96. package/dist/services/ai/generate-html-tool.js.map +1 -0
  97. package/dist/services/ai/generate-icon-tool.d.ts +10 -0
  98. package/dist/services/ai/generate-icon-tool.d.ts.map +1 -0
  99. package/dist/services/ai/generate-icon-tool.js +58 -0
  100. package/dist/services/ai/generate-icon-tool.js.map +1 -0
  101. package/dist/services/ai/html-bridge-script.d.ts +2 -0
  102. package/dist/services/ai/html-bridge-script.d.ts.map +1 -0
  103. package/dist/services/ai/html-bridge-script.js +2 -0
  104. package/dist/services/ai/html-bridge-script.js.map +1 -0
  105. package/dist/services/ai/html-guidelines-tool.d.ts +3 -0
  106. package/dist/services/ai/html-guidelines-tool.d.ts.map +1 -0
  107. package/dist/services/ai/html-guidelines-tool.js +157 -0
  108. package/dist/services/ai/html-guidelines-tool.js.map +1 -0
  109. package/dist/services/ai/html-stream-coordinator.d.ts +92 -0
  110. package/dist/services/ai/html-stream-coordinator.d.ts.map +1 -0
  111. package/dist/services/ai/html-stream-coordinator.js +314 -0
  112. package/dist/services/ai/html-stream-coordinator.js.map +1 -0
  113. package/dist/services/ai/html-theme-link.d.ts +9 -0
  114. package/dist/services/ai/html-theme-link.d.ts.map +1 -0
  115. package/dist/services/ai/html-theme-link.js +12 -0
  116. package/dist/services/ai/html-theme-link.js.map +1 -0
  117. package/dist/services/ai/html-ui-script.d.ts +9 -0
  118. package/dist/services/ai/html-ui-script.d.ts.map +1 -0
  119. package/dist/services/ai/html-ui-script.js +9 -0
  120. package/dist/services/ai/html-ui-script.js.map +1 -0
  121. package/dist/services/ai/image-generation-service.d.ts +26 -0
  122. package/dist/services/ai/image-generation-service.d.ts.map +1 -0
  123. package/dist/services/ai/image-generation-service.js +205 -0
  124. package/dist/services/ai/image-generation-service.js.map +1 -0
  125. package/dist/services/ai/manual-pages/desktop-actions.md +36 -0
  126. package/dist/services/ai/manual-pages/desktop-windows.md +24 -0
  127. package/dist/services/ai/manual-pages/dt-badge.md +36 -0
  128. package/dist/services/ai/manual-pages/dt-button.md +38 -0
  129. package/dist/services/ai/manual-pages/dt-card.md +22 -0
  130. package/dist/services/ai/manual-pages/dt-chart.md +261 -0
  131. package/dist/services/ai/manual-pages/dt-divider.md +25 -0
  132. package/dist/services/ai/manual-pages/dt-grid.md +29 -0
  133. package/dist/services/ai/manual-pages/dt-list-view.md +96 -0
  134. package/dist/services/ai/manual-pages/dt-markdown-editor.md +90 -0
  135. package/dist/services/ai/manual-pages/dt-markdown.md +86 -0
  136. package/dist/services/ai/manual-pages/dt-select.md +21 -0
  137. package/dist/services/ai/manual-pages/dt-stack.md +31 -0
  138. package/dist/services/ai/manual-pages/dt-stat.md +43 -0
  139. package/dist/services/ai/manual-pages/dt-table-view.md +120 -0
  140. package/dist/services/ai/manual-pages/dt-tooltip.md +18 -0
  141. package/dist/services/ai/manual-pages/editing-preview.md +25 -0
  142. package/dist/services/ai/manual-pages/html-actions.md +74 -0
  143. package/dist/services/ai/manual-pages/html-bridge.md +187 -0
  144. package/dist/services/ai/manual-pages/html-components.md +66 -0
  145. package/dist/services/ai/manual-pages/html-examples.md +185 -0
  146. package/dist/services/ai/manual-pages/html-layouts.md +128 -0
  147. package/dist/services/ai/manual-pages/html-storage.md +153 -0
  148. package/dist/services/ai/manual-pages/html-tokens.md +26 -0
  149. package/dist/services/ai/manual-pages/index.d.ts +10 -0
  150. package/dist/services/ai/manual-pages/index.d.ts.map +1 -0
  151. package/dist/services/ai/manual-pages/index.js +186 -0
  152. package/dist/services/ai/manual-pages/index.js.map +1 -0
  153. package/dist/services/ai/manual-tool.d.ts +3 -0
  154. package/dist/services/ai/manual-tool.d.ts.map +1 -0
  155. package/dist/services/ai/manual-tool.js +104 -0
  156. package/dist/services/ai/manual-tool.js.map +1 -0
  157. package/dist/services/ai/pi-session-service.d.ts +101 -0
  158. package/dist/services/ai/pi-session-service.d.ts.map +1 -0
  159. package/dist/services/ai/pi-session-service.js +697 -0
  160. package/dist/services/ai/pi-session-service.js.map +1 -0
  161. package/dist/services/ai/providers.d.ts +21 -0
  162. package/dist/services/ai/providers.d.ts.map +1 -0
  163. package/dist/services/ai/providers.js +93 -0
  164. package/dist/services/ai/providers.js.map +1 -0
  165. package/dist/services/ai/redo-edit-tool.d.ts +9 -0
  166. package/dist/services/ai/redo-edit-tool.d.ts.map +1 -0
  167. package/dist/services/ai/redo-edit-tool.js +44 -0
  168. package/dist/services/ai/redo-edit-tool.js.map +1 -0
  169. package/dist/services/ai/system-prompt.d.ts +7 -0
  170. package/dist/services/ai/system-prompt.d.ts.map +1 -0
  171. package/dist/services/ai/system-prompt.js +72 -0
  172. package/dist/services/ai/system-prompt.js.map +1 -0
  173. package/dist/services/ai/undo-edit-tool.d.ts +9 -0
  174. package/dist/services/ai/undo-edit-tool.d.ts.map +1 -0
  175. package/dist/services/ai/undo-edit-tool.js +44 -0
  176. package/dist/services/ai/undo-edit-tool.js.map +1 -0
  177. package/dist/services/ai/window-tools.d.ts +27 -0
  178. package/dist/services/ai/window-tools.d.ts.map +1 -0
  179. package/dist/services/ai/window-tools.js +155 -0
  180. package/dist/services/ai/window-tools.js.map +1 -0
  181. package/dist/services/backend-host.d.ts +10 -0
  182. package/dist/services/backend-host.d.ts.map +1 -0
  183. package/dist/services/backend-host.js +117 -0
  184. package/dist/services/backend-host.js.map +1 -0
  185. package/dist/services/backend-ipc.d.ts +60 -0
  186. package/dist/services/backend-ipc.d.ts.map +1 -0
  187. package/dist/services/backend-ipc.js +2 -0
  188. package/dist/services/backend-ipc.js.map +1 -0
  189. package/dist/services/backend-process-manager.d.ts +60 -0
  190. package/dist/services/backend-process-manager.d.ts.map +1 -0
  191. package/dist/services/backend-process-manager.js +203 -0
  192. package/dist/services/backend-process-manager.js.map +1 -0
  193. package/dist/services/filesystem.d.ts +8 -0
  194. package/dist/services/filesystem.d.ts.map +1 -0
  195. package/dist/services/filesystem.js +94 -0
  196. package/dist/services/filesystem.js.map +1 -0
  197. package/dist/services/i18n.d.ts +28 -0
  198. package/dist/services/i18n.d.ts.map +1 -0
  199. package/dist/services/i18n.js +76 -0
  200. package/dist/services/i18n.js.map +1 -0
  201. package/dist/services/liveapp-icon.d.ts +9 -0
  202. package/dist/services/liveapp-icon.d.ts.map +1 -0
  203. package/dist/services/liveapp-icon.js +28 -0
  204. package/dist/services/liveapp-icon.js.map +1 -0
  205. package/dist/services/liveapps.d.ts +12 -0
  206. package/dist/services/liveapps.d.ts.map +1 -0
  207. package/dist/services/liveapps.js +84 -0
  208. package/dist/services/liveapps.js.map +1 -0
  209. package/dist/services/logger.d.ts +34 -0
  210. package/dist/services/logger.d.ts.map +1 -0
  211. package/dist/services/logger.js +74 -0
  212. package/dist/services/logger.js.map +1 -0
  213. package/dist/services/messaging.d.ts +14 -0
  214. package/dist/services/messaging.d.ts.map +1 -0
  215. package/dist/services/messaging.js +41 -0
  216. package/dist/services/messaging.js.map +1 -0
  217. package/dist/services/miniapp-icon.d.ts +9 -0
  218. package/dist/services/miniapp-icon.d.ts.map +1 -0
  219. package/dist/services/miniapp-icon.js +25 -0
  220. package/dist/services/miniapp-icon.js.map +1 -0
  221. package/dist/services/miniapp-registry.d.ts +73 -0
  222. package/dist/services/miniapp-registry.d.ts.map +1 -0
  223. package/dist/services/miniapp-registry.js +144 -0
  224. package/dist/services/miniapp-registry.js.map +1 -0
  225. package/dist/services/onboarding-config.d.ts +37 -0
  226. package/dist/services/onboarding-config.d.ts.map +1 -0
  227. package/dist/services/onboarding-config.js +76 -0
  228. package/dist/services/onboarding-config.js.map +1 -0
  229. package/dist/services/preferences.d.ts +10 -0
  230. package/dist/services/preferences.d.ts.map +1 -0
  231. package/dist/services/preferences.js +48 -0
  232. package/dist/services/preferences.js.map +1 -0
  233. package/dist/services/proxy-dispatcher.d.ts +18 -0
  234. package/dist/services/proxy-dispatcher.d.ts.map +1 -0
  235. package/dist/services/proxy-dispatcher.js +33 -0
  236. package/dist/services/proxy-dispatcher.js.map +1 -0
  237. package/dist/services/storage.d.ts +7 -0
  238. package/dist/services/storage.d.ts.map +1 -0
  239. package/dist/services/storage.js +55 -0
  240. package/dist/services/storage.js.map +1 -0
  241. package/dist/services/theme-css.d.ts +21 -0
  242. package/dist/services/theme-css.d.ts.map +1 -0
  243. package/dist/services/theme-css.js +213 -0
  244. package/dist/services/theme-css.js.map +1 -0
  245. package/dist/services/user-db.d.ts +69 -0
  246. package/dist/services/user-db.d.ts.map +1 -0
  247. package/dist/services/user-db.js +175 -0
  248. package/dist/services/user-db.js.map +1 -0
  249. package/dist/services/voice/audio-format.d.ts +5 -0
  250. package/dist/services/voice/audio-format.d.ts.map +1 -0
  251. package/dist/services/voice/audio-format.js +27 -0
  252. package/dist/services/voice/audio-format.js.map +1 -0
  253. package/dist/services/voice/azure-openai-whisper-adapter.d.ts +23 -0
  254. package/dist/services/voice/azure-openai-whisper-adapter.d.ts.map +1 -0
  255. package/dist/services/voice/azure-openai-whisper-adapter.js +60 -0
  256. package/dist/services/voice/azure-openai-whisper-adapter.js.map +1 -0
  257. package/dist/services/voice/openai-whisper-adapter.d.ts +22 -0
  258. package/dist/services/voice/openai-whisper-adapter.d.ts.map +1 -0
  259. package/dist/services/voice/openai-whisper-adapter.js +61 -0
  260. package/dist/services/voice/openai-whisper-adapter.js.map +1 -0
  261. package/dist/services/voice/stt-adapter.d.ts +31 -0
  262. package/dist/services/voice/stt-adapter.d.ts.map +1 -0
  263. package/dist/services/voice/stt-adapter.js +8 -0
  264. package/dist/services/voice/stt-adapter.js.map +1 -0
  265. package/dist/services/voice/vad-segmenter.d.ts +68 -0
  266. package/dist/services/voice/vad-segmenter.d.ts.map +1 -0
  267. package/dist/services/voice/vad-segmenter.js +159 -0
  268. package/dist/services/voice/vad-segmenter.js.map +1 -0
  269. package/dist/services/voice/voice-session.d.ts +54 -0
  270. package/dist/services/voice/voice-session.d.ts.map +1 -0
  271. package/dist/services/voice/voice-session.js +137 -0
  272. package/dist/services/voice/voice-session.js.map +1 -0
  273. package/dist/services/window-manager.d.ts +94 -0
  274. package/dist/services/window-manager.d.ts.map +1 -0
  275. package/dist/services/window-manager.js +282 -0
  276. package/dist/services/window-manager.js.map +1 -0
  277. package/dist/services/workspace.d.ts +51 -0
  278. package/dist/services/workspace.d.ts.map +1 -0
  279. package/dist/services/workspace.js +144 -0
  280. package/dist/services/workspace.js.map +1 -0
  281. package/package.json +89 -0
@@ -0,0 +1,697 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { AuthStorage, createAgentSession, DefaultResourceLoader, ModelRegistry, readTool, writeTool, bashTool, grepTool, findTool, lsTool, SessionManager, } from '@mariozechner/pi-coding-agent';
4
+ import { createDesktopTool } from './desktop-tool.js';
5
+ import { createActionTool } from './action-tool.js';
6
+ import { createLiveAppTool } from './create-liveapp-tool.js';
7
+ import { createReadManualTool } from './manual-tool.js';
8
+ import { HtmlStreamCoordinator } from './html-stream-coordinator.js';
9
+ import { createEditTool } from './edit-tool.js';
10
+ import { createUndoEditTool } from './undo-edit-tool.js';
11
+ import { createRedoEditTool } from './redo-edit-tool.js';
12
+ import { ImageGenerationService } from './image-generation-service.js';
13
+ import { createGenerateIconTool } from './generate-icon-tool.js';
14
+ import { EditHistory, createManagedPathResolver } from './edit-history.js';
15
+ import { AI_PROVIDER_DEFINITIONS, getAiProviderPreferences, getAllAiProviderPreferences, getDefaultAiProvider, } from './providers.js';
16
+ import { registry } from '../miniapp-registry.js';
17
+ import { getUserHomeDir } from '../workspace.js';
18
+ import { DESKTALK_SYSTEM_PROMPT } from './system-prompt.js';
19
+ import { listLiveApps } from '../liveapps.js';
20
+ function isUserMessage(message) {
21
+ return message.role === 'user';
22
+ }
23
+ function isAssistantMessage(message) {
24
+ return message.role === 'assistant';
25
+ }
26
+ function getMessageKey(role, timestamp) {
27
+ return `${role}:${timestamp}`;
28
+ }
29
+ /**
30
+ * Strip the `[Desktop Context]...[/Desktop Context]` block that we prepend
31
+ * to every user message before sending it to pi. The frontend should never
32
+ * display this internal metadata.
33
+ */
34
+ const DESKTOP_CONTEXT_RE = /\[Desktop (?:Context|Content)\][\s\S]*?\[\/Desktop (?:Context|Content)\]\s*/;
35
+ function stripSessionTitleMetadata(text) {
36
+ return stripDesktopContext(text)
37
+ .replace(/^\[Desktop (?:Context|Content)\]\s*/i, '')
38
+ .trim();
39
+ }
40
+ function stripDesktopContext(text) {
41
+ return text.replace(DESKTOP_CONTEXT_RE, '').trim();
42
+ }
43
+ /**
44
+ * Build a short summary of generated HTML content for the conversation history.
45
+ *
46
+ * This replaces the full HTML string in `ToolCall.arguments.content` so that:
47
+ * 1. The LLM doesn't re-read (and anchor on) its own previous output.
48
+ * 2. We save tokens on every subsequent round-trip.
49
+ *
50
+ * The summary preserves the document structure (headings, dt-card titles,
51
+ * tag counts) so the LLM still knows *what* it generated, just not the
52
+ * exact markup.
53
+ */
54
+ export function summarizeHtml(html) {
55
+ const lines = [];
56
+ // Byte length
57
+ const bytes = Buffer.byteLength(html, 'utf-8');
58
+ const sizeLabel = bytes >= 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${bytes} B`;
59
+ lines.push(`[HTML content removed from context to save tokens — ${sizeLabel}]`);
60
+ // Extract <title>
61
+ const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
62
+ if (titleMatch) {
63
+ lines.push(`Title: ${titleMatch[1].trim()}`);
64
+ }
65
+ // Count dt-card elements
66
+ const cardMatches = html.match(/<dt-card[\s>]/gi);
67
+ if (cardMatches) {
68
+ lines.push(`Sections: ${cardMatches.length} dt-card(s)`);
69
+ }
70
+ // Extract headings (h1–h6 text, up to 8)
71
+ const headingRe = /<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi;
72
+ const headings = [];
73
+ let hMatch;
74
+ while ((hMatch = headingRe.exec(html)) !== null && headings.length < 8) {
75
+ const text = hMatch[2].replace(/<[^>]+>/g, '').trim();
76
+ if (text)
77
+ headings.push(` h${hMatch[1]}: ${text}`);
78
+ }
79
+ if (headings.length > 0) {
80
+ lines.push(`Headings:\n${headings.join('\n')}`);
81
+ }
82
+ return lines.join('\n');
83
+ }
84
+ /**
85
+ * Scrub `create_liveapp` tool-call arguments in an assistant message so the
86
+ * full HTML is not re-sent to the LLM on subsequent turns.
87
+ *
88
+ * Mutates `message.content` in-place — this must be called synchronously in
89
+ * the `message_end` subscriber, **before** session persistence runs.
90
+ */
91
+ export function scrubHtmlToolCallArgs(message) {
92
+ for (const block of message.content) {
93
+ if (block.type === 'toolCall' &&
94
+ block.name === 'create_liveapp' &&
95
+ typeof block.arguments?.content === 'string') {
96
+ const toolCall = block;
97
+ const originalHtml = toolCall.arguments.content;
98
+ toolCall.arguments.content = summarizeHtml(originalHtml);
99
+ }
100
+ }
101
+ }
102
+ function getMessageText(message) {
103
+ if (message.role === 'user') {
104
+ if (typeof message.content === 'string') {
105
+ return message.content.trim();
106
+ }
107
+ return message.content
108
+ .map((part) => (typeof part.text === 'string' ? part.text : ''))
109
+ .join('')
110
+ .trim();
111
+ }
112
+ if (message.role === 'assistant') {
113
+ return message.content
114
+ .map((part) => {
115
+ if (part.type === 'text' && typeof part.text === 'string') {
116
+ return part.text;
117
+ }
118
+ return '';
119
+ })
120
+ .join('')
121
+ .trim();
122
+ }
123
+ return '';
124
+ }
125
+ function readMetadata(filePath) {
126
+ if (!existsSync(filePath)) {
127
+ return { byKey: {} };
128
+ }
129
+ try {
130
+ return JSON.parse(readFileSync(filePath, 'utf-8'));
131
+ }
132
+ catch {
133
+ return { byKey: {} };
134
+ }
135
+ }
136
+ function writeMetadata(filePath, store) {
137
+ mkdirSync(dirname(filePath), { recursive: true });
138
+ writeFileSync(filePath, JSON.stringify(store, null, 2), 'utf-8');
139
+ }
140
+ function shouldRegisterProviderBaseUrl(provider, baseUrl) {
141
+ return (Boolean(baseUrl) &&
142
+ [
143
+ 'azure-openai-responses',
144
+ 'openai',
145
+ 'mistral',
146
+ 'groq',
147
+ 'cerebras',
148
+ 'xai',
149
+ 'openrouter',
150
+ 'vercel-ai-gateway',
151
+ 'huggingface',
152
+ 'ollama',
153
+ ].includes(provider));
154
+ }
155
+ function normalizeSessionTitle(value) {
156
+ return stripSessionTitleMetadata(value)
157
+ .replace(/\s+/g, ' ')
158
+ .replace(/[.!?]+$/g, '')
159
+ .trim();
160
+ }
161
+ function generateSessionTitle(prompt) {
162
+ const normalized = normalizeSessionTitle(prompt)
163
+ .replace(/^[#>*\-\d.\s]+/, '')
164
+ .replace(/`+/g, '');
165
+ if (!normalized) {
166
+ return 'New session';
167
+ }
168
+ if (normalized.length <= 48) {
169
+ return normalized;
170
+ }
171
+ const truncated = normalized.slice(0, 48);
172
+ const lastSpace = truncated.lastIndexOf(' ');
173
+ const title = lastSpace >= 24 ? truncated.slice(0, lastSpace) : truncated;
174
+ return `${title.trim()}...`;
175
+ }
176
+ function toSessionSummary(info, isCurrent) {
177
+ const fallbackLabel = info.firstMessage.trim() || 'New session';
178
+ return {
179
+ id: info.id,
180
+ label: normalizeSessionTitle(info.name ?? fallbackLabel) ||
181
+ (isCurrent ? 'Current session' : 'New session'),
182
+ createdAt: info.created.getTime(),
183
+ updatedAt: info.modified.getTime(),
184
+ };
185
+ }
186
+ export class PiSessionService {
187
+ authStorage;
188
+ modelRegistry;
189
+ metadataFilePath;
190
+ getPreference;
191
+ session;
192
+ cwd;
193
+ sessionDir;
194
+ resourceLoader;
195
+ windowManager;
196
+ htmlStreamCoordinator;
197
+ log;
198
+ customTools;
199
+ getCurrentUsername;
200
+ constructor(options) {
201
+ this.session = options.session;
202
+ this.authStorage = options.authStorage;
203
+ this.modelRegistry = options.modelRegistry;
204
+ this.metadataFilePath = options.metadataFilePath;
205
+ this.getPreference = options.getPreference;
206
+ this.cwd = options.cwd;
207
+ this.sessionDir = options.sessionDir;
208
+ this.resourceLoader = options.resourceLoader;
209
+ this.windowManager = options.windowManager;
210
+ this.htmlStreamCoordinator = options.htmlStreamCoordinator;
211
+ this.log = options.logger;
212
+ this.customTools = options.customTools;
213
+ this.getCurrentUsername = options.getCurrentUsername;
214
+ }
215
+ static async create(workspacePaths, getPreference, windowManager, invokeAction, sendAiCommand, getCurrentUsername, logger) {
216
+ const authStorage = AuthStorage.create(join(workspacePaths.config, 'pi-auth.json'));
217
+ const modelRegistry = new ModelRegistry(authStorage);
218
+ const sessionDir = join(workspacePaths.data, 'ai-sessions');
219
+ mkdirSync(sessionDir, { recursive: true });
220
+ const sessionManager = SessionManager.continueRecent(process.cwd(), sessionDir);
221
+ const allProviderPreferences = await getAllAiProviderPreferences(getPreference);
222
+ for (const config of allProviderPreferences) {
223
+ if (config.apiKey) {
224
+ authStorage.setRuntimeApiKey(config.provider, config.apiKey);
225
+ }
226
+ if (shouldRegisterProviderBaseUrl(config.provider, config.baseUrl)) {
227
+ modelRegistry.registerProvider(config.provider, {
228
+ baseUrl: config.baseUrl,
229
+ ...(config.apiKey ? { apiKey: config.apiKey } : {}),
230
+ authHeader: config.provider !== 'ollama',
231
+ });
232
+ }
233
+ }
234
+ const defaultProvider = await getDefaultAiProvider(getPreference);
235
+ const defaultProviderPreferences = await getAiProviderPreferences(getPreference, defaultProvider);
236
+ const initialModel = defaultProviderPreferences.model
237
+ ? modelRegistry.find(defaultProvider, defaultProviderPreferences.model)
238
+ : modelRegistry.getAvailable().find((model) => model.provider === defaultProvider);
239
+ const resourceLoader = new DefaultResourceLoader({
240
+ cwd: process.cwd(),
241
+ additionalSkillPaths: [join(workspacePaths.data, 'skills')],
242
+ appendSystemPromptOverride: (base) => [...base, DESKTALK_SYSTEM_PROMPT],
243
+ });
244
+ await resourceLoader.reload();
245
+ const htmlStreamCoordinator = new HtmlStreamCoordinator({
246
+ sendAiCommand,
247
+ activateMiniApp: (miniAppId) => {
248
+ registry.activate(miniAppId, getCurrentUsername());
249
+ },
250
+ getPreference,
251
+ logger: logger.child({ scope: 'html-stream' }),
252
+ });
253
+ const resolveManagedPath = (inputPath) => createManagedPathResolver([join(workspacePaths.data, 'home', getCurrentUsername())])(inputPath);
254
+ const editHistory = new EditHistory(resolveManagedPath);
255
+ const imageGenerationService = new ImageGenerationService({
256
+ modelRegistry,
257
+ getPreference,
258
+ logger: logger.child({ scope: 'image-generation' }),
259
+ });
260
+ const customTools = [
261
+ createDesktopTool({
262
+ windowManager,
263
+ getMiniApps: () => registry.getManifests(),
264
+ getLiveApps: () => listLiveApps(getUserHomeDir(getCurrentUsername())).map((app) => ({
265
+ id: app.id,
266
+ name: app.name,
267
+ })),
268
+ activateMiniApp: (miniAppId) => {
269
+ registry.activate(miniAppId, getCurrentUsername());
270
+ },
271
+ sendAiCommand,
272
+ }),
273
+ createActionTool({
274
+ windowManager,
275
+ invokeAction,
276
+ }),
277
+ createLiveAppTool({
278
+ sendAiCommand,
279
+ activateMiniApp: (miniAppId) => {
280
+ registry.activate(miniAppId, getCurrentUsername());
281
+ },
282
+ getPreference,
283
+ streamCoordinator: htmlStreamCoordinator,
284
+ }),
285
+ createGenerateIconTool({
286
+ imageGenerationService,
287
+ getCurrentUsername,
288
+ workspaceDataDir: workspacePaths.data,
289
+ }),
290
+ createEditTool({
291
+ editHistory,
292
+ resolvePath: resolveManagedPath,
293
+ }),
294
+ createUndoEditTool({
295
+ editHistory,
296
+ resolvePath: resolveManagedPath,
297
+ }),
298
+ createRedoEditTool({
299
+ editHistory,
300
+ resolvePath: resolveManagedPath,
301
+ }),
302
+ createReadManualTool(),
303
+ ];
304
+ const { session } = await createAgentSession({
305
+ cwd: process.cwd(),
306
+ authStorage,
307
+ modelRegistry,
308
+ model: initialModel,
309
+ sessionManager,
310
+ tools: [readTool, writeTool, bashTool, grepTool, findTool, lsTool],
311
+ customTools,
312
+ resourceLoader,
313
+ });
314
+ return new PiSessionService({
315
+ session,
316
+ authStorage,
317
+ modelRegistry,
318
+ metadataFilePath: join(workspacePaths.data, 'storage', 'ai-message-metadata.json'),
319
+ getPreference,
320
+ cwd: process.cwd(),
321
+ sessionDir,
322
+ resourceLoader,
323
+ windowManager,
324
+ htmlStreamCoordinator,
325
+ logger,
326
+ customTools,
327
+ getCurrentUsername,
328
+ });
329
+ }
330
+ async createSessionWithManager(sessionManager) {
331
+ const { session } = await createAgentSession({
332
+ cwd: this.cwd,
333
+ authStorage: this.authStorage,
334
+ modelRegistry: this.modelRegistry,
335
+ model: this.session.model,
336
+ sessionManager,
337
+ tools: [readTool, writeTool, bashTool, grepTool, findTool, lsTool],
338
+ customTools: this.customTools,
339
+ resourceLoader: this.resourceLoader,
340
+ });
341
+ return session;
342
+ }
343
+ getSessionId() {
344
+ return this.session.sessionId;
345
+ }
346
+ async listSessions() {
347
+ const sessions = await SessionManager.list(this.cwd, this.sessionDir);
348
+ return sessions
349
+ .sort((left, right) => right.modified.getTime() - left.modified.getTime())
350
+ .map((info) => toSessionSummary(info, info.id === this.getSessionId()));
351
+ }
352
+ async createNewSession() {
353
+ this.session = await this.createSessionWithManager(SessionManager.create(this.cwd, this.sessionDir));
354
+ const summary = (await this.listSessions()).find((session) => session.id === this.getSessionId());
355
+ return (summary ?? {
356
+ id: this.getSessionId(),
357
+ label: 'New session',
358
+ createdAt: Date.now(),
359
+ updatedAt: Date.now(),
360
+ });
361
+ }
362
+ async switchSession(sessionId) {
363
+ const sessions = await SessionManager.list(this.cwd, this.sessionDir);
364
+ const target = sessions.find((entry) => entry.id === sessionId);
365
+ if (!target) {
366
+ return false;
367
+ }
368
+ this.session = await this.createSessionWithManager(SessionManager.open(target.path, this.sessionDir));
369
+ return true;
370
+ }
371
+ async renameCurrentSession(title) {
372
+ const nextTitle = normalizeSessionTitle(title) || 'New session';
373
+ this.session.sessionManager.appendSessionInfo(nextTitle);
374
+ const summary = (await this.listSessions()).find((session) => session.id === this.getSessionId());
375
+ return (summary ?? {
376
+ id: this.getSessionId(),
377
+ label: nextTitle,
378
+ createdAt: Date.now(),
379
+ updatedAt: Date.now(),
380
+ });
381
+ }
382
+ async abort() {
383
+ await this.session.abort();
384
+ }
385
+ maybeAssignSessionTitle(inputText) {
386
+ if (this.session.sessionManager.getSessionName()) {
387
+ return;
388
+ }
389
+ const hasHistory = this.session.messages.some((message) => message.role === 'user');
390
+ if (hasHistory) {
391
+ return;
392
+ }
393
+ const title = generateSessionTitle(inputText);
394
+ if (title) {
395
+ this.session.sessionManager.appendSessionInfo(title);
396
+ }
397
+ }
398
+ async getProviderOptions() {
399
+ await this.syncProviderCredentials();
400
+ const defaultProvider = await getDefaultAiProvider(this.getPreference);
401
+ const availableByProvider = new Set(this.modelRegistry.getAvailable().map((model) => model.provider));
402
+ const configuredProviders = await getAllAiProviderPreferences(this.getPreference);
403
+ return {
404
+ defaultProvider,
405
+ providers: AI_PROVIDER_DEFINITIONS.map((provider) => {
406
+ const config = configuredProviders.find((entry) => entry.provider === provider.id);
407
+ return {
408
+ id: provider.id,
409
+ label: provider.label,
410
+ configured: availableByProvider.has(provider.id) ||
411
+ Boolean(config?.model.trim()) ||
412
+ Boolean(config?.apiKey.trim()) ||
413
+ Boolean(config?.baseUrl.trim()),
414
+ model: config?.model ?? '',
415
+ };
416
+ }),
417
+ };
418
+ }
419
+ async syncProviderCredentials() {
420
+ const configuredProviders = await getAllAiProviderPreferences(this.getPreference);
421
+ for (const config of configuredProviders) {
422
+ if (config.apiKey) {
423
+ this.authStorage.setRuntimeApiKey(config.provider, config.apiKey);
424
+ }
425
+ else {
426
+ this.authStorage.removeRuntimeApiKey(config.provider);
427
+ }
428
+ if (shouldRegisterProviderBaseUrl(config.provider, config.baseUrl)) {
429
+ this.modelRegistry.registerProvider(config.provider, {
430
+ baseUrl: config.baseUrl,
431
+ ...(config.apiKey ? { apiKey: config.apiKey } : {}),
432
+ authHeader: config.provider !== 'ollama',
433
+ });
434
+ }
435
+ else {
436
+ this.modelRegistry.unregisterProvider(config.provider);
437
+ }
438
+ }
439
+ }
440
+ async syncPreferences(providerOverride) {
441
+ await this.resourceLoader.reload();
442
+ await this.syncProviderCredentials();
443
+ const configuredProvider = providerOverride ?? (await getDefaultAiProvider(this.getPreference));
444
+ const configuredModel = (await getAiProviderPreferences(this.getPreference, configuredProvider))
445
+ .model;
446
+ const targetModel = configuredModel
447
+ ? this.modelRegistry.find(configuredProvider, configuredModel)
448
+ : this.modelRegistry.getAvailable().find((model) => model.provider === configuredProvider);
449
+ if (!targetModel) {
450
+ throw new Error(configuredModel
451
+ ? `Configured model not found: ${configuredProvider}/${configuredModel}`
452
+ : `No model available for ${configuredProvider}. Configure a model in Preferences -> AI.`);
453
+ }
454
+ if (!this.session.model ||
455
+ this.session.model.provider !== targetModel.provider ||
456
+ this.session.model.id !== targetModel.id) {
457
+ await this.session.setModel(targetModel);
458
+ }
459
+ }
460
+ saveMessageMetadata(role, timestamp, metadata) {
461
+ const store = readMetadata(this.metadataFilePath);
462
+ store.byKey[getMessageKey(role, timestamp)] = metadata;
463
+ writeMetadata(this.metadataFilePath, store);
464
+ }
465
+ getMessageMetadata(role, timestamp) {
466
+ return readMetadata(this.metadataFilePath).byKey[getMessageKey(role, timestamp)];
467
+ }
468
+ getHistory() {
469
+ const result = [];
470
+ for (const message of this.session.messages) {
471
+ if (message.role !== 'user' && message.role !== 'assistant') {
472
+ continue;
473
+ }
474
+ const typedMessage = message;
475
+ const role = typedMessage.role;
476
+ const metadata = this.getMessageMetadata(role, typedMessage.timestamp);
477
+ if (isUserMessage(typedMessage)) {
478
+ const content = stripDesktopContext(getMessageText(typedMessage));
479
+ result.push({
480
+ id: getMessageKey(role, typedMessage.timestamp),
481
+ role,
482
+ content,
483
+ timestamp: typedMessage.timestamp,
484
+ source: metadata?.source ?? 'text',
485
+ });
486
+ continue;
487
+ }
488
+ if (isAssistantMessage(typedMessage)) {
489
+ const baseId = getMessageKey(role, typedMessage.timestamp);
490
+ const assistantFields = {
491
+ provider: typedMessage.provider,
492
+ model: typedMessage.model,
493
+ totalTokens: typedMessage.usage.total,
494
+ };
495
+ // Walk through content blocks and emit separate entries for text vs tool calls
496
+ let textAccumulator = '';
497
+ let thinkingAccumulator = '';
498
+ let blockIndex = 0;
499
+ for (const block of typedMessage.content) {
500
+ if (block.type === 'thinking') {
501
+ const thinkingBlock = block;
502
+ if (typeof thinkingBlock.thinking === 'string') {
503
+ thinkingAccumulator += thinkingBlock.thinking;
504
+ }
505
+ }
506
+ else if (block.type === 'text' && typeof block.text === 'string') {
507
+ textAccumulator += block.text;
508
+ }
509
+ else if (block.type === 'toolCall') {
510
+ // Flush accumulated text before the tool call
511
+ const trimmedText = textAccumulator.trim();
512
+ const trimmedThinking = thinkingAccumulator.trim();
513
+ if (trimmedText || trimmedThinking) {
514
+ result.push({
515
+ id: `${baseId}:text-${blockIndex}`,
516
+ role,
517
+ content: trimmedText,
518
+ timestamp: typedMessage.timestamp,
519
+ ...assistantFields,
520
+ ...(trimmedThinking ? { thinkingContent: trimmedThinking } : {}),
521
+ });
522
+ blockIndex += 1;
523
+ }
524
+ textAccumulator = '';
525
+ thinkingAccumulator = '';
526
+ // Emit tool call as a standalone message
527
+ const toolBlock = block;
528
+ result.push({
529
+ id: `${baseId}:tool-${blockIndex}`,
530
+ role,
531
+ content: '',
532
+ timestamp: typedMessage.timestamp,
533
+ ...assistantFields,
534
+ toolCall: {
535
+ toolName: toolBlock.name,
536
+ params: toolBlock.arguments ?? {},
537
+ },
538
+ });
539
+ blockIndex += 1;
540
+ }
541
+ }
542
+ // Flush any remaining text after the last tool call
543
+ const trimmedText = textAccumulator.trim();
544
+ const trimmedThinking = thinkingAccumulator.trim();
545
+ if (trimmedText || trimmedThinking) {
546
+ result.push({
547
+ id: blockIndex > 0 ? `${baseId}:text-${blockIndex}` : baseId,
548
+ role,
549
+ content: trimmedText,
550
+ timestamp: typedMessage.timestamp,
551
+ ...assistantFields,
552
+ ...(trimmedThinking ? { thinkingContent: trimmedThinking } : {}),
553
+ });
554
+ }
555
+ else if (blockIndex === 0) {
556
+ // No content blocks produced anything — emit empty message so the UI
557
+ // can show a placeholder if needed
558
+ result.push({
559
+ id: baseId,
560
+ role,
561
+ content: '',
562
+ timestamp: typedMessage.timestamp,
563
+ ...assistantFields,
564
+ });
565
+ }
566
+ }
567
+ }
568
+ return result;
569
+ }
570
+ async prompt(input, callbacks) {
571
+ await this.syncPreferences(input.provider);
572
+ this.maybeAssignSessionTitle(input.text);
573
+ const { onEvent } = callbacks;
574
+ let pendingUserSource = input.source;
575
+ let currentAssistantText = '';
576
+ let currentThinkingText = '';
577
+ const unsubscribe = this.session.subscribe((event) => {
578
+ if (event.type === 'message_start' &&
579
+ isAssistantMessage(event.message)) {
580
+ const message = event.message;
581
+ currentAssistantText = '';
582
+ currentThinkingText = '';
583
+ onEvent({
584
+ type: 'message_start',
585
+ provider: message.provider,
586
+ model: message.model,
587
+ });
588
+ return;
589
+ }
590
+ if (event.type === 'message_update' &&
591
+ isAssistantMessage(event.message)) {
592
+ const message = event.message;
593
+ const msgEvent = event.assistantMessageEvent;
594
+ if (msgEvent.type === 'thinking_delta') {
595
+ currentThinkingText += msgEvent.delta;
596
+ onEvent({
597
+ type: 'thinking_update',
598
+ thinkingText: currentThinkingText,
599
+ provider: message.provider,
600
+ model: message.model,
601
+ });
602
+ }
603
+ if (msgEvent.type === 'text_delta') {
604
+ currentAssistantText += msgEvent.delta;
605
+ onEvent({
606
+ type: 'message_update',
607
+ text: currentAssistantText,
608
+ provider: message.provider,
609
+ model: message.model,
610
+ });
611
+ }
612
+ // ── HTML streaming via toolcall events ──────────────────────
613
+ if (msgEvent.type === 'toolcall_start') {
614
+ const partialMsg = msgEvent.partial;
615
+ const toolContent = partialMsg.content[msgEvent.contentIndex];
616
+ this.log.debug({
617
+ toolName: toolContent?.name,
618
+ toolType: toolContent?.type,
619
+ contentIndex: msgEvent.contentIndex,
620
+ }, 'toolcall_start received');
621
+ if (toolContent &&
622
+ toolContent.type === 'toolCall' &&
623
+ toolContent.name === 'create_liveapp') {
624
+ this.log.debug('detected create_liveapp — calling onToolcallStart()');
625
+ this.htmlStreamCoordinator.onToolcallStart();
626
+ }
627
+ }
628
+ if (msgEvent.type === 'toolcall_delta') {
629
+ const session = this.htmlStreamCoordinator.getActiveSession();
630
+ if (session && session.state === 'streaming') {
631
+ this.htmlStreamCoordinator.onToolcallDelta(msgEvent.delta);
632
+ }
633
+ }
634
+ // Emit structured tool_call event so the frontend can render it as a standalone row
635
+ if (msgEvent.type === 'toolcall_end') {
636
+ const toolCall = msgEvent.toolCall;
637
+ if (toolCall) {
638
+ // Reset accumulated text — the text before this tool call is already sent;
639
+ // any text after will start fresh.
640
+ currentAssistantText = '';
641
+ onEvent({
642
+ type: 'tool_call',
643
+ toolCall: {
644
+ toolName: toolCall.name,
645
+ params: toolCall.arguments ?? {},
646
+ },
647
+ });
648
+ }
649
+ }
650
+ return;
651
+ }
652
+ if (event.type === 'message_end') {
653
+ if (isUserMessage(event.message) && pendingUserSource) {
654
+ this.saveMessageMetadata('user', event.message.timestamp, {
655
+ source: pendingUserSource,
656
+ });
657
+ pendingUserSource = null;
658
+ return;
659
+ }
660
+ if (isAssistantMessage(event.message)) {
661
+ const message = event.message;
662
+ // Scrub large HTML content from create_liveapp tool calls so it
663
+ // is not re-sent on every subsequent LLM round-trip. This runs
664
+ // synchronously before session persistence (step 8c in the event
665
+ // pipeline) so both in-memory state and the session file reflect
666
+ // the summarised version.
667
+ // scrubHtmlToolCallArgs(event.message as unknown as AssistantMessage);
668
+ onEvent({
669
+ type: 'message_end',
670
+ text: getMessageText(message),
671
+ provider: message.provider,
672
+ model: message.model,
673
+ usage: {
674
+ totalTokens: message.usage.total,
675
+ },
676
+ });
677
+ }
678
+ }
679
+ });
680
+ try {
681
+ // Prepend the dynamic desktop context to the user's message so the AI
682
+ // always sees the current windows, MiniApps, and available actions
683
+ // without requiring an extra tool call.
684
+ const userHomeDir = getUserHomeDir(this.getCurrentUsername());
685
+ const desktopContext = this.windowManager.getDesktopContext(registry.getManifests(), listLiveApps(userHomeDir).map((app) => ({
686
+ id: app.id,
687
+ name: app.name,
688
+ })), userHomeDir);
689
+ const augmentedText = `${desktopContext}\n\n${input.text}`;
690
+ await this.session.prompt(augmentedText);
691
+ }
692
+ finally {
693
+ unsubscribe();
694
+ }
695
+ }
696
+ }
697
+ //# sourceMappingURL=pi-session-service.js.map