@chainingintention/pi-web-cn 2.0.0 → 2.202610.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (584) hide show
  1. package/README.md +61 -106
  2. package/dist/.plugins-ready +1 -0
  3. package/dist/cli.js +331 -174
  4. package/dist/cli.js.map +1 -1
  5. package/dist/client/apple-touch-icon-dev.png +0 -0
  6. package/dist/client/assets/PiWebApp-BXSziMEg.js +4921 -0
  7. package/dist/client/assets/classic-CyM97Akm.css +54 -0
  8. package/dist/client/assets/external-B9K8duyP.js +2 -0
  9. package/dist/client/assets/graphite-DquaqFXU.css +54 -0
  10. package/dist/client/assets/index-cdtUOfOW.js +2 -0
  11. package/dist/client/assets/paper-CB1oLuRr.css +54 -0
  12. package/dist/client/assets/pi-web-dark-D_KtbM95.css +54 -0
  13. package/dist/client/assets/pi-web-light-DSNLMIBI.css +54 -0
  14. package/dist/client/assets/vendor-editor-core-BfnHL1Dg.js +10 -0
  15. package/dist/client/assets/vendor-editor-languages-bNquDJP0.js +28 -0
  16. package/dist/client/favicon-dev.svg +8 -0
  17. package/dist/client/index.html +7 -51
  18. package/dist/client/manifest.webmanifest +2 -1
  19. package/dist/client/pwa-icon-dev-192.png +0 -0
  20. package/dist/client/pwa-icon-dev-512.png +0 -0
  21. package/dist/config.js +55 -8
  22. package/dist/config.js.map +1 -1
  23. package/dist/docker/piWebDockerCommandPlan.js +13 -4
  24. package/dist/docker/piWebDockerCommandPlan.js.map +1 -1
  25. package/dist/environment.js +4 -0
  26. package/dist/environment.js.map +1 -0
  27. package/dist/localization.js +24 -0
  28. package/dist/localization.js.map +1 -0
  29. package/dist/nativeServices/installedServiceDefinitions.js +617 -0
  30. package/dist/nativeServices/installedServiceDefinitions.js.map +1 -0
  31. package/dist/nativeServices/serviceAction.js +78 -20
  32. package/dist/nativeServices/serviceAction.js.map +1 -1
  33. package/dist/nativeServices/serviceDoctor.js +418 -114
  34. package/dist/nativeServices/serviceDoctor.js.map +1 -1
  35. package/dist/nativeServices/servicePlan.js +2 -2
  36. package/dist/nativeServices/servicePlan.js.map +1 -1
  37. package/dist/pi-packages/captains-log/README.md +11 -0
  38. package/dist/pi-packages/captains-log/dist/browser/channelProtocol.js +29 -0
  39. package/dist/pi-packages/captains-log/dist/browser/index.js +143 -0
  40. package/dist/pi-packages/captains-log/dist/browser/markdown.js +68 -0
  41. package/dist/pi-packages/captains-log/dist/browser/panel.js +148 -0
  42. package/dist/pi-packages/captains-log/dist/browser/protocol.js +26 -0
  43. package/dist/pi-packages/captains-log/dist/companion.js +126 -0
  44. package/dist/pi-packages/captains-log/dist/roundtrip.js +62 -0
  45. package/dist/pi-packages/captains-log/dist/server.js +210 -0
  46. package/dist/pi-packages/captains-log/dist/store.js +75 -0
  47. package/dist/pi-packages/captains-log/docs/usage.md +29 -0
  48. package/dist/pi-packages/captains-log/i18n.js +7 -0
  49. package/dist/pi-packages/captains-log/package.json +35 -0
  50. package/dist/pi-packages/captains-log/tsconfig.json +21 -0
  51. package/dist/pi-packages/captains-log/vite.config.mjs +28 -0
  52. package/dist/pi-packages/relays/extensions/index.js +61 -0
  53. package/dist/pi-packages/relays/i18n.js +7 -0
  54. package/dist/{pi-web-plugins → pi-packages}/relays/markdownDocument.js +3 -2
  55. package/dist/pi-packages/relays/package.json +20 -0
  56. package/dist/pi-packages/relays/pi-web-plugin.js +96 -0
  57. package/dist/pi-packages/relays/prompts/relay-worktree.md +18 -0
  58. package/dist/pi-packages/relays/prompts/relay.md +18 -0
  59. package/dist/{pi-web-plugins → pi-packages}/relays/relayDiscovery.js +2 -2
  60. package/dist/pi-packages/relays/relayDispatch.js +67 -0
  61. package/dist/pi-packages/relays/relayIdentity.js +37 -0
  62. package/dist/{pi-web-plugins → pi-packages}/relays/relaysPanelElement.js +98 -59
  63. package/dist/pi-packages/relays/skills/relay/SKILL.md +50 -0
  64. package/dist/pi-packages/relays/skills/relay-runner/SKILL.md +74 -0
  65. package/dist/pi-packages/workflows/browser/pi-web-plugin.js +42 -0
  66. package/dist/pi-packages/workflows/browser/worktreeDialog.js +170 -0
  67. package/dist/pi-packages/workflows/package.json +23 -0
  68. package/dist/pi-packages/workflows/prompts/worktree.md +21 -0
  69. package/dist/pi-packages/workflows/server-plugin.js +586 -0
  70. package/dist/pi-packages/workflows/vite.config.mjs +17 -0
  71. package/dist/pi-web-plugins/code-highlight/browser/pi-web-plugin.js +22 -0
  72. package/dist/pi-web-plugins/code-highlight/package.json +13 -0
  73. package/dist/pi-web-plugins/files/browser/assets/files-icon-DZObYhxb.svg +3 -0
  74. package/dist/pi-web-plugins/files/browser/pi-web-plugin.js +466 -0
  75. package/dist/pi-web-plugins/files/package.json +15 -0
  76. package/dist/pi-web-plugins/git/browser/git-contract.js +22 -11
  77. package/dist/pi-web-plugins/git/browser/git-panel.js +228 -100
  78. package/dist/pi-web-plugins/git/browser/i18n.js +7 -0
  79. package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +28 -4
  80. package/dist/pi-web-plugins/git/git-backend.js +61 -21
  81. package/dist/pi-web-plugins/git/gitStatusCache.js +48 -0
  82. package/dist/pi-web-plugins/git/i18n.js +7 -0
  83. package/dist/pi-web-plugins/git/package.json +1 -0
  84. package/dist/pi-web-plugins/git/server-plugin.js +45 -24
  85. package/dist/pi-web-plugins/git/workspace-discovery-notify.cjs +14 -0
  86. package/dist/pi-web-plugins/git/workspaceDiscoveryHooks.js +186 -0
  87. package/dist/pi-web-plugins/info/i18n.js +7 -0
  88. package/dist/pi-web-plugins/info/infoInternals.js +76 -65
  89. package/dist/pi-web-plugins/info/package.json +1 -1
  90. package/dist/pi-web-plugins/info/pi-web-plugin.js +40 -42
  91. package/dist/pi-web-plugins/mermaid/browser/mermaid-engine.js +4255 -0
  92. package/dist/pi-web-plugins/mermaid/browser/pi-web-plugin.js +11 -0
  93. package/dist/pi-web-plugins/mermaid/package.json +13 -0
  94. package/dist/pi-web-plugins/terminal/browser/pi-web-plugin.js +219 -0
  95. package/dist/pi-web-plugins/terminal/i18n.js +7 -0
  96. package/dist/pi-web-plugins/terminal/package.json +16 -0
  97. package/dist/pi-web-plugins/terminal/server-plugin.js +367 -0
  98. package/dist/pi-web-plugins/terminal/terminalService.js +436 -0
  99. package/dist/pi-web-plugins/updates/i18n.js +7 -0
  100. package/dist/pi-web-plugins/updates/package.json +1 -1
  101. package/dist/pi-web-plugins/updates/pi-web-plugin.js +143 -140
  102. package/dist/pi-web-plugins/updates/updatesLogic.js +24 -24
  103. package/dist/pi-web-plugins/workspace-tasks/config.js +12 -11
  104. package/dist/pi-web-plugins/workspace-tasks/i18n.js +7 -0
  105. package/dist/pi-web-plugins/workspace-tasks/package.json +1 -1
  106. package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +31 -9
  107. package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +54 -36
  108. package/dist/pi-web-plugins/workspace-tasks/workspaceTasksClient.js +12 -11
  109. package/dist/pi-web-plugins/zh-cn/auth.json +9 -0
  110. package/dist/pi-web-plugins/zh-cn/bundled.json +219 -0
  111. package/dist/pi-web-plugins/zh-cn/cli.json +230 -0
  112. package/dist/pi-web-plugins/zh-cn/client.json +225 -0
  113. package/dist/pi-web-plugins/zh-cn/components.json +1252 -0
  114. package/dist/pi-web-plugins/zh-cn/config-errors.json +188 -0
  115. package/dist/pi-web-plugins/zh-cn/files.json +50 -0
  116. package/dist/pi-web-plugins/zh-cn/machines.json +28 -0
  117. package/dist/pi-web-plugins/zh-cn/main.json +459 -0
  118. package/dist/pi-web-plugins/zh-cn/management-errors.json +23 -0
  119. package/dist/pi-web-plugins/zh-cn/manifest.webmanifest +25 -0
  120. package/dist/pi-web-plugins/zh-cn/metadata.json +3 -0
  121. package/dist/pi-web-plugins/zh-cn/package.json +10 -0
  122. package/dist/pi-web-plugins/zh-cn/pi-web-plugin.js +16 -0
  123. package/dist/pi-web-plugins/zh-cn/projects.json +6 -0
  124. package/dist/pi-web-plugins/zh-cn/provider-auth.json +18 -0
  125. package/dist/pi-web-plugins/zh-cn/routing.json +68 -0
  126. package/dist/pi-web-plugins/zh-cn/server-main.json +99 -0
  127. package/dist/pi-web-plugins/zh-cn/server.json +256 -0
  128. package/dist/piAi.js +4 -0
  129. package/dist/piAi.js.map +1 -0
  130. package/dist/piRuntime.js +150 -0
  131. package/dist/piRuntime.js.map +1 -0
  132. package/dist/piSdk.js +5 -0
  133. package/dist/piSdk.js.map +1 -0
  134. package/dist/piWebUpdate.js +508 -0
  135. package/dist/piWebUpdate.js.map +1 -0
  136. package/dist/piWebVersionReport.js +109 -41
  137. package/dist/piWebVersionReport.js.map +1 -1
  138. package/dist/plugin-api.d.ts +609 -29
  139. package/dist/plugin-api.js +1 -0
  140. package/dist/pluginRecoveryCli.js +27 -25
  141. package/dist/pluginRecoveryCli.js.map +1 -1
  142. package/dist/server/activity/workspaceActivityService.js +13 -6
  143. package/dist/server/activity/workspaceActivityService.js.map +1 -1
  144. package/dist/server/app.js +155 -77
  145. package/dist/server/app.js.map +1 -1
  146. package/dist/server/audit/managementAuditStore.js +6 -1
  147. package/dist/server/audit/managementAuditStore.js.map +1 -1
  148. package/dist/server/audit/normalToolAuditCli.js +45 -43
  149. package/dist/server/audit/normalToolAuditCli.js.map +1 -1
  150. package/dist/server/browserMessageProjection.js +39 -22
  151. package/dist/server/browserMessageProjection.js.map +1 -1
  152. package/dist/server/configRoutes.js +54 -33
  153. package/dist/server/configRoutes.js.map +1 -1
  154. package/dist/server/deploymentIdentity.js +67 -0
  155. package/dist/server/deploymentIdentity.js.map +1 -0
  156. package/dist/server/deploymentIdentityRoutes.js +22 -0
  157. package/dist/server/deploymentIdentityRoutes.js.map +1 -0
  158. package/dist/server/diagnostics/nodePtyNativeModule.js +7 -6
  159. package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -1
  160. package/dist/server/diagnostics/nodePtySpawnHelper.js +21 -20
  161. package/dist/server/diagnostics/nodePtySpawnHelper.js.map +1 -1
  162. package/dist/server/index.js +10 -1
  163. package/dist/server/index.js.map +1 -1
  164. package/dist/server/knownAutoInstallPiPackages.js +36 -0
  165. package/dist/server/knownAutoInstallPiPackages.js.map +1 -0
  166. package/dist/server/knownPiPackages.js +12 -0
  167. package/dist/server/knownPiPackages.js.map +1 -0
  168. package/dist/server/machines/machineClient.js +23 -19
  169. package/dist/server/machines/machineClient.js.map +1 -1
  170. package/dist/server/machines/machinePluginProxyRoutes.js +131 -31
  171. package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
  172. package/dist/server/machines/machineProxyRoutes.js +138 -47
  173. package/dist/server/machines/machineProxyRoutes.js.map +1 -1
  174. package/dist/server/machines/machineRoutes.js +18 -13
  175. package/dist/server/machines/machineRoutes.js.map +1 -1
  176. package/dist/server/machines/machineService.js +40 -18
  177. package/dist/server/machines/machineService.js.map +1 -1
  178. package/dist/server/managementEmbed.js +149 -30
  179. package/dist/server/managementEmbed.js.map +1 -1
  180. package/dist/server/managementPrivileges.js +21 -0
  181. package/dist/server/managementPrivileges.js.map +1 -0
  182. package/dist/server/managementTerminalProxyRoutes.js +124 -0
  183. package/dist/server/managementTerminalProxyRoutes.js.map +1 -0
  184. package/dist/server/navigationPreferenceRoutes.js +59 -0
  185. package/dist/server/navigationPreferenceRoutes.js.map +1 -0
  186. package/dist/server/normalAuth.js +77 -53
  187. package/dist/server/normalAuth.js.map +1 -1
  188. package/dist/server/notices/serverNoticeRoutes.js +43 -0
  189. package/dist/server/notices/serverNoticeRoutes.js.map +1 -0
  190. package/dist/server/notices/serverNoticeService.js +26 -0
  191. package/dist/server/notices/serverNoticeService.js.map +1 -0
  192. package/dist/server/notices/serverNoticeStore.js +122 -0
  193. package/dist/server/notices/serverNoticeStore.js.map +1 -0
  194. package/dist/server/passwordVerifier.js +91 -0
  195. package/dist/server/passwordVerifier.js.map +1 -0
  196. package/dist/server/piPackageIdentity.js +25 -0
  197. package/dist/server/piPackageIdentity.js.map +1 -0
  198. package/dist/server/piPackageService.js +72 -9
  199. package/dist/server/piPackageService.js.map +1 -1
  200. package/dist/server/piWebPluginCatalog.js +212 -35
  201. package/dist/server/piWebPluginCatalog.js.map +1 -1
  202. package/dist/server/piWebPluginLifecycle.js +29 -3
  203. package/dist/server/piWebPluginLifecycle.js.map +1 -1
  204. package/dist/server/piWebPluginService.js +199 -292
  205. package/dist/server/piWebPluginService.js.map +1 -1
  206. package/dist/server/piWebStatus.js +57 -100
  207. package/dist/server/piWebStatus.js.map +1 -1
  208. package/dist/server/pluginCallbackDrain.js +35 -0
  209. package/dist/server/pluginCallbackDrain.js.map +1 -0
  210. package/dist/server/plugins/pluginBackendChannelProxyAdmission.js +84 -0
  211. package/dist/server/plugins/pluginBackendChannelProxyAdmission.js.map +1 -0
  212. package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js +234 -0
  213. package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js.map +1 -0
  214. package/dist/server/plugins/pluginBackendChannelProxyRoutes.js +61 -0
  215. package/dist/server/plugins/pluginBackendChannelProxyRoutes.js.map +1 -0
  216. package/dist/server/plugins/pluginBackendProxyRoutes.js +47 -57
  217. package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -1
  218. package/dist/server/plugins/pluginBackendRegistry.js +804 -0
  219. package/dist/server/plugins/pluginBackendRegistry.js.map +1 -0
  220. package/dist/server/plugins/projectPluginSettings.js +95 -0
  221. package/dist/server/plugins/projectPluginSettings.js.map +1 -0
  222. package/dist/server/plugins/serverPluginCatalogCapability.js +106 -0
  223. package/dist/server/plugins/serverPluginCatalogCapability.js.map +1 -0
  224. package/dist/server/plugins/serverPluginPiSessionEventsCapability.js +34 -0
  225. package/dist/server/plugins/serverPluginPiSessionEventsCapability.js.map +1 -0
  226. package/dist/server/plugins/serverPluginPiSessionsCapability.js +185 -0
  227. package/dist/server/plugins/serverPluginPiSessionsCapability.js.map +1 -0
  228. package/dist/server/plugins/serverPluginRuntime.js +1088 -108
  229. package/dist/server/plugins/serverPluginRuntime.js.map +1 -1
  230. package/dist/server/plugins/serverPluginWorkspacesCapability.js +114 -0
  231. package/dist/server/plugins/serverPluginWorkspacesCapability.js.map +1 -0
  232. package/dist/server/projectTrustRoutes.js +86 -0
  233. package/dist/server/projectTrustRoutes.js.map +1 -0
  234. package/dist/server/projects/projectLifecycleService.js +94 -0
  235. package/dist/server/projects/projectLifecycleService.js.map +1 -0
  236. package/dist/server/projects/projectService.js +7 -4
  237. package/dist/server/projects/projectService.js.map +1 -1
  238. package/dist/server/realtime/sessionEventHub.js +58 -39
  239. package/dist/server/realtime/sessionEventHub.js.map +1 -1
  240. package/dist/server/realtime/sessionEventScope.js +4 -0
  241. package/dist/server/realtime/sessionEventScope.js.map +1 -1
  242. package/dist/server/requestCancellation.js +15 -1
  243. package/dist/server/requestCancellation.js.map +1 -1
  244. package/dist/server/sessiond/agentHttpDispatcher.js +108 -0
  245. package/dist/server/sessiond/agentHttpDispatcher.js.map +1 -0
  246. package/dist/server/sessiond/autoInstallPiPackages.js +63 -0
  247. package/dist/server/sessiond/autoInstallPiPackages.js.map +1 -0
  248. package/dist/server/sessiond/pluginBackendChannelRoutes.js +410 -0
  249. package/dist/server/sessiond/pluginBackendChannelRoutes.js.map +1 -0
  250. package/dist/server/sessiond/pluginBackendRoutes.js +64 -47
  251. package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -1
  252. package/dist/server/sessiond/pluginCatalogRoutes.js +41 -0
  253. package/dist/server/sessiond/pluginCatalogRoutes.js.map +1 -0
  254. package/dist/server/sessiond/projectMutationRoutes.js +39 -0
  255. package/dist/server/sessiond/projectMutationRoutes.js.map +1 -0
  256. package/dist/server/sessiond/sessionDaemonShutdown.js +36 -0
  257. package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -0
  258. package/dist/server/sessiond/sessionDaemonStartup.js +30 -4
  259. package/dist/server/sessiond/sessionDaemonStartup.js.map +1 -1
  260. package/dist/server/sessiond/sessionProxyRoutes.js +263 -91
  261. package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
  262. package/dist/server/sessiond/sessionServiceDependencies.js +5 -0
  263. package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
  264. package/dist/server/sessiond/sessiondStateOwnership.js +174 -39
  265. package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -1
  266. package/dist/server/sessiond/workspaceCatalogRoutes.js +11 -5
  267. package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -1
  268. package/dist/server/sessiond/workspaceNotificationRoutes.js +55 -0
  269. package/dist/server/sessiond/workspaceNotificationRoutes.js.map +1 -0
  270. package/dist/server/sessiond/workspaceRemovalRoutes.js +1 -1
  271. package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -1
  272. package/dist/server/sessiond.js +313 -69
  273. package/dist/server/sessiond.js.map +1 -1
  274. package/dist/server/sessions/askUserDomain.js +232 -0
  275. package/dist/server/sessions/askUserDomain.js.map +1 -0
  276. package/dist/server/sessions/askUserTool.js +18 -21
  277. package/dist/server/sessions/askUserTool.js.map +1 -1
  278. package/dist/server/sessions/attachmentService.js +2 -6
  279. package/dist/server/sessions/attachmentService.js.map +1 -1
  280. package/dist/server/sessions/authRoutes.js +32 -19
  281. package/dist/server/sessions/authRoutes.js.map +1 -1
  282. package/dist/server/sessions/authService.js +31 -11
  283. package/dist/server/sessions/authService.js.map +1 -1
  284. package/dist/server/sessions/builtinCommands.js +28 -21
  285. package/dist/server/sessions/builtinCommands.js.map +1 -1
  286. package/dist/server/sessions/builtinExtensionFactories.js +36 -0
  287. package/dist/server/sessions/builtinExtensionFactories.js.map +1 -0
  288. package/dist/server/sessions/clientSessionPreview.js +14 -0
  289. package/dist/server/sessions/clientSessionPreview.js.map +1 -0
  290. package/dist/server/sessions/companionBackend.js +286 -0
  291. package/dist/server/sessions/companionBackend.js.map +1 -0
  292. package/dist/server/sessions/dockerEnvironmentFacts.js +98 -10
  293. package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -1
  294. package/dist/server/sessions/extensionSessionDispatch.js +76 -0
  295. package/dist/server/sessions/extensionSessionDispatch.js.map +1 -0
  296. package/dist/server/sessions/globalProviderPolicy.js +88 -19
  297. package/dist/server/sessions/globalProviderPolicy.js.map +1 -1
  298. package/dist/server/sessions/managementAgentTools.js +116 -6
  299. package/dist/server/sessions/managementAgentTools.js.map +1 -1
  300. package/dist/server/sessions/managementPermissionSystem.js +36 -14
  301. package/dist/server/sessions/managementPermissionSystem.js.map +1 -1
  302. package/dist/server/sessions/managementSandbox.js +7 -18
  303. package/dist/server/sessions/managementSandbox.js.map +1 -1
  304. package/dist/server/sessions/mcpAuthFlowService.js +289 -0
  305. package/dist/server/sessions/mcpAuthFlowService.js.map +1 -0
  306. package/dist/server/sessions/mcpAuthRoutes.js +104 -0
  307. package/dist/server/sessions/mcpAuthRoutes.js.map +1 -0
  308. package/dist/server/sessions/mcpOAuthCredentialAdapter.js +92 -0
  309. package/dist/server/sessions/mcpOAuthCredentialAdapter.js.map +1 -0
  310. package/dist/server/sessions/messageHistoryTarget.js +79 -0
  311. package/dist/server/sessions/messageHistoryTarget.js.map +1 -0
  312. package/dist/server/sessions/messagePaging.js +2 -2
  313. package/dist/server/sessions/messagePaging.js.map +1 -1
  314. package/dist/server/sessions/modelCatalogRefresher.js +4 -3
  315. package/dist/server/sessions/modelCatalogRefresher.js.map +1 -1
  316. package/dist/server/sessions/oauthLoginFlowService.js +19 -20
  317. package/dist/server/sessions/oauthLoginFlowService.js.map +1 -1
  318. package/dist/server/sessions/piSessionEventConnections.js +68 -0
  319. package/dist/server/sessions/piSessionEventConnections.js.map +1 -0
  320. package/dist/server/sessions/piSessionManagerGateway.js +206 -27
  321. package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
  322. package/dist/server/sessions/piSessionService.js +2508 -991
  323. package/dist/server/sessions/piSessionService.js.map +1 -1
  324. package/dist/server/sessions/plainTextTheme.js +1 -1
  325. package/dist/server/sessions/plainTextTheme.js.map +1 -1
  326. package/dist/server/sessions/projectAuthService.js +35 -6
  327. package/dist/server/sessions/projectAuthService.js.map +1 -1
  328. package/dist/server/sessions/projectSettingsScope.js +88 -33
  329. package/dist/server/sessions/projectSettingsScope.js.map +1 -1
  330. package/dist/server/sessions/projectTrust.js +119 -0
  331. package/dist/server/sessions/projectTrust.js.map +1 -0
  332. package/dist/server/sessions/queuedMessageMutation.js +170 -0
  333. package/dist/server/sessions/queuedMessageMutation.js.map +1 -0
  334. package/dist/server/sessions/relaySkill.js +169 -0
  335. package/dist/server/sessions/relaySkill.js.map +1 -0
  336. package/dist/server/sessions/sessionActivityMarker.js +105 -0
  337. package/dist/server/sessions/sessionActivityMarker.js.map +1 -0
  338. package/dist/server/sessions/sessionArchiveMapping.js +16 -15
  339. package/dist/server/sessions/sessionArchiveMapping.js.map +1 -1
  340. package/dist/server/sessions/sessionArchiveStore.js +5 -1
  341. package/dist/server/sessions/sessionArchiveStore.js.map +1 -1
  342. package/dist/server/sessions/sessionArchiveTree.js +2 -2
  343. package/dist/server/sessions/sessionArchiveTree.js.map +1 -1
  344. package/dist/server/sessions/sessionCleanup.js +4 -3
  345. package/dist/server/sessions/sessionCleanup.js.map +1 -1
  346. package/dist/server/sessions/sessionCommandService.js +79 -87
  347. package/dist/server/sessions/sessionCommandService.js.map +1 -1
  348. package/dist/server/sessions/sessionEnvironmentFacts.js +5 -4
  349. package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -1
  350. package/dist/server/sessions/sessionFileHeader.js +41 -0
  351. package/dist/server/sessions/sessionFileHeader.js.map +1 -1
  352. package/dist/server/sessions/sessionImageDimensions.js +31 -0
  353. package/dist/server/sessions/sessionImageDimensions.js.map +1 -0
  354. package/dist/server/sessions/sessionMediaIndex.js +191 -0
  355. package/dist/server/sessions/sessionMediaIndex.js.map +1 -0
  356. package/dist/server/sessions/sessionMetadata.js +51 -0
  357. package/dist/server/sessions/sessionMetadata.js.map +1 -0
  358. package/dist/server/sessions/sessionModelScope.js +160 -0
  359. package/dist/server/sessions/sessionModelScope.js.map +1 -0
  360. package/dist/server/sessions/sessionNameGenerator.js +16 -35
  361. package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
  362. package/dist/server/sessions/sessionNotificationStore.js +14 -11
  363. package/dist/server/sessions/sessionNotificationStore.js.map +1 -1
  364. package/dist/server/sessions/sessionReadAccess.js +41 -0
  365. package/dist/server/sessions/sessionReadAccess.js.map +1 -0
  366. package/dist/server/sessions/sessionRoutes.js +221 -78
  367. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  368. package/dist/server/sessions/sessionSummaryScanner.js +62 -5
  369. package/dist/server/sessions/sessionSummaryScanner.js.map +1 -1
  370. package/dist/server/sessions/sessionTreeProjection.js +50 -49
  371. package/dist/server/sessions/sessionTreeProjection.js.map +1 -1
  372. package/dist/server/sessions/sessionUiEvents.js +22 -10
  373. package/dist/server/sessions/sessionUiEvents.js.map +1 -1
  374. package/dist/server/sessions/sessionUnreadStore.js +53 -3
  375. package/dist/server/sessions/sessionUnreadStore.js.map +1 -1
  376. package/dist/server/sessions/spawnSessionTool.js +14 -6
  377. package/dist/server/sessions/spawnSessionTool.js.map +1 -1
  378. package/dist/server/sessions/spawnSubsessionTool.js +40 -24
  379. package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
  380. package/dist/server/sessions/spawnTargetResolver.js +1 -1
  381. package/dist/server/sessions/subsessionCompletionDelivery.js +15 -0
  382. package/dist/server/sessions/subsessionCompletionDelivery.js.map +1 -0
  383. package/dist/server/sessions/subsessionCompletionInbox.js +134 -0
  384. package/dist/server/sessions/subsessionCompletionInbox.js.map +1 -0
  385. package/dist/server/sessions/subsessionProvenance.js +27 -0
  386. package/dist/server/sessions/subsessionProvenance.js.map +1 -0
  387. package/dist/server/sessions/transcriptBranchCache.js +81 -0
  388. package/dist/server/sessions/transcriptBranchCache.js.map +1 -0
  389. package/dist/server/sessions/transcriptMarkdown.js +48 -0
  390. package/dist/server/sessions/transcriptMarkdown.js.map +1 -0
  391. package/dist/server/sessions/transcriptMessages.js +57 -0
  392. package/dist/server/sessions/transcriptMessages.js.map +1 -0
  393. package/dist/server/startupLogger.js +13 -0
  394. package/dist/server/startupLogger.js.map +1 -0
  395. package/dist/server/startupUrls.js +27 -0
  396. package/dist/server/startupUrls.js.map +1 -0
  397. package/dist/server/status/machineStatusService.js +15 -7
  398. package/dist/server/status/machineStatusService.js.map +1 -1
  399. package/dist/server/storage/navigationPreferenceStore.js +138 -0
  400. package/dist/server/storage/navigationPreferenceStore.js.map +1 -0
  401. package/dist/server/storage/piPackageDismissalStore.js +78 -0
  402. package/dist/server/storage/piPackageDismissalStore.js.map +1 -0
  403. package/dist/server/terminals/{terminalService.js → managementCommandRunService.js} +98 -102
  404. package/dist/server/terminals/managementCommandRunService.js.map +1 -0
  405. package/dist/server/terminals/managementTerminalRoutes.js +91 -0
  406. package/dist/server/terminals/managementTerminalRoutes.js.map +1 -0
  407. package/dist/server/terminals/requiredTerminalService.js +103 -0
  408. package/dist/server/terminals/requiredTerminalService.js.map +1 -0
  409. package/dist/server/webSocketBridge.js +348 -0
  410. package/dist/server/webSocketBridge.js.map +1 -1
  411. package/dist/server/workbench/accessStateStore.js +33 -5
  412. package/dist/server/workbench/accessStateStore.js.map +1 -1
  413. package/dist/server/workbench/capabilitySearch.js +3 -2
  414. package/dist/server/workbench/capabilitySearch.js.map +1 -1
  415. package/dist/server/workbench/gatewayIntegration.js +40 -13
  416. package/dist/server/workbench/gatewayIntegration.js.map +1 -1
  417. package/dist/server/workbench/knowledgeSearch.js +100 -0
  418. package/dist/server/workbench/knowledgeSearch.js.map +1 -0
  419. package/dist/server/workbench/mcpClient.js +41 -8
  420. package/dist/server/workbench/mcpClient.js.map +1 -1
  421. package/dist/server/workbench/skillSync.js +11 -3
  422. package/dist/server/workbench/skillSync.js.map +1 -1
  423. package/dist/server/workbench/workbenchClient.js +130 -7
  424. package/dist/server/workbench/workbenchClient.js.map +1 -1
  425. package/dist/server/workbench/workbenchTools.js +196 -14
  426. package/dist/server/workbench/workbenchTools.js.map +1 -1
  427. package/dist/server/workingDirectory.js +3 -2
  428. package/dist/server/workingDirectory.js.map +1 -1
  429. package/dist/server/workspaceExplorerRoutes.js +53 -21
  430. package/dist/server/workspaceExplorerRoutes.js.map +1 -1
  431. package/dist/server/workspaces/fileContentService.js +31 -16
  432. package/dist/server/workspaces/fileContentService.js.map +1 -1
  433. package/dist/server/workspaces/fileOperationService.js +11 -9
  434. package/dist/server/workspaces/fileOperationService.js.map +1 -1
  435. package/dist/server/workspaces/filePreviewService.js +10 -6
  436. package/dist/server/workspaces/filePreviewService.js.map +1 -1
  437. package/dist/server/workspaces/fileSuggestions.js +77 -10
  438. package/dist/server/workspaces/fileSuggestions.js.map +1 -1
  439. package/dist/server/workspaces/fileTreeService.js +103 -15
  440. package/dist/server/workspaces/fileTreeService.js.map +1 -1
  441. package/dist/server/workspaces/normalProjectCwdResolver.js +35 -0
  442. package/dist/server/workspaces/normalProjectCwdResolver.js.map +1 -0
  443. package/dist/server/workspaces/pathAccessPolicy.js +24 -18
  444. package/dist/server/workspaces/pathAccessPolicy.js.map +1 -1
  445. package/dist/server/workspaces/pathSafety.js +6 -5
  446. package/dist/server/workspaces/pathSafety.js.map +1 -1
  447. package/dist/server/workspaces/projectPiWebConfig.js +95 -8
  448. package/dist/server/workspaces/projectPiWebConfig.js.map +1 -1
  449. package/dist/server/workspaces/projectWorkspaceCwds.js +0 -5
  450. package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -1
  451. package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +37 -22
  452. package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -1
  453. package/dist/server/workspaces/workspaceCatalog.js +6 -4
  454. package/dist/server/workspaces/workspaceCatalog.js.map +1 -1
  455. package/dist/server/workspaces/workspaceChangeNotifier.js +12 -0
  456. package/dist/server/workspaces/workspaceChangeNotifier.js.map +1 -0
  457. package/dist/server/workspaces/workspaceDeletionRoutes.js +9 -70
  458. package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -1
  459. package/dist/server/workspaces/workspaceNotificationCommand.js +64 -0
  460. package/dist/server/workspaces/workspaceNotificationCommand.js.map +1 -0
  461. package/dist/server/workspaces/workspaceProviderRegistry.js +128 -24
  462. package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -1
  463. package/dist/server/workspaces/workspaceRemovalService.js +41 -7
  464. package/dist/server/workspaces/workspaceRemovalService.js.map +1 -1
  465. package/dist/server/workspaces/workspaceRouteContext.js +1 -1
  466. package/dist/server/workspaces/workspaceRouteContext.js.map +1 -1
  467. package/dist/server/workspaces/workspaceRouteErrors.js +4 -2
  468. package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -1
  469. package/dist/server/workspaces/workspaceUserError.js +18 -0
  470. package/dist/server/workspaces/workspaceUserError.js.map +1 -0
  471. package/dist/server-plugin-api.d.ts +349 -49
  472. package/dist/server-plugin-api.js +344 -2
  473. package/dist/server-plugin-api.js.map +1 -1
  474. package/dist/serverPluginRecovery.js +6 -1
  475. package/dist/serverPluginRecovery.js.map +1 -1
  476. package/dist/sessiond/activeAgentProfile.js +27 -2
  477. package/dist/sessiond/activeAgentProfile.js.map +1 -1
  478. package/dist/sessiond/config.js +4 -3
  479. package/dist/sessiond/config.js.map +1 -1
  480. package/dist/sessiond/sessionDaemonClient.js +75 -19
  481. package/dist/sessiond/sessionDaemonClient.js.map +1 -1
  482. package/dist/sessiond/sessionDaemonErrors.js +49 -0
  483. package/dist/sessiond/sessionDaemonErrors.js.map +1 -0
  484. package/dist/shared/activeAgentProfile.js +2 -34
  485. package/dist/shared/activeAgentProfile.js.map +1 -1
  486. package/dist/shared/activity.js +0 -3
  487. package/dist/shared/activity.js.map +1 -1
  488. package/dist/shared/apiTypes.js +10 -3
  489. package/dist/shared/apiTypes.js.map +1 -1
  490. package/dist/shared/capabilities.js +8 -0
  491. package/dist/shared/capabilities.js.map +1 -1
  492. package/dist/shared/federatedRoutes.js +77 -11
  493. package/dist/shared/federatedRoutes.js.map +1 -1
  494. package/dist/shared/i18n.js +41 -0
  495. package/dist/shared/i18n.js.map +1 -0
  496. package/dist/shared/machinePluginIds.js +30 -7
  497. package/dist/shared/machinePluginIds.js.map +1 -1
  498. package/dist/shared/machineStatus.js +46 -3
  499. package/dist/shared/machineStatus.js.map +1 -1
  500. package/dist/shared/navigationPreferences.js +6 -0
  501. package/dist/shared/navigationPreferences.js.map +1 -0
  502. package/dist/shared/piWebConfigParsing.js +118 -49
  503. package/dist/shared/piWebConfigParsing.js.map +1 -1
  504. package/dist/shared/piWebStatusParsing.js +33 -0
  505. package/dist/shared/piWebStatusParsing.js.map +1 -1
  506. package/dist/shared/pluginApiTypes.d.ts +87 -8
  507. package/dist/shared/pluginApiTypes.js +2 -0
  508. package/dist/shared/pluginApiTypes.js.map +1 -0
  509. package/dist/shared/pluginBackendProtocol.js +218 -2
  510. package/dist/shared/pluginBackendProtocol.js.map +1 -1
  511. package/dist/shared/pluginCatalogTypes.d.ts +49 -0
  512. package/dist/shared/pluginCatalogTypes.js +156 -0
  513. package/dist/shared/pluginCatalogTypes.js.map +1 -0
  514. package/dist/shared/pluginIds.js +10 -1
  515. package/dist/shared/pluginIds.js.map +1 -1
  516. package/dist/shared/requiredTerminalPlugin.js +6 -0
  517. package/dist/shared/requiredTerminalPlugin.js.map +1 -0
  518. package/dist/shared/serverNoticeContract.js +88 -0
  519. package/dist/shared/serverNoticeContract.js.map +1 -0
  520. package/dist/shared/sessionDefaults.js +42 -0
  521. package/dist/shared/sessionDefaults.js.map +1 -0
  522. package/dist/shared/sessionMedia.js +12 -0
  523. package/dist/shared/sessionMedia.js.map +1 -0
  524. package/dist/shared/streamingBehavior.js +8 -0
  525. package/dist/shared/streamingBehavior.js.map +1 -0
  526. package/dist/shared/themePreference.js +24 -0
  527. package/dist/shared/themePreference.js.map +1 -0
  528. package/dist/shared/thinkingLevels.js +17 -16
  529. package/dist/shared/thinkingLevels.js.map +1 -1
  530. package/dist/shared/workspaceFiles.js.map +1 -1
  531. package/dist/workspaceNotificationCli.js +95 -0
  532. package/dist/workspaceNotificationCli.js.map +1 -0
  533. package/docs/README.md +31 -0
  534. package/docs/config.md +344 -277
  535. package/docs/plugins.md +204 -930
  536. package/examples/session-bridge-plugin/README.md +20 -0
  537. package/examples/session-bridge-plugin/docs/usage.md +54 -0
  538. package/examples/session-bridge-plugin/package.json +26 -0
  539. package/examples/session-bridge-plugin/src/browser/index.ts +87 -0
  540. package/examples/session-bridge-plugin/src/browser/protocol.ts +24 -0
  541. package/examples/session-bridge-plugin/src/companion.ts +65 -0
  542. package/examples/session-bridge-plugin/src/reviewRun.ts +39 -0
  543. package/examples/session-bridge-plugin/src/server.ts +85 -0
  544. package/examples/session-bridge-plugin/src/store.ts +80 -0
  545. package/examples/session-bridge-plugin/tsconfig.json +21 -0
  546. package/examples/workspace-provider-plugin/README.md +39 -0
  547. package/examples/workspace-provider-plugin/package.json +25 -0
  548. package/examples/workspace-provider-plugin/src/browser/index.ts +69 -0
  549. package/examples/workspace-provider-plugin/src/server.ts +68 -0
  550. package/examples/workspace-provider-plugin/tsconfig.json +21 -0
  551. package/extensions/pi-web.ts +3 -1
  552. package/package.json +59 -36
  553. package/skills/README.md +28 -2
  554. package/skills/relay/SKILL.md +24 -89
  555. package/skills/relay-runner/SKILL.md +74 -0
  556. package/dist/client/assets/CodeViewer-DCl56K0Z.js +0 -4
  557. package/dist/client/assets/TerminalPanel-DCvoEjUF.js +0 -187
  558. package/dist/client/assets/index-CMvms5cF.js +0 -4325
  559. package/dist/client/assets/vendor-editor-core-vUi74DnD.js +0 -12
  560. package/dist/client/assets/vendor-editor-languages-DoaXUodB.js +0 -46
  561. package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +0 -1
  562. package/dist/client/assets/vendor-terminal-BrP-ENHg.css +0 -1
  563. package/dist/client/assets/vendor-terminal-D8k4UKM2.js +0 -35
  564. package/dist/pi-web-plugins/relays/package.json +0 -9
  565. package/dist/pi-web-plugins/relays/pi-web-plugin.js +0 -44
  566. package/dist/server/git/gitService.js +0 -290
  567. package/dist/server/git/gitService.js.map +0 -1
  568. package/dist/server/gitRoutes.js +0 -23
  569. package/dist/server/gitRoutes.js.map +0 -1
  570. package/dist/server/sessions/pendingAskStore.js +0 -278
  571. package/dist/server/sessions/pendingAskStore.js.map +0 -1
  572. package/dist/server/terminalProxyRoutes.js +0 -178
  573. package/dist/server/terminalProxyRoutes.js.map +0 -1
  574. package/dist/server/terminals/terminalRoutes.js +0 -164
  575. package/dist/server/terminals/terminalRoutes.js.map +0 -1
  576. package/dist/server/terminals/terminalService.js.map +0 -1
  577. package/dist/server/terminals/terminalSize.js +0 -17
  578. package/dist/server/terminals/terminalSize.js.map +0 -1
  579. package/dist/server/workspaces/gitWorktreeDiscovery.js +0 -43
  580. package/dist/server/workspaces/gitWorktreeDiscovery.js.map +0 -1
  581. package/dist/server/workspaces/workspaceService.js +0 -52
  582. package/dist/server/workspaces/workspaceService.js.map +0 -1
  583. /package/dist/{pi-web-plugins → pi-packages}/relays/vendor/README.md +0 -0
  584. /package/dist/{pi-web-plugins → pi-packages}/relays/vendor/marked.esm.js +0 -0
@@ -1,5 +1,8 @@
1
+ import { createTranslator } from "../shared/i18n.js";
1
2
  import { posix as posixPath } from "node:path";
2
- import { createDevelopmentNativeServicePlan, nativeServicePrerequisiteNeedsPathAdvice, resolveProductionNativeServicePlan, validateNativeServicePlan, } from "./servicePlan.js";
3
+ import { TextDecoder, TextEncoder } from "node:util";
4
+ import { ownEnvironmentValue } from "../environment.js";
5
+ import { createDevelopmentNativeServicePlan, nativeServiceManagerRefs, nativeServicePrerequisiteNeedsPathAdvice, resolveProductionNativeServicePlan, validateNativeServicePlan, } from "./servicePlan.js";
3
6
  export function inferInstalledNativeServiceMode(serviceIds) {
4
7
  if (serviceIds.size === 0)
5
8
  return "none";
@@ -11,15 +14,51 @@ export function inferInstalledNativeServiceMode(serviceIds) {
11
14
  return "development";
12
15
  return "ambiguous";
13
16
  }
14
- export function inspectInstalledProductionServiceContext(backend, definitions) {
17
+ /**
18
+ * Select the config path for a caller that is about to probe managed native
19
+ * services. A nonempty caller value is an explicit override and deliberately
20
+ * avoids interpreting installed files. Otherwise every relevant definition
21
+ * must parse to one consistent environment before its persisted path is used.
22
+ */
23
+ export function selectManagedNativeServiceConfig(backend, definitions, callerConfigPath) {
24
+ if (callerConfigPath !== undefined && callerConfigPath !== "") {
25
+ return { ok: true, value: { source: "caller", configPath: callerConfigPath } };
26
+ }
27
+ if (definitions.length === 0)
28
+ return { ok: true, value: { source: "default" } };
15
29
  const parsed = parseConsistentDefinitions(backend, definitions);
30
+ if (!parsed.ok)
31
+ return parsed;
32
+ const firstEnvironment = parsed.value[0]?.environment;
33
+ const installedConfigPath = firstEnvironment === undefined
34
+ ? undefined
35
+ : ownEnvironmentValue(firstEnvironment, "PI_WEB_CONFIG");
36
+ if (installedConfigPath === undefined || installedConfigPath === "") {
37
+ return { ok: true, value: { source: "default" } };
38
+ }
39
+ if (!posixPath.isAbsolute(installedConfigPath)) {
40
+ return {
41
+ ok: false,
42
+ message: `Installed service definitions declare relative PI_WEB_CONFIG ${JSON.stringify(installedConfigPath)}; managed config paths must be absolute.`,
43
+ };
44
+ }
45
+ return { ok: true, value: { source: "installed", configPath: installedConfigPath } };
46
+ }
47
+ export function inspectInstalledNativeServiceDefinitionEnvironment(backend, definition) {
48
+ const parsed = backend.kind === "systemd"
49
+ ? parseSystemdDefinition(definition)
50
+ : parseLaunchdDefinition(definition);
51
+ return parsed.ok ? { ok: true, value: parsed.value.environment } : parsed;
52
+ }
53
+ export function inspectInstalledProductionServiceContext(backend, definitions, t = createTranslator("en", {})) {
54
+ const parsed = parseConsistentDefinitions(backend, definitions, t);
16
55
  if (!parsed.ok)
17
56
  return parsed;
18
57
  const withWorkingDirectory = parsed.value.find((definition) => definition.workingDirectory !== null);
19
58
  if (withWorkingDirectory !== undefined) {
20
59
  return {
21
60
  ok: false,
22
- message: `Installed production service ${withWorkingDirectory.id} unexpectedly has working directory ${withWorkingDirectory.workingDirectory ?? ""}.`,
61
+ message: t("doctor.productionCwd", "Installed production service {arg0} unexpectedly has working directory {arg1}.", { arg0: withWorkingDirectory.id, arg1: withWorkingDirectory.workingDirectory ?? "" }),
23
62
  };
24
63
  }
25
64
  return {
@@ -30,13 +69,13 @@ export function inspectInstalledProductionServiceContext(backend, definitions) {
30
69
  },
31
70
  };
32
71
  }
33
- export function inspectInstalledDevelopmentServiceInput(backend, definitions) {
34
- const parsed = parseConsistentDefinitions(backend, definitions);
72
+ export function inspectInstalledDevelopmentServiceInput(backend, definitions, t = createTranslator("en", {})) {
73
+ const parsed = parseConsistentDefinitions(backend, definitions, t);
35
74
  if (!parsed.ok)
36
75
  return parsed;
37
76
  const first = parsed.value[0] ?? impossibleMissingDefinition();
38
77
  if (first.workingDirectory === null) {
39
- return { ok: false, message: "Installed development services do not declare a working directory." };
78
+ return { ok: false, message: t("doctor.cwdMissing", "Installed development services do not declare a working directory.") };
40
79
  }
41
80
  const input = {
42
81
  backend,
@@ -51,7 +90,7 @@ export function inspectInstalledDevelopmentServiceInput(backend, definitions) {
51
90
  if (expected?.shellCommand !== definition.shellCommand) {
52
91
  return {
53
92
  ok: false,
54
- message: `Installed ${definition.id} service command does not match the canonical development plan.`,
93
+ message: t("doctor.developmentMismatch", "Installed {arg0} service command does not match the canonical development plan.", { arg0: definition.id }),
55
94
  };
56
95
  }
57
96
  }
@@ -77,14 +116,14 @@ export async function runNativeServiceDoctor(target, dependencies) {
77
116
  const validation = await validateNativeServicePlan(plan, dependencies.probe);
78
117
  return { kind: "plan-validation", scope, plan, validation };
79
118
  }
80
- export function formatNativeServiceDoctorResult(result) {
119
+ export function formatNativeServiceDoctorResult(result, t = createTranslator("en", {})) {
81
120
  if (result.kind === "inspection-failure") {
82
121
  return {
83
122
  ok: false,
84
123
  failureKind: "inspection",
85
124
  lines: [
86
- `✗ Installed native-service plan could not be inspected: ${result.message}`,
87
- " Run `pi-web install` or `pi-web install --dev` to replace mixed, partial, or outdated service definitions.",
125
+ t("doctor.inspectionFailed", "✗ Installed native-service plan could not be inspected: {arg0}", { arg0: result.message }),
126
+ t("doctor.replaceDefinitions", " Run `pi-web install` or `pi-web install --dev` to replace mixed, partial, or outdated service definitions."),
88
127
  ],
89
128
  plan: null,
90
129
  adviceShell: null,
@@ -92,26 +131,26 @@ export function formatNativeServiceDoctorResult(result) {
92
131
  failedPrerequisites: [],
93
132
  };
94
133
  }
95
- const lines = [scopeHeading(result.scope)];
134
+ const lines = [scopeHeading(result.scope, t)];
96
135
  if (result.kind === "plan-resolution-failure") {
97
136
  let infrastructure = false;
98
137
  for (const failure of result.failures) {
99
138
  if (failure.kind === "probe-infrastructure") {
100
139
  infrastructure = true;
101
- lines.push(`✗ Native service probe infrastructure failure (${failure.reason}): ${failure.message}`);
140
+ lines.push(t("doctor.infrastructureFailure", "✗ Native service probe infrastructure failure ({arg0}): {arg1}", { arg0: failure.reason, arg1: failure.message }));
102
141
  }
103
142
  else if (failure.kind === "entrypoint-inspection-failure") {
104
143
  infrastructure = true;
105
- lines.push(`✗ Could not inspect bundled ${failure.serviceId} entrypoint ${failure.entrypointPath}: ${failure.message}`);
144
+ lines.push(t("doctor.entrypointFailed", "✗ Could not inspect bundled {arg0} entrypoint {arg1}: {arg2}", { arg0: failure.serviceId, arg1: failure.entrypointPath, arg2: failure.message }));
106
145
  }
107
146
  else {
108
- lines.push(`✗ ${failure.namedCommand} is unavailable to the native service manager, and bundled entrypoint ${failure.bundledEntrypointPath} is missing.`);
147
+ lines.push(t("doctor.commandMissing", "✗ {arg0} is unavailable to the native service manager, and bundled entrypoint {arg1} is missing.", { arg0: failure.namedCommand, arg1: failure.bundledEntrypointPath }));
109
148
  if (failure.namedCommandFailure !== null)
110
149
  lines.push(` ${failure.namedCommandFailure}`);
111
150
  }
112
151
  }
113
152
  if (infrastructure)
114
- lines.push(" This infrastructure failure is not proof of a PATH mismatch.");
153
+ lines.push(t("doctor.notPathProof", " This infrastructure failure is not proof of a PATH mismatch."));
115
154
  return {
116
155
  ok: false,
117
156
  failureKind: infrastructure ? "infrastructure" : "requirements",
@@ -125,10 +164,10 @@ export function formatNativeServiceDoctorResult(result) {
125
164
  }
126
165
  const configuredOverrides = result.plan.services.filter((service) => service.strategy.kind === "configured-override");
127
166
  for (const service of configuredOverrides) {
128
- lines.push(`! ${service.description} uses a configured command override; doctor does not execute arbitrary configured commands.`);
167
+ lines.push(t("doctor.overrideSkipped", "! {arg0} uses a configured command override; doctor does not execute arbitrary configured commands.", { arg0: service.description }));
129
168
  }
130
169
  if (result.validation.ok) {
131
- lines.push("✓ All verifiable native-service plan requirements are satisfied in the service-manager context.");
170
+ lines.push(t("doctor.requirementsSatisfied", "✓ All verifiable native-service plan requirements are satisfied in the service-manager context."));
132
171
  return {
133
172
  ok: true,
134
173
  failureKind: "none",
@@ -144,17 +183,17 @@ export function formatNativeServiceDoctorResult(result) {
144
183
  for (const failure of result.validation.failures) {
145
184
  if (failure.kind === "probe-infrastructure") {
146
185
  infrastructure = true;
147
- lines.push(`✗ Native service probe infrastructure failure (${failure.reason}): ${failure.message}`);
186
+ lines.push(t("doctor.infrastructureFailure", "✗ Native service probe infrastructure failure ({arg0}): {arg1}", { arg0: failure.reason, arg1: failure.message }));
148
187
  }
149
188
  else {
150
189
  failedPrerequisites.push(failure.prerequisite);
151
- lines.push(`✗ Native service requirement failed: ${failure.prerequisite.description}`);
190
+ lines.push(t("doctor.requirementFailed", "✗ Native service requirement failed: {arg0}", { arg0: failure.prerequisite.description }));
152
191
  if (failure.detail !== null && failure.detail !== failure.prerequisite.description)
153
192
  lines.push(` ${failure.detail}`);
154
193
  }
155
194
  }
156
195
  if (infrastructure)
157
- lines.push(" This infrastructure failure is not proof of a PATH mismatch.");
196
+ lines.push(t("doctor.notPathProof", " This infrastructure failure is not proof of a PATH mismatch."));
158
197
  return {
159
198
  ok: false,
160
199
  failureKind: infrastructure ? "infrastructure" : "requirements",
@@ -166,19 +205,19 @@ export function formatNativeServiceDoctorResult(result) {
166
205
  failedPrerequisites,
167
206
  };
168
207
  }
169
- function scopeHeading(scope) {
208
+ function scopeHeading(scope, t = createTranslator("en", {})) {
170
209
  if (scope.kind === "installed-development")
171
- return "Installed development native-service plan:";
172
- return `Prospective production native-service plan (${scope.reason ?? "installed strategy is unknown"}):`;
210
+ return t("doctor.installedDev", "Installed development native-service plan:");
211
+ return t("doctor.prospectiveProduction", "Prospective production native-service plan ({arg0}):", { arg0: scope.reason ?? t("doctor.strategyUnknown", "installed strategy is unknown") });
173
212
  }
174
- function parseConsistentDefinitions(backend, definitions) {
213
+ function parseConsistentDefinitions(backend, definitions, t = createTranslator("en", {})) {
175
214
  if (definitions.length === 0)
176
- return { ok: false, message: "No installed service definitions were provided." };
215
+ return { ok: false, message: t("doctor.definitionsMissing", "No installed service definitions were provided.") };
177
216
  const parsed = [];
178
217
  for (const definition of definitions) {
179
218
  const result = backend.kind === "systemd"
180
- ? parseSystemdDefinition(definition)
181
- : parseLaunchdDefinition(definition);
219
+ ? parseSystemdDefinition(definition, t)
220
+ : parseLaunchdDefinition(definition, t);
182
221
  if (!result.ok)
183
222
  return result;
184
223
  parsed.push(result.value);
@@ -186,13 +225,13 @@ function parseConsistentDefinitions(backend, definitions) {
186
225
  const first = parsed[0] ?? impossibleMissingDefinition();
187
226
  for (const definition of parsed.slice(1)) {
188
227
  if (definition.shell.executable !== first.shell.executable) {
189
- return { ok: false, message: "Installed service definitions use different login shells." };
228
+ return { ok: false, message: t("doctor.shellMismatch", "Installed service definitions use different login shells.") };
190
229
  }
191
230
  if (!recordsEqual(definition.environment, first.environment)) {
192
- return { ok: false, message: "Installed service definitions use different environments." };
231
+ return { ok: false, message: t("doctor.environmentMismatch", "Installed service definitions use different environments.") };
193
232
  }
194
233
  if (definition.workingDirectory !== first.workingDirectory) {
195
- return { ok: false, message: "Installed service definitions use different working directories." };
234
+ return { ok: false, message: t("doctor.cwdMismatch", "Installed service definitions use different working directories.") };
196
235
  }
197
236
  }
198
237
  return { ok: true, value: parsed };
@@ -203,7 +242,9 @@ function systemdServiceDirectives(contents) {
203
242
  let inServiceSection = false;
204
243
  let foundServiceSection = false;
205
244
  for (const line of contents.split(/\r?\n/u)) {
206
- const trimmed = line.trim();
245
+ // systemd syntax trims ASCII spaces and tabs here; JavaScript's trim/\s
246
+ // would also consume Unicode whitespace that systemd treats as key text.
247
+ const trimmed = line.replace(/^[ \t]+|[ \t]+$/gu, "");
207
248
  if (/^\[[^\]]+\]$/u.test(trimmed)) {
208
249
  inServiceSection = trimmed === "[Service]";
209
250
  foundServiceSection ||= inServiceSection;
@@ -211,105 +252,180 @@ function systemdServiceDirectives(contents) {
211
252
  }
212
253
  if (!inServiceSection || trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith(";"))
213
254
  continue;
214
- const match = /^\s*([A-Za-z][A-Za-z0-9]*)=(.*)$/u.exec(line);
255
+ // Slice after the recognized prefix so JavaScript-only line separators remain directive data.
256
+ const match = /^[ \t]*([A-Za-z][A-Za-z0-9]*)=/u.exec(line);
215
257
  const name = match?.[1];
216
- const value = match?.[2];
217
- if (name === undefined || value === undefined || !allowed.has(name))
258
+ if (match === null || name === undefined || !allowed.has(name))
218
259
  return undefined;
219
- directives.push({ name, value });
260
+ directives.push({ name, value: line.slice(match[0].length) });
220
261
  }
221
262
  return foundServiceSection ? directives : undefined;
222
263
  }
223
- function parseSystemdDefinition(definition) {
264
+ function hasSystemdPhysicalLineContinuation(line) {
265
+ let trailingBackslashes = 0;
266
+ for (let index = line.length - 1; index >= 0 && line[index] === "\\"; index -= 1)
267
+ trailingBackslashes += 1;
268
+ return trailingBackslashes % 2 === 1;
269
+ }
270
+ /** Parse PI WEB's bounded ExecStart argument structure, including persisted legacy encodings. */
271
+ function parseSystemdExecStart(value) {
272
+ const environmentPrefix = "/usr/bin/env ";
273
+ const shellOffset = value.startsWith(environmentPrefix) ? environmentPrefix.length : 0;
274
+ const shell = parseSystemdExecStartArgument(value, shellOffset);
275
+ if (shell === undefined)
276
+ return undefined;
277
+ const loginShellSeparator = " -lc ";
278
+ if (!value.startsWith(loginShellSeparator, shell.endOffset))
279
+ return undefined;
280
+ const shellCommandOffset = shell.endOffset + loginShellSeparator.length;
281
+ if (shellCommandOffset >= value.length)
282
+ return undefined;
283
+ return {
284
+ shellArgument: shell.value,
285
+ shellCommandArgument: value.slice(shellCommandOffset),
286
+ };
287
+ }
288
+ /** Scan one systemd argument linearly so delimiters inside quoted values remain data. */
289
+ function parseSystemdExecStartArgument(value, offset) {
290
+ if (offset >= value.length || value[offset] === " " || value[offset] === "\t")
291
+ return undefined;
292
+ let quote = null;
293
+ for (let index = offset; index < value.length; index += 1) {
294
+ const character = value[index];
295
+ if (character === "\\") {
296
+ index += 1;
297
+ if (index >= value.length)
298
+ return undefined;
299
+ continue;
300
+ }
301
+ if (quote === null && (character === " " || character === "\t")) {
302
+ return { value: value.slice(offset, index), endOffset: index };
303
+ }
304
+ if (character !== "\"" && character !== "'")
305
+ continue;
306
+ if (quote === null)
307
+ quote = character;
308
+ else if (character === quote)
309
+ quote = null;
310
+ }
311
+ return quote === null ? { value: value.slice(offset), endOffset: value.length } : undefined;
312
+ }
313
+ // Keep malformed installed input linear; overlapping escaped/raw regex alternatives can backtrack exponentially.
314
+ function isSingleSystemdQuotedValue(value) {
315
+ if (value.length < 2 || !value.startsWith('"') || !value.endsWith('"'))
316
+ return false;
317
+ const closingQuoteIndex = value.length - 1;
318
+ for (let index = 1; index < closingQuoteIndex; index += 1) {
319
+ const character = value[index];
320
+ if (character === '"')
321
+ return false;
322
+ if (character !== "\\")
323
+ continue;
324
+ index += 1;
325
+ if (index >= closingQuoteIndex)
326
+ return false;
327
+ }
328
+ return true;
329
+ }
330
+ function parseSystemdDefinition(definition, t = createTranslator("en", {})) {
331
+ if (definition.contents.split(/\r?\n/u).some(hasSystemdPhysicalLineContinuation)) {
332
+ return {
333
+ ok: false,
334
+ message: `Installed ${definition.id} systemd unit uses physical-line continuation, which cannot be inspected safely.`,
335
+ };
336
+ }
224
337
  const directives = systemdServiceDirectives(definition.contents);
225
338
  if (directives === undefined) {
226
- return { ok: false, message: `Installed ${definition.id} systemd unit has unrecognized service directives.` };
339
+ return { ok: false, message: t("doctor.systemd.unrecognized_service_directives", "Installed {arg0} systemd unit has unrecognized service directives.", { arg0: definition.id }) };
227
340
  }
228
341
  const execStarts = directives.filter((directive) => directive.name === "ExecStart");
229
342
  const execStart = execStarts.length === 1
230
- ? /^(?:\/usr\/bin\/env )?(.+?) -lc (.+)$/u.exec(execStarts[0]?.value ?? "")
231
- : null;
232
- if (execStart?.[1] === undefined || execStart[2] === undefined) {
343
+ ? parseSystemdExecStart(execStarts[0]?.value ?? "")
344
+ : undefined;
345
+ if (execStart === undefined) {
233
346
  return { ok: false, message: `Installed ${definition.id} systemd unit must have exactly one recognized ExecStart.` };
234
347
  }
235
- const shellExecutable = parseSystemdExecArgument(execStart[1]);
348
+ const shellExecutable = parseSystemdExecArgument(execStart.shellArgument);
236
349
  if (shellExecutable === undefined) {
237
- return { ok: false, message: `Installed ${definition.id} systemd unit has an unrecognized login shell argument.` };
350
+ return { ok: false, message: t("doctor.systemd.an_unrecognized_login_shell_argument", "Installed {arg0} systemd unit has an unrecognized login shell argument.", { arg0: definition.id }) };
238
351
  }
239
- const shell = installedShell(shellExecutable);
352
+ const shell = installedShell(shellExecutable, t);
240
353
  if (!shell.ok)
241
354
  return shell;
242
- const shellCommand = parseSystemdShellCommand(shell.value.name, execStart[2]);
355
+ const shellCommand = parseSystemdShellCommand(shell.value.name, execStart.shellCommandArgument);
243
356
  if (shellCommand === undefined) {
244
- return { ok: false, message: `Installed ${definition.id} systemd unit has an unrecognized shell command.` };
357
+ return { ok: false, message: t("doctor.systemd.an_unrecognized_shell_command", "Installed {arg0} systemd unit has an unrecognized shell command.", { arg0: definition.id }) };
245
358
  }
246
- const environment = {};
359
+ const environment = new Map();
247
360
  for (const directive of directives.filter((item) => item.name === "Environment")) {
248
361
  const rawValue = directive.value;
249
- if (!/^"(?:\\.|[^"])*"$/u.test(rawValue)) {
362
+ if (!isSingleSystemdQuotedValue(rawValue)) {
250
363
  return { ok: false, message: `Installed ${definition.id} systemd unit has an unrecognized environment entry.` };
251
364
  }
252
365
  const assignment = parseSystemdDirectiveValue(rawValue);
253
366
  const separator = assignment?.indexOf("=") ?? -1;
254
367
  const key = assignment?.slice(0, separator) ?? "";
255
- if (separator <= 0 || Object.hasOwn(environment, key)) {
368
+ if (separator <= 0 || !/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key) || environment.has(key)) {
256
369
  return { ok: false, message: `Installed ${definition.id} systemd unit has a malformed environment entry.` };
257
370
  }
258
- environment[key] = assignment?.slice(separator + 1) ?? "";
371
+ environment.set(key, assignment?.slice(separator + 1) ?? "");
259
372
  }
260
373
  const workingDirectories = directives.filter((directive) => directive.name === "WorkingDirectory");
261
374
  if (workingDirectories.length > 1) {
262
- return { ok: false, message: `Installed ${definition.id} systemd unit has duplicate working directories.` };
375
+ return { ok: false, message: t("doctor.systemd.duplicate_working_directories", "Installed {arg0} systemd unit has duplicate working directories.", { arg0: definition.id }) };
263
376
  }
264
377
  const rawWorkingDirectory = workingDirectories[0]?.value;
265
378
  if (rawWorkingDirectory?.startsWith('"') === true || rawWorkingDirectory?.startsWith("'") === true) {
266
- return { ok: false, message: `Installed ${definition.id} systemd unit has an invalid quoted working directory.` };
379
+ return { ok: false, message: t("doctor.systemd.an_invalid_quoted_working_directory", "Installed {arg0} systemd unit has an invalid quoted working directory.", { arg0: definition.id }) };
267
380
  }
268
381
  const workingDirectory = rawWorkingDirectory === undefined
269
382
  ? null
270
383
  : parseSystemdDirectiveValue(rawWorkingDirectory);
271
384
  if (workingDirectories.length === 1 && workingDirectory === undefined) {
272
- return { ok: false, message: `Installed ${definition.id} systemd unit has a malformed working directory.` };
385
+ return { ok: false, message: t("doctor.systemd.a_malformed_working_directory", "Installed {arg0} systemd unit has a malformed working directory.", { arg0: definition.id }) };
273
386
  }
274
387
  return {
275
388
  ok: true,
276
- value: { id: definition.id, shell: shell.value, environment, workingDirectory: workingDirectory ?? null, shellCommand },
389
+ value: {
390
+ id: definition.id,
391
+ shell: shell.value,
392
+ environment: Object.fromEntries(environment),
393
+ workingDirectory: workingDirectory ?? null,
394
+ shellCommand,
395
+ },
277
396
  };
278
397
  }
279
- function parseLaunchdDefinition(definition) {
280
- const argumentsMatches = [...definition.contents.matchAll(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/gu)];
281
- const arguments_ = argumentsMatches.length === 1
282
- ? parseXmlStringSequence(argumentsMatches[0]?.[1] ?? "")
283
- : undefined;
398
+ function parseLaunchdDefinition(definition, t = createTranslator("en", {})) {
399
+ const plist = parseLaunchdPlistDocument(definition.contents);
400
+ if (plist === undefined) {
401
+ return { ok: false, message: `Installed ${definition.id} LaunchAgent is not a structurally valid property list.` };
402
+ }
403
+ const label = plistStringValue(plist.get("Label"));
404
+ if (label === undefined) {
405
+ return { ok: false, message: `Installed ${definition.id} LaunchAgent has an unrecognized Label.` };
406
+ }
407
+ const expectedLabel = nativeServiceManagerRefs[definition.id].launchdLabel;
408
+ if (label !== expectedLabel) {
409
+ return {
410
+ ok: false,
411
+ message: `Installed ${definition.id} LaunchAgent declares Label ${JSON.stringify(label)} instead of ${JSON.stringify(expectedLabel)}.`,
412
+ };
413
+ }
414
+ const arguments_ = plistStringArray(plist.get("ProgramArguments"));
284
415
  if (arguments_?.length !== 4 || arguments_[0] !== "/usr/bin/env" || arguments_[2] !== "-lc") {
285
- return { ok: false, message: `Installed ${definition.id} LaunchAgent has unrecognized ProgramArguments.` };
416
+ return { ok: false, message: t("doctor.launchd.unrecognized_ProgramArguments", "Installed {arg0} LaunchAgent has unrecognized ProgramArguments.", { arg0: definition.id }) };
286
417
  }
287
- const shell = installedShell(arguments_[1] ?? "");
418
+ const shell = installedShell(arguments_[1] ?? "", t);
288
419
  if (!shell.ok)
289
420
  return shell;
290
- const environmentMatches = [...definition.contents.matchAll(/<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/gu)];
291
- const environmentKeyCount = [...definition.contents.matchAll(/<key>EnvironmentVariables<\/key>/gu)].length;
292
- if (environmentMatches.length > 1 || environmentKeyCount !== environmentMatches.length) {
293
- return { ok: false, message: `Installed ${definition.id} LaunchAgent has a malformed environment dictionary.` };
294
- }
295
- const environment = environmentMatches.length === 0
296
- ? {}
297
- : parseXmlStringDictionary(environmentMatches[0]?.[1] ?? "");
421
+ const rawEnvironment = plist.get("EnvironmentVariables");
422
+ const environment = rawEnvironment === undefined ? {} : plistStringDictionary(rawEnvironment);
298
423
  if (environment === undefined) {
299
- return { ok: false, message: `Installed ${definition.id} LaunchAgent has a malformed environment dictionary.` };
300
- }
301
- const contentsWithoutEnvironment = environmentMatches[0]?.[0] === undefined
302
- ? definition.contents
303
- : definition.contents.replace(environmentMatches[0][0], "");
304
- const workingDirectoryMatches = [...contentsWithoutEnvironment.matchAll(/<key>WorkingDirectory<\/key>\s*<string>([\s\S]*?)<\/string>/gu)];
305
- const workingDirectoryKeyCount = [...contentsWithoutEnvironment.matchAll(/<key>WorkingDirectory<\/key>/gu)].length;
306
- if (workingDirectoryMatches.length > 1 || workingDirectoryKeyCount !== workingDirectoryMatches.length) {
307
- return { ok: false, message: `Installed ${definition.id} LaunchAgent has a malformed working directory.` };
424
+ return { ok: false, message: t("doctor.launchd.a_malformed_environment_dictionary", "Installed {arg0} LaunchAgent has a malformed environment dictionary.", { arg0: definition.id }) };
308
425
  }
309
- const workingDirectory = workingDirectoryMatches[0]?.[1] === undefined
310
- ? null
311
- : xmlUnescapeStrict(workingDirectoryMatches[0][1]);
312
- if (workingDirectoryMatches.length === 1 && workingDirectory === undefined) {
426
+ const rawWorkingDirectory = plist.get("WorkingDirectory");
427
+ const workingDirectory = rawWorkingDirectory === undefined ? null : plistStringValue(rawWorkingDirectory);
428
+ if (rawWorkingDirectory !== undefined && workingDirectory === undefined) {
313
429
  return { ok: false, message: `Installed ${definition.id} LaunchAgent has a malformed working directory.` };
314
430
  }
315
431
  return {
@@ -323,10 +439,10 @@ function parseLaunchdDefinition(definition) {
323
439
  },
324
440
  };
325
441
  }
326
- function installedShell(executable) {
442
+ function installedShell(executable, t = createTranslator("en", {})) {
327
443
  const name = posixPath.basename(executable).replace(/^-/, "");
328
444
  if (name !== "bash" && name !== "zsh" && name !== "fish") {
329
- return { ok: false, message: `Installed service definition uses unsupported login shell ${executable}.` };
445
+ return { ok: false, message: t("doctor.shellUnsupported", "Installed service definition uses unsupported login shell {arg0}.", { arg0: executable }) };
330
446
  }
331
447
  return {
332
448
  ok: true,
@@ -334,7 +450,10 @@ function installedShell(executable) {
334
450
  };
335
451
  }
336
452
  function parseSystemdExecArgument(value) {
337
- const decoded = parseSystemdEscapedValue(value);
453
+ const quoted = value.startsWith('"') || value.endsWith('"');
454
+ if (quoted ? !isSingleSystemdQuotedValue(value) : /["']/u.test(value))
455
+ return undefined;
456
+ const decoded = decodeSystemdEscapes(quoted ? value.slice(1, -1) : value);
338
457
  return decoded === undefined ? undefined : decodeSystemdSubstitutions(decoded, true);
339
458
  }
340
459
  function parseSystemdDirectiveValue(value) {
@@ -345,14 +464,20 @@ function parseSystemdEscapedValue(value) {
345
464
  const quoted = value.startsWith('"') || value.endsWith('"');
346
465
  if (quoted && (!value.startsWith('"') || !value.endsWith('"')))
347
466
  return undefined;
348
- return systemdUnescape(quoted ? value.slice(1, -1) : value);
467
+ return decodeSystemdEscapes(quoted ? value.slice(1, -1) : value);
349
468
  }
350
- function systemdUnescape(value) {
351
- let result = "";
469
+ export function decodeSystemdEscapes(value) {
470
+ const bytes = [];
471
+ const encoder = new TextEncoder();
352
472
  for (let index = 0; index < value.length; index += 1) {
353
473
  const character = value[index];
354
474
  if (character !== "\\") {
355
- result += character ?? "";
475
+ const codePoint = value.codePointAt(index);
476
+ if (codePoint === undefined || codePoint === 0 || (codePoint >= 0xd800 && codePoint <= 0xdfff))
477
+ return undefined;
478
+ bytes.push(...encoder.encode(String.fromCodePoint(codePoint)));
479
+ if (codePoint > 0xffff)
480
+ index += 1;
356
481
  continue;
357
482
  }
358
483
  const escape = value[index + 1];
@@ -374,23 +499,45 @@ function systemdUnescape(value) {
374
499
  };
375
500
  const simple = simpleEscapes[escape];
376
501
  if (simple !== undefined) {
377
- result += simple;
502
+ bytes.push(...encoder.encode(simple));
378
503
  index += 1;
379
504
  continue;
380
505
  }
506
+ if (/^[0-7]$/u.test(escape)) {
507
+ const encoded = value.slice(index + 1, index + 4);
508
+ if (!/^[0-7]{3}$/u.test(encoded))
509
+ return undefined;
510
+ const decoded = Number.parseInt(encoded, 8);
511
+ if (decoded === 0 || decoded > 0xff)
512
+ return undefined;
513
+ bytes.push(decoded);
514
+ index += 3;
515
+ continue;
516
+ }
381
517
  const length = escape === "x" ? 2 : escape === "u" ? 4 : escape === "U" ? 8 : 0;
382
518
  if (length === 0)
383
519
  return undefined;
384
520
  const encoded = value.slice(index + 2, index + 2 + length);
385
521
  if (encoded.length !== length || !new RegExp(`^[0-9a-fA-F]{${String(length)}}$`, "u").test(encoded))
386
522
  return undefined;
387
- const codePoint = Number.parseInt(encoded, 16);
388
- if (codePoint === 0 || codePoint > 0x10ffff)
523
+ const decoded = Number.parseInt(encoded, 16);
524
+ if (decoded === 0 || decoded > 0x10ffff || (decoded >= 0xd800 && decoded <= 0xdfff))
389
525
  return undefined;
390
- result += String.fromCodePoint(codePoint);
526
+ if (escape === "x")
527
+ bytes.push(decoded);
528
+ else
529
+ bytes.push(...encoder.encode(String.fromCodePoint(decoded)));
391
530
  index += length + 1;
392
531
  }
393
- return result;
532
+ try {
533
+ // Keep a BOM produced by an escape sequence as U+FEFF. The default
534
+ // decoder behavior strips leading UTF-8 BOM bytes, which could turn an
535
+ // invalid BOM-prefixed environment key into PI_WEB_CONFIG.
536
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(Uint8Array.from(bytes));
537
+ }
538
+ catch {
539
+ return undefined;
540
+ }
394
541
  }
395
542
  function decodeSystemdSubstitutions(value, decodeDollars) {
396
543
  let result = "";
@@ -414,7 +561,17 @@ function parseSystemdShellCommand(shell, value) {
414
561
  return undefined;
415
562
  const inner = value.slice(1, -1);
416
563
  const unquoted = shell === "fish" ? fishSingleQuoteUnescape(inner) : inner.replaceAll("'\\''", "'");
417
- return decodeSystemdSubstitutions(unquoted, true);
564
+ if (legacySystemdShellQuote(shell, unquoted) !== value)
565
+ return undefined;
566
+ // The pre-hardening renderer doubled percent specifiers, but its
567
+ // replacement-string "$$" left dollar characters unchanged.
568
+ return decodeSystemdSubstitutions(unquoted, false);
569
+ }
570
+ function legacySystemdShellQuote(shell, value) {
571
+ const escaped = shell === "fish"
572
+ ? value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")
573
+ : value.replaceAll("'", "'\\''");
574
+ return `'${escaped}'`;
418
575
  }
419
576
  function fishSingleQuoteUnescape(value) {
420
577
  let result = "";
@@ -430,34 +587,181 @@ function fishSingleQuoteUnescape(value) {
430
587
  }
431
588
  return result;
432
589
  }
433
- function parseXmlStringSequence(contents) {
590
+ const plistXmlDeclaration = '<?xml version="1.0" encoding="UTF-8"?>';
591
+ const plistDoctype = '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">';
592
+ const maximumPlistDepth = 32;
593
+ /** Parse the complete XML plist subset emitted by PI WEB, rejecting fragments and duplicate dictionary keys. */
594
+ function parseLaunchdPlistDocument(contents) {
595
+ const normalizedContents = normalizePlistXml(contents);
596
+ if (normalizedContents === undefined)
597
+ return undefined;
598
+ const cursor = {
599
+ contents: normalizedContents,
600
+ offset: normalizedContents.startsWith("\uFEFF") ? 1 : 0,
601
+ };
602
+ skipPlistWhitespace(cursor);
603
+ if (consumePlistToken(cursor, plistXmlDeclaration))
604
+ skipPlistWhitespace(cursor);
605
+ else if (cursor.contents.startsWith("<?xml", cursor.offset))
606
+ return undefined;
607
+ if (consumePlistToken(cursor, plistDoctype))
608
+ skipPlistWhitespace(cursor);
609
+ else if (cursor.contents.startsWith("<!DOCTYPE", cursor.offset))
610
+ return undefined;
611
+ if (!consumePlistToken(cursor, '<plist version="1.0">'))
612
+ return undefined;
613
+ const root = parsePlistValue(cursor, 0);
614
+ if (root?.kind !== "dictionary")
615
+ return undefined;
616
+ skipPlistWhitespace(cursor);
617
+ if (!consumePlistToken(cursor, "</plist>"))
618
+ return undefined;
619
+ skipPlistWhitespace(cursor);
620
+ return cursor.offset === cursor.contents.length ? root.value : undefined;
621
+ }
622
+ function parsePlistValue(cursor, depth) {
623
+ if (depth > maximumPlistDepth)
624
+ return undefined;
625
+ skipPlistWhitespace(cursor);
626
+ if (cursor.contents.startsWith("<string>", cursor.offset)) {
627
+ const value = parsePlistTextElement(cursor, "string");
628
+ return value === undefined ? undefined : { kind: "string", value };
629
+ }
630
+ for (const tag of ["data", "date", "integer", "real"]) {
631
+ if (!cursor.contents.startsWith(`<${tag}>`, cursor.offset))
632
+ continue;
633
+ const value = parsePlistTextElement(cursor, tag);
634
+ return value === undefined || !isValidPlistScalarValue(tag, value)
635
+ ? undefined
636
+ : { kind: "scalar", value };
637
+ }
638
+ if (cursor.contents.startsWith("<array", cursor.offset))
639
+ return parsePlistArray(cursor, depth);
640
+ if (cursor.contents.startsWith("<dict", cursor.offset))
641
+ return parsePlistDictionary(cursor, depth);
642
+ if (consumePlistToken(cursor, "<true/>"))
643
+ return { kind: "boolean", value: true };
644
+ if (consumePlistToken(cursor, "<false/>"))
645
+ return { kind: "boolean", value: false };
646
+ return undefined;
647
+ }
648
+ function parsePlistArray(cursor, depth) {
649
+ if (consumePlistToken(cursor, "<array/>"))
650
+ return { kind: "array", value: [] };
651
+ if (!consumePlistToken(cursor, "<array>"))
652
+ return undefined;
434
653
  const values = [];
435
- let cursor = 0;
436
- for (const match of contents.matchAll(/<string>([\s\S]*?)<\/string>/gu)) {
437
- if (contents.slice(cursor, match.index).trim() !== "")
438
- return undefined;
439
- const value = xmlUnescapeStrict(match[1] ?? "");
654
+ for (;;) {
655
+ skipPlistWhitespace(cursor);
656
+ if (consumePlistToken(cursor, "</array>"))
657
+ return { kind: "array", value: values };
658
+ const value = parsePlistValue(cursor, depth + 1);
440
659
  if (value === undefined)
441
660
  return undefined;
442
661
  values.push(value);
443
- cursor = match.index + match[0].length;
444
662
  }
445
- return contents.slice(cursor).trim() === "" ? values : undefined;
446
663
  }
447
- function parseXmlStringDictionary(contents) {
448
- const values = {};
449
- let cursor = 0;
450
- for (const match of contents.matchAll(/<key>([\s\S]*?)<\/key>\s*<string>([\s\S]*?)<\/string>/gu)) {
451
- if (contents.slice(cursor, match.index).trim() !== "")
664
+ function parsePlistDictionary(cursor, depth) {
665
+ if (consumePlistToken(cursor, "<dict/>"))
666
+ return { kind: "dictionary", value: new Map() };
667
+ if (!consumePlistToken(cursor, "<dict>"))
668
+ return undefined;
669
+ const values = new Map();
670
+ for (;;) {
671
+ skipPlistWhitespace(cursor);
672
+ if (consumePlistToken(cursor, "</dict>"))
673
+ return { kind: "dictionary", value: values };
674
+ const key = parsePlistTextElement(cursor, "key");
675
+ if (key === undefined || values.has(key))
676
+ return undefined;
677
+ const value = parsePlistValue(cursor, depth + 1);
678
+ if (value === undefined)
679
+ return undefined;
680
+ values.set(key, value);
681
+ }
682
+ }
683
+ function parsePlistTextElement(cursor, tag) {
684
+ const openingTag = `<${tag}>`;
685
+ const closingTag = `</${tag}>`;
686
+ if (!consumePlistToken(cursor, openingTag))
687
+ return undefined;
688
+ const closingOffset = cursor.contents.indexOf(closingTag, cursor.offset);
689
+ if (closingOffset < 0)
690
+ return undefined;
691
+ const encoded = cursor.contents.slice(cursor.offset, closingOffset);
692
+ cursor.offset = closingOffset + closingTag.length;
693
+ return xmlUnescapeStrict(encoded);
694
+ }
695
+ function normalizePlistXml(contents) {
696
+ for (const character of contents) {
697
+ const codePoint = character.codePointAt(0) ?? 0;
698
+ const isXmlCharacter = codePoint === 0x09
699
+ || codePoint === 0x0a
700
+ || codePoint === 0x0d
701
+ || (codePoint >= 0x20 && codePoint <= 0xd7ff)
702
+ || (codePoint >= 0xe000 && codePoint <= 0xfffd)
703
+ || (codePoint >= 0x10000 && codePoint <= 0x10ffff);
704
+ if (!isXmlCharacter)
705
+ return undefined;
706
+ }
707
+ return contents.replace(/\r\n?/gu, "\n");
708
+ }
709
+ function isValidPlistScalarValue(tag, value) {
710
+ if (tag === "data") {
711
+ const compact = value.replace(/[\t\n\r ]/gu, "");
712
+ return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(compact);
713
+ }
714
+ if (tag === "date") {
715
+ const match = /^(\d{4})-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/u.exec(value);
716
+ if (match?.[1] === undefined || Number(match[1]) === 0)
717
+ return false;
718
+ const timestamp = Date.parse(value);
719
+ return Number.isFinite(timestamp)
720
+ && new Date(timestamp).toISOString() === `${value.slice(0, -1)}.000Z`;
721
+ }
722
+ if (tag === "integer") {
723
+ if (!/^[+-]?(?:0|[1-9][0-9]*)$/u.test(value))
724
+ return false;
725
+ const parsed = BigInt(value);
726
+ return parsed >= -(2n ** 63n) && parsed <= (2n ** 63n) - 1n;
727
+ }
728
+ return /^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/u.test(value)
729
+ && Number.isFinite(Number(value));
730
+ }
731
+ function skipPlistWhitespace(cursor) {
732
+ while (/[\t\n\r ]/u.test(cursor.contents[cursor.offset] ?? ""))
733
+ cursor.offset += 1;
734
+ }
735
+ function consumePlistToken(cursor, token) {
736
+ if (!cursor.contents.startsWith(token, cursor.offset))
737
+ return false;
738
+ cursor.offset += token.length;
739
+ return true;
740
+ }
741
+ function plistStringValue(value) {
742
+ return value?.kind === "string" ? value.value : undefined;
743
+ }
744
+ function plistStringArray(value) {
745
+ if (value?.kind !== "array")
746
+ return undefined;
747
+ const strings = [];
748
+ for (const item of value.value) {
749
+ if (item.kind !== "string")
452
750
  return undefined;
453
- const key = xmlUnescapeStrict(match[1] ?? "");
454
- const value = xmlUnescapeStrict(match[2] ?? "");
455
- if (key === undefined || value === undefined || Object.hasOwn(values, key))
751
+ strings.push(item.value);
752
+ }
753
+ return strings;
754
+ }
755
+ function plistStringDictionary(value) {
756
+ if (value.kind !== "dictionary")
757
+ return undefined;
758
+ const entries = [];
759
+ for (const [key, item] of value.value) {
760
+ if (item.kind !== "string")
456
761
  return undefined;
457
- values[key] = value;
458
- cursor = match.index + match[0].length;
762
+ entries.push([key, item.value]);
459
763
  }
460
- return contents.slice(cursor).trim() === "" ? values : undefined;
764
+ return Object.fromEntries(entries);
461
765
  }
462
766
  function xmlUnescapeStrict(value) {
463
767
  if (/[<>]/u.test(value) || /&(?!(?:apos|quot|gt|lt|amp);)/u.test(value))
@@ -472,7 +776,7 @@ function xmlUnescapeStrict(value) {
472
776
  function recordsEqual(left, right) {
473
777
  const leftEntries = Object.entries(left);
474
778
  return leftEntries.length === Object.keys(right).length
475
- && leftEntries.every(([key, value]) => right[key] === value);
779
+ && leftEntries.every(([key, value]) => Object.hasOwn(right, key) && right[key] === value);
476
780
  }
477
781
  function impossibleMissingDefinition() {
478
782
  throw new Error("Expected at least one installed native service definition");