p-spore 0.1.4 → 0.1.43

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 (394) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +242 -67
  3. package/dist/agents/agent-manager.d.ts +3 -3
  4. package/dist/agents/agent-manager.d.ts.map +1 -1
  5. package/dist/agents/agent-manager.js +3 -2
  6. package/dist/agents/agent-manager.js.map +1 -1
  7. package/dist/application/agent-application.d.ts +29 -5
  8. package/dist/application/agent-application.d.ts.map +1 -1
  9. package/dist/application/agent-application.js +444 -14
  10. package/dist/application/agent-application.js.map +1 -1
  11. package/dist/articles/diff.d.ts +70 -0
  12. package/dist/articles/diff.d.ts.map +1 -0
  13. package/dist/articles/diff.js +330 -0
  14. package/dist/articles/diff.js.map +1 -0
  15. package/dist/articles/docx.d.ts +10 -0
  16. package/dist/articles/docx.d.ts.map +1 -0
  17. package/dist/articles/docx.js +219 -0
  18. package/dist/articles/docx.js.map +1 -0
  19. package/dist/articles/draft.d.ts +18 -0
  20. package/dist/articles/draft.d.ts.map +1 -0
  21. package/dist/articles/draft.js +88 -0
  22. package/dist/articles/draft.js.map +1 -0
  23. package/dist/articles/edits.d.ts +24 -0
  24. package/dist/articles/edits.d.ts.map +1 -0
  25. package/dist/articles/edits.js +54 -0
  26. package/dist/articles/edits.js.map +1 -0
  27. package/dist/articles/html.d.ts +10 -0
  28. package/dist/articles/html.d.ts.map +1 -0
  29. package/dist/articles/html.js +122 -0
  30. package/dist/articles/html.js.map +1 -0
  31. package/dist/articles/plain.d.ts +8 -0
  32. package/dist/articles/plain.d.ts.map +1 -0
  33. package/dist/articles/plain.js +69 -0
  34. package/dist/articles/plain.js.map +1 -0
  35. package/dist/articles/preview.d.ts +43 -0
  36. package/dist/articles/preview.d.ts.map +1 -0
  37. package/dist/articles/preview.js +90 -0
  38. package/dist/articles/preview.js.map +1 -0
  39. package/dist/articles/publish.d.ts +10 -0
  40. package/dist/articles/publish.d.ts.map +1 -0
  41. package/dist/articles/publish.js +54 -0
  42. package/dist/articles/publish.js.map +1 -0
  43. package/dist/articles/receipt.d.ts +16 -0
  44. package/dist/articles/receipt.d.ts.map +1 -0
  45. package/dist/articles/receipt.js +27 -0
  46. package/dist/articles/receipt.js.map +1 -0
  47. package/dist/articles/store.d.ts +78 -0
  48. package/dist/articles/store.d.ts.map +1 -0
  49. package/dist/articles/store.js +308 -0
  50. package/dist/articles/store.js.map +1 -0
  51. package/dist/articles/tool.d.ts +14 -0
  52. package/dist/articles/tool.d.ts.map +1 -0
  53. package/dist/articles/tool.js +175 -0
  54. package/dist/articles/tool.js.map +1 -0
  55. package/dist/articles/writer-context.d.ts +5 -0
  56. package/dist/articles/writer-context.d.ts.map +1 -0
  57. package/dist/articles/writer-context.js +56 -0
  58. package/dist/articles/writer-context.js.map +1 -0
  59. package/dist/articles/writer.d.ts +18 -0
  60. package/dist/articles/writer.d.ts.map +1 -0
  61. package/dist/articles/writer.js +79 -0
  62. package/dist/articles/writer.js.map +1 -0
  63. package/dist/articles/zip-write.d.ts +11 -0
  64. package/dist/articles/zip-write.d.ts.map +1 -0
  65. package/dist/articles/zip-write.js +63 -0
  66. package/dist/articles/zip-write.js.map +1 -0
  67. package/dist/bundled-skills/README.md +17 -0
  68. package/dist/bundled-skills/aegis-search/SKILL.md +59 -0
  69. package/dist/bundled-skills/aegis-search/bin/.gitkeep +0 -0
  70. package/dist/bundled-skills/article-canvas/SKILL.md +28 -0
  71. package/dist/bundled-skills/binaries.json +23 -0
  72. package/dist/bundled-skills/doc-extract/SKILL.md +56 -0
  73. package/dist/cli/main.d.ts.map +1 -1
  74. package/dist/cli/main.js +407 -54
  75. package/dist/cli/main.js.map +1 -1
  76. package/dist/config/config.d.ts +43 -0
  77. package/dist/config/config.d.ts.map +1 -1
  78. package/dist/config/config.js +150 -6
  79. package/dist/config/config.js.map +1 -1
  80. package/dist/daemon/daemon.d.ts.map +1 -1
  81. package/dist/daemon/daemon.js +80 -11
  82. package/dist/daemon/daemon.js.map +1 -1
  83. package/dist/desktop/application.d.ts +4 -0
  84. package/dist/desktop/application.d.ts.map +1 -0
  85. package/dist/desktop/application.js +1688 -0
  86. package/dist/desktop/application.js.map +1 -0
  87. package/dist/desktop/article-export.d.ts +12 -0
  88. package/dist/desktop/article-export.d.ts.map +1 -0
  89. package/dist/desktop/article-export.js +87 -0
  90. package/dist/desktop/article-export.js.map +1 -0
  91. package/dist/desktop/assets/pspore.png +0 -0
  92. package/dist/desktop/assets/pspore.svg +24 -0
  93. package/dist/desktop/conversation-smoke.d.ts +5 -0
  94. package/dist/desktop/conversation-smoke.d.ts.map +1 -0
  95. package/dist/desktop/conversation-smoke.js +107 -0
  96. package/dist/desktop/conversation-smoke.js.map +1 -0
  97. package/dist/desktop/daemon-bridge.d.ts.map +1 -1
  98. package/dist/desktop/daemon-bridge.js +62 -17
  99. package/dist/desktop/daemon-bridge.js.map +1 -1
  100. package/dist/desktop/ipc-contract.d.ts +124 -0
  101. package/dist/desktop/ipc-contract.d.ts.map +1 -1
  102. package/dist/desktop/ipc-contract.js +191 -12
  103. package/dist/desktop/ipc-contract.js.map +1 -1
  104. package/dist/desktop/local-path.d.ts +15 -0
  105. package/dist/desktop/local-path.d.ts.map +1 -0
  106. package/dist/desktop/local-path.js +59 -0
  107. package/dist/desktop/local-path.js.map +1 -0
  108. package/dist/desktop/main.d.ts +10 -1
  109. package/dist/desktop/main.d.ts.map +1 -1
  110. package/dist/desktop/main.js +122 -530
  111. package/dist/desktop/main.js.map +1 -1
  112. package/dist/desktop/native-startup.d.ts +4 -0
  113. package/dist/desktop/native-startup.d.ts.map +1 -0
  114. package/dist/desktop/native-startup.js +57 -0
  115. package/dist/desktop/native-startup.js.map +1 -0
  116. package/dist/desktop/preload.cjs +28 -1
  117. package/dist/desktop/renderer/article-panel.d.ts +45 -0
  118. package/dist/desktop/renderer/article-panel.d.ts.map +1 -0
  119. package/dist/desktop/renderer/article-panel.js +1126 -0
  120. package/dist/desktop/renderer/article-panel.js.map +1 -0
  121. package/dist/desktop/renderer/article-split.d.ts +32 -0
  122. package/dist/desktop/renderer/article-split.d.ts.map +1 -0
  123. package/dist/desktop/renderer/article-split.js +48 -0
  124. package/dist/desktop/renderer/article-split.js.map +1 -0
  125. package/dist/desktop/renderer/composer-file-references.d.ts +23 -0
  126. package/dist/desktop/renderer/composer-file-references.d.ts.map +1 -0
  127. package/dist/desktop/renderer/composer-file-references.js +51 -0
  128. package/dist/desktop/renderer/composer-file-references.js.map +1 -0
  129. package/dist/desktop/renderer/conversation.d.ts +23 -0
  130. package/dist/desktop/renderer/conversation.d.ts.map +1 -0
  131. package/dist/desktop/renderer/conversation.js +66 -0
  132. package/dist/desktop/renderer/conversation.js.map +1 -0
  133. package/dist/desktop/renderer/dom-reconcile.d.ts +15 -0
  134. package/dist/desktop/renderer/dom-reconcile.d.ts.map +1 -0
  135. package/dist/desktop/renderer/dom-reconcile.js +33 -0
  136. package/dist/desktop/renderer/dom-reconcile.js.map +1 -0
  137. package/dist/desktop/renderer/index.html +211 -34
  138. package/dist/desktop/renderer/indexing-view.d.ts +22 -0
  139. package/dist/desktop/renderer/indexing-view.d.ts.map +1 -0
  140. package/dist/desktop/renderer/indexing-view.js +115 -0
  141. package/dist/desktop/renderer/indexing-view.js.map +1 -0
  142. package/dist/desktop/renderer/markdown-view.d.ts +23 -0
  143. package/dist/desktop/renderer/markdown-view.d.ts.map +1 -0
  144. package/dist/desktop/renderer/markdown-view.js +436 -0
  145. package/dist/desktop/renderer/markdown-view.js.map +1 -0
  146. package/dist/desktop/renderer/motion.d.ts +67 -0
  147. package/dist/desktop/renderer/motion.d.ts.map +1 -0
  148. package/dist/desktop/renderer/motion.js +296 -0
  149. package/dist/desktop/renderer/motion.js.map +1 -0
  150. package/dist/desktop/renderer/renderer.js +1799 -152
  151. package/dist/desktop/renderer/renderer.js.map +1 -1
  152. package/dist/desktop/renderer/session-find.d.ts +18 -0
  153. package/dist/desktop/renderer/session-find.d.ts.map +1 -0
  154. package/dist/desktop/renderer/session-find.js +29 -0
  155. package/dist/desktop/renderer/session-find.js.map +1 -0
  156. package/dist/desktop/renderer/session-search.d.ts +30 -0
  157. package/dist/desktop/renderer/session-search.d.ts.map +1 -0
  158. package/dist/desktop/renderer/session-search.js +296 -0
  159. package/dist/desktop/renderer/session-search.js.map +1 -0
  160. package/dist/desktop/renderer/store.d.ts +33 -0
  161. package/dist/desktop/renderer/store.d.ts.map +1 -1
  162. package/dist/desktop/renderer/store.js +92 -19
  163. package/dist/desktop/renderer/store.js.map +1 -1
  164. package/dist/desktop/renderer/styles.css +928 -268
  165. package/dist/desktop/renderer/vendor/gsap.min.js +11 -0
  166. package/dist/desktop/renderer-diagnostics.d.ts +5 -0
  167. package/dist/desktop/renderer-diagnostics.d.ts.map +1 -0
  168. package/dist/desktop/renderer-diagnostics.js +47 -0
  169. package/dist/desktop/renderer-diagnostics.js.map +1 -0
  170. package/dist/desktop/security.d.ts +26 -1
  171. package/dist/desktop/security.d.ts.map +1 -1
  172. package/dist/desktop/security.js +26 -6
  173. package/dist/desktop/security.js.map +1 -1
  174. package/dist/desktop/smoke-fixture.d.ts.map +1 -1
  175. package/dist/desktop/smoke-fixture.js +77 -3
  176. package/dist/desktop/smoke-fixture.js.map +1 -1
  177. package/dist/desktop/splash/index.html +25 -0
  178. package/dist/desktop/splash/splash.d.ts +2 -0
  179. package/dist/desktop/splash/splash.d.ts.map +1 -0
  180. package/dist/desktop/splash/splash.js +8 -0
  181. package/dist/desktop/splash/splash.js.map +1 -0
  182. package/dist/desktop/splash/styles.css +24 -0
  183. package/dist/desktop/system-open.d.ts +16 -0
  184. package/dist/desktop/system-open.d.ts.map +1 -0
  185. package/dist/desktop/system-open.js +90 -0
  186. package/dist/desktop/system-open.js.map +1 -0
  187. package/dist/desktop/validation.d.ts.map +1 -1
  188. package/dist/desktop/validation.js +99 -21
  189. package/dist/desktop/validation.js.map +1 -1
  190. package/dist/desktop/window-security.d.ts +4 -0
  191. package/dist/desktop/window-security.d.ts.map +1 -0
  192. package/dist/desktop/window-security.js +26 -0
  193. package/dist/desktop/window-security.js.map +1 -0
  194. package/dist/documents/errors.d.ts +12 -0
  195. package/dist/documents/errors.d.ts.map +1 -0
  196. package/dist/documents/errors.js +12 -0
  197. package/dist/documents/errors.js.map +1 -0
  198. package/dist/documents/extract.d.ts +37 -0
  199. package/dist/documents/extract.d.ts.map +1 -0
  200. package/dist/documents/extract.js +90 -0
  201. package/dist/documents/extract.js.map +1 -0
  202. package/dist/documents/legacy-office.d.ts +23 -0
  203. package/dist/documents/legacy-office.d.ts.map +1 -0
  204. package/dist/documents/legacy-office.js +439 -0
  205. package/dist/documents/legacy-office.js.map +1 -0
  206. package/dist/documents/ole.d.ts +20 -0
  207. package/dist/documents/ole.d.ts.map +1 -0
  208. package/dist/documents/ole.js +189 -0
  209. package/dist/documents/ole.js.map +1 -0
  210. package/dist/documents/ooxml.d.ts +23 -0
  211. package/dist/documents/ooxml.d.ts.map +1 -0
  212. package/dist/documents/ooxml.js +197 -0
  213. package/dist/documents/ooxml.js.map +1 -0
  214. package/dist/documents/pdf.d.ts +20 -0
  215. package/dist/documents/pdf.d.ts.map +1 -0
  216. package/dist/documents/pdf.js +95 -0
  217. package/dist/documents/pdf.js.map +1 -0
  218. package/dist/documents/plain-text.d.ts +22 -0
  219. package/dist/documents/plain-text.d.ts.map +1 -0
  220. package/dist/documents/plain-text.js +57 -0
  221. package/dist/documents/plain-text.js.map +1 -0
  222. package/dist/documents/zip.d.ts +15 -0
  223. package/dist/documents/zip.d.ts.map +1 -0
  224. package/dist/documents/zip.js +137 -0
  225. package/dist/documents/zip.js.map +1 -0
  226. package/dist/domain/indexing.d.ts +60 -0
  227. package/dist/domain/indexing.d.ts.map +1 -0
  228. package/dist/domain/indexing.js +13 -0
  229. package/dist/domain/indexing.js.map +1 -0
  230. package/dist/domain/runtime.d.ts +133 -5
  231. package/dist/domain/runtime.d.ts.map +1 -1
  232. package/dist/gateway/local-gateway.d.ts +8 -0
  233. package/dist/gateway/local-gateway.d.ts.map +1 -1
  234. package/dist/gateway/local-gateway.js +105 -21
  235. package/dist/gateway/local-gateway.js.map +1 -1
  236. package/dist/indexing/index-service.d.ts +56 -0
  237. package/dist/indexing/index-service.d.ts.map +1 -0
  238. package/dist/indexing/index-service.js +262 -0
  239. package/dist/indexing/index-service.js.map +1 -0
  240. package/dist/kernel/loop.d.ts +10 -0
  241. package/dist/kernel/loop.d.ts.map +1 -1
  242. package/dist/kernel/loop.js +6 -2
  243. package/dist/kernel/loop.js.map +1 -1
  244. package/dist/kernel/reducer.d.ts.map +1 -1
  245. package/dist/kernel/reducer.js +17 -1
  246. package/dist/kernel/reducer.js.map +1 -1
  247. package/dist/kernel/state.d.ts +17 -4
  248. package/dist/kernel/state.d.ts.map +1 -1
  249. package/dist/kernel/state.js +2 -0
  250. package/dist/kernel/state.js.map +1 -1
  251. package/dist/pi/context-budget.d.ts +8 -0
  252. package/dist/pi/context-budget.d.ts.map +1 -0
  253. package/dist/pi/context-budget.js +62 -0
  254. package/dist/pi/context-budget.js.map +1 -0
  255. package/dist/pi/pi-runtime-adapter.d.ts +38 -3
  256. package/dist/pi/pi-runtime-adapter.d.ts.map +1 -1
  257. package/dist/pi/pi-runtime-adapter.js +551 -56
  258. package/dist/pi/pi-runtime-adapter.js.map +1 -1
  259. package/dist/plugins/bundled-skills.d.ts +56 -0
  260. package/dist/plugins/bundled-skills.d.ts.map +1 -0
  261. package/dist/plugins/bundled-skills.js +243 -0
  262. package/dist/plugins/bundled-skills.js.map +1 -0
  263. package/dist/plugins/extension-manager.d.ts +23 -1
  264. package/dist/plugins/extension-manager.d.ts.map +1 -1
  265. package/dist/plugins/extension-manager.js +117 -0
  266. package/dist/plugins/extension-manager.js.map +1 -1
  267. package/dist/plugins/types.d.ts +29 -0
  268. package/dist/plugins/types.d.ts.map +1 -1
  269. package/dist/plugins/workspace-skills.d.ts +36 -0
  270. package/dist/plugins/workspace-skills.d.ts.map +1 -0
  271. package/dist/plugins/workspace-skills.js +309 -0
  272. package/dist/plugins/workspace-skills.js.map +1 -0
  273. package/dist/policy/policy.d.ts.map +1 -1
  274. package/dist/policy/policy.js +22 -3
  275. package/dist/policy/policy.js.map +1 -1
  276. package/dist/protocol/client.d.ts +1 -0
  277. package/dist/protocol/client.d.ts.map +1 -1
  278. package/dist/protocol/client.js +49 -24
  279. package/dist/protocol/client.js.map +1 -1
  280. package/dist/protocol/limits.d.ts +69 -0
  281. package/dist/protocol/limits.d.ts.map +1 -0
  282. package/dist/protocol/limits.js +166 -0
  283. package/dist/protocol/limits.js.map +1 -0
  284. package/dist/protocol/schema.d.ts +6 -0
  285. package/dist/protocol/schema.d.ts.map +1 -1
  286. package/dist/protocol/schema.js.map +1 -1
  287. package/dist/protocol/server-frame-codec.d.ts +18 -0
  288. package/dist/protocol/server-frame-codec.d.ts.map +1 -0
  289. package/dist/protocol/server-frame-codec.js +211 -0
  290. package/dist/protocol/server-frame-codec.js.map +1 -0
  291. package/dist/service/legacy-daemon.d.ts +6 -0
  292. package/dist/service/legacy-daemon.d.ts.map +1 -0
  293. package/dist/service/legacy-daemon.js +42 -0
  294. package/dist/service/legacy-daemon.js.map +1 -0
  295. package/dist/service/service-launch.d.ts +10 -0
  296. package/dist/service/service-launch.d.ts.map +1 -0
  297. package/dist/service/service-launch.js +56 -0
  298. package/dist/service/service-launch.js.map +1 -0
  299. package/dist/service/system-service.d.ts +57 -0
  300. package/dist/service/system-service.d.ts.map +1 -0
  301. package/dist/service/system-service.js +263 -0
  302. package/dist/service/system-service.js.map +1 -0
  303. package/dist/sessions/session-catalog.d.ts +45 -0
  304. package/dist/sessions/session-catalog.d.ts.map +1 -0
  305. package/dist/sessions/session-catalog.js +270 -0
  306. package/dist/sessions/session-catalog.js.map +1 -0
  307. package/dist/sessions/session-file.d.ts +12 -0
  308. package/dist/sessions/session-file.d.ts.map +1 -0
  309. package/dist/sessions/session-file.js +47 -0
  310. package/dist/sessions/session-file.js.map +1 -0
  311. package/dist/settings/compaction-settings.d.ts +10 -0
  312. package/dist/settings/compaction-settings.d.ts.map +1 -0
  313. package/dist/settings/compaction-settings.js +8 -0
  314. package/dist/settings/compaction-settings.js.map +1 -0
  315. package/dist/settings/indexing-settings-service.d.ts +16 -0
  316. package/dist/settings/indexing-settings-service.d.ts.map +1 -0
  317. package/dist/settings/indexing-settings-service.js +57 -0
  318. package/dist/settings/indexing-settings-service.js.map +1 -0
  319. package/dist/settings/indexing-settings.d.ts +30 -0
  320. package/dist/settings/indexing-settings.d.ts.map +1 -0
  321. package/dist/settings/indexing-settings.js +58 -0
  322. package/dist/settings/indexing-settings.js.map +1 -0
  323. package/dist/settings/llm-settings-service.d.ts +6 -0
  324. package/dist/settings/llm-settings-service.d.ts.map +1 -1
  325. package/dist/settings/llm-settings-service.js +137 -12
  326. package/dist/settings/llm-settings-service.js.map +1 -1
  327. package/dist/settings/llm-settings.d.ts +68 -2
  328. package/dist/settings/llm-settings.d.ts.map +1 -1
  329. package/dist/settings/llm-settings.js +119 -17
  330. package/dist/settings/llm-settings.js.map +1 -1
  331. package/dist/settings/model-api.d.ts +10 -0
  332. package/dist/settings/model-api.d.ts.map +1 -0
  333. package/dist/settings/model-api.js +10 -0
  334. package/dist/settings/model-api.js.map +1 -0
  335. package/dist/storage/home.d.ts +1 -0
  336. package/dist/storage/home.d.ts.map +1 -1
  337. package/dist/storage/home.js +37 -2
  338. package/dist/storage/home.js.map +1 -1
  339. package/dist/tools/core-tools.d.ts +2 -0
  340. package/dist/tools/core-tools.d.ts.map +1 -1
  341. package/dist/tools/core-tools.js +91 -1
  342. package/dist/tools/core-tools.js.map +1 -1
  343. package/dist/ui/shared/activity.d.ts +2 -0
  344. package/dist/ui/shared/activity.d.ts.map +1 -1
  345. package/dist/ui/shared/activity.js +18 -4
  346. package/dist/ui/shared/activity.js.map +1 -1
  347. package/dist/ui/shared/full-message.d.ts +22 -0
  348. package/dist/ui/shared/full-message.d.ts.map +1 -0
  349. package/dist/ui/shared/full-message.js +40 -0
  350. package/dist/ui/shared/full-message.js.map +1 -0
  351. package/dist/ui/shared/local-links.d.ts +7 -0
  352. package/dist/ui/shared/local-links.d.ts.map +1 -0
  353. package/dist/ui/shared/local-links.js +24 -0
  354. package/dist/ui/shared/local-links.js.map +1 -0
  355. package/dist/ui/shared/markdown-stream.d.ts +25 -0
  356. package/dist/ui/shared/markdown-stream.d.ts.map +1 -0
  357. package/dist/ui/shared/markdown-stream.js +35 -0
  358. package/dist/ui/shared/markdown-stream.js.map +1 -0
  359. package/dist/ui/shared/markdown.d.ts +70 -0
  360. package/dist/ui/shared/markdown.d.ts.map +1 -0
  361. package/dist/ui/shared/markdown.js +434 -0
  362. package/dist/ui/shared/markdown.js.map +1 -0
  363. package/dist/ui/shared/presentation.d.ts +10 -2
  364. package/dist/ui/shared/presentation.d.ts.map +1 -1
  365. package/dist/ui/shared/presentation.js +83 -22
  366. package/dist/ui/shared/presentation.js.map +1 -1
  367. package/dist/ui/shared/protocol-state.d.ts +19 -0
  368. package/dist/ui/shared/protocol-state.d.ts.map +1 -1
  369. package/dist/ui/shared/protocol-state.js +188 -26
  370. package/dist/ui/shared/protocol-state.js.map +1 -1
  371. package/dist/ui/shared/virtual-list.d.ts +34 -0
  372. package/dist/ui/shared/virtual-list.d.ts.map +1 -0
  373. package/dist/ui/shared/virtual-list.js +143 -0
  374. package/dist/ui/shared/virtual-list.js.map +1 -0
  375. package/dist/ui/tui/commands.d.ts +1 -1
  376. package/dist/ui/tui/commands.d.ts.map +1 -1
  377. package/dist/ui/tui/commands.js +8 -3
  378. package/dist/ui/tui/commands.js.map +1 -1
  379. package/dist/ui/tui/store.d.ts +22 -1
  380. package/dist/ui/tui/store.d.ts.map +1 -1
  381. package/dist/ui/tui/store.js +50 -3
  382. package/dist/ui/tui/store.js.map +1 -1
  383. package/dist/ui/tui/tui-app.d.ts.map +1 -1
  384. package/dist/ui/tui/tui-app.js +231 -31
  385. package/dist/ui/tui/tui-app.js.map +1 -1
  386. package/dist/ui/tui/view.d.ts +7 -0
  387. package/dist/ui/tui/view.d.ts.map +1 -1
  388. package/dist/ui/tui/view.js +161 -57
  389. package/dist/ui/tui/view.js.map +1 -1
  390. package/dist/version.d.ts +1 -1
  391. package/dist/version.d.ts.map +1 -1
  392. package/dist/version.js +1 -1
  393. package/dist/version.js.map +1 -1
  394. package/package.json +13 -2
@@ -1,536 +1,128 @@
1
+ // Keep this entry free of business/daemon imports: ESM evaluates every static
2
+ // dependency before running even the first line that can create the splash.
1
3
  import { mkdir, writeFile } from "node:fs/promises";
2
- import { isAbsolute, join } from "node:path";
4
+ import { join } from "node:path";
3
5
  import { fileURLToPath, pathToFileURL } from "node:url";
4
- import { app, BrowserWindow, ipcMain, session } from "electron";
5
- import { resolvePsporeHome } from "../config/config.js";
6
- import { DaemonSupervisor } from "../daemon/supervisor.js";
7
- import { LlmSettingsService } from "../settings/llm-settings-service.js";
8
- import { initializePsporeHome } from "../storage/home.js";
9
- import { PI_VERSION, PSPORE_VERSION } from "../version.js";
10
- import { DaemonBridge } from "./daemon-bridge.js";
11
- import { DESKTOP_BOOTSTRAP_CHANNEL, DESKTOP_DISPATCH_CHANNEL, DESKTOP_FRAME_CHANNEL, DESKTOP_SETTINGS_GET_CHANNEL, DESKTOP_SETTINGS_SAVE_CHANNEL, decodeDesktopLlmSettingsUpdate, } from "./ipc-contract.js";
12
- import { DESKTOP_CSP, desktopWindowOptions, isTrustedRendererUrl } from "./security.js";
13
- import { createDesktopSmokeState } from "./smoke-fixture.js";
6
+ import { app, BrowserWindow, dialog } from "electron";
7
+ import { captureRendererDiagnostics, waitFor, waitForRenderer } from "./renderer-diagnostics.js";
8
+ import { DESKTOP_ICON } from "./security.js";
9
+ import { connectNativeStartup } from "./native-startup.js";
10
+ import { hardenWindow, installSessionSecurity } from "./window-security.js";
14
11
  const arguments_ = parseArguments(process.argv.slice(1));
15
12
  if (arguments_.smokeDirectory)
16
13
  app.disableHardwareAcceleration();
17
- let window;
18
- let bridge;
19
- let daemonSupervisor;
20
- let settingsService;
21
- let unsubscribeBridge;
22
- let rendererUrl = "";
23
- let exitCode = 0;
14
+ app.setName("pSpore");
15
+ if (process.platform === "linux") {
16
+ // Keep the existing identity: pinned launchers survive overlay upgrades.
17
+ app.setDesktopName("pSpore.desktop");
18
+ app.commandLine.appendSwitch("class", "pSpore");
19
+ }
20
+ let splash;
21
+ let nativeStartup;
24
22
  let shuttingDown = false;
25
- let savingSettings = false;
26
- app.on("window-all-closed", () => requestShutdown(0));
23
+ let stopApplication;
24
+ app.on("window-all-closed", () => shutdown(0));
27
25
  app.on("before-quit", (event) => {
28
- if (!shuttingDown) {
29
- event.preventDefault();
30
- requestShutdown(0);
31
- }
26
+ event.preventDefault();
27
+ shutdown(0);
32
28
  });
33
- process.once("SIGINT", () => requestShutdown(0));
34
- process.once("SIGTERM", () => requestShutdown(0));
35
- app.whenReady().then(startDesktop).catch(failStartup);
36
- async function startDesktop() {
37
- const home = resolvePsporeHome(arguments_.home);
38
- const { paths } = await initializePsporeHome(home);
39
- const moduleDirectory = fileURLToPath(new URL(".", import.meta.url));
40
- const preloadPath = join(moduleDirectory, "preload.cjs");
41
- const rendererPath = join(moduleDirectory, "renderer", "index.html");
42
- rendererUrl = pathToFileURL(rendererPath).href;
43
- installSessionSecurity();
44
- daemonSupervisor = new DaemonSupervisor({ home });
45
- await daemonSupervisor.start();
46
- bridge = new DaemonBridge({
47
- socketPath: paths.socket,
48
- stagingRoot: join(paths.workspace, "staging"),
49
- versions: {
50
- pspore: PSPORE_VERSION,
51
- pi: PI_VERSION,
52
- electron: process.versions.electron,
53
- node: process.versions.node,
54
- },
55
- });
56
- bridge.start();
57
- settingsService = new LlmSettingsService(home, paths);
58
- window = new BrowserWindow(desktopWindowOptions(preloadPath));
59
- hardenWindow(window, rendererUrl);
60
- installIpc(window, bridge, settingsService);
61
- unsubscribeBridge = bridge.subscribe((frame) => sendFrame(window, frame));
62
- await window.loadFile(rendererPath);
63
- if (arguments_.smokeDirectory) {
64
- await runSmoke(window, bridge, arguments_.smokeDirectory, arguments_.smokeFixture);
65
- await bridge.stop();
66
- requestShutdown(exitCode);
67
- return;
68
- }
69
- window.once("ready-to-show", () => window?.show());
70
- if (!window.isVisible())
71
- window.show();
72
- }
73
- function installSessionSecurity() {
74
- const desktopSession = session.defaultSession;
75
- desktopSession.setPermissionCheckHandler(() => false);
76
- desktopSession.setPermissionRequestHandler((_webContents, _permission, callback) => callback(false));
77
- desktopSession.on("will-download", (event) => event.preventDefault());
78
- desktopSession.webRequest.onBeforeRequest({ urls: ["http://*/*", "https://*/*", "ws://*/*", "wss://*/*"] }, (_details, callback) => callback({ cancel: true }));
79
- app.on("web-contents-created", (_event, contents) => {
80
- contents.on("will-attach-webview", (event) => event.preventDefault());
81
- });
29
+ process.once("SIGINT", () => shutdown(0));
30
+ process.once("SIGTERM", () => shutdown(0));
31
+ if (arguments_.smokeDirectory || app.requestSingleInstanceLock()) {
32
+ app.on("second-instance", focusDesktop);
33
+ app.whenReady().then(async () => {
34
+ installSessionSecurity();
35
+ nativeStartup = await connectNativeStartup(focusDesktop);
36
+ if (!nativeStartup)
37
+ await showStartupWindow();
38
+ if (shuttingDown)
39
+ return;
40
+ // No workspace IO, settings, service management or model SDK may run until
41
+ // the startup card has painted and its native window is actually visible.
42
+ const application = await import("./application.js");
43
+ if (shuttingDown)
44
+ return;
45
+ stopApplication = application.requestShutdown;
46
+ await application.startDesktop(arguments_, nativeStartup ?? {
47
+ setMessage: setStartupMessage,
48
+ close: closeStartupWindow,
49
+ });
50
+ }).catch(failStartup);
82
51
  }
83
- function hardenWindow(target, expectedUrl) {
84
- const contents = target.webContents;
85
- contents.setWindowOpenHandler(() => ({ action: "deny" }));
86
- contents.on("will-navigate", (event, url) => {
87
- if (!isTrustedRendererUrl(url, expectedUrl))
88
- event.preventDefault();
89
- });
90
- contents.on("will-redirect", (event, url) => {
91
- if (!isTrustedRendererUrl(url, expectedUrl))
92
- event.preventDefault();
93
- });
94
- contents.on("will-attach-webview", (event) => event.preventDefault());
52
+ else {
53
+ app.exit(0);
95
54
  }
96
- function installIpc(target, daemonBridge, desktopSettings) {
97
- ipcMain.removeHandler(DESKTOP_BOOTSTRAP_CHANNEL);
98
- ipcMain.removeHandler(DESKTOP_DISPATCH_CHANNEL);
99
- ipcMain.removeHandler(DESKTOP_SETTINGS_GET_CHANNEL);
100
- ipcMain.removeHandler(DESKTOP_SETTINGS_SAVE_CHANNEL);
101
- ipcMain.handle(DESKTOP_BOOTSTRAP_CHANNEL, (event) => {
102
- assertTrustedSender(event, target.webContents);
103
- return daemonBridge.bootstrap();
55
+ function focusDesktop() {
56
+ // PDF export also creates a hidden BrowserWindow; only restore the workspace.
57
+ const target = splash ?? BrowserWindow.getAllWindows().find((candidate) => candidate.webContents.getURL() === new URL("./renderer/index.html", import.meta.url).href);
58
+ if (!target || target.isDestroyed())
59
+ return;
60
+ if (target.isMinimized())
61
+ target.restore();
62
+ target.show();
63
+ target.focus();
64
+ }
65
+ async function showStartupWindow() {
66
+ const startupPath = fileURLToPath(new URL("./splash/index.html", import.meta.url));
67
+ const target = new BrowserWindow({
68
+ width: 512, height: 332, useContentSize: true, center: true,
69
+ show: false, frame: false, transparent: true, backgroundColor: "#00000000",
70
+ resizable: false, maximizable: false, minimizable: false,
71
+ // Electron 43's skipTaskbar is only implemented on macOS/Windows. Linux
72
+ // window managers use the native splash type to omit this transient window.
73
+ ...(process.platform === "linux" ? { type: "splash" } : {}),
74
+ skipTaskbar: true, autoHideMenuBar: true, title: "pSpore · 正在启动", icon: DESKTOP_ICON,
75
+ webPreferences: {
76
+ contextIsolation: true, sandbox: true, nodeIntegration: false,
77
+ webSecurity: true, webviewTag: false, spellcheck: false,
78
+ },
104
79
  });
105
- ipcMain.handle(DESKTOP_DISPATCH_CHANNEL, async (event, command) => {
106
- assertTrustedSender(event, target.webContents);
107
- return daemonBridge.dispatch(command);
80
+ splash = target;
81
+ captureRendererDiagnostics(target);
82
+ hardenWindow(target, pathToFileURL(startupPath).href);
83
+ target.on("closed", () => {
84
+ if (splash !== target)
85
+ return;
86
+ splash = undefined;
87
+ shutdown(0);
108
88
  });
109
- ipcMain.handle(DESKTOP_SETTINGS_GET_CHANNEL, async (event) => {
110
- assertTrustedSender(event, target.webContents);
111
- return desktopSettings.get();
89
+ target.once("ready-to-show", () => {
90
+ if (!shuttingDown && !target.isDestroyed())
91
+ target.show();
112
92
  });
113
- ipcMain.handle(DESKTOP_SETTINGS_SAVE_CHANNEL, async (event, candidate) => {
114
- assertTrustedSender(event, target.webContents);
115
- if (savingSettings)
116
- throw new Error("Desktop settings are already being saved");
117
- const update = decodeDesktopLlmSettingsUpdate(candidate);
118
- savingSettings = true;
119
- try {
120
- const settings = await desktopSettings.save(update);
121
- const ownership = daemonSupervisor?.ownership;
122
- if (ownership !== "owned") {
123
- return {
124
- settings,
125
- applied: false,
126
- restartRequired: true,
127
- message: "设置已安全保存;当前由外部 Daemon 提供服务,重启该 Daemon 后生效。",
128
- };
129
- }
130
- await daemonBridge.stop();
131
- await daemonSupervisor?.stop();
132
- await daemonSupervisor?.start();
133
- daemonBridge.start();
134
- await waitFor(() => daemonBridge.bootstrap().connection.phase === "connected", 10_000);
135
- return {
136
- settings,
137
- applied: true,
138
- restartRequired: false,
139
- message: "设置已保存并应用,本地 Daemon 已安全重启。",
140
- };
141
- }
142
- finally {
143
- savingSettings = false;
144
- }
145
- });
146
- }
147
- function assertTrustedSender(event, expected) {
148
- if (event.sender !== expected || !isTrustedRendererUrl(event.senderFrame?.url ?? "", rendererUrl)) {
149
- throw new Error("Rejected IPC from an untrusted renderer");
150
- }
151
- }
152
- function sendFrame(target, frame) {
153
- if (!target || target.isDestroyed() || target.webContents.isDestroyed() || target.webContents.isLoadingMainFrame())
93
+ await target.loadFile(startupPath);
94
+ await waitFor(() => shuttingDown || target.isVisible(), 10_000);
95
+ if (shuttingDown)
154
96
  return;
155
- target.webContents.send(DESKTOP_FRAME_CHANNEL, frame);
156
- }
157
- async function runSmoke(target, daemonBridge, outputDirectory, fixture) {
158
- if (!isAbsolute(outputDirectory))
159
- throw new Error("--smoke-dir must be an absolute path");
160
- await mkdir(outputDirectory, { recursive: true, mode: 0o700 });
161
- target.setContentSize(1_920, 1_080);
162
- target.show();
163
97
  await waitForRenderer(target, "document.documentElement.dataset.ready === 'true'", 10_000);
164
- await waitFor(() => daemonBridge.bootstrap().connection.phase === "connected", 8_000).catch(() => undefined);
165
- let reconnectVerified = false;
166
- if (fixture && daemonBridge.bootstrap().connection.phase === "connected") {
167
- await daemonBridge.stop();
168
- await waitForRenderer(target, "document.documentElement.dataset.connection === 'disconnected'", 5_000);
169
- daemonBridge.start();
170
- await waitFor(() => daemonBridge.bootstrap().connection.phase === "connected", 8_000);
171
- await waitForRenderer(target, "document.documentElement.dataset.connection === 'connected'", 5_000);
172
- reconnectVerified = true;
173
- }
174
- if (fixture) {
175
- const base = daemonBridge.bootstrap();
176
- const cursors = { ...base.cursors, "desktop-smoke": 10 };
177
- sendFrame(target, { version: 1, kind: "snapshot", snapshot: createDesktopSmokeState(base.snapshot, false), cursors });
178
- sendFrame(target, smokeEvent(11, "tool.started", { sessionId: "main", toolName: "shell", command: "npm run check" }));
179
- sendFrame(target, smokeEvent(12, "tool.completed", {
180
- sessionId: "main",
181
- toolName: "shell",
182
- status: "succeeded",
183
- output: Array.from({ length: 80 }, (_, index) => `verification line ${index + 1}: pass`).join("\n"),
184
- }));
185
- sendFrame(target, {
186
- version: 1,
187
- kind: "delta",
188
- streamId: "desktop-smoke",
189
- fromSeq: 13,
190
- seq: 15,
191
- sessionId: "main",
192
- text: "正在归并最终证据;这段文字来自连续 streaming delta。",
193
- });
194
- }
195
- await waitForRenderer(target, "Number(document.documentElement.dataset.renderSequence || '0') > 0", 5_000);
196
- if (fixture)
197
- await waitForRenderer(target, "Boolean(document.querySelector('.message.streaming'))", 5_000);
198
- await setRendererTheme(target, "dark");
199
- await settlePaint();
200
- const mainVisual = await target.webContents.executeJavaScript(`(() => {
201
- const transcript = document.querySelector('#transcript');
202
- const code = document.querySelector('.message.assistant pre');
203
- const composerRect = document.querySelector('.composer')?.getBoundingClientRect();
204
- return {
205
- transcriptScrollable: Boolean(transcript && transcript.scrollHeight > transcript.clientHeight),
206
- codeContained: Boolean(code && code.scrollWidth > code.clientWidth && getComputedStyle(code).overflowX !== 'visible'),
207
- composerVisible: Boolean(composerRect && composerRect.top >= 0 && composerRect.bottom <= innerHeight),
208
- streamingVisible: Boolean(document.querySelector('.message.streaming'))
209
- };
210
- })()`);
211
- const mainImage = await target.webContents.capturePage();
212
- await writeFile(join(outputDirectory, "desktop-main-dark-1920x1080.png"), mainImage.toPNG(), { mode: 0o600 });
213
- const darkTheme = await readThemeSnapshot(target);
214
- const typography = await target.webContents.executeJavaScript(`(() => {
215
- const size = (selector) => Number.parseFloat(getComputedStyle(document.querySelector(selector)).fontSize);
216
- return {
217
- body: Number.parseFloat(getComputedStyle(document.body).fontSize),
218
- conversationTitle: size('.conversation-header h2'),
219
- messageBody: size('.message-body'),
220
- composerInput: size('#composer-input'),
221
- sectionHeading: size('.section-heading h2'),
222
- sessionPrimary: size('.session-item strong'),
223
- sessionSecondary: size('.session-item small'),
224
- denseTitle: size('.dense-item b'),
225
- auxiliary: size('.eyebrow')
226
- };
227
- })()`);
228
- await setRendererTheme(target, "light");
229
- await settlePaint();
230
- const lightTheme = await readThemeSnapshot(target);
231
- const lightImage = await target.webContents.capturePage();
232
- await writeFile(join(outputDirectory, "desktop-main-light-1920x1080.png"), lightImage.toPNG(), { mode: 0o600 });
233
- await setRendererTheme(target, "dark");
234
- await target.webContents.executeJavaScript("document.querySelector('#settings-button')?.click()");
235
- await waitForRenderer(target, "document.querySelector('#settings-overlay')?.hidden === false", 5_000);
236
- await waitForRenderer(target, "document.querySelector('#settings-save')?.disabled === false", 5_000);
237
- await settlePaint();
238
- const settingsImage = await target.webContents.capturePage();
239
- await writeFile(join(outputDirectory, "desktop-settings-1920x1080.png"), settingsImage.toPNG(), { mode: 0o600 });
240
- const settingsProbe = await target.webContents.executeJavaScript(`(async () => {
241
- const dialog = document.querySelector('.settings-dialog');
242
- const dialogRect = dialog?.getBoundingClientRect();
243
- const settings = await window.pspore.getSettings();
244
- const provider = document.querySelector('#settings-provider');
245
- provider.value = 'custom-openai';
246
- provider.dispatchEvent(new Event('change'));
247
- return {
248
- dialogContained: Boolean(dialogRect && dialogRect.left >= 0 && dialogRect.top >= 0 && dialogRect.right <= innerWidth && dialogRect.bottom <= innerHeight),
249
- backgroundInert: Boolean(document.querySelector('#app-shell')?.inert),
250
- apiKeyInputType: document.querySelector('#settings-api-key')?.type || '',
251
- returnedSecret: Object.prototype.hasOwnProperty.call(settings, 'apiKey'),
252
- fieldFontSize: Number.parseFloat(getComputedStyle(provider).fontSize),
253
- providerOptions: Array.from(provider.options).map((option) => option.value),
254
- customBaseUrlEnabled: !document.querySelector('#settings-base-url')?.disabled && Boolean(document.querySelector('#settings-base-url')?.required),
255
- customApiEnabled: !document.querySelector('#settings-api')?.disabled && document.querySelector('#settings-api')?.value === 'openai-completions'
256
- };
257
- })()`);
258
- await target.webContents.executeJavaScript("document.querySelector('#settings-close')?.click()");
259
- await waitForRenderer(target, "document.querySelector('#settings-overlay')?.hidden === true", 2_000);
260
- await target.webContents.executeJavaScript("document.activeElement?.blur()");
261
- target.setContentSize(1_440, 900);
262
- await settlePaint();
263
- const mediumLayout = await readResponsiveLayout(target);
264
- const mediumImage = await target.webContents.capturePage();
265
- await writeFile(join(outputDirectory, "desktop-responsive-1440x900.png"), mediumImage.toPNG(), { mode: 0o600 });
266
- target.setContentSize(1_180, 720);
267
- await settlePaint();
268
- const compactLayout = await readResponsiveLayout(target);
269
- const compactImage = await target.webContents.capturePage();
270
- await writeFile(join(outputDirectory, "desktop-responsive-1180x720.png"), compactImage.toPNG(), { mode: 0o600 });
271
- await target.webContents.executeJavaScript("document.querySelector('#inspector-toggle')?.click()");
272
- await settlePaint();
273
- const compactInspectorLayout = await readResponsiveLayout(target);
274
- await target.webContents.executeJavaScript("document.querySelector('#inspector-close')?.click()");
275
- target.setContentSize(1_920, 1_080);
276
- await settlePaint();
277
- let approvalSize;
278
- if (fixture) {
279
- const base = daemonBridge.bootstrap();
280
- sendFrame(target, {
281
- version: 1,
282
- kind: "snapshot",
283
- snapshot: createDesktopSmokeState(base.snapshot, true),
284
- cursors: { ...base.cursors, "desktop-smoke": 15 },
285
- });
286
- await waitForRenderer(target, "document.documentElement.dataset.approval === 'visible'", 5_000);
287
- await settlePaint();
288
- const approvalImage = await target.webContents.capturePage();
289
- await writeFile(join(outputDirectory, "desktop-approval-1920x1080.png"), approvalImage.toPNG(), { mode: 0o600 });
290
- approvalSize = approvalImage.getSize();
291
- }
292
- const probe = await target.webContents.executeJavaScript(`(() => {
293
- const transcript = document.querySelector('#transcript');
294
- const code = document.querySelector('.message.assistant pre');
295
- const composer = document.querySelector('.composer');
296
- const modal = document.querySelector('.approval-dialog');
297
- const composerRect = composer?.getBoundingClientRect();
298
- const modalRect = modal?.getBoundingClientRect();
299
- return {
300
- viewport: { width: innerWidth, height: innerHeight, devicePixelRatio },
301
- globals: {
302
- process: typeof process,
303
- require: typeof require,
304
- Buffer: typeof Buffer,
305
- electron: typeof electron
306
- },
307
- apiKeys: Object.keys(window.pspore || {}).sort(),
308
- document: {
309
- scrollWidth: document.documentElement.scrollWidth,
310
- scrollHeight: document.documentElement.scrollHeight,
311
- bodyOverflow: getComputedStyle(document.body).overflow,
312
- csp: document.querySelector('meta[http-equiv="Content-Security-Policy"]')?.content || null,
313
- approval: document.documentElement.dataset.approval || null,
314
- transcriptScrollable: Boolean(transcript && transcript.scrollHeight > transcript.clientHeight),
315
- codeContained: Boolean(code && code.scrollWidth > code.clientWidth && getComputedStyle(code).overflowX !== 'visible'),
316
- composerVisible: Boolean(composerRect && composerRect.top >= 0 && composerRect.bottom <= innerHeight),
317
- modalContained: Boolean(modalRect && modalRect.left >= 0 && modalRect.top >= 0 && modalRect.right <= innerWidth && modalRect.bottom <= innerHeight),
318
- backgroundInert: Boolean(document.querySelector('#app-shell')?.inert),
319
- focusLabel: document.activeElement?.textContent?.trim() || null
320
- }
321
- }})()`);
322
- const ipcProbe = await target.webContents.executeJavaScript(`(async () => {
323
- const jobs = await window.pspore.dispatch({ action: 'jobs.list' });
324
- let rejected = false;
325
- let rejectionMessage = '';
326
- try {
327
- await window.pspore.dispatch({ action: 'shell.exec', command: 'id' });
328
- } catch (error) {
329
- rejected = true;
330
- rejectionMessage = String(error?.message || error);
331
- }
332
- return { jobsArray: Array.isArray(jobs), rejected, rejectionMessage };
333
- })()`);
334
- const report = {
335
- version: 1,
336
- capturedAt: new Date().toISOString(),
337
- connection: daemonBridge.bootstrap().connection,
338
- daemonOwnership: daemonSupervisor?.ownership ?? "external",
339
- reconnectVerified,
340
- images: {
341
- main: mainImage.getSize(),
342
- light: lightImage.getSize(),
343
- settings: settingsImage.getSize(),
344
- medium: mediumImage.getSize(),
345
- compact: compactImage.getSize(),
346
- ...(approvalSize ? { approval: approvalSize } : {}),
347
- },
348
- themes: { dark: darkTheme, light: lightTheme },
349
- typography,
350
- mainVisual,
351
- responsive: { medium: mediumLayout, compact: compactLayout, compactInspector: compactInspectorLayout },
352
- settings: settingsProbe,
353
- probe,
354
- ipcProbe,
355
- };
356
- await writeFile(join(outputDirectory, "desktop-security.json"), `${JSON.stringify(report, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
357
- assertSmokeReport(report, fixture);
358
- }
359
- function assertSmokeReport(report, fixture) {
360
- const failures = [];
361
- if (report.connection.phase !== "connected")
362
- failures.push("desktop did not connect to the real daemon");
363
- if (fixture && !report.reconnectVerified)
364
- failures.push("desktop reconnect path was not verified");
365
- if (report.images.main.width !== 1_920 || report.images.main.height !== 1_080)
366
- failures.push("main capture is not 1920x1080");
367
- if (report.images.light.width !== 1_920 || report.images.light.height !== 1_080)
368
- failures.push("light capture is not 1920x1080");
369
- if (report.images.settings.width !== 1_920 || report.images.settings.height !== 1_080)
370
- failures.push("settings capture is not 1920x1080");
371
- if (report.images.medium.width !== 1_440 || report.images.medium.height !== 900)
372
- failures.push("medium capture is not 1440x900");
373
- if (report.images.compact.width !== 1_180 || report.images.compact.height !== 720)
374
- failures.push("compact capture is not 1180x720");
375
- if (report.themes.dark.colorScheme !== "dark" || report.themes.light.colorScheme !== "light")
376
- failures.push("theme color-scheme did not switch");
377
- if (report.themes.dark.backgroundColor === report.themes.light.backgroundColor)
378
- failures.push("theme canvas colors did not switch");
379
- if (report.themes.dark.surfaceColor === report.themes.light.surfaceColor)
380
- failures.push("theme surface colors did not switch");
381
- const minimumFontSizes = {
382
- body: 14,
383
- conversationTitle: 20,
384
- messageBody: 15,
385
- composerInput: 14,
386
- sectionHeading: 15,
387
- sessionPrimary: 13,
388
- sessionSecondary: 11.5,
389
- denseTitle: 12.5,
390
- auxiliary: 11.5,
391
- };
392
- for (const [name, minimum] of Object.entries(minimumFontSizes)) {
393
- const actual = report.typography[name];
394
- if (actual === undefined || !Number.isFinite(actual) || actual < minimum)
395
- failures.push(`${name} font size is below ${minimum}px`);
396
- }
397
- if (report.probe.viewport.width !== 1_920 || report.probe.viewport.height !== 1_080)
398
- failures.push("renderer viewport is not 1920x1080");
399
- if (report.probe.document.scrollWidth !== 1_920 || report.probe.document.scrollHeight !== 1_080)
400
- failures.push("document overflows the viewport");
401
- if (report.probe.document.bodyOverflow !== "hidden")
402
- failures.push("body overflow is not contained");
403
- if (report.probe.document.csp !== DESKTOP_CSP)
404
- failures.push("renderer CSP does not match the strict policy");
405
- for (const global of ["process", "require", "Buffer", "electron"]) {
406
- if (report.probe.globals[global] !== "undefined")
407
- failures.push(`renderer exposes ${global}`);
408
- }
409
- if (report.probe.apiKeys.join(",") !== "bootstrap,dispatch,getSettings,saveSettings,subscribe")
410
- failures.push("preload API surface changed");
411
- if (!report.ipcProbe.jobsArray)
412
- failures.push("allowlisted IPC round trip failed");
413
- if (!report.ipcProbe.rejected || !report.ipcProbe.rejectionMessage.includes("not allowlisted"))
414
- failures.push("non-allowlisted IPC was not rejected");
415
- if (!report.settings.dialogContained || !report.settings.backgroundInert)
416
- failures.push("settings dialog is not contained and modal");
417
- if (report.settings.apiKeyInputType !== "password" || report.settings.returnedSecret)
418
- failures.push("settings API exposed an API key");
419
- if (report.settings.fieldFontSize < 14)
420
- failures.push("settings field font size is below 14px");
421
- if (report.settings.providerOptions.join(",") !== "deepseek,openai,custom-openai")
422
- failures.push("settings provider choices changed");
423
- if (!report.settings.customBaseUrlEnabled || !report.settings.customApiEnabled)
424
- failures.push("custom OpenAI controls are not enabled correctly");
425
- const expectedResponsive = [
426
- ["medium", report.responsive.medium, 1_440, 900],
427
- ["compact", report.responsive.compact, 1_180, 720],
428
- ["compact inspector", report.responsive.compactInspector, 1_180, 720],
429
- ];
430
- for (const [name, layout, width, height] of expectedResponsive) {
431
- if (layout.viewport.width !== width || layout.viewport.height !== height)
432
- failures.push(`${name} viewport changed unexpectedly`);
433
- if (layout.scrollWidth !== width || layout.scrollHeight !== height)
434
- failures.push(`${name} document overflows the viewport`);
435
- if (!layout.composerVisible || layout.conversationWidth < 520)
436
- failures.push(`${name} conversation does not use the available space`);
437
- if (!layout.inspectorContained)
438
- failures.push(`${name} inspector escapes the viewport`);
439
- }
440
- if (!report.responsive.medium.navigationVisible || report.responsive.medium.inspectorVisible)
441
- failures.push("medium layout did not maximize the conversation canvas by default");
442
- if (!report.responsive.compact.navigationVisible || report.responsive.compact.inspectorVisible)
443
- failures.push("compact layout did not collapse the inspector");
444
- if (!report.responsive.compactInspector.inspectorVisible)
445
- failures.push("compact inspector drawer did not open");
446
- if (fixture) {
447
- if (report.images.approval?.width !== 1_920 || report.images.approval.height !== 1_080)
448
- failures.push("approval capture is not 1920x1080");
449
- if (!report.mainVisual.transcriptScrollable || !report.probe.document.transcriptScrollable)
450
- failures.push("transcript fixture did not exercise vertical scrolling");
451
- if (!report.mainVisual.codeContained || !report.probe.document.codeContained)
452
- failures.push("long code did not exercise contained horizontal scrolling");
453
- if (!report.mainVisual.composerVisible || !report.probe.document.composerVisible)
454
- failures.push("composer is not fully visible");
455
- if (!report.mainVisual.streamingVisible)
456
- failures.push("streaming delta is not visible in the main capture");
457
- if (!report.probe.document.modalContained)
458
- failures.push("approval modal escapes the viewport");
459
- if (!report.probe.document.backgroundInert)
460
- failures.push("approval modal did not make the background inert");
461
- if (report.probe.document.focusLabel !== "拒绝")
462
- failures.push("approval modal did not receive deterministic focus");
463
- }
464
- if (failures.length > 0)
465
- throw new Error(`Electron smoke validation failed: ${failures.join("; ")}`);
466
- }
467
- function smokeEvent(seq, type, payload) {
468
- return {
469
- version: 1,
470
- kind: "event",
471
- event: {
472
- v: 1,
473
- kind: "event",
474
- id: `desktop-smoke-${seq}`,
475
- streamId: "desktop-smoke",
476
- seq,
477
- ts: new Date(Date.parse("2026-08-10T08:30:00.000Z") + seq).toISOString(),
478
- durable: false,
479
- type,
480
- payload,
481
- },
482
- };
483
- }
484
- async function waitForRenderer(target, expression, timeoutMs) {
485
- await waitFor(async () => Boolean(await target.webContents.executeJavaScript(expression)), timeoutMs);
486
- }
487
- async function waitFor(predicate, timeoutMs) {
488
- const deadline = Date.now() + timeoutMs;
489
- while (Date.now() < deadline) {
490
- if (await predicate())
491
- return;
492
- await new Promise((resolve) => setTimeout(resolve, 50));
493
- }
494
- throw new Error(`Timed out after ${timeoutMs}ms`);
495
- }
496
- async function settlePaint() {
497
- await new Promise((resolve) => setTimeout(resolve, 320));
498
- }
499
- async function setRendererTheme(target, theme) {
500
- await target.webContents.executeJavaScript(`(() => {
501
- if (document.documentElement.dataset.theme !== ${JSON.stringify(theme)}) {
502
- document.querySelector('#theme-toggle')?.click();
98
+ if (shuttingDown)
99
+ return;
100
+ if (arguments_.smokeDirectory) {
101
+ await mkdir(arguments_.smokeDirectory, { recursive: true, mode: 0o700 });
102
+ const probe = await target.webContents.executeJavaScript(`({
103
+ width: innerWidth, height: innerHeight,
104
+ node: typeof process, preload: typeof window.pspore,
105
+ iconLoaded: document.querySelector('img').complete && document.querySelector('img').naturalWidth > 0,
106
+ overflow: document.documentElement.scrollWidth > innerWidth || document.documentElement.scrollHeight > innerHeight,
107
+ status: document.getElementById('status').textContent
108
+ })`);
109
+ await writeFile(join(arguments_.smokeDirectory, "desktop-startup.json"), JSON.stringify({
110
+ visible: target.isVisible(), mainCreated: BrowserWindow.getAllWindows().some((candidate) => candidate !== target),
111
+ probe,
112
+ }, null, 2));
113
+ await writeFile(join(arguments_.smokeDirectory, "desktop-startup.png"), (await target.webContents.capturePage()).toPNG());
503
114
  }
504
- })()`);
505
- await waitForRenderer(target, `document.documentElement.dataset.theme === ${JSON.stringify(theme)}`, 2_000);
506
115
  }
507
- async function readThemeSnapshot(target) {
508
- return target.webContents.executeJavaScript(`(() => ({
509
- colorScheme: getComputedStyle(document.documentElement).colorScheme,
510
- backgroundColor: getComputedStyle(document.body).backgroundColor,
511
- surfaceColor: getComputedStyle(document.querySelector('.panel')).backgroundColor
512
- }))()`);
116
+ async function setStartupMessage(message) {
117
+ if (!splash || splash.isDestroyed() || shuttingDown)
118
+ return;
119
+ await splash.webContents.executeJavaScript(`document.getElementById('status').textContent = ${JSON.stringify(message)}`);
513
120
  }
514
- async function readResponsiveLayout(target) {
515
- return target.webContents.executeJavaScript(`(() => {
516
- const visible = (selector) => {
517
- const node = document.querySelector(selector);
518
- return Boolean(node && getComputedStyle(node).display !== 'none');
519
- };
520
- const composerRect = document.querySelector('.composer')?.getBoundingClientRect();
521
- const conversationRect = document.querySelector('.conversation')?.getBoundingClientRect();
522
- const inspectorRect = document.querySelector('#inspector-panel')?.getBoundingClientRect();
523
- return {
524
- viewport: { width: innerWidth, height: innerHeight },
525
- scrollWidth: document.documentElement.scrollWidth,
526
- scrollHeight: document.documentElement.scrollHeight,
527
- navigationVisible: visible('#navigation-panel'),
528
- inspectorVisible: visible('#inspector-panel'),
529
- inspectorContained: !visible('#inspector-panel') || Boolean(inspectorRect && inspectorRect.left >= 0 && inspectorRect.top >= 0 && inspectorRect.right <= innerWidth && inspectorRect.bottom <= innerHeight),
530
- conversationWidth: conversationRect?.width || 0,
531
- composerVisible: Boolean(composerRect && composerRect.top >= 0 && composerRect.bottom <= innerHeight)
532
- };
533
- })()`);
121
+ function closeStartupWindow() {
122
+ const target = splash;
123
+ splash = undefined;
124
+ if (target && !target.isDestroyed())
125
+ target.destroy();
534
126
  }
535
127
  function parseArguments(argv) {
536
128
  let home;
@@ -567,26 +159,26 @@ function parseArguments(argv) {
567
159
  };
568
160
  }
569
161
  function failStartup(error) {
570
- exitCode = 1;
162
+ if (shuttingDown)
163
+ return;
571
164
  process.stderr.write(`pSpore desktop: ${toError(error).message}\n`);
572
- requestShutdown(exitCode);
165
+ if (!arguments_.smokeDirectory && app.isReady()) {
166
+ // A service/configuration failure must not leave an endless loading card.
167
+ dialog.showErrorBox("pSpore 启动失败", `无法打开工作空间。\n\n${toError(error).message}\n\n可从应用快捷方式菜单打开启动日志。`);
168
+ }
169
+ shutdown(1);
573
170
  }
574
- function requestShutdown(code) {
171
+ function shutdown(code) {
575
172
  if (shuttingDown)
576
173
  return;
577
174
  shuttingDown = true;
578
- exitCode = code;
579
- unsubscribeBridge?.();
580
- unsubscribeBridge = undefined;
581
- void (async () => {
582
- await bridge?.stop();
583
- await daemonSupervisor?.stop();
584
- })()
585
- .catch((error) => {
586
- process.stderr.write(`pSpore desktop shutdown: ${toError(error).message}\n`);
587
- exitCode = 1;
588
- })
589
- .finally(() => app.exit(exitCode));
175
+ if (stopApplication)
176
+ stopApplication(code);
177
+ else {
178
+ nativeStartup?.close();
179
+ closeStartupWindow();
180
+ app.exit(code);
181
+ }
590
182
  }
591
183
  function toError(error) {
592
184
  return error instanceof Error ? error : new Error(String(error));